diff --git a/Sloth.Web/Controllers/HomeController.cs b/Sloth.Web/Controllers/HomeController.cs index 5428feec..94d8c782 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,55 @@ public async Task Index() return RedirectToAction(nameof(TransactionsController.Index), "Transactions", new { team = team.Slug }); } - return View(teams); + var teamIds = teams.Select(t => t.Id).ToList(); + var teamSlugs = teams.Select(t => t.Slug).ToList(); + var stuckCutoff = DateTime.UtcNow.Date.AddDays(-1); + + 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 => teamSlugs.Contains(t.Source.Team.Slug) + && t.Status == TransactionStatuses.Rejected) + .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); + + var model = new HomeIndexViewModel + { + 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..1189c77c 100644 --- a/Sloth.Web/Views/Home/Index.cshtml +++ b/Sloth.Web/Views/Home/Index.cshtml @@ -1,37 +1,63 @@ @using Sloth.Core.Resources -@model IEnumerable +@model Sloth.Web.Models.HomeViewModels.HomeIndexViewModel @{ ViewData["Title"] = "Home Page"; } -
-
-
-

SLOTH

-
    -
  • Secure Scrubber
  • -
  • Ledger Loader
  • -
  • Online
  • -
  • Transaction
  • -
  • Hub
  • -
-
-
- - @if (User.IsInRole(Roles.SystemAdmin)) - { +
+
+

S.L.O.T.H.

+

Secure Ledger Online Transaction Hub

+
-

All Teams:

+
+

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

-
- @foreach (var team in Model) - { - - } +
+ + + + + + + + + + + + @foreach (var team in Model.Teams) + { + var teamUrl = Url.Action("TeamIndex", "Home", new { team = team.Slug }); + + + + + + + + } + +
NameSlugSourcesRejected TransactionsStuck Transactions
+ @team.Name + @team.Slug@(team.SourceNames.Any() ? string.Join(", ", team.SourceNames) : "None")@team.FailedTransactionCount@team.StuckTransactionCount
- } +
+ +@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 {