mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-08-19 21:03:17 -07:00
Merge branch 'develop' of https://github.com/tidusjar/ombi into develop
This commit is contained in:
commit
0a63dd0ef5
63 changed files with 1986 additions and 398 deletions
32
CHANGELOG.md
32
CHANGELOG.md
|
@ -1,6 +1,31 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## (unreleased)
|
## v3.0.3988 (2018-11-23)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### **New Features**
|
||||||
|
|
||||||
|
- Added Sonarr v3 #2359. [TidusJar]
|
||||||
|
|
||||||
|
### **Fixes**
|
||||||
|
|
||||||
|
- Fixed a potential security vulnerability. [Jamie]
|
||||||
|
|
||||||
|
- Sorted out some of the settings pages, trying to make it consistent. [Jamie]
|
||||||
|
|
||||||
|
- #2669 Fixed missing translations. [TidusJar]
|
||||||
|
|
||||||
|
- Maps alias email variable for welcome emails. [Victor Usoltsev]
|
||||||
|
|
||||||
|
- Increased the logo size on the landing page to match the container below it. [Jamie]
|
||||||
|
|
||||||
|
- Think the request queue is done! [Jamie]
|
||||||
|
|
||||||
|
- Finished off the job. [TidusJar]
|
||||||
|
|
||||||
|
|
||||||
|
## v3.0.3988 (2018-11-23)
|
||||||
|
|
||||||
### **New Features**
|
### **New Features**
|
||||||
|
|
||||||
|
@ -8,8 +33,13 @@
|
||||||
|
|
||||||
- Added the ability to get the ombi user via a Plex Token #2591. [Jamie]
|
- Added the ability to get the ombi user via a Plex Token #2591. [Jamie]
|
||||||
|
|
||||||
|
- Update CHANGELOG.md. [Jamie]
|
||||||
|
|
||||||
### **Fixes**
|
### **Fixes**
|
||||||
|
|
||||||
|
|
||||||
|
- Fixed #2601 [TidusJar]
|
||||||
|
|
||||||
- Made the subscribe/unsubscribe button more obvious on the UI #2309. [Jamie]
|
- Made the subscribe/unsubscribe button more obvious on the UI #2309. [Jamie]
|
||||||
|
|
||||||
- Fixed #2603. [Jamie]
|
- Fixed #2603. [Jamie]
|
||||||
|
|
|
@ -32,7 +32,7 @@ ___
|
||||||
# Features
|
# Features
|
||||||
Here are some of the features Ombi V3 has:
|
Here are some of the features Ombi V3 has:
|
||||||
* Now working without crashes on Linux.
|
* Now working without crashes on Linux.
|
||||||
* Lets users request Movies and TV Shows (whether it being the entire series, an entire season, or even single episodes.)
|
* Lets users request Movies, Music, and TV Shows (whether it being the entire series, an entire season, or even single episodes.)
|
||||||
* Easily manage your requests
|
* Easily manage your requests
|
||||||
* User management system (supports plex.tv, Emby and local accounts)
|
* User management system (supports plex.tv, Emby and local accounts)
|
||||||
* A landing page that will give you the availability of your Plex/Emby server and also add custom notification text to inform your users of downtime.
|
* A landing page that will give you the availability of your Plex/Emby server and also add custom notification text to inform your users of downtime.
|
||||||
|
@ -50,6 +50,7 @@ We integrate with the following applications:
|
||||||
* Emby
|
* Emby
|
||||||
* Sonarr
|
* Sonarr
|
||||||
* Radarr
|
* Radarr
|
||||||
|
* Lidarr
|
||||||
* DogNzb
|
* DogNzb
|
||||||
* Couch Potato
|
* Couch Potato
|
||||||
|
|
||||||
|
@ -87,6 +88,7 @@ We are planning to bring back these features in V3 but for now you can find a li
|
||||||
| DogNzb | Yes | No |
|
| DogNzb | Yes | No |
|
||||||
| Issues | Yes | Yes |
|
| Issues | Yes | Yes |
|
||||||
| Headphones | No | Yes |
|
| Headphones | No | Yes |
|
||||||
|
| Lidarr | Yes | No |
|
||||||
|
|
||||||
# Feature Requests
|
# Feature Requests
|
||||||
Feature requests are handled on FeatHub.
|
Feature requests are handled on FeatHub.
|
||||||
|
|
|
@ -24,16 +24,5 @@ namespace Ombi.Api.Github
|
||||||
request.AddHeader("User-Agent", "Ombi");
|
request.AddHeader("User-Agent", "Ombi");
|
||||||
return await _api.Request<List<CakeThemes>>(request);
|
return await _api.Request<List<CakeThemes>>(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> GetThemesRawContent(string url)
|
|
||||||
{
|
|
||||||
var sections = url.Split('/');
|
|
||||||
var lastPart = sections.Last();
|
|
||||||
url = url.Replace(lastPart, string.Empty);
|
|
||||||
var request = new Request(lastPart, url, HttpMethod.Get);
|
|
||||||
request.AddHeader("Accept", "application/vnd.github.v3+json");
|
|
||||||
request.AddHeader("User-Agent", "Ombi");
|
|
||||||
return await _api.RequestContent(request);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,5 @@ namespace Ombi.Api.Github
|
||||||
public interface IGithubApi
|
public interface IGithubApi
|
||||||
{
|
{
|
||||||
Task<List<CakeThemes>> GetCakeThemes();
|
Task<List<CakeThemes>> GetCakeThemes();
|
||||||
Task<string> GetThemesRawContent(string url);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -11,7 +11,7 @@ using Ombi.Core.Models;
|
||||||
using Ombi.Core.Models.UI;
|
using Ombi.Core.Models.UI;
|
||||||
using Ombi.Core.Rule.Interfaces;
|
using Ombi.Core.Rule.Interfaces;
|
||||||
using Ombi.Core.Settings;
|
using Ombi.Core.Settings;
|
||||||
using Ombi.Schedule.Jobs.Ombi;
|
using Ombi.Helpers;
|
||||||
using Ombi.Settings.Settings.Models;
|
using Ombi.Settings.Settings.Models;
|
||||||
using Ombi.Store.Entities;
|
using Ombi.Store.Entities;
|
||||||
using Ombi.Store.Repository;
|
using Ombi.Store.Repository;
|
||||||
|
@ -114,7 +114,7 @@ namespace Ombi.Core.Engine
|
||||||
foreach (var epInformation in childRequests.SeasonRequests.OrderBy(x => x.SeasonNumber))
|
foreach (var epInformation in childRequests.SeasonRequests.OrderBy(x => x.SeasonNumber))
|
||||||
{
|
{
|
||||||
var orderedEpisodes = epInformation.Episodes.OrderBy(x => x.EpisodeNumber).ToList();
|
var orderedEpisodes = epInformation.Episodes.OrderBy(x => x.EpisodeNumber).ToList();
|
||||||
var episodeString = NewsletterJob.BuildEpisodeList(orderedEpisodes.Select(x => x.EpisodeNumber));
|
var episodeString = StringHelper.BuildEpisodeList(orderedEpisodes.Select(x => x.EpisodeNumber));
|
||||||
finalsb.Append($"Season: {epInformation.SeasonNumber} - Episodes: {episodeString}");
|
finalsb.Append($"Season: {epInformation.SeasonNumber} - Episodes: {episodeString}");
|
||||||
finalsb.Append("<br />");
|
finalsb.Append("<br />");
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,16 +20,18 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Ombi.Api.CouchPotato\Ombi.Api.CouchPotato.csproj" />
|
||||||
<ProjectReference Include="..\Ombi.Api.DogNzb\Ombi.Api.DogNzb.csproj" />
|
<ProjectReference Include="..\Ombi.Api.DogNzb\Ombi.Api.DogNzb.csproj" />
|
||||||
|
<ProjectReference Include="..\Ombi.Api.Emby\Ombi.Api.Emby.csproj" />
|
||||||
<ProjectReference Include="..\Ombi.Api.Lidarr\Ombi.Api.Lidarr.csproj" />
|
<ProjectReference Include="..\Ombi.Api.Lidarr\Ombi.Api.Lidarr.csproj" />
|
||||||
<ProjectReference Include="..\Ombi.Api.Plex\Ombi.Api.Plex.csproj" />
|
<ProjectReference Include="..\Ombi.Api.Plex\Ombi.Api.Plex.csproj" />
|
||||||
|
<ProjectReference Include="..\Ombi.Api.Radarr\Ombi.Api.Radarr.csproj" />
|
||||||
<ProjectReference Include="..\Ombi.Api.SickRage\Ombi.Api.SickRage.csproj" />
|
<ProjectReference Include="..\Ombi.Api.SickRage\Ombi.Api.SickRage.csproj" />
|
||||||
<ProjectReference Include="..\Ombi.Api.Sonarr\Ombi.Api.Sonarr.csproj" />
|
<ProjectReference Include="..\Ombi.Api.Sonarr\Ombi.Api.Sonarr.csproj" />
|
||||||
<ProjectReference Include="..\Ombi.Api.Trakt\Ombi.Api.Trakt.csproj" />
|
<ProjectReference Include="..\Ombi.Api.Trakt\Ombi.Api.Trakt.csproj" />
|
||||||
<ProjectReference Include="..\Ombi.Api.TvMaze\Ombi.Api.TvMaze.csproj" />
|
<ProjectReference Include="..\Ombi.Api.TvMaze\Ombi.Api.TvMaze.csproj" />
|
||||||
<ProjectReference Include="..\Ombi.Helpers\Ombi.Helpers.csproj" />
|
<ProjectReference Include="..\Ombi.Helpers\Ombi.Helpers.csproj" />
|
||||||
<ProjectReference Include="..\Ombi.Notifications\Ombi.Notifications.csproj" />
|
<ProjectReference Include="..\Ombi.Notifications\Ombi.Notifications.csproj" />
|
||||||
<ProjectReference Include="..\Ombi.Schedule\Ombi.Schedule.csproj" />
|
|
||||||
<ProjectReference Include="..\Ombi.Settings\Ombi.Settings.csproj" />
|
<ProjectReference Include="..\Ombi.Settings\Ombi.Settings.csproj" />
|
||||||
<ProjectReference Include="..\Ombi.Store\Ombi.Store.csproj" />
|
<ProjectReference Include="..\Ombi.Store\Ombi.Store.csproj" />
|
||||||
<ProjectReference Include="..\Ombi.TheMovieDbApi\Ombi.Api.TheMovieDb.csproj" />
|
<ProjectReference Include="..\Ombi.TheMovieDbApi\Ombi.Api.TheMovieDb.csproj" />
|
||||||
|
|
|
@ -20,7 +20,7 @@ namespace Ombi.Core.Senders
|
||||||
{
|
{
|
||||||
public MovieSender(ISettingsService<RadarrSettings> radarrSettings, IRadarrApi api, ILogger<MovieSender> log,
|
public MovieSender(ISettingsService<RadarrSettings> radarrSettings, IRadarrApi api, ILogger<MovieSender> log,
|
||||||
ISettingsService<DogNzbSettings> dogSettings, IDogNzbApi dogApi, ISettingsService<CouchPotatoSettings> cpSettings,
|
ISettingsService<DogNzbSettings> dogSettings, IDogNzbApi dogApi, ISettingsService<CouchPotatoSettings> cpSettings,
|
||||||
ICouchPotatoApi cpApi, IRepository<UserQualityProfiles> userProfiles)
|
ICouchPotatoApi cpApi, IRepository<UserQualityProfiles> userProfiles, IRepository<RequestQueue> requestQueue, INotificationHelper notify)
|
||||||
{
|
{
|
||||||
RadarrSettings = radarrSettings;
|
RadarrSettings = radarrSettings;
|
||||||
RadarrApi = api;
|
RadarrApi = api;
|
||||||
|
@ -30,6 +30,8 @@ namespace Ombi.Core.Senders
|
||||||
CouchPotatoSettings = cpSettings;
|
CouchPotatoSettings = cpSettings;
|
||||||
CouchPotatoApi = cpApi;
|
CouchPotatoApi = cpApi;
|
||||||
_userProfiles = userProfiles;
|
_userProfiles = userProfiles;
|
||||||
|
_requestQueuRepository = requestQueue;
|
||||||
|
_notificationHelper = notify;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ISettingsService<RadarrSettings> RadarrSettings { get; }
|
private ISettingsService<RadarrSettings> RadarrSettings { get; }
|
||||||
|
@ -40,9 +42,14 @@ namespace Ombi.Core.Senders
|
||||||
private ISettingsService<CouchPotatoSettings> CouchPotatoSettings { get; }
|
private ISettingsService<CouchPotatoSettings> CouchPotatoSettings { get; }
|
||||||
private ICouchPotatoApi CouchPotatoApi { get; }
|
private ICouchPotatoApi CouchPotatoApi { get; }
|
||||||
private readonly IRepository<UserQualityProfiles> _userProfiles;
|
private readonly IRepository<UserQualityProfiles> _userProfiles;
|
||||||
|
private readonly IRepository<RequestQueue> _requestQueuRepository;
|
||||||
|
private readonly INotificationHelper _notificationHelper;
|
||||||
|
|
||||||
public async Task<SenderResult> Send(MovieRequests model)
|
public async Task<SenderResult> Send(MovieRequests model)
|
||||||
{
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
|
||||||
var cpSettings = await CouchPotatoSettings.GetSettingsAsync();
|
var cpSettings = await CouchPotatoSettings.GetSettingsAsync();
|
||||||
//var watcherSettings = await WatcherSettings.GetSettingsAsync();
|
//var watcherSettings = await WatcherSettings.GetSettingsAsync();
|
||||||
var radarrSettings = await RadarrSettings.GetSettingsAsync();
|
var radarrSettings = await RadarrSettings.GetSettingsAsync();
|
||||||
|
@ -66,12 +73,32 @@ namespace Ombi.Core.Senders
|
||||||
{
|
{
|
||||||
return await SendToCp(model, cpSettings, cpSettings.DefaultProfileId);
|
return await SendToCp(model, cpSettings, cpSettings.DefaultProfileId);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.LogError(e, "Error when seing movie to DVR app, added to the request queue");
|
||||||
|
|
||||||
//if (watcherSettings.Enabled)
|
// Check if already in request quee
|
||||||
//{
|
var existingQueue = await _requestQueuRepository.FirstOrDefaultAsync(x => x.RequestId == model.Id);
|
||||||
// return SendToWatcher(model, watcherSettings);
|
if (existingQueue != null)
|
||||||
//}
|
{
|
||||||
|
existingQueue.RetryCount++;
|
||||||
|
existingQueue.Error = e.Message;
|
||||||
|
await _requestQueuRepository.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await _requestQueuRepository.Add(new RequestQueue
|
||||||
|
{
|
||||||
|
Dts = DateTime.UtcNow,
|
||||||
|
Error = e.Message,
|
||||||
|
RequestId = model.Id,
|
||||||
|
Type = RequestType.Movie,
|
||||||
|
RetryCount = 0
|
||||||
|
});
|
||||||
|
_notificationHelper.Notify(model, NotificationType.ItemAddedToFaultQueue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return new SenderResult
|
return new SenderResult
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,29 +1,40 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using Ombi.Api.Lidarr;
|
using Ombi.Api.Lidarr;
|
||||||
using Ombi.Api.Lidarr.Models;
|
using Ombi.Api.Lidarr.Models;
|
||||||
using Ombi.Api.Radarr;
|
|
||||||
using Ombi.Core.Settings;
|
using Ombi.Core.Settings;
|
||||||
using Ombi.Helpers;
|
using Ombi.Helpers;
|
||||||
using Ombi.Settings.Settings.Models.External;
|
using Ombi.Settings.Settings.Models.External;
|
||||||
|
using Ombi.Store.Entities;
|
||||||
using Ombi.Store.Entities.Requests;
|
using Ombi.Store.Entities.Requests;
|
||||||
using Serilog;
|
using Ombi.Store.Repository;
|
||||||
|
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||||
|
|
||||||
namespace Ombi.Core.Senders
|
namespace Ombi.Core.Senders
|
||||||
{
|
{
|
||||||
public class MusicSender : IMusicSender
|
public class MusicSender : IMusicSender
|
||||||
{
|
{
|
||||||
public MusicSender(ISettingsService<LidarrSettings> lidarr, ILidarrApi lidarrApi)
|
public MusicSender(ISettingsService<LidarrSettings> lidarr, ILidarrApi lidarrApi, ILogger<MusicSender> log,
|
||||||
|
IRepository<RequestQueue> requestQueue, INotificationHelper notify)
|
||||||
{
|
{
|
||||||
_lidarrSettings = lidarr;
|
_lidarrSettings = lidarr;
|
||||||
_lidarrApi = lidarrApi;
|
_lidarrApi = lidarrApi;
|
||||||
|
_log = log;
|
||||||
|
_requestQueueRepository = requestQueue;
|
||||||
|
_notificationHelper = notify;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly ISettingsService<LidarrSettings> _lidarrSettings;
|
private readonly ISettingsService<LidarrSettings> _lidarrSettings;
|
||||||
private readonly ILidarrApi _lidarrApi;
|
private readonly ILidarrApi _lidarrApi;
|
||||||
|
private readonly ILogger _log;
|
||||||
|
private readonly IRepository<RequestQueue> _requestQueueRepository;
|
||||||
|
private readonly INotificationHelper _notificationHelper;
|
||||||
|
|
||||||
public async Task<SenderResult> Send(AlbumRequest model)
|
public async Task<SenderResult> Send(AlbumRequest model)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
var settings = await _lidarrSettings.GetSettingsAsync();
|
var settings = await _lidarrSettings.GetSettingsAsync();
|
||||||
if (settings.Enabled)
|
if (settings.Enabled)
|
||||||
|
@ -33,6 +44,33 @@ namespace Ombi.Core.Senders
|
||||||
|
|
||||||
return new SenderResult { Success = false, Sent = false, Message = "Lidarr is not enabled" };
|
return new SenderResult { Success = false, Sent = false, Message = "Lidarr is not enabled" };
|
||||||
}
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_log.LogError(e, "Exception thrown when sending a music to DVR app, added to the request queue");
|
||||||
|
var existingQueue = await _requestQueueRepository.FirstOrDefaultAsync(x => x.RequestId == model.Id);
|
||||||
|
if (existingQueue != null)
|
||||||
|
{
|
||||||
|
existingQueue.RetryCount++;
|
||||||
|
existingQueue.Error = e.Message;
|
||||||
|
await _requestQueueRepository.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await _requestQueueRepository.Add(new RequestQueue
|
||||||
|
{
|
||||||
|
Dts = DateTime.UtcNow,
|
||||||
|
Error = e.Message,
|
||||||
|
RequestId = model.Id,
|
||||||
|
Type = RequestType.Album,
|
||||||
|
RetryCount = 0
|
||||||
|
});
|
||||||
|
_notificationHelper.Notify(model, NotificationType.ItemAddedToFaultQueue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return new SenderResult { Success = false, Sent = false, Message = "Something went wrong!" };
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<SenderResult> SendToLidarr(AlbumRequest model, LidarrSettings settings)
|
private async Task<SenderResult> SendToLidarr(AlbumRequest model, LidarrSettings settings)
|
||||||
{
|
{
|
||||||
|
|
|
@ -23,7 +23,7 @@ namespace Ombi.Core.Senders
|
||||||
{
|
{
|
||||||
public TvSender(ISonarrApi sonarrApi, ISonarrV3Api sonarrV3Api, ILogger<TvSender> log, ISettingsService<SonarrSettings> sonarrSettings,
|
public TvSender(ISonarrApi sonarrApi, ISonarrV3Api sonarrV3Api, ILogger<TvSender> log, ISettingsService<SonarrSettings> sonarrSettings,
|
||||||
ISettingsService<DogNzbSettings> dog, IDogNzbApi dogApi, ISettingsService<SickRageSettings> srSettings,
|
ISettingsService<DogNzbSettings> dog, IDogNzbApi dogApi, ISettingsService<SickRageSettings> srSettings,
|
||||||
ISickRageApi srApi, IRepository<UserQualityProfiles> userProfiles)
|
ISickRageApi srApi, IRepository<UserQualityProfiles> userProfiles, IRepository<RequestQueue> requestQueue, INotificationHelper notify)
|
||||||
{
|
{
|
||||||
SonarrApi = sonarrApi;
|
SonarrApi = sonarrApi;
|
||||||
SonarrV3Api = sonarrV3Api;
|
SonarrV3Api = sonarrV3Api;
|
||||||
|
@ -34,6 +34,8 @@ namespace Ombi.Core.Senders
|
||||||
SickRageSettings = srSettings;
|
SickRageSettings = srSettings;
|
||||||
SickRageApi = srApi;
|
SickRageApi = srApi;
|
||||||
UserQualityProfiles = userProfiles;
|
UserQualityProfiles = userProfiles;
|
||||||
|
_requestQueueRepository = requestQueue;
|
||||||
|
_notificationHelper = notify;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ISonarrApi SonarrApi { get; }
|
private ISonarrApi SonarrApi { get; }
|
||||||
|
@ -45,8 +47,12 @@ namespace Ombi.Core.Senders
|
||||||
private ISettingsService<DogNzbSettings> DogNzbSettings { get; }
|
private ISettingsService<DogNzbSettings> DogNzbSettings { get; }
|
||||||
private ISettingsService<SickRageSettings> SickRageSettings { get; }
|
private ISettingsService<SickRageSettings> SickRageSettings { get; }
|
||||||
private IRepository<UserQualityProfiles> UserQualityProfiles { get; }
|
private IRepository<UserQualityProfiles> UserQualityProfiles { get; }
|
||||||
|
private readonly IRepository<RequestQueue> _requestQueueRepository;
|
||||||
|
private readonly INotificationHelper _notificationHelper;
|
||||||
|
|
||||||
public async Task<SenderResult> Send(ChildRequests model)
|
public async Task<SenderResult> Send(ChildRequests model)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
var sonarr = await SonarrSettings.GetSettingsAsync();
|
var sonarr = await SonarrSettings.GetSettingsAsync();
|
||||||
if (sonarr.Enabled)
|
if (sonarr.Enabled)
|
||||||
|
@ -100,6 +106,37 @@ namespace Ombi.Core.Senders
|
||||||
Success = true
|
Success = true
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.LogError(e, "Exception thrown when sending a movie to DVR app, added to the request queue");
|
||||||
|
// Check if already in request quee
|
||||||
|
var existingQueue = await _requestQueueRepository.FirstOrDefaultAsync(x => x.RequestId == model.Id);
|
||||||
|
if (existingQueue != null)
|
||||||
|
{
|
||||||
|
existingQueue.RetryCount++;
|
||||||
|
existingQueue.Error = e.Message;
|
||||||
|
await _requestQueueRepository.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await _requestQueueRepository.Add(new RequestQueue
|
||||||
|
{
|
||||||
|
Dts = DateTime.UtcNow,
|
||||||
|
Error = e.Message,
|
||||||
|
RequestId = model.Id,
|
||||||
|
Type = RequestType.TvShow,
|
||||||
|
RetryCount = 0
|
||||||
|
});
|
||||||
|
_notificationHelper.Notify(model, NotificationType.ItemAddedToFaultQueue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new SenderResult
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Message = "Something wen't wrong!"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<DogNzbAddResult> SendToDogNzb(ChildRequests model, DogNzbSettings settings)
|
private async Task<DogNzbAddResult> SendToDogNzb(ChildRequests model, DogNzbSettings settings)
|
||||||
{
|
{
|
||||||
|
@ -177,7 +214,6 @@ namespace Ombi.Core.Senders
|
||||||
qualityToUse = model.ParentRequest.QualityOverride.Value;
|
qualityToUse = model.ParentRequest.QualityOverride.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Are we using v3 sonarr?
|
// Are we using v3 sonarr?
|
||||||
var sonarrV3 = s.V3;
|
var sonarrV3 = s.V3;
|
||||||
var languageProfileId = s.LanguageProfile;
|
var languageProfileId = s.LanguageProfile;
|
||||||
|
|
|
@ -199,6 +199,7 @@ namespace Ombi.DependencyInjection
|
||||||
services.AddTransient<ILidarrArtistSync, LidarrArtistSync>();
|
services.AddTransient<ILidarrArtistSync, LidarrArtistSync>();
|
||||||
services.AddTransient<ILidarrAvailabilityChecker, LidarrAvailabilityChecker>();
|
services.AddTransient<ILidarrAvailabilityChecker, LidarrAvailabilityChecker>();
|
||||||
services.AddTransient<IIssuesPurge, IssuesPurge>();
|
services.AddTransient<IIssuesPurge, IssuesPurge>();
|
||||||
|
services.AddTransient<IResendFailedRequests, ResendFailedRequests>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Security;
|
using System.Security;
|
||||||
|
@ -76,6 +77,49 @@ namespace Ombi.Helpers
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string BuildEpisodeList(IEnumerable<int> orderedEpisodes)
|
||||||
|
{
|
||||||
|
var epSb = new StringBuilder();
|
||||||
|
var previousEpisodes = new List<int>();
|
||||||
|
var previousEpisode = -1;
|
||||||
|
foreach (var ep in orderedEpisodes)
|
||||||
|
{
|
||||||
|
if (ep - 1 == previousEpisode)
|
||||||
|
{
|
||||||
|
// This is the next one
|
||||||
|
previousEpisodes.Add(ep);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (previousEpisodes.Count > 1)
|
||||||
|
{
|
||||||
|
// End it
|
||||||
|
epSb.Append($"{previousEpisodes.First()}-{previousEpisodes.Last()}, ");
|
||||||
|
}
|
||||||
|
else if (previousEpisodes.Count == 1)
|
||||||
|
{
|
||||||
|
epSb.Append($"{previousEpisodes.FirstOrDefault()}, ");
|
||||||
|
}
|
||||||
|
// New one
|
||||||
|
previousEpisodes.Clear();
|
||||||
|
previousEpisodes.Add(ep);
|
||||||
|
}
|
||||||
|
previousEpisode = ep;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (previousEpisodes.Count > 1)
|
||||||
|
{
|
||||||
|
// Got some left over
|
||||||
|
epSb.Append($"{previousEpisodes.First()}-{previousEpisodes.Last()}");
|
||||||
|
}
|
||||||
|
else if (previousEpisodes.Count == 1)
|
||||||
|
{
|
||||||
|
epSb.Append(previousEpisodes.FirstOrDefault());
|
||||||
|
}
|
||||||
|
|
||||||
|
return epSb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
public static string RemoveSpaces(this string str)
|
public static string RemoveSpaces(this string str)
|
||||||
{
|
{
|
||||||
return str.Replace(" ", "");
|
return str.Replace(" ", "");
|
||||||
|
|
|
@ -6,7 +6,6 @@ using Ombi.Api.Discord;
|
||||||
using Ombi.Api.Discord.Models;
|
using Ombi.Api.Discord.Models;
|
||||||
using Ombi.Core.Settings;
|
using Ombi.Core.Settings;
|
||||||
using Ombi.Helpers;
|
using Ombi.Helpers;
|
||||||
using Ombi.Notifications.Interfaces;
|
|
||||||
using Ombi.Notifications.Models;
|
using Ombi.Notifications.Models;
|
||||||
using Ombi.Settings.Settings.Models;
|
using Ombi.Settings.Settings.Models;
|
||||||
using Ombi.Settings.Settings.Models.Notifications;
|
using Ombi.Settings.Settings.Models.Notifications;
|
||||||
|
|
|
@ -8,7 +8,6 @@ using Microsoft.Extensions.Logging;
|
||||||
using MimeKit;
|
using MimeKit;
|
||||||
using Ombi.Core.Settings;
|
using Ombi.Core.Settings;
|
||||||
using Ombi.Helpers;
|
using Ombi.Helpers;
|
||||||
using Ombi.Notifications.Interfaces;
|
|
||||||
using Ombi.Notifications.Models;
|
using Ombi.Notifications.Models;
|
||||||
using Ombi.Notifications.Templates;
|
using Ombi.Notifications.Templates;
|
||||||
using Ombi.Settings.Settings.Models;
|
using Ombi.Settings.Settings.Models;
|
||||||
|
|
|
@ -2,13 +2,10 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Ombi.Api.Discord;
|
|
||||||
using Ombi.Api.Discord.Models;
|
|
||||||
using Ombi.Api.Mattermost;
|
using Ombi.Api.Mattermost;
|
||||||
using Ombi.Api.Mattermost.Models;
|
using Ombi.Api.Mattermost.Models;
|
||||||
using Ombi.Core.Settings;
|
using Ombi.Core.Settings;
|
||||||
using Ombi.Helpers;
|
using Ombi.Helpers;
|
||||||
using Ombi.Notifications.Interfaces;
|
|
||||||
using Ombi.Notifications.Models;
|
using Ombi.Notifications.Models;
|
||||||
using Ombi.Settings.Settings.Models;
|
using Ombi.Settings.Settings.Models;
|
||||||
using Ombi.Settings.Settings.Models.Notifications;
|
using Ombi.Settings.Settings.Models.Notifications;
|
||||||
|
|
|
@ -8,7 +8,6 @@ using Microsoft.Extensions.Logging;
|
||||||
using Ombi.Api.Notifications;
|
using Ombi.Api.Notifications;
|
||||||
using Ombi.Core.Settings;
|
using Ombi.Core.Settings;
|
||||||
using Ombi.Helpers;
|
using Ombi.Helpers;
|
||||||
using Ombi.Notifications.Interfaces;
|
|
||||||
using Ombi.Notifications.Models;
|
using Ombi.Notifications.Models;
|
||||||
using Ombi.Settings.Settings.Models;
|
using Ombi.Settings.Settings.Models;
|
||||||
using Ombi.Settings.Settings.Models.Notifications;
|
using Ombi.Settings.Settings.Models.Notifications;
|
||||||
|
|
|
@ -4,7 +4,6 @@ using Microsoft.Extensions.Logging;
|
||||||
using Ombi.Api.Pushbullet;
|
using Ombi.Api.Pushbullet;
|
||||||
using Ombi.Core.Settings;
|
using Ombi.Core.Settings;
|
||||||
using Ombi.Helpers;
|
using Ombi.Helpers;
|
||||||
using Ombi.Notifications.Interfaces;
|
|
||||||
using Ombi.Notifications.Models;
|
using Ombi.Notifications.Models;
|
||||||
using Ombi.Settings.Settings.Models;
|
using Ombi.Settings.Settings.Models;
|
||||||
using Ombi.Settings.Settings.Models.Notifications;
|
using Ombi.Settings.Settings.Models.Notifications;
|
||||||
|
|
|
@ -5,7 +5,6 @@ using Ombi.Api.Pushbullet;
|
||||||
using Ombi.Api.Pushover;
|
using Ombi.Api.Pushover;
|
||||||
using Ombi.Core.Settings;
|
using Ombi.Core.Settings;
|
||||||
using Ombi.Helpers;
|
using Ombi.Helpers;
|
||||||
using Ombi.Notifications.Interfaces;
|
|
||||||
using Ombi.Notifications.Models;
|
using Ombi.Notifications.Models;
|
||||||
using Ombi.Settings.Settings.Models;
|
using Ombi.Settings.Settings.Models;
|
||||||
using Ombi.Settings.Settings.Models.Notifications;
|
using Ombi.Settings.Settings.Models.Notifications;
|
||||||
|
|
|
@ -5,7 +5,6 @@ using Ombi.Api.Slack;
|
||||||
using Ombi.Api.Slack.Models;
|
using Ombi.Api.Slack.Models;
|
||||||
using Ombi.Core.Settings;
|
using Ombi.Core.Settings;
|
||||||
using Ombi.Helpers;
|
using Ombi.Helpers;
|
||||||
using Ombi.Notifications.Interfaces;
|
|
||||||
using Ombi.Notifications.Models;
|
using Ombi.Notifications.Models;
|
||||||
using Ombi.Settings.Settings.Models;
|
using Ombi.Settings.Settings.Models;
|
||||||
using Ombi.Settings.Settings.Models.Notifications;
|
using Ombi.Settings.Settings.Models.Notifications;
|
||||||
|
|
|
@ -3,7 +3,6 @@ using System.Threading.Tasks;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Ombi.Core.Settings;
|
using Ombi.Core.Settings;
|
||||||
using Ombi.Helpers;
|
using Ombi.Helpers;
|
||||||
using Ombi.Notifications.Interfaces;
|
|
||||||
using Ombi.Notifications.Models;
|
using Ombi.Notifications.Models;
|
||||||
using Ombi.Settings.Settings.Models;
|
using Ombi.Settings.Settings.Models;
|
||||||
using Ombi.Settings.Settings.Models.Notifications;
|
using Ombi.Settings.Settings.Models.Notifications;
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
@ -14,7 +13,7 @@ using Ombi.Store.Entities.Requests;
|
||||||
using Ombi.Store.Repository;
|
using Ombi.Store.Repository;
|
||||||
using Ombi.Store.Repository.Requests;
|
using Ombi.Store.Repository.Requests;
|
||||||
|
|
||||||
namespace Ombi.Notifications.Interfaces
|
namespace Ombi.Notifications
|
||||||
{
|
{
|
||||||
public abstract class BaseNotification<T> : INotification where T : Settings.Settings.Models.Settings, new()
|
public abstract class BaseNotification<T> : INotification where T : Settings.Settings.Models.Settings, new()
|
||||||
{
|
{
|
||||||
|
|
|
@ -6,7 +6,6 @@ using System.Threading.Tasks;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Ombi.Core.Notifications;
|
using Ombi.Core.Notifications;
|
||||||
using Ombi.Helpers;
|
using Ombi.Helpers;
|
||||||
using Ombi.Notifications.Interfaces;
|
|
||||||
using Ombi.Notifications.Models;
|
using Ombi.Notifications.Models;
|
||||||
|
|
||||||
namespace Ombi.Notifications
|
namespace Ombi.Notifications
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using Ombi.Helpers;
|
||||||
using static Ombi.Schedule.Jobs.Ombi.NewsletterJob;
|
using static Ombi.Schedule.Jobs.Ombi.NewsletterJob;
|
||||||
|
|
||||||
namespace Ombi.Schedule.Tests
|
namespace Ombi.Schedule.Tests
|
||||||
|
@ -15,7 +17,7 @@ namespace Ombi.Schedule.Tests
|
||||||
{
|
{
|
||||||
ep.Add(i);
|
ep.Add(i);
|
||||||
}
|
}
|
||||||
var result = BuildEpisodeList(ep);
|
var result = StringHelper.BuildEpisodeList(ep);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ namespace Ombi.Schedule
|
||||||
IEmbyUserImporter embyUserImporter, ISonarrSync cache, ICouchPotatoSync cpCache,
|
IEmbyUserImporter embyUserImporter, ISonarrSync cache, ICouchPotatoSync cpCache,
|
||||||
ISettingsService<JobSettings> jobsettings, ISickRageSync srSync, IRefreshMetadata refresh,
|
ISettingsService<JobSettings> jobsettings, ISickRageSync srSync, IRefreshMetadata refresh,
|
||||||
INewsletterJob newsletter, IPlexRecentlyAddedSync recentlyAddedPlex, ILidarrArtistSync artist,
|
INewsletterJob newsletter, IPlexRecentlyAddedSync recentlyAddedPlex, ILidarrArtistSync artist,
|
||||||
IIssuesPurge purge)
|
IIssuesPurge purge, IResendFailedRequests resender)
|
||||||
{
|
{
|
||||||
_plexContentSync = plexContentSync;
|
_plexContentSync = plexContentSync;
|
||||||
_radarrSync = radarrSync;
|
_radarrSync = radarrSync;
|
||||||
|
@ -38,6 +38,7 @@ namespace Ombi.Schedule
|
||||||
_plexRecentlyAddedSync = recentlyAddedPlex;
|
_plexRecentlyAddedSync = recentlyAddedPlex;
|
||||||
_lidarrArtistSync = artist;
|
_lidarrArtistSync = artist;
|
||||||
_issuesPurge = purge;
|
_issuesPurge = purge;
|
||||||
|
_resender = resender;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly IPlexContentSync _plexContentSync;
|
private readonly IPlexContentSync _plexContentSync;
|
||||||
|
@ -55,6 +56,7 @@ namespace Ombi.Schedule
|
||||||
private readonly INewsletterJob _newsletter;
|
private readonly INewsletterJob _newsletter;
|
||||||
private readonly ILidarrArtistSync _lidarrArtistSync;
|
private readonly ILidarrArtistSync _lidarrArtistSync;
|
||||||
private readonly IIssuesPurge _issuesPurge;
|
private readonly IIssuesPurge _issuesPurge;
|
||||||
|
private readonly IResendFailedRequests _resender;
|
||||||
|
|
||||||
public void Setup()
|
public void Setup()
|
||||||
{
|
{
|
||||||
|
@ -76,6 +78,8 @@ namespace Ombi.Schedule
|
||||||
RecurringJob.AddOrUpdate(() => _embyUserImporter.Start(), JobSettingsHelper.UserImporter(s));
|
RecurringJob.AddOrUpdate(() => _embyUserImporter.Start(), JobSettingsHelper.UserImporter(s));
|
||||||
RecurringJob.AddOrUpdate(() => _plexUserImporter.Start(), JobSettingsHelper.UserImporter(s));
|
RecurringJob.AddOrUpdate(() => _plexUserImporter.Start(), JobSettingsHelper.UserImporter(s));
|
||||||
RecurringJob.AddOrUpdate(() => _newsletter.Start(), JobSettingsHelper.Newsletter(s));
|
RecurringJob.AddOrUpdate(() => _newsletter.Start(), JobSettingsHelper.Newsletter(s));
|
||||||
|
RecurringJob.AddOrUpdate(() => _newsletter.Start(), JobSettingsHelper.Newsletter(s));
|
||||||
|
RecurringJob.AddOrUpdate(() => _resender.Start(), JobSettingsHelper.ResendFailedRequests(s));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -83,7 +83,7 @@ namespace Ombi.Schedule.Jobs.Emby
|
||||||
{
|
{
|
||||||
processed++;
|
processed++;
|
||||||
|
|
||||||
if (ep.LocationType.Equals("Virtual", StringComparison.InvariantCultureIgnoreCase))
|
if (ep.LocationType?.Equals("Virtual", StringComparison.InvariantCultureIgnoreCase) ?? false)
|
||||||
{
|
{
|
||||||
// For some reason Emby is not respecting the `IsVirtualItem` field.
|
// For some reason Emby is not respecting the `IsVirtualItem` field.
|
||||||
continue;
|
continue;
|
||||||
|
|
9
src/Ombi.Schedule/Jobs/Ombi/IResendFailedRequests.cs
Normal file
9
src/Ombi.Schedule/Jobs/Ombi/IResendFailedRequests.cs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Ombi.Schedule.Jobs.Ombi
|
||||||
|
{
|
||||||
|
public interface IResendFailedRequests
|
||||||
|
{
|
||||||
|
Task Start();
|
||||||
|
}
|
||||||
|
}
|
|
@ -677,7 +677,7 @@ namespace Ombi.Schedule.Jobs.Ombi
|
||||||
foreach (var epInformation in results.OrderBy(x => x.SeasonNumber))
|
foreach (var epInformation in results.OrderBy(x => x.SeasonNumber))
|
||||||
{
|
{
|
||||||
var orderedEpisodes = epInformation.Episodes.OrderBy(x => x.EpisodeNumber).ToList();
|
var orderedEpisodes = epInformation.Episodes.OrderBy(x => x.EpisodeNumber).ToList();
|
||||||
var episodeString = BuildEpisodeList(orderedEpisodes.Select(x => x.EpisodeNumber));
|
var episodeString = StringHelper.BuildEpisodeList(orderedEpisodes.Select(x => x.EpisodeNumber));
|
||||||
finalsb.Append($"Season: {epInformation.SeasonNumber} - Episodes: {episodeString}");
|
finalsb.Append($"Season: {epInformation.SeasonNumber} - Episodes: {episodeString}");
|
||||||
finalsb.Append("<br />");
|
finalsb.Append("<br />");
|
||||||
}
|
}
|
||||||
|
@ -715,48 +715,7 @@ namespace Ombi.Schedule.Jobs.Ombi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string BuildEpisodeList(IEnumerable<int> orderedEpisodes)
|
|
||||||
{
|
|
||||||
var epSb = new StringBuilder();
|
|
||||||
var previousEpisodes = new List<int>();
|
|
||||||
var previousEpisode = -1;
|
|
||||||
foreach (var ep in orderedEpisodes)
|
|
||||||
{
|
|
||||||
if (ep - 1 == previousEpisode)
|
|
||||||
{
|
|
||||||
// This is the next one
|
|
||||||
previousEpisodes.Add(ep);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (previousEpisodes.Count > 1)
|
|
||||||
{
|
|
||||||
// End it
|
|
||||||
epSb.Append($"{previousEpisodes.First()}-{previousEpisodes.Last()}, ");
|
|
||||||
}
|
|
||||||
else if (previousEpisodes.Count == 1)
|
|
||||||
{
|
|
||||||
epSb.Append($"{previousEpisodes.FirstOrDefault()}, ");
|
|
||||||
}
|
|
||||||
// New one
|
|
||||||
previousEpisodes.Clear();
|
|
||||||
previousEpisodes.Add(ep);
|
|
||||||
}
|
|
||||||
previousEpisode = ep;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (previousEpisodes.Count > 1)
|
|
||||||
{
|
|
||||||
// Got some left over
|
|
||||||
epSb.Append($"{previousEpisodes.First()}-{previousEpisodes.Last()}");
|
|
||||||
}
|
|
||||||
else if(previousEpisodes.Count == 1)
|
|
||||||
{
|
|
||||||
epSb.Append(previousEpisodes.FirstOrDefault());
|
|
||||||
}
|
|
||||||
|
|
||||||
return epSb.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task ProcessEmbyTv(HashSet<EmbyEpisode> embyContent, StringBuilder sb)
|
private async Task ProcessEmbyTv(HashSet<EmbyEpisode> embyContent, StringBuilder sb)
|
||||||
{
|
{
|
||||||
|
@ -841,7 +800,7 @@ namespace Ombi.Schedule.Jobs.Ombi
|
||||||
foreach (var epInformation in results.OrderBy(x => x.SeasonNumber))
|
foreach (var epInformation in results.OrderBy(x => x.SeasonNumber))
|
||||||
{
|
{
|
||||||
var orderedEpisodes = epInformation.Episodes.OrderBy(x => x.EpisodeNumber).ToList();
|
var orderedEpisodes = epInformation.Episodes.OrderBy(x => x.EpisodeNumber).ToList();
|
||||||
var episodeString = BuildEpisodeList(orderedEpisodes.Select(x => x.EpisodeNumber));
|
var episodeString = StringHelper.BuildEpisodeList(orderedEpisodes.Select(x => x.EpisodeNumber));
|
||||||
finalsb.Append($"Season: {epInformation.SeasonNumber} - Episodes: {episodeString}");
|
finalsb.Append($"Season: {epInformation.SeasonNumber} - Episodes: {episodeString}");
|
||||||
finalsb.Append("<br />");
|
finalsb.Append("<br />");
|
||||||
}
|
}
|
||||||
|
|
75
src/Ombi.Schedule/Jobs/Ombi/ResendFailedRequests.cs
Normal file
75
src/Ombi.Schedule/Jobs/Ombi/ResendFailedRequests.cs
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Ombi.Core;
|
||||||
|
using Ombi.Core.Senders;
|
||||||
|
using Ombi.Store.Entities;
|
||||||
|
using Ombi.Store.Repository;
|
||||||
|
using Ombi.Store.Repository.Requests;
|
||||||
|
|
||||||
|
namespace Ombi.Schedule.Jobs.Ombi
|
||||||
|
{
|
||||||
|
public class ResendFailedRequests : IResendFailedRequests
|
||||||
|
{
|
||||||
|
public ResendFailedRequests(IRepository<RequestQueue> queue, IMovieSender movieSender, ITvSender tvSender, IMusicSender musicSender,
|
||||||
|
IMovieRequestRepository movieRepo, ITvRequestRepository tvRepo, IMusicRequestRepository music)
|
||||||
|
{
|
||||||
|
_requestQueue = queue;
|
||||||
|
_movieSender = movieSender;
|
||||||
|
_tvSender = tvSender;
|
||||||
|
_musicSender = musicSender;
|
||||||
|
_movieRequestRepository = movieRepo;
|
||||||
|
_tvRequestRepository = tvRepo;
|
||||||
|
_musicRequestRepository = music;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly IRepository<RequestQueue> _requestQueue;
|
||||||
|
private readonly IMovieSender _movieSender;
|
||||||
|
private readonly ITvSender _tvSender;
|
||||||
|
private readonly IMusicSender _musicSender;
|
||||||
|
private readonly IMovieRequestRepository _movieRequestRepository;
|
||||||
|
private readonly ITvRequestRepository _tvRequestRepository;
|
||||||
|
private readonly IMusicRequestRepository _musicRequestRepository;
|
||||||
|
|
||||||
|
public async Task Start()
|
||||||
|
{
|
||||||
|
// Get all the failed ones!
|
||||||
|
var failedRequests = _requestQueue.GetAll().Where(x => !x.Completed.HasValue);
|
||||||
|
|
||||||
|
foreach (var request in failedRequests)
|
||||||
|
{
|
||||||
|
if (request.Type == RequestType.Movie)
|
||||||
|
{
|
||||||
|
var movieRequest = await _movieRequestRepository.GetAll().FirstOrDefaultAsync(x => x.Id == request.RequestId);
|
||||||
|
var result = await _movieSender.Send(movieRequest);
|
||||||
|
if (result.Success)
|
||||||
|
{
|
||||||
|
request.Completed = DateTime.UtcNow;
|
||||||
|
await _requestQueue.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (request.Type == RequestType.TvShow)
|
||||||
|
{
|
||||||
|
var tvRequest = await _tvRequestRepository.GetChild().FirstOrDefaultAsync(x => x.Id == request.RequestId);
|
||||||
|
var result = await _tvSender.Send(tvRequest);
|
||||||
|
if (result.Success)
|
||||||
|
{
|
||||||
|
request.Completed = DateTime.UtcNow;
|
||||||
|
await _requestQueue.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (request.Type == RequestType.Album)
|
||||||
|
{
|
||||||
|
var musicRequest = await _musicRequestRepository.GetAll().FirstOrDefaultAsync(x => x.Id == request.RequestId);
|
||||||
|
var result = await _musicSender.Send(musicRequest);
|
||||||
|
if (result.Success)
|
||||||
|
{
|
||||||
|
request.Completed = DateTime.UtcNow;
|
||||||
|
await _requestQueue.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -34,6 +34,7 @@
|
||||||
<ProjectReference Include="..\Ombi.Api.SickRage\Ombi.Api.SickRage.csproj" />
|
<ProjectReference Include="..\Ombi.Api.SickRage\Ombi.Api.SickRage.csproj" />
|
||||||
<ProjectReference Include="..\Ombi.Api.Sonarr\Ombi.Api.Sonarr.csproj" />
|
<ProjectReference Include="..\Ombi.Api.Sonarr\Ombi.Api.Sonarr.csproj" />
|
||||||
<ProjectReference Include="..\Ombi.Api.TvMaze\Ombi.Api.TvMaze.csproj" />
|
<ProjectReference Include="..\Ombi.Api.TvMaze\Ombi.Api.TvMaze.csproj" />
|
||||||
|
<ProjectReference Include="..\Ombi.Core\Ombi.Core.csproj" />
|
||||||
<ProjectReference Include="..\Ombi.Notifications\Ombi.Notifications.csproj" />
|
<ProjectReference Include="..\Ombi.Notifications\Ombi.Notifications.csproj" />
|
||||||
<ProjectReference Include="..\Ombi.Settings\Ombi.Settings.csproj" />
|
<ProjectReference Include="..\Ombi.Settings\Ombi.Settings.csproj" />
|
||||||
<ProjectReference Include="..\Ombi.TheMovieDbApi\Ombi.Api.TheMovieDb.csproj" />
|
<ProjectReference Include="..\Ombi.TheMovieDbApi\Ombi.Api.TheMovieDb.csproj" />
|
||||||
|
|
|
@ -1,55 +1,16 @@
|
||||||
using System;
|
namespace Ombi.Settings.Settings.Models
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Ombi.Helpers;
|
|
||||||
|
|
||||||
namespace Ombi.Settings.Settings.Models
|
|
||||||
{
|
{
|
||||||
public class CustomizationSettings : Settings
|
public class CustomizationSettings : Settings
|
||||||
{
|
{
|
||||||
public string ApplicationName { get; set; }
|
public string ApplicationName { get; set; }
|
||||||
public string ApplicationUrl { get; set; }
|
public string ApplicationUrl { get; set; }
|
||||||
public string CustomCssLink { get; set; }
|
public string CustomCss { get; set; }
|
||||||
public bool EnableCustomDonations { get; set; }
|
public bool EnableCustomDonations { get; set; }
|
||||||
public string CustomDonationUrl { get; set; }
|
public string CustomDonationUrl { get; set; }
|
||||||
public string CustomDonationMessage { get; set; }
|
public string CustomDonationMessage { get; set; }
|
||||||
public string Logo { get; set; }
|
public string Logo { get; set; }
|
||||||
|
|
||||||
public string PresetThemeName { get; set; }
|
|
||||||
public string PresetThemeContent { get; set; }
|
|
||||||
public bool RecentlyAddedPage { get; set; }
|
public bool RecentlyAddedPage { get; set; }
|
||||||
|
|
||||||
[NotMapped]
|
|
||||||
public string PresetThemeVersion
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (HasPresetTheme)
|
|
||||||
{
|
|
||||||
var parts = PresetThemeName.Split('-');
|
|
||||||
return parts[3].Replace(".css", string.Empty);
|
|
||||||
}
|
|
||||||
return string.Empty;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[NotMapped]
|
|
||||||
public string PresetThemeDisplayName
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (HasPresetTheme)
|
|
||||||
{
|
|
||||||
var parts = PresetThemeName.Split('-');
|
|
||||||
return parts[1];
|
|
||||||
}
|
|
||||||
return string.Empty;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[NotMapped]
|
|
||||||
public bool HasPresetTheme => PresetThemeName.HasValue() || PresetThemeContent.HasValue();
|
|
||||||
|
|
||||||
public void AddToUrl(string part)
|
public void AddToUrl(string part)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(ApplicationUrl))
|
if (string.IsNullOrEmpty(ApplicationUrl))
|
||||||
|
|
|
@ -15,5 +15,6 @@
|
||||||
public string Newsletter { get; set; }
|
public string Newsletter { get; set; }
|
||||||
public string LidarrArtistSync { get; set; }
|
public string LidarrArtistSync { get; set; }
|
||||||
public string IssuesPurge { get; set; }
|
public string IssuesPurge { get; set; }
|
||||||
|
public string RetryRequests { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -61,6 +61,10 @@ namespace Ombi.Settings.Settings.Models
|
||||||
{
|
{
|
||||||
return Get(s.IssuesPurge, Cron.Daily());
|
return Get(s.IssuesPurge, Cron.Daily());
|
||||||
}
|
}
|
||||||
|
public static string ResendFailedRequests(JobSettings s)
|
||||||
|
{
|
||||||
|
return Get(s.RetryRequests, Cron.Daily(6));
|
||||||
|
}
|
||||||
|
|
||||||
private static string Get(string settings, string defaultCron)
|
private static string Get(string settings, string defaultCron)
|
||||||
{
|
{
|
||||||
|
|
|
@ -56,6 +56,7 @@ namespace Ombi.Store.Context
|
||||||
public DbSet<RequestSubscription> RequestSubscription { get; set; }
|
public DbSet<RequestSubscription> RequestSubscription { get; set; }
|
||||||
public DbSet<UserNotificationPreferences> UserNotificationPreferences { get; set; }
|
public DbSet<UserNotificationPreferences> UserNotificationPreferences { get; set; }
|
||||||
public DbSet<UserQualityProfiles> UserQualityProfileses { get; set; }
|
public DbSet<UserQualityProfiles> UserQualityProfileses { get; set; }
|
||||||
|
public DbSet<RequestQueue> RequestQueue { get; set; }
|
||||||
|
|
||||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||||
{
|
{
|
||||||
|
|
16
src/Ombi.Store/Entities/RequestQueue.cs
Normal file
16
src/Ombi.Store/Entities/RequestQueue.cs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
using System;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
|
||||||
|
namespace Ombi.Store.Entities
|
||||||
|
{
|
||||||
|
[Table("RequestQueue")]
|
||||||
|
public class RequestQueue : Entity
|
||||||
|
{
|
||||||
|
public int RequestId { get; set; }
|
||||||
|
public RequestType Type { get; set; }
|
||||||
|
public DateTime Dts { get; set; }
|
||||||
|
public string Error { get; set; }
|
||||||
|
public DateTime? Completed { get; set; }
|
||||||
|
public int RetryCount { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,4 @@
|
||||||
using System;
|
namespace Ombi.Store.Entities
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace Ombi.Store.Entities
|
|
||||||
{
|
{
|
||||||
public enum RequestType
|
public enum RequestType
|
||||||
{
|
{
|
||||||
|
|
1204
src/Ombi.Store/Migrations/20181204084915_RequestQueue.Designer.cs
generated
Normal file
1204
src/Ombi.Store/Migrations/20181204084915_RequestQueue.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
35
src/Ombi.Store/Migrations/20181204084915_RequestQueue.cs
Normal file
35
src/Ombi.Store/Migrations/20181204084915_RequestQueue.cs
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
namespace Ombi.Store.Migrations
|
||||||
|
{
|
||||||
|
public partial class RequestQueue : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "RequestQueue",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
RequestId = table.Column<int>(nullable: false),
|
||||||
|
Type = table.Column<int>(nullable: false),
|
||||||
|
Dts = table.Column<DateTime>(nullable: false),
|
||||||
|
Error = table.Column<string>(nullable: true),
|
||||||
|
Completed = table.Column<DateTime>(nullable: true),
|
||||||
|
RetryCount = table.Column<int>(nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_RequestQueue", x => x.Id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "RequestQueue");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,7 +14,7 @@ namespace Ombi.Store.Migrations
|
||||||
{
|
{
|
||||||
#pragma warning disable 612, 618
|
#pragma warning disable 612, 618
|
||||||
modelBuilder
|
modelBuilder
|
||||||
.HasAnnotation("ProductVersion", "2.1.3-rtm-32065");
|
.HasAnnotation("ProductVersion", "2.1.4-rtm-31024");
|
||||||
|
|
||||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
|
||||||
{
|
{
|
||||||
|
@ -508,6 +508,28 @@ namespace Ombi.Store.Migrations
|
||||||
b.ToTable("RecentlyAddedLog");
|
b.ToTable("RecentlyAddedLog");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Ombi.Store.Entities.RequestQueue", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd();
|
||||||
|
|
||||||
|
b.Property<DateTime?>("Completed");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Dts");
|
||||||
|
|
||||||
|
b.Property<string>("Error");
|
||||||
|
|
||||||
|
b.Property<int>("RequestId");
|
||||||
|
|
||||||
|
b.Property<int>("RetryCount");
|
||||||
|
|
||||||
|
b.Property<int>("Type");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("RequestQueue");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b =>
|
modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
|
|
11
src/Ombi/ClientApp/app/interfaces/IFailedRequests.ts
Normal file
11
src/Ombi/ClientApp/app/interfaces/IFailedRequests.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import { RequestType } from ".";
|
||||||
|
export interface IFailedRequestsViewModel {
|
||||||
|
failedId: number;
|
||||||
|
title: string;
|
||||||
|
releaseYear: Date;
|
||||||
|
requestId: number;
|
||||||
|
type: RequestType;
|
||||||
|
dts: Date;
|
||||||
|
error: string;
|
||||||
|
retryCount: number;
|
||||||
|
}
|
|
@ -1,9 +1,9 @@
|
||||||
import { IUser } from "./IUser";
|
import { IUser } from "./IUser";
|
||||||
|
|
||||||
export enum RequestType {
|
export enum RequestType {
|
||||||
|
tvShow = 0,
|
||||||
movie = 1,
|
movie = 1,
|
||||||
tvShow = 2,
|
album = 2,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NEW WORLD
|
// NEW WORLD
|
||||||
|
|
|
@ -114,25 +114,13 @@ export interface ICustomizationSettings extends ISettings {
|
||||||
applicationName: string;
|
applicationName: string;
|
||||||
applicationUrl: string;
|
applicationUrl: string;
|
||||||
logo: string;
|
logo: string;
|
||||||
customCssLink: string;
|
customCss: string;
|
||||||
enableCustomDonations: boolean;
|
enableCustomDonations: boolean;
|
||||||
customDonationUrl: string;
|
customDonationUrl: string;
|
||||||
customDonationMessage: string;
|
customDonationMessage: string;
|
||||||
hasPresetTheme: boolean;
|
|
||||||
presetThemeName: string;
|
|
||||||
presetThemeContent: string;
|
|
||||||
presetThemeDisplayName: string;
|
|
||||||
presetThemeVersion: string;
|
|
||||||
recentlyAddedPage: boolean;
|
recentlyAddedPage: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IThemes {
|
|
||||||
fullName: string;
|
|
||||||
displayName: string;
|
|
||||||
version: string;
|
|
||||||
url: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IJobSettings {
|
export interface IJobSettings {
|
||||||
embyContentSync: string;
|
embyContentSync: string;
|
||||||
sonarrSync: string;
|
sonarrSync: string;
|
||||||
|
@ -147,6 +135,7 @@ export interface IJobSettings {
|
||||||
plexRecentlyAddedSync: string;
|
plexRecentlyAddedSync: string;
|
||||||
lidarrArtistSync: string;
|
lidarrArtistSync: string;
|
||||||
issuesPurge: string;
|
issuesPurge: string;
|
||||||
|
retryRequests: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IIssueSettings extends ISettings {
|
export interface IIssueSettings extends ISettings {
|
||||||
|
|
|
@ -17,3 +17,4 @@ export * from "./IRecentlyAdded";
|
||||||
export * from "./ILidarr";
|
export * from "./ILidarr";
|
||||||
export * from "./ISearchMusicResult";
|
export * from "./ISearchMusicResult";
|
||||||
export * from "./IVote";
|
export * from "./IVote";
|
||||||
|
export * from "./IFailedRequests";
|
||||||
|
|
|
@ -116,7 +116,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="dropdown" *ngIf="result.available && issueCategories && issuesEnabled">
|
<div class="dropdown" *ngIf="result.available && issueCategories && issuesEnabled">
|
||||||
<button class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
|
<button class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
|
||||||
<i class="fa fa-plus"></i> {{'Request.ReportIssue' | translate}}
|
<i class="fa fa-plus"></i> {{'Requests.ReportIssue' | translate}}
|
||||||
<span class="caret"></span>
|
<span class="caret"></span>
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
|
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
|
||||||
|
|
|
@ -15,3 +15,4 @@ export * from "./mobile.service";
|
||||||
export * from "./notificationMessage.service";
|
export * from "./notificationMessage.service";
|
||||||
export * from "./recentlyAdded.service";
|
export * from "./recentlyAdded.service";
|
||||||
export * from "./vote.service";
|
export * from "./vote.service";
|
||||||
|
export * from "./requestretry.service";
|
||||||
|
|
21
src/Ombi/ClientApp/app/services/requestretry.service.ts
Normal file
21
src/Ombi/ClientApp/app/services/requestretry.service.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import { PlatformLocation } from "@angular/common";
|
||||||
|
import { Injectable } from "@angular/core";
|
||||||
|
|
||||||
|
import { HttpClient } from "@angular/common/http";
|
||||||
|
import { Observable } from "rxjs";
|
||||||
|
|
||||||
|
import { IFailedRequestsViewModel } from "../interfaces";
|
||||||
|
import { ServiceHelpers } from "./service.helpers";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class RequestRetryService extends ServiceHelpers {
|
||||||
|
constructor(http: HttpClient, public platformLocation: PlatformLocation) {
|
||||||
|
super(http, "/api/v1/requestretry/", platformLocation);
|
||||||
|
}
|
||||||
|
public getFailedRequests(): Observable<IFailedRequestsViewModel[]> {
|
||||||
|
return this.http.get<IFailedRequestsViewModel[]>(this.url, {headers: this.headers});
|
||||||
|
}
|
||||||
|
public deleteFailedRequest(failedId: number): Observable<boolean> {
|
||||||
|
return this.http.delete<boolean>(`${this.url}/${failedId}`, {headers: this.headers});
|
||||||
|
}
|
||||||
|
}
|
|
@ -31,7 +31,6 @@ import {
|
||||||
ISlackNotificationSettings,
|
ISlackNotificationSettings,
|
||||||
ISonarrSettings,
|
ISonarrSettings,
|
||||||
ITelegramNotifcationSettings,
|
ITelegramNotifcationSettings,
|
||||||
IThemes,
|
|
||||||
IUpdateSettings,
|
IUpdateSettings,
|
||||||
IUserManagementSettings,
|
IUserManagementSettings,
|
||||||
IVoteSettings,
|
IVoteSettings,
|
||||||
|
@ -135,14 +134,6 @@ export class SettingsService extends ServiceHelpers {
|
||||||
return this.http.post<boolean>(`${this.url}/customization`, JSON.stringify(settings), {headers: this.headers});
|
return this.http.post<boolean>(`${this.url}/customization`, JSON.stringify(settings), {headers: this.headers});
|
||||||
}
|
}
|
||||||
|
|
||||||
public getThemes(): Observable<IThemes[]> {
|
|
||||||
return this.http.get<IThemes[]>(`${this.url}/themes`, {headers: this.headers});
|
|
||||||
}
|
|
||||||
|
|
||||||
public getThemeContent(themeUrl: string): Observable<string> {
|
|
||||||
return this.http.get(`${this.url}/themecontent?url=${themeUrl}`, {responseType: "text", headers: this.headers});
|
|
||||||
}
|
|
||||||
|
|
||||||
public getEmailNotificationSettings(): Observable<IEmailNotificationSettings> {
|
public getEmailNotificationSettings(): Observable<IEmailNotificationSettings> {
|
||||||
return this.http.get<IEmailNotificationSettings>(`${this.url}/notifications/email`, {headers: this.headers});
|
return this.http.get<IEmailNotificationSettings>(`${this.url}/notifications/email`, {headers: this.headers});
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,15 +47,6 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="customLink" class="control-label">Custom CSS Link</label>
|
|
||||||
<div>
|
|
||||||
<input type="text" [(ngModel)]="settings.customCssLink" class="form-control form-control-custom " name="customLink" value="{{settings.customCssLink}}"
|
|
||||||
tooltipPosition="top" pTooltip="A link to a CSS file, you can use this to use your own styles for Ombi">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="checkbox">
|
<div class="checkbox">
|
||||||
<input type="checkbox" id="enableCustomDonations" name="enableCustomDonations" [(ngModel)]="settings.enableCustomDonations">
|
<input type="checkbox" id="enableCustomDonations" name="enableCustomDonations" [(ngModel)]="settings.enableCustomDonations">
|
||||||
|
@ -63,7 +54,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group" *ngIf="settings.enableCustomDonations">
|
||||||
<label for="customDonation" class="control-label">Custom Donation URL</label>
|
<label for="customDonation" class="control-label">Custom Donation URL</label>
|
||||||
<div>
|
<div>
|
||||||
<input [disabled]="!settings.enableCustomDonations" type="text" [(ngModel)]="settings.customDonationUrl" class="form-control form-control-custom " name="customDonation" value="{{settings.customDonationUrl}}"
|
<input [disabled]="!settings.enableCustomDonations" type="text" [(ngModel)]="settings.customDonationUrl" class="form-control form-control-custom " name="customDonation" value="{{settings.customDonationUrl}}"
|
||||||
|
@ -71,7 +62,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group" *ngIf="settings.enableCustomDonations">
|
||||||
<label for="customDonationMessage" class="control-label">Donation Button Message</label>
|
<label for="customDonationMessage" class="control-label">Donation Button Message</label>
|
||||||
<div>
|
<div>
|
||||||
<input [disabled]="!settings.enableCustomDonations" type="text" [(ngModel)]="settings.customDonationMessage" class="form-control form-control-custom " name="customDonationMessage" value="{{settings.customDonationMessage}}"
|
<input [disabled]="!settings.enableCustomDonations" type="text" [(ngModel)]="settings.customDonationMessage" class="form-control form-control-custom " name="customDonationMessage" value="{{settings.customDonationMessage}}"
|
||||||
|
@ -89,21 +80,15 @@
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-7">
|
<div class="col-md-7">
|
||||||
<div *ngIf="themes">
|
<div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="presetTheme" class="control-label">Preset Themes</label>
|
<label for="customCss" class="control-label">Custom CSS</label>
|
||||||
<div id="presetTheme">
|
|
||||||
<select class="form-control form-control-custom" (change)="dropDownChange($event)">
|
|
||||||
<option *ngFor="let theme of themes" value="{{theme.fullName}}" [selected]="settings.presetThemeName === theme.fullName">{{theme.displayName}} {{theme.version}}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group language-css" pCode>
|
||||||
|
<textarea rows="25" type="text"
|
||||||
|
pTooltip="Enter your custom styles here" tooltipPosition="top"
|
||||||
|
class="form-control-custom form-control " id="themeContent" name="themeContent" [(ngModel)]="settings.customCss"> {{settings.customCss}} </textarea>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" *ngIf="settings.presetThemeContent">
|
|
||||||
<textarea rows="25" type="text" class="form-control-custom form-control " id="themeContent" name="themeContent" [(ngModel)]="settings.presetThemeContent"> {{settings.presetThemeContent}} </textarea>
|
|
||||||
</div>
|
|
||||||
<small>Preset themes are powered by
|
|
||||||
<a href="https://github.com/leram84/layer.Cake" target="_blank">layer#Cake</a>.
|
|
||||||
</small>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Component, OnInit } from "@angular/core";
|
import { Component, OnInit } from "@angular/core";
|
||||||
|
|
||||||
import { ICustomizationSettings, IThemes } from "../../interfaces";
|
import { ICustomizationSettings } from "../../interfaces";
|
||||||
import { NotificationService } from "../../services";
|
import { NotificationService } from "../../services";
|
||||||
import { SettingsService } from "../../services";
|
import { SettingsService } from "../../services";
|
||||||
|
|
||||||
|
@ -10,7 +10,6 @@ import { SettingsService } from "../../services";
|
||||||
export class CustomizationComponent implements OnInit {
|
export class CustomizationComponent implements OnInit {
|
||||||
|
|
||||||
public settings: ICustomizationSettings;
|
public settings: ICustomizationSettings;
|
||||||
public themes: IThemes[];
|
|
||||||
public advanced: boolean;
|
public advanced: boolean;
|
||||||
|
|
||||||
constructor(private settingsService: SettingsService, private notificationService: NotificationService) { }
|
constructor(private settingsService: SettingsService, private notificationService: NotificationService) { }
|
||||||
|
@ -18,26 +17,6 @@ export class CustomizationComponent implements OnInit {
|
||||||
public ngOnInit() {
|
public ngOnInit() {
|
||||||
this.settingsService.getCustomization().subscribe(x => {
|
this.settingsService.getCustomization().subscribe(x => {
|
||||||
this.settings = x;
|
this.settings = x;
|
||||||
this.settingsService.getThemes().subscribe(t => {
|
|
||||||
this.themes = t;
|
|
||||||
|
|
||||||
const existingTheme = this.themes.filter((item) => {
|
|
||||||
return item.fullName === this.settings.presetThemeName;
|
|
||||||
})[0];
|
|
||||||
|
|
||||||
if (existingTheme) {
|
|
||||||
const index = this.themes.indexOf(existingTheme, 0);
|
|
||||||
if (index > -1) {
|
|
||||||
this.themes.splice(index, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (x.hasPresetTheme) {
|
|
||||||
this.themes.unshift({displayName: x.presetThemeDisplayName, fullName: x.presetThemeName, url: existingTheme.url, version: x.presetThemeVersion});
|
|
||||||
this.themes.unshift({displayName: "None", fullName: "None", url: "", version: ""});
|
|
||||||
} else {
|
|
||||||
this.themes.unshift({displayName: "Please Select", fullName: "-1", url: "-1", version: ""});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -62,26 +41,4 @@ export class CustomizationComponent implements OnInit {
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public dropDownChange(event: any): void {
|
|
||||||
const selectedThemeFullName = <string> event.target.value;
|
|
||||||
const selectedTheme = this.themes.filter((val) => {
|
|
||||||
return val.fullName === selectedThemeFullName;
|
|
||||||
})[0];
|
|
||||||
|
|
||||||
if (selectedTheme.fullName === this.settings.presetThemeName) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedTheme.fullName === "None" || selectedTheme.fullName === "-1") {
|
|
||||||
this.settings.presetThemeName = "";
|
|
||||||
this.settings.presetThemeContent = "";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.settings.presetThemeName = selectedThemeFullName;
|
|
||||||
this.settingsService.getThemeContent(selectedTheme.url).subscribe(x => {
|
|
||||||
this.settings.presetThemeContent = x;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
|
||||||
|
<settings-menu></settings-menu>
|
||||||
|
|
||||||
|
|
||||||
|
<table class="table table-striped table-hover table-responsive table-condensed">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td>Title</td>
|
||||||
|
<td>Type</td>
|
||||||
|
<td>Retry Count</td>
|
||||||
|
<td>Error Description</td>
|
||||||
|
<td>Delete</td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let v of vm">
|
||||||
|
<td class="vcenter">
|
||||||
|
{{v.title}}
|
||||||
|
</td>
|
||||||
|
<td>{{RequestType[v.type] | humanize}}</td>
|
||||||
|
<td class="vcenter">{{v.retryCount}}</td>
|
||||||
|
<td class="vcenter"> <i [pTooltip]="v.error" class="fa fa-info-circle"></i></td>
|
||||||
|
<td class="vcenter"><button class="btn btn-sm btn-danger-outline" (click)="remove(v)">Remove</button></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { Component, OnInit } from "@angular/core";
|
||||||
|
import { IFailedRequestsViewModel, RequestType } from "../../interfaces";
|
||||||
|
import { RequestRetryService } from "../../services";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
templateUrl: "./failedrequest.component.html",
|
||||||
|
})
|
||||||
|
export class FailedRequestsComponent implements OnInit {
|
||||||
|
|
||||||
|
public vm: IFailedRequestsViewModel[];
|
||||||
|
public RequestType = RequestType;
|
||||||
|
|
||||||
|
constructor(private retry: RequestRetryService) { }
|
||||||
|
|
||||||
|
public ngOnInit() {
|
||||||
|
this.retry.getFailedRequests().subscribe(x => this.vm = x);
|
||||||
|
}
|
||||||
|
|
||||||
|
public remove(failed: IFailedRequestsViewModel) {
|
||||||
|
this.retry.deleteFailedRequest(failed.failedId).subscribe(x => {
|
||||||
|
if(x) {
|
||||||
|
const index = this.vm.indexOf(failed);
|
||||||
|
this.vm.splice(index,1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -48,6 +48,13 @@
|
||||||
<small *ngIf="form.get('automaticUpdater').hasError('required')" class="error-text">The Automatic Update is required</small>
|
<small *ngIf="form.get('automaticUpdater').hasError('required')" class="error-text">The Automatic Update is required</small>
|
||||||
<button type="button" class="btn btn-sm btn-primary-outline" (click)="testCron(form.get('automaticUpdater')?.value)">Test</button>
|
<button type="button" class="btn btn-sm btn-primary-outline" (click)="testCron(form.get('automaticUpdater')?.value)">Test</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="retryRequests" class="control-label">Retry Failed Requests</label>
|
||||||
|
<input type="text" class="form-control form-control-custom" [ngClass]="{'form-error': form.get('retryRequests').hasError('required')}" id="retryRequests" name="retryRequests" formControlName="retryRequests">
|
||||||
|
<small *ngIf="form.get('retryRequests').hasError('required')" class="error-text">The Retry Requests is required</small>
|
||||||
|
<button type="button" class="btn btn-sm btn-primary-outline" (click)="testCron(form.get('retryRequests')?.value)">Test</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
|
|
@ -36,6 +36,7 @@ export class JobsComponent implements OnInit {
|
||||||
plexRecentlyAddedSync: [x.plexRecentlyAddedSync, Validators.required],
|
plexRecentlyAddedSync: [x.plexRecentlyAddedSync, Validators.required],
|
||||||
lidarrArtistSync: [x.lidarrArtistSync, Validators.required],
|
lidarrArtistSync: [x.lidarrArtistSync, Validators.required],
|
||||||
issuesPurge: [x.issuesPurge, Validators.required],
|
issuesPurge: [x.issuesPurge, Validators.required],
|
||||||
|
retryRequests: [x.retryRequests, Validators.required],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,25 +19,28 @@
|
||||||
|
|
||||||
|
|
||||||
<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
|
||||||
|
|
||||||
|
<i *ngIf="form.get('ip').hasError('required')" class="fa fa-exclamation-circle error-text" pTooltip="IP/Hostname is required"></i>
|
||||||
|
</label>
|
||||||
|
|
||||||
<input type="text" class="form-control form-control-custom " id="Ip" name="Ip" placeholder="localhost" formControlName="ip" [ngClass]="{'form-error': form.get('ip').hasError('required')}">
|
<input type="text" class="form-control form-control-custom " id="Ip" name="Ip" placeholder="localhost" formControlName="ip" [ngClass]="{'form-error': form.get('ip').hasError('required')}">
|
||||||
<small *ngIf="form.get('ip').hasError('required')" class="error-text">The IP/Hostname is required</small>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="portNumber" class="control-label">Port</label>
|
<label for="portNumber" class="control-label">Port
|
||||||
|
<i *ngIf="form.get('port').hasError('required')" class="fa fa-exclamation-circle error-text" pTooltip="Port is required"></i></label>
|
||||||
|
|
||||||
<input type="text" class="form-control form-control-custom " formControlName="port" id="portNumber" name="Port" placeholder="Port Number" [ngClass]="{'form-error': form.get('port').hasError('required')}">
|
<input type="text" class="form-control form-control-custom " formControlName="port" id="portNumber" name="Port" placeholder="Port Number" [ngClass]="{'form-error': form.get('port').hasError('required')}">
|
||||||
<small *ngIf="form.get('port').hasError('required')" class="error-text">The Port is required</small>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<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 <i *ngIf="form.get('apiKey').hasError('required')" class="fa fa-exclamation-circle error-text" pTooltip="API Key is required"></i></label>
|
||||||
|
|
||||||
<input type="text" class="form-control form-control-custom " [ngClass]="{'form-error': form.get('apiKey').hasError('required')}" id="ApiKey" name="ApiKey" formControlName="apiKey">
|
<input type="text" class="form-control form-control-custom " [ngClass]="{'form-error': form.get('apiKey').hasError('required')}" id="ApiKey" name="ApiKey" formControlName="apiKey">
|
||||||
<small *ngIf="form.get('apiKey').hasError('required')" class="error-text">The API Key is required</small>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="checkbox">
|
<div class="checkbox">
|
||||||
|
@ -56,19 +59,22 @@
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
|
|
||||||
<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
|
||||||
|
<i *ngIf="form.get('defaultQualityProfile').hasError('required')" class="fa fa-exclamation-circle error-text" pTooltip="Quality Profile is required"></i>
|
||||||
|
</label>
|
||||||
<div id="profiles">
|
<div id="profiles">
|
||||||
<select formControlName="defaultQualityProfile" class="form-control form-control-custom col-md-5 form-half" id="select" [ngClass]="{'form-error': form.get('defaultQualityProfile').hasError('required')}">
|
<select formControlName="defaultQualityProfile" class="form-control form-control-custom col-md-5 form-half" id="select" [ngClass]="{'form-error': form.get('defaultQualityProfile').hasError('required')}">
|
||||||
<option *ngFor="let quality of qualities" value="{{quality.id}}">{{quality.name}}</option>
|
<option *ngFor="let quality of qualities" value="{{quality.id}}">{{quality.name}}</option>
|
||||||
</select>
|
</select>
|
||||||
<button (click)="getProfiles(form)" type="button" class="btn btn-primary-outline col-md-4 col-md-push-1">Get Quality Profiles <span *ngIf="profilesRunning" class="fa fa-spinner fa-spin"> </span></button>
|
<button (click)="getProfiles(form)" type="button" class="btn btn-primary-outline col-md-4 col-md-push-1">Get Quality Profiles <span *ngIf="profilesRunning" class="fa fa-spinner fa-spin"> </span></button>
|
||||||
|
|
||||||
<small *ngIf="form.get('defaultQualityProfile').hasError('required')" class="error-text">A Default Quality Profile is required</small>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<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
|
||||||
|
|
||||||
|
<i *ngIf="form.get('defaultRootPath').hasError('required')" class="fa fa-exclamation-circle error-text" pTooltip="Root Path is required"></i>
|
||||||
|
</label>
|
||||||
<div id="rootFolders">
|
<div id="rootFolders">
|
||||||
<select formControlName="defaultRootPath" class="form-control form-control-custom col-md-5 form-half" [ngClass]="{'form-error': form.get('defaultRootPath').hasError('required')}">
|
<select formControlName="defaultRootPath" class="form-control form-control-custom col-md-5 form-half" [ngClass]="{'form-error': form.get('defaultRootPath').hasError('required')}">
|
||||||
<option *ngFor="let folder of rootFolders" value="{{folder.path}}" >{{folder.path}}</option>
|
<option *ngFor="let folder of rootFolders" value="{{folder.path}}" >{{folder.path}}</option>
|
||||||
|
@ -76,12 +82,14 @@
|
||||||
<button (click)="getRootFolders(form)" type="button" class="btn btn-primary-outline col-md-4 col-md-push-1">Get Root Folders <span *ngIf="rootFoldersRunning" class="fa fa-spinner fa-spin"></span></button>
|
<button (click)="getRootFolders(form)" type="button" class="btn btn-primary-outline col-md-4 col-md-push-1">Get Root Folders <span *ngIf="rootFoldersRunning" class="fa fa-spinner fa-spin"></span></button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<small *ngIf="form.get('defaultRootPath').hasError('required')" class="error-text">A Default Root Path is required</small>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="languageProfileId" class="control-label">Language Profile</label>
|
<label for="languageProfileId" class="control-label">Language Profile
|
||||||
|
|
||||||
|
<i *ngIf="form.get('languageProfileId').hasError('required')" class="fa fa-exclamation-circle error-text" pTooltip="Language Profile is required"></i>
|
||||||
|
</label>
|
||||||
<div id="languageProfileId">
|
<div id="languageProfileId">
|
||||||
<select formControlName="languageProfileId" class="form-control form-control-custom col-md-5 form-half" [ngClass]="{'form-error': form.get('languageProfileId').hasError('required')}">
|
<select formControlName="languageProfileId" class="form-control form-control-custom col-md-5 form-half" [ngClass]="{'form-error': form.get('languageProfileId').hasError('required')}">
|
||||||
<option *ngFor="let folder of languageProfiles" value="{{folder.id}}" >{{folder.name}}</option>
|
<option *ngFor="let folder of languageProfiles" value="{{folder.id}}" >{{folder.name}}</option>
|
||||||
|
@ -89,12 +97,14 @@
|
||||||
<button (click)="getLanguageProfiles(form)" type="button" class="btn btn-primary-outline col-md-4 col-md-push-1">Get Languages <span *ngIf="rootFoldersRunning" class="fa fa-spinner fa-spin"></span></button>
|
<button (click)="getLanguageProfiles(form)" type="button" class="btn btn-primary-outline col-md-4 col-md-push-1">Get Languages <span *ngIf="rootFoldersRunning" class="fa fa-spinner fa-spin"></span></button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<small *ngIf="form.get('languageProfileId').hasError('required')" class="error-text">A Language profile is required</small>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="metadataProfileId" class="control-label">Metadata Profile</label>
|
<label for="metadataProfileId" class="control-label">Metadata Profile
|
||||||
|
|
||||||
|
<i *ngIf="form.get('metadataProfileId').hasError('required')" class="fa fa-exclamation-circle error-text" pTooltip="Metadata Profile is required"></i>
|
||||||
|
</label>
|
||||||
<div id="metadataProfileId">
|
<div id="metadataProfileId">
|
||||||
|
|
||||||
<select formControlName="metadataProfileId" class="form-control form-control-custom col-md-5 form-half" [ngClass]="{'form-error': form.get('metadataProfileId').hasError('required')}">
|
<select formControlName="metadataProfileId" class="form-control form-control-custom col-md-5 form-half" [ngClass]="{'form-error': form.get('metadataProfileId').hasError('required')}">
|
||||||
|
@ -102,7 +112,6 @@
|
||||||
</select>
|
</select>
|
||||||
<button (click)="getMetadataProfiles(form)" type="button" class="btn btn-primary-outline col-md-4 col-md-push-1">Get Metadata <span *ngIf="rootFoldersRunning" class="fa fa-spinner fa-spin"></span></button>
|
<button (click)="getMetadataProfiles(form)" type="button" class="btn btn-primary-outline col-md-4 col-md-push-1">Get Metadata <span *ngIf="rootFoldersRunning" class="fa fa-spinner fa-spin"></span></button>
|
||||||
|
|
||||||
<small *ngIf="form.get('metadataProfileId').hasError('required')" class="error-text">A Metadata profile is required</small>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
|
<settings-menu></settings-menu>
|
||||||
<settings-menu></settings-menu>
|
|
||||||
<div *ngIf="form">
|
<div *ngIf="form">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>Radarr Settings</legend>
|
<legend>Radarr Settings</legend>
|
||||||
|
@ -19,25 +18,34 @@
|
||||||
|
|
||||||
|
|
||||||
<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
|
||||||
|
<i *ngIf="form.get('ip').hasError('required')" class="fa fa-exclamation-circle error-text"
|
||||||
|
pTooltip="IP/Hostname is required"></i>
|
||||||
|
</label>
|
||||||
|
|
||||||
<input type="text" class="form-control form-control-custom " id="Ip" name="Ip" placeholder="localhost" formControlName="ip" [ngClass]="{'form-error': form.get('ip').hasError('required')}">
|
<input type="text" class="form-control form-control-custom " id="Ip" name="Ip" placeholder="localhost"
|
||||||
<small *ngIf="form.get('ip').hasError('required')" class="error-text">The IP/Hostname is required</small>
|
formControlName="ip" [ngClass]="{'form-error': form.get('ip').hasError('required')}">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="portNumber" class="control-label">Port</label>
|
<label for="portNumber" class="control-label">Port
|
||||||
|
<i *ngIf="form.get('port').hasError('required')" class="fa fa-exclamation-circle error-text"
|
||||||
|
pTooltip="Port is required"></i>
|
||||||
|
</label>
|
||||||
|
|
||||||
<input type="text" class="form-control form-control-custom " formControlName="port" id="portNumber" name="Port" placeholder="Port Number" [ngClass]="{'form-error': form.get('port').hasError('required')}">
|
<input type="text" class="form-control form-control-custom " formControlName="port" id="portNumber"
|
||||||
<small *ngIf="form.get('port').hasError('required')" class="error-text">The Port is required</small>
|
name="Port" placeholder="Port Number" [ngClass]="{'form-error': form.get('port').hasError('required')}">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<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
|
||||||
|
<i *ngIf="form.get('apiKey').hasError('required')" class="fa fa-exclamation-circle error-text"
|
||||||
|
pTooltip="Api Key is required"></i>
|
||||||
|
</label>
|
||||||
|
|
||||||
<input type="text" class="form-control form-control-custom " [ngClass]="{'form-error': form.get('apiKey').hasError('required')}" id="ApiKey" name="ApiKey" formControlName="apiKey">
|
<input type="text" class="form-control form-control-custom " [ngClass]="{'form-error': form.get('apiKey').hasError('required')}"
|
||||||
<small *ngIf="form.get('apiKey').hasError('required')" class="error-text">The API Key is required</small>
|
id="ApiKey" name="ApiKey" formControlName="apiKey">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="checkbox">
|
<div class="checkbox">
|
||||||
|
@ -49,63 +57,73 @@
|
||||||
<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" formControlName="subDir" id="SubDir" name="SubDir">
|
<input type="text" class="form-control form-control-custom" formControlName="subDir" id="SubDir"
|
||||||
|
name="SubDir">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div>
|
<label for="select" class="control-label">Quality Profiles
|
||||||
<button (click)="getProfiles(form)" type="button" class="btn btn-primary-outline">Get Quality Profiles <span *ngIf="profilesRunning" class="fa fa-spinner fa-spin"> </span></button>
|
<i *ngIf="form.get('defaultQualityProfile').hasError('required')" class="fa fa-exclamation-circle error-text"
|
||||||
</div>
|
pTooltip="Quality Profile is required"></i>
|
||||||
</div>
|
</label>
|
||||||
<div class="form-group">
|
|
||||||
<label for="select" class="control-label">Quality Profiles</label>
|
|
||||||
<div id="profiles">
|
<div id="profiles">
|
||||||
<select formControlName="defaultQualityProfile" class="form-control form-control-custom" id="select" [ngClass]="{'form-error': form.get('defaultQualityProfile').hasError('required')}">
|
<select formControlName="defaultQualityProfile" class="form-control form-control-custom col-md-5 form-half"
|
||||||
|
id="select" [ngClass]="{'form-error': form.get('defaultQualityProfile').hasError('required')}">
|
||||||
<option *ngFor="let quality of qualities" value="{{quality.id}}">{{quality.name}}</option>
|
<option *ngFor="let quality of qualities" value="{{quality.id}}">{{quality.name}}</option>
|
||||||
</select>
|
</select>
|
||||||
|
<button (click)="getProfiles(form)" type="button" class="btn btn-primary-outline col-md-4 col-md-push-1">Get
|
||||||
|
Quality Profiles <span *ngIf="profilesRunning" class="fa fa-spinner fa-spin"> </span></button>
|
||||||
</div>
|
</div>
|
||||||
<small *ngIf="form.get('defaultQualityProfile').hasError('required')" class="error-text">A Default Quality Profile is required</small>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div>
|
<label for="rootFolders" class="control-label">Default Root Folders
|
||||||
<button (click)="getRootFolders(form)" type="button" class="btn btn-primary-outline">Get Root Folders <span *ngIf="rootFoldersRunning" class="fa fa-spinner fa-spin"></span></button>
|
|
||||||
|
|
||||||
</div>
|
<i *ngIf="form.get('defaultRootPath').hasError('required')" class="fa fa-exclamation-circle error-text"
|
||||||
|
pTooltip="Root Path is required"></i>
|
||||||
</div>
|
</label>
|
||||||
<div class="form-group">
|
|
||||||
<label for="rootFolders" class="control-label">Default Root Folders</label>
|
|
||||||
<div id="rootFolders">
|
<div id="rootFolders">
|
||||||
<select formControlName="defaultRootPath" class="form-control form-control-custom" [ngClass]="{'form-error': form.get('defaultRootPath').hasError('required')}">
|
<select formControlName="defaultRootPath" class="form-control form-control-custom col-md-5 form-half"
|
||||||
|
[ngClass]="{'form-error': form.get('defaultRootPath').hasError('required')}">
|
||||||
<option *ngFor="let folder of rootFolders" value="{{folder.path}}">{{folder.path}}</option>
|
<option *ngFor="let folder of rootFolders" value="{{folder.path}}">{{folder.path}}</option>
|
||||||
</select>
|
</select>
|
||||||
|
<button (click)="getRootFolders(form)" type="button" class="btn btn-primary-outline col-md-4 col-md-push-1">Get
|
||||||
|
Root Folders <span *ngIf="rootFoldersRunning" class="fa fa-spinner fa-spin"></span></button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<small *ngIf="form.get('defaultRootPath').hasError('required')" class="error-text">A Default Root Path is required</small>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="rootFolders" class="control-label">Default Minimum Availability</label>
|
<label for="rootFolders" class="control-label">Default Minimum Availability
|
||||||
|
<i *ngIf="form.get('minimumAvailability').hasError('required')" class="fa fa-exclamation-circle error-text"
|
||||||
|
pTooltip="Minimum Availability is required"></i>
|
||||||
|
</label>
|
||||||
<div id="rootFolders">
|
<div id="rootFolders">
|
||||||
<select formControlName="minimumAvailability" class="form-control form-control-custom" [ngClass]="{'form-error': form.get('minimumAvailability').hasError('required')}">
|
<select formControlName="minimumAvailability" class="form-control form-control-custom col-md-5 form-half"
|
||||||
|
[ngClass]="{'form-error': form.get('minimumAvailability').hasError('required')}">
|
||||||
<option *ngFor="let min of minimumAvailabilityOptions" value="{{min.value}}">{{min.name}}</option>
|
<option *ngFor="let min of minimumAvailabilityOptions" value="{{min.value}}">{{min.name}}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<small *ngIf="form.get('minimumAvailability').hasError('required')" type="button" class="error-text">A Default Minimum Availability is required</small>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
<div class="form-group" *ngIf="advanced" style="color:#ff761b">
|
<div class="form-group" *ngIf="advanced" style="color:#ff761b">
|
||||||
<div class="checkbox">
|
<div class="checkbox">
|
||||||
<input type="checkbox" id="addOnly" formControlName="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="form-group">
|
||||||
<div>
|
<div>
|
||||||
<button type="button" [disabled]="form.invalid" (click)="test(form)" class="btn btn-primary-outline">Test Connectivity <span id="spinner"></span></button>
|
<button type="button" [disabled]="form.invalid" (click)="test(form)" class="btn btn-primary-outline">Test
|
||||||
|
Connectivity <span id="spinner"></span></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { AuthGuard } from "../auth/auth.guard";
|
||||||
import { AuthService } from "../auth/auth.service";
|
import { AuthService } from "../auth/auth.service";
|
||||||
import {
|
import {
|
||||||
CouchPotatoService, EmbyService, IssuesService, JobService, LidarrService, MobileService, NotificationMessageService, PlexService, RadarrService,
|
CouchPotatoService, EmbyService, IssuesService, JobService, LidarrService, MobileService, NotificationMessageService, PlexService, RadarrService,
|
||||||
SonarrService, TesterService, ValidationService,
|
RequestRetryService, SonarrService, TesterService, ValidationService,
|
||||||
} from "../services";
|
} from "../services";
|
||||||
|
|
||||||
import { PipeModule } from "../pipes/pipe.module";
|
import { PipeModule } from "../pipes/pipe.module";
|
||||||
|
@ -19,6 +19,7 @@ import { CouchPotatoComponent } from "./couchpotato/couchpotato.component";
|
||||||
import { CustomizationComponent } from "./customization/customization.component";
|
import { CustomizationComponent } from "./customization/customization.component";
|
||||||
import { DogNzbComponent } from "./dognzb/dognzb.component";
|
import { DogNzbComponent } from "./dognzb/dognzb.component";
|
||||||
import { EmbyComponent } from "./emby/emby.component";
|
import { EmbyComponent } from "./emby/emby.component";
|
||||||
|
import { FailedRequestsComponent } from "./failedrequests/failedrequests.component";
|
||||||
import { IssuesComponent } from "./issues/issues.component";
|
import { IssuesComponent } from "./issues/issues.component";
|
||||||
import { JobsComponent } from "./jobs/jobs.component";
|
import { JobsComponent } from "./jobs/jobs.component";
|
||||||
import { LandingPageComponent } from "./landingpage/landingpage.component";
|
import { LandingPageComponent } from "./landingpage/landingpage.component";
|
||||||
|
@ -77,6 +78,7 @@ const routes: Routes = [
|
||||||
{ path: "Newsletter", component: NewsletterComponent, canActivate: [AuthGuard] },
|
{ path: "Newsletter", component: NewsletterComponent, canActivate: [AuthGuard] },
|
||||||
{ path: "Lidarr", component: LidarrComponent, canActivate: [AuthGuard] },
|
{ path: "Lidarr", component: LidarrComponent, canActivate: [AuthGuard] },
|
||||||
{ path: "Vote", component: VoteComponent, canActivate: [AuthGuard] },
|
{ path: "Vote", component: VoteComponent, canActivate: [AuthGuard] },
|
||||||
|
{ path: "FailedRequests", component: FailedRequestsComponent, canActivate: [AuthGuard] },
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
@ -130,6 +132,7 @@ const routes: Routes = [
|
||||||
NewsletterComponent,
|
NewsletterComponent,
|
||||||
LidarrComponent,
|
LidarrComponent,
|
||||||
VoteComponent,
|
VoteComponent,
|
||||||
|
FailedRequestsComponent,
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
RouterModule,
|
RouterModule,
|
||||||
|
@ -149,6 +152,7 @@ const routes: Routes = [
|
||||||
MobileService,
|
MobileService,
|
||||||
NotificationMessageService,
|
NotificationMessageService,
|
||||||
LidarrService,
|
LidarrService,
|
||||||
|
RequestRetryService,
|
||||||
],
|
],
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -54,7 +54,7 @@
|
||||||
<i class="fa fa-music" aria-hidden="true"></i> Music <span class="caret"></span>
|
<i class="fa fa-music" aria-hidden="true"></i> Music <span class="caret"></span>
|
||||||
</a>
|
</a>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Lidarr']">Lidarr (beta)</a></li>
|
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Lidarr']">Lidarr</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
@ -84,6 +84,7 @@
|
||||||
</a>
|
</a>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/About']">About</a></li>
|
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/About']">About</a></li>
|
||||||
|
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/FailedRequests']">Failed Requests</a></li>
|
||||||
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Update']">Update</a></li>
|
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Update']">Update</a></li>
|
||||||
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Jobs']">Jobs</a></li>
|
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Jobs']">Jobs</a></li>
|
||||||
<!-- <li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Logs']">Logs (Not available)</a></li>
|
<!-- <li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Logs']">Logs (Not available)</a></li>
|
||||||
|
|
|
@ -22,34 +22,40 @@
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="checkbox">
|
<div class="checkbox">
|
||||||
<input type="checkbox" id="v3" formControlName="v3">
|
<input type="checkbox" id="v3" formControlName="v3">
|
||||||
<label for="v3">Sonarr V3</label>
|
<label for="v3">V3</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<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
|
||||||
|
|
||||||
|
<i *ngIf="form.get('ip').hasError('required')" class="fa fa-exclamation-circle error-text" pTooltip="The IP/Hostname is required"></i>
|
||||||
|
</label>
|
||||||
|
|
||||||
<input type="text" class="form-control form-control-custom " formControlName="ip" id="Ip" name="Ip"
|
<input type="text" class="form-control form-control-custom " formControlName="ip" id="Ip" name="Ip"
|
||||||
placeholder="localhost" [ngClass]="{'form-error': form.get('ip').hasError('required')}">
|
placeholder="localhost" [ngClass]="{'form-error': form.get('ip').hasError('required')}">
|
||||||
<small *ngIf="form.get('ip').hasError('required')" class="error-text">The IP/Hostname is required</small>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="portNumber" class="control-label">Port</label>
|
<label for="portNumber" class="control-label">Port
|
||||||
|
|
||||||
|
<i *ngIf="form.get('port').hasError('required')" class="fa fa-exclamation-circle error-text" pTooltip="The Port is required"></i>
|
||||||
|
</label>
|
||||||
|
|
||||||
<input type="text" class="form-control form-control-custom " [ngClass]="{'form-error': form.get('port').hasError('required')}"
|
<input type="text" class="form-control form-control-custom " [ngClass]="{'form-error': form.get('port').hasError('required')}"
|
||||||
formControlName="port" id="portNumber" name="Port" placeholder="Port Number">
|
formControlName="port" id="portNumber" name="Port" placeholder="Port Number">
|
||||||
<small *ngIf="form.get('port').hasError('required')" class="error-text">The Port is required</small>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<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
|
||||||
|
|
||||||
|
<i *ngIf="form.get('apiKey').hasError('required')" class="fa fa-exclamation-circle error-text" pTooltip="The API Key is required"></i>
|
||||||
|
</label>
|
||||||
|
|
||||||
<input type="text" class="form-control form-control-custom " [ngClass]="{'form-error': form.get('apiKey').hasError('required')}"
|
<input type="text" class="form-control form-control-custom " [ngClass]="{'form-error': form.get('apiKey').hasError('required')}"
|
||||||
formControlName="apiKey" id="ApiKey" name="ApiKey">
|
formControlName="apiKey" id="ApiKey" name="ApiKey">
|
||||||
<small *ngIf="form.get('apiKey').hasError('required')" class="error-text">The API Key is required</small>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="checkbox">
|
<div class="checkbox">
|
||||||
|
@ -68,9 +74,10 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
|
|
||||||
|
|
||||||
<div class="form-group col-md-12">
|
<div class="form-group col-md-12">
|
||||||
<label for="profiles" class="control-label">Quality Profiles</label>
|
<label for="profiles" class="control-label">Quality Profiles
|
||||||
|
<i *ngIf="form.get('qualityProfile').hasError('required')" class="fa fa-exclamation-circle error-text" pTooltip="A Default Quality Profile is required"></i>
|
||||||
|
</label>
|
||||||
<div id="profiles">
|
<div id="profiles">
|
||||||
<select class="form-control form-control-custom col-md-5 form-half" [ngClass]="{'form-error': form.get('qualityProfile').hasError('required')}"
|
<select class="form-control form-control-custom col-md-5 form-half" [ngClass]="{'form-error': form.get('qualityProfile').hasError('required')}"
|
||||||
id="select" formControlName="qualityProfile">
|
id="select" formControlName="qualityProfile">
|
||||||
|
@ -78,11 +85,12 @@
|
||||||
</select>
|
</select>
|
||||||
<button type="button" (click)="getProfiles(form)" class="btn btn-primary-outline col-md-4 col-md-push-1">
|
<button type="button" (click)="getProfiles(form)" class="btn btn-primary-outline col-md-4 col-md-push-1">
|
||||||
Load Qualities <span *ngIf="profilesRunning" class="fa fa-spinner fa-spin"></span></button>
|
Load Qualities <span *ngIf="profilesRunning" class="fa fa-spinner fa-spin"></span></button>
|
||||||
<small *ngIf="form.get('qualityProfile').hasError('required')" class="error-text">A Default Quality
|
|
||||||
Profile is required</small>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="form-group col-md-12">
|
<div class="form-group col-md-12">
|
||||||
<label for="select" class="control-label">Quality Profiles (Anime)</label>
|
<label for="select" class="control-label">Quality Profiles (Anime)</label>
|
||||||
<div id="qualityProfileAnime">
|
<div id="qualityProfileAnime">
|
||||||
|
@ -94,7 +102,10 @@
|
||||||
|
|
||||||
|
|
||||||
<div class="form-group col-md-12">
|
<div class="form-group col-md-12">
|
||||||
<label for="rootFolders" class="control-label">Default Root Folders</label>
|
<label for="rootFolders" class="control-label">Default Root Folders
|
||||||
|
|
||||||
|
<i *ngIf="form.get('rootPath').hasError('required')" class="fa fa-exclamation-circle error-text" pTooltip="A Default Root Path is required"></i>
|
||||||
|
</label>
|
||||||
<div id="rootFolders">
|
<div id="rootFolders">
|
||||||
<select class="form-control form-control-custom col-md-5 form-half" formControlName="rootPath"
|
<select class="form-control form-control-custom col-md-5 form-half" formControlName="rootPath"
|
||||||
[ngClass]="{'form-error': form.get('rootPath').hasError('required')}">
|
[ngClass]="{'form-error': form.get('rootPath').hasError('required')}">
|
||||||
|
@ -102,9 +113,6 @@
|
||||||
</select>
|
</select>
|
||||||
<button type="button" (click)="getRootFolders(form)" class="btn btn-primary-outline col-md-4 col-md-push-1">
|
<button type="button" (click)="getRootFolders(form)" class="btn btn-primary-outline col-md-4 col-md-push-1">
|
||||||
Load Folders <span *ngIf="rootFoldersRunning" class="fa fa-spinner fa-spin"></span></button>
|
Load Folders <span *ngIf="rootFoldersRunning" class="fa fa-spinner fa-spin"></span></button>
|
||||||
<small *ngIf="form.get('rootPath').hasError('required')" class="error-text">A Default Root Path
|
|
||||||
is
|
|
||||||
required</small>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -119,17 +127,17 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group col-md-12" *ngIf="form.controls.v3.value">
|
<div class="form-group col-md-12" *ngIf="form.controls.v3.value">
|
||||||
<label for="select" class="control-label">Language Profiles</label>
|
<label for="select" class="control-label">Language Profiles
|
||||||
|
<i *ngIf="form.get('languageProfile').hasError('required')" class="fa fa-exclamation-circle error-text" pTooltip="A Language Profile is required"></i>
|
||||||
|
</label>
|
||||||
<div id="langaugeProfile">
|
<div id="langaugeProfile">
|
||||||
<select formControlName="langaugeProfile" class="form-control form-control-custom col-md-5 form-half"
|
<select formControlName="languageProfile" class="form-control form-control-custom col-md-5 form-half"
|
||||||
id="select" [ngClass]="{'form-error': form.get('langaugeProfile').hasError('required')}">
|
id="select" [ngClass]="{'form-error': form.get('languageProfile').hasError('required')}">
|
||||||
<option *ngFor="let lang of languageProfiles" value="{{lang.id}}">{{lang.name}}</option>
|
<option *ngFor="let lang of languageProfiles" [ngValue]="lang.id">{{lang.name}}</option>
|
||||||
</select>
|
</select>
|
||||||
<button (click)="getLanguageProfiles(form)" type="button" class="btn btn-primary-outline col-md-4 col-md-push-1">Load
|
<button (click)="getLanguageProfiles(form)" type="button" class="btn btn-primary-outline col-md-4 col-md-push-1">Load
|
||||||
Languages <span *ngIf="langRunning" class="fa fa-spinner fa-spin"> </span></button>
|
Languages <span *ngIf="langRunning" class="fa fa-spinner fa-spin"> </span></button>
|
||||||
|
|
||||||
<small *ngIf="form.get('langaugeProfile').hasError('required')" class="error-text">A Default
|
|
||||||
Langauage Profile is required</small>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -51,7 +51,7 @@ export class SonarrComponent implements OnInit {
|
||||||
addOnly: [x.addOnly],
|
addOnly: [x.addOnly],
|
||||||
seasonFolders: [x.seasonFolders],
|
seasonFolders: [x.seasonFolders],
|
||||||
v3: [x.v3],
|
v3: [x.v3],
|
||||||
langaugeProfile: [x.languageProfile],
|
languageProfile: [x.languageProfile],
|
||||||
});
|
});
|
||||||
|
|
||||||
if (x.qualityProfile) {
|
if (x.qualityProfile) {
|
||||||
|
@ -63,6 +63,9 @@ export class SonarrComponent implements OnInit {
|
||||||
if(x.languageProfile) {
|
if(x.languageProfile) {
|
||||||
this.getLanguageProfiles(this.form);
|
this.getLanguageProfiles(this.form);
|
||||||
}
|
}
|
||||||
|
if(x.v3) {
|
||||||
|
this.form.controls.languageProfile.setValidators([Validators.required]);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
this.rootFolders = [];
|
this.rootFolders = [];
|
||||||
this.qualities = [];
|
this.qualities = [];
|
||||||
|
|
|
@ -843,6 +843,7 @@ body {
|
||||||
padding: 10px 15px;
|
padding: 10px 15px;
|
||||||
border-bottom: 1px solid transparent;
|
border-bottom: 1px solid transparent;
|
||||||
background: $form-color;
|
background: $form-color;
|
||||||
|
height: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-header > a {
|
.card-header > a {
|
||||||
|
|
92
src/Ombi/Controllers/RequestRetryController.cs
Normal file
92
src/Ombi/Controllers/RequestRetryController.cs
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Ombi.Store.Entities.Requests;
|
||||||
|
using Ombi.Store.Repository;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Ombi.Attributes;
|
||||||
|
using Ombi.Models;
|
||||||
|
using Ombi.Store.Entities;
|
||||||
|
using Ombi.Store.Repository.Requests;
|
||||||
|
|
||||||
|
namespace Ombi.Controllers
|
||||||
|
{
|
||||||
|
[ApiV1]
|
||||||
|
[Admin]
|
||||||
|
[Produces("application/json")]
|
||||||
|
public class RequestRetryController : Controller
|
||||||
|
{
|
||||||
|
public RequestRetryController(IRepository<RequestQueue> requestQueue, IMovieRequestRepository movieRepo,
|
||||||
|
ITvRequestRepository tvRepo, IMusicRequestRepository musicRepo)
|
||||||
|
{
|
||||||
|
_requestQueueRepository = requestQueue;
|
||||||
|
_movieRequestRepository = movieRepo;
|
||||||
|
_tvRequestRepository = tvRepo;
|
||||||
|
_musicRequestRepository = musicRepo;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly IRepository<RequestQueue> _requestQueueRepository;
|
||||||
|
private readonly IMovieRequestRepository _movieRequestRepository;
|
||||||
|
private readonly ITvRequestRepository _tvRequestRepository;
|
||||||
|
private readonly IMusicRequestRepository _musicRequestRepository;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get's all the failed requests that are currently in the queue
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<IEnumerable<FailedRequestViewModel>> GetFailedRequests()
|
||||||
|
{
|
||||||
|
var failed = await _requestQueueRepository.GetAll().Where(x => !x.Completed.HasValue).ToListAsync();
|
||||||
|
|
||||||
|
var vm = new List<FailedRequestViewModel>();
|
||||||
|
foreach (var f in failed)
|
||||||
|
{
|
||||||
|
var vmModel = new FailedRequestViewModel
|
||||||
|
{
|
||||||
|
RequestId = f.RequestId,
|
||||||
|
RetryCount = f.RetryCount,
|
||||||
|
Dts = f.Dts,
|
||||||
|
Error = f.Error,
|
||||||
|
FailedId = f.Id,
|
||||||
|
Type = f.Type
|
||||||
|
};
|
||||||
|
|
||||||
|
if (f.Type == RequestType.Movie)
|
||||||
|
{
|
||||||
|
var request = await _movieRequestRepository.Find(f.RequestId);
|
||||||
|
vmModel.Title = request.Title;
|
||||||
|
vmModel.ReleaseYear = request.ReleaseDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (f.Type == RequestType.Album)
|
||||||
|
{
|
||||||
|
var request = await _musicRequestRepository.Find(f.RequestId);
|
||||||
|
vmModel.Title = request.Title;
|
||||||
|
vmModel.ReleaseYear = request.ReleaseDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (f.Type == RequestType.TvShow)
|
||||||
|
{
|
||||||
|
var request = await _tvRequestRepository.GetChild().Include(x => x.ParentRequest).FirstOrDefaultAsync(x => x.Id == f.RequestId);
|
||||||
|
vmModel.Title = request.Title;
|
||||||
|
vmModel.ReleaseYear = request.ParentRequest.ReleaseDate;
|
||||||
|
}
|
||||||
|
vm.Add(vmModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
return vm;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpDelete("{queueId:int}")]
|
||||||
|
public async Task<IActionResult> Delete(int queueId)
|
||||||
|
{
|
||||||
|
var queueItem = await _requestQueueRepository.GetAll().FirstOrDefaultAsync(x => x.Id == queueId);
|
||||||
|
await _requestQueueRepository.Delete(queueItem);
|
||||||
|
return Json(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -274,19 +274,6 @@ namespace Ombi.Controllers
|
||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the content of the theme available
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="url"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
[HttpGet("themecontent")]
|
|
||||||
[AllowAnonymous]
|
|
||||||
public async Task<IActionResult> GetThemeContent([FromQuery]string url)
|
|
||||||
{
|
|
||||||
var css = await _githubApi.GetThemesRawContent(url);
|
|
||||||
return Content(css, "text/css");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the Sonarr Settings.
|
/// Gets the Sonarr Settings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -520,6 +507,7 @@ namespace Ombi.Controllers
|
||||||
j.Newsletter = j.Newsletter.HasValue() ? j.Newsletter : JobSettingsHelper.Newsletter(j);
|
j.Newsletter = j.Newsletter.HasValue() ? j.Newsletter : JobSettingsHelper.Newsletter(j);
|
||||||
j.LidarrArtistSync = j.LidarrArtistSync.HasValue() ? j.LidarrArtistSync : JobSettingsHelper.LidarrArtistSync(j);
|
j.LidarrArtistSync = j.LidarrArtistSync.HasValue() ? j.LidarrArtistSync : JobSettingsHelper.LidarrArtistSync(j);
|
||||||
j.IssuesPurge = j.IssuesPurge.HasValue() ? j.IssuesPurge : JobSettingsHelper.IssuePurge(j);
|
j.IssuesPurge = j.IssuesPurge.HasValue() ? j.IssuesPurge : JobSettingsHelper.IssuePurge(j);
|
||||||
|
j.RetryRequests = j.RetryRequests.HasValue() ? j.RetryRequests : JobSettingsHelper.ResendFailedRequests(j);
|
||||||
|
|
||||||
return j;
|
return j;
|
||||||
}
|
}
|
||||||
|
|
17
src/Ombi/Models/FailedRequestViewModel.cs
Normal file
17
src/Ombi/Models/FailedRequestViewModel.cs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
using System;
|
||||||
|
using Ombi.Store.Entities;
|
||||||
|
|
||||||
|
namespace Ombi.Models
|
||||||
|
{
|
||||||
|
public class FailedRequestViewModel
|
||||||
|
{
|
||||||
|
public int FailedId { get; set; }
|
||||||
|
public string Title { get; set; }
|
||||||
|
public DateTime ReleaseYear { get; set; }
|
||||||
|
public int RequestId { get; set; }
|
||||||
|
public RequestType Type { get; set; }
|
||||||
|
public DateTime Dts { get; set; }
|
||||||
|
public string Error { get; set; }
|
||||||
|
public int RetryCount { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
@using Ombi.Core.Settings
|
@using Ombi.Core.Settings
|
||||||
|
@using Ombi.Helpers
|
||||||
@using Ombi.Settings.Settings.Models
|
@using Ombi.Settings.Settings.Models
|
||||||
@inject ISettingsService<OmbiSettings> Settings
|
@inject ISettingsService<OmbiSettings> Settings
|
||||||
@inject ISettingsService<CustomizationSettings> CustomizationSettings
|
@inject ISettingsService<CustomizationSettings> CustomizationSettings
|
||||||
|
@ -105,29 +106,12 @@
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@{
|
@{
|
||||||
if (customization.HasPresetTheme)
|
if (customization.CustomCss.HasValue())
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(baseUrl))
|
|
||||||
{
|
|
||||||
if (!customization.PresetThemeContent.Contains("/" + baseUrl))
|
|
||||||
{
|
|
||||||
var index = customization.PresetThemeContent.IndexOf("/api/");
|
|
||||||
if (index > 0)
|
|
||||||
{
|
|
||||||
customization.PresetThemeContent = customization.PresetThemeContent.Insert(index, "/" + baseUrl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@Html.Raw(customization.PresetThemeContent)
|
@Html.Raw(customization.CustomCss)
|
||||||
</style>
|
</style>
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(customization.CustomCssLink))
|
|
||||||
{
|
|
||||||
<link rel="stylesheet" href="@customization.CustomCssLink" asp-append-version="true" />
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@RenderBody()
|
@RenderBody()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue