diff --git a/Cachet-Monitor.sln b/Cachet-Monitor.sln
new file mode 100644
index 0000000..46b35ba
--- /dev/null
+++ b/Cachet-Monitor.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29728.190
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cachet-Monitor", "Cachet-Monitor\Cachet-Monitor.csproj", "{95DC5C5F-1837-4577-BD25-CA8BE900060D}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {95DC5C5F-1837-4577-BD25-CA8BE900060D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {95DC5C5F-1837-4577-BD25-CA8BE900060D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {95DC5C5F-1837-4577-BD25-CA8BE900060D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {95DC5C5F-1837-4577-BD25-CA8BE900060D}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {564B86A6-C853-4278-B04A-BF3776B746B0}
+ EndGlobalSection
+EndGlobal
diff --git a/Cachet-Monitor/Cachet-Monitor.csproj b/Cachet-Monitor/Cachet-Monitor.csproj
new file mode 100644
index 0000000..73d1f50
--- /dev/null
+++ b/Cachet-Monitor/Cachet-Monitor.csproj
@@ -0,0 +1,23 @@
+
+
+
+ Exe
+ netcoreapp3.1
+ Cachet_Monitor
+ 2020.2.24.0
+ 2020.2.24.0
+
+
+
+
+
+
+
+
+
+
+ ..\CachNet.dll
+
+
+
+
diff --git a/Cachet-Monitor/Models/Configuration/Configuration.cs b/Cachet-Monitor/Models/Configuration/Configuration.cs
new file mode 100644
index 0000000..ce41d5a
--- /dev/null
+++ b/Cachet-Monitor/Models/Configuration/Configuration.cs
@@ -0,0 +1,62 @@
+using Cachet_Monitor.Models.Config;
+using System;
+using System.Collections.Generic;
+
+namespace Cachet_Monitor.Models
+{
+ public class Configuration
+ {
+ public Uri ApiBase { get; set; }
+ public string ApiKey { get; set; }
+
+ public List Monitors { get; set; }
+
+ public static Configuration Default = new Configuration
+ {
+ ApiBase = new Uri("https://domain.example.com"),
+ ApiKey = "AAABBBCCC11223344556",
+ Monitors = new List
+ {
+ new Monitor
+ {
+ Name = "Website",
+ Target = "https://example.com",
+ ComponentId = 1,
+ MetricId = 1,
+ Interval = 60,
+ Timeout = 5,
+ Type = MonitorType.HTTP,
+ Settings = new MonitorSettings
+ {
+ ExpectedStatusCode = 200,
+ Method = "GET"
+ }
+ },
+ new Monitor
+ {
+ Name = "Database",
+ Target = "example.com",
+ ComponentId = 1,
+ MetricId = 1,
+ Interval = 60,
+ Timeout = 5,
+ Type = MonitorType.PORT,
+ Settings = new MonitorSettings
+ {
+ Port = 3306
+ }
+ },
+ new Monitor
+ {
+ Name = "Server",
+ Target = "example.com",
+ ComponentId = 1,
+ MetricId = 1,
+ Interval = 60,
+ Timeout = 5,
+ Type = MonitorType.ICMP
+ }
+ }
+ };
+ }
+}
\ No newline at end of file
diff --git a/Cachet-Monitor/Models/Configuration/Monitor/Monitor.cs b/Cachet-Monitor/Models/Configuration/Monitor/Monitor.cs
new file mode 100644
index 0000000..305549e
--- /dev/null
+++ b/Cachet-Monitor/Models/Configuration/Monitor/Monitor.cs
@@ -0,0 +1,24 @@
+namespace Cachet_Monitor.Models.Config
+{
+ public class Monitor
+ {
+ //Meta
+ public string Name;
+
+ public string Target;
+
+ //Cachet
+ public int ComponentId;
+
+ public int MetricId;
+
+ //Timing
+ public int Interval;
+
+ public int Timeout;
+
+ public MonitorType Type;
+
+ public MonitorSettings Settings;
+ }
+}
\ No newline at end of file
diff --git a/Cachet-Monitor/Models/Configuration/Monitor/MonitorSettings.cs b/Cachet-Monitor/Models/Configuration/Monitor/MonitorSettings.cs
new file mode 100644
index 0000000..5713dd2
--- /dev/null
+++ b/Cachet-Monitor/Models/Configuration/Monitor/MonitorSettings.cs
@@ -0,0 +1,15 @@
+namespace Cachet_Monitor.Models.Config
+{
+ public class MonitorSettings
+ {
+ //Http
+ public string Method = "GET";
+
+ public int ExpectedStatusCode = 200;
+
+ //TCP
+ public int Port;
+
+ public int TTL;
+ }
+}
\ No newline at end of file
diff --git a/Cachet-Monitor/Models/Configuration/Monitor/MonitorType.cs b/Cachet-Monitor/Models/Configuration/Monitor/MonitorType.cs
new file mode 100644
index 0000000..9e373a2
--- /dev/null
+++ b/Cachet-Monitor/Models/Configuration/Monitor/MonitorType.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace Cachet_Monitor.Models.Config
+{
+ public enum MonitorType
+ {
+ HTTP,
+ PORT,
+ ICMP
+ }
+}
\ No newline at end of file
diff --git a/Cachet-Monitor/Program.cs b/Cachet-Monitor/Program.cs
new file mode 100644
index 0000000..593a09b
--- /dev/null
+++ b/Cachet-Monitor/Program.cs
@@ -0,0 +1,223 @@
+using Cachet_Monitor.Models;
+using Cachet_Monitor.Models.Config;
+using Monitor = Cachet_Monitor.Models.Config.Monitor;
+using CachNet;
+using Newtonsoft.Json;
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using System.Threading;
+using System.Net.Sockets;
+using System.Net;
+using System.Diagnostics;
+using CachNet.Entities;
+using System.Net.NetworkInformation;
+
+namespace Cachet_Monitor
+{
+ internal class Program
+ {
+ private static Configuration Configuration;
+ private static CachetClient Cachet;
+
+ private static void Main()
+ {
+ {
+ var config = Path.Combine(Environment.CurrentDirectory, "settings.json");
+
+ if (File.Exists(config))
+ {
+ Configuration = JsonConvert.DeserializeObject(File.ReadAllText(config));
+ }
+ else
+ {
+ File.WriteAllText(config, JsonConvert.SerializeObject(Configuration.Default, Formatting.Indented));
+
+ Console.WriteLine($"Written a new instance of the configuration to: {config}");
+ Console.ReadKey();
+ Environment.Exit(0);
+ }
+ }
+
+ Cachet = new CachetClient(Configuration.ApiBase.OriginalString, Configuration.ApiKey);
+
+ MainAsync().GetAwaiter().GetResult();
+ }
+
+ private static async Task MainAsync()
+ {
+ foreach (var monitor in Configuration.Monitors)
+ {
+ new Thread(async () =>
+ {
+ Thread.CurrentThread.IsBackground = true;
+ while (true)
+ {
+ await DoMonitorCheck(Cachet, monitor).ConfigureAwait(false);
+
+ await Task.Delay(monitor.Interval * 1000);
+ }
+ }).Start();
+ }
+
+ await Task.Delay(-1).ConfigureAwait(false);
+ }
+
+ private static async Task DoMonitorCheck(CachetClient Cachet, Monitor monitor)
+ {
+ ComponentStatus componentStatus = 0;
+ switch (monitor.Type)
+ {
+ case MonitorType.PORT:
+ {
+ using (TcpClient tcpClient = new TcpClient())
+ {
+ try
+ {
+ tcpClient.Connect(monitor.Target, monitor.Settings.Port);
+ await Cachet.UpdateComponentAsync(monitor.ComponentId, new PutComponent
+ {
+ Status = ComponentStatus.Operational,
+ });
+ }
+ catch (Exception)
+ {
+ await Cachet.UpdateComponentAsync(monitor.ComponentId, new PutComponent
+ {
+ Status = ComponentStatus.MajorOutage,
+ });
+ }
+ }
+ }
+ break;
+
+ case MonitorType.ICMP:
+ {
+ Ping ping = new Ping();
+ PingOptions options = new PingOptions
+ {
+ DontFragment = true,
+ Ttl = monitor.Settings.TTL
+ };
+
+ PingReply reply = ping.Send(monitor.Target, monitor.Timeout, null, options);
+
+ await Cachet.AddMetricPointAsync(monitor.MetricId, new PostMetricPoint
+ {
+ Value = (int)reply.RoundtripTime
+ });
+
+ if (reply.Status == IPStatus.Success)
+ {
+ await Cachet.UpdateComponentAsync(monitor.ComponentId, new PutComponent
+ {
+ Status = ComponentStatus.Operational,
+ });
+ }
+
+ if (reply.RoundtripTime >= monitor.Timeout)
+ {
+ await Cachet.UpdateComponentAsync(monitor.ComponentId, new PutComponent
+ {
+ Status = ComponentStatus.PerformanceIssues,
+ });
+ }
+
+ if (reply.Status != IPStatus.Success)
+ {
+ if (reply.Status == IPStatus.TimedOut)
+ {
+ await Cachet.UpdateComponentAsync(monitor.ComponentId, new PutComponent
+ {
+ Status = ComponentStatus.PartialOutage,
+ });
+ }
+ else
+ {
+ await Cachet.UpdateComponentAsync(monitor.ComponentId, new PutComponent
+ {
+ Status = ComponentStatus.MajorOutage,
+ });
+ }
+ }
+ }
+ break;
+
+ case MonitorType.HTTP:
+ {
+ HttpWebRequest request = (HttpWebRequest)WebRequest.Create(monitor.Target);
+ request.Timeout = monitor.Timeout * 1000;
+
+ Stopwatch timer = new Stopwatch();
+
+ timer.Start();
+ try
+ {
+ HttpWebResponse response = (HttpWebResponse)request.GetResponse();
+ timer.Stop();
+
+ if (response.StatusCode == (HttpStatusCode)monitor.Settings.ExpectedStatusCode)
+ {
+ var x = await Cachet.UpdateComponentAsync(monitor.ComponentId, new PutComponent
+ {
+ Status = ComponentStatus.Operational,
+ });
+ }
+ else
+ {
+ await Cachet.UpdateComponentAsync(monitor.ComponentId, new PutComponent
+ {
+ Status = ComponentStatus.PartialOutage,
+ });
+ }
+
+ response.Close();
+ }
+ catch (WebException ex)
+ {
+ timer.Stop();
+
+ if (ex.Status == WebExceptionStatus.ProtocolError)
+ {
+ if (ex.Response is HttpWebResponse response)
+ {
+ if (response.StatusCode == (HttpStatusCode)monitor.Settings.ExpectedStatusCode)
+ {
+ await Cachet.UpdateComponentAsync(monitor.ComponentId, new PutComponent
+ {
+ Status = ComponentStatus.Operational,
+ });
+ }
+ }
+ }
+ else
+ {
+ if (ex.Status == WebExceptionStatus.Timeout)
+ {
+ await Cachet.UpdateComponentAsync(monitor.ComponentId, new PutComponent
+ {
+ Status = ComponentStatus.PerformanceIssues,
+ });
+ }
+ else
+ {
+ await Cachet.UpdateComponentAsync(monitor.ComponentId, new PutComponent
+ {
+ Status = ComponentStatus.MajorOutage,
+ });
+ }
+ }
+ }
+
+ await Cachet.AddMetricPointAsync(monitor.MetricId, new PostMetricPoint
+ {
+ Value = (int)timer.ElapsedMilliseconds
+ });
+ }
+ break;
+ }
+
+ Console.WriteLine($"Ran check on \"{monitor.Target}\" Status: {componentStatus}");
+ }
+ }
+}
\ No newline at end of file