Lots of fixes. Becoming more stable now. #865

pull/1488/head
tidusjar 8 years ago
parent 1c6ddc74cb
commit dcf97a1008

@ -6,13 +6,13 @@ namespace Ombi.Api.Discord
{
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 Api Api { get; }
private IApi Api { get; }
public async Task SendMessage(DiscordWebhookBody body, string webhookId, string webhookToken)
{

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

@ -1,7 +1,5 @@
using System;
using System.Net.Http;
using System.Net.Http;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Ombi.Api.Plex.Models;
using Ombi.Api.Plex.Models.Server;
using Ombi.Api.Plex.Models.Status;
@ -10,12 +8,12 @@ namespace Ombi.Api.Plex
{
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 FriendsUri = "https://plex.tv/pms/friends/all";

@ -12,18 +12,18 @@ namespace Ombi.Api.Radarr
{
public class RadarrApi : IRadarrApi
{
public RadarrApi(ILogger<RadarrApi> logger)
public RadarrApi(ILogger<RadarrApi> logger, IApi api)
{
Api = new Api();
Api = api;
Logger = logger;
}
private Api Api { get; }
private IApi Api { get; }
private ILogger<RadarrApi> Logger { get; }
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);
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)
{
var request = new Request(baseUrl, "/api/rootfolder", HttpMethod.Get);
var request = new Request("/api/rootfolder", baseUrl, HttpMethod.Get);
AddHeaders(request, apiKey);
return await Api.Request<List<RadarrRootFolder>>(request);
@ -39,7 +39,7 @@ namespace Ombi.Api.Radarr
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);
return await Api.Request<SystemStatus>(request);
@ -47,7 +47,7 @@ namespace Ombi.Api.Radarr
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);
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)
{
var request = new Request(baseUrl, "/api/movie", HttpMethod.Post);
var request = new Request("/api/movie", baseUrl, HttpMethod.Post);
var options = new RadarrAddMovieResponse
{
@ -97,7 +97,7 @@ namespace Ombi.Api.Radarr
}
catch (JsonSerializationException jse)
{
Logger.LogError(LoggingEvents.RadarrApiException,jse, "Error When adding movie to Radarr");
Logger.LogError(LoggingEvents.RadarrApiException, jse, "Error When adding movie to Radarr");
}
return null;
}

@ -9,16 +9,16 @@ namespace Ombi.Api.Sonarr
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)
{
var request = new Request(baseUrl, "/api/profile", HttpMethod.Get);
var request = new Request("/api/profile", baseUrl, HttpMethod.Get);
request.AddHeader("X-Api-Key", apiKey);
@ -27,7 +27,7 @@ namespace Ombi.Api.Sonarr
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);

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

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

@ -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);
}
}

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

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

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

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

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

@ -10,16 +10,16 @@ namespace Ombi.Api.TheMovieDb
{
public class TheMovieDbApi : IMovieDbApi
{
public TheMovieDbApi(IMapper mapper)
public TheMovieDbApi(IMapper mapper, IApi api)
{
Api = new Api();
Api = api;
Mapper = mapper;
}
private IMapper Mapper { get; }
private readonly string ApiToken = "b8eabaf5608b88d0298aa189dd90bf00";
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)
{

@ -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);
}
}
}

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

@ -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");
}
}
}

@ -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; }
}
}

@ -61,6 +61,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Api.Radarr", "Ombi.Api
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Api.Discord", "Ombi.Api.Discord\Ombi.Api.Discord.csproj", "{5AF2B6D2-5CC6-49FE-928A-BA27AF52B194}"
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
GlobalSection(SolutionConfigurationPlatforms) = preSolution
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}.Release|Any CPU.ActiveCfg = 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
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -166,5 +174,6 @@ Global
{FC6A8F7C-9722-4AE4-960D-277ACB0E81CB} = {6F42AB98-9196-44C4-B888-D5E409F415A1}
{94D04C1F-E35A-499C-B0A0-9FADEBDF8336} = {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
EndGlobal

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

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

@ -1,26 +1,38 @@

<settings-menu></settings-menu>
<div *ngIf="settings">
<fieldset>
<legend>Radarr Settings</legend>
<div style="float: right;">
<div *ngIf="form">
<fieldset>
<legend>Radarr 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)">
<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="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>
</div>
</div>
<input hidden="hidden" name="FullRootPath" id="fullRootPath" value="settings.enable" />
<div class="form-group">
<label for="Ip" class="control-label">Hostname or IP</label>
<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>
@ -28,7 +40,7 @@
<label for="portNumber" class="control-label">Port</label>
<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>
@ -36,39 +48,39 @@
<div class="form-group">
<label for="ApiKey" class="control-label">API Key</label>
<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 class="form-group">
<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 class="form-group">
<label for="SubDir" class="control-label">Base Url</label>
<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 class="form-group">
<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 class="form-group">
<label for="select" class="control-label">Quality Profiles</label>
<div id="profiles">
<select class="form-control form-control-custom" id="select" *ngFor='let quality of qualities'>
<option [selected]="qualityProfile === quality.name" [ngValue]="selectedQuality" value='{{quality.id}}'>{{quality.name}}</option>
<select formControlName="defaultQualityProfile" class="form-control form-control-custom" id="select">
<option *ngFor='let quality of qualities' value='{{quality.id}}'>{{quality.name}}</option>
</select>
</div>
</div>
<div class="form-group">
<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>
@ -76,38 +88,30 @@
<div class="form-group">
<label for="rootFolders" class="control-label">Default Root Folders</label>
<div id="rootFolders">
<select class="form-control form-control-custom" *ngFor='let folder of rootFolders'>
<option [selected]="rootPath === folder.name" [ngValue]="selectedRootFolder" value='{{folder.id}}'>{{folder.name}}</option>
<select formControlName="defaultRootPath" class="form-control form-control-custom">
<option *ngFor='let folder of rootFolders' value='{{folder.id}}'>{{folder.path}}</option>
</select>
</div>
</div>
<div class="form-group" *ngIf="advanced">
<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>
</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>
<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 class="form-group">
<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>
</fieldset>
</form>
</fieldset>
</div>

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

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

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

