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
<h3>Debug Tools</h3>
@@ -14,7 +17,7 @@ else {
<summary>Room List</summary>
@foreach (var roomId in Rooms) {
<a style="color: unset; text-decoration: unset;" href="/RoomStateViewer/@roomId.Replace('.', '~')">
- <RoomListItem RoomInfo="@(new RoomInfo(hs.GetRoom(roomId)))" LoadData="true"></RoomListItem>
+ <RoomListItem Homeserver="hs" RoomInfo="@(new RoomInfo(hs.GetRoom(roomId)))" LoadData="true"></RoomListItem>
</a>
}
</details>
@@ -61,6 +64,7 @@ else {
StateHasChanged();
return;
}
+
if (res.Content.Headers.ContentType.MediaType == "application/json")
GetRequestResult = $"Error: {res.StatusCode}\n" + (await res.Content.ReadFromJsonAsync<object>()).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 @@
<h3>Known Homeserver List</h3>
<hr/>
-<span>Room ID: <FancyTextBox @bind-Value="@RoomId"/><LinkButton OnClick="@Execute">Execute</LinkButton></span>
+<span>Room ID: <FancyTextBox @bind-Value="@RoomId"/><LinkButton OnClickAsync="@Execute">Execute</LinkButton></span>
<span>Stats:</span><br/>
<span>Server count: @entries.Count</span><br/>
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
<h3>Homeserver Admininistration</h3>
<hr/>
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
<h3>Manage external profiles</h3>
-<LinkButton OnClick="AddAllLocalProfiles">Add local sessions</LinkButton>
+<LinkButton OnClickAsync="AddAllLocalProfiles">Add local sessions</LinkButton>
@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 @@
<pre>@MxcUri?.ToJson(ignoreNull: true)</pre>
@if (Event is not null) {
- <LinkButton OnClick="@RedactAllEvents">Redact all messages</LinkButton>
+ <LinkButton OnClickAsync="@RedactAllEvents">Redact all messages</LinkButton>
}
@if (Event?.Sender?.Split(':', 2)[1] == Homeserver?.ServerName) {
<p>User is a local user!</p>
- <LinkButton OnClick="@DeactivateUser">Deactivate User</LinkButton>
- <LinkButton OnClick="@QuarantineMediaByUser">Quarantine all media</LinkButton>
+ <LinkButton OnClickAsync="@DeactivateUser">Deactivate User</LinkButton>
+ <LinkButton OnClickAsync="@QuarantineMediaByUser">Quarantine all media</LinkButton>
}
}
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 @@
<br/>
</details>
- <LinkButton OnClick="@DeleteRoom">Execute</LinkButton>
+ <LinkButton OnClickAsync="@DeleteRoom">Execute</LinkButton>
}
@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 @@
}
</p>
<p>
- <LinkButton OnClick="@(() => DeleteRoom(room))">Delete room</LinkButton>
+ <LinkButton OnClickAsync="@(() => DeleteRoom(room))">Delete room</LinkButton>
<LinkButton target="_blank" href="@($"/HSAdmin/Synapse/ResyncState?roomId={room.RoomId}&via={room.RoomId.Split(':', 2)[1]}")">Resync state</LinkButton>
</p>
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 @@
<span>Via: </span>
<InputText @bind-Value="@Via"></InputText>
<br/>
- <LinkButton OnClick="@Execute">Execute</LinkButton>
+ <LinkButton OnClickAsync="@Execute">Execute</LinkButton>
}
@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 @@
<br/>
</p>
<p>
- <LinkButton OnClick="@(() => Login(user))">Log in</LinkButton>
- @* <LinkButton OnClick="@(() => DeleteRoom(user))">Delete room</LinkButton> *@
+ <LinkButton OnClickAsync="@(() => Login(user))">Log in</LinkButton>
+ @* <LinkButton OnClickAsync="@(() => DeleteRoom(user))">Delete room</LinkButton> *@
@* <LinkButton target="_blank" href="@($"/HSAdmin/Synapse/ResyncState?roomId={user.RoomId}&via={user.RoomId.Split(':', 2)[1]}")">Resync state</LinkButton> *@
</p>
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
<PageTitle>Index</PageTitle>
@@ -58,9 +59,9 @@ Small collection of tools to do not-so-everyday things.
</td>
<td>
<p>
- <LinkButton OnClick="@(() => ManageUser(session.SessionId))">Manage</LinkButton>
- <LinkButton OnClick="@(() => RemoveUser(session.SessionId))">Remove</LinkButton>
- <LinkButton OnClick="@(() => RemoveUser(session.SessionId, true))">Log out</LinkButton>
+ <LinkButton OnClickAsync="@(() => ManageUser(session.SessionId))">Manage</LinkButton>
+ <LinkButton OnClickAsync="@(() => RemoveUser(session.SessionId))">Remove</LinkButton>
+ <LinkButton OnClickAsync="@(() => RemoveUser(session.SessionId, true))">Log out</LinkButton>
</p>
</td>
</tr>
@@ -89,7 +90,7 @@ Small collection of tools to do not-so-everyday things.
</p>
</td>
<td>
- <LinkButton OnClick="@(() => RemoveUser(session.SessionId))">Remove</LinkButton>
+ <LinkButton OnClickAsync="@(() => RemoveUser(session.SessionId))">Remove</LinkButton>
</td>
</tr>
}
@@ -118,10 +119,10 @@ Small collection of tools to do not-so-everyday things.
</p>
</td>
<td>
- <LinkButton OnClick="@(() => Task.Run(() => NavigationManager.NavigateTo($"/InvalidSession?ctx={session.SessionId}")))">Re-login</LinkButton>
+ <LinkButton OnClickAsync="@(() => Task.Run(() => NavigationManager.NavigateTo($"/InvalidSession?ctx={session.SessionId}")))">Re-login</LinkButton>
</td>
<td>
- <LinkButton OnClick="@(() => RemoveUser(session.SessionId))">Remove</LinkButton>
+ <LinkButton OnClickAsync="@(() => RemoveUser(session.SessionId))">Remove</LinkButton>
</td>
</tr>
}
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) {
<p>It appears that the affected user is @_auth.UserId (@_auth.DeviceId) on @_auth.Homeserver!</p>
- <LinkButton OnClick="@(OpenRefreshDialog)">Refresh token</LinkButton>
- <LinkButton OnClick="@(RemoveUser)">Remove</LinkButton>
+ <LinkButton OnClickAsync="@(OpenRefreshDialog)">Refresh token</LinkButton>
+ <LinkButton OnClickAsync="@(RemoveUser)">Remove</LinkButton>
@if (_showRefreshDialog) {
<ModalWindow MinWidth="300" X="275" Y="300" Title="@($"Password for {_auth.UserId}")">
<FancyTextBox IsPassword="true" @bind-Value="@_password"></FancyTextBox>
<br/>
- <LinkButton OnClick="TryLogin">Log in</LinkButton>
+ <LinkButton OnClickAsync="TryLogin">Log in</LinkButton>
@if (_loginException is not null) {
<pre style="color: red;">@_loginException.RawContent</pre>
}
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) {
- <LinkButton OnClick="@(async () => Data.SelectedRoom = room)" Color="@(Data.SelectedRoom == room ? "#FF00FF" : "")">
+ <LinkButton OnClickAsync="@(async () => Data.SelectedRoom = room)" Color="@(Data.SelectedRoom == room ? "#FF00FF" : "")">
@room.RoomName
</LinkButton>
<br/>
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 @@
</p>
<br/>
- <LinkButton OnClick="@Disband" Color="#FF0000">Disband</LinkButton>
- <LinkButton OnClick="@Execute">Next</LinkButton>
+ <LinkButton OnClickAsync="@Disband" Color="#FF0000">Disband</LinkButton>
+ <LinkButton OnClickAsync="@Execute">Next</LinkButton>
}
else {
<p>Discovering spaces, please wait...</p>
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 {
}
<br/>
-<LinkButton OnClick="@Execute">Next</LinkButton>
+<LinkButton OnClickAsync="@Execute">Next</LinkButton>
@{
var _offset = 0;
@@ -41,7 +41,7 @@ else {
<p>Found room assigned to multiple users: <RoomListItem RoomInfo="@room"></RoomListItem></p>
<p>Users:</p>
@foreach (var userProfileResponse in usersList) {
- <LinkButton OnClick="@(() => SetRoomAssignment(room.Room.RoomId, userProfileResponse.Id))">
+ <LinkButton OnClickAsync="@(() => SetRoomAssignment(room.Room.RoomId, userProfileResponse.Id))">
<span>Assign to </span>
<InlineUserItem User="userProfileResponse"></InlineUserItem>
</LinkButton>
@@ -54,7 +54,7 @@ else {
<ModalWindow Title="Re-assign DM" OnCloseClicked="@(() => DmToReassign = null)">
<RoomListItem RoomInfo="@DmToReassign"></RoomListItem>
@foreach (var userProfileResponse in roomMembers[DmToReassign]) {
- <LinkButton OnClick="@(() => SetRoomAssignment(DmToReassign.Room.RoomId, userProfileResponse.Id))">
+ <LinkButton OnClickAsync="@(() => SetRoomAssignment(DmToReassign.Room.RoomId, userProfileResponse.Id))">
<span>Assign to </span>
<InlineUserItem User="userProfileResponse"></InlineUserItem>
</LinkButton>
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 {
}
<br/>
-<LinkButton OnClick="@Execute">Next</LinkButton>
+<LinkButton OnClickAsync="@Execute">Next</LinkButton>
@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 @@
<FancyTextBox @bind-Value="@newRecordInput.Proxy"></FancyTextBox>
</span>
<br/>
-<LinkButton OnClick="@AddRecord">Add account to queue</LinkButton>
-<LinkButton OnClick="@(() => Login(newRecordInput))">Log in</LinkButton>
+<LinkButton OnClickAsync="@AddRecord">Add account to queue</LinkButton>
+<LinkButton OnClickAsync="@(() => Login(newRecordInput))">Log in</LinkButton>
<br/>
<br/>
@@ -44,7 +44,7 @@
<FancyTextBox @bind-Value="@newRecordInput.Proxy"></FancyTextBox>
</span>
<br/>
-<LinkButton OnClick="@(() => AddWithAccessToken(newRecordInput))">Add session</LinkButton>
+<LinkButton OnClickAsync="@(() => AddWithAccessToken(newRecordInput))">Add session</LinkButton>
<br/>
<br/>
@@ -101,7 +101,7 @@
}
</table>
<br/>
-<LinkButton OnClick="@LoginAll">Log in</LinkButton>
+<LinkButton OnClickAsync="@LoginAll">Log in</LinkButton>
@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<Create2> logger
+@* @* ReSharper disable once RedundantUsingDirective - Must not remove this, Rider marks this as "unused" when it's not */ *@
+
+<h3>Room Manager - Create Room</h3>
+
+@if (Ready) {
+ <style>
+ table.table-top-first-tr tr td:first-child {
+ vertical-align: top;
+ }
+ </style>
+ <table class="table-top-first-tr">
+ @if (roomBuilder is RoomUpgradeBuilder roomUpgrade) {
+ <RoomCreateUpgradeOptions roomUpgrade="@roomUpgrade" PageStateHasChanged="@StateHasChanged"/>
+ }
+ else {
+ @* <tr style="padding-bottom: 16px;"> *@
+ @* <td>Preset:</td> *@
+ @* <td> *@
+ @* @if (Presets is null) { *@
+ @* <p style="color: red;">Presets is null!</p> *@
+ @* } *@
+ @* else { *@
+ @* <p style="color: red;">Support for presets is currently disabled!</p> *@
+ @* $1$ <InputSelect @bind-Value="@RoomPreset"> #1# *@
+ @* $1$ @foreach (var createRoomRequest in Presets) { #1# *@
+ @* $1$ <option value="@createRoomRequest.Key">@createRoomRequest.Key</option> #1# *@
+ @* $1$ } #1# *@
+ @* $1$ </InputSelect> #1# *@
+ @* } *@
+ @* </td> *@
+ @* </tr> *@
+ }
+ <RoomCreateBasicRoomInfoOptions roomBuilder="@roomBuilder" PageStateHasChanged="@StateHasChanged" Homeserver="@Homeserver"/>
+ <RoomCreateCreateOptions roomBuilder="@roomBuilder" PageStateHasChanged="@StateHasChanged" Homeserver="@Homeserver"/>
+ <RoomCreatePrivacyOptions roomBuilder="@roomBuilder" PageStateHasChanged="@StateHasChanged" Homeserver="@Homeserver"/>
+ <RoomCreatePermissionsOptions roomBuilder="@roomBuilder" PageStateHasChanged="@StateHasChanged" Homeserver="@Homeserver"/>
+ <RoomCreateMembershipOptions roomBuilder="@roomBuilder" PageStateHasChanged="@StateHasChanged" Homeserver="@Homeserver"/>
+ @* Initial states, should remain at bottom *@
+ </table>
+ <LinkButton OnClickAsync="@CreateRoom">Create room</LinkButton>
+ <br/>
+ <div
+ style="position: fixed; top: 56px; right: 0; width: fit-content; max-width: 25%; height: calc(100vh - 56px); overflow: auto; background-color: #2c3054; padding-right: 32px; border-left: 1px solid #ccc;">
+ <details open>
+ <summary>RoomBuilder state</summary>
+ <InputCheckbox @bind-Value="@ShowNullInState"/>
+ <span>Show null values</span><br/>
+ <pre>
+ @roomBuilder.ToJson(ignoreNull: !ShowNullInState)
+ </pre>
+ </details>
+ </div>
+}
+@if (_matrixException is not null) {
+ <ModalWindow Title="@("Matrix exception: " + _matrixException.ErrorCode)">
+ <pre>
+ @_matrixException.Message
+ </pre>
+ </ModalWindow>
+}
+
+@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<string, CreateRoomRequest>? 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<PolicyList> logger
-<h3>Policy list editor - Editing @(RoomName ?? 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 OnClick="@(() => {
- CurrentlyEditingEvent = new() { Type = "", RawContent = new() };
- return Task.CompletedTask;
- })">Create new policy
-</LinkButton>
-<LinkButton OnClick="@(() => {
- MassCreatePolicies = true;
- return Task.CompletedTask;
- })">Create many new policies
-</LinkButton>
-<LinkButton OnClick="@LoadStatesAsync">Refresh</LinkButton>
-
-@if (Loading) {
- <p>Loading...</p>
-}
-else if (PolicyEventsByType is not { Count: > 0 }) {
- <p>No policies yet</p>
+@if (!IsInitialised) {
+ <p>Connecting to homeserver...</p>
}
else {
- var renderSw = Stopwatch.StartNew();
- var renderTotalSw = Stopwatch.StartNew();
- @foreach (var (type, value) in PolicyEventsByType) {
- <p>
- @(GetValidPolicyEventsByType(type).Count) active,
- @(GetInvalidPolicyEventsByType(type).Count) invalid,
- @(GetRemovedPolicyEventsByType(type).Count) removed
- (@value.Count total)
- @(GetPolicyTypeName(type).ToLower())
- </p>
- }
-
- Console.WriteLine($"Rendered header in {renderSw.GetElapsedAndRestart()}");
-
- var renderSw2 = Stopwatch.StartNew();
- IOrderedEnumerable<Type> 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) {
- <details>
- <summary>
- <span>
- @($"{GetPolicyTypeName(type)}: {GetPolicyEventsByType(type).Count} policies")
- </span>
- <hr style="margin: revert;"/>
- </summary>
- <table class="table table-striped table-hover">
- @{
- 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<TableHideAttribute>() 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()}");
- }
- <thead>
- <tr>
- @foreach (var name in propNames) {
- <th>@name</th>
- }
- <th>Actions</th>
- </tr>
- </thead>
- <tbody style="border-width: 1px;">
- @foreach (var policy in policies.OrderBy(x => x.RawContent?["entity"]?.GetValue<string>())) {
- <tr>
- @{
- var typedContent = policy.TypedContent! as PolicyRuleEventContent;
- }
- @foreach (var prop in proxySafeProps ?? Enumerable.Empty<PropertyInfo>()) {
- if (prop.Name == "Entity") {
- <td>@TruncateMxid(typedContent!.Entity)</td>
- }
- else {
- <td>@prop.GetGetMethod()?.Invoke(typedContent, null)</td>
- }
- }
- <td>
- <div style="display: ruby;">
- @if (PowerLevels.UserHasStatePermission(Homeserver.WhoAmI.UserId, policy.Type)) {
- <LinkButton OnClick="@(() => {
- CurrentlyEditingEvent = policy;
- return Task.CompletedTask;
- })">Edit
- </LinkButton>
- <LinkButton OnClick="@(() => RemovePolicyAsync(policy))">Remove</LinkButton>
- @if (policy.IsLegacyType) {
- <LinkButton OnClick="@(() => RemovePolicyAsync(policy))">Update policy type</LinkButton>
- }
-
- @if (PolicyTypeIds[typeof(ServerPolicyRuleEventContent)].Contains(policy.Type)) {
- <LinkButton OnClick="@(() => {
- 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>
- </tr>
- }
- </tbody>
- </table>
- <details>
- <summary>
- <u>
- @("Invalid " + GetPolicyTypeName(type).ToLower())
- </u>
- </summary>
- <table class="table table-striped table-hover">
- <thead>
- <tr>
- <th>State key</th>
- <th>Json contents</th>
- </tr>
- </thead>
- <tbody>
- @foreach (var policy in invalidPolicies) {
- <tr>
- <td>@policy.StateKey</td>
- <td>
- <pre>@policy.RawContent.ToJson(true, false)</pre>
- </td>
- </tr>
- }
- </tbody>
- </table>
- </details>
- </details>
+ <PolicyListEditorHeader Room="@Room" ReloadStateAsync="@(() => LoadStateAsync(true))"></PolicyListEditorHeader>
+ @if (Loading) {
+ <p>Loading...</p>
}
+ // else if (PolicyEventsByType is not { Count: > 0 }) {
+ @* <p>No policies yet</p> *@
+ // }
+ else {
+ var renderSw = Stopwatch.StartNew();
+ var renderTotalSw = Stopwatch.StartNew();
+ @foreach (var value in PolicyCollections.Values.OrderByDescending(x => x.TotalCount)) {
+ <p>
+ @value.ActivePolicies.Count active,
+ @value.RemovedPolicies.Count removed
+ (@value.TotalCount total)
+ @value.Name.ToLower()
+ </p>
+ }
- 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) {
- <PolicyEditorModal PolicyEvent="@CurrentlyEditingEvent" OnClose="@(() => CurrentlyEditingEvent = null)" OnSave="@(e => UpdatePolicyAsync(e))"></PolicyEditorModal>
-}
+ // var renderSw2 = Stopwatch.StartNew();
+ // IOrderedEnumerable<Type> 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) {
- <ModalWindow Title="Make policy permanent">
-
- </ModalWindow>
-}
+ foreach (var collection in PolicyCollections.Values.OrderByDescending(x => x.ActivePolicies.Count)) {
+ <PolicyListCategoryComponent PolicyCollection="@collection" Room="@Room"></PolicyListCategoryComponent>
+ }
-@if (MassCreatePolicies) {
- <MassPolicyEditorModal Room="@Room" OnClose="@(() => MassCreatePolicies = false)" OnSaved="@(() => {
- MassCreatePolicies = false;
- _ = LoadStatesAsync();
- })"></MassPolicyEditorModal>
+ // foreach (var type in policiesByType) {
+ @* foreach (var type in (List<Type>) []) { *@
+ @* <details> *@
+ @* <summary> *@
+ @* <span> *@
+ @* @($"{GetPolicyTypeName(type)}: {GetPolicyEventsByType(type).Count} policies") *@
+ @* </span> *@
+ @* <hr style="margin: revert;"/> *@
+ @* </summary> *@
+ @* <table class="table table-striped table-hover table-bordered align-middle"> *@
+ @* @{ *@
+ @* 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<TableHideAttribute>() 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()}"); *@
+ @* } *@
+ @* <thead> *@
+ @* <tr> *@
+ @* @foreach (var name in propNames) { *@
+ @* <th>@name</th> *@
+ @* } *@
+ @* <th>Actions</th> *@
+ @* </tr> *@
+ @* </thead> *@
+ @* <tbody> *@
+ @* @foreach (var policy in policies.OrderBy(x => x.RawContent?["entity"]?.GetValue<string>())) { *@
+ @* <PolicyListRowComponent PolicyInfo="@policy" Room="@Room"></PolicyListRowComponent> *@
+ @* } *@
+ @* </tbody> *@
+ @* </table> *@
+ @* <details> *@
+ @* <summary> *@
+ @* <u> *@
+ @* @("Invalid " + GetPolicyTypeName(type).ToLower()) *@
+ @* </u> *@
+ @* </summary> *@
+ @* <table class="table table-striped table-hover"> *@
+ @* <thead> *@
+ @* <tr> *@
+ @* <th>State key</th> *@
+ @* <th>Json contents</th> *@
+ @* </tr> *@
+ @* </thead> *@
+ @* <tbody> *@
+ @* @foreach (var policy in invalidPolicies) { *@
+ @* <tr> *@
+ @* <td>@policy.StateKey</td> *@
+ @* <td> *@
+ @* <pre>@policy.RawContent.ToJson(true, false)</pre> *@
+ @* </td> *@
+ @* </tr> *@
+ @* } *@
+ @* </tbody> *@
+ @* </table> *@
+ @* </details> *@
+ @* </details> *@
+ // }
+
+ // 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<Type, List<StateEventResponse>> 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<StateEventResponse, int> ActiveKicks { get; set; } = [];
- public bool MassCreatePolicies {
- get;
- set {
- field = value;
- StateHasChanged();
- }
- }
+ public Dictionary<StateEventResponse, int> 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>(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<object>(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<TableHideAttribute>() 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<string>())) {
+ 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
+ <StateEventResponse>(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<StateEventResponse> AllPolicies { get; set; } = [];
-
private List<StateEventResponse> GetPolicyEventsByType(Type type) => PolicyEventsByType.ContainsKey(type) ? PolicyEventsByType[type] : [];
- private List<StateEventResponse> GetValidPolicyEventsByType(Type type) => GetPolicyEventsByType(type)
- .Where(x => !string.IsNullOrWhiteSpace(x.RawContent?["recommendation"]?.GetValue<string>())).ToList();
-
- private List<StateEventResponse> GetInvalidPolicyEventsByType(Type type) => GetPolicyEventsByType(type)
- .Where(x => x.RawContent is { Count: > 0 } && string.IsNullOrWhiteSpace(x.RawContent?["recommendation"]?.GetValue<string>())).ToList();
-
- private List<StateEventResponse> GetRemovedPolicyEventsByType(Type type) => GetPolicyEventsByType(type)
- .Where(x => x.RawContent is null or { Count: 0 }).ToList();
+ // private List<StateEventResponse> GetValidPolicyEventsByType(Type type) => GetPolicyEventsByType(type)
+ // .Where(x => !string.IsNullOrWhiteSpace(x.RawContent?["recommendation"]?.GetValue<string>())).ToList();
+ //
+ // private List<StateEventResponse> GetInvalidPolicyEventsByType(Type type) => GetPolicyEventsByType(type)
+ // .Where(x => x.RawContent is { Count: > 0 } && string.IsNullOrWhiteSpace(x.RawContent?["recommendation"]?.GetValue<string>())).ToList();
+ //
+ // private List<StateEventResponse> GetRemovedPolicyEventsByType(Type type) => GetPolicyEventsByType(type)
+ // .Where(x => x.RawContent is null or { Count: 0 }).ToList();
private string? GetPolicyTypeNameOrNull(Type type) => type.GetFriendlyNamePluralOrNull()
?? type.GetCustomAttributes<MatrixEventAttribute>()
@@ -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<Type> KnownPolicyTypes = StateEvent.KnownStateEventTypes.Where(x => x.IsAssignableTo(typeof(PolicyRuleEventContent))).ToFrozenSet();
// event types, unnamed
@@ -339,154 +403,28 @@ else {
private static Dictionary<Type, string[]> PolicyTypeIds = KnownPolicyTypes
.ToDictionary(x => x, x => x.GetCustomAttributes<MatrixEventAttribute>().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>(DraupnirProtectedRoomsData.EventId);
- var rooms = data.Rooms.Select(Homeserver.GetRoom).ToList();
+ Dictionary<Type, PolicyCollection> 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<RoomMemberEventContent>();
- // // 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<RoomMemberEventContent>();
- // 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<string> 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<RoomMemberEventContent>();
- // 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<string, PropertyInfo> 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<StateEventResponse> 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>(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<RoomMemberEventContent>();
+ // // 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<RoomMemberEventContent>();
+ // 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<string> 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<RoomMemberEventContent>();
+ // 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 @@
<h3>Policy list editor - Editing @RoomId</h3>
<hr/>
@* <InputCheckbox @bind-Value="EnableAvatars"></InputCheckbox><label>Enable avatars (WILL EXPOSE YOUR IP TO TARGET HOMESERVERS!)</label> *@
-<LinkButton OnClick="@(() => { CurrentlyEditingEvent = new() { Type = "", RawContent = new() }; return Task.CompletedTask; })">Create new policy</LinkButton>
+<LinkButton OnClickAsync="@(() => { CurrentlyEditingEvent = new() { Type = "", RawContent = new() }; return Task.CompletedTask; })">Create new policy</LinkButton>
@if (Loading) {
<p>Loading...</p>
@@ -71,16 +71,16 @@ else {
}
<div style="display: ruby;">
@if (PowerLevels.UserHasStatePermission(Homeserver.WhoAmI.UserId, policy.Type)) {
- <LinkButton OnClick="@(() => { CurrentlyEditingEvent = policy; return Task.CompletedTask; })">Edit</LinkButton>
- <LinkButton OnClick="@(() => RemovePolicyAsync(policy))">Remove</LinkButton>
+ <LinkButton OnClickAsync="@(() => { CurrentlyEditingEvent = policy; return Task.CompletedTask; })">Edit</LinkButton>
+ <LinkButton OnClickAsync="@(() => RemovePolicyAsync(policy))">Remove</LinkButton>
@if (policy.IsLegacyType) {
- <LinkButton OnClick="@(() => RemovePolicyAsync(policy))">Update policy type</LinkButton>
+ <LinkButton OnClickAsync="@(() => RemovePolicyAsync(policy))">Update policy type</LinkButton>
}
@if (PolicyTypeIds[typeof(ServerPolicyRuleEventContent)].Contains(policy.EventId)) {
- <LinkButton OnClick="@(() => { ServerPolicyToMakePermanent = policy; return Task.CompletedTask; })">Make permanent (wildcard)</LinkButton>
+ <LinkButton OnClickAsync="@(() => { ServerPolicyToMakePermanent = policy; return Task.CompletedTask; })">Make permanent (wildcard)</LinkButton>
@if (CurrentUserIsDraupnir) {
- <LinkButton OnClick="@(() => UpgradePolicyAsync(policy))">Kick matching users</LinkButton>
+ <LinkButton OnClickAsync="@(() => UpgradePolicyAsync(policy))">Kick matching users</LinkButton>
}
}
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
+<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>
+ @foreach (var name in PolicyCollection.PropertiesToDisplay!.Keys) {
+ <th>@name</th>
+ }
+ <th>Actions</th>
+ </tr>
+ </thead>
+ <tbody>
+ @foreach (var policy in PolicyCollection.ActivePolicies.Values.OrderBy(x => x.Policy.RawContent?["entity"]?.GetValue<string>())) {
+ <PolicyListRowComponent PolicyInfo="@policy" PolicyCollection="@PolicyCollection" Room="@Room"></PolicyListRowComponent>
+ }
+ </tbody>
+ </table>
+ <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; }
+
+ 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
+<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>
+}
+
+@code {
+ [Parameter]
+ public required GenericRoom Room { get; set; }
+
+ [Parameter]
+ public required Func<Task> 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>(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) {
+ <tr id="@PolicyInfo.Policy.EventId">
+ @foreach (var prop in PolicyCollection.PropertiesToDisplay.Values) {
+ if (prop.Name == "Entity") {
+ <td>
+ <span>@TruncateMxid(TypedContent.Entity)</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>
+ }
+ </td>
+ }
+ else {
+ <td>@prop.GetGetMethod()?.Invoke(TypedContent, null)</td>
+ }
+ }
+ <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 policy 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>
+ </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; }
+
+ 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<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
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<Index> logger
-<h3>Policy lists </h3> @* <LinkButton href="/Rooms/Create">Create new policy list</LinkButton> *@
+<h3>
+ <span>Policy lists </span>
+ <LinkButton OnClickAsync="@(() => {
+ ShowPolicyListCreationWindow = true;
+ return Task.CompletedTask;
+ })">
+ <span class="oi oi-plus" aria-hidden="true"> Create</span>
+ </LinkButton>
+</h3>
+
@if (!string.IsNullOrWhiteSpace(Status)) {
<p>@Status</p>
@@ -16,22 +28,17 @@
}
<hr/>
-<table>
+<table class="table table-striped table-hover table-bordered align-middle" aria-busy="@isLoading">
<thead>
<tr>
- <th/>
<th>Room name</th>
<th>Policies</th>
+ <th/>
</tr>
</thead>
<tbody>
@foreach (var room in Rooms.OrderByDescending(x => x.PolicyCounts.Sum(y => y.Value))) {
<tr>
- <td>
- <LinkButton href="@($"/Rooms/{room.Room.RoomId}/Policies")">
- <span class="oi oi-pencil" aria-hidden="true"></span>
- </LinkButton>
- </td>
<td style="padding-right: 24px;">
<span>@room.RoomName</span>
@if (room.IsLegacy) {
@@ -50,11 +57,44 @@
<span>@(room.PolicyCounts.GetValueOrDefault(RoomInfo.PolicyType.Server) ?? 0) server policies</span><br/>
<span>@(room.PolicyCounts.GetValueOrDefault(RoomInfo.PolicyType.Room) ?? 0) room policies</span><br/>
</td>
+ <td>
+ <LinkButton href="@($"/Rooms/{room.Room.RoomId}/Policies")">
+ <span class="oi oi-pencil" aria-hidden="true"> View/edit policies</span>
+ </LinkButton>
+ </td>
</tr>
}
</tbody>
</table>
+@if (ShowPolicyListCreationWindow && Homeserver != null) {
+ <ModalWindow Title="New policy list">
+ @if (!string.IsNullOrWhiteSpace(_roomBuilder.Avatar.Url)) {
+ <MxcAvatar Homeserver="@Homeserver" MxcUri="@_roomBuilder.Avatar.Url" Circular="true" Size="4" SizeUnit="em"/>
+ }
+ else {
+ <img class="avatar" style="height: 4em; width: 4em; border-radius: 50%;" src="@IdenticonGenerator.GenerateAsDataUri(Homeserver.WhoAmI.UserId)"/>
+ }
+ <div style="display: inline-block; vertical-align: middle; padding-left: 1em;">
+ <FancyTextBox @bind-Value="@_roomBuilder.Name.Name"></FancyTextBox>
+ <br/>
+ <span>#</span>
+ <FancyTextBox @bind-Value="@_roomBuilder.AliasLocalPart"></FancyTextBox>
+ <span>:@Homeserver!.ServerName</span>
+ <br/>
+ <FancyTextBox @bind-Value="@_roomBuilder.Avatar.Url"></FancyTextBox>
+ <InputFile OnChange="@RoomIconFilePicked"></InputFile>
+ </div>
+ <br/>
+
+ <span>Bot shortcode: </span>
+ <FancyTextBox @bind-Value="@_shortcodeEvent.Shortcode"></FancyTextBox>
+ <br/>
+ <LinkButton OnClickAsync="@CreatePolicyList">Create</LinkButton>
+
+ </ModalWindow>
+}
+
@code {
private List<RoomInfo> 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<UserPolicyRuleEventContent>();
- var serverEventTypes = EventContent.GetMatchingEventTypes<ServerPolicyRuleEventContent>();
- var roomEventTypes = EventContent.GetMatchingEventTypes<RoomPolicyRuleEventContent>();
- var knownPolicyTypes = (List<string>) [..userEventTypes, ..serverEventTypes, ..roomEventTypes];
-
- List<GenericRoom> roomsByType = [];
+ List<Task> _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<Task<RoomInfo>> 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<string> userEventTypes = EventContent.GetMatchingEventTypes<UserPolicyRuleEventContent>();
- private static readonly List<string> serverEventTypes = EventContent.GetMatchingEventTypes<ServerPolicyRuleEventContent>();
- private static readonly List<string> roomEventTypes = EventContent.GetMatchingEventTypes<RoomPolicyRuleEventContent>();
- private static readonly List<string> allKnownPolicyTypes = [..userEventTypes, ..serverEventTypes, ..roomEventTypes];
-
public static async Task<RoomInfo> FromRoom(GenericRoom room, List<StateEventResponse>? state = null, bool legacy = false) {
state ??= await room.GetFullStateAsListAsync();
return new RoomInfo() {
@@ -165,12 +199,40 @@
?? room.RoomId,
Shortcode = (await room.GetStateOrNullAsync<MjolnirShortcodeEventContent>(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
+<tr>
+ <td>Room name:</td>
+ <td>
+ <FancyTextBox @bind-Value="@roomBuilder.Name.Name"></FancyTextBox>
+ </td>
+</tr>
+<tr>
+ <td>Room alias:</td>
+ <td>
+ <InputLocalPart Sigil="#" ServerName="@Homeserver.ServerName" @bind-LocalPart="@roomBuilder.AliasLocalPart"></InputLocalPart>
+ </td>
+</tr>
+<tr>
+ <td>Room icon:</td>
+ <td>
+ @if (!string.IsNullOrWhiteSpace(roomBuilder.Avatar.Url)) {
+ <MxcAvatar Homeserver="Homeserver" MxcUri="@roomBuilder.Avatar.Url" Size="3" SizeUnit="em" Circular="true"/>
+ }
+ else {
+ <img class="avatar" style="height: 3em; width: 3em; border-radius: 50%;" src="@IdenticonGenerator.GenerateAsDataUri(Homeserver.WhoAmI.UserId)"/>
+ }
+ <div style="display: inline-block; vertical-align: middle;">
+ <FancyTextBox @bind-Value="@roomBuilder.Avatar.Url"></FancyTextBox>
+ <br/>
+ <SimpleFilePicker OnFilePicked="@RoomIconFilePicked"></SimpleFilePicker>
+ </div>
+ </td>
+</tr>
+
+@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
+<tr>
+ <td>Room type:</td>
+ <td>
+ @if (RoomTypes.ContainsKey(roomBuilder.Type ?? "")) {
+ <InputSelect @bind-Value="@roomBuilder.Type">
+ @foreach (var type in RoomTypes) {
+ <option value="@type.Key">@type.Value</option>
+ }
+ <option value="custom">Custom ...</option>
+ </InputSelect>
+ }
+ else {
+ <FancyTextBox @bind-Value="@roomBuilder.Type"></FancyTextBox>
+ }
+
+ <span> version </span>
+ @if (Capabilities is null) {
+ <span style="color: #888;">Loading...</span>
+ }
+ else {
+ <InputSelect @bind-Value="@roomBuilder.Version">
+ @foreach (var version in Capabilities.Capabilities.RoomVersions!.Available!) {
+ <option value="@version.Key">@version.Key (@version.Value)</option>
+ }
+ </InputSelect>
+ }
+ </td>
+</tr>
+<tr>
+ <td style="vertical-align: top;">Allow attribution:</td>
+ <td>
+ <InputCheckbox @bind-Value="@AllowAttribution"/>
+ <span>Allow attribution to Rory&::MatrixUtils</span>
+ <LinkButton InlineText="true" OnClick="@(() => ShowAttributionInfo = true)">?</LinkButton>
+ </td>
+</tr>
+
+@if (ShowAttributionInfo) {
+ <ModalWindow Title="Allow attribution to Rory&::MatrixUtils"
+ OnCloseClicked="@(() => ShowAttributionInfo = false)">
+ <span>This will add the following to the room creation content:</span>
+ <br/>
+ <pre>{ "gay.rory.created_using": "Rory&::MatrixUtils (https://mru.rory.gay)" }</pre>
+ <span>This is not visible to users unless they manually inspect the room's create event source.</span>
+ </ModalWindow>
+}
+
+@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<bool?>("rmu.room_create.allow_attribution") ?? true;
+ }
+
+ private static Dictionary<string, string> 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
+<tr>
+ <td style="vertical-align: top;">Initial room state:</td>
+ <td>
+ @foreach (var (displayName, events) in new Dictionary<string, List<StateEvent>>() {
+ { "Important room state (before final access rules)", roomBuilder.ImportantState },
+ { "Additional room state (after final access rules)", roomBuilder.InitialState },
+ }) {
+ <details>
+
+ @code
+ {
+ // private static readonly string[] ImplementedStates = { "m.room.avatar", "m.room.history_visibility", "m.room.guest_access", "m.room.server_acl" };
+ }
+
+ @* <summary>@displayName: @events.Count(x => !ImplementedStates.Contains(x.Type)) events</summary> *@
+ <summary>@displayName: @events.Count events</summary>
+ <table>
+ @* @foreach (var initialState in events.Where(x => !ImplementedStates.Contains(x.Type))) { *@
+ @foreach (var initialState in events) {
+ <tr>
+ <td style="vertical-align: top;">
+ @(initialState.Type):
+ @if (!string.IsNullOrEmpty(initialState.StateKey)) {
+ <br/>
+ <span>(@initialState.StateKey)</span>
+ }
+
+ </td>
+ <td>
+ <pre>@JsonSerializer.Serialize(initialState.RawContent, new JsonSerializerOptions { WriteIndented = true })</pre>
+ </td>
+ </tr>
+ }
+ </table>
+ </details>
+ }
+ </td>
+</tr>
+
+@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
+<tr>
+ <td>Invited members:</td>
+ <td>
+ <details>
+ <summary>@roomBuilder.Invites.Count members</summary>
+ <LinkButton OnClickAsync="@InviteAllSessions" InlineText="true">Invite all logged in accounts</LinkButton>
+ @foreach (var member in roomBuilder.Invites) {
+ <UserListItem _homeserver="Homeserver" UserId="@member.Key"></UserListItem>
+ <span>: </span>
+ <FancyTextBox Value="@member.Value" ValueChanged="@(val => roomBuilder.Invites[member.Key] = val)"/>
+ }
+ </details>
+ </td>
+</tr>
+<tr>
+ <td>Banned members:</td>
+ <td>
+ <details>
+ <summary>@roomBuilder.Bans.Count members</summary>
+ @foreach (var member in roomBuilder.Bans) {
+ <UserListItem _homeserver="Homeserver" UserId="@member.Key"></UserListItem>
+ <span>: </span>
+ <FancyTextBox Value="@member.Value" ValueChanged="@(val => roomBuilder.Bans[member.Key] = val)"/>
+ }
+ </details>
+ </td>
+</tr>
+
+@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
+<tr>
+ <td>Permissions:</td>
+ <details>
+ <summary>
+ @if (roomBuilder.Version is "org.matrix.hydra.11" or "12") {
+ <span>@(roomBuilder.AdditionalCreators.Count + 1) creators, </span>
+ }
+ <span>@roomBuilder.PowerLevels.Users.Count members, @roomBuilder.PowerLevels.Events.Count events</span>
+ </summary>
+
+ @if (roomBuilder.Version is "org.matrix.hydra.11" or "12") {
+ <span style="border-bottom: #444;">Creators:</span>
+ <br/>
+ <span>@Homeserver.WhoAmI.UserId (you - to change, visit <a href="/">the homepage</a>.)</span>
+ <br/>
+
+ <StringListEditor @bind-Items="@roomBuilder.AdditionalCreators"></StringListEditor>
+ <br/>
+ }
+
+ <span style="border-bottom: #444;">Events:</span><br/>
+ @foreach (var eventType in roomBuilder.PowerLevels.Events.Keys) {
+ var _event = eventType;
+ <tr>
+ <td>
+ <LinkButton InlineText="true" OnClick="@(() => {
+ roomBuilder.PowerLevels.Events.Remove(_event);
+ StateHasChanged();
+ })">-
+ </LinkButton>
+ <div style="display: inline-flex;">
+ <FancyTextBox Formatter="@GetPermissionFriendlyName"
+ Value="@_event"
+ ValueChanged="val => { roomBuilder.PowerLevels.Events.ChangeKey(_event, val); }">
+ </FancyTextBox>
+ <span>:</span>
+ </div>
+ </td>
+ <td>
+ <input type="number" value="@roomBuilder.PowerLevels.Events[_event]"
+ @oninput="val => { roomBuilder.PowerLevels.Events[_event] = int.Parse(val.Value.ToString()); }"
+ @onfocusout="@(() => { roomBuilder.PowerLevels.Events = roomBuilder.PowerLevels.Events.OrderByDescending(x => x.Value).ThenBy(x => x.Key).ToDictionary(x => x.Key, x => x.Value); })"/>
+ </td>
+ </tr>
+ }
+ <tr>
+ <td>
+ <LinkButton InlineText="true" OnClick="@(() => {
+ roomBuilder.PowerLevels.Events[""] = 0;
+ StateHasChanged();
+ })">+
+ </LinkButton>
+ </td>
+ </tr>
+
+ <span style="border-bottom: #444;">Users:</span><br/>
+ @foreach (var user in roomBuilder.PowerLevels.Users.Keys) {
+ var _user = user;
+ <tr>
+ <td>
+ <LinkButton InlineText="true" OnClick="@(() => {
+ roomBuilder.PowerLevels.Users.Remove(_user);
+ StateHasChanged();
+ })">-
+ </LinkButton>
+ <div style="display: inline-flex;">
+ <FancyTextBox Value="@_user"
+ ValueChanged="val => { roomBuilder.PowerLevels.Users.ChangeKey(_user, val); }">
+ </FancyTextBox>
+ <span>:</span>
+ </div>
+ </td>
+ <td>
+ <input type="number" value="@roomBuilder.PowerLevels.Users[_user]"
+ @oninput="val => { roomBuilder.PowerLevels.Users[_user] = int.Parse(val.Value.ToString()); }"
+ @onfocusout="@(() => { roomBuilder.PowerLevels.Users = roomBuilder.PowerLevels.Users.OrderByDescending(x => x.Value).ThenBy(x => x.Key).ToDictionary(x => x.Key, x => x.Value); })"/>
+ </td>
+ </tr>
+ }
+ <tr>
+ <td>
+ <LinkButton InlineText="true" OnClick="@(() => {
+ roomBuilder.PowerLevels.Users[""] = 0;
+ StateHasChanged();
+ })">+
+ </LinkButton>
+ </td>
+ </tr>
+ </details>
+</tr>
+
+
+@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
+<tr>
+ <td style="padding-top: 16px;">Join rules:</td>
+ <td style="padding-top: 16px;">
+ <InputSelect @bind-Value="@roomBuilder.JoinRules.JoinRuleValue">
+ <option value="public">Anyone can join</option>
+ <option value="invite">Invite only</option>
+ <option value="knock">Ask to join</option>
+ <option value="restricted">Invite only (or mutual room)</option>
+ <option value="knock_restricted">Ask to join (or mutual room)</option>
+ </InputSelect>
+ </td>
+</tr>
+<tr>
+ <td>History visibility:</td>
+ <td>
+ <InputSelect @bind-Value="@roomBuilder.HistoryVisibility.HistoryVisibility">
+ <option value="invited">Since invite</option>
+ <option value="joined">Since join</option>
+ <option value="shared">Since room creation (members only)</option>
+ <option value="world_readable">World readable (everyone)</option>
+ </InputSelect>
+ </td>
+</tr>
+<tr>
+ <td>Guest access:</td>
+ <td>
+ <InputCheckbox @bind-Value="roomBuilder.GuestAccess.IsGuestAccessEnabled"/>
+ <span>Allow guests to join</span>
+ <LinkButton InlineText="true" href="https://spec.matrix.org/v1.15/client-server-api/#guest-access" target="_blank">?</LinkButton>
+ </td>
+</tr>
+<tr>
+ <td>Server ACLs:</td>
+ <td>
+ @if (roomBuilder.ServerAcls?.Allow is null) {
+ <p>No allow rules exist!</p>
+ <LinkButton OnClick="@(() => { roomBuilder.ServerAcls!.Allow = ["*"]; })">Create sane defaults</LinkButton>
+ }
+ else {
+ <details>
+ <summary>@(roomBuilder.ServerAcls.Allow?.Count) allow rules</summary>
+ <StringListEditor @bind-Items="@roomBuilder.ServerAcls.Allow"></StringListEditor>
+ </details>
+ }
+ @if (roomBuilder.ServerAcls?.Deny is null) {
+ <p>No deny rules exist!</p>
+ <LinkButton OnClick="@(() => { roomBuilder.ServerAcls!.Deny = []; })">Create sane defaults</LinkButton>
+ }
+ else {
+ <details>
+ <summary>@(roomBuilder.ServerAcls.Deny?.Count) deny rules</summary>
+ <StringListEditor @bind-Items="@roomBuilder.ServerAcls.Deny"></StringListEditor>
+ </details>
+ }
+ </td>
+</tr>
+
+@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
+<tr>
+ <td>Room upgrade options</td>
+ <td>
+ <details>
+ <summary>Upgrading from @roomUpgrade.OldRoom.RoomId</summary>
+ <InputCheckbox @bind-Value="@roomUpgrade.UpgradeOptions.InviteMembers"></InputCheckbox>
+ <span>Invite members</span>
+ <br/>
+ <InputCheckbox @bind-Value="@roomUpgrade.UpgradeOptions.InvitePowerlevelUsers"></InputCheckbox>
+ <span>Invite users with powerlevels</span>
+ <br/>
+ <InputCheckbox @bind-Value="@roomUpgrade.UpgradeOptions.MigrateBans"></InputCheckbox>
+ <span>Copy bans (do not use with moderation bots!)</span>
+ <br/>
+ <LinkButton OnClickAsync="@(async () => {
+ await roomUpgrade.ImportAsync();
+ PageStateHasChanged();
+ })">Apply
+ </LinkButton>
+ </details>
+ </td>
+</tr>
+
+@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
<h3>RoomManagerTimeline</h3>
<hr/>
<p>Loaded @Events.Count events...</p>
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
<h3>Server info for @Homeserver</h3>
<hr/>
@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 @@
<span>Room ID: </span>
<InputText @bind-Value="@RoomId"></InputText>
<br/>
-<span>Via server: </span>
-<InputText @bind-Value="@Server"></InputText>
+<span>Via server(s), comma separated: </span>
+<InputText @bind-Value="@Servers"></InputText>
<br/>
-<LinkButton OnClick="@Join">Join</LinkButton>
+<span>Unblock room (Synapse): </span>
+<InputCheckbox @bind-Value="@Unblock"></InputCheckbox>
+<br/>
+<LinkButton OnClickAsync="@Join">Join</LinkButton>
<br/><br/>
@foreach (var line in Log) {
<pre>@line</pre>
@@ -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 @@
<span>Room ID: </span>
<InputText @bind-Value="@RoomId"></InputText>
<br/>
-<LinkButton OnClick="@Leave">Leave</LinkButton>
+<LinkButton OnClickAsync="@Leave">Leave</LinkButton>
<br/><br/>
@foreach (var line in Log) {
<p>@line</p>
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 @@
</details>
<br/>
-<LinkButton OnClick="Execute">Execute</LinkButton>
+<LinkButton OnClickAsync="Execute">Execute</LinkButton>
<br/>
@foreach (var line in Enumerable.Reverse(log)) {
<p>@line</p>
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 @@
<p>Users: </p>
<InputTextArea @bind-Value="@UserIdString"></InputTextArea>
<br/>
-<InputText @bind-Value="@ImportFromRoomId"></InputText><LinkButton OnClick="@DoImportFromRoomId">Import from room (ID)</LinkButton>
+<InputText @bind-Value="@ImportFromRoomId"></InputText><LinkButton OnClickAsync="@DoImportFromRoomId">Import from room (ID)</LinkButton>
<details>
<summary>Rooms to be searched (@rooms.Count)</summary>
@@ -21,7 +21,7 @@
}
</details>
<br/>
-<LinkButton OnClick="Execute">Execute</LinkButton>
+<LinkButton OnClickAsync="Execute">Execute</LinkButton>
<br/>
<details>
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 @@
<br/>
<span>Room ID: </span>
<InputText @bind-Value="@roomId"></InputText>
-<LinkButton OnClick="@Execute">Execute</LinkButton>
+<LinkButton OnClickAsync="@Execute">Execute</LinkButton>
<br/>
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 @@
<br/>
<span>Users:</span>
<InputTextArea @bind-Value="@roomId"></InputTextArea>
-<LinkButton OnClick="@Execute">Execute</LinkButton>
+<LinkButton OnClickAsync="@Execute">Execute</LinkButton>
<br/>
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 @@
</div>
}
<br/>
-<LinkButton OnClick="@Apply">Apply</LinkButton>
+<LinkButton OnClickAsync="@Apply">Apply</LinkButton>
@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 @@
</div>
}
<br/>
-<LinkButton OnClick="@Apply">Apply</LinkButton>
+<LinkButton OnClickAsync="@Apply">Apply</LinkButton>
@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 @@
</div>
}
<br/>
-<LinkButton OnClick="@Apply">Apply</LinkButton>
+<LinkButton OnClickAsync="@Apply">Apply</LinkButton>
@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 @@
<p>Users (regex): </p>
<InputTextArea @bind-Value="@UserIdString"></InputTextArea>
-<LinkButton OnClick="Execute">Execute</LinkButton>
+<LinkButton OnClickAsync="Execute">Execute</LinkButton>
<br/>
-<LinkButton OnClick="RemoveKicks">Remove kicks</LinkButton>
-<LinkButton OnClick="RemoveBans">Remove bans</LinkButton>
+<LinkButton OnClickAsync="RemoveKicks">Remove kicks</LinkButton>
+<LinkButton OnClickAsync="RemoveBans">Remove bans</LinkButton>
<br/>
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 @@
<br/>
<span>Room ID: </span>
<InputText @bind-Value="@roomId"></InputText>
-<LinkButton OnClick="@Execute">Execute</LinkButton>
+<LinkButton OnClickAsync="@Execute">Execute</LinkButton>
<br/>
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 @@
<br/>
<span>Users:</span>
<InputTextArea @bind-Value="@roomId"></InputTextArea>
-<LinkButton OnClick="@Execute">Execute</LinkButton>
+<LinkButton OnClickAsync="@Execute">Execute</LinkButton>
<br/>
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 @@
<br/>
<span>Room ID: </span>
<InputText @bind-Value="@RoomId"></InputText>
-<LinkButton OnClick="@Execute">Execute</LinkButton>
+<LinkButton OnClickAsync="@Execute">Execute</LinkButton>
<p>
<span><InputCheckbox @bind-Value="ChronologicalOrder"/>Chronological order</span>
<span><InputCheckbox @bind-Value="DoDisambiguate"/>Enable extended filters</span>
@@ -30,17 +30,17 @@
<span><InputCheckbox @bind-Value="ShowBans"/> bans</span>
</p>
<p>
- <LinkButton OnClick="@(async () => {
+ <LinkButton OnClickAsync="@(async () => {
ShowJoins = ShowLeaves = ShowKnocks = ShowInvites = ShowBans = false;
StateHasChanged();
})">Hide all
</LinkButton>
- <LinkButton OnClick="@(async () => {
+ <LinkButton OnClickAsync="@(async () => {
ShowJoins = ShowLeaves = ShowKnocks = ShowInvites = ShowBans = true;
StateHasChanged();
})">Show all
</LinkButton>
- <LinkButton OnClick="@(async () => {
+ <LinkButton OnClickAsync="@(async () => {
ShowJoins ^= true;
ShowLeaves ^= true;
ShowKnocks ^= true;
@@ -129,17 +129,17 @@
</p>
<p>
- <LinkButton OnClick="@(async () => {
+ <LinkButton OnClickAsync="@(async () => {
DoDisambiguate = DisambiguateProfileUpdates = DisambiguateKicks = DisambiguateUnbans = DisambiguateInviteAccepted = DisambiguateInviteRejected = DisambiguateInviteRetracted = DisambiguateKnockAccepted = DisambiguateKnockRejected = DisambiguateKnockRetracted = DisambiguateKnockActions = DisambiguateInviteActions = false;
StateHasChanged();
})">Un-disambiguate all
</LinkButton>
- <LinkButton OnClick="@(async () => {
+ <LinkButton OnClickAsync="@(async () => {
DoDisambiguate = DisambiguateProfileUpdates = DisambiguateKicks = DisambiguateUnbans = DisambiguateInviteAccepted = DisambiguateInviteRejected = DisambiguateInviteRetracted = DisambiguateKnockAccepted = DisambiguateKnockRejected = DisambiguateKnockRetracted = DisambiguateKnockActions = DisambiguateInviteActions = true;
StateHasChanged();
})">Disambiguate all
</LinkButton>
- <LinkButton OnClick="@(async () => {
+ <LinkButton OnClickAsync="@(async () => {
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 @@
<p>Set A: </p>
<InputText @bind-Value="@ImportSetASpaceId"></InputText>
-<LinkButton OnClick="@(() => AppendSet(ImportSetASpaceId, RoomsA))">Append Set A</LinkButton>
+<LinkButton OnClickAsync="@(() => AppendSet(ImportSetASpaceId, RoomsA))">Append Set A</LinkButton>
<p>Set B: </p>
<InputText @bind-Value="@ImportSetBSpaceId"></InputText>
-<LinkButton OnClick="@(() => AppendSet(ImportSetBSpaceId, RoomsB))">Append Set B</LinkButton>
+<LinkButton OnClickAsync="@(() => AppendSet(ImportSetBSpaceId, RoomsB))">Append Set B</LinkButton>
<br/>
-<LinkButton OnClick="@Execute">Execute</LinkButton>
+<LinkButton OnClickAsync="@Execute">Execute</LinkButton>
<br/>
<details>
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 @@
<InputTextArea @bind-Value="@UserIdString"></InputTextArea>
<br/>
<InputText @bind-Value="@ImportFromRoomId"></InputText>
-<LinkButton OnClick="@DoImportFromRoomId">Import from room (ID)</LinkButton>
+<LinkButton OnClickAsync="@DoImportFromRoomId">Import from room (ID)</LinkButton>
<details>
<summary>Rooms to be searched (@rooms.Count)</summary>
@@ -21,7 +21,7 @@
}
</details>
<br/>
-<LinkButton OnClick="Execute">Execute</LinkButton>
+<LinkButton OnClickAsync="Execute">Execute</LinkButton>
<br/>
<details>
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 @@
<span>User ID: </span><FancyTextBox @bind-Value="@UserId"/><br/>
<span>Room ID: </span><FancyTextBox @bind-Value="@RoomId"/><br/>
-<LinkButton OnClick="@Execute">Execute</LinkButton>
+<LinkButton OnClickAsync="@Execute">Execute</LinkButton>
<pre>@Result</pre>
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 @@
<p><InputCheckbox @bind-Value="@ChangeKnocking"/> Change knock access: <InputCheckbox @bind-Value="@Knocking"/></p>
<br/>
-<LinkButton OnClick="Execute">Execute</LinkButton>
+<LinkButton OnClickAsync="Execute">Execute</LinkButton>
<br/>
<br/>
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 @@
}
<br/>
-<LinkButton OnClick="Execute">Execute</LinkButton>
+<LinkButton OnClickAsync="Execute">Execute</LinkButton>
<br/>
@foreach (var line in Enumerable.Reverse(log)) {
<p>@line</p>
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 @@
}
<br/>
-<LinkButton OnClick="Execute">Execute</LinkButton>
+<LinkButton OnClickAsync="Execute">Execute</LinkButton>
<br/>
@foreach (var line in Enumerable.Reverse(log)) {
<p>@line</p>
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 @@
<span>Display name: </span><FancyTextBox @bind-Value="@NewProfile.DisplayName"></FancyTextBox><br/>
<span>Avatar URL: </span><FancyTextBox @bind-Value="@NewProfile.AvatarUrl"></FancyTextBox>
<InputFile OnChange="@AvatarChanged"></InputFile><br/>
- <LinkButton OnClick="@(() => UpdateProfile())">Update profile</LinkButton>
- <LinkButton OnClick="@(() => UpdateProfile(true))">Update profile (restore room overrides)</LinkButton>
+ <LinkButton OnClickAsync="@(() => UpdateProfile())">Update profile</LinkButton>
+ <LinkButton OnClickAsync="@(() => UpdateProfile(true))">Update profile (restore room overrides)</LinkButton>
</div>
</div>
@if (!string.IsNullOrWhiteSpace(Status)) {
@@ -44,7 +44,7 @@
<span>Display name: </span><FancyTextBox BackgroundColor="@(room.OwnMembership.DisplayName == OldProfile.DisplayName ? "" : "#ffff0033")" @bind-Value="@room.OwnMembership.DisplayName"></FancyTextBox><br/>
<span>Avatar URL: </span><FancyTextBox BackgroundColor="@(room.OwnMembership.AvatarUrl == OldProfile.AvatarUrl ? "" : "#ffff0033")" @bind-Value="@room.OwnMembership.AvatarUrl"></FancyTextBox>
<InputFile OnChange="@(ifcea => RoomAvatarChanged(ifcea, room.Room.RoomId))"></InputFile><br/>
- <LinkButton OnClick="@(() => UpdateRoomProfile(room.Room.RoomId))">Update profile</LinkButton>
+ <LinkButton OnClickAsync="@(() => UpdateRoomProfile(room.Room.RoomId))">Update profile</LinkButton>
</div>
<br/>
@if (!string.IsNullOrWhiteSpace(Status)) {
|