Implement webhook inspector
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use App\Actions\ConfigureWebhookEndpointAction;
|
||||
use App\Actions\DeleteWebhookRequestAction;
|
||||
use App\Models\User;
|
||||
use App\Models\WebhookEndpoint;
|
||||
use App\Models\WebhookRequest;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Illuminate\View\View;
|
||||
use Livewire\Attributes\Locked;
|
||||
use Livewire\Attributes\Url;
|
||||
use Livewire\Component;
|
||||
use Livewire\WithPagination;
|
||||
|
||||
class Inspector extends Component
|
||||
{
|
||||
use WithPagination;
|
||||
|
||||
#[Locked]
|
||||
public string $endpointId = '';
|
||||
|
||||
#[Url]
|
||||
public string $search = '';
|
||||
|
||||
#[Url]
|
||||
public string $methodFilter = 'ALL';
|
||||
|
||||
#[Url]
|
||||
public ?string $selectedRequestId = null;
|
||||
|
||||
public int $responseStatus = 200;
|
||||
|
||||
public string $responseHeadersJson = '{}';
|
||||
|
||||
public string $responseBody = '{}';
|
||||
|
||||
public bool $responseSaved = false;
|
||||
|
||||
public function mount(WebhookEndpoint $endpoint): void
|
||||
{
|
||||
Gate::authorize('view', $endpoint);
|
||||
|
||||
$this->endpointId = (string) $endpoint->getKey();
|
||||
$this->responseStatus = $endpoint->response_status;
|
||||
$this->responseHeadersJson = json_encode(
|
||||
$endpoint->response_headers ?? [],
|
||||
JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES,
|
||||
) ?: '{}';
|
||||
$this->responseBody = $endpoint->response_body ?? '{}';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function getListeners(): array
|
||||
{
|
||||
$endpoint = $this->endpoint();
|
||||
$prefix = $endpoint->is_public ? 'echo' : 'echo-private';
|
||||
|
||||
return [
|
||||
$prefix.':webhooks.'.$this->endpointId.',.webhook.request.received' => 'requestReceived',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $event
|
||||
*/
|
||||
public function requestReceived(array $event = []): void
|
||||
{
|
||||
if (($event['endpointId'] ?? null) === $this->endpointId) {
|
||||
$this->resetPage();
|
||||
}
|
||||
}
|
||||
|
||||
public function updatedSearch(): void
|
||||
{
|
||||
$this->resetPage();
|
||||
}
|
||||
|
||||
public function updatedMethodFilter(): void
|
||||
{
|
||||
$this->resetPage();
|
||||
}
|
||||
|
||||
public function selectRequest(string $requestId): void
|
||||
{
|
||||
$webhookRequest = $this->endpoint()->requests()->findOrFail($requestId);
|
||||
Gate::authorize('view', $webhookRequest);
|
||||
$this->selectedRequestId = $webhookRequest->getKey();
|
||||
}
|
||||
|
||||
public function clearSelectedRequest(): void
|
||||
{
|
||||
$this->selectedRequestId = null;
|
||||
}
|
||||
|
||||
public function deleteRequest(string $requestId, DeleteWebhookRequestAction $deleteWebhookRequest): void
|
||||
{
|
||||
$webhookRequest = $this->endpoint()->requests()->findOrFail($requestId);
|
||||
$deleteWebhookRequest->handle(Auth::user(), $webhookRequest);
|
||||
|
||||
if ($this->selectedRequestId === $requestId) {
|
||||
$this->selectedRequestId = null;
|
||||
}
|
||||
}
|
||||
|
||||
public function saveResponse(ConfigureWebhookEndpointAction $configureWebhookEndpoint): void
|
||||
{
|
||||
$this->validate([
|
||||
'responseStatus' => ['required', 'integer', 'between:100,599'],
|
||||
'responseBody' => ['string'],
|
||||
'responseHeadersJson' => ['required', 'json'],
|
||||
]);
|
||||
|
||||
$headers = json_decode($this->responseHeadersJson, true);
|
||||
|
||||
if (! is_array($headers)) {
|
||||
throw ValidationException::withMessages([
|
||||
'responseHeadersJson' => 'Response headers must be a JSON object.',
|
||||
]);
|
||||
}
|
||||
|
||||
/** @var User|null $user */
|
||||
$user = Auth::user();
|
||||
$configureWebhookEndpoint->handle(
|
||||
$user,
|
||||
$this->endpoint(),
|
||||
$this->responseStatus,
|
||||
$headers,
|
||||
$this->responseBody,
|
||||
);
|
||||
|
||||
$this->responseSaved = true;
|
||||
}
|
||||
|
||||
public function deleteEndpoint(): void
|
||||
{
|
||||
$endpoint = $this->endpoint();
|
||||
Gate::authorize('delete', $endpoint);
|
||||
$endpoint->delete();
|
||||
$this->redirectRoute('home');
|
||||
}
|
||||
|
||||
public function render(): View
|
||||
{
|
||||
$endpoint = $this->endpoint();
|
||||
$requestQuery = $endpoint->requests()
|
||||
->select(['id', 'method', 'request_uri', 'content_type', 'body_size', 'ip_address', 'received_at'])
|
||||
->when($this->methodFilter !== 'ALL', fn ($query) => $query->where('method', $this->methodFilter))
|
||||
->when($this->search !== '', function ($query): void {
|
||||
$search = '%'.$this->search.'%';
|
||||
|
||||
$query->where(function ($query) use ($search): void {
|
||||
$query->where('request_uri', 'like', $search)
|
||||
->orWhere('ip_address', 'like', $search)
|
||||
->orWhere('user_agent', 'like', $search);
|
||||
});
|
||||
})
|
||||
->latest('received_at');
|
||||
|
||||
$selectedRequest = null;
|
||||
|
||||
if ($this->selectedRequestId !== null) {
|
||||
$selectedRequest = $endpoint->requests()->find($this->selectedRequestId);
|
||||
|
||||
if ($selectedRequest instanceof WebhookRequest) {
|
||||
Gate::authorize('view', $selectedRequest);
|
||||
}
|
||||
}
|
||||
|
||||
return view('livewire.inspector', [
|
||||
'endpoint' => $endpoint,
|
||||
'webhookRequests' => $requestQuery->paginate((int) config('webhooks.page_size', 25)),
|
||||
'selectedRequest' => $selectedRequest,
|
||||
]);
|
||||
}
|
||||
|
||||
private function endpoint(): WebhookEndpoint
|
||||
{
|
||||
$endpoint = WebhookEndpoint::query()->findOrFail($this->endpointId);
|
||||
Gate::authorize('view', $endpoint);
|
||||
|
||||
return $endpoint;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user