From 4d393e17abfea5aad88df4a8148fbe684cdd5312 Mon Sep 17 00:00:00 2001 From: StarFleetCPTN Date: Mon, 24 Mar 2025 16:55:32 -0700 Subject: [PATCH] feat: Implement notification services management - Added a new notifications management page with functionality to create, delete, and test notification services. - Introduced a new template for displaying notification services, including forms for adding and configuring services. - Updated backend handlers to support notification service operations and improved error handling. - Enhanced the user interface with toast notifications for success and error messages related to notification actions. - Refactored existing settings components to integrate with the new notifications management features. --- components/layout.templ | 2 +- components/notifications.templ | 528 +++++++++++++++++++++ components/settings.templ | 9 + internal/web/handlers/routes.go | 2 + internal/web/handlers/settings_handlers.go | 88 +++- 5 files changed, 604 insertions(+), 25 deletions(-) create mode 100644 components/notifications.templ diff --git a/components/layout.templ b/components/layout.templ index 3c4f411..edc6fb9 100644 --- a/components/layout.templ +++ b/components/layout.templ @@ -244,7 +244,7 @@ templ LayoutWithContext(title string, ctx context.Context) { Authentication Providers
  • - Notifications + Notifications
  • } diff --git a/components/notifications.templ b/components/notifications.templ new file mode 100644 index 0000000..565e547 --- /dev/null +++ b/components/notifications.templ @@ -0,0 +1,528 @@ +package components + +import ( + "context" + "fmt" +) + +templ Notifications(ctx context.Context, data SettingsNotificationsData) { + @LayoutWithContext("Notification Services", ctx) { + +
    + + + +
    +
    + + if data.SuccessMessage != "" { + + } + + if data.ErrorMessage != "" { + + } + +
    +

    + + Notification Services +

    +
    + + +
    +

    + Add Notification Service +

    +
    +
    + + +
    + + + + + + + +
    +
    + + + if len(data.NotificationServices) == 0 { +
    +
    + +
    +

    No notification services configured

    +

    Use the form above to add your first notification service.

    +
    + } else { +
    +
      + for _, service := range data.NotificationServices { +
    • +
      +
      +
      +
      + if service.Type == "email" { +
      + +
      + } else if service.Type == "webhook" { +
      + +
      + } else { +
      + +
      + } +
      +

      + { service.Name } +

      +

      + { service.Description } +

      +
      +
      +
      + + +
      +
      +
      +
      +
      + + if service.IsEnabled { + Active + } else { + Disabled + } + + + { service.Type } + + if len(service.EventTriggers) > 0 && service.Type == "webhook" { + + { fmt.Sprintf("%d triggers", len(service.EventTriggers)) } + + } + if service.SuccessCount > 0 || service.FailureCount > 0 { + + { fmt.Sprintf("%d/%d", service.SuccessCount, service.SuccessCount + service.FailureCount) } + + } +
      +
      + if service.Type == "webhook" { +
      +
      + Events: + + if len(service.EventTriggers) == 0 { + None + } else { + for i, trigger := range service.EventTriggers { + if i > 0 { + , + } + { trigger } + } + } + +
      +
      + Retry: + + if service.RetryPolicy == "" { + Default + } else { + { service.RetryPolicy } + } + +
      +
      + } else { +
      + +

      + Last sent: + if service.SuccessCount > 0 { + "Recently" + } else { + "Never" + } +

      +
      + } +
      +
      +
      +
    • + } +
    +
    + } + + +
    +
    +
    + +
    +
    +

    + Notification services allow the system to send alerts for job events such as completion, errors, or when jobs start. +

    +
    +
    +
    +
    +
    + + + } +} \ No newline at end of file diff --git a/components/settings.templ b/components/settings.templ index 2592d2c..216c323 100644 --- a/components/settings.templ +++ b/components/settings.templ @@ -5,6 +5,7 @@ import ( "fmt" ) +// NotificationService defines a notification service configuration type NotificationService struct { ID uint Name string @@ -20,6 +21,14 @@ type NotificationService struct { FailureCount int } +// SettingsNotificationsData contains data for the notifications page in settings +type SettingsNotificationsData struct { + NotificationServices []NotificationService + ErrorMessage string + SuccessMessage string +} + +// SettingsData contains data for the settings page type SettingsData struct { NotificationServices []NotificationService ErrorMessage string diff --git a/internal/web/handlers/routes.go b/internal/web/handlers/routes.go index dae20f0..f4c6387 100644 --- a/internal/web/handlers/routes.go +++ b/internal/web/handlers/routes.go @@ -166,6 +166,8 @@ func (h *Handlers) RegisterRoutes(router *gin.Engine) { authProviderGroup.DELETE("/:id", h.HandleDeleteAuthProvider) authProviderGroup.POST("/:id/test", h.HandleTestAuthProviderConnection) + // Notification routes + settingsGroup.GET("/notifications", h.HandleNotificationsPage) settingsGroup.POST("/notifications", h.HandleCreateNotificationService) settingsGroup.DELETE("/notifications/:id", h.HandleDeleteNotificationService) settingsGroup.POST("/notifications/test", h.HandleTestNotification) diff --git a/internal/web/handlers/settings_handlers.go b/internal/web/handlers/settings_handlers.go index 7f8a47a..9ed60c9 100644 --- a/internal/web/handlers/settings_handlers.go +++ b/internal/web/handlers/settings_handlers.go @@ -59,7 +59,7 @@ func (h *Handlers) HandleSettings(c *gin.Context) { components.Settings(ctx, data).Render(ctx, c.Writer) } -// HandleCreateNotificationService handles POST /settings/notifications +// HandleCreateNotificationService handles POST /admin/settings/notifications func (h *Handlers) HandleCreateNotificationService(c *gin.Context) { // Check if the user has permission to manage settings if !h.checkPermission(c, "system.settings") { @@ -75,8 +75,8 @@ func (h *Handlers) HandleCreateNotificationService(c *gin.Context) { // Validate required fields if name == "" || serviceType == "" { - // Return to settings page with error message - h.handleSettingsWithError(c, "Name and type are required fields.") + // Return to notifications page with error message + h.handleNotificationsWithError(c, "Name and type are required fields.") return } @@ -129,7 +129,7 @@ func (h *Handlers) HandleCreateNotificationService(c *gin.Context) { // Save to database if err := h.DB.Create(&service).Error; err != nil { log.Printf("Error creating notification service: %v", err) - h.handleSettingsWithError(c, "Failed to create notification service: "+err.Error()) + h.handleNotificationsWithError(c, "Failed to create notification service: "+err.Error()) return } @@ -156,11 +156,11 @@ func (h *Handlers) HandleCreateNotificationService(c *gin.Context) { log.Printf("Error creating audit log: %v", err) } - // Redirect back to settings page with success message - h.handleSettingsWithSuccess(c, "Notification service created successfully.") + // Redirect back to notifications page with success message + h.handleNotificationsWithSuccess(c, "Notification service created successfully.") return default: - h.handleSettingsWithError(c, "Invalid notification service type.") + h.handleNotificationsWithError(c, "Invalid notification service type.") return } @@ -177,7 +177,7 @@ func (h *Handlers) HandleCreateNotificationService(c *gin.Context) { // Save to database if err := h.DB.Create(&service).Error; err != nil { log.Printf("Error creating notification service: %v", err) - h.handleSettingsWithError(c, "Failed to create notification service: "+err.Error()) + h.handleNotificationsWithError(c, "Failed to create notification service: "+err.Error()) return } @@ -201,11 +201,11 @@ func (h *Handlers) HandleCreateNotificationService(c *gin.Context) { log.Printf("Error creating audit log: %v", err) } - // Redirect back to settings page with success message - h.handleSettingsWithSuccess(c, "Notification service created successfully.") + // Redirect back to notifications page with success message + h.handleNotificationsWithSuccess(c, "Notification service created successfully.") } -// HandleDeleteNotificationService handles DELETE /settings/notifications/:id +// HandleDeleteNotificationService handles DELETE /admin/settings/notifications/:id func (h *Handlers) HandleDeleteNotificationService(c *gin.Context) { // Check if the user has permission to manage settings if !h.checkPermission(c, "system.settings") { @@ -216,21 +216,21 @@ func (h *Handlers) HandleDeleteNotificationService(c *gin.Context) { // Get service ID from path serviceID, err := strconv.ParseUint(c.Param("id"), 10, 32) if err != nil { - h.handleSettingsWithError(c, "Invalid notification service ID.") + h.handleNotificationsWithError(c, "Invalid notification service ID.") return } // Find service to delete (for audit log) var service db.NotificationService if err := h.DB.First(&service, serviceID).Error; err != nil { - h.handleSettingsWithError(c, "Notification service not found.") + h.handleNotificationsWithError(c, "Notification service not found.") return } // Delete the service if err := h.DB.Delete(&db.NotificationService{}, serviceID).Error; err != nil { log.Printf("Error deleting notification service: %v", err) - h.handleSettingsWithError(c, "Failed to delete notification service: "+err.Error()) + h.handleNotificationsWithError(c, "Failed to delete notification service: "+err.Error()) return } @@ -254,8 +254,8 @@ func (h *Handlers) HandleDeleteNotificationService(c *gin.Context) { log.Printf("Error creating audit log: %v", err) } - // Redirect back to settings page with success message - h.handleSettingsWithSuccess(c, "Notification service deleted successfully.") + // Redirect back to notifications page with success message + h.handleNotificationsWithSuccess(c, "Notification service deleted successfully.") } // HandleTestNotification handles POST /settings/notifications/test @@ -502,8 +502,14 @@ func (h *Handlers) checkPermission(c *gin.Context, permission string) bool { return user.HasPermission(permission) } -// handleSettingsWithError renders the settings page with an error message -func (h *Handlers) handleSettingsWithError(c *gin.Context, errorMessage string) { +// HandleNotificationsPage handles GET /admin/settings/notifications +func (h *Handlers) HandleNotificationsPage(c *gin.Context) { + // Check if the user has permission to view settings + if !h.checkPermission(c, "system.settings") { + c.Redirect(http.StatusFound, "/dashboard") + return + } + var notificationServices []db.NotificationService if err := h.DB.Find(¬ificationServices).Error; err != nil { log.Printf("Error fetching notification services: %v", err) @@ -528,17 +534,51 @@ func (h *Handlers) handleSettingsWithError(c *gin.Context, errorMessage string) }) } - data := components.SettingsData{ + data := components.SettingsNotificationsData{ + NotificationServices: componentServices, + } + + ctx := h.CreateTemplateContext(c) + components.Notifications(ctx, data).Render(ctx, c.Writer) +} + +// handleNotificationsWithError renders the notifications page with an error message +func (h *Handlers) handleNotificationsWithError(c *gin.Context, errorMessage string) { + var notificationServices []db.NotificationService + if err := h.DB.Find(¬ificationServices).Error; err != nil { + log.Printf("Error fetching notification services: %v", err) + } + + // Convert to components.NotificationService + var componentServices []components.NotificationService + for _, service := range notificationServices { + componentServices = append(componentServices, components.NotificationService{ + ID: service.ID, + Name: service.Name, + Type: service.Type, + IsEnabled: service.IsEnabled, + Config: service.Config, + Description: service.Description, + EventTriggers: service.EventTriggers, + PayloadTemplate: service.PayloadTemplate, + SecretKey: service.SecretKey, + RetryPolicy: service.RetryPolicy, + SuccessCount: service.SuccessCount, + FailureCount: service.FailureCount, + }) + } + + data := components.SettingsNotificationsData{ NotificationServices: componentServices, ErrorMessage: errorMessage, } ctx := h.CreateTemplateContext(c) - components.Settings(ctx, data).Render(ctx, c.Writer) + components.Notifications(ctx, data).Render(ctx, c.Writer) } -// handleSettingsWithSuccess renders the settings page with a success message -func (h *Handlers) handleSettingsWithSuccess(c *gin.Context, successMessage string) { +// handleNotificationsWithSuccess renders the notifications page with a success message +func (h *Handlers) handleNotificationsWithSuccess(c *gin.Context, successMessage string) { var notificationServices []db.NotificationService if err := h.DB.Find(¬ificationServices).Error; err != nil { log.Printf("Error fetching notification services: %v", err) @@ -563,11 +603,11 @@ func (h *Handlers) handleSettingsWithSuccess(c *gin.Context, successMessage stri }) } - data := components.SettingsData{ + data := components.SettingsNotificationsData{ NotificationServices: componentServices, SuccessMessage: successMessage, } ctx := h.CreateTemplateContext(c) - components.Settings(ctx, data).Render(ctx, c.Writer) + components.Notifications(ctx, data).Render(ctx, c.Writer) }