Merge branch 'main' into issue/68

# Conflicts:
#	app/Filament/Resources/NodeResource/RelationManagers/AllocationsRelationManager.php
#	app/Filament/Resources/ServerResource/Pages/CreateServer.php
#	app/Filament/Resources/ServerResource/Pages/EditServer.php
#	app/Filament/Resources/ServerResource/RelationManagers/AllocationsRelationManager.php
#	app/Filament/Resources/UserResource/Pages/EditProfile.php
#	app/Models/Node.php
#	app/Models/Objects/DeploymentObject.php
#	app/Services/Allocations/AssignmentService.php
#	app/Services/Servers/ServerCreationService.php
#	app/Services/Servers/TransferServerService.php
#	pint.json
This commit is contained in:
Lance Pioch 2024-10-20 15:14:08 -04:00
commit 4f5e9a6c30
131 changed files with 431 additions and 262 deletions

View File

@ -11,11 +11,8 @@ class CheckEggUpdatesCommand extends Command
{ {
protected $signature = 'p:egg:check-updates'; protected $signature = 'p:egg:check-updates';
public function handle(): void public function handle(EggExporterService $exporterService): void
{ {
/** @var EggExporterService $exporterService */
$exporterService = app(EggExporterService::class);
$eggs = Egg::all(); $eggs = Egg::all();
foreach ($eggs as $egg) { foreach ($eggs as $egg) {
try { try {

View File

@ -70,7 +70,7 @@ class EmailSettingsCommand extends Command
/** /**
* Handle variables for SMTP driver. * Handle variables for SMTP driver.
*/ */
private function setupSmtpDriverVariables() private function setupSmtpDriverVariables(): void
{ {
$this->variables['MAIL_HOST'] = $this->option('host') ?? $this->ask( $this->variables['MAIL_HOST'] = $this->option('host') ?? $this->ask(
trans('command/messages.environment.mail.ask_smtp_host'), trans('command/messages.environment.mail.ask_smtp_host'),
@ -101,7 +101,7 @@ class EmailSettingsCommand extends Command
/** /**
* Handle variables for mailgun driver. * Handle variables for mailgun driver.
*/ */
private function setupMailgunDriverVariables() private function setupMailgunDriverVariables(): void
{ {
$this->variables['MAILGUN_DOMAIN'] = $this->option('host') ?? $this->ask( $this->variables['MAILGUN_DOMAIN'] = $this->option('host') ?? $this->ask(
trans('command/messages.environment.mail.ask_mailgun_domain'), trans('command/messages.environment.mail.ask_mailgun_domain'),
@ -122,7 +122,7 @@ class EmailSettingsCommand extends Command
/** /**
* Handle variables for mandrill driver. * Handle variables for mandrill driver.
*/ */
private function setupMandrillDriverVariables() private function setupMandrillDriverVariables(): void
{ {
$this->variables['MANDRILL_SECRET'] = $this->option('password') ?? $this->ask( $this->variables['MANDRILL_SECRET'] = $this->option('password') ?? $this->ask(
trans('command/messages.environment.mail.ask_mandrill_secret'), trans('command/messages.environment.mail.ask_mandrill_secret'),
@ -133,7 +133,7 @@ class EmailSettingsCommand extends Command
/** /**
* Handle variables for postmark driver. * Handle variables for postmark driver.
*/ */
private function setupPostmarkDriverVariables() private function setupPostmarkDriverVariables(): void
{ {
$this->variables['MAIL_DRIVER'] = 'smtp'; $this->variables['MAIL_DRIVER'] = 'smtp';
$this->variables['MAIL_HOST'] = 'smtp.postmarkapp.com'; $this->variables['MAIL_HOST'] = 'smtp.postmarkapp.com';

View File

@ -51,7 +51,7 @@ class ProcessRunnableCommand extends Command
* never throw an exception out, otherwise you'll end up killing the entire run group causing * never throw an exception out, otherwise you'll end up killing the entire run group causing
* any other schedules to not process correctly. * any other schedules to not process correctly.
*/ */
protected function processSchedule(Schedule $schedule) protected function processSchedule(Schedule $schedule): void
{ {
if ($schedule->tasks->isEmpty()) { if ($schedule->tasks->isEmpty()) {
return; return;

View File

@ -178,7 +178,7 @@ class UpgradeCommand extends Command
$this->info(__('commands.upgrade.success')); $this->info(__('commands.upgrade.success'));
} }
protected function withProgress(ProgressBar $bar, \Closure $callback) protected function withProgress(ProgressBar $bar, \Closure $callback): void
{ {
$bar->clear(); $bar->clear();
$callback(); $callback();

View File

@ -4,6 +4,8 @@ namespace App\Exceptions;
use Exception; use Exception;
use Filament\Notifications\Notification; use Filament\Notifications\Notification;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Illuminate\Http\Response; use Illuminate\Http\Response;
@ -14,8 +16,11 @@ use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
class DisplayException extends PanelException implements HttpExceptionInterface class DisplayException extends PanelException implements HttpExceptionInterface
{ {
public const LEVEL_DEBUG = 'debug'; public const LEVEL_DEBUG = 'debug';
public const LEVEL_INFO = 'info'; public const LEVEL_INFO = 'info';
public const LEVEL_WARNING = 'warning'; public const LEVEL_WARNING = 'warning';
public const LEVEL_ERROR = 'error'; public const LEVEL_ERROR = 'error';
/** /**
@ -46,7 +51,7 @@ class DisplayException extends PanelException implements HttpExceptionInterface
* and then redirecting them back to the page that they came from. If the * and then redirecting them back to the page that they came from. If the
* request originated from an API hit, return the error in JSONAPI spec format. * request originated from an API hit, return the error in JSONAPI spec format.
*/ */
public function render(Request $request) public function render(Request $request): bool|RedirectResponse|JsonResponse
{ {
if ($request->is('livewire/update')) { if ($request->is('livewire/update')) {
Notification::make() Notification::make()
@ -55,13 +60,14 @@ class DisplayException extends PanelException implements HttpExceptionInterface
->danger() ->danger()
->send(); ->send();
return; return false;
} }
if ($request->expectsJson()) { if ($request->expectsJson()) {
return response()->json(Handler::toArray($this), $this->getStatusCode(), $this->getHeaders()); return response()->json(Handler::toArray($this), $this->getStatusCode(), $this->getHeaders());
} }
// @phpstan-ignore-next-line
app(AlertsMessageBag::class)->danger($this->getMessage())->flash(); app(AlertsMessageBag::class)->danger($this->getMessage())->flash();
return redirect()->back()->withInput(); return redirect()->back()->withInput();
@ -73,10 +79,10 @@ class DisplayException extends PanelException implements HttpExceptionInterface
* *
* @throws \Throwable * @throws \Throwable
*/ */
public function report() public function report(): void
{ {
if (!$this->getPrevious() instanceof \Exception || !Handler::isReportable($this->getPrevious())) { if (!$this->getPrevious() instanceof \Exception || !Handler::isReportable($this->getPrevious())) {
return null; return;
} }
try { try {
@ -85,6 +91,6 @@ class DisplayException extends PanelException implements HttpExceptionInterface
throw $this->getPrevious(); throw $this->getPrevious();
} }
return $logger->{$this->getErrorLevel()}($this->getPrevious()); $logger->{$this->getErrorLevel()}($this->getPrevious());
} }
} }

View File

@ -114,7 +114,7 @@ class Handler extends ExceptionHandler
/** /**
* Render an exception into an HTTP response. * Render an exception into an HTTP response.
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* *
* @throws \Throwable * @throws \Throwable
*/ */
@ -140,7 +140,7 @@ class Handler extends ExceptionHandler
* Transform a validation exception into a consistent format to be returned for * Transform a validation exception into a consistent format to be returned for
* calls to the API. * calls to the API.
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
*/ */
public function invalidJson($request, ValidationException $exception): JsonResponse public function invalidJson($request, ValidationException $exception): JsonResponse
{ {
@ -236,7 +236,7 @@ class Handler extends ExceptionHandler
/** /**
* Convert an authentication exception into an unauthenticated response. * Convert an authentication exception into an unauthenticated response.
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
*/ */
protected function unauthenticated($request, AuthenticationException $exception): JsonResponse|RedirectResponse protected function unauthenticated($request, AuthenticationException $exception): JsonResponse|RedirectResponse
{ {
@ -273,6 +273,7 @@ class Handler extends ExceptionHandler
*/ */
public static function toArray(\Throwable $e): array public static function toArray(\Throwable $e): array
{ {
// @phpstan-ignore-next-line
return (new self(app()))->convertExceptionToArray($e); return (new self(app()))->convertExceptionToArray($e);
} }
} }

View File

@ -10,7 +10,7 @@ class HttpForbiddenException extends HttpException
/** /**
* HttpForbiddenException constructor. * HttpForbiddenException constructor.
*/ */
public function __construct(string $message = null, \Throwable $previous = null) public function __construct(?string $message = null, ?\Throwable $previous = null)
{ {
parent::__construct(Response::HTTP_FORBIDDEN, $message, $previous); parent::__construct(Response::HTTP_FORBIDDEN, $message, $previous);
} }

View File

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

View File

@ -11,7 +11,7 @@ class TwoFactorAuthRequiredException extends HttpException implements HttpExcept
/** /**
* TwoFactorAuthRequiredException constructor. * TwoFactorAuthRequiredException constructor.
*/ */
public function __construct(\Throwable $previous = null) 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); parent::__construct(Response::HTTP_BAD_REQUEST, 'Two-factor authentication is required on this account in order to access this endpoint.', $previous);
} }

View File

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

View File

@ -7,6 +7,7 @@ use App\Exceptions\DisplayException;
class TwoFactorAuthenticationTokenInvalid extends DisplayException class TwoFactorAuthenticationTokenInvalid extends DisplayException
{ {
public string $title = 'Invalid 2FA Code'; public string $title = 'Invalid 2FA Code';
public string $icon = 'tabler-2fa'; public string $icon = 'tabler-2fa';
public function __construct() public function __construct()

View File

@ -34,7 +34,7 @@ class BackupManager
/** /**
* Returns a backup adapter instance. * Returns a backup adapter instance.
*/ */
public function adapter(string $name = null): FilesystemAdapter public function adapter(?string $name = null): FilesystemAdapter
{ {
return $this->get($name ?: $this->getDefaultAdapter()); return $this->get($name ?: $this->getDefaultAdapter());
} }
@ -145,7 +145,7 @@ class BackupManager
/** /**
* Unset the given adapter instances. * Unset the given adapter instances.
* *
* @param string|string[] $adapter * @param string|string[] $adapter
*/ */
public function forget(array|string $adapter): self public function forget(array|string $adapter): self
{ {

View File

@ -7,7 +7,9 @@ use App\Models\DatabaseHost;
class DynamicDatabaseConnection class DynamicDatabaseConnection
{ {
public const DB_CHARSET = 'utf8'; public const DB_CHARSET = 'utf8';
public const DB_COLLATION = 'utf8_unicode_ci'; public const DB_COLLATION = 'utf8_unicode_ci';
public const DB_DRIVER = 'mysql'; public const DB_DRIVER = 'mysql';
/** /**

View File

@ -4,17 +4,17 @@ namespace App\Extensions\Themes;
class Theme class Theme
{ {
public function js($path): string public function js(string $path): string
{ {
return sprintf('<script src="%s"></script>' . PHP_EOL, $this->getUrl($path)); return sprintf('<script src="%s"></script>' . PHP_EOL, $this->getUrl($path));
} }
public function css($path): string public function css(string $path): string
{ {
return sprintf('<link media="all" type="text/css" rel="stylesheet" href="%s"/>' . PHP_EOL, $this->getUrl($path)); return sprintf('<link media="all" type="text/css" rel="stylesheet" href="%s"/>' . PHP_EOL, $this->getUrl($path));
} }
protected function getUrl($path): string protected function getUrl(string $path): string
{ {
return '/themes/panel/' . ltrim($path, '/'); return '/themes/panel/' . ltrim($path, '/');
} }

View File

@ -28,16 +28,20 @@ class Dashboard extends Page
public string $activeTab = 'nodes'; public string $activeTab = 'nodes';
private SoftwareVersionService $softwareVersionService;
public function mount(SoftwareVersionService $softwareVersionService): void
{
$this->softwareVersionService = $softwareVersionService;
}
public function getViewData(): array public function getViewData(): array
{ {
/** @var SoftwareVersionService $softwareVersionService */
$softwareVersionService = app(SoftwareVersionService::class);
return [ return [
'inDevelopment' => config('app.version') === 'canary', 'inDevelopment' => config('app.version') === 'canary',
'version' => $softwareVersionService->versionData()['version'], 'version' => $this->softwareVersionService->versionData()['version'],
'latestVersion' => $softwareVersionService->getPanel(), 'latestVersion' => $this->softwareVersionService->getPanel(),
'isLatest' => $softwareVersionService->isLatestPanel(), 'isLatest' => $this->softwareVersionService->isLatestPanel(),
'eggsCount' => Egg::query()->count(), 'eggsCount' => Egg::query()->count(),
'nodesList' => ListNodes::getUrl(), 'nodesList' => ListNodes::getUrl(),
'nodesCount' => Node::query()->count(), 'nodesCount' => Node::query()->count(),
@ -67,7 +71,7 @@ class Dashboard extends Page
CreateAction::make() CreateAction::make()
->label(trans('dashboard/index.sections.intro-support.button_donate')) ->label(trans('dashboard/index.sections.intro-support.button_donate'))
->icon('tabler-cash') ->icon('tabler-cash')
->url($softwareVersionService->getDonations(), true) ->url($this->softwareVersionService->getDonations(), true)
->color('success'), ->color('success'),
], ],
'helpActions' => [ 'helpActions' => [

View File

@ -2,6 +2,7 @@
namespace App\Filament\Pages\Installer; namespace App\Filament\Pages\Installer;
use App\Filament\Pages\Dashboard;
use App\Filament\Pages\Installer\Steps\AdminUserStep; use App\Filament\Pages\Installer\Steps\AdminUserStep;
use App\Filament\Pages\Installer\Steps\CompletedStep; use App\Filament\Pages\Installer\Steps\CompletedStep;
use App\Filament\Pages\Installer\Steps\DatabaseStep; use App\Filament\Pages\Installer\Steps\DatabaseStep;
@ -13,7 +14,6 @@ use App\Services\Users\UserCreationService;
use App\Traits\CheckMigrationsTrait; use App\Traits\CheckMigrationsTrait;
use App\Traits\EnvironmentWriterTrait; use App\Traits\EnvironmentWriterTrait;
use Exception; use Exception;
use Filament\Facades\Filament;
use Filament\Forms\Components\Actions\Action; use Filament\Forms\Components\Actions\Action;
use Filament\Forms\Components\Wizard; use Filament\Forms\Components\Wizard;
use Filament\Forms\Concerns\InteractsWithForms; use Filament\Forms\Concerns\InteractsWithForms;
@ -24,6 +24,7 @@ use Filament\Notifications\Notification;
use Filament\Pages\SimplePage; use Filament\Pages\SimplePage;
use Filament\Support\Enums\MaxWidth; use Filament\Support\Enums\MaxWidth;
use Filament\Support\Exceptions\Halt; use Filament\Support\Exceptions\Halt;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Blade; use Illuminate\Support\Facades\Blade;
use Illuminate\Support\HtmlString; use Illuminate\Support\HtmlString;
@ -37,7 +38,7 @@ class PanelInstaller extends SimplePage implements HasForms
use EnvironmentWriterTrait; use EnvironmentWriterTrait;
use InteractsWithForms; use InteractsWithForms;
public $data = []; public array $data = [];
protected static string $view = 'filament.pages.installer'; protected static string $view = 'filament.pages.installer';
@ -54,7 +55,7 @@ class PanelInstaller extends SimplePage implements HasForms
return env('APP_INSTALLED', true); return env('APP_INSTALLED', true);
} }
public function mount() public function mount(): void
{ {
abort_if(self::isInstalled(), 404); abort_if(self::isInstalled(), 404);
@ -93,7 +94,7 @@ class PanelInstaller extends SimplePage implements HasForms
return 'data'; return 'data';
} }
public function submit() public function submit(): RedirectResponse
{ {
// Disable installer // Disable installer
$this->writeToEnvironment(['APP_INSTALLED' => 'true']); $this->writeToEnvironment(['APP_INSTALLED' => 'true']);
@ -103,7 +104,7 @@ class PanelInstaller extends SimplePage implements HasForms
auth()->guard()->login($this->user, true); auth()->guard()->login($this->user, true);
// Redirect to admin panel // Redirect to admin panel
return redirect(Filament::getPanel('admin')->getUrl()); return redirect(Dashboard::getUrl());
} }
public function writeToEnv(string $key): void public function writeToEnv(string $key): void
@ -159,12 +160,12 @@ class PanelInstaller extends SimplePage implements HasForms
} }
} }
public function createAdminUser(): void public function createAdminUser(UserCreationService $userCreationService): void
{ {
try { try {
$userData = array_get($this->data, 'user'); $userData = array_get($this->data, 'user');
$userData['root_admin'] = true; $userData['root_admin'] = true;
$this->user = app(UserCreationService::class)->handle($userData); $this->user = $userCreationService->handle($userData);
} catch (Exception $exception) { } catch (Exception $exception) {
report($exception); report($exception);

View File

@ -3,6 +3,7 @@
namespace App\Filament\Pages\Installer\Steps; namespace App\Filament\Pages\Installer\Steps;
use App\Filament\Pages\Installer\PanelInstaller; use App\Filament\Pages\Installer\PanelInstaller;
use App\Services\Users\UserCreationService;
use Filament\Forms\Components\TextInput; use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Wizard\Step; use Filament\Forms\Components\Wizard\Step;
@ -28,6 +29,6 @@ class AdminUserStep
->password() ->password()
->revealable(), ->revealable(),
]) ])
->afterValidation(fn () => $installer->createAdminUser()); ->afterValidation(fn (UserCreationService $service) => $installer->createAdminUser($service));
} }
} }

View File

@ -72,7 +72,7 @@ class DatabaseStep
}); });
} }
private static function testConnection(string $driver, $host, $port, $database, $username, $password): bool private static function testConnection(string $driver, string $host, string $port, string $database, string $username, string $password): bool
{ {
if ($driver === 'sqlite') { if ($driver === 'sqlite') {
return true; return true;

View File

@ -56,7 +56,7 @@ class RedisStep
}); });
} }
private static function testConnection($host, $port, $username, $password): bool private static function testConnection(string $host, string $port, string $username, string $password): bool
{ {
try { try {
config()->set('database.redis._panel_install_test', [ config()->set('database.redis._panel_install_test', [

View File

@ -38,6 +38,7 @@ class Settings extends Page implements HasForms
use InteractsWithHeaderActions; use InteractsWithHeaderActions;
protected static ?string $navigationIcon = 'tabler-settings'; protected static ?string $navigationIcon = 'tabler-settings';
protected static ?string $navigationGroup = 'Advanced'; protected static ?string $navigationGroup = 'Advanced';
protected static string $view = 'filament.pages.settings'; protected static string $view = 'filament.pages.settings';

View File

@ -5,12 +5,16 @@ namespace App\Filament\Resources;
use App\Filament\Resources\ApiKeyResource\Pages; use App\Filament\Resources\ApiKeyResource\Pages;
use App\Models\ApiKey; use App\Models\ApiKey;
use Filament\Resources\Resource; use Filament\Resources\Resource;
use Illuminate\Database\Eloquent\Model;
class ApiKeyResource extends Resource class ApiKeyResource extends Resource
{ {
protected static ?string $model = ApiKey::class; protected static ?string $model = ApiKey::class;
protected static ?string $label = 'API Key'; protected static ?string $label = 'API Key';
protected static ?string $navigationIcon = 'tabler-key'; protected static ?string $navigationIcon = 'tabler-key';
protected static ?string $navigationGroup = 'Advanced'; protected static ?string $navigationGroup = 'Advanced';
public static function getNavigationBadge(): ?string public static function getNavigationBadge(): ?string
@ -18,7 +22,7 @@ class ApiKeyResource extends Resource
return static::getModel()::where('key_type', '2')->count() ?: null; return static::getModel()::where('key_type', '2')->count() ?: null;
} }
public static function canEdit($record): bool public static function canEdit(Model $record): bool
{ {
return false; return false;
} }

View File

@ -13,6 +13,7 @@ class DatabaseHostResource extends Resource
protected static ?string $label = 'Database Host'; protected static ?string $label = 'Database Host';
protected static ?string $navigationIcon = 'tabler-database'; protected static ?string $navigationIcon = 'tabler-database';
protected static ?string $navigationGroup = 'Advanced'; protected static ?string $navigationGroup = 'Advanced';
public static function getNavigationBadge(): ?string public static function getNavigationBadge(): ?string

View File

@ -5,6 +5,8 @@ namespace App\Filament\Resources\DatabaseHostResource\Pages;
use App\Filament\Resources\DatabaseHostResource; use App\Filament\Resources\DatabaseHostResource;
use App\Models\Objects\Endpoint; use App\Models\Objects\Endpoint;
use App\Services\Databases\Hosts\HostCreationService; use App\Services\Databases\Hosts\HostCreationService;
use Closure;
use Exception;
use Filament\Forms; use Filament\Forms;
use Filament\Forms\Components\Section; use Filament\Forms\Components\Section;
use Filament\Forms\Components\Select; use Filament\Forms\Components\Select;
@ -17,6 +19,8 @@ use PDOException;
class CreateDatabaseHost extends CreateRecord class CreateDatabaseHost extends CreateRecord
{ {
private HostCreationService $service;
protected static string $resource = DatabaseHostResource::class; protected static string $resource = DatabaseHostResource::class;
protected ?string $heading = 'Database Hosts'; protected ?string $heading = 'Database Hosts';
@ -25,6 +29,11 @@ class CreateDatabaseHost extends CreateRecord
protected ?string $subheading = '(database servers that can have individual databases)'; protected ?string $subheading = '(database servers that can have individual databases)';
public function boot(HostCreationService $service): void
{
$this->service = $service;
}
public function form(Form $form): Form public function form(Form $form): Form
{ {
return $form return $form
@ -95,10 +104,10 @@ class CreateDatabaseHost extends CreateRecord
protected function handleRecordCreation(array $data): Model protected function handleRecordCreation(array $data): Model
{ {
return resolve(HostCreationService::class)->handle($data); return $this->service->handle($data);
} }
public function exception($e, $stopPropagation): void public function exception(Exception $e, Closure $stopPropagation): void
{ {
if ($e instanceof PDOException) { if ($e instanceof PDOException) {
Notification::make() Notification::make()

View File

@ -7,6 +7,8 @@ use App\Filament\Resources\DatabaseHostResource\RelationManagers\DatabasesRelati
use App\Models\DatabaseHost; use App\Models\DatabaseHost;
use App\Models\Objects\Endpoint; use App\Models\Objects\Endpoint;
use App\Services\Databases\Hosts\HostUpdateService; use App\Services\Databases\Hosts\HostUpdateService;
use Closure;
use Exception;
use Filament\Actions; use Filament\Actions;
use Filament\Forms; use Filament\Forms;
use Filament\Forms\Components\Section; use Filament\Forms\Components\Section;
@ -22,6 +24,13 @@ class EditDatabaseHost extends EditRecord
{ {
protected static string $resource = DatabaseHostResource::class; protected static string $resource = DatabaseHostResource::class;
private HostUpdateService $hostUpdateService;
public function boot(HostUpdateService $hostUpdateService): void
{
$this->hostUpdateService = $hostUpdateService;
}
public function form(Form $form): Form public function form(Form $form): Form
{ {
return $form return $form
@ -98,12 +107,16 @@ class EditDatabaseHost extends EditRecord
]; ];
} }
protected function handleRecordUpdate($record, array $data): Model protected function handleRecordUpdate(Model $record, array $data): Model
{ {
return resolve(HostUpdateService::class)->handle($record->id, $data); if (!$record instanceof DatabaseHost) {
return $record;
}
return $this->hostUpdateService->handle($record, $data);
} }
public function exception($e, $stopPropagation): void public function exception(Exception $e, Closure $stopPropagation): void
{ {
if ($e instanceof PDOException) { if ($e instanceof PDOException) {
Notification::make() Notification::make()

View File

@ -32,13 +32,16 @@ class ListDatabaseHosts extends ListRecords
->sortable(), ->sortable(),
TextColumn::make('username') TextColumn::make('username')
->searchable(), ->searchable(),
TextColumn::make('max_databases') TextColumn::make('databases_count')
->numeric() ->counts('databases')
->sortable(), ->icon('tabler-database')
->label('Databases'),
TextColumn::make('node.name') TextColumn::make('node.name')
->numeric() ->icon('tabler-server-2')
->placeholder('No Nodes')
->sortable(), ->sortable(),
]) ])
->checkIfRecordIsSelectableUsing(fn (DatabaseHost $databaseHost) => !$databaseHost->databases_count)
->actions([ ->actions([
EditAction::make(), EditAction::make(),
]) ])

View File

@ -8,6 +8,7 @@ use Filament\Forms\Components\Actions\Action;
use Filament\Forms\Components\TextInput; use Filament\Forms\Components\TextInput;
use Filament\Forms\Form; use Filament\Forms\Form;
use Filament\Forms\Get; use Filament\Forms\Get;
use Filament\Forms\Set;
use Filament\Resources\RelationManagers\RelationManager; use Filament\Resources\RelationManagers\RelationManager;
use Filament\Tables\Actions\DeleteAction; use Filament\Tables\Actions\DeleteAction;
use Filament\Tables\Actions\ViewAction; use Filament\Tables\Actions\ViewAction;
@ -40,6 +41,7 @@ class DatabasesRelationManager extends RelationManager
->formatStateUsing(fn (Get $get, Database $database) => 'jdbc:mysql://' . $get('username') . ':' . urlencode($database->password) . '@' . $database->host->host . ':' . $database->host->port . '/' . $get('database')), ->formatStateUsing(fn (Get $get, Database $database) => 'jdbc:mysql://' . $get('username') . ':' . urlencode($database->password) . '@' . $database->host->host . ':' . $database->host->port . '/' . $get('database')),
]); ]);
} }
public function table(Table $table): Table public function table(Table $table): Table
{ {
return $table return $table
@ -60,7 +62,7 @@ class DatabasesRelationManager extends RelationManager
]); ]);
} }
protected function rotatePassword(DatabasePasswordService $service, Database $database, $set, $get): void protected function rotatePassword(DatabasePasswordService $service, Database $database, Set $set, Get $get): void
{ {
$newPassword = $service->handle($database); $newPassword = $service->handle($database);
$jdbcString = 'jdbc:mysql://' . $get('username') . ':' . urlencode($newPassword) . '@' . $database->host->host . ':' . $database->host->port . '/' . $get('database'); $jdbcString = 'jdbc:mysql://' . $get('username') . ':' . urlencode($newPassword) . '@' . $database->host->host . ':' . $database->host->port . '/' . $get('database');

View File

@ -13,6 +13,7 @@ class DatabaseResource extends Resource
protected static ?string $navigationIcon = 'tabler-database'; protected static ?string $navigationIcon = 'tabler-database';
protected static bool $shouldRegisterNavigation = false; protected static bool $shouldRegisterNavigation = false;
protected static ?string $navigationGroup = 'Advanced'; protected static ?string $navigationGroup = 'Advanced';
public static function getNavigationBadge(): ?string public static function getNavigationBadge(): ?string

View File

@ -21,9 +21,12 @@ class CreateDatabase extends CreateRecord
->searchable() ->searchable()
->preload() ->preload()
->required(), ->required(),
TextInput::make('database_host_id') Select::make('database_host_id')
->required() ->relationship('host', 'name')
->numeric(), ->searchable()
->selectablePlaceholder(false)
->preload()
->required(),
TextInput::make('database') TextInput::make('database')
->required() ->required()
->maxLength(255), ->maxLength(255),

View File

@ -27,6 +27,7 @@ class CreateEgg extends CreateRecord
protected static string $resource = EggResource::class; protected static string $resource = EggResource::class;
protected static bool $canCreateAnother = false; protected static bool $canCreateAnother = false;
public function form(Form $form): Form public function form(Form $form): Form
{ {
return $form return $form

View File

@ -280,10 +280,7 @@ class EditEgg extends EditRecord
->contained(false), ->contained(false),
]) ])
->action(function (array $data, Egg $egg): void { ->action(function (array $data, Egg $egg, EggImporterService $eggImportService): void {
/** @var EggImporterService $eggImportService */
$eggImportService = resolve(EggImporterService::class);
if (!empty($data['egg'])) { if (!empty($data['egg'])) {
try { try {
$eggImportService->fromFile($data['egg'], $egg); $eggImportService->fromFile($data['egg'], $egg);

View File

@ -66,9 +66,10 @@ class ListEggs extends ListRecords
->modalDescription('If you made any changes to the egg they will be overwritten!') ->modalDescription('If you made any changes to the egg they will be overwritten!')
->modalIconColor('danger') ->modalIconColor('danger')
->modalSubmitAction(fn (Actions\StaticAction $action) => $action->color('danger')) ->modalSubmitAction(fn (Actions\StaticAction $action) => $action->color('danger'))
->action(function (Egg $egg) { ->action(function (Egg $egg, EggImporterService $eggImporterService) {
try { try {
app(EggImporterService::class)->fromUrl($egg->update_url, $egg); $eggImporterService->fromUrl($egg->update_url, $egg);
cache()->forget("eggs.{$egg->uuid}.update"); cache()->forget("eggs.{$egg->uuid}.update");
} catch (Exception $exception) { } catch (Exception $exception) {
Notification::make() Notification::make()
@ -97,6 +98,7 @@ class ListEggs extends ListRecords
]), ]),
]); ]);
} }
protected function getHeaderActions(): array protected function getHeaderActions(): array
{ {
return [ return [
@ -129,10 +131,7 @@ class ListEggs extends ListRecords
->contained(false), ->contained(false),
]) ])
->action(function (array $data): void { ->action(function (array $data, EggImporterService $eggImportService): void {
/** @var EggImporterService $eggImportService */
$eggImportService = resolve(EggImporterService::class);
if (!empty($data['egg'])) { if (!empty($data['egg'])) {
/** @var TemporaryUploadedFile[] $eggFile */ /** @var TemporaryUploadedFile[] $eggFile */
$eggFile = $data['egg']; $eggFile = $data['egg'];

View File

@ -11,6 +11,7 @@ class MountResource extends Resource
protected static ?string $model = Mount::class; protected static ?string $model = Mount::class;
protected static ?string $navigationIcon = 'tabler-layers-linked'; protected static ?string $navigationIcon = 'tabler-layers-linked';
protected static ?string $navigationGroup = 'Advanced'; protected static ?string $navigationGroup = 'Advanced';
public static function getNavigationBadge(): ?string public static function getNavigationBadge(): ?string

View File

@ -96,6 +96,7 @@ class EditMount extends EditRecord
'lg' => 2, 'lg' => 2,
]); ]);
} }
protected function getHeaderActions(): array protected function getHeaderActions(): array
{ {
return [ return [

View File

@ -17,6 +17,7 @@ use Filament\Tables\Table;
class ListMounts extends ListRecords class ListMounts extends ListRecords
{ {
protected static string $resource = MountResource::class; protected static string $resource = MountResource::class;
public function table(Table $table): Table public function table(Table $table): Table
{ {
return $table return $table
@ -56,6 +57,7 @@ class ListMounts extends ListRecords
->button(), ->button(),
]); ]);
} }
protected function getHeaderActions(): array protected function getHeaderActions(): array
{ {
return [ return [

View File

@ -399,7 +399,7 @@ class CreateNode extends CreateRecord
protected function getRedirectUrlParameters(): array protected function getRedirectUrlParameters(): array
{ {
return [ return [
'tab' => '-configuration-tab', 'tab' => '-configuration-file-tab',
]; ];
} }

View File

@ -470,12 +470,12 @@ class EditNode extends EditRecord
$this->fillForm(); $this->fillForm();
} }
protected function getColumnSpan() protected function getColumnSpan(): ?int
{ {
return null; return null;
} }
protected function getColumnStart() protected function getColumnStart(): ?int
{ {
return null; return null;
} }

View File

@ -12,6 +12,7 @@ use Illuminate\Support\Number;
class NodeCpuChart extends ChartWidget class NodeCpuChart extends ChartWidget
{ {
protected static ?string $pollingInterval = '5s'; protected static ?string $pollingInterval = '5s';
protected static ?string $maxHeight = '300px'; protected static ?string $maxHeight = '300px';
public ?Model $record = null; public ?Model $record = null;

View File

@ -12,6 +12,7 @@ use Illuminate\Support\Number;
class NodeMemoryChart extends ChartWidget class NodeMemoryChart extends ChartWidget
{ {
protected static ?string $pollingInterval = '5s'; protected static ?string $pollingInterval = '5s';
protected static ?string $maxHeight = '300px'; protected static ?string $maxHeight = '300px';
public ?Model $record = null; public ?Model $record = null;

View File

@ -9,7 +9,9 @@ use Illuminate\Database\Eloquent\Model;
class NodeStorageChart extends ChartWidget class NodeStorageChart extends ChartWidget
{ {
protected static ?string $heading = 'Storage'; protected static ?string $heading = 'Storage';
protected static ?string $pollingInterval = '60s'; protected static ?string $pollingInterval = '60s';
protected static ?string $maxHeight = '300px'; protected static ?string $maxHeight = '300px';
public ?Model $record = null; public ?Model $record = null;

View File

@ -42,6 +42,7 @@ use LogicException;
class CreateServer extends CreateRecord class CreateServer extends CreateRecord
{ {
protected static string $resource = ServerResource::class; protected static string $resource = ServerResource::class;
protected static bool $canCreateAnother = false; protected static bool $canCreateAnother = false;
public ?Node $node = null; public ?Node $node = null;
@ -49,6 +50,13 @@ class CreateServer extends CreateRecord
public array $ports = []; public array $ports = [];
public array $eggDefaultPorts = []; public array $eggDefaultPorts = [];
private ServerCreationService $serverCreationService;
public function boot(ServerCreationService $serverCreationService): void
{
$this->serverCreationService = $serverCreationService;
}
public function form(Form $form): Form public function form(Form $form): Form
{ {
return $form return $form
@ -118,8 +126,9 @@ class CreateServer extends CreateRecord
->hintIconTooltip('Providing a user password is optional. New user email will prompt users to create a password the first time they login.') ->hintIconTooltip('Providing a user password is optional. New user email will prompt users to create a password the first time they login.')
->password(), ->password(),
]) ])
->createOptionUsing(function ($data) { ->createOptionUsing(function ($data, UserCreationService $service) {
resolve(UserCreationService::class)->handle($data); $service->handle($data);
$this->refreshForm(); $this->refreshForm();
}) })
->required(), ->required(),
@ -726,10 +735,7 @@ class CreateServer extends CreateRecord
$data['environment'][$env] = $data['ports'][$data['assignments'][$i]]; $data['environment'][$env] = $data['ports'][$data['assignments'][$i]];
} }
/** @var ServerCreationService $service */ return $this->serverCreationService->handle($data, validateVariables: false);
$service = resolve(ServerCreationService::class);
return $service->handle($data, validateVariables: false);
} }
private function shouldHideComponent(Get $get, Component $component): bool private function shouldHideComponent(Get $get, Component $component): bool

View File

@ -830,6 +830,7 @@ class EditServer extends EditRecord
]); ]);
} }
protected function getHeaderActions(): array protected function getHeaderActions(): array
{ {
return [ return [
@ -838,8 +839,8 @@ class EditServer extends EditRecord
->color('danger') ->color('danger')
->label('Delete') ->label('Delete')
->requiresConfirmation() ->requiresConfirmation()
->action(function (Server $server) { ->action(function (Server $server, ServerDeletionService $service) {
resolve(ServerDeletionService::class)->handle($server); $service->handle($server);
return redirect(ListServers::getUrl()); return redirect(ListServers::getUrl());
}) })
@ -852,6 +853,7 @@ class EditServer extends EditRecord
]; ];
} }
protected function getFormActions(): array protected function getFormActions(): array
{ {
return []; return [];
@ -969,7 +971,7 @@ class EditServer extends EditRecord
return $options; return $options;
} }
protected function rotatePassword(DatabasePasswordService $service, $record, $set, $get): void protected function rotatePassword(DatabasePasswordService $service, Database $record, Set $set, Get $get): void
{ {
$newPassword = $service->handle($record); $newPassword = $service->handle($record);
$jdbcString = 'jdbc:mysql://' . $get('username') . ':' . urlencode($newPassword) . '@' . $record->host->host . ':' . $record->host->port . '/' . $get('database'); $jdbcString = 'jdbc:mysql://' . $get('username') . ':' . urlencode($newPassword) . '@' . $record->host->host . ':' . $record->host->port . '/' . $get('database');

View File

@ -88,6 +88,7 @@ class ListServers extends ListRecords
->button(), ->button(),
]); ]);
} }
protected function getHeaderActions(): array protected function getHeaderActions(): array
{ {
return [ return [

View File

@ -13,7 +13,9 @@ use chillerlan\QRCode\Common\EccLevel;
use chillerlan\QRCode\Common\Version; use chillerlan\QRCode\Common\Version;
use chillerlan\QRCode\QRCode; use chillerlan\QRCode\QRCode;
use chillerlan\QRCode\QROptions; use chillerlan\QRCode\QROptions;
use Closure;
use DateTimeZone; use DateTimeZone;
use Exception;
use Filament\Forms\Components\Actions\Action; use Filament\Forms\Components\Actions\Action;
use Filament\Forms\Components\Grid; use Filament\Forms\Components\Grid;
use Filament\Forms\Components\Placeholder; use Filament\Forms\Components\Placeholder;
@ -38,6 +40,13 @@ use Illuminate\Validation\Rules\Password;
*/ */
class EditProfile extends \Filament\Pages\Auth\EditProfile class EditProfile extends \Filament\Pages\Auth\EditProfile
{ {
private ToggleTwoFactorService $toggleTwoFactorService;
public function boot(ToggleTwoFactorService $toggleTwoFactorService): void
{
$this->toggleTwoFactorService = $toggleTwoFactorService;
}
protected function getForms(): array protected function getForms(): array
{ {
return [ return [
@ -106,7 +115,7 @@ class EditProfile extends \Filament\Pages\Auth\EditProfile
Tab::make('2FA') Tab::make('2FA')
->icon('tabler-shield-lock') ->icon('tabler-shield-lock')
->schema(function () { ->schema(function (TwoFactorSetupService $setupService) {
if ($this->getUser()->use_totp) { if ($this->getUser()->use_totp) {
return [ return [
Placeholder::make('2fa-already-enabled') Placeholder::make('2fa-already-enabled')
@ -124,8 +133,6 @@ class EditProfile extends \Filament\Pages\Auth\EditProfile
->helperText('Enter your current 2FA code to disable Two Factor Authentication'), ->helperText('Enter your current 2FA code to disable Two Factor Authentication'),
]; ];
} }
/** @var TwoFactorSetupService */
$setupService = app(TwoFactorSetupService::class);
['image_url_data' => $url, 'secret' => $secret] = cache()->remember( ['image_url_data' => $url, 'secret' => $secret] = cache()->remember(
"users.{$this->getUser()->id}.2fa.state", "users.{$this->getUser()->id}.2fa.state",
@ -274,23 +281,21 @@ class EditProfile extends \Filament\Pages\Auth\EditProfile
]; ];
} }
protected function handleRecordUpdate($record, $data): Model protected function handleRecordUpdate(Model $record, array $data): Model
{ {
if ($token = $data['2facode'] ?? null) { if (!$record instanceof User) {
/** @var ToggleTwoFactorService $service */ return $record;
$service = resolve(ToggleTwoFactorService::class); }
$tokens = $service->handle($record, $token, true); if ($token = $data['2facode'] ?? null) {
cache()->set("users.$record->id.2fa.tokens", implode("\n", $tokens), 15); $tokens = $this->toggleTwoFactorService->handle($record, $token, true);
cache()->set("users.$record->id.2fa.tokens", implode("\n", $tokens), now()->addSeconds(15));
$this->redirectRoute('filament.admin.auth.profile', ['tab' => '-2fa-tab']); $this->redirectRoute('filament.admin.auth.profile', ['tab' => '-2fa-tab']);
} }
if ($token = $data['2fa-disable-code'] ?? null) { if ($token = $data['2fa-disable-code'] ?? null) {
/** @var ToggleTwoFactorService $service */ $this->toggleTwoFactorService->handle($record, $token, false);
$service = resolve(ToggleTwoFactorService::class);
$service->handle($record, $token, false);
cache()->forget("users.$record->id.2fa.state"); cache()->forget("users.$record->id.2fa.state");
} }
@ -298,7 +303,7 @@ class EditProfile extends \Filament\Pages\Auth\EditProfile
return parent::handleRecordUpdate($record, $data); return parent::handleRecordUpdate($record, $data);
} }
public function exception($e, $stopPropagation): void public function exception(Exception $e, Closure $stopPropagation): void
{ {
if ($e instanceof TwoFactorAuthenticationTokenInvalid) { if ($e instanceof TwoFactorAuthenticationTokenInvalid) {
Notification::make() Notification::make()

View File

@ -18,6 +18,7 @@ use Illuminate\Support\Facades\Hash;
class EditUser extends EditRecord class EditUser extends EditRecord
{ {
protected static string $resource = UserResource::class; protected static string $resource = UserResource::class;
public function form(Form $form): Form public function form(Form $form): Form
{ {
return $form return $form
@ -46,6 +47,7 @@ class EditUser extends EditRecord
])->columns(), ])->columns(),
]); ]);
} }
protected function getHeaderActions(): array protected function getHeaderActions(): array
{ {
return [ return [

View File

@ -78,6 +78,7 @@ class ListUsers extends ListRecords
]), ]),
]); ]);
} }
protected function getHeaderActions(): array protected function getHeaderActions(): array
{ {
return [ return [
@ -110,13 +111,11 @@ class ListUsers extends ListRecords
]), ]),
]) ])
->successRedirectUrl(route('filament.admin.resources.users.index')) ->successRedirectUrl(route('filament.admin.resources.users.index'))
->action(function (array $data) { ->action(function (array $data, UserCreationService $creationService) {
$roles = $data['roles']; $roles = $data['roles'];
$roles = collect($roles)->map(fn ($role) => Role::findById($role)); $roles = collect($roles)->map(fn ($role) => Role::findById($role));
unset($data['roles']); unset($data['roles']);
/** @var UserCreationService $creationService */
$creationService = resolve(UserCreationService::class);
$user = $creationService->handle($data); $user = $creationService->handle($data);
$user->syncRoles($roles); $user->syncRoles($roles);

View File

@ -31,18 +31,18 @@ class ServersRelationManager extends RelationManager
) )
->label('Suspend All Servers') ->label('Suspend All Servers')
->color('warning') ->color('warning')
->action(function () use ($user) { ->action(function (SuspensionService $suspensionService) use ($user) {
foreach ($user->servers()->whereNot('status', ServerState::Suspended)->get() as $server) { foreach ($user->servers()->whereNot('status', ServerState::Suspended)->get() as $server) {
resolve(SuspensionService::class)->toggle($server); $suspensionService->toggle($server);
} }
}), }),
Actions\Action::make('toggleUnsuspend') Actions\Action::make('toggleUnsuspend')
->hidden(fn () => $user->servers()->where('status', ServerState::Suspended)->count() === 0) ->hidden(fn () => $user->servers()->where('status', ServerState::Suspended)->count() === 0)
->label('Unsuspend All Servers') ->label('Unsuspend All Servers')
->color('primary') ->color('primary')
->action(function () use ($user) { ->action(function (SuspensionService $suspensionService) use ($user) {
foreach ($user->servers()->where('status', ServerState::Suspended)->get() as $server) { foreach ($user->servers()->where('status', ServerState::Suspended)->get() as $server) {
resolve(SuspensionService::class)->toggle($server, SuspensionService::ACTION_UNSUSPEND); $suspensionService->toggle($server, SuspensionService::ACTION_UNSUSPEND);
} }
}), }),
]) ])

View File

@ -127,7 +127,7 @@ class EggController extends Controller
/** /**
* Normalizes a string of docker image data into the expected egg format. * Normalizes a string of docker image data into the expected egg format.
*/ */
protected function normalizeDockerImages(string $input = null): array protected function normalizeDockerImages(?string $input = null): array
{ {
$data = array_map(fn ($value) => trim($value), explode("\n", $input ?? '')); $data = array_map(fn ($value) => trim($value), explode("\n", $input ?? ''));

View File

@ -14,6 +14,7 @@ class NodeViewController extends Controller
use JavascriptInjection; use JavascriptInjection;
public const THRESHOLD_PERCENTAGE_LOW = 75; public const THRESHOLD_PERCENTAGE_LOW = 75;
public const THRESHOLD_PERCENTAGE_MEDIUM = 90; public const THRESHOLD_PERCENTAGE_MEDIUM = 90;
/** /**

View File

@ -70,7 +70,7 @@ class ServersController extends Controller
* @throws \App\Exceptions\DisplayException * @throws \App\Exceptions\DisplayException
* @throws \App\Exceptions\Model\DataValidationException * @throws \App\Exceptions\Model\DataValidationException
*/ */
public function toggleInstall(Server $server) public function toggleInstall(Server $server): void
{ {
if ($server->status === ServerState::InstallFailed) { if ($server->status === ServerState::InstallFailed) {
throw new DisplayException(trans('admin/server.exceptions.marked_as_failed')); throw new DisplayException(trans('admin/server.exceptions.marked_as_failed'));
@ -84,8 +84,6 @@ class ServersController extends Controller
->body(trans('admin/server.alerts.install_toggled')) ->body(trans('admin/server.alerts.install_toggled'))
->success() ->success()
->send(); ->send();
return null;
} }
/** /**
@ -94,7 +92,7 @@ class ServersController extends Controller
* @throws \App\Exceptions\DisplayException * @throws \App\Exceptions\DisplayException
* @throws \App\Exceptions\Model\DataValidationException * @throws \App\Exceptions\Model\DataValidationException
*/ */
public function reinstallServer(Server $server) public function reinstallServer(Server $server): void
{ {
$this->reinstallService->handle($server); $this->reinstallService->handle($server);

View File

@ -40,7 +40,7 @@ abstract class ApplicationApiController extends Controller
* Perform dependency injection of certain classes needed for core functionality * Perform dependency injection of certain classes needed for core functionality
* without littering the constructors of classes that extend this abstract. * without littering the constructors of classes that extend this abstract.
*/ */
public function loadDependencies(Fractal $fractal, Request $request) public function loadDependencies(Fractal $fractal, Request $request): void
{ {
$this->fractal = $fractal; $this->fractal = $fractal;
$this->request = $request; $this->request = $request;
@ -51,8 +51,7 @@ abstract class ApplicationApiController extends Controller
* *
* @template T of \App\Transformers\Api\Application\BaseTransformer * @template T of \App\Transformers\Api\Application\BaseTransformer
* *
* @param class-string<T> $abstract * @param class-string<T> $abstract
*
* @return T * @return T
* *
* @noinspection PhpDocSignatureInspection * @noinspection PhpDocSignatureInspection

View File

@ -41,8 +41,7 @@ abstract class ClientApiController extends ApplicationApiController
* *
* @template T of \App\Transformers\Api\Client\BaseClientTransformer * @template T of \App\Transformers\Api\Client\BaseClientTransformer
* *
* @param class-string<T> $abstract * @param class-string<T> $abstract
*
* @return T * @return T
* *
* @noinspection PhpDocSignatureInspection * @noinspection PhpDocSignatureInspection

View File

@ -14,7 +14,7 @@ use App\Http\Requests\Api\Remote\ActivityEventRequest;
class ActivityProcessingController extends Controller class ActivityProcessingController extends Controller
{ {
public function __invoke(ActivityEventRequest $request) public function __invoke(ActivityEventRequest $request): void
{ {
$tz = Carbon::now()->getTimezone(); $tz = Carbon::now()->getTimezone();

View File

@ -51,7 +51,7 @@ abstract class AbstractLoginController extends Controller
* *
* @throws \App\Exceptions\DisplayException * @throws \App\Exceptions\DisplayException
*/ */
protected function sendFailedLoginResponse(Request $request, Authenticatable $user = null, string $message = null) protected function sendFailedLoginResponse(Request $request, ?Authenticatable $user = null, ?string $message = null): never
{ {
$this->incrementLoginAttempts($request); $this->incrementLoginAttempts($request);
$this->fireFailedLoginEvent($user, [ $this->fireFailedLoginEvent($user, [
@ -91,7 +91,7 @@ abstract class AbstractLoginController extends Controller
/** /**
* Determine if the user is logging in using an email or username. * Determine if the user is logging in using an email or username.
*/ */
protected function getField(string $input = null): string protected function getField(?string $input = null): string
{ {
return ($input && str_contains($input, '@')) ? 'email' : 'username'; return ($input && str_contains($input, '@')) ? 'email' : 'username';
} }
@ -99,7 +99,7 @@ abstract class AbstractLoginController extends Controller
/** /**
* Fire a failed login event. * Fire a failed login event.
*/ */
protected function fireFailedLoginEvent(Authenticatable $user = null, array $credentials = []) protected function fireFailedLoginEvent(?Authenticatable $user = null, array $credentials = []): void
{ {
Event::dispatch(new Failed('auth', $user, $credentials)); Event::dispatch(new Failed('auth', $user, $credentials));
} }

View File

@ -16,7 +16,7 @@ class ForgotPasswordController extends Controller
/** /**
* Get the response for a failed password reset link. * Get the response for a failed password reset link.
*/ */
protected function sendResetLinkFailedResponse(Request $request, $response): JsonResponse protected function sendResetLinkFailedResponse(Request $request, string $response): JsonResponse
{ {
// As noted in #358 we will return success even if it failed // As noted in #358 we will return success even if it failed
// to avoid pointing out that an account does or does not // to avoid pointing out that an account does or does not
@ -28,10 +28,8 @@ class ForgotPasswordController extends Controller
/** /**
* Get the response for a successful password reset link. * Get the response for a successful password reset link.
*
* @param string $response
*/ */
protected function sendResetLinkResponse(Request $request, $response): JsonResponse protected function sendResetLinkResponse(Request $request, string $response): JsonResponse
{ {
return response()->json([ return response()->json([
'status' => trans($response), 'status' => trans($response),

View File

@ -72,7 +72,7 @@ class LoginCheckpointController extends AbstractLoginController
} }
} }
return $this->sendFailedLoginResponse($request, $user, !empty($recoveryToken) ? 'The recovery token provided is not valid.' : null); $this->sendFailedLoginResponse($request, $user, !empty($recoveryToken) ? 'The recovery token provided is not valid.' : null);
} }
/** /**

View File

@ -4,11 +4,13 @@ namespace App\Http\Controllers\Auth;
use App\Filament\Pages\Installer\PanelInstaller; use App\Filament\Pages\Installer\PanelInstaller;
use Carbon\CarbonImmutable; use Carbon\CarbonImmutable;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Models\User; use App\Models\User;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use App\Facades\Activity; use App\Facades\Activity;
use Illuminate\View\View;
class LoginController extends AbstractLoginController class LoginController extends AbstractLoginController
{ {
@ -17,7 +19,7 @@ class LoginController extends AbstractLoginController
* base authentication view component. React will take over at this point and * base authentication view component. React will take over at this point and
* turn the login area into an SPA. * turn the login area into an SPA.
*/ */
public function index() public function index(): View|RedirectResponse
{ {
if (!PanelInstaller::isInstalled()) { if (!PanelInstaller::isInstalled()) {
return redirect('/installer'); return redirect('/installer');

View File

@ -64,12 +64,12 @@ class ResetPasswordController extends Controller
* account do not automatically log them in. In those cases, send the user back to the login * account do not automatically log them in. In those cases, send the user back to the login
* form with a note telling them their password was changed and to log back in. * form with a note telling them their password was changed and to log back in.
* *
* @param \Illuminate\Contracts\Auth\CanResetPassword|\App\Models\User $user * @param \Illuminate\Contracts\Auth\CanResetPassword|\App\Models\User $user
* @param string $password * @param string $password
* *
* @throws \App\Exceptions\Model\DataValidationException * @throws \App\Exceptions\Model\DataValidationException
*/ */
protected function resetPassword($user, $password) protected function resetPassword($user, $password): void
{ {
/** @var User $user */ /** @var User $user */
$user->password = $this->hasher->make($password); $user->password = $this->hasher->make($password);

View File

@ -15,7 +15,7 @@ class EnsureStatefulRequests extends EnsureFrontendRequestsAreStateful
* We don't want to support API usage using the cookies, except for requests stemming * We don't want to support API usage using the cookies, except for requests stemming
* from the front-end we control. * from the front-end we control.
*/ */
public static function fromFrontend($request) public static function fromFrontend($request): bool
{ {
if (parent::fromFrontend($request)) { if (parent::fromFrontend($request)) {
return true; return true;

View File

@ -17,7 +17,7 @@ class RedirectIfAuthenticated
/** /**
* Handle an incoming request. * Handle an incoming request.
*/ */
public function handle(Request $request, \Closure $next, string $guard = null): mixed public function handle(Request $request, \Closure $next, ?string $guard = null): mixed
{ {
if ($this->authManager->guard($guard)->check()) { if ($this->authManager->guard($guard)->check()) {
return redirect()->route('index'); return redirect()->route('index');

View File

@ -10,7 +10,9 @@ use App\Exceptions\Http\TwoFactorAuthRequiredException;
class RequireTwoFactorAuthentication class RequireTwoFactorAuthentication
{ {
public const LEVEL_NONE = 0; public const LEVEL_NONE = 0;
public const LEVEL_ADMIN = 1; public const LEVEL_ADMIN = 1;
public const LEVEL_ALL = 2; public const LEVEL_ALL = 2;
/** /**

View File

@ -3,23 +3,26 @@
namespace App\Http\Middleware; namespace App\Http\Middleware;
use GuzzleHttp\Client; use GuzzleHttp\Client;
use Illuminate\Foundation\Application;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\Response; use Illuminate\Http\Response;
use App\Events\Auth\FailedCaptcha; use App\Events\Auth\FailedCaptcha;
use Symfony\Component\HttpKernel\Exception\HttpException; use Symfony\Component\HttpKernel\Exception\HttpException;
class VerifyReCaptcha readonly class VerifyReCaptcha
{ {
/** public function __construct(private Application $app)
* Handle an incoming request. {
*/
}
public function handle(Request $request, \Closure $next): mixed public function handle(Request $request, \Closure $next): mixed
{ {
if (!config('recaptcha.enabled')) { if (!config('recaptcha.enabled')) {
return $next($request); return $next($request);
} }
if (app()->isLocal()) { if ($this->app->isLocal()) {
return $next($request); return $next($request);
} }

View File

@ -28,7 +28,7 @@ abstract class AdminFormRequest extends FormRequest
* Return only the fields that we are interested in from the request. * Return only the fields that we are interested in from the request.
* This will include empty fields as a null value. * This will include empty fields as a null value.
*/ */
public function normalize(array $only = null): array public function normalize(?array $only = null): array
{ {
return $this->only($only ?? array_keys($this->rules())); return $this->only($only ?? array_keys($this->rules()));
} }

View File

@ -3,6 +3,7 @@
namespace App\Http\Requests\Admin\Egg; namespace App\Http\Requests\Admin\Egg;
use App\Http\Requests\Admin\AdminFormRequest; use App\Http\Requests\Admin\AdminFormRequest;
use Illuminate\Validation\Validator;
class EggFormRequest extends AdminFormRequest class EggFormRequest extends AdminFormRequest
{ {
@ -25,7 +26,7 @@ class EggFormRequest extends AdminFormRequest
return $rules; return $rules;
} }
public function withValidator($validator) public function withValidator(Validator $validator): void
{ {
$validator->sometimes('config_from', 'exists:eggs,id', function () { $validator->sometimes('config_from', 'exists:eggs,id', function () {
return (int) $this->input('config_from') !== 0; return (int) $this->input('config_from') !== 0;

View File

@ -27,7 +27,7 @@ class MailSettingsFormRequest extends AdminFormRequest
* Override the default normalization function for this type of request * Override the default normalization function for this type of request
* as we need to accept empty values on the keys. * as we need to accept empty values on the keys.
*/ */
public function normalize(array $only = null): array public function normalize(?array $only = null): array
{ {
$keys = array_flip(array_keys($this->rules())); $keys = array_flip(array_keys($this->rules()));

View File

@ -74,8 +74,7 @@ abstract class ApplicationApiRequest extends FormRequest
* *
* @template T of \Illuminate\Database\Eloquent\Model * @template T of \Illuminate\Database\Eloquent\Model
* *
* @param class-string<T> $expect * @param class-string<T> $expect
*
* @return T * @return T
* *
* @noinspection PhpDocSignatureInspection * @noinspection PhpDocSignatureInspection

View File

@ -12,7 +12,7 @@ class StoreDatabaseHostRequest extends ApplicationApiRequest
protected int $permission = AdminAcl::WRITE; protected int $permission = AdminAcl::WRITE;
public function rules(array $rules = null): array public function rules(?array $rules = null): array
{ {
return $rules ?? DatabaseHost::getRules(); return $rules ?? DatabaseHost::getRules();
} }

View File

@ -6,11 +6,11 @@ use App\Models\DatabaseHost;
class UpdateDatabaseHostRequest extends StoreDatabaseHostRequest class UpdateDatabaseHostRequest extends StoreDatabaseHostRequest
{ {
public function rules(array $rules = null): array public function rules(?array $rules = null): array
{ {
/** @var DatabaseHost $databaseHost */ /** @var DatabaseHost $databaseHost */
$databaseHost = $this->route()->parameter('database_host'); $databaseHost = $this->route()->parameter('database_host');
return $rules ?? DatabaseHost::getRulesForUpdate($databaseHost->id); return $rules ?? DatabaseHost::getRulesForUpdate($databaseHost);
} }
} }

View File

@ -9,11 +9,11 @@ class UpdateMountRequest extends StoreMountRequest
/** /**
* Apply validation rules to this request. * Apply validation rules to this request.
*/ */
public function rules(array $rules = null): array public function rules(?array $rules = null): array
{ {
/** @var Mount $mount */ /** @var Mount $mount */
$mount = $this->route()->parameter('mount'); $mount = $this->route()->parameter('mount');
return Mount::getRulesForUpdate($mount->id); return Mount::getRulesForUpdate($mount);
} }
} }

View File

@ -15,7 +15,7 @@ class StoreNodeRequest extends ApplicationApiRequest
/** /**
* Validation rules to apply to this request. * Validation rules to apply to this request.
*/ */
public function rules(array $rules = null): array public function rules(?array $rules = null): array
{ {
return collect($rules ?? Node::getRules())->only([ return collect($rules ?? Node::getRules())->only([
'public', 'public',

View File

@ -10,11 +10,11 @@ class UpdateNodeRequest extends StoreNodeRequest
* Apply validation rules to this request. Uses the parent class rules() * Apply validation rules to this request. Uses the parent class rules()
* function but passes in the rules for updating rather than creating. * function but passes in the rules for updating rather than creating.
*/ */
public function rules(array $rules = null): array public function rules(?array $rules = null): array
{ {
/** @var Node $node */ /** @var Node $node */
$node = $this->route()->parameter('node'); $node = $this->route()->parameter('node');
return parent::rules(Node::getRulesForUpdate($node->id)); return parent::rules(Node::getRulesForUpdate($node));
} }
} }

View File

@ -11,7 +11,7 @@ class StoreRoleRequest extends ApplicationApiRequest
protected int $permission = AdminAcl::WRITE; protected int $permission = AdminAcl::WRITE;
public function rules(array $rules = null): array public function rules(?array $rules = null): array
{ {
return [ return [
'name' => 'required|string', 'name' => 'required|string',

View File

@ -7,7 +7,7 @@ class AssignUserRolesRequest extends StoreUserRequest
/** /**
* Return the validation rules for this request. * Return the validation rules for this request.
*/ */
public function rules(array $rules = null): array public function rules(?array $rules = null): array
{ {
return [ return [
'roles' => 'array', 'roles' => 'array',

View File

@ -15,7 +15,7 @@ class StoreUserRequest extends ApplicationApiRequest
/** /**
* Return the validation rules for this request. * Return the validation rules for this request.
*/ */
public function rules(array $rules = null): array public function rules(?array $rules = null): array
{ {
$rules = $rules ?? User::getRules(); $rules = $rules ?? User::getRules();

View File

@ -9,10 +9,10 @@ class UpdateUserRequest extends StoreUserRequest
/** /**
* Return the validation rules for this request. * Return the validation rules for this request.
*/ */
public function rules(array $rules = null): array public function rules(?array $rules = null): array
{ {
$userId = $this->parameter('user', User::class)->id; $user = $this->parameter('user', User::class);
return parent::rules(User::getRulesForUpdate($userId)); return parent::rules(User::getRulesForUpdate($user));
} }
} }

View File

@ -49,7 +49,7 @@ abstract class SubuserRequest extends ClientApiRequest
* *
* @throws \Illuminate\Contracts\Container\BindingResolutionException * @throws \Illuminate\Contracts\Container\BindingResolutionException
*/ */
protected function validatePermissionsCanBeAssigned(array $permissions) protected function validatePermissionsCanBeAssigned(array $permissions): void
{ {
$user = $this->user(); $user = $this->user();
/** @var \App\Models\Server $server */ /** @var \App\Models\Server $server */

View File

@ -42,5 +42,4 @@ class NodeStatistics implements ShouldQueue
} }
} }
} }
} }

View File

@ -90,7 +90,7 @@ class RunTaskJob extends Job implements ShouldQueue
/** /**
* Handle a failure while sending the action to the daemon or otherwise processing the job. * Handle a failure while sending the action to the daemon or otherwise processing the job.
*/ */
public function failed(\Exception $exception = null) public function failed(?\Exception $exception = null): void
{ {
$this->markTaskNotQueued(); $this->markTaskNotQueued();
$this->markScheduleComplete(); $this->markScheduleComplete();
@ -99,7 +99,7 @@ class RunTaskJob extends Job implements ShouldQueue
/** /**
* Get the next task in the schedule and queue it for running after the defined period of wait time. * Get the next task in the schedule and queue it for running after the defined period of wait time.
*/ */
private function queueNextTask() private function queueNextTask(): void
{ {
/** @var \App\Models\Task|null $nextTask */ /** @var \App\Models\Task|null $nextTask */
$nextTask = Task::query()->where('schedule_id', $this->task->schedule_id) $nextTask = Task::query()->where('schedule_id', $this->task->schedule_id)
@ -121,7 +121,7 @@ class RunTaskJob extends Job implements ShouldQueue
/** /**
* Marks the parent schedule as being complete. * Marks the parent schedule as being complete.
*/ */
private function markScheduleComplete() private function markScheduleComplete(): void
{ {
$this->task->schedule()->update([ $this->task->schedule()->update([
'is_processing' => false, 'is_processing' => false,
@ -132,7 +132,7 @@ class RunTaskJob extends Job implements ShouldQueue
/** /**
* Mark a specific task as no longer being queued. * Mark a specific task as no longer being queued.
*/ */
private function markTaskNotQueued() private function markTaskNotQueued(): void
{ {
$this->task->update(['is_queued' => false]); $this->task->update(['is_queued' => false]);
} }

View File

@ -3,19 +3,21 @@
namespace App\Livewire; namespace App\Livewire;
use App\Models\Node; use App\Models\Node;
use Illuminate\View\View;
use Livewire\Component; use Livewire\Component;
class NodeSystemInformation extends Component class NodeSystemInformation extends Component
{ {
public Node $node; public Node $node;
public string $sizeClasses; public string $sizeClasses;
public function render() public function render(): View
{ {
return view('livewire.node-system-information'); return view('livewire.node-system-information');
} }
public function placeholder() public function placeholder(): string
{ {
return <<<'HTML' return <<<'HTML'
<div> <div>

View File

@ -123,7 +123,7 @@ class ActivityLog extends Model
* *
* @see https://laravel.com/docs/9.x/eloquent#pruning-models * @see https://laravel.com/docs/9.x/eloquent#pruning-models
*/ */
public function prunable() public function prunable(): Builder
{ {
if (is_null(config('activity.prune_days'))) { if (is_null(config('activity.prune_days'))) {
throw new \LogicException('Cannot prune activity logs: no "prune_days" configuration value is set.'); throw new \LogicException('Cannot prune activity logs: no "prune_days" configuration value is set.');
@ -136,7 +136,7 @@ class ActivityLog extends Model
* Boots the model event listeners. This will trigger an activity log event every * Boots the model event listeners. This will trigger an activity log event every
* time a new model is inserted which can then be captured and worked with as needed. * time a new model is inserted which can then be captured and worked with as needed.
*/ */
protected static function boot() protected static function boot(): void
{ {
parent::boot(); parent::boot();
@ -149,7 +149,7 @@ class ActivityLog extends Model
}); });
} }
public function htmlable() public function htmlable(): string
{ {
$user = $this->actor; $user = $this->actor;
if (!$user instanceof User) { if (!$user instanceof User) {

View File

@ -3,8 +3,9 @@
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Database\Eloquent\Relations\Pivot; use Illuminate\Database\Eloquent\Relations\Pivot;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletingScope;
/** /**
* \App\Models\ActivityLogSubject. * \App\Models\ActivityLogSubject.
@ -25,6 +26,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
class ActivityLogSubject extends Pivot class ActivityLogSubject extends Pivot
{ {
public $incrementing = true; public $incrementing = true;
public $timestamps = false; public $timestamps = false;
protected $table = 'activity_log_subjects'; protected $table = 'activity_log_subjects';
@ -36,15 +38,8 @@ class ActivityLogSubject extends Pivot
return $this->belongsTo(ActivityLog::class); return $this->belongsTo(ActivityLog::class);
} }
public function subject() public function subject(): MorphTo
{ {
$morph = $this->morphTo(); return $this->morphTo()->withoutGlobalScope(SoftDeletingScope::class);
if (in_array(SoftDeletes::class, class_uses_recursive($morph::class))) {
/** @var self|Backup|UserSSHKey $morph - cannot use traits in doc blocks */
return $morph->withTrashed();
}
return $morph;
} }
} }

View File

@ -63,21 +63,28 @@ class ApiKey extends Model
* API representation using fractal. * API representation using fractal.
*/ */
public const RESOURCE_NAME = 'api_key'; public const RESOURCE_NAME = 'api_key';
/** /**
* Different API keys that can exist on the system. * Different API keys that can exist on the system.
*/ */
public const TYPE_NONE = 0; public const TYPE_NONE = 0;
public const TYPE_ACCOUNT = 1; public const TYPE_ACCOUNT = 1;
/* @deprecated */ /* @deprecated */
public const TYPE_APPLICATION = 2; public const TYPE_APPLICATION = 2;
/* @deprecated */ /* @deprecated */
public const TYPE_DAEMON_USER = 3; public const TYPE_DAEMON_USER = 3;
/* @deprecated */ /* @deprecated */
public const TYPE_DAEMON_APPLICATION = 4; public const TYPE_DAEMON_APPLICATION = 4;
/** /**
* The length of API key identifiers. * The length of API key identifiers.
*/ */
public const IDENTIFIER_LENGTH = 16; public const IDENTIFIER_LENGTH = 16;
/** /**
* The length of the actual API key that is encrypted and stored * The length of the actual API key that is encrypted and stored
* in the database. * in the database.

View File

@ -31,6 +31,7 @@ class Backup extends Model
public const RESOURCE_NAME = 'backup'; public const RESOURCE_NAME = 'backup';
public const ADAPTER_DAEMON = 'wings'; public const ADAPTER_DAEMON = 'wings';
public const ADAPTER_AWS_S3 = 's3'; public const ADAPTER_AWS_S3 = 's3';
protected $table = 'backups'; protected $table = 'backups';

View File

@ -70,6 +70,7 @@ class Egg extends Model
* than leaving it null. * than leaving it null.
*/ */
public const FEATURE_EULA_POPUP = 'eula'; public const FEATURE_EULA_POPUP = 'eula';
public const FEATURE_FASTDL = 'fastdl'; public const FEATURE_FASTDL = 'fastdl';
/** /**

View File

@ -11,9 +11,9 @@ class AdminServerFilter implements Filter
* A multi-column filter for the servers table that allows an administrative user to search * A multi-column filter for the servers table that allows an administrative user to search
* across UUID, name, owner username, and owner email. * across UUID, name, owner username, and owner email.
* *
* @param string $value * @param string $value
*/ */
public function __invoke(Builder $query, $value, string $property) public function __invoke(Builder $query, $value, string $property): void
{ {
if ($query->getQuery()->from !== 'servers') { if ($query->getQuery()->from !== 'servers') {
throw new \BadMethodCallException('Cannot use the AdminServerFilter against a non-server model.'); throw new \BadMethodCallException('Cannot use the AdminServerFilter against a non-server model.');

View File

@ -18,9 +18,9 @@ class MultiFieldServerFilter implements Filter
* search across multiple columns. This allows us to provide a very generic search ability for * search across multiple columns. This allows us to provide a very generic search ability for
* the frontend. * the frontend.
* *
* @param string $value * @param string $value
*/ */
public function __invoke(Builder $query, $value, string $property) public function __invoke(Builder $query, $value, string $property): void
{ {
if ($query->getQuery()->from !== 'servers') { if ($query->getQuery()->from !== 'servers') {
throw new \BadMethodCallException('Cannot use the MultiFieldServerFilter against a non-server model.'); throw new \BadMethodCallException('Cannot use the MultiFieldServerFilter against a non-server model.');

View File

@ -38,7 +38,7 @@ abstract class Model extends IlluminateModel
* *
* @throws \Illuminate\Contracts\Container\BindingResolutionException * @throws \Illuminate\Contracts\Container\BindingResolutionException
*/ */
protected static function boot() protected static function boot(): void
{ {
parent::boot(); parent::boot();
@ -69,7 +69,7 @@ abstract class Model extends IlluminateModel
return 'uuid'; return 'uuid';
} }
protected function asDateTime($value) protected function asDateTime($value): Carbon
{ {
$timezone = auth()->user()?->timezone ?? config('app.timezone', 'UTC'); $timezone = auth()->user()?->timezone ?? config('app.timezone', 'UTC');
@ -135,11 +135,9 @@ abstract class Model extends IlluminateModel
* Returns the rules associated with the model, specifically for updating the given model * Returns the rules associated with the model, specifically for updating the given model
* rather than just creating it. * rather than just creating it.
*/ */
public static function getRulesForUpdate($model, string $column = 'id'): array public static function getRulesForUpdate(self $model): array
{ {
if ($model instanceof Model) { [$id, $column] = [$model->getKey(), $model->getKeyName()];
[$id, $column] = [$model->getKey(), $model->getKeyName()];
}
$rules = static::getRules(); $rules = static::getRules();
foreach ($rules as $key => &$data) { foreach ($rules as $key => &$data) {

View File

@ -70,7 +70,7 @@ class Mount extends Model
/** /**
* Blacklisted source paths. * Blacklisted source paths.
*/ */
public static $invalidSourcePaths = [ public static array $invalidSourcePaths = [
'/etc/pelican', '/etc/pelican',
'/var/lib/pelican/volumes', '/var/lib/pelican/volumes',
'/srv/daemon-data', '/srv/daemon-data',
@ -79,7 +79,7 @@ class Mount extends Model
/** /**
* Blacklisted target paths. * Blacklisted target paths.
*/ */
public static $invalidTargetPaths = [ public static array $invalidTargetPaths = [
'/home/container', '/home/container',
]; ];

View File

@ -16,51 +16,81 @@ class Permission extends Model
* Constants defining different permissions available. * Constants defining different permissions available.
*/ */
public const ACTION_WEBSOCKET_CONNECT = 'websocket.connect'; public const ACTION_WEBSOCKET_CONNECT = 'websocket.connect';
public const ACTION_CONTROL_CONSOLE = 'control.console'; public const ACTION_CONTROL_CONSOLE = 'control.console';
public const ACTION_CONTROL_START = 'control.start'; public const ACTION_CONTROL_START = 'control.start';
public const ACTION_CONTROL_STOP = 'control.stop'; public const ACTION_CONTROL_STOP = 'control.stop';
public const ACTION_CONTROL_RESTART = 'control.restart'; public const ACTION_CONTROL_RESTART = 'control.restart';
public const ACTION_DATABASE_READ = 'database.read'; public const ACTION_DATABASE_READ = 'database.read';
public const ACTION_DATABASE_CREATE = 'database.create'; public const ACTION_DATABASE_CREATE = 'database.create';
public const ACTION_DATABASE_UPDATE = 'database.update'; public const ACTION_DATABASE_UPDATE = 'database.update';
public const ACTION_DATABASE_DELETE = 'database.delete'; public const ACTION_DATABASE_DELETE = 'database.delete';
public const ACTION_DATABASE_VIEW_PASSWORD = 'database.view_password'; public const ACTION_DATABASE_VIEW_PASSWORD = 'database.view_password';
public const ACTION_SCHEDULE_READ = 'schedule.read'; public const ACTION_SCHEDULE_READ = 'schedule.read';
public const ACTION_SCHEDULE_CREATE = 'schedule.create'; public const ACTION_SCHEDULE_CREATE = 'schedule.create';
public const ACTION_SCHEDULE_UPDATE = 'schedule.update'; public const ACTION_SCHEDULE_UPDATE = 'schedule.update';
public const ACTION_SCHEDULE_DELETE = 'schedule.delete'; public const ACTION_SCHEDULE_DELETE = 'schedule.delete';
public const ACTION_USER_READ = 'user.read'; public const ACTION_USER_READ = 'user.read';
public const ACTION_USER_CREATE = 'user.create'; public const ACTION_USER_CREATE = 'user.create';
public const ACTION_USER_UPDATE = 'user.update'; public const ACTION_USER_UPDATE = 'user.update';
public const ACTION_USER_DELETE = 'user.delete'; public const ACTION_USER_DELETE = 'user.delete';
public const ACTION_BACKUP_READ = 'backup.read'; public const ACTION_BACKUP_READ = 'backup.read';
public const ACTION_BACKUP_CREATE = 'backup.create'; public const ACTION_BACKUP_CREATE = 'backup.create';
public const ACTION_BACKUP_DELETE = 'backup.delete'; public const ACTION_BACKUP_DELETE = 'backup.delete';
public const ACTION_BACKUP_DOWNLOAD = 'backup.download'; public const ACTION_BACKUP_DOWNLOAD = 'backup.download';
public const ACTION_BACKUP_RESTORE = 'backup.restore'; public const ACTION_BACKUP_RESTORE = 'backup.restore';
public const ACTION_ALLOCATION_READ = 'allocation.read'; public const ACTION_ALLOCATION_READ = 'allocation.read';
public const ACTION_ALLOCATION_CREATE = 'allocation.create'; public const ACTION_ALLOCATION_CREATE = 'allocation.create';
public const ACTION_ALLOCATION_UPDATE = 'allocation.update'; public const ACTION_ALLOCATION_UPDATE = 'allocation.update';
public const ACTION_ALLOCATION_DELETE = 'allocation.delete'; public const ACTION_ALLOCATION_DELETE = 'allocation.delete';
public const ACTION_FILE_READ = 'file.read'; public const ACTION_FILE_READ = 'file.read';
public const ACTION_FILE_READ_CONTENT = 'file.read-content'; public const ACTION_FILE_READ_CONTENT = 'file.read-content';
public const ACTION_FILE_CREATE = 'file.create'; public const ACTION_FILE_CREATE = 'file.create';
public const ACTION_FILE_UPDATE = 'file.update'; public const ACTION_FILE_UPDATE = 'file.update';
public const ACTION_FILE_DELETE = 'file.delete'; public const ACTION_FILE_DELETE = 'file.delete';
public const ACTION_FILE_ARCHIVE = 'file.archive'; public const ACTION_FILE_ARCHIVE = 'file.archive';
public const ACTION_FILE_SFTP = 'file.sftp'; public const ACTION_FILE_SFTP = 'file.sftp';
public const ACTION_STARTUP_READ = 'startup.read'; public const ACTION_STARTUP_READ = 'startup.read';
public const ACTION_STARTUP_UPDATE = 'startup.update'; public const ACTION_STARTUP_UPDATE = 'startup.update';
public const ACTION_STARTUP_DOCKER_IMAGE = 'startup.docker-image'; public const ACTION_STARTUP_DOCKER_IMAGE = 'startup.docker-image';
public const ACTION_SETTINGS_RENAME = 'settings.rename'; public const ACTION_SETTINGS_RENAME = 'settings.rename';
public const ACTION_SETTINGS_REINSTALL = 'settings.reinstall'; public const ACTION_SETTINGS_REINSTALL = 'settings.reinstall';
public const ACTION_ACTIVITY_READ = 'activity.read'; public const ACTION_ACTIVITY_READ = 'activity.read';

View File

@ -74,6 +74,7 @@ use App\Exceptions\Http\Server\ServerStateConflictException;
* @property \App\Models\User $user * @property \App\Models\User $user
* @property \Illuminate\Database\Eloquent\Collection|\App\Models\EggVariable[] $variables * @property \Illuminate\Database\Eloquent\Collection|\App\Models\EggVariable[] $variables
* @property int|null $variables_count * @property int|null $variables_count
*
* @method static \Database\Factories\ServerFactory factory(...$parameters) * @method static \Database\Factories\ServerFactory factory(...$parameters)
* @method static \Illuminate\Database\Eloquent\Builder|Server newModelQuery() * @method static \Illuminate\Database\Eloquent\Builder|Server newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|Server newQuery() * @method static \Illuminate\Database\Eloquent\Builder|Server newQuery()
@ -104,6 +105,7 @@ use App\Exceptions\Http\Server\ServerStateConflictException;
* @method static \Illuminate\Database\Eloquent\Builder|Server whereUpdatedAt($value) * @method static \Illuminate\Database\Eloquent\Builder|Server whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|Server whereUuid($value) * @method static \Illuminate\Database\Eloquent\Builder|Server whereUuid($value)
* @method static \Illuminate\Database\Eloquent\Builder|Server whereuuid_short($value) * @method static \Illuminate\Database\Eloquent\Builder|Server whereuuid_short($value)
*
* @property array|null $docker_labels * @property array|null $docker_labels
* @property Collection<Endpoint>|null $ports * @property Collection<Endpoint>|null $ports
* @property-read mixed $condition * @property-read mixed $condition
@ -111,10 +113,12 @@ use App\Exceptions\Http\Server\ServerStateConflictException;
* @property-read int|null $egg_variables_count * @property-read int|null $egg_variables_count
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ServerVariable> $serverVariables * @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\ServerVariable> $serverVariables
* @property-read int|null $server_variables_count * @property-read int|null $server_variables_count
*
* @method static \Illuminate\Database\Eloquent\Builder|Server whereDockerLabels($value) * @method static \Illuminate\Database\Eloquent\Builder|Server whereDockerLabels($value)
* @method static \Illuminate\Database\Eloquent\Builder|Server whereInstalledAt($value) * @method static \Illuminate\Database\Eloquent\Builder|Server whereInstalledAt($value)
* @method static \Illuminate\Database\Eloquent\Builder|Server wherePorts($value) * @method static \Illuminate\Database\Eloquent\Builder|Server wherePorts($value)
* @method static \Illuminate\Database\Eloquent\Builder|Server whereUuidShort($value) * @method static \Illuminate\Database\Eloquent\Builder|Server whereUuidShort($value)
*
* @mixin \Eloquent * @mixin \Eloquent
*/ */
class Server extends Model class Server extends Model
@ -342,7 +346,7 @@ class Server extends Model
* *
* @throws ServerStateConflictException * @throws ServerStateConflictException
*/ */
public function validateCurrentState() public function validateCurrentState(): void
{ {
if ( if (
$this->isSuspended() || $this->isSuspended() ||
@ -361,7 +365,7 @@ class Server extends Model
* sure the server can be transferred and is not currently being transferred * sure the server can be transferred and is not currently being transferred
* or installed. * or installed.
*/ */
public function validateTransferState() public function validateTransferState(): void
{ {
if ( if (
!$this->isInstalled() || !$this->isInstalled() ||

View File

@ -31,8 +31,11 @@ class Task extends Model
* The default actions that can exist for a task * The default actions that can exist for a task
*/ */
public const ACTION_POWER = 'power'; public const ACTION_POWER = 'power';
public const ACTION_COMMAND = 'command'; public const ACTION_COMMAND = 'command';
public const ACTION_BACKUP = 'backup'; public const ACTION_BACKUP = 'backup';
public const ACTION_DELETE_FILES = 'delete_files'; public const ACTION_DELETE_FILES = 'delete_files';
/** /**

View File

@ -99,6 +99,7 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
use Notifiable; use Notifiable;
public const USER_LEVEL_USER = 0; public const USER_LEVEL_USER = 0;
public const USER_LEVEL_ADMIN = 1; public const USER_LEVEL_ADMIN = 1;
/** /**
@ -233,9 +234,9 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
/** /**
* Send the password reset notification. * Send the password reset notification.
* *
* @param string $token * @param string $token
*/ */
public function sendPasswordResetNotification($token) public function sendPasswordResetNotification($token): void
{ {
Activity::event('auth:reset-password') Activity::event('auth:reset-password')
->withRequestMetadata() ->withRequestMetadata()
@ -248,7 +249,7 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
/** /**
* Store the username as a lowercase string. * Store the username as a lowercase string.
*/ */
public function setUsernameAttribute(string $value) public function setUsernameAttribute(string $value): void
{ {
$this->attributes['username'] = mb_strtolower($value); $this->attributes['username'] = mb_strtolower($value);
} }

View File

@ -0,0 +1,38 @@
<?php
namespace App\PHPStan;
use PhpParser\Node;
use PhpParser\Node\Expr\FuncCall;
use PHPStan\Analyser\Scope;
use PHPStan\Rules\Rule;
class ForbiddenGlobalFunctionsRule implements Rule
{
private array $forbiddenFunctions;
public function __construct(array $forbiddenFunctions = ['app', 'resolve'])
{
$this->forbiddenFunctions = $forbiddenFunctions;
}
public function getNodeType(): string
{
return FuncCall::class;
}
public function processNode(Node $node, Scope $scope): array
{
/** @var FuncCall $node */
if ($node->name instanceof Node\Name) {
$functionName = (string) $node->name;
if (in_array($functionName, $this->forbiddenFunctions, true)) {
return [
sprintf('Usage of global function "%s" is forbidden.', $functionName),
];
}
}
return [];
}
}

View File

@ -41,7 +41,7 @@ class ServerPolicy
* not call the before() function if there isn't a function matching the * not call the before() function if there isn't a function matching the
* policy permission. * policy permission.
*/ */
public function __call(string $name, mixed $arguments) public function __call(string $name, mixed $arguments): void
{ {
// do nothing // do nothing
} }

View File

@ -15,6 +15,7 @@ use Dedoc\Scramble\Support\Generator\SecurityScheme;
use Filament\Support\Colors\Color; use Filament\Support\Colors\Color;
use Filament\Support\Facades\FilamentColor; use Filament\Support\Facades\FilamentColor;
use Illuminate\Database\Eloquent\Relations\Relation; use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Foundation\Application;
use Illuminate\Pagination\Paginator; use Illuminate\Pagination\Paginator;
use Illuminate\Support\Facades\Broadcast; use Illuminate\Support\Facades\Broadcast;
use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Event;
@ -34,7 +35,7 @@ class AppServiceProvider extends ServiceProvider
/** /**
* Bootstrap any application services. * Bootstrap any application services.
*/ */
public function boot(): void public function boot(Application $app): void
{ {
// TODO: remove when old admin area gets yeeted // TODO: remove when old admin area gets yeeted
View::share('appVersion', config('app.version')); View::share('appVersion', config('app.version'));
@ -68,7 +69,7 @@ class AppServiceProvider extends ServiceProvider
->asJson() ->asJson()
->withToken($node->daemon_token) ->withToken($node->daemon_token)
->withHeaders($headers) ->withHeaders($headers)
->withOptions(['verify' => (bool) app()->environment('production')]) ->withOptions(['verify' => (bool) $app->environment('production')])
->timeout(config('panel.guzzle.timeout')) ->timeout(config('panel.guzzle.timeout'))
->connectTimeout(config('panel.guzzle.connect_timeout')) ->connectTimeout(config('panel.guzzle.connect_timeout'))
->baseUrl($node->getConnectionAddress()) ->baseUrl($node->getConnectionAddress())

View File

@ -9,6 +9,7 @@ use Filament\Http\Middleware\DisableBladeIconComponents;
use Filament\Http\Middleware\DispatchServingFilamentEvent; use Filament\Http\Middleware\DispatchServingFilamentEvent;
use Filament\Panel; use Filament\Panel;
use Filament\PanelProvider; use Filament\PanelProvider;
use Filament\Support\Enums\MaxWidth;
use Filament\Support\Facades\FilamentAsset; use Filament\Support\Facades\FilamentAsset;
use Filament\Widgets; use Filament\Widgets;
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse; use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
@ -21,7 +22,7 @@ use Illuminate\View\Middleware\ShareErrorsFromSession;
class AdminPanelProvider extends PanelProvider class AdminPanelProvider extends PanelProvider
{ {
public function boot() public function boot(): void
{ {
FilamentAsset::registerCssVariables([ FilamentAsset::registerCssVariables([
'sidebar-width' => '16rem !important', 'sidebar-width' => '16rem !important',
@ -43,6 +44,8 @@ class AdminPanelProvider extends PanelProvider
->brandLogo(config('app.logo')) ->brandLogo(config('app.logo'))
->brandLogoHeight('2rem') ->brandLogoHeight('2rem')
->profile(EditProfile::class, false) ->profile(EditProfile::class, false)
->maxContentWidth(MaxWidth::ScreenTwoExtraLarge)
->spa()
->discoverResources(in: app_path('Filament/Resources'), for: 'App\\Filament\\Resources') ->discoverResources(in: app_path('Filament/Resources'), for: 'App\\Filament\\Resources')
->discoverPages(in: app_path('Filament/Pages'), for: 'App\\Filament\\Pages') ->discoverPages(in: app_path('Filament/Pages'), for: 'App\\Filament\\Pages')
->discoverClusters(in: app_path('Filament/Clusters'), for: 'App\\Filament\\Clusters') ->discoverClusters(in: app_path('Filament/Clusters'), for: 'App\\Filament\\Clusters')

View File

@ -2,6 +2,7 @@
namespace App\Repositories\Daemon; namespace App\Repositories\Daemon;
use Illuminate\Http\Client\Response;
use Webmozart\Assert\Assert; use Webmozart\Assert\Assert;
use App\Models\Backup; use App\Models\Backup;
use App\Models\Server; use App\Models\Server;
@ -27,7 +28,7 @@ class DaemonBackupRepository extends DaemonRepository
* *
* @throws \App\Exceptions\Http\Connection\DaemonConnectionException * @throws \App\Exceptions\Http\Connection\DaemonConnectionException
*/ */
public function backup(Backup $backup) public function backup(Backup $backup): Response
{ {
Assert::isInstanceOf($this->server, Server::class); Assert::isInstanceOf($this->server, Server::class);
@ -50,7 +51,7 @@ class DaemonBackupRepository extends DaemonRepository
* *
* @throws \App\Exceptions\Http\Connection\DaemonConnectionException * @throws \App\Exceptions\Http\Connection\DaemonConnectionException
*/ */
public function restore(Backup $backup, string $url = null, bool $truncate = false) public function restore(Backup $backup, ?string $url = null, bool $truncate = false): Response
{ {
Assert::isInstanceOf($this->server, Server::class); Assert::isInstanceOf($this->server, Server::class);
@ -73,7 +74,7 @@ class DaemonBackupRepository extends DaemonRepository
* *
* @throws \App\Exceptions\Http\Connection\DaemonConnectionException * @throws \App\Exceptions\Http\Connection\DaemonConnectionException
*/ */
public function delete(Backup $backup) public function delete(Backup $backup): Response
{ {
Assert::isInstanceOf($this->server, Server::class); Assert::isInstanceOf($this->server, Server::class);

View File

@ -5,6 +5,7 @@ namespace App\Repositories\Daemon;
use App\Models\Node; use App\Models\Node;
use GuzzleHttp\Exception\TransferException; use GuzzleHttp\Exception\TransferException;
use App\Exceptions\Http\Connection\DaemonConnectionException; use App\Exceptions\Http\Connection\DaemonConnectionException;
use Illuminate\Http\Client\Response;
class DaemonConfigurationRepository extends DaemonRepository class DaemonConfigurationRepository extends DaemonRepository
{ {
@ -13,7 +14,7 @@ class DaemonConfigurationRepository extends DaemonRepository
* *
* @throws \App\Exceptions\Http\Connection\DaemonConnectionException * @throws \App\Exceptions\Http\Connection\DaemonConnectionException
*/ */
public function getSystemInformation(?int $version = null, $connectTimeout = 5): array public function getSystemInformation(?int $version = null, int $connectTimeout = 5): array
{ {
try { try {
$response = $this $response = $this
@ -34,7 +35,7 @@ class DaemonConfigurationRepository extends DaemonRepository
* *
* @throws \App\Exceptions\Http\Connection\DaemonConnectionException * @throws \App\Exceptions\Http\Connection\DaemonConnectionException
*/ */
public function update(Node $node) public function update(Node $node): Response
{ {
try { try {
return $this->getHttpClient()->post( return $this->getHttpClient()->post(

View File

@ -3,6 +3,7 @@
namespace App\Repositories\Daemon; namespace App\Repositories\Daemon;
use Carbon\CarbonInterval; use Carbon\CarbonInterval;
use Illuminate\Http\Client\Response;
use Webmozart\Assert\Assert; use Webmozart\Assert\Assert;
use App\Models\Server; use App\Models\Server;
use GuzzleHttp\Exception\ClientException; use GuzzleHttp\Exception\ClientException;
@ -15,13 +16,13 @@ class DaemonFileRepository extends DaemonRepository
/** /**
* Return the contents of a given file. * Return the contents of a given file.
* *
* @param int|null $notLargerThan the maximum content length in bytes * @param int|null $notLargerThan the maximum content length in bytes
* *
* @throws \GuzzleHttp\Exception\TransferException * @throws \GuzzleHttp\Exception\TransferException
* @throws \App\Exceptions\Http\Server\FileSizeTooLargeException * @throws \App\Exceptions\Http\Server\FileSizeTooLargeException
* @throws \App\Exceptions\Http\Connection\DaemonConnectionException * @throws \App\Exceptions\Http\Connection\DaemonConnectionException
*/ */
public function getContent(string $path, int $notLargerThan = null): string public function getContent(string $path, ?int $notLargerThan = null): string
{ {
Assert::isInstanceOf($this->server, Server::class); Assert::isInstanceOf($this->server, Server::class);
@ -48,7 +49,7 @@ class DaemonFileRepository extends DaemonRepository
* *
* @throws \App\Exceptions\Http\Connection\DaemonConnectionException * @throws \App\Exceptions\Http\Connection\DaemonConnectionException
*/ */
public function putContent(string $path, string $content) public function putContent(string $path, string $content): Response
{ {
Assert::isInstanceOf($this->server, Server::class); Assert::isInstanceOf($this->server, Server::class);
@ -88,7 +89,7 @@ class DaemonFileRepository extends DaemonRepository
* *
* @throws \App\Exceptions\Http\Connection\DaemonConnectionException * @throws \App\Exceptions\Http\Connection\DaemonConnectionException
*/ */
public function createDirectory(string $name, string $path) public function createDirectory(string $name, string $path): Response
{ {
Assert::isInstanceOf($this->server, Server::class); Assert::isInstanceOf($this->server, Server::class);
@ -110,7 +111,7 @@ class DaemonFileRepository extends DaemonRepository
* *
* @throws \App\Exceptions\Http\Connection\DaemonConnectionException * @throws \App\Exceptions\Http\Connection\DaemonConnectionException
*/ */
public function renameFiles(?string $root, array $files) public function renameFiles(?string $root, array $files): Response
{ {
Assert::isInstanceOf($this->server, Server::class); Assert::isInstanceOf($this->server, Server::class);
@ -132,7 +133,7 @@ class DaemonFileRepository extends DaemonRepository
* *
* @throws \App\Exceptions\Http\Connection\DaemonConnectionException * @throws \App\Exceptions\Http\Connection\DaemonConnectionException
*/ */
public function copyFile(string $location) public function copyFile(string $location): Response
{ {
Assert::isInstanceOf($this->server, Server::class); Assert::isInstanceOf($this->server, Server::class);
@ -153,7 +154,7 @@ class DaemonFileRepository extends DaemonRepository
* *
* @throws \App\Exceptions\Http\Connection\DaemonConnectionException * @throws \App\Exceptions\Http\Connection\DaemonConnectionException
*/ */
public function deleteFiles(?string $root, array $files) public function deleteFiles(?string $root, array $files): Response
{ {
Assert::isInstanceOf($this->server, Server::class); Assert::isInstanceOf($this->server, Server::class);
@ -203,7 +204,7 @@ class DaemonFileRepository extends DaemonRepository
* *
* @throws \App\Exceptions\Http\Connection\DaemonConnectionException * @throws \App\Exceptions\Http\Connection\DaemonConnectionException
*/ */
public function decompressFile(?string $root, string $file) public function decompressFile(?string $root, string $file): Response
{ {
Assert::isInstanceOf($this->server, Server::class); Assert::isInstanceOf($this->server, Server::class);
@ -229,7 +230,7 @@ class DaemonFileRepository extends DaemonRepository
* *
* @throws \App\Exceptions\Http\Connection\DaemonConnectionException * @throws \App\Exceptions\Http\Connection\DaemonConnectionException
*/ */
public function chmodFiles(?string $root, array $files) public function chmodFiles(?string $root, array $files): Response
{ {
Assert::isInstanceOf($this->server, Server::class); Assert::isInstanceOf($this->server, Server::class);
@ -251,7 +252,7 @@ class DaemonFileRepository extends DaemonRepository
* *
* @throws \App\Exceptions\Http\Connection\DaemonConnectionException * @throws \App\Exceptions\Http\Connection\DaemonConnectionException
*/ */
public function pull(string $url, ?string $directory, array $params = []) public function pull(string $url, ?string $directory, array $params = []): Response
{ {
Assert::isInstanceOf($this->server, Server::class); Assert::isInstanceOf($this->server, Server::class);

View File

@ -2,6 +2,7 @@
namespace App\Repositories\Daemon; namespace App\Repositories\Daemon;
use Illuminate\Http\Client\Response;
use Webmozart\Assert\Assert; use Webmozart\Assert\Assert;
use App\Models\Server; use App\Models\Server;
use GuzzleHttp\Exception\TransferException; use GuzzleHttp\Exception\TransferException;
@ -14,7 +15,7 @@ class DaemonPowerRepository extends DaemonRepository
* *
* @throws \App\Exceptions\Http\Connection\DaemonConnectionException * @throws \App\Exceptions\Http\Connection\DaemonConnectionException
*/ */
public function send(string $action) public function send(string $action): Response
{ {
Assert::isInstanceOf($this->server, Server::class); Assert::isInstanceOf($this->server, Server::class);

View File

@ -17,8 +17,8 @@ class Username implements Rule
* *
* Allowed characters: a-z0-9_-. * Allowed characters: a-z0-9_-.
* *
* @param string $attribute * @param string $attribute
* @param mixed $value * @param mixed $value
*/ */
public function passes($attribute, $value): bool public function passes($attribute, $value): bool
{ {

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