ban/unban buttons added to profile

several css tweaks
changed administratorIPs to PrivilegedClients
added time step references to profile page
This commit is contained in:
RaidMax 2018-03-26 23:54:20 -05:00
parent 979b1f2310
commit a07ce112b0
18 changed files with 349 additions and 57 deletions

View File

@ -68,11 +68,6 @@ namespace StatsPlugin.Cheat
double avg = AverageHitOffset / (float)avgcnt; double avg = AverageHitOffset / (float)avgcnt;
} }
/*r = distance,
x = playerX + r*cos(yaw)*cos(pitch),
y = playerY + r*sin(yaw)*cos(pitch)
z = playerZ + r*sin(360-pitch)*/
#endregion #endregion
#region SESSION_RATIOS #region SESSION_RATIOS

View File

@ -25,7 +25,7 @@ namespace IW4MAdmin
{ {
private List<Server> _servers; private List<Server> _servers;
public List<Server> Servers => _servers.OrderByDescending(s => s.ClientNum).ToList(); public List<Server> Servers => _servers.OrderByDescending(s => s.ClientNum).ToList();
public List<int> AdministratorIPs { get; set; } public Dictionary<int, int> PrivilegedClients { get; set; }
public ILogger Logger { get; private set; } public ILogger Logger { get; private set; }
public bool Running { get; private set; } public bool Running { get; private set; }
public EventHandler<Event> ServerEventOccurred { get; private set; } public EventHandler<Event> ServerEventOccurred { get; private set; }
@ -54,7 +54,7 @@ namespace IW4MAdmin
ClientSvc = new ClientService(); ClientSvc = new ClientService();
AliasSvc = new AliasService(); AliasSvc = new AliasService();
PenaltySvc = new PenaltyService(); PenaltySvc = new PenaltyService();
AdministratorIPs = new List<int>(); PrivilegedClients = new Dictionary<int, int>();
ServerEventOccurred += EventAPI.OnServerEventOccurred; ServerEventOccurred += EventAPI.OnServerEventOccurred;
ConfigHandler = new BaseConfigurationHandler<ApplicationConfiguration>("IW4MAdminSettings"); ConfigHandler = new BaseConfigurationHandler<ApplicationConfiguration>("IW4MAdminSettings");
} }
@ -77,9 +77,21 @@ namespace IW4MAdmin
public async Task Init() public async Task Init()
{ {
#region DATABASE #region DATABASE
AdministratorIPs = (await ClientSvc.Find(c => c.Level > Player.Permission.Trusted)) var ipList = (await ClientSvc.Find(c => c.Level > Player.Permission.Trusted))
.Select(c => c.IPAddress) .Select(c => new { c.IPAddress, c.ClientId });
.ToList();
foreach (var a in ipList)
{
try
{
PrivilegedClients.Add(a.IPAddress, a.ClientId);
}
catch (ArgumentException)
{
continue;
}
}
#endregion #endregion
#region CONFIG #region CONFIG

View File

@ -775,9 +775,13 @@ namespace IW4MAdmin
CurrentMap = Maps.Find(m => m.Name == mapname) ?? new Map() { Alias = mapname, Name = mapname }; CurrentMap = Maps.Find(m => m.Name == mapname) ?? new Map() { Alias = mapname, Name = mapname };
// todo: make this more efficient // todo: make this more efficient
((ApplicationManager)(Manager)).AdministratorIPs = (await new GenericRepository<EFClient>().FindAsync(c => c.Level > Player.Permission.Trusted)) ((ApplicationManager)(Manager)).PrivilegedClients = new Dictionary<int, int>();
.Select(c => c.IPAddress) var ClientSvc = new ClientService();
.ToList(); var ipList = (await ClientSvc.Find(c => c.Level > Player.Permission.Trusted))
.Select(c => new { c.IPAddress, c.ClientId });
foreach (var a in ipList)
((ApplicationManager)(Manager)).PrivilegedClients.Add(a.IPAddress, a.ClientId);
} }
if (E.Type == Event.GType.MapEnd) if (E.Type == Event.GType.MapEnd)
@ -887,11 +891,10 @@ namespace IW4MAdmin
return; return;
} }
} }
#if !DEBUG
else else
await Target.CurrentServer.ExecuteCommandAsync($"clientkick {Target.ClientNumber } \"^7Player Temporarily Banned: ^5{ Reason }\""); await Target.CurrentServer.ExecuteCommandAsync($"clientkick {Target.ClientNumber } \"^7Player Temporarily Banned: ^5{ Reason }\"");
#else
#if DEBUG
await Target.CurrentServer.RemovePlayer(Target.ClientNumber); await Target.CurrentServer.RemovePlayer(Target.ClientNumber);
#endif #endif
@ -933,8 +936,9 @@ namespace IW4MAdmin
{ {
// this is set only because they're still in the server. // this is set only because they're still in the server.
Target.Level = Player.Permission.Banned; Target.Level = Player.Permission.Banned;
#if !DEBUG
await Target.CurrentServer.ExecuteCommandAsync($"clientkick {Target.ClientNumber} \"Player Banned: ^5{Message} ^7(appeal at {Website}) ^7\""); await Target.CurrentServer.ExecuteCommandAsync($"clientkick {Target.ClientNumber} \"Player Banned: ^5{Message} ^7(appeal at {Website}) ^7\"");
#if DEBUG #else
await Target.CurrentServer.RemovePlayer(Target.ClientNumber); await Target.CurrentServer.RemovePlayer(Target.ClientNumber);
#endif #endif
} }

