Skip to content

Commit 2a9eee9

Browse files
feat: Enhance agent status tracking with detailed activity status and update UI representation
1 parent 5908080 commit 2a9eee9

5 files changed

Lines changed: 81 additions & 21 deletions

File tree

src/XtremeIdiots.Portal.Web/Controllers/StatusController.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,14 +139,17 @@ public async Task<IActionResult> AgentStatus(CancellationToken cancellationToken
139139
EventsLastHour = summary?.EventsLastHour ?? 0,
140140
PlayerCount = summary?.PlayerCount ?? 0,
141141
CurrentMap = summary?.CurrentMap ?? gs.LiveMap,
142-
IsAgentActive = summary?.IsAgentActive ?? false
142+
IsAgentActive = summary?.IsAgentActive ?? false,
143+
ActivityStatus = summary?.ActivityStatus ?? AgentActivityStatus.Offline
143144
};
144145
}).ToList();
145146

146147
TrackSuccessTelemetry("AgentStatusRetrieved", nameof(AgentStatus), new Dictionary<string, string>
147148
{
148149
{ "ServerCount", models.Count.ToString() },
149-
{ "ActiveCount", models.Count(m => m.IsAgentActive).ToString() }
150+
{ "ActiveCount", models.Count(m => m.ActivityStatus == AgentActivityStatus.Active).ToString() },
151+
{ "IdleCount", models.Count(m => m.ActivityStatus == AgentActivityStatus.Idle).ToString() },
152+
{ "OfflineCount", models.Count(m => m.ActivityStatus == AgentActivityStatus.Offline).ToString() }
150153
});
151154

