Merged PR 69: Network resurrector notification sistem
- Added messaging - Removed Netmash.Application.DataContracts dependency - removed local CorrelationManager - release notes - agent GetSystemVersion query update - Server SystemController - Network resurrector notification sistem - NotificationService update - NotificationContext: rename MachineStatus to ActionStatus - added readme file - config update - configs - release notes updatemaster
parent
c4b129a565
commit
f732578fb5
|
@ -55,6 +55,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "solution items", "solution
|
|||
dependencies.props = dependencies.props
|
||||
Directory.Build.props = Directory.Build.props
|
||||
NuGet.config = NuGet.config
|
||||
README.md = README.md
|
||||
ReleaseNotes.xml = ReleaseNotes.xml
|
||||
EndProjectSection
|
||||
EndProject
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
# Network resurrector
|
||||
|
||||
Everything must be able to be managed remotely. Even the powered off servers. That's how Network resurrector appeared, the tool I wrote specifically to be able to wake up my machines that I don't need to be powered on all the time.
|
||||
|
||||
Network Resurrector is a system that comprises of five essential services which allow for the execution of its core functionality. To enable various additional features, such as the notification mechanism, supplementary components may be added to the system as an option.
|
||||
|
||||
## Main components
|
||||
|
||||
### Frontend
|
||||
* The frontend component is a web application written in React JS that has the role of providing the user with a friendly visual interface through which to interact with the system.
|
||||
|
||||
### API
|
||||
* The API component is a .NET 6 REST API that has the role of mediating the exchange of data and commands between the frontend component and the database or server component.
|
||||
|
||||
### Server
|
||||
* The server component is a .NET 6 service specialized in executing 'WakeOnLAN', 'Ping' and 'Shutdown' actions for the machines in its network.
|
||||
|
||||
### Agent
|
||||
* The agent is a .NET 6 service specialized in executing 'Shutdown', 'Restart', 'Sleep', 'Logout' and 'Lock' actions on the host machine on which it is installed. Each action can be executed at the time of launch or with a certain delay. If an action is requested with a delay and later the user changes his mind, he can cancel the action by executing the separate 'Cancel' type action.
|
||||
* The need for the agent appeared after I realized that the server cannot perform any action on a machine, being outside of it.
|
||||
* The agent solves this problem because it is installed directly on the targeted machine. Later, the API component can delegate the resolution of an action directly to the agent.
|
||||
* Most of the time, the execution flow of an action is realized in the following way:
|
||||
* The user initiates an action, such as starting or stopping a machine, by pressing the corresponding button in the user interface.
|
||||
* The frontend component sends the command to the API.
|
||||
* The API checks who is configured as performer for the respective action for the machine and sends the command to it.
|
||||
* The performer of the action (Agent or Server) executes the command and responds on the flow with status.
|
||||
* In most cases, the Server component handles the machine startup action while the Agent component manages the machine shutdown action.
|
||||
* As is probably already obvious, the agent can be installed on as many machines as desired.
|
||||
|
||||
### Gundo
|
||||
* Gundo is my personal identity server. It manages user authentication within the application and authorizes requests made by it. Further information about Gundo can be found on its dedicated page.
|
||||
|
||||
All communication between the five main components is done exclusively through HTTP.
|
||||
|
||||
## Secondary components
|
||||
|
||||
* NATS Streaming - Used to publish notifications about executed actions.
|
||||
* Correo - Used to send by email the notifications generated by the system.
|
||||
* Seq - Used to collect and view system logs.
|
||||
|
||||
## Notification system
|
||||
|
||||
The notification system is focused on system actions (Wake, Shutdown, etc), and the configuration of notifications is done in structures of the following form:
|
||||
|
||||
```
|
||||
{
|
||||
"To": [ "user@homelab.com" ],
|
||||
"Subject": "Network resurrector: Machine {MACHINE_NAME} has been waked. Status: {ACTION_STATUS}",
|
||||
"Body": "Hello,{NEWLINE:2}Network resurrector processed a command to start machine {MACHINE_FULLNAME} with IP {MACHINE_IP} at {SYSTEM_DATETIME}.{NEWLINE}The performer who was delegated for this action was {ACTION_PERFORMER}.{NEWLINE}Action status: {ACTION_STATUS}{NEWLINE:2}Have a nice day,{NEWLINE}Network resurrector notification system."
|
||||
}
|
||||
```
|
||||
|
||||
The texts can be written at the user's free choice, and the following placeholders can be used in their composition: ```{MACHINE_NAME}```, ```{MACHINE_FULLNAME}```, ```{MACHINE_IP}```, ```{ACTION_STATUS}```, ```{ACTION_PERFORMER}```, ```{SYSTEM_DATETIME}```, ```{ERROR_MESSGE}```, ```{NEWLINE}```, ```{NEWLINE:2}```
|
||||
|
||||
```{NEWLINE:x}``` is a dynamic placeholder. Any number can be written in place of the 'x' character and the notification system will add that many new lines.
|
||||
|
||||
## Database
|
||||
Currently, the database server supported by the system is only Microsoft SQL Server. In the following versions, the system will also be compatible with PostgreSQL and SQLite.
|
||||
|
||||
## Logging
|
||||
The logging functionality is managed with Serilog, and its configuration is done in the ```appsettings.json``` file. In addition to its standard configuration, Network resurrector also has a preconfigured area where two destinations for logs are available: SqlServer database and Seq. Each of the destinations can be activated or not. If logging in the console is sufficient, all additional logging destinations can be disabled.
|
||||
This configuration area is:
|
||||
|
||||
```
|
||||
"Logs": {
|
||||
"SqlServer": {
|
||||
"Enabled": false,
|
||||
"Connection": "Server=<server>;Database=<database>;User Id=<user>;Password=<password>;"
|
||||
},
|
||||
"Seq": {
|
||||
"Enabled": false,
|
||||
"Url": "",
|
||||
"ApiKey": ""
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Hosting
|
||||
All the components of the system are written in cross-platform technologies, so its host can be any environment.
|
|
@ -41,7 +41,13 @@
|
|||
<Note>
|
||||
<Version>1.1.0</Version>
|
||||
<Content>
|
||||
.NET 6 upgrade
|
||||
</Content>
|
||||
Massive improvements
|
||||
• .NET 6 upgrade
|
||||
• Nuget packages upgrade
|
||||
• Added Seq logging
|
||||
• Added messaging and published notifications from command handlers
|
||||
• Refactoring and code cleanup
|
||||
• Added README.md file
|
||||
</Content>
|
||||
</Note>
|
||||
</ReleaseNotes>
|
|
@ -4,15 +4,16 @@
|
|||
<MicrosoftExtensionsHostingPackageVersion>6.0.1</MicrosoftExtensionsHostingPackageVersion>
|
||||
<SerilogPackageVersion>4.1.0</SerilogPackageVersion>
|
||||
<SerilogExtensionsPackageVersion>3.1.0</SerilogExtensionsPackageVersion>
|
||||
<SerilogSinksConsolePackageVersion>4.0.1</SerilogSinksConsolePackageVersion>
|
||||
<SerilogSinksSeqPackageVersion>5.2.2</SerilogSinksSeqPackageVersion>
|
||||
<SerilogSinksMSSqlServerPackageVersion>5.6.1</SerilogSinksMSSqlServerPackageVersion>
|
||||
<AutoMapperPackageVersion>10.1.1</AutoMapperPackageVersion>
|
||||
<AutoMapperExtensionsPackageVersion>8.1.1</AutoMapperExtensionsPackageVersion>
|
||||
<MediatRPackageVersion>9.0.0</MediatRPackageVersion>
|
||||
<NBBPackageVersion>6.0.30</NBBPackageVersion>
|
||||
<EntityFrameworkCorePackageVersion>6.0.1</EntityFrameworkCorePackageVersion>
|
||||
<NetmashExtensionsSwaggerPackageVersion>1.0.6</NetmashExtensionsSwaggerPackageVersion>
|
||||
<NetmashApplicationPackageVersion>1.0.1</NetmashApplicationPackageVersion>
|
||||
<NetmashSecurityAuthenticationPackageVersion>1.0.8</NetmashSecurityAuthenticationPackageVersion>
|
||||
<NetmashDatabaseMigrationPackageVersion>1.2.0</NetmashDatabaseMigrationPackageVersion>
|
||||
<CorreoPublishedLanguage>1.0.1</CorreoPublishedLanguage>
|
||||
</PropertyGroup>
|
||||
</Project>
|
|
@ -10,7 +10,6 @@
|
|||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="$(MicrosoftExtensionsPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="$(MicrosoftExtensionsPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="$(MicrosoftExtensionsPackageVersion)" />
|
||||
<PackageReference Include="Netmash.Application.DataContracts" Version="$(NetmashApplicationPackageVersion)" />
|
||||
<PackageReference Include="System.Security.Principal.Windows" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using MediatR;
|
||||
using Netmash.Application.DataContracts;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
@ -8,31 +9,29 @@ namespace NetworkResurrector.Agent.Application.Queries
|
|||
{
|
||||
public class GetSystemVersion
|
||||
{
|
||||
public class Query : Query<Model>
|
||||
{
|
||||
public Query() { }
|
||||
}
|
||||
public class Query : IRequest<Model> { }
|
||||
|
||||
public class Model
|
||||
{
|
||||
public string Version { get; set; }
|
||||
public DateTime LastUpdateDate { get; set; }
|
||||
}
|
||||
public record Model(string Version, DateTime LastUpdateDate);
|
||||
|
||||
public class QueryHandler : IRequestHandler<Query, Model>
|
||||
{
|
||||
public QueryHandler()
|
||||
{
|
||||
}
|
||||
public QueryHandler() { }
|
||||
|
||||
public async Task<Model> Handle(Query request, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = new Model()
|
||||
{
|
||||
Version = "1.0.0",
|
||||
LastUpdateDate = DateTime.Now
|
||||
};
|
||||
var version = Environment.GetEnvironmentVariable("APP_VERSION");
|
||||
var appDate = Environment.GetEnvironmentVariable("APP_DATE");
|
||||
|
||||
if (string.IsNullOrEmpty(version))
|
||||
version = Assembly.GetEntryAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;
|
||||
|
||||
if (!DateTime.TryParse(appDate, out var lastUpdateDate))
|
||||
{
|
||||
var location = Assembly.GetExecutingAssembly().Location;
|
||||
lastUpdateDate = File.GetLastWriteTime(location);
|
||||
}
|
||||
|
||||
var result = new Model(version, lastUpdateDate);
|
||||
return await Task.FromResult(result);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
using Netmash.Application.DataContracts;
|
||||
using MediatR;
|
||||
using NetworkResurrector.Agent.PublishedLanguage.Dto;
|
||||
using NetworkResurrector.Agent.PublishedLanguage.Events;
|
||||
|
||||
namespace NetworkResurrector.Agent.PublishedLanguage.Commands
|
||||
{
|
||||
public class Cancel : Command<CancelResult>
|
||||
public class Cancel : IRequest<CancelResult>
|
||||
{
|
||||
public ActionOwner Owner { get; set; }
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
using Netmash.Application.DataContracts;
|
||||
using MediatR;
|
||||
using NetworkResurrector.Agent.PublishedLanguage.Dto;
|
||||
using NetworkResurrector.Agent.PublishedLanguage.Events;
|
||||
|
||||
namespace NetworkResurrector.Agent.PublishedLanguage.Commands
|
||||
{
|
||||
public class Lock : Command<LockResult>
|
||||
public class Lock : IRequest<LockResult>
|
||||
{
|
||||
public ActionOwner Owner { get; set; }
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
using Netmash.Application.DataContracts;
|
||||
using MediatR;
|
||||
using NetworkResurrector.Agent.PublishedLanguage.Dto;
|
||||
using NetworkResurrector.Agent.PublishedLanguage.Events;
|
||||
|
||||
namespace NetworkResurrector.Agent.PublishedLanguage.Commands
|
||||
{
|
||||
public class Logout : Command<LogoutResult>
|
||||
public class Logout : IRequest<LogoutResult>
|
||||
{
|
||||
public ActionOwner Owner { get; set; }
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
using Netmash.Application.DataContracts;
|
||||
using MediatR;
|
||||
using NetworkResurrector.Agent.PublishedLanguage.Dto;
|
||||
using NetworkResurrector.Agent.PublishedLanguage.Events;
|
||||
|
||||
namespace NetworkResurrector.Agent.PublishedLanguage.Commands
|
||||
{
|
||||
public class Restart : Command<RestartResult>
|
||||
public class Restart : IRequest<RestartResult>
|
||||
{
|
||||
public ActionOwner Owner { get; set; }
|
||||
public ActionOptions Options { get; set; }
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
using Netmash.Application.DataContracts;
|
||||
using MediatR;
|
||||
using NetworkResurrector.Agent.PublishedLanguage.Dto;
|
||||
using NetworkResurrector.Agent.PublishedLanguage.Events;
|
||||
|
||||
namespace NetworkResurrector.Agent.PublishedLanguage.Commands
|
||||
{
|
||||
public class Shutdown : Command<ShutdownResult>
|
||||
public class Shutdown : IRequest<ShutdownResult>
|
||||
{
|
||||
public ActionOwner Owner { get; set; }
|
||||
public ActionOptions Options { get; set; }
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
using Netmash.Application.DataContracts;
|
||||
using MediatR;
|
||||
using NetworkResurrector.Agent.PublishedLanguage.Dto;
|
||||
using NetworkResurrector.Agent.PublishedLanguage.Events;
|
||||
|
||||
namespace NetworkResurrector.Agent.PublishedLanguage.Commands
|
||||
{
|
||||
public class Sleep : Command<SleepResult>
|
||||
public class Sleep : IRequest<SleepResult>
|
||||
{
|
||||
public ActionOwner Owner { get; set; }
|
||||
public ActionOptions Options { get; set; }
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Netmash.Application.DataContracts" Version="$(NetmashApplicationPackageVersion)" />
|
||||
<PackageReference Include="MediatR" Version="$(MediatRPackageVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
<PackageReference Include="Netmash.Security.Authentication.Identity" Version="$(NetmashSecurityAuthenticationPackageVersion)" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="$(SerilogPackageVersion)" />
|
||||
<PackageReference Include="Serilog.Extensions.Logging" Version="$(SerilogExtensionsPackageVersion)" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="$(SerilogSinksConsolePackageVersion)" />
|
||||
<PackageReference Include="Serilog.Sinks.MSSqlServer" Version="$(SerilogSinksMSSqlServerPackageVersion)" />
|
||||
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="$(AutoMapperExtensionsPackageVersion)" />
|
||||
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="$(MediatRPackageVersion)" />
|
||||
|
|
|
@ -3,6 +3,8 @@ using Microsoft.Extensions.Configuration;
|
|||
using Microsoft.Extensions.Logging;
|
||||
using NetworkResurrector.Agent.PublishedLanguage.Dto;
|
||||
using NetworkResurrector.Agent.Wrapper.Services;
|
||||
using NetworkResurrector.Api.Application.Extensions;
|
||||
using NetworkResurrector.Api.Application.Services.Abstractions;
|
||||
using NetworkResurrector.Api.Domain.Constants;
|
||||
using NetworkResurrector.Api.Domain.Repositories;
|
||||
using NetworkResurrector.Api.PublishedLanguage.Commands;
|
||||
|
@ -21,14 +23,16 @@ namespace NetworkResurrector.Api.Application.CommandHandlers
|
|||
private readonly INetworkRepository _repository;
|
||||
private readonly IResurrectorAgentService _resurrectorAgentService;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly INotificationService _notificationService;
|
||||
|
||||
public ShutdownMachineHandler(ILogger<ShutdownMachineHandler> logger, IResurrectorService resurrectorService, INetworkRepository repository, IResurrectorAgentService resurrectorAgentService, IConfiguration configuration)
|
||||
public ShutdownMachineHandler(ILogger<ShutdownMachineHandler> logger, IResurrectorService resurrectorService, INetworkRepository repository, IResurrectorAgentService resurrectorAgentService, IConfiguration configuration, INotificationService notificationService)
|
||||
{
|
||||
_logger=logger;
|
||||
_resurrectorService=resurrectorService;
|
||||
_repository=repository;
|
||||
_resurrectorAgentService=resurrectorAgentService;
|
||||
_configuration=configuration;
|
||||
_notificationService=notificationService;
|
||||
}
|
||||
|
||||
public async Task<MachineShutdown> Handle(ShutdownMachine command, CancellationToken cancellationToken)
|
||||
|
@ -42,7 +46,8 @@ namespace NetworkResurrector.Api.Application.CommandHandlers
|
|||
var ipAddressOrMachineName = machine.IPv4Address ?? machine.MachineName;
|
||||
MachineShutdown result;
|
||||
|
||||
switch (powerConfiguration.Performer.PerformerCode)
|
||||
var performer = powerConfiguration.Performer.PerformerCode;
|
||||
switch (performer)
|
||||
{
|
||||
case PowerActionPerformers.NETWORK_RESURRECTOR_SERVER:
|
||||
if (command.Delay.HasValue || command.Force)
|
||||
|
@ -64,10 +69,12 @@ namespace NetworkResurrector.Api.Application.CommandHandlers
|
|||
break;
|
||||
|
||||
default:
|
||||
throw new Exception($"Power action performer {powerConfiguration.Performer.PerformerCode} is not implemented.");
|
||||
throw new Exception($"Power action performer {performer} is not implemented.");
|
||||
}
|
||||
|
||||
_logger.LogDebug($"Machine {command.MachineId} shutdown finished. Success: {result.Success}; Status: {result.Status}");
|
||||
var notificationContext = machine.ToNotificationContext(result.Status, performer);
|
||||
await _notificationService.Notify(NotificationType.Shutdown, notificationContext, cancellationToken);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
using MediatR;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NetworkResurrector.Api.Application.Extensions;
|
||||
using NetworkResurrector.Api.Application.Services.Abstractions;
|
||||
using NetworkResurrector.Api.Domain.Constants;
|
||||
using NetworkResurrector.Api.Domain.Repositories;
|
||||
using NetworkResurrector.Api.PublishedLanguage.Commands;
|
||||
|
@ -16,12 +18,14 @@ namespace NetworkResurrector.Api.Application.CommandHandlers
|
|||
private readonly ILogger<WakeMachineHandler> _logger;
|
||||
private readonly IResurrectorService _resurrectorService;
|
||||
private readonly INetworkRepository _repository;
|
||||
private readonly INotificationService _notificationService;
|
||||
|
||||
public WakeMachineHandler(ILogger<WakeMachineHandler> logger, IResurrectorService resurrectorService, INetworkRepository repository)
|
||||
public WakeMachineHandler(ILogger<WakeMachineHandler> logger, IResurrectorService resurrectorService, INetworkRepository repository, INotificationService notificationService)
|
||||
{
|
||||
_logger=logger;
|
||||
_resurrectorService=resurrectorService;
|
||||
_repository=repository;
|
||||
_notificationService=notificationService;
|
||||
}
|
||||
|
||||
public async Task<MachineWaked> Handle(WakeMachine command, CancellationToken cancellationToken)
|
||||
|
@ -33,7 +37,8 @@ namespace NetworkResurrector.Api.Application.CommandHandlers
|
|||
//log activity
|
||||
|
||||
MachineWaked result;
|
||||
switch (powerConfiguration.Performer.PerformerCode)
|
||||
var performer = powerConfiguration.Performer.PerformerCode;
|
||||
switch (performer)
|
||||
{
|
||||
case PowerActionPerformers.NETWORK_RESURRECTOR_SERVER:
|
||||
var wakeResult = await _resurrectorService.Wake(machine.MACAddress);
|
||||
|
@ -45,6 +50,8 @@ namespace NetworkResurrector.Api.Application.CommandHandlers
|
|||
throw new Exception($"Power action performer {powerConfiguration.Performer.PerformerCode} is not implemented.");
|
||||
}
|
||||
|
||||
var notificationContext = machine.ToNotificationContext(result.Status, performer);
|
||||
await _notificationService.Notify(NotificationType.Wake, notificationContext, cancellationToken);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
using NetworkResurrector.Api.Application.Services;
|
||||
using NetworkResurrector.Api.Application.Services.Abstractions;
|
||||
|
||||
namespace NetworkResurrector.Api.Application
|
||||
{
|
||||
|
@ -7,7 +8,7 @@ namespace NetworkResurrector.Api.Application
|
|||
{
|
||||
public static void AddApplicationServices(this IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<CorrelationManager>();
|
||||
services.AddSingleton<INotificationService, NotificationService>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
using NetworkResurrector.Api.Domain.Entities;
|
||||
using NetworkResurrector.Api.Domain.Models.Notifications;
|
||||
|
||||
namespace NetworkResurrector.Api.Application.Extensions
|
||||
{
|
||||
internal static class EntityExtensions
|
||||
{
|
||||
public static NotificationContext ToNotificationContext(this Machine machine, string actionStatus = null, string actionPerformer = null)
|
||||
{
|
||||
return new NotificationContext()
|
||||
{
|
||||
MachineName = machine.MachineName,
|
||||
MachineFullName = machine.FullMachineName,
|
||||
MachineIP = machine.IPv4Address,
|
||||
ActionStatus = actionStatus,
|
||||
ActionPerformer = actionPerformer
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,11 +6,12 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AutoMapper" Version="$(AutoMapperPackageVersion)" />
|
||||
<PackageReference Include="Correo.PublishedLanguage" Version="$(CorreoPublishedLanguage)" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="$(MicrosoftExtensionsPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="$(MicrosoftExtensionsPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="$(MicrosoftExtensionsPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="$(MicrosoftExtensionsPackageVersion)" />
|
||||
<PackageReference Include="Netmash.Application.DataContracts" Version="$(NetmashApplicationPackageVersion)" />
|
||||
<PackageReference Include="NBB.Messaging.Abstractions" Version="$(NBBPackageVersion)" />
|
||||
<PackageReference Include="NetworkResurrector.Agent.Wrapper" Version="1.0.3.1" />
|
||||
<PackageReference Include="NetworkResurrector.Server.Wrapper" Version="1.0.3.3" />
|
||||
</ItemGroup>
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
using AutoMapper;
|
||||
using MediatR;
|
||||
using Netmash.Application.DataContracts;
|
||||
using NetworkResurrector.Api.Domain.Repositories;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -9,10 +8,7 @@ namespace NetworkResurrector.Api.Application.Queries
|
|||
{
|
||||
public class GetMachines
|
||||
{
|
||||
public class Query : Query<Model[]>
|
||||
{
|
||||
public Query() { }
|
||||
}
|
||||
public class Query : IRequest<Model[]> { }
|
||||
|
||||
public class Model
|
||||
{
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using MediatR;
|
||||
using Netmash.Application.DataContracts;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
|
@ -10,12 +9,7 @@ namespace NetworkResurrector.Api.Application.Queries
|
|||
{
|
||||
public class GetSystemVersion
|
||||
{
|
||||
public class Query : Query<Model>
|
||||
{
|
||||
public Query()
|
||||
{
|
||||
}
|
||||
}
|
||||
public class Query : IRequest<Model> { }
|
||||
|
||||
public record Model(string Version, DateTime LastUpdateDate);
|
||||
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
using NetworkResurrector.Api.Domain.Constants;
|
||||
using NetworkResurrector.Api.Domain.Models.Notifications;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace NetworkResurrector.Api.Application.Services.Abstractions
|
||||
{
|
||||
public interface INotificationService
|
||||
{
|
||||
Task Notify(NotificationType type, NotificationContext context, CancellationToken cancellationToken = default);
|
||||
Task Notify(NotificationType type, string machineName, string machineFullName, string machineIP, string actionStatus, string actionPerformer, string errorMessage, CancellationToken cancellationToken = default);
|
||||
Task NotifyError(string errorMessage, CancellationToken cancellationToken = default);
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace NetworkResurrector.Api.Application.Services
|
||||
{
|
||||
public class CorrelationManager
|
||||
{
|
||||
public Guid CorrelationId { get; }
|
||||
|
||||
public CorrelationManager()
|
||||
{
|
||||
CorrelationId=Guid.NewGuid();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
using Correo.PublishedLanguage.Commands;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NBB.Messaging.Abstractions;
|
||||
using NetworkResurrector.Api.Application.Services.Abstractions;
|
||||
using NetworkResurrector.Api.Domain.Constants;
|
||||
using NetworkResurrector.Api.Domain.Models.Notifications;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace NetworkResurrector.Api.Application.Services
|
||||
{
|
||||
internal class NotificationService : INotificationService
|
||||
{
|
||||
private readonly IMessageBusPublisher _messageBusPublisher;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly ILogger<NotificationService> _logger;
|
||||
|
||||
public NotificationService(IMessageBusPublisher messageBusPublisher, IConfiguration configuration, ILogger<NotificationService> logger)
|
||||
{
|
||||
_messageBusPublisher=messageBusPublisher;
|
||||
_configuration=configuration;
|
||||
_logger=logger;
|
||||
}
|
||||
|
||||
private Notification GetNotification(NotificationType type, NotificationContext context)
|
||||
{
|
||||
var template = _configuration.GetSection($"Notifications:{type}").Get<NotificationTemplate>();
|
||||
var notification = new Notification()
|
||||
{
|
||||
To = template.To,
|
||||
Subject = ReplacePlaceholders(template.Subject, context),
|
||||
Body = ReplacePlaceholders(template.Body, context)
|
||||
};
|
||||
|
||||
return notification;
|
||||
}
|
||||
|
||||
private string ReplacePlaceholders(string text, NotificationContext context)
|
||||
{
|
||||
var regex = new Regex(@"\{[A-Z_:0-9]+\}");
|
||||
MatchCollection matches = regex.Matches(text);
|
||||
var distinctMatches = matches.OfType<Match>().Select(m => m.Value).Distinct();
|
||||
foreach (var placeholder in distinctMatches)
|
||||
{
|
||||
var placeholderValue = GetPlaceholderValue(placeholder, context);
|
||||
if (string.IsNullOrEmpty(placeholderValue))
|
||||
_logger.LogWarning($"The placeholder '{placeholder}' was not recognized and was ignored.");
|
||||
else
|
||||
text = text.Replace(placeholder, placeholderValue);
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
private string GetPlaceholderValue(string placeholder, NotificationContext context)
|
||||
{
|
||||
return placeholder switch
|
||||
{
|
||||
NotificationPlaceholder.MACHINE_NAME => context.MachineName,
|
||||
NotificationPlaceholder.MACHINE_FULLNAME => context.MachineFullName,
|
||||
NotificationPlaceholder.MACHINE_IP => context.MachineIP,
|
||||
NotificationPlaceholder.ACTION_STATUS => context.ActionStatus,
|
||||
NotificationPlaceholder.ACTION_PERFORMER => context.ActionPerformer,
|
||||
NotificationPlaceholder.SYSTEM_DATETIME => DateTime.Now.ToString(),
|
||||
NotificationPlaceholder.ERROR_MESSGE => context.ErrorMessage,
|
||||
NotificationPlaceholder.NEWLINE => Environment.NewLine,
|
||||
_ => GetRegexPlaceholderValue(placeholder),
|
||||
};
|
||||
}
|
||||
|
||||
private string GetRegexPlaceholderValue(string placeholder)
|
||||
{
|
||||
var placeholderValue = GetNewLinesPlaceholderValue(placeholder);
|
||||
// check here for other regex placeholders
|
||||
return placeholderValue;
|
||||
}
|
||||
|
||||
private string GetNewLinesPlaceholderValue(string placeholder)
|
||||
{
|
||||
var regex = new Regex(@"\{NEWLINE(?::\d+)?\}");
|
||||
var match = regex.Match(placeholder);
|
||||
if (!match.Success)
|
||||
return null;
|
||||
|
||||
var linesCountRegex = new Regex(@"(?<=:)[0-9]+");
|
||||
var linesCountMatch = linesCountRegex.Match(placeholder);
|
||||
if (!linesCountMatch.Success)
|
||||
return null;
|
||||
|
||||
var linesCount = Convert.ToInt32(linesCountMatch.Value);
|
||||
var placeHolderValue = string.Concat(Enumerable.Repeat(Environment.NewLine, linesCount));
|
||||
|
||||
return placeHolderValue;
|
||||
}
|
||||
|
||||
public async Task Notify(NotificationType type, NotificationContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var notification = GetNotification(type, context);
|
||||
var cmd = new SendEmail()
|
||||
{
|
||||
Subject = notification.Subject,
|
||||
Body = notification.Body,
|
||||
IsBodyHtml = false,
|
||||
To = notification.To.Select(z => new SendEmail.MailAddress(z))
|
||||
};
|
||||
|
||||
await _messageBusPublisher.PublishAsync(cmd, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task NotifyError(string errorMessage, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var context = new NotificationContext()
|
||||
{
|
||||
ErrorMessage = errorMessage
|
||||
};
|
||||
|
||||
await Notify(NotificationType.Error, context, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task Notify(NotificationType type, string machineName, string machineFullName, string machineIP, string actionStatus, string actionPerformer, string errorMessage, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var context = new NotificationContext()
|
||||
{
|
||||
MachineName = machineName,
|
||||
MachineFullName = machineFullName,
|
||||
MachineIP = machineIP,
|
||||
ActionStatus = actionStatus,
|
||||
ActionPerformer = actionPerformer,
|
||||
ErrorMessage= errorMessage,
|
||||
};
|
||||
|
||||
await Notify(type, context, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
namespace NetworkResurrector.Api.Domain.Constants
|
||||
{
|
||||
public enum NotificationType
|
||||
{
|
||||
Wake,
|
||||
Shutdown,
|
||||
Error
|
||||
}
|
||||
|
||||
public struct NotificationPlaceholder
|
||||
{
|
||||
public const string
|
||||
MACHINE_NAME = "{MACHINE_NAME}",
|
||||
MACHINE_FULLNAME = "{MACHINE_FULLNAME}",
|
||||
MACHINE_IP = "{MACHINE_IP}",
|
||||
ACTION_STATUS = "{ACTION_STATUS}",
|
||||
ACTION_PERFORMER = "{ACTION_PERFORMER}",
|
||||
SYSTEM_DATETIME = "{SYSTEM_DATETIME}",
|
||||
ERROR_MESSGE = "{ERROR_MESSGE}",
|
||||
NEWLINE = "{NEWLINE}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
namespace NetworkResurrector.Api.Domain.Models.Notifications
|
||||
{
|
||||
public record Notification
|
||||
{
|
||||
public string[] To { get; init; }
|
||||
public string Subject { get; init; }
|
||||
public string Body { get; init; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
namespace NetworkResurrector.Api.Domain.Models.Notifications
|
||||
{
|
||||
public record NotificationContext
|
||||
{
|
||||
public string MachineName { get; set; }
|
||||
public string MachineFullName { get; set; }
|
||||
public string MachineIP { get; set; }
|
||||
public string ActionStatus { get; set; }
|
||||
public string ActionPerformer { get; set; }
|
||||
public string ErrorMessage { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
namespace NetworkResurrector.Api.Domain.Models.Notifications
|
||||
{
|
||||
public record NotificationTemplate
|
||||
{
|
||||
public string[] To { get; init; }
|
||||
public string Subject { get; init; }
|
||||
public string Body { get; init; }
|
||||
}
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
using Netmash.Application.DataContracts;
|
||||
using MediatR;
|
||||
using NetworkResurrector.Api.PublishedLanguage.Events;
|
||||
|
||||
namespace NetworkResurrector.Api.PublishedLanguage.Commands
|
||||
{
|
||||
public class CancelAction : Command<ActionCanceled>
|
||||
public class CancelAction : IRequest<ActionCanceled>
|
||||
{
|
||||
public int MachineId { get; set; }
|
||||
public int ActionId { get; set; }
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
using Netmash.Application.DataContracts;
|
||||
using MediatR;
|
||||
using NetworkResurrector.Api.PublishedLanguage.Events;
|
||||
|
||||
namespace NetworkResurrector.Api.PublishedLanguage.Commands
|
||||
{
|
||||
public class LockMachine : Command<MachineLocked>
|
||||
public class LockMachine : IRequest<MachineLocked>
|
||||
{
|
||||
public int MachineId { get; set; }
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
using Netmash.Application.DataContracts;
|
||||
using MediatR;
|
||||
using NetworkResurrector.Api.PublishedLanguage.Events;
|
||||
|
||||
namespace NetworkResurrector.Api.PublishedLanguage.Commands
|
||||
{
|
||||
public class LogoutUser : Command<UserLoggedOut>
|
||||
public class LogoutUser : IRequest<UserLoggedOut>
|
||||
{
|
||||
public int MachineId { get; set; }
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
using Netmash.Application.DataContracts;
|
||||
using MediatR;
|
||||
using NetworkResurrector.Api.PublishedLanguage.Events;
|
||||
|
||||
namespace NetworkResurrector.Api.PublishedLanguage.Commands
|
||||
{
|
||||
public class PingMachine : Command<MachinePinged>
|
||||
public class PingMachine : IRequest<MachinePinged>
|
||||
{
|
||||
public int MachineId { get; set; }
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
using Netmash.Application.DataContracts;
|
||||
using MediatR;
|
||||
using NetworkResurrector.Api.PublishedLanguage.Events;
|
||||
|
||||
namespace NetworkResurrector.Api.PublishedLanguage.Commands
|
||||
{
|
||||
public class RestartMachine : Command<MachineRestarted>
|
||||
public class RestartMachine : IRequest<MachineRestarted>
|
||||
{
|
||||
public int MachineId { get; set; }
|
||||
public int? Delay { get; set; }
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
using Netmash.Application.DataContracts;
|
||||
using MediatR;
|
||||
using NetworkResurrector.Api.PublishedLanguage.Events;
|
||||
|
||||
namespace NetworkResurrector.Api.PublishedLanguage.Commands
|
||||
{
|
||||
public class ShutdownMachine : Command<MachineShutdown>
|
||||
public class ShutdownMachine : IRequest<MachineShutdown>
|
||||
{
|
||||
public int MachineId { get; set; }
|
||||
public int? Delay { get; set; }
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
using Netmash.Application.DataContracts;
|
||||
using MediatR;
|
||||
using NetworkResurrector.Api.PublishedLanguage.Events;
|
||||
|
||||
namespace NetworkResurrector.Api.PublishedLanguage.Commands
|
||||
{
|
||||
public class SleepMachine : Command<MachineSlept>
|
||||
public class SleepMachine : IRequest<MachineSlept>
|
||||
{
|
||||
public int MachineId { get; set; }
|
||||
public int? Delay { get; set; }
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
using Netmash.Application.DataContracts;
|
||||
using MediatR;
|
||||
using NetworkResurrector.Api.PublishedLanguage.Events;
|
||||
|
||||
namespace NetworkResurrector.Api.PublishedLanguage.Commands
|
||||
{
|
||||
public class WakeMachine : Command<MachineWaked>
|
||||
public class WakeMachine : IRequest<MachineWaked>
|
||||
{
|
||||
public int MachineId { get; set; }
|
||||
}
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
<TargetFramework>net6.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Netmash.Application.DataContracts" Version="$(NetmashApplicationPackageVersion)" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MediatR" Version="$(MediatRPackageVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
using Microsoft.AspNetCore.Diagnostics;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NetworkResurrector.Api.Application.Services;
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
|
@ -12,27 +11,23 @@ namespace NetworkResurrector.Api.Controllers
|
|||
[ApiExplorerSettings(IgnoreApi = true)]
|
||||
public class ErrorsController : ControllerBase
|
||||
{
|
||||
private readonly CorrelationManager _correlationManager;
|
||||
|
||||
public ErrorsController(CorrelationManager correlationManager)
|
||||
public ErrorsController()
|
||||
{
|
||||
_correlationManager=correlationManager;
|
||||
}
|
||||
|
||||
internal record Error(int Status, string Title, Guid CorrelationId, string Message = null);
|
||||
internal record Error(int Status, string Title, string Message = null);
|
||||
|
||||
[Route("error")]
|
||||
public IActionResult HandleErrors()
|
||||
{
|
||||
var correlationId = _correlationManager.CorrelationId;
|
||||
var context = HttpContext.Features.Get<IExceptionHandlerFeature>();
|
||||
var exception = context.Error;
|
||||
|
||||
return exception switch
|
||||
{
|
||||
ValidationException => StatusCode(StatusCodes.Status404NotFound, new Error(StatusCodes.Status404NotFound, "Internal server error", correlationId, exception.Message)),
|
||||
UnauthorizedAccessException => StatusCode(StatusCodes.Status401Unauthorized, new Error(StatusCodes.Status401Unauthorized, "Internal server error", correlationId)),
|
||||
_ => StatusCode(StatusCodes.Status500InternalServerError, new Error(StatusCodes.Status500InternalServerError, "Internal server error", correlationId)),
|
||||
ValidationException => StatusCode(StatusCodes.Status404NotFound, new Error(StatusCodes.Status404NotFound, "Internal server error", exception.Message)),
|
||||
UnauthorizedAccessException => StatusCode(StatusCodes.Status401Unauthorized, new Error(StatusCodes.Status401Unauthorized, "Internal server error")),
|
||||
_ => StatusCode(StatusCodes.Status500InternalServerError, new Error(StatusCodes.Status500InternalServerError, "Internal server error")),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
namespace NetworkResurrector.Api.Extensions
|
||||
{
|
||||
internal static class DataTypeExtensions
|
||||
{
|
||||
public static string Nullify(this string value)
|
||||
=> string.IsNullOrWhiteSpace(value) ? null : value;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
using Microsoft.Extensions.Configuration;
|
||||
using Serilog;
|
||||
using Serilog.Configuration;
|
||||
using Serilog.Sinks.MSSqlServer;
|
||||
using System;
|
||||
|
||||
namespace NetworkResurrector.Api.Extensions
|
||||
{
|
||||
internal static class LoggingExtensions
|
||||
{
|
||||
internal static LoggerSinkConfiguration ConfiguredDestinations(this LoggerSinkConfiguration writeTo, IConfiguration configuration)
|
||||
{
|
||||
var sqlServerEnabled = configuration.GetValue<bool>("Logs:SqlServer:Enabled");
|
||||
var sqlServerConnection = configuration.GetValue<string>("Logs:SqlServer:Connection");
|
||||
var seqEnabled = configuration.GetValue<bool>("Logs:Seq:Enabled");
|
||||
var seqUrl = configuration.GetValue<string>("Logs:Seq:Url");
|
||||
var seqApiKey = configuration.GetValue<string>("Logs:Seq:ApiKey");
|
||||
|
||||
if (sqlServerEnabled && string.IsNullOrWhiteSpace(sqlServerConnection))
|
||||
throw new Exception("If SqlServer logging is enabled, the SqlServer connection must be configured.");
|
||||
if (seqEnabled && string.IsNullOrWhiteSpace(seqUrl))
|
||||
throw new Exception("If Seq logging is enabled, the Seq URL must be configured.");
|
||||
|
||||
if (sqlServerEnabled)
|
||||
{
|
||||
var columnOptions = new ColumnOptions();
|
||||
columnOptions.Store.Remove(StandardColumn.Properties);
|
||||
columnOptions.Store.Remove(StandardColumn.MessageTemplate);
|
||||
columnOptions.Store.Add(StandardColumn.LogEvent);
|
||||
var mssqlSinkOptions = new MSSqlServerSinkOptions() { AutoCreateSqlTable = true, TableName = "__Logs" };
|
||||
|
||||
writeTo.MSSqlServer(sqlServerConnection, mssqlSinkOptions, columnOptions: columnOptions);
|
||||
}
|
||||
|
||||
if (seqEnabled)
|
||||
writeTo.Seq(seqUrl, apiKey: seqApiKey.Nullify());
|
||||
|
||||
return writeTo;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using NBB.Messaging.Abstractions;
|
||||
using NetworkResurrector.Api.Services;
|
||||
|
||||
namespace NetworkResurrector.Api.Extensions
|
||||
{
|
||||
|
||||
public static class MessagingExtensions
|
||||
{
|
||||
public static void AddMessageBus(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
var enabled = configuration.GetValue<bool>("Messaging:Enabled", false);
|
||||
if (enabled)
|
||||
services.AddMessageBus().AddNatsTransport(configuration);
|
||||
else
|
||||
services.AddSingleton<IMessageBusPublisher, EmptyMessageBusPublisher>();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,7 +15,6 @@ using NetworkResurrector.Api.Domain.Data;
|
|||
using NetworkResurrector.Api.Services;
|
||||
using NetworkResurrector.Server.Wrapper;
|
||||
using Newtonsoft.Json;
|
||||
using System.Reflection;
|
||||
|
||||
namespace NetworkResurrector.Api.Extensions
|
||||
{
|
||||
|
@ -33,7 +32,7 @@ namespace NetworkResurrector.Api.Extensions
|
|||
services.AddScoped<IUserService, UserService>();
|
||||
|
||||
// MediatR
|
||||
services.AddMediatR(GetMediatRAssemblies());
|
||||
services.AddMediatR(typeof(Application.Queries.GetMachines).Assembly);
|
||||
services.AddScoped(typeof(IPipelineBehavior<,>), typeof(RequestPreProcessorBehavior<,>));
|
||||
services.AddScoped(typeof(IPipelineBehavior<,>), typeof(RequestPostProcessorBehavior<,>));
|
||||
|
||||
|
@ -53,14 +52,11 @@ namespace NetworkResurrector.Api.Extensions
|
|||
services.AddMigration(DatabaseType.SQLServer, MetadataLocation.Database);
|
||||
services.AddDataAccess();
|
||||
|
||||
// Application
|
||||
// Application services
|
||||
services.AddApplicationServices();
|
||||
}
|
||||
|
||||
private static Assembly[] GetMediatRAssemblies()
|
||||
{
|
||||
var assembly = typeof(Application.Queries.GetMachines).Assembly;
|
||||
return new Assembly[] { assembly };
|
||||
// Messaging
|
||||
services.AddMessageBus(configuration);
|
||||
}
|
||||
|
||||
public static void Configure(this IApplicationBuilder app)
|
||||
|
|
|
@ -11,13 +11,14 @@
|
|||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="$(MicrosoftExtensionsPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="$(MicrosoftExtensionsPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="$(MicrosoftExtensionsPackageVersion)" />
|
||||
<PackageReference Include="NBB.Messaging.Nats" Version="$(NBBPackageVersion)" />
|
||||
<PackageReference Include="Netmash.Extensions.Swagger" Version="$(NetmashExtensionsSwaggerPackageVersion)" />
|
||||
<PackageReference Include="Netmash.Infrastructure.DatabaseMigration" Version="$(NetmashDatabaseMigrationPackageVersion)" />
|
||||
<PackageReference Include="Netmash.Infrastructure.DatabaseMigration" Version="$(NetmashDatabaseMigrationPackageVersion)" />
|
||||
<PackageReference Include="Netmash.Security.Authentication.Identity" Version="$(NetmashSecurityAuthenticationPackageVersion)" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="$(SerilogPackageVersion)" />
|
||||
<PackageReference Include="Serilog.Extensions.Logging" Version="$(SerilogExtensionsPackageVersion)" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="$(SerilogSinksConsolePackageVersion)" />
|
||||
<PackageReference Include="Serilog.Sinks.MSSqlServer" Version="$(SerilogSinksMSSqlServerPackageVersion)" />
|
||||
<PackageReference Include="Serilog.Sinks.Seq" Version="$(SerilogSinksSeqPackageVersion)" />
|
||||
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="$(AutoMapperExtensionsPackageVersion)" />
|
||||
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="$(MediatRPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="$(MicrosoftExtensionsHostingPackageVersion)" />
|
||||
|
|
|
@ -4,9 +4,6 @@ using Microsoft.Extensions.Configuration;
|
|||
using Microsoft.Extensions.Hosting;
|
||||
using NetworkResurrector.Api.Extensions;
|
||||
using Serilog;
|
||||
using Serilog.Core;
|
||||
using Serilog.Events;
|
||||
using Serilog.Sinks.MSSqlServer;
|
||||
using System;
|
||||
|
||||
namespace NetworkResurrector.Api
|
||||
|
@ -19,27 +16,11 @@ namespace NetworkResurrector.Api
|
|||
|
||||
builder.Host.UseSerilog((_, lc) =>
|
||||
{
|
||||
var connectionString = builder.Configuration.GetConnectionString("DatabaseConnection");
|
||||
var loggingLevelParam = builder.Configuration.GetValue<string>("Logging:LogLevel:Default");
|
||||
|
||||
var loggingLevelOk = Enum.TryParse(loggingLevelParam, out LogEventLevel loggingLevel);
|
||||
if (!loggingLevelOk)
|
||||
throw new Exception($"Logging level '{loggingLevelParam}' is not valid.");
|
||||
|
||||
var loggingLevelSwitch = new LoggingLevelSwitch(loggingLevel);
|
||||
|
||||
var columnOptions = new ColumnOptions();
|
||||
columnOptions.Store.Remove(StandardColumn.Properties);
|
||||
columnOptions.Store.Remove(StandardColumn.MessageTemplate);
|
||||
columnOptions.Store.Add(StandardColumn.LogEvent);
|
||||
var mssqlSinkOptions = new MSSqlServerSinkOptions() { AutoCreateSqlTable = true, TableName = "__Logs" };
|
||||
|
||||
lc
|
||||
.MinimumLevel.ControlledBy(loggingLevelSwitch)
|
||||
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
|
||||
.ReadFrom.Configuration(builder.Configuration)
|
||||
.Enrich.FromLogContext()
|
||||
.WriteTo.Console()
|
||||
.WriteTo.MSSqlServer(connectionString, mssqlSinkOptions, columnOptions: columnOptions);
|
||||
.WriteTo.ConfiguredDestinations(builder.Configuration);
|
||||
});
|
||||
|
||||
builder.Services.ConfigureServices(builder.Configuration);
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
using Microsoft.Extensions.Logging;
|
||||
using NBB.Messaging.Abstractions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace NetworkResurrector.Api.Services
|
||||
{
|
||||
internal class EmptyMessageBusPublisher : IMessageBusPublisher
|
||||
{
|
||||
private readonly ILogger<EmptyMessageBusPublisher> _logger;
|
||||
|
||||
public EmptyMessageBusPublisher(ILogger<EmptyMessageBusPublisher> logger)
|
||||
{
|
||||
_logger=logger;
|
||||
}
|
||||
|
||||
public Task PublishAsync<T>(T message, MessagingPublisherOptions publisherOptions = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_logger.LogDebug($"EmptyMessageBusPublisher: Message {message.GetType().FullName} was published.");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,11 +3,23 @@
|
|||
"ConnectionStrings": {
|
||||
"DatabaseConnection": "Server=#########;Database=#########;User Id=#########;Password=#########;MultipleActiveResultSets=true"
|
||||
},
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Debug",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft.Hosting.Lifetime": "Information"
|
||||
"Serilog": {
|
||||
"MinimumLevel": {
|
||||
"Default": "Information",
|
||||
"Override": {
|
||||
"Microsoft": "Information"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Logs": {
|
||||
"SqlServer": {
|
||||
"Enabled": false,
|
||||
"Connection": "Server=#########;Database=#########;User Id=#########;Password=#########;MultipleActiveResultSets=true"
|
||||
},
|
||||
"Seq": {
|
||||
"Enabled": false,
|
||||
"Url": "",
|
||||
"ApiKey": ""
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
|
@ -15,10 +27,38 @@
|
|||
"Code": "NETWORK_RESURRECTOR_API"
|
||||
},
|
||||
"IdentityServer": {
|
||||
//"BaseAddress": "http://localhost:5063/"
|
||||
"BaseAddress": "https://lab.code-rove.com/identity-server-api/"
|
||||
"BaseAddress": "http://<host>:<port>/api/"
|
||||
},
|
||||
"NetworkResurrectorServer": {
|
||||
"BaseAddress": "http://localhost:5062"
|
||||
"BaseAddress": "http://<host>:<port>"
|
||||
},
|
||||
"Messaging": {
|
||||
"Enabled": false,
|
||||
"TopicPrefix": "HomeLab.",
|
||||
"Source": "NetworkResurrector.Api",
|
||||
"Nats": {
|
||||
"NatsUrl": "nats://<host>:4222",
|
||||
"Cluster": "<cluster>",
|
||||
"ClientId": "NetworkResurrector_Api",
|
||||
"QGroup": "NetworkResurrector.Api",
|
||||
"DurableName": "durable"
|
||||
}
|
||||
},
|
||||
"Notifications": {
|
||||
"Wake": {
|
||||
"To": [ "user@homelab.com" ],
|
||||
"Subject": "Network resurrector: Machine {MACHINE_NAME} has been waked. Status: {ACTION_STATUS}",
|
||||
"Body": "Hello,{NEWLINE:2}Network resurrector processed a command to start machine {MACHINE_FULLNAME} with IP {MACHINE_IP} at {SYSTEM_DATETIME}.{NEWLINE}The performer who was delegated for this action was {ACTION_PERFORMER}.{NEWLINE}Action status: {ACTION_STATUS}{NEWLINE:2}Have a nice day,{NEWLINE}Network resurrector notification system."
|
||||
},
|
||||
"Shutdown": {
|
||||
"To": [ "user@homelab.com" ],
|
||||
"Subject": "Network resurrector: Machine {MACHINE_NAME} has been shut down. Status: {ACTION_STATUS}",
|
||||
"Body": "Hello,{NEWLINE:2}Network resurrector processed a command to shut down machine {MACHINE_FULLNAME} with IP {MACHINE_IP} at {SYSTEM_DATETIME}.{NEWLINE}The performer who was delegated for this action was {ACTION_PERFORMER}.{NEWLINE}Action status: {ACTION_STATUS}{NEWLINE:2}Have a nice day,{NEWLINE}Network resurrector notification system."
|
||||
},
|
||||
"Error": {
|
||||
"To": [ "user@homelab.com" ],
|
||||
"Subject": "Network resurrector: An unexpected error has occurred",
|
||||
"Body": "Hello,{NEWLINE:2}Network resurrector encountered an unexpected error: {ERROR_MESSGE}.{NEWLINE}For more details about the error, check the logging system.{NEWLINE:2}Have a nice day,{NEWLINE}Network resurrector notification system."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,9 +31,8 @@ namespace NetworkResurrector.Server.Application.CommandHandlers
|
|||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var correlationIdMsg = $"CorrelationId: {command.Metadata.CorrelationId}";
|
||||
_logger.LogError(ex, $"An unexpected error has occurred. {correlationIdMsg}");
|
||||
return new MachinePinged(false, $"{ex.Message} {correlationIdMsg}");
|
||||
_logger.LogError(ex, "An unexpected error has occurred.");
|
||||
return new MachinePinged(false, ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,9 +36,8 @@ namespace NetworkResurrector.Server.Application.CommandHandlers
|
|||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var correlationIdMsg = $"CorrelationId: {command.Metadata.CorrelationId}";
|
||||
_logger.LogError(ex, $"An unexpected error has occurred. {correlationIdMsg}");
|
||||
return new MachineShutdown(false, $"{ex.Message} {correlationIdMsg}");
|
||||
_logger.LogError(ex, "An unexpected error has occurred.");
|
||||
return new MachineShutdown(false, ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,9 +29,8 @@ namespace NetworkResurrector.Server.Application.CommandHandlers
|
|||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var correlationIdMsg = $"CorrelationId: {command.Metadata.CorrelationId}";
|
||||
_logger.LogError(ex, $"An unexpected error has occurred. {correlationIdMsg}");
|
||||
return new MachineWaked(false, $"{ex.Message} {correlationIdMsg}");
|
||||
_logger.LogError(ex, "An unexpected error has occurred.");
|
||||
return new MachineWaked(false, ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,41 @@
|
|||
namespace NetworkResurrector.Server.Application.Queries
|
||||
using MediatR;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace NetworkResurrector.Server.Application.Queries
|
||||
{
|
||||
class GetServiceVersion
|
||||
public class GetSystemVersion
|
||||
{
|
||||
//TO DO
|
||||
public class Query : IRequest<Model> { }
|
||||
|
||||
public record Model(string Version, DateTime LastUpdateDate);
|
||||
|
||||
public class QueryHandler : IRequestHandler<Query, Model>
|
||||
{
|
||||
public QueryHandler()
|
||||
{
|
||||
}
|
||||
|
||||
public async Task<Model> Handle(Query request, CancellationToken cancellationToken)
|
||||
{
|
||||
var version = Environment.GetEnvironmentVariable("APP_VERSION");
|
||||
var appDate = Environment.GetEnvironmentVariable("APP_DATE");
|
||||
|
||||
if (string.IsNullOrEmpty(version))
|
||||
version = Assembly.GetEntryAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;
|
||||
|
||||
if (!DateTime.TryParse(appDate, out var lastUpdateDate))
|
||||
{
|
||||
var location = Assembly.GetExecutingAssembly().Location;
|
||||
lastUpdateDate = File.GetLastWriteTime(location);
|
||||
}
|
||||
|
||||
var result = new Model(version, lastUpdateDate);
|
||||
return await Task.FromResult(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,10 @@
|
|||
using Netmash.Application.DataContracts;
|
||||
using MediatR;
|
||||
using NetworkResurrector.Server.PublishedLanguage.Events;
|
||||
using System;
|
||||
|
||||
namespace NetworkResurrector.Server.PublishedLanguage.Commands
|
||||
{
|
||||
public class PingMachine : Command<MachinePinged>
|
||||
public class PingMachine : IRequest<MachinePinged>
|
||||
{
|
||||
public string IpAddressOrMachineName { get; set; }
|
||||
|
||||
public PingMachine(string ipAddressOrMachineName) : base(new Metadata() { CorrelationId = Guid.NewGuid() })
|
||||
{
|
||||
IpAddressOrMachineName = ipAddressOrMachineName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,10 @@
|
|||
using Netmash.Application.DataContracts;
|
||||
using MediatR;
|
||||
using NetworkResurrector.Server.PublishedLanguage.Events;
|
||||
using System;
|
||||
|
||||
namespace NetworkResurrector.Server.PublishedLanguage.Commands
|
||||
{
|
||||
public class ShutdownMachine : Command<MachineShutdown>
|
||||
public class ShutdownMachine : IRequest<MachineShutdown>
|
||||
{
|
||||
public string IPAddressOrMachineName { get; set; }
|
||||
|
||||
public ShutdownMachine(string ipAddressOrMachineName) : base(new Metadata() { CorrelationId = Guid.NewGuid() })
|
||||
{
|
||||
IPAddressOrMachineName = ipAddressOrMachineName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,10 @@
|
|||
using Netmash.Application.DataContracts;
|
||||
using MediatR;
|
||||
using NetworkResurrector.Server.PublishedLanguage.Events;
|
||||
using System;
|
||||
|
||||
namespace NetworkResurrector.Server.PublishedLanguage.Commands
|
||||
{
|
||||
public class WakeMachine : Command<MachineWaked>
|
||||
public class WakeMachine : IRequest<MachineWaked>
|
||||
{
|
||||
public string MacAddress { get; set; }
|
||||
|
||||
public WakeMachine(string macAddress) : base(new Metadata() { CorrelationId = Guid.NewGuid() })
|
||||
{
|
||||
MacAddress = macAddress;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,7 +9,7 @@
|
|||
<Version>1.0.3</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Netmash.Application.DataContracts" Version="$(NetmashApplicationPackageVersion)" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MediatR" Version="$(MediatRPackageVersion)" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
using MediatR;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NetworkResurrector.Server.Application.Queries;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace NetworkResurrector.Server.Controllers
|
||||
{
|
||||
[Authorize]
|
||||
[ApiController]
|
||||
[Route("system")]
|
||||
public class SystemController : ControllerBase
|
||||
{
|
||||
private readonly IMediator _mediator;
|
||||
|
||||
public SystemController(IMediator mediator)
|
||||
{
|
||||
_mediator = mediator;
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpGet("ping")]
|
||||
public IActionResult Ping()
|
||||
{
|
||||
return Ok($"Ping success. System datetime: {DateTime.Now}");
|
||||
}
|
||||
|
||||
[HttpGet("version")]
|
||||
public async Task<IActionResult> GetSystemVersion([FromRoute] GetSystemVersion.Query query)
|
||||
{
|
||||
var result = await _mediator.Send(query);
|
||||
return Ok(result);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,7 +14,6 @@
|
|||
<PackageReference Include="Netmash.Security.Authentication.Identity" Version="$(NetmashSecurityAuthenticationPackageVersion)" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="$(SerilogPackageVersion)" />
|
||||
<PackageReference Include="Serilog.Extensions.Logging" Version="$(SerilogExtensionsPackageVersion)" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="$(SerilogSinksConsolePackageVersion)" />
|
||||
<PackageReference Include="Serilog.Sinks.MSSqlServer" Version="$(SerilogSinksMSSqlServerPackageVersion)" />
|
||||
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="$(AutoMapperExtensionsPackageVersion)" />
|
||||
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="$(MediatRPackageVersion)" />
|
||||
|
|
Loading…
Reference in New Issue