about summary refs log tree commit diff
path: root/MatrixUtils.Web/Pages/Rooms/PolicyListComponents
diff options
context:
space:
mode:
Diffstat (limited to 'MatrixUtils.Web/Pages/Rooms/PolicyListComponents')
-rw-r--r--MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListCategoryComponent.razor74
-rw-r--r--MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListEditorHeader.razor88
-rw-r--r--MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListRowComponent.razor218
3 files changed, 380 insertions, 0 deletions
diff --git a/MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListCategoryComponent.razor b/MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListCategoryComponent.razor
new file mode 100644

index 0000000..932e0fe --- /dev/null +++ b/MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListCategoryComponent.razor
@@ -0,0 +1,74 @@ +@using ArcaneLibs.Extensions +@using LibMatrix.RoomTypes +<details> + <summary> + <span> + @($"{PolicyCollection.Name}: {PolicyCollection.TotalCount} policies") + </span> + <hr style="margin: revert;"/> + </summary> + <table class="table table-striped table-hover table-bordered align-middle"> + <thead> + <tr> + <th>Actions</th> + @foreach (var name in PolicyCollection.PropertiesToDisplay!.Keys) { + <th>@name</th> + } + </tr> + </thead> + <tbody> + @foreach (var policy in PolicyCollection.ActivePolicies.Values.OrderBy(x => x.Policy.RawContent?["entity"]?.GetValue<string>())) { + <PolicyListRowComponent PolicyCollectionStateHasChanged="@StateHasChanged" RenderEventInfo="RenderEventInfo" PolicyInfo="@policy" PolicyCollection="@PolicyCollection" Room="@Room"></PolicyListRowComponent> + } + </tbody> + </table> + @if (RenderInvalidSection) { + <details> + <summary> + <u> + @("Invalid " + PolicyCollection.Name.ToLower()) + </u> + </summary> + <table class="table table-striped table-hover table-bordered align-middle"> + <thead> + <tr> + <th>State key</th> + <th>Json contents</th> + </tr> + </thead> + <tbody> + @foreach (var policy in PolicyCollection.RemovedPolicies.Values) { + <tr> + <td>@policy.Policy.StateKey</td> + <td> + <pre>@policy.Policy.RawContent.ToJson(true, false)</pre> + </td> + </tr> + } + </tbody> + </table> + </details> + } +</details> + +@code { + + [Parameter] + public required PolicyList.PolicyCollection PolicyCollection { get; set; } + + [Parameter] + public required GenericRoom Room { get; set; } + + [Parameter] + public bool RenderEventInfo { get; set; } + + [Parameter] + public bool RenderInvalidSection { get; set; } = true; + + protected override bool ShouldRender() { + // if (PolicyCollection is null) return false; + + return true; + } + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListEditorHeader.razor b/MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListEditorHeader.razor new file mode 100644
index 0000000..b57beae --- /dev/null +++ b/MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListEditorHeader.razor
@@ -0,0 +1,88 @@ +@using LibMatrix +@using LibMatrix.EventTypes.Common +@using LibMatrix.RoomTypes +@using MatrixUtils.Web.Shared.PolicyEditorComponents +<h3>Policy list editor - Editing @(RoomName ?? Room.RoomId)</h3> +@if (!string.IsNullOrWhiteSpace(DraupnirShortcode)) { + <span style="margin-right: 2em;">Shortcode: @DraupnirShortcode</span> +} +@if (!string.IsNullOrWhiteSpace(RoomAlias)) { + <span>Alias: @RoomAlias</span> +} +<hr/> +@* <InputCheckbox @bind-Value="EnableAvatars"></InputCheckbox><label>Enable avatars (WILL EXPOSE YOUR IP TO TARGET HOMESERVERS!)</label> *@ +<LinkButton OnClickAsync="@(() => { + CurrentlyEditingEvent = new() { Type = "", RawContent = new() }; + return Task.CompletedTask; + })">Create new policy +</LinkButton> +<LinkButton OnClickAsync="@(() => { + MassCreatePolicies = true; + return Task.CompletedTask; + })">Create many new policies +</LinkButton> +<LinkButton OnClickAsync="@(() => ReloadStateAsync())">Refresh</LinkButton> + +@if (CurrentlyEditingEvent is not null) { + <PolicyEditorModal PolicyEvent="@CurrentlyEditingEvent" OnClose="@(() => CurrentlyEditingEvent = null)" OnSaveAsync="@UpdatePolicyAsync"></PolicyEditorModal> +} + +@if (MassCreatePolicies) { + <MassPolicyEditorModal Room="@Room" OnClose="@(() => MassCreatePolicies = false)" OnSaved="@(() => { + MassCreatePolicies = false; + // _ = LoadStatesAsync(); + })"></MassPolicyEditorModal> +} +<br/> +<InputCheckbox Value="@RenderEventInfo" ValueChanged="@RenderEventInfoChanged" ValueExpression="@(() => RenderEventInfo)"/> +<span> Render event info</span> + +@code { + + [Parameter] + public required GenericRoom Room { get; set; } + + [Parameter] + public required Func<Task> ReloadStateAsync { get; set; } + + [Parameter] + public required bool RenderEventInfo { get; set; } + + [Parameter] + public required EventCallback<bool> RenderEventInfoChanged { get; set; } + + private string? RoomName { get; set; } + private string? RoomAlias { get; set; } + private string? DraupnirShortcode { get; set; } + + private MatrixEventResponse? CurrentlyEditingEvent { + get; + set { + field = value; + StateHasChanged(); + } + } + + private bool MassCreatePolicies { + get; + set { + field = value; + StateHasChanged(); + } + } + + protected override async Task OnInitializedAsync() { + await Task.WhenAll( + Task.Run(async () => { DraupnirShortcode = (await Room.GetStateOrNullAsync<MjolnirShortcodeEventContent>(MjolnirShortcodeEventContent.EventId))?.Shortcode; }), + Task.Run(async () => { RoomAlias = (await Room.GetCanonicalAliasAsync())?.Alias; }), + Task.Run(async () => { RoomName = await Room.GetNameOrFallbackAsync(); }) + ); + + StateHasChanged(); + } + + private async Task UpdatePolicyAsync(MatrixEventResponse evt) { + Console.WriteLine("UpdatePolicyAsync in PolicyListEditorHeader not yet implemented!"); + } + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListRowComponent.razor b/MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListRowComponent.razor new file mode 100644
index 0000000..3ded78f --- /dev/null +++ b/MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListRowComponent.razor
@@ -0,0 +1,218 @@ +@using System.Reflection +@using ArcaneLibs.Extensions +@using LibMatrix +@using LibMatrix.EventTypes.Spec.State.Policy +@using LibMatrix.RoomTypes +@using MatrixUtils.Web.Shared.PolicyEditorComponents + +@if (_isInitialized && IsVisible) { + <tr id="@PolicyInfo.Policy.EventId"> + <td> + <div style="display: flex; flex-direction: row; gap: 0.5em;"> + @* @if (PowerLevels.UserHasStatePermission(Homeserver.WhoAmI.UserId, Policy.Type)) { *@ + @if (true) { + <LinkButton OnClickAsync="@(() => { + IsEditing = true; + return Task.CompletedTask; + })">Edit + </LinkButton> + <LinkButton OnClickAsync="@RemovePolicyAsync">Remove</LinkButton> + @if (Policy.IsLegacyType) { + <LinkButton OnClickAsync="@RemovePolicyAsync">Update type</LinkButton> + } + + @if (TypedContent.Entity?.StartsWith("@*:", StringComparison.Ordinal) == true) { + <LinkButton OnClickAsync="@ConvertToAclAsync">Convert to ACL</LinkButton> + } + + @* @if (PolicyTypeIds[typeof(ServerPolicyRuleEventContent)].Contains(Policy.Type)) { *@ + @* <LinkButton OnClickAsync="@(() => { *@ + @* ServerPolicyToMakePermanent = Policy; *@ + @* return Task.CompletedTask; *@ + @* })">Make permanent *@ + @* </LinkButton> *@ + @* @if (CurrentUserIsDraupnir) { *@ + @* <LinkButton Color="@(ActiveKicks.ContainsKey(Policy) ? "#FF0000" : null)" OnClick="@(() => DraupnirKickMatching(Policy))">Kick *@ + @* users @(ActiveKicks.TryGetValue(Policy, out var kick) ? $"({kick})" : null) *@ + @* </LinkButton> *@ + @* } *@ + // } + } + else { + <p>No permission to modify</p> + } + </div> + </td> + @foreach (var prop in PolicyCollection.PropertiesToDisplay.Values) { + if (prop.Name == "Entity") { + <td> + <span>@TruncateMxid(TypedContent.Entity)</span> + @foreach (var dup in PolicyInfo.DuplicatedBy) { + <br/> + <span>Duplicated by @dup.FriendlyTypeName.ToLower() <a href="@Anchor(dup.EventId!)">@TruncateMxid(dup.RawContent["entity"]?.GetValue<string>())</a></span> + } + @foreach (var dup in PolicyInfo.MadeRedundantBy) { + <br/> + <span>Also matched by @dup.FriendlyTypeName.ToLower() <a href="@Anchor(dup.EventId!)">@TruncateMxid(dup.RawContent["entity"]?.GetValue<string>())</a></span> + } + @if (RenderEventInfo) { + <br/> + <pre style="margin-bottom: unset;"> + @PolicyInfo.Policy.Type/@PolicyInfo.Policy.StateKey by @PolicyInfo.Policy.Sender at @PolicyInfo.Policy.OriginServerTimestamp + </pre> + } + </td> + } + else { + <td>@prop.GetGetMethod()?.Invoke(TypedContent, null)</td> + } + } + </tr> + + @if (IsEditing) { + <PolicyEditorModal PolicyEvent="@Policy" OnClose="@(() => IsEditing = false)" OnSaveAsync="@UpdatePolicyAsync"></PolicyEditorModal> + } + @* TODO: Implement ability to turn ACLs into wildcards *@ + @*@if (ServerPolicyToMakePermanent is not null) { + <ModalWindow Title="Make policy permanent"> + + </ModalWindow> + }*@ +} + + + +@code { + + [Parameter] + public PolicyList.PolicyCollection.PolicyInfo PolicyInfo { get; set; } + + [Parameter] + public GenericRoom Room { get; set; } = null!; + + [Parameter] + public required PolicyList.PolicyCollection PolicyCollection { get; set; } + + [Parameter] + public bool RenderEventInfo { get; set; } + + [Parameter] + public required Action PolicyCollectionStateHasChanged { get; set; } + + private MatrixEventResponse Policy => PolicyInfo.Policy; + + private bool IsEditing { + get; + set { + field = value; + _isDirty = true; + StateHasChanged(); + } + } + + public bool IsVisible { + get; + set { + field = value; + _isDirty = true; + } + } = true; + + private PolicyRuleEventContent TypedContent { get; set; } + + private bool _isDirty = true; + private bool _isInitialized; + + protected override bool ShouldRender() => _isDirty; + + protected override void OnParametersSet() { + TypedContent = Policy.TypedContent as PolicyRuleEventContent ?? throw new InvalidOperationException("Policy must have a typed content of type PolicyRuleEventContent."); + _isDirty = true; + _isInitialized = true; + // Console.WriteLine($"ParametersSet {Policy.StateKey}"); + } + + private static string TruncateMxid(string? mxid) { + if (string.IsNullOrWhiteSpace(mxid)) return mxid; + var parts = mxid.Split(':', 2); + if (parts[0].Length > 50) + parts[0] = parts[0][..50] + "[...]"; + + if (parts is [_, { Length: > 50 }]) + parts[1] = parts[1][..50] + "[...]"; + + return parts.Length == 1 ? parts[0] : $"{parts[0]}:{parts[1]}"; + } + + private async Task RemovePolicyAsync() { + await Room.SendStateEventAsync(Policy.Type, Policy.StateKey, new { }); + bool shouldUpdateVisibility = true; + PolicyCollection.ActivePolicies.Remove((Policy.Type, Policy.StateKey)); + PolicyCollection.RemovedPolicies.Add((Policy.Type, Policy.StateKey), PolicyInfo); + if (PolicyInfo.DuplicatedBy.Count > 0) { + foreach (var evt in PolicyInfo.DuplicatedBy) { + var matchingEntry = PolicyCollection.ActivePolicies + .FirstOrDefault(x => MatrixEvent.Equals(x.Value.Policy, evt)).Value; + var removals = matchingEntry.DuplicatedBy.RemoveAll(x => MatrixEvent.Equals(x, Policy)); + Console.WriteLine($"Removed {removals} duplicates from {evt.EventId}, matching entry: {matchingEntry.ToJson()}"); + if (PolicyCollection.ViewType == PolicyList.PolicyCollection.SpecialViewType.Duplicates && matchingEntry.DuplicatedBy.Count == 0) { + PolicyCollection.ActivePolicies.Remove((matchingEntry.Policy.Type, matchingEntry.Policy.StateKey)); + PolicyCollection.RemovedPolicies.Add((matchingEntry.Policy.Type, matchingEntry.Policy.StateKey), matchingEntry); + Console.WriteLine($"Also removed {matchingEntry.Policy.EventId} as it is now redundant"); + } + } + + PolicyCollectionStateHasChanged(); + shouldUpdateVisibility = false; + } + + if (PolicyInfo.MadeRedundantBy.Count > 0) { + foreach (var evt in PolicyInfo.MadeRedundantBy) { + var matchingEntry = PolicyCollection.ActivePolicies + .FirstOrDefault(x => MatrixEvent.Equals(x.Value.Policy, evt)).Value; + var removals = matchingEntry.MadeRedundantBy.RemoveAll(x => MatrixEvent.Equals(x, Policy)); + Console.WriteLine($"Removed {removals} redundants from {evt.EventId}, matching entry: {matchingEntry.ToJson()}"); + } + + PolicyCollectionStateHasChanged(); + shouldUpdateVisibility = false; + } + + if (shouldUpdateVisibility) { + IsVisible = false; + StateHasChanged(); + } + // PolicyEventsByType[policyEvent.MappedType].Remove(policyEvent); + // await LoadStatesAsync(); + } + + private async Task UpdatePolicyAsync(MatrixEventResponse evt) { + await Room.SendStateEventAsync(Policy.Type, Policy.StateKey, Policy.RawContent); + // CurrentlyEditingEvent = null; + // await LoadStatesAsync(); + } + + private async Task UpgradePolicyAsync() { + Policy.RawContent["gay.rory.matrixutils.upgraded_from_type"] = Policy.Type; + // await LoadStatesAsync(); + } + + private async Task ConvertToAclAsync() { + if (Policy.RawContent.ContainsKey("entity")) { + var newContent = Policy.ContentAs<ServerPolicyRuleEventContent>(); + newContent!.Entity = newContent.Entity!.Replace("@*:", ""); + await Room.SendStateEventAsync(ServerPolicyRuleEventContent.EventId, newContent.GetDraupnir2StateKey(), newContent); + await Room.SendStateEventAsync(Policy.Type, Policy.StateKey!, new { }); + IsVisible = false; + StateHasChanged(); + } + else { + throw new InvalidOperationException("Policy event must contain an 'entity' field to convert to ACL."); + } + } + + private string Anchor(string anchor) { + return $"{NavigationManager.Uri.Split('#')[0]}#{anchor}"; + } + +} \ No newline at end of file