php-tenant/View/Blade/admin/entitlement-webhook-manager.blade.php
Snider 86dbf4e763 fix: namespace to Core\Mod\Tenant, restructure package
- Changed namespace from Core\Core\Tenant to Core\Mod\Tenant
- Moved src/ contents to root
- Removed Host UK extension files (admin.php, MemberManager, TeamManager)
- Fixed composer.json autoload paths
2026-01-27 00:58:42 +00:00

401 lines
18 KiB
PHP

<div>
{{-- Stats --}}
<div class="mb-6 grid grid-cols-1 gap-4 sm:grid-cols-3">
<x-flux::card>
<div class="flex items-center gap-3">
<div class="rounded-lg bg-blue-100 p-3 dark:bg-blue-900/30">
<x-flux::icon name="webhook" class="h-5 w-5 text-blue-600 dark:text-blue-400" />
</div>
<div>
<div class="text-2xl font-bold">{{ number_format($this->stats['total']) }}</div>
<div class="text-sm text-zinc-500">Total Webhooks</div>
</div>
</div>
</x-flux::card>
<x-flux::card>
<div class="flex items-center gap-3">
<div class="rounded-lg bg-green-100 p-3 dark:bg-green-900/30">
<x-flux::icon name="check-circle" class="h-5 w-5 text-green-600 dark:text-green-400" />
</div>
<div>
<div class="text-2xl font-bold">{{ number_format($this->stats['active']) }}</div>
<div class="text-sm text-zinc-500">Active</div>
</div>
</div>
</x-flux::card>
<x-flux::card>
<div class="flex items-center gap-3">
<div class="rounded-lg bg-red-100 p-3 dark:bg-red-900/30">
<x-flux::icon name="exclamation-triangle" class="h-5 w-5 text-red-600 dark:text-red-400" />
</div>
<div>
<div class="text-2xl font-bold">{{ number_format($this->stats['circuit_broken']) }}</div>
<div class="text-sm text-zinc-500">Circuit Broken</div>
</div>
</div>
</x-flux::card>
</div>
{{-- Message --}}
@if($message)
<div class="mb-4">
<x-flux::alert :variant="$messageType === 'error' ? 'danger' : 'success'" dismissible wire:click="clearMessage">
{{ $message }}
</x-flux::alert>
</div>
@endif
{{-- Filters --}}
<x-flux::card class="mb-6">
<div class="flex flex-wrap items-center gap-4">
<div class="flex-1 min-w-[200px]">
<x-flux::input
wire:model.live.debounce.300ms="search"
placeholder="Search by name or URL..."
icon="magnifying-glass"
/>
</div>
<div class="w-48">
<x-flux::select wire:model.live="workspaceId">
<option value="">All Workspaces</option>
@foreach($this->workspaces as $workspace)
<option value="{{ $workspace->id }}">{{ $workspace->name }}</option>
@endforeach
</x-flux::select>
</div>
<div class="w-40">
<x-flux::select wire:model.live="statusFilter">
<option value="">All Statuses</option>
<option value="active">Active</option>
<option value="inactive">Inactive</option>
<option value="circuit_broken">Circuit Broken</option>
</x-flux::select>
</div>
<x-flux::button wire:click="create" variant="primary">
<x-flux::icon name="plus" class="mr-2 h-4 w-4" />
New Webhook
</x-flux::button>
</div>
</x-flux::card>
{{-- Webhooks Table --}}
<x-flux::card>
<x-flux::table>
<x-flux::table.head>
<x-flux::table.row>
<x-flux::table.header>Webhook</x-flux::table.header>
<x-flux::table.header>Workspace</x-flux::table.header>
<x-flux::table.header>Events</x-flux::table.header>
<x-flux::table.header>Status</x-flux::table.header>
<x-flux::table.header>Deliveries</x-flux::table.header>
<x-flux::table.header class="text-right">Actions</x-flux::table.header>
</x-flux::table.row>
</x-flux::table.head>
<x-flux::table.body>
@forelse($this->webhooks as $webhook)
<x-flux::table.row>
<x-flux::table.cell>
<div>
<div class="font-medium">{{ $webhook->name }}</div>
<div class="text-xs text-zinc-500 truncate max-w-xs" title="{{ $webhook->url }}">
{{ $webhook->url }}
</div>
</div>
</x-flux::table.cell>
<x-flux::table.cell>
<span class="text-sm">{{ $webhook->workspace?->name ?? 'N/A' }}</span>
</x-flux::table.cell>
<x-flux::table.cell>
<div class="flex flex-wrap gap-1">
@foreach($webhook->events as $event)
<x-flux::badge size="sm" color="purple">{{ $event }}</x-flux::badge>
@endforeach
</div>
</x-flux::table.cell>
<x-flux::table.cell>
@if($webhook->isCircuitBroken())
<x-flux::badge color="red">Circuit Broken</x-flux::badge>
@elseif($webhook->is_active)
<x-flux::badge color="green">Active</x-flux::badge>
@else
<x-flux::badge color="zinc">Inactive</x-flux::badge>
@endif
@if($webhook->last_delivery_status)
<div class="mt-1">
<x-flux::badge size="sm" :color="$webhook->last_delivery_status->value === 'success' ? 'green' : ($webhook->last_delivery_status->value === 'failed' ? 'red' : 'amber')">
Last: {{ ucfirst($webhook->last_delivery_status->value) }}
</x-flux::badge>
</div>
@endif
</x-flux::table.cell>
<x-flux::table.cell>
<button
wire:click="viewDeliveries({{ $webhook->id }})"
class="text-blue-600 hover:underline"
>
{{ number_format($webhook->deliveries_count) }} deliveries
</button>
</x-flux::table.cell>
<x-flux::table.cell class="text-right">
<x-flux::dropdown>
<x-slot:trigger>
<x-flux::button variant="ghost" size="sm">
<x-flux::icon name="ellipsis-vertical" class="h-4 w-4" />
</x-flux::button>
</x-slot:trigger>
<x-flux::dropdown.item wire:click="edit({{ $webhook->id }})">
<x-flux::icon name="pencil" class="mr-2 h-4 w-4" />
Edit
</x-flux::dropdown.item>
<x-flux::dropdown.item wire:click="testWebhook({{ $webhook->id }})">
<x-flux::icon name="paper-airplane" class="mr-2 h-4 w-4" />
Send Test
</x-flux::dropdown.item>
<x-flux::dropdown.item wire:click="viewDeliveries({{ $webhook->id }})">
<x-flux::icon name="queue-list" class="mr-2 h-4 w-4" />
View Deliveries
</x-flux::dropdown.item>
<x-flux::dropdown.item wire:click="regenerateSecret({{ $webhook->id }})">
<x-flux::icon name="key" class="mr-2 h-4 w-4" />
Regenerate Secret
</x-flux::dropdown.item>
@if($webhook->isCircuitBroken())
<x-flux::dropdown.item wire:click="resetCircuitBreaker({{ $webhook->id }})">
<x-flux::icon name="arrow-path" class="mr-2 h-4 w-4" />
Reset Circuit Breaker
</x-flux::dropdown.item>
@endif
<x-flux::dropdown.divider />
<x-flux::dropdown.item wire:click="toggleActive({{ $webhook->id }})">
@if($webhook->is_active)
<x-flux::icon name="pause" class="mr-2 h-4 w-4" />
Disable
@else
<x-flux::icon name="play" class="mr-2 h-4 w-4" />
Enable
@endif
</x-flux::dropdown.item>
<x-flux::dropdown.item
wire:click="delete({{ $webhook->id }})"
wire:confirm="Are you sure you want to delete this webhook?"
variant="danger"
>
<x-flux::icon name="trash" class="mr-2 h-4 w-4" />
Delete
</x-flux::dropdown.item>
</x-flux::dropdown>
</x-flux::table.cell>
</x-flux::table.row>
@empty
<x-flux::table.row>
<x-flux::table.cell colspan="6" class="text-center py-8 text-zinc-500">
No webhooks found. Create one to get started.
</x-flux::table.cell>
</x-flux::table.row>
@endforelse
</x-flux::table.body>
</x-flux::table>
<div class="mt-4">
{{ $this->webhooks->links() }}
</div>
</x-flux::card>
{{-- Create/Edit Modal --}}
<x-flux::modal wire:model="showFormModal" max-width="lg">
<x-flux::modal.header>
{{ $editingId ? 'Edit Webhook' : 'Create Webhook' }}
</x-flux::modal.header>
<x-flux::modal.body>
<div class="space-y-4">
@if(!$editingId)
<x-flux::field>
<x-flux::label>Workspace</x-flux::label>
<x-flux::select wire:model="workspaceId">
<option value="">Select a workspace...</option>
@foreach($this->workspaces as $workspace)
<option value="{{ $workspace->id }}">{{ $workspace->name }}</option>
@endforeach
</x-flux::select>
<x-flux::error name="workspaceId" />
</x-flux::field>
@endif
<x-flux::field>
<x-flux::label>Name</x-flux::label>
<x-flux::input wire:model="name" placeholder="My Webhook" />
<x-flux::error name="name" />
</x-flux::field>
<x-flux::field>
<x-flux::label>URL</x-flux::label>
<x-flux::input wire:model="url" type="url" placeholder="https://example.com/webhook" />
<x-flux::error name="url" />
<x-flux::description>The endpoint that will receive webhook POST requests.</x-flux::description>
</x-flux::field>
<x-flux::field>
<x-flux::label>Events</x-flux::label>
<div class="space-y-2 rounded-lg border border-zinc-200 p-3 dark:border-zinc-700">
@foreach($this->availableEvents as $eventKey => $eventInfo)
<label class="flex items-start gap-3 cursor-pointer">
<x-flux::checkbox
wire:model="events"
value="{{ $eventKey }}"
/>
<div>
<div class="font-medium">{{ $eventInfo['name'] }}</div>
<div class="text-xs text-zinc-500">{{ $eventInfo['description'] }}</div>
</div>
</label>
@endforeach
</div>
<x-flux::error name="events" />
</x-flux::field>
<x-flux::field>
<x-flux::label>Max Retry Attempts</x-flux::label>
<x-flux::input wire:model="maxAttempts" type="number" min="1" max="10" />
<x-flux::description>Number of times to retry failed deliveries (1-10).</x-flux::description>
</x-flux::field>
<x-flux::field>
<label class="flex items-center gap-2">
<x-flux::checkbox wire:model="isActive" />
<span>Active</span>
</label>
<x-flux::description>Inactive webhooks will not receive any events.</x-flux::description>
</x-flux::field>
</div>
</x-flux::modal.body>
<x-flux::modal.footer>
<x-flux::button wire:click="closeFormModal" variant="ghost">Cancel</x-flux::button>
<x-flux::button wire:click="save" variant="primary">
{{ $editingId ? 'Update' : 'Create' }}
</x-flux::button>
</x-flux::modal.footer>
</x-flux::modal>
{{-- Deliveries Modal --}}
<x-flux::modal wire:model="showDeliveriesModal" max-width="3xl">
<x-flux::modal.header>
Delivery History
</x-flux::modal.header>
<x-flux::modal.body>
<x-flux::table>
<x-flux::table.head>
<x-flux::table.row>
<x-flux::table.header>Event</x-flux::table.header>
<x-flux::table.header>Status</x-flux::table.header>
<x-flux::table.header>HTTP</x-flux::table.header>
<x-flux::table.header>Attempts</x-flux::table.header>
<x-flux::table.header>Time</x-flux::table.header>
<x-flux::table.header></x-flux::table.header>
</x-flux::table.row>
</x-flux::table.head>
<x-flux::table.body>
@forelse($this->recentDeliveries as $delivery)
<x-flux::table.row>
<x-flux::table.cell>
<x-flux::badge color="purple">{{ $delivery->getEventDisplayName() }}</x-flux::badge>
</x-flux::table.cell>
<x-flux::table.cell>
<x-flux::badge :color="$delivery->getStatusColour()">
{{ ucfirst($delivery->status->value) }}
</x-flux::badge>
</x-flux::table.cell>
<x-flux::table.cell>
{{ $delivery->http_status ?? '-' }}
</x-flux::table.cell>
<x-flux::table.cell>
{{ $delivery->attempts }}
</x-flux::table.cell>
<x-flux::table.cell>
<span title="{{ $delivery->created_at }}">
{{ $delivery->created_at->diffForHumans() }}
</span>
</x-flux::table.cell>
<x-flux::table.cell>
@if($delivery->isFailed())
<x-flux::button
wire:click="retryDelivery({{ $delivery->id }})"
size="sm"
variant="ghost"
>
Retry
</x-flux::button>
@endif
</x-flux::table.cell>
</x-flux::table.row>
@empty
<x-flux::table.row>
<x-flux::table.cell colspan="6" class="text-center py-8 text-zinc-500">
No deliveries yet.
</x-flux::table.cell>
</x-flux::table.row>
@endforelse
</x-flux::table.body>
</x-flux::table>
</x-flux::modal.body>
<x-flux::modal.footer>
<x-flux::button wire:click="closeDeliveriesModal" variant="ghost">Close</x-flux::button>
</x-flux::modal.footer>
</x-flux::modal>
{{-- Secret Modal --}}
<x-flux::modal wire:model="showSecretModal" max-width="md">
<x-flux::modal.header>
Webhook Secret
</x-flux::modal.header>
<x-flux::modal.body>
<x-flux::alert variant="warning" class="mb-4">
Save this secret now. It will not be shown again.
</x-flux::alert>
<div class="rounded-lg bg-zinc-100 p-4 font-mono text-sm dark:bg-zinc-800 break-all">
{{ $displaySecret }}
</div>
<p class="mt-4 text-sm text-zinc-500">
Use this secret to verify webhook signatures. The signature is sent in the
<code class="rounded bg-zinc-100 px-1 dark:bg-zinc-800">X-Signature</code> header
and is a HMAC-SHA256 hash of the JSON payload.
</p>
</x-flux::modal.body>
<x-flux::modal.footer>
<x-flux::button wire:click="closeSecretModal" variant="primary">
I've saved the secret
</x-flux::button>
</x-flux::modal.footer>
</x-flux::modal>
</div>