implement client server connection tracking persistence

This commit is contained in:
RaidMax 2021-08-31 18:21:40 -05:00
parent eff8a29a39
commit c5f9a68102
37 changed files with 4961 additions and 22 deletions

View File

@ -13,6 +13,7 @@ namespace IW4MAdmin.Application
{
private readonly EventLog _eventLog;
private readonly ILogger _logger;
private readonly IEventPublisher _eventPublisher;
private static readonly GameEvent.EventType[] overrideEvents = new[]
{
GameEvent.EventType.Connect,
@ -21,10 +22,11 @@ namespace IW4MAdmin.Application
GameEvent.EventType.Stop
};
public GameEventHandler(ILogger<GameEventHandler> logger)
public GameEventHandler(ILogger<GameEventHandler> logger, IEventPublisher eventPublisher)
{
_eventLog = new EventLog();
_logger = logger;
_eventPublisher = eventPublisher;
}
public void HandleEvent(IManager manager, GameEvent gameEvent)
@ -32,6 +34,7 @@ namespace IW4MAdmin.Application
if (manager.IsRunning || overrideEvents.Contains(gameEvent.Type))
{
EventApi.OnGameEvent(gameEvent);
_eventPublisher.Publish(gameEvent);
Task.Factory.StartNew(() => manager.ExecuteEvent(gameEvent));
}
else

View File

@ -401,6 +401,7 @@ namespace IW4MAdmin.Application
.AddSingleton<IResourceQueryHelper<ClientPaginationRequest, UpdatedAliasResponse>,
UpdatedAliasResourceQueryHelper>()
.AddSingleton<IResourceQueryHelper<ChatSearchQuery, MessageResponse>, ChatResourceQueryHelper>()
.AddSingleton<IResourceQueryHelper<ClientPaginationRequest, ConnectionHistoryResponse>, ConnectionsResourceQueryHelper>()
.AddTransient<IParserPatternMatcher, ParserPatternMatcher>()
.AddSingleton<IRemoteAssemblyHandler, RemoteAssemblyHandler>()
.AddSingleton<IMasterCommunication, MasterCommunication>()
@ -415,6 +416,7 @@ namespace IW4MAdmin.Application
.AddSingleton(typeof(IDataValueCache<,>), typeof(DataValueCache<,>))
.AddSingleton<IServerDataViewer, ServerDataViewer>()
.AddSingleton<IServerDataCollector, ServerDataCollector>()
.AddSingleton<IEventPublisher, EventPublisher>()
.AddSingleton(translationLookup)
.AddDatabaseContextOptions(appConfig);

View File

@ -0,0 +1,60 @@
using System.Linq;
using System.Threading.Tasks;
using Data.Abstractions;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using SharedLibraryCore.Dtos.Meta.Responses;
using SharedLibraryCore.Helpers;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.QueryHelper;
using ILogger = Microsoft.Extensions.Logging.ILogger;
namespace IW4MAdmin.Application.Meta
{
public class
ConnectionsResourceQueryHelper : IResourceQueryHelper<ClientPaginationRequest, ConnectionHistoryResponse>
{
private readonly ILogger _logger;
private readonly IDatabaseContextFactory _contextFactory;
public ConnectionsResourceQueryHelper(ILogger<ConnectionsResourceQueryHelper> logger,
IDatabaseContextFactory contextFactory)
{
_contextFactory = contextFactory;
_logger = logger;
}
public async Task<ResourceQueryHelperResult<ConnectionHistoryResponse>> QueryResource(
ClientPaginationRequest query)
{
_logger.LogDebug("{Class} {@Request}", nameof(ConnectionsResourceQueryHelper), query);
await using var context = _contextFactory.CreateContext(enableTracking: false);
var iqConnections = context.ConnectionHistory.AsNoTracking()
.Where(history => query.ClientId == history.ClientId)
.Where(history => history.CreatedDateTime < query.Before)
.OrderByDescending(history => history.CreatedDateTime);
var connections = await iqConnections.Select(history => new ConnectionHistoryResponse
{
MetaId = history.ClientConnectionId,
ClientId = history.ClientId,
Type = MetaType.ConnectionHistory,
ShouldDisplay = true,
When = history.CreatedDateTime,
ServerName = history.Server.HostName,
ConnectionType = history.ConnectionType
})
.ToListAsync();
_logger.LogDebug("{Class} retrieved {Number} items", nameof(ConnectionsResourceQueryHelper),
connections.Count);
return new ResourceQueryHelperResult<ConnectionHistoryResponse>
{
Results = connections
};
}
}
}

View File

@ -20,11 +20,14 @@ namespace IW4MAdmin.Application.Meta
private readonly IResourceQueryHelper<ClientPaginationRequest, ReceivedPenaltyResponse> _receivedPenaltyHelper;
private readonly IResourceQueryHelper<ClientPaginationRequest, AdministeredPenaltyResponse> _administeredPenaltyHelper;
private readonly IResourceQueryHelper<ClientPaginationRequest, UpdatedAliasResponse> _updatedAliasHelper;
private readonly IResourceQueryHelper<ClientPaginationRequest, ConnectionHistoryResponse>
_connectionHistoryHelper;
public MetaRegistration(ILogger<MetaRegistration> logger, IMetaService metaService, ITranslationLookup transLookup, IEntityService<EFClient> clientEntityService,
IResourceQueryHelper<ClientPaginationRequest, ReceivedPenaltyResponse> receivedPenaltyHelper,
IResourceQueryHelper<ClientPaginationRequest, AdministeredPenaltyResponse> administeredPenaltyHelper,
IResourceQueryHelper<ClientPaginationRequest, UpdatedAliasResponse> updatedAliasHelper)
IResourceQueryHelper<ClientPaginationRequest, UpdatedAliasResponse> updatedAliasHelper,
IResourceQueryHelper<ClientPaginationRequest, ConnectionHistoryResponse> connectionHistoryHelper)
{
_logger = logger;
_transLookup = transLookup;
@ -33,6 +36,7 @@ namespace IW4MAdmin.Application.Meta
_receivedPenaltyHelper = receivedPenaltyHelper;
_administeredPenaltyHelper = administeredPenaltyHelper;
_updatedAliasHelper = updatedAliasHelper;
_connectionHistoryHelper = connectionHistoryHelper;
}
public void Register()
@ -41,6 +45,7 @@ namespace IW4MAdmin.Application.Meta
_metaService.AddRuntimeMeta<ClientPaginationRequest, ReceivedPenaltyResponse>(MetaType.ReceivedPenalty, GetReceivedPenaltiesMeta);
_metaService.AddRuntimeMeta<ClientPaginationRequest, AdministeredPenaltyResponse>(MetaType.Penalized, GetAdministeredPenaltiesMeta);
_metaService.AddRuntimeMeta<ClientPaginationRequest, UpdatedAliasResponse>(MetaType.AliasUpdate, GetUpdatedAliasMeta);
_metaService.AddRuntimeMeta<ClientPaginationRequest, ConnectionHistoryResponse>(MetaType.ConnectionHistory, GetConnectionHistoryMeta);
}
private async Task<IEnumerable<InformationResponse>> GetProfileMeta(ClientPaginationRequest request)
@ -163,5 +168,11 @@ namespace IW4MAdmin.Application.Meta
var aliases = await _updatedAliasHelper.QueryResource(request);
return aliases.Results;
}
private async Task<IEnumerable<ConnectionHistoryResponse>> GetConnectionHistoryMeta(ClientPaginationRequest request)
{
var connections = await _connectionHistoryHelper.QueryResource(request);
return connections.Results;
}
}
}

