adding Cod4 support (for steam GUID is truncated to 16 characters)

exit properly whoops
add all linked accounts to drop down
consolidate linked admin accounts to the most recently seen one
limited some waits to 5s to hopefully prevent a rare thread lock
This commit is contained in:
RaidMax 2018-05-14 12:55:10 -05:00
parent 6e5501b32d
commit 699c19cd4b
30 changed files with 184 additions and 132 deletions

View File

@ -27,9 +27,9 @@
<PackageReference Include="System.Text.Encoding.CodePages" Version="4.4.0" />
</ItemGroup>
<PropertyGroup>
<!--<PropertyGroup>
<ServerGarbageCollection>true</ServerGarbageCollection>
</PropertyGroup>
</PropertyGroup>-->
<ItemGroup>
<ProjectReference Include="..\SharedLibraryCore\SharedLibraryCore.csproj">

View File

@ -82,14 +82,17 @@ namespace IW4MAdmin.Application.EventParsers
if (cleanedEventLine[0] == 'D')
{
return new GameEvent()
if (Regex.Match(cleanedEventLine, @"^(D);((?:bot[0-9]+)|(?:[A-Z]|[0-9])+);([0-9]+);(axis|allies);(.+);((?:[A-Z]|[0-9])+);([0-9]+);(axis|allies);(.+);((?:[0-9]+|[a-z]+|_)+);([0-9]+);((?:[A-Z]|_)+);((?:[a-z]|_)+)$").Success)
{
Type = GameEvent.EventType.Damage,
Data = Regex.Replace(logLine, @"[0-9]+:[0-9]+\ ", "").Trim(),
Origin = server.GetPlayersAsList().First(c => c.NetworkId == lineSplit[5].ConvertLong()),
Target = server.GetPlayersAsList().First(c => c.NetworkId == lineSplit[1].ConvertLong()),
Owner = server
};
return new GameEvent()
{
Type = GameEvent.EventType.Damage,
Data = cleanedEventLine,
Origin = server.GetPlayersAsList().First(c => c.NetworkId == lineSplit[5].ConvertLong()),
Target = server.GetPlayersAsList().First(c => c.NetworkId == lineSplit[1].ConvertLong()),
Owner = server
};
}
}
if (cleanedEventLine.Contains("ExitLevel"))

View File

@ -49,8 +49,11 @@ namespace IW4MAdmin.Application.IO
events.Add(Parser.GetEvent(server, eventLine));
}
catch (Exception)
catch (Exception e)
{
Program.ServerManager.GetLogger().WriteWarning("Could not properly parse event line");
Program.ServerManager.GetLogger().WriteDebug(e.Message);
Program.ServerManager.GetLogger().WriteDebug(eventLine);
}
}
}

View File

@ -137,7 +137,7 @@ namespace IW4MAdmin.Application
};
ServerManager.GetEventHandler().AddEvent(E);
E.OnProcessed.Wait();
E.OnProcessed.Wait(5000);
}
Console.Write('>');
@ -155,6 +155,7 @@ namespace IW4MAdmin.Application
Console.WriteLine($"Exception: {e.Message}");
Console.WriteLine(loc["MANAGER_EXIT"]);
Console.ReadKey();
return;
}
if (ServerManager.GetApplicationSettings().Configuration().EnableWebFront)
@ -171,7 +172,7 @@ namespace IW4MAdmin.Application
private static void OnCancelKey(object sender, ConsoleCancelEventArgs e)
{
ServerManager.Stop();
OnShutdownComplete.Wait();
OnShutdownComplete.Wait(5000);
}
static void CheckDirectories()

View File

