diff --git a/Application/Main.cs b/Application/Main.cs index b02f73058..4369d2c6d 100644 --- a/Application/Main.cs +++ b/Application/Main.cs @@ -9,6 +9,7 @@ using SharedLibraryCore.Configuration; using SharedLibraryCore.Exceptions; using SharedLibraryCore.Helpers; using SharedLibraryCore.Interfaces; +using SharedLibraryCore.Repositories; using System; using System.Linq; using System.Text; @@ -284,6 +285,7 @@ namespace IW4MAdmin.Application .AddSingleton() .AddSingleton() .AddSingleton() + .AddSingleton() .AddTransient() .AddSingleton(_serviceProvider => { diff --git a/SharedLibraryCore/Database/DatabaseContext.cs b/SharedLibraryCore/Database/DatabaseContext.cs index b3ed94e9d..0a5b505c5 100644 --- a/SharedLibraryCore/Database/DatabaseContext.cs +++ b/SharedLibraryCore/Database/DatabaseContext.cs @@ -71,8 +71,8 @@ namespace SharedLibraryCore.Database protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { - // optionsBuilder.UseLoggerFactory(_loggerFactory) - // .EnableSensitiveDataLogging(); + optionsBuilder.UseLoggerFactory(_loggerFactory) + .EnableSensitiveDataLogging(); if (string.IsNullOrEmpty(_ConnectionString)) { diff --git a/SharedLibraryCore/Dtos/AuditInfo.cs b/SharedLibraryCore/Dtos/AuditInfo.cs new file mode 100644 index 000000000..bf963f51b --- /dev/null +++ b/SharedLibraryCore/Dtos/AuditInfo.cs @@ -0,0 +1,57 @@ +using System; + +namespace SharedLibraryCore.Dtos +{ + /// + /// data transfer class for audit information + /// + public class AuditInfo + { + /// + /// name of the origin entity + /// + public string OriginName { get; set; } + + /// + /// id of the origin entity + /// + public int OriginId { get; set; } + + /// + /// name of the target entity + /// + public string TargetName { get; set; } + + /// + /// id of the target entity + /// + public int? TargetId { get; set; } + + /// + /// when the audit event occured + /// + public DateTime When { get; set; } + + /// + /// what audit action occured + /// + public string Action { get; set; } + + /// + /// additional comment data about the audit event + /// + public string Data { get; set; } + + private string oldValue; + /// + /// previous value + /// + public string OldValue { get => oldValue ?? "--"; set => oldValue = value; } + + private string newValue; + /// + /// new value + /// + public string NewValue { get => newValue ?? "--"; set => newValue = value; } + } +} diff --git a/SharedLibraryCore/Dtos/PaginationInfo.cs b/SharedLibraryCore/Dtos/PaginationInfo.cs new file mode 100644 index 000000000..bd7794337 --- /dev/null +++ b/SharedLibraryCore/Dtos/PaginationInfo.cs @@ -0,0 +1,34 @@ +namespace SharedLibraryCore.Dtos +{ + /// + /// pagination information holder class + /// + public class PaginationInfo + { + /// + /// how many items to skip + /// + public int Offset { get; set; } + + /// + /// how many itesm to take + /// + public int Count { get; set; } + + /// + /// filter query + /// + public string Filter { get; set; } + + /// + /// direction of ordering + /// + public SortDirection Direction { get; set; } = SortDirection.Descending; + } + + public enum SortDirection + { + Ascending, + Descending + } +} diff --git a/SharedLibraryCore/Interfaces/IAuditInformationRepository.cs b/SharedLibraryCore/Interfaces/IAuditInformationRepository.cs new file mode 100644 index 000000000..a4bd53a9b --- /dev/null +++ b/SharedLibraryCore/Interfaces/IAuditInformationRepository.cs @@ -0,0 +1,19 @@ +using SharedLibraryCore.Dtos; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace SharedLibraryCore.Interfaces +{ + /// + /// describes the capabilities of the audit info repository + /// + public interface IAuditInformationRepository + { + /// + /// retrieves a list of audit information for given pagination params + /// + /// pagination info + /// + Task> ListAuditInformation(PaginationInfo paginationInfo); + } +} diff --git a/SharedLibraryCore/Repositories/AuditInformationRepository.cs b/SharedLibraryCore/Repositories/AuditInformationRepository.cs new file mode 100644 index 000000000..90f327e43 --- /dev/null +++ b/SharedLibraryCore/Repositories/AuditInformationRepository.cs @@ -0,0 +1,55 @@ +using Microsoft.EntityFrameworkCore; +using SharedLibraryCore.Dtos; +using SharedLibraryCore.Interfaces; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace SharedLibraryCore.Repositories +{ + /// + /// implementation if IAuditInformationRepository + /// + public class AuditInformationRepository : IAuditInformationRepository + { + private readonly IDatabaseContextFactory _contextFactory; + + public AuditInformationRepository(IDatabaseContextFactory contextFactory) + { + _contextFactory = contextFactory; + } + + /// + public async Task> ListAuditInformation(PaginationInfo paginationInfo) + { + using (var ctx = _contextFactory.CreateContext(enableTracking: false)) + { + var iqItems = (from change in ctx.EFChangeHistory + where change.TypeOfChange != Database.Models.EFChangeHistory.ChangeType.Ban + orderby change.TimeChanged descending + join originClient in ctx.Clients + on (change.ImpersonationEntityId ?? change.OriginEntityId) equals originClient.ClientId + join targetClient in ctx.Clients + on change.TargetEntityId equals targetClient.ClientId + into targetChange + from targetClient in targetChange.DefaultIfEmpty() + select new AuditInfo() + { + Action = change.TypeOfChange.ToString(), + OriginName = originClient.CurrentAlias.Name, + OriginId = originClient.ClientId, + TargetName = targetClient == null ? "" : targetClient.CurrentAlias.Name, + TargetId = targetClient == null ? new int?() : targetClient.ClientId, + When = change.TimeChanged, + Data = change.Comment, + OldValue = change.PreviousValue, + NewValue = change.CurrentValue + }) + .Skip(paginationInfo.Offset) + .Take(paginationInfo.Count); + + return await iqItems.ToListAsync(); + } + } + } +} diff --git a/WebfrontCore/Controllers/AdminController.cs b/WebfrontCore/Controllers/AdminController.cs new file mode 100644 index 000000000..e8215e2ca --- /dev/null +++ b/WebfrontCore/Controllers/AdminController.cs @@ -0,0 +1,45 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using SharedLibraryCore; +using SharedLibraryCore.Dtos; +using SharedLibraryCore.Interfaces; +using System.Threading.Tasks; + +namespace WebfrontCore.Controllers +{ + public class AdminController : BaseController + { + private readonly IAuditInformationRepository _auditInformationRepository; + private readonly ITranslationLookup _translationLookup; + private static readonly int DEFAULT_COUNT = 25; + + public AdminController(IManager manager, IAuditInformationRepository auditInformationRepository, ITranslationLookup translationLookup) : base(manager) + { + _auditInformationRepository = auditInformationRepository; + _translationLookup = translationLookup; + } + + [Authorize] + public async Task AuditLog() + { + ViewBag.EnableColorCodes = Manager.GetApplicationSettings().Configuration().EnableColorCodes; + ViewBag.IsFluid = true; + ViewBag.Title = _translationLookup["WEBFRONT_NAV_AUDIT_LOG"]; + ViewBag.InitialOffset = DEFAULT_COUNT; + + var auditItems = await _auditInformationRepository.ListAuditInformation(new PaginationInfo() + { + Count = DEFAULT_COUNT + }); + + return View(auditItems); + } + + public async Task ListAuditLog([FromQuery] PaginationInfo paginationInfo) + { + ViewBag.EnableColorCodes = Manager.GetApplicationSettings().Configuration().EnableColorCodes; + var auditItems = await _auditInformationRepository.ListAuditInformation(paginationInfo); + return PartialView("_ListAuditLog", auditItems); + } + } +} diff --git a/WebfrontCore/Startup.cs b/WebfrontCore/Startup.cs index 66edbc0f8..2d6341c1c 100644 --- a/WebfrontCore/Startup.cs +++ b/WebfrontCore/Startup.cs @@ -101,7 +101,12 @@ namespace WebfrontCore #endif services.AddSingleton(Program.Manager); + + // todo: this needs to be handled more gracefully services.AddSingleton(Program.ApplicationServiceProvider.GetService()); + services.AddSingleton(Program.ApplicationServiceProvider.GetService()); + services.AddSingleton(Program.ApplicationServiceProvider.GetService()); + services.AddSingleton(Program.ApplicationServiceProvider.GetService()); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. diff --git a/WebfrontCore/Views/Admin/AuditLog.cshtml b/WebfrontCore/Views/Admin/AuditLog.cshtml new file mode 100644 index 000000000..87a0ef8b8 --- /dev/null +++ b/WebfrontCore/Views/Admin/AuditLog.cshtml @@ -0,0 +1,34 @@ +@{ + var loc = SharedLibraryCore.Utilities.CurrentLocalization.LocalizationIndex; +} +

@ViewBag.Title

+ + + + + + + + + + + + + + + + + +
@loc["WEBFRONT_PENALTY_TEMPLATE_TYPE"]@loc["WEBFRONT_PENALTY_TEMPLATE_ADMIN"]@loc["WEBFRONT_PENALTY_TEMPLATE_NAME"]@loc["WEBFRONT_ADMIN_AUDIT_LOG_INFO"]@loc["WEBFRONT_ADMIN_AUDIT_LOG_CURRENT"]@loc["WEBFRONT_ADMIN_AUDIT_LOG_TIME"]
+ + +@section scripts { + + + + +} diff --git a/WebfrontCore/Views/Admin/_ListAuditLog.cshtml b/WebfrontCore/Views/Admin/_ListAuditLog.cshtml new file mode 100644 index 000000000..8d8a68025 --- /dev/null +++ b/WebfrontCore/Views/Admin/_ListAuditLog.cshtml @@ -0,0 +1,99 @@ +@using SharedLibraryCore.Dtos +@model IEnumerable +@{ + var loc = SharedLibraryCore.Utilities.CurrentLocalization.LocalizationIndex; +} + +@foreach (var info in Model) +{ + + + @loc["WEBFRONT_PENALTY_TEMPLATE_TYPE"] + + @info.Action + + + + @loc["WEBFRONT_PENALTY_TEMPLATE_ADMIN"] + + + + + + + + @loc["WEBFRONT_PENALTY_TEMPLATE_NAME"] + + @if (info.TargetId != null) + { + + + + } + else + { + -- + } + + + + @loc["WEBFRONT_ADMIN_AUDIT_LOG_INFO"] + + @info.Data + + + @* + @loc["WEBFRONT_ADMIN_AUDIT_LOG_PREVIOUS"] + + @info.OldValue + + *@ + + @loc["WEBFRONT_ADMIN_AUDIT_LOG_CURRENT"] + + @info.NewValue + + + + @loc["WEBFRONT_ADMIN_AUDIT_LOG_TIME"] + + @info.When.ToString() + + + + + + + @info.Action + + + + + + + + @if (info.TargetId != null) + { + + + + } + else + { + -- + } + + + @info.Data + + @* + @info.OldValue + *@ + + @info.NewValue + + + @info.When.ToString() + + +} \ No newline at end of file diff --git a/WebfrontCore/Views/Shared/_Layout.cshtml b/WebfrontCore/Views/Shared/_Layout.cshtml index e8f07b5af..abf01e8ff 100644 --- a/WebfrontCore/Views/Shared/_Layout.cshtml +++ b/WebfrontCore/Views/Shared/_Layout.cshtml @@ -39,38 +39,39 @@ @foreach (var _page in ViewBag.Pages) { - + } @if (!string.IsNullOrEmpty(ViewBag.SocialLink)) { - + } @if (ViewBag.Authorized) { - + @loc["WEBFRONT_NAV_AUDIT_LOG"] + @loc["WEBFRONT_ACTION_RECENT_CLIENTS"] + @loc["WEBFRONT_ACTION_TOKEN"] + @loc["WEBFRONT_NAV_LOGOUT"] + + } else { - + }