where('is_active', false) ->where('consecutive_failures', '>=', Webhook::MAX_FAILURES) ->count(); return $off > 0 ? (string) $off : null; } public static function getNavigationBadgeColor(): ?string { return 'danger'; } public static function form(Schema $schema): Schema { return $schema->components([ Section::make() ->columns(2) ->schema([ TextInput::make('name') ->label(__('panel.webhooks.fields.name')) ->required() ->maxLength(120), TextInput::make('url') ->label(__('panel.webhooks.fields.url')) ->helperText(__('panel.webhooks.fields.url_hint')) ->required() ->url() ->maxLength(2048) ->rule(new PublicHttpsUrl), CheckboxList::make('events') ->label(__('panel.webhooks.fields.events')) ->options(fn () => collect(['*', ...Webhook::EVENTS]) ->mapWithKeys(fn (string $event) => [$event => self::eventLabel($event)]) ->all()) ->descriptions(fn () => collect(['*', ...Webhook::EVENTS]) ->mapWithKeys(fn (string $event) => [$event => $event]) ->all()) ->required() ->columns(2) ->columnSpanFull(), Toggle::make('include_personal_data') ->label(__('panel.webhooks.fields.include_personal_data')) ->helperText(__('panel.webhooks.fields.include_personal_data_hint')), Toggle::make('is_active') ->label(__('panel.webhooks.fields.is_active')) ->default(true), ]), ]); } public static function table(Table $table): Table { return $table ->columns([ TextColumn::make('name') ->label(__('panel.webhooks.fields.name')) ->weight('bold') ->description(fn (Webhook $record) => $record->url) ->searchable(), TextColumn::make('events') ->label(__('panel.webhooks.fields.events')) ->badge() ->color('gray'), IconColumn::make('is_active') ->label(__('panel.webhooks.fields.is_active')) ->boolean(), TextColumn::make('consecutive_failures') ->label(__('panel.webhooks.fields.failures')) ->color(fn (int $state) => $state > 0 ? 'danger' : 'gray'), TextColumn::make('last_delivered_at') ->label(__('panel.webhooks.fields.last_delivered_at')) ->since() ->dateTimeTooltip() ->placeholder('—'), ]) ->recordActions([ self::pingAction(), EditAction::make(), DeleteAction::make(), ]); } public static function eventLabel(string $event): string { $key = str_replace('.', '_', $event); return __("panel.webhooks.events.{$key}"); } public static function pingAction(): Action { return Action::make('ping') ->label(__('panel.webhooks.actions.ping')) ->icon('heroicon-o-paper-airplane') ->color('gray') ->authorize('update') ->action(function (Webhook $record): void { Webhooks::ping($record); Notification::make() ->title(__('panel.webhooks.actions.ping_sent')) ->body(__('panel.webhooks.actions.ping_hint')) ->success() ->send(); }); } /** * A new secret, shown once. The old one stops being valid at once, so the * receiving end has to be updated straight after. */ public static function rotateAction(): Action { return Action::make('rotate_secret') ->label(__('panel.webhooks.actions.rotate')) ->icon('heroicon-o-key') ->color('warning') ->requiresConfirmation() ->modalDescription(__('panel.webhooks.actions.rotate_hint')) ->authorize('update') ->action(function (Webhook $record): void { $secret = Str::random(40); $record->forceFill(['secret' => $secret])->save(); self::showSecret($secret); }); } public static function showSecret(string $secret): void { Notification::make() ->title(__('panel.webhooks.secret.title')) ->body(__('panel.webhooks.secret.body', ['secret' => $secret])) ->warning() ->persistent() ->send(); } public static function getRelations(): array { return [ DeliveriesRelationManager::class, ]; } public static function getPages(): array { return [ 'index' => ListWebhooks::route('/'), 'create' => CreateWebhook::route('/create'), 'edit' => EditWebhook::route('/{record}/edit'), ]; } }