From d33eb7c8965737db468804d3705a886e7db08dc5 Mon Sep 17 00:00:00 2001 From: Rory& Date: Thu, 11 Jan 2024 07:31:54 +0100 Subject: Policy list editor rewrite --- MatrixRoomUtils.Web/Pages/Rooms/PolicyList.razor | 421 +++++++++++------------ 1 file changed, 199 insertions(+), 222 deletions(-) (limited to 'MatrixRoomUtils.Web/Pages/Rooms') diff --git a/MatrixRoomUtils.Web/Pages/Rooms/PolicyList.razor b/MatrixRoomUtils.Web/Pages/Rooms/PolicyList.razor index dbe0648..b89d59c 100644 --- a/MatrixRoomUtils.Web/Pages/Rooms/PolicyList.razor +++ b/MatrixRoomUtils.Web/Pages/Rooms/PolicyList.razor @@ -1,192 +1,124 @@ @page "/Rooms/{RoomId}/Policies" @using LibMatrix -@using LibMatrix.Homeservers @using ArcaneLibs.Extensions @using LibMatrix.EventTypes.Spec.State @using LibMatrix.EventTypes.Spec.State.Policy @using System.Diagnostics -@using System.Diagnostics.CodeAnalysis -@using LibMatrix.Extensions -@using LibMatrix.Responses -

Policy list editor - Editing @RoomId

-
+@using LibMatrix.RoomTypes +@using System.Collections.Frozen +@using System.Reflection +@using ArcaneLibs.Attributes +@using LibMatrix.EventTypes -

- This policy list contains @GetPolicyCount(typeof(ServerPolicyRuleEventContent)) server bans, - @GetPolicyCount(typeof(RoomPolicyRuleEventContent)) room bans and - @GetPolicyCount(typeof(UserPolicyRuleEventContent)) user bans. - @foreach (var (key, value) in PolicyEventsByType) { -

@key.Name: @value.Count

- } -

- +@using MatrixRoomUtils.Web.Shared.PolicyEditorComponents -

Server policies

+

Policy list editor - Editing @RoomId


