Finished the main part of #844 just need testing

pull/854/head
Jamie.Rees 8 years ago
parent b3c7d83529
commit 0811a89c86

@ -34,7 +34,7 @@ namespace Ombi.Api.Interfaces
public interface IWatcherApi
{
WatcherAddMovieResult AddMovie(string imdbId, string apiKey, Uri baseUrl);
List<WatcherListStatusResult> ListMovies(string apiKey, Uri baseUrl);
List<WatcherListStatusResult> ListMovies(string apiKey, Uri baseUrl, string imdbId);
WatcherListStatusResultContainer ListMovies(string apiKey, Uri baseUrl);
WatcherListStatusResultContainer ListMovies(string apiKey, Uri baseUrl, string imdbId);
}
}

@ -24,11 +24,19 @@
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using Newtonsoft.Json;
namespace Ombi.Api.Models.Watcher
{
public class WatcherAddMovieResult
{
public string status { get; set; }
public string message { get; set; }
[JsonIgnore]
public string ErrorMessage { get; set; }
[JsonIgnore]
public bool Error { get; set; }
}
}

@ -33,11 +33,11 @@ namespace Ombi.Api.Models.Watcher
{
public class Quality2
{
[DeserializeAs(Name = "720P")]
[JsonProperty("720P")]
public List<string> Q720P { get; set; }
[DeserializeAs(Name = "1080P")]
[JsonProperty("1080P")]
public List<string> Q1080P { get; set; }
[DeserializeAs( Name = "4K")]
[JsonProperty("4K")]
public List<string> Q4K { get; set; }
public List<string> SD { get; set; }
}
@ -51,7 +51,7 @@ namespace Ombi.Api.Models.Watcher
public class Quality
{
[DeserializeAs(Name = "Quality")]
[JsonProperty("Quality")]
public Quality2 quality { get; set; }
public Filters Filters { get; set; }
}
@ -73,5 +73,15 @@ namespace Ombi.Api.Models.Watcher
public string tomatorating { get; set; }
public string imdbid { get; set; }
public Quality quality { get; set; }
}
public class WatcherListStatusResultContainer
{
public List<WatcherListStatusResult> Results { get; set; }
[JsonIgnore]
public string ErrorMessage { get; set; }
[JsonIgnore]
public bool Error { get; set; }
}
}

@ -1,7 +1,8 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: CouchPotatoApi.cs
// File: WatcherApi.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
@ -23,16 +24,14 @@
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json;
using NLog;
using Ombi.Api.Interfaces;
using Ombi.Api.Models.Movie;
using Ombi.Api.Models.Watcher;
using Ombi.Helpers;
using RestSharp;
namespace Ombi.Api
@ -49,21 +48,68 @@ namespace Ombi.Api
public WatcherAddMovieResult AddMovie(string imdbId, string apiKey, Uri baseUrl)
{
return Send<WatcherAddMovieResult>("addmovie", apiKey, baseUrl, imdbId);
var response = Send("addmovie", apiKey, baseUrl, imdbId);
try
{
return JsonConvert.DeserializeObject<WatcherAddMovieResult>(response.Content);
}
catch (Exception e)
{
Log.Error(e);
return new WatcherAddMovieResult
{
Error = true,
ErrorMessage = e.Message
};
}
}
public List<WatcherListStatusResult> ListMovies(string apiKey, Uri baseUrl)
public WatcherListStatusResultContainer ListMovies(string apiKey, Uri baseUrl)
{
return Send<List<WatcherListStatusResult>>("liststatus", apiKey, baseUrl);
var response = Send("liststatus", apiKey, baseUrl);
try
{
if (response.Content.Contains("No movies found"))
{
return new WatcherListStatusResultContainer();
}
return JsonConvert.DeserializeObject<WatcherListStatusResultContainer>(response.Content);
}
catch (Exception e)
{
Log.Error(e);
return new WatcherListStatusResultContainer
{
Error = true,
ErrorMessage = e.Message
};
}
}
public List<WatcherListStatusResult> ListMovies(string apiKey, Uri baseUrl, string imdbId)
public WatcherListStatusResultContainer ListMovies(string apiKey, Uri baseUrl, string imdbId)
{
return Send<List<WatcherListStatusResult>>("liststatus", apiKey, baseUrl, imdbId);
var response = Send("liststatus", apiKey, baseUrl, imdbId);
try
{
if (response.Content.Contains("No movies found"))
{
return new WatcherListStatusResultContainer();
}
return JsonConvert.DeserializeObject<WatcherListStatusResultContainer>(response.Content);
}
catch (Exception e)
{
Log.Error(e);
return new WatcherListStatusResultContainer
{
Error = true,
ErrorMessage = e.Message
};
}
}
private T Send<T>(string mode, string apiKey, Uri baseUrl, string imdbid = "") where T : new()
private IRestResponse Send(string mode, string apiKey, Uri baseUrl, string imdbid = "")
{
RestRequest request;
request = new RestRequest
@ -72,11 +118,13 @@ namespace Ombi.Api
};
request.AddUrlSegment("apikey", apiKey);
if(!string.IsNullOrEmpty(imdbid))
{ request.AddUrlSegment("imdbid", imdbid);}
if (!string.IsNullOrEmpty(imdbid))
{
request.AddUrlSegment("imdbid", imdbid);
}
request.AddUrlSegment("mode", mode);
return Api.Execute<T>(request, baseUrl);
return Api.Execute(request, baseUrl);
}
}

