-
Notifications
You must be signed in to change notification settings - Fork 52
Expand file tree
/
Copy pathServerConnection.cs
More file actions
205 lines (183 loc) · 8.82 KB
/
ServerConnection.cs
File metadata and controls
205 lines (183 loc) · 8.82 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
/*
* Copyright (c) 2026 Erik Darling, Darling Data LLC
*
* This file is part of the SQL Server Performance Monitor.
*
* Licensed under the MIT License. See LICENSE file in the project root for full license information.
*/
using System;
using System.ComponentModel;
using System.Text.Json.Serialization;
using Microsoft.Data.SqlClient;
using PerformanceMonitorDashboard.Interfaces;
using PerformanceMonitorDashboard.Services;
namespace PerformanceMonitorDashboard.Models
{
public class ServerConnection : INotifyPropertyChanged
{
public string Id { get; set; } = Guid.NewGuid().ToString();
public string ServerName { get; set; } = string.Empty;
public string DisplayName { get; set; } = string.Empty;
/// <summary>
/// Backward compatibility property for old servers.json files.
/// Returns true if authentication type is Windows.
/// Setter updates AuthenticationType for migration from old configs.
/// </summary>
public bool UseWindowsAuth
{
get => AuthenticationType == AuthenticationTypes.Windows;
set
{
// During JSON deserialization of old configs, update AuthenticationType based on UseWindowsAuth
// Only apply this if AuthenticationType is still at default (indicating old JSON without that field)
if (AuthenticationType == AuthenticationTypes.Windows && !value)
{
// Old config with UseWindowsAuth=false -> SQL Server auth
AuthenticationType = AuthenticationTypes.SqlServer;
}
// If value is true, keep Windows (already the default)
}
}
/// <summary>
/// Authentication type: Windows, SqlServer, or EntraMFA.
/// </summary>
public string AuthenticationType { get; set; } = AuthenticationTypes.Windows;
public string? Description { get; set; }
public DateTime CreatedDate { get; set; } = DateTime.Now;
public DateTime LastConnected { get; set; } = DateTime.Now;
public bool IsFavorite { get; set; }
/// <summary>
/// Encryption mode for the connection. Valid values: Optional, Mandatory, Strict.
/// Default is Mandatory for security. Users can opt down to Optional if needed.
/// </summary>
public string EncryptMode { get; set; } = "Mandatory";
/// <summary>
/// Whether to trust the server certificate without validation.
/// Default is false for security. Enable for servers with self-signed certificates.
/// </summary>
public bool TrustServerCertificate { get; set; } = false;
/// <summary>
/// When true, sets ApplicationIntent=ReadOnly on the connection string.
/// Required for connecting to AG listener read-only replicas and
/// Azure SQL Business Critical / Managed Instance built-in read replicas.
/// </summary>
public bool ReadOnlyIntent { get; set; } = false;
/// <summary>
/// When true, sets MultiSubnetFailover=true on the connection string.
/// Recommended for AG listeners and FCIs spanning multiple subnets.
/// </summary>
public bool MultiSubnetFailover { get; set; } = false;
/// <summary>
/// Monthly cost of this server in USD, used for FinOps cost attribution.
/// Set to 0 to hide cost columns. All FinOps costs are proportional to this budget.
/// </summary>
public decimal MonthlyCostUsd { get; set; } = 0m;
/// <summary>
/// Display name with "(Read-Only)" suffix when ReadOnlyIntent is enabled.
/// Used for alerts, tray notifications, tab headers, and dialog messages.
/// </summary>
[JsonIgnore]
public string DisplayNameWithIntent => ReadOnlyIntent ? $"{DisplayName} (Read-Only)" : DisplayName;
private string? _installedVersion;
/// <summary>
/// Installed PerformanceMonitor version on this server. Populated asynchronously
/// by the Manage Servers window. Not persisted — runtime-only display field.
/// Conventional values: null (not yet probed), a 3-part version like "2.10.0",
/// "Not installed", or "Unavailable" when the probe fails.
/// </summary>
[JsonIgnore]
public string? InstalledVersion
{
get => _installedVersion;
set
{
if (_installedVersion == value) return;
_installedVersion = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(InstalledVersion)));
}
}
public event PropertyChangedEventHandler? PropertyChanged;
/// <summary>
/// Display-only property for showing authentication type in UI.
/// </summary>
[JsonIgnore]
public string AuthenticationDisplay => AuthenticationType switch
{
AuthenticationTypes.EntraMFA => "Microsoft Entra MFA",
AuthenticationTypes.SqlServer => "SQL Server",
_ => "Windows"
};
/// <summary>
/// SECURITY: Credentials are NEVER serialized to JSON.
/// They are stored securely in Windows Credential Manager.
/// This method retrieves the connection string with credentials loaded from secure storage.
/// </summary>
/// <param name="credentialService">The credential service to use for retrieving stored credentials</param>
/// <returns>Connection string for SQL Server</returns>
public string GetConnectionString(ICredentialService credentialService)
{
if (AuthenticationType == AuthenticationTypes.EntraMFA)
{
// Build MFA connection string with ActiveDirectoryInteractive
var mfaBuilder = new SqlConnectionStringBuilder
{
DataSource = ServerName,
InitialCatalog = "PerformanceMonitor",
ApplicationName = "PerformanceMonitorDashboard",
ConnectTimeout = 15,
MultipleActiveResultSets = true,
TrustServerCertificate = TrustServerCertificate,
Encrypt = EncryptMode switch
{
"Optional" => SqlConnectionEncryptOption.Optional,
"Strict" => SqlConnectionEncryptOption.Strict,
_ => SqlConnectionEncryptOption.Mandatory
},
ApplicationIntent = ReadOnlyIntent ? ApplicationIntent.ReadOnly : ApplicationIntent.ReadWrite,
MultiSubnetFailover = MultiSubnetFailover,
Authentication = SqlAuthenticationMethod.ActiveDirectoryInteractive
};
// Optionally pre-populate username from credential store
var mfaCred = credentialService.GetCredential(Id);
if (mfaCred.HasValue && !string.IsNullOrEmpty(mfaCred.Value.Username))
mfaBuilder.UserID = mfaCred.Value.Username;
return mfaBuilder.ConnectionString;
}
string? username = null;
string? password = null;
if (AuthenticationType == AuthenticationTypes.SqlServer)
{
var cred = credentialService.GetCredential(Id);
if (cred.HasValue)
{
username = cred.Value.Username;
password = cred.Value.Password;
}
}
return DatabaseService.BuildConnectionString(
ServerName,
UseWindowsAuth,
username,
password,
EncryptMode,
TrustServerCertificate,
ReadOnlyIntent,
MultiSubnetFailover
).ConnectionString;
}
/// <summary>
/// Indicates whether credentials are stored in Windows Credential Manager for this server.
/// Used to validate that SQL auth servers have credentials available.
/// </summary>
/// <param name="credentialService">The credential service to use for checking credentials</param>
/// <returns>True if Windows auth or MFA is used, or if credentials exist in credential manager</returns>
public bool HasStoredCredentials(ICredentialService credentialService)
{
if (AuthenticationType == AuthenticationTypes.Windows || AuthenticationType == AuthenticationTypes.EntraMFA)
{
return true;
}
return credentialService.CredentialExists(Id);
}
}
}