mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-08-19 12:59:39 -07:00
Merge
This commit is contained in:
commit
7f4f2361c5
50 changed files with 460 additions and 59 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -243,3 +243,6 @@ _Pvt_Extensions
|
||||||
# CAKE - C# Make
|
# CAKE - C# Make
|
||||||
/Tools/*
|
/Tools/*
|
||||||
*.db-journal
|
*.db-journal
|
||||||
|
|
||||||
|
# Ignore local vscode config
|
||||||
|
*.vscode
|
||||||
|
|
|
@ -22,5 +22,6 @@ namespace Ombi.Api.Lidarr
|
||||||
Task<List<MetadataProfile>> GetMetadataProfile(string apiKey, string baseUrl);
|
Task<List<MetadataProfile>> GetMetadataProfile(string apiKey, string baseUrl);
|
||||||
Task<List<LanguageProfiles>> GetLanguageProfile(string apiKey, string baseUrl);
|
Task<List<LanguageProfiles>> GetLanguageProfile(string apiKey, string baseUrl);
|
||||||
Task<LidarrStatus> Status(string apiKey, string baseUrl);
|
Task<LidarrStatus> Status(string apiKey, string baseUrl);
|
||||||
|
Task<CommandResult> AlbumSearch(int[] albumIds, string apiKey, string baseUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -154,6 +154,14 @@ namespace Ombi.Api.Lidarr
|
||||||
return Api.Request<LidarrStatus>(request);
|
return Api.Request<LidarrStatus>(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task<CommandResult> AlbumSearch(int[] albumIds, string apiKey, string baseUrl)
|
||||||
|
{
|
||||||
|
var request = new Request($"{ApiVersion}/command/AlbumSearch", baseUrl, HttpMethod.Post);
|
||||||
|
request.AddJsonBody(albumIds);
|
||||||
|
AddHeaders(request, apiKey);
|
||||||
|
return Api.Request<CommandResult>(request);
|
||||||
|
}
|
||||||
|
|
||||||
private void AddHeaders(Request request, string key)
|
private void AddHeaders(Request request, string key)
|
||||||
{
|
{
|
||||||
request.AddHeader("X-Api-Key", key);
|
request.AddHeader("X-Api-Key", key);
|
||||||
|
|
15
src/Ombi.Api.Lidarr/Models/CommandResult.cs
Normal file
15
src/Ombi.Api.Lidarr/Models/CommandResult.cs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ombi.Api.Lidarr.Models
|
||||||
|
{
|
||||||
|
|
||||||
|
public class CommandResult
|
||||||
|
{
|
||||||
|
public string name { get; set; }
|
||||||
|
public DateTime startedOn { get; set; }
|
||||||
|
public DateTime stateChangeTime { get; set; }
|
||||||
|
public bool sendUpdatesToClient { get; set; }
|
||||||
|
public string state { get; set; }
|
||||||
|
public int id { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,7 +17,5 @@ namespace Ombi.Core.Engine.Interfaces
|
||||||
Task<RequestEngineResult> ApproveMovie(MovieRequests request);
|
Task<RequestEngineResult> ApproveMovie(MovieRequests request);
|
||||||
Task<RequestEngineResult> ApproveMovieById(int requestId);
|
Task<RequestEngineResult> ApproveMovieById(int requestId);
|
||||||
Task<RequestEngineResult> DenyMovieById(int modelId);
|
Task<RequestEngineResult> DenyMovieById(int modelId);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Ombi.Core.Models;
|
||||||
using Ombi.Core.Models.Requests;
|
using Ombi.Core.Models.Requests;
|
||||||
using Ombi.Core.Models.UI;
|
using Ombi.Core.Models.UI;
|
||||||
using Ombi.Store.Entities;
|
using Ombi.Store.Entities;
|
||||||
|
@ -22,5 +23,6 @@ namespace Ombi.Core.Engine.Interfaces
|
||||||
Task<int> GetTotal();
|
Task<int> GetTotal();
|
||||||
Task UnSubscribeRequest(int requestId, RequestType type);
|
Task UnSubscribeRequest(int requestId, RequestType type);
|
||||||
Task SubscribeToRequest(int requestId, RequestType type);
|
Task SubscribeToRequest(int requestId, RequestType type);
|
||||||
|
Task<RequestQuotaCountModel> GetRemainingRequests(OmbiUser user = null);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -19,6 +19,7 @@ using Ombi.Core.Settings;
|
||||||
using Ombi.Settings.Settings.Models;
|
using Ombi.Settings.Settings.Models;
|
||||||
using Ombi.Store.Entities.Requests;
|
using Ombi.Store.Entities.Requests;
|
||||||
using Ombi.Store.Repository;
|
using Ombi.Store.Repository;
|
||||||
|
using Ombi.Core.Models;
|
||||||
|
|
||||||
namespace Ombi.Core.Engine
|
namespace Ombi.Core.Engine
|
||||||
{
|
{
|
||||||
|
@ -484,5 +485,49 @@ namespace Ombi.Core.Engine
|
||||||
|
|
||||||
return new RequestEngineResult {Result = true, Message = $"{movieName} has been successfully added!"};
|
return new RequestEngineResult {Result = true, Message = $"{movieName} has been successfully added!"};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<RequestQuotaCountModel> GetRemainingRequests(OmbiUser user)
|
||||||
|
{
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
user = await GetUser();
|
||||||
|
|
||||||
|
// If user is still null after attempting to get the logged in user, return null.
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int limit = user.MovieRequestLimit ?? 0;
|
||||||
|
|
||||||
|
if (limit <= 0)
|
||||||
|
{
|
||||||
|
return new RequestQuotaCountModel()
|
||||||
|
{
|
||||||
|
HasLimit = false,
|
||||||
|
Limit = 0,
|
||||||
|
Remaining = 0,
|
||||||
|
NextRequest = DateTime.Now,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
IQueryable<RequestLog> log = _requestLog.GetAll().Where(x => x.UserId == user.Id && x.RequestType == RequestType.Movie);
|
||||||
|
|
||||||
|
int count = limit - await log.CountAsync(x => x.RequestDate >= DateTime.UtcNow.AddDays(-7));
|
||||||
|
|
||||||
|
DateTime oldestRequestedAt = await log.Where(x => x.RequestDate >= DateTime.UtcNow.AddDays(-7))
|
||||||
|
.OrderBy(x => x.RequestDate)
|
||||||
|
.Select(x => x.RequestDate)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
|
return new RequestQuotaCountModel()
|
||||||
|
{
|
||||||
|
HasLimit = true,
|
||||||
|
Limit = limit,
|
||||||
|
Remaining = count,
|
||||||
|
NextRequest = DateTime.SpecifyKind(oldestRequestedAt.AddDays(7), DateTimeKind.Utc),
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -23,6 +23,7 @@ using Ombi.Core.Settings;
|
||||||
using Ombi.Settings.Settings.Models;
|
using Ombi.Settings.Settings.Models;
|
||||||
using Ombi.Store.Entities.Requests;
|
using Ombi.Store.Entities.Requests;
|
||||||
using Ombi.Store.Repository;
|
using Ombi.Store.Repository;
|
||||||
|
using Ombi.Core.Models;
|
||||||
|
|
||||||
namespace Ombi.Core.Engine
|
namespace Ombi.Core.Engine
|
||||||
{
|
{
|
||||||
|
@ -587,6 +588,15 @@ namespace Ombi.Core.Engine
|
||||||
NotificationHelper.NewRequest(model);
|
NotificationHelper.NewRequest(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await _requestLog.Add(new RequestLog
|
||||||
|
{
|
||||||
|
UserId = (await GetUser()).Id,
|
||||||
|
RequestDate = DateTime.UtcNow,
|
||||||
|
RequestId = model.Id,
|
||||||
|
RequestType = RequestType.TvShow,
|
||||||
|
EpisodeCount = model.SeasonRequests.Select(m => m.Episodes.Count).Sum(),
|
||||||
|
});
|
||||||
|
|
||||||
if (model.Approved)
|
if (model.Approved)
|
||||||
{
|
{
|
||||||
// Autosend
|
// Autosend
|
||||||
|
@ -602,15 +612,58 @@ namespace Ombi.Core.Engine
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
await _requestLog.Add(new RequestLog
|
|
||||||
{
|
|
||||||
UserId = (await GetUser()).Id,
|
|
||||||
RequestDate = DateTime.UtcNow,
|
|
||||||
RequestId = model.Id,
|
|
||||||
RequestType = RequestType.TvShow,
|
|
||||||
});
|
|
||||||
|
|
||||||
return new RequestEngineResult { Result = true };
|
return new RequestEngineResult { Result = true };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<RequestQuotaCountModel> GetRemainingRequests(OmbiUser user)
|
||||||
|
{
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
user = await GetUser();
|
||||||
|
|
||||||
|
// If user is still null after attempting to get the logged in user, return null.
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int limit = user.EpisodeRequestLimit ?? 0;
|
||||||
|
|
||||||
|
if (limit <= 0)
|
||||||
|
{
|
||||||
|
return new RequestQuotaCountModel()
|
||||||
|
{
|
||||||
|
HasLimit = false,
|
||||||
|
Limit = 0,
|
||||||
|
Remaining = 0,
|
||||||
|
NextRequest = DateTime.Now,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
IQueryable<RequestLog> log = _requestLog.GetAll()
|
||||||
|
.Where(x => x.UserId == user.Id
|
||||||
|
&& x.RequestType == RequestType.TvShow
|
||||||
|
&& x.RequestDate >= DateTime.UtcNow.AddDays(-7));
|
||||||
|
|
||||||
|
// Needed, due to a bug which would cause all episode counts to be 0
|
||||||
|
int zeroEpisodeCount = await log.Where(x => x.EpisodeCount == 0).Select(x => x.EpisodeCount).CountAsync();
|
||||||
|
|
||||||
|
int episodeCount = await log.Where(x => x.EpisodeCount != 0).Select(x => x.EpisodeCount).SumAsync();
|
||||||
|
|
||||||
|
int count = limit - (zeroEpisodeCount + episodeCount);
|
||||||
|
|
||||||
|
DateTime oldestRequestedAt = await log.OrderBy(x => x.RequestDate)
|
||||||
|
.Select(x => x.RequestDate)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
|
return new RequestQuotaCountModel()
|
||||||
|
{
|
||||||
|
HasLimit = true,
|
||||||
|
Limit = limit,
|
||||||
|
Remaining = count,
|
||||||
|
NextRequest = DateTime.SpecifyKind(oldestRequestedAt.AddDays(7), DateTimeKind.Utc),
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
15
src/Ombi.Core/Models/RequestQuotaCountModel.cs
Normal file
15
src/Ombi.Core/Models/RequestQuotaCountModel.cs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ombi.Core.Models
|
||||||
|
{
|
||||||
|
public class RequestQuotaCountModel
|
||||||
|
{
|
||||||
|
public bool HasLimit { get; set; }
|
||||||
|
|
||||||
|
public int Limit { get; set; }
|
||||||
|
|
||||||
|
public int Remaining { get; set; }
|
||||||
|
|
||||||
|
public DateTime NextRequest { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,6 +16,8 @@ namespace Ombi.Core.Models.UI
|
||||||
public UserType UserType { get; set; }
|
public UserType UserType { get; set; }
|
||||||
public int MovieRequestLimit { get; set; }
|
public int MovieRequestLimit { get; set; }
|
||||||
public int EpisodeRequestLimit { get; set; }
|
public int EpisodeRequestLimit { get; set; }
|
||||||
|
public RequestQuotaCountModel EpisodeRequestQuota { get; set; }
|
||||||
|
public RequestQuotaCountModel MovieRequestQuota { get; set; }
|
||||||
public int MusicRequestLimit { get; set; }
|
public int MusicRequestLimit { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="3.2.0" />
|
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="3.2.0" />
|
||||||
<PackageReference Include="Hangfire" Version="1.6.19" />
|
<PackageReference Include="Hangfire" Version="1.6.19" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="2.1.1" />
|
<PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="2.1.1" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.1" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.3" />
|
||||||
<PackageReference Include="MiniProfiler.AspNetCore" Version="4.0.0-alpha6-79" />
|
<PackageReference Include="MiniProfiler.AspNetCore" Version="4.0.0-alpha6-79" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
|
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
|
||||||
<PackageReference Include="System.Diagnostics.Process" Version="4.3.0" />
|
<PackageReference Include="System.Diagnostics.Process" Version="4.3.0" />
|
||||||
|
|
|
@ -82,15 +82,20 @@ namespace Ombi.Core.Rule.Rules.Request
|
||||||
// Get the count of requests to be made
|
// Get the count of requests to be made
|
||||||
foreach (var s in child.SeasonRequests)
|
foreach (var s in child.SeasonRequests)
|
||||||
{
|
{
|
||||||
requestCount = s.Episodes.Count;
|
requestCount += s.Episodes.Count;
|
||||||
}
|
}
|
||||||
|
|
||||||
var tvLogs = requestLog.Where(x => x.RequestType == RequestType.TvShow);
|
var tvLogs = requestLog.Where(x => x.RequestType == RequestType.TvShow);
|
||||||
|
|
||||||
// Count how many requests in the past 7 days
|
// Count how many requests in the past 7 days
|
||||||
var tv = tvLogs.Where(x => x.RequestDate >= DateTime.UtcNow.AddDays(-7));
|
var tv = tvLogs.Where(x => x.RequestDate >= DateTime.UtcNow.AddDays(-7));
|
||||||
var count = await tv.Select(x => x.EpisodeCount).CountAsync();
|
|
||||||
count += requestCount; // Add the amount of requests in
|
// Needed, due to a bug which would cause all episode counts to be 0
|
||||||
|
var zeroEpisodeCount = await tv.Where(x => x.EpisodeCount == 0).Select(x => x.EpisodeCount).CountAsync();
|
||||||
|
|
||||||
|
var episodeCount = await tv.Where(x => x.EpisodeCount != 0).Select(x => x.EpisodeCount).SumAsync();
|
||||||
|
|
||||||
|
var count = requestCount + episodeCount + zeroEpisodeCount; // Add the amount of requests in
|
||||||
if (count > episodeLimit)
|
if (count > episodeLimit)
|
||||||
{
|
{
|
||||||
return Fail("You have exceeded your Episode request quota!");
|
return Fail("You have exceeded your Episode request quota!");
|
||||||
|
|
|
@ -123,7 +123,10 @@ namespace Ombi.Core.Senders
|
||||||
existingMovie.monitored = true;
|
existingMovie.monitored = true;
|
||||||
await RadarrApi.UpdateMovie(existingMovie, settings.ApiKey, settings.FullUri);
|
await RadarrApi.UpdateMovie(existingMovie, settings.ApiKey, settings.FullUri);
|
||||||
// Search for it
|
// Search for it
|
||||||
await RadarrApi.MovieSearch(new[] { existingMovie.id }, settings.ApiKey, settings.FullUri);
|
if (!settings.AddOnly)
|
||||||
|
{
|
||||||
|
await RadarrApi.MovieSearch(new[] {existingMovie.id}, settings.ApiKey, settings.FullUri);
|
||||||
|
}
|
||||||
|
|
||||||
return new SenderResult { Success = true, Sent = true };
|
return new SenderResult { Success = true, Sent = true };
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,6 +116,10 @@ namespace Ombi.Core.Senders
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = await _lidarrApi.MontiorAlbum(album.id, settings.ApiKey, settings.FullUri);
|
var result = await _lidarrApi.MontiorAlbum(album.id, settings.ApiKey, settings.FullUri);
|
||||||
|
if (!settings.AddOnly)
|
||||||
|
{
|
||||||
|
await _lidarrApi.AlbumSearch(new[] {result.id}, settings.ApiKey, settings.FullUri);
|
||||||
|
}
|
||||||
if (result.monitored)
|
if (result.monitored)
|
||||||
{
|
{
|
||||||
return new SenderResult {Message = "Album has been requested!", Sent = true, Success = true};
|
return new SenderResult {Message = "Album has been requested!", Sent = true, Success = true};
|
||||||
|
|
|
@ -9,8 +9,8 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="2.1.1" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="2.1.2" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="2.1.1" />
|
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="2.1.2" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="2.1.1" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="2.1.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="EasyCrypto" Version="3.3.2" />
|
<PackageReference Include="EasyCrypto" Version="3.3.2" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="2.1.1" />
|
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="2.1.2" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.1.1" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.1.1" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
|
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
|
||||||
<PackageReference Include="Nito.AsyncEx" Version="5.0.0-pre-05" />
|
<PackageReference Include="Nito.AsyncEx" Version="5.0.0-pre-05" />
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore" Version="2.1.2" />
|
<PackageReference Include="Microsoft.AspNetCore" Version="2.1.3" />
|
||||||
<PackageReference Include="Moq" Version="4.7.99" />
|
<PackageReference Include="Moq" Version="4.7.99" />
|
||||||
<PackageReference Include="Nunit" Version="3.10.1" />
|
<PackageReference Include="Nunit" Version="3.10.1" />
|
||||||
<PackageReference Include="NUnit.ConsoleRunner" Version="3.8.0" />
|
<PackageReference Include="NUnit.ConsoleRunner" Version="3.8.0" />
|
||||||
|
|
|
@ -11,5 +11,6 @@ namespace Ombi.Settings.Settings.Models.External
|
||||||
public bool AlbumFolder { get; set; }
|
public bool AlbumFolder { get; set; }
|
||||||
public int LanguageProfileId { get; set; }
|
public int LanguageProfileId { get; set; }
|
||||||
public int MetadataProfileId { get; set; }
|
public int MetadataProfileId { get; set; }
|
||||||
|
public bool AddOnly { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -10,10 +10,10 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="2.1.1" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="2.1.2" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="2.1.2" />
|
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="2.1.3" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.1.1" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.1.3" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.1" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.3" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
|
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
|
||||||
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="1.1.9" />
|
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="1.1.9" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
6
src/Ombi/ClientApp/app/interfaces/IRemainingRequests.ts
Normal file
6
src/Ombi/ClientApp/app/interfaces/IRemainingRequests.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
export interface IRemainingRequests {
|
||||||
|
hasLimit: boolean;
|
||||||
|
limit: number;
|
||||||
|
remaining: number;
|
||||||
|
nextRequest: Date;
|
||||||
|
}
|
|
@ -93,6 +93,7 @@ export interface ILidarrSettings extends IExternalSettings {
|
||||||
metadataProfileId: number;
|
metadataProfileId: number;
|
||||||
languageProfileId: number;
|
languageProfileId: number;
|
||||||
albumFolder: boolean;
|
albumFolder: boolean;
|
||||||
|
addOnly: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ILandingPageSettings extends ISettings {
|
export interface ILandingPageSettings extends ISettings {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { ICheckbox } from ".";
|
import { ICheckbox } from ".";
|
||||||
|
import { IRemainingRequests } from "./IRemainingRequests";
|
||||||
|
|
||||||
export interface IUser {
|
export interface IUser {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -14,7 +15,10 @@ export interface IUser {
|
||||||
episodeRequestLimit: number;
|
episodeRequestLimit: number;
|
||||||
musicRequestLimit: number;
|
musicRequestLimit: number;
|
||||||
userAccessToken: string;
|
userAccessToken: string;
|
||||||
|
|
||||||
// FOR UI
|
// FOR UI
|
||||||
|
episodeRequestQuota: IRemainingRequests | null;
|
||||||
|
movieRequestQuota: IRemainingRequests | null;
|
||||||
checked: boolean;
|
checked: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -127,11 +127,7 @@ export class LoginComponent implements OnDestroy, OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
public oauth() {
|
public oauth() {
|
||||||
this.plexTv.GetPin(this.clientId, this.appName).subscribe((pin: any) => {
|
const oAuthWindow = window.open(window.location.toString(), "_blank", `toolbar=0,
|
||||||
|
|
||||||
this.authService.login({ usePlexOAuth: true, password: "", rememberMe: true, username: "", plexTvPin: pin }).subscribe(x => {
|
|
||||||
|
|
||||||
window.open(x.url, "_blank", `toolbar=0,
|
|
||||||
location=0,
|
location=0,
|
||||||
status=0,
|
status=0,
|
||||||
menubar=0,
|
menubar=0,
|
||||||
|
@ -139,6 +135,10 @@ export class LoginComponent implements OnDestroy, OnInit {
|
||||||
resizable=1,
|
resizable=1,
|
||||||
width=500,
|
width=500,
|
||||||
height=500`);
|
height=500`);
|
||||||
|
this.plexTv.GetPin(this.clientId, this.appName).subscribe((pin: any) => {
|
||||||
|
|
||||||
|
this.authService.login({ usePlexOAuth: true, password: "", rememberMe: true, username: "", plexTvPin: pin }).subscribe(x => {
|
||||||
|
oAuthWindow!.location.replace(x.url);
|
||||||
|
|
||||||
this.pinTimer = setInterval(() => {
|
this.pinTimer = setInterval(() => {
|
||||||
this.notify.info("Authenticating", "Loading... Please Wait");
|
this.notify.info("Authenticating", "Loading... Please Wait");
|
||||||
|
|
|
@ -80,6 +80,7 @@ export class MovieRequestsComponent implements OnInit {
|
||||||
};
|
};
|
||||||
this.loadInit();
|
this.loadInit();
|
||||||
this.isAdmin = this.auth.hasRole("admin") || this.auth.hasRole("poweruser");
|
this.isAdmin = this.auth.hasRole("admin") || this.auth.hasRole("poweruser");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public paginate(event: IPagenator) {
|
public paginate(event: IPagenator) {
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
<div *ngIf="remaining?.hasLimit">
|
||||||
|
<h4 id="remainingRequests" class="text-center">
|
||||||
|
{{'Requests.Remaining.Quota' | translate: {remaining: remaining.remaining, total: remaining.limit} }}
|
||||||
|
</h4>
|
||||||
|
<h4 class="text-center" *ngIf="daysUntil > 1">
|
||||||
|
{{'Requests.Remaining.NextDays' | translate: {time: daysUntil} }}
|
||||||
|
</h4>
|
||||||
|
<h4 class="text-center" *ngIf="hoursUntil > 1 && daysUntil <= 1">
|
||||||
|
{{'Requests.Remaining.NextHours' | translate: {time: hoursUntil} }}
|
||||||
|
</h4>
|
||||||
|
<h4 class="text-center" *ngIf="minutesUntil >= 1 && hoursUntil <= 1 && daysUntil <= 1" #minutes>
|
||||||
|
{{(minutesUntil == 1 ? 'Requests.Remaining.NextMinute' : 'Requests.Remaining.NextMinutes') | translate: {time: minutesUntil} }}
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<br *ngIf="!remaining?.hasLimit" />
|
||||||
|
<br *ngIf="!remaining?.hasLimit" />
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
import { IRemainingRequests } from "../interfaces/IRemainingRequests";
|
||||||
|
import { RequestService } from "../services";
|
||||||
|
|
||||||
|
import { Component, Input, OnInit } from "@angular/core";
|
||||||
|
import { Observable } from "rxjs";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: "remaining-requests",
|
||||||
|
templateUrl: "./remainingrequests.component.html",
|
||||||
|
})
|
||||||
|
|
||||||
|
export class RemainingRequestsComponent implements OnInit {
|
||||||
|
public remaining: IRemainingRequests;
|
||||||
|
@Input() public movie: boolean;
|
||||||
|
public daysUntil: number;
|
||||||
|
public hoursUntil: number;
|
||||||
|
public minutesUntil: number;
|
||||||
|
@Input() public quotaRefreshEvents: Observable<void>;
|
||||||
|
|
||||||
|
constructor(private requestService: RequestService) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public ngOnInit() {
|
||||||
|
const self = this;
|
||||||
|
|
||||||
|
this.update();
|
||||||
|
|
||||||
|
this.quotaRefreshEvents.subscribe(() => {
|
||||||
|
this.update();
|
||||||
|
});
|
||||||
|
|
||||||
|
setInterval(() => {
|
||||||
|
self.update();
|
||||||
|
}, 60000);
|
||||||
|
}
|
||||||
|
|
||||||
|
public update(): void {
|
||||||
|
const callback = (remaining => {
|
||||||
|
this.remaining = remaining;
|
||||||
|
this.calculateTime();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.movie) {
|
||||||
|
this.requestService.getRemainingMovieRequests().subscribe(callback);
|
||||||
|
} else {
|
||||||
|
this.requestService.getRemainingTvRequests().subscribe(callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private calculateTime(): void {
|
||||||
|
this.daysUntil = Math.ceil(this.daysUntilNextRequest());
|
||||||
|
this.hoursUntil = Math.ceil(this.hoursUntilNextRequest());
|
||||||
|
this.minutesUntil = Math.ceil(this.minutesUntilNextRequest());
|
||||||
|
}
|
||||||
|
|
||||||
|
private daysUntilNextRequest(): number {
|
||||||
|
return (new Date(this.remaining.nextRequest).getTime() - new Date().getTime()) / 1000 / 60 / 60 / 24;
|
||||||
|
}
|
||||||
|
|
||||||
|
private hoursUntilNextRequest(): number {
|
||||||
|
return (new Date(this.remaining.nextRequest).getTime() - new Date().getTime()) / 1000 / 60 / 60;
|
||||||
|
}
|
||||||
|
|
||||||
|
private minutesUntilNextRequest(): number {
|
||||||
|
return (new Date(this.remaining.nextRequest).getTime() - new Date().getTime()) / 1000 / 60;
|
||||||
|
}
|
||||||
|
}
|
27
src/Ombi/ClientApp/app/requests/remainingrequests.module.ts
Normal file
27
src/Ombi/ClientApp/app/requests/remainingrequests.module.ts
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import { NgModule } from "@angular/core";
|
||||||
|
import { FormsModule } from "@angular/forms";
|
||||||
|
import { RouterModule } from "@angular/router";
|
||||||
|
|
||||||
|
import { NgbModule } from "@ng-bootstrap/ng-bootstrap";
|
||||||
|
|
||||||
|
import { SidebarModule, TooltipModule, TreeTableModule } from "primeng/primeng";
|
||||||
|
import { RequestService } from "../services";
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
FormsModule,
|
||||||
|
NgbModule.forRoot(),
|
||||||
|
TreeTableModule,
|
||||||
|
SidebarModule,
|
||||||
|
TooltipModule,
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
RouterModule,
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
RequestService,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class SearchModule { }
|
|
@ -3,14 +3,14 @@
|
||||||
|
|
||||||
<ul id="nav-tabs" class="nav nav-tabs" role="tablist">
|
<ul id="nav-tabs" class="nav nav-tabs" role="tablist">
|
||||||
<li role="presentation" class="active">
|
<li role="presentation" class="active">
|
||||||
<a id="movieTabButton" aria-controls="home" role="tab" data-toggle="tab" (click)="selectMovieTab()"><i class="fa fa-film"></i> {{ 'Requests.MoviesTab' | translate }}</a>
|
<a id="movieTabButton" aria-controls="home" role="tab" data-toggle="tab" (click)="selectMovieTab()" href="#movieTab"><i class="fa fa-film"></i> {{ 'Requests.MoviesTab' | translate }}</a>
|
||||||
</li>
|
</li>
|
||||||
<li role="presentation">
|
<li role="presentation">
|
||||||
<a id="tvTabButton" aria-controls="profile" role="tab" data-toggle="tab" (click)="selectTvTab()"><i class="fa fa-television"></i> {{ 'Requests.TvTab' | translate }}</a>
|
<a id="tvTabButton" aria-controls="profile" role="tab" data-toggle="tab" (click)="selectTvTab()" href="#tvTab"><i class="fa fa-television"></i> {{ 'Requests.TvTab' | translate }}</a>
|
||||||
|
|
||||||
</li>
|
</li>
|
||||||
<li role="presentation">
|
<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>
|
<a id="albumTabButton" aria-controls="profile" role="tab" data-toggle="tab" (click)="selectMusicTab()" href="#albumTab"><i class="fa fa-music"></i> {{ 'Requests.MusicTab' | translate }}</a>
|
||||||
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<div *ngIf="childRequests">
|
<div *ngIf="childRequests">
|
||||||
<hr />
|
<hr />
|
||||||
<div *ngFor="let child of childRequests">
|
<div *ngFor="let child of childRequests" class="clearfix">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
|
|
||||||
<div class="col-md-2">
|
<div class="col-md-2">
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<!-- Movie tab -->
|
<!-- Movie tab -->
|
||||||
<div role="tabpanel" class="tab-pane active" id="MoviesTab">
|
<div role="tabpanel" class="tab-pane active" id="MoviesTab">
|
||||||
|
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input id="search" type="text" class="form-control form-control-custom form-control-search form-control-withbuttons" (keyup)="search($event)">
|
<input id="search" type="text" class="form-control form-control-custom form-control-search form-control-withbuttons" (keyup)="search($event)">
|
||||||
<div class="input-group-addon right-radius">
|
<div class="input-group-addon right-radius">
|
||||||
|
@ -18,8 +19,9 @@
|
||||||
<i class="fa fa-search"></i>
|
<i class="fa fa-search"></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<br />
|
|
||||||
<br />
|
<remaining-requests [movie]="true" [quotaRefreshEvents]="movieRequested.asObservable()" #remainingFilms></remaining-requests>
|
||||||
|
|
||||||
<!-- Movie content -->
|
<!-- Movie content -->
|
||||||
<div id="movieList">
|
<div id="movieList">
|
||||||
<div *ngIf="searchApplied && movieResults?.length <= 0" class='no-search-results'>
|
<div *ngIf="searchApplied && movieResults?.length <= 0" class='no-search-results'>
|
||||||
|
|
|
@ -17,8 +17,10 @@ export class MovieSearchComponent implements OnInit {
|
||||||
|
|
||||||
public searchText: string;
|
public searchText: string;
|
||||||
public searchChanged: Subject<string> = new Subject<string>();
|
public searchChanged: Subject<string> = new Subject<string>();
|
||||||
|
public movieRequested: Subject<void> = new Subject<void>();
|
||||||
public movieResults: ISearchMovieResult[];
|
public movieResults: ISearchMovieResult[];
|
||||||
public result: IRequestEngineResult;
|
public result: IRequestEngineResult;
|
||||||
|
|
||||||
public searchApplied = false;
|
public searchApplied = false;
|
||||||
|
|
||||||
@Input() public issueCategories: IIssueCategory[];
|
@Input() public issueCategories: IIssueCategory[];
|
||||||
|
@ -35,7 +37,6 @@ export class MovieSearchComponent implements OnInit {
|
||||||
private notificationService: NotificationService, private authService: AuthService,
|
private notificationService: NotificationService, private authService: AuthService,
|
||||||
private readonly translate: TranslateService, private sanitizer: DomSanitizer,
|
private readonly translate: TranslateService, private sanitizer: DomSanitizer,
|
||||||
private readonly platformLocation: PlatformLocation) {
|
private readonly platformLocation: PlatformLocation) {
|
||||||
|
|
||||||
this.searchChanged.pipe(
|
this.searchChanged.pipe(
|
||||||
debounceTime(600), // Wait Xms after the last event before emitting last event
|
debounceTime(600), // Wait Xms after the last event before emitting last event
|
||||||
distinctUntilChanged(), // only emit if value is different from previous value
|
distinctUntilChanged(), // only emit if value is different from previous value
|
||||||
|
@ -69,6 +70,7 @@ export class MovieSearchComponent implements OnInit {
|
||||||
result: false,
|
result: false,
|
||||||
errorMessage: "",
|
errorMessage: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
this.popularMovies();
|
this.popularMovies();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,8 +89,8 @@ export class MovieSearchComponent implements OnInit {
|
||||||
try {
|
try {
|
||||||
this.requestService.requestMovie({ theMovieDbId: searchResult.id })
|
this.requestService.requestMovie({ theMovieDbId: searchResult.id })
|
||||||
.subscribe(x => {
|
.subscribe(x => {
|
||||||
|
this.movieRequested.next();
|
||||||
this.result = x;
|
this.result = x;
|
||||||
|
|
||||||
if (this.result.result) {
|
if (this.result.result) {
|
||||||
this.translate.get("Search.RequestAdded", { title: searchResult.title }).subscribe(x => {
|
this.translate.get("Search.RequestAdded", { title: searchResult.title }).subscribe(x => {
|
||||||
this.notificationService.success(x);
|
this.notificationService.success(x);
|
||||||
|
@ -150,7 +152,8 @@ export class MovieSearchComponent implements OnInit {
|
||||||
|
|
||||||
public reportIssue(catId: IIssueCategory, req: ISearchMovieResult) {
|
public reportIssue(catId: IIssueCategory, req: ISearchMovieResult) {
|
||||||
this.issueRequestId = req.id;
|
this.issueRequestId = req.id;
|
||||||
this.issueRequestTitle = req.title + `(${req.releaseDate.getFullYear})`;
|
const releaseDate = new Date(req.releaseDate);
|
||||||
|
this.issueRequestTitle = req.title + ` (${releaseDate.getFullYear()})`;
|
||||||
this.issueCategorySelected = catId;
|
this.issueCategorySelected = catId;
|
||||||
this.issuesBarVisible = true;
|
this.issuesBarVisible = true;
|
||||||
this.issueProviderId = req.id.toString();
|
this.issueProviderId = req.id.toString();
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
<div>
|
<div>
|
||||||
<h4>
|
<h4>
|
||||||
<a href="" target="_blank">
|
<a href="" target="_blank">
|
||||||
{{result.title}}
|
{{result.title | truncate: 36}}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
</h4>
|
</h4>
|
||||||
|
|
|
@ -20,7 +20,7 @@ export class SearchComponent implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
public ngOnInit() {
|
public ngOnInit() {
|
||||||
this.settingsService.getLidarr().subscribe(x => this.musicEnabled = x.enabled);
|
this.settingsService.lidarrEnabled().subscribe(x => this.musicEnabled = x);
|
||||||
this.showMovie = true;
|
this.showMovie = true;
|
||||||
this.showTv = false;
|
this.showTv = false;
|
||||||
this.showMusic = false;
|
this.showMusic = false;
|
||||||
|
|
|
@ -21,6 +21,7 @@ import { SearchService } from "../services";
|
||||||
|
|
||||||
import { AuthGuard } from "../auth/auth.guard";
|
import { AuthGuard } from "../auth/auth.guard";
|
||||||
|
|
||||||
|
import { RemainingRequestsComponent } from "../requests/remainingrequests.component";
|
||||||
import { SharedModule } from "../shared/shared.module";
|
import { SharedModule } from "../shared/shared.module";
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
|
@ -44,6 +45,7 @@ const routes: Routes = [
|
||||||
TvSearchComponent,
|
TvSearchComponent,
|
||||||
SeriesInformationComponent,
|
SeriesInformationComponent,
|
||||||
MovieSearchGridComponent,
|
MovieSearchGridComponent,
|
||||||
|
RemainingRequestsComponent,
|
||||||
MusicSearchComponent,
|
MusicSearchComponent,
|
||||||
ArtistSearchComponent,
|
ArtistSearchComponent,
|
||||||
AlbumSearchComponent,
|
AlbumSearchComponent,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Component, Input, OnInit} from "@angular/core";
|
import { Component, Input, OnInit } from "@angular/core";
|
||||||
|
|
||||||
import { NotificationService } from "../services";
|
import { NotificationService } from "../services";
|
||||||
import { RequestService } from "../services";
|
import { RequestService } from "../services";
|
||||||
|
@ -8,6 +8,8 @@ import { INewSeasonRequests, IRequestEngineResult, ISeasonsViewModel, ITvRequest
|
||||||
import { IEpisodesRequests } from "../interfaces";
|
import { IEpisodesRequests } from "../interfaces";
|
||||||
import { ISearchTvResult } from "../interfaces";
|
import { ISearchTvResult } from "../interfaces";
|
||||||
|
|
||||||
|
import { Subject } from "rxjs";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "seriesinformation",
|
selector: "seriesinformation",
|
||||||
templateUrl: "./seriesinformation.component.html",
|
templateUrl: "./seriesinformation.component.html",
|
||||||
|
@ -18,7 +20,7 @@ export class SeriesInformationComponent implements OnInit {
|
||||||
public result: IRequestEngineResult;
|
public result: IRequestEngineResult;
|
||||||
public series: ISearchTvResult;
|
public series: ISearchTvResult;
|
||||||
public requestedEpisodes: IEpisodesRequests[] = [];
|
public requestedEpisodes: IEpisodesRequests[] = [];
|
||||||
|
@Input() public tvRequested: Subject<void>;
|
||||||
@Input() private seriesId: number;
|
@Input() private seriesId: number;
|
||||||
|
|
||||||
constructor(private searchService: SearchService, private requestService: RequestService, private notificationService: NotificationService) { }
|
constructor(private searchService: SearchService, private requestService: RequestService, private notificationService: NotificationService) { }
|
||||||
|
@ -62,6 +64,7 @@ export class SeriesInformationComponent implements OnInit {
|
||||||
|
|
||||||
this.requestService.requestTv(viewModel)
|
this.requestService.requestTv(viewModel)
|
||||||
.subscribe(x => {
|
.subscribe(x => {
|
||||||
|
this.tvRequested.next();
|
||||||
this.result = x as IRequestEngineResult;
|
this.result = x as IRequestEngineResult;
|
||||||
if (this.result.result) {
|
if (this.result.result) {
|
||||||
this.notificationService.success(
|
this.notificationService.success(
|
||||||
|
|
|
@ -26,15 +26,13 @@
|
||||||
<i id="tvSearchButton" class="fa fa-search"></i>
|
<i id="tvSearchButton" class="fa fa-search"></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<br />
|
|
||||||
<br />
|
<remaining-requests [movie]="false" [quotaRefreshEvents]="tvRequested.asObservable()" #remainingTvShows></remaining-requests>
|
||||||
|
|
||||||
<!-- Movie content -->
|
<!-- Movie content -->
|
||||||
<div id="actorMovieList">
|
<div id="actorMovieList">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<!-- TV content -->
|
<!-- TV content -->
|
||||||
<div id="tvList">
|
<div id="tvList">
|
||||||
|
|
||||||
|
@ -155,7 +153,7 @@
|
||||||
</div>
|
</div>
|
||||||
<!--This is the section that holds the child seasons if they want to specify specific episodes-->
|
<!--This is the section that holds the child seasons if they want to specify specific episodes-->
|
||||||
<div *ngIf="node.open">
|
<div *ngIf="node.open">
|
||||||
<seriesinformation [seriesId]="node.id"></seriesinformation>
|
<seriesinformation [seriesId]="node.id" [tvRequested]="tvRequested"></seriesinformation>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<br/>
|
<br/>
|
||||||
|
|
|
@ -18,6 +18,7 @@ export class TvSearchComponent implements OnInit {
|
||||||
public searchText: string;
|
public searchText: string;
|
||||||
public searchChanged = new Subject<string>();
|
public searchChanged = new Subject<string>();
|
||||||
public tvResults: ISearchTvResult[];
|
public tvResults: ISearchTvResult[];
|
||||||
|
public tvRequested: Subject<void> = new Subject<void>();
|
||||||
public result: IRequestEngineResult;
|
public result: IRequestEngineResult;
|
||||||
public searchApplied = false;
|
public searchApplied = false;
|
||||||
public defaultPoster: string;
|
public defaultPoster: string;
|
||||||
|
@ -161,6 +162,7 @@ export class TvSearchComponent implements OnInit {
|
||||||
|
|
||||||
this.requestService.requestTv(viewModel)
|
this.requestService.requestTv(viewModel)
|
||||||
.subscribe(x => {
|
.subscribe(x => {
|
||||||
|
this.tvRequested.next();
|
||||||
this.result = x;
|
this.result = x;
|
||||||
if (this.result.result) {
|
if (this.result.result) {
|
||||||
this.notificationService.success(
|
this.notificationService.success(
|
||||||
|
@ -195,7 +197,8 @@ export class TvSearchComponent implements OnInit {
|
||||||
|
|
||||||
public reportIssue(catId: IIssueCategory, req: ISearchTvResult) {
|
public reportIssue(catId: IIssueCategory, req: ISearchTvResult) {
|
||||||
this.issueRequestId = req.id;
|
this.issueRequestId = req.id;
|
||||||
this.issueRequestTitle = req.title + `(${req.firstAired})`;
|
const firstAiredDate = new Date(req.firstAired);
|
||||||
|
this.issueRequestTitle = req.title + ` (${firstAiredDate.getFullYear()})`;
|
||||||
this.issueCategorySelected = catId;
|
this.issueCategorySelected = catId;
|
||||||
this.issuesBarVisible = true;
|
this.issuesBarVisible = true;
|
||||||
this.issueProviderId = req.id.toString();
|
this.issueProviderId = req.id.toString();
|
||||||
|
|
|
@ -10,12 +10,22 @@ import { FilterType, IAlbumRequest, IAlbumRequestModel, IAlbumUpdateModel, IChil
|
||||||
import { ITvRequestViewModel } from "../interfaces";
|
import { ITvRequestViewModel } from "../interfaces";
|
||||||
import { ServiceHelpers } from "./service.helpers";
|
import { ServiceHelpers } from "./service.helpers";
|
||||||
|
|
||||||
|
import { IRemainingRequests } from "../interfaces/IRemainingRequests";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RequestService extends ServiceHelpers {
|
export class RequestService extends ServiceHelpers {
|
||||||
constructor(http: HttpClient, public platformLocation: PlatformLocation) {
|
constructor(http: HttpClient, public platformLocation: PlatformLocation) {
|
||||||
super(http, "/api/v1/Request/", platformLocation);
|
super(http, "/api/v1/Request/", platformLocation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getRemainingMovieRequests(): Observable<IRemainingRequests> {
|
||||||
|
return this.http.get<IRemainingRequests>(`${this.url}movie/remaining`, {headers: this.headers});
|
||||||
|
}
|
||||||
|
|
||||||
|
public getRemainingTvRequests(): Observable<IRemainingRequests> {
|
||||||
|
return this.http.get<IRemainingRequests>(`${this.url}tv/remaining`, {headers: this.headers});
|
||||||
|
}
|
||||||
|
|
||||||
public requestMovie(movie: IMovieRequestModel): Observable<IRequestEngineResult> {
|
public requestMovie(movie: IMovieRequestModel): Observable<IRequestEngineResult> {
|
||||||
return this.http.post<IRequestEngineResult>(`${this.url}Movie/`, JSON.stringify(movie), {headers: this.headers});
|
return this.http.post<IRequestEngineResult>(`${this.url}Movie/`, JSON.stringify(movie), {headers: this.headers});
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,6 +96,10 @@ export class SettingsService extends ServiceHelpers {
|
||||||
return this.http.get<ILidarrSettings>(`${this.url}/Lidarr`, {headers: this.headers});
|
return this.http.get<ILidarrSettings>(`${this.url}/Lidarr`, {headers: this.headers});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public lidarrEnabled(): Observable<boolean> {
|
||||||
|
return this.http.get<boolean>(`${this.url}/lidarrenabled`, {headers: this.headers});
|
||||||
|
}
|
||||||
|
|
||||||
public saveLidarr(settings: ILidarrSettings): Observable<boolean> {
|
public saveLidarr(settings: ILidarrSettings): Observable<boolean> {
|
||||||
return this.http.post<boolean>(`${this.url}/Lidarr`, JSON.stringify(settings), {headers: this.headers});
|
return this.http.post<boolean>(`${this.url}/Lidarr`, JSON.stringify(settings), {headers: this.headers});
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,10 @@
|
||||||
<div *ngIf="form">
|
<div *ngIf="form">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>Lidarr Settings</legend>
|
<legend>Lidarr Settings</legend>
|
||||||
|
<div style="float: right;">
|
||||||
|
<span style="vertical-align: top;">Advanced</span>
|
||||||
|
<p-inputSwitch id="customInputSwitch" [(ngModel)]="advanced"></p-inputSwitch>
|
||||||
|
</div>
|
||||||
<form novalidate [formGroup]="form" (ngSubmit)="onSubmit(form)" style="padding-top:5%;">
|
<form novalidate [formGroup]="form" (ngSubmit)="onSubmit(form)" style="padding-top:5%;">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
@ -110,6 +114,12 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group" *ngIf="advanced" style="color:#ff761b">
|
||||||
|
<div class="checkbox">
|
||||||
|
<input type="checkbox" id="addOnly" formControlName="addOnly">
|
||||||
|
<label for="addOnly">Do not search</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { NotificationService } from "../../services";
|
||||||
import { SettingsService } from "../../services";
|
import { SettingsService } from "../../services";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: "./Lidarr.component.html",
|
templateUrl: "./lidarr.component.html",
|
||||||
})
|
})
|
||||||
export class LidarrComponent implements OnInit {
|
export class LidarrComponent implements OnInit {
|
||||||
|
|
||||||
|
@ -45,6 +45,7 @@ export class LidarrComponent implements OnInit {
|
||||||
albumFolder: [x.albumFolder],
|
albumFolder: [x.albumFolder],
|
||||||
languageProfileId: [x.languageProfileId, [Validators.required]],
|
languageProfileId: [x.languageProfileId, [Validators.required]],
|
||||||
metadataProfileId: [x.metadataProfileId, [Validators.required]],
|
metadataProfileId: [x.metadataProfileId, [Validators.required]],
|
||||||
|
addOnly: [x.addOnly],
|
||||||
});
|
});
|
||||||
|
|
||||||
if (x.defaultQualityProfile) {
|
if (x.defaultQualityProfile) {
|
||||||
|
|
|
@ -48,6 +48,12 @@
|
||||||
<th>
|
<th>
|
||||||
Roles
|
Roles
|
||||||
</th>
|
</th>
|
||||||
|
<th>
|
||||||
|
Requests Remaining
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
Next Request Due
|
||||||
|
</th>
|
||||||
<th (click)="setOrder('lastLoggedIn', $event)">
|
<th (click)="setOrder('lastLoggedIn', $event)">
|
||||||
<a> Last Logged In</a>
|
<a> Last Logged In</a>
|
||||||
<span *ngIf="order === 'lastLoggedIn'">
|
<span *ngIf="order === 'lastLoggedIn'">
|
||||||
|
@ -85,6 +91,22 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</td>
|
</td>
|
||||||
|
<td class="td-labelled" data-label="Requests Remaining">
|
||||||
|
<div *ngIf="u.movieRequestQuota != null && u.movieRequestQuota.hasLimit">
|
||||||
|
{{'UserManagment.MovieRemaining' | translate: {remaining: u.movieRequestQuota.remaining, total: u.movieRequestLimit} }}
|
||||||
|
</div>
|
||||||
|
<div *ngIf="u.episodeRequestQuota != null && u.episodeRequestQuota.hasLimit">
|
||||||
|
{{'UserManagment.TvRemaining' | translate: {remaining: u.episodeRequestQuota.remaining, total: u.episodeRequestLimit} }}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="td-labelled" data-label="Request Due">
|
||||||
|
<div *ngIf="u.movieRequestQuota != null && u.movieRequestQuota.remaining != u.movieRequestLimit">
|
||||||
|
{{'UserManagment.MovieDue' | translate: {date: (u.movieRequestQuota.nextRequest | date: 'short')} }}
|
||||||
|
</div>
|
||||||
|
<div *ngIf="u.episodeRequestQuota != null && u.episodeRequestQuota.remaining != u.episodeRequestLimit">
|
||||||
|
{{'UserManagment.TvDue' | translate: {date: (u.episodeRequestQuota.nextRequest | date: 'short')} }}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
<td class="td-labelled" data-label="Last Logged In:">
|
<td class="td-labelled" data-label="Last Logged In:">
|
||||||
{{u.lastLoggedIn | date: 'short'}}
|
{{u.lastLoggedIn | date: 'short'}}
|
||||||
</td>
|
</td>
|
||||||
|
|
|
@ -18,6 +18,8 @@ import { AuthGuard } from "../auth/auth.guard";
|
||||||
import { OrderModule } from "ngx-order-pipe";
|
import { OrderModule } from "ngx-order-pipe";
|
||||||
import { AddPlexUserComponent } from "./addplexuser.component";
|
import { AddPlexUserComponent } from "./addplexuser.component";
|
||||||
|
|
||||||
|
import { SharedModule } from "../shared/shared.module";
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{ path: "", component: UserManagementComponent, canActivate: [AuthGuard] },
|
{ path: "", component: UserManagementComponent, canActivate: [AuthGuard] },
|
||||||
{ path: "user", component: UserManagementUserComponent, canActivate: [AuthGuard] },
|
{ path: "user", component: UserManagementUserComponent, canActivate: [AuthGuard] },
|
||||||
|
@ -38,6 +40,7 @@ const routes: Routes = [
|
||||||
TooltipModule,
|
TooltipModule,
|
||||||
OrderModule,
|
OrderModule,
|
||||||
SidebarModule,
|
SidebarModule,
|
||||||
|
SharedModule,
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
UserManagementComponent,
|
UserManagementComponent,
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
@import './styles.scss';
|
@import './Styles.scss';
|
||||||
@import './scrollbar.scss';
|
@import './scrollbar.scss';
|
|
@ -16,6 +16,7 @@ using Ombi.Api.Plex;
|
||||||
using Ombi.Attributes;
|
using Ombi.Attributes;
|
||||||
using Ombi.Config;
|
using Ombi.Config;
|
||||||
using Ombi.Core.Authentication;
|
using Ombi.Core.Authentication;
|
||||||
|
using Ombi.Core.Engine.Interfaces;
|
||||||
using Ombi.Core.Helpers;
|
using Ombi.Core.Helpers;
|
||||||
using Ombi.Core.Models.UI;
|
using Ombi.Core.Models.UI;
|
||||||
using Ombi.Core.Settings;
|
using Ombi.Core.Settings;
|
||||||
|
@ -62,7 +63,9 @@ namespace Ombi.Controllers
|
||||||
IRepository<RequestSubscription> subscriptionRepository,
|
IRepository<RequestSubscription> subscriptionRepository,
|
||||||
ISettingsService<UserManagementSettings> umSettings,
|
ISettingsService<UserManagementSettings> umSettings,
|
||||||
IRepository<UserNotificationPreferences> notificationPreferences,
|
IRepository<UserNotificationPreferences> notificationPreferences,
|
||||||
IMusicRequestRepository musicRepo)
|
IMusicRequestRepository musicRepo),
|
||||||
|
IMovieRequestEngine movieRequestEngine,
|
||||||
|
ITvRequestEngine tvRequestEngine)
|
||||||
{
|
{
|
||||||
UserManager = user;
|
UserManager = user;
|
||||||
Mapper = mapper;
|
Mapper = mapper;
|
||||||
|
@ -84,6 +87,8 @@ namespace Ombi.Controllers
|
||||||
_requestSubscriptionRepository = subscriptionRepository;
|
_requestSubscriptionRepository = subscriptionRepository;
|
||||||
_notificationRepository = notificationRepository;
|
_notificationRepository = notificationRepository;
|
||||||
_userManagementSettings = umSettings;
|
_userManagementSettings = umSettings;
|
||||||
|
TvRequestEngine = tvRequestEngine;
|
||||||
|
MovieRequestEngine = movieRequestEngine;
|
||||||
_userNotificationPreferences = notificationPreferences;
|
_userNotificationPreferences = notificationPreferences;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,6 +103,8 @@ namespace Ombi.Controllers
|
||||||
private IWelcomeEmail WelcomeEmail { get; }
|
private IWelcomeEmail WelcomeEmail { get; }
|
||||||
private IMovieRequestRepository MovieRepo { get; }
|
private IMovieRequestRepository MovieRepo { get; }
|
||||||
private ITvRequestRepository TvRepo { get; }
|
private ITvRequestRepository TvRepo { get; }
|
||||||
|
private IMovieRequestEngine MovieRequestEngine { get; }
|
||||||
|
private ITvRequestEngine TvRequestEngine { get; }
|
||||||
private IMusicRequestRepository MusicRepo { get; }
|
private IMusicRequestRepository MusicRepo { get; }
|
||||||
private readonly ILogger<IdentityController> _log;
|
private readonly ILogger<IdentityController> _log;
|
||||||
private readonly IPlexApi _plexApi;
|
private readonly IPlexApi _plexApi;
|
||||||
|
@ -109,7 +116,6 @@ namespace Ombi.Controllers
|
||||||
private readonly IRepository<RequestSubscription> _requestSubscriptionRepository;
|
private readonly IRepository<RequestSubscription> _requestSubscriptionRepository;
|
||||||
private readonly IRepository<UserNotificationPreferences> _userNotificationPreferences;
|
private readonly IRepository<UserNotificationPreferences> _userNotificationPreferences;
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This is what the Wizard will call when creating the user for the very first time.
|
/// This is what the Wizard will call when creating the user for the very first time.
|
||||||
/// This should never be called after this.
|
/// This should never be called after this.
|
||||||
|
@ -323,6 +329,16 @@ namespace Ombi.Controllers
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (vm.EpisodeRequestLimit > 0)
|
||||||
|
{
|
||||||
|
vm.EpisodeRequestQuota = await TvRequestEngine.GetRemainingRequests(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vm.MovieRequestLimit > 0)
|
||||||
|
{
|
||||||
|
vm.MovieRequestQuota = await MovieRequestEngine.GetRemainingRequests(user);
|
||||||
|
}
|
||||||
|
|
||||||
return vm;
|
return vm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ using Ombi.Attributes;
|
||||||
using Ombi.Core.Models.UI;
|
using Ombi.Core.Models.UI;
|
||||||
using Ombi.Models;
|
using Ombi.Models;
|
||||||
using Ombi.Store.Entities;
|
using Ombi.Store.Entities;
|
||||||
|
using Ombi.Core.Models;
|
||||||
|
|
||||||
namespace Ombi.Controllers
|
namespace Ombi.Controllers
|
||||||
{
|
{
|
||||||
|
@ -464,5 +465,23 @@ namespace Ombi.Controllers
|
||||||
await TvRequestEngine.UnSubscribeRequest(requestId, RequestType.TvShow);
|
await TvRequestEngine.UnSubscribeRequest(requestId, RequestType.TvShow);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets model containing remaining number of requests.
|
||||||
|
/// </summary>
|
||||||
|
[HttpGet("movie/remaining")]
|
||||||
|
public async Task<RequestQuotaCountModel> GetRemainingMovieRequests()
|
||||||
|
{
|
||||||
|
return await MovieRequestEngine.GetRemainingRequests();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets model containing remaining number of requests.
|
||||||
|
/// </summary>
|
||||||
|
[HttpGet("tv/remaining")]
|
||||||
|
public async Task<RequestQuotaCountModel> GetRemainingTvRequests()
|
||||||
|
{
|
||||||
|
return await TvRequestEngine.GetRemainingRequests();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -8,6 +8,7 @@ using Ombi.Api.Lidarr.Models;
|
||||||
using Ombi.Core;
|
using Ombi.Core;
|
||||||
using Ombi.Core.Engine;
|
using Ombi.Core.Engine;
|
||||||
using Ombi.Core.Engine.Interfaces;
|
using Ombi.Core.Engine.Interfaces;
|
||||||
|
using Ombi.Core.Models;
|
||||||
using Ombi.Core.Models.Search;
|
using Ombi.Core.Models.Search;
|
||||||
using StackExchange.Profiling;
|
using StackExchange.Profiling;
|
||||||
|
|
||||||
|
|
|
@ -328,6 +328,18 @@ namespace Ombi.Controllers
|
||||||
return await Get<LidarrSettings>();
|
return await Get<LidarrSettings>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Lidarr Settings.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
[HttpGet("lidarrenabled")]
|
||||||
|
[AllowAnonymous]
|
||||||
|
public async Task<bool> LidarrEnabled()
|
||||||
|
{
|
||||||
|
var settings = await Get<LidarrSettings>();
|
||||||
|
return settings.Enabled;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Save the Lidarr settings.
|
/// Save the Lidarr settings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -71,7 +71,7 @@
|
||||||
<PackageReference Include="Hangfire.RecurringJobExtensions" Version="1.1.6" />
|
<PackageReference Include="Hangfire.RecurringJobExtensions" Version="1.1.6" />
|
||||||
<PackageReference Include="Hangfire.SQLite" Version="1.4.2" />
|
<PackageReference Include="Hangfire.SQLite" Version="1.4.2" />
|
||||||
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.1.2" />
|
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.1.4" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="2.1.1" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="2.1.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.1.1" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.1.1" />
|
||||||
<PackageReference Include="Microsoft.VisualStudio.Web.BrowserLink" Version="2.1.1" />
|
<PackageReference Include="Microsoft.VisualStudio.Web.BrowserLink" Version="2.1.1" />
|
||||||
|
|
|
@ -150,7 +150,14 @@
|
||||||
"SortRequestDateAsc": "Request Date ▲",
|
"SortRequestDateAsc": "Request Date ▲",
|
||||||
"SortRequestDateDesc": "Request Date ▼",
|
"SortRequestDateDesc": "Request Date ▼",
|
||||||
"SortStatusAsc":"Status ▲",
|
"SortStatusAsc":"Status ▲",
|
||||||
"SortStatusDesc":"Status ▼"
|
"SortStatusDesc":"Status ▼",
|
||||||
|
"Remaining": {
|
||||||
|
"Quota": "{{remaining}}/{{total}} requests remaining",
|
||||||
|
"NextDays": "Another request will be added in {{time}} days",
|
||||||
|
"NextHours": "Another request will be added in {{time}} hours",
|
||||||
|
"NextMinutes": "Another request will be added in {{time}} minutes",
|
||||||
|
"NextMinute": "Another request will be added in {{time}} minute"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"Issues":{
|
"Issues":{
|
||||||
"Title":"Issues",
|
"Title":"Issues",
|
||||||
|
@ -177,5 +184,11 @@
|
||||||
"FilterHeaderRequestStatus":"Status",
|
"FilterHeaderRequestStatus":"Status",
|
||||||
"Approved":"Approved",
|
"Approved":"Approved",
|
||||||
"PendingApproval": "Pending Approval"
|
"PendingApproval": "Pending Approval"
|
||||||
|
},
|
||||||
|
"UserManagment": {
|
||||||
|
"TvRemaining": "TV: {{remaining}}/{{total}} remaining",
|
||||||
|
"MovieRemaining": "Movies: {{remaining}}/{{total}} remaining",
|
||||||
|
"TvDue": "TV: {{date}}",
|
||||||
|
"MovieDue": "Movie: {{date}}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue