All checks were successful
check / check (push) Successful in 1m49s
- Webhook reception handler: look up entrypoint by UUID, verify active,
capture full HTTP request (method, headers, body, content-type), create
Event record, queue Delivery records for each active Target, return 200 OK.
Handles edge cases: unknown UUID → 404, inactive → 410, oversized → 413.
- Delivery engine (internal/delivery): fx-managed background goroutine that
polls for pending/retrying deliveries and dispatches to target type handlers.
Graceful shutdown via context cancellation.
- Target type implementations:
- HTTP: fire-and-forget POST with original headers forwarding
- Retry: exponential backoff (1s, 2s, 4s...) up to max_retries
- Database: immediate success (event already stored)
- Log: slog output with event details
- Webhook management pages with Tailwind CSS + Alpine.js:
- List (/sources): webhooks with entrypoint/target/event counts
- Create (/sources/new): form with auto-created default entrypoint
- Detail (/source/{id}): config, entrypoints, targets, recent events
- Edit (/source/{id}/edit): name, description, retention_days
- Delete (/source/{id}/delete): soft-delete with child records
- Add Entrypoint (/source/{id}/entrypoints): inline form
- Add Target (/source/{id}/targets): type-aware form
- Event Log (/source/{id}/logs): paginated with delivery status
- Updated README: marked completed items, updated naming conventions
table, added delivery engine to package layout and DI docs, updated
column names to reflect entity rename.
- Rebuilt Tailwind CSS for new template classes.
Part of: #15
50 lines
2.2 KiB
HTML
50 lines
2.2 KiB
HTML
{{template "base" .}}
|
|
|
|
{{define "title"}}Sources - Webhooker{{end}}
|
|
|
|
{{define "content"}}
|
|
<div class="max-w-6xl mx-auto px-6 py-8">
|
|
<div class="flex justify-between items-center mb-6">
|
|
<h1 class="text-2xl font-medium text-gray-900">Webhooks</h1>
|
|
<a href="/sources/new" class="btn-primary">
|
|
<svg class="w-5 h-5 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/>
|
|
</svg>
|
|
New Webhook
|
|
</a>
|
|
</div>
|
|
|
|
{{if .Webhooks}}
|
|
<div class="grid gap-4">
|
|
{{range .Webhooks}}
|
|
<a href="/source/{{.ID}}" class="card-elevated p-6 block">
|
|
<div class="flex justify-between items-start">
|
|
<div>
|
|
<h2 class="text-lg font-medium text-gray-900">{{.Name}}</h2>
|
|
{{if .Description}}
|
|
<p class="text-sm text-gray-500 mt-1">{{.Description}}</p>
|
|
{{end}}
|
|
</div>
|
|
<span class="badge-info">{{.RetentionDays}}d retention</span>
|
|
</div>
|
|
<div class="flex gap-6 mt-4 text-sm text-gray-500">
|
|
<span>{{.EntrypointCount}} entrypoint{{if ne .EntrypointCount 1}}s{{end}}</span>
|
|
<span>{{.TargetCount}} target{{if ne .TargetCount 1}}s{{end}}</span>
|
|
<span>{{.EventCount}} event{{if ne .EventCount 1}}s{{end}}</span>
|
|
</div>
|
|
</a>
|
|
{{end}}
|
|
</div>
|
|
{{else}}
|
|
<div class="card p-12 text-center">
|
|
<svg class="w-16 h-16 text-gray-300 mx-auto mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"/>
|
|
</svg>
|
|
<h2 class="text-lg font-medium text-gray-900 mb-2">No webhooks yet</h2>
|
|
<p class="text-gray-500 mb-6">Create your first webhook to start receiving and forwarding events.</p>
|
|
<a href="/sources/new" class="btn-primary">Create Webhook</a>
|
|
</div>
|
|
{{end}}
|
|
</div>
|
|
{{end}}
|