View File

@ -0,0 +1,75 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using SharedLibrary;
using WebfrontCore.ViewModels;
namespace WebfrontCore.Controllers
{
public class ActionController : BaseController
{
public IActionResult BanForm()
{
var info = new ActionInfo()
{
ActionButtonLabel = "Ban",
Name = "Ban",
Inputs = new List<InputInfo>()
{
new InputInfo()
{
Name = "Reason",
Placeholder = ""
}
},
Action = "BanAsync"
};
return View("_ActionForm", info);
}
public async Task<IActionResult> BanAsync(int targetId, string Reason)
{
var server = Manager.GetServers().First();
return await Task.FromResult(RedirectToAction("ExecuteAsync", "Console", new
{
serverId = server.GetHashCode(),
command = $"!ban @{targetId} {Reason}"
}));
}
public IActionResult UnbanForm()
{
var info = new ActionInfo()
{
ActionButtonLabel = "Unban",
Name = "Unban",
Inputs = new List<InputInfo>()
{
new InputInfo()
{
Name = "Reason",
Placeholder = ""
}
},
Action = "UnbanAsync"
};
return View("_ActionForm", info);
}
public async Task<IActionResult> UnbanAsync(int targetId, string Reason)
{
var server = Manager.GetServers().First();
return await Task.FromResult(RedirectToAction("ExecuteAsync", "Console", new
{
serverId = server.GetHashCode(),
command = $"!unban @{targetId} {Reason}"
}));
}
}
}

View File

