about summary refs log tree commit diff
path: root/MatrixUtils.Web/Shared/PolicyEditorComponents
diff options
context:
space:
mode:
Diffstat (limited to 'MatrixUtils.Web/Shared/PolicyEditorComponents')
-rw-r--r--MatrixUtils.Web/Shared/PolicyEditorComponents/MassPolicyEditorModal.razor171
-rw-r--r--MatrixUtils.Web/Shared/PolicyEditorComponents/MassPolicyEditorModal.razor.css15
-rw-r--r--MatrixUtils.Web/Shared/PolicyEditorComponents/PolicyEditorModal.razor60
3 files changed, 208 insertions, 38 deletions
diff --git a/MatrixUtils.Web/Shared/PolicyEditorComponents/MassPolicyEditorModal.razor b/MatrixUtils.Web/Shared/PolicyEditorComponents/MassPolicyEditorModal.razor

index 11ba18a..b49358d 100644 --- a/MatrixUtils.Web/Shared/PolicyEditorComponents/MassPolicyEditorModal.razor +++ b/MatrixUtils.Web/Shared/PolicyEditorComponents/MassPolicyEditorModal.razor
@@ -5,8 +5,9 @@ @using System.Collections.Frozen @using LibMatrix.EventTypes @using LibMatrix.RoomTypes -<ModalWindow Title="@("Creating many new " + (PolicyTypes.ContainsKey(MappedType??"") ? PolicyTypes[MappedType!].GetFriendlyNamePluralOrNull()?.ToLower() ?? PolicyTypes[MappedType!].Name : "event"))" - OnCloseClicked="@OnClose" X="60" Y="60" MinWidth="600"> +<ModalWindow + Title="@("Creating many new " + (PolicyTypes.ContainsKey(MappedType ?? "") ? PolicyTypes[MappedType!].GetFriendlyNamePluralOrNull()?.ToLower() ?? PolicyTypes[MappedType!].Name : "event"))" + OnCloseClicked="@OnClose" X="60" Y="60" MinWidth="600"> <span>Policy type:</span> <select @bind="@MappedType"> <option>Select a value</option> @@ -14,25 +15,58 @@ <option value="@type">@mappedType.GetFriendlyName().ToLower()</option> } </select><br/> - + <span>Reason:</span> - <FancyTextBox @bind-Value="@Reason"></FancyTextBox><br/> - + <FancyTextBox @bind-Value="@Reason"></FancyTextBox> + <br/> + <span>Recommendation:</span> - <FancyTextBox @bind-Value="@Recommendation"></FancyTextBox><br/> + <FancyTextBox @bind-Value="@Recommendation"></FancyTextBox> + <br/> <span>Entities:</span><br/> - <InputTextArea @bind-Value="@Users" style="width: 500px;"></InputTextArea><br/> - - + <FancyTextBox Multiline="true" @bind-Value="@Entities"></FancyTextBox> + <br/> + + @* <details> *@ @* <summary>JSON data</summary> *@ @* <pre> *@ @* $1$ @PolicyEvent.ToJson(true, true) #1# *@ @* </pre> *@ @* </details> *@ - <LinkButton OnClick="@(() => { OnClose.Invoke(); return Task.CompletedTask; })"> Cancel </LinkButton> - <LinkButton OnClick="@(() => { _ = Save(); return Task.CompletedTask; })"> Save </LinkButton> + @if (!VerifyIntent) { + <LinkButton OnClickAsync="@(() => { + OnClose.Invoke(); + return Task.CompletedTask; + })"> Cancel + </LinkButton> + <LinkButton OnClickAsync="@(() => { + _ = Save(); + return Task.CompletedTask; + })"> Save + </LinkButton> + @if (!string.IsNullOrWhiteSpace(Response)) { + <pre style="color: red;">@Response</pre> + } + } + else { + <b class="blink">WARNING!!!</b> + <br/> + + @if (!string.IsNullOrWhiteSpace(Response)) { + <pre style="color: red;">@Response</pre> + } + + <span>Are you sure you want to do this?</span> + <LinkButton Color="#00FF00" OnClick="@(() => { + VerifyIntent = false; + Response = null; + StateHasChanged(); + })">No + </LinkButton> + <LinkButton Color="#FF0000" OnClick="@(() => { _ = Save(force: true); })">Yes</LinkButton> + } </ModalWindow> @@ -47,33 +81,118 @@ [Parameter] public required GenericRoom Room { get; set; } - public string Recommendation { get; set; } = "m.ban"; - public string Reason { get; set; } = "spam"; - public string Users { get; set; } = ""; + private string Recommendation { get; set; } = "m.ban"; + private string Reason { get; set; } = "spam"; - private static FrozenSet<Type> KnownPolicyTypes = StateEvent.KnownStateEventTypes.Where(x => x.IsAssignableTo(typeof(PolicyRuleEventContent))).ToFrozenSet(); + private string Entities { get; set; } = ""; + + private string? Response { + get; + set { + field = value; + StateHasChanged(); + } + } + + private bool VerifyIntent { get; set; } + + private static FrozenSet<Type> KnownPolicyTypes = MatrixEvent.KnownEventTypes.Where(x => x.IsAssignableTo(typeof(PolicyRuleEventContent))).ToFrozenSet(); private static Dictionary<string, Type> PolicyTypes = KnownPolicyTypes .ToDictionary(x => x.GetCustomAttributes<MatrixEventAttribute>().First(y => !string.IsNullOrWhiteSpace(y.EventName)).EventName, x => x); + private static FrozenSet<string> AllKnownPolicyTypes = KnownPolicyTypes + .SelectMany(x => x.GetCustomAttributes<MatrixEventAttribute>().Select(y => y.EventName)) + .ToFrozenSet(); + private string? MappedType { get; set; } - private async Task Save() { + private async Task Save(bool force = false) { + if (string.IsNullOrWhiteSpace(MappedType)) { + Response = "No type selected"; + return; + } + + if (string.IsNullOrWhiteSpace(Entities)) { + Response = "No users selected"; + return; + } + + Console.WriteLine("Saving ---"); + + var entities = Entities.Split("\n", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) + .Select(x => x.Trim()) + .Distinct() + .ToList(); + + if (!force && !Validate(entities, PolicyTypes[MappedType])) { + List<string> distinctTypes = entities + .Select(GuessType) + .Where(x => x != null) + .Distinct() + .Select(x => x!.Name) + .ToList(); + + VerifyIntent = true; + Response = $"Invalid entities. Expected {PolicyTypes[MappedType].Name}, got:\n - " + + string.Join("\n - ", distinctTypes); + return; + } + try { - await DoActualSave(); + await SaveAll(entities); } catch (Exception e) { - Console.WriteLine($"Failed to save: {e}"); + Response = $"Failed to save: {e}"; } } - private async Task DoActualSave() { - Console.WriteLine($"Saving ---"); - Console.WriteLine($"Users = {Users}"); - var users = Users.Split("\n").Select(x => x.Trim()).Where(x => x.StartsWith('@')).ToList(); - var tasks = users.Select(x => ExecuteBan(Room, x)).ToList(); - await Task.WhenAll(tasks); - + private bool Validate(List<string> entities, Type expectedType) { + return entities.All(x => GuessType(x) == expectedType); + } + + private Type? GuessType(string entity) { + var sigil = entity[0]; + return TypesBySigil.GetValueOrDefault(sigil.ToString(), typeof(ServerPolicyRuleEventContent)); + } + + private Dictionary<string, Type> TypesBySigil = new() { + { "@", typeof(UserPolicyRuleEventContent) }, + { "!", typeof(RoomPolicyRuleEventContent) }, + { "#", typeof(RoomPolicyRuleEventContent) } + }; + + private async Task SaveAll(List<string> entities) { + await foreach (var evt in Room.GetFullStateAsync()) { + if (evt is null + || !AllKnownPolicyTypes.Contains(evt.Type) + || !evt.TypedContent!.GetType().IsAssignableTo(PolicyTypes[MappedType!]) + ) continue; + + if (evt.TypedContent is PolicyRuleEventContent content && content.Recommendation == Recommendation && content.Reason == Reason) { + if (content.Entity != null && entities.Contains(content.Entity)) + entities.Remove(content.Entity); + } + } + + // var tasks = entities.Select(x => ExecuteBan(Room, x)).ToList(); + // await Task.WhenAll(tasks); + + var events = entities.Select(entity => { + var content = Activator.CreateInstance(PolicyTypes[MappedType!]) as PolicyRuleEventContent ?? throw new InvalidOperationException("Failed to create event content"); + content.Recommendation = Recommendation; + content.Reason = Reason; + content.Entity = entity; + return new MatrixEvent() { + Type = MappedType, + TypedContent = content, + StateKey = content.GetDraupnir2StateKey() + }; + }); + + foreach (var chunk in events.Chunk(50)) + await Room.BulkSendEventsAsync(chunk); + OnSaved.Invoke(); } @@ -81,7 +200,7 @@ bool success = false; while (!success) { try { - var content = Activator.CreateInstance(PolicyTypes[MappedType!]) as PolicyRuleEventContent; + var content = Activator.CreateInstance(PolicyTypes[MappedType!]) as PolicyRuleEventContent ?? throw new InvalidOperationException("Failed to create event content"); content.Recommendation = Recommendation; content.Reason = Reason; content.Entity = entity; diff --git a/MatrixUtils.Web/Shared/PolicyEditorComponents/MassPolicyEditorModal.razor.css b/MatrixUtils.Web/Shared/PolicyEditorComponents/MassPolicyEditorModal.razor.css new file mode 100644
index 0000000..49ab31b --- /dev/null +++ b/MatrixUtils.Web/Shared/PolicyEditorComponents/MassPolicyEditorModal.razor.css
@@ -0,0 +1,15 @@ +.blink { + animation: blinker 2s linear infinite; +} + +@keyframes blinker { + 0% { + opacity: 1; + } + 50% { + opacity: 0; + } + 100% { + opacity: 1; + } +} \ No newline at end of file diff --git a/MatrixUtils.Web/Shared/PolicyEditorComponents/PolicyEditorModal.razor b/MatrixUtils.Web/Shared/PolicyEditorComponents/PolicyEditorModal.razor
index 5819bee..501ca99 100644 --- a/MatrixUtils.Web/Shared/PolicyEditorComponents/PolicyEditorModal.razor +++ b/MatrixUtils.Web/Shared/PolicyEditorComponents/PolicyEditorModal.razor
@@ -6,7 +6,7 @@ @using System.Collections.Frozen @using LibMatrix.EventTypes <ModalWindow Title="@((string.IsNullOrWhiteSpace(PolicyEvent.EventId) ? "Creating new " : "Editing ") + (PolicyEvent.MappedType.GetFriendlyNameOrNull()?.ToLower() ?? "event"))" - OnCloseClicked="@OnClose" X="60" Y="60" MinWidth="300"> + OnCloseClickedAsync="@InvokeOnClose" X="60" Y="60" MinWidth="300"> @if (string.IsNullOrWhiteSpace(PolicyEvent.EventId)) { <span>Policy type:</span> <select @bind="@MappedType"> @@ -52,7 +52,12 @@ else { switch (Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType) { case Type t when t == typeof(string): - <FancyTextBox Value="@(getter?.Invoke(PolicyData, null) as string)" ValueChanged="@((string e) => { Console.WriteLine($"{prop.Name} ({setter is not null}) -> {e}"); setter?.Invoke(PolicyData, [e]); PolicyEvent.TypedContent = PolicyData; StateHasChanged(); })"></FancyTextBox> + <FancyTextBox Value="@(getter?.Invoke(PolicyData, null) as string)" ValueChanged="@((string e) => { + Console.WriteLine($"{prop.Name} ({setter is not null}) -> {e}"); + setter?.Invoke(PolicyData, [e]); + PolicyEvent.TypedContent = PolicyData; + StateHasChanged(); + })"></FancyTextBox> break; case Type t when t == typeof(DateTime): if (!isNullable) { @@ -61,13 +66,22 @@ else { var value = getter?.Invoke(PolicyData, null) as DateTime?; if (value is null) { - <button @onclick="() => { setter?.Invoke(PolicyData, [DateTime.Now]); PolicyEvent.TypedContent = PolicyData; StateHasChanged(); }">Add value</button> + <button @onclick="() => { setter?.Invoke(PolicyData, [DateTime.Now]); PolicyEvent.TypedContent = PolicyData; StateHasChanged(); }"> + Add value + </button> } else { var notNullValue = Nullable.GetValueRefOrDefaultRef(ref value); Console.WriteLine($"Value: {value?.ToString() ?? "null"}"); - <InputDate TValue="DateTime" ValueExpression="@(() => notNullValue)" ValueChanged="@(e => { Console.WriteLine($"{prop.Name} ({setter is not null}) -> {e}"); setter?.Invoke(PolicyData, [e]); PolicyEvent.TypedContent = PolicyData; StateHasChanged(); })"></InputDate> - <button @onclick="() => { setter?.Invoke(PolicyData, [null]); PolicyEvent.TypedContent = PolicyData; StateHasChanged(); }">Remove value</button> + <InputDate TValue="DateTime" ValueExpression="@(() => notNullValue)" ValueChanged="@(e => { + Console.WriteLine($"{prop.Name} ({setter is not null}) -> {e}"); + setter?.Invoke(PolicyData, [e]); + PolicyEvent.TypedContent = PolicyData; + StateHasChanged(); + })"></InputDate> + <button @onclick="() => { setter?.Invoke(PolicyData, [null]); PolicyEvent.TypedContent = PolicyData; StateHasChanged(); }">Remove + value + </button> } } @@ -88,8 +102,8 @@ @PolicyEvent.ToJson(true, true) </pre> </details> - <LinkButton OnClick="@(() => { OnClose.Invoke(); return Task.CompletedTask; })"> Cancel </LinkButton> - <LinkButton OnClick="@(() => { OnSave.Invoke(PolicyEvent); return Task.CompletedTask; })"> Save </LinkButton> + <LinkButton OnClickAsync="@InvokeOnClose">Cancel</LinkButton> + <LinkButton OnClickAsync="@InvokeOnSave">Save</LinkButton> } else { <p>Policy data is null</p> @@ -99,7 +113,7 @@ @code { [Parameter] - public StateEventResponse? PolicyEvent { + public MatrixEventResponse? PolicyEvent { get => _policyEvent; set { if (value is not null && value != _policyEvent) @@ -111,19 +125,41 @@ } [Parameter] - public required Action OnClose { get; set; } + public Action? OnClose { get; set; } [Parameter] - public required Action<StateEventResponse> OnSave { get; set; } + public Func<Task>? OnCloseAsync { get; set; } + + private async Task InvokeOnClose() { + if (OnClose is not null) + OnClose.Invoke(); + + if (OnCloseAsync is not null) + await OnCloseAsync.Invoke(); + } + + [Parameter] + public Action<MatrixEventResponse>? OnSave { get; set; } + + [Parameter] + public Func<MatrixEventResponse, Task>? OnSaveAsync { get; set; } + + private async Task InvokeOnSave() { + if (OnSave is not null) + OnSave.Invoke(PolicyEvent); + + if (OnSaveAsync is not null) + await OnSaveAsync.Invoke(PolicyEvent); + } public PolicyRuleEventContent? PolicyData { get; set; } - private static FrozenSet<Type> KnownPolicyTypes = StateEvent.KnownStateEventTypes.Where(x => x.IsAssignableTo(typeof(PolicyRuleEventContent))).ToFrozenSet(); + private static FrozenSet<Type> KnownPolicyTypes = MatrixEvent.KnownEventTypes.Where(x => x.IsAssignableTo(typeof(PolicyRuleEventContent))).ToFrozenSet(); private static Dictionary<string, Type> PolicyTypes = KnownPolicyTypes .ToDictionary(x => x.GetCustomAttributes<MatrixEventAttribute>().First(y => !string.IsNullOrWhiteSpace(y.EventName)).EventName, x => x); - private StateEventResponse? _policyEvent; + private MatrixEventResponse? _policyEvent; private string? MappedType { get => _policyEvent?.Type;