Compare commits

..

1 Commits

Author SHA1 Message Date
github-actions[bot]
d39241839c ci(release): bump version 2025-09-04 21:31:53 +00:00
787 changed files with 10118 additions and 8102 deletions

View File

@@ -11,9 +11,9 @@ jobs:
name: UI
runs-on: ubuntu-latest
strategy:
fail-fast: true
fail-fast: false
matrix:
node-version: [20, 22]
node-version: [18, 20]
steps:
- name: Code Checkout
uses: actions/checkout@v4

View File

@@ -6,75 +6,12 @@ on:
- main
pull_request:
env:
APP_ENV: testing
APP_DEBUG: "false"
APP_KEY: ThisIsARandomStringForTests12345
APP_TIMEZONE: UTC
APP_URL: http://localhost/
CACHE_DRIVER: array
MAIL_MAILER: array
SESSION_DRIVER: array
QUEUE_CONNECTION: sync
GUZZLE_TIMEOUT: 60
GUZZLE_CONNECT_TIMEOUT: 60
jobs:
sqlite:
name: SQLite
runs-on: ubuntu-latest
strategy:
fail-fast: true
matrix:
php: [8.2, 8.3, 8.4]
env:
DB_CONNECTION: sqlite
DB_DATABASE: testing.sqlite
steps:
- name: Code Checkout
uses: actions/checkout@v4
- name: Get cache directory
id: composer-cache
run: |
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache
uses: actions/cache@v4
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ matrix.php }}-${{ hashFiles('**/composer.lock') }}
restore-keys: |
${{ runner.os }}-composer-${{ matrix.php }}-
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: bcmath, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip
tools: composer:v2
coverage: none
- name: Install dependencies
run: composer install --no-interaction --no-suggest --no-progress --no-scripts
- name: Create SQLite file
run: touch database/testing.sqlite
- name: Unit tests
run: vendor/bin/pest tests/Unit
env:
DB_HOST: UNIT_NO_DB
SKIP_MIGRATIONS: true
- name: Integration tests
run: vendor/bin/pest tests/Integration
mysql:
name: MySQL
runs-on: ubuntu-latest
strategy:
fail-fast: true
fail-fast: false
matrix:
php: [8.2, 8.3, 8.4]
database: ["mysql:8"]
@@ -88,10 +25,21 @@ jobs:
- 3306
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
env:
APP_ENV: testing
APP_DEBUG: "false"
APP_KEY: ThisIsARandomStringForTests12345
APP_TIMEZONE: UTC
APP_URL: http://localhost/
CACHE_DRIVER: array
MAIL_MAILER: array
SESSION_DRIVER: array
QUEUE_CONNECTION: sync
DB_CONNECTION: mysql
DB_HOST: 127.0.0.1
DB_DATABASE: testing
DB_USERNAME: root
GUZZLE_TIMEOUT: 60
GUZZLE_CONNECT_TIMEOUT: 60
steps:
- name: Code Checkout
uses: actions/checkout@v4
@@ -136,7 +84,7 @@ jobs:
name: MariaDB
runs-on: ubuntu-latest
strategy:
fail-fast: true
fail-fast: false
matrix:
php: [8.2, 8.3, 8.4]
database: ["mariadb:10.6", "mariadb:10.11", "mariadb:11.4"]
@@ -150,10 +98,21 @@ jobs:
- 3306
options: --health-cmd="mariadb-admin ping || mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
env:
APP_ENV: testing
APP_DEBUG: "false"
APP_KEY: ThisIsARandomStringForTests12345
APP_TIMEZONE: UTC
APP_URL: http://localhost/
CACHE_DRIVER: array
MAIL_MAILER: array
SESSION_DRIVER: array
QUEUE_CONNECTION: sync
DB_CONNECTION: mariadb
DB_HOST: 127.0.0.1
DB_DATABASE: testing
DB_USERNAME: root
GUZZLE_TIMEOUT: 60
GUZZLE_CONNECT_TIMEOUT: 60
steps:
- name: Code Checkout
uses: actions/checkout@v4
@@ -194,11 +153,72 @@ jobs:
DB_PORT: ${{ job.services.database.ports[3306] }}
DB_USERNAME: root
sqlite:
name: SQLite
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
php: [8.2, 8.3, 8.4]
env:
APP_ENV: testing
APP_DEBUG: "false"
APP_KEY: ThisIsARandomStringForTests12345
APP_TIMEZONE: UTC
APP_URL: http://localhost/
CACHE_DRIVER: array
MAIL_MAILER: array
SESSION_DRIVER: array
QUEUE_CONNECTION: sync
DB_CONNECTION: sqlite
DB_DATABASE: testing.sqlite
GUZZLE_TIMEOUT: 60
GUZZLE_CONNECT_TIMEOUT: 60
steps:
- name: Code Checkout
uses: actions/checkout@v4
- name: Get cache directory
id: composer-cache
run: |
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- name: Cache
uses: actions/cache@v4
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ matrix.php }}-${{ hashFiles('**/composer.lock') }}
restore-keys: |
${{ runner.os }}-composer-${{ matrix.php }}-
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: bcmath, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip
tools: composer:v2
coverage: none
- name: Install dependencies
run: composer install --no-interaction --no-suggest --no-progress --no-scripts
- name: Create SQLite file
run: touch database/testing.sqlite
- name: Unit tests
run: vendor/bin/pest tests/Unit
env:
DB_HOST: UNIT_NO_DB
SKIP_MIGRATIONS: true
- name: Integration tests
run: vendor/bin/pest tests/Integration
postgresql:
name: PostgreSQL
runs-on: ubuntu-latest
strategy:
fail-fast: true
fail-fast: false
matrix:
php: [8.2, 8.3, 8.4]
database: ["postgres:14"]
@@ -218,11 +238,22 @@ jobs:
--health-timeout 5s
--health-retries 5
env:
APP_ENV: testing
APP_DEBUG: "false"
APP_KEY: ThisIsARandomStringForTests12345
APP_TIMEZONE: UTC
APP_URL: http://localhost/
CACHE_DRIVER: array
MAIL_MAILER: array
SESSION_DRIVER: array
QUEUE_CONNECTION: sync
DB_CONNECTION: pgsql
DB_HOST: 127.0.0.1
DB_DATABASE: testing
DB_USERNAME: postgres
DB_PASSWORD: postgres
GUZZLE_TIMEOUT: 60
GUZZLE_CONNECT_TIMEOUT: 60
steps:
- name: Code Checkout
uses: actions/checkout@v4

View File

@@ -66,6 +66,8 @@ jobs:
permissions:
contents: read
packages: write
strategy:
fail-fast: false
# Start a temp local registry because workflow can not pull from localy loaded images
services:
registry:
@@ -132,11 +134,6 @@ jobs:
docker push localhost:5000/base-php:arm64
rm base-php-arm64.tar base-php-amd64.tar
- name: Update version in config/app.php (tag)
if: "github.event_name == 'release' && github.event.action == 'published'"
run: |
sed -i "s/'version' => 'canary',/'version' => '${{ steps.build_info.outputs.version_tag }}',/" config/app.php
- name: Build and Push (tag)
uses: docker/build-push-action@v6
if: "github.event_name == 'release' && github.event.action == 'published'"

View File

@@ -33,7 +33,7 @@ jobs:
name: PHPStan
runs-on: ubuntu-latest
strategy:
fail-fast: true
fail-fast: false
matrix:
php: [ 8.2, 8.3, 8.4 ]
steps:

View File

@@ -1,20 +0,0 @@
name: Shift
on:
workflow_dispatch:
schedule:
- cron: "0 0 * * 5"
jobs:
shift:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Shift
run: |
curl -X POST -s -retry 5 -m 60 --fail-with-body \
https://laravelshift.com/api/run \
-H "Accept: application/json" \
-d "api_token=${{ secrets.SHIFT_TOKEN }}" \
-d "code=${{ secrets.SHIFT_CODE }}" \
-d "scs=github:${{ github.repository }}:${{ github.ref_name }}"

1
.gitignore vendored
View File

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

View File

@@ -6,7 +6,6 @@ use App\Traits\EnvironmentWriterTrait;
use Illuminate\Console\Command;
use Illuminate\Contracts\Console\Kernel;
use Illuminate\Database\DatabaseManager;
use PDOException;
class DatabaseSettingsCommand extends Command
{
@@ -106,7 +105,7 @@ class DatabaseSettingsCommand extends Command
]);
$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(trans('commands.database_settings.DB_error_2'));
@@ -166,7 +165,7 @@ class DatabaseSettingsCommand extends Command
]);
$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(trans('commands.database_settings.DB_error_2'));

View File

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

View File

@@ -4,8 +4,8 @@ namespace App\Console\Commands\Maintenance;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Illuminate\Contracts\Filesystem\Factory as FilesystemFactory;
use Illuminate\Contracts\Filesystem\Filesystem;
use Illuminate\Contracts\Filesystem\Factory as FilesystemFactory;
use SplFileInfo;
class CleanServiceBackupFilesCommand extends Command

View File

