add client note command and feature

This commit is contained in:
RaidMax 2022-07-20 10:32:26 -05:00
parent fa1567d3f5
commit 51e8b31e42
9 changed files with 183 additions and 9 deletions

View File

@ -0,0 +1,52 @@
using System;
using System.Threading.Tasks;
using Data.Models.Client;
using IW4MAdmin.Application.Meta;
using SharedLibraryCore;
using SharedLibraryCore.Commands;
using SharedLibraryCore.Configuration;
using SharedLibraryCore.Dtos.Meta.Responses;
using SharedLibraryCore.Interfaces;
namespace IW4MAdmin.Application.Commands;
public class AddClientNoteCommand : Command
{
private readonly IMetaServiceV2 _metaService;
public AddClientNoteCommand(CommandConfiguration config, ITranslationLookup layout, IMetaServiceV2 metaService) : base(config, layout)
{
Name = "addnote";
Description = _translationLookup["COMMANDS_ADD_CLIENT_NOTE_DESCRIPTION"];
Alias = "an";
Permission = EFClient.Permission.Moderator;
RequiresTarget = true;
Arguments = new[]
{
new CommandArgument
{
Name = _translationLookup["COMMANDS_ARGS_PLAYER"],
Required = true
},
new CommandArgument
{
Name = _translationLookup["COMMANDS_ARGS_NOTE"],
Required = true
}
};
_metaService = metaService;
}
public override async Task ExecuteAsync(GameEvent gameEvent)
{
var note = new ClientNoteMetaResponse
{
Note = gameEvent.Data?.Trim(),
OriginEntityId = gameEvent.Origin.ClientId,
ModifiedDate = DateTime.UtcNow
};
await _metaService.SetPersistentMetaValue("ClientNotes", note, gameEvent.Target.ClientId);
gameEvent.Origin.Tell(_translationLookup["COMMANDS_ADD_CLIENT_NOTE_SUCCESS"]);
}
}

View File

@ -0,0 +1,13 @@
using System;
using System.Text.Json.Serialization;
namespace SharedLibraryCore.Dtos.Meta.Responses;
public class ClientNoteMetaResponse
{
public string Note { get; set; }
public int OriginEntityId { get; set; }
[JsonIgnore]
public string OriginEntityName { get; set; }
public DateTime ModifiedDate { get; set; }
}

View File

@ -33,5 +33,6 @@ namespace SharedLibraryCore.Dtos
public string ConnectProtocolUrl { get;set; }
public string CurrentServerName { get; set; }
public IGeoLocationResult GeoLocationInfo { get; set; }
public ClientNoteMetaResponse NoteMeta { get; set; }
}
}

View File

@ -938,6 +938,14 @@ namespace SharedLibraryCore.Services
return clientList;
}
public async Task<string> GetClientNameById(int clientId)
{
await using var context = _contextFactory.CreateContext();
var match = await context.Clients.Select(client => new { client.CurrentAlias.Name, client.ClientId })
.FirstOrDefaultAsync(client => client.ClientId == clientId);
return match?.Name;
}
#endregion
}
}

View File

@ -7,11 +7,13 @@ using System.Threading.Tasks;
using Data.Models;
using Data.Models.Client;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using SharedLibraryCore;
using SharedLibraryCore.Commands;
using SharedLibraryCore.Configuration;
using SharedLibraryCore.Dtos;
using SharedLibraryCore.Dtos.Meta.Responses;
using SharedLibraryCore.Helpers;
using SharedLibraryCore.Interfaces;
using WebfrontCore.ViewModels;
@ -32,6 +34,7 @@ namespace WebfrontCore.Controllers
private readonly string _unflagCommandName;
private readonly string _setLevelCommandName;
private readonly string _setClientTagCommandName;
private readonly string _addClientNoteCommandName;
public ActionController(IManager manager, IEnumerable<IManagerCommand> registeredCommands,
ApplicationConfiguration appConfig, IMetaServiceV2 metaService) : base(manager)
@ -76,6 +79,9 @@ namespace WebfrontCore.Controllers
case "SetClientTagCommand":
_setClientTagCommandName = cmd.Name;
break;
case "AddClientNoteCommand":
_addClientNoteCommandName = cmd.Name;
break;
}
}
}
@ -640,6 +646,52 @@ namespace WebfrontCore.Controllers
}));
}
public async Task<IActionResult> AddClientNoteForm(int id)
{
var existingNote = await _metaService.GetPersistentMetaValue<ClientNoteMetaResponse>("ClientNotes", id);
var info = new ActionInfo
{
ActionButtonLabel = Localization["WEBFRONT_CONFIGURATION_BUTTON_SAVE"],
Name = Localization["WEBFRONT_PROFILE_CONTEXT_MENU_NOTE"],
Inputs = new List<InputInfo>
{
new()
{
Name = "note",
Label = Localization["WEBFRONT_ACTION_NOTE_FORM_NOTE"],
Value = existingNote?.Note,
Type = "textarea"
}
},
Action = nameof(AddClientNote),
ShouldRefresh = true
};
return View("_ActionForm", info);
}
public async Task<IActionResult> AddClientNote(int targetId, string note)
{
if (note?.Length > 350 || note?.Count(c => c == '\n') > 4)
{
return StatusCode(StatusCodes.Status400BadRequest, new[]
{
new CommandResponseInfo
{
Response = Localization["WEBFRONT_ACTION_NOTE_INVALID_LENGTH"]
}
});
}
var server = Manager.GetServers().First();
return await Task.FromResult(RedirectToAction("Execute", "Console", new
{
serverId = server.EndPoint,
command =
$"{_appConfig.CommandPrefix}{_addClientNoteCommandName} @{targetId} {note}"
}));
}
private Dictionary<string, string> GetPresetPenaltyReasons() => _appConfig.PresetPenaltyReasons.Values
.Concat(_appConfig.GlobalRules)
.Concat(_appConfig.Servers.SelectMany(server => server.Rules ?? Array.Empty<string>()))

