From 89a14526658e5d061b1aef34ab569e979c9c0cf8 Mon Sep 17 00:00:00 2001 From: Rory& Date: Wed, 6 Aug 2025 03:15:16 +0200 Subject: Various changes, room create/upgrade work --- MatrixUtils.Web/Classes/RmuSessionStore.cs | 2 - .../DefaultRoomCreationTemplate.cs | 8 +- MatrixUtils.Web/MatrixUtils.Web.csproj | 10 +- MatrixUtils.Web/Pages/Dev/DevUtilities.razor | 31 +- MatrixUtils.Web/Pages/Dev/WellKnownRes.razor | 2 +- MatrixUtils.Web/Pages/HSAdmin/HSAdmin.razor | 1 + .../Pages/HSAdmin/HSE/ManageExternalProfiles.razor | 2 +- .../Pages/HSAdmin/Synapse/BlockMedia.razor | 6 +- .../SynapseRoomShutdownWindowContent.razor | 2 +- .../Pages/HSAdmin/Synapse/RoomQuery.razor | 2 +- .../Synapse/SubTools/SynapseRoomStateResync.razor | 2 +- .../Pages/HSAdmin/Synapse/UserList.razor | 4 +- MatrixUtils.Web/Pages/Index.razor | 13 +- MatrixUtils.Web/Pages/InvalidSession.razor | 6 +- .../Client/ClientComponents/ClientRoomList.razor | 2 +- .../Labs/DMSpace/DMSpaceStages/DMSpaceStage1.razor | 4 +- .../Labs/DMSpace/DMSpaceStages/DMSpaceStage2.razor | 6 +- .../Labs/DMSpace/DMSpaceStages/DMSpaceStage3.razor | 2 +- MatrixUtils.Web/Pages/LoginPage.razor | 8 +- MatrixUtils.Web/Pages/Rooms/Create2.razor | 159 +++++ MatrixUtils.Web/Pages/Rooms/PolicyList.razor | 696 ++++++++++----------- MatrixUtils.Web/Pages/Rooms/PolicyList.razor.cs | 144 +++++ MatrixUtils.Web/Pages/Rooms/PolicyList.razor.css | 9 - MatrixUtils.Web/Pages/Rooms/PolicyList2.razor | 12 +- .../PolicyListCategoryComponent.razor | 66 ++ .../PolicyListEditorHeader.razor | 65 ++ .../PolicyListRowComponent.razor | 167 +++++ MatrixUtils.Web/Pages/Rooms/PolicyLists.razor | 160 +++-- MatrixUtils.Web/Pages/Rooms/PolicyLists.razor.css | 6 - .../RoomCreateBasicRoomInfoOptions.razor | 52 ++ .../RoomCreateCreateOptions.razor | 92 +++ .../RoomCreateInitialStateOptions.razor | 52 ++ .../RoomCreateMembershipOptions.razor | 55 ++ .../RoomCreatePermissionsOptions.razor | 123 ++++ .../RoomCreatePrivacyOptions.razor | 70 +++ .../RoomCreateUpgradeOptions.razor | 33 + MatrixUtils.Web/Pages/Rooms/Timeline.razor | 1 + MatrixUtils.Web/Pages/ServerInfo.razor | 1 + MatrixUtils.Web/Pages/Tools/Debug/JoinRoom.razor | 33 +- MatrixUtils.Web/Pages/Tools/Debug/LeaveRoom.razor | 2 +- .../Pages/Tools/Debug/MigrateRoom.razor | 2 +- .../Pages/Tools/Info/SessionCount.razor | 4 +- MatrixUtils.Web/Pages/Tools/InviteCounter.razor | 2 +- MatrixUtils.Web/Pages/Tools/MassCMEBan.razor | 2 +- .../Draupnir/DraupnirProtectedRoomsEditor.razor | 2 +- .../Draupnir/DraupnirProtectionsEditor.razor | 2 +- .../Draupnir/DraupnirWatchedListsEditor.razor | 2 +- .../Pages/Tools/Moderation/FindUsersByRegex.razor | 6 +- .../Pages/Tools/Moderation/InviteCounter.razor | 2 +- .../Pages/Tools/Moderation/MassCMEBan.razor | 2 +- .../Pages/Tools/Moderation/MembershipHistory.razor | 14 +- .../Pages/Tools/Moderation/RoomIntersections.razor | 6 +- .../Pages/Tools/Moderation/UserTrace.razor | 4 +- .../Pages/Tools/Room/DropPowerlevel.razor | 2 +- .../Pages/Tools/Room/SpaceRestrictedJoins.razor | 2 +- .../Pages/Tools/User/CopyPowerlevel.razor | 2 +- .../Pages/Tools/User/MassJoinRoom.razor | 2 +- MatrixUtils.Web/Pages/User/Profile.razor | 6 +- MatrixUtils.Web/Shared/InputLocalPart.razor | 50 ++ .../MassPolicyEditorModal.razor | 23 +- .../PolicyEditorComponents/PolicyEditorModal.razor | 54 +- MatrixUtils.Web/Shared/UserListItem.razor | 18 +- 62 files changed, 1758 insertions(+), 562 deletions(-) create mode 100644 MatrixUtils.Web/Pages/Rooms/Create2.razor create mode 100644 MatrixUtils.Web/Pages/Rooms/PolicyList.razor.cs delete mode 100644 MatrixUtils.Web/Pages/Rooms/PolicyList.razor.css create mode 100644 MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListCategoryComponent.razor create mode 100644 MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListEditorHeader.razor create mode 100644 MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListRowComponent.razor delete mode 100644 MatrixUtils.Web/Pages/Rooms/PolicyLists.razor.css create mode 100644 MatrixUtils.Web/Pages/Rooms/RoomCreateComponents/RoomCreateBasicRoomInfoOptions.razor create mode 100644 MatrixUtils.Web/Pages/Rooms/RoomCreateComponents/RoomCreateCreateOptions.razor create mode 100644 MatrixUtils.Web/Pages/Rooms/RoomCreateComponents/RoomCreateInitialStateOptions.razor create mode 100644 MatrixUtils.Web/Pages/Rooms/RoomCreateComponents/RoomCreateMembershipOptions.razor create mode 100644 MatrixUtils.Web/Pages/Rooms/RoomCreateComponents/RoomCreatePermissionsOptions.razor create mode 100644 MatrixUtils.Web/Pages/Rooms/RoomCreateComponents/RoomCreatePrivacyOptions.razor create mode 100644 MatrixUtils.Web/Pages/Rooms/RoomCreateComponents/RoomCreateUpgradeOptions.razor create mode 100644 MatrixUtils.Web/Shared/InputLocalPart.razor (limited to 'MatrixUtils.Web') diff --git a/MatrixUtils.Web/Classes/RmuSessionStore.cs b/MatrixUtils.Web/Classes/RmuSessionStore.cs index 746f68a..9df8837 100644 --- a/MatrixUtils.Web/Classes/RmuSessionStore.cs +++ b/MatrixUtils.Web/Classes/RmuSessionStore.cs @@ -1,5 +1,3 @@ -using System.Text.Json; -using System.Text.Json.Nodes; using LibMatrix; using LibMatrix.Homeservers; using LibMatrix.Services; diff --git a/MatrixUtils.Web/Classes/RoomCreationTemplates/DefaultRoomCreationTemplate.cs b/MatrixUtils.Web/Classes/RoomCreationTemplates/DefaultRoomCreationTemplate.cs index 7078308..215ad14 100644 --- a/MatrixUtils.Web/Classes/RoomCreationTemplates/DefaultRoomCreationTemplate.cs +++ b/MatrixUtils.Web/Classes/RoomCreationTemplates/DefaultRoomCreationTemplate.cs @@ -82,10 +82,8 @@ public class DefaultRoomCreationTemplate : IRoomCreationTemplate { //TODO: re-implement this } }, - CreationContent = new JsonObject { - { - "type", null - } + CreationContent = new() { + { "type", null } } }; -} +} \ No newline at end of file diff --git a/MatrixUtils.Web/MatrixUtils.Web.csproj b/MatrixUtils.Web/MatrixUtils.Web.csproj index aa9f37f..f418340 100644 --- a/MatrixUtils.Web/MatrixUtils.Web.csproj +++ b/MatrixUtils.Web/MatrixUtils.Web.csproj @@ -39,11 +39,11 @@ - - - - - + + + + + diff --git a/MatrixUtils.Web/Pages/Dev/DevUtilities.razor b/MatrixUtils.Web/Pages/Dev/DevUtilities.razor index 3b2d533..f6392a4 100644 --- a/MatrixUtils.Web/Pages/Dev/DevUtilities.razor +++ b/MatrixUtils.Web/Pages/Dev/DevUtilities.razor @@ -1,5 +1,8 @@ @page "/Dev/Utilities" @using ArcaneLibs.Extensions +@using LibMatrix.EventTypes.Spec.Ephemeral +@using LibMatrix.EventTypes.Spec.State.RoomInfo +@using LibMatrix.Helpers @using MatrixUtils.Abstractions

Debug Tools

@@ -14,7 +17,7 @@ else { Room List @foreach (var roomId in Rooms) { - + } @@ -61,6 +64,7 @@ else { StateHasChanged(); return; } + if (res.Content.Headers.ContentType.MediaType == "application/json") GetRequestResult = $"Error: {res.StatusCode}\n" + (await res.Content.ReadFromJsonAsync()).ToJson(); else @@ -69,7 +73,32 @@ else { catch (Exception e) { GetRequestResult = $"Error: {e}"; } + StateHasChanged(); } + private async Task TestRoomBuilder() { + var rb = new RoomBuilder() { + HistoryVisibility = new RoomHistoryVisibilityEventContent() { HistoryVisibility = RoomHistoryVisibilityEventContent.HistoryVisibilityTypes.Shared }, + ImportantState = [ + new() { + RawContent = new() { + ["type"] = "m.room.name", + ["name"] = "Test Room" + } + }, + new() { + Type = "test", + TypedContent = new PresenceEventContent() { + Presence = "online", + LastActiveAgo = 0, + } + }, + + ] + }; + + await rb.Create(hs); + } + } \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Dev/WellKnownRes.razor b/MatrixUtils.Web/Pages/Dev/WellKnownRes.razor index 1906dd8..c636c56 100644 --- a/MatrixUtils.Web/Pages/Dev/WellKnownRes.razor +++ b/MatrixUtils.Web/Pages/Dev/WellKnownRes.razor @@ -8,7 +8,7 @@

Known Homeserver List


-Room ID: Execute +Room ID: Execute Stats:
Server count: @entries.Count
diff --git a/MatrixUtils.Web/Pages/HSAdmin/HSAdmin.razor b/MatrixUtils.Web/Pages/HSAdmin/HSAdmin.razor index 6a301bf..21b0972 100644 --- a/MatrixUtils.Web/Pages/HSAdmin/HSAdmin.razor +++ b/MatrixUtils.Web/Pages/HSAdmin/HSAdmin.razor @@ -1,5 +1,6 @@ @page "/HSAdmin" @using ArcaneLibs.Extensions +@using LibMatrix.Responses.Federation

Homeserver Admininistration


diff --git a/MatrixUtils.Web/Pages/HSAdmin/HSE/ManageExternalProfiles.razor b/MatrixUtils.Web/Pages/HSAdmin/HSE/ManageExternalProfiles.razor index 87600c6..ec2ec54 100644 --- a/MatrixUtils.Web/Pages/HSAdmin/HSE/ManageExternalProfiles.razor +++ b/MatrixUtils.Web/Pages/HSAdmin/HSE/ManageExternalProfiles.razor @@ -3,7 +3,7 @@ @using LibMatrix.Responses

Manage external profiles

-Add local sessions +Add local sessions @foreach(var p in ExternalProfiles) { diff --git a/MatrixUtils.Web/Pages/HSAdmin/Synapse/BlockMedia.razor b/MatrixUtils.Web/Pages/HSAdmin/Synapse/BlockMedia.razor index d07ff08..594ff35 100644 --- a/MatrixUtils.Web/Pages/HSAdmin/Synapse/BlockMedia.razor +++ b/MatrixUtils.Web/Pages/HSAdmin/Synapse/BlockMedia.razor @@ -24,13 +24,13 @@
@MxcUri?.ToJson(ignoreNull: true)
@if (Event is not null) { - Redact all messages + Redact all messages } @if (Event?.Sender?.Split(':', 2)[1] == Homeserver?.ServerName) {

User is a local user!

- Deactivate User - Quarantine all media + Deactivate User + Quarantine all media } } diff --git a/MatrixUtils.Web/Pages/HSAdmin/Synapse/Components/SynapseRoomShutdownWindowContent.razor b/MatrixUtils.Web/Pages/HSAdmin/Synapse/Components/SynapseRoomShutdownWindowContent.razor index 3b3acac..999e331 100644 --- a/MatrixUtils.Web/Pages/HSAdmin/Synapse/Components/SynapseRoomShutdownWindowContent.razor +++ b/MatrixUtils.Web/Pages/HSAdmin/Synapse/Components/SynapseRoomShutdownWindowContent.razor @@ -50,7 +50,7 @@
- Execute + Execute } @code { diff --git a/MatrixUtils.Web/Pages/HSAdmin/Synapse/RoomQuery.razor b/MatrixUtils.Web/Pages/HSAdmin/Synapse/RoomQuery.razor index 3e38ee2..5e45e5b 100644 --- a/MatrixUtils.Web/Pages/HSAdmin/Synapse/RoomQuery.razor +++ b/MatrixUtils.Web/Pages/HSAdmin/Synapse/RoomQuery.razor @@ -70,7 +70,7 @@ }