@ -341,7 +341,7 @@ namespace IW4MAdmin.Application
{
try
{
Heartbeat.Send(this, true).Wait();
Heartbeat.Send(this, true).Wait(5000);
heartbeatState.Connected = true;
}
@ -356,7 +356,7 @@ namespace IW4MAdmin.Application
{
try
{
Heartbeat.Send(this).Wait();
Heartbeat.Send(this).Wait(5000);
}
catch (System.Net.Http.HttpRequestException e)
{

View File

@ -23,7 +23,7 @@ namespace Application.RconParsers
TempBan = "tempbanclient {0} \"{1}\""
};
private static string StatusRegex = @"^( *[0-9]+) +-*([0-9]+) +((?:[A-Z]+|[0-9]+)) +((?:[a-z]|[0-9]){16}|bot[0-9]+|(?:[0-9]+)) +(.{0,20}) +([0-9]+) +(\d+\.\d+\.\d+.\d+\:-*\d{1,5}|0+.0+:-*\d{1,5}|loopback) +(-*[0-9]+) +([0-9]+) *$";
private static string StatusRegex = @"^( *[0-9]+) +-*([0-9]+) +((?:[A-Z]+|[0-9]+)) +((?:[a-z]|[0-9]){16}|(?:[a-z]|[0-9]){32}|bot[0-9]+|(?:[0-9]+)) *(.{0,32}) +([0-9]+) +(\d+\.\d+\.\d+.\d+\:-*\d{1,5}|0+.0+:-*\d{1,5}|loopback) +(-*[0-9]+) +([0-9]+) *$";
public async Task<string[]> ExecuteCommandAsync(Connection connection, string command)
{

View File

@ -51,7 +51,6 @@ namespace IW4MAdmin
override public async Task<bool> AddPlayer(Player polledPlayer)
{
if ((polledPlayer.Ping == 999 && !polledPlayer.IsBot) ||
polledPlayer.Ping < 1 ||
polledPlayer.ClientNumber < 0)
@ -451,12 +450,16 @@ namespace IW4MAdmin
{
if (E.Type == GameEvent.EventType.Connect)
{
ChatHistory.Add(new ChatInfo()
// this may be a fix for a hard to reproduce null exception error
lock (ChatHistory)
{
Name = E.Origin.Name,
Message = "CONNECTED",
Time = DateTime.UtcNow
});
ChatHistory.Add(new ChatInfo()
{
Name = E.Origin?.Name ?? "ERROR!",
Message = "CONNECTED",
Time = DateTime.UtcNow
});
}
if (E.Origin.Level > Player.Permission.Moderator)
await E.Origin.Tell(string.Format(loc["SERVER_REPORT_COUNT"], E.Owner.Reports.Count));
@ -479,24 +482,35 @@ namespace IW4MAdmin
else if (E.Type == GameEvent.EventType.Disconnect)
{
ChatHistory.Add(new ChatInfo()
// this may be a fix for a hard to reproduce null exception error
lock (ChatHistory)
{
Name = E.Origin.Name,
Message = "DISCONNECTED",
Time = DateTime.UtcNow
});
ChatHistory.Add(new ChatInfo()
{
Name = E.Origin?.Name ?? "ERROR!",
Message = "DISCONNECTED",
Time = DateTime.UtcNow
});
}
}
if (E.Type == GameEvent.EventType.Say && E.Data?.Length >= 2)
if (E.Type == GameEvent.EventType.Say)
{
E.Data = E.Data.StripColors();
ChatHistory.Add(new ChatInfo()
if (E.Data.Length > 0)
{
Name = E.Origin.Name,
Message = E.Data,
Time = DateTime.UtcNow
});
// this may be a fix for a hard to reproduce null exception error
lock (ChatHistory)
{
ChatHistory.Add(new ChatInfo()
{
Name = E.Origin?.Name ?? "ERROR!",
Message = E.Data,
Time = DateTime.UtcNow
});
}
}
}
if (E.Type == GameEvent.EventType.MapChange)
@ -731,7 +745,10 @@ namespace IW4MAdmin
GameName = Utilities.GetGame(version.Value);
if (GameName == Game.IW4)
{
EventParser = new IW4EventParser();
RconParser = new IW4RConParser();
}
else if (GameName == Game.IW5)
EventParser = new IW5EventParser();
else if (GameName == Game.T5M)

View File

@ -81,8 +81,8 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
double newAverage = (previousAverage * (hitLoc.HitCount - 1) + realAgainstPredict) / hitLoc.HitCount;
hitLoc.HitOffsetAverage = (float)newAverage;
if (hitLoc.HitOffsetAverage > Thresholds.MaxOffset)
if (hitLoc.HitOffsetAverage > Thresholds.MaxOffset &&
hitLoc.HitCount > 15)
{
Log.WriteDebug("*** Reached Max Lifetime Average for Angle Difference ***");
Log.WriteDebug($"Lifetime Average = {newAverage}");
@ -92,9 +92,10 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
return new DetectionPenaltyResult()
{
ClientPenalty = Penalty.PenaltyType.Flag,
ClientPenalty = Penalty.PenaltyType.Ban,
Value = hitLoc.HitOffsetAverage,
HitCount = hitLoc.HitCount,
Type = DetectionType.Offset
};
}
@ -102,7 +103,8 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
double sessAverage = (AngleDifferenceAverage * (HitCount - 1) + realAgainstPredict) / HitCount;
AngleDifferenceAverage = sessAverage;
if (sessAverage > Thresholds.MaxOffset)
if (sessAverage > Thresholds.MaxOffset &&
HitCount > 15)
{
Log.WriteDebug("*** Reached Max Session Average for Angle Difference ***");
Log.WriteDebug($"Session Average = {sessAverage}");
@ -124,7 +126,6 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
#endif
}
var currentStrain = Strain.GetStrain(kill.ViewAngles, Math.Max(50, kill.TimeOffset - LastOffset));
LastOffset = kill.TimeOffset;
if (currentStrain > ClientStats.MaxStrain)

View File

@ -28,8 +28,8 @@ namespace IW4MAdmin.Plugins.Stats.Cheat
public const double KillTimeThreshold = 0.2;
public const double MaxStrainBan = 0.4399;
public const double MaxOffset = 4.789;
public const double MaxStrainFlag = 0.2;
public const double MaxOffset = 1.2;
public const double MaxStrainFlag = 1;
public static double GetMarginOfError(int numKills) => 1.6455 / Math.Sqrt(numKills);

View File

@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SharedLibraryCore.Services;
namespace IW4MAdmin.Plugins.Stats.Helpers
{

View File

@ -104,7 +104,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
// get the client's stats from the database if it exists, otherwise create and attach a new one
// if this fails we want to throw an exception
var clientStats = statsSvc.ClientStatSvc.Find(c => c.ClientId == pl.ClientId && c.ServerId == serverId).FirstOrDefault();
var clientStatsSvc = statsSvc.ClientStatSvc;
var clientStats = clientStatsSvc.Find(c => c.ClientId == pl.ClientId && c.ServerId == serverId).FirstOrDefault();
if (clientStats == null)
{
@ -127,7 +128,6 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
};
// insert if they've not been added
var clientStatsSvc = statsSvc.ClientStatSvc;
clientStats = clientStatsSvc.Insert(clientStats);
await clientStatsSvc.SaveChangesAsync();
}
@ -193,10 +193,10 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
detectionStats.TryRemove(pl.ClientId, out Cheat.Detection removedValue4);
// sync their stats before they leave
// clientStats = UpdateStats(clientStats);
// var clientStatsSvc = statsSvc.ClientStatSvc;
// clientStatsSvc.Update(clientStats);
// await clientStatsSvc.SaveChangesAsync();
var clientStatsSvc = statsSvc.ClientStatSvc;
clientStats = UpdateStats(clientStats);
clientStatsSvc.Update(clientStats);
await clientStatsSvc.SaveChangesAsync();
// increment the total play time
serverStats.TotalPlayTime += (int)(DateTime.UtcNow - pl.LastConnection).TotalSeconds;
@ -305,6 +305,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
var clientDetection = Servers[serverId].PlayerDetections[attacker.ClientId];
var clientStats = Servers[serverId].PlayerStats[attacker.ClientId];
var clientStatsSvc = statsSvc.ClientStatSvc;
clientStatsSvc.Update(clientStats);
// increment their hit count
if (kill.DeathType == IW4Info.MeansOfDeath.MOD_PISTOL_BULLET ||
@ -313,7 +315,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
{
clientStats.HitLocations.Single(hl => hl.Location == kill.HitLoc).HitCount += 1;
statsSvc.ClientStatSvc.Update(clientStats);
//statsSvc.ClientStatSvc.Update(clientStats);
// await statsSvc.ClientStatSvc.SaveChangesAsync();
}
@ -345,7 +347,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
{
Data = 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}",
$"{penalty.Type}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}",
Origin = new Player()
{
ClientId = 1,
@ -365,10 +367,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
await executePenalty(clientDetection.ProcessKill(kill, isDamage));
await executePenalty(clientDetection.ProcessTotalRatio(clientStats));
#if DEBUG
statsSvc.ClientStatSvc.Update(clientStats);
await statsSvc.ClientStatSvc.SaveChangesAsync();
#endif
await clientStatsSvc.SaveChangesAsync();
}
}
@ -448,10 +447,10 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
}
// todo: do we want to save this immediately?
var statsSvc = ContextThreads[serverId].ClientStatSvc;
statsSvc.Update(attackerStats);
statsSvc.Update(victimStats);
await statsSvc.SaveChangesAsync();
var clientStatsSvc = ContextThreads[serverId].ClientStatSvc;
clientStatsSvc.Update(attackerStats);
clientStatsSvc.Update(victimStats);
await clientStatsSvc.SaveChangesAsync();
}
/// <summary>
@ -478,7 +477,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
victimStats.KillStreak = 0;
// process the attacker's stats after the kills
//attackerStats = UpdateStats(attackerStats);
attackerStats = UpdateStats(attackerStats);
// update after calculation
attackerStats.TimePlayed += (int)(DateTime.UtcNow - attackerStats.LastActive).TotalSeconds;
@ -630,7 +629,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
Log.WriteDebug("Syncing stats contexts");
await statsSvc.ServerStatsSvc.SaveChangesAsync();
await statsSvc.ClientStatSvc.SaveChangesAsync();
//await statsSvc.ClientStatSvc.SaveChangesAsync();
await statsSvc.KillStatsSvc.SaveChangesAsync();
await statsSvc.ServerSvc.SaveChangesAsync();

View File

@ -20,7 +20,13 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
public GenericRepository<EFServer> ServerSvc { get; private set; }
public GenericRepository<EFClientKill> KillStatsSvc { get; private set; }
public GenericRepository<EFServerStatistics> ServerStatsSvc { get; private set; }
public GenericRepository<EFClientMessage> MessageSvc { get; private set; }
public GenericRepository<EFClientMessage> MessageSvc
{
get
{
return new GenericRepository<EFClientMessage>();
}
}
public ThreadSafeStatsService()
{
@ -28,7 +34,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
ServerSvc = new GenericRepository<EFServer>();
KillStatsSvc = new GenericRepository<EFClientKill>();
ServerStatsSvc = new GenericRepository<EFServerStatistics>();
MessageSvc = new GenericRepository<EFClientMessage>();
//MessageSvc = new GenericRepository<EFClientMessage>();
}
}
}