View File

@ -0,0 +1,44 @@
using System;
using Microsoft.Extensions.Logging;
using SharedLibraryCore;
using SharedLibraryCore.Interfaces;
using ILogger = Microsoft.Extensions.Logging.ILogger;
namespace IW4MAdmin.Application.Misc
{
public class EventPublisher : IEventPublisher
{
public event EventHandler<GameEvent> OnClientDisconnect;
public event EventHandler<GameEvent> OnClientConnect;
private readonly ILogger _logger;
public EventPublisher(ILogger<EventPublisher> logger)
{
_logger = logger;
}
public void Publish(GameEvent gameEvent)
{
_logger.LogDebug("Handling publishing event of type {EventType}", gameEvent.Type);
try
{
if (gameEvent.Type == GameEvent.EventType.Connect)
{
OnClientConnect?.Invoke(this, gameEvent);
}
if (gameEvent.Type == GameEvent.EventType.Disconnect)
{
OnClientDisconnect?.Invoke(this, gameEvent);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Could not publish event of type {EventType}", gameEvent.Type);
}
}
}
}

View File

@ -5,6 +5,7 @@ using System.Threading;
using System.Threading.Tasks;
using Data.Abstractions;
using Data.Models;
using Data.Models.Client;
using Data.Models.Client.Stats.Reference;
using Data.Models.Server;
using Microsoft.EntityFrameworkCore;
@ -23,17 +24,28 @@ namespace IW4MAdmin.Application.Misc
private readonly IManager _manager;
private readonly IDatabaseContextFactory _contextFactory;
private readonly ApplicationConfiguration _appConfig;
private readonly IEventPublisher _eventPublisher;
private bool _inProgress;
private TimeSpan _period;
public ServerDataCollector(ILogger<ServerDataCollector> logger, ApplicationConfiguration appConfig,
IManager manager, IDatabaseContextFactory contextFactory)
IManager manager, IDatabaseContextFactory contextFactory, IEventPublisher eventPublisher)
{
_logger = logger;
_appConfig = appConfig;
_manager = manager;
_contextFactory = contextFactory;
_eventPublisher = eventPublisher;
_eventPublisher.OnClientConnect += SaveConnectionInfo;
_eventPublisher.OnClientDisconnect += SaveConnectionInfo;
}
~ServerDataCollector()
{
_eventPublisher.OnClientConnect -= SaveConnectionInfo;
_eventPublisher.OnClientDisconnect -= SaveConnectionInfo;
}
public async Task BeginCollectionAsync(TimeSpan? period = null, CancellationToken cancellationToken = default)
@ -117,5 +129,19 @@ namespace IW4MAdmin.Application.Misc
context.ServerSnapshots.AddRange(snapshots);
await context.SaveChangesAsync(token);
}
private void SaveConnectionInfo(object sender, GameEvent gameEvent)
{
using var context = _contextFactory.CreateContext(enableTracking: false);
context.ConnectionHistory.Add(new EFClientConnectionHistory
{
ClientId = gameEvent.Origin.ClientId,
ServerId = gameEvent.Owner.GetIdForServer().Result,
ConnectionType = gameEvent.Type == GameEvent.EventType.Connect
? Reference.ConnectionType.Connect
: Reference.ConnectionType.Disconnect
});
context.SaveChanges();
}
}
}

