From f903be2342aebddb025c6147053b3d4f5ad26216 Mon Sep 17 00:00:00 2001 From: taywon Date: Mon, 29 Apr 2024 17:30:47 +0200 Subject: [PATCH] added docker support, various little changes --- Salmon.Model/Monitor/Container.cs | 64 +++++++++++++++++ Salmon.Model/Monitor/Drive.cs | 2 +- Salmon.Model/Monitor/WebEndpoint.cs | 92 +++++++++++++++++++++++++ Salmon.Service/Configuration.cs | 40 +---------- Salmon.Service/Monitor.cs | 9 --- Salmon.Service/Program.cs | 92 ++++++++++++++++++++----- Salmon.Service/RunEnv/config.json | 42 ++++++++++- Salmon.Service/RunEnv/start-service.bat | 1 + Salmon.Service/RunEnv/start-web.bat | 2 + Salmon.Service/RunEnv/start.bat | 2 - Salmon.Service/Salmon.Service.csproj | 3 +- Salmon.Service/Watchers/Base.cs | 37 ++++++++++ Salmon.Service/Watchers/Docker.cs | 68 ++++++++++++++++++ Salmon.Service/Watchers/Executable.cs | 21 ++++++ Salmon.Service/Watchers/WebStatus.cs | 19 +++++ Salmon.Web/Pages/ElementPage.razor | 9 ++- 16 files changed, 431 insertions(+), 72 deletions(-) create mode 100644 Salmon.Model/Monitor/Container.cs create mode 100644 Salmon.Model/Monitor/WebEndpoint.cs delete mode 100644 Salmon.Service/Monitor.cs create mode 100644 Salmon.Service/RunEnv/start-service.bat create mode 100644 Salmon.Service/RunEnv/start-web.bat delete mode 100644 Salmon.Service/RunEnv/start.bat create mode 100644 Salmon.Service/Watchers/Base.cs create mode 100644 Salmon.Service/Watchers/Docker.cs create mode 100644 Salmon.Service/Watchers/Executable.cs create mode 100644 Salmon.Service/Watchers/WebStatus.cs diff --git a/Salmon.Model/Monitor/Container.cs b/Salmon.Model/Monitor/Container.cs new file mode 100644 index 0000000..ba70db3 --- /dev/null +++ b/Salmon.Model/Monitor/Container.cs @@ -0,0 +1,64 @@ +using Salmon.Core; +using Salmon.Core.Cliff; + +namespace Salmon.Model.Monitor; + +public class Container + : Element +{ + [PersistentField] + public bool? Joinable { get; set; } + + [PersistentField] + public bool? Running { get; set; } + + [PersistentField] + public string? ContainerId { get; set; } + + [PersistentField] + public string? Image { get; set; } + + [PersistentField] + public string? ImageId { get; set; } + + [PersistentField] + public string? ContainerStatus { get; set; } + + public bool Ok { + get + { + return Joinable == true && Running == true; + } + } + + protected Container() + { + + } + + public Container(string id) + : base(id) + { + + } + + public override IEnumerable> ImportantProperties() + { + foreach (var i in base.ImportantProperties()) + yield return new KeyValuePair(i.Key, i.Value); + + yield return new KeyValuePair(nameof(Ok), Ok); + + if (ContainerId is not null) + yield return new KeyValuePair(nameof(ContainerId), ContainerId); + + if (Image is not null) + yield return new KeyValuePair(nameof(Image), Image); + + if (ContainerStatus is not null) + yield return new KeyValuePair(nameof(ContainerStatus), ContainerStatus); + + + } + +} diff --git a/Salmon.Model/Monitor/Drive.cs b/Salmon.Model/Monitor/Drive.cs index 0969189..948a606 100644 --- a/Salmon.Model/Monitor/Drive.cs +++ b/Salmon.Model/Monitor/Drive.cs @@ -1,4 +1,4 @@ - using Salmon.Core.Cliff; +using Salmon.Core.Cliff; namespace Salmon.Model.Monitor; diff --git a/Salmon.Model/Monitor/WebEndpoint.cs b/Salmon.Model/Monitor/WebEndpoint.cs new file mode 100644 index 0000000..b692568 --- /dev/null +++ b/Salmon.Model/Monitor/WebEndpoint.cs @@ -0,0 +1,92 @@ +using Salmon.Core; +using Salmon.Core.Cliff; +using System.Diagnostics; + +namespace Salmon.Model.Monitor; + +public class WebEndpoint + : Element +{ + [PersistentField] + public bool Joinable { get; set; } = true; + + [PersistentField] + public string? Uri { get; set; } + + [PersistentField] + public string? Method { get; set; } + + [PersistentField] + public int? Code { get; set; } + + [PersistentField] + public string? Content { get; set; } + + public bool Ok + { + get + { + return Joinable && (Code != null && Code >= 200 && Code < 300); + } + } + + protected WebEndpoint() + { + } + + public WebEndpoint(string identifier) + : base(identifier) + { + + } + + public override IEnumerable> ImportantProperties() + { + foreach (var kv in base.ImportantProperties()) + yield return kv; + + if (Uri is not null) + yield return new KeyValuePair(nameof(Uri), Uri); + + if (Method is not null) + yield return new KeyValuePair(nameof(Method), Method); + + yield return new KeyValuePair(nameof(Ok), Ok); + + if (Code is not null) + yield return new KeyValuePair(nameof(Code), Code); + } + + public static async Task From(string uri, string method = "GET", string? id = null) + { + if(id is null) + id = Helper.CreateIdFrom("webendpoint", uri, method); + + using HttpClient client = new(); + HttpMethod httpmethod = new(method); + HttpRequestMessage request = new(httpmethod, uri); + + HttpResponseMessage response; + var ret = new WebEndpoint(id) + { + Uri = uri, + Method = method + }; + + try + { + response = await client.SendAsync(request); + } + catch(Exception e) + { + ret.Joinable = false; + ret.Content = e.ToString(); + return ret; + } + + ret.Code = (int)response.StatusCode; + ret.Content = response.Content.ToString(); + + return ret; + } +} diff --git a/Salmon.Service/Configuration.cs b/Salmon.Service/Configuration.cs index 4e75059..4dbd97a 100644 --- a/Salmon.Service/Configuration.cs +++ b/Salmon.Service/Configuration.cs @@ -1,8 +1,4 @@ -using Salmon.Core; -using Salmon.Model.Monitor; -using System.Security.Cryptography.X509Certificates; - -namespace Salmon.Service; +namespace Salmon.Service; public class Configuration { @@ -11,37 +7,5 @@ public class Configuration public bool? MonitorHardware { get; set; } = true; public bool? MonitorLocalSoftware { get; set; } = true; - public List Watch { get; set; } = new (); -} - -public abstract class WatcherConfiguration -{ - public abstract Task> ForgeElements(); -} - -public class ExecutableInstanceWatcherConfiguration - : WatcherConfiguration -{ - public string Path { get; set; } - - public override async Task> ForgeElements() - { - List ret = new(); - - foreach (var el in Software.FromPath(Path)) - ret.Add(el); - - return ret; - } -} -public class HttpPageWatcherConfiguration - : WatcherConfiguration -{ - public string Method { get; set; } = "GET"; - public string Uri { get; set; } - - public override async Task> ForgeElements() - { - throw new NotImplementedException(); - } + public List Watch { get; set; } = new (); } \ No newline at end of file diff --git a/Salmon.Service/Monitor.cs b/Salmon.Service/Monitor.cs deleted file mode 100644 index cc5fc66..0000000 --- a/Salmon.Service/Monitor.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Salmon.Service; - - -public class Monitor -{ - private void FlushStatus() - { - } -} diff --git a/Salmon.Service/Program.cs b/Salmon.Service/Program.cs index 154819d..bd46d9a 100644 --- a/Salmon.Service/Program.cs +++ b/Salmon.Service/Program.cs @@ -12,6 +12,8 @@ int? period = null; Uri? uri = null; bool? monitor_hardware = null; bool? monitor_localsoftware = null; +List watchers = new(); +bool? watch_config = true; // 1: use environment variable var period_env = Environment.GetEnvironmentVariable(ENV_PERIOD); @@ -34,39 +36,70 @@ if(uri_env != null) // 2: override with configuration file string configpath = "config.json"; -if (File.Exists(configpath)) +Func> reloadConfigFile = new(async (bool configFileMustExists ) => { + await Task.Delay(500); + + if (!File.Exists(configpath)) + { + if (!configFileMustExists) + return 0; + + Console.WriteLine($"Cannot find config file\"{configpath}\"."); + return 1; + } + + + string content; + try + { + content = File.ReadAllText(configpath); + } + catch(Exception ex) + { + Console.WriteLine($"Cannot open config file\"{configpath}\": {ex}."); + return 1; + } + Console.WriteLine("Found a configuration file, parsing..."); - string content = File.ReadAllText(configpath); + + Configuration? conf = null; try { conf = JsonSerializer.Deserialize(content); } - catch(Exception e) + catch (Exception e) { Console.WriteLine($"Cannot parse config file \"{configpath}\", {e}."); return 1; } - if(conf == null) + if (conf == null) { Console.WriteLine($"Cannot parse config file \"{configpath}\"."); return 1; } - if(conf.Url != null) + if (conf.Url != null) uri = conf.Url; - if(conf.Period != null) + if (conf.Period != null) period = conf.Period; - if(conf.MonitorHardware != null) - monitor_hardware = conf.MonitorHardware; - - if(conf.MonitorLocalSoftware != null) + if (conf.MonitorHardware != null) + monitor_hardware = conf.MonitorHardware; + + if (conf.MonitorLocalSoftware != null) monitor_localsoftware = conf.MonitorLocalSoftware; -} + + if (conf.Watch != null) + watchers = conf.Watch; + + return 0; +}); +if (await reloadConfigFile(false) != 0) + return 0; //todo: override with parameter @@ -84,17 +117,32 @@ if(uri == null) return 1; } - - // Initialisation Transmitter transmitter = new(); - +FileSystemWatcher configWatcher; Console.WriteLine($"Salmon.Service started at {DateTime.Now}."); +if(configpath != null && watch_config == true) +{ + FileInfo fi = new (configpath); + + //watch configuration if needed + configWatcher = new FileSystemWatcher(fi.DirectoryName); + configWatcher.Filter = fi.Name; + configWatcher.NotifyFilter = NotifyFilters.CreationTime + | NotifyFilters.LastWrite; + configWatcher.Changed += async (object sender, FileSystemEventArgs e) => + { + if (await reloadConfigFile(false) != 0) + Console.WriteLine("Cannot use modified config file, using last valid file."); + else + Console.WriteLine("Config file reloaded."); + }; + configWatcher.EnableRaisingEvents = true; +} while ( true ) { - var software = Salmon.Model.Monitor.Software.FromLocal(); List tosend = new(); @@ -105,6 +153,18 @@ while ( true ) var hardwares = Salmon.Model.Monitor.Hardware.FromAllHardware(); tosend.AddRange(hardwares); } + + foreach (var w in watchers) + try + { + foreach (var el in await w.ForgeElements()) + tosend.Add(el); + } + catch(Exception e) + { + Console.WriteLine($"Error while forging element: {e}."); + } + try { @@ -120,4 +180,4 @@ while ( true ) } await Task.Delay(period.Value); -} +} \ No newline at end of file diff --git a/Salmon.Service/RunEnv/config.json b/Salmon.Service/RunEnv/config.json index 0251e3d..b260844 100644 --- a/Salmon.Service/RunEnv/config.json +++ b/Salmon.Service/RunEnv/config.json @@ -1,3 +1,41 @@ { - "Url":"http://localhost:5009/api/Push" -} \ No newline at end of file + "Url":"http://localhost:5009/api/Push", + "Period":15000, + "Watch":[ + { + "Type":"web", + "ShortName": "Voie93quarts", + "Uri":"https://voie93quarts.fr" + }, + { + "Type":"web", + "ShortName": "Voie93quarts fallback", + "Uri":"https://sdgsdgsdg.voie93quarts.fr" + }, + { + "Type":"web", + "ShortName": "Home assistant", + "Uri":"https://ha.voie93quarts.fr" + }, + { + "Type":"web", + "ShortName": "Gitea", + "Uri":"https://git.voie93quarts.fr" + }, + { + "Type":"docker", + "Uri":"http://192.168.1.156:4243", + "Containers":[ + { + "ShortName": "Jellyfin container", + "ContainerName": "/jellyfin" + }, + { + "ShortName": "Gitea container", + "ContainerName": "/gitea" + } + ] + } + + ] +} diff --git a/Salmon.Service/RunEnv/start-service.bat b/Salmon.Service/RunEnv/start-service.bat new file mode 100644 index 0000000..0e0059f --- /dev/null +++ b/Salmon.Service/RunEnv/start-service.bat @@ -0,0 +1 @@ +dotnet run --project ../Salmon.Service.csproj \ No newline at end of file diff --git a/Salmon.Service/RunEnv/start-web.bat b/Salmon.Service/RunEnv/start-web.bat new file mode 100644 index 0000000..a4e4f78 --- /dev/null +++ b/Salmon.Service/RunEnv/start-web.bat @@ -0,0 +1,2 @@ +cd ../../Salmon.Web +dotnet run http \ No newline at end of file diff --git a/Salmon.Service/RunEnv/start.bat b/Salmon.Service/RunEnv/start.bat deleted file mode 100644 index ec524c7..0000000 --- a/Salmon.Service/RunEnv/start.bat +++ /dev/null @@ -1,2 +0,0 @@ -cd .. -dotnet run \ No newline at end of file diff --git a/Salmon.Service/Salmon.Service.csproj b/Salmon.Service/Salmon.Service.csproj index 4263478..22deddb 100644 --- a/Salmon.Service/Salmon.Service.csproj +++ b/Salmon.Service/Salmon.Service.csproj @@ -1,4 +1,4 @@ - + Exe @@ -8,6 +8,7 @@ + diff --git a/Salmon.Service/Watchers/Base.cs b/Salmon.Service/Watchers/Base.cs new file mode 100644 index 0000000..819c04e --- /dev/null +++ b/Salmon.Service/Watchers/Base.cs @@ -0,0 +1,37 @@ +using Salmon.Core; +using System.Text.Json.Serialization; + +namespace Salmon.Service.Watchers; + +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "Type" + , UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FailSerialization) +] +[JsonDerivedType(typeof(Executable), typeDiscriminator: "executable")] +[JsonDerivedType(typeof(WebStatus), typeDiscriminator: "web")] +[JsonDerivedType(typeof(Docker), typeDiscriminator: "docker")] +public abstract class Base +{ + public string? UniqueId { get; set; } + public string? ShortName { get; set; } + public string? LongName { get; set; } + public string? Description { get; set; } + public string? ParentId { get; set; } = null; + + public abstract Task> ForgeElements(); + + public void ApplyField(Element e) + { + if(ShortName is not null) + e.ShortName = ShortName; + + if(LongName is not null) + e.LongName = LongName; + + if(Description is not null) + e.Description = Description; + + if(ParentId is not null) + e.ParentId = ParentId; + } +} \ No newline at end of file diff --git a/Salmon.Service/Watchers/Docker.cs b/Salmon.Service/Watchers/Docker.cs new file mode 100644 index 0000000..e00aa7a --- /dev/null +++ b/Salmon.Service/Watchers/Docker.cs @@ -0,0 +1,68 @@ +using Docker.DotNet; +using Docker.DotNet.Models; +using Salmon.Core; +using Salmon.Model.Monitor; + +namespace Salmon.Service.Watchers; + +public class Docker + : Base +{ + public string Uri { get; set; } + public string Name { get; set; } + public List Containers { get; set; } = new List(); + + public override async Task> ForgeElements() + { + List elements = new(); + + DockerClient client = new DockerClientConfiguration( + new Uri(Uri)) + .CreateClient(); + + var listedContainers = await client.Containers.ListContainersAsync(new global::Docker.DotNet.Models.ContainersListParameters + { + All = true + }); + foreach (var i in Containers) + elements.Add(i.FromResponses(Uri, listedContainers)); + + return elements; + } +} + +public class DockerContainer + : Base +{ + public string ContainerName { get; set; } + + public override Task> ForgeElements() + { + // Implement later with Single... Not very efficient. + throw new NotImplementedException(); + } + + public Element FromResponses(string uri, IList responses) + { + Container ret = new(UniqueId ?? Helper.CreateIdFrom(uri, ContainerName)) + { + Joinable = false + }; + + foreach (var r in responses) + if(r.Names.Contains(ContainerName)) + { + ret.Joinable = true; + ret.Running = r.State == "running"; + ret.ContainerId = r.ID; + ret.ContainerStatus = r.Status; + ret.Image = r.Image; + ret.ImageId = r.ImageID; + break; + } + + ApplyField(ret); + + return ret; + } +} \ No newline at end of file diff --git a/Salmon.Service/Watchers/Executable.cs b/Salmon.Service/Watchers/Executable.cs new file mode 100644 index 0000000..ae1931c --- /dev/null +++ b/Salmon.Service/Watchers/Executable.cs @@ -0,0 +1,21 @@ +using Salmon.Core; +using Salmon.Model.Monitor; + + +namespace Salmon.Service.Watchers; + +public class Executable + : Base +{ + public string Path { get; set; } + + public override async Task> ForgeElements() + { + List ret = new(); + + foreach (var el in Software.FromPath(Path)) + ret.Add(el); + + return ret; + } +} \ No newline at end of file diff --git a/Salmon.Service/Watchers/WebStatus.cs b/Salmon.Service/Watchers/WebStatus.cs new file mode 100644 index 0000000..6591d0a --- /dev/null +++ b/Salmon.Service/Watchers/WebStatus.cs @@ -0,0 +1,19 @@ +using Salmon.Core; + +namespace Salmon.Service.Watchers; + +public class WebStatus + : Base +{ + public string Method { get; set; } = "GET"; + public string Uri { get; set; } + + public override async Task> ForgeElements() + { + List elements = new(); + var ws = await Salmon.Model.Monitor.WebEndpoint.From(Uri, Method); + ApplyField(ws); + elements.Add(ws); + return elements; + } +} \ No newline at end of file diff --git a/Salmon.Web/Pages/ElementPage.razor b/Salmon.Web/Pages/ElementPage.razor index 2da0ab5..ea2ff30 100644 --- a/Salmon.Web/Pages/ElementPage.razor +++ b/Salmon.Web/Pages/ElementPage.razor @@ -2,9 +2,12 @@ @inject Salmon.Core.Instance Salmon; - - - +
+
+ UniqueId +
+ +
@if (Error is not null) {