View File

@ -12,6 +12,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Data.Models;
using SharedLibraryCore.Services;
using Stats.Config;
using WebfrontCore.Permissions;
using WebfrontCore.ViewComponents;
@ -23,13 +24,15 @@ namespace WebfrontCore.Controllers
private readonly IMetaServiceV2 _metaService;
private readonly StatsConfiguration _config;
private readonly IGeoLocationService _geoLocationService;
private readonly ClientService _clientService;
public ClientController(IManager manager, IMetaServiceV2 metaService, StatsConfiguration config,
IGeoLocationService geoLocationService) : base(manager)
IGeoLocationService geoLocationService, ClientService clientService) : base(manager)
{
_metaService = metaService;
_config = config;
_geoLocationService = geoLocationService;
_clientService = clientService;
}
[Obsolete]
@ -53,18 +56,26 @@ namespace WebfrontCore.Controllers
{
_metaService.GetPersistentMetaByLookup(EFMeta.ClientTagV2, EFMeta.ClientTagNameV2, client.ClientId,
token),
_metaService.GetPersistentMeta("GravatarEmail", client.ClientId, token)
_metaService.GetPersistentMeta("GravatarEmail", client.ClientId, token),
};
var persistentMeta = await Task.WhenAll(persistentMetaTask);
var tag = persistentMeta[0];
var gravatar = persistentMeta[1];
var note = await _metaService.GetPersistentMetaValue<ClientNoteMetaResponse>("ClientNotes", client.ClientId,
token);
if (tag?.Value != null)
{
client.SetAdditionalProperty(EFMeta.ClientTagV2, tag.Value);
}
if (note is not null)
{
note.OriginEntityName = await _clientService.GetClientNameById(note.OriginEntityId);
}
// even though we haven't set their level to "banned" yet
// (ie they haven't reconnected with the infringing player identifier)
// we want to show them as banned as to not confuse people.
@ -123,7 +134,8 @@ namespace WebfrontCore.Controllers
: ingameClient.CurrentServer.IP,
ingameClient.CurrentServer.Port),
CurrentServerName = ingameClient?.CurrentServer?.Hostname,
GeoLocationInfo = await _geoLocationService.Locate(client.IPAddressString)
GeoLocationInfo = await _geoLocationService.Locate(client.IPAddressString),
NoteMeta = note
};
var meta = await _metaService.GetRuntimeMeta<InformationResponse>(new ClientPaginationRequest

View File

@ -14,13 +14,13 @@ public enum WebfrontEntity
AuditPage,
RecentPlayersPage,
ProfilePage,
AdminMenu
AdminMenu,
ClientNote
}
public enum WebfrontPermission
{
Read,
Create,
Update,
Write,
Delete
}

View File

@ -52,6 +52,11 @@
</div>
}
else if (inputType == "textarea")
{
<textarea name="@input.Name" class="form-control @(input.Required ? "required" : "")" placeholder="@input.Placeholder" aria-label="@input.Name" aria-describedby="basic-addon-@input.Name">@value</textarea>
}
else
{
<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">

View File

@ -173,6 +173,25 @@
</div>
</div>
@if (!string.IsNullOrWhiteSpace(Model.NoteMeta?.Note))
{
<has-permission entity="ClientNote" required-permission="Read">
<div class="rounded border p-10 m-10 d-flex flex-column flex-md-row" style="border-style: dashed !important">
<i class="align-self-center oi oi-clipboard"></i>
<div class="align-self-center font-size-12 font-weight-light pl-10 pr-10">
@foreach (var line in Model.NoteMeta.Note.Split("\n"))
{
<div class="text-force-break">@line.TrimEnd('\r')</div>
}
<div class="mt-5">
<a asp-controller="Client" asp-action="Profile" asp-route-id="@Model.NoteMeta.OriginEntityId" class="no-decoration ">@Model.NoteMeta.OriginEntityName</a>
<span>&mdash; @Model.NoteMeta.ModifiedDate.HumanizeForCurrentCulture()</span>
</div>
</div>
</div>
</has-permission>
}
<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 with-arrow align-self-center">
@ -284,13 +303,25 @@
{
menuItems.Items.Add(new SideContextMenuItem
{
Title = ViewBag.Localization["WEBFRONT_ACTION_SET_CLIENT_TAG_TITLE"],
Title = ViewBag.Localization["WEBFRONT_PROFILE_CONTEXT_MENU_TAG"],
IsButton = true,
Reference = "SetClientTag",
Icon = "oi-tag",
EntityId = Model.ClientId
});
if ((ViewBag.PermissionsSet as IEnumerable<string>).HasPermission(WebfrontEntity.ClientNote, WebfrontPermission.Write))
{
menuItems.Items.Add(new SideContextMenuItem
{
Title = ViewBag.Localization["WEBFRONT_PROFILE_CONTEXT_MENU_NOTE"],
IsButton = true,
Reference = "AddClientNote",
Icon = "oi-clipboard",
EntityId = Model.ClientId
});
}
menuItems.Items.Add(new SideContextMenuItem
{
Title = ViewBag.Localization["WEBFRONT_PROFILE_CONTEXT_MENU_ACTION_MESSAGE"],