implement audit log view in webfront

This commit is contained in:
RaidMax 2020-04-28 16:48:06 -05:00
parent 58bfd189d0
commit 7715113b56
11 changed files with 372 additions and 21 deletions

View File

@ -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<IConfigurationHandlerFactory, ConfigurationHandlerFactory>()
.AddSingleton<IParserRegexFactory, ParserRegexFactory>()
.AddSingleton<IDatabaseContextFactory, DatabaseContextFactory>()
.AddSingleton<IAuditInformationRepository, AuditInformationRepository>()
.AddTransient<IParserPatternMatcher, ParserPatternMatcher>()
.AddSingleton(_serviceProvider =>
{

View File

@ -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))
{

View File

@ -0,0 +1,57 @@
using System;
namespace SharedLibraryCore.Dtos
{
/// <summary>
/// data transfer class for audit information
/// </summary>
public class AuditInfo
{
/// <summary>
/// name of the origin entity
/// </summary>
public string OriginName { get; set; }
/// <summary>
/// id of the origin entity
/// </summary>
public int OriginId { get; set; }
/// <summary>
/// name of the target entity
/// </summary>
public string TargetName { get; set; }
/// <summary>
/// id of the target entity
/// </summary>
public int? TargetId { get; set; }
/// <summary>
/// when the audit event occured
/// </summary>
public DateTime When { get; set; }
/// <summary>
/// what audit action occured
/// </summary>
public string Action { get; set; }
/// <summary>
/// additional comment data about the audit event
/// </summary>
public string Data { get; set; }
private string oldValue;
/// <summary>
/// previous value
/// </summary>
public string OldValue { get => oldValue ?? "--"; set => oldValue = value; }
private string newValue;
/// <summary>
/// new value
/// </summary>
public string NewValue { get => newValue ?? "--"; set => newValue = value; }
}
}

View File

@ -0,0 +1,34 @@
namespace SharedLibraryCore.Dtos
{
/// <summary>
/// pagination information holder class
/// </summary>
public class PaginationInfo
{
/// <summary>
/// how many items to skip
/// </summary>
public int Offset { get; set; }
/// <summary>
/// how many itesm to take
/// </summary>
public int Count { get; set; }
/// <summary>
/// filter query
/// </summary>
public string Filter { get; set; }
/// <summary>
/// direction of ordering
/// </summary>
public SortDirection Direction { get; set; } = SortDirection.Descending;
}
public enum SortDirection
{
Ascending,
Descending
}
}

View File

@ -0,0 +1,19 @@
using SharedLibraryCore.Dtos;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace SharedLibraryCore.Interfaces
{
/// <summary>
/// describes the capabilities of the audit info repository
/// </summary>
public interface IAuditInformationRepository
{
/// <summary>
/// retrieves a list of audit information for given pagination params
/// </summary>
/// <param name="paginationInfo">pagination info</param>
/// <returns></returns>
Task<IList<AuditInfo>> ListAuditInformation(PaginationInfo paginationInfo);
}
}

View File

@ -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
{
/// <summary>
/// implementation if IAuditInformationRepository
/// </summary>
public class AuditInformationRepository : IAuditInformationRepository
{
private readonly IDatabaseContextFactory _contextFactory;
public AuditInformationRepository(IDatabaseContextFactory contextFactory)
{
_contextFactory = contextFactory;
}
/// <inheritdoc/>
public async Task<IList<AuditInfo>> 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();
}
}
}
}

View File

@ -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<IActionResult> 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<IActionResult> ListAuditLog([FromQuery] PaginationInfo paginationInfo)
{
ViewBag.EnableColorCodes = Manager.GetApplicationSettings().Configuration().EnableColorCodes;
var auditItems = await _auditInformationRepository.ListAuditInformation(paginationInfo);
return PartialView("_ListAuditLog", auditItems);
}
}
}

View File

@ -101,7 +101,12 @@ namespace WebfrontCore
#endif
services.AddSingleton(Program.Manager);
// todo: this needs to be handled more gracefully
services.AddSingleton(Program.ApplicationServiceProvider.GetService<IConfigurationHandlerFactory>());
services.AddSingleton(Program.ApplicationServiceProvider.GetService<IDatabaseContextFactory>());
services.AddSingleton(Program.ApplicationServiceProvider.GetService<IAuditInformationRepository>());
services.AddSingleton(Program.ApplicationServiceProvider.GetService<ITranslationLookup>());
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.

View File

