diff --git a/client/ts/admin.ts b/client/ts/admin.ts
index 50fc044fb00f4291e943948d45496062cf18f3fc..827a6f74c006662473c8fcc7c101ecb74c51ec71 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 b8862a943849706e1e770680aa8d9e70a46715ed..599c3ef67a4ee614f617289875c773ec636a8980 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 0000000000000000000000000000000000000000..57707d88bfc0eaa128a08fb5430480625baaa64f
--- /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 b57c0a0509f8c1bf322cc825dcbbbb185940b805..bc134a6f1ff853dc35584f36b454468502c730da 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 5df9a581ba7ca5224719177e0b4b409a938bc593..f2623af662ea27ef76017ecd3ff11345570bfd8f 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 0b1f27798277c03ec808ab41f01141b9c587f84a..e5ca31a7f4c93d8268870f009def581ded259768 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);
}
}