From 3a2cdc9afe8c46ec056510277e8fd8f244ebaee1 Mon Sep 17 00:00:00 2001 From: koko Date: Fri, 22 Sep 2023 14:53:53 -0400 Subject: [PATCH] Improve moderation case page and case partial (#900) * Add pagination to moderation cases list and tweak case dismissal task * Clean up case partial and add extended case status indicators * Redirect back to cases list after dismissing a case * Fix typo on cases list queue counter * Fix dismissal queue counter * Convert dismiss button check into pattern * Turn down case dismissal task repeat interval to every 1 hour * Use page 0 for case searching * Implement pagination on the admin users list <3 * Fix pagination button padding and update colors to match existing role colors * Fix typo in admin search placeholder * Make cases searchable by user/slot ID instead of reason Due to the current state of the moderation case entity, I can't directly query against the affected user name, so I've added the ability to search for the affected user/slot ID instead of reason. * Actually apply the desired changes instead of just fixing the counts * Grammatical nitpick in the search box placeholder --- .../Controllers/Admin/AdminUserController.cs | 2 +- .../Moderator/ModerationCaseController.cs | 2 +- .../Pages/Admin/AdminPanelPage.cshtml.cs | 2 +- .../Pages/Admin/AdminPanelUsersPage.cshtml | 32 +++++-- .../Pages/Admin/AdminPanelUsersPage.cshtml.cs | 25 ++++- .../Pages/Moderation/CasePage.cshtml | 14 ++- .../Pages/Moderation/CasePage.cshtml.cs | 31 ++++-- .../Partials/ModerationCasePartial.cshtml | 95 ++++++++++++------- .../RepeatingTasks/DismissExpiredCasesTask.cs | 5 +- 9 files changed, 147 insertions(+), 61 deletions(-) diff --git a/ProjectLighthouse.Servers.Website/Controllers/Admin/AdminUserController.cs b/ProjectLighthouse.Servers.Website/Controllers/Admin/AdminUserController.cs index 00babada..3a05d089 100644 --- a/ProjectLighthouse.Servers.Website/Controllers/Admin/AdminUserController.cs +++ b/ProjectLighthouse.Servers.Website/Controllers/Admin/AdminUserController.cs @@ -108,6 +108,6 @@ public class AdminUserController : ControllerBase return this.Redirect($"/moderation/newCase?type={(int)CaseType.UserBan}&affectedId={id}"); } - return this.Redirect("/admin/users"); + return this.Redirect("/admin/users/0"); } } \ No newline at end of file diff --git a/ProjectLighthouse.Servers.Website/Controllers/Moderator/ModerationCaseController.cs b/ProjectLighthouse.Servers.Website/Controllers/Moderator/ModerationCaseController.cs index 71a92c29..b0db7021 100644 --- a/ProjectLighthouse.Servers.Website/Controllers/Moderator/ModerationCaseController.cs +++ b/ProjectLighthouse.Servers.Website/Controllers/Moderator/ModerationCaseController.cs @@ -34,6 +34,6 @@ public class ModerationCaseController : ControllerBase await this.database.SaveChangesAsync(); - return this.Ok(); + return this.Redirect($"/moderation/cases/0"); } } \ No newline at end of file diff --git a/ProjectLighthouse.Servers.Website/Pages/Admin/AdminPanelPage.cshtml.cs b/ProjectLighthouse.Servers.Website/Pages/Admin/AdminPanelPage.cshtml.cs index 345f722d..8a80e2d2 100644 --- a/ProjectLighthouse.Servers.Website/Pages/Admin/AdminPanelPage.cshtml.cs +++ b/ProjectLighthouse.Servers.Website/Pages/Admin/AdminPanelPage.cshtml.cs @@ -27,7 +27,7 @@ public class AdminPanelPage : BaseLayout if (user == null) return this.Redirect("~/login"); if (!user.IsAdmin) return this.NotFound(); - this.Statistics.Add(new AdminPanelStatistic("Users", await StatisticsHelper.UserCount(this.Database), "/admin/users")); + this.Statistics.Add(new AdminPanelStatistic("Users", await StatisticsHelper.UserCount(this.Database), "/admin/users/0")); this.Statistics.Add(new AdminPanelStatistic("Slots", await StatisticsHelper.SlotCount(this.Database, new SlotQueryBuilder()))); this.Statistics.Add(new AdminPanelStatistic("Photos", await StatisticsHelper.PhotoCount(this.Database))); this.Statistics.Add(new AdminPanelStatistic("API Keys", await StatisticsHelper.ApiKeyCount(this.Database), "/admin/keys")); diff --git a/ProjectLighthouse.Servers.Website/Pages/Admin/AdminPanelUsersPage.cshtml b/ProjectLighthouse.Servers.Website/Pages/Admin/AdminPanelUsersPage.cshtml index 16b2f571..b5f04b92 100644 --- a/ProjectLighthouse.Servers.Website/Pages/Admin/AdminPanelUsersPage.cshtml +++ b/ProjectLighthouse.Servers.Website/Pages/Admin/AdminPanelUsersPage.cshtml @@ -1,4 +1,4 @@ -@page "/admin/users" +@page "/admin/users/{pageNumber:int}" @using LBPUnion.ProjectLighthouse.Types.Entities.Profile @using LBPUnion.ProjectLighthouse.Types.Users @model LBPUnion.ProjectLighthouse.Servers.Website.Pages.Admin.AdminPanelUsersPage @@ -11,6 +11,15 @@

There are currently @Model.UserCount users registered to your instance.

Note: Users are ordered by their permissions, then by most-recent-first.

+
+
+ + +
+
+ +
+
@foreach (UserEntity user in Model.Users) { @@ -19,22 +28,21 @@ switch (user.PermissionLevel) { - // jank but works case PermissionLevel.Banned: { - color = "red"; + color = "grey"; subtitle = $"Banned user! Reason: {user.BannedReason}"; break; } case PermissionLevel.Moderator: { - color = "green"; + color = "orange"; subtitle = "Moderator"; break; } case PermissionLevel.Administrator: { - color = "yellow"; + color = "red"; subtitle = "Admin"; break; } @@ -72,4 +80,16 @@
} - \ No newline at end of file + + +
+ +@if (Model.PageNumber != 0) +{ + Previous Page +} +@(Model.PageNumber + 1) / @(Model.PageAmount) +@if (Model.PageNumber < Model.PageAmount - 1) +{ + Next Page +} \ No newline at end of file diff --git a/ProjectLighthouse.Servers.Website/Pages/Admin/AdminPanelUsersPage.cshtml.cs b/ProjectLighthouse.Servers.Website/Pages/Admin/AdminPanelUsersPage.cshtml.cs index b6ed868a..16a4ec88 100644 --- a/ProjectLighthouse.Servers.Website/Pages/Admin/AdminPanelUsersPage.cshtml.cs +++ b/ProjectLighthouse.Servers.Website/Pages/Admin/AdminPanelUsersPage.cshtml.cs @@ -1,4 +1,5 @@ #nullable enable +using LBPUnion.ProjectLighthouse.Configuration; using LBPUnion.ProjectLighthouse.Database; using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts; using LBPUnion.ProjectLighthouse.Types.Entities.Profile; @@ -12,21 +13,39 @@ public class AdminPanelUsersPage : BaseLayout public int UserCount; public List Users = new(); + + public int PageAmount; + public int PageNumber; + public string SearchValue = ""; + public AdminPanelUsersPage(DatabaseContext database) : base(database) {} - public async Task OnGet() + public async Task OnGet([FromRoute] int pageNumber, [FromQuery] string? name) { UserEntity? user = this.Database.UserFromWebRequest(this.Request); if (user == null) return this.Redirect("~/login"); if (!user.IsAdmin) return this.NotFound(); + if (string.IsNullOrWhiteSpace(name)) name = ""; + + this.SearchValue = name.Replace(" ", string.Empty); + + this.UserCount = await this.Database.Users.CountAsync(u => u.Username.Contains(this.SearchValue)); + + this.PageNumber = pageNumber; + this.PageAmount = Math.Max(1, (int)Math.Ceiling((double)this.UserCount / ServerStatics.PageSize)); + + if (this.PageNumber < 0 || this.PageNumber >= this.PageAmount) + return this.Redirect($"/admin/users/{Math.Clamp(this.PageNumber, 0, this.PageAmount - 1)}"); + this.Users = await this.Database.Users .OrderByDescending(u => u.PermissionLevel) .ThenByDescending(u => u.UserId) + .Where(u => u.Username.Contains(this.SearchValue)) + .Skip(pageNumber * ServerStatics.PageSize) + .Take(ServerStatics.PageSize) .ToListAsync(); - - this.UserCount = this.Users.Count; return this.Page(); } diff --git a/ProjectLighthouse.Servers.Website/Pages/Moderation/CasePage.cshtml b/ProjectLighthouse.Servers.Website/Pages/Moderation/CasePage.cshtml index eef4d801..1d3b5137 100644 --- a/ProjectLighthouse.Servers.Website/Pages/Moderation/CasePage.cshtml +++ b/ProjectLighthouse.Servers.Website/Pages/Moderation/CasePage.cshtml @@ -10,11 +10,11 @@ string timeZone = Model.GetTimeZone(); } -

There are @Model.CaseCount total cases, @Model.DismissedCaseCount of which have been dismissed.

+

There are @Model.CaseCount total cases, @Model.ExpiredCaseCount of which are queued for dismissal, and @Model.DismissedCaseCount of which have been dismissed.

- +
@@ -24,4 +24,14 @@ @foreach (ModerationCaseEntity @case in Model.Cases) { @(await Html.PartialAsync("Partials/ModerationCasePartial", @case, ViewData.WithTime(timeZone))) +} + +@if (Model.PageNumber != 0) +{ + Previous Page +} +@(Model.PageNumber + 1) / @(Model.PageAmount) +@if (Model.PageNumber < Model.PageAmount - 1) +{ + Next Page } \ No newline at end of file diff --git a/ProjectLighthouse.Servers.Website/Pages/Moderation/CasePage.cshtml.cs b/ProjectLighthouse.Servers.Website/Pages/Moderation/CasePage.cshtml.cs index 8be500c4..29d8f9c4 100644 --- a/ProjectLighthouse.Servers.Website/Pages/Moderation/CasePage.cshtml.cs +++ b/ProjectLighthouse.Servers.Website/Pages/Moderation/CasePage.cshtml.cs @@ -11,10 +11,11 @@ namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages.Moderation; public class CasePage : BaseLayout { public CasePage(DatabaseContext database) : base(database) - {} + { } public List Cases = new(); public int CaseCount; + public int ExpiredCaseCount; public int DismissedCaseCount; public int PageAmount; @@ -31,18 +32,30 @@ public class CasePage : BaseLayout this.SearchValue = name.Replace(" ", string.Empty); - this.Cases = await this.Database.Cases - .Include(c => c.Creator) - .Include(c => c.Dismisser) - .OrderByDescending(c => c.CaseId) - .ToListAsync(); - - this.CaseCount = await this.Database.Cases.CountAsync(c => c.Reason.Contains(this.SearchValue)); - this.DismissedCaseCount = await this.Database.Cases.CountAsync(c => c.Reason.Contains(this.SearchValue) && c.DismissedAt != null); + this.CaseCount = + await this.Database.Cases.CountAsync(c => + c.AffectedId.ToString().Contains(this.SearchValue)); + this.ExpiredCaseCount = + await this.Database.Cases.CountAsync(c => + c.AffectedId.ToString().Contains(this.SearchValue) && c.DismissedAt == null && c.ExpiresAt < DateTime.UtcNow); + this.DismissedCaseCount = + await this.Database.Cases.CountAsync(c => + c.AffectedId.ToString().Contains(this.SearchValue)&& c.DismissedAt != null); this.PageNumber = pageNumber; this.PageAmount = Math.Max(1, (int)Math.Ceiling((double)this.CaseCount / ServerStatics.PageSize)); + if (this.PageNumber < 0 || this.PageNumber >= this.PageAmount) + return this.Redirect($"/moderation/cases/{Math.Clamp(this.PageNumber, 0, this.PageAmount - 1)}"); + + this.Cases = await this.Database.Cases.Include(c => c.Creator) + .Include(c => c.Dismisser) + .Where(c => c.AffectedId.ToString().Contains(this.SearchValue)) + .OrderByDescending(c => c.CaseId) + .Skip(pageNumber * ServerStatics.PageSize) + .Take(ServerStatics.PageSize) + .ToListAsync(); + return this.Page(); } } \ No newline at end of file diff --git a/ProjectLighthouse.Servers.Website/Pages/Partials/ModerationCasePartial.cshtml b/ProjectLighthouse.Servers.Website/Pages/Partials/ModerationCasePartial.cshtml index 2f108dec..a5011055 100644 --- a/ProjectLighthouse.Servers.Website/Pages/Partials/ModerationCasePartial.cshtml +++ b/ProjectLighthouse.Servers.Website/Pages/Partials/ModerationCasePartial.cshtml @@ -7,42 +7,19 @@ @inject DatabaseContext Database @{ - string color = "blue"; - string timeZone = (string?)ViewData["TimeZone"] ?? TimeZoneInfo.Local.Id; TimeZoneInfo timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(timeZone); - if (Model.Expired) color = "yellow"; - if (Model.Dismissed) color = "green"; + string color = "red"; + + if (Model.Expired) + color = "yellow"; + if (Model.Dismissed) + color = "green"; }

Case #@Model.CaseId: @Model.Type

- - @if (Model.Dismissed) - { - Debug.Assert(Model.DismissedAt != null); - - @if (Model.Dismisser != null) - { -

- This case was dismissed by @Model.DismisserUsername on @TimeZoneInfo.ConvertTime(Model.DismissedAt.Value, timeZoneInfo).ToString("M/d/yyyy @ h:mm tt"). -

- } - else - { -

- This case was dismissed by @Model.DismisserUsername on @TimeZoneInfo.ConvertTime(Model.DismissedAt.Value, timeZoneInfo).ToString("M/d/yyyy @ h:mm tt"). -

- } - - } - else if (Model.Expired) - { -

- This case expired on @TimeZoneInfo.ConvertTime(Model.ExpiresAt!.Value, timeZoneInfo).ToString("M/d/yyyy @ h:mm tt"). -

- } @if (Model.Creator != null && Model.Creator.Username.Length != 0) { @@ -66,14 +43,15 @@ }
} - - + @if (Model.Type.AffectsLevel()) { SlotEntity? slot = await Model.GetSlotAsync(Database); if (slot != null) { -

Affected level: @slot.Name

+

+ Affected level: @slot.Name (@slot.SlotId) +

} } else if (Model.Type.AffectsUser()) @@ -81,10 +59,55 @@ UserEntity? user = await Model.GetUserAsync(Database); if (user != null) { -

Affected user: @user.Username

+

+ Affected user: @user.Username (@user.UserId) +

} } +

Case Status

+ @if (Model.Dismissed) + { + Debug.Assert(Model.DismissedAt != null); + + @if (Model.Dismisser != null) + { +
+ + + This case was dismissed by @Model.DismisserUsername on @TimeZoneInfo.ConvertTime(Model.DismissedAt.Value, timeZoneInfo).ToString("M/d/yyyy @ h:mm tt"). + +
+ } + else + { +
+ + + This case was dismissed by @Model.DismisserUsername on @TimeZoneInfo.ConvertTime(Model.DismissedAt.Value, timeZoneInfo).ToString("M/d/yyyy @ h:mm tt"). + +
+ } + } + else if (Model.Expired) + { +
+ + + This case expired on @TimeZoneInfo.ConvertTime(Model.ExpiresAt!.Value, timeZoneInfo).ToString("M/d/yyyy @ h:mm tt") and has been queued for dismissal. + +
+ } + else + { +
+ + + This case is currently active and will expire on @TimeZoneInfo.ConvertTime(Model.ExpiresAt!.Value, timeZoneInfo).ToString("M/d/yyyy @ h:mm tt"). + +
+ } +

Reason

@if (!string.IsNullOrWhiteSpace(Model.Reason)) { @@ -94,7 +117,7 @@ {
No reason was provided.
} - +

Moderator Notes

@if (!string.IsNullOrWhiteSpace(Model.ModeratorNotes)) { @@ -104,8 +127,8 @@ {
No notes were provided.
} - - @if (!Model.Dismissed) + + @if (Model is { Dismissed: false, Expired: false, }) { diff --git a/ProjectLighthouse/Administration/Maintenance/RepeatingTasks/DismissExpiredCasesTask.cs b/ProjectLighthouse/Administration/Maintenance/RepeatingTasks/DismissExpiredCasesTask.cs index 84b282ad..e6620662 100644 --- a/ProjectLighthouse/Administration/Maintenance/RepeatingTasks/DismissExpiredCasesTask.cs +++ b/ProjectLighthouse/Administration/Maintenance/RepeatingTasks/DismissExpiredCasesTask.cs @@ -14,7 +14,7 @@ namespace LBPUnion.ProjectLighthouse.Administration.Maintenance.RepeatingTasks; public class DismissExpiredCasesTask : IRepeatingTask { public string Name => "Dismiss Expired Cases"; - public TimeSpan RepeatInterval => TimeSpan.FromHours(4); + public TimeSpan RepeatInterval => TimeSpan.FromHours(1); public DateTime LastRan { get; set; } public async Task Run(DatabaseContext database) @@ -31,7 +31,8 @@ public class DismissExpiredCasesTask : IRepeatingTask foreach (ModerationCaseEntity @case in expiredCases) { - @case.DismissedAt = DateTime.Now; + @case.DismissedAt = DateTime.UtcNow; + @case.DismisserUsername = "maintenance task"; Logger.Info($"Dismissed expired case {@case.CaseId}", LogArea.Maintenance); }