View File

@ -43,6 +43,7 @@ namespace Data.Context
public DbSet<EFInboxMessage> InboxMessages { get; set; }
public DbSet<EFServerSnapshot> ServerSnapshots { get;set; }
public DbSet<EFClientConnectionHistory> ConnectionHistory { get; set; }
#endregion
@ -129,12 +130,15 @@ namespace Data.Context
.OnDelete(DeleteBehavior.SetNull);
});
modelBuilder.Entity<EFClientConnectionHistory>(ent => ent.HasIndex(history => history.CreatedDateTime));
// force full name for database conversion
modelBuilder.Entity<EFClient>().ToTable("EFClients");
modelBuilder.Entity<EFAlias>().ToTable("EFAlias");
modelBuilder.Entity<EFAliasLink>().ToTable("EFAliasLinks");
modelBuilder.Entity<EFPenalty>().ToTable("EFPenalties");
modelBuilder.Entity<EFServerSnapshot>().ToTable(nameof(EFServerSnapshot));
modelBuilder.Entity<EFClientConnectionHistory>().ToTable(nameof(EFClientConnectionHistory));
Models.Configuration.StatsModelConfiguration.Configure(modelBuilder);

View File

@ -8,7 +8,7 @@
<PackageId>RaidMax.IW4MAdmin.Data</PackageId>
<Title>RaidMax.IW4MAdmin.Data</Title>
<Authors />
<PackageVersion>1.0.6</PackageVersion>
<PackageVersion>1.0.7</PackageVersion>
</PropertyGroup>
<ItemGroup>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,62 @@
using System;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
namespace Data.Migrations.MySql
{
public partial class AddEFClientConnectionHistory : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "EFClientConnectionHistory",
columns: table => new
{
ClientConnectionId = table.Column<long>(nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
CreatedDateTime = table.Column<DateTime>(nullable: false),
UpdatedDateTime = table.Column<DateTime>(nullable: true),
ClientId = table.Column<int>(nullable: false),
ServerId = table.Column<long>(nullable: false),
ConnectionType = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_EFClientConnectionHistory", x => x.ClientConnectionId);
table.ForeignKey(
name: "FK_EFClientConnectionHistory_EFClients_ClientId",
column: x => x.ClientId,
principalTable: "EFClients",
principalColumn: "ClientId",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_EFClientConnectionHistory_EFServers_ServerId",
column: x => x.ServerId,
principalTable: "EFServers",
principalColumn: "ServerId",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_EFClientConnectionHistory_ClientId",
table: "EFClientConnectionHistory",
column: "ClientId");
migrationBuilder.CreateIndex(
name: "IX_EFClientConnectionHistory_CreatedDateTime",
table: "EFClientConnectionHistory",
column: "CreatedDateTime");
migrationBuilder.CreateIndex(
name: "IX_EFClientConnectionHistory_ServerId",
table: "EFClientConnectionHistory",
column: "ServerId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "EFClientConnectionHistory");
}
}
}

View File

@ -95,6 +95,38 @@ namespace Data.Migrations.MySql
b.ToTable("EFClients");
});
modelBuilder.Entity("Data.Models.Client.EFClientConnectionHistory", b =>
{
b.Property<long>("ClientConnectionId")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
b.Property<int>("ClientId")
.HasColumnType("int");
b.Property<int>("ConnectionType")
.HasColumnType("int");
b.Property<DateTime>("CreatedDateTime")
.HasColumnType("datetime(6)");
b.Property<long>("ServerId")
.HasColumnType("bigint");
b.Property<DateTime?>("UpdatedDateTime")
.HasColumnType("datetime(6)");
b.HasKey("ClientConnectionId");
b.HasIndex("ClientId");
b.HasIndex("CreatedDateTime");
b.HasIndex("ServerId");
b.ToTable("EFClientConnectionHistory");
});
modelBuilder.Entity("Data.Models.Client.EFClientKill", b =>
{
b.Property<long>("KillId")
@ -1109,6 +1141,21 @@ namespace Data.Migrations.MySql
.IsRequired();
});
modelBuilder.Entity("Data.Models.Client.EFClientConnectionHistory", b =>
{
b.HasOne("Data.Models.Client.EFClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Data.Models.Server.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Data.Models.Client.EFClientKill", b =>
{
b.HasOne("Data.Models.Client.EFClient", "Attacker")

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,62 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace Data.Migrations.Postgresql
{
public partial class AddEFClientConnectionHistory : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "EFClientConnectionHistory",
columns: table => new
{
ClientConnectionId = table.Column<long>(nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn),
CreatedDateTime = table.Column<DateTime>(nullable: false),
UpdatedDateTime = table.Column<DateTime>(nullable: true),
ClientId = table.Column<int>(nullable: false),
ServerId = table.Column<long>(nullable: false),
ConnectionType = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_EFClientConnectionHistory", x => x.ClientConnectionId);
table.ForeignKey(
name: "FK_EFClientConnectionHistory_EFClients_ClientId",
column: x => x.ClientId,
principalTable: "EFClients",
principalColumn: "ClientId",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_EFClientConnectionHistory_EFServers_ServerId",
column: x => x.ServerId,
principalTable: "EFServers",
principalColumn: "ServerId",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_EFClientConnectionHistory_ClientId",
table: "EFClientConnectionHistory",
column: "ClientId");
migrationBuilder.CreateIndex(
name: "IX_EFClientConnectionHistory_CreatedDateTime",
table: "EFClientConnectionHistory",
column: "CreatedDateTime");
migrationBuilder.CreateIndex(
name: "IX_EFClientConnectionHistory_ServerId",
table: "EFClientConnectionHistory",
column: "ServerId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "EFClientConnectionHistory");
}
}
}

View File

@ -99,6 +99,39 @@ namespace Data.Migrations.Postgresql
b.ToTable("EFClients");
});
modelBuilder.Entity("Data.Models.Client.EFClientConnectionHistory", b =>
{
b.Property<long>("ClientConnectionId")
.ValueGeneratedOnAdd()
.HasColumnType("bigint")
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn);
b.Property<int>("ClientId")
.HasColumnType("integer");
b.Property<int>("ConnectionType")
.HasColumnType("integer");
b.Property<DateTime>("CreatedDateTime")
.HasColumnType("timestamp without time zone");
b.Property<long>("ServerId")
.HasColumnType("bigint");
b.Property<DateTime?>("UpdatedDateTime")
.HasColumnType("timestamp without time zone");
b.HasKey("ClientConnectionId");
b.HasIndex("ClientId");
b.HasIndex("CreatedDateTime");
b.HasIndex("ServerId");
b.ToTable("EFClientConnectionHistory");
});
modelBuilder.Entity("Data.Models.Client.EFClientKill", b =>
{
b.Property<long>("KillId")
@ -1136,6 +1169,21 @@ namespace Data.Migrations.Postgresql
.IsRequired();
});
modelBuilder.Entity("Data.Models.Client.EFClientConnectionHistory", b =>
{
b.HasOne("Data.Models.Client.EFClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Data.Models.Server.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Data.Models.Client.EFClientKill", b =>
{
b.HasOne("Data.Models.Client.EFClient", "Attacker")

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,61 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
namespace Data.Migrations.Sqlite
{
public partial class AddEFClientConnectionHistory : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "EFClientConnectionHistory",
columns: table => new
{
ClientConnectionId = table.Column<long>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
CreatedDateTime = table.Column<DateTime>(nullable: false),
UpdatedDateTime = table.Column<DateTime>(nullable: true),
ClientId = table.Column<int>(nullable: false),
ServerId = table.Column<long>(nullable: false),
ConnectionType = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_EFClientConnectionHistory", x => x.ClientConnectionId);
table.ForeignKey(
name: "FK_EFClientConnectionHistory_EFClients_ClientId",
column: x => x.ClientId,
principalTable: "EFClients",
principalColumn: "ClientId",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_EFClientConnectionHistory_EFServers_ServerId",
column: x => x.ServerId,
principalTable: "EFServers",
principalColumn: "ServerId",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_EFClientConnectionHistory_ClientId",
table: "EFClientConnectionHistory",
column: "ClientId");
migrationBuilder.CreateIndex(
name: "IX_EFClientConnectionHistory_CreatedDateTime",
table: "EFClientConnectionHistory",
column: "CreatedDateTime");
migrationBuilder.CreateIndex(
name: "IX_EFClientConnectionHistory_ServerId",
table: "EFClientConnectionHistory",
column: "ServerId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "EFClientConnectionHistory");
}
}
}

View File

@ -94,6 +94,38 @@ namespace Data.Migrations.Sqlite
b.ToTable("EFClients");
});
modelBuilder.Entity("Data.Models.Client.EFClientConnectionHistory", b =>
{
b.Property<long>("ClientConnectionId")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("ClientId")
.HasColumnType("INTEGER");
b.Property<int>("ConnectionType")
.HasColumnType("INTEGER");
b.Property<DateTime>("CreatedDateTime")
.HasColumnType("TEXT");
b.Property<long>("ServerId")
.HasColumnType("INTEGER");
b.Property<DateTime?>("UpdatedDateTime")
.HasColumnType("TEXT");
b.HasKey("ClientConnectionId");
b.HasIndex("ClientId");
b.HasIndex("CreatedDateTime");
b.HasIndex("ServerId");
b.ToTable("EFClientConnectionHistory");
});
modelBuilder.Entity("Data.Models.Client.EFClientKill", b =>
{
b.Property<long>("KillId")
@ -1108,6 +1140,21 @@ namespace Data.Migrations.Sqlite
.IsRequired();
});
modelBuilder.Entity("Data.Models.Client.EFClientConnectionHistory", b =>
{
b.HasOne("Data.Models.Client.EFClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Data.Models.Server.EFServer", "Server")
.WithMany()
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Data.Models.Client.EFClientKill", b =>
{
b.HasOne("Data.Models.Client.EFClient", "Attacker")

View File

@ -0,0 +1,25 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Data.Models.Server;
using Stats.Models;
namespace Data.Models.Client
{
public class EFClientConnectionHistory : AuditFields
{
[Key]
public long ClientConnectionId { get; set; }
public int ClientId { get; set; }
[ForeignKey(nameof(ClientId))]
public EFClient Client { get;set; }
public long ServerId { get; set; }
[ForeignKey(nameof(ServerId))]
public EFServer Server { get;set; }
public Reference.ConnectionType ConnectionType { get; set; }
}
}

View File

@ -17,5 +17,11 @@
SHG1 = 9,
CSGO = 10
}
public enum ConnectionType
{
Connect,
Disconnect
}
}
}

View File

@ -10,7 +10,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.SyndicationFeed.ReaderWriter" Version="1.0.2" />
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.8.29.1" PrivateAssets="All" />
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.8.31.1" PrivateAssets="All" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">

View File

@ -10,7 +10,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.8.29.1" PrivateAssets="All" />
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.8.31.1" PrivateAssets="All" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">

View File

@ -23,7 +23,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.8.29.1" PrivateAssets="All" />
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.8.31.1" PrivateAssets="All" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">

View File

@ -19,7 +19,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.8.29.1" PrivateAssets="All" />
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.8.31.1" PrivateAssets="All" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">

View File

@ -16,7 +16,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.8.29.1" PrivateAssets="All" />
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.8.31.1" PrivateAssets="All" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">

View File

@ -17,7 +17,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.8.29.1" PrivateAssets="All" />
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.8.31.1" PrivateAssets="All" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">

View File

@ -20,7 +20,7 @@
</Target>
<ItemGroup>
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.8.29.1" PrivateAssets="All" />
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.8.31.1" PrivateAssets="All" />
</ItemGroup>
</Project>

View File

@ -1,13 +1,11 @@
using SharedLibraryCore.Interfaces;
using System;
using System.Collections.Generic;
using System.Text;
namespace SharedLibraryCore.Dtos.Meta.Responses
{
public class BaseMetaResponse : IClientMeta, IClientMetaResponse
{
public int MetaId { get; set; }
public long MetaId { get; set; }
public int ClientId { get; set; }
public MetaType Type { get; set; }
public DateTime When { get; set; }

View File

@ -0,0 +1,10 @@
using Data.Models;
namespace SharedLibraryCore.Dtos.Meta.Responses
{
public class ConnectionHistoryResponse : BaseMetaResponse
{
public string ServerName { get; set; }
public Reference.ConnectionType ConnectionType { get; set; }
}
}

View File

@ -26,6 +26,7 @@ namespace SharedLibraryCore.Interfaces
ChatMessage,
Penalized,
ReceivedPenalty,
QuickMessage
QuickMessage,
ConnectionHistory
}
}

View File

@ -7,6 +7,6 @@ namespace SharedLibraryCore.Interfaces
public interface IClientMetaResponse
{
int ClientId { get;}
int MetaId { get; }
long MetaId { get; }
}
}

View File

@ -0,0 +1,12 @@
using System;
namespace SharedLibraryCore.Interfaces
{
public interface IEventPublisher
{
event EventHandler<GameEvent> OnClientDisconnect;
event EventHandler<GameEvent> OnClientConnect;
void Publish(GameEvent gameEvent);
}
}

View File

@ -4,7 +4,7 @@
<OutputType>Library</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<PackageId>RaidMax.IW4MAdmin.SharedLibraryCore</PackageId>
<Version>2021.8.29.1</Version>
<Version>2021.8.31.1</Version>
<Authors>RaidMax</Authors>
<Company>Forever None</Company>
<Configurations>Debug;Release;Prerelease</Configurations>
@ -19,7 +19,7 @@
<IsPackable>true</IsPackable>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<Description>Shared Library for IW4MAdmin</Description>
<PackageVersion>2021.8.29.1</PackageVersion>
<PackageVersion>2021.8.31.1</PackageVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Prerelease|AnyCPU'">
@ -44,7 +44,7 @@
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="3.1.10" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="3.1.10" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="RaidMax.IW4MAdmin.Data" Version="1.0.6" />
<PackageReference Include="RaidMax.IW4MAdmin.Data" Version="1.0.7" />
<PackageReference Include="Serilog.AspNetCore" Version="3.4.0" />
<PackageReference Include="SimpleCrypto.NetCore" Version="1.0.0" />
</ItemGroup>

View File

@ -31,7 +31,7 @@ namespace WebfrontCore.Controllers
ID = s.EndPoint,
Port = s.Port,
Map = s.CurrentMap.Alias,
ClientCount = s.ClientNum,
ClientCount = s.Clients.Count(client => client != null),
MaxClients = s.MaxClients,
GameType = s.Gametype,
Players = s.GetClientsAsList()

View File

@ -66,7 +66,8 @@ namespace WebfrontCore.ViewComponents
case MetaType.ReceivedPenalty:
meta = await metaService.GetRuntimeMeta<ReceivedPenaltyResponse>(request, metaType.Value);
break;
default:
case MetaType.ConnectionHistory:
meta = await metaService.GetRuntimeMeta<ConnectionHistoryResponse>(request, metaType.Value);
break;
}
}