- Delete room + Delete room Resync state

diff --git a/MatrixUtils.Web/Pages/HSAdmin/Synapse/SubTools/SynapseRoomStateResync.razor b/MatrixUtils.Web/Pages/HSAdmin/Synapse/SubTools/SynapseRoomStateResync.razor index f3faafa..c2446a2 100644 --- a/MatrixUtils.Web/Pages/HSAdmin/Synapse/SubTools/SynapseRoomStateResync.razor +++ b/MatrixUtils.Web/Pages/HSAdmin/Synapse/SubTools/SynapseRoomStateResync.razor @@ -16,7 +16,7 @@ Via:
- Execute + Execute } @if (Executing) { diff --git a/MatrixUtils.Web/Pages/HSAdmin/Synapse/UserList.razor b/MatrixUtils.Web/Pages/HSAdmin/Synapse/UserList.razor index a2ada30..54ac800 100644 --- a/MatrixUtils.Web/Pages/HSAdmin/Synapse/UserList.razor +++ b/MatrixUtils.Web/Pages/HSAdmin/Synapse/UserList.razor @@ -59,8 +59,8 @@

- Log in - @* Delete room *@ + Log in + @* Delete room *@ @* Resync state *@

diff --git a/MatrixUtils.Web/Pages/Index.razor b/MatrixUtils.Web/Pages/Index.razor index 509c634..82ee0f2 100644 --- a/MatrixUtils.Web/Pages/Index.razor +++ b/MatrixUtils.Web/Pages/Index.razor @@ -4,6 +4,7 @@ @using LibMatrix @using ArcaneLibs @using System.Diagnostics +@using LibMatrix.Responses.Federation Index @@ -58,9 +59,9 @@ Small collection of tools to do not-so-everyday things.

- Manage - Remove - Log out + Manage + Remove + Log out

@@ -89,7 +90,7 @@ Small collection of tools to do not-so-everyday things.

- Remove + Remove } @@ -118,10 +119,10 @@ Small collection of tools to do not-so-everyday things.

