add client note command and feature
This commit is contained in:
parent
fa1567d3f5
commit
51e8b31e42
52
Application/Commands/AddClientNoteCommand.cs
Normal file
52
Application/Commands/AddClientNoteCommand.cs
Normal 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"]);
|
||||
}
|
||||
}
|
@ -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; }
|
||||
}
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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>()))
|
||||
|
@ -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
|
||||
|
@ -14,13 +14,13 @@ public enum WebfrontEntity
|
||||
AuditPage,
|
||||
RecentPlayersPage,
|
||||
ProfilePage,
|
||||
AdminMenu
|
||||
AdminMenu,
|
||||
ClientNote
|
||||
}
|
||||
|
||||
public enum WebfrontPermission
|
||||
{
|
||||
Read,
|
||||
Create,
|
||||
Update,
|
||||
Write,
|
||||
Delete
|
||||
}
|
||||
|
@ -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">
|
||||
|
@ -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>— @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"],
|
||||
|
Loading…
Reference in New Issue
Block a user