diff --git a/.idea/.idea.MatrixUtils/.idea/indexLayout.xml b/.idea/.idea.MatrixUtils/.idea/indexLayout.xml
index d166ec4..4520708 100644
--- a/.idea/.idea.MatrixUtils/.idea/indexLayout.xml
+++ b/.idea/.idea.MatrixUtils/.idea/indexLayout.xml
@@ -2,7 +2,6 @@
<project version="4">
<component name="UserContentModel">
<attachedFolders>
- <Path>LibMatrix/LibMatrix/Homeservers</Path>
<Path>MatrixRoomUtils.Bot/bot_data</Path>
<Path>MatrixRoomUtils.Desktop/bin/Debug/net7.0/mru-desktop</Path>
</attachedFolders>
diff --git a/LibMatrix b/LibMatrix
-Subproject dae1a25664606415e054f3e3b20bbbfabdbb0e9
+Subproject e16e9f3093fab575f5f9323248e7b19fa6d5456
diff --git a/MatrixUtils.Web.Server/Program.cs b/MatrixUtils.Web.Server/Program.cs
index cad3878..59d450a 100644
--- a/MatrixUtils.Web.Server/Program.cs
+++ b/MatrixUtils.Web.Server/Program.cs
@@ -1,3 +1,6 @@
+using LibMatrix.Services;
+using MatrixUtils.Web.Classes;
+
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
@@ -5,6 +8,9 @@ var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();
+builder.Services.AddRoryLibMatrixServices();
+builder.Services.AddScoped<RmuSessionStore>();
+
var app = builder.Build();
// Configure the HTTP request pipeline.
diff --git a/MatrixUtils.Web/Pages/Rooms/PolicyList.razor b/MatrixUtils.Web/Pages/Rooms/PolicyList.razor
index b908e57..5f70187 100644
--- a/MatrixUtils.Web/Pages/Rooms/PolicyList.razor
+++ b/MatrixUtils.Web/Pages/Rooms/PolicyList.razor
@@ -58,7 +58,11 @@ else {
Console.WriteLine($"Rendered header in {renderSw.GetElapsedAndRestart()}");
- @foreach (var type in KnownPolicyTypes.Where(t => GetPolicyEventsByType(t).Count > 0).OrderByDescending(t => GetPolicyEventsByType(t).Count)) {
+ 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>
@@ -68,6 +72,7 @@ else {
</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
@@ -81,6 +86,7 @@ else {
.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>
@@ -125,7 +131,8 @@ else {
</LinkButton>
@if (CurrentUserIsDraupnir) {
<LinkButton Color="@(ActiveKicks.ContainsKey(policy) ? "#FF0000" : null)" OnClick="@(() => DraupnirKickMatching(policy))">Kick
- users @(ActiveKicks.ContainsKey(policy) ? $"({ActiveKicks[policy]})" : null)</LinkButton>
+ users @(ActiveKicks.TryGetValue(policy, out var kick) ? $"({kick})" : null)
+ </LinkButton>
}
}
}
diff --git a/MatrixUtils.Web/Shared/PolicyEditorComponents/MassPolicyEditorModal.razor b/MatrixUtils.Web/Shared/PolicyEditorComponents/MassPolicyEditorModal.razor
index 4ab899c..f7bb200 100644
--- a/MatrixUtils.Web/Shared/PolicyEditorComponents/MassPolicyEditorModal.razor
+++ b/MatrixUtils.Web/Shared/PolicyEditorComponents/MassPolicyEditorModal.razor
@@ -25,7 +25,7 @@
<br/>
<span>Entities:</span><br/>
- <InputTextArea @bind-Value="@Users" style="width: 500px;"></InputTextArea>
+ <FancyTextBox Multiline="true" @bind-Value="@Entities"></FancyTextBox>
<br/>
@@ -35,16 +35,43 @@
@* $1$ @PolicyEvent.ToJson(true, true) #1# *@
@* </pre> *@
@* </details> *@
- <LinkButton OnClick="@(() => {
- OnClose.Invoke();
- return Task.CompletedTask;
- })"> Cancel
- </LinkButton>
- <LinkButton OnClick="@(() => {
- _ = Save();
- return Task.CompletedTask;
- })"> Save
- </LinkButton>
+ @if (!VerifyIntent) {
+ <LinkButton OnClick="@(() => {
+ OnClose.Invoke();
+ return Task.CompletedTask;
+ })"> Cancel
+ </LinkButton>
+ <LinkButton OnClick="@(() => {
+ _ = Save();
+ return Task.CompletedTask;
+ })"> Save
+ </LinkButton>
+ @if (!string.IsNullOrWhiteSpace(Response)) {
+ <pre style="color: red;">@Response</pre>
+ }
+ }
+ else {
+ <b class="blink">WARNING!!!</b>
+ <br/>
+
+ @if (!string.IsNullOrWhiteSpace(Response)) {
+ <pre style="color: red;">@Response</pre>
+ }
+
+ <span>Are you sure you want to do this?</span>
+ <LinkButton Color="#00FF00" OnClick="@(() => {
+ VerifyIntent = false;
+ Response = null;
+ StateHasChanged();
+ return Task.CompletedTask;
+ })">No
+ </LinkButton>
+ <LinkButton Color="#FF0000" OnClick="@(() => {
+ _ = Save(force: true);
+ return Task.CompletedTask;
+ })"> Yes
+ </LinkButton>
+ }
</ModalWindow>
@@ -59,9 +86,20 @@
[Parameter]
public required GenericRoom Room { get; set; }
- public string Recommendation { get; set; } = "m.ban";
- public string Reason { get; set; } = "spam";
- public string Users { get; set; } = "";
+ private string Recommendation { get; set; } = "m.ban";
+ private string Reason { get; set; } = "spam";
+
+ private string Entities { get; set; } = "";
+
+ private string? Response {
+ get;
+ set {
+ field = value;
+ StateHasChanged();
+ }
+ }
+
+ private bool VerifyIntent { get; set; }
private static FrozenSet<Type> KnownPolicyTypes = StateEvent.KnownStateEventTypes.Where(x => x.IsAssignableTo(typeof(PolicyRuleEventContent))).ToFrozenSet();
@@ -74,47 +112,75 @@
private string? MappedType { get; set; }
- private async Task Save() {
- try {
- await DoActualSave();
- }
- catch (Exception e) {
- Console.WriteLine($"Failed to save: {e}");
- }
- }
-
- private async Task DoActualSave() {
+ private async Task Save(bool force = false) {
if (string.IsNullOrWhiteSpace(MappedType)) {
- Console.WriteLine("No type selected");
+ Response = "No type selected";
return;
}
- if (string.IsNullOrWhiteSpace(Users)) {
- Console.WriteLine("No users selected");
+ if (string.IsNullOrWhiteSpace(Entities)) {
+ Response = "No users selected";
return;
}
- Console.WriteLine($"Saving ---");
- Console.WriteLine($"Users = {Users}");
+ Console.WriteLine("Saving ---");
- var users = Users.Split("\n")
+ var entities = Entities.Split("\n", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
.Select(x => x.Trim())
- .Where(x => x.StartsWith('@'))
.Distinct()
.ToList();
+
+ if (!force && !Validate(entities, PolicyTypes[MappedType])) {
+ List<string> distinctTypes = entities
+ .Select(GuessType)
+ .Where(x => x != null)
+ .Distinct()
+ .Select(x => x!.Name)
+ .ToList();
+
+ VerifyIntent = true;
+ Response = $"Invalid entities. Expected {PolicyTypes[MappedType].Name}, got:\n - " +
+ string.Join("\n - ", distinctTypes);
+ return;
+ }
+
+ try {
+ await SaveAll(entities);
+ }
+ catch (Exception e) {
+ Response = $"Failed to save: {e}";
+ }
+ }
+
+ private bool Validate(List<string> entities, Type expectedType) {
+ return entities.All(x => GuessType(x) == expectedType);
+ }
+
+ private Type? GuessType(string entity) {
+ var sigil = entity[0];
+ return TypesBySigil.GetValueOrDefault(sigil.ToString(), typeof(ServerPolicyRuleEventContent));
+ }
+
+ private Dictionary<string, Type> TypesBySigil = new() {
+ { "@", typeof(UserPolicyRuleEventContent) },
+ { "!", typeof(RoomPolicyRuleEventContent) },
+ { "#", typeof(RoomPolicyRuleEventContent) }
+ };
+
+ private async Task SaveAll(List<string> entities) {
await foreach (var evt in Room.GetFullStateAsync()) {
if (evt is null
|| !AllKnownPolicyTypes.Contains(evt.Type)
|| !evt.TypedContent!.GetType().IsAssignableTo(PolicyTypes[MappedType!])
) continue;
-
+
if (evt.TypedContent is PolicyRuleEventContent content && content.Recommendation == Recommendation && content.Reason == Reason) {
- if (content.Entity != null && users.Contains(content.Entity))
- users.Remove(content.Entity);
+ if (content.Entity != null && entities.Contains(content.Entity))
+ entities.Remove(content.Entity);
}
}
-
- var tasks = users.Select(x => ExecuteBan(Room, x)).ToList();
+
+ var tasks = entities.Select(x => ExecuteBan(Room, x)).ToList();
await Task.WhenAll(tasks);
OnSaved.Invoke();
@@ -124,7 +190,7 @@
bool success = false;
while (!success) {
try {
- var content = Activator.CreateInstance(PolicyTypes[MappedType!]) as PolicyRuleEventContent;
+ var content = Activator.CreateInstance(PolicyTypes[MappedType!]) as PolicyRuleEventContent ?? throw new InvalidOperationException("Failed to create event content");
content.Recommendation = Recommendation;
content.Reason = Reason;
content.Entity = entity;
diff --git a/MatrixUtils.Web/Shared/PolicyEditorComponents/MassPolicyEditorModal.razor.css b/MatrixUtils.Web/Shared/PolicyEditorComponents/MassPolicyEditorModal.razor.css
new file mode 100644
index 0000000..49ab31b
--- /dev/null
+++ b/MatrixUtils.Web/Shared/PolicyEditorComponents/MassPolicyEditorModal.razor.css
@@ -0,0 +1,15 @@
+.blink {
+ animation: blinker 2s linear infinite;
+}
+
+@keyframes blinker {
+ 0% {
+ opacity: 1;
+ }
+ 50% {
+ opacity: 0;
+ }
+ 100% {
+ opacity: 1;
+ }
+}
\ No newline at end of file
diff --git a/MatrixUtils.Web/wwwroot/index.html b/MatrixUtils.Web/wwwroot/index.html
index 7425de2..f25d549 100644
--- a/MatrixUtils.Web/wwwroot/index.html
+++ b/MatrixUtils.Web/wwwroot/index.html
@@ -29,16 +29,6 @@
<a class="dismiss">🗙</a>
</div>
<script>
- function BlazorFocusElement(element) {
- if (element == null) return;
- if (element instanceof HTMLElement) {
- console.log(element);
- element.focus();
- } else if (element.hasOwnProperty("__internalId")) {
- console.log("Element is not an HTMLElement", element);
- }
- }
-
function getWidth(element) {
console.log("getWidth", element);
if (element == null) return 0;
diff --git a/MatrixUtils.sln b/MatrixUtils.sln
index 5fb0c1f..6b33671 100644
--- a/MatrixUtils.sln
+++ b/MatrixUtils.sln
@@ -67,6 +67,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MxApiExtensions", "MxApiExt
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Benchmarks", "Benchmarks\Benchmarks.csproj", "{CEECE820-1BA9-4E29-8668-25967B3E712B}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MatrixUtils.Web.Ssr", "MatrixUtils.Web.Ssr\MatrixUtils.Web.Ssr\MatrixUtils.Web.Ssr.csproj", "{35F510FD-98FC-4760-A53B-9176A53A33A2}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MatrixUtils.Web.Ssr.Client", "MatrixUtils.Web.Ssr\MatrixUtils.Web.Ssr.Client\MatrixUtils.Web.Ssr.Client.csproj", "{335DB9D5-FEEE-45E3-B76A-057D8BB48412}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -185,6 +189,14 @@ Global
{CEECE820-1BA9-4E29-8668-25967B3E712B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CEECE820-1BA9-4E29-8668-25967B3E712B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CEECE820-1BA9-4E29-8668-25967B3E712B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {35F510FD-98FC-4760-A53B-9176A53A33A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {35F510FD-98FC-4760-A53B-9176A53A33A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {35F510FD-98FC-4760-A53B-9176A53A33A2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {35F510FD-98FC-4760-A53B-9176A53A33A2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {335DB9D5-FEEE-45E3-B76A-057D8BB48412}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {335DB9D5-FEEE-45E3-B76A-057D8BB48412}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {335DB9D5-FEEE-45E3-B76A-057D8BB48412}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {335DB9D5-FEEE-45E3-B76A-057D8BB48412}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{84BE90C4-2FDE-4A48-B154-58926EF24846} = {933DC8A6-8B1F-46BF-9046-4B636AA46469}
|