@ -0,0 +1,34 @@
@{
var loc = SharedLibraryCore.Utilities.CurrentLocalization.LocalizationIndex;
}
<h4 class="pb-3 text-center">@ViewBag.Title</h4>
<table class="table table-striped">
<thead class="d-none d-lg-table-header-group">
<tr class="bg-primary pt-2 pb-2">
<th scope="col">@loc["WEBFRONT_PENALTY_TEMPLATE_TYPE"]</th>
<th scope="col">@loc["WEBFRONT_PENALTY_TEMPLATE_ADMIN"]</th>
<th scope="col">@loc["WEBFRONT_PENALTY_TEMPLATE_NAME"]</th>
<th scope="col">@loc["WEBFRONT_ADMIN_AUDIT_LOG_INFO"]</th>
<!--<th scope="col">@loc["WEBFRONT_ADMIN_AUDIT_LOG_PREVIOUS"]</th>-->
<th scope="col">@loc["WEBFRONT_ADMIN_AUDIT_LOG_CURRENT"]</th>
<th scope="col" class="text-right">@loc["WEBFRONT_ADMIN_AUDIT_LOG_TIME"]</th>
</tr>
</thead>
<tbody id="audit_log_table_body" class="border-bottom bg-dark">
<partial name="_ListAuditLog" />
</tbody>
</table>
<span id="load_audit_log_button" class="loader-load-more oi oi-chevron-bottom text-center text-primary w-100 h3 pb-0 mb-0 d-none d-lg-block"></span>
@section scripts {
<environment include="Development">
<script type="text/javascript" src="~/js/loader.js"></script>
</environment>
<script>
$(document).ready(function () {
initLoader('/Admin/ListAuditLog', '#audit_log_table_body', @ViewBag.IntialOffset);
});
</script>
}

View File

@ -0,0 +1,99 @@
@using SharedLibraryCore.Dtos
@model IEnumerable<AuditInfo>
@{
var loc = SharedLibraryCore.Utilities.CurrentLocalization.LocalizationIndex;
}
@foreach (var info in Model)
{
<!-- mobile -->
<tr class="d-table-row d-lg-none bg-dark">
<th scope="row" class="bg-primary">@loc["WEBFRONT_PENALTY_TEMPLATE_TYPE"]</th>
<td class="text-light">
@info.Action
</td>
</tr>
<tr class="d-table-row d-lg-none bg-dark">
<th scope="row" class="bg-primary">@loc["WEBFRONT_PENALTY_TEMPLATE_ADMIN"]</th>
<td>
<a asp-controller="Client" asp-action="ProfileAsync" asp-route-id="@info.OriginId" class="link-inverse">
<color-code value="@info.OriginName" allow="@ViewBag.EnableColorCodes"></color-code>
</a>
</td>
</tr>
<tr class="d-table-row d-lg-none bg-dark">
<th scope="row" class="bg-primary">@loc["WEBFRONT_PENALTY_TEMPLATE_NAME"]</th>
<td>
@if (info.TargetId != null)
{
<a asp-controller="Client" asp-action="ProfileAsync" asp-route-id="@info.TargetId" class="link-inverse">
<color-code value="@info.TargetName" allow="@ViewBag.EnableColorCodes"></color-code>
</a>
}
else
{
<span>--</span>
}
</td>
</tr>
<tr class="d-table-row d-lg-none bg-dark">
<th scope="row" class="bg-primary">@loc["WEBFRONT_ADMIN_AUDIT_LOG_INFO"]</th>
<td class="text-light">
@info.Data
</td>
</tr>
@*<tr class="d-table-row d-lg-none bg-dark">
<th scope="row" class="bg-primary">@loc["WEBFRONT_ADMIN_AUDIT_LOG_PREVIOUS"]</th>
<td class="text-light">
@info.OldValue
</td>
</tr>*@
<tr class="d-table-row d-lg-none bg-dark">
<th scope="row" class="bg-primary">@loc["WEBFRONT_ADMIN_AUDIT_LOG_CURRENT"]</th>
<td class="text-light">
@info.NewValue
</td>
</tr>
<tr class="d-table-row d-lg-none bg-dark">
<th scope="row" class="w-25 bg-primary" style="border-bottom: 1px solid #222">@loc["WEBFRONT_ADMIN_AUDIT_LOG_TIME"]</th>
<td class="text-light mb-2 border-bottom">
@info.When.ToString()
</td>
</tr>
<!-- desktop -->
<tr class="d-none d-lg-table-row">
<td class="text-light font-weight-bold">
@info.Action
</td>
<td>
<a asp-controller="Client" asp-action="ProfileAsync" asp-route-id="@info.OriginId" class="link-inverse">
<color-code value="@info.OriginName" allow="@ViewBag.EnableColorCodes"></color-code>
</a>
</td>
<td>
@if (info.TargetId != null)
{
<a asp-controller="Client" asp-action="ProfileAsync" asp-route-id="@info.TargetId" class="link-inverse">
<color-code value="@info.TargetName" allow="@ViewBag.EnableColorCodes"></color-code>
</a>
}
else
{
<span>--</span>
}
</td>
<td class="text-light">
@info.Data
@*<td class="text-light">
@info.OldValue
</td>*@
<td class="text-light">
@info.NewValue
</td>
<td class="text-light text-right">
@info.When.ToString()
</td>
</tr>
}

View File

