mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-07-16 02:02:55 -07:00
Got most of the requesting stuff done! !wip #2313
This commit is contained in:
parent
cda7c0fe4c
commit
b72905ab4a
26 changed files with 1061 additions and 61 deletions
|
@ -16,5 +16,10 @@ namespace Ombi.Api.Lidarr
|
|||
Task<AlbumLookup> GetAlbumByForeignId(string foreignArtistId, string apiKey, string baseUrl);
|
||||
Task<List<ArtistResult>> GetArtists(string apiKey, string baseUrl);
|
||||
Task<List<AlbumResponse>> GetAllAlbums(string apiKey, string baseUrl);
|
||||
Task<ArtistResult> AddArtist(ArtistAdd artist, string apiKey, string baseUrl);
|
||||
Task<AlbumResponse> MontiorAlbum(int albumId, string apiKey, string baseUrl);
|
||||
Task<List<AlbumResponse>> GetAllAlbumsByArtistId(int artistId, string apiKey, string baseUrl);
|
||||
Task<List<MetadataProfile>> GetMetadataProfile(string apiKey, string baseUrl);
|
||||
Task<List<LanguageProfiles>> GetLanguageProfile(string apiKey, string baseUrl);
|
||||
}
|
||||
}
|
|
@ -63,13 +63,13 @@ namespace Ombi.Api.Lidarr
|
|||
return Api.Request<ArtistResult>(request);
|
||||
}
|
||||
|
||||
public Task<ArtistResult> GetArtistByForeignId(string foreignArtistId, string apiKey, string baseUrl)
|
||||
public async Task<ArtistResult> GetArtistByForeignId(string foreignArtistId, string apiKey, string baseUrl)
|
||||
{
|
||||
var request = new Request($"{ApiVersion}/artist/lookup", baseUrl, HttpMethod.Get);
|
||||
|
||||
request.AddQueryString("term", $"lidarr:{foreignArtistId}");
|
||||
AddHeaders(request, apiKey);
|
||||
return Api.Request<ArtistResult>(request);
|
||||
return (await Api.Request<List<ArtistResult>>(request)).FirstOrDefault();
|
||||
}
|
||||
|
||||
public async Task<AlbumLookup> GetAlbumByForeignId(string foreignArtistId, string apiKey, string baseUrl)
|
||||
|
@ -105,6 +105,48 @@ namespace Ombi.Api.Lidarr
|
|||
return Api.Request<List<AlbumResponse>>(request);
|
||||
}
|
||||
|
||||
public Task<ArtistResult> AddArtist(ArtistAdd artist, string apiKey, string baseUrl)
|
||||
{
|
||||
var request = new Request($"{ApiVersion}/artist", baseUrl, HttpMethod.Post);
|
||||
request.AddJsonBody(artist);
|
||||
AddHeaders(request, apiKey);
|
||||
return Api.Request<ArtistResult>(request);
|
||||
}
|
||||
|
||||
public Task<AlbumResponse> MontiorAlbum(int albumId, string apiKey, string baseUrl)
|
||||
{
|
||||
var request = new Request($"{ApiVersion}/album/monitor", baseUrl, HttpMethod.Put);
|
||||
request.AddJsonBody(new
|
||||
{
|
||||
albumIds = new[] { albumId },
|
||||
monitored = true
|
||||
});
|
||||
AddHeaders(request, apiKey);
|
||||
return Api.Request<AlbumResponse>(request);
|
||||
}
|
||||
|
||||
public Task<List<AlbumResponse>> GetAllAlbumsByArtistId(int artistId, string apiKey, string baseUrl)
|
||||
{
|
||||
var request = new Request($"{ApiVersion}/album", baseUrl, HttpMethod.Get);
|
||||
request.AddQueryString("artistId", artistId.ToString());
|
||||
AddHeaders(request, apiKey);
|
||||
return Api.Request<List<AlbumResponse>>(request);
|
||||
}
|
||||
|
||||
public Task<List<LanguageProfiles>> GetLanguageProfile(string apiKey, string baseUrl)
|
||||
{
|
||||
var request = new Request($"{ApiVersion}/languageprofile", baseUrl, HttpMethod.Get);
|
||||
AddHeaders(request, apiKey);
|
||||
return Api.Request<List<LanguageProfiles>>(request);
|
||||
}
|
||||
|
||||
public Task<List<MetadataProfile>> GetMetadataProfile(string apiKey, string baseUrl)
|
||||
{
|
||||
var request = new Request($"{ApiVersion}/metadataprofile", baseUrl, HttpMethod.Get);
|
||||
AddHeaders(request, apiKey);
|
||||
return Api.Request<List<MetadataProfile>>(request);
|
||||
}
|
||||
|
||||
private void AddHeaders(Request request, string key)
|
||||
{
|
||||
request.AddHeader("X-Api-Key", key);
|
||||
|
|
48
src/Ombi.Api.Lidarr/Models/ArtistAdd.cs
Normal file
48
src/Ombi.Api.Lidarr/Models/ArtistAdd.cs
Normal file
|
@ -0,0 +1,48 @@
|
|||
using System;
|
||||
using System.Net.Mime;
|
||||
|
||||
namespace Ombi.Api.Lidarr.Models
|
||||
{
|
||||
public class ArtistAdd
|
||||
{
|
||||
public string status { get; set; }
|
||||
public bool ended { get; set; }
|
||||
public string artistName { get; set; }
|
||||
public string foreignArtistId { get; set; }
|
||||
public int tadbId { get; set; }
|
||||
public int discogsId { get; set; }
|
||||
public string overview { get; set; }
|
||||
public string disambiguation { get; set; }
|
||||
public Link[] links { get; set; }
|
||||
public Image[] images { get; set; }
|
||||
public string remotePoster { get; set; }
|
||||
public int qualityProfileId { get; set; }
|
||||
public int languageProfileId { get; set; }
|
||||
public int metadataProfileId { get; set; }
|
||||
public bool albumFolder { get; set; }
|
||||
public bool monitored { get; set; }
|
||||
public string cleanName { get; set; }
|
||||
public string sortName { get; set; }
|
||||
public object[] tags { get; set; }
|
||||
public DateTime added { get; set; }
|
||||
public Ratings ratings { get; set; }
|
||||
public Statistics statistics { get; set; }
|
||||
public Addoptions addOptions { get; set; }
|
||||
public string rootFolderPath { get; set; }
|
||||
}
|
||||
|
||||
public class Addoptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Future = 1
|
||||
/// Missing = 2
|
||||
/// Existing = 3
|
||||
/// First = 5
|
||||
/// Latest = 4
|
||||
/// None = 6
|
||||
/// </summary>
|
||||
public int selectedOption { get; set; }
|
||||
public bool monitored { get; set; }
|
||||
public bool searchForMissingAlbums { get; set; }
|
||||
}
|
||||
}
|
8
src/Ombi.Api.Lidarr/Models/LanguageProfiles.cs
Normal file
8
src/Ombi.Api.Lidarr/Models/LanguageProfiles.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace Ombi.Api.Lidarr.Models
|
||||
{
|
||||
public class LanguageProfiles
|
||||
{
|
||||
public string name { get; set; }
|
||||
public int id { get; set; }
|
||||
}
|
||||
}
|
8
src/Ombi.Api.Lidarr/Models/MetadataProfile.cs
Normal file
8
src/Ombi.Api.Lidarr/Models/MetadataProfile.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace Ombi.Api.Lidarr.Models
|
||||
{
|
||||
public class MetadataProfile
|
||||
{
|
||||
public string name { get; set; }
|
||||
public int id { get; set; }
|
||||
}
|
||||
}
|
|
@ -15,6 +15,7 @@ using Ombi.Core.Authentication;
|
|||
using Ombi.Core.Engine.Interfaces;
|
||||
using Ombi.Core.Models.UI;
|
||||
using Ombi.Core.Rule.Interfaces;
|
||||
using Ombi.Core.Senders;
|
||||
using Ombi.Core.Settings;
|
||||
using Ombi.Settings.Settings.Models;
|
||||
using Ombi.Settings.Settings.Models.External;
|
||||
|
@ -29,11 +30,11 @@ namespace Ombi.Core.Engine
|
|||
INotificationHelper helper, IRuleEvaluator r, ILogger<MusicRequestEngine> log,
|
||||
OmbiUserManager manager, IRepository<RequestLog> rl, ICacheService cache,
|
||||
ISettingsService<OmbiSettings> ombiSettings, IRepository<RequestSubscription> sub, ILidarrApi lidarr,
|
||||
ISettingsService<LidarrSettings> lidarrSettings)
|
||||
ISettingsService<LidarrSettings> lidarrSettings, IMusicSender sender)
|
||||
: base(user, requestService, r, manager, cache, ombiSettings, sub)
|
||||
{
|
||||
NotificationHelper = helper;
|
||||
//Sender = sender;
|
||||
_musicSender = sender;
|
||||
Logger = log;
|
||||
_requestLog = rl;
|
||||
_lidarrApi = lidarr;
|
||||
|
@ -46,6 +47,7 @@ namespace Ombi.Core.Engine
|
|||
private readonly IRepository<RequestLog> _requestLog;
|
||||
private readonly ISettingsService<LidarrSettings> _lidarrSettings;
|
||||
private readonly ILidarrApi _lidarrApi;
|
||||
private readonly IMusicSender _musicSender;
|
||||
|
||||
/// <summary>
|
||||
/// Requests the Album.
|
||||
|
@ -79,7 +81,8 @@ namespace Ombi.Core.Engine
|
|||
RequestedUserId = userDetails.Id,
|
||||
Title = album.title,
|
||||
Disk = album.images?.FirstOrDefault(x => x.coverType.Equals("disc"))?.url,
|
||||
Cover = album.images?.FirstOrDefault(x => x.coverType.Equals("cover"))?.url
|
||||
Cover = album.images?.FirstOrDefault(x => x.coverType.Equals("cover"))?.url,
|
||||
ForeignArtistId = album?.artist?.foreignArtistId ?? string.Empty
|
||||
};
|
||||
if (requestModel.Cover.IsNullOrEmpty())
|
||||
{
|
||||
|
@ -341,26 +344,25 @@ namespace Ombi.Core.Engine
|
|||
|
||||
if (request.Approved)
|
||||
{
|
||||
//TODO
|
||||
//var result = await Sender.Send(request);
|
||||
//if (result.Success && result.Sent)
|
||||
//{
|
||||
// return new RequestEngineResult
|
||||
// {
|
||||
// Result = true
|
||||
// };
|
||||
//}
|
||||
var result = await _musicSender.Send(request);
|
||||
if (result.Success && result.Sent)
|
||||
{
|
||||
return new RequestEngineResult
|
||||
{
|
||||
Result = true
|
||||
};
|
||||
}
|
||||
|
||||
//if (!result.Success)
|
||||
//{
|
||||
// Logger.LogWarning("Tried auto sending movie but failed. Message: {0}", result.Message);
|
||||
// return new RequestEngineResult
|
||||
// {
|
||||
// Message = result.Message,
|
||||
// ErrorMessage = result.Message,
|
||||
// Result = false
|
||||
// };
|
||||
//}
|
||||
if (!result.Success)
|
||||
{
|
||||
Logger.LogWarning("Tried auto sending album but failed. Message: {0}", result.Message);
|
||||
return new RequestEngineResult
|
||||
{
|
||||
Message = result.Message,
|
||||
ErrorMessage = result.Message,
|
||||
Result = false
|
||||
};
|
||||
}
|
||||
|
||||
// If there are no providers then it's successful but movie has not been sent
|
||||
}
|
||||
|
|
10
src/Ombi.Core/Senders/IMusicSender.cs
Normal file
10
src/Ombi.Core/Senders/IMusicSender.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
using System.Threading.Tasks;
|
||||
using Ombi.Store.Entities.Requests;
|
||||
|
||||
namespace Ombi.Core.Senders
|
||||
{
|
||||
public interface IMusicSender
|
||||
{
|
||||
Task<SenderResult> Send(AlbumRequest model);
|
||||
}
|
||||
}
|
110
src/Ombi.Core/Senders/MusicSender.cs
Normal file
110
src/Ombi.Core/Senders/MusicSender.cs
Normal file
|
@ -0,0 +1,110 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Ombi.Api.Lidarr;
|
||||
using Ombi.Api.Lidarr.Models;
|
||||
using Ombi.Api.Radarr;
|
||||
using Ombi.Core.Settings;
|
||||
using Ombi.Helpers;
|
||||
using Ombi.Settings.Settings.Models.External;
|
||||
using Ombi.Store.Entities.Requests;
|
||||
using Serilog;
|
||||
|
||||
namespace Ombi.Core.Senders
|
||||
{
|
||||
public class MusicSender : IMusicSender
|
||||
{
|
||||
public MusicSender(ISettingsService<LidarrSettings> lidarr, ILidarrApi lidarrApi)
|
||||
{
|
||||
_lidarrSettings = lidarr;
|
||||
_lidarrApi = lidarrApi;
|
||||
}
|
||||
|
||||
private readonly ISettingsService<LidarrSettings> _lidarrSettings;
|
||||
private readonly ILidarrApi _lidarrApi;
|
||||
|
||||
public async Task<SenderResult> Send(AlbumRequest model)
|
||||
{
|
||||
var settings = await _lidarrSettings.GetSettingsAsync();
|
||||
if (settings.Enabled)
|
||||
{
|
||||
return await SendToLidarr(model, settings);
|
||||
}
|
||||
|
||||
return new SenderResult { Success = false, Sent = false, Message = "Lidarr is not enabled" };
|
||||
}
|
||||
|
||||
private async Task<SenderResult> SendToLidarr(AlbumRequest model, LidarrSettings settings)
|
||||
{
|
||||
var qualityToUse = int.Parse(settings.DefaultQualityProfile);
|
||||
//if (model.QualityOverride > 0)
|
||||
//{
|
||||
// qualityToUse = model.QualityOverride;
|
||||
//}
|
||||
|
||||
var rootFolderPath = /*model.RootPathOverride <= 0 ?*/ settings.DefaultRootPath /*: await RadarrRootPath(model.RootPathOverride, settings)*/;
|
||||
|
||||
// Need to get the artist
|
||||
var artist = await _lidarrApi.GetArtistByForeignId(model.ForeignArtistId, settings.ApiKey, settings.FullUri);
|
||||
|
||||
if (artist == null || artist.id <= 0)
|
||||
{
|
||||
// Create artist
|
||||
var newArtist = new ArtistAdd
|
||||
{
|
||||
foreignArtistId = model.ForeignArtistId,
|
||||
addOptions = new Addoptions
|
||||
{
|
||||
monitored = true,
|
||||
searchForMissingAlbums = false,
|
||||
selectedOption = 6 // None
|
||||
},
|
||||
added = DateTime.Now,
|
||||
monitored = true,
|
||||
albumFolder = settings.AlbumFolder,
|
||||
artistName = model.ArtistName,
|
||||
cleanName = model.ArtistName.ToLowerInvariant().RemoveSpaces(),
|
||||
images = new Image[] { },
|
||||
languageProfileId = settings.LanguageProfileId,
|
||||
links = new Link[] {},
|
||||
metadataProfileId = settings.MetadataProfileId,
|
||||
qualityProfileId = qualityToUse,
|
||||
rootFolderPath = rootFolderPath,
|
||||
};
|
||||
|
||||
var result = await _lidarrApi.AddArtist(newArtist, settings.ApiKey, settings.FullUri);
|
||||
if (result != null && result.id > 0)
|
||||
{
|
||||
// Setup the albums
|
||||
await SetupAlbum(model, result, settings);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await SetupAlbum(model, artist, settings);
|
||||
}
|
||||
|
||||
return new SenderResult { Success = false, Sent = false, Message = "Album is already monitored" };
|
||||
}
|
||||
|
||||
private async Task<SenderResult> SetupAlbum(AlbumRequest model, ArtistResult artist, LidarrSettings settings)
|
||||
{
|
||||
// Get the album id
|
||||
var albums = await _lidarrApi.GetAllAlbumsByArtistId(artist.id, settings.ApiKey, settings.FullUri);
|
||||
// Get the album we want.
|
||||
var album = albums.FirstOrDefault(x =>
|
||||
x.foreignAlbumId.Equals(model.ForeignAlbumId, StringComparison.InvariantCultureIgnoreCase));
|
||||
if (album == null)
|
||||
{
|
||||
return new SenderResult { Message = "Could not find album in Lidarr", Sent = false, Success = false };
|
||||
}
|
||||
|
||||
var result = await _lidarrApi.MontiorAlbum(album.id, settings.ApiKey, settings.FullUri);
|
||||
if (result.monitored)
|
||||
{
|
||||
return new SenderResult {Message = "Album has been requested!", Sent = true, Success = true};
|
||||
}
|
||||
return new SenderResult { Message = "Could not set album to monitored", Sent = false, Success = false };
|
||||
}
|
||||
}
|
||||
}
|
|
@ -88,6 +88,7 @@ namespace Ombi.DependencyInjection
|
|||
services.AddTransient<IMusicSearchEngine, MusicSearchEngine>();
|
||||
services.AddTransient<IMusicRequestEngine, MusicRequestEngine>();
|
||||
services.AddTransient<ITvSender, TvSender>();
|
||||
services.AddTransient<IMusicSender, MusicSender>();
|
||||
services.AddTransient<IMassEmailSender, MassEmailSender>();
|
||||
services.AddTransient<IPlexOAuthManager, PlexOAuthManager>();
|
||||
}
|
||||
|
|
|
@ -75,5 +75,10 @@ namespace Ombi.Helpers
|
|||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static string RemoveSpaces(this string str)
|
||||
{
|
||||
return str.Replace(" ", "");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,5 +8,8 @@ namespace Ombi.Settings.Settings.Models.External
|
|||
public string ApiKey { get; set; }
|
||||
public string DefaultQualityProfile { get; set; }
|
||||
public string DefaultRootPath { get; set; }
|
||||
public bool AlbumFolder { get; set; }
|
||||
public int LanguageProfileId { get; set; }
|
||||
public int MetadataProfileId { get; set; }
|
||||
}
|
||||
}
|
|
@ -8,6 +8,11 @@ export interface IRadarrProfile {
|
|||
id: number;
|
||||
}
|
||||
|
||||
export interface IProfiles {
|
||||
name: string;
|
||||
id: number;
|
||||
}
|
||||
|
||||
export interface IMinimumAvailability {
|
||||
value: string;
|
||||
name: string;
|
||||
|
|
|
@ -23,13 +23,14 @@ export interface IMovieRequests extends IFullBaseRequest {
|
|||
export interface IAlbumRequest extends IBaseRequest {
|
||||
foreignAlbumId: string;
|
||||
foreignArtistId: string;
|
||||
Disk: string;
|
||||
disk: string;
|
||||
cover: string;
|
||||
releaseDate: Date;
|
||||
artistName: string;
|
||||
|
||||
subscribed: boolean;
|
||||
showSubscribe: boolean;
|
||||
background: any;
|
||||
}
|
||||
|
||||
export interface IAlbumRequestModel {
|
||||
|
|
|
@ -90,6 +90,9 @@ export interface ILidarrSettings extends IExternalSettings {
|
|||
defaultQualityProfile: string;
|
||||
defaultRootPath: string;
|
||||
fullRootPath: string;
|
||||
metadataProfileId: number;
|
||||
languageProfileId: number;
|
||||
albumFolder: boolean;
|
||||
}
|
||||
|
||||
export interface ILandingPageSettings extends ISettings {
|
||||
|
|
|
@ -0,0 +1,270 @@
|
|||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<input type="text" id="search" class="form-control form-control-custom searchwidth" placeholder="Search" (keyup)="search($event)">
|
||||
<span class="input-group-btn">
|
||||
<button id="filterBtn" class="btn btn-sm btn-info-outline" (click)="filterDisplay = !filterDisplay">
|
||||
<i class="fa fa-filter"></i> {{ 'Requests.Filter' | translate }}
|
||||
</button>
|
||||
|
||||
|
||||
<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-sort"></i> {{ 'Requests.Sort' | translate }}
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" aria-labelledby="dropdownMenu2">
|
||||
<li>
|
||||
<a (click)="setOrder(OrderType.RequestedDateAsc, $event)">{{ 'Requests.SortRequestDateAsc' | translate }}
|
||||
|
||||
</a>
|
||||
<a class="active" (click)="setOrder(OrderType.RequestedDateDesc, $event)">{{ 'Requests.SortRequestDateDesc' | translate }}
|
||||
|
||||
</a>
|
||||
<a (click)="setOrder(OrderType.TitleAsc, $event)">{{ 'Requests.SortTitleAsc' | translate}}
|
||||
|
||||
</a>
|
||||
<a (click)="setOrder(OrderType.TitleDesc, $event)">{{ 'Requests.SortTitleDesc' | translate}}
|
||||
|
||||
</a>
|
||||
<a (click)="setOrder(OrderType.StatusAsc, $event)">{{ 'Requests.SortStatusAsc' | translate}}
|
||||
|
||||
</a>
|
||||
<a (click)="setOrder(OrderType.StatusDesc, $event)">{{ 'Requests.SortStatusDesc' | translate}}
|
||||
|
||||
</a>
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
</span>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<br />
|
||||
|
||||
|
||||
<div>
|
||||
<div *ngFor="let request of albumRequests" class="col-md-4">
|
||||
<div class="row">
|
||||
<div class="album-bg backdrop" [style.background-image]="request.background"></div>
|
||||
<div class="album-tint" style="background-image: linear-gradient(to bottom, rgba(0,0,0,0.6) 0%,rgba(0,0,0,0.6) 100%);"></div>
|
||||
|
||||
<div class="col-sm-12 small-padding">
|
||||
<img *ngIf="request.disk" class="img-responsive poster album-cover" src="{{request.disk}}" alt="poster">
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-sm-12 small-padding">
|
||||
<div>
|
||||
<h4>
|
||||
<a href="" target="_blank">
|
||||
{{request.title}}
|
||||
</a>
|
||||
|
||||
</h4>
|
||||
<h5>
|
||||
<a href="">
|
||||
{{request.artistName}}
|
||||
</a>
|
||||
</h5>
|
||||
</div>
|
||||
|
||||
<div class="request-info">
|
||||
<div class="request-by">
|
||||
<span>{{ 'Requests.RequestedBy' | translate }} </span>
|
||||
<span *ngIf="!isAdmin">{{request.requestedUser.userName}}</span>
|
||||
<span *ngIf="isAdmin && request.requestedUser.alias">{{request.requestedUser.alias}}</span>
|
||||
<span *ngIf="isAdmin && !request.requestedUser.alias">{{request.requestedUser.userName}}</span>
|
||||
</div>
|
||||
<div class="request-status">
|
||||
<span>{{ 'Requests.Status' | translate }} </span>
|
||||
<span class="label label-success" id="requestedStatusLabel">{{request.status}}</span>
|
||||
</div>
|
||||
|
||||
<div class="requested-status">
|
||||
<span>{{ 'Requests.RequestStatus' | translate }} </span>
|
||||
<span *ngIf="request.available" class="label label-success" id="availableLabel" [translate]="'Common.Available'"></span>
|
||||
<span *ngIf="request.approved && !request.available" id="processingRequestLabel" class="label label-info" [translate]="'Common.ProcessingRequest'"></span>
|
||||
<span *ngIf="request.denied" class="label label-danger" id="requestDeclinedLabel" [translate]="'Common.RequestDenied'"></span>
|
||||
<span *ngIf="request.deniedReason" title="{{request.deniedReason}}">
|
||||
<i class="fa fa-info-circle"></i>
|
||||
</span>
|
||||
<span *ngIf="!request.approved && !request.availble && !request.denied" id="pendingApprovalLabel" class="label label-warning"
|
||||
[translate]="'Common.PendingApproval'"></span>
|
||||
|
||||
</div>
|
||||
<div *ngIf="request.denied" id="requestDenied">
|
||||
{{ 'Requests.Denied' | translate }}
|
||||
<i style="color:red;" class="fa fa-check"></i>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div id="releaseDate">{{ 'Requests.TheatricalRelease' | translate: {date: request.releaseDate | date: 'mediumDate'} }}</div>
|
||||
<div *ngIf="request.digitalReleaseDate" id="digitalReleaseDate">{{ 'Requests.DigitalRelease' | translate: {date: request.digitalReleaseDate | date: 'mediumDate'} }}</div>
|
||||
<div id="requestedDate">{{ 'Requests.RequestDate' | translate }} {{request.requestedDate | date}}</div>
|
||||
<br />
|
||||
</div>
|
||||
<div *ngIf="isAdmin">
|
||||
<div *ngIf="request.qualityOverrideTitle" class="quality-override">{{ 'Requests.QualityOverride' | translate }}
|
||||
<span>{{request.qualityOverrideTitle}} </span>
|
||||
</div>
|
||||
<div *ngIf="request.rootPathOverrideTitle" class="root-override">{{ 'Requests.RootFolderOverride' | translate }}
|
||||
<span>{{request.rootPathOverrideTitle}} </span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="col-sm-12 small-padding">
|
||||
<!-- <div class="row">
|
||||
<div class="col-md-2 col-md-push-6">
|
||||
|
||||
<a *ngIf="request.showSubscribe && !request.subscribed" style="color:white" (click)="subscribe(request)" pTooltip="Subscribe for notifications">
|
||||
<i class="fa fa-rss"></i>
|
||||
</a>
|
||||
<a *ngIf="request.showSubscribe && request.subscribed" style="color:red" (click)="unSubscribe(request)" pTooltip="Unsubscribe notification">
|
||||
<i class="fa fa-rss"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div> -->
|
||||
<div *ngIf="isAdmin">
|
||||
<div *ngIf="!request.approved" id="approveBtn">
|
||||
<form>
|
||||
<button (click)="approve(request)" style="text-align: right" class="btn btn-sm btn-success-outline approve" type="submit">
|
||||
<i class="fa fa-plus"></i> {{ 'Common.Approve' | translate }}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<!--Radarr Root Folder-->
|
||||
<!-- <div *ngIf="radarrRootFolders" class="btn-group btn-split" id="rootFolderBtn">
|
||||
<button type="button" class="btn btn-sm btn-warning-outline">
|
||||
<i class="fa fa-plus"></i> {{ 'Requests.ChangeRootFolder' | translate }}
|
||||
</button>
|
||||
<button type="button" class="btn btn-warning-outline dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="caret"></span>
|
||||
<span class="sr-only">Toggle Dropdown</span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li *ngFor="let folder of radarrRootFolders">
|
||||
<a href="#" (click)="selectRootFolder(request, folder, $event)">{{folder.path}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div> -->
|
||||
|
||||
<!--Radarr Quality Profiles -->
|
||||
<!-- <div *ngIf="radarrProfiles" class="btn-group btn-split" id="changeQualityBtn">
|
||||
<button type="button" class="btn btn-sm btn-warning-outline">
|
||||
<i class="fa fa-plus"></i> {{ 'Requests.ChangeQualityProfile' | translate }}
|
||||
</button>
|
||||
<button type="button" class="btn btn-warning-outline dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="caret"></span>
|
||||
<span class="sr-only">Toggle Dropdown</span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li *ngFor="let profile of radarrProfiles">
|
||||
<a href="#" (click)="selectQualityProfile(request, profile, $event)">{{profile.name}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div> -->
|
||||
|
||||
<div *ngIf="!request.denied" id="denyBtn">
|
||||
<button type="button" (click)="deny(request)" class="btn btn-sm btn-danger-outline deny">
|
||||
<i class="fa fa-times"></i> {{ 'Requests.Deny' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<form id="removeBtn">
|
||||
<button (click)="removeRequest(request)" style="text-align: right" class="btn btn-sm btn-danger-outline delete">
|
||||
<i class="fa fa-minus"></i> {{ 'Requests.Remove' | translate }}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<form id="markBtnGroup">
|
||||
<button id="unavailableBtn" *ngIf="request.available" (click)="changeAvailability(request, false)" style="text-align: right"
|
||||
value="false" class="btn btn-sm btn-info-outline change">
|
||||
<i class="fa fa-minus"></i> {{ 'Requests.MarkUnavailable' | translate }}
|
||||
</button>
|
||||
<button id="availableBtn" *ngIf="!request.available" (click)="changeAvailability(request, true)" style="text-align: right"
|
||||
value="true" class="btn btn-sm btn-success-outline change">
|
||||
<i class="fa fa-plus"></i> {{ 'Requests.MarkAvailable' | translate }}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
<!-- <div class="dropdown" *ngIf="issueCategories && issuesEnabled" id="issuesBtn">
|
||||
<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> {{ 'Requests.ReportIssue' | translate }}
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
|
||||
<li *ngFor="let cat of issueCategories">
|
||||
<a [routerLink]="" (click)="reportIssue(cat, request)">{{cat.value}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div> -->
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<p-paginator [rows]="10" [totalRecords]="totalMovies" (onPageChange)="paginate($event)"></p-paginator>
|
||||
</div>
|
||||
|
||||
|
||||
<issue-report [movie]="true" [visible]="issuesBarVisible" (visibleChange)="issuesBarVisible = $event;" [title]="issueRequest?.title"
|
||||
[issueCategory]="issueCategorySelected" [id]="issueRequest?.id" [providerId]="issueProviderId"></issue-report>
|
||||
|
||||
|
||||
<p-sidebar [(visible)]="filterDisplay" styleClass="ui-sidebar-md side-back side-small">
|
||||
<h3>{{ 'Requests.Filter' | translate }}</h3>
|
||||
<hr>
|
||||
<div>
|
||||
<h4>{{ 'Filter.FilterHeaderAvailability' | translate }}</h4>
|
||||
<div class="form-group">
|
||||
<div class="radio">
|
||||
<input type="radio" id="Available" name="Availability" (click)="filterAvailability(filterType.Available, $event)">
|
||||
<label for="Available">{{ 'Common.Available' | translate }}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="radio">
|
||||
<input type="radio" id="notAvailable" name="Availability" (click)="filterAvailability(filterType.NotAvailable, $event)">
|
||||
<label for="notAvailable">{{ 'Common.NotAvailable' | translate }}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h4>{{ 'Filter.FilterHeaderRequestStatus' | translate }}</h4>
|
||||
<div class="form-group">
|
||||
<div class="radio">
|
||||
<input type="radio" id="approved" name="Status" (click)="filterStatus(filterType.Approved, $event)">
|
||||
<label for="approved">{{ 'Filter.Approved' | translate }}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="radio">
|
||||
<input type="radio" id="Processing" name="Status" (click)="filterStatus(filterType.Processing, $event)">
|
||||
<label for="Processing">{{ 'Common.ProcessingRequest' | translate }}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="radio">
|
||||
<input type="radio" id="pendingApproval" name="Status" (click)="filterStatus(filterType.PendingApproval, $event)">
|
||||
<label for="pendingApproval">{{ 'Filter.PendingApproval' | translate }}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-sm btn-primary-outline" (click)="clearFilter($event)">
|
||||
<i class="fa fa-filter"></i> {{ 'Filter.ClearFilter' | translate }}</button>
|
||||
</p-sidebar>
|
350
src/Ombi/ClientApp/app/requests/music/musicrequests.component.ts
Normal file
350
src/Ombi/ClientApp/app/requests/music/musicrequests.component.ts
Normal file
|
@ -0,0 +1,350 @@
|
|||
import { PlatformLocation } from "@angular/common";
|
||||
import { Component, Input, OnInit } from "@angular/core";
|
||||
import { DomSanitizer } from "@angular/platform-browser";
|
||||
import { Subject } from "rxjs";
|
||||
import { debounceTime, distinctUntilChanged } from "rxjs/operators";
|
||||
|
||||
import { AuthService } from "../../auth/auth.service";
|
||||
import { FilterType, IAlbumRequest, IFilter, IIssueCategory, IPagenator, OrderType } from "../../interfaces";
|
||||
import { NotificationService, RequestService } from "../../services";
|
||||
|
||||
@Component({
|
||||
selector: "music-requests",
|
||||
templateUrl: "./musicrequests.component.html",
|
||||
})
|
||||
export class MusicRequestsComponent implements OnInit {
|
||||
public albumRequests: IAlbumRequest[];
|
||||
public defaultPoster: string;
|
||||
|
||||
public searchChanged: Subject<string> = new Subject<string>();
|
||||
public searchText: string;
|
||||
|
||||
public isAdmin: boolean; // Also PowerUser
|
||||
|
||||
@Input() public issueCategories: IIssueCategory[];
|
||||
@Input() public issuesEnabled: boolean;
|
||||
public issuesBarVisible = false;
|
||||
public issueRequest: IAlbumRequest;
|
||||
public issueProviderId: string;
|
||||
public issueCategorySelected: IIssueCategory;
|
||||
|
||||
public filterDisplay: boolean;
|
||||
public filter: IFilter;
|
||||
public filterType = FilterType;
|
||||
|
||||
public orderType: OrderType = OrderType.RequestedDateDesc;
|
||||
public OrderType = OrderType;
|
||||
|
||||
public totalAlbums: number = 100;
|
||||
private currentlyLoaded: number;
|
||||
private amountToLoad: number;
|
||||
|
||||
constructor(
|
||||
private requestService: RequestService,
|
||||
private auth: AuthService,
|
||||
private notificationService: NotificationService,
|
||||
private sanitizer: DomSanitizer,
|
||||
private readonly platformLocation: PlatformLocation) {
|
||||
this.searchChanged.pipe(
|
||||
debounceTime(600), // Wait Xms after the last event before emitting last event
|
||||
distinctUntilChanged(), // only emit if value is different from previous value
|
||||
).subscribe(x => {
|
||||
this.searchText = x as string;
|
||||
if (this.searchText === "") {
|
||||
this.resetSearch();
|
||||
return;
|
||||
}
|
||||
this.requestService.searchAlbumRequests(this.searchText)
|
||||
.subscribe(m => {
|
||||
this.setOverrides(m);
|
||||
this.albumRequests = m;
|
||||
});
|
||||
});
|
||||
this.defaultPoster = "../../../images/default-music-placeholder.png";
|
||||
const base = this.platformLocation.getBaseHrefFromDOM();
|
||||
if (base) {
|
||||
this.defaultPoster = "../../.." + base + "/images/default-music-placeholder.png";
|
||||
}
|
||||
}
|
||||
|
||||
public ngOnInit() {
|
||||
this.amountToLoad = 10;
|
||||
this.currentlyLoaded = 10;
|
||||
this.filter = {
|
||||
availabilityFilter: FilterType.None,
|
||||
statusFilter: FilterType.None,
|
||||
};
|
||||
this.loadInit();
|
||||
this.isAdmin = this.auth.hasRole("admin") || this.auth.hasRole("poweruser");
|
||||
}
|
||||
|
||||
public paginate(event: IPagenator) {
|
||||
const skipAmount = event.first;
|
||||
this.loadRequests(this.amountToLoad, skipAmount);
|
||||
}
|
||||
|
||||
public search(text: any) {
|
||||
this.searchChanged.next(text.target.value);
|
||||
}
|
||||
|
||||
public removeRequest(request: IAlbumRequest) {
|
||||
this.requestService.removeAlbumRequest(request);
|
||||
this.removeRequestFromUi(request);
|
||||
this.loadRequests(this.amountToLoad, this.currentlyLoaded = 0);
|
||||
}
|
||||
|
||||
public changeAvailability(request: IAlbumRequest, available: boolean) {
|
||||
request.available = available;
|
||||
|
||||
if (available) {
|
||||
this.requestService.markAlbumAvailable({ id: request.id }).subscribe(x => {
|
||||
if (x.result) {
|
||||
this.notificationService.success(
|
||||
`${request.title} Is now available`);
|
||||
} else {
|
||||
this.notificationService.warning("Request Available", x.message ? x.message : x.errorMessage);
|
||||
request.approved = false;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.requestService.markAlbumUnavailable({ id: request.id }).subscribe(x => {
|
||||
if (x.result) {
|
||||
this.notificationService.success(
|
||||
`${request.title} Is now unavailable`);
|
||||
} else {
|
||||
this.notificationService.warning("Request Available", x.message ? x.message : x.errorMessage);
|
||||
request.approved = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public approve(request: IAlbumRequest) {
|
||||
request.approved = true;
|
||||
this.approveRequest(request);
|
||||
}
|
||||
|
||||
public deny(request: IAlbumRequest) {
|
||||
request.denied = true;
|
||||
this.denyRequest(request);
|
||||
}
|
||||
|
||||
// public selectRootFolder(searchResult: IAlbumRequest, rootFolderSelected: IRadarrRootFolder, event: any) {
|
||||
// event.preventDefault();
|
||||
// // searchResult.rootPathOverride = rootFolderSelected.id;
|
||||
// this.setOverride(searchResult);
|
||||
// this.updateRequest(searchResult);
|
||||
// }
|
||||
|
||||
// public selectQualityProfile(searchResult: IMovieRequests, profileSelected: IRadarrProfile, event: any) {
|
||||
// event.preventDefault();
|
||||
// searchResult.qualityOverride = profileSelected.id;
|
||||
// this.setOverride(searchResult);
|
||||
// this.updateRequest(searchResult);
|
||||
// }
|
||||
|
||||
public reportIssue(catId: IIssueCategory, req: IAlbumRequest) {
|
||||
this.issueRequest = req;
|
||||
this.issueCategorySelected = catId;
|
||||
this.issuesBarVisible = true;
|
||||
this.issueProviderId = req.foreignAlbumId;
|
||||
}
|
||||
|
||||
public ignore(event: any): void {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
public clearFilter(el: any) {
|
||||
el = el.toElement || el.relatedTarget || el.target || el.srcElement;
|
||||
|
||||
el = el.parentElement;
|
||||
el = el.querySelectorAll("INPUT");
|
||||
for (el of el) {
|
||||
el.checked = false;
|
||||
el.parentElement.classList.remove("active");
|
||||
}
|
||||
|
||||
this.filterDisplay = false;
|
||||
this.filter.availabilityFilter = FilterType.None;
|
||||
this.filter.statusFilter = FilterType.None;
|
||||
|
||||
this.resetSearch();
|
||||
}
|
||||
|
||||
public filterAvailability(filter: FilterType, el: any) {
|
||||
this.filterActiveStyle(el);
|
||||
this.filter.availabilityFilter = filter;
|
||||
this.loadInit();
|
||||
}
|
||||
|
||||
public filterStatus(filter: FilterType, el: any) {
|
||||
this.filterActiveStyle(el);
|
||||
this.filter.statusFilter = filter;
|
||||
this.loadInit();
|
||||
}
|
||||
|
||||
public setOrder(value: OrderType, el: any) {
|
||||
el = el.toElement || el.relatedTarget || el.target || el.srcElement;
|
||||
|
||||
const parent = el.parentElement;
|
||||
const previousFilter = parent.querySelector(".active");
|
||||
|
||||
previousFilter.className = "";
|
||||
el.className = "active";
|
||||
|
||||
this.orderType = value;
|
||||
|
||||
this.loadInit();
|
||||
}
|
||||
|
||||
// public subscribe(request: IAlbumRequest) {
|
||||
// request.subscribed = true;
|
||||
// this.requestService.subscribeToMovie(request.id)
|
||||
// .subscribe(x => {
|
||||
// this.notificationService.success("Subscribed To Movie!");
|
||||
// });
|
||||
// }
|
||||
|
||||
// public unSubscribe(request: IMovieRequests) {
|
||||
// request.subscribed = false;
|
||||
// this.requestService.unSubscribeToMovie(request.id)
|
||||
// .subscribe(x => {
|
||||
// this.notificationService.success("Unsubscribed Movie!");
|
||||
// });
|
||||
// }
|
||||
|
||||
private filterActiveStyle(el: any) {
|
||||
el = el.toElement || el.relatedTarget || el.target || el.srcElement;
|
||||
|
||||
el = el.parentElement; //gets radio div
|
||||
el = el.parentElement; //gets form group div
|
||||
el = el.parentElement; //gets status filter div
|
||||
el = el.querySelectorAll("INPUT");
|
||||
for (el of el) {
|
||||
if (el.checked) {
|
||||
if (!el.parentElement.classList.contains("active")) {
|
||||
el.parentElement.className += " active";
|
||||
}
|
||||
} else {
|
||||
el.parentElement.classList.remove("active");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private loadRequests(amountToLoad: number, currentlyLoaded: number) {
|
||||
this.requestService.getAlbumRequests(amountToLoad, currentlyLoaded, this.orderType, this.filter)
|
||||
.subscribe(x => {
|
||||
this.setOverrides(x.collection);
|
||||
if (!this.albumRequests) {
|
||||
this.albumRequests = [];
|
||||
}
|
||||
this.albumRequests = x.collection;
|
||||
this.totalAlbums = x.total;
|
||||
this.currentlyLoaded = currentlyLoaded + amountToLoad;
|
||||
});
|
||||
}
|
||||
|
||||
private approveRequest(request: IAlbumRequest) {
|
||||
this.requestService.approveAlbum({ id: request.id })
|
||||
.subscribe(x => {
|
||||
request.approved = true;
|
||||
if (x.result) {
|
||||
this.notificationService.success(
|
||||
`Request for ${request.title} has been approved successfully`);
|
||||
} else {
|
||||
this.notificationService.warning("Request Approved", x.message ? x.message : x.errorMessage);
|
||||
request.approved = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private denyRequest(request: IAlbumRequest) {
|
||||
this.requestService.denyAlbum({ id: request.id })
|
||||
.subscribe(x => {
|
||||
if (x.result) {
|
||||
this.notificationService.success(
|
||||
`Request for ${request.title} has been denied successfully`);
|
||||
} else {
|
||||
this.notificationService.warning("Request Denied", x.message ? x.message : x.errorMessage);
|
||||
request.denied = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private loadInit() {
|
||||
this.requestService.getAlbumRequests(this.amountToLoad, 0, this.orderType, this.filter)
|
||||
.subscribe(x => {
|
||||
this.albumRequests = x.collection;
|
||||
this.totalAlbums = x.total;
|
||||
|
||||
this.setOverrides(this.albumRequests);
|
||||
|
||||
if (this.isAdmin) {
|
||||
// this.radarrService.getQualityProfilesFromSettings().subscribe(c => {
|
||||
// this.radarrProfiles = c;
|
||||
// this.albumRequests.forEach((req) => this.setQualityOverrides(req));
|
||||
// });
|
||||
// this.radarrService.getRootFoldersFromSettings().subscribe(c => {
|
||||
// this.radarrRootFolders = c;
|
||||
// this.albumRequests.forEach((req) => this.setRootFolderOverrides(req));
|
||||
// });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private resetSearch() {
|
||||
this.currentlyLoaded = 5;
|
||||
this.loadInit();
|
||||
}
|
||||
|
||||
private removeRequestFromUi(key: IAlbumRequest) {
|
||||
const index = this.albumRequests.indexOf(key, 0);
|
||||
if (index > -1) {
|
||||
this.albumRequests.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
private setOverrides(requests: IAlbumRequest[]): void {
|
||||
requests.forEach((req) => {
|
||||
this.setOverride(req);
|
||||
});
|
||||
}
|
||||
|
||||
// private setQualityOverrides(req: IMovieRequests): void {
|
||||
// if (this.radarrProfiles) {
|
||||
// const profile = this.radarrProfiles.filter((p) => {
|
||||
// return p.id === req.qualityOverride;
|
||||
// });
|
||||
// if (profile.length > 0) {
|
||||
// req.qualityOverrideTitle = profile[0].name;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// private setRootFolderOverrides(req: IMovieRequests): void {
|
||||
// if (this.radarrRootFolders) {
|
||||
// const path = this.radarrRootFolders.filter((folder) => {
|
||||
// return folder.id === req.rootPathOverride;
|
||||
// });
|
||||
// if (path.length > 0) {
|
||||
// req.rootPathOverrideTitle = path[0].path;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
private setOverride(req: IAlbumRequest): void {
|
||||
this.setAlbumBackground(req);
|
||||
// this.setQualityOverrides(req);
|
||||
// this.setRootFolderOverrides(req);
|
||||
}
|
||||
private setAlbumBackground(req: IAlbumRequest) {
|
||||
if (req.disk === null) {
|
||||
if(req.cover === null) {
|
||||
req.disk = this.defaultPoster;
|
||||
} else {
|
||||
req.disk = req.cover;
|
||||
}
|
||||
}
|
||||
req.background = this.sanitizer.bypassSecurityTrustStyle
|
||||
("url(" + req.cover + ")");
|
||||
}
|
||||
}
|
|
@ -9,6 +9,10 @@
|
|||
<a id="tvTabButton" aria-controls="profile" role="tab" data-toggle="tab" (click)="selectTvTab()"><i class="fa fa-television"></i> {{ 'Requests.TvTab' | translate }}</a>
|
||||
|
||||
</li>
|
||||
<li role="presentation">
|
||||
<a id="albumTabButton" aria-controls="profile" role="tab" data-toggle="tab" (click)="selectMusicTab()"><i class="fa fa-music"></i> {{ 'Requests.MusicTab' | translate }}</a>
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- Tab panes -->
|
||||
|
@ -19,5 +23,8 @@
|
|||
<div [hidden]="!showTv">
|
||||
<tv-requests [issueCategories]="issueCategories" [issuesEnabled]="issuesEnabled"></tv-requests>
|
||||
</div>
|
||||
<div [hidden]="!showAlbums">
|
||||
<music-requests [issueCategories]="issueCategories" [issuesEnabled]="issuesEnabled"></music-requests>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ export class RequestComponent implements OnInit {
|
|||
|
||||
public showMovie = true;
|
||||
public showTv = false;
|
||||
public showAlbums = false;
|
||||
|
||||
public issueCategories: IIssueCategory[];
|
||||
public issuesEnabled = false;
|
||||
|
@ -28,10 +29,18 @@ export class RequestComponent implements OnInit {
|
|||
public selectMovieTab() {
|
||||
this.showMovie = true;
|
||||
this.showTv = false;
|
||||
this.showAlbums = false;
|
||||
}
|
||||
|
||||
public selectTvTab() {
|
||||
this.showMovie = false;
|
||||
this.showTv = true;
|
||||
this.showAlbums = false;
|
||||
}
|
||||
|
||||
public selectMusicTab() {
|
||||
this.showMovie = false;
|
||||
this.showTv = false;
|
||||
this.showAlbums = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import { InfiniteScrollModule } from "ngx-infinite-scroll";
|
|||
|
||||
import { ButtonModule, DialogModule, PaginatorModule } from "primeng/primeng";
|
||||
import { MovieRequestsComponent } from "./movierequests.component";
|
||||
import { MusicRequestsComponent } from "./music/musicrequests.component";
|
||||
// Request
|
||||
import { RequestComponent } from "./request.component";
|
||||
import { TvRequestChildrenComponent } from "./tvrequest-children.component";
|
||||
|
@ -23,7 +24,6 @@ import { SharedModule } from "../shared/shared.module";
|
|||
|
||||
const routes: Routes = [
|
||||
{ path: "", component: RequestComponent, canActivate: [AuthGuard] },
|
||||
{ path: ":id", component: TvRequestChildrenComponent, canActivate: [AuthGuard] },
|
||||
];
|
||||
@NgModule({
|
||||
imports: [
|
||||
|
@ -44,6 +44,7 @@ const routes: Routes = [
|
|||
MovieRequestsComponent,
|
||||
TvRequestsComponent,
|
||||
TvRequestChildrenComponent,
|
||||
MusicRequestsComponent,
|
||||
],
|
||||
exports: [
|
||||
RouterModule,
|
||||
|
|
|
@ -3,7 +3,7 @@ import { HttpClient } from "@angular/common/http";
|
|||
import { Injectable } from "@angular/core";
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
import { ILidarrProfile, ILidarrRootFolder } from "../../interfaces";
|
||||
import { ILidarrProfile, ILidarrRootFolder, IProfiles } from "../../interfaces";
|
||||
import { ILidarrSettings } from "../../interfaces";
|
||||
import { ServiceHelpers } from "../service.helpers";
|
||||
|
||||
|
@ -26,4 +26,11 @@ export class LidarrService extends ServiceHelpers {
|
|||
public getQualityProfilesFromSettings(): Observable<ILidarrProfile[]> {
|
||||
return this.http.get<ILidarrProfile[]>(`${this.url}/Profiles/`, {headers: this.headers});
|
||||
}
|
||||
|
||||
public getMetadataProfiles(settings: ILidarrSettings): Observable<IProfiles[]> {
|
||||
return this.http.post<IProfiles[]>(`${this.url}/Metadata/`, JSON.stringify(settings), {headers: this.headers});
|
||||
}
|
||||
public getLanguages(settings: ILidarrSettings): Observable<IProfiles[]> {
|
||||
return this.http.post<IProfiles[]>(`${this.url}/Langauges/`,JSON.stringify(settings), {headers: this.headers});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -159,8 +159,8 @@ export class RequestService extends ServiceHelpers {
|
|||
return this.http.post<IRequestEngineResult>(`${this.url}music/unavailable`, JSON.stringify(Album), {headers: this.headers});
|
||||
}
|
||||
|
||||
public getAlbumRequests(count: number, position: number, order: OrderType, filter: IFilter): Observable<IRequestsViewModel<IMovieRequests>> {
|
||||
return this.http.get<IRequestsViewModel<IMovieRequests>>(`${this.url}music/${count}/${position}/${order}/${filter.statusFilter}/${filter.availabilityFilter}`, {headers: this.headers});
|
||||
public getAlbumRequests(count: number, position: number, order: OrderType, filter: IFilter): Observable<IRequestsViewModel<IAlbumRequest>> {
|
||||
return this.http.get<IRequestsViewModel<IAlbumRequest>>(`${this.url}music/${count}/${position}/${order}/${filter.statusFilter}/${filter.availabilityFilter}`, {headers: this.headers});
|
||||
}
|
||||
|
||||
public searchAlbumRequests(search: string): Observable<IAlbumRequest[]> {
|
||||
|
|
|
@ -50,39 +50,60 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<div>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="select" class="control-label">Quality Profiles</label>
|
||||
<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>
|
||||
</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>
|
||||
<small *ngIf="form.get('defaultQualityProfile').hasError('required')" class="error-text">A Default Quality Profile is required</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div>
|
||||
<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>
|
||||
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="rootFolders" class="control-label">Default Root Folders</label>
|
||||
<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>
|
||||
</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>
|
||||
<small *ngIf="form.get('defaultRootPath').hasError('required')" class="error-text">A Default Root Path is required</small>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<label for="languageProfileId" class="control-label">Language Profile</label>
|
||||
<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')}">
|
||||
<option *ngFor="let folder of languageProfiles" value="{{folder.id}}" >{{folder.name}}</option>
|
||||
</select>
|
||||
<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>
|
||||
<small *ngIf="form.get('languageProfileId').hasError('required')" class="error-text">A Language profile is required</small>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<label for="metadataProfileId" class="control-label">Metadata Profile</label>
|
||||
<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')}">
|
||||
<option *ngFor="let folder of metadataProfiles" value="{{folder.id}}" >{{folder.name}}</option>
|
||||
</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>
|
||||
|
||||
</div>
|
||||
|
||||
<small *ngIf="form.get('metadataProfileId').hasError('required')" class="error-text">A Metadata profile is required</small>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<div>
|
||||
<button type="button" [disabled]="form.invalid" (click)="test(form)" class="btn btn-primary-outline">Test Connectivity <span id="spinner"></span></button>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Component, OnInit } from "@angular/core";
|
||||
import { FormBuilder, FormGroup, Validators } from "@angular/forms";
|
||||
|
||||
import { ILidarrSettings, IMinimumAvailability, IRadarrProfile, IRadarrRootFolder } from "../../interfaces";
|
||||
import { ILidarrSettings, IMinimumAvailability, IProfiles, IRadarrProfile, IRadarrRootFolder } from "../../interfaces";
|
||||
import { IRadarrSettings } from "../../interfaces";
|
||||
import { LidarrService, TesterService } from "../../services";
|
||||
import { NotificationService } from "../../services";
|
||||
|
@ -13,10 +13,14 @@ import { SettingsService } from "../../services";
|
|||
export class LidarrComponent implements OnInit {
|
||||
|
||||
public qualities: IRadarrProfile[];
|
||||
public languageProfiles: IProfiles[];
|
||||
public metadataProfiles: IProfiles[];
|
||||
public rootFolders: IRadarrRootFolder[];
|
||||
public minimumAvailabilityOptions: IMinimumAvailability[];
|
||||
public profilesRunning: boolean;
|
||||
public rootFoldersRunning: boolean;
|
||||
public metadataRunning: boolean;
|
||||
public languageRunning: boolean;
|
||||
public advanced = false;
|
||||
public form: FormGroup;
|
||||
|
||||
|
@ -39,6 +43,9 @@ export class LidarrComponent implements OnInit {
|
|||
subDir: [x.subDir],
|
||||
ip: [x.ip, [Validators.required]],
|
||||
port: [x.port, [Validators.required]],
|
||||
albumFolder: [x.albumFolder],
|
||||
languageProfileId: [x.languageProfileId, [Validators.required]],
|
||||
metadataProfileId: [x.metadataProfileId, [Validators.required]],
|
||||
});
|
||||
|
||||
if (x.defaultQualityProfile) {
|
||||
|
@ -47,6 +54,12 @@ export class LidarrComponent implements OnInit {
|
|||
if (x.defaultRootPath) {
|
||||
this.getRootFolders(this.form);
|
||||
}
|
||||
if (x.languageProfileId) {
|
||||
this.getLanguageProfiles(this.form);
|
||||
}
|
||||
if (x.metadataProfileId) {
|
||||
this.getMetadataProfiles(this.form);
|
||||
}
|
||||
});
|
||||
|
||||
this.qualities = [];
|
||||
|
@ -54,6 +67,12 @@ export class LidarrComponent implements OnInit {
|
|||
|
||||
this.rootFolders = [];
|
||||
this.rootFolders.push({ path: "Please Select", id: -1 });
|
||||
|
||||
this.languageProfiles = [];
|
||||
this.languageProfiles.push({ name: "Please Select", id: -1 });
|
||||
|
||||
this.metadataProfiles = [];
|
||||
this.metadataProfiles.push({ name: "Please Select", id: -1 });
|
||||
}
|
||||
|
||||
public getProfiles(form: FormGroup) {
|
||||
|
@ -78,6 +97,28 @@ export class LidarrComponent implements OnInit {
|
|||
});
|
||||
}
|
||||
|
||||
public getMetadataProfiles(form: FormGroup) {
|
||||
this.metadataRunning = true;
|
||||
this.lidarrService.getMetadataProfiles(form.value).subscribe(x => {
|
||||
this.metadataProfiles = x;
|
||||
this.metadataProfiles.unshift({ name: "Please Select", id: -1 });
|
||||
|
||||
this.metadataRunning = false;
|
||||
this.notificationService.success("Successfully retrieved the Metadata profiles");
|
||||
});
|
||||
}
|
||||
|
||||
public getLanguageProfiles(form: FormGroup) {
|
||||
this.languageRunning = true;
|
||||
this.lidarrService.getLanguages(form.value).subscribe(x => {
|
||||
this.languageProfiles = x;
|
||||
this.languageProfiles.unshift({ name: "Please Select", id: -1 });
|
||||
|
||||
this.languageRunning = false;
|
||||
this.notificationService.success("Successfully retrieved the Language profiles");
|
||||
});
|
||||
}
|
||||
|
||||
public test(form: FormGroup) {
|
||||
if (form.invalid) {
|
||||
this.notificationService.error("Please check your entered values");
|
||||
|
|
|
@ -1004,3 +1004,24 @@ a > h4:hover {
|
|||
vertical-align: baseline;
|
||||
border-radius: .25em;
|
||||
}
|
||||
|
||||
.form-control-grid {
|
||||
display: block;
|
||||
width: inherit !important;
|
||||
height: 39px;
|
||||
color: #fefefe;
|
||||
border-radius: 5px;
|
||||
padding: 8px 16px;
|
||||
font-size: 15px;
|
||||
line-height: 1.42857143;
|
||||
color: #2b3e50;
|
||||
background-color: #ffffff;
|
||||
background-image: none;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 0;
|
||||
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,0.075);
|
||||
box-shadow: inset 0 1px 1px rgba(0,0,0,0.075);
|
||||
-webkit-transition: border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;
|
||||
-o-transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s;
|
||||
transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s;
|
||||
}
|
|
@ -50,6 +50,27 @@ namespace Ombi.Controllers.External
|
|||
return await _lidarrApi.GetRootFolders(settings.ApiKey, settings.FullUri);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Lidarr metadata profiles.
|
||||
/// </summary>
|
||||
/// <param name="settings">The settings.</param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("Metadata")]
|
||||
public async Task<IEnumerable<MetadataProfile>> GetMetadataProfiles([FromBody] LidarrSettings settings)
|
||||
{
|
||||
return await _lidarrApi.GetMetadataProfile(settings.ApiKey, settings.FullUri);
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets the Lidarr Langauge profiles.
|
||||
/// </summary>
|
||||
/// <param name="settings">The settings.</param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("Langauges")]
|
||||
public async Task<IEnumerable<LanguageProfiles>> GetLanguageProfiles([FromBody] LidarrSettings settings)
|
||||
{
|
||||
return await _lidarrApi.GetLanguageProfile(settings.ApiKey, settings.FullUri);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Lidarr profiles using the saved settings
|
||||
/// <remarks>The data is cached for an hour</remarks>
|
||||
|
|
|
@ -119,6 +119,7 @@
|
|||
"Below you can see yours and all other requests, as well as their download and approval status.",
|
||||
"MoviesTab": "Movies",
|
||||
"TvTab": "TV Shows",
|
||||
"MusicTab":"Music",
|
||||
"RequestedBy": "Requested By:",
|
||||
"Status": "Status:",
|
||||
"RequestStatus": "Request status:",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue