From db1a0ad5f57393acdc9501a2144a0ea9086d3f6c Mon Sep 17 00:00:00 2001 From: StarFleetCPTN Date: Wed, 26 Mar 2025 06:26:39 -0700 Subject: [PATCH] feat: Enhance notification services with delete confirmation dialog - Added a confirmation dialog for deleting notification services to prevent accidental deletions. - Updated the notifications management interface to include improved delete button functionality. - Enhanced JavaScript event handling for delete requests, providing user feedback on success and error scenarios. - Refactored notification service deletion logic to ensure accurate tracking and messaging during deletion operations. --- components/layout.templ | 20 ++- components/notifications.templ | 173 +++++++++++++++++++-- internal/scheduler/scheduler.go | 13 ++ internal/web/handlers/settings_handlers.go | 8 +- 4 files changed, 190 insertions(+), 24 deletions(-) diff --git a/components/layout.templ b/components/layout.templ index edc6fb9..80e25ad 100644 --- a/components/layout.templ +++ b/components/layout.templ @@ -237,14 +237,20 @@ templ LayoutWithContext(title string, ctx context.Context) { } @@ -458,8 +464,8 @@ templ LayoutWithContext(title string, ctx context.Context) { } // Initialize admin dropdown toggle if available - const adminDropdownToggle = document.querySelector('[data-collapse-toggle="dropdown-admin"]'); - const adminDropdown = document.getElementById('dropdown-admin'); + const adminDropdownToggle = document.querySelector('[data-collapse-toggle="dropdown-settings"]'); + const adminDropdown = document.getElementById('dropdown-settings'); if (adminDropdownToggle && adminDropdown) { // Check if we should show the dropdown (if current page is under admin section) diff --git a/components/notifications.templ b/components/notifications.templ index b90cf53..77ee14a 100644 --- a/components/notifications.templ +++ b/components/notifications.templ @@ -5,6 +5,66 @@ import ( "fmt" ) +// Dialog component for confirmation dialogs using Flowbite modal +templ NotificationDialog(id string, title string, message string, confirmClass string, confirmText string, action string, serviceID uint, serviceName string) { + +} + +script hideNotificationDialog(id string) { + document.getElementById(id).classList.add("hidden"); + document.getElementById(id).classList.remove("flex"); +} + +script showNotificationDialog(id string) { + document.getElementById(id).classList.remove("hidden"); + document.getElementById(id).classList.add("flex"); +} + +script triggerServiceDelete(dialogId string, serviceID uint, serviceName string) { + // Hide the dialog + document.getElementById(dialogId).classList.add("hidden"); + document.getElementById(dialogId).classList.remove("flex"); + + // Add debugging info + console.log(`Notification service deletion triggered for: ${serviceName} (ID: ${serviceID})`); + + // Store data in a way that's accessible to event handlers + window.lastDeletedService = { + id: serviceID, + name: serviceName + }; + + // Add custom marker to track this deletion + window.currentlyDeletingService = true; +} + templ Notifications(ctx context.Context, data SettingsNotificationsData) { @LayoutWithContext("Notification Services", ctx) { @@ -85,31 +145,113 @@ templ Notifications(ctx context.Context, data SettingsNotificationsData) { // Track all HTMX events for debugging document.addEventListener('htmx:beforeRequest', function(event) { console.log("HTMX before request:", event.detail); + + // Check if this is a DELETE request for a notification service + const path = event.detail.path; + const method = event.detail.verb; + + console.log(`Request path: ${path}, method: ${method}`); + + // Pattern match for notification service deletions (e.g., /admin/settings/notifications/123) + if (path && method === 'DELETE' && path.match(/^\/admin\/settings\/notifications\/\d+$/)) { + console.log("Detected notification service deletion request via URL pattern"); + + // This is definitely a delete request - store this information + window.isServiceDeleteRequest = true; + } }); document.addEventListener('htmx:afterRequest', function(event) { console.log("HTMX after request:", event.detail); - // Check if this is a successful notification service deletion - if (event.detail.pathInfo && - event.detail.pathInfo.requestPath && - event.detail.pathInfo.requestPath.match(/^\/admin\/settings\/notifications\/\d+$/) && - event.detail.verb === 'DELETE' && - event.detail.successful) { + // Check for notification service deletion multiple ways + const isDeleteRequest = + // Check global flag from the triggerServiceDelete function + window.currentlyDeletingService || + // Check flag from beforeRequest handler + window.isServiceDeleteRequest || + // Check URL pattern directly from this event + (event.detail.pathInfo && + event.detail.pathInfo.requestPath && + event.detail.pathInfo.requestPath.match(/^\/admin\/settings\/notifications\/\d+$/) && + event.detail.verb === 'DELETE'); + + console.log(`Is delete request: ${isDeleteRequest}`); + + // If this is a successful delete request, show notification + if (isDeleteRequest && event.detail.successful) { + console.log("Delete request was successful"); - showToast('Notification service deleted successfully', 'success'); + let serviceName = "Unknown"; + + // Try multiple sources for service name + if (event.detail.elt && event.detail.elt.getAttribute) { + serviceName = event.detail.elt.getAttribute('data-service-name') || serviceName; + } + + if (serviceName === "Unknown" && window.lastDeletedService) { + // Fallback to our stored service info + serviceName = window.lastDeletedService.name; + } + + console.log(`Showing success notification for deleted service: ${serviceName}`); + showToast(`Notification service "${serviceName}" deleted successfully`, 'success'); + + // Clear flags + window.currentlyDeletingService = false; + window.isServiceDeleteRequest = false; + window.lastDeletedService = null; } }); document.addEventListener('htmx:responseError', function(event) { console.log("HTMX response error:", event.detail); + // Similar logic as success but for errors + const isDeleteRequest = + window.currentlyDeletingService || + window.isServiceDeleteRequest || + (event.detail.pathInfo && + event.detail.pathInfo.requestPath && + event.detail.pathInfo.requestPath.match(/^\/admin\/settings\/notifications\/\d+$/) && + event.detail.verb === 'DELETE'); + let errorMsg = 'An error occurred'; if (event.detail.xhr && event.detail.xhr.responseText) { errorMsg = event.detail.xhr.responseText; } - showToast(errorMsg, 'error'); + if (isDeleteRequest) { + console.log("Delete request failed"); + + let serviceName = "Unknown"; + + // Try multiple sources for service name + if (event.detail.elt && event.detail.elt.getAttribute) { + serviceName = event.detail.elt.getAttribute('data-service-name') || serviceName; + } + + if (serviceName === "Unknown" && window.lastDeletedService) { + // Fallback to our stored service info + serviceName = window.lastDeletedService.name; + } + + let errorMsg = `Failed to delete notification service "${serviceName}"`; + + if (event.detail.xhr && event.detail.xhr.responseText) { + errorMsg = `Error: ${event.detail.xhr.responseText}`; + } + + console.log(`Showing error notification: ${errorMsg}`); + showToast(errorMsg, 'error'); + + // Clear flags + window.currentlyDeletingService = false; + window.isServiceDeleteRequest = false; + window.lastDeletedService = null; + } else { + showToast(errorMsg, 'error'); + } }); // Handle modal hide buttons @@ -214,12 +356,21 @@ templ Notifications(ctx context.Context, data SettingsNotificationsData) { > + + @NotificationDialog( + fmt.Sprintf("delete-notification-dialog-%d", service.ID), + "Delete Notification Service", + fmt.Sprintf("Are you sure you want to delete the notification service '%s'? This cannot be undone.", service.Name), + "text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:ring-red-300 font-medium rounded-lg text-sm px-5 py-2.5 dark:bg-red-600 dark:hover:bg-red-700 focus:outline-none dark:focus:ring-red-800", + "Delete", + "delete", + service.ID, + service.Name, + ) diff --git a/internal/scheduler/scheduler.go b/internal/scheduler/scheduler.go index 5b6a13c..d20fab3 100644 --- a/internal/scheduler/scheduler.go +++ b/internal/scheduler/scheduler.go @@ -1579,12 +1579,23 @@ func (s *Scheduler) sendServiceWebhookNotification(service *db.NotificationServi // generateDefaultPayload creates a standard webhook payload func generateDefaultPayload(job *db.Job, history *db.JobHistory, config *db.TransferConfig, eventType string) map[string]interface{} { + // get event type + switch eventType { + case "job_start": + eventType = "Job Started" + case "job_complete": + eventType = "Job Completed" + case "job_fail": + eventType = "Job Failed" + } + payload := map[string]interface{}{ "event": eventType, "job": map[string]interface{}{ "id": job.ID, "name": job.Name, "status": history.Status, + "event": eventType, "message": history.ErrorMessage, "started_at": history.StartTime.Format(time.RFC3339), "config_id": config.ID, @@ -2400,6 +2411,8 @@ func (s *Scheduler) sendNtfyNotification(service *db.NotificationService, job *d func (s *Scheduler) sendGotifyNotification(service *db.NotificationService, job *db.Job, history *db.JobHistory, config *db.TransferConfig, eventType string) error { s.log.LogDebug("Sending Gotify notification for job %d", job.ID) + // pretty print job + fmt.Printf("Job: %+v\n", job) // Get Gotify server URL and token from service config serverURL, ok := service.Config["url"] if !ok || serverURL == "" { diff --git a/internal/web/handlers/settings_handlers.go b/internal/web/handlers/settings_handlers.go index 30299c5..aa28adf 100644 --- a/internal/web/handlers/settings_handlers.go +++ b/internal/web/handlers/settings_handlers.go @@ -74,12 +74,6 @@ func (h *Handlers) HandleCreateNotificationService(c *gin.Context) { description := c.PostForm("description") isEnabled := c.PostForm("is_enabled") == "on" - // print the form data - fmt.Println("name", name) - fmt.Println("serviceType", serviceType) - fmt.Println("description", description) - fmt.Println("isEnabled", isEnabled) - // Validate required fields if name == "" || serviceType == "" { // Return to notifications page with error message @@ -727,6 +721,8 @@ func (h *Handlers) HandleTestNotification(c *gin.Context) { config["priority"] = c.PostForm("gotify_priority") config["title"] = c.PostForm("gotify_title_template") + fmt.Println("config", config) + // Validate required fields if config["url"] == "" || config["token"] == "" { c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "Gotify Server URL and Application Token are required"})