diff --git a/Application/Server.cs b/Application/Server.cs index 883a37fe9..594ff01be 100644 --- a/Application/Server.cs +++ b/Application/Server.cs @@ -1024,7 +1024,8 @@ namespace IW4MAdmin Punisher = Origin, Active = true, When = DateTime.UtcNow, - Link = Target.AliasLink + Link = Target.AliasLink, + AutomatedOffense = Origin.AdministeredPenalties.FirstOrDefault()?.AutomatedOffense }; await Manager.GetPenaltyService().Create(newPenalty); diff --git a/Plugins/Stats/Helpers/StatManager.cs b/Plugins/Stats/Helpers/StatManager.cs index 90a5c93b6..564fd5322 100644 --- a/Plugins/Stats/Helpers/StatManager.cs +++ b/Plugins/Stats/Helpers/StatManager.cs @@ -14,6 +14,8 @@ using System.Text.RegularExpressions; using IW4MAdmin.Plugins.Stats.Web.Dtos; using SharedLibraryCore.Database; using Microsoft.EntityFrameworkCore; +using SharedLibraryCore.Database.Models; +using SharedLibraryCore.Services; namespace IW4MAdmin.Plugins.Stats.Helpers { @@ -50,64 +52,70 @@ namespace IW4MAdmin.Plugins.Stats.Helpers var thirtyDaysAgo = DateTime.UtcNow.AddMonths(-1); var iqClientIds = (from stat in context.Set() - join client in context.Clients - on stat.ClientId equals client.ClientId #if !DEBUG - where stat.TimePlayed >= 3600 - where client.Level != Player.Permission.Banned - where client.LastConnection >= thirtyDaysAgo - where stat.Performance > 60 + .Where(s => s.TimePlayed > 3600) + .Where(s => s.EloRating > 60.0) #endif - group stat by stat.ClientId into s - orderby s.Average(cs => cs.Performance) descending - select s.First().ClientId) + where stat.Client.Level != Player.Permission.Banned + where stat.Client.LastConnection >= thirtyDaysAgo + + group stat by stat.ClientId into sj + let performance = sj.Sum(s => (s.EloRating + s.Skill) * s.TimePlayed) / sj.Sum(st => st.TimePlayed) + orderby performance + select new + { + // sj.First().Client.CurrentAlias.Name, + sj.First().Client.ClientId, + Skill = sj.Select(s => s.Skill) + }) + /* + join averageStats in context.Set().Include(c => c.Ratings) + on stat.ClientId equals averageStats.ClientId + where averageStats.Ratings.Count > 0 + group new { stat, averageStats } by averageStats.ClientId into avg + orderby avg.Select(c => c.averageStats.Ratings.OrderByDescending(r => r.RatingId).First().Performance).First() + select new + { + avg.First().stat.Client.CurrentAlias.Name,//sj.Select(c => c.Client.CurrentAlias.Name), + avg.First().stat.ClientId,//sj.First().ClientId, + avg.First().stat.Kills,//Kills = sj.Select(s => s.Kills), + avg.First().stat.Deaths,//Deaths = sj.Select(s => s.Deaths), + avg.First().stat.Performance,//Performance = sj.Select(c => new { c.Performance, c.TimePlayed }), + // KDR = stat.Kills / stat.Deaths,//KDR = sj.Select(c => new { KDR = c.Kills / (double)c.Deaths, c.TimePlayed }), + //TotalPlayTime = sj.Select(c => c.TimePlayed), + avg.First().stat.Client.LastConnection,//sj.First().Client.LastConnection, + avg.First().stat.Client.TotalConnectionTime,//sj.First().Client.TotalConnectionTime, + avg.First().stat.TimePlayed, + RatingId = 0 + // todo: eventually replace this in favor of joining + //AverageHistory = context.Set().SingleOrDefault(r => r.ClientId == stat.ClientId) + })*/ .Skip(start) .Take(count); - var clientIds = await iqClientIds.ToListAsync(); + var stats = await iqClientIds.ToListAsync(); - var iqStats = (from stat in context.Set() - join client in context.Clients - on stat.ClientId equals client.ClientId - where clientIds.Contains(client.ClientId) - select new + var groupedSelection = stats.GroupBy(c => c.ClientId).Select(s => + new TopStatsInfo() + { + /* Name = s.First().Name, + // weighted based on time played + Performance = s.OrderByDescending(r => r.RatingId).First().Performance, + // ditto + KDR = s.First().Deaths == 0 ? s.First().Kills : (double)s.First().Kills / s.First().Deaths, + ClientId = s.First().ClientId, + Deaths = s.First().Deaths, + Kills = s.First().Kills, + LastSeen = Utilities.GetTimePassed(s.First().LastConnection, false), + TimePlayed = Math.Round(s.First().TotalConnectionTime / 3600.0, 1).ToString("#,##0"), + / PerformanceHistory = s.AverageHistory == null || s.AverageHistory?.Ratings.Count < 2 ? + new List() { - client.CurrentAlias.Name, - client.ClientId, - stat.Kills, - stat.Deaths, - stat.EloRating, - stat.Skill, - stat.TimePlayed, - client.LastConnection, - client.TotalConnectionTime - }); - - var stats = await iqStats.ToListAsync(); - - var groupedSelection = stats.GroupBy(s => s.ClientId).Select(s => - new TopStatsInfo() - { - Name = s.Select(c => c.Name).FirstOrDefault(), - // weighted based on time played - Performance = Math.Round - (s - .Where(c => (c.Skill + c.EloRating) / 2.0 > 0) - .Sum(c => (c.Skill + c.EloRating) / 2.0 * c.TimePlayed) / - s.Where(c => (c.Skill + c.EloRating) / 2.0 > 0) - .Sum(c => c.TimePlayed), 2), - // ditto - KDR = Math.Round(s - .Where(c => c.Deaths > 0) - .Sum(c => ((c.Kills / (double)c.Deaths) * c.TimePlayed) / - s.Where(d => d.Deaths > 0) - .Sum(d => d.TimePlayed)), 2), - ClientId = s.Select(c => c.ClientId).FirstOrDefault(), - Deaths = s.Sum(cs => cs.Deaths), - Kills = s.Sum(cs => cs.Kills), - LastSeen = Utilities.GetTimePassed(s.First().LastConnection, false), - TimePlayed = Math.Round(s.First().TotalConnectionTime / 3600.0, 1).ToString("#,##0"), - }); + s.Performance, + s.Performance + } : + s.AverageHistory.Ratings.Select(r => Math.Round(r.Performance, 1)).ToList()*/ + }); var statList = groupedSelection.OrderByDescending(s => s.Performance).ToList(); @@ -215,8 +223,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers Active = true, HitCount = 0, Location = hl - }) - .ToList() + }).ToList() }; // insert if they've not been added @@ -437,7 +444,16 @@ namespace IW4MAdmin.Plugins.Stats.Helpers case Penalty.PenaltyType.Ban: await attacker.Ban(Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_STATS_CHEAT_DETECTED"], new Player() { - ClientId = 1 + ClientId = 1, + AdministeredPenalties = new List() + { + new EFPenalty() + { + AutomatedOffense = penalty.Type == Cheat.Detection.DetectionType.Bone ? + $"{penalty.Type}-{(int)penalty.Location}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}" : + $"{penalty.Type}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}", + } + } }); break; case Penalty.PenaltyType.Flag: @@ -546,6 +562,15 @@ namespace IW4MAdmin.Plugins.Stats.Helpers attackerStats.Skill = 0.0; } + // update their performance +#if !DEBUG + if ((DateTime.UtcNow - attackerStats.LastStatHistoryUpdate).TotalMinutes >= 10) +#endif + { + await UpdateStatHistory(attacker, attackerStats); + attackerStats.LastStatHistoryUpdate = DateTime.UtcNow; + } + // todo: do we want to save this immediately? var clientStatsSvc = ContextThreads[serverId].ClientStatSvc; clientStatsSvc.Update(attackerStats); @@ -553,6 +578,132 @@ namespace IW4MAdmin.Plugins.Stats.Helpers await clientStatsSvc.SaveChangesAsync(); } + /// + /// Update the invidual and average stat history for a client + /// + /// client to update + /// stats of client that is being updated + /// + private async Task UpdateStatHistory(Player client, EFClientStatistics clientStats) + { + int currentServerTotalPlaytime = clientStats.TimePlayed + (int)(DateTime.UtcNow - client.LastConnection).TotalSeconds; + + using (var ctx = new DatabaseContext()) + { + // select the individual history for current server + var iqIndividualStatHistory = from statHistory in ctx.Set() + where statHistory.ClientId == client.ClientId + where statHistory.ServerId == clientStats.ServerId + select statHistory; + + // select the average history for current client + var iqAverageHistory = from stat in ctx.Set() + where stat.ClientId == client.ClientId + select stat; + + // select all stats for current client + var iqClientStats = from stats in ctx.Set() + where stats.ClientId == client.ClientId + where stats.ServerId != clientStats.ServerId + select new + { + stats.Performance, + stats.TimePlayed + }; + + // get the client ranking for the current server + int individualClientRanking = await ctx.Set() + .Where(c => c.ClientId != client.ClientId) + .Where(c => c.ServerId == clientStats.ServerId) + .Where(c => c.Ratings.OrderByDescending(r => r.RatingId).FirstOrDefault().Performance > clientStats.Performance) + .CountAsync() + 1; + + var currentServerHistory = await iqIndividualStatHistory + .Include(r => r.Ratings) + .FirstOrDefaultAsync() ?? new EFClientStatHistory() + { + Active = true, + ClientId = client.ClientId, + Ratings = new List(), + ServerId = clientStats.ServerId + }; + + var averageHistory = await iqAverageHistory + .Include(r => r.Ratings) + .FirstOrDefaultAsync() ?? new EFClientAverageStatHistory() + { + ClientId = client.ClientId, + Ratings = new List(), + Active = true, + }; + + if (currentServerHistory.StatHistoryId == 0) + { + ctx.Add(currentServerHistory); + } + + else + { + ctx.Update(currentServerHistory); + } + + if (averageHistory.Ratings.Count == 0) + { + ctx.Add(averageHistory); + } + + else + { + ctx.Update(averageHistory); + } + + if (currentServerHistory.Ratings.Count > 30) + { + ctx.Entry(currentServerHistory.Ratings.First()).State = EntityState.Deleted; + currentServerHistory.Ratings.Remove(currentServerHistory.Ratings.First()); + } + + currentServerHistory.Ratings.Add(new EFRating() + { + Performance = clientStats.Performance, + Ranking = individualClientRanking, + Active = true, + ClientId = client.ClientId, + }); + + var clientStatsList = await iqClientStats.ToListAsync(); + clientStatsList.Add(new + { + clientStats.Performance, + TimePlayed = currentServerTotalPlaytime + }); + + // weight the performance based on play time + var performanceAverage = clientStatsList.Sum(p => (p.Performance * p.TimePlayed)) / clientStatsList.Sum(p => p.TimePlayed); + + int overallClientRanking = await ctx.Set() + .Where(c => c.ClientId != client.ClientId) + .Where(c => c.Ratings.OrderByDescending(r => r.RatingId).FirstOrDefault().Performance > performanceAverage) + .CountAsync() + 1; + + if (averageHistory.Ratings.Count > 30) + { + ctx.Entry(averageHistory.Ratings.First()).State = EntityState.Deleted; + averageHistory.Ratings.Remove(averageHistory.Ratings.First()); + } + + averageHistory.Ratings.Add(new EFRating() + { + Performance = performanceAverage, + Ranking = overallClientRanking, + Active = true, + ClientId = client.ClientId, + }); + + await ctx.SaveChangesAsync(); + } + } + /// /// Performs the incrementation of kills and deaths for client statistics /// diff --git a/Plugins/Stats/Models/EFClientAverageStatHistory.cs b/Plugins/Stats/Models/EFClientAverageStatHistory.cs new file mode 100644 index 000000000..dd2f41a71 --- /dev/null +++ b/Plugins/Stats/Models/EFClientAverageStatHistory.cs @@ -0,0 +1,22 @@ +using SharedLibraryCore.Database.Models; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Text; + +namespace IW4MAdmin.Plugins.Stats.Models +{ + public class EFClientAverageStatHistory : SharedEntity + { + [Key] + public int ClientId { get; set; } + [ForeignKey("ClientId")] + public virtual EFClient Client { get; set; } + public virtual ICollection Ratings { get; set; } + [Required] + public int LastRatingId { get; set; } + [ForeignKey("LastRatingId")] + public virtual EFRating LastRating { get; set; } + } +} diff --git a/Plugins/Stats/Models/EFClientStatHistory.cs b/Plugins/Stats/Models/EFClientStatHistory.cs new file mode 100644 index 000000000..c8e5308a0 --- /dev/null +++ b/Plugins/Stats/Models/EFClientStatHistory.cs @@ -0,0 +1,23 @@ +using IW4MAdmin.Plugins.Stats.Models; +using SharedLibraryCore.Database.Models; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Text; + +namespace IW4MAdmin.Plugins.Stats.Models +{ + public class EFClientStatHistory : SharedEntity + { + [Key] + public int StatHistoryId { get; set; } + public int ClientId { get; set; } + [ForeignKey("ClientId")] + public virtual EFClient Client { get; set; } + public int ServerId { get; set; } + [ForeignKey("ServerId")] + public virtual EFServer Server { get; set; } + public virtual ICollection Ratings { get; set; } + } +} diff --git a/Plugins/Stats/Models/EFClientStatistics.cs b/Plugins/Stats/Models/EFClientStatistics.cs index 670de1c6a..3ba6a7076 100644 --- a/Plugins/Stats/Models/EFClientStatistics.cs +++ b/Plugins/Stats/Models/EFClientStatistics.cs @@ -96,5 +96,7 @@ namespace IW4MAdmin.Plugins.Stats.Models private List SessionScores = new List() { 0 }; [NotMapped] public IW4Info.Team Team { get; set; } + [NotMapped] + public DateTime LastStatHistoryUpdate { get; set; } = DateTime.UtcNow; } } diff --git a/Plugins/Stats/Models/EFRating.cs b/Plugins/Stats/Models/EFRating.cs new file mode 100644 index 000000000..be0a4a1ff --- /dev/null +++ b/Plugins/Stats/Models/EFRating.cs @@ -0,0 +1,22 @@ +using SharedLibraryCore.Database.Models; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Text; + +namespace IW4MAdmin.Plugins.Stats.Models +{ + public class EFRating : SharedEntity + { + [Key] + public int RatingId { get; set; } + public int ClientId { get; set; } + [ForeignKey("ClientId")] + public EFClient Client { get; set; } + [Required] + public double Performance { get; set; } + [Required] + public int Ranking { get; set; } + } +} diff --git a/Plugins/Stats/Web/Controllers/StatsController.cs b/Plugins/Stats/Web/Controllers/StatsController.cs index 658f15d27..3d9256a3a 100644 --- a/Plugins/Stats/Web/Controllers/StatsController.cs +++ b/Plugins/Stats/Web/Controllers/StatsController.cs @@ -16,7 +16,7 @@ namespace IW4MAdmin.Plugins.Stats.Web.Controllers ViewBag.Title = Utilities.CurrentLocalization.LocalizationIndex.Set["WEBFRONT_STATS_INDEX_TITLE"]; ViewBag.Description = Utilities.CurrentLocalization.LocalizationIndex.Set["WEBFRONT_STATS_INDEX_DESC"]; - return View("Index", await Plugin.Manager.GetTopStats(0, 15)); + return View("Index", await Plugin.Manager.GetTopStats(0, 25)); } [HttpGet] diff --git a/Plugins/Stats/Web/Dtos/TopStatsInfo.cs b/Plugins/Stats/Web/Dtos/TopStatsInfo.cs index 5c58ec6b2..0ae0ef90d 100644 --- a/Plugins/Stats/Web/Dtos/TopStatsInfo.cs +++ b/Plugins/Stats/Web/Dtos/TopStatsInfo.cs @@ -16,5 +16,6 @@ namespace IW4MAdmin.Plugins.Stats.Web.Dtos public string LastSeen { get; set; } public int Kills { get; set; } public int Deaths { get; set; } + public List PerformanceHistory { get; set; } } } diff --git a/Plugins/Stats/Web/Views/Stats/Index.cshtml b/Plugins/Stats/Web/Views/Stats/Index.cshtml index 2c5b4a1f6..099e0fa4b 100644 --- a/Plugins/Stats/Web/Views/Stats/Index.cshtml +++ b/Plugins/Stats/Web/Views/Stats/Index.cshtml @@ -1,13 +1,14 @@ @model List

@ViewBag.Title

-
+
@await Html.PartialAsync("_List", Model)
@section scripts { - - - - - } \ No newline at end of file + + + + + + } diff --git a/Plugins/Stats/Web/Views/Stats/_List.cshtml b/Plugins/Stats/Web/Views/Stats/_List.cshtml index 086be36bc..c4f83ceed 100644 --- a/Plugins/Stats/Web/Views/Stats/_List.cshtml +++ b/Plugins/Stats/Web/Views/Stats/_List.cshtml @@ -25,26 +25,25 @@ return "0_no-place/menu_div_no_place.png"; } } - - @foreach (var stat in Model) - { - - - - - } -
-
-