View File

@ -387,7 +387,7 @@ namespace SharedLibraryCore.Commands
await E.Owner.Broadcast($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_MAPROTATE"]} [^5{E.Origin.Name}^7]");
else
await E.Owner.Broadcast(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_MAPROTATE"]);
Task.Delay(5000).Wait();
await Task.Delay(5000);
await E.Owner.ExecuteCommandAsync("map_rotate");
}
}
@ -553,14 +553,14 @@ namespace SharedLibraryCore.Commands
if (m.Name.ToLower() == newMap || m.Alias.ToLower() == newMap)
{
await E.Owner.Broadcast($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_MAP_SUCCESS"]} ^5{m.Alias}");
Task.Delay(5000).Wait();
await Task.Delay(5000);
await E.Owner.LoadMap(m.Name);
return;
}
}
await E.Owner.Broadcast($"{Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_MAP_UKN"]} ^5{newMap}");
Task.Delay(5000).Wait();
await Task.Delay(5000);
await E.Owner.LoadMap(newMap);
}
}

View File

@ -50,6 +50,8 @@ namespace SharedLibraryCore.Database.Models
[NotMapped]
public string IPAddressString => new System.Net.IPAddress(BitConverter.GetBytes(IPAddress)).ToString();
[NotMapped]
public virtual IDictionary<int, long> LinkedAccounts { get; set; }
public virtual ICollection<EFPenalty> ReceivedPenalties { get; set; }
public virtual ICollection<EFPenalty> AdministeredPenalties { get; set; }

