From 1ac07b8c5f647c60c6987b1628a9b30173c775b7 Mon Sep 17 00:00:00 2001 From: Dmytro Bogatov <dmytro@dbogatov.org> Date: Sun, 6 Aug 2017 16:26:07 +0300 Subject: [PATCH] Endpoint for resolve. --- client/ts/admin.ts | 42 ++++++-- src/web/Controllers/View/AdminController.cs | 67 ++++++++++++- .../DiscrepancyResolutionViewModel.cs | 98 +++++++++++++++++++ src/web/Views/Admin/Index.cshtml | 38 ++++++- .../Components/DiscrepancyCard/Default.cshtml | 35 +++++-- .../DiscrepancyCardViewComponent.cs | 5 +- 6 files changed, 268 insertions(+), 17 deletions(-) create mode 100644 src/web/ViewModels/DiscrepancyResolutionViewModel.cs diff --git a/client/ts/admin.ts b/client/ts/admin.ts index 50fc044..827a6f7 100644 --- a/client/ts/admin.ts +++ b/client/ts/admin.ts @@ -6,17 +6,47 @@ import "bootstrap-select" import * as Waves from "Waves" import "bootstrap" -declare global { - interface JQuery { - jsonViewer(any): void; - } -} - $(async () => { $('.selectpicker').selectpicker(); Utility.fixUtcTime(); + setupDiscrepancyViewer(); + document.dispatchEvent(new Event("page-ready")); }); + +function setupDiscrepancyViewer() { + var resolvedLoaded = 10; + + $("#load-more-resolved-btn").click(() => { + + for (var index = resolvedLoaded + 1; index <= resolvedLoaded + 10; index++) { + $(`.discrepancy-card[data-number="${index}"]`).show(); + } + + resolvedLoaded += 10; + + $("#load-less-resolved-btn").show(); + + if (resolvedLoaded >= $(".discrepancy-resolved").length) { + $("#load-more-resolved-btn").hide(); + } + }); + + $("#load-less-resolved-btn").click(() => { + + for (var index = resolvedLoaded; index > resolvedLoaded - 10; index--) { + $(`.discrepancy-card[data-number="${index}"]`).hide(); + } + + resolvedLoaded -= 10; + + $("#load-more-resolved-btn").show(); + + if (resolvedLoaded <= 10) { + $("#load-less-resolved-btn").hide(); + } + }); +} diff --git a/src/web/Controllers/View/AdminController.cs b/src/web/Controllers/View/AdminController.cs index b8862a9..599c3ef 100644 --- a/src/web/Controllers/View/AdminController.cs +++ b/src/web/Controllers/View/AdminController.cs @@ -34,6 +34,8 @@ namespace StatusMonitor.Web.Controllers.View private readonly IServiceProvider _provider; private readonly ICleanService _cleanService; private readonly IDataContext _context; + private readonly INotificationService _notification; + private readonly IConfiguration _config; public AdminController( @@ -42,7 +44,9 @@ namespace StatusMonitor.Web.Controllers.View IMetricService metricService, IServiceProvider provider, ICleanService cleanService, - IDataContext context + IDataContext context, + INotificationService notification, + IConfiguration congig ) { _metricService = metricService; @@ -51,6 +55,8 @@ namespace StatusMonitor.Web.Controllers.View _provider = provider; _cleanService = cleanService; _context = context; + _notification = notification; + _config = congig; } public async Task<IActionResult> Index() @@ -84,6 +90,65 @@ namespace StatusMonitor.Web.Controllers.View } } + [HttpPost] + [ServiceFilter(typeof(ModelValidation))] + [AutoValidateAntiforgeryToken] + public async Task<IActionResult> ResolveDiscrepancy(DiscrepancyResolutionViewModel model) + { + if ( + await _context + .Discrepancies + .AnyAsync( + d => + d.DateFirstOffense == model.DateFirstOffense && + d.MetricSource == model.Source && + d.MetricType == model.EnumMetricType && + d.Type == model.EnumDiscrepancyType + ) + ) + { + + var discrepancy = + await _context + .Discrepancies + .SingleAsync( + d => + d.DateFirstOffense == model.DateFirstOffense && + d.MetricSource == model.Source && + d.MetricType == model.EnumMetricType && + d.Type == model.EnumDiscrepancyType + ); + + if (discrepancy.Resolved) + { + TempData["MessageSeverity"] = "warning"; + TempData["MessageContent"] = $"Discrepancy {model.EnumDiscrepancyType} from {model.EnumMetricType} of {model.Source} at {model.DateFirstOffense} (UTC) has been already resolved."; + } + else + { + discrepancy.Resolved = true; + discrepancy.DateResolved = DateTime.UtcNow; + + await _notification.ScheduleNotificationAsync( + $"Discrepancy \"{discrepancy.ToStringWithTimeZone(_config["ServiceManager:NotificationService:TimeZone"])}\" has been resolved!", + NotificationSeverity.Medium + ); + + await _context.SaveChangesAsync(); + + TempData["MessageSeverity"] = "success"; + TempData["MessageContent"] = $"Discrepancy {model.EnumDiscrepancyType} from {model.EnumMetricType} of {model.Source} at {model.DateFirstOffense} (UTC) has been resolved."; + } + + return RedirectToAction("Index", "Admin"); + } + else + { + return NotFound($"Discrepancy for the following request not found. {model}"); + } + + } + public IActionResult Metric([FromQuery] string metric) { if (metric == null) diff --git a/src/web/ViewModels/DiscrepancyResolutionViewModel.cs b/src/web/ViewModels/DiscrepancyResolutionViewModel.cs new file mode 100644 index 0000000..57707d8 --- /dev/null +++ b/src/web/ViewModels/DiscrepancyResolutionViewModel.cs @@ -0,0 +1,98 @@ +using System; +using System.ComponentModel.DataAnnotations; +using StatusMonitor.Shared.Extensions; +using StatusMonitor.Shared.Models.Entities; + +namespace StatusMonitor.Web.ViewModels +{ + public class DiscrepancyResolutionViewModel + { + public DateTime DateFirstOffense { get; set; } + + /// <summary> + /// Alias for DateFirstOffense. + /// </summary> + [Required] + public string Date + { + get + { + return DateFirstOffense.ToString(); + } + set + { + try + { + DateFirstOffense = new DateTime(Convert.ToInt64(value)); + } + catch (System.Exception) + { + throw new ArgumentException("Invalid Date parameter."); + } + } + } + + public DiscrepancyType EnumDiscrepancyType { get; set; } + + /// <summary> + /// Alias for EnumDiscrepancyType. + /// </summary> + [Required] + public string DiscrepancyType + { + get + { + return EnumDiscrepancyType.ToString(); + } + set + { + try + { + EnumDiscrepancyType = value.ToEnum<DiscrepancyType>(); + } + catch (System.Exception) + { + throw new ArgumentException("Invalid DiscrepancyType parameter."); + } + } + } + + public Metrics EnumMetricType { get; set; } + + /// <summary> + /// Alias for EnumMetricType. + /// </summary> + [Required] + public string MetricType + { + get + { + return EnumMetricType.ToString(); + } + set + { + try + { + EnumMetricType = value.ToEnum<Metrics>(); + } + catch (System.Exception) + { + throw new ArgumentException("Invalid MetricType parameter."); + } + } + } + + /// <summary> + /// Source identifier. May be server id or website URL. + /// </summary> + [Required] + [StringLength(32)] + [RegularExpression("[a-z0-9\\.\\-]+")] + public string Source { get; set; } + + public override string ToString() + { + return $"Discrepancy removal model: type {DiscrepancyType} from {MetricType} of {Source} at {DateFirstOffense}."; + } + } +} diff --git a/src/web/Views/Admin/Index.cshtml b/src/web/Views/Admin/Index.cshtml index b57c0a0..bc134a6 100644 --- a/src/web/Views/Admin/Index.cshtml +++ b/src/web/Views/Admin/Index.cshtml @@ -149,7 +149,11 @@ { @foreach (var dicrepancy in ((IEnumerable<Discrepancy>)ViewBag.Discrepancies).Where(d => !d.Resolved)) { - <vc:discrepancy-card model=dicrepancy></vc:discrepancy-card> + <vc:discrepancy-card + model=dicrepancy + number=0 + hidden=false + ></vc:discrepancy-card> } } else @@ -160,10 +164,40 @@ <div role="tabpanel" class="tab-pane" id="resovled"> @if ( ((IEnumerable<Discrepancy>)ViewBag.Discrepancies).Any(d => d.Resolved) ) { + var number = 1; + @foreach (var dicrepancy in ((IEnumerable<Discrepancy>)ViewBag.Discrepancies).Where(d => d.Resolved)) { - <vc:discrepancy-card model=dicrepancy></vc:discrepancy-card> + var hidden = number > 10; + + <vc:discrepancy-card + model=dicrepancy + number=number + hidden=hidden + ></vc:discrepancy-card> + + number++; } + <div class="w-100 text-center p-t-10"> + + <button + type="type" + id="load-more-resolved-btn" + class="btn btn-primary btn-md waves-effect" + style="@( ((IEnumerable<Discrepancy>)ViewBag.Discrepancies).Count(d => d.Resolved) <= 10 ? "display: none;" : "")" + > + Show another 10 + </button> + + <button + type="type" + id="load-less-resolved-btn" + class="btn btn-warning btn-md waves-effect" + style="display: none;" + > + Hide another 10 + </button> + </div> } else { diff --git a/src/web/Views/Shared/Components/DiscrepancyCard/Default.cshtml b/src/web/Views/Shared/Components/DiscrepancyCard/Default.cshtml index 5df9a58..f2623af 100644 --- a/src/web/Views/Shared/Components/DiscrepancyCard/Default.cshtml +++ b/src/web/Views/Shared/Components/DiscrepancyCard/Default.cshtml @@ -1,19 +1,40 @@ -<div class="list-group-item media"> +<div + class="list-group-item media discrepancy-card discrepancy-@(Model.Resolved ? "resolved" : "unresolved")" + data-number="@ViewBag.Number" + style="@(ViewBag.Hidden ? "display: none;" : "")" +> <div class="pull-left"> - <i class="zmdi zmdi-@(Model.Resolved ? "check-circle" : "alert-circle-o") zmdi-hc-3x"></i> + <i + class="zmdi zmdi-@(Model.Resolved ? "check-circle" : "alert-circle-o") zmdi-hc-3x" + style="color: @(Model.Resolved ? "green" : "red");" + > + </i> </div> @if (!Model.Resolved) { <div class="pull-right"> <div class="actions"> - <!-- TODO form --> - <button type="submit" class="btn btn-success btn-md waves-effect"> - Resolve - </button> + <form asp-controller="Admin" asp-action="ResolveDiscrepancy" asp-anti-forgery="true"> + + <input type="text" hidden name="Date" value="@Model.DateFirstOffense.Ticks"> + <input type="text" hidden name="DiscrepancyType" value="@Model.Type"> + <input type="text" hidden name="MetricType" value="@Model.MetricType"> + <input type="text" hidden name="Source" value="@Model.MetricSource"> + + <button type="submit" class="btn btn-success btn-md waves-effect"> + Resolve + </button> + </form> </div> </div> } + else + { + <div class="pull-right"> + <p>#@ViewBag.Number + </div> + } <div class="media-body"> <div class="lgi-heading"> @@ -22,7 +43,7 @@ <small class="lgi-text">@Model.Description()</small> <ul class="lgi-attrs"> - <li>Date first offense: <utc-time time="@Model.DateFirstOffense" /></li> + <li>Date started: <utc-time time="@Model.DateFirstOffense" /></li> @if (Model.Resolved) { <li>Date resolved: <utc-time time="@Model.DateResolved" /></li> diff --git a/src/web/Views/ViewComponents/DiscrepancyCardViewComponent.cs b/src/web/Views/ViewComponents/DiscrepancyCardViewComponent.cs index 0b1f277..e5ca31a 100644 --- a/src/web/Views/ViewComponents/DiscrepancyCardViewComponent.cs +++ b/src/web/Views/ViewComponents/DiscrepancyCardViewComponent.cs @@ -13,10 +13,13 @@ namespace StatusMonitor.Web.Views.ViewComponents [ViewComponent(Name = "DiscrepancyCard")] public class DiscrepancyCardViewComponent : ViewComponent { - public async Task<IViewComponentResult> InvokeAsync(Discrepancy model) + public async Task<IViewComponentResult> InvokeAsync(Discrepancy model, int number = 0, bool hidden = false) { await Task.CompletedTask; + ViewBag.Number = number; + ViewBag.Hidden = hidden; + return View(model); } } -- GitLab