mirror of
https://github.com/immense/Remotely.git
synced 2025-10-26 11:27:15 +00:00
Implement Serilog and LogsManager.
This commit is contained in:
parent
509919179f
commit
d1c432efd9
@ -148,7 +148,7 @@ namespace Remotely.Server.API
|
||||
|
||||
if (_appConfig.BannedDevices.Contains(deviceIp))
|
||||
{
|
||||
_dataService.WriteEvent($"Device IP ({deviceIp}) is banned. Sending uninstall command.", null);
|
||||
_logger.LogInformation("Device IP ({deviceIp}) is banned. Sending uninstall command.", deviceIp);
|
||||
|
||||
|
||||
var bannedDevices = _serviceSessionCache.GetAllDevices().Where(x => x.PublicIP == deviceIp);
|
||||
|
||||
@ -15,6 +15,8 @@ using Immense.RemoteControl.Server.Services;
|
||||
using Remotely.Server.Services.RcImplementations;
|
||||
using Immense.RemoteControl.Server.Abstractions;
|
||||
using Immense.RemoteControl.Shared.Helpers;
|
||||
using Microsoft.Build.Framework;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
|
||||
|
||||
@ -32,6 +34,7 @@ namespace Remotely.Server.API
|
||||
private readonly IHubEventHandler _hubEvents;
|
||||
private readonly IDataService _dataService;
|
||||
private readonly SignInManager<RemotelyUser> _signInManager;
|
||||
private readonly ILogger<RemoteControlController> _logger;
|
||||
|
||||
public RemoteControlController(
|
||||
SignInManager<RemotelyUser> signInManager,
|
||||
@ -41,7 +44,8 @@ namespace Remotely.Server.API
|
||||
IServiceHubSessionCache serviceSessionCache,
|
||||
IOtpProvider otpProvider,
|
||||
IHubEventHandler hubEvents,
|
||||
IApplicationConfig appConfig)
|
||||
IApplicationConfig appConfig,
|
||||
ILogger<RemoteControlController> logger)
|
||||
{
|
||||
_dataService = dataService;
|
||||
_serviceHub = serviceHub;
|
||||
@ -51,6 +55,7 @@ namespace Remotely.Server.API
|
||||
_otpProvider = otpProvider;
|
||||
_hubEvents = hubEvents;
|
||||
_signInManager = signInManager;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
[HttpGet("{deviceID}")]
|
||||
@ -75,20 +80,20 @@ namespace Remotely.Server.API
|
||||
if (result.Succeeded &&
|
||||
_dataService.DoesUserHaveAccessToDevice(rcRequest.DeviceID, _dataService.GetUserByNameWithOrg(rcRequest.Email)))
|
||||
{
|
||||
_dataService.WriteEvent($"API login successful for {rcRequest.Email}.", orgId);
|
||||
_logger.LogInformation("API login successful for {rcRequestEmail}.", rcRequest.Email);
|
||||
return await InitiateRemoteControl(rcRequest.DeviceID, orgId);
|
||||
}
|
||||
else if (result.IsLockedOut)
|
||||
{
|
||||
_dataService.WriteEvent($"API login unsuccessful due to lockout for {rcRequest.Email}.", orgId);
|
||||
_logger.LogInformation("API login successful for {rcRequestEmail}.", rcRequest.Email);
|
||||
return Unauthorized("Account is locked.");
|
||||
}
|
||||
else if (result.RequiresTwoFactor)
|
||||
{
|
||||
_dataService.WriteEvent($"API login unsuccessful due to 2FA for {rcRequest.Email}.", orgId);
|
||||
_logger.LogInformation("API login successful for {rcRequestEmail}.", rcRequest.Email);
|
||||
return Unauthorized("Account requires two-factor authentication.");
|
||||
}
|
||||
_dataService.WriteEvent($"API login unsuccessful due to bad attempt for {rcRequest.Email}.", orgId);
|
||||
_logger.LogInformation("API login unsuccessful due to bad attempt for {rcRequestEmail}.", rcRequest.Email);
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
|
||||
@ -1,9 +1,12 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Remotely.Server.Auth;
|
||||
using Remotely.Server.Services;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Remotely.Server.Services;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Remotely.Server.API
|
||||
{
|
||||
@ -11,23 +14,34 @@ namespace Remotely.Server.API
|
||||
[ApiController]
|
||||
public class ServerLogsController : ControllerBase
|
||||
{
|
||||
private readonly IDataService _dataService;
|
||||
private readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions() { WriteIndented = true };
|
||||
private readonly JsonSerializerOptions _jsonOptions = new() { WriteIndented = true };
|
||||
private readonly ILogsManager _logsManager;
|
||||
private readonly ILogger<ServerLogsController> _logger;
|
||||
|
||||
public ServerLogsController(IDataService dataService)
|
||||
public ServerLogsController(
|
||||
ILogsManager logsManager,
|
||||
ILogger<ServerLogsController> logger)
|
||||
{
|
||||
_dataService = dataService;
|
||||
_logsManager = logsManager;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
[ServiceFilter(typeof(ApiAuthorizationFilter))]
|
||||
[HttpGet("Download")]
|
||||
public ActionResult Download()
|
||||
public async Task<IActionResult> Download()
|
||||
{
|
||||
Request.Headers.TryGetValue("OrganizationID", out var orgId);
|
||||
_logger.LogInformation(
|
||||
"Downloading server logs. Remote IP: {ip}",
|
||||
HttpContext.Connection.RemoteIpAddress);
|
||||
|
||||
var logs = _dataService.GetAllEventLogs(User.Identity?.Name, orgId);
|
||||
var fileBytes = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(logs, _jsonOptions));
|
||||
return File(fileBytes, "application/octet-stream", "ServerLogs.json");
|
||||
var zipFile = await _logsManager.ZipAllLogs();
|
||||
Response.OnCompleted(() =>
|
||||
{
|
||||
Directory.Delete(zipFile.DirectoryName, true);
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
|
||||
return File(zipFile.OpenRead(), "application/octet-stream", zipFile.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,6 +12,7 @@ using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.AspNetCore.WebUtilities;
|
||||
using Remotely.Shared.Models;
|
||||
using Remotely.Server.Services;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Remotely.Server.Areas.Identity.Pages.Account
|
||||
{
|
||||
@ -21,14 +22,18 @@ namespace Remotely.Server.Areas.Identity.Pages.Account
|
||||
private readonly UserManager<RemotelyUser> _userManager;
|
||||
private readonly IEmailSenderEx _emailSender;
|
||||
private readonly IDataService _dataService;
|
||||
private readonly ILogger<ForgotPasswordModel> _logger;
|
||||
|
||||
public ForgotPasswordModel(UserManager<RemotelyUser> userManager,
|
||||
public ForgotPasswordModel(
|
||||
UserManager<RemotelyUser> userManager,
|
||||
IEmailSenderEx emailSender,
|
||||
IDataService dataService)
|
||||
IDataService dataService,
|
||||
ILogger<ForgotPasswordModel> logger)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_emailSender = emailSender;
|
||||
_dataService = dataService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
[BindProperty]
|
||||
@ -62,7 +67,8 @@ namespace Remotely.Server.Areas.Identity.Pages.Account
|
||||
values: new { area = "Identity", code },
|
||||
protocol: Request.Scheme);
|
||||
|
||||
_dataService.WriteEvent($"Sending password reset for user {user.UserName}. Reset URL: {callbackUrl}", user.OrganizationID);
|
||||
_logger.LogInformation(
|
||||
"Sending password reset for user {username}. Reset URL: {callbackUrl}", user.UserName, callbackUrl);
|
||||
|
||||
var emailResult = await _emailSender.SendEmailAsync(
|
||||
Input.Email,
|
||||
|
||||
@ -23,6 +23,7 @@ namespace Remotely.Server.Hubs
|
||||
private readonly ICircuitManager _circuitManager;
|
||||
private readonly IDataService _dataService;
|
||||
private readonly IExpiringTokenService _expiringTokenService;
|
||||
private readonly ILogger<AgentHub> _logger;
|
||||
private readonly IServiceHubSessionCache _serviceSessionCache;
|
||||
private readonly IHubContext<ViewerHub> _viewerHubContext;
|
||||
|
||||
@ -31,7 +32,8 @@ namespace Remotely.Server.Hubs
|
||||
IServiceHubSessionCache serviceSessionCache,
|
||||
IHubContext<ViewerHub> viewerHubContext,
|
||||
ICircuitManager circuitManager,
|
||||
IExpiringTokenService expiringTokenService)
|
||||
IExpiringTokenService expiringTokenService,
|
||||
ILogger<AgentHub> logger)
|
||||
{
|
||||
_dataService = dataService;
|
||||
_serviceSessionCache = serviceSessionCache;
|
||||
@ -39,6 +41,7 @@ namespace Remotely.Server.Hubs
|
||||
_appConfig = appConfig;
|
||||
_circuitManager = circuitManager;
|
||||
_expiringTokenService = expiringTokenService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
// TODO: Replace with new invoke capability in .NET 7 in ScriptingController.
|
||||
@ -133,7 +136,7 @@ namespace Remotely.Server.Hubs
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_dataService.WriteEvent(ex, device?.OrganizationID);
|
||||
_logger.LogError(ex, "Error while setting device to online status.");
|
||||
}
|
||||
|
||||
Context.Abort();
|
||||
@ -286,7 +289,7 @@ namespace Remotely.Server.Hubs
|
||||
if (_appConfig.BannedDevices.Any(x => !string.IsNullOrWhiteSpace(x) &&
|
||||
x.Equals(device, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
_dataService.WriteEvent($"Device ID/name/IP ({device}) is banned. Sending uninstall command.", null);
|
||||
_logger.LogWarning("Device ID/name/IP ({device}) is banned. Sending uninstall command.", device);
|
||||
|
||||
_ = Clients.Caller.SendAsync("UninstallAgent");
|
||||
return true;
|
||||
|
||||
@ -222,7 +222,10 @@ namespace Remotely.Server.Hubs
|
||||
if (!_dataService.DoesUserHaveAccessToDevice(deviceId, User))
|
||||
{
|
||||
var device = _dataService.GetDevice(targetDevice.ID);
|
||||
_dataService.WriteEvent($"Remote control attempted by unauthorized user. Device ID: {deviceId}. User Name: {User.UserName}.", EventType.Warning, device?.OrganizationID);
|
||||
_logger.LogWarning(
|
||||
"Remote control attempted by unauthorized user. Device ID: {deviceId}. User Name: {userName}.",
|
||||
deviceId,
|
||||
User.UserName);
|
||||
return Result.Fail<RemoteControlSessionEx>("Unauthorized.");
|
||||
|
||||
}
|
||||
@ -414,13 +417,11 @@ namespace Remotely.Server.Hubs
|
||||
|
||||
public Task UploadFiles(List<string> fileIDs, string transferID, string[] deviceIDs)
|
||||
{
|
||||
_dataService.WriteEvent(new EventLog()
|
||||
{
|
||||
EventType = EventType.Info,
|
||||
Message = $"File transfer started by {User.UserName}. File transfer IDs: {string.Join(", ", fileIDs)}.",
|
||||
TimeStamp = Time.Now,
|
||||
OrganizationID = User.OrganizationID
|
||||
});
|
||||
_logger.LogInformation(
|
||||
"File transfer started by {userName}. File transfer IDs: {fileIds}.",
|
||||
User.UserName,
|
||||
string.Join(", ", fileIDs));
|
||||
|
||||
deviceIDs = _dataService.FilterDeviceIDsByUserPermission(deviceIDs, User);
|
||||
var connections = GetActiveConnectionsForUserOrg(deviceIDs);
|
||||
foreach (var connection in connections)
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
@inject IDataService DataService
|
||||
@inject IToastService ToastService
|
||||
@inject IJsInterop JsInterop
|
||||
@inject ILogsManager LogsManager
|
||||
|
||||
<h3 class="mb-3">Server Logs</h3>
|
||||
|
||||
@ -109,11 +110,12 @@ else
|
||||
{
|
||||
get
|
||||
{
|
||||
return DataService.GetEventLogs(User.UserName,
|
||||
_fromDate,
|
||||
_toDate,
|
||||
_eventType,
|
||||
_messageFilter);
|
||||
return Enumerable.Empty<EventLog>();
|
||||
//return DataService.GetEventLogs(User.UserName,
|
||||
// _fromDate,
|
||||
// _toDate,
|
||||
// _eventType,
|
||||
// _messageFilter);
|
||||
}
|
||||
}
|
||||
|
||||
@ -122,7 +124,7 @@ else
|
||||
var result = await JsInterop.Confirm("Are you sure you want to delete all logs?");
|
||||
if (result)
|
||||
{
|
||||
await DataService.ClearLogs(User.UserName);
|
||||
await LogsManager.DeleteLogs();
|
||||
ToastService.ShowToast("Logs deleted.");
|
||||
}
|
||||
}
|
||||
|
||||
@ -205,6 +205,7 @@ services.AddScoped<IExpiringTokenService, ExpiringTokenService>();
|
||||
services.AddScoped<IScriptScheduleDispatcher, ScriptScheduleDispatcher>();
|
||||
services.AddSingleton<IOtpProvider, OtpProvider>();
|
||||
services.AddSingleton<IEmbeddedServerDataSearcher, EmbeddedServerDataSearcher>();
|
||||
services.AddSingleton<ILogsManager>(LogsManager.Default);
|
||||
|
||||
services.AddRemoteControlServer(config =>
|
||||
{
|
||||
@ -329,8 +330,7 @@ void ConfigureSerilog(WebApplicationBuilder webAppBuilder)
|
||||
dataRetentionDays = retentionSetting;
|
||||
}
|
||||
|
||||
var logPath = Directory.Exists("/remotely-data") ? "/remotely-data/logs" : "logs";
|
||||
Directory.CreateDirectory(logPath);
|
||||
var logPath = LogsManager.Default.GetLogsDirectory();
|
||||
|
||||
void ApplySharedLoggerConfig(LoggerConfiguration loggerConfiguration)
|
||||
{
|
||||
|
||||
@ -1,83 +0,0 @@
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
namespace Remotely.Server.Services
|
||||
{
|
||||
public class DbLogger : ILogger
|
||||
{
|
||||
private readonly string _categoryName;
|
||||
private readonly IWebHostEnvironment _hostEnvironment;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
|
||||
protected static ConcurrentStack<string> ScopeStack { get; } = new ConcurrentStack<string>();
|
||||
|
||||
public DbLogger(string categoryName, IWebHostEnvironment hostEnvironment, IServiceProvider serviceProvider)
|
||||
{
|
||||
_categoryName = categoryName;
|
||||
_hostEnvironment = hostEnvironment;
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
public IDisposable BeginScope<TState>(TState state)
|
||||
{
|
||||
ScopeStack.Push(state.ToString());
|
||||
return new NoopDisposable();
|
||||
}
|
||||
|
||||
public bool IsEnabled(LogLevel logLevel)
|
||||
{
|
||||
switch (logLevel)
|
||||
{
|
||||
case LogLevel.Trace:
|
||||
break;
|
||||
case LogLevel.Debug:
|
||||
case LogLevel.Information:
|
||||
if (_hostEnvironment.IsDevelopment())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case LogLevel.Warning:
|
||||
case LogLevel.Error:
|
||||
case LogLevel.Critical:
|
||||
return true;
|
||||
case LogLevel.None:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
|
||||
{
|
||||
using var scope = _serviceProvider.CreateScope();
|
||||
var dataService = scope.ServiceProvider.GetRequiredService<IDataService>();
|
||||
|
||||
var scopeStack = ScopeStack.Any() ?
|
||||
new string[] { ScopeStack.FirstOrDefault(), ScopeStack.LastOrDefault() } :
|
||||
Array.Empty<string>();
|
||||
|
||||
dataService.WriteLog(logLevel, _categoryName, eventId, state.ToString(), exception, scopeStack);
|
||||
}
|
||||
|
||||
|
||||
private class NoopDisposable : IDisposable
|
||||
{
|
||||
public void Dispose()
|
||||
{
|
||||
while (!ScopeStack.TryPop(out _))
|
||||
{
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,29 +0,0 @@
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
|
||||
namespace Remotely.Server.Services
|
||||
{
|
||||
public class DbLoggerProvider : ILoggerProvider
|
||||
{
|
||||
private readonly IWebHostEnvironment _hostEnvironment;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
|
||||
public DbLoggerProvider(IWebHostEnvironment hostEnvironment, IServiceProvider serviceProvider)
|
||||
{
|
||||
_hostEnvironment = hostEnvironment;
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
|
||||
public ILogger CreateLogger(string categoryName)
|
||||
{
|
||||
return new DbLogger(categoryName, _hostEnvironment, _serviceProvider);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,8 @@
|
||||
using MailKit.Net.Smtp;
|
||||
using MailKit.Security;
|
||||
using Microsoft.AspNetCore.Identity.UI.Services;
|
||||
using Microsoft.Build.Framework;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MimeKit;
|
||||
using MimeKit.Text;
|
||||
using System;
|
||||
@ -15,67 +17,6 @@ namespace Remotely.Server.Services
|
||||
Task<bool> SendEmailAsync(string email, string subject, string htmlMessage, string organizationID = null);
|
||||
}
|
||||
|
||||
public class EmailSenderEx : IEmailSenderEx
|
||||
{
|
||||
public EmailSenderEx(IApplicationConfig appConfig, IDataService dataService)
|
||||
{
|
||||
AppConfig = appConfig;
|
||||
DataService = dataService;
|
||||
}
|
||||
|
||||
private IApplicationConfig AppConfig { get; }
|
||||
private IDataService DataService { get; }
|
||||
|
||||
public async Task<bool> SendEmailAsync(string toEmail, string replyTo, string subject, string htmlMessage, string organizationID = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var message = new MimeMessage();
|
||||
message.From.Add(new MailboxAddress(AppConfig.SmtpDisplayName, AppConfig.SmtpEmail));
|
||||
message.To.Add(MailboxAddress.Parse(toEmail));
|
||||
message.ReplyTo.Add(MailboxAddress.Parse(replyTo));
|
||||
message.Subject = subject;
|
||||
message.Body = new TextPart(TextFormat.Html)
|
||||
{
|
||||
Text = htmlMessage
|
||||
};
|
||||
|
||||
using var client = new SmtpClient();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(AppConfig.SmtpLocalDomain))
|
||||
{
|
||||
client.LocalDomain = AppConfig.SmtpLocalDomain;
|
||||
}
|
||||
|
||||
client.CheckCertificateRevocation = AppConfig.SmtpCheckCertificateRevocation;
|
||||
|
||||
await client.ConnectAsync(AppConfig.SmtpHost, AppConfig.SmtpPort);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(AppConfig.SmtpUserName) &&
|
||||
!string.IsNullOrWhiteSpace(AppConfig.SmtpPassword))
|
||||
{
|
||||
await client.AuthenticateAsync(AppConfig.SmtpUserName, AppConfig.SmtpPassword);
|
||||
}
|
||||
|
||||
await client.SendAsync(message);
|
||||
await client.DisconnectAsync(true);
|
||||
|
||||
DataService.WriteEvent($"Email successfully sent to {toEmail}. Subject: \"{subject}\".", organizationID);
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DataService.WriteEvent(ex, organizationID);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public Task<bool> SendEmailAsync(string email, string subject, string htmlMessage, string organizationID = null)
|
||||
{
|
||||
return SendEmailAsync(email, AppConfig.SmtpEmail, subject, htmlMessage, organizationID);
|
||||
}
|
||||
}
|
||||
public class EmailSender : IEmailSender
|
||||
{
|
||||
public EmailSender(IEmailSenderEx emailSenderEx)
|
||||
@ -91,4 +32,66 @@ namespace Remotely.Server.Services
|
||||
}
|
||||
}
|
||||
|
||||
public class EmailSenderEx : IEmailSenderEx
|
||||
{
|
||||
private readonly IApplicationConfig _appConfig;
|
||||
private readonly ILogger<EmailSenderEx> _logger;
|
||||
|
||||
public EmailSenderEx(
|
||||
IApplicationConfig appConfig,
|
||||
ILogger<EmailSenderEx> logger)
|
||||
{
|
||||
_appConfig = appConfig;
|
||||
_logger = logger;
|
||||
}
|
||||
public async Task<bool> SendEmailAsync(string toEmail, string replyTo, string subject, string htmlMessage, string organizationID = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var message = new MimeMessage();
|
||||
message.From.Add(new MailboxAddress(_appConfig.SmtpDisplayName, _appConfig.SmtpEmail));
|
||||
message.To.Add(MailboxAddress.Parse(toEmail));
|
||||
message.ReplyTo.Add(MailboxAddress.Parse(replyTo));
|
||||
message.Subject = subject;
|
||||
message.Body = new TextPart(TextFormat.Html)
|
||||
{
|
||||
Text = htmlMessage
|
||||
};
|
||||
|
||||
using var client = new SmtpClient();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_appConfig.SmtpLocalDomain))
|
||||
{
|
||||
client.LocalDomain = _appConfig.SmtpLocalDomain;
|
||||
}
|
||||
|
||||
client.CheckCertificateRevocation = _appConfig.SmtpCheckCertificateRevocation;
|
||||
|
||||
await client.ConnectAsync(_appConfig.SmtpHost, _appConfig.SmtpPort);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_appConfig.SmtpUserName) &&
|
||||
!string.IsNullOrWhiteSpace(_appConfig.SmtpPassword))
|
||||
{
|
||||
await client.AuthenticateAsync(_appConfig.SmtpUserName, _appConfig.SmtpPassword);
|
||||
}
|
||||
|
||||
await client.SendAsync(message);
|
||||
await client.DisconnectAsync(true);
|
||||
|
||||
_logger.LogInformation("Email successfully sent to {toEmail}. Subject: \"{subject}\".", toEmail, subject);
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error while sending email.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public Task<bool> SendEmailAsync(string email, string subject, string htmlMessage, string organizationID = null)
|
||||
{
|
||||
return SendEmailAsync(email, _appConfig.SmtpEmail, subject, htmlMessage, organizationID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
80
Server/Services/LogsManager.cs
Normal file
80
Server/Services/LogsManager.cs
Normal file
@ -0,0 +1,80 @@
|
||||
using Remotely.Shared.Extensions;
|
||||
using Serilog;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Remotely.Server.Services
|
||||
{
|
||||
public interface ILogsManager
|
||||
{
|
||||
string GetLogsDirectory();
|
||||
Task<FileInfo> ZipAllLogs();
|
||||
Task DeleteLogs();
|
||||
}
|
||||
|
||||
public class LogsManager : ILogsManager
|
||||
{
|
||||
public static LogsManager Default { get; } = new();
|
||||
|
||||
public string GetLogsDirectory()
|
||||
{
|
||||
var logsDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "logs");
|
||||
if (Directory.Exists("/remotely-data"))
|
||||
{
|
||||
logsDir = "/remotely-data/logs";
|
||||
}
|
||||
return Directory.CreateDirectory(logsDir).FullName;
|
||||
}
|
||||
|
||||
public async Task<FileInfo> ZipAllLogs()
|
||||
{
|
||||
var logsDir = GetLogsDirectory();
|
||||
var baseDir = AppDomain.CurrentDomain.BaseDirectory;
|
||||
var tempDir = Directory.CreateDirectory(Path.Combine(baseDir, "temp", Guid.NewGuid().ToString())).FullName;
|
||||
var zipFilePath = Path.Combine(
|
||||
tempDir,
|
||||
$"Remotely_Logs-{DateTimeOffset.Now:yyyy-MM-dd-HH-mm-ss}.zip");
|
||||
|
||||
using var zipArchive = ZipFile.Open(zipFilePath, ZipArchiveMode.Update);
|
||||
|
||||
var files = Directory.GetFiles(logsDir);
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
var entry = zipArchive.CreateEntry(Path.GetFileName(file));
|
||||
using var entryStream = entry.Open();
|
||||
using var fs = File.Open(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
await fs.CopyToAsync(entryStream);
|
||||
}
|
||||
|
||||
return new FileInfo(zipFilePath);
|
||||
}
|
||||
|
||||
public async Task DeleteLogs()
|
||||
{
|
||||
var logsDir = GetLogsDirectory();
|
||||
|
||||
var files = Directory.GetFiles(logsDir);
|
||||
|
||||
if (!files.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await foreach (var file in files.ToAsyncEnumerable())
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete(file);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("Failed to delete log file: {filename}. Message: {exMessage}", file, ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
20
Shared/Extensions/IEnumerableExtensions.cs
Normal file
20
Shared/Extensions/IEnumerableExtensions.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Remotely.Shared.Extensions
|
||||
{
|
||||
public static class IEnumerableExtensions
|
||||
{
|
||||
public static async IAsyncEnumerable<T> ToAsyncEnumerable<T>(this IEnumerable<T> source)
|
||||
{
|
||||
foreach (var item in source)
|
||||
{
|
||||
yield return item;
|
||||
await Task.Yield();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
52
Shared/Primitives/CallbackDisposable.cs
Normal file
52
Shared/Primitives/CallbackDisposable.cs
Normal file
@ -0,0 +1,52 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Remotely.Shared.Primitives;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// An implementation of <see cref="IDisposable"/> that lets you provide a
|
||||
/// callback, which will be invoked when the object is disposed.
|
||||
/// </summary>
|
||||
public sealed class CallbackDisposable : IDisposable
|
||||
{
|
||||
private readonly Action _callback;
|
||||
private readonly Action<Exception> _exceptionHandler;
|
||||
|
||||
/// <summary>
|
||||
/// Create anew instance where exceptions will be caught and suppressed.
|
||||
/// </summary>
|
||||
/// <param name="callback"></param>
|
||||
public CallbackDisposable(Action callback)
|
||||
: this(callback, (_) => { })
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new instance where exceptions will be caught and passed to the supplied handler.
|
||||
/// </summary>
|
||||
/// <param name="callback"></param>
|
||||
public CallbackDisposable(
|
||||
Action callback,
|
||||
Action<Exception> exceptionHandler)
|
||||
{
|
||||
_callback = callback;
|
||||
_exceptionHandler = exceptionHandler;
|
||||
}
|
||||
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
try
|
||||
{
|
||||
_callback.Invoke();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_exceptionHandler.Invoke(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
52
Shared/Primitives/CallbackDisposableAsync.cs
Normal file
52
Shared/Primitives/CallbackDisposableAsync.cs
Normal file
@ -0,0 +1,52 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Remotely.Shared.Primitives;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// An implementation of <see cref="IAsyncDisposable"/> that lets you provide a
|
||||
/// callback, which will be invoked when the object is disposed.
|
||||
/// </summary>
|
||||
public sealed class CallbackDisposableAsync : IAsyncDisposable
|
||||
{
|
||||
private readonly Func<ValueTask> _callback;
|
||||
private readonly Func<Exception, ValueTask> _exceptionHandler;
|
||||
|
||||
/// <summary>
|
||||
/// Create anew instance where exceptions will be caught and suppressed.
|
||||
/// </summary>
|
||||
/// <param name="callback"></param>
|
||||
public CallbackDisposableAsync(Func<ValueTask> callback)
|
||||
: this(callback, (_) => ValueTask.CompletedTask)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new instance where exceptions will be caught and passed to the supplied handler.
|
||||
/// </summary>
|
||||
/// <param name="callback"></param>
|
||||
public CallbackDisposableAsync(
|
||||
Func<ValueTask> callback,
|
||||
Func<Exception, ValueTask> exceptionHandler)
|
||||
{
|
||||
_callback = callback;
|
||||
_exceptionHandler = exceptionHandler;
|
||||
}
|
||||
|
||||
|
||||
public ValueTask DisposeAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
return _callback.Invoke();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return _exceptionHandler.Invoke(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user