Skip to content

Commit 0bbdd16

Browse files
feat: Enhance Manage Maps view with active rotation details and improved map display
1 parent a379185 commit 0bbdd16

3 files changed

Lines changed: 170 additions & 95 deletions

File tree

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

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,28 @@ public async Task<IActionResult> Manage(Guid id, CancellationToken cancellationT
7979
}
8080
}
8181

82+
// Also fetch map DTOs for maps in the active rotation (they may not be in the RCON set)
83+
var allMaps = mapsCollectionApiResponse.Result?.Data?.Items?.ToList() ?? [];
84+
var existingMapIds = allMaps.Select(m => m.MapId).ToHashSet();
85+
var activeAssignment = assignments.FirstOrDefault(a => a.ActivationState == ActivationState.Active);
86+
if (activeAssignment != null && rotations.TryGetValue(activeAssignment.MapRotationId, out var activeRotation))
87+
{
88+
var missingMapIds = (activeRotation.MapRotationMaps ?? [])
89+
.Select(m => m.MapId)
90+
.Where(id => !existingMapIds.Contains(id))
91+
.ToList();
92+
93+
foreach (var mapId in missingMapIds)
94+
{
95+
var mapResponse = await repositoryApiClient.Maps.V1.GetMap(mapId, cancellationToken).ConfigureAwait(false);
96+
if (mapResponse.IsSuccess && mapResponse.Result?.Data != null)
97+
allMaps.Add(mapResponse.Result.Data);
98+
}
99+
}
100+
82101
var viewModel = new ManageMapsViewModel(gameServerData)
83102
{
84-
Maps = mapsCollectionApiResponse.Result?.Data?.Items?.ToList() ?? [],
103+
Maps = allMaps,
85104
ServerMaps = getLoadedServerMapsFromHostResult.Result?.Data?.Items?.ToList() ?? [],
86105
RconMaps = getServerMapsResult.Result?.Data?.Items?.ToList() ?? [],
87106
RotationAssignments = assignments,

src/XtremeIdiots.Portal.Web/ViewModels/ManageMapsViewModel.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using XtremeIdiots.Portal.Integrations.Servers.Abstractions.Models.V1.Maps;
22
using XtremeIdiots.Portal.Integrations.Servers.Abstractions.Models.V1.Rcon;
3+
using XtremeIdiots.Portal.Repository.Abstractions.Constants.V1;
34
using XtremeIdiots.Portal.Repository.Abstractions.Models.V1.GameServers;
45
using XtremeIdiots.Portal.Repository.Abstractions.Models.V1.MapRotations;
56
using XtremeIdiots.Portal.Repository.Abstractions.Models.V1.Maps;
@@ -14,4 +15,25 @@ public class ManageMapsViewModel(GameServerDto gameServer)
1415
public List<RconMapDto> RconMaps { get; set; } = [];
1516
public List<MapRotationServerAssignmentDto> RotationAssignments { get; set; } = [];
1617
public Dictionary<Guid, MapRotationDto> Rotations { get; set; } = [];
18+
19+
/// <summary>
20+
/// The currently active portal-managed rotation assignment (if any).
21+
/// </summary>
22+
public MapRotationServerAssignmentDto? ActiveAssignment => RotationAssignments
23+
.FirstOrDefault(a => a.ActivationState == ActivationState.Active);
24+
25+
/// <summary>
26+
/// The rotation DTO for the active assignment (if any).
27+
/// </summary>
28+
public MapRotationDto? ActiveRotation => ActiveAssignment != null && Rotations.TryGetValue(ActiveAssignment.MapRotationId, out var r) ? r : null;
29+
30+
/// <summary>
31+
/// All map names in the active rotation (portal-managed), used for "In Rotation" checks.
32+
/// </summary>
33+
public HashSet<string> ActiveRotationMapNames => ActiveRotation?.MapRotationMaps?
34+
.Select(m => Maps.FirstOrDefault(map => map.MapId == m.MapId)?.MapName)
35+
.Where(n => n != null)
36+
.Select(n => n!)
37+
.ToHashSet(StringComparer.OrdinalIgnoreCase)
38+
?? [];
1739
}

src/XtremeIdiots.Portal.Web/Views/MapManager/Manage.cshtml