@ -2,11 +2,8 @@
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Filters;
using SharedLibrary; using SharedLibrary;
using SharedLibrary.Interfaces; using SharedLibrary.Database.Models;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace WebfrontCore.Controllers namespace WebfrontCore.Controllers
{ {
@ -14,19 +11,36 @@ namespace WebfrontCore.Controllers
{ {
protected ApplicationManager Manager; protected ApplicationManager Manager;
protected bool Authorized { get; private set; } protected bool Authorized { get; private set; }
protected EFClient User { get; private set; }
public override void OnActionExecuting(ActionExecutingContext context) public override void OnActionExecuting(ActionExecutingContext context)
{ {
Manager = IW4MAdmin.Program.ServerManager; Manager = IW4MAdmin.Program.ServerManager;
User = new EFClient()
{
ClientId = -1
};
try
{
User.ClientId = Manager.PrivilegedClients[context.HttpContext.Connection.RemoteIpAddress.ToString().ConvertToIP()];
}
catch (KeyNotFoundException)
{
}
Authorized = context.HttpContext.Connection.RemoteIpAddress.ToString() == "127.0.0.1" || Authorized = context.HttpContext.Connection.RemoteIpAddress.ToString() == "127.0.0.1" ||
Manager.AdministratorIPs.Contains(context.HttpContext.Connection.RemoteIpAddress.ToString().ConvertToIP()); User.ClientId >= 0;
ViewBag.Authorized = Authorized; ViewBag.Authorized = Authorized;
ViewBag.Url = Startup.Configuration["Web:Address"]; ViewBag.Url = Startup.Configuration["Web:Address"];
string inviteLink = Manager.GetApplicationSettings().Configuration().DiscordInviteCode; string inviteLink = Manager.GetApplicationSettings().Configuration().DiscordInviteCode;
if (inviteLink != null) if (inviteLink != null)
ViewBag.DiscordLink = inviteLink.Contains("https") ? inviteLink : $"https://discordapp.com/invite/{inviteLink}"; ViewBag.DiscordLink = inviteLink.Contains("https") ? inviteLink : $"https://discordapp.com/invite/{inviteLink}";
else else
ViewBag.DiscorLink = ""; ViewBag.DiscordLink = "";
base.OnActionExecuting(context); base.OnActionExecuting(context);
} }
} }

View File

@ -15,8 +15,18 @@ namespace WebfrontCore.ViewComponents
int ip = HttpContext.Connection.RemoteIpAddress int ip = HttpContext.Connection.RemoteIpAddress
.ToString().ConvertToIP(); .ToString().ConvertToIP();
bool authed = IW4MAdmin.ApplicationManager.GetInstance() bool authed = false;
.AdministratorIPs.Contains(ip);
try
{
var a = IW4MAdmin.ApplicationManager.GetInstance()
.PrivilegedClients[HttpContext.Connection.RemoteIpAddress.ToString().ConvertToIP()];
}
catch (KeyNotFoundException)
{
}
var penalties = await IW4MAdmin.ApplicationManager.GetInstance().GetPenaltyService().GetRecentPenalties(15, offset); var penalties = await IW4MAdmin.ApplicationManager.GetInstance().GetPenaltyService().GetRecentPenalties(15, offset);
var penaltiesDto = penalties.Select(p => new PenaltyInfo() var penaltiesDto = penalties.Select(p => new PenaltyInfo()

View File

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace WebfrontCore.ViewModels
{
public class ActionInfo
{
public string Name { get; set; }
public List<InputInfo> Inputs { get; set; }
public string ActionButtonLabel { get; set; }
public string Action { get; set; }
}
}

View File

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace WebfrontCore.ViewModels
{
public class InputInfo
{
public string Name { get; set; }
public string Placeholder { get; set; }
public string Type { get; set; }
public string Value { get; set; }
}
}

View File

@ -0,0 +1,22 @@
@model WebfrontCore.ViewModels.ActionInfo
@{
Layout = null;
}
<form class="action-form" action="/Action/@Model.Action">
<div class="input-group mb-3">
@foreach (var input in Model.Inputs)
{
<div class="input-group-prepend">
<span class="input-group-text" id="basic-addon-@input.Name">@input.Name</span>
</div>
{
string inputType = input.Type ?? "text";
string value = input.Value ?? "";
<input type="@inputType" name="@input.Name" value="@value" class="form-control" placeholder="@input.Placeholder" aria-label="@input.Name" aria-describedby="basic-addon-@input.Name">
}
}
</div>
<button type="submit" class="btn btn-block btn-primary">@Model.ActionButtonLabel</button>
</form>

View File

@ -26,12 +26,12 @@
if (Model.Level == SharedLibrary.Objects.Player.Permission.User.ToString()) if (Model.Level == SharedLibrary.Objects.Player.Permission.User.ToString())
{ {
<span id="profile_action_ban_btn" class="oi oi-ban text-danger" title="ban" aria-hidden="true"></span> <span id="profile_action_ban_btn" class="profile-action oi oi-ban text-danger" title="Ban Client" data-action="ban" aria-hidden="true"></span>
} }
if (Model.Level == SharedLibrary.Objects.Player.Permission.Banned.ToString()) if (Model.Level == SharedLibrary.Objects.Player.Permission.Banned.ToString())
{ {
<span id="profile_action_unban_btn" class="iconic iconic-carriage-return text-success" title="carriage return" aria-hidden="true"></span> <span id="profile_action_unban_btn" class="profile-action oi oi-action-undo text-success" title="carriage return" data-action="unban" aria-hidden="true"></span>
} }
} }
} }
@ -85,6 +85,10 @@
</div> </div>
</div> </div>
@section targetid {
<input type="hidden" name="targetId" value="@Model.ClientId" />
}
@section scripts { @section scripts {
<script> <script>
const clientInfo = {}; const clientInfo = {};

View File

@ -61,6 +61,30 @@
</div> </div>
</div> </div>
<!-- Action Modal -->
<div class="modal fade" id="actionModal" tabindex="-1" role="dialog" aria-labelledby="actionModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content bg-dark">
<div class="modal-header">
<h5 class="modal-title" id="actionModalLabel">IW4MAdmin</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true" class="text-danger">&times;</span>
</button>
</div>
<div class="modal-body">
</div>
<!--<div class="modal-footer">
<button type="button" class="btn btn-primary">Action</button>
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
</div>-->
</div>
</div>
</div>
<div id="target_id">
@RenderSection("targetid", required: false);
</div>
<!-- End Action Modal -->
<div class="container pt-0 pb-4 pl-4 pr-4"> <div class="container pt-0 pb-4 pl-4 pr-4">
@RenderBody() @RenderBody()
<footer></footer> <footer></footer>

View File

@ -76,3 +76,19 @@ a.link-inverse:hover {
border-left: 0; border-left: 0;
border-right: 0; border-right: 0;
} }
.close {
text-shadow: none !important;
}
.modal-footer {
border-top-color: $orange;
}
.modal-header {
border-bottom-color: $orange;
}
form * {
border-radius: 0 !important;
}

View File

@ -70,10 +70,6 @@
color: white; color: white;
} }
.profile-meta-entry {
}
.penalties-color-kick, .penalties-color-kick,
.penalties-color-unban { .penalties-color-unban {
color: rgba(116, 147, 99, 1); color: rgba(116, 147, 99, 1);
@ -101,22 +97,20 @@
#profile_aliases_btn { #profile_aliases_btn {
position: relative; position: relative;
top: auto; top: -2px;
margin-top: 0.7em;
font-size: 0.5em; font-size: 0.5em;
color: rgb(0, 122, 204); color: rgb(0, 122, 204);
cursor: pointer; cursor: pointer;
} }
#profile_aliases_btn:hover { #profile_aliases_btn:hover {
color: white; opacity: 0.75;
cursor: pointer; cursor: pointer;
} }
#profile_aliases { #profile_aliases {
position: relative; position: relative;
display: none; display: none;
} }
#profile_avatar { #profile_avatar {
@ -141,6 +135,10 @@
border-bottom: 2px rgb(0, 122, 204) solid; border-bottom: 2px rgb(0, 122, 204) solid;
} }
.profile-event-timestep {
font-size: 1.25rem;
}
#profile_level > span.level { #profile_level > span.level {
color: rgba(236, 130, 222, 0.69); color: rgba(236, 130, 222, 0.69);
font-weight: bold; font-weight: bold;
@ -150,10 +148,13 @@
border-bottom: 2px rgb(0, 122, 204) solid; border-bottom: 2px rgb(0, 122, 204) solid;
} }
#profile_name { .profile-action {
font-size: 0.5em;
padding-left: 0.25em;
cursor: pointer;
top: -2px !important;
} }
#profile_info > .text-muted { .profile-action:hover{
opacity: 0.75;
} }

