Files
webhooker/templates/target_edit.html
sneak 433fdceee2
check / check (push) Failing after 1s
Say max_retries is the total attempt count, not a retry count (closes #316)
The delivery core makes max_retries attempts in total: a fresh delivery
starts at attempt one and gives up once the attempt number reaches
max_retries, so 3 is three attempts, not four, and 0 is special-cased to
a single fire-and-forget attempt with no retries and no circuit breaker.
The create and edit target forms called it "Max retries" with no total,
and the README data-model row called it "maximum retry attempts", so an
operator wanting "try, then retry twice" would enter the wrong number.

Both forms and the README rows now state the number is the total number
of delivery attempts, with the 0 case spelled out. The delivery
arithmetic is unchanged. A UI copy test renders both forms and pins the
shared wording so it cannot drift back to a retry count.

Model: opus-4-8
2026-09-21 07:55:07 +00:00

84 lines
4.8 KiB
HTML

{{template "base" .}}
{{define "title"}}Edit {{.Target.Name}} - Webhooker{{end}}
{{define "content"}}
<div class="max-w-2xl mx-auto px-6 py-8">
<div class="mb-6">
<a href="/source/{{.Webhook.ID}}" class="text-sm text-primary-600 hover:text-primary-700">&larr; Back to {{.Webhook.Name}}</a>
<h1 class="text-2xl font-medium text-gray-900 mt-2">Edit Target</h1>
<p class="text-sm text-gray-500 mt-1">Type: {{.Target.Type}}. A target's type cannot be changed; create a new target to deliver a different way.</p>
</div>
<div class="card p-6">
{{if .Error}}
<div class="alert-error">{{.Error}}</div>
{{end}}
{{if or (eq .Target.Type "http") (eq .Target.Type "slack")}}
<div class="mb-6 rounded-md bg-gray-50 p-4 text-sm text-gray-700">
This form shows the target's stored destination in full, including any credential carried in its URL or headers. It is the only page that does; everywhere else the value is masked.
</div>
{{end}}
<form method="POST" action="/source/{{.Webhook.ID}}/targets/{{.Target.ID}}/edit" class="space-y-6">
<input type="hidden" name="csrf_token" value="{{.CSRFToken}}">
<div class="form-group">
<label for="name" class="label">Name</label>
<input type="text" id="name" name="name" value="{{.Target.Name}}" required class="input">
</div>
{{if eq .Target.Type "http"}}
<div class="form-group">
<label for="url" class="label">Destination URL</label>
<input type="url" id="url" name="url" value="{{.Target.Config.URL}}" required class="input">
<p class="text-xs text-gray-500 mt-1">Revalidated on save; destinations that resolve to private or link-local addresses are rejected.</p>
</div>
<div class="form-group">
<label for="headers" class="label">Headers</label>
<textarea id="headers" name="headers" rows="4" class="input" placeholder="Authorization: Bearer ...">{{.Target.Config.Headers}}</textarea>
<p class="text-xs text-gray-500 mt-1">One <code>Name: value</code> per line, sent with every delivery. Leave blank for none. <code>Host</code>, <code>Content-Length</code>, <code>Transfer-Encoding</code>, <code>Connection</code>, <code>Trailer</code> and <code>User-Agent</code> are set by the delivery engine and are rejected here rather than silently ignored. Headers set here are dropped if a redirect leaves the destination's own origin, so a credential cannot follow one to another host.</p>
</div>
<div class="form-group">
<label for="timeout" class="label">Timeout (seconds)</label>
<input type="number" id="timeout" name="timeout" value="{{.Target.Config.Timeout}}" min="0" max="{{.MaxTimeout}}" class="input">
<p class="text-xs text-gray-500 mt-1">Per-request timeout, at most {{.MaxTimeout}} seconds. Leave blank to use the default.</p>
</div>
{{end}}
{{if eq .Target.Type "slack"}}
<div class="form-group">
<label for="url" class="label">Webhook URL</label>
<input type="url" id="url" name="url" value="{{.Target.Config.URL}}" required class="input">
<p class="text-xs text-gray-500 mt-1">Slack or Mattermost incoming webhook URL. Revalidated on save.</p>
</div>
{{end}}
{{if eq .Target.Type "database"}}
<div class="form-group">
<label for="expiry" class="label">Archive Expiry</label>
<input type="text" id="expiry" name="expiry" value="{{.Target.Config.Expiry}}" placeholder="never" class="input">
<p class="text-xs text-gray-500 mt-1">"never" (the default when blank) keeps archived rows forever, or a Go duration like "720h" prunes older rows.</p>
</div>
{{end}}
{{if or (eq .Target.Type "http") (eq .Target.Type "slack")}}
<div class="form-group">
<label for="max_retries" class="label">Max retries</label>
<input type="number" id="max_retries" name="max_retries" value="{{.Target.MaxRetries}}" min="0" max="20" class="input">
<p class="text-xs text-gray-500 mt-1">This is the total number of delivery attempts, not retries on top of the first: a value of 3 makes three attempts in all. 0 means a single attempt with no retries and no circuit breaker.</p>
</div>
{{end}}
<div class="flex gap-3">
<button type="submit" class="btn-primary">Save Changes</button>
<a href="/source/{{.Webhook.ID}}" class="btn-secondary">Cancel</a>
</div>
</form>
</div>
</div>
{{end}}