@ -42,6 +42,7 @@ namespace Ombi.Core
public const string SickRageQueued = nameof(SickRageQueued);
public const string CouchPotatoQualityProfiles = nameof(CouchPotatoQualityProfiles);
public const string CouchPotatoQueued = nameof(CouchPotatoQueued);
public const string WatcherQueued = nameof(WatcherQueued);
public const string GetPlexRequestSettings = nameof(GetPlexRequestSettings);
public const string LastestProductVersion = nameof(LastestProductVersion);
}

@ -0,0 +1,10 @@
using System.Threading.Tasks;
using Ombi.Store;
namespace Ombi.Core
{
public interface IMovieSender
{
Task<MovieSenderResult> Send(RequestedModel model, string qualityId = "");
}
}

@ -0,0 +1,95 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: MovieSender.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.Threading.Tasks;
using NLog;
using Ombi.Api.Interfaces;
using Ombi.Core.SettingModels;
using Ombi.Store;
namespace Ombi.Core
{
public class MovieSender : IMovieSender
{
public MovieSender(ISettingsService<CouchPotatoSettings> cp, ISettingsService<WatcherSettings> watcher,
ICouchPotatoApi cpApi, IWatcherApi watcherApi)
{
CouchPotatoSettings = cp;
WatcherSettings = watcher;
CpApi = cpApi;
WatcherApi = watcherApi;
}
private ISettingsService<CouchPotatoSettings> CouchPotatoSettings { get; }
private ISettingsService<WatcherSettings> WatcherSettings { get; }
private ICouchPotatoApi CpApi { get; }
private IWatcherApi WatcherApi { get; }
private static Logger Log = LogManager.GetCurrentClassLogger();
public async Task<MovieSenderResult> Send(RequestedModel model, string qualityId = "")
{
var cpSettings = await CouchPotatoSettings.GetSettingsAsync();
var watcherSettings = await WatcherSettings.GetSettingsAsync();
if (cpSettings.Enabled)
{
return SendToCp(model, cpSettings, string.IsNullOrEmpty(qualityId) ? cpSettings.ProfileId : qualityId);
}
if (watcherSettings.Enabled)
{
return SendToWatcher(model, watcherSettings);
}
return new MovieSenderResult { Result = false, MovieSendingEnabled = false };
}
private MovieSenderResult SendToWatcher(RequestedModel model, WatcherSettings settings)
{
var result = WatcherApi.AddMovie(model.ImdbId, settings.ApiKey, settings.FullUri);
if (result.Error)
{
Log.Error(result.ErrorMessage);
return new MovieSenderResult { Result = false };
}
if (result.status.Equals("success", StringComparison.CurrentCultureIgnoreCase))
{
return new MovieSenderResult { Result = true, MovieSendingEnabled = true };
}
Log.Error(result.message);
return new MovieSenderResult { Result = false, MovieSendingEnabled = true };
}
private MovieSenderResult SendToCp(RequestedModel model, CouchPotatoSettings settings, string qualityId)
{
var result = CpApi.AddMovie(model.ImdbId, settings.ApiKey, model.Title, settings.FullUri, qualityId);
return new MovieSenderResult { Result = result, MovieSendingEnabled = true };
}
}
}

@ -0,0 +1,40 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: MovieSenderResult.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
namespace Ombi.Core
{
public class MovieSenderResult
{
public bool Result { get; set; }
/// <summary>
/// Gets or sets a value indicating whether we can send to either CP or Watcher.
/// </summary>
/// <value>
/// <c>true</c> if [movie sending enabled]; otherwise, <c>false</c>.
/// </value>
public bool MovieSendingEnabled { get; set; }
}
}

@ -95,9 +95,12 @@
<ItemGroup>
<Compile Include="CacheKeys.cs" />
<Compile Include="HeadphonesSender.cs" />
<Compile Include="IMovieSender.cs" />
<Compile Include="IPlexReadOnlyDatabase.cs" />
<Compile Include="ISecurityExtensions.cs" />
<Compile Include="IStatusChecker.cs" />
<Compile Include="MovieSender.cs" />
<Compile Include="MovieSenderResult.cs" />
<Compile Include="Notification\NotificationMessage.cs" />
<Compile Include="Notification\NotificationMessageContent.cs" />
<Compile Include="Notification\NotificationMessageCurlys.cs" />
@ -119,6 +122,7 @@
<Compile Include="Queue\TransientFaultQueue.cs" />
<Compile Include="SecurityExtensions.cs" />
<Compile Include="SettingModels\AuthenticationSettings.cs" />
<Compile Include="SettingModels\WatcherSettings.cs" />
<Compile Include="SettingModels\ExternalSettings.cs" />
<Compile Include="SettingModels\HeadphonesSettings.cs" />
<Compile Include="SettingModels\LandingPageSettings.cs" />

@ -35,6 +35,7 @@ namespace Ombi.Core.SettingModels
public int SickRageCacher { get; set; }
public int SonarrCacher { get; set; }
public int CouchPotatoCacher { get; set; }
public int WatcherCacher { get; set; }
public int StoreBackup { get; set; }
public int StoreCleanup { get; set; }
public int UserRequestLimitResetter { get; set; }