#@stat.Ranking — @Html.ActionLink(stat.Name, "ProfileAsync", "Client", new { id = stat.ClientId })

- @stat.Performance @loc["PLUGINS_STATS_COMMANDS_PERFORMANCE"]
- @stat.KDR @loc["PLUGINS_STATS_TEXT_KDR"] - @stat.Kills @loc["PLUGINS_STATS_TEXT_KILLS"] - @stat.Deaths @loc["PLUGINS_STATS_TEXT_DEATHS"]
- @loc["WEBFRONT_PROFILE_PLAYER"] @stat.TimePlayed @loc["GLOBAL_HOURS"]
- @loc["WEBFRONT_PROFILE_LSEEN"] @stat.LastSeen @loc["WEBFRONT_PENALTY_TEMPLATE_AGO"] -
-
-
- -
-
+@foreach (var stat in Model) +{ +
+
+

#@stat.Ranking — @Html.ActionLink(stat.Name, "ProfileAsync", "Client", new { id = stat.ClientId })

+ @stat.Performance @loc["PLUGINS_STATS_COMMANDS_PERFORMANCE"]
+ @stat.KDR @loc["PLUGINS_STATS_TEXT_KDR"] + @stat.Kills @loc["PLUGINS_STATS_TEXT_KILLS"] + @stat.Deaths @loc["PLUGINS_STATS_TEXT_DEATHS"]
+ @loc["WEBFRONT_PROFILE_PLAYER"] @stat.TimePlayed @loc["GLOBAL_HOURS"]
+ @loc["WEBFRONT_PROFILE_LSEEN"] @stat.LastSeen @loc["WEBFRONT_PENALTY_TEMPLATE_AGO"] +
+ +
+ +
+ +
+ +
+
+} \ No newline at end of file diff --git a/SharedLibraryCore/Database/Models/EFPenalty.cs b/SharedLibraryCore/Database/Models/EFPenalty.cs index f8cf4a4ee..fdcb365d5 100644 --- a/SharedLibraryCore/Database/Models/EFPenalty.cs +++ b/SharedLibraryCore/Database/Models/EFPenalty.cs @@ -30,6 +30,7 @@ namespace SharedLibraryCore.Database.Models public DateTime Expires { get; set; } [Required] public string Offense { get; set; } + public string AutomatedOffense { get; set; } public Objects.Penalty.PenaltyType Type { get; set; } } } diff --git a/SharedLibraryCore/Dtos/PenaltyInfo.cs b/SharedLibraryCore/Dtos/PenaltyInfo.cs index 925bd98b7..25d789d03 100644 --- a/SharedLibraryCore/Dtos/PenaltyInfo.cs +++ b/SharedLibraryCore/Dtos/PenaltyInfo.cs @@ -14,6 +14,7 @@ namespace SharedLibraryCore.Dtos public int PunisherId { get; set; } public string PunisherLevel { get; set; } public string Offense { get; set; } + public string AutomatedOffense { get; set; } public string Type { get; set; } public string TimePunished { get; set; } public string TimeRemaining { get; set; } diff --git a/SharedLibraryCore/Migrations/20180529233328_AddAutomatedOffenseAndRatingHistory.Designer.cs b/SharedLibraryCore/Migrations/20180529233328_AddAutomatedOffenseAndRatingHistory.Designer.cs new file mode 100644 index 000000000..0f2b874e6 --- /dev/null +++ b/SharedLibraryCore/Migrations/20180529233328_AddAutomatedOffenseAndRatingHistory.Designer.cs @@ -0,0 +1,536 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.Storage.Internal; +using SharedLibraryCore.Database; +using SharedLibraryCore.Objects; +using System; + +namespace SharedLibraryCore.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20180529233328_AddAutomatedOffenseAndRatingHistory")] + partial class AddAutomatedOffenseAndRatingHistory + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.2-rtm-10011"); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientAverageStatHistory", b => + { + b.Property("ClientId"); + + b.Property("Active"); + + b.HasKey("ClientId"); + + b.ToTable("EFClientAverageStatHistory"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientKill", b => + { + b.Property("KillId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("AttackerId"); + + b.Property("Damage"); + + b.Property("DeathOriginVector3Id"); + + b.Property("DeathType"); + + b.Property("HitLoc"); + + b.Property("KillOriginVector3Id"); + + b.Property("Map"); + + b.Property("ServerId"); + + b.Property("VictimId"); + + b.Property("ViewAnglesVector3Id"); + + b.Property("Weapon"); + + b.Property("When"); + + b.HasKey("KillId"); + + b.HasIndex("AttackerId"); + + b.HasIndex("DeathOriginVector3Id"); + + b.HasIndex("KillOriginVector3Id"); + + b.HasIndex("ServerId"); + + b.HasIndex("VictimId"); + + b.HasIndex("ViewAnglesVector3Id"); + + b.ToTable("EFClientKills"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientMessage", b => + { + b.Property("MessageId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ClientId"); + + b.Property("Message"); + + b.Property("ServerId"); + + b.Property("TimeSent"); + + b.HasKey("MessageId"); + + b.HasIndex("ClientId"); + + b.HasIndex("ServerId"); + + b.ToTable("EFClientMessages"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientStatHistory", b => + { + b.Property("StatHistoryId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ClientId"); + + b.Property("ServerId"); + + b.HasKey("StatHistoryId"); + + b.HasIndex("ClientId"); + + b.HasIndex("ServerId"); + + b.ToTable("EFClientStatHistory"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientStatistics", b => + { + b.Property("ClientId"); + + b.Property("ServerId"); + + b.Property("Active"); + + b.Property("Deaths"); + + b.Property("EloRating"); + + b.Property("Kills"); + + b.Property("MaxStrain"); + + b.Property("RollingWeightedKDR"); + + b.Property("SPM"); + + b.Property("Skill"); + + b.Property("TimePlayed"); + + b.HasKey("ClientId", "ServerId"); + + b.HasIndex("ServerId"); + + b.ToTable("EFClientStatistics"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFHitLocationCount", b => + { + b.Property("HitLocationCountId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ClientId") + .HasColumnName("EFClientStatistics_ClientId"); + + b.Property("HitCount"); + + b.Property("HitOffsetAverage"); + + b.Property("Location"); + + b.Property("MaxAngleDistance"); + + b.Property("ServerId") + .HasColumnName("EFClientStatistics_ServerId"); + + b.HasKey("HitLocationCountId"); + + b.HasIndex("ServerId"); + + b.HasIndex("ClientId", "ServerId"); + + b.ToTable("EFHitLocationCounts"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFRating", b => + { + b.Property("RatingId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ClientId"); + + b.Property("EFClientAverageStatHistoryClientId"); + + b.Property("EFClientStatHistoryStatHistoryId"); + + b.Property("Performance"); + + b.Property("Ranking"); + + b.HasKey("RatingId"); + + b.HasIndex("ClientId"); + + b.HasIndex("EFClientAverageStatHistoryClientId"); + + b.HasIndex("EFClientStatHistoryStatHistoryId"); + + b.ToTable("EFRating"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServer", b => + { + b.Property("ServerId"); + + b.Property("Active"); + + b.Property("Port"); + + b.HasKey("ServerId"); + + b.ToTable("EFServers"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServerStatistics", b => + { + b.Property("StatisticId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ServerId"); + + b.Property("TotalKills"); + + b.Property("TotalPlayTime"); + + b.HasKey("StatisticId"); + + b.HasIndex("ServerId"); + + b.ToTable("EFServerStatistics"); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAlias", b => + { + b.Property("AliasId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("DateAdded"); + + b.Property("IPAddress"); + + b.Property("LinkId"); + + b.Property("Name") + .IsRequired(); + + b.HasKey("AliasId"); + + b.HasIndex("LinkId"); + + b.ToTable("EFAlias"); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAliasLink", b => + { + b.Property("AliasLinkId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.HasKey("AliasLinkId"); + + b.ToTable("EFAliasLinks"); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFClient", b => + { + b.Property("ClientId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("AliasLinkId"); + + b.Property("Connections"); + + b.Property("CurrentAliasId"); + + b.Property("FirstConnection"); + + b.Property("LastConnection"); + + b.Property("Level"); + + b.Property("Masked"); + + b.Property("NetworkId"); + + b.Property("Password"); + + b.Property("PasswordSalt"); + + b.Property("TotalConnectionTime"); + + b.HasKey("ClientId"); + + b.HasIndex("AliasLinkId"); + + b.HasIndex("CurrentAliasId"); + + b.HasIndex("NetworkId") + .IsUnique(); + + b.ToTable("EFClients"); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFPenalty", b => + { + b.Property("PenaltyId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("AutomatedOffense"); + + b.Property("Expires"); + + b.Property("LinkId"); + + b.Property("OffenderId"); + + b.Property("Offense") + .IsRequired(); + + b.Property("PunisherId"); + + b.Property("Type"); + + b.Property("When"); + + b.HasKey("PenaltyId"); + + b.HasIndex("LinkId"); + + b.HasIndex("OffenderId"); + + b.HasIndex("PunisherId"); + + b.ToTable("EFPenalties"); + }); + + modelBuilder.Entity("SharedLibraryCore.Helpers.Vector3", b => + { + b.Property("Vector3Id") + .ValueGeneratedOnAdd(); + + b.Property("X"); + + b.Property("Y"); + + b.Property("Z"); + + b.HasKey("Vector3Id"); + + b.ToTable("Vector3"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientAverageStatHistory", b => + { + b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientKill", b => + { + b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Attacker") + .WithMany() + .HasForeignKey("AttackerId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("SharedLibraryCore.Helpers.Vector3", "DeathOrigin") + .WithMany() + .HasForeignKey("DeathOriginVector3Id"); + + b.HasOne("SharedLibraryCore.Helpers.Vector3", "KillOrigin") + .WithMany() + .HasForeignKey("KillOriginVector3Id"); + + b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Victim") + .WithMany() + .HasForeignKey("VictimId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("SharedLibraryCore.Helpers.Vector3", "ViewAngles") + .WithMany() + .HasForeignKey("ViewAnglesVector3Id"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientMessage", b => + { + b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientStatHistory", b => + { + b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientStatistics", b => + { + b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFHitLocationCount", b => + { + b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFClientStatistics") + .WithMany("HitLocations") + .HasForeignKey("ClientId", "ServerId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFRating", b => + { + b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFClientAverageStatHistory") + .WithMany("Ratings") + .HasForeignKey("EFClientAverageStatHistoryClientId"); + + b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFClientStatHistory") + .WithMany("Ratings") + .HasForeignKey("EFClientStatHistoryStatHistoryId"); + }); + + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServerStatistics", b => + { + b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFAlias", b => + { + b.HasOne("SharedLibraryCore.Database.Models.EFAliasLink", "Link") + .WithMany("Children") + .HasForeignKey("LinkId") + .OnDelete(DeleteBehavior.Restrict); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFClient", b => + { + b.HasOne("SharedLibraryCore.Database.Models.EFAliasLink", "AliasLink") + .WithMany() + .HasForeignKey("AliasLinkId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("SharedLibraryCore.Database.Models.EFAlias", "CurrentAlias") + .WithMany() + .HasForeignKey("CurrentAliasId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("SharedLibraryCore.Database.Models.EFPenalty", b => + { + b.HasOne("SharedLibraryCore.Database.Models.EFAliasLink", "Link") + .WithMany("ReceivedPenalties") + .HasForeignKey("LinkId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Offender") + .WithMany("ReceivedPenalties") + .HasForeignKey("OffenderId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Punisher") + .WithMany("AdministeredPenalties") + .HasForeignKey("PunisherId") + .OnDelete(DeleteBehavior.Restrict); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/SharedLibraryCore/Migrations/20180529233328_AddAutomatedOffenseAndRatingHistory.cs b/SharedLibraryCore/Migrations/20180529233328_AddAutomatedOffenseAndRatingHistory.cs new file mode 100644 index 000000000..62b85bd9f --- /dev/null +++ b/SharedLibraryCore/Migrations/20180529233328_AddAutomatedOffenseAndRatingHistory.cs @@ -0,0 +1,139 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using System; +using System.Collections.Generic; + +namespace SharedLibraryCore.Migrations +{ + public partial class AddAutomatedOffenseAndRatingHistory : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "AutomatedOffense", + table: "EFPenalties", + nullable: true); + + migrationBuilder.CreateTable( + name: "EFClientAverageStatHistory", + columns: table => new + { + ClientId = table.Column(nullable: false), + Active = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_EFClientAverageStatHistory", x => x.ClientId); + table.ForeignKey( + name: "FK_EFClientAverageStatHistory_EFClients_ClientId", + column: x => x.ClientId, + principalTable: "EFClients", + principalColumn: "ClientId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "EFClientStatHistory", + columns: table => new + { + StatHistoryId = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Active = table.Column(nullable: false), + ClientId = table.Column(nullable: false), + ServerId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_EFClientStatHistory", x => x.StatHistoryId); + table.ForeignKey( + name: "FK_EFClientStatHistory_EFClients_ClientId", + column: x => x.ClientId, + principalTable: "EFClients", + principalColumn: "ClientId", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_EFClientStatHistory_EFServers_ServerId", + column: x => x.ServerId, + principalTable: "EFServers", + principalColumn: "ServerId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "EFRating", + columns: table => new + { + RatingId = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Active = table.Column(nullable: false), + ClientId = table.Column(nullable: false), + EFClientAverageStatHistoryClientId = table.Column(nullable: true), + EFClientStatHistoryStatHistoryId = table.Column(nullable: true), + Performance = table.Column(nullable: false), + Ranking = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_EFRating", x => x.RatingId); + table.ForeignKey( + name: "FK_EFRating_EFClients_ClientId", + column: x => x.ClientId, + principalTable: "EFClients", + principalColumn: "ClientId", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_EFRating_EFClientAverageStatHistory_EFClientAverageStatHistoryClientId", + column: x => x.EFClientAverageStatHistoryClientId, + principalTable: "EFClientAverageStatHistory", + principalColumn: "ClientId", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_EFRating_EFClientStatHistory_EFClientStatHistoryStatHistoryId", + column: x => x.EFClientStatHistoryStatHistoryId, + principalTable: "EFClientStatHistory", + principalColumn: "StatHistoryId", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_EFClientStatHistory_ClientId", + table: "EFClientStatHistory", + column: "ClientId"); + + migrationBuilder.CreateIndex( + name: "IX_EFClientStatHistory_ServerId", + table: "EFClientStatHistory", + column: "ServerId"); + + migrationBuilder.CreateIndex( + name: "IX_EFRating_ClientId", + table: "EFRating", + column: "ClientId"); + + migrationBuilder.CreateIndex( + name: "IX_EFRating_EFClientAverageStatHistoryClientId", + table: "EFRating", + column: "EFClientAverageStatHistoryClientId"); + + migrationBuilder.CreateIndex( + name: "IX_EFRating_EFClientStatHistoryStatHistoryId", + table: "EFRating", + column: "EFClientStatHistoryStatHistoryId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "EFRating"); + + migrationBuilder.DropTable( + name: "EFClientAverageStatHistory"); + + migrationBuilder.DropTable( + name: "EFClientStatHistory"); + + migrationBuilder.DropColumn( + name: "AutomatedOffense", + table: "EFPenalties"); + } + } +} diff --git a/SharedLibraryCore/Migrations/DatabaseContextModelSnapshot.cs b/SharedLibraryCore/Migrations/DatabaseContextModelSnapshot.cs index 2c3d30d2b..9300a7543 100644 --- a/SharedLibraryCore/Migrations/DatabaseContextModelSnapshot.cs +++ b/SharedLibraryCore/Migrations/DatabaseContextModelSnapshot.cs @@ -20,6 +20,17 @@ namespace SharedLibraryCore.Migrations modelBuilder .HasAnnotation("ProductVersion", "2.0.2-rtm-10011"); + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientAverageStatHistory", b => + { + b.Property("ClientId"); + + b.Property("Active"); + + b.HasKey("ClientId"); + + b.ToTable("EFClientAverageStatHistory"); + }); + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientKill", b => { b.Property("KillId") @@ -92,6 +103,26 @@ namespace SharedLibraryCore.Migrations b.ToTable("EFClientMessages"); }); + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientStatHistory", b => + { + b.Property("StatHistoryId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ClientId"); + + b.Property("ServerId"); + + b.HasKey("StatHistoryId"); + + b.HasIndex("ClientId"); + + b.HasIndex("ServerId"); + + b.ToTable("EFClientStatHistory"); + }); + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientStatistics", b => { b.Property("ClientId"); @@ -153,6 +184,34 @@ namespace SharedLibraryCore.Migrations b.ToTable("EFHitLocationCounts"); }); + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFRating", b => + { + b.Property("RatingId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("ClientId"); + + b.Property("EFClientAverageStatHistoryClientId"); + + b.Property("EFClientStatHistoryStatHistoryId"); + + b.Property("Performance"); + + b.Property("Ranking"); + + b.HasKey("RatingId"); + + b.HasIndex("ClientId"); + + b.HasIndex("EFClientAverageStatHistoryClientId"); + + b.HasIndex("EFClientStatHistoryStatHistoryId"); + + b.ToTable("EFRating"); + }); + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServer", b => { b.Property("ServerId"); @@ -269,6 +328,8 @@ namespace SharedLibraryCore.Migrations b.Property("Active"); + b.Property("AutomatedOffense"); + b.Property("Expires"); b.Property("LinkId"); @@ -311,6 +372,14 @@ namespace SharedLibraryCore.Migrations b.ToTable("Vector3"); }); + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientAverageStatHistory", b => + { + b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + }); + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientKill", b => { b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Attacker") @@ -354,6 +423,19 @@ namespace SharedLibraryCore.Migrations .OnDelete(DeleteBehavior.Cascade); }); + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientStatHistory", b => + { + b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server") + .WithMany() + .HasForeignKey("ServerId") + .OnDelete(DeleteBehavior.Cascade); + }); + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFClientStatistics", b => { b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client") @@ -385,6 +467,22 @@ namespace SharedLibraryCore.Migrations .OnDelete(DeleteBehavior.Cascade); }); + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFRating", b => + { + b.HasOne("SharedLibraryCore.Database.Models.EFClient", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFClientAverageStatHistory") + .WithMany("Ratings") + .HasForeignKey("EFClientAverageStatHistoryClientId"); + + b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFClientStatHistory") + .WithMany("Ratings") + .HasForeignKey("EFClientStatHistoryStatHistoryId"); + }); + modelBuilder.Entity("IW4MAdmin.Plugins.Stats.Models.EFServerStatistics", b => { b.HasOne("IW4MAdmin.Plugins.Stats.Models.EFServer", "Server") diff --git a/SharedLibraryCore/Services/PenaltyService.cs b/SharedLibraryCore/Services/PenaltyService.cs index f20cc8454..966eadce8 100644 --- a/SharedLibraryCore/Services/PenaltyService.cs +++ b/SharedLibraryCore/Services/PenaltyService.cs @@ -28,6 +28,7 @@ namespace SharedLibraryCore.Services Expires = newEntity.Expires, Offense = newEntity.Offense, When = newEntity.When, + AutomatedOffense = newEntity.AutomatedOffense }; if (addedEntity.Expires == DateTime.MaxValue) @@ -162,7 +163,8 @@ namespace SharedLibraryCore.Services PunisherId = penalty.PunisherId, Offense = penalty.Offense, Type = penalty.Type.ToString(), - TimeRemaining = now > penalty.Expires ? "" : penalty.Expires.ToString() + TimeRemaining = now > penalty.Expires ? "" : penalty.Expires.ToString(), + AutomatedOffense = penalty.AutomatedOffense }, When = penalty.When, Sensitive = penalty.Type == Penalty.PenaltyType.Flag @@ -206,7 +208,8 @@ namespace SharedLibraryCore.Services PunisherName = punisherAlias.Name, PunisherId = penalty.PunisherId, Offense = penalty.Offense, - Type = penalty.Type.ToString() + Type = penalty.Type.ToString(), + AutomatedOffense = penalty.AutomatedOffense }, When = penalty.When, Sensitive = penalty.Type == Penalty.PenaltyType.Flag diff --git a/WebfrontCore/Controllers/PenaltyController.cs b/WebfrontCore/Controllers/PenaltyController.cs index b708766b7..23eb5d155 100644 --- a/WebfrontCore/Controllers/PenaltyController.cs +++ b/WebfrontCore/Controllers/PenaltyController.cs @@ -43,7 +43,8 @@ namespace WebfrontCore.Controllers PunisherId = p.PunisherId, Type = p.Type.ToString(), TimePunished = p.When.ToString(), - TimeRemaining = p.Expires.ToString() + TimeRemaining = p.Expires.ToString(), + AutomatedOffense = p.AutomatedOffense }).ToList(); return Json(penaltiesDto); diff --git a/WebfrontCore/ViewComponents/PenaltyListViewComponent.cs b/WebfrontCore/ViewComponents/PenaltyListViewComponent.cs index d492e6ecb..1e8ca0c64 100644 --- a/WebfrontCore/ViewComponents/PenaltyListViewComponent.cs +++ b/WebfrontCore/ViewComponents/PenaltyListViewComponent.cs @@ -20,11 +20,12 @@ namespace WebfrontCore.ViewComponents PunisherId = p.PunisherId, PunisherName = p.Punisher.Name, PunisherLevel = p.Punisher.Level.ToString(), - Offense = p.Offense, + Offense = User.Identity.IsAuthenticated && !string.IsNullOrEmpty(p.AutomatedOffense) ? p.AutomatedOffense : p.Offense, Type = p.Type.ToString(), TimePunished = Utilities.GetTimePassed(p.When, false), TimeRemaining = DateTime.UtcNow > p.Expires ? "" : Utilities.TimeSpanText(p.Expires - DateTime.UtcNow), - Sensitive = p.Type == Penalty.PenaltyType.Flag + Sensitive = p.Type == Penalty.PenaltyType.Flag, + AutomatedOffense = p.AutomatedOffense }); penaltiesDto = User.Identity.IsAuthenticated ? penaltiesDto.ToList() : penaltiesDto.Where(p => !p.Sensitive).ToList(); diff --git a/WebfrontCore/bundleconfig.json b/WebfrontCore/bundleconfig.json index a38a40a02..d99d6deb6 100644 --- a/WebfrontCore/bundleconfig.json +++ b/WebfrontCore/bundleconfig.json @@ -23,7 +23,8 @@ "wwwroot/js/profile.js", "wwwroot/js/server.js", "wwwroot/js/search.js", - "wwwroot/js/loader.js" + "wwwroot/js/loader.js", + "wwwroot/js/stats.js" ], // Optionally specify minification options "minify": { diff --git a/WebfrontCore/wwwroot/css/bootstrap-custom.scss b/WebfrontCore/wwwroot/css/bootstrap-custom.scss index d0ccf7713..e2d297199 100644 --- a/WebfrontCore/wwwroot/css/bootstrap-custom.scss +++ b/WebfrontCore/wwwroot/css/bootstrap-custom.scss @@ -179,7 +179,18 @@ select { margin-top: -3px; } -.stats-ranking-icon { - width: 32px; - height: 32px; +.striped > div:nth-child(even) { + background-color: rgba(0, 0, 0, 0.125); } + +.striped > div:nth-child(odd) { + background-color: rgba(0, 0, 0, 0.2); +} + +.client-rating-graph { + min-height: 100px; +} + +.client-rating-icon { + +} \ No newline at end of file diff --git a/WebfrontCore/wwwroot/js/loader.js b/WebfrontCore/wwwroot/js/loader.js index 09ed54a93..8d3e26663 100644 --- a/WebfrontCore/wwwroot/js/loader.js +++ b/WebfrontCore/wwwroot/js/loader.js @@ -1,6 +1,6 @@ -let offset = 15; -let loadCount = 15; -let isLoading = false; +let loaderOffset = 25; +let loadCount = 25; +let isLoaderLoading = false; let loadUri = ''; let loaderResponseId = ''; @@ -11,26 +11,26 @@ function initLoader(location, loaderId) { } function loadMoreItems() { - if (isLoading) { + if (isLoaderLoading) { return false; } showLoader(); - isLoading = true; - $.get(loadUri, { offset: offset, count : loadCount }) + isLoaderLoading = true; + $.get(loadUri, { offset: loaderOffset, count : loadCount }) .done(function (response) { $(loaderResponseId).append(response); if (response.trim().length === 0) { staleLoader(); } hideLoader(); - isLoading = false; + isLoaderLoading = false; }) .fail(function (jqxhr, statis, error) { errorLoader(); - isLoading = false; + isLoaderLoading = false; }); - offset += loadCount; + loaderOffset += loadCount; } function setupListeners() { diff --git a/WebfrontCore/wwwroot/js/stats.js b/WebfrontCore/wwwroot/js/stats.js new file mode 100644 index 000000000..2a5d7fe7d --- /dev/null +++ b/WebfrontCore/wwwroot/js/stats.js @@ -0,0 +1,61 @@ +function getStatsChart(id, width, height) { + const data = $('#' + id).data('history'); + let fixedData = []; + data.forEach(function (item, i) { + fixedData[i] = { x: i, y: item }; + }); + + return new CanvasJS.Chart(id, { + backgroundColor: 'transparent', + height: height, + width: width, + animationEnabled: false, + toolTip: { + contentFormatter: function (e) { + return e.entries[0].dataPoint.y; + } + }, + axisX: { + interval: 1, + gridThickness: 0, + lineThickness: 0, + tickThickness: 0, + margin: 0, + valueFormatString: " " + }, + axisY: { + gridThickness: 0, + lineThickness: 0, + tickThickness: 0, + minimum: Math.min(...data) - 15, + maximum: Math.max(...data) + 15, + margin: 0, + valueFormatString: " ", + labelMaxWidth: 0 + }, + legend: { + maxWidth: 0, + maxHeight: 0, + dockInsidePlotArea: true + }, + data: [{ + showInLegend: false, + type: "splineArea", + color: 'rgba(0, 122, 204, 0.25)', + markerSize: 0, + dataPoints: fixedData + }] + }); +} + +$(document).ready(function () { + $('.client-rating-graph').each(function (i, element) { + getStatsChart($(element).attr('id'), $(element).width(), $(element).height()).render(); + }); + + $(window).resize(function () { + $('.client-rating-graph').each(function (index, element) { + getStatsChart($(element).attr('id'), $(element).width(), $(element).height()).render(); + }); + }); +});