276 lines
8.8 KiB
PHP
276 lines
8.8 KiB
PHP
<?php
|
|
|
|
namespace App\Filament\Widgets;
|
|
|
|
use App\Api\Plugins\PluginLoader;
|
|
use App\Filament\Resources\ApiProviders\ApiProviderResource;
|
|
use App\Models\ApiProvider;
|
|
use Filament\Actions\Action;
|
|
use Filament\Actions\BulkAction;
|
|
use Filament\Actions\BulkActionGroup;
|
|
use Filament\Notifications\Notification;
|
|
use Filament\Tables\Columns\TextColumn;
|
|
use Filament\Widgets\TableWidget;
|
|
use Illuminate\Database\Eloquent\Builder;
|
|
use Illuminate\Database\Eloquent\Collection;
|
|
use RuntimeException;
|
|
use Throwable;
|
|
|
|
class ApiProviderHealth extends TableWidget
|
|
{
|
|
protected static ?string $heading = 'API Provider Health';
|
|
|
|
protected static ?string $description = 'Status, Tests & Quick enable';
|
|
|
|
protected int|string|array $columnSpan = 'full';
|
|
|
|
protected static ?int $sort = -5;
|
|
|
|
protected function getTableQuery(): Builder
|
|
{
|
|
return ApiProvider::query()
|
|
->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',
|
|
};
|
|
}
|
|
}
|