huge commit for webfront facelift
This commit is contained in:
parent
4023ca37d4
commit
4fbe0ee0ed
@ -25,6 +25,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Jint" Version="3.0.0-beta-2037" />
|
||||
<PackageReference Include="MaxMind.GeoIP2" Version="5.1.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
@ -63,6 +64,9 @@
|
||||
<None Update="Configuration\LoggingConfiguration.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Resources\GeoLite2-Country.mmdb">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
|
||||
|
@ -508,7 +508,8 @@ namespace IW4MAdmin
|
||||
{
|
||||
Origin = E.Origin,
|
||||
Target = E.Target,
|
||||
Reason = E.Data
|
||||
Reason = E.Data,
|
||||
ReportedOn = DateTime.UtcNow
|
||||
});
|
||||
|
||||
var newReport = new EFPenalty()
|
||||
@ -645,7 +646,7 @@ namespace IW4MAdmin
|
||||
}
|
||||
}
|
||||
|
||||
ChatHistory.Add(new ChatInfo()
|
||||
ChatHistory.Add(new ChatInfo
|
||||
{
|
||||
Name = E.Origin.Name,
|
||||
Message = message,
|
||||
|
@ -447,6 +447,7 @@ namespace IW4MAdmin.Application
|
||||
.AddSingleton<IServerDataViewer, ServerDataViewer>()
|
||||
.AddSingleton<IServerDataCollector, ServerDataCollector>()
|
||||
.AddSingleton<IEventPublisher, EventPublisher>()
|
||||
.AddSingleton<IGeoLocationService>(new GeoLocationService(Path.Join(".", "Resources", "GeoLite2-Country.mmdb")))
|
||||
.AddTransient<IScriptPluginTimerHelper, ScriptPluginTimerHelper>()
|
||||
.AddSingleton(translationLookup)
|
||||
.AddDatabaseContextOptions(appConfig);
|
||||
|
@ -83,7 +83,6 @@ namespace IW4MAdmin.Application.Meta
|
||||
Value = lastMapMeta.Value,
|
||||
ShouldDisplay = true,
|
||||
Type = MetaType.Information,
|
||||
Column = 1,
|
||||
Order = 6
|
||||
});
|
||||
}
|
||||
@ -101,8 +100,7 @@ namespace IW4MAdmin.Application.Meta
|
||||
Value = lastServerMeta.Value,
|
||||
ShouldDisplay = true,
|
||||
Type = MetaType.Information,
|
||||
Column = 0,
|
||||
Order = 6
|
||||
Order = 7
|
||||
});
|
||||
}
|
||||
|
||||
@ -120,8 +118,7 @@ namespace IW4MAdmin.Application.Meta
|
||||
Key = _transLookup["WEBFRONT_PROFILE_META_PLAY_TIME"],
|
||||
Value = TimeSpan.FromHours(client.TotalConnectionTime / 3600.0).HumanizeForCurrentCulture(),
|
||||
ShouldDisplay = true,
|
||||
Column = 1,
|
||||
Order = 0,
|
||||
Order = 8,
|
||||
Type = MetaType.Information
|
||||
});
|
||||
|
||||
@ -131,8 +128,7 @@ namespace IW4MAdmin.Application.Meta
|
||||
Key = _transLookup["WEBFRONT_PROFILE_META_FIRST_SEEN"],
|
||||
Value = (DateTime.UtcNow - client.FirstConnection).HumanizeForCurrentCulture(),
|
||||
ShouldDisplay = true,
|
||||
Column = 1,
|
||||
Order = 1,
|
||||
Order = 9,
|
||||
Type = MetaType.Information
|
||||
});
|
||||
|
||||
@ -142,8 +138,7 @@ namespace IW4MAdmin.Application.Meta
|
||||
Key = _transLookup["WEBFRONT_PROFILE_META_LAST_SEEN"],
|
||||
Value = (DateTime.UtcNow - client.LastConnection).HumanizeForCurrentCulture(),
|
||||
ShouldDisplay = true,
|
||||
Column = 1,
|
||||
Order = 2,
|
||||
Order = 10,
|
||||
Type = MetaType.Information
|
||||
});
|
||||
|
||||
@ -154,8 +149,7 @@ namespace IW4MAdmin.Application.Meta
|
||||
Value = client.Connections.ToString("#,##0",
|
||||
new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
|
||||
ShouldDisplay = true,
|
||||
Column = 1,
|
||||
Order = 3,
|
||||
Order = 11,
|
||||
Type = MetaType.Information
|
||||
});
|
||||
|
||||
@ -167,8 +161,7 @@ namespace IW4MAdmin.Application.Meta
|
||||
? Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_TRUE"]
|
||||
: Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_FALSE"],
|
||||
IsSensitive = true,
|
||||
Column = 1,
|
||||
Order = 4,
|
||||
Order = 12,
|
||||
Type = MetaType.Information
|
||||
});
|
||||
|
||||
|
13
Application/Misc/GeoLocationResult.cs
Normal file
13
Application/Misc/GeoLocationResult.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using SharedLibraryCore.Interfaces;
|
||||
|
||||
namespace IW4MAdmin.Application.Misc;
|
||||
|
||||
public class GeoLocationResult : IGeoLocationResult
|
||||
{
|
||||
public string Country { get; set; }
|
||||
public string CountryCode { get; set; }
|
||||
public string Region { get; set; }
|
||||
public string ASN { get; set; }
|
||||
public string Timezone { get; set; }
|
||||
public string Organization { get; set; }
|
||||
}
|
40
Application/Misc/GeoLocationService.cs
Normal file
40
Application/Misc/GeoLocationService.cs
Normal file
@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using MaxMind.GeoIP2;
|
||||
using MaxMind.GeoIP2.Responses;
|
||||
using SharedLibraryCore.Interfaces;
|
||||
|
||||
namespace IW4MAdmin.Application.Misc;
|
||||
|
||||
public class GeoLocationService : IGeoLocationService
|
||||
{
|
||||
private readonly string _sourceAddress;
|
||||
|
||||
public GeoLocationService(string sourceAddress)
|
||||
{
|
||||
_sourceAddress = sourceAddress;
|
||||
}
|
||||
|
||||
public Task<IGeoLocationResult> Locate(string address)
|
||||
{
|
||||
CountryResponse country = null;
|
||||
|
||||
try
|
||||
{
|
||||
using var reader = new DatabaseReader(_sourceAddress);
|
||||
reader.TryCountry(address, out country);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
var response = new GeoLocationResult
|
||||
{
|
||||
Country = country?.Country.Name ?? "Unknown",
|
||||
CountryCode = country?.Country.IsoCode ?? ""
|
||||
};
|
||||
|
||||
return Task.FromResult((IGeoLocationResult)response);
|
||||
}
|
||||
}
|
@ -446,54 +446,7 @@ public class MetaServiceV2 : IMetaServiceV2
|
||||
|
||||
private static IEnumerable<T> ProcessInformationMeta<T>(IEnumerable<T> meta) where T : IClientMeta
|
||||
{
|
||||
var metaList = meta.ToList();
|
||||
var metaWithColumn = metaList
|
||||
.Where(m => m.Column != null)
|
||||
.ToList();
|
||||
|
||||
var columnGrouping = metaWithColumn
|
||||
.GroupBy(m => m.Column)
|
||||
.ToList();
|
||||
|
||||
var metaToSort = metaList.Except(metaWithColumn).ToList();
|
||||
|
||||
var table = columnGrouping.Select(metaItem => new List<T>(metaItem)).ToList();
|
||||
|
||||
while (metaToSort.Count > 0)
|
||||
{
|
||||
var sortingMeta = metaToSort.First();
|
||||
|
||||
int IndexOfSmallestColumn()
|
||||
{
|
||||
var index = 0;
|
||||
var smallestColumnSize = int.MaxValue;
|
||||
for (var i = 0; i < table.Count; i++)
|
||||
{
|
||||
if (table[i].Count >= smallestColumnSize)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
smallestColumnSize = table[i].Count;
|
||||
index = i;
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
var columnIndex = IndexOfSmallestColumn();
|
||||
|
||||
sortingMeta.Column = columnIndex;
|
||||
sortingMeta.Order = columnGrouping
|
||||
.First(group => group.Key == columnIndex)
|
||||
.Count();
|
||||
|
||||
table[columnIndex].Add(sortingMeta);
|
||||
|
||||
metaToSort.Remove(sortingMeta);
|
||||
}
|
||||
|
||||
return metaList;
|
||||
return meta;
|
||||
}
|
||||
|
||||
private static bool ValidArgs(string key, int clientId) => !string.IsNullOrWhiteSpace(key) && clientId > 0;
|
||||
|
BIN
Application/Resources/GeoLite2-Country.mmdb
Normal file
BIN
Application/Resources/GeoLite2-Country.mmdb
Normal file
Binary file not shown.
@ -24,20 +24,22 @@ namespace LiveRadar.Web.Controllers
|
||||
|
||||
[HttpGet]
|
||||
[Route("Radar/{serverId}")]
|
||||
public IActionResult Index(long? serverId = null)
|
||||
public IActionResult Index(string serverId = null)
|
||||
{
|
||||
ViewBag.IsFluid = true;
|
||||
ViewBag.Title = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_RADAR_TITLE"];
|
||||
ViewBag.ActiveServerId = serverId ?? _manager.GetServers().FirstOrDefault()?.EndPoint;
|
||||
ViewBag.Servers = _manager.GetServers()
|
||||
.Where(_server => _server.GameName == Server.Game.IW4)
|
||||
.Select(_server => new ServerInfo()
|
||||
var servers = _manager.GetServers()
|
||||
.Where(server => server.GameName == Server.Game.IW4)
|
||||
.Select(server => new ServerInfo
|
||||
{
|
||||
Name = _server.Hostname,
|
||||
ID = _server.EndPoint
|
||||
Name = server.Hostname,
|
||||
IPAddress = server.IP,
|
||||
Port = server.Port
|
||||
});
|
||||
|
||||
ViewBag.Title = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_RADAR_TITLE"];
|
||||
ViewBag.SelectedServerId = string.IsNullOrEmpty(serverId) ? servers.FirstOrDefault()?.Endpoint : serverId;
|
||||
|
||||
return View();
|
||||
// ReSharper disable once Mvc.ViewNotResolved
|
||||
return View("~/Views/Plugins/LiveRadar/Radar/Index.cshtml", servers);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
|
@ -15,13 +15,6 @@
|
||||
<StartupObject />
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<Content Update="Views\_ViewImports.cshtml">
|
||||
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.3.23.1" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
@ -1,53 +0,0 @@
|
||||
@model IEnumerable<long>
|
||||
|
||||
<style>
|
||||
.progress {
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
.player-stat-icon {
|
||||
height: 1.5rem;
|
||||
width: 1.5rem;
|
||||
background-size: 1.5rem 1.5rem;
|
||||
}
|
||||
</style>
|
||||
<div class="row p-0 ml-auto mr-auto mb-4">
|
||||
<div class="col-12 col-xl-10 p-0 ml-auto mr-auto p-0 pl-lg-3 pr-lg-3 ">
|
||||
<ul class="nav nav-tabs border-top border-bottom nav-fill" role="tablist">
|
||||
@foreach (SharedLibraryCore.Dtos.ServerInfo server in ViewBag.Servers)
|
||||
{
|
||||
<li class="nav-item">
|
||||
<a asp-controller="Radar" asp-action="Index" asp-route-serverId="@server.ID" class="nav-link @(server.ID == ViewBag.ActiveServerId ? "active": "")" aria-selected="@(server.ID == ViewBag.ActiveServerId ? "true": "false")"><color-code value="@server.Name"></color-code></a>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row p-0 ml-auto mr-auto col-12 col-xl-10">
|
||||
<div class="p-0 pl-lg-3 pr-lg-3 m-0 col-lg-3 col-12 text-lg-right text-center player-data-left" style="opacity: 0;">
|
||||
</div>
|
||||
<div class="pl-0 pr-0 pl-lg-3 pr-lg-3 col-lg-6 col-12 pb-4">
|
||||
<div id="map_name" class="h4 text-center pb-2 pt-2 mb-0 bg-primary">—</div>
|
||||
<div id="map_list" style="background-size:cover; padding-bottom: 100% !important;">
|
||||
<canvas id="map_canvas" style="position:absolute;"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-0 pl-lg-3 pr-lg-3 m-0 col-lg-3 col-12 text-lg-left text-center player-data-right" style="opacity: 0;">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- images used by canvas -->
|
||||
<img class="hide" id="hud_death" src="~/images/radar/death.png" />
|
||||
|
||||
@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.ActiveServerId })';
|
||||
const mapDataUrl = '@Url.Action("Map", "Radar", new { serverId = ViewBag.ActiveServerId })';
|
||||
</script>
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
@using SharedLibraryCore
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
@addTagHelper *, SharedLibraryCore
|
@ -193,4 +193,4 @@ namespace IW4MAdmin.Plugins.Stats
|
||||
throw new ArgumentException("No filters specified for chat search");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -130,7 +130,7 @@ namespace SharedLibraryCore
|
||||
new Claim(ClaimTypes.NameIdentifier, Client.CurrentAlias.Name),
|
||||
new Claim(ClaimTypes.Role, Client.Level.ToString()),
|
||||
new Claim(ClaimTypes.Sid, Client.ClientId.ToString()),
|
||||
new Claim(ClaimTypes.PrimarySid, Client.NetworkId.ToString("X"))
|
||||
new Claim(ClaimTypes.PrimarySid, Client.NetworkId.ToString("X")),
|
||||
};
|
||||
var claimsIdentity = new ClaimsIdentity(claims, "login");
|
||||
SignInAsync(new ClaimsPrincipal(claimsIdentity)).Wait();
|
||||
@ -161,6 +161,14 @@ namespace SharedLibraryCore
|
||||
ViewBag.EnablePrivilegedUserPrivacy = AppConfig.EnablePrivilegedUserPrivacy;
|
||||
ViewBag.Configuration = AppConfig;
|
||||
ViewBag.ScriptInjection = AppConfig.Webfront?.ScriptInjection;
|
||||
ViewBag.CommunityInformation = AppConfig.CommunityInformation;
|
||||
ViewBag.ClientCount = Manager.GetServers().Sum(server => server.ClientNum);
|
||||
ViewBag.AdminCount = Manager.GetServers().Sum(server =>
|
||||
server.GetClientsAsList()
|
||||
.Count(client => client.Level >= Data.Models.Client.EFClient.Permission.Trusted));
|
||||
ViewBag.ReportCount = Manager.GetServers().Sum(server =>
|
||||
server.Reports.Count(report => DateTime.UtcNow - report.ReportedOn <= TimeSpan.FromHours(24)));
|
||||
ViewBag.PermissionsSet = PermissionsSet;
|
||||
|
||||
base.OnActionExecuting(context);
|
||||
}
|
||||
|
@ -1,12 +1,14 @@
|
||||
using Data.Models.Client;
|
||||
using System;
|
||||
using Data.Models.Client;
|
||||
|
||||
namespace SharedLibraryCore.Dtos
|
||||
{
|
||||
public class ClientInfo
|
||||
public class ClientInfo
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public int ClientId { get; set; }
|
||||
public int LinkId { get; set; }
|
||||
public EFClient.Permission Level { get; set; }
|
||||
public DateTime LastConnection { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,8 +15,8 @@ namespace SharedLibraryCore.Dtos
|
||||
public int LevelInt { get; set; }
|
||||
public string IPAddress { get; set; }
|
||||
public long NetworkId { get; set; }
|
||||
public List<string> Aliases { get; set; }
|
||||
public List<string> IPs { get; set; }
|
||||
public List<(string, DateTime)> Aliases { get; set; }
|
||||
public List<(string, DateTime)> IPs { get; set; }
|
||||
public bool HasActivePenalty { get; set; }
|
||||
public string ActivePenaltyType { get; set; }
|
||||
public bool Authenticated { get; set; }
|
||||
@ -29,5 +29,8 @@ namespace SharedLibraryCore.Dtos
|
||||
public IDictionary<int, long> LinkedAccounts { get; set; }
|
||||
public MetaType? MetaFilterType { get; set; }
|
||||
public double? ZScore { get; set; }
|
||||
public string ConnectProtocolUrl { get;set; }
|
||||
public string CurrentServerName { get; set; }
|
||||
public IGeoLocationResult GeoLocationInfo { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ namespace SharedLibraryCore.Dtos
|
||||
public bool Online { get; set; }
|
||||
public string ConnectProtocolUrl { get; set; }
|
||||
public string IPAddress { get; set; }
|
||||
public string ExternalIPAddress { get; set; }
|
||||
public bool IsPasswordProtected { get; set; }
|
||||
public string Endpoint => $"{IPAddress}:{Port}";
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
using SharedLibraryCore.Database.Models;
|
||||
using System;
|
||||
using SharedLibraryCore.Database.Models;
|
||||
|
||||
namespace SharedLibraryCore.Helpers
|
||||
{
|
||||
@ -7,5 +8,6 @@ namespace SharedLibraryCore.Helpers
|
||||
public EFClient Target { get; set; }
|
||||
public EFClient Origin { get; set; }
|
||||
public string Reason { get; set; }
|
||||
public DateTime ReportedOn { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
11
SharedLibraryCore/Interfaces/IGeoLocationResult.cs
Normal file
11
SharedLibraryCore/Interfaces/IGeoLocationResult.cs
Normal file
@ -0,0 +1,11 @@
|
||||
namespace SharedLibraryCore.Interfaces;
|
||||
|
||||
public interface IGeoLocationResult
|
||||
{
|
||||
string Country { get; set; }
|
||||
string CountryCode { get; set; }
|
||||
string Region { get; set; }
|
||||
string ASN { get; set; }
|
||||
string Timezone { get; set; }
|
||||
string Organization { get; set; }
|
||||
}
|
8
SharedLibraryCore/Interfaces/IGeoLocationService.cs
Normal file
8
SharedLibraryCore/Interfaces/IGeoLocationService.cs
Normal file
@ -0,0 +1,8 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SharedLibraryCore.Interfaces;
|
||||
|
||||
public interface IGeoLocationService
|
||||
{
|
||||
Task<IGeoLocationResult> Locate(string address);
|
||||
}
|
@ -117,7 +117,7 @@ namespace SharedLibraryCore
|
||||
|
||||
public int ClientNum
|
||||
{
|
||||
get { return Clients.ToArray().Count(p => p != null && !p.IsBot); }
|
||||
get { return Clients.ToArray().Count(p => p != null && Utilities.IsDevelopment || (!p?.IsBot ?? false)); }
|
||||
}
|
||||
|
||||
public int MaxClients { get; protected set; }
|
||||
|
@ -529,32 +529,36 @@ namespace SharedLibraryCore
|
||||
public static bool HasPermission<TEntity, TPermission>(this IEnumerable<string> permissionsSet, TEntity entity,
|
||||
TPermission permission) where TEntity : Enum where TPermission : Enum
|
||||
{
|
||||
return permissionsSet?.Any(raw =>
|
||||
if (permissionsSet == null)
|
||||
{
|
||||
if (raw == "*")
|
||||
return false;
|
||||
}
|
||||
|
||||
var requiredPermission = $"{entity.ToString()}.{permission.ToString()}";
|
||||
var hasAllPermissions = permissionsSet.Any(p => p.Equals("*"));
|
||||
var permissionCheckResult = permissionsSet.Select(p =>
|
||||
{
|
||||
if (p.Equals(requiredPermission, StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var split = raw.Split(".");
|
||||
|
||||
if (split.Length != 2)
|
||||
if (p.Equals($"-{requiredPermission}", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Enum.TryParse(typeof(TEntity), split[0], out var e))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return (bool?)null;
|
||||
}).ToList();
|
||||
|
||||
if (!Enum.TryParse(typeof(TPermission), split[1], out var p))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var permissionNegated = permissionCheckResult.Any(result => result.HasValue && !result.Value);
|
||||
|
||||
return (e?.Equals(entity) ?? false) && (p?.Equals(permission) ?? false);
|
||||
}) ?? false;
|
||||
if (permissionNegated)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return hasAllPermissions || permissionCheckResult.Any(result => result.HasValue && result.Value);
|
||||
}
|
||||
|
||||
public static bool HasPermission<TEntity, TPermission>(this ApplicationConfiguration appConfig,
|
||||
|
@ -80,7 +80,7 @@ namespace WebfrontCore.Controllers.API
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
||||
public async Task<IActionResult> LoginAsync([FromRoute] int clientId,
|
||||
public async Task<IActionResult> Login([FromRoute] int clientId,
|
||||
[FromBody, Required] PasswordRequest request)
|
||||
{
|
||||
if (clientId == 0)
|
||||
@ -145,7 +145,7 @@ namespace WebfrontCore.Controllers.API
|
||||
[HttpPost("{clientId:int}/logout")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
||||
public async Task<IActionResult> LogoutAsync()
|
||||
public async Task<IActionResult> Logout()
|
||||
{
|
||||
if (Authorized)
|
||||
{
|
||||
@ -170,4 +170,4 @@ namespace WebfrontCore.Controllers.API
|
||||
public string Password { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,11 +19,11 @@ namespace WebfrontCore.Controllers
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> LoginAsync(int clientId, string password)
|
||||
public async Task<IActionResult> Login(int clientId, string password)
|
||||
{
|
||||
if (clientId == 0 || string.IsNullOrEmpty(password))
|
||||
{
|
||||
return Unauthorized();
|
||||
return Unauthorized("Invalid credentials");
|
||||
}
|
||||
|
||||
try
|
||||
@ -63,20 +63,20 @@ namespace WebfrontCore.Controllers
|
||||
: HttpContext.Connection.RemoteIpAddress.ToString()
|
||||
});
|
||||
|
||||
return Ok();
|
||||
return Ok($"Welcome {privilegedClient.Name}. You are now logged in");
|
||||
}
|
||||
}
|
||||
|
||||
catch (Exception)
|
||||
{
|
||||
return Unauthorized();
|
||||
return Unauthorized("Could not validate credentials");
|
||||
}
|
||||
|
||||
return Unauthorized();
|
||||
return Unauthorized("Invalid credentials");
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> LogoutAsync()
|
||||
public async Task<IActionResult> Logout()
|
||||
{
|
||||
if (Authorized)
|
||||
{
|
||||
|
@ -69,25 +69,25 @@ namespace WebfrontCore.Controllers
|
||||
|
||||
public IActionResult BanForm()
|
||||
{
|
||||
var info = new ActionInfo()
|
||||
var info = new ActionInfo
|
||||
{
|
||||
ActionButtonLabel = Localization["WEBFRONT_ACTION_BAN_NAME"],
|
||||
Name = "Ban",
|
||||
Inputs = new List<InputInfo>()
|
||||
Inputs = new List<InputInfo>
|
||||
{
|
||||
new InputInfo()
|
||||
new()
|
||||
{
|
||||
Name = "Reason",
|
||||
Label = Localization["WEBFRONT_ACTION_LABEL_REASON"],
|
||||
},
|
||||
new InputInfo()
|
||||
new()
|
||||
{
|
||||
Name = "PresetReason",
|
||||
Type = "select",
|
||||
Label = Localization["WEBFRONT_ACTION_LABEL_PRESET_REASON"],
|
||||
Values = GetPresetPenaltyReasons()
|
||||
},
|
||||
new InputInfo()
|
||||
new()
|
||||
{
|
||||
Name = "Duration",
|
||||
Label = Localization["WEBFRONT_ACTION_LABEL_DURATION"],
|
||||
@ -134,7 +134,7 @@ namespace WebfrontCore.Controllers
|
||||
|
||||
var server = Manager.GetServers().First();
|
||||
|
||||
return await Task.FromResult(RedirectToAction("ExecuteAsync", "Console", new
|
||||
return await Task.FromResult(RedirectToAction("Execute", "Console", new
|
||||
{
|
||||
serverId = server.EndPoint,
|
||||
command
|
||||
@ -143,13 +143,13 @@ namespace WebfrontCore.Controllers
|
||||
|
||||
public IActionResult UnbanForm()
|
||||
{
|
||||
var info = new ActionInfo()
|
||||
var info = new ActionInfo
|
||||
{
|
||||
ActionButtonLabel = Localization["WEBFRONT_ACTION_UNBAN_NAME"],
|
||||
Name = "Unban",
|
||||
Inputs = new List<InputInfo>()
|
||||
Inputs = new List<InputInfo>
|
||||
{
|
||||
new InputInfo()
|
||||
new()
|
||||
{
|
||||
Name = "Reason",
|
||||
Label = Localization["WEBFRONT_ACTION_LABEL_REASON"],
|
||||
@ -162,57 +162,59 @@ namespace WebfrontCore.Controllers
|
||||
return View("_ActionForm", info);
|
||||
}
|
||||
|
||||
public async Task<IActionResult> UnbanAsync(int targetId, string Reason)
|
||||
public async Task<IActionResult> UnbanAsync(int targetId, string reason)
|
||||
{
|
||||
var server = Manager.GetServers().First();
|
||||
|
||||
return await Task.FromResult(RedirectToAction("ExecuteAsync", "Console", new
|
||||
return await Task.FromResult(RedirectToAction("Execute", "Console", new
|
||||
{
|
||||
serverId = server.EndPoint,
|
||||
command = $"{_appConfig.CommandPrefix}{_unbanCommandName} @{targetId} {Reason}"
|
||||
command = $"{_appConfig.CommandPrefix}{_unbanCommandName} @{targetId} {reason}"
|
||||
}));
|
||||
}
|
||||
|
||||
public IActionResult LoginForm()
|
||||
{
|
||||
var login = new ActionInfo()
|
||||
var login = new ActionInfo
|
||||
{
|
||||
ActionButtonLabel = Localization["WEBFRONT_ACTION_LOGIN_NAME"],
|
||||
Name = "Login",
|
||||
Inputs = new List<InputInfo>()
|
||||
Inputs = new List<InputInfo>
|
||||
{
|
||||
new InputInfo()
|
||||
new()
|
||||
{
|
||||
Name = "clientId",
|
||||
Label = Localization["WEBFRONT_ACTION_LABEL_ID"]
|
||||
Label = Localization["WEBFRONT_ACTION_LABEL_ID"],
|
||||
Required = true
|
||||
},
|
||||
new InputInfo()
|
||||
new()
|
||||
{
|
||||
Name = "Password",
|
||||
Label = Localization["WEBFRONT_ACTION_LABEL_PASSWORD"],
|
||||
Type = "password",
|
||||
Required = true
|
||||
}
|
||||
},
|
||||
Action = "LoginAsync"
|
||||
Action = "Login"
|
||||
};
|
||||
|
||||
return View("_ActionForm", login);
|
||||
}
|
||||
|
||||
public async Task<IActionResult> LoginAsync(int clientId, string password)
|
||||
public async Task<IActionResult> Login(int clientId, string password)
|
||||
{
|
||||
return await Task.FromResult(RedirectToAction("LoginAsync", "Account", new {clientId, password}));
|
||||
return await Task.FromResult(RedirectToAction("Login", "Account", new {clientId, password}));
|
||||
}
|
||||
|
||||
public IActionResult EditForm()
|
||||
{
|
||||
var info = new ActionInfo()
|
||||
var info = new ActionInfo
|
||||
{
|
||||
ActionButtonLabel = Localization["WEBFRONT_ACTION_LABEL_EDIT"],
|
||||
Name = "Edit",
|
||||
Inputs = new List<InputInfo>()
|
||||
Inputs = new List<InputInfo>
|
||||
{
|
||||
new InputInfo()
|
||||
new()
|
||||
{
|
||||
Name = "level",
|
||||
Label = Localization["WEBFRONT_PROFILE_LEVEL"],
|
||||
@ -235,7 +237,7 @@ namespace WebfrontCore.Controllers
|
||||
{
|
||||
var server = Manager.GetServers().First();
|
||||
|
||||
return await Task.FromResult(RedirectToAction("ExecuteAsync", "Console", new
|
||||
return await Task.FromResult(RedirectToAction("Execute", "Console", new
|
||||
{
|
||||
serverId = server.EndPoint,
|
||||
command = $"{_appConfig.CommandPrefix}{_setLevelCommandName} @{targetId} {level}"
|
||||
@ -244,7 +246,7 @@ namespace WebfrontCore.Controllers
|
||||
|
||||
public IActionResult GenerateLoginTokenForm()
|
||||
{
|
||||
var info = new ActionInfo()
|
||||
var info = new ActionInfo
|
||||
{
|
||||
ActionButtonLabel = Localization["WEBFRONT_ACTION_LABEL_GENERATE_TOKEN"],
|
||||
Name = "GenerateLoginToken",
|
||||
@ -267,19 +269,19 @@ namespace WebfrontCore.Controllers
|
||||
|
||||
public IActionResult ChatForm(long id)
|
||||
{
|
||||
var info = new ActionInfo()
|
||||
var info = new ActionInfo
|
||||
{
|
||||
ActionButtonLabel = Localization["WEBFRONT_ACTION_LABEL_SUBMIT_MESSAGE"],
|
||||
Name = "Chat",
|
||||
Inputs = new List<InputInfo>
|
||||
{
|
||||
new InputInfo()
|
||||
new()
|
||||
{
|
||||
Name = "message",
|
||||
Type = "text",
|
||||
Label = Localization["WEBFRONT_ACTION_LABEL_MESSAGE"]
|
||||
},
|
||||
new InputInfo()
|
||||
new()
|
||||
{
|
||||
Name = "id",
|
||||
Value = id.ToString(),
|
||||
@ -294,7 +296,7 @@ namespace WebfrontCore.Controllers
|
||||
|
||||
public async Task<IActionResult> ChatAsync(long id, string message)
|
||||
{
|
||||
var server = Manager.GetServers().First(_server => _server.EndPoint == id);
|
||||
var server = Manager.GetServers().First(server => server.EndPoint == id);
|
||||
|
||||
server.ChatHistory.Add(new SharedLibraryCore.Dtos.ChatInfo()
|
||||
{
|
||||
@ -305,7 +307,7 @@ namespace WebfrontCore.Controllers
|
||||
Time = DateTime.Now
|
||||
});
|
||||
|
||||
return await Task.FromResult(RedirectToAction("ExecuteAsync", "Console", new
|
||||
return await Task.FromResult(RedirectToAction("Execute", "Console", new
|
||||
{
|
||||
serverId = server.EndPoint,
|
||||
command = $"{_appConfig.CommandPrefix}{_sayCommandName} {message}"
|
||||
@ -318,7 +320,7 @@ namespace WebfrontCore.Controllers
|
||||
foreach (var client in clients)
|
||||
{
|
||||
client.IPAddress =
|
||||
_appConfig.HasPermission(Client.Level, WebfrontEntity.IPAddress, WebfrontPermission.Read)
|
||||
_appConfig.HasPermission(Client.Level, WebfrontEntity.ClientIPAddress, WebfrontPermission.Read)
|
||||
? client.IPAddress
|
||||
: null;
|
||||
}
|
||||
@ -328,18 +330,18 @@ namespace WebfrontCore.Controllers
|
||||
|
||||
public IActionResult FlagForm()
|
||||
{
|
||||
var info = new ActionInfo()
|
||||
var info = new ActionInfo
|
||||
{
|
||||
ActionButtonLabel = Localization["WEBFRONT_ACTION_FLAG_NAME"],
|
||||
Name = "Flag",
|
||||
Inputs = new List<InputInfo>()
|
||||
Inputs = new List<InputInfo>
|
||||
{
|
||||
new InputInfo()
|
||||
new()
|
||||
{
|
||||
Name = "reason",
|
||||
Label = Localization["WEBFRONT_ACTION_LABEL_REASON"],
|
||||
},
|
||||
new InputInfo()
|
||||
new()
|
||||
{
|
||||
Name = "PresetReason",
|
||||
Type = "select",
|
||||
@ -358,7 +360,7 @@ namespace WebfrontCore.Controllers
|
||||
{
|
||||
var server = Manager.GetServers().First();
|
||||
|
||||
return await Task.FromResult(RedirectToAction("ExecuteAsync", "Console", new
|
||||
return await Task.FromResult(RedirectToAction("Execute", "Console", new
|
||||
{
|
||||
serverId = server.EndPoint,
|
||||
command = $"{_appConfig.CommandPrefix}{_flagCommandName} @{targetId} {presetReason ?? reason}"
|
||||
@ -367,13 +369,13 @@ namespace WebfrontCore.Controllers
|
||||
|
||||
public IActionResult UnflagForm()
|
||||
{
|
||||
var info = new ActionInfo()
|
||||
var info = new ActionInfo
|
||||
{
|
||||
ActionButtonLabel = Localization["WEBFRONT_ACTION_UNFLAG_NAME"],
|
||||
Name = "Unflag",
|
||||
Inputs = new List<InputInfo>()
|
||||
Inputs = new List<InputInfo>
|
||||
{
|
||||
new InputInfo()
|
||||
new()
|
||||
{
|
||||
Name = "reason",
|
||||
Label = Localization["WEBFRONT_ACTION_LABEL_REASON"],
|
||||
@ -390,7 +392,7 @@ namespace WebfrontCore.Controllers
|
||||
{
|
||||
var server = Manager.GetServers().First();
|
||||
|
||||
return await Task.FromResult(RedirectToAction("ExecuteAsync", "Console", new
|
||||
return await Task.FromResult(RedirectToAction("Execute", "Console", new
|
||||
{
|
||||
serverId = server.EndPoint,
|
||||
command = $"{_appConfig.CommandPrefix}{_unflagCommandName} @{targetId} {reason}"
|
||||
@ -399,25 +401,25 @@ namespace WebfrontCore.Controllers
|
||||
|
||||
public IActionResult KickForm(int id)
|
||||
{
|
||||
var info = new ActionInfo()
|
||||
var info = new ActionInfo
|
||||
{
|
||||
ActionButtonLabel = Localization["WEBFRONT_ACTION_KICK_NAME"],
|
||||
Name = "Kick",
|
||||
Inputs = new List<InputInfo>()
|
||||
Inputs = new List<InputInfo>
|
||||
{
|
||||
new InputInfo()
|
||||
new()
|
||||
{
|
||||
Name = "reason",
|
||||
Label = Localization["WEBFRONT_ACTION_LABEL_REASON"],
|
||||
},
|
||||
new InputInfo()
|
||||
new()
|
||||
{
|
||||
Name = "PresetReason",
|
||||
Type = "select",
|
||||
Label = Localization["WEBFRONT_ACTION_LABEL_PRESET_REASON"],
|
||||
Values = GetPresetPenaltyReasons()
|
||||
},
|
||||
new InputInfo()
|
||||
new()
|
||||
{
|
||||
Name = "targetId",
|
||||
Type = "hidden",
|
||||
@ -433,14 +435,14 @@ namespace WebfrontCore.Controllers
|
||||
|
||||
public async Task<IActionResult> KickAsync(int targetId, string reason, string presetReason = null)
|
||||
{
|
||||
var client = Manager.GetActiveClients().FirstOrDefault(_client => _client.ClientId == targetId);
|
||||
var client = Manager.GetActiveClients().FirstOrDefault(client => client.ClientId == targetId);
|
||||
|
||||
if (client == null)
|
||||
{
|
||||
return BadRequest(Localization["WEBFRONT_ACTION_KICK_DISCONNECT"]);
|
||||
}
|
||||
|
||||
return await Task.FromResult(RedirectToAction("ExecuteAsync", "Console", new
|
||||
return await Task.FromResult(RedirectToAction("Execute", "Console", new
|
||||
{
|
||||
serverId = client.CurrentServer.EndPoint,
|
||||
command = $"{_appConfig.CommandPrefix}{_kickCommandName} {client.ClientNumber} {presetReason ?? reason}"
|
||||
@ -449,9 +451,9 @@ namespace WebfrontCore.Controllers
|
||||
|
||||
private Dictionary<string, string> GetPresetPenaltyReasons() => _appConfig.PresetPenaltyReasons.Values
|
||||
.Concat(_appConfig.GlobalRules)
|
||||
.Concat(_appConfig.Servers.SelectMany(server => server.Rules ?? new string[0]))
|
||||
.Concat(_appConfig.Servers.SelectMany(server => server.Rules ?? Array.Empty<string>()))
|
||||
.Distinct()
|
||||
.Select((value, index) => new
|
||||
.Select((value, _) => new
|
||||
{
|
||||
Value = value
|
||||
})
|
||||
|
@ -22,14 +22,22 @@ namespace WebfrontCore.Controllers
|
||||
{
|
||||
private readonly IMetaServiceV2 _metaService;
|
||||
private readonly StatsConfiguration _config;
|
||||
private readonly IGeoLocationService _geoLocationService;
|
||||
|
||||
public ClientController(IManager manager, IMetaServiceV2 metaService, StatsConfiguration config) : base(manager)
|
||||
public ClientController(IManager manager, IMetaServiceV2 metaService, StatsConfiguration config,
|
||||
IGeoLocationService geoLocationService) : base(manager)
|
||||
{
|
||||
_metaService = metaService;
|
||||
_config = config;
|
||||
_geoLocationService = geoLocationService;
|
||||
}
|
||||
|
||||
public async Task<IActionResult> ProfileAsync(int id, MetaType? metaFilterType, CancellationToken token = default)
|
||||
[Obsolete]
|
||||
public IActionResult ProfileAsync(int id, MetaType? metaFilterType,
|
||||
CancellationToken token = default) => RedirectToAction("Profile", "Client", new
|
||||
{ id, metaFilterType, token });
|
||||
|
||||
public async Task<IActionResult> Profile(int id, MetaType? metaFilterType, CancellationToken token = default)
|
||||
{
|
||||
var client = await Manager.GetClientService().Get(id);
|
||||
|
||||
@ -43,7 +51,8 @@ namespace WebfrontCore.Controllers
|
||||
|
||||
var persistentMetaTask = new[]
|
||||
{
|
||||
_metaService.GetPersistentMetaByLookup(EFMeta.ClientTagV2, EFMeta.ClientTagNameV2, client.ClientId, token),
|
||||
_metaService.GetPersistentMetaByLookup(EFMeta.ClientTagV2, EFMeta.ClientTagNameV2, client.ClientId,
|
||||
token),
|
||||
_metaService.GetPersistentMeta("GravatarEmail", client.ClientId, token)
|
||||
};
|
||||
|
||||
@ -74,6 +83,7 @@ namespace WebfrontCore.Controllers
|
||||
}
|
||||
|
||||
displayLevel = string.IsNullOrEmpty(client.Tag) ? displayLevel : $"{displayLevel} ({client.Tag})";
|
||||
var ingameClient = Manager.GetActiveClients().FirstOrDefault(c => c.ClientId == client.ClientId);
|
||||
|
||||
var clientDto = new PlayerInfo
|
||||
{
|
||||
@ -81,29 +91,38 @@ namespace WebfrontCore.Controllers
|
||||
Level = displayLevel,
|
||||
LevelInt = displayLevelInt,
|
||||
ClientId = client.ClientId,
|
||||
IPAddress = PermissionsSet.HasPermission(WebfrontEntity.IPAddress, WebfrontPermission.Read) ? client.IPAddressString : null,
|
||||
IPAddress = PermissionsSet.HasPermission(WebfrontEntity.ClientIPAddress, WebfrontPermission.Read)
|
||||
? client.IPAddressString
|
||||
: null,
|
||||
NetworkId = client.NetworkId,
|
||||
Meta = new List<InformationResponse>(),
|
||||
Aliases = client.AliasLink.Children
|
||||
.Select(_alias => _alias.Name)
|
||||
.GroupBy(_alias => _alias.StripColors())
|
||||
.Select(alias => (alias.Name, alias.DateAdded))
|
||||
.GroupBy(alias => alias.Name.StripColors())
|
||||
// we want the longest "duplicate" name
|
||||
.Select(_grp => _grp.OrderByDescending(_name => _name.Length).First())
|
||||
.Select(grp => grp.OrderByDescending(item => item.Name.Length).First())
|
||||
.Distinct()
|
||||
.OrderBy(a => a)
|
||||
.ToList(),
|
||||
IPs = PermissionsSet.HasPermission(WebfrontEntity.IPAddress, WebfrontPermission.Read) ? client.AliasLink.Children
|
||||
.Where(i => i.IPAddress != null)
|
||||
.OrderByDescending(i => i.DateAdded)
|
||||
.Select(i => i.IPAddress.ConvertIPtoString())
|
||||
.Prepend(client.CurrentAlias.IPAddress.ConvertIPtoString())
|
||||
.Distinct()
|
||||
.ToList() : new List<string>(),
|
||||
HasActivePenalty = activePenalties.Any(_penalty => _penalty.Type != EFPenalty.PenaltyType.Flag),
|
||||
Online = Manager.GetActiveClients().FirstOrDefault(c => c.ClientId == client.ClientId) != null,
|
||||
IPs = PermissionsSet.HasPermission(WebfrontEntity.ClientIPAddress, WebfrontPermission.Read)
|
||||
? client.AliasLink.Children
|
||||
.Select(alias => (alias.IPAddress.ConvertIPtoString(), alias.DateAdded))
|
||||
.GroupBy(alias => alias.Item1)
|
||||
.Select(grp => grp.OrderByDescending(item => item.DateAdded).First())
|
||||
.Distinct()
|
||||
.ToList()
|
||||
: new List<(string, DateTime)>(),
|
||||
HasActivePenalty = activePenalties.Any(penalty => penalty.Type != EFPenalty.PenaltyType.Flag),
|
||||
Online = ingameClient != null,
|
||||
TimeOnline = (DateTime.UtcNow - client.LastConnection).HumanizeForCurrentCulture(),
|
||||
LinkedAccounts = client.LinkedAccounts,
|
||||
MetaFilterType = metaFilterType
|
||||
MetaFilterType = metaFilterType,
|
||||
ConnectProtocolUrl = ingameClient?.CurrentServer.EventParser.URLProtocolFormat.FormatExt(
|
||||
ingameClient.CurrentServer.ResolvedIpEndPoint.Address.IsInternal()
|
||||
? Program.Manager.ExternalIPAddress
|
||||
: ingameClient.CurrentServer.IP,
|
||||
ingameClient.CurrentServer.Port),
|
||||
CurrentServerName = ingameClient?.CurrentServer?.Hostname,
|
||||
GeoLocationInfo = await _geoLocationService.Locate(client.IPAddressString)
|
||||
};
|
||||
|
||||
var meta = await _metaService.GetRuntimeMeta<InformationResponse>(new ClientPaginationRequest
|
||||
@ -126,9 +145,9 @@ namespace WebfrontCore.Controllers
|
||||
clientDto.Meta.AddRange(Authorized ? meta : meta.Where(m => !m.IsSensitive));
|
||||
|
||||
var strippedName = clientDto.Name.StripColors();
|
||||
ViewBag.Title = strippedName.Substring(strippedName.Length - 1).ToLower()[0] == 's' ?
|
||||
strippedName + "'" :
|
||||
strippedName + "'s";
|
||||
ViewBag.Title = strippedName.Substring(strippedName.Length - 1).ToLower()[0] == 's'
|
||||
? strippedName + "'"
|
||||
: strippedName + "'s";
|
||||
ViewBag.Title += " " + Localization["WEBFRONT_CLIENT_PROFILE_TITLE"];
|
||||
ViewBag.Description = $"Client information for {strippedName}";
|
||||
ViewBag.Keywords = $"IW4MAdmin, client, profile, {strippedName}";
|
||||
@ -137,13 +156,13 @@ namespace WebfrontCore.Controllers
|
||||
return View("Profile/Index", clientDto);
|
||||
}
|
||||
|
||||
public async Task<IActionResult> PrivilegedAsync()
|
||||
public async Task<IActionResult> Privileged()
|
||||
{
|
||||
if (Manager.GetApplicationSettings().Configuration().EnablePrivilegedUserPrivacy && !Authorized)
|
||||
{
|
||||
return RedirectToAction("Index", "Home");
|
||||
}
|
||||
|
||||
|
||||
var admins = (await Manager.GetClientService().GetPrivilegedClients())
|
||||
.OrderByDescending(_client => _client.Level)
|
||||
.ThenBy(_client => _client.Name);
|
||||
@ -160,7 +179,8 @@ namespace WebfrontCore.Controllers
|
||||
adminsDict[admin.Level].Add(new ClientInfo()
|
||||
{
|
||||
Name = admin.Name,
|
||||
ClientId = admin.ClientId
|
||||
ClientId = admin.ClientId,
|
||||
LastConnection = admin.LastConnection
|
||||
});
|
||||
}
|
||||
|
||||
@ -171,7 +191,7 @@ namespace WebfrontCore.Controllers
|
||||
return View("Privileged/Index", adminsDict);
|
||||
}
|
||||
|
||||
public async Task<IActionResult> FindAsync(string clientName)
|
||||
public async Task<IActionResult> Find(string clientName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(clientName))
|
||||
{
|
||||
@ -189,11 +209,13 @@ namespace WebfrontCore.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
ViewBag.Title = $"{clientsDto.Count} {Localization["WEBFRONT_CLIENT_SEARCH_MATCHING"]} \"{clientName}\"";
|
||||
ViewBag.SearchTerm = clientName;
|
||||
ViewBag.ResultCount = clientsDto.Count;
|
||||
return View("Find/Index", clientsDto);
|
||||
}
|
||||
|
||||
public IActionResult Meta(int id, int count, int offset, long? startAt, MetaType? metaFilterType, CancellationToken token)
|
||||
public IActionResult Meta(int id, int count, int offset, long? startAt, MetaType? metaFilterType,
|
||||
CancellationToken token)
|
||||
{
|
||||
var request = new ClientPaginationRequest
|
||||
{
|
||||
|
@ -42,15 +42,20 @@ namespace IW4MAdmin.Plugins.Web.StatsWeb.Controllers
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public IActionResult TopPlayersAsync()
|
||||
public IActionResult TopPlayers(string serverId = null)
|
||||
{
|
||||
ViewBag.Title = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_STATS_INDEX_TITLE"];
|
||||
ViewBag.Description = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_STATS_INDEX_DESC"];
|
||||
ViewBag.Servers = _manager.GetServers()
|
||||
.Select(_server => new ServerInfo() {Name = _server.Hostname, ID = _server.EndPoint});
|
||||
ViewBag.Localization = _translationLookup;
|
||||
ViewBag.SelectedServerId = serverId;
|
||||
|
||||
return View("~/Views/Client/Statistics/Index.cshtml");
|
||||
return View("~/Views/Client/Statistics/Index.cshtml", _manager.GetServers()
|
||||
.Select(server => new ServerInfo
|
||||
{
|
||||
Name = server.Hostname,
|
||||
IPAddress = server.IP,
|
||||
Port = server.Port
|
||||
}));
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
@ -169,7 +174,7 @@ namespace IW4MAdmin.Plugins.Web.StatsWeb.Controllers
|
||||
|
||||
var penalty = await context.Penalties
|
||||
.Select(_penalty => new
|
||||
{_penalty.OffenderId, _penalty.PenaltyId, _penalty.When, _penalty.AutomatedOffense})
|
||||
{ _penalty.OffenderId, _penalty.PenaltyId, _penalty.When, _penalty.AutomatedOffense })
|
||||
.FirstOrDefaultAsync(_penalty => _penalty.PenaltyId == penaltyId);
|
||||
|
||||
if (penalty == null)
|
||||
|
@ -39,7 +39,7 @@ namespace WebfrontCore.Controllers
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
return View("Index", Manager.GetApplicationSettings().Configuration());
|
||||
return RedirectToAction("Files");
|
||||
}
|
||||
|
||||
public async Task<IActionResult> Files()
|
||||
@ -256,4 +256,4 @@ namespace WebfrontCore.Controllers
|
||||
.Where(_attr => _attr.GetType() == typeof(ConfigurationIgnore))
|
||||
.FirstOrDefault() as ConfigurationIgnore) != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -34,11 +34,11 @@ namespace WebfrontCore.Controllers
|
||||
return View(activeServers);
|
||||
}
|
||||
|
||||
public async Task<IActionResult> ExecuteAsync(long serverId, string command)
|
||||
public async Task<IActionResult> Execute(long serverId, string command)
|
||||
{
|
||||
var server = Manager.GetServers().First(s => s.EndPoint == serverId);
|
||||
|
||||
var client = new EFClient()
|
||||
var client = new EFClient
|
||||
{
|
||||
ClientId = Client.ClientId,
|
||||
Level = Client.Level,
|
||||
@ -50,7 +50,7 @@ namespace WebfrontCore.Controllers
|
||||
}
|
||||
};
|
||||
|
||||
var remoteEvent = new GameEvent()
|
||||
var remoteEvent = new GameEvent
|
||||
{
|
||||
Type = GameEvent.EventType.Command,
|
||||
Data = command.StartsWith(_appconfig.CommandPrefix) ||
|
||||
@ -97,15 +97,15 @@ namespace WebfrontCore.Controllers
|
||||
{
|
||||
response = new[]
|
||||
{
|
||||
new CommandResponseInfo()
|
||||
new CommandResponseInfo
|
||||
{
|
||||
ClientId = client.ClientId,
|
||||
Response = Utilities.CurrentLocalization.LocalizationIndex["COMMADS_RESTART_SUCCESS"]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return View("_Response", response);
|
||||
|
||||
return remoteEvent.Failed ? StatusCode(400, response) : Ok(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -30,49 +30,15 @@ namespace WebfrontCore.Controllers
|
||||
return View(showOnly);
|
||||
}
|
||||
|
||||
public async Task<IActionResult> ListAsync(int offset = 0, EFPenalty.PenaltyType showOnly = EFPenalty.PenaltyType.Any, bool hideAutomatedPenalties = true)
|
||||
public async Task<IActionResult> ListAsync(int offset = 0, int count = 30, EFPenalty.PenaltyType showOnly = EFPenalty.PenaltyType.Any, bool hideAutomatedPenalties = true)
|
||||
{
|
||||
return await Task.FromResult(View("_List", new ViewModels.PenaltyFilterInfo()
|
||||
return await Task.FromResult(View("_List", new ViewModels.PenaltyFilterInfo
|
||||
{
|
||||
Offset = offset,
|
||||
Count = count,
|
||||
ShowOnly = showOnly,
|
||||
IgnoreAutomated = hideAutomatedPenalties
|
||||
}));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// retrieves all permanent bans ordered by ban date
|
||||
/// if request is authorized, it will include the client's ip address.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task<IActionResult> PublicAsync()
|
||||
{
|
||||
IList<PenaltyInfo> penalties;
|
||||
|
||||
await using var ctx = _contextFactory.CreateContext(false);
|
||||
var iqPenalties = ctx.Penalties
|
||||
.AsNoTracking()
|
||||
.Where(p => p.Type == EFPenalty.PenaltyType.Ban && p.Active)
|
||||
.OrderByDescending(_penalty => _penalty.When)
|
||||
.Select(p => new PenaltyInfo()
|
||||
{
|
||||
Id = p.PenaltyId,
|
||||
OffenderId = p.OffenderId,
|
||||
OffenderName = p.Offender.CurrentAlias.Name,
|
||||
OffenderNetworkId = (ulong)p.Offender.NetworkId,
|
||||
OffenderIPAddress = Authorized ? p.Offender.CurrentAlias.IPAddress.ConvertIPtoString() : null,
|
||||
Offense = p.Offense,
|
||||
PunisherId = p.PunisherId,
|
||||
PunisherNetworkId = (ulong)p.Punisher.NetworkId,
|
||||
PunisherName = p.Punisher.CurrentAlias.Name,
|
||||
PunisherIPAddress = Authorized ? p.Punisher.CurrentAlias.IPAddress.ConvertIPtoString() : null,
|
||||
TimePunished = p.When,
|
||||
AutomatedOffense = Authorized ? p.AutomatedOffense : null,
|
||||
});
|
||||
|
||||
penalties = await iqPenalties.ToListAsync();
|
||||
|
||||
return Json(penalties);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ namespace WebfrontCore.Controllers
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var serverInfo = new ServerInfo()
|
||||
var serverInfo = new ServerInfo
|
||||
{
|
||||
Name = s.Hostname,
|
||||
ID = s.EndPoint,
|
||||
@ -44,7 +44,7 @@ namespace WebfrontCore.Controllers
|
||||
ClientId = p.ClientId,
|
||||
Level = p.Level.ToLocalizedLevelName(),
|
||||
LevelInt = (int) p.Level,
|
||||
ZScore = p.GetAdditionalProperty<EFClientStatistics>(IW4MAdmin.Plugins.Stats.Helpers.StatManager
|
||||
ZScore = p.GetAdditionalProperty<EFClientStatistics>(StatManager
|
||||
.CLIENT_STATS_KEY)?.ZScore
|
||||
}).ToList(),
|
||||
ChatHistory = s.ChatHistory.ToList(),
|
||||
@ -55,37 +55,40 @@ namespace WebfrontCore.Controllers
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public ActionResult Scoreboard()
|
||||
public ActionResult Scoreboard(string serverId)
|
||||
{
|
||||
ViewBag.Title = Localization["WEBFRONT_TITLE_SCOREBOARD"];
|
||||
ViewBag.SelectedServerId = string.IsNullOrEmpty(serverId) ? Manager.GetServers().FirstOrDefault()?.ToString() : serverId;
|
||||
|
||||
return View(ProjectScoreboard(Manager.GetServers(), null, true));
|
||||
return View(ProjectScoreboard(Manager.GetServers(), null, true, false));
|
||||
}
|
||||
|
||||
[HttpGet("[controller]/{id}/scoreboard")]
|
||||
public ActionResult Scoreboard(long id, [FromQuery]string order = null, [FromQuery] bool down = true)
|
||||
public ActionResult Scoreboard(string id, [FromQuery]string order = null, [FromQuery] bool down = true)
|
||||
{
|
||||
var server = Manager.GetServers().FirstOrDefault(srv => srv.EndPoint == id);
|
||||
|
||||
var server = Manager.GetServers().FirstOrDefault(srv => srv.ToString() == id);
|
||||
|
||||
if (server == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
ViewBag.SelectedServerId = id;
|
||||
return View("_Scoreboard", ProjectScoreboard(new[] {server}, order, down).First());
|
||||
}
|
||||
|
||||
private static IEnumerable<ScoreboardInfo> ProjectScoreboard(IEnumerable<Server> servers, string order,
|
||||
bool down)
|
||||
bool down, bool includeDetails = true)
|
||||
{
|
||||
return servers.Select(server => new ScoreboardInfo
|
||||
return servers.Select((server, index) => new ScoreboardInfo
|
||||
{
|
||||
OrderByKey = order,
|
||||
ShouldOrderDescending = down,
|
||||
MapName = server.CurrentMap.ToString(),
|
||||
ServerName = server.Hostname,
|
||||
ServerId = server.EndPoint,
|
||||
ClientInfo = server.GetClientsAsList().Select(client =>
|
||||
ServerId = server.ToString(),
|
||||
ClientInfo = index == 0 && !includeDetails || includeDetails ? server.GetClientsAsList().Select(client =>
|
||||
new
|
||||
{
|
||||
stats = client.GetAdditionalProperty<EFClientStatistics>(StatManager.CLIENT_STATS_KEY),
|
||||
@ -104,7 +107,7 @@ namespace WebfrontCore.Controllers
|
||||
ZScore = clientData.stats?.ZScore == null || clientData.stats.ZScore == 0 ? null : clientData.stats.ZScore,
|
||||
Team = clientData.client.Team
|
||||
})
|
||||
.ToList()
|
||||
.ToList() : new List<ClientScoreboardInfo>()
|
||||
}).ToList();
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Data.Models.Client;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using static SharedLibraryCore.GameEvent;
|
||||
|
||||
namespace WebfrontCore.Middleware
|
||||
@ -81,7 +82,7 @@ namespace WebfrontCore.Middleware
|
||||
// they've been removed
|
||||
if (!_privilegedClientIds.Contains(clientId) && clientId != 1)
|
||||
{
|
||||
await context.SignOutAsync();
|
||||
await context.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,8 +2,19 @@
|
||||
|
||||
public enum WebfrontEntity
|
||||
{
|
||||
IPAddress,
|
||||
MetaAliasUpdate
|
||||
ClientIPAddress,
|
||||
ClientGuid,
|
||||
ClientLevel,
|
||||
MetaAliasUpdate,
|
||||
Penalty,
|
||||
PrivilegedClientsPage,
|
||||
HelpPage,
|
||||
ConsolePage,
|
||||
ConfigurationPage,
|
||||
AuditPage,
|
||||
RecentPlayersPage,
|
||||
ProfilePage,
|
||||
AdminMenu
|
||||
}
|
||||
|
||||
public enum WebfrontPermission
|
||||
|
@ -131,6 +131,8 @@ namespace WebfrontCore
|
||||
Program.ApplicationServiceProvider.GetRequiredService<IServerDistributionCalculator>());
|
||||
services.AddSingleton(Program.ApplicationServiceProvider
|
||||
.GetRequiredService<IConfigurationHandler<DefaultSettings>>());
|
||||
services.AddSingleton(Program.ApplicationServiceProvider
|
||||
.GetRequiredService<IGeoLocationService>());
|
||||
services.AddSingleton(Program.ApplicationServiceProvider
|
||||
.GetRequiredService<StatsConfiguration>());
|
||||
services.AddSingleton(Program.ApplicationServiceProvider.GetRequiredService<IServerDataViewer>());
|
||||
|
42
WebfrontCore/TagHelpers/HasPermission.cs
Normal file
42
WebfrontCore/TagHelpers/HasPermission.cs
Normal file
@ -0,0 +1,42 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Razor.TagHelpers;
|
||||
using SharedLibraryCore;
|
||||
using SharedLibraryCore.Configuration;
|
||||
using WebfrontCore.Permissions;
|
||||
|
||||
namespace WebfrontCore.TagHelpers;
|
||||
|
||||
[HtmlTargetElement("has-permission")]
|
||||
public class HasPermission : TagHelper
|
||||
{
|
||||
[HtmlAttributeName("entity")] public WebfrontEntity Entity { get; set; }
|
||||
|
||||
[HtmlAttributeName("required-permission")]
|
||||
public WebfrontPermission Permission { get; set; }
|
||||
|
||||
private readonly IDictionary<string, List<string>> _permissionSets;
|
||||
private readonly IHttpContextAccessor _contextAccessor;
|
||||
|
||||
public HasPermission(ApplicationConfiguration appConfig, IHttpContextAccessor contextAccessor)
|
||||
{
|
||||
_permissionSets = appConfig.PermissionSets;
|
||||
_contextAccessor = contextAccessor;
|
||||
}
|
||||
|
||||
public override void Process(TagHelperContext context, TagHelperOutput output)
|
||||
{
|
||||
output.TagName = null;
|
||||
var permissionLevel = _contextAccessor?.HttpContext?.User.Claims
|
||||
.FirstOrDefault(claim => claim.Type == ClaimTypes.Role)?.Value;
|
||||
|
||||
var hasPermission = permissionLevel != null && _permissionSets.ContainsKey(permissionLevel) &&
|
||||
_permissionSets[permissionLevel].HasPermission(Entity, Permission);
|
||||
if (!hasPermission)
|
||||
{
|
||||
output.SuppressOutput();
|
||||
}
|
||||
}
|
||||
}
|
@ -7,11 +7,9 @@ namespace WebfrontCore.ViewComponents
|
||||
{
|
||||
public class PenaltyListViewComponent : ViewComponent
|
||||
{
|
||||
private const int PENALTY_COUNT = 15;
|
||||
|
||||
public async Task<IViewComponentResult> InvokeAsync(int offset, EFPenalty.PenaltyType showOnly, bool ignoreAutomated)
|
||||
public async Task<IViewComponentResult> InvokeAsync(int offset, int count, EFPenalty.PenaltyType showOnly, bool ignoreAutomated)
|
||||
{
|
||||
var penalties = await Program.Manager.GetPenaltyService().GetRecentPenalties(PENALTY_COUNT, offset, showOnly, ignoreAutomated);
|
||||
var penalties = await Program.Manager.GetPenaltyService().GetRecentPenalties(count, offset, showOnly, ignoreAutomated);
|
||||
penalties = User.Identity.IsAuthenticated ? penalties : penalties.Where(p => !p.Sensitive).ToList();
|
||||
|
||||
return View("~/Views/Penalty/PenaltyInfoList.cshtml", penalties);
|
||||
|
@ -94,8 +94,8 @@ namespace WebfrontCore.ViewComponents
|
||||
}).ToList(),
|
||||
ChatHistory = server.ChatHistory.ToList(),
|
||||
Online = !server.Throttled,
|
||||
IPAddress =
|
||||
$"{(server.ResolvedIpEndPoint.Address.IsInternal() ? Program.Manager.ExternalIPAddress : server.IP)}:{server.Port}",
|
||||
IPAddress = server.IP,
|
||||
ExternalIPAddress = server.ResolvedIpEndPoint.Address.IsInternal() ? Program.Manager.ExternalIPAddress : server.IP,
|
||||
ConnectProtocolUrl = server.EventParser.URLProtocolFormat.FormatExt(
|
||||
server.ResolvedIpEndPoint.Address.IsInternal() ? Program.Manager.ExternalIPAddress : server.IP,
|
||||
server.Port)
|
||||
|
@ -16,22 +16,16 @@ namespace WebfrontCore.ViewComponents
|
||||
_config = config;
|
||||
}
|
||||
|
||||
public async Task<IViewComponentResult> InvokeAsync(int count, int offset, long? serverId = null)
|
||||
public async Task<IViewComponentResult> InvokeAsync(int count, int offset, string serverEndpoint = null)
|
||||
{
|
||||
if (serverId == 0)
|
||||
{
|
||||
serverId = null;
|
||||
}
|
||||
|
||||
var server = Plugin.ServerManager.GetServers().FirstOrDefault(_server => _server.EndPoint == serverId);
|
||||
|
||||
if (server != null)
|
||||
{
|
||||
serverId = StatManager.GetIdForServer(server);
|
||||
}
|
||||
var server = Plugin.ServerManager.GetServers()
|
||||
.FirstOrDefault(server => server.ToString() == serverEndpoint);
|
||||
|
||||
var serverId = server is null ? (long?)null : StatManager.GetIdForServer(server);
|
||||
|
||||
ViewBag.UseNewStats = _config?.EnableAdvancedMetrics ?? true;
|
||||
ViewBag.SelectedServerName = server?.Hostname;
|
||||
|
||||
return View("~/Views/Client/Statistics/Components/TopPlayers/_List.cshtml",
|
||||
ViewBag.UseNewStats
|
||||
? await Plugin.Manager.GetNewTopStats(offset, count, serverId)
|
||||
|
@ -14,5 +14,6 @@ namespace WebfrontCore.ViewModels
|
||||
public string Value { get; set; }
|
||||
public Dictionary<string, string> Values { get; set; }
|
||||
public bool Checked { get; set; }
|
||||
public bool Required { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,8 @@ namespace WebfrontCore.ViewModels
|
||||
/// number of items offset from the start of the list
|
||||
/// </summary>
|
||||
public int Offset { get; set; }
|
||||
|
||||
public int Count { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// show only a certain type of penalty
|
||||
|
@ -3,17 +3,16 @@ using SharedLibraryCore.Database.Models;
|
||||
|
||||
namespace WebfrontCore.ViewModels
|
||||
{
|
||||
|
||||
public class ScoreboardInfo
|
||||
{
|
||||
public string ServerName { get; set; }
|
||||
public long ServerId { get; set; }
|
||||
public string MapName { get; set; }
|
||||
public string OrderByKey { get; set; }
|
||||
public bool ShouldOrderDescending { get; set; }
|
||||
public List<ClientScoreboardInfo> ClientInfo { get; set; }
|
||||
public string ServerName { get; set; }
|
||||
public string ServerId { get; set; }
|
||||
public string MapName { get; set; }
|
||||
public string OrderByKey { get; set; }
|
||||
public bool ShouldOrderDescending { get; set; }
|
||||
public List<ClientScoreboardInfo> ClientInfo { get; set; }
|
||||
}
|
||||
|
||||
|
||||
public class ClientScoreboardInfo
|
||||
{
|
||||
public string ClientName { get; set; }
|
||||
|
21
WebfrontCore/ViewModels/SideContextMenuItem.cs
Normal file
21
WebfrontCore/ViewModels/SideContextMenuItem.cs
Normal file
@ -0,0 +1,21 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace WebfrontCore.ViewModels;
|
||||
|
||||
public class SideContextMenuItem
|
||||
{
|
||||
public bool IsLink { get; set; }
|
||||
public bool IsButton { get; set; }
|
||||
public bool IsActive { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string Reference { get; set; }
|
||||
public string Icon { get; set; }
|
||||
public string Tooltip { get; set; }
|
||||
}
|
||||
|
||||
|
||||
public class SideContextMenuItems
|
||||
{
|
||||
public string MenuTitle { get; set; }
|
||||
public List<SideContextMenuItem> Items { get; set; } = new();
|
||||
}
|
54
WebfrontCore/ViewModels/TableInfo.cs
Normal file
54
WebfrontCore/ViewModels/TableInfo.cs
Normal file
@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace WebfrontCore.ViewModels;
|
||||
|
||||
public class TableInfo
|
||||
{
|
||||
public string Header { get; set; }
|
||||
public List<ColumnDefinition> Columns { get; } = new();
|
||||
public List<RowDefinition> Rows { get; } = new();
|
||||
public int InitialRowCount { get; }
|
||||
|
||||
public TableInfo(int initialRowCount = 0)
|
||||
{
|
||||
InitialRowCount = initialRowCount;
|
||||
}
|
||||
}
|
||||
|
||||
public class RowDefinition
|
||||
{
|
||||
public List<string> Datum { get; } = new();
|
||||
}
|
||||
|
||||
public class ColumnDefinition
|
||||
{
|
||||
public string Title { get; set; }
|
||||
public string ColumnSpan { get; set; }
|
||||
}
|
||||
|
||||
public static class TableInfoExtensions
|
||||
{
|
||||
public static TableInfo WithColumns(this TableInfo info, IEnumerable<string> columns)
|
||||
{
|
||||
info.Columns.AddRange(columns.Select(column => new ColumnDefinition
|
||||
{
|
||||
Title = column
|
||||
}));
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
public static TableInfo WithRows<T>(this TableInfo info, IEnumerable<T> source,
|
||||
Func<T, IEnumerable<string>> selector)
|
||||
{
|
||||
info.Rows.AddRange(source.Select(row =>
|
||||
{
|
||||
var rowDef = new RowDefinition();
|
||||
rowDef.Datum.AddRange(selector(row));
|
||||
return rowDef;
|
||||
}));
|
||||
return info;
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
@using System.Text.RegularExpressions
|
||||
@model WebfrontCore.ViewModels.CommunityInfo
|
||||
@{
|
||||
IEnumerable<KeyValuePair<(string, long), string[]>> allRules = new[] {new KeyValuePair<(string, long), string[]>((ViewBag.Localization["WEBFRONT_ABOUT_GLOBAL_RULES"], 0), Model.GlobalRules)};
|
||||
IEnumerable<KeyValuePair<(string, long), string[]>> allRules = new[] { new KeyValuePair<(string, long), string[]>((ViewBag.Localization["WEBFRONT_ABOUT_GLOBAL_RULES"], 0), Model.GlobalRules) };
|
||||
var serverRules = Model.ServerRules?.Where(server => server.Value != null && server.Value.Any()).ToList();
|
||||
if (serverRules?.Any() ?? false)
|
||||
{
|
||||
@ -10,26 +10,29 @@
|
||||
}
|
||||
}
|
||||
|
||||
<div class="row text-break">
|
||||
<div class="content text-wrap mt-20">
|
||||
@if (Model.CommunityInformation.EnableBanner)
|
||||
{
|
||||
<img class="img-fluid mb-3" style="max-height: 250px" src="images/community/banner.png" alt="@Model.CommunityInformation.Name"/>
|
||||
<img class="img-fluid mb-20" style="max-height: 250px" src="images/community/banner.png" alt="@Model.CommunityInformation.Name"/>
|
||||
}
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(Model.CommunityInformation.Name))
|
||||
{
|
||||
<h2 class="mb-4 p-0 col-12 text-center text-md-left">
|
||||
<h2 class="content-title">
|
||||
<color-code value="@Model.CommunityInformation.Name"></color-code>
|
||||
</h2>
|
||||
}
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(Model.CommunityInformation.Description))
|
||||
{
|
||||
<div class="p-4 bg-dark border border-primary mb-4 text-white-50 col-12">
|
||||
<h4 class="text-primary">@ViewBag.Localization["WEBFRONT_ABOUT_TITLE"]</h4>
|
||||
<color-code value="@Model.CommunityInformation.Description"></color-code>
|
||||
<div class="mt-3">
|
||||
@foreach (var social in Model.CommunityInformation.SocialAccounts ?? new SocialAccountConfiguration[0])
|
||||
|
||||
<div class="card m-0 rounded">
|
||||
@if (!string.IsNullOrWhiteSpace(Model.CommunityInformation.Description))
|
||||
{
|
||||
<h5 class="text-primary mt-0">@ViewBag.Localization["WEBFRONT_ABOUT_TITLE"]</h5>
|
||||
<div class="text-md-justify">
|
||||
<color-code value="@Model.CommunityInformation.Description"></color-code>
|
||||
</div>
|
||||
<div class="mt-10">
|
||||
@foreach (var social in Model.CommunityInformation.SocialAccounts ?? Array.Empty<SocialAccountConfiguration>())
|
||||
{
|
||||
<div>
|
||||
<a href="@social.Url" target="_blank" title="@social.Title">
|
||||
@ -39,8 +42,8 @@
|
||||
}
|
||||
else if (!string.IsNullOrWhiteSpace(social.IconUrl))
|
||||
{
|
||||
var url = Uri.TryCreate(social.IconUrl, UriKind.Absolute, out var parsedUrl)
|
||||
? parsedUrl.AbsoluteUri
|
||||
var url = Uri.TryCreate(social.IconUrl, UriKind.Absolute, out var parsedUrl)
|
||||
? parsedUrl.AbsoluteUri
|
||||
: $"images/community/{social.IconUrl}";
|
||||
<img class="img-fluid" style="max-width: 1rem; fill: white" src="@url" alt="@social.Title"/>
|
||||
}
|
||||
@ -49,37 +52,40 @@
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
</div>
|
||||
|
||||
@if (allRules.Any(rule => rule.Value.Any()))
|
||||
{
|
||||
<h2 class="pb-3 p-0 col-12 text-center text-md-left">@ViewBag.Localization["WEBFRONT_ABOUT_COMMUNITY_GUIDELINES"]</h2>
|
||||
<h2 class="content-title mt-20">@ViewBag.Localization["WEBFRONT_ABOUT_COMMUNITY_GUIDELINES"]</h2>
|
||||
}
|
||||
|
||||
@foreach (var ((serverName, id), rules) in allRules)
|
||||
{
|
||||
if (!rules.Any())
|
||||
<div class="card m-0 rounded">
|
||||
@foreach (var ((serverName, id), rules) in allRules)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (!rules.Any())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var start = 1;
|
||||
<div class="col-12 bg-dark p-4 border border-primary mb-4 col-12">
|
||||
<div class="text-primary h4">
|
||||
var start = 1;
|
||||
<h5 class="text-primary mt-0">
|
||||
<color-code value="@serverName"></color-code>
|
||||
</div>
|
||||
</h5>
|
||||
@foreach (var rule in rules)
|
||||
{
|
||||
<div class="text-white-50">
|
||||
<div class="rule">
|
||||
@if (!rule.StartsWith("#") && !Regex.IsMatch(rule, @"^\d+(.|\))"))
|
||||
{
|
||||
<span class="text-white">@start.</span>
|
||||
<span>@start.</span>
|
||||
}
|
||||
<color-code value="@rule"></color-code>
|
||||
<span class="text-muted">
|
||||
<color-code value="@rule"></color-code>
|
||||
</span>
|
||||
</div>
|
||||
start++;
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,16 +1,22 @@
|
||||
@model WebfrontCore.ViewModels.ActionInfo
|
||||
@using Humanizer
|
||||
@model WebfrontCore.ViewModels.ActionInfo
|
||||
@{
|
||||
Layout = null;
|
||||
}
|
||||
<h5 class="modal-title mb-10">@Model.Name.Titleize()</h5>
|
||||
@if (Model.Inputs.Any())
|
||||
{
|
||||
<hr class="mb-10"/>
|
||||
}
|
||||
<form class="action-form @(Model.ShouldRefresh ? "refreshable" : "")" action="/Action/@Model.Action">
|
||||
@foreach (var input in Model.Inputs)
|
||||
{
|
||||
string inputType = input.Type ?? "text";
|
||||
string value = input.Value ?? "";
|
||||
var inputType = input.Type ?? "text";
|
||||
var value = input.Value ?? "";
|
||||
|
||||
if (input.Type != "hidden")
|
||||
{
|
||||
<div class="input-group mb-3">
|
||||
<div class="input-group mb-10">
|
||||
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text" id="basic-addon-@input.Name">@input.Label</span>
|
||||
@ -21,7 +27,9 @@
|
||||
<select name="@input.Name" class="form-control" aria-label="@input.Name" aria-describedby="basic-addon-@input.Name">
|
||||
@foreach (var item in input.Values)
|
||||
{
|
||||
<option value="@item.Key"><color-code value="@item.Value"></color-code></option>
|
||||
<option value="@item.Key">
|
||||
<color-code value="@item.Value"></color-code>
|
||||
</option>
|
||||
}
|
||||
</select>
|
||||
}
|
||||
@ -37,7 +45,7 @@
|
||||
|
||||
else
|
||||
{
|
||||
<input type="@inputType" name="@input.Name" value="@value" class="form-control" placeholder="@input.Placeholder" aria-label="@input.Name" aria-describedby="basic-addon-@input.Name">
|
||||
<input type="@inputType" name="@input.Name" value="@value" class="form-control @(input.Required ? "required" : "")" placeholder="@input.Placeholder" aria-label="@input.Name" aria-describedby="basic-addon-@input.Name">
|
||||
}
|
||||
|
||||
</div>
|
||||
@ -47,5 +55,12 @@
|
||||
<input type="@inputType" name="@input.Name" value="@value" hidden="hidden">
|
||||
}
|
||||
}
|
||||
<button type="submit" class="btn btn-block btn-primary">@Model.ActionButtonLabel</button>
|
||||
@if (Model.Inputs.Any())
|
||||
{
|
||||
<hr class="mb-10"/>
|
||||
}
|
||||
<div class="ml-auto">
|
||||
<button type="submit" class="btn btn-primary">@Model.ActionButtonLabel</button>
|
||||
<a href="#" class="btn mr-5" role="button">Close</a>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -1,26 +1,26 @@
|
||||
@{
|
||||
var loc = SharedLibraryCore.Utilities.CurrentLocalization.LocalizationIndex;
|
||||
}
|
||||
<h4 class="pb-3 text-center">@ViewBag.Title</h4>
|
||||
<div class="content mt-0">
|
||||
<h2 class="content-title mt-20">@ViewBag.Title</h2>
|
||||
|
||||
<table class="table table-striped">
|
||||
<thead class="d-none d-lg-table-header-group">
|
||||
<tr class="bg-primary pt-2 pb-2">
|
||||
<table class="table">
|
||||
<thead class="d-none d-lg-table-header-group">
|
||||
<tr class="bg-primary text-light pt-2 pb-2">
|
||||
<th scope="col">@loc["WEBFRONT_PENALTY_TEMPLATE_TYPE"]</th>
|
||||
<th scope="col">@loc["WEBFRONT_PENALTY_TEMPLATE_ADMIN"]</th>
|
||||
<th scope="col">@loc["WEBFRONT_PENALTY_TEMPLATE_NAME"]</th>
|
||||
<th scope="col">@loc["WEBFRONT_ADMIN_AUDIT_LOG_INFO"]</th>
|
||||
<!--<th scope="col">@loc["WEBFRONT_ADMIN_AUDIT_LOG_PREVIOUS"]</th>-->
|
||||
<th scope="col">@loc["WEBFRONT_ADMIN_AUDIT_LOG_CURRENT"]</th>
|
||||
<th scope="col" class="text-right">@loc["WEBFRONT_ADMIN_AUDIT_LOG_TIME"]</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="audit_log_table_body" class="border-bottom bg-dark">
|
||||
<partial name="_ListAuditLog" />
|
||||
</tbody>
|
||||
|
||||
</table>
|
||||
<span id="load_audit_log_button" class="loader-load-more oi oi-chevron-bottom text-center text-primary w-100 h3 pb-0 mb-0 d-none d-lg-block"></span>
|
||||
</thead>
|
||||
<tbody id="audit_log_table_body" class="border-bottom bg-dark">
|
||||
<partial name="_ListAuditLog"/>
|
||||
</tbody>
|
||||
</table>
|
||||
<i id="loaderLoad" class="loader-load-more oi oi-chevron-bottom text-center text-primary d-none d-lg-block mt-10"></i>
|
||||
</div>
|
||||
|
||||
@section scripts {
|
||||
<environment include="Development">
|
||||
|
@ -1,99 +1,71 @@
|
||||
@using SharedLibraryCore.Dtos
|
||||
@model IEnumerable<AuditInfo>
|
||||
@{
|
||||
var loc = SharedLibraryCore.Utilities.CurrentLocalization.LocalizationIndex;
|
||||
var loc = Utilities.CurrentLocalization.LocalizationIndex;
|
||||
}
|
||||
|
||||
@foreach (var info in Model)
|
||||
{
|
||||
<!-- mobile -->
|
||||
<tr class="d-table-row d-lg-none bg-dark">
|
||||
<th scope="row" class="bg-primary">@loc["WEBFRONT_PENALTY_TEMPLATE_TYPE"]</th>
|
||||
<td class="text-light">
|
||||
@info.Action
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="d-table-row d-lg-none bg-dark">
|
||||
<th scope="row" class="bg-primary">@loc["WEBFRONT_PENALTY_TEMPLATE_ADMIN"]</th>
|
||||
<td>
|
||||
<a asp-controller="Client" asp-action="ProfileAsync" asp-route-id="@info.OriginId" class="link-inverse">
|
||||
<color-code value="@info.OriginName"></color-code>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="d-table-row d-lg-none bg-dark">
|
||||
<th scope="row" class="bg-primary">@loc["WEBFRONT_PENALTY_TEMPLATE_NAME"]</th>
|
||||
<td>
|
||||
@if (info.TargetId != null)
|
||||
{
|
||||
<a asp-controller="Client" asp-action="ProfileAsync" asp-route-id="@info.TargetId" class="link-inverse">
|
||||
<color-code value="@info.TargetName"></color-code>
|
||||
</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span>--</span>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="d-table-row d-lg-none bg-dark">
|
||||
<th scope="row" class="bg-primary">@loc["WEBFRONT_ADMIN_AUDIT_LOG_INFO"]</th>
|
||||
<td class="text-light">
|
||||
@info.Data
|
||||
</td>
|
||||
</tr>
|
||||
@*<tr class="d-table-row d-lg-none bg-dark">
|
||||
<th scope="row" class="bg-primary">@loc["WEBFRONT_ADMIN_AUDIT_LOG_PREVIOUS"]</th>
|
||||
<td class="text-light">
|
||||
@info.OldValue
|
||||
</td>
|
||||
</tr>*@
|
||||
<tr class="d-table-row d-lg-none bg-dark">
|
||||
<th scope="row" class="bg-primary">@loc["WEBFRONT_ADMIN_AUDIT_LOG_CURRENT"]</th>
|
||||
<td class="text-light">
|
||||
@info.NewValue
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="d-table-row d-lg-none bg-dark">
|
||||
<th scope="row" class="w-25 bg-primary" style="border-bottom: 1px solid #222">@loc["WEBFRONT_ADMIN_AUDIT_LOG_TIME"]</th>
|
||||
<td class="text-light mb-2 border-bottom">
|
||||
@info.When.ToString()
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- desktop -->
|
||||
<tr class="d-none d-lg-table-row">
|
||||
<td class="text-light font-weight-bold">
|
||||
<tr class="d-none d-lg-table-row bg-dark-dm bg-light-lm">
|
||||
<td class="font-weight-bold">
|
||||
@info.Action
|
||||
</td>
|
||||
<td>
|
||||
<a asp-controller="Client" asp-action="ProfileAsync" asp-route-id="@info.OriginId" class="link-inverse">
|
||||
<a asp-controller="Client" asp-action="Profile" asp-route-id="@info.OriginId">
|
||||
<color-code value="@info.OriginName"></color-code>
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
@if (info.TargetId != null)
|
||||
{
|
||||
<a asp-controller="Client" asp-action="ProfileAsync" asp-route-id="@info.TargetId" class="link-inverse">
|
||||
<a asp-controller="Client" asp-action="Profile" asp-route-id="@info.TargetId">
|
||||
<color-code value="@info.TargetName"></color-code>
|
||||
</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span>--</span>
|
||||
<span>–</span>
|
||||
}
|
||||
</td>
|
||||
<td class="text-light">
|
||||
@info.Data
|
||||
|
||||
@*<td class="text-light">
|
||||
@info.OldValue
|
||||
</td>*@
|
||||
<td class="text-light">
|
||||
<td>
|
||||
@info.Data
|
||||
<td >
|
||||
@info.NewValue
|
||||
</td>
|
||||
<td class="text-light text-right">
|
||||
<td class="text-right">
|
||||
@info.When.ToString()
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- mobile -->
|
||||
<tr class="d-table-row d-lg-none d-flex bg-dark-dm bg-light-lm">
|
||||
<td class="bg-primary text-light text-right flex-grow-0">
|
||||
<div class="mt-5 mb-5">@loc["WEBFRONT_PENALTY_TEMPLATE_TYPE"]</div>
|
||||
<div class="mt-5 mb-5">@loc["WEBFRONT_PENALTY_TEMPLATE_ADMIN"]</div>
|
||||
<div class="mt-5 mb-5">@loc["WEBFRONT_PENALTY_TEMPLATE_NAME"]</div>
|
||||
<div class="mt-5 mb-5">@loc["WEBFRONT_ADMIN_AUDIT_LOG_INFO"]</div>
|
||||
<div class="mt-5 mb-5">@loc["WEBFRONT_ADMIN_AUDIT_LOG_CURRENT"]</div>
|
||||
<div class="mt-5 mb-5">@loc["WEBFRONT_ADMIN_AUDIT_LOG_TIME"]</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="mt-5 mb-5">@info.Action</div>
|
||||
<a asp-controller="Client" asp-action="Profile" asp-route-id="@info.OriginId" class="link-inverse">
|
||||
<color-code value="@info.OriginName"></color-code>
|
||||
</a>
|
||||
@if (info.TargetId != null)
|
||||
{
|
||||
<a asp-controller="Client" asp-action="Profile" asp-route-id="@info.TargetId" class="mt-5 mb-5">
|
||||
<color-code value="@info.TargetName"></color-code>
|
||||
</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="mt-5 mb-5">–</div>
|
||||
}
|
||||
<div class="mt-5 mb-5"> @info.Data</div>
|
||||
<div class="mt-5 mb-5">@info.NewValue</div>
|
||||
<div class="mt-5 mb-5">@info.When.ToString()</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
|
@ -3,60 +3,71 @@
|
||||
var loc = Utilities.CurrentLocalization.LocalizationIndex;
|
||||
}
|
||||
|
||||
<div class="row d-none d-lg-block ">
|
||||
<h4 class="pb-2 text-center col-12">@ViewBag.Title</h4>
|
||||
<div class="mr-auto ml-auto col-12 col-lg-8 border-bottom">
|
||||
<div class="row pt-2 pb-2 bg-primary">
|
||||
<div class="col-5 ">@loc["WEBFRONT_PENALTY_TEMPLATE_NAME"]</div>
|
||||
<div class="col-4">@loc["WEBFRONT_PROFILE_LEVEL"]</div>
|
||||
<div class="col-3 text-right">@loc["WEBFRONT_SEARCH_LAST_CONNECTED"]</div>
|
||||
</div>
|
||||
<!-- 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>
|
||||
|
||||
<table class="table d-none d-md-table">
|
||||
<thead>
|
||||
<tr class="bg-primary text-light">
|
||||
<td>@loc["WEBFRONT_PENALTY_TEMPLATE_NAME"]</td>
|
||||
<td>@loc["WEBFRONT_PROFILE_LEVEL"]</td>
|
||||
<td class="text-right">@loc["WEBFRONT_SEARCH_LAST_CONNECTED"]</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var client in Model)
|
||||
{
|
||||
<div class="row pt-2 pb-2 bg-dark">
|
||||
<div class="col-5">
|
||||
<a asp-controller="Client" asp-action="ProfileAsync" asp-route-id="@client.ClientId">
|
||||
<tr class="bg-dark-dm bg-light-lm">
|
||||
<td class="col-5">
|
||||
<a asp-controller="Client" asp-action="Profile" asp-route-id="@client.ClientId">
|
||||
<color-code value="@client.Name"></color-code>
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
@if (!ViewBag.Authorized && ViewBag.EnablePrivilegedUserPrivacy)
|
||||
{
|
||||
<div class="col-4 level-color-0">@loc["GLOBAL_PERMISSION_USER"]</div>
|
||||
<td class="col-3 level-color-0">@loc["GLOBAL_PERMISSION_USER"]</td>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="col-4 level-color-@client.LevelInt">@client.Level</div>
|
||||
<td class="col-3 level-color-@client.LevelInt">@client.Level</td>
|
||||
}
|
||||
<div class="col-3 text-right">@client.LastConnectionText</div>
|
||||
</div>
|
||||
<td class="col-4 text-right">@client.LastConnection.HumanizeForCurrentCulture()</td>
|
||||
</tr>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row d-lg-none">
|
||||
<div class="w-100 bg-primary text-center h3 mb-0 p-3" style="border-bottom: 1px solid #222">@ViewBag.Title</div>
|
||||
@foreach (var client in Model)
|
||||
{
|
||||
<div class="col-5 bg-primary font-weight-bold" style="border-bottom: 1px solid #222">
|
||||
<div class="p-2">@loc["WEBFRONT_PENALTY_TEMPLATE_NAME"]</div>
|
||||
<div class="p-2">@loc["WEBFRONT_PROFILE_LEVEL"]</div>
|
||||
<div class="p-2">@loc["WEBFRONT_SEARCH_LAST_CONNECTED"]</div>
|
||||
</div>
|
||||
<div class="col-7 bg-dark border-bottom">
|
||||
<div class="p-2">
|
||||
<a asp-controller="Client" asp-action="ProfileAsync" asp-route-id="@client.ClientId" class="link-inverse">
|
||||
<color-code value="@client.Name"></color-code>
|
||||
</a>
|
||||
</div>
|
||||
@if (!ViewBag.Authorized && ViewBag.EnablePrivilegedUserPrivacy)
|
||||
{
|
||||
<div class="p-2 level-color-0">@loc["GLOBAL_PERMISSION_USER"]</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="p-2 level-color-@client.LevelInt">@client.Level</div>
|
||||
}
|
||||
<div class="p-2 text-white-50">@client.LastConnectionText</div>
|
||||
</div>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
<!--- mobile -->
|
||||
<table class="table bg-dark-dm bg-light-lm d-md-none">
|
||||
<tbody>
|
||||
@foreach (var client in Model)
|
||||
{
|
||||
<tr class="d-flex">
|
||||
<td class="bg-primary text-light">
|
||||
<div>@loc["WEBFRONT_PENALTY_TEMPLATE_NAME"]</div>
|
||||
<div>@loc["WEBFRONT_PROFILE_LEVEL"]</div>
|
||||
<div>@loc["WEBFRONT_SEARCH_LAST_CONNECTED"]</div>
|
||||
</td>
|
||||
<td class="flex-grow">
|
||||
<a asp-controller="Client" asp-action="Profile" asp-route-id="@client.ClientId" class="link-inverse">
|
||||
<color-code value="@client.Name"></color-code>
|
||||
</a>
|
||||
@if (!ViewBag.Authorized && ViewBag.EnablePrivilegedUserPrivacy)
|
||||
{
|
||||
<div class="p-2 level-color-0">@loc["GLOBAL_PERMISSION_USER"]</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="p-2 level-color-@client.LevelInt">@client.Level</div>
|
||||
}
|
||||
<div>@client.LastConnection.HumanizeForCurrentCulture()</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
</div>
|
||||
|
@ -1,30 +1,33 @@
|
||||
@using SharedLibraryCore.Dtos.Meta.Responses
|
||||
@model SharedLibraryCore.Helpers.ResourceQueryHelperResult<MessageResponse>
|
||||
|
||||
<div class="content mt-0">
|
||||
@if (ViewBag.Error != null)
|
||||
{
|
||||
<h4 class="text-red">@SharedLibraryCore.Utilities.FormatExt(ViewBag.Localization["WEBFRONT_INVALID_QUERY"], ViewBag.Error.Message)</h4>
|
||||
<h2 class="content-title text-red mt-20">@Utilities.FormatExt(ViewBag.Localization["WEBFRONT_INVALID_QUERY"], ViewBag.Error.Message)</h2>
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
<h4 class="pb-3 text-center">@SharedLibraryCore.Utilities.FormatExt(ViewBag.Localization["WEBFRONT_STATS_MESSAGES_FOUND"], Model.TotalResultCount.ToString("N0"))</h4>
|
||||
<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>
|
||||
|
||||
<table class="table table-striped table-hover">
|
||||
<table class="table bg-dark-dm bg-light-lm rounded" style="table-layout: fixed">
|
||||
<thead class="d-none d-lg-table-header-group">
|
||||
<tr class="bg-primary pt-2 pb-2">
|
||||
<th scope="col">@ViewBag.Localization["WEBFRONT_PENALTY_TEMPLATE_ADMIN"]</th>
|
||||
<th scope="col">@ViewBag.Localization["WEBFRONT_ACTION_LABEL_MESSAGE"]</th>
|
||||
<th scope="col">@ViewBag.Localization["WEBFRONT_STATS_MESSAGE_SERVER_NAME"]</th>
|
||||
<th scope="col" class="text-right">@ViewBag.Localization["WEBFRONT_ADMIN_AUDIT_LOG_TIME"]</th>
|
||||
<tr class="bg-primary text-light">
|
||||
<th colspan="20%">@ViewBag.Localization["WEBFRONT_PENALTY_TEMPLATE_ADMIN"]</th>
|
||||
<th colspan="45%">@ViewBag.Localization["WEBFRONT_ACTION_LABEL_MESSAGE"]</th>
|
||||
<th colspan="20%">@ViewBag.Localization["WEBFRONT_STATS_MESSAGE_SERVER_NAME"]</th>
|
||||
<th colspan="15%" class="text-right">@ViewBag.Localization["WEBFRONT_ADMIN_AUDIT_LOG_TIME"]</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="message_table_body" class="border-bottom bg-dark">
|
||||
<tbody id="message_table_body">
|
||||
<partial name="~/Views/Client/Message/_Item.cshtml" model="@Model.Results" />
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<span id="load_more_messages_button" class="loader-load-more oi oi-chevron-bottom text-center text-primary w-100 h3 pb-0 mb-0 d-none d-lg-block"></span>
|
||||
<div id="loaderLoad" class="mt-10 m-auto text-center d-none d-lg-block">
|
||||
<i class="loader-load-more oi oi-chevron-bottom "></i>
|
||||
</div>
|
||||
|
||||
@section scripts {
|
||||
<environment include="Development">
|
||||
@ -36,4 +39,5 @@ else
|
||||
});
|
||||
</script>
|
||||
}
|
||||
}
|
||||
}
|
||||
</div>
|
||||
|
@ -5,64 +5,56 @@
|
||||
{
|
||||
<!-- desktop -->
|
||||
<tr class="d-none d-lg-table-row">
|
||||
<td>
|
||||
<a asp-controller="Client" asp-action="ProfileAsync" asp-route-id="@message.ClientId" class="link-inverse">
|
||||
<td colspan="20%" class="text-break">
|
||||
<a asp-controller="Client" asp-action="Profile" asp-route-id="@message.ClientId" class="link-inverse">
|
||||
<color-code value="@message.ClientName"></color-code>
|
||||
</a>
|
||||
</td>
|
||||
<td class="text-light w-50 text-break">
|
||||
<td colspan="45%" class="text-break">
|
||||
@if (message.IsHidden && !ViewBag.Authorized)
|
||||
{
|
||||
<color-code value="@SharedLibraryCore.Utilities.FormatExt(ViewBag.Localization["WEBFRONT_CLIENT_META_CHAT_HIDDEN"], message.HiddenMessage)"></color-code>
|
||||
<color-code value="@Utilities.FormatExt(ViewBag.Localization["WEBFRONT_CLIENT_META_CHAT_HIDDEN"], message.HiddenMessage)"></color-code>
|
||||
}
|
||||
else
|
||||
{
|
||||
<color-code value="@message.Message"></color-code>
|
||||
}
|
||||
</td>
|
||||
<td class="text-light">
|
||||
<td colspan="20%" class="text-break">
|
||||
<color-code value="@(message.ServerName ?? "--")"></color-code>
|
||||
</td>
|
||||
<td class="text-right text-light">
|
||||
<td colspan="15%" class="text-right text-break">
|
||||
@message.When
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- mobile -->
|
||||
<tr class="d-table-row d-lg-none bg-dark">
|
||||
<th scope="row" class="bg-primary">@ViewBag.Localization["WEBFRONT_PENALTY_TEMPLATE_ADMIN"]</th>
|
||||
<td class="text-light">
|
||||
<a asp-controller="Client" asp-action="ProfileAsync" asp-route-id="@message.ClientId" class="link-inverse">
|
||||
<tr class="d-flex d-lg-none">
|
||||
<td class="bg-primary text-light">
|
||||
<div>@ViewBag.Localization["WEBFRONT_PENALTY_TEMPLATE_ADMIN"]</div>
|
||||
<div>@ViewBag.Localization["WEBFRONT_ACTION_LABEL_MESSAGE"]</div>
|
||||
<div>@ViewBag.Localization["WEBFRONT_STATS_MESSAGE_SERVER_NAME"]</div>
|
||||
<div>@ViewBag.Localization["WEBFRONT_ADMIN_AUDIT_LOG_TIME"]</div>
|
||||
</td>
|
||||
<td class="flex-fill">
|
||||
<a asp-controller="Client" asp-action="Profile" asp-route-id="@message.ClientId">
|
||||
<color-code value="@message.ClientName"></color-code>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<div>
|
||||
@if (message.IsHidden && !ViewBag.Authorized)
|
||||
{
|
||||
<color-code value="@Utilities.FormatExt(ViewBag.Localization["WEBFRONT_CLIENT_META_CHAT_HIDDEN"], message.HiddenMessage)"></color-code>
|
||||
}
|
||||
else
|
||||
{
|
||||
<color-code value="@message.Message"></color-code>
|
||||
}
|
||||
</div>
|
||||
<div>
|
||||
<color-code value="@(message.ServerName ?? "--")"></color-code>
|
||||
</div>
|
||||
<div> @message.When</div>
|
||||
|
||||
<tr class="d-table-row d-lg-none bg-dark">
|
||||
<th scope="row" class="bg-primary">@ViewBag.Localization["WEBFRONT_ACTION_LABEL_MESSAGE"]</th>
|
||||
<td class="text-light">
|
||||
@if (message.IsHidden && !ViewBag.Authorized)
|
||||
{
|
||||
<color-code value="@SharedLibraryCore.Utilities.FormatExt(ViewBag.Localization["WEBFRONT_CLIENT_META_CHAT_HIDDEN"], message.HiddenMessage)"></color-code>
|
||||
}
|
||||
else
|
||||
{
|
||||
<color-code value="@message.Message"></color-code>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr class="d-table-row d-lg-none bg-dark">
|
||||
<th scope="row" class="bg-primary">@ViewBag.Localization["WEBFRONT_STATS_MESSAGE_SERVER_NAME"]</th>
|
||||
<td class="text-light">
|
||||
<color-code value="@(message.ServerName ?? "--")"></color-code>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr class="d-table-row d-lg-none bg-dark">
|
||||
<th scope="row" class="bg-primary" style="border-bottom: 1px solid #222">@ViewBag.Localization["WEBFRONT_ADMIN_AUDIT_LOG_TIME"]</th>
|
||||
<td class="text-light mb-2 border-bottom">
|
||||
@message.When
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
|
@ -1,24 +1,31 @@
|
||||
@model Dictionary<SharedLibraryCore.Database.Models.EFClient.Permission, IList<SharedLibraryCore.Dtos.ClientInfo>>
|
||||
<div class="content mt-0">
|
||||
<h4 class="content-title mt-20">@ViewBag.Title</h4>
|
||||
|
||||
<h4 class="pb-3 text-center ">@ViewBag.Title</h4>
|
||||
|
||||
<div class="row border-bottom">
|
||||
@{
|
||||
foreach (var key in Model.Keys)
|
||||
{
|
||||
<div class="col-12 bg-primary pt-2 pb-2">
|
||||
@Utilities.ToLocalizedLevelName(key)
|
||||
</div>
|
||||
|
||||
<div class="col-12 bg-dark pt-2 pb-2">
|
||||
@foreach (var client in Model[key])
|
||||
{
|
||||
<a asp-controller="Client" asp-action="ProfileAsync" asp-route-id="@client.ClientId">
|
||||
<color-code value="@client.Name"></color-code>
|
||||
</a>
|
||||
<br />
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@foreach (var key in Model.Keys)
|
||||
{
|
||||
<table class="table mb-20">
|
||||
<thead>
|
||||
<tr class="level-bgcolor-@((int)key)">
|
||||
<th class="text-light">@key.ToLocalizedLevelName()</th>
|
||||
<th class="text-right font-weight-bold">Last Connected</th>
|
||||
</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>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
</div>
|
||||
|
@ -1,6 +1,8 @@
|
||||
@using SharedLibraryCore.Interfaces
|
||||
@using Data.Models
|
||||
@using Data.Models.Client
|
||||
@using WebfrontCore.Permissions
|
||||
@using WebfrontCore.ViewModels
|
||||
@model SharedLibraryCore.Dtos.PlayerInfo
|
||||
@{
|
||||
var match = System.Text.RegularExpressions.Regex.Match(Model.Name.ToUpper(), "[A-Z]").Value;
|
||||
@ -11,70 +13,39 @@
|
||||
var isTempBanned = Model.ActivePenalty?.Type == EFPenalty.PenaltyType.TempBan;
|
||||
var translationKey = $"WEBFRONT_PROFILE_{Model.ActivePenalty?.Type.ToString().ToUpper()}_INFO";
|
||||
var ignoredMetaTypes = new[] { MetaType.Information, MetaType.Other, MetaType.QuickMessage };
|
||||
|
||||
string ClassForPenaltyType(EFPenalty.PenaltyType type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
EFPenalty.PenaltyType.Ban => "alert-danger",
|
||||
EFPenalty.PenaltyType.Flag => "alert-secondary",
|
||||
EFPenalty.PenaltyType.TempBan => "alert-secondary",
|
||||
_ => "alert"
|
||||
};
|
||||
}
|
||||
|
||||
string ClassForProfileBackground()
|
||||
{
|
||||
return (ViewBag.PermissionsSet as IEnumerable<string>).HasPermission(WebfrontEntity.ClientLevel, WebfrontPermission.Read) ? $"level-bgcolor-{Model.LevelInt}" : "level-bgcolor-0";
|
||||
}
|
||||
}
|
||||
|
||||
<div id="profile_wrapper" class="pb-3 row d-flex flex-column flex-lg-row">
|
||||
<!-- Initial/Avatar Column -->
|
||||
<div id="profile_avatar" class="d-block d-lg-inline-flex flex-column mr-auto ml-auto mr-lg-0 ml-lg-0 justify-content-center text-center level-bgcolor-@(!ViewBag.Authorized && ViewBag.EnablePrivilegedUserPrivacy ? "0" : Model.LevelInt.ToString()) @(isTempBanned ? "penalties-bgcolor-tempban" : "")" style="background-image:url('@string.Format("https://gravatar.com/avatar/{0}?size=168&default=blank&rating=pg", gravatarUrl)')">
|
||||
@if (string.IsNullOrEmpty(gravatarUrl))
|
||||
{
|
||||
<span class="profile-shortcode">@shortCode</span>
|
||||
}
|
||||
</div>
|
||||
<!-- Name/Level Column -->
|
||||
<div class="w-50 d-block d-lg-inline-flex flex-column flex-fill text-center text-lg-left pb-3 pb-lg-0 pt-3 pt-lg-0 pl-3 pr-3 ml-auto mr-auto" style="overflow-wrap: anywhere">
|
||||
<div class="mt-n2 d-block d-lg-inline-flex @(ViewBag.Authorized ? "" : "flex-fill")">
|
||||
<div id="profile_name" class="client-name h1 mb-0">
|
||||
<color-code value="@Model.Name"></color-code>
|
||||
</div>
|
||||
@if (ViewBag.Authorized)
|
||||
{
|
||||
<div id="profile_aliases_btn" class="oi oi-caret-bottom h3 ml-0 ml-lg-2 mb-0 pt-lg-2 mt-lg-1"></div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (ViewBag.Authorized)
|
||||
{
|
||||
<div class="d-flex flex-row justify-content-start flex-fill flex-column flex-lg-row mr-lg-2 mb-2 mb-md-0">
|
||||
<div class="ip-lookup-profile align-self-center mr-0 mr-lg-2 ml-lg-n1" data-ip="@Model.IPAddress"></div>
|
||||
<div id="ip_lookup_country" class="h4 mb-2 mb-lg-0 align-self-center text-muted"></div>
|
||||
</div>
|
||||
|
||||
<div id="profile_aliases" class="text-muted pt-0 pt-lg-2 pb-2">
|
||||
@foreach (var linked in Model.LinkedAccounts)
|
||||
{
|
||||
<div>
|
||||
@Html.ActionLink(linked.Value.ToString("X"), "ProfileAsync", "Client", new { id = linked.Key }, new { @class = "link-inverse" })
|
||||
</div>
|
||||
}
|
||||
@foreach (var alias in Model.Aliases)
|
||||
{
|
||||
<div>
|
||||
<color-code value="@alias"></color-code>
|
||||
</div>
|
||||
}
|
||||
|
||||
@foreach (var ip in Model.IPs)
|
||||
{
|
||||
<div>
|
||||
<a class="ip-locate-link" href="#" data-ip="@ip">@ip</a>
|
||||
<a href="/Client/FindAsync?clientName=@ip" class="oi oi-magnifying-glass text-muted"></a>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@if (Model.ActivePenalty != null && (Model.ActivePenalty.Type != EFPenalty.PenaltyType.Flag || ViewBag.Authorized))
|
||||
{
|
||||
<div class="font-weight-bold h4 mb-0 penalties-color-@Model.ActivePenalty.Type.ToString().ToLower()">
|
||||
<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))
|
||||
{
|
||||
switch (result.MatchValue)
|
||||
{
|
||||
case "reason":
|
||||
<span class="text-white 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>
|
||||
<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-white font-weight-lighter">
|
||||
<span class="text-light-dm font-weight-lighter">
|
||||
@((Model.ActivePenalty.Expires.Value - DateTime.UtcNow).HumanizeForCurrentCulture())
|
||||
</span>
|
||||
break;
|
||||
@ -84,121 +55,261 @@
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
else
|
||||
</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)
|
||||
{
|
||||
if (!ViewBag.Authorized && ViewBag.EnablePrivilegedUserPrivacy)
|
||||
{
|
||||
<div id="profile_level" class="font-weight-bold h4 mb-0 level-color-0">
|
||||
@ViewBag.Localization["GLOBAL_PERMISSION_USER"]
|
||||
<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>
|
||||
}
|
||||
<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>
|
||||
|
||||
else
|
||||
{
|
||||
<div id="profile_level" class="font-weight-bold h4 mb-0 level-color-@Model.LevelInt">
|
||||
@Model.Level
|
||||
<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>
|
||||
</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>
|
||||
</div>
|
||||
</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>
|
||||
</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)))
|
||||
{
|
||||
<i class="oi oi-caret-bottom font-size-12" aria-hidden="true"></i>
|
||||
}
|
||||
</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()">
|
||||
<i class="oi oi-magnifying-glass text-muted mr-5"></i>
|
||||
<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>
|
||||
</has-permission>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pr-lg-0 text-center text-lg-right">
|
||||
@if (ViewBag.Authorized)
|
||||
{
|
||||
@if (!isPermBanned)
|
||||
{
|
||||
<div class="profile-action oi oi-flag h3 ml-2 @(isFlagged ? "text-secondary" : "text-success")" data-action="@(isFlagged ? "unflag" : "flag")" aria-hidden="true"></div>
|
||||
}
|
||||
|
||||
@if (Model.LevelInt < (int)ViewBag.User.Level && !Model.HasActivePenalty)
|
||||
{
|
||||
<div id="profile_action_ban_btn" class="profile-action oi oi-lock-unlocked text-success h3 ml-2" title="Ban Client" data-action="ban" aria-hidden="true"></div>
|
||||
}
|
||||
|
||||
@if (Model.LevelInt < (int)ViewBag.User.Level && Model.HasActivePenalty)
|
||||
{
|
||||
@if (isTempBanned)
|
||||
{
|
||||
<div id="profile_action_ban_btn" class="profile-action oi oi-lock-unlocked text-success h3 ml-2" title="Ban Client" data-action="ban" aria-hidden="true"></div>
|
||||
<div id="profile_action_unban_btn" class="profile-action oi oi-lock-locked penalties-color-tempban h3 ml-2" title="Unban Client" data-action="unban" aria-hidden="true"></div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div id="profile_action_unban_btn" class="profile-action oi oi-lock-locked text-danger h3 ml-2" title="Unban Client" data-action="unban" aria-hidden="true"></div>
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@if (Model.LevelInt != -1)
|
||||
{
|
||||
<div id="profile_action_edit_btn" class="profile-action oi oi-cog text-muted h3 ml-2" title="Client Options" data-action="edit" aria-hidden="true"></div>
|
||||
}
|
||||
}
|
||||
@if (ViewBag.UseNewStats)
|
||||
{
|
||||
<a asp-controller="ClientStatistics" asp-action="Advanced" asp-route-id="@Model.ClientId" class="oi oi-graph text-primary h3 ml-2" title="Stats" aria-hidden="true"></a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="profile_info" class="row d-block d-lg-flex flex-row border-bottom border-top pt-2 pb-2">
|
||||
<partial name="Meta/_Information.cshtml" model="@Model.Meta"/>
|
||||
</div>
|
||||
|
||||
<div class="row border-bottom">
|
||||
<div class="d-md-flex flex-fill">
|
||||
<div class="bg-dark p-2 pl-3 pr-3 text-center text-muted border-0 align-self-stretch align-middle" id="filter_meta_container_button">
|
||||
<span class="text-primary" id="meta_filter_dropdown_icon">▼</span>
|
||||
<a>@ViewBag.Localization["WEBFRONT_CLIENT_META_FILTER"]</a>
|
||||
</div>
|
||||
<div id="filter_meta_container" class="d-none d-md-flex flex-md-fill flex-md-wrap">
|
||||
@{
|
||||
const int defaultTabCount = 5;
|
||||
var metaTypes = Enum.GetValues(typeof(MetaType))
|
||||
.Cast<MetaType>()
|
||||
.Where(type => !ignoredMetaTypes.Contains(type))
|
||||
.OrderByDescending(type => type == MetaType.All)
|
||||
.ToList();
|
||||
var selectedMeta = metaTypes.FirstOrDefault(meta => metaTypes.IndexOf(Model.MetaFilterType ?? MetaType.All) >= defaultTabCount && meta != MetaType.All && meta == Model.MetaFilterType);
|
||||
}
|
||||
@foreach (var type in metaTypes.Take(defaultTabCount - 1).Append(selectedMeta == MetaType.Other ? metaTypes[defaultTabCount - 1] : selectedMeta))
|
||||
{
|
||||
<a asp-action="ProfileAsync" asp-controller="Client"
|
||||
class="meta-filter nav-link p-2 pl-3 pr-3 text-center @(Model.MetaFilterType.HasValue && Model.MetaFilterType.Value.ToString() == type.ToString() ? "btn-primary text-white" : "text-muted")"
|
||||
asp-route-id="@Model.ClientId"
|
||||
asp-route-metaFilterType="@type"
|
||||
data-meta-type="@type">
|
||||
@type.ToTranslatedName()
|
||||
</a>
|
||||
}
|
||||
<div class="d-md-none" id="additional_meta_filter">
|
||||
@foreach (var type in (selectedMeta == MetaType.Other ? metaTypes.Skip(defaultTabCount) : metaTypes.Skip(defaultTabCount).Append(metaTypes[defaultTabCount - 1])).Where(meta => selectedMeta == MetaType.Other || meta != selectedMeta))
|
||||
{
|
||||
<a asp-action="ProfileAsync" asp-controller="Client"
|
||||
class="meta-filter nav-link p-2 pl-3 pr-3 text-center @(Model.MetaFilterType.HasValue && Model.MetaFilterType.Value.ToString() == type.ToString() ? "btn-primary text-white" : "text-muted")"
|
||||
asp-route-id="@Model.ClientId"
|
||||
asp-route-metaFilterType="@type"
|
||||
data-meta-type="@type">
|
||||
@type.ToTranslatedName()
|
||||
<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>
|
||||
</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"/>
|
||||
|
||||
<!-- 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)
|
||||
{
|
||||
<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>
|
||||
|
||||
@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-4 pr-4 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>
|
||||
}
|
||||
<!-- actions desktop -->
|
||||
@{
|
||||
var menuItems = new SideContextMenuItems
|
||||
{
|
||||
MenuTitle = "Actions",
|
||||
};
|
||||
|
||||
if (Model.Online)
|
||||
{
|
||||
menuItems.Items.Add(new SideContextMenuItem
|
||||
{
|
||||
Title = "Join Game",
|
||||
IsLink = true,
|
||||
IsButton = true,
|
||||
Reference = Model.ConnectProtocolUrl,
|
||||
Tooltip = $"Playing on {Model.CurrentServerName.StripColors()}",
|
||||
Icon = "oi-play-circle"
|
||||
});
|
||||
}
|
||||
|
||||
if (Model.LevelInt != -1 && ViewBag.Authorized)
|
||||
{
|
||||
menuItems.Items.Add(new SideContextMenuItem
|
||||
{
|
||||
Title = "Change Level",
|
||||
IsButton = true,
|
||||
Reference = "edit",
|
||||
Icon = "oi-cog",
|
||||
});
|
||||
}
|
||||
|
||||
menuItems.Items.Add(new SideContextMenuItem
|
||||
{
|
||||
Title = "View Stats",
|
||||
IsButton = true,
|
||||
IsLink = true,
|
||||
Reference = Url.Action("Advanced", "ClientStatistics", new { id = Model.ClientId }),
|
||||
Icon = "oi-graph",
|
||||
});
|
||||
|
||||
if (!isPermBanned && ViewBag.Authorized)
|
||||
{
|
||||
menuItems.Items.Add(new SideContextMenuItem
|
||||
{
|
||||
Title = isFlagged ? "Unflag" : "Flag",
|
||||
IsButton = true,
|
||||
Reference = isFlagged ? "unflag" : "flag",
|
||||
Icon = "oi-flag"
|
||||
});
|
||||
}
|
||||
|
||||
if ((Model.LevelInt < (int)ViewBag.User.Level && !Model.HasActivePenalty || isTempBanned) && ViewBag.Authorized)
|
||||
{
|
||||
menuItems.Items.Add(new SideContextMenuItem
|
||||
{
|
||||
Title = "Ban",
|
||||
IsButton = true,
|
||||
Reference = "ban",
|
||||
Icon = "oi-lock-unlocked",
|
||||
});
|
||||
}
|
||||
|
||||
if ((Model.LevelInt < (int)ViewBag.User.Level && Model.HasActivePenalty || isTempBanned) && ViewBag.Authorized)
|
||||
{
|
||||
menuItems.Items.Add(new SideContextMenuItem
|
||||
{
|
||||
Title = "Unban",
|
||||
IsButton = true,
|
||||
Reference = "unban",
|
||||
Icon = "oi-lock-locked",
|
||||
});
|
||||
}
|
||||
}
|
||||
<partial name="_SideContextMenu" for="@menuItems"></partial>
|
||||
|
||||
<div class="row">
|
||||
<div class="oi oi-chevron-bottom text-center mt-2 btn btn-primary btn-block loader-load-more" title="Load more meta" data-action="unban" aria-hidden="true"></div>
|
||||
</div>
|
||||
|
||||
@section targetid {
|
||||
|
@ -1,53 +1,60 @@
|
||||
@using SharedLibraryCore.Dtos.Meta.Responses
|
||||
@model AdministeredPenaltyResponse
|
||||
@{
|
||||
string localizationKey = $"WEBFRONT_CLIENT_META_PENALIZED_{Model.PenaltyType.ToString().ToUpper()}_V2";
|
||||
var localizationKey = $"WEBFRONT_CLIENT_META_PENALIZED_{Model.PenaltyType.ToString().ToUpper()}_V2";
|
||||
}
|
||||
|
||||
<div class="d-inline">
|
||||
@foreach (var match in Utilities.SplitTranslationTokens(localizationKey))
|
||||
<has-permission entity="Penalty" required-permission="Read">
|
||||
@if (TempData["ShowMetaHeader"] as bool? ?? false)
|
||||
{
|
||||
if (match.IsInterpolation)
|
||||
{
|
||||
if (match.MatchValue == "action")
|
||||
{
|
||||
<span class="penalties-color-@Model.PenaltyType.ToString().ToLower()">@match.TranslationValue</span>
|
||||
}
|
||||
|
||||
else if (match.MatchValue == "offender")
|
||||
{
|
||||
<span class="text-highlight">
|
||||
<a class="link-inverse" href="@Model.OffenderClientId">
|
||||
<color-code value="@Model.OffenderName"></color-code>
|
||||
</a>
|
||||
</span>
|
||||
}
|
||||
|
||||
else if (match.MatchValue == "reason")
|
||||
{
|
||||
<span class="text-white">
|
||||
@if (ViewBag.Authorized && !string.IsNullOrEmpty(Model.AutomatedOffense) && Model.PenaltyType != Data.Models.EFPenalty.PenaltyType.Warning)
|
||||
{
|
||||
<span>@Utilities.FormatExt(ViewBag.Localization["WEBFRONT_PROFILE_ANTICHEAT_DETECTION"], Model.AutomatedOffense)</span>
|
||||
<span class="oi oi-list-rich align-top text-primary automated-penalty-info-detailed" data-penalty-id="@Model.PenaltyId" style="margin-top: 0.125rem;" title="@ViewBag.Localization["WEBFRONT_CLIENT_META_AC_METRIC"]"></span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<color-code value="@Model.Offense"></color-code>
|
||||
}
|
||||
|
||||
</span>
|
||||
}
|
||||
|
||||
else if (match.MatchValue == "time")
|
||||
{
|
||||
<span class="text-white">@Model.LengthText</span>
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
<span>@match.MatchValue</span>
|
||||
}
|
||||
<partial name="./_MetaHeader.cshtml" for="@Model.When"/>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="d-inline">
|
||||
@foreach (var match in Utilities.SplitTranslationTokens(localizationKey))
|
||||
{
|
||||
if (match.IsInterpolation)
|
||||
{
|
||||
if (match.MatchValue == "action")
|
||||
{
|
||||
<span class="penalties-color-@Model.PenaltyType.ToString().ToLower()">@match.TranslationValue</span>
|
||||
}
|
||||
|
||||
else if (match.MatchValue == "offender")
|
||||
{
|
||||
<span class="text-highlight">
|
||||
<a class="link-inverse" href="@Model.OffenderClientId">
|
||||
<color-code value="@Model.OffenderName"></color-code>
|
||||
</a>
|
||||
</span>
|
||||
}
|
||||
|
||||
else if (match.MatchValue == "reason")
|
||||
{
|
||||
<span class="text-light-dm text-dark-lm">
|
||||
@if (ViewBag.Authorized && !string.IsNullOrEmpty(Model.AutomatedOffense) && Model.PenaltyType != Data.Models.EFPenalty.PenaltyType.Warning)
|
||||
{
|
||||
<span>@Utilities.FormatExt(ViewBag.Localization["WEBFRONT_PROFILE_ANTICHEAT_DETECTION"], Model.AutomatedOffense)</span>
|
||||
<span class="oi oi-list-rich align-top text-primary automated-penalty-info-detailed" data-penalty-id="@Model.PenaltyId" style="margin-top: 0.125rem;" title="@ViewBag.Localization["WEBFRONT_CLIENT_META_AC_METRIC"]"></span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<color-code value="@Model.Offense"></color-code>
|
||||
}
|
||||
|
||||
</span>
|
||||
}
|
||||
|
||||
else if (match.MatchValue == "time")
|
||||
{
|
||||
<span class="text-light-dm text-dark-lm">@Model.LengthText</span>
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
<span>@match.MatchValue</span>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</has-permission>
|
||||
|
@ -4,6 +4,11 @@
|
||||
var localizationKey = $"WEBFRONT_CLIENT_META_CONNECTION_{Model.ConnectionType.ToString().ToUpper()}";
|
||||
}
|
||||
|
||||
@if (TempData["ShowMetaHeader"] as bool? ?? false)
|
||||
{
|
||||
<partial name="./_MetaHeader.cshtml" for="@Model.When"/>
|
||||
}
|
||||
|
||||
@foreach (var token in Utilities.SplitTranslationTokens(localizationKey))
|
||||
{
|
||||
if (token.IsInterpolation)
|
||||
@ -11,10 +16,10 @@
|
||||
switch (token.MatchValue)
|
||||
{
|
||||
case "action":
|
||||
<span class="@(Model.ConnectionType == Reference.ConnectionType.Connect ? "text-light-green" : "text-warning")">@token.TranslationValue</span>
|
||||
<span class="@(Model.ConnectionType == Reference.ConnectionType.Connect ? "text-light-green" : "text-secondary")">@token.TranslationValue</span>
|
||||
break;
|
||||
case "server":
|
||||
<span class="text-white">
|
||||
<span class="text-light-dm text-dark-lm">
|
||||
<color-code value="@Model.ServerName"></color-code>
|
||||
</span>
|
||||
break;
|
||||
|
@ -1,20 +1,20 @@
|
||||
@model IEnumerable<SharedLibraryCore.Dtos.Meta.Responses.InformationResponse>
|
||||
@{
|
||||
var informationMeta = Model
|
||||
.Where(_meta => _meta.Type == SharedLibraryCore.Interfaces.MetaType.Information)
|
||||
.OrderBy(_meta => _meta.Order)
|
||||
.GroupBy(_meta => _meta.Column)
|
||||
.OrderBy(_grouping => _grouping.Key);
|
||||
.Where(meta => meta.Type == SharedLibraryCore.Interfaces.MetaType.Information)
|
||||
.OrderBy(meta => meta.Order)
|
||||
.Select((meta, i) => new { index = i, meta })
|
||||
.GroupBy(meta => meta.index / 5);
|
||||
}
|
||||
|
||||
@foreach (var metaColumn in informationMeta)
|
||||
{
|
||||
<div class="text-center text-lg-left mr-0 mr-lg-4">
|
||||
<div class="mr-20">
|
||||
@foreach (var meta in metaColumn)
|
||||
{
|
||||
<div class="profile-meta-entry" title="@meta.ToolTipText">
|
||||
<div class="profile-meta-entry font-size-12" data-toggle="@(!string.IsNullOrEmpty(meta.meta.ToolTipText) ? "tooltip" : "")" data-title="@meta.meta.ToolTipText" data-placement="bottom">
|
||||
|
||||
@{var results = Utilities.SplitTranslationTokens(meta.Key);}
|
||||
@{var results = Utilities.SplitTranslationTokens(meta.meta.Key);}
|
||||
|
||||
@if (results.Any(_result => _result.IsInterpolation))
|
||||
{
|
||||
@ -22,7 +22,7 @@
|
||||
{
|
||||
if (result.IsInterpolation)
|
||||
{
|
||||
<span class="profile-meta-value text-primary"><color-code value="@meta.Value"></color-code></span>
|
||||
<span class="profile-meta-value text-primary"><color-code value="@meta.meta.Value"></color-code></span>
|
||||
}
|
||||
|
||||
else
|
||||
@ -34,8 +34,8 @@
|
||||
|
||||
else
|
||||
{
|
||||
<span class="profile-meta-value text-primary"><color-code value="@meta.Value"></color-code></span>
|
||||
<span class="profile-meta-title text-muted"> @meta.Key</span>
|
||||
<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>
|
||||
}
|
||||
|
@ -1,17 +1,24 @@
|
||||
@using SharedLibraryCore.Dtos.Meta.Responses
|
||||
@model MessageResponse
|
||||
|
||||
@if (TempData["ShowMetaHeader"] as bool? ?? false)
|
||||
{
|
||||
<partial name="./_MetaHeader.cshtml" for="@Model.When"/>
|
||||
}
|
||||
|
||||
<span class="client-message" data-serverid="@Model.ServerId" data-when="@Model.When.ToFileTimeUtc()">
|
||||
<span class="oi oi-chevron-right text-white-50 align-middle client-message-prefix" title="@ViewBag.Localization["WEBFRONT_PROFILE_MESSAGE_CONTEXT"]" style="font-size: 0.75rem; margin-top: -0.256rem"></span>
|
||||
<span data-title="View Context" data-toggle="tooltip" data-placement="right">
|
||||
<span class="oi oi-chevron-right align-middle client-message-prefix" style="font-size: 0.75rem; margin-top: -0.256rem"></span>
|
||||
</span>
|
||||
<span class="text-muted @(Model.IsQuickMessage ? "font-weight-bold" : "")">
|
||||
@if (!Model.SentIngame)
|
||||
{
|
||||
<span>[<span class="text-primary">@ViewBag.Localization["WEBFRONT_PROFILE_MESSAGE_EXTERNAL"]</span>]</span>
|
||||
}
|
||||
|
||||
|
||||
@if (Model.IsHidden && !ViewBag.Authorized)
|
||||
{
|
||||
<color-code value="@SharedLibraryCore.Utilities.FormatExt(ViewBag.Localization["WEBFRONT_CLIENT_META_CHAT_HIDDEN"], Model.HiddenMessage)"></color-code>
|
||||
<color-code value="@Utilities.FormatExt(ViewBag.Localization["WEBFRONT_CLIENT_META_CHAT_HIDDEN"], Model.HiddenMessage)"></color-code>
|
||||
}
|
||||
|
||||
else
|
||||
|
@ -0,0 +1,6 @@
|
||||
@model DateTime
|
||||
@{ Layout = null;}
|
||||
<div class="pt-5 text-light-dm text-dark-lm font-size-18 ">
|
||||
<span>@Model.HumanizeForCurrentCulture()</span>
|
||||
</div>
|
||||
<hr/>
|
@ -1,29 +1,35 @@
|
||||
@model SharedLibraryCore.Dtos.Meta.Responses.PermissionLevelChangedResponse
|
||||
|
||||
@foreach (var token in Utilities.SplitTranslationTokens("WEBFRONT_CLIENT_META_PERMISSION_CHANGED"))
|
||||
{
|
||||
if (token.IsInterpolation)
|
||||
<has-permission entity="ClientLevel" required-permission="Read">
|
||||
@if (TempData["ShowMetaHeader"] as bool? ?? false)
|
||||
{
|
||||
switch (token.MatchValue)
|
||||
<partial name="./_MetaHeader.cshtml" for="@Model.When"/>
|
||||
}
|
||||
@foreach (var token in Utilities.SplitTranslationTokens("WEBFRONT_CLIENT_META_PERMISSION_CHANGED"))
|
||||
{
|
||||
if (token.IsInterpolation)
|
||||
{
|
||||
case "permission":
|
||||
<span class="level-color-@((int)Model.CurrentPermissionLevel)">@Model.CurrentPermissionLevel.ToLocalizedLevelName()</span>
|
||||
break;
|
||||
case "originClient":
|
||||
<span class="text-highlight">
|
||||
<a class="link-inverse" href="@Model.ChangedById">
|
||||
<color-code value="@Model.ChangedByName"></color-code>
|
||||
</a>
|
||||
</span>
|
||||
break;
|
||||
case "type":
|
||||
<span class="text-white-50">@token.TranslationValue</span>
|
||||
break;
|
||||
switch (token.MatchValue)
|
||||
{
|
||||
case "permission":
|
||||
<span class="level-color-@((int)Model.CurrentPermissionLevel)">@Model.CurrentPermissionLevel.ToLocalizedLevelName()</span>
|
||||
break;
|
||||
case "originClient":
|
||||
<span class="text-highlight">
|
||||
<a asp-controller="Client" asp-action="Profile" asp-route-id="@Model.ChangedById">
|
||||
<color-code value="@Model.ChangedByName"></color-code>
|
||||
</a>
|
||||
</span>
|
||||
break;
|
||||
case "type":
|
||||
<span class="text-white-50">@token.TranslationValue</span>
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
<span class="text-muted">@token.MatchValue</span>
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
<span class="text-muted">@token.MatchValue</span>
|
||||
}
|
||||
}
|
||||
</has-permission>
|
||||
|
@ -2,72 +2,79 @@
|
||||
@model ReceivedPenaltyResponse
|
||||
|
||||
@{
|
||||
string localizationKey = $"WEBFRONT_CLIENT_META_WAS_PENALIZED_{Model.PenaltyType.ToString().ToUpper()}_V2";
|
||||
var localizationKey = $"WEBFRONT_CLIENT_META_WAS_PENALIZED_{Model.PenaltyType.ToString().ToUpper()}_V2";
|
||||
}
|
||||
|
||||
<div class="d-inline">
|
||||
@foreach (var match in Utilities.SplitTranslationTokens(localizationKey))
|
||||
<has-permission entity="ClientLevel" required-permission="Read">
|
||||
@if (TempData["ShowMetaHeader"] as bool? ?? false)
|
||||
{
|
||||
if (match.IsInterpolation)
|
||||
{
|
||||
if (match.MatchValue == "action")
|
||||
{
|
||||
<span class="penalties-color-@Model.PenaltyType.ToString().ToLower()">@match.TranslationValue</span>
|
||||
}
|
||||
|
||||
else if (match.MatchValue == "punisher")
|
||||
{
|
||||
<span class="text-highlight">
|
||||
<a class="link-inverse" href="@Model.PunisherClientId">
|
||||
<color-code value="@Model.PunisherName"></color-code>
|
||||
</a>
|
||||
</span>
|
||||
}
|
||||
|
||||
else if (match.MatchValue == "reason")
|
||||
{
|
||||
<span class="text-white">
|
||||
@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>
|
||||
<span class="oi oi-list-rich align-top text-primary automated-penalty-info-detailed" data-penalty-id="@Model.PenaltyId" style="margin-top: 0.125rem;" title="@ViewBag.Localization["WEBFRONT_CLIENT_META_AC_METRIC"]"></span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<color-code value="@Model.Offense"></color-code>
|
||||
}
|
||||
|
||||
</span>
|
||||
}
|
||||
|
||||
else if (match.MatchValue == "time")
|
||||
{
|
||||
<span class="text-white">@Model.LengthText</span>
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
<span>@match.MatchValue</span>
|
||||
}
|
||||
<partial name="./_MetaHeader.cshtml" for="@Model.When"/>
|
||||
}
|
||||
|
||||
@if (Model.ClientId != Model.OffenderClientId)
|
||||
{
|
||||
<span>—</span>
|
||||
@foreach (var helperResult in Utilities.SplitTranslationTokens("WEBFRONT_PROFILE_LINKED_ACCOUNT"))
|
||||
<div class="d-inline">
|
||||
@foreach (var match in Utilities.SplitTranslationTokens(localizationKey))
|
||||
{
|
||||
if (!helperResult.IsInterpolation)
|
||||
if (match.IsInterpolation)
|
||||
{
|
||||
<span>@helperResult.MatchValue</span>
|
||||
if (match.MatchValue == "action")
|
||||
{
|
||||
<span class="penalties-color-@Model.PenaltyType.ToString().ToLower()">@match.TranslationValue</span>
|
||||
}
|
||||
|
||||
else if (match.MatchValue == "punisher")
|
||||
{
|
||||
<span class="text-highlight">
|
||||
<a class="link-inverse" href="@Model.PunisherClientId">
|
||||
<color-code value="@Model.PunisherName"></color-code>
|
||||
</a>
|
||||
</span>
|
||||
}
|
||||
|
||||
else if (match.MatchValue == "reason")
|
||||
{
|
||||
<span class="text-light-dm text-dark-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>
|
||||
<span class="oi oi-list-rich align-top text-primary automated-penalty-info-detailed" data-penalty-id="@Model.PenaltyId" style="margin-top: 0.125rem;" title="@ViewBag.Localization["WEBFRONT_CLIENT_META_AC_METRIC"]"></span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<color-code value="@Model.Offense"></color-code>
|
||||
}
|
||||
|
||||
</span>
|
||||
}
|
||||
|
||||
else if (match.MatchValue == "time")
|
||||
{
|
||||
<span class="text-light-dm text-dark-lm">@Model.LengthText</span>
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
<a class="link-inverse" href="@Model.OffenderClientId">
|
||||
<color-code value="@Model.OffenderName"></color-code>
|
||||
</a>
|
||||
<span>@match.MatchValue</span>
|
||||
}
|
||||
}
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (Model.ClientId != Model.OffenderClientId)
|
||||
{
|
||||
<span>—</span>
|
||||
@foreach (var helperResult in Utilities.SplitTranslationTokens("WEBFRONT_PROFILE_LINKED_ACCOUNT"))
|
||||
{
|
||||
if (!helperResult.IsInterpolation)
|
||||
{
|
||||
<span>@helperResult.MatchValue</span>
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
<a class="link-inverse" href="@Model.OffenderClientId">
|
||||
<color-code value="@Model.OffenderName"></color-code>
|
||||
</a>
|
||||
}
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</has-permission>
|
||||
|
@ -1,6 +1,11 @@
|
||||
@using SharedLibraryCore.Dtos.Meta.Responses
|
||||
@model UpdatedAliasResponse
|
||||
|
||||
@if (TempData["ShowMetaHeader"] as bool? ?? false)
|
||||
{
|
||||
<partial name="./_MetaHeader.cshtml" for="@Model.When"/>
|
||||
}
|
||||
|
||||
@foreach (var token in Utilities.SplitTranslationTokens("WEBFRONT_PROFILE_META_CONNECT_ALIAS"))
|
||||
{
|
||||
if (token.IsInterpolation)
|
||||
@ -8,12 +13,14 @@
|
||||
switch (token.MatchValue)
|
||||
{
|
||||
case "action":
|
||||
<span class="text-warning">@token.TranslationValue</span>
|
||||
<span class="text-secondary">@token.TranslationValue</span>
|
||||
break;
|
||||
case "alias":
|
||||
<span class="text-white">
|
||||
<span class="text-light-dm text-dark-lm">
|
||||
<color-code value="@Model.Name"></color-code>
|
||||
[@Model.IPAddress]
|
||||
<has-permission entity="ClientIPAddress" required-permission="Read">
|
||||
[@Model.IPAddress]
|
||||
</has-permission>
|
||||
</span>
|
||||
break;
|
||||
}
|
||||
|
@ -6,26 +6,21 @@
|
||||
@using Humanizer
|
||||
@using Humanizer.Localisation
|
||||
@using IW4MAdmin.Plugins.Stats
|
||||
@using WebfrontCore.ViewModels
|
||||
@model Stats.Dtos.AdvancedStatsInfo
|
||||
|
||||
@{
|
||||
ViewBag.Title = "Advanced Client Statistics";
|
||||
ViewBag.Description = Model.ClientName.StripColors();
|
||||
|
||||
const int maxItems = 5;
|
||||
const string headshotKey = "MOD_HEAD_SHOT";
|
||||
const string headshotKey2 = "headshot";
|
||||
const string meleeKey = "MOD_MELEE";
|
||||
|
||||
var suicideKeys = new[] {"MOD_SUICIDE", "MOD_FALLING"};
|
||||
var suicideKeys = new[] { "MOD_SUICIDE", "MOD_FALLING" };
|
||||
// if they've not copied default settings config this could be null
|
||||
var config = (GameStringConfiguration) ViewBag.Config ?? new GameStringConfiguration();
|
||||
var config = (GameStringConfiguration)ViewBag.Config ?? new GameStringConfiguration();
|
||||
|
||||
var headerClass = Model.Level == EFClient.Permission.Banned ? "bg-danger" : "bg-primary";
|
||||
var textClass = Model.Level == EFClient.Permission.Banned ? "text-danger" : "text-primary";
|
||||
var borderBottomClass = Model.Level == EFClient.Permission.Banned ? "border-bottom-danger border-top-danger" : "border-bottom border-top";
|
||||
var borderClass = Model.Level == EFClient.Permission.Banned ? "border-danger" : "border-primary";
|
||||
var buttonClass = Model.Level == EFClient.Permission.Banned ? "btn-danger" : "btn-primary";
|
||||
|
||||
string GetWeaponNameForHit(EFClientHitStatistic stat)
|
||||
{
|
||||
if (stat == null)
|
||||
@ -46,7 +41,7 @@
|
||||
return null;
|
||||
}
|
||||
|
||||
var attachmentText = string.Join('+', new[]
|
||||
var attachmentText = string.Join(" + ", new[]
|
||||
{
|
||||
config.GetStringForGame(attachment.Attachment1.Name, attachment.Attachment1.Game),
|
||||
config.GetStringForGame(attachment.Attachment2?.Name, attachment.Attachment2?.Game),
|
||||
@ -58,11 +53,11 @@
|
||||
|
||||
var weapons = Model.ByWeapon
|
||||
.Where(hit => hit.DamageInflicted > 0 || (hit.DamageInflicted == 0 && hit.HitCount > 0))
|
||||
.GroupBy(hit => new {hit.WeaponId})
|
||||
.GroupBy(hit => new { hit.WeaponId })
|
||||
.Select(group =>
|
||||
{
|
||||
var withoutAttachments = group.FirstOrDefault(hit => hit.WeaponAttachmentComboId == null);
|
||||
var mostUsedAttachment = group.Except(new[] {withoutAttachments})
|
||||
var mostUsedAttachment = group.Except(new[] { withoutAttachments })
|
||||
.OrderByDescending(g => g.DamageInflicted)
|
||||
.GroupBy(g => g.WeaponAttachmentComboId)
|
||||
.FirstOrDefault()
|
||||
@ -72,7 +67,7 @@
|
||||
{
|
||||
return withoutAttachments;
|
||||
}
|
||||
|
||||
|
||||
withoutAttachments.WeaponAttachmentComboId = mostUsedAttachment.WeaponAttachmentComboId;
|
||||
withoutAttachments.WeaponAttachmentCombo = mostUsedAttachment.WeaponAttachmentCombo;
|
||||
|
||||
@ -107,15 +102,15 @@
|
||||
.Where(weapon => weapon.DamageInflicted > 0)
|
||||
.GroupBy(weapon => weapon.WeaponId)
|
||||
.Count()
|
||||
: (int?) null; // want to default to -- in ui instead of 0
|
||||
: (int?)null; // want to default to -- in ui instead of 0
|
||||
|
||||
var activeTime = weapons.Any()
|
||||
? TimeSpan.FromSeconds(weapons.Sum(weapon => weapon.UsageSeconds ?? 0))
|
||||
: (TimeSpan?) null; // want to default to -- in ui instead of 0
|
||||
: (TimeSpan?)null; // want to default to -- in ui instead of 0
|
||||
|
||||
var kdr = aggregate == null
|
||||
? null
|
||||
: Math.Round(aggregate.KillCount / (float) aggregate.DeathCount, 2).ToString(Utilities.CurrentLocalization.Culture);
|
||||
: Math.Round(aggregate.KillCount / (float)aggregate.DeathCount, 2).ToString(Utilities.CurrentLocalization.Culture);
|
||||
|
||||
var serverLegacyStat = Model.LegacyStats
|
||||
.FirstOrDefault(stat => stat.ServerId == Model.ServerId);
|
||||
@ -140,15 +135,15 @@
|
||||
|
||||
var headShots = allPerServer.Any()
|
||||
? allPerServer.Where(hit => hit.MeansOfDeath?.Name == headshotKey || hit.HitLocation?.Name == headshotKey2).Sum(hit => hit.HitCount)
|
||||
: (int?) null; // want to default to -- in ui instead of 0
|
||||
: (int?)null; // want to default to -- in ui instead of 0
|
||||
|
||||
var meleeKills = allPerServer.Any()
|
||||
? allPerServer.Where(hit => hit.MeansOfDeath?.Name == meleeKey).Sum(hit => hit.KillCount)
|
||||
: (int?) null;
|
||||
: (int?)null;
|
||||
|
||||
var suicides = allPerServer.Any()
|
||||
? allPerServer.Where(hit => suicideKeys.Contains(hit.MeansOfDeath?.Name ?? "")).Sum(hit => hit.KillCount)
|
||||
: (int?) null;
|
||||
: (int?)null;
|
||||
|
||||
var statCards = new[]
|
||||
{
|
||||
@ -172,7 +167,7 @@
|
||||
Name = (ViewBag.Localization["WEBFRONT_ADV_STATS_SCORE"] as string).Titleize(),
|
||||
Value = score.ToNumericalString()
|
||||
},
|
||||
new
|
||||
new
|
||||
{
|
||||
Name = (ViewBag.Localization["WEBFRONT_ADV_STATS_ZSCORE"] as string),
|
||||
Value = Model.ZScore.ToNumericalString(2)
|
||||
@ -235,205 +230,170 @@
|
||||
};
|
||||
}
|
||||
|
||||
<div class="w-100 @headerClass mb-1">
|
||||
<select class="w-100 @headerClass text-white pl-4 pr-4 pt-2 pb-2 m-auto h5 @borderClass"
|
||||
id="server_selector"
|
||||
onchange="if (this.value) window.location.href=this.value">
|
||||
@if (Model.ServerId == null)
|
||||
{
|
||||
<option value="@Url.Action("Advanced", "ClientStatistics")" selected>@ViewBag.Localization["WEBFRONT_STATS_INDEX_ALL_SERVERS"]</option>
|
||||
}
|
||||
else
|
||||
{
|
||||
<option value="@Url.Action("Advanced", "ClientStatistics")">@ViewBag.Localization["WEBFRONT_STATS_INDEX_ALL_SERVERS"]</option>
|
||||
}
|
||||
@foreach (var server in Model.Servers)
|
||||
{
|
||||
if (server.Endpoint == Model.ServerEndpoint)
|
||||
{
|
||||
<option value="@Url.Action("Advanced", "ClientStatistics", new {serverId = server.Endpoint})" selected>@server.Name.StripColors()</option>
|
||||
}
|
||||
else
|
||||
{
|
||||
<option value="@Url.Action("Advanced", "ClientStatistics", new {serverId = server.Endpoint})">@server.Name.StripColors()</option>
|
||||
}
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="@headerClass p-4 mb-0 d-flex flex-wrap">
|
||||
<div class="content row mt-20">
|
||||
<!-- main content -->
|
||||
<div class="col-12 col-lg-9 col-xl-10 mt-0">
|
||||
<h2 class="content-title mb-0">Player Stats</h2>
|
||||
<span class="text-muted">
|
||||
<color-code value="@(Model.Servers.FirstOrDefault(server => server.Endpoint == Model.ServerEndpoint)?.Name ?? ViewBag.Localization["WEBFRONT_STATS_INDEX_ALL_SERVERS"])"></color-code>
|
||||
</span>
|
||||
|
||||
<div class="align-self-center d-flex flex-column flex-lg-row text-center text-lg-left mb-3 mb-md-0 p-2 ml-lg-0 mr-lg-0 ml-auto mr-auto">
|
||||
<div class="mr-lg-3 m-auto">
|
||||
<img class="img-fluid align-self-center" id="rank_icon" src="~/images/stats/ranks/rank_@(Model.ZScore.RankIconIndexForZScore()).png" alt="@performance"/>
|
||||
</div>
|
||||
<div class="d-flex flex-column align-self-center" id="client_stats_summary">
|
||||
<div class="h1 mb-0 font-weight-bold">
|
||||
<a asp-controller="Client" asp-action="ProfileAsync" asp-route-id="@Model.ClientId">@Model.ClientName.StripColors()</a>
|
||||
<!-- top card -->
|
||||
<div class="card p-20 m-0 mt-15 mb-15">
|
||||
<div class="align-self-center d-flex flex-column flex-lg-row flex-fill mb-15">
|
||||
<!-- rank icon -->
|
||||
<img class="img-fluid align-self-center w-75" id="rank_icon" src="~/images/stats/ranks/rank_@(Model.ZScore.RankIconIndexForZScore()).png" alt="@performance"/>
|
||||
<!-- summary -->
|
||||
<div class="d-flex flex-column align-self-center m-10 text-center text-lg-left" id="client_stats_summary">
|
||||
<div class="font-size-20 mb-0 font-weight-bold">
|
||||
<a asp-controller="Client" asp-action="Profile" asp-route-id="@Model.ClientId" class="no-decoration">@Model.ClientName.StripColors()</a>
|
||||
</div>
|
||||
@if (Model.Level == EFClient.Permission.Banned)
|
||||
{
|
||||
<div class="h5 mb-0 text-danger">@ViewBag.Localization["GLOBAL_PERMISSION_BANNED"]</div>
|
||||
}
|
||||
else if (Model.ZScore != null)
|
||||
{
|
||||
if (Model.Ranking > 0)
|
||||
{
|
||||
<div class="h5 mb-0">@Html.Raw((ViewBag.Localization["WEBFRONT_ADV_STATS_RANKED"] as string).FormatExt(Model.Ranking))</div>
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
<div class="h5 mb-0">@ViewBag.Localization["WEBFRONT_ADV_STATS_EXPIRED"]</div>
|
||||
}
|
||||
if (Model.ServerId != null)
|
||||
{
|
||||
<div class="h5 mb-0">@Html.Raw((ViewBag.Localization["WEBFRONT_ADV_STATS_PERFORMANCE"] as string).FormatExt($"<span class=\"text-primary\">{performance.ToNumericalString()}</span>"))</div>
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
<div class="h5 mb-0">@Html.Raw((ViewBag.Localization["WEBFRONT_ADV_STATS_RATING"] as string).FormatExt($"<span class=\"text-primary\">{Model.Rating.ToNumericalString()}</span>"))</div>
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
<div class="h5 mb-0">@ViewBag.Localization["WEBFRONT_STATS_INDEX_UNRANKED"]</div>
|
||||
}
|
||||
</div>
|
||||
<!-- history graph -->
|
||||
@if (performanceHistory.Count() > 5)
|
||||
{
|
||||
<div class="w-half m-auto ml-lg-auto " id="client_performance_history_container">
|
||||
<canvas id="client_performance_history" data-history="@Html.Raw(Json.Serialize(performanceHistory))"></canvas>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@if (Model.Level == EFClient.Permission.Banned)
|
||||
{
|
||||
<div class="h5 mb-0">@ViewBag.Localization["GLOBAL_PERMISSION_BANNED"]</div>
|
||||
}
|
||||
else if (Model.ZScore != null)
|
||||
{
|
||||
if (Model.ServerId != null)
|
||||
<hr class="m-10"/>
|
||||
<div class="d-flex flex-row flex-wrap rounded">
|
||||
@foreach (var card in statCards)
|
||||
{
|
||||
<div class="h5 mb-0">@((ViewBag.Localization["WEBFRONT_ADV_STATS_PERFORMANCE"] as string).FormatExt(performance.ToNumericalString()))</div>
|
||||
<div class="stat-card bg-very-dark-dm bg-light-ex-lm p-15 m-md-5 w-half w-md-200 rounded flex-fill">
|
||||
@if (string.IsNullOrWhiteSpace(card.Value))
|
||||
{
|
||||
<div class="m-0">—</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="m-0 font-size-16 text-primary">@card.Value</div>
|
||||
}
|
||||
<div class="font-size-12 text-muted">@card.Name</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
<div class="h5 mb-0">@((ViewBag.Localization["WEBFRONT_ADV_STATS_RATING"] as string).FormatExt(Model.Rating.ToNumericalString()))</div>
|
||||
}
|
||||
|
||||
if (Model.Ranking > 0)
|
||||
{
|
||||
<div class="h5 mb-0">@((ViewBag.Localization["WEBFRONT_ADV_STATS_RANKED"] as string).FormatExt(Model.Ranking.ToNumericalString()))</div>
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
<div class="h5 mb-0">@ViewBag.Localization["WEBFRONT_ADV_STATS_EXPIRED"]</div>
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
<div class="h5 mb-0">@ViewBag.Localization["WEBFRONT_STATS_INDEX_UNRANKED"]</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-50 m-auto ml-md-auto mr-md-0" id="client_performance_history_container">
|
||||
<canvas id="client_performance_history" data-history="@Html.Raw(Json.Serialize(performanceHistory))"></canvas>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="mb-4 bg-dark @borderBottomClass d-flex flex-wrap">
|
||||
@foreach (var card in statCards)
|
||||
{
|
||||
<div class="pl-3 pr-4 pb-3 pt-3 stat-card flex-fill w-50 w-md-auto">
|
||||
@if (string.IsNullOrWhiteSpace(card.Value))
|
||||
{
|
||||
<h5 class="card-title @textClass">—</h5>
|
||||
}
|
||||
else
|
||||
{
|
||||
<h5 class="card-title @textClass">@card.Value</h5>
|
||||
}
|
||||
<h6 class="card-subtitle mb-0 text-muted">@card.Name</h6>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="row">
|
||||
<!-- WEAPONS USED -->
|
||||
<div class="col-12 mb-4">
|
||||
<div class="@headerClass h4 mb-1 p-2">
|
||||
<div class="text-center">@ViewBag.Localization["WEBFRONT_ADV_STATS_WEAP_USAGE"]</div>
|
||||
</div>
|
||||
<table class="table mb-0">
|
||||
<tr class="@headerClass">
|
||||
<th class="text-force-break">@ViewBag.Localization["WEBFRONT_ADV_STATS_WEAPON"]</th>
|
||||
<th class="text-force-break">@ViewBag.Localization["WEBFRONT_ADV_STATS_FAV_ATTACHMENTS"]</th>
|
||||
<th class="text-force-break">@ViewBag.Localization["WEBFRONT_ADV_STATS_KILLS"]</th>
|
||||
<th class="text-force-break">@ViewBag.Localization["WEBFRONT_ADV_STATS_HITS"]</th>
|
||||
<th class="text-force-break">@ViewBag.Localization["WEBFRONT_ADV_STATS_DAMAGE"]</th>
|
||||
<th class="text-force-break">@ViewBag.Localization["WEBFRONT_ADV_STATS_USAGE"]</th>
|
||||
</tr>
|
||||
@foreach (var weaponHit in weapons.Take(maxItems))
|
||||
{
|
||||
<tr class="bg-dark">
|
||||
<td class="@textClass text-force-break">@GetWeaponNameForHit(weaponHit)</td>
|
||||
@{ var attachments = GetWeaponAttachmentName(weaponHit.WeaponAttachmentCombo); }
|
||||
@if (string.IsNullOrWhiteSpace(attachments))
|
||||
{
|
||||
<td class="text-muted text-force-break">—</td>
|
||||
}
|
||||
else
|
||||
{
|
||||
<td class="text-muted text-force-break">@attachments</td>
|
||||
}
|
||||
<td class="text-success text-force-break">@weaponHit.KillCount.ToNumericalString()</td>
|
||||
<td class="text-muted text-force-break">@weaponHit.HitCount.ToNumericalString()</td>
|
||||
<td class="text-muted text-force-break">@weaponHit.DamageInflicted.ToNumericalString()</td>
|
||||
<td class="text-muted text-force-break">@TimeSpan.FromSeconds(weaponHit.UsageSeconds ?? 0).HumanizeForCurrentCulture(minUnit: TimeUnit.Second)</td>
|
||||
</tr>
|
||||
}
|
||||
<!-- OVERFLOW -->
|
||||
@foreach (var weaponHit in weapons.Skip(maxItems))
|
||||
{
|
||||
<tr class="bg-dark hidden-row" style="display:none">
|
||||
<td class="@textClass text-force-break">@GetWeaponNameForHit(weaponHit)</td>
|
||||
@{ var attachments = GetWeaponAttachmentName(weaponHit.WeaponAttachmentCombo); }
|
||||
@if (string.IsNullOrWhiteSpace(attachments))
|
||||
{
|
||||
<td class="text-muted text-force-break">—</td>
|
||||
}
|
||||
else
|
||||
{
|
||||
<td class="text-muted text-force-break">@attachments</td>
|
||||
}
|
||||
<td class="text-success text-force-break">@weaponHit.KillCount.ToNumericalString()</td>
|
||||
<td class="text-muted text-force-break">@weaponHit.HitCount.ToNumericalString()</td>
|
||||
<td class="text-muted text-force-break">@weaponHit.DamageInflicted.ToNumericalString()</td>
|
||||
<td class="text-muted text-force-break">@TimeSpan.FromSeconds(weaponHit.UsageSeconds ?? 0).HumanizeForCurrentCulture()</td>
|
||||
</tr>
|
||||
}
|
||||
<tr>
|
||||
</table>
|
||||
<button class="btn @buttonClass btn-block table-slide">
|
||||
<span class="oi oi-chevron-bottom"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<!-- HIT LOCATIONS -->
|
||||
<div class="col-lg-6 col-12 pr-3 pr-lg-0" id="hit_location_table">
|
||||
<div class="@headerClass h4 mb-1 p-2">
|
||||
<div class="text-center">@ViewBag.Localization["WEBFRONT_ADV_STATS_HIT_LOCATIONS"]</div>
|
||||
</div>
|
||||
<table class="table @borderBottomClass bg-dark mb-0 pb-0">
|
||||
<tr class="@headerClass">
|
||||
<th class="text-force-break">@ViewBag.Localization["WEBFRONT_ADV_STATS_LOCATION"]</th>
|
||||
<th class="text-force-break">@ViewBag.Localization["WEBFRONT_ADV_STATS_HITS"]</th>
|
||||
<th class="text-force-break">@ViewBag.Localization["WEBFRONT_ADV_STATS_PERCENTAGE"]</th>
|
||||
<th class="text-force-break">@ViewBag.Localization["WEBFRONT_ADV_STATS_DAMAGE"]</th>
|
||||
</tr>
|
||||
<div class="d-flex flex-wrap flex-column-reverse flex-xl-row">
|
||||
<!-- hit locations -->
|
||||
@{
|
||||
var totalHits = filteredHitLocations.Sum(hit => hit.HitCount);
|
||||
}
|
||||
@foreach (var hitLocation in filteredHitLocations.Take(8))
|
||||
{
|
||||
<tr>
|
||||
<td class="@textClass text-force-break">@config.GetStringForGame(hitLocation.HitLocation.Name, hitLocation.HitLocation.Game)</td>
|
||||
<td class="text-success text-force-break">@hitLocation.HitCount</td>
|
||||
<td class="text-muted text-force-break">@Math.Round((hitLocation.HitCount / (float) totalHits) * 100.0).ToString(Utilities.CurrentLocalization.Culture)%</td>
|
||||
<td class="text-muted text-force-break">@hitLocation.DamageInflicted.ToNumericalString()</td>
|
||||
</tr>
|
||||
|
||||
var hitLocationsTable = new TableInfo(5)
|
||||
{
|
||||
Header = ViewBag.Localization["WEBFRONT_ADV_STATS_HIT_LOCATIONS"]
|
||||
};
|
||||
|
||||
hitLocationsTable.WithColumns(new string[]
|
||||
{
|
||||
ViewBag.Localization["WEBFRONT_ADV_STATS_LOCATION"],
|
||||
ViewBag.Localization["WEBFRONT_ADV_STATS_HITS"],
|
||||
ViewBag.Localization["WEBFRONT_ADV_STATS_PERCENTAGE"],
|
||||
ViewBag.Localization["WEBFRONT_ADV_STATS_DAMAGE"],
|
||||
}).WithRows(filteredHitLocations, hitLocation => new[]
|
||||
{
|
||||
config.GetStringForGame(hitLocation.HitLocation.Name, hitLocation.HitLocation.Game),
|
||||
hitLocation.HitCount.ToString(),
|
||||
$"{Math.Round((hitLocation.HitCount / (float)totalHits) * 100.0).ToString(Utilities.CurrentLocalization.Culture)}%",
|
||||
hitLocation.DamageInflicted.ToNumericalString()
|
||||
});
|
||||
}
|
||||
|
||||
@foreach (var hitLocation in filteredHitLocations.Skip(8))
|
||||
{
|
||||
<tr class="bg-dark hidden-row" style="display:none;">
|
||||
<td class="@textClass text-force-break">@config.GetStringForGame(hitLocation.HitLocation.Name, hitLocation.HitLocation.Game)</td>
|
||||
<td class="text-success text-force-break">@hitLocation.HitCount</td>
|
||||
<td class="text-muted text-force-break">@Math.Round((hitLocation.HitCount / (float) totalHits) * 100.0).ToString(Utilities.CurrentLocalization.Culture)%</td>
|
||||
<td class="text-muted text-force-break">@hitLocation.DamageInflicted.ToNumericalString()</td>
|
||||
</tr>
|
||||
<div class="mr-0 mr-xl-20 flex-fill flex-xl-grow-1">
|
||||
<partial name="_DataTable" for="@hitLocationsTable"></partial>
|
||||
|
||||
<div class="h-250 p-15 card m-0 d-flex justify-content-center rounded-bottom" id="hitlocation_container">
|
||||
<canvas id="hitlocation_model">
|
||||
</canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- weapons used -->
|
||||
@{
|
||||
var weaponsUsedTable = new TableInfo(10)
|
||||
{
|
||||
Header = ViewBag.Localization["WEBFRONT_ADV_STATS_WEAP_USAGE"]
|
||||
};
|
||||
|
||||
weaponsUsedTable.WithColumns(new string[]
|
||||
{
|
||||
ViewBag.Localization["WEBFRONT_ADV_STATS_WEAPON"],
|
||||
ViewBag.Localization["WEBFRONT_ADV_STATS_FAV_ATTACHMENTS"],
|
||||
ViewBag.Localization["WEBFRONT_ADV_STATS_KILLS"],
|
||||
ViewBag.Localization["WEBFRONT_ADV_STATS_HITS"],
|
||||
ViewBag.Localization["WEBFRONT_ADV_STATS_DAMAGE"],
|
||||
ViewBag.Localization["WEBFRONT_ADV_STATS_USAGE"]
|
||||
}).WithRows(weapons, weapon => new[]
|
||||
{
|
||||
GetWeaponNameForHit(weapon),
|
||||
GetWeaponAttachmentName(weapon.WeaponAttachmentCombo) ?? "--",
|
||||
weapon.KillCount.ToNumericalString(),
|
||||
weapon.HitCount.ToNumericalString(),
|
||||
weapon.DamageInflicted.ToNumericalString(),
|
||||
TimeSpan.FromSeconds(weapon.UsageSeconds ?? 0).HumanizeForCurrentCulture(minUnit: TimeUnit.Second)
|
||||
});
|
||||
}
|
||||
</table>
|
||||
<button class="btn @buttonClass btn-block table-slide">
|
||||
<span class="oi oi-chevron-bottom"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-lg-6 col-12 pl-3 pl-lg-0">
|
||||
<div class="@borderBottomClass text-center h-100" id="hitlocation_container">
|
||||
<canvas id="hitlocation_model">
|
||||
</canvas>
|
||||
|
||||
<div class="flex-fill flex-xl-grow-1">
|
||||
<partial name="_DataTable" for="@weaponsUsedTable"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- side context menu -->
|
||||
@{
|
||||
var menuItems = new SideContextMenuItems
|
||||
{
|
||||
MenuTitle = "Game", Items = Model.Servers.Select(server => new SideContextMenuItem
|
||||
{
|
||||
IsLink = true,
|
||||
Reference = Url.Action("Advanced", "ClientStatistics", new { serverId = server.Endpoint }),
|
||||
Title = server.Name.StripColors(),
|
||||
IsActive = Model.ServerEndpoint == server.Endpoint
|
||||
}).Prepend(new SideContextMenuItem
|
||||
{
|
||||
IsLink = true,
|
||||
Reference = Url.Action("Advanced", "ClientStatistics"),
|
||||
Title = ViewBag.Localization["WEBFRONT_STATS_INDEX_ALL_SERVERS"],
|
||||
IsActive = Model.ServerEndpoint is null
|
||||
}).ToList()
|
||||
};
|
||||
}
|
||||
<partial name="_SideContextMenu" for="@menuItems"></partial>
|
||||
</div>
|
||||
|
||||
|
||||
@{
|
||||
var projection = filteredHitLocations.Select(loc => new
|
||||
{
|
||||
@ -441,7 +401,7 @@
|
||||
// we want to count head and neck as the same
|
||||
percentage = (loc.HitLocation.Name == "head"
|
||||
? filteredHitLocations.FirstOrDefault(c => c.HitLocation.Name == "neck")?.HitCount ?? 0 + loc.HitCount
|
||||
: loc.HitCount) / (float) totalHits
|
||||
: loc.HitCount) / (float)totalHits
|
||||
}).ToList();
|
||||
var maxPercentage = projection.Any() ? projection.Max(p => p.percentage) : 0;
|
||||
}
|
||||
@ -456,4 +416,4 @@
|
||||
<environment include="Development">
|
||||
<script type="text/javascript" src="~/js/advanced_stats.js"></script>
|
||||
</environment>
|
||||
}
|
||||
}
|
||||
|
@ -3,149 +3,90 @@
|
||||
@{
|
||||
Layout = null;
|
||||
var loc = Utilities.CurrentLocalization.LocalizationIndex.Set;
|
||||
double getDeviation(double deviations) => Math.Pow(Math.E, 5.259 + (deviations * 0.812));
|
||||
string rankIcon(double? elo)
|
||||
{
|
||||
if (elo >= getDeviation(-0.75) && elo < getDeviation(1.25))
|
||||
{
|
||||
return "0_no-place/menu_div_no_place.png";
|
||||
}
|
||||
if (elo >= getDeviation(0.125) && elo < getDeviation(0.625))
|
||||
{
|
||||
return "1_iron/menu_div_iron_sub03.png";
|
||||
}
|
||||
if (elo >= getDeviation(0.625) && elo < getDeviation(1.0))
|
||||
{
|
||||
return "2_bronze/menu_div_bronze_sub03.png";
|
||||
}
|
||||
if (elo >= getDeviation(1.0) && elo < getDeviation(1.25))
|
||||
{
|
||||
return "3_silver/menu_div_silver_sub03.png";
|
||||
}
|
||||
if (elo >= getDeviation(1.25) && elo < getDeviation(1.5))
|
||||
{
|
||||
return "4_gold/menu_div_gold_sub03.png";
|
||||
}
|
||||
if (elo >= getDeviation(1.5) && elo < getDeviation(1.75))
|
||||
{
|
||||
return "5_platinum/menu_div_platinum_sub03.png";
|
||||
}
|
||||
if (elo >= getDeviation(1.75) && elo < getDeviation(2.0))
|
||||
{
|
||||
return "6_semipro/menu_div_semipro_sub03.png";
|
||||
}
|
||||
if (elo >= getDeviation(2.0))
|
||||
{
|
||||
return "7_pro/menu_div_pro_sub03.png";
|
||||
}
|
||||
|
||||
return "0_no-place/menu_div_no_place.png";
|
||||
}
|
||||
}
|
||||
|
||||
@if (Model.Count == 0)
|
||||
{
|
||||
<div class="p-2 text-center">@Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_TEXT_NOQUALIFY"]</div>
|
||||
<div class="card m-0 mt-15">
|
||||
<div class="d-flex">
|
||||
<i class="oi oi-timer align-self-center mb-10" style="font-size: 6rem;"></i>
|
||||
<div class="p-15">
|
||||
<h2 class="content-title mb-0">@Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_TEXT_NOQUALIFY"]</h2>
|
||||
<span class="text-muted">Check back after some more time has passed</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@foreach (var stat in Model)
|
||||
{
|
||||
<div class="row ml-0 mr-0 pt-2 pb-2">
|
||||
@if (ViewBag.UseNewStats)
|
||||
{
|
||||
<img class="align-self-center d-block d-md-none m-auto pb-3 pt-3" src="~/images/stats/ranks/rank_@(stat.ZScore.RankIconIndexForZScore()).png" alt="@stat.Performance"/>
|
||||
}
|
||||
<div class="col-md-4 text-md-left text-center">
|
||||
<div class="h2 d-flex flex-row justify-content-center justify-content-md-start align-items-center">
|
||||
<div class="text-muted">#@stat.Ranking</div>
|
||||
@if (stat.RatingChange > 0)
|
||||
{
|
||||
<div class="d-flex flex-column text-center pl-1">
|
||||
<div class="oi oi-caret-top text-success client-rating-change-up"></div>
|
||||
<div class="client-rating-change-amount text-success">@stat.RatingChange</div>
|
||||
<div class="card m-0 mt-15 p-20 d-flex flex-column flex-md-row justify-content-between">
|
||||
<div class="d-flex flex-column w-full w-md-quarter">
|
||||
<div class="d-flex align-items-center mb-15">
|
||||
<div class="d-flex text-muted">
|
||||
<span class="font-size-20">#</span>
|
||||
<div style="font-size: 4.5rem; line-height: 4.5rem;font-family: SFMono-Regular,Menlo,Monaco,Consolas,'Liberation Mono','Courier New',monospace;">@stat.Ranking</div>
|
||||
</div>
|
||||
<div class="ml-10">
|
||||
<div class="d-flex flex-row">
|
||||
<a asp-controller="Client" asp-action="Profile" asp-route-id="@stat.ClientId" class="no-decoration text-light-dm text-dark-lm font-size-20 text-force-break" style="line-height: 2.5rem;">
|
||||
<color-code value="@stat.Name" allow="@ViewBag.EnableColorCodes"></color-code>
|
||||
</a>
|
||||
@if (stat.RatingChange > 0)
|
||||
{
|
||||
<div class="d-flex flex-column text-center ml-5 mr-5 align-self-center">
|
||||
<div class="oi oi-caret-top text-success client-rating-change-up"></div>
|
||||
<div class="client-rating-change-amount text-success">@stat.RatingChange</div>
|
||||
</div>
|
||||
}
|
||||
@if (stat.RatingChange < 0)
|
||||
{
|
||||
<div class="d-flex flex-column text-center ml-5 mr-5 align-self-center">
|
||||
<div class="client-rating-change-amount client-rating-change-amount-down text-danger">@Math.Abs(stat.RatingChange)</div>
|
||||
<div class="oi oi-caret-bottom text-danger client-rating-change-down"></div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@if (stat.RatingChange < 0)
|
||||
{
|
||||
<div class="d-flex flex-column text-center pl-1">
|
||||
<div class="client-rating-change-amount client-rating-change-amount-down text-danger">@Math.Abs(stat.RatingChange)</div>
|
||||
<div class="oi oi-caret-bottom text-danger client-rating-change-down"></div>
|
||||
</div>
|
||||
}
|
||||
<span class="text-muted pr-1 pl-1">–</span>
|
||||
@if (!ViewBag.UseNewStats)
|
||||
{
|
||||
<a asp-controller="Client" asp-action="ProfileAsync" asp-route-id="@stat.ClientId">
|
||||
<color-code value="@stat.Name" allow="ViewBag.EnableColorCodes"></color-code>
|
||||
</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<a asp-controller="ClientStatistics" asp-action="Advanced" asp-route-id="@stat.ClientId">
|
||||
<color-code value="@stat.Name" allow="ViewBag.EnableColorCodes"></color-code>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (ViewBag.UseNewStats)
|
||||
{
|
||||
<div class="d-flex flex-column">
|
||||
<div>
|
||||
<span class="text-primary font-weight-bold h5">
|
||||
<div class="font-size-14">
|
||||
<span class="text-primary">
|
||||
@stat.Performance.ToNumericalString()
|
||||
</span>
|
||||
@if (stat.ServerId == null)
|
||||
{
|
||||
<span class="text-muted font-weight-bold h5">@loc["WEBFRONT_ADV_STATS_RATING"].FormatExt("").ToLower()</span>
|
||||
<span class="text-muted">@loc["WEBFRONT_ADV_STATS_RATING"].FormatExt("").ToLower()</span>
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
<span class="text-muted font-weight-bold h5">@loc["WEBFRONT_ADV_STATS_PERFORMANCE"].FormatExt("").ToLower()</span>
|
||||
<span class="text-muted">@loc["WEBFRONT_ADV_STATS_PERFORMANCE"].FormatExt("").ToLower()</span>
|
||||
}
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-primary">@stat.Kills.ToNumericalString()</span><span class="text-muted"> @loc["PLUGINS_STATS_TEXT_KILLS"]</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-primary">@stat.Deaths.ToNumericalString()</span><span class="text-muted"> @loc["PLUGINS_STATS_TEXT_DEATHS"]</span><br />
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-primary">@stat.KDR</span><span class="text-muted"> @loc["PLUGINS_STATS_TEXT_KDR"]</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-primary">@stat.TimePlayedValue.HumanizeForCurrentCulture() </span><span class="text-muted">@loc["WEBFRONT_PROFILE_PLAYER"]</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-primary"> @stat.LastSeenValue.HumanizeForCurrentCulture() </span><span class="text-muted">@loc["WEBFRONT_PROFILE_LSEEN"]</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="text-primary">@stat.Performance</span> <span class="text-muted"> @loc["PLUGINS_STATS_COMMANDS_PERFORMANCE"]</span>
|
||||
<br/>
|
||||
<span class="text-primary">@stat.KDR</span><span class="text-muted"> @loc["PLUGINS_STATS_TEXT_KDR"]</span>
|
||||
<span class="text-primary">@stat.Kills</span><span class="text-muted"> @loc["PLUGINS_STATS_TEXT_KILLS"]</span>
|
||||
<span class="text-primary">@stat.Deaths</span><span class="text-muted"> @loc["PLUGINS_STATS_TEXT_DEATHS"]</span><br />
|
||||
<span class="text-muted">@loc["WEBFRONT_PROFILE_PLAYER"]</span> <span class="text-primary"> @stat.TimePlayed </span><span class="text-muted">@loc["GLOBAL_TIME_HOURS"]</span><br />
|
||||
<span class="text-muted">@loc["WEBFRONT_PROFILE_LSEEN"]</span><span class="text-primary"> @stat.LastSeen </span><span class="text-muted">@loc["WEBFRONT_PENALTY_TEMPLATE_AGO"]</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="d-flex flex-column font-size-12 text-right text-md-left">
|
||||
<div>
|
||||
<span class="text-primary">@stat.Kills.ToNumericalString()</span><span class="text-muted"> @loc["PLUGINS_STATS_TEXT_KILLS"]</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-primary">@stat.Deaths.ToNumericalString()</span><span class="text-muted"> @loc["PLUGINS_STATS_TEXT_DEATHS"]</span><br/>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-primary">@stat.KDR</span><span class="text-muted"> @loc["PLUGINS_STATS_TEXT_KDR"]</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-primary">@stat.TimePlayedValue.HumanizeForCurrentCulture() </span><span class="text-muted">@loc["WEBFRONT_PROFILE_PLAYER"]</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-primary"> @stat.LastSeenValue.HumanizeForCurrentCulture() </span><span class="text-muted">@loc["WEBFRONT_PROFILE_LSEEN"]</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 client-rating-graph" id="rating_history_@(stat.ClientId + "_" + stat.Id)" data-history="@Html.Raw(Json.Serialize(stat.PerformanceHistory))">
|
||||
|
||||
<div class="w-full w-md-half client-rating-graph" id="rating_history_@(stat.ClientId + "_" + stat.Id)" data-history="@Html.Raw(Json.Serialize(stat.PerformanceHistory))">
|
||||
</div>
|
||||
|
||||
<div class="col-md-2 client-rating-icon text-md-right text-center align-items-center d-flex justify-content-center">
|
||||
@if (ViewBag.UseNewStats)
|
||||
{
|
||||
<img class="align-self-center d-none d-md-block" src="~/images/stats/ranks/rank_@(stat.ZScore.RankIconIndexForZScore()).png" alt="@stat.Performance"/>
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
<img src="/images/icons/@rankIcon(stat.Performance)"/>
|
||||
}
|
||||
<div class="w-quarter align-self-center d-flex justify-content-center">
|
||||
<img class="w-100 h-100" src="~/images/stats/ranks/rank_@(stat.ZScore.RankIconIndexForZScore()).png" alt="@stat.Performance"/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
@ -1,34 +1,48 @@
|
||||
<ul class="nav nav-tabs border-top border-bottom nav-fill row" role="tablist" id="stats_top_players">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active top-players-link" href="#server_0" role="tab" data-toggle="tab" aria-selected="true" data-serverid="0">@ViewBag.Localization["WEBFRONT_STATS_INDEX_ALL_SERVERS"]</a>
|
||||
</li>
|
||||
@model IEnumerable<SharedLibraryCore.Dtos.ServerInfo>
|
||||
@using WebfrontCore.ViewModels
|
||||
|
||||
@foreach (var server in ViewBag.Servers)
|
||||
{
|
||||
<li class="nav-item ">
|
||||
<a class="nav-link top-players-link" href="#server_@server.ID" role="tab" data-toggle="tab" aria-selected="false" data-serverid="@server.ID">
|
||||
<color-code value="@server.Name"></color-code>
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
<div class="tab-content border-bottom row">
|
||||
<div role="tabpanel" class="tab-pane active striped flex-fill" id="server_0">
|
||||
@await Component.InvokeAsync("TopPlayers", new { count = 25, offset = 0 })
|
||||
<div class="content mt-20 row">
|
||||
<div class="col-12 col-lg-9 col-xl-10 mt-0">
|
||||
<h2 class="content-title mb-0">Top Players</h2>
|
||||
<span class="text-muted">
|
||||
<color-code value="@(ViewBag.SelectedServerName ?? ViewBag.Localization["WEBFRONT_STATS_INDEX_ALL_SERVERS"])"></color-code>
|
||||
</span>
|
||||
|
||||
<div id="topPlayersContainer">
|
||||
@await Component.InvokeAsync("TopPlayers", new { count = 25, offset = 0, serverEndpoint = ViewBag.SelectedServerId })
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<i id="loaderLoad" class="oi oi-chevron-bottom loader-load-more text-primary mt-5" aria-hidden="true"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@foreach (var server in ViewBag.Servers)
|
||||
{
|
||||
<div role="tabpanel" class="tab-pane striped flex-fill" id="server_@server.ID">
|
||||
</div>
|
||||
<!-- side context menu -->
|
||||
@{
|
||||
var menuItems = new SideContextMenuItems
|
||||
{
|
||||
MenuTitle = "Game", Items = Model.Select(server => new SideContextMenuItem
|
||||
{
|
||||
IsLink = true,
|
||||
Reference = Url.Action("TopPlayers", "Stats", new { serverId = server.Endpoint }),
|
||||
Title = server.Name.StripColors(),
|
||||
IsActive = ViewBag.SelectedServerId == server.Endpoint
|
||||
}).Prepend(new SideContextMenuItem
|
||||
{
|
||||
IsLink = true,
|
||||
Reference = Url.Action("TopPlayers", "Stats"),
|
||||
Title = ViewBag.Localization["WEBFRONT_STATS_INDEX_ALL_SERVERS"],
|
||||
IsActive = ViewBag.SelectedServerId is null
|
||||
}).ToList()
|
||||
};
|
||||
}
|
||||
<partial name="_SideContextMenu" for="@menuItems"></partial>
|
||||
</div>
|
||||
|
||||
@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', '#server_0', 25);</script>
|
||||
<script>initLoader('/Stats/GetTopPlayersAsync', '#topPlayersContainer', 25);</script>
|
||||
}
|
||||
|
@ -1,24 +1,25 @@
|
||||
@using SharedLibraryCore.Dtos.Meta.Responses
|
||||
@using Humanizer
|
||||
@model IList<MessageResponse>
|
||||
@{
|
||||
Layout = null;
|
||||
}
|
||||
|
||||
<div class="client-message-context">
|
||||
<h5 class="bg-primary pt-2 pb-2 pl-3 mb-0 mt-2 text-white">@Model.First().When.ToString()</h5>
|
||||
<div class="bg-dark p-3 mb-2 border-bottom">
|
||||
<div class="client-message-context mt-10 mb-10">
|
||||
<h5 class="rounded-top bg-primary text-light mb-0 p-10 pt-5 pb-5">@Model.First().When.Humanize()</h5>
|
||||
<div class="bg-dark-dm bg-light-lm p-10 pr-20 rounded-bottom">
|
||||
@foreach (var message in Model)
|
||||
{
|
||||
<span class="text-white">
|
||||
<color-code value="@message.ClientName" allow="ViewBag.EnableColorCodes"></color-code>
|
||||
<span>
|
||||
<color-code value="@message.ClientName" allow="@ViewBag.EnableColorCodes"></color-code>
|
||||
</span>
|
||||
<span>
|
||||
—
|
||||
<span class="@(message.IsQuickMessage ? "font-italic" : "")">
|
||||
<color-code value="@(message.IsHidden ? message.HiddenMessage : message.Message)" allow="ViewBag.EnableColorCodes"></color-code>
|
||||
<span class="@(message.IsQuickMessage ? "font-italic" : "") text-light-dm text-dark-lm">
|
||||
<color-code value="@(message.IsHidden ? message.HiddenMessage : message.Message)" allow="@ViewBag.EnableColorCodes"></color-code>
|
||||
</span>
|
||||
</span>
|
||||
<br />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -3,7 +3,7 @@
|
||||
Layout = null;
|
||||
}
|
||||
|
||||
<div class="penalty-info-context bg-dark p-2 mt-2 mb-2 border-top border-bottom">
|
||||
<div class="penalty-info-context bg-dark-dm bg-light-lm p-20 mt-5 mb-5 rounded-top rounded-bottom border-top border-bottom">
|
||||
@foreach (var snapshot in Model)
|
||||
{
|
||||
<!-- this is not ideal, but I didn't want to manually write out all the properties-->
|
||||
@ -15,9 +15,9 @@
|
||||
continue;
|
||||
}
|
||||
|
||||
<span class="text-white">@prop.Name </span>
|
||||
<span class="text-light-dm text-dark-lm">@prop.Name </span>
|
||||
<span>— @prop.GetValue(snapshot)?.ToString()?.StripColors()</span><br/>
|
||||
}
|
||||
<div class="w-100 mt-1 mb-1 border-bottom"></div>
|
||||
<hr class="mt-10 mb-10"/>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -12,53 +12,36 @@
|
||||
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12 text-white-50 ">
|
||||
<h3 class="text-white">@ViewData["Title"]</h3>
|
||||
<h5 class="mb-4">@noticeText</h5>
|
||||
<div class="content mt-0">
|
||||
<h4 class="content-title mt-20 mb-0">@ViewData["Title"]</h4>
|
||||
<span class="text-muted">@noticeText</span>
|
||||
|
||||
<ul class="nav nav-tabs border-bottom-dark">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" asp-action="Edit">@ViewBag.Localization["WEBFRONT_CONFIGURATION_GUI"]</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" data-toggle="tab" href="#files">@ViewBag.Localization["WEBFRONT_CONFIGURATION_FILES"]</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane" id="editor">
|
||||
@foreach (var file in Model.OrderByDescending(f => f.FileName.Contains("IW4MAdmin")))
|
||||
{
|
||||
<div class="card m-0 p-0 mt-20 mb-20">
|
||||
<div class="bg-primary p-10 rounded-top text-light">
|
||||
<i class="oi oi-expand-down mr-5 expand-file-icon" data-editor-id="#edit_file_@FormatHtmlId(file.FileName)"></i>
|
||||
<span>@file.FileName</span>
|
||||
</div>
|
||||
<div class="tab-pane active" id="files">
|
||||
@foreach (var file in Model)
|
||||
{
|
||||
<div class="bg-primary mb-0 pl-3 pr-3 p-2 text-white d-flex border-bottom-dark">
|
||||
<span class="oi oi-expand-down align-self-center mr-2 expand-file-icon" data-editor-id="#edit_file_@FormatHtmlId(file.FileName)" title="Toggle Expand"></span>
|
||||
<span>@file.FileName</span>
|
||||
</div>
|
||||
<div class="edit-file bg-dark d-none" id="edit_file_@FormatHtmlId(file.FileName)" data-file-name="@file.FileName">
|
||||
<pre class="mb-0 mr-auto" spellcheck="false"><code class="language-json bg-dark editable" contenteditable="true" id="edit_file_code_@FormatHtmlId(file.FileName)">@file.FileContent</code></pre>
|
||||
<button type="button" class="btn btn-primary align-self-end m-3 file-save-button" data-file-name="@file.FileName">Save</button>
|
||||
</div>
|
||||
}
|
||||
<div class="edit-file d-none flex-column" id="edit_file_@FormatHtmlId(file.FileName)" data-file-name="@file.FileName">
|
||||
<pre class="mt-0 font-size-12 flex-fill border-bottom" spellcheck="false"><code class="code language-json editable" contenteditable="true" id="edit_file_code_@FormatHtmlId(file.FileName)">@file.FileContent</code></pre>
|
||||
<button type="button" class="btn btn-primary m-15 mt-0 align-self-start file-save-button" data-file-name="@file.FileName">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@section scripts
|
||||
{
|
||||
<!-- I don't want to include the entire highlight js into the bundle for this 1 page -->
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.2.0/highlight.min.js"></script>
|
||||
<script>
|
||||
@section scripts
|
||||
{
|
||||
<!-- I don't want to include the entire highlight js into the bundle for this 1 page -->
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.2.0/highlight.min.js"></script>
|
||||
<script>
|
||||
if (hljs !== undefined) {
|
||||
hljs.highlightAll();
|
||||
hljs.highlightAll();
|
||||
}
|
||||
</script>
|
||||
|
||||
<environment include="Development">
|
||||
<script type="text/javascript" src="~/js/configuration.js"></script>
|
||||
</environment>
|
||||
}
|
||||
|
||||
</div>
|
||||
<environment include="Development">
|
||||
<script type="text/javascript" src="~/js/configuration.js"></script>
|
||||
</environment>
|
||||
}
|
||||
|
@ -140,4 +140,4 @@
|
||||
<environment include="Development">
|
||||
<script type="text/javascript" src="~/js/configuration.js"></script>
|
||||
</environment>
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,54 @@
|
||||
@model IEnumerable<SharedLibraryCore.Dtos.ServerInfo>
|
||||
|
||||
<div class="row justify-content-center">
|
||||
<div id="console" class="col-md-8">
|
||||
@Html.DropDownList("Server", Model.Select(s => new SelectListItem() { Text = SharedLibraryCore.Utilities.StripColors(s.Name), Value = s.ID.ToString() }).ToList(), new { @class = "form-control bg-dark text-light", id = "console_server_select" })
|
||||
<div id="console_command_response" class="bg-dark p-3">
|
||||
<div class="content mt-0">
|
||||
<h2 class="content-title mt-20">Web Console</h2>
|
||||
|
||||
<div id="console" class="card m-0 mb-20">
|
||||
<div id="console_command_response" class="bg-dark-dm bg-light-lm p-10 code rounded">
|
||||
<pre style="font-size: 8px;"> /(((((((*
|
||||
./((((((((((((((((,
|
||||
.*/(((((((((((((((((((((((((.
|
||||
*((((((((((((((((((((((((((((((((((
|
||||
./(((((((((((((((((((((((((((((((((((/ ,**,
|
||||
./(((((((((((((((((((((((((((((((/ ,******,
|
||||
,(((((((((((((((((((((((((((((* ********//**.
|
||||
*/(((((((((((((((((((((((((* ***************
|
||||
,*** ,(((((((((((((((((((((((, ,***************.
|
||||
,****/**,*/(((((((((((((((((((. *************,
|
||||
************,,((((((((((((((((*. ,************
|
||||
.***********, *((((((((((((( .*//*********
|
||||
.,**********, ,(((((((((/ ***********
|
||||
,**********, *(((((((/ ,**********
|
||||
,/*********, .*(((* **********,
|
||||
,/*********, .*. ***********.
|
||||
,**********, ***********
|
||||
.*********** ***********
|
||||
.************ .***********
|
||||
************. .************,
|
||||
,************* .**************,
|
||||
.***************** *****************.
|
||||
*******************************************,
|
||||
.*//***********************************.
|
||||
********************************,.
|
||||
.************************,
|
||||
,.**,
|
||||
</pre>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="col-12 col-sm-9 pr-1 pr-sm-0">
|
||||
<input id="console_command_value" class="form-control m-0 bg-dark text-light" type="text" />
|
||||
</div>
|
||||
|
||||
<div class="card m-0 rounded">
|
||||
<div class="input-group mb-10">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Server</span>
|
||||
</div>
|
||||
<div class="col pl-1 pl-sm-0">
|
||||
<button id="console_command_button" class="btn btn-primary btn-block m-0">@SharedLibraryCore.Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CONSOLE_EXECUTE"]</button>
|
||||
@Html.DropDownList("Server", Model.Select(s => new SelectListItem { Text = s.Name.StripColors(), Value = s.ID.ToString() }).ToList(), new { @class = "form-control", id = "console_server_select" })
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<input id="console_command_value" class="form-control" placeholder="Enter command..." type="text" required="required"/>
|
||||
<div class="input-group-append">
|
||||
<button id="console_command_button" class="btn btn-primary">
|
||||
@Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CONSOLE_EXECUTE"]
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,74 +1,58 @@
|
||||
@model IEnumerable<(string, IEnumerable<SharedLibraryCore.Interfaces.IManagerCommand>)>
|
||||
@{
|
||||
var loc = SharedLibraryCore.Utilities.CurrentLocalization.LocalizationIndex;
|
||||
var loc = Utilities.CurrentLocalization.LocalizationIndex;
|
||||
}
|
||||
@foreach ((var pluginName, var commandList) in Model)
|
||||
{
|
||||
<h4 class="text-center pb-0 pb-xl-3 pt-3 pt-xl-0">@pluginName</h4>
|
||||
|
||||
<!-- desktop -->
|
||||
<table class="table table-striped border-bottom col-10 ml-auto mr-auto d-none d-lg-table mb-4 text-light">
|
||||
<thead>
|
||||
<tr class="bg-primary text-white">
|
||||
<th scope="col">@loc["WEBFRONT_HELP_COMMAND_DESC_NAME"]</th>
|
||||
<th scope="col">@loc["WEBFRONT_HELP_COMMAND_DESC_ALIAS"]</th>
|
||||
<th scope="col">@loc["WEBFRONT_HELP_COMMAND_DESC_DESCRIPTION"]</th>
|
||||
<th scope="col">@loc["WEBFRONT_HELP_COMMAND_DESC_REQUIRES_TARGET"]</th>
|
||||
<th scope="col">@loc["WEBFRONT_HELP_COMMAND_DESC_SYNTAX"]</th>
|
||||
<th scope="col">@loc["WEBFRONT_HELP_COMMAND_DESC_REQUIRED_LEVEL"]</th>
|
||||
<div class="content mt-20">
|
||||
@foreach (var (pluginName, commandList) in Model)
|
||||
{
|
||||
<h2 class="content-title mb-lg-20 mt-20 ">@(pluginName == "Native" ? "Command List" : pluginName)</h2>
|
||||
|
||||
<table class="table rounded">
|
||||
<thead>
|
||||
<!-- desktop -->
|
||||
<tr class="bg-primary text-light d-none d-lg-table-row ">
|
||||
<th>@loc["WEBFRONT_HELP_COMMAND_DESC_NAME"]</th>
|
||||
<th>@loc["WEBFRONT_HELP_COMMAND_DESC_ALIAS"]</th>
|
||||
<th>@loc["WEBFRONT_HELP_COMMAND_DESC_DESCRIPTION"]</th>
|
||||
<th>@loc["WEBFRONT_HELP_COMMAND_DESC_REQUIRES_TARGET"]</th>
|
||||
<th>@loc["WEBFRONT_HELP_COMMAND_DESC_SYNTAX"]</th>
|
||||
<th class="text-right">@loc["WEBFRONT_HELP_COMMAND_DESC_REQUIRED_LEVEL"]</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var command in commandList)
|
||||
{
|
||||
<tr class="bg-dark">
|
||||
<th scope="row">@command.Name</th>
|
||||
<!-- desktop -->
|
||||
<tr class="d-none d-lg-table-row bg-dark-dm bg-light-lm">
|
||||
<td>@command.Name</td>
|
||||
<td>@command.Alias</td>
|
||||
<td>@command.Description</td>
|
||||
<td>@command.RequiresTarget</td>
|
||||
<td>@ViewBag.CommandPrefix@command.Syntax.Split(@ViewBag.CommandPrefix)[1]</td>
|
||||
<td>@command.Permission.ToLocalizedLevelName()</td>
|
||||
<td>@ViewBag.CommandPrefix@command.Syntax.Split(ViewBag.CommandPrefix)[1]</td>
|
||||
<td class="text-right level-color-@((int)command.Permission)">@command.Permission.ToLocalizedLevelName()</td>
|
||||
</tr>
|
||||
|
||||
<!-- mobile -->
|
||||
<tr class="d-table-row d-lg-none d-flex bg-dark-dm bg-light-lm">
|
||||
<td class="bg-primary text-right" style="min-width: 124px;">
|
||||
<div class="mt-5 mb-5">@loc["WEBFRONT_HELP_COMMAND_DESC_NAME"]</div>
|
||||
<div class="mt-5 mb-5">@loc["WEBFRONT_HELP_COMMAND_DESC_ALIAS"]</div>
|
||||
<div class="mt-5 mb-5">@loc["WEBFRONT_HELP_COMMAND_DESC_DESCRIPTION"]</div>
|
||||
<div class="mt-5 mb-5">@loc["WEBFRONT_HELP_COMMAND_DESC_REQUIRES_TARGET"]</div>
|
||||
<div class="mt-5 mb-5">@loc["WEBFRONT_HELP_COMMAND_DESC_SYNTAX"]</div>
|
||||
<div class="mt-5 mb-5">@loc["WEBFRONT_HELP_COMMAND_DESC_REQUIRED_LEVEL"]</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="mt-5 mb-5">@command.Name</div>
|
||||
<div class="mt-5 mb-5">@command.Alias</div>
|
||||
<div class="mt-5 mb-5">@command.Description</div>
|
||||
<div class="mt-5 mb-5">@command.RequiresTarget</div>
|
||||
<div class="mt-5 mb-5">@ViewBag.CommandPrefix@command.Syntax.Split(ViewBag.CommandPrefix)[1]</div>
|
||||
<div class="mt-5 mb-5 @($"level-color-{(int)command.Permission}")">@command.Permission.ToLocalizedLevelName()</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- mobile -->
|
||||
<table class="table border-bottomb d-table d-lg-none">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col"></th>
|
||||
<th scope="col"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var command in commandList)
|
||||
{
|
||||
<tr>
|
||||
<td scope="row" class="bg-primary">@loc["WEBFRONT_HELP_COMMAND_DESC_NAME"]</td>
|
||||
<td class="bg-dark text-light">@command.Name</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td scope="row" class="bg-primary">@loc["WEBFRONT_HELP_COMMAND_DESC_ALIAS"]</td>
|
||||
<td class="bg-dark text-light">@command.Alias</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td scope="row" class="bg-primary">@loc["WEBFRONT_HELP_COMMAND_DESC_DESCRIPTION"]</td>
|
||||
<td class="bg-dark text-light">@command.Description</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td scope="row" class="bg-primary">@loc["WEBFRONT_HELP_COMMAND_DESC_REQUIRES_TARGET"]</td>
|
||||
<td class="bg-dark text-light">@command.RequiresTarget</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td scope="row" class="bg-primary">@loc["WEBFRONT_HELP_COMMAND_DESC_SYNTAX"]</td>
|
||||
<td class="bg-dark text-light">@ViewBag.CommandPrefix@command.Syntax.Split(@ViewBag.CommandPrefix)[1]</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td scope="row" class="bg-primary" style="border-bottom: #222 1px solid;">@loc["WEBFRONT_HELP_COMMAND_DESC_REQUIRED_LEVEL"]</td>
|
||||
<td class="bg-dark border-bottom @($"level-color-{(int)command.Permission}")">@command.Permission.ToLocalizedLevelName()</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
</div>
|
||||
|
@ -1,44 +1,57 @@
|
||||
@model SharedLibraryCore.Dtos.IW4MAdminInfo
|
||||
@{
|
||||
var loc = SharedLibraryCore.Utilities.CurrentLocalization.LocalizationIndex;
|
||||
string formatTranslation(string translationKey, params object[] values)
|
||||
@using WebfrontCore.ViewModels
|
||||
@using Humanizer
|
||||
@model SharedLibraryCore.Dtos.IW4MAdminInfo
|
||||
@{
|
||||
var loc = Utilities.CurrentLocalization.LocalizationIndex;
|
||||
|
||||
string FormatTranslation(string translationKey, params object[] values)
|
||||
{
|
||||
var split = loc[translationKey].Split("::");
|
||||
return split.Count() == 2 ? $"<span class='font-weight-bold text-primary'>{SharedLibraryCore.Utilities.FormatExt(split[0], values)}</span><span>{split[1]}</span>" : translationKey;
|
||||
return split.Length == 2 ? $"<span class='font-weight-bold text-primary'>{split[0].FormatExt(values)}</span><span>{split[1]}</span>" : translationKey;
|
||||
}
|
||||
}
|
||||
<div class="row mb-4 border-bottom border-top pt-3 pb-3 bg-dark">
|
||||
<div class="col-xl-3 col-12">
|
||||
<div class="text-muted text-center text-xl-left">@Html.Raw(formatTranslation("WEBFRONT_HOME_CLIENTS_ONLINE", Model.TotalOccupiedClientSlots, Model.TotalAvailableClientSlots))</div>
|
||||
</div>
|
||||
<div class="col-xl-3 col-12 moment-date" title="@Model.MaxConcurrentClientsTime">
|
||||
<div class="text-muted text-center text-xl-left">@Html.Raw(formatTranslation("WEBFRONT_HOME_MAX_CONCURRENT_CLIENTS", Model.MaxConcurrentClients.ToString("#,##0")))</div>
|
||||
</div>
|
||||
<div class="col-xl-3 col-12">
|
||||
<div class="text-muted text-center">@Html.Raw(formatTranslation("WEBFRONT_HOME_RECENT_CLIENTS", Model.RecentClientCount.ToString("#,##0")))</div>
|
||||
</div>
|
||||
<div class="col-xl-3 col-12">
|
||||
<div class="text-muted text-center text-xl-right">@Html.Raw(formatTranslation("WEBFRONT_HOME_TOTAL_CLIENTS", Model.TotalClientCount.ToString("#,##0")))</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (Model.ActiveServerGames.Length > 1)
|
||||
{
|
||||
<ul class="nav nav-tabs border-top border-bottom nav-fill row mb-4" role="tablist">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link @(Model.Game.HasValue ? "" : "active")" href="/" role="tab" aria-selected="true">@loc["GAME_ALL"]</a>
|
||||
</li>
|
||||
|
||||
@foreach (var gameName in Model.ActiveServerGames)
|
||||
<div class="content mt-20 row">
|
||||
<div class="col-12 col-lg-9 col-xl-10">
|
||||
<h2 class="content-title mb-0">Server Overview</h2>
|
||||
@if (Model.Game.HasValue)
|
||||
{
|
||||
<li class="nav-item">
|
||||
<a asp-action="Index" asp-controller="Home" asp-route-game="@gameName" class="nav-link @(Model.Game == gameName ? "active" : "")" role="tab" aria-selected="false">@loc[$"GAME_{gameName}"]</a>
|
||||
</li>
|
||||
<span class="text-muted">@loc[$"GAME_{Model.Game.Value}"]</span>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
|
||||
@await Component.InvokeAsync("ServerList", Model.Game)
|
||||
else
|
||||
{
|
||||
<span class="text-muted">@ViewBag.Localization["WEBFRONT_STATS_INDEX_ALL_SERVERS"]</span>
|
||||
}
|
||||
<div class="card p-0 m-0">
|
||||
<div class="d-flex flex-column flex-md-row justify-content-md-between bg-dark-dm bg-light-lm p-10 mt-15 mb-15 rounded">
|
||||
<div class="align-self-center align-content-md-start">@Html.Raw(FormatTranslation("WEBFRONT_HOME_CLIENTS_ONLINE", Model.TotalOccupiedClientSlots, Model.TotalAvailableClientSlots))</div>
|
||||
<div class="align-self-center align-content-md-start" data-toggle="tooltip" data-title="@Model.MaxConcurrentClientsTime.HumanizeForCurrentCulture().Titleize()">@Html.Raw(FormatTranslation("WEBFRONT_HOME_MAX_CONCURRENT_CLIENTS", Model.MaxConcurrentClients.ToString("#,##0")))</div>
|
||||
<div class="align-self-center align-content-md-start">@Html.Raw(FormatTranslation("WEBFRONT_HOME_RECENT_CLIENTS", Model.RecentClientCount.ToString("#,##0")))</div>
|
||||
<div class="align-self-center align-content-md-start">@Html.Raw(FormatTranslation("WEBFRONT_HOME_TOTAL_CLIENTS", Model.TotalClientCount.ToString("#,##0")))</div>
|
||||
</div>
|
||||
</div>
|
||||
@await Component.InvokeAsync("ServerList", Model.Game)
|
||||
</div>
|
||||
|
||||
@{
|
||||
var menuItems = new SideContextMenuItems
|
||||
{
|
||||
MenuTitle = "Game", Items = Model.ActiveServerGames.Select(game => new SideContextMenuItem
|
||||
{
|
||||
IsLink = true,
|
||||
Reference = Url.Action("Index", "Home", new { game }),
|
||||
Title = loc[$"GAME_{game}"],
|
||||
IsActive = game == Model.Game
|
||||
}).Prepend(new SideContextMenuItem
|
||||
{
|
||||
IsLink = true,
|
||||
Reference = Url.Action("Index", "Home"),
|
||||
Title = loc["GAME_ALL"],
|
||||
IsActive = !Model.Game.HasValue
|
||||
}).ToList()
|
||||
};
|
||||
}
|
||||
<partial name="_SideContextMenu" for="@menuItems"></partial>
|
||||
</div>
|
||||
|
||||
@section scripts {
|
||||
<environment include="Development">
|
||||
|
@ -1,86 +1,101 @@
|
||||
@model Data.Models.EFPenalty.PenaltyType
|
||||
@using Humanizer
|
||||
@using Data.Models
|
||||
@model Data.Models.EFPenalty.PenaltyType
|
||||
@{
|
||||
var loc = SharedLibraryCore.Utilities.CurrentLocalization.LocalizationIndex;
|
||||
var loc = Utilities.CurrentLocalization.LocalizationIndex;
|
||||
}
|
||||
<h4 class="pb-3 text-center">@ViewBag.Title</h4>
|
||||
<div class="row">
|
||||
<div class="d-block d-md-flex w-100 pb-2">
|
||||
<select class="form-control bg-dark text-muted" id="penalty_filter_selection">
|
||||
@{
|
||||
foreach (var penaltyType in Enum.GetValues(typeof(Data.Models.EFPenalty.PenaltyType)))
|
||||
{
|
||||
if ((Data.Models.EFPenalty.PenaltyType)penaltyType == Data.Models.EFPenalty.PenaltyType.Any)
|
||||
{
|
||||
if (Model == Data.Models.EFPenalty.PenaltyType.Any)
|
||||
<div class="content mt-20">
|
||||
<h2 class="content-title mt-15">@ViewBag.Title</h2>
|
||||
<table class="table with-fixed-layout bg-dark-dm bg-light-lm">
|
||||
<thead>
|
||||
<tr class="bg-primary text-light d-none d-lg-table-row">
|
||||
<th colspan="20%">@loc["WEBFRONT_PENALTY_TEMPLATE_NAME"]</th>
|
||||
<th colspan="10%">@loc["WEBFRONT_PENALTY_TEMPLATE_TYPE"]</th>
|
||||
<th colspan="35%">@loc["WEBFRONT_PENALTY_TEMPLATE_OFFENSE"]</th>
|
||||
<th colspan="20%">@loc["WEBFRONT_PENALTY_TEMPLATE_ADMIN"]</th>
|
||||
<th colspan="15%" class="text-right">@loc["WEBFRONT_PENALTY_TEMPLATE_TIME"]</th>
|
||||
</tr>
|
||||
<tr class="d-flex d-lg-table-row flex-column">
|
||||
<td colspan="20%" class="p-10 pb-10">
|
||||
<div class="d-flex p-5 pr-15 bg-dark-dm bg-light-lm rounded">
|
||||
<div class="custom-switch">
|
||||
@if (ViewBag.HideAutomatedPenalties)
|
||||
{
|
||||
<option value="@Convert.ToInt32(penaltyType)" selected="selected" )>@loc["WEBFRONT_PENALTY_TEMPLATE_SHOW"] @penaltyType.ToString()</option>
|
||||
<input type="checkbox" id="hide_automated_penalties_checkbox" checked="checked"/>
|
||||
}
|
||||
else
|
||||
{
|
||||
<option value="@Convert.ToInt32(penaltyType)" )>@loc["WEBFRONT_PENALTY_TEMPLATE_SHOW"] @penaltyType.ToString()</option>
|
||||
<input type="checkbox" id="hide_automated_penalties_checkbox"/>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((Data.Models.EFPenalty.PenaltyType)penaltyType == Model)
|
||||
<label for="hide_automated_penalties_checkbox">@loc["WEBFRONT_PENALTY_HIDE_AUTOMATED"]</label>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td colspan="46%" class="p-10 pt-5 pb-5">
|
||||
<div class="input-group mb-10 mb-lg-0">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">@loc["WEBFRONT_PENALTY_TEMPLATE_SHOWONLY"].Titleize()</span>
|
||||
</div>
|
||||
<select class="pl-15 form-control" id="penalty_filter_selection">
|
||||
@foreach (var penaltyType in Enum.GetValues(typeof(EFPenalty.PenaltyType)).Cast<EFPenalty.PenaltyType>().OrderByDescending(penalty => penalty == EFPenalty.PenaltyType.Any))
|
||||
{
|
||||
<option value="@Convert.ToInt32(penaltyType)" selected="selected">@loc["WEBFRONT_PENALTY_TEMPLATE_SHOWONLY"] @penaltyType.ToString()s</option>
|
||||
if (penaltyType == EFPenalty.PenaltyType.Any)
|
||||
{
|
||||
if (Model == EFPenalty.PenaltyType.Any)
|
||||
{
|
||||
<option value="@Convert.ToInt32(penaltyType)" selected="selected">@penaltyType.ToString()</option>
|
||||
}
|
||||
else
|
||||
{
|
||||
<option value="@Convert.ToInt32(penaltyType)">@penaltyType.ToString()</option>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (penaltyType == Model)
|
||||
{
|
||||
<option value="@Convert.ToInt32(penaltyType)" selected="selected">@penaltyType.ToString()</option>
|
||||
}
|
||||
else
|
||||
{
|
||||
<option value="@Convert.ToInt32(penaltyType)">@penaltyType.ToString()</option>
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<option value="@Convert.ToInt32(penaltyType)" )>@loc["WEBFRONT_PENALTY_TEMPLATE_SHOWONLY"] @penaltyType.ToString()s</option>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</select>
|
||||
<div class="pl-md-2 pr-md-2 pt-2 pt-md-0">
|
||||
<label class="toggle-switch">
|
||||
@if (ViewBag.HideAutomatedPenalties)
|
||||
{
|
||||
<input type="checkbox" id="hide_automated_penalties_checkbox" checked="checked" />
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
<input type="checkbox" id="hide_automated_penalties_checkbox" />
|
||||
}
|
||||
<span class="toggle-switch-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="align-self-center">
|
||||
<span class="text-light text-nowrap">@loc["WEBFRONT_PENALTY_HIDE_AUTOMATED"]</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<table class="table table-striped">
|
||||
<thead class="d-none d-lg-table-header-group">
|
||||
<tr class="bg-primary pt-2 pb-2">
|
||||
<th scope="col">@loc["WEBFRONT_PENALTY_TEMPLATE_NAME"]</th>
|
||||
<th scope="col">@loc["WEBFRONT_PENALTY_TEMPLATE_TYPE"]</th>
|
||||
<th scope="col">@loc["WEBFRONT_PENALTY_TEMPLATE_OFFENSE"]</th>
|
||||
<th scope="col">@loc["WEBFRONT_PENALTY_TEMPLATE_ADMIN"]</th>
|
||||
<th scope="col" class="text-right">@loc["WEBFRONT_PENALTY_TEMPLATE_TIME"]</th>
|
||||
</tr>
|
||||
</select>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="penalty_table" class="border-bottom bg-dark">
|
||||
@await Component.InvokeAsync("PenaltyList", new WebfrontCore.ViewModels.PenaltyFilterInfo()
|
||||
{
|
||||
Offset = 0,
|
||||
ShowOnly = Model,
|
||||
IgnoreAutomated = ViewBag.HideAutomatedPenalties
|
||||
})
|
||||
<tbody id="penalty_table">
|
||||
@await Component.InvokeAsync("PenaltyList", new WebfrontCore.ViewModels.PenaltyFilterInfo
|
||||
{
|
||||
Offset = 0,
|
||||
Count = 30,
|
||||
ShowOnly = Model,
|
||||
IgnoreAutomated = ViewBag.HideAutomatedPenalties,
|
||||
})
|
||||
</tbody>
|
||||
</table>
|
||||
<table class="table d-table d-lg-none">
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
<span id="load_penalties_button" class="oi oi-chevron-bottom text-center text-primary w-100 h3 pb-0 mb-0 d-none d-lg-block"></span>
|
||||
|
||||
<div class="w-full text-center">
|
||||
<i id="loaderLoad" class="oi oi-chevron-bottom mt-10 loader-load-more text-primary m-auto" aria-hidden="true"></i>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@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>
|
||||
initLoader('/Penalty/ListAsync', '#penalty_table', 30, 30, [{
|
||||
'name': 'hideAutomatedPenalties',
|
||||
'value': () => document.getElementById('hide_automated_penalties_checkbox').checked
|
||||
}, {
|
||||
'name': 'showOnly',
|
||||
'value': () => $('#penalty_filter_selection').val()
|
||||
}]);
|
||||
</script>
|
||||
}
|
||||
|
@ -4,4 +4,4 @@
|
||||
|
||||
@model WebfrontCore.ViewModels.PenaltyFilterInfo
|
||||
|
||||
@await Component.InvokeAsync("PenaltyList", new { offset = Model.Offset, showOnly = Model.ShowOnly, ignoreAutomated = Model.IgnoreAutomated })
|
||||
@await Component.InvokeAsync("PenaltyList", new { offset = Model.Offset, count= Model.Count, showOnly = Model.ShowOnly, ignoreAutomated = Model.IgnoreAutomated })
|
||||
|
@ -1,84 +1,70 @@
|
||||
@{
|
||||
Layout = null;
|
||||
var loc = SharedLibraryCore.Utilities.CurrentLocalization.LocalizationIndex;
|
||||
var loc = Utilities.CurrentLocalization.LocalizationIndex;
|
||||
|
||||
var canSeeLevel = (ViewBag.PermissionsSet as IEnumerable<string>).HasPermission(WebfrontEntity.ClientLevel, WebfrontPermission.Read) && Model.PunisherLevel != 0;
|
||||
var punisherLevelClass = canSeeLevel ? $"level-color-{(int)Model.PunisherLevel}" : "text-light-dm text-dark-lm";
|
||||
}
|
||||
|
||||
@using WebfrontCore.Permissions
|
||||
@model SharedLibraryCore.Dtos.PenaltyInfo
|
||||
|
||||
<tr class="d-table-row d-lg-none bg-dark">
|
||||
<th scope="row" class="bg-primary">@loc["WEBFRONT_PENALTY_TEMPLATE_NAME"]</th>
|
||||
<td>
|
||||
<a asp-controller="Client" asp-action="ProfileAsync" asp-route-id="@Model.OffenderId" class="link-inverse">
|
||||
<color-code value="@Model.OffenderName"></color-code>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr class="d-table-row d-lg-none bg-dark">
|
||||
<th scope="row" class="bg-primary">@loc["WEBFRONT_PENALTY_TEMPLATE_TYPE"]</th>
|
||||
<td class="penalties-color-@Model.PenaltyTypeText.ToLower()">
|
||||
@Model.PenaltyType
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr class="d-table-row d-lg-none bg-dark">
|
||||
<th scope="row" class="bg-primary">@loc["WEBFRONT_PENALTY_TEMPLATE_OFFENSE"]</th>
|
||||
<td class="text-light">
|
||||
<color-code value="@($"{Model.Offense}{(ViewBag.Authorized ? Model.AdditionalPenaltyInformation : "")}")"></color-code>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr class="d-table-row d-lg-none bg-dark">
|
||||
<th scope="row" class="bg-primary">@loc["WEBFRONT_PENALTY_TEMPLATE_ADMIN"]</th>
|
||||
<td>
|
||||
@Html.ActionLink(SharedLibraryCore.Utilities.StripColors(Model.PunisherName), "ProfileAsync",
|
||||
"Client",
|
||||
new { id = Model.PunisherId },
|
||||
new { @class = !ViewBag.Authorized && ViewBag.EnablePrivilegedUserPrivacy
|
||||
? "level-color-0"
|
||||
: "level-color-" + (int)Model.PunisherLevel })
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr class="d-table-row d-lg-none bg-dark">
|
||||
<th scope="row" class="w-25 bg-primary" style="border-bottom: 1px solid #222">@loc["WEBFRONT_PENALTY_TEMPLATE_TIME"]</th>
|
||||
<td class="text-light mb-2 border-bottom">
|
||||
@{
|
||||
if (Model.Expired)
|
||||
{
|
||||
<span>@Model.TimePunishedString</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span>@Model.TimeRemaining</span>
|
||||
}
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- desktop -->
|
||||
<tr class="d-none d-lg-table-row">
|
||||
<td>
|
||||
<a asp-controller="Client" asp-action="ProfileAsync" asp-route-id="@Model.OffenderId" class="link-inverse">
|
||||
<td colspan="20%">
|
||||
<a asp-controller="Client" asp-action="Profile" asp-route-id="@Model.OffenderId" class="link-inverse">
|
||||
<color-code value="@Model.OffenderName"></color-code>
|
||||
</a>
|
||||
</td>
|
||||
<td class="penalties-color-@Model.PenaltyTypeText.ToLower()">
|
||||
<td colspan="10%" class="penalties-color-@Model.PenaltyTypeText.ToLower()">
|
||||
@Model.PenaltyType
|
||||
</td>
|
||||
<td class="text-light w-50">
|
||||
<td colspan="35%">
|
||||
<color-code value="@($"{Model.Offense}{(ViewBag.Authorized ? Model.AdditionalPenaltyInformation : "")}")"></color-code>
|
||||
</td>
|
||||
<td>
|
||||
@Html.ActionLink(SharedLibraryCore.Utilities.StripColors(Model.PunisherName), "ProfileAsync",
|
||||
"Client",
|
||||
new { id = Model.PunisherId },
|
||||
new { @class = !ViewBag.Authorized && ViewBag.EnablePrivilegedUserPrivacy
|
||||
? "level-color-0"
|
||||
: "level-color-" + (int)Model.PunisherLevel })
|
||||
<td colspan="20%">
|
||||
<a asp-controller="Client" asp-action="Profile" asp-route-id="@Model.PunisherId" class="@punisherLevelClass">
|
||||
<color-code value="@Model.PunisherName"></color-code>
|
||||
</a>
|
||||
</td>
|
||||
<td class="text-right text-light">
|
||||
@{
|
||||
if (Model.Expired)
|
||||
<td colspan="15%" class="text-right">
|
||||
@if (Model.Expired)
|
||||
{
|
||||
<span>@Model.TimePunishedString</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span>@Model.TimeRemaining</span>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- mobile -->
|
||||
<tr class="d-table-row d-lg-none d-flex border-bottom">
|
||||
<td class="bg-primary text-light text-right">
|
||||
<div class="mt-5 mb-5">@loc["WEBFRONT_PENALTY_TEMPLATE_NAME"]</div>
|
||||
<div class="mt-5 mb-5">@loc["WEBFRONT_PENALTY_TEMPLATE_TYPE"]</div>
|
||||
<div class="mt-5 mb-5">@loc["WEBFRONT_PENALTY_TEMPLATE_OFFENSE"]</div>
|
||||
<div class="mt-5 mb-5">@loc["WEBFRONT_PENALTY_TEMPLATE_ADMIN"]</div>
|
||||
<div class="mt-5 mb-5">@loc["WEBFRONT_PENALTY_TEMPLATE_TIME"]</div>
|
||||
</td>
|
||||
<td class="flex-fill">
|
||||
<div class="mt-5 mb-5">
|
||||
<a asp-controller="Client" asp-action="Profile" asp-route-id="@Model.OffenderId" >
|
||||
<color-code value="@Model.OffenderName"></color-code>
|
||||
</a>
|
||||
</div>
|
||||
<div class="mt-5 mb-5 penalties-color-@Model.PenaltyTypeText.ToLower()">
|
||||
@Model.PenaltyType
|
||||
</div>
|
||||
<div class="mt-5 mb-5">
|
||||
<color-code value="@($"{Model.Offense}{(ViewBag.Authorized ? Model.AdditionalPenaltyInformation : "")}")"></color-code>
|
||||
</div>
|
||||
<a asp-controller="Client" asp-action="Profile" asp-route-id="@Model.PunisherId" class="mt-5 mb-5 @((!ViewBag.Authorized && ViewBag.EnablePrivilegedUserPrivacy) || Model.PunisherLevel == 0 ? "text-dark-lm text-light-dm" : "level-color-" + (int)Model.PunisherLevel)">
|
||||
<color-code value="@Model.PunisherName"></color-code>
|
||||
</a>
|
||||
<div class="mt-5 mb-5">
|
||||
@if (Model.Expired)
|
||||
{
|
||||
<span>@Model.TimePunishedString</span>
|
||||
}
|
||||
@ -86,6 +72,6 @@
|
||||
{
|
||||
<span>@Model.TimeRemaining</span>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -1,26 +1,28 @@
|
||||
@model IEnumerable<WebfrontCore.ViewModels.ScoreboardInfo>
|
||||
@using WebfrontCore.ViewModels
|
||||
@model IEnumerable<WebfrontCore.ViewModels.ScoreboardInfo>
|
||||
|
||||
<ul class="nav nav-tabs border-top border-bottom nav-fill row" role="tablist" id="scoreboard_servers">
|
||||
@{ var i = 0; }
|
||||
@foreach (var server in Model)
|
||||
{
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#server_@server.ServerId" role="tab" data-toggle="tab" id="server_@(server.ServerId)_nav" data-serverid="@server.ServerId">
|
||||
<color-code value="@server.ServerName"></color-code>
|
||||
</a>
|
||||
</li>
|
||||
i++;
|
||||
}
|
||||
</ul>
|
||||
<div class="tab-content border-bottom row">
|
||||
@{ i = 0; }
|
||||
@foreach (var server in Model)
|
||||
{
|
||||
<div role="tabpanel" class="scoreboard-container tab-pane striped flex-fill" id="server_@server.ServerId" data-server-id="@server.ServerId">
|
||||
@await Html.PartialAsync("_Scoreboard", server)
|
||||
</div>
|
||||
i++;
|
||||
<div class="content mt-20 row">
|
||||
<div class="col-12 col-lg-9 col-xl-10">
|
||||
@if (Model is not null)
|
||||
{
|
||||
<div class=" scoreboard-container" data-server-id="@ViewBag.SelectedServerId">
|
||||
@await Html.PartialAsync("_Scoreboard", Model.FirstOrDefault(server => server.ServerId == ViewBag.SelectedServerId) ?? Model.First())
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@{
|
||||
var menuItems = new SideContextMenuItems
|
||||
{
|
||||
MenuTitle = "Server", Items = Model.Select(server => new SideContextMenuItem
|
||||
{
|
||||
IsLink = true,
|
||||
Reference = Url.Action("Scoreboard", "Server", new { serverId = server.ServerId }),
|
||||
Title = server.ServerName.StripColors(),
|
||||
IsActive = ViewBag.SelectedServerId == server.ServerId
|
||||
}).ToList()
|
||||
};
|
||||
}
|
||||
<partial name="_SideContextMenu" for="@menuItems"></partial>
|
||||
</div>
|
||||
|
||||
@section scripts {
|
||||
|
@ -2,146 +2,81 @@
|
||||
|
||||
@{
|
||||
Layout = null;
|
||||
int half = Model.ClientCount == 0 || Model.Players.Count == 0 ? 0 : (int)Math.Ceiling(Model.ClientCount / 2.0);
|
||||
}
|
||||
<div class="col-12 col-md-7 d-none d-md-block">
|
||||
@{
|
||||
for (int i = 0; i < Model.ChatHistory.Count; i++)
|
||||
var half = Model.ClientCount == 0 || Model.Players.Count == 0 ? 0 : (int)Math.Ceiling(Model.ClientCount / 2.0);
|
||||
var groupedClients = Model.Players.Select((client, i) => new { index = i, client })
|
||||
.OrderBy(client => client.client.Name)
|
||||
.GroupBy(client => client.index >= half).Select((group, index) => new
|
||||
{
|
||||
if (Model.ChatHistory[i] == null ||
|
||||
Model.ChatHistory[i].Message == null ||
|
||||
Model.ChatHistory[i].Name == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
group,
|
||||
index
|
||||
}).ToList();
|
||||
|
||||
string message = Model.ChatHistory[i].IsHidden && !ViewBag.Authorized ? Model.ChatHistory[i].HiddenMessage : Model.ChatHistory[i].Message;
|
||||
|
||||
if (Model.ChatHistory[i].Message == "CONNECTED")
|
||||
{
|
||||
<span class="text-light">
|
||||
<span class="oi oi-account-login mr-2 text-success"> </span>
|
||||
<color-code value="@Model.ChatHistory[i].Name"></color-code>
|
||||
</span><br />
|
||||
}
|
||||
if (Model.ChatHistory[i].Message == "DISCONNECTED")
|
||||
{
|
||||
<span class="text-light">
|
||||
<span class="oi oi-account-logout mr-2 text-danger"> </span>
|
||||
<color-code value="@Model.ChatHistory[i].Name"></color-code>
|
||||
</span><br />
|
||||
}
|
||||
if (Model.ChatHistory[i].Message != "CONNECTED" && Model.ChatHistory[i].Message != "DISCONNECTED")
|
||||
{
|
||||
<span class="text-light">
|
||||
<color-code value="@Model.ChatHistory[i].Name"></color-code>
|
||||
</span>
|
||||
<span>
|
||||
—
|
||||
<color-code value="@message.CapClientName(48)"></color-code>
|
||||
</span><br />
|
||||
}
|
||||
}
|
||||
string GetIconForState(string messageState)
|
||||
{
|
||||
return messageState switch
|
||||
{
|
||||
"CONNECTED" => "oi-account-login text-success mr-5",
|
||||
"DISCONNECTED" => "oi-account-logout text-danger mr-5",
|
||||
_ => ""
|
||||
};
|
||||
}
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
<div class="row" style="overflow-wrap: anywhere">
|
||||
<div class="col-6 text-left text-md-right">
|
||||
@{
|
||||
for (int i = 0; i < half; i++)
|
||||
{
|
||||
if (i > Model.Players.Count - 1)
|
||||
}
|
||||
|
||||
<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">
|
||||
@foreach (var chat in Model.ChatHistory)
|
||||
{
|
||||
var message = chat.IsHidden && !ViewBag.Authorized ? chat.HiddenMessage : chat.Message;
|
||||
var stateIcon = GetIconForState(chat.Message);
|
||||
|
||||
<div>
|
||||
<i class="oi @stateIcon"></i>
|
||||
|
||||
<span>
|
||||
<color-code value="@chat.Name"></color-code>
|
||||
</span>
|
||||
@if (stateIcon == "")
|
||||
{
|
||||
continue;
|
||||
<span class="text-truncate">
|
||||
—
|
||||
<span class="text-white-dm text-black-lm">
|
||||
<color-code value="@message?.CapClientName(48)"></color-code>
|
||||
</span>
|
||||
</span>
|
||||
}
|
||||
|
||||
string levelColorClass = !ViewBag.Authorized ? "" : $"level-color-{Model.Players[i].LevelInt}";
|
||||
<div class="d-inline-flex">
|
||||
@if (ViewBag.Authorized)
|
||||
{
|
||||
<div class="oi oi-circle-x ml-0 mr-1 profile-action action-kick-button d-inline d-md-none" data-action="kick" data-action-id="@Model.Players[i].ClientId" aria-hidden="true"></div>
|
||||
}
|
||||
|
||||
<a asp-controller="Client" asp-action="ProfileAsync" asp-route-id="@Model.Players[i].ClientId" class="@levelColorClass">
|
||||
<color-code value="@Model.Players[i].Name"></color-code>
|
||||
</a>
|
||||
|
||||
@if (ViewBag.Authorized)
|
||||
{
|
||||
<div class="oi oi-circle-x ml-1 profile-action action-kick-button d-none d-md-inline" data-action="kick" data-action-id="@Model.Players[i].ClientId" aria-hidden="true"></div>
|
||||
}
|
||||
</div>
|
||||
<br />
|
||||
}
|
||||
</div>
|
||||
|
||||
}
|
||||
<hr class="d-block d-md-none"/>
|
||||
</div>
|
||||
<div class="col-6 text-right w-50">
|
||||
@{
|
||||
for (int i = half; i < Math.Min(Model.ClientCount, Model.Players.Count); i++)
|
||||
}
|
||||
|
||||
<div class="d-flex flex-row flex-fill flex-lg-grow-0 w-half-md">
|
||||
|
||||
@foreach (var clientIndex in groupedClients)
|
||||
{
|
||||
<div class="@(clientIndex.index == 1 ? "pl-md-10 text-right" : "pr-md-10") flex-fill">
|
||||
@foreach (var client in clientIndex.group)
|
||||
{
|
||||
if (i > Model.Players.Count - 1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string levelColorClass = !ViewBag.Authorized ? "" : $"level-color-{Model.Players[i].LevelInt}";
|
||||
|
||||
<div>
|
||||
<a asp-controller="Client" asp-action="ProfileAsync" asp-route-id="@Model.Players[i].ClientId" class="@levelColorClass">
|
||||
<color-code value="@Model.Players[i].Name"></color-code>
|
||||
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">
|
||||
<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>
|
||||
</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">
|
||||
<color-code value="@client.client.Name"></color-code>
|
||||
</a>
|
||||
@if (ViewBag.Authorized)
|
||||
{
|
||||
<span class="oi oi-circle-x profile-action align-baseline action-kick-button flex-column mt-0" data-action="kick" data-action-id="@Model.Players[i].ClientId" aria-hidden="true"></span>
|
||||
}
|
||||
<br />
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (groupedClients.Count > 0)
|
||||
{
|
||||
<br/>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@if (Model.ChatHistory.Count > 0)
|
||||
{
|
||||
<div class="w-100 border-bottom d-md-none d-block mt-1 mb-1"></div>
|
||||
}
|
||||
<div class="col-12 col-md-8 d-md-none d-block text-left">
|
||||
@{
|
||||
for (int i = 0; i < Model.ChatHistory.Count; i++)
|
||||
{
|
||||
if (Model.ChatHistory[i] == null ||
|
||||
Model.ChatHistory[i].Message == null ||
|
||||
Model.ChatHistory[i].Name == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string message = Model.ChatHistory[i].IsHidden && !ViewBag.Authorized ? Model.ChatHistory[i].HiddenMessage : Model.ChatHistory[i].Message;
|
||||
|
||||
if (Model.ChatHistory[i].Message == "CONNECTED")
|
||||
{
|
||||
<span class="text-light">
|
||||
<span class="oi oi-account-login mr-2 text-success"> </span>
|
||||
<color-code value="@Model.ChatHistory[i].Name"></color-code>
|
||||
</span><br />
|
||||
}
|
||||
if (Model.ChatHistory[i].Message == "DISCONNECTED")
|
||||
{
|
||||
<span class="text-light">
|
||||
<span class="oi oi-account-logout mr-2 text-danger"> </span>
|
||||
<color-code value="@Model.ChatHistory[i].Name"></color-code>
|
||||
</span><br />
|
||||
}
|
||||
if (Model.ChatHistory[i].Message != "CONNECTED" && Model.ChatHistory[i].Message != "DISCONNECTED")
|
||||
{
|
||||
<span class="text-light">
|
||||
<color-code value="@Model.ChatHistory[i].Name"></color-code>
|
||||
</span>
|
||||
<span>
|
||||
—
|
||||
<color-code value="@message.CapClientName(48)"></color-code>
|
||||
</span><br />
|
||||
}
|
||||
}
|
||||
}
|
||||
</div>
|
||||
|
@ -1,5 +1,6 @@
|
||||
@using WebfrontCore.ViewModels
|
||||
@using System.Globalization
|
||||
@using SharedLibraryCore.Database.Models
|
||||
@model WebfrontCore.ViewModels.ScoreboardInfo
|
||||
@{
|
||||
Layout = null;
|
||||
@ -16,17 +17,29 @@
|
||||
{
|
||||
if (propertyName == (Model.OrderByKey ?? nameof(ClientScoreboardInfo.Score)))
|
||||
{
|
||||
return Model.ShouldOrderDescending ? "<span class=\"ml-2\">▼</span>" : "<span class=\"ml-2\">▲</span>";
|
||||
return Model.ShouldOrderDescending ? "<span class=\"ml-5 font-size-12\">▼</span>" : "<span class=\"ml-5 font-size-12\">▲</span>";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
string GetTeamBackgroundColorClass(ClientScoreboardInfo client)
|
||||
{
|
||||
return $"team-{client.Team.ToString().ToLower()}-bg {(client.Team == EFClient.TeamType.Unknown ? "bg-dark-dm bg-light-lm" : "")}";
|
||||
}
|
||||
}
|
||||
|
||||
<table class="table thead-light bg-dark mb-0 table-responsive-md table-sort"
|
||||
<h4 class="content-title mb-0">
|
||||
Scoreboard
|
||||
</h4>
|
||||
<span class="text-muted">
|
||||
<color-code value="@Model.ServerName"></color-code>
|
||||
</span>
|
||||
|
||||
<table class="table table-sort mt-15"
|
||||
data-sort-column="@(Model.OrderByKey ?? nameof(ClientScoreboardInfo.Score))"
|
||||
data-sort-down="@Model.ShouldOrderDescending.ToString().ToLower()">
|
||||
<tr class="bg-dark border-bottom">
|
||||
<tr class="bg-dark-dm bg-white-lm d-none d-lg-table-row">
|
||||
<th class="table-sort-column" data-column-name="@nameof(ClientScoreboardInfo.ClientName)">@ViewBag.Localization["WEBFRONT_SCOREBOARD_TABLE_PLAYER"]@Html.Raw(GetColumnSortDisplay(nameof(ClientScoreboardInfo.ClientName)))</th>
|
||||
<th class="table-sort-column" data-column-name="@nameof(ClientScoreboardInfo.Score)">@ViewBag.Localization["WEBFRONT_ADV_STATS_SCORE"]@Html.Raw(GetColumnSortDisplay(nameof(ClientScoreboardInfo.Score)))</th>
|
||||
<th class="table-sort-column" data-column-name="@nameof(ClientScoreboardInfo.Kills)">@ViewBag.Localization["WEBFRONT_ADV_STATS_KILLS"]@Html.Raw(GetColumnSortDisplay(nameof(ClientScoreboardInfo.Kills)))</th>
|
||||
@ -38,9 +51,10 @@
|
||||
</tr>
|
||||
@foreach (var client in Model.ShouldOrderDescending ? Model.ClientInfo.OrderByDescending(OrderByFunc) : Model.ClientInfo.OrderBy(OrderByFunc))
|
||||
{
|
||||
<tr class="team-@client.Team.ToString().ToLower()-bg">
|
||||
<!-- desktop -->
|
||||
<tr class="@GetTeamBackgroundColorClass(client) d-none d-lg-table-row">
|
||||
<td>
|
||||
<a asp-controller="Client" asp-action="ProfileAsync" asp-route-id="@client.ClientId">
|
||||
<a asp-controller="Client" asp-action="Profile" asp-route-id="@client.ClientId" class="no-decoration text-light-dm text-dark-lm">
|
||||
<color-code value="@client.ClientName"></color-code>
|
||||
</a>
|
||||
</td>
|
||||
@ -52,5 +66,31 @@
|
||||
<td>@(client.ZScore is null or 0 ? "--" : Math.Round(client.ZScore.Value, 2).ToString(CultureInfo.CurrentCulture))</td>
|
||||
<td class="text-right">@client.Ping</td>
|
||||
</tr>
|
||||
|
||||
<tr class="d-table-row d-lg-none d-flex">
|
||||
<td class="text-right bg-primary text-light flex-grow-0">
|
||||
<div>@ViewBag.Localization["WEBFRONT_SCOREBOARD_TABLE_PLAYER"]</div>
|
||||
<div>@ViewBag.Localization["WEBFRONT_ADV_STATS_SCORE"]</div>
|
||||
<div>@ViewBag.Localization["WEBFRONT_ADV_STATS_KILLS"]</div>
|
||||
<div>@ViewBag.Localization["WEBFRONT_SCOREBOARD_TABLE_DEATHS"]</div>
|
||||
<div>@ViewBag.Localization["WEBFRONT_SCOREBOARD_TABLE_RATIO"]</div>
|
||||
<div>@ViewBag.Localization["WEBFRONT_SCOREBOARD_TABLE_SPM"]</div>
|
||||
<div>@ViewBag.Localization["WEBFRONT_ADV_STATS_ZSCORE"]</div>
|
||||
<div>@ViewBag.Localization["WEBFRONT_SCOREBOARD_TABLE_PING"]</div>
|
||||
</td>
|
||||
|
||||
<td class="@GetTeamBackgroundColorClass(client) flex-fill">
|
||||
<a asp-controller="Client" asp-action="Profile" asp-route-id="@client.ClientId" class="no-decoration text-light-dm text-dark-lm">
|
||||
<color-code value="@client.ClientName"></color-code>
|
||||
</a>
|
||||
<div>@client.Score</div>
|
||||
<div>@(client.Kills ?? 0)</div>
|
||||
<div>@(client.Deaths ?? 0)</div>
|
||||
<div>@Math.Round(client.Kdr ?? 0, 2)</div>
|
||||
<div>@Math.Round(client.ScorePerMinute ?? 0)</div>
|
||||
<div>@(client.ZScore is null or 0 ? "--" : Math.Round(client.ZScore.Value, 2).ToString(CultureInfo.CurrentCulture))</div>
|
||||
<div>@client.Ping</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</table>
|
||||
|
@ -18,65 +18,70 @@
|
||||
}
|
||||
}
|
||||
|
||||
<div class="row server-header pt-1 pb-1 bg-primary " id="server_header_@Model.ID">
|
||||
<div class="col-md-4 text-center text-md-left d-inline-flex justify-content-center justify-content-md-start">
|
||||
<color-code value="@Model.Name"></color-code>
|
||||
<a href="@Model.ConnectProtocolUrl" class="ml-2 mr-2 align-self-center d-none d-md-flex server-join-button" title="@Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_HOME_JOIN_DESC"]">
|
||||
<span class="oi oi-play-circle mr-1 align-self-center"></span>
|
||||
<span class="server-header-ip-address" style="display:none;">@Model.IPAddress</span>
|
||||
</a>
|
||||
@if (ViewBag.Authorized)
|
||||
{
|
||||
<span class="oi oi-chat align-self-center profile-action d-none d-md-flex mr-2" data-action="chat" data-action-id="@Model.ID"></span>
|
||||
}
|
||||
<a asp-controller="Server" asp-action="Scoreboard" asp-fragment="server_@Model.ID" title="@ViewBag.Localization["WEBFRONT_TITLE_SCOREBOARD"]"
|
||||
class="align-self-center d-none d-md-flex">
|
||||
<span class="oi oi-spreadsheet ml-1"></span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="text-center col-md-4 align-self-center">
|
||||
<span>@Model.Map</span>
|
||||
@if (!string.IsNullOrEmpty(Model.GameType) && Model.GameType.Length > 1)
|
||||
{
|
||||
<span>–</span>
|
||||
<span>@Model.GameType</span>
|
||||
}
|
||||
</div>
|
||||
<div class="text-center text-md-right col-md-4 d-flex align-self-center justify-content-center justify-content-md-end flex-column-reverse flex-sm-row">
|
||||
@if (Model.LobbyZScore != null)
|
||||
{
|
||||
<div title="@ViewBag.Localization["WEBFRONT_HOME_RATING_DESC"]" class="cursor-help d-flex flex-row-reverse flex-md-row justify-content-center">
|
||||
<span>@(Model.LobbyZScore ?? 0)</span>
|
||||
<span class="oi oi-bolt align-self-center mr-1 ml-1"></span>
|
||||
<div class="card mt-20 mb-20 ml-0 mr-0 p-0">
|
||||
<div class="p-5 pl-10 pr-10 bg-primary rounded-top d-flex flex-column flex-md-row flex-wrap justify-content-between text-light" id="server_header_@Model.ID">
|
||||
<div class="d-flex align-self-center flex-column-reverse flex-md-row">
|
||||
<div class="ml-5 mr-5 text-center">
|
||||
<color-code value="@Model.Name"></color-code>
|
||||
</div>
|
||||
<div class="d-flex justify-content-center">
|
||||
<!-- connect button -->
|
||||
<a href="@Model.ConnectProtocolUrl" class="text-light align-self-center" title="@Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_HOME_JOIN_DESC"]">
|
||||
<i class="oi oi-play-circle ml-5 mr-5"></i>
|
||||
<span class="server-header-ip-address" style="display:none;">@(Model.ExternalIPAddress):@(Model.Port)</span>
|
||||
</a>
|
||||
<has-permission entity="AdminMenu" required-permission="Update">
|
||||
<!-- send message button -->
|
||||
<a href="#actionModal" class="profile-action text-light align-self-center" data-action="chat" data-action-id="@Model.ID">
|
||||
<i class="oi oi-chat ml-5 mr-5"></i>
|
||||
</a>
|
||||
</has-permission>
|
||||
<!-- scoreboard button -->
|
||||
<a asp-controller="Server" asp-action="Scoreboard" asp-route-serverId="@Model.Endpoint" title="@ViewBag.Localization["WEBFRONT_TITLE_SCOREBOARD"]"
|
||||
class="text-light align-self-center">
|
||||
<i class="oi oi-spreadsheet ml-5 mr-5"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="align-self-center">
|
||||
<span>@Model.Map</span>
|
||||
@if (!string.IsNullOrEmpty(Model.GameType) && Model.GameType.Length > 1)
|
||||
{
|
||||
<span>–</span>
|
||||
<span>@Model.GameType</span>
|
||||
}
|
||||
</div>
|
||||
<div class="align-self-center d-flex flex-column flex-md-row">
|
||||
@if (Model.LobbyZScore != null)
|
||||
{
|
||||
<div data-toggle="tooltip" data-title="@ViewBag.Localization["WEBFRONT_HOME_RATING_DESC"]" class="cursor-help d-flex flex-row-reverse flex-md-row justify-content-center">
|
||||
<span>@(Model.LobbyZScore ?? 0)</span>
|
||||
<span class="oi oi-bolt align-self-center" style="margin-right: 2px; margin-left: 2px"></span>
|
||||
</div>
|
||||
}
|
||||
<div class="mr-5 ml-5 align-self-center">
|
||||
<span class="server-clientcount">@Model.ClientCount</span>/<span class="server-maxclients">@Model.MaxClients</span>
|
||||
</div>
|
||||
}
|
||||
<div>
|
||||
<span class="server-clientcount">@Model.ClientCount</span>/<span class="server-maxclients">@Model.MaxClients</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (ViewBag.Authorized)
|
||||
<div id="server_clientactivity_@Model.ID" class="bg-dark-dm bg-light-lm server-activity">
|
||||
<partial name="../Server/_ClientActivity" for="@Model"/>
|
||||
</div>
|
||||
|
||||
@if (Model.Players.Any())
|
||||
{
|
||||
<div class="p-1 d-flex d-md-none justify-content-center col-12">
|
||||
<span class="oi oi-chat align-self-center profile-action d-flex d-md-none" data-action="chat" data-action-id="@Model.ID"></span>
|
||||
<div class="ml-15 mr-15">
|
||||
<hr/>
|
||||
</div>
|
||||
}
|
||||
<a asp-controller="Server" asp-action="Scoreboard" title="@ViewBag.Localization["WEBFRONT_TITLE_SCOREBOARD"]"
|
||||
class="p-1 d-flex d-md-none justify-content-center col-12">
|
||||
<span class="oi oi-spreadsheet ml-1"></span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="server_clientactivity_@Model.ID" class="bg-dark row server-activity @(Model.ClientCount > 0 ? "pt-2 pb-2" : "")">
|
||||
@await Html.PartialAsync("../Server/_ClientActivity", Model)
|
||||
</div>
|
||||
|
||||
<div class="row server-history mb-4">
|
||||
<div class="server-history-row m-auto" style="position:relative; width: 100%" id="server_history_@Model.ID" data-serverid="@Model.ID"
|
||||
data-clienthistory='@Html.Raw(Json.Serialize(Model.ClientHistory))'
|
||||
data-clienthistory-ex='@Html.Raw(Json.Serialize(Model.ClientHistory.ClientCounts))'
|
||||
data-online="@Model.Online">
|
||||
<canvas id="server_history_canvas_@Model.ID" height="100"></canvas>
|
||||
<div class="server-history">
|
||||
<div class="server-history-row m-auto bg-dark-dm bg-light-lm rounded-bottom" style="position:relative; width: 100%" id="server_history_@Model.ID" data-serverid="@Model.ID"
|
||||
data-clienthistory='@Html.Raw(Json.Serialize(Model.ClientHistory))'
|
||||
data-clienthistory-ex='@Html.Raw(Json.Serialize(Model.ClientHistory.ClientCounts))'
|
||||
data-online="@Model.Online">
|
||||
<canvas id="server_history_canvas_@Model.ID" class="rounded-bottom" height="100"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -19,7 +19,7 @@
|
||||
{
|
||||
<tr>
|
||||
<td class="w-25">
|
||||
<a asp-controller="Client" asp-action="ProfileAsync" asp-route-id="@client.ClientId" class="link-inverse">@client.Name</a>
|
||||
<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
|
||||
@ -39,7 +39,7 @@
|
||||
{
|
||||
<div class="p-2 mb-3 border-bottom" style="background-color: #222;">
|
||||
<div class="d-flex flex-row">
|
||||
<a asp-controller="Client" asp-action="ProfileAsync" asp-route-id="@client.ClientId" class="h4 mr-auto">
|
||||
<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>
|
||||
|
@ -6,9 +6,9 @@
|
||||
|
||||
var lastHeaderEventDate = DateTime.UtcNow;
|
||||
|
||||
TimeSpan timeSpanForEvent(DateTime When)
|
||||
TimeSpan TimeSpanForEvent(DateTime occuredAt)
|
||||
{
|
||||
var timePassed = (DateTime.UtcNow - When);
|
||||
var timePassed = DateTime.UtcNow - occuredAt;
|
||||
var daysPassed = timePassed.TotalDays;
|
||||
var minutesPassed = timePassed.TotalMinutes;
|
||||
|
||||
@ -22,45 +22,43 @@
|
||||
return TimeSpan.FromHours(1);
|
||||
}
|
||||
|
||||
if (daysPassed > 1 && daysPassed <= 7)
|
||||
if (daysPassed is > 1 and <= 7)
|
||||
{
|
||||
return TimeSpan.FromDays(1);
|
||||
}
|
||||
|
||||
if (daysPassed > 7 && daysPassed <= 31)
|
||||
if (daysPassed is > 7 and <= 31)
|
||||
{
|
||||
return TimeSpan.FromDays(31);
|
||||
}
|
||||
|
||||
if (daysPassed > 31 && daysPassed <= 365)
|
||||
if (daysPassed is > 31 and <= 365)
|
||||
{
|
||||
return TimeSpan.FromDays(31);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
return TimeSpan.FromDays(365);
|
||||
}
|
||||
return TimeSpan.FromDays(365);
|
||||
}
|
||||
}
|
||||
|
||||
@if (Model.Count() == 0)
|
||||
@{ var start = 0; }
|
||||
@foreach (var meta in Model.OrderByDescending(meta => meta.When))
|
||||
{
|
||||
<div class="p2 text-muted profile-event-timestep">@ViewBag.Localization["WEBFRONT_CLIENT_META_NONE"]</div>
|
||||
}
|
||||
|
||||
@foreach (var meta in Model.OrderByDescending(_meta => _meta.When))
|
||||
{
|
||||
@if ((lastHeaderEventDate - meta.When) > timeSpanForEvent(lastHeaderEventDate))
|
||||
if (lastHeaderEventDate - meta.When > TimeSpanForEvent(lastHeaderEventDate) && (start > 0 || (DateTime.UtcNow - meta.When).TotalDays > 3))
|
||||
{
|
||||
<div class="p2 text-white profile-event-timestep">
|
||||
<span class="text-primary">—</span>
|
||||
<span>@meta.When.HumanizeForCurrentCulture()</span>
|
||||
</div>
|
||||
TempData["ShowMetaHeader"] = true;
|
||||
lastHeaderEventDate = meta.When;
|
||||
}
|
||||
else
|
||||
{
|
||||
TempData["ShowMetaHeader"] = false;
|
||||
}
|
||||
start++;
|
||||
|
||||
<div class="profile-meta-entry loader-data-time" data-time="@meta.When.ToFileTimeUtc()" title="@Utilities.FormatExt(ViewBag.Localization["WEBFRONT_PROFILE_META_DATE_OCCURRED"], meta.When.ToString())">
|
||||
<partial name="~/Views/Client/Profile/Meta/_@(meta.GetType().Name).cshtml" model="meta" />
|
||||
<div class="profile-meta-entry loader-data-time" data-time="@meta.When.ToFileTimeUtc()" onclick="$('#metaContextDateToggle@(start)').show()">
|
||||
<partial name="~/Views/Client/Profile/Meta/_@(meta.GetType().Name).cshtml" model="meta"/>
|
||||
<div style="display:none" id="metaContextDateToggle@(start)">
|
||||
Event occured at <span class="text-light">@meta.When.ToString()</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
@ -1,15 +1,16 @@
|
||||
@model Exception
|
||||
@using SharedLibraryCore
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "Error";
|
||||
}
|
||||
|
||||
<h4 class="text-danger">@SharedLibraryCore.Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_ERROR_GENERIC_TITLE"]</h4>
|
||||
<h4 class="text-danger">@SharedLibraryCore.Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_ERROR_GENERIC_DESC"]</h4>
|
||||
<strong class="text-warning">
|
||||
@if (Model != null)
|
||||
{
|
||||
@SharedLibraryCore.Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_ERROR_CODE"].FormatExt(Model.Message);
|
||||
}
|
||||
</strong>
|
||||
<div class="content">
|
||||
<h2 class="content-title text-danger">@Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_ERROR_GENERIC_TITLE"]</h2>
|
||||
<h2 class="content-title text-muted">@Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_ERROR_GENERIC_DESC"]</h2>
|
||||
<strong class="text-warning">
|
||||
@if (Model != null)
|
||||
{
|
||||
@Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_ERROR_CODE"].FormatExt(Model.Message)
|
||||
}
|
||||
</strong>
|
||||
</div>
|
||||
|
@ -2,12 +2,20 @@
|
||||
@{
|
||||
ViewData["Title"] = "Error";
|
||||
}
|
||||
|
||||
<h4 class="text-danger">@SharedLibraryCore.Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_ERROR_GENERIC_TITLE"]</h4>
|
||||
<h4 class="text-danger">@SharedLibraryCore.Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_ERROR_GENERIC_DESC"]</h4>
|
||||
<strong class="text-warning">
|
||||
@if (Model.HasValue && Model.Value == 404)
|
||||
{
|
||||
@SharedLibraryCore.Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_ERROR_NOTFOUND"]
|
||||
}
|
||||
</strong>
|
||||
<div class="content">
|
||||
<div class="card m-0">
|
||||
<div class="d-flex">
|
||||
<div class="align-self-center w-100 h-100 mr-20 ui-error-icon"></div>
|
||||
<div>
|
||||
<h2 class="content-title text-primary mb-0">@Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_ERROR_GENERIC_TITLE"]</h2>
|
||||
<h2 class="content-title">@Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_ERROR_GENERIC_DESC"]</h2>
|
||||
<div class="text-muted">
|
||||
@if (Model is 404)
|
||||
{
|
||||
@Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_ERROR_NOTFOUND"]
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
69
WebfrontCore/Views/Shared/_DataTable.cshtml
Normal file
69
WebfrontCore/Views/Shared/_DataTable.cshtml
Normal file
@ -0,0 +1,69 @@
|
||||
@model WebfrontCore.ViewModels.TableInfo
|
||||
@{
|
||||
Layout = null;
|
||||
}
|
||||
|
||||
<h4 class="content-title mb-15 mt-15">
|
||||
<color-code value="@Model.Header"></color-code>
|
||||
</h4>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr class="bg-primary text-light d-none d-lg-table-row">
|
||||
@foreach (var column in Model.Columns)
|
||||
{
|
||||
<th>@column.Title</th>
|
||||
}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@{ var start = 0;}
|
||||
@if (!Model.Rows.Any())
|
||||
{
|
||||
<!-- desktop -->
|
||||
<tr class="bg-dark-dm bg-light-lm d-none d-lg-table-row">
|
||||
<td colspan="@Model.Columns.Count">No data...</td>
|
||||
</tr>
|
||||
<!-- mobile -->
|
||||
<tr class="d-flex d-table-row d-lg-none">
|
||||
<td class="bg-primary text-light text-right w-125">
|
||||
—
|
||||
</td>
|
||||
<td class="bg-dark-dm bg-light-lm flex-fill w-200">No data...</td>
|
||||
</tr>
|
||||
}
|
||||
@foreach (var row in Model.Rows)
|
||||
{
|
||||
<!-- desktop -->
|
||||
<tr class="bg-dark-dm bg-light-lm @(Model.InitialRowCount > 0 && start >= Model.InitialRowCount ? "d-none hidden-row-lg": "d-none d-lg-table-row")">
|
||||
@for (var i = 0; i < Model.Columns.Count; i++)
|
||||
{
|
||||
<td>@row.Datum[i]</td>
|
||||
}
|
||||
</tr>
|
||||
|
||||
<!-- mobile -->
|
||||
<tr class="@(Model.InitialRowCount > 0 && start >= Model.InitialRowCount ? "d-none hidden-row": "d-flex d-table-row d-lg-none")">
|
||||
<td class="bg-primary text-light text-right w-125">
|
||||
@foreach (var column in Model.Columns)
|
||||
{
|
||||
<div class="mt-5 mb-5 text-truncate">@column.Title</div>
|
||||
}
|
||||
</td>
|
||||
<td class="bg-dark-dm bg-light-lm flex-fill w-200">
|
||||
@for (var i = 0; i < Model.Columns.Count; i++)
|
||||
{
|
||||
<div class="mt-5 mb-5 text-truncate" style="min-width:0">@row.Datum[i]</div>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
start++;
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
@if (Model.InitialRowCount > 0 && Model.Rows.Count > 0)
|
||||
{
|
||||
<button class="btn btn-block table-slide" data-toggle="tooltip" data-title="Show @(Model.Rows.Count - Model.InitialRowCount) more rows">
|
||||
<span class="oi oi-chevron-bottom"></span>
|
||||
</button>
|
||||
}
|
@ -1,10 +1,7 @@
|
||||
@{
|
||||
var loc = SharedLibraryCore.Utilities.CurrentLocalization.LocalizationIndex;
|
||||
}
|
||||
<!DOCTYPE html>
|
||||
<!DOCTYPE html >
|
||||
<html xmlns="http://www.w3.org/1999/html" lang="@ViewBag.Language">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<title>@ViewBag.Title | IW4MAdmin</title>
|
||||
<meta property="og:title" content="@ViewBag.Title | IW4MAdmin">
|
||||
@ -16,111 +13,109 @@
|
||||
<meta name="keywords" content="@ViewBag.Keywords">
|
||||
<link rel="icon" type="image/png" href="~/images/icon.png">
|
||||
|
||||
<link href='https://fonts.googleapis.com/css?family=Open+Sans' rel='stylesheet' type='text/css'>
|
||||
<environment include="Development">
|
||||
<link rel="stylesheet" href="~/dynamic/css/global.css?version=@ViewBag.Version" />
|
||||
<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>
|
||||
}
|
||||
</environment>
|
||||
<environment include="Production">
|
||||
<link rel="stylesheet" href="~/dynamic/css/global.min.css?version=@ViewBag.Version" />
|
||||
<link rel="stylesheet" href="~/dynamic/css/global.min.css?version=@ViewBag.Version"/>
|
||||
</environment>
|
||||
@await RenderSectionAsync("styles", false)
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
@Html.ActionLink((string)ViewBag.CustomBranding, "Index", "Home", new { area = "" }, new { @class = "navbar-brand" })
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarCollapse">
|
||||
<ul class="navbar-nav mr-auto">
|
||||
<li class="nav-item d-none d-lg-inline-block d-xl-none">@Html.ActionLink("", "Index", "Home", new {area = ""}, new {@class = "nav-link nav-icon oi oi-hard-drive", title=@loc["WEBFRONT_NAV_SERVERS"]})</li>
|
||||
<li class="nav-item text-center d-lg-none d-xl-inline-block">@Html.ActionLink(loc["WEBFRONT_NAV_SERVERS"], "Index", "Home", new {area = ""}, new {@class = "nav-link"})</li>
|
||||
|
||||
@if (ViewBag.Configuration.CommunityInformation.IsEnabled)
|
||||
{
|
||||
<li class="nav-item d-none d-lg-inline-block d-xl-none">@Html.ActionLink("", "Index", "About", new {area = ""}, new {@class = "nav-link nav-icon oi oi-list-rich", title = loc["WEBFRONT_NAV_ABOUT"]})</li>
|
||||
<li class="nav-item text-center text-lg-left d-lg-none d-xl-inline-block">@Html.ActionLink(loc["WEBFRONT_NAV_ABOUT"], "Index", "About", new {area = ""}, new {@class = "nav-link"})</li>
|
||||
}
|
||||
|
||||
<li class="nav-item d-none d-lg-inline-block d-xl-none">@Html.ActionLink("", "List", "Penalty", new {area = ""}, new {@class = "nav-link nav-icon oi oi-lock-locked", title=loc["WEBFRONT_NAV_PENALTIES"]})</li>
|
||||
<li class="nav-item text-center d-lg-none d-xl-inline-block">@Html.ActionLink(loc["WEBFRONT_NAV_PENALTIES"], "List", "Penalty", new {area = ""}, new {@class = "nav-link"})</li>
|
||||
|
||||
@if (ViewBag.Authorized)
|
||||
{
|
||||
<li class="nav-item d-none d-lg-inline-block d-xl-none">@Html.ActionLink("", "PrivilegedAsync", "Client", new {area = ""}, new {@class = "nav-link nav-icon oi oi-people", title=loc["WEBFRONT_NAV_PRIVILEGED"]})</li>
|
||||
<li class="nav-item text-center text-lg-left d-lg-none d-xl-inline-block">@Html.ActionLink(loc["WEBFRONT_NAV_PRIVILEGED"], "PrivilegedAsync", "Client", new {area = ""}, new {@class = "nav-link"})</li>
|
||||
}
|
||||
else if (!ViewBag.Authorized && !ViewBag.EnablePrivilegedUserPrivacy)
|
||||
{
|
||||
<li class="nav-item d-none d-lg-inline-block d-xl-none">@Html.ActionLink("", "PrivilegedAsync", "Client", new {area = ""}, new {@class = "nav-link nav-icon oi oi-people", title=loc["WEBFRONT_NAV_PRIVILEGED"]})</li>
|
||||
<li class="nav-item text-center text-lg-left d-lg-none d-xl-inline-block">@Html.ActionLink(loc["WEBFRONT_NAV_PRIVILEGED"], "PrivilegedAsync", "Client", new {area = ""}, new {@class = "nav-link"})</li>
|
||||
}
|
||||
|
||||
<li class="nav-item d-none d-lg-inline-block d-xl-none">@Html.ActionLink("", "Help", "Home", new {area = ""}, new {@class = "nav-link nav-icon oi oi-question-mark", title=loc["WEBFRONT_NAV_HELP"]})</li>
|
||||
<li class="nav-item text-center text-lg-left d-lg-none d-xl-inline-block">@Html.ActionLink(loc["WEBFRONT_NAV_HELP"], "Help", "Home", new {area = ""}, new {@class = "nav-link"})</li>
|
||||
|
||||
@foreach (var _page in ViewBag.Pages)
|
||||
{
|
||||
if (_page.Location == "/Stats/TopPlayersAsync")
|
||||
{
|
||||
<li class="nav-item d-none d-lg-inline-block d-xl-none">
|
||||
<a class="nav-link nav-icon oi oi-bar-chart" href="@_page.Location" title="@_page.Name"></a>
|
||||
</li>
|
||||
<li class="nav-item text-center text-lg-left d-lg-none d-xl-inline-block">
|
||||
<a class="nav-link" href="@_page.Location">@_page.Name</a>
|
||||
</li>
|
||||
}
|
||||
else
|
||||
{
|
||||
<li class="nav-item text-center text-lg-left">
|
||||
<a class="nav-link" href="@_page.Location">@_page.Name</a>
|
||||
</li>
|
||||
}
|
||||
}
|
||||
<li class="nav-item text-center text-lg-left"></li>
|
||||
@if (!string.IsNullOrEmpty(ViewBag.SocialLink))
|
||||
{
|
||||
<li class="nav-item text-center text-lg-left"><a href="@ViewBag.SocialLink" class="nav-link" target="_blank">@ViewBag.SocialTitle</a></li>
|
||||
}
|
||||
@if (ViewBag.Authorized)
|
||||
{
|
||||
<li class="nav-link dropdown text-center text-lg-left p-0">
|
||||
<a href="#" class="nav-link oi oi-person dropdown-toggle oi-fix-navbar w-100" id="account_dropdown" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></a>
|
||||
<body class="dark-mode with-custom-webkit-scrollbars with-custom-css-scrollbars" data-set-preferred-mode-onload="true">
|
||||
|
||||
<div class="dropdown-menu p-0" aria-labelledby="account_dropdown">
|
||||
<a asp-controller="Console" asp-action="Index" class="dropdown-item bg-dark text-muted text-center text-lg-left">@loc["WEBFRONT_NAV_CONSOLE"]</a>
|
||||
<a asp-controller="Client" asp-action="ProfileAsync" asp-route-id="@ViewBag.User.ClientId" class="dropdown-item bg-dark text-muted text-center text-lg-left">@loc["WEBFRONT_NAV_PROFILE"]</a>
|
||||
@if (ViewBag.User.Level >= SharedLibraryCore.Database.Models.EFClient.Permission.Owner)
|
||||
{
|
||||
<a asp-controller="Configuration" asp-action="Edit" class="dropdown-item bg-dark text-muted text-center text-lg-left">@loc["WEBFRONT_NAV_EDIT_CONFIGURATION"]</a>
|
||||
}
|
||||
<a asp-controller="Admin" asp-action="AuditLog" class="dropdown-item bg-dark text-muted text-center text-lg-left">@loc["WEBFRONT_NAV_AUDIT_LOG"]</a>
|
||||
<a class="dropdown-item bg-dark text-muted text-center text-lg-left profile-action" href="#" data-action="RecentClients" title="@loc["WEBFRONT_ACTION_RECENT_CLIENTS"]">@loc["WEBFRONT_ACTION_RECENT_CLIENTS"]</a>
|
||||
<a class="dropdown-item bg-dark text-muted text-center text-lg-left profile-action" href="#" data-action="GenerateLoginToken" title="@loc["WEBFRONT_ACTION_TOKEN"]">@loc["WEBFRONT_ACTION_TOKEN"]</a>
|
||||
<a asp-controller="Account" asp-action="LogoutAsync" class="dropdown-item bg-dark text-muted text-center text-lg-left">@loc["WEBFRONT_NAV_LOGOUT"]</a>
|
||||
</div>
|
||||
</li>
|
||||
}
|
||||
else
|
||||
{
|
||||
<li class="nav-item text-center text-md-left">
|
||||
<a href="#" id="profile_action_login_btn" class="nav-link profile-action oi oi-key oi-fix-navbar w-100" title="Login" data-action="login" aria-hidden="true"></a>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
<form class="form-inline text-primary pt-3 pb-3" method="get" action="/Client/FindAsync">
|
||||
<input id="client_search" name="clientName" class="form-control mr-lg-2 w-100" type="text" placeholder="@loc["WEBFRONT_NAV_SEARCH"]" />
|
||||
</form>
|
||||
<!-- Action Modal -->
|
||||
<div class="modal" id="actionModal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div id="modalLoadingBar" class="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">
|
||||
<a href="#" class="btn close" role="button" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</a>
|
||||
<div id="actionModalContent">
|
||||
<h4 class="mt-20">No content available yet...</h4>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- loading icon -->
|
||||
<div class="oi oi-loop-circular layout-loading-icon"></div>
|
||||
<div class="page-wrapper with-navbar with-sidebar" data-sidebar-type="overlayed-sm-and-down">
|
||||
<!-- toast notifications -->
|
||||
<div class="sticky-alerts"></div>
|
||||
<!-- top menu bar -->
|
||||
<nav class="navbar">
|
||||
<button id="toggle-sidebar-btn" class="btn btn-action" type="button" onclick="halfmoon.toggleSidebar()">
|
||||
<i class="oi oi-menu" aria-hidden="true"></i>
|
||||
</button>
|
||||
|
||||
<!-- branding -->
|
||||
<a asp-controller="Home" asp-action="Index" class="navbar-brand mr-20 mr-md-0">
|
||||
<div>@ViewBag.CustomBranding</div>
|
||||
</a>
|
||||
|
||||
<!-- client badges -->
|
||||
<div class="d-none d-md-block">
|
||||
<div class="badge-group ml-20" role="group" aria-label="...">
|
||||
<span class="badge badge-primary">@(ViewBag.ClientCount ?? "-")</span>
|
||||
<span class="badge bg-dark-dm bg-light-lm">Clients</span>
|
||||
</div>
|
||||
|
||||
<has-permission entity="PrivilegedClientsPage" required-permission="Read">
|
||||
<div class="badge-group ml-10" role="group" aria-label="...">
|
||||
<span class="badge badge-success">@(ViewBag.AdminCount ?? "-")</span>
|
||||
<span class="badge bg-dark-dm bg-light-lm">Admins</span>
|
||||
</div>
|
||||
</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>
|
||||
</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>
|
||||
|
||||
@*<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 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">
|
||||
@ -133,75 +128,46 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>-->
|
||||
<!-- End Main Modal -->
|
||||
<!-- Action Modal -->
|
||||
<div class="modal fade" id="actionModal" tabindex="-1" role="dialog" aria-labelledby="actionModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content bg-dark">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="actionModalLabel">IW4MAdmin</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 class="modal-message text-danger mb-3"></div>
|
||||
<div class="modal-body-content"></div>
|
||||
</div>
|
||||
<!--<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary">Action</button>
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
|
||||
</div>-->
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
@RenderBody()
|
||||
|
||||
<div class="content">
|
||||
<div class="badge text-muted">threadsafe.pw</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="target_id">
|
||||
@RenderSection("targetid", required: false)
|
||||
</div>
|
||||
<!-- End Action Modal -->
|
||||
|
||||
<div class="@(ViewBag.IsFluid ?? false ? "container-fluid" : "container") pt-4 pb-4 pl-3 pr-3 pr-lg-4 pl-lg-4">
|
||||
@RenderBody()
|
||||
<footer id="footer_text">
|
||||
<div class="d-lg-none d-block text-center pt-4 pb-4">
|
||||
<a href="https://github.com/RaidMax/IW4M-Admin/releases" target="_blank">
|
||||
@Program.Manager.Version
|
||||
</a>
|
||||
<br />
|
||||
<span class="text-muted">Developed by RaidMax</span>
|
||||
</div>
|
||||
<div class="footer-mobile d-lg-block d-none text-center">
|
||||
<a href="https://github.com/RaidMax/IW4M-Admin/releases" target="_blank">
|
||||
@Program.Manager.Version
|
||||
</a>
|
||||
<br />
|
||||
<span class="text-muted">Developed by RaidMax</span>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
<environment include="Development">
|
||||
<script type="text/javascript" src="~/lib/jquery/dist/jquery.js"></script>
|
||||
<script type="text/javascript" src="~/lib/popper.js/dist/umd/popper.js"></script>
|
||||
<script type="text/javascript" src="~/lib/moment.js/moment.js"></script>
|
||||
<script type="text/javascript" src="~/lib/moment-timezone/moment-timezone.js"></script>
|
||||
<script type="text/javascript" src="~/lib/bootstrap/dist/js/bootstrap.bundle.js"></script>
|
||||
<script type="text/javascript" src="~/lib/canvas.js/canvasjs.js"></script>
|
||||
<script type="text/javascript" src="~/lib/chart.js/dist/Chart.bundle.min.js"></script>
|
||||
<script type="text/javascript" src="~/js/action.js"></script>
|
||||
<script type="text/javascript" src="~/js/search.js"></script>
|
||||
</environment>
|
||||
<environment include="Production">
|
||||
<script type="text/javascript" src="~/js/global.min.js?version=@ViewBag.Version"></script>
|
||||
</environment>
|
||||
<script>
|
||||
</div>
|
||||
<environment include="Development">
|
||||
<script type="text/javascript" src="~/lib/jquery/dist/jquery.js"></script>
|
||||
<script type="text/javascript" src="~/lib/moment.js/moment.js"></script>
|
||||
<script type="text/javascript" src="~/lib/moment-timezone/moment-timezone.js"></script>
|
||||
<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/search.js"></script>
|
||||
</environment>
|
||||
<environment include="Production">
|
||||
<script type="text/javascript" src="~/js/global.min.js?version=@ViewBag.Version"></script>
|
||||
</environment>
|
||||
<script>
|
||||
let _localizationTmp = @Html.Raw(Json.Serialize(ViewBag.Localization));
|
||||
const _localization = [];
|
||||
$.each(_localizationTmp.set, function (key, value) {
|
||||
_localization[key] = value;
|
||||
});
|
||||
</script>
|
||||
@await RenderSectionAsync("scripts", required: false)
|
||||
@Html.Raw(ViewBag.ScriptInjection)
|
||||
@await RenderSectionAsync("scripts", required: false)
|
||||
@Html.Raw(ViewBag.ScriptInjection)
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
166
WebfrontCore/Views/Shared/_LeftNavBar.cshtml
Normal file
166
WebfrontCore/Views/Shared/_LeftNavBar.cshtml
Normal file
@ -0,0 +1,166 @@
|
||||
@using SharedLibraryCore.Configuration
|
||||
@using SharedLibraryCore.Dtos
|
||||
@using Data.Models.Client
|
||||
|
||||
<!-- left side navigation -->
|
||||
<div class="sidebar-overlay" onclick="halfmoon.toggleSidebar()"></div>
|
||||
<div class="sidebar">
|
||||
<div class="sidebar-menu list">
|
||||
<div class="sidebar-content m-0">
|
||||
<div class="pr-20 pl-20 mb-20 d-block d-lg-none">
|
||||
<partial name="_SearchResourceForm"/>
|
||||
</div>
|
||||
<span class="sidebar-title ">Main</span>
|
||||
<div class="sidebar-divider"></div>
|
||||
<!-- servers -->
|
||||
<a asp-controller="Home" asp-action="Index" class="sidebar-link">
|
||||
<i class="oi oi-hard-drive mr-5"></i>
|
||||
<span class="name">@ViewBag.Localization["WEBFRONT_NAV_SERVERS"]</span>
|
||||
</a>
|
||||
<!-- about -->
|
||||
<a asp-controller="About" asp-action="Index" class="sidebar-link">
|
||||
<i class="oi oi-info mr-5"></i>
|
||||
<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>
|
||||
<!-- privileged -->
|
||||
<has-permission entity="PrivilegedClientsPage" required-permission="Read">
|
||||
<a asp-controller="Client" asp-action="Privileged" class="sidebar-link">
|
||||
<i class="oi oi-people mr-5"></i>
|
||||
<span class="name">@ViewBag.Localization["WEBFRONT_NAV_PRIVILEGED"]</span>
|
||||
</a>
|
||||
</has-permission>
|
||||
<!-- help -->
|
||||
<has-permission entity="HelpPage" required-permission="Read">
|
||||
<a asp-controller="Home" asp-action="Help" class="sidebar-link">
|
||||
<i class="oi oi-question-mark mr-5"></i>
|
||||
<span class="name">@ViewBag.Localization["WEBFRONT_NAV_HELP"]</span>
|
||||
</a>
|
||||
</has-permission>
|
||||
<!-- profile -->
|
||||
<has-permission entity="ProfilePage" required-permission="Read">
|
||||
<a asp-controller="Client" asp-action="Profile" asp-route-id="@ViewBag.User.ClientId" class="sidebar-link">
|
||||
<i class="oi oi-person mr-5"></i>
|
||||
<span class="name">Profile</span>
|
||||
</a>
|
||||
</has-permission>
|
||||
|
||||
@if (!ViewBag.Authorized)
|
||||
{
|
||||
<a href="#actionModal" class="profile-action sidebar-link" data-action="login">
|
||||
<i class="oi oi-key mr-5"></i>
|
||||
<span class="name">Login</span>
|
||||
</a>
|
||||
}
|
||||
|
||||
<br/>
|
||||
<!-- stats -->
|
||||
<div class="sidebar-title ">Stats</div>
|
||||
<div class="sidebar-divider"></div>
|
||||
|
||||
@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>
|
||||
<span class="name">@pageLink.Name</span>
|
||||
</a>
|
||||
}
|
||||
<!-- scoreboard -->
|
||||
<a asp-controller="Server" asp-action="Scoreboard" class="sidebar-link">
|
||||
<i class="oi oi-spreadsheet mr-5"></i>
|
||||
<span class="name">Scoreboard</span>
|
||||
</a>
|
||||
<br/>
|
||||
|
||||
<!-- socials -->
|
||||
@if (ViewBag.CommunityInformation?.IsEnabled && ViewBag.CommunityInformation.SocialAccounts.Length > 0)
|
||||
{
|
||||
<span class="sidebar-title ">Socials</span>
|
||||
<div class="sidebar-divider"></div>
|
||||
}
|
||||
|
||||
@foreach (var social in ViewBag.CommunityInformation?.SocialAccounts ?? Array.Empty<SocialAccountConfiguration>())
|
||||
{
|
||||
<a href="@social.Url" class="sidebar-link" target="_blank" title="@social.Title">
|
||||
@if (!string.IsNullOrWhiteSpace(social.IconId))
|
||||
{
|
||||
<i class="oi @social.IconId mr-5"></i>
|
||||
}
|
||||
else if (!string.IsNullOrWhiteSpace(social.IconUrl))
|
||||
{
|
||||
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"/>
|
||||
}
|
||||
<span class="name">@social.Title</span>
|
||||
</a>
|
||||
}
|
||||
<br/>
|
||||
|
||||
<!-- admin -->
|
||||
<has-permission entity="AdminMenu" required-permission="Read">
|
||||
<div class="sidebar-title ">Admin</div>
|
||||
<div class="sidebar-divider"></div>
|
||||
|
||||
<has-permission entity="ConsolePage" required-permission="Read">
|
||||
<a asp-controller="Console" asp-action="Index" class="sidebar-link">
|
||||
<i class="oi oi-terminal mr-5"></i>
|
||||
<span class="name">@ViewBag.Localization["WEBFRONT_NAV_CONSOLE"]</span>
|
||||
</a>
|
||||
</has-permission>
|
||||
@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>
|
||||
<span class="name">Configuration</span>
|
||||
</a>
|
||||
}
|
||||
<has-permission entity="AuditPage" required-permission="Read">
|
||||
<a asp-controller="Admin" asp-action="AuditLog" class="sidebar-link">
|
||||
<i class="oi oi-book mr-5"></i>
|
||||
<span class="name">@ViewBag.Localization["WEBFRONT_NAV_AUDIT_LOG"]</span>
|
||||
</a>
|
||||
</has-permission>
|
||||
@*<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"]">
|
||||
<i class="oi oi-key mr-5"></i>
|
||||
<span class="name">@ViewBag.Localization["WEBFRONT_ACTION_TOKEN"]</span>
|
||||
</a>
|
||||
</has-permission>
|
||||
@if (ViewBag.Authorized)
|
||||
{
|
||||
<a asp-controller="Account" asp-action="Logout" class="sidebar-link">
|
||||
<i class="oi oi-account-logout mr-5"></i>
|
||||
<span class="name">@ViewBag.Localization["WEBFRONT_NAV_LOGOUT"]</span>
|
||||
</a>
|
||||
}
|
||||
|
||||
<br/>
|
||||
<!-- version -->
|
||||
<div class="sidebar-link font-size-12 font-weight-light">
|
||||
@if (ViewBag.Authorized)
|
||||
{
|
||||
<span>Logged in as <color-code value="@ViewBag.User.Name"></color-code></span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span>Not logged in</span>
|
||||
}
|
||||
</div>
|
||||
<div class="sidebar-divider mt-0 mb-0"></div>
|
||||
<a href="https://github.com/RaidMax/IW4M-Admin/releases" class="sidebar-link" target="_blank">
|
||||
<span class="name font-size-12 font-weight-light">IW4MAdmin <span class="text-primary">@Program.Manager.Version</span></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
19
WebfrontCore/Views/Shared/_LoginForm.cshtml
Normal file
19
WebfrontCore/Views/Shared/_LoginForm.cshtml
Normal file
@ -0,0 +1,19 @@
|
||||
@{
|
||||
Layout = null;
|
||||
}
|
||||
|
||||
<form class="action-form" asp-action="Login" asp-controller="Account">
|
||||
<div class="input-group mb-3">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text" id="basic-addon-clientId">Client ID</span>
|
||||
</div>
|
||||
<input type="text" name="clientId" value="" class="form-control" aria-label="clientId" aria-describedby="basic-addon-clientId">
|
||||
</div>
|
||||
<div class="input-group mb-3">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text" id="basic-addon-Password">Token/Password</span>
|
||||
</div>
|
||||
<input type="password" name="Password" value="" class="form-control" aria-label="Password" aria-describedby="basic-addon-Password">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-block btn-primary">Login</button>
|
||||
</form>
|
12
WebfrontCore/Views/Shared/_SearchResourceForm.cshtml
Normal file
12
WebfrontCore/Views/Shared/_SearchResourceForm.cshtml
Normal file
@ -0,0 +1,12 @@
|
||||
<form class="form-inline ml-auto" method="get" asp-controller="Client" asp-action="Find">
|
||||
<div class="input-group">
|
||||
<input id="client_search_mobile" name="clientName" class="form-control" type="text" placeholder="@ViewBag.Localization["WEBFRONT_NAV_SEARCH"]" required="required"/>
|
||||
<div class="input-group-append">
|
||||
<button class="btn" type="submit">
|
||||
<i class="oi oi-magnifying-glass"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
</form>
|
44
WebfrontCore/Views/Shared/_SideContextMenu.cshtml
Normal file
44
WebfrontCore/Views/Shared/_SideContextMenu.cshtml
Normal file
@ -0,0 +1,44 @@
|
||||
@model WebfrontCore.ViewModels.SideContextMenuItems
|
||||
@{ Layout = null; }
|
||||
|
||||
<div class="d-none d-lg-flex col-3 col-xl-2">
|
||||
<div class="content mt-0">
|
||||
<div class="on-this-page-nav pt-0">
|
||||
<div class="title">@Model.MenuTitle</div>
|
||||
|
||||
@foreach (var item in Model.Items)
|
||||
{
|
||||
<a href="@(item.IsLink ? item.Reference : "#actionModal")" class="@(item.IsLink ? "" : "profile-action")" data-action="@(item.IsLink ? "" : item.Reference)">
|
||||
<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>
|
||||
</div>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal" id="contextMenuModal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-content">
|
||||
<div class="content-title">@Model.MenuTitle</div>
|
||||
<hr/>
|
||||
@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)">
|
||||
<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>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
<hr/>
|
||||
<a href="#" class="btn btn-lg btn-danger btn-block mt-15" role="button">Close</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -34,7 +34,10 @@
|
||||
<ItemGroup>
|
||||
<None Include="bundleconfig.json" />
|
||||
<None Include="compilerconfig.json" />
|
||||
<None Include="wwwroot\css\src\global.css" />
|
||||
<None Remove="dotnet-bundle.exe" />
|
||||
<None Remove="dotnet-bundle.dll" />
|
||||
<None Remove="Newtonsoft.Json.dll" />
|
||||
<None Remove="NUglify.dll" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -45,6 +48,9 @@
|
||||
<None Include="wwwroot\css\global.min.css" CopyToPublishDirectory="PreserveNewest" />
|
||||
<None Include="wwwroot\js\global.min.js" CopyToPublishDirectory="PreserveNewest" />
|
||||
<None Include="wwwroot\images\**\*.*" CopyToPublishDirectory="PreserveNewest" />
|
||||
<Content Remove="wwwroot\css\src\main.css.map" />
|
||||
<Content Remove="dotnet-bundle.runtimeconfig.json" />
|
||||
<Content Remove="dotnet-bundle.deps.json" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -59,6 +65,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="ref" />
|
||||
<Folder Include="wwwroot\lib\canvas.js\" />
|
||||
</ItemGroup>
|
||||
|
||||
@ -73,10 +80,6 @@
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Remove="Views\Plugins\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ProjectExtensions>
|
||||
<VisualStudio>
|
||||
<UserProperties />
|
||||
|
@ -1,10 +1,8 @@
|
||||
// Configure bundling and minification for the project.
|
||||
// More info at https://go.microsoft.com/fwlink/?LinkId=808241
|
||||
[
|
||||
[
|
||||
{
|
||||
"outputFileName": "wwwroot/css/global.min.css",
|
||||
// An array of relative input file paths. Globbing patterns supported
|
||||
"inputFiles": [
|
||||
"wwwroot/lib/halfmoon/css/halfmoon-variables.min.css",
|
||||
"wwwroot/css/global.css",
|
||||
"wwwroot/lib/chart.js/dist/Chart.min.css"
|
||||
]
|
||||
@ -13,12 +11,10 @@
|
||||
"outputFileName": "wwwroot/js/global.min.js",
|
||||
"inputFiles": [
|
||||
"wwwroot/lib/jquery/dist/jquery.js",
|
||||
"wwwroot/lib/popper.js/dist/umd/popper.js",
|
||||
"wwwroot/lib/moment.js/moment.min.js",
|
||||
"wwwroot/lib/moment-timezone/moment-timezone.min.js",
|
||||
"wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.min.js",
|
||||
"wwwroot/lib/canvas.js/canvasjs.js",
|
||||
"wwwroot/lib/chart.js/dist/Chart.bundle.min.js",
|
||||
"wwwroot/lib/halfmoon/js/halfmoon.min.js",
|
||||
"wwwroot/js/action.js",
|
||||
"wwwroot/js/console.js",
|
||||
"wwwroot/js/penalty.js",
|
||||
@ -32,12 +28,10 @@
|
||||
"wwwroot/js/advanced_stats.js",
|
||||
"wwwroot/js/liveradar.js"
|
||||
],
|
||||
// Optionally specify minification options
|
||||
"minify": {
|
||||
"enabled": true,
|
||||
"renameLocals": true
|
||||
},
|
||||
// Optinally generate .map file
|
||||
"sourceMap": false
|
||||
}
|
||||
]
|
||||
|
@ -18,11 +18,6 @@
|
||||
"library": "open-iconic@1.1.1",
|
||||
"destination": "wwwroot/lib/open-iconic/"
|
||||
},
|
||||
{
|
||||
"provider": "jsdelivr",
|
||||
"library": "bootstrap@4.3.1",
|
||||
"destination": "wwwroot/lib/bootstrap/"
|
||||
},
|
||||
{
|
||||
"library": "moment@2.24.0",
|
||||
"destination": "wwwroot/lib/moment.js/"
|
||||
@ -30,6 +25,10 @@
|
||||
{
|
||||
"library": "chart.js@2.9.4",
|
||||
"destination": "wwwroot/lib/chart.js"
|
||||
},
|
||||
{
|
||||
"library": "halfmoon@1.1.1",
|
||||
"destination": "wwwroot/lib/halfmoon"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -1,28 +0,0 @@
|
||||
$white: #fff !default;
|
||||
$blue: #007ACC !default;
|
||||
$red: rgba(255, 69, 69, 0.85) !default;
|
||||
$green: rgba(116,147,99, 1) !default;
|
||||
$orange: #fd7e14 !default;
|
||||
$primary: $blue !default;
|
||||
$secondary: $orange !default;
|
||||
$light: rgb(204, 204, 204) !default;
|
||||
$dark: rgb(24, 24, 24) !default;
|
||||
$body-bg: rgb(34, 34, 34) !default;
|
||||
$big-dark: #191919;
|
||||
$body-color: $white !default;
|
||||
$link-color: $white !default;
|
||||
$link-decoration: none !default;
|
||||
$link-hover-color: $primary !default;
|
||||
$link-hover-decoration: none !default;
|
||||
$font-family-sans-serif: -apple-system, BlinkMacSystemFont, "Open Sans", "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol" !default;
|
||||
|
||||
$navbar-padding-y: 0 !default;
|
||||
$navbar-nav-link-padding-x: 2rem !default;
|
||||
$font-size-base: 1rem !default; // Assumes the browser default, typically `16px`
|
||||
$h5-font-size: $font-size-base * 1.25 !default;
|
||||
$h1-font-size: $font-size-base * 2.5 !default;
|
||||
$navbar-brand-font-size: $h1-font-size !default;
|
||||
$navbar-toggler-font-size: $h5-font-size !default;
|
||||
$navbar-dark-hover-color: $primary !default;
|
||||
|
||||
@import '../../lib/bootstrap/scss/bootstrap.scss';
|
@ -3,16 +3,28 @@
|
||||
$icon-font-path: '/font/' !default;
|
||||
@import '../../lib/open-iconic/font/css/open-iconic-bootstrap.scss';
|
||||
|
||||
.navbar-nav .nav-link:hover {
|
||||
background-color: $body-bg;
|
||||
}
|
||||
|
||||
nav.navbar-dark {
|
||||
border-bottom: 1px solid $secondary;
|
||||
}
|
||||
|
||||
a.nav-link {
|
||||
padding: $spacer * 1.5;
|
||||
:root {
|
||||
--blue-color: #007ACC;
|
||||
|
||||
--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;
|
||||
|
||||
--green-color: #8cc982;
|
||||
--lm-base-body-bg-color: rgb(245, 245, 245);
|
||||
--lm-card-bg-color: var(--gray-color-light);
|
||||
--gray-color-light: white;
|
||||
//--dm-border-color: var(--primary-color);
|
||||
//--lm-border-color: var(--primary-color);
|
||||
--card-border-width: 0;
|
||||
}
|
||||
|
||||
.server-history-row {
|
||||
@ -30,42 +42,11 @@ a.nav-link {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.server-history,
|
||||
.server-activity,
|
||||
#mobile_seperator,
|
||||
.border-bottom {
|
||||
border-bottom: 1px solid $primary !important;
|
||||
}
|
||||
|
||||
.border-bottom-dark {
|
||||
border-bottom: 1px solid $body-bg !important;
|
||||
}
|
||||
|
||||
.server-history {
|
||||
background-color: $big-dark;
|
||||
}
|
||||
|
||||
.border-top {
|
||||
border-top: 1px solid $primary !important;
|
||||
}
|
||||
|
||||
#client_search {
|
||||
background-color: #222222 !important;
|
||||
border-radius: 0;
|
||||
border: 1px solid $orange;
|
||||
color: $white;
|
||||
}
|
||||
|
||||
.server-join-button, .server-join-button:hover {
|
||||
color: $white !important;
|
||||
}
|
||||
|
||||
a.link-inverse {
|
||||
color: $primary;
|
||||
}
|
||||
|
||||
a.link-inverse:hover {
|
||||
color: $white;
|
||||
border-top: 1px solid var(--primary-color) !important;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
@ -90,7 +71,6 @@ a.link-inverse:hover {
|
||||
|
||||
#console .form-control, #console button {
|
||||
border-radius: 0;
|
||||
border-color: $primary;
|
||||
border-left: 0;
|
||||
border-right: 0;
|
||||
}
|
||||
@ -100,29 +80,16 @@ a.link-inverse:hover {
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
border-top-color: $orange;
|
||||
border-top-color: var(--secondary-color);
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
border-bottom-color: $orange;
|
||||
}
|
||||
|
||||
form *, select, button.btn {
|
||||
border-radius: 0 !important;
|
||||
border-bottom-color: var(--secondary-color);
|
||||
}
|
||||
|
||||
#penalty_filter_selection {
|
||||
border-left: none !important;
|
||||
border-right: none !important;
|
||||
border-bottom: none !important;
|
||||
border-top: 1px solid $text-muted;
|
||||
}
|
||||
|
||||
.oi-fix-navbar {
|
||||
line-height: 1.5 !important;
|
||||
top: 0 !important;
|
||||
font-size: 1rem !important;
|
||||
}
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
@-webkit-keyframes rotation {
|
||||
from {
|
||||
@ -140,11 +107,11 @@ form *, select, button.btn {
|
||||
top: 50% !important;
|
||||
margin-left: -37px;
|
||||
margin-top: -37px;
|
||||
color: $primary;
|
||||
color: var(--dm-base-text-color);
|
||||
z-index: 100;
|
||||
font-size: 4rem;
|
||||
-webkit-animation: rotation 1s infinite linear;
|
||||
background-color: rgba(0, 0,0, 0.5);
|
||||
background-color: var(--primary-color);
|
||||
border-radius: 40px;
|
||||
padding: 5px;
|
||||
visibility: hidden;
|
||||
@ -161,7 +128,7 @@ form *, select, button.btn {
|
||||
}
|
||||
|
||||
.input-text-danger {
|
||||
border-color: $red !important;
|
||||
border-color: var(--red-color) !important;
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
@ -169,11 +136,6 @@ form *, select, button.btn {
|
||||
-webkit-box-shadow: none;
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
border-bottom: 1px solid $secondary;
|
||||
margin-top: -3px;
|
||||
}
|
||||
|
||||
.striped > div:nth-child(even) {
|
||||
background-color: rgba(0, 0, 0, 0.125);
|
||||
}
|
||||
@ -211,43 +173,6 @@ form *, select, button.btn {
|
||||
right: 1em;
|
||||
}
|
||||
|
||||
.nav-tabs, .nav-tabs .nav-link.active {
|
||||
color: $white !important;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.nav-tabs .nav-link:hover {
|
||||
color: $blue;
|
||||
background-color: rgba(24, 24, 24, 0.75);
|
||||
border: none;
|
||||
}
|
||||
|
||||
.nav-tabs .nav-link {
|
||||
border-radius: 0;
|
||||
padding: 1.5rem;
|
||||
color: #6c757d;
|
||||
border: none;
|
||||
min-width: 20rem;
|
||||
background-color: #181818;
|
||||
}
|
||||
|
||||
.nav-tabs .nav-link.active {
|
||||
border: none;
|
||||
background-color: $blue;
|
||||
}
|
||||
|
||||
.nav-tabs .nav-link.active:hover {
|
||||
background-color: darken($primary, 10%);
|
||||
}
|
||||
|
||||
title {
|
||||
background-color: darken($primary, 10%);
|
||||
}
|
||||
|
||||
.nav-tabs .nav-item {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.mt-n1 {
|
||||
margin-top: -0.25rem !important;
|
||||
}
|
||||
@ -258,7 +183,7 @@ title {
|
||||
|
||||
/* Configuration */
|
||||
.configuration-form input[type='text'], .configuration-form input[type='number'], input.text-box {
|
||||
border: 1px solid $orange;
|
||||
border: 1px solid var(--secondary-color);
|
||||
}
|
||||
|
||||
.hide {
|
||||
@ -273,6 +198,7 @@ title {
|
||||
background-repeat: no-repeat
|
||||
}
|
||||
|
||||
/*
|
||||
.text-color-code-0 {
|
||||
color: $dark;
|
||||
}
|
||||
@ -286,7 +212,7 @@ title {
|
||||
}
|
||||
|
||||
.text-color-code-3 {
|
||||
color: $yellow;
|
||||
// color: $yellow;
|
||||
}
|
||||
|
||||
.text-color-code-4 {
|
||||
@ -294,11 +220,11 @@ title {
|
||||
}
|
||||
|
||||
.text-color-code-5 {
|
||||
color: $teal;
|
||||
// color: $teal;
|
||||
}
|
||||
|
||||
.text-color-code-5 {
|
||||
color: $purple;
|
||||
//color: $purple;
|
||||
}
|
||||
|
||||
.text-color-code-7 {
|
||||
@ -312,7 +238,7 @@ title {
|
||||
.text-color-code-58 {
|
||||
animation: color-change 54s infinite;
|
||||
}
|
||||
|
||||
*/
|
||||
@keyframes color-change {
|
||||
0% {
|
||||
color: #ff0000;
|
||||
@ -343,56 +269,6 @@ title {
|
||||
}
|
||||
}
|
||||
|
||||
.toggle-switch input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
margin:0;
|
||||
padding: 0;
|
||||
}
|
||||
label.toggle-switch {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.toggle-switch {
|
||||
position: relative;
|
||||
height: calc(1.5em + 0.75rem + 2px);
|
||||
width: 6rem;
|
||||
border: 1px solid $text-muted;
|
||||
}
|
||||
|
||||
.toggle-switch-slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: $dark;
|
||||
-webkit-transition: .4s;
|
||||
transition: .4s;
|
||||
}
|
||||
|
||||
.toggle-switch-slider:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: calc(1.5em + 0.75rem);
|
||||
width: 3em;
|
||||
background-color: $light;
|
||||
-webkit-transition: 0.25s;
|
||||
transition: 0.25s;
|
||||
}
|
||||
|
||||
input:checked + .toggle-switch-slider {
|
||||
background-color: $primary;
|
||||
}
|
||||
|
||||
input:checked + .toggle-switch-slider:before {
|
||||
-webkit-transform: translateX(3em);
|
||||
-ms-transform: translateX(3em);
|
||||
transform: translateX(3em);
|
||||
}
|
||||
|
||||
.action-kick-button {
|
||||
color: #492121;
|
||||
margin-top: 0.25rem;
|
||||
@ -411,37 +287,16 @@ input:checked + .toggle-switch-slider:before {
|
||||
|
||||
div.card {
|
||||
min-width: 15rem;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
max-width: 15rem;
|
||||
}
|
||||
|
||||
#rank_icon {
|
||||
max-height: 6rem;
|
||||
}
|
||||
|
||||
.border-bottom-danger {
|
||||
border-bottom: $danger;
|
||||
}
|
||||
|
||||
.border-top-danger {
|
||||
border-top: $danger;
|
||||
}
|
||||
|
||||
.border-danger {
|
||||
border: 1px solid $danger;
|
||||
}
|
||||
|
||||
#client_stats_summary a:hover {
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
#hitlocation_container {
|
||||
background-color: #141414;
|
||||
}
|
||||
|
||||
.cursor-help {
|
||||
cursor: help;
|
||||
}
|
||||
@ -467,3 +322,127 @@ div.card {
|
||||
.team-spectator-bg {
|
||||
background-color: rgba(200, 200, 0, 0.1);
|
||||
}
|
||||
|
||||
|
||||
#penalty_table tr.d-table-row, {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.server-header {
|
||||
--dm-navbar-link-text-color-hover: white;
|
||||
--lm-navbar-link-text-color-hover: white;
|
||||
}
|
||||
|
||||
#console_server_select {
|
||||
--input-border-width: 0;
|
||||
}
|
||||
|
||||
.sidebar-link > .oi {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
table {
|
||||
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;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
.dark-mode .on-this-page-nav {
|
||||
background-color: #25282c;
|
||||
}
|
||||
.on-this-page-nav .title {
|
||||
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);
|
||||
}
|
||||
.dark-mode .on-this-page-nav a {
|
||||
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;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.on-this-page-nav-container {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.no-decoration {
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
.h-125 {
|
||||
height: 12.5rem!important;
|
||||
}
|
||||
|
||||
.w-125 {
|
||||
width: 12.5rem!important;
|
||||
}
|
||||
|
||||
table.with-fixed-layout {
|
||||
table-layout:fixed;
|
||||
}
|
||||
|
||||
table.with-auto-width td {
|
||||
width: 1px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.bg-light-ex-lm {
|
||||
background-color: var(--lm-base-body-bg-color);
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
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%;
|
||||
}
|
||||
|
||||
@keyframes indeterminateAnimation {
|
||||
0% {
|
||||
transform: translateX(0) scaleX(0);
|
||||
}
|
||||
40% {
|
||||
transform: translateX(0) scaleX(0.4);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(100%) scaleX(0.5);
|
||||
}
|
||||
}
|
||||
|
@ -1,93 +1,89 @@
|
||||
@import 'bootstrap-custom.scss';
|
||||
|
||||
.level-bgcolor-console {
|
||||
.level-bgcolor-console {
|
||||
background-color: grey;
|
||||
}
|
||||
|
||||
.level-color-user, .level-color-guest, .level-color-0 {
|
||||
color: #6c757d;
|
||||
color: rgba(255, 255, 255, 0.68);
|
||||
}
|
||||
|
||||
.level-bgcolor-user, .level-bgcolor-guest, .level-bgcolor-0 {
|
||||
background-color: #6c757d;
|
||||
background-color: rgba(255, 255, 255, 0.68);
|
||||
background-color: #6c757d !important;
|
||||
background-color: rgba(255, 255, 255, 0.68) !important;
|
||||
}
|
||||
|
||||
.level-color-trusted, .level-color-2 {
|
||||
color: #749363;
|
||||
color: rgba(116,147,99,1);
|
||||
color: #749363 !important;
|
||||
color: rgba(116,147,99,1) !important;
|
||||
}
|
||||
|
||||
.level-bgcolor-trusted, .level-bgcolor-2 {
|
||||
background-color: #749363;
|
||||
background-color: rgba(116,147,99,1);
|
||||
background-color: #749363 !important;
|
||||
background-color: rgba(116,147,99,1) !important;
|
||||
}
|
||||
|
||||
.level-color-flagged, .level-color-1 {
|
||||
color: #fd9c38;
|
||||
color: rgba(253, 139, 22, 0.85);
|
||||
color: #fd9c38 !important;
|
||||
color: rgba(253, 139, 22, 0.85) !important;
|
||||
}
|
||||
|
||||
.level-bgcolor-flagged, .level-bgcolor-1 {
|
||||
background-color: #fd9c38;
|
||||
background-color: rgba(253, 139, 22, 0.85);
|
||||
background-color: #fd9c38 !important;
|
||||
background-color: rgba(253, 139, 22, 0.85) !important;
|
||||
}
|
||||
|
||||
.level-color-banned, .level-color-console, .level-color--1 {
|
||||
color: #ff6060;
|
||||
color: #ff6060 !important;
|
||||
color: rgba(255, 69, 69, 0.85);
|
||||
}
|
||||
|
||||
.level-bgcolor-banned, .level-bgcolor--1 {
|
||||
background-color: #ff6060;
|
||||
background-color: rgba(255, 69, 69, 0.85);
|
||||
background-color: #ff6060 !important ;
|
||||
background-color: rgba(255, 69, 69, 0.85) !important;
|
||||
}
|
||||
|
||||
.level-color-moderator, .level-color-3 {
|
||||
color: #f0de8b;
|
||||
color: rgba(235, 211, 101, 0.75);
|
||||
color: #f0de8b !important;
|
||||
color: rgba(235, 211, 101, 0.75) !important;
|
||||
}
|
||||
|
||||
.level-bgcolor-moderator, .level-bgcolor-3 {
|
||||
background-color: #f0de8b;
|
||||
background-color: rgba(235, 211, 101, 0.75);
|
||||
background-color: #f0de8b !important;
|
||||
background-color: rgba(235, 211, 101, 0.75) !important;
|
||||
}
|
||||
|
||||
.level-color-administrator, .level-color-4 {
|
||||
color: #f1a8e8;
|
||||
color: rgba(236, 130, 222, 0.69);
|
||||
color: #f1a8e8 !important;
|
||||
color: rgba(236, 130, 222, 0.69) !important;
|
||||
}
|
||||
|
||||
.level-bgcolor-administrator, .level-bgcolor-4 {
|
||||
background-color: #f1a8e8;
|
||||
background-color: rgba(236, 130, 222, 0.69);
|
||||
background-color: #f1a8e8 !important;
|
||||
background-color: rgba(236, 130, 222, 0.69) !important;
|
||||
}
|
||||
|
||||
.level-color-senioradmin, .level-color-5 {
|
||||
color: #50bcc3;
|
||||
color: rgba(50, 177, 185, 0.85);
|
||||
color: #50bcc3 !important;
|
||||
color: rgba(50, 177, 185, 0.85) !important;
|
||||
}
|
||||
|
||||
.level-bgcolor-senioradmin, .level-bgcolor-5 {
|
||||
background-color: #50bcc3;
|
||||
background-color: rgba(50, 177, 185, 0.85);
|
||||
background-color: #50bcc3 !important;
|
||||
background-color: rgba(50, 177, 185, 0.85) !important;
|
||||
}
|
||||
|
||||
.level-color-owner, .level-color-6 {
|
||||
color: rgb(0, 122, 204);
|
||||
color: rgb(0, 122, 204) !important;
|
||||
}
|
||||
|
||||
.level-bgcolor-owner, .level-bgcolor-6 {
|
||||
background-color: $primary;
|
||||
background-color: var(--primary-color) !important;
|
||||
}
|
||||
|
||||
.level-color-8 {
|
||||
color: #de4423;
|
||||
color: #de4423 !important;
|
||||
}
|
||||
|
||||
.level-bgcolor-8 {
|
||||
background-color: #de4423;
|
||||
background-color: #de4423 !important;
|
||||
}
|
||||
|
||||
.profile-meta-title {
|
||||
@ -137,7 +133,6 @@
|
||||
}
|
||||
|
||||
#profile_aliases_btn {
|
||||
color: $blue;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@ -145,10 +140,6 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.oi:hover {
|
||||
color: darken($primary, 10%) !important;
|
||||
}
|
||||
|
||||
#profile_aliases {
|
||||
position: relative;
|
||||
display: none;
|
||||
@ -197,15 +188,28 @@
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
#filter_meta_container .nav-link:hover {
|
||||
background-color: $dark;
|
||||
color: $white !important;
|
||||
}
|
||||
|
||||
.ip-lookup-profile {
|
||||
height: 2.5rem;
|
||||
min-width: 3.0rem;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.status-indicator {
|
||||
height: 1rem;
|
||||
width: 1rem;
|
||||
}
|
||||
|
||||
.with-ripple {
|
||||
animation: ripple 1.5s ease-out infinite;
|
||||
}
|
||||
|
||||
@keyframes ripple {
|
||||
from {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: scale(2);
|
||||
}
|
||||
}
|
||||
|
57
WebfrontCore/wwwroot/images/community/discord.svg
Normal file
57
WebfrontCore/wwwroot/images/community/discord.svg
Normal file
@ -0,0 +1,57 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.0"
|
||||
width="64"
|
||||
height="64"
|
||||
id="svg2"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="Location_dot_lightgrey.svg">
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1600"
|
||||
inkscape:window-height="837"
|
||||
id="namedview5"
|
||||
showgrid="false"
|
||||
inkscape:zoom="7.3822688"
|
||||
inkscape:cx="32"
|
||||
inkscape:cy="32"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg2" />
|
||||
<defs
|
||||
id="defs8" />
|
||||
<circle
|
||||
cx="32"
|
||||
cy="32"
|
||||
r="30"
|
||||
id="circle4"
|
||||
style="fill:#808080;stroke:none;stroke-width:0.1" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
19
WebfrontCore/wwwroot/images/ui/error-face.svg
Normal file
19
WebfrontCore/wwwroot/images/ui/error-face.svg
Normal file
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 17.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="32px" height="32px" viewBox="0 0 32 32" enable-background="new 0 0 32 32" xml:space="preserve">
|
||||
<g>
|
||||
<path fill="#828282" d="M16-0.034C7.159-0.034-0.035,7.158-0.035,16S7.159,32.034,16,32.034S32.035,24.842,32.035,16
|
||||
S24.841-0.034,16-0.034z M16,30.966C7.748,30.966,1.035,24.252,1.035,16S7.748,1.034,16,1.034S30.965,7.748,30.965,16
|
||||
S24.252,30.966,16,30.966z"/>
|
||||
<path fill="#828282" d="M16.163,19.823c-2.789,0-5.477,1.179-7.374,3.235c-0.2,0.217-0.187,0.555,0.031,0.755
|
||||
c0.216,0.2,0.554,0.188,0.755-0.03c1.695-1.838,4.096-2.892,6.587-2.892c2.529,0,4.956,1.081,6.656,2.967
|
||||
c0.105,0.116,0.251,0.176,0.397,0.176c0.128,0,0.256-0.045,0.358-0.138c0.219-0.197,0.237-0.535,0.039-0.755
|
||||
C21.709,21.032,18.994,19.823,16.163,19.823z"/>
|
||||
<path fill="#828282" d="M10.5,16.5c1.103,0,2-0.897,2-2s-0.897-2-2-2s-2,0.897-2,2S9.397,16.5,10.5,16.5z M10.5,13.5
|
||||
c0.551,0,1,0.448,1,1s-0.449,1-1,1s-1-0.448-1-1S9.949,13.5,10.5,13.5z"/>
|
||||
<path fill="#828282" d="M21.5,16.5c1.103,0,2-0.897,2-2s-0.897-2-2-2s-2,0.897-2,2S20.397,16.5,21.5,16.5z M21.5,13.5
|
||||
c0.551,0,1,0.448,1,1s-0.449,1-1,1s-1-0.448-1-1S20.949,13.5,21.5,13.5z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
@ -1,52 +1,101 @@
|
||||
function hideLoader() {
|
||||
$('.layout-loading-icon').fadeOut();
|
||||
$('#mainLoadingBar').fadeOut();
|
||||
$('#modalLoadingBar').fadeOut();
|
||||
}
|
||||
|
||||
function showLoader() {
|
||||
$('.layout-loading-icon').attr('style', 'visibility:visible');
|
||||
$('.layout-loading-icon').removeClass('text-danger');
|
||||
$('.layout-loading-icon').removeClass('text-muted');
|
||||
$('.layout-loading-icon').fadeIn();
|
||||
$('#mainLoadingBar').fadeIn();
|
||||
$('#modalLoadingBar').fadeIn();
|
||||
}
|
||||
|
||||
function errorLoader() {
|
||||
$('.layout-loading-icon').addClass('text-danger');
|
||||
$('#mainLoadingBar').addClass('bg-danger').delay(2000).fadeOut();
|
||||
}
|
||||
|
||||
function staleLoader() {
|
||||
$('.layout-loading-icon').addClass('text-muted');
|
||||
$('#mainLoadingBar').addClass('bg-grey');
|
||||
}
|
||||
|
||||
function getUrlParameter(sParam) {
|
||||
let sPageURL = window.location.search.substring(1),
|
||||
sURLVariables = sPageURL.split('&'),
|
||||
sParameterName,
|
||||
i;
|
||||
|
||||
for (i = 0; i < sURLVariables.length; i++) {
|
||||
sParameterName = sURLVariables[i].split('=');
|
||||
|
||||
if (sParameterName[0] === sParam) {
|
||||
return sParameterName[1] === undefined ? true : decodeURIComponent(sParameterName[1]);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function clearQueryString() {
|
||||
const uri = window.location.href.toString();
|
||||
if (uri.indexOf("?") > 0) {
|
||||
const cleanUri = uri.substring(0, uri.indexOf("?"));
|
||||
window.history.replaceState({}, document.title, cleanUri);
|
||||
}
|
||||
}
|
||||
|
||||
const entityMap = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": ''',
|
||||
'/': '/',
|
||||
'`': '`',
|
||||
'=': '='
|
||||
};
|
||||
|
||||
function escapeHtml (string) {
|
||||
return String(string).replace(/[&<>"'`=\/]/g, function (s) {
|
||||
return entityMap[s];
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
|
||||
let toastMessage = getUrlParameter('toastMessage');
|
||||
|
||||
if (toastMessage) {
|
||||
toastMessage = unescape(toastMessage);
|
||||
}
|
||||
|
||||
if (toastMessage) {
|
||||
clearQueryString();
|
||||
halfmoon.initStickyAlert({
|
||||
content: toastMessage,
|
||||
title: 'Success',
|
||||
alertType: 'alert-success',
|
||||
fillType: 'filled'
|
||||
});
|
||||
}
|
||||
|
||||
hideLoader();
|
||||
|
||||
/*
|
||||
* hide loader when clicking
|
||||
*/
|
||||
$(document).click(function (e) {
|
||||
//hideLoader()
|
||||
});
|
||||
|
||||
/*
|
||||
* handle action modal
|
||||
*/
|
||||
$(document).off('click', '.profile-action');
|
||||
$(document).on('click', '.profile-action', function (e) {
|
||||
$(document).on('click', '.profile-action', function () {
|
||||
const actionType = $(this).data('action');
|
||||
const actionId = $(this).data('action-id');
|
||||
const actionIdKey = actionId === undefined ? '' : '?id=' + actionId;
|
||||
$.get('/Action/' + actionType + 'Form' + actionIdKey)
|
||||
const actionIdKey = actionId === undefined ? '' : `?id=${actionId}`;
|
||||
showLoader();
|
||||
$.get(`/Action/${actionType}Form/${actionIdKey}`)
|
||||
.done(function (response) {
|
||||
$('#actionModal .modal-message').fadeOut('fast');
|
||||
$('#actionModal .modal-body-content').html(response);
|
||||
$('#actionModal').modal();
|
||||
$('#actionModal').trigger('action_form_received', actionType);
|
||||
$('#actionModalContent').html(response);
|
||||
hideLoader();
|
||||
})
|
||||
.fail(function (jqxhr, textStatus, error) {
|
||||
$('#actionModal .modal-body-content').html('');
|
||||
$('#actionModal .modal-message').text(_localization['GLOBAL_ERROR'] + ' — ' + jqxhr.responseText);
|
||||
$('#actionModal').modal();
|
||||
$('#actionModal .modal-message').fadeIn('fast');
|
||||
halfmoon.initStickyAlert({
|
||||
content: jqxhr.responseText,
|
||||
title: 'Error',
|
||||
alertType: 'alert-danger',
|
||||
fillType: 'filled'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -56,35 +105,58 @@ $(document).ready(function () {
|
||||
$(document).on('submit', '.action-form', function (e) {
|
||||
e.preventDefault();
|
||||
$(this).append($('#target_id input'));
|
||||
$('#actionModal').data('should-refresh', $('#actionModal').find('.refreshable').length !== 0);
|
||||
const modal = $('#actionModal');
|
||||
const shouldRefresh = modal.data('should-refresh', modal.find('.refreshable').length !== 0);
|
||||
const data = $(this).serialize();
|
||||
showLoader();
|
||||
|
||||
$.get($(this).attr('action') + '/?' + data)
|
||||
.done(function (response) {
|
||||
hideLoader();
|
||||
// success without content
|
||||
if (response.length === 0) {
|
||||
location.reload();
|
||||
}
|
||||
else {
|
||||
$('#actionModal .modal-message').fadeOut('fast');
|
||||
$('#actionModal .modal-body-content').html(response);
|
||||
$('#actionModal').modal();
|
||||
} else {
|
||||
let message = response;
|
||||
try {
|
||||
message = response.map(r => escapeHtml(r.response));
|
||||
}
|
||||
catch{}
|
||||
if (shouldRefresh) {
|
||||
window.location = `${window.location.href.replace('#actionModal', '')}?toastMessage=${escape(message)}`;
|
||||
}
|
||||
else {
|
||||
modal.modal();
|
||||
halfmoon.initStickyAlert({
|
||||
content: escapeHtml(message),
|
||||
title: 'Executed',
|
||||
alertType: 'alert-primary',
|
||||
fillType: 'filled'
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
.fail(function (jqxhr, textStatus, error) {
|
||||
errorLoader();
|
||||
.fail(function (jqxhr) {
|
||||
hideLoader();
|
||||
if ($('#actionModal .modal-message').text.length > 0) {
|
||||
$('#actionModal .modal-message').fadeOut('fast');
|
||||
|
||||
let message = jqxhr.responseText;
|
||||
|
||||
try {
|
||||
const jsonMessage = $.parseJSON(message);
|
||||
|
||||
if (jsonMessage) {
|
||||
message = jsonMessage.map(r => escapeHtml(r.response));
|
||||
}
|
||||
}
|
||||
if (jqxhr.status === 401) {
|
||||
$('#actionModal .modal-message').text(_localization['WEBFRONT_ACTION_CREDENTIALS']);
|
||||
}
|
||||
else {
|
||||
$('#actionModal .modal-message').text(_localization['GLOBAL_ERROR'] + ' — ' + jqxhr.responseText);
|
||||
}
|
||||
$('#actionModal .modal-message').fadeIn('fast');
|
||||
|
||||
catch{}
|
||||
|
||||
halfmoon.initStickyAlert({
|
||||
content: message.join("<br/>"),
|
||||
title: 'Error',
|
||||
alertType: 'alert-danger',
|
||||
fillType: 'filled'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -96,8 +168,8 @@ $(document).ready(function () {
|
||||
if (actionType === 'RecentClients') {
|
||||
const ipAddresses = $('.client-location-flag');
|
||||
$.each(ipAddresses, function (index, address) {
|
||||
$.get('https://ip2c.org/' + $(address).data('ip'), function (result) {
|
||||
const countryCode = result.split(';')[1].toLowerCase();
|
||||
$.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')`);
|
||||
}
|
||||
@ -105,15 +177,4 @@ $(document).ready(function () {
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
* handle close event to refresh if need be
|
||||
*/
|
||||
$("#actionModal").on("hidden.bs.modal", function () {
|
||||
let shouldRefresh = $(this).data('should-refresh');
|
||||
|
||||
if (shouldRefresh !== undefined && shouldRefresh) {
|
||||
location.reload();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -4,7 +4,15 @@
|
||||
|
||||
$(document).ready(function () {
|
||||
$('.table-slide').click(function () {
|
||||
$(this).siblings().children().children('.hidden-row').slideToggle(0);
|
||||
if ($(window).width() < 993) {
|
||||
$(this).prev().find('.hidden-row').toggleClass('d-none d-flex');
|
||||
} else {
|
||||
$(this).prev().find('.hidden-row-lg').toggleClass('d-none');
|
||||
}
|
||||
|
||||
$(this).attr('data-title', '');
|
||||
$(this).attr('data-toggle', '');
|
||||
|
||||
$(this).children('span').toggleClass('oi-chevron-top oi-chevron-bottom');
|
||||
});
|
||||
setupPerformanceGraph();
|
||||
@ -364,7 +372,7 @@ function renderPerformanceChart() {
|
||||
elements: {
|
||||
line: {
|
||||
fill: false,
|
||||
borderColor: 'rgba(255, 255, 255, 0.75)',
|
||||
borderColor: halfmoon.getPreferredMode() === "light-mode" ? 'rgba(0, 0, 0, 0.85)' : 'rgba(255, 255, 255, 0.75)',
|
||||
borderWidth: 2
|
||||
},
|
||||
point: {
|
||||
@ -407,4 +415,4 @@ function renderPerformanceChart() {
|
||||
data: chartData,
|
||||
options: options
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -7,16 +7,30 @@
|
||||
}
|
||||
|
||||
showLoader();
|
||||
$.get('/Console/ExecuteAsync', { serverId: serverId, command: command })
|
||||
$.get('/Console/Execute', { serverId: serverId, command: command })
|
||||
.done(function (response) {
|
||||
$('#console_command_response pre').html('');
|
||||
|
||||
hideLoader();
|
||||
$('#console_command_response').append(response);
|
||||
response.map(r => r.response).forEach(item => {
|
||||
$('#console_command_response').append(`<div>${item}</div>`);
|
||||
})
|
||||
|
||||
$('#console_command_response').append('<hr/>')
|
||||
$('#console_command_value').val("");
|
||||
})
|
||||
.fail(function (jqxhr, textStatus, error) {
|
||||
.fail(function (response) {
|
||||
$('#console_command_response pre').html('');
|
||||
errorLoader();
|
||||
hideLoader();
|
||||
$('#console_command_response').text(_localization['WEBFRONT_CONSOLE_ERROR'] + error).addClass('text-danger');
|
||||
|
||||
if (response.status < 500) {
|
||||
response.responseJSON.map(r => r.response).forEach(item => {
|
||||
$('#console_command_response').append(`<div class="text-danger">${item}</div>`);
|
||||
})
|
||||
} else {
|
||||
$('#console_command_response').append(`<div class="text-danger">Could not execute command...</div>`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -36,4 +50,4 @@ $(document).ready(function () {
|
||||
executeCommand();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,6 +1,7 @@
|
||||
const textOffset = 15;
|
||||
let previousRadarData = undefined;
|
||||
let newRadarData = undefined;
|
||||
let stateInfo;
|
||||
|
||||
/************************
|
||||
* IW4 *
|
||||
@ -219,42 +220,47 @@ function updatePlayerData() {
|
||||
}
|
||||
|
||||
let column = player.team === 'allies' ? $('.player-data-left') : $('.player-data-right');
|
||||
column.append(`<div class="progress" style="height: 1.5rem; background-color: transparent;">
|
||||
<div style="position: absolute; font-size: 1rem; left: 1.5rem;">${player.name}</div>
|
||||
<div class="progress-bar bg-success" role="progressbar" style="min-width: 0px; width: ${player.health}%" aria-valuenow="${player.health}" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
<div class="progress-bar bg-danger" role="progressbar" style="min-width: 0px; border-right: 0px; width: ${100 - player.health}%" aria-valuenow="${100 - player.health}" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
</div>
|
||||
<div class="d-flex flex-row flex-wrap p-2 mb-4 bg-dark border-bottom">
|
||||
<div style="width: 3rem; height: 1.5rem; background-image:url(${weaponImageForWeapon(player.weapon)}); background-size: 3rem 1.5rem;" class="mr-auto text-left">
|
||||
</div>
|
||||
<div class="player-stat-icon" style="background-image:url('/images/radar/kills.png')"></div>
|
||||
<div class="pr-2">${player.kills}</div>
|
||||
<div class="player-stat-icon" style="background-image:url('/images/radar/death.png')"></div>
|
||||
<div class="pr-3">${player.deaths}</div>
|
||||
<span class="align-self-center oi oi-target pr-1"></span>
|
||||
<div class="pr-3 ">${player.deaths == 0 ? player.kills.toFixed(2) : (player.kills / player.deaths).toFixed(2)}</div>
|
||||
<span class="align-self-center oi oi-graph pr-1"></span>
|
||||
<div>${ player.playTime == 0 ? '—' : Math.round(player.score / (player.playTime / 60))}</div>
|
||||
</div>`);
|
||||
|
||||
let greenProgressClass = 'rounded-top';
|
||||
let redProgressClass = 'rounded-right';
|
||||
|
||||
if (player.health < 100) {
|
||||
greenProgressClass = 'rounded-left';
|
||||
}
|
||||
if (player.health <= 0) {
|
||||
redProgressClass = 'rounded-top';
|
||||
}
|
||||
|
||||
column.append(`
|
||||
<div class="card m-0 p-0 mb-15">
|
||||
<div class="progress h-25">
|
||||
<div class="position-absolute ml-10 text-dark" style="top: 1.2rem;">${player.name}</div>
|
||||
<div class="progress-bar bg-success ${greenProgressClass} h-25" role="progressbar" style="min-width: 0px; width: ${player.health}%" aria-valuenow="${player.health}" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
<div class="progress-bar bg-danger ${redProgressClass} h-25" role="progressbar" style="min-width: 0px; border-right: 0px; width: ${100 - player.health}%" aria-valuenow="${100 - player.health}" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
</div>
|
||||
<div class="ml-10 mr-10 pt-5 pb-5">
|
||||
<div class="d-flex flex-row bg-dark-dm bg-light-lm rounded-bottom">
|
||||
<div style="width: 3rem; height: 1.5rem; background-image:url(${weaponImageForWeapon(player.weapon)}); background-size: 3rem 1.5rem;" class="mr-auto text-left align-self-center" data-toggle="tooltip" data-title="${player.weapon}">
|
||||
</div>
|
||||
<div class="d-flex">
|
||||
<div class="player-stat-icon align-self-center" style="background-image:url('/images/radar/kills.png')"></div>
|
||||
<div class="pr-5 align-self-center">${player.kills}</div>
|
||||
<div class="player-stat-icon align-self-center" style="background-image:url('/images/radar/death.png')"></div>
|
||||
<div class="pr-10 align-self-center">${player.deaths}</div>
|
||||
<span class="align-self-center oi oi-target pr-5"></span>
|
||||
<div class="pr-10 align-self-center">${player.deaths == 0 ? player.kills.toFixed(2) : (player.kills / player.deaths).toFixed(2)}</div>
|
||||
<span class="align-self-center oi oi-graph pr-5"></span>
|
||||
<div>${ player.playTime == 0 ? '—' : Math.round(player.score / (player.playTime / 60))}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`);
|
||||
});
|
||||
|
||||
$('.player-data-left').delay(1000).animate({opacity: 1}, 500);
|
||||
$('.player-data-right').delay(1000).animate({opacity: 1}, 500);
|
||||
}
|
||||
|
||||
const stateInfo = {
|
||||
canvas: $('#map_canvas'),
|
||||
ctx: $('#map_canvas')[0].getContext('2d'),
|
||||
updateFrequency: 750,
|
||||
updateFrameTimeDeviation: 0,
|
||||
forwardDistance: undefined,
|
||||
fovWidth: undefined,
|
||||
mapInfo: undefined,
|
||||
mapScaler: undefined,
|
||||
deathIcons: {},
|
||||
deathIconTime: 4000
|
||||
};
|
||||
|
||||
function updateRadarData() {
|
||||
$.getJSON(radarDataUrl, function (_radarItem) {
|
||||
newRadarData = _radarItem;
|
||||
@ -414,6 +420,20 @@ $(document).ready(function () {
|
||||
if ($('#map_canvas').length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
stateInfo = {
|
||||
canvas: $('#map_canvas'),
|
||||
ctx: $('#map_canvas')[0].getContext('2d'),
|
||||
updateFrequency: 750,
|
||||
updateFrameTimeDeviation: 0,
|
||||
forwardDistance: undefined,
|
||||
fovWidth: undefined,
|
||||
mapInfo: undefined,
|
||||
mapScaler: undefined,
|
||||
deathIcons: {},
|
||||
deathIconTime: 4000
|
||||
};
|
||||
|
||||
$.getJSON(radarDataUrl, function (_map) {
|
||||
stateInfo.mapInfo = _map;
|
||||
updateRadarData();
|
||||
|
@ -13,7 +13,26 @@ function initLoader(location, loaderId, count = 10, start = count, additional =
|
||||
loadCount = count;
|
||||
loaderOffset = start;
|
||||
additionalParams = additional;
|
||||
setupListeners();
|
||||
|
||||
setupMonitor();
|
||||
|
||||
$('#loaderLoad').click(function () {
|
||||
loadMoreItems();
|
||||
});
|
||||
}
|
||||
|
||||
function setupMonitor() {
|
||||
const element = document.querySelector('#loaderLoad')
|
||||
const observer = new window.IntersectionObserver(([entry]) => {
|
||||
if (entry.isIntersecting && $('.content-wrapper').scrollTop() > 10) {
|
||||
loadMoreItems();
|
||||
}
|
||||
}, {
|
||||
root: null,
|
||||
threshold: 1,
|
||||
})
|
||||
|
||||
observer.observe(element);
|
||||
}
|
||||
|
||||
function loadMoreItems() {
|
||||
@ -23,10 +42,10 @@ function loadMoreItems() {
|
||||
|
||||
showLoader();
|
||||
isLoaderLoading = true;
|
||||
let params = { offset: loaderOffset, count: loadCount, startAt: startAt };
|
||||
for (i = 0; i < additionalParams.length; i++) {
|
||||
let params = {offset: loaderOffset, count: loadCount, startAt: startAt};
|
||||
for (let i = 0; i < additionalParams.length; i++) {
|
||||
let param = additionalParams[i];
|
||||
params[param.name] = param.value;
|
||||
params[param.name] = param.value instanceof Function ? param.value() : param.value;
|
||||
}
|
||||
|
||||
$.get(loadUri, params)
|
||||
@ -35,69 +54,22 @@ function loadMoreItems() {
|
||||
if (response.trim().length === 0) {
|
||||
staleLoader();
|
||||
loaderReachedEnd = true;
|
||||
$('.loader-load-more').addClass('disabled');
|
||||
$('.loader-load-more').remove('text-primary').addClass('text-muted');
|
||||
}
|
||||
$(document).trigger("loaderFinished", response);
|
||||
startAt = $(response).filter('.loader-data-time').last().data('time');
|
||||
hideLoader();
|
||||
isLoaderLoading = false;
|
||||
})
|
||||
.fail(function (jqxhr, statis, error) {
|
||||
.fail(function () {
|
||||
errorLoader();
|
||||
halfmoon.initStickyAlert({
|
||||
content: 'Could not load more items...',
|
||||
title: 'Error',
|
||||
alertType: 'alert-danger',
|
||||
fillType: 'filled'
|
||||
});
|
||||
isLoaderLoading = false;
|
||||
});
|
||||
loaderOffset += loadCount;
|
||||
}
|
||||
|
||||
var hasScrollBar = false;
|
||||
|
||||
function _ScrollHandler(e) {
|
||||
//throttle event:
|
||||
/*
|
||||
https://stackoverflow.com/questions/3898130/check-if-a-user-has-scrolled-to-the-bottom
|
||||
*/
|
||||
var $window = $(window);
|
||||
var $document = $(document);
|
||||
hasScrollBar = true;
|
||||
let _throttleTimer = null;
|
||||
let _throttleDelay = 100;
|
||||
|
||||
clearTimeout(_throttleTimer);
|
||||
_throttleTimer = setTimeout(function () {
|
||||
|
||||
//do work
|
||||
if ($window.scrollTop() + $window.height() > $document.height() - 100) {
|
||||
loadMoreItems();
|
||||
}
|
||||
|
||||
}, _throttleDelay);
|
||||
}
|
||||
|
||||
function setupListeners() {
|
||||
if ($(loaderResponseId).length === 1) {
|
||||
/*
|
||||
https://stackoverflow.com/questions/19731730/jquery-js-detect-users-scroll-attempt-without-any-window-overflow-to-scroll
|
||||
*/
|
||||
|
||||
$('html').bind('mousewheel DOMMouseScroll', function (e) {
|
||||
var delta = e.originalEvent.wheelDelta || -e.originalEvent.detail;
|
||||
|
||||
if (delta < 0 && !hasScrollBar) {
|
||||
loadMoreItems();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
$(document).ready(function () {
|
||||
$(window)
|
||||
.off('scroll', _ScrollHandler)
|
||||
.on('scroll', _ScrollHandler);
|
||||
$('.loader-load-more:not(.disabled)').click(function (e) {
|
||||
if (!isLoaderLoading) {
|
||||
loadMoreItems();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user