From a4ed1a59565c4845d0bf62b9b033789d2d1d33f3 Mon Sep 17 00:00:00 2001 From: kkb Date: Wed, 10 Jan 2024 16:49:56 +0100 Subject: [PATCH] before server asp convertion --- insight.sln | 7 + src/Agent/Insight.Agent/Insight.Agent.csproj | 8 +- src/Agent/Insight.Agent/Internal/Config.cs | 6 + .../{Internals => Internal}/Extensions.cs | 0 .../{Internals => Internal}/Helpers.cs | 0 src/Agent/Insight.Agent/Models/Config.cs | 6 - .../Insight.Agent/Network/AgentSession.cs | 37 ++- .../Network/Handlers/AuthenticationHandler.cs | 43 --- .../Handlers/OperationSystemHandler.cs | 2 +- src/Agent/Insight.Agent/Program.cs | 5 +- .../Insight.Agent/Services/_Collector/_Os.cs | 2 +- .../Services/_Collector/_Session.cs | 2 +- .../Extensions/ServiceExtensions.cs | 2 +- src/Api/Insight.Api/Insight.Api.csproj | 2 +- src/Api/Insight.Api/Program.cs | 2 +- .../Insight.Api/appsettings.Development.json | 2 +- src/Api/Insight.Api/appsettings.json | 2 +- .../Extensions/Configuration.cs | 2 +- .../Insight.Domain}/Extensions/Linux.cs | 2 +- src/Core/Insight.Domain/Insight.Domain.csproj | 5 +- .../Extensions/ServiceExtensions.cs | 20 +- .../Insight.Infrastructure.Web.csproj | 7 +- .../Constants/Appsettings.cs | 63 +++- .../Extensions/ConfigurationExtensions.cs | 2 +- .../Extensions/ServiceExtensions.cs | 2 +- .../Insight.Infrastructure.csproj | 6 +- .../Services/AuthenticatorService.cs | 1 - .../Services/IdentityService.cs | 1 - .../Insight.Remote.Shared.csproj | 1 + .../Insight.Server/Constants/Appsettings.cs | 13 - .../Insight.Server/Insight.Server.csproj | 7 + .../Network/Agent/AgentSession.cs | 158 +++++++++- .../Network/Agent/Handlers/AgentHandler.cs | 155 --------- .../Network/Agent/Handlers/TrapHandler.cs | 4 +- .../Agent/Handlers/VirtualMaschineHandler.cs | 2 +- src/Server/Insight.Server/Program.cs | 16 +- .../Services/DispatchService.cs | 2 +- .../Insight.Server/Services/JobService.cs | 105 ------- .../appsettings.Development.json | 18 +- src/Server/Insight.Server/appsettings.json | 14 +- .../Controllers/WeatherForecastController.cs | 33 ++ .../Insight.Server2/Extensions/Async.cs | 52 +++ .../Insight.Server2/Insight.Server2.csproj | 41 +++ .../Insight.Server2/Insight.Server2.http | 6 + .../Insight.Server2/Models/MonitorMessage.cs | 26 ++ .../Network/Agent/AgentSession.cs | 206 ++++++++++++ .../Network/Agent/Handlers/CustomHandler.cs | 27 ++ .../Network/Agent/Handlers/DriveHandler.cs | 141 +++++++++ .../Network/Agent/Handlers/EventHandler.cs | 261 ++++++++++++++++ .../Agent/Handlers/InterfaceHandler.cs | 295 ++++++++++++++++++ .../Agent/Handlers/MainboardHandler.cs | 48 +++ .../Network/Agent/Handlers/MemoryHandler.cs | 79 +++++ .../Agent/Handlers/OperationSystemHandler.cs | 47 +++ .../Network/Agent/Handlers/PrinterHandler.cs | 72 +++++ .../Agent/Handlers/ProcessorHandler.cs | 83 +++++ .../Network/Agent/Handlers/ServiceHandler.cs | 77 +++++ .../Network/Agent/Handlers/SessionHandler.cs | 73 +++++ .../Network/Agent/Handlers/SoftwareHandler.cs | 74 +++++ .../Agent/Handlers/StoragePoolHandler.cs | 243 +++++++++++++++ .../Agent/Handlers/SystemInfoHandler.cs | 47 +++ .../Network/Agent/Handlers/TrapHandler.cs | 264 ++++++++++++++++ .../Network/Agent/Handlers/UpdateHandler.cs | 117 +++++++ .../Network/Agent/Handlers/UserHandler.cs | 183 +++++++++++ .../Agent/Handlers/VideocardHandler.cs | 73 +++++ .../Agent/Handlers/VirtualMaschineHandler.cs | 173 ++++++++++ .../Network/Remote/Handlers/RemoteHandler.cs | 104 ++++++ .../Network/Remote/RemoteSession.cs | 83 +++++ .../Network/Shared/ProxyHandler.cs | 86 +++++ .../Insight.Server2/Network/Web/WebSession.cs | 53 ++++ src/Server/Insight.Server2/Program.cs | 171 ++++++++++ .../Properties/launchSettings.json | 19 ++ .../Services/DispatchService.cs | 148 +++++++++ src/Server/Insight.Server2/WeatherForecast.cs | 13 + .../appsettings.Development.json | 30 ++ src/Server/Insight.Server2/appsettings.json | 30 ++ .../Extensions/ServiceExtensions.cs | 2 +- src/Web/Insight.Web/Insight.Web.csproj | 5 +- .../Hosts/Actions/Console/Index.razor.cs | 2 +- .../PhysicalDisks/Details.razor.cs | 2 +- src/Web/Insight.Web/Program.cs | 13 +- .../Insight.Web/appsettings.Development.json | 6 +- src/Web/Insight.Web/appsettings.json | 4 +- 82 files changed, 3802 insertions(+), 444 deletions(-) create mode 100644 src/Agent/Insight.Agent/Internal/Config.cs rename src/Agent/Insight.Agent/{Internals => Internal}/Extensions.cs (100%) rename src/Agent/Insight.Agent/{Internals => Internal}/Helpers.cs (100%) delete mode 100644 src/Agent/Insight.Agent/Models/Config.cs delete mode 100644 src/Agent/Insight.Agent/Network/Handlers/AuthenticationHandler.cs rename src/{Agent/Insight.Agent => Core/Insight.Domain}/Extensions/Configuration.cs (93%) rename src/{Agent/Insight.Agent => Core/Insight.Domain}/Extensions/Linux.cs (95%) rename src/{Server/Insight.Server => Core/Insight.Infrastructure}/Extensions/ConfigurationExtensions.cs (93%) delete mode 100644 src/Server/Insight.Server/Constants/Appsettings.cs delete mode 100644 src/Server/Insight.Server/Network/Agent/Handlers/AgentHandler.cs delete mode 100644 src/Server/Insight.Server/Services/JobService.cs create mode 100644 src/Server/Insight.Server2/Controllers/WeatherForecastController.cs create mode 100644 src/Server/Insight.Server2/Extensions/Async.cs create mode 100644 src/Server/Insight.Server2/Insight.Server2.csproj create mode 100644 src/Server/Insight.Server2/Insight.Server2.http create mode 100644 src/Server/Insight.Server2/Models/MonitorMessage.cs create mode 100644 src/Server/Insight.Server2/Network/Agent/AgentSession.cs create mode 100644 src/Server/Insight.Server2/Network/Agent/Handlers/CustomHandler.cs create mode 100644 src/Server/Insight.Server2/Network/Agent/Handlers/DriveHandler.cs create mode 100644 src/Server/Insight.Server2/Network/Agent/Handlers/EventHandler.cs create mode 100644 src/Server/Insight.Server2/Network/Agent/Handlers/InterfaceHandler.cs create mode 100644 src/Server/Insight.Server2/Network/Agent/Handlers/MainboardHandler.cs create mode 100644 src/Server/Insight.Server2/Network/Agent/Handlers/MemoryHandler.cs create mode 100644 src/Server/Insight.Server2/Network/Agent/Handlers/OperationSystemHandler.cs create mode 100644 src/Server/Insight.Server2/Network/Agent/Handlers/PrinterHandler.cs create mode 100644 src/Server/Insight.Server2/Network/Agent/Handlers/ProcessorHandler.cs create mode 100644 src/Server/Insight.Server2/Network/Agent/Handlers/ServiceHandler.cs create mode 100644 src/Server/Insight.Server2/Network/Agent/Handlers/SessionHandler.cs create mode 100644 src/Server/Insight.Server2/Network/Agent/Handlers/SoftwareHandler.cs create mode 100644 src/Server/Insight.Server2/Network/Agent/Handlers/StoragePoolHandler.cs create mode 100644 src/Server/Insight.Server2/Network/Agent/Handlers/SystemInfoHandler.cs create mode 100644 src/Server/Insight.Server2/Network/Agent/Handlers/TrapHandler.cs create mode 100644 src/Server/Insight.Server2/Network/Agent/Handlers/UpdateHandler.cs create mode 100644 src/Server/Insight.Server2/Network/Agent/Handlers/UserHandler.cs create mode 100644 src/Server/Insight.Server2/Network/Agent/Handlers/VideocardHandler.cs create mode 100644 src/Server/Insight.Server2/Network/Agent/Handlers/VirtualMaschineHandler.cs create mode 100644 src/Server/Insight.Server2/Network/Remote/Handlers/RemoteHandler.cs create mode 100644 src/Server/Insight.Server2/Network/Remote/RemoteSession.cs create mode 100644 src/Server/Insight.Server2/Network/Shared/ProxyHandler.cs create mode 100644 src/Server/Insight.Server2/Network/Web/WebSession.cs create mode 100644 src/Server/Insight.Server2/Program.cs create mode 100644 src/Server/Insight.Server2/Properties/launchSettings.json create mode 100644 src/Server/Insight.Server2/Services/DispatchService.cs create mode 100644 src/Server/Insight.Server2/WeatherForecast.cs create mode 100644 src/Server/Insight.Server2/appsettings.Development.json create mode 100644 src/Server/Insight.Server2/appsettings.json diff --git a/insight.sln b/insight.sln index 4cde53e..d27bbe4 100644 --- a/insight.sln +++ b/insight.sln @@ -41,6 +41,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Insight.Remote.Windows", "s EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Insight.Infrastructure.Web", "src\Core\Insight.Infrastructure.Web\Insight.Infrastructure.Web.csproj", "{39B81A0D-A88C-44D3-9624-1A19C78A4310}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Insight.Server2", "src\Server\Insight.Server2\Insight.Server2.csproj", "{9F7E88B2-7415-410C-9C31-7720596B0607}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -91,6 +93,10 @@ Global {39B81A0D-A88C-44D3-9624-1A19C78A4310}.Debug|Any CPU.Build.0 = Debug|Any CPU {39B81A0D-A88C-44D3-9624-1A19C78A4310}.Release|Any CPU.ActiveCfg = Release|Any CPU {39B81A0D-A88C-44D3-9624-1A19C78A4310}.Release|Any CPU.Build.0 = Release|Any CPU + {9F7E88B2-7415-410C-9C31-7720596B0607}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9F7E88B2-7415-410C-9C31-7720596B0607}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9F7E88B2-7415-410C-9C31-7720596B0607}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9F7E88B2-7415-410C-9C31-7720596B0607}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -107,6 +113,7 @@ Global {5C4697BD-BC97-484F-9DB1-CA87E2BEAA4B} = {D4D7BF4A-B2E3-470A-A14C-FC658FF7461D} {AF313B47-3079-407F-91D1-9989C1E1AF2A} = {D4D7BF4A-B2E3-470A-A14C-FC658FF7461D} {39B81A0D-A88C-44D3-9624-1A19C78A4310} = {88B03853-2215-4E52-8986-0E76602E5F21} + {9F7E88B2-7415-410C-9C31-7720596B0607} = {038C3821-E554-496D-B585-A3BC193B7913} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {F376A326-7590-4E7E-AB9B-76CED8527AB0} diff --git a/src/Agent/Insight.Agent/Insight.Agent.csproj b/src/Agent/Insight.Agent/Insight.Agent.csproj index 2dc4de2..27e5746 100644 --- a/src/Agent/Insight.Agent/Insight.Agent.csproj +++ b/src/Agent/Insight.Agent/Insight.Agent.csproj @@ -13,11 +13,6 @@ none - - - - - none @@ -27,10 +22,9 @@ - - + diff --git a/src/Agent/Insight.Agent/Internal/Config.cs b/src/Agent/Insight.Agent/Internal/Config.cs new file mode 100644 index 0000000..f79bdff --- /dev/null +++ b/src/Agent/Insight.Agent/Internal/Config.cs @@ -0,0 +1,6 @@ +namespace Insight.Agent; + +internal sealed class Config +{ + public Guid? Serial { get; set; } +} \ No newline at end of file diff --git a/src/Agent/Insight.Agent/Internals/Extensions.cs b/src/Agent/Insight.Agent/Internal/Extensions.cs similarity index 100% rename from src/Agent/Insight.Agent/Internals/Extensions.cs rename to src/Agent/Insight.Agent/Internal/Extensions.cs diff --git a/src/Agent/Insight.Agent/Internals/Helpers.cs b/src/Agent/Insight.Agent/Internal/Helpers.cs similarity index 100% rename from src/Agent/Insight.Agent/Internals/Helpers.cs rename to src/Agent/Insight.Agent/Internal/Helpers.cs diff --git a/src/Agent/Insight.Agent/Models/Config.cs b/src/Agent/Insight.Agent/Models/Config.cs deleted file mode 100644 index 4fdd2ae..0000000 --- a/src/Agent/Insight.Agent/Models/Config.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Insight.Agent.Models; - -public class Config -{ - public Guid? Serial { get; set; } -} \ No newline at end of file diff --git a/src/Agent/Insight.Agent/Network/AgentSession.cs b/src/Agent/Insight.Agent/Network/AgentSession.cs index 8b23245..7e6ed26 100644 --- a/src/Agent/Insight.Agent/Network/AgentSession.cs +++ b/src/Agent/Insight.Agent/Network/AgentSession.cs @@ -1,5 +1,8 @@ -using Insight.Domain.Interfaces; +using Insight.Agent.Services; +using Insight.Domain.Constants; +using Insight.Domain.Interfaces; using Insight.Domain.Network; +using Insight.Domain.Network.Agent.Messages; using Microsoft.Extensions.Logging; using Vaitr.Network; @@ -31,6 +34,11 @@ public class AgentSession(IEnumerable> handlers, I { await base.OnReceivedAsync(context, cancellationToken); + // catch authentication request + if (context.Packet is AuthenticationRequest) + await OnAuthenticationAsync(cancellationToken); + + // pass message to handlers foreach (var handler in _handlers) { try @@ -49,4 +57,31 @@ public class AgentSession(IEnumerable> handlers, I _logger.LogInformation("Agent ({ep?}) Heartbeat", RemoteEndPoint); return default; } + + private async ValueTask OnAuthenticationAsync(CancellationToken cancellationToken) + { + Config? config = null; + + try + { + config = await Configurator.ReadAsync(Configuration.DefaultConfig, cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.LogError("Config ({config}) read error ({exception})", Configuration.DefaultConfig, ex); + } + + if (config is null) + { + config = new Config { Serial = Guid.NewGuid() }; + await Configurator.WriteAsync(config, Configuration.DefaultConfig, cancellationToken).ConfigureAwait(false); + } + + await SendAsync(new AuthenticationResponse + { + Serial = config.Serial ?? throw new InvalidDataException(nameof(config.Serial)), + Version = Configuration.Version, + Hostname = Configuration.Hostname + }, cancellationToken).ConfigureAwait(false); + } } \ No newline at end of file diff --git a/src/Agent/Insight.Agent/Network/Handlers/AuthenticationHandler.cs b/src/Agent/Insight.Agent/Network/Handlers/AuthenticationHandler.cs deleted file mode 100644 index cc71964..0000000 --- a/src/Agent/Insight.Agent/Network/Handlers/AuthenticationHandler.cs +++ /dev/null @@ -1,43 +0,0 @@ -using Insight.Agent.Models; -using Insight.Agent.Services; -using Insight.Domain.Constants; -using Insight.Domain.Interfaces; -using Insight.Domain.Network; -using Insight.Domain.Network.Agent.Messages; - -namespace Insight.Agent.Network.Handlers; - -public class AuthenticationHandler : IMessageHandler -{ - public async ValueTask HandleAsync(AgentSession sender, TMessage message, CancellationToken cancellationToken) where TMessage : IMessage - { - switch (message) - { - case AuthenticationRequest: - { - Config? config = null; - - try - { - config = await Configurator.ReadAsync(Configuration.DefaultConfig, cancellationToken).ConfigureAwait(false); - } - catch (Exception) { } - - if (config is null) - { - config = new Config { Serial = Guid.NewGuid() }; - await Configurator.WriteAsync(config, Configuration.DefaultConfig, cancellationToken).ConfigureAwait(false); - } - - await sender.SendAsync(new AuthenticationResponse - { - Serial = config.Serial ?? throw new InvalidDataException(nameof(config.Serial)), - Version = Configuration.Version, - Hostname = Configuration.Hostname - }, cancellationToken); - - break; - } - } - } -} \ No newline at end of file diff --git a/src/Agent/Insight.Agent/Network/Handlers/OperationSystemHandler.cs b/src/Agent/Insight.Agent/Network/Handlers/OperationSystemHandler.cs index ed62a70..3f3dabe 100644 --- a/src/Agent/Insight.Agent/Network/Handlers/OperationSystemHandler.cs +++ b/src/Agent/Insight.Agent/Network/Handlers/OperationSystemHandler.cs @@ -51,7 +51,7 @@ public class OperationSystemHandler : IMessageHandler if (@object.TryGetValue(properties, "osarchitecture", out var architecture)) { - if (architecture is not null && architecture.Contains("64", StringComparison.CurrentCultureIgnoreCase)) + if (architecture is not null && architecture.Contains("64", StringComparison.CurrentCultureIgnoreCase)) os.Architecture = Architecture.X64; } else diff --git a/src/Agent/Insight.Agent/Program.cs b/src/Agent/Insight.Agent/Program.cs index 3b07be9..3d85d84 100644 --- a/src/Agent/Insight.Agent/Program.cs +++ b/src/Agent/Insight.Agent/Program.cs @@ -1,8 +1,8 @@ -using Insight.Agent.Extensions; -using Insight.Agent.Network; +using Insight.Agent.Network; using Insight.Agent.Network.Handlers; using Insight.Agent.Services; using Insight.Domain.Constants; +using Insight.Domain.Extensions; using Insight.Domain.Interfaces; using Insight.Domain.Network; using Microsoft.Extensions.Configuration; @@ -67,7 +67,6 @@ internal class Program options.UseSerializer>(); }); - services.AddSingleton, AuthenticationHandler>(); services.AddSingleton, ProxyHandler>(); services.AddSingleton, CustomHandler>(); diff --git a/src/Agent/Insight.Agent/Services/_Collector/_Os.cs b/src/Agent/Insight.Agent/Services/_Collector/_Os.cs index b74254d..d1d2daa 100644 --- a/src/Agent/Insight.Agent/Services/_Collector/_Os.cs +++ b/src/Agent/Insight.Agent/Services/_Collector/_Os.cs @@ -1,4 +1,4 @@ -using Insight.Agent.Extensions; +using Insight.Domain.Extensions; using Insight.Domain.Network.Agent.Messages; using Microsoft.Extensions.Logging; using System.Text.RegularExpressions; diff --git a/src/Agent/Insight.Agent/Services/_Collector/_Session.cs b/src/Agent/Insight.Agent/Services/_Collector/_Session.cs index a393ee1..5b98531 100644 --- a/src/Agent/Insight.Agent/Services/_Collector/_Session.cs +++ b/src/Agent/Insight.Agent/Services/_Collector/_Session.cs @@ -1,4 +1,4 @@ -using Insight.Agent.Extensions; +using Insight.Domain.Extensions; using Insight.Domain.Network.Agent.Messages; using Microsoft.Extensions.Logging; using System.Text.RegularExpressions; diff --git a/src/Api/Insight.Api/Extensions/ServiceExtensions.cs b/src/Api/Insight.Api/Extensions/ServiceExtensions.cs index 51342ce..40098f1 100644 --- a/src/Api/Insight.Api/Extensions/ServiceExtensions.cs +++ b/src/Api/Insight.Api/Extensions/ServiceExtensions.cs @@ -2,7 +2,7 @@ using Microsoft.OpenApi.Models; using System.Reflection; -namespace Insight.Api.Hosting; +namespace Insight.Api.Extensions; public static class ServiceExtensions { diff --git a/src/Api/Insight.Api/Insight.Api.csproj b/src/Api/Insight.Api/Insight.Api.csproj index e67d2bf..89f1bc9 100644 --- a/src/Api/Insight.Api/Insight.Api.csproj +++ b/src/Api/Insight.Api/Insight.Api.csproj @@ -22,7 +22,7 @@ - + diff --git a/src/Api/Insight.Api/Program.cs b/src/Api/Insight.Api/Program.cs index d9dea66..66b1d11 100644 --- a/src/Api/Insight.Api/Program.cs +++ b/src/Api/Insight.Api/Program.cs @@ -1,4 +1,4 @@ -using Insight.Api.Hosting; +using Insight.Api.Extensions; using Insight.Domain.Constants; using Insight.Infrastructure; using Microsoft.Extensions.FileProviders; diff --git a/src/Api/Insight.Api/appsettings.Development.json b/src/Api/Insight.Api/appsettings.Development.json index fff92f2..9bac897 100644 --- a/src/Api/Insight.Api/appsettings.Development.json +++ b/src/Api/Insight.Api/appsettings.Development.json @@ -1,7 +1,7 @@ { "AllowedHosts": "*", "Urls": "http://127.0.0.1:5000", - "database": "mongodb://db.insight.local:27017", + "mongo.connection": "mongodb://db.insight.local:27017", "jwt.key": "x5dcaE8fiBmHfgsNrwIEtSWzZkz6gpouzKOIgEiVjxJnW28V1aUnYXF19IcnF5x", "jwt.exp": 3600, "jwt.audience": "http://127.0.0.1:5000", diff --git a/src/Api/Insight.Api/appsettings.json b/src/Api/Insight.Api/appsettings.json index 21706b1..6b62cc2 100644 --- a/src/Api/Insight.Api/appsettings.json +++ b/src/Api/Insight.Api/appsettings.json @@ -1,7 +1,7 @@ { "AllowedHosts": "*", "Urls": "http://127.0.0.1:5000", - "database": "mongodb://127.0.0.1:27017", + "mongo.connection": "mongodb://127.0.0.1:27017", "jwt.key": "x5dcaE8fiBmHfgsNrwIEtSWzZkz6gpouzKOIgEiVjxJnW28V1aUnYXF19IcnF5x", "jwt.exp": 3600, "jwt.audience": "https://insight.webmatic.de/api", diff --git a/src/Agent/Insight.Agent/Extensions/Configuration.cs b/src/Core/Insight.Domain/Extensions/Configuration.cs similarity index 93% rename from src/Agent/Insight.Agent/Extensions/Configuration.cs rename to src/Core/Insight.Domain/Extensions/Configuration.cs index 8646d47..592de33 100644 --- a/src/Agent/Insight.Agent/Extensions/Configuration.cs +++ b/src/Core/Insight.Domain/Extensions/Configuration.cs @@ -1,6 +1,6 @@ using Microsoft.Extensions.Configuration; -namespace Insight.Agent.Extensions; +namespace Insight.Domain.Extensions; public static class ConfigurationExtensions { diff --git a/src/Agent/Insight.Agent/Extensions/Linux.cs b/src/Core/Insight.Domain/Extensions/Linux.cs similarity index 95% rename from src/Agent/Insight.Agent/Extensions/Linux.cs rename to src/Core/Insight.Domain/Extensions/Linux.cs index 0574954..0cd076b 100644 --- a/src/Agent/Insight.Agent/Extensions/Linux.cs +++ b/src/Core/Insight.Domain/Extensions/Linux.cs @@ -1,7 +1,7 @@ using System.Diagnostics; using System.Runtime.Versioning; -namespace Insight.Agent.Extensions; +namespace Insight.Domain.Extensions; public static class Linux { diff --git a/src/Core/Insight.Domain/Insight.Domain.csproj b/src/Core/Insight.Domain/Insight.Domain.csproj index 31bc155..b2e0753 100644 --- a/src/Core/Insight.Domain/Insight.Domain.csproj +++ b/src/Core/Insight.Domain/Insight.Domain.csproj @@ -17,9 +17,8 @@ - - - + + \ No newline at end of file diff --git a/src/Core/Insight.Infrastructure.Web/Extensions/ServiceExtensions.cs b/src/Core/Insight.Infrastructure.Web/Extensions/ServiceExtensions.cs index ad9100b..1dda883 100644 --- a/src/Core/Insight.Infrastructure.Web/Extensions/ServiceExtensions.cs +++ b/src/Core/Insight.Infrastructure.Web/Extensions/ServiceExtensions.cs @@ -1,6 +1,4 @@ -using AspNetCore.Identity.MongoDbCore.Extensions; -using AspNetCore.Identity.MongoDbCore.Infrastructure; -using Insight.Infrastructure.Entities; +using Insight.Infrastructure.Entities; using Insight.Infrastructure.Services; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Builder; @@ -21,10 +19,10 @@ public static partial class ServiceExtensions public static IServiceCollection AddTokenServices(this IServiceCollection services, IConfiguration configuration) { var options = new Models.TokenOptions( - key: configuration.GetValue(Appsettings.JwtKey) ?? throw new Exception($"{Appsettings.JwtKey} value not set (appsettings)"), - expires: configuration.GetValue(Appsettings.JwtExp) ?? throw new Exception($"{Appsettings.JwtExp} value not set (appsettings)"), - audience: configuration.GetValue(Appsettings.JwtAudience) ?? throw new Exception($"{Appsettings.JwtAudience} value not set (appsettings)"), - issuer: configuration.GetValue(Appsettings.JwtIssuer) ?? throw new Exception($"{Appsettings.JwtIssuer} value not set (appsettings)")); + key: configuration.GetValue(Appsettings.Jwt.Key) ?? throw new Exception($"{Appsettings.Jwt.Key} value not set (appsettings)"), + expires: configuration.GetValue(Appsettings.Jwt.Exp) ?? throw new Exception($"{Appsettings.Jwt.Exp} value not set (appsettings)"), + audience: configuration.GetValue(Appsettings.Jwt.Audience) ?? throw new Exception($"{Appsettings.Jwt.Audience} value not set (appsettings)"), + issuer: configuration.GetValue(Appsettings.Jwt.Issuer) ?? throw new Exception($"{Appsettings.Jwt.Issuer} value not set (appsettings)")); services.AddSingleton(options); services.AddTransient(); @@ -56,7 +54,7 @@ public static partial class ServiceExtensions public static IServiceCollection AddIdentityServices(this IServiceCollection services, IConfiguration configuration) { - var connectionString = configuration.GetValue(Appsettings.Database) ?? throw new Exception($"{Appsettings.Database} value not set (appsettings)"); + var connectionString = configuration.GetValue(Appsettings.Mongo.ConnectionString) ?? throw new Exception($"{Appsettings.Mongo.ConnectionString} value not set (appsettings)"); services.AddIdentity(options => { @@ -146,14 +144,14 @@ public static partial class ServiceExtensions options.TokenValidationParameters.ValidateActor = false; - options.TokenValidationParameters.ValidAudience = configuration.GetValue(Appsettings.JwtAudience) ?? throw new Exception($"{Appsettings.JwtAudience} value not set (appsettings)"); + options.TokenValidationParameters.ValidAudience = configuration.GetValue(Appsettings.Jwt.Audience) ?? throw new Exception($"{Appsettings.Jwt.Audience} value not set (appsettings)"); options.TokenValidationParameters.ValidateAudience = true; - options.TokenValidationParameters.ValidIssuer = configuration.GetValue(Appsettings.JwtIssuer) ?? throw new Exception($"{Appsettings.JwtIssuer} value not set (appsettings)"); + options.TokenValidationParameters.ValidIssuer = configuration.GetValue(Appsettings.Jwt.Issuer) ?? throw new Exception($"{Appsettings.Jwt.Issuer} value not set (appsettings)"); options.TokenValidationParameters.ValidateIssuer = true; options.TokenValidationParameters.IssuerSigningKey = new SymmetricSecurityKey( - Encoding.UTF8.GetBytes(configuration.GetValue(Appsettings.JwtKey) ?? throw new Exception($"{Appsettings.JwtKey} value not set (appsettings)")) + Encoding.UTF8.GetBytes(configuration.GetValue(Appsettings.Jwt.Key) ?? throw new Exception($"{Appsettings.Jwt.Key} value not set (appsettings)")) ); options.TokenValidationParameters.ValidateIssuerSigningKey = true; diff --git a/src/Core/Insight.Infrastructure.Web/Insight.Infrastructure.Web.csproj b/src/Core/Insight.Infrastructure.Web/Insight.Infrastructure.Web.csproj index bc64ae5..903db67 100644 --- a/src/Core/Insight.Infrastructure.Web/Insight.Infrastructure.Web.csproj +++ b/src/Core/Insight.Infrastructure.Web/Insight.Infrastructure.Web.csproj @@ -12,13 +12,12 @@ - - - - + + + diff --git a/src/Core/Insight.Infrastructure/Constants/Appsettings.cs b/src/Core/Insight.Infrastructure/Constants/Appsettings.cs index e1adf44..df66859 100644 --- a/src/Core/Insight.Infrastructure/Constants/Appsettings.cs +++ b/src/Core/Insight.Infrastructure/Constants/Appsettings.cs @@ -1,17 +1,58 @@ namespace Insight.Infrastructure; -public class Appsettings +public static class Appsettings { - public const string Database = "database"; - public const string JwtKey = "jwt.key"; - public const string JwtAudience = "jwt.audience"; - public const string JwtIssuer = "jwt.issuer"; - public const string JwtExp = "jwt.exp"; + public static class Mongo + { + public const string ConnectionString = "mongo.connection"; + } - public const string ServerHost = "server.host"; - public const string ServerPort = "server.port"; + public static class Influx + { + public const string Endpoint = "influx.endpoint"; + public const string Token = "influx.token"; + public const string Organization = "influx.org"; + public const string Bucket = "influx.bucket"; + public const string Service = "influx.service"; + } - public const string RemoteServerPort = "remote.port"; - public const string RemoteServerCertificate = "remote.certificate"; - public const string RemoteServerCertificatePassword = "remote.certificate.password"; + public static class Jwt + { + public const string Key = "jwt.key"; + public const string Audience = "jwt.audience"; + public const string Issuer = "jwt.issuer"; + public const string Exp = "jwt.exp"; + } + + public static class Backend + { + public const string Host = "server.host"; + public const string Port = "server.port"; + } + + public static class Agent + { + public const string Port = "agent.port"; + public const string Certificate = "agent.certificate"; + public const string CertificatePassword = "agent.certificate.password"; + } + + public static class Web + { + public const string Port = "web.port"; + public const string Certificate = "web.certificate"; + public const string CertificatePassword = "web.certificate.password"; + } + + public static class Remote + { + public const string Port = "remote.port"; + public const string Certificate = "remote.certificate"; + public const string CertificatePassword = "remote.certificate.password"; + } + + public static class Dispatch + { + public const string Webmatic = "dispatch.webmatic"; + } } \ No newline at end of file diff --git a/src/Server/Insight.Server/Extensions/ConfigurationExtensions.cs b/src/Core/Insight.Infrastructure/Extensions/ConfigurationExtensions.cs similarity index 93% rename from src/Server/Insight.Server/Extensions/ConfigurationExtensions.cs rename to src/Core/Insight.Infrastructure/Extensions/ConfigurationExtensions.cs index 3bc0aa5..b347b0d 100644 --- a/src/Server/Insight.Server/Extensions/ConfigurationExtensions.cs +++ b/src/Core/Insight.Infrastructure/Extensions/ConfigurationExtensions.cs @@ -1,6 +1,6 @@ using Microsoft.Extensions.Configuration; -namespace Insight.Server.Extensions; +namespace Insight.Infrastructure; public static class ConfigurationExtensions { diff --git a/src/Core/Insight.Infrastructure/Extensions/ServiceExtensions.cs b/src/Core/Insight.Infrastructure/Extensions/ServiceExtensions.cs index 5f62c94..34ae81d 100644 --- a/src/Core/Insight.Infrastructure/Extensions/ServiceExtensions.cs +++ b/src/Core/Insight.Infrastructure/Extensions/ServiceExtensions.cs @@ -11,7 +11,7 @@ public static partial class ServiceExtensions { public static IServiceCollection AddDatabase(this IServiceCollection services, IConfiguration configuration, ILoggerFactory? loggerFactory = null) { - var connectionString = configuration.GetValue(Appsettings.Database) ?? throw new Exception($"{Appsettings.Database} value not set (appsettings)"); + var connectionString = configuration.GetValue(Appsettings.Mongo.ConnectionString) ?? throw new Exception($"{Appsettings.Mongo.ConnectionString} value not set (appsettings)"); var settings = MongoClientSettings.FromUrl(new MongoUrl(connectionString)); settings.ConnectTimeout = TimeSpan.FromSeconds(3); diff --git a/src/Core/Insight.Infrastructure/Insight.Infrastructure.csproj b/src/Core/Insight.Infrastructure/Insight.Infrastructure.csproj index 79e2a5e..bd92b41 100644 --- a/src/Core/Insight.Infrastructure/Insight.Infrastructure.csproj +++ b/src/Core/Insight.Infrastructure/Insight.Infrastructure.csproj @@ -12,14 +12,10 @@ - + - - - - diff --git a/src/Core/Insight.Infrastructure/Services/AuthenticatorService.cs b/src/Core/Insight.Infrastructure/Services/AuthenticatorService.cs index 13a40b6..db04a99 100644 --- a/src/Core/Insight.Infrastructure/Services/AuthenticatorService.cs +++ b/src/Core/Insight.Infrastructure/Services/AuthenticatorService.cs @@ -1,6 +1,5 @@ using Insight.Infrastructure.Entities; using Microsoft.AspNetCore.Identity; -using MongoDB.Driver; using System.Globalization; using System.Text; using System.Text.Encodings.Web; diff --git a/src/Core/Insight.Infrastructure/Services/IdentityService.cs b/src/Core/Insight.Infrastructure/Services/IdentityService.cs index 90d935a..80d9e98 100644 --- a/src/Core/Insight.Infrastructure/Services/IdentityService.cs +++ b/src/Core/Insight.Infrastructure/Services/IdentityService.cs @@ -1,6 +1,5 @@ using Insight.Infrastructure.Entities; using Microsoft.AspNetCore.Identity; -using Microsoft.Extensions.Logging; using System.Security.Claims; namespace Insight.Infrastructure.Services; diff --git a/src/Remote/Insight.Remote.Shared/Insight.Remote.Shared.csproj b/src/Remote/Insight.Remote.Shared/Insight.Remote.Shared.csproj index 3a81496..7cb0df8 100644 --- a/src/Remote/Insight.Remote.Shared/Insight.Remote.Shared.csproj +++ b/src/Remote/Insight.Remote.Shared/Insight.Remote.Shared.csproj @@ -18,6 +18,7 @@ + diff --git a/src/Server/Insight.Server/Constants/Appsettings.cs b/src/Server/Insight.Server/Constants/Appsettings.cs deleted file mode 100644 index 07a59f5..0000000 --- a/src/Server/Insight.Server/Constants/Appsettings.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Insight.Server; - -internal static class Appsettings -{ - internal const string AgentServerPort = "agent.server.port"; - internal const string AgentServerCertificate = "agent.server.certificate"; - internal const string AgentServerCertificatePassword = "agent.server.certificate.password"; - internal const string DispatchWebmatic = "dispatch.webmatic"; - - internal const string WebServerPort = "web.server.port"; - internal const string WebServerCertificate = "web.server.certificate"; - internal const string WebServerCertificatePassword = "web.server.certificate.password"; -} \ No newline at end of file diff --git a/src/Server/Insight.Server/Insight.Server.csproj b/src/Server/Insight.Server/Insight.Server.csproj index 92d5d5e..41f9fe3 100644 --- a/src/Server/Insight.Server/Insight.Server.csproj +++ b/src/Server/Insight.Server/Insight.Server.csproj @@ -43,10 +43,17 @@ + + + + + + + diff --git a/src/Server/Insight.Server/Network/Agent/AgentSession.cs b/src/Server/Insight.Server/Network/Agent/AgentSession.cs index 29ac2cf..91744ca 100644 --- a/src/Server/Insight.Server/Network/Agent/AgentSession.cs +++ b/src/Server/Insight.Server/Network/Agent/AgentSession.cs @@ -1,63 +1,95 @@ -using Insight.Domain.Interfaces; +using Insight.Domain.Enums; +using Insight.Domain.Interfaces; using Insight.Domain.Network; using Insight.Domain.Network.Agent.Messages; -using Insight.Server.Network.Agent.Handlers; +using Insight.Infrastructure.Entities; using Microsoft.Extensions.Logging; +using MongoDB.Driver; using Vaitr.Network; namespace Insight.Server.Network.Agent; public class AgentSession( - AgentHandler agentHandler, + IMongoDatabase database, IEnumerable> handlers, ISerializer serializer, ILogger logger) : TcpSession(serializer, logger) { public string? Id { get; set; } - private readonly AgentHandler _agentHandler = agentHandler; + private readonly IMongoDatabase _database = database; private readonly IEnumerable> _handlers = handlers; protected override async ValueTask OnConnectedAsync(CancellationToken cancellationToken) { _logger.LogInformation("Agent ({ep?}) connected", RemoteEndPoint); - var request = new AuthenticationRequest(); + // auth request + await SendAsync(new AuthenticationRequest(), cancellationToken); - foreach (var handler in _handlers) + // wait for ack + for (int i = 0; i < 200; i++) { - await handler.HandleAsync(this, request, cancellationToken); + if (Id is not null) + break; + + await Task.Delay(50, cancellationToken).ConfigureAwait(false); } - await _agentHandler.ConnectedAsync(this, default); - await _agentHandler.StatisticUpdateAsync(this, default); + // if ack not received + if (Id is null) + { + _logger.LogError("Agent ({ep?}) authentication timeout", RemoteEndPoint); + Disconnect(); + return; + } - _logger.LogInformation("Agent ({ep?}) ID: {id}", RemoteEndPoint, Id); + _logger.LogInformation("Agent ({ep?} / {id}) authenticated", RemoteEndPoint, Id); + + // insert log + await WriteLogAsync(CategoryEnum.Network, StatusEnum.Information, $"Connected ({RemoteEndPoint})", default); + + // update entity + await UpdateAsync(default); + + // init inventory task + _ = InitInventoryAsync(cancellationToken); } protected override async ValueTask OnDisconnectedAsync(CancellationToken cancellationToken) { _logger.LogInformation("Agent ({ep?}) disconnected", RemoteEndPoint); - await _agentHandler.StatisticUpdateAsync(this, default); - await _agentHandler.DisconnectedAsync(this, default); + // insert log + await WriteLogAsync(CategoryEnum.Network, StatusEnum.Information, $"Disconnected ({RemoteEndPoint})", default); + + // update entity + await UpdateAsync(default); } protected override async ValueTask OnSentAsync(IPacketContext context, CancellationToken cancellationToken) { await base.OnSentAsync(context, cancellationToken); - await _agentHandler.StatisticUpdateAsync(this, cancellationToken); + // update entity + await UpdateAsync(cancellationToken); } protected override async ValueTask OnReceivedAsync(IPacketContext context, CancellationToken cancellationToken) { await base.OnReceivedAsync(context, cancellationToken); - if (Id is null && context.Packet is not AuthenticationResponse) return; + // only accept auth response if not authenticated + if (Id is null) + { + if (context.Packet is not AuthenticationResponse authentication) return; + await OnAuthenticationAsync(authentication, cancellationToken); + } - await _agentHandler.StatisticUpdateAsync(this, cancellationToken); + // update entity + await UpdateAsync(cancellationToken); + // pass message to handlers foreach (var handler in _handlers) { try @@ -75,6 +107,100 @@ public class AgentSession( { _logger.LogInformation("Agent ({ep?}) Heartbeat", RemoteEndPoint); - await _agentHandler.StatisticUpdateAsync(this, cancellationToken); + // update entity + await UpdateAsync(cancellationToken); + } + + private async ValueTask OnAuthenticationAsync(AuthenticationResponse authentication, CancellationToken cancellationToken) + { + // check serial + if (authentication.Serial == default) + throw new InvalidDataException($"authentication failed ({nameof(authentication.Serial)})"); + + // check version + //if (authentication.Version == default || authentication.Version < Domain.Constants.Configuration.Version) + // throw new InvalidDataException($"authentication failed ({nameof(authentication.Version)})"); + + // upsert agent + await _database.Agent() + .UpdateOneAsync(Builders + .Filter + .Eq(p => p.Serial, authentication.Serial.ToString()), Builders.Update + .SetOnInsert(p => p.Insert, DateTime.Now) + .SetOnInsert(p => p.Serial, authentication.Serial.ToString()) + .Set(p => p.Update, DateTime.Now) + .Set(p => p.Connected, DateTime.Now) + .Set(p => p.Version, authentication.Version) + .Set(p => p.Endpoint, RemoteEndPoint?.ToString()) + .Set(p => p.Hostname, authentication.Hostname), new UpdateOptions + { + IsUpsert = true + }, cancellationToken) + .ConfigureAwait(false); + + // get agent + var agentEntity = await _database.Agent() + .Find(Builders + .Filter + .Eq(p => p.Serial, authentication.Serial.ToString())) + .FirstOrDefaultAsync(cancellationToken) + .ConfigureAwait(false); + + // set session id + Id = agentEntity.Id; + } + + private async ValueTask WriteLogAsync(CategoryEnum category, StatusEnum status, string message, CancellationToken cancellationToken) + { + await _database.AgentLog() + .InsertOneAsync(new AgentLogEntity + { + Insert = DateTime.Now, + Agent = Id, + Category = category.ToString(), + Status = status.ToString(), + Message = message, + Timestamp = DateTime.Now + }, cancellationToken: cancellationToken) + .ConfigureAwait(false); + } + + private async ValueTask UpdateAsync(CancellationToken cancellationToken) + { + await _database.Agent().UpdateOneAsync(Builders + .Filter + .Eq(p => p.Id, Id), Builders + .Update + .Set(p => p.Update, DateTime.Now) + .Set(p => p.Activity, Activity) + .Set(p => p.SentBytes, SentBytes) + .Set(p => p.SentPackets, SentPackets) + .Set(p => p.ReceivedBytes, ReceivedBytes) + .Set(p => p.ReceivedPackets, ReceivedPackets), null, cancellationToken) + .ConfigureAwait(false); + } + + private async Task InitInventoryAsync(CancellationToken cancellationToken) + { + while (cancellationToken.IsCancellationRequested is false) + { + _logger.LogWarning("try get inventory"); + + // find assigned host + var host = await _database.Host() + .CountDocumentsAsync(Builders.Filter.Eq(p => p.Agent, Id), cancellationToken: cancellationToken) + .ConfigureAwait(false); + + // if not assigned => short delay + if (host == 0) + { + await Task.Delay(TimeSpan.FromMinutes(1), cancellationToken); + continue; + } + + // send request + await SendAsync(new InventoryRequest(), cancellationToken); + await Task.Delay(TimeSpan.FromHours(1), cancellationToken); + } } } \ No newline at end of file diff --git a/src/Server/Insight.Server/Network/Agent/Handlers/AgentHandler.cs b/src/Server/Insight.Server/Network/Agent/Handlers/AgentHandler.cs deleted file mode 100644 index 73ab7d9..0000000 --- a/src/Server/Insight.Server/Network/Agent/Handlers/AgentHandler.cs +++ /dev/null @@ -1,155 +0,0 @@ -using Insight.Domain.Enums; -using Insight.Domain.Interfaces; -using Insight.Domain.Network; -using Insight.Domain.Network.Agent.Messages; -using Insight.Infrastructure.Entities; -using Microsoft.Extensions.Logging; -using MongoDB.Driver; - -namespace Insight.Server.Network.Agent.Handlers; - -public class AgentHandler(IMongoDatabase database, ILogger logger) : IMessageHandler -{ - private readonly IMongoDatabase _database = database; - private readonly ILogger _logger = logger; - - public async ValueTask HandleAsync(AgentSession sender, TMessage message, CancellationToken cancellationToken) where TMessage : IMessage - { - switch (message) - { - case AuthenticationRequest authenticationRequest: - await OnAuthenticationRequestAsync(sender, authenticationRequest, cancellationToken); - break; - case AuthenticationResponse authenticationResponse: - await OnAuthenticationResponseAsync(sender, authenticationResponse, cancellationToken); - break; - } - } - - private async ValueTask OnAuthenticationRequestAsync(AgentSession session, AuthenticationRequest message, CancellationToken cancellationToken) - { - await session.SendAsync(message, cancellationToken); - - for (int i = 0; i < 200; i++) - { - if (session.Id is not null) - { - _logger.LogInformation("Agent ({ep?}) authenticated", session.RemoteEndPoint); - return; - } - - await Task.Delay(50, cancellationToken).ConfigureAwait(false); - } - - _logger.LogError("Authentication Timeout ({ep?})", session.RemoteEndPoint); - session.Disconnect(); - } - - private async ValueTask OnAuthenticationResponseAsync(AgentSession session, AuthenticationResponse authentication, CancellationToken cancellationToken) - { - if (authentication is null) - { - throw new NullReferenceException($"authentication failed (empty response)"); - } - - if (authentication.Serial == default) - { - throw new InvalidDataException($"authentication failed ({nameof(authentication.Serial)})"); - } - - //if (authentication.Version == default || authentication.Version < Domain.Constants.Configuration.Version) - //{ - // throw new InvalidDataException($"authentication failed ({nameof(authentication.Version)})"); - //} - - // upsert agent - await _database.Agent().UpdateOneAsync(Builders - .Filter - .Eq(p => p.Serial, authentication.Serial.ToString()), Builders.Update - .SetOnInsert(p => p.Insert, DateTime.Now) - .SetOnInsert(p => p.Serial, authentication.Serial.ToString()) - .Set(p => p.Update, DateTime.Now) - .Set(p => p.Connected, DateTime.Now) - .Set(p => p.Version, authentication.Version) - .Set(p => p.Endpoint, session.RemoteEndPoint?.ToString()) - .Set(p => p.Hostname, authentication.Hostname), new UpdateOptions - { - IsUpsert = true - }, cancellationToken) - .ConfigureAwait(false); - - // get agent - var agentEntity = await _database.Agent() - .Find(Builders - .Filter - .Eq(p => p.Serial, authentication.Serial.ToString())) - .FirstOrDefaultAsync(cancellationToken) - .ConfigureAwait(false); - - // set session id - session.Id = agentEntity.Id; - } - - public async ValueTask ConnectedAsync(AgentSession session, CancellationToken cancellationToken) - { - if (session.Id is null) return; - - // insert connect log - await _database.AgentLog() - .InsertOneAsync(new AgentLogEntity - { - Insert = DateTime.Now, - Agent = session.Id, - Category = CategoryEnum.Network.ToString(), - Status = StatusEnum.Information.ToString(), - Message = $"Connected ({session.RemoteEndPoint})", - Timestamp = DateTime.Now - }, cancellationToken: cancellationToken) - .ConfigureAwait(false); - } - - public async ValueTask DisconnectedAsync(AgentSession session, CancellationToken cancellationToken) - { - if (session.Id is null) return; - - // insert disconnect log - await _database.AgentLog() - .InsertOneAsync(new AgentLogEntity - { - Insert = DateTime.Now, - Agent = session.Id, - Category = CategoryEnum.Network.ToString(), - Status = StatusEnum.Information.ToString(), - Message = $"Disconnected ({session.RemoteEndPoint})", - Timestamp = DateTime.Now - }, cancellationToken: cancellationToken) - .ConfigureAwait(false); - } - - public async ValueTask StatisticUpdateAsync(AgentSession session, CancellationToken cancellationToken) - { - if (session.Id is null) return; - - // update agents stats - await _database.Agent().UpdateOneAsync(Builders - .Filter - .Eq(p => p.Id, session.Id), Builders - .Update - .Set(p => p.Update, DateTime.Now) - .Set(p => p.Activity, session.Activity) - .Set(p => p.SentBytes, session.SentBytes) - .Set(p => p.ReceivedBytes, session.ReceivedBytes) - .Set(p => p.SentPackets, session.SentPackets) - .Set(p => p.ReceivedPackets, session.ReceivedPackets), null, cancellationToken) - .ConfigureAwait(false); - } - - public async ValueTask LogAsync(AgentSession session, AgentLogEntity log, CancellationToken cancellationToken) - { - if (session.Id is null) return; - - await _database.AgentLog() - .InsertOneAsync(log, cancellationToken: cancellationToken) - .ConfigureAwait(false); - } -} \ No newline at end of file diff --git a/src/Server/Insight.Server/Network/Agent/Handlers/TrapHandler.cs b/src/Server/Insight.Server/Network/Agent/Handlers/TrapHandler.cs index c1c2bba..0ef5449 100644 --- a/src/Server/Insight.Server/Network/Agent/Handlers/TrapHandler.cs +++ b/src/Server/Insight.Server/Network/Agent/Handlers/TrapHandler.cs @@ -183,10 +183,10 @@ public partial class TrapHandler(IMongoDatabase database) : IMessageHandler(); services.AddHostedService(); // GLOBAL DEPENDENCIES @@ -84,24 +82,22 @@ internal static class ServiceExtensions services.UseHostedServer(options => { options.Address = IPAddress.Any; - options.Port = configuration.GetValue(Appsettings.AgentServerPort) ?? throw new Exception($"{Appsettings.AgentServerPort} value not set (appsettings)"); + options.Port = configuration.GetValue(Appsettings.Agent.Port) ?? throw new Exception($"{Appsettings.Agent.Port} value not set (appsettings)"); options.Keepalive = 10000; options.Timeout = 30000; options.Backlog = 128; options.Compression = true; options.Encryption = Encryption.Tls12; - options.Certificate = configuration.GetValue(Appsettings.AgentServerCertificate) ?? throw new Exception($"{Appsettings.AgentServerCertificate} value not set (appsettings)"); - options.CertificatePassword = configuration.GetValue(Appsettings.AgentServerCertificatePassword) ?? throw new Exception($"{Appsettings.AgentServerCertificatePassword} value not set (appsettings)"); + options.Certificate = configuration.GetValue(Appsettings.Agent.Certificate) ?? throw new Exception($"{Appsettings.Agent.Certificate} value not set (appsettings)"); + options.CertificatePassword = configuration.GetValue(Appsettings.Agent.CertificatePassword) ?? throw new Exception($"{Appsettings.Agent.CertificatePassword} value not set (appsettings)"); options.UseSerializer>(); }); // HANDLER - services.AddSingleton(); services.AddSingleton, CustomHandler>(); services.AddSingleton, ProxyHandler>(); - services.AddSingleton, AgentHandler>(); services.AddSingleton, DriveHandler>(); services.AddSingleton, Network.Agent.Handlers.EventHandler>(); services.AddSingleton, InterfaceHandler>(); @@ -130,15 +126,15 @@ internal static class ServiceExtensions services.UseHostedServer(options => { options.Address = IPAddress.Any; - options.Port = configuration.GetValue(Appsettings.WebServerPort) ?? throw new Exception($"{Appsettings.WebServerPort} value not set (appsettings)"); + options.Port = configuration.GetValue(Appsettings.Web.Port) ?? throw new Exception($"{Appsettings.Web.Port} value not set (appsettings)"); options.Keepalive = 10000; options.Timeout = 30000; options.Backlog = 128; options.Encryption = Encryption.Tls12; options.Compression = true; - options.Certificate = configuration.GetValue(Appsettings.WebServerCertificate) ?? throw new Exception($"{Appsettings.WebServerCertificate} value not set (appsettings)"); - options.CertificatePassword = configuration.GetValue(Appsettings.WebServerCertificatePassword) ?? throw new Exception($"{Appsettings.WebServerCertificatePassword} value not set (appsettings)"); + options.Certificate = configuration.GetValue(Appsettings.Web.Certificate) ?? throw new Exception($"{Appsettings.Web.Certificate} value not set (appsettings)"); + options.CertificatePassword = configuration.GetValue(Appsettings.Web.CertificatePassword) ?? throw new Exception($"{Appsettings.Web.CertificatePassword} value not set (appsettings)"); options.UseSerializer>(); }); diff --git a/src/Server/Insight.Server/Services/DispatchService.cs b/src/Server/Insight.Server/Services/DispatchService.cs index 7473821..6f08467 100644 --- a/src/Server/Insight.Server/Services/DispatchService.cs +++ b/src/Server/Insight.Server/Services/DispatchService.cs @@ -19,7 +19,7 @@ internal class DispatchService(HttpClient httpClient, IMongoDatabase database, I { _logger.LogTrace("ExecuteAsync"); - var enabled = _configuration.GetValue(Appsettings.DispatchWebmatic) ?? throw new Exception($"{Appsettings.DispatchWebmatic} value not set (appsettings)"); + var enabled = _configuration.GetValue(Appsettings.Dispatch.Webmatic) ?? throw new Exception($"{Appsettings.Dispatch.Webmatic} value not set (appsettings)"); if (enabled is false) return; try diff --git a/src/Server/Insight.Server/Services/JobService.cs b/src/Server/Insight.Server/Services/JobService.cs deleted file mode 100644 index e9a45ce..0000000 --- a/src/Server/Insight.Server/Services/JobService.cs +++ /dev/null @@ -1,105 +0,0 @@ -using Insight.Domain.Network; -using Insight.Domain.Network.Agent.Messages; -using Insight.Infrastructure.Entities; -using Insight.Server.Extensions; -using Insight.Server.Network.Agent; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using MongoDB.Driver; -using Vaitr.Network; - -namespace Insight.Server.Services; - -internal class JobService(ISessionPool agentPool, IMongoDatabase database, ILogger logger) : BackgroundService -{ - private readonly ISessionPool _agentPool = agentPool; - private readonly IMongoDatabase _database = database; - private readonly ILogger _logger = logger; - - protected override async Task ExecuteAsync(CancellationToken cancellationToken) - { - _logger.LogTrace("ExecuteAsync"); - - var jobs = new List - { - RunInventoryAsync(cancellationToken), - //RunCustomsAsync(cancellationToken) - }; - - try - { - await Task.WhenAll(jobs).ConfigureAwait(false); - } - catch (OperationCanceledException) { } - catch (Exception) { } - } - - private async Task RunInventoryAsync(CancellationToken cancellationToken) - { - _logger.LogTrace("RunInventoryAsync"); - - while (cancellationToken.IsCancellationRequested is false) - { - try - { - foreach (var agent in await GetAssignedAgentsAsync(cancellationToken)) - { - await agent.SendAsync(new InventoryRequest(), cancellationToken); - } - } - catch (OperationCanceledException) { } - catch (Exception) { } - finally - { - await Task.Delay(TimeSpan.FromHours(1), cancellationToken); - } - } - } - - private async Task RunCustomsAsync(CancellationToken cancellationToken) - { - _logger.LogTrace("RunCustomsAsync"); - - while (cancellationToken.IsCancellationRequested is false) - { - try - { - // check if agent online - if (_agentPool.FirstOrDefault().Value is not AgentSession agent) continue; - - // proxy-send request packet to agent - await agent.SendAsync(new Request - { - RequestId = "test", - RequestData = "Get-Date" - }, cancellationToken); - } - catch (OperationCanceledException) { } - catch (Exception) { } - finally - { - await Task.Delay(TimeSpan.FromSeconds(3), cancellationToken); - } - } - - - } - - private async ValueTask> GetAssignedAgentsAsync(CancellationToken cancellationToken) - { - var valid = new List(); - - await Async.ParallelForEach(_agentPool.Where(p => p.Value.Id is not null), async x => - { - var host = await _database.Host() - .Find(Builders.Filter.Eq(p => p.Agent, x.Value.Id)) - .FirstOrDefaultAsync(cancellationToken) - .ConfigureAwait(false); - - if (host is null) return; - valid.Add(x.Value); - }); - - return valid; - } -} \ No newline at end of file diff --git a/src/Server/Insight.Server/appsettings.Development.json b/src/Server/Insight.Server/appsettings.Development.json index 859a335..9138b80 100644 --- a/src/Server/Insight.Server/appsettings.Development.json +++ b/src/Server/Insight.Server/appsettings.Development.json @@ -1,17 +1,13 @@ { - "database": "mongodb://db.insight.local:27017", + "mongo.connection": "mongodb://db.insight.local:27017", - "remote.server.port": 3003, - "remote.server.certificate": "localhost.pfx", - "remote.server.certificate.password": "Webmatic12", + "agent.port": 3002, + "agent.certificate": "localhost.pfx", + "agent.certificate.password": "Webmatic12", - "agent.server.port": 3002, - "agent.server.certificate": "localhost.pfx", - "agent.server.certificate.password": "Webmatic12", - - "web.server.port": 3001, - "web.server.certificate": "localhost.pfx", - "web.server.certificate.password": "Webmatic12", + "web.port": 3001, + "web.certificate": "localhost.pfx", + "web.certificate.password": "Webmatic12", "dispatch.webmatic": false } \ No newline at end of file diff --git a/src/Server/Insight.Server/appsettings.json b/src/Server/Insight.Server/appsettings.json index c43acdf..d9ce71c 100644 --- a/src/Server/Insight.Server/appsettings.json +++ b/src/Server/Insight.Server/appsettings.json @@ -1,13 +1,13 @@ { - "database": "mongodb://127.0.0.1:27017", + "mongo.connection": "mongodb://127.0.0.1:27017", - "agent.server.port": 3002, - "agent.server.certificate": "localhost.pfx", - "agent.server.certificate.password": "Webmatic12", + "agent.port": 3002, + "agent.certificate": "localhost.pfx", + "agent.certificate.password": "Webmatic12", - "web.server.port": 3001, - "web.server.certificate": "localhost.pfx", - "web.server.certificate.password": "Webmatic12", + "web.port": 3001, + "web.certificate": "localhost.pfx", + "web.certificate.password": "Webmatic12", "dispatch.webmatic": false } \ No newline at end of file diff --git a/src/Server/Insight.Server2/Controllers/WeatherForecastController.cs b/src/Server/Insight.Server2/Controllers/WeatherForecastController.cs new file mode 100644 index 0000000..4a44f33 --- /dev/null +++ b/src/Server/Insight.Server2/Controllers/WeatherForecastController.cs @@ -0,0 +1,33 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Insight.Server2.Controllers +{ + [ApiController] + [Route("[controller]")] + public class WeatherForecastController : ControllerBase + { + private static readonly string[] Summaries = new[] + { + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" + }; + + private readonly ILogger _logger; + + public WeatherForecastController(ILogger logger) + { + _logger = logger; + } + + [HttpGet(Name = "GetWeatherForecast")] + public IEnumerable Get() + { + return Enumerable.Range(1, 5).Select(index => new WeatherForecast + { + Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), + TemperatureC = Random.Shared.Next(-20, 55), + Summary = Summaries[Random.Shared.Next(Summaries.Length)] + }) + .ToArray(); + } + } +} diff --git a/src/Server/Insight.Server2/Extensions/Async.cs b/src/Server/Insight.Server2/Extensions/Async.cs new file mode 100644 index 0000000..225b569 --- /dev/null +++ b/src/Server/Insight.Server2/Extensions/Async.cs @@ -0,0 +1,52 @@ +using System.Threading.Tasks.Dataflow; + +namespace Insight.Server.Extensions; + +public static class Async +{ + public static async Task ParallelForEach( + this IAsyncEnumerable source, + Func body, + int maxDegreeOfParallelism = DataflowBlockOptions.Unbounded, + TaskScheduler? scheduler = null) + { + var options = new ExecutionDataflowBlockOptions + { + MaxDegreeOfParallelism = maxDegreeOfParallelism + }; + + if (scheduler != null) + options.TaskScheduler = scheduler; + + var block = new ActionBlock(body, options); + + await foreach (var item in source) + block.Post(item); + + block.Complete(); + await block.Completion; + } + + public static async Task ParallelForEach( + this IEnumerable source, + Func body, + int maxDegreeOfParallelism = DataflowBlockOptions.Unbounded, + TaskScheduler? scheduler = null) + { + var options = new ExecutionDataflowBlockOptions + { + MaxDegreeOfParallelism = maxDegreeOfParallelism + }; + + if (scheduler != null) + options.TaskScheduler = scheduler; + + var block = new ActionBlock(body, options); + + foreach (var item in source) + block.Post(item); + + block.Complete(); + await block.Completion; + } +} diff --git a/src/Server/Insight.Server2/Insight.Server2.csproj b/src/Server/Insight.Server2/Insight.Server2.csproj new file mode 100644 index 0000000..d35f0cc --- /dev/null +++ b/src/Server/Insight.Server2/Insight.Server2.csproj @@ -0,0 +1,41 @@ + + + + Exe + net8.0 + latest + Insight + server + 2024.1.10.0 + Insight.Server + enable + enable + none + true + + + + + + + + + + + + + + + + + + + + + + + PreserveNewest + + + + diff --git a/src/Server/Insight.Server2/Insight.Server2.http b/src/Server/Insight.Server2/Insight.Server2.http new file mode 100644 index 0000000..4d3b8e8 --- /dev/null +++ b/src/Server/Insight.Server2/Insight.Server2.http @@ -0,0 +1,6 @@ +@Insight.Server2_HostAddress = http://localhost:5197 + +GET {{Insight.Server2_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/src/Server/Insight.Server2/Models/MonitorMessage.cs b/src/Server/Insight.Server2/Models/MonitorMessage.cs new file mode 100644 index 0000000..3886d21 --- /dev/null +++ b/src/Server/Insight.Server2/Models/MonitorMessage.cs @@ -0,0 +1,26 @@ +using Insight.Domain.Enums; + +namespace Insight.Server.Models; + +internal class MonitorMessage +{ + public DateTime? Timestamp { get; set; } + public string? Community { get; set; } + public ApplicationEnum? Application { get; set; } + public CategoryEnum? Category { get; set; } + public StatusEnum? Status { get; set; } + public string? Endpoint { get; set; } + public string? Hostname { get; set; } + public string? Subject { get; set; } + public string? Message { get; set; } + + public enum ApplicationEnum + { + Unknown = 0, + Insight = 1, + Acronis = 2, + Veeam = 3, + QNAP = 4, + FreeNas = 5 + } +} diff --git a/src/Server/Insight.Server2/Network/Agent/AgentSession.cs b/src/Server/Insight.Server2/Network/Agent/AgentSession.cs new file mode 100644 index 0000000..91744ca --- /dev/null +++ b/src/Server/Insight.Server2/Network/Agent/AgentSession.cs @@ -0,0 +1,206 @@ +using Insight.Domain.Enums; +using Insight.Domain.Interfaces; +using Insight.Domain.Network; +using Insight.Domain.Network.Agent.Messages; +using Insight.Infrastructure.Entities; +using Microsoft.Extensions.Logging; +using MongoDB.Driver; +using Vaitr.Network; + +namespace Insight.Server.Network.Agent; + +public class AgentSession( + IMongoDatabase database, + IEnumerable> handlers, + ISerializer serializer, + ILogger logger) : TcpSession(serializer, logger) +{ + public string? Id { get; set; } + + private readonly IMongoDatabase _database = database; + private readonly IEnumerable> _handlers = handlers; + + protected override async ValueTask OnConnectedAsync(CancellationToken cancellationToken) + { + _logger.LogInformation("Agent ({ep?}) connected", RemoteEndPoint); + + // auth request + await SendAsync(new AuthenticationRequest(), cancellationToken); + + // wait for ack + for (int i = 0; i < 200; i++) + { + if (Id is not null) + break; + + await Task.Delay(50, cancellationToken).ConfigureAwait(false); + } + + // if ack not received + if (Id is null) + { + _logger.LogError("Agent ({ep?}) authentication timeout", RemoteEndPoint); + Disconnect(); + return; + } + + _logger.LogInformation("Agent ({ep?} / {id}) authenticated", RemoteEndPoint, Id); + + // insert log + await WriteLogAsync(CategoryEnum.Network, StatusEnum.Information, $"Connected ({RemoteEndPoint})", default); + + // update entity + await UpdateAsync(default); + + // init inventory task + _ = InitInventoryAsync(cancellationToken); + } + + protected override async ValueTask OnDisconnectedAsync(CancellationToken cancellationToken) + { + _logger.LogInformation("Agent ({ep?}) disconnected", RemoteEndPoint); + + // insert log + await WriteLogAsync(CategoryEnum.Network, StatusEnum.Information, $"Disconnected ({RemoteEndPoint})", default); + + // update entity + await UpdateAsync(default); + } + + protected override async ValueTask OnSentAsync(IPacketContext context, CancellationToken cancellationToken) + { + await base.OnSentAsync(context, cancellationToken); + + // update entity + await UpdateAsync(cancellationToken); + } + + protected override async ValueTask OnReceivedAsync(IPacketContext context, CancellationToken cancellationToken) + { + await base.OnReceivedAsync(context, cancellationToken); + + // only accept auth response if not authenticated + if (Id is null) + { + if (context.Packet is not AuthenticationResponse authentication) return; + await OnAuthenticationAsync(authentication, cancellationToken); + } + + // update entity + await UpdateAsync(cancellationToken); + + // pass message to handlers + foreach (var handler in _handlers) + { + try + { + await handler.HandleAsync(this, context.Packet, cancellationToken); + } + catch (Exception ex) + { + _logger.LogWarning("Agent ({ep?}) {ex}", RemoteEndPoint, ex.ToString()); + } + } + } + + protected override async ValueTask OnHeartbeatAsync(CancellationToken cancellationToken) + { + _logger.LogInformation("Agent ({ep?}) Heartbeat", RemoteEndPoint); + + // update entity + await UpdateAsync(cancellationToken); + } + + private async ValueTask OnAuthenticationAsync(AuthenticationResponse authentication, CancellationToken cancellationToken) + { + // check serial + if (authentication.Serial == default) + throw new InvalidDataException($"authentication failed ({nameof(authentication.Serial)})"); + + // check version + //if (authentication.Version == default || authentication.Version < Domain.Constants.Configuration.Version) + // throw new InvalidDataException($"authentication failed ({nameof(authentication.Version)})"); + + // upsert agent + await _database.Agent() + .UpdateOneAsync(Builders + .Filter + .Eq(p => p.Serial, authentication.Serial.ToString()), Builders.Update + .SetOnInsert(p => p.Insert, DateTime.Now) + .SetOnInsert(p => p.Serial, authentication.Serial.ToString()) + .Set(p => p.Update, DateTime.Now) + .Set(p => p.Connected, DateTime.Now) + .Set(p => p.Version, authentication.Version) + .Set(p => p.Endpoint, RemoteEndPoint?.ToString()) + .Set(p => p.Hostname, authentication.Hostname), new UpdateOptions + { + IsUpsert = true + }, cancellationToken) + .ConfigureAwait(false); + + // get agent + var agentEntity = await _database.Agent() + .Find(Builders + .Filter + .Eq(p => p.Serial, authentication.Serial.ToString())) + .FirstOrDefaultAsync(cancellationToken) + .ConfigureAwait(false); + + // set session id + Id = agentEntity.Id; + } + + private async ValueTask WriteLogAsync(CategoryEnum category, StatusEnum status, string message, CancellationToken cancellationToken) + { + await _database.AgentLog() + .InsertOneAsync(new AgentLogEntity + { + Insert = DateTime.Now, + Agent = Id, + Category = category.ToString(), + Status = status.ToString(), + Message = message, + Timestamp = DateTime.Now + }, cancellationToken: cancellationToken) + .ConfigureAwait(false); + } + + private async ValueTask UpdateAsync(CancellationToken cancellationToken) + { + await _database.Agent().UpdateOneAsync(Builders + .Filter + .Eq(p => p.Id, Id), Builders + .Update + .Set(p => p.Update, DateTime.Now) + .Set(p => p.Activity, Activity) + .Set(p => p.SentBytes, SentBytes) + .Set(p => p.SentPackets, SentPackets) + .Set(p => p.ReceivedBytes, ReceivedBytes) + .Set(p => p.ReceivedPackets, ReceivedPackets), null, cancellationToken) + .ConfigureAwait(false); + } + + private async Task InitInventoryAsync(CancellationToken cancellationToken) + { + while (cancellationToken.IsCancellationRequested is false) + { + _logger.LogWarning("try get inventory"); + + // find assigned host + var host = await _database.Host() + .CountDocumentsAsync(Builders.Filter.Eq(p => p.Agent, Id), cancellationToken: cancellationToken) + .ConfigureAwait(false); + + // if not assigned => short delay + if (host == 0) + { + await Task.Delay(TimeSpan.FromMinutes(1), cancellationToken); + continue; + } + + // send request + await SendAsync(new InventoryRequest(), cancellationToken); + await Task.Delay(TimeSpan.FromHours(1), cancellationToken); + } + } +} \ No newline at end of file diff --git a/src/Server/Insight.Server2/Network/Agent/Handlers/CustomHandler.cs b/src/Server/Insight.Server2/Network/Agent/Handlers/CustomHandler.cs new file mode 100644 index 0000000..14e1e87 --- /dev/null +++ b/src/Server/Insight.Server2/Network/Agent/Handlers/CustomHandler.cs @@ -0,0 +1,27 @@ +using Insight.Domain.Interfaces; +using Insight.Domain.Network; +using Insight.Domain.Network.Agent.Messages; +using Microsoft.Extensions.Logging; + +namespace Insight.Server.Network.Agent.Handlers; + +public class CustomHandler(ILogger logger) : IMessageHandler +{ + private readonly ILogger _logger = logger; + + public async ValueTask HandleAsync(AgentSession sender, TMessage message, CancellationToken cancellationToken) where TMessage : IMessage + { + switch (message) + { + case Response response: + await OnResponseAsync(sender, response, cancellationToken); + break; + } + } + + private ValueTask OnResponseAsync(AgentSession sender, Response response, CancellationToken cancellationToken) + { + _logger.LogWarning("Response: {response}", response.ResponseData); + return default; + } +} \ No newline at end of file diff --git a/src/Server/Insight.Server2/Network/Agent/Handlers/DriveHandler.cs b/src/Server/Insight.Server2/Network/Agent/Handlers/DriveHandler.cs new file mode 100644 index 0000000..2fe86a6 --- /dev/null +++ b/src/Server/Insight.Server2/Network/Agent/Handlers/DriveHandler.cs @@ -0,0 +1,141 @@ +using Insight.Domain.Interfaces; +using Insight.Domain.Network; +using Insight.Domain.Network.Agent.Messages; +using Insight.Infrastructure.Entities; +using MongoDB.Bson; +using MongoDB.Driver; + +namespace Insight.Server.Network.Agent.Handlers; + +public class DriveHandler(IMongoDatabase database) : IMessageHandler +{ + private readonly IMongoDatabase _database = database; + + public async ValueTask HandleAsync(AgentSession sender, TMessage message, CancellationToken cancellationToken) where TMessage : IMessage + { + switch (message) + { + case Collection drives: + await OnDrivesAsync(sender, drives, cancellationToken); + break; + } + } + + private async ValueTask OnDrivesAsync(AgentSession session, List drives, CancellationToken cancellationToken) + { + var agentEntity = await _database.Agent().Find(Builders.Filter.Eq(p => p.Id, session.Id)).FirstOrDefaultAsync(cancellationToken); + if (agentEntity is null) return; + + var hostEntity = await _database.Host().Find(Builders.Filter.Eq(p => p.Agent, agentEntity.Id)).FirstOrDefaultAsync(cancellationToken); + if (hostEntity is null) return; + + var batch = ObjectId.GenerateNewId().ToString(); + var date = DateTime.Now; + + var driveBulk = new List>(); + + if (drives is not null && drives.Count != 0) + { + foreach (var drive in drives) + { + var driveFilter = Builders.Filter.And(new List> + { + Builders.Filter.Eq(x => x.Host, hostEntity.Id), + Builders.Filter.Eq(x => x.Index, drive.Index) + }); + + var driveUpdate = Builders.Update + .SetOnInsert(p => p.Insert, date) + .SetOnInsert(p => p.Host, hostEntity.Id) + .SetOnInsert(p => p.Index, drive.Index) + .Set(p => p.Update, date) + .Set(p => p.Batch, batch) + .Set(p => p.Company, drive.Manufacturer) + .Set(p => p.Name, drive.Name) + .Set(p => p.Size, drive.Size) + .Set(p => p.Type, drive.InterfaceType) + .Set(p => p.Serial, drive.SerialNumber) + .Set(p => p.Firmware, drive.FirmwareRevision) + .Set(p => p.Status, drive.Status) + .Set(p => p.Pnp, drive.PNPDeviceID); + + driveBulk.Add(new UpdateOneModel(driveFilter, driveUpdate) + { + IsUpsert = true + }); + } + } + + driveBulk.Add(new DeleteManyModel(Builders.Filter.And(new List> + { + Builders.Filter.Eq(x => x.Host, hostEntity.Id), + Builders.Filter.Ne(x => x.Batch, batch) + }))); + + var driveResult = await _database.HostDrive().BulkWriteAsync(driveBulk, cancellationToken: cancellationToken); + + // volumes + + var volumeBulk = new List>(); + + if (drives is not null && drives.Count != 0) + { + foreach (var drive in drives) + { + var driveId = await _database.HostDrive() + .Find(p => p.Host == hostEntity.Id && p.Index == drive.Index) + .Project(p => p.Id) + .FirstOrDefaultAsync(cancellationToken: default); + + if (drive.Volumes is not null && drive.Volumes.Count != 0) + { + foreach (var volume in drive.Volumes) + { + var volumeFilter = Builders.Filter.And(new List> + { + Builders.Filter.Eq(x => x.Host, hostEntity.Id), + Builders.Filter.Eq(x => x.Drive, driveId), + Builders.Filter.Eq(x => x.Index, volume.Index) + }); + + var volumeUpdate = Builders.Update + .SetOnInsert(p => p.Insert, date) + .SetOnInsert(p => p.Host, hostEntity.Id) + .SetOnInsert(p => p.Drive, driveId) + .SetOnInsert(p => p.Index, volume.Index) + .Set(p => p.Update, date) + .Set(p => p.Batch, batch) + .Set(p => p.Name, volume.Name) + .Set(p => p.Label, volume.Id) + .Set(p => p.Serial, volume.SerialNumber) + .Set(p => p.Size, volume.Size) + .Set(p => p.FreeSpace, volume.FreeSpace) + .Set(p => p.Type, volume.Type) + .Set(p => p.FileSystem, volume.FileSystem) + .Set(p => p.Compressed, volume.Compressed) + .Set(p => p.Bootable, volume.Bootable) + .Set(p => p.Primary, volume.PrimaryPartition) + .Set(p => p.Boot, volume.Bootable) + .Set(p => p.BlockSize, volume.BlockSize) + .Set(p => p.Blocks, volume.NumberOfBlocks) + .Set(p => p.StartingOffset, volume.StartingOffset) + .Set(p => p.Provider, volume.ProviderName); + + volumeBulk.Add(new UpdateOneModel(volumeFilter, volumeUpdate) + { + IsUpsert = true + }); + } + } + } + } + + volumeBulk.Add(new DeleteManyModel(Builders.Filter.And(new List> + { + Builders.Filter.Eq(x => x.Host, hostEntity.Id), + Builders.Filter.Ne(x => x.Batch, batch) + }))); + + var volumeResult = await _database.HostVolume().BulkWriteAsync(volumeBulk, cancellationToken: cancellationToken); + } +} \ No newline at end of file diff --git a/src/Server/Insight.Server2/Network/Agent/Handlers/EventHandler.cs b/src/Server/Insight.Server2/Network/Agent/Handlers/EventHandler.cs new file mode 100644 index 0000000..ee6080c --- /dev/null +++ b/src/Server/Insight.Server2/Network/Agent/Handlers/EventHandler.cs @@ -0,0 +1,261 @@ +using Insight.Domain.Enums; +using Insight.Domain.Interfaces; +using Insight.Domain.Network; +using Insight.Domain.Network.Agent.Messages; +using Insight.Infrastructure.Entities; +using MongoDB.Driver; +using static Insight.Domain.Network.Agent.Messages.Event; + +namespace Insight.Server.Network.Agent.Handlers; + +public class EventHandler(IMongoDatabase database) : IMessageHandler +{ + private readonly IMongoDatabase _database = database; + + public async ValueTask HandleAsync(AgentSession sender, TMessage message, CancellationToken cancellationToken) where TMessage : IMessage + { + switch (message) + { + case Event @event: + await OnEventAsync(sender, @event, cancellationToken); + break; + } + } + + private async ValueTask OnEventAsync(AgentSession session, Event @event, CancellationToken cancellationToken) + { + var agentEntity = await _database.Agent().Find(Builders.Filter.Eq(p => p.Id, session.Id)).FirstOrDefaultAsync(cancellationToken); + if (agentEntity is null) return; + + var hostEntity = await _database.Host().Find(Builders.Filter.Eq(p => p.Agent, agentEntity.Id)).FirstOrDefaultAsync(cancellationToken); + if (hostEntity is null) return; + + if (FilterEventId(@event)) return; + + var hostLog = await InsertHostLogAsync(hostEntity, @event, cancellationToken); + + if (hostLog is null || FilterMonitoringHostLog(hostLog)) return; + + await _database.HostLogMonitoring() + .InsertOneAsync(new HostLogMonitoringEntity + { + Host = hostEntity.Id, + Insert = hostLog.Insert, + Timestamp = hostLog.Timestamp, + Category = hostLog.Category, + Status = hostLog.Status, + Message = hostLog.Message, + Dispatch = DispatchEnum.Pending.ToString() + }, cancellationToken: cancellationToken); + } + + private async ValueTask InsertAgentLogAsync(AgentSession session, Event @event, CancellationToken cancellationToken) + { + var agentEntity = await _database.Agent() + .Aggregate() + .Match(Builders.Filter.Eq(p => p.Id, session.Id)) + .Lookup(_database.Host(), p => p.Serial, p => p.Agent, p => p.Hosts) + .FirstOrDefaultAsync(cancellationToken); + + if (agentEntity is null) return null; + + StatusEnum? status = @event.Status switch + { + StatusType.Information => StatusEnum.Information, + StatusType.Warning => StatusEnum.Warning, + StatusType.Error => StatusEnum.Error, + _ => null + }; + + CategoryEnum? category = @event.Category?.ToLower() switch + { + "network" => CategoryEnum.Network, + "application" => CategoryEnum.Application, + "security" => CategoryEnum.Security, + "system" => CategoryEnum.System, + _ => CategoryEnum.None + }; + + var date = DateTime.Now; + + var log = new AgentLogEntity + { + Insert = date, + Agent = agentEntity.Id, + Timestamp = @event.Timestamp, + EventId = @event.EventId.ToString(), + Source = @event.Source, + Status = status.ToString(), + Category = category.ToString(), + Message = @event.Message + }; + + await _database.AgentLog().InsertOneAsync(log, cancellationToken: cancellationToken); + return log; + } + + private async ValueTask InsertHostLogAsync(HostEntity hostEntity, Event @event, CancellationToken cancellationToken) + { + StatusEnum? status = @event.Status switch + { + StatusType.Information => StatusEnum.Information, + StatusType.Warning => StatusEnum.Warning, + StatusType.Error => StatusEnum.Error, + StatusType.Critical => StatusEnum.Error, + StatusType.Unknown => null, + _ => null + }; + + var category = CategoryEnum.None; + + if (@event.Category is not null) + { + switch (@event.Category) + { + case var _ when @event.Category.Contains("network", StringComparison.InvariantCultureIgnoreCase): + category = CategoryEnum.Network; + break; + + case var _ when @event.Category.Contains("application", StringComparison.InvariantCultureIgnoreCase): + category = CategoryEnum.Application; + break; + + case var _ when @event.Category.Contains("security", StringComparison.InvariantCultureIgnoreCase): + category = CategoryEnum.Security; + break; + + case var _ when @event.Category.Contains("system", StringComparison.InvariantCultureIgnoreCase): + category = CategoryEnum.System; + break; + + case var _ when @event.Category.Contains("printservice", StringComparison.InvariantCultureIgnoreCase): + category = CategoryEnum.Printer; + break; + + case var _ when @event.Category.Contains("taskscheduler", StringComparison.InvariantCultureIgnoreCase): + category = CategoryEnum.Task; + break; + + case var _ when @event.Category.Contains("terminalservices", StringComparison.InvariantCultureIgnoreCase): + category = CategoryEnum.RDP; + break; + + case var _ when @event.Category.Contains("smbclient", StringComparison.InvariantCultureIgnoreCase): + category = CategoryEnum.Network; + break; + + case var _ when @event.Category.Contains("smbserver", StringComparison.InvariantCultureIgnoreCase): + category = CategoryEnum.Network; + break; + + case var _ when @event.Category.Contains("storagespaces", StringComparison.InvariantCultureIgnoreCase): + category = CategoryEnum.System; + break; + + case var _ when @event.Category.Contains("diagnostics", StringComparison.InvariantCultureIgnoreCase): + category = CategoryEnum.System; + break; + + default: + break; + } + } + + var date = DateTime.Now; + + var log = new HostLogEntity + { + Insert = date, + Host = hostEntity.Id, + Timestamp = @event.Timestamp, + EventId = @event.EventId.ToString(), + Status = status.ToString(), + Source = @event.Source, + Category = category.ToString(), + Message = @event.Message + }; + + await _database.HostLog().InsertOneAsync(log, cancellationToken: cancellationToken); + return log; + } + + private static bool FilterEventId(Event @event) + { + var filter = new List + { + 0, + 3, + 4, + 16, + 37, + 900, + 902, + 903, + 1001, + 1003, + 1008, + 1023, + 1066, + 4624, + 4625, + 4634, + 4648, + 4672, + 4776, + 4798, + 4799, + 5058, + 5059, + 5061, + 5379, + 5612, + 5781, + //7036, + 8014, + 8016, + 8019, + 8194, + 8224, + 9009, + 9027, + 10001, + 10016, + 16384, + 16394, + 36874, + 36888, + }; + + if (filter.Any(p => p == @event.EventId)) return true; + return false; + } + + private static bool FilterMonitoringHostLog(HostLogEntity hostLog) + { + if (Enum.TryParse(hostLog.Status, out StatusType status) is false) return true; + + if (hostLog.Category == CategoryEnum.System.ToString()) + { + if (hostLog.Source == "Service Control Manager" && status < StatusType.Warning) return true; + } + + if (hostLog.Category == CategoryEnum.Task.ToString()) + { + if (status < StatusType.Error) return true; + } + + // skip rdp infos + if (hostLog.Category == CategoryEnum.RDP.ToString()) + { + if (hostLog.EventId == "261") return true; + } + + // skip smbclient (veeam errors) + if (hostLog.Category == CategoryEnum.Security.ToString()) + { + if (hostLog.Source == "Microsoft-Windows-SMBClient") return true; + } + + return false; + } +} \ No newline at end of file diff --git a/src/Server/Insight.Server2/Network/Agent/Handlers/InterfaceHandler.cs b/src/Server/Insight.Server2/Network/Agent/Handlers/InterfaceHandler.cs new file mode 100644 index 0000000..c986aad --- /dev/null +++ b/src/Server/Insight.Server2/Network/Agent/Handlers/InterfaceHandler.cs @@ -0,0 +1,295 @@ +using Insight.Domain.Interfaces; +using Insight.Domain.Network; +using Insight.Domain.Network.Agent.Messages; +using Insight.Infrastructure.Entities; +using MongoDB.Bson; +using MongoDB.Driver; + +namespace Insight.Server.Network.Agent.Handlers; + +public class InterfaceHandler(IMongoDatabase database) : IMessageHandler +{ + private readonly IMongoDatabase _database = database; + + public async ValueTask HandleAsync(AgentSession sender, TMessage message, CancellationToken cancellationToken) where TMessage : IMessage + { + switch (message) + { + case Collection interfaces: + await OnInterfacesAsync(sender, interfaces, cancellationToken); + break; + } + } + + private async ValueTask OnInterfacesAsync(AgentSession session, List interfaces, CancellationToken cancellationToken) + { + var agentEntity = await _database.Agent().Find(Builders.Filter.Eq(p => p.Id, session.Id)).FirstOrDefaultAsync(cancellationToken); + if (agentEntity is null) return; + + var hostEntity = await _database.Host().Find(Builders.Filter.Eq(p => p.Agent, agentEntity.Id)).FirstOrDefaultAsync(cancellationToken); + if (hostEntity is null) return; + + var batch = ObjectId.GenerateNewId().ToString(); + var date = DateTime.Now; + + // interfaces + + if (interfaces is not null && interfaces.Count != 0) + { + var interfaceBulk = new List>(); + + foreach (var @interface in interfaces) + { + var interfaceFilter = Builders.Filter.And(new List> + { + Builders.Filter.Eq(x => x.Host, hostEntity.Id), + Builders.Filter.Eq(x => x.Index, @interface.Index) + }); + + var interfaceUpdate = Builders.Update + .SetOnInsert(p => p.Insert, date) + .SetOnInsert(p => p.Host, hostEntity.Id) + .SetOnInsert(p => p.Index, @interface.Index) + .Set(p => p.Update, date) + .Set(p => p.Batch, batch) + + .Set(p => p.Mac, @interface?.Mac) + .Set(p => p.Name, @interface?.Name) + .Set(p => p.Description, @interface?.Description) + .Set(p => p.Physical, @interface?.Physical) + .Set(p => p.Status, @interface?.Status?.ToString()) + .Set(p => p.Suffix, @interface?.Suffix) + .Set(p => p.Speed, @interface?.Speed) + .Set(p => p.Ipv4Mtu, @interface?.Ipv4Mtu) + .Set(p => p.Ipv4Dhcp, @interface?.Ipv4Dhcp) + + .Set(p => p.Ipv4Forwarding, @interface?.Ipv4Forwarding) + .Set(p => p.Ipv6Mtu, @interface?.Ipv6Mtu) + .Set(p => p.Sent, @interface?.Sent) + .Set(p => p.Received, @interface?.Received) + .Set(p => p.IncomingPacketsDiscarded, @interface?.IncomingPacketsDiscarded) + .Set(p => p.IncomingPacketsWithErrors, @interface?.IncomingPacketsWithErrors) + .Set(p => p.IncomingUnknownProtocolPackets, @interface?.IncomingUnknownProtocolPackets) + .Set(p => p.OutgoingPacketsDiscarded, @interface?.OutgoingPacketsDiscarded) + .Set(p => p.OutgoingPacketsWithErrors, @interface?.OutgoingPacketsWithErrors); + + interfaceBulk.Add(new UpdateOneModel(interfaceFilter, interfaceUpdate) + { + IsUpsert = true + }); + } + + interfaceBulk.Add(new DeleteManyModel(Builders.Filter.And(new List> + { + Builders.Filter.Eq(x => x.Host, hostEntity.Id), + Builders.Filter.Ne(x => x.Batch, batch) + }))); + + var interfaceResult = await _database.HostInterface().BulkWriteAsync(interfaceBulk, cancellationToken: cancellationToken); + } + + // addresses + + if (interfaces is not null && interfaces.Count != 0) + { + var addressBulk = new List>(); + + foreach (var @interface in interfaces) + { + var interfaceId = await _database.HostInterface() + .Find(p => p.Host == hostEntity.Id && p.Index == @interface.Index) + .Project(p => p.Id) + .FirstOrDefaultAsync(cancellationToken: default); + + if (@interface.Addresses is not null && @interface.Addresses.Count != 0) + { + foreach (var address in @interface.Addresses) + { + var addressFilter = Builders.Filter.And(new List> + { + Builders.Filter.Eq(x => x.Host, hostEntity.Id), + Builders.Filter.Eq(x => x.Interface, interfaceId), + Builders.Filter.Eq(x => x.Address, address?.IpAddress?.Address), + Builders.Filter.Eq(x => x.Mask, address?.Ipv4Mask?.Address) + }); + + var addressUpdate = Builders.Update + .SetOnInsert(p => p.Insert, date) + .SetOnInsert(p => p.Host, hostEntity.Id) + .SetOnInsert(p => p.Interface, interfaceId) + .SetOnInsert(p => p.Address, address?.IpAddress?.Address) + .SetOnInsert(p => p.Mask, address?.Ipv4Mask?.Address) + .Set(p => p.Update, date) + .Set(p => p.Batch, batch); + + addressBulk.Add(new UpdateOneModel(addressFilter, addressUpdate) + { + IsUpsert = true + }); + } + } + } + + addressBulk.Add(new DeleteManyModel(Builders.Filter.And(new List> + { + Builders.Filter.Eq(x => x.Host, hostEntity.Id), + Builders.Filter.Ne(x => x.Batch, batch) + }))); + + var addressResult = await _database.HostInterfaceAddress().BulkWriteAsync(addressBulk, cancellationToken: cancellationToken); + } + + // gateways + + if (interfaces is not null && interfaces.Count != 0) + { + var gatewayBulk = new List>(); + + foreach (var @interface in interfaces) + { + var interfaceId = await _database.HostInterface() + .Find(p => p.Host == hostEntity.Id && p.Index == @interface.Index) + .Project(p => p.Id) + .FirstOrDefaultAsync(cancellationToken: default); + + if (@interface.Gateways is not null && @interface.Gateways.Count != 0) + { + foreach (var gateway in @interface.Gateways) + { + var gatewayFilter = Builders.Filter.And(new List> + { + Builders.Filter.Eq(x => x.Host, hostEntity.Id), + Builders.Filter.Eq(x => x.Interface, interfaceId), + Builders.Filter.Eq(x => x.Address, gateway?.Address) + }); + + var gatewayUpdate = Builders.Update + .SetOnInsert(p => p.Insert, date) + .SetOnInsert(p => p.Host, hostEntity.Id) + .SetOnInsert(p => p.Interface, interfaceId) + .SetOnInsert(p => p.Address, gateway?.Address) + .Set(p => p.Update, date) + .Set(p => p.Batch, batch); + + gatewayBulk.Add(new UpdateOneModel(gatewayFilter, gatewayUpdate) + { + IsUpsert = true + }); + } + } + } + + gatewayBulk.Add(new DeleteManyModel(Builders.Filter.And(new List> + { + Builders.Filter.Eq(x => x.Host, hostEntity.Id), + Builders.Filter.Ne(x => x.Batch, batch) + }))); + + var gatewayResult = await _database.HostInterfaceGateway().BulkWriteAsync(gatewayBulk, cancellationToken: cancellationToken); + } + + // nameservers + + if (interfaces is not null && interfaces.Count != 0) + { + var nameserverBulk = new List>(); + + foreach (var @interface in interfaces) + { + var interfaceId = await _database.HostInterface() + .Find(p => p.Host == hostEntity.Id && p.Index == @interface.Index) + .Project(p => p.Id) + .FirstOrDefaultAsync(cancellationToken: default); + + if (@interface.Dns is not null && @interface.Dns.Count != 0) + { + foreach (var nameserver in @interface.Dns) + { + var nameserverFilter = Builders.Filter.And(new List> + { + Builders.Filter.Eq(x => x.Host, hostEntity.Id), + Builders.Filter.Eq(x => x.Interface, interfaceId), + Builders.Filter.Eq(x => x.Address, nameserver?.Address) + }); + + var nameserverUpdate = Builders.Update + .SetOnInsert(p => p.Insert, date) + .SetOnInsert(p => p.Host, hostEntity.Id) + .SetOnInsert(p => p.Interface, interfaceId) + .SetOnInsert(p => p.Address, nameserver?.Address) + .Set(p => p.Update, date) + .Set(p => p.Batch, batch); + + nameserverBulk.Add(new UpdateOneModel(nameserverFilter, nameserverUpdate) + { + IsUpsert = true + }); + } + } + } + + nameserverBulk.Add(new DeleteManyModel(Builders.Filter.And(new List> + { + Builders.Filter.Eq(x => x.Host, hostEntity.Id), + Builders.Filter.Ne(x => x.Batch, batch) + }))); + + var nameserverResult = await _database.HostInterfaceNameserver().BulkWriteAsync(nameserverBulk, cancellationToken: cancellationToken); + } + + // routes + + if (interfaces is not null && interfaces.Count != 0) + { + var routeBulk = new List>(); + + foreach (var @interface in interfaces) + { + var interfaceId = await _database.HostInterface() + .Find(p => p.Host == hostEntity.Id && p.Index == @interface.Index) + .Project(p => p.Id) + .FirstOrDefaultAsync(cancellationToken: default); + + if (@interface.Routes is not null && @interface.Routes.Count != 0) + { + foreach (var route in @interface.Routes) + { + var routeFilter = Builders.Filter.And(new List> + { + Builders.Filter.Eq(x => x.Host, hostEntity.Id), + Builders.Filter.Eq(x => x.Interface, interfaceId), + Builders.Filter.Eq(x => x.Destination, route?.Destination?.Address), + Builders.Filter.Eq(x => x.Gateway, route?.Gateway?.Address), + Builders.Filter.Eq(x => x.Mask, route?.Mask), + }); + + var routeUpdate = Builders.Update + .SetOnInsert(p => p.Insert, date) + .SetOnInsert(p => p.Host, hostEntity.Id) + .SetOnInsert(p => p.Interface, interfaceId) + .SetOnInsert(p => p.Destination, route?.Destination?.Address) + .SetOnInsert(p => p.Gateway, route?.Gateway?.Address) + .SetOnInsert(p => p.Mask, route?.Mask) + .Set(p => p.Update, date) + .Set(p => p.Batch, batch) + + .Set(p => p.Metric, route?.Metric); + + routeBulk.Add(new UpdateOneModel(routeFilter, routeUpdate) + { + IsUpsert = true + }); + } + } + } + + routeBulk.Add(new DeleteManyModel(Builders.Filter.And(new List> + { + Builders.Filter.Eq(x => x.Host, hostEntity.Id), + Builders.Filter.Ne(x => x.Batch, batch) + }))); + + var routeResult = await _database.HostInterfaceRoute().BulkWriteAsync(routeBulk, cancellationToken: cancellationToken); + } + } +} \ No newline at end of file diff --git a/src/Server/Insight.Server2/Network/Agent/Handlers/MainboardHandler.cs b/src/Server/Insight.Server2/Network/Agent/Handlers/MainboardHandler.cs new file mode 100644 index 0000000..95811a8 --- /dev/null +++ b/src/Server/Insight.Server2/Network/Agent/Handlers/MainboardHandler.cs @@ -0,0 +1,48 @@ +using Insight.Domain.Interfaces; +using Insight.Domain.Network; +using Insight.Domain.Network.Agent.Messages; +using Insight.Infrastructure.Entities; +using MongoDB.Driver; + +namespace Insight.Server.Network.Agent.Handlers +{ + public class MainboardHandler(IMongoDatabase database) : IMessageHandler + { + private readonly IMongoDatabase _database = database; + + public async ValueTask HandleAsync(AgentSession sender, TMessage message, CancellationToken cancellationToken) where TMessage : IMessage + { + switch (message) + { + case Mainboard mainboard: + await OnMainboardAsync(sender, mainboard, cancellationToken); + break; + } + } + + private async ValueTask OnMainboardAsync(AgentSession session, Mainboard mainboard, CancellationToken cancellationToken) + { + var agentEntity = await _database.Agent().Find(Builders.Filter.Eq(p => p.Id, session.Id)).FirstOrDefaultAsync(cancellationToken); + if (agentEntity is null) return; + + var hostEntity = await _database.Host().Find(Builders.Filter.Eq(p => p.Agent, agentEntity.Id)).FirstOrDefaultAsync(cancellationToken); + if (hostEntity is null) return; + + var date = DateTime.Now; + + await _database.HostMainboard().UpdateOneAsync(p => p.Host == hostEntity.Id, Builders.Update + .SetOnInsert(p => p.Insert, date) + .SetOnInsert(p => p.Host, hostEntity.Id) + .Set(p => p.Update, date) + .Set(p => p.Name, mainboard?.Manufacturer) + .Set(p => p.Name, mainboard?.Model) + .Set(p => p.Serial, mainboard?.Serial) + .Set(p => p.Bios, mainboard?.BiosManufacturer) + .Set(p => p.Version, mainboard?.BiosVersion) + .Set(p => p.Date, mainboard?.BiosDate), new UpdateOptions + { + IsUpsert = true + }, cancellationToken); + } + } +} diff --git a/src/Server/Insight.Server2/Network/Agent/Handlers/MemoryHandler.cs b/src/Server/Insight.Server2/Network/Agent/Handlers/MemoryHandler.cs new file mode 100644 index 0000000..97e1ae5 --- /dev/null +++ b/src/Server/Insight.Server2/Network/Agent/Handlers/MemoryHandler.cs @@ -0,0 +1,79 @@ +using Insight.Domain.Interfaces; +using Insight.Domain.Network; +using Insight.Domain.Network.Agent.Messages; +using Insight.Infrastructure.Entities; +using MongoDB.Bson; +using MongoDB.Driver; + +namespace Insight.Server.Network.Agent.Handlers; + +public class MemoryHandler(IMongoDatabase database) : IMessageHandler +{ + private readonly IMongoDatabase _database = database; + + public async ValueTask HandleAsync(AgentSession sender, TMessage message, CancellationToken cancellationToken) where TMessage : IMessage + { + switch (message) + { + case Collection memory: + await OnMemoryAsync(sender, memory, cancellationToken); + break; + } + } + + private async ValueTask OnMemoryAsync(AgentSession session, List memory, CancellationToken cancellationToken) + { + var agentEntity = await _database.Agent().Find(Builders.Filter.Eq(p => p.Id, session.Id)).FirstOrDefaultAsync(cancellationToken); + if (agentEntity is null) return; + + var hostEntity = await _database.Host().Find(Builders.Filter.Eq(p => p.Agent, agentEntity.Id)).FirstOrDefaultAsync(cancellationToken); + if (hostEntity is null) return; + + var batch = ObjectId.GenerateNewId().ToString(); + var date = DateTime.Now; + + var bulk = new List>(); + + if (memory is not null && memory.Count != 0) + { + foreach (var mem in memory) + { + var filterDefinition = Builders.Filter.And(new List> + { + Builders.Filter.Eq(x => x.Host, hostEntity.Id), + Builders.Filter.Eq(x => x.Index, mem.Index) + }); + + var updateDefinition = Builders.Update + .SetOnInsert(p => p.Insert, date) + .SetOnInsert(p => p.Host, hostEntity.Id) + .SetOnInsert(p => p.Index, mem.Index) + .Set(p => p.Update, date) + .Set(p => p.Batch, batch) + .Set(p => p.Company, mem.Manufacturer) + .Set(p => p.Name, mem.Model) + .Set(p => p.Tag, mem.Tag) + .Set(p => p.Location, mem.Location) + .Set(p => p.Serial, mem.Serial) + .Set(p => p.Capacity, mem.Capacity) + .Set(p => p.Clock, mem.Speed) + .Set(p => p.CurrentClock, mem.ConfiguredSpeed) + .Set(p => p.Voltage, mem.Voltage) + .Set(p => p.CurrentVoltage, mem.ConfiguredVoltage); + + bulk.Add(new UpdateOneModel(filterDefinition, updateDefinition) + { + IsUpsert = true + }); + } + } + + bulk.Add(new DeleteManyModel(Builders.Filter.And(new List> + { + Builders.Filter.Eq(x => x.Host, hostEntity.Id), + Builders.Filter.Ne(x => x.Batch, batch) + }))); + + var result = await _database.HostMemory().BulkWriteAsync(bulk, cancellationToken: cancellationToken); + } +} \ No newline at end of file diff --git a/src/Server/Insight.Server2/Network/Agent/Handlers/OperationSystemHandler.cs b/src/Server/Insight.Server2/Network/Agent/Handlers/OperationSystemHandler.cs new file mode 100644 index 0000000..3fb0d47 --- /dev/null +++ b/src/Server/Insight.Server2/Network/Agent/Handlers/OperationSystemHandler.cs @@ -0,0 +1,47 @@ +using Insight.Domain.Interfaces; +using Insight.Domain.Network; +using Insight.Domain.Network.Agent.Messages; +using Insight.Infrastructure.Entities; +using MongoDB.Driver; + +namespace Insight.Server.Network.Agent.Handlers; + +public class OperationSystemHandler(IMongoDatabase database) : IMessageHandler +{ + private readonly IMongoDatabase _database = database; + + public async ValueTask HandleAsync(AgentSession sender, TMessage message, CancellationToken cancellationToken) where TMessage : IMessage + { + switch (message) + { + case OperationSystem os: + await OnOperationSystemAsync(sender, os, cancellationToken); + break; + } + } + + private async ValueTask OnOperationSystemAsync(AgentSession session, OperationSystem operatingSystem, CancellationToken cancellationToken) + { + var agentEntity = await _database.Agent().Find(Builders.Filter.Eq(p => p.Id, session.Id)).FirstOrDefaultAsync(cancellationToken); + if (agentEntity is null) return; + + var hostEntity = await _database.Host().Find(Builders.Filter.Eq(p => p.Agent, agentEntity.Id)).FirstOrDefaultAsync(cancellationToken); + if (hostEntity is null) return; + + var date = DateTime.Now; + + await _database.HostOs().UpdateOneAsync(p => p.Host == hostEntity.Id, Builders.Update + .SetOnInsert(p => p.Insert, date) + .SetOnInsert(p => p.Host, hostEntity.Id) + .Set(p => p.Update, date) + .Set(p => p.Name, operatingSystem.Name) + .Set(p => p.Version, operatingSystem.Version) + .Set(p => p.Architecture, operatingSystem.Architecture.ToString()) + .Set(p => p.SerialNumber, operatingSystem.SerialNumber) + .Set(p => p.Virtual, operatingSystem.Virtual) + .Set(p => p.Installed, operatingSystem.InstallDate), new UpdateOptions + { + IsUpsert = true + }, cancellationToken); + } +} \ No newline at end of file diff --git a/src/Server/Insight.Server2/Network/Agent/Handlers/PrinterHandler.cs b/src/Server/Insight.Server2/Network/Agent/Handlers/PrinterHandler.cs new file mode 100644 index 0000000..e57f0c0 --- /dev/null +++ b/src/Server/Insight.Server2/Network/Agent/Handlers/PrinterHandler.cs @@ -0,0 +1,72 @@ +using Insight.Domain.Interfaces; +using Insight.Domain.Network; +using Insight.Domain.Network.Agent.Messages; +using Insight.Infrastructure.Entities; +using MongoDB.Bson; +using MongoDB.Driver; + +namespace Insight.Server.Network.Agent.Handlers; + +public class PrinterHandler(IMongoDatabase database) : IMessageHandler +{ + private readonly IMongoDatabase _database = database; + + public async ValueTask HandleAsync(AgentSession sender, TMessage message, CancellationToken cancellationToken) where TMessage : IMessage + { + switch (message) + { + case Collection printers: + await OnPrintersAsync(sender, printers, cancellationToken); + break; + } + } + + private async ValueTask OnPrintersAsync(AgentSession session, List printers, CancellationToken cancellationToken) + { + var agentEntity = await _database.Agent().Find(Builders.Filter.Eq(p => p.Id, session.Id)).FirstOrDefaultAsync(cancellationToken); + if (agentEntity is null) return; + + var hostEntity = await _database.Host().Find(Builders.Filter.Eq(p => p.Agent, agentEntity.Id)).FirstOrDefaultAsync(cancellationToken); + if (hostEntity is null) return; + + var batch = ObjectId.GenerateNewId().ToString(); + var date = DateTime.Now; + + var bulk = new List>(); + + if (printers is not null && printers.Count != 0) + { + foreach (var printer in printers) + { + var filterDefinition = Builders.Filter.And(new List> + { + Builders.Filter.Eq(x => x.Host, hostEntity.Id), + Builders.Filter.Eq(x => x.Name, printer.Name) + }); + + var updateDefinition = Builders.Update + .SetOnInsert(p => p.Insert, date) + .SetOnInsert(p => p.Host, hostEntity.Id) + .SetOnInsert(p => p.Name, printer.Name) + .Set(p => p.Update, date) + .Set(p => p.Batch, batch) + .Set(p => p.Port, printer.Port) + .Set(p => p.Location, printer.Location) + .Set(p => p.Comment, printer.Comment); + + bulk.Add(new UpdateOneModel(filterDefinition, updateDefinition) + { + IsUpsert = true + }); + } + } + + bulk.Add(new DeleteManyModel(Builders.Filter.And(new List> + { + Builders.Filter.Eq(x => x.Host, hostEntity.Id), + Builders.Filter.Ne(x => x.Batch, batch) + }))); + + var result = await _database.HostPrinter().BulkWriteAsync(bulk, cancellationToken: cancellationToken); + } +} \ No newline at end of file diff --git a/src/Server/Insight.Server2/Network/Agent/Handlers/ProcessorHandler.cs b/src/Server/Insight.Server2/Network/Agent/Handlers/ProcessorHandler.cs new file mode 100644 index 0000000..d11ed9c --- /dev/null +++ b/src/Server/Insight.Server2/Network/Agent/Handlers/ProcessorHandler.cs @@ -0,0 +1,83 @@ +using Insight.Domain.Interfaces; +using Insight.Domain.Network; +using Insight.Domain.Network.Agent.Messages; +using Insight.Infrastructure.Entities; +using MongoDB.Bson; +using MongoDB.Driver; + +namespace Insight.Server.Network.Agent.Handlers; + +public class ProcessorHandler(IMongoDatabase database) : IMessageHandler +{ + private readonly IMongoDatabase _database = database; + + public async ValueTask HandleAsync(AgentSession sender, TMessage message, CancellationToken cancellationToken) where TMessage : IMessage + { + switch (message) + { + case Collection processors: + await OnProcessorsAsync(sender, processors, cancellationToken); + break; + } + } + + private async ValueTask OnProcessorsAsync(AgentSession session, List processors, CancellationToken cancellationToken) + { + var agentEntity = await _database.Agent().Find(Builders.Filter.Eq(p => p.Id, session.Id)).FirstOrDefaultAsync(cancellationToken); + if (agentEntity is null) return; + + var hostEntity = await _database.Host().Find(Builders.Filter.Eq(p => p.Agent, agentEntity.Id)).FirstOrDefaultAsync(cancellationToken); + if (hostEntity is null) return; + + var batch = ObjectId.GenerateNewId().ToString(); + var date = DateTime.Now; + + var bulk = new List>(); + + if (processors is not null && processors.Count != 0) + { + foreach (var processor in processors) + { + var filterDefinition = Builders.Filter.And(new List> + { + Builders.Filter.Eq(x => x.Host, hostEntity.Id), + Builders.Filter.Eq(x => x.Index, processor.Index) + }); + + var updateDefinition = Builders.Update + .SetOnInsert(p => p.Insert, date) + .SetOnInsert(p => p.Host, hostEntity.Id) + .SetOnInsert(p => p.Index, processor.Index) + .Set(p => p.Update, date) + .Set(p => p.Batch, batch) + .Set(p => p.Company, processor.Manufacturer) + .Set(p => p.Name, processor.Name) + .Set(p => p.Socket, processor.Socket) + .Set(p => p.Serial, processor.SerialNumber) + .Set(p => p.Version, processor.Version) + .Set(p => p.Cores, processor.Cores) + .Set(p => p.LogicalCores, processor.LogicalCores) + .Set(p => p.Clock, processor.MaxSpeed) + .Set(p => p.CurrentClock, processor.CurrentSpeed) + .Set(p => p.L1Size, processor.L1Size) + .Set(p => p.L2Size, processor.L2Size) + .Set(p => p.L3Size, processor.L3Size) + .Set(p => p.Virtualization, processor.Virtualization) + .Set(p => p.PNP, processor.DeviceId); + + bulk.Add(new UpdateOneModel(filterDefinition, updateDefinition) + { + IsUpsert = true + }); + } + } + + bulk.Add(new DeleteManyModel(Builders.Filter.And(new List> + { + Builders.Filter.Eq(x => x.Host, hostEntity.Id), + Builders.Filter.Ne(x => x.Batch, batch) + }))); + + var result = await _database.HostProcessor().BulkWriteAsync(bulk, cancellationToken: cancellationToken); + } +} \ No newline at end of file diff --git a/src/Server/Insight.Server2/Network/Agent/Handlers/ServiceHandler.cs b/src/Server/Insight.Server2/Network/Agent/Handlers/ServiceHandler.cs new file mode 100644 index 0000000..be6d45a --- /dev/null +++ b/src/Server/Insight.Server2/Network/Agent/Handlers/ServiceHandler.cs @@ -0,0 +1,77 @@ +using Insight.Domain.Interfaces; +using Insight.Domain.Network; +using Insight.Domain.Network.Agent.Messages; +using Insight.Infrastructure.Entities; +using MongoDB.Bson; +using MongoDB.Driver; + +namespace Insight.Server.Network.Agent.Handlers; + +public class ServiceHandler(IMongoDatabase database) : IMessageHandler +{ + private readonly IMongoDatabase _database = database; + + public async ValueTask HandleAsync(AgentSession sender, TMessage message, CancellationToken cancellationToken) where TMessage : IMessage + { + switch (message) + { + case Collection services: + await OnServicesAsync(sender, services, cancellationToken); + break; + } + } + + private async ValueTask OnServicesAsync(AgentSession session, List services, CancellationToken cancellationToken) + { + var agentEntity = await _database.Agent().Find(Builders.Filter.Eq(p => p.Id, session.Id)).FirstOrDefaultAsync(cancellationToken); + if (agentEntity is null) return; + + var hostEntity = await _database.Host().Find(Builders.Filter.Eq(p => p.Agent, agentEntity.Id)).FirstOrDefaultAsync(cancellationToken); + if (hostEntity is null) return; + + var batch = ObjectId.GenerateNewId().ToString(); + var date = DateTime.Now; + + var bulk = new List>(); + + if (services is not null && services.Count != 0) + { + foreach (var service in services) + { + var filterDefinition = Builders.Filter.And(new List> + { + Builders.Filter.Eq(x => x.Host, hostEntity.Id), + Builders.Filter.Eq(x => x.Name, service.Name) + }); + + var updateDefinition = Builders.Update + .SetOnInsert(p => p.Insert, date) + .SetOnInsert(p => p.Host, hostEntity.Id) + .SetOnInsert(p => p.Name, service.Name) + .Set(p => p.Update, date) + .Set(p => p.Batch, batch) + .Set(p => p.DisplayName, service.Display) + .Set(p => p.Description, service.Description) + .Set(p => p.StartMode, service.StartMode.ToString()) + .Set(p => p.State, service.Status.ToString()) + .Set(p => p.ProcessId, service.ProcessId) + .Set(p => p.Delay, service.Delay) + .Set(p => p.Path, service.PathName) + .Set(p => p.Account, service.Account); + + bulk.Add(new UpdateOneModel(filterDefinition, updateDefinition) + { + IsUpsert = true + }); + } + } + + bulk.Add(new DeleteManyModel(Builders.Filter.And(new List> + { + Builders.Filter.Eq(x => x.Host, hostEntity.Id), + Builders.Filter.Ne(x => x.Batch, batch) + }))); + + var result = await _database.HostService().BulkWriteAsync(bulk, cancellationToken: cancellationToken); + } +} \ No newline at end of file diff --git a/src/Server/Insight.Server2/Network/Agent/Handlers/SessionHandler.cs b/src/Server/Insight.Server2/Network/Agent/Handlers/SessionHandler.cs new file mode 100644 index 0000000..b1ecdc6 --- /dev/null +++ b/src/Server/Insight.Server2/Network/Agent/Handlers/SessionHandler.cs @@ -0,0 +1,73 @@ +using Insight.Domain.Interfaces; +using Insight.Domain.Network; +using Insight.Domain.Network.Agent.Messages; +using Insight.Infrastructure.Entities; +using MongoDB.Bson; +using MongoDB.Driver; + +namespace Insight.Server.Network.Agent.Handlers; + +public class SessionHandler(IMongoDatabase database) : IMessageHandler +{ + private readonly IMongoDatabase _database = database; + + public async ValueTask HandleAsync(AgentSession sender, TMessage message, CancellationToken cancellationToken) where TMessage : IMessage + { + switch (message) + { + case Collection sessions: + await OnSessionsAsync(sender, sessions, cancellationToken); + break; + } + } + + private async ValueTask OnSessionsAsync(AgentSession session, List sessions, CancellationToken cancellationToken) + { + var agentEntity = await _database.Agent().Find(Builders.Filter.Eq(p => p.Id, session.Id)).FirstOrDefaultAsync(cancellationToken); + if (agentEntity is null) return; + + var hostEntity = await _database.Host().Find(Builders.Filter.Eq(p => p.Agent, agentEntity.Id)).FirstOrDefaultAsync(cancellationToken); + if (hostEntity is null) return; + + var batch = ObjectId.GenerateNewId().ToString(); + var date = DateTime.Now; + + var bulk = new List>(); + + if (sessions is not null && sessions.Count != 0) + { + foreach (var sess in sessions) + { + var filterDefinition = Builders.Filter.And(new List> + { + Builders.Filter.Eq(x => x.Host, hostEntity.Id), + Builders.Filter.Eq(x => x.Sid, sess.Sid) + }); + + var updateDefinition = Builders.Update + .SetOnInsert(p => p.Insert, date) + .SetOnInsert(p => p.Host, hostEntity.Id) + .SetOnInsert(p => p.Sid, sess.Sid) + .Set(p => p.Update, date) + .Set(p => p.Batch, batch) + .Set(p => p.User, sess.User) + .Set(p => p.Remote, sess.Remote) + .Set(p => p.Type, sess.Type) + .Set(p => p.State, sess.Status); + + bulk.Add(new UpdateOneModel(filterDefinition, updateDefinition) + { + IsUpsert = true + }); + } + } + + bulk.Add(new DeleteManyModel(Builders.Filter.And(new List> + { + Builders.Filter.Eq(x => x.Host, hostEntity.Id), + Builders.Filter.Ne(x => x.Batch, batch) + }))); + + var result = await _database.HostSession().BulkWriteAsync(bulk, cancellationToken: cancellationToken); + } +} \ No newline at end of file diff --git a/src/Server/Insight.Server2/Network/Agent/Handlers/SoftwareHandler.cs b/src/Server/Insight.Server2/Network/Agent/Handlers/SoftwareHandler.cs new file mode 100644 index 0000000..acb7d0e --- /dev/null +++ b/src/Server/Insight.Server2/Network/Agent/Handlers/SoftwareHandler.cs @@ -0,0 +1,74 @@ +using Insight.Domain.Interfaces; +using Insight.Domain.Network; +using Insight.Domain.Network.Agent.Messages; +using Insight.Infrastructure.Entities; +using MongoDB.Bson; +using MongoDB.Driver; + +namespace Insight.Server.Network.Agent.Handlers; + +public class SoftwareHandler(IMongoDatabase database) : IMessageHandler +{ + private readonly IMongoDatabase _database = database; + + public async ValueTask HandleAsync(AgentSession sender, TMessage message, CancellationToken cancellationToken) where TMessage : IMessage + { + switch (message) + { + case Collection applications: + await OnApplicationsAsync(sender, applications, cancellationToken); + break; + } + } + + private async ValueTask OnApplicationsAsync(AgentSession session, List applications, CancellationToken cancellationToken) + { + var agentEntity = await _database.Agent().Find(Builders.Filter.Eq(p => p.Id, session.Id)).FirstOrDefaultAsync(cancellationToken); + if (agentEntity is null) return; + + var hostEntity = await _database.Host().Find(Builders.Filter.Eq(p => p.Agent, agentEntity.Id)).FirstOrDefaultAsync(cancellationToken); + if (hostEntity is null) return; + + var batch = ObjectId.GenerateNewId().ToString(); + var date = DateTime.Now; + + var bulk = new List>(); + + if (applications is not null && applications.Count != 0) + { + foreach (var app in applications) + { + var filterDefinition = Builders.Filter.And(new List> + { + Builders.Filter.Eq(x => x.Host, hostEntity.Id), + Builders.Filter.Eq(x => x.Name, app.Name), + Builders.Filter.Eq(x => x.Architecture, app.Architecture?.ToString()) + }); + + var updateDefinition = Builders.Update + .SetOnInsert(p => p.Insert, date) + .SetOnInsert(p => p.Host, hostEntity.Id) + .SetOnInsert(p => p.Name, app.Name) + .SetOnInsert(p => p.Architecture, app.Architecture?.ToString()) + .Set(p => p.Update, date) + .Set(p => p.Batch, batch) + .Set(p => p.Company, app.Publisher) + .Set(p => p.Version, app.Version) + .Set(p => p.InstallDate, app.InstallDate); + + bulk.Add(new UpdateOneModel(filterDefinition, updateDefinition) + { + IsUpsert = true + }); + } + } + + bulk.Add(new DeleteManyModel(Builders.Filter.And(new List> + { + Builders.Filter.Eq(x => x.Host, hostEntity.Id), + Builders.Filter.Ne(x => x.Batch, batch) + }))); + + var result = await _database.HostApplication().BulkWriteAsync(bulk, cancellationToken: cancellationToken); + } +} \ No newline at end of file diff --git a/src/Server/Insight.Server2/Network/Agent/Handlers/StoragePoolHandler.cs b/src/Server/Insight.Server2/Network/Agent/Handlers/StoragePoolHandler.cs new file mode 100644 index 0000000..c443248 --- /dev/null +++ b/src/Server/Insight.Server2/Network/Agent/Handlers/StoragePoolHandler.cs @@ -0,0 +1,243 @@ +using Insight.Domain.Interfaces; +using Insight.Domain.Network; +using Insight.Domain.Network.Agent.Messages; +using Insight.Infrastructure.Entities; +using MongoDB.Bson; +using MongoDB.Driver; + +namespace Insight.Server.Network.Agent.Handlers; + +public class StoragePoolHandler(IMongoDatabase database) : IMessageHandler +{ + private readonly IMongoDatabase _database = database; + + public async ValueTask HandleAsync(AgentSession sender, TMessage message, CancellationToken cancellationToken) where TMessage : IMessage + { + switch (message) + { + case Collection storagePools: + await OnStoragePoolsAsync(sender, storagePools, cancellationToken); + break; + } + } + + private async ValueTask OnStoragePoolsAsync(AgentSession session, List? storagePools, CancellationToken cancellationToken) + { + var agentEntity = await _database.Agent().Find(Builders.Filter.Eq(p => p.Id, session.Id)).FirstOrDefaultAsync(cancellationToken); + if (agentEntity is null) return; + + var hostEntity = await _database.Host().Find(Builders.Filter.Eq(p => p.Agent, agentEntity.Id)).FirstOrDefaultAsync(cancellationToken); + if (hostEntity is null) return; + + var batch = ObjectId.GenerateNewId().ToString(); + var date = DateTime.Now; + + // storagepools + + if (storagePools is not null && storagePools.Count != 0) + { + var storagepoolBulk = new List>(); + + foreach (var storagePool in storagePools) + { + var storagePoolFilter = Builders.Filter.And(new List> + { + Builders.Filter.Eq(x => x.Host, hostEntity.Id), + Builders.Filter.Eq(x => x.UniqueId, storagePool.UniqueId) + }); + + List? states = null; + + if (storagePool.States is not null) + { + states = []; + + foreach (var state in storagePool.States) + states.Add(state.ToString()); + } + + var storagePoolUpdate = Builders.Update + .SetOnInsert(p => p.Insert, date) + .SetOnInsert(p => p.Host, hostEntity.Id) + .SetOnInsert(p => p.UniqueId, storagePool.UniqueId) + .Set(p => p.Update, date) + .Set(p => p.Batch, batch) + + .Set(p => p.Name, storagePool.FriendlyName) + .Set(p => p.Health, storagePool.Health?.ToString()) + .Set(p => p.Resiliency, storagePool.Resiliency) + .Set(p => p.Primordial, storagePool.IsPrimordial) + .Set(p => p.ReadOnly, storagePool.IsReadOnly) + .Set(p => p.Clustered, storagePool.IsClustered) + .Set(p => p.Size, storagePool.Size) + .Set(p => p.AllocatedSize, storagePool.AllocatedSize) + .Set(p => p.SectorSize, storagePool.SectorSize) + .Set(p => p.States, states); + + storagepoolBulk.Add(new UpdateOneModel(storagePoolFilter, storagePoolUpdate) + { + IsUpsert = true + }); + } + + storagepoolBulk.Add(new DeleteManyModel(Builders.Filter.And(new List> + { + Builders.Filter.Eq(x => x.Host, hostEntity.Id), + Builders.Filter.Ne(x => x.Batch, batch) + }))); + + var storagePoolResult = await _database.HostStoragePool().BulkWriteAsync(storagepoolBulk, cancellationToken: cancellationToken); + } + + // physicaldisks + + if (storagePools is not null && storagePools.Count != 0) + { + var physicalDiskBulk = new List>(); + + foreach (var storagePool in storagePools) + { + var storagePoolId = await _database.HostStoragePool() + .Find(p => p.Host == hostEntity.Id && p.UniqueId == storagePool.UniqueId) + .Project(p => p.Id) + .FirstOrDefaultAsync(cancellationToken: default); + + if (storagePool.PhysicalDisks is not null && storagePool.PhysicalDisks.Count != 0) + { + foreach (var physicalDisk in storagePool.PhysicalDisks) + { + var physicalDiskFilter = Builders.Filter.And(new List> + { + Builders.Filter.Eq(x => x.Host, hostEntity.Id), + Builders.Filter.Eq(x => x.StoragePool, storagePoolId), + Builders.Filter.Eq(x => x.UniqueId, physicalDisk.UniqueId) + }); + + List? states = null; + + if (physicalDisk.States is not null) + { + states = []; + + foreach (var state in physicalDisk.States) + states.Add(state.ToString()); + } + + var physicalDiskUpdate = Builders.Update + .SetOnInsert(p => p.Insert, date) + .SetOnInsert(p => p.Host, hostEntity.Id) + .SetOnInsert(p => p.StoragePool, storagePoolId) + .SetOnInsert(p => p.UniqueId, physicalDisk.UniqueId) + .Set(p => p.Update, date) + .Set(p => p.Batch, batch) + + .Set(p => p.DeviceId, physicalDisk.DeviceId) + .Set(p => p.Name, physicalDisk.FriendlyName) + .Set(p => p.Manufacturer, physicalDisk.Manufacturer) + .Set(p => p.Model, physicalDisk.Model) + .Set(p => p.Media, physicalDisk.MediaType.ToString()) + .Set(p => p.Bus, physicalDisk.BusType.ToString()) + .Set(p => p.Health, physicalDisk.Health.ToString()) + .Set(p => p.Usage, physicalDisk.Usage) + .Set(p => p.Location, physicalDisk.PhysicalLocation) + .Set(p => p.Serial, physicalDisk.SerialNumber) + .Set(p => p.Firmware, physicalDisk.FirmwareVersion) + .Set(p => p.Size, physicalDisk.Size) + .Set(p => p.AllocatedSize, physicalDisk.AllocatedSize) + .Set(p => p.Footprint, physicalDisk.VirtualDiskFootprint) + .Set(p => p.LogicalSectorSize, physicalDisk.LogicalSectorSize) + .Set(p => p.PhysicalSectorSize, physicalDisk.PhysicalSectorSize) + .Set(p => p.States, states); + + physicalDiskBulk.Add(new UpdateOneModel(physicalDiskFilter, physicalDiskUpdate) + { + IsUpsert = true + }); + } + } + } + + physicalDiskBulk.Add(new DeleteManyModel(Builders.Filter.And(new List> + { + Builders.Filter.Eq(x => x.Host, hostEntity.Id), + Builders.Filter.Ne(x => x.Batch, batch) + }))); + + var physicalDiskResult = await _database.HostStoragePoolPhysicalDisk().BulkWriteAsync(physicalDiskBulk, cancellationToken: cancellationToken); + } + + // virtual disks + + if (storagePools is not null && storagePools.Count != 0) + { + var virtualDiskBulk = new List>(); + + foreach (var storagePool in storagePools) + { + if (storagePool.VirtualDisks is not null && storagePool.VirtualDisks.Count != 0) + { + foreach (var virtualDisk in storagePool.VirtualDisks) + { + var storagePoolId = await _database.HostStoragePool() + .Find(p => p.Host == hostEntity.Id && p.UniqueId == storagePool.UniqueId) + .Project(p => p.Id) + .FirstOrDefaultAsync(cancellationToken: default); + + var virtualDiskFilter = Builders.Filter.And(new List> + { + Builders.Filter.Eq(x => x.Host, hostEntity.Id), + Builders.Filter.Eq(x => x.StoragePool, storagePoolId), + Builders.Filter.Eq(x => x.UniqueId, virtualDisk.UniqueId) + }); + + List? states = null; + + if (virtualDisk.States is not null) + { + states = []; + + foreach (var state in virtualDisk.States) + states.Add(state.ToString()); + } + + var virtualDiskUpdate = Builders.Update + .SetOnInsert(p => p.Insert, date) + .SetOnInsert(p => p.Host, hostEntity.Id) + .SetOnInsert(p => p.StoragePool, storagePoolId) + .SetOnInsert(p => p.UniqueId, virtualDisk.UniqueId) + .Set(p => p.Update, date) + .Set(p => p.Batch, batch) + + .Set(p => p.Name, virtualDisk.FriendlyName) + .Set(p => p.Health, virtualDisk.Health.ToString()) + .Set(p => p.Access, virtualDisk.AccessType.ToString()) + .Set(p => p.Provisioning, virtualDisk.ProvisioningType.ToString()) + .Set(p => p.PhysicalRedundancy, virtualDisk.PhysicalDiskRedundancy) + .Set(p => p.Resiliency, virtualDisk.ResiliencySettingName) + .Set(p => p.Deduplication, virtualDisk.Deduplication) + .Set(p => p.Snapshot, virtualDisk.IsSnapshot) + .Set(p => p.Size, virtualDisk.Size) + .Set(p => p.AllocatedSize, virtualDisk.AllocatedSize) + .Set(p => p.Footprint, virtualDisk.FootprintOnPool) + .Set(p => p.ReadCacheSize, virtualDisk.ReadCacheSize) + .Set(p => p.WriteCacheSize, virtualDisk.WriteCacheSize) + .Set(p => p.States, states); + + virtualDiskBulk.Add(new UpdateOneModel(virtualDiskFilter, virtualDiskUpdate) + { + IsUpsert = true + }); + } + } + } + + virtualDiskBulk.Add(new DeleteManyModel(Builders.Filter.And(new List> + { + Builders.Filter.Eq(x => x.Host, hostEntity.Id), + Builders.Filter.Ne(x => x.Batch, batch) + }))); + + var virtualDiskResult = await _database.HostStoragePoolVirtualDisk().BulkWriteAsync(virtualDiskBulk, cancellationToken: cancellationToken); + } + } +} \ No newline at end of file diff --git a/src/Server/Insight.Server2/Network/Agent/Handlers/SystemInfoHandler.cs b/src/Server/Insight.Server2/Network/Agent/Handlers/SystemInfoHandler.cs new file mode 100644 index 0000000..58a2e09 --- /dev/null +++ b/src/Server/Insight.Server2/Network/Agent/Handlers/SystemInfoHandler.cs @@ -0,0 +1,47 @@ +using Insight.Domain.Interfaces; +using Insight.Domain.Network; +using Insight.Domain.Network.Agent.Messages; +using Insight.Infrastructure.Entities; +using MongoDB.Driver; + +namespace Insight.Server.Network.Agent.Handlers; + +public class SystemInfoHandler(IMongoDatabase database) : IMessageHandler +{ + private readonly IMongoDatabase _database = database; + + public async ValueTask HandleAsync(AgentSession sender, TMessage message, CancellationToken cancellationToken) where TMessage : IMessage + { + switch (message) + { + case SystemInfo systemInfo: + await OnSystemInfoAsync(sender, systemInfo, cancellationToken); + break; + } + } + + private async ValueTask OnSystemInfoAsync(AgentSession session, SystemInfo? system, CancellationToken cancellationToken) + { + var agentEntity = await _database.Agent().Find(Builders.Filter.Eq(p => p.Id, session?.Id)).FirstOrDefaultAsync(cancellationToken); + if (agentEntity is null) return; + + var hostEntity = await _database.Host().Find(Builders.Filter.Eq(p => p.Agent, agentEntity?.Id)).FirstOrDefaultAsync(cancellationToken); + if (hostEntity is null) return; + + var date = DateTime.Now; + + if (hostEntity.Id is null) return; + + await _database.HostSystem().UpdateOneAsync(p => p.Host == hostEntity.Id, Builders.Update + .SetOnInsert(p => p.Insert, date) + .SetOnInsert(p => p.Host, hostEntity.Id) + .Set(p => p.Update, date) + .Set(p => p.BootUpTime, system?.LastBootUpTime) + .Set(p => p.LocalTime, system?.LocalDateTime) + .Set(p => p.Processes, system?.Processes) + .Set(p => p.License, system?.License), new UpdateOptions + { + IsUpsert = true + }, cancellationToken); + } +} \ No newline at end of file diff --git a/src/Server/Insight.Server2/Network/Agent/Handlers/TrapHandler.cs b/src/Server/Insight.Server2/Network/Agent/Handlers/TrapHandler.cs new file mode 100644 index 0000000..0ef5449 --- /dev/null +++ b/src/Server/Insight.Server2/Network/Agent/Handlers/TrapHandler.cs @@ -0,0 +1,264 @@ +using Insight.Domain.Enums; +using Insight.Domain.Interfaces; +using Insight.Domain.Network; +using Insight.Domain.Network.Agent.Messages; +using Insight.Infrastructure.Entities; +using Insight.Server.Models; +using MongoDB.Driver; +using System.Text; +using System.Text.RegularExpressions; +using static Insight.Server.Models.MonitorMessage; + +namespace Insight.Server.Network.Agent.Handlers; + +public partial class TrapHandler(IMongoDatabase database) : IMessageHandler +{ + private readonly IMongoDatabase _database = database; + + public async ValueTask HandleAsync(AgentSession sender, TMessage message, CancellationToken cancellationToken) where TMessage : IMessage + { + switch (message) + { + case Trap trap: + await OnTrapAsync(sender, trap, cancellationToken); + break; + } + } + + private async ValueTask OnTrapAsync(AgentSession session, Trap? trap, CancellationToken cancellationToken) + { + if (trap is null) return; + + var agentEntity = await _database.Agent().Find(Builders.Filter.Eq(p => p.Id, session.Id)).FirstOrDefaultAsync(cancellationToken); + if (agentEntity is null) return; + + var hostEntity = await _database.Host().Find(Builders.Filter.Eq(p => p.Agent, agentEntity.Id)).FirstOrDefaultAsync(cancellationToken); + if (hostEntity is null) return; + + if (TryParse(trap, out var monitoring) is false) + { + //_logger.LogWarning($"Failed to parse"); + } + + // insert monitoring log + var monitoringLog = new HostLogMonitoringEntity + { + Host = hostEntity.Id, + Insert = DateTime.Now, + Timestamp = monitoring?.Timestamp, + Category = monitoring?.Category?.ToString(), + Status = monitoring?.Status?.ToString(), + Hostname = monitoring?.Hostname, + Task = monitoring?.Subject, + Message = monitoring?.Message, + Dispatch = DispatchEnum.Pending.ToString() + }; + + await _database.HostLogMonitoring() + .InsertOneAsync(monitoringLog, cancellationToken: cancellationToken); + + // insert host log + var log = new HostLogEntity + { + Insert = monitoringLog.Insert, + Host = monitoringLog.Host, + Category = monitoringLog.Category, + Status = monitoringLog.Status, + Source = "Trap", + Message = monitoringLog?.Task, + Timestamp = monitoringLog?.Insert + }; + + if (monitoringLog?.Message is not null) log.Message += $"\n{monitoringLog.Message}"; + + await _database.HostLog() + .InsertOneAsync(log, cancellationToken: cancellationToken) + .ConfigureAwait(false); + } + + private static bool TryParse(Trap packet, out MonitorMessage? monitoring) + { + monitoring = null; + + if (packet is null || packet.Data is null || packet.Data.Count == 0) + return false; + + monitoring = new MonitorMessage + { + Community = packet.Community, + Category = CategoryEnum.Monitoring, + Endpoint = packet.Endpoint, + Timestamp = packet.Timestamp + }; + + if (Enum.TryParse(packet.Community, true, out var application)) + monitoring.Application = application; + + StatusEnum? status; + string? task; + string? message; + + switch (application) + { + case ApplicationEnum.Acronis: + monitoring.Application = ApplicationEnum.Acronis; + if (ParseAcronis(packet.Data, out status, out task, out message) is false) return false; + break; + case ApplicationEnum.Veeam: + monitoring.Application = ApplicationEnum.Veeam; + if (ParseVeeam(packet.Data, out status, out task, out message) is false) return false; + break; + case ApplicationEnum.QNAP: + monitoring.Application = ApplicationEnum.QNAP; + monitoring.Category = CategoryEnum.System; + monitoring.Hostname = packet.Hostname; + if (ParseQnap(packet.Data, out status, out task, out message) is false) return false; + break; + default: + return false; + } + + monitoring.Status = status; + monitoring.Subject = task; + monitoring.Message = message; + + return true; + } + + [GeneratedRegex(@"\s+")] + private static partial Regex AcronisRegex(); + + private static bool ParseAcronis(List> data, out StatusEnum? status, out string? task, out string? message) + { + status = data[0].Value?.ToLower() switch + { + "erfolgreich" => StatusEnum.Information, + "success" => StatusEnum.Information, + "information" => StatusEnum.Information, + "warnung" => StatusEnum.Warning, + "warning" => StatusEnum.Warning, + "fehler" => StatusEnum.Error, + "failed" => StatusEnum.Error, + "error" => StatusEnum.Error, + _ => null, + }; + + task = null; + message = null; + + var trim = data[1].Value?.Split(':', StringSplitOptions.None); + if (trim is null || trim.Length == 0) return false; + + task = trim[1].Split("'", StringSplitOptions.None)[1].Split("'")[0].Trim(); + message = trim[1].Split("' ", StringSplitOptions.None)[1].Trim(); + if (message is not null) return true; + + if (data[1].Value is not string val) return false; + + var content = AcronisRegex().Replace(val, ""); + var bytes = Enumerable.Range(0, content.Length) + .Where(x => x % 2 == 0) + .Select(x => Convert.ToByte(content.Substring(x, 2), 16)) + .ToArray(); + + content = Encoding.UTF8.GetString(bytes); + + trim = content.Split(':', StringSplitOptions.None); + if (trim is null || trim.Length == 0) return false; + + task = trim[1].Split("'", StringSplitOptions.None)[1].Split("'")[0].Trim(); + message = trim[1].Split("' ", StringSplitOptions.None)[1].Trim(); + return message is not null; + } + + private static bool ParseVeeam(List> data, out StatusEnum? status, out string? task, out string? message) + { + status = null; + task = null; + message = null; + + var parsed = false; + + try + { + var summary = false; + + if (Guid.TryParse(data[0].Value, out _)) + summary = true; + + if (data[1].Value?.ToLower() == "backup configuration job") + return false; + + status = (summary ? data[2].Value?.ToLower() : data[3].Value?.ToLower()) switch + { + "success" => StatusEnum.Information, + "warning" => StatusEnum.Warning, + "failed" => StatusEnum.Error, + "error" => StatusEnum.Error, + _ => null, + }; + + task = data[1].Value; + parsed = true; + } + catch (Exception) + { + + } + + if (parsed) return true; + return false; + } + + private static bool ParseQnap(List> data, out StatusEnum? status, out string? task, out string? message) + { + status = StatusEnum.Information; + task = null; + message = string.Empty; + + var parsed = false; + + try + { + var keywords = new Dictionary + { + { "power", StatusEnum.Information }, + { "rights", StatusEnum.Information }, + { "added", StatusEnum.Information }, + { "changed", StatusEnum.Information }, + { "password", StatusEnum.Information }, + { "firmware", StatusEnum.Information }, + { "restarting", StatusEnum.Information }, + { "detected", StatusEnum.Warning }, + { "external", StatusEnum.Warning }, + { "threshold", StatusEnum.Warning }, + { "file system", StatusEnum.Warning }, + { "raid", StatusEnum.Warning }, + { "full", StatusEnum.Error }, + { "failure", StatusEnum.Error }, + { "failed", StatusEnum.Error }, + { "resyncing", StatusEnum.Error }, + { "degraded", StatusEnum.Error }, + { "error", StatusEnum.Error }, + { "without error", StatusEnum.Information }, + { "ncsi", StatusEnum.Information } + }; + + foreach (var key in keywords) + if (Regex.IsMatch(string.Concat(data).ToLowerInvariant(), $@"\b{key.Key}\b")) + status = key.Value; + + foreach (var kv in data) + message += kv.Value; + + parsed = true; + } + catch (Exception) + { + //_logger.LogError("{ex}", ex); + } + + if (parsed) return true; + return false; + } +} \ No newline at end of file diff --git a/src/Server/Insight.Server2/Network/Agent/Handlers/UpdateHandler.cs b/src/Server/Insight.Server2/Network/Agent/Handlers/UpdateHandler.cs new file mode 100644 index 0000000..9185b26 --- /dev/null +++ b/src/Server/Insight.Server2/Network/Agent/Handlers/UpdateHandler.cs @@ -0,0 +1,117 @@ +using Insight.Domain.Interfaces; +using Insight.Domain.Network; +using Insight.Domain.Network.Agent.Messages; +using Insight.Infrastructure.Entities; +using MongoDB.Bson; +using MongoDB.Driver; + +namespace Insight.Server.Network.Agent.Handlers; + +public class UpdateHandler(IMongoDatabase database) : IMessageHandler +{ + private readonly IMongoDatabase _database = database; + + public async ValueTask HandleAsync(AgentSession sender, TMessage message, CancellationToken cancellationToken) where TMessage : IMessage + { + switch (message) + { + case UpdateCollection updates: + await OnUpdatesAsync(sender, updates, cancellationToken); + break; + } + } + + private async ValueTask OnUpdatesAsync(AgentSession session, UpdateCollection updates, CancellationToken cancellationToken) + { + var agentEntity = await _database.Agent().Find(Builders.Filter.Eq(p => p.Id, session.Id)).FirstOrDefaultAsync(cancellationToken); + if (agentEntity is null) return; + + var hostEntity = await _database.Host().Find(Builders.Filter.Eq(p => p.Agent, agentEntity.Id)).FirstOrDefaultAsync(cancellationToken); + if (hostEntity is null) return; + + var batch = ObjectId.GenerateNewId().ToString(); + var date = DateTime.Now; + + var bulk = new List>(); + + if (updates is not null) + { + if (updates.Installed is not null && updates.Installed.Count != 0) + { + foreach (var update in updates.Installed) + { + var filterDefinition = Builders.Filter.And(new List> + { + Builders.Filter.Eq(x => x.Host, hostEntity.Id), + Builders.Filter.Eq(x => x.Serial, update.Id) + }); + + var updateDefinition = Builders.Update + .SetOnInsert(p => p.Insert, date) + .SetOnInsert(p => p.Host, hostEntity.Id) + .SetOnInsert(p => p.Serial, update.Id) + .Set(p => p.Update, date) + .Set(p => p.Batch, batch) + + .Set(p => p.Pending, false) + .Set(p => p.Name, update.Name) + .Set(p => p.Description, update.Description) + .Set(p => p.SupportUrl, update.SupportUrl) + .Set(p => p.Result, update.Result.ToString()) + .Set(p => p.Date, update.Date); + + bulk.Add(new UpdateOneModel(filterDefinition, updateDefinition) + { + IsUpsert = true + }); + } + } + + if (updates.Pending is not null && updates.Pending.Count != 0) + { + foreach (var update in updates.Pending) + { + var filterDefinition = Builders.Filter.And(new List> + { + Builders.Filter.Eq(x => x.Host, hostEntity.Id), + Builders.Filter.Eq(x => x.Serial, update.Id) + }); + + var updateDefinition = Builders.Update + .SetOnInsert(p => p.Insert, date) + .SetOnInsert(p => p.Host, hostEntity.Id) + .SetOnInsert(p => p.Serial, update.Id) + .Set(p => p.Update, date) + .Set(p => p.Batch, batch) + + .Set(p => p.Pending, true) + .Set(p => p.Name, update.Name) + .Set(p => p.Description, update.Description) + .Set(p => p.SupportUrl, update.SupportUrl) + .Set(p => p.Result, update.Result?.ToString()) + + .Set(p => p.Type, update.Type.ToString()) + .Set(p => p.Size, update.Size) + .Set(p => p.IsDownloaded, update.IsDownloaded) + .Set(p => p.CanRequestUserInput, update.CanRequestUserInput) + .Set(p => p.RebootBehavior, update.RebootBehavior?.ToString()) + + .Set(p => p.Date, update.Date); + + bulk.Add(new UpdateOneModel(filterDefinition, updateDefinition) + { + IsUpsert = true + }); + } + } + } + + bulk.Add(new DeleteManyModel(Builders.Filter.And(new List> + { + Builders.Filter.Eq(x => x.Host, hostEntity.Id), + Builders.Filter.Ne(x => x.Batch, batch) + }))); + + var result = await _database.HostUpdate().BulkWriteAsync(bulk, cancellationToken: cancellationToken); + } +} \ No newline at end of file diff --git a/src/Server/Insight.Server2/Network/Agent/Handlers/UserHandler.cs b/src/Server/Insight.Server2/Network/Agent/Handlers/UserHandler.cs new file mode 100644 index 0000000..5051971 --- /dev/null +++ b/src/Server/Insight.Server2/Network/Agent/Handlers/UserHandler.cs @@ -0,0 +1,183 @@ +using Insight.Domain.Interfaces; +using Insight.Domain.Network; +using Insight.Domain.Network.Agent.Messages; +using Insight.Infrastructure.Entities; +using MongoDB.Bson; +using MongoDB.Driver; + +namespace Insight.Server.Network.Agent.Handlers; + +public class UserHandler(IMongoDatabase database) : IMessageHandler +{ + private readonly IMongoDatabase _database = database; + + public async ValueTask HandleAsync(AgentSession sender, TMessage message, CancellationToken cancellationToken) where TMessage : IMessage + { + switch (message) + { + case Collection users: + await OnUsersAsync(sender, users, cancellationToken); + break; + } + } + + private async ValueTask OnUsersAsync(AgentSession session, List? users, CancellationToken cancellationToken) + { + var agentEntity = await _database.Agent().Find(Builders.Filter.Eq(p => p.Id, session.Id)).FirstOrDefaultAsync(cancellationToken); + if (agentEntity is null) return; + + var hostEntity = await _database.Host().Find(Builders.Filter.Eq(p => p.Agent, agentEntity.Id)).FirstOrDefaultAsync(cancellationToken); + if (hostEntity is null) return; + + var batch = ObjectId.GenerateNewId().ToString(); + var date = DateTime.Now; + + // users + + if (users is not null && users.Count != 0) + { + var userBulk = new List>(); + + foreach (var user in users) + { + var userFilter = Builders.Filter.And(new List> + { + Builders.Filter.Eq(x => x.Host, hostEntity.Id), + Builders.Filter.Eq(x => x.Domain, user?.Domain), + Builders.Filter.Eq(x => x.Name, user?.Name) + }); + + var userUpdate = Builders.Update + .SetOnInsert(p => p.Insert, date) + .SetOnInsert(p => p.Host, hostEntity.Id) + .SetOnInsert(p => p.Domain, user?.Domain) + .SetOnInsert(p => p.Name, user?.Name) + .Set(p => p.Update, date) + .Set(p => p.Batch, batch) + + .Set(p => p.Sid, user?.Sid) + .Set(p => p.FullName, user?.FullName) + .Set(p => p.Description, user?.Description) + .Set(p => p.Status, user?.Status) + .Set(p => p.LocalAccount, user?.LocalAccount) + .Set(p => p.Disabled, user?.Disabled) + .Set(p => p.Lockout, user?.Lockout) + .Set(p => p.PasswordChangeable, user?.PasswordChangeable) + .Set(p => p.PasswordExpires, user?.PasswordExpires) + .Set(p => p.PasswordRequired, user?.PasswordRequired); + + userBulk.Add(new UpdateOneModel(userFilter, userUpdate) + { + IsUpsert = true + }); + } + + userBulk.Add(new DeleteManyModel(Builders.Filter.And(new List> + { + Builders.Filter.Eq(x => x.Host, hostEntity.Id), + Builders.Filter.Ne(x => x.Batch, batch) + }))); + + var userResult = await _database.HostSystemUser().BulkWriteAsync(userBulk, cancellationToken: cancellationToken); + } + + // groups + + if (users is not null && users.Count != 0) + { + var groupBulk = new List>(); + + var distinctGroups = users.SelectMany(p => p.Groups ?? []) + .GroupBy(p => new { p?.Domain, p?.Name }) + .Select(p => p.First()); + + foreach (var group in distinctGroups) + { + var groupFilter = Builders.Filter.And(new List> + { + Builders.Filter.Eq(x => x.Host, hostEntity.Id), + Builders.Filter.Eq(x => x.Domain, group?.Domain), + Builders.Filter.Eq(x => x.Name, group?.Name) + }); + + var groupUpdate = Builders.Update + .SetOnInsert(p => p.Insert, date) + .SetOnInsert(p => p.Host, hostEntity.Id) + .SetOnInsert(p => p.Domain, group?.Domain) + .SetOnInsert(p => p.Name, group?.Name) + .Set(p => p.Update, date) + .Set(p => p.Batch, batch) + + .Set(p => p.Sid, group?.Sid) + .Set(p => p.Description, group?.Description) + .Set(p => p.LocalAccount, group?.LocalAccount); + + groupBulk.Add(new UpdateOneModel(groupFilter, groupUpdate) + { + IsUpsert = true + }); + } + + groupBulk.Add(new DeleteManyModel(Builders.Filter.And(new List> + { + Builders.Filter.Eq(x => x.Host, hostEntity.Id), + Builders.Filter.Ne(x => x.Batch, batch) + }))); + + var groupResult = await _database.HostSystemGroup().BulkWriteAsync(groupBulk, cancellationToken: cancellationToken); + } + + // relations + if (users is not null && users.Count != 0) + { + var relationBulk = new List>(); + + foreach (var user in users) + { + var userId = await _database.HostSystemUser() + .Find(p => p.Host == hostEntity.Id && p.Domain == user.Domain && p.Name == user.Name) + .Project(p => p.Id) + .FirstOrDefaultAsync(cancellationToken: default); + + if (user.Groups is not null && user.Groups.Count != 0) + { + foreach (var group in user.Groups) + { + var groupId = await _database.HostSystemGroup() + .Find(p => p.Host == hostEntity.Id && p.Domain == group.Domain && p.Name == group.Name) + .Project(p => p.Id) + .FirstOrDefaultAsync(cancellationToken: default); + + var relationFilter = Builders.Filter.And(new List> + { + Builders.Filter.Eq(x => x.Host, hostEntity.Id), + Builders.Filter.Eq(x => x.User, userId), + Builders.Filter.Eq(x => x.Group, groupId) + }); + + var relationUpdate = Builders.Update + .SetOnInsert(p => p.Insert, date) + .SetOnInsert(p => p.Host, hostEntity.Id) + .SetOnInsert(p => p.User, userId) + .SetOnInsert(p => p.Group, groupId) + .Set(p => p.Update, date) + .Set(p => p.Batch, batch); + + relationBulk.Add(new UpdateOneModel(relationFilter, relationUpdate) + { + IsUpsert = true + }); + } + } + } + + relationBulk.Add(new DeleteManyModel(Builders.Filter.And(new List> + { + Builders.Filter.Eq(x => x.Host, hostEntity.Id), + Builders.Filter.Ne(x => x.Batch, batch) + }))); + + var relationResult = await _database.HostSystemUserSystemGroup().BulkWriteAsync(relationBulk, cancellationToken: cancellationToken); + } + } +} \ No newline at end of file diff --git a/src/Server/Insight.Server2/Network/Agent/Handlers/VideocardHandler.cs b/src/Server/Insight.Server2/Network/Agent/Handlers/VideocardHandler.cs new file mode 100644 index 0000000..b8316b5 --- /dev/null +++ b/src/Server/Insight.Server2/Network/Agent/Handlers/VideocardHandler.cs @@ -0,0 +1,73 @@ +using Insight.Domain.Interfaces; +using Insight.Domain.Network; +using Insight.Domain.Network.Agent.Messages; +using Insight.Infrastructure.Entities; +using MongoDB.Bson; +using MongoDB.Driver; + +namespace Insight.Server.Network.Agent.Handlers; + +public class VideocardHandler(IMongoDatabase database) : IMessageHandler +{ + private readonly IMongoDatabase _database = database; + + public async ValueTask HandleAsync(AgentSession sender, TMessage message, CancellationToken cancellationToken) where TMessage : IMessage + { + switch (message) + { + case Collection videocards: + await OnVideocardsAsync(sender, videocards, cancellationToken); + break; + } + } + + private async ValueTask OnVideocardsAsync(AgentSession session, List? videocards, CancellationToken cancellationToken) + { + var agentEntity = await _database.Agent().Find(Builders.Filter.Eq(p => p.Id, session.Id)).FirstOrDefaultAsync(cancellationToken); + if (agentEntity is null) return; + + var hostEntity = await _database.Host().Find(Builders.Filter.Eq(p => p.Agent, agentEntity.Id)).FirstOrDefaultAsync(cancellationToken); + if (hostEntity is null) return; + + var batch = ObjectId.GenerateNewId().ToString(); + var date = DateTime.Now; + + var bulk = new List>(); + + if (videocards is not null && videocards.Count != 0) + { + foreach (var videocard in videocards) + { + var filterDefinition = Builders.Filter.And(new List> + { + Builders.Filter.Eq(x => x.Host, hostEntity.Id), + Builders.Filter.Eq(x => x.Name, videocard.Model) + }); + + var updateDefinition = Builders.Update + .SetOnInsert(p => p.Insert, date) + .SetOnInsert(p => p.Host, hostEntity.Id) + .SetOnInsert(p => p.Name, videocard.Model) + .Set(p => p.Update, date) + .Set(p => p.Batch, batch) + .Set(p => p.Company, null) + .Set(p => p.Memory, videocard.Memory) + .Set(p => p.Driver, videocard.DriverVersion) + .Set(p => p.Date, videocard.DriverDate); + + bulk.Add(new UpdateOneModel(filterDefinition, updateDefinition) + { + IsUpsert = true + }); + } + + bulk.Add(new DeleteManyModel(Builders.Filter.And(new List> + { + Builders.Filter.Eq(x => x.Host, hostEntity.Id), + Builders.Filter.Ne(x => x.Batch, batch) + }))); + + var result = await _database.HostVideocard().BulkWriteAsync(bulk, cancellationToken: cancellationToken); + } + } +} diff --git a/src/Server/Insight.Server2/Network/Agent/Handlers/VirtualMaschineHandler.cs b/src/Server/Insight.Server2/Network/Agent/Handlers/VirtualMaschineHandler.cs new file mode 100644 index 0000000..c70dd02 --- /dev/null +++ b/src/Server/Insight.Server2/Network/Agent/Handlers/VirtualMaschineHandler.cs @@ -0,0 +1,173 @@ +using Insight.Domain.Interfaces; +using Insight.Domain.Network; +using Insight.Domain.Network.Agent.Messages; +using Insight.Infrastructure.Entities; +using MongoDB.Bson; +using MongoDB.Driver; + +namespace Insight.Server.Network.Agent.Handlers; + +public class VirtualMaschineHandler(IMongoDatabase database) : IMessageHandler +{ + private readonly IMongoDatabase _database = database; + + public async ValueTask HandleAsync(AgentSession sender, TMessage message, CancellationToken cancellationToken) where TMessage : IMessage + { + switch (message) + { + case Collection virtualMaschines: + await OnVirtualMaschinesAsync(sender, virtualMaschines, cancellationToken); + break; + } + } + + private async ValueTask OnVirtualMaschinesAsync(AgentSession session, List? virtualMaschines, CancellationToken cancellationToken) + { + var agentEntity = await _database.Agent().Find(Builders.Filter.Eq(p => p.Id, session.Id)).FirstOrDefaultAsync(cancellationToken); + if (agentEntity is null) return; + + var hostEntity = await _database.Host().Find(Builders.Filter.Eq(p => p.Agent, agentEntity.Id)).FirstOrDefaultAsync(cancellationToken); + if (hostEntity is null) return; + + var batch = ObjectId.GenerateNewId().ToString(); + var date = DateTime.Now; + + // virtual maschines + if (virtualMaschines is not null && virtualMaschines.Count != 0) + { + var virtualMaschineBulk = new List>(); + + foreach (var virtualMaschine in virtualMaschines) + { + var virtualMaschineFilter = Builders.Filter.And(new List> + { + Builders.Filter.Eq(x => x.Host, hostEntity.Id), + Builders.Filter.Eq(x => x.UniqueId, virtualMaschine.Id.ToString()) + }); + + var virtualMaschineUpdate = Builders.Update + .SetOnInsert(p => p.Insert, date) + .SetOnInsert(p => p.Host, hostEntity.Id) + .SetOnInsert(p => p.UniqueId, virtualMaschine.Id.ToString()) + .Set(p => p.Update, date) + .Set(p => p.Batch, batch) + + .Set(p => p.Name, virtualMaschine?.Name) + .Set(p => p.Notes, virtualMaschine?.Notes) + .Set(p => p.Enabled, virtualMaschine?.Enabled?.ToString()) + .Set(p => p.EnabledDefault, virtualMaschine?.EnabledDefault?.ToString()) + .Set(p => p.Health, virtualMaschine?.HealthState?.ToString()) + .Set(p => p.Status, virtualMaschine?.Status) + .Set(p => p.OnTime, virtualMaschine?.OnTime) + .Set(p => p.ReplicationState, virtualMaschine?.ReplicationState?.ToString()) + .Set(p => p.ReplicationHealth, virtualMaschine?.ReplicationHealth?.ToString()) + .Set(p => p.ConfigurationVersion, virtualMaschine?.ConfigurationVersion) + .Set(p => p.IntegrationServicesVersionState, virtualMaschine?.IntegrationServicesVersionState?.ToString()) + .Set(p => p.ProcessId, virtualMaschine?.ProcessId) + .Set(p => p.NumberOfProcessors, virtualMaschine?.NumberOfProcessors) + .Set(p => p.ProcessorLoad, virtualMaschine?.ProcessorLoad) + .Set(p => p.MemoryAvailable, virtualMaschine?.MemoryAvailable) + .Set(p => p.MemoryUsage, virtualMaschine?.MemoryUsage) + .Set(p => p.InstallDate, virtualMaschine?.InstallDate) + .Set(p => p.ConfigurationVersion, virtualMaschine?.ConfigurationVersion) + .Set(p => p.TimeOfLastStateChange, virtualMaschine?.TimeOfLastStateChange) + .Set(p => p.LastReplicationTime, virtualMaschine?.LastReplicationTime) + .Set(p => p.Os, virtualMaschine?.GuestOperatingSystem); + + virtualMaschineBulk.Add(new UpdateOneModel(virtualMaschineFilter, virtualMaschineUpdate) + { + IsUpsert = true + }); + } + + virtualMaschineBulk.Add(new DeleteManyModel(Builders.Filter.And(new List> + { + Builders.Filter.Eq(x => x.Host, hostEntity.Id), + Builders.Filter.Ne(x => x.Batch, batch) + }))); + + var virtualMaschineResult = await _database.HostHypervisorVirtualMaschine().BulkWriteAsync(virtualMaschineBulk, cancellationToken: cancellationToken); + } + + // virtual maschine configurations + + if (virtualMaschines is not null && virtualMaschines.Count != 0) + { + var configurationBulk = new List>(); + + foreach (var virtualmaschine in virtualMaschines) + { + var virtualMaschineId = await _database.HostHypervisorVirtualMaschine() + .Find(p => p.Host == hostEntity.Id && p.UniqueId == virtualmaschine.Id.ToString()) + .Project(p => p.Id) + .FirstOrDefaultAsync(cancellationToken: default); + + if (virtualmaschine.Configurations is not null && virtualmaschine.Configurations.Count != 0) + { + foreach (var config in virtualmaschine.Configurations) + { + var configFilter = Builders.Filter.And(new List> + { + Builders.Filter.Eq(x => x.Host, hostEntity.Id), + Builders.Filter.Eq(x => x.VirtualMaschine, virtualMaschineId), + Builders.Filter.Eq(x => x.UniqueId, config.Id) + }); + + // custom "notes" concat + string notes = string.Empty; + + if (config?.Notes is not null) + foreach (var n in config.Notes) notes += n; + + if (config?.Id is null) continue; + + var configUpdate = Builders.Update + .SetOnInsert(p => p.Insert, date) + .SetOnInsert(p => p.Host, hostEntity.Id) + .SetOnInsert(p => p.VirtualMaschine, virtualMaschineId) + .SetOnInsert(p => p.UniqueId, config.Id) + .Set(p => p.Update, date) + .Set(p => p.Batch, batch) + + .Set(p => p.ParentId, config.ParentId) + .Set(p => p.Type, config.Type) + .Set(p => p.Name, config.Name) + .Set(p => p.Notes, notes) + .Set(p => p.CreationTime, config.CreationTime) + .Set(p => p.Generation, config.Generation) + .Set(p => p.Architecture, config.Architecture) + .Set(p => p.SecureBootEnabled, config.SecureBootEnabled) + .Set(p => p.IsAutomaticSnapshot, config.IsAutomaticSnapshot) + .Set(p => p.AutomaticStartupAction, config.AutomaticStartupAction?.ToString()) + .Set(p => p.AutomaticShutdownAction, config.AutomaticShutdownAction?.ToString()) + .Set(p => p.AutomaticRecoveryAction, config.AutomaticRecoveryAction?.ToString()) + .Set(p => p.AutomaticSnapshotsEnabled, config.AutomaticSnapshotsEnabled) + .Set(p => p.BaseBoardSerialNumber, config.BaseBoardSerialNumber) + .Set(p => p.BIOSSerialNumber, config.BIOSSerialNumber) + .Set(p => p.BIOSGUID, config.BIOSGUID) + .Set(p => p.ConfigurationDataRoot, config.ConfigurationDataRoot) + .Set(p => p.ConfigurationFile, config.ConfigurationFile) + .Set(p => p.GuestStateDataRoot, config.GuestStateDataRoot) + .Set(p => p.GuestStateFile, config.GuestStateFile) + .Set(p => p.SnapshotDataRoot, config.SnapshotDataRoot) + .Set(p => p.SuspendDataRoot, config.SuspendDataRoot) + .Set(p => p.SwapFileDataRoot, config.SwapFileDataRoot); + + configurationBulk.Add(new UpdateOneModel(configFilter, configUpdate) + { + IsUpsert = true + }); + } + } + } + + configurationBulk.Add(new DeleteManyModel(Builders.Filter.And(new List> + { + Builders.Filter.Eq(x => x.Host, hostEntity.Id), + Builders.Filter.Ne(x => x.Batch, batch) + }))); + + var configurationResult = await _database.HostVirtualMaschineConfig().BulkWriteAsync(configurationBulk, cancellationToken: cancellationToken); + } + } +} \ No newline at end of file diff --git a/src/Server/Insight.Server2/Network/Remote/Handlers/RemoteHandler.cs b/src/Server/Insight.Server2/Network/Remote/Handlers/RemoteHandler.cs new file mode 100644 index 0000000..9e96aa7 --- /dev/null +++ b/src/Server/Insight.Server2/Network/Remote/Handlers/RemoteHandler.cs @@ -0,0 +1,104 @@ +using Insight.Domain.Interfaces; +using Insight.Domain.Network; +using Insight.Domain.Network.Remote.Messages; +using Microsoft.Extensions.Logging; +using Vaitr.Bus; +using Vaitr.Network; + +namespace Insight.Server.Network.Remote.Handlers; + +public class RemoteHandler(Bus bus, ISessionPool remotePool, ILogger logger) : IMessageHandler +{ + private readonly Bus _bus = bus; + private readonly ISessionPool _remotePool = remotePool; + private readonly ILogger _logger = logger; + + public async ValueTask HandleAsync(RemoteSession sender, TMessage message, CancellationToken cancellationToken) where TMessage : IMessage + { + if (message is RemoteSessionRequest sessionRequest) + { + await OnSessionRequest(sender, sessionRequest, cancellationToken); + } + else if (message is CastRequestResponse castRequestResponse) + { + await OnCastRequestResponse(sender, castRequestResponse, cancellationToken); + } + else if (message is CastMetric metricData) + { + await OnMetricData(sender, metricData, cancellationToken); + } + else if (message is CastScreen screenData) + { + await OnScreenData(sender, screenData, cancellationToken); + } + else if (message is CastCursor cursorData) + { + await OnCursorData(sender, cursorData, cancellationToken); + } + else if (message is CastClipboardReceived clipboardData) + { + await OnClipboardData(sender, clipboardData, cancellationToken); + } + else if (message is CastAudio audioData) + { + await OnAudioData(sender, audioData, cancellationToken); + } + } + + private async Task OnSessionRequest(RemoteSession session, RemoteSessionRequest sessionRequest, CancellationToken cancellationToken) + { + _logger.LogInformation("Remote {session} => SessionRequest", session.Id); + + session.Mode = sessionRequest.Mode; + + await session.SendAsync(new RemoteSessionResponse + { + SessionId = session.Id + }, cancellationToken); + } + + private async Task OnCastRequestResponse(RemoteSession session, CastRequestResponse castRequestResponse, CancellationToken cancellationToken) + { + await _bus.PublishAsync(castRequestResponse, cancellationToken); + } + + private async Task OnMetricData(RemoteSession session, CastMetric streamMetrics, CancellationToken cancellationToken) + { + //_logger.LogInformation($"Remote {session.Id} => MetricData"); + + await _bus.PublishAsync(streamMetrics, cancellationToken); + } + + private async Task OnScreenData(RemoteSession session, CastScreen screenData, CancellationToken cancellationToken) + { + //_logger.LogInformation($"Remote {session.Id} => ScreenData"); + + await _bus.PublishAsync(screenData, cancellationToken); + + await session.SendAsync(new CastScreenReceived + { + Timestamp = screenData.Timestamp + }, cancellationToken); + } + + private async Task OnCursorData(RemoteSession session, CastCursor cursorChanged, CancellationToken cancellationToken) + { + //_logger.LogInformation($"Remote {session.Id} => CursorData"); + + await _bus.PublishAsync(cursorChanged, cancellationToken); + } + + private async Task OnClipboardData(RemoteSession session, CastClipboardReceived clipboardChanged, CancellationToken cancellationToken) + { + _logger.LogInformation("Remote {session} => ClipboardData", session.Id); + + await _bus.PublishAsync(clipboardChanged, cancellationToken); + } + + private async Task OnAudioData(RemoteSession session, CastAudio audioSample, CancellationToken cancellationToken) + { + _logger.LogInformation("Remote {session} => AudioData", session.Id); + + await _bus.PublishAsync(audioSample, cancellationToken); + } +} \ No newline at end of file diff --git a/src/Server/Insight.Server2/Network/Remote/RemoteSession.cs b/src/Server/Insight.Server2/Network/Remote/RemoteSession.cs new file mode 100644 index 0000000..1da69a2 --- /dev/null +++ b/src/Server/Insight.Server2/Network/Remote/RemoteSession.cs @@ -0,0 +1,83 @@ +using Insight.Domain.Enums; +using Insight.Domain.Interfaces; +using Insight.Domain.Network; +using Insight.Server.Network.Web; +using Microsoft.Extensions.Logging; +using Vaitr.Network; + +namespace Insight.Server.Network.Remote; + +public class RemoteSession( + ISessionPool webPool, + IEnumerable> handlers, + ISerializer serializer, + ILogger logger) : TcpSession(serializer, logger) +{ + public string Id { get; } = GenerateRandomId(); + public RemoteControlMode Mode { get; set; } + + private readonly ISessionPool _webPool = webPool; + private readonly IEnumerable> _handlers = handlers; + + public async ValueTask ProxyAsync(TMessage message, CancellationToken cancellationToken) + where TMessage : IMessage + { + // check if web online + if (_webPool.FirstOrDefault().Value is not WebSession web) return; + + // proxy-send request packet to web + await web.SendAsync(new Proxy + { + Message = message + }, cancellationToken); + } + + protected override ValueTask OnConnectedAsync(CancellationToken cancellationToken) + { + _logger.LogInformation("Remote ({ep?}) connected", RemoteEndPoint); + return default; + } + + protected override ValueTask OnDisconnectedAsync(CancellationToken cancellationToken) + { + _logger.LogInformation("Remote ({ep?}) disconnected", RemoteEndPoint); + return default; + } + + protected override async ValueTask OnSentAsync(IPacketContext context, CancellationToken cancellationToken) + { + await base.OnSentAsync(context, cancellationToken); + } + + protected override async ValueTask OnReceivedAsync(IPacketContext context, CancellationToken cancellationToken) + { + await base.OnReceivedAsync(context, cancellationToken); + + foreach (var handler in _handlers) + { + try + { + await handler.HandleAsync(this, context.Packet, cancellationToken); + } + catch (Exception ex) + { + _logger.LogWarning("Remote ({ep?}) {ex}", RemoteEndPoint, ex.ToString()); + } + } + } + + protected override ValueTask OnHeartbeatAsync(CancellationToken cancellationToken) + { + _logger.LogInformation("Remote ({ep?}) Heartbeat", RemoteEndPoint); + return default; + } + + private static string GenerateRandomId() + { + var random = new Random(); + string? sessionId = string.Empty; + + for (var i = 0; i < 3; i++) sessionId += random.Next(0, 999).ToString().PadLeft(3, '0'); + return sessionId; + } +} \ No newline at end of file diff --git a/src/Server/Insight.Server2/Network/Shared/ProxyHandler.cs b/src/Server/Insight.Server2/Network/Shared/ProxyHandler.cs new file mode 100644 index 0000000..0a003b3 --- /dev/null +++ b/src/Server/Insight.Server2/Network/Shared/ProxyHandler.cs @@ -0,0 +1,86 @@ +using Insight.Domain.Interfaces; +using Insight.Domain.Network; +using Insight.Domain.Network.Agent.Messages; +using Insight.Infrastructure.Entities; +using Insight.Server.Network.Agent; +using Insight.Server.Network.Web; +using Microsoft.Extensions.Logging; +using MongoDB.Driver; +using Vaitr.Network; + +namespace Insight.Server.Network.Globals; + +public class ProxyHandler( + ISessionPool agentPool, + ISessionPool webPool, + IMongoDatabase database, + ILogger logger) : IMessageHandler, IMessageHandler +{ + private readonly ISessionPool _agentPool = agentPool; + private readonly ISessionPool _webPool = webPool; + private readonly IMongoDatabase _database = database; + private readonly ILogger _logger = logger; + + public async ValueTask HandleAsync(WebSession sender, TMessage message, CancellationToken cancellationToken) where TMessage : IMessage + { + switch (message) + { + case Proxy proxyRequest: + await OnProxyRequestAsync(sender, proxyRequest, cancellationToken); + break; + } + } + + public async ValueTask HandleAsync(AgentSession sender, TMessage message, CancellationToken cancellationToken) where TMessage : IMessage + { + switch (message) + { + case Proxy proxyResponse: + await OnProxyResponseAsync(sender, proxyResponse, cancellationToken); + break; + } + } + + private async ValueTask OnProxyRequestAsync(WebSession session, Proxy request, CancellationToken cancellationToken) + { + // get host + var hostEntity = await _database.Host() + .Find(Builders + .Filter + .Eq(p => p.Id, request.ProxyId)) + .FirstOrDefaultAsync(cancellationToken); + + if (hostEntity is null) + { + _logger.LogWarning("hostEntity is null"); + return; + } + + // get agent + var agentEntity = await _database.Agent() + .Find(Builders + .Filter + .Eq(p => p.Id, hostEntity.Agent)) + .FirstOrDefaultAsync(cancellationToken); + + if (agentEntity is null) + { + _logger.LogWarning("agentEntity is null"); + return; + } + + // check if agent online + if (_agentPool.FirstOrDefault(p => p.Value.Id == agentEntity.Id).Value is not AgentSession agent) return; + + // proxy-send request packet to agent + await agent.SendAsync(request, cancellationToken); + } + + private async ValueTask OnProxyResponseAsync(AgentSession session, Proxy response, CancellationToken cancellationToken) + { + // check if web online + if (_webPool.FirstOrDefault().Value is not WebSession web) return; + + await web.SendAsync(response, cancellationToken); + } +} \ No newline at end of file diff --git a/src/Server/Insight.Server2/Network/Web/WebSession.cs b/src/Server/Insight.Server2/Network/Web/WebSession.cs new file mode 100644 index 0000000..7603f96 --- /dev/null +++ b/src/Server/Insight.Server2/Network/Web/WebSession.cs @@ -0,0 +1,53 @@ +using Insight.Domain.Interfaces; +using Insight.Domain.Network; +using Microsoft.Extensions.Logging; +using Vaitr.Network; + +namespace Insight.Server.Network.Web; + +public class WebSession(IEnumerable> handlers, ISerializer serializer, ILogger logger) : TcpSession(serializer, logger) +{ + public string? Id { get; set; } + + private readonly IEnumerable> _handlers = handlers; + + protected override ValueTask OnConnectedAsync(CancellationToken cancellationToken) + { + _logger.LogInformation("Web ({ep?}) connected", RemoteEndPoint); + return default; + } + + protected override ValueTask OnDisconnectedAsync(CancellationToken cancellationToken) + { + _logger.LogInformation("Web ({ep?}) disconnected", RemoteEndPoint); + return default; + } + + protected override async ValueTask OnSentAsync(IPacketContext context, CancellationToken cancellationToken) + { + await base.OnSentAsync(context, cancellationToken); + } + + protected override async ValueTask OnReceivedAsync(IPacketContext context, CancellationToken cancellationToken) + { + await base.OnReceivedAsync(context, cancellationToken); + + foreach (var handler in _handlers) + { + try + { + await handler.HandleAsync(this, context.Packet, cancellationToken); + } + catch (Exception ex) + { + _logger.LogWarning("Web ({ep?}) {ex}", RemoteEndPoint, ex.ToString()); + } + } + } + + protected override ValueTask OnHeartbeatAsync(CancellationToken cancellationToken) + { + _logger.LogInformation("Web ({ep?}) Heartbeat", RemoteEndPoint); + return default; + } +} \ No newline at end of file diff --git a/src/Server/Insight.Server2/Program.cs b/src/Server/Insight.Server2/Program.cs new file mode 100644 index 0000000..06410c5 --- /dev/null +++ b/src/Server/Insight.Server2/Program.cs @@ -0,0 +1,171 @@ +using Insight.Domain.Constants; +using Insight.Domain.Interfaces; +using Insight.Domain.Network; +using Insight.Infrastructure; +using Insight.Server.Network.Agent; +using Insight.Server.Network.Agent.Handlers; +using Insight.Server.Network.Globals; +using Insight.Server.Network.Web; +using OpenTelemetry.Metrics; +using OpenTelemetry.Resources; +using System.Net; +using Vaitr.Network; +using Vaitr.Network.Hosting; + +namespace Insight.Server; + +internal class Program +{ + public static async Task Main(string[] args) + { + var builder = WebApplication.CreateBuilder(args); + builder.Host.UseWindowsService(); + builder.Host.UseSystemd(); + + // Configuration + builder.Configuration.Defaults(); + + // Logging + builder.Logging.ClearProviders(); + builder.Logging.SetMinimumLevel(LogLevel.Trace); + builder.Logging.AddSimpleConsole(options => + { + options.IncludeScopes = true; + options.SingleLine = true; + options.TimestampFormat = "yyyy-MM-dd HH:mm:ss.fff "; + }); + builder.Logging.AddFile($"{Configuration.AppDirectory?.FullName}/" + "logs/server_{Date}.log", LogLevel.Trace, fileSizeLimitBytes: 104857600, retainedFileCountLimit: 10, outputTemplate: "{Timestamp:o} [{Level:u3}] {Message} {NewLine}{Exception}"); + + // Modules + builder.AddDefaults(); + builder.AddMetrics(); + builder.AddAgentServices(); + builder.AddWebServices(); + + // HTTP Pipeline + var app = builder.Build(); + + if (app.Environment.IsDevelopment()) + { + app.UseSwagger(); + app.UseSwaggerUI(); + } + + app.UseAuthorization(); + app.MapControllers(); + + await app.RunAsync(); + } +} + +internal static class ServiceExtensions +{ + internal static WebApplicationBuilder AddDefaults(this WebApplicationBuilder builder) + { + builder.Services.AddControllers(); + builder.Services.AddEndpointsApiExplorer(); + builder.Services.AddSwaggerGen(); + + return builder; + } + + internal static WebApplicationBuilder AddMetrics(this WebApplicationBuilder builder) + { + builder.Services.AddOpenTelemetry() + .WithMetrics(provider => + { + provider.ConfigureResource(configure => + { + configure.Clear(); + configure.AddService(builder.Configuration.GetValue(Appsettings.Influx.Service) ?? throw new Exception($"{Appsettings.Influx.Service} value not set (appsettings)")); + }) + .AddRuntimeInstrumentation() + .AddProcessInstrumentation() + .AddAspNetCoreInstrumentation() + //.AddMeter("test") + .AddInfluxDBMetricsExporter(configure => + { + configure.Endpoint = builder.Configuration.GetValue(Appsettings.Influx.Endpoint) ?? throw new Exception($"{Appsettings.Influx.Endpoint} value not set (appsettings)"); + configure.Token = builder.Configuration.GetValue(Appsettings.Influx.Token) ?? throw new Exception($"{Appsettings.Influx.Token} value not set (appsettings)"); + configure.Org = builder.Configuration.GetValue(Appsettings.Influx.Organization) ?? throw new Exception($"{Appsettings.Influx.Organization} value not set (appsettings)"); + configure.Bucket = builder.Configuration.GetValue(Appsettings.Influx.Bucket) ?? throw new Exception($"{Appsettings.Influx.Bucket} value not set (appsettings)"); + + configure.MetricExportIntervalMilliseconds = 1000; + }); + } + ); + + //builder.Services.AddSingleton(); + + return builder; + } + + internal static WebApplicationBuilder AddAgentServices(this WebApplicationBuilder builder) + { + // SERVER + builder.Services.UseHostedServer(options => + { + options.Address = IPAddress.Any; + options.Port = builder.Configuration.GetValue(Appsettings.Agent.Port) ?? throw new Exception($"{Appsettings.Agent.Port} value not set (appsettings)"); + options.Keepalive = 10000; + options.Timeout = 30000; + options.Backlog = 128; + + options.Compression = true; + options.Encryption = Encryption.Tls12; + options.Certificate = builder.Configuration.GetValue(Appsettings.Agent.Certificate) ?? throw new Exception($"{Appsettings.Agent.Certificate} value not set (appsettings)"); + options.CertificatePassword = builder.Configuration.GetValue(Appsettings.Agent.CertificatePassword) ?? throw new Exception($"{Appsettings.Agent.CertificatePassword} value not set (appsettings)"); + + options.UseSerializer>(); + }); + + // HANDLER + builder.Services.AddSingleton, CustomHandler>(); + builder.Services.AddSingleton, ProxyHandler>(); + builder.Services.AddSingleton, DriveHandler>(); + builder.Services.AddSingleton, Network.Agent.Handlers.EventHandler>(); + builder.Services.AddSingleton, InterfaceHandler>(); + builder.Services.AddSingleton, MainboardHandler>(); + builder.Services.AddSingleton, MemoryHandler>(); + builder.Services.AddSingleton, OperationSystemHandler>(); + builder.Services.AddSingleton, PrinterHandler>(); + builder.Services.AddSingleton, ProcessorHandler>(); + builder.Services.AddSingleton, ServiceHandler>(); + builder.Services.AddSingleton, SessionHandler>(); + builder.Services.AddSingleton, SoftwareHandler>(); + builder.Services.AddSingleton, StoragePoolHandler>(); + builder.Services.AddSingleton, SystemInfoHandler>(); + builder.Services.AddSingleton, TrapHandler>(); + builder.Services.AddSingleton, UpdateHandler>(); + builder.Services.AddSingleton, UserHandler>(); + builder.Services.AddSingleton, VideocardHandler>(); + builder.Services.AddSingleton, VirtualMaschineHandler>(); + + return builder; + } + + internal static WebApplicationBuilder AddWebServices(this WebApplicationBuilder builder) + { + // SERVER + builder.Services.UseHostedServer(options => + { + options.Address = IPAddress.Any; + options.Port = builder.Configuration.GetValue(Appsettings.Web.Port) ?? throw new Exception($"{Appsettings.Web.Port} value not set (appsettings)"); + options.Keepalive = 10000; + options.Timeout = 30000; + options.Backlog = 128; + options.Encryption = Encryption.Tls12; + options.Compression = true; + + options.Certificate = builder.Configuration.GetValue(Appsettings.Web.Certificate) ?? throw new Exception($"{Appsettings.Web.Certificate} value not set (appsettings)"); + options.CertificatePassword = builder.Configuration.GetValue(Appsettings.Web.CertificatePassword) ?? throw new Exception($"{Appsettings.Web.CertificatePassword} value not set (appsettings)"); + + options.UseSerializer>(); + }); + + // HANDLER + builder.Services.AddSingleton, ProxyHandler>(); + + return builder; + } +} \ No newline at end of file diff --git a/src/Server/Insight.Server2/Properties/launchSettings.json b/src/Server/Insight.Server2/Properties/launchSettings.json new file mode 100644 index 0000000..060a2b2 --- /dev/null +++ b/src/Server/Insight.Server2/Properties/launchSettings.json @@ -0,0 +1,19 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "Development": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5001", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Production": { + "commandName": "Project", + "applicationUrl": "http://localhost:5001" + } + } +} diff --git a/src/Server/Insight.Server2/Services/DispatchService.cs b/src/Server/Insight.Server2/Services/DispatchService.cs new file mode 100644 index 0000000..6f08467 --- /dev/null +++ b/src/Server/Insight.Server2/Services/DispatchService.cs @@ -0,0 +1,148 @@ +using Insight.Domain.Enums; +using Insight.Infrastructure; +using Insight.Infrastructure.Entities; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using MongoDB.Driver; + +namespace Insight.Server.Services; + +internal class DispatchService(HttpClient httpClient, IMongoDatabase database, IConfiguration configuration, ILogger logger) : BackgroundService +{ + private readonly HttpClient _httpClient = httpClient; + private readonly IMongoDatabase _database = database; + private readonly IConfiguration _configuration = configuration; + private readonly ILogger _logger = logger; + + protected override async Task ExecuteAsync(CancellationToken cancellationToken) + { + _logger.LogTrace("ExecuteAsync"); + + var enabled = _configuration.GetValue(Appsettings.Dispatch.Webmatic) ?? throw new Exception($"{Appsettings.Dispatch.Webmatic} value not set (appsettings)"); + if (enabled is false) return; + + try + { + while (cancellationToken.IsCancellationRequested is false) + { + await DispatchAsync(cancellationToken); + await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken); + } + } + catch (OperationCanceledException) { } + catch (Exception) { } + } + + private async ValueTask DispatchAsync(CancellationToken cancellationToken) + { + _logger.LogTrace($"DispatchAsync"); + + var pendings = await _database.HostLogMonitoring() + .Find(Builders + .Filter.Eq(p => p.Dispatch, DispatchEnum.Pending.ToString())) + .Limit(10) + .ToListAsync(cancellationToken); + + if (pendings is null || pendings.Count == 0) return; + + foreach (var entity in pendings) + { + try + { + var result = await SendAsync(entity, default); + + await _database.HostLogMonitoring() + .UpdateOneAsync(Builders.Filter + .Eq(p => p.Id, entity.Id), Builders + .Update + .Set(p => p.Dispatch, result.ToString()), cancellationToken: default); + } + catch (Exception ex) + { + _logger.LogError("{exception}", ex.Message); + } + finally + { + // webmatic safety offset + await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken: default); + } + } + } + + private async ValueTask SendAsync(HostLogMonitoringEntity monitoring, CancellationToken cancellationToken) + { + _logger.LogTrace("SendAsync ({monitoring})", monitoring); + + var monitoringApi = Monitoring.LogUri; + var monitoringContent = new List>(); + var monitoringResult = new List>(); + + // adjust by category + if (Enum.TryParse(monitoring.Category, true, out var monitoringCategory) is false) return DispatchEnum.Failure; + + if (monitoringCategory == CategoryEnum.Monitoring) monitoringApi = Monitoring.StatusUri; + + // set category (if log) + if (monitoringApi == Monitoring.LogUri) monitoringContent.Add(new KeyValuePair("category", monitoringCategory.ToString())); + + // host resolve + var hostEntity = await _database.Host().Find(Builders.Filter.Eq(p => p.Id, monitoring.Host?.ToString())).FirstOrDefaultAsync(cancellationToken); + if (hostEntity is null) return DispatchEnum.Failure; + + // customer resolve + var customerEntity = await _database.Customer().Find(Builders.Filter.Eq(p => p.Id, hostEntity.Customer)).FirstOrDefaultAsync(cancellationToken); + if (hostEntity is null) return DispatchEnum.Failure; + + // set host name if no remote host set + if (string.IsNullOrEmpty(monitoring.Hostname)) monitoring.Hostname = hostEntity.Name; + + // remove any domain from hostname + if (monitoring.Hostname is not null && monitoring.Hostname.Contains('.')) monitoring.Hostname = monitoring.Hostname.Split(".")[0]; + + // add customer tag to hostname + monitoring.Hostname += $".{customerEntity.Tag}"; + + // if task null, set hostname + if (string.IsNullOrEmpty(monitoring.Task)) monitoring.Task = monitoring.Hostname; + + // insert hostname as computer-name (lowercase) + monitoringContent.Add(new KeyValuePair("computer_name", monitoring.Hostname.ToLower())); + + // insert converted status (api styled) + if (Enum.TryParse(monitoring.Status, true, out var monitoringStatus) is false) return DispatchEnum.Failure; + + monitoringContent.Add(monitoringStatus switch + { + StatusEnum.Information => new KeyValuePair("status", monitoringApi == Monitoring.StatusUri ? "erfolgreich" : "info"), + StatusEnum.Warning => new KeyValuePair("status", monitoringApi == Monitoring.StatusUri ? "Interaktion" : "warning"), + StatusEnum.Error => new KeyValuePair("status", monitoringApi == Monitoring.StatusUri ? "fehlgeschlagen" : "error"), + _ => throw new NotImplementedException(nameof(monitoringStatus)) + }); + + // insert task, timestamp, message, + monitoringContent.Add(new KeyValuePair("task", monitoring.Task)); + + if (monitoring.Timestamp is not null) + { + monitoringContent.Add(new KeyValuePair("timestamp", monitoring.Timestamp.Value.ToLocalTime().ToString())); + } + + if (string.IsNullOrWhiteSpace(monitoring.Message) is false) + { + monitoringContent.Add(new KeyValuePair("message", monitoring.Message)); + } + + // send message + var result = await _httpClient.PostAsync(monitoringApi, new FormUrlEncodedContent(monitoringContent), default); + + monitoringResult.Add(new KeyValuePair("HttpStatusCode", result.StatusCode.ToString())); + monitoringResult.Add(new KeyValuePair("HttpResponseMessage", await result.Content.ReadAsStringAsync(default))); + + // if content != "OK" + if (result is null || result.IsSuccessStatusCode == false) return DispatchEnum.Failure; + + // success + return DispatchEnum.Success; + } +} \ No newline at end of file diff --git a/src/Server/Insight.Server2/WeatherForecast.cs b/src/Server/Insight.Server2/WeatherForecast.cs new file mode 100644 index 0000000..9e81231 --- /dev/null +++ b/src/Server/Insight.Server2/WeatherForecast.cs @@ -0,0 +1,13 @@ +namespace Insight.Server2 +{ + public class WeatherForecast + { + public DateOnly Date { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + + public string? Summary { get; set; } + } +} diff --git a/src/Server/Insight.Server2/appsettings.Development.json b/src/Server/Insight.Server2/appsettings.Development.json new file mode 100644 index 0000000..b133754 --- /dev/null +++ b/src/Server/Insight.Server2/appsettings.Development.json @@ -0,0 +1,30 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "Urls": "http://127.0.0.1:5001", + + "influx.endpoint": "http://10.22.70.40:8086", + "influx.endpoint0": "http://192.168.1.10:8086", + "influx.token": "253pd3prqWfwhyLH3eirJkgCE5n6tzPVZcRwRrDcla9z1fIPVLB6tGqJ641e4yI5LY8lxb1VfJX_HrrIXMEv5A==", + "influx.token0": "x7ldV7EYb8ocnNAVbtpfpX0nS2HMyPxq3WyBPrVWBRT3C3tCjU8L8h5WPeESFnUqVvYubOM48GBydz5b3n69YA==", + "influx.org": "insight", + "influx.bucket": "insight", + "influx.service": "server", + + "mongo.connection": "mongodb://db.insight.local:27017", + + "agent.port": 3002, + "agent.certificate": "localhost.pfx", + "agent.certificate.password": "Webmatic12", + + "web.port": 3001, + "web.certificate": "localhost.pfx", + "web.certificate.password": "Webmatic12", + + "dispatch.webmatic": false +} diff --git a/src/Server/Insight.Server2/appsettings.json b/src/Server/Insight.Server2/appsettings.json new file mode 100644 index 0000000..1726f9b --- /dev/null +++ b/src/Server/Insight.Server2/appsettings.json @@ -0,0 +1,30 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "Urls": "http://127.0.0.1:5001", + + "influx.endpoint": "http://10.22.70.40:8086", + "influx.endpoint0": "http://192.168.1.10:8086", + "influx.token": "253pd3prqWfwhyLH3eirJkgCE5n6tzPVZcRwRrDcla9z1fIPVLB6tGqJ641e4yI5LY8lxb1VfJX_HrrIXMEv5A==", + "influx.token0": "x7ldV7EYb8ocnNAVbtpfpX0nS2HMyPxq3WyBPrVWBRT3C3tCjU8L8h5WPeESFnUqVvYubOM48GBydz5b3n69YA==", + "influx.org": "insight", + "influx.bucket": "insight", + "influx.service": "server", + + "mongo.connection": "mongodb://127.0.0.1:27017", + + "agent.port": 3002, + "agent.certificate": "localhost.pfx", + "agent.certificate.password": "Webmatic12", + + "web.port": 3001, + "web.certificate": "localhost.pfx", + "web.certificate.password": "Webmatic12", + + "dispatch.webmatic": true +} diff --git a/src/Web/Insight.Web/Extensions/ServiceExtensions.cs b/src/Web/Insight.Web/Extensions/ServiceExtensions.cs index 46fc119..ee933f6 100644 --- a/src/Web/Insight.Web/Extensions/ServiceExtensions.cs +++ b/src/Web/Insight.Web/Extensions/ServiceExtensions.cs @@ -4,7 +4,7 @@ using Insight.Web.Services; using Microsoft.AspNetCore.Components.Server.Circuits; using MudBlazor.Services; -namespace Insight.Web.Hosting; +namespace Insight.Web.Extensions; public static class ServiceExtensions { diff --git a/src/Web/Insight.Web/Insight.Web.csproj b/src/Web/Insight.Web/Insight.Web.csproj index b1cacbb..ffbd257 100644 --- a/src/Web/Insight.Web/Insight.Web.csproj +++ b/src/Web/Insight.Web/Insight.Web.csproj @@ -32,8 +32,9 @@ - - + + + diff --git a/src/Web/Insight.Web/Pages/Management/Hosts/Actions/Console/Index.razor.cs b/src/Web/Insight.Web/Pages/Management/Hosts/Actions/Console/Index.razor.cs index 033074f..e73c6f3 100644 --- a/src/Web/Insight.Web/Pages/Management/Hosts/Actions/Console/Index.razor.cs +++ b/src/Web/Insight.Web/Pages/Management/Hosts/Actions/Console/Index.razor.cs @@ -23,7 +23,7 @@ public partial class Index [Inject] private Bus Bus { get; init; } = default!; [Inject] private NavigationManager NavigationManager { get; init; } = default!; - + private readonly string _id = ObjectId.GenerateNewId().ToString(); private string _title = Global.Name; private readonly List _breadcrumbs = []; diff --git a/src/Web/Insight.Web/Pages/Management/Hosts/Systems/StoragePools/PhysicalDisks/Details.razor.cs b/src/Web/Insight.Web/Pages/Management/Hosts/Systems/StoragePools/PhysicalDisks/Details.razor.cs index ff55ee4..fd8513f 100644 --- a/src/Web/Insight.Web/Pages/Management/Hosts/Systems/StoragePools/PhysicalDisks/Details.razor.cs +++ b/src/Web/Insight.Web/Pages/Management/Hosts/Systems/StoragePools/PhysicalDisks/Details.razor.cs @@ -20,7 +20,7 @@ public partial class Details private string _title = Global.Name; private readonly List _breadcrumbs = []; - private HostStoragePoolPhysicalDiskEntity? _hostPhysicalDisk { get; set; } + private HostStoragePoolPhysicalDiskEntity? _hostPhysicalDisk; private async Task LoadDataAsync() { diff --git a/src/Web/Insight.Web/Program.cs b/src/Web/Insight.Web/Program.cs index 4094806..6fc43a8 100644 --- a/src/Web/Insight.Web/Program.cs +++ b/src/Web/Insight.Web/Program.cs @@ -2,7 +2,7 @@ using Insight.Domain.Constants; using Insight.Domain.Interfaces; using Insight.Domain.Network; using Insight.Infrastructure; -using Insight.Web.Hosting; +using Insight.Web.Extensions; using Insight.Web.Middleware; using Insight.Web.Network.Broker; using Insight.Web.Network.Broker.Handlers; @@ -108,7 +108,6 @@ internal class Program // BLAZOR HUBS (SIGNALR) app.MapBlazorHub(); - // FALLBACK app.MapFallbackToPage("/_Host"); @@ -126,8 +125,8 @@ internal static class ServiceExtensions { services.UseHostedClient(options => { - options.Host = configuration.GetValue(Appsettings.ServerHost) ?? throw new Exception($"{Appsettings.ServerHost} value not set (appsettings)"); - options.Port = configuration.GetValue(Appsettings.ServerPort) ?? throw new Exception($"{Appsettings.ServerPort} value not set (appsettings)"); + options.Host = configuration.GetValue(Appsettings.Backend.Host) ?? throw new Exception($"{Appsettings.Backend.Host} value not set (appsettings)"); + options.Port = configuration.GetValue(Appsettings.Backend.Port) ?? throw new Exception($"{Appsettings.Backend.Port} value not set (appsettings)"); options.Keepalive = 10000; options.Timeout = 30000; options.Encryption = Encryption.Tls12; @@ -146,15 +145,15 @@ internal static class ServiceExtensions services.UseHostedServer(options => { options.Address = IPAddress.Any; - options.Port = configuration.GetValue(Appsettings.RemoteServerPort) ?? throw new Exception($"{Appsettings.RemoteServerPort} value not set (appsettings)"); + options.Port = configuration.GetValue(Appsettings.Remote.Port) ?? throw new Exception($"{Appsettings.Remote.Port} value not set (appsettings)"); options.Keepalive = 10000; options.Timeout = 30000; options.Backlog = 128; options.Compression = true; options.Encryption = Encryption.Tls12; - options.Certificate = configuration.GetValue(Appsettings.RemoteServerCertificate) ?? throw new Exception($"{Appsettings.RemoteServerCertificate} value not set (appsettings)"); - options.CertificatePassword = configuration.GetValue(Appsettings.RemoteServerCertificatePassword) ?? throw new Exception($"{Appsettings.RemoteServerCertificatePassword} value not set (appsettings)"); + options.Certificate = configuration.GetValue(Appsettings.Remote.Certificate) ?? throw new Exception($"{Appsettings.Remote.Certificate} value not set (appsettings)"); + options.CertificatePassword = configuration.GetValue(Appsettings.Remote.CertificatePassword) ?? throw new Exception($"{Appsettings.Remote.CertificatePassword} value not set (appsettings)"); options.UseSerializer>(); }); diff --git a/src/Web/Insight.Web/appsettings.Development.json b/src/Web/Insight.Web/appsettings.Development.json index 2890dc9..6544bc9 100644 --- a/src/Web/Insight.Web/appsettings.Development.json +++ b/src/Web/Insight.Web/appsettings.Development.json @@ -1,8 +1,10 @@ { "AllowedHosts": "*", "Urls": "http://127.0.0.1:5001", - "database": "mongodb://db.insight.local:27017", - "database0": "mongodb://insight.webmatic.de:27017", + + "mongo.connection": "mongodb://db.insight.local:27017", + "mongo.connection0": "mongodb://insight.webmatic.de:27017", + "server.host": "insight.local", "server.port": 3001, diff --git a/src/Web/Insight.Web/appsettings.json b/src/Web/Insight.Web/appsettings.json index d9998f4..bf3265f 100644 --- a/src/Web/Insight.Web/appsettings.json +++ b/src/Web/Insight.Web/appsettings.json @@ -1,7 +1,9 @@ { "AllowedHosts": "*", "Urls": "http://127.0.0.1:5001", - "database": "mongodb://127.0.0.1:27017", + + "mongo.connection": "mongodb://127.0.0.1:27017", + "server.host": "127.0.0.1", "server.port": 3001,