Make every clickable control look clickable, in two shared styles (closes #375)
check / check (push) Waiting to run
check / check (push) Waiting to run
Buttons keep btn-primary, btn-secondary and btn-danger and gain a pointer cursor; the site name and the navigation links become btn-secondary buttons. Every control that was plain coloured text (Copy, both Add, the row actions, Resubmit, Replay, the back, footer and download links) now uses btn-small, bordered at rest with hover and focus states. Each card on the webhook list shows an Open label in btn-small and takes its focus outline. Both styles are in static/css/style.css, which the layout now loads. The event log's clickable rows become buttons, so they work by keyboard; Replay moves beside its delivery's row. Rows on the webhook page and in the event log wrap at phone width. The browser test now checks that Copy reads "Copied". Model: opus-5-5
This commit is contained in:
@@ -1326,15 +1326,16 @@ A browser test in `internal/server` loads the webhook page and the event log
|
|||||||
under the real policy and checks that: both add forms stay hidden until Add is
|
under the real policy and checks that: both add forms stay hidden until Add is
|
||||||
clicked; choosing Slack in the add target form leaves the HTTP fields out of
|
clicked; choosing Slack in the add target form leaves the HTTP fields out of
|
||||||
what it submits, also after leaving the page and going back to it, when the
|
what it submits, also after leaving the page and going back to it, when the
|
||||||
browser restores the choice; an event expands and collapses, and so do a
|
browser restores the choice; the Copy button beside an entrypoint URL reads
|
||||||
delivery's attempts inside it; and at phone width the menu button opens and
|
"Copied" once clicked; an event expands and collapses, and so do a delivery's
|
||||||
closes the mobile menu. It also fails if the browser reports a console warning
|
attempts inside it; and at phone width the menu button opens and closes the
|
||||||
or error, an uncaught exception, or anything the policy refused. `make check`
|
mobile menu. It also fails if the browser reports a console warning or error,
|
||||||
and the image build lint it but do not run it, and `make test` leaves it out
|
an uncaught exception, or anything the policy refused. `make check` and the
|
||||||
(its file is built only with the `browser` build tag). Run it with
|
image build lint it but do not run it, and `make test` leaves it out (its file
|
||||||
`make test-browser` after changing `templates/` or `static/js/`: that builds
|
is built only with the `browser` build tag). Run it with `make test-browser`
|
||||||
`Dockerfile.browser`, which runs the test in a digest-pinned headless browser
|
after changing `templates/` or `static/js/`: that builds `Dockerfile.browser`,
|
||||||
image, so the host needs no browser.
|
which runs the test in a digest-pinned headless browser image, so the host
|
||||||
|
needs no browser.
|
||||||
|
|
||||||
The package's tarball is committed as `3p/alpinejs-csp-3.14.9.tgz`, byte for
|
The package's tarball is committed as `3p/alpinejs-csp-3.14.9.tgz`, byte for
|
||||||
byte as the npm registry publishes it. It is a dependency, not this repo's build
|
byte as the npm registry publishes it. It is a dependency, not this repo's build
|
||||||
@@ -3026,7 +3027,7 @@ webhooker/
|
|||||||
│ ├── static.go # //go:embed directive
|
│ ├── static.go # //go:embed directive
|
||||||
│ ├── css/input.css # Tailwind input, source for tailwind.css (make css)
|
│ ├── css/input.css # Tailwind input, source for tailwind.css (make css)
|
||||||
│ ├── css/tailwind.css # Generated stylesheet the pages load
|
│ ├── css/tailwind.css # Generated stylesheet the pages load
|
||||||
│ ├── css/style.css # Older hand-written stylesheet, no longer loaded
|
│ ├── css/style.css # Hand-written, loaded after tailwind.css: btn-small, the pointer cursor for input.css's buttons, the webhook list cards' focus outline
|
||||||
│ ├── js/app.js # Copy-to-clipboard, and the Alpine.js components
|
│ ├── js/app.js # Copy-to-clipboard, and the Alpine.js components
|
||||||
│ └── js/alpine.min.js # Alpine.js CSP build, extracted from 3p/ by make assets, not committed
|
│ └── js/alpine.min.js # Alpine.js CSP build, extracted from 3p/ by make assets, not committed
|
||||||
├── templates/ # Go HTML templates (base, login, sources, etc.)
|
├── templates/ # Go HTML templates (base, login, sources, etc.)
|
||||||
|
|||||||
@@ -82,9 +82,9 @@ func TestNavbarUsesWebhookTerminology(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
assert.Contains(t, body, "Retention: 14 days")
|
assert.Contains(t, body, "Retention: 14 days")
|
||||||
assert.Contains(t, body, `class="btn-text">Webhooks</a>`)
|
assert.Contains(t, body, `class="btn-secondary">Webhooks</a>`)
|
||||||
assert.Contains(
|
assert.Contains(
|
||||||
t, body, `class="btn-text w-full text-left">Webhooks</a>`,
|
t, body, `class="btn-secondary w-full">Webhooks</a>`,
|
||||||
)
|
)
|
||||||
assert.Contains(
|
assert.Contains(
|
||||||
t, body,
|
t, body,
|
||||||
@@ -163,7 +163,7 @@ func TestEventLogPageIsCalledFullEventLog(t *testing.T) {
|
|||||||
)
|
)
|
||||||
assert.Contains(
|
assert.Contains(
|
||||||
t, detailBody,
|
t, detailBody,
|
||||||
`<a href="/hook/wh-1/events" class="btn-text text-sm">Full Event Log</a>`,
|
`<a href="/hook/wh-1/events" class="btn-small">Full Event Log</a>`,
|
||||||
"the link under recent events",
|
"the link under recent events",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -331,8 +331,9 @@ func TestEntrypointCopyButtonIsProgressiveEnhancement(t *testing.T) {
|
|||||||
assert.Contains(t, body, "https://hooks.example.com/h/abc123")
|
assert.Contains(t, body, "https://hooks.example.com/h/abc123")
|
||||||
assert.Contains(
|
assert.Contains(
|
||||||
t, body,
|
t, body,
|
||||||
`hidden data-copy-target="entrypoint-url-ep-1"`,
|
`<button type="button" hidden data-copy-target="entrypoint-url-ep-1"`,
|
||||||
"the button must start hidden and be revealed by script",
|
"the copy control must be a button, start hidden and be "+
|
||||||
|
"revealed by script",
|
||||||
)
|
)
|
||||||
|
|
||||||
// renderTemplate streams to the ResponseWriter, so an abort
|
// renderTemplate streams to the ResponseWriter, so an abort
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/chromedp/cdproto/browser"
|
||||||
"github.com/chromedp/cdproto/log"
|
"github.com/chromedp/cdproto/log"
|
||||||
"github.com/chromedp/cdproto/network"
|
"github.com/chromedp/cdproto/network"
|
||||||
"github.com/chromedp/cdproto/runtime"
|
"github.com/chromedp/cdproto/runtime"
|
||||||
@@ -43,7 +44,7 @@ const (
|
|||||||
// TestAlpineRunsUnderTheSecurityPolicy loads the webhook page and the
|
// TestAlpineRunsUnderTheSecurityPolicy loads the webhook page and the
|
||||||
// event log in a headless browser, served by the real router and so
|
// event log in a headless browser, served by the real router and so
|
||||||
// under the real Content-Security-Policy, and checks that the pages'
|
// under the real Content-Security-Policy, and checks that the pages'
|
||||||
// Alpine.js directives work.
|
// Alpine.js directives and the copy control work.
|
||||||
func TestAlpineRunsUnderTheSecurityPolicy(t *testing.T) {
|
func TestAlpineRunsUnderTheSecurityPolicy(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
@@ -55,6 +56,13 @@ func TestAlpineRunsUnderTheSecurityPolicy(t *testing.T) {
|
|||||||
|
|
||||||
userID, _ := env.seedUser(t, "browser", "browser-password")
|
userID, _ := env.seedUser(t, "browser", "browser-password")
|
||||||
webhook := env.seedWebhook(t, userID)
|
webhook := env.seedWebhook(t, userID)
|
||||||
|
require.NoError(t, env.db.DB().Omit(clause.Associations).Create(
|
||||||
|
&database.Entrypoint{
|
||||||
|
WebhookID: webhook.ID,
|
||||||
|
Path: "3c9e1f7a-5b2d-4e8a-9f6c-2a7d1e4b8c05",
|
||||||
|
Active: true,
|
||||||
|
},
|
||||||
|
).Error)
|
||||||
event := env.seedEvent(t, webhook.ID, `{"hello":"browser"}`)
|
event := env.seedEvent(t, webhook.ID, `{"hello":"browser"}`)
|
||||||
target := env.seedTarget(t, webhook.ID)
|
target := env.seedTarget(t, webhook.ID)
|
||||||
dlv := env.seedFailedDelivery(t, webhook.ID, event.ID, target.ID)
|
dlv := env.seedFailedDelivery(t, webhook.ID, event.ID, target.ID)
|
||||||
@@ -77,6 +85,7 @@ func TestAlpineRunsUnderTheSecurityPolicy(t *testing.T) {
|
|||||||
|
|
||||||
checkAddForms(ctx, t, page)
|
checkAddForms(ctx, t, page)
|
||||||
checkTargetType(ctx, t, page+"/events")
|
checkTargetType(ctx, t, page+"/events")
|
||||||
|
checkCopy(ctx, t, page)
|
||||||
checkEventLog(ctx, t, page+"/events", event.ID, target.Name)
|
checkEventLog(ctx, t, page+"/events", event.ID, target.Name)
|
||||||
checkMobileMenu(ctx, t, page)
|
checkMobileMenu(ctx, t, page)
|
||||||
|
|
||||||
@@ -220,6 +229,8 @@ func click(ctx context.Context, t *testing.T, xpath string) {
|
|||||||
|
|
||||||
// checkAddForms loads a webhook page and checks that each section's add
|
// checkAddForms loads a webhook page and checks that each section's add
|
||||||
// form stays hidden until the Add button beside its heading is clicked.
|
// form stays hidden until the Add button beside its heading is clicked.
|
||||||
|
// The click looks for a button element there, so it also checks that
|
||||||
|
// Add is one.
|
||||||
func checkAddForms(ctx context.Context, t *testing.T, url string) {
|
func checkAddForms(ctx context.Context, t *testing.T, url string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
@@ -327,6 +338,31 @@ func checkTargetType(ctx context.Context, t *testing.T, elsewhere string) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// checkCopy loads a webhook page and checks that the Copy control beside
|
||||||
|
// its entrypoint's URL is a button, and that clicking it copies the URL
|
||||||
|
// and says so: the button reads "Copied" only once the copy succeeded.
|
||||||
|
func checkCopy(ctx context.Context, t *testing.T, url string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
copyButton := `//button[@data-copy-target]`
|
||||||
|
|
||||||
|
// A browser lets the page in its active tab write to the clipboard
|
||||||
|
// on a click. A headless browser refuses unless told to allow it.
|
||||||
|
require.NoError(t, chromedp.Run(
|
||||||
|
ctx,
|
||||||
|
browser.SetPermission(
|
||||||
|
&browser.PermissionDescriptor{Name: "clipboard-write"},
|
||||||
|
browser.PermissionSettingGranted,
|
||||||
|
),
|
||||||
|
loadPage(url),
|
||||||
|
))
|
||||||
|
|
||||||
|
click(ctx, t, copyButton)
|
||||||
|
|
||||||
|
assert.True(t, shown(ctx, copyButton+`[text()="Copied"]`),
|
||||||
|
`clicking Copy does not show "Copied"`)
|
||||||
|
}
|
||||||
|
|
||||||
// checkEventLog loads the event log and checks that clicking an event's
|
// checkEventLog loads the event log and checks that clicking an event's
|
||||||
// row expands it, that in there clicking its delivery shows the
|
// row expands it, that in there clicking its delivery shows the
|
||||||
// delivery's attempts and clicking again hides them, and that clicking
|
// delivery's attempts and clicking again hides them, and that clicking
|
||||||
|
|||||||
@@ -1178,12 +1178,12 @@ func TestHook_LinksBetweenPages(t *testing.T) {
|
|||||||
// mobile menu link.
|
// mobile menu link.
|
||||||
{
|
{
|
||||||
"/user/navigator/",
|
"/user/navigator/",
|
||||||
`href="([^"]+)" class="btn-text">Webhooks<`,
|
`href="([^"]+)" class="btn-secondary">Webhooks<`,
|
||||||
list,
|
list,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"/user/navigator/",
|
"/user/navigator/",
|
||||||
`href="([^"]+)" class="btn-text w-full[^"]*">Webhooks<`,
|
`href="([^"]+)" class="btn-secondary w-full">Webhooks<`,
|
||||||
list,
|
list,
|
||||||
},
|
},
|
||||||
{list, `href="(/hook/[^"]+)"`, page},
|
{list, `href="(/hook/[^"]+)"`, page},
|
||||||
|
|||||||
+48
-1
@@ -1 +1,48 @@
|
|||||||
/* Webhooker custom styles — see input.css for Tailwind theme */
|
/*
|
||||||
|
* The two shared styles for the controls a user clicks. Every page loads
|
||||||
|
* this file after tailwind.css. It is plain CSS: make css does not build
|
||||||
|
* it.
|
||||||
|
*
|
||||||
|
* A button is btn-primary, btn-secondary or btn-danger, from input.css.
|
||||||
|
* A secondary or inline action, such as Copy beside an entrypoint URL or
|
||||||
|
* Edit beside a target, is btn-small.
|
||||||
|
*
|
||||||
|
* Each card on the webhook list is a card-elevated link as a whole. It
|
||||||
|
* shows an Open label in btn-small and takes btn-small's focus outline.
|
||||||
|
*
|
||||||
|
* The rules join tailwind.css's components layer, where input.css puts
|
||||||
|
* its own, so a utility class on an element still overrides them.
|
||||||
|
*/
|
||||||
|
@layer components {
|
||||||
|
/* input.css gives its buttons no pointer cursor. */
|
||||||
|
.btn-primary,
|
||||||
|
.btn-secondary,
|
||||||
|
.btn-danger {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-small {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.25rem 0.625rem;
|
||||||
|
border: 1px solid var(--color-gray-300);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
background-color: var(--color-white);
|
||||||
|
color: var(--color-primary-700);
|
||||||
|
font-size: var(--text-xs);
|
||||||
|
line-height: 1rem;
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-small:hover {
|
||||||
|
border-color: var(--color-primary-500);
|
||||||
|
background-color: var(--color-primary-50);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-small:focus-visible,
|
||||||
|
.card-elevated:focus-visible {
|
||||||
|
outline: 2px solid var(--color-primary-500);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+2
-2
@@ -22,9 +22,9 @@
|
|||||||
<footer class="bg-gray-100 border-t border-gray-200 shadow-[0_-4px_6px_-1px_rgba(0,0,0,0.1)] mt-8">
|
<footer class="bg-gray-100 border-t border-gray-200 shadow-[0_-4px_6px_-1px_rgba(0,0,0,0.1)] mt-8">
|
||||||
<div class="max-w-6xl mx-auto px-8 py-6">
|
<div class="max-w-6xl mx-auto px-8 py-6">
|
||||||
<div class="text-center text-sm text-gray-500 font-mono font-light">
|
<div class="text-center text-sm text-gray-500 font-mono font-light">
|
||||||
<a href="https://git.eeqj.de/sneak/webhooker" class="hover:text-gray-700">Webhooker</a>
|
<a href="https://git.eeqj.de/sneak/webhooker" class="btn-small">Webhooker</a>
|
||||||
<span class="mx-1">by</span>
|
<span class="mx-1">by</span>
|
||||||
<a href="https://sneak.berlin" class="hover:text-gray-700">@sneak</a>
|
<a href="https://sneak.berlin" class="btn-small">@sneak</a>
|
||||||
<span class="mx-3">|</span>
|
<span class="mx-3">|</span>
|
||||||
<span>{{if .Version}}{{.Version}}{{else}}dev{{end}}</span>
|
<span>{{if .Version}}{{.Version}}{{else}}dev{{end}}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>{{block "title" .}}Webhooker{{end}}</title>
|
<title>{{block "title" .}}Webhooker{{end}}</title>
|
||||||
<link rel="stylesheet" href="/s/css/tailwind.css">
|
<link rel="stylesheet" href="/s/css/tailwind.css">
|
||||||
|
<link rel="stylesheet" href="/s/css/style.css">
|
||||||
<style>[x-cloak] { display: none !important; }</style>
|
<style>[x-cloak] { display: none !important; }</style>
|
||||||
{{block "head" .}}{{end}}
|
{{block "head" .}}{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
+10
-10
@@ -2,12 +2,12 @@
|
|||||||
<nav class="app-bar" x-data="collapsible">
|
<nav class="app-bar" x-data="collapsible">
|
||||||
<div class="max-w-6xl mx-auto flex justify-between items-center">
|
<div class="max-w-6xl mx-auto flex justify-between items-center">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<a href="/" class="text-xl font-medium text-gray-900 hover:text-primary-600 transition-colors">Webhooker</a>
|
<a href="/" class="btn-secondary text-xl">Webhooker</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Mobile menu button -->
|
<!-- Mobile menu button -->
|
||||||
{{if .User}}
|
{{if .User}}
|
||||||
<button @click="toggle" class="md:hidden p-2 rounded-md text-gray-500 hover:bg-gray-100">
|
<button type="button" @click="toggle" class="btn-secondary md:hidden p-2">
|
||||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path x-show="closed" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/>
|
<path x-show="closed" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/>
|
||||||
<path x-show="open" x-cloak stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
<path x-show="open" x-cloak stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
||||||
@@ -18,9 +18,9 @@
|
|||||||
<!-- Desktop navigation -->
|
<!-- Desktop navigation -->
|
||||||
<div class="hidden md:flex items-center gap-4">
|
<div class="hidden md:flex items-center gap-4">
|
||||||
{{if .User}}
|
{{if .User}}
|
||||||
<a href="/hooks" class="btn-text">Webhooks</a>
|
<a href="/hooks" class="btn-secondary">Webhooks</a>
|
||||||
<a href="/settings" class="btn-text">Settings</a>
|
<a href="/settings" class="btn-secondary">Settings</a>
|
||||||
<a href="/user/{{.User.Username}}" class="btn-text">
|
<a href="/user/{{.User.Username}}" class="btn-secondary">
|
||||||
<svg class="w-5 h-5 mr-1" fill="currentColor" viewBox="0 0 16 16">
|
<svg class="w-5 h-5 mr-1" fill="currentColor" viewBox="0 0 16 16">
|
||||||
<path d="M11 6a3 3 0 1 1-6 0 3 3 0 0 1 6 0z"/>
|
<path d="M11 6a3 3 0 1 1-6 0 3 3 0 0 1 6 0z"/>
|
||||||
<path fill-rule="evenodd" d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm8-7a7 7 0 0 0-5.468 11.37C3.242 11.226 4.805 10 8 10s4.757 1.225 5.468 2.37A7 7 0 0 0 8 1z"/>
|
<path fill-rule="evenodd" d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm8-7a7 7 0 0 0-5.468 11.37C3.242 11.226 4.805 10 8 10s4.757 1.225 5.468 2.37A7 7 0 0 0 8 1z"/>
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
{{if .CSRFToken}}
|
{{if .CSRFToken}}
|
||||||
<form method="POST" action="/pages/logout" class="inline">
|
<form method="POST" action="/pages/logout" class="inline">
|
||||||
<input type="hidden" name="csrf_token" value="{{.CSRFToken}}">
|
<input type="hidden" name="csrf_token" value="{{.CSRFToken}}">
|
||||||
<button type="submit" class="btn-text">Logout</button>
|
<button type="submit" class="btn-secondary">Logout</button>
|
||||||
</form>
|
</form>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -43,13 +43,13 @@
|
|||||||
<div x-show="open" x-cloak x-transition class="md:hidden mt-4 pt-4 border-t border-gray-200">
|
<div x-show="open" x-cloak x-transition class="md:hidden mt-4 pt-4 border-t border-gray-200">
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
{{if .User}}
|
{{if .User}}
|
||||||
<a href="/hooks" class="btn-text w-full text-left">Webhooks</a>
|
<a href="/hooks" class="btn-secondary w-full">Webhooks</a>
|
||||||
<a href="/settings" class="btn-text w-full text-left">Settings</a>
|
<a href="/settings" class="btn-secondary w-full">Settings</a>
|
||||||
<a href="/user/{{.User.Username}}" class="btn-text w-full text-left">Profile</a>
|
<a href="/user/{{.User.Username}}" class="btn-secondary w-full">Profile</a>
|
||||||
{{if .CSRFToken}}
|
{{if .CSRFToken}}
|
||||||
<form method="POST" action="/pages/logout">
|
<form method="POST" action="/pages/logout">
|
||||||
<input type="hidden" name="csrf_token" value="{{.CSRFToken}}">
|
<input type="hidden" name="csrf_token" value="{{.CSRFToken}}">
|
||||||
<button type="submit" class="btn-text w-full text-left">Logout</button>
|
<button type="submit" class="btn-secondary w-full">Logout</button>
|
||||||
</form>
|
</form>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
no class this wide. -->
|
no class this wide. -->
|
||||||
<div class="mx-auto px-6 py-8" style="max-width: 108rem">
|
<div class="mx-auto px-6 py-8" style="max-width: 108rem">
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<a href="/hooks" class="text-sm text-primary-600 hover:text-primary-700">← Back to webhooks</a>
|
<a href="/hooks" class="btn-small">← Back to webhooks</a>
|
||||||
<div class="flex flex-wrap justify-between items-center gap-2 mt-2">
|
<div class="flex flex-wrap justify-between items-center gap-2 mt-2">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-2xl font-medium text-gray-900">{{.Webhook.Name}}</h1>
|
<h1 class="text-2xl font-medium text-gray-900">{{.Webhook.Name}}</h1>
|
||||||
@@ -35,8 +35,8 @@
|
|||||||
<div class="card" x-data="collapsible">
|
<div class="card" x-data="collapsible">
|
||||||
<div class="p-4 border-b border-gray-200 flex justify-between items-center">
|
<div class="p-4 border-b border-gray-200 flex justify-between items-center">
|
||||||
<h2 class="text-lg font-medium text-gray-900">Entrypoints</h2>
|
<h2 class="text-lg font-medium text-gray-900">Entrypoints</h2>
|
||||||
<button @click="toggle" class="btn-text text-sm">
|
<button type="button" @click="toggle" class="btn-small">
|
||||||
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="w-3 h-3 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"/>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/>
|
||||||
</svg>
|
</svg>
|
||||||
Add
|
Add
|
||||||
@@ -55,9 +55,9 @@
|
|||||||
<div class="divide-y divide-gray-100">
|
<div class="divide-y divide-gray-100">
|
||||||
{{range .Entrypoints}}
|
{{range .Entrypoints}}
|
||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
<div class="flex items-center justify-between mb-1">
|
<div class="flex flex-wrap items-center justify-between gap-2 mb-1">
|
||||||
<span class="text-sm font-medium text-gray-900">{{if .Description}}{{.Description}}{{else}}Entrypoint{{end}}</span>
|
<span class="text-sm font-medium text-gray-900">{{if .Description}}{{.Description}}{{else}}Entrypoint{{end}}</span>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
{{if .Active}}
|
{{if .Active}}
|
||||||
<span class="badge-success">Active</span>
|
<span class="badge-success">Active</span>
|
||||||
{{else}}
|
{{else}}
|
||||||
@@ -65,13 +65,13 @@
|
|||||||
{{end}}
|
{{end}}
|
||||||
<form method="POST" action="/hook/{{$.Webhook.ID}}/entrypoints/{{.ID}}/toggle" class="inline">
|
<form method="POST" action="/hook/{{$.Webhook.ID}}/entrypoints/{{.ID}}/toggle" class="inline">
|
||||||
<input type="hidden" name="csrf_token" value="{{$.CSRFToken}}">
|
<input type="hidden" name="csrf_token" value="{{$.CSRFToken}}">
|
||||||
<button type="submit" class="text-xs text-gray-500 hover:text-primary-600" title="{{if .Active}}Deactivate{{else}}Activate{{end}}">
|
<button type="submit" class="btn-small" title="{{if .Active}}Deactivate{{else}}Activate{{end}}">
|
||||||
{{if .Active}}Deactivate{{else}}Activate{{end}}
|
{{if .Active}}Deactivate{{else}}Activate{{end}}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
<form method="POST" action="/hook/{{$.Webhook.ID}}/entrypoints/{{.ID}}/delete" onsubmit="return confirm('Delete this entrypoint?')" class="inline">
|
<form method="POST" action="/hook/{{$.Webhook.ID}}/entrypoints/{{.ID}}/delete" onsubmit="return confirm('Delete this entrypoint?')" class="inline">
|
||||||
<input type="hidden" name="csrf_token" value="{{$.CSRFToken}}">
|
<input type="hidden" name="csrf_token" value="{{$.CSRFToken}}">
|
||||||
<button type="submit" class="text-xs text-red-500 hover:text-red-700" title="Delete">Delete</button>
|
<button type="submit" class="btn-small text-red-600" title="Delete">Delete</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -79,7 +79,7 @@
|
|||||||
<code id="entrypoint-url-{{.ID}}" class="text-xs text-gray-500 break-all block flex-1">{{$.BaseURL}}/h/{{.Path}}</code>
|
<code id="entrypoint-url-{{.ID}}" class="text-xs text-gray-500 break-all block flex-1">{{$.BaseURL}}/h/{{.Path}}</code>
|
||||||
<!-- Hidden until app.js reveals it; without the
|
<!-- Hidden until app.js reveals it; without the
|
||||||
script the URL above stays selectable. -->
|
script the URL above stays selectable. -->
|
||||||
<button type="button" hidden data-copy-target="entrypoint-url-{{.ID}}" class="text-xs text-gray-500 hover:text-primary-600">Copy</button>
|
<button type="button" hidden data-copy-target="entrypoint-url-{{.ID}}" class="btn-small">Copy</button>
|
||||||
</div>
|
</div>
|
||||||
<!-- The URL above is the entrypoint's credential:
|
<!-- The URL above is the entrypoint's credential:
|
||||||
anyone holding it can submit events. -->
|
anyone holding it can submit events. -->
|
||||||
@@ -94,8 +94,8 @@
|
|||||||
<div class="card" x-data="collapsible">
|
<div class="card" x-data="collapsible">
|
||||||
<div class="p-4 border-b border-gray-200 flex justify-between items-center">
|
<div class="p-4 border-b border-gray-200 flex justify-between items-center">
|
||||||
<h2 class="text-lg font-medium text-gray-900">Targets</h2>
|
<h2 class="text-lg font-medium text-gray-900">Targets</h2>
|
||||||
<button @click="toggle" class="btn-text text-sm">
|
<button type="button" @click="toggle" class="btn-small">
|
||||||
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="w-3 h-3 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"/>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/>
|
||||||
</svg>
|
</svg>
|
||||||
Add
|
Add
|
||||||
@@ -148,25 +148,25 @@
|
|||||||
<div class="divide-y divide-gray-100">
|
<div class="divide-y divide-gray-100">
|
||||||
{{range .Targets}}
|
{{range .Targets}}
|
||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
<div class="flex items-center justify-between mb-1">
|
<div class="flex flex-wrap items-center justify-between gap-2 mb-1">
|
||||||
<span class="text-sm font-medium text-gray-900">{{.Name}}</span>
|
<span class="text-sm font-medium text-gray-900">{{.Name}}</span>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
<span class="badge-info">{{.Type}}</span>
|
<span class="badge-info">{{.Type}}</span>
|
||||||
{{if .Active}}
|
{{if .Active}}
|
||||||
<span class="badge-success">Active</span>
|
<span class="badge-success">Active</span>
|
||||||
{{else}}
|
{{else}}
|
||||||
<span class="badge-error">Inactive</span>
|
<span class="badge-error">Inactive</span>
|
||||||
{{end}}
|
{{end}}
|
||||||
<a href="/hook/{{$.Webhook.ID}}/targets/{{.ID}}/edit" class="text-xs text-gray-500 hover:text-primary-600" title="Edit">Edit</a>
|
<a href="/hook/{{$.Webhook.ID}}/targets/{{.ID}}/edit" class="btn-small" title="Edit">Edit</a>
|
||||||
<form method="POST" action="/hook/{{$.Webhook.ID}}/targets/{{.ID}}/toggle" class="inline">
|
<form method="POST" action="/hook/{{$.Webhook.ID}}/targets/{{.ID}}/toggle" class="inline">
|
||||||
<input type="hidden" name="csrf_token" value="{{$.CSRFToken}}">
|
<input type="hidden" name="csrf_token" value="{{$.CSRFToken}}">
|
||||||
<button type="submit" class="text-xs text-gray-500 hover:text-primary-600" title="{{if .Active}}Deactivate{{else}}Activate{{end}}">
|
<button type="submit" class="btn-small" title="{{if .Active}}Deactivate{{else}}Activate{{end}}">
|
||||||
{{if .Active}}Deactivate{{else}}Activate{{end}}
|
{{if .Active}}Deactivate{{else}}Activate{{end}}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
<form method="POST" action="/hook/{{$.Webhook.ID}}/targets/{{.ID}}/delete" onsubmit="return confirm('Delete this target?')" class="inline">
|
<form method="POST" action="/hook/{{$.Webhook.ID}}/targets/{{.ID}}/delete" onsubmit="return confirm('Delete this target?')" class="inline">
|
||||||
<input type="hidden" name="csrf_token" value="{{$.CSRFToken}}">
|
<input type="hidden" name="csrf_token" value="{{$.CSRFToken}}">
|
||||||
<button type="submit" class="text-xs text-red-500 hover:text-red-700" title="Delete">Delete</button>
|
<button type="submit" class="btn-small text-red-600" title="Delete">Delete</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -188,7 +188,7 @@
|
|||||||
<div class="card mt-6">
|
<div class="card mt-6">
|
||||||
<div class="p-4 border-b border-gray-200 flex justify-between items-center">
|
<div class="p-4 border-b border-gray-200 flex justify-between items-center">
|
||||||
<h2 class="text-lg font-medium text-gray-900">50 Most Recent Events</h2>
|
<h2 class="text-lg font-medium text-gray-900">50 Most Recent Events</h2>
|
||||||
<a href="/hook/{{.Webhook.ID}}/events" class="btn-text text-sm">Full Event Log</a>
|
<a href="/hook/{{.Webhook.ID}}/events" class="btn-small">Full Event Log</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="divide-y divide-gray-100">
|
<div class="divide-y divide-gray-100">
|
||||||
{{range .Events}}
|
{{range .Events}}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
{{define "content"}}
|
{{define "content"}}
|
||||||
<div class="max-w-2xl mx-auto px-6 py-8">
|
<div class="max-w-2xl mx-auto px-6 py-8">
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<a href="/hook/{{.Webhook.ID}}" class="text-sm text-primary-600 hover:text-primary-700">← Back to {{.Webhook.Name}}</a>
|
<a href="/hook/{{.Webhook.ID}}" class="btn-small">← Back to {{.Webhook.Name}}</a>
|
||||||
<h1 class="text-2xl font-medium text-gray-900 mt-2">Edit Webhook</h1>
|
<h1 class="text-2xl font-medium text-gray-900 mt-2">Edit Webhook</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
+29
-27
@@ -5,7 +5,7 @@
|
|||||||
{{define "content"}}
|
{{define "content"}}
|
||||||
<div class="max-w-6xl mx-auto px-6 py-8">
|
<div class="max-w-6xl mx-auto px-6 py-8">
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<a href="/hook/{{.Webhook.ID}}" class="text-sm text-primary-600 hover:text-primary-700">← Back to {{.Webhook.Name}}</a>
|
<a href="/hook/{{.Webhook.ID}}" class="btn-small">← Back to {{.Webhook.Name}}</a>
|
||||||
<div class="flex justify-between items-center mt-2">
|
<div class="flex justify-between items-center mt-2">
|
||||||
<h1 class="text-2xl font-medium text-gray-900">Full Event Log</h1>
|
<h1 class="text-2xl font-medium text-gray-900">Full Event Log</h1>
|
||||||
<span class="text-sm text-gray-500">{{.TotalEvents}} total event{{if ne .TotalEvents 1}}s{{end}}</span>
|
<span class="text-sm text-gray-500">{{.TotalEvents}} total event{{if ne .TotalEvents 1}}s{{end}}</span>
|
||||||
@@ -16,8 +16,8 @@
|
|||||||
<div class="divide-y divide-gray-100">
|
<div class="divide-y divide-gray-100">
|
||||||
{{range .Events}}
|
{{range .Events}}
|
||||||
<div class="p-4" x-data="collapsible">
|
<div class="p-4" x-data="collapsible">
|
||||||
<div class="flex items-center justify-between cursor-pointer" @click="toggle">
|
<button type="button" class="btn-small w-full flex flex-wrap justify-between gap-2 text-left" @click="toggle">
|
||||||
<div class="flex items-center gap-3">
|
<span class="flex flex-wrap items-center gap-3">
|
||||||
<span class="badge-info">{{.Method}}</span>
|
<span class="badge-info">{{.Method}}</span>
|
||||||
<span class="text-sm font-mono text-gray-700">{{.ID}}</span>
|
<span class="text-sm font-mono text-gray-700">{{.ID}}</span>
|
||||||
<span class="text-sm text-gray-500">{{.ContentType}}</span>
|
<span class="text-sm text-gray-500">{{.ContentType}}</span>
|
||||||
@@ -27,8 +27,8 @@
|
|||||||
{{if .ResubmitCount}}
|
{{if .ResubmitCount}}
|
||||||
<span class="text-xs text-gray-500">resubmitted {{.ResubmitCount}} time{{if ne .ResubmitCount 1}}s{{end}}</span>
|
<span class="text-xs text-gray-500">resubmitted {{.ResubmitCount}} time{{if ne .ResubmitCount 1}}s{{end}}</span>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</span>
|
||||||
<div class="flex items-center gap-4">
|
<span class="flex flex-wrap items-center gap-4">
|
||||||
{{range .Deliveries}}
|
{{range .Deliveries}}
|
||||||
<span class="text-xs {{if eq .Status "delivered"}}text-green-600{{else if eq .Status "failed"}}text-red-600{{else if eq .Status "retrying"}}text-yellow-600{{else}}text-gray-400{{end}}">
|
<span class="text-xs {{if eq .Status "delivered"}}text-green-600{{else if eq .Status "failed"}}text-red-600{{else if eq .Status "retrying"}}text-yellow-600{{else}}text-gray-400{{end}}">
|
||||||
{{.Target.DisplayName}}: {{.Status}}
|
{{.Target.DisplayName}}: {{.Status}}
|
||||||
@@ -38,8 +38,8 @@
|
|||||||
<svg class="w-4 h-4 text-gray-400 transition-transform" :class="caretClass" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="w-4 h-4 text-gray-400 transition-transform" :class="caretClass" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</span>
|
||||||
</div>
|
</button>
|
||||||
|
|
||||||
<div x-show="open" x-cloak class="mt-3 p-3 bg-gray-50 rounded-md">
|
<div x-show="open" x-cloak class="mt-3 p-3 bg-gray-50 rounded-md">
|
||||||
<div class="mb-3 flex flex-wrap items-center justify-between gap-2">
|
<div class="mb-3 flex flex-wrap items-center justify-between gap-2">
|
||||||
@@ -50,12 +50,12 @@
|
|||||||
<form method="POST" action="/hook/{{$.Webhook.ID}}/events/{{.ID}}/resubmit" class="inline">
|
<form method="POST" action="/hook/{{$.Webhook.ID}}/events/{{.ID}}/resubmit" class="inline">
|
||||||
<input type="hidden" name="csrf_token" value="{{$.CSRFToken}}">
|
<input type="hidden" name="csrf_token" value="{{$.CSRFToken}}">
|
||||||
<input type="hidden" name="page" value="{{$.Page}}">
|
<input type="hidden" name="page" value="{{$.Page}}">
|
||||||
<button type="submit" class="text-xs text-primary-600 hover:text-primary-700" title="Submit this event again as a new event, to every currently active target">Resubmit</button>
|
<button type="submit" class="btn-small" title="Submit this event again as a new event, to every currently active target">Resubmit</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<pre class="text-xs text-gray-700 overflow-x-auto whitespace-pre-wrap break-all">{{.Body}}</pre>
|
<pre class="text-xs text-gray-700 overflow-x-auto whitespace-pre-wrap break-all">{{.Body}}</pre>
|
||||||
{{if .BodyTruncated}}
|
{{if .BodyTruncated}}
|
||||||
<p class="mt-2 text-xs text-gray-500">Body truncated for display: showing {{.BodyShownBytes}} of {{.BodyBytes}} bytes. The stored body is unchanged — <a href="/hook/{{$.Webhook.ID}}/events/{{.ID}}/body" class="text-primary-600 hover:text-primary-700 underline">download the full body</a>.</p>
|
<p class="mt-2 text-xs text-gray-500">Body truncated for display: showing {{.BodyShownBytes}} of {{.BodyBytes}} bytes. The stored body is unchanged — <a href="/hook/{{$.Webhook.ID}}/events/{{.ID}}/body" class="btn-small">download the full body</a>.</p>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{if .Deliveries}}
|
{{if .Deliveries}}
|
||||||
@@ -64,24 +64,26 @@
|
|||||||
<div class="mt-2 divide-y divide-gray-200">
|
<div class="mt-2 divide-y divide-gray-200">
|
||||||
{{range .Deliveries}}
|
{{range .Deliveries}}
|
||||||
<div class="py-2" x-data="collapsible">
|
<div class="py-2" x-data="collapsible">
|
||||||
<div class="flex items-center justify-between cursor-pointer" @click="toggle">
|
<div class="flex items-center gap-3">
|
||||||
<div class="flex items-center gap-3">
|
<button type="button" class="btn-small flex-1 flex-wrap justify-between gap-2 text-left" @click="toggle">
|
||||||
<span class="text-sm text-gray-700">{{.Target.DisplayName}}</span>
|
<span class="flex flex-wrap items-center gap-3">
|
||||||
<span class="text-xs {{if eq .Status "delivered"}}text-green-600{{else if eq .Status "failed"}}text-red-600{{else if eq .Status "retrying"}}text-yellow-600{{else}}text-gray-400{{end}}">{{.Status}}</span>
|
<span class="text-sm text-gray-700">{{.Target.DisplayName}}</span>
|
||||||
</div>
|
<span class="text-xs {{if eq .Status "delivered"}}text-green-600{{else if eq .Status "failed"}}text-red-600{{else if eq .Status "retrying"}}text-yellow-600{{else}}text-gray-400{{end}}">{{.Status}}</span>
|
||||||
<div class="flex items-center gap-3">
|
</span>
|
||||||
{{if .Status.Terminal}}
|
<span class="flex flex-wrap items-center gap-3">
|
||||||
<form method="POST" action="/hook/{{$.Webhook.ID}}/deliveries/{{.ID}}/replay" class="inline" @click.stop>
|
<span class="text-xs text-gray-400">{{.AttemptCount}} attempt{{if ne .AttemptCount 1}}s{{end}}</span>
|
||||||
<input type="hidden" name="csrf_token" value="{{$.CSRFToken}}">
|
<svg class="w-3 h-3 text-gray-400 transition-transform" :class="caretClass" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<input type="hidden" name="page" value="{{$.Page}}">
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
|
||||||
<button type="submit" class="text-xs text-primary-600 hover:text-primary-700" title="Send this event to the target again">Replay</button>
|
</svg>
|
||||||
</form>
|
</span>
|
||||||
{{end}}
|
</button>
|
||||||
<span class="text-xs text-gray-400">{{.AttemptCount}} attempt{{if ne .AttemptCount 1}}s{{end}}</span>
|
{{if .Status.Terminal}}
|
||||||
<svg class="w-3 h-3 text-gray-400 transition-transform" :class="caretClass" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<form method="POST" action="/hook/{{$.Webhook.ID}}/deliveries/{{.ID}}/replay" class="inline">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
|
<input type="hidden" name="csrf_token" value="{{$.CSRFToken}}">
|
||||||
</svg>
|
<input type="hidden" name="page" value="{{$.Page}}">
|
||||||
</div>
|
<button type="submit" class="btn-small" title="Send this event to the target again">Replay</button>
|
||||||
|
</form>
|
||||||
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div x-show="open" x-cloak class="mt-2 space-y-2">
|
<div x-show="open" x-cloak class="mt-2 space-y-2">
|
||||||
|
|||||||
@@ -25,7 +25,12 @@
|
|||||||
<p class="text-sm text-gray-500 mt-1">{{.Description}}</p>
|
<p class="text-sm text-gray-500 mt-1">{{.Description}}</p>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
<span class="badge-info">Retention: {{.RetentionLabel}}</span>
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
|
<span class="badge-info">Retention: {{.RetentionLabel}}</span>
|
||||||
|
<!-- A label, not a control of its own: the whole card
|
||||||
|
is the link. -->
|
||||||
|
<span class="btn-small">Open →</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-wrap gap-6 mt-4 text-sm text-gray-500">
|
<div class="flex flex-wrap gap-6 mt-4 text-sm text-gray-500">
|
||||||
<span>{{.EntrypointCount}} entrypoint{{if ne .EntrypointCount 1}}s{{end}}{{if .InactiveEntrypointCount}}, {{.InactiveEntrypointCount}} inactive{{end}}</span>
|
<span>{{.EntrypointCount}} entrypoint{{if ne .EntrypointCount 1}}s{{end}}{{if .InactiveEntrypointCount}}, {{.InactiveEntrypointCount}} inactive{{end}}</span>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
{{define "content"}}
|
{{define "content"}}
|
||||||
<div class="max-w-2xl mx-auto px-6 py-8">
|
<div class="max-w-2xl mx-auto px-6 py-8">
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<a href="/hooks" class="text-sm text-primary-600 hover:text-primary-700">← Back to webhooks</a>
|
<a href="/hooks" class="btn-small">← Back to webhooks</a>
|
||||||
<h1 class="text-2xl font-medium text-gray-900 mt-2">Create Webhook</h1>
|
<h1 class="text-2xl font-medium text-gray-900 mt-2">Create Webhook</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
{{define "content"}}
|
{{define "content"}}
|
||||||
<div class="max-w-2xl mx-auto px-6 py-8">
|
<div class="max-w-2xl mx-auto px-6 py-8">
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<a href="/hook/{{.Webhook.ID}}" class="text-sm text-primary-600 hover:text-primary-700">← Back to {{.Webhook.Name}}</a>
|
<a href="/hook/{{.Webhook.ID}}" class="btn-small">← Back to {{.Webhook.Name}}</a>
|
||||||
<h1 class="text-2xl font-medium text-gray-900 mt-2">Edit Target</h1>
|
<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>
|
<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>
|
||||||
|
|||||||
Reference in New Issue
Block a user