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:
@@ -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.
|
// getUserID extracts the user ID from the session.
|
||||||
func (h *Handlers) getUserID(r *http.Request) (string, bool) {
|
func (h *Handlers) getUserID(r *http.Request) (string, bool) {
|
||||||
sess, err := h.session.Get(r)
|
sess, err := h.session.Get(r)
|
||||||
|
|||||||
@@ -105,8 +105,12 @@ func (s *Server) SetupRoutes() {
|
|||||||
r.Post("/edit", s.h.HandleSourceEditSubmit()) // Handle edit submission
|
r.Post("/edit", s.h.HandleSourceEditSubmit()) // Handle edit submission
|
||||||
r.Post("/delete", s.h.HandleSourceDelete()) // Delete webhook
|
r.Post("/delete", s.h.HandleSourceDelete()) // Delete webhook
|
||||||
r.Get("/logs", s.h.HandleSourceLogs()) // View webhook logs
|
r.Get("/logs", s.h.HandleSourceLogs()) // View webhook logs
|
||||||
r.Post("/entrypoints", s.h.HandleEntrypointCreate()) // Add entrypoint
|
r.Post("/entrypoints", s.h.HandleEntrypointCreate()) // Add entrypoint
|
||||||
r.Post("/targets", s.h.HandleTargetCreate()) // Add target
|
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.
|
// Entrypoint endpoint — accepts incoming webhook POST requests only.
|
||||||
|
|||||||
@@ -49,11 +49,21 @@
|
|||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
<div class="flex items-center justify-between mb-1">
|
<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>
|
<span class="text-sm font-medium text-gray-900">{{if .Description}}{{.Description}}{{else}}Entrypoint{{end}}</span>
|
||||||
{{if .Active}}
|
<div class="flex items-center gap-2">
|
||||||
<span class="badge-success">Active</span>
|
{{if .Active}}
|
||||||
{{else}}
|
<span class="badge-success">Active</span>
|
||||||
<span class="badge-error">Inactive</span>
|
{{else}}
|
||||||
{{end}}
|
<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>
|
</div>
|
||||||
<code class="text-xs text-gray-500 break-all block mt-1">{{$.BaseURL}}/webhook/{{.Path}}</code>
|
<code class="text-xs text-gray-500 break-all block mt-1">{{$.BaseURL}}/webhook/{{.Path}}</code>
|
||||||
</div>
|
</div>
|
||||||
@@ -110,6 +120,14 @@
|
|||||||
{{else}}
|
{{else}}
|
||||||
<span class="badge-error">Inactive</span>
|
<span class="badge-error">Inactive</span>
|
||||||
{{end}}
|
{{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>
|
||||||
</div>
|
</div>
|
||||||
{{if .Config}}
|
{{if .Config}}
|
||||||
|
|||||||
Reference in New Issue
Block a user