All checks were successful
check / check (push) Successful in 3m4s
RetentionDays carried gorm:"default:30", so GORM substituted 30 for a zero value while building the insert. A webhook could therefore never be configured to keep its events indefinitely: the reaper's retain-forever branch existed but was unreachable from the normal create and edit flows. Introduce database.RetentionForeverDays = 365 * 1000 as the sentinel for "retain forever" and a Webhook.BeforeSave hook that rewrites any non-positive RetentionDays to it. The rewrite has to live in the hook rather than at the call sites: GORM applies the column default while converting the model to insert values, which happens after BeforeSave, so anything later loses that race. Putting it on the model also means a future call site, such as the planned REST API, cannot bypass it. The reaper now skips a webhook when Webhook.RetainsForever reports true, which recognises the sentinel and keeps honouring the old <= 0 values for rows written before it existed. Without this the sentinel, being positive, would have produced a cutoff a thousand years in the past and a DELETE matching nothing on every sweep. Form handling is shared by create and edit through parseRetentionDays so the two cannot drift: an empty field keeps the previous behaviour (default on create, unchanged on edit), 0 is honoured, and an unparseable or negative value is a 400 that re-renders the form rather than a silently substituted default. The retention inputs drop max="365". That cap was not cosmetic: the edit form pre-fills the stored value, so a retain-forever webhook rendered 365000 into an input capped at 365 and browser validation would have blocked saving any edit to it. min becomes 0 with a hint explaining what 0 does, and the list and detail views render a RetentionLabel of "forever" instead of a raw day count. The 30-day default is consolidated into database.DefaultRetentionDays, referenced from the handler and from the create form's pre-filled value, with a test asserting it agrees with the struct tag that cannot reference it.
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">Retention: {{.RetentionLabel}}</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}}
|