count() ?: null; } public static function table(Table $table): Table { return $table ->openRecordUrlInNewTab() ->reorderable('load_order') ->authorizeReorder(fn () => user()?->can('update plugin')) ->reorderRecordsTriggerAction(fn (Action $action, bool $isReordering) => $action->hiddenLabel()->tooltip($isReordering ? trans('admin/plugin.apply_load_order') : trans('admin/plugin.change_load_order'))) ->defaultSort('load_order') ->columns([ TextColumn::make('name') ->label(trans('admin/plugin.name')) ->description(fn (Plugin $plugin) => (strlen($plugin->description) > 80) ? substr($plugin->description, 0, 80).'...' : $plugin->description) ->icon(fn (Plugin $plugin) => $plugin->isUpdateAvailable() ? TablerIcon::VersionsOff : TablerIcon::Versions) ->iconColor(fn (Plugin $plugin) => $plugin->isUpdateAvailable() ? 'danger' : 'success') ->tooltip(fn (Plugin $plugin) => $plugin->isUpdateAvailable() ? trans('admin/plugin.update_available') : null) ->sortable() ->searchable(), TextColumn::make('author') ->label(trans('admin/plugin.author')) ->sortable(), TextColumn::make('version') ->label(trans('admin/plugin.version')) ->sortable(), TextColumn::make('category') ->label(trans('admin/plugin.category')) ->badge() ->sortable() ->visible(fn ($livewire) => $livewire->activeTab === 'all'), TextColumn::make('status') ->label(trans('admin/plugin.status')) ->badge() ->tooltip(fn (Plugin $plugin) => $plugin->status_message) ->sortable(), ]) ->recordActions([ Action::make('exclude_view') ->label(trans('filament-actions::view.single.label')) ->icon(fn (Plugin $plugin) => $plugin->getReadme() ? TablerIcon::Eye : TablerIcon::EyeShare) ->color('gray') ->visible(fn (Plugin $plugin) => $plugin->getReadme() || $plugin->url) ->url(fn (Plugin $plugin) => !$plugin->getReadme() ? $plugin->url : null, true) ->slideOver(true) ->modalHeading('Readme') ->modalSubmitAction(fn (Plugin $plugin) => Action::make('exclude_visit_website') ->label(trans('admin/plugin.visit_website')) ->visible(!is_null($plugin->url)) ->url($plugin->url, true) ) ->modalCancelActionLabel(trans('filament::components/modal.actions.close.label')) ->schema(fn (Plugin $plugin) => $plugin->getReadme() ? [ TextEntry::make('readme') ->hiddenLabel() ->markdown() ->state(fn (Plugin $plugin) => $plugin->getReadme()), ] : null), Action::make('exclude_settings') ->label(trans('admin/plugin.settings')) ->authorize(fn (Plugin $plugin) => user()?->can('update', $plugin)) ->icon(TablerIcon::Settings) ->color('primary') ->visible(fn (Plugin $plugin) => $plugin->status === PluginStatus::Enabled && $plugin->hasSettings()) ->schema(fn (Plugin $plugin) => $plugin->getSettingsForm()) ->action(fn (array $data, Plugin $plugin) => $plugin->saveSettings($data)) ->slideOver(), ActionGroup::make([ Action::make('exclude_install') ->label(trans('admin/plugin.install')) ->authorize(fn (Plugin $plugin) => user()?->can('update', $plugin)) ->icon(TablerIcon::Terminal) ->color('success') ->hidden(fn (Plugin $plugin) => $plugin->status !== PluginStatus::NotInstalled) ->action(function (Plugin $plugin) { try { InstallPlugin::dispatch(user(), $plugin); Notification::make() ->success() ->title(trans('admin/plugin.notifications.install_started')) ->body(trans('admin/plugin.notifications.background_info')) ->send(); } catch (Exception $exception) { Notification::make() ->danger() ->title(trans('admin/plugin.notifications.install_error')) ->body($exception->getMessage()) ->send(); } }), Action::make('exclude_update') ->label(trans('admin/plugin.update')) ->authorize(fn (Plugin $plugin) => user()?->can('update', $plugin)) ->icon(TablerIcon::Download) ->color('success') ->visible(fn (Plugin $plugin) => $plugin->status !== PluginStatus::NotInstalled && $plugin->isUpdateAvailable()) ->action(function (Plugin $plugin) { try { UpdatePlugin::dispatch(user(), $plugin); Notification::make() ->success() ->title(trans('admin/plugin.notifications.update_started')) ->body(trans('admin/plugin.notifications.background_info')) ->send(); } catch (Exception $exception) { Notification::make() ->danger() ->title(trans('admin/plugin.notifications.update_error')) ->body($exception->getMessage()) ->send(); } }), Action::make('exclude_enable') ->label(trans('admin/plugin.enable')) ->authorize(fn (Plugin $plugin) => user()?->can('update', $plugin)) ->icon(TablerIcon::Check) ->color('success') ->visible(fn (Plugin $plugin) => $plugin->canEnable()) ->requiresConfirmation(fn (Plugin $plugin, PluginService $pluginService) => $plugin->isTheme() && $pluginService->hasThemePluginEnabled()) ->modalHeading(fn (Plugin $plugin, PluginService $pluginService) => $plugin->isTheme() && $pluginService->hasThemePluginEnabled() ? trans('admin/plugin.enable_theme_modal.heading') : null) ->modalDescription(fn (Plugin $plugin, PluginService $pluginService) => $plugin->isTheme() && $pluginService->hasThemePluginEnabled() ? trans('admin/plugin.enable_theme_modal.description') : null) ->action(function (Plugin $plugin, $livewire, PluginService $pluginService) { $pluginService->enablePlugin($plugin); redirect(ListPlugins::getUrl(['tab' => $livewire->activeTab])); Notification::make() ->success() ->title(trans('admin/plugin.notifications.enabled')) ->send(); }), Action::make('exclude_disable') ->label(trans('admin/plugin.disable')) ->authorize(fn (Plugin $plugin) => user()?->can('update', $plugin)) ->icon(TablerIcon::X) ->color('warning') ->visible(fn (Plugin $plugin) => $plugin->canDisable()) ->action(function (Plugin $plugin, $livewire, PluginService $pluginService) { $pluginService->disablePlugin($plugin); redirect(ListPlugins::getUrl(['tab' => $livewire->activeTab])); Notification::make() ->success() ->title(trans('admin/plugin.notifications.disabled')) ->send(); }), Action::make('exclude_delete') ->label(trans('filament-actions::delete.single.label')) ->authorize(fn (Plugin $plugin) => user()?->can('delete', $plugin)) ->icon(TablerIcon::Trash) ->color('danger') ->requiresConfirmation() ->visible(fn (Plugin $plugin) => $plugin->status === PluginStatus::NotInstalled || $plugin->status === PluginStatus::Errored) ->action(function (Plugin $plugin, $livewire, PluginService $pluginService) { $pluginService->deletePlugin($plugin); redirect(ListPlugins::getUrl(['tab' => $livewire->activeTab])); Notification::make() ->success() ->title(trans('admin/plugin.notifications.deleted')) ->send(); }), Action::make('exclude_uninstall') ->label(trans('admin/plugin.uninstall')) ->authorize(fn (Plugin $plugin) => user()?->can('update', $plugin)) ->icon(TablerIcon::Terminal) ->color('danger') ->requiresConfirmation() ->hidden(fn (Plugin $plugin) => $plugin->status === PluginStatus::NotInstalled || $plugin->status === PluginStatus::Errored) ->action(function (Plugin $plugin) { try { UninstallPlugin::dispatch(user(), $plugin); Notification::make() ->success() ->title(trans('admin/plugin.notifications.uninstall_started')) ->body(trans('admin/plugin.notifications.background_info')) ->send(); } catch (Exception $exception) { Notification::make() ->danger() ->title(trans('admin/plugin.notifications.uninstall_error')) ->body($exception->getMessage()) ->send(); } }), ]), ]) ->headerActions([ Action::make('import_from_file') ->hiddenLabel() ->tooltip(trans('admin/plugin.import_from_file')) ->authorize(fn () => user()?->can('create', Plugin::class)) ->icon(TablerIcon::FileDownload) ->schema([ // TODO: switch to new file upload FileUpload::make('file') ->required() ->acceptedFileTypes(['application/zip', 'application/zip-compressed', 'application/x-zip-compressed']) ->preserveFilenames() ->previewable(false) ->storeFiles(false), ]) ->action(function ($data, $livewire, PluginService $pluginService) { try { /** @var UploadedFile $file */ $file = $data['file']; $pluginName = str($file->getClientOriginalName())->before('.zip')->toString(); if (Plugin::where('id', $pluginName)->exists()) { throw new Exception(trans('admin/plugin.notifications.import_exists')); } $pluginService->downloadPluginFromFile($file); Notification::make() ->success() ->title(trans('admin/plugin.notifications.imported')) ->send(); redirect(ListPlugins::getUrl(['tab' => $livewire->activeTab])); } catch (Exception $exception) { report($exception); Notification::make() ->danger() ->title(trans('admin/plugin.notifications.import_failed')) ->body($exception->getMessage()) ->send(); } }), Action::make('import_from_url') ->hiddenLabel() ->tooltip(trans('admin/plugin.import_from_url')) ->authorize(fn () => user()?->can('create', Plugin::class)) ->icon(TablerIcon::WorldDownload) ->schema([ TextInput::make('url') ->required() ->url() ->endsWith('.zip'), ]) ->action(function ($data, $livewire, PluginService $pluginService) { try { $pluginName = str($data['url'])->before('.zip')->explode('/')->last(); if (Plugin::where('id', $pluginName)->exists()) { throw new Exception(trans('admin/plugin.notifications.import_exists')); } $pluginService->downloadPluginFromUrl($data['url']); Notification::make() ->success() ->title(trans('admin/plugin.notifications.imported')) ->send(); redirect(ListPlugins::getUrl(['tab' => $livewire->activeTab])); } catch (Exception $exception) { report($exception); Notification::make() ->danger() ->title(trans('admin/plugin.notifications.import_failed')) ->body($exception->getMessage()) ->send(); } }), ]) ->emptyStateIcon(TablerIcon::Packages) ->emptyStateDescription('') ->emptyStateHeading(trans('admin/plugin.no_plugins')); } public static function getPages(): array { return [ 'index' => ListPlugins::route('/'), ]; } }