View File

@ -22,7 +22,7 @@ $(document).ready(function () {
count++; count++;
return false; return false;
} }
count++ count++;
} }
}); });
@ -68,12 +68,44 @@ $(document).ready(function () {
}); });
/*
* handle action modal
*/
$('.profile-action').click(function (e) {
const actionType = $(this).data('action');
$.get('/Action/' + actionType + 'Form')
.done(function (response) {
$('#actionModal .modal-body').html(response);
$('#actionModal').modal();
})
.fail(function (jqxhr, textStatus, error) {
$('#actionModal .modal-body').html('<span class="text-danger">' + error + '</span>');
$('#actionModal').modal();
});
});
/*
* handle action submit
*/
$(document).on('submit', '.action-form', function (e) {
e.preventDefault();
$(this).append($('#target_id input'));
const data = $(this).serialize();
$.get($(this).attr('action') + '/?' + data)
.done(function (response) {
$('#actionModal .modal-body').html(response);
$('#actionModal').modal();
})
.fail(function (jqxhr, textStatus, error) {
$('#actionModal .modal-body').html('<span class="text-danger">Error' + error + '</span>');
});
});
}); });
function penaltyToName(penaltyName) { function penaltyToName(penaltyName) {
switch (penaltyName) { switch (penaltyName) {
case "Flag": case "Flag":
return "Flagged" return "Flagged";
case "Warning": case "Warning":
return "Warned"; return "Warned";
case "Report": case "Report":
@ -89,15 +121,56 @@ function penaltyToName(penaltyName) {
} }
} }
function shouldIncludePlural(num) {
return num > 1 ? 's' : '';
}
let mostRecentDate = 0;
let currentStepAmount = 0;
let lastStep = "";
function timeStep(stepDifference) {
let hours = (stepDifference / (1000 * 60 * 60));
let days = (stepDifference / (1000 * 60 * 60 * 24));
let weeks = (stepDifference / (1000 * 60 * 60 * 24 * 7));
if (Math.round(weeks) > Math.round(currentStepAmount / 24 * 7)) {
currentStepAmount = Math.round(weeks);
return `${currentStepAmount} week${shouldIncludePlural(currentStepAmount)} ago`;
}
if (Math.round(days) > Math.round(currentStepAmount / 24)) {
currentStepAmount = Math.round(days);
return `${currentStepAmount} day${shouldIncludePlural(currentStepAmount)} ago`;
}
if (Math.round(hours) > currentStepAmount) {
currentStepAmount = Math.round(hours);
return `${currentStepAmount} hour${shouldIncludePlural(currentStepAmount)} ago`;
}
}
function loadMeta(meta) { function loadMeta(meta) {
let eventString = ''; let eventString = '';
const metaDate = Date.parse(meta.when);
if (mostRecentDate === 0) {
mostRecentDate = metaDate;
}
const step = timeStep(new Date().getTime() - metaDate);
if (step !== lastStep) {
$('#profile_events').append('<span class="p2 text-white profile-event-timestep"><span class="text-primary">&mdash;</span> ' + step + '</span>');
lastStep = step;
}
// it's a penalty // it's a penalty
if (meta.class.includes("Penalty")) { if (meta.class.includes("Penalty")) {
if (meta.value.punisherId !== clientInfo.clientId) { if (meta.value.punisherId !== clientInfo.clientId) {
eventString = `<div><span class="penalties-color-${meta.value.type.toLowerCase()}">${penaltyToName(meta.value.type)}</span> by <span class="text-highlight"> <a class="link-inverse" href="${meta.value.punisherId}">${meta.value.punisherName}</a></span > for <span style="color: white; ">${meta.value.offense}</span> ${meta.whenString} ago </div>`; eventString = `<div><span class="penalties-color-${meta.value.type.toLowerCase()}">${penaltyToName(meta.value.type)}</span> by <span class="text-highlight"> <a class="link-inverse" href="${meta.value.punisherId}">${meta.value.punisherName}</a></span > for <span style="color: white; ">${meta.value.offense}</span></div>`;
} }
else { else {
eventString = `<div><span class="penalties-color-${meta.value.type.toLowerCase()}">${penaltyToName(meta.value.type)} </span> <span class="text-highlight"><a class="link-inverse" href="${meta.value.offenderId}"> ${meta.value.offenderName}</a></span > for <span style="color: white; ">${meta.value.offense}</span> ${meta.whenString} ago </div>`; eventString = `<div><span class="penalties-color-${meta.value.type.toLowerCase()}">${penaltyToName(meta.value.type)} </span> <span class="text-highlight"><a class="link-inverse" href="${meta.value.offenderId}"> ${meta.value.offenderName}</a></span > for <span style="color: white; ">${meta.value.offense}</span></div>`;
} }
} }
// it's a message // it's a message

View File

@ -22,7 +22,7 @@
lineThickness: 0, lineThickness: 0,
tickThickness: 0, tickThickness: 0,
margin: 0, margin: 0,
valueFormatString: " ", valueFormatString: " "
}, },
axisY: { axisY: {
gridThickness: 0, gridThickness: 0,
@ -31,19 +31,19 @@
minimum: 0, minimum: 0,
margin: 0, margin: 0,
valueFormatString: " ", valueFormatString: " ",
labelMaxWidth: 0, labelMaxWidth: 0
}, },
legend: { legend: {
maxWidth: 0, maxWidth: 0,
maxHeight: 0, maxHeight: 0,
dockInsidePlotArea: true, dockInsidePlotArea: true
}, },
data: [{ data: [{
showInLegend: false, showInLegend: false,
type: "splineArea", type: "splineArea",
color: "rgba(0, 122, 204, 0.432)", color: "rgba(0, 122, 204, 0.432)",
markerSize: 0, markerSize: 0,
dataPoints: playerHistory, dataPoints: playerHistory
}] }]
}); });
////////////////////////////////////// //////////////////////////////////////
@ -63,9 +63,9 @@ $(window).resize(function () {
$('.server-history-row').each(function (index) { $('.server-history-row').each(function (index) {
let serverId = $(this).data('serverid'); let serverId = $(this).data('serverid');
charts[serverId].options.width = $('.server-header').first().width(); charts[serverId].options.width = $('.server-header').first().width();
charts[serverId].render() charts[serverId].render();
});
}); });
})
function refreshClientActivity() { function refreshClientActivity() {
$('.server-history-row').each(function (index) { $('.server-history-row').each(function (index) {

View File

@ -6085,3 +6085,15 @@ a.link-inverse:hover {
border-left: 0; border-left: 0;
border-right: 0; } border-right: 0; }
.close {
text-shadow: none !important; }
.modal-footer {
border-top-color: #fd7e14; }
.modal-header {
border-bottom-color: #fd7e14; }
form * {
border-radius: 0 !important; }

File diff suppressed because one or more lines are too long