about summary refs log tree commit diff
path: root/MatrixUtils.Web/Pages/Rooms
diff options
context:
space:
mode:
authorRory& <root@rory.gay>2025-09-21 16:03:44 +0200
committerRory& <root@rory.gay>2025-09-21 16:03:44 +0200
commitd105d1a7ec709ddb6240a286bbd7be292a9acd1c (patch)
treea7d174479553fbe23b51a66172a3f5429e78e708 /MatrixUtils.Web/Pages/Rooms
parentRoom upgrade CLI changes, policy list work (diff)
downloadMatrixUtils-master.tar.xz
Various fixes HEAD master
Diffstat (limited to 'MatrixUtils.Web/Pages/Rooms')
-rw-r--r--MatrixUtils.Web/Pages/Rooms/PolicyList.razor203
-rw-r--r--MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListCategoryComponent.razor55
-rw-r--r--MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListEditorHeader.razor47
-rw-r--r--MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListRowComponent.razor92
-rw-r--r--MatrixUtils.Web/Pages/Rooms/Space.razor3
5 files changed, 291 insertions, 109 deletions
diff --git a/MatrixUtils.Web/Pages/Rooms/PolicyList.razor b/MatrixUtils.Web/Pages/Rooms/PolicyList.razor

index 412d8c9..5876861 100644 --- a/MatrixUtils.Web/Pages/Rooms/PolicyList.razor +++ b/MatrixUtils.Web/Pages/Rooms/PolicyList.razor
@@ -5,22 +5,26 @@ @using System.Diagnostics @using LibMatrix.RoomTypes @using System.Collections.Frozen +@using System.Collections.Immutable @using System.Reflection @using System.Text.Json @using ArcaneLibs.Attributes +@using ArcaneLibs.Blazor.Components.Services @using LibMatrix.EventTypes @using LibMatrix.EventTypes.Interop.Draupnir @using LibMatrix.EventTypes.Spec.State.RoomInfo @using SpawnDev.BlazorJS.WebWorkers @using MatrixUtils.Web.Pages.Rooms.PolicyListComponents +@using SpawnDev.BlazorJS @inject WebWorkerService WebWorkerService @inject ILogger<PolicyList> logger +@inject BlazorJSRuntime JsRuntime @if (!IsInitialised) { <p>Connecting to homeserver...</p> } else { - <PolicyListEditorHeader Room="@Room" ReloadStateAsync="@(() => LoadStateAsync(true))"></PolicyListEditorHeader> + <PolicyListEditorHeader Room="@Room" @bind-RenderEventInfo="@RenderEventInfo" ReloadStateAsync="@(() => LoadStateAsync(true))"></PolicyListEditorHeader> @if (Loading) { <p>Loading...</p> } @@ -39,14 +43,36 @@ else { </p> } + @if (DuplicateBans?.ActivePolicies.Count > 0) { + <p style="color: orange;"> + Found @DuplicateBans.Value.ActivePolicies.Count duplicate bans + </p> + } + + @if (RedundantBans?.ActivePolicies.Count > 0) { + <p style="color: orange;"> + Found @RedundantBans.Value.ActivePolicies.Count redundant bans + </p> + } + // logger.LogInformation($"Rendered header in {renderSw.GetElapsedAndRestart()}"); // 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 (DuplicateBans?.ActivePolicies.Count > 0) { + <PolicyListCategoryComponent RenderInvalidSection="false" RenderEventInfo="@RenderEventInfo" PolicyCollection="@DuplicateBans.Value" + Room="@Room"></PolicyListCategoryComponent> + } + + @if (RedundantBans?.ActivePolicies.Count > 0) { + <PolicyListCategoryComponent RenderInvalidSection="false" RenderEventInfo="@RenderEventInfo" PolicyCollection="@RedundantBans.Value" + Room="@Room"></PolicyListCategoryComponent> + } + foreach (var collection in PolicyCollections.Values.OrderByDescending(x => x.ActivePolicies.Count)) { - <PolicyListCategoryComponent PolicyCollection="@collection" Room="@Room"></PolicyListCategoryComponent> + <PolicyListCategoryComponent RenderInvalidSection="false" RenderEventInfo="@RenderEventInfo" PolicyCollection="@collection" Room="@Room"></PolicyListCategoryComponent> } // foreach (var type in policiesByType) { @@ -119,7 +145,7 @@ else { // } // logger.LogInformation($"Rendered policies in {renderSw.GetElapsedAndRestart()}"); - logger.LogInformation($"Rendered in {renderTotalSw.Elapsed}"); + logger.LogInformation("Rendered in {TimeSpan}", renderTotalSw.Elapsed); } } @@ -138,7 +164,13 @@ else { public required string RoomId { get; set; } [Parameter, SupplyParameterFromQuery] - public bool RenderEventInfo { get; set; } + public bool RenderEventInfo { + get; + set { + field = value; + StateHasChanged(); + } + } private Dictionary<Type, List<StateEventResponse>> PolicyEventsByType { get; set; } = new(); @@ -157,6 +189,19 @@ else { public Dictionary<StateEventResponse, int> ActiveKicks { get; set; } = []; + private static FrozenSet<Type> KnownPolicyTypes = StateEvent.KnownStateEventTypes.Where(x => x.IsAssignableTo(typeof(PolicyRuleEventContent))).ToFrozenSet(); + + // event types, unnamed + // private static Dictionary<string, Type> PolicyTypes = KnownPolicyTypes + // .ToDictionary(x => x.GetCustomAttributes<MatrixEventAttribute>().First(y => !string.IsNullOrWhiteSpace(y.EventName)).EventName, x => x); + // + // private static Dictionary<Type, string[]> PolicyTypeIds = KnownPolicyTypes + // .ToDictionary(x => x, x => x.GetCustomAttributes<MatrixEventAttribute>().Select(y => y.EventName).ToArray()); + + Dictionary<Type, PolicyCollection> PolicyCollections { get; set; } = new(); + PolicyCollection? DuplicateBans { get; set; } + PolicyCollection? RedundantBans { get; set; } + protected override async Task OnInitializedAsync() { var sw = Stopwatch.StartNew(); await base.OnInitializedAsync(); @@ -172,18 +217,19 @@ else { StateHasChanged(); await LoadStateAsync(firstLoad: true); Loading = false; - logger.LogInformation($"Policy list editor initialized in {sw.Elapsed}!"); + logger.LogInformation("Policy list editor initialized in {SwElapsed}!", sw.Elapsed); } private async Task LoadStateAsync(bool firstLoad = false) { // preload workers in task pool // await Task.WhenAll(Enumerable.Range(0, WebWorkerService.MaxWorkerCount).Select(async _ => (await WebWorkerService.TaskPool.GetWorkerAsync()).WhenReady).ToList()); + var taskPoolReadyTask = WebWorkerService.TaskPool.SetWorkerCount(WebWorkerService.MaxWorkerCount); 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}"); + logger.LogInformation("LoadStatesAsync: Loaded state in {SwElapsed}", sw.Elapsed); foreach (var type in KnownPolicyTypes) { if (!PolicyCollections.ContainsKey(type)) { @@ -197,7 +243,7 @@ else { 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})"); + logger.LogInformation("{Count} proxy safe props found in {TypeFullName} ({TimeSpan})", proxySafeProps?.Count, type.FullName, filterPropSw.Elapsed); PolicyCollections.Add(type, new() { Name = type.GetFriendlyNamePluralOrNull() ?? type.FullName ?? type.Name, ActivePolicies = [], @@ -210,9 +256,11 @@ else { 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 (count % 100 == 0) + logger.LogInformation("Processing state #{Count:000000} {EvtType} @ {SwElapsed} (took {ParseSwElapsed:c} so far to process)", count, evt.Type, sw.Elapsed, parseSw.Elapsed); + count++; + if (!mappedType.IsAssignableTo(typeof(PolicyRuleEventContent))) continue; var collection = PolicyCollections[mappedType]; @@ -239,21 +287,24 @@ else { } } - logger.LogInformation($"LoadStatesAsync: Processed state in {sw.Elapsed}"); + logger.LogInformation("LoadStatesAsync: Processed state in {SwElapsed}", 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("Policy collection {KeyFullName} has {ActivePoliciesCount} active and {RemovedPoliciesCount} removed policies.", collection.Key.FullName, collection.Value.ActivePolicies.Count, collection.Value.RemovedPolicies.Count); } + await Task.Delay(1); + Loading = false; StateHasChanged(); await Task.Delay(100); + // return; logger.LogInformation("LoadStatesAsync: Scanning for redundant policies..."); var scanSw = Stopwatch.StartNew(); - var allPolicyInfos = PolicyCollections.Values - .SelectMany(x => x.ActivePolicies.Values) - .ToList(); + // var allPolicyInfos = PolicyCollections.Values + // .SelectMany(x => x.ActivePolicies.Values) + // .ToArray(); // var allPolicies = allPolicyInfos // .Select<PolicyCollection.PolicyInfo, (PolicyCollection.PolicyInfo PolicyInfo, PolicyRuleEventContent TypedContent)>(x => (x, (x.Policy.TypedContent as PolicyRuleEventContent)!)) // .ToList(); @@ -290,28 +341,71 @@ else { // } // } - Console.WriteLine($"Scanning for redundant policies in {allPolicyInfos.Count} total policies... ({scanSw.Elapsed})"); + int scanningPolicyCount = 0; + var aggregatedPolicies = PolicyCollections.Values + .Aggregate(new List<StateEventResponse>(), (acc, val) => { + acc.AddRange(val.ActivePolicies.Select(x => x.Value.Policy)); + return acc; + }); + Console.WriteLine($"Scanning for redundant policies in {aggregatedPolicies.Count} total policies... ({scanSw.Elapsed})"); List<Task<List<PolicyCollection.PolicyInfo>>> tasks = []; // try to save some load... - var policiesJson = JsonSerializer.Serialize(allPolicyInfos.Select(x => x.Policy)); - var ranges = Enumerable.Range(0, allPolicyInfos.Count).DistributeSequentially(WebWorkerService.MaxWorkerCount); - foreach (var range in ranges) - tasks.Add(WebWorkerService.TaskPool.Invoke(CheckDuplicatePoliciesAsync, policiesJson, range.First(), range.Last())); - + var policiesJson = JsonSerializer.Serialize(aggregatedPolicies); + var policiesJsonMarshalled = JsRuntime.ReturnMe<SpawnDev.BlazorJS.JSObjects.String>(policiesJson); + var ranges = Enumerable.Range(0, aggregatedPolicies.Count).DistributeSequentially(WebWorkerService.MaxWorkerCount); + await taskPoolReadyTask; + tasks.AddRange(ranges.Select(range => WebWorkerService.TaskPool.Invoke(CheckDuplicatePoliciesAsync, policiesJsonMarshalled, range.First(), range.Last()))); + + Console.WriteLine($"Main: started {tasks.Count} workers in {scanSw.Elapsed}"); // tasks.Add(CheckDuplicatePoliciesAsync(allPolicyInfos, range.First() .. range.Last())); + // var allPolicyEvents = aggregatedPolicies.Select(x => x.Policy).ToList(); + + DuplicateBans = new() { + Name = "Duplicate bans", + ViewType = PolicyCollection.SpecialViewType.Duplicates, + ActivePolicies = [], + RemovedPolicies = [], + PropertiesToDisplay = PolicyCollections.SelectMany(x => x.Value.PropertiesToDisplay).DistinctBy(x => x.Key).ToFrozenDictionary() + }; + + RedundantBans = new() { + Name = "Redundant bans", + ViewType = PolicyCollection.SpecialViewType.Redundant, + ActivePolicies = [], + RemovedPolicies = [], + PropertiesToDisplay = PolicyCollections.SelectMany(x => x.Value.PropertiesToDisplay).DistinctBy(x => x.Key).ToFrozenDictionary() + }; + + var allPolicyInfos = PolicyCollections.Values + .SelectMany(x => x.ActivePolicies.Values) + .ToArray(); + await foreach (var modifiedPolicyInfos in tasks.ToAsyncEnumerable()) { - Console.WriteLine($"Main: got {modifiedPolicyInfos.Count} modified policies from worker, time: {scanSw.Elapsed}"); + if (modifiedPolicyInfos.Count == 0) continue; + var applySw = Stopwatch.StartNew(); + // Console.WriteLine($"Main: got {modifiedPolicyInfos.Count} modified policies from worker, time: {scanSw.Elapsed}"); foreach (var modifiedPolicyInfo in modifiedPolicyInfos) { var original = allPolicyInfos.First(p => p.Policy.EventId == modifiedPolicyInfo.Policy.EventId); - original.DuplicatedBy = modifiedPolicyInfo.DuplicatedBy; - original.MadeRedundantBy = modifiedPolicyInfo.MadeRedundantBy; + original.DuplicatedBy = aggregatedPolicies.Where(x => modifiedPolicyInfo.DuplicatedBy.Any(y => StateEvent.Equals(x, y))).ToList(); + original.MadeRedundantBy = aggregatedPolicies.Where(x => modifiedPolicyInfo.MadeRedundantBy.Any(y => StateEvent.Equals(x, y))).ToList(); + modifiedPolicyInfo.DuplicatedBy = modifiedPolicyInfo.MadeRedundantBy = []; // Early dereference + if (original.DuplicatedBy.Count > 0) { + if (!DuplicateBans.Value.ActivePolicies.ContainsKey((original.Policy.Type, original.Policy.StateKey!))) + DuplicateBans.Value.ActivePolicies.Add((original.Policy.Type, original.Policy.StateKey!), original); + } + + if (original.MadeRedundantBy.Count > 0) { + if (!RedundantBans.Value.ActivePolicies.ContainsKey((original.Policy.Type, original.Policy.StateKey!))) + RedundantBans.Value.ActivePolicies.Add((original.Policy.Type, original.Policy.StateKey!), original); + } + // Console.WriteLine($"Memory usage: {Util.BytesToString(GC.GetTotalMemory(false))}"); } - Console.WriteLine($"Processed {modifiedPolicyInfos.Count} modified policies in {scanSw.Elapsed}"); + Console.WriteLine($"Main: Processed {modifiedPolicyInfos.Count} modified policies in {scanSw.Elapsed} (applied in {applySw.Elapsed})"); } - Console.WriteLine($"Processed {allPolicyInfos.Count} policies in {scanSw.Elapsed}"); + Console.WriteLine($"Processed {allPolicyInfos.Length} policies in {scanSw.Elapsed}"); // // scan for wildcard matches // foreach (var policy in allPolicies) { @@ -381,9 +475,17 @@ else { } [return: WorkerTransfer] + private static async Task<List<PolicyCollection.PolicyInfo>> CheckDuplicatePoliciesAsync(SpawnDev.BlazorJS.JSObjects.String policiesJson, int start, int end) { + var policies = JsonSerializer.Deserialize<List<StateEventResponse>>(policiesJson.ValueOf()); + Console.WriteLine($"Got request to check duplicate policies in range {start} to {end} (length: {end - start}), {policiesJson.ValueOf().Length} bytes of JSON ({policies!.Count} policies)"); + return await CheckDuplicatePoliciesAsync(policies!, start .. end); + } + + [return: WorkerTransfer] private static async Task<List<PolicyCollection.PolicyInfo>> CheckDuplicatePoliciesAsync(string policiesJson, int start, int end) { - Console.WriteLine($"Got request to check duplicate policies in range {start} to {end} (length: {end - start}), {policiesJson.Length} bytes of JSON"); - return await CheckDuplicatePoliciesAsync(JsonSerializer.Deserialize<List<StateEventResponse>>(policiesJson), start .. end); + var policies = JsonSerializer.Deserialize<List<StateEventResponse>>(policiesJson); + Console.WriteLine($"Got request to check duplicate policies in range {start} to {end} (length: {end - start}), {policiesJson.Length} bytes of JSON ({policies!.Count} policies)"); + return await CheckDuplicatePoliciesAsync(policies!, start .. end); } [return: WorkerTransfer] @@ -392,6 +494,8 @@ else { [return: WorkerTransfer] private static async Task<List<PolicyCollection.PolicyInfo>> CheckDuplicatePoliciesAsync(List<StateEventResponse> policies, Range range) { + var sw = Stopwatch.StartNew(); + var jsConsole = App.Host.Services.GetService<JsConsoleService>()!; Console.WriteLine($"Processing policies in range {range} ({range.GetOffsetAndLength(policies.Count).Length}) with {policies.Count} total policies"); var allPolicies = policies .Select(x => (Event: x, TypedContent: (x.TypedContent as PolicyRuleEventContent)!)) @@ -410,10 +514,23 @@ else { Console.WriteLine($"Sanity check failed: Found same type and state key for two different policies: {policyEvent.RawContent.ToJson()} and {otherPolicyEvent.RawContent.ToJson()}"); continue; // same type and state key } + // if(!policyContent.IsHashedRule()) + if (!string.IsNullOrWhiteSpace(policyContent.Entity) && policyContent.Entity == otherPolicyContent.Entity) { + // Console.WriteLine($"Found duplicate policy: {policyEvent.EventId} is duplicated by {otherPolicyEvent.EventId}"); + duplicatedBy.Add(otherPolicyEvent); + } } if (duplicatedBy.Count > 0 || madeRedundantBy.Count > 0) { + var summary = $"Policy {policyEvent.EventId} is:"; + if (duplicatedBy.Count > 0) + summary += $"\n- Duplicated by {duplicatedBy.Count} policies: {string.Join(", ", duplicatedBy.Select(x => x.EventId))}"; + if (madeRedundantBy.Count > 0) + summary += $"\n- Made redundant by {madeRedundantBy.Count} policies: {string.Join(", ", madeRedundantBy.Select(x => x.EventId))}"; + // Console.WriteLine(summary); + await jsConsole.Info(summary); + await Task.Delay(1); modifiedPolicies.Add(new() { Policy = policyEvent, DuplicatedBy = duplicatedBy, @@ -424,6 +541,8 @@ else { // await Task.Delay(1); } + await jsConsole.Info($"Worker: Found {modifiedPolicies.Count} modified policies in range {range} (length: {range.GetOffsetAndLength(policies.Count).Length}) in {sw.Elapsed}"); + return modifiedPolicies; } @@ -437,7 +556,7 @@ else { var states = await Room.GetFullStateAsListAsync(); // PolicyEventsByType.Clear(); - logger.LogInformation($"LoadStatesAsync: Loaded state in {sw.Elapsed}"); + logger.LogInformation("LoadStatesAsync: Loaded state in {SwElapsed}", sw.Elapsed); foreach (var type in KnownPolicyTypes) { if (!PolicyEventsByType.ContainsKey(type)) @@ -463,7 +582,7 @@ else { } 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})"); + logger.LogInformation("[E] LoadStatesAsync: Processed state #{I:000000} {StateType} @ {SwElapsed} (e1={TimeSpan:c}, e2={E2:c}, e3={E3:c}, e4={E4:c}, e5={Zero:c},t={SpswElapsed:c})", count++, state.Type, sw.Elapsed, e1, e2, e3, e4, TimeSpan.Zero, _spsw.Elapsed); continue; } @@ -473,25 +592,25 @@ else { 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})"); + logger.LogInformation("[M] LoadStatesAsync: Processed state #{I:000000} {StateType} @ {SwElapsed} (e1={TimeSpan:c}, e2={E2:c}, e3={E3:c}, e4={E4:c}, e5={E5:c}, e6={E6:c},t={TimeSpan1:c})", count++, state.Type, sw.Elapsed, e1, e2, e3, e4, e5, e6, t); } 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})"); + logger.LogInformation("[N] LoadStatesAsync: Processed state #{I:000000} {StateType} @ {SwElapsed} (e1={TimeSpan:c}, e2={E2:c}, e3={Zero:c}, e4={TimeSpan1:c}, e5={Zero1:c}, e6={TimeSpan2:c}, t={TimeSpan3:c})", count++, state.Type, sw.Elapsed, e1, e2, TimeSpan.Zero, TimeSpan.Zero, TimeSpan.Zero, TimeSpan.Zero, t); } // await Task.Delay(10); // await Task.Yield(); } - logger.LogInformation($"LoadStatesAsync: Processed state in {sw.Elapsed}"); + logger.LogInformation("LoadStatesAsync: Processed state in {SwElapsed}", sw.Elapsed); Loading = false; StateHasChanged(); await Task.Delay(10); await Task.Yield(); - logger.LogInformation($"LoadStatesAsync: yield finished in {sw.Elapsed}"); + logger.LogInformation("LoadStatesAsync: yield finished in {SwElapsed}", sw.Elapsed); } private List<StateEventResponse> GetPolicyEventsByType(Type type) => PolicyEventsByType.ContainsKey(type) ? PolicyEventsByType[type] : []; @@ -511,19 +630,9 @@ else { private string GetPolicyTypeName(Type type) => GetPolicyTypeNameOrNull(type) ?? type.Name; - private static FrozenSet<Type> KnownPolicyTypes = StateEvent.KnownStateEventTypes.Where(x => x.IsAssignableTo(typeof(PolicyRuleEventContent))).ToFrozenSet(); - - // event types, unnamed - private static Dictionary<string, Type> PolicyTypes = KnownPolicyTypes - .ToDictionary(x => x.GetCustomAttributes<MatrixEventAttribute>().First(y => !string.IsNullOrWhiteSpace(y.EventName)).EventName, x => x); - - private static Dictionary<Type, string[]> PolicyTypeIds = KnownPolicyTypes - .ToDictionary(x => x, x => x.GetCustomAttributes<MatrixEventAttribute>().Select(y => y.EventName).ToArray()); - - Dictionary<Type, PolicyCollection> PolicyCollections { get; set; } = new(); - public struct PolicyCollection { public required string Name { get; init; } + public SpecialViewType ViewType { get; init; } public int TotalCount => ActivePolicies.Count + RemovedPolicies.Count; public required Dictionary<(string Type, string StateKey), PolicyInfo> ActivePolicies { get; set; } @@ -537,6 +646,12 @@ else { public required List<StateEventResponse> MadeRedundantBy { get; set; } public required List<StateEventResponse> DuplicatedBy { get; set; } } + + public enum SpecialViewType { + None, + Duplicates, + Redundant, + } } // private struct PolicyStats { diff --git a/MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListCategoryComponent.razor b/MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListCategoryComponent.razor
index b52e03f..932e0fe 100644 --- a/MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListCategoryComponent.razor +++ b/MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListCategoryComponent.razor
@@ -10,43 +10,45 @@ <table class="table table-striped table-hover table-bordered align-middle"> <thead> <tr> + <th>Actions</th> @foreach (var name in PolicyCollection.PropertiesToDisplay!.Keys) { <th>@name</th> } - <th>Actions</th> </tr> </thead> <tbody> @foreach (var policy in PolicyCollection.ActivePolicies.Values.OrderBy(x => x.Policy.RawContent?["entity"]?.GetValue<string>())) { - <PolicyListRowComponent RenderEventInfo="RenderEventInfo" PolicyInfo="@policy" PolicyCollection="@PolicyCollection" Room="@Room"></PolicyListRowComponent> + <PolicyListRowComponent PolicyCollectionStateHasChanged="@StateHasChanged" RenderEventInfo="RenderEventInfo" 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) { + @if (RenderInvalidSection) { + <details> + <summary> + <u> + @("Invalid " + PolicyCollection.Name.ToLower()) + </u> + </summary> + <table class="table table-striped table-hover table-bordered align-middle"> + <thead> <tr> - <td>@policy.Policy.StateKey</td> - <td> - <pre>@policy.Policy.RawContent.ToJson(true, false)</pre> - </td> + <th>State key</th> + <th>Json contents</th> </tr> - } - </tbody> - </table> - </details> + </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 { @@ -60,6 +62,9 @@ [Parameter] public bool RenderEventInfo { get; set; } + [Parameter] + public bool RenderInvalidSection { get; set; } = true; + protected override bool ShouldRender() { // if (PolicyCollection is null) return false; diff --git a/MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListEditorHeader.razor b/MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListEditorHeader.razor
index e82f17d..8585561 100644 --- a/MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListEditorHeader.razor +++ b/MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListEditorHeader.razor
@@ -12,14 +12,14 @@ <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 + CurrentlyEditingEvent = new() { Type = "", RawContent = new() }; + return Task.CompletedTask; + })">Create new policy </LinkButton> <LinkButton OnClickAsync="@(() => { - MassCreatePolicies = true; - return Task.CompletedTask; - })">Create many new policies + MassCreatePolicies = true; + return Task.CompletedTask; + })">Create many new policies </LinkButton> <LinkButton OnClickAsync="@(() => ReloadStateAsync())">Refresh</LinkButton> @@ -33,20 +33,43 @@ // _ = LoadStatesAsync(); })"></MassPolicyEditorModal> } +<br/> +<InputCheckbox Value="@RenderEventInfo" ValueChanged="@RenderEventInfoChanged" ValueExpression="@(() => RenderEventInfo)"/> +<span> Render event info</span> @code { + [Parameter] public required GenericRoom Room { get; set; } - + [Parameter] public required Func<Task> ReloadStateAsync { get; set; } + [Parameter] + public required bool RenderEventInfo { get; set; } + + [Parameter] + public required EventCallback<bool> RenderEventInfoChanged { get; set; } + private string? RoomName { get; set; } private string? RoomAlias { get; set; } private string? DraupnirShortcode { get; set; } - - private StateEventResponse? CurrentlyEditingEvent { get; set { field = value; StateHasChanged(); } } - private bool MassCreatePolicies { get; set { field = value; StateHasChanged(); } } + + private StateEventResponse? CurrentlyEditingEvent { + get; + set { + field = value; + StateHasChanged(); + } + } + + private bool MassCreatePolicies { + get; + set { + field = value; + StateHasChanged(); + } + } protected override async Task OnInitializedAsync() { await Task.WhenAll( @@ -54,12 +77,12 @@ 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!"); + 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
index 9ac5077..cd432c9 100644 --- a/MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListRowComponent.razor +++ b/MatrixUtils.Web/Pages/Rooms/PolicyListComponents/PolicyListRowComponent.razor
@@ -1,4 +1,5 @@ @using System.Reflection +@using ArcaneLibs.Extensions @using LibMatrix @using LibMatrix.EventTypes.Spec.State.Policy @using LibMatrix.RoomTypes @@ -6,30 +7,6 @@ @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.DuplicatedBy) { - <br/> - <span>Duplicated by @dup.FriendlyTypeName.ToLower() <a href="@Anchor(dup.EventId!)">@TruncateMxid(dup.RawContent["entity"]?.GetValue<string>())</a></span> - } - @foreach (var dup in PolicyInfo.MadeRedundantBy) { - <br/> - <span>Also matched by @dup.FriendlyTypeName.ToLower() <a href="@Anchor(dup.EventId!)">@TruncateMxid(dup.RawContent["entity"]?.GetValue<string>())</a></span> - } - @if (RenderEventInfo) { - <br/> - <pre> - @PolicyInfo.Policy.Type/@PolicyInfo.Policy.StateKey by @PolicyInfo.Policy.Sender at @PolicyInfo.Policy.OriginServerTs - </pre> - } - </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)) { *@ @@ -41,7 +18,7 @@ </LinkButton> <LinkButton OnClickAsync="@RemovePolicyAsync">Remove</LinkButton> @if (Policy.IsLegacyType) { - <LinkButton OnClickAsync="@RemovePolicyAsync">Update policy type</LinkButton> + <LinkButton OnClickAsync="@RemovePolicyAsync">Update type</LinkButton> } @if (TypedContent.Entity?.StartsWith("@*:", StringComparison.Ordinal) == true) { @@ -66,6 +43,30 @@ } </div> </td> + @foreach (var prop in PolicyCollection.PropertiesToDisplay.Values) { + if (prop.Name == "Entity") { + <td> + <span>@TruncateMxid(TypedContent.Entity)</span> + @foreach (var dup in PolicyInfo.DuplicatedBy) { + <br/> + <span>Duplicated by @dup.FriendlyTypeName.ToLower() <a href="@Anchor(dup.EventId!)">@TruncateMxid(dup.RawContent["entity"]?.GetValue<string>())</a></span> + } + @foreach (var dup in PolicyInfo.MadeRedundantBy) { + <br/> + <span>Also matched by @dup.FriendlyTypeName.ToLower() <a href="@Anchor(dup.EventId!)">@TruncateMxid(dup.RawContent["entity"]?.GetValue<string>())</a></span> + } + @if (RenderEventInfo) { + <br/> + <pre style="margin-bottom: unset;"> + @PolicyInfo.Policy.Type/@PolicyInfo.Policy.StateKey by @PolicyInfo.Policy.Sender at @PolicyInfo.Policy.OriginServerTimestamp + </pre> + } + </td> + } + else { + <td>@prop.GetGetMethod()?.Invoke(TypedContent, null)</td> + } + } </tr> @if (IsEditing) { @@ -95,6 +96,9 @@ [Parameter] public bool RenderEventInfo { get; set; } + [Parameter] + public required Action PolicyCollectionStateHasChanged { get; set; } + private StateEventResponse Policy => PolicyInfo.Policy; private bool IsEditing { @@ -142,8 +146,42 @@ private async Task RemovePolicyAsync() { await Room.SendStateEventAsync(Policy.Type, Policy.StateKey, new { }); - IsVisible = false; - StateHasChanged(); + bool shouldUpdateVisibility = true; + PolicyCollection.ActivePolicies.Remove((Policy.Type, Policy.StateKey)); + PolicyCollection.RemovedPolicies.Add((Policy.Type, Policy.StateKey), PolicyInfo); + if (PolicyInfo.DuplicatedBy.Count > 0) { + foreach (var evt in PolicyInfo.DuplicatedBy) { + var matchingEntry = PolicyCollection.ActivePolicies + .FirstOrDefault(x => StateEvent.Equals(x.Value.Policy, evt)).Value; + var removals = matchingEntry.DuplicatedBy.RemoveAll(x => StateEvent.Equals(x, Policy)); + Console.WriteLine($"Removed {removals} duplicates from {evt.EventId}, matching entry: {matchingEntry.ToJson()}"); + if (PolicyCollection.ViewType == PolicyList.PolicyCollection.SpecialViewType.Duplicates && matchingEntry.DuplicatedBy.Count == 0) { + PolicyCollection.ActivePolicies.Remove((matchingEntry.Policy.Type, matchingEntry.Policy.StateKey)); + PolicyCollection.RemovedPolicies.Add((matchingEntry.Policy.Type, matchingEntry.Policy.StateKey), matchingEntry); + Console.WriteLine($"Also removed {matchingEntry.Policy.EventId} as it is now redundant"); + } + } + + PolicyCollectionStateHasChanged(); + shouldUpdateVisibility = false; + } + + if (PolicyInfo.MadeRedundantBy.Count > 0) { + foreach (var evt in PolicyInfo.MadeRedundantBy) { + var matchingEntry = PolicyCollection.ActivePolicies + .FirstOrDefault(x => StateEvent.Equals(x.Value.Policy, evt)).Value; + var removals = matchingEntry.MadeRedundantBy.RemoveAll(x => StateEvent.Equals(x, Policy)); + Console.WriteLine($"Removed {removals} redundants from {evt.EventId}, matching entry: {matchingEntry.ToJson()}"); + } + + PolicyCollectionStateHasChanged(); + shouldUpdateVisibility = false; + } + + if (shouldUpdateVisibility) { + IsVisible = false; + StateHasChanged(); + } // PolicyEventsByType[policyEvent.MappedType].Remove(policyEvent); // await LoadStatesAsync(); } diff --git a/MatrixUtils.Web/Pages/Rooms/Space.razor b/MatrixUtils.Web/Pages/Rooms/Space.razor
index 86a4c13..fc9c9bf 100644 --- a/MatrixUtils.Web/Pages/Rooms/Space.razor +++ b/MatrixUtils.Web/Pages/Rooms/Space.razor
@@ -120,7 +120,7 @@ var room = Room!.Homeserver.GetRoom(roomId); if (room is null) return; try { - await room.JoinAsync(ServersInSpace.ToArray()); + await room.JoinAsync(ServersInSpace.Take(10).ToArray()); var joined = false; while (!joined) { var ce = await room.GetCreateEventAsync(); @@ -132,6 +132,7 @@ } } joined = true; + await Task.Delay(1000); } } catch (Exception e) {