changeset 14:2db36ab759de

Add comments.
author Brad Greco <brad@bgreco.net>
date Mon, 22 Apr 2019 21:10:42 -0400
parents a36cc5c123f4
children 23f2e0da1094
files ServerMonitor/Forms/QuickHelpForm.cs ServerMonitor/Forms/ServerForm.cs
diffstat 2 files changed, 121 insertions(+), 25 deletions(-) [+]
line wrap: on
line diff
--- a/ServerMonitor/Forms/QuickHelpForm.cs	Mon Apr 15 19:25:27 2019 -0400
+++ b/ServerMonitor/Forms/QuickHelpForm.cs	Mon Apr 22 21:10:42 2019 -0400
@@ -1,15 +1,10 @@
 using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.Data;
 using System.Drawing;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
 using System.Windows.Forms;
 
 namespace ServerMonitorApp
 {
+    /// <summary>Form for showing help text in a floating popup.</summary>
     public partial class QuickHelpForm : Form
     {
         private string Rtf;
@@ -19,6 +14,8 @@
             InitializeComponent();
         }
 
+        /// <summary>Creates a help popup form with the provided text.</summary>
+        /// <param name="rtf">Text to show in the popup in RTF format.</param>
         public QuickHelpForm(string rtf)
         {
             InitializeComponent();
@@ -27,29 +24,37 @@
 
         private void QuickHelpForm_Load(object sender, EventArgs e)
         {
-            (Owner as CheckForm).HelpLocationChanged += Owner_HelpPositionChanged;
-            HelpTextBox.ContentsResized += HelpTextBox_ContentsResized; // Causes form to resize after Rtf is assigned
+            // Subscribe to the owner's notifications that the location of
+            // the Help button that triggered this popup has changed.
+            (Owner as CheckForm).HelpLocationChanged += Owner_HelpLocationChanged;
+            // Subscribe to this event before setting the text box's text
+            // so we can resize the form to match the text size.
+            HelpTextBox.ContentsResized += HelpTextBox_ContentsResized;
             HelpTextBox.Rtf = Rtf;
         }
 
+        /// <summary>Resizes the popup form to fit the displayed text.</summary>
         private void HelpTextBox_ContentsResized(object sender, ContentsResizedEventArgs e)
         {
             Height = e.NewRectangle.Height + 12;
         }
 
-        private void Owner_HelpPositionChanged(object sender, HelpLocationChangedEventArgs e)
+        /// <summary>Moves the popup form to always appear near the Help button that triggered it.</summary>
+        private void Owner_HelpLocationChanged(object sender, HelpLocationChangedEventArgs e)
         {
             Point location = e.HelpLocation;
             location.Offset(24, 0);
             Location = location;
         }
 
+        // Hides the popup form when ESC is pressed.
         private void Control_KeyDown(object sender, KeyEventArgs e)
         {
             if (e.KeyCode == Keys.Escape)
                 Close();
         }
 
+        // Hides the popup form when it is clicked.
         private void Control_Click(object sender, EventArgs e)
         {
             Close();
--- a/ServerMonitor/Forms/ServerForm.cs	Mon Apr 15 19:25:27 2019 -0400
+++ b/ServerMonitor/Forms/ServerForm.cs	Mon Apr 22 21:10:42 2019 -0400
@@ -1,19 +1,16 @@
-using Renci.SshNet;
-using Renci.SshNet.Common;
-using ServerMonitorApp.Properties;
+using ServerMonitorApp.Properties;
 using System;
 using System.Collections.Generic;
 using System.ComponentModel;
 using System.Data;
 using System.Drawing;
-using System.IO;
 using System.Linq;
-using System.Text;
 using System.Threading.Tasks;
 using System.Windows.Forms;
 
 namespace ServerMonitorApp
 {
+    /// <summary>Form for adding or editing a server and managing its checks.</summary>
     public partial class ServerForm : Form
     {
         private bool isNewServer;
@@ -25,18 +22,26 @@
         private readonly Dictionary<int, CheckForm> checkForms = new Dictionary<int, CheckForm>();
         private readonly Dictionary<CheckBox, CheckStatus> filterChecks;
 
+        /// <summary>The server being edited.</summary>
         public Server Server { get; private set; }
 
+        /// <summary>The checks currently selected by the user.</summary>
         private IEnumerable<Check> SelectedChecks => CheckGrid.SelectedRows.Cast<DataGridViewRow>().Select(r => r.DataBoundItem).Cast<Check>();
 
+        /// <summary>The first check currently selected by the user.</summary>
         private Check SelectedCheck => SelectedChecks.FirstOrDefault();
 
+        /// <summary>ServerForm constructor.</summary>
+        /// <param name="monitor">The global server monitor object.</param>
+        /// <param name="server">The server to edit.</param>
+        /// <param name="isNewServer">Whether an existing server is being edited or a new server is being created.</param>
         public ServerForm(ServerMonitor monitor, Server server, bool isNewServer = false)
         {
             InitializeComponent();
             this.monitor = monitor;
             this.isNewServer = isNewServer;
             Server = server;
+            // Associates filter check boxes with their corresponding check statuses.
             filterChecks = new Dictionary<CheckBox, CheckStatus>
             {
                 { LogSuccessCheckBox, CheckStatus.Success },
@@ -48,16 +53,20 @@
 
         private void ServerForm_Load(object sender, EventArgs e)
         {
+            // Bind the Check grid to the server's checks.
             CheckBindingSource.DataSource = Server.Checks;
             monitor.CheckStatusChanged += Monitor_CheckStatusChanged;
+            // Deselect the default first row selection.
             CheckGrid.ClearSelection();
             if (isNewServer)
             {
+                // Set defaults for a new server.
                 LoginComboBox.SelectedIndex = 0;
                 Icon = CheckStatus.Success.GetIcon();
             }
             else
             {
+                // Update inputs with the server information.
                 SetTitle();
                 SetIcon();
                 NameTextBox.Text = Server.Name;
@@ -71,14 +80,20 @@
                 changedPassword = false;
             }
 
+            // After the input controls have been initialized, bind change listeners to them.
             BindChangeListeners();
+            // Resize the images in buttons to fit the button size.
             FormatImageButtons();
+            // Set the Run and Edit buttons to their default state.
             UpdateCheckButtons();
 
+            // Focus the name text box if it is empty so the user can start typing immediately.
             if (NameTextBox.Text == string.Empty)
                 ActiveControl = NameTextBox;
         }
 
+        /// <summary>Shows the form.</summary>
+        /// <param name="activate">Whether the form should be activated. Otherwise, it will be shown minimized.</param>
         public void Show(bool activate)
         {
             if (!activate)
@@ -86,21 +101,26 @@
             Show();
         }
 
+        /// <summary>Updates the form when the status of a check changes.</summary>
         private void Monitor_CheckStatusChanged(object sender, CheckStatusChangedEventArgs e)
         {
+            // Ignore events for checks that belong to other servers.
             if (e.Check.Server != Server)
                 return;
+            // Refresh the check display with the updated values.
             CheckGrid.Refresh();
             SetIcon();
+            // If there is a result, and the log grid has been initialized, append the log entry.
             if (e.CheckResult != null && logResults != null)
             {
                 logResults.Insert(0, e.CheckResult);
+                // If a filter is applied, also append the log to the filtered list if it matches.
                 if (logResultsFiltered != null && MatchesFilter(e.CheckResult))
                     logResultsFiltered.Insert(0, e.CheckResult);
             }
         }
 
-        /// <summary>Update the server with the current input values</summary>
+        /// <summary>Updates the server with the current input values.</summary>
         /// <param name="forceSave">
         /// If true, immediately update the config file on disk.
         /// If false, only update the config file if it has not been recently updated.
@@ -117,6 +137,8 @@
             if (changedPassword)
                 Server.Password = PasswordTextBox.Text;
 
+            // If a force save is not requested, only save if the last save time is
+            // old enough so we don't end up writing out the file on every keystroke.
             if (forceSave || lastSaveTime < DateTime.Now.AddSeconds(-5))
             {
                 lastSaveTime = DateTime.Now;
@@ -124,6 +146,7 @@
             }
         }
 
+        /// <summary>Sets the window title and header based on the server name and state.</summary>
         private void SetTitle(string title = null)
         {
             title = (title ?? Server.Name) + (Server.Enabled ? "" : " (disabled)");
@@ -131,57 +154,70 @@
             TitleLabel.Text = title;
         }
 
+        /// <summary>Sets the window icon based on the status of the server.</summary>
         private void SetIcon()
         {
             if (Server != null)
                 Icon = Server.Status.GetIcon();
         }
 
+        /// <summary>Updates the window title when the server name changes.</summary>
         private void NameTextBox_TextChanged(object sender, EventArgs e)
         {
             SetTitle(NameTextBox.Text);
         }
 
+        /// <summary>Shows the password or private key controls when the login type changes.</summary>
         private void LoginComboBox_SelectedIndexChanged(object sender, EventArgs e)
         {
             if (LoginComboBox.SelectedIndex == (int)LoginType.PrivateKey)
             {
+                // Show private key controls.
                 PasswordTextBox.Visible = false;
                 KeyTextBox.Visible = true;
                 KeyBrowseButton.Visible = true;
             }
             else
             {
+                // Show password controls.
                 PasswordTextBox.Visible = true;
                 KeyTextBox.Visible = false;
                 KeyBrowseButton.Visible = false;
             }
         }
 
+        /// <summary>Shows a form to create a new check.</summary>
         private void NewCheckButton_Click(object sender, EventArgs e)
         {
             ShowCheckForm(null);
         }
 
+        /// <summary>Shows a form to edit the selected check.</summary>
         private void EditCheckButton_Click(object sender, EventArgs e)
         {
             EditSelectedCheck();
         }
 
+        /// <summary>Executes the selected checks.</summary>
         private void RunButton_Click(object sender, EventArgs e)
         {
             ExecuteChecks(SelectedChecks);
         }
 
+        /// <summary>Executes all checks for the server.</summary>
         private void RunAllButton_Click(object sender, EventArgs e)
         {
             ExecuteChecks(Server.Checks);
         }
 
+        /// <summary>Shows a form to create or edit a check.</summary>
+        /// <param name="check">The check to edit, or null to create a new check.</param>
         private void ShowCheckForm(Check check)
         {
             if (check != null)
             {
+                // Activate the form to edit the check if it is already open.
+                // Otherwise, open a new form.
                 if (!checkForms.TryGetValue(check.Id, out CheckForm form))
                 {
                     form = new CheckForm(monitor, check);
@@ -200,43 +236,55 @@
             }
         }
 
+        /// <summary>Shows a form to edit the selected check.</summary>
         private void EditSelectedCheck()
         {
             ShowCheckForm(SelectedCheck);
         }
 
+        /// <summary>Deletes the selected checks.</summary>
         private void DeleteSelectedChecks()
         {
+            // Prompt to delete unless the "Do not ask again" setting has been set.
             if (Settings.Default.ConfirmDeleteCheck)
             {
                 string message = "Delete " + (SelectedChecks.Count() == 1 ? "\"" + SelectedCheck.Name + "\"" : "selected checks") + "?";
                 using (CheckBoxDialog dialog = new CheckBoxDialog { Message = message })
                 {
                     DialogResult result = dialog.ShowDialog();
+                    // Save the "Do not ask again" setting only if OK was clicked.
                     if (dialog.Checked && result == DialogResult.OK)
                     {
                         Settings.Default.ConfirmDeleteCheck = false;
                         Settings.Default.Save();
                     }
+                    // Do nothing if Cancel was clicked.
                     if (result != DialogResult.OK)
                         return;
                 }
             }
+            // If OK was clicked or no confirmation was shown, delete the checks.
             foreach (Check check in SelectedChecks)
                 Server.DeleteCheck(check);
         }
 
+        /// <summary>Executes the selected checks.</summary>
         private async void ExecuteChecks(IEnumerable<Check> checks)
         {
             await Task.WhenAll(checks.Select(c => monitor.ExecuteCheckAsync(c)));
         }
 
+        /// <summary>Shows the execution history for a check.</summary>
+        /// <param name="check">The check to show execution history for.</param>
         public void ShowLog(Check check)
         {
+            // Switch to the Log tab.
             CheckTabControl.SelectedTab = LogTabPage;
+            // Filter the list to only show history for this check.
             LogCheckComboBox.SelectedItem = check;
         }
 
+        /// <summary>Sets the enabled state of buttons based on whether checks are selected.</summary>
         private void UpdateCheckButtons()
         {
             RunAllButton.Enabled = CheckGrid.RowCount > 0;
@@ -244,6 +292,7 @@
             EditCheckButton.Enabled = CheckGrid.SelectedRows.Count == 1;
         }
 
+        /// <summary>Resizes the images in buttons to fit the button size.</summary>
         private void FormatImageButtons()
         {
             Button[] buttons = new Button[] { NewCheckButton, RunAllButton, RunButton, EditCheckButton, DeleteCheckButton };
@@ -251,46 +300,59 @@
                 Helpers.FormatImageButton(button);
         }
 
+        /// <summary>Binds change listeners to most input controls.</summary>
         private void BindChangeListeners()
         {
             Server.EnabledChanged += Server_EnabledChanged;
+            // Update the server with text box values.
             foreach (Control control in ServerInfoPanel.Controls.OfType<Control>().Where(c => c is TextBox))
                 control.TextChanged += (sender, e) => UpdateServer(false);
+            // Update the server with combo box values.
             foreach (Control control in ServerInfoPanel.Controls.OfType<Control>().Where(c => c is ComboBox))
                 control.TextChanged += (sender, e) => UpdateServer();
+            // Apply the log filter when the filter checkboxes are changed.
             foreach (CheckBox control in LogTabPage.Controls.OfType<CheckBox>())
                 control.CheckedChanged += FilterChanged;
         }
 
+        /// <summary>Handles the closing of a check form.</summary>
         private void CheckForm_FormClosing(object sender, FormClosingEventArgs e)
         {
+            // Remove the form from the list of open forms.
             CheckForm form = (CheckForm)sender;
             form.FormClosing -= CheckForm_FormClosing;
             checkForms.Remove(form.CheckId);
+            // Refresh the check grid if the check was modified.
             if (form.DialogResult == DialogResult.OK)
                 CheckGrid.Refresh();
         }
 
+        /// <summary>Updates the state of buttons based on the check selection.</summary>
         private void CheckGrid_SelectionChanged(object sender, EventArgs e)
         {
             UpdateCheckButtons();
         }
 
+        /// <summary>Sets a flag indicating the password text box contains a real password that should be saved.</summary>
+        /// <remarks>When the form is loaded, the password text box is populated with literal asterisks, not the saved password.</remarks>
         private void PasswordTextBox_TextChanged(object sender, EventArgs e)
         {
             changedPassword = true;
         }
 
+        /// <summary>Saves the server when the form is closed.</summary>
         private void ServerForm_FormClosing(object sender, FormClosingEventArgs e)
         {
             UpdateServer();
         }
 
+        /// <summary>Edits the selected check when it is double clicked.</summary>
         private void CheckGrid_CellDoubleClick(object sender, DataGridViewCellEventArgs e)
         {
             EditSelectedCheck();
         }
 
+        /// <summary>Edits the selected check when ENTER is pressed.</summary>
         private void CheckGrid_KeyDown(object sender, KeyEventArgs e)
         {
             if (e.KeyCode == Keys.Enter)
@@ -300,12 +362,14 @@
             }
         }
 
+        /// <summary>Deletes the selected checks.</summary>
         private void DeleteCheckButton_Click(object sender, EventArgs e)
         {
             DeleteSelectedChecks();
             UpdateServer();
         }
 
+        /// <summary>Shows an icon next to each check indicating the last execution status.</summary>
         private void CheckGrid_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
         {
             if (e.ColumnIndex == StatusColumn.Index)
@@ -314,6 +378,7 @@
             }
         }
 
+        /// <summary>Shows an icon next to each log entry indicating its execution status.</summary>
         private void LogGrid_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
         {
             if (e.ColumnIndex == LogStatusColumn.Index)
@@ -322,6 +387,7 @@
             }
         }
 
+        /// <summary>Refreshes the check filter combo box when the list of checks changes.</summary>
         private void CheckBindingSource_ListChanged(object sender, ListChangedEventArgs e)
         {
             if (Server?.Checks != null)
@@ -333,8 +399,10 @@
             }
         }
 
+        /// <summary>Handles showing the check execution log when the Log tab is selected.</summary>
         private void CheckTabControl_SelectedIndexChanged(object sender, EventArgs e)
         {
+            // The results grid is not always used, and so is initialized just in time.
             if (logResults == null && CheckTabControl.SelectedTab == LogTabPage)
             {
                 logResults = new BindingList<CheckResult>(monitor.GetLog(Server));
@@ -342,24 +410,28 @@
             }
         }
 
+        /// <summary>Shows a hand cursor over the status column as a hint that it can be clicked to jump to the log.</summary>
         private void CheckGrid_CellMouseEnter(object sender, DataGridViewCellEventArgs e)
         {
             if (e.ColumnIndex == StatusColumn.Index)
                 CheckGrid.Cursor = Cursors.Hand;
         }
 
+        /// <summary>Restores the cursor to its default when leaving the status column.</summary>
         private void CheckGrid_CellMouseLeave(object sender, DataGridViewCellEventArgs e)
         {
             if (e.ColumnIndex == StatusColumn.Index)
                 CheckGrid.Cursor = Cursors.Default;
         }
 
+        /// <summary>Jumps to the check log when the status icon is clicked.</summary>
         private void CheckGrid_CellClick(object sender, DataGridViewCellEventArgs e)
         {
             if (e.ColumnIndex == StatusColumn.Index)
                 ShowLog((Check)CheckBindingSource[e.RowIndex]);
         }
 
+        /// <summary>Enables or disables a check when the enable column is clicked.</summary>
         private void CheckGrid_CellContentClick(object sender, DataGridViewCellEventArgs e)
         {
             // The status column is set to read-only, and updates are manually done here.
@@ -367,30 +439,37 @@
             if (e.ColumnIndex == EnabledColumn.Index)
             {
                 Check check = (Check)CheckBindingSource[e.RowIndex];
-                check.Enabled = !(bool)CheckGrid.Rows[e.RowIndex].Cells[e.ColumnIndex].Value; ;
+                check.Enabled = !(bool)CheckGrid.Rows[e.RowIndex].Cells[e.ColumnIndex].Value;
                 Server.UpdateCheck(check);
             }
         }
 
+        /// <summary>Refreshes the log when a log filter control is changed.</summary>
         private void FilterChanged(object sender, EventArgs e)
         {
+            // Determine which check statuses to show.
             filteredStatuses = filterChecks.Where(fc => fc.Key.Checked).Select(fc => fc.Value).ToArray();
             if (filteredStatuses.Length == filterChecks.Count && LogCheckComboBox.SelectedIndex == 0) {
+                // If all statuses are shown and no check is selected, show all log entries.
                 LogGrid.DataSource = logResults;
+                // Unset the filtered list so it can be garbage collected.
                 logResultsFiltered = null;
             }
             else
             {
+                // If any filter is applied, create and display a new list with the filtered log entries.
                 logResultsFiltered = new BindingList<CheckResult>(logResults.Where(result => MatchesFilter(result)).ToList());
                 LogGrid.DataSource = logResultsFiltered;
             }
         }
 
+        /// <summary>Stops the taskbar button flashing when the window receives focus.</summary>
         private void ServerForm_Activated(object sender, EventArgs e)
         {
             Win32Helpers.StopFlashWindowEx(this);
         }
 
+        /// <summary>Opens a file browser to select a private key file.</summary>
         private void KeyBrowseButton_Click(object sender, EventArgs e)
         {
             OpenFileDialog dialog = new OpenFileDialog() { Title = "Select private key file" };
@@ -401,32 +480,47 @@
             }
         }
 
+        /// <summary>Tests whether a log entry matches the active filter.</summary>
+        /// <param name="result">The log entry to test agains the active filter.</param>
         private bool MatchesFilter(CheckResult result)
         {
             return filteredStatuses.Contains(result.CheckStatus) && (LogCheckComboBox.SelectedIndex == 0 || LogCheckComboBox.SelectedItem == result.Check);
         }
 
+        /// <summary>Attempts to open the private key when the private key textbox loses focus.</summary>
         private void KeyTextBox_Leave(object sender, EventArgs e)
         {
             OpenPrivateKey(monitor, Server, this);
         }
 
+        /// <summary>Attempts to open the private key, collecting a password if necessary, and displays a message if it cannot be opened.</summary>
+        /// <param name="monitor">The global server monitor object.</param>
+        /// <param name="server">The server associated with the keyfile to open.</param>
+        /// <param name="owner">The window to use as the owner for password and message boxes.</param>
         public static void OpenPrivateKey(ServerMonitor monitor, Server server, IWin32Window owner)
         {
+            // Nothing to do if the server does not use a private key or one has not been set up yet.
             if (server.LoginType != LoginType.PrivateKey || server.KeyFile.IsNullOrEmpty())
                 return;
 
+            // Attempt to open the keyfile.
             KeyStatus keyStatus = monitor.OpenPrivateKey(server.KeyFile);
+            // If the key is encrypted and has not been opened yet, ask for the password.
             if (keyStatus == KeyStatus.NeedPassword)
             {
                 string message = "Private key password for " + server.Name + ":";
                 Icon icon = SystemIcons.Question;
+                // Attempt to open the keyfile until the correct password is entered or Cancel is clicked.
                 while (keyStatus != KeyStatus.Open)
                 {
+                    // Collect the password.
                     string password = InputDialog.ShowDialog(message, icon, owner);
+                    // Stop asking if Cancel was clicked.
                     if (password == null)
                         return;
+                    // Try to open the key using the collected password.
                     keyStatus = monitor.OpenPrivateKey(server.KeyFile, password);
+                    // If the password was incorrect, try again with a message saying so.
                     if (keyStatus == KeyStatus.NeedPassword)
                     {
                         message = "Incorrect private key password for " + server.Name + ", please try again:";
@@ -436,31 +530,28 @@
             }
             else if (keyStatus == KeyStatus.NotAccessible)
             {
+                // If the private key is not accessible, there is nothing we can do but let the user know.
                 MessageBox.Show("The private key file " + server.KeyFile + " is not accessible or does not exist.", "Cannot open private key", MessageBoxButtons.OK, MessageBoxIcon.Error);
-                return;
             }
         }
 
+        /// <summary>Enables or disables the server when the Enabled box is clicked.</summary>
         private void EnabledCheckBox_Click(object sender, EventArgs e)
         {
             bool enabled = EnabledCheckBox.Checked;
+            // The private key may not be open yet when enabling a server, so do that now.
             if (enabled)
                 OpenPrivateKey(monitor, Server, this);
             Server.Enabled = enabled;
             EnabledCheckBox.Checked = Server.Enabled;
         }
 
-        //private void EnabledCheckBox_CheckedChanged(object sender, EventArgs e)
-        //{
-        //    bool enabled = EnabledCheckBox.Checked;
-        //    if (enabled)
-        //        OpenPrivateKey(monitor, Server, this);
-        //    EnabledCheckBox.Checked = Server.Enabled;
-        //}
-
+        /// <summary>Updates the title and enabled check box when the server is enabled or disabled.</summary>
         private void Server_EnabledChanged(object sender, EventArgs e)
         {
             SetTitle();
+            // The server can also be enabled or disabled from the main server
+            // summary form, so update the checkbox when that happens.
             EnabledCheckBox.Checked = Server.Enabled;
         }
     }