Compare commits
61 Commits
2022.04.05
...
2022.06.02
Author | SHA1 | Date | |
---|---|---|---|
a38789adb9 | |||
e459b2fcde | |||
26853a0005 | |||
ee14306db9 | |||
169105e849 | |||
7c10e0e3de | |||
2f7eb07e39 | |||
0f9f4f597b | |||
880f9333d9 | |||
31da5d352e | |||
83a469cae3 | |||
1f13f9122c | |||
dd8c4f438f | |||
2230036d45 | |||
1700b7da91 | |||
fab97ccad4 | |||
0bf0d033f7 | |||
2bbabcb9e8 | |||
1995dbd080 | |||
a3b94b50e3 | |||
bc38b36e4a | |||
e346aa037e | |||
389c687420 | |||
074e36413e | |||
104d9bdc4c | |||
c51d28937b | |||
ffa8a46feb | |||
91c46dbdd4 | |||
ff0d22c142 | |||
3ad4aa2196 | |||
d462892467 | |||
cabedb6f0b | |||
5b7f5160b2 | |||
0a8e415af8 | |||
7b3ddd58c6 | |||
ed1032415e | |||
35b43e7438 | |||
284c2e9726 | |||
fd049edb3f | |||
4884abee76 | |||
1df76b6ac3 | |||
4c42a1d511 | |||
27635a6dd3 | |||
0175425708 | |||
5e12bf60b5 | |||
2f10ca8599 | |||
62ec18309e | |||
87361bf3d7 | |||
dc45136077 | |||
21b0a7998d | |||
20b8f0b99a | |||
314ff96e71 | |||
d7c4f5452c | |||
3cb50635e5 | |||
4fbe0ee0ed | |||
4023ca37d4 | |||
425ec2621d | |||
15c3ca53e2 | |||
6097ca504c | |||
70cd01eafb | |||
a2d5e37c6f |
1
.gitignore
vendored
1
.gitignore
vendored
@ -224,7 +224,6 @@ bootstrap-custom.min.css
|
|||||||
bootstrap-custom.css
|
bootstrap-custom.css
|
||||||
**/Master/static
|
**/Master/static
|
||||||
**/Master/dev_env
|
**/Master/dev_env
|
||||||
/WebfrontCore/Views/Plugins/*
|
|
||||||
/WebfrontCore/wwwroot/**/dds
|
/WebfrontCore/wwwroot/**/dds
|
||||||
/WebfrontCore/wwwroot/images/radar/*
|
/WebfrontCore/wwwroot/images/radar/*
|
||||||
|
|
||||||
|
@ -24,7 +24,8 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Jint" Version="3.0.0-beta-2037" />
|
<PackageReference Include="Jint" Version="3.0.0-beta-2038" />
|
||||||
|
<PackageReference Include="MaxMind.GeoIP2" Version="5.1.0" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.1">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.1">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
@ -63,6 +64,9 @@
|
|||||||
<None Update="Configuration\LoggingConfiguration.json">
|
<None Update="Configuration\LoggingConfiguration.json">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
|
<None Update="Resources\GeoLite2-Country.mmdb">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
|
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
|
||||||
|
@ -431,6 +431,7 @@ namespace IW4MAdmin
|
|||||||
Clients[E.Origin.ClientNumber] = E.Origin;
|
Clients[E.Origin.ClientNumber] = E.Origin;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
E.Origin.GameName = (Reference.Game?)GameName;
|
||||||
E.Origin = await OnClientConnected(E.Origin);
|
E.Origin = await OnClientConnected(E.Origin);
|
||||||
E.Target = E.Origin;
|
E.Target = E.Origin;
|
||||||
}
|
}
|
||||||
@ -508,7 +509,8 @@ namespace IW4MAdmin
|
|||||||
{
|
{
|
||||||
Origin = E.Origin,
|
Origin = E.Origin,
|
||||||
Target = E.Target,
|
Target = E.Target,
|
||||||
Reason = E.Data
|
Reason = E.Data,
|
||||||
|
ReportedOn = DateTime.UtcNow
|
||||||
});
|
});
|
||||||
|
|
||||||
var newReport = new EFPenalty()
|
var newReport = new EFPenalty()
|
||||||
@ -571,10 +573,8 @@ namespace IW4MAdmin
|
|||||||
Time = DateTime.UtcNow
|
Time = DateTime.UtcNow
|
||||||
});
|
});
|
||||||
|
|
||||||
await _metaService.SetPersistentMeta("LastMapPlayed", CurrentMap.Alias, E.Origin.ClientId,
|
await _metaService.SetPersistentMeta("LastMapPlayed", CurrentMap.Alias, E.Origin.ClientId);
|
||||||
Manager.CancellationToken);
|
await _metaService.SetPersistentMeta("LastServerPlayed", E.Owner.Hostname, E.Origin.ClientId);
|
||||||
await _metaService.SetPersistentMeta("LastServerPlayed", E.Owner.Hostname, E.Origin.ClientId,
|
|
||||||
Manager.CancellationToken);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (E.Type == GameEvent.EventType.PreDisconnect)
|
else if (E.Type == GameEvent.EventType.PreDisconnect)
|
||||||
@ -645,7 +645,7 @@ namespace IW4MAdmin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ChatHistory.Add(new ChatInfo()
|
ChatHistory.Add(new ChatInfo
|
||||||
{
|
{
|
||||||
Name = E.Origin.Name,
|
Name = E.Origin.Name,
|
||||||
Message = message,
|
Message = message,
|
||||||
@ -792,8 +792,16 @@ namespace IW4MAdmin
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
async Task<List<EFClient>[]> PollPlayersAsync()
|
async Task<List<EFClient>[]> PollPlayersAsync()
|
||||||
{
|
{
|
||||||
|
var tokenSource = new CancellationTokenSource();
|
||||||
|
tokenSource.CancelAfter(TimeSpan.FromSeconds(5));
|
||||||
var currentClients = GetClientsAsList();
|
var currentClients = GetClientsAsList();
|
||||||
var statusResponse = await this.GetStatusAsync(Manager.CancellationToken);
|
var statusResponse = await this.GetStatusAsync(tokenSource.Token);
|
||||||
|
|
||||||
|
if (statusResponse is null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
var polledClients = statusResponse.Clients.AsEnumerable();
|
var polledClients = statusResponse.Clients.AsEnumerable();
|
||||||
|
|
||||||
if (Manager.GetApplicationSettings().Configuration().IgnoreBots)
|
if (Manager.GetApplicationSettings().Configuration().IgnoreBots)
|
||||||
@ -907,9 +915,8 @@ namespace IW4MAdmin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DateTime start = DateTime.Now;
|
private DateTime _lastMessageSent = DateTime.Now;
|
||||||
DateTime playerCountStart = DateTime.Now;
|
private DateTime _lastPlayerCount = DateTime.Now;
|
||||||
DateTime lastCount = DateTime.Now;
|
|
||||||
|
|
||||||
public override async Task<bool> ProcessUpdatesAsync(CancellationToken cts)
|
public override async Task<bool> ProcessUpdatesAsync(CancellationToken cts)
|
||||||
{
|
{
|
||||||
@ -923,14 +930,21 @@ namespace IW4MAdmin
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (Manager.GetApplicationSettings().Configuration().RConPollRate == int.MaxValue && Utilities.IsDevelopment)
|
if (Manager.GetApplicationSettings().Configuration().RConPollRate == int.MaxValue &&
|
||||||
|
Utilities.IsDevelopment)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var polledClients = await PollPlayersAsync();
|
var polledClients = await PollPlayersAsync();
|
||||||
|
|
||||||
foreach (var disconnectingClient in polledClients[1].Where(_client => !_client.IsZombieClient /* ignores "fake" zombie clients */))
|
if (polledClients is null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var disconnectingClient in polledClients[1]
|
||||||
|
.Where(client => !client.IsZombieClient /* ignores "fake" zombie clients */))
|
||||||
{
|
{
|
||||||
disconnectingClient.CurrentServer = this;
|
disconnectingClient.CurrentServer = this;
|
||||||
var e = new GameEvent()
|
var e = new GameEvent()
|
||||||
@ -946,23 +960,20 @@ namespace IW4MAdmin
|
|||||||
}
|
}
|
||||||
|
|
||||||
// this are our new connecting clients
|
// this are our new connecting clients
|
||||||
foreach (var client in polledClients[0])
|
foreach (var client in polledClients[0].Where(client =>
|
||||||
|
!string.IsNullOrEmpty(client.Name) && (client.Ping != 999 || client.IsBot)))
|
||||||
{
|
{
|
||||||
// note: this prevents players in ZMBI state from being registered with no name
|
|
||||||
if (string.IsNullOrEmpty(client.Name) || (client.Ping == 999 && !client.IsBot))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
client.CurrentServer = this;
|
client.CurrentServer = this;
|
||||||
var e = new GameEvent()
|
client.GameName = (Reference.Game?)GameName;
|
||||||
|
|
||||||
|
var e = new GameEvent
|
||||||
{
|
{
|
||||||
Type = GameEvent.EventType.PreConnect,
|
Type = GameEvent.EventType.PreConnect,
|
||||||
Origin = client,
|
Origin = client,
|
||||||
Owner = this,
|
Owner = this,
|
||||||
IsBlocking = true,
|
IsBlocking = true,
|
||||||
Extra = client.GetAdditionalProperty<string>("BotGuid"),
|
Extra = client.GetAdditionalProperty<string>("BotGuid"),
|
||||||
Source = GameEvent.EventSource.Status
|
Source = GameEvent.EventSource.Status,
|
||||||
};
|
};
|
||||||
|
|
||||||
Manager.AddEvent(e);
|
Manager.AddEvent(e);
|
||||||
@ -973,19 +984,19 @@ namespace IW4MAdmin
|
|||||||
foreach (var client in polledClients[2])
|
foreach (var client in polledClients[2])
|
||||||
{
|
{
|
||||||
client.CurrentServer = this;
|
client.CurrentServer = this;
|
||||||
var e = new GameEvent()
|
var gameEvent = new GameEvent
|
||||||
{
|
{
|
||||||
Type = GameEvent.EventType.Update,
|
Type = GameEvent.EventType.Update,
|
||||||
Origin = client,
|
Origin = client,
|
||||||
Owner = this
|
Owner = this
|
||||||
};
|
};
|
||||||
|
|
||||||
Manager.AddEvent(e);
|
Manager.AddEvent(gameEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Throttled)
|
if (Throttled)
|
||||||
{
|
{
|
||||||
var _event = new GameEvent()
|
var gameEvent = new GameEvent
|
||||||
{
|
{
|
||||||
Type = GameEvent.EventType.ConnectionRestored,
|
Type = GameEvent.EventType.ConnectionRestored,
|
||||||
Owner = this,
|
Owner = this,
|
||||||
@ -993,54 +1004,52 @@ namespace IW4MAdmin
|
|||||||
Target = Utilities.IW4MAdminClient(this)
|
Target = Utilities.IW4MAdminClient(this)
|
||||||
};
|
};
|
||||||
|
|
||||||
Manager.AddEvent(_event);
|
Manager.AddEvent(gameEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
LastPoll = DateTime.Now;
|
LastPoll = DateTime.Now;
|
||||||
}
|
}
|
||||||
|
|
||||||
catch (NetworkException e)
|
catch (NetworkException ex)
|
||||||
{
|
{
|
||||||
if (!Throttled)
|
if (Throttled)
|
||||||
{
|
{
|
||||||
var gameEvent = new GameEvent
|
return true;
|
||||||
{
|
|
||||||
Type = GameEvent.EventType.ConnectionLost,
|
|
||||||
Owner = this,
|
|
||||||
Origin = Utilities.IW4MAdminClient(this),
|
|
||||||
Target = Utilities.IW4MAdminClient(this),
|
|
||||||
Extra = e,
|
|
||||||
Data = ConnectionErrors.ToString()
|
|
||||||
};
|
|
||||||
|
|
||||||
Manager.AddEvent(gameEvent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RunServerCollection();
|
var gameEvent = new GameEvent
|
||||||
|
{
|
||||||
|
Type = GameEvent.EventType.ConnectionLost,
|
||||||
|
Owner = this,
|
||||||
|
Origin = Utilities.IW4MAdminClient(this),
|
||||||
|
Target = Utilities.IW4MAdminClient(this),
|
||||||
|
Extra = ex,
|
||||||
|
Data = ConnectionErrors.ToString()
|
||||||
|
};
|
||||||
|
|
||||||
|
Manager.AddEvent(gameEvent);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
RunServerCollection();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DateTime.Now - _lastMessageSent <=
|
||||||
|
TimeSpan.FromSeconds(Manager.GetApplicationSettings().Configuration().AutoMessagePeriod) ||
|
||||||
|
BroadcastMessages.Count <= 0 || ClientNum <= 0)
|
||||||
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
LastMessage = DateTime.Now - start;
|
|
||||||
lastCount = DateTime.Now;
|
|
||||||
|
|
||||||
RunServerCollection();
|
|
||||||
|
|
||||||
// send out broadcast messages
|
// send out broadcast messages
|
||||||
if (LastMessage.TotalSeconds > Manager.GetApplicationSettings().Configuration().AutoMessagePeriod
|
var messages =
|
||||||
&& BroadcastMessages.Count > 0
|
(await this.ProcessMessageToken(Manager.GetMessageTokens(), BroadcastMessages[NextMessage])).Split(
|
||||||
&& ClientNum > 0)
|
Environment.NewLine);
|
||||||
{
|
await BroadcastAsync(messages, token: Manager.CancellationToken);
|
||||||
string[] messages = (await this.ProcessMessageToken(Manager.GetMessageTokens(), BroadcastMessages[NextMessage])).Split(Environment.NewLine);
|
|
||||||
|
|
||||||
foreach (string message in messages)
|
NextMessage = NextMessage == BroadcastMessages.Count - 1 ? 0 : NextMessage + 1;
|
||||||
{
|
_lastMessageSent = DateTime.Now;
|
||||||
Broadcast(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
NextMessage = NextMessage == (BroadcastMessages.Count - 1) ? 0 : NextMessage + 1;
|
|
||||||
start = DateTime.Now;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -1052,21 +1061,23 @@ namespace IW4MAdmin
|
|||||||
}
|
}
|
||||||
|
|
||||||
// this one is ok
|
// this one is ok
|
||||||
catch (Exception e) when(e is ServerException || e is RConException)
|
catch (Exception e) when (e is ServerException || e is RConException)
|
||||||
{
|
{
|
||||||
using(LogContext.PushProperty("Server", ToString()))
|
using (LogContext.PushProperty("Server", ToString()))
|
||||||
{
|
{
|
||||||
ServerLogger.LogWarning(e, "Undesirable exception occured during processing updates");
|
ServerLogger.LogWarning(e, "Undesirable exception occured during processing updates");
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
using(LogContext.PushProperty("Server", ToString()))
|
using (LogContext.PushProperty("Server", ToString()))
|
||||||
{
|
{
|
||||||
ServerLogger.LogError(e, "Unexpected exception occured during processing updates");
|
ServerLogger.LogError(e, "Unexpected exception occured during processing updates");
|
||||||
}
|
}
|
||||||
|
|
||||||
Console.WriteLine(loc["SERVER_ERROR_EXCEPTION"].FormatExt($"[{IP}:{Port}]"));
|
Console.WriteLine(loc["SERVER_ERROR_EXCEPTION"].FormatExt($"[{IP}:{Port}]"));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -1076,7 +1087,7 @@ namespace IW4MAdmin
|
|||||||
{
|
{
|
||||||
var appConfig = _serviceProvider.GetService<ApplicationConfiguration>();
|
var appConfig = _serviceProvider.GetService<ApplicationConfiguration>();
|
||||||
|
|
||||||
if (lastCount - playerCountStart < appConfig?.ServerDataCollectionInterval)
|
if (DateTime.Now - _lastPlayerCount < appConfig?.ServerDataCollectionInterval)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1097,7 +1108,7 @@ namespace IW4MAdmin
|
|||||||
Map = CurrentMap.Name
|
Map = CurrentMap.Name
|
||||||
});
|
});
|
||||||
|
|
||||||
playerCountStart = DateTime.Now;
|
_lastPlayerCount = DateTime.Now;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Initialize()
|
public async Task Initialize()
|
||||||
|
@ -447,6 +447,7 @@ namespace IW4MAdmin.Application
|
|||||||
.AddSingleton<IServerDataViewer, ServerDataViewer>()
|
.AddSingleton<IServerDataViewer, ServerDataViewer>()
|
||||||
.AddSingleton<IServerDataCollector, ServerDataCollector>()
|
.AddSingleton<IServerDataCollector, ServerDataCollector>()
|
||||||
.AddSingleton<IEventPublisher, EventPublisher>()
|
.AddSingleton<IEventPublisher, EventPublisher>()
|
||||||
|
.AddSingleton<IGeoLocationService>(new GeoLocationService(Path.Join(".", "Resources", "GeoLite2-Country.mmdb")))
|
||||||
.AddTransient<IScriptPluginTimerHelper, ScriptPluginTimerHelper>()
|
.AddTransient<IScriptPluginTimerHelper, ScriptPluginTimerHelper>()
|
||||||
.AddSingleton(translationLookup)
|
.AddSingleton(translationLookup)
|
||||||
.AddDatabaseContextOptions(appConfig);
|
.AddDatabaseContextOptions(appConfig);
|
||||||
|
@ -83,7 +83,6 @@ namespace IW4MAdmin.Application.Meta
|
|||||||
Value = lastMapMeta.Value,
|
Value = lastMapMeta.Value,
|
||||||
ShouldDisplay = true,
|
ShouldDisplay = true,
|
||||||
Type = MetaType.Information,
|
Type = MetaType.Information,
|
||||||
Column = 1,
|
|
||||||
Order = 6
|
Order = 6
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -101,8 +100,7 @@ namespace IW4MAdmin.Application.Meta
|
|||||||
Value = lastServerMeta.Value,
|
Value = lastServerMeta.Value,
|
||||||
ShouldDisplay = true,
|
ShouldDisplay = true,
|
||||||
Type = MetaType.Information,
|
Type = MetaType.Information,
|
||||||
Column = 0,
|
Order = 7
|
||||||
Order = 6
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,8 +118,7 @@ namespace IW4MAdmin.Application.Meta
|
|||||||
Key = _transLookup["WEBFRONT_PROFILE_META_PLAY_TIME"],
|
Key = _transLookup["WEBFRONT_PROFILE_META_PLAY_TIME"],
|
||||||
Value = TimeSpan.FromHours(client.TotalConnectionTime / 3600.0).HumanizeForCurrentCulture(),
|
Value = TimeSpan.FromHours(client.TotalConnectionTime / 3600.0).HumanizeForCurrentCulture(),
|
||||||
ShouldDisplay = true,
|
ShouldDisplay = true,
|
||||||
Column = 1,
|
Order = 8,
|
||||||
Order = 0,
|
|
||||||
Type = MetaType.Information
|
Type = MetaType.Information
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -131,8 +128,7 @@ namespace IW4MAdmin.Application.Meta
|
|||||||
Key = _transLookup["WEBFRONT_PROFILE_META_FIRST_SEEN"],
|
Key = _transLookup["WEBFRONT_PROFILE_META_FIRST_SEEN"],
|
||||||
Value = (DateTime.UtcNow - client.FirstConnection).HumanizeForCurrentCulture(),
|
Value = (DateTime.UtcNow - client.FirstConnection).HumanizeForCurrentCulture(),
|
||||||
ShouldDisplay = true,
|
ShouldDisplay = true,
|
||||||
Column = 1,
|
Order = 9,
|
||||||
Order = 1,
|
|
||||||
Type = MetaType.Information
|
Type = MetaType.Information
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -142,8 +138,7 @@ namespace IW4MAdmin.Application.Meta
|
|||||||
Key = _transLookup["WEBFRONT_PROFILE_META_LAST_SEEN"],
|
Key = _transLookup["WEBFRONT_PROFILE_META_LAST_SEEN"],
|
||||||
Value = (DateTime.UtcNow - client.LastConnection).HumanizeForCurrentCulture(),
|
Value = (DateTime.UtcNow - client.LastConnection).HumanizeForCurrentCulture(),
|
||||||
ShouldDisplay = true,
|
ShouldDisplay = true,
|
||||||
Column = 1,
|
Order = 10,
|
||||||
Order = 2,
|
|
||||||
Type = MetaType.Information
|
Type = MetaType.Information
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -154,8 +149,7 @@ namespace IW4MAdmin.Application.Meta
|
|||||||
Value = client.Connections.ToString("#,##0",
|
Value = client.Connections.ToString("#,##0",
|
||||||
new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
|
new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
|
||||||
ShouldDisplay = true,
|
ShouldDisplay = true,
|
||||||
Column = 1,
|
Order = 11,
|
||||||
Order = 3,
|
|
||||||
Type = MetaType.Information
|
Type = MetaType.Information
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -167,8 +161,7 @@ namespace IW4MAdmin.Application.Meta
|
|||||||
? Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_TRUE"]
|
? Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_TRUE"]
|
||||||
: Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_FALSE"],
|
: Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_FALSE"],
|
||||||
IsSensitive = true,
|
IsSensitive = true,
|
||||||
Column = 1,
|
Order = 12,
|
||||||
Order = 4,
|
|
||||||
Type = MetaType.Information
|
Type = MetaType.Information
|
||||||
});
|
});
|
||||||
|
|
||||||
|
12
Application/Misc/AsyncResult.cs
Normal file
12
Application/Misc/AsyncResult.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace IW4MAdmin.Application.Misc;
|
||||||
|
|
||||||
|
public class AsyncResult : IAsyncResult
|
||||||
|
{
|
||||||
|
public object AsyncState { get; set; }
|
||||||
|
public WaitHandle AsyncWaitHandle { get; set; }
|
||||||
|
public bool CompletedSynchronously { get; set; }
|
||||||
|
public bool IsCompleted { get; set; }
|
||||||
|
}
|
13
Application/Misc/GeoLocationResult.cs
Normal file
13
Application/Misc/GeoLocationResult.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
using SharedLibraryCore.Interfaces;
|
||||||
|
|
||||||
|
namespace IW4MAdmin.Application.Misc;
|
||||||
|
|
||||||
|
public class GeoLocationResult : IGeoLocationResult
|
||||||
|
{
|
||||||
|
public string Country { get; set; }
|
||||||
|
public string CountryCode { get; set; }
|
||||||
|
public string Region { get; set; }
|
||||||
|
public string ASN { get; set; }
|
||||||
|
public string Timezone { get; set; }
|
||||||
|
public string Organization { get; set; }
|
||||||
|
}
|
40
Application/Misc/GeoLocationService.cs
Normal file
40
Application/Misc/GeoLocationService.cs
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using MaxMind.GeoIP2;
|
||||||
|
using MaxMind.GeoIP2.Responses;
|
||||||
|
using SharedLibraryCore.Interfaces;
|
||||||
|
|
||||||
|
namespace IW4MAdmin.Application.Misc;
|
||||||
|
|
||||||
|
public class GeoLocationService : IGeoLocationService
|
||||||
|
{
|
||||||
|
private readonly string _sourceAddress;
|
||||||
|
|
||||||
|
public GeoLocationService(string sourceAddress)
|
||||||
|
{
|
||||||
|
_sourceAddress = sourceAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<IGeoLocationResult> Locate(string address)
|
||||||
|
{
|
||||||
|
CountryResponse country = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var reader = new DatabaseReader(_sourceAddress);
|
||||||
|
reader.TryCountry(address, out country);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
|
||||||
|
var response = new GeoLocationResult
|
||||||
|
{
|
||||||
|
Country = country?.Country.Name ?? "Unknown",
|
||||||
|
CountryCode = country?.Country.IsoCode ?? ""
|
||||||
|
};
|
||||||
|
|
||||||
|
return Task.FromResult((IGeoLocationResult)response);
|
||||||
|
}
|
||||||
|
}
|
@ -207,7 +207,7 @@ public class MetaServiceV2 : IMetaServiceV2
|
|||||||
|
|
||||||
if (metaValue is null)
|
if (metaValue is null)
|
||||||
{
|
{
|
||||||
_logger.LogWarning("No meta exists for key {Key}, clientId {ClientId}", metaKey, clientId);
|
_logger.LogDebug("No meta exists for key {Key}, clientId {ClientId}", metaKey, clientId);
|
||||||
return default;
|
return default;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -446,54 +446,7 @@ public class MetaServiceV2 : IMetaServiceV2
|
|||||||
|
|
||||||
private static IEnumerable<T> ProcessInformationMeta<T>(IEnumerable<T> meta) where T : IClientMeta
|
private static IEnumerable<T> ProcessInformationMeta<T>(IEnumerable<T> meta) where T : IClientMeta
|
||||||
{
|
{
|
||||||
var metaList = meta.ToList();
|
return meta;
|
||||||
var metaWithColumn = metaList
|
|
||||||
.Where(m => m.Column != null)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var columnGrouping = metaWithColumn
|
|
||||||
.GroupBy(m => m.Column)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var metaToSort = metaList.Except(metaWithColumn).ToList();
|
|
||||||
|
|
||||||
var table = columnGrouping.Select(metaItem => new List<T>(metaItem)).ToList();
|
|
||||||
|
|
||||||
while (metaToSort.Count > 0)
|
|
||||||
{
|
|
||||||
var sortingMeta = metaToSort.First();
|
|
||||||
|
|
||||||
int IndexOfSmallestColumn()
|
|
||||||
{
|
|
||||||
var index = 0;
|
|
||||||
var smallestColumnSize = int.MaxValue;
|
|
||||||
for (var i = 0; i < table.Count; i++)
|
|
||||||
{
|
|
||||||
if (table[i].Count >= smallestColumnSize)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
smallestColumnSize = table[i].Count;
|
|
||||||
index = i;
|
|
||||||
}
|
|
||||||
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
|
|
||||||
var columnIndex = IndexOfSmallestColumn();
|
|
||||||
|
|
||||||
sortingMeta.Column = columnIndex;
|
|
||||||
sortingMeta.Order = columnGrouping
|
|
||||||
.First(group => group.Key == columnIndex)
|
|
||||||
.Count();
|
|
||||||
|
|
||||||
table[columnIndex].Add(sortingMeta);
|
|
||||||
|
|
||||||
metaToSort.Remove(sortingMeta);
|
|
||||||
}
|
|
||||||
|
|
||||||
return metaList;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool ValidArgs(string key, int clientId) => !string.IsNullOrWhiteSpace(key) && clientId > 0;
|
private static bool ValidArgs(string key, int clientId) => !string.IsNullOrWhiteSpace(key) && clientId > 0;
|
||||||
|
@ -276,8 +276,8 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
{
|
{
|
||||||
_logger.LogDebug("OnLoad executing for {Name}", Name);
|
_logger.LogDebug("OnLoad executing for {Name}", Name);
|
||||||
_scriptEngine.SetValue("_manager", manager);
|
_scriptEngine.SetValue("_manager", manager);
|
||||||
_scriptEngine.SetValue("getDvar", GetDvarAsync);
|
_scriptEngine.SetValue("getDvar", BeginGetDvar);
|
||||||
_scriptEngine.SetValue("setDvar", SetDvarAsync);
|
_scriptEngine.SetValue("setDvar", BeginSetDvar);
|
||||||
_scriptEngine.Evaluate("plugin.onLoadAsync(_manager)");
|
_scriptEngine.Evaluate("plugin.onLoadAsync(_manager)");
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
@ -343,7 +343,8 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
/// <param name="commands">commands value from jint parser</param>
|
/// <param name="commands">commands value from jint parser</param>
|
||||||
/// <param name="scriptCommandFactory">factory to create the command from</param>
|
/// <param name="scriptCommandFactory">factory to create the command from</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private IEnumerable<IManagerCommand> GenerateScriptCommands(JsValue commands, IScriptCommandFactory scriptCommandFactory)
|
private IEnumerable<IManagerCommand> GenerateScriptCommands(JsValue commands,
|
||||||
|
IScriptCommandFactory scriptCommandFactory)
|
||||||
{
|
{
|
||||||
var commandList = new List<IManagerCommand>();
|
var commandList = new List<IManagerCommand>();
|
||||||
|
|
||||||
@ -410,7 +411,7 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
|
|
||||||
_scriptEngine.SetValue("_event", gameEvent);
|
_scriptEngine.SetValue("_event", gameEvent);
|
||||||
var jsEventObject = _scriptEngine.Evaluate("_event");
|
var jsEventObject = _scriptEngine.Evaluate("_event");
|
||||||
|
|
||||||
dynamicCommand.execute.Target.Invoke(_scriptEngine, jsEventObject);
|
dynamicCommand.execute.Target.Invoke(_scriptEngine, jsEventObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -424,7 +425,7 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
|
|
||||||
throw new PluginException("A runtime error occured while executing action for script plugin");
|
throw new PluginException("A runtime error occured while executing action for script plugin");
|
||||||
}
|
}
|
||||||
|
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
using (LogContext.PushProperty("Server", gameEvent.Owner?.ToString()))
|
using (LogContext.PushProperty("Server", gameEvent.Owner?.ToString()))
|
||||||
@ -454,83 +455,71 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
return commandList;
|
return commandList;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GetDvarAsync(Server server, string dvarName, Delegate onCompleted)
|
private void BeginGetDvar(Server server, string dvarName, Delegate onCompleted)
|
||||||
{
|
{
|
||||||
Task.Run<Task>(async () =>
|
var tokenSource = new CancellationTokenSource();
|
||||||
|
tokenSource.CancelAfter(TimeSpan.FromSeconds(15));
|
||||||
|
|
||||||
|
server.BeginGetDvar(dvarName, result =>
|
||||||
{
|
{
|
||||||
var tokenSource = new CancellationTokenSource();
|
var shouldRelease = false;
|
||||||
tokenSource.CancelAfter(TimeSpan.FromSeconds(5));
|
|
||||||
string result = null;
|
|
||||||
var success = true;
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
result = (await server.GetDvarAsync<string>(dvarName, token: tokenSource.Token)).Value;
|
_onProcessing.Wait(tokenSource.Token);
|
||||||
}
|
shouldRelease = true;
|
||||||
catch
|
var (success, value) = (ValueTuple<bool, string>)result.AsyncState;
|
||||||
{
|
|
||||||
success = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
await _onProcessing.WaitAsync();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
onCompleted.DynamicInvoke(JsValue.Undefined,
|
|
||||||
new[]
|
|
||||||
{
|
|
||||||
JsValue.FromObject(_scriptEngine, server),
|
|
||||||
JsValue.FromObject(_scriptEngine, dvarName),
|
|
||||||
JsValue.FromObject(_scriptEngine, result),
|
|
||||||
JsValue.FromObject(_scriptEngine, success),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (_onProcessing.CurrentCount == 0)
|
|
||||||
{
|
|
||||||
_onProcessing.Release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
private void SetDvarAsync(Server server, string dvarName, string dvarValue, Delegate onCompleted)
|
|
||||||
{
|
|
||||||
Task.Run<Task>(async () =>
|
|
||||||
{
|
|
||||||
var tokenSource = new CancellationTokenSource();
|
|
||||||
tokenSource.CancelAfter(TimeSpan.FromSeconds(5));
|
|
||||||
var success = true;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await server.SetDvarAsync(dvarName, dvarValue, tokenSource.Token);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
success = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
await _onProcessing.WaitAsync();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
onCompleted.DynamicInvoke(JsValue.Undefined,
|
onCompleted.DynamicInvoke(JsValue.Undefined,
|
||||||
new[]
|
new[]
|
||||||
{
|
{
|
||||||
JsValue.FromObject(_scriptEngine, server),
|
JsValue.FromObject(_scriptEngine, server),
|
||||||
JsValue.FromObject(_scriptEngine, dvarName),
|
JsValue.FromObject(_scriptEngine, dvarName),
|
||||||
JsValue.FromObject(_scriptEngine, dvarValue),
|
JsValue.FromObject(_scriptEngine, value),
|
||||||
JsValue.FromObject(_scriptEngine, success)
|
JsValue.FromObject(_scriptEngine, success)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if (_onProcessing.CurrentCount == 0)
|
if (_onProcessing.CurrentCount == 0 && shouldRelease)
|
||||||
{
|
{
|
||||||
_onProcessing.Release();
|
_onProcessing.Release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}, tokenSource.Token);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BeginSetDvar(Server server, string dvarName, string dvarValue, Delegate onCompleted)
|
||||||
|
{
|
||||||
|
var tokenSource = new CancellationTokenSource();
|
||||||
|
tokenSource.CancelAfter(TimeSpan.FromSeconds(15));
|
||||||
|
|
||||||
|
server.BeginSetDvar(dvarName, dvarValue, result =>
|
||||||
|
{
|
||||||
|
var shouldRelease = false;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_onProcessing.Wait(tokenSource.Token);
|
||||||
|
shouldRelease = true;
|
||||||
|
var success = (bool)result.AsyncState;
|
||||||
|
|
||||||
|
onCompleted.DynamicInvoke(JsValue.Undefined,
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
JsValue.FromObject(_scriptEngine, server),
|
||||||
|
JsValue.FromObject(_scriptEngine, dvarName),
|
||||||
|
JsValue.FromObject(_scriptEngine, dvarValue),
|
||||||
|
JsValue.FromObject(_scriptEngine, success)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (_onProcessing.CurrentCount == 0 && shouldRelease)
|
||||||
|
{
|
||||||
|
_onProcessing.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, tokenSource.Token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ using System.Text.RegularExpressions;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Data.Models;
|
using Data.Models;
|
||||||
|
using IW4MAdmin.Application.Misc;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using static SharedLibraryCore.Server;
|
using static SharedLibraryCore.Server;
|
||||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||||
@ -80,6 +81,7 @@ namespace IW4MAdmin.Application.RConParsers
|
|||||||
|
|
||||||
public async Task<string[]> ExecuteCommandAsync(IRConConnection connection, string command, CancellationToken token = default)
|
public async Task<string[]> ExecuteCommandAsync(IRConConnection connection, string command, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
|
command = command.FormatMessageForEngine(Configuration?.ColorCodeMapping);
|
||||||
var response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, command, token);
|
var response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, command, token);
|
||||||
return response.Where(item => item != Configuration.CommandPrefixes.RConResponse).ToArray();
|
return response.Where(item => item != Configuration.CommandPrefixes.RConResponse).ToArray();
|
||||||
}
|
}
|
||||||
@ -140,6 +142,30 @@ namespace IW4MAdmin.Application.RConParsers
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void BeginGetDvar(IRConConnection connection, string dvarName, AsyncCallback callback, CancellationToken token = default)
|
||||||
|
{
|
||||||
|
GetDvarAsync<string>(connection, dvarName, token: token).ContinueWith(action =>
|
||||||
|
{
|
||||||
|
if (action.Exception is null)
|
||||||
|
{
|
||||||
|
callback?.Invoke(new AsyncResult
|
||||||
|
{
|
||||||
|
IsCompleted = true,
|
||||||
|
AsyncState = (true, action.Result.Value)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
callback?.Invoke(new AsyncResult
|
||||||
|
{
|
||||||
|
IsCompleted = true,
|
||||||
|
AsyncState = (false, (string)null)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, token);
|
||||||
|
}
|
||||||
|
|
||||||
public virtual async Task<IStatusResponse> GetStatusAsync(IRConConnection connection, CancellationToken token = default)
|
public virtual async Task<IStatusResponse> GetStatusAsync(IRConConnection connection, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
var response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND_STATUS, "status", token);
|
var response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND_STATUS, "status", token);
|
||||||
@ -195,6 +221,31 @@ namespace IW4MAdmin.Application.RConParsers
|
|||||||
return (await connection.SendQueryAsync(StaticHelpers.QueryType.SET_DVAR, dvarString, token)).Length > 0;
|
return (await connection.SendQueryAsync(StaticHelpers.QueryType.SET_DVAR, dvarString, token)).Length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void BeginSetDvar(IRConConnection connection, string dvarName, object dvarValue, AsyncCallback callback,
|
||||||
|
CancellationToken token = default)
|
||||||
|
{
|
||||||
|
SetDvarAsync(connection, dvarName, dvarValue, token).ContinueWith(action =>
|
||||||
|
{
|
||||||
|
if (action.Exception is null)
|
||||||
|
{
|
||||||
|
callback?.Invoke(new AsyncResult
|
||||||
|
{
|
||||||
|
IsCompleted = true,
|
||||||
|
AsyncState = true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
callback?.Invoke(new AsyncResult
|
||||||
|
{
|
||||||
|
IsCompleted = true,
|
||||||
|
AsyncState = false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, token);
|
||||||
|
}
|
||||||
|
|
||||||
private List<EFClient> ClientsFromStatus(string[] Status)
|
private List<EFClient> ClientsFromStatus(string[] Status)
|
||||||
{
|
{
|
||||||
List<EFClient> StatusPlayers = new List<EFClient>();
|
List<EFClient> StatusPlayers = new List<EFClient>();
|
||||||
|
BIN
Application/Resources/GeoLite2-Country.mmdb
Normal file
BIN
Application/Resources/GeoLite2-Country.mmdb
Normal file
Binary file not shown.
1636
Data/Migrations/MySql/20220422202702_AddGameToEFClient.Designer.cs
generated
Normal file
1636
Data/Migrations/MySql/20220422202702_AddGameToEFClient.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
25
Data/Migrations/MySql/20220422202702_AddGameToEFClient.cs
Normal file
25
Data/Migrations/MySql/20220422202702_AddGameToEFClient.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Data.Migrations.MySql
|
||||||
|
{
|
||||||
|
public partial class AddGameToEFClient : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "GameName",
|
||||||
|
table: "EFClients",
|
||||||
|
type: "int",
|
||||||
|
nullable: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "GameName",
|
||||||
|
table: "EFClients");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -64,6 +64,9 @@ namespace Data.Migrations.MySql
|
|||||||
b.Property<DateTime>("FirstConnection")
|
b.Property<DateTime>("FirstConnection")
|
||||||
.HasColumnType("datetime(6)");
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<int?>("GameName")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
b.Property<DateTime>("LastConnection")
|
b.Property<DateTime>("LastConnection")
|
||||||
.HasColumnType("datetime(6)");
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
1693
Data/Migrations/Postgresql/20220422203121_AddGameToEFClient.Designer.cs
generated
Normal file
1693
Data/Migrations/Postgresql/20220422203121_AddGameToEFClient.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,25 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Data.Migrations.Postgresql
|
||||||
|
{
|
||||||
|
public partial class AddGameToEFClient : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "GameName",
|
||||||
|
table: "EFClients",
|
||||||
|
type: "integer",
|
||||||
|
nullable: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "GameName",
|
||||||
|
table: "EFClients");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -71,6 +71,9 @@ namespace Data.Migrations.Postgresql
|
|||||||
b.Property<DateTime>("FirstConnection")
|
b.Property<DateTime>("FirstConnection")
|
||||||
.HasColumnType("timestamp without time zone");
|
.HasColumnType("timestamp without time zone");
|
||||||
|
|
||||||
|
b.Property<int?>("GameName")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
b.Property<DateTime>("LastConnection")
|
b.Property<DateTime>("LastConnection")
|
||||||
.HasColumnType("timestamp without time zone");
|
.HasColumnType("timestamp without time zone");
|
||||||
|
|
||||||
|
1634
Data/Migrations/Sqlite/20220422202315_AddGameToEFClient.Designer.cs
generated
Normal file
1634
Data/Migrations/Sqlite/20220422202315_AddGameToEFClient.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
25
Data/Migrations/Sqlite/20220422202315_AddGameToEFClient.cs
Normal file
25
Data/Migrations/Sqlite/20220422202315_AddGameToEFClient.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Data.Migrations.Sqlite
|
||||||
|
{
|
||||||
|
public partial class AddGameToEFClient : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "GameName",
|
||||||
|
table: "EFClients",
|
||||||
|
type: "INTEGER",
|
||||||
|
nullable: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "GameName",
|
||||||
|
table: "EFClients");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -62,6 +62,9 @@ namespace Data.Migrations.Sqlite
|
|||||||
b.Property<DateTime>("FirstConnection")
|
b.Property<DateTime>("FirstConnection")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int?>("GameName")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<DateTime>("LastConnection")
|
b.Property<DateTime>("LastConnection")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
@ -805,7 +808,7 @@ namespace Data.Migrations.Sqlite
|
|||||||
b.Property<string>("SearchableIPAddress")
|
b.Property<string>("SearchableIPAddress")
|
||||||
.ValueGeneratedOnAddOrUpdate()
|
.ValueGeneratedOnAddOrUpdate()
|
||||||
.HasColumnType("TEXT")
|
.HasColumnType("TEXT")
|
||||||
.HasComputedColumnSql("((IPAddress & 255) || '.' || ((IPAddress >> 8) & 255)) || '.' || ((IPAddress >> 16) & 255) || '.' || ((IPAddress >> 24) & 255)");
|
.HasComputedColumnSql("((IPAddress & 255) || '.' || ((IPAddress >> 8) & 255)) || '.' || ((IPAddress >> 16) & 255) || '.' || ((IPAddress >> 24) & 255)", true);
|
||||||
|
|
||||||
b.Property<string>("SearchableName")
|
b.Property<string>("SearchableName")
|
||||||
.HasMaxLength(24)
|
.HasMaxLength(24)
|
||||||
|
@ -63,6 +63,7 @@ namespace Data.Models.Client
|
|||||||
public DateTime FirstConnection { get; set; }
|
public DateTime FirstConnection { get; set; }
|
||||||
[Required]
|
[Required]
|
||||||
public DateTime LastConnection { get; set; }
|
public DateTime LastConnection { get; set; }
|
||||||
|
public Reference.Game? GameName { get; set; } = Reference.Game.UKN;
|
||||||
public bool Masked { get; set; }
|
public bool Masked { get; set; }
|
||||||
[Required]
|
[Required]
|
||||||
public int AliasLinkId { get; set; }
|
public int AliasLinkId { get; set; }
|
||||||
|
@ -53,7 +53,9 @@ steps:
|
|||||||
script: |
|
script: |
|
||||||
Write-Host 'Build Configuration is $(buildConfiguration), Release Type is $(releaseType)'
|
Write-Host 'Build Configuration is $(buildConfiguration), Release Type is $(releaseType)'
|
||||||
md -Force lib\open-iconic\font\css
|
md -Force lib\open-iconic\font\css
|
||||||
wget https://raw.githubusercontent.com/iconic/open-iconic/master/font/css/open-iconic-bootstrap.scss -o lib\open-iconic\font\css\open-iconic-bootstrap.scss
|
wget https://raw.githubusercontent.com/iconic/open-iconic/master/font/css/open-iconic-bootstrap.scss -o lib\open-iconic\font\css\open-iconic-bootstrap-override.scss
|
||||||
|
cd lib\open-iconic\font\css
|
||||||
|
(Get-Content open-iconic-bootstrap-override.scss).replace('../fonts/', '/font/') | Set-Content open-iconic-bootstrap-override.scss
|
||||||
failOnStderr: true
|
failOnStderr: true
|
||||||
workingDirectory: '$(Build.Repository.LocalPath)\WebfrontCore\wwwroot'
|
workingDirectory: '$(Build.Repository.LocalPath)\WebfrontCore\wwwroot'
|
||||||
|
|
||||||
@ -75,6 +77,7 @@ steps:
|
|||||||
Write-Host 'Unzipping download'
|
Write-Host 'Unzipping download'
|
||||||
Expand-Archive -LiteralPath $(Build.Repository.LocalPath)\dotnet-bundle.zip -DestinationPath $(Build.Repository.LocalPath)
|
Expand-Archive -LiteralPath $(Build.Repository.LocalPath)\dotnet-bundle.zip -DestinationPath $(Build.Repository.LocalPath)
|
||||||
Write-Host 'Executing dotnet-bundle'
|
Write-Host 'Executing dotnet-bundle'
|
||||||
|
$(Build.Repository.LocalPath)\dotnet-bundle.exe clean $(Build.Repository.LocalPath)\WebfrontCore\bundleconfig.json
|
||||||
$(Build.Repository.LocalPath)\dotnet-bundle.exe $(Build.Repository.LocalPath)\WebfrontCore\bundleconfig.json
|
$(Build.Repository.LocalPath)\dotnet-bundle.exe $(Build.Repository.LocalPath)\WebfrontCore\bundleconfig.json
|
||||||
failOnStderr: true
|
failOnStderr: true
|
||||||
workingDirectory: '$(Build.Repository.LocalPath)\WebfrontCore'
|
workingDirectory: '$(Build.Repository.LocalPath)\WebfrontCore'
|
||||||
|
16
GameFiles/README.MD
Normal file
16
GameFiles/README.MD
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# Game Interface
|
||||||
|
|
||||||
|
Allows integration of IW4M-Admin to GSC, mainly used for special commands that need to use GSC in order to work.
|
||||||
|
But can also be used to read / write metadata from / to a profile and to get the player permission level.
|
||||||
|
|
||||||
|
|
||||||
|
## Installation Plutonium IW5
|
||||||
|
|
||||||
|
|
||||||
|
Move `_integration.gsc` to `%localappdata%\Plutonium\storage\iw5\scripts\`
|
||||||
|
|
||||||
|
|
||||||
|
## Installation IW4x
|
||||||
|
|
||||||
|
|
||||||
|
Move `_integration.gsc` to `IW4x/userraw/scripts`, `IW4x` being the root folder of your game server.
|
@ -1,7 +1,6 @@
|
|||||||
#include common_scripts\utility;
|
#include common_scripts\utility;
|
||||||
#include maps\mp\_utility;
|
#include maps\mp\_utility;
|
||||||
#include maps\mp\gametypes\_hud_util;
|
#include maps\mp\gametypes\_hud_util;
|
||||||
#include maps\mp\gametypes\_playerlogic;
|
|
||||||
|
|
||||||
init()
|
init()
|
||||||
{
|
{
|
||||||
@ -12,6 +11,7 @@ init()
|
|||||||
level.eventBus.failKey = "fail";
|
level.eventBus.failKey = "fail";
|
||||||
level.eventBus.timeoutKey = "timeout";
|
level.eventBus.timeoutKey = "timeout";
|
||||||
level.eventBus.timeout = 30;
|
level.eventBus.timeout = 30;
|
||||||
|
level.eventBus.gamename = getDvar( "gamename" ); // We want to do a few small detail different on IW5 compared to IW4, nothing where 2 files would make sense.
|
||||||
|
|
||||||
level.clientDataKey = "clientData";
|
level.clientDataKey = "clientData";
|
||||||
|
|
||||||
@ -22,6 +22,8 @@ init()
|
|||||||
level.eventTypes.setClientDataRequested = "SetClientDataRequested";
|
level.eventTypes.setClientDataRequested = "SetClientDataRequested";
|
||||||
level.eventTypes.setClientDataCompleted = "SetClientDataCompleted";
|
level.eventTypes.setClientDataCompleted = "SetClientDataCompleted";
|
||||||
level.eventTypes.executeCommandRequested = "ExecuteCommandRequested";
|
level.eventTypes.executeCommandRequested = "ExecuteCommandRequested";
|
||||||
|
|
||||||
|
level.iw4adminIntegrationDebug = false;
|
||||||
|
|
||||||
SetDvarIfUninitialized( level.eventBus.inVar, "" );
|
SetDvarIfUninitialized( level.eventBus.inVar, "" );
|
||||||
SetDvarIfUninitialized( level.eventBus.outVar, "" );
|
SetDvarIfUninitialized( level.eventBus.outVar, "" );
|
||||||
@ -33,18 +35,26 @@ init()
|
|||||||
level.eventCallbacks[level.eventTypes.clientDataReceived] = ::OnClientDataReceived;
|
level.eventCallbacks[level.eventTypes.clientDataReceived] = ::OnClientDataReceived;
|
||||||
level.eventCallbacks[level.eventTypes.executeCommandRequested] = ::OnExecuteCommand;
|
level.eventCallbacks[level.eventTypes.executeCommandRequested] = ::OnExecuteCommand;
|
||||||
level.eventCallbacks[level.eventTypes.setClientDataCompleted] = ::OnSetClientDataCompleted;
|
level.eventCallbacks[level.eventTypes.setClientDataCompleted] = ::OnSetClientDataCompleted;
|
||||||
|
|
||||||
|
level.clientCommandCallbacks = [];
|
||||||
|
level.clientCommandRusAsTarget = [];
|
||||||
|
|
||||||
if ( GetDvarInt( "sv_iw4madmin_integration_enabled" ) != 1 )
|
if ( GetDvarInt( "sv_iw4madmin_integration_enabled" ) != 1 )
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
InitializeGameMethods();
|
||||||
|
RegisterClientCommands();
|
||||||
|
|
||||||
// start long running tasks
|
// start long running tasks
|
||||||
level thread MonitorClientEvents();
|
level thread MonitorClientEvents();
|
||||||
level thread MonitorBus();
|
level thread MonitorBus();
|
||||||
level thread OnPlayerConnect();
|
level thread OnPlayerConnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//////////////////////////////////
|
//////////////////////////////////
|
||||||
// Client Methods
|
// Client Methods
|
||||||
//////////////////////////////////
|
//////////////////////////////////
|
||||||
@ -59,6 +69,12 @@ OnPlayerConnect()
|
|||||||
|
|
||||||
level.iw4adminIntegrationDebug = GetDvarInt( "sv_iw4madmin_integration_debug" );
|
level.iw4adminIntegrationDebug = GetDvarInt( "sv_iw4madmin_integration_debug" );
|
||||||
|
|
||||||
|
if ( isDefined(player.pers["isBot"]) && player.pers["isBot"] )
|
||||||
|
{
|
||||||
|
// we don't want to track bots
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if ( !isDefined( player.pers[level.clientDataKey] ) )
|
if ( !isDefined( player.pers[level.clientDataKey] ) )
|
||||||
{
|
{
|
||||||
player.pers[level.clientDataKey] = spawnstruct();
|
player.pers[level.clientDataKey] = spawnstruct();
|
||||||
@ -101,26 +117,26 @@ OnPlayerDisconnect()
|
|||||||
|
|
||||||
OnPlayerJoinedTeam()
|
OnPlayerJoinedTeam()
|
||||||
{
|
{
|
||||||
self endon( "disconnect" );
|
self endon( "disconnect" );
|
||||||
|
|
||||||
for( ;; )
|
for( ;; )
|
||||||
{
|
{
|
||||||
self waittill( "joined_team" );
|
self waittill( "joined_team" );
|
||||||
// join spec and join team occur at the same moment - out of order logging would be problematic
|
// join spec and join team occur at the same moment - out of order logging would be problematic
|
||||||
wait( 0.25 );
|
wait( 0.25 );
|
||||||
LogPrint( GenerateJoinTeamString( false ) );
|
LogPrint( GenerateJoinTeamString( false ) );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
OnPlayerJoinedSpectators()
|
OnPlayerJoinedSpectators()
|
||||||
{
|
{
|
||||||
self endon( "disconnect" );
|
self endon( "disconnect" );
|
||||||
|
|
||||||
for( ;; )
|
for( ;; )
|
||||||
{
|
{
|
||||||
self waittill( "joined_spectators" );
|
self waittill( "joined_spectators" );
|
||||||
LogPrint( GenerateJoinTeamString( true ) );
|
LogPrint( GenerateJoinTeamString( true ) );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
OnGameEnded()
|
OnGameEnded()
|
||||||
@ -201,7 +217,7 @@ MonitorClientEvents()
|
|||||||
|
|
||||||
if ( level.iw4adminIntegrationDebug == 1 )
|
if ( level.iw4adminIntegrationDebug == 1 )
|
||||||
{
|
{
|
||||||
self IPrintLn( "Processing Event " + client.event.type + "-" + client.event.subtype );
|
IPrintLn( "Processing Event " + client.event.type + "-" + client.event.subtype );
|
||||||
}
|
}
|
||||||
|
|
||||||
eventHandler = level.eventCallbacks[client.event.type];
|
eventHandler = level.eventCallbacks[client.event.type];
|
||||||
@ -219,6 +235,53 @@ MonitorClientEvents()
|
|||||||
// Helper Methods
|
// Helper Methods
|
||||||
//////////////////////////////////
|
//////////////////////////////////
|
||||||
|
|
||||||
|
RegisterClientCommands()
|
||||||
|
{
|
||||||
|
AddClientCommand( "GiveWeapon", true, ::GiveWeaponImpl );
|
||||||
|
AddClientCommand( "TakeWeapons", true, ::TakeWeaponsImpl );
|
||||||
|
AddClientCommand( "SwitchTeams", true, ::TeamSwitchImpl );
|
||||||
|
AddClientCommand( "Hide", false, ::HideImpl );
|
||||||
|
AddClientCommand( "Unhide", false, ::UnhideImpl );
|
||||||
|
AddClientCommand( "Alert", true, ::AlertImpl );
|
||||||
|
AddClientCommand( "Goto", false, ::GotoImpl );
|
||||||
|
AddClientCommand( "Kill", true, ::KillImpl );
|
||||||
|
AddClientCommand( "SetSpectator", true, ::SetSpectatorImpl );
|
||||||
|
AddClientCommand( "NightMode", false, ::NightModeImpl ); //This really should be a level command
|
||||||
|
AddClientCommand( "LockControls", true, ::LockControlsImpl );
|
||||||
|
AddClientCommand( "UnlockControls", true, ::UnlockControlsImpl );
|
||||||
|
AddClientCommand( "PlayerToMe", true, ::PlayerToMeImpl );
|
||||||
|
AddClientCommand( "NoClip", false, ::NoClipImpl );
|
||||||
|
AddClientCommand( "NoClipOff", false, ::NoClipOffImpl );
|
||||||
|
}
|
||||||
|
|
||||||
|
InitializeGameMethods()
|
||||||
|
{
|
||||||
|
level.overrideMethods = [];
|
||||||
|
level.overrideMethods["god"] = ::_god;
|
||||||
|
level.overrideMethods["noclip"] = ::UnsupportedFunc;
|
||||||
|
|
||||||
|
if ( isDefined( ::God ) )
|
||||||
|
{
|
||||||
|
level.overrideMethods["god"] = ::God;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( isDefined( ::NoClip ) )
|
||||||
|
{
|
||||||
|
level.overrideMethods["noclip"] = ::NoClip;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( level.eventBus.gamename == "IW5" )
|
||||||
|
{ //PlutoIW5 only allows Godmode and NoClip if cheats are on..
|
||||||
|
level.overrideMethods["god"] = ::IW5_God;
|
||||||
|
level.overrideMethods["noclip"] = ::IW5_NoClip;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UnsupportedFunc()
|
||||||
|
{
|
||||||
|
self IPrintLnBold( "Function is not supported!" );
|
||||||
|
}
|
||||||
|
|
||||||
RequestClientMeta( metaKey )
|
RequestClientMeta( metaKey )
|
||||||
{
|
{
|
||||||
getClientMetaEvent = BuildEventRequest( true, level.eventTypes.clientDataRequested, "Meta", self, metaKey );
|
getClientMetaEvent = BuildEventRequest( true, level.eventTypes.clientDataRequested, "Meta", self, metaKey );
|
||||||
@ -482,14 +545,65 @@ NotifyClientEvent( eventInfo )
|
|||||||
if ( level.iw4adminIntegrationDebug == 1 )
|
if ( level.iw4adminIntegrationDebug == 1 )
|
||||||
{
|
{
|
||||||
IPrintLn( "NotifyClientEvent->" + event.data );
|
IPrintLn( "NotifyClientEvent->" + event.data );
|
||||||
|
if( int( eventInfo[3] ) != -1 && !isDefined( origin ) )
|
||||||
|
{
|
||||||
|
IPrintLn( "origin is null but the slot id is " + int( eventInfo[3] ) );
|
||||||
|
}
|
||||||
|
if( int( eventInfo[4] ) != -1 && !isDefined( target ) )
|
||||||
|
{
|
||||||
|
IPrintLn( "target is null but the slot id is " + int( eventInfo[4] ) );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
client = event.origin;
|
if( isDefined( target ) )
|
||||||
|
{
|
||||||
|
client = event.target;
|
||||||
|
}
|
||||||
|
else if( isDefined( origin ) )
|
||||||
|
{
|
||||||
|
client = event.origin;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if ( level.iw4adminIntegrationDebug == 1 )
|
||||||
|
{
|
||||||
|
IPrintLn( "Neither origin or target are set but we are a Client Event, aborting" );
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
client.event = event;
|
client.event = event;
|
||||||
|
|
||||||
level notify( level.eventTypes.localClientEvent, client );
|
level notify( level.eventTypes.localClientEvent, client );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GetPlayerFromClientNum( clientNum )
|
||||||
|
{
|
||||||
|
if ( clientNum < 0 )
|
||||||
|
return undefined;
|
||||||
|
|
||||||
|
for ( i = 0; i < level.players.size; i++ )
|
||||||
|
{
|
||||||
|
if ( level.players[i] getEntityNumber() == clientNum )
|
||||||
|
{
|
||||||
|
return level.players[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
AddClientCommand( commandName, shouldRunAsTarget, callback, shouldOverwrite )
|
||||||
|
{
|
||||||
|
if ( isDefined( level.clientCommandCallbacks[commandName] ) && isDefined( shouldOverwrite ) && !shouldOverwrite ) {
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
level.clientCommandCallbacks[commandName] = callback;
|
||||||
|
level.clientCommandRusAsTarget[commandName] = shouldRunAsTarget == true; //might speed up things later in case someone gives us a string or number instead of a boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//////////////////////////////////
|
//////////////////////////////////
|
||||||
// Event Handlers
|
// Event Handlers
|
||||||
/////////////////////////////////
|
/////////////////////////////////
|
||||||
@ -536,45 +650,18 @@ OnExecuteCommand( event )
|
|||||||
data = ParseDataString( event.data );
|
data = ParseDataString( event.data );
|
||||||
response = "";
|
response = "";
|
||||||
|
|
||||||
switch ( event.subtype )
|
command = level.clientCommandCallbacks[event.subtype];
|
||||||
|
runAsTarget = level.clientCommandRusAsTarget[event.subtype];
|
||||||
|
executionContextEntity = event.origin;
|
||||||
|
if ( runAsTarget ) {
|
||||||
|
executionContextEntity = event.target;
|
||||||
|
}
|
||||||
|
if ( isDefined( command ) ) {
|
||||||
|
response = executionContextEntity [[command]]( event, data );
|
||||||
|
}
|
||||||
|
else if ( level.iw4adminIntegrationDebug == 1 )
|
||||||
{
|
{
|
||||||
case "GiveWeapon":
|
IPrintLn( "Unkown Client command->" + event.subtype);
|
||||||
response = event.target GiveWeaponImpl( data );
|
|
||||||
break;
|
|
||||||
case "TakeWeapons":
|
|
||||||
response = event.target TakeWeaponsImpl();
|
|
||||||
break;
|
|
||||||
case "SwitchTeams":
|
|
||||||
response = event.target TeamSwitchImpl();
|
|
||||||
break;
|
|
||||||
case "Hide":
|
|
||||||
response = self HideImpl();
|
|
||||||
break;
|
|
||||||
case "Unhide":
|
|
||||||
response = self UnhideImpl();
|
|
||||||
break;
|
|
||||||
case "Alert":
|
|
||||||
response = event.target AlertImpl( data );
|
|
||||||
break;
|
|
||||||
case "Goto":
|
|
||||||
if ( IsDefined( event.target ) )
|
|
||||||
{
|
|
||||||
response = self GotoPlayerImpl( event.target );
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
response = self GotoImpl( data );
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "Kill":
|
|
||||||
response = event.target KillImpl();
|
|
||||||
break;
|
|
||||||
case "NightMode":
|
|
||||||
NightModeImpl();
|
|
||||||
break;
|
|
||||||
case "SetSpectator":
|
|
||||||
response = event.target SetSpectatorImpl();
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// send back the response to the origin, but only if they're not the target
|
// send back the response to the origin, but only if they're not the target
|
||||||
@ -597,7 +684,7 @@ OnSetClientDataCompleted( event )
|
|||||||
// Command Implementations
|
// Command Implementations
|
||||||
/////////////////////////////////
|
/////////////////////////////////
|
||||||
|
|
||||||
GiveWeaponImpl( data )
|
GiveWeaponImpl( event, data )
|
||||||
{
|
{
|
||||||
if ( !IsAlive( self ) )
|
if ( !IsAlive( self ) )
|
||||||
{
|
{
|
||||||
@ -628,7 +715,7 @@ TeamSwitchImpl()
|
|||||||
{
|
{
|
||||||
if ( !IsAlive( self ) )
|
if ( !IsAlive( self ) )
|
||||||
{
|
{
|
||||||
return self.name + "^7 is not alive";
|
return self + "^7 is not alive";
|
||||||
}
|
}
|
||||||
|
|
||||||
team = level.allies;
|
team = level.allies;
|
||||||
@ -645,6 +732,93 @@ TeamSwitchImpl()
|
|||||||
return self.name + "^7 switched to " + self.team;
|
return self.name + "^7 switched to " + self.team;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LockControlsImpl()
|
||||||
|
{
|
||||||
|
if ( !IsAlive( self ) )
|
||||||
|
{
|
||||||
|
return self.name + "^7 is not alive";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
self freezeControls( true );
|
||||||
|
self call [[level.overrideMethods["god"]]]( true );
|
||||||
|
self Hide();
|
||||||
|
|
||||||
|
info = [];
|
||||||
|
info[ "alertType" ] = "Alert!";
|
||||||
|
info[ "message" ] = "You have been frozen!";
|
||||||
|
|
||||||
|
self AlertImpl( undefined, info );
|
||||||
|
|
||||||
|
return self.name + "\'s controls are locked";
|
||||||
|
}
|
||||||
|
|
||||||
|
UnlockControlsImpl()
|
||||||
|
{
|
||||||
|
if ( !IsAlive( self ) )
|
||||||
|
{
|
||||||
|
return self.name + "^7 is not alive";
|
||||||
|
}
|
||||||
|
|
||||||
|
self freezeControls( false );
|
||||||
|
self call [[level.overrideMethods["god"]]]( false );
|
||||||
|
self Show();
|
||||||
|
|
||||||
|
return self.name + "\'s controls are unlocked";
|
||||||
|
}
|
||||||
|
|
||||||
|
NoClipImpl()
|
||||||
|
{
|
||||||
|
if ( !IsAlive( self ) )
|
||||||
|
{
|
||||||
|
self IPrintLnBold( "You are not alive" );
|
||||||
|
// Due to bug when sometimes disabling noclip game thinks you're dead
|
||||||
|
// removing the return and allowing them to go back into noclip is probably better.
|
||||||
|
//return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self SetClientDvar( "sv_cheats", 1 );
|
||||||
|
self SetClientDvar( "cg_thirdperson", 1 );
|
||||||
|
self SetClientDvar( "sv_cheats", 0 );
|
||||||
|
|
||||||
|
self call [[level.overrideMethods["god"]]]( true );
|
||||||
|
self call [[level.overrideMethods["noclip"]]]( true );
|
||||||
|
self Hide();
|
||||||
|
|
||||||
|
self.isNoClipped = true;
|
||||||
|
|
||||||
|
self IPrintLnBold( "NoClip enabled" );
|
||||||
|
}
|
||||||
|
|
||||||
|
NoClipOffImpl()
|
||||||
|
{
|
||||||
|
if ( !IsDefined( self.isNoClipped ) || !self.isNoClipped )
|
||||||
|
{
|
||||||
|
self IPrintLnBold( "You are not no-clipped" );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self SetClientDvar( "sv_cheats", 1 );
|
||||||
|
self SetClientDvar( "cg_thirdperson", 0 );
|
||||||
|
self SetClientDvar( "sv_cheats", 0 );
|
||||||
|
|
||||||
|
self call [[level.overrideMethods["god"]]]( false );
|
||||||
|
self call [[level.overrideMethods["noclip"]]]( false );
|
||||||
|
self Show();
|
||||||
|
|
||||||
|
self IPrintLnBold( "NoClip disabled" );
|
||||||
|
|
||||||
|
if ( !IsAlive( self ) && self.isNoClipped )
|
||||||
|
{
|
||||||
|
// Sometimes you will bug exiting noclip where the game thinks you're dead
|
||||||
|
// but you're not. You will retain godmode but be able to run around and kill people.
|
||||||
|
// So, it's important to let the user know.
|
||||||
|
self IPrintLnBold( "^1You are bugged! ^4Swap team." );
|
||||||
|
}
|
||||||
|
|
||||||
|
self.isNoClipped = false;
|
||||||
|
}
|
||||||
|
|
||||||
HideImpl()
|
HideImpl()
|
||||||
{
|
{
|
||||||
if ( !IsAlive( self ) )
|
if ( !IsAlive( self ) )
|
||||||
@ -657,18 +831,17 @@ HideImpl()
|
|||||||
self SetClientDvar( "cg_thirdperson", 1 );
|
self SetClientDvar( "cg_thirdperson", 1 );
|
||||||
self SetClientDvar( "sv_cheats", 0 );
|
self SetClientDvar( "sv_cheats", 0 );
|
||||||
|
|
||||||
if ( !IsDefined( self.savedHealth ) || self.health < 1000 )
|
if ( !IsDefined( self.savedHealth ) || self.health < 1000 )
|
||||||
{
|
{
|
||||||
self.savedHealth = self.health;
|
self.savedHealth = self.health;
|
||||||
self.savedMaxHealth = self.maxhealth;
|
self.savedMaxHealth = self.maxhealth;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.maxhealth = 99999;
|
self call [[level.overrideMethods["god"]]]( true );
|
||||||
self.health = 99999;
|
|
||||||
self.isHidden = true;
|
|
||||||
|
|
||||||
self Hide();
|
self Hide();
|
||||||
|
|
||||||
|
self.isHidden = true;
|
||||||
|
|
||||||
self IPrintLnBold( "You are now ^5hidden ^7from other players" );
|
self IPrintLnBold( "You are now ^5hidden ^7from other players" );
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -690,21 +863,38 @@ UnhideImpl()
|
|||||||
self SetClientDvar( "cg_thirdperson", 0 );
|
self SetClientDvar( "cg_thirdperson", 0 );
|
||||||
self SetClientDvar( "sv_cheats", 0 );
|
self SetClientDvar( "sv_cheats", 0 );
|
||||||
|
|
||||||
self.health = self.savedHealth;
|
self call [[level.overrideMethods["god"]]]( false );
|
||||||
self.maxhealth = self.savedMaxHealth;
|
|
||||||
self.isHidden = false;
|
|
||||||
|
|
||||||
self Show();
|
self Show();
|
||||||
|
|
||||||
|
self.isHidden = false;
|
||||||
|
|
||||||
self IPrintLnBold( "You are now ^5visible ^7to other players" );
|
self IPrintLnBold( "You are now ^5visible ^7to other players" );
|
||||||
}
|
}
|
||||||
|
|
||||||
AlertImpl( data )
|
AlertImpl( event, data )
|
||||||
{
|
{
|
||||||
self thread maps\mp\gametypes\_hud_message::oldNotifyMessage( data["alertType"], data["message"], "compass_waypoint_target", ( 1, 0, 0 ), "ui_mp_nukebomb_timer", 7.5 );
|
if ( level.eventBus.gamename == "IW4" ) {
|
||||||
|
self thread maps\mp\gametypes\_hud_message::oldNotifyMessage( data["alertType"], data["message"], "compass_waypoint_target", ( 1, 0, 0 ), "ui_mp_nukebomb_timer", 7.5 );
|
||||||
|
}
|
||||||
|
if ( level.eventBus.gamename == "IW5" ) { //IW5's notification are a bit different...
|
||||||
|
self thread maps\mp\gametypes\_hud_message::oldNotifyMessage( data["alertType"], data["message"], undefined, ( 1, 0, 0 ), "ui_mp_nukebomb_timer", 7.5 );
|
||||||
|
}
|
||||||
return "Sent alert to " + self.name;
|
return "Sent alert to " + self.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
GotoImpl( data )
|
GotoImpl( event, data )
|
||||||
|
{
|
||||||
|
if ( IsDefined( event.target ) )
|
||||||
|
{
|
||||||
|
return self GotoPlayerImpl( event.target );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return self GotoCoordImpl( data );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GotoCoordImpl( data )
|
||||||
{
|
{
|
||||||
if ( !IsAlive( self ) )
|
if ( !IsAlive( self ) )
|
||||||
{
|
{
|
||||||
@ -729,6 +919,18 @@ GotoPlayerImpl( target )
|
|||||||
self IPrintLnBold( "Moved to " + target.name );
|
self IPrintLnBold( "Moved to " + target.name );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PlayerToMeImpl( event )
|
||||||
|
{
|
||||||
|
if ( !IsAlive( self ) )
|
||||||
|
{
|
||||||
|
return self.name + " is not alive";
|
||||||
|
}
|
||||||
|
|
||||||
|
self SetOrigin( event.origin GetOrigin() );
|
||||||
|
return "Moved here " + self.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
KillImpl()
|
KillImpl()
|
||||||
{
|
{
|
||||||
if ( !IsAlive( self ) )
|
if ( !IsAlive( self ) )
|
||||||
@ -797,3 +999,48 @@ SetSpectatorImpl()
|
|||||||
|
|
||||||
return self.name + " has been moved to spectator";
|
return self.name + " has been moved to spectator";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////
|
||||||
|
// Function Overrides
|
||||||
|
//////////////////////////////////
|
||||||
|
|
||||||
|
_god( isEnabled )
|
||||||
|
{
|
||||||
|
if ( isEnabled == true )
|
||||||
|
{
|
||||||
|
if ( !IsDefined( self.savedHealth ) || self.health < 1000 )
|
||||||
|
{
|
||||||
|
self.savedHealth = self.health;
|
||||||
|
self.savedMaxHealth = self.maxhealth;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.maxhealth = 99999;
|
||||||
|
self.health = 99999;
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if ( !IsDefined( self.savedHealth ) || !IsDefined( self.savedMaxHealth ) )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.health = self.savedHealth;
|
||||||
|
self.maxhealth = self.savedMaxHealth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
IW5_God()
|
||||||
|
{
|
||||||
|
SetDvar( "sv_cheats", 1 );
|
||||||
|
self God();
|
||||||
|
SetDvar( "sv_cheats", 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
IW5_NoClip()
|
||||||
|
{
|
||||||
|
SetDvar( "sv_cheats", 1 );
|
||||||
|
self NoClip();
|
||||||
|
SetDvar( "sv_cheats", 0 );
|
||||||
|
}
|
@ -13,7 +13,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
|||||||
version.txt = version.txt
|
version.txt = version.txt
|
||||||
DeploymentFiles\UpdateIW4MAdmin.ps1 = DeploymentFiles\UpdateIW4MAdmin.ps1
|
DeploymentFiles\UpdateIW4MAdmin.ps1 = DeploymentFiles\UpdateIW4MAdmin.ps1
|
||||||
DeploymentFiles\UpdateIW4MAdmin.sh = DeploymentFiles\UpdateIW4MAdmin.sh
|
DeploymentFiles\UpdateIW4MAdmin.sh = DeploymentFiles\UpdateIW4MAdmin.sh
|
||||||
GameFiles\IW4x\userraw\scripts\_integration.gsc = GameFiles\IW4x\userraw\scripts\_integration.gsc
|
GameFiles\_integration.gsc = GameFiles\_integration.gsc
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharedLibraryCore", "SharedLibraryCore\SharedLibraryCore.csproj", "{AA0541A2-8D51-4AD9-B0AC-3D1F5B162481}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharedLibraryCore", "SharedLibraryCore\SharedLibraryCore.csproj", "{AA0541A2-8D51-4AD9-B0AC-3D1F5B162481}"
|
||||||
@ -52,6 +52,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ScriptPlugins", "ScriptPlug
|
|||||||
Plugins\ScriptPlugins\ParserPlutoniumT4COZM.js = Plugins\ScriptPlugins\ParserPlutoniumT4COZM.js
|
Plugins\ScriptPlugins\ParserPlutoniumT4COZM.js = Plugins\ScriptPlugins\ParserPlutoniumT4COZM.js
|
||||||
Plugins\ScriptPlugins\GameInterface.js = Plugins\ScriptPlugins\GameInterface.js
|
Plugins\ScriptPlugins\GameInterface.js = Plugins\ScriptPlugins\GameInterface.js
|
||||||
Plugins\ScriptPlugins\SubnetBan.js = Plugins\ScriptPlugins\SubnetBan.js
|
Plugins\ScriptPlugins\SubnetBan.js = Plugins\ScriptPlugins\SubnetBan.js
|
||||||
|
Plugins\ScriptPlugins\BanBroadcasting.js = Plugins\ScriptPlugins\BanBroadcasting.js
|
||||||
|
Plugins\ScriptPlugins\ParserH1MOD.js = Plugins\ScriptPlugins\ParserH1MOD.js
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutomessageFeed", "Plugins\AutomessageFeed\AutomessageFeed.csproj", "{F5815359-CFC7-44B4-9A3B-C04BACAD5836}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutomessageFeed", "Plugins\AutomessageFeed\AutomessageFeed.csproj", "{F5815359-CFC7-44B4-9A3B-C04BACAD5836}"
|
||||||
|
@ -33,7 +33,8 @@ namespace Integrations.Cod
|
|||||||
private readonly Encoding _gameEncoding;
|
private readonly Encoding _gameEncoding;
|
||||||
private readonly int _retryAttempts;
|
private readonly int _retryAttempts;
|
||||||
|
|
||||||
public CodRConConnection(IPEndPoint ipEndpoint, string password, ILogger<CodRConConnection> log, Encoding gameEncoding, int retryAttempts)
|
public CodRConConnection(IPEndPoint ipEndpoint, string password, ILogger<CodRConConnection> log,
|
||||||
|
Encoding gameEncoding, int retryAttempts)
|
||||||
{
|
{
|
||||||
RConPassword = password;
|
RConPassword = password;
|
||||||
_gameEncoding = gameEncoding;
|
_gameEncoding = gameEncoding;
|
||||||
@ -66,21 +67,31 @@ namespace Integrations.Cod
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
using (LogContext.PushProperty("Server", Endpoint.ToString()))
|
||||||
|
{
|
||||||
|
_log.LogDebug("Releasing OnComplete {Count}", ActiveQueries[Endpoint].OnComplete.CurrentCount);
|
||||||
|
}
|
||||||
|
|
||||||
if (ActiveQueries[Endpoint].OnComplete.CurrentCount == 0)
|
if (ActiveQueries[Endpoint].OnComplete.CurrentCount == 0)
|
||||||
{
|
{
|
||||||
ActiveQueries[Endpoint].OnComplete.Release();
|
ActiveQueries[Endpoint].OnComplete.Release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<string[]> SendQueryAsyncInternal(StaticHelpers.QueryType type, string parameters = "", CancellationToken token = default)
|
private async Task<string[]> SendQueryAsyncInternal(StaticHelpers.QueryType type, string parameters = "",
|
||||||
|
CancellationToken token = default)
|
||||||
{
|
{
|
||||||
if (!ActiveQueries.ContainsKey(Endpoint))
|
if (!ActiveQueries.ContainsKey(Endpoint))
|
||||||
{
|
{
|
||||||
ActiveQueries.TryAdd(Endpoint, new ConnectionState());
|
ActiveQueries.TryAdd(Endpoint, new ConnectionState());
|
||||||
}
|
}
|
||||||
|
|
||||||
var connectionState = ActiveQueries[Endpoint];
|
if (!ActiveQueries.TryGetValue(Endpoint, out var connectionState))
|
||||||
|
{
|
||||||
|
_log.LogError("Could not retrieve connection state");
|
||||||
|
throw new InvalidOperationException("Could not get connection state");
|
||||||
|
}
|
||||||
|
|
||||||
_log.LogDebug("Waiting for semaphore to be released [{Endpoint}]", Endpoint);
|
_log.LogDebug("Waiting for semaphore to be released [{Endpoint}]", Endpoint);
|
||||||
|
|
||||||
@ -91,6 +102,8 @@ namespace Integrations.Cod
|
|||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
|
_log.LogDebug("OnComplete did not complete before timeout {Count}",
|
||||||
|
connectionState.OnComplete.CurrentCount);
|
||||||
throw new RConException("Timed out waiting for access to rcon socket");
|
throw new RConException("Timed out waiting for access to rcon socket");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,16 +113,20 @@ namespace Integrations.Cod
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await Task.Delay(_config.FloodProtectInterval - (int)timeSinceLastQuery, token);
|
var delay = _config.FloodProtectInterval - (int)timeSinceLastQuery;
|
||||||
|
_log.LogDebug("Delaying for {Delay}ms", delay);
|
||||||
|
await Task.Delay(delay, token);
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
|
_log.LogDebug("Waiting for flood protect did not complete before timeout timeout {Count}",
|
||||||
|
connectionState.OnComplete.CurrentCount);
|
||||||
throw new RConException("Timed out waiting for flood protect to expire");
|
throw new RConException("Timed out waiting for flood protect to expire");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_log.LogDebug("Semaphore has been released [{Endpoint}]", Endpoint);
|
_log.LogDebug("Semaphore has been released [{Endpoint}]", Endpoint);
|
||||||
_log.LogDebug("Query {@QueryInfo}", new { endpoint=Endpoint.ToString(), type, parameters });
|
_log.LogDebug("Query {@QueryInfo}", new { endpoint = Endpoint.ToString(), type, parameters });
|
||||||
|
|
||||||
byte[] payload = null;
|
byte[] payload = null;
|
||||||
var waitForResponse = _config.WaitForResponse;
|
var waitForResponse = _config.WaitForResponse;
|
||||||
@ -163,7 +180,6 @@ namespace Integrations.Cod
|
|||||||
// e.g: emoji -> windows-1252
|
// e.g: emoji -> windows-1252
|
||||||
catch (OverflowException ex)
|
catch (OverflowException ex)
|
||||||
{
|
{
|
||||||
|
|
||||||
using (LogContext.PushProperty("Server", Endpoint.ToString()))
|
using (LogContext.PushProperty("Server", Endpoint.ToString()))
|
||||||
{
|
{
|
||||||
_log.LogError(ex, "Could not convert RCon data payload to desired encoding {Encoding} {Params}",
|
_log.LogError(ex, "Could not convert RCon data payload to desired encoding {Encoding} {Params}",
|
||||||
@ -181,8 +197,8 @@ namespace Integrations.Cod
|
|||||||
using (LogContext.PushProperty("Server", Endpoint.ToString()))
|
using (LogContext.PushProperty("Server", Endpoint.ToString()))
|
||||||
{
|
{
|
||||||
_log.LogInformation(
|
_log.LogInformation(
|
||||||
"Retrying RCon message ({ConnectionAttempts}/{AllowedConnectionFailures} attempts) with parameters {Payload}",
|
"Retrying RCon message ({ConnectionAttempts}/{AllowedConnectionFailures} attempts) with parameters {Payload}",
|
||||||
connectionState.ConnectionAttempts,
|
connectionState.ConnectionAttempts,
|
||||||
_retryAttempts, parameters);
|
_retryAttempts, parameters);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -201,6 +217,7 @@ namespace Integrations.Cod
|
|||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
|
_log.LogDebug("OnSent did not complete in time");
|
||||||
throw new RConException("Timed out waiting for access to RCon send socket");
|
throw new RConException("Timed out waiting for access to RCon send socket");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -211,14 +228,13 @@ namespace Integrations.Cod
|
|||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
throw new RConException("Timed out waiting for access to RCon receive socket");
|
_log.LogDebug("OnReceived did not complete in time");
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (connectionState.OnSentData.CurrentCount == 0)
|
if (connectionState.OnSentData.CurrentCount == 0)
|
||||||
{
|
{
|
||||||
connectionState.OnSentData.Release();
|
connectionState.OnSentData.Release();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
throw new RConException("Timed out waiting for access to RCon receive socket");
|
||||||
}
|
}
|
||||||
|
|
||||||
connectionState.SendEventArgs.UserToken = new ConnectionUserToken
|
connectionState.SendEventArgs.UserToken = new ConnectionUserToken
|
||||||
@ -242,6 +258,7 @@ namespace Integrations.Cod
|
|||||||
|
|
||||||
if ((response?.Length == 0 || response[0].Length == 0) && waitForResponse)
|
if ((response?.Length == 0 || response[0].Length == 0) && waitForResponse)
|
||||||
{
|
{
|
||||||
|
_log.LogDebug("0 bytes received from rcon request");
|
||||||
throw new RConException("Expected response but got 0 bytes back");
|
throw new RConException("Expected response but got 0 bytes back");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -252,6 +269,7 @@ namespace Integrations.Cod
|
|||||||
{
|
{
|
||||||
// if we timed out due to the cancellation token,
|
// if we timed out due to the cancellation token,
|
||||||
// we don't want to count that as an attempt
|
// we don't want to count that as an attempt
|
||||||
|
_log.LogDebug("OperationCanceledException when waiting for payload send to complete");
|
||||||
connectionState.ConnectionAttempts = 0;
|
connectionState.ConnectionAttempts = 0;
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
@ -265,7 +283,8 @@ namespace Integrations.Cod
|
|||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
return Array.Empty<string>();
|
_log.LogDebug("OperationCancelled while waiting for retry");
|
||||||
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
goto retrySend;
|
goto retrySend;
|
||||||
@ -311,11 +330,13 @@ namespace Integrations.Cod
|
|||||||
return Array.Empty<string>();
|
return Array.Empty<string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
var responseString = type == StaticHelpers.QueryType.COMMAND_STATUS ?
|
var responseString = type == StaticHelpers.QueryType.COMMAND_STATUS
|
||||||
ReassembleSegmentedStatus(response) : RecombineMessages(response);
|
? ReassembleSegmentedStatus(response)
|
||||||
|
: RecombineMessages(response);
|
||||||
|
|
||||||
// note: not all games respond if the password is wrong or not set
|
// note: not all games respond if the password is wrong or not set
|
||||||
if (responseString.Contains("Invalid password") || responseString.Contains("rconpassword"))
|
if (responseString.Contains("Invalid password", StringComparison.InvariantCultureIgnoreCase) ||
|
||||||
|
responseString.Contains("rconpassword"))
|
||||||
{
|
{
|
||||||
throw new RConException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_RCON_INVALID"]);
|
throw new RConException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_RCON_INVALID"]);
|
||||||
}
|
}
|
||||||
@ -327,11 +348,14 @@ namespace Integrations.Cod
|
|||||||
|
|
||||||
if (responseString.Contains(_config.ServerNotRunningResponse))
|
if (responseString.Contains(_config.ServerNotRunningResponse))
|
||||||
{
|
{
|
||||||
throw new ServerException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_NOT_RUNNING"].FormatExt(Endpoint.ToString()));
|
throw new ServerException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_NOT_RUNNING"]
|
||||||
|
.FormatExt(Endpoint.ToString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
var responseHeaderMatch = Regex.Match(responseString, _config.CommandPrefixes.RConResponse).Value;
|
var responseHeaderMatch = Regex.Match(responseString, _config.CommandPrefixes.RConResponse).Value;
|
||||||
var headerSplit = responseString.Split(type == StaticHelpers.QueryType.GET_INFO ? _config.CommandPrefixes.RconGetInfoResponseHeader : responseHeaderMatch);
|
var headerSplit = responseString.Split(type == StaticHelpers.QueryType.GET_INFO
|
||||||
|
? _config.CommandPrefixes.RconGetInfoResponseHeader
|
||||||
|
: responseHeaderMatch);
|
||||||
|
|
||||||
if (headerSplit.Length != 2)
|
if (headerSplit.Length != 2)
|
||||||
{
|
{
|
||||||
@ -369,7 +393,8 @@ namespace Integrations.Cod
|
|||||||
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
splitStatusStrings.Add(responseString.Replace(_config.CommandPrefixes.RConResponse, "").TrimEnd('\0'));
|
splitStatusStrings.Add(responseString.Replace(_config.CommandPrefixes.RConResponse, "")
|
||||||
|
.TrimEnd('\0'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -396,8 +421,10 @@ namespace Integrations.Cod
|
|||||||
{
|
{
|
||||||
message = message.Replace(_config.CommandPrefixes.RConResponse, "");
|
message = message.Replace(_config.CommandPrefixes.RConResponse, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.Append(message);
|
builder.Append(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.Append('\n');
|
builder.Append('\n');
|
||||||
return builder.ToString();
|
return builder.ToString();
|
||||||
}
|
}
|
||||||
@ -410,6 +437,7 @@ namespace Integrations.Cod
|
|||||||
|
|
||||||
if (rconSocket is null)
|
if (rconSocket is null)
|
||||||
{
|
{
|
||||||
|
_log.LogDebug("Invalid state");
|
||||||
throw new InvalidOperationException("State is not valid for socket operation");
|
throw new InvalidOperationException("State is not valid for socket operation");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -419,6 +447,7 @@ namespace Integrations.Cod
|
|||||||
// setup the event handlers only once because we're reusing the event args
|
// setup the event handlers only once because we're reusing the event args
|
||||||
connectionState.SendEventArgs.Completed += OnDataSent;
|
connectionState.SendEventArgs.Completed += OnDataSent;
|
||||||
connectionState.ReceiveEventArgs.Completed += OnDataReceived;
|
connectionState.ReceiveEventArgs.Completed += OnDataReceived;
|
||||||
|
connectionState.ReceiveEventArgs.UserToken = connectionState.SendEventArgs.UserToken;
|
||||||
connectionState.SendEventArgs.RemoteEndPoint = Endpoint;
|
connectionState.SendEventArgs.RemoteEndPoint = Endpoint;
|
||||||
connectionState.ReceiveEventArgs.RemoteEndPoint = Endpoint;
|
connectionState.ReceiveEventArgs.RemoteEndPoint = Endpoint;
|
||||||
connectionState.ReceiveEventArgs.DisconnectReuseSocket = true;
|
connectionState.ReceiveEventArgs.DisconnectReuseSocket = true;
|
||||||
@ -435,15 +464,15 @@ namespace Integrations.Cod
|
|||||||
// the send has not been completed asynchronously
|
// the send has not been completed asynchronously
|
||||||
// this really shouldn't ever happen because it's UDP
|
// this really shouldn't ever happen because it's UDP
|
||||||
var complete = await connectionState.OnSentData.WaitAsync(StaticHelpers.SocketTimeout(4), token);
|
var complete = await connectionState.OnSentData.WaitAsync(StaticHelpers.SocketTimeout(4), token);
|
||||||
|
|
||||||
if (!complete)
|
if (!complete)
|
||||||
{
|
{
|
||||||
using(LogContext.PushProperty("Server", Endpoint.ToString()))
|
using (LogContext.PushProperty("Server", Endpoint.ToString()))
|
||||||
{
|
{
|
||||||
_log.LogWarning("Socket timed out while sending RCon data on attempt {Attempt}",
|
_log.LogWarning("Socket timed out while sending RCon data on attempt {Attempt}",
|
||||||
connectionState.ConnectionAttempts);
|
connectionState.ConnectionAttempts);
|
||||||
}
|
}
|
||||||
|
|
||||||
rconSocket.Close();
|
rconSocket.Close();
|
||||||
throw new NetworkException("Timed out sending RCon data", rconSocket);
|
throw new NetworkException("Timed out sending RCon data", rconSocket);
|
||||||
}
|
}
|
||||||
@ -461,7 +490,8 @@ namespace Integrations.Cod
|
|||||||
|
|
||||||
if (receiveDataPending)
|
if (receiveDataPending)
|
||||||
{
|
{
|
||||||
_log.LogDebug("Waiting to asynchronously receive data on attempt #{ConnectionAttempts}", connectionState.ConnectionAttempts);
|
_log.LogDebug("Waiting to asynchronously receive data on attempt #{ConnectionAttempts}",
|
||||||
|
connectionState.ConnectionAttempts);
|
||||||
|
|
||||||
var completed = false;
|
var completed = false;
|
||||||
|
|
||||||
@ -493,6 +523,7 @@ namespace Integrations.Cod
|
|||||||
}
|
}
|
||||||
|
|
||||||
rconSocket.Close();
|
rconSocket.Close();
|
||||||
|
_log.LogDebug("OnDataReceived did not complete in allocated time");
|
||||||
throw new NetworkException("Timed out receiving RCon response", rconSocket);
|
throw new NetworkException("Timed out receiving RCon response", rconSocket);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -521,13 +552,14 @@ namespace Integrations.Cod
|
|||||||
|
|
||||||
private void OnDataReceived(object sender, SocketAsyncEventArgs e)
|
private void OnDataReceived(object sender, SocketAsyncEventArgs e)
|
||||||
{
|
{
|
||||||
_log.LogDebug("Read {BytesTransferred} bytes from {Endpoint}", e.BytesTransferred, e.RemoteEndPoint?.ToString());
|
_log.LogDebug("Read {BytesTransferred} bytes from {Endpoint}", e.BytesTransferred,
|
||||||
|
e.RemoteEndPoint?.ToString());
|
||||||
|
|
||||||
// this occurs when we close the socket
|
// this occurs when we close the socket
|
||||||
if (e.BytesTransferred == 0)
|
if (e.BytesTransferred == 0)
|
||||||
{
|
{
|
||||||
_log.LogDebug("No bytes were transmitted so the connection was probably closed");
|
_log.LogDebug("No bytes were transmitted so the connection was probably closed");
|
||||||
|
|
||||||
var semaphore = ActiveQueries[Endpoint].OnReceivedData;
|
var semaphore = ActiveQueries[Endpoint].OnReceivedData;
|
||||||
|
|
||||||
try
|
try
|
||||||
@ -545,7 +577,7 @@ namespace Integrations.Cod
|
|||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var state = ActiveQueries[Endpoint];
|
var state = ActiveQueries[Endpoint];
|
||||||
var cancellationRequested = ((ConnectionUserToken)e.UserToken)?.CancellationToken.IsCancellationRequested ??
|
var cancellationRequested = ((ConnectionUserToken)e.UserToken)?.CancellationToken.IsCancellationRequested ??
|
||||||
false;
|
false;
|
||||||
@ -566,12 +598,12 @@ namespace Integrations.Cod
|
|||||||
// ignored because we can have the socket operation cancelled (which releases the semaphore) but
|
// ignored because we can have the socket operation cancelled (which releases the semaphore) but
|
||||||
// this thread is not notified because it's an event
|
// this thread is not notified because it's an event
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
state.BytesReadPerSegment.Add(e.BytesTransferred);
|
state.BytesReadPerSegment.Add(e.BytesTransferred);
|
||||||
|
|
||||||
// I don't even want to know why this works for getting more data from Cod4x
|
// I don't even want to know why this works for getting more data from Cod4x
|
||||||
// but I'm leaving it in here as long as it doesn't break anything.
|
// but I'm leaving it in here as long as it doesn't break anything.
|
||||||
// it's very stupid...
|
// it's very stupid...
|
||||||
@ -582,7 +614,7 @@ namespace Integrations.Cod
|
|||||||
var totalBytesTransferred = e.BytesTransferred;
|
var totalBytesTransferred = e.BytesTransferred;
|
||||||
_log.LogDebug("{Total} total bytes transferred with {Available} bytes remaining", totalBytesTransferred,
|
_log.LogDebug("{Total} total bytes transferred with {Available} bytes remaining", totalBytesTransferred,
|
||||||
sock.Available);
|
sock.Available);
|
||||||
|
|
||||||
// we still have available data so the payload was segmented
|
// we still have available data so the payload was segmented
|
||||||
while (sock.Available > 0)
|
while (sock.Available > 0)
|
||||||
{
|
{
|
||||||
@ -596,17 +628,17 @@ namespace Integrations.Cod
|
|||||||
bufferSpaceAvailable);
|
bufferSpaceAvailable);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
state.ReceiveEventArgs.SetBuffer(state.ReceiveBuffer, totalBytesTransferred, sock.Available);
|
state.ReceiveEventArgs.SetBuffer(state.ReceiveBuffer, totalBytesTransferred, sock.Available);
|
||||||
if (sock.ReceiveAsync(state.ReceiveEventArgs))
|
if (sock.ReceiveAsync(state.ReceiveEventArgs))
|
||||||
{
|
{
|
||||||
_log.LogDebug("Remaining bytes are async");
|
_log.LogDebug("Remaining bytes are async");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
_log.LogDebug("Read {BytesTransferred} synchronous bytes from {Endpoint}",
|
_log.LogDebug("Read {BytesTransferred} synchronous bytes from {Endpoint}",
|
||||||
state.ReceiveEventArgs.BytesTransferred, e.RemoteEndPoint?.ToString());
|
state.ReceiveEventArgs.BytesTransferred, e.RemoteEndPoint?.ToString());
|
||||||
|
|
||||||
// we need to increment this here because the callback isn't executed if there's no pending IO
|
// we need to increment this here because the callback isn't executed if there's no pending IO
|
||||||
state.BytesReadPerSegment.Add(state.ReceiveEventArgs.BytesTransferred);
|
state.BytesReadPerSegment.Add(state.ReceiveEventArgs.BytesTransferred);
|
||||||
totalBytesTransferred += state.ReceiveEventArgs.BytesTransferred;
|
totalBytesTransferred += state.ReceiveEventArgs.BytesTransferred;
|
||||||
@ -638,8 +670,9 @@ namespace Integrations.Cod
|
|||||||
|
|
||||||
private void OnDataSent(object sender, SocketAsyncEventArgs e)
|
private void OnDataSent(object sender, SocketAsyncEventArgs e)
|
||||||
{
|
{
|
||||||
_log.LogDebug("Sent {ByteCount} bytes to {Endpoint}", e.Buffer?.Length, e.ConnectSocket?.RemoteEndPoint?.ToString());
|
_log.LogDebug("Sent {ByteCount} bytes to {Endpoint}", e.Buffer?.Length,
|
||||||
|
e.ConnectSocket?.RemoteEndPoint?.ToString());
|
||||||
|
|
||||||
var semaphore = ActiveQueries[Endpoint].OnSentData;
|
var semaphore = ActiveQueries[Endpoint].OnSentData;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -20,7 +20,7 @@ namespace Integrations.Cod
|
|||||||
public int ConnectionAttempts { get; set; }
|
public int ConnectionAttempts { get; set; }
|
||||||
private const int BufferSize = 16384;
|
private const int BufferSize = 16384;
|
||||||
public readonly byte[] ReceiveBuffer = new byte[BufferSize];
|
public readonly byte[] ReceiveBuffer = new byte[BufferSize];
|
||||||
public readonly SemaphoreSlim OnComplete = new SemaphoreSlim(1, 1);
|
public readonly SemaphoreSlim OnComplete = new(1, 1);
|
||||||
public readonly SemaphoreSlim OnSentData = new(1, 1);
|
public readonly SemaphoreSlim OnSentData = new(1, 1);
|
||||||
public readonly SemaphoreSlim OnReceivedData = new (1, 1);
|
public readonly SemaphoreSlim OnReceivedData = new (1, 1);
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
using SharedLibraryCore;
|
using SharedLibraryCore.Interfaces;
|
||||||
using SharedLibraryCore.Interfaces;
|
|
||||||
|
|
||||||
namespace AutomessageFeed
|
namespace AutomessageFeed
|
||||||
{
|
{
|
||||||
@ -11,16 +10,6 @@ namespace AutomessageFeed
|
|||||||
|
|
||||||
public IBaseConfiguration Generate()
|
public IBaseConfiguration Generate()
|
||||||
{
|
{
|
||||||
EnableFeed = Utilities.PromptBool(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_AUTOMESSAGEFEED_PROMPT_ENABLE"]);
|
|
||||||
|
|
||||||
if (EnableFeed)
|
|
||||||
{
|
|
||||||
FeedUrl = Utilities.PromptString(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_AUTOMESSAGEFEED_URL"]);
|
|
||||||
MaxFeedItems = Utilities.PromptInt(Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_AUTOMESSAGEFEED_PROMPT_MAXITEMS"],
|
|
||||||
Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_AUTOMESSAGEFEED_PROMPT_MAXITEMS_DESC"],
|
|
||||||
0, int.MaxValue, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +15,8 @@ namespace LiveRadar.Web.Controllers
|
|||||||
private static LiveRadarConfiguration _config;
|
private static LiveRadarConfiguration _config;
|
||||||
private readonly IConfigurationHandler<LiveRadarConfiguration> _configurationHandler;
|
private readonly IConfigurationHandler<LiveRadarConfiguration> _configurationHandler;
|
||||||
|
|
||||||
public RadarController(IManager manager, IConfigurationHandlerFactory configurationHandlerFactory) : base(manager)
|
public RadarController(IManager manager, IConfigurationHandlerFactory configurationHandlerFactory) :
|
||||||
|
base(manager)
|
||||||
{
|
{
|
||||||
_manager = manager;
|
_manager = manager;
|
||||||
_configurationHandler =
|
_configurationHandler =
|
||||||
@ -23,29 +24,33 @@ namespace LiveRadar.Web.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[Route("Radar/{serverId}")]
|
[Route("Radar/{serverId?}")]
|
||||||
public IActionResult Index(long? serverId = null)
|
public IActionResult Index(string serverId = null)
|
||||||
{
|
{
|
||||||
ViewBag.IsFluid = true;
|
var servers = _manager.GetServers()
|
||||||
ViewBag.Title = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_RADAR_TITLE"];
|
.Where(server => server.GameName == Server.Game.IW4)
|
||||||
ViewBag.ActiveServerId = serverId ?? _manager.GetServers().FirstOrDefault()?.EndPoint;
|
.Select(server => new ServerInfo
|
||||||
ViewBag.Servers = _manager.GetServers()
|
|
||||||
.Where(_server => _server.GameName == Server.Game.IW4)
|
|
||||||
.Select(_server => new ServerInfo()
|
|
||||||
{
|
{
|
||||||
Name = _server.Hostname,
|
Name = server.Hostname,
|
||||||
ID = _server.EndPoint
|
IPAddress = server.IP,
|
||||||
|
Port = server.Port
|
||||||
});
|
});
|
||||||
|
|
||||||
return View();
|
ViewBag.Title = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_RADAR_TITLE"];
|
||||||
|
ViewBag.SelectedServerId = string.IsNullOrEmpty(serverId) ? servers.FirstOrDefault()?.Endpoint : serverId;
|
||||||
|
|
||||||
|
// ReSharper disable once Mvc.ViewNotResolved
|
||||||
|
return View("~/Views/Plugins/LiveRadar/Radar/Index.cshtml", servers);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[Route("Radar/{serverId}/Map")]
|
[Route("Radar/{serverId}/Map")]
|
||||||
public async Task<IActionResult> Map(long? serverId = null)
|
public async Task<IActionResult> Map(string serverId = null)
|
||||||
{
|
{
|
||||||
var server = serverId == null ? _manager.GetServers().FirstOrDefault() : _manager.GetServers().FirstOrDefault(_server => _server.EndPoint == serverId);
|
var server = serverId == null
|
||||||
|
? _manager.GetServers().FirstOrDefault()
|
||||||
|
: _manager.GetServers().FirstOrDefault(server => server.ToString() == serverId);
|
||||||
|
|
||||||
if (server == null)
|
if (server == null)
|
||||||
{
|
{
|
||||||
return NotFound();
|
return NotFound();
|
||||||
@ -56,15 +61,15 @@ namespace LiveRadar.Web.Controllers
|
|||||||
await _configurationHandler.BuildAsync();
|
await _configurationHandler.BuildAsync();
|
||||||
_config = _configurationHandler.Configuration() ?? new LiveRadarConfiguration();
|
_config = _configurationHandler.Configuration() ?? new LiveRadarConfiguration();
|
||||||
}
|
}
|
||||||
|
|
||||||
var map = _config.Maps.FirstOrDefault(_map => _map.Name == server.CurrentMap.Name);
|
var map = _config.Maps.FirstOrDefault(map => map.Name == server.CurrentMap.Name);
|
||||||
|
|
||||||
if (map == null)
|
if (map == null)
|
||||||
{
|
{
|
||||||
// occurs if we don't recognize the map
|
// occurs if we don't recognize the map
|
||||||
return StatusCode(StatusCodes.Status422UnprocessableEntity);
|
return StatusCode(StatusCodes.Status422UnprocessableEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
map.Alias = server.CurrentMap.Alias;
|
map.Alias = server.CurrentMap.Alias;
|
||||||
return Json(map);
|
return Json(map);
|
||||||
}
|
}
|
||||||
@ -72,27 +77,21 @@ namespace LiveRadar.Web.Controllers
|
|||||||
[HttpGet]
|
[HttpGet]
|
||||||
[Route("Radar/{serverId}/Data")]
|
[Route("Radar/{serverId}/Data")]
|
||||||
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
||||||
public IActionResult Data(long? serverId = null)
|
public IActionResult Data(string serverId = null)
|
||||||
{
|
{
|
||||||
var server = serverId == null ? _manager.GetServers()[0] : _manager.GetServers().First(_server => _server.EndPoint == serverId);
|
var server = serverId == null
|
||||||
var radarInfo = server.GetClientsAsList().Select(_client => _client.GetAdditionalProperty<RadarEvent>("LiveRadar")).ToList();
|
? _manager.GetServers().FirstOrDefault()
|
||||||
return Json(radarInfo);
|
: _manager.GetServers().FirstOrDefault(server => server.ToString() == serverId);
|
||||||
}
|
|
||||||
|
if (server == null)
|
||||||
[HttpGet]
|
|
||||||
[Route("Radar/Update")]
|
|
||||||
public IActionResult Update(string payload)
|
|
||||||
{
|
|
||||||
/*var radarUpdate = RadarEvent.Parse(payload);
|
|
||||||
var client = _manager.GetActiveClients().FirstOrDefault(_client => _client.NetworkId == radarUpdate.Guid);
|
|
||||||
|
|
||||||
if (client != null)
|
|
||||||
{
|
{
|
||||||
radarUpdate.Name = client.Name.StripColors();
|
return NotFound();
|
||||||
client.SetAdditionalProperty("LiveRadar", radarUpdate);
|
}
|
||||||
}*/
|
|
||||||
|
var radarInfo = server.GetClientsAsList()
|
||||||
return Ok();
|
.Select(client => client.GetAdditionalProperty<RadarEvent>("LiveRadar")).ToList();
|
||||||
|
|
||||||
|
return Json(radarInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,13 +15,6 @@
|
|||||||
<StartupObject />
|
<StartupObject />
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Content Update="Views\_ViewImports.cshtml">
|
|
||||||
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
|
|
||||||
</Content>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.3.23.1" PrivateAssets="All" />
|
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.3.23.1" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
@ -46,7 +46,7 @@ namespace LiveRadar
|
|||||||
S.CustomCallback &&
|
S.CustomCallback &&
|
||||||
!addedPage)
|
!addedPage)
|
||||||
{
|
{
|
||||||
E.Owner.Manager.GetPageList().Pages.Add(Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_RADAR_TITLE"], "/Radar/All");
|
E.Owner.Manager.GetPageList().Pages.Add(Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_RADAR_TITLE"], "/Radar");
|
||||||
addedPage = true;
|
addedPage = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -77,7 +77,15 @@ namespace LiveRadar
|
|||||||
|
|
||||||
lock (lockObject)
|
lock (lockObject)
|
||||||
{
|
{
|
||||||
generatedBotGuid = _botGuidLookups.ContainsKey(botKey)
|
var hasBotKey = _botGuidLookups.ContainsKey(botKey);
|
||||||
|
|
||||||
|
if (!hasBotKey && ((string)E.Extra).IsBotGuid())
|
||||||
|
{
|
||||||
|
// edge case where the bot guid has not been registered yet
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
generatedBotGuid = hasBotKey
|
||||||
? _botGuidLookups[botKey]
|
? _botGuidLookups[botKey]
|
||||||
: (E.Extra.ToString() ?? "0").ConvertGuidToLong(NumberStyles.HexNumber);
|
: (E.Extra.ToString() ?? "0").ConvertGuidToLong(NumberStyles.HexNumber);
|
||||||
}
|
}
|
||||||
|
@ -1,53 +0,0 @@
|
|||||||
@model IEnumerable<long>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.progress {
|
|
||||||
border-radius: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.player-stat-icon {
|
|
||||||
height: 1.5rem;
|
|
||||||
width: 1.5rem;
|
|
||||||
background-size: 1.5rem 1.5rem;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<div class="row p-0 ml-auto mr-auto mb-4">
|
|
||||||
<div class="col-12 col-xl-10 p-0 ml-auto mr-auto p-0 pl-lg-3 pr-lg-3 ">
|
|
||||||
<ul class="nav nav-tabs border-top border-bottom nav-fill" role="tablist">
|
|
||||||
@foreach (SharedLibraryCore.Dtos.ServerInfo server in ViewBag.Servers)
|
|
||||||
{
|
|
||||||
<li class="nav-item">
|
|
||||||
<a asp-controller="Radar" asp-action="Index" asp-route-serverId="@server.ID" class="nav-link @(server.ID == ViewBag.ActiveServerId ? "active": "")" aria-selected="@(server.ID == ViewBag.ActiveServerId ? "true": "false")"><color-code value="@server.Name"></color-code></a>
|
|
||||||
</li>
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row p-0 ml-auto mr-auto col-12 col-xl-10">
|
|
||||||
<div class="p-0 pl-lg-3 pr-lg-3 m-0 col-lg-3 col-12 text-lg-right text-center player-data-left" style="opacity: 0;">
|
|
||||||
</div>
|
|
||||||
<div class="pl-0 pr-0 pl-lg-3 pr-lg-3 col-lg-6 col-12 pb-4">
|
|
||||||
<div id="map_name" class="h4 text-center pb-2 pt-2 mb-0 bg-primary">—</div>
|
|
||||||
<div id="map_list" style="background-size:cover; padding-bottom: 100% !important;">
|
|
||||||
<canvas id="map_canvas" style="position:absolute;"></canvas>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="p-0 pl-lg-3 pr-lg-3 m-0 col-lg-3 col-12 text-lg-left text-center player-data-right" style="opacity: 0;">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- images used by canvas -->
|
|
||||||
<img class="hide" id="hud_death" src="~/images/radar/death.png" />
|
|
||||||
|
|
||||||
@section scripts {
|
|
||||||
<environment include="Development">
|
|
||||||
<script type="text/javascript" src="~/js/liveradar.js" defer="defer"></script>
|
|
||||||
</environment>
|
|
||||||
|
|
||||||
<script type="text/javascript">
|
|
||||||
const radarDataUrl = '@Url.Action("Data", "Radar", new { serverId = ViewBag.ActiveServerId })';
|
|
||||||
const mapDataUrl = '@Url.Action("Map", "Radar", new { serverId = ViewBag.ActiveServerId })';
|
|
||||||
</script>
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
@using SharedLibraryCore
|
|
||||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
|
||||||
@addTagHelper *, SharedLibraryCore
|
|
@ -9,7 +9,6 @@ namespace IW4MAdmin.Plugins.Login
|
|||||||
|
|
||||||
public IBaseConfiguration Generate()
|
public IBaseConfiguration Generate()
|
||||||
{
|
{
|
||||||
RequirePrivilegedClientLogin = Utilities.PromptBool("Require privileged client login");
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
48
Plugins/ScriptPlugins/BanBroadcasting.js
Normal file
48
Plugins/ScriptPlugins/BanBroadcasting.js
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
const broadcastMessage = (server, message) => {
|
||||||
|
server.Manager.GetServers().forEach(s => {
|
||||||
|
s.Broadcast(message);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const plugin = {
|
||||||
|
author: 'Amos',
|
||||||
|
version: 1.0,
|
||||||
|
name: 'Broadcast Bans',
|
||||||
|
|
||||||
|
onEventAsync: function (gameEvent, server) {
|
||||||
|
if (!this.enableBroadcastBans) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gameEvent.TypeName === 'Ban') {
|
||||||
|
let penalty = undefined;
|
||||||
|
gameEvent.Origin.AdministeredPenalties?.forEach(p => {
|
||||||
|
penalty = p.AutomatedOffense;
|
||||||
|
})
|
||||||
|
|
||||||
|
if (gameEvent.Origin.ClientId === 1 && penalty !== undefined) {
|
||||||
|
let localization = _localization.LocalizationIndex['PLUGINS_BROADCAST_BAN_ACMESSAGE'].replace('{{targetClient}}', gameEvent.Target.CleanedName);
|
||||||
|
broadcastMessage(server, localization);
|
||||||
|
} else {
|
||||||
|
let localization = _localization.LocalizationIndex['PLUGINS_BROADCAST_BAN_MESSAGE'].replace('{{targetClient}}', gameEvent.Target.CleanedName);
|
||||||
|
broadcastMessage(server, localization);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onLoadAsync: function (manager) {
|
||||||
|
this.configHandler = _configHandler;
|
||||||
|
this.enableBroadcastBans = this.configHandler.GetValue('EnableBroadcastBans');
|
||||||
|
|
||||||
|
if (this.enableBroadcastBans === undefined) {
|
||||||
|
this.enableBroadcastBans = false;
|
||||||
|
this.configHandler.SetValue('EnableBroadcastBans', this.enableBroadcastBans);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onUnloadAsync: function () {
|
||||||
|
},
|
||||||
|
|
||||||
|
onTickAsync: function (server) {
|
||||||
|
}
|
||||||
|
};
|
@ -85,7 +85,7 @@ let commands = [{
|
|||||||
name: 'weapon name',
|
name: 'weapon name',
|
||||||
required: true
|
required: true
|
||||||
}],
|
}],
|
||||||
supportedGames: ['IW4'],
|
supportedGames: ['IW4', 'IW5'],
|
||||||
execute: (gameEvent) => {
|
execute: (gameEvent) => {
|
||||||
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
|
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
|
||||||
return;
|
return;
|
||||||
@ -103,7 +103,7 @@ let commands = [{
|
|||||||
name: 'player',
|
name: 'player',
|
||||||
required: true
|
required: true
|
||||||
}],
|
}],
|
||||||
supportedGames: ['IW4'],
|
supportedGames: ['IW4', 'IW5'],
|
||||||
execute: (gameEvent) => {
|
execute: (gameEvent) => {
|
||||||
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
|
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
|
||||||
return;
|
return;
|
||||||
@ -121,7 +121,7 @@ let commands = [{
|
|||||||
name: 'player',
|
name: 'player',
|
||||||
required: true
|
required: true
|
||||||
}],
|
}],
|
||||||
supportedGames: ['IW4'],
|
supportedGames: ['IW4', 'IW5'],
|
||||||
execute: (gameEvent) => {
|
execute: (gameEvent) => {
|
||||||
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
|
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
|
||||||
return;
|
return;
|
||||||
@ -129,6 +129,72 @@ let commands = [{
|
|||||||
sendScriptCommand(gameEvent.Owner, 'SwitchTeams', gameEvent.Origin, gameEvent.Target, undefined);
|
sendScriptCommand(gameEvent.Owner, 'SwitchTeams', gameEvent.Origin, gameEvent.Target, undefined);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'lockcontrols',
|
||||||
|
description: 'locks target player\'s controls',
|
||||||
|
alias: 'lc',
|
||||||
|
permission: 'Administrator',
|
||||||
|
targetRequired: true,
|
||||||
|
arguments: [{
|
||||||
|
name: 'player',
|
||||||
|
required: true
|
||||||
|
}],
|
||||||
|
supportedGames: ['IW4', 'IW5'],
|
||||||
|
execute: (gameEvent) => {
|
||||||
|
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sendScriptCommand(gameEvent.Owner, 'LockControls', gameEvent.Origin, gameEvent.Target, undefined);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'unlockcontrols',
|
||||||
|
description: 'unlocks target player\'s controls',
|
||||||
|
alias: 'ulc',
|
||||||
|
permission: 'Administrator',
|
||||||
|
targetRequired: true,
|
||||||
|
arguments: [{
|
||||||
|
name: 'player',
|
||||||
|
required: true
|
||||||
|
}],
|
||||||
|
supportedGames: ['IW4', 'IW5'],
|
||||||
|
execute: (gameEvent) => {
|
||||||
|
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sendScriptCommand(gameEvent.Owner, 'UnlockControls', gameEvent.Origin, gameEvent.Target, undefined);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'noclip',
|
||||||
|
description: 'enable noclip on yourself ingame',
|
||||||
|
alias: 'nc',
|
||||||
|
permission: 'SeniorAdmin',
|
||||||
|
targetRequired: false,
|
||||||
|
arguments: [],
|
||||||
|
supportedGames: ['IW4', 'IW5'],
|
||||||
|
execute: (gameEvent) => {
|
||||||
|
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sendScriptCommand(gameEvent.Owner, 'NoClip', gameEvent.Origin, gameEvent.Origin, undefined);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'noclipoff',
|
||||||
|
description: 'disable noclip on yourself ingame',
|
||||||
|
alias: 'nco',
|
||||||
|
permission: 'SeniorAdmin',
|
||||||
|
targetRequired: false,
|
||||||
|
arguments: [],
|
||||||
|
supportedGames: ['IW4', 'IW5'],
|
||||||
|
execute: (gameEvent) => {
|
||||||
|
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sendScriptCommand(gameEvent.Owner, 'NoClipOff', gameEvent.Origin, gameEvent.Origin, undefined);
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'hide',
|
name: 'hide',
|
||||||
description: 'hide yourself ingame',
|
description: 'hide yourself ingame',
|
||||||
@ -136,7 +202,7 @@ let commands = [{
|
|||||||
permission: 'SeniorAdmin',
|
permission: 'SeniorAdmin',
|
||||||
targetRequired: false,
|
targetRequired: false,
|
||||||
arguments: [],
|
arguments: [],
|
||||||
supportedGames: ['IW4'],
|
supportedGames: ['IW4', 'IW5'],
|
||||||
execute: (gameEvent) => {
|
execute: (gameEvent) => {
|
||||||
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
|
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
|
||||||
return;
|
return;
|
||||||
@ -151,7 +217,7 @@ let commands = [{
|
|||||||
permission: 'SeniorAdmin',
|
permission: 'SeniorAdmin',
|
||||||
targetRequired: false,
|
targetRequired: false,
|
||||||
arguments: [],
|
arguments: [],
|
||||||
supportedGames: ['IW4'],
|
supportedGames: ['IW4', 'IW5'],
|
||||||
execute: (gameEvent) => {
|
execute: (gameEvent) => {
|
||||||
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
|
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
|
||||||
return;
|
return;
|
||||||
@ -173,7 +239,7 @@ let commands = [{
|
|||||||
name: 'message',
|
name: 'message',
|
||||||
required: true
|
required: true
|
||||||
}],
|
}],
|
||||||
supportedGames: ['IW4'],
|
supportedGames: ['IW4', 'IW5'],
|
||||||
execute: (gameEvent) => {
|
execute: (gameEvent) => {
|
||||||
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
|
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
|
||||||
return;
|
return;
|
||||||
@ -194,7 +260,7 @@ let commands = [{
|
|||||||
name: 'player',
|
name: 'player',
|
||||||
required: true
|
required: true
|
||||||
}],
|
}],
|
||||||
supportedGames: ['IW4'],
|
supportedGames: ['IW4', 'IW5'],
|
||||||
execute: (gameEvent) => {
|
execute: (gameEvent) => {
|
||||||
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
|
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
|
||||||
return;
|
return;
|
||||||
@ -202,6 +268,24 @@ let commands = [{
|
|||||||
sendScriptCommand(gameEvent.Owner, 'Goto', gameEvent.Origin, gameEvent.Target, undefined);
|
sendScriptCommand(gameEvent.Owner, 'Goto', gameEvent.Origin, gameEvent.Target, undefined);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'playertome',
|
||||||
|
description: 'teleport a player to you',
|
||||||
|
alias: 'p2m',
|
||||||
|
permission: 'SeniorAdmin',
|
||||||
|
targetRequired: true,
|
||||||
|
arguments: [{
|
||||||
|
name: 'player',
|
||||||
|
required: true
|
||||||
|
}],
|
||||||
|
supportedGames: ['IW4', 'IW5'],
|
||||||
|
execute: (gameEvent) => {
|
||||||
|
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sendScriptCommand(gameEvent.Owner, 'PlayerToMe', gameEvent.Origin, gameEvent.Target, undefined);
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'goto',
|
name: 'goto',
|
||||||
description: 'teleport to a position',
|
description: 'teleport to a position',
|
||||||
@ -220,7 +304,7 @@ let commands = [{
|
|||||||
name: 'z',
|
name: 'z',
|
||||||
required: true
|
required: true
|
||||||
}],
|
}],
|
||||||
supportedGames: ['IW4'],
|
supportedGames: ['IW4', 'IW5'],
|
||||||
execute: (gameEvent) => {
|
execute: (gameEvent) => {
|
||||||
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
|
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
|
||||||
return;
|
return;
|
||||||
@ -244,7 +328,7 @@ let commands = [{
|
|||||||
name: 'player',
|
name: 'player',
|
||||||
required: true
|
required: true
|
||||||
}],
|
}],
|
||||||
supportedGames: ['IW4'],
|
supportedGames: ['IW4', 'IW5'],
|
||||||
execute: (gameEvent) => {
|
execute: (gameEvent) => {
|
||||||
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
|
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
|
||||||
return;
|
return;
|
||||||
@ -259,7 +343,7 @@ let commands = [{
|
|||||||
permission: 'SeniorAdmin',
|
permission: 'SeniorAdmin',
|
||||||
targetRequired: false,
|
targetRequired: false,
|
||||||
arguments: [],
|
arguments: [],
|
||||||
supportedGames: ['IW4'],
|
supportedGames: ['IW4', 'IW5'],
|
||||||
execute: (gameEvent) => {
|
execute: (gameEvent) => {
|
||||||
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
|
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
|
||||||
return;
|
return;
|
||||||
@ -277,7 +361,7 @@ let commands = [{
|
|||||||
name: 'player',
|
name: 'player',
|
||||||
required: true
|
required: true
|
||||||
}],
|
}],
|
||||||
supportedGames: ['IW4'],
|
supportedGames: ['IW4', 'IW5'],
|
||||||
execute: (gameEvent) => {
|
execute: (gameEvent) => {
|
||||||
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
|
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
|
||||||
return;
|
return;
|
||||||
@ -482,14 +566,14 @@ const pollForEvents = server => {
|
|||||||
if (!state.waitingOnOutput) {
|
if (!state.waitingOnOutput) {
|
||||||
if (state.queuedMessages.length === 0) {
|
if (state.queuedMessages.length === 0) {
|
||||||
logger.WriteDebug('No messages in queue');
|
logger.WriteDebug('No messages in queue');
|
||||||
return;``
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
state.waitingOnOutput = true;
|
state.waitingOnOutput = true;
|
||||||
const nextMessage = state.queuedMessages.splice(0, 1);
|
const nextMessage = state.queuedMessages.splice(0, 1);
|
||||||
setDvar(server, outDvar, nextMessage, onSetDvar);
|
setDvar(server, outDvar, nextMessage, onSetDvar);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.waitingOnOutput) {
|
if (state.waitingOnOutput) {
|
||||||
getDvar(server, outDvar, onReceivedDvar);
|
getDvar(server, outDvar, onReceivedDvar);
|
||||||
}
|
}
|
||||||
|
42
Plugins/ScriptPlugins/ParserH1MOD.js
Normal file
42
Plugins/ScriptPlugins/ParserH1MOD.js
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
var rconParser;
|
||||||
|
var eventParser;
|
||||||
|
|
||||||
|
var plugin = {
|
||||||
|
author: 'fed',
|
||||||
|
version: 0.1,
|
||||||
|
name: 'H1-Mod Parser',
|
||||||
|
isParser: true,
|
||||||
|
|
||||||
|
onEventAsync: function(gameEvent, server) {},
|
||||||
|
|
||||||
|
onLoadAsync: function(manager) {
|
||||||
|
rconParser = manager.GenerateDynamicRConParser(this.name);
|
||||||
|
eventParser = manager.GenerateDynamicEventParser(this.name);
|
||||||
|
|
||||||
|
rconParser.Configuration.CommandPrefixes.Kick = 'kickClient {0} "{1}"';
|
||||||
|
rconParser.Configuration.CommandPrefixes.Ban = 'kickClient {0} "{1}"';
|
||||||
|
rconParser.Configuration.CommandPrefixes.TempBan = 'kickClient {0} "{1}"';
|
||||||
|
rconParser.Configuration.CommandPrefixes.Tell = 'tellraw {0} "{1}"';
|
||||||
|
rconParser.Configuration.CommandPrefixes.Say = 'sayraw "{0}"';
|
||||||
|
rconParser.Configuration.CommandPrefixes.RConResponse = '\xff\xff\xff\xffprint';
|
||||||
|
rconParser.Configuration.Dvar.Pattern = '^ *\\"(.+)\\" is: \\"(.+)?\\" default: \\"(.+)?\\"';
|
||||||
|
rconParser.Configuration.Status.Pattern = '^ *([0-9]+) +-?([0-9]+) +(Yes|No) +((?:[A-Z]+|[0-9]+)) +((?:[a-z]|[0-9]){8,32}|(?:[a-z]|[0-9]){8,32}|bot[0-9]+|(?:[0-9]+)) *(.{0,32}) +(\\d+\\.\\d+\\.\\d+.\\d+\\:-*\\d{1,5}|0+.0+:-*\\d{1,5}|loopback|unknown|bot) +(-*[0-9]+) *$';
|
||||||
|
rconParser.Configuration.StatusHeader.Pattern = 'num +score +bot +ping +guid +name +address +qport *';
|
||||||
|
rconParser.Configuration.Status.AddMapping(102, 4);
|
||||||
|
rconParser.Configuration.Status.AddMapping(103, 5);
|
||||||
|
rconParser.Configuration.Status.AddMapping(104, 6);
|
||||||
|
rconParser.Configuration.WaitForResponse = false;
|
||||||
|
rconParser.Configuration.DefaultRConPort = 27016;
|
||||||
|
|
||||||
|
eventParser.Configuration.GameDirectory = '';
|
||||||
|
|
||||||
|
rconParser.Version = 'H1 MP 1.15 build 1251288 Tue Jul 23 13:38:30 2019 win64';
|
||||||
|
rconParser.GameName = 11; // H1
|
||||||
|
eventParser.Version = 'H1 MP 1.15 build 1251288 Tue Jul 23 13:38:30 2019 win64';
|
||||||
|
eventParser.GameName = 11; // H1
|
||||||
|
},
|
||||||
|
|
||||||
|
onUnloadAsync: function() {},
|
||||||
|
|
||||||
|
onTickAsync: function(server) {}
|
||||||
|
};
|
@ -1,5 +1,6 @@
|
|||||||
const cidrRegex = /^([0-9]{1,3}\.){3}[0-9]{1,3}(\/([0-9]|[1-2][0-9]|3[0-2]))?$/;
|
const cidrRegex = /^([0-9]{1,3}\.){3}[0-9]{1,3}(\/([0-9]|[1-2][0-9]|3[0-2]))?$/;
|
||||||
const validCIDR = input => cidrRegex.test(input);
|
const validCIDR = input => cidrRegex.test(input);
|
||||||
|
let subnetList = [];
|
||||||
|
|
||||||
const commands = [{
|
const commands = [{
|
||||||
name: "bansubnet",
|
name: "bansubnet",
|
||||||
@ -20,8 +21,8 @@ const commands = [{
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
plugin.subnetList.push(input);
|
subnetList.push(input);
|
||||||
_configHandler.SetValue('SubnetBanList', plugin.subnetList);
|
_configHandler.SetValue('SubnetBanList', subnetList);
|
||||||
|
|
||||||
gameEvent.Origin.Tell(`Added ${input} to subnet banlist`);
|
gameEvent.Origin.Tell(`Added ${input} to subnet banlist`);
|
||||||
}
|
}
|
||||||
@ -73,7 +74,7 @@ const plugin = {
|
|||||||
|
|
||||||
onEventAsync: (gameEvent, server) => {
|
onEventAsync: (gameEvent, server) => {
|
||||||
if (gameEvent.TypeName === 'Join') {
|
if (gameEvent.TypeName === 'Join') {
|
||||||
if (!isSubnetBanned(gameEvent.Origin.IPAddressString, this.subnetList, this.logger)) {
|
if (!isSubnetBanned(gameEvent.Origin.IPAddressString, subnetList, this.logger)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,7 +92,7 @@ const plugin = {
|
|||||||
if (list !== undefined) {
|
if (list !== undefined) {
|
||||||
list.forEach(element => {
|
list.forEach(element => {
|
||||||
const ban = String(element);
|
const ban = String(element);
|
||||||
this.subnetList.push(ban)
|
subnetList.push(ban)
|
||||||
});
|
});
|
||||||
this.logger.WriteInfo(`Loaded ${list.length} banned subnets`);
|
this.logger.WriteInfo(`Loaded ${list.length} banned subnets`);
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
let vpnExceptionIds = [];
|
||||||
const commands = [{
|
const commands = [{
|
||||||
name: "whitelistvpn",
|
name: "whitelistvpn",
|
||||||
description: "whitelists a player's client id from VPN detection",
|
description: "whitelists a player's client id from VPN detection",
|
||||||
@ -9,8 +10,8 @@ const commands = [{
|
|||||||
required: true
|
required: true
|
||||||
}],
|
}],
|
||||||
execute: (gameEvent) => {
|
execute: (gameEvent) => {
|
||||||
plugin.vpnExceptionIds.push(gameEvent.Target.ClientId);
|
vpnExceptionIds.push(gameEvent.Target.ClientId);
|
||||||
plugin.configHandler.SetValue('vpnExceptionIds', plugin.vpnExceptionIds);
|
plugin.configHandler.SetValue('vpnExceptionIds', vpnExceptionIds);
|
||||||
|
|
||||||
gameEvent.Origin.Tell(`Successfully whitelisted ${gameEvent.Target.Name}`);
|
gameEvent.Origin.Tell(`Successfully whitelisted ${gameEvent.Target.Name}`);
|
||||||
}
|
}
|
||||||
@ -22,13 +23,13 @@ const plugin = {
|
|||||||
name: 'VPN Detection Plugin',
|
name: 'VPN Detection Plugin',
|
||||||
manager: null,
|
manager: null,
|
||||||
logger: null,
|
logger: null,
|
||||||
vpnExceptionIds: [],
|
|
||||||
|
|
||||||
checkForVpn: function (origin) {
|
checkForVpn: function (origin) {
|
||||||
let exempt = false;
|
let exempt = false;
|
||||||
// prevent players that are exempt from being kicked
|
// prevent players that are exempt from being kicked
|
||||||
this.vpnExceptionIds.forEach(function (id) {
|
vpnExceptionIds.forEach(function (id) {
|
||||||
if (id === origin.ClientId) {
|
if (id == origin.ClientId) { // when loaded from the config the "id" type is not the same as the ClientId type
|
||||||
exempt = true;
|
exempt = true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -79,8 +80,8 @@ const plugin = {
|
|||||||
this.logger = manager.GetLogger(0);
|
this.logger = manager.GetLogger(0);
|
||||||
|
|
||||||
this.configHandler = _configHandler;
|
this.configHandler = _configHandler;
|
||||||
this.configHandler.GetValue('vpnExceptionIds').forEach(element => this.vpnExceptionIds.push(element));
|
this.configHandler.GetValue('vpnExceptionIds').forEach(element => vpnExceptionIds.push(element));
|
||||||
this.logger.WriteInfo(`Loaded ${this.vpnExceptionIds.length} ids into whitelist`);
|
this.logger.WriteInfo(`Loaded ${vpnExceptionIds.length} ids into whitelist`);
|
||||||
},
|
},
|
||||||
|
|
||||||
onUnloadAsync: function () {
|
onUnloadAsync: function () {
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using static IW4MAdmin.Plugins.Stats.Cheat.Detection;
|
using static IW4MAdmin.Plugins.Stats.Cheat.Detection;
|
||||||
using static SharedLibraryCore.Server;
|
using static SharedLibraryCore.Server;
|
||||||
|
|
||||||
@ -7,7 +9,15 @@ namespace Stats.Config
|
|||||||
public class AnticheatConfiguration
|
public class AnticheatConfiguration
|
||||||
{
|
{
|
||||||
public bool Enable { get; set; }
|
public bool Enable { get; set; }
|
||||||
|
[Obsolete]
|
||||||
public IDictionary<long, DetectionType[]> ServerDetectionTypes { get; set; } = new Dictionary<long, DetectionType[]>();
|
public IDictionary<long, DetectionType[]> ServerDetectionTypes { get; set; } = new Dictionary<long, DetectionType[]>();
|
||||||
|
|
||||||
|
public IDictionary<Game, DetectionType[]> GameDetectionTypes { get; set; } =
|
||||||
|
new Dictionary<Game, DetectionType[]>()
|
||||||
|
{
|
||||||
|
{ Game.IW4, Enum.GetValues(typeof(DetectionType)).Cast<DetectionType>().ToArray() },
|
||||||
|
{ Game.T6, new[] { DetectionType.Offset, DetectionType.Snap, DetectionType.Strain } }
|
||||||
|
};
|
||||||
public IList<long> IgnoredClientIds { get; set; } = new List<long>();
|
public IList<long> IgnoredClientIds { get; set; } = new List<long>();
|
||||||
public IDictionary<Game, IDictionary<DetectionType, string[]>> IgnoredDetectionSpecification{ get; set; } = new Dictionary<Game, IDictionary<DetectionType, string[]>>
|
public IDictionary<Game, IDictionary<DetectionType, string[]>> IgnoredDetectionSpecification{ get; set; } = new Dictionary<Game, IDictionary<DetectionType, string[]>>
|
||||||
{
|
{
|
||||||
|
@ -193,4 +193,4 @@ namespace IW4MAdmin.Plugins.Stats
|
|||||||
throw new ArgumentException("No filters specified for chat search");
|
throw new ArgumentException("No filters specified for chat search");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -633,32 +633,44 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var hit = new EFClientKill()
|
EFClientKill hit;
|
||||||
|
try
|
||||||
{
|
{
|
||||||
Active = true,
|
hit = new EFClientKill
|
||||||
AttackerId = attacker.ClientId,
|
{
|
||||||
VictimId = victim.ClientId,
|
Active = true,
|
||||||
ServerId = serverId,
|
AttackerId = attacker.ClientId,
|
||||||
DeathOrigin = vDeathOrigin,
|
VictimId = victim.ClientId,
|
||||||
KillOrigin = vKillOrigin,
|
ServerId = serverId,
|
||||||
DeathType = (int) ParseEnum<IW4Info.MeansOfDeath>.Get(type, typeof(IW4Info.MeansOfDeath)),
|
DeathOrigin = vDeathOrigin,
|
||||||
Damage = int.Parse(damage),
|
KillOrigin = vKillOrigin,
|
||||||
HitLoc = (int) ParseEnum<IW4Info.HitLocation>.Get(hitLoc, typeof(IW4Info.HitLocation)),
|
DeathType = (int) ParseEnum<IW4Info.MeansOfDeath>.Get(type, typeof(IW4Info.MeansOfDeath)),
|
||||||
WeaponReference = weapon,
|
Damage = int.Parse(damage),
|
||||||
ViewAngles = vViewAngles,
|
HitLoc = (int) ParseEnum<IW4Info.HitLocation>.Get(hitLoc, typeof(IW4Info.HitLocation)),
|
||||||
TimeOffset = long.Parse(offset),
|
WeaponReference = weapon,
|
||||||
When = time,
|
ViewAngles = vViewAngles,
|
||||||
IsKillstreakKill = isKillstreakKill[0] != '0',
|
TimeOffset = long.Parse(offset),
|
||||||
AdsPercent = float.Parse(Ads, System.Globalization.CultureInfo.InvariantCulture),
|
When = time,
|
||||||
Fraction = double.Parse(fraction, System.Globalization.CultureInfo.InvariantCulture),
|
IsKillstreakKill = isKillstreakKill[0] != '0',
|
||||||
VisibilityPercentage = double.Parse(visibilityPercentage,
|
AdsPercent = float.Parse(Ads, System.Globalization.CultureInfo.InvariantCulture),
|
||||||
System.Globalization.CultureInfo.InvariantCulture),
|
Fraction = double.Parse(fraction, System.Globalization.CultureInfo.InvariantCulture),
|
||||||
IsKill = !isDamage,
|
VisibilityPercentage = double.Parse(visibilityPercentage,
|
||||||
AnglesList = snapshotAngles,
|
System.Globalization.CultureInfo.InvariantCulture),
|
||||||
IsAlive = isAlive == "1",
|
IsKill = !isDamage,
|
||||||
TimeSinceLastAttack = long.Parse(lastAttackTime),
|
AnglesList = snapshotAngles,
|
||||||
GameName = (int) attacker.CurrentServer.GameName
|
IsAlive = isAlive == "1",
|
||||||
};
|
TimeSinceLastAttack = long.Parse(lastAttackTime),
|
||||||
|
GameName = (int) attacker.CurrentServer.GameName
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_log.LogError(ex, "Could not parse script hit data. Damage={Damage}, TimeOffset={Offset}, TimeSinceLastAttack={LastAttackTime}",
|
||||||
|
damage, offset, lastAttackTime);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
hit.SetAdditionalProperty("HitLocationReference", hitLoc);
|
hit.SetAdditionalProperty("HitLocationReference", hitLoc);
|
||||||
|
|
||||||
@ -769,7 +781,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_log.LogError(ex, "Could not save hit or anti-cheat info {@attacker} {@victim} {server}", attacker,
|
_log.LogError(ex, "Could not save hit or anti-cheat info {Attacker} {Victim} {Server}", attacker,
|
||||||
victim, serverId);
|
victim, serverId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -806,7 +818,8 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
|
|
||||||
private bool ShouldUseDetection(Server server, DetectionType detectionType, long clientId)
|
private bool ShouldUseDetection(Server server, DetectionType detectionType, long clientId)
|
||||||
{
|
{
|
||||||
var detectionTypes = Plugin.Config.Configuration().AnticheatConfiguration.ServerDetectionTypes;
|
var serverDetectionTypes = Plugin.Config.Configuration().AnticheatConfiguration.ServerDetectionTypes;
|
||||||
|
var gameDetectionTypes = Plugin.Config.Configuration().AnticheatConfiguration.GameDetectionTypes;
|
||||||
var ignoredClients = Plugin.Config.Configuration().AnticheatConfiguration.IgnoredClientIds;
|
var ignoredClients = Plugin.Config.Configuration().AnticheatConfiguration.IgnoredClientIds;
|
||||||
|
|
||||||
if (ignoredClients.Contains(clientId))
|
if (ignoredClients.Contains(clientId))
|
||||||
@ -814,10 +827,9 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!detectionTypes[server.EndPoint].Contains(detectionType))
|
if (!serverDetectionTypes[server.EndPoint].Contains(detectionType))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -826,6 +838,18 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
|
|||||||
catch (KeyNotFoundException)
|
catch (KeyNotFoundException)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!gameDetectionTypes[server.GameName].Contains(detectionType))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -186,7 +186,7 @@ namespace IW4MAdmin.Plugins.Stats
|
|||||||
manager.GetPageList()
|
manager.GetPageList()
|
||||||
.Pages.Add(
|
.Pages.Add(
|
||||||
Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_TOP_TEXT"],
|
Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_TOP_TEXT"],
|
||||||
"/Stats/TopPlayersAsync");
|
"/Stats/TopPlayers");
|
||||||
|
|
||||||
// meta data info
|
// meta data info
|
||||||
async Task<IEnumerable<InformationResponse>> GetStats(ClientPaginationRequest request, CancellationToken token = default)
|
async Task<IEnumerable<InformationResponse>> GetStats(ClientPaginationRequest request, CancellationToken token = default)
|
||||||
@ -301,8 +301,7 @@ namespace IW4MAdmin.Plugins.Stats
|
|||||||
Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 1",
|
Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 1",
|
||||||
Value = chestRatio.ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)) + '%',
|
Value = chestRatio.ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)) + '%',
|
||||||
Type = MetaType.Information,
|
Type = MetaType.Information,
|
||||||
Column = 2,
|
Order = 100,
|
||||||
Order = 0,
|
|
||||||
ToolTipText = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM1"],
|
ToolTipText = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM1"],
|
||||||
IsSensitive = true
|
IsSensitive = true
|
||||||
},
|
},
|
||||||
@ -311,8 +310,7 @@ namespace IW4MAdmin.Plugins.Stats
|
|||||||
Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 2",
|
Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 2",
|
||||||
Value = abdomenRatio.ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)) + '%',
|
Value = abdomenRatio.ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)) + '%',
|
||||||
Type = MetaType.Information,
|
Type = MetaType.Information,
|
||||||
Column = 2,
|
Order = 101,
|
||||||
Order = 1,
|
|
||||||
ToolTipText = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM2"],
|
ToolTipText = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM2"],
|
||||||
IsSensitive = true
|
IsSensitive = true
|
||||||
},
|
},
|
||||||
@ -321,8 +319,7 @@ namespace IW4MAdmin.Plugins.Stats
|
|||||||
Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 3",
|
Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 3",
|
||||||
Value = chestAbdomenRatio.ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)) + '%',
|
Value = chestAbdomenRatio.ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)) + '%',
|
||||||
Type = MetaType.Information,
|
Type = MetaType.Information,
|
||||||
Column = 2,
|
Order = 102,
|
||||||
Order = 2,
|
|
||||||
ToolTipText = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM3"],
|
ToolTipText = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM3"],
|
||||||
IsSensitive = true
|
IsSensitive = true
|
||||||
},
|
},
|
||||||
@ -331,8 +328,7 @@ namespace IW4MAdmin.Plugins.Stats
|
|||||||
Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 4",
|
Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 4",
|
||||||
Value = headRatio.ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)) + '%',
|
Value = headRatio.ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)) + '%',
|
||||||
Type = MetaType.Information,
|
Type = MetaType.Information,
|
||||||
Column = 2,
|
Order = 103,
|
||||||
Order = 3,
|
|
||||||
ToolTipText = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM4"],
|
ToolTipText = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM4"],
|
||||||
IsSensitive = true
|
IsSensitive = true
|
||||||
},
|
},
|
||||||
@ -342,8 +338,7 @@ namespace IW4MAdmin.Plugins.Stats
|
|||||||
// todo: make sure this is wrapped somewhere else
|
// todo: make sure this is wrapped somewhere else
|
||||||
Value = $"{Math.Round(((float)hitOffsetAverage), 4).ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName))}°",
|
Value = $"{Math.Round(((float)hitOffsetAverage), 4).ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName))}°",
|
||||||
Type = MetaType.Information,
|
Type = MetaType.Information,
|
||||||
Column = 2,
|
Order = 104,
|
||||||
Order = 4,
|
|
||||||
ToolTipText = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM5"],
|
ToolTipText = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM5"],
|
||||||
IsSensitive = true
|
IsSensitive = true
|
||||||
},
|
},
|
||||||
@ -352,8 +347,7 @@ namespace IW4MAdmin.Plugins.Stats
|
|||||||
Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 6",
|
Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 6",
|
||||||
Value = Math.Round(maxStrain, 3).ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
|
Value = Math.Round(maxStrain, 3).ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
|
||||||
Type = MetaType.Information,
|
Type = MetaType.Information,
|
||||||
Column = 2,
|
Order = 105,
|
||||||
Order = 5,
|
|
||||||
ToolTipText = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM6"],
|
ToolTipText = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM6"],
|
||||||
IsSensitive = true
|
IsSensitive = true
|
||||||
},
|
},
|
||||||
@ -362,8 +356,7 @@ namespace IW4MAdmin.Plugins.Stats
|
|||||||
Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 7",
|
Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 7",
|
||||||
Value = Math.Round(averageSnapValue, 3).ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
|
Value = Math.Round(averageSnapValue, 3).ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
|
||||||
Type = MetaType.Information,
|
Type = MetaType.Information,
|
||||||
Column = 2,
|
Order = 106,
|
||||||
Order = 6,
|
|
||||||
ToolTipText = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM7"],
|
ToolTipText = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM7"],
|
||||||
IsSensitive = true
|
IsSensitive = true
|
||||||
}
|
}
|
||||||
|
@ -130,7 +130,7 @@ namespace SharedLibraryCore
|
|||||||
new Claim(ClaimTypes.NameIdentifier, Client.CurrentAlias.Name),
|
new Claim(ClaimTypes.NameIdentifier, Client.CurrentAlias.Name),
|
||||||
new Claim(ClaimTypes.Role, Client.Level.ToString()),
|
new Claim(ClaimTypes.Role, Client.Level.ToString()),
|
||||||
new Claim(ClaimTypes.Sid, Client.ClientId.ToString()),
|
new Claim(ClaimTypes.Sid, Client.ClientId.ToString()),
|
||||||
new Claim(ClaimTypes.PrimarySid, Client.NetworkId.ToString("X"))
|
new Claim(ClaimTypes.PrimarySid, Client.NetworkId.ToString("X")),
|
||||||
};
|
};
|
||||||
var claimsIdentity = new ClaimsIdentity(claims, "login");
|
var claimsIdentity = new ClaimsIdentity(claims, "login");
|
||||||
SignInAsync(new ClaimsPrincipal(claimsIdentity)).Wait();
|
SignInAsync(new ClaimsPrincipal(claimsIdentity)).Wait();
|
||||||
@ -161,6 +161,14 @@ namespace SharedLibraryCore
|
|||||||
ViewBag.EnablePrivilegedUserPrivacy = AppConfig.EnablePrivilegedUserPrivacy;
|
ViewBag.EnablePrivilegedUserPrivacy = AppConfig.EnablePrivilegedUserPrivacy;
|
||||||
ViewBag.Configuration = AppConfig;
|
ViewBag.Configuration = AppConfig;
|
||||||
ViewBag.ScriptInjection = AppConfig.Webfront?.ScriptInjection;
|
ViewBag.ScriptInjection = AppConfig.Webfront?.ScriptInjection;
|
||||||
|
ViewBag.CommunityInformation = AppConfig.CommunityInformation;
|
||||||
|
ViewBag.ClientCount = Manager.GetServers().Sum(server => server.ClientNum);
|
||||||
|
ViewBag.AdminCount = Manager.GetServers().Sum(server =>
|
||||||
|
server.GetClientsAsList()
|
||||||
|
.Count(client => client.Level >= Data.Models.Client.EFClient.Permission.Trusted));
|
||||||
|
ViewBag.ReportCount = Manager.GetServers().Sum(server =>
|
||||||
|
server.Reports.Count(report => DateTime.UtcNow - report.ReportedOn <= TimeSpan.FromHours(24)));
|
||||||
|
ViewBag.PermissionsSet = PermissionsSet;
|
||||||
|
|
||||||
base.OnActionExecuting(context);
|
base.OnActionExecuting(context);
|
||||||
}
|
}
|
||||||
|
@ -85,7 +85,7 @@ namespace SharedLibraryCore.Configuration
|
|||||||
public bool EnableWebfrontConnectionWhitelist { get; set; }
|
public bool EnableWebfrontConnectionWhitelist { get; set; }
|
||||||
|
|
||||||
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_WHITELIST_LIST")]
|
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_WHITELIST_LIST")]
|
||||||
public string[] WebfrontConnectionWhitelist { get; set; } = new string[0];
|
public string[] WebfrontConnectionWhitelist { get; set; } = Array.Empty<string>();
|
||||||
|
|
||||||
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_CUSTOM_LOCALE")]
|
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_CUSTOM_LOCALE")]
|
||||||
[ConfigurationLinked("CustomLocale")]
|
[ConfigurationLinked("CustomLocale")]
|
||||||
@ -122,13 +122,13 @@ namespace SharedLibraryCore.Configuration
|
|||||||
public int AutoMessagePeriod { get; set; }
|
public int AutoMessagePeriod { get; set; }
|
||||||
|
|
||||||
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_AUTOMESSAGES")]
|
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_AUTOMESSAGES")]
|
||||||
public string[] AutoMessages { get; set; } = new string[0];
|
public string[] AutoMessages { get; set; } = Array.Empty<string>();
|
||||||
|
|
||||||
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_GLOBAL_RULES")]
|
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_GLOBAL_RULES")]
|
||||||
public string[] GlobalRules { get; set; } = new string[0];
|
public string[] GlobalRules { get; set; } = Array.Empty<string>();
|
||||||
|
|
||||||
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_DISALLOWED_NAMES")]
|
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_DISALLOWED_NAMES")]
|
||||||
public string[] DisallowedClientNames { get; set; } = new string[0];
|
public string[] DisallowedClientNames { get; set; } = Array.Empty<string>();
|
||||||
|
|
||||||
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_MAP_CHANGE_DELAY")]
|
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_MAP_CHANGE_DELAY")]
|
||||||
public int MapChangeDelaySeconds { get; set; } = 5;
|
public int MapChangeDelaySeconds { get; set; } = 5;
|
||||||
@ -156,7 +156,7 @@ namespace SharedLibraryCore.Configuration
|
|||||||
|
|
||||||
[ConfigurationIgnore]
|
[ConfigurationIgnore]
|
||||||
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_PRESET_BAN_REASONS")]
|
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_PRESET_BAN_REASONS")]
|
||||||
public Dictionary<string, string> PresetPenaltyReasons { get; set; } = new Dictionary<string, string>
|
public Dictionary<string, string> PresetPenaltyReasons { get; set; } = new()
|
||||||
{ { "afk", "Away from keyboard" }, { "ci", "Connection interrupted. Reconnect" } };
|
{ { "afk", "Away from keyboard" }, { "ci", "Connection interrupted. Reconnect" } };
|
||||||
|
|
||||||
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_ENABLE_PRIVILEGED_USER_PRIVACY")]
|
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_ENABLE_PRIVILEGED_USER_PRIVACY")]
|
||||||
@ -198,7 +198,7 @@ namespace SharedLibraryCore.Configuration
|
|||||||
: ManualWebfrontUrl;
|
: ManualWebfrontUrl;
|
||||||
|
|
||||||
[ConfigurationIgnore] public bool IgnoreServerConnectionLost { get; set; }
|
[ConfigurationIgnore] public bool IgnoreServerConnectionLost { get; set; }
|
||||||
[ConfigurationIgnore] public Uri MasterUrl { get; set; } = new Uri("http://api.raidmax.org:5000");
|
[ConfigurationIgnore] public Uri MasterUrl { get; set; } = new("http://api.raidmax.org:5000");
|
||||||
|
|
||||||
public IBaseConfiguration Generate()
|
public IBaseConfiguration Generate()
|
||||||
{
|
{
|
||||||
@ -207,22 +207,7 @@ namespace SharedLibraryCore.Configuration
|
|||||||
|
|
||||||
EnableWebFront = loc["SETUP_ENABLE_WEBFRONT"].PromptBool();
|
EnableWebFront = loc["SETUP_ENABLE_WEBFRONT"].PromptBool();
|
||||||
EnableMultipleOwners = loc["SETUP_ENABLE_MULTIOWN"].PromptBool();
|
EnableMultipleOwners = loc["SETUP_ENABLE_MULTIOWN"].PromptBool();
|
||||||
EnableSteppedHierarchy = loc["SETUP_ENABLE_STEPPEDPRIV"].PromptBool();
|
|
||||||
EnableCustomSayName = loc["SETUP_ENABLE_CUSTOMSAY"].PromptBool();
|
|
||||||
|
|
||||||
var useCustomParserEncoding = loc["SETUP_USE_CUSTOMENCODING"].PromptBool();
|
|
||||||
if (useCustomParserEncoding)
|
|
||||||
{
|
|
||||||
CustomParserEncoding = loc["SETUP_ENCODING_STRING"].PromptString();
|
|
||||||
}
|
|
||||||
|
|
||||||
WebfrontBindUrl = "http://0.0.0.0:1624";
|
WebfrontBindUrl = "http://0.0.0.0:1624";
|
||||||
|
|
||||||
if (EnableCustomSayName)
|
|
||||||
{
|
|
||||||
CustomSayName = loc["SETUP_SAY_NAME"].PromptString();
|
|
||||||
}
|
|
||||||
|
|
||||||
EnableSocialLink = loc["SETUP_DISPLAY_SOCIAL"].PromptBool();
|
EnableSocialLink = loc["SETUP_DISPLAY_SOCIAL"].PromptBool();
|
||||||
|
|
||||||
if (EnableSocialLink)
|
if (EnableSocialLink)
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
using Data.Models.Client;
|
using System;
|
||||||
|
using Data.Models.Client;
|
||||||
|
|
||||||
namespace SharedLibraryCore.Dtos
|
namespace SharedLibraryCore.Dtos
|
||||||
{
|
{
|
||||||
public class ClientInfo
|
public class ClientInfo
|
||||||
{
|
{
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public int ClientId { get; set; }
|
public int ClientId { get; set; }
|
||||||
public int LinkId { get; set; }
|
public int LinkId { get; set; }
|
||||||
public EFClient.Permission Level { get; set; }
|
public EFClient.Permission Level { get; set; }
|
||||||
|
public DateTime LastConnection { get; set; }
|
||||||
|
public bool IsMasked { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ namespace SharedLibraryCore.Dtos
|
|||||||
public int Offset { get; set; }
|
public int Offset { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// how many itesm to take
|
/// how many items to take
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int Count { get; set; } = 100;
|
public int Count { get; set; } = 100;
|
||||||
|
|
||||||
@ -35,4 +35,4 @@ namespace SharedLibraryCore.Dtos
|
|||||||
Ascending,
|
Ascending,
|
||||||
Descending
|
Descending
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,8 +15,8 @@ namespace SharedLibraryCore.Dtos
|
|||||||
public int LevelInt { get; set; }
|
public int LevelInt { get; set; }
|
||||||
public string IPAddress { get; set; }
|
public string IPAddress { get; set; }
|
||||||
public long NetworkId { get; set; }
|
public long NetworkId { get; set; }
|
||||||
public List<string> Aliases { get; set; }
|
public List<(string, DateTime)> Aliases { get; set; }
|
||||||
public List<string> IPs { get; set; }
|
public List<(string, DateTime)> IPs { get; set; }
|
||||||
public bool HasActivePenalty { get; set; }
|
public bool HasActivePenalty { get; set; }
|
||||||
public string ActivePenaltyType { get; set; }
|
public string ActivePenaltyType { get; set; }
|
||||||
public bool Authenticated { get; set; }
|
public bool Authenticated { get; set; }
|
||||||
@ -29,5 +29,8 @@ namespace SharedLibraryCore.Dtos
|
|||||||
public IDictionary<int, long> LinkedAccounts { get; set; }
|
public IDictionary<int, long> LinkedAccounts { get; set; }
|
||||||
public MetaType? MetaFilterType { get; set; }
|
public MetaType? MetaFilterType { get; set; }
|
||||||
public double? ZScore { get; set; }
|
public double? ZScore { get; set; }
|
||||||
|
public string ConnectProtocolUrl { get;set; }
|
||||||
|
public string CurrentServerName { get; set; }
|
||||||
|
public IGeoLocationResult GeoLocationInfo { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,11 +15,13 @@ namespace SharedLibraryCore.Dtos
|
|||||||
public int MaxClients { get; set; }
|
public int MaxClients { get; set; }
|
||||||
public List<ChatInfo> ChatHistory { get; set; }
|
public List<ChatInfo> ChatHistory { get; set; }
|
||||||
public List<PlayerInfo> Players { get; set; }
|
public List<PlayerInfo> Players { get; set; }
|
||||||
|
public List<Report> Reports { get; set; }
|
||||||
public ClientHistoryInfo ClientHistory { get; set; }
|
public ClientHistoryInfo ClientHistory { get; set; }
|
||||||
public long ID { get; set; }
|
public long ID { get; set; }
|
||||||
public bool Online { get; set; }
|
public bool Online { get; set; }
|
||||||
public string ConnectProtocolUrl { get; set; }
|
public string ConnectProtocolUrl { get; set; }
|
||||||
public string IPAddress { get; set; }
|
public string IPAddress { get; set; }
|
||||||
|
public string ExternalIPAddress { get; set; }
|
||||||
public bool IsPasswordProtected { get; set; }
|
public bool IsPasswordProtected { get; set; }
|
||||||
public string Endpoint => $"{IPAddress}:{Port}";
|
public string Endpoint => $"{IPAddress}:{Port}";
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using SharedLibraryCore.Database.Models;
|
using System;
|
||||||
|
using SharedLibraryCore.Database.Models;
|
||||||
|
|
||||||
namespace SharedLibraryCore.Helpers
|
namespace SharedLibraryCore.Helpers
|
||||||
{
|
{
|
||||||
@ -7,5 +8,6 @@ namespace SharedLibraryCore.Helpers
|
|||||||
public EFClient Target { get; set; }
|
public EFClient Target { get; set; }
|
||||||
public EFClient Origin { get; set; }
|
public EFClient Origin { get; set; }
|
||||||
public string Reason { get; set; }
|
public string Reason { get; set; }
|
||||||
|
public DateTime ReportedOn { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
11
SharedLibraryCore/Interfaces/IGeoLocationResult.cs
Normal file
11
SharedLibraryCore/Interfaces/IGeoLocationResult.cs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
namespace SharedLibraryCore.Interfaces;
|
||||||
|
|
||||||
|
public interface IGeoLocationResult
|
||||||
|
{
|
||||||
|
string Country { get; set; }
|
||||||
|
string CountryCode { get; set; }
|
||||||
|
string Region { get; set; }
|
||||||
|
string ASN { get; set; }
|
||||||
|
string Timezone { get; set; }
|
||||||
|
string Organization { get; set; }
|
||||||
|
}
|
8
SharedLibraryCore/Interfaces/IGeoLocationService.cs
Normal file
8
SharedLibraryCore/Interfaces/IGeoLocationService.cs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SharedLibraryCore.Interfaces;
|
||||||
|
|
||||||
|
public interface IGeoLocationService
|
||||||
|
{
|
||||||
|
Task<IGeoLocationResult> Locate(string address);
|
||||||
|
}
|
@ -55,6 +55,8 @@ namespace SharedLibraryCore.Interfaces
|
|||||||
/// <param name="token"></param>
|
/// <param name="token"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
Task<Dvar<T>> GetDvarAsync<T>(IRConConnection connection, string dvarName, T fallbackValue = default, CancellationToken token = default);
|
Task<Dvar<T>> GetDvarAsync<T>(IRConConnection connection, string dvarName, T fallbackValue = default, CancellationToken token = default);
|
||||||
|
|
||||||
|
void BeginGetDvar(IRConConnection connection, string dvarName, AsyncCallback callback, CancellationToken token = default);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// set value of DVAR by name
|
/// set value of DVAR by name
|
||||||
@ -65,6 +67,8 @@ namespace SharedLibraryCore.Interfaces
|
|||||||
/// <param name="token"></param>
|
/// <param name="token"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
Task<bool> SetDvarAsync(IRConConnection connection, string dvarName, object dvarValue, CancellationToken token = default);
|
Task<bool> SetDvarAsync(IRConConnection connection, string dvarName, object dvarValue, CancellationToken token = default);
|
||||||
|
|
||||||
|
void BeginSetDvar(IRConConnection connection, string dvarName, object dvarValue, AsyncCallback callback, CancellationToken token = default);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// executes a console command on the server
|
/// executes a console command on the server
|
||||||
|
@ -626,6 +626,8 @@ namespace SharedLibraryCore.Database.Models
|
|||||||
Utilities.DefaultLogger.LogInformation("Client {client} is joining the game from {source}", ToString(),
|
Utilities.DefaultLogger.LogInformation("Client {client} is joining the game from {source}", ToString(),
|
||||||
ipAddress.HasValue ? "Status" : "Log");
|
ipAddress.HasValue ? "Status" : "Log");
|
||||||
|
|
||||||
|
GameName = (Reference.Game)CurrentServer.GameName;
|
||||||
|
|
||||||
if (ipAddress != null)
|
if (ipAddress != null)
|
||||||
{
|
{
|
||||||
IPAddress = ipAddress;
|
IPAddress = ipAddress;
|
||||||
|
@ -117,7 +117,7 @@ namespace SharedLibraryCore
|
|||||||
|
|
||||||
public int ClientNum
|
public int ClientNum
|
||||||
{
|
{
|
||||||
get { return Clients.ToArray().Count(p => p != null && !p.IsBot); }
|
get { return Clients.ToArray().Count(p => p != null && Utilities.IsDevelopment || (!p?.IsBot ?? false)); }
|
||||||
}
|
}
|
||||||
|
|
||||||
public int MaxClients { get; protected set; }
|
public int MaxClients { get; protected set; }
|
||||||
@ -222,7 +222,7 @@ namespace SharedLibraryCore
|
|||||||
public GameEvent Broadcast(string message, EFClient sender = null)
|
public GameEvent Broadcast(string message, EFClient sender = null)
|
||||||
{
|
{
|
||||||
var formattedMessage = string.Format(RconParser.Configuration.CommandPrefixes.Say ?? "",
|
var formattedMessage = string.Format(RconParser.Configuration.CommandPrefixes.Say ?? "",
|
||||||
$"{(CustomSayEnabled && GameName == Game.IW4 ? $"{CustomSayName}: " : "")}{message.FormatMessageForEngine(RconParser.Configuration.ColorCodeMapping)}");
|
$"{(CustomSayEnabled && GameName == Game.IW4 ? $"{CustomSayName}: " : "")}{message}");
|
||||||
ServerLogger.LogDebug("All-> {Message}",
|
ServerLogger.LogDebug("All-> {Message}",
|
||||||
message.FormatMessageForEngine(RconParser.Configuration.ColorCodeMapping).StripColors());
|
message.FormatMessageForEngine(RconParser.Configuration.ColorCodeMapping).StripColors());
|
||||||
|
|
||||||
@ -270,8 +270,6 @@ namespace SharedLibraryCore
|
|||||||
/// <param name="targetClient">EFClient to send message to</param>
|
/// <param name="targetClient">EFClient to send message to</param>
|
||||||
protected async Task Tell(string message, EFClient targetClient)
|
protected async Task Tell(string message, EFClient targetClient)
|
||||||
{
|
{
|
||||||
var engineMessage = message.FormatMessageForEngine(RconParser.Configuration.ColorCodeMapping);
|
|
||||||
|
|
||||||
if (!Utilities.IsDevelopment)
|
if (!Utilities.IsDevelopment)
|
||||||
{
|
{
|
||||||
var temporalClientId = targetClient.GetAdditionalProperty<string>("ConnectionClientId");
|
var temporalClientId = targetClient.GetAdditionalProperty<string>("ConnectionClientId");
|
||||||
@ -280,7 +278,7 @@ namespace SharedLibraryCore
|
|||||||
|
|
||||||
var formattedMessage = string.Format(RconParser.Configuration.CommandPrefixes.Tell,
|
var formattedMessage = string.Format(RconParser.Configuration.CommandPrefixes.Tell,
|
||||||
clientNumber,
|
clientNumber,
|
||||||
$"{(CustomSayEnabled && GameName == Game.IW4 ? $"{CustomSayName}: " : "")}{engineMessage}");
|
$"{(CustomSayEnabled && GameName == Game.IW4 ? $"{CustomSayName}: " : "")}{message}");
|
||||||
if (targetClient.ClientNumber > -1 && message.Length > 0 &&
|
if (targetClient.ClientNumber > -1 && message.Length > 0 &&
|
||||||
targetClient.Level != Data.Models.Client.EFClient.Permission.Console)
|
targetClient.Level != Data.Models.Client.EFClient.Permission.Console)
|
||||||
{
|
{
|
||||||
@ -296,13 +294,14 @@ namespace SharedLibraryCore
|
|||||||
if (targetClient.Level == Data.Models.Client.EFClient.Permission.Console)
|
if (targetClient.Level == Data.Models.Client.EFClient.Permission.Console)
|
||||||
{
|
{
|
||||||
Console.ForegroundColor = ConsoleColor.Green;
|
Console.ForegroundColor = ConsoleColor.Green;
|
||||||
|
var cleanMessage = message.FormatMessageForEngine(RconParser.Configuration.ColorCodeMapping)
|
||||||
|
.StripColors();
|
||||||
using (LogContext.PushProperty("Server", ToString()))
|
using (LogContext.PushProperty("Server", ToString()))
|
||||||
{
|
{
|
||||||
ServerLogger.LogInformation("Command output received: {Message}",
|
ServerLogger.LogInformation("Command output received: {Message}", cleanMessage);
|
||||||
engineMessage.StripColors());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Console.WriteLine(engineMessage.StripColors());
|
Console.WriteLine(cleanMessage);
|
||||||
Console.ForegroundColor = ConsoleColor.Gray;
|
Console.ForegroundColor = ConsoleColor.Gray;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -47,13 +47,15 @@ namespace SharedLibraryCore.Services
|
|||||||
private readonly ApplicationConfiguration _appConfig;
|
private readonly ApplicationConfiguration _appConfig;
|
||||||
private readonly IDatabaseContextFactory _contextFactory;
|
private readonly IDatabaseContextFactory _contextFactory;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
private readonly IGeoLocationService _geoLocationService;
|
||||||
|
|
||||||
public ClientService(ILogger<ClientService> logger, IDatabaseContextFactory databaseContextFactory,
|
public ClientService(ILogger<ClientService> logger, IDatabaseContextFactory databaseContextFactory,
|
||||||
ApplicationConfiguration appConfig)
|
ApplicationConfiguration appConfig, IGeoLocationService geoLocationService)
|
||||||
{
|
{
|
||||||
_contextFactory = databaseContextFactory;
|
_contextFactory = databaseContextFactory;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_appConfig = appConfig;
|
_appConfig = appConfig;
|
||||||
|
_geoLocationService = geoLocationService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<EFClient> Create(EFClient entity)
|
public async Task<EFClient> Create(EFClient entity)
|
||||||
@ -101,7 +103,8 @@ namespace SharedLibraryCore.Services
|
|||||||
Level = Permission.User,
|
Level = Permission.User,
|
||||||
FirstConnection = DateTime.UtcNow,
|
FirstConnection = DateTime.UtcNow,
|
||||||
LastConnection = DateTime.UtcNow,
|
LastConnection = DateTime.UtcNow,
|
||||||
NetworkId = entity.NetworkId
|
NetworkId = entity.NetworkId,
|
||||||
|
GameName = (Reference.Game)entity.CurrentServer.GameName
|
||||||
};
|
};
|
||||||
|
|
||||||
_logger.LogDebug("[create] adding {entity} to context", entity.ToString());
|
_logger.LogDebug("[create] adding {entity} to context", entity.ToString());
|
||||||
@ -281,6 +284,8 @@ namespace SharedLibraryCore.Services
|
|||||||
entity.PasswordSalt = temporalClient.PasswordSalt;
|
entity.PasswordSalt = temporalClient.PasswordSalt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
entity.GameName ??= temporalClient.GameName;
|
||||||
|
|
||||||
// update in database
|
// update in database
|
||||||
await context.SaveChangesAsync();
|
await context.SaveChangesAsync();
|
||||||
return entity.ToPartialClient();
|
return entity.ToPartialClient();
|
||||||
@ -355,7 +360,8 @@ namespace SharedLibraryCore.Services
|
|||||||
Level = Permission.User,
|
Level = Permission.User,
|
||||||
FirstConnection = DateTime.UtcNow,
|
FirstConnection = DateTime.UtcNow,
|
||||||
LastConnection = DateTime.UtcNow,
|
LastConnection = DateTime.UtcNow,
|
||||||
NetworkId = entity.NetworkId
|
NetworkId = entity.NetworkId,
|
||||||
|
GameName = (Reference.Game)entity.CurrentServer.GameName
|
||||||
};
|
};
|
||||||
|
|
||||||
if (existingAlias == null)
|
if (existingAlias == null)
|
||||||
@ -782,7 +788,8 @@ namespace SharedLibraryCore.Services
|
|||||||
Password = client.Password,
|
Password = client.Password,
|
||||||
PasswordSalt = client.PasswordSalt,
|
PasswordSalt = client.PasswordSalt,
|
||||||
NetworkId = client.NetworkId,
|
NetworkId = client.NetworkId,
|
||||||
LastConnection = client.LastConnection
|
LastConnection = client.LastConnection,
|
||||||
|
Masked = client.Masked
|
||||||
};
|
};
|
||||||
|
|
||||||
return await iqClients.ToListAsync();
|
return await iqClients.ToListAsync();
|
||||||
@ -895,24 +902,32 @@ namespace SharedLibraryCore.Services
|
|||||||
/// gets the 10 most recently added clients to IW4MAdmin
|
/// gets the 10 most recently added clients to IW4MAdmin
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task<IList<PlayerInfo>> GetRecentClients()
|
public async Task<IList<PlayerInfo>> GetRecentClients(PaginationRequest request)
|
||||||
{
|
{
|
||||||
var startOfPeriod = DateTime.UtcNow.AddHours(-24);
|
var startOfPeriod = DateTime.UtcNow.AddHours(-24);
|
||||||
|
|
||||||
await using var context = _contextFactory.CreateContext(false);
|
await using var context = _contextFactory.CreateContext(false);
|
||||||
var iqClients = context.Clients
|
var iqClients = context.Clients
|
||||||
.Where(_client => _client.CurrentAlias.IPAddress != null)
|
.Where(client => client.CurrentAlias.IPAddress != null)
|
||||||
.Where(_client => _client.FirstConnection >= startOfPeriod)
|
.Where(client => client.FirstConnection >= startOfPeriod)
|
||||||
.OrderByDescending(_client => _client.FirstConnection)
|
.OrderByDescending(client => client.FirstConnection)
|
||||||
.Select(_client => new PlayerInfo
|
.Select(client => new PlayerInfo
|
||||||
{
|
{
|
||||||
ClientId = _client.ClientId,
|
ClientId = client.ClientId,
|
||||||
Name = _client.CurrentAlias.Name,
|
Name = client.CurrentAlias.Name,
|
||||||
IPAddress = _client.CurrentAlias.IPAddress.ConvertIPtoString(),
|
IPAddress = client.CurrentAlias.IPAddress.ConvertIPtoString(),
|
||||||
LastConnection = _client.FirstConnection
|
LastConnection = client.FirstConnection
|
||||||
});
|
})
|
||||||
|
.Skip(request.Offset)
|
||||||
|
.Take(request.Count);
|
||||||
|
|
||||||
return await iqClients.ToListAsync();
|
var clientList = await iqClients.ToListAsync();
|
||||||
|
foreach (var client in clientList)
|
||||||
|
{
|
||||||
|
client.GeoLocationInfo = await _geoLocationService.Locate(client.IPAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
return clientList;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
@ -154,6 +154,7 @@ namespace SharedLibraryCore
|
|||||||
}
|
}
|
||||||
|
|
||||||
str = Regex.Replace(str, @"(\^+((?![a-z]|[A-Z]).){0,1})+", "");
|
str = Regex.Replace(str, @"(\^+((?![a-z]|[A-Z]).){0,1})+", "");
|
||||||
|
str = Regex.Replace(str, @"\(Color::(.{1,16})\)", "");
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,7 +184,7 @@ namespace SharedLibraryCore
|
|||||||
output = output.Replace(match.Value, mapping.TryGetValue(key, out var code) ? code : "");
|
output = output.Replace(match.Value, mapping.TryGetValue(key, out var code) ? code : "");
|
||||||
}
|
}
|
||||||
|
|
||||||
return output.FixIW4ForwardSlash() + mapping[ColorCodes.White.ToString()];
|
return output.FixIW4ForwardSlash();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly IList<string> _zmGameTypes = new[] { "zclassic", "zstandard", "zcleansed", "zgrief" };
|
private static readonly IList<string> _zmGameTypes = new[] { "zclassic", "zstandard", "zcleansed", "zgrief" };
|
||||||
@ -528,32 +529,36 @@ namespace SharedLibraryCore
|
|||||||
public static bool HasPermission<TEntity, TPermission>(this IEnumerable<string> permissionsSet, TEntity entity,
|
public static bool HasPermission<TEntity, TPermission>(this IEnumerable<string> permissionsSet, TEntity entity,
|
||||||
TPermission permission) where TEntity : Enum where TPermission : Enum
|
TPermission permission) where TEntity : Enum where TPermission : Enum
|
||||||
{
|
{
|
||||||
return permissionsSet?.Any(raw =>
|
if (permissionsSet == null)
|
||||||
{
|
{
|
||||||
if (raw == "*")
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var requiredPermission = $"{entity.ToString()}.{permission.ToString()}";
|
||||||
|
var hasAllPermissions = permissionsSet.Any(p => p.Equals("*"));
|
||||||
|
var permissionCheckResult = permissionsSet.Select(p =>
|
||||||
|
{
|
||||||
|
if (p.Equals(requiredPermission, StringComparison.InvariantCultureIgnoreCase))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var split = raw.Split(".");
|
|
||||||
|
|
||||||
if (split.Length != 2)
|
if (p.Equals($"-{requiredPermission}", StringComparison.InvariantCultureIgnoreCase))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Enum.TryParse(typeof(TEntity), split[0], out var e))
|
return (bool?)null;
|
||||||
{
|
}).ToList();
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Enum.TryParse(typeof(TPermission), split[1], out var p))
|
var permissionNegated = permissionCheckResult.Any(result => result.HasValue && !result.Value);
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (e?.Equals(entity) ?? false) && (p?.Equals(permission) ?? false);
|
if (permissionNegated)
|
||||||
}) ?? false;
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasAllPermissions || permissionCheckResult.Any(result => result.HasValue && result.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool HasPermission<TEntity, TPermission>(this ApplicationConfiguration appConfig,
|
public static bool HasPermission<TEntity, TPermission>(this ApplicationConfiguration appConfig,
|
||||||
@ -768,6 +773,11 @@ namespace SharedLibraryCore
|
|||||||
{
|
{
|
||||||
return await server.RconParser.GetDvarAsync(server.RemoteConnection, dvarName, fallbackValue, token);
|
return await server.RconParser.GetDvarAsync(server.RemoteConnection, dvarName, fallbackValue, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void BeginGetDvar(this Server server, string dvarName, AsyncCallback callback, CancellationToken token = default)
|
||||||
|
{
|
||||||
|
server.RconParser.BeginGetDvar(server.RemoteConnection, dvarName, callback, token);
|
||||||
|
}
|
||||||
|
|
||||||
public static async Task<Dvar<T>> GetDvarAsync<T>(this Server server, string dvarName,
|
public static async Task<Dvar<T>> GetDvarAsync<T>(this Server server, string dvarName,
|
||||||
T fallbackValue = default)
|
T fallbackValue = default)
|
||||||
@ -803,6 +813,12 @@ namespace SharedLibraryCore
|
|||||||
{
|
{
|
||||||
await server.RconParser.SetDvarAsync(server.RemoteConnection, dvarName, dvarValue, token);
|
await server.RconParser.SetDvarAsync(server.RemoteConnection, dvarName, dvarValue, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void BeginSetDvar(this Server server, string dvarName, object dvarValue,
|
||||||
|
AsyncCallback callback, CancellationToken token = default)
|
||||||
|
{
|
||||||
|
server.RconParser.BeginSetDvar(server.RemoteConnection, dvarName, dvarValue, callback, token);
|
||||||
|
}
|
||||||
|
|
||||||
public static async Task SetDvarAsync(this Server server, string dvarName, object dvarValue)
|
public static async Task SetDvarAsync(this Server server, string dvarName, object dvarValue)
|
||||||
{
|
{
|
||||||
@ -819,9 +835,17 @@ namespace SharedLibraryCore
|
|||||||
return await ExecuteCommandAsync(server, commandName, default);
|
return await ExecuteCommandAsync(server, commandName, default);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Task<IStatusResponse> GetStatusAsync(this Server server, CancellationToken token)
|
public static async Task<IStatusResponse> GetStatusAsync(this Server server, CancellationToken token)
|
||||||
{
|
{
|
||||||
return server.RconParser.GetStatusAsync(server.RemoteConnection, token);
|
try
|
||||||
|
{
|
||||||
|
return await server.RconParser.GetStatusAsync(server.RemoteConnection, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
catch (TaskCanceledException)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -80,7 +80,7 @@ namespace WebfrontCore.Controllers.API
|
|||||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||||
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
||||||
public async Task<IActionResult> LoginAsync([FromRoute] int clientId,
|
public async Task<IActionResult> Login([FromRoute] int clientId,
|
||||||
[FromBody, Required] PasswordRequest request)
|
[FromBody, Required] PasswordRequest request)
|
||||||
{
|
{
|
||||||
if (clientId == 0)
|
if (clientId == 0)
|
||||||
@ -145,7 +145,7 @@ namespace WebfrontCore.Controllers.API
|
|||||||
[HttpPost("{clientId:int}/logout")]
|
[HttpPost("{clientId:int}/logout")]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
||||||
public async Task<IActionResult> LogoutAsync()
|
public async Task<IActionResult> Logout()
|
||||||
{
|
{
|
||||||
if (Authorized)
|
if (Authorized)
|
||||||
{
|
{
|
||||||
@ -170,4 +170,4 @@ namespace WebfrontCore.Controllers.API
|
|||||||
public string Password { get; set; }
|
public string Password { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
25
WebfrontCore/Controllers/API/PenaltyController.cs
Normal file
25
WebfrontCore/Controllers/API/PenaltyController.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using SharedLibraryCore;
|
||||||
|
using SharedLibraryCore.Interfaces;
|
||||||
|
using WebfrontCore.QueryHelpers.Models;
|
||||||
|
|
||||||
|
namespace WebfrontCore.Controllers.API;
|
||||||
|
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
public class PenaltyController : BaseController
|
||||||
|
{
|
||||||
|
private readonly IResourceQueryHelper<BanInfoRequest, BanInfo> _banInfoQueryHelper;
|
||||||
|
|
||||||
|
public PenaltyController(IManager manager, IResourceQueryHelper<BanInfoRequest, BanInfo> banInfoQueryHelper) : base(manager)
|
||||||
|
{
|
||||||
|
_banInfoQueryHelper = banInfoQueryHelper;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("BanInfo/{clientName}")]
|
||||||
|
public async Task<IActionResult> BanInfo(BanInfoRequest request)
|
||||||
|
{
|
||||||
|
var result = await _banInfoQueryHelper.QueryResource(request);
|
||||||
|
return Json(result);
|
||||||
|
}
|
||||||
|
}
|
@ -19,11 +19,11 @@ namespace WebfrontCore.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task<IActionResult> LoginAsync(int clientId, string password)
|
public async Task<IActionResult> Login(int clientId, string password)
|
||||||
{
|
{
|
||||||
if (clientId == 0 || string.IsNullOrEmpty(password))
|
if (clientId == 0 || string.IsNullOrEmpty(password))
|
||||||
{
|
{
|
||||||
return Unauthorized();
|
return Unauthorized("Invalid credentials");
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
@ -63,20 +63,20 @@ namespace WebfrontCore.Controllers
|
|||||||
: HttpContext.Connection.RemoteIpAddress.ToString()
|
: HttpContext.Connection.RemoteIpAddress.ToString()
|
||||||
});
|
});
|
||||||
|
|
||||||
return Ok();
|
return Ok($"Welcome {privilegedClient.Name}. You are now logged in");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
return Unauthorized();
|
return Unauthorized("Could not validate credentials");
|
||||||
}
|
}
|
||||||
|
|
||||||
return Unauthorized();
|
return Unauthorized("Invalid credentials");
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task<IActionResult> LogoutAsync()
|
public async Task<IActionResult> Logout()
|
||||||
{
|
{
|
||||||
if (Authorized)
|
if (Authorized)
|
||||||
{
|
{
|
||||||
|
@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Mvc;
|
|||||||
using SharedLibraryCore;
|
using SharedLibraryCore;
|
||||||
using SharedLibraryCore.Commands;
|
using SharedLibraryCore.Commands;
|
||||||
using SharedLibraryCore.Configuration;
|
using SharedLibraryCore.Configuration;
|
||||||
|
using SharedLibraryCore.Dtos;
|
||||||
using SharedLibraryCore.Interfaces;
|
using SharedLibraryCore.Interfaces;
|
||||||
using WebfrontCore.Permissions;
|
using WebfrontCore.Permissions;
|
||||||
using WebfrontCore.ViewModels;
|
using WebfrontCore.ViewModels;
|
||||||
@ -69,25 +70,25 @@ namespace WebfrontCore.Controllers
|
|||||||
|
|
||||||
public IActionResult BanForm()
|
public IActionResult BanForm()
|
||||||
{
|
{
|
||||||
var info = new ActionInfo()
|
var info = new ActionInfo
|
||||||
{
|
{
|
||||||
ActionButtonLabel = Localization["WEBFRONT_ACTION_BAN_NAME"],
|
ActionButtonLabel = Localization["WEBFRONT_ACTION_BAN_NAME"],
|
||||||
Name = "Ban",
|
Name = "Ban",
|
||||||
Inputs = new List<InputInfo>()
|
Inputs = new List<InputInfo>
|
||||||
{
|
{
|
||||||
new InputInfo()
|
new()
|
||||||
{
|
{
|
||||||
Name = "Reason",
|
Name = "Reason",
|
||||||
Label = Localization["WEBFRONT_ACTION_LABEL_REASON"],
|
Label = Localization["WEBFRONT_ACTION_LABEL_REASON"],
|
||||||
},
|
},
|
||||||
new InputInfo()
|
new()
|
||||||
{
|
{
|
||||||
Name = "PresetReason",
|
Name = "PresetReason",
|
||||||
Type = "select",
|
Type = "select",
|
||||||
Label = Localization["WEBFRONT_ACTION_LABEL_PRESET_REASON"],
|
Label = Localization["WEBFRONT_ACTION_LABEL_PRESET_REASON"],
|
||||||
Values = GetPresetPenaltyReasons()
|
Values = GetPresetPenaltyReasons()
|
||||||
},
|
},
|
||||||
new InputInfo()
|
new()
|
||||||
{
|
{
|
||||||
Name = "Duration",
|
Name = "Duration",
|
||||||
Label = Localization["WEBFRONT_ACTION_LABEL_DURATION"],
|
Label = Localization["WEBFRONT_ACTION_LABEL_DURATION"],
|
||||||
@ -134,7 +135,7 @@ namespace WebfrontCore.Controllers
|
|||||||
|
|
||||||
var server = Manager.GetServers().First();
|
var server = Manager.GetServers().First();
|
||||||
|
|
||||||
return await Task.FromResult(RedirectToAction("ExecuteAsync", "Console", new
|
return await Task.FromResult(RedirectToAction("Execute", "Console", new
|
||||||
{
|
{
|
||||||
serverId = server.EndPoint,
|
serverId = server.EndPoint,
|
||||||
command
|
command
|
||||||
@ -143,13 +144,13 @@ namespace WebfrontCore.Controllers
|
|||||||
|
|
||||||
public IActionResult UnbanForm()
|
public IActionResult UnbanForm()
|
||||||
{
|
{
|
||||||
var info = new ActionInfo()
|
var info = new ActionInfo
|
||||||
{
|
{
|
||||||
ActionButtonLabel = Localization["WEBFRONT_ACTION_UNBAN_NAME"],
|
ActionButtonLabel = Localization["WEBFRONT_ACTION_UNBAN_NAME"],
|
||||||
Name = "Unban",
|
Name = "Unban",
|
||||||
Inputs = new List<InputInfo>()
|
Inputs = new List<InputInfo>
|
||||||
{
|
{
|
||||||
new InputInfo()
|
new()
|
||||||
{
|
{
|
||||||
Name = "Reason",
|
Name = "Reason",
|
||||||
Label = Localization["WEBFRONT_ACTION_LABEL_REASON"],
|
Label = Localization["WEBFRONT_ACTION_LABEL_REASON"],
|
||||||
@ -162,57 +163,59 @@ namespace WebfrontCore.Controllers
|
|||||||
return View("_ActionForm", info);
|
return View("_ActionForm", info);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IActionResult> UnbanAsync(int targetId, string Reason)
|
public async Task<IActionResult> UnbanAsync(int targetId, string reason)
|
||||||
{
|
{
|
||||||
var server = Manager.GetServers().First();
|
var server = Manager.GetServers().First();
|
||||||
|
|
||||||
return await Task.FromResult(RedirectToAction("ExecuteAsync", "Console", new
|
return await Task.FromResult(RedirectToAction("Execute", "Console", new
|
||||||
{
|
{
|
||||||
serverId = server.EndPoint,
|
serverId = server.EndPoint,
|
||||||
command = $"{_appConfig.CommandPrefix}{_unbanCommandName} @{targetId} {Reason}"
|
command = $"{_appConfig.CommandPrefix}{_unbanCommandName} @{targetId} {reason}"
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
public IActionResult LoginForm()
|
public IActionResult LoginForm()
|
||||||
{
|
{
|
||||||
var login = new ActionInfo()
|
var login = new ActionInfo
|
||||||
{
|
{
|
||||||
ActionButtonLabel = Localization["WEBFRONT_ACTION_LOGIN_NAME"],
|
ActionButtonLabel = Localization["WEBFRONT_ACTION_LOGIN_NAME"],
|
||||||
Name = "Login",
|
Name = "Login",
|
||||||
Inputs = new List<InputInfo>()
|
Inputs = new List<InputInfo>
|
||||||
{
|
{
|
||||||
new InputInfo()
|
new()
|
||||||
{
|
{
|
||||||
Name = "clientId",
|
Name = "clientId",
|
||||||
Label = Localization["WEBFRONT_ACTION_LABEL_ID"]
|
Label = Localization["WEBFRONT_ACTION_LABEL_ID"],
|
||||||
|
Required = true
|
||||||
},
|
},
|
||||||
new InputInfo()
|
new()
|
||||||
{
|
{
|
||||||
Name = "Password",
|
Name = "Password",
|
||||||
Label = Localization["WEBFRONT_ACTION_LABEL_PASSWORD"],
|
Label = Localization["WEBFRONT_ACTION_LABEL_PASSWORD"],
|
||||||
Type = "password",
|
Type = "password",
|
||||||
|
Required = true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Action = "LoginAsync"
|
Action = "Login"
|
||||||
};
|
};
|
||||||
|
|
||||||
return View("_ActionForm", login);
|
return View("_ActionForm", login);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IActionResult> LoginAsync(int clientId, string password)
|
public async Task<IActionResult> Login(int clientId, string password)
|
||||||
{
|
{
|
||||||
return await Task.FromResult(RedirectToAction("LoginAsync", "Account", new {clientId, password}));
|
return await Task.FromResult(RedirectToAction("Login", "Account", new {clientId, password}));
|
||||||
}
|
}
|
||||||
|
|
||||||
public IActionResult EditForm()
|
public IActionResult EditForm()
|
||||||
{
|
{
|
||||||
var info = new ActionInfo()
|
var info = new ActionInfo
|
||||||
{
|
{
|
||||||
ActionButtonLabel = Localization["WEBFRONT_ACTION_LABEL_EDIT"],
|
ActionButtonLabel = Localization["WEBFRONT_ACTION_LABEL_EDIT"],
|
||||||
Name = "Edit",
|
Name = "Edit",
|
||||||
Inputs = new List<InputInfo>()
|
Inputs = new List<InputInfo>
|
||||||
{
|
{
|
||||||
new InputInfo()
|
new()
|
||||||
{
|
{
|
||||||
Name = "level",
|
Name = "level",
|
||||||
Label = Localization["WEBFRONT_PROFILE_LEVEL"],
|
Label = Localization["WEBFRONT_PROFILE_LEVEL"],
|
||||||
@ -235,7 +238,7 @@ namespace WebfrontCore.Controllers
|
|||||||
{
|
{
|
||||||
var server = Manager.GetServers().First();
|
var server = Manager.GetServers().First();
|
||||||
|
|
||||||
return await Task.FromResult(RedirectToAction("ExecuteAsync", "Console", new
|
return await Task.FromResult(RedirectToAction("Execute", "Console", new
|
||||||
{
|
{
|
||||||
serverId = server.EndPoint,
|
serverId = server.EndPoint,
|
||||||
command = $"{_appConfig.CommandPrefix}{_setLevelCommandName} @{targetId} {level}"
|
command = $"{_appConfig.CommandPrefix}{_setLevelCommandName} @{targetId} {level}"
|
||||||
@ -244,7 +247,7 @@ namespace WebfrontCore.Controllers
|
|||||||
|
|
||||||
public IActionResult GenerateLoginTokenForm()
|
public IActionResult GenerateLoginTokenForm()
|
||||||
{
|
{
|
||||||
var info = new ActionInfo()
|
var info = new ActionInfo
|
||||||
{
|
{
|
||||||
ActionButtonLabel = Localization["WEBFRONT_ACTION_LABEL_GENERATE_TOKEN"],
|
ActionButtonLabel = Localization["WEBFRONT_ACTION_LABEL_GENERATE_TOKEN"],
|
||||||
Name = "GenerateLoginToken",
|
Name = "GenerateLoginToken",
|
||||||
@ -267,19 +270,19 @@ namespace WebfrontCore.Controllers
|
|||||||
|
|
||||||
public IActionResult ChatForm(long id)
|
public IActionResult ChatForm(long id)
|
||||||
{
|
{
|
||||||
var info = new ActionInfo()
|
var info = new ActionInfo
|
||||||
{
|
{
|
||||||
ActionButtonLabel = Localization["WEBFRONT_ACTION_LABEL_SUBMIT_MESSAGE"],
|
ActionButtonLabel = Localization["WEBFRONT_ACTION_LABEL_SUBMIT_MESSAGE"],
|
||||||
Name = "Chat",
|
Name = "Chat",
|
||||||
Inputs = new List<InputInfo>
|
Inputs = new List<InputInfo>
|
||||||
{
|
{
|
||||||
new InputInfo()
|
new()
|
||||||
{
|
{
|
||||||
Name = "message",
|
Name = "message",
|
||||||
Type = "text",
|
Type = "text",
|
||||||
Label = Localization["WEBFRONT_ACTION_LABEL_MESSAGE"]
|
Label = Localization["WEBFRONT_ACTION_LABEL_MESSAGE"]
|
||||||
},
|
},
|
||||||
new InputInfo()
|
new()
|
||||||
{
|
{
|
||||||
Name = "id",
|
Name = "id",
|
||||||
Value = id.ToString(),
|
Value = id.ToString(),
|
||||||
@ -294,7 +297,7 @@ namespace WebfrontCore.Controllers
|
|||||||
|
|
||||||
public async Task<IActionResult> ChatAsync(long id, string message)
|
public async Task<IActionResult> ChatAsync(long id, string message)
|
||||||
{
|
{
|
||||||
var server = Manager.GetServers().First(_server => _server.EndPoint == id);
|
var server = Manager.GetServers().First(server => server.EndPoint == id);
|
||||||
|
|
||||||
server.ChatHistory.Add(new SharedLibraryCore.Dtos.ChatInfo()
|
server.ChatHistory.Add(new SharedLibraryCore.Dtos.ChatInfo()
|
||||||
{
|
{
|
||||||
@ -305,41 +308,55 @@ namespace WebfrontCore.Controllers
|
|||||||
Time = DateTime.Now
|
Time = DateTime.Now
|
||||||
});
|
});
|
||||||
|
|
||||||
return await Task.FromResult(RedirectToAction("ExecuteAsync", "Console", new
|
return await Task.FromResult(RedirectToAction("Execute", "Console", new
|
||||||
{
|
{
|
||||||
serverId = server.EndPoint,
|
serverId = server.EndPoint,
|
||||||
command = $"{_appConfig.CommandPrefix}{_sayCommandName} {message}"
|
command = $"{_appConfig.CommandPrefix}{_sayCommandName} {message}"
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IActionResult> RecentClientsForm()
|
public async Task<IActionResult> RecentClientsForm(PaginationRequest request)
|
||||||
{
|
{
|
||||||
var clients = await Manager.GetClientService().GetRecentClients();
|
ViewBag.First = request.Offset == 0;
|
||||||
foreach (var client in clients)
|
|
||||||
|
if (request.Count > 20)
|
||||||
{
|
{
|
||||||
client.IPAddress =
|
request.Count = 20;
|
||||||
_appConfig.HasPermission(Client.Level, WebfrontEntity.IPAddress, WebfrontPermission.Read)
|
|
||||||
? client.IPAddress
|
|
||||||
: null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var clients = await Manager.GetClientService().GetRecentClients(request);
|
||||||
|
|
||||||
return View("~/Views/Shared/Components/Client/_RecentClients.cshtml", clients);
|
return request.Offset == 0
|
||||||
|
? View("~/Views/Shared/Components/Client/_RecentClientsContainer.cshtml", clients)
|
||||||
|
: View("~/Views/Shared/Components/Client/_RecentClients.cshtml", clients);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IActionResult RecentReportsForm()
|
||||||
|
{
|
||||||
|
var serverInfo = Manager.GetServers().Select(server =>
|
||||||
|
new ServerInfo
|
||||||
|
{
|
||||||
|
Name = server.Hostname,
|
||||||
|
Reports = server.Reports.Where(report => (DateTime.UtcNow - report.ReportedOn).TotalHours <= 24).ToList()
|
||||||
|
});
|
||||||
|
|
||||||
|
return View("Partials/_Reports", serverInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IActionResult FlagForm()
|
public IActionResult FlagForm()
|
||||||
{
|
{
|
||||||
var info = new ActionInfo()
|
var info = new ActionInfo
|
||||||
{
|
{
|
||||||
ActionButtonLabel = Localization["WEBFRONT_ACTION_FLAG_NAME"],
|
ActionButtonLabel = Localization["WEBFRONT_ACTION_FLAG_NAME"],
|
||||||
Name = "Flag",
|
Name = "Flag",
|
||||||
Inputs = new List<InputInfo>()
|
Inputs = new List<InputInfo>
|
||||||
{
|
{
|
||||||
new InputInfo()
|
new()
|
||||||
{
|
{
|
||||||
Name = "reason",
|
Name = "reason",
|
||||||
Label = Localization["WEBFRONT_ACTION_LABEL_REASON"],
|
Label = Localization["WEBFRONT_ACTION_LABEL_REASON"],
|
||||||
},
|
},
|
||||||
new InputInfo()
|
new()
|
||||||
{
|
{
|
||||||
Name = "PresetReason",
|
Name = "PresetReason",
|
||||||
Type = "select",
|
Type = "select",
|
||||||
@ -358,7 +375,7 @@ namespace WebfrontCore.Controllers
|
|||||||
{
|
{
|
||||||
var server = Manager.GetServers().First();
|
var server = Manager.GetServers().First();
|
||||||
|
|
||||||
return await Task.FromResult(RedirectToAction("ExecuteAsync", "Console", new
|
return await Task.FromResult(RedirectToAction("Execute", "Console", new
|
||||||
{
|
{
|
||||||
serverId = server.EndPoint,
|
serverId = server.EndPoint,
|
||||||
command = $"{_appConfig.CommandPrefix}{_flagCommandName} @{targetId} {presetReason ?? reason}"
|
command = $"{_appConfig.CommandPrefix}{_flagCommandName} @{targetId} {presetReason ?? reason}"
|
||||||
@ -367,13 +384,13 @@ namespace WebfrontCore.Controllers
|
|||||||
|
|
||||||
public IActionResult UnflagForm()
|
public IActionResult UnflagForm()
|
||||||
{
|
{
|
||||||
var info = new ActionInfo()
|
var info = new ActionInfo
|
||||||
{
|
{
|
||||||
ActionButtonLabel = Localization["WEBFRONT_ACTION_UNFLAG_NAME"],
|
ActionButtonLabel = Localization["WEBFRONT_ACTION_UNFLAG_NAME"],
|
||||||
Name = "Unflag",
|
Name = "Unflag",
|
||||||
Inputs = new List<InputInfo>()
|
Inputs = new List<InputInfo>
|
||||||
{
|
{
|
||||||
new InputInfo()
|
new()
|
||||||
{
|
{
|
||||||
Name = "reason",
|
Name = "reason",
|
||||||
Label = Localization["WEBFRONT_ACTION_LABEL_REASON"],
|
Label = Localization["WEBFRONT_ACTION_LABEL_REASON"],
|
||||||
@ -390,7 +407,7 @@ namespace WebfrontCore.Controllers
|
|||||||
{
|
{
|
||||||
var server = Manager.GetServers().First();
|
var server = Manager.GetServers().First();
|
||||||
|
|
||||||
return await Task.FromResult(RedirectToAction("ExecuteAsync", "Console", new
|
return await Task.FromResult(RedirectToAction("Execute", "Console", new
|
||||||
{
|
{
|
||||||
serverId = server.EndPoint,
|
serverId = server.EndPoint,
|
||||||
command = $"{_appConfig.CommandPrefix}{_unflagCommandName} @{targetId} {reason}"
|
command = $"{_appConfig.CommandPrefix}{_unflagCommandName} @{targetId} {reason}"
|
||||||
@ -399,25 +416,25 @@ namespace WebfrontCore.Controllers
|
|||||||
|
|
||||||
public IActionResult KickForm(int id)
|
public IActionResult KickForm(int id)
|
||||||
{
|
{
|
||||||
var info = new ActionInfo()
|
var info = new ActionInfo
|
||||||
{
|
{
|
||||||
ActionButtonLabel = Localization["WEBFRONT_ACTION_KICK_NAME"],
|
ActionButtonLabel = Localization["WEBFRONT_ACTION_KICK_NAME"],
|
||||||
Name = "Kick",
|
Name = "Kick",
|
||||||
Inputs = new List<InputInfo>()
|
Inputs = new List<InputInfo>
|
||||||
{
|
{
|
||||||
new InputInfo()
|
new()
|
||||||
{
|
{
|
||||||
Name = "reason",
|
Name = "reason",
|
||||||
Label = Localization["WEBFRONT_ACTION_LABEL_REASON"],
|
Label = Localization["WEBFRONT_ACTION_LABEL_REASON"],
|
||||||
},
|
},
|
||||||
new InputInfo()
|
new()
|
||||||
{
|
{
|
||||||
Name = "PresetReason",
|
Name = "PresetReason",
|
||||||
Type = "select",
|
Type = "select",
|
||||||
Label = Localization["WEBFRONT_ACTION_LABEL_PRESET_REASON"],
|
Label = Localization["WEBFRONT_ACTION_LABEL_PRESET_REASON"],
|
||||||
Values = GetPresetPenaltyReasons()
|
Values = GetPresetPenaltyReasons()
|
||||||
},
|
},
|
||||||
new InputInfo()
|
new()
|
||||||
{
|
{
|
||||||
Name = "targetId",
|
Name = "targetId",
|
||||||
Type = "hidden",
|
Type = "hidden",
|
||||||
@ -433,14 +450,14 @@ namespace WebfrontCore.Controllers
|
|||||||
|
|
||||||
public async Task<IActionResult> KickAsync(int targetId, string reason, string presetReason = null)
|
public async Task<IActionResult> KickAsync(int targetId, string reason, string presetReason = null)
|
||||||
{
|
{
|
||||||
var client = Manager.GetActiveClients().FirstOrDefault(_client => _client.ClientId == targetId);
|
var client = Manager.GetActiveClients().FirstOrDefault(client => client.ClientId == targetId);
|
||||||
|
|
||||||
if (client == null)
|
if (client == null)
|
||||||
{
|
{
|
||||||
return BadRequest(Localization["WEBFRONT_ACTION_KICK_DISCONNECT"]);
|
return BadRequest(Localization["WEBFRONT_ACTION_KICK_DISCONNECT"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return await Task.FromResult(RedirectToAction("ExecuteAsync", "Console", new
|
return await Task.FromResult(RedirectToAction("Execute", "Console", new
|
||||||
{
|
{
|
||||||
serverId = client.CurrentServer.EndPoint,
|
serverId = client.CurrentServer.EndPoint,
|
||||||
command = $"{_appConfig.CommandPrefix}{_kickCommandName} {client.ClientNumber} {presetReason ?? reason}"
|
command = $"{_appConfig.CommandPrefix}{_kickCommandName} {client.ClientNumber} {presetReason ?? reason}"
|
||||||
@ -449,9 +466,9 @@ namespace WebfrontCore.Controllers
|
|||||||
|
|
||||||
private Dictionary<string, string> GetPresetPenaltyReasons() => _appConfig.PresetPenaltyReasons.Values
|
private Dictionary<string, string> GetPresetPenaltyReasons() => _appConfig.PresetPenaltyReasons.Values
|
||||||
.Concat(_appConfig.GlobalRules)
|
.Concat(_appConfig.GlobalRules)
|
||||||
.Concat(_appConfig.Servers.SelectMany(server => server.Rules ?? new string[0]))
|
.Concat(_appConfig.Servers.SelectMany(server => server.Rules ?? Array.Empty<string>()))
|
||||||
.Distinct()
|
.Distinct()
|
||||||
.Select((value, index) => new
|
.Select((value, _) => new
|
||||||
{
|
{
|
||||||
Value = value
|
Value = value
|
||||||
})
|
})
|
||||||
|
@ -22,14 +22,22 @@ namespace WebfrontCore.Controllers
|
|||||||
{
|
{
|
||||||
private readonly IMetaServiceV2 _metaService;
|
private readonly IMetaServiceV2 _metaService;
|
||||||
private readonly StatsConfiguration _config;
|
private readonly StatsConfiguration _config;
|
||||||
|
private readonly IGeoLocationService _geoLocationService;
|
||||||
|
|
||||||
public ClientController(IManager manager, IMetaServiceV2 metaService, StatsConfiguration config) : base(manager)
|
public ClientController(IManager manager, IMetaServiceV2 metaService, StatsConfiguration config,
|
||||||
|
IGeoLocationService geoLocationService) : base(manager)
|
||||||
{
|
{
|
||||||
_metaService = metaService;
|
_metaService = metaService;
|
||||||
_config = config;
|
_config = config;
|
||||||
|
_geoLocationService = geoLocationService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IActionResult> ProfileAsync(int id, MetaType? metaFilterType, CancellationToken token = default)
|
[Obsolete]
|
||||||
|
public IActionResult ProfileAsync(int id, MetaType? metaFilterType,
|
||||||
|
CancellationToken token = default) => RedirectToAction("Profile", "Client", new
|
||||||
|
{ id, metaFilterType });
|
||||||
|
|
||||||
|
public async Task<IActionResult> Profile(int id, MetaType? metaFilterType, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
var client = await Manager.GetClientService().Get(id);
|
var client = await Manager.GetClientService().Get(id);
|
||||||
|
|
||||||
@ -43,7 +51,8 @@ namespace WebfrontCore.Controllers
|
|||||||
|
|
||||||
var persistentMetaTask = new[]
|
var persistentMetaTask = new[]
|
||||||
{
|
{
|
||||||
_metaService.GetPersistentMetaByLookup(EFMeta.ClientTagV2, EFMeta.ClientTagNameV2, client.ClientId, token),
|
_metaService.GetPersistentMetaByLookup(EFMeta.ClientTagV2, EFMeta.ClientTagNameV2, client.ClientId,
|
||||||
|
token),
|
||||||
_metaService.GetPersistentMeta("GravatarEmail", client.ClientId, token)
|
_metaService.GetPersistentMeta("GravatarEmail", client.ClientId, token)
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -74,6 +83,7 @@ namespace WebfrontCore.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
displayLevel = string.IsNullOrEmpty(client.Tag) ? displayLevel : $"{displayLevel} ({client.Tag})";
|
displayLevel = string.IsNullOrEmpty(client.Tag) ? displayLevel : $"{displayLevel} ({client.Tag})";
|
||||||
|
var ingameClient = Manager.GetActiveClients().FirstOrDefault(c => c.ClientId == client.ClientId);
|
||||||
|
|
||||||
var clientDto = new PlayerInfo
|
var clientDto = new PlayerInfo
|
||||||
{
|
{
|
||||||
@ -81,29 +91,38 @@ namespace WebfrontCore.Controllers
|
|||||||
Level = displayLevel,
|
Level = displayLevel,
|
||||||
LevelInt = displayLevelInt,
|
LevelInt = displayLevelInt,
|
||||||
ClientId = client.ClientId,
|
ClientId = client.ClientId,
|
||||||
IPAddress = PermissionsSet.HasPermission(WebfrontEntity.IPAddress, WebfrontPermission.Read) ? client.IPAddressString : null,
|
IPAddress = PermissionsSet.HasPermission(WebfrontEntity.ClientIPAddress, WebfrontPermission.Read)
|
||||||
|
? client.IPAddressString
|
||||||
|
: null,
|
||||||
NetworkId = client.NetworkId,
|
NetworkId = client.NetworkId,
|
||||||
Meta = new List<InformationResponse>(),
|
Meta = new List<InformationResponse>(),
|
||||||
Aliases = client.AliasLink.Children
|
Aliases = client.AliasLink.Children
|
||||||
.Select(_alias => _alias.Name)
|
.Select(alias => (alias.Name, alias.DateAdded))
|
||||||
.GroupBy(_alias => _alias.StripColors())
|
.GroupBy(alias => alias.Name.StripColors())
|
||||||
// we want the longest "duplicate" name
|
// we want the longest "duplicate" name
|
||||||
.Select(_grp => _grp.OrderByDescending(_name => _name.Length).First())
|
.Select(grp => grp.OrderByDescending(item => item.Name.Length).First())
|
||||||
.Distinct()
|
.Distinct()
|
||||||
.OrderBy(a => a)
|
|
||||||
.ToList(),
|
.ToList(),
|
||||||
IPs = PermissionsSet.HasPermission(WebfrontEntity.IPAddress, WebfrontPermission.Read) ? client.AliasLink.Children
|
IPs = PermissionsSet.HasPermission(WebfrontEntity.ClientIPAddress, WebfrontPermission.Read)
|
||||||
.Where(i => i.IPAddress != null)
|
? client.AliasLink.Children
|
||||||
.OrderByDescending(i => i.DateAdded)
|
.Select(alias => (alias.IPAddress.ConvertIPtoString(), alias.DateAdded))
|
||||||
.Select(i => i.IPAddress.ConvertIPtoString())
|
.GroupBy(alias => alias.Item1)
|
||||||
.Prepend(client.CurrentAlias.IPAddress.ConvertIPtoString())
|
.Select(grp => grp.OrderByDescending(item => item.DateAdded).First())
|
||||||
.Distinct()
|
.Distinct()
|
||||||
.ToList() : new List<string>(),
|
.ToList()
|
||||||
HasActivePenalty = activePenalties.Any(_penalty => _penalty.Type != EFPenalty.PenaltyType.Flag),
|
: new List<(string, DateTime)>(),
|
||||||
Online = Manager.GetActiveClients().FirstOrDefault(c => c.ClientId == client.ClientId) != null,
|
HasActivePenalty = activePenalties.Any(penalty => penalty.Type != EFPenalty.PenaltyType.Flag),
|
||||||
|
Online = ingameClient != null,
|
||||||
TimeOnline = (DateTime.UtcNow - client.LastConnection).HumanizeForCurrentCulture(),
|
TimeOnline = (DateTime.UtcNow - client.LastConnection).HumanizeForCurrentCulture(),
|
||||||
LinkedAccounts = client.LinkedAccounts,
|
LinkedAccounts = client.LinkedAccounts,
|
||||||
MetaFilterType = metaFilterType
|
MetaFilterType = metaFilterType,
|
||||||
|
ConnectProtocolUrl = ingameClient?.CurrentServer.EventParser.URLProtocolFormat.FormatExt(
|
||||||
|
ingameClient.CurrentServer.ResolvedIpEndPoint.Address.IsInternal()
|
||||||
|
? Program.Manager.ExternalIPAddress
|
||||||
|
: ingameClient.CurrentServer.IP,
|
||||||
|
ingameClient.CurrentServer.Port),
|
||||||
|
CurrentServerName = ingameClient?.CurrentServer?.Hostname,
|
||||||
|
GeoLocationInfo = await _geoLocationService.Locate(client.IPAddressString)
|
||||||
};
|
};
|
||||||
|
|
||||||
var meta = await _metaService.GetRuntimeMeta<InformationResponse>(new ClientPaginationRequest
|
var meta = await _metaService.GetRuntimeMeta<InformationResponse>(new ClientPaginationRequest
|
||||||
@ -126,9 +145,9 @@ namespace WebfrontCore.Controllers
|
|||||||
clientDto.Meta.AddRange(Authorized ? meta : meta.Where(m => !m.IsSensitive));
|
clientDto.Meta.AddRange(Authorized ? meta : meta.Where(m => !m.IsSensitive));
|
||||||
|
|
||||||
var strippedName = clientDto.Name.StripColors();
|
var strippedName = clientDto.Name.StripColors();
|
||||||
ViewBag.Title = strippedName.Substring(strippedName.Length - 1).ToLower()[0] == 's' ?
|
ViewBag.Title = strippedName.Substring(strippedName.Length - 1).ToLower()[0] == 's'
|
||||||
strippedName + "'" :
|
? strippedName + "'"
|
||||||
strippedName + "'s";
|
: strippedName + "'s";
|
||||||
ViewBag.Title += " " + Localization["WEBFRONT_CLIENT_PROFILE_TITLE"];
|
ViewBag.Title += " " + Localization["WEBFRONT_CLIENT_PROFILE_TITLE"];
|
||||||
ViewBag.Description = $"Client information for {strippedName}";
|
ViewBag.Description = $"Client information for {strippedName}";
|
||||||
ViewBag.Keywords = $"IW4MAdmin, client, profile, {strippedName}";
|
ViewBag.Keywords = $"IW4MAdmin, client, profile, {strippedName}";
|
||||||
@ -137,13 +156,13 @@ namespace WebfrontCore.Controllers
|
|||||||
return View("Profile/Index", clientDto);
|
return View("Profile/Index", clientDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IActionResult> PrivilegedAsync()
|
public async Task<IActionResult> Privileged()
|
||||||
{
|
{
|
||||||
if (Manager.GetApplicationSettings().Configuration().EnablePrivilegedUserPrivacy && !Authorized)
|
if (Manager.GetApplicationSettings().Configuration().EnablePrivilegedUserPrivacy && !Authorized)
|
||||||
{
|
{
|
||||||
return RedirectToAction("Index", "Home");
|
return RedirectToAction("Index", "Home");
|
||||||
}
|
}
|
||||||
|
|
||||||
var admins = (await Manager.GetClientService().GetPrivilegedClients())
|
var admins = (await Manager.GetClientService().GetPrivilegedClients())
|
||||||
.OrderByDescending(_client => _client.Level)
|
.OrderByDescending(_client => _client.Level)
|
||||||
.ThenBy(_client => _client.Name);
|
.ThenBy(_client => _client.Name);
|
||||||
@ -157,10 +176,12 @@ namespace WebfrontCore.Controllers
|
|||||||
adminsDict.Add(admin.Level, new List<ClientInfo>());
|
adminsDict.Add(admin.Level, new List<ClientInfo>());
|
||||||
}
|
}
|
||||||
|
|
||||||
adminsDict[admin.Level].Add(new ClientInfo()
|
adminsDict[admin.Level].Add(new ClientInfo
|
||||||
{
|
{
|
||||||
Name = admin.Name,
|
Name = admin.Name,
|
||||||
ClientId = admin.ClientId
|
ClientId = admin.ClientId,
|
||||||
|
LastConnection = admin.LastConnection,
|
||||||
|
IsMasked = admin.Masked
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -171,7 +192,7 @@ namespace WebfrontCore.Controllers
|
|||||||
return View("Privileged/Index", adminsDict);
|
return View("Privileged/Index", adminsDict);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IActionResult> FindAsync(string clientName)
|
public async Task<IActionResult> Find(string clientName)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(clientName))
|
if (string.IsNullOrWhiteSpace(clientName))
|
||||||
{
|
{
|
||||||
@ -189,11 +210,13 @@ namespace WebfrontCore.Controllers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ViewBag.Title = $"{clientsDto.Count} {Localization["WEBFRONT_CLIENT_SEARCH_MATCHING"]} \"{clientName}\"";
|
ViewBag.SearchTerm = clientName;
|
||||||
|
ViewBag.ResultCount = clientsDto.Count;
|
||||||
return View("Find/Index", clientsDto);
|
return View("Find/Index", clientsDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IActionResult Meta(int id, int count, int offset, long? startAt, MetaType? metaFilterType, CancellationToken token)
|
public IActionResult Meta(int id, int count, int offset, long? startAt, MetaType? metaFilterType,
|
||||||
|
CancellationToken token)
|
||||||
{
|
{
|
||||||
var request = new ClientPaginationRequest
|
var request = new ClientPaginationRequest
|
||||||
{
|
{
|
||||||
|
@ -42,15 +42,20 @@ namespace IW4MAdmin.Plugins.Web.StatsWeb.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public IActionResult TopPlayersAsync()
|
public IActionResult TopPlayers(string serverId = null)
|
||||||
{
|
{
|
||||||
ViewBag.Title = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_STATS_INDEX_TITLE"];
|
ViewBag.Title = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_STATS_INDEX_TITLE"];
|
||||||
ViewBag.Description = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_STATS_INDEX_DESC"];
|
ViewBag.Description = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_STATS_INDEX_DESC"];
|
||||||
ViewBag.Servers = _manager.GetServers()
|
|
||||||
.Select(_server => new ServerInfo() {Name = _server.Hostname, ID = _server.EndPoint});
|
|
||||||
ViewBag.Localization = _translationLookup;
|
ViewBag.Localization = _translationLookup;
|
||||||
|
ViewBag.SelectedServerId = serverId;
|
||||||
|
|
||||||
return View("~/Views/Client/Statistics/Index.cshtml");
|
return View("~/Views/Client/Statistics/Index.cshtml", _manager.GetServers()
|
||||||
|
.Select(server => new ServerInfo
|
||||||
|
{
|
||||||
|
Name = server.Hostname,
|
||||||
|
IPAddress = server.IP,
|
||||||
|
Port = server.Port
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
@ -169,7 +174,7 @@ namespace IW4MAdmin.Plugins.Web.StatsWeb.Controllers
|
|||||||
|
|
||||||
var penalty = await context.Penalties
|
var penalty = await context.Penalties
|
||||||
.Select(_penalty => new
|
.Select(_penalty => new
|
||||||
{_penalty.OffenderId, _penalty.PenaltyId, _penalty.When, _penalty.AutomatedOffense})
|
{ _penalty.OffenderId, _penalty.PenaltyId, _penalty.When, _penalty.AutomatedOffense })
|
||||||
.FirstOrDefaultAsync(_penalty => _penalty.PenaltyId == penaltyId);
|
.FirstOrDefaultAsync(_penalty => _penalty.PenaltyId == penaltyId);
|
||||||
|
|
||||||
if (penalty == null)
|
if (penalty == null)
|
||||||
|
@ -39,7 +39,7 @@ namespace WebfrontCore.Controllers
|
|||||||
return Unauthorized();
|
return Unauthorized();
|
||||||
}
|
}
|
||||||
|
|
||||||
return View("Index", Manager.GetApplicationSettings().Configuration());
|
return RedirectToAction("Files");
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IActionResult> Files()
|
public async Task<IActionResult> Files()
|
||||||
@ -256,4 +256,4 @@ namespace WebfrontCore.Controllers
|
|||||||
.Where(_attr => _attr.GetType() == typeof(ConfigurationIgnore))
|
.Where(_attr => _attr.GetType() == typeof(ConfigurationIgnore))
|
||||||
.FirstOrDefault() as ConfigurationIgnore) != null;
|
.FirstOrDefault() as ConfigurationIgnore) != null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,11 +34,11 @@ namespace WebfrontCore.Controllers
|
|||||||
return View(activeServers);
|
return View(activeServers);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IActionResult> ExecuteAsync(long serverId, string command)
|
public async Task<IActionResult> Execute(long serverId, string command)
|
||||||
{
|
{
|
||||||
var server = Manager.GetServers().First(s => s.EndPoint == serverId);
|
var server = Manager.GetServers().First(s => s.EndPoint == serverId);
|
||||||
|
|
||||||
var client = new EFClient()
|
var client = new EFClient
|
||||||
{
|
{
|
||||||
ClientId = Client.ClientId,
|
ClientId = Client.ClientId,
|
||||||
Level = Client.Level,
|
Level = Client.Level,
|
||||||
@ -50,7 +50,7 @@ namespace WebfrontCore.Controllers
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var remoteEvent = new GameEvent()
|
var remoteEvent = new GameEvent
|
||||||
{
|
{
|
||||||
Type = GameEvent.EventType.Command,
|
Type = GameEvent.EventType.Command,
|
||||||
Data = command.StartsWith(_appconfig.CommandPrefix) ||
|
Data = command.StartsWith(_appconfig.CommandPrefix) ||
|
||||||
@ -97,15 +97,15 @@ namespace WebfrontCore.Controllers
|
|||||||
{
|
{
|
||||||
response = new[]
|
response = new[]
|
||||||
{
|
{
|
||||||
new CommandResponseInfo()
|
new CommandResponseInfo
|
||||||
{
|
{
|
||||||
ClientId = client.ClientId,
|
ClientId = client.ClientId,
|
||||||
Response = Utilities.CurrentLocalization.LocalizationIndex["COMMADS_RESTART_SUCCESS"]
|
Response = Utilities.CurrentLocalization.LocalizationIndex["COMMADS_RESTART_SUCCESS"]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return View("_Response", response);
|
return remoteEvent.Failed ? StatusCode(400, response) : Ok(response);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,49 +30,15 @@ namespace WebfrontCore.Controllers
|
|||||||
return View(showOnly);
|
return View(showOnly);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IActionResult> ListAsync(int offset = 0, EFPenalty.PenaltyType showOnly = EFPenalty.PenaltyType.Any, bool hideAutomatedPenalties = true)
|
public async Task<IActionResult> ListAsync(int offset = 0, int count = 30, EFPenalty.PenaltyType showOnly = EFPenalty.PenaltyType.Any, bool hideAutomatedPenalties = true)
|
||||||
{
|
{
|
||||||
return await Task.FromResult(View("_List", new ViewModels.PenaltyFilterInfo()
|
return await Task.FromResult(View("_List", new ViewModels.PenaltyFilterInfo
|
||||||
{
|
{
|
||||||
Offset = offset,
|
Offset = offset,
|
||||||
|
Count = count,
|
||||||
ShowOnly = showOnly,
|
ShowOnly = showOnly,
|
||||||
IgnoreAutomated = hideAutomatedPenalties
|
IgnoreAutomated = hideAutomatedPenalties
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// retrieves all permanent bans ordered by ban date
|
|
||||||
/// if request is authorized, it will include the client's ip address.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
public async Task<IActionResult> PublicAsync()
|
|
||||||
{
|
|
||||||
IList<PenaltyInfo> penalties;
|
|
||||||
|
|
||||||
await using var ctx = _contextFactory.CreateContext(false);
|
|
||||||
var iqPenalties = ctx.Penalties
|
|
||||||
.AsNoTracking()
|
|
||||||
.Where(p => p.Type == EFPenalty.PenaltyType.Ban && p.Active)
|
|
||||||
.OrderByDescending(_penalty => _penalty.When)
|
|
||||||
.Select(p => new PenaltyInfo()
|
|
||||||
{
|
|
||||||
Id = p.PenaltyId,
|
|
||||||
OffenderId = p.OffenderId,
|
|
||||||
OffenderName = p.Offender.CurrentAlias.Name,
|
|
||||||
OffenderNetworkId = (ulong)p.Offender.NetworkId,
|
|
||||||
OffenderIPAddress = Authorized ? p.Offender.CurrentAlias.IPAddress.ConvertIPtoString() : null,
|
|
||||||
Offense = p.Offense,
|
|
||||||
PunisherId = p.PunisherId,
|
|
||||||
PunisherNetworkId = (ulong)p.Punisher.NetworkId,
|
|
||||||
PunisherName = p.Punisher.CurrentAlias.Name,
|
|
||||||
PunisherIPAddress = Authorized ? p.Punisher.CurrentAlias.IPAddress.ConvertIPtoString() : null,
|
|
||||||
TimePunished = p.When,
|
|
||||||
AutomatedOffense = Authorized ? p.AutomatedOffense : null,
|
|
||||||
});
|
|
||||||
|
|
||||||
penalties = await iqPenalties.ToListAsync();
|
|
||||||
|
|
||||||
return Json(penalties);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ namespace WebfrontCore.Controllers
|
|||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
var serverInfo = new ServerInfo()
|
var serverInfo = new ServerInfo
|
||||||
{
|
{
|
||||||
Name = s.Hostname,
|
Name = s.Hostname,
|
||||||
ID = s.EndPoint,
|
ID = s.EndPoint,
|
||||||
@ -44,7 +44,7 @@ namespace WebfrontCore.Controllers
|
|||||||
ClientId = p.ClientId,
|
ClientId = p.ClientId,
|
||||||
Level = p.Level.ToLocalizedLevelName(),
|
Level = p.Level.ToLocalizedLevelName(),
|
||||||
LevelInt = (int) p.Level,
|
LevelInt = (int) p.Level,
|
||||||
ZScore = p.GetAdditionalProperty<EFClientStatistics>(IW4MAdmin.Plugins.Stats.Helpers.StatManager
|
ZScore = p.GetAdditionalProperty<EFClientStatistics>(StatManager
|
||||||
.CLIENT_STATS_KEY)?.ZScore
|
.CLIENT_STATS_KEY)?.ZScore
|
||||||
}).ToList(),
|
}).ToList(),
|
||||||
ChatHistory = s.ChatHistory.ToList(),
|
ChatHistory = s.ChatHistory.ToList(),
|
||||||
@ -55,36 +55,39 @@ namespace WebfrontCore.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public ActionResult Scoreboard()
|
public ActionResult Scoreboard(string serverId)
|
||||||
{
|
{
|
||||||
ViewBag.Title = Localization["WEBFRONT_TITLE_SCOREBOARD"];
|
ViewBag.Title = Localization["WEBFRONT_TITLE_SCOREBOARD"];
|
||||||
|
ViewBag.SelectedServerId = string.IsNullOrEmpty(serverId) ? Manager.GetServers().FirstOrDefault()?.ToString() : serverId;
|
||||||
|
|
||||||
return View(ProjectScoreboard(Manager.GetServers(), null, true));
|
return View(ProjectScoreboard(Manager.GetServers(), null, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("[controller]/{id}/scoreboard")]
|
[HttpGet("[controller]/{id}/scoreboard")]
|
||||||
public ActionResult Scoreboard(long id, [FromQuery]string order = null, [FromQuery] bool down = true)
|
public ActionResult Scoreboard(string id, [FromQuery]string order = null, [FromQuery] bool down = true)
|
||||||
{
|
{
|
||||||
var server = Manager.GetServers().FirstOrDefault(srv => srv.EndPoint == id);
|
|
||||||
|
var server = Manager.GetServers().FirstOrDefault(srv => srv.ToString() == id);
|
||||||
|
|
||||||
if (server == null)
|
if (server == null)
|
||||||
{
|
{
|
||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ViewBag.SelectedServerId = id;
|
||||||
return View("_Scoreboard", ProjectScoreboard(new[] {server}, order, down).First());
|
return View("_Scoreboard", ProjectScoreboard(new[] {server}, order, down).First());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IEnumerable<ScoreboardInfo> ProjectScoreboard(IEnumerable<Server> servers, string order,
|
private static IEnumerable<ScoreboardInfo> ProjectScoreboard(IEnumerable<Server> servers, string order,
|
||||||
bool down)
|
bool down)
|
||||||
{
|
{
|
||||||
return servers.Select(server => new ScoreboardInfo
|
return servers.Select((server, index) => new ScoreboardInfo
|
||||||
{
|
{
|
||||||
OrderByKey = order,
|
OrderByKey = order,
|
||||||
ShouldOrderDescending = down,
|
ShouldOrderDescending = down,
|
||||||
MapName = server.CurrentMap.ToString(),
|
MapName = server.CurrentMap.ToString(),
|
||||||
ServerName = server.Hostname,
|
ServerName = server.Hostname,
|
||||||
ServerId = server.EndPoint,
|
ServerId = server.ToString(),
|
||||||
ClientInfo = server.GetClientsAsList().Select(client =>
|
ClientInfo = server.GetClientsAsList().Select(client =>
|
||||||
new
|
new
|
||||||
{
|
{
|
||||||
|
@ -7,6 +7,7 @@ using System.Linq;
|
|||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Data.Models.Client;
|
using Data.Models.Client;
|
||||||
|
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||||
using static SharedLibraryCore.GameEvent;
|
using static SharedLibraryCore.GameEvent;
|
||||||
|
|
||||||
namespace WebfrontCore.Middleware
|
namespace WebfrontCore.Middleware
|
||||||
@ -81,7 +82,7 @@ namespace WebfrontCore.Middleware
|
|||||||
// they've been removed
|
// they've been removed
|
||||||
if (!_privilegedClientIds.Contains(clientId) && clientId != 1)
|
if (!_privilegedClientIds.Contains(clientId) && clientId != 1)
|
||||||
{
|
{
|
||||||
await context.SignOutAsync();
|
await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,8 +2,19 @@
|
|||||||
|
|
||||||
public enum WebfrontEntity
|
public enum WebfrontEntity
|
||||||
{
|
{
|
||||||
IPAddress,
|
ClientIPAddress,
|
||||||
MetaAliasUpdate
|
ClientGuid,
|
||||||
|
ClientLevel,
|
||||||
|
MetaAliasUpdate,
|
||||||
|
Penalty,
|
||||||
|
PrivilegedClientsPage,
|
||||||
|
HelpPage,
|
||||||
|
ConsolePage,
|
||||||
|
ConfigurationPage,
|
||||||
|
AuditPage,
|
||||||
|
RecentPlayersPage,
|
||||||
|
ProfilePage,
|
||||||
|
AdminMenu
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum WebfrontPermission
|
public enum WebfrontPermission
|
||||||
|
96
WebfrontCore/QueryHelpers/BanInfoResourceQueryHelper.cs
Normal file
96
WebfrontCore/QueryHelpers/BanInfoResourceQueryHelper.cs
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Data.Abstractions;
|
||||||
|
using Data.Models.Client;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using SharedLibraryCore;
|
||||||
|
using SharedLibraryCore.Helpers;
|
||||||
|
using SharedLibraryCore.Interfaces;
|
||||||
|
using WebfrontCore.QueryHelpers.Models;
|
||||||
|
|
||||||
|
namespace WebfrontCore.QueryHelpers;
|
||||||
|
|
||||||
|
public class BanInfoResourceQueryHelper : IResourceQueryHelper<BanInfoRequest, BanInfo>
|
||||||
|
{
|
||||||
|
private readonly IDatabaseContextFactory _contextFactory;
|
||||||
|
|
||||||
|
public BanInfoResourceQueryHelper(IDatabaseContextFactory contextFactory)
|
||||||
|
{
|
||||||
|
_contextFactory = contextFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ResourceQueryHelperResult<BanInfo>> QueryResource(BanInfoRequest query)
|
||||||
|
{
|
||||||
|
if (query.Count > 30)
|
||||||
|
{
|
||||||
|
query.Count = 30;
|
||||||
|
}
|
||||||
|
|
||||||
|
await using var context = _contextFactory.CreateContext(false);
|
||||||
|
|
||||||
|
var matchingClients = await context.Clients.Where(client =>
|
||||||
|
EF.Functions.ILike(client.CurrentAlias.SearchableName ?? client.CurrentAlias.Name, $"%{query.ClientName.Trim()}%"))
|
||||||
|
.Where(client => client.Level == EFClient.Permission.Banned)
|
||||||
|
.OrderByDescending(client => client.LastConnection)
|
||||||
|
.Skip(query.Offset)
|
||||||
|
.Take(query.Count)
|
||||||
|
.Select(client => new
|
||||||
|
{
|
||||||
|
client.CurrentAlias.Name,
|
||||||
|
client.NetworkId,
|
||||||
|
client.AliasLinkId,
|
||||||
|
client.ClientId
|
||||||
|
}).ToListAsync();
|
||||||
|
|
||||||
|
var usedIps = await context.Aliases
|
||||||
|
.Where(alias => matchingClients.Select(client => client.AliasLinkId).Contains(alias.LinkId))
|
||||||
|
.Where(alias => alias.IPAddress != null)
|
||||||
|
.Select(alias => new { alias.IPAddress, alias.LinkId })
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
var usedIpsGrouped = usedIps
|
||||||
|
.GroupBy(alias => alias.LinkId)
|
||||||
|
.ToDictionary(key => key.Key, value => value.Select(alias => alias.IPAddress).Distinct());
|
||||||
|
|
||||||
|
var searchingNetworkIds = matchingClients.Select(client => client.NetworkId);
|
||||||
|
var searchingIps = usedIpsGrouped.SelectMany(group => group.Value);
|
||||||
|
|
||||||
|
var matchedPenalties = await context.PenaltyIdentifiers.Where(identifier =>
|
||||||
|
searchingNetworkIds.Contains(identifier.NetworkId) ||
|
||||||
|
searchingIps.Contains(identifier.IPv4Address))
|
||||||
|
.Select(penalty => new
|
||||||
|
{
|
||||||
|
penalty.CreatedDateTime,
|
||||||
|
PunisherName = penalty.Penalty.Punisher.CurrentAlias.Name,
|
||||||
|
Offense = string.IsNullOrEmpty(penalty.Penalty.AutomatedOffense) ? penalty.Penalty.Offense : "Anticheat Detection",
|
||||||
|
LinkId = penalty.Penalty.Offender.AliasLinkId,
|
||||||
|
penalty.Penalty.PunisherId
|
||||||
|
})
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
var groupedPenalties = matchedPenalties.GroupBy(penalty => penalty.LinkId)
|
||||||
|
.ToDictionary(key => key.Key, value => value.FirstOrDefault());
|
||||||
|
|
||||||
|
var results = matchingClients.Select(client =>
|
||||||
|
{
|
||||||
|
var matchedPenalty =
|
||||||
|
groupedPenalties.ContainsKey(client.AliasLinkId) ? groupedPenalties[client.AliasLinkId] : null;
|
||||||
|
return new BanInfo
|
||||||
|
{
|
||||||
|
DateTime = matchedPenalty?.CreatedDateTime,
|
||||||
|
OffenderName = client.Name.StripColors(),
|
||||||
|
OffenderId = client.ClientId,
|
||||||
|
PunisherName = matchedPenalty?.PunisherName.StripColors(),
|
||||||
|
PunisherId = matchedPenalty?.PunisherId,
|
||||||
|
Offense = matchedPenalty?.Offense
|
||||||
|
};
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
return new ResourceQueryHelperResult<BanInfo>
|
||||||
|
{
|
||||||
|
RetrievedResultCount = results.Count,
|
||||||
|
TotalResultCount = results.Count,
|
||||||
|
Results = results
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
12
WebfrontCore/QueryHelpers/Models/BanInfo.cs
Normal file
12
WebfrontCore/QueryHelpers/Models/BanInfo.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
public class BanInfo
|
||||||
|
{
|
||||||
|
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 DateTime? DateTime { get; set; }
|
||||||
|
public long? TimeStamp => DateTime.HasValue ? new DateTimeOffset(DateTime.Value, TimeSpan.Zero).ToUnixTimeSeconds() : null;
|
||||||
|
}
|
8
WebfrontCore/QueryHelpers/Models/BanInfoRequest.cs
Normal file
8
WebfrontCore/QueryHelpers/Models/BanInfoRequest.cs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
using SharedLibraryCore.Dtos;
|
||||||
|
|
||||||
|
namespace WebfrontCore.QueryHelpers.Models;
|
||||||
|
|
||||||
|
public class BanInfoRequest : PaginationRequest
|
||||||
|
{
|
||||||
|
public string ClientName { get; set; }
|
||||||
|
}
|
@ -24,11 +24,12 @@ using System.Reflection;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Data.Abstractions;
|
using Data.Abstractions;
|
||||||
using Data.Helpers;
|
using Data.Helpers;
|
||||||
using IW4MAdmin.Plugins.Stats.Config;
|
|
||||||
using Stats.Client.Abstractions;
|
using Stats.Client.Abstractions;
|
||||||
using Stats.Config;
|
using Stats.Config;
|
||||||
using WebfrontCore.Controllers.API.Validation;
|
using WebfrontCore.Controllers.API.Validation;
|
||||||
using WebfrontCore.Middleware;
|
using WebfrontCore.Middleware;
|
||||||
|
using WebfrontCore.QueryHelpers;
|
||||||
|
using WebfrontCore.QueryHelpers.Models;
|
||||||
|
|
||||||
namespace WebfrontCore
|
namespace WebfrontCore
|
||||||
{
|
{
|
||||||
@ -127,10 +128,13 @@ namespace WebfrontCore
|
|||||||
services.AddSingleton(Program.ApplicationServiceProvider.GetRequiredService<IMetaServiceV2>());
|
services.AddSingleton(Program.ApplicationServiceProvider.GetRequiredService<IMetaServiceV2>());
|
||||||
services.AddSingleton(Program.ApplicationServiceProvider.GetRequiredService<ApplicationConfiguration>());
|
services.AddSingleton(Program.ApplicationServiceProvider.GetRequiredService<ApplicationConfiguration>());
|
||||||
services.AddSingleton(Program.ApplicationServiceProvider.GetRequiredService<ClientService>());
|
services.AddSingleton(Program.ApplicationServiceProvider.GetRequiredService<ClientService>());
|
||||||
|
services.AddSingleton<IResourceQueryHelper<BanInfoRequest, BanInfo>, BanInfoResourceQueryHelper>();
|
||||||
services.AddSingleton(
|
services.AddSingleton(
|
||||||
Program.ApplicationServiceProvider.GetRequiredService<IServerDistributionCalculator>());
|
Program.ApplicationServiceProvider.GetRequiredService<IServerDistributionCalculator>());
|
||||||
services.AddSingleton(Program.ApplicationServiceProvider
|
services.AddSingleton(Program.ApplicationServiceProvider
|
||||||
.GetRequiredService<IConfigurationHandler<DefaultSettings>>());
|
.GetRequiredService<IConfigurationHandler<DefaultSettings>>());
|
||||||
|
services.AddSingleton(Program.ApplicationServiceProvider
|
||||||
|
.GetRequiredService<IGeoLocationService>());
|
||||||
services.AddSingleton(Program.ApplicationServiceProvider
|
services.AddSingleton(Program.ApplicationServiceProvider
|
||||||
.GetRequiredService<StatsConfiguration>());
|
.GetRequiredService<StatsConfiguration>());
|
||||||
services.AddSingleton(Program.ApplicationServiceProvider.GetRequiredService<IServerDataViewer>());
|
services.AddSingleton(Program.ApplicationServiceProvider.GetRequiredService<IServerDataViewer>());
|
||||||
|
42
WebfrontCore/TagHelpers/HasPermission.cs
Normal file
42
WebfrontCore/TagHelpers/HasPermission.cs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Razor.TagHelpers;
|
||||||
|
using SharedLibraryCore;
|
||||||
|
using SharedLibraryCore.Configuration;
|
||||||
|
using WebfrontCore.Permissions;
|
||||||
|
|
||||||
|
namespace WebfrontCore.TagHelpers;
|
||||||
|
|
||||||
|
[HtmlTargetElement("has-permission")]
|
||||||
|
public class HasPermission : TagHelper
|
||||||
|
{
|
||||||
|
[HtmlAttributeName("entity")] public WebfrontEntity Entity { get; set; }
|
||||||
|
|
||||||
|
[HtmlAttributeName("required-permission")]
|
||||||
|
public WebfrontPermission Permission { get; set; }
|
||||||
|
|
||||||
|
private readonly IDictionary<string, List<string>> _permissionSets;
|
||||||
|
private readonly IHttpContextAccessor _contextAccessor;
|
||||||
|
|
||||||
|
public HasPermission(ApplicationConfiguration appConfig, IHttpContextAccessor contextAccessor)
|
||||||
|
{
|
||||||
|
_permissionSets = appConfig.PermissionSets;
|
||||||
|
_contextAccessor = contextAccessor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Process(TagHelperContext context, TagHelperOutput output)
|
||||||
|
{
|
||||||
|
output.TagName = null;
|
||||||
|
var permissionLevel = _contextAccessor?.HttpContext?.User.Claims
|
||||||
|
.FirstOrDefault(claim => claim.Type == ClaimTypes.Role)?.Value;
|
||||||
|
|
||||||
|
var hasPermission = permissionLevel != null && _permissionSets.ContainsKey(permissionLevel) &&
|
||||||
|
_permissionSets[permissionLevel].HasPermission(Entity, Permission);
|
||||||
|
if (!hasPermission)
|
||||||
|
{
|
||||||
|
output.SuppressOutput();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -7,11 +7,9 @@ namespace WebfrontCore.ViewComponents
|
|||||||
{
|
{
|
||||||
public class PenaltyListViewComponent : ViewComponent
|
public class PenaltyListViewComponent : ViewComponent
|
||||||
{
|
{
|
||||||
private const int PENALTY_COUNT = 15;
|
public async Task<IViewComponentResult> InvokeAsync(int offset, int count, EFPenalty.PenaltyType showOnly, bool ignoreAutomated)
|
||||||
|
|
||||||
public async Task<IViewComponentResult> InvokeAsync(int offset, EFPenalty.PenaltyType showOnly, bool ignoreAutomated)
|
|
||||||
{
|
{
|
||||||
var penalties = await Program.Manager.GetPenaltyService().GetRecentPenalties(PENALTY_COUNT, offset, showOnly, ignoreAutomated);
|
var penalties = await Program.Manager.GetPenaltyService().GetRecentPenalties(count, offset, showOnly, ignoreAutomated);
|
||||||
penalties = User.Identity.IsAuthenticated ? penalties : penalties.Where(p => !p.Sensitive).ToList();
|
penalties = User.Identity.IsAuthenticated ? penalties : penalties.Where(p => !p.Sensitive).ToList();
|
||||||
|
|
||||||
return View("~/Views/Penalty/PenaltyInfoList.cshtml", penalties);
|
return View("~/Views/Penalty/PenaltyInfoList.cshtml", penalties);
|
||||||
|
@ -26,9 +26,12 @@ namespace WebfrontCore.ViewComponents
|
|||||||
_appConfig = appConfig;
|
_appConfig = appConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IViewComponentResult> InvokeAsync(int clientId, int count, int offset, DateTime? startAt, MetaType? metaType, CancellationToken token)
|
public async Task<IViewComponentResult> InvokeAsync(int clientId, int count, int offset, DateTime? startAt,
|
||||||
|
MetaType? metaType, CancellationToken token)
|
||||||
{
|
{
|
||||||
var level = (EFClient.Permission)Enum.Parse(typeof(EFClient.Permission), UserClaimsPrincipal.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Role)?.Value ?? "User");
|
var level = (EFClient.Permission)Enum.Parse(typeof(EFClient.Permission),
|
||||||
|
UserClaimsPrincipal.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Role)?.Value ??
|
||||||
|
EFClient.Permission.User.ToString());
|
||||||
|
|
||||||
var request = new ClientPaginationRequest
|
var request = new ClientPaginationRequest
|
||||||
{
|
{
|
||||||
@ -39,8 +42,13 @@ namespace WebfrontCore.ViewComponents
|
|||||||
};
|
};
|
||||||
|
|
||||||
var meta = await GetClientMeta(_metaService, metaType, level, request, token);
|
var meta = await GetClientMeta(_metaService, metaType, level, request, token);
|
||||||
ViewBag.Localization = SharedLibraryCore.Utilities.CurrentLocalization.LocalizationIndex;
|
ViewBag.Localization = Utilities.CurrentLocalization.LocalizationIndex;
|
||||||
|
|
||||||
|
if (!meta.Any())
|
||||||
|
{
|
||||||
|
return Content(string.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
return View("_List", meta);
|
return View("_List", meta);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,7 +61,7 @@ namespace WebfrontCore.ViewComponents
|
|||||||
{
|
{
|
||||||
permissionSet = new List<string>();
|
permissionSet = new List<string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (metaType is null or MetaType.All)
|
if (metaType is null or MetaType.All)
|
||||||
{
|
{
|
||||||
meta = await metaService.GetRuntimeMeta(request, token);
|
meta = await metaService.GetRuntimeMeta(request, token);
|
||||||
@ -65,8 +73,11 @@ namespace WebfrontCore.ViewComponents
|
|||||||
{
|
{
|
||||||
MetaType.Information => await metaService.GetRuntimeMeta<InformationResponse>(request,
|
MetaType.Information => await metaService.GetRuntimeMeta<InformationResponse>(request,
|
||||||
metaType.Value, token),
|
metaType.Value, token),
|
||||||
MetaType.AliasUpdate => permissionSet.HasPermission(WebfrontEntity.MetaAliasUpdate, WebfrontPermission.Read) ? await metaService.GetRuntimeMeta<UpdatedAliasResponse>(request,
|
MetaType.AliasUpdate => permissionSet.HasPermission(WebfrontEntity.MetaAliasUpdate,
|
||||||
metaType.Value, token) : new List<IClientMeta>(),
|
WebfrontPermission.Read)
|
||||||
|
? await metaService.GetRuntimeMeta<UpdatedAliasResponse>(request,
|
||||||
|
metaType.Value, token)
|
||||||
|
: new List<IClientMeta>(),
|
||||||
MetaType.ChatMessage => await metaService.GetRuntimeMeta<MessageResponse>(request, metaType.Value,
|
MetaType.ChatMessage => await metaService.GetRuntimeMeta<MessageResponse>(request, metaType.Value,
|
||||||
token),
|
token),
|
||||||
MetaType.Penalized => await metaService.GetRuntimeMeta<AdministeredPenaltyResponse>(request,
|
MetaType.Penalized => await metaService.GetRuntimeMeta<AdministeredPenaltyResponse>(request,
|
||||||
@ -83,7 +94,7 @@ namespace WebfrontCore.ViewComponents
|
|||||||
|
|
||||||
if (level < EFClient.Permission.Trusted)
|
if (level < EFClient.Permission.Trusted)
|
||||||
{
|
{
|
||||||
meta = meta.Where(_meta => !_meta.IsSensitive);
|
meta = meta?.Where(_meta => !_meta.IsSensitive);
|
||||||
}
|
}
|
||||||
|
|
||||||
return meta;
|
return meta;
|
||||||
|
@ -94,8 +94,8 @@ namespace WebfrontCore.ViewComponents
|
|||||||
}).ToList(),
|
}).ToList(),
|
||||||
ChatHistory = server.ChatHistory.ToList(),
|
ChatHistory = server.ChatHistory.ToList(),
|
||||||
Online = !server.Throttled,
|
Online = !server.Throttled,
|
||||||
IPAddress =
|
IPAddress = server.IP,
|
||||||
$"{(server.ResolvedIpEndPoint.Address.IsInternal() ? Program.Manager.ExternalIPAddress : server.IP)}:{server.Port}",
|
ExternalIPAddress = server.ResolvedIpEndPoint.Address.IsInternal() ? Program.Manager.ExternalIPAddress : server.IP,
|
||||||
ConnectProtocolUrl = server.EventParser.URLProtocolFormat.FormatExt(
|
ConnectProtocolUrl = server.EventParser.URLProtocolFormat.FormatExt(
|
||||||
server.ResolvedIpEndPoint.Address.IsInternal() ? Program.Manager.ExternalIPAddress : server.IP,
|
server.ResolvedIpEndPoint.Address.IsInternal() ? Program.Manager.ExternalIPAddress : server.IP,
|
||||||
server.Port)
|
server.Port)
|
||||||
|
@ -16,22 +16,16 @@ namespace WebfrontCore.ViewComponents
|
|||||||
_config = config;
|
_config = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IViewComponentResult> InvokeAsync(int count, int offset, long? serverId = null)
|
public async Task<IViewComponentResult> InvokeAsync(int count, int offset, string serverEndpoint = null)
|
||||||
{
|
{
|
||||||
if (serverId == 0)
|
var server = Plugin.ServerManager.GetServers()
|
||||||
{
|
.FirstOrDefault(server => server.ToString() == serverEndpoint);
|
||||||
serverId = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var server = Plugin.ServerManager.GetServers().FirstOrDefault(_server => _server.EndPoint == serverId);
|
|
||||||
|
|
||||||
if (server != null)
|
|
||||||
{
|
|
||||||
serverId = StatManager.GetIdForServer(server);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
var serverId = server is null ? (long?)null : StatManager.GetIdForServer(server);
|
||||||
|
|
||||||
ViewBag.UseNewStats = _config?.EnableAdvancedMetrics ?? true;
|
ViewBag.UseNewStats = _config?.EnableAdvancedMetrics ?? true;
|
||||||
|
ViewBag.SelectedServerName = server?.Hostname;
|
||||||
|
|
||||||
return View("~/Views/Client/Statistics/Components/TopPlayers/_List.cshtml",
|
return View("~/Views/Client/Statistics/Components/TopPlayers/_List.cshtml",
|
||||||
ViewBag.UseNewStats
|
ViewBag.UseNewStats
|
||||||
? await Plugin.Manager.GetNewTopStats(offset, count, serverId)
|
? await Plugin.Manager.GetNewTopStats(offset, count, serverId)
|
||||||
|
@ -14,5 +14,6 @@ namespace WebfrontCore.ViewModels
|
|||||||
public string Value { get; set; }
|
public string Value { get; set; }
|
||||||
public Dictionary<string, string> Values { get; set; }
|
public Dictionary<string, string> Values { get; set; }
|
||||||
public bool Checked { get; set; }
|
public bool Checked { get; set; }
|
||||||
|
public bool Required { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,8 @@ namespace WebfrontCore.ViewModels
|
|||||||
/// number of items offset from the start of the list
|
/// number of items offset from the start of the list
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int Offset { get; set; }
|
public int Offset { get; set; }
|
||||||
|
|
||||||
|
public int Count { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// show only a certain type of penalty
|
/// show only a certain type of penalty
|
||||||
|
@ -3,17 +3,16 @@ using SharedLibraryCore.Database.Models;
|
|||||||
|
|
||||||
namespace WebfrontCore.ViewModels
|
namespace WebfrontCore.ViewModels
|
||||||
{
|
{
|
||||||
|
|
||||||
public class ScoreboardInfo
|
public class ScoreboardInfo
|
||||||
{
|
{
|
||||||
public string ServerName { get; set; }
|
public string ServerName { get; set; }
|
||||||
public long ServerId { get; set; }
|
public string ServerId { get; set; }
|
||||||
public string MapName { get; set; }
|
public string MapName { get; set; }
|
||||||
public string OrderByKey { get; set; }
|
public string OrderByKey { get; set; }
|
||||||
public bool ShouldOrderDescending { get; set; }
|
public bool ShouldOrderDescending { get; set; }
|
||||||
public List<ClientScoreboardInfo> ClientInfo { get; set; }
|
public List<ClientScoreboardInfo> ClientInfo { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ClientScoreboardInfo
|
public class ClientScoreboardInfo
|
||||||
{
|
{
|
||||||
public string ClientName { get; set; }
|
public string ClientName { get; set; }
|
||||||
|
22
WebfrontCore/ViewModels/SideContextMenuItem.cs
Normal file
22
WebfrontCore/ViewModels/SideContextMenuItem.cs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace WebfrontCore.ViewModels;
|
||||||
|
|
||||||
|
public class SideContextMenuItem
|
||||||
|
{
|
||||||
|
public bool IsLink { get; set; }
|
||||||
|
public bool IsButton { get; set; }
|
||||||
|
public bool IsActive { get; set; }
|
||||||
|
public string Title { get; set; }
|
||||||
|
public string Reference { get; set; }
|
||||||
|
public string Icon { get; set; }
|
||||||
|
public string Tooltip { get; set; }
|
||||||
|
public int? EntityId { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public class SideContextMenuItems
|
||||||
|
{
|
||||||
|
public string MenuTitle { get; set; }
|
||||||
|
public List<SideContextMenuItem> Items { get; set; } = new();
|
||||||
|
}
|
54
WebfrontCore/ViewModels/TableInfo.cs
Normal file
54
WebfrontCore/ViewModels/TableInfo.cs
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace WebfrontCore.ViewModels;
|
||||||
|
|
||||||
|
public class TableInfo
|
||||||
|
{
|
||||||
|
public string Header { get; set; }
|
||||||
|
public List<ColumnDefinition> Columns { get; } = new();
|
||||||
|
public List<RowDefinition> Rows { get; } = new();
|
||||||
|
public int InitialRowCount { get; }
|
||||||
|
|
||||||
|
public TableInfo(int initialRowCount = 0)
|
||||||
|
{
|
||||||
|
InitialRowCount = initialRowCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RowDefinition
|
||||||
|
{
|
||||||
|
public List<string> Datum { get; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ColumnDefinition
|
||||||
|
{
|
||||||
|
public string Title { get; set; }
|
||||||
|
public string ColumnSpan { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class TableInfoExtensions
|
||||||
|
{
|
||||||
|
public static TableInfo WithColumns(this TableInfo info, IEnumerable<string> columns)
|
||||||
|
{
|
||||||
|
info.Columns.AddRange(columns.Select(column => new ColumnDefinition
|
||||||
|
{
|
||||||
|
Title = column
|
||||||
|
}));
|
||||||
|
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TableInfo WithRows<T>(this TableInfo info, IEnumerable<T> source,
|
||||||
|
Func<T, IEnumerable<string>> selector)
|
||||||
|
{
|
||||||
|
info.Rows.AddRange(source.Select(row =>
|
||||||
|
{
|
||||||
|
var rowDef = new RowDefinition();
|
||||||
|
rowDef.Datum.AddRange(selector(row));
|
||||||
|
return rowDef;
|
||||||
|
}));
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,8 @@
|
|||||||
@using SharedLibraryCore.Configuration
|
@using SharedLibraryCore.Configuration
|
||||||
|
@using System.Text.RegularExpressions
|
||||||
@model WebfrontCore.ViewModels.CommunityInfo
|
@model WebfrontCore.ViewModels.CommunityInfo
|
||||||
@{
|
@{
|
||||||
IEnumerable<KeyValuePair<(string, long), string[]>> allRules = new[] {new KeyValuePair<(string, long), string[]>((ViewBag.Localization["WEBFRONT_ABOUT_GLOBAL_RULES"], 0), Model.GlobalRules)};
|
IEnumerable<KeyValuePair<(string, long), string[]>> allRules = new[] { new KeyValuePair<(string, long), string[]>((ViewBag.Localization["WEBFRONT_ABOUT_GLOBAL_RULES"], 0), Model.GlobalRules) };
|
||||||
var serverRules = Model.ServerRules?.Where(server => server.Value != null && server.Value.Any()).ToList();
|
var serverRules = Model.ServerRules?.Where(server => server.Value != null && server.Value.Any()).ToList();
|
||||||
if (serverRules?.Any() ?? false)
|
if (serverRules?.Any() ?? false)
|
||||||
{
|
{
|
||||||
@ -9,26 +10,29 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="row text-break">
|
<div class="content text-wrap mt-20">
|
||||||
@if (Model.CommunityInformation.EnableBanner)
|
@if (Model.CommunityInformation.EnableBanner)
|
||||||
{
|
{
|
||||||
<img class="img-fluid mb-3" style="max-height: 250px" src="images/community/banner.png" alt="@Model.CommunityInformation.Name"/>
|
<img class="img-fluid mb-20" style="max-height: 250px" src="images/community/banner.png" alt="@Model.CommunityInformation.Name"/>
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (!string.IsNullOrWhiteSpace(Model.CommunityInformation.Name))
|
@if (!string.IsNullOrWhiteSpace(Model.CommunityInformation.Name))
|
||||||
{
|
{
|
||||||
<h2 class="mb-4 p-0 col-12 text-center text-md-left">
|
<h2 class="content-title">
|
||||||
<color-code value="@Model.CommunityInformation.Name"></color-code>
|
<color-code value="@Model.CommunityInformation.Name"></color-code>
|
||||||
</h2>
|
</h2>
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (!string.IsNullOrWhiteSpace(Model.CommunityInformation.Description))
|
|
||||||
{
|
<div class="card m-0 rounded">
|
||||||
<div class="p-4 bg-dark border border-primary mb-4 text-white-50 col-12">
|
@if (!string.IsNullOrWhiteSpace(Model.CommunityInformation.Description))
|
||||||
<h4 class="text-primary">@ViewBag.Localization["WEBFRONT_ABOUT_TITLE"]</h4>
|
{
|
||||||
<color-code value="@Model.CommunityInformation.Description"></color-code>
|
<h5 class="text-primary mt-0">@ViewBag.Localization["WEBFRONT_ABOUT_TITLE"]</h5>
|
||||||
<div class="mt-3">
|
<div class="text-md-justify">
|
||||||
@foreach (var social in Model.CommunityInformation.SocialAccounts ?? new SocialAccountConfiguration[0])
|
<color-code value="@Model.CommunityInformation.Description"></color-code>
|
||||||
|
</div>
|
||||||
|
<div class="mt-10">
|
||||||
|
@foreach (var social in Model.CommunityInformation.SocialAccounts ?? Array.Empty<SocialAccountConfiguration>())
|
||||||
{
|
{
|
||||||
<div>
|
<div>
|
||||||
<a href="@social.Url" target="_blank" title="@social.Title">
|
<a href="@social.Url" target="_blank" title="@social.Title">
|
||||||
@ -38,8 +42,8 @@
|
|||||||
}
|
}
|
||||||
else if (!string.IsNullOrWhiteSpace(social.IconUrl))
|
else if (!string.IsNullOrWhiteSpace(social.IconUrl))
|
||||||
{
|
{
|
||||||
var url = Uri.TryCreate(social.IconUrl, UriKind.Absolute, out var parsedUrl)
|
var url = Uri.TryCreate(social.IconUrl, UriKind.Absolute, out var parsedUrl)
|
||||||
? parsedUrl.AbsoluteUri
|
? parsedUrl.AbsoluteUri
|
||||||
: $"images/community/{social.IconUrl}";
|
: $"images/community/{social.IconUrl}";
|
||||||
<img class="img-fluid" style="max-width: 1rem; fill: white" src="@url" alt="@social.Title"/>
|
<img class="img-fluid" style="max-width: 1rem; fill: white" src="@url" alt="@social.Title"/>
|
||||||
}
|
}
|
||||||
@ -48,34 +52,40 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
}
|
||||||
}
|
|
||||||
|
</div>
|
||||||
|
|
||||||
@if (allRules.Any(rule => rule.Value.Any()))
|
@if (allRules.Any(rule => rule.Value.Any()))
|
||||||
{
|
{
|
||||||
<h2 class="pb-3 p-0 col-12 text-center text-md-left">@ViewBag.Localization["WEBFRONT_ABOUT_COMMUNITY_GUIDELINES"]</h2>
|
<h2 class="content-title mt-20">@ViewBag.Localization["WEBFRONT_ABOUT_COMMUNITY_GUIDELINES"]</h2>
|
||||||
}
|
}
|
||||||
|
|
||||||
@foreach (var ((serverName, id), rules) in allRules)
|
<div class="card m-0 rounded">
|
||||||
{
|
@foreach (var ((serverName, id), rules) in allRules)
|
||||||
if (!rules.Any())
|
|
||||||
{
|
{
|
||||||
continue;
|
if (!rules.Any())
|
||||||
}
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
var start = 1;
|
var start = 1;
|
||||||
<div class="col-12 bg-dark p-4 border border-primary mb-4 col-12">
|
<h5 class="text-primary mt-0">
|
||||||
<div class="text-primary h4">
|
|
||||||
<color-code value="@serverName"></color-code>
|
<color-code value="@serverName"></color-code>
|
||||||
</div>
|
</h5>
|
||||||
@foreach (var rule in rules)
|
@foreach (var rule in rules)
|
||||||
{
|
{
|
||||||
<div class="text-white-50">
|
<div class="rule">
|
||||||
<span class="text-white">@start.</span>
|
@if (!rule.StartsWith("#") && !Regex.IsMatch(rule, @"^\d+(.|\))"))
|
||||||
<color-code value="@rule"></color-code>
|
{
|
||||||
|
<span>@start.</span>
|
||||||
|
}
|
||||||
|
<span class="text-muted">
|
||||||
|
<color-code value="@rule"></color-code>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
start++;
|
start++;
|
||||||
}
|
}
|
||||||
</div>
|
}
|
||||||
}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,16 +1,22 @@
|
|||||||
@model WebfrontCore.ViewModels.ActionInfo
|
@using Humanizer
|
||||||
|
@model WebfrontCore.ViewModels.ActionInfo
|
||||||
@{
|
@{
|
||||||
Layout = null;
|
Layout = null;
|
||||||
}
|
}
|
||||||
|
<h5 class="modal-title mb-10">@Model.Name.Titleize()</h5>
|
||||||
|
@if (Model.Inputs.Any())
|
||||||
|
{
|
||||||
|
<hr class="mb-10"/>
|
||||||
|
}
|
||||||
<form class="action-form @(Model.ShouldRefresh ? "refreshable" : "")" action="/Action/@Model.Action">
|
<form class="action-form @(Model.ShouldRefresh ? "refreshable" : "")" action="/Action/@Model.Action">
|
||||||
@foreach (var input in Model.Inputs)
|
@foreach (var input in Model.Inputs)
|
||||||
{
|
{
|
||||||
string inputType = input.Type ?? "text";
|
var inputType = input.Type ?? "text";
|
||||||
string value = input.Value ?? "";
|
var value = input.Value ?? "";
|
||||||
|
|
||||||
if (input.Type != "hidden")
|
if (input.Type != "hidden")
|
||||||
{
|
{
|
||||||
<div class="input-group mb-3">
|
<div class="input-group mb-10">
|
||||||
|
|
||||||
<div class="input-group-prepend">
|
<div class="input-group-prepend">
|
||||||
<span class="input-group-text" id="basic-addon-@input.Name">@input.Label</span>
|
<span class="input-group-text" id="basic-addon-@input.Name">@input.Label</span>
|
||||||
@ -21,7 +27,9 @@
|
|||||||
<select name="@input.Name" class="form-control" aria-label="@input.Name" aria-describedby="basic-addon-@input.Name">
|
<select name="@input.Name" class="form-control" aria-label="@input.Name" aria-describedby="basic-addon-@input.Name">
|
||||||
@foreach (var item in input.Values)
|
@foreach (var item in input.Values)
|
||||||
{
|
{
|
||||||
<option value="@item.Key">@item.Value</option>
|
<option value="@item.Key">
|
||||||
|
<color-code value="@item.Value"></color-code>
|
||||||
|
</option>
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
}
|
}
|
||||||
@ -37,7 +45,7 @@
|
|||||||
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<input type="@inputType" name="@input.Name" value="@value" class="form-control" placeholder="@input.Placeholder" aria-label="@input.Name" aria-describedby="basic-addon-@input.Name">
|
<input type="@inputType" name="@input.Name" value="@value" class="form-control @(input.Required ? "required" : "")" placeholder="@input.Placeholder" aria-label="@input.Name" aria-describedby="basic-addon-@input.Name">
|
||||||
}
|
}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@ -47,5 +55,12 @@
|
|||||||
<input type="@inputType" name="@input.Name" value="@value" hidden="hidden">
|
<input type="@inputType" name="@input.Name" value="@value" hidden="hidden">
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
<button type="submit" class="btn btn-block btn-primary">@Model.ActionButtonLabel</button>
|
@if (Model.Inputs.Any())
|
||||||
</form>
|
{
|
||||||
|
<hr class="mb-10"/>
|
||||||
|
}
|
||||||
|
<div class="ml-auto">
|
||||||
|
<button type="submit" class="btn btn-primary">@Model.ActionButtonLabel</button>
|
||||||
|
<a href="#" class="btn mr-5" role="button" onclick="halfmoon.toggleModal('actionModal');">Close</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
@ -1,31 +1,28 @@
|
|||||||
@{
|
@{
|
||||||
var loc = SharedLibraryCore.Utilities.CurrentLocalization.LocalizationIndex;
|
var loc = SharedLibraryCore.Utilities.CurrentLocalization.LocalizationIndex;
|
||||||
}
|
}
|
||||||
<h4 class="pb-3 text-center">@ViewBag.Title</h4>
|
<div class="content mt-0">
|
||||||
|
<h2 class="content-title mt-20">@ViewBag.Title</h2>
|
||||||
|
|
||||||
<table class="table table-striped">
|
<table class="table">
|
||||||
<thead class="d-none d-lg-table-header-group">
|
<thead class="d-none d-lg-table-header-group">
|
||||||
<tr class="bg-primary pt-2 pb-2">
|
<tr class="bg-primary text-light pt-2 pb-2">
|
||||||
<th scope="col">@loc["WEBFRONT_PENALTY_TEMPLATE_TYPE"]</th>
|
<th scope="col">@loc["WEBFRONT_PENALTY_TEMPLATE_TYPE"]</th>
|
||||||
<th scope="col">@loc["WEBFRONT_PENALTY_TEMPLATE_ADMIN"]</th>
|
<th scope="col">@loc["WEBFRONT_PENALTY_TEMPLATE_ADMIN"]</th>
|
||||||
<th scope="col">@loc["WEBFRONT_PENALTY_TEMPLATE_NAME"]</th>
|
<th scope="col">@loc["WEBFRONT_PENALTY_TEMPLATE_NAME"]</th>
|
||||||
<th scope="col">@loc["WEBFRONT_ADMIN_AUDIT_LOG_INFO"]</th>
|
<th scope="col">@loc["WEBFRONT_ADMIN_AUDIT_LOG_INFO"]</th>
|
||||||
<!--<th scope="col">@loc["WEBFRONT_ADMIN_AUDIT_LOG_PREVIOUS"]</th>-->
|
|
||||||
<th scope="col">@loc["WEBFRONT_ADMIN_AUDIT_LOG_CURRENT"]</th>
|
<th scope="col">@loc["WEBFRONT_ADMIN_AUDIT_LOG_CURRENT"]</th>
|
||||||
<th scope="col" class="text-right">@loc["WEBFRONT_ADMIN_AUDIT_LOG_TIME"]</th>
|
<th scope="col" class="text-right">@loc["WEBFRONT_ADMIN_AUDIT_LOG_TIME"]</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="audit_log_table_body" class="border-bottom bg-dark">
|
<tbody id="audit_log_table_body" class="border-bottom bg-dark">
|
||||||
<partial name="_ListAuditLog" />
|
<partial name="_ListAuditLog"/>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
</table>
|
||||||
</table>
|
<i id="loaderLoad" class="loader-load-more oi oi-chevron-bottom text-center w-full text-primary mt-10"></i>
|
||||||
<span id="load_audit_log_button" class="loader-load-more oi oi-chevron-bottom text-center text-primary w-100 h3 pb-0 mb-0 d-none d-lg-block"></span>
|
</div>
|
||||||
|
|
||||||
@section scripts {
|
@section scripts {
|
||||||
<environment include="Development">
|
|
||||||
<script type="text/javascript" src="~/js/loader.js"></script>
|
|
||||||
</environment>
|
|
||||||
<script>
|
<script>
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
initLoader('/Admin/ListAuditLog', '#audit_log_table_body', @ViewBag.IntialOffset);
|
initLoader('/Admin/ListAuditLog', '#audit_log_table_body', @ViewBag.IntialOffset);
|
||||||
|
@ -1,99 +1,71 @@
|
|||||||
@using SharedLibraryCore.Dtos
|
@using SharedLibraryCore.Dtos
|
||||||
@model IEnumerable<AuditInfo>
|
@model IEnumerable<AuditInfo>
|
||||||
@{
|
@{
|
||||||
var loc = SharedLibraryCore.Utilities.CurrentLocalization.LocalizationIndex;
|
var loc = Utilities.CurrentLocalization.LocalizationIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
@foreach (var info in Model)
|
@foreach (var info in Model)
|
||||||
{
|
{
|
||||||
<!-- mobile -->
|
|
||||||
<tr class="d-table-row d-lg-none bg-dark">
|
|
||||||
<th scope="row" class="bg-primary">@loc["WEBFRONT_PENALTY_TEMPLATE_TYPE"]</th>
|
|
||||||
<td class="text-light">
|
|
||||||
@info.Action
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr class="d-table-row d-lg-none bg-dark">
|
|
||||||
<th scope="row" class="bg-primary">@loc["WEBFRONT_PENALTY_TEMPLATE_ADMIN"]</th>
|
|
||||||
<td>
|
|
||||||
<a asp-controller="Client" asp-action="ProfileAsync" asp-route-id="@info.OriginId" class="link-inverse">
|
|
||||||
<color-code value="@info.OriginName"></color-code>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr class="d-table-row d-lg-none bg-dark">
|
|
||||||
<th scope="row" class="bg-primary">@loc["WEBFRONT_PENALTY_TEMPLATE_NAME"]</th>
|
|
||||||
<td>
|
|
||||||
@if (info.TargetId != null)
|
|
||||||
{
|
|
||||||
<a asp-controller="Client" asp-action="ProfileAsync" asp-route-id="@info.TargetId" class="link-inverse">
|
|
||||||
<color-code value="@info.TargetName"></color-code>
|
|
||||||
</a>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<span>--</span>
|
|
||||||
}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr class="d-table-row d-lg-none bg-dark">
|
|
||||||
<th scope="row" class="bg-primary">@loc["WEBFRONT_ADMIN_AUDIT_LOG_INFO"]</th>
|
|
||||||
<td class="text-light">
|
|
||||||
@info.Data
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
@*<tr class="d-table-row d-lg-none bg-dark">
|
|
||||||
<th scope="row" class="bg-primary">@loc["WEBFRONT_ADMIN_AUDIT_LOG_PREVIOUS"]</th>
|
|
||||||
<td class="text-light">
|
|
||||||
@info.OldValue
|
|
||||||
</td>
|
|
||||||
</tr>*@
|
|
||||||
<tr class="d-table-row d-lg-none bg-dark">
|
|
||||||
<th scope="row" class="bg-primary">@loc["WEBFRONT_ADMIN_AUDIT_LOG_CURRENT"]</th>
|
|
||||||
<td class="text-light">
|
|
||||||
@info.NewValue
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr class="d-table-row d-lg-none bg-dark">
|
|
||||||
<th scope="row" class="w-25 bg-primary" style="border-bottom: 1px solid #222">@loc["WEBFRONT_ADMIN_AUDIT_LOG_TIME"]</th>
|
|
||||||
<td class="text-light mb-2 border-bottom">
|
|
||||||
@info.When.ToString()
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<!-- desktop -->
|
<!-- desktop -->
|
||||||
<tr class="d-none d-lg-table-row">
|
<tr class="d-none d-lg-table-row bg-dark-dm bg-light-lm">
|
||||||
<td class="text-light font-weight-bold">
|
<td class="font-weight-bold">
|
||||||
@info.Action
|
@info.Action
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a asp-controller="Client" asp-action="ProfileAsync" asp-route-id="@info.OriginId" class="link-inverse">
|
<a asp-controller="Client" asp-action="Profile" asp-route-id="@info.OriginId">
|
||||||
<color-code value="@info.OriginName"></color-code>
|
<color-code value="@info.OriginName"></color-code>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@if (info.TargetId != null)
|
@if (info.TargetId != null)
|
||||||
{
|
{
|
||||||
<a asp-controller="Client" asp-action="ProfileAsync" asp-route-id="@info.TargetId" class="link-inverse">
|
<a asp-controller="Client" asp-action="Profile" asp-route-id="@info.TargetId">
|
||||||
<color-code value="@info.TargetName"></color-code>
|
<color-code value="@info.TargetName"></color-code>
|
||||||
</a>
|
</a>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<span>--</span>
|
<span>–</span>
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
<td class="text-light">
|
<td>
|
||||||
@info.Data
|
@info.Data
|
||||||
|
<td >
|
||||||
@*<td class="text-light">
|
|
||||||
@info.OldValue
|
|
||||||
</td>*@
|
|
||||||
<td class="text-light">
|
|
||||||
@info.NewValue
|
@info.NewValue
|
||||||
</td>
|
</td>
|
||||||
<td class="text-light text-right">
|
<td class="text-right">
|
||||||
@info.When.ToString()
|
@info.When.ToString()
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
<!-- mobile -->
|
||||||
|
<tr class="d-table-row d-lg-none d-flex bg-dark-dm bg-light-lm">
|
||||||
|
<td class="bg-primary text-light text-right flex-grow-0">
|
||||||
|
<div class="mt-5 mb-5">@loc["WEBFRONT_PENALTY_TEMPLATE_TYPE"]</div>
|
||||||
|
<div class="mt-5 mb-5">@loc["WEBFRONT_PENALTY_TEMPLATE_ADMIN"]</div>
|
||||||
|
<div class="mt-5 mb-5">@loc["WEBFRONT_PENALTY_TEMPLATE_NAME"]</div>
|
||||||
|
<div class="mt-5 mb-5">@loc["WEBFRONT_ADMIN_AUDIT_LOG_INFO"]</div>
|
||||||
|
<div class="mt-5 mb-5">@loc["WEBFRONT_ADMIN_AUDIT_LOG_CURRENT"]</div>
|
||||||
|
<div class="mt-5 mb-5">@loc["WEBFRONT_ADMIN_AUDIT_LOG_TIME"]</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="mt-5 mb-5">@info.Action</div>
|
||||||
|
<a asp-controller="Client" asp-action="Profile" asp-route-id="@info.OriginId" class="link-inverse">
|
||||||
|
<color-code value="@info.OriginName"></color-code>
|
||||||
|
</a>
|
||||||
|
@if (info.TargetId != null)
|
||||||
|
{
|
||||||
|
<a asp-controller="Client" asp-action="Profile" asp-route-id="@info.TargetId" class="mt-5 mb-5">
|
||||||
|
<color-code value="@info.TargetName"></color-code>
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="mt-5 mb-5">–</div>
|
||||||
|
}
|
||||||
|
<div class="mt-5 mb-5"> @info.Data</div>
|
||||||
|
<div class="mt-5 mb-5">@info.NewValue</div>
|
||||||
|
<div class="mt-5 mb-5">@info.When.ToString()</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
}
|
}
|
||||||
|
@ -3,60 +3,72 @@
|
|||||||
var loc = Utilities.CurrentLocalization.LocalizationIndex;
|
var loc = Utilities.CurrentLocalization.LocalizationIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="row d-none d-lg-block ">
|
<!-- desktop -->
|
||||||
<h4 class="pb-2 text-center col-12">@ViewBag.Title</h4>
|
<div class="content mt-0">
|
||||||
<div class="mr-auto ml-auto col-12 col-lg-8 border-bottom">
|
<h2 class="content-title mt-20 mb-0">Search Results</h2>
|
||||||
<div class="row pt-2 pb-2 bg-primary">
|
<div class="text-muted mb-15"><span class="badge">@ViewBag.SearchTerm</span> returned <span class="text-primary">@ViewBag.ResultCount</span> matche(s)</div>
|
||||||
<div class="col-5 ">@loc["WEBFRONT_PENALTY_TEMPLATE_NAME"]</div>
|
|
||||||
<div class="col-4">@loc["WEBFRONT_PROFILE_LEVEL"]</div>
|
|
||||||
<div class="col-3 text-right">@loc["WEBFRONT_SEARCH_LAST_CONNECTED"]</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<table class="table d-none d-md-table">
|
||||||
|
<thead>
|
||||||
|
<tr class="bg-primary text-light">
|
||||||
|
<td>@loc["WEBFRONT_PENALTY_TEMPLATE_NAME"]</td>
|
||||||
|
<td>@loc["WEBFRONT_PROFILE_LEVEL"]</td>
|
||||||
|
<td class="text-right">@loc["WEBFRONT_SEARCH_LAST_CONNECTED"]</td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
@foreach (var client in Model)
|
@foreach (var client in Model)
|
||||||
{
|
{
|
||||||
<div class="row pt-2 pb-2 bg-dark">
|
<tr class="bg-dark-dm bg-light-lm">
|
||||||
<div class="col-5">
|
<td class="col-5">
|
||||||
<a asp-controller="Client" asp-action="ProfileAsync" asp-route-id="@client.ClientId">
|
<a asp-controller="Client" asp-action="Profile" asp-route-id="@client.ClientId">
|
||||||
<color-code value="@client.Name"></color-code>
|
<color-code value="@client.Name"></color-code>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</td>
|
||||||
@if (!ViewBag.Authorized && ViewBag.EnablePrivilegedUserPrivacy)
|
@if (!ViewBag.Authorized && ViewBag.EnablePrivilegedUserPrivacy)
|
||||||
{
|
{
|
||||||
<div class="col-4 level-color-0">@loc["GLOBAL_PERMISSION_USER"]</div>
|
<td class="col-3 level-color-0">@loc["GLOBAL_PERMISSION_USER"]</td>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<div class="col-4 level-color-@client.LevelInt">@client.Level</div>
|
<td class="col-3 level-color-@client.LevelInt">@client.Level</td>
|
||||||
}
|
}
|
||||||
<div class="col-3 text-right">@client.LastConnectionText</div>
|
<td class="col-4 text-right">@client.LastConnection.HumanizeForCurrentCulture()</td>
|
||||||
</div>
|
</tr>
|
||||||
}
|
}
|
||||||
</div>
|
</tbody>
|
||||||
</div>
|
</table>
|
||||||
<div class="row d-lg-none">
|
|
||||||
<div class="w-100 bg-primary text-center h3 mb-0 p-3" style="border-bottom: 1px solid #222">@ViewBag.Title</div>
|
|
||||||
@foreach (var client in Model)
|
<!--- mobile -->
|
||||||
{
|
<table class="table bg-dark-dm bg-light-lm d-md-none">
|
||||||
<div class="col-5 bg-primary font-weight-bold" style="border-bottom: 1px solid #222">
|
<tbody>
|
||||||
<div class="p-2">@loc["WEBFRONT_PENALTY_TEMPLATE_NAME"]</div>
|
@foreach (var client in Model)
|
||||||
<div class="p-2">@loc["WEBFRONT_PROFILE_LEVEL"]</div>
|
{
|
||||||
<div class="p-2">@loc["WEBFRONT_SEARCH_LAST_CONNECTED"]</div>
|
<tr class="d-flex">
|
||||||
</div>
|
<td class="bg-primary text-light">
|
||||||
<div class="col-7 bg-dark border-bottom">
|
<div>@loc["WEBFRONT_PENALTY_TEMPLATE_NAME"]</div>
|
||||||
<div class="p-2">
|
<div>@loc["WEBFRONT_PROFILE_LEVEL"]</div>
|
||||||
<a asp-controller="Client" asp-action="ProfileAsync" asp-route-id="@client.ClientId" class="link-inverse">
|
<div>@loc["WEBFRONT_SEARCH_LAST_CONNECTED"]</div>
|
||||||
<color-code value="@client.Name"></color-code>
|
</td>
|
||||||
</a>
|
<td class="flex-grow">
|
||||||
</div>
|
<a asp-controller="Client" asp-action="Profile" asp-route-id="@client.ClientId" class="link-inverse">
|
||||||
@if (!ViewBag.Authorized && ViewBag.EnablePrivilegedUserPrivacy)
|
<color-code value="@client.Name"></color-code>
|
||||||
{
|
</a>
|
||||||
<div class="p-2 level-color-0">@loc["GLOBAL_PERMISSION_USER"]</div>
|
@if (!ViewBag.Authorized && ViewBag.EnablePrivilegedUserPrivacy)
|
||||||
}
|
{
|
||||||
else
|
<div class="p-2 level-color-0">@loc["GLOBAL_PERMISSION_USER"]</div>
|
||||||
{
|
}
|
||||||
<div class="p-2 level-color-@client.LevelInt">@client.Level</div>
|
else
|
||||||
}
|
{
|
||||||
<div class="p-2 text-white-50">@client.LastConnectionText</div>
|
<div class="p-2 level-color-@client.LevelInt">@client.Level</div>
|
||||||
</div>
|
}
|
||||||
}
|
<div>@client.LastConnection.HumanizeForCurrentCulture()</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,39 +1,41 @@
|
|||||||
@using SharedLibraryCore.Dtos.Meta.Responses
|
@using SharedLibraryCore.Dtos.Meta.Responses
|
||||||
@model SharedLibraryCore.Helpers.ResourceQueryHelperResult<MessageResponse>
|
@model SharedLibraryCore.Helpers.ResourceQueryHelperResult<MessageResponse>
|
||||||
|
|
||||||
|
<div class="content mt-0">
|
||||||
@if (ViewBag.Error != null)
|
@if (ViewBag.Error != null)
|
||||||
{
|
{
|
||||||
<h4 class="text-red">@SharedLibraryCore.Utilities.FormatExt(ViewBag.Localization["WEBFRONT_INVALID_QUERY"], ViewBag.Error.Message)</h4>
|
<h2 class="content-title text-red mt-20">@Utilities.FormatExt(ViewBag.Localization["WEBFRONT_INVALID_QUERY"], ViewBag.Error.Message)</h2>
|
||||||
}
|
}
|
||||||
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<h4 class="pb-3 text-center">@SharedLibraryCore.Utilities.FormatExt(ViewBag.Localization["WEBFRONT_STATS_MESSAGES_FOUND"], Model.TotalResultCount.ToString("N0"))</h4>
|
<h2 class="content-title mt-20 mb-0">Search Results</h2>
|
||||||
|
<div class="text-muted mb-15">@Html.Raw(Utilities.FormatExt(ViewBag.Localization["WEBFRONT_STATS_MESSAGES_FOUND"], $"<span class=\"badge\">{Model.TotalResultCount.ToString("N0")}</span>"))</div>
|
||||||
|
|
||||||
<table class="table table-striped table-hover">
|
<table class="table bg-dark-dm bg-light-lm rounded" style="table-layout: fixed">
|
||||||
<thead class="d-none d-lg-table-header-group">
|
<thead class="d-none d-lg-table-header-group">
|
||||||
<tr class="bg-primary pt-2 pb-2">
|
<tr class="bg-primary text-light">
|
||||||
<th scope="col">@ViewBag.Localization["WEBFRONT_PENALTY_TEMPLATE_ADMIN"]</th>
|
<th colspan="20%">@ViewBag.Localization["WEBFRONT_PENALTY_TEMPLATE_ADMIN"]</th>
|
||||||
<th scope="col">@ViewBag.Localization["WEBFRONT_ACTION_LABEL_MESSAGE"]</th>
|
<th colspan="45%">@ViewBag.Localization["WEBFRONT_ACTION_LABEL_MESSAGE"]</th>
|
||||||
<th scope="col">@ViewBag.Localization["WEBFRONT_STATS_MESSAGE_SERVER_NAME"]</th>
|
<th colspan="20%">@ViewBag.Localization["WEBFRONT_STATS_MESSAGE_SERVER_NAME"]</th>
|
||||||
<th scope="col" class="text-right">@ViewBag.Localization["WEBFRONT_ADMIN_AUDIT_LOG_TIME"]</th>
|
<th colspan="15%" class="text-right">@ViewBag.Localization["WEBFRONT_ADMIN_AUDIT_LOG_TIME"]</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="message_table_body" class="border-bottom bg-dark">
|
<tbody id="message_table_body">
|
||||||
<partial name="~/Views/Client/Message/_Item.cshtml" model="@Model.Results" />
|
<partial name="~/Views/Client/Message/_Item.cshtml" model="@Model.Results" />
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<span id="load_more_messages_button" class="loader-load-more oi oi-chevron-bottom text-center text-primary w-100 h3 pb-0 mb-0 d-none d-lg-block"></span>
|
<div id="loaderLoad" class="mt-10 m-auto text-center d-none d-lg-block">
|
||||||
|
<i class="loader-load-more oi oi-chevron-bottom "></i>
|
||||||
|
</div>
|
||||||
|
|
||||||
@section scripts {
|
@section scripts {
|
||||||
<environment include="Development">
|
|
||||||
<script type="text/javascript" src="~/js/loader.js"></script>
|
|
||||||
</environment>
|
|
||||||
<script>
|
<script>
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
initLoader('/Message/FindNext?query=@ViewBag.Query', '#message_table_body', @Model.RetrievedResultCount, @ViewBag.QueryLimit);
|
initLoader('/Message/FindNext?query=@ViewBag.Query', '#message_table_body', @Model.RetrievedResultCount, @ViewBag.QueryLimit);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
</div>
|
||||||
|
@ -5,64 +5,56 @@
|
|||||||
{
|
{
|
||||||
<!-- desktop -->
|
<!-- desktop -->
|
||||||
<tr class="d-none d-lg-table-row">
|
<tr class="d-none d-lg-table-row">
|
||||||
<td>
|
<td colspan="20%" class="text-break">
|
||||||
<a asp-controller="Client" asp-action="ProfileAsync" asp-route-id="@message.ClientId" class="link-inverse">
|
<a asp-controller="Client" asp-action="Profile" asp-route-id="@message.ClientId" class="link-inverse">
|
||||||
<color-code value="@message.ClientName"></color-code>
|
<color-code value="@message.ClientName"></color-code>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-light w-50 text-break">
|
<td colspan="45%" class="text-break">
|
||||||
@if (message.IsHidden && !ViewBag.Authorized)
|
@if (message.IsHidden && !ViewBag.Authorized)
|
||||||
{
|
{
|
||||||
<color-code value="@SharedLibraryCore.Utilities.FormatExt(ViewBag.Localization["WEBFRONT_CLIENT_META_CHAT_HIDDEN"], message.HiddenMessage)"></color-code>
|
<color-code value="@Utilities.FormatExt(ViewBag.Localization["WEBFRONT_CLIENT_META_CHAT_HIDDEN"], message.HiddenMessage)"></color-code>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<color-code value="@message.Message"></color-code>
|
<color-code value="@message.Message"></color-code>
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
<td class="text-light">
|
<td colspan="20%" class="text-break">
|
||||||
<color-code value="@(message.ServerName ?? "--")"></color-code>
|
<color-code value="@(message.ServerName ?? "--")"></color-code>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-right text-light">
|
<td colspan="15%" class="text-right text-break">
|
||||||
@message.When
|
@message.When
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<!-- mobile -->
|
<!-- mobile -->
|
||||||
<tr class="d-table-row d-lg-none bg-dark">
|
<tr class="d-flex d-lg-none">
|
||||||
<th scope="row" class="bg-primary">@ViewBag.Localization["WEBFRONT_PENALTY_TEMPLATE_ADMIN"]</th>
|
<td class="bg-primary text-light">
|
||||||
<td class="text-light">
|
<div>@ViewBag.Localization["WEBFRONT_PENALTY_TEMPLATE_ADMIN"]</div>
|
||||||
<a asp-controller="Client" asp-action="ProfileAsync" asp-route-id="@message.ClientId" class="link-inverse">
|
<div>@ViewBag.Localization["WEBFRONT_ACTION_LABEL_MESSAGE"]</div>
|
||||||
|
<div>@ViewBag.Localization["WEBFRONT_STATS_MESSAGE_SERVER_NAME"]</div>
|
||||||
|
<div>@ViewBag.Localization["WEBFRONT_ADMIN_AUDIT_LOG_TIME"]</div>
|
||||||
|
</td>
|
||||||
|
<td class="flex-fill">
|
||||||
|
<a asp-controller="Client" asp-action="Profile" asp-route-id="@message.ClientId">
|
||||||
<color-code value="@message.ClientName"></color-code>
|
<color-code value="@message.ClientName"></color-code>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
<div>
|
||||||
</tr>
|
@if (message.IsHidden && !ViewBag.Authorized)
|
||||||
|
{
|
||||||
|
<color-code value="@Utilities.FormatExt(ViewBag.Localization["WEBFRONT_CLIENT_META_CHAT_HIDDEN"], message.HiddenMessage)"></color-code>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<color-code value="@message.Message"></color-code>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<color-code value="@(message.ServerName ?? "--")"></color-code>
|
||||||
|
</div>
|
||||||
|
<div> @message.When</div>
|
||||||
|
|
||||||
<tr class="d-table-row d-lg-none bg-dark">
|
|
||||||
<th scope="row" class="bg-primary">@ViewBag.Localization["WEBFRONT_ACTION_LABEL_MESSAGE"]</th>
|
|
||||||
<td class="text-light">
|
|
||||||
@if (message.IsHidden && !ViewBag.Authorized)
|
|
||||||
{
|
|
||||||
<color-code value="@SharedLibraryCore.Utilities.FormatExt(ViewBag.Localization["WEBFRONT_CLIENT_META_CHAT_HIDDEN"], message.HiddenMessage)"></color-code>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<color-code value="@message.Message"></color-code>
|
|
||||||
}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr class="d-table-row d-lg-none bg-dark">
|
|
||||||
<th scope="row" class="bg-primary">@ViewBag.Localization["WEBFRONT_STATS_MESSAGE_SERVER_NAME"]</th>
|
|
||||||
<td class="text-light">
|
|
||||||
<color-code value="@(message.ServerName ?? "--")"></color-code>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr class="d-table-row d-lg-none bg-dark">
|
|
||||||
<th scope="row" class="bg-primary" style="border-bottom: 1px solid #222">@ViewBag.Localization["WEBFRONT_ADMIN_AUDIT_LOG_TIME"]</th>
|
|
||||||
<td class="text-light mb-2 border-bottom">
|
|
||||||
@message.When
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
|
@ -1,24 +1,46 @@
|
|||||||
@model Dictionary<SharedLibraryCore.Database.Models.EFClient.Permission, IList<SharedLibraryCore.Dtos.ClientInfo>>
|
@model Dictionary<Data.Models.Client.EFClient.Permission, IList<SharedLibraryCore.Dtos.ClientInfo>>
|
||||||
|
<div class="content mt-0">
|
||||||
|
<h4 class="content-title mt-20">@ViewBag.Title</h4>
|
||||||
|
|
||||||
<h4 class="pb-3 text-center ">@ViewBag.Title</h4>
|
|
||||||
|
|
||||||
<div class="row border-bottom">
|
@foreach (var key in Model.Keys)
|
||||||
@{
|
{
|
||||||
foreach (var key in Model.Keys)
|
<table class="table mb-20">
|
||||||
{
|
<thead>
|
||||||
<div class="col-12 bg-primary pt-2 pb-2">
|
<tr class="level-bgcolor-@((int)key)">
|
||||||
@Utilities.ToLocalizedLevelName(key)
|
<th class="text-light">@key.ToLocalizedLevelName()</th>
|
||||||
</div>
|
<th class="text-right font-weight-bold">Last Connected</th>
|
||||||
|
</tr>
|
||||||
<div class="col-12 bg-dark pt-2 pb-2">
|
</thead>
|
||||||
@foreach (var client in Model[key])
|
<tbody>
|
||||||
|
<has-permission entity="ClientLevel" required-permission="Read">
|
||||||
|
@foreach (var client in Model[key].OrderByDescending(client => client.LastConnection))
|
||||||
{
|
{
|
||||||
<a asp-controller="Client" asp-action="ProfileAsync" asp-route-id="@client.ClientId">
|
if (!ViewBag.Authorized && client.IsMasked)
|
||||||
<color-code value="@client.Name"></color-code>
|
{
|
||||||
</a>
|
continue;
|
||||||
<br />
|
}
|
||||||
|
<tr class="bg-dark-dm bg-light-lm">
|
||||||
|
<td>
|
||||||
|
@if (client.IsMasked)
|
||||||
|
{
|
||||||
|
<span data-toggle="tooltip" data-title="Client is masked">
|
||||||
|
<span class="oi oi-shield mr-5 font-size-12"></span>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
|
||||||
|
<a asp-controller="Client" asp-action="Profile" asp-route-id="@client.ClientId">
|
||||||
|
<color-code value="@client.Name"></color-code>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td class="text-right">
|
||||||
|
@client.LastConnection.HumanizeForCurrentCulture()
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
}
|
}
|
||||||
</div>
|
</has-permission>
|
||||||
}
|
</tbody>
|
||||||
|
</table>
|
||||||
}
|
}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
@using SharedLibraryCore.Interfaces
|
@using SharedLibraryCore.Interfaces
|
||||||
@using Data.Models
|
@using Data.Models
|
||||||
@using Data.Models.Client
|
@using Data.Models.Client
|
||||||
|
@using WebfrontCore.Permissions
|
||||||
|
@using WebfrontCore.ViewModels
|
||||||
@model SharedLibraryCore.Dtos.PlayerInfo
|
@model SharedLibraryCore.Dtos.PlayerInfo
|
||||||
@{
|
@{
|
||||||
var match = System.Text.RegularExpressions.Regex.Match(Model.Name.ToUpper(), "[A-Z]").Value;
|
var match = System.Text.RegularExpressions.Regex.Match(Model.Name.ToUpper(), "[A-Z]").Value;
|
||||||
@ -11,194 +13,331 @@
|
|||||||
var isTempBanned = Model.ActivePenalty?.Type == EFPenalty.PenaltyType.TempBan;
|
var isTempBanned = Model.ActivePenalty?.Type == EFPenalty.PenaltyType.TempBan;
|
||||||
var translationKey = $"WEBFRONT_PROFILE_{Model.ActivePenalty?.Type.ToString().ToUpper()}_INFO";
|
var translationKey = $"WEBFRONT_PROFILE_{Model.ActivePenalty?.Type.ToString().ToUpper()}_INFO";
|
||||||
var ignoredMetaTypes = new[] { MetaType.Information, MetaType.Other, MetaType.QuickMessage };
|
var ignoredMetaTypes = new[] { MetaType.Information, MetaType.Other, MetaType.QuickMessage };
|
||||||
|
|
||||||
|
string ClassForPenaltyType(EFPenalty.PenaltyType type)
|
||||||
|
{
|
||||||
|
return type switch
|
||||||
|
{
|
||||||
|
EFPenalty.PenaltyType.Ban => "alert-danger",
|
||||||
|
EFPenalty.PenaltyType.Flag => "alert-secondary",
|
||||||
|
EFPenalty.PenaltyType.TempBan => "alert-secondary",
|
||||||
|
_ => "alert"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
string ClassForProfileBackground()
|
||||||
|
{
|
||||||
|
return (ViewBag.PermissionsSet as IEnumerable<string>).HasPermission(WebfrontEntity.ClientLevel, WebfrontPermission.Read) ? $"level-bgcolor-{Model.LevelInt}" : "level-bgcolor-0";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
<div id="profile_wrapper" class="pb-3 row d-flex flex-column flex-lg-row">
|
<div class="content row mt-20">
|
||||||
<!-- Initial/Avatar Column -->
|
<div class="col-12 col-lg-9 col-xl-10">
|
||||||
<div id="profile_avatar" class="d-block d-lg-inline-flex flex-column mr-auto ml-auto mr-lg-0 ml-lg-0 justify-content-center text-center level-bgcolor-@(!ViewBag.Authorized && ViewBag.EnablePrivilegedUserPrivacy ? "0" : Model.LevelInt.ToString()) @(isTempBanned ? "penalties-bgcolor-tempban" : "")" style="background-image:url('@string.Format("https://gravatar.com/avatar/{0}?size=168&default=blank&rating=pg", gravatarUrl)')">
|
@if (Model.ActivePenalty != null)
|
||||||
@if (string.IsNullOrEmpty(gravatarUrl))
|
{
|
||||||
{
|
<has-permission entity="ClientLevel" required-permission="Read">
|
||||||
<span class="profile-shortcode">@shortCode</span>
|
<div class="alert @ClassForPenaltyType(Model.ActivePenalty.Type) mt-10 mb-10" role="alert">
|
||||||
}
|
@foreach (var result in Utilities.SplitTranslationTokens(translationKey))
|
||||||
</div>
|
|
||||||
<!-- Name/Level Column -->
|
|
||||||
<div class="w-50 d-block d-lg-inline-flex flex-column flex-fill text-center text-lg-left pb-3 pb-lg-0 pt-3 pt-lg-0 pl-3 pr-3 ml-auto mr-auto" style="overflow-wrap: anywhere">
|
|
||||||
<div class="mt-n2 d-block d-lg-inline-flex @(ViewBag.Authorized ? "" : "flex-fill")">
|
|
||||||
<div id="profile_name" class="client-name h1 mb-0">
|
|
||||||
<color-code value="@Model.Name"></color-code>
|
|
||||||
</div>
|
|
||||||
@if (ViewBag.Authorized)
|
|
||||||
{
|
{
|
||||||
<div id="profile_aliases_btn" class="oi oi-caret-bottom h3 ml-0 ml-lg-2 mb-0 pt-lg-2 mt-lg-1"></div>
|
switch (result.MatchValue)
|
||||||
|
{
|
||||||
|
case "reason":
|
||||||
|
<span class="text-light-dm font-weight-lighter">@(ViewBag.Authorized ? !string.IsNullOrEmpty(Model.ActivePenalty.AutomatedOffense) && Model.ActivePenalty.Type != EFPenalty.PenaltyType.Warning ? Utilities.FormatExt(ViewBag.Localization["WEBFRONT_PROFILE_ANTICHEAT_DETECTION"], Model.ActivePenalty.AutomatedOffense) : Model.ActivePenalty.Offense.StripColors() : Model.ActivePenalty.Offense.StripColors())</span>
|
||||||
|
break;
|
||||||
|
case "time":
|
||||||
|
<span class="text-light-dm font-weight-lighter">
|
||||||
|
@((Model.ActivePenalty.Expires.Value - DateTime.UtcNow).HumanizeForCurrentCulture())
|
||||||
|
</span>
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
<span>@result.MatchValue</span>
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
</has-permission>
|
||||||
|
}
|
||||||
|
|
||||||
@if (ViewBag.Authorized)
|
<h2 class="content-title mb-10">Player Profile</h2>
|
||||||
{
|
|
||||||
<div class="d-flex flex-row justify-content-start flex-fill flex-column flex-lg-row mr-lg-2 mb-2 mb-md-0">
|
|
||||||
<div class="ip-lookup-profile align-self-center mr-0 mr-lg-2 ml-lg-n1" data-ip="@Model.IPAddress"></div>
|
|
||||||
<div id="ip_lookup_country" class="h4 mb-2 mb-lg-0 align-self-center text-muted"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="profile_aliases" class="text-muted pt-0 pt-lg-2 pb-2">
|
<div id="profile_wrapper" class="mb-10 mt-10">
|
||||||
@foreach (var linked in Model.LinkedAccounts)
|
|
||||||
{
|
|
||||||
<div>
|
|
||||||
@Html.ActionLink(linked.Value.ToString("X"), "ProfileAsync", "Client", new { id = linked.Key }, new { @class = "link-inverse" })
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
@foreach (var alias in Model.Aliases)
|
|
||||||
{
|
|
||||||
<div>
|
|
||||||
<color-code value="@alias"></color-code>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
@foreach (var ip in Model.IPs)
|
<!-- online status indicator -->
|
||||||
|
@if (Model.Online)
|
||||||
|
{
|
||||||
|
<div class="bg-success rounded-circle position-absolute status-indicator z-20 mt-10 ml-10" data-toggle="tooltip" data-placement="bottom" data-title="Client is online"></div>
|
||||||
|
<div class="bg-success rounded-circle position-absolute status-indicator with-ripple z-10 mt-10 ml-10"></div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<!-- main profile row -->
|
||||||
|
<div class="card p-15 ml-0 mr-0 mt-0 mb-10 d-flex flex-fill flex-wrap flex-column flex-md-row justify-content-center">
|
||||||
|
<div class="pl-5 pr-5 d-flex flex-column flex-md-row">
|
||||||
|
<div id="profile_avatar" class="w-150 w-md-100 h-150 h-md-100 mt-5 mb-5 d-flex justify-content-center align-self-center rounded @ClassForProfileBackground() @(isTempBanned ? "penalties-bgcolor-tempban" : "")" style="background-image:url('@($"https://gravatar.com/avatar/{gravatarUrl}?size=168&default=blank&rating=pg")">
|
||||||
|
@if (string.IsNullOrEmpty(gravatarUrl))
|
||||||
{
|
{
|
||||||
<div>
|
<div class="profile-shortcode align-self-center text-dark-lm">@shortCode</div>
|
||||||
<a class="ip-locate-link" href="#" data-ip="@ip">@ip</a>
|
|
||||||
<a href="/Client/FindAsync?clientName=@ip" class="oi oi-magnifying-glass text-muted"></a>
|
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
<div class="d-flex flex-column align-self-center ml-20 mr-20 mt-10 mb-10 mt-md-0 mb-md-0 text-center text-md-left">
|
||||||
@if (Model.ActivePenalty != null && (Model.ActivePenalty.Type != EFPenalty.PenaltyType.Flag || ViewBag.Authorized))
|
<!-- name -->
|
||||||
{
|
<div id="profile_name">
|
||||||
<div class="font-weight-bold h4 mb-0 penalties-color-@Model.ActivePenalty.Type.ToString().ToLower()">
|
<span class="font-size-20 font-weight-medium">
|
||||||
@foreach (var result in Utilities.SplitTranslationTokens(translationKey))
|
<color-code value="@Model.Name"></color-code>
|
||||||
{
|
</span>
|
||||||
switch (result.MatchValue)
|
<has-permission entity="MetaAliasUpdate" required-permission="Read">
|
||||||
|
<div class="dropdown with-arrow">
|
||||||
|
<div data-toggle="dropdown" id="profileAliasHistory" aria-haspopup="true" aria-expanded="false">
|
||||||
|
@if (Model.Aliases.Any())
|
||||||
|
{
|
||||||
|
<i class="oi oi-caret-bottom font-size-12" aria-hidden="true"></i>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="dropdown-menu dropdown-menu-center @(Model.Aliases.Where(alias => !alias.Item1.Contains(" ")).Max(alias => (int?)alias.Item1.Length) >= 15 ? "w-250" : "")" aria-labelledby="profileAliasHistory">
|
||||||
|
@foreach (var (alias, dateAdded) in Model.Aliases.OrderByDescending(alias => alias.Item2).Take(15))
|
||||||
|
{
|
||||||
|
<a asp-controller="Client" asp-action="Find" asp-route-clientName="@alias.StripColors()" class="dropdown-item" data-toggle="tooltip" data-title="@dateAdded.HumanizeForCurrentCulture()">
|
||||||
|
<i class="oi oi-magnifying-glass text-muted mr-5"></i>
|
||||||
|
<color-code value="@alias"></color-code>
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
@if (Model.Aliases.Count > 15)
|
||||||
|
{
|
||||||
|
<div class="dropdown-divider"></div>
|
||||||
|
<span class="dropdown-item bg-dark-dm bg-light-lm">...and @(Model.Aliases.Count - 15) more</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</has-permission>
|
||||||
|
</div>
|
||||||
|
<!-- permission level -->
|
||||||
|
<has-permission entity="ClientLevel" required-permission="Read">
|
||||||
|
<div class="align-self-center align-self-md-start font-weight-bold font-size-16 level-color-@Model.LevelInt">
|
||||||
|
<div class="d-flex flex-row">
|
||||||
|
<span>@Model.Level</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</has-permission>
|
||||||
|
|
||||||
|
<!-- guid -->
|
||||||
|
<has-permission entity="ClientGuid" required-permission="Read">
|
||||||
|
<div class="dropdown dropup with-arrow">
|
||||||
|
<div class="text-muted" data-toggle="dropdown" id="altGuidFormatsDropdown" aria-haspopup="true" aria-expanded="false">@Model.NetworkId.ToString("X")</div>
|
||||||
|
<div class="dropdown-menu" aria-labelledby="altGuidFormatsDropdown">
|
||||||
|
<div class="p-10 font-size-12">
|
||||||
|
<div class="">Alternative Formats</div>
|
||||||
|
<div class="dropdown-divider mt-5 mb-5"></div>
|
||||||
|
<div class="text-muted font-weight-lighter">@((ulong)Model.NetworkId)</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</has-permission>
|
||||||
|
|
||||||
|
<!-- ip address -->
|
||||||
|
<div class="align-self-center align-self-md-start d-flex flex-row">
|
||||||
|
<span class="text-muted mr-5">@Model.IPAddress</span>
|
||||||
|
<has-permission entity="MetaAliasUpdate" required-permission="Read">
|
||||||
|
<div class="dropdown with-arrow">
|
||||||
|
<div data-toggle="dropdown" id="profileIPAddressHistory" aria-haspopup="true" aria-expanded="false">
|
||||||
|
@if (Model.IPs.Any(ip => !string.IsNullOrEmpty(ip.Item1)))
|
||||||
|
{
|
||||||
|
<i class="oi oi-caret-bottom font-size-12 text-muted" aria-hidden="true"></i>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div class="dropdown-menu dropdown-menu-center" aria-labelledby="profileAliasHistory">
|
||||||
|
@foreach (var (ip, dateAdded) in Model.IPs.OrderByDescending(ip => ip.Item2).Take(15))
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(ip))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
<div class="d-flex dropdown-item" data-toggle="tooltip" data-title="@dateAdded.HumanizeForCurrentCulture()">
|
||||||
|
<a asp-controller="Client" asp-action="Find" asp-route-clientName="@ip">
|
||||||
|
<i class="oi oi-magnifying-glass text-muted mr-5"></i>
|
||||||
|
</a>
|
||||||
|
<a href="#contextModal" class="profile-ip-lookup dropdown-item p-0 m-0" data-ip="@ip">
|
||||||
|
<color-code value="@ip"></color-code>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
@if (Model.IPs.Count > 15)
|
||||||
|
{
|
||||||
|
<div class="dropdown-divider"></div>
|
||||||
|
<span class="dropdown-item bg-dark-dm bg-light-lm">...and @(Model.IPs.Count - 15) more</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</has-permission>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex-fill d-flex justify-content-center justify-content-md-end mt-10 mt-md-0">
|
||||||
|
<!-- country flag -->
|
||||||
|
<div id="ipGeoDropdown" class="dropdown with-arrow align-self-center">
|
||||||
|
<a href="#" data-toggle="dropdown" id="avatar-popover-toggle" aria-haspopup="true" aria-expanded="false">
|
||||||
|
@if (!string.IsNullOrEmpty(Model.GeoLocationInfo.CountryCode))
|
||||||
{
|
{
|
||||||
case "reason":
|
<div class="profile-country-flag w-100 rounded align-self-center" style="background-image: url('https://flagcdn.com/w160/@(Model.GeoLocationInfo.CountryCode.ToLower()).png')" data-ip="@Model.IPAddress"></div>
|
||||||
<span class="text-white font-weight-lighter">@(ViewBag.Authorized ? !string.IsNullOrEmpty(Model.ActivePenalty.AutomatedOffense) && Model.ActivePenalty.Type != EFPenalty.PenaltyType.Warning ? Utilities.FormatExt(ViewBag.Localization["WEBFRONT_PROFILE_ANTICHEAT_DETECTION"], Model.ActivePenalty.AutomatedOffense) : Model.ActivePenalty.Offense.StripColors() : Model.ActivePenalty.Offense.StripColors())</span>
|
|
||||||
break;
|
|
||||||
case "time":
|
|
||||||
<span class="text-white font-weight-lighter">
|
|
||||||
@((Model.ActivePenalty.Expires.Value - DateTime.UtcNow).HumanizeForCurrentCulture())
|
|
||||||
</span>
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
<span>@result.MatchValue</span>
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (!ViewBag.Authorized && ViewBag.EnablePrivilegedUserPrivacy)
|
|
||||||
{
|
|
||||||
<div id="profile_level" class="font-weight-bold h4 mb-0 level-color-0">
|
|
||||||
@ViewBag.Localization["GLOBAL_PERMISSION_USER"]
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<div id="profile_level" class="font-weight-bold h4 mb-0 level-color-@Model.LevelInt">
|
|
||||||
@Model.Level
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="pr-lg-0 text-center text-lg-right">
|
|
||||||
@if (ViewBag.Authorized)
|
|
||||||
{
|
|
||||||
@if (!isPermBanned)
|
|
||||||
{
|
|
||||||
<div class="profile-action oi oi-flag h3 ml-2 @(isFlagged ? "text-secondary" : "text-success")" data-action="@(isFlagged ? "unflag" : "flag")" aria-hidden="true"></div>
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (Model.LevelInt < (int)ViewBag.User.Level && !Model.HasActivePenalty)
|
|
||||||
{
|
|
||||||
<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 && Model.HasActivePenalty)
|
|
||||||
{
|
|
||||||
@if (isTempBanned)
|
|
||||||
{
|
|
||||||
<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>
|
|
||||||
<div id="profile_action_unban_btn" class="profile-action oi oi-lock-locked penalties-color-tempban h3 ml-2" title="Unban Client" data-action="unban" aria-hidden="true"></div>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<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>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@if (Model.LevelInt != -1)
|
|
||||||
{
|
|
||||||
<div id="profile_action_edit_btn" class="profile-action oi oi-cog text-muted h3 ml-2" title="Client Options" data-action="edit" aria-hidden="true"></div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@if (ViewBag.UseNewStats)
|
|
||||||
{
|
|
||||||
<a asp-controller="ClientStatistics" asp-action="Advanced" asp-route-id="@Model.ClientId" class="oi oi-graph text-primary h3 ml-2" title="Stats" aria-hidden="true"></a>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="profile_info" class="row d-block d-lg-flex flex-row border-bottom border-top pt-2 pb-2">
|
|
||||||
<partial name="Meta/_Information.cshtml" model="@Model.Meta"/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row border-bottom">
|
|
||||||
<div class="d-md-flex flex-fill">
|
|
||||||
<div class="bg-dark p-2 pl-3 pr-3 text-center text-muted border-0 align-self-stretch align-middle" id="filter_meta_container_button">
|
|
||||||
<span class="text-primary" id="meta_filter_dropdown_icon">▼</span>
|
|
||||||
<a>@ViewBag.Localization["WEBFRONT_CLIENT_META_FILTER"]</a>
|
|
||||||
</div>
|
|
||||||
<div id="filter_meta_container" class="d-none d-md-flex flex-md-fill flex-md-wrap">
|
|
||||||
@{
|
|
||||||
const int defaultTabCount = 5;
|
|
||||||
var metaTypes = Enum.GetValues(typeof(MetaType))
|
|
||||||
.Cast<MetaType>()
|
|
||||||
.Where(type => !ignoredMetaTypes.Contains(type))
|
|
||||||
.OrderByDescending(type => type == MetaType.All)
|
|
||||||
.ToList();
|
|
||||||
var selectedMeta = metaTypes.FirstOrDefault(meta => metaTypes.IndexOf(Model.MetaFilterType ?? MetaType.All) >= defaultTabCount && meta != MetaType.All && meta == Model.MetaFilterType);
|
|
||||||
}
|
|
||||||
@foreach (var type in metaTypes.Take(defaultTabCount - 1).Append(selectedMeta == MetaType.Other ? metaTypes[defaultTabCount - 1] : selectedMeta))
|
|
||||||
{
|
|
||||||
<a asp-action="ProfileAsync" asp-controller="Client"
|
|
||||||
class="meta-filter nav-link p-2 pl-3 pr-3 text-center @(Model.MetaFilterType.HasValue && Model.MetaFilterType.Value.ToString() == type.ToString() ? "btn-primary text-white" : "text-muted")"
|
|
||||||
asp-route-id="@Model.ClientId"
|
|
||||||
asp-route-metaFilterType="@type"
|
|
||||||
data-meta-type="@type">
|
|
||||||
@type.ToTranslatedName()
|
|
||||||
</a>
|
</a>
|
||||||
}
|
<div class="dropdown-menu dropdown-menu-center z-30" aria-labelledby="avatar-popover-toggle">
|
||||||
<div class="d-md-none" id="additional_meta_filter">
|
<has-permission entity="ClientIPAddress" required-permission="Read">
|
||||||
@foreach (var type in (selectedMeta == MetaType.Other ? metaTypes.Skip(defaultTabCount) : metaTypes.Skip(defaultTabCount).Append(metaTypes[defaultTabCount - 1])).Where(meta => selectedMeta == MetaType.Other || meta != selectedMeta))
|
<h6 class="dropdown-header font-weight-bold">@Model.IPAddress</h6>
|
||||||
{
|
<div class="dropdown-divider"></div>
|
||||||
<a asp-action="ProfileAsync" asp-controller="Client"
|
</has-permission>
|
||||||
class="meta-filter nav-link p-2 pl-3 pr-3 text-center @(Model.MetaFilterType.HasValue && Model.MetaFilterType.Value.ToString() == type.ToString() ? "btn-primary text-white" : "text-muted")"
|
|
||||||
asp-route-id="@Model.ClientId"
|
<div class="dropdown-item geo-country">@Model.GeoLocationInfo.Country</div>
|
||||||
asp-route-metaFilterType="@type"
|
@if (!string.IsNullOrEmpty(Model.GeoLocationInfo.Region))
|
||||||
data-meta-type="@type">
|
{
|
||||||
@type.ToTranslatedName()
|
<div class="dropdown-item text-muted geo-region">@Model.GeoLocationInfo.Region</div>
|
||||||
</a>
|
<div class="dropdown-divider"></div>
|
||||||
}
|
}
|
||||||
|
@if (!string.IsNullOrEmpty(Model.GeoLocationInfo.Organization))
|
||||||
|
{
|
||||||
|
<div class="dropdown-item geo-organization">@Model.GeoLocationInfo.Organization</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<hr class="mr-5 ml-5"/>
|
||||||
|
<!-- meta info block -->
|
||||||
|
<div class="d-flex flex-column flex-md-row text-center text-md-left flex-wrap">
|
||||||
|
<partial name="Meta/_Information.cshtml" model="@Model.Meta"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if ((!ViewBag.Authorized && !ViewBag.EnablePrivilegedUserPrivacy) || ViewBag.Authorized)
|
|
||||||
|
<hr class="mt-10 mb-10"/>
|
||||||
|
|
||||||
|
<!-- meta filter list -->
|
||||||
|
<div class="mb-10 mt-15">
|
||||||
|
@foreach (var type in Enum.GetValues(typeof(MetaType)).Cast<MetaType>().Where(meta => !ignoredMetaTypes.Contains(meta)).OrderByDescending(meta => meta == MetaType.All))
|
||||||
|
{
|
||||||
|
var buttonClass = !Model.MetaFilterType.HasValue && type == MetaType.All || Model.MetaFilterType.HasValue && Model.MetaFilterType.Value.ToString() == type.ToString() ? "btn-primary text-light" : "text-muted";
|
||||||
|
<a asp-action="Profile" asp-controller="Client"
|
||||||
|
class="meta-filter no-decoration"
|
||||||
|
asp-route-id="@Model.ClientId"
|
||||||
|
asp-route-metaFilterType="@type"
|
||||||
|
data-meta-type="@type">
|
||||||
|
<button class="btn btn-sm d-none d-md-inline mt-5 mb-5 @buttonClass">@type.ToTranslatedName()</button>
|
||||||
|
<button class="btn btn-block d-block d-md-none mt-10 mb-10 @buttonClass">@type.ToTranslatedName()</button>
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (!ViewBag.Authorized && !ViewBag.EnablePrivilegedUserPrivacy || ViewBag.Authorized)
|
||||||
{
|
{
|
||||||
<div class="row d-md-flex pt-2">
|
<div class="row d-md-flex pt-2">
|
||||||
<div id="profile_events" class="text-muted text-left pl-4 pr-4 pl-md-0 pr-md-0">
|
<div id="profile_events" class="text-muted text-left pl-md-0 pr-md-0">
|
||||||
@await Component.InvokeAsync("ProfileMetaList", new { clientId = Model.ClientId, count = 30, offset = 0, startAt = DateTime.UtcNow, metaType = Model.MetaFilterType })
|
@await Component.InvokeAsync("ProfileMetaList", new { clientId = Model.ClientId, count = 30, offset = 0, startAt = DateTime.UtcNow, metaType = Model.MetaFilterType })
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="row">
|
<hr class="mt-10 mb-10"/>
|
||||||
<div class="oi oi-chevron-bottom text-center mt-2 btn btn-primary btn-block loader-load-more" title="Load more meta" data-action="unban" aria-hidden="true"></div>
|
|
||||||
|
<div class="text-center">
|
||||||
|
<i id="loaderLoad" class="oi oi-chevron-bottom loader-load-more text-primary" aria-hidden="true"></i>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- actions desktop -->
|
||||||
|
@{
|
||||||
|
var menuItems = new SideContextMenuItems
|
||||||
|
{
|
||||||
|
MenuTitle = "Actions",
|
||||||
|
};
|
||||||
|
|
||||||
|
if (Model.Online)
|
||||||
|
{
|
||||||
|
menuItems.Items.Add(new SideContextMenuItem
|
||||||
|
{
|
||||||
|
Title = "Join Game",
|
||||||
|
IsLink = true,
|
||||||
|
IsButton = true,
|
||||||
|
Reference = Model.ConnectProtocolUrl,
|
||||||
|
Tooltip = $"Playing on {Model.CurrentServerName.StripColors()}",
|
||||||
|
Icon = "oi-play-circle"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Model.LevelInt != -1 && ViewBag.Authorized)
|
||||||
|
{
|
||||||
|
menuItems.Items.Add(new SideContextMenuItem
|
||||||
|
{
|
||||||
|
Title = "Change Level",
|
||||||
|
IsButton = true,
|
||||||
|
Reference = "edit",
|
||||||
|
Icon = "oi-cog",
|
||||||
|
EntityId = Model.ClientId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
menuItems.Items.Add(new SideContextMenuItem
|
||||||
|
{
|
||||||
|
Title = "View Stats",
|
||||||
|
IsButton = true,
|
||||||
|
IsLink = true,
|
||||||
|
Reference = Url.Action("Advanced", "ClientStatistics", new { id = Model.ClientId }),
|
||||||
|
Icon = "oi-graph",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!isPermBanned && ViewBag.Authorized)
|
||||||
|
{
|
||||||
|
menuItems.Items.Add(new SideContextMenuItem
|
||||||
|
{
|
||||||
|
Title = isFlagged ? "Unflag" : "Flag",
|
||||||
|
IsButton = true,
|
||||||
|
Reference = isFlagged ? "unflag" : "flag",
|
||||||
|
Icon = "oi-flag",
|
||||||
|
EntityId = Model.ClientId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Model.LevelInt < (int)ViewBag.User.Level && Model.Online)
|
||||||
|
{
|
||||||
|
menuItems.Items.Add(new SideContextMenuItem
|
||||||
|
{
|
||||||
|
Title = "Kick",
|
||||||
|
IsButton = true,
|
||||||
|
Reference = "kick",
|
||||||
|
Icon = "oi-circle-x",
|
||||||
|
EntityId = Model.ClientId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((Model.LevelInt < (int)ViewBag.User.Level && !Model.HasActivePenalty || isTempBanned) && ViewBag.Authorized)
|
||||||
|
{
|
||||||
|
menuItems.Items.Add(new SideContextMenuItem
|
||||||
|
{
|
||||||
|
Title = "Ban",
|
||||||
|
IsButton = true,
|
||||||
|
Reference = "ban",
|
||||||
|
Icon = "oi-lock-unlocked",
|
||||||
|
EntityId = Model.ClientId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((Model.LevelInt < (int)ViewBag.User.Level && Model.HasActivePenalty || isTempBanned) && ViewBag.Authorized)
|
||||||
|
{
|
||||||
|
menuItems.Items.Add(new SideContextMenuItem
|
||||||
|
{
|
||||||
|
Title = "Unban",
|
||||||
|
IsButton = true,
|
||||||
|
Reference = "unban",
|
||||||
|
Icon = "oi-lock-locked",
|
||||||
|
EntityId = Model.ClientId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
<partial name="_SideContextMenu" for="@menuItems"></partial>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@section targetid {
|
@section targetid {
|
||||||
@ -207,7 +346,6 @@
|
|||||||
|
|
||||||
@section scripts {
|
@section scripts {
|
||||||
<environment include="Development">
|
<environment include="Development">
|
||||||
<script type="text/javascript" src="~/js/loader.js"></script>
|
|
||||||
<script type="text/javascript" src="~/js/profile.js"></script>
|
<script type="text/javascript" src="~/js/profile.js"></script>
|
||||||
</environment>
|
</environment>
|
||||||
<script>initLoader('/Client/Meta/@Model.ClientId', '#profile_events', 30, 30, [{ 'name': 'metaFilterType', 'value': '@Model.MetaFilterType' }]);</script>
|
<script>initLoader('/Client/Meta/@Model.ClientId', '#profile_events', 30, 30, [{ 'name': 'metaFilterType', 'value': '@Model.MetaFilterType' }]);</script>
|
||||||
|
@ -1,53 +1,60 @@
|
|||||||
@using SharedLibraryCore.Dtos.Meta.Responses
|
@using SharedLibraryCore.Dtos.Meta.Responses
|
||||||
@model AdministeredPenaltyResponse
|
@model AdministeredPenaltyResponse
|
||||||
@{
|
@{
|
||||||
string localizationKey = $"WEBFRONT_CLIENT_META_PENALIZED_{Model.PenaltyType.ToString().ToUpper()}_V2";
|
var localizationKey = $"WEBFRONT_CLIENT_META_PENALIZED_{Model.PenaltyType.ToString().ToUpper()}_V2";
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="d-inline">
|
<has-permission entity="Penalty" required-permission="Read">
|
||||||
@foreach (var match in Utilities.SplitTranslationTokens(localizationKey))
|
@if (TempData["ShowMetaHeader"] as bool? ?? false)
|
||||||
{
|
{
|
||||||
if (match.IsInterpolation)
|
<partial name="./_MetaHeader.cshtml" for="@Model.When"/>
|
||||||
{
|
|
||||||
if (match.MatchValue == "action")
|
|
||||||
{
|
|
||||||
<span class="penalties-color-@Model.PenaltyType.ToString().ToLower()">@match.TranslationValue</span>
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (match.MatchValue == "offender")
|
|
||||||
{
|
|
||||||
<span class="text-highlight">
|
|
||||||
<a class="link-inverse" href="@Model.OffenderClientId">
|
|
||||||
<color-code value="@Model.OffenderName"></color-code>
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (match.MatchValue == "reason")
|
|
||||||
{
|
|
||||||
<span class="text-white">
|
|
||||||
@if (ViewBag.Authorized && !string.IsNullOrEmpty(Model.AutomatedOffense) && Model.PenaltyType != Data.Models.EFPenalty.PenaltyType.Warning)
|
|
||||||
{
|
|
||||||
<span>@Utilities.FormatExt(ViewBag.Localization["WEBFRONT_PROFILE_ANTICHEAT_DETECTION"], Model.AutomatedOffense)</span>
|
|
||||||
<span class="oi oi-list-rich align-top text-primary automated-penalty-info-detailed" data-penalty-id="@Model.PenaltyId" style="margin-top: 0.125rem;" title="@ViewBag.Localization["WEBFRONT_CLIENT_META_AC_METRIC"]"></span>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<color-code value="@Model.Offense"></color-code>
|
|
||||||
}
|
|
||||||
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (match.MatchValue == "time")
|
|
||||||
{
|
|
||||||
<span class="text-white">@Model.LengthText</span>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<span>@match.MatchValue</span>
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</div>
|
|
||||||
|
<div class="d-inline">
|
||||||
|
@foreach (var match in Utilities.SplitTranslationTokens(localizationKey))
|
||||||
|
{
|
||||||
|
if (match.IsInterpolation)
|
||||||
|
{
|
||||||
|
if (match.MatchValue == "action")
|
||||||
|
{
|
||||||
|
<span class="penalties-color-@Model.PenaltyType.ToString().ToLower()">@match.TranslationValue</span>
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (match.MatchValue == "offender")
|
||||||
|
{
|
||||||
|
<span class="text-highlight">
|
||||||
|
<a class="link-inverse" href="@Model.OffenderClientId">
|
||||||
|
<color-code value="@Model.OffenderName"></color-code>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (match.MatchValue == "reason")
|
||||||
|
{
|
||||||
|
<span class="text-light-dm text-dark-lm">
|
||||||
|
@if (ViewBag.Authorized && !string.IsNullOrEmpty(Model.AutomatedOffense) && Model.PenaltyType != Data.Models.EFPenalty.PenaltyType.Warning)
|
||||||
|
{
|
||||||
|
<span>@Utilities.FormatExt(ViewBag.Localization["WEBFRONT_PROFILE_ANTICHEAT_DETECTION"], Model.AutomatedOffense)</span>
|
||||||
|
<span class="oi oi-list-rich align-top text-primary automated-penalty-info-detailed" data-penalty-id="@Model.PenaltyId" style="margin-top: 0.125rem;" title="@ViewBag.Localization["WEBFRONT_CLIENT_META_AC_METRIC"]"></span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<color-code value="@Model.Offense"></color-code>
|
||||||
|
}
|
||||||
|
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (match.MatchValue == "time")
|
||||||
|
{
|
||||||
|
<span class="text-light-dm text-dark-lm">@Model.LengthText</span>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span>@match.MatchValue</span>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</has-permission>
|
||||||
|
@ -4,6 +4,11 @@
|
|||||||
var localizationKey = $"WEBFRONT_CLIENT_META_CONNECTION_{Model.ConnectionType.ToString().ToUpper()}";
|
var localizationKey = $"WEBFRONT_CLIENT_META_CONNECTION_{Model.ConnectionType.ToString().ToUpper()}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@if (TempData["ShowMetaHeader"] as bool? ?? false)
|
||||||
|
{
|
||||||
|
<partial name="./_MetaHeader.cshtml" for="@Model.When"/>
|
||||||
|
}
|
||||||
|
|
||||||
@foreach (var token in Utilities.SplitTranslationTokens(localizationKey))
|
@foreach (var token in Utilities.SplitTranslationTokens(localizationKey))
|
||||||
{
|
{
|
||||||
if (token.IsInterpolation)
|
if (token.IsInterpolation)
|
||||||
@ -11,10 +16,10 @@
|
|||||||
switch (token.MatchValue)
|
switch (token.MatchValue)
|
||||||
{
|
{
|
||||||
case "action":
|
case "action":
|
||||||
<span class="@(Model.ConnectionType == Reference.ConnectionType.Connect ? "text-light-green" : "text-warning")">@token.TranslationValue</span>
|
<span class="@(Model.ConnectionType == Reference.ConnectionType.Connect ? "text-light-green" : "text-secondary")">@token.TranslationValue</span>
|
||||||
break;
|
break;
|
||||||
case "server":
|
case "server":
|
||||||
<span class="text-white">
|
<span class="text-light-dm text-dark-lm">
|
||||||
<color-code value="@Model.ServerName"></color-code>
|
<color-code value="@Model.ServerName"></color-code>
|
||||||
</span>
|
</span>
|
||||||
break;
|
break;
|
||||||
|
@ -1,20 +1,22 @@
|
|||||||
@model IEnumerable<SharedLibraryCore.Dtos.Meta.Responses.InformationResponse>
|
@using Humanizer
|
||||||
|
@model IEnumerable<SharedLibraryCore.Dtos.Meta.Responses.InformationResponse>
|
||||||
@{
|
@{
|
||||||
var informationMeta = Model
|
var informationMeta = Model
|
||||||
.Where(_meta => _meta.Type == SharedLibraryCore.Interfaces.MetaType.Information)
|
.Where(meta => meta.Type == SharedLibraryCore.Interfaces.MetaType.Information)
|
||||||
.OrderBy(_meta => _meta.Order)
|
.OrderBy(meta => meta.Order)
|
||||||
.GroupBy(_meta => _meta.Column)
|
.Select((meta, i) => new { index = i, meta })
|
||||||
.OrderBy(_grouping => _grouping.Key);
|
.GroupBy(meta => meta.index / 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<div class="d-flex flex-wrap">
|
||||||
@foreach (var metaColumn in informationMeta)
|
@foreach (var metaColumn in informationMeta)
|
||||||
{
|
{
|
||||||
<div class="text-center text-lg-left mr-0 mr-lg-4">
|
<!-- <div class="mr-20"> -->
|
||||||
@foreach (var meta in metaColumn)
|
@foreach (var meta in metaColumn)
|
||||||
{
|
{
|
||||||
<div class="profile-meta-entry" title="@meta.ToolTipText">
|
<div class="m-md-5 p-15 w-half rounded bg-very-dark-dm bg-light-ex-lm profile-meta-entry font-size-12 w-md-100 w-lg-150" data-toggle="@(!string.IsNullOrEmpty(meta.meta.ToolTipText) ? "tooltip" : "")" data-title="@meta.meta.ToolTipText" data-placement="bottom">
|
||||||
|
|
||||||
@{var results = Utilities.SplitTranslationTokens(meta.Key);}
|
@{var results = Utilities.SplitTranslationTokens(meta.meta.Key);}
|
||||||
|
|
||||||
@if (results.Any(_result => _result.IsInterpolation))
|
@if (results.Any(_result => _result.IsInterpolation))
|
||||||
{
|
{
|
||||||
@ -22,22 +24,23 @@
|
|||||||
{
|
{
|
||||||
if (result.IsInterpolation)
|
if (result.IsInterpolation)
|
||||||
{
|
{
|
||||||
<span class="profile-meta-value text-primary"><color-code value="@meta.Value"></color-code></span>
|
<div class="profile-meta-value text-primary font-size-14"><color-code value="@meta.meta.Value"></color-code></div>
|
||||||
}
|
}
|
||||||
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<span class="profile-meta-title text-muted">@result.MatchValue</span>
|
<span class="profile-meta-title text-muted font-size-12">@result.MatchValue.Titleize()</span>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<span class="profile-meta-value text-primary"><color-code value="@meta.Value"></color-code></span>
|
<div class="profile-meta-value text-primary font-size-14"><color-code value="@meta.meta.Value"></color-code></div>
|
||||||
<span class="profile-meta-title text-muted"> @meta.Key</span>
|
<div class="profile-meta-title text-muted font-size-12">@meta.meta.Key.Titleize()</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
<!-- </div> -->
|
||||||
}
|
}
|
||||||
|
</div>
|
||||||
|
@ -1,17 +1,24 @@
|
|||||||
@using SharedLibraryCore.Dtos.Meta.Responses
|
@using SharedLibraryCore.Dtos.Meta.Responses
|
||||||
@model MessageResponse
|
@model MessageResponse
|
||||||
|
|
||||||
|
@if (TempData["ShowMetaHeader"] as bool? ?? false)
|
||||||
|
{
|
||||||
|
<partial name="./_MetaHeader.cshtml" for="@Model.When"/>
|
||||||
|
}
|
||||||
|
|
||||||
<span class="client-message" data-serverid="@Model.ServerId" data-when="@Model.When.ToFileTimeUtc()">
|
<span class="client-message" data-serverid="@Model.ServerId" data-when="@Model.When.ToFileTimeUtc()">
|
||||||
<span class="oi oi-chevron-right text-white-50 align-middle client-message-prefix" title="@ViewBag.Localization["WEBFRONT_PROFILE_MESSAGE_CONTEXT"]" style="font-size: 0.75rem; margin-top: -0.256rem"></span>
|
<span data-title="View Context" data-toggle="tooltip" data-placement="right">
|
||||||
|
<span class="oi oi-chevron-right align-middle client-message-prefix" style="font-size: 0.75rem; margin-top: -0.256rem"></span>
|
||||||
|
</span>
|
||||||
<span class="text-muted @(Model.IsQuickMessage ? "font-weight-bold" : "")">
|
<span class="text-muted @(Model.IsQuickMessage ? "font-weight-bold" : "")">
|
||||||
@if (!Model.SentIngame)
|
@if (!Model.SentIngame)
|
||||||
{
|
{
|
||||||
<span>[<span class="text-primary">@ViewBag.Localization["WEBFRONT_PROFILE_MESSAGE_EXTERNAL"]</span>]</span>
|
<span>[<span class="text-primary">@ViewBag.Localization["WEBFRONT_PROFILE_MESSAGE_EXTERNAL"]</span>]</span>
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (Model.IsHidden && !ViewBag.Authorized)
|
@if (Model.IsHidden && !ViewBag.Authorized)
|
||||||
{
|
{
|
||||||
<color-code value="@SharedLibraryCore.Utilities.FormatExt(ViewBag.Localization["WEBFRONT_CLIENT_META_CHAT_HIDDEN"], Model.HiddenMessage)"></color-code>
|
<color-code value="@Utilities.FormatExt(ViewBag.Localization["WEBFRONT_CLIENT_META_CHAT_HIDDEN"], Model.HiddenMessage)"></color-code>
|
||||||
}
|
}
|
||||||
|
|
||||||
else
|
else
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
@model DateTime
|
||||||
|
@{ Layout = null;}
|
||||||
|
<div class="pt-5 text-light-dm text-dark-lm font-size-18 ">
|
||||||
|
<span>@Model.HumanizeForCurrentCulture()</span>
|
||||||
|
</div>
|
||||||
|
<hr/>
|
@ -1,29 +1,35 @@
|
|||||||
@model SharedLibraryCore.Dtos.Meta.Responses.PermissionLevelChangedResponse
|
@model SharedLibraryCore.Dtos.Meta.Responses.PermissionLevelChangedResponse
|
||||||
|
|
||||||
@foreach (var token in Utilities.SplitTranslationTokens("WEBFRONT_CLIENT_META_PERMISSION_CHANGED"))
|
<has-permission entity="ClientLevel" required-permission="Read">
|
||||||
{
|
@if (TempData["ShowMetaHeader"] as bool? ?? false)
|
||||||
if (token.IsInterpolation)
|
|
||||||
{
|
{
|
||||||
switch (token.MatchValue)
|
<partial name="./_MetaHeader.cshtml" for="@Model.When"/>
|
||||||
|
}
|
||||||
|
@foreach (var token in Utilities.SplitTranslationTokens("WEBFRONT_CLIENT_META_PERMISSION_CHANGED"))
|
||||||
|
{
|
||||||
|
if (token.IsInterpolation)
|
||||||
{
|
{
|
||||||
case "permission":
|
switch (token.MatchValue)
|
||||||
<span class="level-color-@((int)Model.CurrentPermissionLevel)">@Model.CurrentPermissionLevel.ToLocalizedLevelName()</span>
|
{
|
||||||
break;
|
case "permission":
|
||||||
case "originClient":
|
<span class="level-color-@((int)Model.CurrentPermissionLevel)">@Model.CurrentPermissionLevel.ToLocalizedLevelName()</span>
|
||||||
<span class="text-highlight">
|
break;
|
||||||
<a class="link-inverse" href="@Model.ChangedById">
|
case "originClient":
|
||||||
<color-code value="@Model.ChangedByName"></color-code>
|
<span class="text-highlight">
|
||||||
</a>
|
<a asp-controller="Client" asp-action="Profile" asp-route-id="@Model.ChangedById">
|
||||||
</span>
|
<color-code value="@Model.ChangedByName"></color-code>
|
||||||
break;
|
</a>
|
||||||
case "type":
|
</span>
|
||||||
<span class="text-white-50">@token.TranslationValue</span>
|
break;
|
||||||
break;
|
case "type":
|
||||||
|
<span class="text-white-50">@token.TranslationValue</span>
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span class="text-muted">@token.MatchValue</span>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
</has-permission>
|
||||||
else
|
|
||||||
{
|
|
||||||
<span class="text-muted">@token.MatchValue</span>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -2,72 +2,79 @@
|
|||||||
@model ReceivedPenaltyResponse
|
@model ReceivedPenaltyResponse
|
||||||
|
|
||||||
@{
|
@{
|
||||||
string localizationKey = $"WEBFRONT_CLIENT_META_WAS_PENALIZED_{Model.PenaltyType.ToString().ToUpper()}_V2";
|
var localizationKey = $"WEBFRONT_CLIENT_META_WAS_PENALIZED_{Model.PenaltyType.ToString().ToUpper()}_V2";
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="d-inline">
|
<has-permission entity="ClientLevel" required-permission="Read">
|
||||||
@foreach (var match in Utilities.SplitTranslationTokens(localizationKey))
|
@if (TempData["ShowMetaHeader"] as bool? ?? false)
|
||||||
{
|
{
|
||||||
if (match.IsInterpolation)
|
<partial name="./_MetaHeader.cshtml" for="@Model.When"/>
|
||||||
{
|
|
||||||
if (match.MatchValue == "action")
|
|
||||||
{
|
|
||||||
<span class="penalties-color-@Model.PenaltyType.ToString().ToLower()">@match.TranslationValue</span>
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (match.MatchValue == "punisher")
|
|
||||||
{
|
|
||||||
<span class="text-highlight">
|
|
||||||
<a class="link-inverse" href="@Model.PunisherClientId">
|
|
||||||
<color-code value="@Model.PunisherName"></color-code>
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (match.MatchValue == "reason")
|
|
||||||
{
|
|
||||||
<span class="text-white">
|
|
||||||
@if (ViewBag.Authorized && !string.IsNullOrEmpty(Model.AutomatedOffense) && Model.PenaltyType != Data.Models.EFPenalty.PenaltyType.Warning && Model.PenaltyType != Data.Models.EFPenalty.PenaltyType.Kick)
|
|
||||||
{
|
|
||||||
<span>@Utilities.FormatExt(ViewBag.Localization["WEBFRONT_PROFILE_ANTICHEAT_DETECTION"], Model.AutomatedOffense)</span>
|
|
||||||
<span class="oi oi-list-rich align-top text-primary automated-penalty-info-detailed" data-penalty-id="@Model.PenaltyId" style="margin-top: 0.125rem;" title="@ViewBag.Localization["WEBFRONT_CLIENT_META_AC_METRIC"]"></span>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<color-code value="@Model.Offense"></color-code>
|
|
||||||
}
|
|
||||||
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (match.MatchValue == "time")
|
|
||||||
{
|
|
||||||
<span class="text-white">@Model.LengthText</span>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<span>@match.MatchValue</span>
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (Model.ClientId != Model.OffenderClientId)
|
<div class="d-inline">
|
||||||
{
|
@foreach (var match in Utilities.SplitTranslationTokens(localizationKey))
|
||||||
<span>—</span>
|
|
||||||
@foreach (var helperResult in Utilities.SplitTranslationTokens("WEBFRONT_PROFILE_LINKED_ACCOUNT"))
|
|
||||||
{
|
{
|
||||||
if (!helperResult.IsInterpolation)
|
if (match.IsInterpolation)
|
||||||
{
|
{
|
||||||
<span>@helperResult.MatchValue</span>
|
if (match.MatchValue == "action")
|
||||||
|
{
|
||||||
|
<span class="penalties-color-@Model.PenaltyType.ToString().ToLower()">@match.TranslationValue</span>
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (match.MatchValue == "punisher")
|
||||||
|
{
|
||||||
|
<span class="text-highlight">
|
||||||
|
<a asp-action="Profile" asp-controller="Client" asp-route-id="@Model.PunisherClientId">
|
||||||
|
<color-code value="@Model.PunisherName"></color-code>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (match.MatchValue == "reason")
|
||||||
|
{
|
||||||
|
<span class="text-white-dm text-black-lm">
|
||||||
|
@if (ViewBag.Authorized && !string.IsNullOrEmpty(Model.AutomatedOffense) && Model.PenaltyType != Data.Models.EFPenalty.PenaltyType.Warning && Model.PenaltyType != Data.Models.EFPenalty.PenaltyType.Kick)
|
||||||
|
{
|
||||||
|
<span>@Utilities.FormatExt(ViewBag.Localization["WEBFRONT_PROFILE_ANTICHEAT_DETECTION"], Model.AutomatedOffense)</span>
|
||||||
|
<span class="oi oi-list-rich align-top text-primary automated-penalty-info-detailed" data-penalty-id="@Model.PenaltyId" style="margin-top: 0.125rem;" title="@ViewBag.Localization["WEBFRONT_CLIENT_META_AC_METRIC"]"></span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<color-code value="@Model.Offense"></color-code>
|
||||||
|
}
|
||||||
|
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (match.MatchValue == "time")
|
||||||
|
{
|
||||||
|
<span class="text-light-dm text-dark-lm">@Model.LengthText</span>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<a class="link-inverse" href="@Model.OffenderClientId">
|
<span>@match.MatchValue</span>
|
||||||
<color-code value="@Model.OffenderName"></color-code>
|
|
||||||
</a>
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
</div>
|
@if (Model.ClientId != Model.OffenderClientId)
|
||||||
|
{
|
||||||
|
<span>—</span>
|
||||||
|
@foreach (var helperResult in Utilities.SplitTranslationTokens("WEBFRONT_PROFILE_LINKED_ACCOUNT"))
|
||||||
|
{
|
||||||
|
if (!helperResult.IsInterpolation)
|
||||||
|
{
|
||||||
|
<span>@helperResult.MatchValue</span>
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<a class="link-inverse" href="@Model.OffenderClientId">
|
||||||
|
<color-code value="@Model.OffenderName"></color-code>
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</has-permission>
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user