- Re-login + Re-login - Remove + Remove } diff --git a/MatrixUtils.Web/Pages/InvalidSession.razor b/MatrixUtils.Web/Pages/InvalidSession.razor index 1ec99f6..f86d112 100644 --- a/MatrixUtils.Web/Pages/InvalidSession.razor +++ b/MatrixUtils.Web/Pages/InvalidSession.razor @@ -8,14 +8,14 @@ @if (_auth is not null) {

It appears that the affected user is @_auth.UserId (@_auth.DeviceId) on @_auth.Homeserver!

- Refresh token - Remove + Refresh token + Remove @if (_showRefreshDialog) {
- Log in + Log in @if (_loginException is not null) {
@_loginException.RawContent
} diff --git a/MatrixUtils.Web/Pages/Labs/Client/ClientComponents/ClientRoomList.razor b/MatrixUtils.Web/Pages/Labs/Client/ClientComponents/ClientRoomList.razor index 8831dd1..56c8cfe 100644 --- a/MatrixUtils.Web/Pages/Labs/Client/ClientComponents/ClientRoomList.razor +++ b/MatrixUtils.Web/Pages/Labs/Client/ClientComponents/ClientRoomList.razor @@ -1,7 +1,7 @@ @using ClientContext = MatrixUtils.Web.Pages.Labs.Client.Index.ClientContext @* user header and room list *@ @foreach (var room in Data.SyncWrapper.Rooms) { - + @room.RoomName
diff --git a/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage1.razor b/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage1.razor index a974a8f..360548d 100644 --- a/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage1.razor +++ b/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage1.razor @@ -28,8 +28,8 @@


- Disband - Next + Disband + Next } else {

Discovering spaces, please wait...

diff --git a/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage2.razor b/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage2.razor index 9f31647..25d1629 100644 --- a/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage2.razor +++ b/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage2.razor @@ -31,7 +31,7 @@ else { }
-Next +Next @{ var _offset = 0; @@ -41,7 +41,7 @@ else {

Found room assigned to multiple users:

Users:

@foreach (var userProfileResponse in usersList) { - + Assign to @@ -54,7 +54,7 @@ else { @foreach (var userProfileResponse in roomMembers[DmToReassign]) { - + Assign to diff --git a/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage3.razor b/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage3.razor index e5a8b22..2bed22e 100644 --- a/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage3.razor +++ b/MatrixUtils.Web/Pages/Labs/DMSpace/DMSpaceStages/DMSpaceStage3.razor @@ -59,7 +59,7 @@ else { }
-Next +Next @code { diff --git a/MatrixUtils.Web/Pages/LoginPage.razor b/MatrixUtils.Web/Pages/LoginPage.razor index 88577a2..38ede74 100644 --- a/MatrixUtils.Web/Pages/LoginPage.razor +++ b/MatrixUtils.Web/Pages/LoginPage.razor @@ -22,8 +22,8 @@
-Add account to queue -Log in +Add account to queue +Log in

@@ -44,7 +44,7 @@
-Add session +Add session

@@ -101,7 +101,7 @@ }
-Log in +Log in @code { diff --git a/MatrixUtils.Web/Pages/Rooms/Create2.razor b/MatrixUtils.Web/Pages/Rooms/Create2.razor new file mode 100644 index 0000000..19f6fbc --- /dev/null +++ b/MatrixUtils.Web/Pages/Rooms/Create2.razor @@ -0,0 +1,159 @@ +@page "/Rooms/Create2" +@using System.Text.Json +@using System.Reflection +@using ArcaneLibs +@using ArcaneLibs.Extensions +@using Blazored.LocalStorage +@using LibMatrix +@using LibMatrix.EventTypes.Spec.State.RoomInfo +@using LibMatrix.Helpers +@using LibMatrix.Responses +@using MatrixUtils.Web.Classes.RoomCreationTemplates +@using MatrixUtils.Web.Pages.Rooms.RoomCreateComponents +@inject ILogger logger +@* @* ReSharper disable once RedundantUsingDirective - Must not remove this, Rider marks this as "unused" when it's not */ *@ + +

Room Manager - Create Room

+ +@if (Ready) { + + + @if (roomBuilder is RoomUpgradeBuilder roomUpgrade) { + + } + else { + @* *@ + @* *@ + @* *@ + @* *@ + } + + + + + + @* Initial states, should remain at bottom *@ +
Preset: *@ + @* @if (Presets is null) { *@ + @*

Presets is null!

*@ + @* } *@ + @* else { *@ + @*

Support for presets is currently disabled!

*@ + @* $1$ #1# *@ + @* $1$ @foreach (var createRoomRequest in Presets) { #1# *@ + @* $1$ #1# *@ + @* $1$ } #1# *@ + @* $1$ #1# *@ + @* } *@ + @*
+ Create room +
+
+
+ RoomBuilder state + + Show null values
+
+                @roomBuilder.ToJson(ignoreNull: !ShowNullInState)
+            
+
+
+} +@if (_matrixException is not null) { + +
+            @_matrixException.Message
+        
+
+} + +@code { + +#region State + + [Parameter, SupplyParameterFromQuery(Name = "previousRoomId")] + public string? PreviousRoomId { get; set; } + + private bool ShowNullInState { get; set; } + + private bool Ready { get; set; } + + private RoomBuilder roomBuilder { get; set; } = new(); + + private AuthenticatedHomeserverGeneric? Homeserver { get; set; } + + private MatrixException? _matrixException { get; set; } + +#endregion + +#region Presets + + private Dictionary? Presets { get; set; } = new(); + // private string RoomPreset { + // get => Presets.ContainsValue(roomBuilder) ? Presets.First(x => x.Value == roomBuilder).Key : "Not a preset"; + // set { + // roomBuilder = Presets[value]; + // JsonChanged(); + // StateHasChanged(); + // } + // } + +#endregion + + protected override async Task OnInitializedAsync() { + Homeserver = await sessionStore.GetCurrentHomeserver(navigateOnFailure: true); + if (Homeserver is null) return; + if (!string.IsNullOrWhiteSpace(PreviousRoomId)) { + roomBuilder = new RoomUpgradeBuilder(Homeserver.GetRoom(PreviousRoomId)); + } + + roomBuilder.ServerAcls.Allow = ["*"]; + roomBuilder.ServerAcls.Deny = []; + + // foreach (var x in Assembly.GetExecutingAssembly().GetTypes().Where(x => x.IsClass && !x.IsAbstract && x.GetInterfaces().Contains(typeof(IRoomCreationTemplate))).ToList()) { + // Console.WriteLine($"Found room creation template in class: {x.FullName}"); + // var instance = (IRoomCreationTemplate)Activator.CreateInstance(x); + // Presets[instance.Name] = instance.CreateRoomRequest; + // } + // + // Presets = Presets.OrderBy(x => x.Key).ToDictionary(x => x.Key, x => x.Value); + + // if (!Presets.ContainsKey("Default")) { + // Console.WriteLine($"No default room found in {Presets.Count} presets: {string.Join(", ", Presets.Keys)}"); + // } + // else RoomPreset = "Default"; + + Ready = true; + StateHasChanged(); + if (roomBuilder is RoomUpgradeBuilder roomUpgrade) { + await roomUpgrade.ImportAsync().ConfigureAwait(false); + StateHasChanged(); + } + } + + protected override bool ShouldRender() { + if (roomBuilder.Type == "") + roomBuilder.Type = null; // Reset to null if empty, so it doesn't get sent as an empty string + var result = base.ShouldRender(); + logger.LogInformation("ShouldRender: " + result); + return result; + } + + private async Task CreateRoom() { + Console.WriteLine("Create room"); + Console.WriteLine(roomBuilder.ToJson()); + roomBuilder.AdditionalCreationContent["gay.rory.created_using"] = "Rory&::MatrixUtils (https://mru.rory.gay)"; + try { + var newRoom = await roomBuilder.Create(Homeserver); + } + catch (MatrixException e) { + _matrixException = e; + } + } + +} diff --git a/MatrixUtils.Web/Pages/Rooms/PolicyList.razor b/MatrixUtils.Web/Pages/Rooms/PolicyList.razor index 96879b8..cdb5894 100644 --- a/MatrixUtils.Web/Pages/Rooms/PolicyList.razor +++ b/MatrixUtils.Web/Pages/Rooms/PolicyList.razor @@ -8,190 +8,118 @@ @using System.Reflection @using ArcaneLibs.Attributes @using LibMatrix.EventTypes -@using LibMatrix.EventTypes.Common @using LibMatrix.EventTypes.Interop.Draupnir @using LibMatrix.EventTypes.Spec.State.RoomInfo - -@using MatrixUtils.Web.Shared.PolicyEditorComponents @using SpawnDev.BlazorJS.WebWorkers +@using MatrixUtils.Web.Pages.Rooms.PolicyListComponents @inject WebWorkerService WebWorkerService +@inject ILogger logger -

Policy list editor - Editing @(RoomName ?? RoomId)

-@if (!string.IsNullOrWhiteSpace(DraupnirShortcode)) { - Shortcode: @DraupnirShortcode -} -@if (!string.IsNullOrWhiteSpace(RoomAlias)) { - Alias: @RoomAlias -} -
-@* *@ -Create new policy - -Create many new policies - -Refresh - -@if (Loading) { -

Loading...

-} -else if (PolicyEventsByType is not { Count: > 0 }) { -

No policies yet

+@if (!IsInitialised) { +

Connecting to homeserver...

} else { - var renderSw = Stopwatch.StartNew(); - var renderTotalSw = Stopwatch.StartNew(); - @foreach (var (type, value) in PolicyEventsByType) { -

- @(GetValidPolicyEventsByType(type).Count) active, - @(GetInvalidPolicyEventsByType(type).Count) invalid, - @(GetRemovedPolicyEventsByType(type).Count) removed - (@value.Count total) - @(GetPolicyTypeName(type).ToLower()) -

- } - - Console.WriteLine($"Rendered header in {renderSw.GetElapsedAndRestart()}"); - - var renderSw2 = Stopwatch.StartNew(); - IOrderedEnumerable policiesByType = KnownPolicyTypes.Where(t => GetPolicyEventsByType(t).Count > 0).OrderByDescending(t => GetPolicyEventsByType(t).Count); - Console.WriteLine($"Ordered policy types by count in {renderSw2.GetElapsedAndRestart()}"); - - foreach (var type in policiesByType) { -
- - - @($"{GetPolicyTypeName(type)}: {GetPolicyEventsByType(type).Count} policies") - -
-
- - @{ - var renderSw3 = Stopwatch.StartNew(); - var policies = GetValidPolicyEventsByType(type); - var invalidPolicies = GetInvalidPolicyEventsByType(type); - // enumerate all properties with friendly name - var props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance) - .Where(x => (x.GetFriendlyNameOrNull() ?? x.GetJsonPropertyNameOrNull()) is not null) - .Where(x => x.GetCustomAttribute() is null) - .ToFrozenSet(); - var propNames = props.Select(x => x.GetFriendlyNameOrNull() ?? x.GetJsonPropertyName()!).ToFrozenSet(); - - var proxySafeProps = type.GetProperties(BindingFlags.Public | BindingFlags.Instance) - .Where(x => props.Any(y => y.Name == x.Name)) - .ToFrozenSet(); - Console.WriteLine($"{proxySafeProps?.Count} proxy safe props found in {policies.FirstOrDefault()?.TypedContent?.GetType()}"); - Console.WriteLine($"Filtered policies and got properties in {renderSw3.GetElapsedAndRestart()}"); - } - - - @foreach (var name in propNames) { - - } - - - - - @foreach (var policy in policies.OrderBy(x => x.RawContent?["entity"]?.GetValue())) { - - @{ - var typedContent = policy.TypedContent! as PolicyRuleEventContent; - } - @foreach (var prop in proxySafeProps ?? Enumerable.Empty()) { - if (prop.Name == "Entity") { - - } - else { - - } - } - - - } - -
@nameActions
@TruncateMxid(typedContent!.Entity)@prop.GetGetMethod()?.Invoke(typedContent, null) -
- @if (PowerLevels.UserHasStatePermission(Homeserver.WhoAmI.UserId, policy.Type)) { - Edit - - Remove - @if (policy.IsLegacyType) { - Update policy type - } - - @if (PolicyTypeIds[typeof(ServerPolicyRuleEventContent)].Contains(policy.Type)) { - Make permanent - - @if (CurrentUserIsDraupnir) { - Kick - users @(ActiveKicks.TryGetValue(policy, out var kick) ? $"({kick})" : null) - - } - } - } - else { -

No permission to modify

- } -
-
-
- - - @("Invalid " + GetPolicyTypeName(type).ToLower()) - - - - - - - - - - - @foreach (var policy in invalidPolicies) { - - - - - } - -
State keyJson contents
@policy.StateKey -
@policy.RawContent.ToJson(true, false)
-
-
-
+ + @if (Loading) { +

Loading...

} + // else if (PolicyEventsByType is not { Count: > 0 }) { + @*

No policies yet

*@ + // } + else { + var renderSw = Stopwatch.StartNew(); + var renderTotalSw = Stopwatch.StartNew(); + @foreach (var value in PolicyCollections.Values.OrderByDescending(x => x.TotalCount)) { +

+ @value.ActivePolicies.Count active, + @value.RemovedPolicies.Count removed + (@value.TotalCount total) + @value.Name.ToLower() +

+ } - Console.WriteLine($"Rendered policies in {renderSw.GetElapsedAndRestart()}"); - Console.WriteLine($"Rendered in {renderTotalSw.Elapsed}"); -} + // logger.LogInformation($"Rendered header in {renderSw.GetElapsedAndRestart()}"); -@if (CurrentlyEditingEvent is not null) { - -} + // var renderSw2 = Stopwatch.StartNew(); + // IOrderedEnumerable policiesByType = KnownPolicyTypes.Where(t => GetPolicyEventsByType(t).Count > 0).OrderByDescending(t => GetPolicyEventsByType(t).Count); + // logger.LogInformation($"Ordered policy types by count in {renderSw2.GetElapsedAndRestart()}"); -@if (ServerPolicyToMakePermanent is not null) { - - - -} + foreach (var collection in PolicyCollections.Values.OrderByDescending(x => x.ActivePolicies.Count)) { + + } -@if (MassCreatePolicies) { - + // foreach (var type in policiesByType) { + @* foreach (var type in (List) []) { *@ + @*
*@ + @* *@ + @* *@ + @* @($"{GetPolicyTypeName(type)}: {GetPolicyEventsByType(type).Count} policies") *@ + @* *@ + @*
*@ + @*
*@ + @* *@ + @* @{ *@ + @* var renderSw3 = Stopwatch.StartNew(); *@ + @* var policies = GetValidPolicyEventsByType(type); *@ + @* var invalidPolicies = GetInvalidPolicyEventsByType(type); *@ + @* // enumerate all properties with friendly name *@ + @* var props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance) *@ + @* .Where(x => (x.GetFriendlyNameOrNull() ?? x.GetJsonPropertyNameOrNull()) is not null) *@ + @* .Where(x => x.GetCustomAttribute() is null) *@ + @* .ToFrozenSet(); *@ + @* var propNames = props.Select(x => x.GetFriendlyNameOrNull() ?? x.GetJsonPropertyName()!).ToFrozenSet(); *@ + @* *@ + @* var proxySafeProps = type.GetProperties(BindingFlags.Public | BindingFlags.Instance) *@ + @* .Where(x => props.Any(y => y.Name == x.Name)) *@ + @* .ToFrozenSet(); *@ + @* logger.LogInformation($"{proxySafeProps?.Count} proxy safe props found in {policies.FirstOrDefault()?.TypedContent?.GetType()}"); *@ + @* logger.LogInformation($"Filtered policies and got properties in {renderSw3.GetElapsedAndRestart()}"); *@ + @* } *@ + @* *@ + @* *@ + @* @foreach (var name in propNames) { *@ + @* *@ + @* } *@ + @* *@ + @* *@ + @* *@ + @* *@ + @* @foreach (var policy in policies.OrderBy(x => x.RawContent?["entity"]?.GetValue())) { *@ + @* *@ + @* } *@ + @* *@ + @*
@nameActions
*@ + @*
*@ + @* *@ + @* *@ + @* @("Invalid " + GetPolicyTypeName(type).ToLower()) *@ + @* *@ + @* *@ + @* *@ + @* *@ + @* *@ + @* *@ + @* *@ + @* *@ + @* *@ + @* *@ + @* @foreach (var policy in invalidPolicies) { *@ + @* *@ + @* *@ + @* *@ + @* *@ + @* } *@ + @* *@ + @*
State keyJson contents
@policy.StateKey *@ + @*
@policy.RawContent.ToJson(true, false)
*@ + @*
*@ + @*
*@ + @*
*@ + // } + + // logger.LogInformation($"Rendered policies in {renderSw.GetElapsedAndRestart()}"); + logger.LogInformation($"Rendered in {renderTotalSw.Elapsed}"); + } } @code { @@ -202,6 +130,7 @@ else { private const bool Debug = false; #endif + private bool IsInitialised { get; set; } = false; private bool Loading { get; set; } = true; [Parameter] @@ -209,14 +138,6 @@ else { private Dictionary> PolicyEventsByType { get; set; } = new(); - private StateEventResponse? CurrentlyEditingEvent { - get; - set { - field = value; - StateHasChanged(); - } - } - public StateEventResponse? ServerPolicyToMakePermanent { get; set { @@ -225,22 +146,12 @@ else { } } - private AuthenticatedHomeserverGeneric Homeserver { get; set; } - private GenericRoom Room { get; set; } - private RoomPowerLevelEventContent PowerLevels { get; set; } + private AuthenticatedHomeserverGeneric Homeserver { get; set; } = null!; + private GenericRoom Room { get; set; } = null!; + private RoomPowerLevelEventContent PowerLevels { get; set; } = null!; public bool CurrentUserIsDraupnir { get; set; } - public string? RoomName { get; set; } - public string? RoomAlias { get; set; } - public string? DraupnirShortcode { get; set; } - public Dictionary ActiveKicks { get; set; } = []; - public bool MassCreatePolicies { - get; - set { - field = value; - StateHasChanged(); - } - } + public Dictionary ActiveKicks { get; set; } = []; protected override async Task OnInitializedAsync() { var sw = Stopwatch.StartNew(); @@ -248,64 +159,234 @@ else { Homeserver = (await sessionStore.GetCurrentHomeserver(navigateOnFailure: true))!; if (Homeserver is null) return; Room = Homeserver.GetRoom(RoomId!); + IsInitialised = true; + StateHasChanged(); await Task.WhenAll( Task.Run(async () => { PowerLevels = (await Room.GetPowerLevelsAsync())!; }), - Task.Run(async () => { DraupnirShortcode = (await Room.GetStateOrNullAsync(MjolnirShortcodeEventContent.EventId))?.Shortcode; }), - Task.Run(async () => { RoomAlias = (await Room.GetCanonicalAliasAsync())?.Alias; }), - Task.Run(async () => { RoomName = await Room.GetNameOrFallbackAsync(); }), Task.Run(async () => { CurrentUserIsDraupnir = (await Homeserver.GetAccountDataOrNullAsync(DraupnirProtectedRoomsData.EventId)) is not null; }) ); StateHasChanged(); - await LoadStatesAsync(); - Console.WriteLine($"Policy list editor initialized in {sw.Elapsed}!"); + await LoadStateAsync(firstLoad: true); + Loading = false; + logger.LogInformation($"Policy list editor initialized in {sw.Elapsed}!"); } - private async Task LoadStatesAsync() { + private async Task LoadStateAsync(bool firstLoad = false) { + var sw = Stopwatch.StartNew(); + // Loading = true; + // var states = Room.GetFullStateAsync(); + var states = await Room.GetFullStateAsListAsync(); + // PolicyEventsByType.Clear(); + + logger.LogInformation($"LoadStatesAsync: Loaded state in {sw.Elapsed}"); + + foreach (var type in KnownPolicyTypes) { + if (!PolicyCollections.ContainsKey(type)) { + var filterPropSw = Stopwatch.StartNew(); + // enumerate all properties with friendly name + var props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(x => (x.GetFriendlyNameOrNull() ?? x.GetJsonPropertyNameOrNull()) is not null) + .Where(x => x.GetCustomAttribute() is null) + .ToFrozenSet(); + + var proxySafeProps = type.GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(x => props.Any(y => y.Name == x.Name)) + .ToFrozenDictionary(x => x.GetFriendlyNameOrNull() ?? x.GetJsonPropertyName(), x => x); + logger.LogInformation($"{proxySafeProps?.Count} proxy safe props found in {type.FullName} ({filterPropSw.Elapsed})"); + PolicyCollections.Add(type, new() { + Name = type.GetFriendlyNamePluralOrNull() ?? type.FullName ?? type.Name, + ActivePolicies = [], + RemovedPolicies = [], + PropertiesToDisplay = proxySafeProps + }); + } + } + + var count = 0; + var parseSw = Stopwatch.StartNew(); + foreach (var evt in states) { + var sw2 = Stopwatch.StartNew(); + var mappedType = evt.MappedType; + logger.LogInformation($"Processing state #{count++:000000} {evt.Type} @ {sw.Elapsed} (took {parseSw.Elapsed:c} so far to process)"); + if (!mappedType.IsAssignableTo(typeof(PolicyRuleEventContent))) continue; + + var collection = PolicyCollections[mappedType]; + + var key = (evt.Type, evt.StateKey!); + var policyInfo = new PolicyCollection.PolicyInfo { + Policy = evt, + MadeRedundantBy = [] + }; + if (evt.RawContent is null or { Count: 0 } || string.IsNullOrWhiteSpace(evt.RawContent?["recommendation"]?.GetValue())) { + collection.ActivePolicies.Remove(key); + if (!collection.RemovedPolicies.TryAdd(key, policyInfo)) { + if (StateEvent.Equals(collection.RemovedPolicies[key].Policy, evt)) continue; + collection.RemovedPolicies[key] = policyInfo; + } + } + else { + collection.RemovedPolicies.Remove(key); + if (!collection.ActivePolicies.TryAdd(key, policyInfo)) { + if (StateEvent.Equals(collection.ActivePolicies[key].Policy, evt)) continue; + collection.ActivePolicies[key] = policyInfo; + } + } + } + + logger.LogInformation($"LoadStatesAsync: Processed state in {sw.Elapsed}"); + foreach (var collection in PolicyCollections) { + logger.LogInformation($"Policy collection {collection.Key.FullName} has {collection.Value.ActivePolicies.Count} active and {collection.Value.RemovedPolicies.Count} removed policies."); + } + + logger.LogInformation("LoadStatesAsync: Scanning for redundant policies..."); + + Loading = false; + var allPolicies = PolicyCollections.Values + .SelectMany(x => x.ActivePolicies.Values) + .Select(x => (x, x.Policy.TypedContent as PolicyRuleEventContent)) + .ToList(); + var wildcardPolicies = allPolicies + .Where(x => x.Item2!.IsGlobRule() || x.Item2 is ServerPolicyRuleEventContent) + .ToList(); + Console.WriteLine($"Got {allPolicies.Count} total policies, {wildcardPolicies.Count} wildcard policies."); + int i = 0; + int hits = 0; + int redundant = 0; + int duplicates = 0; + foreach (var policy in allPolicies) { + if (policy.Item2 is null) continue; + var matchingPolicies = wildcardPolicies + .Where(x => + !StateEvent.TypeKeyPairMatches(policy.x.Policy, x.x.Policy) + && x.Item2!.EntityMatches(policy.Item2.Entity!) + ) + .ToList(); + + if (matchingPolicies.Count > 0) { + logger.LogInformation($"{i} Got {matchingPolicies.Count} hits for {policy.x.Policy.RawContent.ToJson()}: {matchingPolicies.Select(x => x.x.Policy.RawContent).ToJson()}"); + foreach (var match in matchingPolicies) { + policy.x.MadeRedundantBy.Add(match.x.Policy); + } + + hits++; + redundant += matchingPolicies.Count; + + if (hits % 5 == 0) + StateHasChanged(); + } + else logger.LogInformation("Sleeping..."); + await Task.Delay(1); + i++; + } + + i = 0; + foreach (var policy in allPolicies) { + if (policy.Item2 is null) continue; + var matchingPolicies = allPolicies + .Where(x => + !StateEvent.TypeKeyPairMatches(policy.x.Policy, x.x.Policy) + && x.Item2!.Entity == policy.Item2.Entity! + ) + .ToList(); + + if (matchingPolicies.Count > 0) { + logger.LogInformation($"{i} Got {matchingPolicies.Count} duplicates for {policy.x.Policy.RawContent.ToJson()}: {matchingPolicies.Select(x => x.x.Policy.RawContent).ToJson()}"); + foreach (var match in matchingPolicies) { + policy.x.MadeRedundantBy.Add(match.x.Policy); + } + + hits++; + redundant += matchingPolicies.Count; + + if (hits % 5 == 0) + StateHasChanged(); + } + else logger.LogInformation("Sleeping..."); + await Task.Delay(1); + i++; + } + + logger.LogInformation($"LoadStatesAsync: Found {hits} ({redundant} redundant, {duplicates} duplicates) redundant policies in {sw.Elapsed}"); + StateHasChanged(); + } + + // the old one: + private async Task LoadStatesAsync(bool firstLoad = false) { + await LoadStateAsync(firstLoad); + return; var sw = Stopwatch.StartNew(); Loading = true; // var states = Room.GetFullStateAsync(); var states = await Room.GetFullStateAsListAsync(); // PolicyEventsByType.Clear(); - Console.WriteLine($"LoadStatesAsync: Loaded state in {sw.Elapsed}"); + logger.LogInformation($"LoadStatesAsync: Loaded state in {sw.Elapsed}"); - foreach (var state in states) { - if (state is null) continue; - if (!state.MappedType.IsAssignableTo(typeof(PolicyRuleEventContent))) continue; + foreach (var type in KnownPolicyTypes) { + if (!PolicyEventsByType.ContainsKey(type)) + PolicyEventsByType.Add(type, new List + (16000)); + } - if (!PolicyEventsByType.ContainsKey(state.MappedType)) PolicyEventsByType.Add(state.MappedType, new()); + int count = 0; + foreach (var state in states) { + var _spsw = Stopwatch.StartNew(); + TimeSpan e1, e2, e3, e4, e5, e6, t; + if (!state.MappedType.IsAssignableTo(typeof(PolicyRuleEventContent))) continue; + e1 = _spsw.Elapsed; var targetPolicies = PolicyEventsByType[state.MappedType]; + e2 = _spsw.Elapsed; + if (!firstLoad && targetPolicies.FirstOrDefault(x => StateEvent.TypeKeyPairMatches(x, state)) is { } evt) { + e3 = _spsw.Elapsed; + if (StateEvent.Equals(evt, state)) { + if (count % 100 == 0) { + await Task.Delay(10); + await Task.Yield(); + } - if (targetPolicies.FirstOrDefault(x => StateEvent.TypeKeyPairMatches(x, state)) is { } evt) { - if (StateEvent.Equals(evt, state)) continue; + e4 = _spsw.Elapsed; + logger.LogInformation($"[E] LoadStatesAsync: Processed state #{count++:000000} {state.Type} @ {sw.Elapsed} (e1={e1:c}, e2={e2:c}, e3={e3:c}, e4={e4:c}, e5={TimeSpan.Zero:c},t={_spsw.Elapsed:c})"); + continue; + } + + e4 = _spsw.Elapsed; targetPolicies.Remove(evt); + e5 = _spsw.Elapsed; + targetPolicies.Add(state); + e6 = _spsw.Elapsed; + t = _spsw.Elapsed; + logger.LogInformation($"[M] LoadStatesAsync: Processed state #{count++:000000} {state.Type} @ {sw.Elapsed} (e1={e1:c}, e2={e2:c}, e3={e3:c}, e4={e4:c}, e5={e5:c}, e6={e6:c},t={t:c})"); + } + else { targetPolicies.Add(state); + t = _spsw.Elapsed; + logger.LogInformation($"[N] LoadStatesAsync: Processed state #{count++:000000} {state.Type} @ {sw.Elapsed} (e1={e1:c}, e2={e2:c}, e3={TimeSpan.Zero:c}, e4={TimeSpan.Zero:c}, e5={TimeSpan.Zero:c}, e6={TimeSpan.Zero:c}, t={t:c})"); } - else targetPolicies.Add(state); + + // await Task.Delay(10); + // await Task.Yield(); } - Console.WriteLine($"LoadStatesAsync: Processed state in {sw.Elapsed}"); + logger.LogInformation($"LoadStatesAsync: Processed state in {sw.Elapsed}"); Loading = false; StateHasChanged(); await Task.Delay(10); await Task.Yield(); - Console.WriteLine($"LoadStatesAsync: yield finished in {sw.Elapsed}"); + logger.LogInformation($"LoadStatesAsync: yield finished in {sw.Elapsed}"); } - // private List AllPolicies { get; set; } = []; - private List GetPolicyEventsByType(Type type) => PolicyEventsByType.ContainsKey(type) ? PolicyEventsByType[type] : []; - private List GetValidPolicyEventsByType(Type type) => GetPolicyEventsByType(type) - .Where(x => !string.IsNullOrWhiteSpace(x.RawContent?["recommendation"]?.GetValue())).ToList(); - - private List GetInvalidPolicyEventsByType(Type type) => GetPolicyEventsByType(type) - .Where(x => x.RawContent is { Count: > 0 } && string.IsNullOrWhiteSpace(x.RawContent?["recommendation"]?.GetValue())).ToList(); - - private List GetRemovedPolicyEventsByType(Type type) => GetPolicyEventsByType(type) - .Where(x => x.RawContent is null or { Count: 0 }).ToList(); + // private List GetValidPolicyEventsByType(Type type) => GetPolicyEventsByType(type) + // .Where(x => !string.IsNullOrWhiteSpace(x.RawContent?["recommendation"]?.GetValue())).ToList(); + // + // private List GetInvalidPolicyEventsByType(Type type) => GetPolicyEventsByType(type) + // .Where(x => x.RawContent is { Count: > 0 } && string.IsNullOrWhiteSpace(x.RawContent?["recommendation"]?.GetValue())).ToList(); + // + // private List GetRemovedPolicyEventsByType(Type type) => GetPolicyEventsByType(type) + // .Where(x => x.RawContent is null or { Count: 0 }).ToList(); private string? GetPolicyTypeNameOrNull(Type type) => type.GetFriendlyNamePluralOrNull() ?? type.GetCustomAttributes() @@ -313,23 +394,6 @@ else { private string GetPolicyTypeName(Type type) => GetPolicyTypeNameOrNull(type) ?? type.Name; - private async Task RemovePolicyAsync(StateEventResponse policyEvent) { - await Room.SendStateEventAsync(policyEvent.Type, policyEvent.StateKey, new { }); - PolicyEventsByType[policyEvent.MappedType].Remove(policyEvent); - await LoadStatesAsync(); - } - - private async Task UpdatePolicyAsync(StateEventResponse policyEvent) { - await Room.SendStateEventAsync(policyEvent.Type, policyEvent.StateKey, policyEvent.RawContent); - CurrentlyEditingEvent = null; - await LoadStatesAsync(); - } - - private async Task UpgradePolicyAsync(StateEventResponse policyEvent) { - policyEvent.RawContent["gay.rory.matrixutils.upgraded_from_type"] = policyEvent.Type; - await LoadStatesAsync(); - } - private static FrozenSet KnownPolicyTypes = StateEvent.KnownStateEventTypes.Where(x => x.IsAssignableTo(typeof(PolicyRuleEventContent))).ToFrozenSet(); // event types, unnamed @@ -339,154 +403,28 @@ else { private static Dictionary PolicyTypeIds = KnownPolicyTypes .ToDictionary(x => x, x => x.GetCustomAttributes().Select(y => y.EventName).ToArray()); - 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 struct PolicyStats { - public int Active { get; set; } - public int Invalid { get; set; } - public int Removed { get; set; } - } - -#region Draupnir interop - - private SemaphoreSlim ss = new(16, 16); - - private async Task DraupnirKickMatching(StateEventResponse policy) { - try { - var content = policy.TypedContent! as PolicyRuleEventContent; - if (content is null) return; - if (string.IsNullOrWhiteSpace(content.Entity)) return; - - var data = await Homeserver.GetAccountDataAsync(DraupnirProtectedRoomsData.EventId); - var rooms = data.Rooms.Select(Homeserver.GetRoom).ToList(); + Dictionary PolicyCollections { get; set; } = new(); - ActiveKicks.Add(policy, rooms.Count); - StateHasChanged(); - await Task.Delay(500); - - // for (int i = 0; i < 12; i++) { - // _ = WebWorkerService.TaskPool.Invoke(WasteCpu); - // } - - // static async Task runKicks(string roomId, PolicyRuleEventContent content) { - // Console.WriteLine($"Checking {roomId}..."); - // // Console.WriteLine($"Checking {room.RoomId}..."); - // // - // // try { - // // var members = await room.GetMembersListAsync(); - // // foreach (var member in members) { - // // var membership = member.ContentAs(); - // // if (member.StateKey == room.Homeserver.WhoAmI.UserId) continue; - // // if (membership?.Membership is "leave" or "ban") continue; - // // - // // if (content.EntityMatches(member.StateKey!)) - // // // await room.KickAsync(member.StateKey, content.Reason ?? "No reason given"); - // // Console.WriteLine($"Would kick {member.StateKey} from {room.RoomId} (EntityMatches)"); - // // } - // // } - // // finally { - // // Console.WriteLine($"Finished checking {room.RoomId}..."); - // // } - // } - // - // try { - // var tasks = rooms.Select(room => WebWorkerService.TaskPool.Invoke(runKicks, room.RoomId, content)).ToList(); - // - // await Task.WhenAll(tasks); - // } - // catch (Exception e) { - // Console.WriteLine(e); - // } - - await NastyInternalsPleaseIgnore.ExecuteKickWithWasmWorkers(WebWorkerService, Homeserver, policy, data.Rooms); - // await Task.Run(async () => { - // foreach (var room in rooms) { - // try { - // Console.WriteLine($"Checking {room.RoomId}..."); - // var members = await room.GetMembersListAsync(); - // foreach (var member in members) { - // var membership = member.ContentAs(); - // if (member.StateKey == room.Homeserver.WhoAmI.UserId) continue; - // if (membership?.Membership is "leave" or "ban") continue; - // - // if (content.EntityMatches(member.StateKey!)) - // // await room.KickAsync(member.StateKey, content.Reason ?? "No reason given"); - // Console.WriteLine($"Would kick {member.StateKey} from {room.RoomId} (EntityMatches)"); - // } - // ActiveKicks[policy]--; - // StateHasChanged(); - // } - // finally { - // Console.WriteLine($"Finished checking {room.RoomId}..."); - // } - // } - // }); - } - finally { - ActiveKicks.Remove(policy); - StateHasChanged(); - await Task.Delay(500); - } - } + public struct PolicyCollection { + public required string Name { get; init; } + public int TotalCount => ActivePolicies.Count + RemovedPolicies.Count; -#region Nasty, nasty internals, please ignore! + public required Dictionary<(string Type, string StateKey), PolicyInfo> ActivePolicies { get; set; } - private static class NastyInternalsPleaseIgnore { - public static async Task ExecuteKickWithWasmWorkers(WebWorkerService workerService, AuthenticatedHomeserverGeneric hs, StateEventResponse evt, List roomIds) { - try { - // var tasks = roomIds.Select(roomId => workerService.TaskPool.Invoke(ExecuteKickInternal, hs.WellKnownUris.Client, hs.AccessToken, roomId, content.Entity)).ToList(); - var tasks = roomIds.Select(roomId => workerService.TaskPool.Invoke(ExecuteKickInternal2, hs.WellKnownUris, hs.AccessToken, roomId, evt)).ToList(); - // workerService.TaskPool.Invoke(ExecuteKickInternal, hs.BaseUrl, hs.AccessToken, roomIds, content.Entity); - await Task.WhenAll(tasks); - } - catch (Exception e) { - Console.WriteLine(e); - } - } - - private static async Task ExecuteKickInternal(string homeserverBaseUrl, string accessToken, string roomId, string entity) { - try { - Console.WriteLine("args: " + string.Join(", ", homeserverBaseUrl, accessToken, roomId, entity)); - Console.WriteLine($"Checking {roomId}..."); - var hs = new AuthenticatedHomeserverGeneric(homeserverBaseUrl, new() { Client = homeserverBaseUrl }, null, accessToken); - Console.WriteLine($"Got HS..."); - var room = hs.GetRoom(roomId); - Console.WriteLine($"Got room..."); - var members = await room.GetMembersListAsync(); - Console.WriteLine($"Got members..."); - // foreach (var member in members) { - // var membership = member.ContentAs(); - // if (member.StateKey == hs.WhoAmI.UserId) continue; - // if (membership?.Membership is "leave" or "ban") continue; - // - // if (entity == member.StateKey) - // // await room.KickAsync(member.StateKey, content.Reason ?? "No reason given"); - // Console.WriteLine($"Would kick {member.StateKey} from {room.RoomId} (EntityMatches)"); - // } - } - catch (Exception e) { - Console.WriteLine(e); - } - } + // public Dictionary<(string Type, string StateKey), StateEventResponse> InvalidPolicies { get; set; } + public required Dictionary<(string Type, string StateKey), PolicyInfo> RemovedPolicies { get; set; } + public required FrozenDictionary PropertiesToDisplay { get; set; } - private async static Task ExecuteKickInternal2(HomeserverResolverService.WellKnownUris wellKnownUris, string accessToken, string roomId, StateEventResponse policy) { - Console.WriteLine($"Checking {roomId}..."); - Console.WriteLine(policy.EventId); + public struct PolicyInfo { + public required StateEventResponse Policy { get; init; } + public required List MadeRedundantBy { get; set; } } } -#endregion - -#endregion + // private struct PolicyStats { + // public int Active { get; set; } + // public int Invalid { get; set; } + // public int Removed { get; set; } + // } } \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Rooms/PolicyList.razor.cs b/MatrixUtils.Web/Pages/Rooms/PolicyList.razor.cs new file mode 100644 index 0000000..0106c6e --- /dev/null +++ b/MatrixUtils.Web/Pages/Rooms/PolicyList.razor.cs @@ -0,0 +1,144 @@ +using LibMatrix; +using LibMatrix.EventTypes.Interop.Draupnir; +using LibMatrix.EventTypes.Spec.State.Policy; +using LibMatrix.Homeservers; +using LibMatrix.Services; +using SpawnDev.BlazorJS.WebWorkers; + +namespace MatrixUtils.Web.Pages.Rooms; + +public partial class PolicyList { + +#region Draupnir interop + + private SemaphoreSlim ss = new(16, 16); + + private async Task DraupnirKickMatching(StateEventResponse policy) { + try { + var content = policy.TypedContent! as PolicyRuleEventContent; + if (content is null) return; + if (string.IsNullOrWhiteSpace(content.Entity)) return; + + var data = await Homeserver.GetAccountDataAsync(DraupnirProtectedRoomsData.EventId); + var rooms = data.Rooms.Select(Homeserver.GetRoom).ToList(); + + ActiveKicks.Add(policy, rooms.Count); + StateHasChanged(); + await Task.Delay(500); + + // for (int i = 0; i < 12; i++) { + // _ = WebWorkerService.TaskPool.Invoke(WasteCpu); + // } + + // static async Task runKicks(string roomId, PolicyRuleEventContent content) { + // Console.WriteLine($"Checking {roomId}..."); + // // Console.WriteLine($"Checking {room.RoomId}..."); + // // + // // try { + // // var members = await room.GetMembersListAsync(); + // // foreach (var member in members) { + // // var membership = member.ContentAs(); + // // if (member.StateKey == room.Homeserver.WhoAmI.UserId) continue; + // // if (membership?.Membership is "leave" or "ban") continue; + // // + // // if (content.EntityMatches(member.StateKey!)) + // // // await room.KickAsync(member.StateKey, content.Reason ?? "No reason given"); + // // Console.WriteLine($"Would kick {member.StateKey} from {room.RoomId} (EntityMatches)"); + // // } + // // } + // // finally { + // // Console.WriteLine($"Finished checking {room.RoomId}..."); + // // } + // } + // + // try { + // var tasks = rooms.Select(room => WebWorkerService.TaskPool.Invoke(runKicks, room.RoomId, content)).ToList(); + // + // await Task.WhenAll(tasks); + // } + // catch (Exception e) { + // Console.WriteLine(e); + // } + + await NastyInternalsPleaseIgnore.ExecuteKickWithWasmWorkers(WebWorkerService, Homeserver, policy, data.Rooms); + // await Task.Run(async () => { + // foreach (var room in rooms) { + // try { + // Console.WriteLine($"Checking {room.RoomId}..."); + // var members = await room.GetMembersListAsync(); + // foreach (var member in members) { + // var membership = member.ContentAs(); + // if (member.StateKey == room.Homeserver.WhoAmI.UserId) continue; + // if (membership?.Membership is "leave" or "ban") continue; + // + // if (content.EntityMatches(member.StateKey!)) + // // await room.KickAsync(member.StateKey, content.Reason ?? "No reason given"); + // Console.WriteLine($"Would kick {member.StateKey} from {room.RoomId} (EntityMatches)"); + // } + // ActiveKicks[policy]--; + // StateHasChanged(); + // } + // finally { + // Console.WriteLine($"Finished checking {room.RoomId}..."); + // } + // } + // }); + } + finally { + ActiveKicks.Remove(policy); + StateHasChanged(); + await Task.Delay(500); + } + } + +#region Nasty, nasty internals, please ignore! + + private static class NastyInternalsPleaseIgnore { + public static async Task ExecuteKickWithWasmWorkers(WebWorkerService workerService, AuthenticatedHomeserverGeneric hs, StateEventResponse evt, List roomIds) { + try { + // var tasks = roomIds.Select(roomId => workerService.TaskPool.Invoke(ExecuteKickInternal, hs.WellKnownUris.Client, hs.AccessToken, roomId, content.Entity)).ToList(); + var tasks = roomIds.Select(roomId => workerService.TaskPool.Invoke(ExecuteKickInternal2, hs.WellKnownUris, hs.AccessToken, roomId, evt)).ToList(); + // workerService.TaskPool.Invoke(ExecuteKickInternal, hs.BaseUrl, hs.AccessToken, roomIds, content.Entity); + await Task.WhenAll(tasks); + } + catch (Exception e) { + Console.WriteLine(e); + } + } + + private static async Task ExecuteKickInternal(string homeserverBaseUrl, string accessToken, string roomId, string entity) { + try { + Console.WriteLine("args: " + string.Join(", ", homeserverBaseUrl, accessToken, roomId, entity)); + Console.WriteLine($"Checking {roomId}..."); + var hs = new AuthenticatedHomeserverGeneric(homeserverBaseUrl, new() { Client = homeserverBaseUrl }, null, accessToken); + Console.WriteLine($"Got HS..."); + var room = hs.GetRoom(roomId); + Console.WriteLine($"Got room..."); + var members = await room.GetMembersListAsync(); + Console.WriteLine($"Got members..."); + // foreach (var member in members) { + // var membership = member.ContentAs(); + // if (member.StateKey == hs.WhoAmI.UserId) continue; + // if (membership?.Membership is "leave" or "ban") continue; + // + // if (entity == member.StateKey) + // // await room.KickAsync(member.StateKey, content.Reason ?? "No reason given"); + // Console.WriteLine($"Would kick {member.StateKey} from {room.RoomId} (EntityMatches)"); + // } + } + catch (Exception e) { + Console.WriteLine(e); + } + } + + private async static Task ExecuteKickInternal2(HomeserverResolverService.WellKnownUris wellKnownUris, string accessToken, string roomId, StateEventResponse policy) { + Console.WriteLine($"Checking {roomId}..."); + Console.WriteLine(policy.EventId); + } + } + +#endregion + +#endregion + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Rooms/PolicyList.razor.css b/MatrixUtils.Web/Pages/Rooms/PolicyList.razor.css deleted file mode 100644 index afe9fb0..0000000 --- a/MatrixUtils.Web/Pages/Rooms/PolicyList.razor.css +++ /dev/null @@ -1,9 +0,0 @@ -th { - border-width: 1px; -} - -table { - width: fit-content; - border-width: 1px; - vertical-align: middle; -} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Rooms/PolicyList2.razor b/MatrixUtils.Web/Pages/Rooms/PolicyList2.razor index 982fc5a..5d5bb5d 100644 --- a/MatrixUtils.Web/Pages/Rooms/PolicyList2.razor +++ b/MatrixUtils.Web/Pages/Rooms/PolicyList2.razor @@ -15,7 +15,7 @@

Policy list editor - Editing @RoomId


@* *@ -Create new policy +Create new policy @if (Loading) {

Loading...

@@ -71,16 +71,16 @@ else { }
@if (PowerLevels.UserHasStatePermission(Homeserver.WhoAmI.UserId, policy.Type)) { - Edit - Remove + Edit + Remove @if (policy.IsLegacyType) { - Update policy type + Update policy type } @if (PolicyTypeIds[typeof(ServerPolicyRuleEventContent)].Contains(policy.EventId)) { - Make permanent (wildcard) + Make permanent (wildcard) @if (CurrentUserIsDraupnir) { - Kick matching users + Kick matching users } } else { diff --git a/MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListCategoryComponent.razor b/MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListCategoryComponent.razor new file mode 100644 index 0000000..f818b62 --- /dev/null +++ b/MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListCategoryComponent.razor @@ -0,0 +1,66 @@ +@using ArcaneLibs.Extensions +@using LibMatrix.RoomTypes +
+ + + @($"{PolicyCollection.Name}: {PolicyCollection.TotalCount} policies") + +
+
+ + + + @foreach (var name in PolicyCollection.PropertiesToDisplay!.Keys) { + + } + + + + + @foreach (var policy in PolicyCollection.ActivePolicies.Values.OrderBy(x => x.Policy.RawContent?["entity"]?.GetValue())) { + + } + +
@nameActions
+
+ + + @("Invalid " + PolicyCollection.Name.ToLower()) + + + + + + + + + + + @foreach (var policy in PolicyCollection.RemovedPolicies.Values) { + + + + + } + +
State keyJson contents
@policy.Policy.StateKey +
@policy.Policy.RawContent.ToJson(true, false)
+
+
+
+ +@code { + + [Parameter] + public required PolicyList.PolicyCollection PolicyCollection { get; set; } + + [Parameter] + public required GenericRoom Room { get; set; } + + 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..e82f17d --- /dev/null +++ b/MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListEditorHeader.razor @@ -0,0 +1,65 @@ +@using LibMatrix +@using LibMatrix.EventTypes.Common +@using LibMatrix.RoomTypes +@using MatrixUtils.Web.Shared.PolicyEditorComponents +

Policy list editor - Editing @(RoomName ?? Room.RoomId)

+@if (!string.IsNullOrWhiteSpace(DraupnirShortcode)) { + Shortcode: @DraupnirShortcode +} +@if (!string.IsNullOrWhiteSpace(RoomAlias)) { + Alias: @RoomAlias +} +
+@* *@ +Create new policy + +Create many new policies + +Refresh + +@if (CurrentlyEditingEvent is not null) { + +} + +@if (MassCreatePolicies) { + +} + +@code { + [Parameter] + public required GenericRoom Room { get; set; } + + [Parameter] + public required Func ReloadStateAsync { get; set; } + + private string? RoomName { get; set; } + private string? RoomAlias { get; set; } + private string? DraupnirShortcode { get; set; } + + private StateEventResponse? 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.EventId))?.Shortcode; }), + Task.Run(async () => { RoomAlias = (await Room.GetCanonicalAliasAsync())?.Alias; }), + Task.Run(async () => { RoomName = await Room.GetNameOrFallbackAsync(); }) + ); + + StateHasChanged(); + } + + private async Task UpdatePolicyAsync(StateEventResponse 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..11de82c --- /dev/null +++ b/MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListRowComponent.razor @@ -0,0 +1,167 @@ +@using System.Reflection +@using LibMatrix +@using LibMatrix.EventTypes.Spec.State.Policy +@using LibMatrix.RoomTypes +@using MatrixUtils.Web.Shared.PolicyEditorComponents + +@if (_isInitialized && IsVisible) { + + @foreach (var prop in PolicyCollection.PropertiesToDisplay.Values) { + if (prop.Name == "Entity") { + + @TruncateMxid(TypedContent.Entity) + @foreach (var dup in PolicyInfo.MadeRedundantBy) { +
+ Also matched by @dup.FriendlyTypeName.ToLower() @TruncateMxid(dup.RawContent["entity"].GetValue()) + } + + } + else { + @prop.GetGetMethod()?.Invoke(TypedContent, null) + } + } + +
+ @* @if (PowerLevels.UserHasStatePermission(Homeserver.WhoAmI.UserId, Policy.Type)) { *@ + @if (true) { + Edit + + Remove + @if (Policy.IsLegacyType) { + Update policy type + } + + @if (TypedContent.Entity?.StartsWith("@*:", StringComparison.Ordinal) == true) { + Convert to ACL + } + + @* @if (PolicyTypeIds[typeof(ServerPolicyRuleEventContent)].Contains(Policy.Type)) { *@ + @* Make permanent *@ + @* *@ + @* @if (CurrentUserIsDraupnir) { *@ + @* Kick *@ + @* users @(ActiveKicks.TryGetValue(Policy, out var kick) ? $"({kick})" : null) *@ + @* *@ + @* } *@ + // } + } + else { +

No permission to modify

+ } +
+ + + + @if (IsEditing) { + + } + @* TODO: Implement ability to turn ACLs into wildcards *@ + @*@if (ServerPolicyToMakePermanent is not null) { + + + + }*@ +} + + + +@code { + + [Parameter] + public PolicyList.PolicyCollection.PolicyInfo PolicyInfo { get; set; } + + [Parameter] + public GenericRoom Room { get; set; } = null!; + + [Parameter] + public required PolicyList.PolicyCollection PolicyCollection { get; set; } + + private StateEventResponse 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 { }); + IsVisible = false; + StateHasChanged(); + // PolicyEventsByType[policyEvent.MappedType].Remove(policyEvent); + // await LoadStatesAsync(); + } + + private async Task UpdatePolicyAsync(StateEventResponse 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(); + 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 diff --git a/MatrixUtils.Web/Pages/Rooms/PolicyLists.razor b/MatrixUtils.Web/Pages/Rooms/PolicyLists.razor index 2903ab8..6df56ba 100644 --- a/MatrixUtils.Web/Pages/Rooms/PolicyLists.razor +++ b/MatrixUtils.Web/Pages/Rooms/PolicyLists.razor @@ -1,12 +1,24 @@ @page "/PolicyLists" +@using ArcaneLibs @using ArcaneLibs.Extensions @using LibMatrix @using LibMatrix.EventTypes @using LibMatrix.EventTypes.Common @using LibMatrix.EventTypes.Spec.State.Policy +@using LibMatrix.Helpers +@using LibMatrix.Responses @using LibMatrix.RoomTypes @inject ILogger logger -

Policy lists

@* Create new policy list *@ +

+ Policy lists + + + +

+ @if (!string.IsNullOrWhiteSpace(Status)) {

@Status

@@ -16,22 +28,17 @@ }
- +
- + @foreach (var room in Rooms.OrderByDescending(x => x.PolicyCounts.Sum(y => y.Value))) { - + }
Room name Policies
- - - - @room.RoomName @if (room.IsLegacy) { @@ -50,11 +57,44 @@ @(room.PolicyCounts.GetValueOrDefault(RoomInfo.PolicyType.Server) ?? 0) server policies
@(room.PolicyCounts.GetValueOrDefault(RoomInfo.PolicyType.Room) ?? 0) room policies
+ + + +
+@if (ShowPolicyListCreationWindow && Homeserver != null) { + + @if (!string.IsNullOrWhiteSpace(_roomBuilder.Avatar.Url)) { + + } + else { + + } +
+ +
+ # + + :@Homeserver!.ServerName +
+ + +
+
+ + Bot shortcode: + +
+ Create + +
+} + @code { private List Rooms { get; } = []; @@ -65,44 +105,39 @@ Homeserver = await sessionStore.GetCurrentHomeserver(navigateOnFailure: true); if (Homeserver is null) return; + isLoading = true; Status = "Fetching rooms..."; - - var userEventTypes = EventContent.GetMatchingEventTypes(); - var serverEventTypes = EventContent.GetMatchingEventTypes(); - var roomEventTypes = EventContent.GetMatchingEventTypes(); - var knownPolicyTypes = (List) [..userEventTypes, ..serverEventTypes, ..roomEventTypes]; - - List roomsByType = []; + List _tasks = []; await foreach (var room in Homeserver.GetJoinedRoomsByType("support.feline.policy.lists.msc.v1")) { - roomsByType.Add(room); + // roomsByType.Add(room); Status2 = $"Found {room.RoomId} (MSC3784)..."; + _tasks.Add(Task.Run(async () => { + Rooms.Add(await RoomInfo.FromRoom(room)); + StateHasChanged(); + })); } - List> tasks = roomsByType.Select(async room => { - Status2 = $"Fetching room {room.RoomId}..."; - return await RoomInfo.FromRoom(room); - }).ToList(); + await Task.WhenAll(_tasks); - var results = tasks.ToAsyncEnumerable(); - await foreach (var result in results) { - Rooms.Add(result); - StateHasChanged(); - } + isLoading = false; + Status = ""; + Status2 = ""; + } + private async Task ScanLegacyLists() { + isLoading = true; Status = "Searching for legacy lists..."; - var rooms = (await Homeserver.GetJoinedRooms()) .Where(x => !Rooms.Any(y => y.Room.RoomId == x.RoomId)) .Select(async room => { var state = await room.GetFullStateAsListAsync(); var policies = state - .Where(x => knownPolicyTypes.Contains(x.Type)) + .Where(x => PolicyRoom.SpecPolicyEventTypes.Contains(x.Type)) .ToList(); if (policies.Count == 0) return null; Status2 = $"Found legacy list {room.RoomId}..."; return await RoomInfo.FromRoom(room, state, true); - }) - .ToAsyncEnumerable(); + }).ToAsyncEnumerable(); await foreach (var room in rooms) { if (room is not null) { @@ -110,32 +145,36 @@ StateHasChanged(); } } - + + isLoading = false; Status = ""; Status2 = ""; - await base.OnInitializedAsync(); } - private string _status; - - public string Status { - get => _status; + private string? Status { + get; set { - _status = value; + field = value; StateHasChanged(); } } - private string _status2; - - public string Status2 { - get => _status2; + private string? Status2 { + get; set { - _status2 = value; + field = value; StateHasChanged(); } } + private bool ShowPolicyListCreationWindow { + get; + set { + field = value; + StateHasChanged(); + } + } = true; + private class RoomInfo { public GenericRoom Room { get; set; } public string RoomName { get; set; } @@ -149,11 +188,6 @@ Server } - private static readonly List userEventTypes = EventContent.GetMatchingEventTypes(); - private static readonly List serverEventTypes = EventContent.GetMatchingEventTypes(); - private static readonly List roomEventTypes = EventContent.GetMatchingEventTypes(); - private static readonly List allKnownPolicyTypes = [..userEventTypes, ..serverEventTypes, ..roomEventTypes]; - public static async Task FromRoom(GenericRoom room, List? state = null, bool legacy = false) { state ??= await room.GetFullStateAsListAsync(); return new RoomInfo() { @@ -165,12 +199,40 @@ ?? room.RoomId, Shortcode = (await room.GetStateOrNullAsync(MjolnirShortcodeEventContent.EventId))?.Shortcode, PolicyCounts = new() { - { PolicyType.User, state.Count(x => userEventTypes.Contains(x.Type)) }, - { PolicyType.Server, state.Count(x => serverEventTypes.Contains(x.Type)) }, - { PolicyType.Room, state.Count(x => roomEventTypes.Contains(x.Type)) } + { PolicyType.User, state.Count(x => PolicyRoom.UserPolicyEventTypes.Contains(x.Type)) }, + { PolicyType.Server, state.Count(x => PolicyRoom.ServerPolicyEventTypes.Contains(x.Type)) }, + { PolicyType.Room, state.Count(x => PolicyRoom.RoomPolicyEventTypes.Contains(x.Type)) } } }; } } + private readonly RoomBuilder _roomBuilder = new() { + Type = "support.feline.policy.lists.msc.v1", + Name = new() { Name = "New policy list" }, + AliasLocalPart = "policies" + }; + + private readonly MjolnirShortcodeEventContent _shortcodeEvent = new() { + Shortcode = "policy-list" + }; + + private bool isLoading = true; + + private static readonly SvgIdenticonGenerator IdenticonGenerator = new(); + + private async Task RoomIconFilePicked(InputFileChangeEventArgs obj) { + var res = await Homeserver!.UploadFile(obj.File.Name, obj.File.OpenReadStream(), obj.File.ContentType); + Console.WriteLine(res); + _roomBuilder.Avatar.Url = res; + StateHasChanged(); + } + + private async Task CreatePolicyList() { + var room = await _roomBuilder.Create(Homeserver!); + Status = $"Created policy list {room.RoomId} ({room.GetNameAsync()})"; + await room.SendStateEventAsync(MjolnirShortcodeEventContent.EventId, _shortcodeEvent); + NavigationManager.NavigateTo($"/Rooms/{room.RoomId}/Policies"); + } + } \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Rooms/PolicyLists.razor.css b/MatrixUtils.Web/Pages/Rooms/PolicyLists.razor.css deleted file mode 100644 index f9b5b3f..0000000 --- a/MatrixUtils.Web/Pages/Rooms/PolicyLists.razor.css +++ /dev/null @@ -1,6 +0,0 @@ -table, th, td { - border-width: 1px; -} -td { - padding: 8px; -} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Rooms/RoomCreateComponents/RoomCreateBasicRoomInfoOptions.razor b/MatrixUtils.Web/Pages/Rooms/RoomCreateComponents/RoomCreateBasicRoomInfoOptions.razor new file mode 100644 index 0000000..c1ee202 --- /dev/null +++ b/MatrixUtils.Web/Pages/Rooms/RoomCreateComponents/RoomCreateBasicRoomInfoOptions.razor @@ -0,0 +1,52 @@ +@using ArcaneLibs +@using LibMatrix.Helpers + + Room name: + + + + + + Room alias: + + + + + + Room icon: + + @if (!string.IsNullOrWhiteSpace(roomBuilder.Avatar.Url)) { + + } + else { + + } +
+ +
+ +
+ + + +@code { + + [Parameter] + public required RoomBuilder roomBuilder { get; set; } + + [Parameter] + public required Action PageStateHasChanged { get; set; } + + [Parameter] + public AuthenticatedHomeserverGeneric Homeserver { get; set; } + + private static readonly SvgIdenticonGenerator IdenticonGenerator = new(); + + private async Task RoomIconFilePicked(InputFileChangeEventArgs obj) { + var res = await Homeserver.UploadFile(obj.File.Name, obj.File.OpenReadStream(), obj.File.ContentType); + Console.WriteLine(res); + roomBuilder.Avatar.Url = res; + PageStateHasChanged(); + } + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Rooms/RoomCreateComponents/RoomCreateCreateOptions.razor b/MatrixUtils.Web/Pages/Rooms/RoomCreateComponents/RoomCreateCreateOptions.razor new file mode 100644 index 0000000..3f4a73d --- /dev/null +++ b/MatrixUtils.Web/Pages/Rooms/RoomCreateComponents/RoomCreateCreateOptions.razor @@ -0,0 +1,92 @@ +@using Blazored.LocalStorage +@using LibMatrix.Helpers +@inject ILocalStorageService LocalStorage + + Room type: + + @if (RoomTypes.ContainsKey(roomBuilder.Type ?? "")) { + + @foreach (var type in RoomTypes) { + + } + + + } + else { + + } + + version + @if (Capabilities is null) { + Loading... + } + else { + + @foreach (var version in Capabilities.Capabilities.RoomVersions!.Available!) { + + } + + } + + + + Allow attribution: + + + Allow attribution to Rory&::MatrixUtils + ? + + + +@if (ShowAttributionInfo) { + + This will add the following to the room creation content: +
+
{ "gay.rory.created_using": "Rory&::MatrixUtils (https://mru.rory.gay)" }
+ This is not visible to users unless they manually inspect the room's create event source. +
+} + +@code { + + [Parameter] + public required RoomBuilder roomBuilder { get; set; } + + [Parameter] + public required Action PageStateHasChanged { get; set; } + + [Parameter] + public AuthenticatedHomeserverGeneric Homeserver { get; set; } + + private AuthenticatedHomeserverGeneric.CapabilitiesResponse? Capabilities { get; set; } + + private bool ShowAttributionInfo { + get; + set { + field = value; + StateHasChanged(); + } + } + + private bool AllowAttribution { + get; + set { + field = value; + _ = LocalStorage.SetItemAsync("rmu.room_create.allow_attribution", value); + } + } = true; + + protected override async Task OnInitializedAsync() { + Capabilities = await Homeserver.GetCapabilitiesAsync(); + roomBuilder.Version = Capabilities.Capabilities.RoomVersions!.Default; + AllowAttribution = await LocalStorage.GetItemAsync("rmu.room_create.allow_attribution") ?? true; + } + + private static Dictionary RoomTypes { get; } = new() { + { "", "Room" }, + { "m.space", "Space" }, + { "support.feline.policy.lists.msc.v1", "Policy list" } + }; + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Rooms/RoomCreateComponents/RoomCreateInitialStateOptions.razor b/MatrixUtils.Web/Pages/Rooms/RoomCreateComponents/RoomCreateInitialStateOptions.razor new file mode 100644 index 0000000..272bd8b --- /dev/null +++ b/MatrixUtils.Web/Pages/Rooms/RoomCreateComponents/RoomCreateInitialStateOptions.razor @@ -0,0 +1,52 @@ +@using System.Text.Json +@using LibMatrix +@using LibMatrix.Helpers + + Initial room state: + + @foreach (var (displayName, events) in new Dictionary>() { + { "Important room state (before final access rules)", roomBuilder.ImportantState }, + { "Additional room state (after final access rules)", roomBuilder.InitialState }, + }) { +
+ + @code + { + // private static readonly string[] ImplementedStates = { "m.room.avatar", "m.room.history_visibility", "m.room.guest_access", "m.room.server_acl" }; + } + + @* @displayName: @events.Count(x => !ImplementedStates.Contains(x.Type)) events *@ + @displayName: @events.Count events + + @* @foreach (var initialState in events.Where(x => !ImplementedStates.Contains(x.Type))) { *@ + @foreach (var initialState in events) { + + + + + } +
+ @(initialState.Type): + @if (!string.IsNullOrEmpty(initialState.StateKey)) { +
+ (@initialState.StateKey) + } + +
+
@JsonSerializer.Serialize(initialState.RawContent, new JsonSerializerOptions { WriteIndented = true })
+
+
+ } + + + +@code { + [Parameter] + public required RoomBuilder roomBuilder { get; set; } + + [Parameter] + public required Action PageStateHasChanged { get; set; } + + [Parameter] + public AuthenticatedHomeserverGeneric Homeserver { get; set; } +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Rooms/RoomCreateComponents/RoomCreateMembershipOptions.razor b/MatrixUtils.Web/Pages/Rooms/RoomCreateComponents/RoomCreateMembershipOptions.razor new file mode 100644 index 0000000..5170d2d --- /dev/null +++ b/MatrixUtils.Web/Pages/Rooms/RoomCreateComponents/RoomCreateMembershipOptions.razor @@ -0,0 +1,55 @@ +@using ArcaneLibs.Extensions +@using LibMatrix.Helpers + + Invited members: + +
+ @roomBuilder.Invites.Count members + Invite all logged in accounts + @foreach (var member in roomBuilder.Invites) { + + : + + } +
+ + + + Banned members: + +
+ @roomBuilder.Bans.Count members + @foreach (var member in roomBuilder.Bans) { + + : + + } +
+ + + +@code { + + [Parameter] + public required RoomBuilder roomBuilder { get; set; } + + [Parameter] + public required Action PageStateHasChanged { get; set; } + + [Parameter] + public AuthenticatedHomeserverGeneric Homeserver { get; set; } + + private async Task InviteAllSessions() { + var sessions = await sessionStore.GetAllSessions(); + foreach (var session in sessions) { + if (roomBuilder.Invites.ContainsKey(session.Value.Auth.UserId) || session.Value.Auth.UserId == Homeserver!.WhoAmI.UserId) continue; + Console.WriteLine("Inviting " + session.Value.Auth.UserId); + roomBuilder.Invites.Add(session.Value.Auth.UserId, null); + Console.WriteLine("--"); + } + + Console.WriteLine("Got all sessions, invited: " + string.Join(", ", roomBuilder.Invites.Keys)); + StateHasChanged(); + } + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Rooms/RoomCreateComponents/RoomCreatePermissionsOptions.razor b/MatrixUtils.Web/Pages/Rooms/RoomCreateComponents/RoomCreatePermissionsOptions.razor new file mode 100644 index 0000000..ba28b82 --- /dev/null +++ b/MatrixUtils.Web/Pages/Rooms/RoomCreateComponents/RoomCreatePermissionsOptions.razor @@ -0,0 +1,123 @@ +@using ArcaneLibs.Extensions +@using LibMatrix.Helpers + + Permissions: +
+ + @if (roomBuilder.Version is "org.matrix.hydra.11" or "12") { + @(roomBuilder.AdditionalCreators.Count + 1) creators, + } + @roomBuilder.PowerLevels.Users.Count members, @roomBuilder.PowerLevels.Events.Count events + + + @if (roomBuilder.Version is "org.matrix.hydra.11" or "12") { + Creators: +
+ @Homeserver.WhoAmI.UserId (you - to change, visit the homepage.) +
+ + +
+ } + + Events:
+ @foreach (var eventType in roomBuilder.PowerLevels.Events.Keys) { + var _event = eventType; + + + - + +
+ + + : +
+ + + + + + } + + + + + + + + + Users:
+ @foreach (var user in roomBuilder.PowerLevels.Users.Keys) { + var _user = user; + + + - + +
+ + + : +
+ + + + + + } + + + + + + + +
+ + + +@code { + + [Parameter] + public required RoomBuilder roomBuilder { get; set; } + + [Parameter] + public required Action PageStateHasChanged { get; set; } + + [Parameter] + public AuthenticatedHomeserverGeneric Homeserver { get; set; } + + private string GetPermissionFriendlyName(string key) => key switch { + "m.reaction" => "Send reaction", + "m.room.avatar" => "Change room icon", + "m.room.canonical_alias" => "Change room alias", + "m.room.encryption" => "Enable encryption", + "m.room.history_visibility" => "Change history visibility", + "m.room.name" => "Change room name", + "m.room.power_levels" => "Change power levels", + "m.room.tombstone" => "Upgrade room", + "m.room.topic" => "Change room topic", + "m.room.pinned_events" => "Pin events", + "m.room.server_acl" => "Change server ACLs", + "org.matrix.msc4284.policy" => "Change policy server", + "m.room.guest_access" => "Change guest access", + _ => key + }; + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Rooms/RoomCreateComponents/RoomCreatePrivacyOptions.razor b/MatrixUtils.Web/Pages/Rooms/RoomCreateComponents/RoomCreatePrivacyOptions.razor new file mode 100644 index 0000000..76f61c4 --- /dev/null +++ b/MatrixUtils.Web/Pages/Rooms/RoomCreateComponents/RoomCreatePrivacyOptions.razor @@ -0,0 +1,70 @@ +@using LibMatrix.Helpers + + Join rules: + + + + + + + + + + + + History visibility: + + + + + + + + + + + Guest access: + + + Allow guests to join + ? + + + + Server ACLs: + + @if (roomBuilder.ServerAcls?.Allow is null) { +

No allow rules exist!

+ Create sane defaults + } + else { +
+ @(roomBuilder.ServerAcls.Allow?.Count) allow rules + +
+ } + @if (roomBuilder.ServerAcls?.Deny is null) { +

No deny rules exist!

+ Create sane defaults + } + else { +
+ @(roomBuilder.ServerAcls.Deny?.Count) deny rules + +
+ } + + + +@code { + + [Parameter] + public required RoomBuilder roomBuilder { get; set; } + + [Parameter] + public required Action PageStateHasChanged { get; set; } + + [Parameter] + public AuthenticatedHomeserverGeneric Homeserver { get; set; } + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Rooms/RoomCreateComponents/RoomCreateUpgradeOptions.razor b/MatrixUtils.Web/Pages/Rooms/RoomCreateComponents/RoomCreateUpgradeOptions.razor new file mode 100644 index 0000000..3e8c3dd --- /dev/null +++ b/MatrixUtils.Web/Pages/Rooms/RoomCreateComponents/RoomCreateUpgradeOptions.razor @@ -0,0 +1,33 @@ +@using LibMatrix.Helpers + + Room upgrade options + +
+ Upgrading from @roomUpgrade.OldRoom.RoomId + + Invite members +
+ + Invite users with powerlevels +
+ + Copy bans (do not use with moderation bots!) +
+ Apply + +
+ + + +@code { + + [Parameter] + public required RoomUpgradeBuilder roomUpgrade { get; set; } + + [Parameter] + public required Action PageStateHasChanged { get; set; } + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/Rooms/Timeline.razor b/MatrixUtils.Web/Pages/Rooms/Timeline.razor index 108581c..2af819b 100644 --- a/MatrixUtils.Web/Pages/Rooms/Timeline.razor +++ b/MatrixUtils.Web/Pages/Rooms/Timeline.razor @@ -3,6 +3,7 @@ @using LibMatrix @using LibMatrix.EventTypes.Spec @using LibMatrix.EventTypes.Spec.State.RoomInfo +@using LibMatrix.Responses

RoomManagerTimeline


Loaded @Events.Count events...

diff --git a/MatrixUtils.Web/Pages/ServerInfo.razor b/MatrixUtils.Web/Pages/ServerInfo.razor index 8dd7907..3da93f2 100644 --- a/MatrixUtils.Web/Pages/ServerInfo.razor +++ b/MatrixUtils.Web/Pages/ServerInfo.razor @@ -1,6 +1,7 @@ @page "/ServerInfo/{Homeserver}" @using LibMatrix.Responses @using ArcaneLibs.Extensions +@using LibMatrix.Responses.Federation

Server info for @Homeserver


@if (ServerVersionResponse is not null) { diff --git a/MatrixUtils.Web/Pages/Tools/Debug/JoinRoom.razor b/MatrixUtils.Web/Pages/Tools/Debug/JoinRoom.razor index 319c9e7..cb56a40 100644 --- a/MatrixUtils.Web/Pages/Tools/Debug/JoinRoom.razor +++ b/MatrixUtils.Web/Pages/Tools/Debug/JoinRoom.razor @@ -5,10 +5,13 @@ Room ID:
-Via server: - +Via server(s), comma separated: +
-Join +Unblock room (Synapse): + +
+Join

@foreach (var line in Log) {
@line
@@ -21,9 +24,12 @@ [Parameter, SupplyParameterFromQuery(Name = "roomId")] public string? RoomId { get; set; } - + [Parameter, SupplyParameterFromQuery(Name = "via")] - public string? Server { get; set; } + public string? Servers { get; set; } + + [Parameter, SupplyParameterFromQuery(Name = "unblock")] + public bool Unblock { get; set; } = false; protected override async Task OnInitializedAsync() { hs = await sessionStore.GetCurrentHomeserver(navigateOnFailure: true); @@ -39,14 +45,19 @@ if (string.IsNullOrWhiteSpace(RoomId)) return; var room = hs.GetRoom(RoomId); Log.Add("Got room object..."); - - if (hs is AuthenticatedHomeserverSynapse synapse) { - await synapse.Admin.BlockRoom(RoomId, false); - Log.Add($"Synapse: unblocked room"); + + if (Unblock && hs is AuthenticatedHomeserverSynapse synapse) { + try { + await synapse.Admin.BlockRoom(RoomId, false); + Log.Add($"Synapse: unblocked room"); + } + catch (Exception e) { + Log.Add($"Synapse: failed to unblock room: {e}"); + } } - + try { - await room.JoinAsync([Server], checkIfAlreadyMember: false); + await room.JoinAsync(Servers?.Split(','), checkIfAlreadyMember: false); Log.Add("Joined room!"); } catch (Exception e) { diff --git a/MatrixUtils.Web/Pages/Tools/Debug/LeaveRoom.razor b/MatrixUtils.Web/Pages/Tools/Debug/LeaveRoom.razor index 7844331..c40fa0b 100644 --- a/MatrixUtils.Web/Pages/Tools/Debug/LeaveRoom.razor +++ b/MatrixUtils.Web/Pages/Tools/Debug/LeaveRoom.razor @@ -5,7 +5,7 @@ Room ID:
-Leave +Leave

@foreach (var line in Log) {

@line

diff --git a/MatrixUtils.Web/Pages/Tools/Debug/MigrateRoom.razor b/MatrixUtils.Web/Pages/Tools/Debug/MigrateRoom.razor index 0943216..b0f7dbf 100644 --- a/MatrixUtils.Web/Pages/Tools/Debug/MigrateRoom.razor +++ b/MatrixUtils.Web/Pages/Tools/Debug/MigrateRoom.razor @@ -17,7 +17,7 @@
-Execute +Execute
@foreach (var line in Enumerable.Reverse(log)) {

@line

diff --git a/MatrixUtils.Web/Pages/Tools/Info/SessionCount.razor b/MatrixUtils.Web/Pages/Tools/Info/SessionCount.razor index 5c238b3..fcdb3d0 100644 --- a/MatrixUtils.Web/Pages/Tools/Info/SessionCount.razor +++ b/MatrixUtils.Web/Pages/Tools/Info/SessionCount.razor @@ -11,7 +11,7 @@

Users:


-Import from room (ID) +Import from room (ID)
Rooms to be searched (@rooms.Count) @@ -21,7 +21,7 @@ }

-Execute +Execute
diff --git a/MatrixUtils.Web/Pages/Tools/InviteCounter.razor b/MatrixUtils.Web/Pages/Tools/InviteCounter.razor index 2313884..16a3853 100644 --- a/MatrixUtils.Web/Pages/Tools/InviteCounter.razor +++ b/MatrixUtils.Web/Pages/Tools/InviteCounter.razor @@ -9,7 +9,7 @@
Room ID: -Execute +Execute
diff --git a/MatrixUtils.Web/Pages/Tools/MassCMEBan.razor b/MatrixUtils.Web/Pages/Tools/MassCMEBan.razor index a252e6b..5b0f510 100644 --- a/MatrixUtils.Web/Pages/Tools/MassCMEBan.razor +++ b/MatrixUtils.Web/Pages/Tools/MassCMEBan.razor @@ -7,7 +7,7 @@
Users: -Execute +Execute
diff --git a/MatrixUtils.Web/Pages/Tools/Moderation/Draupnir/DraupnirProtectedRoomsEditor.razor b/MatrixUtils.Web/Pages/Tools/Moderation/Draupnir/DraupnirProtectedRoomsEditor.razor index b0d5a65..1ff97c8 100644 --- a/MatrixUtils.Web/Pages/Tools/Moderation/Draupnir/DraupnirProtectedRoomsEditor.razor +++ b/MatrixUtils.Web/Pages/Tools/Moderation/Draupnir/DraupnirProtectedRoomsEditor.razor @@ -53,7 +53,7 @@
}
-Apply +Apply @code { diff --git a/MatrixUtils.Web/Pages/Tools/Moderation/Draupnir/DraupnirProtectionsEditor.razor b/MatrixUtils.Web/Pages/Tools/Moderation/Draupnir/DraupnirProtectionsEditor.razor index ea39c9a..9b0266c 100644 --- a/MatrixUtils.Web/Pages/Tools/Moderation/Draupnir/DraupnirProtectionsEditor.razor +++ b/MatrixUtils.Web/Pages/Tools/Moderation/Draupnir/DraupnirProtectionsEditor.razor @@ -49,7 +49,7 @@ }
-Apply +Apply @code { diff --git a/MatrixUtils.Web/Pages/Tools/Moderation/Draupnir/DraupnirWatchedListsEditor.razor b/MatrixUtils.Web/Pages/Tools/Moderation/Draupnir/DraupnirWatchedListsEditor.razor index 9e70687..69a9048 100644 --- a/MatrixUtils.Web/Pages/Tools/Moderation/Draupnir/DraupnirWatchedListsEditor.razor +++ b/MatrixUtils.Web/Pages/Tools/Moderation/Draupnir/DraupnirWatchedListsEditor.razor @@ -49,7 +49,7 @@ }
-Apply +Apply @code { diff --git a/MatrixUtils.Web/Pages/Tools/Moderation/FindUsersByRegex.razor b/MatrixUtils.Web/Pages/Tools/Moderation/FindUsersByRegex.razor index b62cf57..1fd0ff6 100644 --- a/MatrixUtils.Web/Pages/Tools/Moderation/FindUsersByRegex.razor +++ b/MatrixUtils.Web/Pages/Tools/Moderation/FindUsersByRegex.razor @@ -14,10 +14,10 @@

Users (regex):

-Execute +Execute
-Remove kicks -Remove bans +Remove kicks +Remove bans
diff --git a/MatrixUtils.Web/Pages/Tools/Moderation/InviteCounter.razor b/MatrixUtils.Web/Pages/Tools/Moderation/InviteCounter.razor index 5c5946f..ac68e3d 100644 --- a/MatrixUtils.Web/Pages/Tools/Moderation/InviteCounter.razor +++ b/MatrixUtils.Web/Pages/Tools/Moderation/InviteCounter.razor @@ -9,7 +9,7 @@
Room ID: -Execute +Execute
diff --git a/MatrixUtils.Web/Pages/Tools/Moderation/MassCMEBan.razor b/MatrixUtils.Web/Pages/Tools/Moderation/MassCMEBan.razor index 8fdad84..605890d 100644 --- a/MatrixUtils.Web/Pages/Tools/Moderation/MassCMEBan.razor +++ b/MatrixUtils.Web/Pages/Tools/Moderation/MassCMEBan.razor @@ -8,7 +8,7 @@
Users: -Execute +Execute
diff --git a/MatrixUtils.Web/Pages/Tools/Moderation/MembershipHistory.razor b/MatrixUtils.Web/Pages/Tools/Moderation/MembershipHistory.razor index 1ec3cd0..11c4a80 100644 --- a/MatrixUtils.Web/Pages/Tools/Moderation/MembershipHistory.razor +++ b/MatrixUtils.Web/Pages/Tools/Moderation/MembershipHistory.razor @@ -16,7 +16,7 @@
Room ID: -Execute +Execute

Chronological order Enable extended filters @@ -30,17 +30,17 @@ bans

- { ShowJoins = ShowLeaves = ShowKnocks = ShowInvites = ShowBans = false; StateHasChanged(); })">Hide all - { ShowJoins = ShowLeaves = ShowKnocks = ShowInvites = ShowBans = true; StateHasChanged(); })">Show all - { ShowJoins ^= true; ShowLeaves ^= true; ShowKnocks ^= true; @@ -129,17 +129,17 @@

- { DoDisambiguate = DisambiguateProfileUpdates = DisambiguateKicks = DisambiguateUnbans = DisambiguateInviteAccepted = DisambiguateInviteRejected = DisambiguateInviteRetracted = DisambiguateKnockAccepted = DisambiguateKnockRejected = DisambiguateKnockRetracted = DisambiguateKnockActions = DisambiguateInviteActions = false; StateHasChanged(); })">Un-disambiguate all - { DoDisambiguate = DisambiguateProfileUpdates = DisambiguateKicks = DisambiguateUnbans = DisambiguateInviteAccepted = DisambiguateInviteRejected = DisambiguateInviteRetracted = DisambiguateKnockAccepted = DisambiguateKnockRejected = DisambiguateKnockRetracted = DisambiguateKnockActions = DisambiguateInviteActions = true; StateHasChanged(); })">Disambiguate all - { DisambiguateProfileUpdates ^= true; DisambiguateKicks ^= true; DisambiguateUnbans ^= true; diff --git a/MatrixUtils.Web/Pages/Tools/Moderation/RoomIntersections.razor b/MatrixUtils.Web/Pages/Tools/Moderation/RoomIntersections.razor index 31fde0b..ee77532 100644 --- a/MatrixUtils.Web/Pages/Tools/Moderation/RoomIntersections.razor +++ b/MatrixUtils.Web/Pages/Tools/Moderation/RoomIntersections.razor @@ -8,13 +8,13 @@

Set A:

-Append Set A +Append Set A

Set B:

-Append Set B +Append Set B
-Execute +Execute
diff --git a/MatrixUtils.Web/Pages/Tools/Moderation/UserTrace.razor b/MatrixUtils.Web/Pages/Tools/Moderation/UserTrace.razor index c3cc09c..f39a2eb 100644 --- a/MatrixUtils.Web/Pages/Tools/Moderation/UserTrace.razor +++ b/MatrixUtils.Web/Pages/Tools/Moderation/UserTrace.razor @@ -11,7 +11,7 @@
-Import from room (ID) +Import from room (ID)
Rooms to be searched (@rooms.Count) @@ -21,7 +21,7 @@ }

-Execute +Execute
diff --git a/MatrixUtils.Web/Pages/Tools/Room/DropPowerlevel.razor b/MatrixUtils.Web/Pages/Tools/Room/DropPowerlevel.razor index 3f9c141..208cd19 100644 --- a/MatrixUtils.Web/Pages/Tools/Room/DropPowerlevel.razor +++ b/MatrixUtils.Web/Pages/Tools/Room/DropPowerlevel.razor @@ -6,7 +6,7 @@ User ID:
Room ID:
-Execute +Execute
@Result
diff --git a/MatrixUtils.Web/Pages/Tools/Room/SpaceRestrictedJoins.razor b/MatrixUtils.Web/Pages/Tools/Room/SpaceRestrictedJoins.razor index 5d5ca20..d6ae945 100644 --- a/MatrixUtils.Web/Pages/Tools/Room/SpaceRestrictedJoins.razor +++ b/MatrixUtils.Web/Pages/Tools/Room/SpaceRestrictedJoins.razor @@ -10,7 +10,7 @@

Change knock access:


-Execute +Execute

diff --git a/MatrixUtils.Web/Pages/Tools/User/CopyPowerlevel.razor b/MatrixUtils.Web/Pages/Tools/User/CopyPowerlevel.razor index e5ffd5b..b893970 100644 --- a/MatrixUtils.Web/Pages/Tools/User/CopyPowerlevel.razor +++ b/MatrixUtils.Web/Pages/Tools/User/CopyPowerlevel.razor @@ -12,7 +12,7 @@ }
-Execute +Execute
@foreach (var line in Enumerable.Reverse(log)) {

@line

diff --git a/MatrixUtils.Web/Pages/Tools/User/MassJoinRoom.razor b/MatrixUtils.Web/Pages/Tools/User/MassJoinRoom.razor index c373a37..748f2fb 100644 --- a/MatrixUtils.Web/Pages/Tools/User/MassJoinRoom.razor +++ b/MatrixUtils.Web/Pages/Tools/User/MassJoinRoom.razor @@ -13,7 +13,7 @@ }
-Execute +Execute
@foreach (var line in Enumerable.Reverse(log)) {

@line

diff --git a/MatrixUtils.Web/Pages/User/Profile.razor b/MatrixUtils.Web/Pages/User/Profile.razor index ccd3e7b..290b92a 100644 --- a/MatrixUtils.Web/Pages/User/Profile.razor +++ b/MatrixUtils.Web/Pages/User/Profile.razor @@ -15,8 +15,8 @@ Display name:
Avatar URL:
- Update profile - Update profile (restore room overrides) + Update profile + Update profile (restore room overrides) @if (!string.IsNullOrWhiteSpace(Status)) { @@ -44,7 +44,7 @@ Display name:
Avatar URL:
- Update profile + Update profile
@if (!string.IsNullOrWhiteSpace(Status)) { diff --git a/MatrixUtils.Web/Shared/InputLocalPart.razor b/MatrixUtils.Web/Shared/InputLocalPart.razor new file mode 100644 index 0000000..8f34377 --- /dev/null +++ b/MatrixUtils.Web/Shared/InputLocalPart.razor @@ -0,0 +1,50 @@ +
+ @if (!string.IsNullOrWhiteSpace(Label)) { + + } + @Sigil + + : + @if (ServerNameChanged is not null) { + + } + else { + @ServerName + } +
+ +@code { + + [Parameter] + public string? Label { get; set; } + + [Parameter] + public required string Sigil { get; set; } + + [Parameter] + public string? LocalPart { + get; + set { + if (field == value) return; + field = value; + LocalPartChanged.InvokeAsync(value); + } + } + + [Parameter] + public EventCallback LocalPartChanged { get; set; } + + [Parameter] + public string? ServerName { + get; + set { + if (field == value) return; + field = value; + ServerNameChanged?.InvokeAsync(value); + } + } + + [Parameter] + public EventCallback? ServerNameChanged { get; set; } + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Shared/PolicyEditorComponents/MassPolicyEditorModal.razor b/MatrixUtils.Web/Shared/PolicyEditorComponents/MassPolicyEditorModal.razor index f7bb200..99dbbc3 100644 --- a/MatrixUtils.Web/Shared/PolicyEditorComponents/MassPolicyEditorModal.razor +++ b/MatrixUtils.Web/Shared/PolicyEditorComponents/MassPolicyEditorModal.razor @@ -36,15 +36,15 @@ @* *@ @*
*@ @if (!VerifyIntent) { - Cancel + Cancel - Save + Save @if (!string.IsNullOrWhiteSpace(Response)) {
@Response
@@ -63,14 +63,9 @@ VerifyIntent = false; Response = null; StateHasChanged(); - return Task.CompletedTask; })">No
- Yes - + Yes } diff --git a/MatrixUtils.Web/Shared/PolicyEditorComponents/PolicyEditorModal.razor b/MatrixUtils.Web/Shared/PolicyEditorComponents/PolicyEditorModal.razor index 5819bee..0205e16 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 + OnCloseClickedAsync="@InvokeOnClose" X="60" Y="60" MinWidth="300"> @if (string.IsNullOrWhiteSpace(PolicyEvent.EventId)) { Policy type: