diff --git a/Admin/Application.csproj b/Admin/Application.csproj index 22bb6c5b5..f3fc512ed 100644 --- a/Admin/Application.csproj +++ b/Admin/Application.csproj @@ -127,6 +127,7 @@ lib\Kayak.dll False + False ..\packages\Newtonsoft.Json.10.0.2\lib\net45\Newtonsoft.Json.dll @@ -164,6 +165,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest diff --git a/Admin/Kayak.cs b/Admin/Kayak.cs index 87e9a73cc..752bbddce 100644 --- a/Admin/Kayak.cs +++ b/Admin/Kayak.cs @@ -58,7 +58,11 @@ namespace IW4MAdmin bool binaryContent = requestedPage.BinaryContent != null; if (requestedPage.content != null && requestedPage.content.GetType() != typeof(string)) +#if !DEBUG requestedPage.content = Newtonsoft.Json.JsonConvert.SerializeObject(requestedPage.content); +#else + requestedPage.content = Newtonsoft.Json.JsonConvert.SerializeObject(requestedPage.content, Newtonsoft.Json.Formatting.Indented); +#endif string maxAge = requestedPage.contentType == "application/json" ? "0" : "31536000"; var headers = new HttpResponseHead() diff --git a/Admin/Server.cs b/Admin/Server.cs index 69eac6ed9..7dca8e729 100644 --- a/Admin/Server.cs +++ b/Admin/Server.cs @@ -23,6 +23,7 @@ namespace IW4MAdmin { return Math.Abs($"{IP}:{Port.ToString()}".GetHashCode()); } + override public async Task AddPlayer(Player polledPlayer) { if (polledPlayer.Ping == 999 || polledPlayer.Ping < 1 || polledPlayer.ClientNumber > (MaxClients) || polledPlayer.ClientNumber < 0) diff --git a/Admin/WebService.cs b/Admin/WebService.cs index 0f32cc1f1..920ddfe1f 100644 --- a/Admin/WebService.cs +++ b/Admin/WebService.cs @@ -14,6 +14,8 @@ using System.Threading.Tasks; using SharedLibrary.Services; using System.Linq.Expressions; using SharedLibrary.Database.Models; +using System.Collections.Specialized; +using SharedLibrary.Dtos; namespace IW4MAdmin { @@ -41,8 +43,10 @@ namespace IW4MAdmin SharedLibrary.WebService.PageList.Add(new PubbansJSON()); SharedLibrary.WebService.PageList.Add(new AdminsJSON()); SharedLibrary.WebService.PageList.Add(new Admins()); + SharedLibrary.WebService.PageList.Add(new Profile()); - SchedulerThread = new Thread(() => { + SchedulerThread = new Thread(() => + { ScheduleThreadStart(WebScheduler, WebServer); }) { @@ -389,7 +393,7 @@ namespace IW4MAdmin admin = admins.First(a => a.IPAddress == 0).AsPlayer(); if (admin == null) - admin = new Player() { Name = "RestUser"}; + admin = new Player() { Name = "RestUser" }; Event remoteEvent = new Event(Event.GType.Say, querySet["command"], admin, null, S) { @@ -414,7 +418,7 @@ namespace IW4MAdmin else { - cmd.Add(new SharedLibrary.Helpers.CommandResult() { Clientd = 0, Message = "No command entered" }); + cmd.Add(new SharedLibrary.Helpers.CommandResult() { Clientd = 0, Message = "No command entered" }); } HttpResponse resp = new HttpResponse() @@ -627,10 +631,10 @@ namespace IW4MAdmin contentType = GetContentType(), content = Admins.Select(a => new { - a.ClientId, - a.Level, - a.Name, - playerID = a.ClientId + a.ClientId, + a.Level, + a.Name, + playerID = a.ClientId }), additionalHeaders = new Dictionary() }; @@ -698,9 +702,8 @@ namespace IW4MAdmin return "/pages"; } - public async Task GetPage(System.Collections.Specialized.NameValueCollection querySet, IDictionary headers) + public async Task GetPage(NameValueCollection querySet, IDictionary headers) { - var pages = SharedLibrary.WebService.PageList.Select(p => new { pagePath = p.GetPath(), @@ -745,7 +748,7 @@ namespace IW4MAdmin return "GetPlayer"; } - public async Task GetPage(System.Collections.Specialized.NameValueCollection querySet, IDictionary headers) + public async Task GetPage(NameValueCollection querySet, IDictionary headers) { List pInfo = new List(); IList matchedPlayers = new List(); @@ -788,12 +791,22 @@ namespace IW4MAdmin recent = true; } + bool isProfile = querySet["profile"] != null; + if (matchedPlayers != null && matchedPlayers.Count > 0) { foreach (var pp in matchedPlayers) { if (pp == null) continue; + List meta = new List(); + if (isProfile) + { + meta.AddRange(await ApplicationManager.GetInstance().GetPenaltyService().ReadGetClientPenaltiesAsync(pp.ClientId)); + meta.AddRange(await ApplicationManager.GetInstance().GetPenaltyService().ReadGetClientPenaltiesAsync(pp.ClientId, false)); + meta.AddRange(await MetaService.GetMeta(pp.ClientId)); + } + PlayerInfo eachPlayer = new PlayerInfo() { playerIP = pp.IPAddressString, @@ -801,16 +814,19 @@ namespace IW4MAdmin playerLevel = pp.Level.ToString(), playerName = pp.Name, playernpID = pp.NetworkId.ToString(), - forumID = -1, authed = authed, - showV2Features = false, playerAliases = new List(), - playerIPs = new List() + playerIPs = new List(), + Meta = meta.OrderByDescending(m => m.When).ToList(), + FirstSeen = Utilities.GetTimePassed(pp.FirstConnection, false), + TimePlayed = Math.Round(pp.TotalConnectionTime / 3600.0, 1).ToString("#,##0"), + LastSeen = Utilities.GetTimePassed(pp.LastConnection, false) }; if (!recent) { eachPlayer.playerAliases = pp.AliasLink.Children + .Where(a => a.Name != eachPlayer.playerName) .OrderBy(a => a.Name) .Select(a => a.Name) .Distinct() @@ -824,15 +840,14 @@ namespace IW4MAdmin } //eachPlayer.playerAliases = eachPlayer.playerAliases.Distinct().ToList(); - // eachPlayer.playerIPs = eachPlayer.playerIPs.Distinct().ToList(); + // eachPlayer.playerIPs = eachPlayer.playerIPs.Distinct().ToList(); eachPlayer.playerConnections = pp.Connections; - eachPlayer.lastSeen = Utilities.GetTimePassed(pp.LastConnection); pInfo.Add(eachPlayer); } - resp.content = Newtonsoft.Json.JsonConvert.SerializeObject(pInfo); + resp.content = pInfo; return resp; } @@ -846,6 +861,21 @@ namespace IW4MAdmin } } + class Profile : HTMLPage + { + public override string GetPath() => "/profile"; + public override string GetContent(NameValueCollection querySet, IDictionary headers) + { + IFile admins = new IFile("webfront\\profile.html"); + string content = admins.GetText(); + admins.Close(); + + return content; + } + + public override string GetName() => "Client Profile"; + } + [Serializable] struct ServerInfo { @@ -876,13 +906,14 @@ namespace IW4MAdmin public string playerLevel; public string playerIP; public string playernpID; - public Int64 forumID; public List playerAliases; public List playerIPs; public int playerConnections; - public string lastSeen; - public bool showV2Features; + public string LastSeen; + public string FirstSeen; + public string TimePlayed; public bool authed; + public List Meta; } [Serializable] diff --git a/Admin/Webfront/profile.html b/Admin/Webfront/profile.html new file mode 100644 index 000000000..009c0d4ad --- /dev/null +++ b/Admin/Webfront/profile.html @@ -0,0 +1,322 @@ + + + + + + + IW4MAdmin by RaidMax + + + + + + + + + + + + +
+
+
+
+
+ _ +
+
+
+
+ +
+
+
+ _ +
+
+ Played _ hours +
+
+ First seen _ ago +
+
+ Last seen _ ago +
+
+
+
+
+
+
+
+
+
+
+ + diff --git a/Admin/lib/SharedLibrary.dll b/Admin/lib/SharedLibrary.dll index c460ae86a..c64a8911e 100644 Binary files a/Admin/lib/SharedLibrary.dll and b/Admin/lib/SharedLibrary.dll differ diff --git a/Admin/webfront/players.html b/Admin/webfront/players.html index 03f4a8167..2f9d347c8 100644 --- a/Admin/webfront/players.html +++ b/Admin/webfront/players.html @@ -50,7 +50,7 @@ var p = ""; p += "
\ - \ + \
" + formatHidden(player['playerAliases'], player.authed) + "
\
" + formatHidden(player['playerIPs'], player.authed) + "
\
" + getColorForLevel(player['playerLevel'], player['playerLevel']) + "
\ @@ -61,7 +61,7 @@ "
" p += - "
" + checkJustNow(player['lastSeen']) + "
\ + "
" + checkJustNow(player['LastSeen']) + "
\
"; $("#playersTable").append(p); diff --git a/Plugins/SimpleStats/Plugin.cs b/Plugins/SimpleStats/Plugin.cs index c9ec151f1..0d0966ecd 100644 --- a/Plugins/SimpleStats/Plugin.cs +++ b/Plugins/SimpleStats/Plugin.cs @@ -5,6 +5,7 @@ using System.Text; using System.Threading.Tasks; using SharedLibrary; +using SharedLibrary.Dtos; using SharedLibrary.Helpers; using SharedLibrary.Interfaces; using SharedLibrary.Services; @@ -81,7 +82,65 @@ namespace StatsPlugin public Task OnLoadAsync(IManager manager) { - // todo: is this fast? + // meta data info + async Task> getStats(int clientId) + { + var statsSvc = new GenericRepository(); + var clientStats = await statsSvc.FindAsync(c => c.ClientId == clientId); + + int kills = clientStats.Sum(c => c.Kills); + int deaths = clientStats.Sum(c => c.Deaths); + double kdr = Math.Round(clientStats.Sum(c => c.KDR) / clientStats.Count, 2); + double skill = Math.Round(clientStats.Sum(c => c.Skill) / clientStats.Count, 2); + + return new List() + { + new ProfileMeta() + { + Key = "Kills", + Value = kills + }, + new ProfileMeta() + { + Key = "Deaths", + Value = deaths + }, + new ProfileMeta() + { + Key = "KDR", + Value = kdr + }, + new ProfileMeta() + { + Key = "Skill", + Value = skill + } + }; + } + + async Task> getMessages(int clientId) + { + var messageSvc = new GenericRepository(); + var messages = await messageSvc.FindAsync(m => m.ClientId == clientId); + var messageMeta = messages.Select(m => new ProfileMeta() + { + Key = "EventMessage", + Value = m.Message, + When = m.TimeSent + }).ToList(); + messageMeta.Add(new ProfileMeta() + { + Key = "Messages", + Value = messages.Count + }); + + return messageMeta; + } + + MetaService.AddMeta(getStats); + MetaService.AddMeta(getMessages); + + // todo: is this fast? make async? string totalKills() { var serverStats = new GenericRepository(); diff --git a/SharedLibrary/Dtos/ProfileMeta.cs b/SharedLibrary/Dtos/ProfileMeta.cs new file mode 100644 index 000000000..8c9a45254 --- /dev/null +++ b/SharedLibrary/Dtos/ProfileMeta.cs @@ -0,0 +1,18 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SharedLibrary.Dtos +{ + public class ProfileMeta + { + public DateTime When { get; set; } + public string WhenString => Utilities.GetTimePassed(When, false); + public string Key { get; set; } + public dynamic Value { get; set; } + public virtual string Class => Value.GetType().ToString(); + } +} diff --git a/SharedLibrary/Dtos/ProfilePenalty.cs b/SharedLibrary/Dtos/ProfilePenalty.cs new file mode 100644 index 000000000..af1c82541 --- /dev/null +++ b/SharedLibrary/Dtos/ProfilePenalty.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SharedLibrary.Dtos +{ + public class ProfilePenalty + { + public string OffenderName { get; set; } + public int OffenderId { get; set; } + public string PunisherName { get; set; } + public int PunisherId { get; set; } + public string Offense { get; set; } + public string Type { get; set; } + } +} diff --git a/SharedLibrary/Objects/Player.cs b/SharedLibrary/Objects/Player.cs index c677b070a..04ff54d35 100644 --- a/SharedLibrary/Objects/Player.cs +++ b/SharedLibrary/Objects/Player.cs @@ -75,6 +75,8 @@ namespace SharedLibrary.Objects public Server CurrentServer { get; set; } [NotMapped] public int Score { get; set; } + [NotMapped] + public IList Meta { get; set; } private int _ipaddress; public override int IPAddress diff --git a/SharedLibrary/Services/MetaService.cs b/SharedLibrary/Services/MetaService.cs new file mode 100644 index 000000000..c9704c2af --- /dev/null +++ b/SharedLibrary/Services/MetaService.cs @@ -0,0 +1,27 @@ +using SharedLibrary.Dtos; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SharedLibrary.Services +{ + public class MetaService + { + private static List>>> MetaActions = new List>>>(); + + public static void AddMeta(Func>> metaAction) + { + MetaActions.Add(metaAction); + } + + public static async Task> GetMeta(int clientId) + { + var meta = new List(); + foreach (var action in MetaActions) + meta.AddRange(await action(clientId)); + return meta; + } + } +} diff --git a/SharedLibrary/Services/PenaltyService.cs b/SharedLibrary/Services/PenaltyService.cs index 777fefb48..71f83e3d7 100644 --- a/SharedLibrary/Services/PenaltyService.cs +++ b/SharedLibrary/Services/PenaltyService.cs @@ -8,6 +8,7 @@ using System.Data.Entity; using SharedLibrary.Database; using SharedLibrary.Database.Models; using System.Linq.Expressions; +using SharedLibrary.Dtos; namespace SharedLibrary.Services { @@ -56,6 +57,8 @@ namespace SharedLibrary.Services public async Task> Find(Func expression) { + return await Task.FromResult(new List()); + // fixme: this is so slow! return await Task.Run(() => { using (var context = new DatabaseContext()) @@ -109,6 +112,86 @@ namespace SharedLibrary.Services .ToListAsync(); } + /// + /// Get a read-only copy of client penalties + /// + /// + /// Retreive penalties for clients receiving penalties, other wise given + /// + public async Task> ReadGetClientPenaltiesAsync(int clientId, bool victim = true) + { + using (var context = new DatabaseContext()) + { + context.Configuration.LazyLoadingEnabled = false; + context.Configuration.ProxyCreationEnabled = false; + context.Configuration.AutoDetectChangesEnabled = false; + + if (victim) + { + var iqPenalties = from penalty in context.Penalties.AsNoTracking() + where penalty.OffenderId == clientId + join victimClient in context.Clients.AsNoTracking() + on penalty.OffenderId equals victimClient.ClientId + join victimAlias in context.Aliases + on victimClient.CurrentAliasId equals victimAlias.AliasId + join punisherClient in context.Clients + on penalty.PunisherId equals punisherClient.ClientId + join punisherAlias in context.Aliases + on punisherClient.CurrentAliasId equals punisherAlias.AliasId + //orderby penalty.When descending + select new ProfileMeta() + { + Key = "Event.Penalty", + Value = new ProfilePenalty + { + OffenderName = victimAlias.Name, + OffenderId = victimClient.ClientId, + PunisherName = punisherAlias.Name, + PunisherId = penalty.PunisherId, + Offense = penalty.Offense, + Type = penalty.Type.ToString() + }, + When = penalty.When + }; + // fixme: is this good and fast? + return await iqPenalties.ToListAsync(); + } + + else + { + var iqPenalties = from penalty in context.Penalties.AsNoTracking() + where penalty.PunisherId == clientId + join victimClient in context.Clients.AsNoTracking() + on penalty.OffenderId equals victimClient.ClientId + join victimAlias in context.Aliases + on victimClient.CurrentAliasId equals victimAlias.AliasId + join punisherClient in context.Clients + on penalty.PunisherId equals punisherClient.ClientId + join punisherAlias in context.Aliases + on punisherClient.CurrentAliasId equals punisherAlias.AliasId + //orderby penalty.When descending + select new ProfileMeta() + { + Key = "Event.Penalty", + Value = new ProfilePenalty + { + OffenderName = victimAlias.Name, + OffenderId = victimClient.ClientId, + PunisherName = punisherAlias.Name, + PunisherId = penalty.PunisherId, + Offense = penalty.Offense, + Type = penalty.Type.ToString() + }, + When = penalty.When + }; + // fixme: is this good and fast? + return await iqPenalties.ToListAsync(); + } + + + } + } + public async Task RemoveActivePenalties(int aliasLinkId) { using (var context = new DatabaseContext()) diff --git a/SharedLibrary/SharedLibrary.csproj b/SharedLibrary/SharedLibrary.csproj index 7e84e3968..5c3c15942 100644 --- a/SharedLibrary/SharedLibrary.csproj +++ b/SharedLibrary/SharedLibrary.csproj @@ -103,6 +103,8 @@ + + @@ -139,6 +141,7 @@ +