Mercurial > servermonitor
view ServerMonitor/Forms/ServerSummaryForm.cs @ 18:b713b9db4c82
HTTP checks.
author | Brad Greco <brad@bgreco.net> |
---|---|
date | Mon, 27 May 2019 15:40:44 -0400 |
parents | 7626b099aefd |
children | b3128fe10d57 |
line wrap: on
line source
using NAppUpdate.Framework; using NAppUpdate.Framework.Sources; using ServerMonitorApp.Properties; using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Windows.Forms; namespace ServerMonitorApp { /// <summary>Main application form that shows an overview of all servers.</summary> public partial class ServerSummaryForm : Form { private readonly Dictionary<Server, ServerForm> serverForms = new Dictionary<Server, ServerForm>(); private ServerMonitor monitor; public ServerSummaryForm() { InitializeComponent(); } private void ServerSummaryForm_Load(object sender, EventArgs e) { // Restore the window size from the previous session. Size size = Settings.Default.SummaryFormSize; if (size.Height > 0 && size.Width > 0) Size = size; // Resize the images in buttons to fit the button size. Helpers.FormatImageButton(NewServerButton); Helpers.FormatImageButton(SettingsButton); // Create the global server monitor object. monitor = new ServerMonitor(this); // Load the server configuration file. while (true) { try { // If the configuration file is loaded successfully, proceed. monitor.LoadServers(); break; } catch (Exception ex) { // If there was an error loading the config file, show it. DialogResult result = MessageBox.Show("Could not load servers. Please fix or delete the file " + monitor.ConfigFile + Environment.NewLine + Environment.NewLine + "Error details:" + Environment.NewLine + ex.GetAllMessages(), "Error loading servers", MessageBoxButtons.RetryCancel, MessageBoxIcon.Error); // If the error message was cancelled, exit. Otherwise, retry by continuing the loop. if (result == DialogResult.Cancel) { Environment.Exit(1); } } } // Listen to server monitor events. monitor.CheckStatusChanged += Monitor_CheckStatusChanged; // Show the servers. RefreshDisplay(); // If any servers have encrypted private keys, attempt to open them immediately // rather than interrupting the user later when they are first used. CollectPrivateKeyPasswords(); CheckForUpdate(); } /// <summary>Shows a form to edit or create a server.</summary> /// <param name="server">The server to edit. If null, a new server will be created.</param> /// <param name="activate">Whether the server form should be activated.</param> /// <returns>The created or existing server for for the server.</returns> private ServerForm ShowServerForm(Server server, bool activate = true) { bool isNewServer = false; if (server == null) { // Create a new server if none was given. server = new Server(); // The server is added to the server monitor immediately so that // checks can be created and run. If the server was created by // mistake, it will automatically be removed when the form is closed // as long as no information was entered into the form. monitor.AddServer(server); isNewServer = true; } if (serverForms.TryGetValue(server, out ServerForm form)) { // If the server form is already open, just activate it if requested. if (activate) form.Activate(); } else { // Open a new server form for the server. form = new ServerForm(monitor, server, isNewServer); // Keep track of the form so it can be activated later if the server is clicked again. serverForms[server] = form; form.FormClosing += ServerForm_FormClosing; form.Show(activate); } return form; } /// <summary>Refreshes the server list with the server monitor data.</summary> private void RefreshDisplay() { // Delete all server controls and recreate them. ServerPanel.Controls.Clear(); foreach (Server server in monitor.Servers) { // Subscribe to server events. server.EnabledChanged -= Server_EnabledChanged; server.EnabledChanged += Server_EnabledChanged; // Create a server control and add it to the panel. ServerSummaryControl control = new ServerSummaryControl(server); control.ContextMenuStrip = ServerContextMenu; control.Click += ServerSummaryControl_Click; ServerPanel.Controls.Add(control); } // Refresh the form icon that depends on the status of all servers. UpdateIcon(); } /// <summary>Refreshes a single server control.</summary> /// <param name="server">The server to refresh.</param> private void RefreshServer(Server server) { ServerPanel.Controls.Cast<ServerSummaryControl>().FirstOrDefault(c => c.Server == server).Refresh(); // The server's status might have changed, so refresh the form icon. UpdateIcon(); } /// <summary>Flashes the taskbar button for a server form.</summary> /// <param name="check">The check that needs attention.</param> public void AlertServerForm(Check check) { // Show the form, but don't activate it since the user did not initiate this event. ServerForm form = ShowServerForm(check.Server, false); // Flash the taskbar button. Win32Helpers.FlashWindowEx(form); // If the form was not already open, focus the Log tab and display // only the log entries for this check. Do not do this if the form // was already open since the user might be in the middle of doing // something with it. if (!serverForms.ContainsKey(check.Server)) { form.ShowLog(check); } } /// <summary>Shows a balloon popup with the results of a failed check.</summary> /// <param name="check">The check that failed.</param> public void ShowBalloon(CheckResult result) { string title = string.Format("{0}: {1} failed", result.Check.Server.Name, result.Check.Name); NotifyIcon.Tag = result; NotifyIcon.ShowBalloonTip(30000, title, result.Message, GetToolTipIcon(result.CheckStatus)); } /// <summary>Updates the form icon to reflect a summary of the status of all servers.</summary> private void UpdateIcon() { // The status for the summary icon is the most severe status of all servers. // When performing the comparison: // - Enabled servers use their current status. // - If a server is disabled due to a locked private key, report a warning. // Otherwise, report success to effectively ignore it. // The integer value of the CheckStatus enum increases with the severity, // so the maximum value of all servers gives the most severe status. CheckStatus status = monitor.Servers .Select(s => s.Enabled ? s.Status : s.KeyStatus == KeyStatus.NeedPassword ? CheckStatus.Warning : CheckStatus.Success) .DefaultIfEmpty(CheckStatus.Success) .Max(); Icon = status.GetIcon(); NotifyIcon.Icon = Icon; } /// <summary>Prompts the user for the passwords to open all encrypted private keys.</summary> private void CollectPrivateKeyPasswords() { // List of paths to keyfiles. List<string> triedKeys = new List<string>(); foreach (Server server in monitor.Servers) { // If the same private key is used for multiple servers, don't prompt // the user multiple times to open the same keyfile. if (triedKeys.Contains(server.KeyFile)) continue; // Show the prompt. ServerForm.OpenPrivateKey(monitor, server, this); // Keep track of the keyfile so we don't needlessly ask again. triedKeys.Add(server.KeyFile); } } /// <summary>Refreshes a server control when the server state changes.</summary> private void Server_EnabledChanged(object sender, EventArgs e) { RefreshServer((Server)sender); } /// <summary>Refreshes a server control when the server status might have changed.</summary> private void Monitor_CheckStatusChanged(object sender, CheckStatusChangedEventArgs e) { if (e.CheckResult != null) { RefreshServer(e.Check.Server); } } /// <summary>Gets a Windows tooltip icon based on the severity of the message.</summary> /// <param name="status">The status of the check that will be reported in the balloon tip.</param> private ToolTipIcon GetToolTipIcon(CheckStatus status) { switch (status) { case CheckStatus.Error: return ToolTipIcon.Error; case CheckStatus.Warning: return ToolTipIcon.Warning; case CheckStatus.Information: return ToolTipIcon.Info; default: return ToolTipIcon.None; } } /// <summary>Shows a server form when a server control is clicked.</summary> private void ServerSummaryControl_Click(object sender, EventArgs e) { ShowServerForm(((ServerSummaryControl)sender).Server); } /// <summary>Handles the closing of a server form.</summary> private void ServerForm_FormClosing(object sender, FormClosingEventArgs e) { ServerForm form = (ServerForm)sender; form.FormClosing -= ServerForm_FormClosing; Server server = form.Server; // Remove the closed form from the list of open forms. serverForms.Remove(form.Server); // If there is no user data associated with the server, it can be deleted. // This usually happens when the New Server button is clicked and the server form // is closed without entering any information. if (server.IsEmpty()) { monitor.DeleteServer(server); } RefreshDisplay(); } /// <summary>Shows a server form to create a new server.</summary> private void NewServerButton_Click(object sender, EventArgs e) { ShowServerForm(null); } /// <summary>Hides the main form instead of exiting the application based on user preferences.</summary> /// <remarks>Allows the monitor to run in the background without being shown in the taskbar.</remarks> private void ServerSummaryForm_FormClosing(object sender, FormClosingEventArgs e) { if ((e.CloseReason == CloseReason.None || e.CloseReason == CloseReason.UserClosing) && Settings.Default.HideToNotificationArea) { Hide(); e.Cancel = true; } } /// <summary>Shows the settings form.</summary> private void SettingsButton_Click(object sender, EventArgs e) { new SettingsForm().Show(); } /// <summary>Shows the details of a failed check when the balloon notification is clicked.</summary> private void NotifyIcon_BalloonTipClicked(object sender, EventArgs e) { CheckResult result = (CheckResult)(sender as NotifyIcon).Tag; ServerForm form = ShowServerForm(result.Check.Server); form.ShowLog(result.Check); form.WindowState = FormWindowState.Normal; } /// <summary>Handles the server context menu.</summary> private void ServerContextMenu_ItemClicked(object sender, ToolStripItemClickedEventArgs e) { Server server = GetClickedServer((ContextMenuStrip)e.ClickedItem.Owner); if (e.ClickedItem == DeleteServerMenuItem) { // Close the menu immediately so it doesn't stay open while the messagebox is shown. ServerContextMenu.Close(); // Show the server delete confirmation dialog. No option to not ask again // since it's a rare and very destructive event. DialogResult result = MessageBox.Show( string.Format("The server \"{0}\" and its {1} {2} will be deleted.", server.Name, server.Checks.Count, server.Checks.Count == 1 ? "check" : "checks"), "Delete server", MessageBoxButtons.OKCancel, MessageBoxIcon.Warning); if (result == DialogResult.OK) { monitor.DeleteServer(server); RefreshDisplay(); } } else if (e.ClickedItem == ToggleEnableServerMenuItem) { bool enable = ToggleEnableServerMenuItem.Text == "Enable"; if (enable) { // Close the menu immediately so it doesn't stay open while the messagebox is shown. ServerContextMenu.Close(); // Attempt to open the private key for the server immediately since it has not // been opened yet. ServerForm.OpenPrivateKey(monitor, server, this); } server.Enabled = enable; RefreshDisplay(); } } /// <summary>Activates the appropriate Enable/Disable menu option based on the server's current state.</summary> private void ServerContextMenu_Opening(object sender, CancelEventArgs e) { Server server = GetClickedServer((ContextMenuStrip)sender); ToggleEnableServerMenuItem.Text = server.Enabled ? "Disable" : "Enable"; } /// <summary>Gets the server corresponding to a server context menu.</summary> private Server GetClickedServer(ContextMenuStrip menu) { return ((ServerSummaryControl)menu.SourceControl).Server; } /// <summary>Saves the window size after it is resized so it can be restored next time the program is run.</summary> private void ServerSummaryForm_ResizeEnd(object sender, EventArgs e) { Settings.Default.SummaryFormSize = Size; Settings.Default.Save(); } /// <summary>Shows the main form when the WM_SHOWMONITOR message is received.</summary> /// <remarks> /// This is used to make this a single-instance application. When a second copy of the program /// is launched, it sends this message to activate the first copy and then exits. /// </remarks> protected override void WndProc(ref Message m) { if (m.Msg == Win32Helpers.WM_SHOWMONITOR) ShowWindow(); base.WndProc(ref m); } /// <summary>Handles clicks on the notification area icon.</summary> private void NotifyIcon_MouseClick(object sender, MouseEventArgs e) { if (e.Button == MouseButtons.Left) ShowWindow(); else if (e.Button == MouseButtons.Right) NotificationIconMenu.Show(); } /// <summary>Shows the window.</summary> private void ShowWindow() { if (WindowState == FormWindowState.Minimized) WindowState = FormWindowState.Normal; // Do various things to try to get this window on top. // We only do this as a result of user input, so it's ok. ;) Show(); TopMost = true; TopMost = false; Activate(); } /// <summary>Handles clicks on the notification icon menu.</summary> private void NotificationIconMenu_ItemClicked(object sender, ToolStripItemClickedEventArgs e) { if (e.ClickedItem == ShowServerMonitorMenuItem) ShowWindow(); else if (e.ClickedItem == ExitMenuItem) Application.Exit(); } /// <summary>Begins checking for program updates in the background.</summary> private void CheckForUpdate() { UpdateManager manager = UpdateManager.Instance; // Make the update manager happy if the program was just restarted to apply an update. manager.ReinstateIfRestarted(); manager.UpdateSource = new SimpleWebSource("https://www.bgreco.net/test/servermonitor.xml"); if (manager.State == UpdateManager.UpdateProcessState.NotChecked) manager.BeginCheckForUpdates(CheckForUpdatesCallback, null); } /// <summary>Callback after the program update check completes.</summary> private void CheckForUpdatesCallback(IAsyncResult result) { UpdateManager manager = UpdateManager.Instance; if (manager.UpdatesAvailable > 0) { // Extract the new version number from the result. GetUpdateInfo(out string version, out string _); // If the user has not chosen to ignore this update, show a notification. if (Settings.Default.IgnoreUpdate != version) Invoke((MethodInvoker)(() => UpdatePanel.Show())); } } /// <summary>Applies the program updates.</summary> private void PrepareUpdatesCallback(IAsyncResult result) { UpdateManager manager = UpdateManager.Instance; manager.EndCheckForUpdates(result); manager.ApplyUpdates(true); } /// <summary>Shows information about a program update when the notification is clicked.</summary> private void UpdateLabel_Click(object sender, EventArgs e) { // Extract the update information from the update manager result. GetUpdateInfo(out string version, out string changeMessage); string message = "Server Monitor version {0} is available for download." + Environment.NewLine + Environment.NewLine + "What's new:" + Environment.NewLine + "{1}" + Environment.NewLine + Environment.NewLine + "Would you like to download and apply the update now?"; using (UpdateDialog dialog = new UpdateDialog { Message = string.Format(message, version, changeMessage) }) { DialogResult result = dialog.ShowDialog(); // If the user declined the update and asked not to be notified again, // save the preference so they will not be asked again for this version. if (dialog.Checked && result == DialogResult.Cancel) { Settings.Default.IgnoreUpdate = version; Settings.Default.Save(); UpdatePanel.Hide(); } // If Yes was not chosen, do not apply the update. if (result != DialogResult.OK) return; } UpdateManager.Instance.BeginPrepareUpdates(PrepareUpdatesCallback, null); } /// <summary>Extracts the update information from the update manager result.</summary> private void GetUpdateInfo(out string version, out string changeMessage) { // The update description is in the form {version}:{change message}. string[] parts = UpdateManager.Instance.Tasks.First().Description.Split(new char[] { ':' }, 2); version = parts[0]; changeMessage = parts[1]; } } }