mirror of
https://github.com/RaidMax/IW4M-Admin.git
synced 2025-06-10 15:20:48 -05:00
improve server clientcount/activity graph on server overview
This commit is contained in:
@ -48,7 +48,7 @@ namespace WebfrontCore.Controllers
|
||||
.CLIENT_STATS_KEY)?.ZScore
|
||||
}).ToList(),
|
||||
ChatHistory = s.ChatHistory.ToList(),
|
||||
PlayerHistory = s.ClientHistory.ToArray(),
|
||||
ClientHistory = s.ClientHistory,
|
||||
IsPasswordProtected = !string.IsNullOrEmpty(s.GamePassword)
|
||||
};
|
||||
return PartialView("_ClientActivity", serverInfo);
|
||||
|
@ -19,16 +19,27 @@ namespace WebfrontCore.ViewComponents
|
||||
{
|
||||
private readonly IServerDataViewer _serverDataViewer;
|
||||
private readonly ApplicationConfiguration _appConfig;
|
||||
private readonly DefaultSettings _defaultSettings;
|
||||
|
||||
public ServerListViewComponent(IServerDataViewer serverDataViewer,
|
||||
ApplicationConfiguration applicationConfiguration)
|
||||
ApplicationConfiguration applicationConfiguration, DefaultSettings defaultSettings)
|
||||
{
|
||||
_serverDataViewer = serverDataViewer;
|
||||
_appConfig = applicationConfiguration;
|
||||
_defaultSettings = defaultSettings;
|
||||
}
|
||||
|
||||
public IViewComponentResult Invoke(Game? game)
|
||||
{
|
||||
if (game.HasValue)
|
||||
{
|
||||
ViewBag.Maps = _defaultSettings.Maps.FirstOrDefault(map => map.Game == game);
|
||||
}
|
||||
else
|
||||
{
|
||||
ViewBag.Maps = _defaultSettings.Maps.SelectMany(maps => maps.Maps).ToList();
|
||||
}
|
||||
|
||||
var servers = Program.Manager.GetServers().Where(server => !game.HasValue || server.GameName == game);
|
||||
|
||||
var serverInfo = new List<ServerInfo>();
|
||||
@ -47,14 +58,14 @@ namespace WebfrontCore.ViewComponents
|
||||
|
||||
var counts = clientHistory.ClientCounts?.AsEnumerable() ?? Enumerable.Empty<ClientCountSnapshot>();
|
||||
|
||||
if (server.ClientHistory.Count > 0)
|
||||
if (server.ClientHistory.ClientCounts.Any())
|
||||
{
|
||||
counts = counts.Union(server.ClientHistory
|
||||
.Select(history => history.ToClientCountSnapshot()).Where(history =>
|
||||
history.Time > (clientHistory.ClientCounts?.LastOrDefault()?.Time ?? DateTime.MinValue)));
|
||||
counts = counts.Union(server.ClientHistory.ClientCounts.Where(history =>
|
||||
history.Time > (clientHistory.ClientCounts?.LastOrDefault()?.Time ?? DateTime.MinValue)))
|
||||
.Where(history => history.Time >= DateTime.UtcNow - _appConfig.MaxClientHistoryTime);
|
||||
}
|
||||
|
||||
serverInfo.Add(new ServerInfo()
|
||||
serverInfo.Add(new ServerInfo
|
||||
{
|
||||
Name = server.Hostname,
|
||||
ID = server.EndPoint,
|
||||
@ -63,7 +74,11 @@ namespace WebfrontCore.ViewComponents
|
||||
ClientCount = server.Clients.Count(client => client != null),
|
||||
MaxClients = server.MaxClients,
|
||||
GameType = server.GametypeName,
|
||||
PlayerHistory = server.ClientHistory.ToArray(),
|
||||
ClientHistory = new ClientHistoryInfo
|
||||
{
|
||||
ServerId = server.EndPoint,
|
||||
ClientCounts = counts.ToList()
|
||||
},
|
||||
Players = server.GetClientsAsList()
|
||||
.Select(p => new PlayerInfo()
|
||||
{
|
||||
@ -77,9 +92,6 @@ namespace WebfrontCore.ViewComponents
|
||||
.CLIENT_STATS_KEY)?.ZScore
|
||||
}).ToList(),
|
||||
ChatHistory = server.ChatHistory.ToList(),
|
||||
ClientCountHistory =
|
||||
counts.Where(history => history.Time >= DateTime.UtcNow - _appConfig.MaxClientHistoryTime)
|
||||
.ToList(),
|
||||
Online = !server.Throttled,
|
||||
IPAddress =
|
||||
$"{(server.ResolvedIpEndPoint.Address.IsInternal() ? Program.Manager.ExternalIPAddress : server.IP)}:{server.Port}",
|
||||
|
@ -1,6 +1,21 @@
|
||||
@model SharedLibraryCore.Dtos.ServerInfo
|
||||
@{
|
||||
Layout = null;
|
||||
|
||||
string GetMapName(string mapCode)
|
||||
{
|
||||
if (ViewBag.Maps?.Count == 0)
|
||||
{
|
||||
return mapCode;
|
||||
}
|
||||
|
||||
return (ViewBag.Maps as List<Map>)?.FirstOrDefault(map => map.Name == mapCode)?.Alias ?? mapCode;
|
||||
}
|
||||
|
||||
foreach (var snapshot in Model.ClientHistory.ClientCounts)
|
||||
{
|
||||
snapshot.MapAlias = GetMapName(snapshot.Map);
|
||||
}
|
||||
}
|
||||
|
||||
<div class="row server-header pt-1 pb-1 bg-primary " id="server_header_@Model.ID">
|
||||
@ -58,8 +73,10 @@
|
||||
</div>
|
||||
|
||||
<div class="row server-history mb-4">
|
||||
<div class="server-history-row" id="server_history_@Model.ID" data-serverid="@Model.ID"
|
||||
data-clienthistory='@Html.Raw(Json.Serialize(Model.PlayerHistory))'
|
||||
data-clienthistory-ex='@Html.Raw(Json.Serialize(Model.ClientCountHistory))'
|
||||
data-online="@Model.Online"></div>
|
||||
<div class="server-history-row" style="position:relative; width: 80vw" id="server_history_@Model.ID" data-serverid="@Model.ID"
|
||||
data-clienthistory='@Html.Raw(Json.Serialize(Model.ClientHistory))'
|
||||
data-clienthistory-ex='@Html.Raw(Json.Serialize(Model.ClientHistory.ClientCounts))'
|
||||
data-online="@Model.Online">
|
||||
<canvas id="server_history_canvas_@Model.ID" height="100"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,64 +1,163 @@
|
||||
function getPlayerHistoryChart(playerHistory, i, width, color, maxClients) {
|
||||
///////////////////////////////////////
|
||||
// thanks to canvasjs :(
|
||||
playerHistory.forEach(function (item, i) {
|
||||
playerHistory[i].x = new Date(playerHistory[i].timeString);
|
||||
playerHistory[i].y = playerHistory[i].clientCount;
|
||||
function createDiagonalPattern(color = 'black') {
|
||||
let shape = document.createElement('canvas');
|
||||
shape.width = 10;
|
||||
shape.height = 10;
|
||||
let c = shape.getContext('2d');
|
||||
c.strokeStyle = color;
|
||||
c.beginPath();
|
||||
c.moveTo(2, 0);
|
||||
c.lineTo(10, 8);
|
||||
c.stroke();
|
||||
c.beginPath();
|
||||
c.moveTo(0, 8);
|
||||
c.lineTo(2, 10);
|
||||
c.stroke();
|
||||
return c.createPattern(shape, 'repeat');
|
||||
}
|
||||
|
||||
function getPlayerHistoryChart(playerHistory, i, width, maxClients) {
|
||||
const primaryColor = $('title').css('background-color');
|
||||
const rgb = primaryColor.match(/\d+/g);
|
||||
const fillColor = `rgba(${rgb[0]}, ${rgb[1]}, ${rgb[2]}, 0.66)`;
|
||||
const offlineFillColor = 'rgba(255, 96, 96, 0.55)';
|
||||
|
||||
const onlineTime = [];
|
||||
const offlineTime = [];
|
||||
const mapChange = [];
|
||||
let lastMap = '';
|
||||
|
||||
playerHistory.forEach((elem, i) => {
|
||||
if (elem.map !== lastMap) {
|
||||
mapChange.push(i);
|
||||
lastMap = elem.map;
|
||||
}
|
||||
|
||||
if (elem.connectionInterrupted) {
|
||||
offlineTime.push({
|
||||
clientCount: maxClients,
|
||||
timeString: elem.timeString
|
||||
});
|
||||
|
||||
onlineTime.push({
|
||||
clientCount: 0,
|
||||
timeString: elem.timeString
|
||||
})
|
||||
} else {
|
||||
offlineTime.push({
|
||||
clientCount: 0,
|
||||
timeString: elem.timeString
|
||||
});
|
||||
|
||||
onlineTime.push(elem)
|
||||
}
|
||||
});
|
||||
|
||||
return new CanvasJS.Chart(`server_history_${i}`, {
|
||||
backgroundColor: '#191919',
|
||||
height: 100,
|
||||
width: width,
|
||||
animationEnabled: true,
|
||||
toolTip: {
|
||||
contentFormatter: function (e) {
|
||||
const date = moment.utc(e.entries[0].dataPoint.x);
|
||||
return date.local().calendar() + " - " + e.entries[0].dataPoint.y + " players";
|
||||
let animationProgress = 0;
|
||||
let initialAnimationComplete = false;
|
||||
const originalLineDraw = Chart.controllers.line.prototype.draw;
|
||||
Chart.helpers.extend(Chart.controllers.line.prototype, {
|
||||
draw: function () {
|
||||
originalLineDraw.apply(this, arguments);
|
||||
|
||||
const chart = this.chart;
|
||||
const ctx = chart.chart.ctx;
|
||||
|
||||
chart.config.data.lineAtIndexes.forEach((elem, index) => {
|
||||
const xScale = chart.scales['x-axis-0'];
|
||||
const yScale = chart.scales['y-axis-0'];
|
||||
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(xScale.getPixelForValue(undefined, elem), yScale.getPixelForValue(playerHistory[elem].clientCount) / (initialAnimationComplete ? 1 : animationProgress));
|
||||
ctx.strokeStyle = 'rgba(255, 255, 255, 0.1)';
|
||||
ctx.lineTo(xScale.getPixelForValue(undefined, elem), yScale.bottom);
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const canvas = document.getElementById(`server_history_canvas_${i}`);
|
||||
canvas.setAttribute('width', width);
|
||||
|
||||
return new Chart(document.getElementById(`server_history_canvas_${i}`), {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: playerHistory.map(history => history.timeString),
|
||||
datasets: [{
|
||||
data: onlineTime.map(history => history.clientCount),
|
||||
backgroundColor: fillColor,
|
||||
borderColor: primaryColor,
|
||||
borderWidth: 2,
|
||||
hoverBorderColor: 'white',
|
||||
hoverBorderWidth: 2
|
||||
},
|
||||
{
|
||||
data: offlineTime.map(history => history.clientCount),
|
||||
backgroundColor: createDiagonalPattern(offlineFillColor),
|
||||
borderColor: offlineFillColor,
|
||||
borderWidth: 2,
|
||||
hoverBorderColor: 'white',
|
||||
hoverBorderWidth: 2
|
||||
}],
|
||||
lineAtIndexes: mapChange,
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
onResize: function(chart, size) {
|
||||
console.log(size);
|
||||
},
|
||||
legend: false,
|
||||
defaultFontFamily: '-apple-system, BlinkMacSystemFont, "Open Sans", "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"',
|
||||
tooltips: {
|
||||
callbacks: {
|
||||
// todo: localization at some point
|
||||
label: context => context.datasetIndex !== 1 ? `${context.value} players on ${playerHistory[context.index].mapAlias}` : context.value === '0' ? '' : 'Server Unreachable!',
|
||||
title: context => context[0].datasetIndex !== 1 ? moment(context[0].label).local().calendar() : ''
|
||||
},
|
||||
mode: 'nearest',
|
||||
intersect: false,
|
||||
animationDuration: 0,
|
||||
cornerRadius: 0,
|
||||
displayColors: false
|
||||
},
|
||||
scales: {
|
||||
xAxes: [{
|
||||
display: false,
|
||||
}],
|
||||
yAxes: [{
|
||||
display: false,
|
||||
gridLines: {
|
||||
display: false
|
||||
},
|
||||
ticks: {
|
||||
max: 1,
|
||||
min: maxClients + 2
|
||||
}
|
||||
}]
|
||||
},
|
||||
hover: {
|
||||
mode: 'nearest',
|
||||
intersect: false
|
||||
},
|
||||
elements: {
|
||||
point: {
|
||||
radius: 0
|
||||
}
|
||||
},
|
||||
animation: {
|
||||
duration: 1000,
|
||||
onProgress: function (context) {
|
||||
animationProgress = context.currentStep / context.numSteps;
|
||||
if (animationProgress >= 1) {
|
||||
initialAnimationComplete = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
axisX: {
|
||||
interval: 1,
|
||||
gridThickness: 0,
|
||||
lineThickness: 0,
|
||||
tickThickness: 0,
|
||||
margin: 0,
|
||||
valueFormatString: " "
|
||||
},
|
||||
axisY: {
|
||||
gridThickness: 0,
|
||||
lineThickness: 0,
|
||||
tickThickness: 0,
|
||||
minimum: 0,
|
||||
maximum: maxClients + 1,
|
||||
margin: 0,
|
||||
valueFormatString: " ",
|
||||
labelMaxWidth: 0
|
||||
},
|
||||
legend: {
|
||||
maxWidth: 0,
|
||||
maxHeight: 0,
|
||||
dockInsidePlotArea: true
|
||||
},
|
||||
data: [{
|
||||
showInLegend: false,
|
||||
type: "splineArea",
|
||||
color: color,
|
||||
markerSize: 0,
|
||||
dataPoints: playerHistory
|
||||
}]
|
||||
});
|
||||
//////////////////////////////////////
|
||||
}
|
||||
var charts = {};
|
||||
|
||||
$(window).resize(function () {
|
||||
$('.server-history-row').each(function (index) {
|
||||
let serverId = $(this).data('serverid');
|
||||
charts[serverId].options.width = $('.server-header').first().width();
|
||||
charts[serverId].render();
|
||||
});
|
||||
});
|
||||
|
||||
function refreshClientActivity() {
|
||||
$('.server-history-row').each(function (index) {
|
||||
@ -88,17 +187,13 @@ $(document).ready(function () {
|
||||
let clientHistory = $(this).data('clienthistory-ex');
|
||||
let serverId = $(this).data('serverid');
|
||||
let maxClients = parseInt($('#server_header_' + serverId + ' .server-maxclients').text());
|
||||
let primaryColor = $('title').css('background-color');
|
||||
let color = $(this).data('online') === 'True' ? primaryColor : '#ff6060';
|
||||
let width = $('.server-header').first().width();
|
||||
let historyChart = getPlayerHistoryChart(clientHistory, serverId, width, color, maxClients);
|
||||
historyChart.render();
|
||||
charts[serverId] = historyChart;
|
||||
getPlayerHistoryChart(clientHistory, serverId, width, maxClients);
|
||||
});
|
||||
|
||||
|
||||
$('.moment-date').each((index, element) => {
|
||||
const title = $(element).attr('title');
|
||||
|
||||
|
||||
if (title !== undefined) {
|
||||
const date = new Date(title);
|
||||
$(element).attr('title', moment.utc(date).calendar());
|
||||
|
Reference in New Issue
Block a user