Filament v4 🎉 (#1651)

Co-authored-by: RMartinOscar <40749467+RMartinOscar@users.noreply.github.com>
Co-authored-by: Boy132 <Boy132@users.noreply.github.com>
Co-authored-by: Lance Pioch <git@lance.sh>
This commit is contained in:
Charles 2025-09-08 13:12:33 -04:00 committed by GitHub
parent 32eb1abd4a
commit 1900c04b71
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
379 changed files with 5648 additions and 6671 deletions

View File

@ -13,7 +13,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
node-version: [18, 20] node-version: [20, 22]
steps: steps:
- name: Code Checkout - name: Code Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4

1
.gitignore vendored
View File

@ -24,6 +24,5 @@ yarn-error.log
public/assets/manifest.json public/assets/manifest.json
/database/*.sqlite* /database/*.sqlite*
filament-monaco-editor/
_ide_helper* _ide_helper*
/.phpstorm.meta.php /.phpstorm.meta.php

View File

@ -2,6 +2,7 @@
namespace App\Console\Commands\Environment; namespace App\Console\Commands\Environment;
use PDOException;
use App\Traits\EnvironmentWriterTrait; use App\Traits\EnvironmentWriterTrait;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Illuminate\Contracts\Console\Kernel; use Illuminate\Contracts\Console\Kernel;
@ -105,7 +106,7 @@ class DatabaseSettingsCommand extends Command
]); ]);
$this->database->connection('_panel_command_test')->getPdo(); $this->database->connection('_panel_command_test')->getPdo();
} catch (\PDOException $exception) { } catch (PDOException $exception) {
$this->output->error(sprintf('Unable to connect to the MySQL server using the provided credentials. The error returned was "%s".', $exception->getMessage())); $this->output->error(sprintf('Unable to connect to the MySQL server using the provided credentials. The error returned was "%s".', $exception->getMessage()));
$this->output->error(trans('commands.database_settings.DB_error_2')); $this->output->error(trans('commands.database_settings.DB_error_2'));
@ -165,7 +166,7 @@ class DatabaseSettingsCommand extends Command
]); ]);
$this->database->connection('_panel_command_test')->getPdo(); $this->database->connection('_panel_command_test')->getPdo();
} catch (\PDOException $exception) { } catch (PDOException $exception) {
$this->output->error(sprintf('Unable to connect to the MariaDB server using the provided credentials. The error returned was "%s".', $exception->getMessage())); $this->output->error(sprintf('Unable to connect to the MariaDB server using the provided credentials. The error returned was "%s".', $exception->getMessage()));
$this->output->error(trans('commands.database_settings.DB_error_2')); $this->output->error(trans('commands.database_settings.DB_error_2'));

View File

@ -2,6 +2,7 @@
namespace App\Console\Commands\Environment; namespace App\Console\Commands\Environment;
use App\Exceptions\PanelException;
use App\Traits\EnvironmentWriterTrait; use App\Traits\EnvironmentWriterTrait;
use Illuminate\Console\Command; use Illuminate\Console\Command;
@ -28,7 +29,7 @@ class EmailSettingsCommand extends Command
/** /**
* Handle command execution. * Handle command execution.
* *
* @throws \App\Exceptions\PanelException * @throws PanelException
*/ */
public function handle(): void public function handle(): void
{ {

View File

@ -2,6 +2,7 @@
namespace App\Console\Commands\Maintenance; namespace App\Console\Commands\Maintenance;
use InvalidArgumentException;
use App\Models\Backup; use App\Models\Backup;
use Carbon\CarbonImmutable; use Carbon\CarbonImmutable;
use Illuminate\Console\Command; use Illuminate\Console\Command;
@ -16,7 +17,7 @@ class PruneOrphanedBackupsCommand extends Command
{ {
$since = $this->option('prune-age') ?? config('backups.prune_age', 360); $since = $this->option('prune-age') ?? config('backups.prune_age', 360);
if (!$since || !is_digit($since)) { if (!$since || !is_digit($since)) {
throw new \InvalidArgumentException('The "--prune-age" argument must be a value greater than 0.'); throw new InvalidArgumentException('The "--prune-age" argument must be a value greater than 0.');
} }
$query = Backup::query() $query = Backup::query()

View File

@ -2,6 +2,7 @@
namespace App\Console\Commands\Node; namespace App\Console\Commands\Node;
use App\Exceptions\Model\DataValidationException;
use App\Models\Node; use App\Models\Node;
use Illuminate\Console\Command; use Illuminate\Console\Command;
@ -34,7 +35,7 @@ class MakeNodeCommand extends Command
/** /**
* Handle the command execution process. * Handle the command execution process.
* *
* @throws \App\Exceptions\Model\DataValidationException * @throws DataValidationException
*/ */
public function handle(): void public function handle(): void
{ {

View File

@ -17,7 +17,7 @@ class NodeConfigurationCommand extends Command
{ {
$column = ctype_digit((string) $this->argument('node')) ? 'id' : 'uuid'; $column = ctype_digit((string) $this->argument('node')) ? 'id' : 'uuid';
/** @var \App\Models\Node $node */ /** @var Node $node */
$node = Node::query()->where($column, $this->argument('node'))->firstOr(function () { $node = Node::query()->where($column, $this->argument('node'))->firstOr(function () {
$this->error(trans('commands.node_config.error_not_exist')); $this->error(trans('commands.node_config.error_not_exist'));

View File

@ -2,6 +2,9 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use Closure;
use Exception;
use Illuminate\Foundation\Application;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use App\Console\Kernel; use App\Console\Kernel;
use Symfony\Component\Process\Process; use Symfony\Component\Process\Process;
@ -28,7 +31,7 @@ class UpgradeCommand extends Command
* This places the application in maintenance mode as well while the commands * This places the application in maintenance mode as well while the commands
* are being executed. * are being executed.
* *
* @throws \Exception * @throws Exception
*/ */
public function handle(): void public function handle(): void
{ {
@ -129,9 +132,9 @@ class UpgradeCommand extends Command
}); });
}); });
/** @var \Illuminate\Foundation\Application $app */ /** @var Application $app */
$app = require __DIR__ . '/../../../bootstrap/app.php'; $app = require __DIR__ . '/../../../bootstrap/app.php';
/** @var \App\Console\Kernel $kernel */ /** @var Kernel $kernel */
$kernel = $app->make(Kernel::class); $kernel = $app->make(Kernel::class);
$kernel->bootstrap(); $kernel->bootstrap();
$this->setLaravel($app); $this->setLaravel($app);
@ -174,7 +177,7 @@ class UpgradeCommand extends Command
$this->info(trans('commands.upgrade.success')); $this->info(trans('commands.upgrade.success'));
} }
protected function withProgress(ProgressBar $bar, \Closure $callback): void protected function withProgress(ProgressBar $bar, Closure $callback): void
{ {
$bar->clear(); $bar->clear();
$callback(); $callback();

View File

@ -2,6 +2,7 @@
namespace App\Console\Commands\User; namespace App\Console\Commands\User;
use App\Exceptions\Model\DataValidationException;
use App\Models\User; use App\Models\User;
use Illuminate\Console\Command; use Illuminate\Console\Command;
@ -14,7 +15,7 @@ class DisableTwoFactorCommand extends Command
/** /**
* Handle command execution process. * Handle command execution process.
* *
* @throws \App\Exceptions\Model\DataValidationException * @throws DataValidationException
*/ */
public function handle(): void public function handle(): void
{ {
@ -24,10 +25,12 @@ class DisableTwoFactorCommand extends Command
$email = $this->option('email') ?? $this->ask(trans('command/messages.user.ask_email')); $email = $this->option('email') ?? $this->ask(trans('command/messages.user.ask_email'));
$user = User::query()->where('email', $email)->firstOrFail(); $user = User::where('email', $email)->firstOrFail();
$user->use_totp = false; $user->update([
$user->totp_secret = null; 'mfa_app_secret' => null,
$user->save(); 'mfa_app_recovery_codes' => null,
'mfa_email_enabled' => false,
]);
$this->info(trans('command/messages.user.2fa_disabled', ['email' => $user->email])); $this->info(trans('command/messages.user.2fa_disabled', ['email' => $user->email]));
} }

View File

@ -2,6 +2,7 @@
namespace App\Console\Commands\User; namespace App\Console\Commands\User;
use App\Exceptions\Model\DataValidationException;
use Exception; use Exception;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use App\Services\Users\UserCreationService; use App\Services\Users\UserCreationService;
@ -25,7 +26,7 @@ class MakeUserCommand extends Command
* Handle command request to create a new user. * Handle command request to create a new user.
* *
* @throws Exception * @throws Exception
* @throws \App\Exceptions\Model\DataValidationException * @throws DataValidationException
*/ */
public function handle(): int public function handle(): int
{ {

View File

@ -0,0 +1,37 @@
<?php
namespace App\Enums;
enum CustomizationKey: string
{
case ConsoleRows = 'console_rows';
case ConsoleFont = 'console_font';
case ConsoleFontSize = 'console_font_size';
case ConsoleGraphPeriod = 'console_graph_period';
case TopNavigation = 'top_navigation';
case DashboardLayout = 'dashboard_layout';
public function getDefaultValue(): string|int|bool
{
return match ($this) {
self::ConsoleRows => 30,
self::ConsoleFont => 'monospace',
self::ConsoleFontSize => 14,
self::ConsoleGraphPeriod => 30,
self::TopNavigation => false,
self::DashboardLayout => 'grid',
};
}
/** @return array<string, string|int|bool> */
public static function getDefaultCustomization(): array
{
$default = [];
foreach (self::cases() as $key) {
$default[$key->value] = $key->getDefaultValue();
}
return $default;
}
}

View File

@ -1,141 +0,0 @@
<?php
namespace App\Enums;
use Filament\Support\Contracts\HasLabel;
enum EditorLanguages: string implements HasLabel
{
case plaintext = 'plaintext';
case abap = 'abap';
case apex = 'apex';
case azcali = 'azcali';
case bat = 'bat';
case bicep = 'bicep';
case cameligo = 'cameligo';
case coljure = 'coljure';
case coffeescript = 'coffeescript';
case c = 'c';
case cpp = 'cpp';
case csharp = 'csharp';
case csp = 'csp';
case css = 'css';
case cypher = 'cypher';
case dart = 'dart';
case dockerfile = 'dockerfile';
case ecl = 'ecl';
case elixir = 'elixir';
case flow9 = 'flow9';
case fsharp = 'fsharp';
case go = 'go';
case graphql = 'graphql';
case handlebars = 'handlebars';
case hcl = 'hcl';
case html = 'html';
case ini = 'ini';
case java = 'java';
case javascript = 'javascript';
case julia = 'julia';
case json = 'json';
case kotlin = 'kotlin';
case less = 'less';
case lexon = 'lexon';
case lua = 'lua';
case liquid = 'liquid';
case m3 = 'm3';
case markdown = 'markdown';
case mdx = 'mdx';
case mips = 'mips';
case msdax = 'msdax';
case mysql = 'mysql';
case objectivec = 'objective-c';
case pascal = 'pascal';
case pascaligo = 'pascaligo';
case perl = 'perl';
case pgsql = 'pgsql';
case php = 'php';
case pla = 'pla';
case postiats = 'postiats';
case powerquery = 'powerquery';
case powershell = 'powershell';
case proto = 'proto';
case pug = 'pug';
case python = 'python';
case qsharp = 'qsharp';
case r = 'r';
case razor = 'razor';
case redis = 'redis';
case redshift = 'redshift';
case restructuredtext = 'restructuredtext';
case ruby = 'ruby';
case rust = 'rust';
case sb = 'sb';
case scala = 'scala';
case scheme = 'scheme';
case scss = 'scss';
case shell = 'shell';
case sol = 'sol';
case aes = 'aes';
case sparql = 'sparql';
case sql = 'sql';
case st = 'st';
case swift = 'swift';
case systemverilog = 'systemverilog';
case verilog = 'verilog';
case tcl = 'tcl';
case twig = 'twig';
case typescript = 'typescript';
case typespec = 'typespec';
case vb = 'vb';
case wgsl = 'wgsl';
case xml = 'xml';
case yaml = 'yaml';
public static function fromWithAlias(string $match): self
{
return match ($match) {
'h' => self::c,
'cc', 'hpp' => self::cpp,
'cs' => self::csharp,
'class' => self::java,
'htm' => self::html,
'js', 'mjs', 'cjs' => self::javascript,
'kt', 'kts' => self::kotlin,
'md' => self::markdown,
'm' => self::objectivec,
'pl', 'pm' => self::perl,
'php3', 'php4', 'php5', 'phtml' => self::php,
'py', 'pyc', 'pyo', 'pyi' => self::python,
'rdata', 'rds' => self::r,
'rb', 'erb' => self::ruby,
'sc' => self::scala,
'sh', 'zsh' => self::shell,
'ts', 'tsx' => self::typescript,
'yml' => self::yaml,
default => self::tryFrom($match) ?? self::plaintext,
};
}
public function getLabel(): string
{
return $this->name;
}
}

View File

@ -5,6 +5,7 @@ namespace App\Enums;
enum StartupVariableType: string enum StartupVariableType: string
{ {
case Text = 'text'; case Text = 'text';
case Number = 'number';
case Select = 'select'; case Select = 'select';
case Toggle = 'toggle'; // TODO: add toggle to blade view case Toggle = 'toggle';
} }

View File

@ -2,6 +2,7 @@
namespace App\Exceptions; namespace App\Exceptions;
use Throwable;
use Exception; use Exception;
use Filament\Notifications\Notification; use Filament\Notifications\Notification;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
@ -28,7 +29,7 @@ class DisplayException extends PanelException implements HttpExceptionInterface
/** /**
* DisplayException constructor. * DisplayException constructor.
*/ */
public function __construct(string $message, ?\Throwable $previous = null, protected string $level = self::LEVEL_ERROR, int $code = 0) public function __construct(string $message, ?Throwable $previous = null, protected string $level = self::LEVEL_ERROR, int $code = 0)
{ {
parent::__construct($message, $code, $previous); parent::__construct($message, $code, $previous);
} }
@ -79,11 +80,11 @@ class DisplayException extends PanelException implements HttpExceptionInterface
* Log the exception to the logs using the defined error level only if the previous * Log the exception to the logs using the defined error level only if the previous
* exception is set. * exception is set.
* *
* @throws \Throwable * @throws Throwable
*/ */
public function report(): void public function report(): void
{ {
if (!$this->getPrevious() instanceof \Exception || !Handler::isReportable($this->getPrevious())) { if (!$this->getPrevious() instanceof Exception || !Handler::isReportable($this->getPrevious())) {
return; return;
} }

View File

@ -2,6 +2,9 @@
namespace App\Exceptions; namespace App\Exceptions;
use PDOException;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
@ -79,7 +82,7 @@ class Handler extends ExceptionHandler
$this->dontReport = []; $this->dontReport = [];
} }
$this->reportable(function (\PDOException $ex) { $this->reportable(function (PDOException $ex) {
$ex = $this->generateCleanedExceptionStack($ex); $ex = $this->generateCleanedExceptionStack($ex);
}); });
@ -88,7 +91,7 @@ class Handler extends ExceptionHandler
}); });
} }
private function generateCleanedExceptionStack(\Throwable $exception): string private function generateCleanedExceptionStack(Throwable $exception): string
{ {
$cleanedStack = ''; $cleanedStack = '';
foreach ($exception->getTrace() as $index => $item) { foreach ($exception->getTrace() as $index => $item) {
@ -117,11 +120,11 @@ class Handler extends ExceptionHandler
/** /**
* Render an exception into an HTTP response. * Render an exception into an HTTP response.
* *
* @param \Illuminate\Http\Request $request * @param Request $request
* *
* @throws \Throwable * @throws Throwable
*/ */
public function render($request, \Throwable $e): Response public function render($request, Throwable $e): Response
{ {
$connections = $this->container->make(Connection::class); $connections = $this->container->make(Connection::class);
@ -143,7 +146,7 @@ class Handler extends ExceptionHandler
* Transform a validation exception into a consistent format to be returned for * Transform a validation exception into a consistent format to be returned for
* calls to the API. * calls to the API.
* *
* @param \Illuminate\Http\Request $request * @param Request $request
*/ */
public function invalidJson($request, ValidationException $exception): JsonResponse public function invalidJson($request, ValidationException $exception): JsonResponse
{ {
@ -249,7 +252,7 @@ class Handler extends ExceptionHandler
/** /**
* Return an array of exceptions that should not be reported. * Return an array of exceptions that should not be reported.
*/ */
public static function isReportable(\Exception $exception): bool public static function isReportable(Exception $exception): bool
{ {
return (new self(Container::getInstance()))->shouldReport($exception); return (new self(Container::getInstance()))->shouldReport($exception);
} }
@ -257,7 +260,7 @@ class Handler extends ExceptionHandler
/** /**
* Convert an authentication exception into an unauthenticated response. * Convert an authentication exception into an unauthenticated response.
* *
* @param \Illuminate\Http\Request $request * @param Request $request
*/ */
protected function unauthenticated($request, AuthenticationException $exception): JsonResponse|RedirectResponse protected function unauthenticated($request, AuthenticationException $exception): JsonResponse|RedirectResponse
{ {
@ -291,7 +294,7 @@ class Handler extends ExceptionHandler
* *
* @return array<mixed> * @return array<mixed>
*/ */
public static function toArray(\Throwable $e): array public static function toArray(Throwable $e): array
{ {
return self::exceptionToArray($e); return self::exceptionToArray($e);
} }

View File

@ -2,6 +2,7 @@
namespace App\Exceptions\Http; namespace App\Exceptions\Http;
use Throwable;
use Illuminate\Http\Response; use Illuminate\Http\Response;
use Symfony\Component\HttpKernel\Exception\HttpException; use Symfony\Component\HttpKernel\Exception\HttpException;
@ -10,7 +11,7 @@ class HttpForbiddenException extends HttpException
/** /**
* HttpForbiddenException constructor. * HttpForbiddenException constructor.
*/ */
public function __construct(?string $message = null, ?\Throwable $previous = null) public function __construct(?string $message = null, ?Throwable $previous = null)
{ {
parent::__construct(Response::HTTP_FORBIDDEN, $message, $previous); parent::__construct(Response::HTTP_FORBIDDEN, $message, $previous);
} }

View File

@ -2,6 +2,7 @@
namespace App\Exceptions\Http\Server; namespace App\Exceptions\Http\Server;
use Throwable;
use App\Enums\ServerState; use App\Enums\ServerState;
use App\Models\Server; use App\Models\Server;
use Symfony\Component\HttpKernel\Exception\ConflictHttpException; use Symfony\Component\HttpKernel\Exception\ConflictHttpException;
@ -12,7 +13,7 @@ class ServerStateConflictException extends ConflictHttpException
* Exception thrown when the server is in an unsupported state for API access or * Exception thrown when the server is in an unsupported state for API access or
* certain operations within the codebase. * certain operations within the codebase.
*/ */
public function __construct(Server $server, ?\Throwable $previous = null) public function __construct(Server $server, ?Throwable $previous = null)
{ {
$message = 'This server is currently in an unsupported state, please try again later.'; $message = 'This server is currently in an unsupported state, please try again later.';
if ($server->isSuspended()) { if ($server->isSuspended()) {

View File

@ -1,18 +0,0 @@
<?php
namespace App\Exceptions\Http;
use Illuminate\Http\Response;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
class TwoFactorAuthRequiredException extends HttpException implements HttpExceptionInterface
{
/**
* TwoFactorAuthRequiredException constructor.
*/
public function __construct(?\Throwable $previous = null)
{
parent::__construct(Response::HTTP_BAD_REQUEST, 'Two-factor authentication is required on this account in order to access this endpoint.', $previous);
}
}

View File

@ -2,13 +2,15 @@
namespace App\Exceptions; namespace App\Exceptions;
use Exception;
use App\Exceptions\Solutions\ManifestDoesNotExistSolution;
use Spatie\Ignition\Contracts\Solution; use Spatie\Ignition\Contracts\Solution;
use Spatie\Ignition\Contracts\ProvidesSolution; use Spatie\Ignition\Contracts\ProvidesSolution;
class ManifestDoesNotExistException extends \Exception implements ProvidesSolution class ManifestDoesNotExistException extends Exception implements ProvidesSolution
{ {
public function getSolution(): Solution public function getSolution(): Solution
{ {
return new Solutions\ManifestDoesNotExistSolution(); return new ManifestDoesNotExistSolution();
} }
} }

View File

@ -2,4 +2,6 @@
namespace App\Exceptions; namespace App\Exceptions;
class PanelException extends \Exception {} use Exception;
class PanelException extends Exception {}

View File

@ -2,6 +2,7 @@
namespace App\Exceptions\Service; namespace App\Exceptions\Service;
use Throwable;
use App\Exceptions\DisplayException; use App\Exceptions\DisplayException;
class ServiceLimitExceededException extends DisplayException class ServiceLimitExceededException extends DisplayException
@ -10,7 +11,7 @@ class ServiceLimitExceededException extends DisplayException
* Exception thrown when something goes over a defined limit, such as allocated * Exception thrown when something goes over a defined limit, such as allocated
* ports, tasks, databases, etc. * ports, tasks, databases, etc.
*/ */
public function __construct(string $message, ?\Throwable $previous = null) public function __construct(string $message, ?Throwable $previous = null)
{ {
parent::__construct($message, $previous, self::LEVEL_WARNING); parent::__construct($message, $previous, self::LEVEL_WARNING);
} }

View File

@ -1,17 +0,0 @@
<?php
namespace App\Exceptions\Service\User;
use App\Exceptions\DisplayException;
class TwoFactorAuthenticationTokenInvalid extends DisplayException
{
public string $title = 'Invalid 2FA Code';
public string $icon = 'tabler-2fa';
public function __construct()
{
parent::__construct('The provided two-factor authentication token was not valid.');
}
}

View File

@ -2,6 +2,7 @@
namespace App\Extensions\Backups; namespace App\Extensions\Backups;
use InvalidArgumentException;
use Closure; use Closure;
use Aws\S3\S3Client; use Aws\S3\S3Client;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
@ -64,7 +65,7 @@ class BackupManager
$config = $this->getConfig($name); $config = $this->getConfig($name);
if (empty($config['adapter'])) { if (empty($config['adapter'])) {
throw new \InvalidArgumentException("Backup disk [$name] does not have a configured adapter."); throw new InvalidArgumentException("Backup disk [$name] does not have a configured adapter.");
} }
$adapter = $config['adapter']; $adapter = $config['adapter'];
@ -82,7 +83,7 @@ class BackupManager
return $instance; return $instance;
} }
throw new \InvalidArgumentException("Adapter [$adapter] is not supported."); throw new InvalidArgumentException("Adapter [$adapter] is not supported.");
} }
/** /**

View File

@ -2,7 +2,7 @@
namespace App\Extensions\Captcha\Schemas; namespace App\Extensions\Captcha\Schemas;
use Filament\Forms\Components\Component; use Filament\Schemas\Components\Component;
use Filament\Forms\Components\TextInput; use Filament\Forms\Components\TextInput;
use Illuminate\Support\Str; use Illuminate\Support\Str;

View File

@ -2,7 +2,7 @@
namespace App\Extensions\Captcha\Schemas; namespace App\Extensions\Captcha\Schemas;
use Filament\Forms\Components\Component; use Filament\Schemas\Components\Component;
interface CaptchaSchemaInterface interface CaptchaSchemaInterface
{ {

View File

@ -5,8 +5,7 @@ namespace App\Extensions\Captcha\Schemas\Turnstile;
use App\Extensions\Captcha\Schemas\CaptchaSchemaInterface; use App\Extensions\Captcha\Schemas\CaptchaSchemaInterface;
use App\Extensions\Captcha\Schemas\BaseSchema; use App\Extensions\Captcha\Schemas\BaseSchema;
use Exception; use Exception;
use Filament\Forms\Components\Component as BaseComponent; use Filament\Infolists\Components\TextEntry;
use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\Toggle; use Filament\Forms\Components\Toggle;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Http;
use Illuminate\Support\HtmlString; use Illuminate\Support\HtmlString;
@ -23,7 +22,7 @@ class TurnstileSchema extends BaseSchema implements CaptchaSchemaInterface
return env('CAPTCHA_TURNSTILE_ENABLED', false); return env('CAPTCHA_TURNSTILE_ENABLED', false);
} }
public function getFormComponent(): BaseComponent public function getFormComponent(): Component
{ {
return Component::make('turnstile'); return Component::make('turnstile');
} }
@ -39,7 +38,9 @@ class TurnstileSchema extends BaseSchema implements CaptchaSchemaInterface
} }
/** /**
* @return BaseComponent[] * @return \Filament\Support\Components\Component[]
*
* @throws Exception
*/ */
public function getSettingsForm(): array public function getSettingsForm(): array
{ {
@ -53,10 +54,10 @@ class TurnstileSchema extends BaseSchema implements CaptchaSchemaInterface
->onColor('success') ->onColor('success')
->offColor('danger') ->offColor('danger')
->default(env('CAPTCHA_TURNSTILE_VERIFY_DOMAIN', true)), ->default(env('CAPTCHA_TURNSTILE_VERIFY_DOMAIN', true)),
Placeholder::make('info') TextEntry::make('info')
->label(trans('admin/setting.captcha.info_label')) ->label(trans('admin/setting.captcha.info_label'))
->columnSpan(2) ->columnSpan(2)
->content(new HtmlString(trans('admin/setting.captcha.info'))), ->state(new HtmlString(trans('admin/setting.captcha.info'))),
]); ]);
} }

View File

@ -12,8 +12,8 @@ use Closure;
use Exception; use Exception;
use Filament\Actions\Action; use Filament\Actions\Action;
use Filament\Facades\Filament; use Filament\Facades\Filament;
use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\TextInput; use Filament\Forms\Components\TextInput;
use Filament\Infolists\Components\TextEntry;
use Filament\Notifications\Notification; use Filament\Notifications\Notification;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Blade; use Illuminate\Support\Facades\Blade;
@ -36,6 +36,9 @@ class GSLTokenSchema implements FeatureSchemaInterface
return 'gsl_token'; return 'gsl_token';
} }
/**
* @throws Exception
*/
public function getAction(): Action public function getAction(): Action
{ {
/** @var Server $server */ /** @var Server $server */
@ -51,9 +54,9 @@ class GSLTokenSchema implements FeatureSchemaInterface
->modalHeading('Invalid GSL token') ->modalHeading('Invalid GSL token')
->modalDescription('It seems like your Gameserver Login Token (GSL token) is invalid or has expired.') ->modalDescription('It seems like your Gameserver Login Token (GSL token) is invalid or has expired.')
->modalSubmitActionLabel('Update GSL Token') ->modalSubmitActionLabel('Update GSL Token')
->disabledForm(fn () => !auth()->user()->can(Permission::ACTION_STARTUP_UPDATE, $server)) ->disabledSchema(fn () => !auth()->user()->can(Permission::ACTION_STARTUP_UPDATE, $server))
->form([ ->schema([
Placeholder::make('info') TextEntry::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.'))), ->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') TextInput::make('gsltoken')
->label('GSL Token') ->label('GSL Token')

View File

@ -10,8 +10,8 @@ use App\Repositories\Daemon\DaemonServerRepository;
use Exception; use Exception;
use Filament\Actions\Action; use Filament\Actions\Action;
use Filament\Facades\Filament; use Filament\Facades\Filament;
use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\Select; use Filament\Forms\Components\Select;
use Filament\Infolists\Components\TextEntry;
use Filament\Notifications\Notification; use Filament\Notifications\Notification;
class JavaVersionSchema implements FeatureSchemaInterface class JavaVersionSchema implements FeatureSchemaInterface
@ -44,9 +44,9 @@ class JavaVersionSchema implements FeatureSchemaInterface
->modalHeading('Unsupported Java Version') ->modalHeading('Unsupported Java Version')
->modalDescription('This server is currently running an unsupported version of Java and cannot be started.') ->modalDescription('This server is currently running an unsupported version of Java and cannot be started.')
->modalSubmitActionLabel('Update Docker Image') ->modalSubmitActionLabel('Update Docker Image')
->disabledForm(fn () => !auth()->user()->can(Permission::ACTION_STARTUP_DOCKER_IMAGE, $server)) ->disabledSchema(fn () => !auth()->user()->can(Permission::ACTION_STARTUP_DOCKER_IMAGE, $server))
->form([ ->schema([
Placeholder::make('java') TextEntry::make('java')
->label('Please select a supported version from the list below to continue starting the server.'), ->label('Please select a supported version from the list below to continue starting the server.'),
Select::make('image') Select::make('image')
->label('Docker Image') ->label('Docker Image')

View File

@ -6,7 +6,7 @@ use App\Models\ApiKey;
use Laravel\Sanctum\NewAccessToken as SanctumAccessToken; use Laravel\Sanctum\NewAccessToken as SanctumAccessToken;
/** /**
* @property \App\Models\ApiKey $accessToken * @property ApiKey $accessToken
*/ */
class NewAccessToken extends SanctumAccessToken class NewAccessToken extends SanctumAccessToken
{ {

View File

@ -2,6 +2,7 @@
namespace App\Extensions\Lcobucci\JWT\Encoding; namespace App\Extensions\Lcobucci\JWT\Encoding;
use DateTimeImmutable;
use Lcobucci\JWT\ClaimsFormatter; use Lcobucci\JWT\ClaimsFormatter;
use Lcobucci\JWT\Token\RegisteredClaims; use Lcobucci\JWT\Token\RegisteredClaims;
@ -20,7 +21,7 @@ final class TimestampDates implements ClaimsFormatter
continue; continue;
} }
assert($claims[$claim] instanceof \DateTimeImmutable); assert($claims[$claim] instanceof DateTimeImmutable);
$claims[$claim] = $claims[$claim]->getTimestamp(); $claims[$claim] = $claims[$claim]->getTimestamp();
} }

View File

@ -2,8 +2,8 @@
namespace App\Extensions\OAuth; namespace App\Extensions\OAuth;
use Filament\Forms\Components\Component; use Filament\Schemas\Components\Component;
use Filament\Forms\Components\Wizard\Step; use Filament\Schemas\Components\Wizard\Step;
interface OAuthSchemaInterface interface OAuthSchemaInterface
{ {

View File

@ -2,13 +2,12 @@
namespace App\Extensions\OAuth\Schemas; namespace App\Extensions\OAuth\Schemas;
use Filament\Forms\Components\Placeholder; use Filament\Schemas\Components\Wizard\Step;
use Filament\Infolists\Components\TextEntry;
use Filament\Forms\Components\TextInput; use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Wizard\Step;
use Illuminate\Support\Facades\Blade; use Illuminate\Support\Facades\Blade;
use Illuminate\Support\HtmlString; use Illuminate\Support\HtmlString;
use SocialiteProviders\Discord\Provider; use SocialiteProviders\Discord\Provider;
use Webbingbrasil\FilamentCopyActions\Forms\Actions\CopyAction;
final class DiscordSchema extends OAuthSchema final class DiscordSchema extends OAuthSchema
{ {
@ -27,15 +26,17 @@ final class DiscordSchema extends OAuthSchema
return array_merge([ return array_merge([
Step::make('Register new Discord OAuth App') Step::make('Register new Discord OAuth App')
->schema([ ->schema([
Placeholder::make('') TextEntry::make('create_application')
->content(new HtmlString(Blade::render('<p>Visit the <x-filament::link href="https://discord.com/developers/applications" target="_blank">Discord Developer Portal</x-filament::link> and click on <b>New Application</b>. Enter a <b>Name</b> (e.g. your panel name) and click on <b>Create</b>.</p><p>Copy the <b>Client ID</b> and the <b>Client Secret</b> from the OAuth2 tab, you will need them in the final step.</p>'))), ->hiddenLabel()
Placeholder::make('') ->state(new HtmlString(Blade::render('<p>Visit the <x-filament::link href="https://discord.com/developers/applications" target="_blank">Discord Developer Portal</x-filament::link> and click on <b>New Application</b>. Enter a <b>Name</b> (e.g. your panel name) and click on <b>Create</b>.</p><p>Copy the <b>Client ID</b> and the <b>Client Secret</b> from the OAuth2 tab, you will need them in the final step.</p>'))),
->content(new HtmlString('<p>Under <b>Redirects</b> add the below URL.</p>')), TextEntry::make('set_redirect')
->hiddenLabel()
->state(new HtmlString('<p>Under <b>Redirects</b> add the below URL.</p>')),
TextInput::make('_noenv_callback') TextInput::make('_noenv_callback')
->label('Redirect URL') ->label('Redirect URL')
->dehydrated() ->dehydrated()
->disabled() ->disabled()
->hintAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null) ->hintCopy()
->formatStateUsing(fn () => url('/auth/oauth/callback/discord')), ->formatStateUsing(fn () => url('/auth/oauth/callback/discord')),
]), ]),
], parent::getSetupSteps()); ], parent::getSetupSteps());

View File

@ -2,12 +2,11 @@
namespace App\Extensions\OAuth\Schemas; namespace App\Extensions\OAuth\Schemas;
use Filament\Forms\Components\Placeholder; use Filament\Schemas\Components\Wizard\Step;
use Filament\Infolists\Components\TextEntry;
use Filament\Forms\Components\TextInput; use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Wizard\Step;
use Illuminate\Support\Facades\Blade; use Illuminate\Support\Facades\Blade;
use Illuminate\Support\HtmlString; use Illuminate\Support\HtmlString;
use Webbingbrasil\FilamentCopyActions\Forms\Actions\CopyAction;
final class GithubSchema extends OAuthSchema final class GithubSchema extends OAuthSchema
{ {
@ -21,21 +20,24 @@ final class GithubSchema extends OAuthSchema
return array_merge([ return array_merge([
Step::make('Register new Github OAuth App') Step::make('Register new Github OAuth App')
->schema([ ->schema([
Placeholder::make('') TextEntry::make('create_application')
->content(new HtmlString(Blade::render('<p>Visit the <x-filament::link href="https://github.com/settings/developers" target="_blank">Github Developer Dashboard</x-filament::link>, go to <b>OAuth Apps</b> and click on <b>New OAuth App</b>.</p><p>Enter an <b>Application name</b> (e.g. your panel name), set <b>Homepage URL</b> to your panel url and enter the below url as <b>Authorization callback URL</b>.</p>'))), ->hiddenLabel()
->state(new HtmlString(Blade::render('<p>Visit the <x-filament::link href="https://github.com/settings/developers" target="_blank">Github Developer Dashboard</x-filament::link>, go to <b>OAuth Apps</b> and click on <b>New OAuth App</b>.</p><p>Enter an <b>Application name</b> (e.g. your panel name), set <b>Homepage URL</b> to your panel url and enter the below url as <b>Authorization callback URL</b>.</p>'))),
TextInput::make('_noenv_callback') TextInput::make('_noenv_callback')
->label('Authorization callback URL') ->label('Authorization callback URL')
->dehydrated() ->dehydrated()
->disabled() ->disabled()
->hintAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null) ->hintCopy()
->default(fn () => url('/auth/oauth/callback/github')), ->default(fn () => url('/auth/oauth/callback/github')),
Placeholder::make('') TextEntry::make('register_application')
->content(new HtmlString('<p>When you filled all fields click on <b>Register application</b>.</p>')), ->hiddenLabel()
->state(new HtmlString('<p>When you filled all fields click on <b>Register application</b>.</p>')),
]), ]),
Step::make('Create Client Secret') Step::make('Create Client Secret')
->schema([ ->schema([
Placeholder::make('') TextEntry::make('create_client_secret')
->content(new HtmlString('<p>Once you registered your app, generate a new <b>Client Secret</b>.</p><p>You will also need the <b>Client ID</b>.</p>')), ->hiddenLabel()
->state(new HtmlString('<p>Once you registered your app, generate a new <b>Client Secret</b>.</p><p>You will also need the <b>Client ID</b>.</p>')),
]), ]),
], parent::getSetupSteps()); ], parent::getSetupSteps());
} }

View File

@ -2,12 +2,11 @@
namespace App\Extensions\OAuth\Schemas; namespace App\Extensions\OAuth\Schemas;
use Filament\Forms\Components\Placeholder; use Filament\Schemas\Components\Wizard\Step;
use Filament\Infolists\Components\TextEntry;
use Filament\Forms\Components\TextInput; use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Wizard\Step;
use Illuminate\Support\Facades\Blade; use Illuminate\Support\Facades\Blade;
use Illuminate\Support\HtmlString; use Illuminate\Support\HtmlString;
use Webbingbrasil\FilamentCopyActions\Forms\Actions\CopyAction;
final class GitlabSchema extends OAuthSchema final class GitlabSchema extends OAuthSchema
{ {
@ -41,13 +40,14 @@ final class GitlabSchema extends OAuthSchema
return array_merge([ return array_merge([
Step::make('Register new Gitlab OAuth App') Step::make('Register new Gitlab OAuth App')
->schema([ ->schema([
Placeholder::make('') TextEntry::make('register_application')
->content(new HtmlString(Blade::render('Check out the <x-filament::link href="https://docs.gitlab.com/integration/oauth_provider/" target="_blank">Gitlab docs</x-filament::link> on how to create the oauth app.'))), ->hiddenLabel()
->state(new HtmlString(Blade::render('Check out the <x-filament::link href="https://docs.gitlab.com/integration/oauth_provider/" target="_blank">Gitlab docs</x-filament::link> on how to create the oauth app.'))),
TextInput::make('_noenv_callback') TextInput::make('_noenv_callback')
->label('Redirect URI') ->label('Redirect URI')
->dehydrated() ->dehydrated()
->disabled() ->disabled()
->hintAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null) ->hintCopy()
->default(fn () => url('/auth/oauth/callback/gitlab')), ->default(fn () => url('/auth/oauth/callback/gitlab')),
]), ]),
], parent::getSetupSteps()); ], parent::getSetupSteps());

View File

@ -3,11 +3,11 @@
namespace App\Extensions\OAuth\Schemas; namespace App\Extensions\OAuth\Schemas;
use App\Extensions\OAuth\OAuthSchemaInterface; use App\Extensions\OAuth\OAuthSchemaInterface;
use Filament\Forms\Components\Component;
use Filament\Forms\Components\TextInput; use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle; use Filament\Forms\Components\Toggle;
use Filament\Forms\Components\Wizard\Step; use Filament\Schemas\Components\Utilities\Set;
use Filament\Forms\Set; use Filament\Schemas\Components\Wizard\Step;
use Filament\Schemas\Components\Component;
use Illuminate\Support\Str; use Illuminate\Support\Str;
abstract class OAuthSchema implements OAuthSchemaInterface abstract class OAuthSchema implements OAuthSchemaInterface
@ -57,7 +57,7 @@ abstract class OAuthSchema implements OAuthSchemaInterface
->default(env("OAUTH_{$id}_CLIENT_SECRET")), ->default(env("OAUTH_{$id}_CLIENT_SECRET")),
Toggle::make("OAUTH_{$id}_SHOULD_CREATE_MISSING_USERS") Toggle::make("OAUTH_{$id}_SHOULD_CREATE_MISSING_USERS")
->label(trans('admin/setting.oauth.create_missing_users')) ->label(trans('admin/setting.oauth.create_missing_users'))
->columnSpanFull() ->columnSpan(2)
->inline(false) ->inline(false)
->onIcon('tabler-check') ->onIcon('tabler-check')
->offIcon('tabler-x') ->offIcon('tabler-x')
@ -68,7 +68,7 @@ abstract class OAuthSchema implements OAuthSchemaInterface
->default(env("OAUTH_{$id}_SHOULD_CREATE_MISSING_USERS")), ->default(env("OAUTH_{$id}_SHOULD_CREATE_MISSING_USERS")),
Toggle::make("OAUTH_{$id}_SHOULD_LINK_MISSING_USERS") Toggle::make("OAUTH_{$id}_SHOULD_LINK_MISSING_USERS")
->label(trans('admin/setting.oauth.link_missing_users')) ->label(trans('admin/setting.oauth.link_missing_users'))
->columnSpanFull() ->columnSpan(2)
->inline(false) ->inline(false)
->onIcon('tabler-check') ->onIcon('tabler-check')
->offIcon('tabler-x') ->offIcon('tabler-x')

View File

@ -2,9 +2,9 @@
namespace App\Extensions\OAuth\Schemas; namespace App\Extensions\OAuth\Schemas;
use Filament\Forms\Components\Placeholder; use Filament\Schemas\Components\Wizard\Step;
use Filament\Infolists\Components\TextEntry;
use Filament\Forms\Components\TextInput; use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Wizard\Step;
use Illuminate\Support\Facades\Blade; use Illuminate\Support\Facades\Blade;
use Illuminate\Support\HtmlString; use Illuminate\Support\HtmlString;
use SocialiteProviders\Steam\Provider; use SocialiteProviders\Steam\Provider;
@ -52,8 +52,9 @@ final class SteamSchema extends OAuthSchema
return array_merge([ return array_merge([
Step::make('Create API Key') Step::make('Create API Key')
->schema([ ->schema([
Placeholder::make('') TextEntry::make('create_api_key')
->content(new HtmlString(Blade::render('Visit <x-filament::link href="https://steamcommunity.com/dev/apikey" target="_blank">https://steamcommunity.com/dev/apikey</x-filament::link> to generate an API key.'))), ->hiddenLabel()
->state(new HtmlString(Blade::render('Visit <x-filament::link href="https://steamcommunity.com/dev/apikey" target="_blank">https://steamcommunity.com/dev/apikey</x-filament::link> to generate an API key.'))),
]), ]),
], parent::getSetupSteps()); ], parent::getSetupSteps());
} }

View File

@ -2,6 +2,8 @@
namespace App\Extensions\Spatie\Fractalistic; namespace App\Extensions\Spatie\Fractalistic;
use Spatie\Fractalistic\Exceptions\InvalidTransformation;
use Spatie\Fractalistic\Exceptions\NoTransformerSpecified;
use League\Fractal\Scope; use League\Fractal\Scope;
use League\Fractal\TransformerAbstract; use League\Fractal\TransformerAbstract;
use Spatie\Fractal\Fractal as SpatieFractal; use Spatie\Fractal\Fractal as SpatieFractal;
@ -14,8 +16,8 @@ class Fractal extends SpatieFractal
/** /**
* Create fractal data. * Create fractal data.
* *
* @throws \Spatie\Fractalistic\Exceptions\InvalidTransformation * @throws InvalidTransformation
* @throws \Spatie\Fractalistic\Exceptions\NoTransformerSpecified * @throws NoTransformerSpecified
*/ */
public function createData(): Scope public function createData(): Scope
{ {

View File

@ -7,7 +7,7 @@ use Filament\Pages\Dashboard as BaseDashboard;
class Dashboard extends BaseDashboard class Dashboard extends BaseDashboard
{ {
protected static ?string $navigationIcon = 'tabler-layout-dashboard'; protected static string|\BackedEnum|null $navigationIcon = 'tabler-layout-dashboard';
private SoftwareVersionService $softwareVersionService; private SoftwareVersionService $softwareVersionService;
@ -16,7 +16,7 @@ class Dashboard extends BaseDashboard
$this->softwareVersionService = $softwareVersionService; $this->softwareVersionService = $softwareVersionService;
} }
public function getColumns(): int public function getColumns(): int|array
{ {
return 1; return 1;
} }

View File

@ -13,9 +13,9 @@ use Spatie\Health\ResultStores\ResultStore;
class Health extends Page class Health extends Page
{ {
protected static ?string $navigationIcon = 'tabler-heart'; protected static string|\BackedEnum|null $navigationIcon = 'tabler-heart';
protected static string $view = 'filament.pages.health'; protected string $view = 'filament.pages.health';
/** @var array<string, string> */ /** @var array<string, string> */
protected $listeners = [ protected $listeners = [

View File

@ -10,42 +10,43 @@ use App\Notifications\MailTested;
use App\Traits\EnvironmentWriterTrait; use App\Traits\EnvironmentWriterTrait;
use App\Traits\Filament\CanCustomizeHeaderActions; use App\Traits\Filament\CanCustomizeHeaderActions;
use App\Traits\Filament\CanCustomizeHeaderWidgets; use App\Traits\Filament\CanCustomizeHeaderWidgets;
use BackedEnum;
use Exception; use Exception;
use Filament\Actions\Action; use Filament\Actions\Action;
use Filament\Actions\ActionGroup; use Filament\Actions\ActionGroup;
use Filament\Forms\Components\Actions;
use Filament\Forms\Components\Actions\Action as FormAction;
use Filament\Forms\Components\Component;
use Filament\Forms\Components\FileUpload; use Filament\Forms\Components\FileUpload;
use Filament\Forms\Components\Group;
use Filament\Forms\Components\Hidden; use Filament\Forms\Components\Hidden;
use Filament\Forms\Components\Section;
use Filament\Forms\Components\Select; use Filament\Forms\Components\Select;
use Filament\Forms\Components\Tabs;
use Filament\Forms\Components\Tabs\Tab;
use Filament\Forms\Components\TagsInput; use Filament\Forms\Components\TagsInput;
use Filament\Forms\Components\TextInput; use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle; use Filament\Forms\Components\Toggle;
use Filament\Forms\Components\ToggleButtons; use Filament\Forms\Components\ToggleButtons;
use Filament\Forms\Concerns\InteractsWithForms; use Filament\Forms\Concerns\InteractsWithForms;
use Filament\Forms\Contracts\HasForms;
use Filament\Forms\Form;
use Filament\Forms\Get;
use Filament\Forms\Set;
use Filament\Notifications\Notification; use Filament\Notifications\Notification;
use Filament\Pages\Concerns\InteractsWithHeaderActions; use Filament\Pages\Concerns\InteractsWithHeaderActions;
use Filament\Pages\Page; use Filament\Pages\Page;
use Filament\Support\Enums\MaxWidth; use Filament\Schemas\Components\Actions;
use Filament\Schemas\Components\Component;
use Filament\Schemas\Components\Group;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Components\StateCasts\BooleanStateCast;
use Filament\Schemas\Components\Tabs;
use Filament\Schemas\Components\Tabs\Tab;
use Filament\Schemas\Components\Utilities\Get;
use Filament\Schemas\Components\Utilities\Set;
use Filament\Support\Enums\Width;
use Illuminate\Http\Client\Factory; use Illuminate\Http\Client\Factory;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Notification as MailNotification; use Illuminate\Support\Facades\Notification as MailNotification;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Filament\Schemas\Contracts\HasSchemas;
use Filament\Schemas\Schema;
/** /**
* @property Form $form * @property Schema $form
*/ */
class Settings extends Page implements HasForms class Settings extends Page implements HasSchemas
{ {
use CanCustomizeHeaderActions, InteractsWithHeaderActions { use CanCustomizeHeaderActions, InteractsWithHeaderActions {
CanCustomizeHeaderActions::getHeaderActions insteadof InteractsWithHeaderActions; CanCustomizeHeaderActions::getHeaderActions insteadof InteractsWithHeaderActions;
@ -54,9 +55,9 @@ class Settings extends Page implements HasForms
use EnvironmentWriterTrait; use EnvironmentWriterTrait;
use InteractsWithForms; use InteractsWithForms;
protected static ?string $navigationIcon = 'tabler-settings'; protected static string|\BackedEnum|null $navigationIcon = 'tabler-settings';
protected static string $view = 'filament.pages.settings'; protected string $view = 'filament.pages.settings';
protected OAuthService $oauthService; protected OAuthService $oauthService;
@ -94,6 +95,11 @@ class Settings extends Page implements HasForms
return trans('admin/setting.title'); return trans('admin/setting.title');
} }
/**
* @return array<Component>
*
* @throws Exception
*/
protected function getFormSchema(): array protected function getFormSchema(): array
{ {
return [ return [
@ -110,7 +116,7 @@ class Settings extends Page implements HasForms
->label(trans('admin/setting.navigation.captcha')) ->label(trans('admin/setting.navigation.captcha'))
->icon('tabler-shield') ->icon('tabler-shield')
->schema($this->captchaSettings()) ->schema($this->captchaSettings())
->columns(3), ->columns(1),
Tab::make('mail') Tab::make('mail')
->label(trans('admin/setting.navigation.mail')) ->label(trans('admin/setting.navigation.mail'))
->icon('tabler-mail') ->icon('tabler-mail')
@ -122,7 +128,8 @@ class Settings extends Page implements HasForms
Tab::make('oauth') Tab::make('oauth')
->label(trans('admin/setting.navigation.oauth')) ->label(trans('admin/setting.navigation.oauth'))
->icon('tabler-brand-oauth') ->icon('tabler-brand-oauth')
->schema($this->oauthSettings()), ->schema($this->oauthSettings())
->columns(1),
Tab::make('misc') Tab::make('misc')
->label(trans('admin/setting.navigation.misc')) ->label(trans('admin/setting.navigation.misc'))
->icon('tabler-tool') ->icon('tabler-tool')
@ -131,7 +138,9 @@ class Settings extends Page implements HasForms
]; ];
} }
/** @return Component[] */ /** @return Component[]
* @throws Exception
*/
private function generalSettings(): array private function generalSettings(): array
{ {
return [ return [
@ -166,8 +175,7 @@ class Settings extends Page implements HasForms
->offIcon('tabler-x') ->offIcon('tabler-x')
->onColor('success') ->onColor('success')
->offColor('danger') ->offColor('danger')
->formatStateUsing(fn ($state): bool => (bool) $state) ->stateCast(new BooleanStateCast(false))
->afterStateUpdated(fn ($state, Set $set) => $set('APP_DEBUG', (bool) $state))
->default(env('APP_DEBUG', config('app.debug'))), ->default(env('APP_DEBUG', config('app.debug'))),
]), ]),
Group::make() Group::make()
@ -186,19 +194,17 @@ class Settings extends Page implements HasForms
->offIcon('tabler-x') ->offIcon('tabler-x')
->onColor('success') ->onColor('success')
->offColor('danger') ->offColor('danger')
->formatStateUsing(fn ($state) => (bool) $state) ->stateCast(new BooleanStateCast(false))
->afterStateUpdated(fn ($state, Set $set) => $set('FILAMENT_UPLOADABLE_AVATARS', (bool) $state))
->default(env('FILAMENT_UPLOADABLE_AVATARS', config('panel.filament.uploadable-avatars'))), ->default(env('FILAMENT_UPLOADABLE_AVATARS', config('panel.filament.uploadable-avatars'))),
]), ]),
ToggleButtons::make('PANEL_USE_BINARY_PREFIX') ToggleButtons::make('PANEL_USE_BINARY_PREFIX')
->label(trans('admin/setting.general.unit_prefix')) ->label(trans('admin/setting.general.unit_prefix'))
->inline() ->inline()
->options([ ->options([
false => trans('admin/setting.general.decimal_prefix'), 0 => trans('admin/setting.general.decimal_prefix'),
true => trans('admin/setting.general.binary_prefix'), 1 => trans('admin/setting.general.binary_prefix'),
]) ])
->formatStateUsing(fn ($state): bool => (bool) $state) ->stateCast(new BooleanStateCast(false, true))
->afterStateUpdated(fn ($state, Set $set) => $set('PANEL_USE_BINARY_PREFIX', (bool) $state))
->default(env('PANEL_USE_BINARY_PREFIX', config('panel.use_binary_prefix'))), ->default(env('PANEL_USE_BINARY_PREFIX', config('panel.use_binary_prefix'))),
ToggleButtons::make('APP_2FA_REQUIRED') ToggleButtons::make('APP_2FA_REQUIRED')
->label(trans('admin/setting.general.2fa_requirement')) ->label(trans('admin/setting.general.2fa_requirement'))
@ -214,7 +220,7 @@ class Settings extends Page implements HasForms
Select::make('FILAMENT_WIDTH') Select::make('FILAMENT_WIDTH')
->label(trans('admin/setting.general.display_width')) ->label(trans('admin/setting.general.display_width'))
->native(false) ->native(false)
->options(MaxWidth::class) ->options(Width::class)
->selectablePlaceholder(false) ->selectablePlaceholder(false)
->default(env('FILAMENT_WIDTH', config('panel.filament.display-width'))), ->default(env('FILAMENT_WIDTH', config('panel.filament.display-width'))),
TagsInput::make('TRUSTED_PROXIES') TagsInput::make('TRUSTED_PROXIES')
@ -224,14 +230,14 @@ class Settings extends Page implements HasForms
->placeholder(trans('admin/setting.general.trusted_proxies_help')) ->placeholder(trans('admin/setting.general.trusted_proxies_help'))
->default(env('TRUSTED_PROXIES', implode(',', Arr::wrap(config('trustedproxy.proxies'))))) ->default(env('TRUSTED_PROXIES', implode(',', Arr::wrap(config('trustedproxy.proxies')))))
->hintActions([ ->hintActions([
FormAction::make('clear') Action::make('clear')
->label(trans('admin/setting.general.clear')) ->label(trans('admin/setting.general.clear'))
->color('danger') ->color('danger')
->icon('tabler-trash') ->icon('tabler-trash')
->requiresConfirmation() ->requiresConfirmation()
->authorize(fn () => auth()->user()->can('update settings')) ->authorize(fn () => auth()->user()->can('update settings'))
->action(fn (Set $set) => $set('TRUSTED_PROXIES', [])), ->action(fn (Set $set) => $set('TRUSTED_PROXIES', [])),
FormAction::make('cloudflare') Action::make('cloudflare')
->label(trans('admin/setting.general.set_to_cf')) ->label(trans('admin/setting.general.set_to_cf'))
->icon('tabler-brand-cloudflare') ->icon('tabler-brand-cloudflare')
->authorize(fn () => auth()->user()->can('update settings')) ->authorize(fn () => auth()->user()->can('update settings'))
@ -262,6 +268,8 @@ class Settings extends Page implements HasForms
/** /**
* @return Component[] * @return Component[]
*
* @throws Exception
*/ */
private function captchaSettings(): array private function captchaSettings(): array
{ {
@ -281,12 +289,12 @@ class Settings extends Page implements HasForms
->live() ->live()
->default(env("CAPTCHA_{$id}_ENABLED")), ->default(env("CAPTCHA_{$id}_ENABLED")),
Actions::make([ Actions::make([
FormAction::make("disable_captcha_$id") Action::make("disable_captcha_$id")
->visible(fn (Get $get) => $get("CAPTCHA_{$id}_ENABLED")) ->visible(fn (Get $get) => $get("CAPTCHA_{$id}_ENABLED"))
->label(trans('admin/setting.captcha.disable')) ->label(trans('admin/setting.captcha.disable'))
->color('danger') ->color('danger')
->action(fn (Set $set) => $set("CAPTCHA_{$id}_ENABLED", false)), ->action(fn (Set $set) => $set("CAPTCHA_{$id}_ENABLED", false)),
FormAction::make("enable_captcha_$id") Action::make("enable_captcha_$id")
->visible(fn (Get $get) => !$get("CAPTCHA_{$id}_ENABLED")) ->visible(fn (Get $get) => !$get("CAPTCHA_{$id}_ENABLED"))
->label(trans('admin/setting.captcha.enable')) ->label(trans('admin/setting.captcha.enable'))
->color('success') ->color('success')
@ -304,6 +312,8 @@ class Settings extends Page implements HasForms
/** /**
* @return Component[] * @return Component[]
*
* @throws Exception
*/ */
private function mailSettings(): array private function mailSettings(): array
{ {
@ -323,7 +333,7 @@ class Settings extends Page implements HasForms
->live() ->live()
->default(env('MAIL_MAILER', config('mail.default'))) ->default(env('MAIL_MAILER', config('mail.default')))
->hintAction( ->hintAction(
FormAction::make('test') Action::make('test')
->label(trans('admin/setting.mail.test_mail')) ->label(trans('admin/setting.mail.test_mail'))
->icon('tabler-send') ->icon('tabler-send')
->hidden(fn (Get $get) => $get('MAIL_MAILER') === 'log') ->hidden(fn (Get $get) => $get('MAIL_MAILER') === 'log')
@ -381,6 +391,7 @@ class Settings extends Page implements HasForms
Section::make(trans('admin/setting.mail.from_settings')) Section::make(trans('admin/setting.mail.from_settings'))
->description(trans('admin/setting.mail.from_settings_help')) ->description(trans('admin/setting.mail.from_settings_help'))
->columns() ->columns()
->columnSpanFull()
->schema([ ->schema([
TextInput::make('MAIL_FROM_ADDRESS') TextInput::make('MAIL_FROM_ADDRESS')
->label(trans('admin/setting.mail.from_address')) ->label(trans('admin/setting.mail.from_address'))
@ -394,6 +405,7 @@ class Settings extends Page implements HasForms
]), ]),
Section::make(trans('admin/setting.mail.smtp.smtp_title')) Section::make(trans('admin/setting.mail.smtp.smtp_title'))
->columns() ->columns()
->columnSpanFull()
->visible(fn (Get $get) => $get('MAIL_MAILER') === 'smtp') ->visible(fn (Get $get) => $get('MAIL_MAILER') === 'smtp')
->schema([ ->schema([
TextInput::make('MAIL_HOST') TextInput::make('MAIL_HOST')
@ -430,6 +442,7 @@ class Settings extends Page implements HasForms
]), ]),
Section::make(trans('admin/setting.mail.mailgun.mailgun_title')) Section::make(trans('admin/setting.mail.mailgun.mailgun_title'))
->columns() ->columns()
->columnSpanFull()
->visible(fn (Get $get) => $get('MAIL_MAILER') === 'mailgun') ->visible(fn (Get $get) => $get('MAIL_MAILER') === 'mailgun')
->schema([ ->schema([
TextInput::make('MAILGUN_DOMAIN') TextInput::make('MAILGUN_DOMAIN')
@ -450,6 +463,8 @@ class Settings extends Page implements HasForms
/** /**
* @return Component[] * @return Component[]
*
* @throws Exception
*/ */
private function backupSettings(): array private function backupSettings(): array
{ {
@ -467,6 +482,7 @@ class Settings extends Page implements HasForms
Section::make(trans('admin/setting.backup.throttle')) Section::make(trans('admin/setting.backup.throttle'))
->description(trans('admin/setting.backup.throttle_help')) ->description(trans('admin/setting.backup.throttle_help'))
->columns() ->columns()
->columnSpanFull()
->schema([ ->schema([
TextInput::make('BACKUP_THROTTLE_LIMIT') TextInput::make('BACKUP_THROTTLE_LIMIT')
->label(trans('admin/setting.backup.limit')) ->label(trans('admin/setting.backup.limit'))
@ -514,8 +530,7 @@ class Settings extends Page implements HasForms
->onColor('success') ->onColor('success')
->offColor('danger') ->offColor('danger')
->live() ->live()
->formatStateUsing(fn ($state): bool => (bool) $state) ->stateCast(new BooleanStateCast(false))
->afterStateUpdated(fn ($state, Set $set) => $set('AWS_USE_PATH_STYLE_ENDPOINT', (bool) $state))
->default(env('AWS_USE_PATH_STYLE_ENDPOINT', config('backups.disks.s3.use_path_style_endpoint'))), ->default(env('AWS_USE_PATH_STYLE_ENDPOINT', config('backups.disks.s3.use_path_style_endpoint'))),
]), ]),
]; ];
@ -523,6 +538,8 @@ class Settings extends Page implements HasForms
/** /**
* @return Component[] * @return Component[]
*
* @throws Exception
*/ */
private function oauthSettings(): array private function oauthSettings(): array
{ {
@ -543,12 +560,12 @@ class Settings extends Page implements HasForms
->live() ->live()
->default(env($key)), ->default(env($key)),
Actions::make([ Actions::make([
FormAction::make("disable_oauth_$id") Action::make("disable_oauth_$id")
->visible(fn (Get $get) => $get($key)) ->visible(fn (Get $get) => $get($key))
->label(trans('admin/setting.oauth.disable')) ->label(trans('admin/setting.oauth.disable'))
->color('danger') ->color('danger')
->action(fn (Set $set) => $set($key, false)), ->action(fn (Set $set) => $set($key, false)),
FormAction::make("enable_oauth_$id") Action::make("enable_oauth_$id")
->visible(fn (Get $get) => !$get($key)) ->visible(fn (Get $get) => !$get($key))
->label(trans('admin/setting.oauth.enable')) ->label(trans('admin/setting.oauth.enable'))
->color('success') ->color('success')
@ -578,6 +595,8 @@ class Settings extends Page implements HasForms
/** /**
* @return Component[] * @return Component[]
*
* @throws Exception
*/ */
private function miscSettings(): array private function miscSettings(): array
{ {
@ -596,8 +615,7 @@ class Settings extends Page implements HasForms
->offColor('danger') ->offColor('danger')
->live() ->live()
->columnSpanFull() ->columnSpanFull()
->formatStateUsing(fn ($state): bool => (bool) $state) ->stateCast(new BooleanStateCast(false))
->afterStateUpdated(fn ($state, Set $set) => $set('PANEL_CLIENT_ALLOCATIONS_ENABLED', (bool) $state))
->default(env('PANEL_CLIENT_ALLOCATIONS_ENABLED', config('panel.client_features.allocations.enabled'))), ->default(env('PANEL_CLIENT_ALLOCATIONS_ENABLED', config('panel.client_features.allocations.enabled'))),
TextInput::make('PANEL_CLIENT_ALLOCATIONS_RANGE_START') TextInput::make('PANEL_CLIENT_ALLOCATIONS_RANGE_START')
->label(trans('admin/setting.misc.auto_allocation.start')) ->label(trans('admin/setting.misc.auto_allocation.start'))
@ -688,8 +706,7 @@ class Settings extends Page implements HasForms
->onColor('success') ->onColor('success')
->offColor('danger') ->offColor('danger')
->live() ->live()
->formatStateUsing(fn ($state): bool => (bool) $state) ->stateCast(new BooleanStateCast(false))
->afterStateUpdated(fn ($state, Set $set) => $set('APP_ACTIVITY_HIDE_ADMIN', (bool) $state))
->default(env('APP_ACTIVITY_HIDE_ADMIN', config('activity.hide_admin_activity'))), ->default(env('APP_ACTIVITY_HIDE_ADMIN', config('activity.hide_admin_activity'))),
]), ]),
Section::make(trans('admin/setting.misc.api.title')) Section::make(trans('admin/setting.misc.api.title'))
@ -767,8 +784,19 @@ class Settings extends Page implements HasForms
$data = $this->form->getState(); $data = $this->form->getState();
unset($data['ConsoleFonts']); unset($data['ConsoleFonts']);
$data = array_map(function ($value) {
// Convert bools to a string, so they are correctly written to the .env file // Convert bools to a string, so they are correctly written to the .env file
$data = array_map(fn ($value) => is_bool($value) ? ($value ? 'true' : 'false') : $value, $data); if (is_bool($value)) {
return $value ? 'true' : 'false';
}
// Convert enum to its value
if ($value instanceof BackedEnum) {
return $value->value;
}
return $value;
}, $data);
$this->writeToEnvironment($data); $this->writeToEnvironment($data);

View File

@ -1,24 +1,26 @@
<?php <?php
namespace App\Filament\Admin\Resources; namespace App\Filament\Admin\Resources\ApiKeys;
use App\Filament\Admin\Resources\ApiKeyResource\Pages; use Filament\Schemas\Schema;
use App\Filament\Admin\Resources\UserResource\Pages\EditUser; use App\Filament\Admin\Resources\ApiKeys\Pages\ListApiKeys;
use App\Filament\Admin\Resources\ApiKeys\Pages\CreateApiKey;
use App\Filament\Admin\Resources\Users\Pages\EditUser;
use App\Filament\Components\Tables\Columns\DateTimeColumn; use App\Filament\Components\Tables\Columns\DateTimeColumn;
use App\Models\ApiKey; use App\Models\ApiKey;
use App\Traits\Filament\CanCustomizePages; use App\Traits\Filament\CanCustomizePages;
use App\Traits\Filament\CanCustomizeRelations; use App\Traits\Filament\CanCustomizeRelations;
use App\Traits\Filament\CanModifyForm; use App\Traits\Filament\CanModifyForm;
use App\Traits\Filament\CanModifyTable; use App\Traits\Filament\CanModifyTable;
use Filament\Forms\Components\Fieldset; use Exception;
use Filament\Actions\CreateAction;
use Filament\Actions\DeleteAction;
use Filament\Forms\Components\TagsInput; use Filament\Forms\Components\TagsInput;
use Filament\Forms\Components\Textarea; use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\ToggleButtons; use Filament\Forms\Components\ToggleButtons;
use Filament\Forms\Form;
use Filament\Resources\Pages\PageRegistration; use Filament\Resources\Pages\PageRegistration;
use Filament\Resources\Resource; use Filament\Resources\Resource;
use Filament\Tables\Actions\CreateAction; use Filament\Schemas\Components\Fieldset;
use Filament\Tables\Actions\DeleteAction;
use Filament\Tables\Columns\TextColumn; use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table; use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
@ -32,7 +34,7 @@ class ApiKeyResource extends Resource
protected static ?string $model = ApiKey::class; protected static ?string $model = ApiKey::class;
protected static ?string $navigationIcon = 'tabler-key'; protected static string|\BackedEnum|null $navigationIcon = 'tabler-key';
public static function getNavigationLabel(): string public static function getNavigationLabel(): string
{ {
@ -66,6 +68,9 @@ class ApiKeyResource extends Resource
return trans('admin/dashboard.advanced'); return trans('admin/dashboard.advanced');
} }
/**
* @throws Exception
*/
public static function defaultTable(Table $table): Table public static function defaultTable(Table $table): Table
{ {
return $table return $table
@ -74,7 +79,7 @@ class ApiKeyResource extends Resource
->label(trans('admin/apikey.table.key')) ->label(trans('admin/apikey.table.key'))
->icon('tabler-clipboard-text') ->icon('tabler-clipboard-text')
->state(fn (ApiKey $key) => $key->identifier . $key->token) ->state(fn (ApiKey $key) => $key->identifier . $key->token)
->copyable(), ->copyable(fn () => request()->isSecure()),
TextColumn::make('memo') TextColumn::make('memo')
->label(trans('admin/apikey.table.description')) ->label(trans('admin/apikey.table.description'))
->wrap() ->wrap()
@ -91,7 +96,7 @@ class ApiKeyResource extends Resource
->icon('tabler-user') ->icon('tabler-user')
->url(fn (ApiKey $apiKey) => auth()->user()->can('update', $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([ ->recordActions([
DeleteAction::make(), DeleteAction::make(),
]) ])
->emptyStateIcon('tabler-key') ->emptyStateIcon('tabler-key')
@ -102,16 +107,15 @@ class ApiKeyResource extends Resource
]); ]);
} }
public static function defaultForm(Form $form): Form /**
* @throws Exception
*/
public static function defaultForm(Schema $schema): Schema
{ {
return $form return $schema
->schema([ ->components([
Fieldset::make('Permissions') Fieldset::make('Permissions')
->columns([ ->columnSpanFull()
'default' => 1,
'sm' => 1,
'md' => 2,
])
->schema( ->schema(
collect(ApiKey::getPermissionList())->map(fn ($resource) => ToggleButtons::make('permissions_' . $resource) collect(ApiKey::getPermissionList())->map(fn ($resource) => ToggleButtons::make('permissions_' . $resource)
->label(str($resource)->replace('_', ' ')->title())->inline() ->label(str($resource)->replace('_', ' ')->title())->inline()
@ -156,8 +160,8 @@ class ApiKeyResource extends Resource
public static function getDefaultPages(): array public static function getDefaultPages(): array
{ {
return [ return [
'index' => Pages\ListApiKeys::route('/'), 'index' => ListApiKeys::route('/'),
'create' => Pages\CreateApiKey::route('/create'), 'create' => CreateApiKey::route('/create'),
]; ];
} }
} }

View File

@ -1,8 +1,8 @@
<?php <?php
namespace App\Filament\Admin\Resources\ApiKeyResource\Pages; namespace App\Filament\Admin\Resources\ApiKeys\Pages;
use App\Filament\Admin\Resources\ApiKeyResource; use App\Filament\Admin\Resources\ApiKeys\ApiKeyResource;
use App\Models\ApiKey; use App\Models\ApiKey;
use App\Traits\Filament\CanCustomizeHeaderActions; use App\Traits\Filament\CanCustomizeHeaderActions;
use App\Traits\Filament\CanCustomizeHeaderWidgets; use App\Traits\Filament\CanCustomizeHeaderWidgets;

View File

@ -1,8 +1,8 @@
<?php <?php
namespace App\Filament\Admin\Resources\ApiKeyResource\Pages; namespace App\Filament\Admin\Resources\ApiKeys\Pages;
use App\Filament\Admin\Resources\ApiKeyResource; use App\Filament\Admin\Resources\ApiKeys\ApiKeyResource;
use App\Models\ApiKey; use App\Models\ApiKey;
use App\Traits\Filament\CanCustomizeHeaderActions; use App\Traits\Filament\CanCustomizeHeaderActions;
use App\Traits\Filament\CanCustomizeHeaderWidgets; use App\Traits\Filament\CanCustomizeHeaderWidgets;

View File

@ -1,26 +1,30 @@
<?php <?php
namespace App\Filament\Admin\Resources; namespace App\Filament\Admin\Resources\DatabaseHosts;
use App\Filament\Admin\Resources\DatabaseHostResource\Pages; use App\Filament\Admin\Resources\DatabaseHosts\RelationManagers\DatabasesRelationManager;
use App\Filament\Admin\Resources\DatabaseHostResource\RelationManagers; use App\Filament\Admin\Resources\DatabaseHosts\Pages\ListDatabaseHosts;
use App\Filament\Admin\Resources\DatabaseHosts\Pages\CreateDatabaseHost;
use App\Filament\Admin\Resources\DatabaseHosts\Pages\ViewDatabaseHost;
use App\Filament\Admin\Resources\DatabaseHosts\Pages\EditDatabaseHost;
use App\Models\DatabaseHost; use App\Models\DatabaseHost;
use App\Traits\Filament\CanCustomizePages; use App\Traits\Filament\CanCustomizePages;
use App\Traits\Filament\CanCustomizeRelations; use App\Traits\Filament\CanCustomizeRelations;
use App\Traits\Filament\CanModifyForm; use App\Traits\Filament\CanModifyForm;
use App\Traits\Filament\CanModifyTable; use App\Traits\Filament\CanModifyTable;
use Filament\Forms\Components\Section; 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\Select;
use Filament\Forms\Components\TextInput; use Filament\Forms\Components\TextInput;
use Filament\Forms\Form;
use Filament\Forms\Set;
use Filament\Resources\Pages\PageRegistration; use Filament\Resources\Pages\PageRegistration;
use Filament\Resources\RelationManagers\RelationManager; use Filament\Resources\RelationManagers\RelationManager;
use Filament\Resources\Resource; use Filament\Resources\Resource;
use Filament\Tables\Actions\CreateAction; use Filament\Schemas\Components\Section;
use Filament\Tables\Actions\DeleteBulkAction; use Filament\Schemas\Components\Utilities\Set;
use Filament\Tables\Actions\EditAction; use Filament\Schemas\Schema;
use Filament\Tables\Actions\ViewAction;
use Filament\Tables\Columns\TextColumn; use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table; use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
@ -34,7 +38,7 @@ class DatabaseHostResource extends Resource
protected static ?string $model = DatabaseHost::class; protected static ?string $model = DatabaseHost::class;
protected static ?string $navigationIcon = 'tabler-database'; protected static string|\BackedEnum|null $navigationIcon = 'tabler-database';
protected static ?string $recordTitleAttribute = 'name'; protected static ?string $recordTitleAttribute = 'name';
@ -63,6 +67,9 @@ class DatabaseHostResource extends Resource
return trans('admin/dashboard.advanced'); return trans('admin/dashboard.advanced');
} }
/**
* @throws Exception
*/
public static function defaultTable(Table $table): Table public static function defaultTable(Table $table): Table
{ {
return $table return $table
@ -85,7 +92,7 @@ class DatabaseHostResource extends Resource
->placeholder(trans('admin/databasehost.no_nodes')), ->placeholder(trans('admin/databasehost.no_nodes')),
]) ])
->checkIfRecordIsSelectableUsing(fn (DatabaseHost $databaseHost) => !$databaseHost->databases_count) ->checkIfRecordIsSelectableUsing(fn (DatabaseHost $databaseHost) => !$databaseHost->databases_count)
->actions([ ->recordActions([
ViewAction::make() ViewAction::make()
->hidden(fn ($record) => static::canEdit($record)), ->hidden(fn ($record) => static::canEdit($record)),
EditAction::make(), EditAction::make(),
@ -101,11 +108,15 @@ class DatabaseHostResource extends Resource
]); ]);
} }
public static function defaultForm(Form $form): Form /**
* @throws Exception
*/
public static function defaultForm(Schema $schema): Schema
{ {
return $form return $schema
->schema([ ->components([
Section::make() Section::make()
->columnSpanFull()
->columns([ ->columns([
'default' => 2, 'default' => 2,
'sm' => 3, 'sm' => 3,
@ -166,7 +177,7 @@ class DatabaseHostResource extends Resource
public static function getDefaultRelations(): array public static function getDefaultRelations(): array
{ {
return [ return [
RelationManagers\DatabasesRelationManager::class, DatabasesRelationManager::class,
]; ];
} }
@ -174,10 +185,10 @@ class DatabaseHostResource extends Resource
public static function getDefaultPages(): array public static function getDefaultPages(): array
{ {
return [ return [
'index' => Pages\ListDatabaseHosts::route('/'), 'index' => ListDatabaseHosts::route('/'),
'create' => Pages\CreateDatabaseHost::route('/create'), 'create' => CreateDatabaseHost::route('/create'),
'view' => Pages\ViewDatabaseHost::route('/{record}'), 'view' => ViewDatabaseHost::route('/{record}'),
'edit' => Pages\EditDatabaseHost::route('/{record}/edit'), 'edit' => EditDatabaseHost::route('/{record}/edit'),
]; ];
} }

View File

@ -1,30 +1,31 @@
<?php <?php
namespace App\Filament\Admin\Resources\DatabaseHostResource\Pages; namespace App\Filament\Admin\Resources\DatabaseHosts\Pages;
use App\Filament\Admin\Resources\DatabaseHostResource; use App\Filament\Admin\Resources\DatabaseHosts\DatabaseHostResource;
use App\Services\Databases\Hosts\HostCreationService; use App\Services\Databases\Hosts\HostCreationService;
use App\Traits\Filament\CanCustomizeHeaderActions; use App\Traits\Filament\CanCustomizeHeaderActions;
use App\Traits\Filament\CanCustomizeHeaderWidgets; use App\Traits\Filament\CanCustomizeHeaderWidgets;
use Filament\Forms\Components\Fieldset; use Exception;
use Filament\Forms\Components\Hidden; use Filament\Forms\Components\Hidden;
use Filament\Forms\Components\Placeholder; use Filament\Infolists\Components\TextEntry;
use Filament\Forms\Components\Select; use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput; use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle; use Filament\Forms\Components\Toggle;
use Filament\Forms\Components\Wizard\Step; use Filament\Schemas\Components\Fieldset;
use Filament\Forms\Get; use Filament\Schemas\Components\Utilities\Get;
use Filament\Forms\Set; use Filament\Schemas\Components\Utilities\Set;
use Filament\Notifications\Notification; use Filament\Notifications\Notification;
use Filament\Resources\Pages\CreateRecord; use Filament\Resources\Pages\CreateRecord;
use Filament\Resources\Pages\CreateRecord\Concerns\HasWizard; use Filament\Resources\Pages\CreateRecord\Concerns\HasWizard;
use Filament\Schemas\Components\Wizard\Step;
use Filament\Support\Exceptions\Halt; use Filament\Support\Exceptions\Halt;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\HtmlString; use Illuminate\Support\HtmlString;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use PDOException; use PDOException;
use Webbingbrasil\FilamentCopyActions\Forms\Actions\CopyAction; use Throwable;
class CreateDatabaseHost extends CreateRecord class CreateDatabaseHost extends CreateRecord
{ {
@ -43,15 +44,18 @@ class CreateDatabaseHost extends CreateRecord
$this->service = $service; $this->service = $service;
} }
/** @return Step[] */ /** @return Step[]
* @throws Exception
*/
public function getSteps(): array public function getSteps(): array
{ {
return [ return [
Step::make(trans('admin/databasehost.setup.preparations')) Step::make(trans('admin/databasehost.setup.preparations'))
->columns() ->columns()
->schema([ ->schema([
Placeholder::make('') TextEntry::make('setup')
->content(trans('admin/databasehost.setup.note')), ->hiddenLabel()
->state(trans('admin/databasehost.setup.note')),
Toggle::make('different_server') Toggle::make('different_server')
->label(new HtmlString(trans('admin/databasehost.setup.different_server'))) ->label(new HtmlString(trans('admin/databasehost.setup.different_server')))
->dehydrated(false) ->dehydrated(false)
@ -84,31 +88,34 @@ class CreateDatabaseHost extends CreateRecord
->schema([ ->schema([
Fieldset::make(trans('admin/databasehost.setup.database_user')) Fieldset::make(trans('admin/databasehost.setup.database_user'))
->schema([ ->schema([
Placeholder::make('') TextEntry::make('cli_login')
->content(new HtmlString(trans('admin/databasehost.setup.cli_login'))) ->hiddenLabel()
->state(new HtmlString(trans('admin/databasehost.setup.cli_login')))
->columnSpanFull(), ->columnSpanFull(),
TextInput::make('create_user') TextInput::make('create_user')
->label(trans('admin/databasehost.setup.command_create_user')) ->label(trans('admin/databasehost.setup.command_create_user'))
->default(fn (Get $get) => "CREATE USER '{$get('username')}'@'{$get('panel_ip')}' IDENTIFIED BY '{$get('password')}';") ->default(fn (Get $get) => "CREATE USER '{$get('username')}'@'{$get('panel_ip')}' IDENTIFIED BY '{$get('password')}';")
->disabled() ->disabled()
->dehydrated(false) ->dehydrated(false)
->suffixAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null) ->copyable(fn () => request()->isSecure())
->columnSpanFull(), ->columnSpanFull(),
TextInput::make('assign_permissions') TextInput::make('assign_permissions')
->label(trans('admin/databasehost.setup.command_assign_permissions')) ->label(trans('admin/databasehost.setup.command_assign_permissions'))
->default(fn (Get $get) => "GRANT ALL PRIVILEGES ON *.* TO '{$get('username')}'@'{$get('panel_ip')}' WITH GRANT OPTION;") ->default(fn (Get $get) => "GRANT ALL PRIVILEGES ON *.* TO '{$get('username')}'@'{$get('panel_ip')}' WITH GRANT OPTION;")
->disabled() ->disabled()
->dehydrated(false) ->dehydrated(false)
->suffixAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null) ->copyable(fn () => request()->isSecure())
->columnSpanFull(), ->columnSpanFull(),
Placeholder::make('') TextEntry::make('cli_exit')
->content(new HtmlString(trans('admin/databasehost.setup.cli_exit'))) ->hiddenLabel()
->state(new HtmlString(trans('admin/databasehost.setup.cli_exit')))
->columnSpanFull(), ->columnSpanFull(),
]), ]),
Fieldset::make(trans('admin/databasehost.setup.external_access')) Fieldset::make(trans('admin/databasehost.setup.external_access'))
->schema([ ->schema([
Placeholder::make('') TextEntry::make('allow_external_access')
->content(new HtmlString(trans('admin/databasehost.setup.allow_external_access'))) ->hiddenLabel()
->state(new HtmlString(trans('admin/databasehost.setup.allow_external_access')))
->columnSpanFull(), ->columnSpanFull(),
]), ]),
]), ]),
@ -155,6 +162,10 @@ class CreateDatabaseHost extends CreateRecord
]; ];
} }
/**
* @throws Halt
* @throws Throwable
*/
protected function handleRecordCreation(array $data): Model protected function handleRecordCreation(array $data): Model
{ {
try { try {

View File

@ -1,8 +1,8 @@
<?php <?php
namespace App\Filament\Admin\Resources\DatabaseHostResource\Pages; namespace App\Filament\Admin\Resources\DatabaseHosts\Pages;
use App\Filament\Admin\Resources\DatabaseHostResource; use App\Filament\Admin\Resources\DatabaseHosts\DatabaseHostResource;
use App\Models\DatabaseHost; use App\Models\DatabaseHost;
use App\Services\Databases\Hosts\HostUpdateService; use App\Services\Databases\Hosts\HostUpdateService;
use App\Traits\Filament\CanCustomizeHeaderActions; use App\Traits\Filament\CanCustomizeHeaderActions;

View File

@ -1,8 +1,8 @@
<?php <?php
namespace App\Filament\Admin\Resources\DatabaseHostResource\Pages; namespace App\Filament\Admin\Resources\DatabaseHosts\Pages;
use App\Filament\Admin\Resources\DatabaseHostResource; use App\Filament\Admin\Resources\DatabaseHosts\DatabaseHostResource;
use App\Models\DatabaseHost; use App\Models\DatabaseHost;
use App\Traits\Filament\CanCustomizeHeaderActions; use App\Traits\Filament\CanCustomizeHeaderActions;
use App\Traits\Filament\CanCustomizeHeaderWidgets; use App\Traits\Filament\CanCustomizeHeaderWidgets;

View File

@ -1,8 +1,8 @@
<?php <?php
namespace App\Filament\Admin\Resources\DatabaseHostResource\Pages; namespace App\Filament\Admin\Resources\DatabaseHosts\Pages;
use App\Filament\Admin\Resources\DatabaseHostResource; use App\Filament\Admin\Resources\DatabaseHosts\DatabaseHostResource;
use App\Traits\Filament\CanCustomizeHeaderActions; use App\Traits\Filament\CanCustomizeHeaderActions;
use App\Traits\Filament\CanCustomizeHeaderWidgets; use App\Traits\Filament\CanCustomizeHeaderWidgets;
use Filament\Actions\Action; use Filament\Actions\Action;

View File

@ -1,15 +1,15 @@
<?php <?php
namespace App\Filament\Admin\Resources\DatabaseHostResource\RelationManagers; namespace App\Filament\Admin\Resources\DatabaseHosts\RelationManagers;
use App\Filament\Components\Forms\Actions\RotateDatabasePasswordAction; use Filament\Actions\DeleteAction;
use Filament\Actions\ViewAction;
use Filament\Schemas\Schema;
use App\Filament\Components\Actions\RotateDatabasePasswordAction;
use App\Filament\Components\Tables\Columns\DateTimeColumn; use App\Filament\Components\Tables\Columns\DateTimeColumn;
use App\Models\Database; use App\Models\Database;
use Filament\Forms\Components\TextInput; use Filament\Forms\Components\TextInput;
use Filament\Forms\Form;
use Filament\Resources\RelationManagers\RelationManager; use Filament\Resources\RelationManagers\RelationManager;
use Filament\Tables\Actions\DeleteAction;
use Filament\Tables\Actions\ViewAction;
use Filament\Tables\Columns\TextColumn; use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table; use Filament\Tables\Table;
@ -17,10 +17,10 @@ class DatabasesRelationManager extends RelationManager
{ {
protected static string $relationship = 'databases'; protected static string $relationship = 'databases';
public function form(Form $form): Form public function form(Schema $schema): Schema
{ {
return $form return $schema
->schema([ ->components([
TextInput::make('database') TextInput::make('database')
->columnSpanFull(), ->columnSpanFull(),
TextInput::make('username') TextInput::make('username')
@ -49,7 +49,7 @@ class DatabasesRelationManager extends RelationManager
public function table(Table $table): Table public function table(Table $table): Table
{ {
return $table return $table
->recordTitleAttribute('servers') ->recordTitleAttribute('database')
->heading('') ->heading('')
->columns([ ->columns([
TextColumn::make('database') TextColumn::make('database')
@ -69,12 +69,10 @@ class DatabasesRelationManager extends RelationManager
DateTimeColumn::make('created_at') DateTimeColumn::make('created_at')
->label(trans('admin/databasehost.table.created_at')), ->label(trans('admin/databasehost.table.created_at')),
]) ])
->actions([ ->recordActions([
DeleteAction::make()
->authorize(fn (Database $database) => auth()->user()->can('delete', $database)),
ViewAction::make() ViewAction::make()
->color('primary') ->color('primary'),
->hidden(fn () => !auth()->user()->can('viewAny', Database::class)), DeleteAction::make(),
]); ]);
} }
} }

View File

@ -1,9 +1,12 @@
<?php <?php
namespace App\Filament\Admin\Resources; namespace App\Filament\Admin\Resources\Eggs;
use App\Filament\Admin\Resources\EggResource\Pages; use App\Enums\CustomizationKey;
use App\Filament\Admin\Resources\EggResource\RelationManagers; use App\Filament\Admin\Resources\Eggs\RelationManagers\ServersRelationManager;
use App\Filament\Admin\Resources\Eggs\Pages\ListEggs;
use App\Filament\Admin\Resources\Eggs\Pages\CreateEgg;
use App\Filament\Admin\Resources\Eggs\Pages\EditEgg;
use App\Models\Egg; use App\Models\Egg;
use App\Traits\Filament\CanCustomizePages; use App\Traits\Filament\CanCustomizePages;
use App\Traits\Filament\CanCustomizeRelations; use App\Traits\Filament\CanCustomizeRelations;
@ -18,18 +21,18 @@ class EggResource extends Resource
protected static ?string $model = Egg::class; protected static ?string $model = Egg::class;
protected static ?string $navigationIcon = 'tabler-eggs'; protected static string|\BackedEnum|null $navigationIcon = 'tabler-eggs';
protected static ?string $recordTitleAttribute = 'name'; protected static ?string $recordTitleAttribute = 'name';
public static function getNavigationBadge(): ?string public static function getNavigationBadge(): ?string
{ {
return static::getModel()::count() ?: null; return ($count = static::getModel()::count()) > 0 ? (string) $count : null;
} }
public static function getNavigationGroup(): ?string public static function getNavigationGroup(): ?string
{ {
return !empty(auth()->user()->getCustomization()['top_navigation']) ? false : trans('admin/dashboard.server'); return auth()->user()->getCustomization(CustomizationKey::TopNavigation) ? false : trans('admin/dashboard.server');
} }
public static function getNavigationLabel(): string public static function getNavigationLabel(): string
@ -56,7 +59,7 @@ class EggResource extends Resource
public static function getDefaultRelations(): array public static function getDefaultRelations(): array
{ {
return [ return [
RelationManagers\ServersRelationManager::class, ServersRelationManager::class,
]; ];
} }
@ -64,9 +67,9 @@ class EggResource extends Resource
public static function getDefaultPages(): array public static function getDefaultPages(): array
{ {
return [ return [
'index' => Pages\ListEggs::route('/'), 'index' => ListEggs::route('/'),
'create' => Pages\CreateEgg::route('/create'), 'create' => CreateEgg::route('/create'),
'edit' => Pages\EditEgg::route('/{record}/edit'), 'edit' => EditEgg::route('/{record}/edit'),
]; ];
} }
} }

View File

@ -1,9 +1,9 @@
<?php <?php
namespace App\Filament\Admin\Resources\EggResource\Pages; namespace App\Filament\Admin\Resources\Eggs\Pages;
use AbdelhamidErrahmouni\FilamentMonacoEditor\MonacoEditor; use Exception;
use App\Filament\Admin\Resources\EggResource; use App\Filament\Admin\Resources\Eggs\EggResource;
use App\Filament\Components\Forms\Fields\CopyFrom; use App\Filament\Components\Forms\Fields\CopyFrom;
use App\Models\EggVariable; use App\Models\EggVariable;
use App\Traits\Filament\CanCustomizeHeaderActions; use App\Traits\Filament\CanCustomizeHeaderActions;
@ -11,24 +11,25 @@ use App\Traits\Filament\CanCustomizeHeaderWidgets;
use Filament\Actions\Action; use Filament\Actions\Action;
use Filament\Actions\ActionGroup; use Filament\Actions\ActionGroup;
use Filament\Forms\Components\Checkbox; use Filament\Forms\Components\Checkbox;
use Filament\Forms\Components\Fieldset; use Filament\Forms\Components\CodeEditor;
use Filament\Forms\Components\Hidden; use Filament\Forms\Components\Hidden;
use Filament\Forms\Components\KeyValue; use Filament\Forms\Components\KeyValue;
use Filament\Forms\Components\Repeater; use Filament\Forms\Components\Repeater;
use Filament\Forms\Components\Select; use Filament\Forms\Components\Select;
use Filament\Forms\Components\Tabs;
use Filament\Forms\Components\Tabs\Tab;
use Filament\Forms\Components\TagsInput; use Filament\Forms\Components\TagsInput;
use Filament\Forms\Components\Textarea; use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput; use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle; use Filament\Forms\Components\Toggle;
use Filament\Forms\Form;
use Filament\Forms\Get;
use Filament\Forms\Set;
use Filament\Resources\Pages\CreateRecord; use Filament\Resources\Pages\CreateRecord;
use Filament\Schemas\Components\Fieldset;
use Filament\Schemas\Components\Tabs;
use Filament\Schemas\Components\Tabs\Tab;
use Filament\Schemas\Components\Utilities\Get;
use Filament\Schemas\Components\Utilities\Set;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Illuminate\Validation\Rules\Unique; use Illuminate\Validation\Rules\Unique;
use Filament\Schemas\Schema;
class CreateEgg extends CreateRecord class CreateEgg extends CreateRecord
{ {
@ -52,10 +53,13 @@ class CreateEgg extends CreateRecord
return []; return [];
} }
public function form(Form $form): Form /**
* @throws Exception
*/
public function form(Schema $schema): Schema
{ {
return $form return $schema
->schema([ ->components([
Tabs::make()->tabs([ Tabs::make()->tabs([
Tab::make('configuration') Tab::make('configuration')
->label(trans('admin/egg.tabs.configuration')) ->label(trans('admin/egg.tabs.configuration'))
@ -153,11 +157,10 @@ class CreateEgg extends CreateRecord
->columnSpanFull() ->columnSpanFull()
->schema([ ->schema([
Repeater::make('variables') Repeater::make('variables')
->label('') ->hiddenLabel()
->addActionLabel(trans('admin/egg.add_new_variable')) ->addActionLabel(trans('admin/egg.add_new_variable'))
->grid() ->grid()
->relationship('variables') ->relationship('variables')
->name('name')
->reorderable()->orderColumn() ->reorderable()->orderColumn()
->collapsible()->collapsed() ->collapsible()->collapsed()
->columnSpan(2) ->columnSpan(2)
@ -189,7 +192,7 @@ class CreateEgg extends CreateRecord
->maxLength(255) ->maxLength(255)
->columnSpanFull() ->columnSpanFull()
->afterStateUpdated(fn (Set $set, $state) => $set('env_variable', str($state)->trim()->snake()->upper()->toString())) ->afterStateUpdated(fn (Set $set, $state) => $set('env_variable', str($state)->trim()->snake()->upper()->toString()))
->unique(modifyRuleUsing: fn (Unique $rule, Get $get) => $rule->where('egg_id', $get('../../id')), ignoreRecord: true) ->unique(modifyRuleUsing: fn (Unique $rule, Get $get) => $rule->where('egg_id', $get('../../id')))
->validationMessages([ ->validationMessages([
'unique' => trans('admin/egg.error_unique'), 'unique' => trans('admin/egg.error_unique'),
]) ])
@ -202,7 +205,7 @@ class CreateEgg extends CreateRecord
->suffix('}}') ->suffix('}}')
->hintIcon('tabler-code') ->hintIcon('tabler-code')
->hintIconTooltip(fn ($state) => "{{{$state}}}") ->hintIconTooltip(fn ($state) => "{{{$state}}}")
->unique(modifyRuleUsing: fn (Unique $rule, Get $get) => $rule->where('egg_id', $get('../../id')), ignoreRecord: true) ->unique(modifyRuleUsing: fn (Unique $rule, Get $get) => $rule->where('egg_id', $get('../../id')))
->rules(EggVariable::getRulesForField('env_variable')) ->rules(EggVariable::getRulesForField('env_variable'))
->validationMessages([ ->validationMessages([
'unique' => trans('admin/egg.error_unique'), 'unique' => trans('admin/egg.error_unique'),
@ -264,13 +267,10 @@ class CreateEgg extends CreateRecord
'/bin/bash' => '/bin/bash', '/bin/bash' => '/bin/bash',
]) ])
->required(), ->required(),
MonacoEditor::make('script_install') CodeEditor::make('script_install')
->label(trans('admin/egg.script_install')) ->label(trans('admin/egg.script_install'))
->columnSpanFull() ->columnSpanFull()
->fontSize('16px') ->lazy(),
->language('shell')
->lazy()
->view('filament.plugins.monaco-editor'),
]), ]),
])->columnSpanFull()->persistTabInQueryString(), ])->columnSpanFull()->persistTabInQueryString(),

View File

@ -1,9 +1,9 @@
<?php <?php
namespace App\Filament\Admin\Resources\EggResource\Pages; namespace App\Filament\Admin\Resources\Eggs\Pages;
use AbdelhamidErrahmouni\FilamentMonacoEditor\MonacoEditor; use Exception;
use App\Filament\Admin\Resources\EggResource; use App\Filament\Admin\Resources\Eggs\EggResource;
use App\Filament\Components\Actions\ExportEggAction; use App\Filament\Components\Actions\ExportEggAction;
use App\Filament\Components\Actions\ImportEggAction; use App\Filament\Components\Actions\ImportEggAction;
use App\Filament\Components\Forms\Fields\CopyFrom; use App\Filament\Components\Forms\Fields\CopyFrom;
@ -15,22 +15,23 @@ use Filament\Actions\Action;
use Filament\Actions\ActionGroup; use Filament\Actions\ActionGroup;
use Filament\Actions\DeleteAction; use Filament\Actions\DeleteAction;
use Filament\Forms\Components\Checkbox; use Filament\Forms\Components\Checkbox;
use Filament\Forms\Components\Fieldset; use Filament\Forms\Components\CodeEditor;
use Filament\Schemas\Components\Fieldset;
use Filament\Forms\Components\Hidden; use Filament\Forms\Components\Hidden;
use Filament\Forms\Components\KeyValue; use Filament\Forms\Components\KeyValue;
use Filament\Forms\Components\Repeater; use Filament\Forms\Components\Repeater;
use Filament\Forms\Components\Select; use Filament\Forms\Components\Select;
use Filament\Forms\Components\Tabs;
use Filament\Forms\Components\Tabs\Tab;
use Filament\Forms\Components\TagsInput; use Filament\Forms\Components\TagsInput;
use Filament\Forms\Components\Textarea; use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput; use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle; use Filament\Forms\Components\Toggle;
use Filament\Forms\Form; use Filament\Schemas\Components\Tabs;
use Filament\Forms\Get; use Filament\Schemas\Components\Tabs\Tab;
use Filament\Forms\Set; use Filament\Schemas\Components\Utilities\Get;
use Filament\Schemas\Components\Utilities\Set;
use Filament\Resources\Pages\EditRecord; use Filament\Resources\Pages\EditRecord;
use Illuminate\Validation\Rules\Unique; use Illuminate\Validation\Rules\Unique;
use Filament\Schemas\Schema;
class EditEgg extends EditRecord class EditEgg extends EditRecord
{ {
@ -39,10 +40,13 @@ class EditEgg extends EditRecord
protected static string $resource = EggResource::class; protected static string $resource = EggResource::class;
public function form(Form $form): Form /**
* @throws Exception
*/
public function form(Schema $schema): Schema
{ {
return $form return $schema
->schema([ ->components([
Tabs::make()->tabs([ Tabs::make()->tabs([
Tab::make('configuration') Tab::make('configuration')
->label(trans('admin/egg.tabs.configuration')) ->label(trans('admin/egg.tabs.configuration'))
@ -143,10 +147,9 @@ class EditEgg extends EditRecord
->icon('tabler-variable') ->icon('tabler-variable')
->schema([ ->schema([
Repeater::make('variables') Repeater::make('variables')
->label('') ->hiddenLabel()
->grid() ->grid()
->relationship('variables') ->relationship('variables')
->name('name')
->reorderable() ->reorderable()
->collapsible()->collapsed() ->collapsible()->collapsed()
->orderColumn() ->orderColumn()
@ -178,7 +181,7 @@ class EditEgg extends EditRecord
->maxLength(255) ->maxLength(255)
->columnSpanFull() ->columnSpanFull()
->afterStateUpdated(fn (Set $set, $state) => $set('env_variable', str($state)->trim()->snake()->upper()->toString())) ->afterStateUpdated(fn (Set $set, $state) => $set('env_variable', str($state)->trim()->snake()->upper()->toString()))
->unique(modifyRuleUsing: fn (Unique $rule, Get $get) => $rule->where('egg_id', $get('../../id')), ignoreRecord: true) ->unique(modifyRuleUsing: fn (Unique $rule, Get $get) => $rule->where('egg_id', $get('../../id')))
->validationMessages([ ->validationMessages([
'unique' => trans('admin/egg.error_unique'), 'unique' => trans('admin/egg.error_unique'),
]) ])
@ -191,7 +194,7 @@ class EditEgg extends EditRecord
->suffix('}}') ->suffix('}}')
->hintIcon('tabler-code') ->hintIcon('tabler-code')
->hintIconTooltip(fn ($state) => "{{{$state}}}") ->hintIconTooltip(fn ($state) => "{{{$state}}}")
->unique(modifyRuleUsing: fn (Unique $rule, Get $get) => $rule->where('egg_id', $get('../../id')), ignoreRecord: true) ->unique(modifyRuleUsing: fn (Unique $rule, Get $get) => $rule->where('egg_id', $get('../../id')))
->rules(EggVariable::getRulesForField('env_variable')) ->rules(EggVariable::getRulesForField('env_variable'))
->validationMessages([ ->validationMessages([
'unique' => trans('admin/egg.error_unique'), 'unique' => trans('admin/egg.error_unique'),
@ -253,13 +256,9 @@ class EditEgg extends EditRecord
'/bin/bash' => '/bin/bash', '/bin/bash' => '/bin/bash',
]) ])
->required(), ->required(),
MonacoEditor::make('script_install') CodeEditor::make('script_install')
->label(trans('admin/egg.script_install')) ->hiddenLabel()
->placeholderText('') ->columnSpanFull(),
->columnSpanFull()
->fontSize('16px')
->language('shell')
->view('filament.plugins.monaco-editor'),
]), ]),
])->columnSpanFull()->persistTabInQueryString(), ])->columnSpanFull()->persistTabInQueryString(),
]); ]);

View File

@ -1,28 +1,26 @@
<?php <?php
namespace App\Filament\Admin\Resources\EggResource\Pages; namespace App\Filament\Admin\Resources\Eggs\Pages;
use App\Filament\Admin\Resources\EggResource; use Exception;
use App\Filament\Components\Actions\ImportEggAction as ImportEggHeaderAction; use App\Filament\Admin\Resources\Eggs\EggResource;
use App\Filament\Components\Tables\Actions\ExportEggAction; use App\Filament\Components\Actions\ExportEggAction;
use App\Filament\Components\Tables\Actions\ImportEggAction; use App\Filament\Components\Actions\ImportEggAction;
use App\Filament\Components\Tables\Actions\UpdateEggAction; use App\Filament\Components\Actions\UpdateEggAction;
use App\Filament\Components\Tables\Actions\UpdateEggBulkAction; use App\Filament\Components\Actions\UpdateEggBulkAction;
use App\Filament\Components\Tables\Filters\TagsFilter; use App\Filament\Components\Tables\Filters\TagsFilter;
use App\Models\Egg; use App\Models\Egg;
use App\Traits\Filament\CanCustomizeHeaderActions; use App\Traits\Filament\CanCustomizeHeaderActions;
use App\Traits\Filament\CanCustomizeHeaderWidgets; use App\Traits\Filament\CanCustomizeHeaderWidgets;
use Filament\Actions\Action; use Filament\Actions\Action;
use Filament\Actions\ActionGroup; use Filament\Actions\ActionGroup;
use Filament\Actions\CreateAction as CreateHeaderAction; use Filament\Actions\CreateAction;
use Filament\Actions\DeleteBulkAction;
use Filament\Actions\EditAction;
use Filament\Actions\ReplicateAction;
use Filament\Resources\Pages\ListRecords; use Filament\Resources\Pages\ListRecords;
use Filament\Tables\Actions\CreateAction;
use Filament\Tables\Actions\DeleteBulkAction;
use Filament\Tables\Actions\EditAction;
use Filament\Tables\Actions\ReplicateAction;
use Filament\Tables\Columns\TextColumn; use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table; use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Str; use Illuminate\Support\Str;
class ListEggs extends ListRecords class ListEggs extends ListRecords
@ -32,6 +30,9 @@ class ListEggs extends ListRecords
protected static string $resource = EggResource::class; protected static string $resource = EggResource::class;
/**
* @throws Exception
*/
public function table(Table $table): Table public function table(Table $table): Table
{ {
return $table return $table
@ -53,7 +54,7 @@ class ListEggs extends ListRecords
->icon('tabler-server') ->icon('tabler-server')
->label(trans('admin/egg.servers')), ->label(trans('admin/egg.servers')),
]) ])
->actions([ ->recordActions([
EditAction::make() EditAction::make()
->iconButton() ->iconButton()
->tooltip(trans('filament-actions::edit.single.label')), ->tooltip(trans('filament-actions::edit.single.label')),
@ -62,7 +63,7 @@ class ListEggs extends ListRecords
->tooltip(trans('filament-actions::export.modal.actions.export.label')), ->tooltip(trans('filament-actions::export.modal.actions.export.label')),
UpdateEggAction::make() UpdateEggAction::make()
->iconButton() ->iconButton()
->tooltip(trans('admin/egg.update')), ->tooltip(trans_choice('admin/egg.update', 1)),
ReplicateAction::make() ReplicateAction::make()
->iconButton() ->iconButton()
->tooltip(trans('filament-actions::replicate.single.label')) ->tooltip(trans('filament-actions::replicate.single.label'))
@ -78,15 +79,15 @@ class ListEggs extends ListRecords
]) ])
->groupedBulkActions([ ->groupedBulkActions([
DeleteBulkAction::make() DeleteBulkAction::make()
->before(fn (DeleteBulkAction $action, Collection $records) => $action->records($records->filter(function ($egg) { ->before(fn (&$records) => $records = $records->filter(function ($egg) {
/** @var Egg $egg */ /** @var Egg $egg */
return $egg->servers_count <= 0; return $egg->servers_count <= 0;
}))), })),
UpdateEggBulkAction::make() UpdateEggBulkAction::make()
->before(fn (UpdateEggBulkAction $action, Collection $records) => $action->records($records->filter(function ($egg) { ->before(fn (&$records) => $records = $records->filter(function ($egg) {
/** @var Egg $egg */ /** @var Egg $egg */
return cache()->get("eggs.$egg->uuid.update", false); return cache()->get("eggs.$egg->uuid.update", false);
}))), })),
]) ])
->emptyStateIcon('tabler-eggs') ->emptyStateIcon('tabler-eggs')
->emptyStateDescription('') ->emptyStateDescription('')
@ -102,13 +103,15 @@ class ListEggs extends ListRecords
]); ]);
} }
/** @return array<Action|ActionGroup> */ /** @return array<Action|ActionGroup>
* @throws Exception
*/
protected function getDefaultHeaderActions(): array protected function getDefaultHeaderActions(): array
{ {
return [ return [
ImportEggHeaderAction::make() ImportEggAction::make()
->multiple(), ->multiple(),
CreateHeaderAction::make(), CreateAction::make(),
]; ];
} }
} }

View File

@ -1,6 +1,6 @@
<?php <?php
namespace App\Filament\Admin\Resources\EggResource\RelationManagers; namespace App\Filament\Admin\Resources\Eggs\RelationManagers;
use App\Models\Server; use App\Models\Server;
use Filament\Resources\RelationManagers\RelationManager; use Filament\Resources\RelationManagers\RelationManager;
@ -40,7 +40,7 @@ class ServersRelationManager extends RelationManager
->label(trans('admin/server.primary_allocation')) ->label(trans('admin/server.primary_allocation'))
->disabled() ->disabled()
->options(fn (Server $server) => $server->allocations->take(1)->mapWithKeys(fn ($allocation) => [$allocation->id => $allocation->address])) ->options(fn (Server $server) => $server->allocations->take(1)->mapWithKeys(fn ($allocation) => [$allocation->id => $allocation->address]))
->placeholder('None') ->placeholder(trans('admin/server.none'))
->sortable(), ->sortable(),
]); ]);
} }

View File

@ -1,26 +1,30 @@
<?php <?php
namespace App\Filament\Admin\Resources; namespace App\Filament\Admin\Resources\Mounts;
use App\Filament\Admin\Resources\MountResource\Pages; use App\Filament\Admin\Resources\Mounts\Pages\ListMounts;
use App\Filament\Admin\Resources\Mounts\Pages\CreateMount;
use App\Filament\Admin\Resources\Mounts\Pages\ViewMount;
use App\Filament\Admin\Resources\Mounts\Pages\EditMount;
use Exception;
use App\Models\Mount; use App\Models\Mount;
use App\Traits\Filament\CanCustomizePages; use App\Traits\Filament\CanCustomizePages;
use App\Traits\Filament\CanCustomizeRelations; use App\Traits\Filament\CanCustomizeRelations;
use App\Traits\Filament\CanModifyForm; use App\Traits\Filament\CanModifyForm;
use App\Traits\Filament\CanModifyTable; use App\Traits\Filament\CanModifyTable;
use Filament\Forms\Components\Group; use Filament\Actions\CreateAction;
use Filament\Forms\Components\Section; use Filament\Actions\DeleteBulkAction;
use Filament\Actions\EditAction;
use Filament\Actions\ViewAction;
use Filament\Forms\Components\Select; use Filament\Forms\Components\Select;
use Filament\Forms\Components\Textarea; use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput; use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\ToggleButtons; use Filament\Forms\Components\ToggleButtons;
use Filament\Forms\Form;
use Filament\Resources\Pages\PageRegistration; use Filament\Resources\Pages\PageRegistration;
use Filament\Resources\Resource; use Filament\Resources\Resource;
use Filament\Tables\Actions\CreateAction; use Filament\Schemas\Components\Group;
use Filament\Tables\Actions\DeleteBulkAction; use Filament\Schemas\Components\Section;
use Filament\Tables\Actions\EditAction; use Filament\Schemas\Schema;
use Filament\Tables\Actions\ViewAction;
use Filament\Tables\Columns\TextColumn; use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table; use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
@ -34,7 +38,7 @@ class MountResource extends Resource
protected static ?string $model = Mount::class; protected static ?string $model = Mount::class;
protected static ?string $navigationIcon = 'tabler-layers-linked'; protected static string|\BackedEnum|null $navigationIcon = 'tabler-layers-linked';
protected static ?string $recordTitleAttribute = 'name'; protected static ?string $recordTitleAttribute = 'name';
@ -63,6 +67,9 @@ class MountResource extends Resource
return trans('admin/dashboard.advanced'); return trans('admin/dashboard.advanced');
} }
/**
* @throws Exception
*/
public static function defaultTable(Table $table): Table public static function defaultTable(Table $table): Table
{ {
return $table return $table
@ -88,7 +95,7 @@ class MountResource extends Resource
->color(fn ($state) => $state ? 'success' : 'warning') ->color(fn ($state) => $state ? 'success' : 'warning')
->formatStateUsing(fn ($state) => $state ? trans('admin/mount.toggles.read_only') : trans('admin/mount.toggles.writable')), ->formatStateUsing(fn ($state) => $state ? trans('admin/mount.toggles.read_only') : trans('admin/mount.toggles.writable')),
]) ])
->actions([ ->recordActions([
ViewAction::make() ViewAction::make()
->hidden(fn ($record) => static::canEdit($record)), ->hidden(fn ($record) => static::canEdit($record)),
EditAction::make(), EditAction::make(),
@ -104,10 +111,13 @@ class MountResource extends Resource
]); ]);
} }
public static function defaultForm(Form $form): Form /**
* @throws Exception
*/
public static function defaultForm(Schema $schema): Schema
{ {
return $form return $schema
->schema([ ->components([
Section::make()->schema([ Section::make()->schema([
TextInput::make('name') TextInput::make('name')
->label(trans('admin/mount.name')) ->label(trans('admin/mount.name'))
@ -176,10 +186,10 @@ class MountResource extends Resource
public static function getDefaultPages(): array public static function getDefaultPages(): array
{ {
return [ return [
'index' => Pages\ListMounts::route('/'), 'index' => ListMounts::route('/'),
'create' => Pages\CreateMount::route('/create'), 'create' => CreateMount::route('/create'),
'view' => Pages\ViewMount::route('/{record}'), 'view' => ViewMount::route('/{record}'),
'edit' => Pages\EditMount::route('/{record}/edit'), 'edit' => EditMount::route('/{record}/edit'),
]; ];
} }

View File

@ -1,8 +1,8 @@
<?php <?php
namespace App\Filament\Admin\Resources\MountResource\Pages; namespace App\Filament\Admin\Resources\Mounts\Pages;
use App\Filament\Admin\Resources\MountResource; use App\Filament\Admin\Resources\Mounts\MountResource;
use App\Traits\Filament\CanCustomizeHeaderActions; use App\Traits\Filament\CanCustomizeHeaderActions;
use App\Traits\Filament\CanCustomizeHeaderWidgets; use App\Traits\Filament\CanCustomizeHeaderWidgets;
use Filament\Actions\Action; use Filament\Actions\Action;

View File

@ -1,8 +1,8 @@
<?php <?php
namespace App\Filament\Admin\Resources\MountResource\Pages; namespace App\Filament\Admin\Resources\Mounts\Pages;
use App\Filament\Admin\Resources\MountResource; use App\Filament\Admin\Resources\Mounts\MountResource;
use App\Traits\Filament\CanCustomizeHeaderActions; use App\Traits\Filament\CanCustomizeHeaderActions;
use App\Traits\Filament\CanCustomizeHeaderWidgets; use App\Traits\Filament\CanCustomizeHeaderWidgets;
use Filament\Actions\Action; use Filament\Actions\Action;

View File

@ -1,8 +1,8 @@
<?php <?php
namespace App\Filament\Admin\Resources\MountResource\Pages; namespace App\Filament\Admin\Resources\Mounts\Pages;
use App\Filament\Admin\Resources\MountResource; use App\Filament\Admin\Resources\Mounts\MountResource;
use App\Models\Mount; use App\Models\Mount;
use App\Traits\Filament\CanCustomizeHeaderActions; use App\Traits\Filament\CanCustomizeHeaderActions;
use App\Traits\Filament\CanCustomizeHeaderWidgets; use App\Traits\Filament\CanCustomizeHeaderWidgets;

View File

@ -1,8 +1,8 @@
<?php <?php
namespace App\Filament\Admin\Resources\MountResource\Pages; namespace App\Filament\Admin\Resources\Mounts\Pages;
use App\Filament\Admin\Resources\MountResource; use App\Filament\Admin\Resources\Mounts\MountResource;
use App\Traits\Filament\CanCustomizeHeaderActions; use App\Traits\Filament\CanCustomizeHeaderActions;
use App\Traits\Filament\CanCustomizeHeaderWidgets; use App\Traits\Filament\CanCustomizeHeaderWidgets;
use Filament\Actions\Action; use Filament\Actions\Action;

View File

@ -1,9 +1,13 @@
<?php <?php
namespace App\Filament\Admin\Resources; namespace App\Filament\Admin\Resources\Nodes;
use App\Filament\Admin\Resources\NodeResource\Pages; use App\Enums\CustomizationKey;
use App\Filament\Admin\Resources\NodeResource\RelationManagers; use App\Filament\Admin\Resources\Nodes\RelationManagers\AllocationsRelationManager;
use App\Filament\Admin\Resources\Nodes\RelationManagers\NodesRelationManager;
use App\Filament\Admin\Resources\Nodes\Pages\ListNodes;
use App\Filament\Admin\Resources\Nodes\Pages\CreateNode;
use App\Filament\Admin\Resources\Nodes\Pages\EditNode;
use App\Models\Node; use App\Models\Node;
use App\Traits\Filament\CanCustomizePages; use App\Traits\Filament\CanCustomizePages;
use App\Traits\Filament\CanCustomizeRelations; use App\Traits\Filament\CanCustomizeRelations;
@ -19,7 +23,7 @@ class NodeResource extends Resource
protected static ?string $model = Node::class; protected static ?string $model = Node::class;
protected static ?string $navigationIcon = 'tabler-server-2'; protected static string|\BackedEnum|null $navigationIcon = 'tabler-server-2';
protected static ?string $recordTitleAttribute = 'name'; protected static ?string $recordTitleAttribute = 'name';
@ -40,8 +44,7 @@ class NodeResource extends Resource
public static function getNavigationGroup(): ?string public static function getNavigationGroup(): ?string
{ {
return !empty(auth()->user()->getCustomization()['top_navigation']) ? false : trans('admin/dashboard.server'); return auth()->user()->getCustomization(CustomizationKey::TopNavigation) ? false : trans('admin/dashboard.server');
} }
public static function getNavigationBadge(): ?string public static function getNavigationBadge(): ?string
@ -53,8 +56,8 @@ class NodeResource extends Resource
public static function getDefaultRelations(): array public static function getDefaultRelations(): array
{ {
return [ return [
RelationManagers\AllocationsRelationManager::class, AllocationsRelationManager::class,
RelationManagers\NodesRelationManager::class, NodesRelationManager::class,
]; ];
} }
@ -62,9 +65,9 @@ class NodeResource extends Resource
public static function getDefaultPages(): array public static function getDefaultPages(): array
{ {
return [ return [
'index' => Pages\ListNodes::route('/'), 'index' => ListNodes::route('/'),
'create' => Pages\CreateNode::route('/create'), 'create' => CreateNode::route('/create'),
'edit' => Pages\EditNode::route('/{record}/edit'), 'edit' => EditNode::route('/{record}/edit'),
]; ];
} }

View File

@ -1,25 +1,26 @@
<?php <?php
namespace App\Filament\Admin\Resources\NodeResource\Pages; namespace App\Filament\Admin\Resources\Nodes\Pages;
use App\Filament\Admin\Resources\NodeResource; use App\Filament\Admin\Resources\Nodes\NodeResource;
use App\Models\Node; use App\Models\Node;
use App\Traits\Filament\CanCustomizeHeaderActions; use App\Traits\Filament\CanCustomizeHeaderActions;
use App\Traits\Filament\CanCustomizeHeaderWidgets; use App\Traits\Filament\CanCustomizeHeaderWidgets;
use Filament\Forms; use Exception;
use Filament\Forms\Components\Actions\Action; use Filament\Actions\Action;
use Filament\Forms\Components\Grid;
use Filament\Forms\Components\Hidden; use Filament\Forms\Components\Hidden;
use Filament\Forms\Components\TagsInput; use Filament\Forms\Components\TagsInput;
use Filament\Forms\Components\TextInput; use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\ToggleButtons; use Filament\Forms\Components\ToggleButtons;
use Filament\Forms\Components\Wizard; use Filament\Schemas\Components\Grid;
use Filament\Forms\Components\Wizard\Step; use Filament\Schemas\Components\Wizard;
use Filament\Forms\Get; use Filament\Schemas\Components\Wizard\Step;
use Filament\Forms\Set; use Filament\Schemas\Components\Utilities\Get;
use Filament\Schemas\Components\Utilities\Set;
use Filament\Resources\Pages\CreateRecord; use Filament\Resources\Pages\CreateRecord;
use Illuminate\Support\Facades\Blade; use Illuminate\Support\Facades\Blade;
use Illuminate\Support\HtmlString; use Illuminate\Support\HtmlString;
use Filament\Schemas\Schema;
class CreateNode extends CreateRecord class CreateNode extends CreateRecord
{ {
@ -30,10 +31,13 @@ class CreateNode extends CreateRecord
protected static bool $canCreateAnother = false; protected static bool $canCreateAnother = false;
public function form(Forms\Form $form): Forms\Form /**
* @throws Exception
*/
public function form(Schema $schema): Schema
{ {
return $form return $schema
->schema([ ->components([
Wizard::make([ Wizard::make([
Step::make('basic') Step::make('basic')
->label(trans('admin/node.tabs.basic_settings')) ->label(trans('admin/node.tabs.basic_settings'))

View File

@ -1,8 +1,13 @@
<?php <?php
namespace App\Filament\Admin\Resources\NodeResource\Pages; namespace App\Filament\Admin\Resources\Nodes\Pages;
use App\Filament\Admin\Resources\NodeResource; use Filament\Actions\Action;
use Filament\Actions\DeleteAction;
use Filament\Schemas\Components\Actions;
use Filament\Schemas\Components\Grid;
use Throwable;
use App\Filament\Admin\Resources\Nodes\NodeResource;
use App\Models\Node; use App\Models\Node;
use App\Repositories\Daemon\DaemonConfigurationRepository; use App\Repositories\Daemon\DaemonConfigurationRepository;
use App\Services\Helpers\SoftwareVersionService; use App\Services\Helpers\SoftwareVersionService;
@ -11,28 +16,25 @@ use App\Services\Nodes\NodeUpdateService;
use App\Traits\Filament\CanCustomizeHeaderActions; use App\Traits\Filament\CanCustomizeHeaderActions;
use App\Traits\Filament\CanCustomizeHeaderWidgets; use App\Traits\Filament\CanCustomizeHeaderWidgets;
use Exception; use Exception;
use Filament\Actions;
use Filament\Forms;
use Filament\Forms\Components\Actions as FormActions;
use Filament\Forms\Components\Fieldset;
use Filament\Forms\Components\Grid;
use Filament\Forms\Components\Hidden; use Filament\Forms\Components\Hidden;
use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\Tabs;
use Filament\Forms\Components\Tabs\Tab;
use Filament\Forms\Components\TagsInput; use Filament\Forms\Components\TagsInput;
use Filament\Forms\Components\Textarea; use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput; use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\ToggleButtons; use Filament\Forms\Components\ToggleButtons;
use Filament\Forms\Components\View; use Filament\Infolists\Components\TextEntry;
use Filament\Forms\Get; use Filament\Schemas\Components\Fieldset;
use Filament\Forms\Set; 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\Notifications\Notification; use Filament\Notifications\Notification;
use Filament\Resources\Pages\EditRecord; use Filament\Resources\Pages\EditRecord;
use Filament\Schemas\Components\StateCasts\BooleanStateCast;
use Filament\Schemas\Components\View;
use Filament\Schemas\Schema;
use Filament\Support\Enums\Alignment; use Filament\Support\Enums\Alignment;
use Illuminate\Http\Client\ConnectionException; use Illuminate\Http\Client\ConnectionException;
use Illuminate\Support\HtmlString; use Illuminate\Support\HtmlString;
use Webbingbrasil\FilamentCopyActions\Forms\Actions\CopyAction;
class EditNode extends EditRecord class EditNode extends EditRecord
{ {
@ -51,9 +53,12 @@ class EditNode extends EditRecord
$this->nodeUpdateService = $nodeUpdateService; $this->nodeUpdateService = $nodeUpdateService;
} }
public function form(Forms\Form $form): Forms\Form /**
* @throws Throwable
*/
public function form(Schema $schema): Schema
{ {
return $form->schema([ return $schema->components([
Tabs::make('Tabs') Tabs::make('Tabs')
->columns([ ->columns([
'default' => 2, 'default' => 2,
@ -77,19 +82,20 @@ class EditNode extends EditRecord
Fieldset::make() Fieldset::make()
->label(trans('admin/node.node_info')) ->label(trans('admin/node.node_info'))
->columns(4) ->columns(4)
->columnSpanFull()
->schema([ ->schema([
Placeholder::make('') TextEntry::make('wings_version')
->label(trans('admin/node.wings_version')) ->label(trans('admin/node.wings_version'))
->content(fn (Node $node, SoftwareVersionService $versionService) => ($node->systemInformation()['version'] ?? trans('admin/node.unknown')) . ' ' . trans('admin/node.latest', ['version' => $versionService->latestWingsVersion()])), ->state(fn (Node $node, SoftwareVersionService $versionService) => ($node->systemInformation()['version'] ?? trans('admin/node.unknown')) . ' ' . trans('admin/node.latest', ['version' => $versionService->latestWingsVersion()])),
Placeholder::make('') TextEntry::make('cpu_threads')
->label(trans('admin/node.cpu_threads')) ->label(trans('admin/node.cpu_threads'))
->content(fn (Node $node) => $node->systemInformation()['cpu_count'] ?? 0), ->state(fn (Node $node) => $node->systemInformation()['cpu_count'] ?? 0),
Placeholder::make('') TextEntry::make('architecture')
->label(trans('admin/node.architecture')) ->label(trans('admin/node.architecture'))
->content(fn (Node $node) => $node->systemInformation()['architecture'] ?? trans('admin/node.unknown')), ->state(fn (Node $node) => $node->systemInformation()['architecture'] ?? trans('admin/node.unknown')),
Placeholder::make('') TextEntry::make('kernel')
->label(trans('admin/node.kernel')) ->label(trans('admin/node.kernel'))
->content(fn (Node $node) => $node->systemInformation()['kernel_version'] ?? trans('admin/node.unknown')), ->state(fn (Node $node) => $node->systemInformation()['kernel_version'] ?? trans('admin/node.unknown')),
]), ]),
View::make('filament.components.node-cpu-chart') View::make('filament.components.node-cpu-chart')
->columnSpan([ ->columnSpan([
@ -176,13 +182,14 @@ class EditNode extends EditRecord
->default(null) ->default(null)
->hint(fn (Get $get) => $get('ip')) ->hint(fn (Get $get) => $get('ip'))
->hintColor('success') ->hintColor('success')
->stateCast(new BooleanStateCast(false, true))
->options([ ->options([
true => trans('admin/node.valid'), 1 => trans('admin/node.valid'),
false => trans('admin/node.invalid'), 0 => trans('admin/node.invalid'),
]) ])
->colors([ ->colors([
true => 'success', 1 => 'success',
false => 'danger', 0 => 'danger',
]) ])
->columnSpan(1), ->columnSpan(1),
TextInput::make('daemon_connect') TextInput::make('daemon_connect')
@ -285,7 +292,7 @@ class EditNode extends EditRecord
'lg' => 2, 'lg' => 2,
]) ])
->label(trans('admin/node.node_uuid')) ->label(trans('admin/node.node_uuid'))
->hintAction(fn () => request()->isSecure() ? CopyAction::make() : null) ->hintCopy()
->disabled(), ->disabled(),
TagsInput::make('tags') TagsInput::make('tags')
->label(trans('admin/node.tags')) ->label(trans('admin/node.tags'))
@ -339,14 +346,16 @@ class EditNode extends EditRecord
'md' => 1, 'md' => 1,
'lg' => 3, 'lg' => 3,
]) ])
->label(trans('admin/node.use_for_deploy'))->inline() ->label(trans('admin/node.use_for_deploy'))
->inline()
->stateCast(new BooleanStateCast(false, true))
->options([ ->options([
true => trans('admin/node.yes'), 1 => trans('admin/node.yes'),
false => trans('admin/node.no'), 0 => trans('admin/node.no'),
]) ])
->colors([ ->colors([
true => 'success', 1 => 'success',
false => 'danger', 0 => 'danger',
]), ]),
ToggleButtons::make('maintenance_mode') ToggleButtons::make('maintenance_mode')
->columnSpan([ ->columnSpan([
@ -355,16 +364,18 @@ class EditNode extends EditRecord
'md' => 1, 'md' => 1,
'lg' => 3, 'lg' => 3,
]) ])
->label(trans('admin/node.maintenance_mode'))->inline() ->label(trans('admin/node.maintenance_mode'))
->inline()
->hinticon('tabler-question-mark') ->hinticon('tabler-question-mark')
->hintIconTooltip(trans('admin/node.maintenance_mode_help')) ->hintIconTooltip(trans('admin/node.maintenance_mode_help'))
->stateCast(new BooleanStateCast(false, true))
->options([ ->options([
true => trans('admin/node.enabled'), 1 => trans('admin/node.enabled'),
false => trans('admin/node.disabled'), 0 => trans('admin/node.disabled'),
]) ])
->colors([ ->colors([
false => 'success', 1 => 'danger',
true => 'danger', 0 => 'success',
]), ]),
Grid::make() Grid::make()
->columns([ ->columns([
@ -382,13 +393,14 @@ class EditNode extends EditRecord
->afterStateUpdated(fn (Set $set) => $set('memory_overallocate', 0)) ->afterStateUpdated(fn (Set $set) => $set('memory_overallocate', 0))
->formatStateUsing(fn (Get $get) => $get('memory') == 0) ->formatStateUsing(fn (Get $get) => $get('memory') == 0)
->live() ->live()
->stateCast(new BooleanStateCast(false, true))
->options([ ->options([
true => trans('admin/node.unlimited'), 1 => trans('admin/node.unlimited'),
false => trans('admin/node.limited'), 0 => trans('admin/node.limited'),
]) ])
->colors([ ->colors([
true => 'primary', 1 => 'primary',
false => 'warning', 0 => 'warning',
]) ])
->columnSpan([ ->columnSpan([
'default' => 1, 'default' => 1,
@ -427,6 +439,7 @@ class EditNode extends EditRecord
->suffix('%'), ->suffix('%'),
]), ]),
Grid::make() Grid::make()
->columnSpanFull()
->columns([ ->columns([
'default' => 1, 'default' => 1,
'sm' => 1, 'sm' => 1,
@ -441,13 +454,14 @@ class EditNode extends EditRecord
->afterStateUpdated(fn (Set $set) => $set('disk', 0)) ->afterStateUpdated(fn (Set $set) => $set('disk', 0))
->afterStateUpdated(fn (Set $set) => $set('disk_overallocate', 0)) ->afterStateUpdated(fn (Set $set) => $set('disk_overallocate', 0))
->formatStateUsing(fn (Get $get) => $get('disk') == 0) ->formatStateUsing(fn (Get $get) => $get('disk') == 0)
->stateCast(new BooleanStateCast(false, true))
->options([ ->options([
true => trans('admin/node.unlimited'), 1 => trans('admin/node.unlimited'),
false => trans('admin/node.limited'), 0 => trans('admin/node.limited'),
]) ])
->colors([ ->colors([
true => 'primary', 1 => 'primary',
false => 'warning', 0 => 'warning',
]) ])
->columnSpan([ ->columnSpan([
'default' => 1, 'default' => 1,
@ -496,13 +510,14 @@ class EditNode extends EditRecord
->afterStateUpdated(fn (Set $set) => $set('cpu', 0)) ->afterStateUpdated(fn (Set $set) => $set('cpu', 0))
->afterStateUpdated(fn (Set $set) => $set('cpu_overallocate', 0)) ->afterStateUpdated(fn (Set $set) => $set('cpu_overallocate', 0))
->formatStateUsing(fn (Get $get) => $get('cpu') == 0) ->formatStateUsing(fn (Get $get) => $get('cpu') == 0)
->stateCast(new BooleanStateCast(false, true))
->options([ ->options([
true => trans('admin/node.unlimited'), 1 => trans('admin/node.unlimited'),
false => trans('admin/node.limited'), 0 => trans('admin/node.limited'),
]) ])
->colors([ ->colors([
true => 'primary', 1 => 'primary',
false => 'warning', 0 => 'warning',
]) ])
->columnSpan(2), ->columnSpan(2),
TextInput::make('cpu') TextInput::make('cpu')
@ -530,21 +545,22 @@ class EditNode extends EditRecord
->label(trans('admin/node.tabs.config_file')) ->label(trans('admin/node.tabs.config_file'))
->icon('tabler-code') ->icon('tabler-code')
->schema([ ->schema([
Placeholder::make('instructions') TextEntry::make('instructions')
->label(trans('admin/node.instructions')) ->label(trans('admin/node.instructions'))
->columnSpanFull() ->columnSpanFull()
->content(new HtmlString(trans('admin/node.instructions_help'))), ->state(new HtmlString(trans('admin/node.instructions_help'))),
Textarea::make('config') Textarea::make('config')
->label('/etc/pelican/config.yml') ->label('/etc/pelican/config.yml')
->disabled() ->disabled()
->rows(19) ->rows(19)
->hintAction(fn () => request()->isSecure() ? CopyAction::make() : null) ->hintCopy()
->columnSpanFull(), ->columnSpanFull(),
Grid::make() Grid::make()
->columns() ->columns()
->columnSpanFull()
->schema([ ->schema([
FormActions::make([ Actions::make([
FormActions\Action::make('autoDeploy') Action::make('autoDeploy')
->label(trans('admin/node.auto_deploy')) ->label(trans('admin/node.auto_deploy'))
->color('primary') ->color('primary')
->modalHeading(trans('admin/node.auto_deploy')) ->modalHeading(trans('admin/node.auto_deploy'))
@ -552,7 +568,7 @@ class EditNode extends EditRecord
->modalSubmitAction(false) ->modalSubmitAction(false)
->modalCancelAction(false) ->modalCancelAction(false)
->modalFooterActionsAlignment(Alignment::Center) ->modalFooterActionsAlignment(Alignment::Center)
->form([ ->schema([
ToggleButtons::make('docker') ToggleButtons::make('docker')
->label(trans('admin/node.auto_label')) ->label(trans('admin/node.auto_label'))
->live() ->live()
@ -560,28 +576,29 @@ class EditNode extends EditRecord
->inline() ->inline()
->default(false) ->default(false)
->afterStateUpdated(fn (bool $state, NodeAutoDeployService $service, Node $node, Set $set) => $set('generatedToken', $service->handle(request(), $node, $state))) ->afterStateUpdated(fn (bool $state, NodeAutoDeployService $service, Node $node, Set $set) => $set('generatedToken', $service->handle(request(), $node, $state)))
->stateCast(new BooleanStateCast(false, true))
->options([ ->options([
false => trans('admin/node.standalone'), 0 => trans('admin/node.standalone'),
true => trans('admin/node.docker'), 1 => trans('admin/node.docker'),
]) ])
->colors([ ->colors([
false => 'primary', 0 => 'primary',
true => 'success', 1 => 'success',
]) ])
->columnSpan(1), ->columnSpan(1),
Textarea::make('generatedToken') Textarea::make('generatedToken')
->label(trans('admin/node.auto_command')) ->label(trans('admin/node.auto_command'))
->readOnly() ->readOnly()
->autosize() ->autosize()
->hintAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null) ->hintCopy()
->formatStateUsing(fn (NodeAutoDeployService $service, Node $node, Set $set, Get $get) => $set('generatedToken', $service->handle(request(), $node, $get('docker')))), ->formatStateUsing(fn (NodeAutoDeployService $service, Node $node, Set $set, Get $get) => $set('generatedToken', $service->handle(request(), $node, $get('docker')))),
]) ])
->mountUsing(function (Forms\Form $form) { ->mountUsing(function (Schema $schema) {
$form->fill(); $schema->fill();
}), }),
])->fullWidth(), ])->fullWidth(),
FormActions::make([ Actions::make([
FormActions\Action::make('resetKey') Action::make('resetKey')
->label(trans('admin/node.reset_token')) ->label(trans('admin/node.reset_token'))
->color('danger') ->color('danger')
->requiresConfirmation() ->requiresConfirmation()
@ -634,11 +651,11 @@ class EditNode extends EditRecord
return []; return [];
} }
/** @return array<Actions\Action|Actions\ActionGroup> */ /** @return array<Action|Actions> */
protected function getDefaultHeaderActions(): array protected function getDefaultHeaderActions(): array
{ {
return [ return [
Actions\DeleteAction::make() DeleteAction::make()
->disabled(fn (Node $node) => $node->servers()->count() > 0) ->disabled(fn (Node $node) => $node->servers()->count() > 0)
->label(fn (Node $node) => $node->servers()->count() > 0 ? trans('admin/node.node_has_servers') : trans('filament-actions::delete.single.label')), ->label(fn (Node $node) => $node->servers()->count() > 0 ? trans('admin/node.node_has_servers') : trans('filament-actions::delete.single.label')),
$this->getSaveFormAction()->formId('form'), $this->getSaveFormAction()->formId('form'),

View File

@ -1,17 +1,18 @@
<?php <?php
namespace App\Filament\Admin\Resources\NodeResource\Pages; namespace App\Filament\Admin\Resources\Nodes\Pages;
use App\Filament\Admin\Resources\NodeResource; use App\Filament\Admin\Resources\Nodes\NodeResource;
use App\Filament\Components\Tables\Columns\NodeHealthColumn; use App\Filament\Components\Tables\Columns\NodeHealthColumn;
use App\Filament\Components\Tables\Filters\TagsFilter; use App\Filament\Components\Tables\Filters\TagsFilter;
use App\Models\Node; use App\Models\Node;
use App\Traits\Filament\CanCustomizeHeaderActions; use App\Traits\Filament\CanCustomizeHeaderActions;
use App\Traits\Filament\CanCustomizeHeaderWidgets; use App\Traits\Filament\CanCustomizeHeaderWidgets;
use Filament\Actions; use Filament\Actions\Action;
use Filament\Actions\ActionGroup;
use Filament\Resources\Pages\ListRecords; use Filament\Resources\Pages\ListRecords;
use Filament\Tables\Actions\CreateAction; use Filament\Actions\CreateAction;
use Filament\Tables\Actions\EditAction; use Filament\Actions\EditAction;
use Filament\Tables\Columns\IconColumn; use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Columns\TextColumn; use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table; use Filament\Tables\Table;
@ -63,7 +64,7 @@ class ListNodes extends ListRecords
->sortable() ->sortable()
->icon('tabler-brand-docker'), ->icon('tabler-brand-docker'),
]) ])
->actions([ ->recordActions([
EditAction::make(), EditAction::make(),
]) ])
->emptyStateIcon('tabler-server-2') ->emptyStateIcon('tabler-server-2')
@ -78,11 +79,11 @@ class ListNodes extends ListRecords
]); ]);
} }
/** @return array<Actions\Action|Actions\ActionGroup> */ /** @return array<Action|ActionGroup> */
protected function getDefaultHeaderActions(): array protected function getDefaultHeaderActions(): array
{ {
return [ return [
Actions\CreateAction::make() CreateAction::make()
->hidden(fn () => Node::count() <= 0), ->hidden(fn () => Node::count() <= 0),
]; ];
} }

View File

@ -1,19 +1,20 @@
<?php <?php
namespace App\Filament\Admin\Resources\NodeResource\RelationManagers; namespace App\Filament\Admin\Resources\Nodes\RelationManagers;
use App\Filament\Admin\Resources\ServerResource\Pages\CreateServer; use App\Filament\Admin\Resources\Servers\Pages\CreateServer;
use App\Models\Allocation; use App\Models\Allocation;
use App\Models\Node; use App\Models\Node;
use App\Services\Allocations\AssignmentService; use App\Services\Allocations\AssignmentService;
use Exception;
use Filament\Actions\Action;
use Filament\Forms\Components\Select; use Filament\Forms\Components\Select;
use Filament\Forms\Components\TagsInput; use Filament\Forms\Components\TagsInput;
use Filament\Forms\Components\TextInput; use Filament\Forms\Components\TextInput;
use Filament\Forms\Get; use Filament\Schemas\Components\Utilities\Get;
use Filament\Forms\Set; use Filament\Schemas\Components\Utilities\Set;
use Filament\Resources\RelationManagers\RelationManager; use Filament\Resources\RelationManagers\RelationManager;
use Filament\Tables\Actions\Action; use Filament\Actions\DeleteBulkAction;
use Filament\Tables\Actions\DeleteBulkAction;
use Filament\Tables\Columns\SelectColumn; use Filament\Tables\Columns\SelectColumn;
use Filament\Tables\Columns\TextColumn; use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Columns\TextInputColumn; use Filament\Tables\Columns\TextInputColumn;
@ -26,13 +27,16 @@ class AllocationsRelationManager extends RelationManager
{ {
protected static string $relationship = 'allocations'; protected static string $relationship = 'allocations';
protected static ?string $icon = 'tabler-plug-connected'; protected static string|\BackedEnum|null $icon = 'tabler-plug-connected';
public function setTitle(): string public function setTitle(): string
{ {
return trans('admin/server.allocations'); return trans('admin/server.allocations');
} }
/**
* @throws Exception
*/
public function table(Table $table): Table public function table(Table $table): Table
{ {
return $table return $table
@ -79,7 +83,7 @@ class AllocationsRelationManager extends RelationManager
->headerActions([ ->headerActions([
Action::make('create new allocation') Action::make('create new allocation')
->label(trans('admin/node.create_allocation')) ->label(trans('admin/node.create_allocation'))
->form(fn () => [ ->schema(fn () => [
Select::make('allocation_ip') Select::make('allocation_ip')
->options(collect($this->getOwnerRecord()->ipAddresses())->mapWithKeys(fn (string $ip) => [$ip => $ip])) ->options(collect($this->getOwnerRecord()->ipAddresses())->mapWithKeys(fn (string $ip) => [$ip => $ip]))
->label(trans('admin/node.ip_address')) ->label(trans('admin/node.ip_address'))

View File

@ -1,6 +1,6 @@
<?php <?php
namespace App\Filament\Admin\Resources\NodeResource\RelationManagers; namespace App\Filament\Admin\Resources\Nodes\RelationManagers;
use App\Models\Server; use App\Models\Server;
use Filament\Resources\RelationManagers\RelationManager; use Filament\Resources\RelationManagers\RelationManager;
@ -12,7 +12,7 @@ class NodesRelationManager extends RelationManager
{ {
protected static string $relationship = 'servers'; protected static string $relationship = 'servers';
protected static ?string $icon = 'tabler-brand-docker'; protected static string|\BackedEnum|null $icon = 'tabler-brand-docker';
public function setTitle(): string public function setTitle(): string
{ {
@ -46,7 +46,7 @@ class NodesRelationManager extends RelationManager
->disabled(fn (Server $server) => $server->allocations->count() <= 1) ->disabled(fn (Server $server) => $server->allocations->count() <= 1)
->options(fn (Server $server) => $server->allocations->take(1)->mapWithKeys(fn ($allocation) => [$allocation->id => $allocation->address])) ->options(fn (Server $server) => $server->allocations->take(1)->mapWithKeys(fn ($allocation) => [$allocation->id => $allocation->address]))
->selectablePlaceholder(fn (SelectColumn $select) => !$select->isDisabled()) ->selectablePlaceholder(fn (SelectColumn $select) => !$select->isDisabled())
->placeholder('None') ->placeholder(trans('admin/node.none'))
->sortable(), ->sortable(),
TextColumn::make('memory')->label(trans('admin/node.memory'))->icon('tabler-device-desktop-analytics'), TextColumn::make('memory')->label(trans('admin/node.memory'))->icon('tabler-device-desktop-analytics'),
TextColumn::make('cpu')->label(trans('admin/node.cpu'))->icon('tabler-cpu'), TextColumn::make('cpu')->label(trans('admin/node.cpu'))->icon('tabler-cpu'),

View File

@ -1,6 +1,6 @@
<?php <?php
namespace App\Filament\Admin\Resources\NodeResource\Widgets; namespace App\Filament\Admin\Resources\Nodes\Widgets;
use App\Models\Node; use App\Models\Node;
use Filament\Support\RawJs; use Filament\Support\RawJs;
@ -8,9 +8,9 @@ use Filament\Widgets\ChartWidget;
class NodeCpuChart extends ChartWidget class NodeCpuChart extends ChartWidget
{ {
protected static ?string $pollingInterval = '5s'; protected ?string $pollingInterval = '5s';
protected static ?string $maxHeight = '300px'; protected ?string $maxHeight = '300px';
public Node $node; public Node $node;

View File

@ -1,6 +1,6 @@
<?php <?php
namespace App\Filament\Admin\Resources\NodeResource\Widgets; namespace App\Filament\Admin\Resources\Nodes\Widgets;
use App\Models\Node; use App\Models\Node;
use Filament\Support\RawJs; use Filament\Support\RawJs;
@ -8,9 +8,9 @@ use Filament\Widgets\ChartWidget;
class NodeMemoryChart extends ChartWidget class NodeMemoryChart extends ChartWidget
{ {
protected static ?string $pollingInterval = '5s'; protected ?string $pollingInterval = '5s';
protected static ?string $maxHeight = '300px'; protected ?string $maxHeight = '300px';
public Node $node; public Node $node;

View File

@ -1,19 +1,19 @@
<?php <?php
namespace App\Filament\Admin\Resources\NodeResource\Widgets; namespace App\Filament\Admin\Resources\Nodes\Widgets;
use App\Models\Node; use App\Models\Node;
use Filament\Widgets\ChartWidget; use Filament\Widgets\ChartWidget;
class NodeStorageChart extends ChartWidget class NodeStorageChart extends ChartWidget
{ {
protected static ?string $pollingInterval = '360s'; protected ?string $pollingInterval = '360s';
protected static ?string $maxHeight = '200px'; protected ?string $maxHeight = '200px';
public Node $node; public Node $node;
protected static ?array $options = [ protected ?array $options = [
'scales' => [ 'scales' => [
'x' => [ 'x' => [
'grid' => [ 'grid' => [

View File

@ -1,8 +1,8 @@
<?php <?php
namespace App\Filament\Admin\Resources\RoleResource\Pages; namespace App\Filament\Admin\Resources\Roles\Pages;
use App\Filament\Admin\Resources\RoleResource; use App\Filament\Admin\Resources\Roles\RoleResource;
use App\Models\Role; use App\Models\Role;
use App\Traits\Filament\CanCustomizeHeaderActions; use App\Traits\Filament\CanCustomizeHeaderActions;
use App\Traits\Filament\CanCustomizeHeaderWidgets; use App\Traits\Filament\CanCustomizeHeaderWidgets;

View File

@ -1,8 +1,8 @@
<?php <?php
namespace App\Filament\Admin\Resources\RoleResource\Pages; namespace App\Filament\Admin\Resources\Roles\Pages;
use App\Filament\Admin\Resources\RoleResource; use App\Filament\Admin\Resources\Roles\RoleResource;
use App\Models\Role; use App\Models\Role;
use App\Traits\Filament\CanCustomizeHeaderActions; use App\Traits\Filament\CanCustomizeHeaderActions;
use App\Traits\Filament\CanCustomizeHeaderWidgets; use App\Traits\Filament\CanCustomizeHeaderWidgets;

View File

@ -1,8 +1,8 @@
<?php <?php
namespace App\Filament\Admin\Resources\RoleResource\Pages; namespace App\Filament\Admin\Resources\Roles\Pages;
use App\Filament\Admin\Resources\RoleResource; use App\Filament\Admin\Resources\Roles\RoleResource;
use App\Traits\Filament\CanCustomizeHeaderActions; use App\Traits\Filament\CanCustomizeHeaderActions;
use App\Traits\Filament\CanCustomizeHeaderWidgets; use App\Traits\Filament\CanCustomizeHeaderWidgets;
use Filament\Actions\Action; use Filament\Actions\Action;

View File

@ -1,8 +1,8 @@
<?php <?php
namespace App\Filament\Admin\Resources\RoleResource\Pages; namespace App\Filament\Admin\Resources\Roles\Pages;
use App\Filament\Admin\Resources\RoleResource; use App\Filament\Admin\Resources\Roles\RoleResource;
use App\Traits\Filament\CanCustomizeHeaderActions; use App\Traits\Filament\CanCustomizeHeaderActions;
use App\Traits\Filament\CanCustomizeHeaderWidgets; use App\Traits\Filament\CanCustomizeHeaderWidgets;
use Filament\Actions\Action; use Filament\Actions\Action;

View File

@ -1,29 +1,35 @@
<?php <?php
namespace App\Filament\Admin\Resources; namespace App\Filament\Admin\Resources\Roles;
use App\Filament\Admin\Resources\RoleResource\Pages; use App\Enums\CustomizationKey;
use App\Filament\Admin\Resources\Roles\Pages\ListRoles;
use App\Filament\Admin\Resources\Roles\Pages\CreateRole;
use App\Filament\Admin\Resources\Roles\Pages\ViewRole;
use App\Filament\Admin\Resources\Roles\Pages\EditRole;
use BackedEnum;
use App\Models\Role; use App\Models\Role;
use Exception;
use Filament\Actions\Action;
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\CanCustomizePages;
use App\Traits\Filament\CanCustomizeRelations; use App\Traits\Filament\CanCustomizeRelations;
use App\Traits\Filament\CanModifyForm; use App\Traits\Filament\CanModifyForm;
use App\Traits\Filament\CanModifyTable; use App\Traits\Filament\CanModifyTable;
use Filament\Forms\Components\Actions\Action;
use Filament\Forms\Components\CheckboxList; use Filament\Forms\Components\CheckboxList;
use Filament\Forms\Components\Component; use Filament\Schemas\Schema;
use Filament\Forms\Components\Fieldset;
use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\Section;
use Filament\Forms\Components\Select; use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput; use Filament\Forms\Components\TextInput;
use Filament\Forms\Form; use Filament\Infolists\Components\TextEntry;
use Filament\Forms\Get;
use Filament\Resources\Pages\PageRegistration; use Filament\Resources\Pages\PageRegistration;
use Filament\Resources\Resource; use Filament\Resources\Resource;
use Filament\Tables\Actions\CreateAction; use Filament\Schemas\Components\Component;
use Filament\Tables\Actions\DeleteBulkAction; use Filament\Schemas\Components\Fieldset;
use Filament\Tables\Actions\EditAction; use Filament\Schemas\Components\Section;
use Filament\Tables\Actions\ViewAction; use Filament\Schemas\Components\Utilities\Get;
use Filament\Tables\Columns\TextColumn; use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table; use Filament\Tables\Table;
use Illuminate\Support\Str; use Illuminate\Support\Str;
@ -38,7 +44,7 @@ class RoleResource extends Resource
protected static ?string $model = Role::class; protected static ?string $model = Role::class;
protected static ?string $navigationIcon = 'tabler-users-group'; protected static string|\BackedEnum|null $navigationIcon = 'tabler-users-group';
protected static ?string $recordTitleAttribute = 'name'; protected static ?string $recordTitleAttribute = 'name';
@ -59,22 +65,24 @@ class RoleResource extends Resource
public static function getNavigationGroup(): ?string public static function getNavigationGroup(): ?string
{ {
return !empty(auth()->user()->getCustomization()['top_navigation']) ? trans('admin/dashboard.advanced') : trans('admin/dashboard.user'); return auth()->user()->getCustomization(CustomizationKey::TopNavigation) ? trans('admin/dashboard.advanced') : trans('admin/dashboard.user');
} }
public static function getNavigationBadge(): ?string public static function getNavigationBadge(): ?string
{ {
return static::getModel()::count() ?: null; return ($count = static::getModel()::count()) > 0 ? (string) $count : null;
} }
/**
* @throws Exception
*/
public static function defaultTable(Table $table): Table public static function defaultTable(Table $table): Table
{ {
return $table return $table
->columns([ ->columns([
TextColumn::make('name') TextColumn::make('name')
->label(trans('admin/role.name')) ->label(trans('admin/role.name'))
->sortable() ->sortable(),
->searchable(),
TextColumn::make('permissions_count') TextColumn::make('permissions_count')
->label(trans('admin/role.permissions')) ->label(trans('admin/role.permissions'))
->badge() ->badge()
@ -90,7 +98,7 @@ class RoleResource extends Resource
->counts('users') ->counts('users')
->icon('tabler-users'), ->icon('tabler-users'),
]) ])
->actions([ ->recordActions([
ViewAction::make() ViewAction::make()
->hidden(fn ($record) => static::canEdit($record)), ->hidden(fn ($record) => static::canEdit($record)),
EditAction::make(), EditAction::make(),
@ -107,7 +115,10 @@ class RoleResource extends Resource
]); ]);
} }
public static function defaultForm(Form $form): Form /**
* @throws Exception
*/
public static function defaultForm(Schema $schema): Schema
{ {
$permissionSections = []; $permissionSections = [];
@ -121,9 +132,9 @@ class RoleResource extends Resource
$permissionSections[] = self::makeSection($model, $options); $permissionSections[] = self::makeSection($model, $options);
} }
return $form return $schema
->columns(1) ->columns(1)
->schema([ ->components([
TextInput::make('name') TextInput::make('name')
->label(trans('admin/role.name')) ->label(trans('admin/role.name'))
->required() ->required()
@ -136,9 +147,9 @@ class RoleResource extends Resource
->columns(3) ->columns(3)
->schema($permissionSections) ->schema($permissionSections)
->hidden(fn (Get $get) => $get('name') === Role::ROOT_ADMIN), ->hidden(fn (Get $get) => $get('name') === Role::ROOT_ADMIN),
Placeholder::make('permissions') TextEntry::make('permissions')
->label(trans('admin/role.permissions')) ->label(trans('admin/role.permissions'))
->content(trans('admin/role.root_admin', ['role' => Role::ROOT_ADMIN])) ->state(trans('admin/role.root_admin', ['role' => Role::ROOT_ADMIN]))
->visible(fn (Get $get) => $get('name') === Role::ROOT_ADMIN), ->visible(fn (Get $get) => $get('name') === Role::ROOT_ADMIN),
Select::make('nodes') Select::make('nodes')
->label(trans('admin/role.nodes')) ->label(trans('admin/role.nodes'))
@ -152,7 +163,9 @@ class RoleResource extends Resource
} }
/** /**
* @param string[]|int[]|Permission[]|\BackedEnum[] $options * @param string[]|int[]|Permission[]|BackedEnum[] $options
*
* @throws Exception
*/ */
private static function makeSection(string $model, array $options): Section private static function makeSection(string $model, array $options): Section
{ {
@ -213,10 +226,10 @@ class RoleResource extends Resource
public static function getDefaultPages(): array public static function getDefaultPages(): array
{ {
return [ return [
'index' => Pages\ListRoles::route('/'), 'index' => ListRoles::route('/'),
'create' => Pages\CreateRole::route('/create'), 'create' => CreateRole::route('/create'),
'view' => Pages\ViewRole::route('/{record}'), 'view' => ViewRole::route('/{record}'),
'edit' => Pages\EditRole::route('/{record}/edit'), 'edit' => EditRole::route('/{record}/edit'),
]; ];
} }
} }

View File

@ -1,8 +1,8 @@
<?php <?php
namespace App\Filament\Admin\Resources\ServerResource\Pages; namespace App\Filament\Admin\Resources\Servers\Pages;
use App\Filament\Admin\Resources\ServerResource; use App\Filament\Admin\Resources\Servers\ServerResource;
use App\Filament\Components\Forms\Fields\StartupVariable; use App\Filament\Components\Forms\Fields\StartupVariable;
use App\Models\Allocation; use App\Models\Allocation;
use App\Models\Egg; use App\Models\Egg;
@ -15,33 +15,33 @@ use App\Services\Users\UserCreationService;
use App\Traits\Filament\CanCustomizeHeaderActions; use App\Traits\Filament\CanCustomizeHeaderActions;
use App\Traits\Filament\CanCustomizeHeaderWidgets; use App\Traits\Filament\CanCustomizeHeaderWidgets;
use Exception; use Exception;
use Filament\Forms; use Filament\Actions\Action;
use Filament\Forms\Components\Actions\Action; use Filament\Schemas\Components\Fieldset;
use Filament\Forms\Components\Fieldset;
use Filament\Forms\Components\Grid;
use Filament\Forms\Components\Hidden; use Filament\Forms\Components\Hidden;
use Filament\Forms\Components\KeyValue; use Filament\Forms\Components\KeyValue;
use Filament\Forms\Components\Placeholder; use Filament\Infolists\Components\TextEntry;
use Filament\Forms\Components\Repeater; use Filament\Forms\Components\Repeater;
use Filament\Forms\Components\Section;
use Filament\Forms\Components\Select; use Filament\Forms\Components\Select;
use Filament\Forms\Components\TagsInput; use Filament\Forms\Components\TagsInput;
use Filament\Forms\Components\Textarea; use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput; use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\ToggleButtons; use Filament\Forms\Components\ToggleButtons;
use Filament\Forms\Components\Wizard; use Filament\Schemas\Components\Grid;
use Filament\Forms\Components\Wizard\Step; use Filament\Schemas\Components\Section;
use Filament\Forms\Form; use Filament\Schemas\Components\Utilities\Get;
use Filament\Forms\Get; use Filament\Schemas\Components\Utilities\Set;
use Filament\Forms\Set;
use Filament\Notifications\Notification; use Filament\Notifications\Notification;
use Filament\Resources\Pages\CreateRecord; use Filament\Resources\Pages\CreateRecord;
use Filament\Schemas\Components\Wizard;
use Filament\Schemas\Components\Wizard\Step;
use Filament\Support\Exceptions\Halt; use Filament\Support\Exceptions\Halt;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Blade; use Illuminate\Support\Facades\Blade;
use Illuminate\Support\HtmlString; use Illuminate\Support\HtmlString;
use LogicException; use LogicException;
use Filament\Schemas\Schema;
use Random\RandomException;
class CreateServer extends CreateRecord class CreateServer extends CreateRecord
{ {
@ -61,10 +61,14 @@ class CreateServer extends CreateRecord
$this->serverCreationService = $serverCreationService; $this->serverCreationService = $serverCreationService;
} }
public function form(Form $form): Form /**
* @throws RandomException
* @throws Exception
*/
public function form(Schema $schema): Schema
{ {
return $form return $schema
->schema([ ->components([
Wizard::make([ Wizard::make([
Step::make('Information') Step::make('Information')
->label(trans('admin/server.tabs.information')) ->label(trans('admin/server.tabs.information'))
@ -79,7 +83,7 @@ class CreateServer extends CreateRecord
TextInput::make('name') TextInput::make('name')
->prefixIcon('tabler-server') ->prefixIcon('tabler-server')
->label(trans('admin/server.name')) ->label(trans('admin/server.name'))
->suffixAction(Forms\Components\Actions\Action::make('random') ->suffixAction(Action::make('random')
->icon('tabler-dice-' . random_int(1, 6)) ->icon('tabler-dice-' . random_int(1, 6))
->action(function (Set $set, Get $get) { ->action(function (Set $set, Get $get) {
$egg = Egg::find($get('egg_id')); $egg = Egg::find($get('egg_id'));
@ -104,7 +108,7 @@ class CreateServer extends CreateRecord
'sm' => 2, 'sm' => 2,
'md' => 2, 'md' => 2,
]) ])
->unique(ignoreRecord: true) ->unique()
->maxLength(255), ->maxLength(255),
Select::make('node_id') Select::make('node_id')
@ -418,14 +422,12 @@ class CreateServer extends CreateRecord
->collapsible() ->collapsible()
->columnSpanFull() ->columnSpanFull()
->schema([ ->schema([
Placeholder::make(trans('admin/server.select_egg')) TextEntry::make(trans('admin/server.select_egg'))
->hidden(fn (Get $get) => $get('egg_id')), ->hidden(fn (Get $get) => $get('egg_id')),
TextEntry::make(trans('admin/server.no_variables'))
Placeholder::make(trans('admin/server.no_variables'))
->hidden(fn (Get $get) => !$get('egg_id') || ->hidden(fn (Get $get) => !$get('egg_id') ||
Egg::query()->find($get('egg_id'))?->variables()?->count() Egg::query()->find($get('egg_id'))?->variables()?->count()
), ),
Repeater::make('server_variables') Repeater::make('server_variables')
->hiddenLabel() ->hiddenLabel()
->relationship('serverVariables', fn (Builder $query) => $query->orderByPowerJoins('variable.sort')) ->relationship('serverVariables', fn (Builder $query) => $query->orderByPowerJoins('variable.sort'))

View File

@ -1,24 +1,21 @@
<?php <?php
namespace App\Filament\Admin\Resources\ServerResource\Pages; namespace App\Filament\Admin\Resources\Servers\Pages;
use AbdelhamidErrahmouni\FilamentMonacoEditor\MonacoEditor;
use App\Enums\SuspendAction; use App\Enums\SuspendAction;
use App\Filament\Admin\Resources\ServerResource; use App\Filament\Admin\Resources\Servers\RelationManagers\AllocationsRelationManager;
use App\Filament\Components\Forms\Actions\PreviewStartupAction; use App\Filament\Admin\Resources\Servers\RelationManagers\DatabasesRelationManager;
use App\Filament\Components\Forms\Actions\RotateDatabasePasswordAction; use App\Filament\Admin\Resources\Servers\ServerResource;
use App\Filament\Components\Actions\PreviewStartupAction;
use App\Filament\Components\Forms\Fields\StartupVariable; use App\Filament\Components\Forms\Fields\StartupVariable;
use App\Filament\Server\Pages\Console; use App\Filament\Server\Pages\Console;
use App\Models\Allocation; use App\Models\Allocation;
use App\Models\Database;
use App\Models\DatabaseHost;
use App\Models\Egg; use App\Models\Egg;
use App\Models\Node; use App\Models\Node;
use App\Models\Server; use App\Models\Server;
use App\Models\ServerVariable; use App\Models\ServerVariable;
use App\Models\User; use App\Models\User;
use App\Repositories\Daemon\DaemonServerRepository; use App\Repositories\Daemon\DaemonServerRepository;
use App\Services\Databases\DatabaseManagementService;
use App\Services\Eggs\EggChangerService; use App\Services\Eggs\EggChangerService;
use App\Services\Servers\RandomWordService; use App\Services\Servers\RandomWordService;
use App\Services\Servers\ReinstallServerService; use App\Services\Servers\ReinstallServerService;
@ -29,35 +26,37 @@ use App\Services\Servers\TransferServerService;
use App\Traits\Filament\CanCustomizeHeaderActions; use App\Traits\Filament\CanCustomizeHeaderActions;
use App\Traits\Filament\CanCustomizeHeaderWidgets; use App\Traits\Filament\CanCustomizeHeaderWidgets;
use Exception; use Exception;
use Filament\Actions; use Filament\Actions\Action;
use Filament\Forms\Components\Actions as FormActions; use Filament\Actions\ActionGroup;
use Filament\Forms\Components\Actions\Action; use Filament\Forms\Components\CodeEditor;
use Filament\Forms\Components\Component;
use Filament\Forms\Components\Fieldset;
use Filament\Forms\Components\Grid;
use Filament\Forms\Components\Hidden; use Filament\Forms\Components\Hidden;
use Filament\Forms\Components\KeyValue; use Filament\Forms\Components\KeyValue;
use Filament\Forms\Components\Repeater; use Filament\Forms\Components\Repeater;
use Filament\Forms\Components\Select; use Filament\Forms\Components\Select;
use Filament\Forms\Components\Tabs; use Filament\Schemas\Components\Grid;
use Filament\Forms\Components\Tabs\Tab; use Filament\Schemas\Components\Tabs;
use Filament\Schemas\Components\Tabs\Tab;
use Filament\Forms\Components\TagsInput; use Filament\Forms\Components\TagsInput;
use Filament\Forms\Components\Textarea; use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput; use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle; use Filament\Forms\Components\Toggle;
use Filament\Forms\Components\ToggleButtons; use Filament\Forms\Components\ToggleButtons;
use Filament\Forms\Form; use Filament\Schemas\Components\Utilities\Get;
use Filament\Forms\Get; use Filament\Schemas\Components\Utilities\Set;
use Filament\Forms\Set;
use Filament\Notifications\Notification; use Filament\Notifications\Notification;
use Filament\Resources\Pages\EditRecord; use Filament\Resources\Pages\EditRecord;
use Filament\Schemas\Components\Actions;
use Filament\Schemas\Components\Component;
use Filament\Schemas\Components\Fieldset;
use Filament\Schemas\Components\StateCasts\BooleanStateCast;
use Filament\Support\Enums\Alignment; use Filament\Support\Enums\Alignment;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Client\ConnectionException; use Illuminate\Http\Client\ConnectionException;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use Illuminate\Support\HtmlString; use Illuminate\Support\HtmlString;
use LogicException; use LogicException;
use Webbingbrasil\FilamentCopyActions\Forms\Actions\CopyAction; use Filament\Schemas\Schema;
use Random\RandomException;
class EditServer extends EditRecord class EditServer extends EditRecord
{ {
@ -73,10 +72,14 @@ class EditServer extends EditRecord
$this->daemonServerRepository = $daemonServerRepository; $this->daemonServerRepository = $daemonServerRepository;
} }
public function form(Form $form): Form /**
* @throws RandomException
* @throws Exception
*/
public function form(Schema $schema): Schema
{ {
return $form return $schema
->schema([ ->components([
Tabs::make('Tabs') Tabs::make('Tabs')
->persistTabInQueryString() ->persistTabInQueryString()
->columns([ ->columns([
@ -112,7 +115,6 @@ class EditServer extends EditRecord
]) ])
->required() ->required()
->maxLength(255), ->maxLength(255),
Select::make('owner_id') Select::make('owner_id')
->prefixIcon('tabler-user') ->prefixIcon('tabler-user')
->label(trans('admin/server.owner')) ->label(trans('admin/server.owner'))
@ -127,7 +129,6 @@ class EditServer extends EditRecord
->getOptionLabelFromRecordUsing(fn (User $user) => "$user->username ($user->email)") ->getOptionLabelFromRecordUsing(fn (User $user) => "$user->username ($user->email)")
->preload() ->preload()
->required(), ->required(),
ToggleButtons::make('condition') ToggleButtons::make('condition')
->label(trans('admin/server.server_status')) ->label(trans('admin/server.server_status'))
->formatStateUsing(fn (Server $server) => $server->condition) ->formatStateUsing(fn (Server $server) => $server->condition)
@ -148,10 +149,9 @@ class EditServer extends EditRecord
->modalSubmitAction(false) ->modalSubmitAction(false)
->modalFooterActionsAlignment(Alignment::Right) ->modalFooterActionsAlignment(Alignment::Right)
->modalCancelActionLabel(trans('filament::components/modal.actions.close.label')) ->modalCancelActionLabel(trans('filament::components/modal.actions.close.label'))
->form([ ->schema([
MonacoEditor::make('logs') CodeEditor::make('logs')
->hiddenLabel() ->hiddenLabel()
->placeholderText(trans('admin/server.no_log'))
->formatStateUsing(function (Server $server, DaemonServerRepository $serverRepository) { ->formatStateUsing(function (Server $server, DaemonServerRepository $serverRepository) {
try { try {
return $serverRepository->setServer($server)->getInstallLogs(); return $serverRepository->setServer($server)->getInstallLogs();
@ -167,9 +167,7 @@ class EditServer extends EditRecord
} }
return ''; return '';
}) }),
->language('shell')
->view('filament.plugins.monaco-editor-logs'),
]) ])
), ),
@ -179,7 +177,7 @@ class EditServer extends EditRecord
TextInput::make('uuid') TextInput::make('uuid')
->label(trans('admin/server.uuid')) ->label(trans('admin/server.uuid'))
->suffixAction(fn () => request()->isSecure() ? CopyAction::make() : null) ->copyable(fn () => request()->isSecure())
->columnSpan([ ->columnSpan([
'default' => 2, 'default' => 2,
'sm' => 1, 'sm' => 1,
@ -190,7 +188,7 @@ class EditServer extends EditRecord
->dehydrated(false), ->dehydrated(false),
TextInput::make('uuid_short') TextInput::make('uuid_short')
->label(trans('admin/server.short_uuid')) ->label(trans('admin/server.short_uuid'))
->suffixAction(fn () => request()->isSecure() ? CopyAction::make() : null) ->copyable(fn () => request()->isSecure())
->columnSpan([ ->columnSpan([
'default' => 2, 'default' => 2,
'sm' => 1, 'sm' => 1,
@ -207,7 +205,7 @@ class EditServer extends EditRecord
'md' => 2, 'md' => 2,
'lg' => 3, 'lg' => 3,
]) ])
->unique(ignoreRecord: true) ->unique()
->maxLength(255), ->maxLength(255),
Select::make('node_id') Select::make('node_id')
->label(trans('admin/server.node')) ->label(trans('admin/server.node'))
@ -225,6 +223,7 @@ class EditServer extends EditRecord
->icon('tabler-brand-docker') ->icon('tabler-brand-docker')
->schema([ ->schema([
Fieldset::make(trans('admin/server.resource_limits')) Fieldset::make(trans('admin/server.resource_limits'))
->columnSpanFull()
->columns([ ->columns([
'default' => 1, 'default' => 1,
'sm' => 2, 'sm' => 2,
@ -242,13 +241,14 @@ class EditServer extends EditRecord
->afterStateUpdated(fn (Set $set) => $set('cpu', 0)) ->afterStateUpdated(fn (Set $set) => $set('cpu', 0))
->formatStateUsing(fn (Get $get) => $get('cpu') == 0) ->formatStateUsing(fn (Get $get) => $get('cpu') == 0)
->live() ->live()
->stateCast(new BooleanStateCast(false, true))
->options([ ->options([
true => trans('admin/server.unlimited'), 1 => trans('admin/server.unlimited'),
false => trans('admin/server.limited'), 0 => trans('admin/server.limited'),
]) ])
->colors([ ->colors([
true => 'primary', 1 => 'primary',
false => 'warning', 0 => 'warning',
]) ])
->columnSpan(2), ->columnSpan(2),
@ -272,13 +272,14 @@ class EditServer extends EditRecord
->afterStateUpdated(fn (Set $set) => $set('memory', 0)) ->afterStateUpdated(fn (Set $set) => $set('memory', 0))
->formatStateUsing(fn (Get $get) => $get('memory') == 0) ->formatStateUsing(fn (Get $get) => $get('memory') == 0)
->live() ->live()
->stateCast(new BooleanStateCast(false, true))
->options([ ->options([
true => trans('admin/server.unlimited'), 1 => trans('admin/server.unlimited'),
false => trans('admin/server.limited'), 0 => trans('admin/server.limited'),
]) ])
->colors([ ->colors([
true => 'primary', 1 => 'primary',
false => 'warning', 0 => 'warning',
]) ])
->columnSpan(2), ->columnSpan(2),
@ -305,13 +306,14 @@ class EditServer extends EditRecord
->live() ->live()
->afterStateUpdated(fn (Set $set) => $set('disk', 0)) ->afterStateUpdated(fn (Set $set) => $set('disk', 0))
->formatStateUsing(fn (Get $get) => $get('disk') == 0) ->formatStateUsing(fn (Get $get) => $get('disk') == 0)
->stateCast(new BooleanStateCast(false, true))
->options([ ->options([
true => trans('admin/server.unlimited'), 1 => trans('admin/server.unlimited'),
false => trans('admin/server.limited'), 0 => trans('admin/server.limited'),
]) ])
->colors([ ->colors([
true => 'primary', 1 => 'primary',
false => 'warning', 0 => 'warning',
]) ])
->columnSpan(2), ->columnSpan(2),
@ -328,6 +330,7 @@ class EditServer extends EditRecord
]), ]),
Fieldset::make(trans('admin/server.advanced_limits')) Fieldset::make(trans('admin/server.advanced_limits'))
->columnSpanFull()
->columns([ ->columns([
'default' => 1, 'default' => 1,
'sm' => 2, 'sm' => 2,
@ -345,17 +348,18 @@ class EditServer extends EditRecord
->schema([ ->schema([
ToggleButtons::make('cpu_pinning') ToggleButtons::make('cpu_pinning')
->label(trans('admin/server.cpu_pin'))->inlineLabel()->inline() ->label(trans('admin/server.cpu_pin'))->inlineLabel()->inline()
->default(false) ->default(0)
->afterStateUpdated(fn (Set $set) => $set('threads', [])) ->afterStateUpdated(fn (Set $set) => $set('threads', []))
->formatStateUsing(fn (Get $get) => !empty($get('threads'))) ->formatStateUsing(fn (Get $get) => !empty($get('threads')))
->live() ->live()
->stateCast(new BooleanStateCast(false, true))
->options([ ->options([
false => trans('admin/server.disabled'), 0 => trans('admin/server.disabled'),
true => trans('admin/server.enabled'), 1 => trans('admin/server.enabled'),
]) ])
->colors([ ->colors([
false => 'success', 0 => 'success',
true => 'warning', 1 => 'warning',
]) ])
->columnSpan(2), ->columnSpan(2),
@ -425,21 +429,27 @@ class EditServer extends EditRecord
->columnSpanFull() ->columnSpanFull()
->schema([ ->schema([
ToggleButtons::make('oom_killer') ToggleButtons::make('oom_killer')
->label(trans('admin/server.oom'))->inlineLabel()->inline() ->dehydrated()
->label(trans('admin/server.oom'))
->formatStateUsing(fn ($state) => $state)
->inlineLabel()
->inline()
->columnSpan(2) ->columnSpan(2)
->stateCast(new BooleanStateCast(false, true))
->options([ ->options([
false => trans('admin/server.disabled'), 0 => trans('admin/server.disabled'),
true => trans('admin/server.enabled'), 1 => trans('admin/server.enabled'),
]) ])
->colors([ ->colors([
false => 'success', 0 => 'success',
true => 'danger', 1 => 'danger',
]), ]),
]), ]),
]), ]),
Fieldset::make(trans('admin/server.feature_limits')) Fieldset::make(trans('admin/server.feature_limits'))
->inlineLabel() ->inlineLabel()
->columnSpanFull()
->columns([ ->columns([
'default' => 1, 'default' => 1,
'sm' => 2, 'sm' => 2,
@ -467,6 +477,7 @@ class EditServer extends EditRecord
->numeric(), ->numeric(),
]), ]),
Fieldset::make(trans('admin/server.docker_settings')) Fieldset::make(trans('admin/server.docker_settings'))
->columnSpanFull()
->columns([ ->columns([
'default' => 1, 'default' => 1,
'sm' => 2, 'sm' => 2,
@ -561,7 +572,7 @@ class EditServer extends EditRecord
// Use redirect instead of fillForm to prevent server variables from duplicating // Use redirect instead of fillForm to prevent server variables from duplicating
$this->redirect($this->getUrl(['record' => $server, 'tab' => '-egg-tab']), true); $this->redirect($this->getUrl(['record' => $server, 'tab' => '-egg-tab']), true);
}) })
->form(fn (Server $server) => [ ->schema(fn (Server $server) => [
Select::make('egg_id') Select::make('egg_id')
->label(trans('admin/server.new_egg')) ->label(trans('admin/server.new_egg'))
->prefixIcon('tabler-egg') ->prefixIcon('tabler-egg')
@ -576,24 +587,26 @@ class EditServer extends EditRecord
), ),
ToggleButtons::make('skip_scripts') ToggleButtons::make('skip_scripts')
->label(trans('admin/server.install_script'))->inline() ->label(trans('admin/server.install_script'))
->inline()
->columnSpan([ ->columnSpan([
'default' => 6, 'default' => 6,
'sm' => 1, 'sm' => 1,
'md' => 1, 'md' => 1,
'lg' => 2, 'lg' => 2,
]) ])
->stateCast(new BooleanStateCast(false, true))
->options([ ->options([
false => trans('admin/server.yes'), 0 => trans('admin/server.yes'),
true => trans('admin/server.skip'), 1 => trans('admin/server.skip'),
]) ])
->colors([ ->colors([
false => 'primary', 0 => 'primary',
true => 'danger', 1 => 'danger',
]) ])
->icons([ ->icons([
false => 'tabler-code', 0 => 'tabler-code',
true => 'tabler-code-off', 1 => 'tabler-code-off',
]) ])
->required(), ->required(),
Hidden::make('previewing') Hidden::make('previewing')
@ -606,7 +619,7 @@ class EditServer extends EditRecord
->hintAction(PreviewStartupAction::make('preview')), ->hintAction(PreviewStartupAction::make('preview')),
Textarea::make('defaultStartup') Textarea::make('defaultStartup')
->hintAction(fn () => request()->isSecure() ? CopyAction::make() : null) ->hintCopy()
->label(trans('admin/server.default_startup')) ->label(trans('admin/server.default_startup'))
->disabled() ->disabled()
->autosize() ->autosize()
@ -653,157 +666,12 @@ class EditServer extends EditRecord
->schema(fn (Get $get) => [ ->schema(fn (Get $get) => [
ServerResource::getMountCheckboxList($get), ServerResource::getMountCheckboxList($get),
]), ]),
Tab::make('databases')
->label(trans('admin/server.databases'))
->hidden(fn () => !auth()->user()->can('viewAny', Database::class))
->icon('tabler-database')
->columns(4)
->schema([
Repeater::make('databases')
->label('')
->grid()
->helperText(fn (Server $server) => $server->databases->isNotEmpty() ? '' : trans('admin/server.no_databases'))
->columns(2)
->schema([
TextInput::make('host')
->label(trans('admin/databasehost.table.host'))
->disabled()
->formatStateUsing(fn ($record) => $record->address())
->suffixAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null)
->columnSpan(1),
TextInput::make('database')
->label(trans('admin/databasehost.table.database'))
->disabled()
->formatStateUsing(fn ($record) => $record->database)
->suffixAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null)
->hintAction(
Action::make('Delete')
->label(trans('filament-actions::delete.single.modal.actions.delete.label'))
->authorize(fn (Database $database) => auth()->user()->can('delete', $database))
->color('danger')
->icon('tabler-trash')
->requiresConfirmation()
->modalIcon('tabler-database-x')
->modalHeading(trans('admin/server.delete_db_heading'))
->modalSubmitActionLabel(trans('filament-actions::delete.single.label'))
->modalDescription(fn (Get $get) => trans('admin/server.delete_db', ['name' => $get('database')]))
->action(function (DatabaseManagementService $service, $record) {
try {
$service->delete($record);
Notification::make()
->title(trans('server/database.delete_notification', ['database' => $record->database]))
->success()
->send();
} catch (Exception $e) {
Notification::make()
->title(trans('server/database.delete_notification_fail', ['database' => $record->database]))
->body($e->getMessage())
->danger()
->persistent()->send();
}
$this->fillForm();
})
),
TextInput::make('username')
->label(trans('admin/databasehost.table.username'))
->disabled()
->formatStateUsing(fn ($record) => $record->username)
->suffixAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null)
->columnSpan(1),
TextInput::make('password')
->label(trans('admin/databasehost.table.password'))
->disabled()
->password()
->revealable()
->columnSpan(1)
->hintAction(RotateDatabasePasswordAction::make())
->suffixAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null)
->formatStateUsing(fn (Database $database) => $database->password),
TextInput::make('remote')
->disabled()
->formatStateUsing(fn (Database $record) => $record->remote === '%' ? 'Anywhere ( % )' : $record->remote)
->columnSpan(1)
->label(trans('admin/databasehost.table.remote')),
TextInput::make('max_connections')
->label(trans('admin/databasehost.table.max_connections'))
->disabled()
->formatStateUsing(fn (Database $record) => $record->max_connections === 0 ? 'Unlimited' : $record->max_connections)
->columnSpan(1),
TextInput::make('jdbc')
->disabled()
->password()
->revealable()
->label(trans('admin/databasehost.table.connection_string'))
->columnSpan(2)
->formatStateUsing(fn (Database $record) => $record->jdbc)
->suffixAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null),
])
->relationship('databases')
->deletable(false)
->addable(false)
->columnSpan(4),
FormActions::make([
Action::make('createDatabase')
->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')
->modalSubmitActionLabel(trans('admin/server.create_database'))
->action(function (array $data, DatabaseManagementService $service, Server $server) {
$data['database'] ??= str_random(12);
$data['remote'] ??= '%';
$data['database'] = $service->generateUniqueDatabaseName($data['database'], $server->id);
try {
$service->setValidateDatabaseLimit(false)->create($server, $data);
Notification::make()
->title(trans('server/database.create_notification', ['database' => $data['database']]))
->success()
->send();
} catch (Exception $e) {
Notification::make()
->title(trans('server/database.create_notification_fail', ['database' => $data['database']]))
->body($e->getMessage())
->danger()
->persistent()->send();
}
$this->fillForm();
})
->form([
Select::make('database_host_id')
->label(trans('admin/databasehost.table.name'))
->required()
->placeholder('Select Database Host')
->options(fn (Server $server) => DatabaseHost::query()
->whereHas('nodes', fn ($query) => $query->where('nodes.id', $server->node_id))
->pluck('name', 'id')
)
->default(fn () => (DatabaseHost::query()->first())?->id)
->selectablePlaceholder(false),
TextInput::make('database')
->label(trans('admin/server.name'))
->alphaDash()
->prefix(fn (Server $server) => 's' . $server->id . '_')
->hintIcon('tabler-question-mark')
->hintIconTooltip(trans('admin/databasehost.table.name_helper')),
TextInput::make('remote')
->columnSpan(1)
->regex('/^[\w\-\/.%:]+$/')
->label(trans('admin/databasehost.table.remote'))
->hintIcon('tabler-question-mark')
->hintIconTooltip(trans('admin/databasehost.table.remote_helper')),
]),
])->alignCenter()->columnSpanFull(),
]),
Tab::make('actions') Tab::make('actions')
->label(trans('admin/server.actions')) ->label(trans('admin/server.actions'))
->icon('tabler-settings') ->icon('tabler-settings')
->schema([ ->schema([
Fieldset::make(trans('admin/server.actions')) Fieldset::make(trans('admin/server.actions'))
->columnSpanFull()
->columns([ ->columns([
'default' => 1, 'default' => 1,
'sm' => 2, 'sm' => 2,
@ -814,7 +682,7 @@ class EditServer extends EditRecord
Grid::make() Grid::make()
->columnSpan(3) ->columnSpan(3)
->schema([ ->schema([
FormActions::make([ Actions::make([
Action::make('toggleInstall') Action::make('toggleInstall')
->label(trans('admin/server.toggle_install')) ->label(trans('admin/server.toggle_install'))
->disabled(fn (Server $server) => $server->isSuspended()) ->disabled(fn (Server $server) => $server->isSuspended())
@ -832,7 +700,6 @@ class EditServer extends EditRecord
->success() ->success()
->send(); ->send();
$this->refreshFormData(['status', 'docker']);
} catch (Exception) { } catch (Exception) {
Notification::make() Notification::make()
->title(trans('admin/server.notifications.reinstall_failed')) ->title(trans('admin/server.notifications.reinstall_failed'))
@ -849,7 +716,6 @@ class EditServer extends EditRecord
->success() ->success()
->send(); ->send();
$this->refreshFormData(['status', 'docker']);
} catch (Exception $exception) { } catch (Exception $exception) {
Notification::make() Notification::make()
->title(trans('admin/server.notifications.install_toggle_failed')) ->title(trans('admin/server.notifications.install_toggle_failed'))
@ -860,13 +726,14 @@ class EditServer extends EditRecord
} }
}), }),
])->fullWidth(), ])->fullWidth(),
ToggleButtons::make('') ToggleButtons::make('install_help')
->hiddenLabel()
->hint(trans('admin/server.toggle_install_help')), ->hint(trans('admin/server.toggle_install_help')),
]), ]),
Grid::make() Grid::make()
->columnSpan(3) ->columnSpan(3)
->schema([ ->schema([
FormActions::make([ Actions::make([
Action::make('toggleSuspend') Action::make('toggleSuspend')
->label(trans('admin/server.suspend')) ->label(trans('admin/server.suspend'))
->color('warning') ->color('warning')
@ -880,7 +747,6 @@ class EditServer extends EditRecord
->title(trans('admin/server.notifications.server_suspended')) ->title(trans('admin/server.notifications.server_suspended'))
->send(); ->send();
$this->refreshFormData(['status', 'docker']);
} catch (Exception) { } catch (Exception) {
Notification::make() Notification::make()
->warning() ->warning()
@ -902,7 +768,6 @@ class EditServer extends EditRecord
->title(trans('admin/server.notifications.server_unsuspended')) ->title(trans('admin/server.notifications.server_unsuspended'))
->send(); ->send();
$this->refreshFormData(['status', 'docker']);
} catch (Exception) { } catch (Exception) {
Notification::make() Notification::make()
->warning() ->warning()
@ -912,22 +777,24 @@ class EditServer extends EditRecord
} }
}), }),
])->fullWidth(), ])->fullWidth(),
ToggleButtons::make('') ToggleButtons::make('server_suspend')
->hiddenLabel()
->hidden(fn (Server $server) => $server->isSuspended()) ->hidden(fn (Server $server) => $server->isSuspended())
->hint(trans('admin/server.notifications.server_suspend_help')), ->hint(trans('admin/server.notifications.server_suspend_help')),
ToggleButtons::make('') ToggleButtons::make('server_unsuspend')
->hiddenLabel()
->hidden(fn (Server $server) => !$server->isSuspended()) ->hidden(fn (Server $server) => !$server->isSuspended())
->hint(trans('admin/server.notifications.server_unsuspend_help')), ->hint(trans('admin/server.notifications.server_unsuspend_help')),
]), ]),
Grid::make() Grid::make()
->columnSpan(3) ->columnSpan(3)
->schema([ ->schema([
FormActions::make([ Actions::make([
Action::make('transfer') Action::make('transfer')
->label(trans('admin/server.transfer')) ->label(trans('admin/server.transfer'))
->disabled(fn (Server $server) => Node::count() <= 1 || $server->isInConflictState()) ->disabled(fn (Server $server) => Node::count() <= 1 || $server->isInConflictState())
->modalheading(trans('admin/server.transfer')) ->modalheading(trans('admin/server.transfer'))
->form($this->transferServer()) ->schema($this->transferServer())
->action(function (TransferServerService $transfer, Server $server, $data) { ->action(function (TransferServerService $transfer, Server $server, $data) {
try { try {
$transfer->handle($server, Arr::get($data, 'node_id'), Arr::get($data, 'allocation_id'), Arr::get($data, 'allocation_additional', [])); $transfer->handle($server, Arr::get($data, 'node_id'), Arr::get($data, 'allocation_id'), Arr::get($data, 'allocation_additional', []));
@ -945,13 +812,14 @@ class EditServer extends EditRecord
} }
}), }),
])->fullWidth(), ])->fullWidth(),
ToggleButtons::make('') ToggleButtons::make('server_transfer')
->hiddenLabel()
->hint(new HtmlString(trans('admin/server.transfer_help'))), ->hint(new HtmlString(trans('admin/server.transfer_help'))),
]), ]),
Grid::make() Grid::make()
->columnSpan(3) ->columnSpan(3)
->schema([ ->schema([
FormActions::make([ Actions::make([
Action::make('reinstall') Action::make('reinstall')
->label(trans('admin/server.reinstall')) ->label(trans('admin/server.reinstall'))
->color('danger') ->color('danger')
@ -967,8 +835,6 @@ class EditServer extends EditRecord
->title(trans('admin/server.notifications.reinstall_started')) ->title(trans('admin/server.notifications.reinstall_started'))
->success() ->success()
->send(); ->send();
$this->refreshFormData(['status', 'docker']);
} catch (Exception) { } catch (Exception) {
Notification::make() Notification::make()
->title(trans('admin/server.notifications.reinstall_failed')) ->title(trans('admin/server.notifications.reinstall_failed'))
@ -978,7 +844,8 @@ class EditServer extends EditRecord
} }
}), }),
])->fullWidth(), ])->fullWidth(),
ToggleButtons::make('') ToggleButtons::make('server_reinstall')
->hiddenLabel()
->hint(trans('admin/server.reinstall_help')), ->hint(trans('admin/server.reinstall_help')),
]), ]),
]), ]),
@ -987,7 +854,9 @@ class EditServer extends EditRecord
]); ]);
} }
/** @return Component[] */ /** @return Component[]
* @throws Exception
*/
protected function transferServer(): array protected function transferServer(): array
{ {
return [ return [
@ -1021,7 +890,7 @@ class EditServer extends EditRecord
]; ];
} }
/** @return array<Actions\Action|Actions\ActionGroup> */ /** @return array<Action|ActionGroup> */
protected function getDefaultHeaderActions(): array protected function getDefaultHeaderActions(): array
{ {
/** @var Server $server */ /** @var Server $server */
@ -1030,7 +899,7 @@ class EditServer extends EditRecord
$canForceDelete = cache()->get("servers.$server->uuid.canForceDelete", false); $canForceDelete = cache()->get("servers.$server->uuid.canForceDelete", false);
return [ return [
Actions\Action::make('Delete') Action::make('Delete')
->color('danger') ->color('danger')
->label(trans('filament-actions::delete.single.label')) ->label(trans('filament-actions::delete.single.label'))
->modalHeading(trans('filament-actions::delete.single.modal.heading', ['label' => $this->getRecordTitle()])) ->modalHeading(trans('filament-actions::delete.single.modal.heading', ['label' => $this->getRecordTitle()]))
@ -1044,7 +913,7 @@ class EditServer extends EditRecord
} catch (ConnectionException) { } catch (ConnectionException) {
cache()->put("servers.$server->uuid.canForceDelete", true, now()->addMinutes(5)); cache()->put("servers.$server->uuid.canForceDelete", true, now()->addMinutes(5));
Notification::make() return Notification::make()
->title(trans('admin/server.notifications.error_server_delete')) ->title(trans('admin/server.notifications.error_server_delete'))
->body(trans('admin/server.notifications.error_server_delete_body')) ->body(trans('admin/server.notifications.error_server_delete_body'))
->color('warning') ->color('warning')
@ -1054,8 +923,8 @@ class EditServer extends EditRecord
} }
}) })
->hidden(fn () => $canForceDelete) ->hidden(fn () => $canForceDelete)
->authorize(fn (Server $server) => auth()->user()->can('delete', $server)), ->authorize(fn (Server $server) => auth()->user()->can('delete server', $server)),
Actions\Action::make('ForceDelete') Action::make('ForceDelete')
->color('danger') ->color('danger')
->label(trans('filament-actions::force-delete.single.label')) ->label(trans('filament-actions::force-delete.single.label'))
->modalHeading(trans('filament-actions::force-delete.single.modal.heading', ['label' => $this->getRecordTitle()])) ->modalHeading(trans('filament-actions::force-delete.single.modal.heading', ['label' => $this->getRecordTitle()]))
@ -1067,12 +936,12 @@ class EditServer extends EditRecord
return redirect(ListServers::getUrl(panel: 'admin')); return redirect(ListServers::getUrl(panel: 'admin'));
} catch (ConnectionException) { } catch (ConnectionException) {
cache()->forget("servers.$server->uuid.canForceDelete"); return cache()->forget("servers.$server->uuid.canForceDelete");
} }
}) })
->visible(fn () => $canForceDelete) ->visible(fn () => $canForceDelete)
->authorize(fn (Server $server) => auth()->user()->can('delete', $server)), ->authorize(fn (Server $server) => auth()->user()->can('delete server', $server)),
Actions\Action::make('console') Action::make('console')
->label(trans('admin/server.console')) ->label(trans('admin/server.console'))
->icon('tabler-terminal') ->icon('tabler-terminal')
->url(fn (Server $server) => Console::getUrl(panel: 'server', tenant: $server)), ->url(fn (Server $server) => Console::getUrl(panel: 'server', tenant: $server)),
@ -1124,4 +993,12 @@ class EditServer extends EditRecord
{ {
return null; return null;
} }
public function getRelationManagers(): array
{
return [
AllocationsRelationManager::class,
DatabasesRelationManager::class,
];
}
} }

View File

@ -1,17 +1,17 @@
<?php <?php
namespace App\Filament\Admin\Resources\ServerResource\Pages; namespace App\Filament\Admin\Resources\Servers\Pages;
use App\Filament\Server\Pages\Console; use App\Filament\Server\Pages\Console;
use App\Filament\Admin\Resources\ServerResource; use App\Filament\Admin\Resources\Servers\ServerResource;
use App\Models\Server; use App\Models\Server;
use App\Traits\Filament\CanCustomizeHeaderActions; use App\Traits\Filament\CanCustomizeHeaderActions;
use App\Traits\Filament\CanCustomizeHeaderWidgets; use App\Traits\Filament\CanCustomizeHeaderWidgets;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords; use Filament\Resources\Pages\ListRecords;
use Filament\Tables\Actions\Action; use Filament\Actions\Action;
use Filament\Tables\Actions\CreateAction; use Filament\Actions\ActionGroup;
use Filament\Tables\Actions\EditAction; use Filament\Actions\CreateAction;
use Filament\Actions\EditAction;
use Filament\Tables\Columns\SelectColumn; use Filament\Tables\Columns\SelectColumn;
use Filament\Tables\Columns\TextColumn; use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Grouping\Group; use Filament\Tables\Grouping\Group;
@ -77,7 +77,7 @@ class ListServers extends ListRecords
->disabled(fn (Server $server) => $server->allocations->count() <= 1) ->disabled(fn (Server $server) => $server->allocations->count() <= 1)
->options(fn (Server $server) => $server->allocations->mapWithKeys(fn ($allocation) => [$allocation->id => $allocation->address])) ->options(fn (Server $server) => $server->allocations->mapWithKeys(fn ($allocation) => [$allocation->id => $allocation->address]))
->selectablePlaceholder(fn (Server $server) => $server->allocations->count() <= 1) ->selectablePlaceholder(fn (Server $server) => $server->allocations->count() <= 1)
->placeholder('None') ->placeholder(trans('admin/server.none'))
->sortable(), ->sortable(),
TextColumn::make('allocation_id_readonly') TextColumn::make('allocation_id_readonly')
->label(trans('admin/server.primary_allocation')) ->label(trans('admin/server.primary_allocation'))
@ -92,7 +92,7 @@ class ListServers extends ListRecords
->numeric() ->numeric()
->sortable(), ->sortable(),
]) ])
->actions([ ->recordActions([
Action::make('View') Action::make('View')
->label(trans('admin/server.view')) ->label(trans('admin/server.view'))
->icon('tabler-terminal') ->icon('tabler-terminal')
@ -109,11 +109,11 @@ class ListServers extends ListRecords
]); ]);
} }
/** @return array<Actions\Action|Actions\ActionGroup> */ /** @return array<Action|ActionGroup> */
protected function getDefaultHeaderActions(): array protected function getDefaultHeaderActions(): array
{ {
return [ return [
Actions\CreateAction::make() CreateAction::make()
->hidden(fn () => Server::count() <= 0), ->hidden(fn () => Server::count() <= 0),
]; ];
} }

View File

@ -1,21 +1,22 @@
<?php <?php
namespace App\Filament\Admin\Resources\ServerResource\RelationManagers; namespace App\Filament\Admin\Resources\Servers\RelationManagers;
use App\Filament\Admin\Resources\ServerResource\Pages\CreateServer; use App\Filament\Admin\Resources\Servers\Pages\CreateServer;
use App\Models\Allocation; use App\Models\Allocation;
use App\Models\Server; use App\Models\Server;
use App\Services\Allocations\AssignmentService; use App\Services\Allocations\AssignmentService;
use Filament\Actions\DissociateAction;
use Filament\Forms\Components\Select; use Filament\Forms\Components\Select;
use Filament\Forms\Components\TagsInput; use Filament\Forms\Components\TagsInput;
use Filament\Forms\Components\TextInput; use Filament\Forms\Components\TextInput;
use Filament\Forms\Get; use Filament\Schemas\Components\Utilities\Get;
use Filament\Forms\Set; use Filament\Schemas\Components\Utilities\Set;
use Filament\Resources\RelationManagers\RelationManager; use Filament\Resources\RelationManagers\RelationManager;
use Filament\Tables\Actions\AssociateAction; use Filament\Actions\DissociateBulkAction;
use Filament\Tables\Actions\CreateAction; use Filament\Actions\CreateAction;
use Filament\Tables\Actions\DissociateAction; use Filament\Actions\AssociateAction;
use Filament\Tables\Actions\DissociateBulkAction; use Filament\Actions\Action;
use Filament\Tables\Columns\IconColumn; use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Columns\TextColumn; use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Columns\TextInputColumn; use Filament\Tables\Columns\TextInputColumn;
@ -60,7 +61,11 @@ class AllocationsRelationManager extends RelationManager
->default(fn (Allocation $allocation) => $allocation->id === $this->getOwnerRecord()->allocation_id) ->default(fn (Allocation $allocation) => $allocation->id === $this->getOwnerRecord()->allocation_id)
->label(trans('admin/server.primary')), ->label(trans('admin/server.primary')),
]) ])
->actions([ ->recordActions([
Action::make('make-primary')
->label(trans('admin/server.make_primary'))
->action(fn (Allocation $allocation) => $this->getOwnerRecord()->update(['allocation_id' => $allocation->id]) && $this->deselectAllTableRecords())
->hidden(fn (Allocation $allocation) => $allocation->id === $this->getOwnerRecord()->allocation_id),
DissociateAction::make() DissociateAction::make()
->after(function (Allocation $allocation) { ->after(function (Allocation $allocation) {
$allocation->update(['notes' => null]); $allocation->update(['notes' => null]);
@ -70,7 +75,7 @@ class AllocationsRelationManager extends RelationManager
->headerActions([ ->headerActions([
CreateAction::make()->label(trans('admin/server.create_allocation')) CreateAction::make()->label(trans('admin/server.create_allocation'))
->createAnother(false) ->createAnother(false)
->form(fn () => [ ->schema(fn () => [
Select::make('allocation_ip') Select::make('allocation_ip')
->options(collect($this->getOwnerRecord()->node->ipAddresses())->mapWithKeys(fn (string $ip) => [$ip => $ip])) ->options(collect($this->getOwnerRecord()->node->ipAddresses())->mapWithKeys(fn (string $ip) => [$ip => $ip]))
->label(trans('admin/server.ip_address')) ->label(trans('admin/server.ip_address'))

View File

@ -0,0 +1,156 @@
<?php
namespace App\Filament\Admin\Resources\Servers\RelationManagers;
use Filament\Actions\DeleteAction;
use Filament\Actions\ViewAction;
use Filament\Schemas\Schema;
use App\Filament\Components\Actions\RotateDatabasePasswordAction;
use App\Filament\Components\Tables\Columns\DateTimeColumn;
use App\Models\Database;
use App\Models\DatabaseHost;
use App\Models\Server;
use App\Services\Databases\DatabaseManagementService;
use App\Services\Servers\RandomWordService;
use Exception;
use Filament\Actions\CreateAction;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Notifications\Notification;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Support\Exceptions\Halt;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
/**
* @method Server getOwnerRecord()
*/
class DatabasesRelationManager extends RelationManager
{
protected static string $relationship = 'databases';
public function form(Schema $schema): Schema
{
return $schema
->components([
TextInput::make('database')
->columnSpanFull(),
TextInput::make('username')
->label(trans('admin/databasehost.table.username')),
TextInput::make('password')
->label(trans('admin/databasehost.table.password'))
->password()
->revealable()
->hintAction(RotateDatabasePasswordAction::make())
->formatStateUsing(fn (Database $database) => $database->password),
TextInput::make('remote')
->label(trans('admin/databasehost.table.remote'))
->formatStateUsing(fn (Database $record) => $record->remote === '%' ? trans('admin/databasehost.anywhere'). ' ( % )' : $record->remote),
TextInput::make('max_connections')
->label(trans('admin/databasehost.table.max_connections'))
->formatStateUsing(fn (Database $record) => $record->max_connections === 0 ? trans('admin/databasehost.unlimited') : $record->max_connections),
TextInput::make('jdbc')
->label(trans('admin/databasehost.table.connection_string'))
->columnSpanFull()
->password()
->revealable()
->formatStateUsing(fn (Database $database) => $database->jdbc),
]);
}
public function table(Table $table): Table
{
return $table
->recordTitleAttribute('database')
->columns([
TextColumn::make('database')
->icon('tabler-database'),
TextColumn::make('username')
->label(trans('admin/databasehost.table.username'))
->icon('tabler-user'),
TextColumn::make('remote')
->label(trans('admin/databasehost.table.remote'))
->formatStateUsing(fn (Database $record) => $record->remote === '%' ? trans('admin/databasehost.anywhere'). ' ( % )' : $record->remote),
TextColumn::make('server.name')
->icon('tabler-brand-docker')
->url(fn (Database $database) => route('filament.admin.resources.servers.edit', ['record' => $database->server_id])),
TextColumn::make('max_connections')
->label(trans('admin/databasehost.table.max_connections'))
->formatStateUsing(fn ($record) => $record->max_connections === 0 ? trans('admin/databasehost.unlimited') : $record->max_connections),
DateTimeColumn::make('created_at')
->label(trans('admin/databasehost.table.created_at')),
])
->recordActions([
ViewAction::make()
->color('primary'),
DeleteAction::make()
->using(function (Database $database, DatabaseManagementService $service) {
try {
$service->delete($database);
Notification::make()
->title(trans('server/database.delete_notification', ['database' => $database->database]))
->success()
->send();
} catch (Exception $exception) {
Notification::make()
->title(trans('server/database.delete_notification_fail', ['database' => $database->database]))
->danger()
->send();
report($exception);
}
}),
])
->headerActions([
CreateAction::make()
->disabled(fn () => DatabaseHost::count() < 1)
->label(fn () => DatabaseHost::count() < 1 ? trans('admin/server.no_db_hosts') : trans('admin/server.create_database'))
->color(fn () => DatabaseHost::count() < 1 ? 'danger' : 'primary')
->createAnother(false)
->action(function (array $data, DatabaseManagementService $service, RandomWordService $randomWordService) {
$data['database'] ??= $randomWordService->word() . random_int(1, 420);
$data['remote'] ??= '%';
$data['database'] = $service->generateUniqueDatabaseName($data['database'], $this->getOwnerRecord()->id);
try {
return $service->setValidateDatabaseLimit(false)->create($this->getOwnerRecord(), $data);
} catch (Exception $exception) {
Notification::make()
->title(trans('admin/server.failed_to_create'))
->body($exception->getMessage())
->danger()
->persistent()->send();
throw new Halt();
}
})
->schema([
Select::make('database_host_id')
->label(trans('admin/databasehost.model_label'))
->required()
->placeholder(trans('admin/databasehost.table.select_placeholder'))
->options(fn () => DatabaseHost::query()
->whereHas('nodes', fn ($query) => $query->where('nodes.id', $this->getOwnerRecord()->node_id))
->pluck('name', 'id')
)
->default(fn () => (DatabaseHost::query()->first())?->id)
->selectablePlaceholder(false),
TextInput::make('database')
->label(trans('admin/server.name'))
->alphaDash()
->prefix(fn () => 's' . $this->getOwnerRecord()->id . '_')
->hintIcon('tabler-question-mark')
->hintIconTooltip(trans('admin/databasehost.table.name_helper')),
TextInput::make('remote')
->columnSpan(1)
->regex('/^[\w\-\/.%:]+$/')
->label(trans('admin/databasehost.table.remote'))
->default('%')
->hintIcon('tabler-question-mark')
->hintIconTooltip(trans('admin/databasehost.table.remote_helper')),
]),
]);
}
}

View File

@ -1,18 +1,22 @@
<?php <?php
namespace App\Filament\Admin\Resources; namespace App\Filament\Admin\Resources\Servers;
use App\Filament\Admin\Resources\ServerResource\Pages; use App\Enums\CustomizationKey;
use App\Filament\Admin\Resources\ServerResource\RelationManagers; use App\Filament\Admin\Resources\Servers\RelationManagers\AllocationsRelationManager;
use App\Filament\Admin\Resources\Servers\Pages\ListServers;
use App\Filament\Admin\Resources\Servers\Pages\CreateServer;
use App\Filament\Admin\Resources\Servers\Pages\EditServer;
use Exception;
use App\Models\Mount; use App\Models\Mount;
use App\Models\Server; use App\Models\Server;
use App\Traits\Filament\CanCustomizePages; use App\Traits\Filament\CanCustomizePages;
use App\Traits\Filament\CanCustomizeRelations; use App\Traits\Filament\CanCustomizeRelations;
use Filament\Forms\Components\CheckboxList; use Filament\Forms\Components\CheckboxList;
use Filament\Forms\Get;
use Filament\Resources\Pages\PageRegistration; use Filament\Resources\Pages\PageRegistration;
use Filament\Resources\RelationManagers\RelationManager; use Filament\Resources\RelationManagers\RelationManager;
use Filament\Resources\Resource; use Filament\Resources\Resource;
use Filament\Schemas\Components\Utilities\Get;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
class ServerResource extends Resource class ServerResource extends Resource
@ -22,7 +26,7 @@ class ServerResource extends Resource
protected static ?string $model = Server::class; protected static ?string $model = Server::class;
protected static ?string $navigationIcon = 'tabler-brand-docker'; protected static string|\BackedEnum|null $navigationIcon = 'tabler-brand-docker';
protected static ?string $recordTitleAttribute = 'name'; protected static ?string $recordTitleAttribute = 'name';
@ -43,7 +47,7 @@ class ServerResource extends Resource
public static function getNavigationGroup(): ?string public static function getNavigationGroup(): ?string
{ {
return !empty(auth()->user()->getCustomization()['top_navigation']) ? false : trans('admin/dashboard.server'); return auth()->user()->getCustomization(CustomizationKey::TopNavigation) ? false : trans('admin/dashboard.server');
} }
public static function getNavigationBadge(): ?string public static function getNavigationBadge(): ?string
@ -51,6 +55,9 @@ class ServerResource extends Resource
return (string) static::getEloquentQuery()->count() ?: null; return (string) static::getEloquentQuery()->count() ?: null;
} }
/**
* @throws Exception
*/
public static function getMountCheckboxList(Get $get): CheckboxList public static function getMountCheckboxList(Get $get): CheckboxList
{ {
$allowedMounts = Mount::all(); $allowedMounts = Mount::all();
@ -78,7 +85,7 @@ class ServerResource extends Resource
public static function getDefaultRelations(): array public static function getDefaultRelations(): array
{ {
return [ return [
RelationManagers\AllocationsRelationManager::class, AllocationsRelationManager::class,
]; ];
} }
@ -86,9 +93,9 @@ class ServerResource extends Resource
public static function getDefaultPages(): array public static function getDefaultPages(): array
{ {
return [ return [
'index' => Pages\ListServers::route('/'), 'index' => ListServers::route('/'),
'create' => Pages\CreateServer::route('/create'), 'create' => CreateServer::route('/create'),
'edit' => Pages\EditServer::route('/{record}/edit'), 'edit' => EditServer::route('/{record}/edit'),
]; ];
} }

View File

@ -1,8 +1,8 @@
<?php <?php
namespace App\Filament\Admin\Resources\UserResource\Pages; namespace App\Filament\Admin\Resources\Users\Pages;
use App\Filament\Admin\Resources\UserResource; use App\Filament\Admin\Resources\Users\UserResource;
use App\Models\Role; use App\Models\Role;
use App\Services\Users\UserCreationService; use App\Services\Users\UserCreationService;
use App\Traits\Filament\CanCustomizeHeaderActions; use App\Traits\Filament\CanCustomizeHeaderActions;

View File

@ -1,8 +1,8 @@
<?php <?php
namespace App\Filament\Admin\Resources\UserResource\Pages; namespace App\Filament\Admin\Resources\Users\Pages;
use App\Filament\Admin\Resources\UserResource; use App\Filament\Admin\Resources\Users\UserResource;
use App\Models\User; use App\Models\User;
use App\Services\Users\UserUpdateService; use App\Services\Users\UserUpdateService;
use App\Traits\Filament\CanCustomizeHeaderActions; use App\Traits\Filament\CanCustomizeHeaderActions;

View File

@ -1,14 +1,14 @@
<?php <?php
namespace App\Filament\Admin\Resources\UserResource\Pages; namespace App\Filament\Admin\Resources\Users\Pages;
use App\Filament\Admin\Resources\UserResource; use App\Filament\Admin\Resources\Users\UserResource;
use App\Traits\Filament\CanCustomizeHeaderActions; use App\Traits\Filament\CanCustomizeHeaderActions;
use App\Traits\Filament\CanCustomizeHeaderWidgets; use App\Traits\Filament\CanCustomizeHeaderWidgets;
use Filament\Resources\Pages\ListRecords;
use Filament\Actions\Action; use Filament\Actions\Action;
use Filament\Actions\ActionGroup; use Filament\Actions\ActionGroup;
use Filament\Actions\CreateAction; use Filament\Actions\CreateAction;
use Filament\Resources\Pages\ListRecords;
class ListUsers extends ListRecords class ListUsers extends ListRecords
{ {

View File

@ -1,8 +1,8 @@
<?php <?php
namespace App\Filament\Admin\Resources\UserResource\Pages; namespace App\Filament\Admin\Resources\Users\Pages;
use App\Filament\Admin\Resources\UserResource; use App\Filament\Admin\Resources\Users\UserResource;
use App\Traits\Filament\CanCustomizeHeaderActions; use App\Traits\Filament\CanCustomizeHeaderActions;
use App\Traits\Filament\CanCustomizeHeaderWidgets; use App\Traits\Filament\CanCustomizeHeaderWidgets;
use Filament\Actions\Action; use Filament\Actions\Action;

View File

@ -1,14 +1,14 @@
<?php <?php
namespace App\Filament\Admin\Resources\UserResource\RelationManagers; namespace App\Filament\Admin\Resources\Users\RelationManagers;
use App\Enums\ServerState; use App\Enums\ServerState;
use App\Enums\SuspendAction; use App\Enums\SuspendAction;
use App\Models\Server; use App\Models\Server;
use App\Models\User; use App\Models\User;
use App\Services\Servers\SuspensionService; use App\Services\Servers\SuspensionService;
use Filament\Actions\Action;
use Filament\Resources\RelationManagers\RelationManager; use Filament\Resources\RelationManagers\RelationManager;
use Filament\Tables\Actions;
use Filament\Tables\Columns\SelectColumn; use Filament\Tables\Columns\SelectColumn;
use Filament\Tables\Columns\TextColumn; use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table; use Filament\Tables\Table;
@ -26,7 +26,7 @@ class ServersRelationManager extends RelationManager
->searchable(false) ->searchable(false)
->heading(trans('admin/user.servers')) ->heading(trans('admin/user.servers'))
->headerActions([ ->headerActions([
Actions\Action::make('toggleSuspend') Action::make('toggleSuspend')
->hidden(fn () => $user->servers() ->hidden(fn () => $user->servers()
->whereNot('status', ServerState::Suspended) ->whereNot('status', ServerState::Suspended)
->orWhereNull('status') ->orWhereNull('status')
@ -38,7 +38,7 @@ class ServersRelationManager extends RelationManager
collect($user->servers)->filter(fn ($server) => !$server->isSuspended()) collect($user->servers)->filter(fn ($server) => !$server->isSuspended())
->each(fn ($server) => $suspensionService->handle($server, SuspendAction::Suspend)); ->each(fn ($server) => $suspensionService->handle($server, SuspendAction::Suspend));
}), }),
Actions\Action::make('toggleUnsuspend') Action::make('toggleUnsuspend')
->hidden(fn () => $user->servers()->where('status', ServerState::Suspended)->count() === 0) ->hidden(fn () => $user->servers()->where('status', ServerState::Suspended)->count() === 0)
->label(trans('admin/server.unsuspend_all')) ->label(trans('admin/server.unsuspend_all'))
->color('primary') ->color('primary')
@ -72,7 +72,7 @@ class ServersRelationManager extends RelationManager
->label(trans('admin/server.primary_allocation')) ->label(trans('admin/server.primary_allocation'))
->disabled() ->disabled()
->options(fn (Server $server) => $server->allocations->take(1)->mapWithKeys(fn ($allocation) => [$allocation->id => $allocation->address])) ->options(fn (Server $server) => $server->allocations->take(1)->mapWithKeys(fn ($allocation) => [$allocation->id => $allocation->address]))
->placeholder('None') ->placeholder(trans('admin/server.none'))
->sortable(), ->sortable(),
TextColumn::make('image')->hidden(), TextColumn::make('image')->hidden(),
TextColumn::make('databases_count') TextColumn::make('databases_count')

View File

@ -1,30 +1,35 @@
<?php <?php
namespace App\Filament\Admin\Resources; namespace App\Filament\Admin\Resources\Users;
use App\Filament\Admin\Resources\UserResource\Pages; use App\Enums\CustomizationKey;
use App\Filament\Admin\Resources\UserResource\RelationManagers; use App\Filament\Admin\Resources\Users\RelationManagers\ServersRelationManager;
use App\Filament\Admin\Resources\Users\Pages\ListUsers;
use App\Filament\Admin\Resources\Users\Pages\CreateUser;
use App\Filament\Admin\Resources\Users\Pages\ViewUser;
use App\Filament\Admin\Resources\Users\Pages\EditUser;
use App\Models\Role; use App\Models\Role;
use App\Models\User; use App\Models\User;
use App\Traits\Filament\CanCustomizePages; use App\Traits\Filament\CanCustomizePages;
use App\Traits\Filament\CanCustomizeRelations; use App\Traits\Filament\CanCustomizeRelations;
use App\Traits\Filament\CanModifyForm; use App\Traits\Filament\CanModifyForm;
use App\Traits\Filament\CanModifyTable; use App\Traits\Filament\CanModifyTable;
use Exception;
use Filament\Facades\Filament; use Filament\Facades\Filament;
use Filament\Forms\Components\CheckboxList; use Filament\Forms\Components\CheckboxList;
use Filament\Forms\Components\TextInput; use Filament\Forms\Components\TextInput;
use Filament\Forms\Form;
use Filament\Resources\Pages\PageRegistration; use Filament\Resources\Pages\PageRegistration;
use Filament\Resources\RelationManagers\RelationManager; use Filament\Resources\RelationManagers\RelationManager;
use Filament\Resources\Resource; use Filament\Resources\Resource;
use Filament\Tables\Actions\DeleteBulkAction; use Filament\Actions\DeleteBulkAction;
use Filament\Tables\Actions\EditAction; use Filament\Actions\EditAction;
use Filament\Tables\Actions\ViewAction; use Filament\Actions\ViewAction;
use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Columns\ImageColumn; use Filament\Tables\Columns\ImageColumn;
use Filament\Tables\Columns\TextColumn; use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table; use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Filament\Schemas\Schema;
use Filament\Tables\Columns\IconColumn;
class UserResource extends Resource class UserResource extends Resource
{ {
@ -35,7 +40,7 @@ class UserResource extends Resource
protected static ?string $model = User::class; protected static ?string $model = User::class;
protected static ?string $navigationIcon = 'tabler-users'; protected static string|\BackedEnum|null $navigationIcon = 'tabler-users';
protected static ?string $recordTitleAttribute = 'username'; protected static ?string $recordTitleAttribute = 'username';
@ -56,14 +61,17 @@ class UserResource extends Resource
public static function getNavigationGroup(): ?string public static function getNavigationGroup(): ?string
{ {
return !empty(auth()->user()->getCustomization()['top_navigation']) ? false : trans('admin/dashboard.user'); return auth()->user()->getCustomization(CustomizationKey::TopNavigation) ? false : trans('admin/dashboard.user');
} }
public static function getNavigationBadge(): ?string public static function getNavigationBadge(): ?string
{ {
return static::getModel()::count() ?: null; return ($count = static::getModel()::count()) > 0 ? (string) $count : null;
} }
/**
* @throws Exception
*/
public static function defaultTable(Table $table): Table public static function defaultTable(Table $table): Table
{ {
return $table return $table
@ -75,15 +83,17 @@ class UserResource extends Resource
->alignCenter() ->alignCenter()
->defaultImageUrl(fn (User $user) => Filament::getUserAvatarUrl($user)), ->defaultImageUrl(fn (User $user) => Filament::getUserAvatarUrl($user)),
TextColumn::make('username') TextColumn::make('username')
->label(trans('admin/user.username')), ->label(trans('admin/user.username'))
->searchable(),
TextColumn::make('email') TextColumn::make('email')
->label(trans('admin/user.email')) ->label(trans('admin/user.email'))
->icon('tabler-mail'), ->icon('tabler-mail')
IconColumn::make('use_totp') ->searchable(),
IconColumn::make('mfa_email_enabled')
->label(trans('profile.tabs.2fa')) ->label(trans('profile.tabs.2fa'))
->visibleFrom('lg') ->visibleFrom('lg')
->icon(fn (User $user) => $user->use_totp ? 'tabler-lock' : 'tabler-lock-open-off') ->icon(fn (User $user) => filled($user->mfa_app_secret) ? 'tabler-qrcode' : ($user->mfa_email_enabled ? 'tabler-mail' : 'tabler-lock-open-off'))
->boolean(), ->tooltip(fn (User $user) => filled($user->mfa_app_secret) ? 'App' : ($user->mfa_email_enabled ? 'E-Mail' : 'None')),
TextColumn::make('roles.name') TextColumn::make('roles.name')
->label(trans('admin/user.roles')) ->label(trans('admin/user.roles'))
->badge() ->badge()
@ -99,7 +109,7 @@ class UserResource extends Resource
->counts('subusers') ->counts('subusers')
->icon('tabler-users'), ->icon('tabler-users'),
]) ])
->actions([ ->recordActions([
ViewAction::make() ViewAction::make()
->hidden(fn ($record) => static::canEdit($record)), ->hidden(fn ($record) => static::canEdit($record)),
EditAction::make(), EditAction::make(),
@ -110,23 +120,23 @@ class UserResource extends Resource
]); ]);
} }
public static function defaultForm(Form $form): Form public static function defaultForm(Schema $schema): Schema
{ {
return $form return $schema
->columns(['default' => 1, 'lg' => 3]) ->columns(['default' => 1, 'lg' => 3])
->schema([ ->components([
TextInput::make('username') TextInput::make('username')
->label(trans('admin/user.username')) ->label(trans('admin/user.username'))
->alphaNum() ->alphaNum()
->required() ->required()
->unique(ignoreRecord: true) ->unique()
->minLength(3) ->minLength(3)
->maxLength(255), ->maxLength(255),
TextInput::make('email') TextInput::make('email')
->label(trans('admin/user.email')) ->label(trans('admin/user.email'))
->email() ->email()
->required() ->required()
->unique(ignoreRecord: true) ->unique()
->maxLength(255), ->maxLength(255),
TextInput::make('password') TextInput::make('password')
->label(trans('admin/user.password')) ->label(trans('admin/user.password'))
@ -134,7 +144,7 @@ class UserResource extends Resource
->hintIconTooltip(fn ($operation) => $operation === 'create' ? trans('admin/user.password_help') : null) ->hintIconTooltip(fn ($operation) => $operation === 'create' ? trans('admin/user.password_help') : null)
->password(), ->password(),
CheckboxList::make('roles') CheckboxList::make('roles')
->hidden(fn (User $user) => $user->isRootAdmin()) ->hidden(fn (?User $user) => $user && $user->isRootAdmin())
->relationship('roles', 'name', fn (Builder $query) => $query->whereNot('id', Role::getRootAdmin()->id)) ->relationship('roles', 'name', fn (Builder $query) => $query->whereNot('id', Role::getRootAdmin()->id))
->saveRelationshipsUsing(fn (User $user, array $state) => $user->syncRoles(collect($state)->map(fn ($role) => Role::findById($role)))) ->saveRelationshipsUsing(fn (User $user, array $state) => $user->syncRoles(collect($state)->map(fn ($role) => Role::findById($role))))
->dehydrated() ->dehydrated()
@ -142,7 +152,7 @@ class UserResource extends Resource
->columnSpanFull() ->columnSpanFull()
->bulkToggleable(false), ->bulkToggleable(false),
CheckboxList::make('root_admin_role') CheckboxList::make('root_admin_role')
->visible(fn (User $user) => $user->isRootAdmin()) ->visible(fn (?User $user) => $user && $user->isRootAdmin())
->disabled() ->disabled()
->options([ ->options([
'root_admin' => Role::ROOT_ADMIN, 'root_admin' => Role::ROOT_ADMIN,
@ -161,7 +171,7 @@ class UserResource extends Resource
public static function getDefaultRelations(): array public static function getDefaultRelations(): array
{ {
return [ return [
RelationManagers\ServersRelationManager::class, ServersRelationManager::class,
]; ];
} }
@ -169,10 +179,10 @@ class UserResource extends Resource
public static function getDefaultPages(): array public static function getDefaultPages(): array
{ {
return [ return [
'index' => Pages\ListUsers::route('/'), 'index' => ListUsers::route('/'),
'create' => Pages\CreateUser::route('/create'), 'create' => CreateUser::route('/create'),
'view' => Pages\ViewUser::route('/{record}'), 'view' => ViewUser::route('/{record}'),
'edit' => Pages\EditUser::route('/{record}/edit'), 'edit' => EditUser::route('/{record}/edit'),
]; ];
} }
} }

View File

@ -1,8 +1,8 @@
<?php <?php
namespace App\Filament\Admin\Resources\WebhookResource\Pages; namespace App\Filament\Admin\Resources\Webhooks\Pages;
use App\Filament\Admin\Resources\WebhookResource; use App\Filament\Admin\Resources\Webhooks\WebhookResource;
use App\Traits\Filament\CanCustomizeHeaderActions; use App\Traits\Filament\CanCustomizeHeaderActions;
use App\Traits\Filament\CanCustomizeHeaderWidgets; use App\Traits\Filament\CanCustomizeHeaderWidgets;
use Filament\Actions\Action; use Filament\Actions\Action;

View File

@ -1,8 +1,8 @@
<?php <?php
namespace App\Filament\Admin\Resources\WebhookResource\Pages; namespace App\Filament\Admin\Resources\Webhooks\Pages;
use App\Filament\Admin\Resources\WebhookResource; use App\Filament\Admin\Resources\Webhooks\WebhookResource;
use App\Traits\Filament\CanCustomizeHeaderActions; use App\Traits\Filament\CanCustomizeHeaderActions;
use App\Traits\Filament\CanCustomizeHeaderWidgets; use App\Traits\Filament\CanCustomizeHeaderWidgets;
use Filament\Actions\Action; use Filament\Actions\Action;

View File

@ -1,8 +1,8 @@
<?php <?php
namespace App\Filament\Admin\Resources\WebhookResource\Pages; namespace App\Filament\Admin\Resources\Webhooks\Pages;
use App\Filament\Admin\Resources\WebhookResource; use App\Filament\Admin\Resources\Webhooks\WebhookResource;
use App\Models\WebhookConfiguration; use App\Models\WebhookConfiguration;
use App\Traits\Filament\CanCustomizeHeaderActions; use App\Traits\Filament\CanCustomizeHeaderActions;
use App\Traits\Filament\CanCustomizeHeaderWidgets; use App\Traits\Filament\CanCustomizeHeaderWidgets;

View File

@ -1,8 +1,8 @@
<?php <?php
namespace App\Filament\Admin\Resources\WebhookResource\Pages; namespace App\Filament\Admin\Resources\Webhooks\Pages;
use App\Filament\Admin\Resources\WebhookResource; use App\Filament\Admin\Resources\Webhooks\WebhookResource;
use App\Traits\Filament\CanCustomizeHeaderActions; use App\Traits\Filament\CanCustomizeHeaderActions;
use App\Traits\Filament\CanCustomizeHeaderWidgets; use App\Traits\Filament\CanCustomizeHeaderWidgets;
use Filament\Actions\Action; use Filament\Actions\Action;

View File

@ -1,43 +1,46 @@
<?php <?php
namespace App\Filament\Admin\Resources; namespace App\Filament\Admin\Resources\Webhooks;
use App\Filament\Admin\Resources\WebhookResource\Pages; use App\Filament\Admin\Resources\Webhooks\Pages\CreateWebhookConfiguration;
use App\Filament\Admin\Resources\WebhookResource\Pages\EditWebhookConfiguration; use App\Filament\Admin\Resources\Webhooks\Pages\EditWebhookConfiguration;
use App\Filament\Admin\Resources\Webhooks\Pages\ListWebhookConfigurations;
use App\Filament\Admin\Resources\Webhooks\Pages\ViewWebhookConfiguration;
use App\Livewire\AlertBanner; use App\Livewire\AlertBanner;
use App\Models\WebhookConfiguration; use App\Models\WebhookConfiguration;
use Exception;
use Filament\Actions\Action;
use Filament\Actions\CreateAction;
use Filament\Actions\DeleteBulkAction;
use App\Traits\Filament\CanCustomizePages; use App\Traits\Filament\CanCustomizePages;
use App\Traits\Filament\CanCustomizeRelations; use App\Traits\Filament\CanCustomizeRelations;
use App\Traits\Filament\CanModifyForm; use App\Traits\Filament\CanModifyForm;
use App\Traits\Filament\CanModifyTable; use App\Traits\Filament\CanModifyTable;
use Filament\Actions\EditAction;
use Filament\Actions\ReplicateAction;
use Filament\Actions\ViewAction;
use Filament\Forms\Components\Checkbox; use Filament\Forms\Components\Checkbox;
use Filament\Forms\Components\CheckboxList; use Filament\Forms\Components\CheckboxList;
use Filament\Forms\Components\ColorPicker; use Filament\Forms\Components\ColorPicker;
use Filament\Forms\Components\KeyValue; use Filament\Forms\Components\KeyValue;
use Filament\Forms\Components\Repeater; use Filament\Forms\Components\Repeater;
use Filament\Forms\Components\Section;
use Filament\Forms\Components\Textarea; use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput; use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\ToggleButtons; use Filament\Forms\Components\ToggleButtons;
use Filament\Forms\Components\Actions\Action;
use Filament\Forms\Form;
use Filament\Resources\Pages\PageRegistration; use Filament\Resources\Pages\PageRegistration;
use Filament\Forms\Get;
use Filament\Resources\Resource; use Filament\Resources\Resource;
use Filament\Forms\Set; use Filament\Schemas\Components\Section;
use Filament\Tables\Actions\DeleteBulkAction; use Filament\Schemas\Components\Utilities\Get;
use Filament\Tables\Actions\ViewAction; use Filament\Schemas\Components\Utilities\Set;
use Filament\Tables\Actions\EditAction; use Filament\Support\Components\Component;
use Filament\Tables\Actions\ReplicateAction;
use Filament\Tables\Actions\CreateAction;
use Filament\Tables\Columns\IconColumn; use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Columns\TextColumn; use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\SelectFilter; use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Table; use Filament\Tables\Table;
use Livewire\Features\SupportEvents\HandlesEvents; use Filament\Schemas\Schema;
use App\Enums\WebhookType;
use Filament\Forms\Components\Component;
use Livewire\Component as Livewire; use Livewire\Component as Livewire;
use App\Enums\WebhookType;
use Livewire\Features\SupportEvents\HandlesEvents;
class WebhookResource extends Resource class WebhookResource extends Resource
{ {
@ -49,7 +52,7 @@ class WebhookResource extends Resource
protected static ?string $model = WebhookConfiguration::class; protected static ?string $model = WebhookConfiguration::class;
protected static ?string $navigationIcon = 'tabler-webhook'; protected static string|\BackedEnum|null $navigationIcon = 'tabler-webhook';
protected static ?string $recordTitleAttribute = 'description'; protected static ?string $recordTitleAttribute = 'description';
@ -70,7 +73,7 @@ class WebhookResource extends Resource
public static function getNavigationBadge(): ?string public static function getNavigationBadge(): ?string
{ {
return static::getModel()::count() ?: null; return ($count = static::getModel()::count()) > 0 ? (string) $count : null;
} }
public static function getNavigationGroup(): ?string public static function getNavigationGroup(): ?string
@ -93,7 +96,7 @@ class WebhookResource extends Resource
TextColumn::make('endpoint') TextColumn::make('endpoint')
->label(trans('admin/webhook.table.endpoint')), ->label(trans('admin/webhook.table.endpoint')),
]) ])
->actions([ ->recordActions([
ViewAction::make() ViewAction::make()
->hidden(fn (WebhookConfiguration $record) => static::canEdit($record)), ->hidden(fn (WebhookConfiguration $record) => static::canEdit($record)),
EditAction::make(), EditAction::make(),
@ -122,10 +125,10 @@ class WebhookResource extends Resource
]); ]);
} }
public static function defaultForm(Form $form): Form public static function defaultForm(Schema $schema): Schema
{ {
return $form return $schema
->schema([ ->components([
ToggleButtons::make('type') ToggleButtons::make('type')
->live() ->live()
->inline() ->inline()
@ -138,9 +141,9 @@ class WebhookResource extends Resource
->label(trans('admin/webhook.endpoint')) ->label(trans('admin/webhook.endpoint'))
->required() ->required()
->columnSpanFull() ->columnSpanFull()
->afterStateUpdated(fn (string $state, Set $set) => $set('type', str($state)->contains('discord.com') ? WebhookType::Discord->value : WebhookType::Regular->value)), ->afterStateUpdated(fn (string $state, Set $set) => $set('type', str($state)->contains('discord.com') ? WebhookType::Discord : WebhookType::Regular)),
Section::make(trans('admin/webhook.regular')) Section::make(trans('admin/webhook.regular'))
->hidden(fn (Get $get) => $get('type') === WebhookType::Discord->value) ->hidden(fn (Get $get) => $get('type') === WebhookType::Discord)
->dehydratedWhenHidden() ->dehydratedWhenHidden()
->schema(fn () => self::getRegularFields()) ->schema(fn () => self::getRegularFields())
->headerActions([ ->headerActions([
@ -154,13 +157,14 @@ class WebhookResource extends Resource
]) ])
->formBefore(), ->formBefore(),
Section::make(trans('admin/webhook.discord')) Section::make(trans('admin/webhook.discord'))
->hidden(fn (Get $get) => $get('type') === WebhookType::Regular->value) ->hidden(fn (Get $get) => $get('type') === WebhookType::Regular)
->dehydratedWhenHidden() ->dehydratedWhenHidden()
->afterStateUpdated(fn (Livewire $livewire) => $livewire->dispatch('refresh-widget')) ->afterStateUpdated(fn (Livewire $livewire) => $livewire->dispatch('refresh-widget'))
->schema(fn () => self::getDiscordFields()) ->schema(fn () => self::getDiscordFields())
->view('filament.components.webhooksection') ->view('filament.components.webhooksection')
->aside() ->aside()
->formBefore(), ->formBefore()
->columnSpanFull(),
Section::make(trans('admin/webhook.events')) Section::make(trans('admin/webhook.events'))
->schema([ ->schema([
CheckboxList::make('events') CheckboxList::make('events')
@ -175,7 +179,9 @@ class WebhookResource extends Resource
]); ]);
} }
/** @return Component[] */ /** @return Component[]
* @throws Exception
*/
private static function getRegularFields(): array private static function getRegularFields(): array
{ {
return [ return [
@ -187,7 +193,9 @@ class WebhookResource extends Resource
]; ];
} }
/** @return Component[] */ /** @return Component[]
* @throws Exception
*/
private static function getDiscordFields(): array private static function getDiscordFields(): array
{ {
return [ return [
@ -337,10 +345,10 @@ class WebhookResource extends Resource
public static function getDefaultPages(): array public static function getDefaultPages(): array
{ {
return [ return [
'index' => Pages\ListWebhookConfigurations::route('/'), 'index' => ListWebhookConfigurations::route('/'),
'create' => Pages\CreateWebhookConfiguration::route('/create'), 'create' => CreateWebhookConfiguration::route('/create'),
'view' => Pages\ViewWebhookConfiguration::route('/{record}'), 'view' => ViewWebhookConfiguration::route('/{record}'),
'edit' => Pages\EditWebhookConfiguration::route('/{record}/edit'), 'edit' => EditWebhookConfiguration::route('/{record}/edit'),
]; ];
} }
} }

View File

@ -2,10 +2,11 @@
namespace App\Filament\Admin\Widgets; namespace App\Filament\Admin\Widgets;
use Filament\Forms\Components\Actions\Action; use Exception;
use Filament\Forms\Components\Placeholder; use Filament\Actions\Action;
use Filament\Forms\Components\Section; use Filament\Infolists\Components\TextEntry;
use Filament\Forms\Form; use Filament\Schemas\Components\Section;
use Filament\Schemas\Schema;
class CanaryWidget extends FormWidget class CanaryWidget extends FormWidget
{ {
@ -16,10 +17,13 @@ class CanaryWidget extends FormWidget
return config('app.version') === 'canary'; return config('app.version') === 'canary';
} }
public function form(Form $form): Form /**
* @throws Exception
*/
public function form(Schema $schema): Schema
{ {
return $form return $schema
->schema([ ->components([
Section::make(trans('admin/dashboard.sections.intro-developers.heading')) Section::make(trans('admin/dashboard.sections.intro-developers.heading'))
->icon('tabler-code') ->icon('tabler-code')
->iconColor('primary') ->iconColor('primary')
@ -27,10 +31,12 @@ class CanaryWidget extends FormWidget
->collapsed() ->collapsed()
->persistCollapsed() ->persistCollapsed()
->schema([ ->schema([
Placeholder::make('') TextEntry::make('info')
->content(trans('admin/dashboard.sections.intro-developers.content')), ->hiddenLabel()
Placeholder::make('') ->state(trans('admin/dashboard.sections.intro-developers.content')),
->content(trans('admin/dashboard.sections.intro-developers.extra_note')), TextEntry::make('extra')
->hiddenLabel()
->state(trans('admin/dashboard.sections.intro-developers.extra_note')),
]) ])
->headerActions([ ->headerActions([
Action::make('issues') Action::make('issues')

View File

@ -8,7 +8,7 @@ use Illuminate\Support\Carbon;
class DiscordPreview extends Widget class DiscordPreview extends Widget
{ {
protected static string $view = 'filament.admin.widgets.discord-preview'; protected string $view = 'filament.admin.widgets.discord-preview';
/** @var array<string, string> */ /** @var array<string, string> */
protected $listeners = [ protected $listeners = [

View File

@ -12,5 +12,5 @@ abstract class FormWidget extends Widget implements HasForms
protected static bool $isLazy = false; protected static bool $isLazy = false;
protected static string $view = 'filament.admin.widgets.form-widget'; protected string $view = 'filament.admin.widgets.form-widget';
} }

View File

@ -2,27 +2,32 @@
namespace App\Filament\Admin\Widgets; namespace App\Filament\Admin\Widgets;
use Filament\Forms\Components\Actions\Action; use Exception;
use Filament\Forms\Components\Placeholder; use Filament\Actions\Action;
use Filament\Forms\Components\Section; use Filament\Infolists\Components\TextEntry;
use Filament\Forms\Form; use Filament\Schemas\Components\Section;
use Filament\Schemas\Schema;
class HelpWidget extends FormWidget class HelpWidget extends FormWidget
{ {
protected static ?int $sort = 4; protected static ?int $sort = 4;
public function form(Form $form): Form /**
* @throws Exception
*/
public function form(Schema $schema): Schema
{ {
return $form return $schema
->schema([ ->components([
Section::make(trans('admin/dashboard.sections.intro-help.heading')) Section::make(trans('admin/dashboard.sections.intro-help.heading'))
->icon('tabler-question-mark') ->icon('tabler-question-mark')
->iconColor('info') ->iconColor('info')
->collapsible() ->collapsible()
->persistCollapsed() ->persistCollapsed()
->schema([ ->schema([
Placeholder::make('') TextEntry::make('info')
->content(trans('admin/dashboard.sections.intro-help.content')), ->hiddenLabel()
->state(trans('admin/dashboard.sections.intro-help.content')),
]) ])
->headerActions([ ->headerActions([
Action::make('docs') Action::make('docs')

View File

@ -2,12 +2,13 @@
namespace App\Filament\Admin\Widgets; namespace App\Filament\Admin\Widgets;
use App\Filament\Admin\Resources\NodeResource\Pages\CreateNode; use Exception;
use App\Filament\Admin\Resources\Nodes\Pages\CreateNode;
use App\Models\Node; use App\Models\Node;
use Filament\Forms\Components\Actions\Action; use Filament\Actions\Action;
use Filament\Forms\Components\Placeholder; use Filament\Infolists\Components\TextEntry;
use Filament\Forms\Components\Section; use Filament\Schemas\Components\Section;
use Filament\Forms\Form; use Filament\Schemas\Schema;
class NoNodesWidget extends FormWidget class NoNodesWidget extends FormWidget
{ {
@ -18,18 +19,22 @@ class NoNodesWidget extends FormWidget
return Node::count() <= 0; return Node::count() <= 0;
} }
public function form(Form $form): Form /**
* @throws Exception
*/
public function form(Schema $schema): Schema
{ {
return $form return $schema
->schema([ ->components([
Section::make(trans('admin/dashboard.sections.intro-first-node.heading')) Section::make(trans('admin/dashboard.sections.intro-first-node.heading'))
->icon('tabler-server-2') ->icon('tabler-server-2')
->iconColor('primary') ->iconColor('primary')
->collapsible() ->collapsible()
->persistCollapsed() ->persistCollapsed()
->schema([ ->schema([
Placeholder::make('') TextEntry::make('info')
->content(trans('admin/dashboard.sections.intro-first-node.content')), ->hiddenLabel()
->state(trans('admin/dashboard.sections.intro-first-node.content')),
]) ])
->headerActions([ ->headerActions([
Action::make('create-node') Action::make('create-node')

View File

@ -2,29 +2,35 @@
namespace App\Filament\Admin\Widgets; namespace App\Filament\Admin\Widgets;
use Filament\Forms\Components\Actions\Action; use Exception;
use Filament\Forms\Components\Placeholder; use Filament\Actions\Action;
use Filament\Forms\Components\Section; use Filament\Infolists\Components\TextEntry;
use Filament\Forms\Form; use Filament\Schemas\Components\Section;
use Filament\Schemas\Schema;
class SupportWidget extends FormWidget class SupportWidget extends FormWidget
{ {
protected static ?int $sort = 3; protected static ?int $sort = 3;
public function form(Form $form): Form /**
* @throws Exception
*/
public function form(Schema $schema): Schema
{ {
return $form return $schema
->schema([ ->components([
Section::make(trans('admin/dashboard.sections.intro-support.heading')) Section::make(trans('admin/dashboard.sections.intro-support.heading'))
->icon('tabler-heart-filled') ->icon('tabler-heart-filled')
->iconColor('danger') ->iconColor('danger')
->collapsible() ->collapsible()
->persistCollapsed() ->persistCollapsed()
->schema([ ->schema([
Placeholder::make('') TextEntry::make('info')
->content(trans('admin/dashboard.sections.intro-support.content')), ->hiddenLabel()
Placeholder::make('') ->state(trans('admin/dashboard.sections.intro-support.content')),
->content(trans('admin/dashboard.sections.intro-support.extra_note')), TextEntry::make('extra')
->hiddenLabel()
->state(trans('admin/dashboard.sections.intro-support.extra_note')),
]) ])
->headerActions([ ->headerActions([
Action::make('donate') Action::make('donate')

View File

@ -3,10 +3,11 @@
namespace App\Filament\Admin\Widgets; namespace App\Filament\Admin\Widgets;
use App\Services\Helpers\SoftwareVersionService; use App\Services\Helpers\SoftwareVersionService;
use Filament\Forms\Components\Actions\Action; use Exception;
use Filament\Forms\Components\Placeholder; use Filament\Actions\Action;
use Filament\Forms\Components\Section; use Filament\Infolists\Components\TextEntry;
use Filament\Forms\Form; use Filament\Schemas\Components\Section;
use Filament\Schemas\Schema;
class UpdateWidget extends FormWidget class UpdateWidget extends FormWidget
{ {
@ -19,26 +20,30 @@ class UpdateWidget extends FormWidget
$this->softwareVersionService = $softwareVersionService; $this->softwareVersionService = $softwareVersionService;
} }
public function form(Form $form): Form /**
* @throws Exception
*/
public function form(Schema $schema): Schema
{ {
$isLatest = $this->softwareVersionService->isLatestPanel(); $isLatest = $this->softwareVersionService->isLatestPanel();
return $form return $schema
->schema([ ->components([
$isLatest $isLatest
? Section::make(trans('admin/dashboard.sections.intro-no-update.heading')) ? Section::make(trans('admin/dashboard.sections.intro-no-update.heading'))
->icon('tabler-checkbox') ->icon('tabler-checkbox')
->iconColor('success') ->iconColor('success')
->schema([ ->schema([
Placeholder::make('') TextEntry::make('info')
->content(trans('admin/dashboard.sections.intro-no-update.content', ['version' => $this->softwareVersionService->currentPanelVersion()])), ->hiddenLabel()
->state(trans('admin/dashboard.sections.intro-no-update.content', ['version' => $this->softwareVersionService->currentPanelVersion()])),
]) ])
: Section::make(trans('admin/dashboard.sections.intro-update-available.heading')) : Section::make(trans('admin/dashboard.sections.intro-update-available.heading'))
->icon('tabler-info-circle') ->icon('tabler-info-circle')
->iconColor('warning') ->iconColor('warning')
->schema([ ->schema([
Placeholder::make('') TextEntry::make('info')
->content(trans('admin/dashboard.sections.intro-update-available.content', ['latestVersion' => $this->softwareVersionService->latestPanelVersion()])), ->state(trans('admin/dashboard.sections.intro-update-available.content', ['latestVersion' => $this->softwareVersionService->latestPanelVersion()])),
]) ])
->headerActions([ ->headerActions([
Action::make('update') Action::make('update')

View File

@ -1,9 +1,10 @@
<?php <?php
namespace App\Filament\App\Resources\ServerResource\Pages; namespace App\Filament\App\Resources\Servers\Pages;
use App\Enums\CustomizationKey;
use App\Enums\ServerResourceType; use App\Enums\ServerResourceType;
use App\Filament\App\Resources\ServerResource; use App\Filament\App\Resources\Servers\ServerResource;
use App\Filament\Components\Tables\Columns\ServerEntryColumn; use App\Filament\Components\Tables\Columns\ServerEntryColumn;
use App\Filament\Server\Pages\Console; use App\Filament\Server\Pages\Console;
use App\Models\Permission; use App\Models\Permission;
@ -11,13 +12,13 @@ use App\Models\Server;
use App\Repositories\Daemon\DaemonServerRepository; use App\Repositories\Daemon\DaemonServerRepository;
use App\Traits\Filament\CanCustomizeHeaderActions; use App\Traits\Filament\CanCustomizeHeaderActions;
use App\Traits\Filament\CanCustomizeHeaderWidgets; use App\Traits\Filament\CanCustomizeHeaderWidgets;
use Filament\Actions\Action;
use Filament\Actions\ActionGroup;
use Filament\Notifications\Notification; use Filament\Notifications\Notification;
use Filament\Resources\Components\Tab;
use Filament\Resources\Pages\ListRecords; use Filament\Resources\Pages\ListRecords;
use Filament\Schemas\Components\Tabs\Tab;
use Filament\Support\Enums\Alignment; use Filament\Support\Enums\Alignment;
use Filament\Support\Enums\IconSize; use Filament\Support\Enums\IconSize;
use Filament\Tables\Actions\Action;
use Filament\Tables\Actions\ActionGroup;
use Filament\Tables\Columns\Column; use Filament\Tables\Columns\Column;
use Filament\Tables\Columns\Layout\Stack; use Filament\Tables\Columns\Layout\Stack;
use Filament\Tables\Columns\TextColumn; use Filament\Tables\Columns\TextColumn;
@ -45,7 +46,8 @@ class ListServers extends ListRecords
$this->daemonServerRepository = new DaemonServerRepository(); $this->daemonServerRepository = new DaemonServerRepository();
} }
/** @return Stack[] */ /** @return Stack[]
*/
protected function gridColumns(): array protected function gridColumns(): array
{ {
return [ return [
@ -102,7 +104,7 @@ class ListServers extends ListRecords
{ {
$baseQuery = auth()->user()->accessibleServers(); $baseQuery = auth()->user()->accessibleServers();
$usingGrid = (auth()->user()->getCustomization()['dashboard_layout'] ?? 'grid') === 'grid'; $usingGrid = auth()->user()->getCustomization(CustomizationKey::DashboardLayout) === 'grid';
return $table return $table
->paginated(false) ->paginated(false)
@ -110,8 +112,8 @@ class ListServers extends ListRecords
->poll('15s') ->poll('15s')
->columns($usingGrid ? $this->gridColumns() : $this->tableColumns()) ->columns($usingGrid ? $this->gridColumns() : $this->tableColumns())
->recordUrl(!$usingGrid ? (fn (Server $server) => Console::getUrl(panel: 'server', tenant: $server)) : null) ->recordUrl(!$usingGrid ? (fn (Server $server) => Console::getUrl(panel: 'server', tenant: $server)) : null)
->actions(!$usingGrid ? ActionGroup::make(static::getPowerActions(view: 'table')) : []) ->recordActions(!$usingGrid ? ActionGroup::make(static::getPowerActions(view: 'table')) : [])
->actionsAlignment(Alignment::Center->value) ->recordActionsAlignment(Alignment::Center->value)
->contentGrid($usingGrid ? ['default' => 1, 'md' => 2] : null) ->contentGrid($usingGrid ? ['default' => 1, 'md' => 2] : null)
->emptyStateIcon('tabler-brand-docker') ->emptyStateIcon('tabler-brand-docker')
->emptyStateDescription('') ->emptyStateDescription('')

View File

@ -1,8 +1,8 @@
<?php <?php
namespace App\Filament\App\Resources; namespace App\Filament\App\Resources\Servers;
use App\Filament\App\Resources\ServerResource\Pages; use App\Filament\App\Resources\Servers\Pages\ListServers;
use App\Models\Server; use App\Models\Server;
use Filament\Resources\Resource; use Filament\Resources\Resource;
@ -22,7 +22,7 @@ class ServerResource extends Resource
public static function getPages(): array public static function getPages(): array
{ {
return [ return [
'index' => Pages\ListServers::route('/'), 'index' => ListServers::route('/'),
]; ];
} }
} }

View File

@ -6,7 +6,7 @@ use App\Enums\EggFormat;
use App\Models\Egg; use App\Models\Egg;
use App\Services\Eggs\Sharing\EggExporterService; use App\Services\Eggs\Sharing\EggExporterService;
use Filament\Actions\Action; use Filament\Actions\Action;
use Filament\Forms\Components\Placeholder; use Filament\Infolists\Components\TextEntry;
use Filament\Support\Enums\Alignment; use Filament\Support\Enums\Alignment;
class ExportEggAction extends Action class ExportEggAction extends Action
@ -22,32 +22,33 @@ class ExportEggAction extends Action
$this->label(trans('filament-actions::export.modal.actions.export.label')); $this->label(trans('filament-actions::export.modal.actions.export.label'));
$this->tableIcon('tabler-download');
$this->authorize(fn () => auth()->user()->can('export egg')); $this->authorize(fn () => auth()->user()->can('export egg'));
$this->modalHeading(fn (Egg $egg) => trans('filament-actions::export.modal.actions.export.label') . ' ' . $egg->name); $this->modalHeading(fn (Egg $egg) => trans('filament-actions::export.modal.actions.export.label') . ' ' . $egg->name);
$this->modalIcon($this->icon); $this->modalIcon($this->icon);
$this->form([ $this->schema([
Placeholder::make('') TextEntry::make('label')
->label(fn (Egg $egg) => trans('admin/egg.export.modal', ['egg' => $egg->name])), ->hiddenLabel()
->state(fn (Egg $egg) => trans('admin/egg.export.modal', ['egg' => $egg->name])),
]); ]);
$this->modalFooterActionsAlignment(Alignment::Center); $this->modalFooterActionsAlignment(Alignment::Center);
$this->modalFooterActions([ $this->modalFooterActions([ //TODO: Close modal after clicking ->close() does not allow action to preform before closing modal
Action::make('json') Action::make('json')
->label(trans('admin/egg.export.as', ['format' => 'json'])) ->label(trans('admin/egg.export.as', ['format' => 'json']))
->action(fn (EggExporterService $service, Egg $egg) => response()->streamDownload(function () use ($service, $egg) { ->action(fn (EggExporterService $service, Egg $egg) => response()->streamDownload(function () use ($service, $egg) {
echo $service->handle($egg->id, EggFormat::JSON); echo $service->handle($egg->id, EggFormat::JSON);
}, 'egg-' . $egg->getKebabName() . '.json')) }, 'egg-' . $egg->getKebabName() . '.json')),
->close(),
Action::make('yaml') Action::make('yaml')
->label(trans('admin/egg.export.as', ['format' => 'yaml'])) ->label(trans('admin/egg.export.as', ['format' => 'yaml']))
->action(fn (EggExporterService $service, Egg $egg) => response()->streamDownload(function () use ($service, $egg) { ->action(fn (EggExporterService $service, Egg $egg) => response()->streamDownload(function () use ($service, $egg) {
echo $service->handle($egg->id, EggFormat::YAML); echo $service->handle($egg->id, EggFormat::YAML);
}, 'egg-' . $egg->getKebabName() . '.yaml')) }, 'egg-' . $egg->getKebabName() . '.yaml')),
->close(),
]); ]);
} }
} }

Some files were not shown because too many files have changed in this diff Show More