Skip to content

Commit ad27efa

Browse files
feat: Enhance map management with file display and search functionality
1 parent 388f2a7 commit ad27efa

7 files changed

Lines changed: 137 additions & 21 deletions

File tree

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

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
<div class="wrapper wrapper-content animated fadeInRight">
1111

12+
<input type="hidden" id="pushMapGameType" value="@Model.GameServer.GameType" />
13+
1214
<div class="ibox">
1315
<div class="ibox-title">
1416
<h5><server-name title="@Model.GameServer.Title" live-title="@Model.LiveStatus?.Title"></server-name> — Map Manager</h5>
@@ -27,9 +29,6 @@
2729
<span class="badge bg-secondary me-1">@Model.LiveStatus.Mod</span>
2830
}
2931
<server-host host="@Model.GameServer.Hostname" port="@Model.GameServer.QueryPort"></server-host>
30-
<server-link type="gametracker" host="@Model.GameServer.Hostname" port="@Model.GameServer.QueryPort"></server-link>
31-
<server-link type="steam" game="@Model.GameServer.GameType" host="@Model.GameServer.Hostname" port="@Model.GameServer.QueryPort"></server-link>
32-
<server-link type="hlsw" game="@Model.GameServer.GameType" host="@Model.GameServer.Hostname" port="@Model.GameServer.QueryPort"></server-link>
3332
</div>
3433
</div>
3534
<div class="ibox-content">
@@ -121,6 +120,7 @@
121120
<tr>
122121
<th style="width: 40px;">#</th>
123122
<th>Name</th>
123+
<th>Map Files</th>
124124
<th style="width: 110px;">Remote Status</th>
125125
<th style="width: 120px;">Popularity</th>
126126
<th style="width: 80px;"></th>
@@ -146,6 +146,21 @@
146146
<span class="badge bg-warning text-dark ms-1" title="This map has known issues"><i class="fa-solid fa-triangle-exclamation"></i> Problematic</span>
147147
}
148148
</td>
149+
<td>
150+
@if (map?.MapFiles != null && map.MapFiles.Count > 0)
151+
{
152+
<ul class="list-unstyled mb-0 map-files">
153+
@foreach (var file in map.MapFiles)
154+
{
155+
<li><a href="@file.Url">@file.FileName</a></li>
156+
}
157+
</ul>
158+
}
159+
else
160+
{
161+
<span class="text-muted">No Map Files</span>
162+
}
163+
</td>
149164
<td>
150165
@if (remoteMatch)
151166
{
@@ -184,18 +199,23 @@
184199
<table id="mapRotationTable" class="table table-striped table-hover w-100">
185200
<thead>
186201
<tr>
202+
<th style="width: 40px;">#</th>
187203
<th>Name</th>
204+
<th>Map Files</th>
188205
<th style="width: 110px;">Remote Status</th>
189206
<th style="width: 120px;">Popularity</th>
190207
<th style="width: 80px;"></th>
191208
</tr>
192209
</thead>
193210
<tbody>
211+
@{ var rconIndex = 0; }
194212
@foreach (var item in Model.RconMaps.OrderBy(x => x.MapName))
195213
{
214+
rconIndex++;
196215
mapsByName.TryGetValue(item.MapName, out var map);
197216
var remoteMatch = serverMapsByName.ContainsKey(item.MapName);
198217
<tr>
218+
<td><small class="text-muted">@rconIndex</small></td>
199219
<td>
200220
@item.MapName
201221
@if (map?.MapStatus == MapHealthStatus.Blacklisted)
@@ -207,6 +227,21 @@
207227
<span class="badge bg-warning text-dark ms-1" title="This map has known issues"><i class="fa-solid fa-triangle-exclamation"></i> Problematic</span>
208228
}
209229
</td>
230+
<td>
231+
@if (map?.MapFiles != null && map.MapFiles.Count > 0)
232+
{
233+
<ul class="list-unstyled mb-0 map-files">
234+
@foreach (var file in map.MapFiles)
235+
{
236+
<li><a href="@file.Url">@file.FileName</a></li>
237+
}
238+
</ul>
239+
}
240+
else
241+
{
242+
<span class="text-muted">No Map Files</span>
243+
}
244+
</td>
210245
<td>
211246
@if (remoteMatch)
212247
{

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
<div class="mb-3">
1919
<label asp-for="MapName" class="form-label"></label>
20-
<input asp-for="MapName" class="form-control" />
20+
<input asp-for="MapName" class="form-control" autocomplete="off" placeholder="Search for maps by name..." />
2121
<span asp-validation-for="MapName" class="text-danger"></span>
2222
</div>
2323

src/XtremeIdiots.Portal.Web/Views/ServerAdmin/ServerDetail.cshtml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
<div class="d-flex justify-content-between align-items-center">
2121
<h4 class="mb-0">
2222
<game-type-icon game="@gs.GameType" size="24" class="me-2" />
23-
@serverTitle
23+
<server-name title="@gs.Title" live-title="@liveStatus?.Title" />
2424
@if (liveStatus != null)
2525
{
2626
<small class="text-muted ms-2">@liveStatus.CurrentPlayers / @liveStatus.MaxPlayers players</small>

src/XtremeIdiots.Portal.Web/wwwroot/js/ban-file-monitors-index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ $(document).ready(function () {
1313
autoWidth: false,
1414
paging: true,
1515
pageLength: 25,
16-
order: [[3, 'desc']], // Last Sync descending
16+
order: [], // Preserve server-side ordering (ServerListPosition)
1717
columnDefs: [
1818
{ targets: 0, responsivePriority: 1 }, // Game Server - always visible
1919
{ targets: 1, responsivePriority: 3 }, // File Path - medium priority

src/XtremeIdiots.Portal.Web/wwwroot/js/map-manager.js

Lines changed: 94 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@ $(document).ready(function () {
1313
autoWidth: false,
1414
paging: true,
1515
pageLength: 25,
16-
order: [[0, 'asc']], // Name ascending
16+
order: [[0, 'asc']],
1717
columnDefs: [
18-
{ targets: 0, responsivePriority: 1, orderable: true }, // Name - always visible
19-
{ targets: 1, responsivePriority: 4, orderable: false }, // Game Type - not sortable
20-
{ targets: 2, responsivePriority: 5, orderable: false }, // Files - not sortable
21-
{ targets: 3, responsivePriority: 3, orderable: false }, // Remote Status - not sortable
22-
{ targets: 4, responsivePriority: 6, orderable: false }, // Popularity - not sortable
23-
{ targets: 5, responsivePriority: 7, orderable: false } // Image - not sortable
18+
{ targets: 0, responsivePriority: 2, orderable: true }, // # (sort order)
19+
{ targets: 1, responsivePriority: 1, orderable: true }, // Name - always visible
20+
{ targets: 2, responsivePriority: 5, orderable: false }, // Map Files
21+
{ targets: 3, responsivePriority: 3, orderable: false }, // Remote Status
22+
{ targets: 4, responsivePriority: 6, orderable: false }, // Popularity
23+
{ targets: 5, responsivePriority: 7, orderable: false } // Image
2424
]
2525
});
2626
}
@@ -38,14 +38,95 @@ $(document).ready(function () {
3838
autoWidth: false,
3939
paging: true,
4040
pageLength: 25,
41-
order: [[0, 'asc']], // Name ascending
41+
order: [[0, 'asc']],
4242
columnDefs: [
43-
{ targets: 0, responsivePriority: 1, orderable: true }, // Name - always visible
44-
{ targets: 1, responsivePriority: 4, orderable: false }, // Path - not sortable
45-
{ targets: 2, responsivePriority: 3, orderable: false }, // Rotation Status - not sortable
46-
{ targets: 3, responsivePriority: 5, orderable: false }, // Modified - not sortable
47-
{ targets: 4, responsivePriority: 2, orderable: false } // Actions - not sortable
43+
{ targets: 0, responsivePriority: 1, orderable: true }, // Name
44+
{ targets: 1, responsivePriority: 4, orderable: false }, // Path
45+
{ targets: 2, responsivePriority: 3, orderable: false }, // Rotation Status
46+
{ targets: 3, responsivePriority: 5, orderable: false }, // Health
47+
{ targets: 4, responsivePriority: 6, orderable: false }, // Modified
48+
{ targets: 5, responsivePriority: 2, orderable: false } // Actions
4849
]
4950
});
5051
}
52+
53+
// Push Map to Remote - Map Search
54+
initPushMapSearch();
5155
});
56+
57+
function initPushMapSearch() {
58+
const $input = $('#MapName');
59+
if ($input.length === 0) return;
60+
61+
$(document).off('click.pushMapSearch');
62+
63+
let timer = null;
64+
const $wrapper = $input.parent();
65+
$wrapper.css('position', 'relative');
66+
67+
const $suggestions = $('<div class="map-suggestions list-group position-absolute bg-white shadow" style="z-index:1050; max-height:300px; overflow-y:auto; display:none; width:100%;"></div>');
68+
$input.after($suggestions);
69+
70+
function clearSuggestions() {
71+
$suggestions.hide().empty();
72+
}
73+
74+
function search(term) {
75+
if (!term || term.length < 2) { clearSuggestions(); return; }
76+
77+
const gameTypeAttr = document.getElementById('pushMapGameType');
78+
const gt = gameTypeAttr ? gameTypeAttr.value : '';
79+
const url = '/MapSearch/Maps?term=' + encodeURIComponent(term) + (gt ? '&gameType=' + encodeURIComponent(gt) : '');
80+
81+
fetch(url)
82+
.then(r => r.json())
83+
.then(results => {
84+
$suggestions.empty();
85+
if (!Array.isArray(results) || results.length === 0) { clearSuggestions(); return; }
86+
87+
results.forEach(r => {
88+
const $item = $('<button type="button" class="list-group-item list-group-item-action py-2 px-3 d-flex align-items-center" role="option"></button>');
89+
$item.append($('<img>').attr('src', r.imageUrl || '/images/noimage.jpg').css({ width: '40px', height: '28px', objectFit: 'cover', borderRadius: '3px' }).addClass('me-2'));
90+
$item.append($('<span>').text(r.text));
91+
$item.on('click', function () {
92+
$input.val(r.text);
93+
clearSuggestions();
94+
});
95+
$suggestions.append($item);
96+
});
97+
98+
if ($suggestions.children().length > 0) {
99+
$suggestions.show();
100+
} else {
101+
clearSuggestions();
102+
}
103+
})
104+
.catch(() => clearSuggestions());
105+
}
106+
107+
$input.on('input', function () {
108+
clearTimeout(timer);
109+
const term = $(this).val().trim();
110+
timer = setTimeout(() => search(term), 300);
111+
});
112+
113+
$input.on('keydown', function (e) {
114+
if (!$suggestions.is(':visible')) return;
115+
const $items = $suggestions.find('.list-group-item');
116+
if ($items.length === 0) return;
117+
let idx = $items.index($items.filter('.active'));
118+
if (e.key === 'ArrowDown') { e.preventDefault(); idx = (idx + 1) % $items.length; }
119+
else if (e.key === 'ArrowUp') { e.preventDefault(); idx = (idx <= 0 ? $items.length - 1 : idx - 1); }
120+
else if (e.key === 'Enter') { e.preventDefault(); if (idx >= 0) $items.eq(idx).click(); return; }
121+
else if (e.key === 'Escape') { clearSuggestions(); return; }
122+
else return;
123+
$items.removeClass('active');
124+
$items.eq(idx).addClass('active')[0].scrollIntoView({ block: 'nearest' });
125+
});
126+
127+
$(document).on('click.pushMapSearch', function (e) {
128+
if (!$(e.target).closest('#MapName').length && !$(e.target).closest('.map-suggestions').length) {
129+
clearSuggestions();
130+
}
131+
});
132+
}

src/XtremeIdiots.Portal.Web/wwwroot/js/server-admin-index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ $(document).ready(function () {
1313
autoWidth: false,
1414
paging: false,
1515
searching: false,
16-
order: [[0, 'asc']], // Title ascending
16+
order: [], // Preserve server-side ordering (ServerListPosition)
1717
columnDefs: [
1818
{ targets: 0, responsivePriority: 1, orderable: true }, // Title - always visible
1919
{ targets: 1, responsivePriority: 4, orderable: true }, // Hostname - collapsible

src/XtremeIdiots.Portal.Web/wwwroot/js/servers-index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ $(document).ready(function () {
1313
autoWidth: false,
1414
paging: true,
1515
pageLength: 25,
16-
order: [[1, 'asc']], // Title ascending
16+
order: [], // Preserve server-side ordering (ServerListPosition)
1717
columnDefs: [
1818
{ targets: 0, responsivePriority: 6, orderable: false }, // Game icon - not sortable
1919
{ targets: 1, responsivePriority: 1 }, // Title - always visible, sortable

0 commit comments

Comments
 (0)