diff --git a/app/Filament/Admin/Pages/ListLogs.php b/app/Filament/Admin/Pages/ListLogs.php new file mode 100644 index 000000000..4c728b7c2 --- /dev/null +++ b/app/Filament/Admin/Pages/ListLogs.php @@ -0,0 +1,130 @@ +emptyStateHeading(trans('admin/log.empty_table')) + ->emptyStateIcon('tabler-check') + ->columns([ + NameColumn::make('date'), + LevelColumn::make(Level::ALL) + ->tooltip(trans('admin/log.total_logs')), + LevelColumn::make(Level::Error) + ->tooltip(trans('admin/log.error')), + LevelColumn::make(Level::Warning) + ->tooltip(trans('admin/log.warning')), + LevelColumn::make(Level::Notice) + ->tooltip(trans('admin/log.notice')), + LevelColumn::make(Level::Info) + ->tooltip(trans('admin/log.info')), + LevelColumn::make(Level::Debug) + ->tooltip(trans('admin/log.debug')), + ]) + ->recordActions([ + ViewLogAction::make() + ->icon('tabler-file-description')->iconSize(IconSize::Medium), + DownloadAction::make() + ->icon('tabler-file-download')->iconSize(IconSize::Medium), + Action::make('uploadLogs') + ->button() + ->hiddenLabel() + ->icon('tabler-world-upload')->iconSize(IconSize::Medium) + ->requiresConfirmation() + ->modalHeading(trans('admin/log.actions.upload_logs')) + ->modalDescription(fn ($record) => trans('admin/log.actions.upload_logs_description', ['file' => $record['date'], 'url' => 'https://logs.pelican.dev'])) + ->action(function ($record) { + $logPath = storage_path('logs/' . $record['date']); + + if (!file_exists($logPath)) { + Notification::make() + ->title(trans('admin/log.actions.log_not_found')) + ->body(trans('admin/log.actions.log_not_found_description', ['filename' => $record['date']])) + ->danger() + ->send(); + + return; + } + + $lines = file($logPath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + $totalLines = count($lines); + $uploadLines = $totalLines <= 1000 ? $lines : array_slice($lines, -1000); + $content = implode("\n", $uploadLines); + + $logUrl = 'https://logs.pelican.dev'; + try { + $response = Http::timeout(10)->asMultipart()->post($logUrl, [ + [ + 'name' => 'c', + 'contents' => $content, + ], + [ + 'name' => 'e', + 'contents' => '14d', + ], + ]); + + if ($response->failed()) { + Notification::make() + ->title(trans('admin/log.actions.failed_to_upload')) + ->body(trans('admin/log.actions.failed_to_upload_description', ['status' => $response->status()])) + ->danger() + ->send(); + + return; + } + + $data = $response->json(); + $url = $data['url']; + + Notification::make() + ->title(trans('admin/log.actions.log_upload')) + ->body("{$url}") + ->success() + ->actions([ + Action::make('viewLogs') + ->label(trans('admin/log.actions.view_logs')) + ->url($url) + ->openUrlInNewTab(true), + ]) + ->persistent() + ->send(); + + } catch (\Exception $e) { + Notification::make() + ->title(trans('admin/log.actions.failed_to_upload')) + ->body($e->getMessage()) + ->danger() + ->send(); + + return; + } + }), + DeleteAction::make() + ->icon('tabler-trash')->iconSize(IconSize::Medium), + ]); + } +} diff --git a/app/Filament/Admin/Pages/ViewLogs.php b/app/Filament/Admin/Pages/ViewLogs.php new file mode 100644 index 000000000..3e24a1b08 --- /dev/null +++ b/app/Filament/Admin/Pages/ViewLogs.php @@ -0,0 +1,105 @@ +icon('tabler-trash')->iconSize(IconSize::Medium), + DownloadAction::make(withTooltip: true) + ->icon('tabler-file-download')->iconSize(IconSize::Medium), + Action::make('uploadLogs') + ->button() + ->hiddenLabel() + ->icon('tabler-world-upload')->iconSize(IconSize::Medium) + ->requiresConfirmation() + ->tooltip(trans('admin/log.actions.upload_tooltip', ['url' => 'logs.pelican.dev'])) + ->modalHeading(trans('admin/log.actions.upload_logs')) + ->modalDescription(fn () => trans('admin/log.actions.upload_logs_description', ['file' => $this->resolveRecordDate(), 'url' => 'https://logs.pelican.dev'])) + ->action(function () { + $logPath = storage_path('logs/' . $this->resolveRecordDate()); + + if (!file_exists($logPath)) { + Notification::make() + ->title(trans('admin/log.actions.log_not_found')) + ->body(trans('admin/log.actions.log_not_found_description', ['filename' => $this->resolveRecordDate()])) + ->danger() + ->send(); + + return; + } + + $lines = file($logPath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + $totalLines = count($lines); + $uploadLines = $totalLines <= 1000 ? $lines : array_slice($lines, -1000); + $content = implode("\n", $uploadLines); + + $logUrl = 'https://logs.pelican.dev'; + try { + $response = Http::timeout(10)->asMultipart()->post($logUrl, [ + [ + 'name' => 'c', + 'contents' => $content, + ], + [ + 'name' => 'e', + 'contents' => '14d', + ], + ]); + + if ($response->failed()) { + Notification::make() + ->title(trans('admin/log.actions.failed_to_upload')) + ->body(trans('admin/log.actions.failed_to_upload_description', ['status' => $response->status()])) + ->danger() + ->send(); + + return; + } + + $data = $response->json(); + $url = $data['url']; + + Notification::make() + ->title(trans('admin/log.actions.log_upload')) + ->body("{$url}") + ->success() + ->actions([ + Action::make('viewLogs') + ->label(trans('admin/log.actions.view_logs')) + ->url($url) + ->openUrlInNewTab(true), + ]) + ->persistent() + ->send(); + + } catch (\Exception $e) { + Notification::make() + ->title(trans('admin/log.actions.failed_to_upload')) + ->body($e->getMessage()) + ->danger() + ->send(); + + return; + } + }), + BackAction::make() + ->icon('tabler-arrow-left')->iconSize(IconSize::Medium), + ]; + } +} diff --git a/app/Filament/Admin/Resources/Mounts/MountResource.php b/app/Filament/Admin/Resources/Mounts/MountResource.php index bbf8aad31..ba3c78216 100644 --- a/app/Filament/Admin/Resources/Mounts/MountResource.php +++ b/app/Filament/Admin/Resources/Mounts/MountResource.php @@ -126,7 +126,7 @@ class MountResource extends Resource ToggleButtons::make('read_only') ->label(trans('admin/mount.read_only')) ->helperText(trans('admin/mount.read_only_help')) - ->stateCast(new BooleanStateCast(false)) + ->stateCast(new BooleanStateCast(false, true)) ->options([ false => trans('admin/mount.toggles.writable'), true => trans('admin/mount.toggles.read_only'), @@ -140,8 +140,7 @@ class MountResource extends Resource true => 'success', ]) ->inline() - ->default(false) - ->required(), + ->default(false), TextInput::make('source') ->label(trans('admin/mount.source')) ->required() diff --git a/app/Filament/Admin/Resources/Nodes/Pages/EditNode.php b/app/Filament/Admin/Resources/Nodes/Pages/EditNode.php index 175e4a1c2..29aedeb01 100644 --- a/app/Filament/Admin/Resources/Nodes/Pages/EditNode.php +++ b/app/Filament/Admin/Resources/Nodes/Pages/EditNode.php @@ -4,7 +4,7 @@ namespace App\Filament\Admin\Resources\Nodes\Pages; use App\Filament\Admin\Resources\Nodes\NodeResource; use App\Models\Node; -use App\Repositories\Daemon\DaemonConfigurationRepository; +use App\Repositories\Daemon\DaemonSystemRepository; use App\Services\Helpers\SoftwareVersionService; use App\Services\Nodes\NodeAutoDeployService; use App\Services\Nodes\NodeUpdateService; @@ -14,6 +14,8 @@ use Exception; use Filament\Actions\Action; use Filament\Actions\DeleteAction; use Filament\Forms\Components\Hidden; +use Filament\Forms\Components\Slider; +use Filament\Forms\Components\Slider\Enums\PipsMode; use Filament\Forms\Components\TagsInput; use Filament\Forms\Components\Textarea; use Filament\Forms\Components\TextInput; @@ -25,6 +27,7 @@ use Filament\Resources\Pages\EditRecord; use Filament\Schemas\Components\Actions; use Filament\Schemas\Components\Fieldset; use Filament\Schemas\Components\Grid; +use Filament\Schemas\Components\Section; use Filament\Schemas\Components\StateCasts\BooleanStateCast; use Filament\Schemas\Components\Tabs; use Filament\Schemas\Components\Tabs\Tab; @@ -33,7 +36,10 @@ use Filament\Schemas\Components\Utilities\Set; use Filament\Schemas\Components\View; use Filament\Schemas\Schema; use Filament\Support\Enums\Alignment; +use Filament\Support\Enums\IconSize; +use Filament\Support\RawJs; use Illuminate\Http\Client\ConnectionException; +use Illuminate\Support\Facades\Http; use Illuminate\Support\HtmlString; use Phiki\Grammar\Grammar; use Throwable; @@ -45,13 +51,13 @@ class EditNode extends EditRecord protected static string $resource = NodeResource::class; - private DaemonConfigurationRepository $daemonConfigurationRepository; + private DaemonSystemRepository $daemonSystemRepository; private NodeUpdateService $nodeUpdateService; - public function boot(DaemonConfigurationRepository $daemonConfigurationRepository, NodeUpdateService $nodeUpdateService): void + public function boot(DaemonSystemRepository $daemonSystemRepository, NodeUpdateService $nodeUpdateService): void { - $this->daemonConfigurationRepository = $daemonConfigurationRepository; + $this->daemonSystemRepository = $daemonSystemRepository; $this->nodeUpdateService = $nodeUpdateService; } @@ -624,6 +630,154 @@ class EditNode extends EditRecord ])->fullWidth(), ]), ]), + Tab::make('diagnostics') + ->label(trans('admin/node.tabs.diagnostics')) + ->icon('tabler-heart-search') + ->schema([ + Section::make('diag') + ->heading(trans('admin/node.tabs.diagnostics')) + ->columnSpanFull() + ->columns(4) + ->disabled(fn (Get $get) => $get('pulled')) + ->headerActions([ + Action::make('pull') + ->label(trans('admin/node.diagnostics.pull')) + ->icon('tabler-cloud-download')->iconButton()->iconSize(IconSize::ExtraLarge) + ->hidden(fn (Get $get) => $get('pulled')) + ->action(function (Get $get, Set $set, Node $node) { + $includeEndpoints = $get('include_endpoints') ?? true; + $includeLogs = $get('include_logs') ?? true; + $logLines = $get('log_lines') ?? 200; + + try { + $response = $this->daemonSystemRepository->setNode($node)->getDiagnostics($logLines, $includeEndpoints, $includeLogs); + + if ($response->status() === 404) { + Notification::make() + ->title(trans('admin/node.diagnostics.404')) + ->warning() + ->send(); + + return; + } + + $set('pulled', true); + $set('uploaded', false); + $set('log', $response->body()); + + Notification::make() + ->title(trans('admin/node.diagnostics.logs_pulled')) + ->success() + ->send(); + } catch (ConnectionException $e) { + Notification::make() + ->title(trans('admin/node.error_connecting', ['node' => $node->name])) + ->body($e->getMessage()) + ->danger() + ->send(); + + } + }), + Action::make('upload') + ->label(trans('admin/node.diagnostics.upload')) + ->visible(fn (Get $get) => $get('pulled') ?? false) + ->icon('tabler-cloud-upload')->iconButton()->iconSize(IconSize::ExtraLarge) + ->action(function (Get $get, Set $set) { + try { + $response = Http::asMultipart()->post('https://logs.pelican.dev', [ + [ + 'name' => 'c', + 'contents' => $get('log'), + ], + [ + 'name' => 'e', + 'contents' => '14d', + ], + ]); + + if ($response->failed()) { + Notification::make() + ->title(trans('admin/node.diagnostics.upload_failed')) + ->body(fn () => $response->status() . ' - ' . $response->body()) + ->danger() + ->send(); + + return; + } + + $data = $response->json(); + $url = $data['url']; + + Notification::make() + ->title(trans('admin/node.diagnostics.logs_uploaded')) + ->body("{$url}") + ->success() + ->actions([ + Action::make('viewLogs') + ->label(trans('admin/node.diagnostics.view_logs')) + ->url($url) + ->openUrlInNewTab(true), + ]) + ->persistent() + ->send(); + $set('log', $url); + $set('pulled', false); + $set('uploaded', true); + + } catch (\Exception $e) { + Notification::make() + ->title(trans('admin/node.diagnostics.upload_failed')) + ->body($e->getMessage()) + ->danger() + ->send(); + } + }), + Action::make('clear') + ->label(trans('admin/node.diagnostics.clear')) + ->visible(fn (Get $get) => $get('pulled') ?? false) + ->icon('tabler-trash')->iconButton()->iconSize(IconSize::ExtraLarge)->color('danger') + ->action(function (Get $get, Set $set) { + $set('pulled', false); + $set('uploaded', false); + $set('log', null); + $this->refresh(); + } + ), + ]) + ->schema([ + ToggleButtons::make('include_endpoints') + ->hintIcon('tabler-question-mark')->inline() + ->hintIconTooltip(trans('admin/node.diagnostics.include_endpoints_hint')) + ->formatStateUsing(fn () => 1) + ->boolean(), + ToggleButtons::make('include_logs') + ->live() + ->hintIcon('tabler-question-mark')->inline() + ->hintIconTooltip(trans('admin/node.diagnostics.include_logs_hint')) + ->formatStateUsing(fn () => 1) + ->boolean(), + Slider::make('log_lines') + ->columnSpan(2) + ->hiddenLabel() + ->live() + ->tooltips(RawJs::make(<<<'JS' + `${$value} lines` + JS)) + ->visible(fn (Get $get) => $get('include_logs')) + ->range(minValue: 100, maxValue: 500) + ->pips(PipsMode::Steps, density: 10) + ->step(50) + ->formatStateUsing(fn () => 200) + ->fillTrack(), + Hidden::make('pulled'), + Hidden::make('uploaded'), + ]), + Textarea::make('log') + ->hiddenLabel() + ->columnSpanFull() + ->rows(35) + ->visible(fn (Get $get) => ($get('pulled') ?? false) || ($get('uploaded') ?? false)), + ]), ]), ]); } @@ -681,7 +835,7 @@ class EditNode extends EditRecord try { if ($changed) { - $this->daemonConfigurationRepository->setNode($node)->update($node); + $this->daemonSystemRepository->setNode($node)->update($node); } parent::getSavedNotification()?->send(); } catch (ConnectionException) { diff --git a/app/Filament/Admin/Resources/Nodes/RelationManagers/AllocationsRelationManager.php b/app/Filament/Admin/Resources/Nodes/RelationManagers/AllocationsRelationManager.php index 3846c562f..b421e6d83 100644 --- a/app/Filament/Admin/Resources/Nodes/RelationManagers/AllocationsRelationManager.php +++ b/app/Filament/Admin/Resources/Nodes/RelationManagers/AllocationsRelationManager.php @@ -3,6 +3,7 @@ namespace App\Filament\Admin\Resources\Nodes\RelationManagers; use App\Filament\Admin\Resources\Servers\Pages\CreateServer; +use App\Filament\Components\Actions\UpdateNodeAllocations; use App\Models\Allocation; use App\Models\Node; use App\Services\Allocations\AssignmentService; @@ -80,7 +81,9 @@ class AllocationsRelationManager extends RelationManager ->searchable() ->label(trans('admin/node.table.ip')), ]) - ->headerActions([ + ->toolbarActions([ + DeleteBulkAction::make() + ->authorize(fn () => user()?->can('update', $this->getOwnerRecord())), Action::make('create new allocation') ->label(trans('admin/node.create_allocation')) ->schema(fn () => [ @@ -118,9 +121,8 @@ class AllocationsRelationManager extends RelationManager ->required(), ]) ->action(fn (array $data, AssignmentService $service) => $service->handle($this->getOwnerRecord(), $data)), - ]) - ->groupedBulkActions([ - DeleteBulkAction::make() + UpdateNodeAllocations::make() + ->nodeRecord($this->getOwnerRecord()) ->authorize(fn () => user()?->can('update', $this->getOwnerRecord())), ]); } diff --git a/app/Filament/Admin/Resources/Servers/Pages/CreateServer.php b/app/Filament/Admin/Resources/Servers/Pages/CreateServer.php index 2c44cef7b..7db8e8fc1 100644 --- a/app/Filament/Admin/Resources/Servers/Pages/CreateServer.php +++ b/app/Filament/Admin/Resources/Servers/Pages/CreateServer.php @@ -116,6 +116,14 @@ class CreateServer extends CreateRecord ->prefixIcon('tabler-server-2') ->selectablePlaceholder(false) ->default(function () { + $lastUsedNode = session()->get('last_utilized_node'); + + if ($lastUsedNode && user()?->accessibleNodes()->where('id', $lastUsedNode)->exists()) { + $this->node = Node::find($lastUsedNode); + + return $this->node?->id; + } + /** @var ?Node $latestNode */ $latestNode = user()?->accessibleNodes()->latest()->first(); $this->node = $latestNode; @@ -829,6 +837,8 @@ class CreateServer extends CreateRecord $data['allocation_additional'] = collect($allocation_additional)->filter()->all(); } + session()->put('last_utilized_node', $data['node_id']); + try { return $this->serverCreationService->handle($data); } catch (Exception $exception) { diff --git a/app/Filament/Admin/Resources/Servers/RelationManagers/AllocationsRelationManager.php b/app/Filament/Admin/Resources/Servers/RelationManagers/AllocationsRelationManager.php index 296a0fdb8..d143a1af4 100644 --- a/app/Filament/Admin/Resources/Servers/RelationManagers/AllocationsRelationManager.php +++ b/app/Filament/Admin/Resources/Servers/RelationManagers/AllocationsRelationManager.php @@ -11,6 +11,7 @@ use Filament\Actions\AssociateAction; use Filament\Actions\CreateAction; use Filament\Actions\DissociateAction; use Filament\Actions\DissociateBulkAction; +use Filament\Forms\Components\Hidden; use Filament\Forms\Components\Select; use Filament\Forms\Components\TagsInput; use Filament\Forms\Components\TextInput; @@ -60,16 +61,35 @@ class AllocationsRelationManager extends RelationManager ->action(fn (Allocation $allocation) => $this->getOwnerRecord()->update(['allocation_id' => $allocation->id]) && $this->deselectAllTableRecords()) ->default(fn (Allocation $allocation) => $allocation->id === $this->getOwnerRecord()->allocation_id) ->label(trans('admin/server.primary')), + IconColumn::make('is_locked') + ->label(trans('admin/server.locked')) + ->tooltip(trans('admin/server.locked_helper')) + ->trueIcon('tabler-lock') + ->falseIcon('tabler-lock-open'), ]) ->recordActions([ Action::make('make-primary') ->label(trans('admin/server.make_primary')) ->action(fn (Allocation $allocation) => $this->getOwnerRecord()->update(['allocation_id' => $allocation->id]) && $this->deselectAllTableRecords()) ->hidden(fn (Allocation $allocation) => $allocation->id === $this->getOwnerRecord()->allocation_id), + Action::make('lock') + ->label(trans('admin/server.lock')) + ->action(fn (Allocation $allocation) => $allocation->update(['is_locked' => true]) && $this->deselectAllTableRecords()) + ->hidden(fn (Allocation $allocation) => $allocation->is_locked), + Action::make('unlock') + ->label(trans('admin/server.unlock')) + ->action(fn (Allocation $allocation) => $allocation->update(['is_locked' => false]) && $this->deselectAllTableRecords()) + ->visible(fn (Allocation $allocation) => $allocation->is_locked), DissociateAction::make() ->after(function (Allocation $allocation) { - $allocation->update(['notes' => null]); - $this->getOwnerRecord()->allocation_id && $this->getOwnerRecord()->update(['allocation_id' => $this->getOwnerRecord()->allocations()->first()?->id]); + $allocation->update([ + 'notes' => null, + 'is_locked' => false, + ]); + + if (!$this->getOwnerRecord()->allocation_id) { + $this->getOwnerRecord()->update(['allocation_id' => $this->getOwnerRecord()->allocations()->first()?->id]); + } }), ]) ->headerActions([ @@ -107,6 +127,8 @@ class AllocationsRelationManager extends RelationManager ->afterStateUpdated(fn ($state, Set $set, Get $get) => $set('allocation_ports', CreateServer::retrieveValidPorts($this->getOwnerRecord()->node, $state, $get('allocation_ip')))) ->splitKeys(['Tab', ' ', ',']) ->required(), + Hidden::make('is_locked') + ->default(true), ]) ->action(fn (array $data, AssignmentService $service) => $service->handle($this->getOwnerRecord()->node, $data, $this->getOwnerRecord())), AssociateAction::make() @@ -116,13 +138,25 @@ class AllocationsRelationManager extends RelationManager ->recordSelectOptionsQuery(fn ($query) => $query->whereBelongsTo($this->getOwnerRecord()->node)->whereNull('server_id')) ->recordSelectSearchColumns(['ip', 'port']) ->label(trans('admin/server.add_allocation')) - ->after(fn (array $data) => !$this->getOwnerRecord()->allocation_id && $this->getOwnerRecord()->update(['allocation_id' => $data['recordId'][0]])), + ->after(function (array $data) { + Allocation::whereIn('id', array_values(array_unique($data['recordId'])))->update(['is_locked' => true]); + + if (!$this->getOwnerRecord()->allocation_id) { + $this->getOwnerRecord()->update(['allocation_id' => $data['recordId'][0]]); + } + }), ]) ->groupedBulkActions([ DissociateBulkAction::make() ->after(function () { - Allocation::whereNull('server_id')->update(['notes' => null]); - $this->getOwnerRecord()->allocation_id && $this->getOwnerRecord()->update(['allocation_id' => $this->getOwnerRecord()->allocations()->first()?->id]); + Allocation::whereNull('server_id')->update([ + 'notes' => null, + 'is_locked' => false, + ]); + + if (!$this->getOwnerRecord()->allocation_id) { + $this->getOwnerRecord()->update(['allocation_id' => $this->getOwnerRecord()->allocations()->first()?->id]); + } }), ]); } diff --git a/app/Filament/Admin/Resources/Servers/RelationManagers/DatabasesRelationManager.php b/app/Filament/Admin/Resources/Servers/RelationManagers/DatabasesRelationManager.php index cfe82568b..06d86c400 100644 --- a/app/Filament/Admin/Resources/Servers/RelationManagers/DatabasesRelationManager.php +++ b/app/Filament/Admin/Resources/Servers/RelationManagers/DatabasesRelationManager.php @@ -81,6 +81,7 @@ class DatabasesRelationManager extends RelationManager ViewAction::make() ->color('primary'), DeleteAction::make() + ->successNotificationTitle(null) ->using(function (Database $database, DatabaseManagementService $service) { try { $service->delete($database); diff --git a/app/Filament/Components/Actions/UpdateNodeAllocations.php b/app/Filament/Components/Actions/UpdateNodeAllocations.php new file mode 100644 index 000000000..cf186f82c --- /dev/null +++ b/app/Filament/Components/Actions/UpdateNodeAllocations.php @@ -0,0 +1,106 @@ +label(trans('admin/node.bulk_update_ip')); + + $this->icon('tabler-replace'); + + $this->color('warning'); + + $this->requiresConfirmation(); + + $this->modalHeading(trans('admin/node.bulk_update_ip')); + + $this->modalDescription(trans('admin/node.bulk_update_ip_description')); + + $this->modalIconColor('warning'); + + $this->modalSubmitActionLabel(trans('admin/node.update_ip')); + + $this->schema(function () { + /** @var Node $node */ + $node = $this->record; + + $currentIps = Allocation::where('node_id', $node->id) + ->pluck('ip') + ->unique() + ->values() + ->all(); + + return [ + Select::make('old_ip') + ->label(trans('admin/node.old_ip')) + ->options(array_combine($currentIps, $currentIps)) + ->selectablePlaceholder(false) + ->required() + ->live(), + Select::make('new_ip') + ->label(trans('admin/node.new_ip')) + ->options(fn () => array_combine($node->ipAddresses(), $node->ipAddresses()) ?: []) + ->required() + ->different('old_ip'), + ]; + }); + + $this->action(function (array $data) { + /** @var Node $node */ + $node = $this->record; + $allocations = Allocation::where('node_id', $node->id)->where('ip', $data['old_ip'])->get(); + + if ($allocations->count() === 0) { + Notification::make() + ->title(trans('admin/node.no_allocations_to_update')) + ->warning() + ->send(); + + return; + } + + $updated = 0; + $failed = 0; + + foreach ($allocations as $allocation) { + try { + $allocation->update(['ip' => $data['new_ip']]); + $updated++; + } catch (Exception $exception) { + $failed++; + report($exception); + } + } + + Notification::make() + ->title(trans('admin/node.ip_updated', ['count' => $updated, 'total' => $allocations->count()])) + ->body($failed > 0 ? trans('admin/node.ip_update_failed', ['count' => $failed]) : null) + ->status($failed > 0 ? 'warning' : 'success') + ->persistent() + ->send(); + }); + } + + public function nodeRecord(Node $node): static + { + $this->record = $node; + + return $this; + } +} diff --git a/app/Filament/Server/Resources/Allocations/AllocationResource.php b/app/Filament/Server/Resources/Allocations/AllocationResource.php index 92e70c554..eb3cdebfa 100644 --- a/app/Filament/Server/Resources/Allocations/AllocationResource.php +++ b/app/Filament/Server/Resources/Allocations/AllocationResource.php @@ -73,15 +73,22 @@ class AllocationResource extends Resource ->action(fn (Allocation $allocation) => user()?->can(PERMISSION::ACTION_ALLOCATION_UPDATE, $server) && $server->update(['allocation_id' => $allocation->id])) ->default(fn (Allocation $allocation) => $allocation->id === $server->allocation_id) ->label(trans('server/network.primary')), + IconColumn::make('is_locked') + ->label(trans('server/network.locked')) + ->tooltip(trans('server/network.locked_helper')) + ->trueIcon('tabler-lock') + ->falseIcon('tabler-lock-open'), ]) ->recordActions([ DetachAction::make() + ->visible(fn (Allocation $allocation) => !$allocation->is_locked || user()?->can('update', $allocation->node)) ->authorize(fn () => user()?->can(Permission::ACTION_ALLOCATION_DELETE, $server)) ->label(trans('server/network.delete')) ->icon('tabler-trash') ->action(function (Allocation $allocation) { - Allocation::query()->where('id', $allocation->id)->update([ + Allocation::where('id', $allocation->id)->update([ 'notes' => null, + 'is_locked' => false, 'server_id' => null, ]); @@ -93,12 +100,12 @@ class AllocationResource extends Resource ->after(fn (Allocation $allocation) => $allocation->id === $server->allocation_id && $server->update(['allocation_id' => $server->allocations()->first()?->id])), ]) ->toolbarActions([ - Action::make('addAllocation') + Action::make('add_allocation') ->hiddenLabel()->iconButton()->iconSize(IconSize::ExtraLarge) ->icon(fn () => $server->allocations()->count() >= $server->allocation_limit ? 'tabler-network-off' : 'tabler-network') ->authorize(fn () => user()?->can(Permission::ACTION_ALLOCATION_CREATE, $server)) ->tooltip(fn () => $server->allocations()->count() >= $server->allocation_limit ? trans('server/network.limit') : trans('server/network.add')) - ->hidden(fn () => !config('panel.client_features.allocations.enabled')) + ->hidden(fn () => !config('panel.client_features.allocations.enabled') || $server->allocation === null) ->disabled(fn () => $server->allocations()->count() >= $server->allocation_limit) ->color(fn () => $server->allocations()->count() >= $server->allocation_limit ? 'danger' : 'primary') ->action(function (FindAssignableAllocationService $service) use ($server) { diff --git a/app/Filament/Server/Resources/Backups/BackupResource.php b/app/Filament/Server/Resources/Backups/BackupResource.php index 7506675be..4c00db678 100644 --- a/app/Filament/Server/Resources/Backups/BackupResource.php +++ b/app/Filament/Server/Resources/Backups/BackupResource.php @@ -230,6 +230,7 @@ class BackupResource extends Resource ->disabled(fn (Backup $backup) => $backup->is_locked && $backup->status !== BackupStatus::Failed) ->modalDescription(fn (Backup $backup) => trans('server/backup.actions.delete.description', ['backup' => $backup->name])) ->modalSubmitActionLabel(trans('server/backup.actions.delete.title')) + ->successNotificationTitle(null) ->action(function (Backup $backup, DeleteBackupService $deleteBackupService) { try { $deleteBackupService->handle($backup); @@ -265,6 +266,7 @@ class BackupResource extends Resource ->color(fn () => $server->backups()->count() >= $server->backup_limit ? 'danger' : 'primary') ->createAnother(false) ->hiddenLabel()->iconButton()->iconSize(IconSize::ExtraLarge) + ->successNotificationTitle(null) ->action(function (InitiateBackupService $initiateBackupService, $data) use ($server) { $action = $initiateBackupService->setIgnoredFiles(explode(PHP_EOL, $data['ignored'] ?? '')); diff --git a/app/Filament/Server/Resources/Databases/DatabaseResource.php b/app/Filament/Server/Resources/Databases/DatabaseResource.php index b87efd9a5..e5bbe5da3 100644 --- a/app/Filament/Server/Resources/Databases/DatabaseResource.php +++ b/app/Filament/Server/Resources/Databases/DatabaseResource.php @@ -138,6 +138,7 @@ class DatabaseResource extends Resource ViewAction::make() ->modalHeading(fn (Database $database) => trans('server/database.viewing', ['database' => $database->database])), DeleteAction::make() + ->successNotificationTitle(null) ->using(function (Database $database, DatabaseManagementService $service) { try { $service->delete($database); @@ -164,6 +165,7 @@ class DatabaseResource extends Resource ->disabled(fn () => $server->databases()->count() >= $server->database_limit) ->color(fn () => $server->databases()->count() >= $server->database_limit ? 'danger' : 'primary') ->createAnother(false) + ->successNotificationTitle(null) ->schema([ Grid::make() ->columns(2) diff --git a/app/Filament/Server/Resources/Files/Pages/ListFiles.php b/app/Filament/Server/Resources/Files/Pages/ListFiles.php index 6dcb77151..e83217ea4 100644 --- a/app/Filament/Server/Resources/Files/Pages/ListFiles.php +++ b/app/Filament/Server/Resources/Files/Pages/ListFiles.php @@ -12,8 +12,10 @@ use App\Models\File; use App\Models\Permission; use App\Models\Server; use App\Repositories\Daemon\DaemonFileRepository; +use App\Services\Nodes\NodeJWTService; use App\Traits\Filament\CanCustomizeHeaderActions; use App\Traits\Filament\CanCustomizeHeaderWidgets; +use Carbon\CarbonImmutable; use Exception; use Filament\Actions\Action; use Filament\Actions\ActionGroup; @@ -25,7 +27,6 @@ use Filament\Actions\EditAction; use Filament\Facades\Filament; use Filament\Forms\Components\CheckboxList; use Filament\Forms\Components\CodeEditor; -use Filament\Forms\Components\FileUpload; use Filament\Forms\Components\Select; use Filament\Forms\Components\TextInput; use Filament\Infolists\Components\TextEntry; @@ -34,8 +35,6 @@ use Filament\Panel; use Filament\Resources\Pages\ListRecords; use Filament\Resources\Pages\PageRegistration; use Filament\Schemas\Components\Grid; -use Filament\Schemas\Components\Tabs; -use Filament\Schemas\Components\Tabs\Tab; use Filament\Schemas\Components\Utilities\Get; use Filament\Support\Enums\IconSize; use Filament\Support\Facades\FilamentView; @@ -43,7 +42,7 @@ use Filament\Tables\Columns\TextColumn; use Filament\Tables\Enums\PaginationMode; use Filament\Tables\Table; use Illuminate\Database\Eloquent\Collection; -use Illuminate\Http\UploadedFile; +use Illuminate\Http\Client\ConnectionException; use Illuminate\Routing\Route; use Illuminate\Support\Carbon; use Illuminate\Support\Facades\Route as RouteFacade; @@ -56,6 +55,8 @@ class ListFiles extends ListRecords protected static string $resource = FileResource::class; + protected string $view = 'filament.server.pages.list-files'; + #[Locked] public string $path = '/'; @@ -504,7 +505,7 @@ class ListFiles extends ListRecords ->color('primary') ->action(function ($data) { try { - $this->getDaemonFileRepository()->createDirectory($data['name'], $this->path); + $this->createFolder($data['name']); Activity::event('server:file.create-directory') ->property(['directory' => $this->path, 'name' => $data['name']]) @@ -528,58 +529,30 @@ class ListFiles extends ListRecords ->label(trans('server/file.actions.new_folder.folder_name')) ->required(), ]), - Action::make('upload') + Action::make('uploadFile') ->authorize(fn () => user()?->can(Permission::ACTION_FILE_CREATE, $server)) - ->hiddenLabel()->icon('tabler-upload')->iconButton()->iconSize(IconSize::ExtraLarge) - ->tooltip(trans('server/file.actions.upload.title')) + ->view('filament.server.pages.file-upload'), + Action::make('uploadURL') + ->authorize(fn () => user()?->can(Permission::ACTION_FILE_CREATE, $server)) + ->hiddenLabel()->icon('tabler-download')->iconButton()->iconSize(IconSize::ExtraLarge) + ->tooltip(trans('server/file.actions.upload.from_url')) + ->modalHeading(trans('server/file.actions.upload.from_url')) ->color('success') ->action(function ($data) { - if (count($data['files']) > 0 && !isset($data['url'])) { - /** @var UploadedFile $file */ - foreach ($data['files'] as $file) { - $this->getDaemonFileRepository()->putContent(join_paths($this->path, $file->getClientOriginalName()), $file->getContent()); + $this->getDaemonFileRepository()->pull($data['url'], $this->path); - Activity::event('server:file.uploaded') - ->property('directory', $this->path) - ->property('file', $file->getClientOriginalName()) - ->log(); - } - } elseif ($data['url'] !== null) { - $this->getDaemonFileRepository()->pull($data['url'], $this->path); - - Activity::event('server:file.pull') - ->property('url', $data['url']) - ->property('directory', $this->path) - ->log(); - } + Activity::event('server:file.pull') + ->property('url', $data['url']) + ->property('directory', $this->path) + ->log(); $this->refreshPage(); }) ->schema([ - Tabs::make() - ->contained(false) - ->schema([ - Tab::make('files') - ->label(trans('server/file.actions.upload.from_files')) - ->live() - ->schema([ - FileUpload::make('files') - ->storeFiles(false) - ->previewable(false) - ->preserveFilenames() - ->maxSize((int) round($server->node->upload_size * (config('panel.use_binary_prefix') ? 1.048576 * 1024 : 1000))) - ->multiple(), - ]), - Tab::make('url') - ->label(trans('server/file.actions.upload.url')) - ->live() - ->disabled(fn (Get $get) => count($get('files')) > 0) - ->schema([ - TextInput::make('url') - ->label(trans('server/file.actions.upload.url')) - ->url(), - ]), - ]), + TextInput::make('url') + ->label(trans('server/file.actions.upload.url')) + ->required() + ->url(), ]), Action::make('search') ->authorize(fn () => user()?->can(Permission::ACTION_FILE_READ, $server)) @@ -627,6 +600,81 @@ class ListFiles extends ListRecords }; } + public function getUploadUrl(NodeJWTService $jwtService): string + { + /** @var Server $server */ + $server = Filament::getTenant(); + + if (!user()?->can(Permission::ACTION_FILE_CREATE, $server)) { + abort(403, 'You do not have permission to upload files.'); + } + + $token = $jwtService + ->setExpiresAt(CarbonImmutable::now()->addMinutes(15)) + ->setUser(user()) + ->setClaims(['server_uuid' => $server->uuid]) + ->handle($server->node, user()->id . $server->uuid); + + return sprintf( + '%s/upload/file?token=%s', + $server->node->getConnectionAddress(), + $token->toString() + ); + } + + public function getUploadSizeLimit(): int + { + /** @var Server $server */ + $server = Filament::getTenant(); + + return $server->node->upload_size * 1024 * 1024; + } + + /** + * @throws ConnectionException + * @throws FileExistsException + * @throws \Throwable + */ + public function createFolder(string $folderPath): void + { + /** @var Server $server */ + $server = Filament::getTenant(); + + if (!user()?->can(Permission::ACTION_FILE_CREATE, $server)) { + abort(403, 'You do not have permission to create folders.'); + } + + try { + $this->getDaemonFileRepository()->createDirectory($folderPath, $this->path); + + Activity::event('server:file.create-directory') + ->property(['directory' => $this->path, 'name' => $folderPath]) + ->log(); + + } catch (FileExistsException) { + // Ignore if the folder already exists. + } catch (ConnectionException $e) { + Notification::make() + ->body($e->getMessage()) + ->danger() + ->send(); + + } + } + + /** + * @param string[] $files + */ + public function logUploadedFiles(array $files): void + { + $filesCollection = collect($files); + + Activity::event('server:files.uploaded') + ->property('directory', $this->path) + ->property('files', $filesCollection) + ->log(); + } + private function getDaemonFileRepository(): DaemonFileRepository { /** @var Server $server */ diff --git a/app/Filament/Server/Resources/Users/UserResource.php b/app/Filament/Server/Resources/Users/UserResource.php index 4b5d98ff6..9baf9300e 100644 --- a/app/Filament/Server/Resources/Users/UserResource.php +++ b/app/Filament/Server/Resources/Users/UserResource.php @@ -140,6 +140,7 @@ class UserResource extends Resource DeleteAction::make() ->label(trans('server/user.delete')) ->hidden(fn (User $user) => user()?->id === $user->id) + ->successNotificationTitle(null) ->action(function (User $user, SubuserDeletionService $subuserDeletionService) use ($server) { $subuser = $server->subusers->where('user_id', $user->id)->first(); $subuserDeletionService->handle($subuser, $server); @@ -154,6 +155,7 @@ class UserResource extends Resource ->hidden(fn (User $user) => user()?->id === $user->id) ->authorize(fn () => user()?->can(Permission::ACTION_USER_UPDATE, $server)) ->modalHeading(fn (User $user) => trans('server/user.editing', ['user' => $user->email])) + ->successNotificationTitle(null) ->action(function (array $data, SubuserUpdateService $subuserUpdateService, User $user) use ($server) { $subuser = $server->subusers->where('user_id', $user->id)->first(); diff --git a/app/Jobs/ProcessWebhook.php b/app/Jobs/ProcessWebhook.php index 7fae84546..12f53200a 100644 --- a/app/Jobs/ProcessWebhook.php +++ b/app/Jobs/ProcessWebhook.php @@ -34,7 +34,9 @@ class ProcessWebhook implements ShouldQueue $data = reset($data); } - $data = Arr::wrap(json_decode($data, true) ?? []); + if (is_string($data)) { + $data = Arr::wrap(json_decode($data, true) ?? []); + } $data['event'] = $this->webhookConfiguration->transformClassName($this->eventName); if ($this->webhookConfiguration->type === WebhookType::Discord) { @@ -55,12 +57,13 @@ class ProcessWebhook implements ShouldQueue } try { - $customHeaders = $this->webhookConfiguration->headers; $headers = []; - foreach ($customHeaders as $key => $value) { - $headers[$key] = $this->webhookConfiguration->replaceVars($data, $value); - } + if ($this->webhookConfiguration->type === WebhookType::Regular) { + foreach ($this->webhookConfiguration->headers as $key => $value) { + $headers[$key] = $this->webhookConfiguration->replaceVars($data, $value); + } + } Http::withHeaders($headers)->post($this->webhookConfiguration->endpoint, $data)->throw(); $successful = now(); } catch (Exception $exception) { diff --git a/app/Models/Allocation.php b/app/Models/Allocation.php index 6c7ac88a9..fc529829c 100644 --- a/app/Models/Allocation.php +++ b/app/Models/Allocation.php @@ -29,6 +29,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; * @property string $address * @property Server|null $server * @property Node $node + * @property bool $is_locked * * @method static AllocationFactory factory(...$parameters) * @method static Builder|Allocation newModelQuery() @@ -55,6 +56,10 @@ class Allocation extends Model */ public const RESOURCE_NAME = 'allocation'; + protected $attributes = [ + 'is_locked' => false, + ]; + /** * Fields that are not mass assignable. */ @@ -68,10 +73,17 @@ class Allocation extends Model 'ip_alias' => ['nullable', 'string'], 'server_id' => ['nullable', 'exists:servers,id'], 'notes' => ['nullable', 'string', 'max:256'], + 'is_locked' => ['boolean'], ]; protected static function booted(): void { + static::updating(function (self $allocation) { + if (is_null($allocation->server_id)) { + $allocation->is_locked = false; + } + }); + static::deleting(function (self $allocation) { throw_if($allocation->server_id, new ServerUsingAllocationException(trans('exceptions.allocations.server_using'))); }); @@ -83,6 +95,7 @@ class Allocation extends Model 'node_id' => 'integer', 'port' => 'integer', 'server_id' => 'integer', + 'is_locked' => 'bool', ]; } diff --git a/app/Models/Node.php b/app/Models/Node.php index 52b02ac11..e4f127d84 100644 --- a/app/Models/Node.php +++ b/app/Models/Node.php @@ -4,7 +4,7 @@ namespace App\Models; use App\Contracts\Validatable; use App\Exceptions\Service\HasActiveServersException; -use App\Repositories\Daemon\DaemonConfigurationRepository; +use App\Repositories\Daemon\DaemonSystemRepository; use App\Traits\HasValidation; use Carbon\Carbon; use Exception; @@ -316,7 +316,7 @@ class Node extends Model implements Validatable { return once(function () { try { - return (new DaemonConfigurationRepository()) + return (new DaemonSystemRepository()) ->setNode($this) ->getSystemInformation(); } catch (Exception $exception) { diff --git a/app/Models/WebhookConfiguration.php b/app/Models/WebhookConfiguration.php index 31daa294a..60283f4d8 100644 --- a/app/Models/WebhookConfiguration.php +++ b/app/Models/WebhookConfiguration.php @@ -171,10 +171,14 @@ class WebhookConfiguration extends Model } /** - * @param array $replacement + * @param array|object $replacement * */ - public function replaceVars(array $replacement, string $subject): string + public function replaceVars(array|object $replacement, string $subject): string { + if (is_object($replacement)) { + $replacement = $replacement->toArray(); + } + return preg_replace_callback( '/{{(.*?)}}/', function ($matches) use ($replacement) { diff --git a/app/Providers/Filament/AdminPanelProvider.php b/app/Providers/Filament/AdminPanelProvider.php index 9c838eb02..9d0a274db 100644 --- a/app/Providers/Filament/AdminPanelProvider.php +++ b/app/Providers/Filament/AdminPanelProvider.php @@ -2,7 +2,9 @@ namespace App\Providers\Filament; -use AchyutN\FilamentLogViewer\FilamentLogViewer; +use App\Filament\Admin\Pages\ListLogs; +use App\Filament\Admin\Pages\ViewLogs; +use Boquizo\FilamentLogViewer\FilamentLogViewerPlugin; use App\Facades\Plugins; use Filament\Actions\Action; use Filament\Facades\Filament; @@ -36,8 +38,11 @@ class AdminPanelProvider extends PanelProvider ->discoverPages(in: app_path('Filament/Admin/Pages'), for: 'App\\Filament\\Admin\\Pages') ->discoverWidgets(in: app_path('Filament/Admin/Widgets'), for: 'App\\Filament\\Admin\\Widgets') ->plugins([ - FilamentLogViewer::make() + FilamentLogViewerPlugin::make() ->authorize(fn () => user()->can('view panelLog')) + ->listLogs(ListLogs::class) + ->viewLog(ViewLogs::class) + ->navigationLabel(fn () => trans('admin/log.navigation.panel_logs')) ->navigationGroup(fn () => trans('admin/dashboard.advanced')) ->navigationIcon('tabler-file-info'), ]); diff --git a/app/Providers/Filament/AppPanelProvider.php b/app/Providers/Filament/AppPanelProvider.php index 15c582877..9119ee5a7 100644 --- a/app/Providers/Filament/AppPanelProvider.php +++ b/app/Providers/Filament/AppPanelProvider.php @@ -2,7 +2,7 @@ namespace App\Providers\Filament; -use AchyutN\FilamentLogViewer\FilamentLogViewer; +use Boquizo\FilamentLogViewer\FilamentLogViewerPlugin; use App\Facades\Plugins; use Filament\Actions\Action; use Filament\Facades\Filament; @@ -27,7 +27,7 @@ class AppPanelProvider extends PanelProvider ]) ->discoverResources(in: app_path('Filament/App/Resources'), for: 'App\\Filament\\App\\Resources') ->plugins([ - FilamentLogViewer::make() + FilamentLogViewerPlugin::make() ->authorize(false), ]); diff --git a/app/Repositories/Daemon/DaemonConfigurationRepository.php b/app/Repositories/Daemon/DaemonSystemRepository.php similarity index 69% rename from app/Repositories/Daemon/DaemonConfigurationRepository.php rename to app/Repositories/Daemon/DaemonSystemRepository.php index 916e7d459..71d88a138 100644 --- a/app/Repositories/Daemon/DaemonConfigurationRepository.php +++ b/app/Repositories/Daemon/DaemonSystemRepository.php @@ -6,7 +6,7 @@ use App\Models\Node; use Illuminate\Http\Client\ConnectionException; use Illuminate\Http\Client\Response; -class DaemonConfigurationRepository extends DaemonRepository +class DaemonSystemRepository extends DaemonRepository { /** * Returns system information from the daemon instance. @@ -30,6 +30,23 @@ class DaemonConfigurationRepository extends DaemonRepository })->json(); } + /** + * Retrieve diagnostics from the daemon for the current node. + * + * + * @throws ConnectionException + */ + public function getDiagnostics(int $lines, bool $includeEndpoints, bool $includeLogs): Response + { + return $this->getHttpClient() + ->timeout(5) + ->get('/api/diagnostics', [ + 'log_lines' => $lines, + 'include_endpoints' => $includeEndpoints ? 'true' : 'false', + 'include_logs' => $includeLogs ? 'true' : 'false', + ]); + } + /** * Updates the configuration information for a daemon. Updates the information for * this instance using a passed-in model. This allows us to change plenty of information diff --git a/app/Services/Allocations/AssignmentService.php b/app/Services/Allocations/AssignmentService.php index b3a0f2ca8..65974e5a3 100644 --- a/app/Services/Allocations/AssignmentService.php +++ b/app/Services/Allocations/AssignmentService.php @@ -85,6 +85,7 @@ class AssignmentService 'port' => (int) $unit, 'ip_alias' => array_get($data, 'allocation_alias'), 'server_id' => $server->id ?? null, + 'is_locked' => array_get($data, 'is_locked', false), ]; } } else { @@ -98,6 +99,7 @@ class AssignmentService 'port' => (int) $port, 'ip_alias' => array_get($data, 'allocation_alias'), 'server_id' => $server->id ?? null, + 'is_locked' => array_get($data, 'is_locked', false), ]; } diff --git a/app/Services/Nodes/NodeUpdateService.php b/app/Services/Nodes/NodeUpdateService.php index 13b094979..6ef132a46 100644 --- a/app/Services/Nodes/NodeUpdateService.php +++ b/app/Services/Nodes/NodeUpdateService.php @@ -4,7 +4,7 @@ namespace App\Services\Nodes; use App\Exceptions\Service\Node\ConfigurationNotPersistedException; use App\Models\Node; -use App\Repositories\Daemon\DaemonConfigurationRepository; +use App\Repositories\Daemon\DaemonSystemRepository; use Illuminate\Database\ConnectionInterface; use Illuminate\Http\Client\ConnectionException; use Illuminate\Support\Str; @@ -17,7 +17,7 @@ class NodeUpdateService */ public function __construct( private ConnectionInterface $connection, - private DaemonConfigurationRepository $configurationRepository, + private DaemonSystemRepository $configurationRepository, ) {} /** diff --git a/app/Services/Servers/ServerCreationService.php b/app/Services/Servers/ServerCreationService.php index f2483c5f6..f15f84f34 100644 --- a/app/Services/Servers/ServerCreationService.php +++ b/app/Services/Servers/ServerCreationService.php @@ -191,6 +191,7 @@ class ServerCreationService ->get() ->each(function (Allocation $allocation) use ($server) { $allocation->server_id = $server->id; + $allocation->is_locked = true; $allocation->save(); }); } diff --git a/app/Traits/ResolvesRecordDate.php b/app/Traits/ResolvesRecordDate.php new file mode 100644 index 000000000..7323fb60f --- /dev/null +++ b/app/Traits/ResolvesRecordDate.php @@ -0,0 +1,45 @@ +record ?? null); + + if (is_scalar($r)) { + return (string) $r; + } + + if (is_array($r)) { + return Arr::get($r, 'date') !== null ? (string) Arr::get($r, 'date') : null; + } + + if (is_object($r)) { + if (method_exists($r, 'getAttribute')) { + $val = $r->getAttribute('date'); + if ($val !== null) { + return (string) $val; + } + } + + if (isset($r->date) || property_exists($r, 'date')) { + return (string) $r->date; + } + + if (method_exists($r, 'toArray')) { + $arr = $r->toArray(); + + return Arr::get($arr, 'date') !== null ? (string) Arr::get($arr, 'date') : null; + } + } + + return null; + } +} diff --git a/composer.json b/composer.json index 5c4d85b9d..d0331df03 100644 --- a/composer.json +++ b/composer.json @@ -9,13 +9,13 @@ "ext-mbstring": "*", "ext-pdo": "*", "ext-zip": "*", - "achyutn/filament-log-viewer": "^1.4", "aws/aws-sdk-php": "^3.356", "calebporzio/sushi": "^2.5", "dedoc/scramble": "^0.12.10", "filament/filament": "~4.0", + "gboquizosanchez/filament-log-viewer": "^2.1", "guzzlehttp/guzzle": "^7.10", - "laravel/framework": "^12.31", + "laravel/framework": "^12.37", "laravel/helpers": "^1.7", "laravel/sanctum": "^4.2", "laravel/socialite": "^5.23", diff --git a/composer.lock b/composer.lock index c272d583d..8fd6b7d2c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,89 +4,8 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "101c2afb1f31acb872b4bed541397cd2", + "content-hash": "fc2037e2f16ad43582ceaf9d41aba799", "packages": [ - { - "name": "achyutn/filament-log-viewer", - "version": "v1.5.2", - "source": { - "type": "git", - "url": "https://github.com/achyutkneupane/filament-log-viewer.git", - "reference": "e285e5cb359d92d17c64e981de13d0e7741f4121" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/achyutkneupane/filament-log-viewer/zipball/e285e5cb359d92d17c64e981de13d0e7741f4121", - "reference": "e285e5cb359d92d17c64e981de13d0e7741f4121", - "shasum": "" - }, - "require": { - "filament/filament": "^4.0", - "phiki/phiki": "^2.0", - "php": ">=8.2" - }, - "require-dev": { - "larastan/larastan": "^3.0", - "laravel/pint": "^1.23", - "orchestra/testbench": "^10.4", - "pestphp/pest": "^3.8", - "pestphp/pest-plugin-laravel": "^3.2", - "pestphp/pest-plugin-livewire": "^3.0", - "phpstan/phpstan": "^2.1", - "rector/rector": "^2.1" - }, - "type": "library", - "extra": { - "laravel": { - "providers": [ - "AchyutN\\FilamentLogViewer\\LogViewerProvider" - ] - } - }, - "autoload": { - "psr-4": { - "AchyutN\\FilamentLogViewer\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Achyut Neupane", - "email": "achyutkneupane@gmail.com", - "homepage": "https://achyut.com.np", - "role": "Maintainer" - } - ], - "description": "A Filament package to view and manage Laravel logs.", - "keywords": [ - "Viewer", - "filament", - "laravel", - "log" - ], - "support": { - "issues": "https://github.com/achyutkneupane/filament-log-viewer/issues", - "source": "https://github.com/achyutkneupane/filament-log-viewer/tree/v1.5.2" - }, - "funding": [ - { - "url": "https://www.buymeacoffee.com/achyutn", - "type": "buy_me_a_coffee" - }, - { - "url": "https://github.com/achyutkneupane", - "type": "github" - }, - { - "url": "https://www.patreon.com/Achyut", - "type": "patreon" - } - ], - "time": "2025-10-10T18:58:40+00:00" - }, { "name": "anourvalar/eloquent-serialize", "version": "1.3.4", @@ -209,16 +128,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.359.3", + "version": "3.359.8", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "a32e4c9522f0b61c947fafa1713d3a24b397a757" + "reference": "a5be7ed5efd25d70a74275daeff896b896d9c286" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/a32e4c9522f0b61c947fafa1713d3a24b397a757", - "reference": "a32e4c9522f0b61c947fafa1713d3a24b397a757", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/a5be7ed5efd25d70a74275daeff896b896d9c286", + "reference": "a5be7ed5efd25d70a74275daeff896b896d9c286", "shasum": "" }, "require": { @@ -300,9 +219,9 @@ "support": { "forum": "https://github.com/aws/aws-sdk-php/discussions", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.359.3" + "source": "https://github.com/aws/aws-sdk-php/tree/3.359.8" }, - "time": "2025-10-31T18:15:22+00:00" + "time": "2025-11-07T19:48:19+00:00" }, { "name": "blade-ui-kit/blade-heroicons", @@ -2023,6 +1942,70 @@ ], "time": "2023-10-12T05:21:21+00:00" }, + { + "name": "gboquizosanchez/filament-log-viewer", + "version": "2.1.8", + "source": { + "type": "git", + "url": "https://github.com/gboquizosanchez/filament-log-viewer.git", + "reference": "85480879ed0f4da15257393f6c2e0c0ea0892403" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/gboquizosanchez/filament-log-viewer/zipball/85480879ed0f4da15257393f6c2e0c0ea0892403", + "reference": "85480879ed0f4da15257393f6c2e0c0ea0892403", + "shasum": "" + }, + "require": { + "ext-zip": "*", + "owenvoke/blade-fontawesome": "^2.9", + "php": "^8.2|^8.3|^8.4", + "symfony/polyfill-php83": "^1.33" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.64", + "hermes/dependencies": "^1.1", + "larastan/larastan": "^2.9", + "orchestra/testbench": "^9.1", + "pestphp/pest": "^3.5" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Boquizo\\FilamentLogViewer\\FilamentLogViewerServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Boquizo\\FilamentLogViewer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Germán Boquizo Sánchez", + "email": "germanboquizosanchez@gmail.com", + "role": "Developer" + } + ], + "description": "Filament Log Viewer", + "homepage": "https://github.com/gboquizosanchez", + "keywords": [ + "filament", + "laravel", + "log-viewer" + ], + "support": { + "issues": "https://github.com/gboquizosanchez/filament-log-viewer/issues", + "source": "https://github.com/gboquizosanchez/filament-log-viewer/tree/2.1.8" + }, + "time": "2025-11-07T21:40:26+00:00" + }, { "name": "graham-campbell/result-type", "version": "v1.1.3", @@ -2561,16 +2544,16 @@ }, { "name": "laravel/framework", - "version": "v12.36.1", + "version": "v12.37.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "cad110d7685fbab990a6bb8184d0cfd847d7c4d8" + "reference": "3c3c4ad30f5b528b164a7c09aa4ad03118c4c125" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/cad110d7685fbab990a6bb8184d0cfd847d7c4d8", - "reference": "cad110d7685fbab990a6bb8184d0cfd847d7c4d8", + "url": "https://api.github.com/repos/laravel/framework/zipball/3c3c4ad30f5b528b164a7c09aa4ad03118c4c125", + "reference": "3c3c4ad30f5b528b164a7c09aa4ad03118c4c125", "shasum": "" }, "require": { @@ -2776,7 +2759,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-10-29T14:20:57+00:00" + "time": "2025-11-04T15:39:33+00:00" }, { "name": "laravel/helpers", @@ -5207,6 +5190,66 @@ ], "time": "2025-01-30T13:51:11+00:00" }, + { + "name": "owenvoke/blade-fontawesome", + "version": "v2.9.1", + "source": { + "type": "git", + "url": "https://github.com/owenvoke/blade-fontawesome.git", + "reference": "94dcd0c78f43f8234b0d9c76c903ecd288b8b0d1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/owenvoke/blade-fontawesome/zipball/94dcd0c78f43f8234b0d9c76c903ecd288b8b0d1", + "reference": "94dcd0c78f43f8234b0d9c76c903ecd288b8b0d1", + "shasum": "" + }, + "require": { + "blade-ui-kit/blade-icons": "^1.5", + "illuminate/support": "^10.34|^11.0|^12.0", + "php": "^8.1" + }, + "require-dev": { + "laravel/pint": "^1.13", + "orchestra/testbench": "^8.12|^9.0|^10.0", + "pestphp/pest": "^2.26|^3.7", + "phpstan/phpstan": "^1.10|^2.1", + "symfony/var-dumper": "^6.3|^7.2" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "OwenVoke\\BladeFontAwesome\\BladeFontAwesomeServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "OwenVoke\\BladeFontAwesome\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A package to easily make use of Font Awesome in your Laravel Blade views", + "support": { + "issues": "https://github.com/owenvoke/blade-fontawesome/issues", + "source": "https://github.com/owenvoke/blade-fontawesome/tree/v2.9.1" + }, + "funding": [ + { + "url": "https://ecologi.com/owenvoke?gift-trees", + "type": "custom" + }, + { + "url": "https://github.com/owenvoke", + "type": "github" + } + ], + "time": "2025-03-28T16:03:42+00:00" + }, { "name": "paragonie/constant_time_encoding", "version": "v3.1.3", @@ -5328,16 +5371,16 @@ }, { "name": "phiki/phiki", - "version": "v2.0.4", + "version": "v2.0.5", "source": { "type": "git", "url": "https://github.com/phikiphp/phiki.git", - "reference": "160785c50c01077780ab217e5808f00ab8f05a13" + "reference": "36d03e4c103b825f2657db966730d43e2035ff00" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phikiphp/phiki/zipball/160785c50c01077780ab217e5808f00ab8f05a13", - "reference": "160785c50c01077780ab217e5808f00ab8f05a13", + "url": "https://api.github.com/repos/phikiphp/phiki/zipball/36d03e4c103b825f2657db966730d43e2035ff00", + "reference": "36d03e4c103b825f2657db966730d43e2035ff00", "shasum": "" }, "require": { @@ -5383,7 +5426,7 @@ "description": "Syntax highlighting using TextMate grammars in PHP.", "support": { "issues": "https://github.com/phikiphp/phiki/issues", - "source": "https://github.com/phikiphp/phiki/tree/v2.0.4" + "source": "https://github.com/phikiphp/phiki/tree/v2.0.5" }, "funding": [ { @@ -5395,7 +5438,7 @@ "type": "other" } ], - "time": "2025-09-20T17:21:02+00:00" + "time": "2025-11-04T20:03:45+00:00" }, { "name": "phpdocumentor/reflection", @@ -7757,16 +7800,16 @@ }, { "name": "spatie/laravel-permission", - "version": "6.22.0", + "version": "6.23.0", "source": { "type": "git", "url": "https://github.com/spatie/laravel-permission.git", - "reference": "8c87966ddc21893bfda54b792047473703992625" + "reference": "9e41247bd512b1e6c229afbc1eb528f7565ae3bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-permission/zipball/8c87966ddc21893bfda54b792047473703992625", - "reference": "8c87966ddc21893bfda54b792047473703992625", + "url": "https://api.github.com/repos/spatie/laravel-permission/zipball/9e41247bd512b1e6c229afbc1eb528f7565ae3bb", + "reference": "9e41247bd512b1e6c229afbc1eb528f7565ae3bb", "shasum": "" }, "require": { @@ -7828,7 +7871,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-permission/issues", - "source": "https://github.com/spatie/laravel-permission/tree/6.22.0" + "source": "https://github.com/spatie/laravel-permission/tree/6.23.0" }, "funding": [ { @@ -7836,7 +7879,7 @@ "type": "github" } ], - "time": "2025-10-27T21:58:45+00:00" + "time": "2025-11-03T20:16:13+00:00" }, { "name": "spatie/laravel-query-builder", @@ -8258,16 +8301,16 @@ }, { "name": "symfony/console", - "version": "v7.3.5", + "version": "v7.3.6", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "cdb80fa5869653c83cfe1a9084a673b6daf57ea7" + "reference": "c28ad91448f86c5f6d9d2c70f0cf68bf135f252a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/cdb80fa5869653c83cfe1a9084a673b6daf57ea7", - "reference": "cdb80fa5869653c83cfe1a9084a673b6daf57ea7", + "url": "https://api.github.com/repos/symfony/console/zipball/c28ad91448f86c5f6d9d2c70f0cf68bf135f252a", + "reference": "c28ad91448f86c5f6d9d2c70f0cf68bf135f252a", "shasum": "" }, "require": { @@ -8332,7 +8375,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.3.5" + "source": "https://github.com/symfony/console/tree/v7.3.6" }, "funding": [ { @@ -8352,20 +8395,20 @@ "type": "tidelift" } ], - "time": "2025-10-14T15:46:26+00:00" + "time": "2025-11-04T01:21:42+00:00" }, { "name": "symfony/css-selector", - "version": "v7.3.0", + "version": "v7.3.6", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "601a5ce9aaad7bf10797e3663faefce9e26c24e2" + "reference": "84321188c4754e64273b46b406081ad9b18e8614" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/601a5ce9aaad7bf10797e3663faefce9e26c24e2", - "reference": "601a5ce9aaad7bf10797e3663faefce9e26c24e2", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/84321188c4754e64273b46b406081ad9b18e8614", + "reference": "84321188c4754e64273b46b406081ad9b18e8614", "shasum": "" }, "require": { @@ -8401,7 +8444,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v7.3.0" + "source": "https://github.com/symfony/css-selector/tree/v7.3.6" }, "funding": [ { @@ -8412,12 +8455,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-25T14:21:43+00:00" + "time": "2025-10-29T17:24:25+00:00" }, { "name": "symfony/deprecation-contracts", @@ -8488,16 +8535,16 @@ }, { "name": "symfony/error-handler", - "version": "v7.3.4", + "version": "v7.3.6", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "99f81bc944ab8e5dae4f21b4ca9972698bbad0e4" + "reference": "bbe40bfab84323d99dab491b716ff142410a92a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/99f81bc944ab8e5dae4f21b4ca9972698bbad0e4", - "reference": "99f81bc944ab8e5dae4f21b4ca9972698bbad0e4", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/bbe40bfab84323d99dab491b716ff142410a92a8", + "reference": "bbe40bfab84323d99dab491b716ff142410a92a8", "shasum": "" }, "require": { @@ -8545,7 +8592,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v7.3.4" + "source": "https://github.com/symfony/error-handler/tree/v7.3.6" }, "funding": [ { @@ -8565,7 +8612,7 @@ "type": "tidelift" } ], - "time": "2025-09-11T10:12:26+00:00" + "time": "2025-10-31T19:12:50+00:00" }, { "name": "symfony/event-dispatcher", @@ -8797,16 +8844,16 @@ }, { "name": "symfony/html-sanitizer", - "version": "v7.3.3", + "version": "v7.3.6", "source": { "type": "git", "url": "https://github.com/symfony/html-sanitizer.git", - "reference": "8740fc48979f649dee8b8fc51a2698e5c190bf12" + "reference": "3855e827adb1b675adcb98ad7f92681e293f2d77" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/html-sanitizer/zipball/8740fc48979f649dee8b8fc51a2698e5c190bf12", - "reference": "8740fc48979f649dee8b8fc51a2698e5c190bf12", + "url": "https://api.github.com/repos/symfony/html-sanitizer/zipball/3855e827adb1b675adcb98ad7f92681e293f2d77", + "reference": "3855e827adb1b675adcb98ad7f92681e293f2d77", "shasum": "" }, "require": { @@ -8846,7 +8893,7 @@ "sanitizer" ], "support": { - "source": "https://github.com/symfony/html-sanitizer/tree/v7.3.3" + "source": "https://github.com/symfony/html-sanitizer/tree/v7.3.6" }, "funding": [ { @@ -8866,20 +8913,20 @@ "type": "tidelift" } ], - "time": "2025-08-12T10:34:03+00:00" + "time": "2025-10-30T13:22:58+00:00" }, { "name": "symfony/http-client", - "version": "v7.3.4", + "version": "v7.3.6", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "4b62871a01c49457cf2a8e560af7ee8a94b87a62" + "reference": "3c0a55a2c8e21e30a37022801c11c7ab5a6cb2de" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/4b62871a01c49457cf2a8e560af7ee8a94b87a62", - "reference": "4b62871a01c49457cf2a8e560af7ee8a94b87a62", + "url": "https://api.github.com/repos/symfony/http-client/zipball/3c0a55a2c8e21e30a37022801c11c7ab5a6cb2de", + "reference": "3c0a55a2c8e21e30a37022801c11c7ab5a6cb2de", "shasum": "" }, "require": { @@ -8946,7 +8993,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v7.3.4" + "source": "https://github.com/symfony/http-client/tree/v7.3.6" }, "funding": [ { @@ -8966,7 +9013,7 @@ "type": "tidelift" } ], - "time": "2025-09-11T10:12:26+00:00" + "time": "2025-11-05T17:41:46+00:00" }, { "name": "symfony/http-client-contracts", @@ -9048,16 +9095,16 @@ }, { "name": "symfony/http-foundation", - "version": "v7.3.5", + "version": "v7.3.6", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "ce31218c7cac92eab280762c4375fb70a6f4f897" + "reference": "6379e490d6ecfc5c4224ff3a754b90495ecd135c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/ce31218c7cac92eab280762c4375fb70a6f4f897", - "reference": "ce31218c7cac92eab280762c4375fb70a6f4f897", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/6379e490d6ecfc5c4224ff3a754b90495ecd135c", + "reference": "6379e490d6ecfc5c4224ff3a754b90495ecd135c", "shasum": "" }, "require": { @@ -9107,7 +9154,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v7.3.5" + "source": "https://github.com/symfony/http-foundation/tree/v7.3.6" }, "funding": [ { @@ -9127,20 +9174,20 @@ "type": "tidelift" } ], - "time": "2025-10-24T21:42:11+00:00" + "time": "2025-11-06T11:05:57+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.3.5", + "version": "v7.3.6", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "24fd3f123532e26025f49f1abefcc01a69ef15ab" + "reference": "f9a34dc0196677250e3609c2fac9de9e1551a262" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/24fd3f123532e26025f49f1abefcc01a69ef15ab", - "reference": "24fd3f123532e26025f49f1abefcc01a69ef15ab", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/f9a34dc0196677250e3609c2fac9de9e1551a262", + "reference": "f9a34dc0196677250e3609c2fac9de9e1551a262", "shasum": "" }, "require": { @@ -9225,7 +9272,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.3.5" + "source": "https://github.com/symfony/http-kernel/tree/v7.3.6" }, "funding": [ { @@ -9245,7 +9292,7 @@ "type": "tidelift" } ], - "time": "2025-10-28T10:19:01+00:00" + "time": "2025-11-06T20:58:12+00:00" }, { "name": "symfony/mailer", @@ -10454,16 +10501,16 @@ }, { "name": "symfony/routing", - "version": "v7.3.4", + "version": "v7.3.6", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "8dc648e159e9bac02b703b9fbd937f19ba13d07c" + "reference": "c97abe725f2a1a858deca629a6488c8fc20c3091" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/8dc648e159e9bac02b703b9fbd937f19ba13d07c", - "reference": "8dc648e159e9bac02b703b9fbd937f19ba13d07c", + "url": "https://api.github.com/repos/symfony/routing/zipball/c97abe725f2a1a858deca629a6488c8fc20c3091", + "reference": "c97abe725f2a1a858deca629a6488c8fc20c3091", "shasum": "" }, "require": { @@ -10515,7 +10562,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v7.3.4" + "source": "https://github.com/symfony/routing/tree/v7.3.6" }, "funding": [ { @@ -10535,20 +10582,20 @@ "type": "tidelift" } ], - "time": "2025-09-11T10:12:26+00:00" + "time": "2025-11-05T07:57:47+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.6.0", + "version": "v3.6.1", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4" + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4", - "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", "shasum": "" }, "require": { @@ -10602,7 +10649,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/service-contracts/tree/v3.6.1" }, "funding": [ { @@ -10613,12 +10660,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-04-25T09:37:31+00:00" + "time": "2025-07-15T11:30:57+00:00" }, { "name": "symfony/string", @@ -10812,16 +10863,16 @@ }, { "name": "symfony/translation-contracts", - "version": "v3.6.0", + "version": "v3.6.1", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d" + "reference": "65a8bc82080447fae78373aa10f8d13b38338977" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/df210c7a2573f1913b2d17cc95f90f53a73d8f7d", - "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/65a8bc82080447fae78373aa10f8d13b38338977", + "reference": "65a8bc82080447fae78373aa10f8d13b38338977", "shasum": "" }, "require": { @@ -10870,7 +10921,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/translation-contracts/tree/v3.6.1" }, "funding": [ { @@ -10881,12 +10932,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-27T08:32:26+00:00" + "time": "2025-07-15T13:41:35+00:00" }, { "name": "symfony/uid", @@ -12203,16 +12258,16 @@ }, { "name": "larastan/larastan", - "version": "v3.7.2", + "version": "v3.8.0", "source": { "type": "git", "url": "https://github.com/larastan/larastan.git", - "reference": "a761859a7487bd7d0cb8b662a7538a234d5bb5ae" + "reference": "d13ef96d652d1b2a8f34f1760ba6bf5b9c98112e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/larastan/larastan/zipball/a761859a7487bd7d0cb8b662a7538a234d5bb5ae", - "reference": "a761859a7487bd7d0cb8b662a7538a234d5bb5ae", + "url": "https://api.github.com/repos/larastan/larastan/zipball/d13ef96d652d1b2a8f34f1760ba6bf5b9c98112e", + "reference": "d13ef96d652d1b2a8f34f1760ba6bf5b9c98112e", "shasum": "" }, "require": { @@ -12226,7 +12281,7 @@ "illuminate/pipeline": "^11.44.2 || ^12.4.1", "illuminate/support": "^11.44.2 || ^12.4.1", "php": "^8.2", - "phpstan/phpstan": "^2.1.28" + "phpstan/phpstan": "^2.1.29" }, "require-dev": { "doctrine/coding-standard": "^13", @@ -12239,7 +12294,8 @@ "phpunit/phpunit": "^10.5.35 || ^11.5.15" }, "suggest": { - "orchestra/testbench": "Using Larastan for analysing a package needs Testbench" + "orchestra/testbench": "Using Larastan for analysing a package needs Testbench", + "phpmyadmin/sql-parser": "Install to enable Larastan's optional phpMyAdmin-based SQL parser automatically" }, "type": "phpstan-extension", "extra": { @@ -12280,7 +12336,7 @@ ], "support": { "issues": "https://github.com/larastan/larastan/issues", - "source": "https://github.com/larastan/larastan/tree/v3.7.2" + "source": "https://github.com/larastan/larastan/tree/v3.8.0" }, "funding": [ { @@ -12288,7 +12344,7 @@ "type": "github" } ], - "time": "2025-09-19T09:03:05+00:00" + "time": "2025-10-27T23:09:14+00:00" }, { "name": "laravel/pail", diff --git a/database/migrations/2025_10_14_065517_add_is_locked_to_allocations.php b/database/migrations/2025_10_14_065517_add_is_locked_to_allocations.php new file mode 100644 index 000000000..f7c429cb2 --- /dev/null +++ b/database/migrations/2025_10_14_065517_add_is_locked_to_allocations.php @@ -0,0 +1,28 @@ +boolean('is_locked')->default(false); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('allocations', function (Blueprint $table) { + $table->dropColumn('is_locked'); + }); + } +}; diff --git a/lang/en/admin/log.php b/lang/en/admin/log.php new file mode 100644 index 000000000..4c1a13159 --- /dev/null +++ b/lang/en/admin/log.php @@ -0,0 +1,26 @@ + 'Yay! No Errors!', + 'total_logs' => 'Total Logs', + 'error' => 'Error', + 'warning' => 'Warning', + 'notice' => 'Notice', + 'info' => 'Info', + 'debug' => 'Debug', + 'navigation' => [ + 'panel_logs' => 'Panel Logs', + ], + 'actions' => [ + 'upload_logs' => 'Upload Logs?', + 'upload_logs_description' => 'This will upload :file to :url Are you sure you wish to do this?', + 'view_logs' => 'View Logs', + 'log_not_found' => 'Log not found!', + 'log_not_found_description' => 'Could not find log for :filename', + 'failed_to_upload' => 'Failed to upload.', + 'failed_to_upload_description' => 'HTTP Status: :status', + 'log_upload' => 'Log Uploaded!', + 'log_upload_action' => 'View Log', + 'upload_tooltip' => 'Upload to :url', + ], +]; diff --git a/lang/en/admin/node.php b/lang/en/admin/node.php index a726300ee..40b514673 100644 --- a/lang/en/admin/node.php +++ b/lang/en/admin/node.php @@ -10,6 +10,7 @@ return [ 'basic_settings' => 'Basic Settings', 'advanced_settings' => 'Advanced Settings', 'config_file' => 'Configuration File', + 'diagnostics' => 'Diagnostics', ], 'table' => [ 'health' => 'Health', @@ -43,7 +44,7 @@ return [ 'error' => 'This is the domain name that points to your node\'s IP Address. If you\'ve already set up this, you can verify it by checking the next field!', 'fqdn_help' => 'Your panel is currently secured via an SSL certificate and that means your nodes require one too. You must use a domain name, because you cannot get SSL certificates for IP Addresses.', 'dns' => 'DNS Record Check', - 'dns_help' => 'This lets you know if you DNS record is pointing to the correct IP address.', + 'dns_help' => 'This lets you know if your DNS record is pointing to the correct IP address.', 'valid' => 'Valid', 'invalid' => 'Invalid', 'port' => 'Port', @@ -117,8 +118,35 @@ return [ 'error_connecting_description' => 'The configuration could not be automatically updated on Wings, you will need to manually update the configuration file.', 'allocation' => 'Allocation', + 'diagnostics' => [ + 'header' => 'Node Diagnostics', + 'include_endpoints' => 'Include Endpoints', + 'include_endpoints_hint' => 'Including endpoints will show panel urls within the logs and NOT obscure them.', + 'include_logs' => 'Include Logs', + 'include_logs_hint' => 'Including logs will show recent logs and help track down possible issues.', + 'run_diagnostics' => 'Run Diagnostics', + 'upload_to_pelican' => 'Upload Logs', + 'logs_pulled' => 'Logs Pulled!', + 'logs_uploaded' => 'Logs Uploaded', + 'upload_failed' => 'Logs Upload Failed', + 'view_logs' => 'View Logs', + 'pull' => 'Pull', + 'upload' => 'Upload', + 'clear' => 'Clear', + '404' => 'The requested diagnostic report could not be found. Make sure wings is up to date and try again.', + ], + 'cloudflare_issue' => [ 'title' => 'Cloudflare Issue', 'body' => 'Your Node is not accessible by Cloudflare', ], + + 'bulk_update_ip' => 'Update IPs', + 'bulk_update_ip_description' => 'Replace an old IP address with a new one for allocations. This is useful when a node\'s IP address changes', + 'update_ip' => 'Update IP', + 'old_ip' => 'Old IP Address', + 'new_ip' => 'New IP Address', + 'no_allocations_to_update' => 'No allocations with the selected old IP address were found', + 'ip_updated' => 'Successfully updated :count of :total allocation(s)', + 'ip_update_failed' => ':count allocation(s) failed to update', ]; diff --git a/lang/en/admin/server.php b/lang/en/admin/server.php index cd74c4159..935ca8232 100644 --- a/lang/en/admin/server.php +++ b/lang/en/admin/server.php @@ -13,6 +13,10 @@ return [ 'ports' => 'Ports', 'alias' => 'Alias', 'alias_helper' => 'Optional display name to help you remember what these are.', + 'locked' => 'Locked?', + 'locked_helper' => 'Users won\'t be able to delete locked allocations', + 'lock' => 'Lock', + 'unlock' => 'Unlock', 'name' => 'Name', 'external_id' => 'External ID', 'owner' => 'Owner', diff --git a/lang/en/server/file.php b/lang/en/server/file.php index dc936a477..63ac2c1dc 100644 --- a/lang/en/server/file.php +++ b/lang/en/server/file.php @@ -17,6 +17,11 @@ return [ 'from_files' => 'Upload Files', 'from_url' => 'Upload from URL', 'url' => 'URL', + 'drop_files' => 'Drop files to upload', + 'success' => 'Files uploaded successfully', + 'failed' => 'Failed to upload files', + 'header' => 'Uploading Files', + 'error' => 'An error occurred while uploading', ], 'rename' => [ 'title' => 'Rename', diff --git a/lang/en/server/network.php b/lang/en/server/network.php index 21ef09012..6ff0bf1bb 100644 --- a/lang/en/server/network.php +++ b/lang/en/server/network.php @@ -12,4 +12,6 @@ return [ 'primary' => 'Primary', 'make' => 'Make', 'delete' => 'Delete', + 'locked' => 'Locked?', + 'locked_helper' => 'Locked allocations can only be deleted by admins', ]; diff --git a/package.json b/package.json index ff4b3b123..47ca60a88 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "laravel-vite-plugin": "^1.0", "prettier": "^3.4.2", "tailwindcss": "^4.1.4", - "vite": "6.2.6" + "vite": "7.1.11" }, "dependencies": { "@xterm/addon-fit": "^0.10.0", diff --git a/resources/css/app.css b/resources/css/app.css index 8a072990b..8f68d0fb4 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -24,4 +24,6 @@ /* Required by widgets */ @import '../../vendor/filament/widgets/resources/css/index.css'; +@source '../../vendor/gboquizosanchez/filament-log-viewer/resources/views/**/*.blade.php'; + @variant dark (&:where(.dark, .dark *)); diff --git a/resources/views/filament/components/list-logs.blade.php b/resources/views/filament/components/list-logs.blade.php new file mode 100644 index 000000000..ce096a2d8 --- /dev/null +++ b/resources/views/filament/components/list-logs.blade.php @@ -0,0 +1,3 @@ + + {{ $this->table }} + diff --git a/resources/views/filament/server/pages/file-upload.blade.php b/resources/views/filament/server/pages/file-upload.blade.php new file mode 100644 index 000000000..c59899b23 --- /dev/null +++ b/resources/views/filament/server/pages/file-upload.blade.php @@ -0,0 +1,373 @@ +
+ + + +
+
+
+

+ {{ trans('server/file.actions.upload.header') }} - + + of + +

+
+ +
+
+ + + + +
+
+
+
+
+
diff --git a/resources/views/filament/server/pages/list-files.blade.php b/resources/views/filament/server/pages/list-files.blade.php new file mode 100644 index 000000000..6dc456e23 --- /dev/null +++ b/resources/views/filament/server/pages/list-files.blade.php @@ -0,0 +1,441 @@ + +
+
+
+
+ + + + + + +

+ {{ trans('server/file.actions.upload.drop_files') }} +

+
+
+
+ +
+
+
+

+ {{ trans('server/file.actions.upload.header') }} - + + of + +

+
+ +
+
+ + + + +
+
+
+
+
+ + {{ $this->table }} +
+ + +
diff --git a/yarn.lock b/yarn.lock index 64f28924b..866904019 100644 --- a/yarn.lock +++ b/yarn.lock @@ -182,105 +182,115 @@ "@emnapi/runtime" "^1.4.0" "@tybys/wasm-util" "^0.9.0" -"@rollup/rollup-android-arm-eabi@4.40.0": - version "4.40.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.0.tgz#d964ee8ce4d18acf9358f96adc408689b6e27fe3" - integrity sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg== +"@rollup/rollup-android-arm-eabi@4.53.1": + version "4.53.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.1.tgz#63f6bdc496180079976e655473d5bea99b21f3ff" + integrity sha512-bxZtughE4VNVJlL1RdoSE545kc4JxL7op57KKoi59/gwuU5rV6jLWFXXc8jwgFoT6vtj+ZjO+Z2C5nrY0Cl6wA== -"@rollup/rollup-android-arm64@4.40.0": - version "4.40.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.0.tgz#9b5e130ecc32a5fc1e96c09ff371743ee71a62d3" - integrity sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w== +"@rollup/rollup-android-arm64@4.53.1": + version "4.53.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.1.tgz#177f5e504d2f332edd0ddd3682f91ab72528fb60" + integrity sha512-44a1hreb02cAAfAKmZfXVercPFaDjqXCK+iKeVOlJ9ltvnO6QqsBHgKVPTu+MJHSLLeMEUbeG2qiDYgbFPU48g== -"@rollup/rollup-darwin-arm64@4.40.0": - version "4.40.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.0.tgz#ef439182c739b20b3c4398cfc03e3c1249ac8903" - integrity sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ== +"@rollup/rollup-darwin-arm64@4.53.1": + version "4.53.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.1.tgz#ffdbe0cc43c88a35be2821f99cdff4c7a5ee2116" + integrity sha512-usmzIgD0rf1syoOZ2WZvy8YpXK5G1V3btm3QZddoGSa6mOgfXWkkv+642bfUUldomgrbiLQGrPryb7DXLovPWQ== -"@rollup/rollup-darwin-x64@4.40.0": - version "4.40.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.0.tgz#d7380c1531ab0420ca3be16f17018ef72dd3d504" - integrity sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA== +"@rollup/rollup-darwin-x64@4.53.1": + version "4.53.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.1.tgz#27a4852923010abbcd1f028c7e8bd6bf0ccbe755" + integrity sha512-is3r/k4vig2Gt8mKtTlzzyaSQ+hd87kDxiN3uDSDwggJLUV56Umli6OoL+/YZa/KvtdrdyNfMKHzL/P4siOOmg== -"@rollup/rollup-freebsd-arm64@4.40.0": - version "4.40.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.0.tgz#cbcbd7248823c6b430ce543c59906dd3c6df0936" - integrity sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg== +"@rollup/rollup-freebsd-arm64@4.53.1": + version "4.53.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.1.tgz#a02b83018e487674ab445198786bef9b41cad9f0" + integrity sha512-QJ1ksgp/bDJkZB4daldVmHaEQkG4r8PUXitCOC2WRmRaSaHx5RwPoI3DHVfXKwDkB+Sk6auFI/+JHacTekPRSw== -"@rollup/rollup-freebsd-x64@4.40.0": - version "4.40.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.0.tgz#96bf6ff875bab5219c3472c95fa6eb992586a93b" - integrity sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw== +"@rollup/rollup-freebsd-x64@4.53.1": + version "4.53.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.1.tgz#fe898a4f0ff7c30f8377c3976ae76b89720c41da" + integrity sha512-J6ma5xgAzvqsnU6a0+jgGX/gvoGokqpkx6zY4cWizRrm0ffhHDpJKQgC8dtDb3+MqfZDIqs64REbfHDMzxLMqQ== -"@rollup/rollup-linux-arm-gnueabihf@4.40.0": - version "4.40.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.0.tgz#d80cd62ce6d40f8e611008d8dbf03b5e6bbf009c" - integrity sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA== +"@rollup/rollup-linux-arm-gnueabihf@4.53.1": + version "4.53.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.1.tgz#be5a731a9f7bd7bc707457a768940b6107a9215e" + integrity sha512-JzWRR41o2U3/KMNKRuZNsDUAcAVUYhsPuMlx5RUldw0E4lvSIXFUwejtYz1HJXohUmqs/M6BBJAUBzKXZVddbg== -"@rollup/rollup-linux-arm-musleabihf@4.40.0": - version "4.40.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.0.tgz#75440cfc1e8d0f87a239b4c31dfeaf4719b656b7" - integrity sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg== +"@rollup/rollup-linux-arm-musleabihf@4.53.1": + version "4.53.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.1.tgz#30ce6548a9e3591303507c37280300edb0cd1d14" + integrity sha512-L8kRIrnfMrEoHLHtHn+4uYA52fiLDEDyezgxZtGUTiII/yb04Krq+vk3P2Try+Vya9LeCE9ZHU8CXD6J9EhzHQ== -"@rollup/rollup-linux-arm64-gnu@4.40.0": - version "4.40.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.0.tgz#ac527485ecbb619247fb08253ec8c551a0712e7c" - integrity sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg== +"@rollup/rollup-linux-arm64-gnu@4.53.1": + version "4.53.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.1.tgz#ec76f4223335e86cd61b0d596f34e97223f4f711" + integrity sha512-ysAc0MFRV+WtQ8li8hi3EoFi7us6d1UzaS/+Dp7FYZfg3NdDljGMoVyiIp6Ucz7uhlYDBZ/zt6XI0YEZbUO11Q== -"@rollup/rollup-linux-arm64-musl@4.40.0": - version "4.40.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.0.tgz#74d2b5cb11cf714cd7d1682e7c8b39140e908552" - integrity sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ== +"@rollup/rollup-linux-arm64-musl@4.53.1": + version "4.53.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.1.tgz#9d4d87c2988ec8e4bb3cf4516dda7ef6d09dcd3d" + integrity sha512-UV6l9MJpDbDZZ/fJvqNcvO1PcivGEf1AvKuTcHoLjVZVFeAMygnamCTDikCVMRnA+qJe+B3pSbgX2+lBMqgBhA== -"@rollup/rollup-linux-loongarch64-gnu@4.40.0": - version "4.40.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.0.tgz#a0a310e51da0b5fea0e944b0abd4be899819aef6" - integrity sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg== +"@rollup/rollup-linux-loong64-gnu@4.53.1": + version "4.53.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.1.tgz#584bc6f3c33b30c3dbfdad36ac9c7792e4df5199" + integrity sha512-UDUtelEprkA85g95Q+nj3Xf0M4hHa4DiJ+3P3h4BuGliY4NReYYqwlc0Y8ICLjN4+uIgCEvaygYlpf0hUj90Yg== -"@rollup/rollup-linux-powerpc64le-gnu@4.40.0": - version "4.40.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.0.tgz#4077e2862b0ac9f61916d6b474d988171bd43b83" - integrity sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw== +"@rollup/rollup-linux-ppc64-gnu@4.53.1": + version "4.53.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.1.tgz#3e9a3b095a7d7da6043cb9caa54439d3b598aaf5" + integrity sha512-vrRn+BYhEtNOte/zbc2wAUQReJXxEx2URfTol6OEfY2zFEUK92pkFBSXRylDM7aHi+YqEPJt9/ABYzmcrS4SgQ== -"@rollup/rollup-linux-riscv64-gnu@4.40.0": - version "4.40.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.0.tgz#5812a1a7a2f9581cbe12597307cc7ba3321cf2f3" - integrity sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA== +"@rollup/rollup-linux-riscv64-gnu@4.53.1": + version "4.53.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.1.tgz#f3c3d6523d246eef4aa1eed265f1ba31b9eef7c8" + integrity sha512-gto/1CxHyi4A7YqZZNznQYrVlPSaodOBPKM+6xcDSCMVZN/Fzb4K+AIkNz/1yAYz9h3Ng+e2fY9H6bgawVq17w== -"@rollup/rollup-linux-riscv64-musl@4.40.0": - version "4.40.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.0.tgz#973aaaf4adef4531375c36616de4e01647f90039" - integrity sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ== +"@rollup/rollup-linux-riscv64-musl@4.53.1": + version "4.53.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.1.tgz#0a8944b4f29a1ba923fb9c2ddb829e621f004988" + integrity sha512-KZ6Vx7jAw3aLNjFR8eYVcQVdFa/cvBzDNRFM3z7XhNNunWjA03eUrEwJYPk0G8V7Gs08IThFKcAPS4WY/ybIrQ== -"@rollup/rollup-linux-s390x-gnu@4.40.0": - version "4.40.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.0.tgz#9bad59e907ba5bfcf3e9dbd0247dfe583112f70b" - integrity sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw== +"@rollup/rollup-linux-s390x-gnu@4.53.1": + version "4.53.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.1.tgz#bcb48f2d509ef6b33ba89f7d76a2f3805be8d4c8" + integrity sha512-HvEixy2s/rWNgpwyKpXJcHmE7om1M89hxBTBi9Fs6zVuLU4gOrEMQNbNsN/tBVIMbLyysz/iwNiGtMOpLAOlvA== -"@rollup/rollup-linux-x64-gnu@4.40.0": - version "4.40.0" - resolved "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.0.tgz" - integrity sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ== +"@rollup/rollup-linux-x64-gnu@4.53.1": + version "4.53.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.1.tgz#ca9045e3b8e8dc0797e55d0229d5c664211bf366" + integrity sha512-E/n8x2MSjAQgjj9IixO4UeEUeqXLtiA7pyoXCFYLuXpBA/t2hnbIdxHfA7kK9BFsYAoNU4st1rHYdldl8dTqGA== -"@rollup/rollup-linux-x64-musl@4.40.0": - version "4.40.0" - resolved "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.0.tgz" - integrity sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw== +"@rollup/rollup-linux-x64-musl@4.53.1": + version "4.53.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.1.tgz#740876db76078e37bd43cc8584ff1c7f6b382df8" + integrity sha512-IhJ087PbLOQXCN6Ui/3FUkI9pWNZe/Z7rEIVOzMsOs1/HSAECCvSZ7PkIbkNqL/AZn6WbZvnoVZw/qwqYMo4/w== -"@rollup/rollup-win32-arm64-msvc@4.40.0": - version "4.40.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.0.tgz#c5bee19fa670ff5da5f066be6a58b4568e9c650b" - integrity sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ== +"@rollup/rollup-openharmony-arm64@4.53.1": + version "4.53.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.1.tgz#3ff19213afe46b806fb6ec105f2664e4027e4cbc" + integrity sha512-0++oPNgLJHBblreu0SFM7b3mAsBJBTY0Ksrmu9N6ZVrPiTkRgda52mWR7TKhHAsUb9noCjFvAw9l6ZO1yzaVbA== -"@rollup/rollup-win32-ia32-msvc@4.40.0": - version "4.40.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.0.tgz#846e02c17044bd922f6f483a3b4d36aac6e2b921" - integrity sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA== +"@rollup/rollup-win32-arm64-msvc@4.53.1": + version "4.53.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.1.tgz#cbba39610831747f8050a306811776534df1030d" + integrity sha512-VJXivz61c5uVdbmitLkDlbcTk9Or43YC2QVLRkqp86QoeFSqI81bNgjhttqhKNMKnQMWnecOCm7lZz4s+WLGpQ== -"@rollup/rollup-win32-x64-msvc@4.40.0": - version "4.40.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.0.tgz#fd92d31a2931483c25677b9c6698106490cbbc76" - integrity sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ== +"@rollup/rollup-win32-ia32-msvc@4.53.1": + version "4.53.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.1.tgz#5453c7ebba95d2bbfcc94c744c05586d587fb640" + integrity sha512-NmZPVTUOitCXUH6erJDzTQ/jotYw4CnkMDjCYRxNHVD9bNyfrGoIse684F9okwzKCV4AIHRbUkeTBc9F2OOH5Q== + +"@rollup/rollup-win32-x64-gnu@4.53.1": + version "4.53.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.1.tgz#01e1acb0dacb220d13c8992340f7bc868a564832" + integrity sha512-2SNj7COIdAf6yliSpLdLG8BEsp5lgzRehgfkP0Av8zKfQFKku6JcvbobvHASPJu4f3BFxej5g+HuQPvqPhHvpQ== + +"@rollup/rollup-win32-x64-msvc@4.53.1": + version "4.53.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.1.tgz#56eeb602545ec03ce84633b331c2e3ece07b99c3" + integrity sha512-rLarc1Ofcs3DHtgSzFO31pZsCh8g05R2azN1q3fF+H423Co87My0R+tazOEvYVKXSLh8C4LerMK41/K7wlklcg== "@tailwindcss/forms@^0.5.9": version "0.5.10" @@ -410,10 +420,10 @@ dependencies: tslib "^2.4.0" -"@types/estree@1.0.7": - version "1.0.7" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.7.tgz#4158d3105276773d5b7695cd4834b1722e4f37a8" - integrity sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ== +"@types/estree@1.0.8": + version "1.0.8" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e" + integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== "@xterm/addon-fit@^0.10.0": version "0.10.0" @@ -632,6 +642,11 @@ escalade@^3.1.1, escalade@^3.2.0: resolved "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz" integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== +fdir@^6.5.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350" + integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg== + foreground-child@^3.3.1: version "3.3.1" resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.1.tgz#32e8e9ed1b68a3497befb9ac2b6adf92a638576f" @@ -817,9 +832,9 @@ minipass@^7.1.2: resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== -nanoid@^3.3.8: +nanoid@^3.3.11: version "3.3.11" - resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b" integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w== node-releases@^2.0.19: @@ -860,6 +875,11 @@ picomatch@^2.3.1: resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== +picomatch@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.3.tgz#796c76136d1eead715db1e7bad785dedd695a042" + integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q== + postcss-selector-parser@6.0.10: version "6.0.10" resolved "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz" @@ -873,12 +893,12 @@ postcss-value-parser@^4.2.0: resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== -postcss@^8.5.3: - version "8.5.3" - resolved "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz" - integrity sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A== +postcss@^8.5.6: + version "8.5.6" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.6.tgz#2825006615a619b4f62a9e7426cc120b349a8f3c" + integrity sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg== dependencies: - nanoid "^3.3.8" + nanoid "^3.3.11" picocolors "^1.1.1" source-map-js "^1.2.1" @@ -897,33 +917,35 @@ require-directory@^2.1.1: resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz" integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== -rollup@^4.30.1: - version "4.40.0" - resolved "https://registry.npmjs.org/rollup/-/rollup-4.40.0.tgz" - integrity sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w== +rollup@^4.43.0: + version "4.53.1" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.53.1.tgz#84d1d378584a15dedfcdcff7767a8b9d92d8d3d9" + integrity sha512-n2I0V0lN3E9cxxMqBCT3opWOiQBzRN7UG60z/WDKqdX2zHUS/39lezBcsckZFsV6fUTSnfqI7kHf60jDAPGKug== dependencies: - "@types/estree" "1.0.7" + "@types/estree" "1.0.8" optionalDependencies: - "@rollup/rollup-android-arm-eabi" "4.40.0" - "@rollup/rollup-android-arm64" "4.40.0" - "@rollup/rollup-darwin-arm64" "4.40.0" - "@rollup/rollup-darwin-x64" "4.40.0" - "@rollup/rollup-freebsd-arm64" "4.40.0" - "@rollup/rollup-freebsd-x64" "4.40.0" - "@rollup/rollup-linux-arm-gnueabihf" "4.40.0" - "@rollup/rollup-linux-arm-musleabihf" "4.40.0" - "@rollup/rollup-linux-arm64-gnu" "4.40.0" - "@rollup/rollup-linux-arm64-musl" "4.40.0" - "@rollup/rollup-linux-loongarch64-gnu" "4.40.0" - "@rollup/rollup-linux-powerpc64le-gnu" "4.40.0" - "@rollup/rollup-linux-riscv64-gnu" "4.40.0" - "@rollup/rollup-linux-riscv64-musl" "4.40.0" - "@rollup/rollup-linux-s390x-gnu" "4.40.0" - "@rollup/rollup-linux-x64-gnu" "4.40.0" - "@rollup/rollup-linux-x64-musl" "4.40.0" - "@rollup/rollup-win32-arm64-msvc" "4.40.0" - "@rollup/rollup-win32-ia32-msvc" "4.40.0" - "@rollup/rollup-win32-x64-msvc" "4.40.0" + "@rollup/rollup-android-arm-eabi" "4.53.1" + "@rollup/rollup-android-arm64" "4.53.1" + "@rollup/rollup-darwin-arm64" "4.53.1" + "@rollup/rollup-darwin-x64" "4.53.1" + "@rollup/rollup-freebsd-arm64" "4.53.1" + "@rollup/rollup-freebsd-x64" "4.53.1" + "@rollup/rollup-linux-arm-gnueabihf" "4.53.1" + "@rollup/rollup-linux-arm-musleabihf" "4.53.1" + "@rollup/rollup-linux-arm64-gnu" "4.53.1" + "@rollup/rollup-linux-arm64-musl" "4.53.1" + "@rollup/rollup-linux-loong64-gnu" "4.53.1" + "@rollup/rollup-linux-ppc64-gnu" "4.53.1" + "@rollup/rollup-linux-riscv64-gnu" "4.53.1" + "@rollup/rollup-linux-riscv64-musl" "4.53.1" + "@rollup/rollup-linux-s390x-gnu" "4.53.1" + "@rollup/rollup-linux-x64-gnu" "4.53.1" + "@rollup/rollup-linux-x64-musl" "4.53.1" + "@rollup/rollup-openharmony-arm64" "4.53.1" + "@rollup/rollup-win32-arm64-msvc" "4.53.1" + "@rollup/rollup-win32-ia32-msvc" "4.53.1" + "@rollup/rollup-win32-x64-gnu" "4.53.1" + "@rollup/rollup-win32-x64-msvc" "4.53.1" fsevents "~2.3.2" rxjs-compat@^6.5.4: @@ -1037,6 +1059,14 @@ tapable@^2.2.0: resolved "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz" integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== +tinyglobby@^0.2.15: + version "0.2.15" + resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.15.tgz#e228dd1e638cea993d2fdb4fcd2d4602a79951c2" + integrity sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ== + dependencies: + fdir "^6.5.0" + picomatch "^4.0.3" + tree-kill@^1.2.2: version "1.2.2" resolved "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz" @@ -1068,14 +1098,17 @@ vite-plugin-full-reload@^1.1.0: picocolors "^1.0.0" picomatch "^2.3.1" -vite@6.2.6: - version "6.2.6" - resolved "https://registry.npmjs.org/vite/-/vite-6.2.6.tgz" - integrity sha512-9xpjNl3kR4rVDZgPNdTL0/c6ao4km69a/2ihNQbcANz8RuCOK3hQBmLSJf3bRKVQjVMda+YvizNE8AwvogcPbw== +vite@7.1.11: + version "7.1.11" + resolved "https://registry.yarnpkg.com/vite/-/vite-7.1.11.tgz#4d006746112fee056df64985191e846ebfb6007e" + integrity sha512-uzcxnSDVjAopEUjljkWh8EIrg6tlzrjFUfMcR1EVsRDGwf/ccef0qQPRyOrROwhrTDaApueq+ja+KLPlzR/zdg== dependencies: esbuild "^0.25.0" - postcss "^8.5.3" - rollup "^4.30.1" + fdir "^6.5.0" + picomatch "^4.0.3" + postcss "^8.5.6" + rollup "^4.43.0" + tinyglobby "^0.2.15" optionalDependencies: fsevents "~2.3.3"