Compare commits
33 Commits
2022.04.20
...
2022.06.02
Author | SHA1 | Date | |
---|---|---|---|
0f9f4f597b | |||
880f9333d9 | |||
31da5d352e | |||
83a469cae3 | |||
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 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -224,7 +224,6 @@ bootstrap-custom.min.css
|
||||
bootstrap-custom.css
|
||||
**/Master/static
|
||||
**/Master/dev_env
|
||||
/WebfrontCore/Views/Plugins/*
|
||||
/WebfrontCore/wwwroot/**/dds
|
||||
/WebfrontCore/wwwroot/images/radar/*
|
||||
|
||||
|
@ -431,6 +431,7 @@ namespace IW4MAdmin
|
||||
Clients[E.Origin.ClientNumber] = E.Origin;
|
||||
try
|
||||
{
|
||||
E.Origin.GameName = (Reference.Game?)GameName;
|
||||
E.Origin = await OnClientConnected(E.Origin);
|
||||
E.Target = E.Origin;
|
||||
}
|
||||
@ -572,10 +573,8 @@ namespace IW4MAdmin
|
||||
Time = DateTime.UtcNow
|
||||
});
|
||||
|
||||
await _metaService.SetPersistentMeta("LastMapPlayed", CurrentMap.Alias, E.Origin.ClientId,
|
||||
Manager.CancellationToken);
|
||||
await _metaService.SetPersistentMeta("LastServerPlayed", E.Owner.Hostname, E.Origin.ClientId,
|
||||
Manager.CancellationToken);
|
||||
await _metaService.SetPersistentMeta("LastMapPlayed", CurrentMap.Alias, E.Origin.ClientId);
|
||||
await _metaService.SetPersistentMeta("LastServerPlayed", E.Owner.Hostname, E.Origin.ClientId);
|
||||
}
|
||||
|
||||
else if (E.Type == GameEvent.EventType.PreDisconnect)
|
||||
@ -952,6 +951,8 @@ namespace IW4MAdmin
|
||||
!string.IsNullOrEmpty(client.Name) && (client.Ping != 999 || client.IsBot)))
|
||||
{
|
||||
client.CurrentServer = this;
|
||||
client.GameName = (Reference.Game?)GameName;
|
||||
|
||||
var e = new GameEvent
|
||||
{
|
||||
Type = GameEvent.EventType.PreConnect,
|
||||
@ -959,7 +960,7 @@ namespace IW4MAdmin
|
||||
Owner = this,
|
||||
IsBlocking = true,
|
||||
Extra = client.GetAdditionalProperty<string>("BotGuid"),
|
||||
Source = GameEvent.EventSource.Status
|
||||
Source = GameEvent.EventSource.Status,
|
||||
};
|
||||
|
||||
Manager.AddEvent(e);
|
||||
|
@ -456,7 +456,7 @@ namespace IW4MAdmin.Application.Misc
|
||||
|
||||
private void GetDvarAsync(Server server, string dvarName, Delegate onCompleted)
|
||||
{
|
||||
Task.Run<Task>(async () =>
|
||||
Task.Run(() =>
|
||||
{
|
||||
var tokenSource = new CancellationTokenSource();
|
||||
tokenSource.CancelAfter(TimeSpan.FromSeconds(5));
|
||||
@ -464,14 +464,14 @@ namespace IW4MAdmin.Application.Misc
|
||||
var success = true;
|
||||
try
|
||||
{
|
||||
result = (await server.GetDvarAsync<string>(dvarName, token: tokenSource.Token)).Value;
|
||||
result = server.GetDvarAsync<string>(dvarName, token: tokenSource.Token).GetAwaiter().GetResult().Value;
|
||||
}
|
||||
catch
|
||||
{
|
||||
success = false;
|
||||
}
|
||||
|
||||
await _onProcessing.WaitAsync();
|
||||
_onProcessing.Wait();
|
||||
try
|
||||
{
|
||||
onCompleted.DynamicInvoke(JsValue.Undefined,
|
||||
@ -495,7 +495,7 @@ namespace IW4MAdmin.Application.Misc
|
||||
}
|
||||
private void SetDvarAsync(Server server, string dvarName, string dvarValue, Delegate onCompleted)
|
||||
{
|
||||
Task.Run<Task>(async () =>
|
||||
Task.Run(() =>
|
||||
{
|
||||
var tokenSource = new CancellationTokenSource();
|
||||
tokenSource.CancelAfter(TimeSpan.FromSeconds(5));
|
||||
@ -503,14 +503,14 @@ namespace IW4MAdmin.Application.Misc
|
||||
|
||||
try
|
||||
{
|
||||
await server.SetDvarAsync(dvarName, dvarValue, tokenSource.Token);
|
||||
server.SetDvarAsync(dvarName, dvarValue, tokenSource.Token).GetAwaiter().GetResult();
|
||||
}
|
||||
catch
|
||||
{
|
||||
success = false;
|
||||
}
|
||||
|
||||
await _onProcessing.WaitAsync();
|
||||
_onProcessing.Wait();
|
||||
try
|
||||
{
|
||||
onCompleted.DynamicInvoke(JsValue.Undefined,
|
||||
|
@ -80,6 +80,7 @@ namespace IW4MAdmin.Application.RConParsers
|
||||
|
||||
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);
|
||||
return response.Where(item => item != Configuration.CommandPrefixes.RConResponse).ToArray();
|
||||
}
|
||||
|
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")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<int?>("GameName")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("LastConnection")
|
||||
.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")
|
||||
.HasColumnType("timestamp without time zone");
|
||||
|
||||
b.Property<int?>("GameName")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<DateTime>("LastConnection")
|
||||
.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")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("GameName")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("LastConnection")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
@ -805,7 +808,7 @@ namespace Data.Migrations.Sqlite
|
||||
b.Property<string>("SearchableIPAddress")
|
||||
.ValueGeneratedOnAddOrUpdate()
|
||||
.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")
|
||||
.HasMaxLength(24)
|
||||
|
@ -63,6 +63,7 @@ namespace Data.Models.Client
|
||||
public DateTime FirstConnection { get; set; }
|
||||
[Required]
|
||||
public DateTime LastConnection { get; set; }
|
||||
public Reference.Game? GameName { get; set; } = Reference.Game.UKN;
|
||||
public bool Masked { get; set; }
|
||||
[Required]
|
||||
public int AliasLinkId { get; set; }
|
||||
|
@ -55,7 +55,7 @@ steps:
|
||||
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-override.scss
|
||||
cd lib\open-iconic\font\css
|
||||
(Get-Content open-iconic-bootstrap-override.scss).replace('../fonts/', '/fonts/') | Set-Content open-iconic-bootstrap-override.scss
|
||||
(Get-Content open-iconic-bootstrap-override.scss).replace('../fonts/', '/font/') | Set-Content open-iconic-bootstrap-override.scss
|
||||
failOnStderr: true
|
||||
workingDirectory: '$(Build.Repository.LocalPath)\WebfrontCore\wwwroot'
|
||||
|
||||
|
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 maps\mp\_utility;
|
||||
#include maps\mp\gametypes\_hud_util;
|
||||
#include maps\mp\gametypes\_playerlogic;
|
||||
|
||||
init()
|
||||
{
|
||||
@ -12,6 +11,7 @@ init()
|
||||
level.eventBus.failKey = "fail";
|
||||
level.eventBus.timeoutKey = "timeout";
|
||||
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";
|
||||
|
||||
@ -23,6 +23,8 @@ init()
|
||||
level.eventTypes.setClientDataCompleted = "SetClientDataCompleted";
|
||||
level.eventTypes.executeCommandRequested = "ExecuteCommandRequested";
|
||||
|
||||
level.iw4adminIntegrationDebug = false;
|
||||
|
||||
SetDvarIfUninitialized( level.eventBus.inVar, "" );
|
||||
SetDvarIfUninitialized( level.eventBus.outVar, "" );
|
||||
SetDvarIfUninitialized( "sv_iw4madmin_integration_enabled", 1 );
|
||||
@ -34,17 +36,25 @@ init()
|
||||
level.eventCallbacks[level.eventTypes.executeCommandRequested] = ::OnExecuteCommand;
|
||||
level.eventCallbacks[level.eventTypes.setClientDataCompleted] = ::OnSetClientDataCompleted;
|
||||
|
||||
level.clientCommandCallbacks = [];
|
||||
level.clientCommandRusAsTarget = [];
|
||||
|
||||
if ( GetDvarInt( "sv_iw4madmin_integration_enabled" ) != 1 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
InitializeGameMethods();
|
||||
RegisterClientCommands();
|
||||
|
||||
// start long running tasks
|
||||
level thread MonitorClientEvents();
|
||||
level thread MonitorBus();
|
||||
level thread OnPlayerConnect();
|
||||
}
|
||||
|
||||
|
||||
|
||||
//////////////////////////////////
|
||||
// Client Methods
|
||||
//////////////////////////////////
|
||||
@ -59,6 +69,12 @@ OnPlayerConnect()
|
||||
|
||||
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] ) )
|
||||
{
|
||||
player.pers[level.clientDataKey] = spawnstruct();
|
||||
@ -101,26 +117,26 @@ OnPlayerDisconnect()
|
||||
|
||||
OnPlayerJoinedTeam()
|
||||
{
|
||||
self endon( "disconnect" );
|
||||
self endon( "disconnect" );
|
||||
|
||||
for( ;; )
|
||||
{
|
||||
self waittill( "joined_team" );
|
||||
for( ;; )
|
||||
{
|
||||
self waittill( "joined_team" );
|
||||
// join spec and join team occur at the same moment - out of order logging would be problematic
|
||||
wait( 0.25 );
|
||||
LogPrint( GenerateJoinTeamString( false ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OnPlayerJoinedSpectators()
|
||||
{
|
||||
self endon( "disconnect" );
|
||||
self endon( "disconnect" );
|
||||
|
||||
for( ;; )
|
||||
{
|
||||
for( ;; )
|
||||
{
|
||||
self waittill( "joined_spectators" );
|
||||
LogPrint( GenerateJoinTeamString( true ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OnGameEnded()
|
||||
@ -201,7 +217,7 @@ MonitorClientEvents()
|
||||
|
||||
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];
|
||||
@ -219,6 +235,53 @@ MonitorClientEvents()
|
||||
// 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 )
|
||||
{
|
||||
getClientMetaEvent = BuildEventRequest( true, level.eventTypes.clientDataRequested, "Meta", self, metaKey );
|
||||
@ -482,14 +545,65 @@ NotifyClientEvent( eventInfo )
|
||||
if ( level.iw4adminIntegrationDebug == 1 )
|
||||
{
|
||||
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;
|
||||
|
||||
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
|
||||
/////////////////////////////////
|
||||
@ -536,45 +650,18 @@ OnExecuteCommand( event )
|
||||
data = ParseDataString( event.data );
|
||||
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":
|
||||
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;
|
||||
IPrintLn( "Unkown Client command->" + event.subtype);
|
||||
}
|
||||
|
||||
// send back the response to the origin, but only if they're not the target
|
||||
@ -597,7 +684,7 @@ OnSetClientDataCompleted( event )
|
||||
// Command Implementations
|
||||
/////////////////////////////////
|
||||
|
||||
GiveWeaponImpl( data )
|
||||
GiveWeaponImpl( event, data )
|
||||
{
|
||||
if ( !IsAlive( self ) )
|
||||
{
|
||||
@ -628,7 +715,7 @@ TeamSwitchImpl()
|
||||
{
|
||||
if ( !IsAlive( self ) )
|
||||
{
|
||||
return self.name + "^7 is not alive";
|
||||
return self + "^7 is not alive";
|
||||
}
|
||||
|
||||
team = level.allies;
|
||||
@ -645,6 +732,93 @@ TeamSwitchImpl()
|
||||
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()
|
||||
{
|
||||
if ( !IsAlive( self ) )
|
||||
@ -657,18 +831,17 @@ HideImpl()
|
||||
self SetClientDvar( "cg_thirdperson", 1 );
|
||||
self SetClientDvar( "sv_cheats", 0 );
|
||||
|
||||
if ( !IsDefined( self.savedHealth ) || self.health < 1000 )
|
||||
if ( !IsDefined( self.savedHealth ) || self.health < 1000 )
|
||||
{
|
||||
self.savedHealth = self.health;
|
||||
self.savedMaxHealth = self.maxhealth;
|
||||
}
|
||||
|
||||
self.maxhealth = 99999;
|
||||
self.health = 99999;
|
||||
self.isHidden = true;
|
||||
|
||||
self call [[level.overrideMethods["god"]]]( true );
|
||||
self Hide();
|
||||
|
||||
self.isHidden = true;
|
||||
|
||||
self IPrintLnBold( "You are now ^5hidden ^7from other players" );
|
||||
}
|
||||
|
||||
@ -690,21 +863,38 @@ UnhideImpl()
|
||||
self SetClientDvar( "cg_thirdperson", 0 );
|
||||
self SetClientDvar( "sv_cheats", 0 );
|
||||
|
||||
self.health = self.savedHealth;
|
||||
self.maxhealth = self.savedMaxHealth;
|
||||
self call [[level.overrideMethods["god"]]]( false );
|
||||
self Show();
|
||||
|
||||
self.isHidden = false;
|
||||
|
||||
self Show();
|
||||
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;
|
||||
}
|
||||
|
||||
GotoImpl( data )
|
||||
GotoImpl( event, data )
|
||||
{
|
||||
if ( IsDefined( event.target ) )
|
||||
{
|
||||
return self GotoPlayerImpl( event.target );
|
||||
}
|
||||
else
|
||||
{
|
||||
return self GotoCoordImpl( data );
|
||||
}
|
||||
}
|
||||
|
||||
GotoCoordImpl( data )
|
||||
{
|
||||
if ( !IsAlive( self ) )
|
||||
{
|
||||
@ -729,6 +919,18 @@ GotoPlayerImpl( target )
|
||||
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()
|
||||
{
|
||||
if ( !IsAlive( self ) )
|
||||
@ -797,3 +999,48 @@ SetSpectatorImpl()
|
||||
|
||||
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
|
||||
DeploymentFiles\UpdateIW4MAdmin.ps1 = DeploymentFiles\UpdateIW4MAdmin.ps1
|
||||
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
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharedLibraryCore", "SharedLibraryCore\SharedLibraryCore.csproj", "{AA0541A2-8D51-4AD9-B0AC-3D1F5B162481}"
|
||||
@ -52,6 +52,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ScriptPlugins", "ScriptPlug
|
||||
Plugins\ScriptPlugins\ParserPlutoniumT4COZM.js = Plugins\ScriptPlugins\ParserPlutoniumT4COZM.js
|
||||
Plugins\ScriptPlugins\GameInterface.js = Plugins\ScriptPlugins\GameInterface.js
|
||||
Plugins\ScriptPlugins\SubnetBan.js = Plugins\ScriptPlugins\SubnetBan.js
|
||||
Plugins\ScriptPlugins\BanBroadcasting.js = Plugins\ScriptPlugins\BanBroadcasting.js
|
||||
EndProjectSection
|
||||
EndProject
|
||||
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 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;
|
||||
_gameEncoding = gameEncoding;
|
||||
@ -66,6 +67,11 @@ namespace Integrations.Cod
|
||||
}
|
||||
finally
|
||||
{
|
||||
using (LogContext.PushProperty("Server", Endpoint.ToString()))
|
||||
{
|
||||
_log.LogDebug("Releasing OnComplete {Count}", ActiveQueries[Endpoint].OnComplete.CurrentCount);
|
||||
}
|
||||
|
||||
if (ActiveQueries[Endpoint].OnComplete.CurrentCount == 0)
|
||||
{
|
||||
ActiveQueries[Endpoint].OnComplete.Release();
|
||||
@ -73,14 +79,19 @@ namespace Integrations.Cod
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
{
|
||||
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);
|
||||
|
||||
@ -91,6 +102,8 @@ namespace Integrations.Cod
|
||||
}
|
||||
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");
|
||||
}
|
||||
|
||||
@ -100,16 +113,20 @@ namespace Integrations.Cod
|
||||
{
|
||||
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)
|
||||
{
|
||||
_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");
|
||||
}
|
||||
}
|
||||
|
||||
_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;
|
||||
var waitForResponse = _config.WaitForResponse;
|
||||
@ -163,7 +180,6 @@ namespace Integrations.Cod
|
||||
// e.g: emoji -> windows-1252
|
||||
catch (OverflowException ex)
|
||||
{
|
||||
|
||||
using (LogContext.PushProperty("Server", Endpoint.ToString()))
|
||||
{
|
||||
_log.LogError(ex, "Could not convert RCon data payload to desired encoding {Encoding} {Params}",
|
||||
@ -201,6 +217,7 @@ namespace Integrations.Cod
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
_log.LogDebug("OnSent did not complete in time");
|
||||
throw new RConException("Timed out waiting for access to RCon send socket");
|
||||
}
|
||||
|
||||
@ -211,14 +228,13 @@ namespace Integrations.Cod
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw new RConException("Timed out waiting for access to RCon receive socket");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_log.LogDebug("OnReceived did not complete in time");
|
||||
if (connectionState.OnSentData.CurrentCount == 0)
|
||||
{
|
||||
connectionState.OnSentData.Release();
|
||||
}
|
||||
|
||||
throw new RConException("Timed out waiting for access to RCon receive socket");
|
||||
}
|
||||
|
||||
connectionState.SendEventArgs.UserToken = new ConnectionUserToken
|
||||
@ -242,6 +258,7 @@ namespace Integrations.Cod
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
@ -252,6 +269,7 @@ namespace Integrations.Cod
|
||||
{
|
||||
// if we timed out due to the cancellation token,
|
||||
// we don't want to count that as an attempt
|
||||
_log.LogDebug("OperationCanceledException when waiting for payload send to complete");
|
||||
connectionState.ConnectionAttempts = 0;
|
||||
}
|
||||
catch
|
||||
@ -265,7 +283,8 @@ namespace Integrations.Cod
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
return Array.Empty<string>();
|
||||
_log.LogDebug("OperationCancelled while waiting for retry");
|
||||
throw;
|
||||
}
|
||||
|
||||
goto retrySend;
|
||||
@ -311,11 +330,13 @@ namespace Integrations.Cod
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
|
||||
var responseString = type == StaticHelpers.QueryType.COMMAND_STATUS ?
|
||||
ReassembleSegmentedStatus(response) : RecombineMessages(response);
|
||||
var responseString = type == StaticHelpers.QueryType.COMMAND_STATUS
|
||||
? ReassembleSegmentedStatus(response)
|
||||
: RecombineMessages(response);
|
||||
|
||||
// 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"]);
|
||||
}
|
||||
@ -327,11 +348,14 @@ namespace Integrations.Cod
|
||||
|
||||
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 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)
|
||||
{
|
||||
@ -369,7 +393,8 @@ namespace Integrations.Cod
|
||||
|
||||
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, "");
|
||||
}
|
||||
|
||||
builder.Append(message);
|
||||
}
|
||||
|
||||
builder.Append('\n');
|
||||
return builder.ToString();
|
||||
}
|
||||
@ -410,6 +437,7 @@ namespace Integrations.Cod
|
||||
|
||||
if (rconSocket is null)
|
||||
{
|
||||
_log.LogDebug("Invalid state");
|
||||
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
|
||||
connectionState.SendEventArgs.Completed += OnDataSent;
|
||||
connectionState.ReceiveEventArgs.Completed += OnDataReceived;
|
||||
connectionState.ReceiveEventArgs.UserToken = connectionState.SendEventArgs.UserToken;
|
||||
connectionState.SendEventArgs.RemoteEndPoint = Endpoint;
|
||||
connectionState.ReceiveEventArgs.RemoteEndPoint = Endpoint;
|
||||
connectionState.ReceiveEventArgs.DisconnectReuseSocket = true;
|
||||
@ -438,7 +467,7 @@ namespace Integrations.Cod
|
||||
|
||||
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}",
|
||||
connectionState.ConnectionAttempts);
|
||||
@ -461,7 +490,8 @@ namespace Integrations.Cod
|
||||
|
||||
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;
|
||||
|
||||
@ -493,6 +523,7 @@ namespace Integrations.Cod
|
||||
}
|
||||
|
||||
rconSocket.Close();
|
||||
_log.LogDebug("OnDataReceived did not complete in allocated time");
|
||||
throw new NetworkException("Timed out receiving RCon response", rconSocket);
|
||||
}
|
||||
}
|
||||
@ -521,7 +552,8 @@ namespace Integrations.Cod
|
||||
|
||||
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
|
||||
if (e.BytesTransferred == 0)
|
||||
@ -638,7 +670,8 @@ namespace Integrations.Cod
|
||||
|
||||
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;
|
||||
try
|
||||
|
@ -20,7 +20,7 @@ namespace Integrations.Cod
|
||||
public int ConnectionAttempts { get; set; }
|
||||
private const int BufferSize = 16384;
|
||||
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 OnReceivedData = new (1, 1);
|
||||
|
||||
|
@ -15,7 +15,8 @@ namespace LiveRadar.Web.Controllers
|
||||
private static LiveRadarConfiguration _config;
|
||||
private readonly IConfigurationHandler<LiveRadarConfiguration> _configurationHandler;
|
||||
|
||||
public RadarController(IManager manager, IConfigurationHandlerFactory configurationHandlerFactory) : base(manager)
|
||||
public RadarController(IManager manager, IConfigurationHandlerFactory configurationHandlerFactory) :
|
||||
base(manager)
|
||||
{
|
||||
_manager = manager;
|
||||
_configurationHandler =
|
||||
@ -23,10 +24,10 @@ namespace LiveRadar.Web.Controllers
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("Radar/{serverId}")]
|
||||
[Route("Radar/{serverId?}")]
|
||||
public IActionResult Index(string serverId = null)
|
||||
{
|
||||
var servers = _manager.GetServers()
|
||||
var servers = _manager.GetServers()
|
||||
.Where(server => server.GameName == Server.Game.IW4)
|
||||
.Select(server => new ServerInfo
|
||||
{
|
||||
@ -44,9 +45,11 @@ namespace LiveRadar.Web.Controllers
|
||||
|
||||
[HttpGet]
|
||||
[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)
|
||||
{
|
||||
@ -59,7 +62,7 @@ namespace LiveRadar.Web.Controllers
|
||||
_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)
|
||||
{
|
||||
@ -74,27 +77,21 @@ namespace LiveRadar.Web.Controllers
|
||||
[HttpGet]
|
||||
[Route("Radar/{serverId}/Data")]
|
||||
[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 radarInfo = server.GetClientsAsList().Select(_client => _client.GetAdditionalProperty<RadarEvent>("LiveRadar")).ToList();
|
||||
return Json(radarInfo);
|
||||
}
|
||||
var server = serverId == null
|
||||
? _manager.GetServers().FirstOrDefault()
|
||||
: _manager.GetServers().FirstOrDefault(server => server.ToString() == serverId);
|
||||
|
||||
[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)
|
||||
if (server == null)
|
||||
{
|
||||
radarUpdate.Name = client.Name.StripColors();
|
||||
client.SetAdditionalProperty("LiveRadar", radarUpdate);
|
||||
}*/
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return Ok();
|
||||
var radarInfo = server.GetClientsAsList()
|
||||
.Select(client => client.GetAdditionalProperty<RadarEvent>("LiveRadar")).ToList();
|
||||
|
||||
return Json(radarInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ namespace LiveRadar
|
||||
S.CustomCallback &&
|
||||
!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;
|
||||
}
|
||||
}
|
||||
@ -77,7 +77,15 @@ namespace LiveRadar
|
||||
|
||||
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]
|
||||
: (E.Extra.ToString() ?? "0").ConvertGuidToLong(NumberStyles.HexNumber);
|
||||
}
|
||||
|
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',
|
||||
required: true
|
||||
}],
|
||||
supportedGames: ['IW4'],
|
||||
supportedGames: ['IW4', 'IW5'],
|
||||
execute: (gameEvent) => {
|
||||
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
|
||||
return;
|
||||
@ -103,7 +103,7 @@ let commands = [{
|
||||
name: 'player',
|
||||
required: true
|
||||
}],
|
||||
supportedGames: ['IW4'],
|
||||
supportedGames: ['IW4', 'IW5'],
|
||||
execute: (gameEvent) => {
|
||||
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
|
||||
return;
|
||||
@ -121,7 +121,7 @@ let commands = [{
|
||||
name: 'player',
|
||||
required: true
|
||||
}],
|
||||
supportedGames: ['IW4'],
|
||||
supportedGames: ['IW4', 'IW5'],
|
||||
execute: (gameEvent) => {
|
||||
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
|
||||
return;
|
||||
@ -129,6 +129,72 @@ let commands = [{
|
||||
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',
|
||||
description: 'hide yourself ingame',
|
||||
@ -136,7 +202,7 @@ let commands = [{
|
||||
permission: 'SeniorAdmin',
|
||||
targetRequired: false,
|
||||
arguments: [],
|
||||
supportedGames: ['IW4'],
|
||||
supportedGames: ['IW4', 'IW5'],
|
||||
execute: (gameEvent) => {
|
||||
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
|
||||
return;
|
||||
@ -151,7 +217,7 @@ let commands = [{
|
||||
permission: 'SeniorAdmin',
|
||||
targetRequired: false,
|
||||
arguments: [],
|
||||
supportedGames: ['IW4'],
|
||||
supportedGames: ['IW4', 'IW5'],
|
||||
execute: (gameEvent) => {
|
||||
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
|
||||
return;
|
||||
@ -173,7 +239,7 @@ let commands = [{
|
||||
name: 'message',
|
||||
required: true
|
||||
}],
|
||||
supportedGames: ['IW4'],
|
||||
supportedGames: ['IW4', 'IW5'],
|
||||
execute: (gameEvent) => {
|
||||
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
|
||||
return;
|
||||
@ -194,7 +260,7 @@ let commands = [{
|
||||
name: 'player',
|
||||
required: true
|
||||
}],
|
||||
supportedGames: ['IW4'],
|
||||
supportedGames: ['IW4', 'IW5'],
|
||||
execute: (gameEvent) => {
|
||||
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
|
||||
return;
|
||||
@ -202,6 +268,24 @@ let commands = [{
|
||||
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',
|
||||
description: 'teleport to a position',
|
||||
@ -220,7 +304,7 @@ let commands = [{
|
||||
name: 'z',
|
||||
required: true
|
||||
}],
|
||||
supportedGames: ['IW4'],
|
||||
supportedGames: ['IW4', 'IW5'],
|
||||
execute: (gameEvent) => {
|
||||
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
|
||||
return;
|
||||
@ -244,7 +328,7 @@ let commands = [{
|
||||
name: 'player',
|
||||
required: true
|
||||
}],
|
||||
supportedGames: ['IW4'],
|
||||
supportedGames: ['IW4', 'IW5'],
|
||||
execute: (gameEvent) => {
|
||||
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
|
||||
return;
|
||||
@ -259,7 +343,7 @@ let commands = [{
|
||||
permission: 'SeniorAdmin',
|
||||
targetRequired: false,
|
||||
arguments: [],
|
||||
supportedGames: ['IW4'],
|
||||
supportedGames: ['IW4', 'IW5'],
|
||||
execute: (gameEvent) => {
|
||||
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
|
||||
return;
|
||||
@ -277,7 +361,7 @@ let commands = [{
|
||||
name: 'player',
|
||||
required: true
|
||||
}],
|
||||
supportedGames: ['IW4'],
|
||||
supportedGames: ['IW4', 'IW5'],
|
||||
execute: (gameEvent) => {
|
||||
if (!validateEnabled(gameEvent.Owner, gameEvent.Origin)) {
|
||||
return;
|
||||
@ -482,7 +566,7 @@ const pollForEvents = server => {
|
||||
if (!state.waitingOnOutput) {
|
||||
if (state.queuedMessages.length === 0) {
|
||||
logger.WriteDebug('No messages in queue');
|
||||
return;``
|
||||
return;
|
||||
}
|
||||
|
||||
state.waitingOnOutput = true;
|
||||
|
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) {}
|
||||
};
|
@ -10,5 +10,6 @@ namespace SharedLibraryCore.Dtos
|
||||
public int LinkId { get; set; }
|
||||
public EFClient.Permission Level { get; set; }
|
||||
public DateTime LastConnection { get; set; }
|
||||
public bool IsMasked { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ namespace SharedLibraryCore.Dtos
|
||||
public int MaxClients { get; set; }
|
||||
public List<ChatInfo> ChatHistory { get; set; }
|
||||
public List<PlayerInfo> Players { get; set; }
|
||||
public List<Report> Reports { get; set; }
|
||||
public ClientHistoryInfo ClientHistory { get; set; }
|
||||
public long ID { get; set; }
|
||||
public bool Online { get; set; }
|
||||
|
@ -626,6 +626,8 @@ namespace SharedLibraryCore.Database.Models
|
||||
Utilities.DefaultLogger.LogInformation("Client {client} is joining the game from {source}", ToString(),
|
||||
ipAddress.HasValue ? "Status" : "Log");
|
||||
|
||||
GameName = (Reference.Game)CurrentServer.GameName;
|
||||
|
||||
if (ipAddress != null)
|
||||
{
|
||||
IPAddress = ipAddress;
|
||||
|
@ -222,7 +222,7 @@ namespace SharedLibraryCore
|
||||
public GameEvent Broadcast(string message, EFClient sender = null)
|
||||
{
|
||||
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}",
|
||||
message.FormatMessageForEngine(RconParser.Configuration.ColorCodeMapping).StripColors());
|
||||
|
||||
@ -270,8 +270,6 @@ namespace SharedLibraryCore
|
||||
/// <param name="targetClient">EFClient to send message to</param>
|
||||
protected async Task Tell(string message, EFClient targetClient)
|
||||
{
|
||||
var engineMessage = message.FormatMessageForEngine(RconParser.Configuration.ColorCodeMapping);
|
||||
|
||||
if (!Utilities.IsDevelopment)
|
||||
{
|
||||
var temporalClientId = targetClient.GetAdditionalProperty<string>("ConnectionClientId");
|
||||
@ -280,7 +278,7 @@ namespace SharedLibraryCore
|
||||
|
||||
var formattedMessage = string.Format(RconParser.Configuration.CommandPrefixes.Tell,
|
||||
clientNumber,
|
||||
$"{(CustomSayEnabled && GameName == Game.IW4 ? $"{CustomSayName}: " : "")}{engineMessage}");
|
||||
$"{(CustomSayEnabled && GameName == Game.IW4 ? $"{CustomSayName}: " : "")}{message}");
|
||||
if (targetClient.ClientNumber > -1 && message.Length > 0 &&
|
||||
targetClient.Level != Data.Models.Client.EFClient.Permission.Console)
|
||||
{
|
||||
@ -296,13 +294,14 @@ namespace SharedLibraryCore
|
||||
if (targetClient.Level == Data.Models.Client.EFClient.Permission.Console)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
var cleanMessage = message.FormatMessageForEngine(RconParser.Configuration.ColorCodeMapping)
|
||||
.StripColors();
|
||||
using (LogContext.PushProperty("Server", ToString()))
|
||||
{
|
||||
ServerLogger.LogInformation("Command output received: {Message}",
|
||||
engineMessage.StripColors());
|
||||
ServerLogger.LogInformation("Command output received: {Message}", cleanMessage);
|
||||
}
|
||||
|
||||
Console.WriteLine(engineMessage.StripColors());
|
||||
Console.WriteLine(cleanMessage);
|
||||
Console.ForegroundColor = ConsoleColor.Gray;
|
||||
}
|
||||
}
|
||||
|
@ -47,13 +47,15 @@ namespace SharedLibraryCore.Services
|
||||
private readonly ApplicationConfiguration _appConfig;
|
||||
private readonly IDatabaseContextFactory _contextFactory;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IGeoLocationService _geoLocationService;
|
||||
|
||||
public ClientService(ILogger<ClientService> logger, IDatabaseContextFactory databaseContextFactory,
|
||||
ApplicationConfiguration appConfig)
|
||||
ApplicationConfiguration appConfig, IGeoLocationService geoLocationService)
|
||||
{
|
||||
_contextFactory = databaseContextFactory;
|
||||
_logger = logger;
|
||||
_appConfig = appConfig;
|
||||
_geoLocationService = geoLocationService;
|
||||
}
|
||||
|
||||
public async Task<EFClient> Create(EFClient entity)
|
||||
@ -101,7 +103,8 @@ namespace SharedLibraryCore.Services
|
||||
Level = Permission.User,
|
||||
FirstConnection = 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());
|
||||
@ -281,6 +284,8 @@ namespace SharedLibraryCore.Services
|
||||
entity.PasswordSalt = temporalClient.PasswordSalt;
|
||||
}
|
||||
|
||||
entity.GameName ??= temporalClient.GameName;
|
||||
|
||||
// update in database
|
||||
await context.SaveChangesAsync();
|
||||
return entity.ToPartialClient();
|
||||
@ -355,7 +360,8 @@ namespace SharedLibraryCore.Services
|
||||
Level = Permission.User,
|
||||
FirstConnection = DateTime.UtcNow,
|
||||
LastConnection = DateTime.UtcNow,
|
||||
NetworkId = entity.NetworkId
|
||||
NetworkId = entity.NetworkId,
|
||||
GameName = (Reference.Game)entity.CurrentServer.GameName
|
||||
};
|
||||
|
||||
if (existingAlias == null)
|
||||
@ -782,7 +788,8 @@ namespace SharedLibraryCore.Services
|
||||
Password = client.Password,
|
||||
PasswordSalt = client.PasswordSalt,
|
||||
NetworkId = client.NetworkId,
|
||||
LastConnection = client.LastConnection
|
||||
LastConnection = client.LastConnection,
|
||||
Masked = client.Masked
|
||||
};
|
||||
|
||||
return await iqClients.ToListAsync();
|
||||
@ -895,24 +902,32 @@ namespace SharedLibraryCore.Services
|
||||
/// gets the 10 most recently added clients to IW4MAdmin
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task<IList<PlayerInfo>> GetRecentClients()
|
||||
public async Task<IList<PlayerInfo>> GetRecentClients(PaginationRequest request)
|
||||
{
|
||||
var startOfPeriod = DateTime.UtcNow.AddHours(-24);
|
||||
|
||||
await using var context = _contextFactory.CreateContext(false);
|
||||
var iqClients = context.Clients
|
||||
.Where(_client => _client.CurrentAlias.IPAddress != null)
|
||||
.Where(_client => _client.FirstConnection >= startOfPeriod)
|
||||
.OrderByDescending(_client => _client.FirstConnection)
|
||||
.Select(_client => new PlayerInfo
|
||||
.Where(client => client.CurrentAlias.IPAddress != null)
|
||||
.Where(client => client.FirstConnection >= startOfPeriod)
|
||||
.OrderByDescending(client => client.FirstConnection)
|
||||
.Select(client => new PlayerInfo
|
||||
{
|
||||
ClientId = _client.ClientId,
|
||||
Name = _client.CurrentAlias.Name,
|
||||
IPAddress = _client.CurrentAlias.IPAddress.ConvertIPtoString(),
|
||||
LastConnection = _client.FirstConnection
|
||||
});
|
||||
ClientId = client.ClientId,
|
||||
Name = client.CurrentAlias.Name,
|
||||
IPAddress = client.CurrentAlias.IPAddress.ConvertIPtoString(),
|
||||
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
|
||||
|
@ -184,7 +184,7 @@ namespace SharedLibraryCore
|
||||
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" };
|
||||
|
@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Mvc;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Commands;
|
||||
using SharedLibraryCore.Configuration;
|
||||
using SharedLibraryCore.Dtos;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
using WebfrontCore.Permissions;
|
||||
using WebfrontCore.ViewModels;
|
||||
@ -314,18 +315,32 @@ namespace WebfrontCore.Controllers
|
||||
}));
|
||||
}
|
||||
|
||||
public async Task<IActionResult> RecentClientsForm()
|
||||
public async Task<IActionResult> RecentClientsForm(PaginationRequest request)
|
||||
{
|
||||
var clients = await Manager.GetClientService().GetRecentClients();
|
||||
foreach (var client in clients)
|
||||
ViewBag.First = request.Offset == 0;
|
||||
|
||||
if (request.Count > 20)
|
||||
{
|
||||
client.IPAddress =
|
||||
_appConfig.HasPermission(Client.Level, WebfrontEntity.ClientIPAddress, WebfrontPermission.Read)
|
||||
? client.IPAddress
|
||||
: null;
|
||||
request.Count = 20;
|
||||
}
|
||||
|
||||
return View("~/Views/Shared/Components/Client/_RecentClients.cshtml", clients);
|
||||
var clients = await Manager.GetClientService().GetRecentClients(request);
|
||||
|
||||
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()
|
||||
|
@ -176,11 +176,12 @@ namespace WebfrontCore.Controllers
|
||||
adminsDict.Add(admin.Level, new List<ClientInfo>());
|
||||
}
|
||||
|
||||
adminsDict[admin.Level].Add(new ClientInfo()
|
||||
adminsDict[admin.Level].Add(new ClientInfo
|
||||
{
|
||||
Name = admin.Name,
|
||||
ClientId = admin.ClientId,
|
||||
LastConnection = admin.LastConnection
|
||||
LastConnection = admin.LastConnection,
|
||||
IsMasked = admin.Masked
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,7 @@ public class SideContextMenuItem
|
||||
public string Reference { get; set; }
|
||||
public string Icon { get; set; }
|
||||
public string Tooltip { get; set; }
|
||||
public int? EntityId { get; set; }
|
||||
}
|
||||
|
||||
|
||||
|
@ -61,6 +61,6 @@
|
||||
}
|
||||
<div class="ml-auto">
|
||||
<button type="submit" class="btn btn-primary">@Model.ActionButtonLabel</button>
|
||||
<a href="#" class="btn mr-5" role="button">Close</a>
|
||||
<a href="#" class="btn mr-5" role="button" onclick="halfmoon.toggleModal('actionModal');">Close</a>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -23,9 +23,6 @@
|
||||
</div>
|
||||
|
||||
@section scripts {
|
||||
<environment include="Development">
|
||||
<script type="text/javascript" src="~/js/loader.js"></script>
|
||||
</environment>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
initLoader('/Admin/ListAuditLog', '#audit_log_table_body', @ViewBag.IntialOffset);
|
||||
|
@ -5,7 +5,8 @@
|
||||
|
||||
<!-- desktop -->
|
||||
<div class="content mt-0">
|
||||
<h2 class="content-title mt-20">@ViewBag.ResultCount result(s) for <span class="badge badge-primary font-size-18">@ViewBag.SearchTerm</span></h2>
|
||||
<h2 class="content-title mt-20 mb-0">Search Results</h2>
|
||||
<div class="text-muted mb-15"><span class="badge">@ViewBag.SearchTerm</span> returned <span class="text-primary">@ViewBag.ResultCount</span> matche(s)</div>
|
||||
|
||||
<table class="table d-none d-md-table">
|
||||
<thead>
|
||||
|
@ -9,7 +9,8 @@
|
||||
|
||||
else
|
||||
{
|
||||
<h2 class="content-title mt-20">@Html.Raw(Utilities.FormatExt(ViewBag.Localization["WEBFRONT_STATS_MESSAGES_FOUND"], $"<span class=\"badge badge-primary font-size-18\">{Model.TotalResultCount.ToString("N0")}</span>"))</h2>
|
||||
<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 bg-dark-dm bg-light-lm rounded" style="table-layout: fixed">
|
||||
<thead class="d-none d-lg-table-header-group">
|
||||
@ -30,9 +31,6 @@ else
|
||||
</div>
|
||||
|
||||
@section scripts {
|
||||
<environment include="Development">
|
||||
<script type="text/javascript" src="~/js/loader.js"></script>
|
||||
</environment>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
initLoader('/Message/FindNext?query=@ViewBag.Query', '#message_table_body', @Model.RetrievedResultCount, @ViewBag.QueryLimit);
|
||||
|
@ -1,7 +1,8 @@
|
||||
@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>
|
||||
|
||||
|
||||
@foreach (var key in Model.Keys)
|
||||
{
|
||||
<table class="table mb-20">
|
||||
@ -12,20 +13,34 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var client in Model[key].OrderByDescending(client => client.LastConnection))
|
||||
{
|
||||
<tr class="bg-dark-dm bg-light-lm">
|
||||
<td>
|
||||
<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>
|
||||
}
|
||||
<has-permission entity="ClientLevel" required-permission="Read">
|
||||
@foreach (var client in Model[key].OrderByDescending(client => client.LastConnection))
|
||||
{
|
||||
if (!ViewBag.Authorized && client.IsMasked)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
<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>
|
||||
}
|
||||
</has-permission>
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
|
||||
</div>
|
||||
|
@ -33,205 +33,217 @@
|
||||
|
||||
<div class="content row mt-20">
|
||||
<div class="col-12 col-lg-9 col-xl-10">
|
||||
@if (Model.ActivePenalty != null)
|
||||
{
|
||||
<has-permission entity="ClientLevel" required-permission="Read">
|
||||
<div class="alert @ClassForPenaltyType(Model.ActivePenalty.Type) mt-10 mb-10" role="alert">
|
||||
@foreach (var result in Utilities.SplitTranslationTokens(translationKey))
|
||||
@if (Model.ActivePenalty != null)
|
||||
{
|
||||
<has-permission entity="ClientLevel" required-permission="Read">
|
||||
<div class="alert @ClassForPenaltyType(Model.ActivePenalty.Type) mt-10 mb-10" role="alert">
|
||||
@foreach (var result in Utilities.SplitTranslationTokens(translationKey))
|
||||
{
|
||||
switch (result.MatchValue)
|
||||
{
|
||||
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;
|
||||
}
|
||||
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>
|
||||
</has-permission>
|
||||
}
|
||||
|
||||
<h2 class="content-title mb-10">Player Profile</h2>
|
||||
|
||||
<div id="profile_wrapper" class="mb-10 mt-10">
|
||||
|
||||
<!-- 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 class="profile-shortcode align-self-center text-dark-lm">@shortCode</div>
|
||||
}
|
||||
</div>
|
||||
</has-permission>
|
||||
}
|
||||
<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">
|
||||
<!-- name -->
|
||||
<div id="profile_name">
|
||||
<span class="font-size-20 font-weight-medium">
|
||||
<color-code value="@Model.Name"></color-code>
|
||||
</span>
|
||||
<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>
|
||||
|
||||
<h2 class="content-title mb-10">Player Profile</h2>
|
||||
|
||||
<div id="profile_wrapper" class="mb-10 mt-10">
|
||||
|
||||
<!-- 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-20 ml-0 mr-0 mt-0 mb-10 d-flex flex-fill flex-wrap flex-column flex-md-row justify-content-center">
|
||||
<div class="d-flex flex-column flex-md-row">
|
||||
<div id="profile_avatar" class="w-150 w-md-100 h-150 h-md-100 mt-10 mb-10 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 class="profile-shortcode align-self-center text-dark-lm">@shortCode</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>
|
||||
<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">
|
||||
<!-- name -->
|
||||
<div id="profile_name">
|
||||
<span class="font-size-20 font-weight-medium">
|
||||
<color-code value="@Model.Name"></color-code>
|
||||
</span>
|
||||
<has-permission entity="MetaAliasUpdate" required-permission="Read">
|
||||
<div class="dropdown dropright 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 @(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" 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>
|
||||
<!-- 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>
|
||||
<!-- 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>
|
||||
</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>
|
||||
</has-permission>
|
||||
</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>
|
||||
<!-- 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>
|
||||
</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 dropright with-arrow">
|
||||
<div data-toggle="dropdown" id="profileIPAddressHistory" aria-haspopup="true" aria-expanded="false">
|
||||
@if (Model.IPs.Any(ip => !string.IsNullOrEmpty(ip.Item1)))
|
||||
<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))
|
||||
{
|
||||
<i class="oi oi-caret-bottom font-size-12" aria-hidden="true"></i>
|
||||
continue;
|
||||
}
|
||||
</div>
|
||||
<div class="dropdown-menu" aria-labelledby="profileAliasHistory">
|
||||
@foreach (var (ip, dateAdded) in Model.IPs.OrderByDescending(ip => ip.Item2).Take(15))
|
||||
{
|
||||
<a asp-controller="Client" asp-action="Find" asp-route-clientName="@ip" class="dropdown-item" data-toggle="tooltip" data-title="@dateAdded.HumanizeForCurrentCulture()">
|
||||
<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>
|
||||
}
|
||||
@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>
|
||||
}
|
||||
@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>
|
||||
</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 dropleft 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))
|
||||
{
|
||||
<div class="ip-lookup-profile w-100 rounded align-self-center" style="height:5rem;background-image: url('https://flagcdn.com/w80/@(Model.GeoLocationInfo.CountryCode.ToLower()).png')" data-ip="@Model.IPAddress"></div>
|
||||
}
|
||||
</a>
|
||||
<div class="dropdown-menu dropdown-menu-center z-30" aria-labelledby="avatar-popover-toggle">
|
||||
<has-permission entity="ClientIPAddress" required-permission="Read">
|
||||
<h6 class="dropdown-header font-weight-bold">@Model.IPAddress</h6>
|
||||
<div class="dropdown-divider"></div>
|
||||
</has-permission>
|
||||
|
||||
<div class="dropdown-item geo-country">@Model.GeoLocationInfo.Country</div>
|
||||
@if (!string.IsNullOrEmpty(Model.GeoLocationInfo.Region))
|
||||
{
|
||||
<div class="dropdown-item text-muted geo-region">@Model.GeoLocationInfo.Region</div>
|
||||
<div class="dropdown-divider"></div>
|
||||
}
|
||||
@if (!string.IsNullOrEmpty(Model.GeoLocationInfo.Organization))
|
||||
{
|
||||
<div class="dropdown-item geo-organization">@Model.GeoLocationInfo.Organization</div>
|
||||
}
|
||||
</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))
|
||||
{
|
||||
<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>
|
||||
}
|
||||
</a>
|
||||
<div class="dropdown-menu dropdown-menu-center z-30" aria-labelledby="avatar-popover-toggle">
|
||||
<has-permission entity="ClientIPAddress" required-permission="Read">
|
||||
<h6 class="dropdown-header font-weight-bold">@Model.IPAddress</h6>
|
||||
<div class="dropdown-divider"></div>
|
||||
</has-permission>
|
||||
|
||||
<div class="dropdown-item geo-country">@Model.GeoLocationInfo.Country</div>
|
||||
@if (!string.IsNullOrEmpty(Model.GeoLocationInfo.Region))
|
||||
{
|
||||
<div class="dropdown-item text-muted geo-region">@Model.GeoLocationInfo.Region</div>
|
||||
<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>
|
||||
|
||||
<!-- 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>
|
||||
|
||||
<hr class="mt-10 mb-10"/>
|
||||
<hr class="mt-10 mb-10"/>
|
||||
|
||||
<!-- meta filter list -->
|
||||
<div class="mb-10 mt-10">
|
||||
@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)
|
||||
<!-- 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))
|
||||
{
|
||||
<div class="row d-md-flex pt-2">
|
||||
<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 })
|
||||
</div>
|
||||
</div>
|
||||
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>
|
||||
|
||||
<hr class="mt-10 mb-10"/>
|
||||
|
||||
<div class="text-center">
|
||||
<i id="loaderLoad" class="oi oi-chevron-bottom loader-load-more text-primary" aria-hidden="true"></i>
|
||||
@if (!ViewBag.Authorized && !ViewBag.EnablePrivilegedUserPrivacy || ViewBag.Authorized)
|
||||
{
|
||||
<div class="row d-md-flex pt-2">
|
||||
<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 })
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<hr class="mt-10 mb-10"/>
|
||||
|
||||
<div class="text-center">
|
||||
<i id="loaderLoad" class="oi oi-chevron-bottom loader-load-more text-primary" aria-hidden="true"></i>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@ -263,6 +275,7 @@
|
||||
IsButton = true,
|
||||
Reference = "edit",
|
||||
Icon = "oi-cog",
|
||||
EntityId = Model.ClientId
|
||||
});
|
||||
}
|
||||
|
||||
@ -282,7 +295,20 @@
|
||||
Title = isFlagged ? "Unflag" : "Flag",
|
||||
IsButton = true,
|
||||
Reference = isFlagged ? "unflag" : "flag",
|
||||
Icon = "oi-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
|
||||
});
|
||||
}
|
||||
|
||||
@ -294,6 +320,7 @@
|
||||
IsButton = true,
|
||||
Reference = "ban",
|
||||
Icon = "oi-lock-unlocked",
|
||||
EntityId = Model.ClientId
|
||||
});
|
||||
}
|
||||
|
||||
@ -305,6 +332,7 @@
|
||||
IsButton = true,
|
||||
Reference = "unban",
|
||||
Icon = "oi-lock-locked",
|
||||
EntityId = Model.ClientId
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -318,7 +346,6 @@
|
||||
|
||||
@section scripts {
|
||||
<environment include="Development">
|
||||
<script type="text/javascript" src="~/js/loader.js"></script>
|
||||
<script type="text/javascript" src="~/js/profile.js"></script>
|
||||
</environment>
|
||||
<script>initLoader('/Client/Meta/@Model.ClientId', '#profile_events', 30, 30, [{ 'name': 'metaFilterType', 'value': '@Model.MetaFilterType' }]);</script>
|
||||
|
@ -1,4 +1,5 @@
|
||||
@model IEnumerable<SharedLibraryCore.Dtos.Meta.Responses.InformationResponse>
|
||||
@using Humanizer
|
||||
@model IEnumerable<SharedLibraryCore.Dtos.Meta.Responses.InformationResponse>
|
||||
@{
|
||||
var informationMeta = Model
|
||||
.Where(meta => meta.Type == SharedLibraryCore.Interfaces.MetaType.Information)
|
||||
@ -7,12 +8,13 @@
|
||||
.GroupBy(meta => meta.index / 5);
|
||||
}
|
||||
|
||||
<div class="d-flex flex-wrap">
|
||||
@foreach (var metaColumn in informationMeta)
|
||||
{
|
||||
<div class="mr-20">
|
||||
<!-- <div class="mr-20"> -->
|
||||
@foreach (var meta in metaColumn)
|
||||
{
|
||||
<div class="profile-meta-entry font-size-12" data-toggle="@(!string.IsNullOrEmpty(meta.meta.ToolTipText) ? "tooltip" : "")" data-title="@meta.meta.ToolTipText" data-placement="bottom">
|
||||
<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.meta.Key);}
|
||||
|
||||
@ -22,22 +24,23 @@
|
||||
{
|
||||
if (result.IsInterpolation)
|
||||
{
|
||||
<span class="profile-meta-value text-primary"><color-code value="@meta.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
|
||||
{
|
||||
<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
|
||||
{
|
||||
<span class="profile-meta-value text-primary"><color-code value="@meta.meta.Value"></color-code></span>
|
||||
<span class="profile-meta-title text-muted">@meta.meta.Key</span>
|
||||
<div class="profile-meta-value text-primary font-size-14"><color-code value="@meta.meta.Value"></color-code></div>
|
||||
<div class="profile-meta-title text-muted font-size-12">@meta.meta.Key.Titleize()</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<!-- </div> -->
|
||||
}
|
||||
</div>
|
||||
|
@ -24,7 +24,7 @@
|
||||
else if (match.MatchValue == "punisher")
|
||||
{
|
||||
<span class="text-highlight">
|
||||
<a class="link-inverse" href="@Model.PunisherClientId">
|
||||
<a asp-action="Profile" asp-controller="Client" asp-route-id="@Model.PunisherClientId">
|
||||
<color-code value="@Model.PunisherName"></color-code>
|
||||
</a>
|
||||
</span>
|
||||
@ -32,7 +32,7 @@
|
||||
|
||||
else if (match.MatchValue == "reason")
|
||||
{
|
||||
<span class="text-light-dm text-dark-lm">
|
||||
<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>
|
||||
|
@ -41,7 +41,6 @@
|
||||
@section scripts
|
||||
{
|
||||
<environment include="Development">
|
||||
<script type="text/javascript" src="~/js/loader.js"></script>
|
||||
<script type="text/javascript" src="~/js/stats.js"></script>
|
||||
</environment>
|
||||
<script>initLoader('/Stats/GetTopPlayersAsync', '#topPlayersContainer', 25);</script>
|
||||
|
@ -86,7 +86,6 @@
|
||||
|
||||
@section scripts {
|
||||
<environment include="Development">
|
||||
<script type="text/javascript" src="~/js/loader.js"></script>
|
||||
<script type="text/javascript" src="~/js/penalty.js"></script>
|
||||
</environment>
|
||||
<script>
|
||||
|
@ -1,10 +1,15 @@
|
||||
@{
|
||||
Layout = null;
|
||||
|
||||
bool CanSeeLevel(PenaltyInfo penalty) => (ViewBag.PermissionsSet as IEnumerable<string>).HasPermission(WebfrontEntity.ClientLevel, WebfrontPermission.Read) || penalty.PenaltyType == EFPenalty.PenaltyType.Report;
|
||||
}
|
||||
@using WebfrontCore.Permissions
|
||||
@using SharedLibraryCore.Dtos
|
||||
@using Data.Models
|
||||
@model IList<SharedLibraryCore.Dtos.PenaltyInfo>
|
||||
|
||||
@{
|
||||
foreach (var penalty in Model)
|
||||
foreach (var penalty in Model.Where(CanSeeLevel))
|
||||
{
|
||||
await Html.RenderPartialAsync("_Penalty", penalty);
|
||||
}
|
||||
|
69
WebfrontCore/Views/Plugins/LiveRadar/Radar/Index.cshtml
Normal file
69
WebfrontCore/Views/Plugins/LiveRadar/Radar/Index.cshtml
Normal file
@ -0,0 +1,69 @@
|
||||
@using WebfrontCore.ViewModels
|
||||
@model IEnumerable<SharedLibraryCore.Dtos.ServerInfo>
|
||||
|
||||
<style>
|
||||
.progress {
|
||||
border-radius: 0;
|
||||
}
|
||||
.player-stat-icon {
|
||||
height: 1.5rem;
|
||||
width: 1.5rem;
|
||||
background-size: 1.5rem 1.5rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="content mt-20 row">
|
||||
<div class="col-12 col-lg-9 col-xl-10">
|
||||
<h2 class="content-title mb-0">Live Radar</h2>
|
||||
<div class="text-muted mb-15">
|
||||
<color-code value="@((Model.FirstOrDefault(server => server.Endpoint == ViewBag.SelectedServerId) ?? Model.First()).Name)"></color-code>
|
||||
</div>
|
||||
<div class="d-flex flex-column flex-md-row justify-content-between ">
|
||||
<div class="player-data-left w-md-quarter" style="opacity: 0;">
|
||||
</div>
|
||||
<div class="w-md-half m-0 mb-15 ml-md-15 mr-md-15">
|
||||
<div id="map_name" class="bg-dark-dm bg-light-lm text-center p-10 rounded-top">—</div>
|
||||
<div id="map_list" class="rounded" style="background-size:cover; padding-bottom: 100% !important;">
|
||||
<canvas id="map_canvas" style="position:absolute;"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<div class="player-data-right w-md-quarter" style="opacity: 0;">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- side context menu -->
|
||||
@{
|
||||
var menuItems = new SideContextMenuItems
|
||||
{
|
||||
MenuTitle = "Game", Items = Model.Select(server => new SideContextMenuItem
|
||||
{
|
||||
IsLink = true,
|
||||
// ReSharper disable Mvc.ActionNotResolved
|
||||
// ReSharper disable Mvc.ControllerNotResolved
|
||||
Reference = Url.Action("Index", "Radar", new { serverId = server.Endpoint }),
|
||||
Title = server.Name.StripColors(),
|
||||
IsActive = ViewBag.SelectedServerId == server.Endpoint
|
||||
}).ToList()
|
||||
};
|
||||
}
|
||||
|
||||
<partial name="_SideContextMenu" for="@menuItems"></partial>
|
||||
|
||||
<!-- images used by canvas -->
|
||||
<img class="hide" id="hud_death" src="~/images/radar/death.png"/>
|
||||
|
||||
</div>
|
||||
|
||||
@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.SelectedServerId })';
|
||||
const mapDataUrl = '@Url.Action("Map", "Radar", new { serverId = ViewBag.SelectedServerId })';
|
||||
// ReSharper restore Mvc.ActionNotResolved
|
||||
// ReSharper restore Mvc.ControllerNotResolved
|
||||
</script>
|
||||
}
|
@ -25,13 +25,13 @@
|
||||
<div class="pt-15 pl-15 pr-15 d-flex flex-wrap flex-column flex-md-row justify-content-between w-full w-auto-lg">
|
||||
@if (groupedClients.Count > 0)
|
||||
{
|
||||
<div class="flex-fill flex-lg-grow-0 w-half-md mr-md-10 mb-md-10">
|
||||
<div class="flex-fill flex-lg-grow-0 w-full w-md-half pr-md-10 pb-md-10">
|
||||
@foreach (var chat in Model.ChatHistory)
|
||||
{
|
||||
var message = chat.IsHidden && !ViewBag.Authorized ? chat.HiddenMessage : chat.Message;
|
||||
var stateIcon = GetIconForState(chat.Message);
|
||||
|
||||
<div>
|
||||
<div class="text-truncate">
|
||||
<i class="oi @stateIcon"></i>
|
||||
|
||||
<span>
|
||||
@ -53,21 +53,21 @@
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="d-flex flex-row flex-fill flex-lg-grow-0 w-half-md">
|
||||
<div class="d-flex flex-row w-full w-md-half pl-md-10 pb-md-10">
|
||||
|
||||
@foreach (var clientIndex in groupedClients)
|
||||
{
|
||||
<div class="@(clientIndex.index == 1 ? "pl-md-10 text-right" : "pr-md-10") flex-fill">
|
||||
<div class="@(clientIndex.index == 1 ? "pl-md-10" : "pr-md-10") w-half w-xl-full">
|
||||
@foreach (var client in clientIndex.group)
|
||||
{
|
||||
var levelColorClass = !ViewBag.Authorized || client.client.LevelInt == 0 ? "text-light-dm text-dark-lm" : $"level-color-{client.client.LevelInt}";
|
||||
<div class="d-flex @(clientIndex.index == 1 ? "flex-row-reverse" : "") w-xl-150">
|
||||
<div class="d-flex @(clientIndex.index == 1 ? "justify-content-start ml-auto flex-row-reverse" : "ml-auto") w-full w-xl-200">
|
||||
<has-permission entity="AdminMenu" required-permission="Update">
|
||||
<a href="#actionModal" class="profile-action" data-action="kick" data-action-id="@client.client.ClientId" aria-hidden="true">
|
||||
<i class="oi oi-circle-x font-size-12 @levelColorClass"></i>
|
||||
<i class="oi oi-circle-x font-size-12 @levelColorClass @(clientIndex.index == 1 ? "ml-5" : "mr-5")"></i>
|
||||
</a>
|
||||
</has-permission>
|
||||
<a asp-controller="Client" asp-action="Profile" asp-route-id="@client.client.ClientId" class="@(clientIndex.index == 1 ? "mr-5" : "ml-5") @levelColorClass no-decoration">
|
||||
<a asp-controller="Client" asp-action="Profile" asp-route-id="@client.client.ClientId" class="@levelColorClass no-decoration text-truncate">
|
||||
<color-code value="@client.client.Name"></color-code>
|
||||
</a>
|
||||
</div>
|
||||
|
@ -1,52 +1,30 @@
|
||||
@model IEnumerable<SharedLibraryCore.Dtos.PlayerInfo>
|
||||
@{
|
||||
Layout = null;
|
||||
var loc = SharedLibraryCore.Utilities.CurrentLocalization.LocalizationIndex;
|
||||
}
|
||||
|
||||
|
||||
@*<table class="table table-striped">
|
||||
<thead>
|
||||
<tr class="bg-primary pt-2 pb-2">
|
||||
<th scope="col">@loc["WEBFRONT_PENALTY_TEMPLATE_NAME"]</th>
|
||||
<th scope="col">@loc["WEBFRONT_CONFIGURATION_SERVER_IP"]</th>
|
||||
<th scope="col">@loc["WEBFRONT_PROFILE_LOOKUP_LOCATION"]</th>
|
||||
<th scope="col" class="text-right">@loc["WEBFRONT_SEARCH_LAST_CONNECTED"]</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
@foreach (var client in Model)
|
||||
{
|
||||
<tr>
|
||||
<td class="w-25">
|
||||
<a asp-controller="Client" asp-action="Profile" asp-route-id="@client.ClientId" class="link-inverse">@client.Name</a>
|
||||
</td>
|
||||
<td class="w-25">
|
||||
@client.IPAddress
|
||||
</td>
|
||||
<td>
|
||||
<div class="client-location-flag" data-ip="@client.IPAddress" />
|
||||
</td>
|
||||
<td class="text-right">
|
||||
@client.LastConnectionText
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
}
|
||||
</table>*@
|
||||
|
||||
@foreach (var client in Model)
|
||||
{
|
||||
<div class="p-2 mb-3 border-bottom" style="background-color: #222;">
|
||||
<div class="bg-very-dark-dm bg-light-ex-lm p-15 rounded mb-10">
|
||||
<div class="d-flex flex-row">
|
||||
<a asp-controller="Client" asp-action="Profile" asp-route-id="@client.ClientId" class="h4 mr-auto">
|
||||
<color-code value="@client.Name"></color-code>
|
||||
</a>
|
||||
<div class="client-location-flag align-self-center" data-ip="@client.IPAddress"></div>
|
||||
@if (client.GeoLocationInfo is not null)
|
||||
{
|
||||
@if (!string.IsNullOrEmpty(client.GeoLocationInfo.CountryCode))
|
||||
{
|
||||
<div data-toggle="tooltip" data-title="@client.GeoLocationInfo.Country">
|
||||
<div class="rounded" style="width: 40px; height: 25.66px; background-repeat: no-repeat; background-position: center center; background-image: url('https://flagcdn.com/w40/@(client.GeoLocationInfo.CountryCode.ToLower()).png')"></div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
<div class="d-flex flex-row">
|
||||
<div class="align-self-center mr-auto">@client.IPAddress</div>
|
||||
<div class="align-self-center">@client.LastConnectionText</div>
|
||||
<has-permission entity="ClientIPAddress" required-permission="Read">
|
||||
<div class="align-self-center mr-auto">@client.IPAddress</div>
|
||||
</has-permission>
|
||||
<div class="align-self-center text-muted font-size-12">@client.LastConnection.HumanizeForCurrentCulture()</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
@ -0,0 +1,13 @@
|
||||
@model IEnumerable<SharedLibraryCore.Dtos.PlayerInfo>
|
||||
@{
|
||||
Layout = null;
|
||||
}
|
||||
|
||||
<div class="mb-15 text-center font-weight-lighter">New clients connected in the last <span class="text-primary">24</span> hours</div>
|
||||
|
||||
<div id="recentClientContainer">
|
||||
<partial name="~/Views/Shared/Components/Client/_RecentClients.cshtml" for="@Model"/>
|
||||
</div>
|
||||
|
||||
<i class="loader-load-more oi oi-chevron-bottom text-center text-primary w-full"></i>
|
||||
<script>initLoader('/Action/RecentClientsForm', '#recentClientContainer', 20);</script>
|
39
WebfrontCore/Views/Shared/Partials/_Reports.cshtml
Normal file
39
WebfrontCore/Views/Shared/Partials/_Reports.cshtml
Normal file
@ -0,0 +1,39 @@
|
||||
@using Data.Models
|
||||
@using SharedLibraryCore.Dtos.Meta.Responses
|
||||
@model IEnumerable<SharedLibraryCore.Dtos.ServerInfo>
|
||||
@{
|
||||
Layout = null;
|
||||
}
|
||||
<div class="content-title">Recent Reports</div>
|
||||
<div class="text-muted">Last 24 hours</div>
|
||||
|
||||
@foreach (var server in Model.Where(server => server.Reports.Any()))
|
||||
{
|
||||
<div class="rounded bg-very-dark-dm bg-light-ex-lm mt-10 mb-10 p-10">
|
||||
<h5 class="mt-0 mb-5 text-truncate">
|
||||
<color-code value="@server.Name"></color-code>
|
||||
</h5>
|
||||
@foreach (var report in server.Reports.OrderByDescending(report => report.ReportedOn))
|
||||
{
|
||||
var penalty = new ReceivedPenaltyResponse
|
||||
{
|
||||
OffenderName = report.Target.Name,
|
||||
OffenderClientId = report.Target.ClientId,
|
||||
PunisherName = report.Origin.Name,
|
||||
PunisherClientId = report.Origin.ClientId,
|
||||
Offense = report.Reason,
|
||||
PenaltyType = EFPenalty.PenaltyType.Report,
|
||||
ClientId = report.Target.ClientId
|
||||
};
|
||||
<div class="font-weight-bold">@report.ReportedOn.HumanizeForCurrentCulture()</div>
|
||||
<div class="font-size-12">
|
||||
<a asp-action="Profile" asp-controller="Client" asp-route-id="@report.Target.ClientId">
|
||||
<color-code value="@report.Target.Name"></color-code>
|
||||
</a>
|
||||
<partial name="~/Views/Client/Profile/Meta/_ReceivedPenaltyResponse.cshtml" for="@penalty"/>
|
||||
</div>
|
||||
<hr/>
|
||||
|
||||
}
|
||||
</div>
|
||||
}
|
@ -12,43 +12,43 @@
|
||||
<meta name="description" content="@ViewBag.Description">
|
||||
<meta name="keywords" content="@ViewBag.Keywords">
|
||||
<link rel="icon" type="image/png" href="~/images/icon.png">
|
||||
|
||||
<environment include="Development">
|
||||
<link rel="stylesheet" href="~/lib/halfmoon/css/halfmoon-variables.css"/>
|
||||
<link rel="stylesheet" href="/css/src/main.css"/>
|
||||
@if (ViewBag.Configuration.WebfrontPrimaryColor is not null)
|
||||
{
|
||||
<style>
|
||||
:root {
|
||||
--blue-color: @ViewBag.Configuration.WebfrontPrimaryColor;
|
||||
}
|
||||
</style>
|
||||
}
|
||||
@if (ViewBag.Configuration.WebfrontSecondaryColor is not null)
|
||||
{
|
||||
<style>
|
||||
:root {
|
||||
--yellow-color: @ViewBag.Configuration.WebfrontSecondaryColor;
|
||||
}
|
||||
</style>
|
||||
}
|
||||
<link rel="stylesheet" href="/css/open-iconic.css"/>
|
||||
</environment>
|
||||
<environment include="Production">
|
||||
<link rel="stylesheet" href="~/css/global.min.css?version=@ViewBag.Version"/>
|
||||
</environment>
|
||||
@if (ViewBag.Configuration.WebfrontPrimaryColor is not null)
|
||||
{
|
||||
<style>
|
||||
:root {
|
||||
--blue-color: @ViewBag.Configuration.WebfrontPrimaryColor;
|
||||
}
|
||||
</style>
|
||||
}
|
||||
@if (ViewBag.Configuration.WebfrontSecondaryColor is not null)
|
||||
{
|
||||
<style>
|
||||
:root {
|
||||
--yellow-color: @ViewBag.Configuration.WebfrontSecondaryColor;
|
||||
}
|
||||
</style>
|
||||
}
|
||||
@await RenderSectionAsync("styles", false)
|
||||
</head>
|
||||
<body class="dark-mode with-custom-webkit-scrollbars with-custom-css-scrollbars" data-set-preferred-mode-onload="true">
|
||||
|
||||
<!-- Action Modal -->
|
||||
<div class="modal" id="actionModal" tabindex="-1" role="dialog">
|
||||
<!-- action modal -->
|
||||
<div class="modal" id="actionModal" tabindex="-1" role="dialog" style="z-index: 1000">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div id="modalLoadingBar" class="progress-bar position-absolute flex-fill position-fixed z-30" style="display:none">
|
||||
<div id="modalLoadingBar" class="modal-loading-bar progress-bar position-absolute flex-fill position-fixed z-30" style="display:none">
|
||||
<div class="progress-bar-value"></div>
|
||||
</div>
|
||||
<div class="modal-content">
|
||||
<div class="modal-content p-10">
|
||||
<div class="modal-content">
|
||||
<a href="#" class="btn close" role="button" aria-label="Close">
|
||||
<a href="#" onclick="halfmoon.toggleModal('actionModal')" class="btn close" role="button" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</a>
|
||||
<div id="actionModalContent">
|
||||
@ -59,9 +59,30 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- context modal -->
|
||||
<div class="modal" id="contextModal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-loading-bar progress-bar position-absolute flex-fill position-fixed z-30" style="display:none">
|
||||
<div class="progress-bar-value"></div>
|
||||
</div>
|
||||
<div class="modal-content">
|
||||
<div class="modal-content p-10">
|
||||
<a href="#" onclick="halfmoon.toggleModal('contextModal')" class="btn close" role="button" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</a>
|
||||
<div id="contextModalContent">
|
||||
<div class="modal-title"></div>
|
||||
<hr/>
|
||||
<div class="modal-body font-size-12"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="page-wrapper with-navbar with-sidebar" data-sidebar-type="overlayed-sm-and-down">
|
||||
<!-- toast notifications -->
|
||||
<div class="sticky-alerts"></div>
|
||||
<div class="sticky-alerts" style="z-index: 1001"></div>
|
||||
<!-- top menu bar -->
|
||||
<nav class="navbar">
|
||||
<button id="toggle-sidebar-btn" class="btn btn-action" type="button" onclick="halfmoon.toggleSidebar()">
|
||||
@ -88,53 +109,40 @@
|
||||
</has-permission>
|
||||
|
||||
<has-permission entity="AdminMenu" required-permission="Read">
|
||||
<div class="badge-group ml-10" role="group">
|
||||
<span class="badge badge-danger">@(ViewBag.ReportCount ?? "-")</span>
|
||||
<span class="badge bg-dark-dm bg-light-lm">Reports</span>
|
||||
</div>
|
||||
<a href="#actionModal" class="profile-action no-decoration" data-action="RecentReports" data-toggle="tooltip" data-title="View recent reports" data-placement="bottom">
|
||||
<div class="badge-group ml-10" role="group">
|
||||
<span class="badge badge-danger">@(ViewBag.ReportCount ?? "-")</span>
|
||||
<span class="badge bg-dark-dm bg-light-lm">Reports</span>
|
||||
</div>
|
||||
</a>
|
||||
</has-permission>
|
||||
</div>
|
||||
|
||||
<div class="d-flex d-lg-none ml-auto">
|
||||
<a href="#contextMenuModal">
|
||||
<button class="btn" type="button">
|
||||
<i class="oi oi-ellipses"></i>
|
||||
</button>
|
||||
</a>
|
||||
<div class="d-flex ml-auto">
|
||||
<div class="btn btn-action mr-10 ml-10" onclick="halfmoon.toggleDarkMode()" data-toggle="tooltip" data-title="Toggle display mode" data-placement="bottom">
|
||||
<i class="oi oi-moon"></i>
|
||||
</div>
|
||||
<div class="d-none d-md-block ">
|
||||
|
||||
<partial name="_SearchResourceForm"/>
|
||||
</div>
|
||||
<div class="d-flex d-lg-none">
|
||||
<a href="#" onclick="halfmoon.toggleModal('contextMenuModal')">
|
||||
<button class="btn" type="button">
|
||||
<i class="oi oi-ellipses"></i>
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@*<div class="d-none d-md-flex ml-auto">
|
||||
<div class="btn oi btn-square btn-action mr-10" data-glyph="moon" onclick="halfmoon.toggleDarkMode()" title="Toggle display mode"></div>
|
||||
</div>*@
|
||||
|
||||
<div class="d-none d-lg-block ml-auto">
|
||||
<partial name="_SearchResourceForm"/>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<partial name="_LeftNavBar"/>
|
||||
|
||||
<!-- Main Modal -->
|
||||
<!--<div class="modal fade" id="mainModal" tabindex="-1" role="dialog" aria-labelledby="mainModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content bg-dark">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="mainModalLabel"></h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true" class="text-danger">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>-->
|
||||
<!-- End Main Modal -->
|
||||
<div id="target_id">
|
||||
@await RenderSectionAsync("targetid", required: false)
|
||||
</div>
|
||||
|
||||
<!-- End Action Modal -->
|
||||
<div class="container-fluid content-wrapper">
|
||||
<div id="mainLoadingBar" class="progress-bar position-absolute flex-fill position-fixed z-30" style="display: none">
|
||||
<div class="progress-bar-value"></div>
|
||||
@ -155,6 +163,7 @@
|
||||
<script type="text/javascript" src="~/lib/chart.js/dist/Chart.bundle.min.js"></script>
|
||||
<script type="text/javascript" src="~/lib/halfmoon/js/halfmoon.js"></script>
|
||||
<script type="text/javascript" src="~/js/action.js"></script>
|
||||
<script type="text/javascript" src="~/js/loader.js"></script>
|
||||
<script type="text/javascript" src="~/js/search.js"></script>
|
||||
</environment>
|
||||
<environment include="Production">
|
||||
|
@ -23,10 +23,12 @@
|
||||
<span class="name">@ViewBag.Localization["WEBFRONT_NAV_ABOUT"]</span>
|
||||
</a>
|
||||
<!-- penalties -->
|
||||
<a asp-controller="Penalty" asp-action="List" class="sidebar-link">
|
||||
<i class="oi oi-lock-locked mr-5"></i>
|
||||
<span class="name">@ViewBag.Localization["WEBFRONT_NAV_PENALTIES"]</span>
|
||||
</a>
|
||||
<has-permission entity="Penalty" required-permission="Read">
|
||||
<a asp-controller="Penalty" asp-action="List" class="sidebar-link">
|
||||
<i class="oi oi-lock-locked mr-5"></i>
|
||||
<span class="name">@ViewBag.Localization["WEBFRONT_NAV_PENALTIES"]</span>
|
||||
</a>
|
||||
</has-permission>
|
||||
<!-- privileged -->
|
||||
<has-permission entity="PrivilegedClientsPage" required-permission="Read">
|
||||
<a asp-controller="Client" asp-action="Privileged" class="sidebar-link">
|
||||
@ -65,7 +67,7 @@
|
||||
@foreach (Page pageLink in ViewBag.Pages)
|
||||
{
|
||||
<a class="sidebar-link" href="@pageLink.Location">
|
||||
<i class="oi @(pageLink.Location.EndsWith("Radar/All") ? "oi-wifi" : "oi-bar-chart") mr-5"></i>
|
||||
<i class="oi @(pageLink.Location.EndsWith("Radar") ? "oi-wifi" : "oi-bar-chart") mr-5"></i>
|
||||
<span class="name">@pageLink.Name</span>
|
||||
</a>
|
||||
}
|
||||
@ -95,9 +97,9 @@
|
||||
var url = Uri.TryCreate(social.IconUrl, UriKind.Absolute, out Uri parsedUrl)
|
||||
? parsedUrl.AbsoluteUri
|
||||
: $"/images/community/{social.IconUrl}";
|
||||
<img class="img-fluid mr-5" style="max-height: 1.2rem" src="@url" alt="@social.Title"/>
|
||||
<img class="img-fluid social-icon" style="max-height: 1.2rem" src="@url" alt="@social.Title"/>
|
||||
}
|
||||
<span class="name">@social.Title</span>
|
||||
<span class="name text-truncate">@social.Title</span>
|
||||
</a>
|
||||
}
|
||||
<br/>
|
||||
@ -113,7 +115,7 @@
|
||||
<span class="name">@ViewBag.Localization["WEBFRONT_NAV_CONSOLE"]</span>
|
||||
</a>
|
||||
</has-permission>
|
||||
@if (ViewBag.User.Level == EFClient.Permission.Owner)
|
||||
@if (ViewBag.User.Level >= EFClient.Permission.Owner)
|
||||
{
|
||||
<a asp-controller="Configuration" asp-action="Edit" class="sidebar-link">
|
||||
<i class="oi oi-cog mr-5"></i>
|
||||
@ -126,13 +128,13 @@
|
||||
<span class="name">@ViewBag.Localization["WEBFRONT_NAV_AUDIT_LOG"]</span>
|
||||
</a>
|
||||
</has-permission>
|
||||
@*<has-permission entity="RecentPlayersPage" required-permission="Read">
|
||||
<has-permission entity="RecentPlayersPage" required-permission="Read">
|
||||
<a class="sidebar-link profile-action" href="#actionModal" data-action="RecentClients" title="@ViewBag.Localization["WEBFRONT_ACTION_RECENT_CLIENTS"]">
|
||||
<i class="oi oi-timer mr-5"></i>
|
||||
<span class="name">@ViewBag.Localization["WEBFRONT_ACTION_RECENT_CLIENTS"]</span>
|
||||
</a>
|
||||
</has-permission>*@
|
||||
<a class="sidebar-link profile-action" href="#actionModal" data-action="GenerateLoginToken" title="@ViewBag.Localization["WEBFRONT_ACTION_TOKEN"]">
|
||||
</has-permission>
|
||||
<a class="sidebar-link profile-action" href="#actionModal" data-action="GenerateLoginToken" data-response-duration="30000" title="@ViewBag.Localization["WEBFRONT_ACTION_TOKEN"]">
|
||||
<i class="oi oi-key mr-5"></i>
|
||||
<span class="name">@ViewBag.Localization["WEBFRONT_ACTION_TOKEN"]</span>
|
||||
</a>
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
@foreach (var item in Model.Items)
|
||||
{
|
||||
<a href="@(item.IsLink ? item.Reference : "#actionModal")" class="@(item.IsLink ? "" : "profile-action")" data-action="@(item.IsLink ? "" : item.Reference)">
|
||||
<a href="@(item.IsLink ? item.Reference : "#")" class="@(item.IsLink ? "" : "profile-action")" data-action="@(item.IsLink ? "" : item.Reference)" data-action-id="@item.EntityId">
|
||||
<div class="@(item.IsButton ? "btn btn-block" : "")" data-title="@item.Tooltip" data-placement="left" data-toggle="@(string.IsNullOrEmpty(item.Tooltip) ? "" : "tooltip")">
|
||||
<i class="@(string.IsNullOrEmpty(item.Icon) ? "" : $"oi {item.Icon}") mr-5 font-size-12"></i>
|
||||
<span class="@(item.IsActive ? "text-primary" : "") text-truncate">@item.Title</span>
|
||||
@ -28,7 +28,7 @@
|
||||
@foreach (var item in Model.Items)
|
||||
{
|
||||
<div class="mt-15 mb-15">
|
||||
<a href="@(item.IsLink ? item.Reference : "#actionModal")" class="@(item.IsLink ? "" : "profile-action") no-decoration" data-action="@(item.IsLink ? "" : item.Reference)">
|
||||
<a href="@(item.IsLink ? item.Reference : "#")" class="@(item.IsLink ? "" : "profile-action") no-decoration" data-action="@(item.IsLink ? "" : item.Reference)" data-action-id="@item.EntityId">
|
||||
<div class="btn btn-block btn-lg @(item.IsActive ? "btn-primary" : "") text-truncate" data-title="@item.Tooltip" data-toggle="@(string.IsNullOrEmpty(item.Tooltip) ? "" : "tooltip")">
|
||||
<i class="@(string.IsNullOrEmpty(item.Icon) ? "" : $"oi {item.Icon}") mr-5 font-size-12"></i>
|
||||
<span>@item.Title</span>
|
||||
|
@ -78,6 +78,6 @@
|
||||
</ProjectExtensions>
|
||||
|
||||
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
|
||||
<Exec Command="if $(ConfigurationName) == Debug ( 
powershell -Command wget https://raw.githubusercontent.com/iconic/open-iconic/master/font/css/open-iconic-bootstrap.scss -o $(ProjectDir)wwwroot\lib\open-iconic\font\css\open-iconic-bootstrap-override.scss
echo d | xcopy /f /y $(ProjectDir)wwwroot\lib\open-iconic\font\fonts $(ProjectDir)wwwroot\font\
powershell -Command "((Get-Content -path $(ProjectDir)wwwroot\lib\open-iconic\font\css\open-iconic-bootstrap-override.scss -Raw) -replace '../fonts/','/fonts/') | Set-Content -Path $(ProjectDir)wwwroot\lib\open-iconic\font\css\open-iconic-bootstrap-override.scss"
)" />
|
||||
<Exec Command="if $(ConfigurationName) == Debug ( 
powershell -Command wget https://raw.githubusercontent.com/iconic/open-iconic/master/font/css/open-iconic-bootstrap.scss -o $(ProjectDir)wwwroot\lib\open-iconic\font\css\open-iconic-bootstrap-override.scss
echo d | xcopy /f /y $(ProjectDir)wwwroot\lib\open-iconic\font\fonts $(ProjectDir)wwwroot\font\
powershell -Command "((Get-Content -path $(ProjectDir)wwwroot\lib\open-iconic\font\css\open-iconic-bootstrap-override.scss -Raw) -replace '../fonts/','/font/') | Set-Content -Path $(ProjectDir)wwwroot\lib\open-iconic\font\css\open-iconic-bootstrap-override.scss"
)" />
|
||||
</Target>
|
||||
</Project>
|
||||
|
@ -1,196 +1,198 @@
|
||||
@import 'profile.scss';
|
||||
|
||||
:root {
|
||||
--blue-color: #007ACC;
|
||||
--blue-color: #117ac0;
|
||||
|
||||
--yellow-color: #fe7e4c;
|
||||
--yellow-color-dark: #fe7e4c88;
|
||||
--yellow-color-very-dark: #fe7e4c22;
|
||||
--yellow-color-light: #fe7e4c88;
|
||||
--yellow-color-very-light: #fe7e4c22;
|
||||
--yellow-color: #fe7e4c;
|
||||
--yellow-color-dark: #fe7e4c88;
|
||||
--yellow-color-very-dark: #fe7e4c22;
|
||||
--yellow-color-light: #fe7e4c88;
|
||||
--yellow-color-very-light: #fe7e4c22;
|
||||
|
||||
--red-color: #ff6060;
|
||||
--red-color-dark: #ff606088;
|
||||
--red-color-very-dark: #ff606022;
|
||||
--red-color-light: #ff606088;
|
||||
--red-color-very-light: #ff606022;
|
||||
--red-color: #ff6060;
|
||||
--red-color-dark: #ff606088;
|
||||
--red-color-very-dark: #ff606022;
|
||||
--red-color-light: #ff606088;
|
||||
--red-color-very-light: #ff606022;
|
||||
|
||||
--green-color: #8cc982;
|
||||
--lm-base-body-bg-color: rgb(245, 245, 245);
|
||||
--lm-card-bg-color: var(--gray-color-light);
|
||||
--gray-color-light: white;
|
||||
--card-border-width: 0;
|
||||
--green-color: #8cc982;
|
||||
--lm-base-body-bg-color: rgb(245, 245, 245);
|
||||
--lm-card-bg-color: var(--gray-color-light);
|
||||
--gray-color-light: white;
|
||||
--card-border-width: 0;
|
||||
|
||||
--dm-modal-overlay-bg-color: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
|
||||
.server-history-row {
|
||||
height: 100px;
|
||||
padding: 0 !important;
|
||||
height: 100px;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.canvasjs-chart-credit {
|
||||
display: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.table thead th,
|
||||
.table th,
|
||||
.table td {
|
||||
border: none;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.server-history {
|
||||
}
|
||||
|
||||
.border-top {
|
||||
border-top: 1px solid var(--primary-color) !important;
|
||||
border-top: 1px solid var(--primary-color) !important;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.d-md-table-header-group {
|
||||
display: table-header-group !important;
|
||||
}
|
||||
.d-md-table-header-group {
|
||||
display: table-header-group !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 992px) {
|
||||
.d-lg-table-header-group {
|
||||
display: table-header-group !important;
|
||||
}
|
||||
.d-lg-table-header-group {
|
||||
display: table-header-group !important;
|
||||
}
|
||||
}
|
||||
|
||||
#console_command_response {
|
||||
min-height: 20rem;
|
||||
min-height: 20rem;
|
||||
}
|
||||
|
||||
#console_command_response hr {
|
||||
border-color: #6c757d;
|
||||
border-color: #6c757d;
|
||||
}
|
||||
|
||||
#console .form-control, #console button {
|
||||
border-radius: 0;
|
||||
border-left: 0;
|
||||
border-right: 0;
|
||||
border-radius: 0;
|
||||
border-left: 0;
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
.close {
|
||||
text-shadow: none !important;
|
||||
text-shadow: none !important;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
border-top-color: var(--secondary-color);
|
||||
border-top-color: var(--secondary-color);
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
border-bottom-color: var(--secondary-color);
|
||||
border-bottom-color: var(--secondary-color);
|
||||
}
|
||||
|
||||
#penalty_filter_selection {
|
||||
border: none !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
@-webkit-keyframes rotation {
|
||||
from {
|
||||
-webkit-transform: rotate(359deg);
|
||||
}
|
||||
from {
|
||||
-webkit-transform: rotate(359deg);
|
||||
}
|
||||
|
||||
to {
|
||||
-webkit-transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
-webkit-transform: rotate(0deg);
|
||||
}
|
||||
}
|
||||
|
||||
.layout-loading-icon {
|
||||
position: fixed !important;
|
||||
left: 50%;
|
||||
top: 50% !important;
|
||||
margin-left: -37px;
|
||||
margin-top: -37px;
|
||||
color: var(--dm-base-text-color);
|
||||
z-index: 100;
|
||||
font-size: 4rem;
|
||||
-webkit-animation: rotation 1s infinite linear;
|
||||
background-color: var(--primary-color);
|
||||
border-radius: 40px;
|
||||
padding: 5px;
|
||||
visibility: hidden;
|
||||
position: fixed !important;
|
||||
left: 50%;
|
||||
top: 50% !important;
|
||||
margin-left: -37px;
|
||||
margin-top: -37px;
|
||||
color: var(--dm-base-text-color);
|
||||
z-index: 100;
|
||||
font-size: 4rem;
|
||||
-webkit-animation: rotation 1s infinite linear;
|
||||
background-color: var(--primary-color);
|
||||
border-radius: 40px;
|
||||
padding: 5px;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.loader-load-more {
|
||||
border-radius: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.input-border-transition {
|
||||
-webkit-transition: border 500ms ease-out;
|
||||
-moz-transition: border 500ms ease-out;
|
||||
-o-transition: border 500ms ease-out;
|
||||
-webkit-transition: border 500ms ease-out;
|
||||
-moz-transition: border 500ms ease-out;
|
||||
-o-transition: border 500ms ease-out;
|
||||
}
|
||||
|
||||
.input-text-danger {
|
||||
border-color: var(--red-color) !important;
|
||||
border-color: var(--red-color) !important;
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
box-shadow: none;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
-webkit-box-shadow: none;
|
||||
}
|
||||
|
||||
.striped > div:nth-child(even) {
|
||||
background-color: rgba(0, 0, 0, 0.125);
|
||||
background-color: rgba(0, 0, 0, 0.125);
|
||||
}
|
||||
|
||||
.striped > div:nth-child(odd) {
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.client-rating-graph {
|
||||
min-height: 100px;
|
||||
min-height: 100px;
|
||||
}
|
||||
|
||||
.client-rating-icon {
|
||||
}
|
||||
|
||||
.client-rating-change-up, .client-rating-change-down {
|
||||
font-size: 0.75rem;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.client-rating-change-amount {
|
||||
font-size: 1rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.oi, .table-sort-column {
|
||||
cursor: pointer;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#footer_text {
|
||||
font-size: 0.85rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.footer-mobile {
|
||||
position: fixed;
|
||||
bottom: 1em;
|
||||
right: 1em;
|
||||
position: fixed;
|
||||
bottom: 1em;
|
||||
right: 1em;
|
||||
}
|
||||
|
||||
.mt-n1 {
|
||||
margin-top: -0.25rem !important;
|
||||
margin-top: -0.25rem !important;
|
||||
}
|
||||
|
||||
.mt-n2 {
|
||||
margin-top: -0.5rem !important;
|
||||
margin-top: -0.5rem !important;
|
||||
}
|
||||
|
||||
/* Configuration */
|
||||
.configuration-form input[type='text'], .configuration-form input[type='number'], input.text-box {
|
||||
border: 1px solid var(--secondary-color);
|
||||
border: 1px solid var(--secondary-color);
|
||||
}
|
||||
|
||||
.hide {
|
||||
display: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.client-location-flag {
|
||||
width: 3rem;
|
||||
height: 1.5rem;
|
||||
background-image: url('/images/radar/hud_weapons/hud_neutral.png');
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat
|
||||
width: 3rem;
|
||||
height: 1.5rem;
|
||||
background-image: url('/images/radar/hud_weapons/hud_neutral.png');
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat
|
||||
}
|
||||
|
||||
/*
|
||||
@ -235,61 +237,60 @@
|
||||
}
|
||||
*/
|
||||
@keyframes color-change {
|
||||
0% {
|
||||
color: #ff0000;
|
||||
}
|
||||
0% {
|
||||
color: #ff0000;
|
||||
}
|
||||
|
||||
16.66% {
|
||||
color: #ffff00;
|
||||
}
|
||||
16.66% {
|
||||
color: #ffff00;
|
||||
}
|
||||
|
||||
33.33% {
|
||||
color: #00ff00;
|
||||
}
|
||||
33.33% {
|
||||
color: #00ff00;
|
||||
}
|
||||
|
||||
50% {
|
||||
color: #00ffff;
|
||||
}
|
||||
50% {
|
||||
color: #00ffff;
|
||||
}
|
||||
|
||||
66.66% {
|
||||
color: #0000ff;
|
||||
}
|
||||
66.66% {
|
||||
color: #0000ff;
|
||||
}
|
||||
|
||||
83.33% {
|
||||
color: #ff00ff;
|
||||
}
|
||||
83.33% {
|
||||
color: #ff00ff;
|
||||
}
|
||||
|
||||
100% {
|
||||
color: #ff0000;
|
||||
}
|
||||
100% {
|
||||
color: #ff0000;
|
||||
}
|
||||
}
|
||||
|
||||
.action-kick-button {
|
||||
color: #492121;
|
||||
margin-top: 0.25rem;
|
||||
color: #492121;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.action-kick-button:hover {
|
||||
color: #ff6060 !important;
|
||||
color: rgba(255, 69, 69, 0.85) !important;
|
||||
color: #ff6060 !important;
|
||||
color: rgba(255, 69, 69, 0.85) !important;
|
||||
}
|
||||
|
||||
.text-force-break
|
||||
{
|
||||
word-break: break-all;
|
||||
overflow-wrap: break-word;
|
||||
.text-force-break {
|
||||
word-break: break-all;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
div.card {
|
||||
min-width: 15rem;
|
||||
min-width: 15rem;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
max-width: 15rem;
|
||||
max-width: 15rem;
|
||||
}
|
||||
|
||||
#client_stats_summary a:hover {
|
||||
color: #e0e0e0;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
.cursor-help {
|
||||
@ -297,147 +298,158 @@ div.card {
|
||||
}
|
||||
|
||||
.text-light-green {
|
||||
color: #88aa82;
|
||||
color: #88aa82;
|
||||
}
|
||||
|
||||
.nav-icon {
|
||||
padding-left: 1rem !important;
|
||||
padding-right: 1rem !important;
|
||||
line-height: 1.45rem !important;
|
||||
padding-left: 1rem !important;
|
||||
padding-right: 1rem !important;
|
||||
line-height: 1.45rem !important;
|
||||
}
|
||||
|
||||
.team-allies-bg {
|
||||
background-color: rgba(0, 0, 139, 0.1);
|
||||
background-color: rgba(0, 0, 139, 0.1);
|
||||
}
|
||||
|
||||
.team-axis-bg {
|
||||
background-color: rgba(139, 0, 0, 0.1);
|
||||
background-color: rgba(139, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.team-spectator-bg {
|
||||
background-color: rgba(200, 200, 0, 0.1);
|
||||
background-color: rgba(200, 200, 0, 0.1);
|
||||
}
|
||||
|
||||
|
||||
#penalty_table tr.d-table-row, {
|
||||
border: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.server-header {
|
||||
--dm-navbar-link-text-color-hover: white;
|
||||
--lm-navbar-link-text-color-hover: white;
|
||||
--dm-navbar-link-text-color-hover: white;
|
||||
--lm-navbar-link-text-color-hover: white;
|
||||
}
|
||||
|
||||
#console_server_select {
|
||||
--input-border-width: 0;
|
||||
--input-border-width: 0;
|
||||
}
|
||||
|
||||
.sidebar-link > .oi {
|
||||
font-size: 1.2rem;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-radius: var(--base-border-radius) !important;
|
||||
overflow: hidden;
|
||||
border-collapse: collapse;
|
||||
border-radius: var(--base-border-radius) !important;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ui-error-icon {
|
||||
background-image:url('/images/ui/error-face.svg');
|
||||
background-size: contain;
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
background-image: url('/images/ui/error-face.svg');
|
||||
background-size: contain;
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
/* Sidenav */
|
||||
.on-this-page-nav {
|
||||
position: fixed;
|
||||
margin-right: 2rem;
|
||||
z-index: 20;
|
||||
background-color: var(--lm-base-body-bg-color);
|
||||
padding: 0.5rem;
|
||||
border-radius: 0.4rem;
|
||||
position: fixed;
|
||||
margin-right: 2rem;
|
||||
z-index: 20;
|
||||
background-color: var(--lm-base-body-bg-color);
|
||||
padding: 0.5rem;
|
||||
border-radius: 0.4rem;
|
||||
}
|
||||
|
||||
.dark-mode .on-this-page-nav {
|
||||
background-color: #25282c;
|
||||
background-color: #25282c;
|
||||
}
|
||||
|
||||
.on-this-page-nav .title {
|
||||
font-weight: 500;
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
font-weight: 500;
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.on-this-page-nav a {
|
||||
text-align: left;
|
||||
display: block;
|
||||
padding: 0.4rem 0 0.4rem 2rem;
|
||||
line-height: 1.4;
|
||||
color: rgba(0, 0, 0, 0.7);
|
||||
border-left: 1px solid rgba(0, 0, 0, 0.15);
|
||||
text-align: left;
|
||||
display: block;
|
||||
padding: 0.4rem 0 0.4rem 2rem;
|
||||
line-height: 1.4;
|
||||
color: rgba(0, 0, 0, 0.7);
|
||||
border-left: 1px solid rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.dark-mode .on-this-page-nav a {
|
||||
color: rgba(255, 255, 255, 0.65);
|
||||
border-color: rgba(255, 255, 255, 0.15);
|
||||
color: rgba(255, 255, 255, 0.65);
|
||||
border-color: rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
|
||||
.on-this-page-nav a:hover,
|
||||
.dark-mode .on-this-page-nav a:hover, {
|
||||
color: #1890ff;
|
||||
text-decoration: none;
|
||||
color: #1890ff;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.on-this-page-nav-container {
|
||||
display: none;
|
||||
}
|
||||
.on-this-page-nav-container {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.no-decoration {
|
||||
text-decoration: none !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
.h-125 {
|
||||
height: 12.5rem!important;
|
||||
height: 12.5rem !important;
|
||||
}
|
||||
|
||||
.w-125 {
|
||||
width: 12.5rem!important;
|
||||
width: 12.5rem !important;
|
||||
}
|
||||
|
||||
table.with-fixed-layout {
|
||||
table-layout:fixed;
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
table.with-auto-width td {
|
||||
width: 1px;
|
||||
white-space: nowrap;
|
||||
width: 1px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.bg-light-ex-lm {
|
||||
background-color: var(--lm-base-body-bg-color);
|
||||
background-color: var(--lm-base-body-bg-color);
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 2px;
|
||||
background-color: var(--primary-color);
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
height: 2px;
|
||||
background-color: var(--primary-color);
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-bar-value {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color:black;
|
||||
animation: indeterminateAnimation 2s infinite linear;
|
||||
transform-origin: 0 50%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: black;
|
||||
animation: indeterminateAnimation 2s infinite linear;
|
||||
transform-origin: 0 50%;
|
||||
}
|
||||
|
||||
@keyframes indeterminateAnimation {
|
||||
0% {
|
||||
transform: translateX(0) scaleX(0);
|
||||
}
|
||||
40% {
|
||||
transform: translateX(0) scaleX(0.4);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(100%) scaleX(0.5);
|
||||
}
|
||||
0% {
|
||||
transform: translateX(0) scaleX(0);
|
||||
}
|
||||
40% {
|
||||
transform: translateX(0) scaleX(0.4);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(100%) scaleX(0.5);
|
||||
}
|
||||
}
|
||||
|
||||
img.social-icon {
|
||||
max-height: 1.75rem;
|
||||
margin-right: 0.6rem;
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
@ -5,11 +5,15 @@
|
||||
.level-color-user, .level-color-guest, .level-color-0 {
|
||||
}
|
||||
|
||||
.level-bgcolor-user, .level-bgcolor-guest, .level-bgcolor-0 {
|
||||
.dark-mode .level-bgcolor-user, .dark-mode .level-bgcolor-guest, .dark-mode .level-bgcolor-0 {
|
||||
background-color: #6c757d !important;
|
||||
background-color: rgba(255, 255, 255, 0.68) !important;
|
||||
}
|
||||
|
||||
.level-bgcolor-user, .level-bgcolor-guest, .level-bgcolor-0 {
|
||||
background-color: var(--lm-base-body-bg-color) !important;
|
||||
}
|
||||
|
||||
.level-color-trusted, .level-color-2 {
|
||||
color: #749363 !important;
|
||||
color: rgba(116,147,99,1) !important;
|
||||
@ -188,10 +192,11 @@
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.ip-lookup-profile {
|
||||
.profile-country-flag {
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
height:5rem;
|
||||
}
|
||||
|
||||
.status-indicator {
|
||||
|
@ -1,11 +1,11 @@
|
||||
function hideLoader() {
|
||||
$('#mainLoadingBar').fadeOut();
|
||||
$('#modalLoadingBar').fadeOut();
|
||||
$('.modal-loading-bar').fadeOut();
|
||||
}
|
||||
|
||||
function showLoader() {
|
||||
$('#mainLoadingBar').fadeIn();
|
||||
$('#modalLoadingBar').fadeIn();
|
||||
$('.modal-loading-bar').fadeIn();
|
||||
}
|
||||
|
||||
function errorLoader() {
|
||||
@ -60,6 +60,7 @@ function escapeHtml (string) {
|
||||
$(document).ready(function () {
|
||||
|
||||
let toastMessage = getUrlParameter('toastMessage');
|
||||
const duration = parseInt(getUrlParameter('duration'));
|
||||
|
||||
if (toastMessage) {
|
||||
toastMessage = unescape(toastMessage);
|
||||
@ -71,23 +72,28 @@ $(document).ready(function () {
|
||||
content: toastMessage,
|
||||
title: 'Success',
|
||||
alertType: 'alert-success',
|
||||
fillType: 'filled'
|
||||
fillType: 'filled',
|
||||
timeShown: duration
|
||||
});
|
||||
}
|
||||
|
||||
hideLoader();
|
||||
|
||||
$(document).off('click', '.profile-action');
|
||||
$(document).on('click', '.profile-action', function () {
|
||||
$(document).on('click', '.profile-action', function (e) {
|
||||
e.preventDefault();
|
||||
const actionType = $(this).data('action');
|
||||
const actionId = $(this).data('action-id');
|
||||
const responseDuration = $(this).data('response-duration') || 5000;
|
||||
const actionIdKey = actionId === undefined ? '' : `?id=${actionId}`;
|
||||
showLoader();
|
||||
$.get(`/Action/${actionType}Form/${actionIdKey}`)
|
||||
.done(function (response) {
|
||||
$('#actionModal .modal-message').fadeOut('fast');
|
||||
$('#actionModal .modal-message').fadeOut('fast')
|
||||
$('#actionModal').attr('data-response-duration', responseDuration);
|
||||
$('#actionModalContent').html(response);
|
||||
hideLoader();
|
||||
halfmoon.toggleModal('actionModal');
|
||||
})
|
||||
.fail(function (jqxhr, textStatus, error) {
|
||||
halfmoon.initStickyAlert({
|
||||
@ -108,6 +114,7 @@ $(document).ready(function () {
|
||||
const modal = $('#actionModal');
|
||||
const shouldRefresh = modal.data('should-refresh', modal.find('.refreshable').length !== 0);
|
||||
const data = $(this).serialize();
|
||||
const duration = modal.data('response-duration');
|
||||
showLoader();
|
||||
|
||||
$.get($(this).attr('action') + '/?' + data)
|
||||
@ -123,7 +130,7 @@ $(document).ready(function () {
|
||||
}
|
||||
catch{}
|
||||
if (shouldRefresh) {
|
||||
window.location = `${window.location.href.replace('#actionModal', '')}?toastMessage=${escape(message)}`;
|
||||
window.location = `${window.location.href.replace('#', '')}?toastMessage=${escape(message)}${duration ? `&duration=${duration}` : ''}`;
|
||||
}
|
||||
else {
|
||||
modal.modal();
|
||||
@ -151,30 +158,17 @@ $(document).ready(function () {
|
||||
|
||||
catch{}
|
||||
|
||||
if (message instanceof Array)
|
||||
{
|
||||
message = message.join("<br/>");
|
||||
}
|
||||
|
||||
halfmoon.initStickyAlert({
|
||||
content: message.join("<br/>"),
|
||||
content: message,
|
||||
title: 'Error',
|
||||
alertType: 'alert-danger',
|
||||
fillType: 'filled'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
* handle loading of recent clients
|
||||
*/
|
||||
$('#actionModal').off('action_form_received');
|
||||
$('#actionModal').on('action_form_received', function (e, actionType) {
|
||||
if (actionType === 'RecentClients') {
|
||||
const ipAddresses = $('.client-location-flag');
|
||||
$.each(ipAddresses, function (index, address) {
|
||||
$.get(`https://get.geojs.io/v1/ip/country/${(address).data('ip')}.json`, function (result) {
|
||||
const countryCode = result['country'].toLowerCase();
|
||||
if (countryCode !== 'zz') {
|
||||
$(address).css('background-image', `url('https://flagcdn.com/w80/${countryCode}.png')`);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -13,7 +13,7 @@
|
||||
|
||||
hideLoader();
|
||||
response.map(r => r.response).forEach(item => {
|
||||
$('#console_command_response').append(`<div>${item}</div>`);
|
||||
$('#console_command_response').append(`<div>${escapeHtml(item)}</div>`);
|
||||
})
|
||||
|
||||
$('#console_command_response').append('<hr/>')
|
||||
@ -26,7 +26,7 @@
|
||||
|
||||
if (response.status < 500) {
|
||||
response.responseJSON.map(r => r.response).forEach(item => {
|
||||
$('#console_command_response').append(`<div class="text-danger">${item}</div>`);
|
||||
$('#console_command_response').append(`<div class="text-danger">${escapeHtml(item)}</div>`);
|
||||
})
|
||||
} else {
|
||||
$('#console_command_response').append(`<div class="text-danger">Could not execute command...</div>`);
|
||||
|
@ -14,11 +14,20 @@ function initLoader(location, loaderId, count = 10, start = count, additional =
|
||||
loaderOffset = start;
|
||||
additionalParams = additional;
|
||||
|
||||
setupMonitor();
|
||||
try {
|
||||
setupMonitor();
|
||||
}
|
||||
catch {
|
||||
// ignored (can happen when the action modal loader exists but no page level loader does)
|
||||
}
|
||||
|
||||
$('#loaderLoad').click(function () {
|
||||
loadMoreItems();
|
||||
});
|
||||
|
||||
$('.loader-load-more').click(function() {
|
||||
loadMoreItems();
|
||||
})
|
||||
}
|
||||
|
||||
function setupMonitor() {
|
||||
|
@ -55,36 +55,35 @@
|
||||
/*
|
||||
get ip geolocation info into modal
|
||||
*/
|
||||
$('.ip-locate-link').click(function (e) {
|
||||
e.preventDefault();
|
||||
$('.profile-ip-lookup').click(function (e) {
|
||||
const ip = $(this).data("ip");
|
||||
$.getJSON(`https://ipwhois.app/json/${ip}`)
|
||||
.done(function (response) {
|
||||
$('#mainModal .modal-title').text(ip);
|
||||
$('#mainModal .modal-body').text('');
|
||||
$('#contextModal .modal-title').text(ip);
|
||||
const modalBody = $('#contextModal .modal-body');
|
||||
modalBody.text('');
|
||||
if (response.isp.length > 0) {
|
||||
$('#mainModal .modal-body').append(`${_localization['WEBFRONT_PROFILE_LOOKUP_ISP']} — ${response.isp}<br/>`);
|
||||
modalBody.append(`${_localization['WEBFRONT_PROFILE_LOOKUP_ISP']} — <span class="text-muted">${response.isp}</span><br/>`);
|
||||
}
|
||||
if (response.org.length > 0) {
|
||||
$('#mainModal .modal-body').append(`${_localization['WEBFRONT_PROFILE_LOOKUP_ORG']} — ${response.org}<br/>`);
|
||||
modalBody.append(`${_localization['WEBFRONT_PROFILE_LOOKUP_ORG']} — <span class="text-muted">${response.org}</span><br/>`);
|
||||
}
|
||||
if (response.region.length > 0 || response.city.length > 0 || response.country.length > 0 || response.timezone_gmt.length > 0) {
|
||||
$('#mainModal .modal-body').append(`${_localization['WEBFRONT_PROFILE_LOOKUP_LOCATION']} — `);
|
||||
modalBody.append(`${_localization['WEBFRONT_PROFILE_LOOKUP_LOCATION']} —`);
|
||||
}
|
||||
if (response.city.length > 0) {
|
||||
$('#mainModal .modal-body').append(response.city);
|
||||
modalBody.append(`<span class="text-muted">${response.city}</span>`);
|
||||
}
|
||||
if (response.region.length > 0) {
|
||||
$('#mainModal .modal-body').append((response.region.length > 0 ? ', ' : '') + response.region);
|
||||
modalBody.append(`<span class="text-muted">${(response.region.length > 0 ? ', ' : '') + response.region}</span>`);
|
||||
}
|
||||
if (response.country.length > 0) {
|
||||
$('#mainModal .modal-body').append((response.country.length > 0 ? ', ' : '') + response.country);
|
||||
modalBody.append(`<span class="text-muted">${(response.country.length > 0 ? ', ' : '') + response.country}</span>`);
|
||||
}
|
||||
if (response.timezone_gmt.length > 0) {
|
||||
$('#mainModal .modal-body').append((response.timezone_gmt.length > 0 ? ', ' : '') + response.timezone_gmt);
|
||||
modalBody.append(`<br/>Timezone — <span class="text-muted">UTC${response.timezone_gmt}</span>`);
|
||||
}
|
||||
|
||||
$('#mainModal').modal();
|
||||
modalBody.append('</span>');
|
||||
})
|
||||
.fail(function (jqxhr, textStatus, error) {
|
||||
$('#mainModal .modal-title').text("Error");
|
||||
|
Reference in New Issue
Block a user