mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-08-19 21:03:17 -07:00
feat: Radarr tags (#4815)
This commit is contained in:
parent
c1271572f1
commit
6fa506491f
28 changed files with 493 additions and 183 deletions
|
@ -1,4 +1,5 @@
|
||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Ombi.Api.Radarr.Models;
|
using Ombi.Api.Radarr.Models;
|
||||||
using Ombi.Api.Radarr.Models.V3;
|
using Ombi.Api.Radarr.Models.V3;
|
||||||
|
@ -14,7 +15,8 @@ namespace Ombi.Api.Radarr
|
||||||
Task<MovieResponse> GetMovie(int id, string apiKey, string baseUrl);
|
Task<MovieResponse> GetMovie(int id, string apiKey, string baseUrl);
|
||||||
Task<MovieResponse> UpdateMovie(MovieResponse movie, string apiKey, string baseUrl);
|
Task<MovieResponse> UpdateMovie(MovieResponse movie, string apiKey, string baseUrl);
|
||||||
Task<bool> MovieSearch(int[] movieIds, string apiKey, string baseUrl);
|
Task<bool> MovieSearch(int[] movieIds, string apiKey, string baseUrl);
|
||||||
Task<RadarrAddMovie> AddMovie(int tmdbId, string title, int year, int qualityId, string rootPath,string apiKey, string baseUrl, bool searchNow, string minimumAvailability);
|
Task<RadarrAddMovie> AddMovie(int tmdbId, string title, int year, int qualityId, string rootPath,string apiKey, string baseUrl, bool searchNow, string minimumAvailability, List<int> tags);
|
||||||
Task<List<Tag>> GetTags(string apiKey, string baseUrl);
|
Task<List<Tag>> GetTags(string apiKey, string baseUrl);
|
||||||
|
Task<Tag> CreateTag(string apiKey, string baseUrl, string tagName);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -29,5 +29,6 @@ namespace Ombi.Api.Radarr.Models
|
||||||
public int year { get; set; }
|
public int year { get; set; }
|
||||||
public string minimumAvailability { get; set; }
|
public string minimumAvailability { get; set; }
|
||||||
public long sizeOnDisk { get; set; }
|
public long sizeOnDisk { get; set; }
|
||||||
|
public int[] tags { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -72,7 +72,7 @@ namespace Ombi.Api.Radarr
|
||||||
return await Api.Request<MovieResponse>(request);
|
return await Api.Request<MovieResponse>(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<RadarrAddMovie> AddMovie(int tmdbId, string title, int year, int qualityId, string rootPath, string apiKey, string baseUrl, bool searchNow, string minimumAvailability)
|
public async Task<RadarrAddMovie> AddMovie(int tmdbId, string title, int year, int qualityId, string rootPath, string apiKey, string baseUrl, bool searchNow, string minimumAvailability, List<int> tags)
|
||||||
{
|
{
|
||||||
var request = new Request("/api/v3/movie", baseUrl, HttpMethod.Post);
|
var request = new Request("/api/v3/movie", baseUrl, HttpMethod.Post);
|
||||||
|
|
||||||
|
@ -86,7 +86,8 @@ namespace Ombi.Api.Radarr
|
||||||
monitored = true,
|
monitored = true,
|
||||||
year = year,
|
year = year,
|
||||||
minimumAvailability = minimumAvailability,
|
minimumAvailability = minimumAvailability,
|
||||||
sizeOnDisk = 0
|
sizeOnDisk = 0,
|
||||||
|
tags = tags.Any() ? tags.ToArray() : Enumerable.Empty<int>().ToArray()
|
||||||
};
|
};
|
||||||
|
|
||||||
if (searchNow)
|
if (searchNow)
|
||||||
|
@ -156,5 +157,14 @@ namespace Ombi.Api.Radarr
|
||||||
{
|
{
|
||||||
request.AddHeader("X-Api-Key", key);
|
request.AddHeader("X-Api-Key", key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task<Tag> CreateTag(string apiKey, string baseUrl, string tagName)
|
||||||
|
{
|
||||||
|
var request = new Request($"/api/v3/tag", baseUrl, HttpMethod.Post);
|
||||||
|
request.AddHeader("X-Api-Key", apiKey);
|
||||||
|
request.AddJsonBody(new { Label = tagName });
|
||||||
|
|
||||||
|
return Api.Request<Tag>(request);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,8 @@ using Ombi.Store.Entities;
|
||||||
using Ombi.Store.Repository;
|
using Ombi.Store.Repository;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Ombi.Api.Radarr.Models;
|
using Ombi.Api.Radarr.Models;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Ombi.Api.Sonarr;
|
||||||
|
|
||||||
namespace Ombi.Core.Senders
|
namespace Ombi.Core.Senders
|
||||||
{
|
{
|
||||||
|
@ -67,7 +69,7 @@ namespace Ombi.Core.Senders
|
||||||
}
|
}
|
||||||
if (radarrSettings.Enabled)
|
if (radarrSettings.Enabled)
|
||||||
{
|
{
|
||||||
return await SendToRadarr(model, is4K, radarrSettings);
|
return await SendToRadarr(model, radarrSettings);
|
||||||
}
|
}
|
||||||
|
|
||||||
var dogSettings = await _dogNzbSettings.GetSettingsAsync();
|
var dogSettings = await _dogNzbSettings.GetSettingsAsync();
|
||||||
|
@ -131,7 +133,7 @@ namespace Ombi.Core.Senders
|
||||||
return await _dogNzbApi.AddMovie(settings.ApiKey, id);
|
return await _dogNzbApi.AddMovie(settings.ApiKey, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<SenderResult> SendToRadarr(MovieRequests model, bool is4K, RadarrSettings settings)
|
private async Task<SenderResult> SendToRadarr(MovieRequests model, RadarrSettings settings)
|
||||||
{
|
{
|
||||||
var qualityToUse = int.Parse(settings.DefaultQualityProfile);
|
var qualityToUse = int.Parse(settings.DefaultQualityProfile);
|
||||||
|
|
||||||
|
@ -154,6 +156,17 @@ namespace Ombi.Core.Senders
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var tags = new List<int>();
|
||||||
|
if (settings.Tag.HasValue)
|
||||||
|
{
|
||||||
|
tags.Add(settings.Tag.Value);
|
||||||
|
}
|
||||||
|
if (settings.SendUserTags)
|
||||||
|
{
|
||||||
|
var userTag = await GetOrCreateTag(model, settings);
|
||||||
|
tags.Add(userTag.id);
|
||||||
|
}
|
||||||
|
|
||||||
// Overrides on the request take priority
|
// Overrides on the request take priority
|
||||||
if (model.QualityOverride > 0)
|
if (model.QualityOverride > 0)
|
||||||
{
|
{
|
||||||
|
@ -174,7 +187,7 @@ namespace Ombi.Core.Senders
|
||||||
{
|
{
|
||||||
var result = await _radarrV3Api.AddMovie(model.TheMovieDbId, model.Title, model.ReleaseDate.Year,
|
var result = await _radarrV3Api.AddMovie(model.TheMovieDbId, model.Title, model.ReleaseDate.Year,
|
||||||
qualityToUse, rootFolderPath, settings.ApiKey, settings.FullUri, !settings.AddOnly,
|
qualityToUse, rootFolderPath, settings.ApiKey, settings.FullUri, !settings.AddOnly,
|
||||||
settings.MinimumAvailability);
|
settings.MinimumAvailability, tags);
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(result.Error?.message))
|
if (!string.IsNullOrEmpty(result.Error?.message))
|
||||||
{
|
{
|
||||||
|
@ -212,5 +225,17 @@ namespace Ombi.Core.Senders
|
||||||
var selectedPath = paths.FirstOrDefault(x => x.id == overrideId);
|
var selectedPath = paths.FirstOrDefault(x => x.id == overrideId);
|
||||||
return selectedPath?.path ?? string.Empty;
|
return selectedPath?.path ?? string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<Tag> GetOrCreateTag(MovieRequests model, RadarrSettings s)
|
||||||
|
{
|
||||||
|
var tagName = model.RequestedUser.UserName;
|
||||||
|
// Does tag exist?
|
||||||
|
|
||||||
|
var allTags = await _radarrV3Api.GetTags(s.ApiKey, s.FullUri);
|
||||||
|
var existingTag = allTags.FirstOrDefault(x => x.label.Equals(tagName, StringComparison.InvariantCultureIgnoreCase));
|
||||||
|
existingTag ??= await _radarrV3Api.CreateTag(s.ApiKey, s.FullUri, tagName);
|
||||||
|
|
||||||
|
return existingTag;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -9,6 +9,8 @@
|
||||||
public bool AddOnly { get; set; }
|
public bool AddOnly { get; set; }
|
||||||
public string MinimumAvailability { get; set; }
|
public string MinimumAvailability { get; set; }
|
||||||
public bool ScanForAvailability { get; set; }
|
public bool ScanForAvailability { get; set; }
|
||||||
|
public int? Tag { get; set; }
|
||||||
|
public bool SendUserTags { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Radarr4KSettings : RadarrSettings
|
public class Radarr4KSettings : RadarrSettings
|
||||||
|
|
|
@ -19,11 +19,11 @@
|
||||||
public string RootPathAnime { get; set; }
|
public string RootPathAnime { get; set; }
|
||||||
public int? AnimeTag { get; set; }
|
public int? AnimeTag { get; set; }
|
||||||
public int? Tag { get; set; }
|
public int? Tag { get; set; }
|
||||||
|
public bool SendUserTags { get; set; }
|
||||||
public bool AddOnly { get; set; }
|
public bool AddOnly { get; set; }
|
||||||
public int LanguageProfile { get; set; }
|
public int LanguageProfile { get; set; }
|
||||||
public int LanguageProfileAnime { get; set; }
|
public int LanguageProfileAnime { get; set; }
|
||||||
public bool ScanForAvailability { get; set; }
|
public bool ScanForAvailability { get; set; }
|
||||||
|
|
||||||
public bool SendUserTags { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -14,6 +14,7 @@ import { BrowserModule } from "@angular/platform-browser";
|
||||||
import { ButtonModule } from "primeng/button";
|
import { ButtonModule } from "primeng/button";
|
||||||
import { CUSTOMIZATION_INITIALIZER } from "./state/customization/customization-initializer";
|
import { CUSTOMIZATION_INITIALIZER } from "./state/customization/customization-initializer";
|
||||||
import { SONARR_INITIALIZER } from "./state/sonarr/sonarr-initializer";
|
import { SONARR_INITIALIZER } from "./state/sonarr/sonarr-initializer";
|
||||||
|
import { RADARR_INITIALIZER } from "./state/radarr/radarr-initializer";
|
||||||
import { ConfirmDialogModule } from "primeng/confirmdialog";
|
import { ConfirmDialogModule } from "primeng/confirmdialog";
|
||||||
import { CookieComponent } from "./auth/cookie.component";
|
import { CookieComponent } from "./auth/cookie.component";
|
||||||
import { CookieService } from "ng2-cookies";
|
import { CookieService } from "ng2-cookies";
|
||||||
|
@ -24,6 +25,7 @@ import { DialogModule } from "primeng/dialog";
|
||||||
import { FEATURES_INITIALIZER } from "./state/features/features-initializer";
|
import { FEATURES_INITIALIZER } from "./state/features/features-initializer";
|
||||||
import { FeatureState } from "./state/features";
|
import { FeatureState } from "./state/features";
|
||||||
import { SonarrSettingsState } from "./state/sonarr";
|
import { SonarrSettingsState } from "./state/sonarr";
|
||||||
|
import { RadarrSettingsState } from "./state/radarr";
|
||||||
import { JwtModule } from "@auth0/angular-jwt";
|
import { JwtModule } from "@auth0/angular-jwt";
|
||||||
import { LandingPageComponent } from "./landingpage/landingpage.component";
|
import { LandingPageComponent } from "./landingpage/landingpage.component";
|
||||||
import { LandingPageService } from "./services";
|
import { LandingPageService } from "./services";
|
||||||
|
@ -162,7 +164,7 @@ export function JwtTokenGetter() {
|
||||||
}),
|
}),
|
||||||
SidebarModule,
|
SidebarModule,
|
||||||
MatNativeDateModule, MatIconModule, MatSidenavModule, MatListModule, MatToolbarModule, LayoutModule, MatSlideToggleModule,
|
MatNativeDateModule, MatIconModule, MatSidenavModule, MatListModule, MatToolbarModule, LayoutModule, MatSlideToggleModule,
|
||||||
NgxsModule.forRoot([CustomizationState, FeatureState, SonarrSettingsState], {
|
NgxsModule.forRoot([CustomizationState, FeatureState, SonarrSettingsState, RadarrSettingsState], {
|
||||||
developmentMode: !environment.production,
|
developmentMode: !environment.production,
|
||||||
}),
|
}),
|
||||||
...environment.production ? [] :
|
...environment.production ? [] :
|
||||||
|
@ -211,6 +213,7 @@ export function JwtTokenGetter() {
|
||||||
FEATURES_INITIALIZER,
|
FEATURES_INITIALIZER,
|
||||||
SONARR_INITIALIZER,
|
SONARR_INITIALIZER,
|
||||||
CUSTOMIZATION_INITIALIZER,
|
CUSTOMIZATION_INITIALIZER,
|
||||||
|
RADARR_INITIALIZER,
|
||||||
{
|
{
|
||||||
provide: APP_BASE_HREF,
|
provide: APP_BASE_HREF,
|
||||||
useValue: window["baseHref"]
|
useValue: window["baseHref"]
|
||||||
|
|
|
@ -157,7 +157,7 @@ export class DiscoverCardComponent implements OnInit {
|
||||||
AdminRequestDialogComponent,
|
AdminRequestDialogComponent,
|
||||||
{
|
{
|
||||||
width: "700px",
|
width: "700px",
|
||||||
data: { type: RequestType.movie, id: this.result.id },
|
data: { type: RequestType.movie, id: this.result.id, is4k: is4k },
|
||||||
panelClass: "modal-panel",
|
panelClass: "modal-panel",
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -160,6 +160,8 @@ export interface IRadarrSettings extends IExternalSettings {
|
||||||
addOnly: boolean;
|
addOnly: boolean;
|
||||||
minimumAvailability: string;
|
minimumAvailability: string;
|
||||||
scanForAvailability: boolean;
|
scanForAvailability: boolean;
|
||||||
|
tag: number | null;
|
||||||
|
sendUserTags: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IRadarrCombined {
|
export interface IRadarrCombined {
|
||||||
|
|
|
@ -6,14 +6,13 @@ import { TranslateService } from "@ngx-translate/core";
|
||||||
import { APP_BASE_HREF } from "@angular/common";
|
import { APP_BASE_HREF } from "@angular/common";
|
||||||
import { AuthService } from "../auth/auth.service";
|
import { AuthService } from "../auth/auth.service";
|
||||||
import { IAuthenticationSettings, ICustomizationSettings } from "../interfaces";
|
import { IAuthenticationSettings, ICustomizationSettings } from "../interfaces";
|
||||||
import { PlexTvService } from "../services";
|
import { PlexTvService, StatusService, SettingsService } from "../services";
|
||||||
import { SettingsService } from "../services";
|
|
||||||
import { StatusService } from "../services";
|
|
||||||
|
|
||||||
import { StorageService } from "../shared/storage/storage-service";
|
import { StorageService } from "../shared/storage/storage-service";
|
||||||
import { MatSnackBar } from "@angular/material/snack-bar";
|
import { MatSnackBar } from "@angular/material/snack-bar";
|
||||||
import { CustomizationFacade } from "../state/customization";
|
import { CustomizationFacade } from "../state/customization";
|
||||||
import { SonarrFacade } from "app/state/sonarr";
|
import { SonarrFacade } from "app/state/sonarr";
|
||||||
|
import { RadarrFacade } from "app/state/radarr";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: "./login.component.html",
|
templateUrl: "./login.component.html",
|
||||||
|
@ -62,6 +61,7 @@ export class LoginComponent implements OnDestroy, OnInit {
|
||||||
private plexTv: PlexTvService,
|
private plexTv: PlexTvService,
|
||||||
private store: StorageService,
|
private store: StorageService,
|
||||||
private sonarrFacade: SonarrFacade,
|
private sonarrFacade: SonarrFacade,
|
||||||
|
private radarrFacade: RadarrFacade,
|
||||||
private readonly notify: MatSnackBar
|
private readonly notify: MatSnackBar
|
||||||
) {
|
) {
|
||||||
this.href = href;
|
this.href = href;
|
||||||
|
@ -89,7 +89,7 @@ export class LoginComponent implements OnDestroy, OnInit {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (authService.loggedIn()) {
|
if (authService.loggedIn()) {
|
||||||
this.router.navigate(["/"]);
|
this.loadStores();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,7 +144,7 @@ export class LoginComponent implements OnDestroy, OnInit {
|
||||||
|
|
||||||
if (this.authService.loggedIn()) {
|
if (this.authService.loggedIn()) {
|
||||||
this.ngOnDestroy();
|
this.ngOnDestroy();
|
||||||
this.sonarrFacade.load().subscribe();
|
this.loadStores();
|
||||||
this.router.navigate(["/"]);
|
this.router.navigate(["/"]);
|
||||||
} else {
|
} else {
|
||||||
this.notify.open(this.errorBody, "OK", {
|
this.notify.open(this.errorBody, "OK", {
|
||||||
|
@ -221,7 +221,7 @@ export class LoginComponent implements OnDestroy, OnInit {
|
||||||
this.oAuthWindow.close();
|
this.oAuthWindow.close();
|
||||||
}
|
}
|
||||||
this.oauthLoading = false;
|
this.oauthLoading = false;
|
||||||
this.sonarrFacade.load().subscribe();
|
this.loadStores();
|
||||||
this.router.navigate(["search"]);
|
this.router.navigate(["search"]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -252,7 +252,7 @@ export class LoginComponent implements OnDestroy, OnInit {
|
||||||
|
|
||||||
if (this.authService.loggedIn()) {
|
if (this.authService.loggedIn()) {
|
||||||
this.ngOnDestroy();
|
this.ngOnDestroy();
|
||||||
this.sonarrFacade.load().subscribe();
|
this.loadStores();
|
||||||
this.router.navigate(["/"]);
|
this.router.navigate(["/"]);
|
||||||
} else {
|
} else {
|
||||||
this.notify.open(this.errorBody, "OK", {
|
this.notify.open(this.errorBody, "OK", {
|
||||||
|
@ -274,4 +274,9 @@ export class LoginComponent implements OnDestroy, OnInit {
|
||||||
public ngOnDestroy() {
|
public ngOnDestroy() {
|
||||||
clearInterval(this.pinTimer);
|
clearInterval(this.pinTimer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private loadStores() {
|
||||||
|
this.sonarrFacade.load().subscribe();
|
||||||
|
this.radarrFacade.load().subscribe();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,7 +75,7 @@ export class MovieDetailsComponent implements OnInit{
|
||||||
this.isAdmin = this.auth.hasRole("admin") || this.auth.hasRole("poweruser");
|
this.isAdmin = this.auth.hasRole("admin") || this.auth.hasRole("poweruser");
|
||||||
|
|
||||||
if (this.isAdmin) {
|
if (this.isAdmin) {
|
||||||
this.showAdvanced = await this.radarrService.isRadarrEnabled();
|
this.showAdvanced = await firstValueFrom(this.radarrService.isRadarrEnabled());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.imdbId) {
|
if (this.imdbId) {
|
||||||
|
@ -111,7 +111,7 @@ export class MovieDetailsComponent implements OnInit{
|
||||||
is4K = false;
|
is4K = false;
|
||||||
}
|
}
|
||||||
if (this.isAdmin) {
|
if (this.isAdmin) {
|
||||||
const dialog = this.dialog.open(AdminRequestDialogComponent, { width: "700px", data: { type: RequestType.movie, id: this.movie.id }, panelClass: 'modal-panel' });
|
const dialog = this.dialog.open(AdminRequestDialogComponent, { width: "700px", data: { type: RequestType.movie, id: this.movie.id, is4K: is4K }, panelClass: 'modal-panel' });
|
||||||
dialog.afterClosed().subscribe(async (result) => {
|
dialog.afterClosed().subscribe(async (result) => {
|
||||||
if (result) {
|
if (result) {
|
||||||
const requestResult = await firstValueFrom(this.requestService.requestMovie({ theMovieDbId: this.theMovidDbId,
|
const requestResult = await firstValueFrom(this.requestService.requestMovie({ theMovieDbId: this.theMovidDbId,
|
||||||
|
|
|
@ -62,7 +62,7 @@ export class TvRequestGridComponent {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.isAdmin) {
|
if (this.isAdmin) {
|
||||||
const dialog = this.dialog.open(AdminRequestDialogComponent, { width: "700px", data: { type: RequestType.tvShow, id: this.tv.id }, panelClass: 'modal-panel' });
|
const dialog = this.dialog.open(AdminRequestDialogComponent, { width: "700px", data: { type: RequestType.tvShow, id: this.tv.id, is4k: null }, panelClass: 'modal-panel' });
|
||||||
dialog.afterClosed().subscribe(async (result) => {
|
dialog.afterClosed().subscribe(async (result) => {
|
||||||
if (result) {
|
if (result) {
|
||||||
viewModel.requestOnBehalf = result.username?.id;
|
viewModel.requestOnBehalf = result.username?.id;
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { HttpClient } from "@angular/common/http";
|
||||||
import { Injectable, Inject } from "@angular/core";
|
import { Injectable, Inject } from "@angular/core";
|
||||||
import { Observable } from "rxjs";
|
import { Observable } from "rxjs";
|
||||||
|
|
||||||
import { IRadarrProfile, IRadarrRootFolder } from "../../interfaces";
|
import { IRadarrProfile, IRadarrRootFolder, ITag } from "../../interfaces";
|
||||||
import { IRadarrSettings } from "../../interfaces";
|
import { IRadarrSettings } from "../../interfaces";
|
||||||
import { ServiceHelpers } from "../service.helpers";
|
import { ServiceHelpers } from "../service.helpers";
|
||||||
|
|
||||||
|
@ -23,10 +23,28 @@ export class RadarrService extends ServiceHelpers {
|
||||||
public getRootFoldersFromSettings(): Observable<IRadarrRootFolder[]> {
|
public getRootFoldersFromSettings(): Observable<IRadarrRootFolder[]> {
|
||||||
return this.http.get<IRadarrRootFolder[]>(`${this.url}/RootFolders/`, { headers: this.headers });
|
return this.http.get<IRadarrRootFolder[]>(`${this.url}/RootFolders/`, { headers: this.headers });
|
||||||
}
|
}
|
||||||
|
|
||||||
public getQualityProfilesFromSettings(): Observable<IRadarrProfile[]> {
|
public getQualityProfilesFromSettings(): Observable<IRadarrProfile[]> {
|
||||||
return this.http.get<IRadarrProfile[]>(`${this.url}/Profiles/`, { headers: this.headers });
|
return this.http.get<IRadarrProfile[]>(`${this.url}/Profiles/`, { headers: this.headers });
|
||||||
}
|
}
|
||||||
public isRadarrEnabled(): Promise<boolean> {
|
|
||||||
return this.http.get<boolean>(`${this.url}/enabled/`, { headers: this.headers }).toPromise();
|
public getRootFolders4kFromSettings(): Observable<IRadarrRootFolder[]> {
|
||||||
|
return this.http.get<IRadarrRootFolder[]>(`${this.url}/RootFolders/4k`, { headers: this.headers });
|
||||||
|
}
|
||||||
|
|
||||||
|
public getQualityProfiles4kFromSettings(): Observable<IRadarrProfile[]> {
|
||||||
|
return this.http.get<IRadarrProfile[]>(`${this.url}/Profiles/4k`, { headers: this.headers });
|
||||||
|
}
|
||||||
|
|
||||||
|
public isRadarrEnabled(): Observable<boolean> {
|
||||||
|
return this.http.get<boolean>(`${this.url}/enabled/`, { headers: this.headers });
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTagsWithSettings(settings: IRadarrSettings): Observable<ITag[]> {
|
||||||
|
return this.http.post<ITag[]>(`${this.url}/tags/`, JSON.stringify(settings), { headers: this.headers });
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTags(): Observable<ITag[]> {
|
||||||
|
return this.http.get<ITag[]>(`${this.url}/tags/`, { headers: this.headers })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,10 @@
|
||||||
<div class="md-form-field">
|
<div class="md-form-field">
|
||||||
<mat-slide-toggle formControlName="scanForAvailability">Scan for Availability</mat-slide-toggle>
|
<mat-slide-toggle formControlName="scanForAvailability">Scan for Availability</mat-slide-toggle>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="md-form-field">
|
||||||
|
<mat-slide-toggle formControlName="sendUserTags" id="sendUserTags">Add the user as a tag</mat-slide-toggle>
|
||||||
|
<small><br>This will add the username of the requesting user as a tag in Sonarr. If the tag doesn't exist, Ombi will create it.</small>
|
||||||
|
</div>
|
||||||
<div class="md-form-field" >
|
<div class="md-form-field" >
|
||||||
<mat-slide-toggle formControlName="addOnly">
|
<mat-slide-toggle formControlName="addOnly">
|
||||||
Do not search for Movies
|
Do not search for Movies
|
||||||
|
@ -79,6 +83,22 @@
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="md-form-field">
|
||||||
|
<div class="md-form-field" style="display:inline;">
|
||||||
|
<button mat-raised-button (click)="getTags(form)" type="button" color="primary">Load Tags <span *ngIf="tagsRunning" class="fas fa-spinner fa-spin"></span></button>
|
||||||
|
</div>
|
||||||
|
<div class="md-form-field" style="margin-top:1em;"></div>
|
||||||
|
<mat-form-field appearance="outline" >
|
||||||
|
<mat-label>Tag</mat-label>
|
||||||
|
<mat-select formControlName="tag">
|
||||||
|
<mat-option *ngFor="let tag of tags" [value]="tag.id">
|
||||||
|
{{tag.label}}
|
||||||
|
</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="md-form-field">
|
<div class="md-form-field">
|
||||||
<mat-form-field appearance="outline" >
|
<mat-form-field appearance="outline" >
|
||||||
<mat-label>Default Minimum Availability</mat-label>
|
<mat-label>Default Minimum Availability</mat-label>
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import { ChangeDetectionStrategy, Component, OnInit } from "@angular/core";
|
import { ChangeDetectionStrategy, Component, OnInit } from "@angular/core";
|
||||||
import { ControlContainer, UntypedFormGroup, Validators } from "@angular/forms";
|
import { ControlContainer, UntypedFormGroup, Validators } from "@angular/forms";
|
||||||
|
import { finalize, map } from "rxjs";
|
||||||
|
|
||||||
import { IMinimumAvailability, IRadarrProfile, IRadarrRootFolder, IRadarrSettings } from "../../../interfaces";
|
import { IMinimumAvailability, IRadarrProfile, IRadarrRootFolder, IRadarrSettings, ITag } from "../../../interfaces";
|
||||||
import { TesterService, NotificationService, RadarrService } from "../../../services";
|
import { TesterService, NotificationService, RadarrService } from "../../../services";
|
||||||
|
|
||||||
|
|
||||||
|
@ -16,8 +17,10 @@ export class RadarrFormComponent implements OnInit {
|
||||||
public qualities: IRadarrProfile[];
|
public qualities: IRadarrProfile[];
|
||||||
public rootFolders: IRadarrRootFolder[];
|
public rootFolders: IRadarrRootFolder[];
|
||||||
public minimumAvailabilityOptions: IMinimumAvailability[];
|
public minimumAvailabilityOptions: IMinimumAvailability[];
|
||||||
|
public tags: ITag[];
|
||||||
public profilesRunning: boolean;
|
public profilesRunning: boolean;
|
||||||
public rootFoldersRunning: boolean;
|
public rootFoldersRunning: boolean;
|
||||||
|
public tagsRunning: boolean;
|
||||||
public form: UntypedFormGroup;
|
public form: UntypedFormGroup;
|
||||||
|
|
||||||
constructor(private radarrService: RadarrService,
|
constructor(private radarrService: RadarrService,
|
||||||
|
@ -34,6 +37,10 @@ export class RadarrFormComponent implements OnInit {
|
||||||
|
|
||||||
this.rootFolders = [];
|
this.rootFolders = [];
|
||||||
this.rootFolders.push({ path: "Please Select", id: -1 });
|
this.rootFolders.push({ path: "Please Select", id: -1 });
|
||||||
|
|
||||||
|
this.tags = [];
|
||||||
|
this.tags.push({ label: "None", id: -1 });
|
||||||
|
|
||||||
this.minimumAvailabilityOptions = [
|
this.minimumAvailabilityOptions = [
|
||||||
{ name: "Announced", value: "Announced" },
|
{ name: "Announced", value: "Announced" },
|
||||||
{ name: "In Cinemas", value: "InCinemas" },
|
{ name: "In Cinemas", value: "InCinemas" },
|
||||||
|
@ -47,9 +54,16 @@ export class RadarrFormComponent implements OnInit {
|
||||||
if (this.form.controls.defaultRootPath.value) {
|
if (this.form.controls.defaultRootPath.value) {
|
||||||
this.getRootFolders(this.form);
|
this.getRootFolders(this.form);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.form.controls.tag.value) {
|
||||||
|
this.getTags(this.form);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.toggleValidators();
|
||||||
}
|
}
|
||||||
|
|
||||||
public toggleValidators() {
|
public toggleValidators() {
|
||||||
|
debugger;
|
||||||
const enabled = this.form.controls.enabled.value as boolean;
|
const enabled = this.form.controls.enabled.value as boolean;
|
||||||
this.form.controls.apiKey.setValidators(enabled ? [Validators.required] : null);
|
this.form.controls.apiKey.setValidators(enabled ? [Validators.required] : null);
|
||||||
this.form.controls.defaultQualityProfile.setValidators(enabled ? [Validators.required] : null);
|
this.form.controls.defaultQualityProfile.setValidators(enabled ? [Validators.required] : null);
|
||||||
|
@ -81,6 +95,20 @@ export class RadarrFormComponent implements OnInit {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getTags(form: UntypedFormGroup) {
|
||||||
|
this.tagsRunning = true;
|
||||||
|
this.radarrService.getTagsWithSettings(form.value).pipe(
|
||||||
|
finalize(() => {
|
||||||
|
this.tagsRunning = false;
|
||||||
|
this.tags.unshift({ label: "None", id: -1 });
|
||||||
|
this.notificationService.success("Successfully retrieved the Tags");
|
||||||
|
}),
|
||||||
|
map(result => {
|
||||||
|
this.tags = result;
|
||||||
|
})
|
||||||
|
).subscribe()
|
||||||
|
}
|
||||||
|
|
||||||
public test(form: UntypedFormGroup) {
|
public test(form: UntypedFormGroup) {
|
||||||
if (form.invalid) {
|
if (form.invalid) {
|
||||||
this.notificationService.error("Please check your entered values");
|
this.notificationService.error("Please check your entered values");
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import { Component, OnInit, QueryList, ViewChild, ViewChildren } from "@angular/core";
|
import { Component, OnInit, QueryList, ViewChildren } from "@angular/core";
|
||||||
import { UntypedFormBuilder, UntypedFormGroup } from "@angular/forms";
|
import { UntypedFormBuilder, UntypedFormGroup } from "@angular/forms";
|
||||||
|
import { RadarrFacade } from "app/state/radarr";
|
||||||
|
|
||||||
import { IMinimumAvailability, IRadarrCombined, IRadarrProfile, IRadarrRootFolder } from "../../interfaces";
|
import { IMinimumAvailability, IRadarrCombined, IRadarrProfile, IRadarrRootFolder } from "../../interfaces";
|
||||||
import { NotificationService, SettingsService } from "../../services";
|
import { NotificationService } from "../../services";
|
||||||
import { FeaturesFacade } from "../../state/features/features.facade";
|
import { FeaturesFacade } from "../../state/features/features.facade";
|
||||||
import { RadarrFormComponent } from "./components/radarr-form.component";
|
import { RadarrFormComponent } from "./components/radarr-form.component";
|
||||||
|
|
||||||
|
@ -23,7 +24,7 @@ export class RadarrComponent implements OnInit {
|
||||||
@ViewChildren('4kForm') public form4k: QueryList<RadarrFormComponent>;
|
@ViewChildren('4kForm') public form4k: QueryList<RadarrFormComponent>;
|
||||||
@ViewChildren('normalForm') public normalForm: QueryList<RadarrFormComponent>;
|
@ViewChildren('normalForm') public normalForm: QueryList<RadarrFormComponent>;
|
||||||
|
|
||||||
constructor(private settingsService: SettingsService,
|
constructor(private radarrFacade: RadarrFacade,
|
||||||
private notificationService: NotificationService,
|
private notificationService: NotificationService,
|
||||||
private featureFacade: FeaturesFacade,
|
private featureFacade: FeaturesFacade,
|
||||||
private fb: UntypedFormBuilder) { }
|
private fb: UntypedFormBuilder) { }
|
||||||
|
@ -31,34 +32,38 @@ export class RadarrComponent implements OnInit {
|
||||||
|
|
||||||
public ngOnInit() {
|
public ngOnInit() {
|
||||||
this.is4kEnabled = this.featureFacade.is4kEnabled();
|
this.is4kEnabled = this.featureFacade.is4kEnabled();
|
||||||
this.settingsService.getRadarr()
|
this.radarrFacade.state$()
|
||||||
.subscribe(x => {
|
.subscribe(x => {
|
||||||
this.form = this.fb.group({
|
this.form = this.fb.group({
|
||||||
radarr: this.fb.group({
|
radarr: this.fb.group({
|
||||||
enabled: [x.radarr.enabled],
|
enabled: [x.settings.radarr.enabled],
|
||||||
apiKey: [x.radarr.apiKey],
|
apiKey: [x.settings.radarr.apiKey],
|
||||||
defaultQualityProfile: [+x.radarr.defaultQualityProfile],
|
defaultQualityProfile: [+x.settings.radarr.defaultQualityProfile],
|
||||||
defaultRootPath: [x.radarr.defaultRootPath],
|
defaultRootPath: [x.settings.radarr.defaultRootPath],
|
||||||
ssl: [x.radarr.ssl],
|
tag: [x.settings.radarr.tag],
|
||||||
subDir: [x.radarr.subDir],
|
sendUserTags: [x.settings.radarr.sendUserTags],
|
||||||
ip: [x.radarr.ip],
|
ssl: [x.settings.radarr.ssl],
|
||||||
port: [x.radarr.port],
|
subDir: [x.settings.radarr.subDir],
|
||||||
addOnly: [x.radarr.addOnly],
|
ip: [x.settings.radarr.ip],
|
||||||
minimumAvailability: [x.radarr.minimumAvailability],
|
port: [x.settings.radarr.port],
|
||||||
scanForAvailability: [x.radarr.scanForAvailability]
|
addOnly: [x.settings.radarr.addOnly],
|
||||||
|
minimumAvailability: [x.settings.radarr.minimumAvailability],
|
||||||
|
scanForAvailability: [x.settings.radarr.scanForAvailability]
|
||||||
}),
|
}),
|
||||||
radarr4K: this.fb.group({
|
radarr4K: this.fb.group({
|
||||||
enabled: [x.radarr4K.enabled],
|
enabled: [x.settings.radarr4K.enabled],
|
||||||
apiKey: [x.radarr4K.apiKey],
|
apiKey: [x.settings.radarr4K.apiKey],
|
||||||
defaultQualityProfile: [+x.radarr4K.defaultQualityProfile],
|
defaultQualityProfile: [+x.settings.radarr4K.defaultQualityProfile],
|
||||||
defaultRootPath: [x.radarr4K.defaultRootPath],
|
defaultRootPath: [x.settings.radarr4K.defaultRootPath],
|
||||||
ssl: [x.radarr4K.ssl],
|
tag: [x.settings.radarr4K.tag],
|
||||||
subDir: [x.radarr4K.subDir],
|
sendUserTags: [x.settings.radarr4K.sendUserTags],
|
||||||
ip: [x.radarr4K.ip],
|
ssl: [x.settings.radarr4K.ssl],
|
||||||
port: [x.radarr4K.port],
|
subDir: [x.settings.radarr4K.subDir],
|
||||||
addOnly: [x.radarr4K.addOnly],
|
ip: [x.settings.radarr4K.ip],
|
||||||
minimumAvailability: [x.radarr4K.minimumAvailability],
|
port: [x.settings.radarr4K.port],
|
||||||
scanForAvailability: [x.radarr4K.scanForAvailability]
|
addOnly: [x.settings.radarr4K.addOnly],
|
||||||
|
minimumAvailability: [x.settings.radarr4K.minimumAvailability],
|
||||||
|
scanForAvailability: [x.settings.radarr4K.scanForAvailability]
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
this.normalForm.changes.forEach((comp => {
|
this.normalForm.changes.forEach((comp => {
|
||||||
|
@ -70,7 +75,6 @@ export class RadarrComponent implements OnInit {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -82,17 +86,26 @@ export class RadarrComponent implements OnInit {
|
||||||
const radarrForm = form.controls.radarr as UntypedFormGroup;
|
const radarrForm = form.controls.radarr as UntypedFormGroup;
|
||||||
const radarr4KForm = form.controls.radarr4K as UntypedFormGroup;
|
const radarr4KForm = form.controls.radarr4K as UntypedFormGroup;
|
||||||
|
|
||||||
if (radarrForm.controls.enabled.value && (radarrForm.controls.defaultQualityProfile.value === -1 || radarrForm.controls.defaultRootPath.value === "Please Select")) {
|
if (radarrForm.controls.enabled.value && (radarrForm.controls.defaultQualityProfile.value === -1
|
||||||
|
|| radarrForm.controls.defaultRootPath.value === "Please Select")) {
|
||||||
this.notificationService.error("Please check your entered values for Radarr");
|
this.notificationService.error("Please check your entered values for Radarr");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (radarr4KForm.controls.enabled.value && (radarr4KForm.controls.defaultQualityProfile.value === -1 || radarr4KForm.controls.defaultRootPath.value === "Please Select")) {
|
if (radarr4KForm.controls.enabled.value && (radarr4KForm.controls.defaultQualityProfile.value === -1
|
||||||
|
|| radarr4KForm.controls.defaultRootPath.value === "Please Select")) {
|
||||||
this.notificationService.error("Please check your entered values for Radarr 4K");
|
this.notificationService.error("Please check your entered values for Radarr 4K");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (radarr4KForm.controls.tag.value === -1) {
|
||||||
|
radarr4KForm.controls.tag.setValue(null);
|
||||||
|
}
|
||||||
|
if (radarrForm.controls.tag.value === -1) {
|
||||||
|
radarr4KForm.controls.tag.setValue(null);
|
||||||
|
}
|
||||||
|
|
||||||
const settings = <IRadarrCombined> form.value;
|
const settings = <IRadarrCombined> form.value;
|
||||||
this.settingsService.saveRadarr(settings).subscribe(x => {
|
this.radarrFacade.updateSettings(settings).subscribe(x => {
|
||||||
if (x) {
|
if (x) {
|
||||||
this.notificationService.success("Successfully saved Radarr settings");
|
this.notificationService.success("Successfully saved Radarr settings");
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -138,7 +138,7 @@
|
||||||
<div id="tag" class="col-md-6">
|
<div id="tag" class="col-md-6">
|
||||||
<div class="md-form-field" style="display:contents;">
|
<div class="md-form-field" style="display:contents;">
|
||||||
<mat-form-field appearance="outline">
|
<mat-form-field appearance="outline">
|
||||||
<mat-label>Default Tag</mat-label>
|
<mat-label>Tag</mat-label>
|
||||||
<mat-select formControlName="tag">
|
<mat-select formControlName="tag">
|
||||||
<mat-option *ngFor="let tag of tags" [value]="tag.id">{{tag.label}} </mat-option>
|
<mat-option *ngFor="let tag of tags" [value]="tag.id">{{tag.label}} </mat-option>
|
||||||
</mat-select>
|
</mat-select>
|
||||||
|
|
|
@ -65,25 +65,25 @@
|
||||||
|
|
||||||
<!-- Radarr -->
|
<!-- Radarr -->
|
||||||
<div *ngIf="data.type === RequestType.movie && radarrEnabled"><hr />
|
<div *ngIf="data.type === RequestType.movie && radarrEnabled"><hr />
|
||||||
<div>
|
<div>
|
||||||
<h3>Radarr Overrides</h3>
|
<h3>Radarr Overrides</h3>
|
||||||
<mat-form-field appearance="outline" floatLabel=auto>
|
<mat-form-field appearance="outline" floatLabel=auto>
|
||||||
<mat-label>{{'MediaDetails.QualityProfilesSelect' | translate }}</mat-label>
|
<mat-label>{{'MediaDetails.QualityProfilesSelect' | translate }}</mat-label>
|
||||||
<mat-select id="radarrQualitySelect" formControlName="radarrPathId">
|
<mat-select id="radarrQualitySelect" formControlName="radarrPathId">
|
||||||
<mat-option id="radarrQualitySelect{{profile.id}}" *ngFor="let profile of radarrProfiles" value="{{profile.id}}">{{profile.name}}</mat-option>
|
<mat-option id="radarrQualitySelect{{profile.id}}" *ngFor="let profile of radarrProfiles" value="{{profile.id}}">{{profile.name}}</mat-option>
|
||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<div mat-dialog-content>
|
||||||
|
<mat-form-field appearance="outline" floatLabel=auto>
|
||||||
|
<mat-label>{{'MediaDetails.RootFolderSelect' | translate }}</mat-label>
|
||||||
|
<mat-select id="radarrFolderSelect" formControlName="radarrFolderId">
|
||||||
|
<mat-option id="radarrFolderSelect{{profile.id}}" *ngFor="let profile of radarrRootFolders" value="{{profile.id}}">{{profile.path}}</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div mat-dialog-content>
|
<!-- End Radarr-->
|
||||||
<mat-form-field appearance="outline" floatLabel=auto>
|
|
||||||
<mat-label>{{'MediaDetails.RootFolderSelect' | translate }}</mat-label>
|
|
||||||
<mat-select id="radarrFolderSelect" formControlName="radarrFolderId">
|
|
||||||
<mat-option id="radarrFolderSelect{{profile.id}}" *ngFor="let profile of radarrRootFolders" value="{{profile.id}}">{{profile.path}}</mat-option>
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- End Radarr-->
|
|
||||||
|
|
||||||
|
|
||||||
<div mat-dialog-actions class="right-buttons">
|
<div mat-dialog-actions class="right-buttons">
|
||||||
|
|
|
@ -1,124 +1,143 @@
|
||||||
import { Component, Inject, OnInit } from "@angular/core";
|
import { Component, Inject, OnInit } from '@angular/core';
|
||||||
import { UntypedFormBuilder, UntypedFormGroup } from "@angular/forms";
|
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
|
||||||
import { MatDialogRef, MAT_DIALOG_DATA } from "@angular/material/dialog";
|
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||||
import { SonarrFacade } from "app/state/sonarr";
|
import { RadarrFacade } from 'app/state/radarr';
|
||||||
import { firstValueFrom, Observable } from "rxjs";
|
import { SonarrFacade } from 'app/state/sonarr';
|
||||||
import { startWith, map } from "rxjs/operators";
|
import { firstValueFrom, Observable } from 'rxjs';
|
||||||
import { ILanguageProfiles, IRadarrProfile, IRadarrRootFolder, ISonarrProfile, ISonarrRootFolder, IUserDropdown, RequestType } from "../../interfaces";
|
import { startWith, map } from 'rxjs/operators';
|
||||||
import { IdentityService, RadarrService, SonarrService } from "../../services";
|
import {
|
||||||
|
ILanguageProfiles,
|
||||||
|
IRadarrProfile,
|
||||||
|
IRadarrRootFolder,
|
||||||
|
ISonarrProfile,
|
||||||
|
ISonarrRootFolder,
|
||||||
|
IUserDropdown,
|
||||||
|
RequestType,
|
||||||
|
} from '../../interfaces';
|
||||||
|
import { IdentityService, RadarrService, SonarrService } from '../../services';
|
||||||
|
|
||||||
export interface IAdminRequestDialogData {
|
export interface IAdminRequestDialogData {
|
||||||
type: RequestType,
|
type: RequestType;
|
||||||
id: number
|
id: number;
|
||||||
|
is4k: boolean | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "admin-request-dialog",
|
selector: 'admin-request-dialog',
|
||||||
templateUrl: "admin-request-dialog.component.html",
|
templateUrl: 'admin-request-dialog.component.html',
|
||||||
styleUrls: [ "admin-request-dialog.component.scss" ]
|
styleUrls: ['admin-request-dialog.component.scss'],
|
||||||
})
|
})
|
||||||
export class AdminRequestDialogComponent implements OnInit {
|
export class AdminRequestDialogComponent implements OnInit {
|
||||||
constructor(
|
constructor(
|
||||||
public dialogRef: MatDialogRef<AdminRequestDialogComponent>,
|
public dialogRef: MatDialogRef<AdminRequestDialogComponent>,
|
||||||
@Inject(MAT_DIALOG_DATA) public data: IAdminRequestDialogData,
|
@Inject(MAT_DIALOG_DATA) public data: IAdminRequestDialogData,
|
||||||
private identityService: IdentityService,
|
private identityService: IdentityService,
|
||||||
private sonarrService: SonarrService,
|
private sonarrService: SonarrService,
|
||||||
private radarrService: RadarrService,
|
private radarrService: RadarrService,
|
||||||
private fb: UntypedFormBuilder,
|
private fb: UntypedFormBuilder,
|
||||||
private sonarrFacade: SonarrFacade
|
private sonarrFacade: SonarrFacade,
|
||||||
) {}
|
private radarrFacade: RadarrFacade,
|
||||||
|
) {}
|
||||||
|
|
||||||
public form: UntypedFormGroup;
|
public form: UntypedFormGroup;
|
||||||
public RequestType = RequestType;
|
public RequestType = RequestType;
|
||||||
|
|
||||||
public options: IUserDropdown[];
|
public options: IUserDropdown[];
|
||||||
public filteredOptions: Observable<IUserDropdown[]>;
|
public filteredOptions: Observable<IUserDropdown[]>;
|
||||||
public userId: string;
|
public userId: string;
|
||||||
|
|
||||||
public radarrEnabled: boolean;
|
public radarrEnabled: boolean;
|
||||||
public sonarrEnabled: boolean;
|
public radarr4kEnabled: boolean;
|
||||||
|
public sonarrEnabled: boolean;
|
||||||
|
|
||||||
public sonarrProfiles: ISonarrProfile[];
|
public sonarrProfiles: ISonarrProfile[];
|
||||||
public sonarrRootFolders: ISonarrRootFolder[];
|
public sonarrRootFolders: ISonarrRootFolder[];
|
||||||
public sonarrLanguageProfiles: ILanguageProfiles[];
|
public sonarrLanguageProfiles: ILanguageProfiles[];
|
||||||
public radarrProfiles: IRadarrProfile[];
|
public radarrProfiles: IRadarrProfile[];
|
||||||
public radarrRootFolders: IRadarrRootFolder[];
|
public radarrRootFolders: IRadarrRootFolder[];
|
||||||
|
|
||||||
public async ngOnInit() {
|
public async ngOnInit() {
|
||||||
|
this.form = this.fb.group({
|
||||||
|
username: [null],
|
||||||
|
sonarrPathId: [null],
|
||||||
|
sonarrFolderId: [null],
|
||||||
|
sonarrLanguageId: [null],
|
||||||
|
radarrPathId: [null],
|
||||||
|
radarrFolderId: [null],
|
||||||
|
});
|
||||||
|
|
||||||
this.form = this.fb.group({
|
this.options = await firstValueFrom(this.identityService.getUsersDropdown());
|
||||||
username: [null],
|
|
||||||
sonarrPathId: [null],
|
|
||||||
sonarrFolderId: [null],
|
|
||||||
sonarrLanguageId: [null],
|
|
||||||
radarrPathId: [null],
|
|
||||||
radarrFolderId: [null]
|
|
||||||
})
|
|
||||||
|
|
||||||
this.options = await firstValueFrom(this.identityService.getUsersDropdown());
|
this.filteredOptions = this.form.controls['username'].valueChanges.pipe(
|
||||||
|
startWith(''),
|
||||||
|
map((value) => this._filter(value)),
|
||||||
|
);
|
||||||
|
|
||||||
this.filteredOptions = this.form.controls['username'].valueChanges.pipe(
|
if (this.data.type === RequestType.tvShow) {
|
||||||
startWith(""),
|
this.sonarrEnabled = this.sonarrFacade.isEnabled();
|
||||||
map((value) => this._filter(value))
|
if (this.sonarrEnabled) {
|
||||||
);
|
console.log(this.sonarrFacade.version());
|
||||||
|
if (this.sonarrFacade.version()[0] === '3') {
|
||||||
|
this.sonarrService.getV3LanguageProfilesWithoutSettings().subscribe((profiles: ILanguageProfiles[]) => {
|
||||||
|
this.sonarrLanguageProfiles = profiles;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.sonarrService.getQualityProfilesWithoutSettings().subscribe((c) => {
|
||||||
|
this.sonarrProfiles = c;
|
||||||
|
});
|
||||||
|
this.sonarrService.getRootFoldersWithoutSettings().subscribe((c) => {
|
||||||
|
this.sonarrRootFolders = c;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.data.type === RequestType.movie) {
|
||||||
|
this.radarrEnabled = this.radarrFacade.isEnabled();
|
||||||
|
this.radarr4kEnabled = this.radarrFacade.is4KEnabled();
|
||||||
|
|
||||||
if (this.data.type === RequestType.tvShow) {
|
if (this.data.is4k ?? false) {
|
||||||
this.sonarrEnabled = this.sonarrFacade.isEnabled();
|
if (this.radarr4kEnabled) {
|
||||||
if (this.sonarrEnabled) {
|
this.radarrService.getQualityProfiles4kFromSettings().subscribe((c) => {
|
||||||
console.log(this.sonarrFacade.version());
|
this.radarrProfiles = c;
|
||||||
if (this.sonarrFacade.version()[0] === "3") {
|
});
|
||||||
this.sonarrService.getV3LanguageProfilesWithoutSettings().subscribe((profiles: ILanguageProfiles[]) => {
|
this.radarrService.getRootFolders4kFromSettings().subscribe((c) => {
|
||||||
this.sonarrLanguageProfiles = profiles;
|
this.radarrRootFolders = c;
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
this.sonarrService.getQualityProfilesWithoutSettings().subscribe(c => {
|
} else {
|
||||||
this.sonarrProfiles = c;
|
if (this.radarrEnabled) {
|
||||||
});
|
this.radarrService.getQualityProfilesFromSettings().subscribe((c) => {
|
||||||
this.sonarrService.getRootFoldersWithoutSettings().subscribe(c => {
|
this.radarrProfiles = c;
|
||||||
this.sonarrRootFolders = c;
|
});
|
||||||
});
|
this.radarrService.getRootFoldersFromSettings().subscribe((c) => {
|
||||||
}
|
this.radarrRootFolders = c;
|
||||||
}
|
});
|
||||||
if (this.data.type === RequestType.movie) {
|
}
|
||||||
this.radarrEnabled = await this.radarrService.isRadarrEnabled();
|
}
|
||||||
if (this.radarrEnabled) {
|
}
|
||||||
this.radarrService.getQualityProfilesFromSettings().subscribe(c => {
|
}
|
||||||
this.radarrProfiles = c;
|
|
||||||
});
|
|
||||||
this.radarrService.getRootFoldersFromSettings().subscribe(c => {
|
|
||||||
this.radarrRootFolders = c;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public displayFn(user: IUserDropdown): string {
|
public displayFn(user: IUserDropdown): string {
|
||||||
const username = user?.username ? user.username : "";
|
const username = user?.username ? user.username : '';
|
||||||
const email = user?.email ? `(${user.email})` : "";
|
const email = user?.email ? `(${user.email})` : '';
|
||||||
if (username || email) {
|
if (username || email) {
|
||||||
return `${username} ${email}`;
|
return `${username} ${email}`;
|
||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
private _filter(value: string | IUserDropdown): IUserDropdown[] {
|
private _filter(value: string | IUserDropdown): IUserDropdown[] {
|
||||||
const filterValue =
|
const filterValue = typeof value === 'string' ? value.toLowerCase() : value.username.toLowerCase();
|
||||||
typeof value === "string"
|
|
||||||
? value.toLowerCase()
|
|
||||||
: value.username.toLowerCase();
|
|
||||||
|
|
||||||
return this.options.filter((option) =>
|
return this.options.filter((option) => option.username.toLowerCase().includes(filterValue));
|
||||||
option.username.toLowerCase().includes(filterValue)
|
}
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async submitRequest() {
|
public async submitRequest() {
|
||||||
const model = this.form.value;
|
const model = this.form.value;
|
||||||
model.radarrQualityOverrideTitle = this.radarrProfiles?.filter(x => x.id == model.radarrPathId)[0]?.name;
|
model.radarrQualityOverrideTitle = this.radarrProfiles?.filter((x) => x.id == model.radarrPathId)[0]?.name;
|
||||||
model.radarrRootFolderTitle = this.radarrRootFolders?.filter(x => x.id == model.radarrFolderId)[0]?.path;
|
model.radarrRootFolderTitle = this.radarrRootFolders?.filter((x) => x.id == model.radarrFolderId)[0]?.path;
|
||||||
model.sonarrRootFolderTitle = this.sonarrRootFolders?.filter(x => x.id == model.sonarrFolderId)[0]?.path;
|
model.sonarrRootFolderTitle = this.sonarrRootFolders?.filter((x) => x.id == model.sonarrFolderId)[0]?.path;
|
||||||
model.sonarrQualityOverrideTitle = this.sonarrProfiles?.filter(x => x.id == model.sonarrPathId)[0]?.name;
|
model.sonarrQualityOverrideTitle = this.sonarrProfiles?.filter((x) => x.id == model.sonarrPathId)[0]?.name;
|
||||||
model.sonarrLanguageProfileTitle = this.sonarrLanguageProfiles?.filter(x => x.id == model.sonarrLanguageId)[0]?.name;
|
model.sonarrLanguageProfileTitle = this.sonarrLanguageProfiles?.filter((x) => x.id == model.sonarrLanguageId)[0]?.name;
|
||||||
this.dialogRef.close(model);
|
this.dialogRef.close(model);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,7 +62,7 @@ export class EpisodeRequestComponent {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.data.isAdmin) {
|
if (this.data.isAdmin) {
|
||||||
const dialog = this.dialog.open(AdminRequestDialogComponent, { width: "700px", data: { type: RequestType.tvShow, id: this.data.series.id }, panelClass: 'modal-panel' });
|
const dialog = this.dialog.open(AdminRequestDialogComponent, { width: "700px", data: { type: RequestType.tvShow, id: this.data.series.id, is4k: null }, panelClass: 'modal-panel' });
|
||||||
dialog.afterClosed().subscribe(async (result) => {
|
dialog.afterClosed().subscribe(async (result) => {
|
||||||
if (result) {
|
if (result) {
|
||||||
viewModel.requestOnBehalf = result.username?.id;
|
viewModel.requestOnBehalf = result.username?.id;
|
||||||
|
|
4
src/Ombi/ClientApp/src/app/state/radarr/index.ts
Normal file
4
src/Ombi/ClientApp/src/app/state/radarr/index.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
export * from './radarr.state';
|
||||||
|
export * from './radarr.actions';
|
||||||
|
export * from './radarr.facade';
|
||||||
|
export * from './radarr.selectors';
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { APP_INITIALIZER } from "@angular/core";
|
||||||
|
import { Observable } from "rxjs";
|
||||||
|
import { RadarrFacade } from "./radarr.facade";
|
||||||
|
|
||||||
|
export const RADARR_INITIALIZER = {
|
||||||
|
provide: APP_INITIALIZER,
|
||||||
|
useFactory: (radarrFacade: RadarrFacade) => (): Observable<unknown> => {
|
||||||
|
return radarrFacade.load();
|
||||||
|
},
|
||||||
|
multi: true,
|
||||||
|
deps: [RadarrFacade],
|
||||||
|
};
|
10
src/Ombi/ClientApp/src/app/state/radarr/radarr.actions.ts
Normal file
10
src/Ombi/ClientApp/src/app/state/radarr/radarr.actions.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import { IRadarrCombined } from "../../interfaces";
|
||||||
|
|
||||||
|
export class LoadSettings {
|
||||||
|
public static readonly type = '[Radarr] LoadSettings';
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UpdateSettings {
|
||||||
|
public static readonly type = '[Radarr] UpdateSettings';
|
||||||
|
constructor(public settings: IRadarrCombined) { }
|
||||||
|
}
|
27
src/Ombi/ClientApp/src/app/state/radarr/radarr.facade.ts
Normal file
27
src/Ombi/ClientApp/src/app/state/radarr/radarr.facade.ts
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import { IRadarrCombined } from "../../interfaces";
|
||||||
|
import { Injectable } from "@angular/core";
|
||||||
|
import { Observable } from "rxjs";
|
||||||
|
import { Store } from "@ngxs/store";
|
||||||
|
import { RadarrState } from "./types";
|
||||||
|
import { RadarrSelectors } from "./radarr.selectors";
|
||||||
|
import { LoadSettings, UpdateSettings } from "./radarr.actions";
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class RadarrFacade {
|
||||||
|
|
||||||
|
public constructor(private store: Store) {}
|
||||||
|
|
||||||
|
public state$ = (): Observable<RadarrState> => this.store.select(RadarrSelectors.state);
|
||||||
|
|
||||||
|
public updateSettings = (settings: IRadarrCombined): Observable<unknown> => this.store.dispatch(new UpdateSettings(settings));
|
||||||
|
|
||||||
|
public load = (): Observable<unknown> => this.store.dispatch(new LoadSettings());
|
||||||
|
|
||||||
|
public settings = (): IRadarrCombined => this.store.selectSnapshot(RadarrSelectors.settings);
|
||||||
|
|
||||||
|
public isEnabled = (): boolean => this.store.selectSnapshot(RadarrSelectors.isEnabled);
|
||||||
|
|
||||||
|
public is4KEnabled = (): boolean => this.store.selectSnapshot(RadarrSelectors.is4KEnabled);
|
||||||
|
}
|
26
src/Ombi/ClientApp/src/app/state/radarr/radarr.selectors.ts
Normal file
26
src/Ombi/ClientApp/src/app/state/radarr/radarr.selectors.ts
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import { RadarrState, RADARR_STATE_TOKEN } from "./types";
|
||||||
|
import { Selector } from "@ngxs/store";
|
||||||
|
import { IRadarrCombined } from "../../interfaces";
|
||||||
|
|
||||||
|
export class RadarrSelectors {
|
||||||
|
|
||||||
|
@Selector([RADARR_STATE_TOKEN])
|
||||||
|
public static state(state: RadarrState): RadarrState {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Selector([RadarrSelectors.state])
|
||||||
|
public static settings(state: RadarrState): IRadarrCombined {
|
||||||
|
return state.settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Selector([RadarrSelectors.settings])
|
||||||
|
public static isEnabled(settings: IRadarrCombined): boolean {
|
||||||
|
return settings?.radarr?.enabled ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Selector([RadarrSelectors.settings])
|
||||||
|
public static is4KEnabled(settings: IRadarrCombined): boolean {
|
||||||
|
return settings?.radarr4K?.enabled ?? false;
|
||||||
|
}
|
||||||
|
}
|
41
src/Ombi/ClientApp/src/app/state/radarr/radarr.state.ts
Normal file
41
src/Ombi/ClientApp/src/app/state/radarr/radarr.state.ts
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import { Action, State, StateContext } from "@ngxs/store";
|
||||||
|
|
||||||
|
import { RadarrState, RADARR_STATE_TOKEN } from "./types";
|
||||||
|
import { SettingsService } from "../../services";
|
||||||
|
import { AuthService } from "../../auth/auth.service";
|
||||||
|
import { Injectable } from "@angular/core";
|
||||||
|
import { combineLatest, Observable, of } from "rxjs";
|
||||||
|
import { map, tap } from "rxjs/operators";
|
||||||
|
import { IRadarrCombined } from "../../interfaces";
|
||||||
|
import { LoadSettings, UpdateSettings } from "./radarr.actions";
|
||||||
|
|
||||||
|
@State({
|
||||||
|
name: RADARR_STATE_TOKEN
|
||||||
|
})
|
||||||
|
@Injectable()
|
||||||
|
export class RadarrSettingsState {
|
||||||
|
constructor(private settingsService: SettingsService, private authService: AuthService) { }
|
||||||
|
|
||||||
|
@Action(LoadSettings)
|
||||||
|
public load({ setState }: StateContext<RadarrState>): Observable<RadarrState> {
|
||||||
|
const isAdmin = this.authService.isAdmin();
|
||||||
|
const calls = isAdmin ? [this.settingsService.getRadarr()] : [of({})];
|
||||||
|
|
||||||
|
return combineLatest(calls).pipe(
|
||||||
|
tap(([settings]) =>
|
||||||
|
{
|
||||||
|
setState({settings: settings as IRadarrCombined});
|
||||||
|
}),
|
||||||
|
map((result) => <RadarrState>{settings: result[0]})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Action(UpdateSettings)
|
||||||
|
public enable(ctx: StateContext<RadarrState>, { settings }: UpdateSettings): Observable<RadarrState> {
|
||||||
|
const state = ctx.getState();
|
||||||
|
return this.settingsService.saveRadarr(settings).pipe(
|
||||||
|
tap((_) => ctx.setState({...state, settings})),
|
||||||
|
map(_ => <RadarrState>{...state, settings})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
8
src/Ombi/ClientApp/src/app/state/radarr/types.ts
Normal file
8
src/Ombi/ClientApp/src/app/state/radarr/types.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import { IRadarrCombined } from "../../interfaces";
|
||||||
|
import { StateToken } from "@ngxs/store";
|
||||||
|
|
||||||
|
export const RADARR_STATE_TOKEN = new StateToken<RadarrState>('RadarrState');
|
||||||
|
|
||||||
|
export interface RadarrState {
|
||||||
|
settings: IRadarrCombined;
|
||||||
|
}
|
|
@ -18,18 +18,18 @@ namespace Ombi.Controllers.V1.External
|
||||||
public class RadarrController : ControllerBase
|
public class RadarrController : ControllerBase
|
||||||
{
|
{
|
||||||
|
|
||||||
public RadarrController(IRadarrApi radarr, ISettingsService<RadarrSettings> settings,
|
public RadarrController(
|
||||||
ICacheService mem, IRadarrV3Api radarrV3Api)
|
ISettingsService<RadarrSettings> settings,
|
||||||
|
ISettingsService<Radarr4KSettings> radarr4kSettings,
|
||||||
|
IRadarrV3Api radarrV3Api)
|
||||||
{
|
{
|
||||||
_radarrApi = radarr;
|
|
||||||
_radarrSettings = settings;
|
_radarrSettings = settings;
|
||||||
_cache = mem;
|
_radarr4KSettings = radarr4kSettings;
|
||||||
_radarrV3Api = radarrV3Api;
|
_radarrV3Api = radarrV3Api;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly IRadarrApi _radarrApi;
|
|
||||||
private readonly ISettingsService<RadarrSettings> _radarrSettings;
|
private readonly ISettingsService<RadarrSettings> _radarrSettings;
|
||||||
private readonly ICacheService _cache;
|
private readonly ISettingsService<Radarr4KSettings> _radarr4KSettings;
|
||||||
private readonly IRadarrV3Api _radarrV3Api;
|
private readonly IRadarrV3Api _radarrV3Api;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the Radarr profiles.
|
/// Gets the Radarr profiles.
|
||||||
|
@ -80,6 +80,23 @@ namespace Ombi.Controllers.V1.External
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Radarr 4K profiles using the saved settings
|
||||||
|
/// <remarks>The data is cached for an hour</remarks>
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
[HttpGet("Profiles/4k")]
|
||||||
|
[PowerUser]
|
||||||
|
public async Task<IActionResult> GetProfiles4K()
|
||||||
|
{
|
||||||
|
var settings = await _radarr4KSettings.GetSettingsAsync();
|
||||||
|
if (settings.Enabled)
|
||||||
|
{
|
||||||
|
return Ok(await _radarrV3Api.GetProfiles(settings.ApiKey, settings.FullUri));
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the Radarr root folders using the saved settings.
|
/// Gets the Radarr root folders using the saved settings.
|
||||||
/// <remarks>The data is cached for an hour</remarks>
|
/// <remarks>The data is cached for an hour</remarks>
|
||||||
|
@ -97,6 +114,23 @@ namespace Ombi.Controllers.V1.External
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Radarr 4K root folders using the saved settings.
|
||||||
|
/// <remarks>The data is cached for an hour</remarks>
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
[HttpGet("RootFolders/4k")]
|
||||||
|
[PowerUser]
|
||||||
|
public async Task<IEnumerable<RadarrRootFolder>> GetRootFolders4K()
|
||||||
|
{
|
||||||
|
var settings = await _radarr4KSettings.GetSettingsAsync();
|
||||||
|
if (settings.Enabled)
|
||||||
|
{
|
||||||
|
return await _radarrV3Api.GetRootFolders(settings.ApiKey, settings.FullUri);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the Radarr tags
|
/// Gets the Radarr tags
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue