Lots of fixes. Becoming more stable now. #865

This commit is contained in:
tidusjar 2017-06-21 17:02:29 +01:00
parent 1c6ddc74cb
commit dcf97a1008
31 changed files with 1021 additions and 381 deletions

View file

@ -6,13 +6,13 @@ namespace Ombi.Api.Discord
{ {
public class DiscordApi : IDiscordApi public class DiscordApi : IDiscordApi
{ {
public DiscordApi() public DiscordApi(IApi api)
{ {
Api = new Api(); Api = api;
} }
private string Endpoint => "https://discordapp.com/api/"; //webhooks/270828242636636161/lLysOMhJ96AFO1kvev0bSqP-WCZxKUh1UwfubhIcLkpS0DtM3cg4Pgeraw3waoTXbZii private string Endpoint => "https://discordapp.com/api/"; //webhooks/270828242636636161/lLysOMhJ96AFO1kvev0bSqP-WCZxKUh1UwfubhIcLkpS0DtM3cg4Pgeraw3waoTXbZii
private Api Api { get; } private IApi Api { get; }
public async Task SendMessage(DiscordWebhookBody body, string webhookId, string webhookToken) public async Task SendMessage(DiscordWebhookBody body, string webhookId, string webhookToken)
{ {

View file

@ -9,12 +9,12 @@ namespace Ombi.Api.Emby
{ {
public class EmbyApi : IEmbyApi public class EmbyApi : IEmbyApi
{ {
public EmbyApi() public EmbyApi(IApi api)
{ {
Api = new Api(); Api = api;
} }
private Api Api { get; } private IApi Api { get; }
/// <summary> /// <summary>
/// Returns all users from the Emby Instance /// Returns all users from the Emby Instance

View file

@ -1,7 +1,5 @@
using System; using System.Net.Http;
using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json;
using Ombi.Api.Plex.Models; using Ombi.Api.Plex.Models;
using Ombi.Api.Plex.Models.Server; using Ombi.Api.Plex.Models.Server;
using Ombi.Api.Plex.Models.Status; using Ombi.Api.Plex.Models.Status;
@ -10,12 +8,12 @@ namespace Ombi.Api.Plex
{ {
public class PlexApi : IPlexApi public class PlexApi : IPlexApi
{ {
public PlexApi() public PlexApi(IApi api)
{ {
Api = new Api(); Api = api;
} }
private Api Api { get; } private IApi Api { get; }
private const string SignInUri = "https://plex.tv/users/sign_in.json"; private const string SignInUri = "https://plex.tv/users/sign_in.json";
private const string FriendsUri = "https://plex.tv/pms/friends/all"; private const string FriendsUri = "https://plex.tv/pms/friends/all";

View file

@ -12,18 +12,18 @@ namespace Ombi.Api.Radarr
{ {
public class RadarrApi : IRadarrApi public class RadarrApi : IRadarrApi
{ {
public RadarrApi(ILogger<RadarrApi> logger) public RadarrApi(ILogger<RadarrApi> logger, IApi api)
{ {
Api = new Api(); Api = api;
Logger = logger; Logger = logger;
} }
private Api Api { get; } private IApi Api { get; }
private ILogger<RadarrApi> Logger { get; } private ILogger<RadarrApi> Logger { get; }
public async Task<List<RadarrProfile>> GetProfiles(string apiKey, string baseUrl) public async Task<List<RadarrProfile>> GetProfiles(string apiKey, string baseUrl)
{ {
var request = new Request(baseUrl, "/api/profile", HttpMethod.Get); var request = new Request("/api/profile", baseUrl, HttpMethod.Get);
AddHeaders(request, apiKey); AddHeaders(request, apiKey);
return await Api.Request<List<RadarrProfile>>(request); return await Api.Request<List<RadarrProfile>>(request);
@ -31,7 +31,7 @@ namespace Ombi.Api.Radarr
public async Task<List<RadarrRootFolder>> GetRootFolders(string apiKey, string baseUrl) public async Task<List<RadarrRootFolder>> GetRootFolders(string apiKey, string baseUrl)
{ {
var request = new Request(baseUrl, "/api/rootfolder", HttpMethod.Get); var request = new Request("/api/rootfolder", baseUrl, HttpMethod.Get);
AddHeaders(request, apiKey); AddHeaders(request, apiKey);
return await Api.Request<List<RadarrRootFolder>>(request); return await Api.Request<List<RadarrRootFolder>>(request);
@ -39,7 +39,7 @@ namespace Ombi.Api.Radarr
public async Task<SystemStatus> SystemStatus(string apiKey, string baseUrl) public async Task<SystemStatus> SystemStatus(string apiKey, string baseUrl)
{ {
var request = new Request(baseUrl, "/api/system/status", HttpMethod.Get); var request = new Request("/api/system/status", baseUrl, HttpMethod.Get);
AddHeaders(request, apiKey); AddHeaders(request, apiKey);
return await Api.Request<SystemStatus>(request); return await Api.Request<SystemStatus>(request);
@ -47,7 +47,7 @@ namespace Ombi.Api.Radarr
public async Task<List<MovieResponse>> GetMovies(string apiKey, string baseUrl) public async Task<List<MovieResponse>> GetMovies(string apiKey, string baseUrl)
{ {
var request = new Request(baseUrl, "/api/movie", HttpMethod.Get); var request = new Request("/api/movie", baseUrl, HttpMethod.Get);
AddHeaders(request, apiKey); AddHeaders(request, apiKey);
return await Api.Request<List<MovieResponse>>(request); return await Api.Request<List<MovieResponse>>(request);
@ -55,7 +55,7 @@ namespace Ombi.Api.Radarr
public async Task<RadarrAddMovieResponse> AddMovie(int tmdbId, string title, int year, int qualityId, string rootPath, string apiKey, string baseUrl, bool searchNow = false) public async Task<RadarrAddMovieResponse> AddMovie(int tmdbId, string title, int year, int qualityId, string rootPath, string apiKey, string baseUrl, bool searchNow = false)
{ {
var request = new Request(baseUrl, "/api/movie", HttpMethod.Post); var request = new Request("/api/movie", baseUrl, HttpMethod.Post);
var options = new RadarrAddMovieResponse var options = new RadarrAddMovieResponse
{ {

View file

@ -9,16 +9,16 @@ namespace Ombi.Api.Sonarr
public class SonarrApi : ISonarrApi public class SonarrApi : ISonarrApi
{ {
public SonarrApi() public SonarrApi(IApi api)
{ {
Api = new Api(); Api = api;
} }
private Api Api { get; } private IApi Api { get; }
public async Task<IEnumerable<SonarrProfile>> GetProfiles(string apiKey, string baseUrl) public async Task<IEnumerable<SonarrProfile>> GetProfiles(string apiKey, string baseUrl)
{ {
var request = new Request(baseUrl, "/api/profile", HttpMethod.Get); var request = new Request("/api/profile", baseUrl, HttpMethod.Get);
request.AddHeader("X-Api-Key", apiKey); request.AddHeader("X-Api-Key", apiKey);
@ -27,7 +27,7 @@ namespace Ombi.Api.Sonarr
public async Task<IEnumerable<SonarrRootFolder>> GetRootFolders(string apiKey, string baseUrl) public async Task<IEnumerable<SonarrRootFolder>> GetRootFolders(string apiKey, string baseUrl)
{ {
var request = new Request(baseUrl, "/api/rootfolder", HttpMethod.Get); var request = new Request("/api/rootfolder", baseUrl, HttpMethod.Get);
request.AddHeader("X-Api-Key", apiKey); request.AddHeader("X-Api-Key", apiKey);

View file

@ -11,14 +11,14 @@ namespace Ombi.Api.TvMaze
{ {
public class TvMazeApi : ITvMazeApi public class TvMazeApi : ITvMazeApi
{ {
public TvMazeApi(ILogger<TvMazeApi> logger) public TvMazeApi(ILogger<TvMazeApi> logger, IApi api)
{ {
Api = new Ombi.Api.Api(); Api = api;
Logger = logger; Logger = logger;
//Mapper = mapper; //Mapper = mapper;
} }
private string Uri = "http://api.tvmaze.com"; private string Uri = "http://api.tvmaze.com";
private Api Api { get; } private IApi Api { get; }
private ILogger<TvMazeApi> Logger { get; } private ILogger<TvMazeApi> Logger { get; }
public async Task<List<TvMazeSearch>> Search(string searchTerm) public async Task<List<TvMazeSearch>> Search(string searchTerm)

View file

@ -4,11 +4,20 @@ using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Xml.Serialization; using System.Xml.Serialization;
using Newtonsoft.Json; using Newtonsoft.Json;
using Microsoft.Extensions.Logging;
using Ombi.Helpers;
namespace Ombi.Api namespace Ombi.Api
{ {
public class Api public class Api : IApi
{ {
public Api(ILogger<Api> log)
{
Logger = log;
}
private ILogger<Api> Logger { get; }
private static readonly JsonSerializerSettings Settings = new JsonSerializerSettings private static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
{ {
NullValueHandling = NullValueHandling.Ignore NullValueHandling = NullValueHandling.Ignore
@ -36,12 +45,10 @@ namespace Ombi.Api
{ {
if (!httpResponseMessage.IsSuccessStatusCode) if (!httpResponseMessage.IsSuccessStatusCode)
{ {
// Logging Logger.LogError(LoggingEvents.ApiException, $"StatusCode: {httpResponseMessage.StatusCode}, Reason: {httpResponseMessage.ReasonPhrase}");
} }
// do something with the response // do something with the response
var data = httpResponseMessage.Content; var data = httpResponseMessage.Content;
var receivedString = await data.ReadAsStringAsync(); var receivedString = await data.ReadAsStringAsync();
if (request.ContentType == ContentType.Json) if (request.ContentType == ContentType.Json)
{ {
@ -82,7 +89,7 @@ namespace Ombi.Api
{ {
if (!httpResponseMessage.IsSuccessStatusCode) if (!httpResponseMessage.IsSuccessStatusCode)
{ {
// Logging Logger.LogError(LoggingEvents.ApiException, $"StatusCode: {httpResponseMessage.StatusCode}, Reason: {httpResponseMessage.ReasonPhrase}");
} }
// do something with the response // do something with the response
var data = httpResponseMessage.Content; var data = httpResponseMessage.Content;
@ -116,7 +123,7 @@ namespace Ombi.Api
{ {
if (!httpResponseMessage.IsSuccessStatusCode) if (!httpResponseMessage.IsSuccessStatusCode)
{ {
// Logging Logger.LogError(LoggingEvents.ApiException, $"StatusCode: {httpResponseMessage.StatusCode}, Reason: {httpResponseMessage.ReasonPhrase}");
} }
} }
} }

11
src/Ombi.Api/IApi.cs Normal file
View file

@ -0,0 +1,11 @@
using System.Threading.Tasks;
namespace Ombi.Api
{
public interface IApi
{
Task Request(Request request);
Task<T> Request<T>(Request request);
Task<string> RequestContent(Request request);
}
}

View file

@ -6,8 +6,13 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="1.1.2" />
<PackageReference Include="Newtonsoft.Json" Version="10.0.2" /> <PackageReference Include="Newtonsoft.Json" Version="10.0.2" />
<PackageReference Include="System.Xml.XmlSerializer" Version="4.3.0" /> <PackageReference Include="System.Xml.XmlSerializer" Version="4.3.0" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Ombi.Helpers\Ombi.Helpers.csproj" />
</ItemGroup>
</Project> </Project>

View file

@ -29,6 +29,7 @@ using Ombi.Store.Repository;
using Ombi.Core.Rules; using Ombi.Core.Rules;
using Ombi.Notifications.Agents; using Ombi.Notifications.Agents;
using Ombi.Schedule.Jobs.Radarr; using Ombi.Schedule.Jobs.Radarr;
using Ombi.Api;
namespace Ombi.DependencyInjection namespace Ombi.DependencyInjection
{ {
@ -52,11 +53,12 @@ namespace Ombi.DependencyInjection
services.AddTransient<ITvRequestEngine, TvRequestEngine>(); services.AddTransient<ITvRequestEngine, TvRequestEngine>();
services.AddTransient<ITvSearchEngine, TvSearchEngine>(); services.AddTransient<ITvSearchEngine, TvSearchEngine>();
services.AddSingleton<IRuleEvaluator, RuleEvaluator>(); services.AddSingleton<IRuleEvaluator, RuleEvaluator>();
services.AddSingleton<IMovieSender, MovieSender>(); services.AddTransient<IMovieSender, MovieSender>();
} }
public static void RegisterApi(this IServiceCollection services) public static void RegisterApi(this IServiceCollection services)
{ {
services.AddTransient<IApi, Api.Api>();
services.AddTransient<IMovieDbApi, Api.TheMovieDb.TheMovieDbApi>(); services.AddTransient<IMovieDbApi, Api.TheMovieDb.TheMovieDbApi>();
services.AddTransient<IPlexApi, PlexApi>(); services.AddTransient<IPlexApi, PlexApi>();
services.AddTransient<IEmbyApi, EmbyApi>(); services.AddTransient<IEmbyApi, EmbyApi>();
@ -78,7 +80,7 @@ namespace Ombi.DependencyInjection
services.AddTransient<ISettingsResolver, SettingsResolver>(); services.AddTransient<ISettingsResolver, SettingsResolver>();
services.AddTransient<IPlexContentRepository, PlexContentRepository>(); services.AddTransient<IPlexContentRepository, PlexContentRepository>();
services.AddTransient<INotificationTemplatesRepository, NotificationTemplatesRepository>(); services.AddTransient<INotificationTemplatesRepository, NotificationTemplatesRepository>();
services.AddTransient(typeof(ISettingsService<>), typeof(SettingsServiceV2<>)); services.AddTransient(typeof(ISettingsService<>), typeof(SettingsService<>));
} }
public static void RegisterServices(this IServiceCollection services) public static void RegisterServices(this IServiceCollection services)
{ {

View file

@ -8,7 +8,6 @@ namespace Ombi.Settings.Settings.Models.External
public string ApiKey { get; set; } public string ApiKey { get; set; }
public string DefaultQualityProfile { get; set; } public string DefaultQualityProfile { get; set; }
public string DefaultRootPath { get; set; } public string DefaultRootPath { get; set; }
public string FullRootPath { get; set; }
public bool AddOnly { get; set; } public bool AddOnly { get; set; }
} }
} }

View file

@ -13,7 +13,5 @@
/// The root path. /// The root path.
/// </value> /// </value>
public string RootPath { get; set; } public string RootPath { get; set; }
public string FullRootPath { get; set; }
} }
} }

View file

@ -4,21 +4,24 @@ using Ombi.Core.Settings;
using Ombi.Helpers; using Ombi.Helpers;
using Ombi.Store.Entities; using Ombi.Store.Entities;
using Ombi.Store.Repository; using Ombi.Store.Repository;
using Microsoft.AspNetCore.DataProtection;
namespace Ombi.Settings.Settings namespace Ombi.Settings.Settings
{ {
public class SettingsServiceV2<T> : ISettingsService<T> public class SettingsService<T> : ISettingsService<T>
where T : Ombi.Settings.Settings.Models.Settings, new() where T : Ombi.Settings.Settings.Models.Settings, new()
{ {
public SettingsServiceV2(ISettingsRepository repo) public SettingsService(ISettingsRepository repo, IDataProtectionProvider provider)
{ {
Repo = repo; Repo = repo;
EntityName = typeof(T).Name; EntityName = typeof(T).Name;
_protector = provider.CreateProtector(GetType().FullName);
} }
private ISettingsRepository Repo { get; } private ISettingsRepository Repo { get; }
private string EntityName { get; } private string EntityName { get; }
private readonly IDataProtector _protector;
public T GetSettings() public T GetSettings()
{ {
@ -119,12 +122,12 @@ namespace Ombi.Settings.Settings
private string EncryptSettings(GlobalSettings settings) private string EncryptSettings(GlobalSettings settings)
{ {
return StringCipher.EncryptString(settings.Content, $"Ombiv3SettingsEncryptionPassword"); return _protector.Protect(settings.Content);
} }
private string DecryptSettings(GlobalSettings settings) private string DecryptSettings(GlobalSettings settings)
{ {
return StringCipher.DecryptString(settings.Content, $"Ombiv3SettingsEncryptionPassword"); return _protector.Unprotect(settings.Content);
} }
} }
} }

View file

@ -10,16 +10,16 @@ namespace Ombi.Api.TheMovieDb
{ {
public class TheMovieDbApi : IMovieDbApi public class TheMovieDbApi : IMovieDbApi
{ {
public TheMovieDbApi(IMapper mapper) public TheMovieDbApi(IMapper mapper, IApi api)
{ {
Api = new Api(); Api = api;
Mapper = mapper; Mapper = mapper;
} }
private IMapper Mapper { get; } private IMapper Mapper { get; }
private readonly string ApiToken = "b8eabaf5608b88d0298aa189dd90bf00"; private readonly string ApiToken = "b8eabaf5608b88d0298aa189dd90bf00";
private static readonly string BaseUri ="http://api.themoviedb.org/3/"; private static readonly string BaseUri ="http://api.themoviedb.org/3/";
private Api Api { get; } private IApi Api { get; }
public async Task<MovieResponseDto> GetMovieInformation(int movieId) public async Task<MovieResponseDto> GetMovieInformation(int movieId)
{ {

View file

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Ombi.Updater
{
public class Installer
{
public void Start(StartupOptions options)
{
// Kill Ombi Process
var p = new ProcessProvider();
p.Kill(options.OmbiProcessId);
}
}
}

View file

@ -0,0 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp1.1</TargetFramework>
</PropertyGroup>
</Project>

View file

@ -0,0 +1,203 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
namespace Ombi.Updater
{
public class ProcessProvider
{
public const string OmbiProcessName = "Ombi";
public int GetCurrentProcessId()
{
return Process.GetCurrentProcess().Id;
}
public ProcessInfo GetCurrentProcess()
{
return ConvertToProcessInfo(Process.GetCurrentProcess());
}
public bool Exists(int processId)
{
return GetProcessById(processId) != null;
}
public bool Exists(string processName)
{
return GetProcessesByName(processName).Any();
}
public ProcessInfo GetProcessById(int id)
{
Console.WriteLine("Finding process with Id:{0}", id);
var processInfo = ConvertToProcessInfo(Process.GetProcesses().FirstOrDefault(p => p.Id == id));
if (processInfo == null)
{
Console.WriteLine("Unable to find process with ID {0}", id);
}
else
{
Console.WriteLine("Found process {0}", processInfo.ToString());
}
return processInfo;
}
public List<ProcessInfo> FindProcessByName(string name)
{
return GetProcessesByName(name).Select(ConvertToProcessInfo).Where(c => c != null).ToList();
}
public void WaitForExit(Process process)
{
Console.WriteLine("Waiting for process {0} to exit.", process.ProcessName);
process.WaitForExit();
}
public void SetPriority(int processId, ProcessPriorityClass priority)
{
var process = Process.GetProcessById(processId);
Console.WriteLine("Updating [{0}] process priority from {1} to {2}",
process.ProcessName,
process.PriorityClass,
priority);
process.PriorityClass = priority;
}
public void Kill(int processId)
{
var process = Process.GetProcesses().FirstOrDefault(p => p.Id == processId);
if (process == null)
{
Console.WriteLine("Cannot find process with id: {0}", processId);
return;
}
process.Refresh();
if (process.Id != Process.GetCurrentProcess().Id && process.HasExited)
{
Console.WriteLine("Process has already exited");
return;
}
Console.WriteLine("[{0}]: Killing process", process.Id);
process.Kill();
Console.WriteLine("[{0}]: Waiting for exit", process.Id);
process.WaitForExit();
Console.WriteLine("[{0}]: Process terminated successfully", process.Id);
}
public void KillAll(string processName)
{
var processes = GetProcessesByName(processName);
Console.WriteLine("Found {0} processes to kill", processes.Count);
foreach (var processInfo in processes)
{
if (processInfo.Id == Process.GetCurrentProcess().Id)
{
Console.WriteLine("Tried killing own process, skipping: {0} [{1}]", processInfo.Id, processInfo.ProcessName);
continue;
}
Console.WriteLine("Killing process: {0} [{1}]", processInfo.Id, processInfo.ProcessName);
Kill(processInfo.Id);
}
}
private ProcessInfo ConvertToProcessInfo(Process process)
{
if (process == null) return null;
process.Refresh();
ProcessInfo processInfo = null;
try
{
if (process.Id <= 0) return null;
processInfo = new ProcessInfo
{
Id = process.Id,
Name = process.ProcessName,
StartPath = GetExeFileName(process)
};
if (process.Id != Process.GetCurrentProcess().Id && process.HasExited)
{
processInfo = null;
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
return processInfo;
}
private static string GetExeFileName(Process process)
{
return process.MainModule.FileName;
}
private List<System.Diagnostics.Process> GetProcessesByName(string name)
{
//TODO: move this to an OS specific class
var monoProcesses = Process.GetProcessesByName("mono")
.Union(Process.GetProcessesByName("mono-sgen"))
.Where(process =>
process.Modules.Cast<ProcessModule>()
.Any(module =>
module.ModuleName.ToLower() == name.ToLower() + ".exe"));
var processes = Process.GetProcessesByName(name)
.Union(monoProcesses).ToList();
Console.WriteLine("Found {0} processes with the name: {1}", processes.Count, name);
try
{
foreach (var process in processes)
{
Console.WriteLine(" - [{0}] {1}", process.Id, process.ProcessName);
}
}
catch
{
// Don't crash on gettings some log data.
}
return processes;
}
}
public class ProcessInfo
{
public int Id { get; set; }
public string Name { get; set; }
public string StartPath { get; set; }
public override string ToString()
{
return string.Format("{0}:{1} [{2}]", Id, Name ?? "Unknown", StartPath ?? "Unknown");
}
}
}

View file

@ -0,0 +1,44 @@
using System;
using System.Diagnostics;
using System.Linq;
namespace Ombi.Updater
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("=======================================");
Console.WriteLine(" Starting the Ombi Updater");
Console.WriteLine("=======================================");
var options = CheckArgs(args);
}
private static StartupOptions CheckArgs(string[] args)
{
if(args.Length <= 0)
{
Console.WriteLine("No Args Provided... Exiting");
Environment.Exit(1);
}
var p = new ProcessProvider();
var ombiProc = p.FindProcessByName("Ombi").FirstOrDefault().Id;
return new StartupOptions
{
ApplicationPath = args[0],
OmbiProcessId = ombiProc
};
}
}
public class StartupOptions
{
public string ApplicationPath { get; set; }
public int OmbiProcessId { get; set; }
}
}

View file

@ -61,6 +61,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Api.Radarr", "Ombi.Api
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Api.Discord", "Ombi.Api.Discord\Ombi.Api.Discord.csproj", "{5AF2B6D2-5CC6-49FE-928A-BA27AF52B194}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Api.Discord", "Ombi.Api.Discord\Ombi.Api.Discord.csproj", "{5AF2B6D2-5CC6-49FE-928A-BA27AF52B194}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Updater", "Ombi.Updater\Ombi.Updater.csproj", "{6294A82D-4915-4FC3-B301-8F985716F34C}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Update", "Update", "{D11FE57E-1E57-491D-A1D4-01AEF4BE5CB6}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -147,6 +151,10 @@ Global
{5AF2B6D2-5CC6-49FE-928A-BA27AF52B194}.Debug|Any CPU.Build.0 = Debug|Any CPU {5AF2B6D2-5CC6-49FE-928A-BA27AF52B194}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5AF2B6D2-5CC6-49FE-928A-BA27AF52B194}.Release|Any CPU.ActiveCfg = Release|Any CPU {5AF2B6D2-5CC6-49FE-928A-BA27AF52B194}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5AF2B6D2-5CC6-49FE-928A-BA27AF52B194}.Release|Any CPU.Build.0 = Release|Any CPU {5AF2B6D2-5CC6-49FE-928A-BA27AF52B194}.Release|Any CPU.Build.0 = Release|Any CPU
{6294A82D-4915-4FC3-B301-8F985716F34C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6294A82D-4915-4FC3-B301-8F985716F34C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6294A82D-4915-4FC3-B301-8F985716F34C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6294A82D-4915-4FC3-B301-8F985716F34C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -166,5 +174,6 @@ Global
{FC6A8F7C-9722-4AE4-960D-277ACB0E81CB} = {6F42AB98-9196-44C4-B888-D5E409F415A1} {FC6A8F7C-9722-4AE4-960D-277ACB0E81CB} = {6F42AB98-9196-44C4-B888-D5E409F415A1}
{94D04C1F-E35A-499C-B0A0-9FADEBDF8336} = {9293CA11-360A-4C20-A674-B9E794431BF5} {94D04C1F-E35A-499C-B0A0-9FADEBDF8336} = {9293CA11-360A-4C20-A674-B9E794431BF5}
{5AF2B6D2-5CC6-49FE-928A-BA27AF52B194} = {9293CA11-360A-4C20-A674-B9E794431BF5} {5AF2B6D2-5CC6-49FE-928A-BA27AF52B194} = {9293CA11-360A-4C20-A674-B9E794431BF5}
{6294A82D-4915-4FC3-B301-8F985716F34C} = {D11FE57E-1E57-491D-A1D4-01AEF4BE5CB6}
EndGlobalSection EndGlobalSection
EndGlobal EndGlobal

View file

@ -46,11 +46,12 @@ export interface IPlexLibraries {
export interface ISonarrSettings extends IExternalSettings { export interface ISonarrSettings extends IExternalSettings {
apiKey: string, apiKey: string,
enable: boolean, enabled: boolean,
qualityProfile: string, qualityProfile: string,
seasonFolders: boolean, seasonFolders: boolean,
rootPath: string, rootPath: string,
fullRootPath: string, fullRootPath: string,
addOnly:boolean,
} }
export interface IRadarrSettings extends IExternalSettings { export interface IRadarrSettings extends IExternalSettings {

View file

@ -35,8 +35,8 @@
<div class="form-group"> <div class="form-group">
<div> <div>
<button [disabled]="form.invalid" type="submit" (click)="test(form)" class="btn btn-primary-outline"> <button [disabled]="form.invalid" type="button" (click)="test(form)" class="btn btn-primary-outline">
Test Connectivity Test
<div id="spinner"></div> <div id="spinner"></div>
</button> </button>
</div> </div>

View file

@ -1,26 +1,38 @@
 
<settings-menu></settings-menu> <settings-menu></settings-menu>
<div *ngIf="settings"> <div *ngIf="form">
<fieldset> <fieldset>
<legend>Radarr Settings</legend> <legend>Radarr Settings</legend>
<div style="float: right;"> <div style="float: right;">
<span style="vertical-align: top;">Advanced</span> <span style="vertical-align: top;">Advanced</span>
<p-inputSwitch id="customInputSwitch" [(ngModel)]="advanced"></p-inputSwitch> <p-inputSwitch id="customInputSwitch" [(ngModel)]="advanced"></p-inputSwitch>
</div> </div>
<form novalidate [formGroup]="form" (ngSubmit)="onSubmit(form)">
<div *ngIf="form.invalid" class="alert alert-danger">
<div *ngIf="form.dirty">
<div *ngIf="form.get('ip').hasError('required')">The IP/Hostname is required</div>
<div *ngIf="form.get('port').hasError('required')">The Port is required</div>
<div *ngIf="form.get('apiKey').hasError('required')">The Api Key is required</div>
</div>
<div>
<div *ngIf="form.get('defaultQualityProfile').hasError('required')">A Default Quality Profile is required</div>
<div *ngIf="form.get('defaultRootPath').hasError('required')">A Default Root Path is required</div>
</div>
</div>
<div class="form-group"> <div class="form-group">
<div class="checkbox"> <div class="checkbox">
<input type="checkbox" id="enable" [(ngModel)]="settings.enable" ng-checked="settings.enable"> <input type="checkbox" id="enable" formControlName="enabled" ng-checked="form.enabled">
<label for="enable">Enable</label> <label for="enable">Enable</label>
</div> </div>
</div> </div>
<input hidden="hidden" name="FullRootPath" id="fullRootPath" value="settings.enable" />
<div class="form-group"> <div class="form-group">
<label for="Ip" class="control-label">Hostname or IP</label> <label for="Ip" class="control-label">Hostname or IP</label>
<div class=""> <div class="">
<input type="text" class="form-control form-control-custom " [(ngModel)]="settings.ip" id="Ip" name="Ip" placeholder="localhost" value="{{settings.ip}}"> <input type="text" class="form-control form-control-custom "id="Ip" name="Ip" placeholder="localhost" formControlName="ip">
</div> </div>
</div> </div>
@ -28,7 +40,7 @@
<label for="portNumber" class="control-label">Port</label> <label for="portNumber" class="control-label">Port</label>
<div class=""> <div class="">
<input type="text" class="form-control form-control-custom " [(ngModel)]="settings.port" id="portNumber" name="Port" placeholder="Port Number" value="{{settings.port}}"> <input type="text" class="form-control form-control-custom " formControlName="port" id="portNumber" name="Port" placeholder="Port Number" >
</div> </div>
</div> </div>
@ -36,39 +48,39 @@
<div class="form-group"> <div class="form-group">
<label for="ApiKey" class="control-label">API Key</label> <label for="ApiKey" class="control-label">API Key</label>
<div> <div>
<input type="text" class="form-control form-control-custom " [(ngModel)]="settings.apiKey" id="ApiKey" name="ApiKey" value="{{settings.apiKey}}"> <input type="text" class="form-control form-control-custom " id="ApiKey" name="ApiKey" formControlName="apiKey">
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="checkbox"> <div class="checkbox">
<input type="checkbox" id="Ssl" name="Ssl" ng-checked="settings.ssl"><label for="Ssl">SSL</label> <input type="checkbox" id="Ssl" name="Ssl" formControlName="ssl"><label for="Ssl">SSL</label>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="SubDir" class="control-label">Base Url</label> <label for="SubDir" class="control-label">Base Url</label>
<div> <div>
<input type="text" class="form-control form-control-custom" [(ngModel)]="settings.subDir" id="SubDir" name="SubDir" value="@Model.SubDir"> <input type="text" class="form-control form-control-custom" formControlName="subDir" id="SubDir" name="SubDir">
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<div> <div>
<button type="submit" (click)="getProfiles()" class="btn btn-primary-outline">Get Quality Profiles <span *ngIf="profilesRunning" class="fa fa-spinner fa-spin"> </span></button> <button type="submit" (click)="getProfiles(form)" class="btn btn-primary-outline">Get Quality Profiles <span *ngIf="profilesRunning" class="fa fa-spinner fa-spin"> </span></button>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="select" class="control-label">Quality Profiles</label> <label for="select" class="control-label">Quality Profiles</label>
<div id="profiles"> <div id="profiles">
<select class="form-control form-control-custom" id="select" *ngFor='let quality of qualities'> <select formControlName="defaultQualityProfile" class="form-control form-control-custom" id="select">
<option [selected]="qualityProfile === quality.name" [ngValue]="selectedQuality" value='{{quality.id}}'>{{quality.name}}</option> <option *ngFor='let quality of qualities' value='{{quality.id}}'>{{quality.name}}</option>
</select> </select>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<div> <div>
<button type="submit" (click)="getRootFolders()" class="btn btn-primary-outline">Get Root Folders <span *ngIf="rootFoldersRunning" class="fa fa-spinner fa-spin" ></span></button> <button type="submit" (click)="getRootFolders(form)" class="btn btn-primary-outline">Get Root Folders <span *ngIf="rootFoldersRunning" class="fa fa-spinner fa-spin"></span></button>
</div> </div>
@ -76,38 +88,30 @@
<div class="form-group"> <div class="form-group">
<label for="rootFolders" class="control-label">Default Root Folders</label> <label for="rootFolders" class="control-label">Default Root Folders</label>
<div id="rootFolders"> <div id="rootFolders">
<select class="form-control form-control-custom" *ngFor='let folder of rootFolders'> <select formControlName="defaultRootPath" class="form-control form-control-custom">
<option [selected]="rootPath === folder.name" [ngValue]="selectedRootFolder" value='{{folder.id}}'>{{folder.name}}</option> <option *ngFor='let folder of rootFolders' value='{{folder.id}}'>{{folder.path}}</option>
</select> </select>
</div> </div>
</div> </div>
<div class="form-group" *ngIf="advanced"> <div class="form-group" *ngIf="advanced">
<div class="checkbox"> <div class="checkbox">
<input type="checkbox" id="addOnly" [(ngModel)]="settings.addOnly" ng-checked="settings.addOnly"> <input type="checkbox" id="addOnly" formControlName="addOnly">
<label for="addOnly">Do not search</label> <label for="addOnly">Do not search</label>
</div> </div>
</div> </div>
<div class="form-group">
<div class="checkbox">
<input type="checkbox" id="SeasonFolders" name="SeasonFolders" ng-checked="settings.seasonFolders">
<label for="SeasonFolders">Enable season folders</label>
</div>
<label>Enabled Season Folders to organize seasons into individual folders within a show.</label>
</div>
<div class="form-group"> <div class="form-group">
<div> <div>
<button (click)="test()" type="submit" class="btn btn-primary-outline">Test Connectivity <span id="spinner" ></span></button> <button [disabled]="form.invalid" (click)="test()" class="btn btn-primary-outline">Test Connectivity <span id="spinner"></span></button>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<div> <div>
<button (click)="save()" type="submit" class="btn btn-primary-outline ">Submit</button> <button type="submit" [disabled]="form.invalid" class="btn btn-primary-outline ">Submit</button>
</div> </div>
</div> </div>
</form>
</fieldset> </fieldset>
</div> </div>

View file

@ -1,6 +1,7 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { Subject } from 'rxjs/Subject'; import { Subject } from 'rxjs/Subject';
import "rxjs/add/operator/takeUntil"; import "rxjs/add/operator/takeUntil";
import { FormGroup, Validators, FormBuilder } from '@angular/forms';
import { IRadarrSettings } from '../../interfaces/ISettings'; import { IRadarrSettings } from '../../interfaces/ISettings';
import { IRadarrProfile, IRadarrRootFolder } from '../../interfaces/IRadarr'; import { IRadarrProfile, IRadarrRootFolder } from '../../interfaces/IRadarr';
@ -14,35 +15,53 @@ import { NotificationService } from "../../services/notification.service";
}) })
export class RadarrComponent implements OnInit { export class RadarrComponent implements OnInit {
constructor(private settingsService: SettingsService, private radarrService: RadarrService, private notificationService: NotificationService) { } constructor(private settingsService: SettingsService, private radarrService: RadarrService, private notificationService: NotificationService,
private fb: FormBuilder) { }
settings: IRadarrSettings;
qualities: IRadarrProfile[]; qualities: IRadarrProfile[];
rootFolders: IRadarrRootFolder[]; rootFolders: IRadarrRootFolder[];
selectedRootFolder: IRadarrRootFolder;
selectedQuality: IRadarrProfile;
profilesRunning: boolean; profilesRunning: boolean;
rootFoldersRunning: boolean; rootFoldersRunning: boolean;
advanced = false; advanced = false;
private subscriptions = new Subject<void>(); private subscriptions = new Subject<void>();
form : FormGroup;
ngOnInit(): void { ngOnInit(): void {
this.settingsService.getRadarr() this.settingsService.getRadarr()
.takeUntil(this.subscriptions) .takeUntil(this.subscriptions)
.subscribe(x => { .subscribe(x => {
this.settings = x;
this.form = this.fb.group({
enabled: [x.enabled],
apiKey: [x.apiKey, [Validators.required]],
defaultQualityProfile: [x.defaultQualityProfile, [Validators.required]],
defaultRootPath: [x.defaultRootPath, [Validators.required]],
ssl: [x.ssl],
subDir: [x.subDir],
ip: [x.ip, [Validators.required]],
port: [x.port, [Validators.required]],
addOnly: [x.addOnly],
}); });
if (x.defaultQualityProfile)
{
this.getProfiles(this.form);
}
if (x.defaultRootPath)
{
this.getRootFolders(this.form);
}
});
} }
getProfiles() { getProfiles(form: FormGroup) {
this.profilesRunning = true; this.profilesRunning = true;
this.radarrService.getQualityProfiles(this.settings).subscribe(x => { this.radarrService.getQualityProfiles(form.value).subscribe(x => {
this.qualities = x; this.qualities = x;
this.profilesRunning = false; this.profilesRunning = false;
@ -50,9 +69,9 @@ export class RadarrComponent implements OnInit {
}); });
} }
getRootFolders() { getRootFolders(form: FormGroup) {
this.rootFoldersRunning = true; this.rootFoldersRunning = true;
this.radarrService.getRootFolders(this.settings).subscribe(x => { this.radarrService.getRootFolders(form.value).subscribe(x => {
this.rootFolders = x; this.rootFolders = x;
this.rootFoldersRunning = false; this.rootFoldersRunning = false;
@ -64,14 +83,21 @@ export class RadarrComponent implements OnInit {
// TODO // TODO
} }
save() { onSubmit(form: FormGroup) {
this.settingsService.saveRadarr(this.settings).subscribe(x => { if (form.invalid) {
this.notificationService.error("Validation", "Please check your entered values");
return
}
var settings = <IRadarrSettings>form.value;
this.settingsService.saveRadarr(settings).subscribe(x => {
if (x) { if (x) {
this.notificationService.success("Settings Saved", "Successfully saved Radarr settings"); this.notificationService.success("Settings Saved", "Successfully saved Radarr settings");
} else { } else {
this.notificationService.success("Settings Saved", "There was an error when saving the Radarr settings"); this.notificationService.success("Settings Saved", "There was an error when saving the Radarr settings");
} }
}); });
} }
ngOnDestroy(): void { ngOnDestroy(): void {

View file

@ -20,6 +20,7 @@ import { RadarrComponent } from './radarr/radarr.component';
import { LandingPageComponent } from './landingpage/landingpage.component'; import { LandingPageComponent } from './landingpage/landingpage.component';
import { CustomizationComponent } from './customization/customization.component'; import { CustomizationComponent } from './customization/customization.component';
import { EmailNotificationComponent } from './notifications/emailnotification.component'; import { EmailNotificationComponent } from './notifications/emailnotification.component';
import { DiscordComponent } from './notifications/discord.component';
import { NotificationTemplate } from './notifications/notificationtemplate.component'; import { NotificationTemplate } from './notifications/notificationtemplate.component';
import { SettingsMenuComponent } from './settingsmenu.component'; import { SettingsMenuComponent } from './settingsmenu.component';
@ -36,6 +37,7 @@ const routes: Routes = [
{ path: 'Settings/LandingPage', component: LandingPageComponent, canActivate: [AuthGuard] }, { path: 'Settings/LandingPage', component: LandingPageComponent, canActivate: [AuthGuard] },
{ path: 'Settings/Customization', component: CustomizationComponent, canActivate: [AuthGuard] }, { path: 'Settings/Customization', component: CustomizationComponent, canActivate: [AuthGuard] },
{ path: 'Settings/Email', component: EmailNotificationComponent, canActivate: [AuthGuard] }, { path: 'Settings/Email', component: EmailNotificationComponent, canActivate: [AuthGuard] },
{ path: 'Settings/Discord', component: DiscordComponent, canActivate: [AuthGuard] },
]; ];
@NgModule({ @NgModule({
@ -60,11 +62,12 @@ const routes: Routes = [
EmbyComponent, EmbyComponent,
LandingPageComponent, LandingPageComponent,
CustomizationComponent, CustomizationComponent,
DiscordComponent,
SonarrComponent, SonarrComponent,
RadarrComponent, RadarrComponent,
EmailNotificationComponent, EmailNotificationComponent,
HumanizePipe, HumanizePipe,
NotificationTemplate NotificationTemplate,
], ],
exports: [ exports: [
RouterModule RouterModule

View file

@ -42,6 +42,7 @@
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Email']">Email</a></li> <li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Email']">Email</a></li>
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Newsletter']">Newsletter</a></li> <li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Newsletter']">Newsletter</a></li>
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Discord']">Discord</a></li>
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Pushbullet']">Pushbullet</a></li> <li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Pushbullet']">Pushbullet</a></li>
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Pushover']">Pushover</a></li> <li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Pushover']">Pushover</a></li>
</ul> </ul>

View file

@ -1,22 +1,36 @@
 
<settings-menu></settings-menu> <settings-menu></settings-menu>
<div *ngIf="settings"> <div *ngIf="form">
<fieldset> <fieldset>
<legend>Sonarr Settings</legend> <legend>Sonarr Settings</legend>
<div style="float: right;">
<span style="vertical-align: top;">Advanced</span>
<p-inputSwitch id="customInputSwitch" [(ngModel)]="advanced"></p-inputSwitch>
</div>
<form novalidate [formGroup]="form" (ngSubmit)="onSubmit(form)" style="padding-top:5%;">
<div *ngIf="form.invalid" class="alert alert-danger">
<div *ngIf="form.dirty">
<div *ngIf="form.get('ip').hasError('required')">The IP/Hostname is required</div>
<div *ngIf="form.get('port').hasError('required')">The Port is required</div>
<div *ngIf="form.get('apiKey').hasError('required')">The Api Key is required</div>
</div>
<div>
<div *ngIf="form.get('qualityProfile').hasError('required')">A Default Quality Profile is required</div>
<div *ngIf="form.get('rootPath').hasError('required')">A Default Root Path is required</div>
</div>
</div>
<div class="form-group"> <div class="form-group">
<div class="checkbox"> <div class="checkbox">
<input type="checkbox" [(ngModel)]="settings.enable" ng-checked="settings.enable"> <input type="checkbox" id="enable" formControlName="enabled" >
<label for="enable">Enable</label> <label for="enable">Enable</label>
</div> </div>
</div> </div>
<input hidden="hidden" name="FullRootPath" id="fullRootPath" value="settings.enable" />
<div class="form-group"> <div class="form-group">
<label for="Ip" class="control-label">Sonarr Hostname or IP</label> <label for="Ip" class="control-label">Sonarr Hostname or IP</label>
<div class=""> <div class="">
<input type="text" class="form-control form-control-custom " [(ngModel)]="settings.ip" id="Ip" name="Ip" placeholder="localhost" value="{{settings.ip}}"> <input type="text" class="form-control form-control-custom " formControlName="ip" id="Ip" name="Ip" placeholder="localhost" >
</div> </div>
</div> </div>
@ -24,7 +38,7 @@
<label for="portNumber" class="control-label">Port</label> <label for="portNumber" class="control-label">Port</label>
<div class=""> <div class="">
<input type="text" class="form-control form-control-custom " [(ngModel)]="settings.port" id="portNumber" name="Port" placeholder="Port Number" value="{{settings.port}}"> <input type="text" class="form-control form-control-custom " formControlName="port" id="portNumber" name="Port" placeholder="Port Number" >
</div> </div>
</div> </div>
@ -32,39 +46,39 @@
<div class="form-group"> <div class="form-group">
<label for="ApiKey" class="control-label">Sonarr API Key</label> <label for="ApiKey" class="control-label">Sonarr API Key</label>
<div> <div>
<input type="text" class="form-control form-control-custom " [(ngModel)]="settings.apiKey" id="ApiKey" name="ApiKey" value="{{settings.apiKey}}"> <input type="text" class="form-control form-control-custom " formControlName="apiKey" id="ApiKey" name="ApiKey">
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="checkbox"> <div class="checkbox">
<input type="checkbox" id="Ssl" name="Ssl" ng-checked="settings.ssl"><label for="Ssl">SSL</label> <input type="checkbox" id="Ssl" name="Ssl" formControlName="ssl"><label for="Ssl">SSL</label>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="SubDir" class="control-label">Sonarr Base Url</label> <label for="SubDir" class="control-label">Sonarr Base Url</label>
<div> <div>
<input type="text" class="form-control form-control-custom" [(ngModel)]="settings.subDir" id="SubDir" name="SubDir" value="@Model.SubDir"> <input type="text" class="form-control form-control-custom" formControlName="subDir" id="SubDir" name="SubDir" >
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<div> <div>
<button type="submit" (click)="getProfiles()" class="btn btn-primary-outline">Get Quality Profiles <span *ngIf="profilesRunning" class="fa fa-spinner fa-spin"></span></button> <button type="button" (click)="getProfiles(form)" class="btn btn-primary-outline">Get Quality Profiles <span *ngIf="profilesRunning" class="fa fa-spinner fa-spin"></span></button>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="select" class="control-label">Quality Profiles</label> <label for="select" class="control-label">Quality Profiles</label>
<div id="profiles"> <div id="profiles">
<select class="form-control form-control-custom" id="select" *ngFor='let quality of qualities'> <select class="form-control form-control-custom" id="select" formControlName="qualityProfile">
<option [selected]="qualityProfile === quality.name" [ngValue]="selectedQuality" value='{{quality.id}}'>{{quality.name}}</option> <option *ngFor='let quality of qualities' value='{{quality.id}}'>{{quality.name}}</option>
</select> </select>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<div> <div>
<button type="submit" (click)="getRootFolders()" class="btn btn-primary-outline">Get Root Folders <span *ngIf="rootFoldersRunning" class="fa fa-spinner fa-spin" ></span></button> <button type="button" (click)="getRootFolders(form)" class="btn btn-primary-outline">Get Root Folders <span *ngIf="rootFoldersRunning" class="fa fa-spinner fa-spin" ></span></button>
</div> </div>
@ -72,8 +86,8 @@
<div class="form-group"> <div class="form-group">
<label for="rootFolders" class="control-label">Default Root Folders</label> <label for="rootFolders" class="control-label">Default Root Folders</label>
<div id="rootFolders"> <div id="rootFolders">
<select class="form-control form-control-custom" *ngFor='let folder of rootFolders'> <select class="form-control form-control-custom" formControlName="rootPath">
<option [selected]="rootPath === folder.name" [ngValue]="selectedRootFolder" value='{{folder.id}}'>{{folder.name}}</option> <option *ngFor='let folder of rootFolders' value='{{folder.id}}'>{{folder.path}}</option>
</select> </select>
</div> </div>
</div> </div>
@ -81,22 +95,31 @@
<div class="form-group"> <div class="form-group">
<div class="checkbox"> <div class="checkbox">
<input type="checkbox" id="SeasonFolders" name="SeasonFolders" ng-checked="settings.seasonFolders"> <input type="checkbox" id="SeasonFolders" name="SeasonFolders"formControlName="seasonFolders">
<label for="SeasonFolders">Enable season folders</label> <label for="SeasonFolders">Enable season folders</label>
</div> </div>
<label>Enabled Season Folders to organize seasons into individual folders within a show.</label> <label>Enabled Season Folders to organize seasons into individual folders within a show.</label>
</div> </div>
<div class="form-group" *ngIf="advanced">
<div class="checkbox">
<input type="checkbox" id="addOnly" formControlName="addOnly">
<label for="addOnly">Do not search</label>
</div>
</div>
<div class="form-group"> <div class="form-group">
<div> <div>
<button (click)="test()" type="submit" class="btn btn-primary-outline">Test Connectivity <span id="spinner"> </span></button> <button type="button" (click)="test()" class="btn btn-primary-outline">Test Connectivity <span id="spinner"> </span></button>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<div> <div>
<button (click)="save()" type="submit" class="btn btn-primary-outline ">Submit</button> <button type="submit" class="btn btn-primary-outline ">Submit</button>
</div> </div>
</div> </div>
</form>
</fieldset> </fieldset>
</div> </div>

View file

@ -1,8 +1,8 @@
import { Component, OnInit, OnDestroy } from '@angular/core'; import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subject } from 'rxjs/Subject'; import { Subject } from 'rxjs/Subject';
import "rxjs/add/operator/takeUntil"; import "rxjs/add/operator/takeUntil";
import { FormGroup, Validators, FormBuilder } from '@angular/forms';
import { ISonarrSettings } from '../../interfaces/ISettings'
import { ISonarrProfile, ISonarrRootFolder } from '../../interfaces/ISonarr' import { ISonarrProfile, ISonarrRootFolder } from '../../interfaces/ISonarr'
import { SettingsService } from '../../services/settings.service'; import { SettingsService } from '../../services/settings.service';
import { SonarrService } from '../../services/applications/sonarr.service'; import { SonarrService } from '../../services/applications/sonarr.service';
@ -14,9 +14,8 @@ import { NotificationService } from "../../services/notification.service";
}) })
export class SonarrComponent implements OnInit, OnDestroy { export class SonarrComponent implements OnInit, OnDestroy {
constructor(private settingsService: SettingsService, private sonarrService: SonarrService, private notificationService: NotificationService) { } constructor(private settingsService: SettingsService, private sonarrService: SonarrService, private notificationService: NotificationService,
private fb : FormBuilder) { }
settings: ISonarrSettings;
qualities: ISonarrProfile[]; qualities: ISonarrProfile[];
rootFolders: ISonarrRootFolder[]; rootFolders: ISonarrRootFolder[];
@ -27,20 +26,43 @@ export class SonarrComponent implements OnInit, OnDestroy {
profilesRunning: boolean; profilesRunning: boolean;
rootFoldersRunning: boolean; rootFoldersRunning: boolean;
private subscriptions = new Subject<void>(); private subscriptions = new Subject<void>();
form : FormGroup;
advanced = false;
ngOnInit(): void { ngOnInit(): void {
this.settingsService.getSonarr() this.settingsService.getSonarr()
.takeUntil(this.subscriptions) .takeUntil(this.subscriptions)
.subscribe(x => { .subscribe(x => {
this.settings = x;
this.form = this.fb.group({
enabled: [x.enabled],
apiKey: [x.apiKey, [Validators.required]],
qualityProfile: [x.qualityProfile, [Validators.required]],
rootPath: [x.rootPath, [Validators.required]],
ssl: [x.ssl],
subDir: [x.subDir],
ip: [x.ip, [Validators.required]],
port: [x.port, [Validators.required]],
addOnly: [x.addOnly],
seasonFolders: [x.seasonFolders],
});
if (x.qualityProfile)
{
this.getProfiles(this.form);
}
if (x.rootPath)
{
this.getRootFolders(this.form);
}
}); });
} }
getProfiles() { getProfiles(form:FormGroup) {
this.profilesRunning = true; this.profilesRunning = true;
this.sonarrService.getQualityProfiles(this.settings) this.sonarrService.getQualityProfiles(form.value)
.takeUntil(this.subscriptions) .takeUntil(this.subscriptions)
.subscribe(x => { .subscribe(x => {
this.qualities = x; this.qualities = x;
@ -50,9 +72,9 @@ export class SonarrComponent implements OnInit, OnDestroy {
}); });
} }
getRootFolders() { getRootFolders(form:FormGroup) {
this.rootFoldersRunning = true; this.rootFoldersRunning = true;
this.sonarrService.getRootFolders(this.settings) this.sonarrService.getRootFolders(form.value)
.takeUntil(this.subscriptions) .takeUntil(this.subscriptions)
.subscribe(x => { .subscribe(x => {
this.rootFolders = x; this.rootFolders = x;
@ -66,17 +88,12 @@ export class SonarrComponent implements OnInit, OnDestroy {
// TODO // TODO
} }
save() { onSubmit(form:FormGroup) {
if (form.invalid) {
if (!this.qualities || !this.rootFolders) { this.notificationService.error("Validation", "Please check your entered values");
return
this.notificationService.error("Settings Saved", "Please make sure we have selected a quality profile");
} }
if (!this.rootFolders) { this.settingsService.saveSonarr(form.value)
this.notificationService.error("Settings Saved", "Please make sure we have a root folder");
}
this.settingsService.saveSonarr(this.settings)
.takeUntil(this.subscriptions) .takeUntil(this.subscriptions)
.subscribe(x => { .subscribe(x => {
if (x) { if (x) {

View file

@ -22,6 +22,7 @@ namespace Ombi.Controllers.External
/// </summary> /// </summary>
/// <param name="service">The service.</param> /// <param name="service">The service.</param>
/// <param name="notification">The notification.</param> /// <param name="notification">The notification.</param>
/// <param name="emailN">The notification.</param>
public TesterController(INotificationService service, IDiscordNotification notification, IEmailNotification emailN) public TesterController(INotificationService service, IDiscordNotification notification, IEmailNotification emailN)
{ {
Service = service; Service = service;
@ -42,7 +43,7 @@ namespace Ombi.Controllers.External
public bool Discord([FromBody] DiscordNotificationSettings settings) public bool Discord([FromBody] DiscordNotificationSettings settings)
{ {
settings.Enabled = true; settings.Enabled = true;
BackgroundJob.Enqueue(() => Service.PublishTest(new NotificationOptions{NotificationType = NotificationType.Test}, settings, DiscordNotification)); BackgroundJob.Enqueue(() => Service.PublishTest(new NotificationOptions{NotificationType = NotificationType.Test}, settings, (DiscordNotification)DiscordNotification));
return true; return true;
} }

View file

@ -230,7 +230,7 @@ namespace Ombi.Controllers
/// </summary> /// </summary>
/// <param name="model">The model.</param> /// <param name="model">The model.</param>
/// <returns></returns> /// <returns></returns>
[HttpPost("notifications/email")] [HttpPost("notifications/discord")]
public async Task<bool> DiscordNotificationSettings([FromBody] DiscordNotificationsViewModel model) public async Task<bool> DiscordNotificationSettings([FromBody] DiscordNotificationsViewModel model)
{ {
// Save the email settings // Save the email settings
@ -247,7 +247,7 @@ namespace Ombi.Controllers
/// Gets the discord Notification Settings. /// Gets the discord Notification Settings.
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
[HttpGet("notifications/email")] [HttpGet("notifications/discord")]
public async Task<DiscordNotificationsViewModel> DiscordNotificationSettings() public async Task<DiscordNotificationsViewModel> DiscordNotificationSettings()
{ {
var emailSettings = await Get<DiscordNotificationSettings>(); var emailSettings = await Get<DiscordNotificationSettings>();
@ -262,7 +262,7 @@ namespace Ombi.Controllers
private async Task<List<NotificationTemplates>> BuildTemplates(NotificationAgent agent) private async Task<List<NotificationTemplates>> BuildTemplates(NotificationAgent agent)
{ {
var templates = await TemplateRepository.GetAllTemplates(agent); var templates = await TemplateRepository.GetAllTemplates(agent);
return templates.ToList(); return templates.OrderBy(x => x.NotificationType.ToString()).ToList();
} }

File diff suppressed because it is too large Load diff