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 ConnectProtocolUrl { get;set; }
|
||||||
public string CurrentServerName { get; set; }
|
public string CurrentServerName { get; set; }
|
||||||
public IGeoLocationResult GeoLocationInfo { get; set; }
|
public IGeoLocationResult GeoLocationInfo { get; set; }
|
||||||
|
public ClientNoteMetaResponse NoteMeta { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -938,6 +938,14 @@ namespace SharedLibraryCore.Services
|
|||||||
return clientList;
|
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
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,11 +7,13 @@ using System.Threading.Tasks;
|
|||||||
using Data.Models;
|
using Data.Models;
|
||||||
using Data.Models.Client;
|
using Data.Models.Client;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using SharedLibraryCore;
|
using SharedLibraryCore;
|
||||||
using SharedLibraryCore.Commands;
|
using SharedLibraryCore.Commands;
|
||||||
using SharedLibraryCore.Configuration;
|
using SharedLibraryCore.Configuration;
|
||||||
using SharedLibraryCore.Dtos;
|
using SharedLibraryCore.Dtos;
|
||||||
|
using SharedLibraryCore.Dtos.Meta.Responses;
|
||||||
using SharedLibraryCore.Helpers;
|
using SharedLibraryCore.Helpers;
|
||||||
using SharedLibraryCore.Interfaces;
|
using SharedLibraryCore.Interfaces;
|
||||||
using WebfrontCore.ViewModels;
|
using WebfrontCore.ViewModels;
|
||||||
@ -32,6 +34,7 @@ namespace WebfrontCore.Controllers
|
|||||||
private readonly string _unflagCommandName;
|
private readonly string _unflagCommandName;
|
||||||
private readonly string _setLevelCommandName;
|
private readonly string _setLevelCommandName;
|
||||||
private readonly string _setClientTagCommandName;
|
private readonly string _setClientTagCommandName;
|
||||||
|
private readonly string _addClientNoteCommandName;
|
||||||
|
|
||||||
public ActionController(IManager manager, IEnumerable<IManagerCommand> registeredCommands,
|
public ActionController(IManager manager, IEnumerable<IManagerCommand> registeredCommands,
|
||||||
ApplicationConfiguration appConfig, IMetaServiceV2 metaService) : base(manager)
|
ApplicationConfiguration appConfig, IMetaServiceV2 metaService) : base(manager)
|
||||||
@ -76,6 +79,9 @@ namespace WebfrontCore.Controllers
|
|||||||
case "SetClientTagCommand":
|
case "SetClientTagCommand":
|
||||||
_setClientTagCommandName = cmd.Name;
|
_setClientTagCommandName = cmd.Name;
|
||||||
break;
|
break;
|
||||||
|
case "AddClientNoteCommand":
|
||||||
|
_addClientNoteCommandName = cmd.Name;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -639,6 +645,52 @@ namespace WebfrontCore.Controllers
|
|||||||
$"{_appConfig.CommandPrefix}{_setClientTagCommandName} @{targetId} {clientTag}"
|
$"{_appConfig.CommandPrefix}{_setClientTagCommandName} @{targetId} {clientTag}"
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
private Dictionary<string, string> GetPresetPenaltyReasons() => _appConfig.PresetPenaltyReasons.Values
|
||||||
.Concat(_appConfig.GlobalRules)
|
.Concat(_appConfig.GlobalRules)
|
||||||
|
@ -12,6 +12,7 @@ using System.Linq;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Data.Models;
|
using Data.Models;
|
||||||
|
using SharedLibraryCore.Services;
|
||||||
using Stats.Config;
|
using Stats.Config;
|
||||||
using WebfrontCore.Permissions;
|
using WebfrontCore.Permissions;
|
||||||
using WebfrontCore.ViewComponents;
|
using WebfrontCore.ViewComponents;
|
||||||
@ -23,13 +24,15 @@ namespace WebfrontCore.Controllers
|
|||||||
private readonly IMetaServiceV2 _metaService;
|
private readonly IMetaServiceV2 _metaService;
|
||||||
private readonly StatsConfiguration _config;
|
private readonly StatsConfiguration _config;
|
||||||
private readonly IGeoLocationService _geoLocationService;
|
private readonly IGeoLocationService _geoLocationService;
|
||||||
|
private readonly ClientService _clientService;
|
||||||
|
|
||||||
public ClientController(IManager manager, IMetaServiceV2 metaService, StatsConfiguration config,
|
public ClientController(IManager manager, IMetaServiceV2 metaService, StatsConfiguration config,
|
||||||
IGeoLocationService geoLocationService) : base(manager)
|
IGeoLocationService geoLocationService, ClientService clientService) : base(manager)
|
||||||
{
|
{
|
||||||
_metaService = metaService;
|
_metaService = metaService;
|
||||||
_config = config;
|
_config = config;
|
||||||
_geoLocationService = geoLocationService;
|
_geoLocationService = geoLocationService;
|
||||||
|
_clientService = clientService;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Obsolete]
|
[Obsolete]
|
||||||
@ -53,18 +56,26 @@ namespace WebfrontCore.Controllers
|
|||||||
{
|
{
|
||||||
_metaService.GetPersistentMetaByLookup(EFMeta.ClientTagV2, EFMeta.ClientTagNameV2, client.ClientId,
|
_metaService.GetPersistentMetaByLookup(EFMeta.ClientTagV2, EFMeta.ClientTagNameV2, client.ClientId,
|
||||||
token),
|
token),
|
||||||
_metaService.GetPersistentMeta("GravatarEmail", client.ClientId, token)
|
_metaService.GetPersistentMeta("GravatarEmail", client.ClientId, token),
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var persistentMeta = await Task.WhenAll(persistentMetaTask);
|
var persistentMeta = await Task.WhenAll(persistentMetaTask);
|
||||||
var tag = persistentMeta[0];
|
var tag = persistentMeta[0];
|
||||||
var gravatar = persistentMeta[1];
|
var gravatar = persistentMeta[1];
|
||||||
|
var note = await _metaService.GetPersistentMetaValue<ClientNoteMetaResponse>("ClientNotes", client.ClientId,
|
||||||
|
token);
|
||||||
|
|
||||||
if (tag?.Value != null)
|
if (tag?.Value != null)
|
||||||
{
|
{
|
||||||
client.SetAdditionalProperty(EFMeta.ClientTagV2, tag.Value);
|
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
|
// even though we haven't set their level to "banned" yet
|
||||||
// (ie they haven't reconnected with the infringing player identifier)
|
// (ie they haven't reconnected with the infringing player identifier)
|
||||||
// we want to show them as banned as to not confuse people.
|
// we want to show them as banned as to not confuse people.
|
||||||
@ -123,7 +134,8 @@ namespace WebfrontCore.Controllers
|
|||||||
: ingameClient.CurrentServer.IP,
|
: ingameClient.CurrentServer.IP,
|
||||||
ingameClient.CurrentServer.Port),
|
ingameClient.CurrentServer.Port),
|
||||||
CurrentServerName = ingameClient?.CurrentServer?.Hostname,
|
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
|
var meta = await _metaService.GetRuntimeMeta<InformationResponse>(new ClientPaginationRequest
|
||||||
|
@ -14,13 +14,13 @@ public enum WebfrontEntity
|
|||||||
AuditPage,
|
AuditPage,
|
||||||
RecentPlayersPage,
|
RecentPlayersPage,
|
||||||
ProfilePage,
|
ProfilePage,
|
||||||
AdminMenu
|
AdminMenu,
|
||||||
|
ClientNote
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum WebfrontPermission
|
public enum WebfrontPermission
|
||||||
{
|
{
|
||||||
Read,
|
Read,
|
||||||
Create,
|
Write,
|
||||||
Update,
|
|
||||||
Delete
|
Delete
|
||||||
}
|
}
|
||||||
|
@ -51,6 +51,11 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</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
|
else
|
||||||
{
|
{
|
||||||
|
@ -173,6 +173,25 @@
|
|||||||
</div>
|
</div>
|
||||||
</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">
|
<div class="flex-fill d-flex justify-content-center justify-content-md-end mt-10 mt-md-0">
|
||||||
<!-- country flag -->
|
<!-- country flag -->
|
||||||
<div id="ipGeoDropdown" class="dropdown with-arrow align-self-center">
|
<div id="ipGeoDropdown" class="dropdown with-arrow align-self-center">
|
||||||
@ -279,18 +298,30 @@
|
|||||||
EntityId = Model.ClientId
|
EntityId = Model.ClientId
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ViewBag.Authorized)
|
if (ViewBag.Authorized)
|
||||||
{
|
{
|
||||||
menuItems.Items.Add(new SideContextMenuItem
|
menuItems.Items.Add(new SideContextMenuItem
|
||||||
{
|
{
|
||||||
Title = ViewBag.Localization["WEBFRONT_ACTION_SET_CLIENT_TAG_TITLE"],
|
Title = ViewBag.Localization["WEBFRONT_PROFILE_CONTEXT_MENU_TAG"],
|
||||||
IsButton = true,
|
IsButton = true,
|
||||||
Reference = "SetClientTag",
|
Reference = "SetClientTag",
|
||||||
Icon = "oi-tag",
|
Icon = "oi-tag",
|
||||||
EntityId = Model.ClientId
|
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
|
menuItems.Items.Add(new SideContextMenuItem
|
||||||
{
|
{
|
||||||
Title = ViewBag.Localization["WEBFRONT_PROFILE_CONTEXT_MENU_ACTION_MESSAGE"],
|
Title = ViewBag.Localization["WEBFRONT_PROFILE_CONTEXT_MENU_ACTION_MESSAGE"],
|
||||||
|
Loading…
x
Reference in New Issue
Block a user