New: App health displayed in UI
This commit is contained in:
parent
90a6bcaa47
commit
c8ae9f40fb
|
@ -0,0 +1,32 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using NzbDrone.Core.Datastore.Events;
|
||||||
|
using NzbDrone.Core.HealthCheck;
|
||||||
|
using NzbDrone.Core.Messaging.Commands;
|
||||||
|
using NzbDrone.Core.Messaging.Events;
|
||||||
|
|
||||||
|
namespace NzbDrone.Api.Health
|
||||||
|
{
|
||||||
|
public class HealthModule : NzbDroneRestModuleWithSignalR<HealthResource, HealthCheck>,
|
||||||
|
IHandle<TriggerHealthCheckEvent>
|
||||||
|
{
|
||||||
|
private readonly IHealthCheckService _healthCheckService;
|
||||||
|
|
||||||
|
public HealthModule(ICommandExecutor commandExecutor, IHealthCheckService healthCheckService)
|
||||||
|
: base(commandExecutor)
|
||||||
|
{
|
||||||
|
_healthCheckService = healthCheckService;
|
||||||
|
GetResourceAll = GetHealth;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<HealthResource> GetHealth()
|
||||||
|
{
|
||||||
|
return ToListResource(_healthCheckService.PerformHealthCheck);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Handle(TriggerHealthCheckEvent message)
|
||||||
|
{
|
||||||
|
BroadcastResourceChange(ModelAction.Sync);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
using System;
|
||||||
|
using NzbDrone.Api.REST;
|
||||||
|
using NzbDrone.Core.HealthCheck;
|
||||||
|
|
||||||
|
|
||||||
|
namespace NzbDrone.Api.Health
|
||||||
|
{
|
||||||
|
public class HealthResource : RestResource
|
||||||
|
{
|
||||||
|
public HealthCheckResultType Type { get; set; }
|
||||||
|
public String Message { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -132,6 +132,8 @@
|
||||||
<Compile Include="Frontend\Mappers\IMapHttpRequestsToDisk.cs" />
|
<Compile Include="Frontend\Mappers\IMapHttpRequestsToDisk.cs" />
|
||||||
<Compile Include="Frontend\Mappers\StaticResourceMapperBase.cs" />
|
<Compile Include="Frontend\Mappers\StaticResourceMapperBase.cs" />
|
||||||
<Compile Include="Frontend\StaticResourceModule.cs" />
|
<Compile Include="Frontend\StaticResourceModule.cs" />
|
||||||
|
<Compile Include="Health\HistoryResource.cs" />
|
||||||
|
<Compile Include="Health\HealthModule.cs" />
|
||||||
<Compile Include="History\HistoryResource.cs" />
|
<Compile Include="History\HistoryResource.cs" />
|
||||||
<Compile Include="History\HistoryModule.cs" />
|
<Compile Include="History\HistoryModule.cs" />
|
||||||
<Compile Include="Metadata\MetadataResource.cs" />
|
<Compile Include="Metadata\MetadataResource.cs" />
|
||||||
|
|
|
@ -92,7 +92,7 @@ namespace NzbDrone.Automation.Test.PageModel
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
return Find(By.LinkText("System"));
|
return Find(By.PartialLinkText("System"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -52,5 +52,10 @@ namespace NzbDrone.Common
|
||||||
{
|
{
|
||||||
return CollapseSpace.Replace(text, " ").Trim();
|
return CollapseSpace.Replace(text, " ").Trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool IsNullOrWhiteSpace(this string text)
|
||||||
|
{
|
||||||
|
return String.IsNullOrWhiteSpace(text);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using FluentAssertions;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using NzbDrone.Core.Download;
|
||||||
|
using NzbDrone.Core.HealthCheck.Checks;
|
||||||
|
using NzbDrone.Core.Test.Framework;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class DownloadClientCheckFixture : CoreTest<DownloadClientCheck>
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void should_return_warning_when_download_client_has_not_been_configured()
|
||||||
|
{
|
||||||
|
Mocker.GetMock<IProvideDownloadClient>()
|
||||||
|
.Setup(s => s.GetDownloadClient())
|
||||||
|
.Returns((IDownloadClient)null);
|
||||||
|
|
||||||
|
Subject.Check().ShouldBeWarning();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_return_error_when_download_client_throws()
|
||||||
|
{
|
||||||
|
var downloadClient = Mocker.GetMock<IDownloadClient>();
|
||||||
|
|
||||||
|
downloadClient.Setup(s => s.GetQueue())
|
||||||
|
.Throws<Exception>();
|
||||||
|
|
||||||
|
Mocker.GetMock<IProvideDownloadClient>()
|
||||||
|
.Setup(s => s.GetDownloadClient())
|
||||||
|
.Returns(downloadClient.Object);
|
||||||
|
|
||||||
|
Subject.Check().ShouldBeError();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_return_null_when_download_client_returns()
|
||||||
|
{
|
||||||
|
var downloadClient = Mocker.GetMock<IDownloadClient>();
|
||||||
|
|
||||||
|
downloadClient.Setup(s => s.GetQueue())
|
||||||
|
.Returns(new List<QueueItem>());
|
||||||
|
|
||||||
|
Mocker.GetMock<IProvideDownloadClient>()
|
||||||
|
.Setup(s => s.GetDownloadClient())
|
||||||
|
.Returns(downloadClient.Object);
|
||||||
|
|
||||||
|
Subject.Check().Should().BeNull();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Moq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using NzbDrone.Common.Disk;
|
||||||
|
using NzbDrone.Core.Configuration;
|
||||||
|
using NzbDrone.Core.HealthCheck.Checks;
|
||||||
|
using NzbDrone.Core.Test.Framework;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class DroneFactoryCheckFixture : CoreTest<DroneFactoryCheck>
|
||||||
|
{
|
||||||
|
private const string DRONE_FACTORY_FOLDER = @"C:\Test\Unsorted";
|
||||||
|
|
||||||
|
private void GivenDroneFactoryFolder(bool exists = false)
|
||||||
|
{
|
||||||
|
Mocker.GetMock<IConfigService>()
|
||||||
|
.SetupGet(s => s.DownloadedEpisodesFolder)
|
||||||
|
.Returns(DRONE_FACTORY_FOLDER);
|
||||||
|
|
||||||
|
Mocker.GetMock<IDiskProvider>()
|
||||||
|
.Setup(s => s.FolderExists(DRONE_FACTORY_FOLDER))
|
||||||
|
.Returns(exists);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_return_warning_when_drone_factory_folder_is_not_configured()
|
||||||
|
{
|
||||||
|
Mocker.GetMock<IConfigService>()
|
||||||
|
.SetupGet(s => s.DownloadedEpisodesFolder)
|
||||||
|
.Returns("");
|
||||||
|
|
||||||
|
Subject.Check().ShouldBeWarning();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_return_error_when_drone_factory_folder_does_not_exist()
|
||||||
|
{
|
||||||
|
GivenDroneFactoryFolder();
|
||||||
|
|
||||||
|
Subject.Check().ShouldBeError();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_return_error_when_unable_to_write_to_drone_factory_folder()
|
||||||
|
{
|
||||||
|
GivenDroneFactoryFolder(true);
|
||||||
|
|
||||||
|
Mocker.GetMock<IDiskProvider>()
|
||||||
|
.Setup(s => s.WriteAllText(It.IsAny<String>(), It.IsAny<String>()))
|
||||||
|
.Throws<Exception>();
|
||||||
|
|
||||||
|
Subject.Check().ShouldBeError();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_return_null_when_no_issues_found()
|
||||||
|
{
|
||||||
|
GivenDroneFactoryFolder(true);
|
||||||
|
|
||||||
|
Subject.Check().Should().BeNull();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
using FluentAssertions;
|
||||||
|
using NzbDrone.Core.HealthCheck;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||||
|
{
|
||||||
|
public static class HealthCheckFixtureExtensions
|
||||||
|
{
|
||||||
|
public static void ShouldBeWarning(this Core.HealthCheck.HealthCheck result)
|
||||||
|
{
|
||||||
|
result.Type.Should().Be(HealthCheckResultType.Warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ShouldBeError(this Core.HealthCheck.HealthCheck result)
|
||||||
|
{
|
||||||
|
result.Type.Should().Be(HealthCheckResultType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using FizzWare.NBuilder;
|
||||||
|
using FluentAssertions;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using NzbDrone.Core.Download;
|
||||||
|
using NzbDrone.Core.HealthCheck.Checks;
|
||||||
|
using NzbDrone.Core.Indexers;
|
||||||
|
using NzbDrone.Core.Test.Framework;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class IndexerCheckFixture : CoreTest<IndexerCheck>
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void should_return_error_when_not_indexers_are_enabled()
|
||||||
|
{
|
||||||
|
Mocker.GetMock<IIndexerFactory>()
|
||||||
|
.Setup(s => s.GetAvailableProviders())
|
||||||
|
.Returns(new List<IIndexer>());
|
||||||
|
|
||||||
|
Subject.Check().ShouldBeError();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_return_warning_when_only_enabled_indexer_is_wombles()
|
||||||
|
{
|
||||||
|
var indexer = Mocker.GetMock<IIndexer>();
|
||||||
|
indexer.SetupGet(s => s.SupportsSearching).Returns(false);
|
||||||
|
|
||||||
|
Mocker.GetMock<IIndexerFactory>()
|
||||||
|
.Setup(s => s.GetAvailableProviders())
|
||||||
|
.Returns(new List<IIndexer>{indexer.Object});
|
||||||
|
|
||||||
|
Subject.Check().ShouldBeWarning();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_return_null_when_multiple_multiple_indexers_are_enabled()
|
||||||
|
{
|
||||||
|
var indexers = new List<IIndexer>{Mocker.GetMock<IIndexer>().Object, Mocker.GetMock<IIndexer>().Object};
|
||||||
|
|
||||||
|
Mocker.GetMock<IIndexerFactory>()
|
||||||
|
.Setup(s => s.GetAvailableProviders())
|
||||||
|
.Returns(indexers);
|
||||||
|
|
||||||
|
Subject.Check().Should().BeNull();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
using System;
|
||||||
|
using Moq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using NzbDrone.Common.Disk;
|
||||||
|
using NzbDrone.Common.EnvironmentInfo;
|
||||||
|
using NzbDrone.Core.HealthCheck.Checks;
|
||||||
|
using NzbDrone.Core.Test.Framework;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Test.HealthCheck.Checks
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class UpdateCheckFixture : CoreTest<UpdateCheck>
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void should_return_error_when_app_folder_is_write_protected()
|
||||||
|
{
|
||||||
|
WindowsOnly();
|
||||||
|
|
||||||
|
Mocker.GetMock<IAppFolderInfo>()
|
||||||
|
.Setup(s => s.StartUpFolder)
|
||||||
|
.Returns(@"C:\NzbDrone");
|
||||||
|
|
||||||
|
Mocker.GetMock<IDiskProvider>()
|
||||||
|
.Setup(s => s.WriteAllText(It.IsAny<String>(), It.IsAny<String>()))
|
||||||
|
.Throws<Exception>();
|
||||||
|
|
||||||
|
Subject.Check().ShouldBeError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -125,6 +125,11 @@
|
||||||
<Compile Include="Framework\CoreTest.cs" />
|
<Compile Include="Framework\CoreTest.cs" />
|
||||||
<Compile Include="Framework\DbTest.cs" />
|
<Compile Include="Framework\DbTest.cs" />
|
||||||
<Compile Include="Framework\NBuilderExtensions.cs" />
|
<Compile Include="Framework\NBuilderExtensions.cs" />
|
||||||
|
<Compile Include="HealthCheck\Checks\UpdateCheckFixture.cs" />
|
||||||
|
<Compile Include="HealthCheck\Checks\IndexerCheckFixture.cs" />
|
||||||
|
<Compile Include="HealthCheck\Checks\DroneFactoryCheckFixture.cs" />
|
||||||
|
<Compile Include="HealthCheck\Checks\DownloadClientCheckFixture.cs" />
|
||||||
|
<Compile Include="HealthCheck\Checks\HealthCheckFixtureExtentions.cs" />
|
||||||
<Compile Include="HistoryTests\HistoryServiceFixture.cs" />
|
<Compile Include="HistoryTests\HistoryServiceFixture.cs" />
|
||||||
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedHistoryItemsFixture.cs" />
|
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedHistoryItemsFixture.cs" />
|
||||||
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedEpisodeFilesFixture.cs" />
|
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedEpisodeFilesFixture.cs" />
|
||||||
|
|
|
@ -58,11 +58,21 @@ namespace NzbDrone.Core.Download.Clients.Blackhole
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Execute(TestBlackholeCommand message)
|
public override void Test()
|
||||||
{
|
{
|
||||||
var testPath = Path.Combine(message.Folder, "drone_test.txt");
|
PerformTest(Settings.Folder);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PerformTest(string folder)
|
||||||
|
{
|
||||||
|
var testPath = Path.Combine(folder, "drone_test.txt");
|
||||||
_diskProvider.WriteAllText(testPath, DateTime.Now.ToString());
|
_diskProvider.WriteAllText(testPath, DateTime.Now.ToString());
|
||||||
_diskProvider.DeleteFile(testPath);
|
_diskProvider.DeleteFile(testPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Execute(TestBlackholeCommand message)
|
||||||
|
{
|
||||||
|
PerformTest(message.Folder);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,7 +80,12 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public VersionResponse GetVersion(string host = null, int port = 0, string username = null, string password = null)
|
public override void Test()
|
||||||
|
{
|
||||||
|
_proxy.GetVersion(Settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
private VersionResponse GetVersion(string host = null, int port = 0, string username = null, string password = null)
|
||||||
{
|
{
|
||||||
return _proxy.GetVersion(Settings);
|
return _proxy.GetVersion(Settings);
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,11 +80,21 @@ namespace NzbDrone.Core.Download.Clients.Pneumatic
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Execute(TestPneumaticCommand message)
|
public override void Test()
|
||||||
{
|
{
|
||||||
var testPath = Path.Combine(message.Folder, "drone_test.txt");
|
PerformTest(Settings.Folder);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PerformTest(string folder)
|
||||||
|
{
|
||||||
|
var testPath = Path.Combine(folder, "drone_test.txt");
|
||||||
_diskProvider.WriteAllText(testPath, DateTime.Now.ToString());
|
_diskProvider.WriteAllText(testPath, DateTime.Now.ToString());
|
||||||
_diskProvider.DeleteFile(testPath);
|
_diskProvider.DeleteFile(testPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Execute(TestPneumaticCommand message)
|
||||||
|
{
|
||||||
|
PerformTest(message.Folder);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -122,6 +122,11 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||||
_sabnzbdProxy.RemoveFrom("history", id, Settings);
|
_sabnzbdProxy.RemoveFrom("history", id, Settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void Test()
|
||||||
|
{
|
||||||
|
_sabnzbdProxy.GetCategories(Settings);
|
||||||
|
}
|
||||||
|
|
||||||
public void Execute(TestSabnzbdCommand message)
|
public void Execute(TestSabnzbdCommand message)
|
||||||
{
|
{
|
||||||
var settings = new SabnzbdSettings();
|
var settings = new SabnzbdSettings();
|
||||||
|
|
|
@ -44,5 +44,6 @@ namespace NzbDrone.Core.Download
|
||||||
public abstract IEnumerable<HistoryItem> GetHistory(int start = 0, int limit = 10);
|
public abstract IEnumerable<HistoryItem> GetHistory(int start = 0, int limit = 10);
|
||||||
public abstract void RemoveFromQueue(string id);
|
public abstract void RemoveFromQueue(string id);
|
||||||
public abstract void RemoveFromHistory(string id);
|
public abstract void RemoveFromHistory(string id);
|
||||||
|
public abstract void Test();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common.Composition;
|
using NzbDrone.Common.Composition;
|
||||||
|
using NzbDrone.Core.Messaging.Events;
|
||||||
using NzbDrone.Core.ThingiProvider;
|
using NzbDrone.Core.ThingiProvider;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Download
|
namespace NzbDrone.Core.Download
|
||||||
|
@ -15,8 +16,8 @@ namespace NzbDrone.Core.Download
|
||||||
{
|
{
|
||||||
private readonly IDownloadClientRepository _providerRepository;
|
private readonly IDownloadClientRepository _providerRepository;
|
||||||
|
|
||||||
public DownloadClientFactory(IDownloadClientRepository providerRepository, IEnumerable<IDownloadClient> providers, IContainer container, Logger logger)
|
public DownloadClientFactory(IDownloadClientRepository providerRepository, IEnumerable<IDownloadClient> providers, IContainer container, IEventAggregator eventAggregator, Logger logger)
|
||||||
: base(providerRepository, providers, container, logger)
|
: base(providerRepository, providers, container, eventAggregator, logger)
|
||||||
{
|
{
|
||||||
_providerRepository = providerRepository;
|
_providerRepository = providerRepository;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,4 @@
|
||||||
using System.Collections.Generic;
|
using System.Linq;
|
||||||
using System.Linq;
|
|
||||||
using NzbDrone.Core.Configuration;
|
|
||||||
using NzbDrone.Core.Download.Clients;
|
|
||||||
using NzbDrone.Core.Download.Clients.Nzbget;
|
|
||||||
using NzbDrone.Core.Download.Clients.Sabnzbd;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.Download
|
namespace NzbDrone.Core.Download
|
||||||
{
|
{
|
||||||
|
|
|
@ -11,5 +11,6 @@ namespace NzbDrone.Core.Download
|
||||||
IEnumerable<HistoryItem> GetHistory(int start = 0, int limit = 0);
|
IEnumerable<HistoryItem> GetHistory(int start = 0, int limit = 0);
|
||||||
void RemoveFromQueue(string id);
|
void RemoveFromQueue(string id);
|
||||||
void RemoveFromHistory(string id);
|
void RemoveFromHistory(string id);
|
||||||
|
void Test();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
using NzbDrone.Core.Messaging.Commands;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.HealthCheck
|
||||||
|
{
|
||||||
|
public class CheckHealthCommand : Command
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
using System;
|
||||||
|
using NzbDrone.Core.Download;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.HealthCheck.Checks
|
||||||
|
{
|
||||||
|
public class DownloadClientCheck : IProvideHealthCheck
|
||||||
|
{
|
||||||
|
private readonly IProvideDownloadClient _downloadClientProvider;
|
||||||
|
|
||||||
|
public DownloadClientCheck(IProvideDownloadClient downloadClientProvider)
|
||||||
|
{
|
||||||
|
_downloadClientProvider = downloadClientProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HealthCheck Check()
|
||||||
|
{
|
||||||
|
var downloadClient = _downloadClientProvider.GetDownloadClient();
|
||||||
|
|
||||||
|
if (downloadClient == null)
|
||||||
|
{
|
||||||
|
return new HealthCheck(HealthCheckResultType.Warning, "No download client is available");
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
downloadClient.GetQueue();
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
return new HealthCheck(HealthCheckResultType.Error, "Unable to communicate with download client");
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using NzbDrone.Common;
|
||||||
|
using NzbDrone.Common.Disk;
|
||||||
|
using NzbDrone.Core.Configuration;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.HealthCheck.Checks
|
||||||
|
{
|
||||||
|
public class DroneFactoryCheck : IProvideHealthCheck
|
||||||
|
{
|
||||||
|
private readonly IConfigService _configService;
|
||||||
|
private readonly IDiskProvider _diskProvider;
|
||||||
|
|
||||||
|
public DroneFactoryCheck(IConfigService configService, IDiskProvider diskProvider)
|
||||||
|
{
|
||||||
|
_configService = configService;
|
||||||
|
_diskProvider = diskProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HealthCheck Check()
|
||||||
|
{
|
||||||
|
var droneFactoryFolder = _configService.DownloadedEpisodesFolder;
|
||||||
|
|
||||||
|
if (droneFactoryFolder.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
return new HealthCheck(HealthCheckResultType.Warning, "Drone factory folder is not configured");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_diskProvider.FolderExists(droneFactoryFolder))
|
||||||
|
{
|
||||||
|
return new HealthCheck(HealthCheckResultType.Error, "Drone factory folder does not exist");
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var testPath = Path.Combine(droneFactoryFolder, "drone_test.txt");
|
||||||
|
_diskProvider.WriteAllText(testPath, DateTime.Now.ToString());
|
||||||
|
_diskProvider.DeleteFile(testPath);
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
return new HealthCheck(HealthCheckResultType.Error, "Unable to write to drone factory folder");
|
||||||
|
}
|
||||||
|
|
||||||
|
//Todo: Unable to import one or more files/folders from
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
using System.Linq;
|
||||||
|
using NzbDrone.Core.Indexers;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.HealthCheck.Checks
|
||||||
|
{
|
||||||
|
public class IndexerCheck : IProvideHealthCheck
|
||||||
|
{
|
||||||
|
private readonly IIndexerFactory _indexerFactory;
|
||||||
|
|
||||||
|
public IndexerCheck(IIndexerFactory indexerFactory)
|
||||||
|
{
|
||||||
|
_indexerFactory = indexerFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HealthCheck Check()
|
||||||
|
{
|
||||||
|
var enabled = _indexerFactory.GetAvailableProviders();
|
||||||
|
|
||||||
|
if (!enabled.Any())
|
||||||
|
{
|
||||||
|
return new HealthCheck(HealthCheckResultType.Error, "No indexers are enabled");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (enabled.All(i => i.SupportsSearching == false))
|
||||||
|
{
|
||||||
|
return new HealthCheck(HealthCheckResultType.Warning, "Enabled indexers do not support searching");
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using NzbDrone.Common.Disk;
|
||||||
|
using NzbDrone.Common.EnvironmentInfo;
|
||||||
|
using NzbDrone.Core.Update;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.HealthCheck.Checks
|
||||||
|
{
|
||||||
|
public class UpdateCheck : IProvideHealthCheck
|
||||||
|
{
|
||||||
|
private readonly IDiskProvider _diskProvider;
|
||||||
|
private readonly IAppFolderInfo _appFolderInfo;
|
||||||
|
private readonly ICheckUpdateService _checkUpdateService;
|
||||||
|
|
||||||
|
public UpdateCheck(IDiskProvider diskProvider, IAppFolderInfo appFolderInfo, ICheckUpdateService checkUpdateService)
|
||||||
|
{
|
||||||
|
_diskProvider = diskProvider;
|
||||||
|
_appFolderInfo = appFolderInfo;
|
||||||
|
_checkUpdateService = checkUpdateService;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public HealthCheck Check()
|
||||||
|
{
|
||||||
|
if (OsInfo.IsWindows)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var testPath = Path.Combine(_appFolderInfo.StartUpFolder, "drone_test.txt");
|
||||||
|
_diskProvider.WriteAllText(testPath, DateTime.Now.ToString());
|
||||||
|
_diskProvider.DeleteFile(testPath);
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
return new HealthCheck(HealthCheckResultType.Error,
|
||||||
|
"Unable to update, running from write-protected folder");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BuildInfo.BuildDateTime < DateTime.UtcNow.AddDays(-14))
|
||||||
|
{
|
||||||
|
if (_checkUpdateService.AvailableUpdate() != null)
|
||||||
|
{
|
||||||
|
return new HealthCheck(HealthCheckResultType.Warning, "New update is available");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
using System;
|
||||||
|
using NzbDrone.Core.Datastore;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.HealthCheck
|
||||||
|
{
|
||||||
|
public class HealthCheck : ModelBase
|
||||||
|
{
|
||||||
|
public HealthCheckResultType Type { get; set; }
|
||||||
|
public String Message { get; set; }
|
||||||
|
|
||||||
|
public HealthCheck(HealthCheckResultType type, string message)
|
||||||
|
{
|
||||||
|
Type = type;
|
||||||
|
Message = message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum HealthCheckResultType
|
||||||
|
{
|
||||||
|
Warning = 1,
|
||||||
|
Error = 2
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Core.Configuration.Events;
|
||||||
|
using NzbDrone.Core.Download;
|
||||||
|
using NzbDrone.Core.Indexers;
|
||||||
|
using NzbDrone.Core.Messaging.Commands;
|
||||||
|
using NzbDrone.Core.Messaging.Events;
|
||||||
|
using NzbDrone.Core.ThingiProvider.Events;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.HealthCheck
|
||||||
|
{
|
||||||
|
public interface IHealthCheckService
|
||||||
|
{
|
||||||
|
List<HealthCheck> PerformHealthCheck();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class HealthCheckService : IHealthCheckService,
|
||||||
|
IExecute<CheckHealthCommand>,
|
||||||
|
IHandleAsync<ConfigSavedEvent>,
|
||||||
|
IHandleAsync<ProviderUpdatedEvent<IIndexer>>,
|
||||||
|
IHandleAsync<ProviderUpdatedEvent<IDownloadClient>>
|
||||||
|
{
|
||||||
|
private readonly IEnumerable<IProvideHealthCheck> _healthChecks;
|
||||||
|
private readonly IEventAggregator _eventAggregator;
|
||||||
|
private readonly Logger _logger;
|
||||||
|
|
||||||
|
public HealthCheckService(IEnumerable<IProvideHealthCheck> healthChecks, IEventAggregator eventAggregator, Logger logger)
|
||||||
|
{
|
||||||
|
_healthChecks = healthChecks;
|
||||||
|
_eventAggregator = eventAggregator;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<HealthCheck> PerformHealthCheck()
|
||||||
|
{
|
||||||
|
_logger.Trace("Checking health");
|
||||||
|
var result = _healthChecks.Select(c => c.Check()).Where(c => c != null).ToList();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Execute(CheckHealthCommand message)
|
||||||
|
{
|
||||||
|
//Until we have stored health checks we should just trigger the complete event
|
||||||
|
//and let the clients check in
|
||||||
|
//Multiple connected clients means we're going to compute the health check multiple times
|
||||||
|
//Multiple checks feels a bit ugly, but means the most up to date information goes to the client
|
||||||
|
_eventAggregator.PublishEvent(new TriggerHealthCheckEvent());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void HandleAsync(ConfigSavedEvent message)
|
||||||
|
{
|
||||||
|
_eventAggregator.PublishEvent(new TriggerHealthCheckEvent());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void HandleAsync(ProviderUpdatedEvent<IIndexer> message)
|
||||||
|
{
|
||||||
|
_eventAggregator.PublishEvent(new TriggerHealthCheckEvent());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void HandleAsync(ProviderUpdatedEvent<IDownloadClient> message)
|
||||||
|
{
|
||||||
|
_eventAggregator.PublishEvent(new TriggerHealthCheckEvent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
namespace NzbDrone.Core.HealthCheck
|
||||||
|
{
|
||||||
|
public interface IProvideHealthCheck
|
||||||
|
{
|
||||||
|
HealthCheck Check();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
using NzbDrone.Common.Messaging;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.HealthCheck
|
||||||
|
{
|
||||||
|
public class TriggerHealthCheckEvent : IEvent
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,6 +9,7 @@ namespace NzbDrone.Core.Indexers
|
||||||
IParseFeed Parser { get; }
|
IParseFeed Parser { get; }
|
||||||
DownloadProtocol Protocol { get; }
|
DownloadProtocol Protocol { get; }
|
||||||
Boolean SupportsPaging { get; }
|
Boolean SupportsPaging { get; }
|
||||||
|
Boolean SupportsSearching { get; }
|
||||||
|
|
||||||
IEnumerable<string> RecentFeed { get; }
|
IEnumerable<string> RecentFeed { get; }
|
||||||
IEnumerable<string> GetEpisodeSearchUrls(string seriesTitle, int tvRageId, int seasonNumber, int episodeNumber);
|
IEnumerable<string> GetEpisodeSearchUrls(string seriesTitle, int tvRageId, int seasonNumber, int episodeNumber);
|
||||||
|
|
|
@ -35,6 +35,7 @@ namespace NzbDrone.Core.Indexers
|
||||||
public abstract DownloadProtocol Protocol { get; }
|
public abstract DownloadProtocol Protocol { get; }
|
||||||
|
|
||||||
public abstract bool SupportsPaging { get; }
|
public abstract bool SupportsPaging { get; }
|
||||||
|
public virtual bool SupportsSearching { get { return true; } }
|
||||||
|
|
||||||
protected TSettings Settings
|
protected TSettings Settings
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common.Composition;
|
using NzbDrone.Common.Composition;
|
||||||
|
using NzbDrone.Core.Messaging.Events;
|
||||||
using NzbDrone.Core.ThingiProvider;
|
using NzbDrone.Core.ThingiProvider;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Indexers
|
namespace NzbDrone.Core.Indexers
|
||||||
|
@ -16,8 +17,8 @@ namespace NzbDrone.Core.Indexers
|
||||||
private readonly IIndexerRepository _providerRepository;
|
private readonly IIndexerRepository _providerRepository;
|
||||||
private readonly INewznabTestService _newznabTestService;
|
private readonly INewznabTestService _newznabTestService;
|
||||||
|
|
||||||
public IndexerFactory(IIndexerRepository providerRepository, IEnumerable<IIndexer> providers, IContainer container, INewznabTestService newznabTestService, Logger logger)
|
public IndexerFactory(IIndexerRepository providerRepository, IEnumerable<IIndexer> providers, IContainer container, IEventAggregator eventAggregator, INewznabTestService newznabTestService, Logger logger)
|
||||||
: base(providerRepository, providers, container, logger)
|
: base(providerRepository, providers, container, eventAggregator, logger)
|
||||||
{
|
{
|
||||||
_providerRepository = providerRepository;
|
_providerRepository = providerRepository;
|
||||||
_newznabTestService = newznabTestService;
|
_newznabTestService = newznabTestService;
|
||||||
|
|
|
@ -22,6 +22,14 @@ namespace NzbDrone.Core.Indexers.Wombles
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override bool SupportsSearching
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override IParseFeed Parser
|
public override IParseFeed Parser
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|
|
@ -7,6 +7,7 @@ using NzbDrone.Core.Configuration.Events;
|
||||||
using NzbDrone.Core.DataAugmentation.Scene;
|
using NzbDrone.Core.DataAugmentation.Scene;
|
||||||
using NzbDrone.Core.DataAugmentation.Xem;
|
using NzbDrone.Core.DataAugmentation.Xem;
|
||||||
using NzbDrone.Core.Download;
|
using NzbDrone.Core.Download;
|
||||||
|
using NzbDrone.Core.HealthCheck;
|
||||||
using NzbDrone.Core.Housekeeping;
|
using NzbDrone.Core.Housekeeping;
|
||||||
using NzbDrone.Core.Indexers;
|
using NzbDrone.Core.Indexers;
|
||||||
using NzbDrone.Core.Instrumentation.Commands;
|
using NzbDrone.Core.Instrumentation.Commands;
|
||||||
|
@ -50,6 +51,7 @@ namespace NzbDrone.Core.Jobs
|
||||||
new ScheduledTask{ Interval = 1, TypeName = typeof(DownloadedEpisodesScanCommand).FullName},
|
new ScheduledTask{ Interval = 1, TypeName = typeof(DownloadedEpisodesScanCommand).FullName},
|
||||||
new ScheduledTask{ Interval = 1, TypeName = typeof(TrackedCommandCleanupCommand).FullName},
|
new ScheduledTask{ Interval = 1, TypeName = typeof(TrackedCommandCleanupCommand).FullName},
|
||||||
new ScheduledTask{ Interval = 1, TypeName = typeof(CheckForFailedDownloadCommand).FullName},
|
new ScheduledTask{ Interval = 1, TypeName = typeof(CheckForFailedDownloadCommand).FullName},
|
||||||
|
new ScheduledTask{ Interval = 5, TypeName = typeof(CheckHealthCommand).FullName},
|
||||||
new ScheduledTask{ Interval = 1*60, TypeName = typeof(ApplicationUpdateCommand).FullName},
|
new ScheduledTask{ Interval = 1*60, TypeName = typeof(ApplicationUpdateCommand).FullName},
|
||||||
new ScheduledTask{ Interval = 1*60, TypeName = typeof(TrimLogCommand).FullName},
|
new ScheduledTask{ Interval = 1*60, TypeName = typeof(TrimLogCommand).FullName},
|
||||||
new ScheduledTask{ Interval = 3*60, TypeName = typeof(UpdateSceneMappingCommand).FullName},
|
new ScheduledTask{ Interval = 3*60, TypeName = typeof(UpdateSceneMappingCommand).FullName},
|
||||||
|
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common.Composition;
|
using NzbDrone.Common.Composition;
|
||||||
|
using NzbDrone.Core.Messaging.Events;
|
||||||
using NzbDrone.Core.Metadata.Consumers.Fake;
|
using NzbDrone.Core.Metadata.Consumers.Fake;
|
||||||
using NzbDrone.Core.ThingiProvider;
|
using NzbDrone.Core.ThingiProvider;
|
||||||
|
|
||||||
|
@ -17,8 +18,8 @@ namespace NzbDrone.Core.Metadata
|
||||||
{
|
{
|
||||||
private readonly IMetadataRepository _providerRepository;
|
private readonly IMetadataRepository _providerRepository;
|
||||||
|
|
||||||
public MetadataFactory(IMetadataRepository providerRepository, IEnumerable<IMetadata> providers, IContainer container, Logger logger)
|
public MetadataFactory(IMetadataRepository providerRepository, IEnumerable<IMetadata> providers, IContainer container, IEventAggregator eventAggregator, Logger logger)
|
||||||
: base(providerRepository, providers, container, logger)
|
: base(providerRepository, providers, container, eventAggregator, logger)
|
||||||
{
|
{
|
||||||
_providerRepository = providerRepository;
|
_providerRepository = providerRepository;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common.Composition;
|
using NzbDrone.Common.Composition;
|
||||||
|
using NzbDrone.Core.Messaging.Events;
|
||||||
using NzbDrone.Core.ThingiProvider;
|
using NzbDrone.Core.ThingiProvider;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Notifications
|
namespace NzbDrone.Core.Notifications
|
||||||
|
@ -15,8 +16,8 @@ namespace NzbDrone.Core.Notifications
|
||||||
|
|
||||||
public class NotificationFactory : ProviderFactory<INotification, NotificationDefinition>, INotificationFactory
|
public class NotificationFactory : ProviderFactory<INotification, NotificationDefinition>, INotificationFactory
|
||||||
{
|
{
|
||||||
public NotificationFactory(INotificationRepository providerRepository, IEnumerable<INotification> providers, IContainer container, Logger logger)
|
public NotificationFactory(INotificationRepository providerRepository, IEnumerable<INotification> providers, IContainer container, IEventAggregator eventAggregator, Logger logger)
|
||||||
: base(providerRepository, providers, container, logger)
|
: base(providerRepository, providers, container, eventAggregator, logger)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -264,6 +264,13 @@
|
||||||
<Compile Include="Exceptions\DownstreamException.cs" />
|
<Compile Include="Exceptions\DownstreamException.cs" />
|
||||||
<Compile Include="Exceptions\NzbDroneClientException.cs" />
|
<Compile Include="Exceptions\NzbDroneClientException.cs" />
|
||||||
<Compile Include="Exceptions\StatusCodeToExceptions.cs" />
|
<Compile Include="Exceptions\StatusCodeToExceptions.cs" />
|
||||||
|
<Compile Include="HealthCheck\CheckHealthCommand.cs" />
|
||||||
|
<Compile Include="HealthCheck\Checks\DownloadClientCheck.cs" />
|
||||||
|
<Compile Include="HealthCheck\Checks\DroneFactoryCheck.cs" />
|
||||||
|
<Compile Include="HealthCheck\Checks\IndexerCheck.cs" />
|
||||||
|
<Compile Include="HealthCheck\Checks\UpdateCheck.cs" />
|
||||||
|
<Compile Include="HealthCheck\HealthCheck.cs" />
|
||||||
|
<Compile Include="HealthCheck\TriggerHealthCheckEvent.cs" />
|
||||||
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedEpisodes.cs" />
|
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedEpisodes.cs" />
|
||||||
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedHistoryItems.cs" />
|
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedHistoryItems.cs" />
|
||||||
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedMetadataFiles.cs" />
|
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedMetadataFiles.cs" />
|
||||||
|
@ -342,6 +349,8 @@
|
||||||
<Compile Include="Metadata\MetadataRepository.cs" />
|
<Compile Include="Metadata\MetadataRepository.cs" />
|
||||||
<Compile Include="Metadata\MetadataService.cs" />
|
<Compile Include="Metadata\MetadataService.cs" />
|
||||||
<Compile Include="Metadata\MetadataType.cs" />
|
<Compile Include="Metadata\MetadataType.cs" />
|
||||||
|
<Compile Include="HealthCheck\HealthCheckService.cs" />
|
||||||
|
<Compile Include="HealthCheck\IProvideHealthCheck.cs" />
|
||||||
<Compile Include="Notifications\NotificationFactory.cs" />
|
<Compile Include="Notifications\NotificationFactory.cs" />
|
||||||
<Compile Include="Notifications\NotificationService.cs" />
|
<Compile Include="Notifications\NotificationService.cs" />
|
||||||
<Compile Include="Notifications\DownloadMessage.cs" />
|
<Compile Include="Notifications\DownloadMessage.cs" />
|
||||||
|
@ -503,6 +512,7 @@
|
||||||
<Compile Include="Rest\JsonNetSerializer.cs" />
|
<Compile Include="Rest\JsonNetSerializer.cs" />
|
||||||
<Compile Include="RootFolders\RootFolderRepository.cs" />
|
<Compile Include="RootFolders\RootFolderRepository.cs" />
|
||||||
<Compile Include="ThingiProvider\ConfigContractNotFoundException.cs" />
|
<Compile Include="ThingiProvider\ConfigContractNotFoundException.cs" />
|
||||||
|
<Compile Include="ThingiProvider\Events\ProviderUpdatedEvent.cs" />
|
||||||
<Compile Include="ThingiProvider\IProvider.cs" />
|
<Compile Include="ThingiProvider\IProvider.cs" />
|
||||||
<Compile Include="Qualities\QualityProfileInUseException.cs" />
|
<Compile Include="Qualities\QualityProfileInUseException.cs" />
|
||||||
<Compile Include="Qualities\QualityDefinitionRepository.cs" />
|
<Compile Include="Qualities\QualityDefinitionRepository.cs" />
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using NzbDrone.Common.Messaging;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.ThingiProvider.Events
|
||||||
|
{
|
||||||
|
public class ProviderUpdatedEvent<TProvider> : IEvent
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ using NLog;
|
||||||
using NzbDrone.Common.Composition;
|
using NzbDrone.Common.Composition;
|
||||||
using NzbDrone.Core.Lifecycle;
|
using NzbDrone.Core.Lifecycle;
|
||||||
using NzbDrone.Core.Messaging.Events;
|
using NzbDrone.Core.Messaging.Events;
|
||||||
|
using NzbDrone.Core.ThingiProvider.Events;
|
||||||
|
|
||||||
namespace NzbDrone.Core.ThingiProvider
|
namespace NzbDrone.Core.ThingiProvider
|
||||||
{
|
{
|
||||||
|
@ -14,6 +15,7 @@ namespace NzbDrone.Core.ThingiProvider
|
||||||
{
|
{
|
||||||
private readonly IProviderRepository<TProviderDefinition> _providerRepository;
|
private readonly IProviderRepository<TProviderDefinition> _providerRepository;
|
||||||
private readonly IContainer _container;
|
private readonly IContainer _container;
|
||||||
|
private readonly IEventAggregator _eventAggregator;
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
|
|
||||||
protected readonly List<TProvider> _providers;
|
protected readonly List<TProvider> _providers;
|
||||||
|
@ -21,10 +23,12 @@ namespace NzbDrone.Core.ThingiProvider
|
||||||
protected ProviderFactory(IProviderRepository<TProviderDefinition> providerRepository,
|
protected ProviderFactory(IProviderRepository<TProviderDefinition> providerRepository,
|
||||||
IEnumerable<TProvider> providers,
|
IEnumerable<TProvider> providers,
|
||||||
IContainer container,
|
IContainer container,
|
||||||
|
IEventAggregator eventAggregator,
|
||||||
Logger logger)
|
Logger logger)
|
||||||
{
|
{
|
||||||
_providerRepository = providerRepository;
|
_providerRepository = providerRepository;
|
||||||
_container = container;
|
_container = container;
|
||||||
|
_eventAggregator = eventAggregator;
|
||||||
_providers = providers.ToList();
|
_providers = providers.ToList();
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
@ -62,6 +66,7 @@ namespace NzbDrone.Core.ThingiProvider
|
||||||
public virtual void Update(TProviderDefinition definition)
|
public virtual void Update(TProviderDefinition definition)
|
||||||
{
|
{
|
||||||
_providerRepository.Update(definition);
|
_providerRepository.Update(definition);
|
||||||
|
_eventAggregator.PublishEvent(new ProviderUpdatedEvent<TProvider>());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Delete(int id)
|
public void Delete(int id)
|
||||||
|
|
|
@ -48,7 +48,7 @@
|
||||||
|
|
||||||
.icon-nd-warning:before {
|
.icon-nd-warning:before {
|
||||||
.icon(@warning-sign);
|
.icon(@warning-sign);
|
||||||
color : #f89406;
|
color : @orange;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-nd-edit:before {
|
.icon-nd-edit:before {
|
||||||
|
@ -79,7 +79,7 @@
|
||||||
|
|
||||||
.icon-nd-form-warning:before {
|
.icon-nd-form-warning:before {
|
||||||
.icon(@warning-sign);
|
.icon(@warning-sign);
|
||||||
color: #f89406;
|
color: @orange;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-nd-form-danger:before {
|
.icon-nd-form-danger:before {
|
||||||
|
@ -175,4 +175,14 @@
|
||||||
|
|
||||||
.icon-nd-restart:before {
|
.icon-nd-restart:before {
|
||||||
.icon(@repeat);
|
.icon(@repeat);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-nd-health-warning:before {
|
||||||
|
.icon(@exclamation-sign);
|
||||||
|
color : @orange
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-nd-health-error:before {
|
||||||
|
.icon(@exclamation-sign);
|
||||||
|
color : @errorText
|
||||||
}
|
}
|
|
@ -22,6 +22,8 @@
|
||||||
li {
|
li {
|
||||||
list-style-type : none;
|
list-style-type : none;
|
||||||
display : inline-block;
|
display : inline-block;
|
||||||
|
position : relative;
|
||||||
|
|
||||||
a {
|
a {
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
|
@ -38,21 +40,20 @@
|
||||||
font-weight : 100;
|
font-weight : 100;
|
||||||
}
|
}
|
||||||
span.label.pull-right {
|
span.label.pull-right {
|
||||||
position : relative;
|
position : absolute;
|
||||||
top : 24px;
|
top : 28px;
|
||||||
right : 14px;
|
right : 18px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.backdrop #nav-region {
|
.backdrop {
|
||||||
background-color : #000000;
|
#nav-region {
|
||||||
.opacity(0.85);
|
background-color : #000000;
|
||||||
}
|
.opacity(0.85);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#nav-region li a:hover, #in-sub-nav li a.active {
|
|
||||||
background-color : #555555;
|
|
||||||
text-decoration : none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#nav-region {
|
#nav-region {
|
||||||
|
@ -62,6 +63,19 @@
|
||||||
.span12 {
|
.span12 {
|
||||||
margin-left : 0px;
|
margin-left : 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
a {
|
||||||
|
&:hover {
|
||||||
|
background-color : #555555;
|
||||||
|
text-decoration : none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.search {
|
.search {
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
'use strict';
|
||||||
|
define(
|
||||||
|
[
|
||||||
|
'backbone',
|
||||||
|
'Health/HealthModel',
|
||||||
|
'Mixins/backbone.signalr.mixin'
|
||||||
|
], function (Backbone, HealthModel) {
|
||||||
|
var Collection = Backbone.Collection.extend({
|
||||||
|
url : window.NzbDrone.ApiRoot + '/health',
|
||||||
|
model: HealthModel
|
||||||
|
});
|
||||||
|
|
||||||
|
var collection = new Collection().bindSignalR();
|
||||||
|
collection.fetch();
|
||||||
|
return collection;
|
||||||
|
});
|
|
@ -0,0 +1,9 @@
|
||||||
|
'use strict';
|
||||||
|
define(
|
||||||
|
[
|
||||||
|
'backbone'
|
||||||
|
], function (Backbone) {
|
||||||
|
return Backbone.Model.extend({
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,39 @@
|
||||||
|
'use strict';
|
||||||
|
define(
|
||||||
|
[
|
||||||
|
'underscore',
|
||||||
|
'marionette',
|
||||||
|
'Health/HealthCollection'
|
||||||
|
], function (_, Marionette, HealthCollection) {
|
||||||
|
return Marionette.ItemView.extend({
|
||||||
|
initialize: function () {
|
||||||
|
this.listenTo(HealthCollection, 'sync', this._healthSync);
|
||||||
|
HealthCollection.fetch();
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function () {
|
||||||
|
this.$el.empty();
|
||||||
|
|
||||||
|
if (HealthCollection.length === 0) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
var count = HealthCollection.length;
|
||||||
|
var label = 'label-warning';
|
||||||
|
var errors = HealthCollection.some(function (model) {
|
||||||
|
return model.get('type') === 'error';
|
||||||
|
});
|
||||||
|
|
||||||
|
if (errors) {
|
||||||
|
label = 'label-important';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$el.html('<span class="label pull-right {0}">{1}</span>'.format(label, count));
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
_healthSync: function () {
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
|
@ -47,6 +47,7 @@
|
||||||
<i class="icon-laptop"></i>
|
<i class="icon-laptop"></i>
|
||||||
<br>
|
<br>
|
||||||
System
|
System
|
||||||
|
<span id="x-health"></span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
|
@ -3,10 +3,15 @@ define(
|
||||||
[
|
[
|
||||||
'marionette',
|
'marionette',
|
||||||
'jquery',
|
'jquery',
|
||||||
|
'Health/HealthView',
|
||||||
'Navbar/Search'
|
'Navbar/Search'
|
||||||
], function (Marionette, $) {
|
], function (Marionette, $, HealthView) {
|
||||||
return Marionette.ItemView.extend({
|
return Marionette.Layout.extend({
|
||||||
template: 'Navbar/NavbarTemplate',
|
template: 'Navbar/NavbarLayoutTemplate',
|
||||||
|
|
||||||
|
regions: {
|
||||||
|
health: '#x-health'
|
||||||
|
},
|
||||||
|
|
||||||
ui: {
|
ui: {
|
||||||
search: '.x-series-search'
|
search: '.x-series-search'
|
||||||
|
@ -18,6 +23,7 @@ define(
|
||||||
|
|
||||||
onRender: function () {
|
onRender: function () {
|
||||||
this.ui.search.bindSearch();
|
this.ui.search.bindSearch();
|
||||||
|
this.health.show(new HealthView());
|
||||||
},
|
},
|
||||||
|
|
||||||
onClick: function (event) {
|
onClick: function (event) {
|
||||||
|
@ -30,9 +36,9 @@ define(
|
||||||
var href = event.target.getAttribute('href');
|
var href = event.target.getAttribute('href');
|
||||||
|
|
||||||
//if couldn't find it look up'
|
//if couldn't find it look up'
|
||||||
if (!href && target.parent('a') && target.parent('a')[0]) {
|
if (!href && target.closest('a') && target.closest('a')[0]) {
|
||||||
|
|
||||||
var linkElement = target.parent('a')[0];
|
var linkElement = target.closest('a')[0];
|
||||||
|
|
||||||
href = linkElement.getAttribute('href');
|
href = linkElement.getAttribute('href');
|
||||||
this.setActive(linkElement);
|
this.setActive(linkElement);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
define([
|
define([
|
||||||
'vent',
|
'vent',
|
||||||
'marionette',
|
'marionette',
|
||||||
'backgrid',
|
'backgrid',
|
||||||
'System/Info/DiskSpace/DiskSpaceCollection',
|
'System/Info/DiskSpace/DiskSpaceCollection',
|
||||||
'Shared/LoadingView',
|
'Shared/LoadingView',
|
||||||
|
@ -14,6 +14,7 @@ define([
|
||||||
regions: {
|
regions: {
|
||||||
grid: '#x-grid'
|
grid: '#x-grid'
|
||||||
},
|
},
|
||||||
|
|
||||||
columns:
|
columns:
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
|
@ -37,6 +38,7 @@ define([
|
||||||
this.collection = new DiskSpaceCollection();
|
this.collection = new DiskSpaceCollection();
|
||||||
this.listenTo(this.collection, 'sync', this._showTable);
|
this.listenTo(this.collection, 'sync', this._showTable);
|
||||||
},
|
},
|
||||||
|
|
||||||
onRender : function() {
|
onRender : function() {
|
||||||
this.grid.show(new LoadingView());
|
this.grid.show(new LoadingView());
|
||||||
},
|
},
|
||||||
|
@ -44,6 +46,7 @@ define([
|
||||||
onShow: function() {
|
onShow: function() {
|
||||||
this.collection.fetch();
|
this.collection.fetch();
|
||||||
},
|
},
|
||||||
|
|
||||||
_showTable: function() {
|
_showTable: function() {
|
||||||
this.grid.show(new Backgrid.Grid({
|
this.grid.show(new Backgrid.Grid({
|
||||||
row: Backgrid.Row,
|
row: Backgrid.Row,
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
'use strict';
|
||||||
|
define(
|
||||||
|
[
|
||||||
|
'Cells/NzbDroneCell'
|
||||||
|
], function (NzbDroneCell) {
|
||||||
|
return NzbDroneCell.extend({
|
||||||
|
|
||||||
|
className: 'log-level-cell',
|
||||||
|
|
||||||
|
render: function () {
|
||||||
|
|
||||||
|
var level = this._getValue();
|
||||||
|
this.$el.html('<i class="icon-nd-health-{0}" title="{1}"/>'.format(this._getValue().toLowerCase(), level));
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,55 @@
|
||||||
|
'use strict';
|
||||||
|
define(
|
||||||
|
[
|
||||||
|
'marionette',
|
||||||
|
'backgrid',
|
||||||
|
'Health/HealthCollection',
|
||||||
|
'System/Info/Health/HealthCell',
|
||||||
|
'System/Info/Health/HealthOkView'
|
||||||
|
], function (Marionette, Backgrid, HealthCollection, HealthCell, HealthOkView) {
|
||||||
|
return Marionette.Layout.extend({
|
||||||
|
template: 'System/Info/Health/HealthLayoutTemplate',
|
||||||
|
|
||||||
|
regions: {
|
||||||
|
grid: '#x-health-grid'
|
||||||
|
},
|
||||||
|
|
||||||
|
columns:
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: 'type',
|
||||||
|
label: '',
|
||||||
|
cell: HealthCell
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'message',
|
||||||
|
label: 'Message',
|
||||||
|
cell: 'string'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
initialize: function () {
|
||||||
|
this.listenTo(HealthCollection, 'sync', this.render);
|
||||||
|
HealthCollection.fetch();
|
||||||
|
},
|
||||||
|
|
||||||
|
onRender : function() {
|
||||||
|
if (HealthCollection.length === 0) {
|
||||||
|
this.grid.show(new HealthOkView());
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
this._showTable();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_showTable: function() {
|
||||||
|
this.grid.show(new Backgrid.Grid({
|
||||||
|
row: Backgrid.Row,
|
||||||
|
columns: this.columns,
|
||||||
|
collection: HealthCollection,
|
||||||
|
className:'table table-hover'
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,6 @@
|
||||||
|
<fieldset class="x-health">
|
||||||
|
<legend>Health</legend>
|
||||||
|
|
||||||
|
<div id="x-health-grid"/>
|
||||||
|
</fieldset>
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
'use strict';
|
||||||
|
define(
|
||||||
|
[
|
||||||
|
'marionette'
|
||||||
|
], function (Marionette) {
|
||||||
|
return Marionette.ItemView.extend({
|
||||||
|
template: 'System/Info/Health/HealthOkViewTemplate'
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,2 @@
|
||||||
|
No issues with your configuration
|
||||||
|
|
|
@ -4,22 +4,26 @@ define(
|
||||||
'backbone',
|
'backbone',
|
||||||
'marionette',
|
'marionette',
|
||||||
'System/Info/About/AboutView',
|
'System/Info/About/AboutView',
|
||||||
'System/Info/DiskSpace/DiskSpaceLayout'
|
'System/Info/DiskSpace/DiskSpaceLayout',
|
||||||
|
'System/Info/Health/HealthLayout'
|
||||||
], function (Backbone,
|
], function (Backbone,
|
||||||
Marionette,
|
Marionette,
|
||||||
AboutView,
|
AboutView,
|
||||||
DiskSpaceLayout) {
|
DiskSpaceLayout,
|
||||||
|
HealthLayout) {
|
||||||
return Marionette.Layout.extend({
|
return Marionette.Layout.extend({
|
||||||
template: 'System/Info/SystemInfoLayoutTemplate',
|
template: 'System/Info/SystemInfoLayoutTemplate',
|
||||||
|
|
||||||
regions: {
|
regions: {
|
||||||
about : '#about',
|
about : '#about',
|
||||||
diskSpace: '#diskspace'
|
diskSpace: '#diskspace',
|
||||||
|
health : '#health'
|
||||||
},
|
},
|
||||||
|
|
||||||
onRender: function () {
|
onRender: function () {
|
||||||
this.about.show(new AboutView());
|
this.about.show(new AboutView());
|
||||||
this.diskSpace.show(new DiskSpaceLayout());
|
this.diskSpace.show(new DiskSpaceLayout());
|
||||||
|
this.health.show(new HealthLayout());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
<div class="span12" id="health"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
<div class="span12" id="about"></div>
|
<div class="span12" id="about"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -33,9 +33,9 @@ define(
|
||||||
|
|
||||||
var href = event.target.getAttribute('href');
|
var href = event.target.getAttribute('href');
|
||||||
|
|
||||||
if (!href && $target.parent('a') && $target.parent('a')[0]) {
|
if (!href && $target.closest('a') && $target.closest('a')[0]) {
|
||||||
|
|
||||||
var linkElement = $target.parent('a')[0];
|
var linkElement = $target.closest('a')[0];
|
||||||
|
|
||||||
href = linkElement.getAttribute('href');
|
href = linkElement.getAttribute('href');
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue