From d01ea094b35359e91713c4d71e1aeb51289559c8 Mon Sep 17 00:00:00 2001 From: Jason Sylvestre Date: Wed, 3 Jun 2026 07:34:36 -0700 Subject: [PATCH 1/5] Enhance home page with admin team overview Provide System Administrators with a table overview of all teams, including associated sources and counts for failed and stuck transactions. --- Sloth.Web/Controllers/HomeController.cs | 51 +++++++++++++++- .../HomeViewModels/HomeIndexViewModel.cs | 22 +++++++ Sloth.Web/Views/Home/Index.cshtml | 59 +++++++++++++++---- 3 files changed, 121 insertions(+), 11 deletions(-) create mode 100644 Sloth.Web/Models/HomeViewModels/HomeIndexViewModel.cs diff --git a/Sloth.Web/Controllers/HomeController.cs b/Sloth.Web/Controllers/HomeController.cs index 5428feec..3d93bf67 100644 --- a/Sloth.Web/Controllers/HomeController.cs +++ b/Sloth.Web/Controllers/HomeController.cs @@ -1,3 +1,5 @@ +using System; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; @@ -8,6 +10,7 @@ using Sloth.Core.Resources; using Sloth.Web.Identity; using Sloth.Web.Models; +using Sloth.Web.Models.HomeViewModels; using Sloth.Web.Resources; namespace Sloth.Web.Controllers @@ -29,7 +32,53 @@ public async Task Index() return RedirectToAction(nameof(TransactionsController.Index), "Transactions", new { team = team.Slug }); } - return View(teams); + var model = new HomeIndexViewModel(); + if (User.IsInRole(Roles.SystemAdmin)) + { + var stuckCutoff = DateTime.UtcNow.Date.AddDays(-1); + var failedProcessingCutoff = DateTime.UtcNow.Date.AddDays(-5); + + var teamsWithSources = await DbContext.Teams + .Include(t => t.Sources) + .AsNoTracking() + .OrderBy(t => t.Name) + .ToListAsync(); + + var failedTransactionCounts = await DbContext.Transactions + .Where(t => t.Status == TransactionStatuses.Rejected + || (t.Status == TransactionStatuses.Processing && t.LastModified < failedProcessingCutoff)) + .GroupBy(t => t.Source.Team.Slug) + .Select(t => new + { + Slug = t.Key, + Count = t.Count(), + }) + .ToDictionaryAsync(t => t.Slug, t => t.Count); + + var stuckTransactionCounts = await DbContext.Transactions + .Where(t => (t.Status == TransactionStatuses.Processing && t.LastModified < stuckCutoff) + || (t.Status == TransactionStatuses.Scheduled && t.LastModified < stuckCutoff)) + .GroupBy(t => t.Source.Team.Slug) + .Select(t => new + { + Slug = t.Key, + Count = t.Count(), + }) + .ToDictionaryAsync(t => t.Slug, t => t.Count); + + model.Teams = teamsWithSources + .Select(t => new HomeTeamSummaryViewModel + { + Name = t.Name, + Slug = t.Slug, + SourceNames = t.Sources?.OrderBy(s => s.Name).Select(s => s.Name).ToList() ?? new List(), + FailedTransactionCount = failedTransactionCounts.TryGetValue(t.Slug, out var failedCount) ? failedCount : 0, + StuckTransactionCount = stuckTransactionCounts.TryGetValue(t.Slug, out var stuckCount) ? stuckCount : 0, + }) + .ToList(); + } + + return View(model); } [Authorize(Policy = PolicyCodes.TeamAnyRole)] diff --git a/Sloth.Web/Models/HomeViewModels/HomeIndexViewModel.cs b/Sloth.Web/Models/HomeViewModels/HomeIndexViewModel.cs new file mode 100644 index 00000000..e6cfdf24 --- /dev/null +++ b/Sloth.Web/Models/HomeViewModels/HomeIndexViewModel.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; + +namespace Sloth.Web.Models.HomeViewModels +{ + public class HomeIndexViewModel + { + public IReadOnlyList Teams { get; set; } = new List(); + } + + public class HomeTeamSummaryViewModel + { + public string Name { get; set; } + + public string Slug { get; set; } + + public IReadOnlyList SourceNames { get; set; } = new List(); + + public int FailedTransactionCount { get; set; } + + public int StuckTransactionCount { get; set; } + } +} diff --git a/Sloth.Web/Views/Home/Index.cshtml b/Sloth.Web/Views/Home/Index.cshtml index 5d65a8b9..5da4398f 100644 --- a/Sloth.Web/Views/Home/Index.cshtml +++ b/Sloth.Web/Views/Home/Index.cshtml @@ -1,5 +1,5 @@ @using Sloth.Core.Resources -@model IEnumerable +@model Sloth.Web.Models.HomeViewModels.HomeIndexViewModel @{ ViewData["Title"] = "Home Page"; } @@ -23,15 +23,54 @@

All Teams:

-
- @foreach (var team in Model) - { - - } +
+ + + + + + + + + + + + @foreach (var team in Model.Teams) + { + var teamUrl = Url.Action("TeamIndex", "Home", new { team = team.Slug }); + + + + + + + + } + +
NameSlugSourcesFailed TransactionsStuck Transactions
+ @team.Name + @team.Slug@(team.SourceNames.Any() ? string.Join(", ", team.SourceNames) : "None")@team.FailedTransactionCount@team.StuckTransactionCount
}
+ +@section AdditionalScripts { + @if (User.IsInRole(Roles.SystemAdmin)) + { + + } +} From 0822051e91406188300ece3da2d31a1b387d4747 Mon Sep 17 00:00:00 2001 From: Jason Sylvestre Date: Wed, 3 Jun 2026 07:58:07 -0700 Subject: [PATCH 2/5] Extend home page team overview to all users Previously, the home page's team summary table and transaction status counts were only visible to System Administrators. This update refactors the controller logic and view to display a filtered summary of relevant teams for all authenticated users, improving accessibility and utility of the home page. --- Sloth.Web/Controllers/HomeController.cs | 70 +++++++++++++------------ Sloth.Web/Views/Home/Index.cshtml | 37 ++++++------- 2 files changed, 52 insertions(+), 55 deletions(-) diff --git a/Sloth.Web/Controllers/HomeController.cs b/Sloth.Web/Controllers/HomeController.cs index 3d93bf67..e2517e6a 100644 --- a/Sloth.Web/Controllers/HomeController.cs +++ b/Sloth.Web/Controllers/HomeController.cs @@ -32,41 +32,45 @@ public async Task Index() return RedirectToAction(nameof(TransactionsController.Index), "Transactions", new { team = team.Slug }); } - var model = new HomeIndexViewModel(); - if (User.IsInRole(Roles.SystemAdmin)) - { - var stuckCutoff = DateTime.UtcNow.Date.AddDays(-1); - var failedProcessingCutoff = DateTime.UtcNow.Date.AddDays(-5); + var teamIds = teams.Select(t => t.Id).ToList(); + var teamSlugs = teams.Select(t => t.Slug).ToList(); + var stuckCutoff = DateTime.UtcNow.Date.AddDays(-1); + var failedProcessingCutoff = DateTime.UtcNow.Date.AddDays(-5); - var teamsWithSources = await DbContext.Teams - .Include(t => t.Sources) - .AsNoTracking() - .OrderBy(t => t.Name) - .ToListAsync(); + var teamsWithSources = await DbContext.Teams + .Include(t => t.Sources) + .Where(t => teamIds.Contains(t.Id)) + .AsNoTracking() + .OrderBy(t => t.Name) + .ToListAsync(); - var failedTransactionCounts = await DbContext.Transactions - .Where(t => t.Status == TransactionStatuses.Rejected - || (t.Status == TransactionStatuses.Processing && t.LastModified < failedProcessingCutoff)) - .GroupBy(t => t.Source.Team.Slug) - .Select(t => new - { - Slug = t.Key, - Count = t.Count(), - }) - .ToDictionaryAsync(t => t.Slug, t => t.Count); + var failedTransactionCounts = await DbContext.Transactions + .Where(t => teamSlugs.Contains(t.Source.Team.Slug) + && (t.Status == TransactionStatuses.Rejected + || (t.Status == TransactionStatuses.Processing && t.LastModified < failedProcessingCutoff))) + .GroupBy(t => t.Source.Team.Slug) + .Select(t => new + { + Slug = t.Key, + Count = t.Count(), + }) + .ToDictionaryAsync(t => t.Slug, t => t.Count); - var stuckTransactionCounts = await DbContext.Transactions - .Where(t => (t.Status == TransactionStatuses.Processing && t.LastModified < stuckCutoff) - || (t.Status == TransactionStatuses.Scheduled && t.LastModified < stuckCutoff)) - .GroupBy(t => t.Source.Team.Slug) - .Select(t => new - { - Slug = t.Key, - Count = t.Count(), - }) - .ToDictionaryAsync(t => t.Slug, t => t.Count); + var stuckTransactionCounts = await DbContext.Transactions + .Where(t => teamSlugs.Contains(t.Source.Team.Slug) + && ((t.Status == TransactionStatuses.Processing && t.LastModified < stuckCutoff) + || (t.Status == TransactionStatuses.Scheduled && t.LastModified < stuckCutoff))) + .GroupBy(t => t.Source.Team.Slug) + .Select(t => new + { + Slug = t.Key, + Count = t.Count(), + }) + .ToDictionaryAsync(t => t.Slug, t => t.Count); - model.Teams = teamsWithSources + var model = new HomeIndexViewModel + { + Teams = teamsWithSources .Select(t => new HomeTeamSummaryViewModel { Name = t.Name, @@ -75,8 +79,8 @@ public async Task Index() FailedTransactionCount = failedTransactionCounts.TryGetValue(t.Slug, out var failedCount) ? failedCount : 0, StuckTransactionCount = stuckTransactionCounts.TryGetValue(t.Slug, out var stuckCount) ? stuckCount : 0, }) - .ToList(); - } + .ToList(), + }; return View(model); } diff --git a/Sloth.Web/Views/Home/Index.cshtml b/Sloth.Web/Views/Home/Index.cshtml index 5da4398f..d08d3e95 100644 --- a/Sloth.Web/Views/Home/Index.cshtml +++ b/Sloth.Web/Views/Home/Index.cshtml @@ -18,10 +18,7 @@ - @if (User.IsInRole(Roles.SystemAdmin)) - { - -

All Teams:

+

@(User.IsInRole(Roles.SystemAdmin) ? "All Teams:" : "Your Teams:")

@@ -51,26 +48,22 @@
- } @section AdditionalScripts { - @if (User.IsInRole(Roles.SystemAdmin)) - { - - } + $('#homeTeamsTable tbody').on('click', 'tr', function () { + window.location = $(this).data('href'); + }); + } From 6d8c33c8233901961963b36ff8c837a8f58efd65 Mon Sep 17 00:00:00 2001 From: Jason Sylvestre Date: Wed, 3 Jun 2026 09:24:37 -0700 Subject: [PATCH 3/5] Refine home page branding and layout Reworks the display of the S.L.O.T.H. acronym and its full name, and restructures the layout of the home page heading and team overview sections for improved visual presentation. --- Sloth.Web/Views/Home/Index.cshtml | 24 ++++++--------- Sloth.Web/wwwroot/scss/layout.scss | 49 ++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 15 deletions(-) diff --git a/Sloth.Web/Views/Home/Index.cshtml b/Sloth.Web/Views/Home/Index.cshtml index d08d3e95..faffad55 100644 --- a/Sloth.Web/Views/Home/Index.cshtml +++ b/Sloth.Web/Views/Home/Index.cshtml @@ -4,21 +4,14 @@ ViewData["Title"] = "Home Page"; } -
-
-
-

SLOTH

-
    -
  • Secure Scrubber
  • -
  • Ledger Loader
  • -
  • Online
  • -
  • Transaction
  • -
  • Hub
  • -
-
-
- -

@(User.IsInRole(Roles.SystemAdmin) ? "All Teams:" : "Your Teams:")

+
+
+

S.L.O.T.H.

+

Secure Ledger Online Transaction Hub

+
+ +
+

@(User.IsInRole(Roles.SystemAdmin) ? "All Teams" : "Your Teams")

@@ -48,6 +41,7 @@
+
@section AdditionalScripts { diff --git a/Sloth.Web/wwwroot/scss/layout.scss b/Sloth.Web/wwwroot/scss/layout.scss index d3d3c419..aaa34432 100644 --- a/Sloth.Web/wwwroot/scss/layout.scss +++ b/Sloth.Web/wwwroot/scss/layout.scss @@ -98,6 +98,55 @@ footer { .body-content { padding-top: 2rem; } +.home-page { + .home-heading { + width: 100%; + max-width: 960px; + margin: 0; + padding: 2.5rem 0 1.25rem; + + h1 { + margin-bottom: 0.1rem; + font-size: 2rem; + line-height: 1.1; + } + + p { + margin-bottom: 0; + color: $secondary-font; + font-size: 0.95rem; + text-transform: uppercase; + } + } + + .home-teams { + margin-top: 3.5rem; + + h2 { + display: inline-block; + margin: 0 0 1rem; + padding-bottom: 0.45rem; + border-bottom: 3px solid $primary-color; + color: $primary-color; + font-size: 1.5rem; + line-height: 1.2; + } + } + + .responsive-table { + border: 1px solid $borders-sloth; + background-color: #fff; + } + + table.sloth-table { + margin-bottom: 0; + border: 0; + + thead th { + background-color: $bg-primary; + } + } +} .filters-wrapper { margin-bottom: 3%; .status-filter { From 2e0c5968e6f5c37a818f29ec3a53e813cffcc337 Mon Sep 17 00:00:00 2001 From: Jason Sylvestre Date: Wed, 3 Jun 2026 11:11:58 -0700 Subject: [PATCH 4/5] Upgrade CodeQL actions to v3 Updates the security scanning workflow to leverage the latest features and improvements provided by CodeQL v3. --- .github/workflows/codeql.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 01696ad1..f47b6c9d 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -42,7 +42,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -56,7 +56,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v2 + uses: github/codeql-action/autobuild@v3 # â„šī¸ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun @@ -69,6 +69,6 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 with: category: "/language:${{matrix.language}}" From b73418cb588166b285bec58ebde4a0bbea02e0a3 Mon Sep 17 00:00:00 2001 From: Jason Sylvestre Date: Wed, 3 Jun 2026 14:11:30 -0700 Subject: [PATCH 5/5] Rename 'Failed Transactions' to 'Rejected Transactions' Updates the home page dashboard to only count transactions with a 'Rejected' status as 'Rejected Transactions'. Previously, this metric also included 'Processing' transactions older than 5 days, which are now excluded for improved clarity. --- Sloth.Web/Controllers/HomeController.cs | 4 +--- Sloth.Web/Views/Home/Index.cshtml | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Sloth.Web/Controllers/HomeController.cs b/Sloth.Web/Controllers/HomeController.cs index e2517e6a..94d8c782 100644 --- a/Sloth.Web/Controllers/HomeController.cs +++ b/Sloth.Web/Controllers/HomeController.cs @@ -35,7 +35,6 @@ public async Task Index() var teamIds = teams.Select(t => t.Id).ToList(); var teamSlugs = teams.Select(t => t.Slug).ToList(); var stuckCutoff = DateTime.UtcNow.Date.AddDays(-1); - var failedProcessingCutoff = DateTime.UtcNow.Date.AddDays(-5); var teamsWithSources = await DbContext.Teams .Include(t => t.Sources) @@ -46,8 +45,7 @@ public async Task Index() var failedTransactionCounts = await DbContext.Transactions .Where(t => teamSlugs.Contains(t.Source.Team.Slug) - && (t.Status == TransactionStatuses.Rejected - || (t.Status == TransactionStatuses.Processing && t.LastModified < failedProcessingCutoff))) + && t.Status == TransactionStatuses.Rejected) .GroupBy(t => t.Source.Team.Slug) .Select(t => new { diff --git a/Sloth.Web/Views/Home/Index.cshtml b/Sloth.Web/Views/Home/Index.cshtml index faffad55..1189c77c 100644 --- a/Sloth.Web/Views/Home/Index.cshtml +++ b/Sloth.Web/Views/Home/Index.cshtml @@ -20,7 +20,7 @@ Name Slug Sources - Failed Transactions + Rejected Transactions Stuck Transactions