Organise tools somewhat, set proper icons for nav menu
1 files changed, 276 insertions, 0 deletions
diff --git a/MatrixUtils.Web/Pages/Tools/Moderation/MembershipHistory.razor b/MatrixUtils.Web/Pages/Tools/Moderation/MembershipHistory.razor
new file mode 100644
index 0000000..e5ba004
--- /dev/null
+++ b/MatrixUtils.Web/Pages/Tools/Moderation/MembershipHistory.razor
@@ -0,0 +1,276 @@
+@page "/Tools/Moderation/MembershipHistory"
+@using System.Collections.ObjectModel
+@using LibMatrix
+@using LibMatrix.EventTypes.Spec.State
+<h3>Membership history viewer</h3>
+<hr/>
+
+<br/>
+<span>Room ID: </span>
+<InputText @bind-Value="@roomId"></InputText>
+<LinkButton OnClick="@Execute">Execute</LinkButton>
+<p><InputCheckbox @bind-Value="ChronologicalOrder"/> Chronological order</p>
+<p>
+ <span>Show </span>
+ <InputCheckbox @bind-Value="ShowJoins"/> joins
+ <InputCheckbox @bind-Value="ShowLeaves"/> leaves
+ <InputCheckbox @bind-Value="ShowUpdates"/> profile updates
+ <InputCheckbox @bind-Value="ShowKnocks"/> knocks
+ <InputCheckbox @bind-Value="ShowInvites"/> invites
+ <InputCheckbox @bind-Value="ShowKicks"/> kicks
+ <InputCheckbox @bind-Value="ShowBans"/> bans
+</p>
+<p>
+ <LinkButton OnClick="@(async () => { ShowJoins = ShowLeaves = ShowUpdates = ShowKnocks = ShowInvites = ShowKicks = ShowBans = false; })">Hide all</LinkButton>
+ <LinkButton OnClick="@(async () => { ShowJoins = ShowLeaves = ShowUpdates = ShowKnocks = ShowInvites = ShowKicks = ShowBans = true; })">Show all</LinkButton>
+ <LinkButton OnClick="@(async () => { ShowJoins ^= true; ShowLeaves ^= true; ShowUpdates ^= true; ShowKnocks ^= true; ShowInvites ^= true; ShowKicks ^= true; ShowBans ^= true; })">Toggle all</LinkButton>
+</p>
+<p>
+ <span>Sender: </span>
+ <InputSelect @bind-Value="Sender">
+ <option value="">All</option>
+ @foreach (var sender in Memberships.Select(x => x.Sender).Distinct()) {
+ <option value="@sender">@sender</option>
+ }
+ </InputSelect>
+</p>
+<p>
+ <span>User: </span>
+ <InputSelect @bind-Value="User">
+ <option value="">All</option>
+ @foreach (var user in Memberships.Select(x => x.StateKey).Distinct()) {
+ <option value="@user">@user</option>
+ }
+ </InputSelect>
+</p>
+
+
+<br/>
+
+<details>
+ <summary>Results</summary>
+ @{
+ Dictionary<string, StateEventResponse> previousMemberships = [];
+ var filteredMemberships = Memberships.AsEnumerable();
+ if (ChronologicalOrder) {
+ filteredMemberships = filteredMemberships.Reverse();
+ }
+ if(!string.IsNullOrWhiteSpace(Sender)) {
+ filteredMemberships = filteredMemberships.Where(x => x.Sender == Sender);
+ }
+ if(!string.IsNullOrWhiteSpace(User)) {
+ filteredMemberships = filteredMemberships.Where(x => x.StateKey == User);
+ }
+
+ @foreach (var membership in filteredMemberships) {
+ RoomMemberEventContent content = membership.TypedContent as RoomMemberEventContent;
+ @switch (content.Membership) {
+ case RoomMemberEventContent.MembershipTypes.Invite: {
+ if (_showInvites) {
+ <p style="color: green;">@membership.Sender invited @membership.StateKey @(string.IsNullOrWhiteSpace(content.Reason) ? "" : $"(reason: {content.Reason})")</p>
+ }
+
+ break;
+ }
+ case RoomMemberEventContent.MembershipTypes.Ban: {
+ if (_showBans) {
+ <p style="color: red;">@membership.Sender banned @membership.StateKey @(string.IsNullOrWhiteSpace(content.Reason) ? "" : $"(reason: {content.Reason})")</p>
+ }
+
+ break;
+ }
+ case RoomMemberEventContent.MembershipTypes.Leave: {
+ if (membership.Sender == membership.StateKey) {
+ if (_showLeaves) {
+ <p style="color: #C66;">@membership.Sender left the room</p>
+ }
+ }
+ else {
+ if (_showKicks) {
+ <p style="color: darkorange;">@membership.Sender kicked @membership.StateKey @(string.IsNullOrWhiteSpace(content.Reason) ? "" : $"(reason: {content.Reason})")</p>
+ }
+ }
+
+ break;
+ }
+ case RoomMemberEventContent.MembershipTypes.Knock: {
+ if (_showKnocks) {
+ <p>@membership.Sender knocked @membership.StateKey @(string.IsNullOrWhiteSpace(content.Reason) ? "" : $"(reason: {content.Reason})")</p>
+ }
+
+ break;
+ }
+ case RoomMemberEventContent.MembershipTypes.Join: {
+ if (previousMemberships.TryGetValue(membership.StateKey, out var previous)
+ && (previous.TypedContent as RoomMemberEventContent).Membership == RoomMemberEventContent.MembershipTypes.Join) {
+ if (_showUpdates) {
+ <p style="color: #777;">@membership.Sender changed their profile</p>
+ }
+ }
+ else {
+ if (_showJoins) {
+ <p style="color: #6C6;">@membership.Sender joined the room @(string.IsNullOrWhiteSpace(content.Reason) ? "" : $"(reason: {content.Reason})")</p>
+ }
+ }
+
+ break;
+ }
+ default: {
+ <b>Unknown membership @content.Membership!</b>
+ break;
+ }
+ }
+
+ previousMemberships[membership.StateKey] = membership;
+ }
+ }
+</details>
+
+<br/>
+<details open>
+ <summary>Log</summary>
+ @foreach (var line in log.Reverse()) {
+ <pre>@line</pre>
+ }
+</details>
+
+@code {
+
+#region Filter bindings
+
+ private bool _chronologicalOrder = false;
+
+ private bool ChronologicalOrder {
+ get => _chronologicalOrder;
+ set {
+ _chronologicalOrder = value;
+ StateHasChanged();
+ }
+ }
+
+ private bool _showJoins = true;
+
+ private bool ShowJoins {
+ get => _showJoins;
+ set {
+ _showJoins = value;
+ StateHasChanged();
+ }
+ }
+
+ private bool _showLeaves = true;
+
+ private bool ShowLeaves {
+ get => _showLeaves;
+ set {
+ _showLeaves = value;
+ StateHasChanged();
+ }
+ }
+
+ private bool _showUpdates = true;
+
+ private bool ShowUpdates {
+ get => _showUpdates;
+ set {
+ _showUpdates = value;
+ StateHasChanged();
+ }
+ }
+
+ private bool _showKnocks = true;
+
+ private bool ShowKnocks {
+ get => _showKnocks;
+ set {
+ _showKnocks = value;
+ StateHasChanged();
+ }
+ }
+
+ private bool _showInvites = true;
+
+ private bool ShowInvites {
+ get => _showInvites;
+ set {
+ _showInvites = value;
+ StateHasChanged();
+ }
+ }
+
+ private bool _showKicks = true;
+
+ private bool ShowKicks {
+ get => _showKicks;
+ set {
+ _showKicks = value;
+ StateHasChanged();
+ }
+ }
+
+ private bool _showBans = true;
+
+ private bool ShowBans {
+ get => _showBans;
+ set {
+ _showBans = value;
+ StateHasChanged();
+ }
+ }
+
+ private string sender = "";
+
+ private string Sender {
+ get => sender;
+ set {
+ sender = value;
+ StateHasChanged();
+ }
+ }
+
+ private string user = "";
+
+ private string User {
+ get => user;
+ set {
+ user = value;
+ StateHasChanged();
+ }
+ }
+
+#endregion
+
+ private ObservableCollection<string> log { get; set; } = new();
+ private List<StateEventResponse> Memberships { get; set; } = [];
+ private AuthenticatedHomeserverGeneric hs { get; set; }
+
+ [Parameter, SupplyParameterFromQuery(Name = "room")]
+ public string roomId { get; set; }
+
+ protected override async Task OnInitializedAsync() {
+ log.CollectionChanged += (sender, args) => StateHasChanged();
+ hs = await RMUStorage.GetCurrentSessionOrNavigate();
+ if (hs is null) return;
+
+ StateHasChanged();
+ Console.WriteLine("Rerendered!");
+ await base.OnInitializedAsync();
+ if (!string.IsNullOrWhiteSpace(roomId))
+ await Execute();
+ }
+
+ private async Task Execute() {
+ Memberships.Clear();
+ var room = hs.GetRoom(roomId);
+ var events = room.GetManyMessagesAsync(limit: int.MaxValue, chunkSize: 5000);
+ await foreach (var resp in events) {
+ var all = resp.State.Concat(resp.Chunk);
+ Memberships.AddRange(all.Where(x => x.Type == RoomMemberEventContent.EventId));
+
+ log.Add($"{resp.State.Count} state, {resp.Chunk.Count} timeline");
+ }
+
+ StateHasChanged();
+ }
+
+}
\ No newline at end of file
|