# HG changeset patch # User Brad Greco # Date 1546825748 18000 # Node ID 453ecc1ed9eadfc33f12f2a5287a49e27a0c98de # Parent 9e92780ebc0fe868a04a9a24408f3bf399a421c0 Disk space check diff -r 9e92780ebc0f -r 453ecc1ed9ea ServerMonitor/Controls/CheckControl.cs --- a/ServerMonitor/Controls/CheckControl.cs Tue Jan 01 21:14:47 2019 -0500 +++ b/ServerMonitor/Controls/CheckControl.cs Sun Jan 06 20:49:08 2019 -0500 @@ -27,9 +27,12 @@ IEnumerable panels = CheckGroupBox.Controls.OfType(); foreach (Panel panel in panels) { - CheckBox mainCheckBox = panel.Controls.OfType().OrderBy(c => c.Left).First(); - mainCheckBox.CheckedChanged += CheckControl_CheckedChanged; - DisablePanelByCheckBox(mainCheckBox); + CheckBox mainCheckBox = panel.Controls.OfType().OrderBy(c => c.Left).FirstOrDefault(); + if (mainCheckBox != null) + { + mainCheckBox.CheckedChanged += CheckControl_CheckedChanged; + DisablePanelByCheckBox(mainCheckBox); + } } } diff -r 9e92780ebc0f -r 453ecc1ed9ea ServerMonitor/Controls/DiskSpaceCheckControl.Designer.cs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ServerMonitor/Controls/DiskSpaceCheckControl.Designer.cs Sun Jan 06 20:49:08 2019 -0500 @@ -0,0 +1,135 @@ +namespace ServerMonitorApp +{ + partial class DiskSpaceCheckControl + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.ResponseBodyPanel = new System.Windows.Forms.Panel(); + this.FreeSpaceUnitsComboBox = new System.Windows.Forms.ComboBox(); + this.FreeSpaceLabel = new System.Windows.Forms.Label(); + this.FreeSpaceTextBox = new System.Windows.Forms.TextBox(); + this.DeviceLabel = new System.Windows.Forms.Label(); + this.DeviceTextBox = new System.Windows.Forms.TextBox(); + this.CheckGroupBox.SuspendLayout(); + this.ResponseBodyPanel.SuspendLayout(); + this.SuspendLayout(); + // + // CheckGroupBox + // + this.CheckGroupBox.Controls.Add(this.ResponseBodyPanel); + this.CheckGroupBox.Controls.Add(this.DeviceLabel); + this.CheckGroupBox.Controls.Add(this.DeviceTextBox); + this.CheckGroupBox.Size = new System.Drawing.Size(526, 87); + this.CheckGroupBox.Text = "null"; + // + // ResponseBodyPanel + // + this.ResponseBodyPanel.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.ResponseBodyPanel.Controls.Add(this.FreeSpaceUnitsComboBox); + this.ResponseBodyPanel.Controls.Add(this.FreeSpaceLabel); + this.ResponseBodyPanel.Controls.Add(this.FreeSpaceTextBox); + this.ResponseBodyPanel.Location = new System.Drawing.Point(9, 48); + this.ResponseBodyPanel.Name = "ResponseBodyPanel"; + this.ResponseBodyPanel.Size = new System.Drawing.Size(511, 28); + this.ResponseBodyPanel.TabIndex = 21; + // + // FreeSpaceUnitsComboBox + // + this.FreeSpaceUnitsComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.FreeSpaceUnitsComboBox.FormattingEnabled = true; + this.FreeSpaceUnitsComboBox.Items.AddRange(new object[] { + "MB", + "GB", + "percent"}); + this.FreeSpaceUnitsComboBox.Location = new System.Drawing.Point(188, 4); + this.FreeSpaceUnitsComboBox.Name = "FreeSpaceUnitsComboBox"; + this.FreeSpaceUnitsComboBox.Size = new System.Drawing.Size(64, 21); + this.FreeSpaceUnitsComboBox.TabIndex = 23; + // + // FreeSpaceLabel + // + this.FreeSpaceLabel.AutoSize = true; + this.FreeSpaceLabel.Location = new System.Drawing.Point(-3, 7); + this.FreeSpaceLabel.Name = "FreeSpaceLabel"; + this.FreeSpaceLabel.Size = new System.Drawing.Size(110, 13); + this.FreeSpaceLabel.TabIndex = 22; + this.FreeSpaceLabel.Text = "Free space is at least:"; + // + // FreeSpaceTextBox + // + this.FreeSpaceTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.FreeSpaceTextBox.Location = new System.Drawing.Point(113, 4); + this.FreeSpaceTextBox.Name = "FreeSpaceTextBox"; + this.FreeSpaceTextBox.Size = new System.Drawing.Size(69, 20); + this.FreeSpaceTextBox.TabIndex = 7; + // + // DeviceLabel + // + this.DeviceLabel.AutoSize = true; + this.DeviceLabel.Location = new System.Drawing.Point(6, 25); + this.DeviceLabel.Name = "DeviceLabel"; + this.DeviceLabel.Size = new System.Drawing.Size(69, 13); + this.DeviceLabel.TabIndex = 18; + this.DeviceLabel.Text = "File / device:"; + // + // DeviceTextBox + // + this.DeviceTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.DeviceTextBox.Location = new System.Drawing.Point(77, 22); + this.DeviceTextBox.Name = "DeviceTextBox"; + this.DeviceTextBox.Size = new System.Drawing.Size(443, 20); + this.DeviceTextBox.TabIndex = 17; + this.DeviceTextBox.Text = "/"; + // + // DiskSpaceCheckControl + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Name = "DiskSpaceCheckControl"; + this.Size = new System.Drawing.Size(526, 87); + this.Load += new System.EventHandler(this.DiskSpaceCheckControl_Load); + this.CheckGroupBox.ResumeLayout(false); + this.CheckGroupBox.PerformLayout(); + this.ResponseBodyPanel.ResumeLayout(false); + this.ResponseBodyPanel.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.Panel ResponseBodyPanel; + private System.Windows.Forms.TextBox FreeSpaceTextBox; + private System.Windows.Forms.Label DeviceLabel; + private System.Windows.Forms.TextBox DeviceTextBox; + private System.Windows.Forms.ComboBox FreeSpaceUnitsComboBox; + private System.Windows.Forms.Label FreeSpaceLabel; + } +} diff -r 9e92780ebc0f -r 453ecc1ed9ea ServerMonitor/Controls/DiskSpaceCheckControl.cs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ServerMonitor/Controls/DiskSpaceCheckControl.cs Sun Jan 06 20:49:08 2019 -0500 @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Data; +using System.Linq; +using System.Text; +using System.Windows.Forms; + +namespace ServerMonitorApp +{ + [CheckType(typeof(DiskSpaceCheck))] + public partial class DiskSpaceCheckControl : CheckControl + { + public DiskSpaceCheckControl() + { + InitializeComponent(); + } + + private void DiskSpaceCheckControl_Load(object sender, EventArgs e) + { + FreeSpaceUnitsComboBox.SelectedIndex = 1; + } + + public override void LoadCheck(Check check1) + { + DiskSpaceCheck check = (DiskSpaceCheck)check1; + DeviceTextBox.Text = check.Device; + FreeSpaceTextBox.Text = check.MinFreeSpace.ToString(); + FreeSpaceUnitsComboBox.SelectedIndex = (int)check.FreeSpaceUnits; + } + + public override void UpdateCheck(Check check1) + { + DiskSpaceCheck check = (DiskSpaceCheck)check1; + check.Device = DeviceTextBox.Text.Trim(); + check.FreeSpaceUnits = (FreeSpaceUnits)FreeSpaceUnitsComboBox.SelectedIndex; + try + { + check.MinFreeSpace = double.Parse(FreeSpaceTextBox.Text); + } + catch + { + check.MinFreeSpace = 0; + throw new UpdateCheckException("Free space must be numeric."); + } + } + } +} \ No newline at end of file diff -r 9e92780ebc0f -r 453ecc1ed9ea ServerMonitor/Controls/DiskSpaceCheckControl.resx --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ServerMonitor/Controls/DiskSpaceCheckControl.resx Sun Jan 06 20:49:08 2019 -0500 @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff -r 9e92780ebc0f -r 453ecc1ed9ea ServerMonitor/Helpers.cs --- a/ServerMonitor/Helpers.cs Tue Jan 01 21:14:47 2019 -0500 +++ b/ServerMonitor/Helpers.cs Sun Jan 06 20:49:08 2019 -0500 @@ -31,6 +31,11 @@ return aString == null || aString.Trim() == string.Empty; } + public static string ConvertNewlines(this string aString) + { + return aString.Replace("\r\n", "\n").Replace('\r', '\n'); + } + public static T GetAttribute(this Type type) where T : Attribute { return type.GetCustomAttributes(typeof(T), false).SingleOrDefault() as T; diff -r 9e92780ebc0f -r 453ecc1ed9ea ServerMonitor/Objects/Check.cs --- a/ServerMonitor/Objects/Check.cs Tue Jan 01 21:14:47 2019 -0500 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,234 +0,0 @@ -using System; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; -using System.Xml.Serialization; - -namespace ServerMonitorApp -{ - /*public enum CheckType - { - Command - }*/ - - public enum CheckStatus - { - Success, - Information, - Warning, - Error, - Running, - Disabled, - } - - public abstract class Check - { - private static Type[] _checkTypes; - - public static Type[] CheckTypes - { - get - { - return _checkTypes ?? (_checkTypes = typeof(Check).Assembly.GetTypes() - .Where(t => t.IsSubclassOf(typeof(Check))) - .OrderBy(t => t.GetAttribute()?.DisplayWeight).ToArray()); - } - } - - public int Id { get; set; } - - public string Name { get; set; } - - /*public CheckType Type { get; set; }*/ - - public int Timeout { get; set; } - - public bool Enabled { get; set; } - - public Schedule Schedule { get; set; } - - public DateTime LastRunTime { get; set; } - - public DateTime LastScheduledRunTime { get; set; } - - public DateTime NextRunTime { get; set; } - - public string LastMessage { get; set; } - - public CheckStatus Status { get; set; } - - public CheckStatus FailStatus { get; set; } - - [XmlIgnore] - public Server Server { get; set; } - - public Check() - { - FailStatus = CheckStatus.Error; - } - - public override string ToString() - { - return Name; - } - - public virtual string Validate(bool saving = true) - { - string message = string.Empty; - if (Name.IsNullOrEmpty() && saving) - message += "Name cannot be blank." + Environment.NewLine; - return message; - } - - //public virtual CheckStatus Execute() - //{ - // //TODO - // throw new NotImplementedException(); - //} - - public async Task ExecuteAsync(CancellationToken token = default(CancellationToken), bool update = true) - { - //TODO check cancellation token before proceeding - CheckResult result; - DateTime startTime = DateTime.Now; - try - { - Task checkTask = ExecuteCheckAsync(token); - try - { - if (await Task.WhenAny(checkTask, Task.Delay(Timeout, token)) == checkTask) - { - result = await checkTask; - } - else - { - result = Fail("Timed out."); - } - } - catch (TaskCanceledException) - { - return null; - } - } - catch (Exception e) - { - result = Fail(e.GetBaseException().Message); - } - result.StartTime = startTime; - result.EndTime = DateTime.Now; - // If a check is executed from the CheckForm, we don't want to update the status or log the event. - if (update) - { - Status = result.CheckStatus; - LastMessage = result.Message; - LastRunTime = result.EndTime; - } - return result; - } - - protected CheckResult Pass(string message) - { - return new CheckResult(this, CheckStatus.Success, message); - } - - protected CheckResult Fail(string message) - { - return new CheckResult(this, FailStatus, message); - } - - protected CheckResult Fail(Exception e) - { - return new CheckResult(this, FailStatus, e.GetBaseException().Message); - } - - protected virtual CheckResult GetIntResult(int expectedValue, int resultValue, string description) - { - if (expectedValue == resultValue) - return Pass(string.Format("{0}: {1}", description, resultValue)); - else - return Fail(string.Format("{0}: {1} (expected: {2})", description, resultValue, expectedValue)); - } - - protected virtual CheckResult GetStringResult(MatchType matchType, string expectedPattern, bool useRegex, string resultValue, string description) - { - bool match; - if (useRegex) - { - if (matchType.In(MatchType.Equals, MatchType.NotEquals)) - { - if (!expectedPattern.StartsWith("^")) - expectedPattern = "^" + expectedPattern; - if (!expectedPattern.EndsWith("$")) - expectedPattern += "$"; - } - Regex re = new Regex(expectedPattern, RegexOptions.Singleline); - match = re.IsMatch(resultValue); - } - else - { - if (matchType.In(MatchType.Equals, MatchType.NotEquals)) - { - match = expectedPattern == resultValue; - } - else if (matchType.In(MatchType.Contains, MatchType.NotContains)) - { - match = resultValue.Contains(expectedPattern); - } - else - { - if (decimal.TryParse(expectedPattern, out decimal expectedNumeric) && - decimal.TryParse(resultValue, out decimal resultNumeric)) - { - match = (matchType == MatchType.GreaterThan && resultNumeric > expectedNumeric) || - (matchType == MatchType.LessThan && resultNumeric < expectedNumeric); - } - else - { - return Fail(string.Format("{0} is not numeric: {1}", description, resultValue)); - } - } - } - - if (matchType.In(MatchType.Equals, MatchType.Contains)) - { - if (match) - return Pass(string.Format("{0} {1} the pattern: {2}", description, matchType.ToString().ToLower(), expectedPattern)); - else - return Fail(string.Format("{0} does not {1} the pattern: {2} ({0}: {3})", description, matchType.ToString().ToLower().TrimEnd('s'), expectedPattern, resultValue)); - } - else if (matchType.In(MatchType.NotEquals, MatchType.NotContains)) - { - if (match) - return Fail(string.Format("{0} {1} the pattern: {2} ({0}: {3})", description, matchType.ToString().ToLower().Replace("not", ""), expectedPattern, resultValue)); - else - return Pass(string.Format("{0} does not {1} the pattern: {2}", description, matchType.ToString().ToLower().TrimEnd('s').Replace("not", ""), expectedPattern)); - } - else - { - if (match) - return Pass(string.Format("{0} ({1}) is {2} {3}", description, resultValue, matchType.ToString().ToLower().Replace("than", " than"), expectedPattern)); - else - return Fail(string.Format("{0} ({1}) is not {2} {3}", description, resultValue, matchType.ToString().ToLower().Replace("than", " than"), expectedPattern)); - } - } - - protected CheckResult MergeResults(params CheckResult[] results) - { - StringBuilder message = new StringBuilder(); - bool failed = false; - foreach (CheckResult result in results) - { - if (result == null) - continue; - if (result.CheckStatus != CheckStatus.Success) - failed = true; - message.AppendLine(result.Message); - } - return failed ? Fail(message.ToString().Trim()) : Pass(message.ToString().Trim()); - } - - protected abstract Task ExecuteCheckAsync(CancellationToken token); - } -} \ No newline at end of file diff -r 9e92780ebc0f -r 453ecc1ed9ea ServerMonitor/Objects/CheckResult.cs --- a/ServerMonitor/Objects/CheckResult.cs Tue Jan 01 21:14:47 2019 -0500 +++ b/ServerMonitor/Objects/CheckResult.cs Sun Jan 06 20:49:08 2019 -0500 @@ -32,7 +32,7 @@ StartTime.ToString(dateFormat).Replace("T", " "), EndTime.ToString(dateFormat).Replace("T", " "), CheckStatus, - Message.Replace("\r\n", "\n").Replace('\r', '\n').Replace("\n", "\\n")); + Message.ConvertNewlines().Replace("\n", "\\n")); } public static CheckResult FromLogString(Check check, string logString) diff -r 9e92780ebc0f -r 453ecc1ed9ea ServerMonitor/Objects/Checks/Check.cs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ServerMonitor/Objects/Checks/Check.cs Sun Jan 06 20:49:08 2019 -0500 @@ -0,0 +1,234 @@ +using System; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using System.Xml.Serialization; + +namespace ServerMonitorApp +{ + /*public enum CheckType + { + Command + }*/ + + public enum CheckStatus + { + Success, + Information, + Warning, + Error, + Running, + Disabled, + } + + public abstract class Check + { + private static Type[] _checkTypes; + + public static Type[] CheckTypes + { + get + { + return _checkTypes ?? (_checkTypes = typeof(Check).Assembly.GetTypes() + .Where(t => t.IsSubclassOf(typeof(Check))) + .OrderBy(t => t.GetAttribute()?.DisplayWeight).ToArray()); + } + } + + public int Id { get; set; } + + public string Name { get; set; } + + /*public CheckType Type { get; set; }*/ + + public int Timeout { get; set; } + + public bool Enabled { get; set; } + + public Schedule Schedule { get; set; } + + public DateTime LastRunTime { get; set; } + + public DateTime LastScheduledRunTime { get; set; } + + public DateTime NextRunTime { get; set; } + + public string LastMessage { get; set; } + + public CheckStatus Status { get; set; } + + public CheckStatus FailStatus { get; set; } + + [XmlIgnore] + public Server Server { get; set; } + + public Check() + { + FailStatus = CheckStatus.Error; + } + + public override string ToString() + { + return Name; + } + + public virtual string Validate(bool saving = true) + { + string message = string.Empty; + if (Name.IsNullOrEmpty() && saving) + message += "Name cannot be blank." + Environment.NewLine; + return message; + } + + //public virtual CheckStatus Execute() + //{ + // //TODO + // throw new NotImplementedException(); + //} + + public async Task ExecuteAsync(CancellationToken token = default(CancellationToken), bool update = true) + { + //TODO check cancellation token before proceeding + CheckResult result; + DateTime startTime = DateTime.Now; + try + { + Task checkTask = ExecuteCheckAsync(token); + try + { + if (await Task.WhenAny(checkTask, Task.Delay(Timeout, token)) == checkTask) + { + result = await checkTask; + } + else + { + result = Fail("Timed out."); + } + } + catch (TaskCanceledException) + { + return null; + } + } + catch (Exception e) + { + result = Fail(e.GetBaseException().Message); + } + result.StartTime = startTime; + result.EndTime = DateTime.Now; + // If a check is executed from the CheckForm, we don't want to update the status or log the event. + if (update) + { + Status = result.CheckStatus; + LastMessage = result.Message; + LastRunTime = result.EndTime; + } + return result; + } + + public CheckResult Pass(string message) + { + return new CheckResult(this, CheckStatus.Success, message); + } + + public CheckResult Fail(string message) + { + return new CheckResult(this, FailStatus, message); + } + + protected CheckResult Fail(Exception e) + { + return new CheckResult(this, FailStatus, e.GetBaseException().Message); + } + + protected CheckResult GetIntResult(int expectedValue, int resultValue, string description) + { + if (expectedValue == resultValue) + return Pass(string.Format("{0}: {1}", description, resultValue)); + else + return Fail(string.Format("{0}: {1} (expected: {2})", description, resultValue, expectedValue)); + } + + protected CheckResult GetStringResult(MatchType matchType, string expectedPattern, bool useRegex, string resultValue, string description) + { + bool match; + if (useRegex) + { + if (matchType.In(MatchType.Equals, MatchType.NotEquals)) + { + if (!expectedPattern.StartsWith("^")) + expectedPattern = "^" + expectedPattern; + if (!expectedPattern.EndsWith("$")) + expectedPattern += "$"; + } + Regex re = new Regex(expectedPattern, RegexOptions.Singleline); + match = re.IsMatch(resultValue); + } + else + { + if (matchType.In(MatchType.Equals, MatchType.NotEquals)) + { + match = expectedPattern == resultValue; + } + else if (matchType.In(MatchType.Contains, MatchType.NotContains)) + { + match = resultValue.Contains(expectedPattern); + } + else + { + if (decimal.TryParse(expectedPattern, out decimal expectedNumeric) && + decimal.TryParse(resultValue, out decimal resultNumeric)) + { + match = (matchType == MatchType.GreaterThan && resultNumeric > expectedNumeric) || + (matchType == MatchType.LessThan && resultNumeric < expectedNumeric); + } + else + { + return Fail(string.Format("{0} is not numeric: {1}", description, resultValue)); + } + } + } + + if (matchType.In(MatchType.Equals, MatchType.Contains)) + { + if (match) + return Pass(string.Format("{0} {1} the pattern: {2}", description, matchType.ToString().ToLower(), expectedPattern)); + else + return Fail(string.Format("{0} does not {1} the pattern: {2} ({0}: {3})", description, matchType.ToString().ToLower().TrimEnd('s'), expectedPattern, resultValue)); + } + else if (matchType.In(MatchType.NotEquals, MatchType.NotContains)) + { + if (match) + return Fail(string.Format("{0} {1} the pattern: {2} ({0}: {3})", description, matchType.ToString().ToLower().Replace("not", ""), expectedPattern, resultValue)); + else + return Pass(string.Format("{0} does not {1} the pattern: {2}", description, matchType.ToString().ToLower().TrimEnd('s').Replace("not", ""), expectedPattern)); + } + else + { + if (match) + return Pass(string.Format("{0} ({1}) is {2} {3}", description, resultValue, matchType.ToString().ToLower().Replace("than", " than"), expectedPattern)); + else + return Fail(string.Format("{0} ({1}) is not {2} {3}", description, resultValue, matchType.ToString().ToLower().Replace("than", " than"), expectedPattern)); + } + } + + protected CheckResult MergeResults(params CheckResult[] results) + { + StringBuilder message = new StringBuilder(); + bool failed = false; + foreach (CheckResult result in results) + { + if (result == null) + continue; + if (result.CheckStatus != CheckStatus.Success) + failed = true; + message.AppendLine(result.Message); + } + return failed ? Fail(message.ToString().Trim()) : Pass(message.ToString().Trim()); + } + + protected abstract Task ExecuteCheckAsync(CancellationToken token); + } +} \ No newline at end of file diff -r 9e92780ebc0f -r 453ecc1ed9ea ServerMonitor/Objects/Checks/DiskSpaceCheck.cs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ServerMonitor/Objects/Checks/DiskSpaceCheck.cs Sun Jan 06 20:49:08 2019 -0500 @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ServerMonitorApp +{ + [DisplayName("Disk space check"), Description("Check the remaining free disk space"), DisplayWeight(11)] + public class DiskSpaceCheck : SshCheck + { + public string Device { get; set; } + + public double MinFreeSpace { get; set; } + + public FreeSpaceUnits FreeSpaceUnits { get; set; } + + public DiskSpaceCheck() + { + Command = "df -P -k {0} | awk 'NR>1' | tr -s ' ' | cut -d ' ' -f 4,5"; + CheckExitCode = true; + ExitCode = 0; + } + + protected override string GetCommand() + { + return string.Format(base.GetCommand(), Device); + } + + protected override List ProcessCommandResult(string output, int exitCode) + { + List results = base.ProcessCommandResult(output, exitCode); + if (output.Split('\n').Length > 1) + { + results.Add(Fail("df output was more than one line: " + output)); + } + else + { + + string[] tokens = output.Split(new char[0], StringSplitOptions.RemoveEmptyEntries); + if (FreeSpaceUnits == FreeSpaceUnits.percent) + { + if (int.TryParse(tokens[1].Replace("%", ""), out int percent)) + { + percent = 100 - percent; + string message = string.Format("Free disk space is {0}%", percent); + if (percent < MinFreeSpace) + results.Add(Fail(message)); + else + results.Add(Pass(message)); + } + else + { + results.Add(Fail("Unable to parse df output as integer: " + tokens[1].Replace("%", ""))); + } + } + else + { + if (int.TryParse(tokens[0], out int freeSpace)) + { + freeSpace /= 1024; + if (FreeSpaceUnits == FreeSpaceUnits.GB) + freeSpace /= 1024; + string message = string.Format("Free disk space is {0} {1}", freeSpace, FreeSpaceUnits); + if (freeSpace < MinFreeSpace) + results.Add(Fail(message)); + else + results.Add(Pass(message)); + } + else + { + results.Add(Fail("Unable to parse df output as integer: " + tokens[0])); + } + } + } + return results; + } + + public override string Validate(bool saving = true) + { + string message = base.Validate(); + if (Device.IsNullOrEmpty()) + message += "Device is required." + Environment.NewLine; + if (MinFreeSpace <= 0) + message += "Free space must be greater than 0." + Environment.NewLine; + else if (FreeSpaceUnits == FreeSpaceUnits.percent && MinFreeSpace > 100) + message += "Free space percent must be between 0 and 100." + Environment.NewLine; + return message; + } + } + + public enum FreeSpaceUnits { MB = 0, GB = 1, percent = 2 } +} diff -r 9e92780ebc0f -r 453ecc1ed9ea ServerMonitor/Objects/Checks/HttpCheck.cs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ServerMonitor/Objects/Checks/HttpCheck.cs Sun Jan 06 20:49:08 2019 -0500 @@ -0,0 +1,135 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; + +namespace ServerMonitorApp +{ + [DisplayName("HTTP check"), Description("Check the result of an HTTP request"), DisplayWeight(1)] + public class HttpCheck : Check + { + public string Url { get; set; } + + public bool CheckResponseCode { get; set; } + + public int ResponseCode { get; set; } + + public bool CheckResponseLength { get; set; } + + public string ResponseLengthMin { get; set; } + + public string ResponseLengthMax { get; set; } + + public bool CheckResponseBody { get; set; } + + public MatchType ResponseBodyMatchType { get; set; } + + public string ResponseBodyPattern { get; set; } + + public bool ResponseBodyUseRegex { get; set; } + + protected override Task ExecuteCheckAsync(CancellationToken token) + { + throw new NotImplementedException(); + } + + public override string Validate(bool saving = true) + { + string message = base.Validate(); + if (Url.IsNullOrEmpty()) + message += "URL cannot be blank." + Environment.NewLine; + if (!CheckResponseCode && !CheckResponseLength && !CheckResponseBody) + message += "At least one check must be enabled." + Environment.NewLine; + if (CheckResponseBody && ResponseBodyUseRegex) + { + try + { + Regex re = new Regex(ResponseBodyPattern); + } + catch (ArgumentException) + { + message += "Invalid regular expression for response body." + Environment.NewLine; + } + } + return message; + } + + //protected override CheckResult GetIntResult(int expectedValue, int resultValue, string description) + //{ + // CheckResult result = base.GetIntResult(expectedValue, resultValue, description); + + //} + +/* +100 Continue[RFC7231, Section 6.2.1] +101 Switching Protocols[RFC7231, Section 6.2.2] +102 Processing[RFC2518] +103 Early Hints[RFC8297] +200 OK[RFC7231, Section 6.3.1] +201 Created[RFC7231, Section 6.3.2] +202 Accepted[RFC7231, Section 6.3.3] +203 Non-Authoritative Information[RFC7231, Section 6.3.4] +204 No Content[RFC7231, Section 6.3.5] +205 Reset Content[RFC7231, Section 6.3.6] +206 Partial Content[RFC7233, Section 4.1] +207 Multi-Status[RFC4918] +208 Already Reported[RFC5842] +226 IM Used[RFC3229] +300 Multiple Choices[RFC7231, Section 6.4.1] +301 Moved Permanently[RFC7231, Section 6.4.2] +302 Found[RFC7231, Section 6.4.3] +303 See Other[RFC7231, Section 6.4.4] +304 Not Modified[RFC7232, Section 4.1] +305 Use Proxy[RFC7231, Section 6.4.5] +306 (Unused)[RFC7231, Section 6.4.6] +307 Temporary Redirect[RFC7231, Section 6.4.7] +308 Permanent Redirect[RFC7538] +400 Bad Request[RFC7231, Section 6.5.1] +401 Unauthorized[RFC7235, Section 3.1] +402 Payment Required[RFC7231, Section 6.5.2] +403 Forbidden[RFC7231, Section 6.5.3] +404 Not Found[RFC7231, Section 6.5.4] +405 Method Not Allowed[RFC7231, Section 6.5.5] +406 Not Acceptable[RFC7231, Section 6.5.6] +407 Proxy Authentication Required[RFC7235, Section 3.2] +408 Request Timeout[RFC7231, Section 6.5.7] +409 Conflict[RFC7231, Section 6.5.8] +410 Gone[RFC7231, Section 6.5.9] +411 Length Required[RFC7231, Section 6.5.10] +412 Precondition Failed[RFC7232, Section 4.2][RFC8144, Section 3.2] +413 Payload Too Large[RFC7231, Section 6.5.11] +414 URI Too Long[RFC7231, Section 6.5.12] +415 Unsupported Media Type[RFC7231, Section 6.5.13][RFC7694, Section 3] +416 Range Not Satisfiable[RFC7233, Section 4.4] +417 Expectation Failed[RFC7231, Section 6.5.14] +421 Misdirected Request[RFC7540, Section 9.1.2] +422 Unprocessable Entity[RFC4918] +423 Locked[RFC4918] +424 Failed Dependency[RFC4918] +425 Too Early[RFC8470] +426 Upgrade Required[RFC7231, Section 6.5.15] +427 Unassigned +428 Precondition Required[RFC6585] +429 Too Many Requests[RFC6585] +430 Unassigned +431 Request Header Fields Too Large[RFC6585] +451 Unavailable For Legal Reasons[RFC7725] +500 Internal Server Error[RFC7231, Section 6.6.1] +501 Not Implemented[RFC7231, Section 6.6.2] +502 Bad Gateway[RFC7231, Section 6.6.3] +503 Service Unavailable[RFC7231, Section 6.6.4] +504 Gateway Timeout[RFC7231, Section 6.6.5] +505 HTTP Version Not Supported[RFC7231, Section 6.6.6] +506 Variant Also Negotiates[RFC2295] +507 Insufficient Storage[RFC4918] +508 Loop Detected[RFC5842] +509 Unassigned +510 Not Extended[RFC2774] +511 Network Authentication Required[RFC6585] +*/ + } +} diff -r 9e92780ebc0f -r 453ecc1ed9ea ServerMonitor/Objects/Checks/PingCheck.cs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ServerMonitor/Objects/Checks/PingCheck.cs Sun Jan 06 20:49:08 2019 -0500 @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Net.NetworkInformation; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace ServerMonitorApp +{ + [DisplayName("Ping check"), Description("Check if the server responds to a ping request"), DisplayWeight(0)] + public class PingCheck : Check + { + protected async override Task ExecuteCheckAsync(CancellationToken token) + { + using (Ping ping = new Ping()) + { + try + { + PingReply reply = await ping.SendPingAsync(Server.Host, Timeout); + if (reply.Status == IPStatus.Success) + return Pass(string.Format("Reply received in {0} ms", reply.RoundtripTime)); + else + return Fail("Ping result: " + reply.Status); + } + catch (PingException e) + { + return Fail(e); + } + } + + // Cancellable version that doesn't actulaly work + // might be useful as a TaskCompletionSource example though + // + //TaskCompletionSource tcs = new TaskCompletionSource + // { + // if (e.Error != null) + // tcs.SetResult(Fail("Ping failed: " + e.Error.GetBaseException().Message)); + // else if (e.Reply.Status != IPStatus.Success) + // tcs.SetResult(Fail("Ping failed: " + e.Reply.Status.ToString())); + // else + // tcs.SetResult(Pass("Ping completed in " + e.Reply.RoundtripTime + "ms")); + // }; + // ping.SendAsync(Server.Host, Timeout, null); + //} + + //return tcs.Task; + } + } +} diff -r 9e92780ebc0f -r 453ecc1ed9ea ServerMonitor/Objects/Checks/SshCheck.cs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ServerMonitor/Objects/Checks/SshCheck.cs Sun Jan 06 20:49:08 2019 -0500 @@ -0,0 +1,115 @@ +using Renci.SshNet; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; + +namespace ServerMonitorApp +{ + [DisplayName("SSH check"), Description("Check the result of a command run over SSH"), DisplayWeight(10)] + public class SshCheck : Check + { + public string Command { get; set; } + + public bool CheckExitCode { get; set; } + + public int ExitCode { get; set; } + + public bool CheckCommandOutput { get; set; } + + public MatchType CommandOutputMatchType { get; set; } + + public string CommandOutputPattern { get; set; } + + public bool CommandOutputUseRegex { get; set; } + + protected override Task ExecuteCheckAsync(CancellationToken token) + { + return Task.Run(() => + { + try + { + if (!Server.SshClient.IsConnected) + Server.SshClient.Connect(); + token.ThrowIfCancellationRequested(); + using (SshCommand command = Server.SshClient.CreateCommand(GetCommand())) + { + token.Register(command.CancelAsync); + IAsyncResult ar = command.BeginExecute(); + token.ThrowIfCancellationRequested(); + string output = (command.EndExecute(ar).Trim() + command.Error.Trim()).ConvertNewlines(); + return MergeResults(ProcessCommandResult(output, command.ExitStatus).ToArray()); + } + } + catch (Exception e) + { + return Fail(e); + } + }, token); + //TaskCompletionSource tcs = new TaskCompletionSource(); + + ////TODO timeout + //if (!Server.SshClient.IsConnected) + // Server.SshClient.Connect(); + //using (SshCommand command = Server.SshClient.CreateCommand(Command)) + //{ + // token.Register(command.CancelAsync); + // command.BeginExecute(asyncResult => + // { + // string result = command.EndExecute(asyncResult); + // tcs.SetResult(new CheckResult(this, CheckStatus.Success, result)); + // }); + //} + + //return tcs.Task; + } + + protected virtual string GetCommand() + { + return Command; + } + + protected virtual List ProcessCommandResult(string output, int exitCode) + { + List results = new List(); + if (CheckExitCode) + results.Add(GetIntResult(ExitCode, exitCode, "Exit code")); + if (CheckCommandOutput) + results.Add(GetStringResult(CommandOutputMatchType, CommandOutputPattern, CommandOutputUseRegex, output, "Command output")); + return results; + } + + public override string Validate(bool saving = true) + { + string message = base.Validate(); + if (Server.Port <= 0) + message += "Server SSH port is required." + Environment.NewLine; + if (Server.Username.IsNullOrEmpty()) + message += "Server SSH username is required." + Environment.NewLine; + if (Server.LoginType == LoginType.Password && Server.Password.IsNullOrEmpty()) + message += "Server SSH password is required." + Environment.NewLine; + if (Server.LoginType == LoginType.PrivateKey && Server.KeyFile.IsNullOrEmpty()) + message += "Server SSH key is required." + Environment.NewLine; + if (Command.IsNullOrEmpty()) + message += "Command is required." + Environment.NewLine; + if (!CheckExitCode && !CheckCommandOutput) + message += "At least one check must be enabled." + Environment.NewLine; + if (CheckCommandOutput && CommandOutputUseRegex) + { + try + { + Regex re = new Regex(CommandOutputPattern); + } + catch (ArgumentException) + { + message += "Invalid regular expression for command output." + Environment.NewLine; + } + } + return message; + } + } +} diff -r 9e92780ebc0f -r 453ecc1ed9ea ServerMonitor/Objects/HttpCheck.cs --- a/ServerMonitor/Objects/HttpCheck.cs Tue Jan 01 21:14:47 2019 -0500 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,135 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; - -namespace ServerMonitorApp -{ - [DisplayName("HTTP check"), Description("Check the result of an HTTP request"), DisplayWeight(1)] - public class HttpCheck : Check - { - public string Url { get; set; } - - public bool CheckResponseCode { get; set; } - - public int ResponseCode { get; set; } - - public bool CheckResponseLength { get; set; } - - public string ResponseLengthMin { get; set; } - - public string ResponseLengthMax { get; set; } - - public bool CheckResponseBody { get; set; } - - public MatchType ResponseBodyMatchType { get; set; } - - public string ResponseBodyPattern { get; set; } - - public bool ResponseBodyUseRegex { get; set; } - - protected override Task ExecuteCheckAsync(CancellationToken token) - { - throw new NotImplementedException(); - } - - public override string Validate(bool saving = true) - { - string message = base.Validate(); - if (Url.IsNullOrEmpty()) - message += "URL cannot be blank." + Environment.NewLine; - if (!CheckResponseCode && !CheckResponseLength && !CheckResponseBody) - message += "At least one check must be enabled." + Environment.NewLine; - if (CheckResponseBody && ResponseBodyUseRegex) - { - try - { - Regex re = new Regex(ResponseBodyPattern); - } - catch (ArgumentException) - { - message += "Invalid regular expression for response body." + Environment.NewLine; - } - } - return message; - } - - //protected override CheckResult GetIntResult(int expectedValue, int resultValue, string description) - //{ - // CheckResult result = base.GetIntResult(expectedValue, resultValue, description); - - //} - -/* -100 Continue[RFC7231, Section 6.2.1] -101 Switching Protocols[RFC7231, Section 6.2.2] -102 Processing[RFC2518] -103 Early Hints[RFC8297] -200 OK[RFC7231, Section 6.3.1] -201 Created[RFC7231, Section 6.3.2] -202 Accepted[RFC7231, Section 6.3.3] -203 Non-Authoritative Information[RFC7231, Section 6.3.4] -204 No Content[RFC7231, Section 6.3.5] -205 Reset Content[RFC7231, Section 6.3.6] -206 Partial Content[RFC7233, Section 4.1] -207 Multi-Status[RFC4918] -208 Already Reported[RFC5842] -226 IM Used[RFC3229] -300 Multiple Choices[RFC7231, Section 6.4.1] -301 Moved Permanently[RFC7231, Section 6.4.2] -302 Found[RFC7231, Section 6.4.3] -303 See Other[RFC7231, Section 6.4.4] -304 Not Modified[RFC7232, Section 4.1] -305 Use Proxy[RFC7231, Section 6.4.5] -306 (Unused)[RFC7231, Section 6.4.6] -307 Temporary Redirect[RFC7231, Section 6.4.7] -308 Permanent Redirect[RFC7538] -400 Bad Request[RFC7231, Section 6.5.1] -401 Unauthorized[RFC7235, Section 3.1] -402 Payment Required[RFC7231, Section 6.5.2] -403 Forbidden[RFC7231, Section 6.5.3] -404 Not Found[RFC7231, Section 6.5.4] -405 Method Not Allowed[RFC7231, Section 6.5.5] -406 Not Acceptable[RFC7231, Section 6.5.6] -407 Proxy Authentication Required[RFC7235, Section 3.2] -408 Request Timeout[RFC7231, Section 6.5.7] -409 Conflict[RFC7231, Section 6.5.8] -410 Gone[RFC7231, Section 6.5.9] -411 Length Required[RFC7231, Section 6.5.10] -412 Precondition Failed[RFC7232, Section 4.2][RFC8144, Section 3.2] -413 Payload Too Large[RFC7231, Section 6.5.11] -414 URI Too Long[RFC7231, Section 6.5.12] -415 Unsupported Media Type[RFC7231, Section 6.5.13][RFC7694, Section 3] -416 Range Not Satisfiable[RFC7233, Section 4.4] -417 Expectation Failed[RFC7231, Section 6.5.14] -421 Misdirected Request[RFC7540, Section 9.1.2] -422 Unprocessable Entity[RFC4918] -423 Locked[RFC4918] -424 Failed Dependency[RFC4918] -425 Too Early[RFC8470] -426 Upgrade Required[RFC7231, Section 6.5.15] -427 Unassigned -428 Precondition Required[RFC6585] -429 Too Many Requests[RFC6585] -430 Unassigned -431 Request Header Fields Too Large[RFC6585] -451 Unavailable For Legal Reasons[RFC7725] -500 Internal Server Error[RFC7231, Section 6.6.1] -501 Not Implemented[RFC7231, Section 6.6.2] -502 Bad Gateway[RFC7231, Section 6.6.3] -503 Service Unavailable[RFC7231, Section 6.6.4] -504 Gateway Timeout[RFC7231, Section 6.6.5] -505 HTTP Version Not Supported[RFC7231, Section 6.6.6] -506 Variant Also Negotiates[RFC2295] -507 Insufficient Storage[RFC4918] -508 Loop Detected[RFC5842] -509 Unassigned -510 Not Extended[RFC2774] -511 Network Authentication Required[RFC6585] -*/ - } -} diff -r 9e92780ebc0f -r 453ecc1ed9ea ServerMonitor/Objects/PingCheck.cs --- a/ServerMonitor/Objects/PingCheck.cs Tue Jan 01 21:14:47 2019 -0500 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,56 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Net.NetworkInformation; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace ServerMonitorApp -{ - [DisplayName("Ping check"), Description("Check if the server responds to a ping request"), DisplayWeight(0)] - public class PingCheck : Check - { - protected async override Task ExecuteCheckAsync(CancellationToken token) - { - using (Ping ping = new Ping()) - { - try - { - PingReply reply = await ping.SendPingAsync(Server.Host, Timeout); - if (reply.Status == IPStatus.Success) - return Pass(string.Format("Reply received in {0} ms", reply.RoundtripTime)); - else - return Fail("Ping result: " + reply.Status); - } - catch (PingException e) - { - return Fail(e); - } - } - - // Cancellable version that doesn't actulaly work - // might be useful as a TaskCompletionSource example though - // - //TaskCompletionSource tcs = new TaskCompletionSource - // { - // if (e.Error != null) - // tcs.SetResult(Fail("Ping failed: " + e.Error.GetBaseException().Message)); - // else if (e.Reply.Status != IPStatus.Success) - // tcs.SetResult(Fail("Ping failed: " + e.Reply.Status.ToString())); - // else - // tcs.SetResult(Pass("Ping completed in " + e.Reply.RoundtripTime + "ms")); - // }; - // ping.SendAsync(Server.Host, Timeout, null); - //} - - //return tcs.Task; - } - } -} diff -r 9e92780ebc0f -r 453ecc1ed9ea ServerMonitor/Objects/SshCheck.cs --- a/ServerMonitor/Objects/SshCheck.cs Tue Jan 01 21:14:47 2019 -0500 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,106 +0,0 @@ -using Renci.SshNet; -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; - -namespace ServerMonitorApp -{ - [DisplayName("SSH check"), Description("Check the result of a command run over SSH"), DisplayWeight(10)] - public class SshCheck : Check - { - public string Command { get; set; } - - public bool CheckExitCode { get; set; } - - public int ExitCode { get; set; } - - public bool CheckCommandOutput { get; set; } - - public MatchType CommandOutputMatchType { get; set; } - - public string CommandOutputPattern { get; set; } - - public bool CommandOutputUseRegex { get; set; } - - protected override Task ExecuteCheckAsync(CancellationToken token) - { - return Task.Run(() => - { - try - { - if (!Server.SshClient.IsConnected) - Server.SshClient.Connect(); - token.ThrowIfCancellationRequested(); - using (SshCommand command = Server.SshClient.CreateCommand(Command)) - { - token.Register(command.CancelAsync); - IAsyncResult ar = command.BeginExecute(); - token.ThrowIfCancellationRequested(); - string result = command.EndExecute(ar).Trim() + command.Error.Trim(); - - CheckResult exitCodeResult = null, commandOutputResult = null; - if (CheckExitCode) - exitCodeResult = GetIntResult(ExitCode, command.ExitStatus, "Exit code"); - if (CheckCommandOutput) - commandOutputResult = GetStringResult(CommandOutputMatchType, CommandOutputPattern, CommandOutputUseRegex, result, "Command output"); - return MergeResults(exitCodeResult, commandOutputResult); - } - } - catch (Exception e) - { - return Fail(e); - } - }, token); - //TaskCompletionSource tcs = new TaskCompletionSource(); - - ////TODO timeout - //if (!Server.SshClient.IsConnected) - // Server.SshClient.Connect(); - //using (SshCommand command = Server.SshClient.CreateCommand(Command)) - //{ - // token.Register(command.CancelAsync); - // command.BeginExecute(asyncResult => - // { - // string result = command.EndExecute(asyncResult); - // tcs.SetResult(new CheckResult(this, CheckStatus.Success, result)); - // }); - //} - - //return tcs.Task; - } - - public override string Validate(bool saving = true) - { - string message = base.Validate(); - if (Server.Port <= 0) - message += "Server SSH port is required." + Environment.NewLine; - if (Server.Username.IsNullOrEmpty()) - message += "Server SSH username is required." + Environment.NewLine; - if (Server.LoginType == LoginType.Password && Server.Password.IsNullOrEmpty()) - message += "Server SSH password is required." + Environment.NewLine; - if (Server.LoginType == LoginType.PrivateKey && Server.KeyFile.IsNullOrEmpty()) - message += "Server SSH key is required." + Environment.NewLine; - if (Command.IsNullOrEmpty()) - message += "Command is required." + Environment.NewLine; - if (!CheckExitCode && !CheckCommandOutput) - message += "At least one check must be enabled." + Environment.NewLine; - if (CheckCommandOutput && CommandOutputUseRegex) - { - try - { - Regex re = new Regex(CommandOutputPattern); - } - catch (ArgumentException) - { - message += "Invalid regular expression for command output." + Environment.NewLine; - } - } - return message; - } - } -} diff -r 9e92780ebc0f -r 453ecc1ed9ea ServerMonitor/ServerMonitor.csproj --- a/ServerMonitor/ServerMonitor.csproj Tue Jan 01 21:14:47 2019 -0500 +++ b/ServerMonitor/ServerMonitor.csproj Sun Jan 06 20:49:08 2019 -0500 @@ -62,6 +62,12 @@ CheckControl.cs + + UserControl + + + DiskSpaceCheckControl.cs + UserControl @@ -77,6 +83,7 @@ Component + Form @@ -97,11 +104,11 @@ QuickHelpForm.cs - + - + - + @@ -117,7 +124,7 @@ ServerSummaryForm.cs - + @@ -129,6 +136,9 @@ CheckControl.cs + + DiskSpaceCheckControl.cs + SshCheckControl.cs @@ -217,5 +227,6 @@ + \ No newline at end of file