View File

@ -59,7 +59,7 @@ namespace WebfrontCore.ViewComponents
ID = server.EndPoint,
Port = server.Port,
Map = server.CurrentMap.Alias,
ClientCount = server.ClientNum,
ClientCount = server.Clients.Count(client => client != null),
MaxClients = server.MaxClients,
GameType = server.Gametype,
PlayerHistory = server.ClientHistory.ToArray(),

View File

@ -0,0 +1,28 @@
@using Data.Models
@model SharedLibraryCore.Dtos.Meta.Responses.ConnectionHistoryResponse
@{
var localizationKey = $"WEBFRONT_CLIENT_META_CONNECTION_{Model.ConnectionType.ToString().ToUpper()}";
}
@foreach (var token in Utilities.SplitTranslationTokens(localizationKey))
{
if (token.IsInterpolation)
{
switch (token.MatchValue)
{
case "action":
<span class="@(Model.ConnectionType == Reference.ConnectionType.Connect ? "text-light-green" : "text-warning")">@token.TranslationValue</span>
break;
case "server":
<span class="text-white">
<color-code value="@Model.ServerName" allow="@ViewBag.EnableColorCodes"></color-code>
</span>
break;
}
}
else
{
<span class="text-muted">@token.MatchValue</span>
}
}

View File

@ -441,3 +441,7 @@ div.card {
.cursor-help {
cursor: help;
}
.text-light-green {
color: #88aa82;
}