fix: provide full Pulse URL in Gotify/ntfy webhook notifications (addresses #415)

- Added PULSE_PUBLIC_URL config option to specify the full URL to access Pulse
- Updated notification manager to use publicURL when constructing webhook payloads
- Modified prepareWebhookData to construct full URLs instead of just paths
- Fixed test webhooks to use configured publicURL instead of hardcoded values
- Gotify and ntfy notifications now include clickable links that work properly
This commit is contained in:
Pulse Monitor 2025-09-04 12:07:23 +00:00
parent d1703ce4e9
commit 2391329d28
4 changed files with 41 additions and 6 deletions

View file

@ -58,6 +58,7 @@ type Config struct {
FrontendPort int `envconfig:"FRONTEND_PORT" default:"7655"`
ConfigPath string `envconfig:"CONFIG_PATH" default:"/etc/pulse"`
DataPath string `envconfig:"DATA_PATH" default:"/var/lib/pulse"`
PublicURL string `envconfig:"PULSE_PUBLIC_URL" default:""` // Full URL to access Pulse (e.g., http://192.168.1.100:7655)
// Proxmox VE connections
PVEInstances []PVEInstance
@ -480,6 +481,10 @@ func Load() (*Config, error) {
cfg.EnvOverrides["allowedOrigins"] = true
log.Info().Str("origins", allowedOrigins).Msg("Allowed origins overridden by ALLOWED_ORIGINS env var")
}
if publicURL := os.Getenv("PULSE_PUBLIC_URL"); publicURL != "" {
cfg.PublicURL = publicURL
log.Info().Str("url", publicURL).Msg("Public URL configured from PULSE_PUBLIC_URL env var")
}
// Set log level
switch cfg.LogLevel {

View file

@ -157,7 +157,7 @@ func New(cfg *config.Config) (*Monitor, error) {
rateTracker: NewRateTracker(),
metricsHistory: NewMetricsHistory(1000, 24*time.Hour), // Keep up to 1000 points or 24 hours
alertManager: alerts.NewManager(),
notificationMgr: notifications.NewNotificationManager(),
notificationMgr: notifications.NewNotificationManager(cfg.PublicURL),
configPersist: config.NewConfigPersistence(cfg.DataPath),
discoveryService: nil, // Will be initialized in Start()
authFailures: make(map[string]int),

View file

@ -47,6 +47,7 @@ type NotificationManager struct {
pendingAlerts []*alerts.Alert
groupTimer *time.Timer
groupByNode bool
publicURL string // Full URL to access Pulse
groupByGuest bool
webhookHistory []WebhookDelivery // Keep last 100 webhook deliveries for debugging
}
@ -92,7 +93,7 @@ type WebhookConfig struct {
}
// NewNotificationManager creates a new notification manager
func NewNotificationManager() *NotificationManager {
func NewNotificationManager(publicURL string) *NotificationManager {
return &NotificationManager{
enabled: true,
cooldown: 5 * time.Minute,
@ -103,6 +104,7 @@ func NewNotificationManager() *NotificationManager {
groupByNode: true,
groupByGuest: false,
webhookHistory: make([]WebhookDelivery, 0, 100), // Pre-allocate for 100 entries
publicURL: publicURL,
}
}
@ -820,6 +822,19 @@ func (n *NotificationManager) sendWebhook(webhook WebhookConfig, alert *alerts.A
func (n *NotificationManager) prepareWebhookData(alert *alerts.Alert, customFields map[string]interface{}) WebhookPayloadData {
duration := time.Since(alert.StartTime)
// Construct full Pulse URL if publicURL is configured
instance := alert.Instance
if n.publicURL != "" && alert.Instance != "" {
// If alert.Instance is just the instance name (e.g., "pve-10.0.1.21")
// construct the full URL to view this instance in Pulse
if !strings.HasPrefix(alert.Instance, "http://") && !strings.HasPrefix(alert.Instance, "https://") {
// Remove trailing slash from publicURL if present
publicURL := strings.TrimRight(n.publicURL, "/")
// Construct the full URL with the instance path
instance = fmt.Sprintf("%s/%s", publicURL, alert.Instance)
}
}
return WebhookPayloadData{
ID: alert.ID,
Level: string(alert.Level),
@ -827,7 +842,7 @@ func (n *NotificationManager) prepareWebhookData(alert *alerts.Alert, customFiel
ResourceName: alert.ResourceName,
ResourceID: alert.ResourceID,
Node: alert.Node,
Instance: alert.Instance,
Instance: instance,
Message: alert.Message,
Value: alert.Value,
Threshold: alert.Threshold,
@ -1129,6 +1144,12 @@ func (n *NotificationManager) SendTestNotification(method string) error {
// SendTestWebhook sends a test notification to a specific webhook
func (n *NotificationManager) SendTestWebhook(webhook WebhookConfig) error {
// Create a test alert for webhook testing with realistic values
// Use the configured publicURL if available, otherwise use a placeholder
instanceURL := n.publicURL
if instanceURL == "" {
instanceURL = "http://your-pulse-instance:7655"
}
testAlert := &alerts.Alert{
ID: "test-webhook-" + webhook.ID,
Type: "cpu",
@ -1136,7 +1157,7 @@ func (n *NotificationManager) SendTestWebhook(webhook WebhookConfig) error {
ResourceID: "webhook-test",
ResourceName: "Test Alert",
Node: "test-node",
Instance: "http://your-pulse-instance:7655", // Placeholder URL for test webhooks
Instance: instanceURL, // Use the actual Pulse URL
Message: fmt.Sprintf("This is a test alert from Pulse to verify your %s webhook is working correctly", webhook.Name),
Value: 85.5,
Threshold: 80.0,
@ -1158,7 +1179,10 @@ func (n *NotificationManager) SendTestWebhook(webhook WebhookConfig) error {
func (n *NotificationManager) SendTestNotificationWithConfig(method string, config *EmailConfig, nodeInfo *TestNodeInfo) error {
// Use actual node info if provided, otherwise use defaults
nodeName := "test-node"
instanceURL := "https://proxmox.local:8006"
instanceURL := n.publicURL
if instanceURL == "" {
instanceURL = "https://proxmox.local:8006"
}
if nodeInfo != nil {
if nodeInfo.NodeName != "" {
nodeName = nodeInfo.NodeName

View file

@ -405,6 +405,12 @@ func formatWebhookDuration(d time.Duration) string {
// TestEnhancedWebhook tests a webhook with a specific payload
func (n *NotificationManager) TestEnhancedWebhook(webhook EnhancedWebhookConfig) (int, string, error) {
// Use the configured publicURL if available, otherwise use a placeholder
instanceURL := n.publicURL
if instanceURL == "" {
instanceURL = "https://192.168.1.100:8006"
}
// Create test alert
testAlert := &alerts.Alert{
ID: "test-" + time.Now().Format("20060102-150405"),
@ -413,7 +419,7 @@ func (n *NotificationManager) TestEnhancedWebhook(webhook EnhancedWebhookConfig)
ResourceID: "100",
ResourceName: "Test VM",
Node: "pve-node-01",
Instance: "https://192.168.1.100:8006",
Instance: instanceURL,
Message: "Test webhook notification from Pulse Monitoring",
Value: 85.5,
Threshold: 80.0,