152155
Logger.LogInformation("User {UserId} retrieved agent status for {ServerCount} servers",

src/XtremeIdiots.Portal.Web/Services/AgentTelemetryService.cs

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ public class AgentTelemetryService(
1212
ILogger<AgentTelemetryService> logger) : IAgentTelemetryService
1313
{
1414
private const int AgentActiveThresholdMinutes = 5;
15+
private const int AgentIdleThresholdMinutes = 60;
1516

1617
public async Task<AgentServerStatus> GetServerStatusAsync(Guid serverId, CancellationToken ct = default)
1718
{
@@ -62,6 +63,8 @@ public async Task<AgentServerStatus> GetServerStatusAsync(Guid serverId, Cancell
6263
var isActive = lastEvent.HasValue &&
6364
(DateTime.UtcNow - lastEvent.Value).TotalMinutes <= AgentActiveThresholdMinutes;
6465

66+
var activityStatus = ResolveActivityStatus(lastEvent);
67+
6568
status = status with
6669
{
6770
LastEventReceived = lastEvent,
@@ -70,7 +73,8 @@ public async Task<AgentServerStatus> GetServerStatusAsync(Guid serverId, Cancell
7073
ChatMessagesLastHour = chatMessages,
7174
BansDetectedLast24h = bansDetected,
7275
ModerationTriggersLast24h = moderationTriggers,
73-
IsAgentActive = isActive
76+
IsAgentActive = isActive,
77+
ActivityStatus = activityStatus
7478
};
7579
}
7680

@@ -95,16 +99,17 @@ public async Task<IReadOnlyList<AgentServerSummary>> GetAllServersStatusAsync(Ca
9599

96100
var query = new StringBuilder();
97101
query.Append("customEvents");
98-
query.Append(" | where timestamp > ago(1h)");
102+
query.Append(" | where timestamp > ago(2h)");
99103
query.Append(" | extend serverId = tostring(customDimensions.ServerId)");
100104
query.Append(" | where isnotempty(serverId)");
101-
query.Append(" | summarize lastEvent=max(timestamp), eventCount=count(),");
102-
query.Append(" playerConnects=countif(name == 'PlayerConnected'),");
105+
query.Append(" | summarize lastEvent=max(timestamp),");
106+
query.Append(" eventCount=countif(timestamp > ago(1h)),");
107+
query.Append(" playerConnects=countif(name == 'PlayerConnected' and timestamp > ago(1h)),");
103108
query.Append(" lastMap=take_any(tostring(customDimensions.MapName))");
104109
query.Append(" by serverId");
105110
query.Append(" | order by lastEvent desc");
106111

107-
var response = await ExecuteQueryAsync(resourceId, query.ToString(), TimeSpan.FromHours(1), ct).ConfigureAwait(false);
112+
var response = await ExecuteQueryAsync(resourceId, query.ToString(), TimeSpan.FromHours(2), ct).ConfigureAwait(false);
108113

109114
var results = new List<AgentServerSummary>();
110115

@@ -120,14 +125,17 @@ public async Task<IReadOnlyList<AgentServerSummary>> GetAllServersStatusAsync(Ca
120125
var isActive = lastEvent.HasValue &&
121126
(DateTime.UtcNow - lastEvent.Value).TotalMinutes <= AgentActiveThresholdMinutes;
122127

128+
var activityStatus = ResolveActivityStatus(lastEvent);
129+
123130
results.Add(new AgentServerSummary
124131
{
125132
ServerId = serverId,
126133
LastEventReceived = lastEvent,
127134
EventsLastHour = GetIntValue(row, columns, "eventCount"),
128135
PlayerCount = GetIntValue(row, columns, "playerConnects"),
129136
CurrentMap = GetStringValue(row, columns, "lastMap"),
130-
IsAgentActive = isActive
137+
IsAgentActive = isActive,
138+
ActivityStatus = activityStatus
131139
});
132140
}
133141

@@ -148,6 +156,22 @@ private async Task<LogsQueryResult> ExecuteQueryAsync(
148156
return response.Value;
149157
}
150158

159+
private static AgentActivityStatus ResolveActivityStatus(DateTime? lastEvent)
160+
{
161+
if (!lastEvent.HasValue)
162+
return AgentActivityStatus.Offline;
163+
164+
var minutesAgo = (DateTime.UtcNow - lastEvent.Value).TotalMinutes;
165+
166+
if (minutesAgo <= AgentActiveThresholdMinutes)
167+
return AgentActivityStatus.Active;
168+
169+
if (minutesAgo <= AgentIdleThresholdMinutes)
170+
return AgentActivityStatus.Idle;
171+
172+
return AgentActivityStatus.Offline;
173+
}
174+
151175
private string GetAppInsightsResourceId()
152176
{
153177
var resourceId = configuration["ApplicationInsights:ResourceId"];

src/XtremeIdiots.Portal.Web/Services/IAgentTelemetryService.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,20 @@
11
namespace XtremeIdiots.Portal.Web.Services;
22

3+
/// <summary>
4+
/// Tri-state activity status for a game server agent.
5+
/// </summary>
6+
public enum AgentActivityStatus
7+
{
8+
/// <summary>Agent is running and processing events (heartbeat within last 5 minutes).</summary>
9+
Active,
10+
11+
/// <summary>Agent is running but idle — no events recently, but heard from within the last hour.</summary>
12+
Idle,
13+
14+
/// <summary>Agent has not reported in over an hour — may be down.</summary>
15+
Offline
16+
}
17+
318
public record AgentServerStatus
419
{
520
public DateTime? LastEventReceived { get; init; }
@@ -11,6 +26,7 @@ public record AgentServerStatus
1126
public int BansDetectedLast24h { get; init; }
1227
public int ModerationTriggersLast24h { get; init; }
1328
public bool IsAgentActive { get; init; }
29+
public AgentActivityStatus ActivityStatus { get; init; }
1430
}
1531

1632
public record AgentServerSummary
@@ -23,6 +39,7 @@ public record AgentServerSummary
2339
public int PlayerCount { get; init; }
2440
public string? CurrentMap { get; init; }
2541
public bool IsAgentActive { get; init; }
42+
public AgentActivityStatus ActivityStatus { get; init; }
2643
}
2744

2845
public interface IAgentTelemetryService

src/XtremeIdiots.Portal.Web/Views/Servers/ServerInfo.cshtml

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
@using XtremeIdiots.Portal.Web.Extensions
2+
@using XtremeIdiots.Portal.Web.Services
23
@using XtremeIdiots.Portal.Web.ViewModels
34
@inject IConfiguration Configuration
45
@model ServersGameServerViewModel
@@ -147,13 +148,17 @@
147148
<div class="ibox">
148149
<div class="ibox-title">
149150
<h5><i class="fa-solid fa-robot"></i> Agent Status</h5>
150-
@if (agentStatus.IsAgentActive)
151+
@switch (agentStatus.ActivityStatus)
151152
{
152-
<span class="badge bg-success float-end">Active</span>
153-
}
154-
else
155-
{
156-
<span class="badge bg-danger float-end">Inactive</span>
153+
case AgentActivityStatus.Active:
154+
<span class="badge bg-success float-end">Active</span>
155+
break;
156+
case AgentActivityStatus.Idle:
157+
<span class="badge bg-info float-end">Idle</span>
158+
break;
159+
case AgentActivityStatus.Offline:
160+
<span class="badge bg-danger float-end">Offline</span>
161+
break;
157162
}
158163
</div>
159164
<div class="ibox-content">

src/XtremeIdiots.Portal.Web/Views/Status/AgentStatus.cshtml

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,17 @@
2121
<h5>Agent Status Overview</h5>
2222
<span class="float-end">
2323
@{
24-
var activeCount = Model.Count(m => m.IsAgentActive);
24+
var activeCount = Model.Count(m => m.ActivityStatus == AgentActivityStatus.Active);
25+
var idleCount = Model.Count(m => m.ActivityStatus == AgentActivityStatus.Idle);
26+
var offlineCount = Model.Count(m => m.ActivityStatus == AgentActivityStatus.Offline);
2527
var totalCount = Model.Count;
2628
}
2729
<span class="badge bg-success">@activeCount Active</span>
30+
<span class="badge bg-info">@idleCount Idle</span>
31+
@if (offlineCount > 0)
32+
{
33+
<span class="badge bg-danger">@offlineCount Offline</span>
34+
}
2835
<span class="badge bg-secondary">@totalCount Total</span>
2936
</span>
3037
</div>
@@ -61,13 +68,17 @@
6168
</td>
6269
<td>@server.GameType</td>
6370
<td>
64-
@if (server.IsAgentActive)
65-
{
66-
<span class="badge bg-success">Active</span>
67-
}
68-
else
71+
@switch (server.ActivityStatus)
6972
{
70-
<span class="badge bg-danger">Inactive</span>
73+
case AgentActivityStatus.Active:
74+
<span class="badge bg-success">Active</span>
75+
break;
76+
case AgentActivityStatus.Idle:
77+
<span class="badge bg-info">Idle</span>
78+
break;
79+
case AgentActivityStatus.Offline:
80+
<span class="badge bg-danger">Offline</span>
81+
break;
7182
}
7283
</td>
7384
<td>

0 commit comments

Comments
 (0)