orderByRaw('enabled = 0 desc') ->orderByRaw("case when coalesce(last_status, '') = 'online' then 1 else 0 end") ->orderByDesc('updated_at'); } protected function getTableColumns(): array { return [ TextColumn::make('name') ->label('Provider') ->searchable() ->sortable() ->wrap() ->url(fn (ApiProvider $record): string => ApiProviderResource::getUrl('edit', ['record' => $record])) ->openUrlInNewTab(), TextColumn::make('plugin') ->label('Plugin') ->badge() ->color('gray'), TextColumn::make('status_label') ->label('Status') ->badge() ->state(fn (ApiProvider $record): string => $this->statusLabel($record)) ->color(fn (ApiProvider $record): string => $this->statusColor($record)), TextColumn::make('last_response_time_ms') ->label('Latency') ->formatStateUsing(fn (?int $state): string => $state ? $state.' ms' : '—') ->color('gray'), TextColumn::make('last_checked_at') ->label('Geprüft') ->formatStateUsing(fn ($state): string => $state ? $state->diffForHumans() : 'Noch nie') ->sortable(), TextColumn::make('last_error') ->label('Letzter Fehler') ->limit(40) ->tooltip(fn (ApiProvider $record): ?string => $record->last_error), ]; } protected function getTableActions(): array { return [ Action::make('test') ->label('Testen') ->icon('heroicon-o-bolt') ->color('warning') ->action(function (ApiProvider $record): void { $this->testProvider($record); }), Action::make('toggle') ->label(fn (ApiProvider $record): string => $record->enabled ? 'Deaktivieren' : 'Aktivieren') ->icon('heroicon-o-power') ->color(fn (ApiProvider $record): string => $record->enabled ? 'gray' : 'success') ->requiresConfirmation(fn (ApiProvider $record): bool => $record->enabled) ->action(function (ApiProvider $record): void { if ($record->enabled) { $this->disableProvider($record); return; } $this->enableProvider($record, true); }), ]; } protected function getTableHeaderActions(): array { return [ Action::make('refresh') ->label('Alle testen') ->icon('heroicon-o-arrow-path') ->color('gray') ->action(function (): void { $this->testAllProviders(); }), ]; } protected function getTableBulkActions(): array { return [ BulkActionGroup::make([ BulkAction::make('bulkEnable') ->label('Aktivieren') ->icon('heroicon-o-power') ->color('success') ->action(function (Collection $records): void { $records->each(function (ApiProvider $record): void { $this->enableProvider($record, false); }); }), BulkAction::make('bulkDisable') ->label('Deaktivieren') ->icon('heroicon-o-stop') ->color('gray') ->action(function (Collection $records): void { $records->each(function (ApiProvider $record): void { $this->disableProvider($record); }); }), ]), ]; } private function testProvider(ApiProvider $provider): void { $result = $this->probeProvider($provider); $notification = Notification::make() ->title($result['ok'] ? 'Verbindung erfolgreich' : 'Verbindung fehlgeschlagen') ->body($result['error'] ?? 'Provider antwortet.'); $result['ok'] ? $notification->success() : $notification->danger(); $notification->send(); } private function testAllProviders(): void { ApiProvider::query()->get()->each(fn (ApiProvider $provider) => $this->probeProvider($provider)); Notification::make() ->title('Tests gestartet') ->body('Alle Provider wurden geprüft.') ->success() ->send(); } private function enableProvider(ApiProvider $provider, bool $withTest = true): void { $ok = true; $result = null; if ($withTest) { $result = $this->probeProvider($provider); $ok = $result['ok']; } if ($ok) { $provider->forceFill(['enabled' => true])->save(); Notification::make() ->title('Provider aktiviert') ->body($provider->name.' ist aktiviert'.($result ? ' (Test ok).' : '.')) ->success() ->send(); return; } Notification::make() ->title('Aktivierung abgebrochen') ->body($result['error'] ?? 'Verbindung fehlgeschlagen.') ->danger() ->send(); } private function disableProvider(ApiProvider $provider): void { $provider->disableWithDependencies(); Notification::make() ->title('Provider deaktiviert') ->body($provider->name.' wurde deaktiviert.') ->success() ->send(); } private function probeProvider(ApiProvider $provider): array { $startedAt = microtime(true); $status = $provider->enabled ? 'offline' : 'disabled'; $error = null; $duration = null; $ok = false; try { if (! $provider->plugin) { $status = 'plugin_missing'; throw new RuntimeException('Kein Plugin definiert.'); } $plugin = PluginLoader::getPlugin($provider->plugin, $provider); $success = $plugin->testConnection($provider->toArray()); $status = $success ? 'online' : 'offline'; $ok = $success; } catch (Throwable $exception) { $status = match (true) { $status === 'disabled' => 'disabled', $status === 'plugin_missing' => 'plugin_missing', default => 'error', }; $error = $exception->getMessage(); } finally { $duration = (int) round((microtime(true) - $startedAt) * 1000); } $provider->forceFill([ 'last_checked_at' => now(), 'last_status' => $status, 'last_response_time_ms' => $duration, 'last_error' => $error, ])->save(); return [ 'ok' => $ok, 'error' => $error, 'duration' => $duration, ]; } private function statusLabel(ApiProvider $provider): string { if (! $provider->enabled) { return 'Disabled'; } return match ($provider->last_status) { 'online' => 'Online', 'offline' => 'Offline', 'error' => 'Error', 'plugin_missing' => 'Plugin fehlt', default => 'Unbekannt', }; } private function statusColor(ApiProvider $provider): string { if (! $provider->enabled) { return 'gray'; } return match ($provider->last_status) { 'online' => 'success', 'offline' => 'danger', 'error' => 'danger', 'plugin_missing' => 'warning', default => 'warning', }; } }