add ban management page
This commit is contained in:
parent
c493fbe13d
commit
acc967e50a
@ -47,6 +47,8 @@ namespace SharedLibraryCore
|
|||||||
public static char[] DirectorySeparatorChars = { '\\', '/' };
|
public static char[] DirectorySeparatorChars = { '\\', '/' };
|
||||||
public static char CommandPrefix { get; set; } = '!';
|
public static char CommandPrefix { get; set; } = '!';
|
||||||
|
|
||||||
|
public static string ToStandardFormat(this DateTime? time) => time?.ToString("yyyy-MM-dd H:mm:ss");
|
||||||
|
|
||||||
public static EFClient IW4MAdminClient(Server server = null)
|
public static EFClient IW4MAdminClient(Server server = null)
|
||||||
{
|
{
|
||||||
return new EFClient
|
return new EFClient
|
||||||
|
@ -142,7 +142,7 @@ namespace WebfrontCore.Controllers
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
public IActionResult UnbanForm()
|
public IActionResult UnbanForm(long? id)
|
||||||
{
|
{
|
||||||
var info = new ActionInfo
|
var info = new ActionInfo
|
||||||
{
|
{
|
||||||
@ -159,6 +159,15 @@ namespace WebfrontCore.Controllers
|
|||||||
Action = "UnbanAsync",
|
Action = "UnbanAsync",
|
||||||
ShouldRefresh = true
|
ShouldRefresh = true
|
||||||
};
|
};
|
||||||
|
if (id is not null)
|
||||||
|
{
|
||||||
|
info.Inputs.Add(new()
|
||||||
|
{
|
||||||
|
Name = "targetId",
|
||||||
|
Value = id.ToString(),
|
||||||
|
Type = "hidden"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return View("_ActionForm", info);
|
return View("_ActionForm", info);
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ using SharedLibraryCore;
|
|||||||
using SharedLibraryCore.Dtos;
|
using SharedLibraryCore.Dtos;
|
||||||
using SharedLibraryCore.Interfaces;
|
using SharedLibraryCore.Interfaces;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using WebfrontCore.QueryHelpers.Models;
|
||||||
|
|
||||||
namespace WebfrontCore.Controllers
|
namespace WebfrontCore.Controllers
|
||||||
{
|
{
|
||||||
@ -11,12 +12,16 @@ namespace WebfrontCore.Controllers
|
|||||||
{
|
{
|
||||||
private readonly IAuditInformationRepository _auditInformationRepository;
|
private readonly IAuditInformationRepository _auditInformationRepository;
|
||||||
private readonly ITranslationLookup _translationLookup;
|
private readonly ITranslationLookup _translationLookup;
|
||||||
|
private readonly IResourceQueryHelper<BanInfoRequest, BanInfo> _banInfoQueryHelper;
|
||||||
private static readonly int DEFAULT_COUNT = 25;
|
private static readonly int DEFAULT_COUNT = 25;
|
||||||
|
|
||||||
public AdminController(IManager manager, IAuditInformationRepository auditInformationRepository, ITranslationLookup translationLookup) : base(manager)
|
public AdminController(IManager manager, IAuditInformationRepository auditInformationRepository,
|
||||||
|
ITranslationLookup translationLookup,
|
||||||
|
IResourceQueryHelper<BanInfoRequest, BanInfo> banInfoQueryHelper) : base(manager)
|
||||||
{
|
{
|
||||||
_auditInformationRepository = auditInformationRepository;
|
_auditInformationRepository = auditInformationRepository;
|
||||||
_translationLookup = translationLookup;
|
_translationLookup = translationLookup;
|
||||||
|
_banInfoQueryHelper = banInfoQueryHelper;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize]
|
[Authorize]
|
||||||
@ -27,7 +32,7 @@ namespace WebfrontCore.Controllers
|
|||||||
ViewBag.Title = _translationLookup["WEBFRONT_NAV_AUDIT_LOG"];
|
ViewBag.Title = _translationLookup["WEBFRONT_NAV_AUDIT_LOG"];
|
||||||
ViewBag.InitialOffset = DEFAULT_COUNT;
|
ViewBag.InitialOffset = DEFAULT_COUNT;
|
||||||
|
|
||||||
var auditItems = await _auditInformationRepository.ListAuditInformation(new PaginationRequest()
|
var auditItems = await _auditInformationRepository.ListAuditInformation(new PaginationRequest
|
||||||
{
|
{
|
||||||
Count = DEFAULT_COUNT
|
Count = DEFAULT_COUNT
|
||||||
});
|
});
|
||||||
@ -41,5 +46,25 @@ namespace WebfrontCore.Controllers
|
|||||||
var auditItems = await _auditInformationRepository.ListAuditInformation(paginationInfo);
|
var auditItems = await _auditInformationRepository.ListAuditInformation(paginationInfo);
|
||||||
return PartialView("_ListAuditLog", auditItems);
|
return PartialView("_ListAuditLog", auditItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<IActionResult> BanManagement([FromQuery] BanInfoRequest request)
|
||||||
|
{
|
||||||
|
var results = await _banInfoQueryHelper.QueryResource(request);
|
||||||
|
|
||||||
|
ViewBag.ClientName = request.ClientName;
|
||||||
|
ViewBag.ClientId = request.ClientId;
|
||||||
|
ViewBag.ClientIP = request.ClientIP;
|
||||||
|
ViewBag.ClientGuid = request.ClientGuid;
|
||||||
|
|
||||||
|
ViewBag.Title = "Ban Management";
|
||||||
|
|
||||||
|
return View(results.Results);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IActionResult> BanManagementList([FromQuery] BanInfoRequest request)
|
||||||
|
{
|
||||||
|
var results = await _banInfoQueryHelper.QueryResource(request);
|
||||||
|
return PartialView("_BanEntries", results.Results);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
using System.Linq;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Data.Abstractions;
|
using Data.Abstractions;
|
||||||
|
using Data.Models;
|
||||||
using Data.Models.Client;
|
using Data.Models.Client;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using SharedLibraryCore;
|
using SharedLibraryCore;
|
||||||
@ -21,16 +25,26 @@ public class BanInfoResourceQueryHelper : IResourceQueryHelper<BanInfoRequest, B
|
|||||||
|
|
||||||
public async Task<ResourceQueryHelperResult<BanInfo>> QueryResource(BanInfoRequest query)
|
public async Task<ResourceQueryHelperResult<BanInfo>> QueryResource(BanInfoRequest query)
|
||||||
{
|
{
|
||||||
if (query.Count > 30)
|
if (query.Count > 10)
|
||||||
{
|
{
|
||||||
query.Count = 30;
|
query.Count = 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
await using var context = _contextFactory.CreateContext(false);
|
await using var context = _contextFactory.CreateContext(false);
|
||||||
|
|
||||||
var matchingClients = await context.Clients.Where(client =>
|
var iqMatchingClients = context.Clients.Where(client => client.Level == EFClient.Permission.Banned);
|
||||||
EF.Functions.ILike(client.CurrentAlias.SearchableName ?? client.CurrentAlias.Name, $"%{query.ClientName.Trim()}%"))
|
iqMatchingClients = SetupSearchArgs(query, iqMatchingClients);
|
||||||
.Where(client => client.Level == EFClient.Permission.Banned)
|
|
||||||
|
if (string.IsNullOrEmpty(query.ClientName) && string.IsNullOrEmpty(query.ClientGuid) &&
|
||||||
|
query.ClientId is null && string.IsNullOrEmpty(query.ClientIP))
|
||||||
|
{
|
||||||
|
return new ResourceQueryHelperResult<BanInfo>
|
||||||
|
{
|
||||||
|
Results = Enumerable.Empty<BanInfo>()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var matchingClients = await iqMatchingClients
|
||||||
.OrderByDescending(client => client.LastConnection)
|
.OrderByDescending(client => client.LastConnection)
|
||||||
.Skip(query.Offset)
|
.Skip(query.Offset)
|
||||||
.Take(query.Count)
|
.Take(query.Count)
|
||||||
@ -39,52 +53,117 @@ public class BanInfoResourceQueryHelper : IResourceQueryHelper<BanInfoRequest, B
|
|||||||
client.CurrentAlias.Name,
|
client.CurrentAlias.Name,
|
||||||
client.NetworkId,
|
client.NetworkId,
|
||||||
client.AliasLinkId,
|
client.AliasLinkId,
|
||||||
client.ClientId
|
client.ClientId,
|
||||||
|
client.CurrentAlias.IPAddress
|
||||||
}).ToListAsync();
|
}).ToListAsync();
|
||||||
|
|
||||||
var usedIps = await context.Aliases
|
var results = new List<BanInfo>();
|
||||||
.Where(alias => matchingClients.Select(client => client.AliasLinkId).Contains(alias.LinkId))
|
var matchedClientIds = new List<int?>();
|
||||||
.Where(alias => alias.IPAddress != null)
|
var lateDateTime = DateTime.Now.AddYears(100);
|
||||||
.Select(alias => new { alias.IPAddress, alias.LinkId })
|
|
||||||
.ToListAsync();
|
|
||||||
|
|
||||||
var usedIpsGrouped = usedIps
|
// would prefer not to loop this, but unfortunately due to the data design
|
||||||
.GroupBy(alias => alias.LinkId)
|
// we can't properly group on ip and alias link
|
||||||
.ToDictionary(key => key.Key, value => value.Select(alias => alias.IPAddress).Distinct());
|
foreach (var matchingClient in matchingClients)
|
||||||
|
|
||||||
var searchingNetworkIds = matchingClients.Select(client => client.NetworkId);
|
|
||||||
var searchingIps = usedIpsGrouped.SelectMany(group => group.Value);
|
|
||||||
|
|
||||||
var matchedPenalties = await context.PenaltyIdentifiers.Where(identifier =>
|
|
||||||
searchingNetworkIds.Contains(identifier.NetworkId) ||
|
|
||||||
searchingIps.Contains(identifier.IPv4Address))
|
|
||||||
.Select(penalty => new
|
|
||||||
{
|
|
||||||
penalty.CreatedDateTime,
|
|
||||||
PunisherName = penalty.Penalty.Punisher.CurrentAlias.Name,
|
|
||||||
Offense = string.IsNullOrEmpty(penalty.Penalty.AutomatedOffense) ? penalty.Penalty.Offense : "Anticheat Detection",
|
|
||||||
LinkId = penalty.Penalty.Offender.AliasLinkId,
|
|
||||||
penalty.Penalty.PunisherId
|
|
||||||
})
|
|
||||||
.ToListAsync();
|
|
||||||
|
|
||||||
var groupedPenalties = matchedPenalties.GroupBy(penalty => penalty.LinkId)
|
|
||||||
.ToDictionary(key => key.Key, value => value.FirstOrDefault());
|
|
||||||
|
|
||||||
var results = matchingClients.Select(client =>
|
|
||||||
{
|
{
|
||||||
var matchedPenalty =
|
var usedIps = await context.Aliases
|
||||||
groupedPenalties.ContainsKey(client.AliasLinkId) ? groupedPenalties[client.AliasLinkId] : null;
|
.Where(alias => matchingClient.AliasLinkId == alias.LinkId)
|
||||||
return new BanInfo
|
.Where(alias => alias.IPAddress != null)
|
||||||
|
.Select(alias => new { alias.IPAddress, alias.LinkId })
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
var searchingNetworkId = matchingClient.NetworkId;
|
||||||
|
var searchingIps = usedIps.Select(ip => ip.IPAddress).Distinct();
|
||||||
|
|
||||||
|
var matchedPenalties = await context.PenaltyIdentifiers.Where(identifier =>
|
||||||
|
identifier.NetworkId == searchingNetworkId ||
|
||||||
|
searchingIps.Contains(identifier.IPv4Address))
|
||||||
|
.Where(identifier => identifier.Penalty.Expires == null || identifier.Penalty.Expires > lateDateTime)
|
||||||
|
.Select(penalty => new
|
||||||
|
{
|
||||||
|
penalty.CreatedDateTime,
|
||||||
|
PunisherName = penalty.Penalty.Punisher.CurrentAlias.Name,
|
||||||
|
OffenderName = penalty.Penalty.Offender.CurrentAlias.Name,
|
||||||
|
Offense = string.IsNullOrEmpty(penalty.Penalty.AutomatedOffense)
|
||||||
|
? penalty.Penalty.Offense
|
||||||
|
: "Anticheat Detection",
|
||||||
|
LinkId = penalty.Penalty.Offender.AliasLinkId,
|
||||||
|
penalty.Penalty.OffenderId,
|
||||||
|
penalty.Penalty.PunisherId,
|
||||||
|
penalty.IPv4Address,
|
||||||
|
penalty.Penalty.Offender.NetworkId
|
||||||
|
})
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
if (!matchedPenalties.Any())
|
||||||
{
|
{
|
||||||
DateTime = matchedPenalty?.CreatedDateTime,
|
var linkIds = (await context.Aliases
|
||||||
OffenderName = client.Name.StripColors(),
|
.Where(alias => alias.IPAddress != null && searchingIps.Contains(alias.IPAddress))
|
||||||
OffenderId = client.ClientId,
|
.Select(alias => alias.LinkId)
|
||||||
PunisherName = matchedPenalty?.PunisherName.StripColors(),
|
.ToListAsync()).Distinct();
|
||||||
PunisherId = matchedPenalty?.PunisherId,
|
|
||||||
Offense = matchedPenalty?.Offense
|
|
||||||
};
|
matchedPenalties = await context.Penalties.Where(penalty => penalty.Type == EFPenalty.PenaltyType.Ban)
|
||||||
}).ToList();
|
.Where(penalty => penalty.Expires == null || penalty.Expires > lateDateTime)
|
||||||
|
.Where(penalty => penalty.LinkId != null && linkIds.Contains(penalty.LinkId.Value))
|
||||||
|
.OrderByDescending(penalty => penalty.When)
|
||||||
|
.Select(penalty => new
|
||||||
|
{
|
||||||
|
CreatedDateTime = penalty.When,
|
||||||
|
PunisherName = penalty.Punisher.CurrentAlias.Name,
|
||||||
|
OffenderName = penalty.Offender.CurrentAlias.Name,
|
||||||
|
Offense = string.IsNullOrEmpty(penalty.AutomatedOffense)
|
||||||
|
? penalty.Offense
|
||||||
|
: "Anticheat Detection",
|
||||||
|
LinkId = penalty.Offender.AliasLinkId,
|
||||||
|
penalty.OffenderId,
|
||||||
|
penalty.PunisherId,
|
||||||
|
IPv4Address = penalty.Offender.CurrentAlias.IPAddress,
|
||||||
|
penalty.Offender.NetworkId
|
||||||
|
}).ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
var allPenalties = matchedPenalties.Select(penalty => new PenaltyInfo
|
||||||
|
{
|
||||||
|
DateTime = penalty.CreatedDateTime,
|
||||||
|
Offense = penalty.Offense,
|
||||||
|
PunisherInfo = new RelatedClientInfo
|
||||||
|
{
|
||||||
|
ClientName = penalty.PunisherName.StripColors(),
|
||||||
|
ClientId = penalty.PunisherId,
|
||||||
|
},
|
||||||
|
OffenderInfo = new RelatedClientInfo
|
||||||
|
{
|
||||||
|
ClientName = penalty.OffenderName.StripColors(),
|
||||||
|
ClientId = penalty.OffenderId,
|
||||||
|
IPAddress = penalty.IPv4Address,
|
||||||
|
NetworkId = penalty.NetworkId
|
||||||
|
}
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
|
||||||
|
if (matchedClientIds.Contains(matchingClient.ClientId))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
matchedClientIds.Add(matchingClient.ClientId);
|
||||||
|
var relatedEntities =
|
||||||
|
allPenalties.Where(penalty => penalty.OffenderInfo.ClientId != matchingClient.ClientId);
|
||||||
|
|
||||||
|
matchedClientIds.AddRange(relatedEntities.Select(client => client.OffenderInfo.ClientId));
|
||||||
|
|
||||||
|
results.Add(new BanInfo
|
||||||
|
{
|
||||||
|
ClientName = matchingClient.Name.StripColors(),
|
||||||
|
ClientId = matchingClient.ClientId,
|
||||||
|
NetworkId = matchingClient.NetworkId,
|
||||||
|
IPAddress = matchingClient.IPAddress,
|
||||||
|
|
||||||
|
AssociatedPenalties = relatedEntities,
|
||||||
|
AttachedPenalty = allPenalties.FirstOrDefault(penalty =>
|
||||||
|
penalty.OffenderInfo.ClientId == matchingClient.ClientId)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return new ResourceQueryHelperResult<BanInfo>
|
return new ResourceQueryHelperResult<BanInfo>
|
||||||
{
|
{
|
||||||
@ -93,4 +172,61 @@ public class BanInfoResourceQueryHelper : IResourceQueryHelper<BanInfoRequest, B
|
|||||||
Results = results
|
Results = results
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IQueryable<EFClient> SetupSearchArgs(BanInfoRequest query, IQueryable<EFClient> source)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(query.ClientName))
|
||||||
|
{
|
||||||
|
var nameToSearch = query.ClientName.Trim().ToLower();
|
||||||
|
source = source.Where(client =>
|
||||||
|
EF.Functions.Like(client.CurrentAlias.SearchableName ?? client.CurrentAlias.Name.ToLower(),
|
||||||
|
$"%{nameToSearch}%"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(query.ClientGuid))
|
||||||
|
{
|
||||||
|
long? parsedGuid = null;
|
||||||
|
if (!long.TryParse(query.ClientGuid, NumberStyles.HexNumber, null, out var guid))
|
||||||
|
{
|
||||||
|
if (!long.TryParse(query.ClientGuid, out var guid2))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
parsedGuid = guid2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
parsedGuid = guid;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parsedGuid is not null)
|
||||||
|
{
|
||||||
|
source = source.Where(client => client.NetworkId == parsedGuid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query.ClientId is not null)
|
||||||
|
{
|
||||||
|
source = source.Where(client => client.ClientId == query.ClientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(query.ClientIP))
|
||||||
|
{
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
var parsedIp = query.ClientIP.ConvertToIP();
|
||||||
|
if (parsedIp is not null)
|
||||||
|
{
|
||||||
|
source = source.Where(client => client.CurrentAlias.IPAddress == parsedIp);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
query.ClientIP = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return source;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,33 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace WebfrontCore.QueryHelpers.Models;
|
||||||
|
|
||||||
public class BanInfo
|
public class BanInfo
|
||||||
{
|
{
|
||||||
public string OffenderName { get; set; }
|
public string ClientName { get; set; }
|
||||||
public int OffenderId { get; set; }
|
public int ClientId { get; set; }
|
||||||
public string PunisherName { get; set; }
|
public int? IPAddress { get; set; }
|
||||||
public int? PunisherId { get; set; }
|
public long NetworkId { get; set; }
|
||||||
|
public PenaltyInfo AttachedPenalty { get; set; }
|
||||||
|
public IEnumerable<PenaltyInfo> AssociatedPenalties { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PenaltyInfo
|
||||||
|
{
|
||||||
|
public RelatedClientInfo OffenderInfo { get; set; }
|
||||||
|
public RelatedClientInfo PunisherInfo { get; set; }
|
||||||
public string Offense { get; set; }
|
public string Offense { get; set; }
|
||||||
public DateTime? DateTime { get; set; }
|
public DateTime? DateTime { get; set; }
|
||||||
public long? TimeStamp => DateTime.HasValue ? new DateTimeOffset(DateTime.Value, TimeSpan.Zero).ToUnixTimeSeconds() : null;
|
|
||||||
|
public long? TimeStamp =>
|
||||||
|
DateTime.HasValue ? new DateTimeOffset(DateTime.Value, TimeSpan.Zero).ToUnixTimeSeconds() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RelatedClientInfo
|
||||||
|
{
|
||||||
|
public string ClientName { get; set; }
|
||||||
|
public int? ClientId { get; set; }
|
||||||
|
public int? IPAddress { get; set; }
|
||||||
|
public long? NetworkId { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -5,4 +5,7 @@ namespace WebfrontCore.QueryHelpers.Models;
|
|||||||
public class BanInfoRequest : PaginationRequest
|
public class BanInfoRequest : PaginationRequest
|
||||||
{
|
{
|
||||||
public string ClientName { get; set; }
|
public string ClientName { get; set; }
|
||||||
|
public string ClientGuid { get; set; }
|
||||||
|
public int? ClientId { get; set; }
|
||||||
|
public string ClientIP { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Microsoft.AspNetCore.Html;
|
||||||
|
|
||||||
namespace WebfrontCore.ViewModels;
|
namespace WebfrontCore.ViewModels;
|
||||||
|
|
||||||
@ -19,7 +20,7 @@ public class TableInfo
|
|||||||
|
|
||||||
public class RowDefinition
|
public class RowDefinition
|
||||||
{
|
{
|
||||||
public List<string> Datum { get; } = new();
|
public List<ColumnTypeDefinition> Datum { get; } = new();
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ColumnDefinition
|
public class ColumnDefinition
|
||||||
@ -28,6 +29,23 @@ public class ColumnDefinition
|
|||||||
public string ColumnSpan { get; set; }
|
public string ColumnSpan { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum ColumnType
|
||||||
|
{
|
||||||
|
Text,
|
||||||
|
Link,
|
||||||
|
Icon,
|
||||||
|
Button
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ColumnTypeDefinition
|
||||||
|
{
|
||||||
|
public ColumnType Type { get; set; }
|
||||||
|
public string Value { get; set; }
|
||||||
|
public string Data { get; set; }
|
||||||
|
public IHtmlContent Template { get; set; }
|
||||||
|
public int Id { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public static class TableInfoExtensions
|
public static class TableInfoExtensions
|
||||||
{
|
{
|
||||||
public static TableInfo WithColumns(this TableInfo info, IEnumerable<string> columns)
|
public static TableInfo WithColumns(this TableInfo info, IEnumerable<string> columns)
|
||||||
@ -42,6 +60,16 @@ public static class TableInfoExtensions
|
|||||||
|
|
||||||
public static TableInfo WithRows<T>(this TableInfo info, IEnumerable<T> source,
|
public static TableInfo WithRows<T>(this TableInfo info, IEnumerable<T> source,
|
||||||
Func<T, IEnumerable<string>> selector)
|
Func<T, IEnumerable<string>> selector)
|
||||||
|
{
|
||||||
|
return WithRows(info, source, (outer) => selector(outer).Select(item => new ColumnTypeDefinition
|
||||||
|
{
|
||||||
|
Value = item,
|
||||||
|
Type = ColumnType.Text
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TableInfo WithRows<T>(this TableInfo info, IEnumerable<T> source,
|
||||||
|
Func<T, IEnumerable<ColumnTypeDefinition>> selector)
|
||||||
{
|
{
|
||||||
info.Rows.AddRange(source.Select(row =>
|
info.Rows.AddRange(source.Select(row =>
|
||||||
{
|
{
|
||||||
|
66
WebfrontCore/Views/Admin/BanManagement.cshtml
Normal file
66
WebfrontCore/Views/Admin/BanManagement.cshtml
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
@model IEnumerable<WebfrontCore.QueryHelpers.Models.BanInfo>
|
||||||
|
|
||||||
|
<div class="content mt-0">
|
||||||
|
<h2 class="content-title mt-20 mb-10">@ViewBag.Title</h2>
|
||||||
|
@if (!Model.Any())
|
||||||
|
{
|
||||||
|
<div class="text-muted mb-10">Search for records...</div>
|
||||||
|
}
|
||||||
|
<form method="get" class="mt-10">
|
||||||
|
<div class="d-flex flex-column flex-md-row">
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" class="form-control bg-dark-dm bg-light-ex-lm" id="clientNameInput" name="clientName" value="@ViewBag.ClientName" placeholder="Client Name">
|
||||||
|
<div class="input-group-append">
|
||||||
|
<button class="btn bg-dark-dm bg-light-ex-lm" type="submit">
|
||||||
|
<i class="oi oi-magnifying-glass"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="input-group mr-md-5 ml-md-10 mt-10 mb-5 mt-md-0 mb-md-0">
|
||||||
|
<input type="text" class="form-control bg-dark-dm bg-light-ex-lm" id="clientGuidInput" name="clientGuid" value="@ViewBag.ClientGuid" placeholder="Client GUID">
|
||||||
|
<div class="input-group-append">
|
||||||
|
<button class="btn bg-dark-dm bg-light-ex-lm" type="submit">
|
||||||
|
<i class="oi oi-magnifying-glass"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="input-group mr-md-10 ml-md-5 mb-10 mt-5 mt-md-0 mb-md-0">
|
||||||
|
<input type="text" class="form-control bg-dark-dm bg-light-ex-lm" id="clientIPInput" name="clientIP" value="@ViewBag.ClientIP" placeholder="Client IP">
|
||||||
|
<div class="input-group-append">
|
||||||
|
<button class="btn bg-dark-dm bg-light-ex-lm" type="submit">
|
||||||
|
<i class="oi oi-magnifying-glass"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="number" class="form-control bg-dark-dm bg-light-ex-lm" id="clientIdInput" name="clientId" value="@ViewBag.ClientId" placeholder="Client Id">
|
||||||
|
<div class="input-group-append">
|
||||||
|
<button class="btn bg-dark-dm bg-light-ex-lm" type="submit">
|
||||||
|
<i class="oi oi-magnifying-glass"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div id="ban_entry_list">
|
||||||
|
<partial name="_BanEntries" for="@Model"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (Model.Any())
|
||||||
|
{
|
||||||
|
<div class="w-full text-center">
|
||||||
|
<i id="loaderLoad" class="oi oi-chevron-bottom mt-10 loader-load-more text-primary m-auto" aria-hidden="true"></i>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@section scripts {
|
||||||
|
<script>
|
||||||
|
initLoader('/Admin/BanManagementList', '#ban_entry_list', 10, 10, [{ 'name': 'clientIP', 'value': () => $('#clientIPInput').val() },
|
||||||
|
{ 'name': 'clientGuid', 'value': () => $('#clientGuidInput').val() },
|
||||||
|
{ 'name': 'clientName', 'value': () => $('#clientNameInput').val() },
|
||||||
|
{ 'name': 'clientId', 'value': () => $('#clientIdInput').val() }]);
|
||||||
|
</script>
|
||||||
|
}
|
63
WebfrontCore/Views/Admin/_BanEntries.cshtml
Normal file
63
WebfrontCore/Views/Admin/_BanEntries.cshtml
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
@model IEnumerable<WebfrontCore.QueryHelpers.Models.BanInfo>
|
||||||
|
|
||||||
|
@foreach (var ban in Model)
|
||||||
|
{
|
||||||
|
if (ban.AttachedPenalty is null && !ban.AssociatedPenalties.Any())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="card p-10 m-0 mt-15 mb-15">
|
||||||
|
<div class="d-flex flex-row flex-wrap">
|
||||||
|
<div class="d-flex p-15 mr-md-10 w-full w-md-200 bg-very-dark-dm bg-light-ex-lm rounded">
|
||||||
|
<div class="align-self-center ">
|
||||||
|
<a asp-controller="Client" asp-action="Profile" asp-route-id="@ban.ClientId" class="font-size-18 no-decoration">@ban.ClientName</a>
|
||||||
|
<has-permission entity="ClientGuid" required-permission="Read">
|
||||||
|
<div class="text-muted">@ban.NetworkId.ToString("X")</div>
|
||||||
|
</has-permission>
|
||||||
|
<has-permission entity="ClientIPAddress" required-permission="Read">
|
||||||
|
<div class="text-muted">@ban.IPAddress.ConvertIPtoString()</div>
|
||||||
|
</has-permission>
|
||||||
|
@if (ban.AttachedPenalty is not null)
|
||||||
|
{
|
||||||
|
<br/>
|
||||||
|
<div class="text-muted font-weight-light">@ban.AttachedPenalty.Offense.CapClientName(30)</div>
|
||||||
|
<div class="text-danger font-weight-light">@ban.AttachedPenalty.DateTime.ToStandardFormat()</div>
|
||||||
|
<div class="btn profile-action mt-10 w-100" data-action="unban" data-action-id="@ban.ClientId">Unban</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<br/>
|
||||||
|
<div class="align-self-end text-muted font-weight-light">
|
||||||
|
<span class="oi oi-warning font-size-12"></span>
|
||||||
|
<span>Link-Only Ban</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@foreach (var associatedEntity in ban.AssociatedPenalties)
|
||||||
|
{
|
||||||
|
<div class="d-flex flex-wrap flex-column w-full w-md-200 p-10">
|
||||||
|
<div data-toggle="tooltip" data-title="Linked via shared IP" class="d-flex">
|
||||||
|
<i class="oi oi-link-intact align-self-center"></i>
|
||||||
|
<div class="text-truncate ml-5 mr-5">
|
||||||
|
<a asp-controller="Client" asp-action="Profile" asp-route-id="@associatedEntity.OffenderInfo.ClientId" class="font-size-18 no-decoration">@associatedEntity.OffenderInfo.ClientName</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<has-permission entity="ClientGuid" required-permission="Read">
|
||||||
|
<div class="text-muted">@associatedEntity.OffenderInfo.NetworkId?.ToString("X")</div>
|
||||||
|
</has-permission>
|
||||||
|
<has-permission entity="ClientIPAddress" required-permission="Read">
|
||||||
|
<div class="text-muted">@associatedEntity.OffenderInfo.IPAddress.ConvertIPtoString()</div>
|
||||||
|
</has-permission>
|
||||||
|
<br/>
|
||||||
|
<div class="text-muted font-weight-light">@associatedEntity.Offense.CapClientName(30)</div>
|
||||||
|
<div class="text-danger font-weight-light">@associatedEntity.DateTime.ToStandardFormat()</div>
|
||||||
|
<div class="btn profile-action mt-10 w-100" data-action="unban" data-action-id="@associatedEntity.OffenderInfo.ClientId">Unban</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
@model WebfrontCore.ViewModels.TableInfo
|
@using WebfrontCore.ViewModels
|
||||||
|
@model WebfrontCore.ViewModels.TableInfo
|
||||||
@{
|
@{
|
||||||
Layout = null;
|
Layout = null;
|
||||||
}
|
}
|
||||||
@ -17,7 +18,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@{ var start = 0;}
|
@{ var start = 0; }
|
||||||
@if (!Model.Rows.Any())
|
@if (!Model.Rows.Any())
|
||||||
{
|
{
|
||||||
<!-- desktop -->
|
<!-- desktop -->
|
||||||
@ -35,15 +36,40 @@
|
|||||||
@foreach (var row in Model.Rows)
|
@foreach (var row in Model.Rows)
|
||||||
{
|
{
|
||||||
<!-- desktop -->
|
<!-- desktop -->
|
||||||
<tr class="bg-dark-dm bg-light-lm @(Model.InitialRowCount > 0 && start >= Model.InitialRowCount ? "d-none hidden-row-lg": "d-none d-lg-table-row")">
|
<tr class="bg-dark-dm bg-light-lm @(Model.InitialRowCount > 0 && start >= Model.InitialRowCount ? "d-none hidden-row-lg" : "d-none d-lg-table-row")">
|
||||||
@for (var i = 0; i < Model.Columns.Count; i++)
|
@for (var i = 0; i < Model.Columns.Count; i++)
|
||||||
{
|
{
|
||||||
<td>@row.Datum[i]</td>
|
var data = row.Datum[i];
|
||||||
|
<td>
|
||||||
|
@if (data.Template is null)
|
||||||
|
{
|
||||||
|
if (data.Type == ColumnType.Text)
|
||||||
|
{
|
||||||
|
<span>@data.Value</span>
|
||||||
|
}
|
||||||
|
if (data.Type == ColumnType.Link)
|
||||||
|
{
|
||||||
|
<a href="@data.Data" class="no-decoration">@data.Value</a>
|
||||||
|
}
|
||||||
|
if (data.Type == ColumnType.Icon)
|
||||||
|
{
|
||||||
|
<span class="oi @data.Value profile-action" data-action="@data.Data" data-action-id="@data.Id"></span>
|
||||||
|
}
|
||||||
|
if (data.Type == ColumnType.Button)
|
||||||
|
{
|
||||||
|
<div class="btn profile-action" data-action="@data.Data" data-action-id="@data.Id">@data.Value</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
@data.Template
|
||||||
|
}
|
||||||
|
</td>
|
||||||
}
|
}
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<!-- mobile -->
|
<!-- mobile -->
|
||||||
<tr class="@(Model.InitialRowCount > 0 && start >= Model.InitialRowCount ? "d-none hidden-row": "d-flex d-table-row d-lg-none")">
|
<tr class="@(Model.InitialRowCount > 0 && start >= Model.InitialRowCount ? "d-none hidden-row" : "d-flex d-table-row d-lg-none")">
|
||||||
<td class="bg-primary text-light text-right w-125">
|
<td class="bg-primary text-light text-right w-125">
|
||||||
@foreach (var column in Model.Columns)
|
@foreach (var column in Model.Columns)
|
||||||
{
|
{
|
||||||
@ -53,7 +79,21 @@
|
|||||||
<td class="bg-dark-dm bg-light-lm flex-fill w-200">
|
<td class="bg-dark-dm bg-light-lm flex-fill w-200">
|
||||||
@for (var i = 0; i < Model.Columns.Count; i++)
|
@for (var i = 0; i < Model.Columns.Count; i++)
|
||||||
{
|
{
|
||||||
<div class="mt-5 mb-5 text-truncate" style="min-width:0">@row.Datum[i]</div>
|
var data = row.Datum[i];
|
||||||
|
<div class="mt-5 mb-5 text-truncate" style="min-width:0">
|
||||||
|
@if (data.Type == ColumnType.Text)
|
||||||
|
{
|
||||||
|
<span>@data.Value</span>
|
||||||
|
}
|
||||||
|
@if (data.Type == ColumnType.Link)
|
||||||
|
{
|
||||||
|
<a href="@data.Data">@data.Value</a>
|
||||||
|
}
|
||||||
|
@if (data.Type == ColumnType.Icon)
|
||||||
|
{
|
||||||
|
<span class="oi @data.Value profile-action" data-action="@data.Data" data-action-id="@data.Id"></span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -115,30 +115,35 @@
|
|||||||
<span class="name">@ViewBag.Localization["WEBFRONT_NAV_CONSOLE"]</span>
|
<span class="name">@ViewBag.Localization["WEBFRONT_NAV_CONSOLE"]</span>
|
||||||
</a>
|
</a>
|
||||||
</has-permission>
|
</has-permission>
|
||||||
@if (ViewBag.User.Level >= EFClient.Permission.Owner)
|
<has-permission entity="Penalty" required-permission="Read"></has-permission>
|
||||||
{
|
<a asp-controller="Admin" asp-action="BanManagement" class="sidebar-link">
|
||||||
<a asp-controller="Configuration" asp-action="Edit" class="sidebar-link">
|
<i class="oi oi-ban mr-5"></i>
|
||||||
<i class="oi oi-cog mr-5"></i>
|
<span class="name">Ban Management</span>
|
||||||
<span class="name">Configuration</span>
|
|
||||||
</a>
|
|
||||||
}
|
|
||||||
<has-permission entity="AuditPage" required-permission="Read">
|
|
||||||
<a asp-controller="Admin" asp-action="AuditLog" class="sidebar-link">
|
|
||||||
<i class="oi oi-book mr-5"></i>
|
|
||||||
<span class="name">@ViewBag.Localization["WEBFRONT_NAV_AUDIT_LOG"]</span>
|
|
||||||
</a>
|
|
||||||
</has-permission>
|
|
||||||
<has-permission entity="RecentPlayersPage" required-permission="Read">
|
|
||||||
<a class="sidebar-link profile-action" href="#actionModal" data-action="RecentClients" title="@ViewBag.Localization["WEBFRONT_ACTION_RECENT_CLIENTS"]">
|
|
||||||
<i class="oi oi-timer mr-5"></i>
|
|
||||||
<span class="name">@ViewBag.Localization["WEBFRONT_ACTION_RECENT_CLIENTS"]</span>
|
|
||||||
</a>
|
|
||||||
</has-permission>
|
|
||||||
<a class="sidebar-link profile-action" href="#actionModal" data-action="GenerateLoginToken" data-response-duration="30000" title="@ViewBag.Localization["WEBFRONT_ACTION_TOKEN"]">
|
|
||||||
<i class="oi oi-key mr-5"></i>
|
|
||||||
<span class="name">@ViewBag.Localization["WEBFRONT_ACTION_TOKEN"]</span>
|
|
||||||
</a>
|
</a>
|
||||||
</has-permission>
|
</has-permission>
|
||||||
|
@if (ViewBag.User.Level >= EFClient.Permission.Owner)
|
||||||
|
{
|
||||||
|
<a asp-controller="Configuration" asp-action="Edit" class="sidebar-link">
|
||||||
|
<i class="oi oi-cog mr-5"></i>
|
||||||
|
<span class="name">Configuration</span>
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
<has-permission entity="AuditPage" required-permission="Read">
|
||||||
|
<a asp-controller="Admin" asp-action="AuditLog" class="sidebar-link">
|
||||||
|
<i class="oi oi-book mr-5"></i>
|
||||||
|
<span class="name">@ViewBag.Localization["WEBFRONT_NAV_AUDIT_LOG"]</span>
|
||||||
|
</a>
|
||||||
|
</has-permission>
|
||||||
|
<has-permission entity="RecentPlayersPage" required-permission="Read">
|
||||||
|
<a class="sidebar-link profile-action" href="#actionModal" data-action="RecentClients" title="@ViewBag.Localization["WEBFRONT_ACTION_RECENT_CLIENTS"]">
|
||||||
|
<i class="oi oi-timer mr-5"></i>
|
||||||
|
<span class="name">@ViewBag.Localization["WEBFRONT_ACTION_RECENT_CLIENTS"]</span>
|
||||||
|
</a>
|
||||||
|
</has-permission>
|
||||||
|
<a class="sidebar-link profile-action" href="#actionModal" data-action="GenerateLoginToken" data-response-duration="30000" title="@ViewBag.Localization["WEBFRONT_ACTION_TOKEN"]">
|
||||||
|
<i class="oi oi-key mr-5"></i>
|
||||||
|
<span class="name">@ViewBag.Localization["WEBFRONT_ACTION_TOKEN"]</span>
|
||||||
|
</a>
|
||||||
@if (ViewBag.Authorized)
|
@if (ViewBag.Authorized)
|
||||||
{
|
{
|
||||||
<a asp-controller="Account" asp-action="Logout" class="sidebar-link">
|
<a asp-controller="Account" asp-action="Logout" class="sidebar-link">
|
||||||
|
Loading…
Reference in New Issue
Block a user