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
{
/// Main application form that shows an overview of all servers.
public partial class ServerSummaryForm : Form
{
private readonly Dictionary serverForms = new Dictionary();
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();
}
/// Shows a form to edit or create a server.
/// The server to edit. If null, a new server will be created.
/// Whether the server form should be activated.
/// The created or existing server for for the server.
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;
}
/// Refreshes the server list with the server monitor data.
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();
}
/// Refreshes a single server control.
/// The server to refresh.
private void RefreshServer(Server server)
{
ServerPanel.Controls.Cast().FirstOrDefault(c => c.Server == server).Refresh();
// The server's status might have changed, so refresh the form icon.
UpdateIcon();
}
/// Flashes the taskbar button for a server form.
/// The check that needs attention.
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);
}
}
/// Shows a balloon popup with the results of a failed check.
/// The check that failed.
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));
}
/// Updates the form icon to reflect a summary of the status of all servers.
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;
}
/// Prompts the user for the passwords to open all encrypted private keys.
private void CollectPrivateKeyPasswords()
{
// List of paths to keyfiles.
List triedKeys = new List();
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);
}
}
/// Refreshes a server control when the server state changes.
private void Server_EnabledChanged(object sender, EventArgs e)
{
RefreshServer((Server)sender);
}
/// Refreshes a server control when the server status might have changed.
private void Monitor_CheckStatusChanged(object sender, CheckStatusChangedEventArgs e)
{
if (e.CheckResult != null)
{
RefreshServer(e.Check.Server);
}
}
/// Gets a Windows tooltip icon based on the severity of the message.
/// The status of the check that will be reported in the balloon tip.
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;
}
}
/// Shows a server form when a server control is clicked.
private void ServerSummaryControl_Click(object sender, EventArgs e)
{
ShowServerForm(((ServerSummaryControl)sender).Server);
}
/// Handles the closing of a server form.
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();
}
/// Shows a server form to create a new server.
private void NewServerButton_Click(object sender, EventArgs e)
{
ShowServerForm(null);
}
/// Hides the main form instead of exiting the application based on user preferences.
/// Allows the monitor to run in the background without being shown in the taskbar.
private void ServerSummaryForm_FormClosing(object sender, FormClosingEventArgs e)
{
if ((e.CloseReason == CloseReason.None || e.CloseReason == CloseReason.UserClosing) && Settings.Default.HideToNotificationArea)
{
Hide();
e.Cancel = true;
}
}
/// Shows the settings form.
private void SettingsButton_Click(object sender, EventArgs e)
{
new SettingsForm().Show();
}
/// Shows the details of a failed check when the balloon notification is clicked.
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;
}
/// Handles the server context menu.
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();
}
}
/// Activates the appropriate Enable/Disable menu option based on the server's current state.
private void ServerContextMenu_Opening(object sender, CancelEventArgs e)
{
Server server = GetClickedServer((ContextMenuStrip)sender);
ToggleEnableServerMenuItem.Text = server.Enabled ? "Disable" : "Enable";
}
/// Gets the server corresponding to a server context menu.
private Server GetClickedServer(ContextMenuStrip menu)
{
return ((ServerSummaryControl)menu.SourceControl).Server;
}
/// Saves the window size after it is resized so it can be restored next time the program is run.
private void ServerSummaryForm_ResizeEnd(object sender, EventArgs e)
{
Settings.Default.SummaryFormSize = Size;
Settings.Default.Save();
}
/// Shows the main form when the WM_SHOWMONITOR message is received.
///
/// 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.
///
protected override void WndProc(ref Message m)
{
if (m.Msg == Win32Helpers.WM_SHOWMONITOR)
ShowWindow();
base.WndProc(ref m);
}
/// Handles clicks on the notification area icon.
private void NotifyIcon_MouseClick(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
ShowWindow();
else if (e.Button == MouseButtons.Right)
NotificationIconMenu.Show();
}
/// Shows the window.
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();
}
/// Handles clicks on the notification icon menu.
private void NotificationIconMenu_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
{
if (e.ClickedItem == ShowServerMonitorMenuItem)
ShowWindow();
else if (e.ClickedItem == ExitMenuItem)
Application.Exit();
}
/// Begins checking for program updates in the background.
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);
}
/// Callback after the program update check completes.
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()));
}
}
/// Applies the program updates.
private void PrepareUpdatesCallback(IAsyncResult result)
{
UpdateManager manager = UpdateManager.Instance;
manager.EndCheckForUpdates(result);
manager.ApplyUpdates(true);
}
/// Shows information about a program update when the notification is clicked.
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);
}
/// Extracts the update information from the update manager result.
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];
}
}
}