diff options
author | Rory& <root@rory.gay> | 2024-01-11 07:31:54 +0100 |
---|---|---|
committer | Rory& <root@rory.gay> | 2024-01-11 07:31:54 +0100 |
commit | d33eb7c8965737db468804d3705a886e7db08dc5 (patch) | |
tree | ac520011de25b6fe6d0708594c15141559b21fe9 /MatrixRoomUtils.Web | |
parent | Internal changes to policy list viewer (extensibility), fix duplicating chang... (diff) | |
download | MatrixUtils-d33eb7c8965737db468804d3705a886e7db08dc5.tar.xz |
Policy list editor rewrite
Diffstat (limited to 'MatrixRoomUtils.Web')
-rw-r--r-- | MatrixRoomUtils.Web/Pages/Rooms/PolicyList.razor | 421 | ||||
-rw-r--r-- | MatrixRoomUtils.Web/Shared/PolicyEditorComponents/PolicyEditorModal.razor | 92 |
2 files changed, 291 insertions, 222 deletions
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 -<h3>Policy list editor - Editing @RoomId</h3> -<hr/> +@using LibMatrix.RoomTypes +@using System.Collections.Frozen +@using System.Reflection +@using ArcaneLibs.Attributes +@using LibMatrix.EventTypes -<p> - 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) { - <p>@key.Name: @value.Count</p> - } -</p> -<InputCheckbox @bind-Value="EnableAvatars"></InputCheckbox><label>Enable avatars (WILL EXPOSE YOUR IP TO TARGET HOMESERVERS!)</label> +@using MatrixRoomUtils.Web.Shared.PolicyEditorComponents -<h3>Server policies</h3> +<h3>Policy list editor - Editing @RoomId</h3> <hr/> -@if (!GetPolicyEventsByType(typeof(ServerPolicyRuleEventContent)).Any()) { - <p>No server policies</p> -} -else { - <table class="table table-striped table-hover" style="width: fit-content;"> - <thead> - <tr> - <th style="max-width: 50vw;">Server</th> - <th>Reason</th> - <th>Expires</th> - <th>Actions</th> - </tr> - </thead> - <tbody> - @foreach (var policyEvent in GetValidPolicyEventsByType(typeof(ServerPolicyRuleEventContent))) { - var policyData = policyEvent.TypedContent as PolicyRuleEventContent; - <tr> - <td> - <span>Entity: @policyData.Entity</span> - <span><br/>State: @policyEvent.StateKey</span> - </td> - <td>@policyData.Reason</td> - <td> - @policyData.ExpiryDateTime - </td> - <td> - <button class="btn" @* @onclick="async () => await RemovePolicyAsync(policyEvent)" *@>Edit</button> - @* <button class="btn btn-danger" $1$ @onclick="async () => await RemovePolicyAsync(policyEvent)" #1#>Remove</button> *@ - </td> - </tr> - } - </tbody> - </table> - <details> - <summary>Redacted or invalid events</summary> - <table class="table table-striped table-hover" style="width: fit-content;"> - <thead> - <tr> - <th style="max-width: 50vw;">State key</th> - <th>Serialised Contents</th> - </tr> - </thead> - <tbody> - @foreach (var policyEvent in GetInvalidPolicyEventsByType(typeof(ServerPolicyRuleEventContent))) { - <tr> - <td>@policyEvent.StateKey</td> - <td>@policyEvent.RawContent.ToJson(false, true)</td> - </tr> - } - </tbody> - </table> - </details> +@* <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> + +@if (Loading) { + <p>Loading...</p> } -<h3>Room policies</h3> -<hr/> -@if (!GetPolicyEventsByType(typeof(RoomPolicyRuleEventContent)).Any()) { - <p>No room policies</p> +else if (PolicyEventsByType is not { Count: > 0 }) { + <p>No policies yet</p> } else { - <table class="table table-striped table-hover" style="width: fit-content;"> - <thead> - <tr> - <th style="max-width: 50vw;">Room</th> - <th>Reason</th> - <th>Expires</th> - <th>Actions</th> - </tr> - </thead> - <tbody> - @foreach (var policyEvent in GetValidPolicyEventsByType(typeof(RoomPolicyRuleEventContent))) { - var policyData = policyEvent.TypedContent as PolicyRuleEventContent; - <tr> - <td>Entity: @policyData.Entity<br/>State: @policyEvent.StateKey</td> - <td>@policyData.Reason</td> - <td> - @policyData.ExpiryDateTime - </td> - <td> - <button class="btn btn-danger" @* @onclick="async () => await RemovePolicyAsync(policyEvent)" *@>Remove</button> - </td> - </tr> - } - </tbody> - </table> - <details> - <summary>Redacted or invalid events</summary> - <table class="table table-striped table-hover" style="width: fit-content;"> - <thead> - <tr> - <th style="max-width: 50vw;">State key</th> - <th>Serialised Contents</th> - </tr> - </thead> - <tbody> - @foreach (var policyEvent in GetInvalidPolicyEventsByType(typeof(RoomPolicyRuleEventContent))) { + @foreach (var (type, value) in PolicyEventsByType) { + <p> + @(GetValidPolicyEventsByType(type).Count) active, + @(GetInvalidPolicyEventsByType(type).Count) invalid + (@value.Count total) + @(GetPolicyTypeName(type).ToLower()) + </p> + } + + @foreach (var type in KnownPolicyTypes.OrderByDescending(t => GetPolicyEventsByType(t).Count)) { + <details> + <summary> + <span> + @($"{GetPolicyTypeName(type)}: {GetPolicyEventsByType(type).Count} policies") + </span> + <hr style="margin: revert;"/> + </summary> + <table class="table table-striped table-hover" style="width: fit-content; border-width: 1px; vertical-align: middle;"> + @{ + 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(); + } + <thead> <tr> - <td>@policyEvent.StateKey</td> - <td>@policyEvent.RawContent!.ToJson(false, true)</td> + @foreach (var name in propNames) { + <th style="border-width: 1px">@name</th> + } + <th style="border-width: 1px">Actions</th> </tr> - } - </tbody> - </table> - </details> -} -<h3>User policies</h3> -<hr/> -@if (!GetPolicyEventsByType(typeof(UserPolicyRuleEventContent)).Any()) { - <p>No user policies</p> -} -else { - <table class="table table-striped table-hover" style="width: fit-content;"> - <thead> - <tr> - @if (EnableAvatars) { - <th></th> - } - <th style="max-width: 0.2vw; word-wrap: anywhere;">User</th> - <th>Reason</th> - <th>Expires</th> - <th>Actions</th> - </tr> - </thead> - <tbody> - @foreach (var policyEvent in GetValidPolicyEventsByType(typeof(UserPolicyRuleEventContent))) { - var policyData = policyEvent.TypedContent as PolicyRuleEventContent; - <tr> - @if (EnableAvatars) { - <td> - @if (Avatars.ContainsKey(policyData.Entity)) { - <img class="avatar48" src="@Avatars[policyData.Entity]"/> + </thead> + <tbody style="border-width: 1px;"> + @foreach (var policy in policies.OrderBy(x => x.RawContent?["entity"]?.GetValue<string>())) { + <tr> + @{ + 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()}"); } - </td> + @foreach (var prop in proxySafeProps ?? Enumerable.Empty<PropertyInfo>()) { + <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> + } + } + </div> + </td> + </tr> } - <td style="word-wrap: anywhere;">Entity: @string.Join("", policyData.Entity.Take(64))<br/>State: @string.Join("", policyEvent.StateKey.Take(64))</td> - <td>@policyData.Reason</td> - <td> - @policyData.ExpiryDateTime - </td> - <td> - <button class="btn btn-danger" @* @onclick="async () => await RemovePolicyAsync(policyEvent)" *@>Remove</button> - </td> - </tr> - } - </tbody> - </table> - <details> - <summary>Redacted or invalid events</summary> - <table class="table table-striped table-hover" style="width: fit-content;"> - <thead> - <tr> - <th>State key</th> - <th>Serialised Contents</th> - </tr> - </thead> - <tbody> - @foreach (var policyEvent in GetInvalidPolicyEventsByType(typeof(UserPolicyRuleEventContent))) { - <tr> - <td>@policyEvent.StateKey</td> - <td>@policyEvent.RawContent.ToJson(false, true)</td> - </tr> - } - </tbody> - </table> - </details> + </tbody> + </table> + <details> + <summary> + <u> + @("Invalid " + GetPolicyTypeName(type).ToLower()) + </u> + </summary> + <table class="table table-striped table-hover" style="width: fit-content; border-width: 1px; vertical-align: middle;"> + <thead> + <tr> + <th style="border-width: 1px">State key</th> + <th style="border-width: 1px">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> + } +} + +@if (CurrentlyEditingEvent is not null) { + <PolicyEditorModal PolicyEvent="@CurrentlyEditingEvent" OnClose="@(() => CurrentlyEditingEvent = null)" OnSave="@(e => UpdatePolicyAsync(e))"></PolicyEditorModal> } @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<string, string?> Avatars = new(); + // static readonly Dictionary<string, string?> Avatars = new(); // static readonly Dictionary<string, RemoteHomeserver> Servers = new(); // private static List<StateEventResponse> PolicyEvents { get; set; } = new(); private Dictionary<Type, List<StateEventResponse>> 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<string>()).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<string>()).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<KeyValuePair<string, UserProfileResponse>?> TryGetProfile(RemoteHomeserver server, string mxid) { + // try { + // return new KeyValuePair<string, UserProfileResponse>(mxid, await server.GetProfileAsync(mxid)); + // } + // catch { + // return null; + // } + // } + + 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?["entity"]?.GetValue<string>())).ToList(); + + private List<StateEventResponse> GetInvalidPolicyEventsByType(Type type) => GetPolicyEventsByType(type) + .Where(x => string.IsNullOrWhiteSpace(x.RawContent?["entity"]?.GetValue<string>())).ToList(); + + private string? GetPolicyTypeNameOrNull(Type type) => type.GetFriendlyNamePluralOrNull() + ?? type.GetCustomAttributes<MatrixEventAttribute>() + .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<KeyValuePair<string, UserProfileResponse>?> TryGetProfile(RemoteHomeserver server, string mxid) { - try { - return new KeyValuePair<string, UserProfileResponse>(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<StateEventResponse> GetPolicyEventsByType(Type type) => PolicyEventsByType.ContainsKey(type) ? PolicyEventsByType[type] : []; - private List<StateEventResponse> GetValidPolicyEventsByType(Type type) => GetPolicyEventsByType(type).Where(x => !string.IsNullOrWhiteSpace(x.RawContent?["entity"]?.GetValue<string>())).ToList(); - private List<StateEventResponse> GetInvalidPolicyEventsByType(Type type) => GetPolicyEventsByType(type).Where(x => string.IsNullOrWhiteSpace(x.RawContent?["entity"]?.GetValue<string>())).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<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); } \ No newline at end of file diff --git a/MatrixRoomUtils.Web/Shared/PolicyEditorComponents/PolicyEditorModal.razor b/MatrixRoomUtils.Web/Shared/PolicyEditorComponents/PolicyEditorModal.razor new file mode 100644 index 0000000..4fd151d --- /dev/null +++ b/MatrixRoomUtils.Web/Shared/PolicyEditorComponents/PolicyEditorModal.razor @@ -0,0 +1,92 @@ +@using LibMatrix.EventTypes.Spec.State.Policy +@using System.Reflection +@using ArcaneLibs.Attributes +@using ArcaneLibs.Extensions +@using LibMatrix +@using System.Collections.Frozen +@using LibMatrix.EventTypes +<ModalWindow Title="@((string.IsNullOrWhiteSpace(PolicyEvent.EventId) ? "Creating new " : "Editing ") + (PolicyEvent.MappedType.GetFriendlyNameOrNull()?.ToLower() ?? "event"))" + OnCloseClicked="@OnClose" X="60" Y="60" MinWidth="300"> + @{ + var policyData = (PolicyEvent.TypedContent as PolicyRuleEventContent)!; + } + @if (string.IsNullOrWhiteSpace(PolicyEvent.EventId)) { + <span>Policy type:</span> + <select @bind="@PolicyEvent.Type"> + <option>Select a value</option> + @foreach (var (type, mappedType) in PolicyTypes) { + <option value="@type">@mappedType.GetFriendlyName().ToLower()</option> + } + </select> + } + + + @{ + // enumerate all properties with friendly name + var props = PolicyEvent.MappedType.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(); + } + <table> + <thead style="border-bottom: solid #ffffff44 1px;"> + <tr> + <th>Property</th> + <th>Value</th> + </tr> + </thead> + <tbody> + @foreach (var prop in props) { + <tr> + <td style="padding-right: 8px;"> + <span>@prop.GetFriendlyName()</span> + @if (Nullable.GetUnderlyingType(prop.PropertyType) is not null) { + <span style="color: red;">*</span> + } + </td> + @{ + var getter = prop.GetGetMethod(); + var setter = prop.GetSetMethod(); + } + @switch (Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType) { + case Type t when t == typeof(string): + <FancyTextBox Value="@(getter?.Invoke(policyData, null) as string)" ValueChanged="@(e => { Console.WriteLine($"{prop.Name} ({setter is not null}) -> {e}"); setter?.Invoke(policyData, [e]); StateHasChanged(); })"></FancyTextBox> + break; + default: + <p style="color: red;">Unsupported type: @prop.PropertyType</p> + break; + } + </tr> + } + </tbody> + </table> + <br/> + <pre> + @PolicyEvent.ToJson(true, false) + </pre> + <LinkButton OnClick="@(() => { OnClose.Invoke(); return Task.CompletedTask; })">Cancel</LinkButton> + <LinkButton OnClick="@(() => { OnSave.Invoke(PolicyEvent); return Task.CompletedTask; })">Save</LinkButton> + @* <span>Target entity: </span> *@ + @* <FancyTextBox @bind-Value="@policyData.Entity"></FancyTextBox><br/> *@ + @* <span>Reason: </span> *@ + @* <FancyTextBox @bind-Value="@policyData.Reason"></FancyTextBox> *@ +</ModalWindow> + +@code { + + [Parameter] + public StateEventResponse? PolicyEvent { get; set; } + + [Parameter] + public required Action OnClose { get; set; } + + [Parameter] + public required Action<StateEventResponse> OnSave { get; set; } + + private static FrozenSet<Type> KnownPolicyTypes = StateEvent.KnownStateEventTypes.Where(x => x.IsAssignableTo(typeof(PolicyRuleEventContent))).ToFrozenSet(); + + private static Dictionary<string, Type> PolicyTypes = KnownPolicyTypes + .ToDictionary(x => x.GetCustomAttributes<MatrixEventAttribute>().First(y => !string.IsNullOrWhiteSpace(y.EventName)).EventName, x => x); + +} \ No newline at end of file |