View File

@ -24,5 +24,6 @@ namespace SharedLibraryCore.Dtos
public List<ProfileMeta> Meta { get; set; }
public bool Online { get; set; }
public string TimeOnline { get; set; }
public IDictionary<int, long> LinkedAccounts { get; set; }
}
}

View File

@ -14,7 +14,7 @@ namespace SharedLibraryCore.Dtos
public string GameType { get; set; }
public int ClientCount { get; set; }
public int MaxClients { get; set; }
public ChatInfo[] ChatHistory { get; set; }
public List<ChatInfo> ChatHistory { get; set; }
public List<PlayerInfo> Players { get; set; }
public Helpers.PlayerHistory[] PlayerHistory { get; set; }
public int ID { get; set; }

View File

@ -33,25 +33,6 @@ namespace SharedLibraryCore.RCon
}
}
class ResponseEvent
{
public int Id { get; set; }
public string[] Response { get; set; }
public Task Awaiter
{
get
{
return Task.Run(() => FinishedEvent.Wait());
}
}
private ManualResetEventSlim FinishedEvent;
public ResponseEvent()
{
FinishedEvent = new ManualResetEventSlim();
}
}
public class Connection
{
public IPEndPoint Endpoint { get; private set; }
@ -110,7 +91,7 @@ namespace SharedLibraryCore.RCon
OnSent.Set();
}
catch (SocketException)
catch (Exception)
{
}
}
@ -167,9 +148,9 @@ namespace SharedLibraryCore.RCon
public async Task<string[]> SendQueryAsync(StaticHelpers.QueryType type, string parameters = "", bool waitForResponse = true)
{
// will this really prevent flooding?
if ((DateTime.Now - LastQuery).TotalMilliseconds < 250)
if ((DateTime.Now - LastQuery).TotalMilliseconds < 350)
{
await Task.Delay(250);
await Task.Delay(350);
}
LastQuery = DateTime.Now;

View File

@ -107,11 +107,33 @@ namespace SharedLibraryCore.Services
{
using (var context = new DatabaseContext())
{
return await context.Clients
.AsNoTracking()
.Include(c => c.CurrentAlias)
.Include(c => c.AliasLink.Children)
.SingleOrDefaultAsync(e => e.ClientId == entityID);
context.ChangeTracker.AutoDetectChangesEnabled = false;
context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
var iqClient = from client in context.Clients
.Include(c => c.CurrentAlias)
.Include(c => c.AliasLink.Children)
where client.ClientId == entityID
select new
{
Client = client,
LinkedAccounts = (from linkedClient in context.Clients
where client.AliasLinkId == linkedClient.AliasLinkId
select new
{
linkedClient.ClientId,
linkedClient.NetworkId
})
};
var foundClient = await iqClient.FirstOrDefaultAsync();
foundClient.Client.LinkedAccounts = new Dictionary<int, long>();
// todo: find out the best way to do this
// I'm doing this here because I don't know the best way to have multiple awaits in the query
foreach (var linked in foundClient.LinkedAccounts)
foundClient.Client.LinkedAccounts.Add(linked.ClientId, linked.NetworkId);
return foundClient.Client;
}
}
@ -216,11 +238,15 @@ namespace SharedLibraryCore.Services
{
using (var context = new DatabaseContext())
{
return await context.Clients
.AsNoTracking()
.Include(c => c.CurrentAlias)
.Where(c => c.Level >= Player.Permission.Trusted)
.ToListAsync();
context.ChangeTracker.AutoDetectChangesEnabled = false;
context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
var iqClients = from client in context.Clients
.Include(c => c.CurrentAlias)
where client.Level >= Player.Permission.Trusted
select client;
return await iqClients.ToListAsync();
}
}
@ -233,14 +259,14 @@ namespace SharedLibraryCore.Services
{
var iqClients = (from alias in context.Aliases
.AsNoTracking()
where alias.Name.ToLower()
.Contains(name.ToLower())
join link in context.AliasLinks
on alias.LinkId equals link.AliasLinkId
join client in context.Clients
.AsNoTracking()
on alias.LinkId equals client.AliasLinkId
select client)
where alias.Name.ToLower()
.Contains(name.ToLower())
join link in context.AliasLinks
on alias.LinkId equals link.AliasLinkId
join client in context.Clients
.AsNoTracking()
on alias.LinkId equals client.AliasLinkId
select client)
.Distinct()
.Include(c => c.CurrentAlias)
.Include(c => c.AliasLink.Children);
@ -255,13 +281,13 @@ namespace SharedLibraryCore.Services
{
var iqClients = (from alias in context.Aliases
.AsNoTracking()
where alias.IPAddress == ipAddress
join link in context.AliasLinks
on alias.LinkId equals link.AliasLinkId
join client in context.Clients
.AsNoTracking()
on alias.LinkId equals client.AliasLinkId
select client)
where alias.IPAddress == ipAddress
join link in context.AliasLinks
on alias.LinkId equals link.AliasLinkId
join client in context.Clients
.AsNoTracking()
on alias.LinkId equals client.AliasLinkId
select client)
.Distinct()
.Include(c => c.CurrentAlias)
.Include(c => c.AliasLink.Children);

View File

@ -12,7 +12,7 @@ namespace SharedLibraryCore.Services
// https://stackoverflow.com/questions/43677906/crud-operations-with-entityframework-using-generic-type
public class GenericRepository<TEntity> where TEntity : class
{
private dynamic _context;
private DatabaseContext _context;
private DbSet<TEntity> _dbSet;
protected DbContext Context
@ -101,7 +101,6 @@ namespace SharedLibraryCore.Services
dbSet.Attach(entity);
dbSet.Remove(entity);
}
public virtual void Delete(TEntity entity)
@ -119,7 +118,6 @@ namespace SharedLibraryCore.Services
T entity = dbSet.Find(id);
dbSet.Attach(entity);
dbSet.Remove(entity);
}
public virtual void Delete(object id)