Lines changed: 128 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -193,96 +193,128 @@
193193

194194
<div class="ibox">
195195
<div class="ibox-title">
196-
<h5>Current Map Rotation - sv_mapRotation</h5>
196+
@if (Model.ActiveRotation != null)
197+
{
198+
<h5>Active Rotation — <a asp-controller="MapRotations" asp-action="Details" asp-route-id="@Model.ActiveRotation.MapRotationId">@Model.ActiveRotation.Title</a></h5>
199+
}
200+
else
201+
{
202+
<h5>Current Map Rotationsv_mapRotation (RCON)</h5>
203+
}
197204
</div>
198205

199206
<div class="ibox-content">
200-
<div class="table-responsive">
201-
<table id="mapRotationTable" class="table table-striped table-hover w-100">
202-
<thead>
203-
<tr>
204-
<th>
205-
@Html.Label("Name")
206-
</th>
207-
<th>
208-
@Html.Label("Game Type")
209-
</th>
210-
<th>
211-
@Html.Label("Files")
212-
</th>
213-
<th>
214-
@Html.Label("Remote Status")
215-
</th>
216-
<th>
217-
@Html.Label("Popularity")
218-
</th>
219-
<th>
220-
</th>
221-
</tr>
222-
</thead>
223-
<tbody>
224-
@foreach (var item in Model.RconMaps.OrderBy(x => x.MapName))
225-
{
226-
var map = Model.Maps.FirstOrDefault(x => x.MapName == item.MapName);
227-
228-
<tr>
229-
<td>
230-
@Html.DisplayFor(modelItem => item.MapName)
231-
</td>
232-
<td>
233-
@Html.DisplayFor(modelItem => item.GameType)
234-
</td>
235-
<td>
236-
@{
237-
if (map != null)
238-
{
239-
foreach (var mapFile in map.MapFiles)
240-
{
241-
<li>
242-
<a href="@mapFile.Url">@mapFile.FileName</a>
243-
</li>
244-
}
245-
}
246-
else
247-
{
248-
<strong>No files found on redirect</strong>
249-
}
250-
}
251-
</td>
252-
<td>
253-
@{
254-
var remoteMatch = Model.ServerMaps.FirstOrDefault(x => x.Name == item.MapName);
255-
if (remoteMatch != null)
256-
{
257-
<span class="badge bg-success">On Host</span>
258-
}
259-
else
260-
{
261-
<span class="badge bg-danger">Not On Host</span>
262-
}
207+
@if (Model.ActiveRotation != null)
208+
{
209+
<div class="mb-2">
210+
<span class="badge bg-info">@Model.ActiveRotation.GameMode</span>
211+
<span class="badge bg-success">Active</span>
212+
<small class="text-muted ms-2">
213+
<code>@Model.ActiveAssignment?.ConfigVariableName</code>
214+
@if (!string.IsNullOrEmpty(Model.ActiveAssignment?.ConfigFilePath))
215+
{
216+
<span>in <code>@Model.ActiveAssignment.ConfigFilePath</code></span>
263217
}
264-
</td>
265-
<td>
266-
@if (map != null)
267-
{
268-
<map-popularity name="@item.MapName" like-percentage="@map.LikePercentage" dislike-percentage="@map.DislikePercentage" total-likes="@map.TotalLikes" total-dislikes="@map.TotalDislikes" votes="@map.TotalVotes"></map-popularity>
269-
}
270-
else
271-
{
272-
<strong>No popularity data found</strong>
273-
}
274-
</td>
275-
<td>
276-
@if (map != null)
277-
{
278-
<map-image uri="@map.MapImageUri"></map-image>
279-
}
280-
</td>
281-
</tr>
282-
}
283-
</tbody>
284-
</table>
285-
</div>
218+
· @(Model.ActiveRotation.MapRotationMaps?.Count ?? 0) maps · Version @Model.ActiveRotation.Version
219+
</small>
220+
</div>
221+
<div class="table-responsive">
222+
<table id="mapRotationTable" class="table table-striped table-hover w-100">
223+
<thead>
224+
<tr>
225+
<th>#</th>
226+
<th>Name</th>
227+
<th>Remote Status</th>
228+
<th>Popularity</th>
229+
<th></th>
230+
</tr>
231+
</thead>
232+
<tbody>
233+
@{ var sortedRotMaps = Model.ActiveRotation.MapRotationMaps?.OrderBy(m => m.SortOrder).ToList() ?? []; }
234+
@for (var i = 0; i < sortedRotMaps.Count; i++)
235+
{
236+
var rotMap = sortedRotMaps[i];
237+
var map = Model.Maps.FirstOrDefault(m => m.MapId == rotMap.MapId);
238+
var mapName = map?.MapName ?? rotMap.MapId.ToString();
239+
var remoteMatch = Model.ServerMaps.FirstOrDefault(x => string.Equals(x.Name, mapName, StringComparison.OrdinalIgnoreCase));
240+
<tr>
241+
<td><small class="text-muted">@(i + 1)</small></td>
242+
<td>@mapName</td>
243+
<td>
244+
@if (remoteMatch != null)
245+
{
246+
<span class="badge bg-success">On Host</span>
247+
}
248+
else
249+
{
250+
<span class="badge bg-danger">Not On Host</span>
251+
}
252+
</td>
253+
<td>
254+
@if (map != null)
255+
{
256+
<map-popularity name="@mapName" like-percentage="@map.LikePercentage" dislike-percentage="@map.DislikePercentage" total-likes="@map.TotalLikes" total-dislikes="@map.TotalDislikes" votes="@map.TotalVotes"></map-popularity>
257+
}
258+
</td>
259+
<td>
260+
@if (map != null)
261+
{
262+
<map-image uri="@map.MapImageUri"></map-image>
263+
}
264+
</td>
265+
</tr>
266+
}
267+
</tbody>
268+
</table>
269+
</div>
270+
}
271+
else
272+
{
273+
<div class="table-responsive">
274+
<table id="mapRotationTable" class="table table-striped table-hover w-100">
275+
<thead>
276+
<tr>
277+
<th>Name</th>
278+
<th>Remote Status</th>
279+
<th>Popularity</th>
280+
<th></th>
281+
</tr>
282+
</thead>
283+
<tbody>
284+
@foreach (var item in Model.RconMaps.OrderBy(x => x.MapName))
285+
{
286+
var map = Model.Maps.FirstOrDefault(x => x.MapName == item.MapName);
287+
var remoteMatch = Model.ServerMaps.FirstOrDefault(x => x.Name == item.MapName);
288+
<tr>
289+
<td>@item.MapName</td>
290+
<td>
291+
@if (remoteMatch != null)
292+
{
293+
<span class="badge bg-success">On Host</span>
294+
}
295+
else
296+
{
297+
<span class="badge bg-danger">Not On Host</span>
298+
}
299+
</td>
300+
<td>
301+
@if (map != null)
302+
{
303+
<map-popularity name="@item.MapName" like-percentage="@map.LikePercentage" dislike-percentage="@map.DislikePercentage" total-likes="@map.TotalLikes" total-dislikes="@map.TotalDislikes" votes="@map.TotalVotes"></map-popularity>
304+
}
305+
</td>
306+
<td>
307+
@if (map != null)
308+
{
309+
<map-image uri="@map.MapImageUri"></map-image>
310+
}
311+
</td>
312+
</tr>
313+
}
314+
</tbody>
315+
</table>
316+
</div>
317+
}
286318
</div>
287319
</div>
288320

@@ -325,14 +357,16 @@
325357
</td>
326358
<td>
327359
@{
328-
if (Model.RconMaps.Any(x => x.MapName == item.Name))
329-
{
330-
<span class="badge bg-success">In Rotation</span>
331-
}
332-
else
333-
{
334-
<span class="badge bg-danger">Not In Rotation</span>
335-
}
360+
var inPortalRotation = Model.ActiveRotationMapNames.Contains(item.Name);
361+
var inRconRotation = Model.RconMaps.Any(x => x.MapName == item.Name);
362+
if (inPortalRotation || inRconRotation)
363+
{
364+
<span class="badge bg-success">In Rotation</span>
365+
}
366+
else
367+
{
368+
<span class="badge bg-danger">Not In Rotation</span>
369+
}
336370
}
337371
</td>
338372
<td>

0 commit comments

Comments
 (0)