@@ -5,7 +5,6 @@ namespace App\Console\Commands\Maintenance;
use App\Models\Backup;
use Carbon\CarbonImmutable;
use Illuminate\Console\Command;
use InvalidArgumentException;
class PruneOrphanedBackupsCommand extends Command
{
@@ -17,7 +16,7 @@ class PruneOrphanedBackupsCommand extends Command
{
$since = $this->option('prune-age') ?? config('backups.prune_age', 360);
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()

View File

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

View File

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

View File

@@ -2,10 +2,10 @@
namespace App\Console\Commands\Schedule;
use App\Models\Schedule;
use App\Services\Schedules\ProcessScheduleService;
use Illuminate\Console\Command;
use App\Models\Schedule;
use Illuminate\Database\Eloquent\Builder;
use App\Services\Schedules\ProcessScheduleService;
use Throwable;
class ProcessRunnableCommand extends Command

View File

@@ -3,12 +3,12 @@
namespace App\Console\Commands\Server;
use App\Models\Server;
use App\Repositories\Daemon\DaemonServerRepository;
use Exception;
use Illuminate\Console\Command;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Validation\Factory as ValidatorFactory;
use Illuminate\Validation\ValidationException;
use Illuminate\Validation\Factory as ValidatorFactory;
use App\Repositories\Daemon\DaemonPowerRepository;
use Exception;
class BulkPowerActionCommand extends Command
{
@@ -19,7 +19,7 @@ class BulkPowerActionCommand extends Command
protected $description = 'Perform bulk power management on large groupings of servers or nodes at once.';
public function handle(DaemonServerRepository $serverRepository, ValidatorFactory $validator): void
public function handle(DaemonPowerRepository $powerRepository, ValidatorFactory $validator): void
{
$action = $this->argument('action');
$nodes = empty($this->option('nodes')) ? [] : explode(',', $this->option('nodes'));
@@ -52,7 +52,7 @@ class BulkPowerActionCommand extends Command
$bar = $this->output->createProgressBar($count);
$this->getQueryBuilder($servers, $nodes)->get()->each(function ($server, int $index) use ($action, $serverRepository, &$bar): mixed {
$this->getQueryBuilder($servers, $nodes)->get()->each(function ($server, int $index) use ($action, $powerRepository, &$bar): mixed {
$bar->clear();
if (!$server instanceof Server) {
@@ -60,7 +60,7 @@ class BulkPowerActionCommand extends Command
}
try {
$serverRepository->setServer($server)->power($action);
$powerRepository->setServer($server)->send($action);
} catch (Exception $exception) {
$this->output->error(trans('command/messages.server.power.action_failed', [
'name' => $server->name,

View File

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

View File

@@ -3,8 +3,8 @@
namespace App\Console\Commands\User;
use App\Models\User;
use Illuminate\Console\Command;
use Webmozart\Assert\Assert;
use Illuminate\Console\Command;
class DeleteUserCommand extends Command
{

View File

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

View File

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

View File

@@ -1,37 +0,0 @@
<?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

@@ -0,0 +1,141 @@
<?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

@@ -1,27 +0,0 @@
<?php
namespace App\Enums;
use Filament\Support\Contracts\HasColor;
use Filament\Support\Contracts\HasLabel;
enum ScheduleStatus: string implements HasColor, HasLabel
{
case Inactive = 'inactive';
case Processing = 'processing';
case Active = 'active';
public function getColor(): string
{
return match ($this) {
self::Inactive => 'danger',
self::Processing => 'warning',
self::Active => 'success',
};
}
public function getLabel(): string
{
return trans('server/schedule.schedule_status.' . $this->value);
}
}

View File

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

View File

@@ -2,9 +2,9 @@
namespace App\Enums;
use Filament\Support\Contracts\HasLabel;
use Filament\Support\Contracts\HasColor;
use Filament\Support\Contracts\HasIcon;
use Filament\Support\Contracts\HasLabel;
enum WebhookType: string implements HasColor, HasIcon, HasLabel
{

View File

@@ -2,9 +2,9 @@
namespace App\Events;
use Illuminate\Support\Str;
use App\Models\ActivityLog;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
class ActivityLogged extends Event
{

View File

@@ -2,8 +2,8 @@
namespace App\Events\Auth;
use App\Events\Event;
use App\Models\User;
use App\Events\Event;
class ProvidedAuthenticationToken extends Event
{

View File

@@ -4,14 +4,13 @@ namespace App\Exceptions;
use Exception;
use Filament\Notifications\Notification;
use Illuminate\Container\Container;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Psr\Log\LoggerInterface;
use Illuminate\Http\Response;
use Illuminate\Container\Container;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
use Throwable;
/**
* @deprecated
@@ -29,7 +28,7 @@ class DisplayException extends PanelException implements HttpExceptionInterface
/**
* 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);
}
@@ -80,11 +79,11 @@ class DisplayException extends PanelException implements HttpExceptionInterface
* Log the exception to the logs using the defined error level only if the previous
* exception is set.
*
* @throws Throwable
* @throws \Throwable
*/
public function report(): void
{
if (!$this->getPrevious() instanceof Exception || !Handler::isReportable($this->getPrevious())) {
if (!$this->getPrevious() instanceof \Exception || !Handler::isReportable($this->getPrevious())) {
return;
}

View File

@@ -2,27 +2,24 @@
namespace App\Exceptions;
use Exception;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Collection;
use Illuminate\Container\Container;
use Illuminate\Database\Connection;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Foundation\Application;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Session\TokenMismatchException;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
use PDOException;
use Symfony\Component\HttpFoundation\Response;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
use Symfony\Component\Mailer\Exception\TransportException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
use Throwable;
class Handler extends ExceptionHandler
@@ -82,7 +79,7 @@ class Handler extends ExceptionHandler
$this->dontReport = [];
}
$this->reportable(function (PDOException $ex) {
$this->reportable(function (\PDOException $ex) {
$ex = $this->generateCleanedExceptionStack($ex);
});
@@ -91,7 +88,7 @@ class Handler extends ExceptionHandler
});
}
private function generateCleanedExceptionStack(Throwable $exception): string
private function generateCleanedExceptionStack(\Throwable $exception): string
{
$cleanedStack = '';
foreach ($exception->getTrace() as $index => $item) {
@@ -120,11 +117,11 @@ class Handler extends ExceptionHandler
/**
* Render an exception into an HTTP response.
*
* @param Request $request
* @param \Illuminate\Http\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);
@@ -146,7 +143,7 @@ class Handler extends ExceptionHandler
* Transform a validation exception into a consistent format to be returned for
* calls to the API.
*
* @param Request $request
* @param \Illuminate\Http\Request $request
*/
public function invalidJson($request, ValidationException $exception): JsonResponse
{
@@ -252,7 +249,7 @@ class Handler extends ExceptionHandler
/**
* 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);
}
@@ -260,7 +257,7 @@ class Handler extends ExceptionHandler
/**
* Convert an authentication exception into an unauthenticated response.
*
* @param Request $request
* @param \Illuminate\Http\Request $request
*/
protected function unauthenticated($request, AuthenticationException $exception): JsonResponse|RedirectResponse
{
@@ -294,7 +291,7 @@ class Handler extends ExceptionHandler
*
* @return array<mixed>
*/
public static function toArray(Throwable $e): array
public static function toArray(\Throwable $e): array
{
return self::exceptionToArray($e);
}

View File

@@ -4,14 +4,13 @@ namespace App\Exceptions\Http;
use Illuminate\Http\Response;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Throwable;
class HttpForbiddenException extends HttpException
{
/**
* 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);
}

View File

@@ -5,7 +5,6 @@ namespace App\Exceptions\Http\Server;
use App\Enums\ServerState;
use App\Models\Server;
use Symfony\Component\HttpKernel\Exception\ConflictHttpException;
use Throwable;
class ServerStateConflictException extends ConflictHttpException
{
@@ -13,7 +12,7 @@ class ServerStateConflictException extends ConflictHttpException
* Exception thrown when the server is in an unsupported state for API access or
* 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.';
if ($server->isSuspended()) {

View File

@@ -0,0 +1,18 @@
<?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,15 +2,13 @@
namespace App\Exceptions;
use App\Exceptions\Solutions\ManifestDoesNotExistSolution;
use Exception;
use Spatie\Ignition\Contracts\ProvidesSolution;
use Spatie\Ignition\Contracts\Solution;
use Spatie\Ignition\Contracts\ProvidesSolution;
class ManifestDoesNotExistException extends Exception implements ProvidesSolution
class ManifestDoesNotExistException extends \Exception implements ProvidesSolution
{
public function getSolution(): Solution
{
return new ManifestDoesNotExistSolution();
return new Solutions\ManifestDoesNotExistSolution();
}
}

View File

@@ -2,11 +2,11 @@
namespace App\Exceptions\Model;
use Illuminate\Support\MessageBag;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Contracts\Validation\Validator;
use App\Exceptions\PanelException;
use Illuminate\Contracts\Support\MessageProvider;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\MessageBag;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
class DataValidationException extends PanelException implements HttpExceptionInterface, MessageProvider

View File

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

View File

@@ -2,8 +2,8 @@
namespace App\Exceptions\Service;
use App\Exceptions\DisplayException;
use Illuminate\Http\Response;
use App\Exceptions\DisplayException;
class HasActiveServersException extends DisplayException
{

View File

@@ -3,7 +3,6 @@
namespace App\Exceptions\Service;
use App\Exceptions\DisplayException;
use Throwable;
class ServiceLimitExceededException extends DisplayException
{
@@ -11,7 +10,7 @@ class ServiceLimitExceededException extends DisplayException
* Exception thrown when something goes over a defined limit, such as allocated
* 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);
}

View File

@@ -0,0 +1,17 @@
<?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,16 +2,15 @@
namespace App\Extensions\Backups;
use App\Extensions\Filesystem\S3Filesystem;
use Aws\S3\S3Client;
use Closure;
use Illuminate\Foundation\Application;
use Aws\S3\S3Client;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use InvalidArgumentException;
use League\Flysystem\FilesystemAdapter;
use League\Flysystem\InMemory\InMemoryFilesystemAdapter;
use Webmozart\Assert\Assert;
use Illuminate\Foundation\Application;
use League\Flysystem\FilesystemAdapter;
use App\Extensions\Filesystem\S3Filesystem;
use League\Flysystem\InMemory\InMemoryFilesystemAdapter;
class BackupManager
{
@@ -65,7 +64,7 @@ class BackupManager
$config = $this->getConfig($name);
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'];
@@ -83,7 +82,7 @@ class BackupManager
return $instance;
}
throw new InvalidArgumentException("Adapter [$adapter] is not supported.");
throw new \InvalidArgumentException("Adapter [$adapter] is not supported.");
}
/**

View File

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

View File

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

View File

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

View File

@@ -7,13 +7,13 @@ use App\Facades\Activity;
use App\Models\Permission;
use App\Models\Server;
use App\Models\ServerVariable;
use App\Repositories\Daemon\DaemonServerRepository;
use App\Repositories\Daemon\DaemonPowerRepository;
use Closure;
use Exception;
use Filament\Actions\Action;
use Filament\Facades\Filament;
use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\TextInput;
use Filament\Infolists\Components\TextEntry;
use Filament\Notifications\Notification;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Blade;
@@ -36,9 +36,6 @@ class GSLTokenSchema implements FeatureSchemaInterface
return 'gsl_token';
}
/**
* @throws Exception
*/
public function getAction(): Action
{
/** @var Server $server */
@@ -54,9 +51,9 @@ class GSLTokenSchema implements FeatureSchemaInterface
->modalHeading('Invalid GSL token')
->modalDescription('It seems like your Gameserver Login Token (GSL token) is invalid or has expired.')
->modalSubmitActionLabel('Update GSL Token')
->disabledSchema(fn () => !auth()->user()->can(Permission::ACTION_STARTUP_UPDATE, $server))
->schema([
TextEntry::make('info')
->disabledForm(fn () => !auth()->user()->can(Permission::ACTION_STARTUP_UPDATE, $server))
->form([
Placeholder::make('info')
->label(new HtmlString(Blade::render('You can either <x-filament::link href="https://steamcommunity.com/dev/managegameservers" target="_blank">generate a new one</x-filament::link> and enter it below or leave the field blank to remove it completely.'))),
TextInput::make('gsltoken')
->label('GSL Token')
@@ -73,12 +70,13 @@ class GSLTokenSchema implements FeatureSchemaInterface
}
},
])
->hintIcon('tabler-code', fn () => implode('|', $serverVariable->variable->rules))
->hintIcon('tabler-code')
->label(fn () => $serverVariable->variable->name)
->hintIconTooltip(fn () => implode('|', $serverVariable->variable->rules))
->prefix(fn () => '{{' . $serverVariable->variable->env_variable . '}}')
->helperText(fn () => empty($serverVariable->variable->description) ? '—' : $serverVariable->variable->description),
])
->action(function (array $data, DaemonServerRepository $serverRepository) use ($server, $serverVariable) {
->action(function (array $data, DaemonPowerRepository $powerRepository) use ($server, $serverVariable) {
/** @var Server $server */
$server = Filament::getTenant();
try {
@@ -100,7 +98,7 @@ class GSLTokenSchema implements FeatureSchemaInterface
->log();
}
$serverRepository->setServer($server)->power('restart');
$powerRepository->setServer($server)->send('restart');
Notification::make()
->title('GSL Token updated')

View File

@@ -6,12 +6,12 @@ use App\Extensions\Features\FeatureSchemaInterface;
use App\Facades\Activity;
use App\Models\Permission;
use App\Models\Server;
use App\Repositories\Daemon\DaemonServerRepository;
use App\Repositories\Daemon\DaemonPowerRepository;
use Exception;
use Filament\Actions\Action;
use Filament\Facades\Filament;
use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\Select;
use Filament\Infolists\Components\TextEntry;
use Filament\Notifications\Notification;
class JavaVersionSchema implements FeatureSchemaInterface
@@ -44,9 +44,9 @@ class JavaVersionSchema implements FeatureSchemaInterface
->modalHeading('Unsupported Java Version')
->modalDescription('This server is currently running an unsupported version of Java and cannot be started.')
->modalSubmitActionLabel('Update Docker Image')
->disabledSchema(fn () => !auth()->user()->can(Permission::ACTION_STARTUP_DOCKER_IMAGE, $server))
->schema([
TextEntry::make('java')
->disabledForm(fn () => !auth()->user()->can(Permission::ACTION_STARTUP_DOCKER_IMAGE, $server))
->form([
Placeholder::make('java')
->label('Please select a supported version from the list below to continue starting the server.'),
Select::make('image')
->label('Docker Image')
@@ -59,7 +59,7 @@ class JavaVersionSchema implements FeatureSchemaInterface
->preload()
->native(false),
])
->action(function (array $data, DaemonServerRepository $serverRepository) use ($server) {
->action(function (array $data, DaemonPowerRepository $powerRepository) use ($server) {
try {
$new = $data['image'];
$original = $server->image;
@@ -71,7 +71,7 @@ class JavaVersionSchema implements FeatureSchemaInterface
->log();
}
$serverRepository->setServer($server)->power('restart');
$powerRepository->setServer($server)->send('restart');
Notification::make()
->title('Docker image updated')

View File

@@ -5,7 +5,7 @@ namespace App\Extensions\Features\Schemas;
use App\Extensions\Features\FeatureSchemaInterface;
use App\Models\Server;
use App\Repositories\Daemon\DaemonFileRepository;
use App\Repositories\Daemon\DaemonServerRepository;
use App\Repositories\Daemon\DaemonPowerRepository;
use Exception;
use Filament\Actions\Action;
use Filament\Facades\Filament;
@@ -35,14 +35,14 @@ class MinecraftEulaSchema implements FeatureSchemaInterface
->modalHeading('Minecraft EULA')
->modalDescription(new HtmlString(Blade::render('By pressing "I Accept" below you are indicating your agreement to the <x-filament::link href="https://minecraft.net/eula" target="_blank">Minecraft EULA </x-filament::link>.')))
->modalSubmitActionLabel('I Accept')
->action(function (DaemonFileRepository $fileRepository, DaemonServerRepository $serverRepository) {
->action(function (DaemonFileRepository $fileRepository, DaemonPowerRepository $powerRepository) {
try {
/** @var Server $server */
$server = Filament::getTenant();
$fileRepository->setServer($server)->putContent('eula.txt', 'eula=true');
$serverRepository->setServer($server)->power('restart');
$powerRepository->setServer($server)->send('restart');
Notification::make()
->title('Minecraft EULA accepted')

View File

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

View File

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

View File

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

View File

@@ -2,12 +2,13 @@
namespace App\Extensions\OAuth\Schemas;
use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\TextInput;
use Filament\Infolists\Components\TextEntry;
use Filament\Schemas\Components\Wizard\Step;
use Filament\Forms\Components\Wizard\Step;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\HtmlString;
use SocialiteProviders\Discord\Provider;
use Webbingbrasil\FilamentCopyActions\Forms\Actions\CopyAction;
final class DiscordSchema extends OAuthSchema
{
@@ -26,17 +27,15 @@ final class DiscordSchema extends OAuthSchema
return array_merge([
Step::make('Register new Discord OAuth App')
->schema([
TextEntry::make('create_application')
->hiddenLabel()
->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>'))),
TextEntry::make('set_redirect')
->hiddenLabel()
->state(new HtmlString('<p>Under <b>Redirects</b> add the below URL.</p>')),
Placeholder::make('')
->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>'))),
Placeholder::make('')
->content(new HtmlString('<p>Under <b>Redirects</b> add the below URL.</p>')),
TextInput::make('_noenv_callback')
->label('Redirect URL')
->dehydrated()
->disabled()
->hintCopy()
->hintAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null)
->formatStateUsing(fn () => url('/auth/oauth/callback/discord')),
]),
], parent::getSetupSteps());

View File

@@ -2,11 +2,12 @@
namespace App\Extensions\OAuth\Schemas;
use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\TextInput;
use Filament\Infolists\Components\TextEntry;
use Filament\Schemas\Components\Wizard\Step;
use Filament\Forms\Components\Wizard\Step;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\HtmlString;
use Webbingbrasil\FilamentCopyActions\Forms\Actions\CopyAction;
final class GithubSchema extends OAuthSchema
{
@@ -20,24 +21,21 @@ final class GithubSchema extends OAuthSchema
return array_merge([
Step::make('Register new Github OAuth App')
->schema([
TextEntry::make('create_application')
->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>'))),
Placeholder::make('')
->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>'))),
TextInput::make('_noenv_callback')
->label('Authorization callback URL')
->dehydrated()
->disabled()
->hintCopy()
->hintAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null)
->default(fn () => url('/auth/oauth/callback/github')),
TextEntry::make('register_application')
->hiddenLabel()
->state(new HtmlString('<p>When you filled all fields click on <b>Register application</b>.</p>')),
Placeholder::make('')
->content(new HtmlString('<p>When you filled all fields click on <b>Register application</b>.</p>')),
]),
Step::make('Create Client Secret')
->schema([
TextEntry::make('create_client_secret')
->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>')),
Placeholder::make('')
->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>')),
]),
], parent::getSetupSteps());
}

View File

@@ -2,11 +2,12 @@
namespace App\Extensions\OAuth\Schemas;
use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\TextInput;
use Filament\Infolists\Components\TextEntry;
use Filament\Schemas\Components\Wizard\Step;
use Filament\Forms\Components\Wizard\Step;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\HtmlString;
use Webbingbrasil\FilamentCopyActions\Forms\Actions\CopyAction;
final class GitlabSchema extends OAuthSchema
{
@@ -40,14 +41,13 @@ final class GitlabSchema extends OAuthSchema
return array_merge([
Step::make('Register new Gitlab OAuth App')
->schema([
TextEntry::make('register_application')
->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.'))),
Placeholder::make('')
->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.'))),
TextInput::make('_noenv_callback')
->label('Redirect URI')
->dehydrated()
->disabled()
->hintCopy()
->hintAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null)
->default(fn () => url('/auth/oauth/callback/gitlab')),
]),
], parent::getSetupSteps());

View File

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

View File

@@ -2,9 +2,9 @@
namespace App\Extensions\OAuth\Schemas;
use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\TextInput;
use Filament\Infolists\Components\TextEntry;
use Filament\Schemas\Components\Wizard\Step;
use Filament\Forms\Components\Wizard\Step;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\HtmlString;
use SocialiteProviders\Steam\Provider;
@@ -52,9 +52,8 @@ final class SteamSchema extends OAuthSchema
return array_merge([
Step::make('Create API Key')
->schema([
TextEntry::make('create_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.'))),
Placeholder::make('')
->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.'))),
]),
], parent::getSetupSteps());
}

View File

@@ -2,22 +2,20 @@
namespace App\Extensions\Spatie\Fractalistic;
use App\Extensions\League\Fractal\Serializers\PanelSerializer;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
use League\Fractal\Scope;
use League\Fractal\TransformerAbstract;
use Spatie\Fractal\Fractal as SpatieFractal;
use Spatie\Fractalistic\Exceptions\InvalidTransformation;
use Spatie\Fractalistic\Exceptions\NoTransformerSpecified;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
use App\Extensions\League\Fractal\Serializers\PanelSerializer;
class Fractal extends SpatieFractal
{
/**
* Create fractal data.
*
* @throws InvalidTransformation
* @throws NoTransformerSpecified
* @throws \Spatie\Fractalistic\Exceptions\InvalidTransformation
* @throws \Spatie\Fractalistic\Exceptions\NoTransformerSpecified
*/
public function createData(): Scope
{

View File

@@ -2,8 +2,8 @@
namespace App\Facades;
use App\Services\Activity\ActivityLogService;
use Illuminate\Support\Facades\Facade;
use App\Services\Activity\ActivityLogService;
class Activity extends Facade
{

View File

@@ -2,8 +2,8 @@
namespace App\Facades;
use App\Services\Activity\ActivityLogTargetableService;
use Illuminate\Support\Facades\Facade;
use App\Services\Activity\ActivityLogTargetableService;
class LogTarget extends Facade
{

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,8 +1,8 @@
<?php
namespace App\Filament\Admin\Resources\ApiKeys\Pages;
namespace App\Filament\Admin\Resources\ApiKeyResource\Pages;
use App\Filament\Admin\Resources\ApiKeys\ApiKeyResource;
use App\Filament\Admin\Resources\ApiKeyResource;
use App\Models\ApiKey;
use App\Traits\Filament\CanCustomizeHeaderActions;
use App\Traits\Filament\CanCustomizeHeaderWidgets;
@@ -10,7 +10,6 @@ use Filament\Actions\Action;
use Filament\Actions\ActionGroup;
use Filament\Resources\Pages\CreateRecord;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
class CreateApiKey extends CreateRecord
{
@@ -37,7 +36,7 @@ class CreateApiKey extends CreateRecord
protected function handleRecordCreation(array $data): Model
{
$data['identifier'] = ApiKey::generateTokenIdentifier(ApiKey::TYPE_APPLICATION);
$data['token'] = Str::random(ApiKey::KEY_LENGTH);
$data['token'] = str_random(ApiKey::KEY_LENGTH);
$data['user_id'] = auth()->user()->id;
$data['key_type'] = ApiKey::TYPE_APPLICATION;

View File

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

View File

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

View File

@@ -1,31 +1,30 @@
<?php
namespace App\Filament\Admin\Resources\DatabaseHosts\Pages;
namespace App\Filament\Admin\Resources\DatabaseHostResource\Pages;
use App\Filament\Admin\Resources\DatabaseHosts\DatabaseHostResource;
use App\Filament\Admin\Resources\DatabaseHostResource;
use App\Services\Databases\Hosts\HostCreationService;
use App\Traits\Filament\CanCustomizeHeaderActions;
use App\Traits\Filament\CanCustomizeHeaderWidgets;
use Exception;
use Filament\Forms\Components\Fieldset;
use Filament\Forms\Components\Hidden;
use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
use Filament\Infolists\Components\TextEntry;
use Filament\Forms\Components\Wizard\Step;
use Filament\Forms\Get;
use Filament\Forms\Set;
use Filament\Notifications\Notification;
use Filament\Resources\Pages\CreateRecord;
use Filament\Resources\Pages\CreateRecord\Concerns\HasWizard;
use Filament\Schemas\Components\Fieldset;
use Filament\Schemas\Components\Utilities\Get;
use Filament\Schemas\Components\Utilities\Set;
use Filament\Schemas\Components\Wizard\Step;
use Filament\Support\Exceptions\Halt;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\HtmlString;
use Illuminate\Support\Str;
use PDOException;
use Throwable;
use Webbingbrasil\FilamentCopyActions\Forms\Actions\CopyAction;
class CreateDatabaseHost extends CreateRecord
{
@@ -44,18 +43,15 @@ class CreateDatabaseHost extends CreateRecord
$this->service = $service;
}
/** @return Step[]
* @throws Exception
*/
/** @return Step[] */
public function getSteps(): array
{
return [
Step::make(trans('admin/databasehost.setup.preparations'))
->columns()
->schema([
TextEntry::make('setup')
->hiddenLabel()
->state(trans('admin/databasehost.setup.note')),
Placeholder::make('')
->content(trans('admin/databasehost.setup.note')),
Toggle::make('different_server')
->label(new HtmlString(trans('admin/databasehost.setup.different_server')))
->dehydrated(false)
@@ -88,34 +84,31 @@ class CreateDatabaseHost extends CreateRecord
->schema([
Fieldset::make(trans('admin/databasehost.setup.database_user'))
->schema([
TextEntry::make('cli_login')
->hiddenLabel()
->state(new HtmlString(trans('admin/databasehost.setup.cli_login')))
Placeholder::make('')
->content(new HtmlString(trans('admin/databasehost.setup.cli_login')))
->columnSpanFull(),
TextInput::make('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')}';")
->disabled()
->dehydrated(false)
->copyable()
->suffixAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null)
->columnSpanFull(),
TextInput::make('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;")
->disabled()
->dehydrated(false)
->copyable()
->suffixAction(fn (string $state) => request()->isSecure() ? CopyAction::make()->copyable($state) : null)
->columnSpanFull(),
TextEntry::make('cli_exit')
->hiddenLabel()
->state(new HtmlString(trans('admin/databasehost.setup.cli_exit')))
Placeholder::make('')
->content(new HtmlString(trans('admin/databasehost.setup.cli_exit')))
->columnSpanFull(),
]),
Fieldset::make(trans('admin/databasehost.setup.external_access'))
->schema([
TextEntry::make('allow_external_access')
->hiddenLabel()
->state(new HtmlString(trans('admin/databasehost.setup.allow_external_access')))
Placeholder::make('')
->content(new HtmlString(trans('admin/databasehost.setup.allow_external_access')))
->columnSpanFull(),
]),
]),
@@ -143,7 +136,7 @@ class CreateDatabaseHost extends CreateRecord
->maxValue(65535),
TextInput::make('max_databases')
->label(trans('admin/databasehost.max_database'))
->helperText(trans('admin/databasehost.max_databases_help'))
->helpertext(trans('admin/databasehost.max_databases_help'))
->placeholder(trans('admin/databasehost.unlimited'))
->numeric(),
TextInput::make('name')
@@ -162,10 +155,6 @@ class CreateDatabaseHost extends CreateRecord
];
}
/**
* @throws Halt
* @throws Throwable
*/
protected function handleRecordCreation(array $data): Model
{
try {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,32 +1,31 @@
<?php
namespace App\Filament\Admin\Resources\Eggs\Pages;
namespace App\Filament\Admin\Resources\EggResource\Pages;
use App\Filament\Admin\Resources\Eggs\EggResource;
use AbdelhamidErrahmouni\FilamentMonacoEditor\MonacoEditor;
use App\Filament\Admin\Resources\EggResource;
use App\Filament\Components\Forms\Fields\CopyFrom;
use App\Models\EggVariable;
use App\Traits\Filament\CanCustomizeHeaderActions;
use App\Traits\Filament\CanCustomizeHeaderWidgets;
use Exception;
use Filament\Actions\Action;
use Filament\Actions\ActionGroup;
use Filament\Forms\Components\Checkbox;
use Filament\Forms\Components\CodeEditor;
use Filament\Forms\Components\Fieldset;
use Filament\Forms\Components\Hidden;
use Filament\Forms\Components\KeyValue;
use Filament\Forms\Components\Repeater;
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\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
use Filament\Forms\Form;
use Filament\Forms\Get;
use Filament\Forms\Set;
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 Filament\Schemas\Schema;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
use Illuminate\Validation\Rules\Unique;
@@ -53,13 +52,10 @@ class CreateEgg extends CreateRecord
return [];
}
/**
* @throws Exception
*/
public function form(Schema $schema): Schema
public function form(Form $form): Form
{
return $schema
->components([
return $form
->schema([
Tabs::make()->tabs([
Tab::make('configuration')
->label(trans('admin/egg.tabs.configuration'))
@@ -102,7 +98,8 @@ class CreateEgg extends CreateRecord
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 1, 'lg' => 1]),
Toggle::make('force_outgoing_ip')
->label(trans('admin/egg.force_ip'))
->hintIcon('tabler-question-mark', trans('admin/egg.force_ip_help')),
->hintIcon('tabler-question-mark')
->hintIconTooltip(trans('admin/egg.force_ip_help')),
Hidden::make('script_is_privileged')
->default(1),
TagsInput::make('tags')
@@ -110,7 +107,8 @@ class CreateEgg extends CreateRecord
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 2]),
TextInput::make('update_url')
->label(trans('admin/egg.update_url'))
->hintIcon('tabler-question-mark', trans('admin/egg.update_url_help'))
->hintIcon('tabler-question-mark')
->hintIconTooltip(trans('admin/egg.update_url_help'))
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 2])
->url(),
KeyValue::make('docker_images')
@@ -155,10 +153,11 @@ class CreateEgg extends CreateRecord
->columnSpanFull()
->schema([
Repeater::make('variables')
->hiddenLabel()
->label('')
->addActionLabel(trans('admin/egg.add_new_variable'))
->grid()
->relationship('variables')
->name('name')
->reorderable()->orderColumn()
->collapsible()->collapsed()
->columnSpan(2)
@@ -190,7 +189,7 @@ class CreateEgg extends CreateRecord
->maxLength(255)
->columnSpanFull()
->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')))
->unique(modifyRuleUsing: fn (Unique $rule, Get $get) => $rule->where('egg_id', $get('../../id')), ignoreRecord: true)
->validationMessages([
'unique' => trans('admin/egg.error_unique'),
])
@@ -201,8 +200,9 @@ class CreateEgg extends CreateRecord
->maxLength(255)
->prefix('{{')
->suffix('}}')
->hintIcon('tabler-code', fn ($state) => "{{{$state}}}")
->unique(modifyRuleUsing: fn (Unique $rule, Get $get) => $rule->where('egg_id', $get('../../id')))
->hintIcon('tabler-code')
->hintIconTooltip(fn ($state) => "{{{$state}}}")
->unique(modifyRuleUsing: fn (Unique $rule, Get $get) => $rule->where('egg_id', $get('../../id')), ignoreRecord: true)
->rules(EggVariable::getRulesForField('env_variable'))
->validationMessages([
'unique' => trans('admin/egg.error_unique'),
@@ -264,10 +264,13 @@ class CreateEgg extends CreateRecord
'/bin/bash' => '/bin/bash',
])
->required(),
CodeEditor::make('script_install')
MonacoEditor::make('script_install')
->label(trans('admin/egg.script_install'))
->columnSpanFull()
->lazy(),
->fontSize('16px')
->language('shell')
->lazy()
->view('filament.plugins.monaco-editor'),
]),
])->columnSpanFull()->persistTabInQueryString(),

View File

@@ -1,8 +1,9 @@
<?php
namespace App\Filament\Admin\Resources\Eggs\Pages;
namespace App\Filament\Admin\Resources\EggResource\Pages;
use App\Filament\Admin\Resources\Eggs\EggResource;
use AbdelhamidErrahmouni\FilamentMonacoEditor\MonacoEditor;
use App\Filament\Admin\Resources\EggResource;
use App\Filament\Components\Actions\ExportEggAction;
use App\Filament\Components\Actions\ImportEggAction;
use App\Filament\Components\Forms\Fields\CopyFrom;
@@ -10,27 +11,25 @@ use App\Models\Egg;
use App\Models\EggVariable;
use App\Traits\Filament\CanCustomizeHeaderActions;
use App\Traits\Filament\CanCustomizeHeaderWidgets;
use Exception;
use Filament\Actions\Action;
use Filament\Actions\ActionGroup;
use Filament\Actions\DeleteAction;
use Filament\Forms\Components\Checkbox;
use Filament\Forms\Components\CodeEditor;
use Filament\Forms\Components\Fieldset;
use Filament\Forms\Components\Hidden;
use Filament\Forms\Components\KeyValue;
use Filament\Forms\Components\Repeater;
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\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
use Filament\Forms\Form;
use Filament\Forms\Get;
use Filament\Forms\Set;
use Filament\Resources\Pages\EditRecord;
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 Filament\Schemas\Schema;
use Illuminate\Validation\Rules\Unique;
class EditEgg extends EditRecord
@@ -40,13 +39,10 @@ class EditEgg extends EditRecord
protected static string $resource = EggResource::class;
/**
* @throws Exception
*/
public function form(Schema $schema): Schema
public function form(Form $form): Form
{
return $schema
->components([
return $form
->schema([
Tabs::make()->tabs([
Tab::make('configuration')
->label(trans('admin/egg.tabs.configuration'))
@@ -97,7 +93,8 @@ class EditEgg extends EditRecord
Toggle::make('force_outgoing_ip')
->inline(false)
->label(trans('admin/egg.force_ip'))
->hintIcon('tabler-question-mark', trans('admin/egg.force_ip_help')),
->hintIcon('tabler-question-mark')
->hintIconTooltip(trans('admin/egg.force_ip_help')),
Hidden::make('script_is_privileged')
->helperText('The docker images available to servers using this egg.'),
TagsInput::make('tags')
@@ -106,7 +103,8 @@ class EditEgg extends EditRecord
TextInput::make('update_url')
->label(trans('admin/egg.update_url'))
->url()
->hintIcon('tabler-question-mark', trans('admin/egg.update_url_help'))
->hintIcon('tabler-question-mark')
->hintIconTooltip(trans('admin/egg.update_url_help'))
->columnSpan(['default' => 1, 'sm' => 1, 'md' => 2, 'lg' => 2]),
KeyValue::make('docker_images')
->label(trans('admin/egg.docker_images'))
@@ -145,9 +143,10 @@ class EditEgg extends EditRecord
->icon('tabler-variable')
->schema([
Repeater::make('variables')
->hiddenLabel()
->label('')
->grid()
->relationship('variables')
->name('name')
->reorderable()
->collapsible()->collapsed()
->orderColumn()
@@ -179,7 +178,7 @@ class EditEgg extends EditRecord
->maxLength(255)
->columnSpanFull()
->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')))
->unique(modifyRuleUsing: fn (Unique $rule, Get $get) => $rule->where('egg_id', $get('../../id')), ignoreRecord: true)
->validationMessages([
'unique' => trans('admin/egg.error_unique'),
])
@@ -190,8 +189,9 @@ class EditEgg extends EditRecord
->maxLength(255)
->prefix('{{')
->suffix('}}')
->hintIcon('tabler-code', fn ($state) => "{{{$state}}}")
->unique(modifyRuleUsing: fn (Unique $rule, Get $get) => $rule->where('egg_id', $get('../../id')))
->hintIcon('tabler-code')
->hintIconTooltip(fn ($state) => "{{{$state}}}")
->unique(modifyRuleUsing: fn (Unique $rule, Get $get) => $rule->where('egg_id', $get('../../id')), ignoreRecord: true)
->rules(EggVariable::getRulesForField('env_variable'))
->validationMessages([
'unique' => trans('admin/egg.error_unique'),
@@ -253,9 +253,13 @@ class EditEgg extends EditRecord
'/bin/bash' => '/bin/bash',
])
->required(),
CodeEditor::make('script_install')
->hiddenLabel()
->columnSpanFull(),
MonacoEditor::make('script_install')
->label(trans('admin/egg.script_install'))
->placeholderText('')
->columnSpanFull()
->fontSize('16px')
->language('shell')
->view('filament.plugins.monaco-editor'),
]),
])->columnSpanFull()->persistTabInQueryString(),
]);

View File

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

View File

@@ -1,6 +1,6 @@
<?php
namespace App\Filament\Admin\Resources\Eggs\RelationManagers;
namespace App\Filament\Admin\Resources\EggResource\RelationManagers;
use App\Models\Server;
use Filament\Resources\RelationManagers\RelationManager;
@@ -23,13 +23,16 @@ class ServersRelationManager extends RelationManager
->columns([
TextColumn::make('user.username')
->label(trans('admin/server.owner'))
->icon('tabler-user')
->url(fn (Server $server): string => route('filament.admin.resources.users.edit', ['record' => $server->user]))
->sortable(),
TextColumn::make('name')
->label(trans('admin/server.name'))
->icon('tabler-brand-docker')
->url(fn (Server $server): string => route('filament.admin.resources.servers.edit', ['record' => $server]))
->sortable(),
TextColumn::make('node.name')
->icon('tabler-server-2')
->url(fn (Server $server): string => route('filament.admin.resources.nodes.edit', ['record' => $server->node])),
TextColumn::make('image')
->label(trans('admin/server.docker_image')),
@@ -37,7 +40,7 @@ class ServersRelationManager extends RelationManager
->label(trans('admin/server.primary_allocation'))
->disabled()
->options(fn (Server $server) => $server->allocations->take(1)->mapWithKeys(fn ($allocation) => [$allocation->id => $allocation->address]))
->placeholder(trans('admin/server.none'))
->placeholder('None')
->sortable(),
]);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,24 +1,23 @@
<?php
namespace App\Filament\Admin\Resources\Nodes\Pages;
namespace App\Filament\Admin\Resources\NodeResource\Pages;
use App\Filament\Admin\Resources\Nodes\NodeResource;
use App\Filament\Admin\Resources\NodeResource;
use App\Models\Node;
use App\Traits\Filament\CanCustomizeHeaderActions;
use App\Traits\Filament\CanCustomizeHeaderWidgets;
use Exception;
use Filament\Actions\Action;
use Filament\Forms;
use Filament\Forms\Components\Actions\Action;
use Filament\Forms\Components\Grid;
use Filament\Forms\Components\Hidden;
use Filament\Forms\Components\TagsInput;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\ToggleButtons;
use Filament\Forms\Components\Wizard;
use Filament\Forms\Components\Wizard\Step;
use Filament\Forms\Get;
use Filament\Forms\Set;
use Filament\Resources\Pages\CreateRecord;
use Filament\Schemas\Components\Grid;
use Filament\Schemas\Components\Utilities\Get;
use Filament\Schemas\Components\Utilities\Set;
use Filament\Schemas\Components\Wizard;
use Filament\Schemas\Components\Wizard\Step;
use Filament\Schemas\Schema;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\HtmlString;
@@ -31,13 +30,10 @@ class CreateNode extends CreateRecord
protected static bool $canCreateAnother = false;
/**
* @throws Exception
*/
public function form(Schema $schema): Schema
public function form(Forms\Form $form): Forms\Form
{
return $schema
->components([
return $form
->schema([
Wizard::make([
Step::make('basic')
->label(trans('admin/node.tabs.basic_settings'))
@@ -226,7 +222,8 @@ class CreateNode extends CreateRecord
->label(trans('admin/node.maintenance_mode'))->inline()
->columnSpan(1)
->default(false)
->hintIcon('tabler-question-mark', trans('admin/node.maintenance_mode_help'))
->hinticon('tabler-question-mark')
->hintIconTooltip(trans('admin/node.maintenance_mode_help'))
->options([
true => trans('admin/node.enabled'),
false => trans('admin/node.disabled'),
@@ -253,7 +250,8 @@ class CreateNode extends CreateRecord
TextInput::make('upload_size')
->label(trans('admin/node.upload_limit'))
->helperText(trans('admin/node.upload_limit_help.0'))
->hintIcon('tabler-question-mark', trans('admin/node.upload_limit_help.1'))
->hintIcon('tabler-question-mark')
->hintIconTooltip(trans('admin/node.upload_limit_help.1'))
->columnSpan(1)
->numeric()->required()
->default(256)
@@ -415,7 +413,7 @@ class CreateNode extends CreateRecord
protected function getRedirectUrlParameters(): array
{
return [
'tab' => 'configuration-file::data::tab',
'tab' => '-configuration-file-tab',
];
}

View File

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

View File

@@ -1,18 +1,17 @@
<?php
namespace App\Filament\Admin\Resources\Nodes\Pages;
namespace App\Filament\Admin\Resources\NodeResource\Pages;
use App\Filament\Admin\Resources\Nodes\NodeResource;
use App\Filament\Admin\Resources\NodeResource;
use App\Filament\Components\Tables\Columns\NodeHealthColumn;
use App\Filament\Components\Tables\Filters\TagsFilter;
use App\Models\Node;
use App\Traits\Filament\CanCustomizeHeaderActions;
use App\Traits\Filament\CanCustomizeHeaderWidgets;
use Filament\Actions\Action;
use Filament\Actions\ActionGroup;
use Filament\Actions\CreateAction;
use Filament\Actions\EditAction;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;
use Filament\Tables\Actions\CreateAction;
use Filament\Tables\Actions\EditAction;
use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
@@ -37,11 +36,13 @@ class ListNodes extends ListRecords
NodeHealthColumn::make('health'),
TextColumn::make('name')
->label(trans('admin/node.table.name'))
->icon('tabler-server-2')
->sortable()
->searchable(),
TextColumn::make('fqdn')
->visibleFrom('md')
->label(trans('admin/node.table.address'))
->icon('tabler-network')
->sortable()
->searchable(),
IconColumn::make('scheme')
@@ -59,9 +60,10 @@ class ListNodes extends ListRecords
->visibleFrom('sm')
->counts('servers')
->label(trans('admin/node.table.servers'))
->sortable(),
->sortable()
->icon('tabler-brand-docker'),
])
->recordActions([
->actions([
EditAction::make(),
])
->emptyStateIcon('tabler-server-2')
@@ -76,11 +78,11 @@ class ListNodes extends ListRecords
]);
}
/** @return array<Action|ActionGroup> */
/** @return array<Actions\Action|Actions\ActionGroup> */
protected function getDefaultHeaderActions(): array
{
return [
CreateAction::make()
Actions\CreateAction::make()
->hidden(fn () => Node::count() <= 0),
];
}

View File

@@ -1,20 +1,19 @@
<?php
namespace App\Filament\Admin\Resources\Nodes\RelationManagers;
namespace App\Filament\Admin\Resources\NodeResource\RelationManagers;
use App\Filament\Admin\Resources\Servers\Pages\CreateServer;
use App\Filament\Admin\Resources\ServerResource\Pages\CreateServer;
use App\Models\Allocation;
use App\Models\Node;
use App\Services\Allocations\AssignmentService;
use Exception;
use Filament\Actions\Action;
use Filament\Actions\DeleteBulkAction;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TagsInput;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Get;
use Filament\Forms\Set;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Schemas\Components\Utilities\Get;
use Filament\Schemas\Components\Utilities\Set;
use Filament\Tables\Actions\Action;
use Filament\Tables\Actions\DeleteBulkAction;
use Filament\Tables\Columns\SelectColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Columns\TextInputColumn;
@@ -27,22 +26,19 @@ class AllocationsRelationManager extends RelationManager
{
protected static string $relationship = 'allocations';
protected static string|\BackedEnum|null $icon = 'tabler-plug-connected';
protected static ?string $icon = 'tabler-plug-connected';
public function setTitle(): string
{
return trans('admin/server.allocations');
}
/**
* @throws Exception
*/
public function table(Table $table): Table
{
return $table
->recordTitleAttribute('address')
->checkIfRecordIsSelectableUsing(fn (Allocation $allocation) => $allocation->server_id === null)
->paginationPageOptions([10, 20, 50, 100, 200, 500])
->paginationPageOptions(['10', '20', '50', '100', '200', '500'])
->searchable()
->heading('')
->selectCurrentPageOnly() //Prevent people from trying to nuke 30,000 ports at once.... -,-
@@ -83,24 +79,15 @@ class AllocationsRelationManager extends RelationManager
->headerActions([
Action::make('create new allocation')
->label(trans('admin/node.create_allocation'))
->schema(fn () => [
->form(fn () => [
Select::make('allocation_ip')
->options(fn () => 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'))
->inlineLabel()
->ip()
->helperText(trans('admin/node.ip_help'))
->afterStateUpdated(fn (Set $set) => $set('allocation_ports', []))
->live()
->hintAction(
Action::make('refresh')
->iconButton()
->icon('tabler-refresh')
->tooltip(trans('admin/node.refresh'))
->action(function () {
cache()->forget("nodes.{$this->getOwnerRecord()->id}.ips");
})
)
->required(),
TextInput::make('allocation_alias')
->label(trans('admin/node.table.alias'))

View File

@@ -1,6 +1,6 @@
<?php
namespace App\Filament\Admin\Resources\Nodes\RelationManagers;
namespace App\Filament\Admin\Resources\NodeResource\RelationManagers;
use App\Models\Server;
use Filament\Resources\RelationManagers\RelationManager;
@@ -12,7 +12,7 @@ class NodesRelationManager extends RelationManager
{
protected static string $relationship = 'servers';
protected static string|\BackedEnum|null $icon = 'tabler-brand-docker';
protected static ?string $icon = 'tabler-brand-docker';
public function setTitle(): string
{
@@ -27,15 +27,18 @@ class NodesRelationManager extends RelationManager
->columns([
TextColumn::make('user.username')
->label(trans('admin/node.table.owner'))
->icon('tabler-user')
->url(fn (Server $server): string => route('filament.admin.resources.users.edit', ['record' => $server->user]))
->searchable(),
TextColumn::make('name')
->label(trans('admin/node.table.name'))
->icon('tabler-brand-docker')
->url(fn (Server $server): string => route('filament.admin.resources.servers.edit', ['record' => $server]))
->searchable()
->sortable(),
TextColumn::make('egg.name')
->label(trans('admin/node.table.egg'))
->icon('tabler-egg')
->url(fn (Server $server): string => route('filament.admin.resources.eggs.edit', ['record' => $server->user]))
->sortable(),
SelectColumn::make('allocation.id')
@@ -43,18 +46,20 @@ class NodesRelationManager extends RelationManager
->disabled(fn (Server $server) => $server->allocations->count() <= 1)
->options(fn (Server $server) => $server->allocations->take(1)->mapWithKeys(fn ($allocation) => [$allocation->id => $allocation->address]))
->selectablePlaceholder(fn (SelectColumn $select) => !$select->isDisabled())
->placeholder(trans('admin/node.none'))
->placeholder('None')
->sortable(),
TextColumn::make('memory')->label(trans('admin/node.memory')),
TextColumn::make('cpu')->label(trans('admin/node.cpu')),
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('databases_count')
->counts('databases')
->label(trans('admin/node.databases'))
->icon('tabler-database')
->numeric()
->sortable(),
TextColumn::make('backups_count')
->counts('backups')
->label(trans('admin/node.backups'))
->icon('tabler-file-download')
->numeric()
->sortable(),
]);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,8 +1,8 @@
<?php
namespace App\Filament\Admin\Resources\Servers\Pages;
namespace App\Filament\Admin\Resources\ServerResource\Pages;
use App\Filament\Admin\Resources\Servers\ServerResource;
use App\Filament\Admin\Resources\ServerResource;
use App\Filament\Components\Forms\Fields\StartupVariable;
use App\Models\Allocation;
use App\Models\Egg;
@@ -15,33 +15,33 @@ use App\Services\Users\UserCreationService;
use App\Traits\Filament\CanCustomizeHeaderActions;
use App\Traits\Filament\CanCustomizeHeaderWidgets;
use Exception;
use Filament\Actions\Action;
use Filament\Forms;
use Filament\Forms\Components\Actions\Action;
use Filament\Forms\Components\Fieldset;
use Filament\Forms\Components\Grid;
use Filament\Forms\Components\Hidden;
use Filament\Forms\Components\KeyValue;
use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\Repeater;
use Filament\Forms\Components\Section;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TagsInput;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\ToggleButtons;
use Filament\Infolists\Components\TextEntry;
use Filament\Forms\Components\Wizard;
use Filament\Forms\Components\Wizard\Step;
use Filament\Forms\Form;
use Filament\Forms\Get;
use Filament\Forms\Set;
use Filament\Notifications\Notification;
use Filament\Resources\Pages\CreateRecord;
use Filament\Schemas\Components\Fieldset;
use Filament\Schemas\Components\Grid;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Components\Utilities\Get;
use Filament\Schemas\Components\Utilities\Set;
use Filament\Schemas\Components\Wizard;
use Filament\Schemas\Components\Wizard\Step;
use Filament\Schemas\Schema;
use Filament\Support\Exceptions\Halt;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\HtmlString;
use LogicException;
use Random\RandomException;
class CreateServer extends CreateRecord
{
@@ -61,14 +61,10 @@ class CreateServer extends CreateRecord
$this->serverCreationService = $serverCreationService;
}
/**
* @throws RandomException
* @throws Exception
*/
public function form(Schema $schema): Schema
public function form(Form $form): Form
{
return $schema
->components([
return $form
->schema([
Wizard::make([
Step::make('Information')
->label(trans('admin/server.tabs.information'))
@@ -83,7 +79,7 @@ class CreateServer extends CreateRecord
TextInput::make('name')
->prefixIcon('tabler-server')
->label(trans('admin/server.name'))
->suffixAction(Action::make('random')
->suffixAction(Forms\Components\Actions\Action::make('random')
->icon('tabler-dice-' . random_int(1, 6))
->action(function (Set $set, Get $get) {
$egg = Egg::find($get('egg_id'));
@@ -108,7 +104,7 @@ class CreateServer extends CreateRecord
'sm' => 2,
'md' => 2,
])
->unique()
->unique(ignoreRecord: true)
->maxLength(255),
Select::make('node_id')
@@ -169,7 +165,8 @@ class CreateServer extends CreateRecord
TextInput::make('password')
->label(trans('admin/user.password'))
->hintIcon('tabler-question-mark', trans('admin/user.password_help'))
->hintIcon('tabler-question-mark')
->hintIconTooltip(trans('admin/user.password_help'))
->password(),
])
->createOptionUsing(function ($data, UserCreationService $service) {
@@ -218,21 +215,12 @@ class CreateServer extends CreateRecord
return [
Select::make('allocation_ip')
->options(fn () => collect(Node::find($get('node_id'))?->ipAddresses())->mapWithKeys(fn (string $ip) => [$ip => $ip]))
->options(collect(Node::find($get('node_id'))?->ipAddresses())->mapWithKeys(fn (string $ip) => [$ip => $ip]))
->label(trans('admin/server.ip_address'))->inlineLabel()
->helperText(trans('admin/server.ip_address_helper'))
->afterStateUpdated(fn (Set $set) => $set('allocation_ports', []))
->ip()
->live()
->hintAction(
Action::make('refresh')
->iconButton()
->icon('tabler-refresh')
->tooltip(trans('admin/node.refresh'))
->action(function () use ($get) {
cache()->forget("nodes.{$get('node_id')}.ips");
})
)
->required(),
TextInput::make('allocation_alias')
->label(trans('admin/server.alias'))->inlineLabel()
@@ -430,12 +418,14 @@ class CreateServer extends CreateRecord
->collapsible()
->columnSpanFull()
->schema([
TextEntry::make(trans('admin/server.select_egg'))
Placeholder::make(trans('admin/server.select_egg'))
->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') ||
Egg::query()->find($get('egg_id'))?->variables()?->count()
),
Repeater::make('server_variables')
->hiddenLabel()
->relationship('serverVariables', fn (Builder $query) => $query->orderByPowerJoins('variable.sort'))
@@ -529,7 +519,8 @@ class CreateServer extends CreateRecord
->hidden(fn (Get $get) => $get('unlimited_mem'))
->label(trans('admin/server.memory_limit'))->inlineLabel()
->suffix(config('panel.use_binary_prefix') ? 'MiB' : 'MB')
->hintIcon('tabler-question-mark', trans('admin/server.memory_helper'))
->hintIcon('tabler-question-mark')
->hintIconToolTip(trans('admin/server.memory_helper'))
->default(0)
->required()
->columnSpan(2)

View File

@@ -1,21 +1,24 @@
<?php
namespace App\Filament\Admin\Resources\Servers\Pages;
namespace App\Filament\Admin\Resources\ServerResource\Pages;
use AbdelhamidErrahmouni\FilamentMonacoEditor\MonacoEditor;
use App\Enums\SuspendAction;
use App\Filament\Admin\Resources\Servers\RelationManagers\AllocationsRelationManager;
use App\Filament\Admin\Resources\Servers\RelationManagers\DatabasesRelationManager;
use App\Filament\Admin\Resources\Servers\ServerResource;
use App\Filament\Components\Actions\PreviewStartupAction;
use App\Filament\Admin\Resources\ServerResource;
use App\Filament\Components\Forms\Actions\PreviewStartupAction;
use App\Filament\Components\Forms\Actions\RotateDatabasePasswordAction;
use App\Filament\Components\Forms\Fields\StartupVariable;
use App\Filament\Components\StateCasts\ServerConditionStateCast;
use App\Filament\Server\Pages\Console;
use App\Models\Allocation;
use App\Models\Database;
use App\Models\DatabaseHost;
use App\Models\Egg;
use App\Models\Node;
use App\Models\Server;
use App\Models\ServerVariable;
use App\Models\User;
use App\Repositories\Daemon\DaemonServerRepository;
use App\Services\Databases\DatabaseManagementService;
use App\Services\Eggs\EggChangerService;
use App\Services\Servers\RandomWordService;
use App\Services\Servers\ReinstallServerService;
@@ -26,37 +29,35 @@ use App\Services\Servers\TransferServerService;
use App\Traits\Filament\CanCustomizeHeaderActions;
use App\Traits\Filament\CanCustomizeHeaderWidgets;
use Exception;
use Filament\Actions\Action;
use Filament\Actions\ActionGroup;
use Filament\Forms\Components\CodeEditor;
use Filament\Actions;
use Filament\Forms\Components\Actions as FormActions;
use Filament\Forms\Components\Actions\Action;
use Filament\Forms\Components\Component;
use Filament\Forms\Components\Fieldset;
use Filament\Forms\Components\Grid;
use Filament\Forms\Components\Hidden;
use Filament\Forms\Components\KeyValue;
use Filament\Forms\Components\Repeater;
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\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
use Filament\Forms\Components\ToggleButtons;
use Filament\Forms\Form;
use Filament\Forms\Get;
use Filament\Forms\Set;
use Filament\Notifications\Notification;
use Filament\Resources\Pages\EditRecord;
use Filament\Schemas\Components\Actions;
use Filament\Schemas\Components\Component;
use Filament\Schemas\Components\Fieldset;
use Filament\Schemas\Components\Grid;
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\Schemas\Schema;
use Filament\Support\Enums\Alignment;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Support\Arr;
use Illuminate\Support\HtmlString;
use LogicException;
use Random\RandomException;
use Webbingbrasil\FilamentCopyActions\Forms\Actions\CopyAction;
class EditServer extends EditRecord
{
@@ -72,14 +73,10 @@ class EditServer extends EditRecord
$this->daemonServerRepository = $daemonServerRepository;
}
/**
* @throws RandomException
* @throws Exception
*/
public function form(Schema $schema): Schema
public function form(Form $form): Form
{
return $schema
->components([
return $form
->schema([
Tabs::make('Tabs')
->persistTabInQueryString()
->columns([
@@ -115,6 +112,7 @@ class EditServer extends EditRecord
])
->required()
->maxLength(255),
Select::make('owner_id')
->prefixIcon('tabler-user')
->label(trans('admin/server.owner'))
@@ -129,13 +127,13 @@ class EditServer extends EditRecord
->getOptionLabelFromRecordUsing(fn (User $user) => "$user->username ($user->email)")
->preload()
->required(),
ToggleButtons::make('condition')
->label(trans('admin/server.server_status'))
->formatStateUsing(fn (Server $server) => $server->condition)
->options(fn ($state) => [$state->value => $state->getLabel()])
->colors(fn ($state) => [$state->value => $state->getColor()])
->icons(fn ($state) => [$state->value => $state->getIcon()])
->stateCast(new ServerConditionStateCast())
->columnSpan([
'default' => 2,
'sm' => 1,
@@ -150,9 +148,10 @@ class EditServer extends EditRecord
->modalSubmitAction(false)
->modalFooterActionsAlignment(Alignment::Right)
->modalCancelActionLabel(trans('filament::components/modal.actions.close.label'))
->schema([
CodeEditor::make('logs')
->form([
MonacoEditor::make('logs')
->hiddenLabel()
->placeholderText(trans('admin/server.no_log'))
->formatStateUsing(function (Server $server, DaemonServerRepository $serverRepository) {
try {
return $serverRepository->setServer($server)->getInstallLogs();
@@ -168,7 +167,9 @@ class EditServer extends EditRecord
}
return '';
}),
})
->language('shell')
->view('filament.plugins.monaco-editor-logs'),
])
),
@@ -178,7 +179,7 @@ class EditServer extends EditRecord
TextInput::make('uuid')
->label(trans('admin/server.uuid'))
->copyable()
->suffixAction(fn () => request()->isSecure() ? CopyAction::make() : null)
->columnSpan([
'default' => 2,
'sm' => 1,
@@ -189,7 +190,7 @@ class EditServer extends EditRecord
->dehydrated(false),
TextInput::make('uuid_short')
->label(trans('admin/server.short_uuid'))
->copyable()
->suffixAction(fn () => request()->isSecure() ? CopyAction::make() : null)
->columnSpan([
'default' => 2,
'sm' => 1,
@@ -206,7 +207,7 @@ class EditServer extends EditRecord
'md' => 2,
'lg' => 3,
])
->unique()
->unique(ignoreRecord: true)
->maxLength(255),
Select::make('node_id')
->label(trans('admin/server.node'))
@@ -224,7 +225,6 @@ class EditServer extends EditRecord
->icon('tabler-brand-docker')
->schema([
Fieldset::make(trans('admin/server.resource_limits'))
->columnSpanFull()
->columns([
'default' => 1,
'sm' => 2,
@@ -242,14 +242,13 @@ class EditServer extends EditRecord
->afterStateUpdated(fn (Set $set) => $set('cpu', 0))
->formatStateUsing(fn (Get $get) => $get('cpu') == 0)
->live()
->stateCast(new BooleanStateCast(false, true))
->options([
1 => trans('admin/server.unlimited'),
0 => trans('admin/server.limited'),
true => trans('admin/server.unlimited'),
false => trans('admin/server.limited'),
])
->colors([
1 => 'primary',
0 => 'warning',
true => 'primary',
false => 'warning',
])
->columnSpan(2),
@@ -273,14 +272,13 @@ class EditServer extends EditRecord
->afterStateUpdated(fn (Set $set) => $set('memory', 0))
->formatStateUsing(fn (Get $get) => $get('memory') == 0)
->live()
->stateCast(new BooleanStateCast(false, true))
->options([
1 => trans('admin/server.unlimited'),
0 => trans('admin/server.limited'),
true => trans('admin/server.unlimited'),
false => trans('admin/server.limited'),
])
->colors([
1 => 'primary',
0 => 'warning',
true => 'primary',
false => 'warning',
])
->columnSpan(2),
@@ -289,7 +287,8 @@ class EditServer extends EditRecord
->hidden(fn (Get $get) => $get('unlimited_mem'))
->label(trans('admin/server.memory_limit'))->inlineLabel()
->suffix(config('panel.use_binary_prefix') ? 'MiB' : 'MB')
->hintIcon('tabler-question-mark', trans('admin/server.memory_helper'))
->hintIcon('tabler-question-mark')
->hintIconToolTip(trans('admin/server.memory_helper'))
->required()
->columnSpan(2)
->numeric()
@@ -306,14 +305,13 @@ class EditServer extends EditRecord
->live()
->afterStateUpdated(fn (Set $set) => $set('disk', 0))
->formatStateUsing(fn (Get $get) => $get('disk') == 0)
->stateCast(new BooleanStateCast(false, true))
->options([
1 => trans('admin/server.unlimited'),
0 => trans('admin/server.limited'),
true => trans('admin/server.unlimited'),
false => trans('admin/server.limited'),
])
->colors([
1 => 'primary',
0 => 'warning',
true => 'primary',
false => 'warning',
])
->columnSpan(2),
@@ -330,7 +328,6 @@ class EditServer extends EditRecord
]),
Fieldset::make(trans('admin/server.advanced_limits'))
->columnSpanFull()
->columns([
'default' => 1,
'sm' => 2,
@@ -348,18 +345,17 @@ class EditServer extends EditRecord
->schema([
ToggleButtons::make('cpu_pinning')
->label(trans('admin/server.cpu_pin'))->inlineLabel()->inline()
->default(0)
->default(false)
->afterStateUpdated(fn (Set $set) => $set('threads', []))
->formatStateUsing(fn (Get $get) => !empty($get('threads')))
->live()
->stateCast(new BooleanStateCast(false, true))
->options([
0 => trans('admin/server.disabled'),
1 => trans('admin/server.enabled'),
false => trans('admin/server.disabled'),
true => trans('admin/server.enabled'),
])
->colors([
0 => 'success',
1 => 'warning',
false => 'success',
true => 'warning',
])
->columnSpan(2),
@@ -429,27 +425,21 @@ class EditServer extends EditRecord
->columnSpanFull()
->schema([
ToggleButtons::make('oom_killer')
->dehydrated()
->label(trans('admin/server.oom'))
->formatStateUsing(fn ($state) => $state)
->inlineLabel()
->inline()
->label(trans('admin/server.oom'))->inlineLabel()->inline()
->columnSpan(2)
->stateCast(new BooleanStateCast(false, true))
->options([
0 => trans('admin/server.disabled'),
1 => trans('admin/server.enabled'),
false => trans('admin/server.disabled'),
true => trans('admin/server.enabled'),
])
->colors([
0 => 'success',
1 => 'danger',
false => 'success',
true => 'danger',
]),
]),
]),
Fieldset::make(trans('admin/server.feature_limits'))
->inlineLabel()
->columnSpanFull()
->columns([
'default' => 1,
'sm' => 2,
@@ -477,7 +467,6 @@ class EditServer extends EditRecord
->numeric(),
]),
Fieldset::make(trans('admin/server.docker_settings'))
->columnSpanFull()
->columns([
'default' => 1,
'sm' => 2,
@@ -567,12 +556,12 @@ class EditServer extends EditRecord
Action::make('change_egg')
->label(trans('admin/server.change_egg'))
->action(function (array $data, Server $server, EggChangerService $service) {
$service->handle($server, $data['egg_id'], $data['keep_old_variables']);
$service->handle($server, $data['egg_id'], $data['keepOldVariables']);
// Use redirect instead of fillForm to prevent server variables from duplicating
$this->redirect($this->getUrl(['record' => $server, 'tab' => 'egg::data::tab']), true);
$this->redirect($this->getUrl(['record' => $server, 'tab' => '-egg-tab']), true);
})
->schema(fn (Server $server) => [
->form(fn (Server $server) => [
Select::make('egg_id')
->label(trans('admin/server.new_egg'))
->prefixIcon('tabler-egg')
@@ -580,33 +569,31 @@ class EditServer extends EditRecord
->searchable()
->preload()
->required(),
Toggle::make('keep_old_variables')
Toggle::make('keepOldVariables')
->label(trans('admin/server.keep_old_variables'))
->default(true),
])
),
ToggleButtons::make('skip_scripts')
->label(trans('admin/server.install_script'))
->inline()
->label(trans('admin/server.install_script'))->inline()
->columnSpan([
'default' => 6,
'sm' => 1,
'md' => 1,
'lg' => 2,
])
->stateCast(new BooleanStateCast(false, true))
->options([
0 => trans('admin/server.yes'),
1 => trans('admin/server.skip'),
false => trans('admin/server.yes'),
true => trans('admin/server.skip'),
])
->colors([
0 => 'primary',
1 => 'danger',
false => 'primary',
true => 'danger',
])
->icons([
0 => 'tabler-code',
1 => 'tabler-code-off',
false => 'tabler-code',
true => 'tabler-code-off',
])
->required(),
Hidden::make('previewing')
@@ -619,7 +606,7 @@ class EditServer extends EditRecord
->hintAction(PreviewStartupAction::make('preview')),
Textarea::make('defaultStartup')
->hintCopy()
->hintAction(fn () => request()->isSecure() ? CopyAction::make() : null)
->label(trans('admin/server.default_startup'))
->disabled()
->autosize()
@@ -636,7 +623,14 @@ class EditServer extends EditRecord
/** @var Server $server */
$server = $this->getRecord();
$server->ensureVariablesExist();
foreach ($server->variables as $variable) {
ServerVariable::query()->firstOrCreate([
'server_id' => $server->id,
'variable_id' => $variable->id,
], [
'variable_value' => $variable->server_value ?? '',
]);
}
return $query->orderByPowerJoins('variable.sort');
})
@@ -659,12 +653,142 @@ class EditServer extends EditRecord
->schema(fn (Get $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 $databaseManagementService, $record) {
$databaseManagementService->delete($record);
$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, RandomWordService $randomWordService) {
if (empty($data['database'])) {
$data['database'] = $randomWordService->word() . random_int(1, 420);
}
if (empty($data['remote'])) {
$data['remote'] = '%';
}
$data['database'] = $service->generateUniqueDatabaseName($data['database'], $server->id);
try {
$service->setValidateDatabaseLimit(false)->create($server, $data);
} catch (Exception $e) {
Notification::make()
->title(trans('admin/server.failed_to_create'))
->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')
->label(trans('admin/server.actions'))
->icon('tabler-settings')
->schema([
Fieldset::make(trans('admin/server.actions'))
->columnSpanFull()
->columns([
'default' => 1,
'sm' => 2,
@@ -675,7 +799,7 @@ class EditServer extends EditRecord
Grid::make()
->columnSpan(3)
->schema([
Actions::make([
FormActions::make([
Action::make('toggleInstall')
->label(trans('admin/server.toggle_install'))
->disabled(fn (Server $server) => $server->isSuspended())
@@ -693,6 +817,7 @@ class EditServer extends EditRecord
->success()
->send();
$this->refreshFormData(['status', 'docker']);
} catch (Exception) {
Notification::make()
->title(trans('admin/server.notifications.reinstall_failed'))
@@ -709,6 +834,7 @@ class EditServer extends EditRecord
->success()
->send();
$this->refreshFormData(['status', 'docker']);
} catch (Exception $exception) {
Notification::make()
->title(trans('admin/server.notifications.install_toggle_failed'))
@@ -719,14 +845,13 @@ class EditServer extends EditRecord
}
}),
])->fullWidth(),
ToggleButtons::make('install_help')
->hiddenLabel()
ToggleButtons::make('')
->hint(trans('admin/server.toggle_install_help')),
]),
Grid::make()
->columnSpan(3)
->schema([
Actions::make([
FormActions::make([
Action::make('toggleSuspend')
->label(trans('admin/server.suspend'))
->color('warning')
@@ -740,6 +865,7 @@ class EditServer extends EditRecord
->title(trans('admin/server.notifications.server_suspended'))
->send();
$this->refreshFormData(['status', 'docker']);
} catch (Exception) {
Notification::make()
->warning()
@@ -761,6 +887,7 @@ class EditServer extends EditRecord
->title(trans('admin/server.notifications.server_unsuspended'))
->send();
$this->refreshFormData(['status', 'docker']);
} catch (Exception) {
Notification::make()
->warning()
@@ -770,24 +897,22 @@ class EditServer extends EditRecord
}
}),
])->fullWidth(),
ToggleButtons::make('server_suspend')
->hiddenLabel()
ToggleButtons::make('')
->hidden(fn (Server $server) => $server->isSuspended())
->hint(trans('admin/server.notifications.server_suspend_help')),
ToggleButtons::make('server_unsuspend')
->hiddenLabel()
ToggleButtons::make('')
->hidden(fn (Server $server) => !$server->isSuspended())
->hint(trans('admin/server.notifications.server_unsuspend_help')),
]),
Grid::make()
->columnSpan(3)
->schema([
Actions::make([
FormActions::make([
Action::make('transfer')
->label(trans('admin/server.transfer'))
->disabled(fn (Server $server) => Node::count() <= 1 || $server->isInConflictState())
->modalHeading(trans('admin/server.transfer'))
->schema($this->transferServer())
->modalheading(trans('admin/server.transfer'))
->form($this->transferServer())
->action(function (TransferServerService $transfer, Server $server, $data) {
try {
$transfer->handle($server, Arr::get($data, 'node_id'), Arr::get($data, 'allocation_id'), Arr::get($data, 'allocation_additional', []));
@@ -805,14 +930,13 @@ class EditServer extends EditRecord
}
}),
])->fullWidth(),
ToggleButtons::make('server_transfer')
->hiddenLabel()
ToggleButtons::make('')
->hint(new HtmlString(trans('admin/server.transfer_help'))),
]),
Grid::make()
->columnSpan(3)
->schema([
Actions::make([
FormActions::make([
Action::make('reinstall')
->label(trans('admin/server.reinstall'))
->color('danger')
@@ -828,6 +952,8 @@ class EditServer extends EditRecord
->title(trans('admin/server.notifications.reinstall_started'))
->success()
->send();
$this->refreshFormData(['status', 'docker']);
} catch (Exception) {
Notification::make()
->title(trans('admin/server.notifications.reinstall_failed'))
@@ -837,8 +963,7 @@ class EditServer extends EditRecord
}
}),
])->fullWidth(),
ToggleButtons::make('server_reinstall')
->hiddenLabel()
ToggleButtons::make('')
->hint(trans('admin/server.reinstall_help')),
]),
]),
@@ -847,9 +972,7 @@ class EditServer extends EditRecord
]);
}
/** @return Component[]
* @throws Exception
*/
/** @return Component[] */
protected function transferServer(): array
{
return [
@@ -883,7 +1006,7 @@ class EditServer extends EditRecord
];
}
/** @return array<Action|ActionGroup> */
/** @return array<Actions\Action|Actions\ActionGroup> */
protected function getDefaultHeaderActions(): array
{
/** @var Server $server */
@@ -892,7 +1015,7 @@ class EditServer extends EditRecord
$canForceDelete = cache()->get("servers.$server->uuid.canForceDelete", false);
return [
Action::make('Delete')
Actions\Action::make('Delete')
->color('danger')
->label(trans('filament-actions::delete.single.label'))
->modalHeading(trans('filament-actions::delete.single.modal.heading', ['label' => $this->getRecordTitle()]))
@@ -906,7 +1029,7 @@ class EditServer extends EditRecord
} catch (ConnectionException) {
cache()->put("servers.$server->uuid.canForceDelete", true, now()->addMinutes(5));
return Notification::make()
Notification::make()
->title(trans('admin/server.notifications.error_server_delete'))
->body(trans('admin/server.notifications.error_server_delete_body'))
->color('warning')
@@ -916,8 +1039,8 @@ class EditServer extends EditRecord
}
})
->hidden(fn () => $canForceDelete)
->authorize(fn (Server $server) => auth()->user()->can('delete server', $server)),
Action::make('ForceDelete')
->authorize(fn (Server $server) => auth()->user()->can('delete', $server)),
Actions\Action::make('ForceDelete')
->color('danger')
->label(trans('filament-actions::force-delete.single.label'))
->modalHeading(trans('filament-actions::force-delete.single.modal.heading', ['label' => $this->getRecordTitle()]))
@@ -929,12 +1052,12 @@ class EditServer extends EditRecord
return redirect(ListServers::getUrl(panel: 'admin'));
} catch (ConnectionException) {
return cache()->forget("servers.$server->uuid.canForceDelete");
cache()->forget("servers.$server->uuid.canForceDelete");
}
})
->visible(fn () => $canForceDelete)
->authorize(fn (Server $server) => auth()->user()->can('delete server', $server)),
Action::make('console')
->authorize(fn (Server $server) => auth()->user()->can('delete', $server)),
Actions\Action::make('console')
->label(trans('admin/server.console'))
->icon('tabler-terminal')
->url(fn (Server $server) => Console::getUrl(panel: 'server', tenant: $server)),
@@ -986,12 +1109,4 @@ class EditServer extends EditRecord
{
return null;
}
public function getRelationManagers(): array
{
return [
AllocationsRelationManager::class,
DatabasesRelationManager::class,
];
}
}

View File

@@ -1,17 +1,17 @@
<?php
namespace App\Filament\Admin\Resources\Servers\Pages;
namespace App\Filament\Admin\Resources\ServerResource\Pages;
use App\Filament\Admin\Resources\Servers\ServerResource;
use App\Filament\Server\Pages\Console;
use App\Filament\Admin\Resources\ServerResource;
use App\Models\Server;
use App\Traits\Filament\CanCustomizeHeaderActions;
use App\Traits\Filament\CanCustomizeHeaderWidgets;
use Filament\Actions\Action;
use Filament\Actions\ActionGroup;
use Filament\Actions\CreateAction;
use Filament\Actions\EditAction;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;
use Filament\Tables\Actions\Action;
use Filament\Tables\Actions\CreateAction;
use Filament\Tables\Actions\EditAction;
use Filament\Tables\Columns\SelectColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Grouping\Group;
@@ -47,23 +47,27 @@ class ListServers extends ListRecords
->searchable(),
TextColumn::make('name')
->label(trans('admin/server.name'))
->icon('tabler-brand-docker')
->searchable()
->sortable(),
TextColumn::make('node.name')
->label(trans('admin/server.node'))
->url(fn (Server $server) => route('filament.admin.resources.nodes.edit', ['record' => $server->node]))
->icon('tabler-server-2')
->url(fn (Server $server): string => route('filament.admin.resources.nodes.edit', ['record' => $server->node]))
->hidden(fn (Table $table) => $table->getGrouping()?->getId() === 'node.name')
->sortable()
->searchable(),
TextColumn::make('egg.name')
->icon('tabler-egg')
->label(trans('admin/server.egg'))
->url(fn (Server $server) => route('filament.admin.resources.eggs.edit', ['record' => $server->egg]))
->url(fn (Server $server): string => route('filament.admin.resources.eggs.edit', ['record' => $server->egg]))
->hidden(fn (Table $table) => $table->getGrouping()?->getId() === 'egg.name')
->sortable()
->searchable(),
TextColumn::make('user.username')
->icon('tabler-user')
->label(trans('admin/user.username'))
->url(fn (Server $server) => route('filament.admin.resources.users.edit', ['record' => $server->user]))
->url(fn (Server $server): string => route('filament.admin.resources.users.edit', ['record' => $server->user]))
->hidden(fn (Table $table) => $table->getGrouping()?->getId() === 'user.username')
->sortable()
->searchable(),
@@ -73,22 +77,25 @@ class ListServers extends ListRecords
->disabled(fn (Server $server) => $server->allocations->count() <= 1)
->options(fn (Server $server) => $server->allocations->mapWithKeys(fn ($allocation) => [$allocation->id => $allocation->address]))
->selectablePlaceholder(fn (Server $server) => $server->allocations->count() <= 1)
->placeholder(trans('admin/server.none'))
->placeholder('None')
->sortable(),
TextColumn::make('allocation_id_readonly')
->label(trans('admin/server.primary_allocation'))
->hidden(fn () => auth()->user()->can('update server')) // TODO: update to policy check (fn (Server $server) --> $server is empty)
->state(fn (Server $server) => $server->allocation->address ?? trans('admin/server.none')),
->disabled(fn (Server $server) => $server->allocations->count() <= 1)
->state(fn (Server $server) => $server->allocation->address ?? 'None'),
TextColumn::make('image')->hidden(),
TextColumn::make('backups_count')
->counts('backups')
->label(trans('admin/server.backups'))
->icon('tabler-file-download')
->numeric()
->sortable(),
])
->recordActions([
->actions([
Action::make('View')
->label(trans('admin/server.view'))
->icon('tabler-terminal')
->url(fn (Server $server) => Console::getUrl(panel: 'server', tenant: $server))
->authorize(fn (Server $server) => auth()->user()->canAccessTenant($server)),
EditAction::make(),
@@ -102,11 +109,11 @@ class ListServers extends ListRecords
]);
}
/** @return array<Action|ActionGroup> */
/** @return array<Actions\Action|Actions\ActionGroup> */
protected function getDefaultHeaderActions(): array
{
return [
CreateAction::make()
Actions\CreateAction::make()
->hidden(fn () => Server::count() <= 0),
];
}

View File

@@ -1,22 +1,21 @@
<?php
namespace App\Filament\Admin\Resources\Servers\RelationManagers;
namespace App\Filament\Admin\Resources\ServerResource\RelationManagers;
use App\Filament\Admin\Resources\Servers\Pages\CreateServer;
use App\Filament\Admin\Resources\ServerResource\Pages\CreateServer;
use App\Models\Allocation;
use App\Models\Server;
use App\Services\Allocations\AssignmentService;
use Filament\Actions\Action;
use Filament\Actions\AssociateAction;
use Filament\Actions\CreateAction;
use Filament\Actions\DissociateAction;
use Filament\Actions\DissociateBulkAction;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TagsInput;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Get;
use Filament\Forms\Set;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Schemas\Components\Utilities\Get;
use Filament\Schemas\Components\Utilities\Set;
use Filament\Tables\Actions\AssociateAction;
use Filament\Tables\Actions\CreateAction;
use Filament\Tables\Actions\DissociateAction;
use Filament\Tables\Actions\DissociateBulkAction;
use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Columns\TextInputColumn;
@@ -61,11 +60,7 @@ class AllocationsRelationManager extends RelationManager
->default(fn (Allocation $allocation) => $allocation->id === $this->getOwnerRecord()->allocation_id)
->label(trans('admin/server.primary')),
])
->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),
->actions([
DissociateAction::make()
->after(function (Allocation $allocation) {
$allocation->update(['notes' => null]);
@@ -75,22 +70,13 @@ class AllocationsRelationManager extends RelationManager
->headerActions([
CreateAction::make()->label(trans('admin/server.create_allocation'))
->createAnother(false)
->schema(fn () => [
->form(fn () => [
Select::make('allocation_ip')
->options(fn () => 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'))
->inlineLabel()
->ip()
->live()
->hintAction(
Action::make('refresh')
->iconButton()
->icon('tabler-refresh')
->tooltip(trans('admin/node.refresh'))
->action(function () {
cache()->forget("nodes.{$this->getOwnerRecord()->node->id}.ips");
})
)
->afterStateUpdated(fn (Set $set) => $set('allocation_ports', []))
->required(),
TextInput::make('allocation_alias')

View File

@@ -1,150 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\Servers\RelationManagers;
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\Actions\DeleteAction;
use Filament\Actions\ViewAction;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Notifications\Notification;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Schemas\Schema;
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'),
TextColumn::make('username')
->label(trans('admin/databasehost.table.username')),
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')
->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()
->options(fn () => DatabaseHost::query()
->whereHas('nodes', fn ($query) => $query->where('nodes.id', $this->getOwnerRecord()->node_id))
->pluck('name', 'id')
)
->selectablePlaceholder(false)
->default(fn () => (DatabaseHost::query()->first())?->id),
TextInput::make('database')
->label(trans('admin/server.name'))
->alphaDash()
->prefix(fn () => 's' . $this->getOwnerRecord()->id . '_')
->hintIcon('tabler-question-mark', trans('admin/databasehost.table.name_helper')),
TextInput::make('remote')
->columnSpan(1)
->regex('/^[\w\-\/.%:]+$/')
->label(trans('admin/databasehost.table.remote'))
->default('%')
->hintIcon('tabler-question-mark', trans('admin/databasehost.table.remote_helper')),
]),
]);
}
}

View File

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

View File

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

View File

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

View File

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

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