mirror of
https://github.com/techgarage-ir/MTWireGuard.git
synced 2025-08-28 13:58:09 +02:00
Added Serilog to manage logs
Handle required fields while starting
This commit is contained in:
parent
6fbfaa1007
commit
b128154c60
17 changed files with 749 additions and 154 deletions
|
@ -1,15 +1,12 @@
|
||||||
using Hangfire;
|
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||||
using Hangfire.Storage.SQLite;
|
|
||||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
|
||||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.IdentityModel.Tokens;
|
|
||||||
using MTWireGuard.Application.Mapper;
|
using MTWireGuard.Application.Mapper;
|
||||||
using MTWireGuard.Application.Repositories;
|
using MTWireGuard.Application.Repositories;
|
||||||
using MTWireGuard.Application.Services;
|
using MTWireGuard.Application.Services;
|
||||||
|
using Serilog;
|
||||||
|
using Serilog.Ui.SqliteDataProvider;
|
||||||
|
using Serilog.Ui.Web;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace MTWireGuard.Application
|
namespace MTWireGuard.Application
|
||||||
{
|
{
|
||||||
|
@ -17,16 +14,15 @@ namespace MTWireGuard.Application
|
||||||
{
|
{
|
||||||
public static void AddApplicationServices(this IServiceCollection services)
|
public static void AddApplicationServices(this IServiceCollection services)
|
||||||
{
|
{
|
||||||
|
// Add Serilog
|
||||||
|
services.AddLogging(loggingBuilder =>
|
||||||
|
{
|
||||||
|
loggingBuilder.AddSerilog(Helper.LoggerConfiguration(), dispose: true);
|
||||||
|
});
|
||||||
|
|
||||||
// Add DBContext
|
// Add DBContext
|
||||||
services.AddDbContext<DBContext>();
|
services.AddDbContext<DBContext>();
|
||||||
|
|
||||||
// Add HangFire
|
|
||||||
services.AddHangfire(config =>
|
|
||||||
{
|
|
||||||
config.UseSQLiteStorage(Path.Join(AppDomain.CurrentDomain.BaseDirectory, "MikrotikWireguard.db"));
|
|
||||||
});
|
|
||||||
services.AddHangfireServer();
|
|
||||||
|
|
||||||
// Auto Mapper Configurations
|
// Auto Mapper Configurations
|
||||||
services.AddSingleton<PeerMapping>();
|
services.AddSingleton<PeerMapping>();
|
||||||
services.AddSingleton<ServerMapping>();
|
services.AddSingleton<ServerMapping>();
|
||||||
|
@ -79,6 +75,9 @@ namespace MTWireGuard.Application
|
||||||
o.Conventions.AllowAnonymousToPage("/Login");
|
o.Conventions.AllowAnonymousToPage("/Login");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Add HttpContextAccessor
|
||||||
|
services.AddHttpContextAccessor();
|
||||||
|
|
||||||
// Add Session
|
// Add Session
|
||||||
services.AddDistributedMemoryCache();
|
services.AddDistributedMemoryCache();
|
||||||
|
|
||||||
|
@ -91,6 +90,12 @@ namespace MTWireGuard.Application
|
||||||
|
|
||||||
// Add CORS
|
// Add CORS
|
||||||
services.AddCors();
|
services.AddCors();
|
||||||
|
|
||||||
|
// Add SerilogUI
|
||||||
|
services.AddSerilogUi(options =>
|
||||||
|
{
|
||||||
|
options.UseSqliteServer($"Data Source={Helper.GetLogPath("logs.db")}", "Logs");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
30
Application/ClientIdEnricher.cs
Normal file
30
Application/ClientIdEnricher.cs
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
using Serilog.Configuration;
|
||||||
|
using Serilog;
|
||||||
|
using Serilog.Core;
|
||||||
|
using Serilog.Events;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MTWireGuard.Application
|
||||||
|
{
|
||||||
|
public class ClientIdEnricher(string clientId) : ILogEventEnricher
|
||||||
|
{
|
||||||
|
private readonly string _clientId = clientId;
|
||||||
|
|
||||||
|
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
|
||||||
|
{
|
||||||
|
logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("ClientId", _clientId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class LoggingExtensions
|
||||||
|
{
|
||||||
|
public static LoggerConfiguration WithClientId(this LoggerEnrichmentConfiguration enrichmentConfiguration, string clientId)
|
||||||
|
{
|
||||||
|
return enrichmentConfiguration.With(new ClientIdEnricher(clientId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
59
Application/ExceptionHandler.cs
Normal file
59
Application/ExceptionHandler.cs
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
using Microsoft.AspNetCore.Diagnostics;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MTWireGuard.Application
|
||||||
|
{
|
||||||
|
public class ExceptionHandler(Serilog.ILogger logger, IHttpContextAccessor contextAccessor) : IExceptionHandler
|
||||||
|
{
|
||||||
|
private readonly Serilog.ILogger logger = logger;
|
||||||
|
private readonly IHttpContextAccessor contextAccessor = contextAccessor;
|
||||||
|
|
||||||
|
public ValueTask<bool> TryHandleAsync(HttpContext httpContext, Exception exception, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string exceptionType = exception.GetType().Name,
|
||||||
|
message = exception.Message,
|
||||||
|
stackTrace = exception.StackTrace,
|
||||||
|
//details = JsonConvert.SerializeObject(exception)!;
|
||||||
|
details = exception.Source;
|
||||||
|
|
||||||
|
ExceptionHandlerContext.Message = message;
|
||||||
|
ExceptionHandlerContext.StackTrace = stackTrace;
|
||||||
|
ExceptionHandlerContext.Details = details;
|
||||||
|
|
||||||
|
if (SetupValidator.IsValid)
|
||||||
|
{
|
||||||
|
logger.Error(exception, "Unhandled error");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logger.Error("Error in configuration: {Title}, {Description}", SetupValidator.Title, SetupValidator.Description);
|
||||||
|
}
|
||||||
|
|
||||||
|
contextAccessor.HttpContext.Response.Redirect("/Error", true);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
logger.Fatal(ex, "Error In Exception Handler");
|
||||||
|
}
|
||||||
|
return ValueTask.FromResult(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ExceptionHandlerContext
|
||||||
|
{
|
||||||
|
public static string Message { get; internal set; }
|
||||||
|
public static string StackTrace { get; internal set; }
|
||||||
|
public static string Details { get; internal set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,19 +1,23 @@
|
||||||
using AutoMapper;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using MTWireGuard.Application.MinimalAPI;
|
|
||||||
using MTWireGuard.Application.Models;
|
using MTWireGuard.Application.Models;
|
||||||
using MTWireGuard.Application.Repositories;
|
using MTWireGuard.Application.Repositories;
|
||||||
|
using Serilog;
|
||||||
|
using Serilog.Events;
|
||||||
|
using Serilog.Exceptions;
|
||||||
|
using Serilog.Exceptions.Core;
|
||||||
|
using Serilog.Filters;
|
||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace MTWireGuard.Application
|
namespace MTWireGuard.Application
|
||||||
{
|
{
|
||||||
|
@ -33,6 +37,11 @@ namespace MTWireGuard.Application
|
||||||
return $"/tool fetch mode=http url=\"{apiURL}\" http-method=post check-certificate=no http-data=([/interface/wireguard/peers/print show-ids proplist=rx,tx as-value]);";
|
return $"/tool fetch mode=http url=\"{apiURL}\" http-method=post check-certificate=no http-data=([/interface/wireguard/peers/print show-ids proplist=rx,tx as-value]);";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string UserExpirationScript(string userID)
|
||||||
|
{
|
||||||
|
return $"/interface/wireguard/peers/disable {userID}";
|
||||||
|
}
|
||||||
|
|
||||||
public static int ParseEntityID(string entityID)
|
public static int ParseEntityID(string entityID)
|
||||||
{
|
{
|
||||||
return Convert.ToInt32(entityID[1..], 16);
|
return Convert.ToInt32(entityID[1..], 16);
|
||||||
|
@ -70,6 +79,11 @@ namespace MTWireGuard.Application
|
||||||
SizeSuffixes[mag]);
|
SizeSuffixes[mag]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static long GigabyteToByte(int gigabyte)
|
||||||
|
{
|
||||||
|
return Convert.ToInt64(gigabyte * (1024L * 1024 * 1024));
|
||||||
|
}
|
||||||
|
|
||||||
#region API Section
|
#region API Section
|
||||||
public static List<UsageObject> ParseTrafficUsage(string input)
|
public static List<UsageObject> ParseTrafficUsage(string input)
|
||||||
{
|
{
|
||||||
|
@ -118,17 +132,20 @@ namespace MTWireGuard.Application
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
public static async void HandleUserTraffics(List<DataUsage> updates, DBContext dbContext, IMikrotikRepository API)
|
public static async void HandleUserTraffics(List<DataUsage> updates, DBContext dbContext, IMikrotikRepository API, ILogger logger)
|
||||||
{
|
{
|
||||||
var dataUsages = await dbContext.DataUsages.ToListAsync();
|
var dataUsages = await dbContext.DataUsages.ToListAsync();
|
||||||
var existingItems = dataUsages.OrderBy(x => x.CreationTime).ToList();
|
var existingItems = dataUsages.OrderBy(x => x.CreationTime).ToList();
|
||||||
var lastKnownTraffics = dbContext.LastKnownTraffic.ToList();
|
var lastKnownTraffics = dbContext.LastKnownTraffic.ToList();
|
||||||
var users = await dbContext.Users.ToListAsync();
|
var users = await dbContext.Users.ToListAsync();
|
||||||
|
|
||||||
foreach (var item in updates)
|
foreach (var item in updates)
|
||||||
{
|
{
|
||||||
var tempUser = users.Find(x => x.Id == item.UserID);
|
var tempUser = users.Find(x => x.Id == item.UserID);
|
||||||
if (tempUser == null) continue;
|
if (tempUser == null) continue;
|
||||||
using var transaction = await dbContext.Database.BeginTransactionAsync();
|
|
||||||
|
using var transactionDbContext = new DBContext(); // Create a new DbContext for each transaction
|
||||||
|
using var transaction = await transactionDbContext.Database.BeginTransactionAsync();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
LastKnownTraffic lastKnown = lastKnownTraffics.Find(x => x.UserID == item.UserID);
|
LastKnownTraffic lastKnown = lastKnownTraffics.Find(x => x.UserID == item.UserID);
|
||||||
|
@ -137,7 +154,7 @@ namespace MTWireGuard.Application
|
||||||
var old = existingItems.FindLast(oldItem => oldItem.UserID == item.UserID);
|
var old = existingItems.FindLast(oldItem => oldItem.UserID == item.UserID);
|
||||||
if (old == null)
|
if (old == null)
|
||||||
{
|
{
|
||||||
await dbContext.DataUsages.AddAsync(item);
|
await transactionDbContext.DataUsages.AddAsync(item);
|
||||||
tempUser.RX = item.RX + lastKnown.RX;
|
tempUser.RX = item.RX + lastKnown.RX;
|
||||||
tempUser.TX = item.TX + lastKnown.TX;
|
tempUser.TX = item.TX + lastKnown.TX;
|
||||||
}
|
}
|
||||||
|
@ -146,41 +163,58 @@ namespace MTWireGuard.Application
|
||||||
if ((old.RX <= item.RX || old.TX <= item.TX) &&
|
if ((old.RX <= item.RX || old.TX <= item.TX) &&
|
||||||
(old.RX != item.RX && old.TX != item.TX)) // Normal Data (and not duplicate)
|
(old.RX != item.RX && old.TX != item.TX)) // Normal Data (and not duplicate)
|
||||||
{
|
{
|
||||||
await dbContext.DataUsages.AddAsync(item);
|
await transactionDbContext.DataUsages.AddAsync(item);
|
||||||
}
|
}
|
||||||
else if (old.RX > item.RX || old.TX > item.TX) // Server Reset
|
else if (old.RX > item.RX || old.TX > item.TX) // Server Reset
|
||||||
{
|
{
|
||||||
lastKnown.RX = old.RX;
|
lastKnown.RX = old.RX;
|
||||||
lastKnown.TX = old.TX;
|
lastKnown.TX = old.TX;
|
||||||
lastKnown.CreationTime = DateTime.Now;
|
lastKnown.CreationTime = DateTime.Now;
|
||||||
dbContext.LastKnownTraffic.Update(lastKnown);
|
transactionDbContext.LastKnownTraffic.Update(lastKnown);
|
||||||
item.ResetNotes = $"System reset detected at: {DateTime.Now}";
|
item.ResetNotes = $"System reset detected at: {DateTime.Now}";
|
||||||
await dbContext.DataUsages.AddAsync(item);
|
await transactionDbContext.DataUsages.AddAsync(item);
|
||||||
}
|
}
|
||||||
if (item.RX > old.RX) tempUser.RX = item.RX + lastKnown.RX;
|
if (item.RX > old.RX) tempUser.RX = item.RX + lastKnown.RX;
|
||||||
if (item.TX > old.TX) tempUser.TX = item.TX + lastKnown.TX;
|
if (item.TX > old.TX) tempUser.TX = item.TX + lastKnown.TX;
|
||||||
}
|
}
|
||||||
if (tempUser.TrafficLimit > 0 && tempUser.RX + tempUser.TX >= tempUser.TrafficLimit)
|
if (tempUser.TrafficLimit > 0 && tempUser.RX + tempUser.TX >= GigabyteToByte(tempUser.TrafficLimit))
|
||||||
{
|
{
|
||||||
// Disable User
|
// Disable User
|
||||||
|
logger.Information($"User #{tempUser.Id} reached {tempUser.RX + tempUser.TX} of {GigabyteToByte(tempUser.TrafficLimit)} bandwidth.");
|
||||||
var disable = await API.DisableUser(item.UserID);
|
var disable = await API.DisableUser(item.UserID);
|
||||||
if (disable.Code != "200")
|
if (disable.Code != "200")
|
||||||
{
|
{
|
||||||
Console.WriteLine("Failed disabling user");
|
logger.Error("Failed disabling user", new
|
||||||
|
{
|
||||||
|
userId = item.UserID,
|
||||||
|
disable.Code,
|
||||||
|
disable.Title,
|
||||||
|
disable.Description
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logger.Information("Disabled user due to bandwidth limit", new
|
||||||
|
{
|
||||||
|
item.UserID,
|
||||||
|
TrafficUsed = Helper.ConvertByteSize(tempUser.RX + tempUser.TX),
|
||||||
|
tempUser.TrafficLimit
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dbContext.Users.Update(tempUser);
|
transactionDbContext.Users.Update(tempUser);
|
||||||
await dbContext.SaveChangesAsync();
|
await transactionDbContext.SaveChangesAsync();
|
||||||
transaction.Commit();
|
await transaction.CommitAsync();
|
||||||
}
|
}
|
||||||
catch (DbUpdateException ex)
|
catch (DbUpdateException ex)
|
||||||
{
|
{
|
||||||
Console.WriteLine(ex.Message);
|
logger.Error(ex.Message);
|
||||||
transaction.Rollback();
|
await transaction.RollbackAsync();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Console.WriteLine(ex.Message);
|
logger.Error(ex.Message);
|
||||||
|
await transaction.RollbackAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -190,6 +224,63 @@ namespace MTWireGuard.Application
|
||||||
return System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString();
|
return System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Return full path of requested file in app's home directory
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filename">requested file name</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string GetHomePath(string filename)
|
||||||
|
{
|
||||||
|
return RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "/home/app" : Path.Join(AppDomain.CurrentDomain.BaseDirectory, filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Return full path of requested file in log files directory
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filename">requested file name</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string GetLogPath(string filename)
|
||||||
|
{
|
||||||
|
return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? Path.Join(AppDomain.CurrentDomain.BaseDirectory, "log", filename) : Path.Join("/var/log/mtwireguard", filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetIDFile() => GetHomePath("identifier.id");
|
||||||
|
public static string GetIDContent() => File.ReadAllText(GetIDFile());
|
||||||
|
|
||||||
|
public static Serilog.Core.Logger LoggerConfiguration()
|
||||||
|
{
|
||||||
|
return new LoggerConfiguration()
|
||||||
|
.Enrich.WithExceptionDetails(new DestructuringOptionsBuilder()
|
||||||
|
.WithDefaultDestructurers()
|
||||||
|
.WithRootName("Message").WithRootName("Exception").WithRootName("Exception"))
|
||||||
|
.Enrich.WithProperty("App.Version", System.Reflection.Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? "0.0.0.0")
|
||||||
|
.Enrich.WithMachineName()
|
||||||
|
.Enrich.WithEnvironmentUserName()
|
||||||
|
.Enrich.WithClientId(GetIDContent())
|
||||||
|
.WriteTo.Logger(lc => lc
|
||||||
|
.Filter.ByIncludingOnly(AspNetCoreRequestLogging())
|
||||||
|
.WriteTo.File(
|
||||||
|
GetLogPath("access.log"),
|
||||||
|
rollingInterval: RollingInterval.Day,
|
||||||
|
retainedFileCountLimit: 31
|
||||||
|
))
|
||||||
|
.WriteTo.Logger(lc => lc
|
||||||
|
.Filter.ByIncludingOnly(LogEvent => LogEvent.Exception != null)
|
||||||
|
.WriteTo.Seq("https://mtwglogger.techgarage.ir/"))
|
||||||
|
.WriteTo.Logger(lc => lc
|
||||||
|
.Filter.ByExcluding(AspNetCoreRequestLogging())
|
||||||
|
.WriteTo.SQLite(GetLogPath("logs.db")))
|
||||||
|
.CreateLogger();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Func<LogEvent, bool> AspNetCoreRequestLogging()
|
||||||
|
{
|
||||||
|
return e =>
|
||||||
|
Matching.FromSource("Microsoft.AspNetCore.Hosting.Diagnostics").Invoke(e) ||
|
||||||
|
Matching.FromSource("Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware").Invoke(e) ||
|
||||||
|
Matching.FromSource("Microsoft.AspNetCore.Routing.EndpointMiddleware").Invoke(e);
|
||||||
|
}
|
||||||
|
|
||||||
public static TimeSpan ConvertToTimeSpan(string input)
|
public static TimeSpan ConvertToTimeSpan(string input)
|
||||||
{
|
{
|
||||||
int weeks = 0;
|
int weeks = 0;
|
||||||
|
|
|
@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Routing;
|
using Microsoft.AspNetCore.Routing;
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.IdentityModel.Tokens;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
using MikrotikAPI;
|
using MikrotikAPI;
|
||||||
using MTWireGuard.Application.Models;
|
using MTWireGuard.Application.Models;
|
||||||
|
@ -115,11 +116,11 @@ namespace MTWireGuard.Application.MinimalAPI
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static async Task<Results<Accepted, ProblemHttpResult>> TrafficUsageUpdate(
|
public static async Task<Results<Accepted, ProblemHttpResult>> TrafficUsageUpdate(
|
||||||
[FromServices] IMapper mapper,
|
[FromServices] IMapper mapper,
|
||||||
|
[FromServices] Serilog.ILogger logger,
|
||||||
[FromServices] DBContext dbContext,
|
[FromServices] DBContext dbContext,
|
||||||
[FromServices] IMikrotikRepository mikrotikRepository,
|
[FromServices] IMikrotikRepository mikrotikRepository,
|
||||||
HttpContext context)
|
HttpContext context)
|
||||||
{
|
{
|
||||||
|
|
||||||
StreamReader reader = new(context.Request.Body);
|
StreamReader reader = new(context.Request.Body);
|
||||||
string body = await reader.ReadToEndAsync();
|
string body = await reader.ReadToEndAsync();
|
||||||
|
|
||||||
|
@ -128,7 +129,7 @@ namespace MTWireGuard.Application.MinimalAPI
|
||||||
|
|
||||||
if (updates == null || updates.Count < 1) return TypedResults.Problem("Empty data");
|
if (updates == null || updates.Count < 1) return TypedResults.Problem("Empty data");
|
||||||
|
|
||||||
Helper.HandleUserTraffics(updates, dbContext, mikrotikRepository);
|
Helper.HandleUserTraffics(updates, dbContext, mikrotikRepository, logger);
|
||||||
|
|
||||||
return TypedResults.Accepted("Done");
|
return TypedResults.Accepted("Done");
|
||||||
}
|
}
|
||||||
|
|
18
Application/SerilogUiAuthorizeFilter.cs
Normal file
18
Application/SerilogUiAuthorizeFilter.cs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Serilog.Ui.Web.Authorization;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MTWireGuard.Application
|
||||||
|
{
|
||||||
|
public class SerilogUiAuthorizeFilter : IUiAuthorizationFilter
|
||||||
|
{
|
||||||
|
public bool Authorize(HttpContext httpContext)
|
||||||
|
{
|
||||||
|
return httpContext.User.Identity is { IsAuthenticated: true };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,86 +1,77 @@
|
||||||
using AutoMapper;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using MTWireGuard.Application.Models;
|
|
||||||
using MTWireGuard.Application.Repositories;
|
using MTWireGuard.Application.Repositories;
|
||||||
using MTWireGuard.Application.Services;
|
using Serilog;
|
||||||
using System;
|
using System.Net.NetworkInformation;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using static System.Runtime.InteropServices.JavaScript.JSType;
|
|
||||||
|
|
||||||
namespace MTWireGuard.Application
|
namespace MTWireGuard.Application
|
||||||
{
|
{
|
||||||
public class SetupValidator(IServiceProvider serviceProvider)
|
public class SetupValidator(IServiceProvider serviceProvider)
|
||||||
{
|
{
|
||||||
private IMikrotikRepository api;
|
private IMikrotikRepository api;
|
||||||
|
private ILogger logger;
|
||||||
|
|
||||||
public async Task Validate()
|
public static bool IsValid { get; private set; }
|
||||||
|
public static string Title { get; private set; }
|
||||||
|
public static string Description { get; private set; }
|
||||||
|
|
||||||
|
public async Task<bool> Validate()
|
||||||
{
|
{
|
||||||
var envVariables = ValidateEnvironmentVariables();
|
InitializeServices();
|
||||||
if (envVariables)
|
|
||||||
|
if (ValidateEnvironmentVariables())
|
||||||
{
|
{
|
||||||
Console.BackgroundColor = ConsoleColor.Black;
|
LogAndDisplayError("Environment variables are not set!", "Please set \"MT_IP\", \"MT_USER\", \"MT_PASS\", \"MT_PUBLIC_IP\" variables in container environment.");
|
||||||
Console.ForegroundColor = ConsoleColor.Red;
|
IsValid = false;
|
||||||
Console.WriteLine($"[-] Environment variables are not set!");
|
return false;
|
||||||
Console.WriteLine($"[!] Please set \"MT_IP\", \"MT_USER\", \"MT_PASS\", \"MT_PUBLIC_IP\" variables in container environment.");
|
|
||||||
Console.ResetColor();
|
|
||||||
Shutdown();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
serviceProvider.GetService<DBContext>().Database.EnsureCreated();
|
if (!File.Exists(Helper.GetIDFile()))
|
||||||
api = serviceProvider.GetService<IMikrotikRepository>();
|
{
|
||||||
|
using var fs = File.OpenWrite(Helper.GetIDFile());
|
||||||
|
var id = Guid.NewGuid().ToString();
|
||||||
|
id = id[(id.LastIndexOf('-') + 1)..];
|
||||||
|
byte[] identifier = new UTF8Encoding(true).GetBytes(id);
|
||||||
|
fs.Write(identifier, 0, identifier.Length);
|
||||||
|
}
|
||||||
|
|
||||||
var (apiConnection, apiConnectionMessage) = await ValidateAPIConnection();
|
var (apiConnection, apiConnectionMessage) = await ValidateAPIConnection();
|
||||||
if (!apiConnection)
|
if (!apiConnection)
|
||||||
{
|
{
|
||||||
Console.BackgroundColor = ConsoleColor.Black;
|
var MT_IP = Environment.GetEnvironmentVariable("MT_IP");
|
||||||
Console.ForegroundColor = ConsoleColor.Red;
|
var ping = new Ping();
|
||||||
Console.WriteLine($"[-] Error connecting to the router api!");
|
var reply = ping.Send(MT_IP, 60 * 1000);
|
||||||
Console.WriteLine($"[!] {apiConnectionMessage}");
|
if (reply.Status == IPStatus.Success)
|
||||||
Console.ResetColor();
|
{
|
||||||
Shutdown();
|
LogAndDisplayError("Error connecting to the router api!", apiConnectionMessage);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogAndDisplayError("Error connecting to the router api!", $"Can't find Mikrotik API server at address: {MT_IP}\r\nping status: {reply.Status}");
|
||||||
|
}
|
||||||
|
IsValid = false;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var ip = GetIPAddress();
|
var ip = GetIPAddress();
|
||||||
if (string.IsNullOrEmpty(ip))
|
if (string.IsNullOrEmpty(ip))
|
||||||
{
|
{
|
||||||
Console.BackgroundColor = ConsoleColor.Black;
|
LogAndDisplayError("Error getting container IP address!", "Invalid container IP address.");
|
||||||
Console.ForegroundColor = ConsoleColor.Red;
|
IsValid = false;
|
||||||
Console.WriteLine($"[-] Error getting container IP address!");
|
return false;
|
||||||
Console.ResetColor();
|
|
||||||
Shutdown();
|
|
||||||
}
|
}
|
||||||
var scripts = await api.GetScripts();
|
|
||||||
var schedulers = await api.GetSchedulers();
|
|
||||||
var trafficScript = scripts.Find(x => x.Name == "SendTrafficUsage");
|
|
||||||
var trafficScheduler = schedulers.Find(x => x.Name == "TrafficUsage");
|
|
||||||
|
|
||||||
if (trafficScript == null)
|
if (!await api.TryConnectAsync())
|
||||||
{
|
{
|
||||||
var create = await api.CreateScript(new()
|
LogAndDisplayError("Error connecting to the router api!", "Connecting to API failed.");
|
||||||
{
|
IsValid = false;
|
||||||
Name = "SendTrafficUsage",
|
return false;
|
||||||
Policies = ["write", "read", "test"],
|
|
||||||
DontRequiredPermissions = false,
|
|
||||||
Source = Helper.PeersTrafficUsageScript($"http://{ip}/api/usage")
|
|
||||||
});
|
|
||||||
var result = create.Code;
|
|
||||||
}
|
|
||||||
if (trafficScheduler == null)
|
|
||||||
{
|
|
||||||
var create = await api.CreateScheduler(new()
|
|
||||||
{
|
|
||||||
Name = "TrafficUsage",
|
|
||||||
Interval = new TimeSpan(0, 5, 0),
|
|
||||||
OnEvent = "SendTrafficUsage",
|
|
||||||
Policies = ["write", "read", "test"]
|
|
||||||
});
|
|
||||||
var result = create.Code;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await EnsureTrafficScripts(ip);
|
||||||
|
IsValid = true;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool ValidateEnvironmentVariables()
|
private static bool ValidateEnvironmentVariables()
|
||||||
|
@ -105,7 +96,7 @@ namespace MTWireGuard.Application
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetIPAddress()
|
private string GetIPAddress()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -115,11 +106,69 @@ namespace MTWireGuard.Application
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Console.WriteLine(ex.Message);
|
logger.Error(ex, "Error getting container IP address.");
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void LogAndDisplayError(string title, string description)
|
||||||
|
{
|
||||||
|
Title = title;
|
||||||
|
Description = description;
|
||||||
|
Console.BackgroundColor = ConsoleColor.Black;
|
||||||
|
Console.ForegroundColor = ConsoleColor.Red;
|
||||||
|
Console.WriteLine($"[-] {Title}");
|
||||||
|
Console.WriteLine($"[!] {Description}");
|
||||||
|
Console.ResetColor();
|
||||||
|
logger.Error("Error in container configuration", new { Error = Title, Description });
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeServices()
|
||||||
|
{
|
||||||
|
serviceProvider.GetService<DBContext>().Database.EnsureCreated();
|
||||||
|
api = serviceProvider.GetService<IMikrotikRepository>();
|
||||||
|
logger = serviceProvider.GetService<ILogger>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task EnsureTrafficScripts(string ip)
|
||||||
|
{
|
||||||
|
var scripts = await api.GetScripts();
|
||||||
|
var schedulers = await api.GetSchedulers();
|
||||||
|
|
||||||
|
//if (scripts.Find(x => x.Name == "SendTrafficUsage") == null)
|
||||||
|
//{
|
||||||
|
// var create = await api.CreateScript(new()
|
||||||
|
// {
|
||||||
|
// Name = "SendTrafficUsage",
|
||||||
|
// Policies = ["write", "read", "test", "ftp"],
|
||||||
|
// DontRequiredPermissions = false,
|
||||||
|
// Source = Helper.PeersTrafficUsageScript($"http://{ip}/api/usage")
|
||||||
|
// });
|
||||||
|
// var result = create.Code;
|
||||||
|
// logger.Information("Created TrafficUsage Script", new
|
||||||
|
// {
|
||||||
|
// result = create
|
||||||
|
// });
|
||||||
|
//}
|
||||||
|
if (schedulers.Find(x => x.Name == "TrafficUsage") == null)
|
||||||
|
{
|
||||||
|
var create = await api.CreateScheduler(new()
|
||||||
|
{
|
||||||
|
Name = "TrafficUsage",
|
||||||
|
Interval = new TimeSpan(0, 5, 0),
|
||||||
|
//OnEvent = "SendTrafficUsage",
|
||||||
|
OnEvent = Helper.PeersTrafficUsageScript($"http://{ip}/api/usage"),
|
||||||
|
Policies = ["write", "read", "test", "ftp"],
|
||||||
|
Comment = "update wireguard peers traffic usage"
|
||||||
|
});
|
||||||
|
var result = create.Code;
|
||||||
|
logger.Information("Created TrafficUsage Scheduler", new
|
||||||
|
{
|
||||||
|
result = create
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void Shutdown()
|
private static void Shutdown()
|
||||||
{
|
{
|
||||||
Environment.Exit(0);
|
Environment.Exit(0);
|
||||||
|
|
|
@ -14,6 +14,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MTWireGuard.Application", "
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MikrotikAPI", "MikrotikAPI\MikrotikAPI.csproj", "{357EE40B-AA30-482C-94CF-34854BE24D61}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MikrotikAPI", "MikrotikAPI\MikrotikAPI.csproj", "{357EE40B-AA30-482C-94CF-34854BE24D61}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Serilog.Ui.SqliteProvider", "Serilog.Ui.SqliteProvider\Serilog.Ui.SqliteProvider.csproj", "{4D0ED34E-E84B-4861-82DC-C2149DCD6E14}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
@ -32,6 +34,10 @@ Global
|
||||||
{357EE40B-AA30-482C-94CF-34854BE24D61}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{357EE40B-AA30-482C-94CF-34854BE24D61}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{357EE40B-AA30-482C-94CF-34854BE24D61}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{357EE40B-AA30-482C-94CF-34854BE24D61}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{357EE40B-AA30-482C-94CF-34854BE24D61}.Release|Any CPU.Build.0 = Release|Any CPU
|
{357EE40B-AA30-482C-94CF-34854BE24D61}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{4D0ED34E-E84B-4861-82DC-C2149DCD6E14}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{4D0ED34E-E84B-4861-82DC-C2149DCD6E14}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{4D0ED34E-E84B-4861-82DC-C2149DCD6E14}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{4D0ED34E-E84B-4861-82DC-C2149DCD6E14}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Serilog.Ui.Core;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Serilog.Ui.SqliteDataProvider
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Sqlite data provider specific extension methods for <see cref="SerilogUiOptionsBuilder"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static class SerilogUiOptionBuilderExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Configures the SerilogUi to connect to a Sqlite database.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="optionsBuilder"> The options builder. </param>
|
||||||
|
/// <param name="connectionString"> The connection string. </param>
|
||||||
|
/// <param name="tableName"> Name of the table. </param>
|
||||||
|
/// <exception cref="ArgumentNullException"> throw if connectionString is null </exception>
|
||||||
|
/// <exception cref="ArgumentNullException"> throw is tableName is null </exception>
|
||||||
|
public static void UseSqliteServer(
|
||||||
|
this SerilogUiOptionsBuilder optionsBuilder,
|
||||||
|
string connectionString,
|
||||||
|
string tableName
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(connectionString))
|
||||||
|
throw new ArgumentNullException(nameof(connectionString));
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(tableName))
|
||||||
|
throw new ArgumentNullException(nameof(tableName));
|
||||||
|
|
||||||
|
var relationProvider = new RelationalDbOptions
|
||||||
|
{
|
||||||
|
ConnectionString = connectionString,
|
||||||
|
TableName = tableName
|
||||||
|
};
|
||||||
|
|
||||||
|
((ISerilogUiOptionsBuilder)optionsBuilder).Services
|
||||||
|
.AddScoped<IDataProvider, SqliteDataProvider>(p => ActivatorUtilities.CreateInstance<SqliteDataProvider>(p, relationProvider));
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
16
Serilog.Ui.SqliteProvider/Serilog.Ui.SqliteProvider.csproj
Normal file
16
Serilog.Ui.SqliteProvider/Serilog.Ui.SqliteProvider.csproj
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Dapper" Version="2.1.35" />
|
||||||
|
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="8.0.5" />
|
||||||
|
<PackageReference Include="Serilog.UI" Version="2.6.0" />
|
||||||
|
<PackageReference Include="SQLite" Version="3.13.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
134
Serilog.Ui.SqliteProvider/SqliteDataProvider.cs
Normal file
134
Serilog.Ui.SqliteProvider/SqliteDataProvider.cs
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
using Dapper;
|
||||||
|
using Microsoft.Data.Sqlite;
|
||||||
|
using Serilog.Ui.Core;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Serilog.Ui.SqliteDataProvider
|
||||||
|
{
|
||||||
|
public class SqliteDataProvider(RelationalDbOptions options) : IDataProvider
|
||||||
|
{
|
||||||
|
private readonly RelationalDbOptions _options = options ?? throw new ArgumentNullException(nameof(options));
|
||||||
|
|
||||||
|
public async Task<(IEnumerable<LogModel>, int)> FetchDataAsync(
|
||||||
|
int page,
|
||||||
|
int count,
|
||||||
|
string level = null,
|
||||||
|
string searchCriteria = null,
|
||||||
|
DateTime? startDate = null,
|
||||||
|
DateTime? endDate = null
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var logsTask = GetLogs(page - 1, count, level, searchCriteria, startDate, endDate);
|
||||||
|
var logCountTask = CountLogs(level, searchCriteria, startDate, endDate);
|
||||||
|
|
||||||
|
await Task.WhenAll(logsTask, logCountTask);
|
||||||
|
|
||||||
|
return (await logsTask, await logCountTask);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Name => _options.ToDataProviderName("SQLite");
|
||||||
|
|
||||||
|
private Task<IEnumerable<LogModel>> GetLogs(
|
||||||
|
int page,
|
||||||
|
int count,
|
||||||
|
string level,
|
||||||
|
string searchCriteria,
|
||||||
|
DateTime? startDate,
|
||||||
|
DateTime? endDate)
|
||||||
|
{
|
||||||
|
var queryBuilder = new StringBuilder();
|
||||||
|
queryBuilder.Append("SELECT Id, RenderedMessage AS Message, Level, Timestamp, Exception, Properties FROM ");
|
||||||
|
queryBuilder.Append(_options.TableName);
|
||||||
|
queryBuilder.Append(" ");
|
||||||
|
|
||||||
|
GenerateWhereClause(queryBuilder, level, searchCriteria, startDate, endDate);
|
||||||
|
|
||||||
|
queryBuilder.Append("ORDER BY Id DESC LIMIT @Offset, @Count");
|
||||||
|
|
||||||
|
using (var connection = new SqliteConnection(_options.ConnectionString))
|
||||||
|
{
|
||||||
|
var param = new
|
||||||
|
{
|
||||||
|
Offset = page * count,
|
||||||
|
Count = count,
|
||||||
|
Level = level,
|
||||||
|
Search = searchCriteria != null ? $"%{searchCriteria}%" : null,
|
||||||
|
StartDate = startDate,
|
||||||
|
EndDate = endDate
|
||||||
|
};
|
||||||
|
var logs = connection.Query<LogModel>(queryBuilder.ToString(), param);
|
||||||
|
var index = 1;
|
||||||
|
foreach (var log in logs)
|
||||||
|
log.RowNo = (page * count) + index++;
|
||||||
|
|
||||||
|
return Task.FromResult(logs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task<int> CountLogs(
|
||||||
|
string level,
|
||||||
|
string searchCriteria,
|
||||||
|
DateTime? startDate = null,
|
||||||
|
DateTime? endDate = null)
|
||||||
|
{
|
||||||
|
var queryBuilder = new StringBuilder();
|
||||||
|
queryBuilder.Append("SELECT COUNT(Id) FROM ");
|
||||||
|
queryBuilder.Append(_options.TableName);
|
||||||
|
queryBuilder.Append(" ");
|
||||||
|
|
||||||
|
GenerateWhereClause(queryBuilder, level, searchCriteria, startDate, endDate);
|
||||||
|
|
||||||
|
using var connection = new SqliteConnection(_options.ConnectionString);
|
||||||
|
return Task.FromResult(connection.QueryFirstOrDefault<int>(queryBuilder.ToString(),
|
||||||
|
new
|
||||||
|
{
|
||||||
|
Level = level,
|
||||||
|
Search = searchCriteria != null ? "%" + searchCriteria + "%" : null,
|
||||||
|
StartDate = startDate,
|
||||||
|
EndDate = endDate
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GenerateWhereClause(
|
||||||
|
StringBuilder queryBuilder,
|
||||||
|
string level,
|
||||||
|
string searchCriteria,
|
||||||
|
DateTime? startDate = null,
|
||||||
|
DateTime? endDate = null)
|
||||||
|
{
|
||||||
|
var whereIncluded = false;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(level))
|
||||||
|
{
|
||||||
|
queryBuilder.Append("WHERE Level = @Level ");
|
||||||
|
whereIncluded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(searchCriteria))
|
||||||
|
{
|
||||||
|
queryBuilder.Append(whereIncluded
|
||||||
|
? "AND (RenderedMessage LIKE @Search OR Exception LIKE @Search) "
|
||||||
|
: "WHERE (RenderedMessage LIKE @Search OR Exception LIKE @Search) ");
|
||||||
|
whereIncluded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startDate != null)
|
||||||
|
{
|
||||||
|
queryBuilder.Append(whereIncluded
|
||||||
|
? "AND Timestamp >= @StartDate "
|
||||||
|
: "WHERE Timestamp >= @StartDate ");
|
||||||
|
whereIncluded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (endDate != null)
|
||||||
|
{
|
||||||
|
queryBuilder.Append(whereIncluded
|
||||||
|
? "AND Timestamp <= @EndDate "
|
||||||
|
: "WHERE Timestamp <= @EndDate ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
@ -11,18 +11,23 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="AutoMapper" Version="12.0.1" />
|
<PackageReference Include="AutoMapper" Version="13.0.1" />
|
||||||
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.1" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.6">
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.1">
|
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.6" />
|
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.0" />
|
||||||
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="8.0.0" />
|
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="8.0.2" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
<PackageReference Include="NuGet.Protocol" Version="6.8.0" />
|
<PackageReference Include="NuGet.Protocol" Version="6.10.1" />
|
||||||
<PackageReference Include="Razor.Templating.Core" Version="1.9.0" />
|
<PackageReference Include="Razor.Templating.Core" Version="2.0.0" />
|
||||||
<PackageReference Include="System.Drawing.Common" Version="8.0.1" />
|
<PackageReference Include="Serilog.AspNetCore" Version="8.0.1" />
|
||||||
|
<PackageReference Include="Serilog.Enrichers.Environment" Version="3.0.1" />
|
||||||
|
<PackageReference Include="Serilog.Exceptions" Version="8.4.0" />
|
||||||
|
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||||
|
<PackageReference Include="Serilog.Sinks.Seq" Version="8.0.0" />
|
||||||
|
<PackageReference Include="Serilog.Sinks.SQLite" Version="6.0.0" />
|
||||||
|
<PackageReference Include="System.Drawing.Common" Version="8.0.6" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
@page
|
@page
|
||||||
@model ErrorModel
|
@model ErrorModel
|
||||||
|
@inject IHttpContextAccessor contextAccessor
|
||||||
@{
|
@{
|
||||||
Layout = null;
|
Layout = null;
|
||||||
|
var httpContext = contextAccessor.HttpContext;
|
||||||
|
string title = Application.ExceptionHandlerContext.Message,
|
||||||
|
message = Application.ExceptionHandlerContext.StackTrace,
|
||||||
|
details = Application.ExceptionHandlerContext.Details;
|
||||||
}
|
}
|
||||||
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
|
@ -32,6 +37,16 @@
|
||||||
|
|
||||||
<!-- Theme Style Switcher-->
|
<!-- Theme Style Switcher-->
|
||||||
<script src="assets/js/themeSwitcher.js"></script>
|
<script src="assets/js/themeSwitcher.js"></script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.accordion-body {
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accordion-body p {
|
||||||
|
max-height: 30vh;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="min-vh-100 d-flex flex-row align-items-center">
|
<div class="min-vh-100 d-flex flex-row align-items-center">
|
||||||
|
@ -39,10 +54,45 @@
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="clearfix">
|
<div class="clearfix">
|
||||||
<h1 class="float-start display-3 me-4">500</h1>
|
<h1 class="float-start display-3 mx-2 mt-4">500</h1>
|
||||||
<h4 class="pt-3">@ViewBag.Title</h4>
|
<h4 class="pt-3">@title</h4>
|
||||||
<h6>@ViewBag.Message</h6>
|
@if (!Application.SetupValidator.IsValid)
|
||||||
<p class="text-medium-emphasis">@Html.Raw(ViewBag.Details)</p>
|
{
|
||||||
|
<hr />
|
||||||
|
<h4 class="text-danger"><i class='bx bxs-chevrons-right'></i> Invalid Setup Variables</h4>
|
||||||
|
<strong>@Application.SetupValidator.Title</strong>
|
||||||
|
<br />
|
||||||
|
<p>@Application.SetupValidator.Description</p>
|
||||||
|
}
|
||||||
|
<div class="accordion mb-3" id="infoAccordion">
|
||||||
|
<div class="accordion-item">
|
||||||
|
<h2 class="accordion-header">
|
||||||
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#titleAccordion" aria-expanded="true" aria-controls="titleAccordion">
|
||||||
|
Error Message
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="titleAccordion" class="accordion-collapse collapse" data-bs-parent="#infoAccordion">
|
||||||
|
<div class="accordion-body">
|
||||||
|
<p class="text-medium-emphasis text-break">@message</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="accordion-item">
|
||||||
|
<h2 class="accordion-header">
|
||||||
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#detailsAccordion" aria-expanded="true" aria-controls="detailsAccordion">
|
||||||
|
Error Details
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="detailsAccordion" class="accordion-collapse collapse" data-bs-parent="#infoAccordion">
|
||||||
|
<div class="accordion-body">
|
||||||
|
<p class="text-medium-emphasis text-break">@Html.Raw(details)</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<a href="/Debug" class="btn btn-primary">
|
||||||
|
<i class="bx bx-notepad me-1"></i> <span class="d-none d-lg-inline-block">View Logs</span>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,15 +1,19 @@
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Diagnostics;
|
using Microsoft.AspNetCore.Diagnostics;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
|
using Microsoft.VisualStudio.Web.CodeGenerators.Mvc.Templates.Blazor;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
|
||||||
namespace MTWireGuard.Pages
|
namespace MTWireGuard.Pages
|
||||||
{
|
{
|
||||||
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
||||||
[IgnoreAntiforgeryToken]
|
[IgnoreAntiforgeryToken]
|
||||||
|
[AllowAnonymous]
|
||||||
|
|
||||||
public class ErrorModel : PageModel
|
public class ErrorModel : PageModel
|
||||||
{
|
{
|
||||||
|
|
||||||
public ErrorModel()
|
public ErrorModel()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,19 +3,29 @@ using MTWireGuard.Middlewares;
|
||||||
using MTWireGuard.Application;
|
using MTWireGuard.Application;
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using MTWireGuard.Application.MinimalAPI;
|
using MTWireGuard.Application.MinimalAPI;
|
||||||
|
using Serilog;
|
||||||
|
using Serilog.Exceptions.Core;
|
||||||
|
using Serilog.Exceptions;
|
||||||
|
using System.Configuration;
|
||||||
|
using Serilog.Ui.Web;
|
||||||
|
using Serilog.Ui.Web.Authorization;
|
||||||
|
|
||||||
|
internal class Program
|
||||||
|
{
|
||||||
|
public static bool isValid { get; private set; }
|
||||||
|
public static string validationMessage { get; private set; }
|
||||||
|
|
||||||
|
private static async Task Main(string[] args)
|
||||||
|
{
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
builder.Services.AddControllersWithViews();
|
||||||
|
builder.Services.AddExceptionHandler<ExceptionHandler>();
|
||||||
|
builder.Services.AddProblemDetails();
|
||||||
builder.Services.AddApplicationServices();
|
builder.Services.AddApplicationServices();
|
||||||
|
|
||||||
var app = builder.Build();
|
builder.Host.UseSerilog(Helper.LoggerConfiguration());
|
||||||
|
|
||||||
// Configure the HTTP request pipeline.
|
var app = builder.Build();
|
||||||
if (!app.Environment.IsDevelopment())
|
|
||||||
{
|
|
||||||
app.UseExceptionHandler("/Error");
|
|
||||||
app.UseHsts();
|
|
||||||
}
|
|
||||||
|
|
||||||
app.UseHttpsRedirection();
|
app.UseHttpsRedirection();
|
||||||
|
|
||||||
|
@ -23,10 +33,13 @@ var serviceScope = app.Services.CreateScope().ServiceProvider;
|
||||||
|
|
||||||
// Validate Prerequisite
|
// Validate Prerequisite
|
||||||
var validator = new SetupValidator(serviceScope);
|
var validator = new SetupValidator(serviceScope);
|
||||||
await validator.Validate();
|
isValid = await validator.Validate();
|
||||||
|
|
||||||
if (!app.Environment.IsDevelopment())
|
if (!app.Environment.IsDevelopment())
|
||||||
|
{
|
||||||
app.UseStaticFiles();
|
app.UseStaticFiles();
|
||||||
|
app.UseHsts();
|
||||||
|
}
|
||||||
else
|
else
|
||||||
app.UseStaticFiles(new StaticFileOptions()
|
app.UseStaticFiles(new StaticFileOptions()
|
||||||
{
|
{
|
||||||
|
@ -37,11 +50,9 @@ else
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.UseDependencyCheck();
|
app.UseExceptionHandler();
|
||||||
app.UseClientReporting();
|
app.UseClientReporting();
|
||||||
app.UseExceptionHandling();
|
|
||||||
//app.UseAntiForgery();
|
//app.UseAntiForgery();
|
||||||
|
|
||||||
app.UseRouting();
|
app.UseRouting();
|
||||||
|
|
||||||
app.UseAuthentication();
|
app.UseAuthentication();
|
||||||
|
@ -62,4 +73,21 @@ app.UseCors(options =>
|
||||||
options.AllowAnyOrigin();
|
options.AllowAnyOrigin();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.UseSerilogRequestLogging();
|
||||||
|
|
||||||
|
app.UseSerilogUi(options =>
|
||||||
|
{
|
||||||
|
options.RoutePrefix = "Debug";
|
||||||
|
options.InjectStylesheet("/assets/lib/boxicons/css/boxicons.min.css");
|
||||||
|
options.InjectStylesheet("/assets/css/serilogui.css");
|
||||||
|
options.InjectJavascript("/assets/js/serilogui.js");
|
||||||
|
options.Authorization.AuthenticationType = AuthenticationType.Jwt;
|
||||||
|
options.Authorization.Filters =
|
||||||
|
[
|
||||||
|
new SerilogUiAuthorizeFilter()
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
app.Run();
|
app.Run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
46
UI/wwwroot/assets/css/serilogui.css
Normal file
46
UI/wwwroot/assets/css/serilogui.css
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
#sidebar {
|
||||||
|
background: #343a40;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sidebar .logo {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sidebar ul li a {
|
||||||
|
padding: 0;
|
||||||
|
border-radius: .5rem;
|
||||||
|
border-bottom: 3px solid;
|
||||||
|
outline: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
margin: .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sidebar ul li a span {
|
||||||
|
margin-right: 15px;
|
||||||
|
width: 50px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 40px;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
margin-right: .25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sidebar ul.components li a {
|
||||||
|
--bs-primary: #4582ec;
|
||||||
|
padding: 10px 0;
|
||||||
|
color: var(--bs-primary);
|
||||||
|
background-color: rgba(105,108,255,.16) !important;
|
||||||
|
border-color: var(--bs-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
#sidebar.active ul.components li a {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
#logTable .log-level {
|
||||||
|
border-radius: 5px;
|
||||||
|
user-select: none;
|
||||||
|
}
|
6
UI/wwwroot/assets/js/serilogui.js
Normal file
6
UI/wwwroot/assets/js/serilogui.js
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
let favicon = document.createElement('link');
|
||||||
|
favicon.href = 'img/favicon.ico';
|
||||||
|
favicon.rel = 'icon';
|
||||||
|
document.getElementsByTagName('head')[0].appendChild(favicon);
|
||||||
|
document.querySelector('#sidebar.active .logo:first-child').innerHTML = 'MW';
|
||||||
|
document.querySelector('#sidebar.active .logo:last-child').innerHTML = 'MTWireguard';
|
Loading…
Add table
Add a link
Reference in a new issue