mirror of
https://github.com/pelican-dev/panel.git
synced 2026-05-04 18:00:48 +03:00
Compare commits
1 Commits
release/v1
...
release/v1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
777cc532e7 |
@@ -1,43 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Checks;
|
||||
|
||||
use App\Models\Node;
|
||||
use App\Services\Helpers\SoftwareVersionService;
|
||||
use Spatie\Health\Checks\Check;
|
||||
use Spatie\Health\Checks\Result;
|
||||
use Spatie\Health\Enums\Status;
|
||||
|
||||
class NodeVersionsCheck extends Check
|
||||
{
|
||||
public function __construct(private SoftwareVersionService $versionService) {}
|
||||
|
||||
public function run(): Result
|
||||
{
|
||||
$all = Node::query()->count();
|
||||
|
||||
if ($all === 0) {
|
||||
$result = Result::make()->notificationMessage('No Nodes created')->shortSummary('No Nodes');
|
||||
$result->status = Status::skipped();
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
$latestVersion = $this->versionService->latestWingsVersion();
|
||||
|
||||
$outdated = Node::query()->get()
|
||||
->filter(fn (Node $node) => !isset($node->systemInformation()['exception']) && $node->systemInformation()['version'] !== $latestVersion)
|
||||
->count();
|
||||
|
||||
$result = Result::make()
|
||||
->meta([
|
||||
'all' => $all,
|
||||
'outdated' => $outdated,
|
||||
])
|
||||
->shortSummary($outdated === 0 ? 'All up-to-date' : "{$outdated}/{$all} outdated");
|
||||
|
||||
return $outdated === 0
|
||||
? $result->ok('All Nodes are up-to-date.')
|
||||
: $result->failed(':outdated/:all Nodes are outdated.');
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Checks;
|
||||
|
||||
use App\Services\Helpers\SoftwareVersionService;
|
||||
use Spatie\Health\Checks\Check;
|
||||
use Spatie\Health\Checks\Result;
|
||||
|
||||
class PanelVersionCheck extends Check
|
||||
{
|
||||
public function __construct(private SoftwareVersionService $versionService) {}
|
||||
|
||||
public function run(): Result
|
||||
{
|
||||
$isLatest = $this->versionService->isLatestPanel();
|
||||
$currentVersion = $this->versionService->currentPanelVersion();
|
||||
$latestVersion = $this->versionService->latestPanelVersion();
|
||||
|
||||
$result = Result::make()
|
||||
->meta([
|
||||
'isLatest' => $isLatest,
|
||||
'currentVersion' => $currentVersion,
|
||||
'latestVersion' => $latestVersion,
|
||||
])
|
||||
->shortSummary($isLatest ? 'up-to-date' : 'outdated');
|
||||
|
||||
return $isLatest
|
||||
? $result->ok('Panel is up-to-date.')
|
||||
: $result->failed('Installed version is `:currentVersion` but latest is `:latestVersion`.');
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Checks;
|
||||
|
||||
use Spatie\Health\Checks\Checks\UsedDiskSpaceCheck as BaseCheck;
|
||||
|
||||
class UsedDiskSpaceCheck extends BaseCheck
|
||||
{
|
||||
protected function getDiskUsagePercentage(): int
|
||||
{
|
||||
$freeSpace = disk_free_space($this->filesystemName ?? '/');
|
||||
$totalSpace = disk_total_space($this->filesystemName ?? '/');
|
||||
|
||||
return 100 - ($freeSpace * 100 / $totalSpace);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use App\Services\Helpers\SoftwareVersionService;
|
||||
|
||||
class InfoCommand extends Command
|
||||
{
|
||||
@@ -10,8 +11,98 @@ class InfoCommand extends Command
|
||||
|
||||
protected $signature = 'p:info';
|
||||
|
||||
/**
|
||||
* InfoCommand constructor.
|
||||
*/
|
||||
public function __construct(private SoftwareVersionService $versionService)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle execution of command.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$this->call('about');
|
||||
$this->output->title('Version Information');
|
||||
$this->table([], [
|
||||
['Panel Version', $this->versionService->currentPanelVersion()],
|
||||
['Latest Version', $this->versionService->latestPanelVersion()],
|
||||
['Up-to-Date', $this->versionService->isLatestPanel() ? 'Yes' : $this->formatText('No', 'bg=red')],
|
||||
], 'compact');
|
||||
|
||||
$this->output->title('Application Configuration');
|
||||
$this->table([], [
|
||||
['Environment', config('app.env') === 'production' ? config('app.env') : $this->formatText(config('app.env'), 'bg=red')],
|
||||
['Debug Mode', config('app.debug') ? $this->formatText('Yes', 'bg=red') : 'No'],
|
||||
['Application Name', config('app.name')],
|
||||
['Application URL', config('app.url')],
|
||||
['Installation Directory', base_path()],
|
||||
['Cache Driver', config('cache.default')],
|
||||
['Queue Driver', config('queue.default') === 'sync' ? $this->formatText(config('queue.default'), 'bg=red') : config('queue.default')],
|
||||
['Session Driver', config('session.driver')],
|
||||
['Filesystem Driver', config('filesystems.default')],
|
||||
], 'compact');
|
||||
|
||||
$this->output->title('Database Configuration');
|
||||
$driver = config('database.default');
|
||||
if ($driver === 'sqlite') {
|
||||
$this->table([], [
|
||||
['Driver', $driver],
|
||||
['Database', config("database.connections.$driver.database")],
|
||||
], 'compact');
|
||||
} else {
|
||||
$this->table([], [
|
||||
['Driver', $driver],
|
||||
['Host', config("database.connections.$driver.host")],
|
||||
['Port', config("database.connections.$driver.port")],
|
||||
['Database', config("database.connections.$driver.database")],
|
||||
['Username', config("database.connections.$driver.username")],
|
||||
], 'compact');
|
||||
}
|
||||
|
||||
$this->output->title('Email Configuration');
|
||||
$driver = config('mail.default');
|
||||
if ($driver === 'smtp') {
|
||||
$this->table([], [
|
||||
['Driver', $driver],
|
||||
['Host', config("mail.mailers.$driver.host")],
|
||||
['Port', config("mail.mailers.$driver.port")],
|
||||
['Username', config("mail.mailers.$driver.username")],
|
||||
['Encryption', config("mail.mailers.$driver.encryption")],
|
||||
['From Address', config('mail.from.address')],
|
||||
['From Name', config('mail.from.name')],
|
||||
], 'compact');
|
||||
} else {
|
||||
$this->table([], [
|
||||
['Driver', $driver],
|
||||
['From Address', config('mail.from.address')],
|
||||
['From Name', config('mail.from.name')],
|
||||
], 'compact');
|
||||
}
|
||||
|
||||
$this->output->title('Backup Configuration');
|
||||
$driver = config('backups.default');
|
||||
if ($driver === 's3') {
|
||||
$this->table([], [
|
||||
['Driver', $driver],
|
||||
['Region', config("backups.disks.$driver.region")],
|
||||
['Bucket', config("backups.disks.$driver.bucket")],
|
||||
['Endpoint', config("backups.disks.$driver.endpoint")],
|
||||
['Use path style endpoint', config("backups.disks.$driver.use_path_style_endpoint") ? 'Yes' : 'No'],
|
||||
], 'compact');
|
||||
} else {
|
||||
$this->table([], [
|
||||
['Driver', $driver],
|
||||
], 'compact');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format output in a Name: Value manner.
|
||||
*/
|
||||
private function formatText(string $value, string $opts = ''): string
|
||||
{
|
||||
return sprintf('<%s>%s</>', $opts, $value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,8 +13,6 @@ use App\Models\Webhook;
|
||||
use Illuminate\Console\Scheduling\Schedule;
|
||||
use Illuminate\Database\Console\PruneCommand;
|
||||
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
||||
use Spatie\Health\Commands\RunHealthChecksCommand;
|
||||
use Spatie\Health\Commands\ScheduleCheckHeartbeatCommand;
|
||||
|
||||
class Kernel extends ConsoleKernel
|
||||
{
|
||||
@@ -55,8 +53,5 @@ class Kernel extends ConsoleKernel
|
||||
if (config('panel.webhook.prune_days')) {
|
||||
$schedule->command(PruneCommand::class, ['--model' => [Webhook::class]])->daily();
|
||||
}
|
||||
|
||||
$schedule->command(ScheduleCheckHeartbeatCommand::class)->everyMinute();
|
||||
$schedule->command(RunHealthChecksCommand::class)->everyFiveMinutes();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,45 +52,4 @@ enum ContainerStatus: string
|
||||
self::Offline => 'gray',
|
||||
};
|
||||
}
|
||||
|
||||
public function colorHex(): string
|
||||
{
|
||||
return match ($this) {
|
||||
self::Created, self::Restarting => '#2563EB',
|
||||
self::Starting, self::Paused, self::Removing, self::Stopping => '#D97706',
|
||||
self::Running => '#22C55E',
|
||||
self::Exited, self::Missing, self::Dead, self::Offline => '#EF4444',
|
||||
};
|
||||
}
|
||||
|
||||
public function isStartingOrStopping(): bool
|
||||
{
|
||||
return in_array($this, [ContainerStatus::Starting, ContainerStatus::Stopping, ContainerStatus::Restarting]);
|
||||
}
|
||||
|
||||
public function isStartable(): bool
|
||||
{
|
||||
return !in_array($this, [ContainerStatus::Running, ContainerStatus::Starting, ContainerStatus::Stopping, ContainerStatus::Restarting]);
|
||||
}
|
||||
|
||||
public function isRestartable(): bool
|
||||
{
|
||||
if ($this->isStartable()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return !in_array($this, [ContainerStatus::Offline]);
|
||||
}
|
||||
|
||||
public function isStoppable(): bool
|
||||
{
|
||||
return !in_array($this, [ContainerStatus::Starting, ContainerStatus::Stopping, ContainerStatus::Restarting, ContainerStatus::Exited, ContainerStatus::Offline]);
|
||||
}
|
||||
|
||||
public function isKillable(): bool
|
||||
{
|
||||
// [ContainerStatus::Restarting, ContainerStatus::Removing, ContainerStatus::Dead, ContainerStatus::Created]
|
||||
|
||||
return !in_array($this, [ContainerStatus::Offline, ContainerStatus::Running, ContainerStatus::Exited]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,5 +13,4 @@ enum RolePermissionModels: string
|
||||
case Role = 'role';
|
||||
case Server = 'server';
|
||||
case User = 'user';
|
||||
case Webhook = 'webhook';
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum ServerResourceType
|
||||
{
|
||||
case Unit;
|
||||
case Percentage;
|
||||
case Time;
|
||||
}
|
||||
@@ -13,5 +13,5 @@ class Installed extends Event
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public Server $server, public bool $successful, public bool $initialInstall) {}
|
||||
public function __construct(public Server $server) {}
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\Server;
|
||||
|
||||
use App\Events\Event;
|
||||
use App\Models\Subuser;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class SubUserAdded extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public Subuser $subuser) {}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events\Server;
|
||||
|
||||
use App\Events\Event;
|
||||
use App\Models\Server;
|
||||
use App\Models\User;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class SubUserRemoved extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(public Server $server, public User $user) {}
|
||||
}
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace App\Exceptions\Http\Connection;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Http\Response;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use App\Exceptions\DisplayException;
|
||||
use Illuminate\Support\Facades\Context;
|
||||
|
||||
@@ -22,7 +22,7 @@ class DaemonConnectionException extends DisplayException
|
||||
/**
|
||||
* Throw a displayable exception caused by a daemon connection error.
|
||||
*/
|
||||
public function __construct(?Exception $previous, bool $useStatusCode = true)
|
||||
public function __construct(GuzzleException $previous, bool $useStatusCode = true)
|
||||
{
|
||||
/** @var \GuzzleHttp\Psr7\Response|null $response */
|
||||
$response = method_exists($previous, 'getResponse') ? $previous->getResponse() : null;
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Pages;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Pages\Page;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Spatie\Health\Commands\RunHealthChecksCommand;
|
||||
use Spatie\Health\ResultStores\ResultStore;
|
||||
|
||||
class Health extends Page
|
||||
{
|
||||
protected static ?string $navigationIcon = 'tabler-heart';
|
||||
|
||||
protected static ?string $navigationGroup = 'Advanced';
|
||||
|
||||
protected static string $view = 'filament.pages.health';
|
||||
|
||||
// @phpstan-ignore-next-line
|
||||
protected $listeners = [
|
||||
'refresh-component' => '$refresh',
|
||||
];
|
||||
|
||||
protected function getActions(): array
|
||||
{
|
||||
return [
|
||||
Action::make('refresh')
|
||||
->button()
|
||||
->action('refresh'),
|
||||
];
|
||||
}
|
||||
|
||||
protected function getViewData(): array
|
||||
{
|
||||
// @phpstan-ignore-next-line
|
||||
$checkResults = app(ResultStore::class)->latestResults();
|
||||
|
||||
if ($checkResults === null) {
|
||||
Artisan::call(RunHealthChecksCommand::class);
|
||||
|
||||
$this->dispatch('refresh-component');
|
||||
}
|
||||
|
||||
return [
|
||||
'lastRanAt' => new Carbon($checkResults?->finishedAt),
|
||||
'checkResults' => $checkResults,
|
||||
];
|
||||
}
|
||||
|
||||
public function refresh(): void
|
||||
{
|
||||
Artisan::call(RunHealthChecksCommand::class);
|
||||
|
||||
$this->dispatch('refresh-component');
|
||||
|
||||
Notification::make()
|
||||
->title('Health check results refreshed')
|
||||
->success()
|
||||
->send();
|
||||
}
|
||||
|
||||
public static function getNavigationBadge(): ?string
|
||||
{
|
||||
// @phpstan-ignore-next-line
|
||||
$results = app(ResultStore::class)->latestResults();
|
||||
|
||||
if ($results === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$results = json_decode($results->toJson(), true);
|
||||
|
||||
$failed = array_reduce($results['checkResults'], function ($numFailed, $result) {
|
||||
return $numFailed + ($result['status'] === 'failed' ? 1 : 0);
|
||||
}, 0);
|
||||
|
||||
return $failed === 0 ? null : (string) $failed;
|
||||
}
|
||||
|
||||
public static function getNavigationBadgeColor(): string
|
||||
{
|
||||
return self::getNavigationBadge() > null ? 'danger' : '';
|
||||
}
|
||||
|
||||
public static function getNavigationBadgeTooltip(): ?string
|
||||
{
|
||||
// @phpstan-ignore-next-line
|
||||
$results = app(ResultStore::class)->latestResults();
|
||||
|
||||
if ($results === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$results = json_decode($results->toJson(), true);
|
||||
|
||||
$failedNames = array_reduce($results['checkResults'], function ($carry, $result) {
|
||||
if ($result['status'] === 'failed') {
|
||||
$carry[] = $result['name'];
|
||||
}
|
||||
|
||||
return $carry;
|
||||
}, []);
|
||||
|
||||
return 'Failed: ' . implode(', ', $failedNames);
|
||||
}
|
||||
|
||||
public static function getNavigationIcon(): string
|
||||
{
|
||||
// @phpstan-ignore-next-line
|
||||
$results = app(ResultStore::class)->latestResults();
|
||||
|
||||
if ($results === null) {
|
||||
return 'tabler-heart-question';
|
||||
}
|
||||
|
||||
return $results->containsFailingCheck() ? 'tabler-heart-exclamation' : 'tabler-heart-check';
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\EggResource\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\EggResource;
|
||||
use App\Filament\Components\Actions\ImportEggAction as ImportEggHeaderAction;
|
||||
use App\Filament\Components\Tables\Actions\ExportEggAction;
|
||||
use App\Filament\Components\Tables\Actions\ImportEggAction;
|
||||
use App\Filament\Components\Tables\Actions\UpdateEggAction;
|
||||
use App\Models\Egg;
|
||||
use Filament\Actions\CreateAction as CreateHeaderAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Filament\Tables\Actions\BulkActionGroup;
|
||||
use Filament\Tables\Actions\CreateAction;
|
||||
use Filament\Tables\Actions\DeleteBulkAction;
|
||||
use Filament\Tables\Actions\EditAction;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class ListEggs extends ListRecords
|
||||
{
|
||||
protected static string $resource = EggResource::class;
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->searchable(true)
|
||||
->defaultPaginationPageOption(25)
|
||||
->checkIfRecordIsSelectableUsing(fn (Egg $egg) => $egg->servers_count <= 0)
|
||||
->columns([
|
||||
TextColumn::make('id')
|
||||
->label('Id')
|
||||
->hidden(),
|
||||
TextColumn::make('name')
|
||||
->icon('tabler-egg')
|
||||
->description(fn ($record): ?string => (strlen($record->description) > 120) ? substr($record->description, 0, 120).'...' : $record->description)
|
||||
->wrap()
|
||||
->searchable()
|
||||
->sortable(),
|
||||
TextColumn::make('servers_count')
|
||||
->counts('servers')
|
||||
->icon('tabler-server')
|
||||
->label('Servers'),
|
||||
])
|
||||
->actions([
|
||||
EditAction::make(),
|
||||
ExportEggAction::make(),
|
||||
UpdateEggAction::make(),
|
||||
])
|
||||
->bulkActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make()
|
||||
->authorize(fn () => auth()->user()->can('delete egg')),
|
||||
]),
|
||||
])
|
||||
->emptyStateIcon('tabler-eggs')
|
||||
->emptyStateDescription('')
|
||||
->emptyStateHeading('No Eggs')
|
||||
->emptyStateActions([
|
||||
CreateAction::make()
|
||||
->label('Create Egg'),
|
||||
ImportEggAction::make(),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
ImportEggHeaderAction::make(),
|
||||
CreateHeaderAction::make()
|
||||
->label('Create Egg'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\UserResource\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\UserResource;
|
||||
use App\Models\Role;
|
||||
use App\Services\Users\UserCreationService;
|
||||
use Filament\Forms\Components\CheckboxList;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class CreateUser extends CreateRecord
|
||||
{
|
||||
protected static string $resource = UserResource::class;
|
||||
|
||||
protected static bool $canCreateAnother = false;
|
||||
|
||||
private UserCreationService $service;
|
||||
|
||||
public function boot(UserCreationService $service): void
|
||||
{
|
||||
$this->service = $service;
|
||||
}
|
||||
|
||||
public function form(Form $form): Form
|
||||
{
|
||||
return $form
|
||||
->columns(['default' => 1, 'lg' => 3])
|
||||
->schema([
|
||||
TextInput::make('username')
|
||||
->alphaNum()
|
||||
->required()
|
||||
->unique()
|
||||
->minLength(3)
|
||||
->maxLength(255),
|
||||
TextInput::make('email')
|
||||
->email()
|
||||
->required()
|
||||
->unique()
|
||||
->maxLength(255),
|
||||
TextInput::make('password')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('Providing a user password is optional. New user email will prompt users to create a password the first time they login.')
|
||||
->password(),
|
||||
CheckboxList::make('roles')
|
||||
->disableOptionWhen(fn (string $value): bool => $value == Role::getRootAdmin()->id)
|
||||
->relationship('roles', 'name')
|
||||
->dehydrated()
|
||||
->label('Admin Roles')
|
||||
->columnSpanFull()
|
||||
->bulkToggleable(false),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
$this->getCreateFormAction()->formId('form'),
|
||||
];
|
||||
}
|
||||
|
||||
protected function getFormActions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
protected function handleRecordCreation(array $data): Model
|
||||
{
|
||||
$data['root_admin'] = false;
|
||||
|
||||
$roles = $data['roles'];
|
||||
$roles = collect($roles)->map(fn ($role) => Role::findById($role));
|
||||
unset($data['roles']);
|
||||
|
||||
$user = $this->service->handle($data);
|
||||
|
||||
$user->syncRoles($roles);
|
||||
|
||||
return $user;
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\UserResource\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\UserResource;
|
||||
use App\Models\User;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Filament\Tables\Actions\BulkActionGroup;
|
||||
use Filament\Tables\Actions\DeleteBulkAction;
|
||||
use Filament\Tables\Actions\EditAction;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Tables\Columns\ImageColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class ListUsers extends ListRecords
|
||||
{
|
||||
protected static string $resource = UserResource::class;
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->searchable(false)
|
||||
->columns([
|
||||
ImageColumn::make('picture')
|
||||
->visibleFrom('lg')
|
||||
->label('')
|
||||
->extraImgAttributes(['class' => 'rounded-full'])
|
||||
->defaultImageUrl(fn (User $user) => 'https://gravatar.com/avatar/' . md5(strtolower($user->email))),
|
||||
TextColumn::make('external_id')
|
||||
->searchable()
|
||||
->hidden(),
|
||||
TextColumn::make('uuid')
|
||||
->label('UUID')
|
||||
->hidden()
|
||||
->searchable(),
|
||||
TextColumn::make('username')
|
||||
->searchable(),
|
||||
TextColumn::make('email')
|
||||
->searchable()
|
||||
->icon('tabler-mail'),
|
||||
IconColumn::make('use_totp')
|
||||
->label('2FA')
|
||||
->visibleFrom('lg')
|
||||
->icon(fn (User $user) => $user->use_totp ? 'tabler-lock' : 'tabler-lock-open-off')
|
||||
->boolean()
|
||||
->sortable(),
|
||||
TextColumn::make('roles.name')
|
||||
->label('Roles')
|
||||
->badge()
|
||||
->icon('tabler-users-group')
|
||||
->placeholder('No roles'),
|
||||
TextColumn::make('servers_count')
|
||||
->counts('servers')
|
||||
->icon('tabler-server')
|
||||
->label('Servers'),
|
||||
TextColumn::make('subusers_count')
|
||||
->visibleFrom('sm')
|
||||
->label('Subusers')
|
||||
->counts('subusers')
|
||||
->icon('tabler-users'),
|
||||
])
|
||||
->actions([
|
||||
EditAction::make(),
|
||||
])
|
||||
->checkIfRecordIsSelectableUsing(fn (User $user) => auth()->user()->id !== $user->id && !$user->servers_count)
|
||||
->bulkActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make()
|
||||
->authorize(fn () => auth()->user()->can('delete user')),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make()
|
||||
->label('Create User'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -3,15 +3,14 @@
|
||||
namespace App\Filament\App\Resources\ServerResource\Pages;
|
||||
|
||||
use App\Filament\App\Resources\ServerResource;
|
||||
use App\Filament\Components\Tables\Columns\ServerEntryColumn;
|
||||
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\Number;
|
||||
|
||||
class ListServers extends ListRecords
|
||||
{
|
||||
@@ -19,12 +18,9 @@ class ListServers extends ListRecords
|
||||
|
||||
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')
|
||||
->query(fn () => auth()->user()->can('viewList server') ? Server::query() : auth()->user()->accessibleServers())
|
||||
->columns([
|
||||
Stack::make([
|
||||
ServerEntryColumn::make('server_entry')
|
||||
@@ -33,29 +29,71 @@ class ListServers extends ListRecords
|
||||
])
|
||||
->contentGrid([
|
||||
'default' => 1,
|
||||
'md' => 2,
|
||||
'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(),
|
||||
]);
|
||||
->emptyStateHeading('You don\'t have access to any servers!');
|
||||
}
|
||||
|
||||
// @phpstan-ignore-next-line
|
||||
private function uptime(Server $server): string
|
||||
{
|
||||
$uptime = collect(cache()->get("servers.{$server->id}.uptime"))->last() ?? 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(collect(cache()->get("servers.{$server->id}.cpu_absolute"))->last() ?? 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 = collect(cache()->get("servers.{$server->id}.memory_bytes"))->last() ?? 0;
|
||||
$totalMemory = collect(cache()->get("servers.{$server->id}.memory_limit_bytes"))->last() ?? 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 = collect(cache()->get("servers.{$server->id}.disk_bytes"))->last() ?? 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 : '');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Components\Actions;
|
||||
|
||||
use App\Models\Egg;
|
||||
use App\Services\Eggs\Sharing\EggExporterService;
|
||||
use Filament\Actions\Action;
|
||||
|
||||
class ExportEggAction extends Action
|
||||
{
|
||||
public static function getDefaultName(): ?string
|
||||
{
|
||||
return 'export';
|
||||
}
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->label('Export');
|
||||
|
||||
$this->authorize(fn () => auth()->user()->can('export egg'));
|
||||
|
||||
$this->action(fn (EggExporterService $service, Egg $egg) => response()->streamDownload(function () use ($service, $egg) {
|
||||
echo $service->handle($egg->id);
|
||||
}, 'egg-' . $egg->getKebabName() . '.json'));
|
||||
}
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Components\Actions;
|
||||
|
||||
use App\Services\Eggs\Sharing\EggImporterService;
|
||||
use Exception;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Forms\Components\FileUpload;
|
||||
use Filament\Forms\Components\Tabs;
|
||||
use Filament\Forms\Components\Tabs\Tab;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Notifications\Notification;
|
||||
use Livewire\Features\SupportFileUploads\TemporaryUploadedFile;
|
||||
|
||||
class ImportEggAction extends Action
|
||||
{
|
||||
public static function getDefaultName(): ?string
|
||||
{
|
||||
return 'import';
|
||||
}
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->label('Import');
|
||||
|
||||
$this->authorize(fn () => auth()->user()->can('import egg'));
|
||||
|
||||
$this->form([
|
||||
Tabs::make('Tabs')
|
||||
->contained(false)
|
||||
->tabs([
|
||||
Tab::make('From File')
|
||||
->icon('tabler-file-upload')
|
||||
->schema([
|
||||
FileUpload::make('egg')
|
||||
->label('Egg')
|
||||
->hint('This should be the json file ( egg-minecraft.json )')
|
||||
->acceptedFileTypes(['application/json'])
|
||||
->storeFiles(false)
|
||||
->multiple(),
|
||||
]),
|
||||
Tab::make('From URL')
|
||||
->icon('tabler-world-upload')
|
||||
->schema([
|
||||
TextInput::make('url')
|
||||
->label('URL')
|
||||
->hint('This URL should point to a single json file')
|
||||
->url(),
|
||||
]),
|
||||
]),
|
||||
]);
|
||||
|
||||
$this->action(function (array $data, EggImporterService $eggImportService): void {
|
||||
try {
|
||||
if (!empty($data['egg'])) {
|
||||
/** @var TemporaryUploadedFile[] $eggFile */
|
||||
$eggFile = $data['egg'];
|
||||
|
||||
foreach ($eggFile as $file) {
|
||||
$eggImportService->fromFile($file);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($data['url'])) {
|
||||
$eggImportService->fromUrl($data['url']);
|
||||
}
|
||||
} catch (Exception $exception) {
|
||||
Notification::make()
|
||||
->title('Import Failed')
|
||||
->body($exception->getMessage())
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
report($exception);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Notification::make()
|
||||
->title('Import Success')
|
||||
->success()
|
||||
->send();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Components\Forms\Actions;
|
||||
|
||||
use App\Models\Database;
|
||||
use App\Services\Databases\DatabasePasswordService;
|
||||
use Exception;
|
||||
use Filament\Actions\StaticAction;
|
||||
use Filament\Forms\Components\Actions\Action;
|
||||
use Filament\Forms\Set;
|
||||
use Filament\Notifications\Notification;
|
||||
|
||||
class RotateDatabasePasswordAction extends Action
|
||||
{
|
||||
public static function getDefaultName(): ?string
|
||||
{
|
||||
return 'rotate';
|
||||
}
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->label('Rotate');
|
||||
|
||||
$this->icon('tabler-refresh');
|
||||
|
||||
$this->authorize(fn (Database $database) => auth()->user()->can('update database', $database));
|
||||
|
||||
$this->modalHeading('Rotate Password');
|
||||
|
||||
$this->modalIconColor('warning');
|
||||
|
||||
$this->modalSubmitAction(fn (StaticAction $action) => $action->color('warning'));
|
||||
|
||||
$this->requiresConfirmation();
|
||||
|
||||
$this->action(function (DatabasePasswordService $service, Database $database, Set $set) {
|
||||
try {
|
||||
$service->handle($database);
|
||||
|
||||
$database->refresh();
|
||||
|
||||
$set('password', $database->password);
|
||||
$set('jdbc', $database->jdbc);
|
||||
|
||||
Notification::make()
|
||||
->title('Password rotated')
|
||||
->success()
|
||||
->send();
|
||||
} catch (Exception $exception) {
|
||||
Notification::make()
|
||||
->title('Password rotation failed')
|
||||
->body($exception->getMessage())
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
report($exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Components\Tables\Actions;
|
||||
|
||||
use App\Models\Egg;
|
||||
use App\Services\Eggs\Sharing\EggExporterService;
|
||||
use Filament\Tables\Actions\Action;
|
||||
|
||||
class ExportEggAction extends Action
|
||||
{
|
||||
public static function getDefaultName(): ?string
|
||||
{
|
||||
return 'export';
|
||||
}
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->label('Export');
|
||||
|
||||
$this->icon('tabler-download');
|
||||
|
||||
$this->authorize(fn () => auth()->user()->can('export egg'));
|
||||
|
||||
$this->action(fn (EggExporterService $service, Egg $egg) => response()->streamDownload(function () use ($service, $egg) {
|
||||
echo $service->handle($egg->id);
|
||||
}, 'egg-' . $egg->getKebabName() . '.json'));
|
||||
}
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Components\Tables\Actions;
|
||||
|
||||
use App\Services\Eggs\Sharing\EggImporterService;
|
||||
use Exception;
|
||||
use Filament\Forms\Components\FileUpload;
|
||||
use Filament\Forms\Components\Tabs;
|
||||
use Filament\Forms\Components\Tabs\Tab;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Tables\Actions\Action;
|
||||
use Livewire\Features\SupportFileUploads\TemporaryUploadedFile;
|
||||
|
||||
class ImportEggAction extends Action
|
||||
{
|
||||
public static function getDefaultName(): ?string
|
||||
{
|
||||
return 'import';
|
||||
}
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->label('Import');
|
||||
|
||||
$this->authorize(fn () => auth()->user()->can('import egg'));
|
||||
|
||||
$this->form([
|
||||
Tabs::make('Tabs')
|
||||
->contained(false)
|
||||
->tabs([
|
||||
Tab::make('From File')
|
||||
->icon('tabler-file-upload')
|
||||
->schema([
|
||||
FileUpload::make('egg')
|
||||
->label('Egg')
|
||||
->hint('This should be the json file ( egg-minecraft.json )')
|
||||
->acceptedFileTypes(['application/json'])
|
||||
->storeFiles(false)
|
||||
->multiple(),
|
||||
]),
|
||||
Tab::make('From URL')
|
||||
->icon('tabler-world-upload')
|
||||
->schema([
|
||||
TextInput::make('url')
|
||||
->label('URL')
|
||||
->hint('This URL should point to a single json file')
|
||||
->url(),
|
||||
]),
|
||||
]),
|
||||
]);
|
||||
|
||||
$this->action(function (array $data, EggImporterService $eggImportService): void {
|
||||
try {
|
||||
if (!empty($data['egg'])) {
|
||||
/** @var TemporaryUploadedFile[] $eggFile */
|
||||
$eggFile = $data['egg'];
|
||||
|
||||
foreach ($eggFile as $file) {
|
||||
$eggImportService->fromFile($file);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($data['url'])) {
|
||||
$eggImportService->fromUrl($data['url']);
|
||||
}
|
||||
} catch (Exception $exception) {
|
||||
Notification::make()
|
||||
->title('Import Failed')
|
||||
->body($exception->getMessage())
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
report($exception);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Notification::make()
|
||||
->title('Import Success')
|
||||
->success()
|
||||
->send();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Components\Tables\Actions;
|
||||
|
||||
use App\Models\Egg;
|
||||
use App\Services\Eggs\Sharing\EggImporterService;
|
||||
use Exception;
|
||||
use Filament\Actions\StaticAction;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Tables\Actions\Action;
|
||||
|
||||
class UpdateEggAction extends Action
|
||||
{
|
||||
public static function getDefaultName(): ?string
|
||||
{
|
||||
return 'update';
|
||||
}
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->label('Update');
|
||||
|
||||
$this->icon('tabler-cloud-download');
|
||||
|
||||
$this->color('success');
|
||||
|
||||
$this->requiresConfirmation();
|
||||
|
||||
$this->modalHeading('Are you sure you want to update this egg?');
|
||||
|
||||
$this->modalDescription('If you made any changes to the egg they will be overwritten!');
|
||||
|
||||
$this->modalIconColor('danger');
|
||||
|
||||
$this->modalSubmitAction(fn (StaticAction $action) => $action->color('danger'));
|
||||
|
||||
$this->action(function (Egg $egg, EggImporterService $eggImporterService) {
|
||||
try {
|
||||
$eggImporterService->fromUrl($egg->update_url, $egg);
|
||||
|
||||
cache()->forget("eggs.$egg->uuid.update");
|
||||
} catch (Exception $exception) {
|
||||
Notification::make()
|
||||
->title('Egg Update failed')
|
||||
->body($exception->getMessage())
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
report($exception);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Notification::make()
|
||||
->title('Egg updated')
|
||||
->body($egg->name)
|
||||
->success()
|
||||
->send();
|
||||
});
|
||||
|
||||
$this->authorize(fn () => auth()->user()->can('import egg'));
|
||||
|
||||
$this->visible(fn (Egg $egg) => cache()->get("eggs.$egg->uuid.update", false));
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Components\Tables\Columns;
|
||||
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
|
||||
class NodeHealthColumn extends IconColumn
|
||||
{
|
||||
protected string $view = 'livewire.columns.version-column';
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->alignCenter();
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
<?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\CreateNode;
|
||||
use App\Filament\Resources\NodeResource\Pages\ListNodes;
|
||||
use App\Models\Egg;
|
||||
use App\Models\Node;
|
||||
use App\Models\Server;
|
||||
@@ -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\CacheStep;
|
||||
use App\Filament\Pages\Installer\Steps\DatabaseStep;
|
||||
use App\Filament\Pages\Installer\Steps\EnvironmentStep;
|
||||
use App\Filament\Pages\Installer\Steps\QueueStep;
|
||||
use App\Filament\Pages\Installer\Steps\RequirementsStep;
|
||||
use App\Filament\Pages\Installer\Steps\SessionStep;
|
||||
use App\Models\User;
|
||||
use App\Services\Users\UserCreationService;
|
||||
use App\Traits\CheckMigrationsTrait;
|
||||
@@ -1,8 +1,8 @@
|
||||
<?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;
|
||||
@@ -1,8 +1,8 @@
|
||||
<?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;
|
||||
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Installer\Steps;
|
||||
namespace App\Filament\Pages\Installer\Steps;
|
||||
|
||||
use App\Livewire\Installer\PanelInstaller;
|
||||
use App\Filament\Pages\Installer\PanelInstaller;
|
||||
use Filament\Forms\Components\Fieldset;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Wizard\Step;
|
||||
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Installer\Steps;
|
||||
namespace App\Filament\Pages\Installer\Steps;
|
||||
|
||||
use App\Livewire\Installer\PanelInstaller;
|
||||
use App\Filament\Pages\Installer\PanelInstaller;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Forms\Components\ToggleButtons;
|
||||
@@ -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;
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Installer\Steps;
|
||||
namespace App\Filament\Pages\Installer\Steps;
|
||||
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\ToggleButtons;
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Pages;
|
||||
namespace App\Filament\Pages;
|
||||
|
||||
use App\Models\Backup;
|
||||
use App\Notifications\MailTested;
|
||||
@@ -10,7 +10,6 @@ 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,12 +22,12 @@ 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 Illuminate\Http\Client\Factory;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Illuminate\Support\Facades\Notification as MailNotification;
|
||||
use Illuminate\Support\HtmlString;
|
||||
|
||||
@@ -38,6 +37,7 @@ use Illuminate\Support\HtmlString;
|
||||
class Settings extends Page implements HasForms
|
||||
{
|
||||
use EnvironmentWriterTrait;
|
||||
use HasUnsavedDataChangesAlert;
|
||||
use InteractsWithForms;
|
||||
use InteractsWithHeaderActions;
|
||||
|
||||
@@ -84,10 +84,6 @@ class Settings extends Page implements HasForms
|
||||
->label('Backup')
|
||||
->icon('tabler-box')
|
||||
->schema($this->backupSettings()),
|
||||
Tab::make('OAuth')
|
||||
->label('OAuth')
|
||||
->icon('tabler-brand-oauth')
|
||||
->schema($this->oauthSettings()),
|
||||
Tab::make('misc')
|
||||
->label('Misc')
|
||||
->icon('tabler-tool')
|
||||
@@ -168,33 +164,27 @@ class Settings extends Page implements HasForms
|
||||
->label('Set to Cloudflare IPs')
|
||||
->icon('tabler-brand-cloudflare')
|
||||
->authorize(fn () => auth()->user()->can('update settings'))
|
||||
->action(function (Factory $client, Set $set) {
|
||||
->action(function (Client $client, Set $set) {
|
||||
$ips = collect();
|
||||
|
||||
try {
|
||||
$response = $client
|
||||
->timeout(3)
|
||||
->connectTimeout(3)
|
||||
->get('https://api.cloudflare.com/client/v4/ips');
|
||||
|
||||
$response = $client->request(
|
||||
'GET',
|
||||
'https://api.cloudflare.com/client/v4/ips',
|
||||
config('panel.guzzle')
|
||||
);
|
||||
if ($response->getStatusCode() === 200) {
|
||||
$result = $response->json('result');
|
||||
$result = json_decode($response->getBody(), true)['result'];
|
||||
foreach (['ipv4_cidrs', 'ipv6_cidrs'] as $value) {
|
||||
$ips->push(...data_get($result, $value));
|
||||
}
|
||||
$ips->unique();
|
||||
}
|
||||
} catch (Exception) {
|
||||
} catch (GuzzleException $e) {
|
||||
}
|
||||
|
||||
$set('TRUSTED_PROXIES', $ips->values()->all());
|
||||
}),
|
||||
]),
|
||||
Select::make('FILAMENT_WIDTH')
|
||||
->label('Display Width')
|
||||
->native(false)
|
||||
->options(MaxWidth::class)
|
||||
->default(env('FILAMENT_WIDTH', config('panel.filament.display-width'))),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -250,12 +240,12 @@ class Settings extends Page implements HasForms
|
||||
->columnSpanFull()
|
||||
->inline()
|
||||
->options([
|
||||
'log' => '/storage/logs Directory',
|
||||
'log' => 'Print mails to Log',
|
||||
'smtp' => 'SMTP Server',
|
||||
'sendmail' => 'sendmail Binary',
|
||||
'mailgun' => 'Mailgun',
|
||||
'mandrill' => 'Mandrill',
|
||||
'postmark' => 'Postmark',
|
||||
'sendmail' => 'sendmail (PHP)',
|
||||
])
|
||||
->live()
|
||||
->default(env('MAIL_MAILER', config('mail.default')))
|
||||
@@ -416,74 +406,6 @@ class Settings extends Page implements HasForms
|
||||
];
|
||||
}
|
||||
|
||||
private function oauthSettings(): array
|
||||
{
|
||||
$oauthProviders = Config::get('auth.oauth');
|
||||
|
||||
$formFields = [];
|
||||
|
||||
foreach ($oauthProviders as $providerName => $providerConfig) {
|
||||
$providerEnvPrefix = strtoupper($providerName);
|
||||
|
||||
$fields = [
|
||||
Toggle::make("OAUTH_{$providerEnvPrefix}_ENABLED")
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
->onIcon('tabler-check')
|
||||
->offIcon('tabler-x')
|
||||
->live()
|
||||
->columnSpan(1)
|
||||
->label('Enabled')
|
||||
->default(env("OAUTH_{$providerEnvPrefix}_ENABLED", false)),
|
||||
];
|
||||
|
||||
if (array_key_exists('client_id', $providerConfig['service'] ?? [])) {
|
||||
$fields[] = TextInput::make("OAUTH_{$providerEnvPrefix}_CLIENT_ID")
|
||||
->label('Client ID')
|
||||
->columnSpan(2)
|
||||
->required()
|
||||
->password()
|
||||
->revealable()
|
||||
->autocomplete(false)
|
||||
->hidden(fn (Get $get) => !$get("OAUTH_{$providerEnvPrefix}_ENABLED"))
|
||||
->default(env("OAUTH_{$providerEnvPrefix}_CLIENT_ID", $providerConfig['service']['client_id'] ?? ''))
|
||||
->placeholder('Client ID');
|
||||
}
|
||||
|
||||
if (array_key_exists('client_secret', $providerConfig['service'] ?? [])) {
|
||||
$fields[] = TextInput::make("OAUTH_{$providerEnvPrefix}_CLIENT_SECRET")
|
||||
->label('Client Secret')
|
||||
->columnSpan(2)
|
||||
->required()
|
||||
->password()
|
||||
->revealable()
|
||||
->autocomplete(false)
|
||||
->hidden(fn (Get $get) => !$get("OAUTH_{$providerEnvPrefix}_ENABLED"))
|
||||
->default(env("OAUTH_{$providerEnvPrefix}_CLIENT_SECRET", $providerConfig['service']['client_secret'] ?? ''))
|
||||
->placeholder('Client Secret');
|
||||
}
|
||||
|
||||
if (array_key_exists('base_url', $providerConfig['service'] ?? [])) {
|
||||
$fields[] = TextInput::make("OAUTH_{$providerEnvPrefix}_BASE_URL")
|
||||
->label('Base URL')
|
||||
->columnSpanFull()
|
||||
->autocomplete(false)
|
||||
->hidden(fn (Get $get) => !$get("OAUTH_{$providerEnvPrefix}_ENABLED"))
|
||||
->default(env("OAUTH_{$providerEnvPrefix}_BASE_URL", ''))
|
||||
->placeholder('Base URL');
|
||||
}
|
||||
|
||||
$formFields[] = Section::make(ucfirst($providerName))
|
||||
->columns(5)
|
||||
->icon($providerConfig['icon'] ?? 'tabler-brand-oauth')
|
||||
->collapsed(fn () => !env("OAUTH_{$providerEnvPrefix}_ENABLED", false))
|
||||
->collapsible()
|
||||
->schema($fields);
|
||||
}
|
||||
|
||||
return $formFields;
|
||||
}
|
||||
|
||||
private function miscSettings(): array
|
||||
{
|
||||
return [
|
||||
@@ -661,6 +583,11 @@ class Settings extends Page implements HasForms
|
||||
return 'data';
|
||||
}
|
||||
|
||||
protected function hasUnsavedDataChangesAlert(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function save(): void
|
||||
{
|
||||
try {
|
||||
@@ -674,6 +601,8 @@ class Settings extends Page implements HasForms
|
||||
Artisan::call('config:clear');
|
||||
Artisan::call('queue:restart');
|
||||
|
||||
$this->rememberData();
|
||||
|
||||
$this->redirect($this->getUrl());
|
||||
|
||||
Notification::make()
|
||||
@@ -1,20 +1,17 @@
|
||||
<?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;
|
||||
|
||||
class ApiKeyResource extends Resource
|
||||
{
|
||||
protected static ?string $model = ApiKey::class;
|
||||
|
||||
protected static ?string $modelLabel = 'Application API Key';
|
||||
|
||||
protected static ?string $pluralModelLabel = 'Application API Keys';
|
||||
|
||||
protected static ?string $navigationLabel = 'API Keys';
|
||||
protected static ?string $label = 'API Key';
|
||||
|
||||
protected static ?string $navigationIcon = 'tabler-key';
|
||||
|
||||
@@ -25,6 +22,11 @@ class ApiKeyResource extends Resource
|
||||
return static::getModel()::where('key_type', ApiKey::TYPE_APPLICATION)->count() ?: null;
|
||||
}
|
||||
|
||||
public static function canEdit(Model $record): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
@@ -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;
|
||||
@@ -17,19 +17,7 @@ class CreateApiKey extends CreateRecord
|
||||
{
|
||||
protected static string $resource = ApiKeyResource::class;
|
||||
|
||||
protected static bool $canCreateAnother = false;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
$this->getCreateFormAction()->formId('form'),
|
||||
];
|
||||
}
|
||||
|
||||
protected function getFormActions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
protected ?string $heading = 'Create Application API Key';
|
||||
|
||||
public function form(Form $form): Form
|
||||
{
|
||||
@@ -1,10 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\ApiKeyResource\Pages;
|
||||
namespace App\Filament\Resources\ApiKeyResource\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\ApiKeyResource;
|
||||
use App\Filament\Components\Tables\Columns\DateTimeColumn;
|
||||
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;
|
||||
@@ -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;
|
||||
|
||||
@@ -10,12 +10,12 @@ class DatabaseHostResource extends Resource
|
||||
{
|
||||
protected static ?string $model = DatabaseHost::class;
|
||||
|
||||
protected static ?string $label = 'Database Host';
|
||||
|
||||
protected static ?string $navigationIcon = 'tabler-database';
|
||||
|
||||
protected static ?string $navigationGroup = 'Advanced';
|
||||
|
||||
protected static ?string $recordTitleAttribute = 'name';
|
||||
|
||||
public static function getNavigationBadge(): ?string
|
||||
{
|
||||
return static::getModel()::count() ?: null;
|
||||
@@ -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,17 +13,20 @@ 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;
|
||||
|
||||
class CreateDatabaseHost extends CreateRecord
|
||||
{
|
||||
private HostCreationService $service;
|
||||
|
||||
protected static string $resource = DatabaseHostResource::class;
|
||||
|
||||
protected ?string $heading = 'Database Hosts';
|
||||
|
||||
protected static bool $canCreateAnother = false;
|
||||
|
||||
private HostCreationService $service;
|
||||
protected ?string $subheading = '(database servers that can have individual databases)';
|
||||
|
||||
public function boot(HostCreationService $service): void
|
||||
{
|
||||
@@ -74,13 +79,13 @@ class CreateDatabaseHost extends CreateRecord
|
||||
->revealable()
|
||||
->maxLength(255)
|
||||
->required(),
|
||||
Select::make('node_ids')
|
||||
->multiple()
|
||||
Select::make('node_id')
|
||||
->searchable()
|
||||
->preload()
|
||||
->unique()
|
||||
->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'),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
@@ -99,18 +104,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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,13 @@ class EditDatabaseHost extends EditRecord
|
||||
->password()
|
||||
->revealable()
|
||||
->maxLength(255),
|
||||
Select::make('nodes')
|
||||
->multiple()
|
||||
Select::make('node_id')
|
||||
->searchable()
|
||||
->preload()
|
||||
->unique()
|
||||
->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'),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
@@ -116,18 +117,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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
])
|
||||
@@ -1,12 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\DatabaseHostResource\RelationManagers;
|
||||
namespace App\Filament\Resources\DatabaseHostResource\RelationManagers;
|
||||
|
||||
use App\Filament\Components\Forms\Actions\RotateDatabasePasswordAction;
|
||||
use App\Filament\Components\Tables\Columns\DateTimeColumn;
|
||||
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;
|
||||
use Filament\Forms\Get;
|
||||
use Filament\Forms\Set;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Tables\Actions\DeleteAction;
|
||||
use Filament\Tables\Actions\ViewAction;
|
||||
@@ -27,19 +30,25 @@ class DatabasesRelationManager extends RelationManager
|
||||
TextInput::make('password')
|
||||
->password()
|
||||
->revealable()
|
||||
->hintAction(RotateDatabasePasswordAction::make())
|
||||
->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 (Database $record) => $record->remote === '%' ? 'Anywhere ( % )' : $record->remote),
|
||||
->formatStateUsing(fn ($record) => $record->remote === '%' ? 'Anywhere ( % )' : $record->remote),
|
||||
TextInput::make('max_connections')
|
||||
->formatStateUsing(fn (Database $record) => $record->max_connections === 0 ? 'Unlimited' : $record->max_connections),
|
||||
TextInput::make('jdbc')
|
||||
->formatStateUsing(fn ($record) => $record->max_connections === 0 ? 'Unlimited' : $record->max_connections),
|
||||
TextInput::make('JDBC')
|
||||
->label('JDBC Connection String')
|
||||
->columnSpanFull()
|
||||
->password()
|
||||
->revealable()
|
||||
->formatStateUsing(fn (Database $database) => $database->jdbc),
|
||||
->formatStateUsing(fn (Get $get, Database $database) => 'jdbc:mysql://' . $get('username') . ':' . urlencode($database->password) . '@' . $database->host->host . ':' . $database->host->port . '/' . $get('database')),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -53,7 +62,7 @@ class DatabasesRelationManager extends RelationManager
|
||||
TextColumn::make('username')
|
||||
->icon('tabler-user'),
|
||||
TextColumn::make('remote')
|
||||
->formatStateUsing(fn (Database $record) => $record->remote === '%' ? 'Anywhere ( % )' : $record->remote),
|
||||
->formatStateUsing(fn ($record) => $record->remote === '%' ? 'Anywhere ( % )' : $record->remote),
|
||||
TextColumn::make('server.name')
|
||||
->icon('tabler-brand-docker')
|
||||
->url(fn (Database $database) => route('filament.admin.resources.servers.edit', ['record' => $database->server_id])),
|
||||
@@ -69,4 +78,13 @@ class DatabasesRelationManager extends RelationManager
|
||||
->hidden(fn () => !auth()->user()->can('viewList database')),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function rotatePassword(DatabasePasswordService $service, Database $database, Set $set, Get $get): void
|
||||
{
|
||||
$newPassword = $service->handle($database);
|
||||
$jdbcString = 'jdbc:mysql://' . $get('username') . ':' . urlencode($newPassword) . '@' . $database->host->host . ':' . $database->host->port . '/' . $get('database');
|
||||
|
||||
$set('password', $newPassword);
|
||||
$set('JDBC', $jdbcString);
|
||||
}
|
||||
}
|
||||
32
app/Filament/Resources/DatabaseResource.php
Normal file
32
app/Filament/Resources/DatabaseResource.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Filament\Resources\DatabaseResource\Pages;
|
||||
use App\Models\Database;
|
||||
use Filament\Resources\Resource;
|
||||
|
||||
class DatabaseResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Database::class;
|
||||
|
||||
protected static ?string $navigationIcon = 'tabler-database';
|
||||
|
||||
protected static bool $shouldRegisterNavigation = false;
|
||||
|
||||
protected static ?string $navigationGroup = 'Advanced';
|
||||
|
||||
public static function getNavigationBadge(): ?string
|
||||
{
|
||||
return static::getModel()::count() ?: null;
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListDatabases::route('/'),
|
||||
'create' => Pages\CreateDatabase::route('/create'),
|
||||
'edit' => Pages\EditDatabase::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\DatabaseResource\Pages;
|
||||
|
||||
use App\Filament\Resources\DatabaseResource;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateDatabase extends CreateRecord
|
||||
{
|
||||
protected static string $resource = DatabaseResource::class;
|
||||
|
||||
public function form(Form $form): Form
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
Select::make('server_id')
|
||||
->relationship('server', 'name')
|
||||
->searchable()
|
||||
->preload()
|
||||
->required(),
|
||||
Select::make('database_host_id')
|
||||
->relationship('host', 'name')
|
||||
->searchable()
|
||||
->selectablePlaceholder(false)
|
||||
->preload()
|
||||
->required(),
|
||||
TextInput::make('database')
|
||||
->required()
|
||||
->maxLength(255),
|
||||
TextInput::make('remote')
|
||||
->required()
|
||||
->maxLength(255)
|
||||
->default('%'),
|
||||
TextInput::make('username')
|
||||
->required()
|
||||
->maxLength(255),
|
||||
TextInput::make('password')
|
||||
->password()
|
||||
->revealable()
|
||||
->required(),
|
||||
TextInput::make('max_connections')
|
||||
->numeric()
|
||||
->minValue(0)
|
||||
->default(0),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\DatabaseResource\Pages;
|
||||
|
||||
use App\Filament\Resources\DatabaseResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditDatabase extends EditRecord
|
||||
{
|
||||
protected static string $resource = DatabaseResource::class;
|
||||
|
||||
public function form(Form $form): Form
|
||||
{
|
||||
return $form
|
||||
->schema([
|
||||
Select::make('server_id')
|
||||
->relationship('server', 'name')
|
||||
->searchable()
|
||||
->preload()
|
||||
->required(),
|
||||
TextInput::make('database_host_id')
|
||||
->required()
|
||||
->numeric(),
|
||||
TextInput::make('database')
|
||||
->required()
|
||||
->maxLength(255),
|
||||
TextInput::make('remote')
|
||||
->required()
|
||||
->maxLength(255)
|
||||
->default('%'),
|
||||
TextInput::make('username')
|
||||
->required()
|
||||
->maxLength(255),
|
||||
TextInput::make('password')
|
||||
->password()
|
||||
->revealable()
|
||||
->required(),
|
||||
TextInput::make('max_connections')
|
||||
->numeric()
|
||||
->minValue(0)
|
||||
->default(0),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\DeleteAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\DatabaseResource\Pages;
|
||||
|
||||
use App\Filament\Resources\DatabaseResource;
|
||||
use App\Tables\Columns\DateTimeColumn;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Filament\Tables\Actions\BulkActionGroup;
|
||||
use Filament\Tables\Actions\DeleteBulkAction;
|
||||
use Filament\Tables\Actions\EditAction;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class ListDatabases extends ListRecords
|
||||
{
|
||||
protected static string $resource = DatabaseResource::class;
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('server.name')
|
||||
->numeric()
|
||||
->sortable(),
|
||||
TextColumn::make('database_host_id')
|
||||
->numeric()
|
||||
->sortable(),
|
||||
TextColumn::make('database')
|
||||
->searchable(),
|
||||
TextColumn::make('username')
|
||||
->searchable(),
|
||||
TextColumn::make('remote')
|
||||
->searchable(),
|
||||
TextColumn::make('max_connections')
|
||||
->numeric()
|
||||
->sortable(),
|
||||
DateTimeColumn::make('created_at')
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
DateTimeColumn::make('updated_at')
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
])
|
||||
->actions([
|
||||
EditAction::make(),
|
||||
])
|
||||
->bulkActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make()
|
||||
->authorize(fn () => auth()->user()->can('delete database')),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\CreateAction::make(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -14,6 +14,8 @@ class EggResource extends Resource
|
||||
|
||||
protected static ?string $recordTitleAttribute = 'name';
|
||||
|
||||
protected static ?string $recordRouteKeyName = 'id';
|
||||
|
||||
public static function getNavigationBadge(): ?string
|
||||
{
|
||||
return static::getModel()::count() ?: null;
|
||||
@@ -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;
|
||||
@@ -28,18 +28,6 @@ class CreateEgg extends CreateRecord
|
||||
|
||||
protected static bool $canCreateAnother = false;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
$this->getCreateFormAction()->formId('form'),
|
||||
];
|
||||
}
|
||||
|
||||
protected function getFormActions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function form(Form $form): Form
|
||||
{
|
||||
return $form
|
||||
@@ -1,18 +1,21 @@
|
||||
<?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\Components\Actions\ExportEggAction;
|
||||
use App\Filament\Components\Actions\ImportEggAction;
|
||||
use App\Filament\Resources\EggResource;
|
||||
use App\Filament\Resources\EggResource\RelationManagers\ServersRelationManager;
|
||||
use App\Models\Egg;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use App\Services\Eggs\Sharing\EggExporterService;
|
||||
use App\Services\Eggs\Sharing\EggImporterService;
|
||||
use Exception;
|
||||
use Filament\Actions;
|
||||
use Filament\Forms\Components\Checkbox;
|
||||
use Filament\Forms\Components\Fieldset;
|
||||
use Filament\Forms\Components\FileUpload;
|
||||
use Filament\Forms\Components\Hidden;
|
||||
use Filament\Forms\Components\KeyValue;
|
||||
use Filament\Forms\Components\Placeholder;
|
||||
use Filament\Forms\Components\Repeater;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\Tabs;
|
||||
@@ -23,6 +26,7 @@ use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Forms\Set;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditEgg extends EditRecord
|
||||
@@ -225,7 +229,6 @@ class EditEgg extends EditRecord
|
||||
->default('ash'),
|
||||
MonacoEditor::make('script_install')
|
||||
->label('Install Script')
|
||||
->placeholderText('')
|
||||
->columnSpanFull()
|
||||
->fontSize('16px')
|
||||
->language('shell')
|
||||
@@ -238,11 +241,83 @@ class EditEgg extends EditRecord
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make()
|
||||
Actions\DeleteAction::make('deleteEgg')
|
||||
->disabled(fn (Egg $egg): bool => $egg->servers()->count() > 0)
|
||||
->label(fn (Egg $egg): string => $egg->servers()->count() <= 0 ? 'Delete' : 'In Use'),
|
||||
ExportEggAction::make(),
|
||||
ImportEggAction::make(),
|
||||
Actions\Action::make('exportEgg')
|
||||
->label('Export')
|
||||
->color('primary')
|
||||
->action(fn (EggExporterService $service, Egg $egg) => response()->streamDownload(function () use ($service, $egg) {
|
||||
echo $service->handle($egg->id);
|
||||
}, 'egg-' . $egg->getKebabName() . '.json'))
|
||||
->authorize(fn () => auth()->user()->can('export egg')),
|
||||
Actions\Action::make('importEgg')
|
||||
->label('Import')
|
||||
->form([
|
||||
Placeholder::make('warning')
|
||||
->label('This will overwrite the current egg to the one you upload.'),
|
||||
Tabs::make('Tabs')
|
||||
->tabs([
|
||||
Tab::make('From File')
|
||||
->icon('tabler-file-upload')
|
||||
->schema([
|
||||
FileUpload::make('egg')
|
||||
->label('Egg')
|
||||
->hint('eg. minecraft.json')
|
||||
->acceptedFileTypes(['application/json'])
|
||||
->storeFiles(false),
|
||||
]),
|
||||
Tab::make('From URL')
|
||||
->icon('tabler-world-upload')
|
||||
->schema([
|
||||
TextInput::make('url')
|
||||
->label('URL')
|
||||
->default(fn (Egg $egg): ?string => $egg->update_url)
|
||||
->hint('Link to the egg file (eg. minecraft.json)')
|
||||
->url(),
|
||||
]),
|
||||
])
|
||||
->contained(false),
|
||||
|
||||
])
|
||||
->action(function (array $data, Egg $egg, EggImporterService $eggImportService): void {
|
||||
if (!empty($data['egg'])) {
|
||||
try {
|
||||
$eggImportService->fromFile($data['egg'], $egg);
|
||||
} catch (Exception $exception) {
|
||||
Notification::make()
|
||||
->title('Import Failed')
|
||||
->body($exception->getMessage())
|
||||
->danger() // Will Robinson
|
||||
->send();
|
||||
|
||||
report($exception);
|
||||
|
||||
return;
|
||||
}
|
||||
} elseif (!empty($data['url'])) {
|
||||
try {
|
||||
$eggImportService->fromUrl($data['url'], $egg);
|
||||
} catch (Exception $exception) {
|
||||
Notification::make()
|
||||
->title('Import Failed')
|
||||
->body($exception->getMessage())
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
report($exception);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$this->refreshForm();
|
||||
Notification::make()
|
||||
->title('Import Success')
|
||||
->success()
|
||||
->send();
|
||||
})
|
||||
->authorize(fn () => auth()->user()->can('import egg')),
|
||||
$this->getSaveFormAction()->formId('form'),
|
||||
];
|
||||
}
|
||||
180
app/Filament/Resources/EggResource/Pages/ListEggs.php
Normal file
180
app/Filament/Resources/EggResource/Pages/ListEggs.php
Normal file
@@ -0,0 +1,180 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\EggResource\Pages;
|
||||
|
||||
use App\Filament\Resources\EggResource;
|
||||
use App\Models\Egg;
|
||||
use App\Services\Eggs\Sharing\EggExporterService;
|
||||
use App\Services\Eggs\Sharing\EggImporterService;
|
||||
use Exception;
|
||||
use Filament\Actions;
|
||||
use Filament\Forms\Components\FileUpload;
|
||||
use Filament\Forms\Components\Tabs;
|
||||
use Filament\Forms\Components\Tabs\Tab;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Filament\Tables\Actions\Action;
|
||||
use Filament\Tables\Actions\BulkActionGroup;
|
||||
use Filament\Tables\Actions\DeleteBulkAction;
|
||||
use Filament\Tables\Actions\EditAction;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Livewire\Features\SupportFileUploads\TemporaryUploadedFile;
|
||||
|
||||
class ListEggs extends ListRecords
|
||||
{
|
||||
protected static string $resource = EggResource::class;
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->searchable(true)
|
||||
->defaultPaginationPageOption(25)
|
||||
->checkIfRecordIsSelectableUsing(fn (Egg $egg) => $egg->servers_count <= 0)
|
||||
->columns([
|
||||
TextColumn::make('id')
|
||||
->label('Id')
|
||||
->hidden(),
|
||||
TextColumn::make('name')
|
||||
->icon('tabler-egg')
|
||||
->description(fn ($record): ?string => (strlen($record->description) > 120) ? substr($record->description, 0, 120).'...' : $record->description)
|
||||
->wrap()
|
||||
->searchable()
|
||||
->sortable(),
|
||||
TextColumn::make('servers_count')
|
||||
->counts('servers')
|
||||
->icon('tabler-server')
|
||||
->label('Servers'),
|
||||
])
|
||||
->actions([
|
||||
EditAction::make(),
|
||||
Action::make('export')
|
||||
->icon('tabler-download')
|
||||
->label('Export')
|
||||
->color('primary')
|
||||
->action(fn (EggExporterService $service, Egg $egg) => response()->streamDownload(function () use ($service, $egg) {
|
||||
echo $service->handle($egg->id);
|
||||
}, 'egg-' . $egg->getKebabName() . '.json'))
|
||||
->authorize(fn () => auth()->user()->can('export egg')),
|
||||
Action::make('update')
|
||||
->icon('tabler-cloud-download')
|
||||
->label('Update')
|
||||
->color('success')
|
||||
->requiresConfirmation()
|
||||
->modalHeading('Are you sure you want to update this egg?')
|
||||
->modalDescription('If you made any changes to the egg they will be overwritten!')
|
||||
->modalIconColor('danger')
|
||||
->modalSubmitAction(fn (Actions\StaticAction $action) => $action->color('danger'))
|
||||
->action(function (Egg $egg, EggImporterService $eggImporterService) {
|
||||
try {
|
||||
$eggImporterService->fromUrl($egg->update_url, $egg);
|
||||
|
||||
cache()->forget("eggs.{$egg->uuid}.update");
|
||||
} catch (Exception $exception) {
|
||||
Notification::make()
|
||||
->title('Egg Update failed')
|
||||
->body($exception->getMessage())
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
report($exception);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Notification::make()
|
||||
->title('Egg updated')
|
||||
->success()
|
||||
->send();
|
||||
})
|
||||
->authorize(fn () => auth()->user()->can('import egg'))
|
||||
->visible(fn (Egg $egg) => cache()->get("eggs.{$egg->uuid}.update", false)),
|
||||
])
|
||||
->bulkActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make()
|
||||
->authorize(fn () => auth()->user()->can('delete egg')),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\CreateAction::make('create')->label('Create Egg'),
|
||||
|
||||
Actions\Action::make('import')
|
||||
->label('Import')
|
||||
->form([
|
||||
Tabs::make('Tabs')
|
||||
->tabs([
|
||||
Tab::make('From File')
|
||||
->icon('tabler-file-upload')
|
||||
->schema([
|
||||
FileUpload::make('egg')
|
||||
->label('Egg')
|
||||
->hint('This should be the json file ( egg-minecraft.json )')
|
||||
->acceptedFileTypes(['application/json'])
|
||||
->storeFiles(false)
|
||||
->multiple(),
|
||||
]),
|
||||
Tab::make('From URL')
|
||||
->icon('tabler-world-upload')
|
||||
->schema([
|
||||
TextInput::make('url')
|
||||
->label('URL')
|
||||
->hint('This URL should point to a single json file')
|
||||
->url(),
|
||||
]),
|
||||
])
|
||||
->contained(false),
|
||||
|
||||
])
|
||||
->action(function (array $data, EggImporterService $eggImportService): void {
|
||||
if (!empty($data['egg'])) {
|
||||
/** @var TemporaryUploadedFile[] $eggFile */
|
||||
$eggFile = $data['egg'];
|
||||
|
||||
foreach ($eggFile as $file) {
|
||||
try {
|
||||
$eggImportService->fromFile($file);
|
||||
} catch (Exception $exception) {
|
||||
Notification::make()
|
||||
->title('Import Failed')
|
||||
->body($exception->getMessage())
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
report($exception);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($data['url'])) {
|
||||
try {
|
||||
$eggImportService->fromUrl($data['url']);
|
||||
} catch (Exception $exception) {
|
||||
Notification::make()
|
||||
->title('Import Failed')
|
||||
->body($exception->getMessage())
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
report($exception);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Notification::make()
|
||||
->title('Import Success')
|
||||
->success()
|
||||
->send();
|
||||
})
|
||||
->authorize(fn () => auth()->user()->can('import egg')),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
@@ -14,8 +14,6 @@ class MountResource extends Resource
|
||||
|
||||
protected static ?string $navigationGroup = 'Advanced';
|
||||
|
||||
protected static ?string $recordTitleAttribute = 'name';
|
||||
|
||||
public static function getNavigationBadge(): ?string
|
||||
{
|
||||
return static::getModel()::count() ?: null;
|
||||
@@ -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;
|
||||
@@ -21,18 +21,6 @@ class CreateMount extends CreateRecord
|
||||
|
||||
protected static bool $canCreateAnother = false;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
$this->getCreateFormAction()->formId('form'),
|
||||
];
|
||||
}
|
||||
|
||||
protected function getFormActions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function form(Form $form): Form
|
||||
{
|
||||
return $form
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -23,6 +23,8 @@ class CreateNode extends CreateRecord
|
||||
|
||||
protected static bool $canCreateAnother = false;
|
||||
|
||||
protected ?string $subheading = 'which is a machine that runs your Servers';
|
||||
|
||||
public function form(Forms\Form $form): Forms\Form
|
||||
{
|
||||
return $form
|
||||
@@ -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 App\Services\Helpers\SoftwareVersionService;
|
||||
use App\Services\Nodes\NodeAutoDeployService;
|
||||
@@ -48,12 +48,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')
|
||||
@@ -72,20 +67,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')
|
||||
@@ -1,9 +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\Components\Tables\Columns\NodeHealthColumn;
|
||||
use App\Filament\Resources\NodeResource;
|
||||
use App\Models\Node;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
@@ -28,7 +27,10 @@ class ListNodes extends ListRecords
|
||||
->label('UUID')
|
||||
->searchable()
|
||||
->hidden(),
|
||||
NodeHealthColumn::make('health'),
|
||||
IconColumn::make('health')
|
||||
->alignCenter()
|
||||
->state(fn (Node $node) => $node)
|
||||
->view('livewire.columns.version-column'),
|
||||
TextColumn::make('name')
|
||||
->icon('tabler-server-2')
|
||||
->sortable()
|
||||
@@ -1,16 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\NodeResource\RelationManagers;
|
||||
namespace App\Filament\Resources\NodeResource\RelationManagers;
|
||||
|
||||
use App\Filament\Admin\Resources\ServerResource\Pages\CreateServer;
|
||||
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;
|
||||
use Filament\Forms\Get;
|
||||
use Filament\Forms\Set;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Tables;
|
||||
@@ -50,20 +47,16 @@ 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')
|
||||
@@ -76,14 +69,12 @@ class AllocationsRelationManager extends RelationManager
|
||||
->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()
|
||||
->helperText("Usually your machine's public IP unless you are port forwarding.")
|
||||
->afterStateUpdated(fn (Set $set) => $set('allocation_ports', []))
|
||||
->live()
|
||||
->required(),
|
||||
TextInput::make('allocation_alias')
|
||||
->label('Alias')
|
||||
@@ -101,10 +92,54 @@ class AllocationsRelationManager extends RelationManager
|
||||
->label('Ports')
|
||||
->inlineLabel()
|
||||
->live()
|
||||
->disabled(fn (Get $get) => empty($get('allocation_ip')))
|
||||
->afterStateUpdated(fn ($state, Set $set, Get $get) => $set('allocation_ports',
|
||||
CreateServer::retrieveValidPorts($this->getOwnerRecord(), $state, $get('allocation_ip')))
|
||||
)
|
||||
->afterStateUpdated(function ($state, Set $set) {
|
||||
$ports = collect();
|
||||
$update = false;
|
||||
foreach ($state as $portEntry) {
|
||||
if (!str_contains($portEntry, '-')) {
|
||||
if (is_numeric($portEntry)) {
|
||||
$ports->push((int) $portEntry);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Do not add non numerical ports
|
||||
$update = true;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$update = true;
|
||||
[$start, $end] = explode('-', $portEntry);
|
||||
if (!is_numeric($start) || !is_numeric($end)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$start = max((int) $start, 0);
|
||||
$end = min((int) $end, 2 ** 16 - 1);
|
||||
foreach (range($start, $end) as $i) {
|
||||
$ports->push($i);
|
||||
}
|
||||
}
|
||||
|
||||
$uniquePorts = $ports->unique()->values();
|
||||
if ($ports->count() > $uniquePorts->count()) {
|
||||
$update = true;
|
||||
$ports = $uniquePorts;
|
||||
}
|
||||
|
||||
$sortedPorts = $ports->sort()->values();
|
||||
if ($sortedPorts->all() !== $ports->all()) {
|
||||
$update = true;
|
||||
$ports = $sortedPorts;
|
||||
}
|
||||
|
||||
$ports = $ports->filter(fn ($port) => $port > 1024 && $port < 65535)->values();
|
||||
|
||||
if ($update) {
|
||||
$set('allocation_ports', $ports->all());
|
||||
}
|
||||
})
|
||||
->splitKeys(['Tab', ' ', ','])
|
||||
->required(),
|
||||
])
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -26,8 +26,8 @@ class NodeCpuChart extends ChartWidget
|
||||
$cpu = collect(cache()->get("nodes.$node->id.cpu_percent"))
|
||||
->slice(-10)
|
||||
->map(fn ($value, $key) => [
|
||||
'cpu' => Number::format($value * $threads, maxPrecision: 2),
|
||||
'timestamp' => Carbon::createFromTimestamp($key, auth()->user()->timezone ?? 'UTC')->format('H:i:s'),
|
||||
'cpu' => Number::format($value * $threads, maxPrecision: 2, locale: auth()->user()->language),
|
||||
'timestamp' => Carbon::createFromTimestamp($key, (auth()->user()->timezone ?? 'UTC'))->format('H:i:s'),
|
||||
])
|
||||
->all();
|
||||
|
||||
@@ -43,7 +43,6 @@ class NodeCpuChart extends ChartWidget
|
||||
],
|
||||
],
|
||||
'labels' => array_column($cpu, 'timestamp'),
|
||||
'locale' => auth()->user()->language ?? 'en',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -24,8 +24,8 @@ class NodeMemoryChart extends ChartWidget
|
||||
|
||||
$memUsed = collect(cache()->get("nodes.$node->id.memory_used"))->slice(-10)
|
||||
->map(fn ($value, $key) => [
|
||||
'memory' => Number::format(config('panel.use_binary_prefix') ? $value / 1024 / 1024 / 1024 : $value / 1000 / 1000 / 1000, maxPrecision: 2),
|
||||
'timestamp' => Carbon::createFromTimestamp($key, auth()->user()->timezone ?? 'UTC')->format('H:i:s'),
|
||||
'memory' => Number::format(config('panel.use_binary_prefix') ? $value / 1024 / 1024 / 1024 : $value / 1000 / 1000 / 1000, maxPrecision: 2, locale: auth()->user()->language),
|
||||
'timestamp' => Carbon::createFromTimestamp($key, (auth()->user()->timezone ?? 'UTC'))->format('H:i:s'),
|
||||
])
|
||||
->all();
|
||||
|
||||
@@ -41,7 +41,6 @@ class NodeMemoryChart extends ChartWidget
|
||||
],
|
||||
],
|
||||
'labels' => array_column($memUsed, 'timestamp'),
|
||||
'locale' => auth()->user()->language ?? 'en',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -1,10 +1,10 @@
|
||||
<?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\Forms\Components\Actions\Action;
|
||||
use Filament\Forms\Components\CheckboxList;
|
||||
@@ -24,8 +24,6 @@ class RoleResource extends Resource
|
||||
|
||||
protected static ?string $navigationIcon = 'tabler-users-group';
|
||||
|
||||
protected static ?string $navigationGroup = 'Advanced';
|
||||
|
||||
protected static ?string $recordTitleAttribute = 'name';
|
||||
|
||||
public static function getNavigationBadge(): ?string
|
||||
@@ -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;
|
||||
@@ -14,23 +14,11 @@ use Spatie\Permission\Models\Permission;
|
||||
*/
|
||||
class CreateRole extends CreateRecord
|
||||
{
|
||||
public Collection $permissions;
|
||||
|
||||
protected static string $resource = RoleResource::class;
|
||||
|
||||
protected static bool $canCreateAnother = false;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
$this->getCreateFormAction()->formId('form'),
|
||||
];
|
||||
}
|
||||
|
||||
protected function getFormActions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
public Collection $permissions;
|
||||
|
||||
protected function mutateFormDataBeforeCreate(array $data): array
|
||||
{
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -34,9 +34,7 @@ use Filament\Forms\Components\Wizard\Step;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Forms\Get;
|
||||
use Filament\Forms\Set;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
use Filament\Support\Exceptions\Halt;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
@@ -70,8 +68,9 @@ class CreateServer extends CreateRecord
|
||||
->completedIcon('tabler-check')
|
||||
->columns([
|
||||
'default' => 1,
|
||||
'sm' => 4,
|
||||
'sm' => 1,
|
||||
'md' => 4,
|
||||
'lg' => 6,
|
||||
])
|
||||
->schema([
|
||||
TextInput::make('name')
|
||||
@@ -88,51 +87,24 @@ class CreateServer extends CreateRecord
|
||||
$set('name', $prefix . $word);
|
||||
}))
|
||||
->columnSpan([
|
||||
'default' => 1,
|
||||
'sm' => 2,
|
||||
'default' => 2,
|
||||
'sm' => 3,
|
||||
'md' => 2,
|
||||
'lg' => 3,
|
||||
])
|
||||
->required()
|
||||
->maxLength(255),
|
||||
|
||||
TextInput::make('external_id')
|
||||
->label('External ID')
|
||||
->columnSpan([
|
||||
'default' => 1,
|
||||
'sm' => 2,
|
||||
'md' => 2,
|
||||
])
|
||||
->unique()
|
||||
->maxLength(255),
|
||||
|
||||
Select::make('node_id')
|
||||
->disabledOn('edit')
|
||||
->prefixIcon('tabler-server-2')
|
||||
->default(fn () => ($this->node = Node::query()->latest()->first())?->id)
|
||||
->columnSpan([
|
||||
'default' => 1,
|
||||
'sm' => 2,
|
||||
'md' => 2,
|
||||
])
|
||||
->live()
|
||||
->relationship('node', 'name')
|
||||
->searchable()
|
||||
->preload()
|
||||
->afterStateUpdated(function (Set $set, $state) {
|
||||
$set('allocation_id', null);
|
||||
$this->node = Node::find($state);
|
||||
})
|
||||
->required(),
|
||||
|
||||
Select::make('owner_id')
|
||||
->preload()
|
||||
->prefixIcon('tabler-user')
|
||||
->default(auth()->user()->id)
|
||||
->label('Owner')
|
||||
->columnSpan([
|
||||
'default' => 1,
|
||||
'sm' => 2,
|
||||
'md' => 2,
|
||||
'default' => 2,
|
||||
'sm' => 3,
|
||||
'md' => 3,
|
||||
'lg' => 3,
|
||||
])
|
||||
->relationship('user', 'username')
|
||||
->searchable(['username', 'email'])
|
||||
@@ -162,15 +134,36 @@ class CreateServer extends CreateRecord
|
||||
})
|
||||
->required(),
|
||||
|
||||
Select::make('node_id')
|
||||
->disabledOn('edit')
|
||||
->prefixIcon('tabler-server-2')
|
||||
->default(fn () => ($this->node = Node::query()->latest()->first())?->id)
|
||||
->columnSpan([
|
||||
'default' => 2,
|
||||
'sm' => 3,
|
||||
'md' => 6,
|
||||
'lg' => 6,
|
||||
])
|
||||
->live()
|
||||
->relationship('node', 'name')
|
||||
->searchable()
|
||||
->preload()
|
||||
->afterStateUpdated(function (Set $set, $state) {
|
||||
$set('allocation_id', null);
|
||||
$this->node = Node::find($state);
|
||||
})
|
||||
->required(),
|
||||
|
||||
Select::make('allocation_id')
|
||||
->preload()
|
||||
->live()
|
||||
->prefixIcon('tabler-network')
|
||||
->label('Primary Allocation')
|
||||
->columnSpan([
|
||||
'default' => 1,
|
||||
'sm' => 2,
|
||||
'default' => 2,
|
||||
'sm' => 3,
|
||||
'md' => 2,
|
||||
'lg' => 3,
|
||||
])
|
||||
->disabled(fn (Get $get) => $get('node_id') === null)
|
||||
->searchable(['ip', 'port', 'ip_alias'])
|
||||
@@ -198,47 +191,87 @@ class CreateServer extends CreateRecord
|
||||
->where('node_id', $get('node_id'))
|
||||
->whereNull('server_id'),
|
||||
)
|
||||
->createOptionForm(function (Get $get) {
|
||||
$getPage = $get;
|
||||
->createOptionForm(fn (Get $get) => [
|
||||
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')
|
||||
->inlineLabel()
|
||||
->default(null)
|
||||
->datalist([
|
||||
$get('name'),
|
||||
Egg::find($get('egg_id'))?->name,
|
||||
])
|
||||
->helperText('Optional display name to help you remember what these are.')
|
||||
->required(false),
|
||||
TagsInput::make('allocation_ports')
|
||||
->placeholder('Examples: 27015, 27017-27019')
|
||||
->helperText(new HtmlString('
|
||||
These are the ports that users can connect to this Server through.
|
||||
<br />
|
||||
You would have to port forward these on your home network.
|
||||
'))
|
||||
->label('Ports')
|
||||
->inlineLabel()
|
||||
->live()
|
||||
->afterStateUpdated(function ($state, Set $set) {
|
||||
$ports = collect();
|
||||
$update = false;
|
||||
foreach ($state as $portEntry) {
|
||||
if (!str_contains($portEntry, '-')) {
|
||||
if (is_numeric($portEntry)) {
|
||||
$ports->push((int) $portEntry);
|
||||
|
||||
return [
|
||||
Select::make('allocation_ip')
|
||||
->options(collect(Node::find($get('node_id'))?->ipAddresses())->mapWithKeys(fn (string $ip) => [$ip => $ip]))
|
||||
->label('IP Address')
|
||||
->helperText("Usually your machine's public IP unless you are port forwarding.")
|
||||
->afterStateUpdated(fn (Set $set) => $set('allocation_ports', []))
|
||||
->inlineLabel()
|
||||
->ipv4()
|
||||
->live()
|
||||
->required(),
|
||||
TextInput::make('allocation_alias')
|
||||
->label('Alias')
|
||||
->inlineLabel()
|
||||
->default(null)
|
||||
->datalist([
|
||||
$get('name'),
|
||||
Egg::find($get('egg_id'))?->name,
|
||||
])
|
||||
->helperText('Optional display name to help you remember what these are.')
|
||||
->required(false),
|
||||
TagsInput::make('allocation_ports')
|
||||
->placeholder('Examples: 27015, 27017-27019')
|
||||
->helperText(new HtmlString('
|
||||
These are the ports that users can connect to this Server through.
|
||||
<br />
|
||||
You would have to port forward these on your home network.
|
||||
'))
|
||||
->label('Ports')
|
||||
->inlineLabel()
|
||||
->live()
|
||||
->disabled(fn (Get $get) => empty($get('allocation_ip')))
|
||||
->afterStateUpdated(fn ($state, Set $set, Get $get) => $set('allocation_ports',
|
||||
CreateServer::retrieveValidPorts(Node::find($getPage('node_id')), $state, $get('allocation_ip')))
|
||||
)
|
||||
->splitKeys(['Tab', ' ', ','])
|
||||
->required(),
|
||||
];
|
||||
})
|
||||
continue;
|
||||
}
|
||||
|
||||
// Do not add non-numerical ports
|
||||
$update = true;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$update = true;
|
||||
[$start, $end] = explode('-', $portEntry);
|
||||
if (!is_numeric($start) || !is_numeric($end)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$start = max((int) $start, 0);
|
||||
$end = min((int) $end, 2 ** 16 - 1);
|
||||
$range = $start <= $end ? range($start, $end) : range($end, $start);
|
||||
foreach ($range as $i) {
|
||||
if ($i > 1024 && $i <= 65535) {
|
||||
$ports->push($i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$uniquePorts = $ports->unique()->values();
|
||||
if ($ports->count() > $uniquePorts->count()) {
|
||||
$update = true;
|
||||
$ports = $uniquePorts;
|
||||
}
|
||||
|
||||
$sortedPorts = $ports->sort()->values();
|
||||
if ($sortedPorts->all() !== $ports->all()) {
|
||||
$update = true;
|
||||
$ports = $sortedPorts;
|
||||
}
|
||||
|
||||
if ($update) {
|
||||
$set('allocation_ports', $ports->all());
|
||||
}
|
||||
})
|
||||
->splitKeys(['Tab', ' ', ','])
|
||||
->required(),
|
||||
])
|
||||
->createOptionUsing(function (array $data, Get $get, AssignmentService $assignmentService): int {
|
||||
return collect(
|
||||
$assignmentService->handle(Node::find($get('node_id')), $data)
|
||||
@@ -249,9 +282,10 @@ class CreateServer extends CreateRecord
|
||||
Repeater::make('allocation_additional')
|
||||
->label('Additional Allocations')
|
||||
->columnSpan([
|
||||
'default' => 1,
|
||||
'sm' => 2,
|
||||
'md' => 2,
|
||||
'default' => 2,
|
||||
'sm' => 3,
|
||||
'md' => 3,
|
||||
'lg' => 3,
|
||||
])
|
||||
->addActionLabel('Add Allocation')
|
||||
->disabled(fn (Get $get) => $get('allocation_id') === null)
|
||||
@@ -287,9 +321,10 @@ class CreateServer extends CreateRecord
|
||||
->placeholder('Description')
|
||||
->rows(3)
|
||||
->columnSpan([
|
||||
'default' => 1,
|
||||
'sm' => 4,
|
||||
'md' => 4,
|
||||
'default' => 2,
|
||||
'sm' => 6,
|
||||
'md' => 6,
|
||||
'lg' => 6,
|
||||
])
|
||||
->label('Description'),
|
||||
]),
|
||||
@@ -500,37 +535,6 @@ class CreateServer extends CreateRecord
|
||||
'lg' => 3,
|
||||
])
|
||||
->schema([
|
||||
Grid::make()
|
||||
->columns(4)
|
||||
->columnSpanFull()
|
||||
->schema([
|
||||
ToggleButtons::make('unlimited_cpu')
|
||||
->label('CPU')->inlineLabel()->inline()
|
||||
->default(true)
|
||||
->afterStateUpdated(fn (Set $set) => $set('cpu', 0))
|
||||
->live()
|
||||
->options([
|
||||
true => 'Unlimited',
|
||||
false => 'Limited',
|
||||
])
|
||||
->colors([
|
||||
true => 'primary',
|
||||
false => 'warning',
|
||||
])
|
||||
->columnSpan(2),
|
||||
|
||||
TextInput::make('cpu')
|
||||
->dehydratedWhenHidden()
|
||||
->hidden(fn (Get $get) => $get('unlimited_cpu'))
|
||||
->label('CPU Limit')->inlineLabel()
|
||||
->suffix('%')
|
||||
->default(0)
|
||||
->required()
|
||||
->columnSpan(2)
|
||||
->numeric()
|
||||
->minValue(0)
|
||||
->helperText('100% equals one CPU core.'),
|
||||
]),
|
||||
Grid::make()
|
||||
->columns(4)
|
||||
->columnSpanFull()
|
||||
@@ -561,6 +565,7 @@ class CreateServer extends CreateRecord
|
||||
->numeric()
|
||||
->minValue(0),
|
||||
]),
|
||||
|
||||
Grid::make()
|
||||
->columns(4)
|
||||
->columnSpanFull()
|
||||
@@ -592,6 +597,37 @@ class CreateServer extends CreateRecord
|
||||
->minValue(0),
|
||||
]),
|
||||
|
||||
Grid::make()
|
||||
->columns(4)
|
||||
->columnSpanFull()
|
||||
->schema([
|
||||
ToggleButtons::make('unlimited_cpu')
|
||||
->label('CPU')->inlineLabel()->inline()
|
||||
->default(true)
|
||||
->afterStateUpdated(fn (Set $set) => $set('cpu', 0))
|
||||
->live()
|
||||
->options([
|
||||
true => 'Unlimited',
|
||||
false => 'Limited',
|
||||
])
|
||||
->colors([
|
||||
true => 'primary',
|
||||
false => 'warning',
|
||||
])
|
||||
->columnSpan(2),
|
||||
|
||||
TextInput::make('cpu')
|
||||
->dehydratedWhenHidden()
|
||||
->hidden(fn (Get $get) => $get('unlimited_cpu'))
|
||||
->label('CPU Limit')->inlineLabel()
|
||||
->suffix('%')
|
||||
->default(0)
|
||||
->required()
|
||||
->columnSpan(2)
|
||||
->numeric()
|
||||
->minValue(0)
|
||||
->helperText('100% equals one CPU core.'),
|
||||
]),
|
||||
]),
|
||||
|
||||
Fieldset::make('Advanced Limits')
|
||||
@@ -603,40 +639,6 @@ class CreateServer extends CreateRecord
|
||||
'lg' => 3,
|
||||
])
|
||||
->schema([
|
||||
Hidden::make('io')
|
||||
->helperText('The IO performance relative to other running containers')
|
||||
->label('Block IO Proportion')
|
||||
->default(500),
|
||||
|
||||
Grid::make()
|
||||
->columns(4)
|
||||
->columnSpanFull()
|
||||
->schema([
|
||||
ToggleButtons::make('cpu_pinning')
|
||||
->label('CPU Pinning')->inlineLabel()->inline()
|
||||
->default(false)
|
||||
->afterStateUpdated(fn (Set $set) => $set('threads', []))
|
||||
->live()
|
||||
->options([
|
||||
false => 'Disabled',
|
||||
true => 'Enabled',
|
||||
])
|
||||
->colors([
|
||||
false => 'success',
|
||||
true => 'warning',
|
||||
])
|
||||
->columnSpan(2),
|
||||
|
||||
TagsInput::make('threads')
|
||||
->dehydratedWhenHidden()
|
||||
->hidden(fn (Get $get) => !$get('cpu_pinning'))
|
||||
->label('Pinned Threads')->inlineLabel()
|
||||
->required(fn (Get $get) => $get('cpu_pinning'))
|
||||
->columnSpan(2)
|
||||
->separator()
|
||||
->splitKeys([','])
|
||||
->placeholder('Add pinned thread, e.g. 0 or 2-4'),
|
||||
]),
|
||||
Grid::make()
|
||||
->columns(4)
|
||||
->columnSpanFull()
|
||||
@@ -685,6 +687,41 @@ class CreateServer extends CreateRecord
|
||||
->integer(),
|
||||
]),
|
||||
|
||||
Hidden::make('io')
|
||||
->helperText('The IO performance relative to other running containers')
|
||||
->label('Block IO Proportion')
|
||||
->default(500),
|
||||
|
||||
Grid::make()
|
||||
->columns(4)
|
||||
->columnSpanFull()
|
||||
->schema([
|
||||
ToggleButtons::make('cpu_pinning')
|
||||
->label('CPU Pinning')->inlineLabel()->inline()
|
||||
->default(false)
|
||||
->afterStateUpdated(fn (Set $set) => $set('threads', []))
|
||||
->live()
|
||||
->options([
|
||||
false => 'Disabled',
|
||||
true => 'Enabled',
|
||||
])
|
||||
->colors([
|
||||
false => 'success',
|
||||
true => 'warning',
|
||||
])
|
||||
->columnSpan(2),
|
||||
|
||||
TagsInput::make('threads')
|
||||
->dehydratedWhenHidden()
|
||||
->hidden(fn (Get $get) => !$get('cpu_pinning'))
|
||||
->label('Pinned Threads')->inlineLabel()
|
||||
->required(fn (Get $get) => $get('cpu_pinning'))
|
||||
->columnSpan(2)
|
||||
->separator()
|
||||
->splitKeys([','])
|
||||
->placeholder('Add pinned thread, e.g. 0 or 2-4'),
|
||||
]),
|
||||
|
||||
Grid::make()
|
||||
->columns(4)
|
||||
->columnSpanFull()
|
||||
@@ -839,18 +876,7 @@ class CreateServer extends CreateRecord
|
||||
{
|
||||
$data['allocation_additional'] = collect($data['allocation_additional'])->filter()->all();
|
||||
|
||||
try {
|
||||
return $this->serverCreationService->handle($data);
|
||||
} catch (Exception $exception) {
|
||||
Notification::make()
|
||||
->title('Could not create server')
|
||||
->body($exception->getMessage())
|
||||
->color('danger')
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
throw new Halt();
|
||||
}
|
||||
return $this->serverCreationService->handle($data);
|
||||
}
|
||||
|
||||
private function shouldHideComponent(Get $get, Component $component): bool
|
||||
@@ -883,88 +909,4 @@ class CreateServer extends CreateRecord
|
||||
->mapWithKeys(fn ($value) => [$value => $value])
|
||||
->all();
|
||||
}
|
||||
|
||||
public static function retrieveValidPorts(Node $node, array $portEntries, string $ip): array
|
||||
{
|
||||
$portRangeLimit = AssignmentService::PORT_RANGE_LIMIT;
|
||||
$portFloor = AssignmentService::PORT_FLOOR;
|
||||
$portCeil = AssignmentService::PORT_CEIL;
|
||||
|
||||
$ports = collect();
|
||||
|
||||
$existingPorts = $node
|
||||
->allocations()
|
||||
->where('ip', $ip)
|
||||
->pluck('port')
|
||||
->all();
|
||||
|
||||
foreach ($portEntries as $portEntry) {
|
||||
$start = $end = $portEntry;
|
||||
if (str_contains($portEntry, '-')) {
|
||||
[$start, $end] = explode('-', $portEntry);
|
||||
}
|
||||
|
||||
if (!is_numeric($start) || !is_numeric($end)) {
|
||||
Notification::make()
|
||||
->title('Invalid Port Range')
|
||||
->danger()
|
||||
->body("Your port range are not valid integers: $portEntry")
|
||||
->send();
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$start = (int) $start;
|
||||
$end = (int) $end;
|
||||
$range = $start <= $end ? range($start, $end) : range($end, $start);
|
||||
|
||||
if (count($range) > $portRangeLimit) {
|
||||
Notification::make()
|
||||
->title('Too many ports at one time!')
|
||||
->danger()
|
||||
->body("The current limit is $portRangeLimit number of ports at one time.")
|
||||
->send();
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($range as $i) {
|
||||
// Invalid port number
|
||||
if ($i <= $portFloor || $i > $portCeil) {
|
||||
Notification::make()
|
||||
->title('Port not in valid range')
|
||||
->danger()
|
||||
->body("$i is not in the valid port range between $portFloor-$portCeil")
|
||||
->send();
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Already exists
|
||||
if (in_array($i, $existingPorts)) {
|
||||
Notification::make()
|
||||
->title('Port already in use')
|
||||
->danger()
|
||||
->body("$i is already with an allocation")
|
||||
->send();
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$ports->push($i);
|
||||
}
|
||||
}
|
||||
|
||||
$uniquePorts = $ports->unique()->values();
|
||||
if ($ports->count() > $uniquePorts->count()) {
|
||||
$ports = $uniquePorts;
|
||||
}
|
||||
|
||||
$sortedPorts = $ports->sort()->values();
|
||||
if ($sortedPorts->all() !== $ports->all()) {
|
||||
$ports = $sortedPorts;
|
||||
}
|
||||
|
||||
return $ports->all();
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,11 @@
|
||||
<?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\Components\Forms\Actions\RotateDatabasePasswordAction;
|
||||
use App\Filament\Server\Pages\Console;
|
||||
use App\Filament\Resources\ServerResource;
|
||||
use App\Filament\Resources\ServerResource\RelationManagers\AllocationsRelationManager;
|
||||
use App\Models\Database;
|
||||
use App\Models\DatabaseHost;
|
||||
use App\Models\Egg;
|
||||
@@ -15,7 +13,7 @@ use App\Models\Mount;
|
||||
use App\Models\Server;
|
||||
use App\Models\ServerVariable;
|
||||
use App\Services\Databases\DatabaseManagementService;
|
||||
use App\Services\Eggs\EggChangerService;
|
||||
use App\Services\Databases\DatabasePasswordService;
|
||||
use App\Services\Servers\RandomWordService;
|
||||
use App\Services\Servers\ReinstallServerService;
|
||||
use App\Services\Servers\ServerDeletionService;
|
||||
@@ -39,7 +37,6 @@ use Filament\Forms\Components\Tabs\Tab;
|
||||
use Filament\Forms\Components\TagsInput;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Forms\Components\ToggleButtons;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Forms\Get;
|
||||
@@ -161,7 +158,6 @@ class EditServer extends EditRecord
|
||||
'md' => 2,
|
||||
'lg' => 3,
|
||||
])
|
||||
->unique()
|
||||
->maxLength(255),
|
||||
Select::make('node_id')
|
||||
->label('Node')
|
||||
@@ -185,35 +181,6 @@ class EditServer extends EditRecord
|
||||
'lg' => 3,
|
||||
])
|
||||
->schema([
|
||||
Grid::make()
|
||||
->columns(4)
|
||||
->columnSpanFull()
|
||||
->schema([
|
||||
ToggleButtons::make('unlimited_cpu')
|
||||
->label('CPU')->inlineLabel()->inline()
|
||||
->afterStateUpdated(fn (Set $set) => $set('cpu', 0))
|
||||
->formatStateUsing(fn (Get $get) => $get('cpu') == 0)
|
||||
->live()
|
||||
->options([
|
||||
true => 'Unlimited',
|
||||
false => 'Limited',
|
||||
])
|
||||
->colors([
|
||||
true => 'primary',
|
||||
false => 'warning',
|
||||
])
|
||||
->columnSpan(2),
|
||||
|
||||
TextInput::make('cpu')
|
||||
->dehydratedWhenHidden()
|
||||
->hidden(fn (Get $get) => $get('unlimited_cpu'))
|
||||
->label('CPU Limit')->inlineLabel()
|
||||
->suffix('%')
|
||||
->required()
|
||||
->columnSpan(2)
|
||||
->numeric()
|
||||
->minValue(0),
|
||||
]),
|
||||
Grid::make()
|
||||
->columns(4)
|
||||
->columnSpanFull()
|
||||
@@ -273,6 +240,36 @@ class EditServer extends EditRecord
|
||||
->numeric()
|
||||
->minValue(0),
|
||||
]),
|
||||
|
||||
Grid::make()
|
||||
->columns(4)
|
||||
->columnSpanFull()
|
||||
->schema([
|
||||
ToggleButtons::make('unlimited_cpu')
|
||||
->label('CPU')->inlineLabel()->inline()
|
||||
->afterStateUpdated(fn (Set $set) => $set('cpu', 0))
|
||||
->formatStateUsing(fn (Get $get) => $get('cpu') == 0)
|
||||
->live()
|
||||
->options([
|
||||
true => 'Unlimited',
|
||||
false => 'Limited',
|
||||
])
|
||||
->colors([
|
||||
true => 'primary',
|
||||
false => 'warning',
|
||||
])
|
||||
->columnSpan(2),
|
||||
|
||||
TextInput::make('cpu')
|
||||
->dehydratedWhenHidden()
|
||||
->hidden(fn (Get $get) => $get('unlimited_cpu'))
|
||||
->label('CPU Limit')->inlineLabel()
|
||||
->suffix('%')
|
||||
->required()
|
||||
->columnSpan(2)
|
||||
->numeric()
|
||||
->minValue(0),
|
||||
]),
|
||||
]),
|
||||
|
||||
Fieldset::make('Advanced Limits')
|
||||
@@ -287,36 +284,6 @@ class EditServer extends EditRecord
|
||||
->columns(4)
|
||||
->columnSpanFull()
|
||||
->schema([
|
||||
Grid::make()
|
||||
->columns(4)
|
||||
->columnSpanFull()
|
||||
->schema([
|
||||
ToggleButtons::make('cpu_pinning')
|
||||
->label('CPU Pinning')->inlineLabel()->inline()
|
||||
->default(false)
|
||||
->afterStateUpdated(fn (Set $set) => $set('threads', []))
|
||||
->formatStateUsing(fn (Get $get) => !empty($get('threads')))
|
||||
->live()
|
||||
->options([
|
||||
false => 'Disabled',
|
||||
true => 'Enabled',
|
||||
])
|
||||
->colors([
|
||||
false => 'success',
|
||||
true => 'warning',
|
||||
])
|
||||
->columnSpan(2),
|
||||
|
||||
TagsInput::make('threads')
|
||||
->dehydratedWhenHidden()
|
||||
->hidden(fn (Get $get) => !$get('cpu_pinning'))
|
||||
->label('Pinned Threads')->inlineLabel()
|
||||
->required(fn (Get $get) => $get('cpu_pinning'))
|
||||
->columnSpan(2)
|
||||
->separator()
|
||||
->splitKeys([','])
|
||||
->placeholder('Add pinned thread, e.g. 0 or 2-4'),
|
||||
]),
|
||||
ToggleButtons::make('swap_support')
|
||||
->live()
|
||||
->label('Swap Memory')->inlineLabel()->inline()
|
||||
@@ -368,6 +335,37 @@ class EditServer extends EditRecord
|
||||
->helperText('The IO performance relative to other running containers')
|
||||
->label('Block IO Proportion'),
|
||||
|
||||
Grid::make()
|
||||
->columns(4)
|
||||
->columnSpanFull()
|
||||
->schema([
|
||||
ToggleButtons::make('cpu_pinning')
|
||||
->label('CPU Pinning')->inlineLabel()->inline()
|
||||
->default(false)
|
||||
->afterStateUpdated(fn (Set $set) => $set('threads', []))
|
||||
->formatStateUsing(fn (Get $get) => !empty($get('threads')))
|
||||
->live()
|
||||
->options([
|
||||
false => 'Disabled',
|
||||
true => 'Enabled',
|
||||
])
|
||||
->colors([
|
||||
false => 'success',
|
||||
true => 'warning',
|
||||
])
|
||||
->columnSpan(2),
|
||||
|
||||
TagsInput::make('threads')
|
||||
->dehydratedWhenHidden()
|
||||
->hidden(fn (Get $get) => !$get('cpu_pinning'))
|
||||
->label('Pinned Threads')->inlineLabel()
|
||||
->required(fn (Get $get) => $get('cpu_pinning'))
|
||||
->columnSpan(2)
|
||||
->separator()
|
||||
->splitKeys([','])
|
||||
->placeholder('Add pinned thread, e.g. 0 or 2-4'),
|
||||
]),
|
||||
|
||||
Grid::make()
|
||||
->columns(4)
|
||||
->columnSpanFull()
|
||||
@@ -399,19 +397,16 @@ class EditServer extends EditRecord
|
||||
])
|
||||
->schema([
|
||||
TextInput::make('allocation_limit')
|
||||
->label('Allocations')
|
||||
->suffixIcon('tabler-network')
|
||||
->required()
|
||||
->minValue(0)
|
||||
->numeric(),
|
||||
TextInput::make('database_limit')
|
||||
->label('Databases')
|
||||
->suffixIcon('tabler-database')
|
||||
->required()
|
||||
->minValue(0)
|
||||
->numeric(),
|
||||
TextInput::make('backup_limit')
|
||||
->label('Backups')
|
||||
->suffixIcon('tabler-copy-check')
|
||||
->required()
|
||||
->minValue(0)
|
||||
@@ -422,7 +417,7 @@ class EditServer extends EditRecord
|
||||
'default' => 1,
|
||||
'sm' => 2,
|
||||
'md' => 3,
|
||||
'lg' => 4,
|
||||
'lg' => 3,
|
||||
])
|
||||
->schema([
|
||||
Select::make('select_image')
|
||||
@@ -443,12 +438,7 @@ class EditServer extends EditRecord
|
||||
return array_flip($images) + ['ghcr.io/custom-image' => 'Custom Image'];
|
||||
})
|
||||
->selectablePlaceholder(false)
|
||||
->columnSpan([
|
||||
'default' => 1,
|
||||
'sm' => 2,
|
||||
'md' => 3,
|
||||
'lg' => 2,
|
||||
]),
|
||||
->columnSpan(1),
|
||||
|
||||
TextInput::make('image')
|
||||
->label('Image')
|
||||
@@ -464,12 +454,7 @@ class EditServer extends EditRecord
|
||||
}
|
||||
})
|
||||
->placeholder('Enter a custom Image')
|
||||
->columnSpan([
|
||||
'default' => 1,
|
||||
'sm' => 2,
|
||||
'md' => 3,
|
||||
'lg' => 2,
|
||||
]),
|
||||
->columnSpan(2),
|
||||
|
||||
KeyValue::make('docker_labels')
|
||||
->label('Container Labels')
|
||||
@@ -488,7 +473,7 @@ class EditServer extends EditRecord
|
||||
])
|
||||
->schema([
|
||||
Select::make('egg_id')
|
||||
->disabled()
|
||||
->disabledOn('edit')
|
||||
->prefixIcon('tabler-egg')
|
||||
->columnSpan([
|
||||
'default' => 6,
|
||||
@@ -499,28 +484,7 @@ class EditServer extends EditRecord
|
||||
->relationship('egg', 'name')
|
||||
->searchable()
|
||||
->preload()
|
||||
->required()
|
||||
->hintAction(
|
||||
Action::make('change_egg')
|
||||
->action(function (array $data, Server $server, EggChangerService $service) {
|
||||
$service->handle($server, $data['egg_id'], $data['keepOldVariables']);
|
||||
|
||||
// Use redirect instead of fillForm to prevent server variables from duplicating
|
||||
$this->redirect($this->getUrl(['record' => $server, 'tab' => '-egg-tab']), true);
|
||||
})
|
||||
->form(fn (Server $server) => [
|
||||
Select::make('egg_id')
|
||||
->label('New Egg')
|
||||
->prefixIcon('tabler-egg')
|
||||
->options(fn () => Egg::all()->filter(fn (Egg $egg) => $egg->id !== $server->egg->id)->mapWithKeys(fn (Egg $egg) => [$egg->id => $egg->name]))
|
||||
->searchable()
|
||||
->preload()
|
||||
->required(),
|
||||
Toggle::make('keepOldVariables')
|
||||
->label('Keep old variables if possible?')
|
||||
->default(true),
|
||||
])
|
||||
),
|
||||
->required(),
|
||||
|
||||
ToggleButtons::make('skip_scripts')
|
||||
->label('Run Egg Install Script?')->inline()
|
||||
@@ -679,24 +643,31 @@ class EditServer extends EditRecord
|
||||
->password()
|
||||
->revealable()
|
||||
->columnSpan(1)
|
||||
->hintAction(RotateDatabasePasswordAction::make())
|
||||
->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()
|
||||
)
|
||||
->formatStateUsing(fn (Database $database) => $database->password),
|
||||
TextInput::make('remote')
|
||||
->disabled()
|
||||
->formatStateUsing(fn (Database $record) => $record->remote === '%' ? 'Anywhere ( % )' : $record->remote)
|
||||
->formatStateUsing(fn ($record) => $record->remote === '%' ? 'Anywhere ( % )' : $record->remote)
|
||||
->columnSpan(1)
|
||||
->label('Connections From'),
|
||||
TextInput::make('max_connections')
|
||||
->disabled()
|
||||
->formatStateUsing(fn (Database $record) => $record->max_connections === 0 ? 'Unlimited' : $record->max_connections)
|
||||
->formatStateUsing(fn ($record) => $record->max_connections === 0 ? 'Unlimited' : $record->max_connections)
|
||||
->columnSpan(1),
|
||||
TextInput::make('jdbc')
|
||||
TextInput::make('JDBC')
|
||||
->disabled()
|
||||
->password()
|
||||
->revealable()
|
||||
->label('JDBC Connection String')
|
||||
->columnSpan(2)
|
||||
->formatStateUsing(fn (Database $record) => $record->jdbc),
|
||||
->formatStateUsing(fn (Get $get, $record) => 'jdbc:mysql://' . $get('username') . ':' . urlencode($record->password) . '@' . $record->host->host . ':' . $record->host->port . '/' . $get('database')),
|
||||
])
|
||||
->relationship('databases')
|
||||
->deletable(false)
|
||||
@@ -735,10 +706,7 @@ class EditServer extends EditRecord
|
||||
->label('Database Host')
|
||||
->required()
|
||||
->placeholder('Select Database Host')
|
||||
->options(fn (Server $server) => DatabaseHost::query()
|
||||
->whereHas('nodes', fn ($query) => $query->where('nodes.id', $server->node_id))
|
||||
->pluck('name', 'id')
|
||||
)
|
||||
->relationship('node.databaseHosts', 'name')
|
||||
->default(fn () => (DatabaseHost::query()->first())?->id)
|
||||
->selectablePlaceholder(false),
|
||||
TextInput::make('database')
|
||||
@@ -902,7 +870,7 @@ class EditServer extends EditRecord
|
||||
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'),
|
||||
];
|
||||
|
||||
@@ -957,4 +925,13 @@ class EditServer extends EditRecord
|
||||
->mapWithKeys(fn ($value) => [$value => $value])
|
||||
->all();
|
||||
}
|
||||
|
||||
protected function rotatePassword(DatabasePasswordService $service, Database $record, Set $set, Get $get): void
|
||||
{
|
||||
$newPassword = $service->handle($record);
|
||||
$jdbcString = 'jdbc:mysql://' . $get('username') . ':' . urlencode($newPassword) . '@' . $record->host->host . ':' . $record->host->port . '/' . $get('database');
|
||||
|
||||
$set('password', $newPassword);
|
||||
$set('JDBC', $jdbcString);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
<?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;
|
||||
@@ -1,16 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\ServerResource\RelationManagers;
|
||||
namespace App\Filament\Resources\ServerResource\RelationManagers;
|
||||
|
||||
use App\Filament\Admin\Resources\ServerResource\Pages\CreateServer;
|
||||
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;
|
||||
use Filament\Forms\Get;
|
||||
use Filament\Forms\Set;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Tables;
|
||||
@@ -74,13 +71,12 @@ 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()
|
||||
->helperText("Usually your machine's public IP unless you are port forwarding.")
|
||||
->afterStateUpdated(fn (Set $set) => $set('allocation_ports', []))
|
||||
->required(),
|
||||
TextInput::make('allocation_alias')
|
||||
->label('Alias')
|
||||
@@ -98,9 +94,54 @@ class AllocationsRelationManager extends RelationManager
|
||||
->label('Ports')
|
||||
->inlineLabel()
|
||||
->live()
|
||||
->afterStateUpdated(fn ($state, Set $set, Get $get) => $set('allocation_ports',
|
||||
CreateServer::retrieveValidPorts($this->getOwnerRecord()->node, $state, $get('allocation_ip')))
|
||||
)
|
||||
->afterStateUpdated(function ($state, Set $set) {
|
||||
$ports = collect();
|
||||
$update = false;
|
||||
foreach ($state as $portEntry) {
|
||||
if (!str_contains($portEntry, '-')) {
|
||||
if (is_numeric($portEntry)) {
|
||||
$ports->push((int) $portEntry);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Do not add non numerical ports
|
||||
$update = true;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$update = true;
|
||||
[$start, $end] = explode('-', $portEntry);
|
||||
if (!is_numeric($start) || !is_numeric($end)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$start = max((int) $start, 0);
|
||||
$end = min((int) $end, 2 ** 16 - 1);
|
||||
foreach (range($start, $end) as $i) {
|
||||
$ports->push($i);
|
||||
}
|
||||
}
|
||||
|
||||
$uniquePorts = $ports->unique()->values();
|
||||
if ($ports->count() > $uniquePorts->count()) {
|
||||
$update = true;
|
||||
$ports = $uniquePorts;
|
||||
}
|
||||
|
||||
$sortedPorts = $ports->sort()->values();
|
||||
if ($sortedPorts->all() !== $ports->all()) {
|
||||
$update = true;
|
||||
$ports = $sortedPorts;
|
||||
}
|
||||
|
||||
$ports = $ports->filter(fn ($port) => $port > 1024 && $port < 65535)->values();
|
||||
|
||||
if ($update) {
|
||||
$set('allocation_ports', $ports->all());
|
||||
}
|
||||
})
|
||||
->splitKeys(['Tab', ' ', ','])
|
||||
->required(),
|
||||
])
|
||||
@@ -110,7 +151,6 @@ class AllocationsRelationManager extends RelationManager
|
||||
->associateAnother(false)
|
||||
->preloadRecordSelect()
|
||||
->recordSelectOptionsQuery(fn ($query) => $query->whereBelongsTo($this->getOwnerRecord()->node)->whereNull('server_id'))
|
||||
->recordSelectSearchColumns(['ip', 'port'])
|
||||
->label('Add Allocation'),
|
||||
])
|
||||
->bulkActions([
|
||||
@@ -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,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -31,7 +31,6 @@ class UserResource extends Resource
|
||||
{
|
||||
return [
|
||||
'index' => Pages\ListUsers::route('/'),
|
||||
'create' => Pages\CreateUser::route('/create'),
|
||||
'edit' => Pages\EditUser::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
@@ -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;
|
||||
@@ -14,7 +14,9 @@ use chillerlan\QRCode\Common\EccLevel;
|
||||
use chillerlan\QRCode\Common\Version;
|
||||
use chillerlan\QRCode\QRCode;
|
||||
use chillerlan\QRCode\QROptions;
|
||||
use Closure;
|
||||
use DateTimeZone;
|
||||
use Exception;
|
||||
use Filament\Forms\Components\Actions;
|
||||
use Filament\Forms\Components\Actions\Action;
|
||||
use Filament\Forms\Components\Grid;
|
||||
@@ -29,9 +31,7 @@ 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;
|
||||
@@ -43,7 +43,7 @@ use Laravel\Socialite\Facades\Socialite;
|
||||
/**
|
||||
* @method User getUser()
|
||||
*/
|
||||
class EditProfile extends BaseEditProfile
|
||||
class EditProfile extends \Filament\Pages\Auth\EditProfile
|
||||
{
|
||||
private ToggleTwoFactorService $toggleTwoFactorService;
|
||||
|
||||
@@ -54,7 +54,7 @@ class EditProfile extends BaseEditProfile
|
||||
|
||||
public function getMaxWidth(): MaxWidth|string
|
||||
{
|
||||
return config('panel.filament.display-width', 'screen-2xl');
|
||||
return MaxWidth::SevenExtraLarge;
|
||||
}
|
||||
|
||||
protected function getForms(): array
|
||||
@@ -142,7 +142,7 @@ class EditProfile extends BaseEditProfile
|
||||
continue;
|
||||
}
|
||||
|
||||
$unlink = array_key_exists($name, $this->getUser()->oauth ?? []);
|
||||
$unlink = array_key_exists($name, $this->getUser()->oauth);
|
||||
|
||||
$providers[] = Action::make("oauth_$name")
|
||||
->label(($unlink ? 'Unlink ' : 'Link ') . Str::title($name))
|
||||
@@ -272,25 +272,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();
|
||||
}),
|
||||
]),
|
||||
@@ -361,19 +352,7 @@ class EditProfile extends BaseEditProfile
|
||||
}
|
||||
|
||||
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,6 +360,21 @@ class EditProfile extends BaseEditProfile
|
||||
return parent::handleRecordUpdate($record, $data);
|
||||
}
|
||||
|
||||
public function exception(Exception $e, Closure $stopPropagation): void
|
||||
{
|
||||
if ($e instanceof TwoFactorAuthenticationTokenInvalid) {
|
||||
Notification::make()
|
||||
->title('Invalid 2FA Code')
|
||||
->body($e->getMessage())
|
||||
->color('danger')
|
||||
->icon('tabler-2fa')
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
$stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
protected function getFormActions(): array
|
||||
{
|
||||
return [];
|
||||
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\UserResource\Pages;
|
||||
namespace App\Filament\Resources\UserResource\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\UserResource;
|
||||
use App\Filament\Resources\UserResource;
|
||||
use App\Models\Role;
|
||||
use App\Models\User;
|
||||
use Filament\Actions\DeleteAction;
|
||||
@@ -24,25 +24,19 @@ class EditUser extends EditRecord
|
||||
return $form
|
||||
->schema([
|
||||
Section::make()->schema([
|
||||
TextInput::make('username')
|
||||
->required()
|
||||
->minLength(3)
|
||||
->maxLength(255),
|
||||
TextInput::make('email')
|
||||
->email()
|
||||
->required()
|
||||
->maxLength(255),
|
||||
TextInput::make('username')->required()->minLength(3)->maxLength(255),
|
||||
TextInput::make('email')->email()->required()->maxLength(255),
|
||||
TextInput::make('password')
|
||||
->dehydrateStateUsing(fn (string $state): string => Hash::make($state))
|
||||
->dehydrated(fn (?string $state): bool => filled($state))
|
||||
->required(fn (string $operation): bool => $operation === 'create')
|
||||
->password(),
|
||||
Select::make('language')
|
||||
->required()
|
||||
->hidden()
|
||||
->default('en')
|
||||
->options(fn (User $user) => $user->getAvailableLanguages()),
|
||||
Hidden::make('skipValidation')
|
||||
->default(true),
|
||||
Hidden::make('skipValidation')->default(true),
|
||||
CheckboxList::make('roles')
|
||||
->disabled(fn (User $user) => $user->id === auth()->user()->id)
|
||||
->disableOptionWhen(fn (string $value): bool => $value == Role::getRootAdmin()->id)
|
||||
@@ -50,8 +44,7 @@ class EditUser extends EditRecord
|
||||
->label('Admin Roles')
|
||||
->columnSpanFull()
|
||||
->bulkToggleable(false),
|
||||
])
|
||||
->columns(['default' => 1, 'lg' => 3]),
|
||||
])->columns(),
|
||||
]);
|
||||
}
|
||||
|
||||
134
app/Filament/Resources/UserResource/Pages/ListUsers.php
Normal file
134
app/Filament/Resources/UserResource/Pages/ListUsers.php
Normal file
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\UserResource\Pages;
|
||||
|
||||
use App\Filament\Resources\UserResource;
|
||||
use App\Models\Role;
|
||||
use App\Models\User;
|
||||
use App\Services\Users\UserCreationService;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Forms\Components\CheckboxList;
|
||||
use Filament\Forms\Components\Grid;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Filament\Tables\Actions\BulkActionGroup;
|
||||
use Filament\Tables\Actions\DeleteBulkAction;
|
||||
use Filament\Tables\Actions\EditAction;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Tables\Columns\ImageColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class ListUsers extends ListRecords
|
||||
{
|
||||
protected static string $resource = UserResource::class;
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->searchable(false)
|
||||
->columns([
|
||||
ImageColumn::make('picture')
|
||||
->visibleFrom('lg')
|
||||
->label('')
|
||||
->extraImgAttributes(['class' => 'rounded-full'])
|
||||
->defaultImageUrl(fn (User $user) => 'https://gravatar.com/avatar/' . md5(strtolower($user->email))),
|
||||
TextColumn::make('external_id')
|
||||
->searchable()
|
||||
->hidden(),
|
||||
TextColumn::make('uuid')
|
||||
->label('UUID')
|
||||
->hidden()
|
||||
->searchable(),
|
||||
TextColumn::make('username')
|
||||
->searchable(),
|
||||
TextColumn::make('email')
|
||||
->searchable()
|
||||
->icon('tabler-mail'),
|
||||
IconColumn::make('use_totp')
|
||||
->label('2FA')
|
||||
->visibleFrom('lg')
|
||||
->icon(fn (User $user) => $user->use_totp ? 'tabler-lock' : 'tabler-lock-open-off')
|
||||
->boolean()->sortable(),
|
||||
TextColumn::make('roles_count')
|
||||
->counts('roles')
|
||||
->icon('tabler-users-group')
|
||||
->label('Roles')
|
||||
->formatStateUsing(fn (User $user, $state) => $state . ($user->isRootAdmin() ? ' (Root Admin)' : '')),
|
||||
TextColumn::make('servers_count')
|
||||
->counts('servers')
|
||||
->icon('tabler-server')
|
||||
->label('Servers'),
|
||||
TextColumn::make('subusers_count')
|
||||
->visibleFrom('sm')
|
||||
->label('Subusers')
|
||||
->counts('subusers')
|
||||
->icon('tabler-users'),
|
||||
// ->formatStateUsing(fn (string $state, $record): string => (string) ($record->servers_count + $record->subusers_count))
|
||||
])
|
||||
->actions([
|
||||
EditAction::make(),
|
||||
])
|
||||
->checkIfRecordIsSelectableUsing(fn (User $user) => auth()->user()->id !== $user->id && !$user->servers_count)
|
||||
->bulkActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make()
|
||||
->authorize(fn () => auth()->user()->can('delete user')),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make('create')
|
||||
->label('Create User')
|
||||
->createAnother(false)
|
||||
->form([
|
||||
Grid::make()
|
||||
->schema([
|
||||
TextInput::make('username')
|
||||
->alphaNum()
|
||||
->required()
|
||||
->unique()
|
||||
->minLength(3)
|
||||
->maxLength(255),
|
||||
TextInput::make('email')
|
||||
->email()
|
||||
->required()
|
||||
->unique()
|
||||
->maxLength(255),
|
||||
TextInput::make('password')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->hintIconTooltip('Providing a user password is optional. New user email will prompt users to create a password the first time they login.')
|
||||
->password(),
|
||||
CheckboxList::make('roles')
|
||||
->disableOptionWhen(fn (string $value): bool => $value == Role::getRootAdmin()->id)
|
||||
->relationship('roles', 'name')
|
||||
->dehydrated()
|
||||
->label('Admin Roles')
|
||||
->columnSpanFull()
|
||||
->bulkToggleable(false),
|
||||
]),
|
||||
])
|
||||
->successRedirectUrl(route('filament.admin.resources.users.index'))
|
||||
->action(function (array $data, UserCreationService $creationService) {
|
||||
$roles = $data['roles'];
|
||||
$roles = collect($roles)->map(fn ($role) => Role::findById($role));
|
||||
unset($data['roles']);
|
||||
|
||||
$user = $creationService->handle($data);
|
||||
|
||||
$user->syncRoles($roles);
|
||||
|
||||
Notification::make()
|
||||
->title('User Created!')
|
||||
->success()
|
||||
->send();
|
||||
|
||||
return redirect()->route('filament.admin.resources.users.index');
|
||||
}),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\UserResource\RelationManagers;
|
||||
namespace App\Filament\Resources\UserResource\RelationManagers;
|
||||
|
||||
use App\Enums\ServerState;
|
||||
use App\Models\Server;
|
||||
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources;
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Filament\Admin\Resources\WebhookResource\Pages;
|
||||
use App\Filament\Resources\WebhookResource\Pages;
|
||||
use App\Models\WebhookConfiguration;
|
||||
use Filament\Resources\Resource;
|
||||
|
||||
@@ -10,15 +10,11 @@ class WebhookResource extends Resource
|
||||
{
|
||||
protected static ?string $model = WebhookConfiguration::class;
|
||||
|
||||
protected static ?string $modelLabel = 'Webhook';
|
||||
|
||||
protected static ?string $pluralModelLabel = 'Webhooks';
|
||||
|
||||
protected static ?string $navigationIcon = 'tabler-webhook';
|
||||
|
||||
protected static ?string $navigationGroup = 'Advanced';
|
||||
|
||||
protected static ?string $recordTitleAttribute = 'description';
|
||||
protected static ?string $label = 'Webhooks';
|
||||
|
||||
public static function getNavigationBadge(): ?string
|
||||
{
|
||||
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\WebhookResource\Pages;
|
||||
namespace App\Filament\Resources\WebhookResource\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\WebhookResource;
|
||||
use App\Filament\Resources\WebhookResource;
|
||||
use App\Models\WebhookConfiguration;
|
||||
use Filament\Forms\Components\CheckboxList;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
@@ -13,20 +13,6 @@ class CreateWebhookConfiguration extends CreateRecord
|
||||
{
|
||||
protected static string $resource = WebhookResource::class;
|
||||
|
||||
protected static bool $canCreateAnother = false;
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
$this->getCreateFormAction()->formId('form'),
|
||||
];
|
||||
}
|
||||
|
||||
protected function getFormActions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function form(Form $form): Form
|
||||
{
|
||||
return $form
|
||||
@@ -1,9 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\WebhookResource\Pages;
|
||||
namespace App\Filament\Resources\WebhookResource\Pages;
|
||||
|
||||
use App\Models\WebhookConfiguration;
|
||||
use App\Filament\Admin\Resources\WebhookResource;
|
||||
use App\Filament\Resources\WebhookResource;
|
||||
use Filament\Actions;
|
||||
use Filament\Forms\Components\CheckboxList;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\WebhookResource\Pages;
|
||||
namespace App\Filament\Resources\WebhookResource\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\WebhookResource;
|
||||
use App\Filament\Resources\WebhookResource;
|
||||
use App\Models\WebhookConfiguration;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace App\Filament\Server\Pages;
|
||||
|
||||
use App\Enums\ContainerStatus;
|
||||
use App\Filament\Server\Widgets\ServerConsole;
|
||||
use App\Filament\Server\Widgets\ServerCpuChart;
|
||||
use App\Filament\Server\Widgets\ServerMemoryChart;
|
||||
@@ -12,8 +11,6 @@ use App\Models\Server;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Pages\Page;
|
||||
use Filament\Support\Enums\ActionSize;
|
||||
use Livewire\Attributes\On;
|
||||
|
||||
class Console extends Page
|
||||
{
|
||||
@@ -23,8 +20,6 @@ class Console extends Page
|
||||
|
||||
protected static string $view = 'filament.server.pages.console';
|
||||
|
||||
public ContainerStatus $status = ContainerStatus::Offline;
|
||||
|
||||
public function getWidgetData(): array
|
||||
{
|
||||
return [
|
||||
@@ -54,18 +49,6 @@ class Console extends Page
|
||||
return 3;
|
||||
}
|
||||
|
||||
#[On('console-status')]
|
||||
public function receivedConsoleUpdate(?string $state = null): void
|
||||
{
|
||||
if ($state) {
|
||||
$this->status = ContainerStatus::from($state);
|
||||
}
|
||||
|
||||
$this->cachedHeaderActions = [];
|
||||
|
||||
$this->cacheHeaderActions();
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
/** @var Server $server */
|
||||
@@ -74,29 +57,16 @@ class Console extends Page
|
||||
return [
|
||||
Action::make('start')
|
||||
->color('primary')
|
||||
->size(ActionSize::ExtraLarge)
|
||||
->action(fn () => $this->dispatch('setServerState', state: 'start', uuid: $server->uuid))
|
||||
->disabled(fn () => $server->isInConflictState() || !$this->status->isStartable()),
|
||||
->action(fn () => $this->dispatch('setServerState', state: 'start'))
|
||||
->disabled(fn () => $server->isInConflictState()),
|
||||
Action::make('restart')
|
||||
->color('gray')
|
||||
->size(ActionSize::ExtraLarge)
|
||||
->action(fn () => $this->dispatch('setServerState', state: 'restart', uuid: $server->uuid))
|
||||
->disabled(fn () => $server->isInConflictState() || !$this->status->isRestartable()),
|
||||
->action(fn () => $this->dispatch('setServerState', state: 'restart'))
|
||||
->disabled(fn () => $server->isInConflictState() || $server->retrieveStatus() == 'offline'),
|
||||
Action::make('stop')
|
||||
->color('danger')
|
||||
->size(ActionSize::ExtraLarge)
|
||||
->action(fn () => $this->dispatch('setServerState', state: 'stop', uuid: $server->uuid))
|
||||
->hidden(fn () => $this->status->isStartingOrStopping() || $this->status->isKillable())
|
||||
->disabled(fn () => $server->isInConflictState() || !$this->status->isStoppable()),
|
||||
Action::make('kill')
|
||||
->color('danger')
|
||||
->requiresConfirmation()
|
||||
->modalHeading('Do you wish to kill this server?')
|
||||
->modalDescription('This can result in data corruption and/or data loss!')
|
||||
->modalSubmitActionLabel('Kill Server')
|
||||
->size(ActionSize::ExtraLarge)
|
||||
->action(fn () => $this->dispatch('setServerState', state: 'kill', uuid: $server->uuid))
|
||||
->hidden(fn () => $server->isInConflictState() || !$this->status->isKillable()),
|
||||
->action(fn () => $this->dispatch('setServerState', state: 'stop'))
|
||||
->disabled(fn () => $server->isInConflictState() || $server->retrieveStatus() == 'offline'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ use Filament\Notifications\Notification;
|
||||
use Filament\Support\Enums\Alignment;
|
||||
use GuzzleHttp\Exception\TransferException;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Number;
|
||||
|
||||
class Settings extends ServerFormPage
|
||||
{
|
||||
@@ -54,7 +53,7 @@ class Settings extends ServerFormPage
|
||||
->schema([
|
||||
TextInput::make('name')
|
||||
->label('Server Name')
|
||||
->disabled(fn () => !auth()->user()->can(Permission::ACTION_SETTINGS_RENAME, $server))
|
||||
->disabled(!auth()->user()->can(Permission::ACTION_SETTINGS_RENAME, $server))
|
||||
->required()
|
||||
->columnSpan([
|
||||
'default' => 1,
|
||||
@@ -66,8 +65,7 @@ class Settings extends ServerFormPage
|
||||
->afterStateUpdated(fn ($state, Server $server) => $this->updateName($state, $server)),
|
||||
Textarea::make('description')
|
||||
->label('Server Description')
|
||||
->hidden(!config('panel.editable_server_descriptions'))
|
||||
->disabled(fn () => !auth()->user()->can(Permission::ACTION_SETTINGS_RENAME, $server))
|
||||
->disabled(!auth()->user()->can(Permission::ACTION_SETTINGS_RENAME, $server))
|
||||
->columnSpan([
|
||||
'default' => 1,
|
||||
'sm' => 2,
|
||||
@@ -100,48 +98,21 @@ class Settings extends ServerFormPage
|
||||
'lg' => 3,
|
||||
])
|
||||
->schema([
|
||||
TextInput::make('cpu')
|
||||
->label('')
|
||||
->prefix('CPU')
|
||||
->prefixIcon('tabler-cpu')
|
||||
->columnSpan(1)
|
||||
->disabled()
|
||||
->formatStateUsing(fn ($state, Server $server) => !$state ? 'Unlimited' : Number::format($server->cpu, locale: auth()->user()->language) . '%'),
|
||||
TextInput::make('memory')
|
||||
->label('')
|
||||
->prefix('Memory')
|
||||
->prefixIcon('tabler-device-desktop-analytics')
|
||||
->columnSpan(1)
|
||||
->disabled()
|
||||
->formatStateUsing(fn ($state, Server $server) => !$state ? 'Unlimited' : convert_bytes_to_readable($server->memory * 2 ** 20)),
|
||||
TextInput::make('disk')
|
||||
->label('')
|
||||
->prefix('Disk Space')
|
||||
->prefixIcon('tabler-device-sd-card')
|
||||
->columnSpan(1)
|
||||
->disabled()
|
||||
->formatStateUsing(fn ($state, Server $server) => !$state ? 'Unlimited' : convert_bytes_to_readable($server->disk * 2 ** 20)),
|
||||
TextInput::make('backup_limit')
|
||||
->label('')
|
||||
->prefix('Backups')
|
||||
->prefixIcon('tabler-file-zip')
|
||||
->label('Backup Limit')
|
||||
->columnSpan(1)
|
||||
->disabled()
|
||||
->formatStateUsing(fn ($state, Server $server) => !$state ? 'No Backups' : $server->backups->count() . ' of ' . $state),
|
||||
->formatStateUsing(fn ($state, Server $server) => !$state ? 'No Backups can be created' : $server->backups->count() . ' of ' . $state),
|
||||
TextInput::make('database_limit')
|
||||
->label('')
|
||||
->prefix('Databases')
|
||||
->prefixIcon('tabler-database')
|
||||
->label('Database Limit')
|
||||
->columnSpan(1)
|
||||
->disabled()
|
||||
->formatStateUsing(fn ($state, Server $server) => !$state ? 'No Databases' : $server->databases->count() . ' of ' . $state),
|
||||
->formatStateUsing(fn ($state, Server $server) => !$state ? 'No Databases can be created' : $server->databases->count() . ' of ' . $state),
|
||||
TextInput::make('allocation_limit')
|
||||
->label('')
|
||||
->prefix('Allocations')
|
||||
->prefixIcon('tabler-network')
|
||||
->label('Allocation Limit')
|
||||
->columnSpan(1)
|
||||
->disabled()
|
||||
->formatStateUsing(fn ($state, Server $server) => !$state ? 'No Additional Allocations' : $server->allocations->count() . ' of ' . $state),
|
||||
->formatStateUsing(fn ($state, Server $server) => !$state ? 'No additional Allocations can be created' : $server->allocations->count() . ' of ' . ($state + 1)),
|
||||
]),
|
||||
]),
|
||||
Section::make('Node Information')
|
||||
@@ -196,7 +167,7 @@ class Settings extends ServerFormPage
|
||||
->footerActions([
|
||||
Action::make('reinstall')
|
||||
->color('danger')
|
||||
->disabled(fn () => !auth()->user()->can(Permission::ACTION_SETTINGS_REINSTALL, $server))
|
||||
->disabled(!auth()->user()->can(Permission::ACTION_SETTINGS_REINSTALL, $server))
|
||||
->label('Reinstall')
|
||||
->requiresConfirmation()
|
||||
->modalHeading('Are you sure you want to reinstall the server?')
|
||||
@@ -268,7 +239,7 @@ class Settings extends ServerFormPage
|
||||
|
||||
public function updateDescription(string $description, Server $server): void
|
||||
{
|
||||
abort_unless(auth()->user()->can(Permission::ACTION_SETTINGS_RENAME, $server) && config('panel.editable_server_descriptions'), 403);
|
||||
abort_unless(auth()->user()->can(Permission::ACTION_SETTINGS_RENAME, $server), 403);
|
||||
|
||||
$original = $server->description;
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ class Startup extends ServerFormPage
|
||||
->label('Docker Image')
|
||||
->live()
|
||||
->visible(fn (Server $server) => in_array($server->image, $server->egg->docker_images))
|
||||
->disabled(fn () => !auth()->user()->can(Permission::ACTION_STARTUP_DOCKER_IMAGE, $server))
|
||||
->disabled(!auth()->user()->can(Permission::ACTION_STARTUP_DOCKER_IMAGE, $server))
|
||||
->afterStateUpdated(function ($state, Server $server) {
|
||||
$original = $server->image;
|
||||
$server->forceFill(['image' => $state])->saveOrFail();
|
||||
@@ -97,7 +97,7 @@ class Startup extends ServerFormPage
|
||||
->label('')
|
||||
->relationship('viewableServerVariables')
|
||||
->grid()
|
||||
->disabled(fn () => !auth()->user()->can(Permission::ACTION_STARTUP_UPDATE, $server))
|
||||
->disabled(!auth()->user()->can(Permission::ACTION_STARTUP_UPDATE, $server))
|
||||
->reorderable(false)->addable(false)->deletable(false)
|
||||
->schema(function () {
|
||||
$text = TextInput::make('variable_value')
|
||||
@@ -153,7 +153,7 @@ class Startup extends ServerFormPage
|
||||
|
||||
public static function canAccess(): bool
|
||||
{
|
||||
return parent::canAccess() && auth()->user()->can(Permission::ACTION_STARTUP_READ, Filament::getTenant());
|
||||
return auth()->user()->can(Permission::ACTION_STARTUP_READ, Filament::getTenant());
|
||||
}
|
||||
|
||||
private function shouldHideComponent(ServerVariable $serverVariable, Component $component): bool
|
||||
|
||||
@@ -17,9 +17,9 @@ class ActivityResource extends Resource
|
||||
{
|
||||
protected static ?string $model = ActivityLog::class;
|
||||
|
||||
protected static ?string $modelLabel = 'Activity';
|
||||
protected static ?string $label = 'Activity';
|
||||
|
||||
protected static ?string $pluralModelLabel = 'Activity';
|
||||
protected static ?string $pluralLabel = 'Activity';
|
||||
|
||||
protected static ?int $navigationSort = 8;
|
||||
|
||||
@@ -52,6 +52,19 @@ class ActivityResource extends Resource
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: find better way handle server conflict state
|
||||
public static function canAccess(): bool
|
||||
{
|
||||
/** @var Server $server */
|
||||
$server = Filament::getTenant();
|
||||
|
||||
if ($server->isInConflictState()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return parent::canAccess();
|
||||
}
|
||||
|
||||
public static function canViewAny(): bool
|
||||
{
|
||||
return auth()->user()->can(Permission::ACTION_ACTIVITY_READ, Filament::getTenant());
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace App\Filament\Server\Resources\ActivityResource\Pages;
|
||||
use App\Filament\Server\Resources\ActivityResource;
|
||||
use App\Models\ActivityLog;
|
||||
use App\Models\User;
|
||||
use App\Filament\Components\Tables\Columns\DateTimeColumn;
|
||||
use App\Tables\Columns\DateTimeColumn;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
@@ -14,9 +14,9 @@ class AllocationResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Allocation::class;
|
||||
|
||||
protected static ?string $modelLabel = 'Network';
|
||||
protected static ?string $label = 'Network';
|
||||
|
||||
protected static ?string $pluralModelLabel = 'Network';
|
||||
protected static ?string $pluralLabel = 'Network';
|
||||
|
||||
protected static ?int $navigationSort = 7;
|
||||
|
||||
|
||||
@@ -35,10 +35,8 @@ class ListAllocations extends ListRecords
|
||||
->hidden(),
|
||||
TextColumn::make('port'),
|
||||
TextInputColumn::make('notes')
|
||||
->visibleFrom('sm')
|
||||
->disabled(fn () => !auth()->user()->can(Permission::ACTION_ALLOCATION_UPDATE, $server))
|
||||
->label('Notes')
|
||||
->placeholder('No Notes'),
|
||||
->label('Notes'),
|
||||
IconColumn::make('primary')
|
||||
->icon(fn ($state) => match ($state) {
|
||||
true => 'tabler-star-filled',
|
||||
|
||||
@@ -12,8 +12,8 @@ use App\Models\Server;
|
||||
use App\Repositories\Daemon\DaemonBackupRepository;
|
||||
use App\Services\Backups\DownloadLinkService;
|
||||
use App\Services\Backups\InitiateBackupService;
|
||||
use App\Filament\Components\Tables\Columns\BytesColumn;
|
||||
use App\Filament\Components\Tables\Columns\DateTimeColumn;
|
||||
use App\Tables\Columns\BytesColumn;
|
||||
use App\Tables\Columns\DateTimeColumn;
|
||||
use Filament\Actions;
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Forms\Components\Checkbox;
|
||||
@@ -44,7 +44,8 @@ class ListBackups extends ListRecords
|
||||
->schema([
|
||||
TextInput::make('name')
|
||||
->label('Name')
|
||||
->columnSpanFull(),
|
||||
->columnSpanFull()
|
||||
->required(),
|
||||
TextArea::make('ignored')
|
||||
->columnSpanFull()
|
||||
->label('Ignored Files & Directories'),
|
||||
@@ -73,7 +74,6 @@ class ListBackups extends ListRecords
|
||||
->label('Successful')
|
||||
->boolean(),
|
||||
IconColumn::make('is_locked')
|
||||
->visibleFrom('md')
|
||||
->label('Lock Status')
|
||||
->icon(fn (Backup $backup) => !$backup->is_locked ? 'tabler-lock-open' : 'tabler-lock'),
|
||||
])
|
||||
|
||||
@@ -2,20 +2,21 @@
|
||||
|
||||
namespace App\Filament\Server\Resources\DatabaseResource\Pages;
|
||||
|
||||
use App\Filament\Components\Forms\Actions\RotateDatabasePasswordAction;
|
||||
use App\Filament\Components\Tables\Columns\DateTimeColumn;
|
||||
use App\Filament\Server\Resources\DatabaseResource;
|
||||
use App\Models\Database;
|
||||
use App\Models\DatabaseHost;
|
||||
use App\Models\Permission;
|
||||
use App\Models\Server;
|
||||
use App\Services\Databases\DatabaseManagementService;
|
||||
use App\Services\Databases\DatabasePasswordService;
|
||||
use App\Tables\Columns\DateTimeColumn;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Forms\Components\Actions\Action;
|
||||
use Filament\Forms\Components\Grid;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Forms\Get;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Filament\Tables\Actions\DeleteAction;
|
||||
use Filament\Tables\Actions\ViewAction;
|
||||
@@ -43,8 +44,16 @@ class ListDatabases extends ListRecords
|
||||
->password()->revealable()
|
||||
->hidden(fn () => !auth()->user()->can(Permission::ACTION_DATABASE_VIEW_PASSWORD, $server))
|
||||
->hintAction(
|
||||
RotateDatabasePasswordAction::make()
|
||||
Action::make('rotate')
|
||||
->authorize(fn () => auth()->user()->can(Permission::ACTION_DATABASE_UPDATE, $server))
|
||||
->icon('tabler-refresh')
|
||||
->requiresConfirmation()
|
||||
->action(function (DatabasePasswordService $service, Database $database, $set, $get) {
|
||||
$newPassword = $service->handle($database);
|
||||
|
||||
$set('password', $newPassword);
|
||||
$set('JDBC', 'jdbc:mysql://' . $get('username') . ':' . urlencode($newPassword) . '@' . $database->host->host . ':' . $database->host->port . '/' . $get('database'));
|
||||
})
|
||||
)
|
||||
->suffixAction(CopyAction::make())
|
||||
->formatStateUsing(fn (Database $database) => $database->password),
|
||||
@@ -52,13 +61,13 @@ class ListDatabases extends ListRecords
|
||||
->label('Connections From'),
|
||||
TextInput::make('max_connections')
|
||||
->formatStateUsing(fn (Database $database) => $database->max_connections === 0 ? $database->max_connections : 'Unlimited'),
|
||||
TextInput::make('jdbc')
|
||||
TextInput::make('JDBC')
|
||||
->label('JDBC Connection String')
|
||||
->password()->revealable()
|
||||
->hidden(!auth()->user()->can(Permission::ACTION_DATABASE_VIEW_PASSWORD, $server))
|
||||
->suffixAction(CopyAction::make())
|
||||
->columnSpanFull()
|
||||
->formatStateUsing(fn (Database $database) => $database->jdbc),
|
||||
->formatStateUsing(fn (Get $get, Database $database) => 'jdbc:mysql://' . $get('username') . ':' . urlencode($database->password) . '@' . $database->host->host . ':' . $database->host->port . '/' . $get('database')),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -92,16 +101,10 @@ class ListDatabases extends ListRecords
|
||||
->createAnother(false)
|
||||
->form([
|
||||
Grid::make()
|
||||
->columns(2)
|
||||
->columns(3)
|
||||
->schema([
|
||||
Select::make('database_host_id')
|
||||
->label('Database Host')
|
||||
->columnSpan(2)
|
||||
->required()
|
||||
->placeholder('Select Database Host')
|
||||
->options(fn () => $server->node->databaseHosts->mapWithKeys(fn (DatabaseHost $databaseHost) => [$databaseHost->id => $databaseHost->name])),
|
||||
TextInput::make('database')
|
||||
->columnSpan(1)
|
||||
->columnSpan(2)
|
||||
->label('Database Name')
|
||||
->prefix('s'. $server->id . '_')
|
||||
->hintIcon('tabler-question-mark')
|
||||
@@ -116,6 +119,8 @@ class ListDatabases extends ListRecords
|
||||
if (empty($data['database'])) {
|
||||
$data['database'] = str_random(12);
|
||||
}
|
||||
|
||||
$data['database_host_id'] = DatabaseHost::where('node_id', $server->node_id)->first()->id;
|
||||
$data['database'] = 's'. $server->id . '_' . $data['database'];
|
||||
|
||||
$service->create($server, $data);
|
||||
|
||||
@@ -24,7 +24,6 @@ use Filament\Panel;
|
||||
use Filament\Resources\Pages\Page;
|
||||
use Filament\Resources\Pages\PageRegistration;
|
||||
use Filament\Support\Enums\Alignment;
|
||||
use Illuminate\Contracts\Filesystem\FileNotFoundException;
|
||||
use Illuminate\Routing\Route;
|
||||
use Illuminate\Support\Facades\Route as RouteFacade;
|
||||
use Livewire\Attributes\Locked;
|
||||
@@ -42,8 +41,6 @@ class EditFiles extends Page
|
||||
|
||||
protected static string $view = 'filament.server.pages.edit-file';
|
||||
|
||||
protected static ?string $title = '';
|
||||
|
||||
#[Locked]
|
||||
public string $path;
|
||||
|
||||
@@ -65,25 +62,22 @@ class EditFiles extends Page
|
||||
->options(EditorLanguages::class)
|
||||
->hidden() //TODO Fix Dis
|
||||
->default(function () {
|
||||
$ext = pathinfo($this->path, PATHINFO_EXTENSION);
|
||||
$split = explode('.', $this->path);
|
||||
|
||||
if ($ext === 'yml') {
|
||||
return 'yaml';
|
||||
}
|
||||
|
||||
return $ext;
|
||||
return end($split);
|
||||
}),
|
||||
Section::make('Editing: ' . $this->path)
|
||||
->footerActions([
|
||||
Action::make('save')
|
||||
->label('Save')
|
||||
->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_UPDATE, $server))
|
||||
->label('Save Changes')
|
||||
->authorize(auth()->user()->can(Permission::ACTION_FILE_UPDATE, $server))
|
||||
->icon('tabler-device-floppy')
|
||||
->keyBindings('mod+s')
|
||||
->action(function (DaemonFileRepository $fileRepository) use ($server) {
|
||||
->action(function () use ($server) {
|
||||
$data = $this->form->getState();
|
||||
|
||||
$fileRepository
|
||||
// @phpstan-ignore-next-line
|
||||
app(DaemonFileRepository::class)
|
||||
->setServer($server)
|
||||
->putContent($this->path, $data['editor'] ?? '');
|
||||
|
||||
@@ -97,8 +91,6 @@ class EditFiles extends Page
|
||||
->title('Saved File')
|
||||
->body(fn () => $this->path)
|
||||
->send();
|
||||
|
||||
$this->redirect(ListFiles::getUrl(['path' => dirname($this->path)]));
|
||||
}),
|
||||
Action::make('cancel')
|
||||
->label('Cancel')
|
||||
@@ -110,15 +102,11 @@ class EditFiles extends Page
|
||||
->schema([
|
||||
MonacoEditor::make('editor')
|
||||
->label('')
|
||||
->placeholderText('')
|
||||
->formatStateUsing(function (DaemonFileRepository $fileRepository) use ($server) {
|
||||
try {
|
||||
return $fileRepository
|
||||
->setServer($server)
|
||||
->getContent($this->path, config('panel.files.max_edit_size'));
|
||||
} catch (FileNotFoundException) {
|
||||
abort(404, $this->path . ' not found.');
|
||||
}
|
||||
->formatStateUsing(function () use ($server) {
|
||||
// @phpstan-ignore-next-line
|
||||
return app(DaemonFileRepository::class)
|
||||
->setServer($server)
|
||||
->getContent($this->path, config('panel.files.max_edit_size'));
|
||||
})
|
||||
->language(fn (Get $get) => $get('lang') ?? 'plaintext')
|
||||
->view('filament.plugins.monaco-editor'),
|
||||
|
||||
@@ -11,8 +11,8 @@ use App\Models\Permission;
|
||||
use App\Models\Server;
|
||||
use App\Repositories\Daemon\DaemonFileRepository;
|
||||
use App\Services\Nodes\NodeJWTService;
|
||||
use App\Filament\Components\Tables\Columns\BytesColumn;
|
||||
use App\Filament\Components\Tables\Columns\DateTimeColumn;
|
||||
use App\Tables\Columns\BytesColumn;
|
||||
use App\Tables\Columns\DateTimeColumn;
|
||||
use Carbon\CarbonImmutable;
|
||||
use Filament\Actions\Action as HeaderAction;
|
||||
use Filament\Facades\Filament;
|
||||
@@ -80,18 +80,13 @@ class ListFiles extends ListRecords
|
||||
return $table
|
||||
->paginated([15, 25, 50, 100])
|
||||
->defaultPaginationPageOption(15)
|
||||
->query(fn () => File::get($server, $this->path)->orderByDesc('is_directory'))
|
||||
->defaultSort('name')
|
||||
->query(fn () => File::get($server, $this->path)->orderByDesc('is_directory')->orderBy('name'))
|
||||
->columns([
|
||||
TextColumn::make('name')
|
||||
->searchable()
|
||||
->sortable()
|
||||
->icon(fn (File $file) => $file->getIcon()),
|
||||
BytesColumn::make('size')
|
||||
->visibleFrom('md')
|
||||
->sortable(),
|
||||
BytesColumn::make('size'),
|
||||
DateTimeColumn::make('modified_at')
|
||||
->visibleFrom('md')
|
||||
->since()
|
||||
->sortable(),
|
||||
])
|
||||
@@ -131,8 +126,9 @@ class ListFiles extends ListRecords
|
||||
->default(fn (File $file) => $file->name)
|
||||
->required(),
|
||||
])
|
||||
->action(function ($data, File $file, DaemonFileRepository $fileRepository) use ($server) {
|
||||
$fileRepository
|
||||
->action(function ($data, File $file) use ($server) {
|
||||
// @phpstan-ignore-next-line
|
||||
app(DaemonFileRepository::class)
|
||||
->setServer($server)
|
||||
->renameFiles($this->path, [['to' => $data['name'], 'from' => $file->name]]);
|
||||
|
||||
@@ -152,8 +148,9 @@ class ListFiles extends ListRecords
|
||||
->label('Copy')
|
||||
->icon('tabler-copy')
|
||||
->visible(fn (File $file) => $file->is_file)
|
||||
->action(function (File $file, DaemonFileRepository $fileRepository) use ($server) {
|
||||
$fileRepository
|
||||
->action(function (File $file) use ($server) {
|
||||
// @phpstan-ignore-next-line
|
||||
app(DaemonFileRepository::class)
|
||||
->setServer($server)
|
||||
->copyFile(join_paths($this->path, $file->name));
|
||||
|
||||
@@ -173,8 +170,9 @@ class ListFiles extends ListRecords
|
||||
->label('Download')
|
||||
->icon('tabler-download')
|
||||
->visible(fn (File $file) => $file->is_file)
|
||||
->action(function (File $file, NodeJWTService $service) use ($server) {
|
||||
$token = $service
|
||||
->action(function (File $file) use ($server) {
|
||||
// @phpstan-ignore-next-line
|
||||
$token = app(NodeJWTService::class)
|
||||
->setExpiresAt(CarbonImmutable::now()->addMinutes(15))
|
||||
->setUser(auth()->user())
|
||||
->setClaims([
|
||||
@@ -203,10 +201,11 @@ class ListFiles extends ListRecords
|
||||
Placeholder::make('new_location')
|
||||
->content(fn (Get $get) => resolve_path('./' . join_paths($this->path, $get('location')))),
|
||||
])
|
||||
->action(function ($data, File $file, DaemonFileRepository $fileRepository) use ($server) {
|
||||
->action(function ($data, File $file) use ($server) {
|
||||
$location = resolve_path(join_paths($this->path, $data['location']));
|
||||
|
||||
$fileRepository
|
||||
// @phpstan-ignore-next-line
|
||||
app(DaemonFileRepository::class)
|
||||
->setServer($server)
|
||||
->renameFiles($this->path, [['to' => $location, 'from' => $file->name]]);
|
||||
|
||||
@@ -262,14 +261,15 @@ class ListFiles extends ListRecords
|
||||
return $this->getPermissionsFromModeBit($mode);
|
||||
}),
|
||||
])
|
||||
->action(function ($data, File $file, DaemonFileRepository $fileRepository) use ($server) {
|
||||
->action(function ($data, File $file) use ($server) {
|
||||
$owner = (in_array('read', $data['owner']) ? 4 : 0) | (in_array('write', $data['owner']) ? 2 : 0) | (in_array('execute', $data['owner']) ? 1 : 0);
|
||||
$group = (in_array('read', $data['group']) ? 4 : 0) | (in_array('write', $data['group']) ? 2 : 0) | (in_array('execute', $data['group']) ? 1 : 0);
|
||||
$public = (in_array('read', $data['public']) ? 4 : 0) | (in_array('write', $data['public']) ? 2 : 0) | (in_array('execute', $data['public']) ? 1 : 0);
|
||||
|
||||
$mode = $owner . $group . $public;
|
||||
|
||||
$fileRepository
|
||||
// @phpstan-ignore-next-line
|
||||
app(DaemonFileRepository::class)
|
||||
->setServer($server)
|
||||
->chmodFiles($this->path, [['file' => $file->name, 'mode' => $mode]]);
|
||||
|
||||
@@ -282,8 +282,9 @@ class ListFiles extends ListRecords
|
||||
->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_ARCHIVE, $server))
|
||||
->label('Archive')
|
||||
->icon('tabler-archive')
|
||||
->action(function (File $file, DaemonFileRepository $fileRepository) use ($server) {
|
||||
$fileRepository
|
||||
->action(function (File $file) use ($server) {
|
||||
// @phpstan-ignore-next-line
|
||||
app(DaemonFileRepository::class)
|
||||
->setServer($server)
|
||||
->compressFiles($this->path, [$file->name]);
|
||||
|
||||
@@ -304,8 +305,9 @@ class ListFiles extends ListRecords
|
||||
->label('Unarchive')
|
||||
->icon('tabler-archive')
|
||||
->visible(fn (File $file) => $file->isArchive())
|
||||
->action(function (File $file, DaemonFileRepository $fileRepository) use ($server) {
|
||||
$fileRepository
|
||||
->action(function (File $file) use ($server) {
|
||||
// @phpstan-ignore-next-line
|
||||
app(DaemonFileRepository::class)
|
||||
->setServer($server)
|
||||
->decompressFile($this->path, $file->name);
|
||||
|
||||
@@ -329,8 +331,9 @@ class ListFiles extends ListRecords
|
||||
->requiresConfirmation()
|
||||
->modalDescription(fn (File $file) => $file->name)
|
||||
->modalHeading('Delete file?')
|
||||
->action(function (File $file, DaemonFileRepository $fileRepository) use ($server) {
|
||||
$fileRepository
|
||||
->action(function (File $file) use ($server) {
|
||||
// @phpstan-ignore-next-line
|
||||
app(DaemonFileRepository::class)
|
||||
->setServer($server)
|
||||
->deleteFiles($this->path, [$file->name]);
|
||||
|
||||
@@ -354,12 +357,14 @@ class ListFiles extends ListRecords
|
||||
Placeholder::make('new_location')
|
||||
->content(fn (Get $get) => resolve_path('./' . join_paths($this->path, $get('location') ?? ''))),
|
||||
])
|
||||
->action(function (Collection $files, $data, DaemonFileRepository $fileRepository) use ($server) {
|
||||
->action(function (Collection $files, $data) use ($server) {
|
||||
$location = resolve_path(join_paths($this->path, $data['location']));
|
||||
|
||||
// @phpstan-ignore-next-line
|
||||
$files = $files->map(fn ($file) => ['to' => $location, 'from' => $file->name])->toArray();
|
||||
$fileRepository
|
||||
|
||||
// @phpstan-ignore-next-line
|
||||
app(DaemonFileRepository::class)
|
||||
->setServer($server)
|
||||
->renameFiles($this->path, $files);
|
||||
|
||||
@@ -375,11 +380,12 @@ class ListFiles extends ListRecords
|
||||
}),
|
||||
BulkAction::make('archive')
|
||||
->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_ARCHIVE, $server))
|
||||
->action(function (Collection $files, DaemonFileRepository $fileRepository) use ($server) {
|
||||
->action(function (Collection $files) use ($server) {
|
||||
// @phpstan-ignore-next-line
|
||||
$files = $files->map(fn ($file) => $file->name)->toArray();
|
||||
|
||||
$fileRepository
|
||||
// @phpstan-ignore-next-line
|
||||
app(DaemonFileRepository::class)
|
||||
->setServer($server)
|
||||
->compressFiles($this->path, $files);
|
||||
|
||||
@@ -397,10 +403,12 @@ class ListFiles extends ListRecords
|
||||
}),
|
||||
DeleteBulkAction::make()
|
||||
->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_DELETE, $server))
|
||||
->action(function (Collection $files, DaemonFileRepository $fileRepository) use ($server) {
|
||||
->action(function (Collection $files) use ($server) {
|
||||
// @phpstan-ignore-next-line
|
||||
$files = $files->map(fn ($file) => $file->name)->toArray();
|
||||
$fileRepository
|
||||
|
||||
// @phpstan-ignore-next-line
|
||||
app(DaemonFileRepository::class)
|
||||
->setServer($server)
|
||||
->deleteFiles($this->path, $files);
|
||||
|
||||
@@ -430,8 +438,9 @@ class ListFiles extends ListRecords
|
||||
->color('gray')
|
||||
->keyBindings('')
|
||||
->modalSubmitActionLabel('Create')
|
||||
->action(function ($data, DaemonFileRepository $fileRepository) use ($server) {
|
||||
$fileRepository
|
||||
->action(function ($data) use ($server) {
|
||||
// @phpstan-ignore-next-line
|
||||
app(DaemonFileRepository::class)
|
||||
->setServer($server)
|
||||
->putContent(join_paths($this->path, $data['name']), $data['editor'] ?? '');
|
||||
|
||||
@@ -458,8 +467,9 @@ class ListFiles extends ListRecords
|
||||
->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_CREATE, $server))
|
||||
->label('New Folder')
|
||||
->color('gray')
|
||||
->action(function ($data, DaemonFileRepository $fileRepository) use ($server) {
|
||||
$fileRepository
|
||||
->action(function ($data) use ($server) {
|
||||
// @phpstan-ignore-next-line
|
||||
app(DaemonFileRepository::class)
|
||||
->setServer($server)
|
||||
->createDirectory($data['name'], $this->path);
|
||||
|
||||
@@ -475,11 +485,12 @@ class ListFiles extends ListRecords
|
||||
HeaderAction::make('upload')
|
||||
->authorize(fn () => auth()->user()->can(Permission::ACTION_FILE_CREATE, $server))
|
||||
->label('Upload')
|
||||
->action(function ($data, DaemonFileRepository $fileRepository) use ($server) {
|
||||
->action(function ($data) use ($server) {
|
||||
if (count($data['files']) > 0 && !isset($data['url'])) {
|
||||
/** @var UploadedFile $file */
|
||||
foreach ($data['files'] as $file) {
|
||||
$fileRepository
|
||||
// @phpstan-ignore-next-line
|
||||
app(DaemonFileRepository::class)
|
||||
->setServer($server)
|
||||
->putContent(join_paths($this->path, $file->getClientOriginalName()), $file->getContent());
|
||||
|
||||
@@ -489,7 +500,8 @@ class ListFiles extends ListRecords
|
||||
->log();
|
||||
}
|
||||
} elseif ($data['url'] !== null) {
|
||||
$fileRepository
|
||||
// @phpstan-ignore-next-line
|
||||
app(DaemonFileRepository::class)
|
||||
->setServer($server)
|
||||
->pull($data['url'], $this->path);
|
||||
|
||||
@@ -533,7 +545,6 @@ class ListFiles extends ListRecords
|
||||
->form([
|
||||
TextInput::make('searchTerm')
|
||||
->placeholder('Enter a search term, e.g. *.txt')
|
||||
->regex('/^[^*]*\*?[^*]*$/')
|
||||
->minLength(3),
|
||||
])
|
||||
->action(fn ($data) => redirect(SearchFiles::getUrl([
|
||||
|
||||
@@ -5,8 +5,8 @@ namespace App\Filament\Server\Resources\FileResource\Pages;
|
||||
use App\Filament\Server\Resources\FileResource;
|
||||
use App\Models\File;
|
||||
use App\Models\Server;
|
||||
use App\Filament\Components\Tables\Columns\BytesColumn;
|
||||
use App\Filament\Components\Tables\Columns\DateTimeColumn;
|
||||
use App\Tables\Columns\BytesColumn;
|
||||
use App\Tables\Columns\DateTimeColumn;
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
|
||||
@@ -14,7 +14,6 @@ use Filament\Forms\Components\Section;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Forms\Components\ToggleButtons;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Forms\Set;
|
||||
use Filament\Resources\Resource;
|
||||
@@ -64,100 +63,54 @@ class ScheduleResource extends Resource
|
||||
public static function form(Form $form): Form
|
||||
{
|
||||
return $form
|
||||
->columns([
|
||||
'default' => 4,
|
||||
'lg' => 5,
|
||||
])
|
||||
->columns(10)
|
||||
->schema([
|
||||
TextInput::make('name')
|
||||
->columnSpan([
|
||||
'default' => 4,
|
||||
'md' => 3,
|
||||
'lg' => 4,
|
||||
])
|
||||
->columnSpan(10)
|
||||
->label('Schedule Name')
|
||||
->placeholder('A human readable identifier for this schedule.')
|
||||
->autocomplete(false)
|
||||
->required(),
|
||||
ToggleButtons::make('Status')
|
||||
->formatStateUsing(fn (Schedule $schedule) => !$schedule->is_active ? 'inactive' : ($schedule->is_processing ? 'processing' : 'active'))
|
||||
->options(fn (Schedule $schedule) => !$schedule->is_active ? ['inactive' => 'Inactive'] : ($schedule->is_processing ? ['processing' => 'Processing'] : ['active' => 'Active']))
|
||||
->colors([
|
||||
'inactive' => 'danger',
|
||||
'processing' => 'warning',
|
||||
'active' => 'success',
|
||||
])
|
||||
->visibleOn('view')
|
||||
->columnSpan([
|
||||
'default' => 4,
|
||||
'md' => 1,
|
||||
'lg' => 1,
|
||||
]),
|
||||
Toggle::make('only_when_online')
|
||||
->label('Only when Server is Online?')
|
||||
->hintIconTooltip('Only execute this schedule when the server is in a running state.')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->inline(false)
|
||||
->columnSpan([
|
||||
'default' => 2,
|
||||
'lg' => 3,
|
||||
])
|
||||
->columnSpan(5)
|
||||
->required()
|
||||
->default(1),
|
||||
Toggle::make('is_active')
|
||||
->label('Enable Schedule?')
|
||||
->hintIconTooltip('This schedule will be executed automatically if enabled.')
|
||||
->hintIcon('tabler-question-mark')
|
||||
->inline(false)
|
||||
->columnSpan([
|
||||
'default' => 2,
|
||||
'lg' => 2,
|
||||
])
|
||||
->columnSpan(5)
|
||||
->required()
|
||||
->default(1),
|
||||
TextInput::make('cron_minute')
|
||||
->columnSpan([
|
||||
'default' => 2,
|
||||
'lg' => 1,
|
||||
])
|
||||
->columnSpan(2)
|
||||
->label('Minute')
|
||||
->default('*/5')
|
||||
->required(),
|
||||
TextInput::make('cron_hour')
|
||||
->columnSpan([
|
||||
'default' => 2,
|
||||
'lg' => 1,
|
||||
])
|
||||
->columnSpan(2)
|
||||
->label('Hour')
|
||||
->default('*')
|
||||
->required(),
|
||||
TextInput::make('cron_day_of_month')
|
||||
->columnSpan([
|
||||
'default' => 2,
|
||||
'lg' => 1,
|
||||
])
|
||||
->columnSpan(2)
|
||||
->label('Day of Month')
|
||||
->default('*')
|
||||
->required(),
|
||||
TextInput::make('cron_month')
|
||||
->columnSpan([
|
||||
'default' => 2,
|
||||
'lg' => 1,
|
||||
])
|
||||
->columnSpan(2)
|
||||
->label('Month')
|
||||
->default('*')
|
||||
->required(),
|
||||
TextInput::make('cron_day_of_week')
|
||||
->columnSpan([
|
||||
'default' => 2,
|
||||
'lg' => 1,
|
||||
])
|
||||
->columnSpan(2)
|
||||
->label('Day of Week')
|
||||
->default('*')
|
||||
->required(),
|
||||
Section::make('Presets')
|
||||
->hiddenOn('view')
|
||||
->columns(1)
|
||||
->schema([
|
||||
Actions::make([
|
||||
Action::make('hourly')
|
||||
|
||||
@@ -13,9 +13,8 @@ class EditSchedule extends EditRecord
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\ViewAction::make(),
|
||||
Actions\DeleteAction::make(),
|
||||
$this->getSaveFormAction()->formId('form')->label('Save'),
|
||||
$this->getCancelFormAction()->formId('form'),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -23,9 +22,4 @@ class EditSchedule extends EditRecord
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
protected function getFormActions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace App\Filament\Server\Resources\ScheduleResource\Pages;
|
||||
|
||||
use App\Filament\Server\Resources\ScheduleResource;
|
||||
use App\Models\Schedule;
|
||||
use App\Filament\Components\Tables\Columns\DateTimeColumn;
|
||||
use App\Tables\Columns\DateTimeColumn;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Filament\Tables\Actions\DeleteAction;
|
||||
@@ -33,7 +33,6 @@ class ListSchedules extends ListRecords
|
||||
->sortable(),
|
||||
DateTimeColumn::make('last_run_at')
|
||||
->label('Last run')
|
||||
->placeholder('Never')
|
||||
->since()
|
||||
->sortable(),
|
||||
DateTimeColumn::make('next_run_at')
|
||||
|
||||
@@ -2,13 +2,8 @@
|
||||
|
||||
namespace App\Filament\Server\Resources\ScheduleResource\Pages;
|
||||
|
||||
use App\Facades\Activity;
|
||||
use App\Filament\Server\Resources\ScheduleResource;
|
||||
use App\Models\Permission;
|
||||
use App\Models\Schedule;
|
||||
use App\Services\Schedules\ProcessScheduleService;
|
||||
use Filament\Actions;
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Resources\Pages\ViewRecord;
|
||||
|
||||
class ViewSchedule extends ViewRecord
|
||||
@@ -18,21 +13,6 @@ class ViewSchedule extends ViewRecord
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\Action::make('runNow')
|
||||
->authorize(fn () => auth()->user()->can(Permission::ACTION_SCHEDULE_UPDATE, Filament::getTenant()))
|
||||
->label(fn (Schedule $schedule) => $schedule->tasks->count() === 0 ? 'No tasks' : ($schedule->is_processing ? 'Processing' : 'Run now'))
|
||||
->color(fn (Schedule $schedule) => $schedule->tasks->count() === 0 || $schedule->is_processing ? 'warning' : 'primary')
|
||||
->disabled(fn (Schedule $schedule) => $schedule->tasks->count() === 0 || $schedule->is_processing)
|
||||
->action(function (ProcessScheduleService $service, Schedule $schedule) {
|
||||
$service->handle($schedule, true);
|
||||
|
||||
Activity::event('server:schedule.execute')
|
||||
->subject($schedule)
|
||||
->property('name', $schedule->name)
|
||||
->log();
|
||||
|
||||
$this->fillForm();
|
||||
}),
|
||||
Actions\EditAction::make(),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -5,13 +5,11 @@ namespace App\Filament\Server\Resources\ScheduleResource\RelationManagers;
|
||||
use App\Facades\Activity;
|
||||
use App\Models\Schedule;
|
||||
use App\Models\Task;
|
||||
use Filament\Tables\Actions\DeleteAction;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Forms\Get;
|
||||
use Filament\Tables\Actions\EditAction;
|
||||
use Filament\Tables\Table;
|
||||
use Filament\Resources\RelationManagers\RelationManager;
|
||||
use Filament\Tables\Actions\CreateAction;
|
||||
@@ -22,50 +20,6 @@ class TasksRelationManager extends RelationManager
|
||||
{
|
||||
protected static string $relationship = 'tasks';
|
||||
|
||||
private function getActionOptions(bool $full = true): array
|
||||
{
|
||||
return [
|
||||
Task::ACTION_POWER => $full ? 'Send power action' : 'Power action',
|
||||
Task::ACTION_COMMAND => $full ? 'Send command' : 'Command',
|
||||
Task::ACTION_BACKUP => $full ? 'Create backup' : 'Files to ignore',
|
||||
Task::ACTION_DELETE_FILES => $full ? 'Delete files' : 'Files to delete',
|
||||
];
|
||||
}
|
||||
|
||||
private function getTaskForm(Schedule $schedule): array
|
||||
{
|
||||
return [
|
||||
Select::make('action')
|
||||
->required()
|
||||
->live()
|
||||
->disableOptionWhen(fn (string $value): bool => $value === Task::ACTION_BACKUP && $schedule->server->backup_limit === 0)
|
||||
->options($this->getActionOptions())
|
||||
->selectablePlaceholder(false),
|
||||
Textarea::make('payload')
|
||||
->hidden(fn (Get $get) => $get('action') === Task::ACTION_POWER)
|
||||
->label(fn (Get $get) => $this->getActionOptions(false)[$get('action')] ?? 'Payload'),
|
||||
Select::make('payload')
|
||||
->visible(fn (Get $get) => $get('action') === Task::ACTION_POWER)
|
||||
->label('Power Action')
|
||||
->required()
|
||||
->options([
|
||||
'start' => 'Start',
|
||||
'restart' => 'Restart',
|
||||
'stop' => 'Stop',
|
||||
'kill' => 'Kill',
|
||||
])
|
||||
->selectablePlaceholder(false),
|
||||
TextInput::make('time_offset')
|
||||
->hidden(fn (Get $get) => config('queue.default') === 'sync' || $get('sequence_id') === 1)
|
||||
->default(0)
|
||||
->numeric()
|
||||
->minValue(0)
|
||||
->maxValue(900)
|
||||
->suffix('Seconds'),
|
||||
Toggle::make('continue_on_failure'),
|
||||
];
|
||||
}
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
/** @var Schedule $schedule */
|
||||
@@ -75,34 +29,63 @@ class TasksRelationManager extends RelationManager
|
||||
->reorderable('sequence_id', true)
|
||||
->columns([
|
||||
TextColumn::make('action')
|
||||
->state(fn (Task $task) => $this->getActionOptions()[$task->action] ?? $task->action),
|
||||
TextColumn::make('payload')
|
||||
->state(function (Task $task) {
|
||||
$payload = match ($task->payload) {
|
||||
'start', 'restart', 'stop', 'kill' => mb_ucfirst($task->payload),
|
||||
default => $task->payload
|
||||
};
|
||||
|
||||
return explode(PHP_EOL, $payload);
|
||||
})
|
||||
->badge(),
|
||||
->state(fn (Task $task) => match ($task->action) {
|
||||
Task::ACTION_POWER => 'Send power action',
|
||||
Task::ACTION_COMMAND => 'Send command',
|
||||
Task::ACTION_BACKUP => 'Create backup',
|
||||
Task::ACTION_DELETE_FILES => 'Delete files',
|
||||
default => $task->action
|
||||
}),
|
||||
TextColumn::make('time_offset')
|
||||
->hidden(fn () => config('queue.default') === 'sync')
|
||||
->suffix(' Seconds'),
|
||||
IconColumn::make('continue_on_failure')
|
||||
->boolean(),
|
||||
])
|
||||
->actions([
|
||||
EditAction::make()
|
||||
->form($this->getTaskForm($schedule)),
|
||||
DeleteAction::make(),
|
||||
])
|
||||
->headerActions([
|
||||
CreateAction::make()
|
||||
->createAnother(false)
|
||||
->label(fn () => $schedule->tasks()->count() >= config('panel.client_features.schedules.per_schedule_task_limit', 10) ? 'Task Limit Reached' : 'Create Task')
|
||||
->disabled(fn () => $schedule->tasks()->count() >= config('panel.client_features.schedules.per_schedule_task_limit', 10))
|
||||
->form($this->getTaskForm($schedule))
|
||||
->form([
|
||||
Select::make('action')
|
||||
->required()
|
||||
->live()
|
||||
->disableOptionWhen(fn (string $value): bool => $value === Task::ACTION_BACKUP && $schedule->server->backup_limit === 0)
|
||||
->options([
|
||||
Task::ACTION_POWER => 'Send power action',
|
||||
Task::ACTION_COMMAND => 'Send command',
|
||||
Task::ACTION_BACKUP => 'Create backup',
|
||||
Task::ACTION_DELETE_FILES => 'Delete files',
|
||||
]),
|
||||
Textarea::make('payload')
|
||||
->hidden(fn (Get $get) => $get('action') === Task::ACTION_POWER)
|
||||
->label(fn (Get $get) => match ($get('action')) {
|
||||
Task::ACTION_POWER => 'Power action',
|
||||
Task::ACTION_COMMAND => 'Command',
|
||||
Task::ACTION_BACKUP => 'Files to ignore',
|
||||
Task::ACTION_DELETE_FILES => 'Files to delete',
|
||||
default => 'Payload'
|
||||
}),
|
||||
Select::make('payload')
|
||||
->visible(fn (Get $get) => $get('action') === Task::ACTION_POWER)
|
||||
->label('Power Action')
|
||||
->required()
|
||||
->options([
|
||||
'start' => 'Start',
|
||||
'restart' => 'Restart',
|
||||
'stop' => 'Stop',
|
||||
'Kill' => 'Kill',
|
||||
]),
|
||||
TextInput::make('time_offset')
|
||||
->hidden(fn (Get $get) => config('queue.default') === 'sync' || $get('sequence_id') === 1)
|
||||
->default(0)
|
||||
->numeric()
|
||||
->minValue(0)
|
||||
->maxValue(900)
|
||||
->suffix('Seconds'),
|
||||
Toggle::make('continue_on_failure'),
|
||||
])
|
||||
->action(function ($data) use ($schedule) {
|
||||
$sequenceId = ($schedule->tasks()->orderByDesc('sequence_id')->first()->sequence_id ?? 0) + 1;
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@ class UserResource extends Resource
|
||||
}),
|
||||
EditAction::make()
|
||||
->label('Edit User')
|
||||
->hidden(fn (User $user) => auth()->user()->id === $user->id)
|
||||
|
||||
->authorize(fn () => auth()->user()->can(Permission::ACTION_USER_UPDATE, $server))
|
||||
->modalHeading(fn (User $user) => 'Editing ' . $user->email)
|
||||
->action(function (array $data, SubuserUpdateService $subuserUpdateService, User $user) use ($server) {
|
||||
|
||||
@@ -6,7 +6,6 @@ use App\Filament\Server\Resources\UserResource;
|
||||
use App\Models\Permission;
|
||||
use App\Models\Server;
|
||||
use App\Services\Subusers\SubuserCreationService;
|
||||
use Exception;
|
||||
use Filament\Actions;
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Forms\Components\Actions as assignAll;
|
||||
@@ -54,7 +53,8 @@ class ListUsers extends ListRecords
|
||||
'md' => 4,
|
||||
'lg' => 5,
|
||||
])
|
||||
->required(),
|
||||
->required()
|
||||
->unique(),
|
||||
assignAll::make([
|
||||
Action::make('assignAll')
|
||||
->label('Assign All')
|
||||
@@ -65,6 +65,7 @@ class ListUsers extends ListRecords
|
||||
'start',
|
||||
'stop',
|
||||
'restart',
|
||||
'kill',
|
||||
],
|
||||
'user' => [
|
||||
'read',
|
||||
@@ -364,28 +365,18 @@ class ListUsers extends ListRecords
|
||||
->modalHeading('Invite User')
|
||||
->modalSubmitActionLabel('Invite')
|
||||
->action(function (array $data, SubuserCreationService $service) use ($server) {
|
||||
$email = strtolower($data['email']);
|
||||
$email = $data['email'];
|
||||
|
||||
if (in_array('console', $data['control'])) {
|
||||
$data['websocket'][0] = 'connect';
|
||||
}
|
||||
|
||||
$permissions = collect($data)->forget('email')->map(fn ($permissions, $key) => collect($permissions)->map(fn ($permission) => "$key.$permission"))->flatten()->all();
|
||||
$service->handle($server, $email, $permissions);
|
||||
|
||||
try {
|
||||
$service->handle($server, $email, $permissions);
|
||||
|
||||
Notification::make()
|
||||
->title('User Invited!')
|
||||
->success()
|
||||
->send();
|
||||
} catch (Exception $exception) {
|
||||
Notification::make()
|
||||
->title('Failed')
|
||||
->body($exception->getMessage())
|
||||
->danger()
|
||||
->send();
|
||||
}
|
||||
Notification::make()
|
||||
->title('User Invited!')
|
||||
->success()
|
||||
->send();
|
||||
|
||||
return redirect(self::getUrl(tenant: $server));
|
||||
}),
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user