-@if (!GetPolicyEventsByType(typeof(ServerPolicyRuleEventContent)).Any()) { -

No server policies

-} -else { - - - - - - - - - - - @foreach (var policyEvent in GetValidPolicyEventsByType(typeof(ServerPolicyRuleEventContent))) { - var policyData = policyEvent.TypedContent as PolicyRuleEventContent; - - - - - - - } - -
ServerReasonExpiresActions
- Entity: @policyData.Entity -
State: @policyEvent.StateKey
-
@policyData.Reason - @policyData.ExpiryDateTime - - - @* *@ -
-
- Redacted or invalid events - - - - - - - - - @foreach (var policyEvent in GetInvalidPolicyEventsByType(typeof(ServerPolicyRuleEventContent))) { - - - - - } - -
State keySerialised Contents
@policyEvent.StateKey@policyEvent.RawContent.ToJson(false, true)
-
+@* *@ +Create new policy + +@if (Loading) { +

Loading...

} -

Room policies

-
-@if (!GetPolicyEventsByType(typeof(RoomPolicyRuleEventContent)).Any()) { -

No room policies

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

No policies yet

} else { - - - - - - - - - - - @foreach (var policyEvent in GetValidPolicyEventsByType(typeof(RoomPolicyRuleEventContent))) { - var policyData = policyEvent.TypedContent as PolicyRuleEventContent; - - - - - - - } - -
RoomReasonExpiresActions
Entity: @policyData.Entity
State: @policyEvent.StateKey
@policyData.Reason - @policyData.ExpiryDateTime - - -
-
- Redacted or invalid events - - - - - - - - - @foreach (var policyEvent in GetInvalidPolicyEventsByType(typeof(RoomPolicyRuleEventContent))) { + @foreach (var (type, value) in PolicyEventsByType) { +

+ @(GetValidPolicyEventsByType(type).Count) active, + @(GetInvalidPolicyEventsByType(type).Count) invalid + (@value.Count total) + @(GetPolicyTypeName(type).ToLower()) +

+ } + + @foreach (var type in KnownPolicyTypes.OrderByDescending(t => GetPolicyEventsByType(t).Count)) { +
+ + + @($"{GetPolicyTypeName(type)}: {GetPolicyEventsByType(type).Count} policies") + +
+
+
State keySerialised Contents
+ @{ + var policies = GetValidPolicyEventsByType(type); + var invalidPolicies = GetInvalidPolicyEventsByType(type); + // enumerate all properties with friendly name + var props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(x => (x.GetFriendlyNameOrNull() ?? x.GetJsonPropertyNameOrNull()) is not null) + .Where(x => x.GetCustomAttribute() is null) + .ToFrozenSet(); + var propNames = props.Select(x => x.GetFriendlyNameOrNull() ?? x.GetJsonPropertyName()!).ToFrozenSet(); + } + - - + @foreach (var name in propNames) { + + } + - } - -
@policyEvent.StateKey@policyEvent.RawContent!.ToJson(false, true)@nameActions
-
-} -

User policies

-
-@if (!GetPolicyEventsByType(typeof(UserPolicyRuleEventContent)).Any()) { -

No user policies

-} -else { - - - - @if (EnableAvatars) { - - } - - - - - - - - @foreach (var policyEvent in GetValidPolicyEventsByType(typeof(UserPolicyRuleEventContent))) { - var policyData = policyEvent.TypedContent as PolicyRuleEventContent; - - @if (EnableAvatars) { - + @foreach (var policy in policies.OrderBy(x => x.RawContent?["entity"]?.GetValue())) { + + @{ + var typedContent = policy.TypedContent!; + var proxySafeProps = typedContent.GetType().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()}"); } - + @foreach (var prop in proxySafeProps ?? Enumerable.Empty()) { + + } + + } - - - - - - } - -
UserReasonExpiresActions
- @if (Avatars.ContainsKey(policyData.Entity)) { - + +
@prop.GetGetMethod()?.Invoke(typedContent, null) +
+ @if (PowerLevels.UserHasStatePermission(Homeserver.WhoAmI.UserId, policy.Type)) { + Edit + Remove + @if (policy.IsLegacyType) { + Update policy type + } + } +
+
Entity: @string.Join("", policyData.Entity.Take(64))
State: @string.Join("", policyEvent.StateKey.Take(64))
@policyData.Reason - @policyData.ExpiryDateTime - - -
-
- Redacted or invalid events - - - - - - - - - @foreach (var policyEvent in GetInvalidPolicyEventsByType(typeof(UserPolicyRuleEventContent))) { - - - - - } - -
State keySerialised Contents
@policyEvent.StateKey@policyEvent.RawContent.ToJson(false, true)
-
+ + +
+ + + @("Invalid " + GetPolicyTypeName(type).ToLower()) + + + + + + + + + + + @foreach (var policy in invalidPolicies) { + + + + + } + +
State keyJson contents
@policy.StateKey +
@policy.RawContent.ToJson(true, false)
+
+
+ + } +} + +@if (CurrentlyEditingEvent is not null) { + } @code { @@ -197,47 +129,59 @@ else { private const bool Debug = false; #endif + private bool Loading { get; set; } = true; //get room list // - sync withroom list filter // Type = support.feline.msc3784 //support.feline.policy.lists.msc.v1 [Parameter] - public string? RoomId { get; set; } + public string RoomId { get; set; } = null!; private bool _enableAvatars; + private StateEventResponse? _currentlyEditingEvent; - static readonly Dictionary Avatars = new(); + // static readonly Dictionary Avatars = new(); // static readonly Dictionary Servers = new(); // private static List PolicyEvents { get; set; } = new(); private Dictionary> PolicyEventsByType { get; set; } = new(); - public bool EnableAvatars { - get => _enableAvatars; + private StateEventResponse? CurrentlyEditingEvent { + get => _currentlyEditingEvent; set { - _enableAvatars = value; - if (value) GetAllAvatars(); + _currentlyEditingEvent = value; + StateHasChanged(); } } + // public bool EnableAvatars { + // get => _enableAvatars; + // set { + // _enableAvatars = value; + // if (value) GetAllAvatars(); + // } + // } + + private AuthenticatedHomeserverGeneric Homeserver { get; set; } + private GenericRoom Room { get; set; } + private RoomPowerLevelEventContent PowerLevels { get; set; } + protected override async Task OnInitializedAsync() { var sw = Stopwatch.StartNew(); await base.OnInitializedAsync(); - var hs = await MRUStorage.GetCurrentSessionOrNavigate(); - if (hs is null) return; - RoomId = RoomId.Replace('~', '.'); + Homeserver = (await MRUStorage.GetCurrentSessionOrNavigate())!; + if (Homeserver is null) return; + Room = Homeserver.GetRoom(RoomId!); + PowerLevels = (await Room.GetPowerLevelsAsync())!; await LoadStatesAsync(); Console.WriteLine($"Policy list editor initialized in {sw.Elapsed}!"); } private async Task LoadStatesAsync() { - var hs = await MRUStorage.GetCurrentSessionOrNavigate(); - if (hs is null) return; - - var room = hs.GetRoom(RoomId); - - var states = room.GetFullStateAsync(); + Loading = true; + var states = Room.GetFullStateAsync(); + PolicyEventsByType.Clear(); await foreach (var state in states) { if (state is null) continue; if (!state.MappedType.IsAssignableTo(typeof(PolicyRuleEventContent))) continue; @@ -245,46 +189,79 @@ else { PolicyEventsByType[state.MappedType].Add(state); } + Loading = false; StateHasChanged(); } - private async Task GetAllAvatars() { - // if (!_enableAvatars) return; - Console.WriteLine("Getting avatars..."); - var users = GetValidPolicyEventsByType(typeof(UserPolicyRuleEventContent)).Select(x => x.RawContent!["entity"]!.GetValue()).Where(x => x.Contains(':') && !x.Contains("*")).ToList(); - Console.WriteLine($"Got {users.Count} users!"); - var usersByHomeServer = users.GroupBy(x => x!.Split(':')[1]).ToDictionary(x => x.Key!, x => x.ToList()); - Console.WriteLine($"Got {usersByHomeServer.Count} homeservers!"); - var homeserverTasks = usersByHomeServer.Keys.Select(x => RemoteHomeserver.TryCreate(x)).ToAsyncEnumerable(); - await foreach (var server in homeserverTasks) { - if (server is null) continue; - var profileTasks = usersByHomeServer[server.BaseUrl].Select(x => TryGetProfile(server, x)).ToList(); - await Task.WhenAll(profileTasks); - profileTasks.RemoveAll(x => x.Result is not { Value: { AvatarUrl: not null } }); - foreach (var profile in profileTasks.Select(x => x.Result!.Value)) { - // if (profile is null) continue; - if (!string.IsNullOrWhiteSpace(profile.Value.AvatarUrl)) { - var url = await hsResolver.ResolveMediaUri(server.BaseUrl, profile.Value.AvatarUrl); - Avatars.TryAdd(profile.Key, url); - } - else Avatars.TryAdd(profile.Key, null); - } - StateHasChanged(); - } + // private async Task GetAllAvatars() { + // // if (!_enableAvatars) return; + // Console.WriteLine("Getting avatars..."); + // var users = GetValidPolicyEventsByType(typeof(UserPolicyRuleEventContent)).Select(x => x.RawContent!["entity"]!.GetValue()).Where(x => x.Contains(':') && !x.Contains("*")).ToList(); + // Console.WriteLine($"Got {users.Count} users!"); + // var usersByHomeServer = users.GroupBy(x => x!.Split(':')[1]).ToDictionary(x => x.Key!, x => x.ToList()); + // Console.WriteLine($"Got {usersByHomeServer.Count} homeservers!"); + // var homeserverTasks = usersByHomeServer.Keys.Select(x => RemoteHomeserver.TryCreate(x)).ToAsyncEnumerable(); + // await foreach (var server in homeserverTasks) { + // if (server is null) continue; + // var profileTasks = usersByHomeServer[server.BaseUrl].Select(x => TryGetProfile(server, x)).ToList(); + // await Task.WhenAll(profileTasks); + // profileTasks.RemoveAll(x => x.Result is not { Value: { AvatarUrl: not null } }); + // foreach (var profile in profileTasks.Select(x => x.Result!.Value)) { + // // if (profile is null) continue; + // if (!string.IsNullOrWhiteSpace(profile.Value.AvatarUrl)) { + // var url = await hsResolver.ResolveMediaUri(server.BaseUrl, profile.Value.AvatarUrl); + // Avatars.TryAdd(profile.Key, url); + // } + // else Avatars.TryAdd(profile.Key, null); + // } + // + // StateHasChanged(); + // } + // } + // + // private async Task?> TryGetProfile(RemoteHomeserver server, string mxid) { + // try { + // return new KeyValuePair(mxid, await server.GetProfileAsync(mxid)); + // } + // catch { + // return null; + // } + // } + + private List GetPolicyEventsByType(Type type) => PolicyEventsByType.ContainsKey(type) ? PolicyEventsByType[type] : []; + + private List GetValidPolicyEventsByType(Type type) => GetPolicyEventsByType(type) + .Where(x => !string.IsNullOrWhiteSpace(x.RawContent?["entity"]?.GetValue())).ToList(); + + private List GetInvalidPolicyEventsByType(Type type) => GetPolicyEventsByType(type) + .Where(x => string.IsNullOrWhiteSpace(x.RawContent?["entity"]?.GetValue())).ToList(); + + private string? GetPolicyTypeNameOrNull(Type type) => type.GetFriendlyNamePluralOrNull() + ?? type.GetCustomAttributes() + .FirstOrDefault(x => !string.IsNullOrWhiteSpace(x.EventName))?.EventName; + + 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?> TryGetProfile(RemoteHomeserver server, string mxid) { - try { - return new KeyValuePair(mxid, await server.GetProfileAsync(mxid)); - } - catch { - return null; - } + private async Task UpdatePolicyAsync(StateEventResponse policyEvent) { + await Room.SendStateEventAsync(policyEvent.Type, policyEvent.StateKey, policyEvent.RawContent); + await LoadStatesAsync(); } - private List GetPolicyEventsByType(Type type) => PolicyEventsByType.ContainsKey(type) ? PolicyEventsByType[type] : []; - private List GetValidPolicyEventsByType(Type type) => GetPolicyEventsByType(type).Where(x => !string.IsNullOrWhiteSpace(x.RawContent?["entity"]?.GetValue())).ToList(); - private List GetInvalidPolicyEventsByType(Type type) => GetPolicyEventsByType(type).Where(x => string.IsNullOrWhiteSpace(x.RawContent?["entity"]?.GetValue())).ToList(); - private int GetPolicyCount(Type type) => PolicyEventsByType.ContainsKey(type) ? PolicyEventsByType[type].Count : 0; + private async Task UpgradePolicyAsync(StateEventResponse policyEvent) { + policyEvent.RawContent["upgraded_from_type"] = policyEvent.Type; + await LoadStatesAsync(); + } + + private static FrozenSet KnownPolicyTypes = StateEvent.KnownStateEventTypes.Where(x => x.IsAssignableTo(typeof(PolicyRuleEventContent))).ToFrozenSet(); + + // event types, unnamed + private static Dictionary PolicyTypes = KnownPolicyTypes + .ToDictionary(x => x.GetCustomAttributes().First(y => !string.IsNullOrWhiteSpace(y.EventName)).EventName, x => x); } \ No newline at end of file -- cgit 1.4.1