@ -1,22 +1,36 @@

<settings-menu></settings-menu>
<div *ngIf="settings">
<div *ngIf="form">
<fieldset>
<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="checkbox">
<input type="checkbox" [(ngModel)]="settings.enable" ng-checked="settings.enable">
<input type="checkbox" id="enable" formControlName="enabled" >
<label for="enable">Enable</label>
</div>
</div>
<input hidden="hidden" name="FullRootPath" id="fullRootPath" value="settings.enable" />
<div class="form-group">
<label for="Ip" class="control-label">Sonarr Hostname or IP</label>
<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>
@ -24,7 +38,7 @@
<label for="portNumber" class="control-label">Port</label>
<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>
@ -32,39 +46,39 @@
<div class="form-group">
<label for="ApiKey" class="control-label">Sonarr API Key</label>
<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 class="form-group">
<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 class="form-group">
<label for="SubDir" class="control-label">Sonarr Base Url</label>
<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 class="form-group">
<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 class="form-group">
<label for="select" class="control-label">Quality Profiles</label>
<div id="profiles">
<select class="form-control form-control-custom" id="select" *ngFor='let quality of qualities'>
<option [selected]="qualityProfile === quality.name" [ngValue]="selectedQuality" value='{{quality.id}}'>{{quality.name}}</option>
<select class="form-control form-control-custom" id="select" formControlName="qualityProfile">
<option *ngFor='let quality of qualities' value='{{quality.id}}'>{{quality.name}}</option>
</select>
</div>
</div>
<div class="form-group">
<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>
@ -72,8 +86,8 @@
<div class="form-group">
<label for="rootFolders" class="control-label">Default Root Folders</label>
<div id="rootFolders">
<select class="form-control form-control-custom" *ngFor='let folder of rootFolders'>
<option [selected]="rootPath === folder.name" [ngValue]="selectedRootFolder" value='{{folder.id}}'>{{folder.name}}</option>
<select class="form-control form-control-custom" formControlName="rootPath">
<option *ngFor='let folder of rootFolders' value='{{folder.id}}'>{{folder.path}}</option>
</select>
</div>
</div>
@ -81,22 +95,31 @@
<div class="form-group">
<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>
</div>
<label>Enabled Season Folders to organize seasons into individual folders within a show.</label>
</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>
<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 class="form-group">
<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>
</form>
</fieldset>
</div>

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

@ -22,6 +22,7 @@ namespace Ombi.Controllers.External
/// </summary>
/// <param name="service">The service.</param>
/// <param name="notification">The notification.</param>
/// <param name="emailN">The notification.</param>
public TesterController(INotificationService service, IDiscordNotification notification, IEmailNotification emailN)
{
Service = service;
@ -42,7 +43,7 @@ namespace Ombi.Controllers.External
public bool Discord([FromBody] DiscordNotificationSettings settings)
{
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;
}

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

@ -56,4 +56,4 @@ gulp.task('test', callback => runSequence('test_compile', 'test_run'));
gulp.task('build', callback => runSequence('vendor', 'main', callback));
gulp.task('analyse', callback => runSequence('analyse_var', 'build'));
gulp.task('full', callback => runSequence('clean', 'build'));
gulp.task('publish', callback => runSequence('prod_var', 'build'));
gulp.task('publish', callback => runSequence('prod_var', 'build'));

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save