mirror of
https://github.com/StarFleetCPTN/GoMFT.git
synced 2026-09-08 15:41:20 +02:00
- Replaced icons with images and updated text labels in the layout template for better clarity. - Enhanced mobile menu structure and added logo for consistent branding. - Removed deprecated notification form and dialog components to streamline the codebase. - Improved notification handling in the backend, ensuring correct URL formatting and payload structure for notifications. - Added debug logging for notification requests to facilitate troubleshooting.
1584 lines
52 KiB
Go
1584 lines
52 KiB
Go
package handlers
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"crypto/hmac"
|
|
"crypto/sha256"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/starfleetcptn/gomft/components"
|
|
"github.com/starfleetcptn/gomft/components/notifications/form"
|
|
"github.com/starfleetcptn/gomft/components/notifications/list"
|
|
"github.com/starfleetcptn/gomft/components/notifications/types"
|
|
"github.com/starfleetcptn/gomft/internal/db"
|
|
)
|
|
|
|
// HandleSettings handles GET /settings
|
|
func (h *Handlers) HandleSettings(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)
|
|
}
|
|
|
|
// 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.GetIsEnabled(), // Use getter
|
|
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.SettingsData{
|
|
NotificationServices: componentServices,
|
|
}
|
|
|
|
ctx := h.CreateTemplateContext(c)
|
|
components.Settings(ctx, data).Render(ctx, c.Writer)
|
|
}
|
|
|
|
// 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") {
|
|
c.Redirect(http.StatusFound, "/dashboard")
|
|
return
|
|
}
|
|
|
|
// Parse form data
|
|
name := c.PostForm("name")
|
|
serviceType := c.PostForm("type")
|
|
description := c.PostForm("description")
|
|
isEnabled := c.PostForm("is_enabled") == "on"
|
|
// Read event triggers directly from the form array
|
|
eventTriggers := c.PostFormArray("event_triggers[]")
|
|
|
|
// Validate required fields
|
|
if name == "" || serviceType == "" {
|
|
// Return to notifications page with error message
|
|
h.handleNotificationsWithError(c, "Name and type are required fields.")
|
|
return
|
|
}
|
|
|
|
// Create config map based on service type
|
|
config := make(map[string]string)
|
|
// eventTriggers slice is now populated above
|
|
fmt.Println("serviceType", serviceType)
|
|
switch serviceType {
|
|
case "email":
|
|
config["smtp_host"] = c.PostForm("smtp_host")
|
|
config["smtp_port"] = c.PostForm("smtp_port")
|
|
config["smtp_username"] = c.PostForm("smtp_username")
|
|
config["smtp_password"] = c.PostForm("smtp_password")
|
|
config["from_email"] = c.PostForm("from_email")
|
|
|
|
case "pushbullet":
|
|
config["api_key"] = c.PostForm("pushbullet_api_key")
|
|
config["device_iden"] = c.PostForm("pushbullet_device_iden")
|
|
config["title_template"] = c.PostForm("pushbullet_title_template")
|
|
config["body_template"] = c.PostForm("pushbullet_body_template")
|
|
|
|
// Event triggers are handled above
|
|
|
|
// Validate required fields
|
|
if config["api_key"] == "" {
|
|
h.handleNotificationsWithError(c, "Pushbullet API Key is required.")
|
|
return
|
|
}
|
|
|
|
// Create new notification service
|
|
service := db.NotificationService{
|
|
Name: name,
|
|
Type: serviceType,
|
|
// IsEnabled will be set using the helper method below
|
|
Config: config,
|
|
Description: description,
|
|
EventTriggers: eventTriggers,
|
|
CreatedBy: c.GetUint("userID"),
|
|
}
|
|
service.SetIsEnabled(isEnabled) // Use helper method
|
|
|
|
// Save to database
|
|
if err := h.DB.Create(&service).Error; err != nil {
|
|
log.Printf("Error creating notification service: %v", err)
|
|
h.handleNotificationsWithError(c, "Failed to create notification service: "+err.Error())
|
|
return
|
|
}
|
|
|
|
// Create audit log
|
|
auditDetails := map[string]interface{}{
|
|
"name": service.Name,
|
|
"type": service.Type,
|
|
"is_enabled": service.GetIsEnabled(), // Use getter
|
|
"description": service.Description,
|
|
"event_triggers": eventTriggers,
|
|
}
|
|
|
|
auditLog := db.AuditLog{
|
|
Action: "create",
|
|
EntityType: "notification_service",
|
|
EntityID: service.ID,
|
|
UserID: c.GetUint("userID"),
|
|
Details: auditDetails,
|
|
}
|
|
|
|
if err := h.DB.Create(&auditLog).Error; err != nil {
|
|
log.Printf("Error creating audit log: %v", err)
|
|
}
|
|
|
|
// Redirect back to notifications page with success message
|
|
h.handleNotificationsWithSuccess(c, "Pushbullet notification service created successfully.")
|
|
return
|
|
|
|
case "ntfy":
|
|
config["server"] = c.PostForm("ntfy_server")
|
|
config["topic"] = c.PostForm("ntfy_topic")
|
|
config["priority"] = c.PostForm("ntfy_priority")
|
|
config["username"] = c.PostForm("ntfy_username")
|
|
config["password"] = c.PostForm("ntfy_password")
|
|
config["title"] = c.PostForm("ntfy_title")
|
|
|
|
// Validate required fields
|
|
if config["topic"] == "" {
|
|
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "Ntfy Topic is required"})
|
|
return
|
|
}
|
|
|
|
// Set default server if not provided
|
|
if config["server"] == "" {
|
|
config["server"] = "https://ntfy.sh"
|
|
}
|
|
|
|
// Create the URL for the notification - no need to include topic in the URL
|
|
ntfyURL := strings.TrimRight(config["server"], "/")
|
|
|
|
// Create the notification data
|
|
ntfyData := map[string]interface{}{
|
|
"topic": config["topic"],
|
|
"title": "GoMFT Test Notification",
|
|
"message": "This is a test notification from GoMFT",
|
|
}
|
|
|
|
// Add priority if provided
|
|
if config["priority"] != "" {
|
|
priority, err := strconv.Atoi(config["priority"])
|
|
if err == nil {
|
|
ntfyData["priority"] = priority
|
|
} else {
|
|
ntfyData["priority"] = config["priority"]
|
|
}
|
|
}
|
|
|
|
// Add title if provided and not empty
|
|
if config["title"] != "" {
|
|
ntfyData["title"] = config["title"]
|
|
}
|
|
|
|
// Marshal to JSON
|
|
jsonData, err := json.Marshal(ntfyData)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "Failed to prepare test notification: " + err.Error()})
|
|
return
|
|
}
|
|
|
|
// Create request
|
|
req, err := http.NewRequest("POST", ntfyURL, bytes.NewBuffer(jsonData))
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "Failed to create request: " + err.Error()})
|
|
return
|
|
}
|
|
|
|
// Set the Content-Type header
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
// Add authentication if provided
|
|
if config["username"] != "" && config["password"] != "" {
|
|
req.SetBasicAuth(config["username"], config["password"])
|
|
}
|
|
|
|
// Debug info
|
|
fmt.Printf("ntfy URL: %s\n", ntfyURL)
|
|
fmt.Printf("ntfy payload: %s\n", string(jsonData))
|
|
|
|
// Send the request
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "Failed to send test notification: " + err.Error()})
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
// Read response body for error details if needed
|
|
respBody, _ := io.ReadAll(resp.Body)
|
|
|
|
fmt.Printf("ntfy response status: %s\n", resp.Status)
|
|
fmt.Printf("ntfy response body: %s\n", string(respBody))
|
|
|
|
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
|
|
c.JSON(http.StatusOK, gin.H{"success": true, "message": "Test notification sent successfully"})
|
|
} else {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "Test notification returned error: " + resp.Status + " - " + string(respBody)})
|
|
}
|
|
return
|
|
|
|
case "gotify":
|
|
config["url"] = c.PostForm("gotify_url")
|
|
config["token"] = c.PostForm("gotify_token")
|
|
config["priority"] = c.PostForm("gotify_priority")
|
|
config["title_template"] = c.PostForm("gotify_title_template")
|
|
config["message_template"] = c.PostForm("gotify_message_template")
|
|
|
|
// Event triggers are handled above
|
|
|
|
// Validate required fields
|
|
if config["url"] == "" || config["token"] == "" {
|
|
h.handleNotificationsWithError(c, "Gotify Server URL and Application Token are required.")
|
|
return
|
|
}
|
|
|
|
// Create new notification service
|
|
service := db.NotificationService{
|
|
Name: name,
|
|
Type: serviceType,
|
|
// IsEnabled will be set using the helper method below
|
|
Config: config,
|
|
Description: description,
|
|
EventTriggers: eventTriggers,
|
|
CreatedBy: c.GetUint("userID"),
|
|
}
|
|
service.SetIsEnabled(isEnabled) // Use helper method
|
|
|
|
// Save to database
|
|
if err := h.DB.Create(&service).Error; err != nil {
|
|
log.Printf("Error creating notification service: %v", err)
|
|
h.handleNotificationsWithError(c, "Failed to create notification service: "+err.Error())
|
|
return
|
|
}
|
|
|
|
// Create audit log
|
|
auditDetails := map[string]interface{}{
|
|
"name": service.Name,
|
|
"type": service.Type,
|
|
"is_enabled": service.GetIsEnabled(), // Use getter
|
|
"description": service.Description,
|
|
"event_triggers": eventTriggers,
|
|
}
|
|
|
|
auditLog := db.AuditLog{
|
|
Action: "create",
|
|
EntityType: "notification_service",
|
|
EntityID: service.ID,
|
|
UserID: c.GetUint("userID"),
|
|
Details: auditDetails,
|
|
}
|
|
|
|
if err := h.DB.Create(&auditLog).Error; err != nil {
|
|
log.Printf("Error creating audit log: %v", err)
|
|
}
|
|
|
|
// Redirect back to notifications page with success message
|
|
h.handleNotificationsWithSuccess(c, "Gotify notification service created successfully.")
|
|
return
|
|
|
|
case "pushover":
|
|
config["app_token"] = c.PostForm("pushover_app_token")
|
|
config["user_key"] = c.PostForm("pushover_user_key")
|
|
config["device"] = c.PostForm("pushover_device")
|
|
config["priority"] = c.PostForm("pushover_priority")
|
|
config["sound"] = c.PostForm("pushover_sound")
|
|
config["title_template"] = c.PostForm("pushover_title_template")
|
|
config["message_template"] = c.PostForm("pushover_message_template")
|
|
|
|
// Event triggers are handled above
|
|
|
|
// Validate required fields
|
|
if config["app_token"] == "" || config["user_key"] == "" {
|
|
h.handleNotificationsWithError(c, "Pushover API Token and User Key are required.")
|
|
return
|
|
}
|
|
|
|
// Create new notification service
|
|
service := db.NotificationService{
|
|
Name: name,
|
|
Type: serviceType,
|
|
// IsEnabled will be set using the helper method below
|
|
Config: config,
|
|
Description: description,
|
|
EventTriggers: eventTriggers,
|
|
CreatedBy: c.GetUint("userID"),
|
|
}
|
|
service.SetIsEnabled(isEnabled) // Use helper method
|
|
|
|
// Save to database
|
|
if err := h.DB.Create(&service).Error; err != nil {
|
|
log.Printf("Error creating notification service: %v", err)
|
|
h.handleNotificationsWithError(c, "Failed to create notification service: "+err.Error())
|
|
return
|
|
}
|
|
|
|
// Create audit log
|
|
auditDetails := map[string]interface{}{
|
|
"name": service.Name,
|
|
"type": service.Type,
|
|
"is_enabled": service.GetIsEnabled(), // Use getter
|
|
"description": service.Description,
|
|
"event_triggers": eventTriggers,
|
|
}
|
|
|
|
auditLog := db.AuditLog{
|
|
Action: "create",
|
|
EntityType: "notification_service",
|
|
EntityID: service.ID,
|
|
UserID: c.GetUint("userID"),
|
|
Details: auditDetails,
|
|
}
|
|
|
|
if err := h.DB.Create(&auditLog).Error; err != nil {
|
|
log.Printf("Error creating audit log: %v", err)
|
|
}
|
|
|
|
// Redirect back to notifications page with success message
|
|
h.handleNotificationsWithSuccess(c, "Pushover notification service created successfully.")
|
|
return
|
|
|
|
case "webhook":
|
|
config["webhook_url"] = c.PostForm("webhook_url")
|
|
config["method"] = c.PostForm("method")
|
|
config["headers"] = c.PostForm("headers")
|
|
|
|
// Add the new webhook fields
|
|
// Event triggers are handled above
|
|
|
|
// Create new notification service with additional fields
|
|
service := db.NotificationService{
|
|
Name: name,
|
|
Type: serviceType,
|
|
// IsEnabled will be set using the helper method below
|
|
Config: config,
|
|
Description: description,
|
|
EventTriggers: eventTriggers,
|
|
PayloadTemplate: c.PostForm("payload_template"),
|
|
SecretKey: c.PostForm("secret_key"),
|
|
RetryPolicy: c.PostForm("retry_policy"),
|
|
CreatedBy: c.GetUint("userID"),
|
|
}
|
|
service.SetIsEnabled(isEnabled) // Use helper method
|
|
|
|
// Save to database
|
|
if err := h.DB.Create(&service).Error; err != nil {
|
|
log.Printf("Error creating notification service: %v", err)
|
|
h.handleNotificationsWithError(c, "Failed to create notification service: "+err.Error())
|
|
return
|
|
}
|
|
|
|
// Create audit log
|
|
auditDetails := map[string]interface{}{
|
|
"name": service.Name,
|
|
"type": service.Type,
|
|
"is_enabled": service.GetIsEnabled(), // Use getter
|
|
"description": service.Description,
|
|
"event_triggers": eventTriggers,
|
|
"retry_policy": service.RetryPolicy,
|
|
"has_secret_key": service.SecretKey != "",
|
|
}
|
|
|
|
auditLog := db.AuditLog{
|
|
Action: "create",
|
|
EntityType: "notification_service",
|
|
EntityID: service.ID,
|
|
UserID: c.GetUint("userID"),
|
|
Details: auditDetails,
|
|
}
|
|
|
|
if err := h.DB.Create(&auditLog).Error; err != nil {
|
|
log.Printf("Error creating audit log: %v", err)
|
|
}
|
|
|
|
// Redirect back to notifications page with success message
|
|
h.handleNotificationsWithSuccess(c, "Notification service created successfully.")
|
|
return
|
|
default:
|
|
h.handleNotificationsWithError(c, "Invalid notification service type.")
|
|
return
|
|
}
|
|
|
|
// Create new notification service
|
|
service := db.NotificationService{
|
|
Name: name,
|
|
Type: serviceType,
|
|
// IsEnabled will be set using the helper method below
|
|
Config: config,
|
|
Description: description,
|
|
CreatedBy: c.GetUint("userID"),
|
|
}
|
|
service.SetIsEnabled(isEnabled) // Use helper method
|
|
|
|
// Save to database
|
|
if err := h.DB.Create(&service).Error; err != nil {
|
|
log.Printf("Error creating notification service: %v", err)
|
|
h.handleNotificationsWithError(c, "Failed to create notification service: "+err.Error())
|
|
return
|
|
}
|
|
|
|
// Create audit log
|
|
auditDetails := map[string]interface{}{
|
|
"name": service.Name,
|
|
"type": service.Type,
|
|
"is_enabled": service.GetIsEnabled(), // Use getter
|
|
"description": service.Description,
|
|
}
|
|
|
|
auditLog := db.AuditLog{
|
|
Action: "create",
|
|
EntityType: "notification_service",
|
|
EntityID: service.ID,
|
|
UserID: c.GetUint("userID"),
|
|
Details: auditDetails,
|
|
}
|
|
|
|
if err := h.DB.Create(&auditLog).Error; err != nil {
|
|
log.Printf("Error creating audit log: %v", err)
|
|
}
|
|
|
|
// Redirect back to notifications page with success message
|
|
h.handleNotificationsWithSuccess(c, "Notification service created successfully.")
|
|
}
|
|
|
|
// 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") {
|
|
c.Redirect(http.StatusFound, "/dashboard")
|
|
return
|
|
}
|
|
|
|
// Get service ID from path
|
|
serviceID, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
|
if err != nil {
|
|
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.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.handleNotificationsWithError(c, "Failed to delete notification service: "+err.Error())
|
|
return
|
|
}
|
|
|
|
// Create audit log
|
|
auditDetails := map[string]interface{}{
|
|
"name": service.Name,
|
|
"type": service.Type,
|
|
"is_enabled": service.IsEnabled,
|
|
"description": service.Description,
|
|
}
|
|
|
|
auditLog := db.AuditLog{
|
|
Action: "delete",
|
|
EntityType: "notification_service",
|
|
EntityID: service.ID,
|
|
UserID: c.GetUint("userID"),
|
|
Details: auditDetails,
|
|
}
|
|
|
|
if err := h.DB.Create(&auditLog).Error; err != nil {
|
|
log.Printf("Error creating audit log: %v", err)
|
|
}
|
|
|
|
// Redirect back to notifications page with success message
|
|
h.handleNotificationsWithSuccess(c, "Notification service deleted successfully.")
|
|
}
|
|
|
|
// HandleTestNotification handles POST /settings/notifications/test
|
|
// This endpoint tests a notification configuration without saving it
|
|
func (h *Handlers) HandleTestNotification(c *gin.Context) {
|
|
// Check that the user has permissions to view settings
|
|
if !h.checkPermission(c, "system.settings") {
|
|
c.JSON(http.StatusForbidden, gin.H{"success": false, "message": "You do not have permission to manage settings"})
|
|
return
|
|
}
|
|
|
|
// Get notification service configuration
|
|
serviceType := c.PostForm("type")
|
|
config := make(map[string]string)
|
|
|
|
// print the form data
|
|
fmt.Println("serviceType", serviceType)
|
|
|
|
// Process based on service type
|
|
switch serviceType {
|
|
case "email":
|
|
config["smtp_host"] = c.PostForm("smtp_host")
|
|
config["smtp_port"] = c.PostForm("smtp_port")
|
|
config["smtp_username"] = c.PostForm("smtp_username")
|
|
config["smtp_password"] = c.PostForm("smtp_password")
|
|
config["from_email"] = c.PostForm("from_email")
|
|
config["to_email"] = c.PostForm("to_email")
|
|
config["use_tls"] = c.PostForm("use_tls")
|
|
|
|
// Validate required fields
|
|
if config["smtp_host"] == "" || config["smtp_port"] == "" || config["from_email"] == "" || config["to_email"] == "" {
|
|
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "SMTP Host, Port, From Email, and To Email are required"})
|
|
return
|
|
}
|
|
|
|
// For email, we'll simply simulate a successful test
|
|
// In a real implementation, you would want to actually try to send an email
|
|
c.JSON(http.StatusOK, gin.H{"success": true, "message": "Test email notification would be sent successfully"})
|
|
return
|
|
|
|
case "webhook":
|
|
config["webhook_url"] = c.PostForm("webhook_url")
|
|
config["webhook_method"] = c.PostForm("webhook_method")
|
|
config["webhook_headers"] = c.PostForm("webhook_headers")
|
|
config["webhook_body_template"] = c.PostForm("webhook_body_template")
|
|
|
|
// Validate required fields
|
|
if config["webhook_url"] == "" {
|
|
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "Webhook URL is required"})
|
|
return
|
|
}
|
|
|
|
// Send a test webhook
|
|
// This is a simplified example - in a real implementation, you would want to use the templates
|
|
// and properly format the request based on the configured method, headers, etc.
|
|
resp, err := http.Post(config["webhook_url"], "application/json", bytes.NewBuffer([]byte(`{"message":"This is a test notification from GoMFT"}`)))
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "Failed to send test webhook: " + err.Error()})
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
|
|
c.JSON(http.StatusOK, gin.H{"success": true, "message": "Test webhook sent successfully"})
|
|
} else {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "Test webhook returned non-success status code: " + resp.Status})
|
|
}
|
|
return
|
|
|
|
case "pushbullet":
|
|
config["api_key"] = c.PostForm("pushbullet_api_key")
|
|
config["device_iden"] = c.PostForm("pushbullet_device_iden")
|
|
|
|
// Validate required fields
|
|
if config["api_key"] == "" {
|
|
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "Pushbullet API Key is required"})
|
|
return
|
|
}
|
|
|
|
// Create a test push request
|
|
pushURL := "https://api.pushbullet.com/v2/pushes"
|
|
pushData := map[string]interface{}{
|
|
"type": "note",
|
|
"title": "GoMFT Test Notification",
|
|
"body": "This is a test notification from GoMFT",
|
|
}
|
|
|
|
// Add device identifier if provided
|
|
if config["device_iden"] != "" {
|
|
pushData["device_iden"] = config["device_iden"]
|
|
}
|
|
|
|
jsonData, err := json.Marshal(pushData)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "Failed to prepare test push: " + err.Error()})
|
|
return
|
|
}
|
|
|
|
// Create the request
|
|
req, err := http.NewRequest("POST", pushURL, bytes.NewBuffer(jsonData))
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "Failed to create request: " + err.Error()})
|
|
return
|
|
}
|
|
|
|
// Set headers
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req.Header.Set("Access-Token", config["api_key"])
|
|
|
|
// Send the request
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "Failed to send test push: " + err.Error()})
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
// Read response body for error details if needed
|
|
respBody, _ := io.ReadAll(resp.Body)
|
|
|
|
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
|
|
c.JSON(http.StatusOK, gin.H{"success": true, "message": "Test push sent successfully"})
|
|
} else {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "Test push returned error: " + resp.Status + " - " + string(respBody)})
|
|
}
|
|
return
|
|
|
|
case "ntfy":
|
|
config["topic"] = c.PostForm("ntfy_topic")
|
|
config["server"] = c.PostForm("ntfy_server")
|
|
config["priority"] = c.PostForm("ntfy_priority")
|
|
config["title"] = c.PostForm("ntfy_title")
|
|
|
|
// Validate required fields
|
|
if config["topic"] == "" {
|
|
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "Ntfy Topic is required"})
|
|
return
|
|
}
|
|
|
|
// Set default server if not provided
|
|
if config["server"] == "" {
|
|
config["server"] = "https://ntfy.sh"
|
|
}
|
|
|
|
// Create the URL for the notification - no need to include topic in the URL
|
|
ntfyURL := strings.TrimRight(config["server"], "/")
|
|
|
|
// Create the notification data
|
|
ntfyData := map[string]interface{}{
|
|
"topic": config["topic"],
|
|
"title": "GoMFT Test Notification",
|
|
"message": "This is a test notification from GoMFT",
|
|
}
|
|
|
|
// Add priority if provided
|
|
if config["priority"] != "" {
|
|
priority, err := strconv.Atoi(config["priority"])
|
|
if err == nil {
|
|
ntfyData["priority"] = priority
|
|
} else {
|
|
ntfyData["priority"] = config["priority"]
|
|
}
|
|
}
|
|
|
|
// Add title if provided and not empty
|
|
if config["title"] != "" {
|
|
ntfyData["title"] = config["title"]
|
|
}
|
|
|
|
// Marshal to JSON
|
|
jsonData, err := json.Marshal(ntfyData)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "Failed to prepare test notification: " + err.Error()})
|
|
return
|
|
}
|
|
|
|
// Create request
|
|
req, err := http.NewRequest("POST", ntfyURL, bytes.NewBuffer(jsonData))
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "Failed to create request: " + err.Error()})
|
|
return
|
|
}
|
|
|
|
// Set the Content-Type header
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
// Add authentication if provided
|
|
if config["username"] != "" && config["password"] != "" {
|
|
req.SetBasicAuth(config["username"], config["password"])
|
|
}
|
|
|
|
// Debug info
|
|
fmt.Printf("ntfy URL: %s\n", ntfyURL)
|
|
fmt.Printf("ntfy payload: %s\n", string(jsonData))
|
|
|
|
// Send the request
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "Failed to send test notification: " + err.Error()})
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
// Read response body for error details if needed
|
|
respBody, _ := io.ReadAll(resp.Body)
|
|
|
|
fmt.Printf("ntfy response status: %s\n", resp.Status)
|
|
fmt.Printf("ntfy response body: %s\n", string(respBody))
|
|
|
|
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
|
|
c.JSON(http.StatusOK, gin.H{"success": true, "message": "Test notification sent successfully"})
|
|
} else {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "Test notification returned error: " + resp.Status + " - " + string(respBody)})
|
|
}
|
|
return
|
|
|
|
case "gotify":
|
|
config["url"] = c.PostForm("gotify_url")
|
|
config["token"] = c.PostForm("gotify_token")
|
|
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"})
|
|
return
|
|
}
|
|
|
|
// Create the URL for the notification
|
|
gotifyURL := fmt.Sprintf("%s/message", strings.TrimRight(config["url"], "/"))
|
|
|
|
// Set default values for any missing fields
|
|
title := "GoMFT Test Notification"
|
|
if config["title"] != "" {
|
|
title = config["title"]
|
|
}
|
|
|
|
priority := 5 // Default priority
|
|
if config["priority"] != "" {
|
|
priorityInt, err := strconv.Atoi(config["priority"])
|
|
if err == nil {
|
|
priority = priorityInt
|
|
}
|
|
}
|
|
|
|
// Create the notification data
|
|
gotifyData := map[string]interface{}{
|
|
"title": title,
|
|
"message": "This is a test notification from GoMFT",
|
|
"priority": priority,
|
|
}
|
|
|
|
jsonData, err := json.Marshal(gotifyData)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "Failed to prepare test notification: " + err.Error()})
|
|
return
|
|
}
|
|
|
|
// Create the request
|
|
req, err := http.NewRequest("POST", gotifyURL, bytes.NewBuffer(jsonData))
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "Failed to create request: " + err.Error()})
|
|
return
|
|
}
|
|
|
|
// Set headers
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req.Header.Set("X-Gotify-Key", config["token"])
|
|
|
|
// Send the request
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "Failed to send test notification: " + err.Error()})
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
// Read response body for error details if needed
|
|
respBody, _ := io.ReadAll(resp.Body)
|
|
|
|
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
|
|
c.JSON(http.StatusOK, gin.H{"success": true, "message": "Test notification sent successfully"})
|
|
} else {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "Test notification returned error: " + resp.Status + " - " + string(respBody)})
|
|
}
|
|
return
|
|
|
|
case "pushover":
|
|
config["app_token"] = c.PostForm("pushover_app_token")
|
|
config["user_key"] = c.PostForm("pushover_user_key")
|
|
config["device"] = c.PostForm("pushover_device")
|
|
config["priority"] = c.PostForm("pushover_priority")
|
|
config["sound"] = c.PostForm("pushover_sound")
|
|
|
|
// Validate required fields
|
|
if config["app_token"] == "" || config["user_key"] == "" {
|
|
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "Pushover API Token and User Key are required"})
|
|
return
|
|
}
|
|
|
|
// Create the URL for the notification
|
|
pushoverURL := "https://api.pushover.net/1/messages.json"
|
|
|
|
// Create the form data for Pushover
|
|
formData := url.Values{}
|
|
formData.Set("token", config["app_token"])
|
|
formData.Set("user", config["user_key"])
|
|
formData.Set("title", "GoMFT Test Notification")
|
|
formData.Set("message", "This is a test notification from GoMFT")
|
|
|
|
// Add optional fields if provided
|
|
if config["device"] != "" {
|
|
formData.Set("device", config["device"])
|
|
}
|
|
if config["priority"] != "" {
|
|
formData.Set("priority", config["priority"])
|
|
}
|
|
if config["sound"] != "" {
|
|
formData.Set("sound", config["sound"])
|
|
}
|
|
|
|
// Send the request
|
|
resp, err := http.PostForm(pushoverURL, formData)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": "Failed to send test notification: " + err.Error()})
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
// Read response body for error details if needed
|
|
respBody, _ := io.ReadAll(resp.Body)
|
|
var pushoverResp map[string]interface{}
|
|
json.Unmarshal(respBody, &pushoverResp)
|
|
|
|
if resp.StatusCode >= 200 && resp.StatusCode < 300 && pushoverResp["status"].(float64) == 1 {
|
|
c.JSON(http.StatusOK, gin.H{"success": true, "message": "Test notification sent successfully"})
|
|
} else {
|
|
errorMsg := "Test notification returned error"
|
|
if errStr, ok := pushoverResp["errors"]; ok {
|
|
errorMsg = errorMsg + ": " + fmt.Sprintf("%v", errStr)
|
|
}
|
|
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": errorMsg})
|
|
}
|
|
return
|
|
|
|
default:
|
|
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "Unknown service type: " + serviceType})
|
|
}
|
|
}
|
|
|
|
// replacePlaceholders replaces placeholders in templates with sample values
|
|
func replacePlaceholders(template string) string {
|
|
// Replace common placeholders
|
|
replacements := map[string]string{
|
|
"{{job.id}}": "sample-job-123",
|
|
"{{job.name}}": "Test Job",
|
|
"{{job.status}}": "completed",
|
|
"{{job.message}}": "This is a test notification",
|
|
"{{job.event}}": "job_complete",
|
|
"{{job.started_at}}": time.Now().Add(-5 * time.Minute).Format(time.RFC3339),
|
|
"{{job.completed_at}}": time.Now().Format(time.RFC3339),
|
|
"{{job.duration_seconds}}": "300",
|
|
"{{job.config_id}}": "config-456",
|
|
"{{job.config_name}}": "Test Config",
|
|
"{{job.transfer_bytes}}": "1024",
|
|
"{{job.file_count}}": "5",
|
|
"{{instance.id}}": "gomft-instance-1",
|
|
"{{instance.name}}": "GoMFT Test Instance",
|
|
"{{instance.version}}": "1.0.0",
|
|
"{{instance.environment}}": "testing",
|
|
"{{timestamp}}": time.Now().Format(time.RFC3339),
|
|
"{{notification.id}}": "test-notification",
|
|
}
|
|
|
|
result := template
|
|
for placeholder, value := range replacements {
|
|
result = strings.Replace(result, placeholder, value, -1)
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// sendTestPushbulletNotification sends a test Pushbullet notification
|
|
func sendTestPushbulletNotification(apiKey, deviceIden, title, body string) error {
|
|
const pushbulletAPI = "https://api.pushbullet.com/v2/pushes"
|
|
|
|
// Create payload
|
|
payload := map[string]interface{}{
|
|
"type": "note",
|
|
"title": title,
|
|
"body": body,
|
|
}
|
|
|
|
// Add device_iden if provided
|
|
if deviceIden != "" {
|
|
payload["device_iden"] = deviceIden
|
|
}
|
|
|
|
// Convert payload to JSON
|
|
jsonPayload, err := json.Marshal(payload)
|
|
if err != nil {
|
|
return fmt.Errorf("error creating JSON payload: %v", err)
|
|
}
|
|
|
|
// Create request
|
|
req, err := http.NewRequest("POST", pushbulletAPI, bytes.NewBuffer(jsonPayload))
|
|
if err != nil {
|
|
return fmt.Errorf("error creating request: %v", err)
|
|
}
|
|
|
|
// Set headers
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req.Header.Set("Access-Token", apiKey)
|
|
|
|
// Send request
|
|
client := &http.Client{Timeout: 10 * time.Second}
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return fmt.Errorf("error sending request: %v", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
// Check response
|
|
if resp.StatusCode >= 400 {
|
|
bodyBytes, _ := io.ReadAll(resp.Body)
|
|
return fmt.Errorf("pushbullet API error %d: %s", resp.StatusCode, string(bodyBytes))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// sendTestNtfyNotification sends a test Ntfy notification
|
|
func sendTestNtfyNotification(server, topic, title, message string, priority int, username, password string) error {
|
|
// Ensure server doesn't end with a slash
|
|
server = strings.TrimSuffix(server, "/")
|
|
|
|
// Build URL
|
|
ntfyURL := fmt.Sprintf("%s/%s", server, topic)
|
|
|
|
// Create request
|
|
req, err := http.NewRequest("POST", ntfyURL, bytes.NewBufferString(message))
|
|
if err != nil {
|
|
return fmt.Errorf("error creating request: %v", err)
|
|
}
|
|
|
|
// Set headers
|
|
req.Header.Set("Title", title)
|
|
req.Header.Set("Priority", strconv.Itoa(priority))
|
|
|
|
// Set authentication if provided
|
|
if username != "" && password != "" {
|
|
req.SetBasicAuth(username, password)
|
|
}
|
|
|
|
// Send request
|
|
client := &http.Client{Timeout: 10 * time.Second}
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return fmt.Errorf("error sending request: %v", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
// Check response
|
|
if resp.StatusCode >= 400 {
|
|
bodyBytes, _ := io.ReadAll(resp.Body)
|
|
return fmt.Errorf("ntfy API error %d: %s", resp.StatusCode, string(bodyBytes))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// generateSamplePayload creates a sample payload for testing
|
|
func generateSamplePayload(template string) string {
|
|
// If no template provided, use a default sample
|
|
if template == "" {
|
|
return `{
|
|
"event": "job_complete",
|
|
"job": {
|
|
"id": "sample-job-123",
|
|
"name": "Test Job",
|
|
"status": "completed",
|
|
"message": "This is a test notification",
|
|
"started_at": "` + time.Now().Add(-5*time.Minute).Format(time.RFC3339) + `",
|
|
"completed_at": "` + time.Now().Format(time.RFC3339) + `",
|
|
"duration_seconds": 300,
|
|
"config_id": "config-456",
|
|
"config_name": "Test Config",
|
|
"transfer_bytes": 1024,
|
|
"file_count": 5
|
|
},
|
|
"instance": {
|
|
"id": "gomft-instance-1",
|
|
"name": "GoMFT Test Instance",
|
|
"version": "1.0.0",
|
|
"environment": "testing"
|
|
},
|
|
"timestamp": "` + time.Now().Format(time.RFC3339) + `",
|
|
"notification_id": "test-notification"
|
|
}`
|
|
}
|
|
|
|
// Replace placeholders in the template with sample values
|
|
samplePayload := template
|
|
// Replace common placeholders
|
|
replacements := map[string]string{
|
|
"{{job.id}}": "sample-job-123",
|
|
"{{job.name}}": "Test Job",
|
|
"{{job.status}}": "completed",
|
|
"{{job.message}}": "This is a test notification",
|
|
"{{job.event}}": "job_complete",
|
|
"{{job.started_at}}": time.Now().Add(-5 * time.Minute).Format(time.RFC3339),
|
|
"{{job.completed_at}}": time.Now().Format(time.RFC3339),
|
|
"{{job.duration_seconds}}": "300",
|
|
"{{job.config_id}}": "config-456",
|
|
"{{job.config_name}}": "Test Config",
|
|
"{{job.transfer_bytes}}": "1024",
|
|
"{{job.file_count}}": "5",
|
|
"{{instance.id}}": "gomft-instance-1",
|
|
"{{instance.name}}": "GoMFT Test Instance",
|
|
"{{instance.version}}": "1.0.0",
|
|
"{{instance.environment}}": "testing",
|
|
"{{timestamp}}": time.Now().Format(time.RFC3339),
|
|
"{{notification.id}}": "test-notification",
|
|
}
|
|
|
|
for placeholder, value := range replacements {
|
|
samplePayload = strings.Replace(samplePayload, placeholder, value, -1)
|
|
}
|
|
|
|
return samplePayload
|
|
}
|
|
|
|
// sendTestWebhook sends a test webhook to the specified URL
|
|
func sendTestWebhook(config map[string]string, payload string, secretKey string) error {
|
|
webhookURL := config["webhook_url"]
|
|
method := config["method"]
|
|
if method == "" {
|
|
method = "POST"
|
|
}
|
|
|
|
// Create the request
|
|
req, err := http.NewRequest(method, webhookURL, bytes.NewBufferString(payload))
|
|
if err != nil {
|
|
return fmt.Errorf("error creating request: %v", err)
|
|
}
|
|
|
|
// Set default Content-Type if not specified
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
// Parse and set custom headers
|
|
if config["headers"] != "" {
|
|
var headers map[string]string
|
|
if err := json.Unmarshal([]byte(config["headers"]), &headers); err == nil {
|
|
for key, value := range headers {
|
|
req.Header.Set(key, value)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add signature if secret key is provided
|
|
if secretKey != "" {
|
|
signature := calculateSignature(payload, secretKey)
|
|
req.Header.Set("X-GoMFT-Signature", signature)
|
|
}
|
|
|
|
// Send the request
|
|
client := &http.Client{
|
|
Timeout: 10 * time.Second,
|
|
}
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return fmt.Errorf("error sending webhook: %v", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
// Check the response
|
|
if resp.StatusCode >= 400 {
|
|
body, _ := io.ReadAll(resp.Body)
|
|
return fmt.Errorf("webhook returned error %d: %s", resp.StatusCode, string(body))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// calculateSignature generates an HMAC signature for webhook payloads
|
|
func calculateSignature(payload string, secretKey string) string {
|
|
h := hmac.New(sha256.New, []byte(secretKey))
|
|
h.Write([]byte(payload))
|
|
return fmt.Sprintf("sha256=%x", h.Sum(nil))
|
|
}
|
|
|
|
// Helper function to check if user has a specific permission
|
|
func (h *Handlers) checkPermission(c *gin.Context, permission string) bool {
|
|
// If user is admin, they have all permissions
|
|
isAdmin, exists := c.Get("isAdmin")
|
|
if exists && isAdmin.(bool) {
|
|
return true
|
|
}
|
|
|
|
// Get user from context
|
|
userID := c.GetUint("userID")
|
|
if userID == 0 {
|
|
return false
|
|
}
|
|
|
|
// Check if user has the required permission
|
|
// h.DB.UserHasPermission undefined (type *db.DB has no field or method UserHasPermission)
|
|
// Load user with roles and check permission
|
|
var user db.User
|
|
if err := h.DB.Preload("Roles").First(&user, userID).Error; err != nil {
|
|
log.Printf("Error loading user: %v", err)
|
|
return false
|
|
}
|
|
|
|
return user.HasPermission(permission)
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
|
|
// Convert to components.NotificationService
|
|
var componentServices []types.NotificationServiceData // Use types.NotificationServiceData
|
|
for _, service := range notificationServices {
|
|
componentServices = append(componentServices, types.NotificationServiceData{ // Use types.NotificationServiceData
|
|
ID: service.ID,
|
|
Name: service.Name,
|
|
Type: service.Type,
|
|
IsEnabled: service.GetIsEnabled(), // Use getter
|
|
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 := types.SettingsNotificationsData{ // Use types.SettingsNotificationsData
|
|
NotificationServices: componentServices,
|
|
}
|
|
|
|
ctx := h.CreateTemplateContext(c)
|
|
list.List(ctx, data).Render(ctx, c.Writer) // Use list.List
|
|
}
|
|
|
|
// HandleNewNotificationPage handles GET /admin/settings/notifications/new
|
|
func (h *Handlers) HandleNewNotificationPage(c *gin.Context) {
|
|
// Check if the user has permission to view settings
|
|
if !h.checkPermission(c, "system.settings") {
|
|
c.Redirect(http.StatusFound, "/dashboard")
|
|
return
|
|
}
|
|
|
|
data := types.NotificationFormData{ // Use types.NotificationFormData
|
|
IsNew: true,
|
|
NotificationService: &struct {
|
|
ID uint
|
|
Name string
|
|
Description string
|
|
Type string
|
|
IsEnabled bool
|
|
EventTriggers []string
|
|
RetryPolicy string
|
|
WebhookURL string
|
|
Method string
|
|
Headers string
|
|
PayloadTemplate string
|
|
SecretKey string
|
|
PushbulletAPIKey string
|
|
PushbulletDeviceID string
|
|
PushbulletTitleTemplate string
|
|
PushbulletBodyTemplate string
|
|
NtfyServer string
|
|
NtfyTopic string
|
|
NtfyPriority string
|
|
NtfyUsername string
|
|
NtfyPassword string
|
|
NtfyTitleTemplate string
|
|
NtfyMessageTemplate string
|
|
GotifyURL string
|
|
GotifyToken string
|
|
GotifyPriority string
|
|
GotifyTitleTemplate string
|
|
GotifyMessageTemplate string
|
|
PushoverAPIToken string
|
|
PushoverUserKey string
|
|
PushoverDevice string
|
|
PushoverPriority string
|
|
PushoverSound string
|
|
PushoverTitleTemplate string
|
|
PushoverMessageTemplate string
|
|
}{},
|
|
}
|
|
|
|
ctx := h.CreateTemplateContext(c)
|
|
form.NotificationForm(ctx, data).Render(ctx, c.Writer) // Use form.NotificationForm
|
|
}
|
|
|
|
// HandleEditNotificationPage handles GET /admin/settings/notifications/:id/edit
|
|
func (h *Handlers) HandleEditNotificationPage(c *gin.Context) {
|
|
// Check if the user has permission to view settings
|
|
if !h.checkPermission(c, "system.settings") {
|
|
c.Redirect(http.StatusFound, "/dashboard")
|
|
return
|
|
}
|
|
|
|
// Get service ID from path
|
|
serviceID, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
|
if err != nil {
|
|
h.handleNotificationsWithError(c, "Invalid notification service ID.")
|
|
return
|
|
}
|
|
|
|
// Find the notification service
|
|
var service db.NotificationService
|
|
if err := h.DB.First(&service, serviceID).Error; err != nil {
|
|
h.handleNotificationsWithError(c, "Notification service not found.")
|
|
return
|
|
}
|
|
|
|
// Convert to NotificationFormData
|
|
data := types.NotificationFormData{ // Use types.NotificationFormData
|
|
IsNew: false,
|
|
NotificationService: &struct {
|
|
ID uint
|
|
Name string
|
|
Description string
|
|
Type string
|
|
IsEnabled bool
|
|
EventTriggers []string
|
|
RetryPolicy string
|
|
WebhookURL string
|
|
Method string
|
|
Headers string
|
|
PayloadTemplate string
|
|
SecretKey string
|
|
PushbulletAPIKey string
|
|
PushbulletDeviceID string
|
|
PushbulletTitleTemplate string
|
|
PushbulletBodyTemplate string
|
|
NtfyServer string
|
|
NtfyTopic string
|
|
NtfyPriority string
|
|
NtfyUsername string
|
|
NtfyPassword string
|
|
NtfyTitleTemplate string
|
|
NtfyMessageTemplate string
|
|
GotifyURL string
|
|
GotifyToken string
|
|
GotifyPriority string
|
|
GotifyTitleTemplate string
|
|
GotifyMessageTemplate string
|
|
PushoverAPIToken string
|
|
PushoverUserKey string
|
|
PushoverDevice string
|
|
PushoverPriority string
|
|
PushoverSound string
|
|
PushoverTitleTemplate string
|
|
PushoverMessageTemplate string
|
|
}{
|
|
ID: service.ID,
|
|
Name: service.Name,
|
|
Description: service.Description,
|
|
Type: service.Type,
|
|
IsEnabled: service.GetIsEnabled(), // Use getter
|
|
EventTriggers: service.EventTriggers,
|
|
RetryPolicy: service.RetryPolicy,
|
|
PayloadTemplate: service.PayloadTemplate,
|
|
SecretKey: service.SecretKey,
|
|
WebhookURL: service.Config["webhook_url"],
|
|
Method: service.Config["method"],
|
|
Headers: service.Config["headers"],
|
|
PushbulletAPIKey: service.Config["api_key"],
|
|
PushbulletDeviceID: service.Config["device_iden"],
|
|
PushbulletTitleTemplate: service.Config["title_template"],
|
|
PushbulletBodyTemplate: service.Config["body_template"],
|
|
NtfyServer: service.Config["server"],
|
|
NtfyTopic: service.Config["topic"],
|
|
NtfyPriority: service.Config["priority"],
|
|
NtfyUsername: service.Config["username"],
|
|
NtfyPassword: service.Config["password"],
|
|
NtfyTitleTemplate: service.Config["title_template"],
|
|
NtfyMessageTemplate: service.Config["message_template"],
|
|
GotifyURL: service.Config["url"],
|
|
GotifyToken: service.Config["token"],
|
|
GotifyPriority: service.Config["priority"],
|
|
GotifyTitleTemplate: service.Config["title_template"],
|
|
GotifyMessageTemplate: service.Config["message_template"],
|
|
PushoverAPIToken: service.Config["app_token"],
|
|
PushoverUserKey: service.Config["user_key"],
|
|
PushoverDevice: service.Config["device"],
|
|
PushoverPriority: service.Config["priority"],
|
|
PushoverSound: service.Config["sound"],
|
|
PushoverTitleTemplate: service.Config["title_template"],
|
|
PushoverMessageTemplate: service.Config["message_template"],
|
|
},
|
|
}
|
|
|
|
// Add type-specific fields
|
|
if service.Type == "webhook" {
|
|
data.NotificationService.WebhookURL = service.Config["webhook_url"]
|
|
data.NotificationService.Method = service.Config["method"]
|
|
data.NotificationService.Headers = service.Config["headers"]
|
|
}
|
|
|
|
ctx := h.CreateTemplateContext(c)
|
|
form.NotificationForm(ctx, data).Render(ctx, c.Writer) // Use form.NotificationForm
|
|
}
|
|
|
|
// HandleUpdateNotificationService handles PUT /admin/settings/notifications/:id
|
|
func (h *Handlers) HandleUpdateNotificationService(c *gin.Context) {
|
|
// Check if the user has permission to manage settings
|
|
if !h.checkPermission(c, "system.settings") {
|
|
c.Redirect(http.StatusFound, "/dashboard")
|
|
return
|
|
}
|
|
|
|
// Get service ID from path
|
|
serviceID, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
|
if err != nil {
|
|
h.handleNotificationsWithError(c, "Invalid notification service ID.")
|
|
return
|
|
}
|
|
|
|
// Find the notification service
|
|
var service db.NotificationService
|
|
if err := h.DB.First(&service, serviceID).Error; err != nil {
|
|
h.handleNotificationsWithError(c, "Notification service not found.")
|
|
return
|
|
}
|
|
|
|
// --- DEBUG: Print received form data ---
|
|
log.Println("--- Received POST Form Data (Update Notification) ---")
|
|
if err := c.Request.ParseForm(); err == nil {
|
|
postData := c.Request.PostForm
|
|
if len(postData) > 0 {
|
|
for key, values := range postData {
|
|
log.Printf(" %s: %v\n", key, values)
|
|
}
|
|
} else {
|
|
log.Println(" (No POST form data found or parsed)")
|
|
}
|
|
} else {
|
|
log.Printf(" Error parsing form: %v\n", err)
|
|
}
|
|
log.Println("-------------------------------------------------")
|
|
// --- END DEBUG ---
|
|
|
|
// Parse form data
|
|
name := c.PostForm("name")
|
|
serviceType := c.PostForm("type")
|
|
description := c.PostForm("description")
|
|
|
|
// Correctly handle boolean checkbox with hidden input
|
|
isEnabled := false // Default to false
|
|
if err := c.Request.ParseForm(); err == nil { // Ensure form is parsed
|
|
isEnabledValues := c.Request.PostForm["is_enabled"] // Get slice of values
|
|
for _, v := range isEnabledValues {
|
|
if v == "true" {
|
|
isEnabled = true // Checkbox was checked if "true" is present
|
|
break
|
|
}
|
|
}
|
|
} else {
|
|
log.Printf("Error parsing form during update: %v", err)
|
|
// Decide if this is a fatal error or if we can proceed assuming false
|
|
// For now, we proceed with isEnabled = false
|
|
}
|
|
|
|
eventTriggers := c.PostFormArray("event_triggers[]")
|
|
|
|
// Validate required fields
|
|
if name == "" || serviceType == "" {
|
|
h.handleNotificationsWithError(c, "Name and type are required fields.")
|
|
return
|
|
}
|
|
|
|
// Update basic fields
|
|
service.Name = name
|
|
service.Type = serviceType
|
|
service.Description = description
|
|
service.EventTriggers = eventTriggers
|
|
service.SetIsEnabled(isEnabled) // Use the correctly determined boolean
|
|
|
|
// Update type-specific fields based on service type
|
|
switch serviceType {
|
|
case "webhook":
|
|
// Update webhook-specific fields
|
|
service.Config["webhook_url"] = c.PostForm("webhook_url")
|
|
service.Config["method"] = c.PostForm("method")
|
|
service.Config["headers"] = c.PostForm("headers")
|
|
service.PayloadTemplate = c.PostForm("payload_template")
|
|
service.SecretKey = c.PostForm("secret_key")
|
|
service.RetryPolicy = c.PostForm("retry_policy")
|
|
|
|
// Event triggers are handled above
|
|
|
|
case "pushbullet":
|
|
// Update Pushbullet-specific fields
|
|
service.Config["api_key"] = c.PostForm("pushbullet_api_key")
|
|
service.Config["device_iden"] = c.PostForm("pushbullet_device_iden")
|
|
service.Config["title_template"] = c.PostForm("pushbullet_title_template")
|
|
service.Config["body_template"] = c.PostForm("pushbullet_body_template")
|
|
|
|
// Event triggers are handled above
|
|
|
|
case "ntfy":
|
|
// Update Ntfy-specific fields
|
|
service.Config["server"] = c.PostForm("ntfy_server")
|
|
service.Config["topic"] = c.PostForm("ntfy_topic")
|
|
service.Config["priority"] = c.PostForm("ntfy_priority")
|
|
service.Config["username"] = c.PostForm("ntfy_username")
|
|
service.Config["password"] = c.PostForm("ntfy_password")
|
|
service.Config["title_template"] = c.PostForm("ntfy_title_template")
|
|
service.Config["message_template"] = c.PostForm("ntfy_message_template")
|
|
|
|
// Event triggers are handled above
|
|
|
|
case "gotify":
|
|
// Update Gotify-specific fields
|
|
service.Config["url"] = c.PostForm("gotify_url")
|
|
service.Config["token"] = c.PostForm("gotify_token")
|
|
service.Config["priority"] = c.PostForm("gotify_priority")
|
|
service.Config["title_template"] = c.PostForm("gotify_title_template")
|
|
service.Config["message_template"] = c.PostForm("gotify_message_template")
|
|
|
|
// Event triggers are handled above
|
|
|
|
case "pushover":
|
|
// Update Pushover-specific fields
|
|
service.Config["app_token"] = c.PostForm("pushover_app_token")
|
|
service.Config["user_key"] = c.PostForm("pushover_user_key")
|
|
service.Config["device"] = c.PostForm("pushover_device")
|
|
service.Config["priority"] = c.PostForm("pushover_priority")
|
|
service.Config["sound"] = c.PostForm("pushover_sound")
|
|
service.Config["title_template"] = c.PostForm("pushover_title_template")
|
|
service.Config["message_template"] = c.PostForm("pushover_message_template")
|
|
|
|
// Event triggers are handled above
|
|
}
|
|
|
|
// Save to database
|
|
if err := h.DB.Save(&service).Error; err != nil {
|
|
log.Printf("Error updating notification service: %v", err)
|
|
h.handleNotificationsWithError(c, "Failed to update notification service: "+err.Error())
|
|
return
|
|
}
|
|
|
|
// Create audit log
|
|
auditDetails := map[string]interface{}{
|
|
"name": service.Name,
|
|
"type": service.Type,
|
|
"is_enabled": service.GetIsEnabled(), // Use getter
|
|
"description": service.Description,
|
|
"event_triggers": service.EventTriggers,
|
|
}
|
|
|
|
auditLog := db.AuditLog{
|
|
Action: "update",
|
|
EntityType: "notification_service",
|
|
EntityID: service.ID,
|
|
UserID: c.GetUint("userID"),
|
|
Details: auditDetails,
|
|
}
|
|
|
|
if err := h.DB.Create(&auditLog).Error; err != nil {
|
|
log.Printf("Error creating audit log: %v", err)
|
|
}
|
|
|
|
// Redirect back to notifications page with success message
|
|
h.handleNotificationsWithSuccess(c, "Notification service updated successfully.")
|
|
}
|
|
|
|
// 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 []types.NotificationServiceData // Use types.NotificationServiceData
|
|
for _, service := range notificationServices {
|
|
componentServices = append(componentServices, types.NotificationServiceData{ // Use types.NotificationServiceData
|
|
ID: service.ID,
|
|
Name: service.Name,
|
|
Type: service.Type,
|
|
IsEnabled: service.GetIsEnabled(), // Use getter
|
|
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 := types.SettingsNotificationsData{ // Use types.SettingsNotificationsData
|
|
NotificationServices: componentServices,
|
|
ErrorMessage: errorMessage,
|
|
}
|
|
|
|
ctx := h.CreateTemplateContext(c)
|
|
list.List(ctx, data).Render(ctx, c.Writer) // Use list.List
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
|
|
// Convert to components.NotificationService
|
|
var componentServices []types.NotificationServiceData // Use types.NotificationServiceData
|
|
for _, service := range notificationServices {
|
|
componentServices = append(componentServices, types.NotificationServiceData{ // Use types.NotificationServiceData
|
|
ID: service.ID,
|
|
Name: service.Name,
|
|
Type: service.Type,
|
|
IsEnabled: service.GetIsEnabled(), // Use getter
|
|
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 := types.SettingsNotificationsData{ // Use types.SettingsNotificationsData
|
|
NotificationServices: componentServices,
|
|
SuccessMessage: successMessage,
|
|
}
|
|
|
|
ctx := h.CreateTemplateContext(c)
|
|
list.List(ctx, data).Render(ctx, c.Writer) // Use list.List
|
|
}
|