diff --git a/client/ts/admin.ts b/client/ts/admin.ts
index 50fc044fb00f4291e943948d45496062cf18f3fc..52bba78c13e0b251e97743e9d6f14c5c14883322 100644
--- a/client/ts/admin.ts
+++ b/client/ts/admin.ts
@@ -6,17 +6,52 @@ 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"));
 });
+
+/**
+ * Controls "load more" and "load less" buttons for
+ * resolved discrepancies list
+ * 
+ */
+function setupDiscrepancyViewer() : void {
+	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/client/ts/modules/utility.ts b/client/ts/modules/utility.ts
index 0a320f2935e9c32a734181e7d103782db6f4eb58..44d31ebbbf4a14e07aa3c0a44f1b816110cfa32e 100644
--- a/client/ts/modules/utility.ts
+++ b/client/ts/modules/utility.ts
@@ -161,16 +161,24 @@ export class Utility {
 			);
 		});
 
+		$(".utc-date").each(function () {
+			$(this).text(
+				Utility.toLocalTimezone(
+					new Date($(this).text())
+				).toString()
+			);
+		});
+
 	}
 
-	public static toUtcDate(date : Date): number {
+	public static toUtcDate(date: Date): number {
 		return Date.UTC(
-			date.getUTCFullYear(), 
-			date.getUTCMonth(), 
-			date.getUTCDate(),  
-			date.getUTCHours(), 
-			date.getUTCMinutes(), 
-			date.getUTCSeconds(), 
+			date.getUTCFullYear(),
+			date.getUTCMonth(),
+			date.getUTCDate(),
+			date.getUTCHours(),
+			date.getUTCMinutes(),
+			date.getUTCSeconds(),
 			date.getUTCMilliseconds()
 		);
 	}
diff --git a/src/shared/Models/Entities/Discrepancies.cs b/src/shared/Models/Entities/Discrepancies.cs
index e69692d0f0bdd7c5dfb32e10918a2b837ec9fbfd..c7e079d7fa01972c55cb702c0336f1f9806f0631 100644
--- a/src/shared/Models/Entities/Discrepancies.cs
+++ b/src/shared/Models/Entities/Discrepancies.cs
@@ -43,6 +43,26 @@ namespace StatusMonitor.Shared.Models.Entities
 		}
 
 		public override string ToString() => ToStringWithTimeZone();
+
+		/// <summary>
+		/// Human readable description of the discrepancy.
+		/// </summary>
+		/// <returns>Human readable description of the discrepancy</returns>
+		public string Description() {
+			switch (Type)
+			{
+				case DiscrepancyType.GapInData:
+					return "Occurs if difference in timestamps between two consecutive data points exceeds certain value.";
+				case DiscrepancyType.HighLoad:
+					return "Occurs if CPU load exceeds certain value for some number of consecutive data points.";
+				case DiscrepancyType.LowHealth:
+					return "Occurs if system health value drops below certain value for some number of consecutive health reports.";
+				case DiscrepancyType.PingFailedNTimes:
+					return "Occurs if ping fails certain number of consecutive readings.";
+				default:
+					return "Unknown type";
+			}
+		}
 	}
 
 	public enum DiscrepancyType
diff --git a/src/shared/Services/NotificationService.cs b/src/shared/Services/NotificationService.cs
index 229a82a24c3c150f519a9a67b82cb8902b73591a..88b4b43e0b2300b7ee1479ac41c647f73b93329b 100644
--- a/src/shared/Services/NotificationService.cs
+++ b/src/shared/Services/NotificationService.cs
@@ -154,7 +154,7 @@ namespace StatusMonitor.Shared.Services
 			return
 				unresolved == 0 ?
 				"There are no outstanding issues. Well done." :
-				$"There {(unresolved == 1 ? "is" : "are")} still outstanding {unresolved} issue{(unresolved == 1 ? "" : "s")}."
+				$"There {(unresolved == 1 ? "is" : "are")} still outstanding {unresolved} issue{(unresolved == 1 ? "" : "s")}. See admin panel."
 			;
 
 		}
diff --git a/src/web/Controllers/View/AdminController.cs b/src/web/Controllers/View/AdminController.cs
index a5bf362a3f48b55b50f2ba099c8663d5f86be44d..599c3ef67a4ee614f617289875c773ec636a8980 100644
--- a/src/web/Controllers/View/AdminController.cs
+++ b/src/web/Controllers/View/AdminController.cs
@@ -33,6 +33,9 @@ namespace StatusMonitor.Web.Controllers.View
 		private readonly IMetricService _metricService;
 		private readonly IServiceProvider _provider;
 		private readonly ICleanService _cleanService;
