feat: Gate update alerts as Pro-only feature

- Add FeatureUpdateAlerts constant for Pro license gating
- Add feature to all Pro tier feature lists
- Add SetLicenseChecker method to alerts Manager
- Check Pro license in checkDockerContainerImageUpdate before alerting
- Wire license checker from router to alert manager

Free users still see update badges in the UI.
Pro users get proactive alerts after 24h of pending updates.
This commit is contained in:
rcourtman
2025-12-27 18:28:09 +00:00
parent cf44b0cca6
commit d1a8383cd5
3 changed files with 43 additions and 1 deletions

View File

@@ -530,6 +530,8 @@ type Manager struct {
// When a host agent is running on a Proxmox node, we prefer the host agent
// alerts and suppress the node alerts to avoid duplicate monitoring.
hostAgentHostnames map[string]struct{} // Normalized hostnames (lowercase)
// License checking for Pro-only alert features
hasProFeature func(feature string) bool
}
type ackRecord struct {
@@ -715,6 +717,14 @@ func NewManager() *Manager {
return m
}
// SetLicenseChecker sets the function used to check Pro license features.
// This enables gating Pro-only alert features like update alerts.
func (m *Manager) SetLicenseChecker(checker func(feature string) bool) {
m.mu.Lock()
defer m.mu.Unlock()
m.hasProFeature = checker
}
// addRecentlyResolvedUnlocked records a resolved alert assuming the caller does not hold m.mu.
func (m *Manager) addRecentlyResolvedUnlocked(alertID string, resolved *ResolvedAlert) {
m.resolvedMutex.Lock()
@@ -4352,8 +4362,20 @@ func (m *Manager) checkDockerContainerImageUpdate(host models.DockerHost, contai
// Check if update detection is enabled
m.mu.RLock()
delayHours := m.config.DockerDefaults.UpdateAlertDelayHours
hasProFeature := m.hasProFeature
m.mu.RUnlock()
// Update alerts are a Pro-only feature
// Free users still see update badges in the UI, but alerts require Pro
if hasProFeature != nil && !hasProFeature("update_alerts") {
// Not licensed for update alerts - clear any existing alert and tracking
m.clearAlert(alertID)
m.mu.Lock()
delete(m.dockerUpdateFirstSeen, resourceID)
m.mu.Unlock()
return
}
// Negative value means disabled
if delayHours < 0 {
m.clearAlert(alertID)

View File

@@ -1183,6 +1183,16 @@ func (r *Router) setupRoutes() {
r.aiSettingsHandler.SetMetadataProvider(metadataProvider)
// Wire license checker for Pro feature gating (AI Patrol, Alert Analysis, Auto-Fix)
r.aiSettingsHandler.SetLicenseChecker(r.licenseHandlers.Service())
// Wire license checker for alert manager Pro features (Update Alerts)
if r.monitor != nil {
alertMgr := r.monitor.GetAlertManager()
if alertMgr != nil {
licSvc := r.licenseHandlers.Service()
alertMgr.SetLicenseChecker(func(feature string) bool {
return licSvc.HasFeature(feature)
})
}
}
r.mux.HandleFunc("/api/settings/ai", RequireAdmin(r.config, RequireScope(config.ScopeSettingsRead, r.aiSettingsHandler.HandleGetAISettings)))
r.mux.HandleFunc("/api/settings/ai/update", RequireAdmin(r.config, RequireScope(config.ScopeSettingsWrite, r.aiSettingsHandler.HandleUpdateAISettings)))
r.mux.HandleFunc("/api/ai/test", RequireAdmin(r.config, RequireScope(config.ScopeSettingsWrite, r.aiSettingsHandler.HandleTestAIConnection)))

View File

@@ -4,12 +4,15 @@ package license
// Feature constants represent gated features in Pulse Pro.
// These are embedded in license JWTs and checked at runtime.
const (
// Pro tier features
// Pro tier features - AI
FeatureAIPatrol = "ai_patrol" // Background AI health monitoring
FeatureAIAlerts = "ai_alerts" // AI analysis when alerts fire
FeatureAIAutoFix = "ai_autofix" // Automatic remediation
FeatureKubernetesAI = "kubernetes_ai" // AI analysis of K8s (NOT basic monitoring)
// Pro tier features - Monitoring
FeatureUpdateAlerts = "update_alerts" // Alerts for pending container/package updates
// MSP tier features (FUTURE - not in v1 launch)
FeatureMultiUser = "multi_user" // RBAC - NOT IMPLEMENTED YET
FeatureWhiteLabel = "white_label" // Custom branding - NOT IMPLEMENTED YET
@@ -39,24 +42,28 @@ var TierFeatures = map[Tier][]string{
FeatureAIAlerts,
FeatureAIAutoFix,
FeatureKubernetesAI,
FeatureUpdateAlerts,
},
TierProAnnual: {
FeatureAIPatrol,
FeatureAIAlerts,
FeatureAIAutoFix,
FeatureKubernetesAI,
FeatureUpdateAlerts,
},
TierLifetime: {
FeatureAIPatrol,
FeatureAIAlerts,
FeatureAIAutoFix,
FeatureKubernetesAI,
FeatureUpdateAlerts,
},
TierMSP: {
FeatureAIPatrol,
FeatureAIAlerts,
FeatureAIAutoFix,
FeatureKubernetesAI,
FeatureUpdateAlerts,
FeatureUnlimited,
// Note: FeatureMultiUser, FeatureWhiteLabel, FeatureMultiTenant
// are on the roadmap but NOT included until implemented
@@ -66,6 +73,7 @@ var TierFeatures = map[Tier][]string{
FeatureAIAlerts,
FeatureAIAutoFix,
FeatureKubernetesAI,
FeatureUpdateAlerts,
FeatureUnlimited,
FeatureMultiUser,
FeatureWhiteLabel,
@@ -118,6 +126,8 @@ func GetFeatureDisplayName(feature string) string {
return "AI Auto-Fix"
case FeatureKubernetesAI:
return "Kubernetes AI Analysis"
case FeatureUpdateAlerts:
return "Update Alerts (Container/Package Updates)"
case FeatureMultiUser:
return "Multi-User / RBAC"
case FeatureWhiteLabel: