Compare commits

..

1 Commits

Author SHA1 Message Date
github-actions[bot]
4f240ac00f ci(release): bump version 2024-10-27 00:54:17 +00:00
580 changed files with 23926 additions and 8995 deletions

View File

@@ -52,11 +52,11 @@ crond -L /var/log/crond -l 5
export SUPERVISORD_CADDY=false
## disable caddy if SKIP_CADDY is set
if [[ "${SKIP_CADDY:-}" == "true" ]]; then
echo "Starting PHP-FPM only"
else
if [[ -z $SKIP_CADDY ]]; then
echo "Starting PHP-FPM and Caddy"
export SUPERVISORD_CADDY=true
else
echo "Starting PHP-FPM only"
fi
chown -R www-data:www-data /pelican-data/.env /pelican-data/database

View File

@@ -87,7 +87,7 @@ jobs:
fail-fast: false
matrix:
php: [8.2, 8.3]
database: ["mariadb:10.6", "mariadb:10.11", "mariadb:11.4"]
database: ["mariadb:10.3", "mariadb:10.11", "mariadb:11.4"]
services:
database:
image: ${{ matrix.database }}

View File

@@ -26,8 +26,8 @@ class InfoCommand extends Command
{
$this->output->title('Version Information');
$this->table([], [
['Panel Version', $this->versionService->currentPanelVersion()],
['Latest Version', $this->versionService->latestPanelVersion()],
['Panel Version', $this->versionService->versionData()['version']],
['Latest Version', $this->versionService->getPanel()],
['Up-to-Date', $this->versionService->isLatestPanel() ? 'Yes' : $this->formatText('No', 'bg=red')],
], 'compact');

View File

@@ -65,7 +65,6 @@ class BulkPowerActionCommand extends Command
$bar = $this->output->createProgressBar($count);
$powerRepository = $this->powerRepository;
// @phpstan-ignore-next-line
$this->getQueryBuilder($servers, $nodes)->each(function (Server $server) use ($action, $powerRepository, &$bar) {
$bar->clear();

View File

@@ -9,7 +9,6 @@ use App\Console\Commands\Maintenance\PruneOrphanedBackupsCommand;
use App\Console\Commands\Schedule\ProcessRunnableCommand;
use App\Jobs\NodeStatistics;
use App\Models\ActivityLog;
use App\Models\Webhook;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Database\Console\PruneCommand;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
@@ -49,9 +48,5 @@ class Kernel extends ConsoleKernel
if (config('activity.prune_days')) {
$schedule->command(PruneCommand::class, ['--model' => [ActivityLog::class]])->daily();
}
if (config('panel.webhook.prune_days')) {
$schedule->command(PruneCommand::class, ['--model' => [Webhook::class]])->daily();
}
}
}

View File

@@ -1,98 +0,0 @@
<?php
namespace App\Enums;
use Filament\Support\Contracts\HasLabel;
enum EditorLanguages: string implements HasLabel
{
case plaintext = 'plaintext';
case abap = 'abap';
case apex = 'apex';
case azcali = 'azcali';
case bat = 'bat';
case bicep = 'bicep';
case cameligo = 'cameligo';
case coljure = 'coljure';
case coffeescript = 'coffeescript';
case c = 'c';
case cpp = 'cpp';
case csharp = 'csharp';
case csp = 'csp';
case css = 'css';
case cypher = 'cypher';
case dart = 'dart';
case dockerfile = 'dockerfile';
case ecl = 'ecl';
case elixir = 'elixir';
case flow9 = 'flow9';
case fsharp = 'fsharp';
case go = 'go';
case graphql = 'graphql';
case handlebars = 'handlebars';
case hcl = 'hcl';
case html = 'html';
case ini = 'ini';
case java = 'java';
case javascript = 'javascript';
case julia = 'julia';
case kotlin = 'kotlin';
case less = 'less';
case lexon = 'lexon';
case lua = 'lua';
case liquid = 'liquid';
case m3 = 'm3';
case markdown = 'markdown';
case mdx = 'mdx';
case mips = 'mips';
case msdax = 'msdax';
case mysql = 'mysql';
case objectivec = 'objective-c';
case pascal = 'pascal';
case pascaligo = 'pascaligo';
case perl = 'perl';
case pgsql = 'pgsql';
case php = 'php';
case pla = 'pla';
case postiats = 'postiats';
case powerquery = 'powerquery';
case powershell = 'powershell';
case proto = 'proto';
case pug = 'pug';
case python = 'python';
case qsharp = 'qsharp';
case r = 'r';
case razor = 'razor';
case redis = 'redis';
case redshift = 'redshift';
case restructuredtext = 'restructuredtext';
case ruby = 'ruby';
case rust = 'rust';
case sb = 'sb';
case scala = 'scala';
case scheme = 'scheme';
case scss = 'scss';
case shell = 'shell';
case sol = 'sol';
case aes = 'aes';
case sparql = 'sparql';
case sql = 'sql';
case st = 'st';
case swift = 'swift';
case systemverilog = 'systemverilog';
case verilog = 'verilog';
case tcl = 'tcl';
case twig = 'twig';
case typescript = 'typescript';
case typespec = 'typespec';
case vb = 'vb';
case wgsl = 'wgsl';
case xml = 'xml';
case yaml = 'yaml';
case json = 'json';
public function getLabel(): ?string
{
return $this->name;
}
}

View File

@@ -13,5 +13,4 @@ enum RolePermissionModels: string
case Role = 'role';
case Server = 'server';
case User = 'user';
case Webhook = 'webhook';
}

View File

@@ -8,7 +8,9 @@ use Illuminate\Database\Eloquent\Model;
class ActivityLogged extends Event
{
public function __construct(public ActivityLog $model) {}
public function __construct(public ActivityLog $model)
{
}
public function is(string $event): bool
{

View File

@@ -7,5 +7,7 @@ use App\Events\Event;
class DirectLogin extends Event
{
public function __construct(public User $user, public bool $remember) {}
public function __construct(public User $user, public bool $remember)
{
}
}

View File

@@ -12,5 +12,7 @@ class FailedCaptcha extends Event
/**
* Create a new event instance.
*/
public function __construct(public string $ip, public ?string $message) {}
public function __construct(public string $ip, public string $domain)
{
}
}

View File

@@ -12,5 +12,7 @@ class FailedPasswordReset extends Event
/**
* Create a new event instance.
*/
public function __construct(public string $ip, public string $email) {}
public function __construct(public string $ip, public string $email)
{
}
}

View File

@@ -7,5 +7,7 @@ use App\Events\Event;
class ProvidedAuthenticationToken extends Event
{
public function __construct(public User $user, public bool $recovery = false) {}
public function __construct(public User $user, public bool $recovery = false)
{
}
}

View File

@@ -2,4 +2,6 @@
namespace App\Events;
abstract class Event {}
abstract class Event
{
}

View File

@@ -13,5 +13,7 @@ class Installed extends Event
/**
* Create a new event instance.
*/
public function __construct(public Server $server) {}
public function __construct(public Server $server)
{
}
}

View File

@@ -0,0 +1,7 @@
<?php
namespace App\Exceptions;
class AccountNotFoundException extends \Exception
{
}

View File

@@ -0,0 +1,7 @@
<?php
namespace App\Exceptions;
class AutoDeploymentException extends \Exception
{
}

View File

@@ -10,6 +10,7 @@ use Illuminate\Http\Request;
use Psr\Log\LoggerInterface;
use Illuminate\Http\Response;
use Illuminate\Container\Container;
use Prologue\Alerts\AlertsMessageBag;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
class DisplayException extends PanelException implements HttpExceptionInterface
@@ -66,6 +67,9 @@ class DisplayException extends PanelException implements HttpExceptionInterface
return response()->json(Handler::toArray($this), $this->getStatusCode(), $this->getHeaders());
}
// @phpstan-ignore-next-line
app(AlertsMessageBag::class)->danger($this->getMessage())->flash();
return redirect()->back()->withInput();
}

View File

@@ -4,4 +4,6 @@ namespace App\Exceptions\Http\Base;
use App\Exceptions\DisplayException;
class InvalidPasswordProvidedException extends DisplayException {}
class InvalidPasswordProvidedException extends DisplayException
{
}

View File