+		private readonly IDataContext _context;
+		private readonly INotificationService _notification;
+		private readonly IConfiguration _config;
 
 
 		public AdminController(
@@ -40,7 +43,10 @@ namespace StatusMonitor.Web.Controllers.View
 			ILogger<AdminController> logger,
 			IMetricService metricService,
 			IServiceProvider provider,
-			ICleanService cleanService
+			ICleanService cleanService,
+			IDataContext context,
+			INotificationService notification,
+			IConfiguration congig
 		)
 		{
 			_metricService = metricService;
@@ -48,6 +54,9 @@ namespace StatusMonitor.Web.Controllers.View
 			_loggingService = loggingService;
 			_provider = provider;
 			_cleanService = cleanService;
+			_context = context;
+			_notification = notification;
+			_config = congig;
 		}
 
 		public async Task<IActionResult> Index()
@@ -55,6 +64,11 @@ namespace StatusMonitor.Web.Controllers.View
 			ViewBag.Metrics = await _metricService
 				.GetMetricsAsync();
 
+			ViewBag.Discrepancies = await _context
+				.Discrepancies
+				.OrderByDescending(d => d.DateFirstOffense)
+				.ToListAsync();
+
 			return View();
 		}
 
@@ -76,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/TagHelpers/UtcTimeTagHelper.cs b/src/web/TagHelpers/UtcTimeTagHelper.cs
index f098874e699b9153c0734854535fb732bab287a5..c350d1047aa1118095734590e9183968ac93c352 100644
--- a/src/web/TagHelpers/UtcTimeTagHelper.cs
+++ b/src/web/TagHelpers/UtcTimeTagHelper.cs
@@ -12,6 +12,12 @@ namespace StatusMonitor.Web.TagHelpers
 	/// </summary>
 	public class UtcTimeTagHelper : TagHelper
 	{
+		/// <summary>
+		/// If true, the full date should be rendered
+		/// Otherwise, only time part should be rendered
+		/// </summary>
+		public bool ShowDate { get; set; } = false;
+
 		public DateTime Time { get; set; }
 
 		[ViewContext]
@@ -26,7 +32,8 @@ namespace StatusMonitor.Web.TagHelpers
 			output.TagMode = TagMode.StartTagAndEndTag;
 
 			output.Attributes.Clear();
-			output.Attributes.Add("class", "utc-time");
+			
+			output.Attributes.Add("class", ShowDate ? "utc-date" : "utc-time");
 
 			output.Content.SetHtmlContent(Time.ToString());
 
diff --git a/src/web/ViewModels/DiscrepancyResolutionViewModel.cs b/src/web/ViewModels/DiscrepancyResolutionViewModel.cs
new file mode 100644
index 0000000000000000000000000000000000000000..189ef559c94427a7abe6b120d337ce49b3f47ffa
--- /dev/null
+++ b/src/web/ViewModels/DiscrepancyResolutionViewModel.cs
@@ -0,0 +1,102 @@
+using System;
+using System.ComponentModel.DataAnnotations;
+using StatusMonitor.Shared.Extensions;
+using StatusMonitor.Shared.Models.Entities;
+
+namespace StatusMonitor.Web.ViewModels
+{
+	public class DiscrepancyResolutionViewModel
+	{
+		/// <summary>
+		/// Timestamp when the discrepancy starts.
+		/// </summary>
+		public DateTime DateFirstOffense { get; set; }
+
+		/// <summary>
+		/// Alias for DateFirstOffense.
+		/// Should represent the number of ticks (eq. DateTime.UtcNow.Ticks)
+		/// </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 a01a95b2b3a899954dbbde05ce2691325decef4d..25dc3ba382f27f72f72fc7a98ef2b28e2ffacd45 100644
--- a/src/web/Views/Admin/Index.cshtml
+++ b/src/web/Views/Admin/Index.cshtml
@@ -1,4 +1,7 @@
 @using StatusMonitor.Shared.Models.Entities
+@using Microsoft.Extensions.Configuration
+
+@inject IConfiguration Config
 
 @{ 
     ViewData["Title"] = "Admin panel"; 
@@ -11,91 +14,214 @@
 	</div>
 
 	<div class="row">
-		<div class="col-md-3">
-			<div class="card">
-				<div class="card-header">
-					<h2>
-						Manual cleaunup
-						<small>
-							Run clean service manually with a specified max age
-						</small>
-					</h2>
-				</div>
-				<div class="card-body card-padding">
-					<div class="row">
-						<form 
-							role="form" 
-							asp-controller="Admin" 
-							asp-action="Clean" 
-							method="post"
-							novalidate
-						>
-							<div class="col-md-8 col-sm-12">
-
-								<h5>Set max age</h5>
-
-								<select class="selectpicker" name="maxAge">
-									<option value="0">Everything</option>
-									<option value="1">1 minutes</option>
-									<option value="10">10 minutes</option>
-									<option value="20">30 minutes</option>
-									<option value="60">1 hour</option>
-									<option value="240">4 hours</option>
-									<option value="720">12 hours</option>
-									<option value="1440">1 day</option>
-									<option value="4320">3 days</option>
-									<option value="10080">1 week</option>
-									<option value="43200">1 month</option>
-								</select>
+
+		<div class="col-md-6">
+
+			<div class="row">
+
+				<div class="col-md-6">
+					<div class="card">
+						<div class="card-header">
+							<h2>
+								Manual cleaunup
+								<small>
+									Run clean service manually with a specified max age
+								</small>
+							</h2>
+						</div>
+						<div class="card-body card-padding">
+							<div class="row">
+								<form 
+									role="form" 
+									asp-controller="Admin" 
+									asp-action="Clean" 
+									method="post"
+									novalidate
+								>
+									<div class="col-md-8 col-sm-12">
+
+										<h5>Set max age</h5>
+
+										<select class="selectpicker" name="maxAge">
+											<option value="0">Everything</option>
+											<option value="1">1 minutes</option>
+											<option value="10">10 minutes</option>
+											<option value="20">30 minutes</option>
+											<option value="60">1 hour</option>
+											<option value="240">4 hours</option>
+											<option value="720">12 hours</option>
+											<option value="1440">1 day</option>
+											<option value="4320">3 days</option>
+											<option value="10080">1 week</option>
+											<option value="43200">1 month</option>
+										</select>
+									</div>
+									<div class="col-md-4 col-sm-12">
+										<button type="submit" class="btn btn-danger btn-md waves-effect">Clean</button>
+									</div>
+								</form>
 							</div>
-							<div class="col-md-4 col-sm-12">
-								<button type="submit" class="btn btn-danger btn-md waves-effect">Clean</button>
+						</div>
+					</div>
+				</div>
+
+				<div class="col-md-6">
+					<div class="card">
+						<div class="card-header">
+							<h2>
+								Go to metric
+								<small>
+									Go to the metric's page
+								</small>
+							</h2>
+						</div>
+						<div class="card-body card-padding">
+							<div class="row">
+								<form 
+									role="form" 
+									asp-controller="Admin" 
+									asp-action="Metric" 
+									method="get"
+									novalidate
+								>
+									<div class="col-md-8 col-sm-12">
+
+										<h5>Metric</h5>
+
+										<select class="selectpicker" name="metric" data-live-search="true">
+											@foreach (var metric in ViewBag.Metrics)
+											{
+												<option value="@((Metrics)metric.Type)@@@(metric.Source)">@(metric.Title) from @(metric.Source)</option>
+											}
+										</select>
+									</div>
+									<div class="col-md-4 col-sm-12">
+										<button type="submit" class="btn btn-primary btn-md waves-effect">Go</button>
+									</div>
+								</form>
 							</div>
-						</form>
+						</div>
 					</div>
 				</div>
+
 			</div>
-		</div>
 
-		<div class="col-md-3">
-			<div class="card">
-				<div class="card-header">
-					<h2>
-						Go to metric
-						<small>
-							Go to the metric's page
-						</small>
-					</h2>
-				</div>
-				<div class="card-body card-padding">
-					<div class="row">
-						<form 
-							role="form" 
-							asp-controller="Admin" 
-							asp-action="Metric" 
-							method="get"
-							novalidate
-						>
-							<div class="col-md-8 col-sm-12">
-
-								<h5>Metric</h5>
-
-								<select class="selectpicker" name="metric" data-live-search="true">
-									@foreach (var metric in ViewBag.Metrics)
-									{
-										<option value="@((Metrics)metric.Type)@@@(metric.Source)">@(metric.Title) from @(metric.Source)</option>
+			<div class="row">
+
+				<div class="col-md-12">
+
+					<div class="card">
+                        <div class="card-header">
+                            <h2>Discrepancies
+                                <small>
+									@{
+										var discrepancyDataStartDate = DateTime.UtcNow - new TimeSpan(0, 0, Convert.ToInt32(Config["ServiceManager:CleanService:MaxAge"]));
 									}
-								</select>
-							</div>
-							<div class="col-md-4 col-sm-12">
-								<button type="submit" class="btn btn-primary btn-md waves-effect">Go</button>
-							</div>
-						</form>
+									View the list of resolved and unresolved dicrepancies from <utc-time time="@discrepancyDataStartDate" show-date=true /> until now.
+                                </small>
+                            </h2>
+                        </div>
+
+                        <div class="card-body card-padding">
+                            <div role="tabpanel">
+                                <ul class="tab-nav" role="tablist">
+                                    <li class="active">
+										<a 
+											href="#unresolved" 
+											aria-controls="unresolved" 
+											role="tab" 
+											data-toggle="tab" 
+											aria-expanded="true"
+										>
+											Unresolved ( @(((IEnumerable<Discrepancy>)ViewBag.Discrepancies).Count(d => !d.Resolved)) )
+										</a>
+									</li>
+                                    <li class="">
+										<a 
+											href="#resovled" 
+											aria-controls="resovled" 
+											role="tab" 
+											data-toggle="tab" 
+											aria-expanded="false"
+										>
+											Resolved ( @(((IEnumerable<Discrepancy>)ViewBag.Discrepancies).Count(d => d.Resolved)) )
+										</a>
+                                    </li>
+                                </ul>
+
+                                <div class="tab-content">
+                                    <div role="tabpanel" class="tab-pane active" id="unresolved">
+										@if ( ((IEnumerable<Discrepancy>)ViewBag.Discrepancies).Any(d => !d.Resolved) )
+										{
+											@foreach (var dicrepancy in ((IEnumerable<Discrepancy>)ViewBag.Discrepancies).Where(d => !d.Resolved))
+											{
+												<vc:discrepancy-card 
+													model=dicrepancy
+													number=0
+													hidden=false
+												></vc:discrepancy-card>
+											}
+										}
+										else
+										{
+                                        	@: <h3>No outstanding issues! Well done!</h3>
+										}
+                                    </div>
+                                    <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))
+											{
+												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
+										{
+                                        	@: <h3>No discrepancies have been noticed.</h3>
+										}
+                                    </div>
+                                </div>
+                            </div>
+
+                        </div>
+
 					</div>
+
 				</div>
+
 			</div>
-		</div>
 
+		</div>
 
 		<div class="col-md-6">
 			<div class="card">
diff --git a/src/web/Views/Shared/Components/DiscrepancyCard/Default.cshtml b/src/web/Views/Shared/Components/DiscrepancyCard/Default.cshtml
new file mode 100644
index 0000000000000000000000000000000000000000..d24685471d1bb7cdae78a0ac34bf7b9982d6e1bd
--- /dev/null
+++ b/src/web/Views/Shared/Components/DiscrepancyCard/Default.cshtml
@@ -0,0 +1,63 @@
+<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"
+			style="color: @(Model.Resolved ? "green" : "red");"
+		>
+		</i>
+	</div>
+
+	@if (!Model.Resolved)
+	{
+		<div class="pull-right">
+			<div class="actions">
+				<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">
+			Discrepancy of type <strong>@Model.Type</strong> from
+			<a 
+				asp-controller="Home" 
+				asp-action="Metric" 
+				asp-route-type="@Model.MetricType" 
+				asp-route-source="@Model.MetricSource"
+			>
+				 <em>@Model.MetricType</em> of <em>@Model.MetricSource</em>
+			</a>
+			.
+		</div>
+		<small class="lgi-text">@Model.Description()</small>
+
+		<ul class="lgi-attrs">
+			<li>Date started: <utc-time time="@Model.DateFirstOffense" /></li>
+			@if (Model.Resolved)
+			{
+				<li>Date resolved: <utc-time time="@Model.DateResolved" /></li>
+				<li>Duration: @( Math.Round((@Model.DateResolved - @Model.DateFirstOffense).TotalSeconds) ) seconds</li>
+			}
+		</ul>
+	</div>
+</div>
diff --git a/src/web/Views/ViewComponents/DiscrepancyCardViewComponent.cs b/src/web/Views/ViewComponents/DiscrepancyCardViewComponent.cs
new file mode 100644
index 0000000000000000000000000000000000000000..e5ca31a7f4c93d8268870f009def581ded259768
--- /dev/null
+++ b/src/web/Views/ViewComponents/DiscrepancyCardViewComponent.cs
@@ -0,0 +1,26 @@
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Configuration;
+using StatusMonitor.Shared.Extensions;
+using StatusMonitor.Shared.Models.Entities;
+
+namespace StatusMonitor.Web.Views.ViewComponents
+{
+	/// <summary>
+	/// View component responsible for rendering discrepancy card.
+	/// </summary>
+	[ViewComponent(Name = "DiscrepancyCard")]
+	public class DiscrepancyCardViewComponent : ViewComponent
+	{
+		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);
+		}
+	}
+}
diff --git a/test/ControllerTests/AdminController/AdminControllerTest.cs b/test/ControllerTests/AdminController/AdminControllerTest.cs
index db3922b63c1c4ae076958c621799cf5d568470b6..3721b2bdebaf7cd6b161de68e7a23589d5ccd573 100644
--- a/test/ControllerTests/AdminController/AdminControllerTest.cs
+++ b/test/ControllerTests/AdminController/AdminControllerTest.cs
@@ -27,12 +27,30 @@ namespace StatusMonitor.Tests.ControllerTests
 		private readonly Mock<IMetricService> _mockMetricService = new Mock<IMetricService>();
 		private readonly Mock<IApiController> _mockApiController = new Mock<IApiController>();
 		private readonly Mock<ICleanService> _mockCleanService = new Mock<ICleanService>();
+		private readonly Mock<INotificationService> _mockNotificationService = new Mock<INotificationService>();
+		private readonly Mock<IConfiguration> _mockConfig = new Mock<IConfiguration>();
+		
+		private readonly IDataContext _context;
 		
 
 		private readonly AdminController _controller;
 
 		public AdminControllerTest()
 		{
+			var services = new ServiceCollection();
+
+			var mockEnv = new Mock<IHostingEnvironment>();
+			mockEnv
+				.SetupGet(environment => environment.EnvironmentName)
+				.Returns("Testing");
+			var env = mockEnv.Object;
+
+			services.RegisterSharedServices(env, new Mock<IConfiguration>().Object);
+
+			_context = services
+				.BuildServiceProvider()
+				.GetRequiredService<IDataContext>();
+
 			var mockServiceProvider = new Mock<IServiceProvider>();
 			mockServiceProvider
 				.Setup(provider => provider.GetService(typeof(IApiController)))
@@ -43,8 +61,12 @@ namespace StatusMonitor.Tests.ControllerTests
 				new Mock<ILogger<AdminController>>().Object,
 				_mockMetricService.Object,
 				mockServiceProvider.Object,
-				_mockCleanService.Object
+				_mockCleanService.Object,
+				_context,
+				_mockNotificationService.Object,
+				_mockConfig.Object
 			);
+
 			_controller.ControllerContext.HttpContext = new DefaultHttpContext();
 			_controller.TempData = new Mock<ITempDataDictionary>().Object;
 		}
diff --git a/test/ControllerTests/AdminController/ResolveDiscrepancy.cs b/test/ControllerTests/AdminController/ResolveDiscrepancy.cs
new file mode 100644
index 0000000000000000000000000000000000000000..97786d9e1cb5d75df33d99cc38f41df36a7c5544
--- /dev/null
+++ b/test/ControllerTests/AdminController/ResolveDiscrepancy.cs
@@ -0,0 +1,172 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.DependencyInjection;
+using StatusMonitor.Web.Controllers.View;
+using StatusMonitor.Shared.Extensions;
+using StatusMonitor.Shared.Models.Entities;
+using StatusMonitor.Shared.Services;
+using Xunit;
+using Microsoft.AspNetCore.Http;
+using Moq;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Logging;
+using StatusMonitor.Shared.Models;
+using Microsoft.AspNetCore.Mvc.ViewFeatures;
+using StatusMonitor.Web.Services;
+using System.Linq;
+using StatusMonitor.Web.ViewModels;
+using Microsoft.EntityFrameworkCore;
+
+namespace StatusMonitor.Tests.ControllerTests
+{
+	public partial class AdminControllerTest
+	{
+		[Fact]
+		public async Task ResolveDiscrepancySuccess()
+		{
+			// Arrange
+			var now = DateTime.UtcNow;
+
+			await _context.Discrepancies.AddAsync(
+				new Discrepancy
+				{
+					Type = DiscrepancyType.GapInData,
+					MetricType = Metrics.CpuLoad,
+					MetricSource = "the-source",
+					DateFirstOffense = now,
+					Resolved = false
+				}
+			);
+			await _context.SaveChangesAsync();
+
+			var input = new DiscrepancyResolutionViewModel
+			{
+				DateFirstOffense = now,
+				EnumDiscrepancyType = DiscrepancyType.GapInData,
+				EnumMetricType = Metrics.CpuLoad,
+				Source = "the-source"
+			};
+
+			// Act
+			var result = await _controller.ResolveDiscrepancy(input);
+
+			// Assert
+			var redirectResult = Assert.IsType<RedirectToActionResult>(result);
+
+			Assert.Equal("Index", redirectResult.ActionName);
+			Assert.Equal("Admin", redirectResult.ControllerName);
+
+			Assert.True(
+				(await _context
+					.Discrepancies
+					.SingleAsync(d =>
+						d.DateFirstOffense == now &&
+						d.MetricSource == "the-source" &&
+						d.MetricType == Metrics.CpuLoad &&
+						d.Type == DiscrepancyType.GapInData
+					)
+				).Resolved
+			);
+			Assert.InRange(
+				(await _context
+					.Discrepancies
+					.SingleAsync(d =>
+						d.DateFirstOffense == now &&
+						d.MetricSource == "the-source" &&
+						d.MetricType == Metrics.CpuLoad &&
+						d.Type == DiscrepancyType.GapInData
+					)
+				).DateResolved,
+				now,
+				now.AddMinutes(1)
+			);
+
+			_mockNotificationService
+				.Verify(
+					n => n.ScheduleNotificationAsync(
+						It.Is<string>(s => s.Contains("the-source")),
+						NotificationSeverity.Medium
+					)
+				);
+
+			_mockConfig.Verify(conf => conf["ServiceManager:NotificationService:TimeZone"]);
+		}
+
+		[Fact]
+		public async Task ResolveDiscrepancyWarning()
+		{
+			// Arrange
+			var now = DateTime.UtcNow;
+
+			await _context.Discrepancies.AddAsync(
+				new Discrepancy
+				{
+					Type = DiscrepancyType.GapInData,
+					MetricType = Metrics.CpuLoad,
+					MetricSource = "the-source",
+					DateFirstOffense = now,
+					Resolved = true
+				}
+			);
+			await _context.SaveChangesAsync();
+
+			var input = new DiscrepancyResolutionViewModel
+			{
+				DateFirstOffense = now,
+				EnumDiscrepancyType = DiscrepancyType.GapInData,
+				EnumMetricType = Metrics.CpuLoad,
+				Source = "the-source"
+			};
+
+			// Act
+			var result = await _controller.ResolveDiscrepancy(input);
+
+			// Assert
+			var redirectResult = Assert.IsType<RedirectToActionResult>(result);
+
+			Assert.Equal("Index", redirectResult.ActionName);
+			Assert.Equal("Admin", redirectResult.ControllerName);
+
+			_mockNotificationService
+				.Verify(
+					n => n.ScheduleNotificationAsync(
+						It.Is<string>(s => s.Contains("the-source")),
+						NotificationSeverity.Medium
+					),
+					Times.Never()
+				);
+		}
+
+		[Fact]
+		public async Task ResolveDiscrepancyNotFound()
+		{
+			// Arrange
+			var input = new DiscrepancyResolutionViewModel
+			{
+				DateFirstOffense = DateTime.UtcNow,
+				EnumDiscrepancyType = DiscrepancyType.GapInData,
+				EnumMetricType = Metrics.CpuLoad,
+				Source = "the-source"
+			};
+
+			// Act
+			var result = await _controller.ResolveDiscrepancy(input);
+
+			// Assert
+			var notFoundResult = Assert.IsType<NotFoundObjectResult>(result);
+
+			_mockNotificationService
+				.Verify(
+					n => n.ScheduleNotificationAsync(
+						It.Is<string>(s => s.Contains("the-source")),
+						NotificationSeverity.Medium
+					),
+					Times.Never()
+				);
+		}
+	}
+}