diff --git a/src/Umbraco.Core/Configuration/HealthChecks/DisabledHealthCheckElement.cs b/src/Umbraco.Core/Configuration/HealthChecks/DisabledHealthCheckElement.cs new file mode 100644 index 0000000000..3eb22f5928 --- /dev/null +++ b/src/Umbraco.Core/Configuration/HealthChecks/DisabledHealthCheckElement.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Linq; +using System.Text; + +namespace Umbraco.Core.Configuration.HealthChecks +{ + public class DisabledHealthCheckElement : ConfigurationElement + { + private const string ID_KEY = "id"; + private const string DISABLED_ON_KEY = "disabledOn"; + private const string DISABLED_BY_KEY = "disabledBy"; + + [ConfigurationProperty(ID_KEY, IsKey = true, IsRequired = true)] + public Guid Id + { + get + { + return ((Guid)(base[ID_KEY])); + } + } + + [ConfigurationProperty(DISABLED_ON_KEY, IsKey = false, IsRequired = false)] + public DateTime DisabledOn + { + get + { + return ((DateTime)(base[DISABLED_ON_KEY])); + } + } + + [ConfigurationProperty(DISABLED_BY_KEY, IsKey = false, IsRequired = false)] + public int DisabledBy + { + get + { + return ((int)(base[DISABLED_BY_KEY])); + } + } + } +} diff --git a/src/Umbraco.Core/Configuration/HealthChecks/DisabledHealthChecksElementCollection.cs b/src/Umbraco.Core/Configuration/HealthChecks/DisabledHealthChecksElementCollection.cs new file mode 100644 index 0000000000..b429d72f49 --- /dev/null +++ b/src/Umbraco.Core/Configuration/HealthChecks/DisabledHealthChecksElementCollection.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Configuration; + +namespace Umbraco.Core.Configuration.HealthChecks +{ + [ConfigurationCollection(typeof(DisabledHealthCheckElement), AddItemName = "check")] + public class DisabledHealthChecksElementCollection : ConfigurationElementCollection, IEnumerable + { + protected override ConfigurationElement CreateNewElement() + { + return new DisabledHealthCheckElement(); + } + + protected override object GetElementKey(ConfigurationElement element) + { + return ((DisabledHealthCheckElement)(element)).Id; + } + + new public DisabledHealthCheckElement this[string key] + { + get + { + return (DisabledHealthCheckElement)BaseGet(key); + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + for (var i = 0; i < Count; i++) + { + yield return BaseGet(i) as DisabledHealthCheckElement; + } + } + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} diff --git a/src/Umbraco.Core/Configuration/HealthChecks/HealthCheckNotificationSettingsElement.cs b/src/Umbraco.Core/Configuration/HealthChecks/HealthCheckNotificationSettingsElement.cs new file mode 100644 index 0000000000..8c533d9bac --- /dev/null +++ b/src/Umbraco.Core/Configuration/HealthChecks/HealthCheckNotificationSettingsElement.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Linq; +using System.Text; + +namespace Umbraco.Core.Configuration.HealthChecks +{ + public class HealthCheckNotificationSettingsElement : ConfigurationElement + { + private const string ENABLED_KEY = "enabled"; + private const string FIRST_RUN_TIME_KEY = "firstRunTime"; + private const string PERIOD_KEY = "periodInHours"; + private const string RECIPIENT_EMAIL_KEY = "recipientEmail"; + private const string WEBHOOK_URL_KEY = "webHookUrl"; + private const string DISABLED_CHECKS_KEY = "disabledChecks"; + + [ConfigurationProperty(ENABLED_KEY, IsRequired = true)] + public bool Enabled + { + get + { + return ((bool)(base[ENABLED_KEY])); + } + } + + [ConfigurationProperty(FIRST_RUN_TIME_KEY, IsRequired = true)] + public string FirstRunTime + { + get + { + return ((string)(base[FIRST_RUN_TIME_KEY])); + } + } + + [ConfigurationProperty(PERIOD_KEY, IsRequired = true)] + public int PeriodInHours + { + get + { + return ((int)(base[PERIOD_KEY])); + } + } + + [ConfigurationProperty(RECIPIENT_EMAIL_KEY, IsRequired = true)] + public string RecipientEmail + { + get + { + return ((string)(base[RECIPIENT_EMAIL_KEY])); + } + } + + [ConfigurationProperty(WEBHOOK_URL_KEY, IsRequired = true)] + public string WebhookUrl + { + get + { + return ((string)(base[WEBHOOK_URL_KEY])); + } + } + + [ConfigurationProperty(DISABLED_CHECKS_KEY, IsDefaultCollection = true, IsRequired = false)] + public DisabledHealthChecksElementCollection DisabledChecks + { + get + { + return ((DisabledHealthChecksElementCollection)(base[DISABLED_CHECKS_KEY])); + } + } + } +} diff --git a/src/Umbraco.Core/Configuration/HealthChecks/HealthChecksSection.cs b/src/Umbraco.Core/Configuration/HealthChecks/HealthChecksSection.cs new file mode 100644 index 0000000000..20e18c0d6d --- /dev/null +++ b/src/Umbraco.Core/Configuration/HealthChecks/HealthChecksSection.cs @@ -0,0 +1,22 @@ +using System.Configuration; + +namespace Umbraco.Core.Configuration.HealthChecks +{ + public class HealthChecksSection : ConfigurationSection + { + private const string DISABLED_CHECKS_KEY = "disabledChecks"; + private const string NOTIFICATION_SETTINGS_KEY = "notificationSettings"; + + [ConfigurationProperty(DISABLED_CHECKS_KEY)] + public DisabledHealthChecksElementCollection DisabledChecks + { + get { return ((DisabledHealthChecksElementCollection)(base[DISABLED_CHECKS_KEY])); } + } + + [ConfigurationProperty(NOTIFICATION_SETTINGS_KEY, IsRequired = true)] + public HealthCheckNotificationSettingsElement NotificationSettings + { + get { return ((HealthCheckNotificationSettingsElement)(base[NOTIFICATION_SETTINGS_KEY])); } + } + } +} diff --git a/src/Umbraco.Core/DateTimeExtensions.cs b/src/Umbraco.Core/DateTimeExtensions.cs index 1851eded9f..6a127f152d 100644 --- a/src/Umbraco.Core/DateTimeExtensions.cs +++ b/src/Umbraco.Core/DateTimeExtensions.cs @@ -41,7 +41,43 @@ namespace Umbraco.Core Hour, Minute, Second + } + + /// + /// Calculates the number of minutes from a date time, on a rolling daily basis (so if + /// date time is before the time, calculate onto next day) + /// + /// Date to start from + /// Time to compare against (in Hmm form, e.g. 330, 2200) + /// + public static int PeriodicMinutesFrom(this DateTime fromDateTime, string scheduledTime) + { + // Ensure time provided is 4 digits long + if (scheduledTime.Length == 3) + { + scheduledTime = "0" + scheduledTime; + } + + var scheduledHour = int.Parse(scheduledTime.Substring(0, 2)); + var scheduledMinute = int.Parse(scheduledTime.Substring(2)); + + DateTime scheduledDateTime; + if (IsScheduledInRemainingDay(fromDateTime, scheduledHour, scheduledMinute)) + { + scheduledDateTime = new DateTime(fromDateTime.Year, fromDateTime.Month, fromDateTime.Day, scheduledHour, scheduledMinute, 0); + } + else + { + var nextDay = fromDateTime.AddDays(1); + scheduledDateTime = new DateTime(nextDay.Year, nextDay.Month, nextDay.Day, scheduledHour, scheduledMinute, 0); + } + + return (int)(scheduledDateTime - fromDateTime).TotalMinutes; + } + + private static bool IsScheduledInRemainingDay(DateTime fromDateTime, int scheduledHour, int scheduledMinute) + { + return scheduledHour > fromDateTime.Hour || (scheduledHour == fromDateTime.Hour && scheduledMinute >= fromDateTime.Minute); } - } } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index e55eab43e2..6cd97c2db0 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -218,8 +218,12 @@ + + + + diff --git a/src/Umbraco.Tests/DateTimeExtensionsTests.cs b/src/Umbraco.Tests/DateTimeExtensionsTests.cs new file mode 100644 index 0000000000..83a47b3551 --- /dev/null +++ b/src/Umbraco.Tests/DateTimeExtensionsTests.cs @@ -0,0 +1,46 @@ +using System; +using NUnit.Framework; +using Umbraco.Core; + +namespace Umbraco.Tests +{ + [TestFixture] + public class DateTimeExtensionsTests + { + [Test] + public void PeriodicMinutesFrom_PostTime_CalculatesMinutesBetween() + { + var nowDateTime = new DateTime(2017, 1, 1, 10, 30, 0); + var scheduledTime = "1145"; + var minutesBetween = nowDateTime.PeriodicMinutesFrom(scheduledTime); + Assert.AreEqual(75, minutesBetween); + } + + [Test] + public void PeriodicMinutesFrom_PriorTime_CalculatesMinutesBetween() + { + var nowDateTime = new DateTime(2017, 1, 1, 10, 30, 0); + var scheduledTime = "900"; + var minutesBetween = nowDateTime.PeriodicMinutesFrom(scheduledTime); + Assert.AreEqual(1350, minutesBetween); + } + + [Test] + public void PeriodicMinutesFrom_PriorTime_WithLeadingZero_CalculatesMinutesBetween() + { + var nowDateTime = new DateTime(2017, 1, 1, 10, 30, 0); + var scheduledTime = "0900"; + var minutesBetween = nowDateTime.PeriodicMinutesFrom(scheduledTime); + Assert.AreEqual(1350, minutesBetween); + } + + [Test] + public void PeriodicMinutesFrom_SameTime_CalculatesMinutesBetween() + { + var nowDateTime = new DateTime(2017, 1, 1, 10, 30, 0); + var scheduledTime = "1030"; + var minutesBetween = nowDateTime.PeriodicMinutesFrom(scheduledTime); + Assert.AreEqual(0, minutesBetween); + } + } +} diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index eb57d210fc..3b5cd3d6eb 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -161,6 +161,7 @@ + diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index eba9c4f1f2..942d510d13 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -40,8 +40,7 @@ v4.5 true - - + 44319 enabled disabled false @@ -633,6 +632,13 @@ EmbeddedMedia.config + + Designer + + + HealthChecks.config + Designer + umbracoSettings.config Designer diff --git a/src/Umbraco.Web.UI/config/HealthChecks.Release.config b/src/Umbraco.Web.UI/config/HealthChecks.Release.config new file mode 100644 index 0000000000..6d3b43b3b7 --- /dev/null +++ b/src/Umbraco.Web.UI/config/HealthChecks.Release.config @@ -0,0 +1,5 @@ + + + + + diff --git a/src/Umbraco.Web.UI/config/HealthChecks.config b/src/Umbraco.Web.UI/config/HealthChecks.config new file mode 100644 index 0000000000..da7c6d00b4 --- /dev/null +++ b/src/Umbraco.Web.UI/config/HealthChecks.config @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + diff --git a/src/Umbraco.Web.UI/web.Template.Debug.config b/src/Umbraco.Web.UI/web.Template.Debug.config index 22cdd682fe..5f07e506ec 100644 --- a/src/Umbraco.Web.UI/web.Template.Debug.config +++ b/src/Umbraco.Web.UI/web.Template.Debug.config @@ -33,6 +33,7 @@
+
@@ -47,6 +48,7 @@ + diff --git a/src/Umbraco.Web.UI/web.Template.config b/src/Umbraco.Web.UI/web.Template.config index 3a484944e6..c271c006c5 100644 --- a/src/Umbraco.Web.UI/web.Template.config +++ b/src/Umbraco.Web.UI/web.Template.config @@ -12,7 +12,8 @@
- +
+
@@ -26,6 +27,7 @@ + diff --git a/src/Umbraco.Web/HealthCheck/Checks/Config/AbstractConfigCheck.cs b/src/Umbraco.Web/HealthCheck/Checks/Config/AbstractConfigCheck.cs index 001bd4e3e8..0168467bd7 100644 --- a/src/Umbraco.Web/HealthCheck/Checks/Config/AbstractConfigCheck.cs +++ b/src/Umbraco.Web/HealthCheck/Checks/Config/AbstractConfigCheck.cs @@ -7,7 +7,6 @@ using Umbraco.Core.Services; namespace Umbraco.Web.HealthCheck.Checks.Config { - public abstract class AbstractConfigCheck : HealthCheck { private readonly ConfigurationService _configurationService; @@ -46,7 +45,7 @@ namespace Umbraco.Web.HealthCheck.Checks.Config protected AbstractConfigCheck(HealthCheckContext healthCheckContext) : base(healthCheckContext) { _textService = healthCheckContext.ApplicationContext.Services.TextService; - _configurationService = new ConfigurationService(AbsoluteFilePath, XPath); + _configurationService = new ConfigurationService(AbsoluteFilePath, XPath, _textService); } /// diff --git a/src/Umbraco.Web/HealthCheck/Checks/Config/ConfigurationService.cs b/src/Umbraco.Web/HealthCheck/Checks/Config/ConfigurationService.cs index dd92cfa5ec..439ef4cf72 100644 --- a/src/Umbraco.Web/HealthCheck/Checks/Config/ConfigurationService.cs +++ b/src/Umbraco.Web/HealthCheck/Checks/Config/ConfigurationService.cs @@ -17,11 +17,11 @@ namespace Umbraco.Web.HealthCheck.Checks.Config /// The absolute file location of the configuration file /// The XPath to select the value /// - public ConfigurationService(string configFilePath, string xPath) + public ConfigurationService(string configFilePath, string xPath, ILocalizedTextService textService) { _configFilePath = configFilePath; _xPath = xPath; - _textService = UmbracoContext.Current.Application.Services.TextService; + _textService = textService; } /// diff --git a/src/Umbraco.Web/HealthCheck/Checks/Security/ClickJackingCheck.cs b/src/Umbraco.Web/HealthCheck/Checks/Security/ClickJackingCheck.cs index 54f72dc88a..03ef48c5b5 100644 --- a/src/Umbraco.Web/HealthCheck/Checks/Security/ClickJackingCheck.cs +++ b/src/Umbraco.Web/HealthCheck/Checks/Security/ClickJackingCheck.cs @@ -61,13 +61,10 @@ namespace Umbraco.Web.HealthCheck.Checks.Security { var message = string.Empty; var success = false; - var url = HealthCheckContext.HttpContext.Request.Url; // Access the site home page and check for the click-jack protection header or meta tag - var serverVariables = HealthCheckContext.HttpContext.Request.ServerVariables; - var useSsl = GlobalSettings.UseSSL || serverVariables["SERVER_PORT"] == "443"; - var address = string.Format("http{0}://{1}:{2}", useSsl ? "s" : "", url.Host.ToLower(), url.Port); - var request = WebRequest.Create(address); + var url = HealthCheckContext.SiteUrl; + var request = WebRequest.Create(url); request.Method = "GET"; try { @@ -88,7 +85,7 @@ namespace Umbraco.Web.HealthCheck.Checks.Security } catch (Exception ex) { - message = _textService.Localize("healthcheck/httpsCheckInvalidUrl", new[] { address, ex.Message }); + message = _textService.Localize("healthcheck/httpsCheckInvalidUrl", new[] { url, ex.Message }); } var actions = new List(); diff --git a/src/Umbraco.Web/HealthCheck/Checks/Security/ExcessiveHeadersCheck.cs b/src/Umbraco.Web/HealthCheck/Checks/Security/ExcessiveHeadersCheck.cs index 2ca63662d8..a808538a31 100644 --- a/src/Umbraco.Web/HealthCheck/Checks/Security/ExcessiveHeadersCheck.cs +++ b/src/Umbraco.Web/HealthCheck/Checks/Security/ExcessiveHeadersCheck.cs @@ -46,13 +46,10 @@ namespace Umbraco.Web.HealthCheck.Checks.Security { var message = string.Empty; var success = false; - var url = HealthCheckContext.HttpContext.Request.Url; + var url = HealthCheckContext.SiteUrl; // Access the site home page and check for the headers - var serverVariables = HealthCheckContext.HttpContext.Request.ServerVariables; - var useSsl = GlobalSettings.UseSSL || serverVariables["SERVER_PORT"] == "443"; - var address = string.Format("http{0}://{1}:{2}", useSsl ? "s" : "", url.Host.ToLower(), url.Port); - var request = WebRequest.Create(address); + var request = WebRequest.Create(url); request.Method = "HEAD"; try { @@ -69,7 +66,7 @@ namespace Umbraco.Web.HealthCheck.Checks.Security } catch (Exception ex) { - message = _textService.Localize("healthcheck/httpsCheckInvalidUrl", new[] { address, ex.Message }); + message = _textService.Localize("healthcheck/httpsCheckInvalidUrl", new[] { url, ex.Message }); } var actions = new List(); diff --git a/src/Umbraco.Web/HealthCheck/Checks/Security/HttpsCheck.cs b/src/Umbraco.Web/HealthCheck/Checks/Security/HttpsCheck.cs index 4e2dc4f8f5..4235ce665c 100644 --- a/src/Umbraco.Web/HealthCheck/Checks/Security/HttpsCheck.cs +++ b/src/Umbraco.Web/HealthCheck/Checks/Security/HttpsCheck.cs @@ -54,12 +54,11 @@ namespace Umbraco.Web.HealthCheck.Checks.Security { var message = string.Empty; var success = false; - var url = HealthCheckContext.HttpContext.Request.Url; // Attempt to access the site over HTTPS to see if it HTTPS is supported // and a valid certificate has been configured - var address = string.Format("https://{0}:{1}", url.Host.ToLower(), url.Port); - var request = (HttpWebRequest)WebRequest.Create(address); + var url = HealthCheckContext.SiteUrl.Replace("http:", "https:"); + var request = (HttpWebRequest)WebRequest.Create(url); request.Method = "HEAD"; try @@ -74,11 +73,11 @@ namespace Umbraco.Web.HealthCheck.Checks.Security { message = exception.Status == WebExceptionStatus.TrustFailure ? _textService.Localize("healthcheck/httpsCheckInvalidCertificate", new [] { exception.Message }) - : _textService.Localize("healthcheck/httpsCheckInvalidUrl", new [] { address, exception.Message }); + : _textService.Localize("healthcheck/httpsCheckInvalidUrl", new [] { url, exception.Message }); } else { - message = _textService.Localize("healthcheck/httpsCheckInvalidUrl", new[] { address, ex.Message }); + message = _textService.Localize("healthcheck/httpsCheckInvalidUrl", new[] { url, ex.Message }); } } @@ -149,7 +148,7 @@ namespace Umbraco.Web.HealthCheck.Checks.Security { var configFile = IOHelper.MapPath("~/Web.config"); const string xPath = "/configuration/appSettings/add[@key='umbracoUseSSL']/@value"; - var configurationService = new ConfigurationService(configFile, xPath); + var configurationService = new ConfigurationService(configFile, xPath, _textService); var updateConfigFile = configurationService.UpdateConfigFile("true"); if (updateConfigFile.Success) diff --git a/src/Umbraco.Web/HealthCheck/Checks/Services/SmtpCheck.cs b/src/Umbraco.Web/HealthCheck/Checks/Services/SmtpCheck.cs index a1f085865c..6a59bfd65e 100644 --- a/src/Umbraco.Web/HealthCheck/Checks/Services/SmtpCheck.cs +++ b/src/Umbraco.Web/HealthCheck/Checks/Services/SmtpCheck.cs @@ -48,7 +48,7 @@ namespace Umbraco.Web.HealthCheck.Checks.Services var message = string.Empty; var success = false; - var config = WebConfigurationManager.OpenWebConfiguration(HealthCheckContext.HttpContext.Request.ApplicationPath); + var config = WebConfigurationManager.OpenWebConfiguration(HealthCheckContext.ApplicationPath); var settings = (MailSettingsSectionGroup)config.GetSectionGroup("system.net/mailSettings"); if (settings == null) { diff --git a/src/Umbraco.Web/HealthCheck/HealthCheckAction.cs b/src/Umbraco.Web/HealthCheck/HealthCheckAction.cs index 526ca0ece3..18931cf1dd 100644 --- a/src/Umbraco.Web/HealthCheck/HealthCheckAction.cs +++ b/src/Umbraco.Web/HealthCheck/HealthCheckAction.cs @@ -8,6 +8,8 @@ namespace Umbraco.Web.HealthCheck [DataContract(Name = "healtCheckAction", Namespace = "")] public class HealthCheckAction { + private readonly ILocalizedTextService _textService; + /// /// Empty ctor used for serialization /// @@ -51,7 +53,7 @@ namespace Umbraco.Web.HealthCheck /// The name of the action - this is used to name the fix button /// [DataMember(Name = "name")] - private string _name = UmbracoContext.Current.Application.Services.TextService.Localize("healthcheck/rectifyButton"); + private string _name; public string Name { get { return _name; } diff --git a/src/Umbraco.Web/HealthCheck/HealthCheckContext.cs b/src/Umbraco.Web/HealthCheck/HealthCheckContext.cs index 4ec02a2386..eb09eb5f59 100644 --- a/src/Umbraco.Web/HealthCheck/HealthCheckContext.cs +++ b/src/Umbraco.Web/HealthCheck/HealthCheckContext.cs @@ -9,19 +9,44 @@ namespace Umbraco.Web.HealthCheck /// public class HealthCheckContext { + private readonly HttpContextBase _httpContext; + + private readonly UmbracoContext _umbracoContext; + public HealthCheckContext(HttpContextBase httpContext, UmbracoContext umbracoContext) { if (httpContext == null) throw new ArgumentNullException("httpContext"); if (umbracoContext == null) throw new ArgumentNullException("umbracoContext"); - HttpContext = httpContext; - UmbracoContext = umbracoContext; - ApplicationContext = UmbracoContext.Application; + _httpContext = httpContext; + _umbracoContext = umbracoContext; + ApplicationContext = _umbracoContext.Application; + } + + public HealthCheckContext(ApplicationContext applicationContext) + { + ApplicationContext = applicationContext; } - public HttpContextBase HttpContext { get; private set; } - public UmbracoContext UmbracoContext { get; private set; } public ApplicationContext ApplicationContext { get; private set; } - //TODO: Do we need any more info/service exposed here? + public string SiteUrl + { + get + { + return _httpContext != null + ? _httpContext.Request.Url.GetLeftPart(UriPartial.Authority) + : ApplicationContext.UmbracoApplicationUrl.Replace("/umbraco", string.Empty); + } + } + + public string ApplicationPath + { + get + { + return _httpContext != null + ? _httpContext.Request.ApplicationPath + : "/"; + } + } } } \ No newline at end of file diff --git a/src/Umbraco.Web/HealthCheck/HealthCheckResolver.cs b/src/Umbraco.Web/HealthCheck/HealthCheckResolver.cs index 7ae302fa49..80630f20e3 100644 --- a/src/Umbraco.Web/HealthCheck/HealthCheckResolver.cs +++ b/src/Umbraco.Web/HealthCheck/HealthCheckResolver.cs @@ -20,7 +20,7 @@ namespace Umbraco.Web.HealthCheck internal class HealthCheckResolver : LazyManyObjectsResolverBase, IHealthCheckResolver { public HealthCheckResolver(ILogger logger, Func> lazyTypeList) - : base(new HealthCheckServiceProvider(), logger, lazyTypeList, ObjectLifetimeScope.HttpRequest) + : base(new HealthCheckServiceProvider(), logger, lazyTypeList, ObjectLifetimeScope.Transient) { } @@ -46,9 +46,13 @@ namespace Umbraco.Web.HealthCheck var found = serviceType.GetConstructor(normalArgs); if (found != null) { + var gotUmbracoContext = UmbracoContext.Current != null; + var healthCheckContext = gotUmbracoContext + ? new HealthCheckContext(new HttpContextWrapper(HttpContext.Current), UmbracoContext.Current) + : new HealthCheckContext(ApplicationContext.Current); return found.Invoke(new object[] { - new HealthCheckContext(new HttpContextWrapper(HttpContext.Current), UmbracoContext.Current) + healthCheckContext }); } diff --git a/src/Umbraco.Web/Scheduling/HealthCheckNotifier.cs b/src/Umbraco.Web/Scheduling/HealthCheckNotifier.cs new file mode 100644 index 0000000000..794941c6b4 --- /dev/null +++ b/src/Umbraco.Web/Scheduling/HealthCheckNotifier.cs @@ -0,0 +1,86 @@ +using System.Configuration; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Umbraco.Core; +using Umbraco.Core.Configuration.HealthChecks; +using Umbraco.Core.Logging; +using Umbraco.Web.HealthCheck; + +namespace Umbraco.Web.Scheduling +{ + internal class HealthCheckNotifier : RecurringTaskBase + { + private readonly ApplicationContext _appContext; + private readonly IHealthCheckResolver _healthCheckResolver; + + public HealthCheckNotifier(IBackgroundTaskRunner runner, int delayMilliseconds, int periodMilliseconds, + ApplicationContext appContext) + : base(runner, delayMilliseconds, periodMilliseconds) + { + _appContext = appContext; + _healthCheckResolver = HealthCheckResolver.Current; + } + + public override async Task PerformRunAsync(CancellationToken token) + { + if (_appContext == null) return true; // repeat... + + // ensure we do not run if not main domain, but do NOT lock it + if (_appContext.MainDom.IsMainDom == false) + { + LogHelper.Debug("Does not run if not MainDom."); + return false; // do NOT repeat, going down + } + + using (DisposableTimer.DebugDuration(() => "Health checks executing", () => "Health checks complete")) + { + var healthCheckConfig = (HealthChecksSection)ConfigurationManager.GetSection("umbracoConfiguration/HealthChecks"); + + // Don't notify for any checks that are disabled, nor for any disabled + // just for notifications + var disabledCheckIds = healthCheckConfig.NotificationSettings.DisabledChecks + .Select(x => x.Id) + .Union(healthCheckConfig.DisabledChecks + .Select(x => x.Id)) + .Distinct() + .ToArray(); + + var checks = _healthCheckResolver.HealthChecks + .Where(x => disabledCheckIds.Contains(x.Id) == false); + + var sb = new StringBuilder(); + foreach (var check in checks) + { + // TODO: get all sub-checks, not just first + var status = check.GetStatus().First(); + sb.AppendFormat(" - Check {0} returned {1} with message {2}.", check.Name, status.ResultType, status.Message); + sb.AppendLine(); + } + + // TODO: get email address and send + if (!string.IsNullOrEmpty(healthCheckConfig.NotificationSettings.RecipientEmail)) + { + + } + + // TODO: get web hook and post + if (!string.IsNullOrEmpty(healthCheckConfig.NotificationSettings.WebhookUrl)) + { + + } + + LogHelper.Info("Health check results:"); + LogHelper.Info(sb.ToString()); + } + + return true; // repeat + } + + public override bool IsAsync + { + get { return true; } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Scheduling/Scheduler.cs b/src/Umbraco.Web/Scheduling/Scheduler.cs index 82dd32b870..85a5843486 100644 --- a/src/Umbraco.Web/Scheduling/Scheduler.cs +++ b/src/Umbraco.Web/Scheduling/Scheduler.cs @@ -1,8 +1,10 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Configuration; using System.Threading; -using System.Web; using Umbraco.Core; using Umbraco.Core.Configuration; +using Umbraco.Core.Configuration.HealthChecks; using Umbraco.Core.Logging; using Umbraco.Web.Routing; @@ -21,6 +23,7 @@ namespace Umbraco.Web.Scheduling private BackgroundTaskRunner _publishingRunner; private BackgroundTaskRunner _tasksRunner; private BackgroundTaskRunner _scrubberRunner; + private BackgroundTaskRunner _healthCheckRunner; private bool _started = false; private object _locker = new object(); private IBackgroundTask[] _tasks; @@ -35,6 +38,7 @@ namespace Umbraco.Web.Scheduling _publishingRunner = new BackgroundTaskRunner("ScheduledPublishing", applicationContext.ProfilingLogger.Logger); _tasksRunner = new BackgroundTaskRunner("ScheduledTasks", applicationContext.ProfilingLogger.Logger); _scrubberRunner = new BackgroundTaskRunner("LogScrubber", applicationContext.ProfilingLogger.Logger); + _healthCheckRunner = new BackgroundTaskRunner("HealthCheckNotifier", applicationContext.ProfilingLogger.Logger); //We will start the whole process when a successful request is made UmbracoModule.RouteAttempt += UmbracoModuleRouteAttempt; @@ -61,14 +65,29 @@ namespace Umbraco.Web.Scheduling LogHelper.Debug(() => "Initializing the scheduler"); var settings = UmbracoConfig.For.UmbracoSettings(); + var healthCheckConfig = (HealthChecksSection)ConfigurationManager.GetSection("umbracoConfiguration/HealthChecks"); + + const int DelayMilliseconds = 60000; var tasks = new List { - new KeepAlive(_keepAliveRunner, 60000, 300000, e.UmbracoContext.Application), - new ScheduledPublishing(_publishingRunner, 60000, 60000, e.UmbracoContext.Application, settings), - new ScheduledTasks(_tasksRunner, 60000, 60000, e.UmbracoContext.Application, settings), - new LogScrubber(_scrubberRunner, 60000, LogScrubber.GetLogScrubbingInterval(settings), e.UmbracoContext.Application, settings) + new KeepAlive(_keepAliveRunner, DelayMilliseconds, 300000, e.UmbracoContext.Application), + new ScheduledPublishing(_publishingRunner, DelayMilliseconds, 60000, e.UmbracoContext.Application, settings), + new ScheduledTasks(_tasksRunner, DelayMilliseconds, 60000, e.UmbracoContext.Application, settings), + new LogScrubber(_scrubberRunner, DelayMilliseconds, LogScrubber.GetLogScrubbingInterval(settings), e.UmbracoContext.Application, settings), }; + if (healthCheckConfig.NotificationSettings.Enabled) + { + var delayInMilliseconds = DateTime.Now.PeriodicMinutesFrom(healthCheckConfig.NotificationSettings.FirstRunTime) * 60 * 1000; + if (delayInMilliseconds < DelayMilliseconds) + { + delayInMilliseconds = DelayMilliseconds; + } + + var periodInMilliseconds = healthCheckConfig.NotificationSettings.PeriodInHours * 60 * 60 * 1000; + tasks.Add(new HealthCheckNotifier(_healthCheckRunner, delayInMilliseconds, periodInMilliseconds, e.UmbracoContext.Application)); + } + // ping/keepalive // on all servers _keepAliveRunner.TryAdd(tasks[0]); @@ -83,6 +102,11 @@ namespace Umbraco.Web.Scheduling // install on all, will only run on non-slaves servers _scrubberRunner.TryAdd(tasks[3]); + if (healthCheckConfig.NotificationSettings.Enabled) + { + _healthCheckRunner.TryAdd(tasks[4]); + } + return tasks.ToArray(); }); } diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 0b5652e509..34a9274065 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -419,6 +419,7 @@ +