View File

@ -208,7 +208,8 @@ namespace SharedLibraryCore.Services
Offense = penalty.Offense,
Type = penalty.Type.ToString()
},
When = penalty.When
When = penalty.When,
Sensitive = penalty.Type == Penalty.PenaltyType.Flag
};
// fixme: is this good and fast?
var list = await iqPenalties.ToListAsync();

View File

@ -182,6 +182,7 @@ namespace SharedLibraryCore
public static long ConvertLong(this string str)
{
str = str.Substring(0, Math.Min(str.Length, 16));
if (Int64.TryParse(str, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out long id))
return id;
var bot = Regex.Match(str, @"bot[0-9]+").Value;

View File

@ -71,7 +71,7 @@ namespace WebfrontCore.Controllers
catch (System.Collections.Generic.KeyNotFoundException)
{
// force the "banned" client to be signed out
HttpContext.SignOutAsync().Wait();
HttpContext.SignOutAsync().Wait(5000);
}
}

View File

@ -41,7 +41,8 @@ namespace WebfrontCore.Controllers
.OrderBy(i => i)
.ToList(),
Online = Manager.GetActiveClients().FirstOrDefault(c => c.ClientId == client.ClientId) != null,
TimeOnline = (DateTime.UtcNow - client.LastConnection).TimeSpanText()
TimeOnline = (DateTime.UtcNow - client.LastConnection).TimeSpanText(),
LinkedAccounts = client.LinkedAccounts
};
var meta = await MetaService.GetMeta(client.ClientId);
@ -54,7 +55,7 @@ namespace WebfrontCore.Controllers
clientDto.Meta.Add(new ProfileMeta()
{
Key = Localization["WEBFRONT_CLIENT_META_MASKED"],
Value = client.Masked ? Localization["WEBFRONT_CLIENT_META_TRUE"]: Localization["WEBFRONT_CLIENT_META_FALSE"],
Value = client.Masked ? Localization["WEBFRONT_CLIENT_META_TRUE"] : Localization["WEBFRONT_CLIENT_META_FALSE"],
Sensitive = true,
When = DateTime.MinValue
});
@ -94,7 +95,9 @@ namespace WebfrontCore.Controllers
{
var admins = (await Manager.GetClientService().GetPrivilegedClients())
.Where(a => a.Active)
.OrderByDescending(a => a.Level);
.OrderByDescending(a => a.Level).ThenByDescending(a => a.LastConnection)
.GroupBy(a => a.AliasLinkId).Select(a => a.First());
var adminsDict = new Dictionary<SharedLibraryCore.Objects.Player.Permission, IList<ClientInfo>>();
foreach (var admin in admins)

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Mvc;
using SharedLibraryCore.Dtos;
@ -21,6 +22,11 @@ namespace WebfrontCore.Controllers
public IActionResult Error()
{
var exceptionFeature = HttpContext.Features.Get<IExceptionHandlerPathFeature>();
Manager.GetLogger().WriteError($"[Webfront] {exceptionFeature.Error.Message}");
Manager.GetLogger().WriteDebug(exceptionFeature.Path);
Manager.GetLogger().WriteDebug(exceptionFeature.Error.StackTrace);
ViewBag.Description = Localization["WEBFRONT_ERROR_DESC"];
ViewBag.Title = Localization["WEBFRONT_ERROR_TITLE"];
return View();

View File

@ -13,7 +13,6 @@ namespace WebfrontCore.Controllers
[ResponseCache(NoStore = true, Duration = 0)]
public IActionResult ClientActivity(int id)
{
var s = Manager.GetServers().FirstOrDefault(s2 => s2.GetHashCode() == id);
if (s == null)
return View("Error", "Invalid server!");
@ -27,14 +26,15 @@ namespace WebfrontCore.Controllers
ClientCount = s.ClientNum,
MaxClients = s.MaxClients,
GameType = s.Gametype,
Players = s.Players.Where(p => p != null).Select(p => new PlayerInfo
Players = s.GetPlayersAsList()
.Select(p => new PlayerInfo
{
Name = p.Name,
ClientId = p.ClientId,
Level = p.Level.ToString(),
LevelInt = (int)p.Level
}).ToList(),
ChatHistory = s.ChatHistory.OrderBy(c => c.Time).Take((int)Math.Ceiling(s.ClientNum / 2.0)).ToArray(),
ChatHistory = s.ChatHistory,
PlayerHistory = s.PlayerHistory.ToArray(),
};
return PartialView("_ClientActivity", serverInfo);

View File

@ -19,7 +19,7 @@ namespace WebfrontCore.ViewComponents
MaxClients = s.MaxClients,
GameType = s.Gametype,
PlayerHistory = s.PlayerHistory.ToArray(),
Players = s.Players.Where(p => p != null)
Players = s.GetPlayersAsList()
.Select(p => new PlayerInfo()
{
Name = p.Name,
@ -27,7 +27,7 @@ namespace WebfrontCore.ViewComponents
Level = p.Level.ToString(),
LevelInt = (int)p.Level
}).ToList(),
ChatHistory = s.ChatHistory.ToArray(),
ChatHistory = s.ChatHistory,
Online = !s.Throttled
}).ToList();
return View("_List", serverInfo);

View File

@ -31,13 +31,13 @@
<div id="profile_aliases_btn" class="oi oi-caret-bottom h3 ml-0 ml-md-2"></div>
@if (Model.LevelInt < (int)ViewBag.User.Level &&
(SharedLibraryCore.Objects.Player.Permission)Model.LevelInt != SharedLibraryCore.Objects.Player.Permission.Banned)
(SharedLibraryCore.Objects.Player.Permission)Model.LevelInt != SharedLibraryCore.Objects.Player.Permission.Banned)
{
<div id="profile_action_ban_btn" class="profile-action oi oi-lock-unlocked text-success h3 ml-2" title="Ban Client" data-action="ban" aria-hidden="true"></div>
}
@if (Model.LevelInt < (int)ViewBag.User.Level &&
(SharedLibraryCore.Objects.Player.Permission)Model.LevelInt == SharedLibraryCore.Objects.Player.Permission.Banned)
(SharedLibraryCore.Objects.Player.Permission)Model.LevelInt == SharedLibraryCore.Objects.Player.Permission.Banned)
{
<div id="profile_action_unban_btn" class="profile-action oi oi-lock-locked text-danger h3 ml-2" title="Unban Client" data-action="unban" aria-hidden="true"></div>
}
@ -45,10 +45,13 @@
<div id="profile_aliases" class="pr-0 pr-sm-4 pb-2 mb-2 text-muted order-0">
@{
<span class="text-secondary">@Model.NetworkId.ToString("X")</span> <br />
foreach (var linked in Model.LinkedAccounts)
{
@Html.ActionLink(linked.Value.ToString("X"), "ProfileAsync", "Client", new { id = linked.Key}, new { @class = "link-inverse" })<br/>
}
foreach (string alias in Model.Aliases)
{
@alias <br />
@alias<br />
}
if (ViewBag.Authorized)

View File

@ -6,7 +6,7 @@
}
<div class="col-12 col-md-8 d-none d-md-block">
@{
for (int i = 0; i < Model.ChatHistory.Length; i++)
for (int i = 0; i < Model.ChatHistory.Count; i++)
{
string message = @Model.ChatHistory[i].Message;
if (Model.ChatHistory[i].Message == "CONNECTED")
@ -47,13 +47,13 @@
</div>
</div>
</div>
@if (Model.ChatHistory.Length > 0)
@if (Model.ChatHistory.Count > 0)
{
<div class="w-100 border-bottom d-md-none d-block mt-1 mb-1"></div>
}
<div class="col-12 col-md-8 d-md-none d-block text-left">
@{
for (int i = 0; i < Model.ChatHistory.Length; i++)
for (int i = 0; i < Model.ChatHistory.Count; i++)
{
string message = @Model.ChatHistory[i].Message;
if (Model.ChatHistory[i].Message == "CONNECTED")

View File

@ -3,7 +3,6 @@
@{
Layout = null;
ViewBag.Description += Model.Name + ", ";
}
<div class="row server-header" id="server_header_@Model.ID">

View File

@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<!--<MvcRazorCompileOnPublish>false</MvcRazorCompileOnPublish>-->
<MvcRazorCompileOnPublish>false</MvcRazorCompileOnPublish>
<PreserveCompilationContext>true</PreserveCompilationContext>
<TypeScriptToolsVersion>2.6</TypeScriptToolsVersion>
<PackageId>RaidMax.IW4MAdmin.WebfrontCore</PackageId>