@@ -24,7 +24,10 @@ namespace PerformanceMonitorDashboard.Services
2424 public class WebhookAlertService
2525 {
2626 private const string EditionName = "Performance Monitor Dashboard" ;
27+ private const string TeamsWebhookCredentialKey = "TeamsWebhook" ;
28+ private const string SlackWebhookCredentialKey = "SlackWebhook" ;
2729 private static readonly JsonSerializerOptions s_jsonOptions = new ( ) { PropertyNamingPolicy = null } ;
30+ private static readonly CredentialService s_credentialService = new ( ) ;
2831
2932 private readonly UserPreferencesService _preferencesService ;
3033 private readonly ConcurrentDictionary < string , DateTime > _cooldowns = new ( ) ;
@@ -42,6 +45,50 @@ public WebhookAlertService(UserPreferencesService preferencesService)
4245 Current = this ;
4346 }
4447
48+ /// <summary>
49+ /// Gets a webhook URL from Windows Credential Manager.
50+ /// </summary>
51+ public static string GetWebhookUrl ( string credentialKey )
52+ {
53+ try
54+ {
55+ var cred = s_credentialService . GetCredential ( credentialKey ) ;
56+ return cred ? . Password ?? "" ;
57+ }
58+ catch ( Exception ex )
59+ {
60+ Logger . Error ( $ "Failed to retrieve webhook URL for { credentialKey } : { ex . Message } ") ;
61+ return "" ;
62+ }
63+ }
64+
65+ /// <summary>
66+ /// Saves a webhook URL to Windows Credential Manager.
67+ /// </summary>
68+ public static void SaveWebhookUrl ( string credentialKey , string url )
69+ {
70+ try
71+ {
72+ if ( string . IsNullOrWhiteSpace ( url ) )
73+ {
74+ s_credentialService . DeleteCredential ( credentialKey ) ;
75+ }
76+ else
77+ {
78+ s_credentialService . SaveCredential ( credentialKey , "webhook" , url ) ;
79+ }
80+ }
81+ catch ( Exception ex )
82+ {
83+ Logger . Error ( $ "Failed to save webhook URL for { credentialKey } : { ex . Message } ") ;
84+ }
85+ }
86+
87+ public static string GetTeamsWebhookUrl ( ) => GetWebhookUrl ( TeamsWebhookCredentialKey ) ;
88+ public static string GetSlackWebhookUrl ( ) => GetWebhookUrl ( SlackWebhookCredentialKey ) ;
89+ public static void SaveTeamsWebhookUrl ( string url ) => SaveWebhookUrl ( TeamsWebhookCredentialKey , url ) ;
90+ public static void SaveSlackWebhookUrl ( string url ) => SaveWebhookUrl ( SlackWebhookCredentialKey , url ) ;
91+
4592 /// <summary>
4693 /// Sends webhook alerts to all configured channels (Teams and/or Slack).
4794 /// Respects the email cooldown setting for throttling. Never throws.
@@ -67,12 +114,14 @@ public async Task<bool> TrySendWebhookAlertsAsync(
67114
68115 bool sent = false ;
69116
70- if ( prefs . TeamsWebhookEnabled && ! string . IsNullOrWhiteSpace ( prefs . TeamsWebhookUrl ) )
117+ var teamsUrl = GetTeamsWebhookUrl ( ) ;
118+ if ( prefs . TeamsWebhookEnabled && ! string . IsNullOrWhiteSpace ( teamsUrl ) )
71119 {
72120 sent |= await TrySendTeamsAlertAsync ( prefs , metricName , serverName , currentValue , thresholdValue , context ) ;
73121 }
74122
75- if ( prefs . SlackWebhookEnabled && ! string . IsNullOrWhiteSpace ( prefs . SlackWebhookUrl ) )
123+ var slackUrl = GetSlackWebhookUrl ( ) ;
124+ if ( prefs . SlackWebhookEnabled && ! string . IsNullOrWhiteSpace ( slackUrl ) )
76125 {
77126 sent |= await TrySendSlackAlertAsync ( prefs , metricName , serverName , currentValue , thresholdValue , context ) ;
78127 }
@@ -148,7 +197,7 @@ private async Task<bool> TrySendTeamsAlertAsync(
148197 try
149198 {
150199 var payload = BuildTeamsPayload ( metricName , serverName , currentValue , thresholdValue , context : context ) ;
151- var error = await PostWebhookAsync ( prefs . TeamsWebhookUrl , payload , prefs . TeamsProxyAddress ) ;
200+ var error = await PostWebhookAsync ( GetTeamsWebhookUrl ( ) , payload , prefs . TeamsProxyAddress ) ;
152201
153202 if ( error != null )
154203 {
@@ -268,7 +317,7 @@ private async Task<bool> TrySendSlackAlertAsync(
268317 try
269318 {
270319 var payload = BuildSlackPayload ( metricName , serverName , currentValue , thresholdValue , context : context ) ;
271- var error = await PostWebhookAsync ( prefs . SlackWebhookUrl , payload , prefs . SlackProxyAddress ) ;
320+ var error = await PostWebhookAsync ( GetSlackWebhookUrl ( ) , payload , prefs . SlackProxyAddress ) ;
272321
273322 if ( error != null )
274323 {
0 commit comments