Monorepo for Tangled tangled.org

appview/pages: add webhook settings ui #1070

merged opened by anirudh.fi targeting master from icy/qlyxxp

Add hooks tab to repository settings with webhook management interface. Includes forms for adding/editing webhooks with URL, secret, and event configuration. Shows webhook status with active/inactive toggle.

Signed-off-by: Anirudh Oppiliappan anirudh@tangled.org

Labels

None yet.

assignee

None yet.

Participants 2
AT URI
at://did:plc:hwevmowznbiukdf6uk5dwrrq/sh.tangled.repo.pull/3menyebs7wi22
+276
Diff #5
+1
appview/pages/funcmap.go
··· 466 {"Name": "general", "Icon": "sliders-horizontal"}, 467 {"Name": "access", "Icon": "users"}, 468 {"Name": "pipelines", "Icon": "layers-2"}, 469 }, 470 } 471 },
··· 466 {"Name": "general", "Icon": "sliders-horizontal"}, 467 {"Name": "access", "Icon": "users"}, 468 {"Name": "pipelines", "Icon": "layers-2"}, 469 + {"Name": "hooks", "Icon": "webhook"}, 470 }, 471 } 472 },
+14
appview/pages/pages.go
··· 964 return p.executeRepo("repo/settings/pipelines", w, params) 965 } 966 967 type RepoIssuesParams struct { 968 LoggedInUser *oauth.MultiAccountUser 969 RepoInfo repoinfo.RepoInfo
··· 964 return p.executeRepo("repo/settings/pipelines", w, params) 965 } 966 967 + type RepoWebhooksSettingsParams struct { 968 + LoggedInUser *oauth.MultiAccountUser 969 + RepoInfo repoinfo.RepoInfo 970 + Active string 971 + Tab string 972 + Webhooks []models.Webhook 973 + } 974 + 975 + func (p *Pages) RepoWebhooksSettings(w io.Writer, params RepoWebhooksSettingsParams) error { 976 + params.Active = "settings" 977 + params.Tab = "hooks" 978 + return p.executeRepo("repo/settings/hooks", w, params) 979 + } 980 + 981 type RepoIssuesParams struct { 982 LoggedInUser *oauth.MultiAccountUser 983 RepoInfo repoinfo.RepoInfo
+261
appview/pages/templates/repo/settings/hooks.html
···
··· 1 + {{ define "title" }}{{ .Tab }} settings &middot; {{ .RepoInfo.FullName }}{{ end }} 2 + 3 + {{ define "repoContent" }} 4 + <section class="w-full grid grid-cols-1 md:grid-cols-4 gap-2"> 5 + <div class="col-span-1"> 6 + {{ template "repo/settings/fragments/sidebar" . }} 7 + </div> 8 + <div class="col-span-1 md:col-span-3 flex flex-col gap-6 p-2"> 9 + {{ template "webhooksSettings" . }} 10 + <div id="operation-error" class="text-red-500 dark:text-red-400"></div> 11 + </div> 12 + </section> 13 + {{ end }} 14 + 15 + {{ define "webhooksSettings" }} 16 + <div class="grid grid-cols-1 md:grid-cols-3 gap-4 items-center"> 17 + <div class="col-span-1 md:col-span-2"> 18 + <h2 class="text-sm pb-2 uppercase font-bold">Webhooks</h2> 19 + <p class="text-gray-500 dark:text-gray-400"> 20 + Webhooks allow external services to be notified when certain events happen. 21 + When the specified events happen, we'll send a POST request to each of the URLs you provide. 22 + </p> 23 + </div> 24 + <div class="col-span-1 md:col-span-1 md:justify-self-end"> 25 + <button 26 + class="btn flex items-center gap-2" 27 + popovertarget="add-webhook-modal" 28 + {{ if not .RepoInfo.Roles.IsOwner }}disabled{{ end }} 29 + popovertargetaction="toggle"> 30 + {{ i "plus" "size-4" }} 31 + new webhook 32 + </button> 33 + <div 34 + id="add-webhook-modal" 35 + popover 36 + class="bg-white w-full sm:w-[40rem] dark:bg-gray-800 p-6 max-h-dvh overflow-y-auto rounded border border-gray-200 dark:border-gray-700 drop-shadow dark:text-white backdrop:bg-gray-400/50 dark:backdrop:bg-gray-800/50"> 37 + {{ template "addWebhookModal" . }} 38 + </div> 39 + </div> 40 + </div> 41 + <div class="flex flex-col rounded border border-gray-200 dark:border-gray-700 divide-y divide-gray-200 dark:divide-gray-700 w-full"> 42 + {{ range .Webhooks }} 43 + <div id="webhook-{{.Id}}" class="flex flex-col gap-2 p-4"> 44 + <div class="flex items-start justify-between"> 45 + <div class="flex-1"> 46 + <div class="flex items-center gap-2"> 47 + <span class="font-mono text-sm break-all">{{ .Url }}</span> 48 + {{ if .Active }} 49 + <span class="inline-flex items-center gap-1 px-2 py-1 text-sm rounded bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200"> 50 + {{ i "circle-check" "size-4" }} 51 + active 52 + </span> 53 + {{ else }} 54 + <span class="inline-flex items-center gap-1 px-2 py-1 text-sm rounded bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-200"> 55 + {{ i "circle" "size-4" }} 56 + inactive 57 + </span> 58 + {{ end }} 59 + </div> 60 + <div class="text-sm text-gray-500 dark:text-gray-400 mt-1"> 61 + Events: {{ range $i, $e := .Events }}{{ if $i }}, {{ end }}{{ $e }}{{ end }} 62 + </div> 63 + </div> 64 + {{ if $.RepoInfo.Roles.IsOwner }} 65 + <div class="flex gap-2 items-center"> 66 + <button 67 + class="btn text-sm flex items-center gap-1" 68 + popovertarget="edit-webhook-modal-{{.Id}}" 69 + popovertargetaction="toggle"> 70 + {{ i "pencil" "size-4" }} 71 + edit 72 + </button> 73 + <button 74 + class="btn text-sm text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300 flex items-center gap-1 group" 75 + hx-delete="/{{ $.RepoInfo.FullName }}/settings/hooks/{{ .Id }}" 76 + hx-swap="none" 77 + hx-confirm="Are you sure you want to delete this webhook?"> 78 + {{ i "trash-2" "size-4" }} 79 + delete 80 + {{ i "loader-circle" "size-4 animate-spin hidden group-[.htmx-request]:inline" }} 81 + </button> 82 + </div> 83 + {{ end }} 84 + </div> 85 + {{ if $.RepoInfo.Roles.IsOwner }} 86 + <div class="flex items-center"> 87 + <label class="flex items-center gap-2 cursor-pointer"> 88 + <input 89 + type="checkbox" 90 + {{ if .Active }}checked{{ end }} 91 + hx-post="/{{ $.RepoInfo.FullName }}/settings/hooks/{{ .Id }}/toggle" 92 + hx-swap="none" 93 + class="cursor-pointer" 94 + /> 95 + <span class="text-sm">Active</span> 96 + </label> 97 + </div> 98 + <div 99 + id="edit-webhook-modal-{{.Id}}" 100 + popover 101 + class="bg-white w-full sm:w-[40rem] dark:bg-gray-800 p-6 max-h-dvh overflow-y-auto rounded border border-gray-200 dark:border-gray-700 drop-shadow dark:text-white backdrop:bg-gray-400/50 dark:backdrop:bg-gray-800/50"> 102 + {{ template "editWebhookModal" (list $ .) }} 103 + </div> 104 + {{ end }} 105 + </div> 106 + {{ else }} 107 + <div class="flex items-center justify-center p-4 text-gray-500"> 108 + no webhooks configured yet 109 + </div> 110 + {{ end }} 111 + </div> 112 + {{ end }} 113 + 114 + {{ define "addWebhookModal" }} 115 + <form 116 + hx-post="/{{ $.RepoInfo.FullName }}/settings/hooks" 117 + hx-swap="none" 118 + class="flex flex-col gap-4" 119 + > 120 + <h3 class="uppercase font-bold">New Webhook</h3> 121 + 122 + <div class="flex flex-col gap-2"> 123 + <label for="webhook-url" class="text-sm font-semibold">Payload URL</label> 124 + <input 125 + type="url" 126 + id="webhook-url" 127 + name="url" 128 + required 129 + placeholder="https://example.com/webhook" 130 + class="w-full" 131 + /> 132 + <p class="text-sm text-gray-500 dark:text-gray-400"> 133 + The URL that will receive the webhook POST requests. 134 + </p> 135 + </div> 136 + 137 + <div class="flex flex-col gap-2"> 138 + <label for="webhook-secret" class="text-sm font-semibold">Secret (optional)</label> 139 + <input 140 + type="text" 141 + id="webhook-secret" 142 + name="secret" 143 + placeholder="Optional: provide a secret for signed webhooks" 144 + class="w-full font-mono text-sm" 145 + /> 146 + <p class="text-sm text-gray-500 dark:text-gray-400"> 147 + If provided, webhook payloads will be signed with HMAC-SHA256. Leave blank to send unsigned webhooks. 148 + </p> 149 + </div> 150 + 151 + <div class="flex flex-col gap-2"> 152 + <label class="text-sm font-semibold">Events</label> 153 + <div class="flex flex-col gap-2 ml-4"> 154 + <div class="flex items-center gap-2"> 155 + <input type="checkbox" name="event_push" value="on" checked /> 156 + <span class="text-sm">Push events</span> 157 + </div> 158 + <p class="text-sm text-gray-500 dark:text-gray-400"> 159 + Additional event types (pull requests, issues) will be available in future updates. 160 + </p> 161 + </div> 162 + </div> 163 + 164 + <div class="flex items-center gap-2"> 165 + <input type="checkbox" id="webhook-active" name="active" value="on" checked /> 166 + <label for="webhook-active" class="text-sm font-semibold">Active</label> 167 + </div> 168 + 169 + <div class="flex gap-2 pt-2 justify-end"> 170 + <button 171 + type="button" 172 + popovertarget="add-webhook-modal" 173 + popovertargetaction="hide" 174 + class="btn flex items-center gap-2 text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300" 175 + > 176 + {{ i "x" "size-4" }} cancel 177 + </button> 178 + <button type="submit" class="btn-create flex items-center gap-2 group"> 179 + {{ i "plus" "size-4" }} add webhook 180 + {{ i "loader-circle" "size-4 animate-spin hidden group-[.htmx-request]:inline" }} 181 + </button> 182 + </div> 183 + <div id="webhooks-error" class="text-red-500 dark:text-red-400"></div> 184 + </form> 185 + {{ end }} 186 + 187 + {{ define "editWebhookModal" }} 188 + {{ $ctx := index . 0 }} 189 + {{ $webhook := index . 1 }} 190 + <form 191 + hx-put="/{{ $ctx.RepoInfo.FullName }}/settings/hooks/{{ $webhook.Id }}" 192 + hx-swap="none" 193 + class="flex flex-col gap-4" 194 + > 195 + <h3 class="uppercase font-bold">Edit Webhook</h3> 196 + 197 + <div class="flex flex-col gap-2"> 198 + <label for="edit-webhook-url-{{ $webhook.Id }}" class="text-sm font-semibold">Payload URL</label> 199 + <input 200 + type="url" 201 + id="edit-webhook-url-{{ $webhook.Id }}" 202 + name="url" 203 + required 204 + value="{{ $webhook.Url }}" 205 + class="w-full" 206 + /> 207 + </div> 208 + 209 + <div class="flex flex-col gap-2"> 210 + <label for="edit-webhook-secret-{{ $webhook.Id }}" class="text-sm font-semibold">Secret (optional)</label> 211 + <input 212 + type="text" 213 + id="edit-webhook-secret-{{ $webhook.Id }}" 214 + name="secret" 215 + placeholder="Leave blank to keep current secret" 216 + class="w-full font-mono text-sm" 217 + /> 218 + <p class="text-sm text-gray-500 dark:text-gray-400"> 219 + Leave blank to keep the existing secret. Remove value to disable signing. 220 + </p> 221 + </div> 222 + 223 + <div class="flex flex-col gap-2"> 224 + <label class="text-sm font-semibold">Events</label> 225 + <div class="flex flex-col gap-2 ml-4"> 226 + {{ $hasPush := false }} 227 + {{ range $webhook.Events }} 228 + {{ if eq . "push" }}{{ $hasPush = true }}{{ end }} 229 + {{ end }} 230 + <div class="flex items-center gap-2"> 231 + <input type="checkbox" name="event_push" value="on" {{ if $hasPush }}checked{{ end }} /> 232 + <span class="text-sm">Push events</span> 233 + </div> 234 + <p class="text-sm text-gray-500 dark:text-gray-400"> 235 + Additional event types (pull requests, issues) will be available in future updates. 236 + </p> 237 + </div> 238 + </div> 239 + 240 + <div class="flex items-center gap-2"> 241 + <input type="checkbox" id="edit-webhook-active-{{ $webhook.Id }}" name="active" value="on" {{ if $webhook.Active }}checked{{ end }} /> 242 + <span for="edit-webhook-active-{{ $webhook.Id }}">Enable</span> 243 + </div> 244 + 245 + <div class="flex gap-2 pt-2 justify-end"> 246 + <button 247 + type="button" 248 + popovertarget="edit-webhook-modal-{{ $webhook.Id }}" 249 + popovertargetaction="hide" 250 + class="btn flex items-center gap-2 text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300" 251 + > 252 + {{ i "x" "size-4" }} cancel 253 + </button> 254 + <button type="submit" class="btn-create flex items-center gap-2 group"> 255 + {{ i "save" "size-4" }} save 256 + {{ i "loader-circle" "size-4 animate-spin hidden group-[.htmx-request]:inline" }} 257 + </button> 258 + </div> 259 + <div id="webhooks-error" class="text-red-500 dark:text-red-400"></div> 260 + </form> 261 + {{ end }}

History

6 rounds 1 comment
sign up or login to add to the discussion
1 commit
expand
appview/pages: add webhook settings ui
3/3 success
expand
expand 0 comments
pull request successfully merged
1 commit
expand
appview/pages: add webhook settings ui
3/3 success
expand
expand 0 comments
1 commit
expand
appview/pages: add webhook settings ui
3/3 success
expand
expand 0 comments
1 commit
expand
appview/pages: add webhook settings ui
3/3 success
expand
expand 0 comments
1 commit
expand
appview/pages: add webhook settings ui
3/3 success
expand
expand 1 comment

lgtm, works well with gating for non-owners etc.

1 commit
expand
appview/pages: add webhook settings ui
3/3 success
expand
expand 0 comments