feat: add entrypoint/target management controls (closes #25)
Add toggle (activate/deactivate) and delete buttons for individual
entrypoints and targets on the webhook detail page. Each action is a
POST form submission with ownership verification.
New routes:
POST /source/{id}/entrypoints/{entrypointID}/delete
POST /source/{id}/entrypoints/{entrypointID}/toggle
POST /source/{id}/targets/{targetID}/delete
POST /source/{id}/targets/{targetID}/toggle
This commit is contained in:
parent
2606d41c60
commit
f21a007a3c
@ -527,6 +527,144 @@ func (h *Handlers) HandleTargetCreate() http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// HandleEntrypointDelete handles deleting an individual entrypoint.
|
||||
func (h *Handlers) HandleEntrypointDelete() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
userID, ok := h.getUserID(r)
|
||||
if !ok {
|
||||
http.Redirect(w, r, "/pages/login", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
sourceID := chi.URLParam(r, "sourceID")
|
||||
entrypointID := chi.URLParam(r, "entrypointID")
|
||||
|
||||
// Verify webhook ownership
|
||||
var webhook database.Webhook
|
||||
if err := h.db.DB().Where("id = ? AND user_id = ?", sourceID, userID).First(&webhook).Error; err != nil {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Delete entrypoint (must belong to this webhook)
|
||||
result := h.db.DB().Where("id = ? AND webhook_id = ?", entrypointID, webhook.ID).Delete(&database.Entrypoint{})
|
||||
if result.Error != nil {
|
||||
h.log.Error("failed to delete entrypoint", "error", result.Error)
|
||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/source/"+webhook.ID, http.StatusSeeOther)
|
||||
}
|
||||
}
|
||||
|
||||
// HandleEntrypointToggle handles toggling the active state of an entrypoint.
|
||||
func (h *Handlers) HandleEntrypointToggle() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
userID, ok := h.getUserID(r)
|
||||
if !ok {
|
||||
http.Redirect(w, r, "/pages/login", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
sourceID := chi.URLParam(r, "sourceID")
|
||||
entrypointID := chi.URLParam(r, "entrypointID")
|
||||
|
||||
// Verify webhook ownership
|
||||
var webhook database.Webhook
|
||||
if err := h.db.DB().Where("id = ? AND user_id = ?", sourceID, userID).First(&webhook).Error; err != nil {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Find the entrypoint
|
||||
var entrypoint database.Entrypoint
|
||||
if err := h.db.DB().Where("id = ? AND webhook_id = ?", entrypointID, webhook.ID).First(&entrypoint).Error; err != nil {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Toggle active state
|
||||
entrypoint.Active = !entrypoint.Active
|
||||
if err := h.db.DB().Save(&entrypoint).Error; err != nil {
|
||||
h.log.Error("failed to toggle entrypoint", "error", err)
|
||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/source/"+webhook.ID, http.StatusSeeOther)
|
||||
}
|
||||
}
|
||||
|
||||
// HandleTargetDelete handles deleting an individual target.
|
||||
func (h *Handlers) HandleTargetDelete() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
userID, ok := h.getUserID(r)
|
||||
if !ok {
|
||||
http.Redirect(w, r, "/pages/login", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
sourceID := chi.URLParam(r, "sourceID")
|
||||
targetID := chi.URLParam(r, "targetID")
|
||||
|
||||
// Verify webhook ownership
|
||||
var webhook database.Webhook
|
||||
if err := h.db.DB().Where("id = ? AND user_id = ?", sourceID, userID).First(&webhook).Error; err != nil {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Delete target (must belong to this webhook)
|
||||
result := h.db.DB().Where("id = ? AND webhook_id = ?", targetID, webhook.ID).Delete(&database.Target{})
|
||||
if result.Error != nil {
|
||||
h.log.Error("failed to delete target", "error", result.Error)
|
||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/source/"+webhook.ID, http.StatusSeeOther)
|
||||
}
|
||||
}
|
||||
|
||||
// HandleTargetToggle handles toggling the active state of a target.
|
||||
func (h *Handlers) HandleTargetToggle() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
userID, ok := h.getUserID(r)
|
||||
if !ok {
|
||||
http.Redirect(w, r, "/pages/login", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
sourceID := chi.URLParam(r, "sourceID")
|
||||
targetID := chi.URLParam(r, "targetID")
|
||||
|
||||
// Verify webhook ownership
|
||||
var webhook database.Webhook
|
||||
if err := h.db.DB().Where("id = ? AND user_id = ?", sourceID, userID).First(&webhook).Error; err != nil {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Find the target
|
||||
var target database.Target
|
||||
if err := h.db.DB().Where("id = ? AND webhook_id = ?", targetID, webhook.ID).First(&target).Error; err != nil {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Toggle active state
|
||||
target.Active = !target.Active
|
||||
if err := h.db.DB().Save(&target).Error; err != nil {
|
||||
h.log.Error("failed to toggle target", "error", err)
|
||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/source/"+webhook.ID, http.StatusSeeOther)
|
||||
}
|
||||
}
|
||||
|
||||
// getUserID extracts the user ID from the session.
|
||||
func (h *Handlers) getUserID(r *http.Request) (string, bool) {
|
||||
sess, err := h.session.Get(r)
|
||||
|
||||
@ -106,7 +106,11 @@ func (s *Server) SetupRoutes() {
|
||||
r.Post("/delete", s.h.HandleSourceDelete()) // Delete webhook
|
||||
r.Get("/logs", s.h.HandleSourceLogs()) // View webhook logs
|
||||
r.Post("/entrypoints", s.h.HandleEntrypointCreate()) // Add entrypoint
|
||||
r.Post("/entrypoints/{entrypointID}/delete", s.h.HandleEntrypointDelete()) // Delete entrypoint
|
||||
r.Post("/entrypoints/{entrypointID}/toggle", s.h.HandleEntrypointToggle()) // Toggle entrypoint active
|
||||
r.Post("/targets", s.h.HandleTargetCreate()) // Add target
|
||||
r.Post("/targets/{targetID}/delete", s.h.HandleTargetDelete()) // Delete target
|
||||
r.Post("/targets/{targetID}/toggle", s.h.HandleTargetToggle()) // Toggle target active
|
||||
})
|
||||
|
||||
// Entrypoint endpoint — accepts incoming webhook POST requests only.
|
||||
|
||||
@ -49,11 +49,21 @@
|
||||
<div class="p-4">
|
||||
<div class="flex items-center justify-between mb-1">
|
||||
<span class="text-sm font-medium text-gray-900">{{if .Description}}{{.Description}}{{else}}Entrypoint{{end}}</span>
|
||||
<div class="flex items-center gap-2">
|
||||
{{if .Active}}
|
||||
<span class="badge-success">Active</span>
|
||||
{{else}}
|
||||
<span class="badge-error">Inactive</span>
|
||||
{{end}}
|
||||
<form method="POST" action="/source/{{$.Webhook.ID}}/entrypoints/{{.ID}}/toggle" class="inline">
|
||||
<button type="submit" class="text-xs text-gray-500 hover:text-primary-600" title="{{if .Active}}Deactivate{{else}}Activate{{end}}">
|
||||
{{if .Active}}Deactivate{{else}}Activate{{end}}
|
||||
</button>
|
||||
</form>
|
||||
<form method="POST" action="/source/{{$.Webhook.ID}}/entrypoints/{{.ID}}/delete" onsubmit="return confirm('Delete this entrypoint?')" class="inline">
|
||||
<button type="submit" class="text-xs text-red-500 hover:text-red-700" title="Delete">Delete</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<code class="text-xs text-gray-500 break-all block mt-1">{{$.BaseURL}}/webhook/{{.Path}}</code>
|
||||
</div>
|
||||
@ -110,6 +120,14 @@
|
||||
{{else}}
|
||||
<span class="badge-error">Inactive</span>
|
||||
{{end}}
|
||||
<form method="POST" action="/source/{{$.Webhook.ID}}/targets/{{.ID}}/toggle" class="inline">
|
||||
<button type="submit" class="text-xs text-gray-500 hover:text-primary-600" title="{{if .Active}}Deactivate{{else}}Activate{{end}}">
|
||||
{{if .Active}}Deactivate{{else}}Activate{{end}}
|
||||
</button>
|
||||
</form>
|
||||
<form method="POST" action="/source/{{$.Webhook.ID}}/targets/{{.ID}}/delete" onsubmit="return confirm('Delete this target?')" class="inline">
|
||||
<button type="submit" class="text-xs text-red-500 hover:text-red-700" title="Delete">Delete</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{{if .Config}}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user