@ -0,0 +1,34 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: WatcherSettings.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
namespace Ombi.Core.SettingModels
{
public sealed class WatcherSettings : ExternalSettings
{
public bool Enabled { get; set; }
public string ApiKey { get; set; }
}
}

@ -0,0 +1,8 @@
namespace Ombi.Services.Interfaces
{
public interface IWatcherCacher
{
void Queued();
string[] QueuedIds();
}
}

@ -30,6 +30,7 @@ namespace Ombi.Services.Jobs
{
public const string StoreBackup = "Database Backup";
public const string CpCacher = "CouchPotato Cacher";
public const string WatcherCacher = "Watcher Cacher";
public const string SonarrCacher = "Sonarr Cacher";
public const string SrCacher = "SickRage Cacher";
public const string PlexChecker = "Plex Availability Cacher";

@ -0,0 +1,132 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: PlexAvailabilityChecker.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.Linq;
using NLog;
using Ombi.Api.Interfaces;
using Ombi.Api.Models.Movie;
using Ombi.Core;
using Ombi.Core.SettingModels;
using Ombi.Helpers;
using Ombi.Services.Interfaces;
using Quartz;
namespace Ombi.Services.Jobs
{
public class WatcherCacher : IJob, IWatcherCacher
{
public WatcherCacher(
ISettingsService<WatcherSettings> watcher,
IWatcherApi watcherApi, ICacheProvider cache, IJobRecord rec)
{
WatcherSettings = watcher;
WatcherApi = WatcherApi;
Cache = cache;
Job = rec;
}
private ISettingsService<WatcherSettings> WatcherSettings { get; }
private ICacheProvider Cache { get; }
private IWatcherApi WatcherApi { get; }
private IJobRecord Job { get; }
private static Logger Log = LogManager.GetCurrentClassLogger();
public void Queued()
{
Log.Trace("Getting the settings");
var watcherSettings = WatcherSettings.GetSettings();
Job.SetRunning(true, JobNames.WatcherCacher);
try
{
if (watcherSettings.Enabled)
{
var movies = WatcherApi.ListMovies(watcherSettings.ApiKey, watcherSettings.FullUri);
if (movies.Error)
{
Log.Error("Error when trying to get Watchers movies");
Log.Error(movies.ErrorMessage);
}
var wantedMovies =
movies?.Results?.Where(x => x.status.Equals("Wanted", StringComparison.CurrentCultureIgnoreCase));
if (wantedMovies != null && wantedMovies.Any())
{
Cache.Set(CacheKeys.WatcherQueued, movies.Results.Select(x => x.imdbid), CacheKeys.TimeFrameMinutes.SchedulerCaching);
}
}
}
catch (Exception e)
{
Log.Error(e);
}
finally
{
Job.Record(JobNames.WatcherCacher);
Job.SetRunning(false, JobNames.WatcherCacher);
}
}
// we do not want to set here...
public string[] QueuedIds()
{
try
{
var watcherSettings = WatcherSettings.GetSettings();
if (watcherSettings.Enabled)
{
var movies = Cache.Get<string[]>(CacheKeys.WatcherQueued);
if (movies != null)
{
return movies;
}
return new string[] {};
}
}
catch (Exception e)
{
Log.Error(e);
return new string[] { };
}
return new string[] {};
}
public void Execute(IJobExecutionContext context)
{
Queued();
}
}
}

@ -86,8 +86,10 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Interfaces\IWatcherCacher.cs" />
<Compile Include="Interfaces\IJobRecord.cs" />
<Compile Include="Interfaces\INotificationEngine.cs" />
<Compile Include="Jobs\WatcherCacher.cs" />
<Compile Include="Jobs\HtmlTemplateGenerator.cs" />
<Compile Include="Jobs\IPlexContentCacher.cs" />
<Compile Include="Jobs\IRecentlyAdded.cs" />

@ -33,6 +33,7 @@ using System.Linq;
using NLog;
using Ombi.Core;
using Ombi.Core.SettingModels;
using Ombi.Services.Interfaces;
using Ombi.Services.Jobs;
using Ombi.UI.Helpers;
using Quartz;
@ -68,6 +69,7 @@ namespace Ombi.UI.Jobs
JobBuilder.Create<SickRageCacher>().WithIdentity("SickRageCacher", "Cache").Build(),
JobBuilder.Create<SonarrCacher>().WithIdentity("SonarrCacher", "Cache").Build(),
JobBuilder.Create<CouchPotatoCacher>().WithIdentity("CouchPotatoCacher", "Cache").Build(),
JobBuilder.Create<WatcherCacher>().WithIdentity("WatcherCacher", "Cache").Build(),
JobBuilder.Create<StoreBackup>().WithIdentity("StoreBackup", "Database").Build(),
JobBuilder.Create<StoreCleanup>().WithIdentity("StoreCleanup", "Database").Build(),
JobBuilder.Create<UserRequestLimitResetter>().WithIdentity("UserRequestLimiter", "Request").Build(),
@ -117,6 +119,10 @@ namespace Ombi.UI.Jobs
{
s.CouchPotatoCacher = 60;
}
if (s.WatcherCacher == 0)
{
s.WatcherCacher = 60;
}
if (s.PlexAvailabilityChecker == 0)
{
s.PlexAvailabilityChecker = 60;
@ -208,6 +214,13 @@ namespace Ombi.UI.Jobs
.WithSimpleSchedule(x => x.WithIntervalInMinutes(s.CouchPotatoCacher).RepeatForever())
.Build();
var watcherCacher =
TriggerBuilder.Create()
.WithIdentity("WatcherCacher", "Cache")
.StartAt(DateBuilder.FutureDate(4, IntervalUnit.Minute))
.WithSimpleSchedule(x => x.WithIntervalInMinutes(s.WatcherCacher).RepeatForever())
.Build();
var storeBackup =
TriggerBuilder.Create()
.WithIdentity("StoreBackup", "Database")
@ -258,6 +271,7 @@ namespace Ombi.UI.Jobs
triggers.Add(srCacher);
triggers.Add(sonarrCacher);
triggers.Add(cpCacher);
triggers.Add(watcherCacher);
triggers.Add(storeBackup);
triggers.Add(storeCleanup);
triggers.Add(userRequestLimiter);

@ -75,6 +75,7 @@ namespace Ombi.UI.Modules.Admin
private ISettingsService<PushoverNotificationSettings> PushoverService { get; }
private ISettingsService<HeadphonesSettings> HeadphonesService { get; }
private ISettingsService<NewletterSettings> NewsLetterService { get; }
private ISettingsService<WatcherSettings> WatcherSettings { get; }
private ISettingsService<LogSettings> LogService { get; }
private IPlexApi PlexApi { get; }
private ISonarrApi SonarrApi { get; }
@ -116,7 +117,8 @@ namespace Ombi.UI.Modules.Admin
ICacheProvider cache, ISettingsService<SlackNotificationSettings> slackSettings,
ISlackApi slackApi, ISettingsService<LandingPageSettings> lp,
ISettingsService<ScheduledJobsSettings> scheduler, IJobRecord rec, IAnalytics analytics,
ISettingsService<NotificationSettingsV2> notifyService, IRecentlyAdded recentlyAdded
ISettingsService<NotificationSettingsV2> notifyService, IRecentlyAdded recentlyAdded,
ISettingsService<WatcherSettings> watcherSettings
, ISecurityExtensions security) : base("admin", prService, security)
{
PrService = prService;
@ -147,6 +149,7 @@ namespace Ombi.UI.Modules.Admin
Analytics = analytics;
NotifySettings = notifyService;
RecentlyAdded = recentlyAdded;
WatcherSettings = watcherSettings;
Before += (ctx) => Security.AdminLoginRedirect(Permissions.Administrator, ctx);
@ -374,6 +377,18 @@ namespace Ombi.UI.Modules.Admin
return Response.AsJson(valid.SendJsonError());
}
var watcherSettings = WatcherSettings.GetSettings();
if (watcherSettings.Enabled)
{
return
Response.AsJson(new JsonResponseModel
{
Result = false,
Message = "Cannot have Watcher and CouchPotato both enabled."
});
}
couchPotatoSettings.ApiKey = couchPotatoSettings.ApiKey.Trim();
var result = CpService.SaveSettings(couchPotatoSettings);
return Response.AsJson(result

@ -0,0 +1,107 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: SystemStatusModule.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Threading.Tasks;
using MarkdownSharp;
using Nancy;
using Nancy.ModelBinding;
using Nancy.Responses.Negotiation;
using Nancy.Validation;
using Ombi.Core;
using Ombi.Core.SettingModels;
using Ombi.Core.StatusChecker;
using Ombi.Helpers;
using Ombi.Helpers.Analytics;
using Ombi.Helpers.Permissions;
using Ombi.UI.Helpers;
using Ombi.UI.Models;
using Action = Ombi.Helpers.Analytics.Action;
using ISecurityExtensions = Ombi.Core.ISecurityExtensions;
namespace Ombi.UI.Modules.Admin
{
public class IntegrationModule : BaseModule
{
public IntegrationModule(ISettingsService<PlexRequestSettings> settingsService, ISettingsService<WatcherSettings> watcher,
ISettingsService<CouchPotatoSettings> cp,ISecurityExtensions security, IAnalytics a) : base("admin", settingsService, security)
{
WatcherSettings = watcher;
Analytics = a;
Before += (ctx) => Security.AdminLoginRedirect(Permissions.Administrator, ctx);
Get["/watcher", true] = async (x, ct) => await Watcher();
Post["/watcher", true] = async (x, ct) => await SaveWatcher();
}
private ISettingsService<WatcherSettings> WatcherSettings { get; }
private ISettingsService<CouchPotatoSettings> CpSettings { get; }
private IAnalytics Analytics { get; }
private async Task<Negotiator> Watcher()
{
var settings = await WatcherSettings.GetSettingsAsync();
return View["Watcher", settings];
}
private async Task<Response> SaveWatcher()
{
var settings = this.Bind<WatcherSettings>();
var valid = this.Validate(settings);
if (!valid.IsValid)
{
return Response.AsJson(valid.SendJsonError());
}
var cpSettings = await CpSettings.GetSettingsAsync().ConfigureAwait(false);
if (cpSettings.Enabled)
{
return
Response.AsJson(new JsonResponseModel
{
Result = false,
Message = "Cannot have Watcher and CouchPotato both enabled."
});
}
settings.ApiKey = settings.ApiKey.Trim();
var result = await WatcherSettings.SaveSettingsAsync(settings);
return Response.AsJson(result
? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for Watcher!" }
: new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." });
}
}
}

@ -45,7 +45,8 @@ namespace Ombi.UI.Modules
{
public ApplicationTesterModule(ICouchPotatoApi cpApi, ISonarrApi sonarrApi, IPlexApi plexApi,
ISickRageApi srApi, IHeadphonesApi hpApi, ISettingsService<PlexRequestSettings> pr, ISecurityExtensions security) : base("test", pr, security)
ISickRageApi srApi, IHeadphonesApi hpApi, ISettingsService<PlexRequestSettings> pr, ISecurityExtensions security,
IWatcherApi watcherApi) : base("test", pr, security)
{
this.RequiresAuthentication();
@ -54,6 +55,7 @@ namespace Ombi.UI.Modules
PlexApi = plexApi;
SickRageApi = srApi;
HeadphonesApi = hpApi;
WatcherApi = watcherApi;
Post["/cp"] = _ => CouchPotatoTest();
Post["/sonarr"] = _ => SonarrTest();
@ -61,6 +63,7 @@ namespace Ombi.UI.Modules
Post["/sickrage"] = _ => SickRageTest();
Post["/headphones"] = _ => HeadphonesTest();
Post["/plexdb"] = _ => TestPlexDb();
Post["/watcher"] = _ => WatcherTest();
}
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
@ -69,6 +72,7 @@ namespace Ombi.UI.Modules
private IPlexApi PlexApi { get; }
private ISickRageApi SickRageApi { get; }
private IHeadphonesApi HeadphonesApi { get; }
private IWatcherApi WatcherApi { get; }
private Response CouchPotatoTest()
{
@ -86,7 +90,7 @@ namespace Ombi.UI.Modules
: Response.AsJson(new JsonResponseModel { Result = false, Message = "Could not connect to CouchPotato, please check your settings." });
}
catch (Exception e) // Exceptions are expected if we cannot connect so we will just log and swallow them.
catch (Exception e) // Exceptions are expected if we cannot connect so we will just log and swallow them.
{
Log.Warn("Exception thrown when attempting to get CP's status: ");
Log.Warn(e);
@ -99,6 +103,35 @@ namespace Ombi.UI.Modules
}
}
private Response WatcherTest()
{
var settings = this.Bind<WatcherSettings>();
var valid = this.Validate(settings);
if (!valid.IsValid)
{
return Response.AsJson(valid.SendJsonError());
}
try
{
var status = WatcherApi.ListMovies(settings.ApiKey, settings.FullUri);
return !status.Error
? Response.AsJson(new JsonResponseModel { Result = true, Message = "Connected to Watcher successfully!" })
: Response.AsJson(new JsonResponseModel { Result = false, Message = $"Could not connect to Watcher, Error: {status.ErrorMessage}" });
}
catch (Exception e) // Exceptions are expected if we cannot connect so we will just log and swallow them.
{
Log.Warn("Exception thrown when attempting to test Watcher ");
Log.Warn(e);
var message = $"Could not connect to Watcher, please check your settings. <strong>Exception Message:</strong> {e.Message}";
if (e.InnerException != null)
{
message = $"Could not connect to Watcher, please check your settings. <strong>Exception Message:</strong> {e.InnerException.Message}";
}
return Response.AsJson(new JsonResponseModel { Result = false, Message = message });
}
}
private Response SonarrTest()
{
var sonarrSettings = this.Bind<SonarrSettings>();

@ -47,17 +47,15 @@ namespace Ombi.UI.Modules
public class ApprovalModule : BaseAuthModule
{
public ApprovalModule(IRequestService service, ISettingsService<CouchPotatoSettings> cpService, ICouchPotatoApi cpApi, ISonarrApi sonarrApi,
public ApprovalModule(IRequestService service, ISonarrApi sonarrApi,
ISettingsService<SonarrSettings> sonarrSettings, ISickRageApi srApi, ISettingsService<SickRageSettings> srSettings,
ISettingsService<HeadphonesSettings> hpSettings, IHeadphonesApi hpApi, ISettingsService<PlexRequestSettings> pr, ITransientFaultQueue faultQueue
, ISecurityExtensions security) : base("approval", pr, security)
, ISecurityExtensions security, IMovieSender movieSender) : base("approval", pr, security)
{
Before += (ctx) => Security.AdminLoginRedirect(ctx, Permissions.Administrator,Permissions.ManageRequests);
Service = service;
CpService = cpService;
CpApi = cpApi;
SonarrApi = sonarrApi;
SonarrSettings = sonarrSettings;
SickRageApi = srApi;
@ -65,6 +63,7 @@ namespace Ombi.UI.Modules
HeadphonesSettings = hpSettings;
HeadphoneApi = hpApi;
FaultQueue = faultQueue;
MovieSender = movieSender;
Post["/approve", true] = async (x, ct) => await Approve((int)Request.Form.requestid, (string)Request.Form.qualityId);
Post["/deny", true] = async (x, ct) => await DenyRequest((int)Request.Form.requestid, (string)Request.Form.reason);
@ -77,15 +76,14 @@ namespace Ombi.UI.Modules
}
private IRequestService Service { get; }
private IMovieSender MovieSender { get; }
private static Logger Log = LogManager.GetCurrentClassLogger();
private ISettingsService<SonarrSettings> SonarrSettings { get; }
private ISettingsService<SickRageSettings> SickRageSettings { get; }
private ISettingsService<CouchPotatoSettings> CpService { get; }
private ISettingsService<HeadphonesSettings> HeadphonesSettings { get; }
private ISonarrApi SonarrApi { get; }
private ISickRageApi SickRageApi { get; }
private ICouchPotatoApi CpApi { get; }
private IHeadphonesApi HeadphoneApi { get; }
private ITransientFaultQueue FaultQueue { get; }
@ -186,19 +184,19 @@ namespace Ombi.UI.Modules
private async Task<Response> RequestMovieAndUpdateStatus(RequestedModel request, string qualityId)
{
var cpSettings = await CpService.GetSettingsAsync();
Log.Info("Adding movie to CouchPotato : {0}", request.Title);
if (!cpSettings.Enabled)
var result = await MovieSender.Send(request, qualityId);
if (!result.MovieSendingEnabled)
{
// Approve it
request.Approved = true;
Log.Warn("We approved movie: {0} but could not add it to CouchPotato because it has not been setup", request.Title);
Log.Warn("We approved movie: {0} but could not add it to CouchPotato/Watcher because it has not been setup", request.Title);
// Update the record
var inserted = await Service.UpdateRequestAsync(request);
return Response.AsJson(inserted
? new JsonResponseModel { Result = true, Message = "This has been approved, but It has not been sent to CouchPotato because it has not been configured." }
? new JsonResponseModel { Result = true, Message = "This has been approved, but It has not been sent to CouchPotato/Watcher because it has not been configured." }
: new JsonResponseModel
{
Result = false,
@ -206,9 +204,7 @@ namespace Ombi.UI.Modules
});
}
var result = CpApi.AddMovie(request.ImdbId, cpSettings.ApiKey, request.Title, cpSettings.FullUri, string.IsNullOrEmpty(qualityId) ? cpSettings.ProfileId : qualityId);
Log.Trace("Adding movie to CP result {0}", result);
if (result)
if (result.Result)
{
// Approve it
request.Approved = true;
@ -230,7 +226,7 @@ namespace Ombi.UI.Modules
{
Result = false,
Message =
"Something went wrong adding the movie to CouchPotato! Please check your settings."
"Something went wrong adding the movie! Please check your settings."
});
}
@ -415,26 +411,23 @@ namespace Ombi.UI.Modules
private async Task<Response> UpdateRequestsAsync(RequestedModel[] requestedModels)
{
var cpSettings = await CpService.GetSettingsAsync();
var updatedRequests = new List<RequestedModel>();
foreach (var r in requestedModels)
{
if (r.Type == RequestType.Movie)
{
if (cpSettings.Enabled)
var movieResult = await MovieSender.Send(r);
if (movieResult.Result)
{
var res = SendMovie(cpSettings, r, CpApi);
if (res)
{
r.Approved = true;
updatedRequests.Add(r);
}
else
{
Log.Error("Could not approve and send the movie {0} to couch potato!", r.Title);
}
r.Approved = true;
updatedRequests.Add(r);
}
else
{
Log.Error("Could not approve and send the movie {0} to couch potato!", r.Title);
}
if(!movieResult.MovieSendingEnabled)
{
r.Approved = true;
updatedRequests.Add(r);
@ -512,13 +505,5 @@ namespace Ombi.UI.Modules
: Response.AsJson(new JsonResponseModel { Result = false, Message = "An error happened, could not update the DB" });
}
private bool SendMovie(CouchPotatoSettings settings, RequestedModel r, ICouchPotatoApi cp)
{
Log.Info("Adding movie to CP : {0}", r.Title);
var result = cp.AddMovie(r.ImdbId, settings.ApiKey, r.Title, settings.FullUri, settings.ProfileId);
Log.Trace("Adding movie to CP result {0}", result);
return result;
}
}
}

@ -66,22 +66,22 @@ namespace Ombi.UI.Modules
{
public class SearchModule : BaseAuthModule
{
public SearchModule(ICacheProvider cache, ISettingsService<CouchPotatoSettings> cpSettings,
public SearchModule(ICacheProvider cache,
ISettingsService<PlexRequestSettings> prSettings, IAvailabilityChecker checker,
IRequestService request, ISonarrApi sonarrApi, ISettingsService<SonarrSettings> sonarrSettings,
ISettingsService<SickRageSettings> sickRageService, ICouchPotatoApi cpApi, ISickRageApi srApi,
ISettingsService<SickRageSettings> sickRageService, ISickRageApi srApi,
INotificationService notify, IMusicBrainzApi mbApi, IHeadphonesApi hpApi,
ISettingsService<HeadphonesSettings> hpService,
ICouchPotatoCacher cpCacher, ISonarrCacher sonarrCacher, ISickRageCacher sickRageCacher, IPlexApi plexApi,
ICouchPotatoCacher cpCacher, IWatcherCacher watcherCacher, ISonarrCacher sonarrCacher, ISickRageCacher sickRageCacher, IPlexApi plexApi,
ISettingsService<PlexSettings> plexService, ISettingsService<AuthenticationSettings> auth,
IRepository<UsersToNotify> u, ISettingsService<EmailNotificationSettings> email,
IIssueService issue, IAnalytics a, IRepository<RequestLimit> rl, ITransientFaultQueue tfQueue, IRepository<PlexContent> content, ISecurityExtensions security)
IIssueService issue, IAnalytics a, IRepository<RequestLimit> rl, ITransientFaultQueue tfQueue, IRepository<PlexContent> content,
ISecurityExtensions security, IMovieSender movieSender)
: base("search", prSettings, security)
{
Auth = auth;
PlexService = plexService;
PlexApi = plexApi;
CpService = cpSettings;
PrService = prSettings;
MovieApi = new TheMovieDbApi();
Cache = cache;
@ -92,7 +92,6 @@ namespace Ombi.UI.Modules
RequestService = request;
SonarrApi = sonarrApi;
SonarrService = sonarrSettings;
CouchPotatoApi = cpApi;
SickRageService = sickRageService;
SickrageApi = srApi;
NotificationService = notify;
@ -107,7 +106,8 @@ namespace Ombi.UI.Modules
FaultQueue = tfQueue;
TvApi = new TvMazeApi();
PlexContentRepository = content;
MovieSender = movieSender;
WatcherCacher = watcherCacher;
Get["SearchIndex", "/", true] = async (x, ct) => await RequestLoad();
@ -128,20 +128,19 @@ namespace Ombi.UI.Modules
Get["/seasons"] = x => GetSeasons();
Get["/episodes", true] = async (x, ct) => await GetEpisodes();
}
private IWatcherCacher WatcherCacher { get; }
private IMovieSender MovieSender { get; }
private IRepository<PlexContent> PlexContentRepository { get; }
private TvMazeApi TvApi { get; }
private IPlexApi PlexApi { get; }
private TheMovieDbApi MovieApi { get; }
private INotificationService NotificationService { get; }
private ICouchPotatoApi CouchPotatoApi { get; }
private ISonarrApi SonarrApi { get; }
private ISickRageApi SickrageApi { get; }
private IRequestService RequestService { get; }
private ICacheProvider Cache { get; }
private ISettingsService<AuthenticationSettings> Auth { get; }
private ISettingsService<PlexSettings> PlexService { get; }
private ISettingsService<CouchPotatoSettings> CpService { get; }
private ISettingsService<PlexRequestSettings> PrService { get; }
private ISettingsService<SonarrSettings> SonarrService { get; }
private ISettingsService<SickRageSettings> SickRageService { get; }
@ -236,9 +235,9 @@ namespace Ombi.UI.Modules
var cpCached = CpCacher.QueuedIds();
var watcherCached = WatcherCacher.QueuedIds();
var content = PlexContentRepository.GetAll();
var plexMovies = Checker.GetPlexMovies(content);
var settings = await PrService.GetSettingsAsync();
var viewMovies = new List<SearchMovieViewModel>();
var counter = 0;
foreach (var movie in apiMovies)
@ -290,6 +289,10 @@ namespace Ombi.UI.Modules
{
viewMovie.Requested = true;
}
else if(watcherCached.Contains(imdbId) && canSee) // compare to the watcher db
{
viewMovie.Requested = true;
}
viewMovies.Add(viewMovie);
}
@ -564,30 +567,25 @@ namespace Ombi.UI.Modules
{
if (ShouldAutoApprove(RequestType.Movie, settings, Username))
{
var cpSettings = await CpService.GetSettingsAsync();
model.Approved = true;
if (cpSettings.Enabled)
var result = await MovieSender.Send(model);
if (result.Result)
{
return await AddRequest(model, settings,
$"{fullMovieName} {Resources.UI.Search_SuccessfullyAdded}");
}
if (!result.MovieSendingEnabled)
{
Log.Info("Adding movie to CP (No approval required)");
var result = CouchPotatoApi.AddMovie(model.ImdbId, cpSettings.ApiKey, model.Title,
cpSettings.FullUri, cpSettings.ProfileId);
Log.Debug("Adding movie to CP result {0}", result);
if (result)
{
return
await
AddRequest(model, settings,
$"{fullMovieName} {Resources.UI.Search_SuccessfullyAdded}");
}
return Response.AsJson(new JsonResponseModel
{
Result = false,
Message = Resources.UI.Search_CouchPotatoError
});
model.Approved = true;
return await AddRequest(model, settings, $"{fullMovieName} {Resources.UI.Search_SuccessfullyAdded}");
}
model.Approved = true;
return await AddRequest(model, settings, $"{fullMovieName} {Resources.UI.Search_SuccessfullyAdded}");
return Response.AsJson(new JsonResponseModel
{
Result = false,
Message = Resources.UI.Search_CouchPotatoError
});
}

@ -43,6 +43,7 @@ namespace Ombi.UI.NinjectModules
{
Bind<IAvailabilityChecker>().To<PlexAvailabilityChecker>();
Bind<ICouchPotatoCacher>().To<CouchPotatoCacher>();
Bind<IWatcherCacher>().To<IWatcherCacher>();
Bind<ISonarrCacher>().To<SonarrCacher>();
Bind<ISickRageCacher>().To<SickRageCacher>();
Bind<IRecentlyAdded>().To<RecentlyAdded>();

@ -251,6 +251,7 @@
<Compile Include="Models\UserManagement\UserUpdateViewModel.cs" />
<Compile Include="Modules\Admin\AboutModule.cs" />
<Compile Include="Modules\Admin\CustomizationModule.cs" />
<Compile Include="Modules\Admin\IntegrationModule.cs" />
<Compile Include="Modules\Admin\UserManagementSettingsModule.cs" />
<Compile Include="Modules\Admin\FaultQueueModule.cs" />
<Compile Include="Modules\Admin\SystemStatusModule.cs" />
@ -282,6 +283,7 @@
</Compile>
<Compile Include="Start\StartupOptions.cs" />
<Compile Include="Start\UpdateValue.cs" />
<Compile Include="Validators\WatcherValidator.cs" />
<Compile Include="Validators\SlackSettingsValidator.cs" />
<Compile Include="Validators\UserViewModelValidator.cs" />
<Compile Include="Validators\HeadphonesValidator.cs" />
@ -761,6 +763,9 @@
<Content Include="Views\About\About.cshtml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Views\Integration\Watcher.cshtml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<None Include="Views\Admin\NewsletterSettings.cshtml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>

@ -0,0 +1,43 @@

#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: SonarrValidator.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using FluentValidation;
using Ombi.Core.SettingModels;
namespace Ombi.UI.Validators
{
public class WatcherValidator : AbstractValidator<WatcherSettings>
{
public WatcherValidator()
{
RuleFor(request => request.Ip).NotEmpty().WithMessage("You must specify a IP/Host name.");
RuleFor(request => request.Port).NotEmpty().WithMessage("You must specify a Port.");
RuleFor(request => request.ApiKey).NotEmpty().WithMessage("You must specify a Api Key.");
}
}
}

@ -0,0 +1,133 @@
@using Ombi.UI.Helpers
@inherits Nancy.ViewEngines.Razor.NancyRazorViewBase<Ombi.Core.SettingModels.WatcherSettings>
@Html.Partial("Shared/Partial/_Sidebar")
@{
int port;
if (Model.Port == 0)
{
port = 9090;
}
else
{
port = Model.Port;
}
}
<div class="col-sm-8 col-sm-push-1">
<form class="form-horizontal" method="POST" id="mainForm">
<fieldset>
<legend>Watcher Settings</legend>
@Html.Checkbox(Model.Enabled, "Enabled", "Enabled")
<div class="form-group">
<label for="Ip" class="control-label">Watcher Hostname or IP</label>
<div class="">
<input type="text" class="form-control form-control-custom " id="Ip" name="Ip" placeholder="localhost" value="@Model.Ip">
</div>
</div>
<div class="form-group">
<label for="portNumber" class="control-label">Port</label>
<div class="">
<input type="text" class="form-control form-control-custom " id="portNumber" name="Port" placeholder="Port Number" value="@port">
</div>
</div>
<div class="form-group">
<label for="ApiKey" class="control-label">Watcher API Key</label>
<div>
<input type="text" class="form-control form-control-custom " id="ApiKey" name="ApiKey" value="@Model.ApiKey">
</div>
</div>
@Html.Checkbox(Model.Ssl, "Ssl", "SSL")
<div class="form-group">
<label for="SubDir" class="control-label">Watcher Base Url</label>
<div>
<input type="text" class="form-control form-control-custom " id="SubDir" name="SubDir" value="@Model.SubDir">
</div>
</div>
<br />
<div class="form-group">
<div>
<button id="testWatcher" type="submit" class="btn btn-primary-outline">Test Connectivity <div id="spinner"> </div></button>
</div>
</div>
<div class="form-group">
<div>
<button id="save" type="submit" class="btn btn-primary-outline">Submit</button>
</div>
</div>
</fieldset>
</form>
</div>
<script>
$(function () {
var baseUrl = '@Html.GetBaseUrl()';
$('#testWatcher').click(function (e) {
e.preventDefault();
var $form = $("#mainForm");
var url = createBaseUrl(baseUrl, "/test/watcher");
$('#spinner').attr("class", "fa fa-spinner fa-spin");
$.ajax({
type: $form.prop("method"),
url: url,
data: $form.serialize(),
dataType: "json",
success: function (response) {
console.log(response);
if (response.result === true) {
$('#spinner').attr("class", "fa fa-check");
generateNotify(response.message, "success");
} else {
generateNotify(response.message, "warning");
$('#spinner').attr("class", "fa fa-times");
}
},
error: function (e) {
console.log(e);
generateNotify("Something went wrong!", "danger");
$('#spinner').attr("class", "fa fa-times");
}
});
});
$('#save').click(function (e) {
e.preventDefault();
var $form = $("#mainForm");
var data = $form.serialize();
$.ajax({
type: $form.prop("method"),
data: data,
url: $form.prop("action"),
dataType: "json",
success: function (response) {
if (response.result === true) {
generateNotify(response.message, "success");
} else {
generateNotify(response.message, "warning");
}
},
error: function (e) {
console.log(e);
generateNotify("Something went wrong!", "danger");
}
});
});
});
</script>

@ -9,6 +9,7 @@
@Html.GetSidebarUrl(Context, "/admin/usermanagementsettings", "User Management Settings")
@Html.GetSidebarUrl(Context, "/admin/plex", "Plex")
@Html.GetSidebarUrl(Context, "/admin/couchpotato", "CouchPotato")
@Html.GetSidebarUrl(Context, "/admin/watcher", "Watcher")
@Html.GetSidebarUrl(Context, "/admin/sonarr", "Sonarr")
@Html.GetSidebarUrl(Context, "/admin/sickrage", "SickRage")
@Html.GetSidebarUrl(Context, "/admin/headphones", "Headphones (Beta)")

Loading…
Cancel
Save