diff --git a/Endpoint/App.config b/Endpoint/App.config new file mode 100644 index 0000000..e83ee72 --- /dev/null +++ b/Endpoint/App.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/Endpoint/Endpoint.csproj b/Endpoint/Endpoint.csproj new file mode 100644 index 0000000..a86a8cb --- /dev/null +++ b/Endpoint/Endpoint.csproj @@ -0,0 +1,105 @@ + + + + Debug + AnyCPU + 9.0.21022 + 2.0 + {7686BAA6-A0C5-4FA5-BE6E-FA044C2FFDD4} + Library + Properties + Endpoint + Endpoint + v2.0 + 512 + + + 3.5 + + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + false + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + AllRules.ruleset + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + AllRules.ruleset + + + + False + References\HtmlAgilityPack.dll + + + + + + + + + + + + + + + + + + + + + + + + + False + .NET Framework 3.5 SP1 Client Profile + false + + + False + .NET Framework 3.5 SP1 + true + + + False + Windows Installer 3.1 + true + + + + + \ No newline at end of file diff --git a/Endpoint/HtmlEndpoint.cs b/Endpoint/HtmlEndpoint.cs new file mode 100644 index 0000000..a8254a5 --- /dev/null +++ b/Endpoint/HtmlEndpoint.cs @@ -0,0 +1,112 @@ +using System; +using System.IO; +using System.Text; +using System.Xml; +using HtmlAgilityPack; + +namespace Endpoint +{ + /// + /// A website endpoint + /// + public class HtmlEndpoint : HttpEndpoint + { + /// + /// Gets or sets the xpath query for the html document + /// + /// The xpath query. + public string XpathQuery { get; set; } + + /// + /// Gets or sets the expected query results. + /// + /// The expected query results. + public string ExpectedXpathResult { get; set; } + + /// + /// Gets or sets the post. + /// + /// The post. + public string RequestContent { get; set; } + + /// + /// Returns the HTML parsed into a standard . + /// Uses the library for "out of the web" (poorly formatted) html file support. + /// + /// The HTML + /// An from the HTML + private static XmlDocument getHtmlXml(string html) + { + var htmlDocument = new HtmlDocument(); + htmlDocument.LoadHtml(html); + + var xmlDocument = new XmlDocument(); + using (Stream stream = new MemoryStream()) + { + XmlWriter xmlTextWriter = new XmlTextWriter(stream, Encoding.UTF8); + + htmlDocument.Save(xmlTextWriter); + + stream.Seek(0, SeekOrigin.Begin); + + using (var sr = new StreamReader(stream)) + xmlDocument.Load(sr); + + xmlTextWriter.Close(); + } + return xmlDocument; + } + + /// + /// Returns the HTML grabbed from the passed in URL parsed into a standard . + /// Uses the library for "out of the web" html file support. + /// + /// The URL. + /// The post data. + /// An from the HTML + private static XmlDocument getHtmlXml(Uri url, string requestContent) + { + return getHtmlXml(GetUrlContent(url, "application/x-www-form-urlencoded", requestContent, null)); + } + + /// + /// Gets the current status result of the endpoint + /// + /// Status + public override Status GetStatus() + { + var baseStatus = base.GetStatus(); + if (baseStatus != Status.Up) + return baseStatus; + + try + { + var xml = getHtmlXml(Uri, RequestContent); + + // xml.Save(@"c:\test.xml"); + + var nodes = xml.SelectNodes(XpathQuery); + + // verify html has expected value + if (nodes == null || nodes.Count == 0) + { + StatusDescription = "Couldn't find expected value in html"; + return Status.Error; + } + var value = nodes[0].Value.Trim(); + if (value != ExpectedXpathResult) + { + StatusDescription = String.Format("Result was: '{0}', was expecting '{1}'", value, + ExpectedXpathResult); + return Status.Error; + } + } + catch (Exception ex) + { + StatusDescription = ex.Message; + return Status.Error; + } + return Status.Up; + } + } +} diff --git a/Endpoint/HttpEndpoint.cs b/Endpoint/HttpEndpoint.cs new file mode 100644 index 0000000..9bd5b99 --- /dev/null +++ b/Endpoint/HttpEndpoint.cs @@ -0,0 +1,124 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Text; + +namespace Endpoint +{ + /// + /// Http Endpoint - such as a webserver, a webservice, etc + /// + [Serializable] + public class HttpEndpoint : IEndpoint + { + /// + /// Gets or sets the name. + /// + /// The name. + public string Name { get; set; } + + /// + /// URI of the service + /// + public Uri Uri { get; set; } + + /// + /// Gets or sets the URI string. + /// + /// The URI string. + public string UriString + { + get { return Uri.ToString(); } + set { Uri = new Uri(value); } + } + + /// + /// Returns the response string grabbed from the passed in URL. + /// + /// The URL. + /// The HTTP content type. + /// The request content. + /// The web headers. + /// A string containing response + protected static string GetUrlContent(Uri url, string contentType, string requestContent, + Dictionary webHeaders) + { + // POST request + if (!string.IsNullOrEmpty(contentType) && !string.IsNullOrEmpty(requestContent)) + { + var webRequest = (HttpWebRequest) WebRequest.Create(url); + + // NOTE: need a proxy? Here's where it would go + // webRequest.Proxy = new WebProxy(); + + // SOAP + webRequest.ContentType = contentType; + if (webHeaders != null && webHeaders.Count > 0) + { + foreach (var webHeader in webHeaders) + webRequest.Headers.Add(webHeader.Key, webHeader.Value); + } + + webRequest.Method = "POST"; + + //We need to count how many bytes we're sending. Post'ed Faked Forms should be name=value& + var bytes = Encoding.ASCII.GetBytes(requestContent); + webRequest.ContentLength = bytes.Length; + using (var requestStream = webRequest.GetRequestStream()) + { + requestStream.Write(bytes, 0, bytes.Length); //write it to the stream + } + var webResponse = webRequest.GetResponse(); + if (webResponse == null) + return null; + using (var streamReader = new StreamReader(webResponse.GetResponseStream())) + { + return streamReader.ReadToEnd(); + } + } + + // GET Request + var client = new WebClient(); + using (var data = client.OpenRead(url)) + using (var reader = new StreamReader(data)) + { + return reader.ReadToEnd(); + } + } + + /// + /// Returns a string explaining details on the current status + /// + /// string with status message + public virtual string StatusDescription { get; protected set; } + + /// + /// Gets the current status result of the endpoint + /// + /// Status + public virtual Status GetStatus() + { + try + { + var request = WebRequest.Create(Uri); + + using (var response = (HttpWebResponse) request.GetResponse()) + { + StatusDescription = response.StatusDescription; + return response.StatusCode == HttpStatusCode.OK + ? Status.Up + : Status.Unreachable; + } + } + catch (WebException ex) + { + StatusDescription = ex.Message + " (" + ex.Status + ")"; + + return ex.Status == WebExceptionStatus.Timeout + ? Status.Timeout + : Status.Unreachable; + } + } + } +} diff --git a/Endpoint/IEndpoint.cs b/Endpoint/IEndpoint.cs new file mode 100644 index 0000000..aa77d42 --- /dev/null +++ b/Endpoint/IEndpoint.cs @@ -0,0 +1,26 @@ + +namespace Endpoint +{ + /// + /// Describes a service endpoint, and the methods to find its current status + /// + public interface IEndpoint + { + /// + /// Gets the service name + /// + string Name { get; } + + /// + /// Gets the current status result of the endpoint + /// + /// Status + Status GetStatus(); + + /// + /// Returns a string explaining details on the current status + /// + /// string with status message + string StatusDescription { get; } + } +} diff --git a/Endpoint/Properties/AssemblyInfo.cs b/Endpoint/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..aa6cc70 --- /dev/null +++ b/Endpoint/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Endpoint")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Endpoint")] +[assembly: AssemblyCopyright("Copyright © 2008")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("aef466bc-73b7-4f86-a374-8db991adb437")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Endpoint/References/HtmlAgilityPack.dll b/Endpoint/References/HtmlAgilityPack.dll new file mode 100644 index 0000000..413539d Binary files /dev/null and b/Endpoint/References/HtmlAgilityPack.dll differ diff --git a/Endpoint/References/Oracle.DataAccess.dll b/Endpoint/References/Oracle.DataAccess.dll new file mode 100644 index 0000000..283ac96 Binary files /dev/null and b/Endpoint/References/Oracle.DataAccess.dll differ diff --git a/Endpoint/SoapEndpoint.cs b/Endpoint/SoapEndpoint.cs new file mode 100644 index 0000000..2ce3959 --- /dev/null +++ b/Endpoint/SoapEndpoint.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using System.Xml; + +namespace Endpoint +{ + /// + /// A SOAP webservice endpoint. + /// + public class SoapEndpoint : HttpEndpoint + { + private string _xpathNamespaces = string.Empty; + private Dictionary _namespaceToUri; + + /// + /// Gets or sets the xpath query for the html document + /// + /// The xpath query. + public string XpathQuery { get; set; } + + /// + /// Gets or sets the namespaces used in the xpath query + /// + /// The xpath namespaces. + public string XpathNamespaces + { + get { return _xpathNamespaces; } + set + { + _xpathNamespaces = value; + if (string.IsNullOrEmpty(_xpathNamespaces)) return; + + var split = _xpathNamespaces.Split(','); + if (split.Length != 2) + throw new ArgumentException("Need a comma separated pair in XpathNamespaces."); + + _namespaceToUri = new Dictionary(); + _namespaceToUri.Add(split[0], split[1]); + } + } + + /// + /// Gets or sets the expected query results. + /// + /// The expected query results. + public string ExpectedXpathResult { get; set; } + + /// + /// Gets or sets the SOAP action. + /// + /// The post. + public string SoapAction { get; set; } + + /// + /// Gets or sets the SOAP request. + /// + /// The post. + public string SoapRequest { get; set; } + + /// + /// Gets the current status result of the endpoint + /// + /// Status + public override Status GetStatus() + { + var baseStatus = base.GetStatus(); + if (baseStatus != Status.Up) // if we can't even get to the webserver, no need to try and call a WS. + return baseStatus; + + try + { + var headers = new Dictionary {{"SOAPAction", SoapAction}}; + + var xml = new XmlDocument(); + + xml.LoadXml(GetUrlContent(Uri, "text/xml; charset=utf-8", SoapRequest, headers)); + + XmlNodeList nodes; + if (_namespaceToUri != null) + { + var nsMgr = new XmlNamespaceManager(xml.NameTable); + foreach (var namespaceToUri in _namespaceToUri) + { + nsMgr.AddNamespace(namespaceToUri.Key, namespaceToUri.Value); + } + + nodes = xml.SelectNodes(XpathQuery, nsMgr); + } + else + nodes = xml.SelectNodes(XpathQuery); + + //xml.Save(@"c:\soap.xml"); + + // verify response has expected value + if (nodes == null || nodes.Count == 0) + { + StatusDescription = "Couldn't find expected value in SOAP response"; + return Status.Error; + } + var value = nodes[0].Value.Trim(); + if (value != ExpectedXpathResult) + { + StatusDescription = String.Format("Result was: '{0}', was expecting '{1}'", value, + ExpectedXpathResult); + return Status.Error; + } + } + catch (Exception ex) + { + StatusDescription = ex.Message; + return Status.Error; + } + return Status.Up; + } + } +} diff --git a/Endpoint/Status.cs b/Endpoint/Status.cs new file mode 100644 index 0000000..6ebecc3 --- /dev/null +++ b/Endpoint/Status.cs @@ -0,0 +1,30 @@ +namespace Endpoint +{ + /// + /// Status of an endpoint + /// + /// The expects these to be in order of best-to-worst descending. + public enum Status + { + /// + /// Service state is unknown - this will occur only before the first poll. + /// + Unknown, + /// + /// Service is working + /// + Up, + /// + /// Service can be resolved, but does not respond + /// + Timeout, + /// + /// Service can not be resolved + /// + Unreachable, + /// + /// Service is available, but returns an error message + /// + Error + } +} \ No newline at end of file diff --git a/Endpoint/StatusComparer.cs b/Endpoint/StatusComparer.cs new file mode 100644 index 0000000..51c0173 --- /dev/null +++ b/Endpoint/StatusComparer.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; + +namespace Endpoint +{ + /// + /// Compares a Status for which is the worst status. + /// + public class StatusComparer : IComparer + { + /// + /// Compares two objects and returns a value indicating whether one is less than, equal to, or greater than the other. + /// + /// The first object to compare. + /// The second object to compare. + /// + /// Value + /// Condition + /// Less than zero + /// is less than . + /// Zero + /// equals . + /// Greater than zero + /// is greater than . + /// + public int Compare(Status x, Status y) + { + return y.CompareTo(x); + } + } +} diff --git a/Endpoint/WindowsServiceEndpoint.cs b/Endpoint/WindowsServiceEndpoint.cs new file mode 100644 index 0000000..ac5d432 --- /dev/null +++ b/Endpoint/WindowsServiceEndpoint.cs @@ -0,0 +1,73 @@ +using System; +using System.ServiceProcess; + +namespace Endpoint +{ + /// + /// Endpoint for a Windows Service + /// + public class WindowsServiceEndpoint : IEndpoint + { + /// + /// Gets the service name + /// + public string Name { get; set; } + + /// + /// Returns a string explaining details on the current status + /// + /// string with status message + public string StatusDescription { get; protected set; } + + /// + /// Gets or sets the name of the windows service. + /// + /// The name of the service. + public string ServiceName { get; set; } + + /// + /// Gets or sets the name of the machine the service runs on. + /// + /// The name of the machine. + public string MachineName { get; set; } + + /// + /// Gets the current status result of the endpoint + /// + /// Status + public Status GetStatus() + { + ServiceController myservice; + try + { + if (!string.IsNullOrEmpty(MachineName)) + myservice = new ServiceController(ServiceName, MachineName); + else + myservice = new ServiceController(ServiceName); + } + catch (Exception ex) + { + StatusDescription = ex.Message; + return Status.Unreachable; + } + + try + { + switch (myservice.Status) + { + case ServiceControllerStatus.Running: + StatusDescription = "Running"; + return Status.Up; + default: + StatusDescription = myservice.Status.ToString(); + return Status.Error; + } + } + catch (Exception ex) + { + StatusDescription = ex.Message; + return Status.Error; + } + } + } +} diff --git a/Endpoint/WmiEndpoint.cs b/Endpoint/WmiEndpoint.cs new file mode 100644 index 0000000..0ac17da --- /dev/null +++ b/Endpoint/WmiEndpoint.cs @@ -0,0 +1,244 @@ +using System; +using System.Management; + +namespace Endpoint +{ + /// + /// Endpoint for Windows Management Instrumentation (WMI) + /// + public class WmiEndpoint : IEndpoint + { + /// + /// Gets the service name + /// + public string Name { get; set; } + + /// + /// Returns a string explaining details on the current status + /// + /// string with status message + public string StatusDescription { get; protected set; } + + /// + /// Gets or sets the name of the machine to query with WMI. + /// + /// The name of the machine. + public string MachineName { get; set; } + + /// + /// Gets or sets the object query string. + /// + /// The object query string. + public string ObjectQueryString { get; set; } + + /// + /// Gets or sets the connection username. + /// + /// The connection username. + public string ConnectionUsername { get; set; } + + /// + /// Gets or sets the connection password. + /// + /// The connection password. + public string ConnectionPassword { get; set; } + + /// + /// Gets or sets the name of the result property - this needs to be returned by the query. + /// + /// The name of the result property. + public string ResultPropertyName { get; set; } + + /// + /// Report error when result is this value. + /// + /// A result indicating an error. + public string ErrorResult { get; set; } + + /// + /// Report up when result is this value. + /// + /// A result indicating everything is ok. + public string UpResult { get; set; } + + /// + /// Report error when result is below this number + /// + /// + public double MinimumThreshold { get; set; } + + /// + /// Report error when result is above this number + /// + /// + public double MaximumThreshold { get; set; } + + /// + /// Initializes a new instance of the class. + /// + public WmiEndpoint() + { + MinimumThreshold = double.MinValue; + MaximumThreshold = double.MaxValue; + } + + /// + /// Gets the current status result of the endpoint + /// + /// Status + public Status GetStatus() + { + var connectionOptions = new ConnectionOptions(); + if (!string.IsNullOrEmpty(ConnectionUsername) && !string.IsNullOrEmpty(ConnectionPassword)) + { + connectionOptions.Username = ConnectionUsername; + connectionOptions.Password = ConnectionPassword; + } + + ManagementScope managementScope; + try + { + managementScope = new ManagementScope(MachineName, connectionOptions); + } + catch (Exception e) + { + StatusDescription = string.Format("Management Scope for MachineName \"{1}\" Failed : \"{0}\"", e.Message, MachineName); + return Status.Error; + } + + ObjectQuery objectQuery; + try + { + objectQuery = new ObjectQuery(ObjectQueryString); + } + catch (Exception e) + { + StatusDescription = string.Format("ObjectQuery initialization \"{1}\" Failed : \"{0}\"", e.Message, ObjectQueryString); + return Status.Error; + } + + ManagementObjectSearcher results; + try + { + //Execute the query + results = new ManagementObjectSearcher(managementScope, objectQuery); + } + catch (Exception e) + { + StatusDescription = string.Format("Building Query failed : \"{0}\"", e.Message); + return Status.Error; + } + + ManagementObjectCollection managementObjectCollection; + try + { + //Get the results + managementObjectCollection = results.Get(); + + if (managementObjectCollection.Count == 0) + { + StatusDescription = string.Format("Query returned 0 results : \"{0}\"", ObjectQueryString); + return Status.Error; + } + } + catch (Exception ex) + { + StatusDescription = "Error retrieving results. Exception: " + ex.Message; + return Status.Unreachable; + } + + ManagementObject firstResult = null; + + //take only the first result if there are more than one + foreach (ManagementObject result in managementObjectCollection) + { + firstResult = result; + break; + } + + if (firstResult == null) + { + StatusDescription = "Problem accessing first result object"; + return Status.Error; + } + + object resultValue; + try + { + resultValue = firstResult[ResultPropertyName]; + } + catch (Exception ex) + { + StatusDescription = string.Format("Error retrieving result property \"{0}\". Exception: {1}", + ResultPropertyName, ex.Message); + return Status.Error; + } + + if (resultValue == null) + { + StatusDescription = string.Format("Result value was null for property \"{0}\".", ResultPropertyName); + return Status.Error; + } + + return getStatus(resultValue.ToString()); + } + + /// + /// Gets the status. + /// + /// The result value string. + /// + private Status getStatus(string resultValueString) + { + // Up Value + if (!string.IsNullOrEmpty(UpResult) && UpResult != resultValueString) + { + StatusDescription = + string.Format("Result for property \"{0}\" was not expected value. Expected \"{1}\" but was \"{2}\"", + ResultPropertyName, UpResult ?? "(NULL)", resultValueString); + return Status.Error; + } + + // Error Value + if (!string.IsNullOrEmpty(ErrorResult) && ErrorResult == resultValueString) + { + StatusDescription = + string.Format("Result for property \"{0}\" was error value - \"{1}\"", + ResultPropertyName, resultValueString); + return Status.Error; + } + + if (MinimumThreshold != double.MinValue || MaximumThreshold != double.MaxValue) + { + double resultValueDouble; + if (Double.TryParse(resultValueString, out resultValueDouble)) + { + if (resultValueDouble < MinimumThreshold) + { + StatusDescription = + string.Format("Result for property \"{0}\" was less then threshold - {1} < {2}", + ResultPropertyName, resultValueDouble, MinimumThreshold); + return Status.Error; + } + if (resultValueDouble > MaximumThreshold) + { + StatusDescription = + string.Format("Result for property \"{0}\" was more then threshold - {1} > {2}", + ResultPropertyName, resultValueDouble, MaximumThreshold); + return Status.Error; + } + } + else + { + StatusDescription = + string.Format("Result for property \"{0}\" was not a number - \"{1}\"", + ResultPropertyName, resultValueString); + return Status.Error; + } + } + + StatusDescription = resultValueString; + return Status.Up; + } + } +} diff --git a/EndpointTest/EndpointTest.csproj b/EndpointTest/EndpointTest.csproj new file mode 100644 index 0000000..675c9b0 --- /dev/null +++ b/EndpointTest/EndpointTest.csproj @@ -0,0 +1,117 @@ + + + + Debug + AnyCPU + 9.0.21022 + 2.0 + {76D78812-B640-4EF0-957B-15D0E9000A84} + Library + Properties + EndpointTest + EndpointTest + v4.0 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + 3.5 + + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + false + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + AllRules.ruleset + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + AllRules.ruleset + + + + False + ..\Endpoint\References\HtmlAgilityPack.dll + + + + False + ..\Endpoint\References\Oracle.DataAccess.dll + + + + + 3.5 + + + + + + + + + + + + + + + {7686BAA6-A0C5-4FA5-BE6E-FA044C2FFDD4} + Endpoint + + + + + False + Microsoft .NET Framework 4 %28x86 and x64%29 + true + + + False + .NET Framework 3.5 SP1 Client Profile + false + + + False + .NET Framework 3.5 SP1 + false + + + False + Windows Installer 3.1 + true + + + + + \ No newline at end of file diff --git a/EndpointTest/HtmlEndpointTest.cs b/EndpointTest/HtmlEndpointTest.cs new file mode 100644 index 0000000..4201f83 --- /dev/null +++ b/EndpointTest/HtmlEndpointTest.cs @@ -0,0 +1,53 @@ +using System; +using Endpoint; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace EndpointTest +{ + /// + ///This is a test class for HtmlEndpointTest and is intended + ///to contain all HtmlEndpointTest Unit Tests + /// + [TestClass] + public class HtmlEndpointTest + { + // Random website for functional testing + private readonly Uri _goodUri = new Uri("http://www.seekwellness.com/sca/"); + + /// + ///A test for HtmlEndpointConfiguration + /// + [TestMethod] + public void HtmlEndpointConfigurationTest() + { + var target = new HtmlEndpoint + { + UriString = _goodUri.OriginalString, + XpathQuery = "//td[contains(text(), 'Use this form')]/text()", + ExpectedXpathResult = "Use this form to order your", + RequestContent = "redeem_coupon=true&f_coupon_code=stuff" + }; + + var status = target.GetStatus(); + Assert.AreEqual(Status.Up, status, target.StatusDescription); + } + + /// + ///A test for HtmlEndpointConfiguration + /// + [TestMethod] + public void HtmlEndpointFormTest() + { + var target = new HtmlEndpoint + { + UriString = _goodUri.OriginalString, + XpathQuery = "//p[@class='errorMsg']/text()", + ExpectedXpathResult = "- Please enter a valid coupon code.", + RequestContent = "redeem_coupon=true&f_coupon_code=stuff" + }; + + var status = target.GetStatus(); + Assert.AreEqual(Status.Up, status, target.StatusDescription); + } + } +} diff --git a/EndpointTest/HttpEndpointTest.cs b/EndpointTest/HttpEndpointTest.cs new file mode 100644 index 0000000..bb4213e --- /dev/null +++ b/EndpointTest/HttpEndpointTest.cs @@ -0,0 +1,90 @@ +using System; +using Endpoint; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace EndpointTest +{ + /// + ///This is a test class for HttpEndpointTest and is intended + ///to contain all HttpEndpointTest Unit Tests + /// + [TestClass] + public class HttpEndpointTest + { + private readonly Uri _goodUri = new Uri("http://www.google.com"); + private readonly Uri _missingUri = new Uri("http://www.google.com/gasdfasdh598yqwejbiasalsdjfhaogle"); + private readonly Uri _unreachableDomainUri = new Uri("http://gasdfasdh598yqwejbiasalsdjfhaogle"); + private readonly Uri _downUri = new Uri("http://192.168.0.153"); + private readonly Uri _httpsUri = new Uri("https://sourceforge.net/"); + + /// + ///A test for HttpEndpoint Constructor + /// + [TestMethod] + public void HttpEndpointConstructorTest() + { + HttpEndpoint endpoint = new HttpEndpoint {Uri = _goodUri}; + Assert.AreEqual(_goodUri, endpoint.Uri); + } + + /// + ///A test for UrlString + /// + [TestMethod] + public void UrlStringTest() + { + HttpEndpoint endpoint = new HttpEndpoint {UriString = "http://uri/"}; + Assert.AreEqual("http://uri/", endpoint.Uri.AbsoluteUri); + } + + /// + ///A test for GetStatus + /// + [TestMethod] + public void GetStatusGoodTest() + { + HttpEndpoint endpoint = new HttpEndpoint {Uri = _goodUri}; + Assert.AreEqual(Status.Up, endpoint.GetStatus()); + } + + /// + ///A test for GetStatus + /// + [TestMethod] + public void GetStatusMissingTest() + { + HttpEndpoint endpoint = new HttpEndpoint {Uri = _missingUri}; + Assert.AreEqual(Status.Unreachable, endpoint.GetStatus()); + } + + /// + ///A test for GetStatus + /// + [TestMethod] + public void GetStatusUnreachableTest() + { + HttpEndpoint endpoint = new HttpEndpoint {Uri = _unreachableDomainUri}; + Assert.AreEqual(Status.Unreachable, endpoint.GetStatus()); + } + + /// + ///A test for GetStatus + /// + [TestMethod] + public void GetStatusDownTest() + { + HttpEndpoint endpoint = new HttpEndpoint {Uri = _downUri}; + Assert.AreEqual(Status.Unreachable, endpoint.GetStatus()); + } + + /// + ///A test for GetStatus + /// + [TestMethod] + public void GetHttpsTest() + { + HttpEndpoint endpoint = new HttpEndpoint {Uri = _httpsUri}; + Assert.AreEqual(Status.Up, endpoint.GetStatus()); + } + } +} diff --git a/EndpointTest/Properties/AssemblyInfo.cs b/EndpointTest/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..b7ed628 --- /dev/null +++ b/EndpointTest/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("EndpointTest")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("EndpointTest")] +[assembly: AssemblyCopyright("Copyright © 2008")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM componenets. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("b5dcbcfc-f05e-4e58-bd8b-e4cbf660065d")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Revision and Build Numbers +// by using the '*' as shown below: +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/EndpointTest/ServiceEndpointConfigurationTest.cs b/EndpointTest/ServiceEndpointConfigurationTest.cs new file mode 100644 index 0000000..6474f2d --- /dev/null +++ b/EndpointTest/ServiceEndpointConfigurationTest.cs @@ -0,0 +1,57 @@ +using System; +using System.IO; +using Endpoint; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Collections.Generic; + +namespace EndpointTest +{ + ///// + /////This is a test class for ServiceEndpointConfigurationTest and is intended + /////to contain all ServiceEndpointConfigurationTest Unit Tests + ///// + //[TestClass()] + //public class ServiceEndpointConfigurationTest + //{ + // private readonly Uri _uri = new Uri("http://www.google.com"); + // private const string _testFilename = "testFilename.xml"; + + // /// + // ///A test for SaveConfigurationList + // /// + // [TestMethod] + // public void SaveAndLoadConfigurationListTest() + // { + // try + // { + // HttpEndpointConfiguration httpEndpoint = new HttpEndpointConfiguration("", _uri); + // OracleEndpointConfiguration oracleEndpoint = new OracleEndpointConfiguration("", "connection", "query", "result"); + + // List serviceEndpointConfigurations + // = new List { httpEndpoint, oracleEndpoint }; + + // IServiceEndpointConfiguration.SaveConfigurationList(serviceEndpointConfigurations, _testFilename); + + // // load the results + // List loadedList = IServiceEndpointConfiguration.LoadConfigurationList(_testFilename); + + // // verify that they're the same + // foreach (IServiceEndpointConfiguration serviceEndpointConfiguration in serviceEndpointConfigurations) + // { + // IServiceEndpointConfiguration tmp = serviceEndpointConfiguration; + + // IServiceEndpointConfiguration foundConfig + // = loadedList.Find(loadedEndpointConfig => loadedEndpointConfig.Equals(tmp)); + // Assert.IsNotNull(foundConfig); + // } + // } + // finally + // { + // FileInfo f = new FileInfo(_testFilename); + // // f.CopyTo(@"c:\test.xml"); + // if (f.Exists) + // f.Delete(); + // } + // } + //} +} diff --git a/EndpointTest/SoapEndpointTest.cs b/EndpointTest/SoapEndpointTest.cs new file mode 100644 index 0000000..6c47471 --- /dev/null +++ b/EndpointTest/SoapEndpointTest.cs @@ -0,0 +1,62 @@ +using Endpoint; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace EndpointTest +{ + /// + /// Functional Tests for SoapEndpoint + /// + [TestClass] + public class SoapEndpointTest + { + // Random internet webservice + private const string URISTRING = @"http://www.weather.gov/forecasts/xml/SOAP_server/ndfdXMLserver.php"; + + private const string SOAPACTION = + @"http://www.weather.gov/forecasts/xml/DWMLgen/wsdl/ndfdXML.wsdl#GmlTimeSeries"; + + private const string SOAPREQUEST = + @"" + + @" " + + @" " + + @" " + + @" ?" + + @" ?" + + @" ?" + + @" ?" + + @" ?" + + @" ?" + + @" " + + @" " + + @""; + + private const string XPATHNAMESPACES = + @"a,http://www.opengis.net/ows"; + + private const string XPATHQUERY = + @"//a:Exception[1]/@ExceptionText"; + + private const string EXPECTEDXPATHRESULT = + @"VERSION key not found"; + + /// + /// Runs GetStatus + /// + [TestMethod] + public void GetStatusTest() + { + var target = new SoapEndpoint + { + UriString = URISTRING, + XpathQuery = XPATHQUERY, + ExpectedXpathResult = EXPECTEDXPATHRESULT, + SoapAction = SOAPACTION, + SoapRequest = SOAPREQUEST, + XpathNamespaces = XPATHNAMESPACES + }; + + var status = target.GetStatus(); + Assert.AreEqual(Status.Up, status); + } + } +} diff --git a/EndpointTest/WindowsServiceEndpointTest.cs b/EndpointTest/WindowsServiceEndpointTest.cs new file mode 100644 index 0000000..65f991c --- /dev/null +++ b/EndpointTest/WindowsServiceEndpointTest.cs @@ -0,0 +1,32 @@ +using Endpoint; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace EndpointTest +{ + /// + /// Tests for WindowsServiceEndpoint + /// + [TestClass] + public class WindowsServiceEndpointTest + { + /// + /// Use a service that should always be running on localhost + /// + private const string SERVICENAME = "Windows Time"; + + /// + /// Runs GetStatus + /// + [TestMethod] + public void GetStatusTest() + { + var target = new WindowsServiceEndpoint + { + ServiceName = SERVICENAME + }; + + var status = target.GetStatus(); + Assert.AreEqual(Status.Up, status); + } + } +} diff --git a/EndpointTest/WmiEndpointTest.cs b/EndpointTest/WmiEndpointTest.cs new file mode 100644 index 0000000..efb80e1 --- /dev/null +++ b/EndpointTest/WmiEndpointTest.cs @@ -0,0 +1,141 @@ +using Endpoint; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace EndpointTest +{ + /// + /// Tests for WmiEndpoint + /// + [TestClass] + public class WmiEndpointTest + { + private const string MACHINENAME = @"\\localhost"; + + /// + /// Run GetStatus using "UpResult" expecting an up status. + /// + [TestMethod] + public void GetStatusUpPositiveTest() + { + var target = new WmiEndpoint + { + MachineName = MACHINENAME, + ObjectQueryString = "Select * from Win32_Process", + ResultPropertyName = "Name", + UpResult = "System Idle Process" + }; + + var status = target.GetStatus(); + Assert.AreEqual(Status.Up, status); + Assert.AreEqual("System Idle Process", target.StatusDescription); + } + + /// + /// Run GetStatus using "UpResult" expecting an error status. + /// + [TestMethod] + public void GetStatusUpNegativeTest() + { + var target = new WmiEndpoint + { + MachineName = MACHINENAME, + ObjectQueryString = "Select * from Win32_Process", + ResultPropertyName = "Name", + UpResult = "Not A Real Process" + }; + + var status = target.GetStatus(); + Assert.AreEqual(Status.Error, status); + } + + /// + /// Run GetStatus using "ErrorResult" expecting an error status. + /// + [TestMethod] + public void GetStatusErrorPositiveTest() + { + var target = new WmiEndpoint + { + MachineName = MACHINENAME, + ObjectQueryString = "Select * from Win32_Process", + ResultPropertyName = "Name", + ErrorResult = "System Idle Process" + }; + + var status = target.GetStatus(); + Assert.AreEqual(Status.Error, status); + } + + /// + /// Run GetStatus using "MinimumThreshold" expecting an up status. + /// + [TestMethod] + public void GetStatusMinimumThresholdPositiveTest() + { + var target = new WmiEndpoint + { + MachineName = MACHINENAME, + ObjectQueryString = "select FreeSpace from Win32_LogicalDisk where DeviceID='C:'", + ResultPropertyName = "FreeSpace", + MinimumThreshold = 10 // assume you have more than 10B free on C: + }; + + var status = target.GetStatus(); + Assert.AreEqual(Status.Up, status); + } + + /// + /// Run GetStatus using "MinimumThreshold" expecting an up status. + /// + [TestMethod] + public void GetStatusMinimumThresholdNegativeTest() + { + var target = new WmiEndpoint + { + MachineName = MACHINENAME, + ObjectQueryString = "select FreeSpace from Win32_LogicalDisk where DeviceID='C:'", + ResultPropertyName = "FreeSpace", + MinimumThreshold = 1e15 // assume you have less than a petabyte free on C: + }; + + var status = target.GetStatus(); + Assert.AreEqual(Status.Error, status); + } + + /// + /// Run GetStatus using "MaximumThreshold" expecting an up status. + /// + [TestMethod] + public void GetStatusMaximumThresholdPositiveTest() + { + var target = new WmiEndpoint + { + MachineName = MACHINENAME, + ObjectQueryString = "select FreeSpace from Win32_LogicalDisk where DeviceID='C:'", + ResultPropertyName = "FreeSpace", + MaximumThreshold = 1e15 // assume you have less than a petabyte free on C: + }; + + var status = target.GetStatus(); + Assert.AreEqual(Status.Up, status); + } + + /// + /// Run GetStatus using "MaximumThreshold" expecting an up status. + /// + [TestMethod] + public void GetStatusMaximumThresholdNegativeTest() + { + var target = new WmiEndpoint + { + MachineName = MACHINENAME, + ObjectQueryString = "select FreeSpace from Win32_LogicalDisk where DeviceID='C:'", + ResultPropertyName = "FreeSpace", + MaximumThreshold = 10 // assume you have less than a 10B free on C: + }; + + var status = target.GetStatus(); + Assert.AreEqual(Status.Error, status); + } + } +} diff --git a/LocalTestRun.testrunconfig b/LocalTestRun.testrunconfig new file mode 100644 index 0000000..dd8747e --- /dev/null +++ b/LocalTestRun.testrunconfig @@ -0,0 +1,5 @@ + + + This is a default test run configuration for a local test run. + + \ No newline at end of file diff --git a/ServiceDashBored.sln b/ServiceDashBored.sln new file mode 100644 index 0000000..1af2e98 --- /dev/null +++ b/ServiceDashBored.sln @@ -0,0 +1,41 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{DA481DF1-5754-4499-AEA2-21F809256F35}" + ProjectSection(SolutionItems) = preProject + LocalTestRun.testrunconfig = LocalTestRun.testrunconfig + ServiceDashBored.vsmdi = ServiceDashBored.vsmdi + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceDashBored", "ServiceDashBored\ServiceDashBored.csproj", "{2D46A996-7DEF-4592-B401-70090686B3F2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Endpoint", "Endpoint\Endpoint.csproj", "{7686BAA6-A0C5-4FA5-BE6E-FA044C2FFDD4}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EndpointTest", "EndpointTest\EndpointTest.csproj", "{76D78812-B640-4EF0-957B-15D0E9000A84}" +EndProject +Global + GlobalSection(TestCaseManagementSettings) = postSolution + CategoryFile = ServiceDashBored.vsmdi + EndGlobalSection + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {2D46A996-7DEF-4592-B401-70090686B3F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2D46A996-7DEF-4592-B401-70090686B3F2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2D46A996-7DEF-4592-B401-70090686B3F2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2D46A996-7DEF-4592-B401-70090686B3F2}.Release|Any CPU.Build.0 = Release|Any CPU + {7686BAA6-A0C5-4FA5-BE6E-FA044C2FFDD4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7686BAA6-A0C5-4FA5-BE6E-FA044C2FFDD4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7686BAA6-A0C5-4FA5-BE6E-FA044C2FFDD4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7686BAA6-A0C5-4FA5-BE6E-FA044C2FFDD4}.Release|Any CPU.Build.0 = Release|Any CPU + {76D78812-B640-4EF0-957B-15D0E9000A84}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {76D78812-B640-4EF0-957B-15D0E9000A84}.Debug|Any CPU.Build.0 = Debug|Any CPU + {76D78812-B640-4EF0-957B-15D0E9000A84}.Release|Any CPU.ActiveCfg = Release|Any CPU + {76D78812-B640-4EF0-957B-15D0E9000A84}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/ServiceDashBored.vsmdi b/ServiceDashBored.vsmdi new file mode 100644 index 0000000..3a2f48b --- /dev/null +++ b/ServiceDashBored.vsmdi @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/ServiceDashBored/EndpointStatus.cs b/ServiceDashBored/EndpointStatus.cs new file mode 100644 index 0000000..247ae5b --- /dev/null +++ b/ServiceDashBored/EndpointStatus.cs @@ -0,0 +1,309 @@ +using System; +using System.ComponentModel; +using System.Drawing; +using System.Threading; +using BitFactory.Logging; +using Endpoint; +using ServiceDashBored.Properties; + +namespace ServiceDashBored +{ + /// + /// Monitors a single Endpoint's Status + /// + public class EndpointStatus : INotifyPropertyChanged, IDisposable + { + #region Private Members + + private readonly IEndpoint _endpoint; + private readonly Timer _lastUpdateTimer; + private DateTime _lastUpdate; + private readonly Logger _logger; + private readonly TimeSpan _updatePeriod; + private static readonly object _propertyUpdateSyncObject = new Object(); + private static readonly object _callbackSyncObject = new Object(); + private bool _insideCallback; + + #endregion + + #region Properties + + /// + /// Gets the icon based on the status. + /// + public Icon StatusIcon { get; private set; } + + /// + /// Gets service's current status. + /// + /// The status. + public Status Status { get; private set; } + + /// + /// Returns a string explaining details on the current status + /// + public string StatusDescription { get; private set; } + + /// + /// Gets the time since the last update + /// + /// + public string TimeSinceLastUpdate + { + get + { + if (_lastUpdate == DateTime.MinValue) + return "never"; + if (_lastUpdate == DateTime.MaxValue) + return "updating"; + + var timeSpan = DateTime.Now - _lastUpdate; + if (timeSpan.Minutes > 0 ) + return String.Format("{0}m {1}s", timeSpan.Minutes, timeSpan.Seconds); + return + timeSpan.Seconds + 1 + "s"; + } + } + + /// + /// Gets the name of the service. + /// + /// The name of the service. + public string ServiceName + { + get + { + return _endpoint.Name; + } + } + + /// + /// Sets the last update time. + /// + /// The last update time. + private DateTime lastUpdate + { + set + { + _lastUpdate = value; + SignalPropertyChanged("TimeSinceLastUpdate"); + } + } + + #endregion + + #region Public Methods + + /// + /// Callback for the threadpool + /// + /// The thread context. + /// The callback was triggered from a timeout + public void ThreadPoolCallback(object threadContext, bool isTimeOut) + { + lock(_callbackSyncObject) + { + if (_insideCallback) + return; + _insideCallback = true; + } + try + { + // turn off the timer while waiting for an update + _lastUpdateTimer.Change(Timeout.Infinite, Timeout.Infinite); + updateStatus(); + } + catch (Exception ex) + { + // log exception + _logger.LogError(_endpoint.Name + ": " + ex); + } + finally + { + _lastUpdateTimer.Change(_updatePeriod, _updatePeriod); + _insideCallback = false; + } + } + + #endregion + + #region Private Methods + + /// + /// Gets the current status result of the endpoint + /// + /// Status + private void updateStatus() + { + lastUpdate = DateTime.MaxValue; + var newStatus = _endpoint.GetStatus(); + if (Status != newStatus) + { + Status = newStatus; + StatusDescription = _endpoint.StatusDescription; + StatusIcon = GetStatusIcon(Status); + // NOTE: wait to signal the property changes until all of them have been updated + SignalPropertyChanged("Status"); + SignalPropertyChanged("StatusDescription"); + SignalPropertyChanged("StatusIcon"); + } + lastUpdate = DateTime.Now; + } + + /// + /// Callback for the timer + /// + /// The obj. + private void timerCallback(object obj) + { + SignalPropertyChanged("TimeSinceLastUpdate"); + } + + #endregion + + #region Public Methods + + /// + /// Gets the status icon. + /// + /// The status. + /// + public static Icon GetStatusIcon(Status status) + { + switch (status) + { + case Status.Unknown: + return Resources.question; + case Status.Up: + return Resources.up; + case Status.Error: + case Status.Unreachable: + return Resources.remove; + case Status.Timeout: + return Resources.warning; + default: + return Resources.up; + } + } + + #endregion + + #region Constructor + + /// + /// Initializes a new instance of the class. + /// + /// The endpoint. + /// The logger. + public EndpointStatus(IEndpoint endpoint, Logger logger) + { + _endpoint = endpoint; + lastUpdate = DateTime.MinValue; + _logger = logger; + _updatePeriod = new TimeSpan(0, 0, 0, 1); + _lastUpdateTimer = new Timer(timerCallback, 0, _updatePeriod, _updatePeriod); + StatusIcon = GetStatusIcon(Status); + Status = Status.Unknown; + StatusDescription = string.Empty; + } + + #endregion + + #region PropertyChanged event + + /// + ///Occurs when a property value changes. + /// + public event PropertyChangedEventHandler PropertyChanged; + + #endregion + + #region PropertyChanged methods + + /// + /// Checks if the property () has been updated or changed. + /// + /// Name of the property. + /// The old value. + /// The new value. + /// True if the value was updated, false otherwise. + protected bool CheckPropertyChanged(string propertyName, ref T oldValue, ref T newValue) where T : class + { + if (oldValue == null && newValue == null) + { + return false; + } + + if (oldValue == null || !oldValue.Equals(newValue)) + { + oldValue = newValue; + + SignalPropertyChanged(propertyName); + + return true; + } + + return false; + } + + /// + /// Checks if the property () has been updated or changed. + /// + /// Name of the property. + /// The old value. + /// The new value. + /// True if the value was updated, false otherwise. + protected bool CheckValuePropertyChanged(string propertyName, ref T oldValue, ref T newValue) where T : struct + { + if (!oldValue.Equals(newValue)) + { + oldValue = newValue; + + SignalPropertyChanged(propertyName); + + return true; + } + + return false; + } + + /// + /// Creates the event that a property has changed value + /// + /// + protected void SignalPropertyChanged(string propertyName) + { + if (PropertyChanged == null) + return; + try + { + lock (_propertyUpdateSyncObject) // send only one property changed event at once + { + PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); + } + } + catch (InvalidOperationException) // HACK: Ignore the "BindingSource cannot be its own data source." + { + } + catch (Exception ex) + { + _logger.LogWarning("SignalPropertyChanged: " + ex); + } + } + + #endregion + + #region IDisposable + + /// + ///Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + ///2 + public void Dispose() + { + _lastUpdateTimer.Dispose(); + } + + #endregion + } +} diff --git a/ServiceDashBored/Main.Designer.cs b/ServiceDashBored/Main.Designer.cs new file mode 100644 index 0000000..3160e23 --- /dev/null +++ b/ServiceDashBored/Main.Designer.cs @@ -0,0 +1,256 @@ +namespace ServiceDashBored +{ + partial class Main + { + /// + /// 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 (notifyIcon != null) + { + notifyIcon.Visible = false; + notifyIcon.Dispose(); + notifyIcon = null; + } + + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Main)); + this.notifyIcon = new System.Windows.Forms.NotifyIcon(this.components); + this.trayContextMenuStrip = new System.Windows.Forms.ContextMenuStrip(this.components); + this.exitToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.dataGridView = new System.Windows.Forms.DataGridView(); + this.statusIconDataGridViewImageColumn = new System.Windows.Forms.DataGridViewImageColumn(); + this.ServiceName = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.TimeSinceLastUpdate = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.endpointBindingSource = new System.Windows.Forms.BindingSource(this.components); + this.cellTextBox = new System.Windows.Forms.TextBox(); + this.statusTextBox = new System.Windows.Forms.RichTextBox(); + this.splitContainer = new System.Windows.Forms.SplitContainer(); + this.dataGridContextMenuStrip = new System.Windows.Forms.ContextMenuStrip(this.components); + this.updateNowToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.trayContextMenuStrip.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.dataGridView)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.endpointBindingSource)).BeginInit(); + this.splitContainer.Panel1.SuspendLayout(); + this.splitContainer.Panel2.SuspendLayout(); + this.splitContainer.SuspendLayout(); + this.dataGridContextMenuStrip.SuspendLayout(); + this.SuspendLayout(); + // + // notifyIcon + // + this.notifyIcon.ContextMenuStrip = this.trayContextMenuStrip; + this.notifyIcon.Text = "Service DashBored"; + this.notifyIcon.Visible = true; + this.notifyIcon.MouseDoubleClick += new System.Windows.Forms.MouseEventHandler(this.notifyIcon_MouseDoubleClick); + // + // trayContextMenuStrip + // + this.trayContextMenuStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.exitToolStripMenuItem}); + this.trayContextMenuStrip.Name = "trayContextMenuStrip"; + this.trayContextMenuStrip.Size = new System.Drawing.Size(104, 26); + // + // exitToolStripMenuItem + // + this.exitToolStripMenuItem.Name = "exitToolStripMenuItem"; + this.exitToolStripMenuItem.Size = new System.Drawing.Size(103, 22); + this.exitToolStripMenuItem.Text = "Exit"; + this.exitToolStripMenuItem.Click += new System.EventHandler(this.exitToolStripMenuItem_Click); + // + // dataGridView + // + this.dataGridView.AllowUserToAddRows = false; + this.dataGridView.AllowUserToDeleteRows = false; + this.dataGridView.AllowUserToResizeColumns = false; + this.dataGridView.AllowUserToResizeRows = false; + this.dataGridView.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.dataGridView.AutoGenerateColumns = false; + this.dataGridView.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.Fill; + this.dataGridView.AutoSizeRowsMode = System.Windows.Forms.DataGridViewAutoSizeRowsMode.DisplayedCells; + this.dataGridView.BorderStyle = System.Windows.Forms.BorderStyle.None; + this.dataGridView.CellBorderStyle = System.Windows.Forms.DataGridViewCellBorderStyle.None; + this.dataGridView.ClipboardCopyMode = System.Windows.Forms.DataGridViewClipboardCopyMode.EnableWithoutHeaderText; + this.dataGridView.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.DisableResizing; + this.dataGridView.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] { + this.statusIconDataGridViewImageColumn, + this.ServiceName, + this.TimeSinceLastUpdate}); + this.dataGridView.DataSource = this.endpointBindingSource; + this.dataGridView.EditMode = System.Windows.Forms.DataGridViewEditMode.EditProgrammatically; + this.dataGridView.EnableHeadersVisualStyles = false; + this.dataGridView.Location = new System.Drawing.Point(12, 12); + this.dataGridView.MultiSelect = false; + this.dataGridView.Name = "dataGridView"; + this.dataGridView.ReadOnly = true; + this.dataGridView.RowHeadersBorderStyle = System.Windows.Forms.DataGridViewHeaderBorderStyle.Single; + this.dataGridView.RowHeadersVisible = false; + this.dataGridView.RowHeadersWidthSizeMode = System.Windows.Forms.DataGridViewRowHeadersWidthSizeMode.DisableResizing; + this.dataGridView.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; + this.dataGridView.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect; + this.dataGridView.Size = new System.Drawing.Size(368, 126); + this.dataGridView.TabIndex = 1; + this.dataGridView.RowEnter += new System.Windows.Forms.DataGridViewCellEventHandler(this.dataGridView_RowEnter); + this.dataGridView.MouseUp += new System.Windows.Forms.MouseEventHandler(this.dataGridView_MouseUp); + this.dataGridView.DataError += new System.Windows.Forms.DataGridViewDataErrorEventHandler(this.dataGridView_DataError); + // + // statusIconDataGridViewImageColumn + // + this.statusIconDataGridViewImageColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.None; + this.statusIconDataGridViewImageColumn.DataPropertyName = "StatusIcon"; + this.statusIconDataGridViewImageColumn.HeaderText = ""; + this.statusIconDataGridViewImageColumn.Name = "statusIconDataGridViewImageColumn"; + this.statusIconDataGridViewImageColumn.ReadOnly = true; + this.statusIconDataGridViewImageColumn.Resizable = System.Windows.Forms.DataGridViewTriState.False; + this.statusIconDataGridViewImageColumn.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.Automatic; + this.statusIconDataGridViewImageColumn.Width = 30; + // + // ServiceName + // + this.ServiceName.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill; + this.ServiceName.DataPropertyName = "ServiceName"; + this.ServiceName.HeaderText = "Service"; + this.ServiceName.MinimumWidth = 100; + this.ServiceName.Name = "ServiceName"; + this.ServiceName.ReadOnly = true; + // + // TimeSinceLastUpdate + // + this.TimeSinceLastUpdate.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.None; + this.TimeSinceLastUpdate.DataPropertyName = "TimeSinceLastUpdate"; + this.TimeSinceLastUpdate.HeaderText = "Last Update"; + this.TimeSinceLastUpdate.Name = "TimeSinceLastUpdate"; + this.TimeSinceLastUpdate.ReadOnly = true; + this.TimeSinceLastUpdate.Resizable = System.Windows.Forms.DataGridViewTriState.False; + this.TimeSinceLastUpdate.Width = 90; + // + // endpointBindingSource + // + this.endpointBindingSource.DataSource = typeof(ServiceDashBored.EndpointStatus); + // + // cellTextBox + // + this.cellTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.cellTextBox.Location = new System.Drawing.Point(12, 144); + this.cellTextBox.Name = "cellTextBox"; + this.cellTextBox.ReadOnly = true; + this.cellTextBox.Size = new System.Drawing.Size(368, 20); + this.cellTextBox.TabIndex = 2; + // + // statusTextBox + // + this.statusTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.statusTextBox.BackColor = System.Drawing.SystemColors.Window; + this.statusTextBox.Location = new System.Drawing.Point(12, 3); + this.statusTextBox.Name = "statusTextBox"; + this.statusTextBox.ReadOnly = true; + this.statusTextBox.Size = new System.Drawing.Size(368, 85); + this.statusTextBox.TabIndex = 3; + this.statusTextBox.Text = ""; + // + // splitContainer + // + this.splitContainer.Dock = System.Windows.Forms.DockStyle.Fill; + this.splitContainer.Location = new System.Drawing.Point(0, 0); + this.splitContainer.Name = "splitContainer"; + this.splitContainer.Orientation = System.Windows.Forms.Orientation.Horizontal; + // + // splitContainer.Panel1 + // + this.splitContainer.Panel1.Controls.Add(this.cellTextBox); + this.splitContainer.Panel1.Controls.Add(this.dataGridView); + this.splitContainer.Panel1MinSize = 135; + // + // splitContainer.Panel2 + // + this.splitContainer.Panel2.Controls.Add(this.statusTextBox); + this.splitContainer.Panel2MinSize = 100; + this.splitContainer.Size = new System.Drawing.Size(392, 270); + this.splitContainer.SplitterDistance = 166; + this.splitContainer.SplitterWidth = 3; + this.splitContainer.TabIndex = 4; + // + // dataGridContextMenuStrip + // + this.dataGridContextMenuStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.updateNowToolStripMenuItem}); + this.dataGridContextMenuStrip.Name = "dataGridContextMenuStrip"; + this.dataGridContextMenuStrip.Size = new System.Drawing.Size(145, 26); + // + // updateNowToolStripMenuItem + // + this.updateNowToolStripMenuItem.Name = "updateNowToolStripMenuItem"; + this.updateNowToolStripMenuItem.Size = new System.Drawing.Size(144, 22); + this.updateNowToolStripMenuItem.Text = "Update Now"; + this.updateNowToolStripMenuItem.Click += new System.EventHandler(this.updateNowToolStripMenuItem_Click); + // + // Main + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(392, 270); + this.Controls.Add(this.splitContainer); + this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); + this.MaximizeBox = false; + this.MinimumSize = new System.Drawing.Size(400, 300); + this.Name = "Main"; + this.Text = "Service DashBored"; + this.Load += new System.EventHandler(this.main_Load); + this.Resize += new System.EventHandler(this.main_Resize); + this.trayContextMenuStrip.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.dataGridView)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.endpointBindingSource)).EndInit(); + this.splitContainer.Panel1.ResumeLayout(false); + this.splitContainer.Panel1.PerformLayout(); + this.splitContainer.Panel2.ResumeLayout(false); + this.splitContainer.ResumeLayout(false); + this.dataGridContextMenuStrip.ResumeLayout(false); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.NotifyIcon notifyIcon; + private System.Windows.Forms.ContextMenuStrip trayContextMenuStrip; + private System.Windows.Forms.ToolStripMenuItem exitToolStripMenuItem; + private System.Windows.Forms.DataGridView dataGridView; + private System.Windows.Forms.BindingSource endpointBindingSource; + private System.Windows.Forms.TextBox cellTextBox; + private System.Windows.Forms.DataGridViewImageColumn statusIconDataGridViewImageColumn; + private System.Windows.Forms.DataGridViewTextBoxColumn TimeSinceLastUpdate; + private System.Windows.Forms.RichTextBox statusTextBox; + private System.Windows.Forms.SplitContainer splitContainer; + private System.Windows.Forms.DataGridViewTextBoxColumn ServiceName; + private System.Windows.Forms.ContextMenuStrip dataGridContextMenuStrip; + private System.Windows.Forms.ToolStripMenuItem updateNowToolStripMenuItem; + } +} + diff --git a/ServiceDashBored/Main.cs b/ServiceDashBored/Main.cs new file mode 100644 index 0000000..eab70b6 --- /dev/null +++ b/ServiceDashBored/Main.cs @@ -0,0 +1,428 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Threading; +using System.Windows.Forms; +using BitFactory.Logging; +using Endpoint; + +namespace ServiceDashBored +{ + /// + /// DashBored's UI. + /// + public partial class Main : Form + { + /// + /// Number of seconds between trying to poll an endpoint for its status + /// + private const int ENDPOINT_POLL_TIME_INTERVAL = 30; + + /// + /// Number of milliseconds to display status updates on the system tray notify balloon + /// + private const int NOTIFY_BALLOON_UPDATE_DELAY = 300; + + /// + /// Win32 message for when the user ends their session. + /// + /// http://msdn.microsoft.com/en-us/library/aa376890(VS.85).aspx + private const int WM_QUERYENDSESSION = 0x11; + + /// + /// The status to display in the system tray + /// + private Status _systemTrayStatusIcon = Status.Timeout; + private readonly BindingList _endpoints; + private readonly Dictionary _endpointStatuses; + private readonly Dictionary _endpointEventWaitHandle; + + /// + /// Default font for the Status Description text box + /// + private readonly Font _defaultFont = new Font("Microsoft Sans Serif", (float)8.25, FontStyle.Regular); + /// + /// Bold font for the Status Description text box + /// + private readonly Font _boldFont = new Font("Microsoft Sans Serif", (float)8.25, FontStyle.Bold); + /// + /// Different colors to use for each Status + /// + private readonly Dictionary _statusColor + = new Dictionary + { + {Status.Error, Color.DarkRed}, + {Status.Timeout, Color.DarkOrange}, + {Status.Unknown, Color.DarkOrange}, + {Status.Unreachable, Color.DarkRed}, + {Status.Up, Color.DarkGreen}, + }; + + /// + /// Set to true when windows is closing the current session. + /// + private bool _endSessionPending; + + /// + /// Logger instance. + /// + private readonly Logger _logger; + + /// + /// Initializes a new instance of the class. + /// + public Main(IEnumerable endpoints, Logger logger) + { + InitializeComponent(); + + _logger = logger; + + _logger.LogInfo("Starting."); + + loadMainIcon(Status.Up); + + _endpoints = new BindingList(); + endpointBindingSource.DataSource = _endpoints; + _endpointStatuses = new Dictionary(); + _endpointEventWaitHandle = new Dictionary(); + + foreach (var serviceEndpoint in endpoints) + { + registerServiceEndpoint(serviceEndpoint); + } + } + + /// + /// Override to intercept WM_QUERYENDSESSION - so we know when the current OS session is ending. + /// + /// + /// http://www.pixvillage.com/blogs/devblog/archive/2005/03/26/174.aspx + protected override void WndProc(ref Message m) + { + if (m.Msg == WM_QUERYENDSESSION) + _endSessionPending = true; + base.WndProc(ref m); + } + + /// + /// Minimize when the close button is clicked + /// + /// + protected override void OnClosing(CancelEventArgs e) + { + if (!_endSessionPending) + { + e.Cancel = true; + WindowState = FormWindowState.Minimized; // minimize instead of close + } + base.OnClosing(e); + } + + /// + /// Handles the Resize event of the Main control. + /// + /// The source of the event. + /// The instance containing the event data. + void main_Resize(object sender, EventArgs e) + { + ShowInTaskbar = WindowState != FormWindowState.Minimized; + } + + /// + /// Handles the Load event of the Main control. + /// + /// The source of the event. + /// The instance containing the event data. + private void main_Load(object sender, EventArgs e) + { + notifyIcon.BalloonTipTitle = "Service DashBored"; + notifyIcon.BalloonTipClicked += notifyIcon_BalloonTipClicked; + } + + /// + /// Handles the PropertyChanged event of an Endpoint. + /// + /// The source of the event. + /// The instance containing the event data. + void endpoint_PropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName != "Status") return; + + var senderEndpointStatus = sender as EndpointStatus; + if (senderEndpointStatus == null) + throw new ArgumentException("sender is not EndpointStatus - it is: " + sender.GetType()); + + Invoke((MethodInvoker)(()=>updateStatus(senderEndpointStatus))); + } + + /// + /// Handles the BalloonTipClicked event of the notifyIcon control. + /// + /// The source of the event. + /// The instance containing the event data. + void notifyIcon_BalloonTipClicked(object sender, EventArgs e) + { + TopMost = true; + WindowState = FormWindowState.Normal; + Focus(); + TopMost = false; + } + + /// + /// Handles the Click event of the exitToolStripMenuItem control. + /// + /// The source of the event. + /// The instance containing the event data. + private void exitToolStripMenuItem_Click(object sender, EventArgs e) + { + _logger.LogInfo("User Exiting."); + Application.Exit(); + } + + /// + /// When notify is double clicked, show the main window + /// + /// + /// + private void notifyIcon_MouseDoubleClick(object sender, MouseEventArgs e) + { + if (WindowState == FormWindowState.Normal) + { + WindowState = FormWindowState.Minimized; + } + else + { + TopMost = true; + WindowState = FormWindowState.Normal; + Focus(); + TopMost = false; + } + } + + private void dataGridView_RowEnter(object sender, DataGridViewCellEventArgs e) + { + var endpointStatus = (EndpointStatus)dataGridView.Rows[e.RowIndex].DataBoundItem; + cellTextBox.Text = string.Format("{0}. Status Description: {1}", + endpointStatus.ServiceName, endpointStatus.StatusDescription); + } + + private void dataGridView_DataError(object sender, DataGridViewDataErrorEventArgs e) + { + _logger.LogError(e.Exception); + } + + /// + /// Registers the service endpoint. + /// + /// The service endpoint. + private void registerServiceEndpoint(IEndpoint endpoint) + { + var endpointStatus = new EndpointStatus(endpoint, _logger); + + // Time Interval MasterThread will be executed + var timeOut = TimeSpan.FromSeconds(ENDPOINT_POLL_TIME_INTERVAL); + + // Register a timed callback method in the threadpool. + var waitObject = new AutoResetEvent(false); + ThreadPool.RegisterWaitForSingleObject( + waitObject, + endpointStatus.ThreadPoolCallback, + null, + timeOut, + false + ); + + _endpoints.Add(endpointStatus); + _endpointStatuses.Add(endpointStatus, endpointStatus.Status); + _endpointEventWaitHandle.Add(endpointStatus, waitObject); + endpointStatus.PropertyChanged += endpoint_PropertyChanged; + + waitObject.Set(); // signal an initial callback when done registying + } + + /// + /// Loads the icon based on the status. + /// + /// The status. + private void loadMainIcon(Status status) + { + if (_systemTrayStatusIcon == status) + return; + + notifyIcon.Icon = EndpointStatus.GetStatusIcon(status); + + notifyIcon.BalloonTipIcon = (status == Status.Up) ? ToolTipIcon.Info : ToolTipIcon.Error; + + _systemTrayStatusIcon = status; + } + + /// + /// Updates the status for the given endpoint. + /// + /// Execute on UI thread + /// The endpoint status. + private void updateStatus(EndpointStatus endpointStatus) + { + if (endpointStatus == null) + throw new ArgumentNullException("endpointStatus"); + + lock (_endpoints) + { + // if the status hasn't changed, don't do anything... + if (_endpointStatuses[endpointStatus] == endpointStatus.Status) return; + + // if the status has changed... + var oldStatus = _endpointStatuses[endpointStatus]; + _endpointStatuses[endpointStatus] = endpointStatus.Status; + + // find the worst status of all + var statuses = new List(_endpointStatuses.Values); + statuses.Sort(new StatusComparer()); + loadMainIcon(statuses[0]); + + // update the system tray notify ballon info + notifyIcon.BalloonTipText = getShortServiceStatusMessage(endpointStatus); + notifyIcon.Text = endpointStatus.Status == Status.Up ? "Service Running" : "Service Problem"; + + // update the info in the status text box + updateStatusTextBox(); + + if (oldStatus != Status.Unknown || endpointStatus.Status != Status.Up) + { + notifyIcon.ShowBalloonTip(NOTIFY_BALLOON_UPDATE_DELAY); + _logger.LogInfo(string.Format("{0}. {1} : {2}", endpointStatus.ServiceName, endpointStatus.Status, + endpointStatus.StatusDescription)); + } + } + } + + /// + /// Updates the status text box - call this from the UI thread. + /// + private void updateStatusTextBox() + { + statusTextBox.Text = String.Empty; + var first = true; + foreach (var endpointStatus in _endpoints) + { + if (endpointStatus.Status == Status.Up || endpointStatus.Status == Status.Unknown) + continue; + + if (!first) + { + statusTextBox.SelectedText = Environment.NewLine + Environment.NewLine; + } + else + { + first = false; + } + + // Make the service description all pretty in the rich text box + statusTextBox.SelectionColor = _statusColor[endpointStatus.Status]; + statusTextBox.SelectionFont = _defaultFont; + statusTextBox.SelectedText = endpointStatus.ServiceName; + + statusTextBox.SelectionColor = Color.Black; + statusTextBox.SelectionFont = _defaultFont; + statusTextBox.SelectedText = ". "; + + statusTextBox.SelectionColor = _statusColor[endpointStatus.Status]; + statusTextBox.SelectionFont = _boldFont; + statusTextBox.SelectedText = endpointStatus.Status.ToString(); + + statusTextBox.SelectionColor = Color.Black; + statusTextBox.SelectionFont = _defaultFont; + statusTextBox.SelectedText = " : " + endpointStatus.StatusDescription; + } + } + + /// + /// Gets the service status message. + /// + /// The sender endpoint status. + /// + private static string getShortServiceStatusMessage(EndpointStatus senderEndpointStatus) + { + var msg = string.Empty; + switch (senderEndpointStatus.Status) + { + case Status.Error: + msg = "Service Error: " + senderEndpointStatus.ServiceName; + break; + case Status.Timeout: + msg = "Service Timeout: " + senderEndpointStatus.ServiceName; + break; + case Status.Unknown: + msg = "Status Unknown: " + senderEndpointStatus.ServiceName; + break; + case Status.Unreachable: + msg = "Service Unreachable: " + senderEndpointStatus.ServiceName; + break; + case Status.Up: + msg = "Service Running: " + senderEndpointStatus.ServiceName; + break; + } + return msg; + } + + private void dataGridView_MouseUp(object sender, MouseEventArgs e) + { + var hitTestInfo = dataGridView.HitTest(e.X, e.Y); + if (hitTestInfo.Type != DataGridViewHitTestType.Cell) + { + // deselect everything + var selectedEndpointStatus = getSelectedDataGridViewRow(); + if (selectedEndpointStatus != null) + selectedEndpointStatus.Selected = false; + return; + } + + if (e.Button == MouseButtons.Right && hitTestInfo.RowIndex >= 0) + { + // select row on right click as well + if (hitTestInfo.RowIndex >= 0) + { + var selectedEndpointStatus = getSelectedDataGridViewRow(); + if (selectedEndpointStatus != null) + selectedEndpointStatus.Selected = false; + + var clickedRow = dataGridView.Rows[hitTestInfo.RowIndex]; + clickedRow.Selected = true; + } + + // show the context menu + dataGridContextMenuStrip.Show(dataGridView, e.Location); + } + } + + private DataGridViewRow getSelectedDataGridViewRow() + { + return dataGridView.SelectedRows.Count > 0 + ? dataGridView.Rows[dataGridView.SelectedRows[0].Index] + : null; + } + + private EndpointStatus getSelectedEndpointStatus() + { + var dataGridViewRow = getSelectedDataGridViewRow(); + return dataGridViewRow != null + ? dataGridViewRow.DataBoundItem as EndpointStatus + : null; + } + + private void updateNowToolStripMenuItem_Click(object sender, EventArgs e) + { + try + { + var endpointStatus = getSelectedEndpointStatus(); + if (endpointStatus != null) + _endpointEventWaitHandle[endpointStatus].Set(); + } + catch (Exception ex) + { + _logger.LogError(ex); + } + } + } +} diff --git a/ServiceDashBored/Main.resx b/ServiceDashBored/Main.resx new file mode 100644 index 0000000..e54c8b6 --- /dev/null +++ b/ServiceDashBored/Main.resx @@ -0,0 +1,305 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 160, 17 + + + 17, 17 + + + True + + + 245, 17 + + + 395, 17 + + + True + + + + + AAABAAEAMDAAAAEAIACoJQAAFgAAACgAAAAwAAAAYAAAAAEAIAAAAAAAAAAAABMLAAATCwAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAEAAAADAAAABQAA + AAUAAAAFAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABQAAAAUAAAADAAAAAgAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAA + AAMAAAACAAAACQAAAA0AAAAPAAAADwAAAA8AAAAPAAAADwAAAA8AAAAPAAAADQAAAAkAAAACAAAAAgAA + AAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAABAAAAABAPDjQ4NzSmPTw5qURDQKtMSkesU1FNq1hWUqtbWlarV1ZSq1FPTKtKSEWrQkA+qzo5 + Nqk0MzGnEhIRPQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAC8uLGWLiIH/hoN9/4iFf/+Kh4H/jImC/42Kg/+NioP/i4iC/4iF + f/+EgXz/gH14/3x5df+AfXj/ODc1ggAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8PDiRcWVXsYl9a/15cV/9eW1b/XFpV/1tY + VP9aV1P/WVZS/1hWUf9XVVH/V1VQ/1dVUf9UUk70EBAPLQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABGRkdZcHFxj3R1doh3d3iIfH19iISEhIhcXFyITk5Pik5OT4hqaWnZenp4/3t6 + eP99fHr/fn58/39/ff9/f37/f359/319fP98fHr/enp5/3h4d/9ub23oU1RUjFNTVIpeXl+Jg4SFiIKC + goh7fHyId3h4iHV2d45MTExcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABMTU67SktM/0tMTv9MTU7/TU5P/09QUf9XWFn/W1xd/15f + YP9dX2D/X2Fh/2JjZP9kZWf/Zmdp/2hpav9pamv/aWpr/2hqa/9oaWr/Z2hq/2VmaP9lZmf/Z2hp/2Nk + Zv9tbWr/iYh5/1hZWf9SU1T/UFFS/01OT/9DQ0TOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGFiYgNJSku/Pj8//zk6Ov84OTn/Nzc4/zU1 + Nv80NTX/NDU1/zQ1Nf80NTb/NTY2/zY3N/82Nzj/Nzg4/zc4Of84ODn/ODg5/zg5Of84OTn/ODk6/zg5 + Of84OTr/ODk5/zY3OP9HR0L/c3Jg/zw9O/83ODn/Nzc4/z4/P/82NzjSAAAABwAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGhoaAJPUVK/LS0s/yUk + If8nJiP/JiUi/yUkIf8kIyD/IiIf/yEhHv8gIB3/Hx8c/x4eG/8dHRr/HRwa/xwcGv8cHBr/HBsZ/xwb + Gf8bGxn/GxoZ/xoaGf8aGhj/GhoY/xoaGP8ZGBf/FRQU/xkZF/8YGBb/FRUT/yYnJ/9FQj73JxwNogIC + AGEAAAA6AAAAIAAAAAsAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHJz + dAJYWVq/Li4t/ykoJP8rKib/Kikl/ykoJf8nJiP/JiUi/yUkIf8kIyD/IiIf/yEhHv8hIR7/ICAd/yAg + Hf8gHxz/Hx8c/x8fHP8fHhv/Hh4b/x4eG/8dHRv/HRwa/xwcGv8cGxr/GxsZ/xsbGf8bGxn/GBgW/yMl + Jv9PSUH/e1kt/2VII/QsIA+7AgEBfgAAAFoAAAA5AAAAGwAAAAkAAAACAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAHd3dwJeX2C/MjIw/ywrJ/8uLSn/LSwo/ywrJ/8rKib/KSgl/ycmJP8nJSL/JSQh/yQj + IP8kIyD/IyMg/yMiH/8jIh//IiIf/yEhHv8hIR7/ISEe/yAgHf8fHxz/Hx8c/x4eG/8eHhv/HR0b/x0d + G/8dHRv/GhoY/yQlJ/9NRz//fFkt/39cLv9/XC7/WEAg5RMOB6AAAAB5AAAAXwAAADwAAAAeAAAACwAA + AAMAAAABAAAAAAAAAAAAAAAAAAAAAHh4eAJkZWa/NTUz/zAvKv8yMCz/MS8r/zAuKv8uLSn/LCso/ysq + Jv8qKSX/KCck/ygnJP8nJiP/JyYj/yYlIv8mJSL/JSQh/yUkIf8lJCH/JCMg/yMiH/8iIh//ISEe/yEg + Hf8gIB3/Hx8d/x8fHf8eHhz/HBwa/yQmJ/9MRj7/gFor/35aLP99Wi3/gV0v/3NUKvgmHA6xAAAAfQAA + AG4AAABPAAAALgAAABYAAAAJAAAAAwAAAAEAAAAAAAAAAH6AgwJra22/ODg2/zUyLv82NDD/NTMv/zQy + Lv8yMCz/MC8r/y8uKv8uLSn/LCsn/ysqJ/8rKib/Kikm/yopJv8qKCX/KSgl/ykoJf8oJyT/JyYj/yYl + Iv8mJSL/JCQh/yQjIP8jIyD/IiIf/yIiH/8hIR7/Hh4b/yQmJ/9MRj3/hl8u/4ReLv+BXC7/flsu/31b + Lv94WC39MCMStQAAAHkAAABtAAAAUgAAADQAAAAcAAAADQAAAAUAAAAAAAAAAIqKigJxcnS/PDs5/zk3 + Mv87OTX/OTcz/zg2Mv83NTD/NTMv/zMxLf8yMCz/MS8r/zAuKv8vLir/Ly4q/y8tKf8uLSn/LSwp/y0s + KP8sKyf/Kyon/yopJf8oJyT/KCck/ycmI/8mJSL/JSUi/yUkIf8kJCH/ISEe/yUmJ/9MRj3/jGUy/4lj + Mf+GYTH/g2Aw/35cL/9lSif/blEq/ygdD7YAAABwAAAAZAAAAEsAAAAyAAAAHQAAAA0AAAAAAAAAAIuL + iwN2eHnAPz88/z48N/8/PTj/Pjw3/z07Nv87OTX/Ojgz/zg2Mv83NTH/NjQw/zY0MP81My//NDIv/zQy + Lv80Mi7/MzEt/zIwLP8xLyv/Ly4q/y4tKf8tLCj/LCsn/yopJv8qKSb/KSgl/ygnJP8nJiP/JSQh/yYn + KP9MRj3/k2s4/49pNv+LZjX/iGQ0/4djM/9kSij/Uj0h/1hBIv8fFgyhAAAAYQAAAFUAAAA/AAAAKgAA + ABcAAAAAAAAAAIuLiwN6e3zARENA/0NBPP9FQj3/Q0E8/0I/Ov9APjn/Pz04/z48N/89Ozb/PDo1/zs5 + NP86ODT/Ojg0/zo4M/85NzP/ODYy/zc1Mf82NDD/NDIu/zMxLf8xMCz/MC8r/y8uKv8uLSn/LSwo/ywr + KP8rKif/KCck/ycoKP9NRz7/mHNA/5ZwPf+RbTz/jmo5/4toN/9/XjL/XUYm/1dBJP9RPCHyDQkFdgAA + AFEAAABDAAAAMAAAAB0AAAAAAAAAAJGRkgN+f4DASUhG/0pHQv9LSUT/SkdC/0hGQf9HRD//RUM+/0RC + PP9DQTz/QkA7/0E/Ov9BPzr/QD45/z89OP8/PTj/Pjw3/zw6Nf86ODX/OTcz/zg2Mv82NDD/NTMv/zMy + Lv8yMS3/MTAs/zAvK/8vLir/LCsn/ygpKv9MRz7/nXxJ/5t4Rv+YdUT/k3FA/49tPf+Nazv/gmE1/2BI + Kf9hSSn/RzUdwwAAAEsAAABBAAAAMQAAAB8AAAAAAAAAAJubmwODg4XAUE5M/1NQSv9SUEn/UU5I/09N + R/9OTEb/TUpE/0xJQ/9LSEP/SkdC/0lGQf9IRkD/R0U//0ZEP/9FQz7/Q0E8/0E/Ov9APTn/Pjw3/zw7 + Nv87OTT/OTcz/zg2Mv83NTH/NjQw/zUzL/8zMi7/MC8r/ykqK/9MSED/oIVU/5+BUP+dfU3/mXlK/5V1 + Rv+Pb0H/jGs9/4JjOP96XTT/f181/yEZDnEAAAAyAAAALQAAAB0AAAAAAAAAAJubmwOFh4jAVFNQ/15c + Vf9dW1T/WlhR/1hWT/9XVU7/VlNM/1RSS/9TUUr/U1BJ/1FPSP9QTkf/T01G/01LRf9MSUP/SkdC/0dF + QP9FQz7/Q0E8/0E/Ov9APjj/Pjw4/z07N/88OjX/Ojg0/zk3M/83NjL/NDMv/yssLP9MSUL/pY5f/6OK + XP+hhlj/noFU/5l8T/+Kb0b/h2tB/4ZpP/+FaD3/clg0/1xHKccAAAAqAAAAJQAAABkAAAAAAAAAAJub + mwOIiYrAWVhU/2ZkXP9pZl//aGVe/2ZjW/9kYFn/Yl9X/19cVf9eW1T/XFpS/1pYUP9ZVk//VlRN/1RS + S/9SUEn/UE1H/05LRf9LSEP/SUZB/0ZEP/9EQj7/Q0E8/0JAO/9BPzr/Pz04/z07N/87OTX/ODYy/ywt + Lf9MSkP/qpZp/6iTZ/+ljmP/pIpf/5h/V/+OdU7/gmpG/4VrRf+Ha0P/f2U+/4NnPvsdFw5JAAAAFgAA + ABMAAAAAAAAAAJycnAOLi43AXl1a/3FuZv9zcGj/c3Bo/3NvZ/9zb2f/cW5l/29sY/9taWH/a2df/2hl + Xf9mY1v/ZGBZ/2FeVv9eW1T/W1hR/1hWT/9VU03/U1BL/1BNSP9OS0b/SkdC/0dFQP9FQj3/Q0E8/0E/ + Ov8/PTn/PDk1/y4tLf9NTEf/rqB5/6yacv+qlW3/p5Fp/6CJYv+dhV3/moBY/5N6Uf+PdU3/i3BI/45y + SP9PQCmTAAAABwAAAA4AAAAAAAAAAJydnQONj5DAZGNf/3x4cP9+enL/f3tz/397c/9/e3L/f3ty/316 + cP98eG//eXVs/3Zyaf9zb2f/b2xk/2toYP9nZFz/ZGFa/2BeV/9dW1T/WlhR/1dVT/9VU03/U1BL/1FO + Sf9MSUT/R0VA/0RCPf9CQDv/Pz04/y8vLv9OTkr/tKuR/7GliP+unnr/qJVw/6OPbP+ciGT/m4Vg/5iA + Wv+Te1T/jnVP/450Tf9vWzzGAQEBBwAAAAcAAAAAAAAAAJ2dnQOPkJHAamhl/4iEev+Lh37/jIl//42J + f/+Nin//jIl+/4uHfP+IhXr/hoJ4/4J+dP99enD/eXVt/3Vxaf9wbWX/bGlg/2hlXf9jYVr/YF5X/11b + VP9bWFL/WFZQ/1ZTTv9TUEv/UE1I/0tIQ/9FQz7/QT86/zEwL/9QT0z/uK+W/6yiif+zqZD/rqCB/6WT + c/+mknD/nYpn/5qFYf+XgVz/kXtW/411Uf+Lc0/4HRgQKgAAAAAAAAAAAAAAAJ2dnQKPkJG/b25q/5OP + hf+Wk4n/mJSK/5qWi/+blov/mZWK/5iUif+VkYb/ko6D/42Kf/+IhXv/g4B2/356cf95dG3/dHBp/29s + ZP9raGD/Z2Rd/2NhWv9fXVb/XFtU/1lXUf9XVE//U1BL/1BNSP9LSUT/REI9/zExMP9SUk//ubCX/6qd + hP+6sZv/r6SN/5iJcP+olnb/oY9v/5+Laf+ahWP/lH9c/5B6V/+Se1b/MioeUgAAAAAAAAAAAAAAAJyc + nAOPkJHAdXRv/56bj/+inpP/paGW/6eil/+no5f/pqGW/6WglP+hnZH/nZmO/5iUif+Tj4T/jYl//4aD + ef+BfXT/e3hw/3Zza/9ybmf/bWpj/2lnX/9lY1z/YV9Y/15bVf9aV1L/V1RP/1NQS/9PTEf/SkhD/zMz + Mv9UVFH/vrWc/6qbgf+4rZf/p5uD/5+Sev+tnoP/ppZ3/6ORcf+di2n/l4Ri/5J+XP+VgFz/QjgoaQAA + AAAAAAAAAAAAAJ2dnQKPkJG/enhz/6ikmf+tqZ7/r6ug/7Gtof+yraH/sayg/6+qnv+rppr/p6KW/6Gd + kv+cmI3/lZGG/46Lgf+IhXv/gn52/315cf94dW3/c3Bo/25rZP9qZ2D/ZWNc/2FfWP9dW1T/WVZR/1VS + Tf9QTkn/TEpE/zY2Nf9TUk//uq2T/7Gjiv+toIj/p5uD/6KVfv+fk3z/p5h+/6aWeP+gjm//moho/5SC + Yf+YhGL/ST8uegAAAAAAAAAAAAAAAJ2dnAONjo+/fnx3/7Gtov+1saX/ubSo/7u2qv+7tqr/urWp/7ez + p/+0r6P/r6qe/6mkmf+jnpP/nJiN/5WSh/+Oi4H/iIR7/4J+dv98eXH/eHVt/3NwaP9ua2T/aWdg/2Ri + W/9fXlf/W1lT/1dUT/9SUEv/TktG/zc3Nv9SUEz/vrCX/7mtk/+ypo7/raGK/6echP+flH3/ppmD/6qb + f/+jk3X/nItt/5aFZv+UgmL/gXFVggAAAAAAAAAAAAAAAJ2dnQKMjY6/gH96/7q1qf++uKz/wLuv/8K9 + sf/CvbH/wbyv/765rf+6tan/tbCk/6+qnv+opJj/op2S/5qXjP+TkIb/jYmA/4eDev+Bfnb/fHlx/3d0 + bP9yb2j/bGpj/2dlXv9iYFr/XltW/1pXUv9VUk3/UE5I/zg4N/9VU0//x7uh/8G0nP+5rpb/s6eQ/6yg + iv+lmYP/q6CM/6ibhP+gkXX/n49x/5mIav+TgmT/k4JkggAAAAAAAAAAAAAAAJydnQKKjI2/gH54/7y3 + qv/Auq7/wr2x/8S/sv/Ev7L/w72x/8C7rv+8t6r/t7Gl/7Crn/+ppZn/op6T/5uXjP+UkIb/jYmA/4eD + ev+Bfnb/fHlx/3d0bP9yb2j/bWtj/2dmXv9jYVr/X1xX/1pXUv9VUk3/UE1I/zk4N/9WVVH/0MOq/8i8 + pP/AtJ3/ua2W/7Glj/+qnoj/rKGM/6qfif+jlXr/oJB0/5qLbv+UhWn/kYFlegAAAAAAAAAAAAAAAKGh + oQOJiovAYmFf/2tpZP9vbWj/cG5p/3JxbP9ycWz/cG9r/29uaf9ta2f/amhk/2ZlYP9jYV3/Xl1Z/1pZ + Vf9WVVH/UlFN/01MSf9KSUb/R0ZD/0NCQP9APz3/PT06/zs6OP83Nzb/NDQz/zIxMP8vLi3/Kioo/yYn + Kf9TUk//2Mux/9DDqv/GuqL/vLGa/7Spk/+soYv/pJmC/6aahP+mmYD/oZJ2/5yNcf+Wh2z/koRpagAA + AAAAAAAAAAAAAAAAAACVlpeQh4iJ1oeIic2HiInNh4eJzn98ePh/e3T/gHx2/398dv99enb/fHp3/3x6 + d/95eHX/eHd0/3h3dP92dXL/dXRy/3h3c/93d3P/d3Zz/3Z2cv92dnL/c3Ju/3Fvav9wbmr/b21o/2tq + Zf9oZ2L/ZGNe/1taV/+GgHb/4dS6/9THsP/Kvqb/wLSe/7arlf+uoo3/pZmD/56Tff+lmID/o5V5/52P + c/+XiW//k4VsUQAAAAAAAAAAAAAAAAAAAAClpqYBm5ubApmZmQKZmpsBAAAAAIVxUrqQd1H/l4Bb/5WB + Yf+PgGf/lodv/52Qef+bkHv/o5iC/66jjv+qoI3/uK6Z/8vCrf/UzLb/3tW//+nhyv/17dX/9evS//Pk + yP/77M////PV///22f//9dj//fDU//Xozf/w4sj/4tW8/9fKs//MwKn/wrag/7itlv+vo43/p5uF/6CU + fv+lmIH/oZN6/5uOdP+YinH3lIdvJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH5n + Q4KLc0//kXpX/4p4XP+NfmT/lIVt/5uOd/+dkXz/qp6H/7GlkP+too//v7Sg/8vBrP/VzLX/39W//+ri + y//069P/+fHZ///64v//+eL///zh///84f//+97///bZ//zt0v/w4sj/49a9/9jLs//Nwan/wraf/7is + lv+vo43/ppqE/6ichv+sn4j/m493/5qNdf+Yi3PDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAIFrSz6IclD/kHpY/4d3XP+OfmT/lIVt/5uNd/+ekn3/pJiD/7Clj/+zqJT/wLWg/8rA + q//Rx7L/3dO9/+nfyP/069P/+vPb////6f///e3////x////6f///+f///vh//3y2f/z5s7/5trD/9rO + uP/Pw63/xLii/7qvmf+xppD/qZ6I/6aahv+mmoT/npJ7/5qNd/+ZjHSIAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACFcVC7kXtY/4l4Xf+NfWT/k4Rs/5uNdv+iloD/n5SA/6me + iv+yp5P/uK2a/8a8p//Euqf/2s+6/+fdx//z6NH//fXc//303P/+9uD///7t///95v///eX///jg//zw + 2f/y5s//59vF/9vQu//RxrH/xrun/72ynf+0qZT/rKGN/6Wahv+elID/mo97/5aLdveZjHQqAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACEcFBej3lX/5J+Xv+VhGb/mYpv/56S + eP+kmYH/pp2G/6ich/+vpJD/tauX/8K4o/+/taP/0siz/97Tvv/q38n/+e3W//3z2//68Nn///jg///3 + 3///9t//+/DY//To0v/r38j/4dW//9bLtv/Mwaz/w7ei/7qumf+xppH/qp+L/6SYhf+dkn//mI56/5mN + drQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACHclIEinVUzJaA + Xf+ah2b/nY9w/56Qdf+hk3z/qZ+I/6echv+nnYn/t6yY/8C1oP+9s6D/x7yp/9jNuP/k2cP/8OXN//ru + 1v/269T/9OnS//ft1f/88dn/9OjQ/+vfyP/l2cL/28+5/9DFr//HvKb/v7Od/7aqlf+uo47/p5yI/6OY + g/+iln7/nZF5/5uNdTwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAiHNTSpF8Wv+ZhGL/nIxs/5+SdP+jl3z/qJ+F/6qhif+rn4j/t6uU/7yxm/+6r5z/v7Wi/87D + rv/f07z/5tvE/+7jy//z58//7OHK//Llzv/569L/9OfO/+vfxv/d0br/0saw/8m+p//Btp//ua2Y/7So + kv+ypo//qp+J/6mchP+jln3/nZB3nQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAIt3VpuWgV7/m4hn/56PcP+ilXj/ppyA/6yiif+pnof/sKOL/7er + lP+zqpb/urCc/8O5pP/UybL/3dG6/+PXv//o3MT/697G/+3gx//s38b/5dm//+PWvP/bzrX/z8Kq/8C2 + n/+7sJn/u66W/7eqkv+ypo7/rKCI/6aZgP+gk3rfnZB3GwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIt3VQ+QfFrMmYVj/52MbP+gk3P/pJl7/6mf + g/+qn4f/qJuF/66ii/+to47/s6iV/7yynf/MwKn/08av/9jLs//cz7b/3tG4/9/SuP/f0bf/2syz/9XH + rv/Uxaz/zL6l/7+zm/+4rJX/uayT/7Snjv+toYn/qJuC/6OVfPueknlHAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACNeVgklH9d4JuI + Zv+ejm7/opV2/6ebff+roYT/qp6G/6WXgv+onIf/rKGM/7arlP/Et57/yLyj/83Ap//NwKj/0cOq/9LE + q//SxKr/0MKn/8y+pP/Ju6H/xLac/7+xl/+5rJL/tKaN/6+hiP+pm4P/pJd+/qCTemAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAj3taKZeCYdudimj/oJBw/6SWd/+onX7/rKKG/6yhh/+qnIP/p5uE/7GkjP+7rpP/v7GX/8K1 + mv/As5r/w7Wb/8e5nv/HuZ3/xbec/8O1mv+/spf/u66T/7epj/+zpYv/rqGH/6qcg/+kl377oZR7XgAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAJJ9XRyYhGO+notr/6GRcf+klnf/qJx+/62jhf+vpYb/q52B/66e + g/+zpIj/tqeL/7mqjv+7rZD/vK2R/72ukv+9rpL/vK2R/7qrkP+3qI3/tKaK/7CiiP+tn4X/qZuB/6SW + feugk3pIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACUgGADmYVlfJ6La/WhkXH/pZZ2/6ib + e/+soIH/rqOE/6yegP+rmn3/rp2A/7Cgg/+yooT/s6OG/7Skh/+0pYj/tKSI/7Kih/+voIX/rZ6D/6qb + gP+mmH3/opR6uKGTeh8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJuH + Zymei2uioY9v+qSUdP+nmHn/qpx9/62ggP+snn7/qZh5/6iXeP+pmHr/q5l8/6ybfv+sm3//q5t//6qa + f/+omH3/pZZ7/6KTeM6hkndXAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAn4xrK6GObYmjkXDgpZRz+aiYd/+qnHr/rJ18/6qaev+mlXb/pJJ0/6SS + df+kk3b/o5N3/qOSdu2hkXWsoJB0R6GQdQYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACij24Io5BvJqSRcGCllHOYp5Z0wamY + d9WqmXjXqZh4yqWVdqyfjXB4no1wOp+PchEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////8AAP///////wAA//AAf/// + AAD/8AA///8AAP/wAH///wAA8AAAAH//AADgAAAAP/8AAOAAAAA//wAA4AAAAB//AADgAAAAB/8AAOAA + AAAB/wAA4AAAAAD/AADgAAAAAH8AAOAAAAAAPwAA4AAAAAAfAADgAAAAAB8AAOAAAAAADwAA4AAAAAAP + AADgAAAAAAcAAOAAAAAABwAA4AAAAAADAADgAAAAAAMAAOAAAAAAAwAA4AAAAAADAADgAAAAAAMAAOAA + AAAAAwAA4AAAAAABAADgAAAAAAEAAOAAAAAAAwAA4AAAAAADAADgAAAAAAMAAP8AAAAAAwAA/wAAAAAD + AAD/gAAAAAMAAP+AAAAABwAA/8AAAAAHAAD/wAAAAA8AAP/gAAAADwAA/+AAAAAfAAD/8AAAAD8AAP/4 + AAAAfwAA//wAAAD/AAD//gAAAf8AAP//gAAD/wAA///AAA//AAD///AAP/8AAP///wP//wAA//////// + AAA= + + + \ No newline at end of file diff --git a/ServiceDashBored/Program.cs b/ServiceDashBored/Program.cs new file mode 100644 index 0000000..e0da7bb --- /dev/null +++ b/ServiceDashBored/Program.cs @@ -0,0 +1,42 @@ +using System; +using System.Windows.Forms; +using BitFactory.Logging; +using Castle.Core.Resource; +using Castle.Windsor; +using Castle.Windsor.Configuration.Interpreters; + +namespace ServiceDashBored +{ + static class Program + { + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + IWindsorContainer container = + new WindsorContainer( + new XmlInterpreter(new ConfigResource("castle"))); + + // Request the component to use it + var form = (Main)container[typeof(Main)]; + + try + { + // Use the component + Application.Run(form); + } + catch (Exception ex) + { + var logger = (Logger)container[typeof(Logger)]; + logger.LogFatal(ex); + } + finally + { + // Release it + container.Release(form); + } + } + } +} diff --git a/ServiceDashBored/Properties/AssemblyInfo.cs b/ServiceDashBored/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..3f25458 --- /dev/null +++ b/ServiceDashBored/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ServiceDashBored")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("ServiceDashBored")] +[assembly: AssemblyCopyright("Copyright © 2008")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("ff0209e6-0dad-4e5c-8dc2-c1f004eafab0")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/ServiceDashBored/Properties/DataSources/EndpointStatus.datasource b/ServiceDashBored/Properties/DataSources/EndpointStatus.datasource new file mode 100644 index 0000000..5e3fa60 --- /dev/null +++ b/ServiceDashBored/Properties/DataSources/EndpointStatus.datasource @@ -0,0 +1,10 @@ + + + + ServiceDashBored.EndpointStatus, ServiceDashBored, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + \ No newline at end of file diff --git a/ServiceDashBored/Properties/Resources.Designer.cs b/ServiceDashBored/Properties/Resources.Designer.cs new file mode 100644 index 0000000..fbd6897 --- /dev/null +++ b/ServiceDashBored/Properties/Resources.Designer.cs @@ -0,0 +1,98 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30128.1 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ServiceDashBored.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ServiceDashBored.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + internal static System.Drawing.Icon accept { + get { + object obj = ResourceManager.GetObject("accept", resourceCulture); + return ((System.Drawing.Icon)(obj)); + } + } + + internal static System.Drawing.Icon question { + get { + object obj = ResourceManager.GetObject("question", resourceCulture); + return ((System.Drawing.Icon)(obj)); + } + } + + internal static System.Drawing.Icon remove { + get { + object obj = ResourceManager.GetObject("remove", resourceCulture); + return ((System.Drawing.Icon)(obj)); + } + } + + internal static System.Drawing.Icon up { + get { + object obj = ResourceManager.GetObject("up", resourceCulture); + return ((System.Drawing.Icon)(obj)); + } + } + + internal static System.Drawing.Icon warning { + get { + object obj = ResourceManager.GetObject("warning", resourceCulture); + return ((System.Drawing.Icon)(obj)); + } + } + } +} diff --git a/ServiceDashBored/Properties/Resources.resx b/ServiceDashBored/Properties/Resources.resx new file mode 100644 index 0000000..a551597 --- /dev/null +++ b/ServiceDashBored/Properties/Resources.resx @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + ..\Resources\accept.ico;System.Drawing.Icon, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\question.ico;System.Drawing.Icon, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\remove.ico;System.Drawing.Icon, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\up.ico;System.Drawing.Icon, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\warning.ico;System.Drawing.Icon, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + \ No newline at end of file diff --git a/ServiceDashBored/References/BitFactory.Logging.dll b/ServiceDashBored/References/BitFactory.Logging.dll new file mode 100644 index 0000000..c56317c Binary files /dev/null and b/ServiceDashBored/References/BitFactory.Logging.dll differ diff --git a/ServiceDashBored/References/Castle.Core.dll b/ServiceDashBored/References/Castle.Core.dll new file mode 100644 index 0000000..a10d597 Binary files /dev/null and b/ServiceDashBored/References/Castle.Core.dll differ diff --git a/ServiceDashBored/References/Castle.DynamicProxy2.dll b/ServiceDashBored/References/Castle.DynamicProxy2.dll new file mode 100644 index 0000000..4461908 Binary files /dev/null and b/ServiceDashBored/References/Castle.DynamicProxy2.dll differ diff --git a/ServiceDashBored/References/Castle.MicroKernel.dll b/ServiceDashBored/References/Castle.MicroKernel.dll new file mode 100644 index 0000000..9f7616c Binary files /dev/null and b/ServiceDashBored/References/Castle.MicroKernel.dll differ diff --git a/ServiceDashBored/References/Castle.Windsor.dll b/ServiceDashBored/References/Castle.Windsor.dll new file mode 100644 index 0000000..c577e4a Binary files /dev/null and b/ServiceDashBored/References/Castle.Windsor.dll differ diff --git a/ServiceDashBored/Resources/DryIcons Free Icons Aesthetica 2.0 Icon Set.URL b/ServiceDashBored/Resources/DryIcons Free Icons Aesthetica 2.0 Icon Set.URL new file mode 100644 index 0000000..f8f9508 --- /dev/null +++ b/ServiceDashBored/Resources/DryIcons Free Icons Aesthetica 2.0 Icon Set.URL @@ -0,0 +1,2 @@ +[InternetShortcut] +URL=http://dryicons.com/free-icons/preview/aesthetica-version-2/ diff --git a/ServiceDashBored/Resources/Up.ico b/ServiceDashBored/Resources/Up.ico new file mode 100644 index 0000000..e19d86c Binary files /dev/null and b/ServiceDashBored/Resources/Up.ico differ diff --git a/ServiceDashBored/Resources/accept.ico b/ServiceDashBored/Resources/accept.ico new file mode 100644 index 0000000..a1ebe4e Binary files /dev/null and b/ServiceDashBored/Resources/accept.ico differ diff --git a/ServiceDashBored/Resources/question.ico b/ServiceDashBored/Resources/question.ico new file mode 100644 index 0000000..705634a Binary files /dev/null and b/ServiceDashBored/Resources/question.ico differ diff --git a/ServiceDashBored/Resources/remove.ico b/ServiceDashBored/Resources/remove.ico new file mode 100644 index 0000000..fca3b4a Binary files /dev/null and b/ServiceDashBored/Resources/remove.ico differ diff --git a/ServiceDashBored/Resources/warning.ico b/ServiceDashBored/Resources/warning.ico new file mode 100644 index 0000000..630d345 Binary files /dev/null and b/ServiceDashBored/Resources/warning.ico differ diff --git a/ServiceDashBored/ServiceDashBored.csproj b/ServiceDashBored/ServiceDashBored.csproj new file mode 100644 index 0000000..18dfbd1 --- /dev/null +++ b/ServiceDashBored/ServiceDashBored.csproj @@ -0,0 +1,148 @@ + + + + Debug + AnyCPU + 9.0.21022 + 2.0 + {2D46A996-7DEF-4592-B401-70090686B3F2} + WinExe + Properties + ServiceDashBored + ServiceDashBored + v2.0 + 512 + + + 3.5 + + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + false + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + AllRules.ruleset + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + AllRules.ruleset + + + + False + References\BitFactory.Logging.dll + + + False + References\Castle.Core.dll + + + False + References\Castle.DynamicProxy2.dll + + + False + References\Castle.MicroKernel.dll + + + False + References\Castle.Windsor.dll + + + + + + + + + + + + + + + Form + + + Main.cs + + + + + Main.cs + Designer + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + True + + + + + + + + + + + + {7686BAA6-A0C5-4FA5-BE6E-FA044C2FFDD4} + Endpoint + + + + + False + .NET Framework 3.5 SP1 Client Profile + false + + + False + .NET Framework 3.5 SP1 + true + + + False + Windows Installer 3.1 + true + + + + + \ No newline at end of file diff --git a/ServiceDashBored/app.config b/ServiceDashBored/app.config new file mode 100644 index 0000000..154153f --- /dev/null +++ b/ServiceDashBored/app.config @@ -0,0 +1,157 @@ + + + +
+ + + + + + + + ${google.endpoint} + ${10.21.63.6.endpoint} + ${localhost.freespace.endpoint} + + + + + + + + ServiceDashBored.log + + + + + + kpcodl06:8080 (paypal) + http://kpcodl06:8080/paypal_server/PayPalService + + + + + + kpapqlz03:8080 (paypal) + http://kpapqlz03:8080/paypal_server/PayPalService + + + + + + kpcoql03:8083 (OrderService) + http://kpcoql03.jewelry.qa:8083/catalyst_server/OrderService + + + + + + 10.21.63.6 (ws-virtual) + http://10.21.63.6 + + + + + + 10.21.63.7 (ws-virtual) + http://10.21.63.7 + + + + + + 10.21.63.8 (ws-virtual) + http://10.21.63.8 + + + + + + 10.21.63.10 (ws-virtual) + http://10.21.63.10 + + + + + + 10.21.63.11 (ws-physical) + http://10.21.63.11 + + + + + + 10.21.63.12 (ws-physical) + http://10.21.63.12 + + + + + + images.jewelry.dmz (ws-virtual) + http://images.jewelry.dmz + + + + + + Google + http://www.google.com + + + + + + localhost D: Freespace + \\localhost + select FreeSpace from Win32_LogicalDisk where DeviceID='D:' + FreeSpace + 50e6 + + + + + + kpapdl26:8080 (payment) + http://kpapdl26:8080/core_server/paymentservice + //jtv:provider/text() + jtv,http://jtv.com + NoDecision + "urn:getPaymentKey" + + ]]> + + + + + + + pscoql03:8080 (payment) + http://pscoql03:8080/core_server/paymentservice + //jtv:provider/text() + jtv,http://jtv.com + NoDecision + "urn:getPaymentKey" + + ]]> + + + + + + + 10.20.7.26:8080 (payment) + http://10.20.7.26:8080/core_server/paymentservice + //jtv:provider/text() + jtv,http://jtv.com + NoDecision + "urn:getPaymentKey" + + ]]> + + + + + + + \ No newline at end of file diff --git a/ServiceDashBored/main.ico b/ServiceDashBored/main.ico new file mode 100644 index 0000000..7d734dd Binary files /dev/null and b/ServiceDashBored/main.ico differ diff --git a/ServiceDashBoredTests/Properties/AssemblyInfo.cs b/ServiceDashBoredTests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..912da74 --- /dev/null +++ b/ServiceDashBoredTests/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ServiceDashBoredTests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("ServiceDashBoredTests")] +[assembly: AssemblyCopyright("Copyright © 2008")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM componenets. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("515c4aa0-000b-4576-b132-a01941b92b89")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Revision and Build Numbers +// by using the '*' as shown below: +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/ServiceDashBoredTests/ServiceDashBoredTests.csproj b/ServiceDashBoredTests/ServiceDashBoredTests.csproj new file mode 100644 index 0000000..a3f06be --- /dev/null +++ b/ServiceDashBoredTests/ServiceDashBoredTests.csproj @@ -0,0 +1,51 @@ + + + Debug + AnyCPU + 9.0.21022 + 2.0 + {4160DFC0-6484-4B92-A6C8-8EAF8655C1CD} + Library + Properties + ServiceDashBoredTests + ServiceDashBoredTests + v3.5 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + 3.5 + + + + + + + + \ No newline at end of file