@@ -7,6 +7,9 @@ use GuzzleHttp\Exception\GuzzleException;
use App\Exceptions\DisplayException;
use Illuminate\Support\Facades\Context;
/**
* @method \GuzzleHttp\Exception\GuzzleException getPrevious()
*/
class DaemonConnectionException extends DisplayException
{
private int $statusCode = Response::HTTP_GATEWAY_TIMEOUT;

View File

@@ -0,0 +1,9 @@
<?php
namespace App\Exceptions\Http\Server;
use App\Exceptions\DisplayException;
class FileTypeNotEditableException extends DisplayException
{
}

View File

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

View File

@@ -0,0 +1,9 @@
<?php
namespace App\Exceptions\Repository\Daemon;
use App\Exceptions\Repository\RepositoryException;
class InvalidPowerSignalException extends RepositoryException
{
}

View File

@@ -4,4 +4,6 @@ namespace App\Exceptions\Repository;
use App\Exceptions\DisplayException;
class DuplicateDatabaseNameException extends DisplayException {}
class DuplicateDatabaseNameException extends DisplayException
{
}

View File

@@ -0,0 +1,9 @@
<?php
namespace App\Exceptions\Repository;
use App\Exceptions\PanelException;
class RepositoryException extends PanelException
{
}

View File

@@ -0,0 +1,9 @@
<?php
namespace App\Exceptions\Service\Allocation;
use App\Exceptions\PanelException;
class AllocationDoesNotBelongToServerException extends PanelException
{
}

View File

@@ -4,4 +4,6 @@ namespace App\Exceptions\Service\Allocation;
use App\Exceptions\DisplayException;
class ServerUsingAllocationException extends DisplayException {}
class ServerUsingAllocationException extends DisplayException
{
}

View File

@@ -4,4 +4,6 @@ namespace App\Exceptions\Service\Deployment;
use App\Exceptions\DisplayException;
class NoViableAllocationException extends DisplayException {}
class NoViableAllocationException extends DisplayException
{
}

View File

@@ -0,0 +1,9 @@
<?php
namespace App\Exceptions\Service\Egg;
use App\Exceptions\DisplayException;
class BadJsonFormatException extends DisplayException
{
}

View File

@@ -4,4 +4,6 @@ namespace App\Exceptions\Service\Egg;
use App\Exceptions\DisplayException;
class HasChildrenException extends DisplayException {}
class HasChildrenException extends DisplayException
{
}

View File

@@ -0,0 +1,9 @@
<?php
namespace App\Exceptions\Service\Egg;
use App\Exceptions\DisplayException;
class NoParentConfigurationFoundException extends DisplayException
{
}

View File

@@ -4,4 +4,6 @@ namespace App\Exceptions\Service\Egg\Variable;
use App\Exceptions\DisplayException;
class BadValidationRuleException extends DisplayException {}
class BadValidationRuleException extends DisplayException
{
}

View File

@@ -4,4 +4,6 @@ namespace App\Exceptions\Service\Egg\Variable;
use App\Exceptions\DisplayException;
class ReservedVariableNameException extends DisplayException {}
class ReservedVariableNameException extends DisplayException
{
}

View File

@@ -4,4 +4,6 @@ namespace App\Exceptions\Service;
use App\Exceptions\DisplayException;
class InvalidFileUploadException extends DisplayException {}
class InvalidFileUploadException extends DisplayException
{
}

View File

@@ -4,4 +4,6 @@ namespace App\Exceptions\Service\Node;
use App\Exceptions\DisplayException;
class ConfigurationNotPersistedException extends DisplayException {}
class ConfigurationNotPersistedException extends DisplayException
{
}

View File

@@ -0,0 +1,9 @@
<?php
namespace App\Exceptions\Service\Schedule\Task;
use App\Exceptions\DisplayException;
class TaskIntervalTooLongException extends DisplayException
{
}

View File

@@ -0,0 +1,9 @@
<?php
namespace App\Exceptions\Service\Server;
use App\Exceptions\PanelException;
class RequiredVariableMissingException extends PanelException
{
}

View File

@@ -4,4 +4,6 @@ namespace App\Exceptions\Service\Subuser;
use App\Exceptions\DisplayException;
class ServerSubuserExistsException extends DisplayException {}
class ServerSubuserExistsException extends DisplayException
{
}

View File

@@ -4,4 +4,6 @@ namespace App\Exceptions\Service\Subuser;
use App\Exceptions\DisplayException;
class UserIsServerOwnerException extends DisplayException {}
class UserIsServerOwnerException extends DisplayException
{
}

View File

@@ -0,0 +1,9 @@
<?php
namespace App\Exceptions\Transformer;
use App\Exceptions\PanelException;
class InvalidTransformerLevelException extends PanelException
{
}

View File

@@ -27,7 +27,9 @@ class BackupManager
/**
* BackupManager constructor.
*/
public function __construct(protected Application $app) {}
public function __construct(protected Application $app)
{
}
/**
* Returns a backup adapter instance.

View File

@@ -0,0 +1,13 @@
<?php
namespace App\Extensions\Facades;
use Illuminate\Support\Facades\Facade;
class Theme extends Facade
{
protected static function getFacadeAccessor(): string
{
return 'extensions.themes';
}
}

View File

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

14
app/Facades/LogBatch.php Normal file
View File

@@ -0,0 +1,14 @@
<?php
namespace App\Facades;
use Illuminate\Support\Facades\Facade;
use App\Services\Activity\ActivityLogBatchService;
class LogBatch extends Facade
{
protected static function getFacadeAccessor(): string
{
return ActivityLogBatchService::class;
}
}

View File

@@ -1,32 +0,0 @@
<?php
namespace App\Filament\Admin\Resources;
use App\Filament\Admin\Resources\WebhookResource\Pages;
use App\Models\WebhookConfiguration;
use Filament\Resources\Resource;
class WebhookResource extends Resource
{
protected static ?string $model = WebhookConfiguration::class;
protected static ?string $navigationIcon = 'tabler-webhook';
protected static ?string $navigationGroup = 'Advanced';
protected static ?string $label = 'Webhooks';
public static function getNavigationBadge(): ?string
{
return static::getModel()::count() ?: null;
}
public static function getPages(): array
{
return [
'index' => Pages\ListWebhookConfigurations::route('/'),
'create' => Pages\CreateWebhookConfiguration::route('/create'),
'edit' => Pages\EditWebhookConfiguration::route('/{record}/edit'),
];
}
}

View File

@@ -1,36 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\WebhookResource\Pages;
use App\Filament\Admin\Resources\WebhookResource;
use App\Models\WebhookConfiguration;
use Filament\Forms\Components\CheckboxList;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Form;
use Filament\Resources\Pages\CreateRecord;
class CreateWebhookConfiguration extends CreateRecord
{
protected static string $resource = WebhookResource::class;
public function form(Form $form): Form
{
return $form
->schema([
TextInput::make('endpoint')
->activeUrl()
->required(),
TextInput::make('description')
->required(),
CheckboxList::make('events')
->lazy()
->options(fn () => WebhookConfiguration::filamentCheckboxList())
->searchable()
->bulkToggleable()
->columns(3)
->columnSpanFull()
->gridDirection('row')
->required(),
]);
}
}

View File

@@ -1,57 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\WebhookResource\Pages;
use App\Models\WebhookConfiguration;
use App\Filament\Admin\Resources\WebhookResource;
use Filament\Actions;
use Filament\Forms\Components\CheckboxList;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Form;
use Filament\Resources\Pages\EditRecord;
class EditWebhookConfiguration extends EditRecord
{
protected static string $resource = WebhookResource::class;
public function form(Form $form): Form
{
return $form
->schema([
TextInput::make('endpoint')
->label('Endpoint')
->activeUrl()
->required(),
TextInput::make('description')
->label('Description')
->required(),
CheckboxList::make('events')
->label('Events')
->lazy()
->options(fn () => WebhookConfiguration::filamentCheckboxList())
->searchable()
->bulkToggleable()
->columns(3)
->columnSpanFull()
->gridDirection('row')
->required(),
]);
}
protected function getFormActions(): array
{
return [];
}
protected function getHeaderActions(): array
{
return [
Actions\DeleteAction::make()
->label('Delete')
->modalHeading('Are you sure you want to delete this?')
->modalDescription('')
->modalSubmitActionLabel('Delete'),
$this->getSaveFormAction()->formId('form'),
];
}
}

View File

@@ -1,52 +0,0 @@
<?php
namespace App\Filament\Admin\Resources\WebhookResource\Pages;
use App\Filament\Admin\Resources\WebhookResource;
use App\Models\WebhookConfiguration;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;
use Filament\Tables\Actions\CreateAction;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
use Filament\Tables\Actions\EditAction;
use Filament\Tables\Actions\DeleteAction;
class ListWebhookConfigurations extends ListRecords
{
protected static string $resource = WebhookResource::class;
public function table(Table $table): Table
{
return $table
->columns([
TextColumn::make('description')
->label('Description'),
TextColumn::make('endpoint')
->label('Endpoint'),
])
->actions([
DeleteAction::make()
->label('Delete'),
EditAction::make()
->label('Edit'),
])
->emptyStateIcon('tabler-webhook')
->emptyStateDescription('')
->emptyStateHeading('No Webhooks')
->emptyStateActions([
CreateAction::make('create')
->label('Create Webhook')
->button(),
]);
}
protected function getHeaderActions(): array
{
return [
Actions\CreateAction::make()
->label('Create Webhook')
->hidden(fn () => WebhookConfiguration::count() <= 0),
];
}
}

View File

@@ -1,28 +0,0 @@
<?php
namespace App\Filament\App\Resources;
use App\Filament\App\Resources\ServerResource\Pages;
use App\Models\Server;
use Filament\Resources\Resource;
class ServerResource extends Resource
{
protected static ?string $model = Server::class;
protected static ?string $slug = '/';
protected static bool $shouldRegisterNavigation = false;
public static function canAccess(): bool
{
return true;
}
public static function getPages(): array
{
return [
'index' => Pages\ListServers::route('/'),
];
}
}

View File

@@ -1,124 +0,0 @@
<?php
namespace App\Filament\App\Resources\ServerResource\Pages;
use App\Filament\App\Resources\ServerResource;
use App\Filament\Server\Pages\Console;
use App\Models\Server;
use App\Tables\Columns\ServerEntryColumn;
use Carbon\CarbonInterface;
use Filament\Resources\Pages\ListRecords;
use Filament\Tables\Columns\Layout\Stack;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Filters\TernaryFilter;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Arr;
use Illuminate\Support\Number;
class ListServers extends ListRecords
{
protected static string $resource = ServerResource::class;
public function table(Table $table): Table
{
$baseQuery = auth()->user()->can('viewList server') ? Server::query() : auth()->user()->accessibleServers();
return $table
->paginated(false)
->query(fn () => $baseQuery)
->poll('15s')
->columns([
Stack::make([
ServerEntryColumn::make('server_entry')
->searchable(['name']),
]),
])
->contentGrid([
'default' => 1,
'xl' => 2,
])
->recordUrl(fn (Server $server) => Console::getUrl(panel: 'server', tenant: $server))
->emptyStateIcon('tabler-brand-docker')
->emptyStateDescription('')
->emptyStateHeading('You don\'t have access to any servers!')
->persistFiltersInSession()
->filters([
TernaryFilter::make('only_my_servers')
->label('Owned by')
->placeholder('All servers')
->trueLabel('My Servers')
->falseLabel('Others\' Servers')
->default()
->queries(
true: fn (Builder $query) => $query->where('owner_id', auth()->user()->id),
false: fn (Builder $query) => $query->whereNot('owner_id', auth()->user()->id),
blank: fn (Builder $query) => $query,
),
SelectFilter::make('egg')
->relationship('egg', 'name', fn (Builder $query) => $query->whereIn('id', $baseQuery->pluck('egg_id')))
->searchable()
->preload(),
]);
}
// @phpstan-ignore-next-line
private function uptime(Server $server): string
{
$uptime = Arr::get($server->resources(), 'uptime', 0);
if ($uptime === 0) {
return 'Offline';
}
return now()->subMillis($uptime)->diffForHumans(syntax: CarbonInterface::DIFF_ABSOLUTE, short: true, parts: 2);
}
// @phpstan-ignore-next-line
private function cpu(Server $server): string
{
$cpu = Number::format(Arr::get($server->resources(), 'cpu_absolute', 0), maxPrecision: 2, locale: auth()->user()->language) . '%';
$max = Number::format($server->cpu, locale: auth()->user()->language) . '%';
return $cpu . ($server->cpu > 0 ? ' Of ' . $max : '');
}
// @phpstan-ignore-next-line
private function memory(Server $server): string
{
$latestMemoryUsed = Arr::get($server->resources(), 'memory_bytes', 0);
$totalMemory = Arr::get($server->resources(), 'memory_limit_bytes', 0);
$used = config('panel.use_binary_prefix')
? Number::format($latestMemoryUsed / 1024 / 1024 / 1024, maxPrecision: 2, locale: auth()->user()->language) .' GiB'
: Number::format($latestMemoryUsed / 1000 / 1000 / 1000, maxPrecision: 2, locale: auth()->user()->language) . ' GB';
if ($totalMemory === 0) {
$total = config('panel.use_binary_prefix')
? Number::format($server->memory / 1024, maxPrecision: 2, locale: auth()->user()->language) .' GiB'
: Number::format($server->memory / 1000, maxPrecision: 2, locale: auth()->user()->language) . ' GB';
} else {
$total = config('panel.use_binary_prefix')
? Number::format($totalMemory / 1024 / 1024 / 1024, maxPrecision: 2, locale: auth()->user()->language) .' GiB'
: Number::format($totalMemory / 1000 / 1000 / 1000, maxPrecision: 2, locale: auth()->user()->language) . ' GB';
}
return $used . ($server->memory > 0 ? ' Of ' . $total : '');
}
// @phpstan-ignore-next-line
private function disk(Server $server): string
{
$usedDisk = Arr::get($server->resources(), 'disk_bytes', 0);
$used = config('panel.use_binary_prefix')
? Number::format($usedDisk / 1024 / 1024 / 1024, maxPrecision: 2, locale: auth()->user()->language) .' GiB'
: Number::format($usedDisk / 1000 / 1000 / 1000, maxPrecision: 2, locale: auth()->user()->language) . ' GB';
$total = config('panel.use_binary_prefix')
? Number::format($server->disk / 1024, maxPrecision: 2, locale: auth()->user()->language) .' GiB'
: Number::format($server->disk / 1000, maxPrecision: 2, locale: auth()->user()->language) . ' GB';
return $used . ($server->disk > 0 ? ' Of ' . $total : '');
}
}

View File

@@ -1,84 +0,0 @@
<?php
namespace App\Filament\Pages\Auth;
use Coderflex\FilamentTurnstile\Forms\Components\Turnstile;
use Filament\Forms\Components\Actions;
use Filament\Forms\Components\Actions\Action;
use Filament\Forms\Components\Component;
use Filament\Forms\Components\TextInput;
use Filament\Pages\Auth\Login as BaseLogin;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
class Login extends BaseLogin
{
protected function getForms(): array
{
return [
'form' => $this->form(
$this->makeForm()
->schema([
$this->getLoginFormComponent(),
$this->getPasswordFormComponent(),
$this->getRememberFormComponent(),
$this->getOAuthFormComponent(),
Turnstile::make('captcha')
->hidden(!config('turnstile.turnstile_enabled'))
->validationMessages([
'required' => config('turnstile.error_messages.turnstile_check_message'),
]),
])
->statePath('data'),
),
];
}
protected function throwFailureValidationException(): never
{
$this->dispatch('reset-captcha');
throw ValidationException::withMessages([
'data.login' => __('filament-panels::pages/auth/login.messages.failed'),
]);
}
protected function getLoginFormComponent(): Component
{
return TextInput::make('login')
->label('Login')
->required()
->autocomplete()
->autofocus()
->extraInputAttributes(['tabindex' => 1]);
}
protected function getOAuthFormComponent(): Component
{
$actions = [];
foreach (config('auth.oauth') as $name => $data) {
if (!$data['enabled']) {
continue;
}
$actions[] = Action::make("oauth_$name")
->label(Str::title($name))
->icon($data['icon'])
->color($data['color'])
->url(route('auth.oauth.redirect', ['driver' => $name], false));
}
return Actions::make($actions);
}
protected function getCredentialsFromFormData(array $data): array
{
$loginType = filter_var($data['login'], FILTER_VALIDATE_EMAIL) ? 'email' : 'username';
return [
$loginType => mb_strtolower($data['login']),
'password' => $data['password'],
];
}
}

View File

@@ -1,9 +1,8 @@
<?php
namespace App\Filament\Admin\Pages;
namespace App\Filament\Pages;
use App\Filament\Admin\Resources\NodeResource\Pages\CreateNode;
use App\Filament\Admin\Resources\NodeResource\Pages\ListNodes;
use App\Filament\Resources\NodeResource\Pages\ListNodes;
use App\Models\Egg;
use App\Models\Node;
use App\Models\Server;
@@ -40,8 +39,8 @@ class Dashboard extends Page
{
return [
'inDevelopment' => config('app.version') === 'canary',
'version' => $this->softwareVersionService->currentPanelVersion(),
'latestVersion' => $this->softwareVersionService->latestPanelVersion(),
'version' => $this->softwareVersionService->versionData()['version'],
'latestVersion' => $this->softwareVersionService->getPanel(),
'isLatest' => $this->softwareVersionService->isLatestPanel(),
'eggsCount' => Egg::query()->count(),
'nodesList' => ListNodes::getUrl(),
@@ -66,13 +65,13 @@ class Dashboard extends Page
CreateAction::make()
->label(trans('dashboard/index.sections.intro-first-node.button_label'))
->icon('tabler-server-2')
->url(CreateNode::getUrl()),
->url(route('filament.admin.resources.nodes.create')),
],
'supportActions' => [
CreateAction::make()
->label(trans('dashboard/index.sections.intro-support.button_donate'))
->icon('tabler-cash')
->url('https://pelican.dev/donate', true)
->url($this->softwareVersionService->getDonations(), true)
->color('success'),
],
'helpActions' => [

View File

@@ -1,14 +1,14 @@
<?php
namespace App\Livewire\Installer;
namespace App\Filament\Pages\Installer;
use App\Filament\Admin\Pages\Dashboard;
use App\Livewire\Installer\Steps\CacheStep;
use App\Livewire\Installer\Steps\DatabaseStep;
use App\Livewire\Installer\Steps\EnvironmentStep;
use App\Livewire\Installer\Steps\QueueStep;
use App\Livewire\Installer\Steps\RequirementsStep;
use App\Livewire\Installer\Steps\SessionStep;
use App\Filament\Pages\Dashboard;
use App\Filament\Pages\Installer\Steps\AdminUserStep;
use App\Filament\Pages\Installer\Steps\CompletedStep;
use App\Filament\Pages\Installer\Steps\DatabaseStep;
use App\Filament\Pages\Installer\Steps\EnvironmentStep;
use App\Filament\Pages\Installer\Steps\RedisStep;
use App\Filament\Pages\Installer\Steps\RequirementsStep;
use App\Models\User;
use App\Services\Users\UserCreationService;
use App\Traits\CheckMigrationsTrait;
@@ -19,10 +19,13 @@ use Filament\Forms\Components\Wizard;
use Filament\Forms\Concerns\InteractsWithForms;
use Filament\Forms\Contracts\HasForms;
use Filament\Forms\Form;
use Filament\Forms\Get;
use Filament\Notifications\Notification;
use Filament\Pages\SimplePage;
use Filament\Support\Enums\MaxWidth;
use Filament\Support\Exceptions\Halt;
use Illuminate\Http\RedirectResponse;
use Illuminate\Routing\Redirector;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\HtmlString;
@@ -40,6 +43,8 @@ class PanelInstaller extends SimplePage implements HasForms
protected static string $view = 'filament.pages.installer';
private User $user;
public function getMaxWidth(): MaxWidth|string
{
return MaxWidth::SevenExtraLarge;
@@ -65,9 +70,10 @@ class PanelInstaller extends SimplePage implements HasForms
RequirementsStep::make(),
EnvironmentStep::make($this),
DatabaseStep::make($this),
CacheStep::make($this),
QueueStep::make($this),
SessionStep::make(),
RedisStep::make($this)
->hidden(fn (Get $get) => $get('env_general.SESSION_DRIVER') != 'redis' && $get('env_general.QUEUE_CONNECTION') != 'redis' && $get('env_general.CACHE_STORE') != 'redis'),
AdminUserStep::make($this),
CompletedStep::make(),
])
->persistStepInQueryString()
->nextAction(fn (Action $action) => $action->keyBindings('enter'))
@@ -89,33 +95,23 @@ class PanelInstaller extends SimplePage implements HasForms
return 'data';
}
public function submit(UserCreationService $userCreationService): void
public function submit(): Redirector|RedirectResponse
{
try {
// Disable installer
$this->writeToEnvironment(['APP_INSTALLED' => 'true']);
// Disable installer
$this->writeToEnvironment(['APP_INSTALLED' => 'true']);
// Run migrations
$this->runMigrations();
// Login user
$this->user ??= User::all()->filter(fn ($user) => $user->isRootAdmin())->first();
auth()->guard()->login($this->user, true);
// Create admin user & login
$user = $this->createAdminUser($userCreationService);
auth()->guard()->login($user, true);
// Write session data at the very end to avoid "page expired" errors
$this->writeToEnv('env_session');
// Redirect to admin panel
$this->redirect(Dashboard::getUrl());
} catch (Halt) {
}
// Redirect to admin panel
return redirect(Dashboard::getUrl());
}
public function writeToEnv(string $key): void
{
try {
$variables = array_get($this->data, $key);
$variables = array_filter($variables); // Filter array to remove NULL values
$this->writeToEnvironment($variables);
} catch (Exception $exception) {
report($exception);
@@ -133,12 +129,13 @@ class PanelInstaller extends SimplePage implements HasForms
Artisan::call('config:clear');
}
public function runMigrations(): void
public function runMigrations(string $driver): void
{
try {
Artisan::call('migrate', [
'--force' => true,
'--seed' => true,
'--database' => $driver,
]);
} catch (Exception $exception) {
report($exception);
@@ -164,13 +161,12 @@ class PanelInstaller extends SimplePage implements HasForms
}
}
public function createAdminUser(UserCreationService $userCreationService): User
public function createAdminUser(UserCreationService $userCreationService): void
{
try {
$userData = array_get($this->data, 'user');
$userData['root_admin'] = true;
return $userCreationService->handle($userData);
$this->user = $userCreationService->handle($userData);
} catch (Exception $exception) {
report($exception);

View File

@@ -0,0 +1,34 @@
<?php
namespace App\Filament\Pages\Installer\Steps;
use App\Filament\Pages\Installer\PanelInstaller;
use App\Services\Users\UserCreationService;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Wizard\Step;
class AdminUserStep
{
public static function make(PanelInstaller $installer): Step
{
return Step::make('user')
->label('Admin User')
->schema([
TextInput::make('user.email')
->label('Admin E-Mail')
->required()
->email()
->placeholder('admin@example.com'),
TextInput::make('user.username')
->label('Admin Username')
->required()
->placeholder('admin'),
TextInput::make('user.password')
->label('Admin Password')
->required()
->password()
->revealable(),
])
->afterValidation(fn (UserCreationService $service) => $installer->createAdminUser($service));
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace App\Filament\Pages\Installer\Steps;
use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Wizard\Step;
use Illuminate\Support\HtmlString;
use Webbingbrasil\FilamentCopyActions\Forms\Actions\CopyAction;
class CompletedStep
{
public static function make(): Step
{
return Step::make('complete')
->label('Setup complete')
->schema([
Placeholder::make('')
->content(new HtmlString('The setup is nearly complete!<br>As last step you need to create a new cronjob that runs every minute to process specific tasks, such as session cleanup and scheduled tasks, and also create a queue worker.')),
TextInput::make('crontab')
->label(new HtmlString('Run the following command to setup your crontab. Note that <code>www-data</code> is your webserver user. On some systems this username might be different!'))
->disabled()
->hintAction(CopyAction::make())
->default('(crontab -l -u www-data 2>/dev/null; echo "* * * * * php ' . base_path() . '/artisan schedule:run >> /dev/null 2>&1") | crontab -u www-data -'),
TextInput::make('queueService')
->label(new HtmlString('To setup the queue worker service you simply have to run the following command.'))
->disabled()
->hintAction(CopyAction::make())
->default('sudo php ' . base_path() . '/artisan p:environment:queue-service'),
Placeholder::make('')
->content('After you finished these two last tasks you can click on "Finish" and use your new panel! Have fun!'),
]);
}
}

View File

@@ -1,102 +1,74 @@
<?php
namespace App\Livewire\Installer\Steps;
namespace App\Filament\Pages\Installer\Steps;
use App\Livewire\Installer\PanelInstaller;
use App\Filament\Pages\Installer\PanelInstaller;
use Exception;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\ToggleButtons;
use Filament\Forms\Components\Wizard\Step;
use Filament\Forms\Get;
use Filament\Forms\Set;
use Filament\Notifications\Notification;
use Filament\Support\Exceptions\Halt;
use Illuminate\Support\Facades\DB;
class DatabaseStep
{
public const DATABASE_DRIVERS = [
'sqlite' => 'SQLite',
'mariadb' => 'MariaDB',
'mysql' => 'MySQL',
];
public static function make(PanelInstaller $installer): Step
{
return Step::make('database')
->label('Database')
->columns()
->schema([
ToggleButtons::make('env_database.DB_CONNECTION')
->label('Database Driver')
->hintIcon('tabler-question-mark')
->hintIconTooltip('The driver used for the panel database. We recommend "SQLite".')
->required()
->inline()
->options(self::DATABASE_DRIVERS)
->default(config('database.default'))
->live()
->afterStateUpdated(function ($state, Set $set, Get $get) {
$set('env_database.DB_DATABASE', $state === 'sqlite' ? 'database.sqlite' : 'panel');
if ($state === 'sqlite') {
$set('env_database.DB_HOST', null);
$set('env_database.DB_PORT', null);
$set('env_database.DB_USERNAME', null);
$set('env_database.DB_PASSWORD', null);
} else {
$set('env_database.DB_HOST', $get('env_database.DB_HOST') ?? '127.0.0.1');
$set('env_database.DB_PORT', $get('env_database.DB_PORT') ?? '3306');
$set('env_database.DB_USERNAME', $get('env_database.DB_USERNAME') ?? 'pelican');
}
}),
TextInput::make('env_database.DB_DATABASE')
->label(fn (Get $get) => $get('env_database.DB_CONNECTION') === 'sqlite' ? 'Database Path' : 'Database Name')
->placeholder(fn (Get $get) => $get('env_database.DB_CONNECTION') === 'sqlite' ? 'database.sqlite' : 'panel')
->label(fn (Get $get) => $get('env_general.DB_CONNECTION') === 'sqlite' ? 'Database Path' : 'Database Name')
->columnSpanFull()
->hintIcon('tabler-question-mark')
->hintIconTooltip(fn (Get $get) => $get('env_database.DB_CONNECTION') === 'sqlite' ? 'The path of your .sqlite file relative to the database folder.' : 'The name of the panel database.')
->hintIconTooltip(fn (Get $get) => $get('env_general.DB_CONNECTION') === 'sqlite' ? 'The path of your .sqlite file relative to the database folder.' : 'The name of the panel database.')
->required()
->default('database.sqlite'),
->default(fn (Get $get) => env('DB_DATABASE', $get('env_general.DB_CONNECTION') === 'sqlite' ? 'database.sqlite' : 'panel')),
TextInput::make('env_database.DB_HOST')
->label('Database Host')
->placeholder('127.0.0.1')
->hintIcon('tabler-question-mark')
->hintIconTooltip('The host of your database. Make sure it is reachable.')
->required(fn (Get $get) => $get('env_database.DB_CONNECTION') !== 'sqlite')
->hidden(fn (Get $get) => $get('env_database.DB_CONNECTION') === 'sqlite'),
->required(fn (Get $get) => $get('env_general.DB_CONNECTION') !== 'sqlite')
->default(fn (Get $get) => $get('env_general.DB_CONNECTION') !== 'sqlite' ? env('DB_HOST', '127.0.0.1') : null)
->hidden(fn (Get $get) => $get('env_general.DB_CONNECTION') === 'sqlite'),
TextInput::make('env_database.DB_PORT')
->label('Database Port')
->placeholder('3306')
->hintIcon('tabler-question-mark')
->hintIconTooltip('The port of your database.')
->required(fn (Get $get) => $get('env_general.DB_CONNECTION') !== 'sqlite')
->numeric()
->minValue(1)
->maxValue(65535)
->required(fn (Get $get) => $get('env_database.DB_CONNECTION') !== 'sqlite')
->hidden(fn (Get $get) => $get('env_database.DB_CONNECTION') === 'sqlite'),
->default(fn (Get $get) => $get('env_general.DB_CONNECTION') !== 'sqlite' ? env('DB_PORT', 3306) : null)
->hidden(fn (Get $get) => $get('env_general.DB_CONNECTION') === 'sqlite'),
TextInput::make('env_database.DB_USERNAME')
->label('Database Username')
->placeholder('pelican')
->hintIcon('tabler-question-mark')
->hintIconTooltip('The name of your database user.')
->required(fn (Get $get) => $get('env_database.DB_CONNECTION') !== 'sqlite')
->hidden(fn (Get $get) => $get('env_database.DB_CONNECTION') === 'sqlite'),
->required(fn (Get $get) => $get('env_general.DB_CONNECTION') !== 'sqlite')
->default(fn (Get $get) => $get('env_general.DB_CONNECTION') !== 'sqlite' ? env('DB_USERNAME', 'pelican') : null)
->hidden(fn (Get $get) => $get('env_general.DB_CONNECTION') === 'sqlite'),
TextInput::make('env_database.DB_PASSWORD')
->label('Database Password')
->hintIcon('tabler-question-mark')
->hintIconTooltip('The password of your database user. Can be empty.')
->password()
->revealable()
->hidden(fn (Get $get) => $get('env_database.DB_CONNECTION') === 'sqlite'),
->default(fn (Get $get) => $get('env_general.DB_CONNECTION') !== 'sqlite' ? env('DB_PASSWORD') : null)
->hidden(fn (Get $get) => $get('env_general.DB_CONNECTION') === 'sqlite'),
])
->afterValidation(function (Get $get) use ($installer) {
$driver = $get('env_database.DB_CONNECTION');
$driver = $get('env_general.DB_CONNECTION');
if (!self::testConnection($driver, $get('env_database.DB_HOST'), $get('env_database.DB_PORT'), $get('env_database.DB_DATABASE'), $get('env_database.DB_USERNAME'), $get('env_database.DB_PASSWORD'))) {
throw new Halt('Database connection failed');
}
$installer->writeToEnv('env_database');
$installer->runMigrations($driver);
});
}

View File

@@ -0,0 +1,98 @@
<?php
namespace App\Filament\Pages\Installer\Steps;
use App\Filament\Pages\Installer\PanelInstaller;
use App\Traits\EnvironmentWriterTrait;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\ToggleButtons;
use Filament\Forms\Components\Wizard\Step;
use Filament\Forms\Set;
class EnvironmentStep
{
use EnvironmentWriterTrait;
public const CACHE_DRIVERS = [
'file' => 'Filesystem',
'redis' => 'Redis',
];
public const SESSION_DRIVERS = [
'file' => 'Filesystem',
'database' => 'Database',
'cookie' => 'Cookie',
'redis' => 'Redis',
];
public const QUEUE_DRIVERS = [
'database' => 'Database',
'sync' => 'Sync',
'redis' => 'Redis',
];
public const DATABASE_DRIVERS = [
'sqlite' => 'SQLite',
'mariadb' => 'MariaDB',
'mysql' => 'MySQL',
];
public static function make(PanelInstaller $installer): Step
{
return Step::make('environment')
->label('Environment')
->columns()
->schema([
TextInput::make('env_general.APP_NAME')
->label('App Name')
->hintIcon('tabler-question-mark')
->hintIconTooltip('This will be the Name of your Panel.')
->required()
->default(config('app.name')),
TextInput::make('env_general.APP_URL')
->label('App URL')
->hintIcon('tabler-question-mark')
->hintIconTooltip('This will be the URL you access your Panel from.')
->required()
->default(url(''))
->live()
->afterStateUpdated(fn ($state, Set $set) => $set('env_general.SESSION_SECURE_COOKIE', str_starts_with($state, 'https://') ? 'true' : 'false')),
TextInput::make('env_general.SESSION_SECURE_COOKIE')
->hidden()
->default(str_starts_with(url(''), 'https://') ? 'true' : 'false'),
ToggleButtons::make('env_general.CACHE_STORE')
->label('Cache Driver')
->hintIcon('tabler-question-mark')
->hintIconTooltip('The driver used for caching. We recommend "Filesystem".')
->required()
->inline()
->options(self::CACHE_DRIVERS)
->default(config('cache.default', 'file')),
ToggleButtons::make('env_general.SESSION_DRIVER')
->label('Session Driver')
->hintIcon('tabler-question-mark')
->hintIconTooltip('The driver used for storing sessions. We recommend "Filesystem" or "Database".')
->required()
->inline()
->options(self::SESSION_DRIVERS)
->default(config('session.driver', 'file')),
ToggleButtons::make('env_general.QUEUE_CONNECTION')
->label('Queue Driver')
->hintIcon('tabler-question-mark')
->hintIconTooltip('The driver used for handling queues. We recommend "Database".')
->required()
->inline()
->options(self::QUEUE_DRIVERS)
->default(config('queue.default', 'database')),
ToggleButtons::make('env_general.DB_CONNECTION')
->label('Database Driver')
->hintIcon('tabler-question-mark')
->hintIconTooltip('The driver used for the panel database. We recommend "SQLite".')
->required()
->inline()
->options(self::DATABASE_DRIVERS)
->default(config('database.default', 'sqlite')),
])
->afterValidation(fn () => $installer->writeToEnv('env_general'));
}
}

View File

@@ -0,0 +1,82 @@
<?php
namespace App\Filament\Pages\Installer\Steps;
use App\Filament\Pages\Installer\PanelInstaller;
use App\Traits\EnvironmentWriterTrait;
use Exception;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Wizard\Step;
use Filament\Forms\Get;
use Filament\Notifications\Notification;
use Filament\Support\Exceptions\Halt;
use Illuminate\Support\Facades\Redis;
class RedisStep
{
use EnvironmentWriterTrait;
public static function make(PanelInstaller $installer): Step
{
return Step::make('redis')
->label('Redis')
->columns()
->schema([
TextInput::make('env_redis.REDIS_HOST')
->label('Redis Host')
->hintIcon('tabler-question-mark')
->hintIconTooltip('The host of your redis server. Make sure it is reachable.')
->required()
->default(config('database.redis.default.host')),
TextInput::make('env_redis.REDIS_PORT')
->label('Redis Port')
->hintIcon('tabler-question-mark')
->hintIconTooltip('The port of your redis server.')
->required()
->default(config('database.redis.default.port')),
TextInput::make('env_redis.REDIS_USERNAME')
->label('Redis Username')
->hintIcon('tabler-question-mark')
->hintIconTooltip('The name of your redis user. Can be empty')
->default(config('database.redis.default.username')),
TextInput::make('env_redis.REDIS_PASSWORD')
->label('Redis Password')
->hintIcon('tabler-question-mark')
->hintIconTooltip('The password for your redis user. Can be empty.')
->password()
->revealable()
->default(config('database.redis.default.password')),
])
->afterValidation(function (Get $get) use ($installer) {
if (!self::testConnection($get('env_redis.REDIS_HOST'), $get('env_redis.REDIS_PORT'), $get('env_redis.REDIS_USERNAME'), $get('env_redis.REDIS_PASSWORD'))) {
throw new Halt('Redis connection failed');
}
$installer->writeToEnv('env_redis');
});
}
private static function testConnection(string $host, null|string|int $port, ?string $username, ?string $password): bool
{
try {
config()->set('database.redis._panel_install_test', [
'host' => $host,
'port' => $port,
'username' => $username,
'password' => $password,
]);
Redis::connection('_panel_install_test')->command('ping');
} catch (Exception $exception) {
Notification::make()
->title('Redis connection failed')
->body($exception->getMessage())
->danger()
->send();
return false;
}
return true;
}
}

View File

@@ -1,6 +1,6 @@
<?php
namespace App\Livewire\Installer\Steps;
namespace App\Filament\Pages\Installer\Steps;
use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\Section;

View File

@@ -1,6 +1,6 @@
<?php
namespace App\Filament\Admin\Pages;
namespace App\Filament\Pages;
use App\Models\Backup;
use App\Notifications\MailTested;
@@ -8,9 +8,7 @@ use App\Traits\EnvironmentWriterTrait;
use Exception;
use Filament\Actions\Action;
use Filament\Forms\Components\Actions\Action as FormAction;
use Filament\Forms\Components\Placeholder;
use Filament\Forms\Components\Section;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\Tabs;
use Filament\Forms\Components\Tabs\Tab;
use Filament\Forms\Components\TagsInput;
@@ -23,14 +21,11 @@ use Filament\Forms\Form;
use Filament\Forms\Get;
use Filament\Forms\Set;
use Filament\Notifications\Notification;
use Filament\Pages\Concerns\HasUnsavedDataChangesAlert;
use Filament\Pages\Concerns\InteractsWithHeaderActions;
use Filament\Pages\Page;
use Filament\Support\Enums\MaxWidth;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Notification as MailNotification;
use Illuminate\Support\HtmlString;
/**
* @property Form $form
@@ -38,6 +33,7 @@ use Illuminate\Support\HtmlString;
class Settings extends Page implements HasForms
{
use EnvironmentWriterTrait;
use HasUnsavedDataChangesAlert;
use InteractsWithForms;
use InteractsWithHeaderActions;
@@ -71,11 +67,10 @@ class Settings extends Page implements HasForms
->label('General')
->icon('tabler-home')
->schema($this->generalSettings()),
Tab::make('captcha')
->label('Captcha')
Tab::make('recaptcha')
->label('reCAPTCHA')
->icon('tabler-shield')
->schema($this->captchaSettings())
->columns(3),
->schema($this->recaptchaSettings()),
Tab::make('mail')
->label('Mail')
->icon('tabler-mail')
@@ -151,7 +146,7 @@ class Settings extends Page implements HasForms
->separator()
->splitKeys(['Tab', ' '])
->placeholder('New IP or IP Range')
->default(env('TRUSTED_PROXIES', implode(',', config('trustedproxy.proxies'))))
->default(env('TRUSTED_PROXIES', config('trustedproxy.proxies')))
->hintActions([
FormAction::make('clear')
->label('Clear')
@@ -164,76 +159,56 @@ class Settings extends Page implements HasForms
->label('Set to Cloudflare IPs')
->icon('tabler-brand-cloudflare')
->authorize(fn () => auth()->user()->can('update settings'))
->action(function (Client $client, Set $set) {
$ips = collect();
try {
$response = $client->request(
'GET',
'https://api.cloudflare.com/client/v4/ips',
config('panel.guzzle')
);
if ($response->getStatusCode() === 200) {
$result = json_decode($response->getBody(), true)['result'];
foreach (['ipv4_cidrs', 'ipv6_cidrs'] as $value) {
$ips->push(...data_get($result, $value));
}
$ips->unique();
}
} catch (GuzzleException $e) {
}
$set('TRUSTED_PROXIES', $ips->values()->all());
}),
->action(fn (Set $set) => $set('TRUSTED_PROXIES', [
'173.245.48.0/20',
'103.21.244.0/22',
'103.22.200.0/22',
'103.31.4.0/22',
'141.101.64.0/18',
'108.162.192.0/18',
'190.93.240.0/20',
'188.114.96.0/20',
'197.234.240.0/22',
'198.41.128.0/17',
'162.158.0.0/15',
'104.16.0.0/13',
'104.24.0.0/14',
'172.64.0.0/13',
'131.0.72.0/22',
])),
]),
Select::make('FILAMENT_WIDTH')
->label('Display Width')
->native(false)
->options(MaxWidth::class)
->default(env('FILAMENT_WIDTH', config('panel.filament.display-width'))),
];
}
private function captchaSettings(): array
private function recaptchaSettings(): array
{
return [
Toggle::make('TURNSTILE_ENABLED')
->label('Enable Turnstile Captcha?')
Toggle::make('RECAPTCHA_ENABLED')
->label('Enable reCAPTCHA?')
->inline(false)
->columnSpan(1)
->onIcon('tabler-check')
->offIcon('tabler-x')
->onColor('success')
->offColor('danger')
->live()
->formatStateUsing(fn ($state): bool => (bool) $state)
->afterStateUpdated(fn ($state, Set $set) => $set('TURNSTILE_ENABLED', (bool) $state))
->default(env('TURNSTILE_ENABLED', config('turnstile.turnstile_enabled'))),
Placeholder::make('info')
->columnSpan(2)
->content(new HtmlString('<p>You can generate the keys on your <u><a href="https://developers.cloudflare.com/turnstile/get-started/#get-a-sitekey-and-secret-key" target="_blank">Cloudflare Dashboard</a></u>. A Cloudflare account is required.</p>')),
TextInput::make('TURNSTILE_SITE_KEY')
->label('Site Key')
->afterStateUpdated(fn ($state, Set $set) => $set('RECAPTCHA_ENABLED', (bool) $state))
->default(env('RECAPTCHA_ENABLED', config('recaptcha.enabled'))),
TextInput::make('RECAPTCHA_DOMAIN')
->label('Domain')
->required()
->visible(fn (Get $get) => $get('TURNSTILE_ENABLED'))
->default(env('TURNSTILE_SITE_KEY', config('turnstile.turnstile_site_key')))
->placeholder('1x00000000000000000000AA'),
TextInput::make('TURNSTILE_SECRET_KEY')
->visible(fn (Get $get) => $get('RECAPTCHA_ENABLED'))
->default(env('RECAPTCHA_DOMAIN', config('recaptcha.domain'))),
TextInput::make('RECAPTCHA_WEBSITE_KEY')
->label('Website Key')
->required()
->visible(fn (Get $get) => $get('RECAPTCHA_ENABLED'))
->default(env('RECAPTCHA_WEBSITE_KEY', config('recaptcha.website_key'))),
TextInput::make('RECAPTCHA_SECRET_KEY')
->label('Secret Key')
->required()
->visible(fn (Get $get) => $get('TURNSTILE_ENABLED'))
->default(env('TURNSTILE_SECRET_KEY', config('turnstile.secret_key')))
->placeholder('1x0000000000000000000000000000000AA'),
Toggle::make('TURNSTILE_VERIFY_DOMAIN')
->label('Verify domain?')
->inline(false)
->onIcon('tabler-check')
->offIcon('tabler-x')
->onColor('success')
->offColor('danger')
->visible(fn (Get $get) => $get('TURNSTILE_ENABLED'))
->formatStateUsing(fn ($state): bool => (bool) $state)
->afterStateUpdated(fn ($state, Set $set) => $set('TURNSTILE_VERIFY_DOMAIN', (bool) $state))
->default(env('TURNSTILE_VERIFY_DOMAIN', config('turnstile.turnstile_verify_domain'))),
->visible(fn (Get $get) => $get('RECAPTCHA_ENABLED'))
->default(env('RECAPTCHA_SECRET_KEY', config('recaptcha.secret_key'))),
];
}
@@ -565,21 +540,7 @@ class Settings extends Page implements HasForms
->afterStateUpdated(fn ($state, Set $set) => $set('PANEL_EDITABLE_SERVER_DESCRIPTIONS', (bool) $state))
->default(env('PANEL_EDITABLE_SERVER_DESCRIPTIONS', config('panel.editable_server_descriptions'))),
]),
Section::make('Webhook')
->description('Configure how often old webhook logs should be pruned.')
->columns()
->collapsible()
->collapsed()
->schema([
TextInput::make('APP_WEBHOOK_PRUNE_DAYS')
->label('Prune age')
->required()
->numeric()
->minValue(1)
->maxValue(365)
->suffix('Days')
->default(env('APP_WEBHOOK_PRUNE_DAYS', config('panel.webhook.prune_days'))),
]),
];
}
@@ -588,6 +549,11 @@ class Settings extends Page implements HasForms
return 'data';
}
protected function hasUnsavedDataChangesAlert(): bool
{
return true;
}
public function save(): void
{
try {
@@ -601,6 +567,8 @@ class Settings extends Page implements HasForms
Artisan::call('config:clear');
Artisan::call('queue:restart');
$this->rememberData();
$this->redirect($this->getUrl());
Notification::make()

View File

@@ -1,8 +1,8 @@
<?php
namespace App\Filament\Admin\Resources;
namespace App\Filament\Resources;
use App\Filament\Admin\Resources\ApiKeyResource\Pages;
use App\Filament\Resources\ApiKeyResource\Pages;
use App\Models\ApiKey;
use Filament\Resources\Resource;
use Illuminate\Database\Eloquent\Model;

View File

@@ -1,8 +1,8 @@
<?php
namespace App\Filament\Admin\Resources\ApiKeyResource\Pages;
namespace App\Filament\Resources\ApiKeyResource\Pages;
use App\Filament\Admin\Resources\ApiKeyResource;
use App\Filament\Resources\ApiKeyResource;
use App\Models\ApiKey;
use Filament\Forms\Components\Fieldset;
use Filament\Forms\Components\Hidden;
@@ -11,7 +11,6 @@ use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\ToggleButtons;
use Filament\Forms\Form;
use Filament\Resources\Pages\CreateRecord;
use Illuminate\Database\Eloquent\Model;
class CreateApiKey extends CreateRecord
{
@@ -42,7 +41,7 @@ class CreateApiKey extends CreateRecord
'md' => 2,
])
->schema(
collect(ApiKey::getPermissionList())->map(fn ($resource) => ToggleButtons::make('permissions_' . $resource)
collect(ApiKey::RESOURCES)->map(fn ($resource) => ToggleButtons::make("r_$resource")
->label(str($resource)->replace('_', ' ')->title())->inline()
->options([
0 => 'None',
@@ -88,20 +87,4 @@ class CreateApiKey extends CreateRecord
->columnSpanFull(),
]);
}
protected function handleRecordCreation(array $data): Model
{
$permissions = [];
foreach (ApiKey::getPermissionList() as $permission) {
if (isset($data['permissions_' . $permission])) {
$permissions[$permission] = intval($data['permissions_' . $permission]);
unset($data['permissions_' . $permission]);
}
}
$data['permissions'] = $permissions;
return parent::handleRecordCreation($data);
}
}

View File

@@ -1,10 +1,9 @@
<?php
namespace App\Filament\Admin\Resources\ApiKeyResource\Pages;
namespace App\Filament\Resources\ApiKeyResource\Pages;
use App\Filament\Admin\Resources\ApiKeyResource;
use App\Filament\Resources\ApiKeyResource;
use App\Models\ApiKey;
use App\Tables\Columns\DateTimeColumn;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;
use Filament\Tables\Actions\CreateAction;
@@ -36,13 +35,15 @@ class ListApiKeys extends ListRecords
->hidden()
->searchable(),
DateTimeColumn::make('last_used_at')
TextColumn::make('last_used_at')
->label('Last Used')
->placeholder('Not Used')
->dateTime()
->sortable(),
DateTimeColumn::make('created_at')
TextColumn::make('created_at')
->label('Created')
->dateTime()
->sortable(),
TextColumn::make('user.username')

View File

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

View File

@@ -1,9 +1,11 @@
<?php
namespace App\Filament\Admin\Resources\DatabaseHostResource\Pages;
namespace App\Filament\Resources\DatabaseHostResource\Pages;
use App\Filament\Admin\Resources\DatabaseHostResource;
use App\Filament\Resources\DatabaseHostResource;
use App\Services\Databases\Hosts\HostCreationService;
use Closure;
use Exception;
use Filament\Forms;
use Filament\Forms\Components\Section;
use Filament\Forms\Components\Select;
@@ -11,7 +13,6 @@ use Filament\Forms\Components\TextInput;
use Filament\Forms\Form;
use Filament\Notifications\Notification;
use Filament\Resources\Pages\CreateRecord;
use Filament\Support\Exceptions\Halt;
use Illuminate\Database\Eloquent\Model;
use PDOException;
@@ -78,13 +79,12 @@ class CreateDatabaseHost extends CreateRecord
->revealable()
->maxLength(255)
->required(),
Select::make('node_ids')
->multiple()
Select::make('node_id')
->searchable()
->preload()
->helperText('This setting only defaults to this database host when adding a database to a server on the selected node.')
->label('Linked Nodes')
->relationship('nodes', 'name'),
->label('Linked Node')
->relationship('node', 'name'),
]),
]);
}
@@ -103,18 +103,21 @@ class CreateDatabaseHost extends CreateRecord
protected function handleRecordCreation(array $data): Model
{
try {
return $this->service->handle($data);
} catch (PDOException $exception) {
return $this->service->handle($data);
}
public function exception(Exception $e, Closure $stopPropagation): void
{
if ($e instanceof PDOException) {
Notification::make()
->title('Error connecting to database host')
->body($exception->getMessage())
->body($e->getMessage())
->color('danger')
->icon('tabler-database')
->danger()
->send();
throw new Halt();
$stopPropagation();
}
}
}

View File

@@ -1,11 +1,13 @@
<?php
namespace App\Filament\Admin\Resources\DatabaseHostResource\Pages;
namespace App\Filament\Resources\DatabaseHostResource\Pages;
use App\Filament\Admin\Resources\DatabaseHostResource;
use App\Filament\Admin\Resources\DatabaseHostResource\RelationManagers\DatabasesRelationManager;
use App\Filament\Resources\DatabaseHostResource;
use App\Filament\Resources\DatabaseHostResource\RelationManagers\DatabasesRelationManager;
use App\Models\DatabaseHost;
use App\Services\Databases\Hosts\HostUpdateService;
use Closure;
use Exception;
use Filament\Actions;
use Filament\Forms;
use Filament\Forms\Components\Section;
@@ -14,7 +16,6 @@ use Filament\Forms\Components\TextInput;
use Filament\Forms\Form;
use Filament\Notifications\Notification;
use Filament\Resources\Pages\EditRecord;
use Filament\Support\Exceptions\Halt;
use Illuminate\Database\Eloquent\Model;
use PDOException;
@@ -73,13 +74,12 @@ class EditDatabaseHost extends EditRecord
->password()
->revealable()
->maxLength(255),
Select::make('nodes')
->multiple()
Select::make('node_id')
->searchable()
->preload()
->helperText('This setting only defaults to this database host when adding a database to a server on the selected node.')
->label('Linked Nodes')
->relationship('nodes', 'name'),
->label('Linked Node')
->relationship('node', 'name'),
]),
]);
}
@@ -101,13 +101,9 @@ class EditDatabaseHost extends EditRecord
public function getRelationManagers(): array
{
if (DatabasesRelationManager::canViewForRecord($this->getRecord(), static::class)) {
return [
DatabasesRelationManager::class,
];
}
return [];
return [
DatabasesRelationManager::class,
];
}
protected function handleRecordUpdate(Model $record, array $data): Model
@@ -116,18 +112,21 @@ class EditDatabaseHost extends EditRecord
return $record;
}
try {
return $this->hostUpdateService->handle($record, $data);
} catch (PDOException $exception) {
return $this->hostUpdateService->handle($record, $data);
}
public function exception(Exception $e, Closure $stopPropagation): void
{
if ($e instanceof PDOException) {
Notification::make()
->title('Error connecting to database host')
->body($exception->getMessage())
->body($e->getMessage())
->color('danger')
->icon('tabler-database')
->danger()
->send();
throw new Halt();
$stopPropagation();
}
}
}

View File

@@ -1,8 +1,8 @@
<?php
namespace App\Filament\Admin\Resources\DatabaseHostResource\Pages;
namespace App\Filament\Resources\DatabaseHostResource\Pages;
use App\Filament\Admin\Resources\DatabaseHostResource;
use App\Filament\Resources\DatabaseHostResource;
use App\Models\DatabaseHost;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;
@@ -36,9 +36,8 @@ class ListDatabaseHosts extends ListRecords
->counts('databases')
->icon('tabler-database')
->label('Databases'),
TextColumn::make('nodes.name')
TextColumn::make('node.name')
->icon('tabler-server-2')
->badge()
->placeholder('No Nodes')
->sortable(),
])

View File

@@ -1,10 +1,9 @@
<?php
namespace App\Filament\Admin\Resources\DatabaseHostResource\RelationManagers;
namespace App\Filament\Resources\DatabaseHostResource\RelationManagers;
use App\Models\Database;
use App\Services\Databases\DatabasePasswordService;
use App\Tables\Columns\DateTimeColumn;
use Filament\Forms\Components\Actions\Action;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Form;
@@ -24,30 +23,21 @@ class DatabasesRelationManager extends RelationManager
{
return $form
->schema([
TextInput::make('database')
->columnSpanFull(),
TextInput::make('database')->columnSpanFull(),
TextInput::make('username'),
TextInput::make('password')
->password()
->revealable()
->hintAction(
Action::make('rotate')
->icon('tabler-refresh')
->requiresConfirmation()
->action(fn (DatabasePasswordService $service, Database $database, $set, $get) => $this->rotatePassword($service, $database, $set, $get))
->authorize(fn (Database $database) => auth()->user()->can('update database', $database))
)
->formatStateUsing(fn (Database $database) => $database->password),
TextInput::make('remote')
->label('Connections From')
->formatStateUsing(fn ($record) => $record->remote === '%' ? 'Anywhere ( % )' : $record->remote),
TextInput::make('max_connections')
->formatStateUsing(fn ($record) => $record->max_connections === 0 ? 'Unlimited' : $record->max_connections),
TextInput::make('remote')->label('Connections From'),
TextInput::make('max_connections'),
TextInput::make('JDBC')
->label('JDBC Connection String')
->columnSpanFull()
->password()
->revealable()
->formatStateUsing(fn (Get $get, Database $database) => 'jdbc:mysql://' . $get('username') . ':' . urlencode($database->password) . '@' . $database->host->host . ':' . $database->host->port . '/' . $get('database')),
]);
}
@@ -57,25 +47,18 @@ class DatabasesRelationManager extends RelationManager
return $table
->recordTitleAttribute('servers')
->columns([
TextColumn::make('database')
->icon('tabler-database'),
TextColumn::make('username')
->icon('tabler-user'),
TextColumn::make('remote')
->formatStateUsing(fn ($record) => $record->remote === '%' ? 'Anywhere ( % )' : $record->remote),
TextColumn::make('database')->icon('tabler-database'),
TextColumn::make('username')->icon('tabler-user'),
TextColumn::make('remote'),
TextColumn::make('server.name')
->icon('tabler-brand-docker')
->url(fn (Database $database) => route('filament.admin.resources.servers.edit', ['record' => $database->server_id])),
TextColumn::make('max_connections')
->formatStateUsing(fn ($record) => $record->max_connections === 0 ? 'Unlimited' : $record->max_connections),
DateTimeColumn::make('created_at'),
TextColumn::make('max_connections'),
TextColumn::make('created_at')->dateTime(),
])
->actions([
DeleteAction::make()
->authorize(fn (Database $database) => auth()->user()->can('delete database', $database)),
ViewAction::make()
->color('primary')
->hidden(fn () => !auth()->user()->can('viewList database')),
DeleteAction::make(),
ViewAction::make()->color('primary'),
]);
}

View File

@@ -1,8 +1,8 @@
<?php
namespace App\Filament\Admin\Resources;
namespace App\Filament\Resources;
use App\Filament\Admin\Resources\DatabaseResource\Pages;
use App\Filament\Resources\DatabaseResource\Pages;
use App\Models\Database;
use Filament\Resources\Resource;

View File

@@ -1,8 +1,8 @@
<?php
namespace App\Filament\Admin\Resources\DatabaseResource\Pages;
namespace App\Filament\Resources\DatabaseResource\Pages;
use App\Filament\Admin\Resources\DatabaseResource;
use App\Filament\Resources\DatabaseResource;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Form;

View File

@@ -1,8 +1,8 @@
<?php
namespace App\Filament\Admin\Resources\DatabaseResource\Pages;
namespace App\Filament\Resources\DatabaseResource\Pages;
use App\Filament\Admin\Resources\DatabaseResource;
use App\Filament\Resources\DatabaseResource;
use Filament\Actions;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;

View File

@@ -1,9 +1,8 @@
<?php
namespace App\Filament\Admin\Resources\DatabaseResource\Pages;
namespace App\Filament\Resources\DatabaseResource\Pages;
use App\Filament\Admin\Resources\DatabaseResource;
use App\Tables\Columns\DateTimeColumn;
use App\Filament\Resources\DatabaseResource;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;
use Filament\Tables\Actions\BulkActionGroup;
@@ -35,10 +34,12 @@ class ListDatabases extends ListRecords
TextColumn::make('max_connections')
->numeric()
->sortable(),
DateTimeColumn::make('created_at')
TextColumn::make('created_at')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
DateTimeColumn::make('updated_at')
TextColumn::make('updated_at')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
])

View File

@@ -1,8 +1,8 @@
<?php
namespace App\Filament\Admin\Resources;
namespace App\Filament\Resources;
use App\Filament\Admin\Resources\EggResource\Pages;
use App\Filament\Resources\EggResource\Pages;
use App\Models\Egg;
use Filament\Resources\Resource;

View File

@@ -1,9 +1,9 @@
<?php
namespace App\Filament\Admin\Resources\EggResource\Pages;
namespace App\Filament\Resources\EggResource\Pages;
use AbdelhamidErrahmouni\FilamentMonacoEditor\MonacoEditor;
use App\Filament\Admin\Resources\EggResource;
use App\Filament\Resources\EggResource;
use Filament\Forms\Components\Checkbox;
use Filament\Forms\Components\Fieldset;
use Filament\Forms\Components\Hidden;

View File

@@ -1,10 +1,10 @@
<?php
namespace App\Filament\Admin\Resources\EggResource\Pages;
namespace App\Filament\Resources\EggResource\Pages;
use AbdelhamidErrahmouni\FilamentMonacoEditor\MonacoEditor;
use App\Filament\Admin\Resources\EggResource;
use App\Filament\Admin\Resources\EggResource\RelationManagers\ServersRelationManager;
use App\Filament\Resources\EggResource;
use App\Filament\Resources\EggResource\RelationManagers\ServersRelationManager;
use App\Models\Egg;
use App\Services\Eggs\Sharing\EggExporterService;
use App\Services\Eggs\Sharing\EggImporterService;
@@ -229,7 +229,6 @@ class EditEgg extends EditRecord
->default('ash'),
MonacoEditor::make('script_install')
->label('Install Script')
->placeholderText('')
->columnSpanFull()
->fontSize('16px')
->language('shell')

View File

@@ -1,8 +1,8 @@
<?php
namespace App\Filament\Admin\Resources\EggResource\Pages;
namespace App\Filament\Resources\EggResource\Pages;
use App\Filament\Admin\Resources\EggResource;
use App\Filament\Resources\EggResource;
use App\Models\Egg;
use App\Services\Eggs\Sharing\EggExporterService;
use App\Services\Eggs\Sharing\EggImporterService;
@@ -142,7 +142,6 @@ class ListEggs extends ListRecords
} catch (Exception $exception) {
Notification::make()
->title('Import Failed')
->body($exception->getMessage())
->danger()
->send();
@@ -159,7 +158,6 @@ class ListEggs extends ListRecords
} catch (Exception $exception) {
Notification::make()
->title('Import Failed')
->body($exception->getMessage())
->danger()
->send();

View File

@@ -1,6 +1,6 @@
<?php
namespace App\Filament\Admin\Resources\EggResource\RelationManagers;
namespace App\Filament\Resources\EggResource\RelationManagers;
use App\Models\Server;
use Filament\Resources\RelationManagers\RelationManager;

View File

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

View File

@@ -1,8 +1,8 @@
<?php
namespace App\Filament\Admin\Resources\MountResource\Pages;
namespace App\Filament\Resources\MountResource\Pages;
use App\Filament\Admin\Resources\MountResource;
use App\Filament\Resources\MountResource;
use Filament\Forms\Components\Group;
use Filament\Forms\Components\Hidden;
use Filament\Forms\Components\Section;

View File

@@ -1,8 +1,8 @@
<?php
namespace App\Filament\Admin\Resources\MountResource\Pages;
namespace App\Filament\Resources\MountResource\Pages;
use App\Filament\Admin\Resources\MountResource;
use App\Filament\Resources\MountResource;
use Filament\Actions;
use Filament\Forms\Components\Group;
use Filament\Forms\Components\Section;

View File

@@ -1,8 +1,8 @@
<?php
namespace App\Filament\Admin\Resources\MountResource\Pages;
namespace App\Filament\Resources\MountResource\Pages;
use App\Filament\Admin\Resources\MountResource;
use App\Filament\Resources\MountResource;
use App\Models\Mount;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;

View File

@@ -1,9 +1,10 @@
<?php
namespace App\Filament\Admin\Resources;
namespace App\Filament\Resources;
use App\Filament\Admin\Resources\NodeResource\Pages;
use App\Filament\Admin\Resources\NodeResource\RelationManagers;
use App\Filament\Resources\NodeResource\Pages;
use App\Filament\Resources\NodeResource\RelationManagers\AllocationsRelationManager;
use App\Filament\Resources\NodeResource\RelationManagers\NodesRelationManager;
use App\Models\Node;
use Filament\Resources\Resource;
@@ -23,8 +24,8 @@ class NodeResource extends Resource
public static function getRelations(): array
{
return [
RelationManagers\AllocationsRelationManager::class,
RelationManagers\NodesRelationManager::class,
AllocationsRelationManager::class,
NodesRelationManager::class,
];
}

View File

@@ -1,8 +1,8 @@
<?php
namespace App\Filament\Admin\Resources\NodeResource\Pages;
namespace App\Filament\Resources\NodeResource\Pages;
use App\Filament\Admin\Resources\NodeResource;
use App\Filament\Resources\NodeResource;
use Filament\Forms;
use Filament\Forms\Components\Actions\Action;
use Filament\Forms\Components\Grid;

View File

@@ -1,10 +1,9 @@
<?php
namespace App\Filament\Admin\Resources\NodeResource\Pages;
namespace App\Filament\Resources\NodeResource\Pages;
use App\Filament\Admin\Resources\NodeResource;
use App\Filament\Resources\NodeResource;
use App\Models\Node;
use App\Services\Helpers\SoftwareVersionService;
use App\Services\Nodes\NodeAutoDeployService;
use App\Services\Nodes\NodeUpdateService;
use Filament\Actions;
@@ -48,12 +47,7 @@ class EditNode extends EditRecord
Tab::make('')
->label('Overview')
->icon('tabler-chart-area-line-filled')
->columns([
'default' => 4,
'sm' => 2,
'md' => 4,
'lg' => 4,
])
->columns(6)
->schema([
Fieldset::make()
->label('Node Information')
@@ -61,7 +55,7 @@ class EditNode extends EditRecord
->schema([
Placeholder::make('')
->label('Wings Version')
->content(fn (Node $node, SoftwareVersionService $versionService) => ($node->systemInformation()['version'] ?? 'Unknown') . ' (Latest: ' . $versionService->latestWingsVersion() . ')'),
->content(fn (Node $node) => $node->systemInformation()['version'] ?? 'Unknown'),
Placeholder::make('')
->label('CPU Threads')
->content(fn (Node $node) => $node->systemInformation()['cpu_count'] ?? 0),
@@ -72,20 +66,8 @@ class EditNode extends EditRecord
->label('Kernel')
->content(fn (Node $node) => $node->systemInformation()['kernel_version'] ?? 'Unknown'),
]),
View::make('filament.components.node-cpu-chart')
->columnSpan([
'default' => 4,
'sm' => 1,
'md' => 2,
'lg' => 2,
]),
View::make('filament.components.node-memory-chart')
->columnSpan([
'default' => 4,
'sm' => 1,
'md' => 2,
'lg' => 2,
]),
View::make('filament.components.node-cpu-chart')->columnSpan(3),
View::make('filament.components.node-memory-chart')->columnSpan(3),
// TODO: Make purdy View::make('filament.components.node-storage-chart')->columnSpan(3),
]),
Tab::make('Basic Settings')

View File

@@ -1,8 +1,8 @@
<?php
namespace App\Filament\Admin\Resources\NodeResource\Pages;
namespace App\Filament\Resources\NodeResource\Pages;
use App\Filament\Admin\Resources\NodeResource;
use App\Filament\Resources\NodeResource;
use App\Models\Node;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;

View File

@@ -1,11 +1,10 @@
<?php
namespace App\Filament\Admin\Resources\NodeResource\RelationManagers;
namespace App\Filament\Resources\NodeResource\RelationManagers;
use App\Models\Allocation;
use App\Models\Node;
use App\Services\Allocations\AssignmentService;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TagsInput;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Form;
@@ -48,20 +47,15 @@ class AllocationsRelationManager extends RelationManager
// All assigned allocations
->checkIfRecordIsSelectableUsing(fn (Allocation $allocation) => $allocation->server_id === null)
->paginationPageOptions(['10', '20', '50', '100', '200', '500', '1000'])
->searchable()
->selectCurrentPageOnly() //Prevent people from trying to nuke 30,000 ports at once.... -,-
->columns([
TextColumn::make('id')
->toggleable()
->toggledHiddenByDefault(),
TextColumn::make('id'),
TextColumn::make('port')
->searchable()
->label('Port'),
TextColumn::make('server.name')
->label('Server')
->icon('tabler-brand-docker')
->visibleFrom('md')
->searchable()
->url(fn (Allocation $allocation): string => $allocation->server ? route('filament.admin.resources.servers.edit', ['record' => $allocation->server]) : ''),
TextInputColumn::make('ip_alias')
@@ -71,11 +65,17 @@ class AllocationsRelationManager extends RelationManager
->searchable()
->label('IP'),
])
->filters([
//
])
->actions([
//
])
->headerActions([
Tables\Actions\Action::make('create new allocation')->label('Create Allocations')
->form(fn () => [
Select::make('allocation_ip')
->options(collect($this->getOwnerRecord()->ipAddresses())->mapWithKeys(fn (string $ip) => [$ip => $ip]))
TextInput::make('allocation_ip')
->datalist($this->getOwnerRecord()->ipAddresses())
->label('IP Address')
->inlineLabel()
->ipv4()

View File

@@ -1,6 +1,6 @@
<?php
namespace App\Filament\Admin\Resources\NodeResource\RelationManagers;
namespace App\Filament\Resources\NodeResource\RelationManagers;
use App\Models\Server;
use Filament\Resources\RelationManagers\RelationManager;

View File

@@ -1,6 +1,6 @@
<?php
namespace App\Filament\Admin\Resources\NodeResource\Widgets;
namespace App\Filament\Resources\NodeResource\Widgets;
use App\Models\Node;
use Carbon\Carbon;

View File

@@ -1,6 +1,6 @@
<?php
namespace App\Filament\Admin\Resources\NodeResource\Widgets;
namespace App\Filament\Resources\NodeResource\Widgets;
use App\Models\Node;
use Carbon\Carbon;

View File

@@ -1,6 +1,6 @@
<?php
namespace App\Filament\Admin\Resources\NodeResource\Widgets;
namespace App\Filament\Resources\NodeResource\Widgets;
use App\Models\Node;
use Filament\Widgets\ChartWidget;

View File

@@ -1,11 +1,12 @@
<?php
namespace App\Filament\Admin\Resources;
namespace App\Filament\Resources;
use App\Enums\RolePermissionModels;
use App\Enums\RolePermissionPrefixes;
use App\Filament\Admin\Resources\RoleResource\Pages;
use App\Filament\Resources\RoleResource\Pages;
use App\Models\Role;
use Filament\Facades\Filament;
use Filament\Forms\Components\Actions\Action;
use Filament\Forms\Components\CheckboxList;
use Filament\Forms\Components\Component;
@@ -70,7 +71,7 @@ class RoleResource extends Resource
->disabled(fn (Get $get) => $get('name') === Role::ROOT_ADMIN),
TextInput::make('guard_name')
->label('Guard Name')
->default(Role::DEFAULT_GUARD_NAME)
->default(Filament::getCurrentPanel()?->getAuthGuard() ?? '')
->nullable()
->hidden(),
Fieldset::make('Permissions')
@@ -91,8 +92,6 @@ class RoleResource extends Resource
$icon = ('\App\Filament\Resources\\' . $model . 'Resource')::getNavigationIcon();
} elseif (class_exists('\App\Filament\Pages\\' . $model)) {
$icon = ('\App\Filament\Pages\\' . $model)::getNavigationIcon();
} elseif (class_exists('\App\Filament\Server\Resources\\' . $model . 'Resource')) {
$icon = ('\App\Filament\Server\Resources\\' . $model . 'Resource')::getNavigationIcon();
}
return Section::make(Str::headline(Str::plural($model)))

View File

@@ -1,8 +1,8 @@
<?php
namespace App\Filament\Admin\Resources\RoleResource\Pages;
namespace App\Filament\Resources\RoleResource\Pages;
use App\Filament\Admin\Resources\RoleResource;
use App\Filament\Resources\RoleResource;
use App\Models\Role;
use Filament\Resources\Pages\CreateRecord;
use Illuminate\Support\Arr;

View File

@@ -1,8 +1,8 @@
<?php
namespace App\Filament\Admin\Resources\RoleResource\Pages;
namespace App\Filament\Resources\RoleResource\Pages;
use App\Filament\Admin\Resources\RoleResource;
use App\Filament\Resources\RoleResource;
use App\Models\Role;
use Filament\Actions\DeleteAction;
use Filament\Resources\Pages\EditRecord;

View File

@@ -1,8 +1,8 @@
<?php
namespace App\Filament\Admin\Resources\RoleResource\Pages;
namespace App\Filament\Resources\RoleResource\Pages;
use App\Filament\Admin\Resources\RoleResource;
use App\Filament\Resources\RoleResource;
use App\Models\Role;
use Filament\Actions\CreateAction;
use Filament\Resources\Pages\ListRecords;

View File

@@ -1,8 +1,8 @@
<?php
namespace App\Filament\Admin\Resources;
namespace App\Filament\Resources;
use App\Filament\Admin\Resources\ServerResource\Pages;
use App\Filament\Resources\ServerResource\Pages;
use App\Models\Server;
use Filament\Resources\Resource;

View File

@@ -1,8 +1,8 @@
<?php
namespace App\Filament\Admin\Resources\ServerResource\Pages;
namespace App\Filament\Resources\ServerResource\Pages;
use App\Filament\Admin\Resources\ServerResource;
use App\Filament\Resources\ServerResource;
use App\Models\Allocation;
use App\Models\Egg;
use App\Models\Node;
@@ -113,7 +113,6 @@ class CreateServer extends CreateRecord
TextInput::make('username')
->alphaNum()
->required()
->minLength(3)
->maxLength(255),
TextInput::make('email')
@@ -192,12 +191,13 @@ class CreateServer extends CreateRecord
->whereNull('server_id'),
)
->createOptionForm(fn (Get $get) => [
Select::make('allocation_ip')
->options(collect(Node::find($get('node_id'))?->ipAddresses())->mapWithKeys(fn (string $ip) => [$ip => $ip]))
TextInput::make('allocation_ip')
->datalist(Node::find($get('node_id'))?->ipAddresses() ?? [])
->label('IP Address')
->inlineLabel()
->ipv4()
->helperText("Usually your machine's public IP unless you are port forwarding.")
// ->selectablePlaceholder(false)
->required(),
TextInput::make('allocation_alias')
->label('Alias')
@@ -787,7 +787,6 @@ class CreateServer extends CreateRecord
->schema([
Select::make('select_image')
->label('Image Name')
->live()
->afterStateUpdated(fn (Set $set, $state) => $set('image', $state))
->options(function ($state, Get $get, Set $set) {
$egg = Egg::query()->find($get('egg_id'));
@@ -812,7 +811,7 @@ class CreateServer extends CreateRecord
TextInput::make('image')
->label('Image')
->required()
->debounce(500)
->afterStateUpdated(function ($state, Get $get, Set $set) {
$egg = Egg::query()->find($get('egg_id'));
$images = $egg->docker_images ?? [];

View File

@@ -1,25 +1,20 @@
<?php
namespace App\Filament\Admin\Resources\ServerResource\Pages;
namespace App\Filament\Resources\ServerResource\Pages;
use App\Enums\ContainerStatus;
use App\Enums\ServerState;
use App\Filament\Admin\Resources\ServerResource;
use App\Filament\Admin\Resources\ServerResource\RelationManagers\AllocationsRelationManager;
use App\Filament\Server\Pages\Console;
use App\Filament\Resources\ServerResource;
use App\Http\Controllers\Admin\ServersController;
use App\Models\Database;
use App\Models\DatabaseHost;
use App\Models\Egg;
use App\Models\Mount;
use App\Models\Server;
use App\Models\ServerVariable;
use App\Services\Databases\DatabaseManagementService;
use App\Services\Databases\DatabasePasswordService;
use App\Services\Servers\RandomWordService;
use App\Services\Servers\ReinstallServerService;
use App\Services\Servers\ServerDeletionService;
use App\Services\Servers\SuspensionService;
use App\Services\Servers\ToggleInstallService;
use App\Services\Servers\TransferServerService;
use Closure;
use Exception;
@@ -30,7 +25,6 @@ use Filament\Forms\Components\CheckboxList;
use Filament\Forms\Components\Fieldset;
use Filament\Forms\Components\Grid;
use Filament\Forms\Components\Hidden;
use Filament\Forms\Components\KeyValue;
use Filament\Forms\Components\Repeater;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\Tabs;
@@ -423,7 +417,6 @@ class EditServer extends EditRecord
->schema([
Select::make('select_image')
->label('Image Name')
->live()
->afterStateUpdated(fn (Set $set, $state) => $set('image', $state))
->options(function ($state, Get $get, Set $set) {
$egg = Egg::query()->find($get('egg_id'));
@@ -443,7 +436,7 @@ class EditServer extends EditRecord
TextInput::make('image')
->label('Image')
->required()
->debounce(500)
->afterStateUpdated(function ($state, Get $get, Set $set) {
$egg = Egg::query()->find($get('egg_id'));
$images = $egg->docker_images ?? [];
@@ -457,7 +450,7 @@ class EditServer extends EditRecord
->placeholder('Enter a custom Image')
->columnSpan(2),
KeyValue::make('docker_labels')
Forms\Components\KeyValue::make('docker_labels')
->label('Container Labels')
->keyLabel('Label Name')
->valueLabel('Label Description')
@@ -513,19 +506,23 @@ class EditServer extends EditRecord
->label('Startup Command')
->required()
->columnSpan(6)
->autosize(),
->rows(function ($state) {
return str($state)->explode("\n")->reduce(
fn (int $carry, $line) => $carry + floor(strlen($line) / 125),
0
);
}),
Textarea::make('defaultStartup')
->hintAction(CopyAction::make())
->label('Default Startup Command')
->disabled()
->autosize()
->columnSpan(6)
->formatStateUsing(function ($state, Get $get) {
$egg = Egg::query()->find($get('egg_id'));
return $egg->startup;
}),
})
->columnSpan(6),
Repeater::make('server_variables')
->relationship('serverVariables', function (Builder $query) {
@@ -599,16 +596,14 @@ class EditServer extends EditRecord
->schema([
CheckboxList::make('mounts')
->relationship('mounts')
->options(fn (Server $server) => $server->node->mounts->filter(fn (Mount $mount) => $mount->eggs->contains($server->egg))->mapWithKeys(fn (Mount $mount) => [$mount->id => $mount->name]))
->descriptions(fn (Server $server) => $server->node->mounts->mapWithKeys(fn (Mount $mount) => [$mount->id => "$mount->source -> $mount->target"]))
->options(fn (Server $server) => $server->node->mounts->mapWithKeys(fn ($mount) => [$mount->id => $mount->name]))
->descriptions(fn (Server $server) => $server->node->mounts->mapWithKeys(fn ($mount) => [$mount->id => "$mount->source -> $mount->target"]))
->label('Mounts')
->helperText(fn (Server $server) => $server->node->mounts->isNotEmpty() ? '' : 'No Mounts exist for this Node')
->columnSpanFull(),
]),
Tab::make('Databases')
->hidden(fn () => !auth()->user()->can('viewList database'))
->icon('tabler-database')
->columns(4)
->schema([
Repeater::make('databases')
->grid()
@@ -622,50 +617,35 @@ class EditServer extends EditRecord
->formatStateUsing(fn ($record) => $record->database)
->hintAction(
Action::make('Delete')
->authorize(fn (Database $database) => auth()->user()->can('delete database', $database))
->color('danger')
->icon('tabler-trash')
->requiresConfirmation()
->modalIcon('tabler-database-x')
->modalHeading('Delete Database?')
->modalSubmitActionLabel(fn (Get $get) => 'Delete ' . $get('database') . '?')
->modalDescription(fn (Get $get) => 'Are you sure you want to delete ' . $get('database') . '?')
->action(function (DatabaseManagementService $databaseManagementService, $record) {
$databaseManagementService->delete($record);
$this->fillForm();
})
->action(fn (DatabaseManagementService $databaseManagementService, $record) => $databaseManagementService->delete($record))
),
TextInput::make('username')
->disabled()
->formatStateUsing(fn ($record) => $record->username)
->columnSpan(1),
->columnSpan(2),
TextInput::make('password')
->disabled()
->password()
->revealable()
->columnSpan(1)
->hintAction(
Action::make('rotate')
->authorize(fn (Database $database) => auth()->user()->can('update database', $database))
->icon('tabler-refresh')
->modalHeading('Change Database Password?')
->action(fn (DatabasePasswordService $service, $record, $set, $get) => $this->rotatePassword($service, $record, $set, $get))
->requiresConfirmation()
->action(fn (DatabasePasswordService $service, $record, $set, $get) => $this->rotatePassword($service, $record, $set, $get))
)
->formatStateUsing(fn (Database $database) => $database->password),
->formatStateUsing(fn (Database $database) => $database->password)
->columnSpan(2),
TextInput::make('remote')
->disabled()
->formatStateUsing(fn ($record) => $record->remote === '%' ? 'Anywhere ( % )' : $record->remote)
->formatStateUsing(fn ($record) => $record->remote)
->columnSpan(1)
->label('Connections From'),
TextInput::make('max_connections')
->disabled()
->formatStateUsing(fn ($record) => $record->max_connections === 0 ? 'Unlimited' : $record->max_connections)
->formatStateUsing(fn ($record) => $record->max_connections)
->columnSpan(1),
TextInput::make('JDBC')
->disabled()
->password()
->revealable()
->label('JDBC Connection String')
->columnSpan(2)
->formatStateUsing(fn (Get $get, $record) => 'jdbc:mysql://' . $get('username') . ':' . urlencode($record->password) . '@' . $record->host->host . ':' . $record->host->port . '/' . $get('database')),
@@ -674,58 +654,7 @@ class EditServer extends EditRecord
->deletable(false)
->addable(false)
->columnSpan(4),
Forms\Components\Actions::make([
Action::make('createDatabase')
->authorize(fn () => auth()->user()->can('create database'))
->disabled(fn () => DatabaseHost::query()->count() < 1)
->label(fn () => DatabaseHost::query()->count() < 1 ? 'No Database Hosts' : 'Create Database')
->color(fn () => DatabaseHost::query()->count() < 1 ? 'danger' : 'primary')
->modalSubmitActionLabel('Create Database')
->action(function (array $data, DatabaseManagementService $service, Server $server, RandomWordService $randomWordService) {
if (empty($data['database'])) {
$data['database'] = $randomWordService->word() . random_int(1, 420);
}
if (empty($data['remote'])) {
$data['remote'] = '%';
}
$data['database'] = $service->generateUniqueDatabaseName($data['database'], $server->id);
try {
$service->setValidateDatabaseLimit(false)->create($server, $data);
} catch (Exception $e) {
Notification::make()
->title('Failed to Create Database')
->body($e->getMessage())
->danger()
->persistent()->send();
}
$this->fillForm();
})
->form([
Select::make('database_host_id')
->label('Database Host')
->required()
->placeholder('Select Database Host')
->relationship('node.databaseHosts', 'name',
fn (Builder $query, Server $server) => $query->whereRelation('nodes', 'nodes.id', $server->node_id))
->default(fn () => (DatabaseHost::query()->first())?->id)
->selectablePlaceholder(false),
TextInput::make('database')
->label('Database Name')
->alphaDash()
->prefix(fn (Server $server) => 's' . $server->id . '_')
->hintIcon('tabler-question-mark')
->hintIconTooltip('Leaving this blank will auto generate a random name'),
TextInput::make('remote')
->columnSpan(1)
->regex('/^[\w\-\/.%:]+$/')
->label('Connections From')
->hintIcon('tabler-question-mark')
->hintIconTooltip('Where connections should be allowed from. Leave blank to allow connections from anywhere.'),
]),
])->alignCenter()->columnSpanFull(),
]),
])->columns(4),
Tab::make('Actions')
->icon('tabler-settings')
->schema([
@@ -744,8 +673,8 @@ class EditServer extends EditRecord
Action::make('toggleInstall')
->label('Toggle Install Status')
->disabled(fn (Server $server) => $server->isSuspended())
->action(function (ToggleInstallService $service, Server $server) {
$service->handle($server);
->action(function (ServersController $serversController, Server $server) {
$serversController->toggleInstall($server);
$this->refreshFormData(['status', 'docker']);
}),
@@ -831,7 +760,7 @@ class EditServer extends EditRecord
->modalHeading('Are you sure you want to reinstall this server?')
->modalDescription('!! This can result in unrecoverable data loss !!')
->disabled(fn (Server $server) => $server->isSuspended())
->action(fn (ReinstallServerService $service, Server $server) => $service->handle($server)),
->action(fn (ServersController $serversController, Server $server) => $serversController->reinstallServer($server)),
])->fullWidth(),
ToggleButtons::make('')
->hint('This will reinstall the server with the assigned egg install script.'),
@@ -866,13 +795,13 @@ class EditServer extends EditRecord
->action(function (Server $server, ServerDeletionService $service) {
$service->handle($server);
return redirect(ListServers::getUrl(panel: 'admin'));
return redirect(ListServers::getUrl());
})
->authorize(fn (Server $server) => auth()->user()->can('delete server', $server)),
Actions\Action::make('console')
->label('Console')
->icon('tabler-terminal')
->url(fn (Server $server) => Console::getUrl(panel: 'server', tenant: $server)),
->url(fn (Server $server) => "/server/$server->uuid_short"),
$this->getSaveFormAction()->formId('form'),
];
@@ -897,7 +826,7 @@ class EditServer extends EditRecord
public function getRelationManagers(): array
{
return [
AllocationsRelationManager::class,
ServerResource\RelationManagers\AllocationsRelationManager::class,
];
}

View File

@@ -1,9 +1,8 @@
<?php
namespace App\Filament\Admin\Resources\ServerResource\Pages;
namespace App\Filament\Resources\ServerResource\Pages;
use App\Filament\Server\Pages\Console;
use App\Filament\Admin\Resources\ServerResource;
use App\Filament\Resources\ServerResource;
use App\Models\Server;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;
@@ -83,8 +82,8 @@ class ListServers extends ListRecords
->actions([
Action::make('View')
->icon('tabler-terminal')
->url(fn (Server $server) => Console::getUrl(panel: 'server', tenant: $server))
->authorize(fn (Server $server) => auth()->user()->canAccessTenant($server)),
->url(fn (Server $server) => "/server/$server->uuid_short")
->authorize(fn () => auth()->user()->can('view server')),
EditAction::make(),
])
->emptyStateIcon('tabler-brand-docker')

View File

@@ -1,11 +1,10 @@
<?php
namespace App\Filament\Admin\Resources\ServerResource\RelationManagers;
namespace App\Filament\Resources\ServerResource\RelationManagers;
use App\Models\Allocation;
use App\Models\Server;
use App\Services\Allocations\AssignmentService;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TagsInput;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Form;
@@ -41,7 +40,6 @@ class AllocationsRelationManager extends RelationManager
public function table(Table $table): Table
{
return $table
->selectCurrentPageOnly()
->recordTitleAttribute('ip')
->recordTitle(fn (Allocation $allocation) => "$allocation->ip:$allocation->port")
->checkIfRecordIsSelectableUsing(fn (Allocation $record) => $record->id !== $this->getOwnerRecord()->allocation_id)
@@ -72,8 +70,8 @@ class AllocationsRelationManager extends RelationManager
CreateAction::make()->label('Create Allocation')
->createAnother(false)
->form(fn () => [
Select::make('allocation_ip')
->options(collect($this->getOwnerRecord()->node->ipAddresses())->mapWithKeys(fn (string $ip) => [$ip => $ip]))
TextInput::make('allocation_ip')
->datalist($this->getOwnerRecord()->node->ipAddresses())
->label('IP Address')
->inlineLabel()
->ipv4()
@@ -151,8 +149,7 @@ class AllocationsRelationManager extends RelationManager
->multiple()
->associateAnother(false)
->preloadRecordSelect()
->recordSelectOptionsQuery(fn ($query) => $query->whereBelongsTo($this->getOwnerRecord()->node)->whereNull('server_id'))
->recordSelectSearchColumns(['ip', 'port'])
->recordSelectOptionsQuery(fn ($query) => $query->whereBelongsTo($this->getOwnerRecord()->node))
->label('Add Allocation'),
])
->bulkActions([

View File

@@ -1,9 +1,9 @@
<?php
namespace App\Filament\Admin\Resources;
namespace App\Filament\Resources;
use App\Filament\Admin\Resources\UserResource\Pages;
use App\Filament\Admin\Resources\UserResource\RelationManagers;
use App\Filament\Resources\UserResource\Pages;
use App\Filament\Resources\UserResource\RelationManagers\ServersRelationManager;
use App\Models\User;
use Filament\Resources\Resource;
@@ -23,7 +23,7 @@ class UserResource extends Resource
public static function getRelations(): array
{
return [
RelationManagers\ServersRelationManager::class,
ServersRelationManager::class,
];
}

View File

@@ -1,6 +1,6 @@
<?php
namespace App\Filament\Pages\Auth;
namespace App\Filament\Resources\UserResource\Pages;
use App\Exceptions\Service\User\TwoFactorAuthenticationTokenInvalid;
use App\Facades\Activity;
@@ -9,13 +9,13 @@ use App\Models\ApiKey;
use App\Models\User;
use App\Services\Users\ToggleTwoFactorService;
use App\Services\Users\TwoFactorSetupService;
use App\Services\Users\UserUpdateService;
use chillerlan\QRCode\Common\EccLevel;
use chillerlan\QRCode\Common\Version;
use chillerlan\QRCode\QRCode;
use chillerlan\QRCode\QROptions;
use Closure;
use DateTimeZone;
use Filament\Forms\Components\Actions;
use Exception;
use Filament\Forms\Components\Actions\Action;
use Filament\Forms\Components\Grid;
use Filament\Forms\Components\Placeholder;
@@ -29,21 +29,16 @@ use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Get;
use Filament\Notifications\Notification;
use Filament\Pages\Auth\EditProfile as BaseEditProfile;
use Filament\Support\Enums\MaxWidth;
use Filament\Support\Exceptions\Halt;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\HtmlString;
use Illuminate\Support\Str;
use Illuminate\Validation\Rules\Password;
use Laravel\Socialite\Facades\Socialite;
/**
* @method User getUser()
*/
class EditProfile extends BaseEditProfile
class EditProfile extends \Filament\Pages\Auth\EditProfile
{
private ToggleTwoFactorService $toggleTwoFactorService;
@@ -52,11 +47,6 @@ class EditProfile extends BaseEditProfile
$this->toggleTwoFactorService = $toggleTwoFactorService;
}
public function getMaxWidth(): MaxWidth|string
{
return config('panel.filament.display-width', 'screen-2xl');
}
protected function getForms(): array
{
return [
@@ -123,53 +113,6 @@ class EditProfile extends BaseEditProfile
->options(fn (User $user) => $user->getAvailableLanguages()),
]),
Tab::make('OAuth')
->icon('tabler-brand-oauth')
->visible(function () {
foreach (config('auth.oauth') as $name => $data) {
if ($data['enabled']) {
return true;
}
}
return false;
})
->schema(function () {
$providers = [];
foreach (config('auth.oauth') as $name => $data) {
if (!$data['enabled']) {
continue;
}
$unlink = array_key_exists($name, $this->getUser()->oauth ?? []);
$providers[] = Action::make("oauth_$name")
->label(($unlink ? 'Unlink ' : 'Link ') . Str::title($name))
->icon($unlink ? 'tabler-unlink' : 'tabler-link')
->color($data['color'])
->action(function (UserUpdateService $updateService) use ($name, $unlink) {
if ($unlink) {
$oauth = auth()->user()->oauth;
unset($oauth[$name]);
$updateService->handle(auth()->user(), ['oauth' => $oauth]);
$this->fillForm();
Notification::make()
->title("OAuth provider '$name' unlinked")
->success()
->send();
} elseif (config("auth.oauth.$name.enabled")) {
redirect(Socialite::with($name)->redirect()->getTargetUrl());
}
});
}
return [Actions::make($providers)];
}),
Tab::make('2FA')
->icon('tabler-shield-lock')
->schema(function (TwoFactorSetupService $setupService) {
@@ -242,7 +185,7 @@ class EditProfile extends BaseEditProfile
->content(fn () => new HtmlString("
<div style='width: 300px; background-color: rgb(24, 24, 27);'>$image</div>
"))
->helperText('Setup Key: ' . $secret),
->helperText('Setup Key: '. $secret),
TextInput::make('2facode')
->label('Code')
->requiredWith('2fapassword')
@@ -272,25 +215,16 @@ class EditProfile extends BaseEditProfile
])->headerActions([
Action::make('Create')
->disabled(fn (Get $get) => $get('description') === null)
->successRedirectUrl(self::getUrl(['tab' => '-api-keys-tab']))
->successRedirectUrl(route('filament.admin.auth.profile', ['tab' => '-api-keys-tab']))
->action(function (Get $get, Action $action, User $user) {
$token = $user->createToken(
$get('description'),
$get('allowed_ips'),
);
Activity::event('user:api-key.create')
->subject($token->accessToken)
->property('identifier', $token->accessToken->identifier)
->log();
Notification::make()
->title('API Key created')
->body($token->accessToken->identifier . $token->plainTextToken)
->persistent()
->success()
->send();
$action->success();
}),
]),
@@ -355,25 +289,13 @@ class EditProfile extends BaseEditProfile
if ($token = $data['2facode'] ?? null) {
$tokens = $this->toggleTwoFactorService->handle($record, $token, true);
cache()->put("users.$record->id.2fa.tokens", implode("\n", $tokens), now()->addSeconds(15));
cache()->set("users.$record->id.2fa.tokens", implode("\n", $tokens), now()->addSeconds(15));
$this->redirectRoute('filament.admin.auth.profile', ['tab' => '-2fa-tab']);
}
if ($token = $data['2fa-disable-code'] ?? null) {
try {
$this->toggleTwoFactorService->handle($record, $token, false);
} catch (TwoFactorAuthenticationTokenInvalid $exception) {
Notification::make()
->title('Invalid 2FA Code')
->body($exception->getMessage())
->color('danger')
->icon('tabler-2fa')
->danger()
->send();
throw new Halt();
}
$this->toggleTwoFactorService->handle($record, $token, false);
cache()->forget("users.$record->id.2fa.state");
}
@@ -381,16 +303,18 @@ class EditProfile extends BaseEditProfile
return parent::handleRecordUpdate($record, $data);
}
protected function getFormActions(): array
public function exception(Exception $e, Closure $stopPropagation): void
{
return [];
}
protected function getHeaderActions(): array
{
return [
$this->getSaveFormAction()->formId('form'),
];
if ($e instanceof TwoFactorAuthenticationTokenInvalid) {
Notification::make()
->title('Invalid 2FA Code')
->body($e->getMessage())
->color('danger')
->icon('tabler-2fa')
->danger()
->send();
$stopPropagation();
}
}
}

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