@ -39,38 +39,39 @@
<li class="nav-item text-center text-lg-left">@Html.ActionLink(loc["WEBFRONT_NAV_HELP"], "Help", "Home", new { area = "" }, new { @class = "nav-link" })</li>
@foreach (var _page in ViewBag.Pages)
{
<li class="nav-item text-center text-lg-left">
<a class="nav-link" href="@_page.Location">@_page.Name</a>
</li>
<li class="nav-item text-center text-lg-left">
<a class="nav-link" href="@_page.Location">@_page.Name</a>
</li>
}
<li class="nav-item text-center text-lg-left"></li>
@if (!string.IsNullOrEmpty(ViewBag.SocialLink))
{
<li class="nav-item text-center text-lg-left"><a href="@ViewBag.SocialLink" class="nav-link" target="_blank">@ViewBag.SocialTitle</a></li>
<li class="nav-item text-center text-lg-left"><a href="@ViewBag.SocialLink" class="nav-link" target="_blank">@ViewBag.SocialTitle</a></li>
}
@if (ViewBag.Authorized)
{
<li class="nav-link dropdown text-center text-lg-left p-0">
<a href="#" class="nav-link oi oi-person dropdown-toggle oi-fix-navbar w-100" id="account_dropdown" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></a>
<li class="nav-link dropdown text-center text-lg-left p-0">
<a href="#" class="nav-link oi oi-person dropdown-toggle oi-fix-navbar w-100" id="account_dropdown" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></a>
<div class="dropdown-menu p-0" aria-labelledby="account_dropdown">
<a asp-controller="Console" asp-action="Index" class="dropdown-item bg-dark text-muted text-center text-lg-left">@loc["WEBFRONT_NAV_CONSOLE"]</a>
<a asp-controller="Client" asp-action="ProfileAsync" asp-route-id="@ViewBag.User.ClientId" class="dropdown-item bg-dark text-muted text-center text-lg-left">@loc["WEBFRONT_NAV_PROFILE"]</a>
@if (ViewBag.User.Level >= SharedLibraryCore.Database.Models.EFClient.Permission.Owner)
<div class="dropdown-menu p-0" aria-labelledby="account_dropdown">
<a asp-controller="Console" asp-action="Index" class="dropdown-item bg-dark text-muted text-center text-lg-left">@loc["WEBFRONT_NAV_CONSOLE"]</a>
<a asp-controller="Client" asp-action="ProfileAsync" asp-route-id="@ViewBag.User.ClientId" class="dropdown-item bg-dark text-muted text-center text-lg-left">@loc["WEBFRONT_NAV_PROFILE"]</a>
@if (ViewBag.User.Level >= SharedLibraryCore.Database.Models.EFClient.Permission.Owner)
{
<a asp-controller="Configuration" asp-action="Edit" class="dropdown-item bg-dark text-muted text-center text-lg-left">@loc["WEBFRONT_NAV_EDIT_CONFIGURATION"]</a>
<a asp-controller="Configuration" asp-action="Edit" class="dropdown-item bg-dark text-muted text-center text-lg-left">@loc["WEBFRONT_NAV_EDIT_CONFIGURATION"]</a>
}
<a class="dropdown-item bg-dark text-muted text-center text-lg-left profile-action" href="#" data-action="RecentClients" title="@loc["WEBFRONT_ACTION_RECENT_CLIENTS"]">@loc["WEBFRONT_ACTION_RECENT_CLIENTS"]</a>
<a class="dropdown-item bg-dark text-muted text-center text-lg-left profile-action" href="#" data-action="GenerateLoginToken" title="@loc["WEBFRONT_ACTION_TOKEN"]">@loc["WEBFRONT_ACTION_TOKEN"]</a>
<a asp-controller="Account" asp-action="LogoutAsync" class="dropdown-item bg-dark text-muted text-center text-lg-left">@loc["WEBFRONT_NAV_LOGOUT"]</a>
</div>
</li>
<a asp-controller="Admin" asp-action="AuditLog" class="dropdown-item bg-dark text-muted text-center text-lg-left">@loc["WEBFRONT_NAV_AUDIT_LOG"]</a>
<a class="dropdown-item bg-dark text-muted text-center text-lg-left profile-action" href="#" data-action="RecentClients" title="@loc["WEBFRONT_ACTION_RECENT_CLIENTS"]">@loc["WEBFRONT_ACTION_RECENT_CLIENTS"]</a>
<a class="dropdown-item bg-dark text-muted text-center text-lg-left profile-action" href="#" data-action="GenerateLoginToken" title="@loc["WEBFRONT_ACTION_TOKEN"]">@loc["WEBFRONT_ACTION_TOKEN"]</a>
<a asp-controller="Account" asp-action="LogoutAsync" class="dropdown-item bg-dark text-muted text-center text-lg-left">@loc["WEBFRONT_NAV_LOGOUT"]</a>
</div>
</li>
}
else
{
<li class="nav-item text-center text-md-left">
<a href="#" id="profile_action_login_btn" class="nav-link profile-action oi oi-key oi-fix-navbar w-100" title="Login" data-action="login" aria-hidden="true"></a>
</li>
<li class="nav-item text-center text-md-left">
<a href="#" id="profile_action_login_btn" class="nav-link profile-action oi oi-key oi-fix-navbar w-100" title="Login" data-action="login" aria-hidden="true"></a>
</li>
}
</ul>
<form class="form-inline text-primary pt-3 pb-3" method="get" action="/Client/FindAsync">