comparison ServerMonitor/Forms/ServerForm.cs @ 14:2db36ab759de

Add comments.
author Brad Greco <brad@bgreco.net>
date Mon, 22 Apr 2019 21:10:42 -0400
parents 052aa62cb42a
children 7645122aa7a9
comparison
equal deleted inserted replaced
13:a36cc5c123f4 14:2db36ab759de
1 using Renci.SshNet; 1 using ServerMonitorApp.Properties;
2 using Renci.SshNet.Common;
3 using ServerMonitorApp.Properties;
4 using System; 2 using System;
5 using System.Collections.Generic; 3 using System.Collections.Generic;
6 using System.ComponentModel; 4 using System.ComponentModel;
7 using System.Data; 5 using System.Data;
8 using System.Drawing; 6 using System.Drawing;
9 using System.IO;
10 using System.Linq; 7 using System.Linq;
11 using System.Text;
12 using System.Threading.Tasks; 8 using System.Threading.Tasks;
13 using System.Windows.Forms; 9 using System.Windows.Forms;
14 10
15 namespace ServerMonitorApp 11 namespace ServerMonitorApp
16 { 12 {
13 /// <summary>Form for adding or editing a server and managing its checks.</summary>
17 public partial class ServerForm : Form 14 public partial class ServerForm : Form
18 { 15 {
19 private bool isNewServer; 16 private bool isNewServer;
20 private bool changedPassword; 17 private bool changedPassword;
21 private DateTime lastSaveTime; 18 private DateTime lastSaveTime;
23 private BindingList<CheckResult> logResults, logResultsFiltered; 20 private BindingList<CheckResult> logResults, logResultsFiltered;
24 private CheckStatus[] filteredStatuses; 21 private CheckStatus[] filteredStatuses;
25 private readonly Dictionary<int, CheckForm> checkForms = new Dictionary<int, CheckForm>(); 22 private readonly Dictionary<int, CheckForm> checkForms = new Dictionary<int, CheckForm>();
26 private readonly Dictionary<CheckBox, CheckStatus> filterChecks; 23 private readonly Dictionary<CheckBox, CheckStatus> filterChecks;
27 24
25 /// <summary>The server being edited.</summary>
28 public Server Server { get; private set; } 26 public Server Server { get; private set; }
29 27
28 /// <summary>The checks currently selected by the user.</summary>
30 private IEnumerable<Check> SelectedChecks => CheckGrid.SelectedRows.Cast<DataGridViewRow>().Select(r => r.DataBoundItem).Cast<Check>(); 29 private IEnumerable<Check> SelectedChecks => CheckGrid.SelectedRows.Cast<DataGridViewRow>().Select(r => r.DataBoundItem).Cast<Check>();
31 30
31 /// <summary>The first check currently selected by the user.</summary>
32 private Check SelectedCheck => SelectedChecks.FirstOrDefault(); 32 private Check SelectedCheck => SelectedChecks.FirstOrDefault();
33 33
34 /// <summary>ServerForm constructor.</summary>
35 /// <param name="monitor">The global server monitor object.</param>
36 /// <param name="server">The server to edit.</param>
37 /// <param name="isNewServer">Whether an existing server is being edited or a new server is being created.</param>
34 public ServerForm(ServerMonitor monitor, Server server, bool isNewServer = false) 38 public ServerForm(ServerMonitor monitor, Server server, bool isNewServer = false)
35 { 39 {
36 InitializeComponent(); 40 InitializeComponent();
37 this.monitor = monitor; 41 this.monitor = monitor;
38 this.isNewServer = isNewServer; 42 this.isNewServer = isNewServer;
39 Server = server; 43 Server = server;
44 // Associates filter check boxes with their corresponding check statuses.
40 filterChecks = new Dictionary<CheckBox, CheckStatus> 45 filterChecks = new Dictionary<CheckBox, CheckStatus>
41 { 46 {
42 { LogSuccessCheckBox, CheckStatus.Success }, 47 { LogSuccessCheckBox, CheckStatus.Success },
43 { LogInformationCheckBox, CheckStatus.Information }, 48 { LogInformationCheckBox, CheckStatus.Information },
44 { LogWarningCheckBox, CheckStatus.Warning }, 49 { LogWarningCheckBox, CheckStatus.Warning },
46 }; 51 };
47 } 52 }
48 53
49 private void ServerForm_Load(object sender, EventArgs e) 54 private void ServerForm_Load(object sender, EventArgs e)
50 { 55 {
56 // Bind the Check grid to the server's checks.
51 CheckBindingSource.DataSource = Server.Checks; 57 CheckBindingSource.DataSource = Server.Checks;
52 monitor.CheckStatusChanged += Monitor_CheckStatusChanged; 58 monitor.CheckStatusChanged += Monitor_CheckStatusChanged;
59 // Deselect the default first row selection.
53 CheckGrid.ClearSelection(); 60 CheckGrid.ClearSelection();
54 if (isNewServer) 61 if (isNewServer)
55 { 62 {
63 // Set defaults for a new server.
56 LoginComboBox.SelectedIndex = 0; 64 LoginComboBox.SelectedIndex = 0;
57 Icon = CheckStatus.Success.GetIcon(); 65 Icon = CheckStatus.Success.GetIcon();
58 } 66 }
59 else 67 else
60 { 68 {
69 // Update inputs with the server information.
61 SetTitle(); 70 SetTitle();
62 SetIcon(); 71 SetIcon();
63 NameTextBox.Text = Server.Name; 72 NameTextBox.Text = Server.Name;
64 HostTextBox.Text = Server.Host; 73 HostTextBox.Text = Server.Host;
65 EnabledCheckBox.Checked = Server.Enabled; 74 EnabledCheckBox.Checked = Server.Enabled;
69 LoginComboBox.SelectedIndex = (int)Server.LoginType; 78 LoginComboBox.SelectedIndex = (int)Server.LoginType;
70 KeyTextBox.Text = Server.KeyFile; 79 KeyTextBox.Text = Server.KeyFile;
71 changedPassword = false; 80 changedPassword = false;
72 } 81 }
73 82
83 // After the input controls have been initialized, bind change listeners to them.
74 BindChangeListeners(); 84 BindChangeListeners();
85 // Resize the images in buttons to fit the button size.
75 FormatImageButtons(); 86 FormatImageButtons();
87 // Set the Run and Edit buttons to their default state.
76 UpdateCheckButtons(); 88 UpdateCheckButtons();
77 89
90 // Focus the name text box if it is empty so the user can start typing immediately.
78 if (NameTextBox.Text == string.Empty) 91 if (NameTextBox.Text == string.Empty)
79 ActiveControl = NameTextBox; 92 ActiveControl = NameTextBox;
80 } 93 }
81 94
95 /// <summary>Shows the form.</summary>
96 /// <param name="activate">Whether the form should be activated. Otherwise, it will be shown minimized.</param>
82 public void Show(bool activate) 97 public void Show(bool activate)
83 { 98 {
84 if (!activate) 99 if (!activate)
85 WindowState = FormWindowState.Minimized; 100 WindowState = FormWindowState.Minimized;
86 Show(); 101 Show();
87 } 102 }
88 103
104 /// <summary>Updates the form when the status of a check changes.</summary>
89 private void Monitor_CheckStatusChanged(object sender, CheckStatusChangedEventArgs e) 105 private void Monitor_CheckStatusChanged(object sender, CheckStatusChangedEventArgs e)
90 { 106 {
107 // Ignore events for checks that belong to other servers.
91 if (e.Check.Server != Server) 108 if (e.Check.Server != Server)
92 return; 109 return;
110 // Refresh the check display with the updated values.
93 CheckGrid.Refresh(); 111 CheckGrid.Refresh();
94 SetIcon(); 112 SetIcon();
113 // If there is a result, and the log grid has been initialized, append the log entry.
95 if (e.CheckResult != null && logResults != null) 114 if (e.CheckResult != null && logResults != null)
96 { 115 {
97 logResults.Insert(0, e.CheckResult); 116 logResults.Insert(0, e.CheckResult);
117 // If a filter is applied, also append the log to the filtered list if it matches.
98 if (logResultsFiltered != null && MatchesFilter(e.CheckResult)) 118 if (logResultsFiltered != null && MatchesFilter(e.CheckResult))
99 logResultsFiltered.Insert(0, e.CheckResult); 119 logResultsFiltered.Insert(0, e.CheckResult);
100 } 120 }
101 } 121 }
102 122
103 /// <summary>Update the server with the current input values</summary> 123 /// <summary>Updates the server with the current input values.</summary>
104 /// <param name="forceSave"> 124 /// <param name="forceSave">
105 /// If true, immediately update the config file on disk. 125 /// If true, immediately update the config file on disk.
106 /// If false, only update the config file if it has not been recently updated. 126 /// If false, only update the config file if it has not been recently updated.
107 /// </param> 127 /// </param>
108 private void UpdateServer(bool forceSave = true) 128 private void UpdateServer(bool forceSave = true)
115 Server.LoginType = (LoginType)LoginComboBox.SelectedIndex; 135 Server.LoginType = (LoginType)LoginComboBox.SelectedIndex;
116 Server.KeyFile = KeyTextBox.Text.Trim(); 136 Server.KeyFile = KeyTextBox.Text.Trim();
117 if (changedPassword) 137 if (changedPassword)
118 Server.Password = PasswordTextBox.Text; 138 Server.Password = PasswordTextBox.Text;
119 139
140 // If a force save is not requested, only save if the last save time is
141 // old enough so we don't end up writing out the file on every keystroke.
120 if (forceSave || lastSaveTime < DateTime.Now.AddSeconds(-5)) 142 if (forceSave || lastSaveTime < DateTime.Now.AddSeconds(-5))
121 { 143 {
122 lastSaveTime = DateTime.Now; 144 lastSaveTime = DateTime.Now;
123 monitor.SaveServers(); 145 monitor.SaveServers();
124 } 146 }
125 } 147 }
126 148
149 /// <summary>Sets the window title and header based on the server name and state.</summary>
127 private void SetTitle(string title = null) 150 private void SetTitle(string title = null)
128 { 151 {
129 title = (title ?? Server.Name) + (Server.Enabled ? "" : " (disabled)"); 152 title = (title ?? Server.Name) + (Server.Enabled ? "" : " (disabled)");
130 Text = title; 153 Text = title;
131 TitleLabel.Text = title; 154 TitleLabel.Text = title;
132 } 155 }
133 156
157 /// <summary>Sets the window icon based on the status of the server.</summary>
134 private void SetIcon() 158 private void SetIcon()
135 { 159 {
136 if (Server != null) 160 if (Server != null)
137 Icon = Server.Status.GetIcon(); 161 Icon = Server.Status.GetIcon();
138 } 162 }
139 163
164 /// <summary>Updates the window title when the server name changes.</summary>
140 private void NameTextBox_TextChanged(object sender, EventArgs e) 165 private void NameTextBox_TextChanged(object sender, EventArgs e)
141 { 166 {
142 SetTitle(NameTextBox.Text); 167 SetTitle(NameTextBox.Text);
143 } 168 }
144 169
170 /// <summary>Shows the password or private key controls when the login type changes.</summary>
145 private void LoginComboBox_SelectedIndexChanged(object sender, EventArgs e) 171 private void LoginComboBox_SelectedIndexChanged(object sender, EventArgs e)
146 { 172 {
147 if (LoginComboBox.SelectedIndex == (int)LoginType.PrivateKey) 173 if (LoginComboBox.SelectedIndex == (int)LoginType.PrivateKey)
148 { 174 {
175 // Show private key controls.
149 PasswordTextBox.Visible = false; 176 PasswordTextBox.Visible = false;
150 KeyTextBox.Visible = true; 177 KeyTextBox.Visible = true;
151 KeyBrowseButton.Visible = true; 178 KeyBrowseButton.Visible = true;
152 } 179 }
153 else 180 else
154 { 181 {
182 // Show password controls.
155 PasswordTextBox.Visible = true; 183 PasswordTextBox.Visible = true;
156 KeyTextBox.Visible = false; 184 KeyTextBox.Visible = false;
157 KeyBrowseButton.Visible = false; 185 KeyBrowseButton.Visible = false;
158 } 186 }
159 } 187 }
160 188
189 /// <summary>Shows a form to create a new check.</summary>
161 private void NewCheckButton_Click(object sender, EventArgs e) 190 private void NewCheckButton_Click(object sender, EventArgs e)
162 { 191 {
163 ShowCheckForm(null); 192 ShowCheckForm(null);
164 } 193 }
165 194
195 /// <summary>Shows a form to edit the selected check.</summary>
166 private void EditCheckButton_Click(object sender, EventArgs e) 196 private void EditCheckButton_Click(object sender, EventArgs e)
167 { 197 {
168 EditSelectedCheck(); 198 EditSelectedCheck();
169 } 199 }
170 200
201 /// <summary>Executes the selected checks.</summary>
171 private void RunButton_Click(object sender, EventArgs e) 202 private void RunButton_Click(object sender, EventArgs e)
172 { 203 {
173 ExecuteChecks(SelectedChecks); 204 ExecuteChecks(SelectedChecks);
174 } 205 }
175 206
207 /// <summary>Executes all checks for the server.</summary>
176 private void RunAllButton_Click(object sender, EventArgs e) 208 private void RunAllButton_Click(object sender, EventArgs e)
177 { 209 {
178 ExecuteChecks(Server.Checks); 210 ExecuteChecks(Server.Checks);
179 } 211 }
180 212
213 /// <summary>Shows a form to create or edit a check.</summary>
214 /// <param name="check">The check to edit, or null to create a new check.</param>
181 private void ShowCheckForm(Check check) 215 private void ShowCheckForm(Check check)
182 { 216 {
183 if (check != null) 217 if (check != null)
184 { 218 {
219 // Activate the form to edit the check if it is already open.
220 // Otherwise, open a new form.
185 if (!checkForms.TryGetValue(check.Id, out CheckForm form)) 221 if (!checkForms.TryGetValue(check.Id, out CheckForm form))
186 { 222 {
187 form = new CheckForm(monitor, check); 223 form = new CheckForm(monitor, check);
188 checkForms[check.Id] = form; 224 checkForms[check.Id] = form;
189 form.FormClosing += CheckForm_FormClosing; 225 form.FormClosing += CheckForm_FormClosing;
198 { 234 {
199 new CheckForm(monitor, Server).Show(); 235 new CheckForm(monitor, Server).Show();
200 } 236 }
201 } 237 }
202 238
239 /// <summary>Shows a form to edit the selected check.</summary>
203 private void EditSelectedCheck() 240 private void EditSelectedCheck()
204 { 241 {
205 ShowCheckForm(SelectedCheck); 242 ShowCheckForm(SelectedCheck);
206 } 243 }
207 244
245 /// <summary>Deletes the selected checks.</summary>
208 private void DeleteSelectedChecks() 246 private void DeleteSelectedChecks()
209 { 247 {
248 // Prompt to delete unless the "Do not ask again" setting has been set.
210 if (Settings.Default.ConfirmDeleteCheck) 249 if (Settings.Default.ConfirmDeleteCheck)
211 { 250 {
212 string message = "Delete " + (SelectedChecks.Count() == 1 ? "\"" + SelectedCheck.Name + "\"" : "selected checks") + "?"; 251 string message = "Delete " + (SelectedChecks.Count() == 1 ? "\"" + SelectedCheck.Name + "\"" : "selected checks") + "?";
213 using (CheckBoxDialog dialog = new CheckBoxDialog { Message = message }) 252 using (CheckBoxDialog dialog = new CheckBoxDialog { Message = message })
214 { 253 {
215 DialogResult result = dialog.ShowDialog(); 254 DialogResult result = dialog.ShowDialog();
255 // Save the "Do not ask again" setting only if OK was clicked.
216 if (dialog.Checked && result == DialogResult.OK) 256 if (dialog.Checked && result == DialogResult.OK)
217 { 257 {
218 Settings.Default.ConfirmDeleteCheck = false; 258 Settings.Default.ConfirmDeleteCheck = false;
219 Settings.Default.Save(); 259 Settings.Default.Save();
220 } 260 }
261 // Do nothing if Cancel was clicked.
221 if (result != DialogResult.OK) 262 if (result != DialogResult.OK)
222 return; 263 return;
223 } 264 }
224 } 265 }
266 // If OK was clicked or no confirmation was shown, delete the checks.
225 foreach (Check check in SelectedChecks) 267 foreach (Check check in SelectedChecks)
226 Server.DeleteCheck(check); 268 Server.DeleteCheck(check);
227 } 269 }
228 270
271 /// <summary>Executes the selected checks.</summary>
229 private async void ExecuteChecks(IEnumerable<Check> checks) 272 private async void ExecuteChecks(IEnumerable<Check> checks)
230 { 273 {
231 await Task.WhenAll(checks.Select(c => monitor.ExecuteCheckAsync(c))); 274 await Task.WhenAll(checks.Select(c => monitor.ExecuteCheckAsync(c)));
232 } 275 }
233 276
277 /// <summary>Shows the execution history for a check.</summary>
278 /// <param name="check">The check to show execution history for.</param>
234 public void ShowLog(Check check) 279 public void ShowLog(Check check)
235 { 280 {
281 // Switch to the Log tab.
236 CheckTabControl.SelectedTab = LogTabPage; 282 CheckTabControl.SelectedTab = LogTabPage;
283 // Filter the list to only show history for this check.
237 LogCheckComboBox.SelectedItem = check; 284 LogCheckComboBox.SelectedItem = check;
238 } 285 }
239 286
287 /// <summary>Sets the enabled state of buttons based on whether checks are selected.</summary>
240 private void UpdateCheckButtons() 288 private void UpdateCheckButtons()
241 { 289 {
242 RunAllButton.Enabled = CheckGrid.RowCount > 0; 290 RunAllButton.Enabled = CheckGrid.RowCount > 0;
243 RunButton.Enabled = DeleteCheckButton.Enabled = CheckGrid.SelectedRows.Count > 0; 291 RunButton.Enabled = DeleteCheckButton.Enabled = CheckGrid.SelectedRows.Count > 0;
244 EditCheckButton.Enabled = CheckGrid.SelectedRows.Count == 1; 292 EditCheckButton.Enabled = CheckGrid.SelectedRows.Count == 1;
245 } 293 }
246 294
295 /// <summary>Resizes the images in buttons to fit the button size.</summary>
247 private void FormatImageButtons() 296 private void FormatImageButtons()
248 { 297 {
249 Button[] buttons = new Button[] { NewCheckButton, RunAllButton, RunButton, EditCheckButton, DeleteCheckButton }; 298 Button[] buttons = new Button[] { NewCheckButton, RunAllButton, RunButton, EditCheckButton, DeleteCheckButton };
250 foreach (Button button in buttons) 299 foreach (Button button in buttons)
251 Helpers.FormatImageButton(button); 300 Helpers.FormatImageButton(button);
252 } 301 }
253 302
303 /// <summary>Binds change listeners to most input controls.</summary>
254 private void BindChangeListeners() 304 private void BindChangeListeners()
255 { 305 {
256 Server.EnabledChanged += Server_EnabledChanged; 306 Server.EnabledChanged += Server_EnabledChanged;
307 // Update the server with text box values.
257 foreach (Control control in ServerInfoPanel.Controls.OfType<Control>().Where(c => c is TextBox)) 308 foreach (Control control in ServerInfoPanel.Controls.OfType<Control>().Where(c => c is TextBox))
258 control.TextChanged += (sender, e) => UpdateServer(false); 309 control.TextChanged += (sender, e) => UpdateServer(false);
310 // Update the server with combo box values.
259 foreach (Control control in ServerInfoPanel.Controls.OfType<Control>().Where(c => c is ComboBox)) 311 foreach (Control control in ServerInfoPanel.Controls.OfType<Control>().Where(c => c is ComboBox))
260 control.TextChanged += (sender, e) => UpdateServer(); 312 control.TextChanged += (sender, e) => UpdateServer();
313 // Apply the log filter when the filter checkboxes are changed.
261 foreach (CheckBox control in LogTabPage.Controls.OfType<CheckBox>()) 314 foreach (CheckBox control in LogTabPage.Controls.OfType<CheckBox>())
262 control.CheckedChanged += FilterChanged; 315 control.CheckedChanged += FilterChanged;
263 } 316 }
264 317
318 /// <summary>Handles the closing of a check form.</summary>
265 private void CheckForm_FormClosing(object sender, FormClosingEventArgs e) 319 private void CheckForm_FormClosing(object sender, FormClosingEventArgs e)
266 { 320 {
321 // Remove the form from the list of open forms.
267 CheckForm form = (CheckForm)sender; 322 CheckForm form = (CheckForm)sender;
268 form.FormClosing -= CheckForm_FormClosing; 323 form.FormClosing -= CheckForm_FormClosing;
269 checkForms.Remove(form.CheckId); 324 checkForms.Remove(form.CheckId);
325 // Refresh the check grid if the check was modified.
270 if (form.DialogResult == DialogResult.OK) 326 if (form.DialogResult == DialogResult.OK)
271 CheckGrid.Refresh(); 327 CheckGrid.Refresh();
272 } 328 }
273 329
330 /// <summary>Updates the state of buttons based on the check selection.</summary>
274 private void CheckGrid_SelectionChanged(object sender, EventArgs e) 331 private void CheckGrid_SelectionChanged(object sender, EventArgs e)
275 { 332 {
276 UpdateCheckButtons(); 333 UpdateCheckButtons();
277 } 334 }
278 335
336 /// <summary>Sets a flag indicating the password text box contains a real password that should be saved.</summary>
337 /// <remarks>When the form is loaded, the password text box is populated with literal asterisks, not the saved password.</remarks>
279 private void PasswordTextBox_TextChanged(object sender, EventArgs e) 338 private void PasswordTextBox_TextChanged(object sender, EventArgs e)
280 { 339 {
281 changedPassword = true; 340 changedPassword = true;
282 } 341 }
283 342
343 /// <summary>Saves the server when the form is closed.</summary>
284 private void ServerForm_FormClosing(object sender, FormClosingEventArgs e) 344 private void ServerForm_FormClosing(object sender, FormClosingEventArgs e)
285 { 345 {
286 UpdateServer(); 346 UpdateServer();
287 } 347 }
288 348
349 /// <summary>Edits the selected check when it is double clicked.</summary>
289 private void CheckGrid_CellDoubleClick(object sender, DataGridViewCellEventArgs e) 350 private void CheckGrid_CellDoubleClick(object sender, DataGridViewCellEventArgs e)
290 { 351 {
291 EditSelectedCheck(); 352 EditSelectedCheck();
292 } 353 }
293 354
355 /// <summary>Edits the selected check when ENTER is pressed.</summary>
294 private void CheckGrid_KeyDown(object sender, KeyEventArgs e) 356 private void CheckGrid_KeyDown(object sender, KeyEventArgs e)
295 { 357 {
296 if (e.KeyCode == Keys.Enter) 358 if (e.KeyCode == Keys.Enter)
297 { 359 {
298 EditSelectedCheck(); 360 EditSelectedCheck();
299 e.Handled = true; 361 e.Handled = true;
300 } 362 }
301 } 363 }
302 364
365 /// <summary>Deletes the selected checks.</summary>
303 private void DeleteCheckButton_Click(object sender, EventArgs e) 366 private void DeleteCheckButton_Click(object sender, EventArgs e)
304 { 367 {
305 DeleteSelectedChecks(); 368 DeleteSelectedChecks();
306 UpdateServer(); 369 UpdateServer();
307 } 370 }
308 371
372 /// <summary>Shows an icon next to each check indicating the last execution status.</summary>
309 private void CheckGrid_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e) 373 private void CheckGrid_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
310 { 374 {
311 if (e.ColumnIndex == StatusColumn.Index) 375 if (e.ColumnIndex == StatusColumn.Index)
312 { 376 {
313 e.Value = ((CheckStatus)e.Value).GetImage(); 377 e.Value = ((CheckStatus)e.Value).GetImage();
314 } 378 }
315 } 379 }
316 380
381 /// <summary>Shows an icon next to each log entry indicating its execution status.</summary>
317 private void LogGrid_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e) 382 private void LogGrid_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
318 { 383 {
319 if (e.ColumnIndex == LogStatusColumn.Index) 384 if (e.ColumnIndex == LogStatusColumn.Index)
320 { 385 {
321 e.Value = ((CheckStatus)e.Value).GetImage(); 386 e.Value = ((CheckStatus)e.Value).GetImage();
322 } 387 }
323 } 388 }
324 389
390 /// <summary>Refreshes the check filter combo box when the list of checks changes.</summary>
325 private void CheckBindingSource_ListChanged(object sender, ListChangedEventArgs e) 391 private void CheckBindingSource_ListChanged(object sender, ListChangedEventArgs e)
326 { 392 {
327 if (Server?.Checks != null) 393 if (Server?.Checks != null)
328 { 394 {
329 LogCheckComboBox.Items.Clear(); 395 LogCheckComboBox.Items.Clear();
331 LogCheckComboBox.Items.AddRange(Server.Checks.ToArray()); 397 LogCheckComboBox.Items.AddRange(Server.Checks.ToArray());
332 LogCheckComboBox.SelectedIndex = 0; 398 LogCheckComboBox.SelectedIndex = 0;
333 } 399 }
334 } 400 }
335 401
402 /// <summary>Handles showing the check execution log when the Log tab is selected.</summary>
336 private void CheckTabControl_SelectedIndexChanged(object sender, EventArgs e) 403 private void CheckTabControl_SelectedIndexChanged(object sender, EventArgs e)
337 { 404 {
405 // The results grid is not always used, and so is initialized just in time.
338 if (logResults == null && CheckTabControl.SelectedTab == LogTabPage) 406 if (logResults == null && CheckTabControl.SelectedTab == LogTabPage)
339 { 407 {
340 logResults = new BindingList<CheckResult>(monitor.GetLog(Server)); 408 logResults = new BindingList<CheckResult>(monitor.GetLog(Server));
341 LogGrid.DataSource = logResults; 409 LogGrid.DataSource = logResults;
342 } 410 }
343 } 411 }
344 412
413 /// <summary>Shows a hand cursor over the status column as a hint that it can be clicked to jump to the log.</summary>
345 private void CheckGrid_CellMouseEnter(object sender, DataGridViewCellEventArgs e) 414 private void CheckGrid_CellMouseEnter(object sender, DataGridViewCellEventArgs e)
346 { 415 {
347 if (e.ColumnIndex == StatusColumn.Index) 416 if (e.ColumnIndex == StatusColumn.Index)
348 CheckGrid.Cursor = Cursors.Hand; 417 CheckGrid.Cursor = Cursors.Hand;
349 } 418 }
350 419
420 /// <summary>Restores the cursor to its default when leaving the status column.</summary>
351 private void CheckGrid_CellMouseLeave(object sender, DataGridViewCellEventArgs e) 421 private void CheckGrid_CellMouseLeave(object sender, DataGridViewCellEventArgs e)
352 { 422 {
353 if (e.ColumnIndex == StatusColumn.Index) 423 if (e.ColumnIndex == StatusColumn.Index)
354 CheckGrid.Cursor = Cursors.Default; 424 CheckGrid.Cursor = Cursors.Default;
355 } 425 }
356 426
427 /// <summary>Jumps to the check log when the status icon is clicked.</summary>
357 private void CheckGrid_CellClick(object sender, DataGridViewCellEventArgs e) 428 private void CheckGrid_CellClick(object sender, DataGridViewCellEventArgs e)
358 { 429 {
359 if (e.ColumnIndex == StatusColumn.Index) 430 if (e.ColumnIndex == StatusColumn.Index)
360 ShowLog((Check)CheckBindingSource[e.RowIndex]); 431 ShowLog((Check)CheckBindingSource[e.RowIndex]);
361 } 432 }
362 433
434 /// <summary>Enables or disables a check when the enable column is clicked.</summary>
363 private void CheckGrid_CellContentClick(object sender, DataGridViewCellEventArgs e) 435 private void CheckGrid_CellContentClick(object sender, DataGridViewCellEventArgs e)
364 { 436 {
365 // The status column is set to read-only, and updates are manually done here. 437 // The status column is set to read-only, and updates are manually done here.
366 // Otherwise, the change doesn't take effect until the cell loses focus. 438 // Otherwise, the change doesn't take effect until the cell loses focus.
367 if (e.ColumnIndex == EnabledColumn.Index) 439 if (e.ColumnIndex == EnabledColumn.Index)
368 { 440 {
369 Check check = (Check)CheckBindingSource[e.RowIndex]; 441 Check check = (Check)CheckBindingSource[e.RowIndex];
370 check.Enabled = !(bool)CheckGrid.Rows[e.RowIndex].Cells[e.ColumnIndex].Value; ; 442 check.Enabled = !(bool)CheckGrid.Rows[e.RowIndex].Cells[e.ColumnIndex].Value;
371 Server.UpdateCheck(check); 443 Server.UpdateCheck(check);
372 } 444 }
373 } 445 }
374 446
447 /// <summary>Refreshes the log when a log filter control is changed.</summary>
375 private void FilterChanged(object sender, EventArgs e) 448 private void FilterChanged(object sender, EventArgs e)
376 { 449 {
450 // Determine which check statuses to show.
377 filteredStatuses = filterChecks.Where(fc => fc.Key.Checked).Select(fc => fc.Value).ToArray(); 451 filteredStatuses = filterChecks.Where(fc => fc.Key.Checked).Select(fc => fc.Value).ToArray();
378 if (filteredStatuses.Length == filterChecks.Count && LogCheckComboBox.SelectedIndex == 0) { 452 if (filteredStatuses.Length == filterChecks.Count && LogCheckComboBox.SelectedIndex == 0) {
453 // If all statuses are shown and no check is selected, show all log entries.
379 LogGrid.DataSource = logResults; 454 LogGrid.DataSource = logResults;
455 // Unset the filtered list so it can be garbage collected.
380 logResultsFiltered = null; 456 logResultsFiltered = null;
381 } 457 }
382 else 458 else
383 { 459 {
460 // If any filter is applied, create and display a new list with the filtered log entries.
384 logResultsFiltered = new BindingList<CheckResult>(logResults.Where(result => MatchesFilter(result)).ToList()); 461 logResultsFiltered = new BindingList<CheckResult>(logResults.Where(result => MatchesFilter(result)).ToList());
385 LogGrid.DataSource = logResultsFiltered; 462 LogGrid.DataSource = logResultsFiltered;
386 } 463 }
387 } 464 }
388 465
466 /// <summary>Stops the taskbar button flashing when the window receives focus.</summary>
389 private void ServerForm_Activated(object sender, EventArgs e) 467 private void ServerForm_Activated(object sender, EventArgs e)
390 { 468 {
391 Win32Helpers.StopFlashWindowEx(this); 469 Win32Helpers.StopFlashWindowEx(this);
392 } 470 }
393 471
472 /// <summary>Opens a file browser to select a private key file.</summary>
394 private void KeyBrowseButton_Click(object sender, EventArgs e) 473 private void KeyBrowseButton_Click(object sender, EventArgs e)
395 { 474 {
396 OpenFileDialog dialog = new OpenFileDialog() { Title = "Select private key file" }; 475 OpenFileDialog dialog = new OpenFileDialog() { Title = "Select private key file" };
397 if (dialog.ShowDialog() == DialogResult.OK) 476 if (dialog.ShowDialog() == DialogResult.OK)
398 { 477 {
399 KeyTextBox.Text = dialog.FileName; 478 KeyTextBox.Text = dialog.FileName;
400 UpdateServer(); 479 UpdateServer();
401 } 480 }
402 } 481 }
403 482
483 /// <summary>Tests whether a log entry matches the active filter.</summary>
484 /// <param name="result">The log entry to test agains the active filter.</param>
404 private bool MatchesFilter(CheckResult result) 485 private bool MatchesFilter(CheckResult result)
405 { 486 {
406 return filteredStatuses.Contains(result.CheckStatus) && (LogCheckComboBox.SelectedIndex == 0 || LogCheckComboBox.SelectedItem == result.Check); 487 return filteredStatuses.Contains(result.CheckStatus) && (LogCheckComboBox.SelectedIndex == 0 || LogCheckComboBox.SelectedItem == result.Check);
407 } 488 }
408 489
490 /// <summary>Attempts to open the private key when the private key textbox loses focus.</summary>
409 private void KeyTextBox_Leave(object sender, EventArgs e) 491 private void KeyTextBox_Leave(object sender, EventArgs e)
410 { 492 {
411 OpenPrivateKey(monitor, Server, this); 493 OpenPrivateKey(monitor, Server, this);
412 } 494 }
413 495
496 /// <summary>Attempts to open the private key, collecting a password if necessary, and displays a message if it cannot be opened.</summary>
497 /// <param name="monitor">The global server monitor object.</param>
498 /// <param name="server">The server associated with the keyfile to open.</param>
499 /// <param name="owner">The window to use as the owner for password and message boxes.</param>
414 public static void OpenPrivateKey(ServerMonitor monitor, Server server, IWin32Window owner) 500 public static void OpenPrivateKey(ServerMonitor monitor, Server server, IWin32Window owner)
415 { 501 {
502 // Nothing to do if the server does not use a private key or one has not been set up yet.
416 if (server.LoginType != LoginType.PrivateKey || server.KeyFile.IsNullOrEmpty()) 503 if (server.LoginType != LoginType.PrivateKey || server.KeyFile.IsNullOrEmpty())
417 return; 504 return;
418 505
506 // Attempt to open the keyfile.
419 KeyStatus keyStatus = monitor.OpenPrivateKey(server.KeyFile); 507 KeyStatus keyStatus = monitor.OpenPrivateKey(server.KeyFile);
508 // If the key is encrypted and has not been opened yet, ask for the password.
420 if (keyStatus == KeyStatus.NeedPassword) 509 if (keyStatus == KeyStatus.NeedPassword)
421 { 510 {
422 string message = "Private key password for " + server.Name + ":"; 511 string message = "Private key password for " + server.Name + ":";
423 Icon icon = SystemIcons.Question; 512 Icon icon = SystemIcons.Question;
513 // Attempt to open the keyfile until the correct password is entered or Cancel is clicked.
424 while (keyStatus != KeyStatus.Open) 514 while (keyStatus != KeyStatus.Open)
425 { 515 {
516 // Collect the password.
426 string password = InputDialog.ShowDialog(message, icon, owner); 517 string password = InputDialog.ShowDialog(message, icon, owner);
518 // Stop asking if Cancel was clicked.
427 if (password == null) 519 if (password == null)
428 return; 520 return;
521 // Try to open the key using the collected password.
429 keyStatus = monitor.OpenPrivateKey(server.KeyFile, password); 522 keyStatus = monitor.OpenPrivateKey(server.KeyFile, password);
523 // If the password was incorrect, try again with a message saying so.
430 if (keyStatus == KeyStatus.NeedPassword) 524 if (keyStatus == KeyStatus.NeedPassword)
431 { 525 {
432 message = "Incorrect private key password for " + server.Name + ", please try again:"; 526 message = "Incorrect private key password for " + server.Name + ", please try again:";
433 icon = SystemIcons.Error; 527 icon = SystemIcons.Error;
434 } 528 }
435 } 529 }
436 } 530 }
437 else if (keyStatus == KeyStatus.NotAccessible) 531 else if (keyStatus == KeyStatus.NotAccessible)
438 { 532 {
533 // If the private key is not accessible, there is nothing we can do but let the user know.
439 MessageBox.Show("The private key file " + server.KeyFile + " is not accessible or does not exist.", "Cannot open private key", MessageBoxButtons.OK, MessageBoxIcon.Error); 534 MessageBox.Show("The private key file " + server.KeyFile + " is not accessible or does not exist.", "Cannot open private key", MessageBoxButtons.OK, MessageBoxIcon.Error);
440 return; 535 }
441 } 536 }
442 } 537
443 538 /// <summary>Enables or disables the server when the Enabled box is clicked.</summary>
444 private void EnabledCheckBox_Click(object sender, EventArgs e) 539 private void EnabledCheckBox_Click(object sender, EventArgs e)
445 { 540 {
446 bool enabled = EnabledCheckBox.Checked; 541 bool enabled = EnabledCheckBox.Checked;
542 // The private key may not be open yet when enabling a server, so do that now.
447 if (enabled) 543 if (enabled)
448 OpenPrivateKey(monitor, Server, this); 544 OpenPrivateKey(monitor, Server, this);
449 Server.Enabled = enabled; 545 Server.Enabled = enabled;
450 EnabledCheckBox.Checked = Server.Enabled; 546 EnabledCheckBox.Checked = Server.Enabled;
451 } 547 }
452 548
453 //private void EnabledCheckBox_CheckedChanged(object sender, EventArgs e) 549 /// <summary>Updates the title and enabled check box when the server is enabled or disabled.</summary>
454 //{
455 // bool enabled = EnabledCheckBox.Checked;
456 // if (enabled)
457 // OpenPrivateKey(monitor, Server, this);
458 // EnabledCheckBox.Checked = Server.Enabled;
459 //}
460
461 private void Server_EnabledChanged(object sender, EventArgs e) 550 private void Server_EnabledChanged(object sender, EventArgs e)
462 { 551 {
463 SetTitle(); 552 SetTitle();
553 // The server can also be enabled or disabled from the main server
554 // summary form, so update the checkbox when that happens.
464 EnabledCheckBox.Checked = Server.Enabled; 555 EnabledCheckBox.Checked = Server.Enabled;
465 } 556 }
466 } 557 }
467 } 558 }