From 8220f41e0b9a4fbad56143c0e9cbd1d169802dc3 Mon Sep 17 00:00:00 2001 From: tidusjar Date: Mon, 4 Jan 2021 14:14:24 +0000 Subject: [PATCH] * Added the ability to request on behalf of a user * Moved the "advanced" section into a small cog icon on the media details page * Added some more information on the movie panel e.g. Requested By user --- src/Ombi.Core/Engine/MovieRequestEngine.cs | 32 ++- src/Ombi.Core/Engine/TvRequestEngine.cs | 34 +++- .../Models/Requests/MovieRequestViewModel.cs | 1 + .../Models/Requests/TvRequestViewModel.cs | 2 + src/Ombi.Core/Models/UI/UserViewModel.cs | 6 + .../card/discover-card-details.component.ts | 2 +- .../discover-collections.component.ts | 2 +- .../grid/discover-grid.component.ts | 6 +- .../ClientApp/src/app/interfaces/IRadarr.ts | 7 +- .../src/app/interfaces/IRequestModel.ts | 1 + .../src/app/interfaces/ISearchTvResult.ts | 1 + .../ClientApp/src/app/interfaces/IUser.ts | 5 + .../src/app/media-details/components/index.ts | 10 +- .../movie/movie-details.component.html | 29 +-- .../movie/movie-details.component.ts | 35 +++- .../movie-admin-panel.component.html | 3 - .../movie-admin-panel.component.ts | 76 -------- .../movie-advanced-options.component.html | 4 +- .../movie-advanced-options.component.ts | 53 ++++- .../movie-information-panel.component.html | 20 +- .../request-behalf.component.html | 22 +++ .../request-behalf.component.ts | 52 +++++ .../tv-admin-panel.component.html | 3 - .../tv-admin-panel.component.ts | 83 -------- .../tv-advanced-options.component.html | 23 +++ .../tv-advanced-options.component.ts | 55 ++++++ .../tv-information-panel.component.html | 4 +- .../components/tv/tv-details.component.html | 184 ++++++++++-------- .../components/tv/tv-details.component.ts | 39 +++- .../media-details.component.scss | 4 + .../app/media-details/media-details.module.ts | 4 +- .../src/app/services/identity.service.ts | 6 +- .../episode-request.component.html | 4 +- .../episode-request.component.ts | 33 ++-- src/Ombi/ClientApp/src/styles/shared.scss | 6 +- src/Ombi/Controllers/V1/IdentityController.cs | 25 +++ src/Ombi/wwwroot/translations/en.json | 6 +- 37 files changed, 552 insertions(+), 330 deletions(-) delete mode 100644 src/Ombi/ClientApp/src/app/media-details/components/movie/panels/movie-admin-panel/movie-admin-panel.component.html delete mode 100644 src/Ombi/ClientApp/src/app/media-details/components/movie/panels/movie-admin-panel/movie-admin-panel.component.ts create mode 100644 src/Ombi/ClientApp/src/app/media-details/components/shared/request-behalf/request-behalf.component.html create mode 100644 src/Ombi/ClientApp/src/app/media-details/components/shared/request-behalf/request-behalf.component.ts delete mode 100644 src/Ombi/ClientApp/src/app/media-details/components/tv/panels/tv-admin-panel/tv-admin-panel.component.html delete mode 100644 src/Ombi/ClientApp/src/app/media-details/components/tv/panels/tv-admin-panel/tv-admin-panel.component.ts create mode 100644 src/Ombi/ClientApp/src/app/media-details/components/tv/panels/tv-advanced-options/tv-advanced-options.component.html create mode 100644 src/Ombi/ClientApp/src/app/media-details/components/tv/panels/tv-advanced-options/tv-advanced-options.component.ts diff --git a/src/Ombi.Core/Engine/MovieRequestEngine.cs b/src/Ombi.Core/Engine/MovieRequestEngine.cs index 741503795..c9dbde067 100644 --- a/src/Ombi.Core/Engine/MovieRequestEngine.cs +++ b/src/Ombi.Core/Engine/MovieRequestEngine.cs @@ -67,6 +67,22 @@ namespace Ombi.Core.Engine $"{movieInfo.Title}{(!string.IsNullOrEmpty(movieInfo.ReleaseDate) ? $" ({DateTime.Parse(movieInfo.ReleaseDate).Year})" : string.Empty)}"; var userDetails = await GetUser(); + var canRequestOnBehalf = false; + + if (model.RequestOnBehalf.HasValue()) + { + canRequestOnBehalf = await UserManager.IsInRoleAsync(userDetails, OmbiRoles.PowerUser) || await UserManager.IsInRoleAsync(userDetails, OmbiRoles.Admin); + + if (!canRequestOnBehalf) + { + return new RequestEngineResult + { + Result = false, + Message = "You do not have the correct permissions to request on behalf of users!", + ErrorMessage = $"You do not have the correct permissions to request on behalf of users!" + }; + } + } var requestModel = new MovieRequests { @@ -82,7 +98,7 @@ namespace Ombi.Core.Engine Status = movieInfo.Status, RequestedDate = DateTime.UtcNow, Approved = false, - RequestedUserId = userDetails.Id, + RequestedUserId = canRequestOnBehalf ? model.RequestOnBehalf : userDetails.Id, Background = movieInfo.BackdropPath, LangCode = model.LanguageCode, RequestedByAlias = model.RequestedByAlias @@ -103,7 +119,7 @@ namespace Ombi.Core.Engine if (requestModel.Approved) // The rules have auto approved this { - var requestEngineResult = await AddMovieRequest(requestModel, fullMovieName); + var requestEngineResult = await AddMovieRequest(requestModel, fullMovieName, model.RequestOnBehalf); if (requestEngineResult.Result) { var result = await ApproveMovie(requestModel); @@ -124,7 +140,7 @@ namespace Ombi.Core.Engine // If there are no providers then it's successful but movie has not been sent } - return await AddMovieRequest(requestModel, fullMovieName); + return await AddMovieRequest(requestModel, fullMovieName, model.RequestOnBehalf); } @@ -270,7 +286,7 @@ namespace Ombi.Core.Engine allRequests = allRequests.Where(x => x.Available); break; case RequestStatus.Denied: - allRequests = allRequests.Where(x => x.Denied.HasValue && x.Denied.Value && !x.Available); + allRequests = allRequests.Where(x => x.Denied.HasValue && x.Denied.Value && !x.Available); break; default: break; @@ -429,7 +445,7 @@ namespace Ombi.Core.Engine public async Task GetRequest(int requestId) { var request = await MovieRepository.GetWithUser().Where(x => x.Id == requestId).FirstOrDefaultAsync(); - await CheckForSubscription(new HideResult(), new List{request }); + await CheckForSubscription(new HideResult(), new List { request }); return request; } @@ -654,19 +670,19 @@ namespace Ombi.Core.Engine }; } - private async Task AddMovieRequest(MovieRequests model, string movieName) + private async Task AddMovieRequest(MovieRequests model, string movieName, string requestOnBehalf) { await MovieRepository.Add(model); var result = await RunSpecificRule(model, SpecificRules.CanSendNotification); if (result.Success) - { + { await NotificationHelper.NewRequest(model); } await _requestLog.Add(new RequestLog { - UserId = (await GetUser()).Id, + UserId = requestOnBehalf.HasValue() ? requestOnBehalf : (await GetUser()).Id, RequestDate = DateTime.UtcNow, RequestId = model.Id, RequestType = RequestType.Movie, diff --git a/src/Ombi.Core/Engine/TvRequestEngine.cs b/src/Ombi.Core/Engine/TvRequestEngine.cs index e066261f4..b06c99d49 100644 --- a/src/Ombi.Core/Engine/TvRequestEngine.cs +++ b/src/Ombi.Core/Engine/TvRequestEngine.cs @@ -51,12 +51,28 @@ namespace Ombi.Core.Engine public async Task RequestTvShow(TvRequestViewModel tv) { var user = await GetUser(); + var canRequestOnBehalf = false; + + if (tv.RequestOnBehalf.HasValue()) + { + canRequestOnBehalf = await UserManager.IsInRoleAsync(user, OmbiRoles.PowerUser) || await UserManager.IsInRoleAsync(user, OmbiRoles.Admin); + + if (!canRequestOnBehalf) + { + return new RequestEngineResult + { + Result = false, + Message = "You do not have the correct permissions to request on behalf of users!", + ErrorMessage = $"You do not have the correct permissions to request on behalf of users!" + }; + } + } var tvBuilder = new TvShowRequestBuilder(TvApi, MovieDbApi); (await tvBuilder .GetShowInfo(tv.TvDbId)) .CreateTvList(tv) - .CreateChild(tv, user.Id); + .CreateChild(tv, canRequestOnBehalf ? tv.RequestOnBehalf : user.Id); await tvBuilder.BuildEpisodes(tv); @@ -124,12 +140,12 @@ namespace Ombi.Core.Engine ErrorMessage = "This has already been requested" }; } - return await AddExistingRequest(tvBuilder.ChildRequest, existingRequest); + return await AddExistingRequest(tvBuilder.ChildRequest, existingRequest, tv.RequestOnBehalf); } // This is a new request var newRequest = tvBuilder.CreateNewRequest(tv); - return await AddRequest(newRequest.NewRequest); + return await AddRequest(newRequest.NewRequest, tv.RequestOnBehalf); } public async Task> GetRequests(int count, int position, OrderFilterModel type) @@ -736,21 +752,21 @@ namespace Ombi.Core.Engine } } - private async Task AddExistingRequest(ChildRequests newRequest, TvRequests existingRequest) + private async Task AddExistingRequest(ChildRequests newRequest, TvRequests existingRequest, string requestOnBehalf) { // Add the child existingRequest.ChildRequests.Add(newRequest); await TvRepository.Update(existingRequest); - return await AfterRequest(newRequest); + return await AfterRequest(newRequest, requestOnBehalf); } - private async Task AddRequest(TvRequests model) + private async Task AddRequest(TvRequests model, string requestOnBehalf) { await TvRepository.Add(model); // This is a new request so we should only have 1 child - return await AfterRequest(model.ChildRequests.FirstOrDefault()); + return await AfterRequest(model.ChildRequests.FirstOrDefault(), requestOnBehalf); } private static List SortEpisodes(List items) @@ -766,7 +782,7 @@ namespace Ombi.Core.Engine } - private async Task AfterRequest(ChildRequests model) + private async Task AfterRequest(ChildRequests model, string requestOnBehalf) { var sendRuleResult = await RunSpecificRule(model, SpecificRules.CanSendNotification); if (sendRuleResult.Success) @@ -776,7 +792,7 @@ namespace Ombi.Core.Engine await _requestLog.Add(new RequestLog { - UserId = (await GetUser()).Id, + UserId = requestOnBehalf.HasValue() ? requestOnBehalf : (await GetUser()).Id, RequestDate = DateTime.UtcNow, RequestId = model.Id, RequestType = RequestType.TvShow, diff --git a/src/Ombi.Core/Models/Requests/MovieRequestViewModel.cs b/src/Ombi.Core/Models/Requests/MovieRequestViewModel.cs index 5a79d2982..22d1cc449 100644 --- a/src/Ombi.Core/Models/Requests/MovieRequestViewModel.cs +++ b/src/Ombi.Core/Models/Requests/MovieRequestViewModel.cs @@ -33,6 +33,7 @@ namespace Ombi.Core.Models.Requests { public int TheMovieDbId { get; set; } public string LanguageCode { get; set; } = "en"; + public string RequestOnBehalf { get; set; } /// /// This is only set from a HTTP Header diff --git a/src/Ombi.Core/Models/Requests/TvRequestViewModel.cs b/src/Ombi.Core/Models/Requests/TvRequestViewModel.cs index c17925b1b..15349462b 100644 --- a/src/Ombi.Core/Models/Requests/TvRequestViewModel.cs +++ b/src/Ombi.Core/Models/Requests/TvRequestViewModel.cs @@ -12,6 +12,8 @@ namespace Ombi.Core.Models.Requests public List Seasons { get; set; } = new List(); [JsonIgnore] public string RequestedByAlias { get; set; } + + public string RequestOnBehalf { get; set; } } public class SeasonsViewModel diff --git a/src/Ombi.Core/Models/UI/UserViewModel.cs b/src/Ombi.Core/Models/UI/UserViewModel.cs index e74e5037d..0c3c9e349 100644 --- a/src/Ombi.Core/Models/UI/UserViewModel.cs +++ b/src/Ombi.Core/Models/UI/UserViewModel.cs @@ -30,4 +30,10 @@ namespace Ombi.Core.Models.UI public string Value { get; set; } public bool Enabled { get; set; } } + + public class UserViewModelDropdown + { + public string Id { get; set; } + public string Username { get; set; } + } } \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/discover/components/card/discover-card-details.component.ts b/src/Ombi/ClientApp/src/app/discover/components/card/discover-card-details.component.ts index fa13b1866..f3d1276ad 100644 --- a/src/Ombi/ClientApp/src/app/discover/components/card/discover-card-details.component.ts +++ b/src/Ombi/ClientApp/src/app/discover/components/card/discover-card-details.component.ts @@ -67,7 +67,7 @@ export class DiscoverCardDetailsComponent implements OnInit { public async request() { this.loading = true; if (this.data.type === RequestType.movie) { - const result = await this.requestService.requestMovie({ theMovieDbId: this.data.id, languageCode: "" }).toPromise(); + const result = await this.requestService.requestMovie({ theMovieDbId: this.data.id, languageCode: "", requestOnBehalf: null }).toPromise(); this.loading = false; if (result.result) { diff --git a/src/Ombi/ClientApp/src/app/discover/components/collections/discover-collections.component.ts b/src/Ombi/ClientApp/src/app/discover/components/collections/discover-collections.component.ts index 9008dd8ca..dd634ef92 100644 --- a/src/Ombi/ClientApp/src/app/discover/components/collections/discover-collections.component.ts +++ b/src/Ombi/ClientApp/src/app/discover/components/collections/discover-collections.component.ts @@ -34,7 +34,7 @@ export class DiscoverCollectionsComponent implements OnInit { public async requestCollection() { await this.collection.collection.forEach(async (movie) => { - await this.requestService.requestMovie({theMovieDbId: movie.id, languageCode: null}).toPromise(); + await this.requestService.requestMovie({theMovieDbId: movie.id, languageCode: null, requestOnBehalf: null}).toPromise(); }); this.messageService.send("Requested Collection"); } diff --git a/src/Ombi/ClientApp/src/app/discover/components/grid/discover-grid.component.ts b/src/Ombi/ClientApp/src/app/discover/components/grid/discover-grid.component.ts index 4c28c1174..42f371dc2 100644 --- a/src/Ombi/ClientApp/src/app/discover/components/grid/discover-grid.component.ts +++ b/src/Ombi/ClientApp/src/app/discover/components/grid/discover-grid.component.ts @@ -5,7 +5,7 @@ import { ImageService, RequestService, SearchV2Service } from "../../../services import { MatDialog } from "@angular/material/dialog"; import { ISearchTvResultV2 } from "../../../interfaces/ISearchTvResultV2"; import { ISearchMovieResultV2 } from "../../../interfaces/ISearchMovieResultV2"; -import { EpisodeRequestComponent } from "../../../shared/episode-request/episode-request.component"; +import { EpisodeRequestComponent, EpisodeRequestData } from "../../../shared/episode-request/episode-request.component"; import { MatSnackBar } from "@angular/material/snack-bar"; import { Router } from "@angular/router"; import { DomSanitizer } from "@angular/platform-browser"; @@ -139,7 +139,7 @@ export class DiscoverGridComponent implements OnInit { public async request() { this.requesting = true; if (this.result.type === RequestType.movie) { - const result = await this.requestService.requestMovie({ theMovieDbId: this.result.id, languageCode: "" }).toPromise(); + const result = await this.requestService.requestMovie({ theMovieDbId: this.result.id, languageCode: "", requestOnBehalf: null }).toPromise(); if (result.result) { this.result.requested = true; @@ -148,7 +148,7 @@ export class DiscoverGridComponent implements OnInit { this.notification.open(result.errorMessage, "Ok"); } } else if (this.result.type === RequestType.tvShow) { - this.dialog.open(EpisodeRequestComponent, { width: "700px", data: this.tv, panelClass: 'modal-panel' }) + this.dialog.open(EpisodeRequestComponent, { width: "700px", data: { series: this.tv, requestOnBehalf: null }, panelClass: 'modal-panel' }) } this.requesting = false; } diff --git a/src/Ombi/ClientApp/src/app/interfaces/IRadarr.ts b/src/Ombi/ClientApp/src/app/interfaces/IRadarr.ts index a52b19d9d..e01d74cd5 100644 --- a/src/Ombi/ClientApp/src/app/interfaces/IRadarr.ts +++ b/src/Ombi/ClientApp/src/app/interfaces/IRadarr.ts @@ -1,4 +1,7 @@ -export interface IRadarrRootFolder { +import { IChildRequests, IMovieRequests } from "."; +import { ITvRequests } from "./IRequestModel"; + +export interface IRadarrRootFolder { id: number; path: string; } @@ -24,4 +27,6 @@ export interface IAdvancedData { rootFolder: IRadarrRootFolder; rootFolders: IRadarrRootFolder[]; rootFolderId: number; + movieRequest: IMovieRequests; + tvRequest: ITvRequests; } \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/interfaces/IRequestModel.ts b/src/Ombi/ClientApp/src/app/interfaces/IRequestModel.ts index 4efb5ee76..1ee92f336 100644 --- a/src/Ombi/ClientApp/src/app/interfaces/IRequestModel.ts +++ b/src/Ombi/ClientApp/src/app/interfaces/IRequestModel.ts @@ -167,6 +167,7 @@ export interface IEpisodesRequests { export interface IMovieRequestModel { theMovieDbId: number; languageCode: string | undefined; + requestOnBehalf: string | undefined; } export interface IFilter { diff --git a/src/Ombi/ClientApp/src/app/interfaces/ISearchTvResult.ts b/src/Ombi/ClientApp/src/app/interfaces/ISearchTvResult.ts index 477cc6dcd..73533d7c6 100644 --- a/src/Ombi/ClientApp/src/app/interfaces/ISearchTvResult.ts +++ b/src/Ombi/ClientApp/src/app/interfaces/ISearchTvResult.ts @@ -46,6 +46,7 @@ export interface ITvRequestViewModel { latestSeason: boolean; tvDbId: number; seasons: ISeasonsViewModel[]; + requestOnBehalf: string | undefined; } export interface ISeasonsViewModel { diff --git a/src/Ombi/ClientApp/src/app/interfaces/IUser.ts b/src/Ombi/ClientApp/src/app/interfaces/IUser.ts index 4235d55cf..f03238b7c 100644 --- a/src/Ombi/ClientApp/src/app/interfaces/IUser.ts +++ b/src/Ombi/ClientApp/src/app/interfaces/IUser.ts @@ -24,6 +24,11 @@ export interface IUser { musicRequestQuota: IRemainingRequests | null; } +export interface IUserDropdown { + username: string; + id: string; +} + export interface IUserQualityProfiles { sonarrQualityProfileAnime: number; sonarrRootPathAnime: number; diff --git a/src/Ombi/ClientApp/src/app/media-details/components/index.ts b/src/Ombi/ClientApp/src/app/media-details/components/index.ts index 001eb20bf..52892ade2 100644 --- a/src/Ombi/ClientApp/src/app/media-details/components/index.ts +++ b/src/Ombi/ClientApp/src/app/media-details/components/index.ts @@ -9,7 +9,6 @@ import { MediaPosterComponent } from "./shared/media-poster/media-poster.compone import { CastCarouselComponent } from "./shared/cast-carousel/cast-carousel.component"; import { DenyDialogComponent } from "./shared/deny-dialog/deny-dialog.component"; import { TvRequestsPanelComponent } from "./tv/panels/tv-requests/tv-requests-panel.component"; -import { MovieAdminPanelComponent } from "./movie/panels/movie-admin-panel/movie-admin-panel.component"; import { MovieAdvancedOptionsComponent } from "./movie/panels/movie-advanced-options/movie-advanced-options.component"; import { SearchService, RequestService, RadarrService, IssuesService, SonarrService } from "../../services"; import { RequestServiceV2 } from "../../services/requestV2.service"; @@ -18,7 +17,8 @@ import { ArtistDetailsComponent } from "./artist/artist-details.component"; import { ArtistInformationPanel } from "./artist/panels/artist-information-panel/artist-information-panel.component"; import { ArtistReleasePanel } from "./artist/panels/artist-release-panel/artist-release-panel.component"; import { IssuesPanelComponent } from "./shared/issues-panel/issues-panel.component"; -import { TvAdminPanelComponent } from "./tv/panels/tv-admin-panel/tv-admin-panel.component"; +import { TvAdvancedOptionsComponent } from "./tv/panels/tv-advanced-options/tv-advanced-options.component"; +import { RequestBehalfComponent } from "./shared/request-behalf/request-behalf.component"; export const components: any[] = [ MovieDetailsComponent, @@ -32,21 +32,23 @@ export const components: any[] = [ CastCarouselComponent, DenyDialogComponent, TvRequestsPanelComponent, - MovieAdminPanelComponent, MovieAdvancedOptionsComponent, + TvAdvancedOptionsComponent, NewIssueComponent, ArtistDetailsComponent, ArtistInformationPanel, ArtistReleasePanel, + RequestBehalfComponent, IssuesPanelComponent, - TvAdminPanelComponent, ]; export const entryComponents: any[] = [ YoutubeTrailerComponent, DenyDialogComponent, MovieAdvancedOptionsComponent, + TvAdvancedOptionsComponent, NewIssueComponent, + RequestBehalfComponent, ]; export const providers: any[] = [ diff --git a/src/Ombi/ClientApp/src/app/media-details/components/movie/movie-details.component.html b/src/Ombi/ClientApp/src/app/media-details/components/movie/movie-details.component.html index 375ca40ec..933c6ef2c 100644 --- a/src/Ombi/ClientApp/src/app/media-details/components/movie/movie-details.component.html +++ b/src/Ombi/ClientApp/src/app/media-details/components/movie/movie-details.component.html @@ -21,7 +21,7 @@ -
+
@@ -64,23 +64,28 @@ +
- - - + +
+ + + + +
-
- - - - - - - diff --git a/src/Ombi/ClientApp/src/app/media-details/components/movie/movie-details.component.ts b/src/Ombi/ClientApp/src/app/media-details/components/movie/movie-details.component.ts index 55a69fe15..56b9d3ffe 100644 --- a/src/Ombi/ClientApp/src/app/media-details/components/movie/movie-details.component.ts +++ b/src/Ombi/ClientApp/src/app/media-details/components/movie/movie-details.component.ts @@ -1,5 +1,5 @@ import { Component, ViewEncapsulation } from "@angular/core"; -import { ImageService, SearchV2Service, RequestService, MessageService } from "../../../services"; +import { ImageService, SearchV2Service, RequestService, MessageService, RadarrService } from "../../../services"; import { ActivatedRoute } from "@angular/router"; import { DomSanitizer } from "@angular/platform-browser"; import { ISearchMovieResultV2 } from "../../../interfaces/ISearchMovieResultV2"; @@ -10,6 +10,9 @@ import { IMovieRequests, RequestType, IAdvancedData } from "../../../interfaces" import { DenyDialogComponent } from "../shared/deny-dialog/deny-dialog.component"; import { NewIssueComponent } from "../shared/new-issue/new-issue.component"; import { StorageService } from "../../../shared/storage/storage-service"; +import { MovieAdvancedOptionsComponent } from "./panels/movie-advanced-options/movie-advanced-options.component"; +import { RequestServiceV2 } from "../../../services/requestV2.service"; +import { RequestBehalfComponent } from "../shared/request-behalf/request-behalf.component"; @Component({ templateUrl: "./movie-details.component.html", @@ -30,6 +33,7 @@ export class MovieDetailsComponent { constructor(private searchService: SearchV2Service, private route: ActivatedRoute, private sanitizer: DomSanitizer, private imageService: ImageService, public dialog: MatDialog, private requestService: RequestService, + private requestService2: RequestServiceV2, private radarrService: RadarrService, public messageService: MessageService, private auth: AuthService, private storage: StorageService) { this.route.params.subscribe((params: any) => { @@ -47,6 +51,10 @@ export class MovieDetailsComponent { this.isAdmin = this.auth.hasRole("admin") || this.auth.hasRole("poweruser"); + if (this.isAdmin) { + this.showAdvanced = await this.radarrService.isRadarrEnabled(); + } + if (this.imdbId) { this.searchService.getMovieByImdbId(this.imdbId).subscribe(async x => { this.movie = x; @@ -76,8 +84,8 @@ export class MovieDetailsComponent { } } - public async request() { - const result = await this.requestService.requestMovie({ theMovieDbId: this.theMovidDbId, languageCode: null }).toPromise(); + public async request(userId?: string) { + const result = await this.requestService.requestMovie({ theMovieDbId: this.theMovidDbId, languageCode: null, requestOnBehalf: userId }).toPromise(); if (result.result) { this.movie.requested = true; this.messageService.send(result.message, "Ok"); @@ -144,4 +152,25 @@ export class MovieDetailsComponent { this.movieRequest.rootPathOverrideTitle = data.profiles.filter(x => x.id == data.profileId)[0].name; } } + + public async openAdvancedOptions() { + const dialog = this.dialog.open(MovieAdvancedOptionsComponent, { width: "700px", data: { movieRequest: this.movieRequest }, panelClass: 'modal-panel' }) + await dialog.afterClosed().subscribe(async result => { + if (result) { + result.rootFolder = result.rootFolders.filter(f => f.id === +result.rootFolderId)[0]; + result.profile = result.profiles.filter(f => f.id === +result.profileId)[0]; + await this.requestService2.updateMovieAdvancedOptions({ qualityOverride: result.profileId, rootPathOverride: result.rootFolderId, requestId: this.movieRequest.id }).toPromise(); + this.setAdvancedOptions(result); + } + }); + } + + public async openRequestOnBehalf() { + const dialog = this.dialog.open(RequestBehalfComponent, { width: "700px", panelClass: 'modal-panel' }) + await dialog.afterClosed().subscribe(async result => { + if (result) { + await this.request(result.id); + } + }); + } } diff --git a/src/Ombi/ClientApp/src/app/media-details/components/movie/panels/movie-admin-panel/movie-admin-panel.component.html b/src/Ombi/ClientApp/src/app/media-details/components/movie/panels/movie-admin-panel/movie-admin-panel.component.html deleted file mode 100644 index 94bb66a12..000000000 --- a/src/Ombi/ClientApp/src/app/media-details/components/movie/panels/movie-admin-panel/movie-admin-panel.component.html +++ /dev/null @@ -1,3 +0,0 @@ -
- -
\ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/media-details/components/movie/panels/movie-admin-panel/movie-admin-panel.component.ts b/src/Ombi/ClientApp/src/app/media-details/components/movie/panels/movie-admin-panel/movie-admin-panel.component.ts deleted file mode 100644 index e19840a6d..000000000 --- a/src/Ombi/ClientApp/src/app/media-details/components/movie/panels/movie-admin-panel/movie-admin-panel.component.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { Component, Input, OnInit, EventEmitter, Output } from "@angular/core"; -import { RadarrService } from "../../../../../services"; -import { IRadarrProfile, IRadarrRootFolder, IMovieRequests, IAdvancedData } from "../../../../../interfaces"; -import { MatDialog } from "@angular/material/dialog"; -import { MovieAdvancedOptionsComponent } from "../movie-advanced-options/movie-advanced-options.component"; -import { RequestServiceV2 } from "../../../../../services/requestV2.service"; - -@Component({ - templateUrl: "./movie-admin-panel.component.html", - selector: "movie-admin-panel", -}) -export class MovieAdminPanelComponent implements OnInit { - - @Input() public movie: IMovieRequests; - @Output() public advancedOptionsChanged = new EventEmitter(); - @Output() public radarrEnabledChange = new EventEmitter(); - - public radarrEnabled: boolean; - public radarrProfiles: IRadarrProfile[]; - public selectedRadarrProfile: IRadarrProfile; - public radarrRootFolders: IRadarrRootFolder[]; - public selectRadarrRootFolders: IRadarrRootFolder; - - constructor(private radarrService: RadarrService, private requestService: RequestServiceV2, private dialog: MatDialog) { } - - public async ngOnInit() { - this.radarrEnabled = await this.radarrService.isRadarrEnabled(); - if (this.radarrEnabled) { - this.radarrService.getQualityProfilesFromSettings().subscribe(c => { - this.radarrProfiles = c; - this.setQualityOverrides(); - }); - this.radarrService.getRootFoldersFromSettings().subscribe(c => { - this.radarrRootFolders = c; - this.setRootFolderOverrides(); - }); - } - - this.radarrEnabledChange.emit(this.radarrEnabled); - } - - public async openAdvancedOptions() { - const dialog = this.dialog.open(MovieAdvancedOptionsComponent, { width: "700px", data: { profiles: this.radarrProfiles, rootFolders: this.radarrRootFolders }, panelClass: 'modal-panel' }) - await dialog.afterClosed().subscribe(async result => { - if(result) { - // get the name and ids - result.rootFolder = result.rootFolders.filter(f => f.id === +result.rootFolderId)[0]; - result.profile = result.profiles.filter(f => f.id === +result.profileId)[0]; - await this.requestService.updateMovieAdvancedOptions({qualityOverride: result.profileId, rootPathOverride: result.rootFolderId, requestId: this.movie.id}).toPromise(); - this.advancedOptionsChanged.emit(result); - } - }); - } - - private setQualityOverrides(): void { - if (this.radarrProfiles) { - const profile = this.radarrProfiles.filter((p) => { - return p.id === this.movie.qualityOverride; - }); - if (profile.length > 0) { - this.movie.qualityOverrideTitle = profile[0].name; - } - } - } - - private setRootFolderOverrides(): void { - if (this.radarrRootFolders) { - const path = this.radarrRootFolders.filter((folder) => { - return folder.id === this.movie.rootPathOverride; - }); - if (path.length > 0) { - this.movie.rootPathOverrideTitle = path[0].path; - } - } - } -} diff --git a/src/Ombi/ClientApp/src/app/media-details/components/movie/panels/movie-advanced-options/movie-advanced-options.component.html b/src/Ombi/ClientApp/src/app/media-details/components/movie/panels/movie-advanced-options/movie-advanced-options.component.html index 8caecdfdf..e839b49b1 100644 --- a/src/Ombi/ClientApp/src/app/media-details/components/movie/panels/movie-advanced-options/movie-advanced-options.component.html +++ b/src/Ombi/ClientApp/src/app/media-details/components/movie/panels/movie-advanced-options/movie-advanced-options.component.html @@ -5,7 +5,7 @@ {{'MediaDetails.QualityProfilesSelect' | translate }} - {{profile.name}} + {{profile.name}}
@@ -13,7 +13,7 @@ {{'MediaDetails.RootFolderSelect' | translate }} - {{profile.path}} + {{profile.path}}
diff --git a/src/Ombi/ClientApp/src/app/media-details/components/movie/panels/movie-advanced-options/movie-advanced-options.component.ts b/src/Ombi/ClientApp/src/app/media-details/components/movie/panels/movie-advanced-options/movie-advanced-options.component.ts index d5c3310fb..d164644ff 100644 --- a/src/Ombi/ClientApp/src/app/media-details/components/movie/panels/movie-advanced-options/movie-advanced-options.component.ts +++ b/src/Ombi/ClientApp/src/app/media-details/components/movie/panels/movie-advanced-options/movie-advanced-options.component.ts @@ -1,14 +1,55 @@ -import { Component, Inject } from "@angular/core"; +import { Component, Inject, OnInit } from "@angular/core"; import { MatDialogRef, MAT_DIALOG_DATA } from "@angular/material/dialog"; -import { IAdvancedData } from "../../../../../interfaces"; +import { IAdvancedData, IRadarrProfile, IRadarrRootFolder } from "../../../../../interfaces"; +import { RadarrService } from "../../../../../services"; @Component({ templateUrl: "./movie-advanced-options.component.html", selector: "movie-advanced-options", }) -export class MovieAdvancedOptionsComponent { - +export class MovieAdvancedOptionsComponent implements OnInit { + + public radarrProfiles: IRadarrProfile[]; + public radarrRootFolders: IRadarrRootFolder[]; + constructor(public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: IAdvancedData, - ) { - } + private radarrService: RadarrService + ) { + } + + + public async ngOnInit() { + this.radarrService.getQualityProfilesFromSettings().subscribe(c => { + this.radarrProfiles = c; + this.data.profiles = c; + this.setQualityOverrides(); + }); + this.radarrService.getRootFoldersFromSettings().subscribe(c => { + this.radarrRootFolders = c; + this.data.rootFolders = c; + this.setRootFolderOverrides(); + }); + } + + private setQualityOverrides(): void { + if (this.radarrProfiles) { + const profile = this.radarrProfiles.filter((p) => { + return p.id === this.data.movieRequest.qualityOverride; + }); + if (profile.length > 0) { + this.data.movieRequest.qualityOverrideTitle = profile[0].name; + } + } + } + + private setRootFolderOverrides(): void { + if (this.radarrRootFolders) { + const path = this.radarrRootFolders.filter((folder) => { + return folder.id === this.data.movieRequest.rootPathOverride; + }); + if (path.length > 0) { + this.data.movieRequest.rootPathOverrideTitle = path[0].path; + } + } + } } diff --git a/src/Ombi/ClientApp/src/app/media-details/components/movie/panels/movie-information-panel.component.html b/src/Ombi/ClientApp/src/app/media-details/components/movie/panels/movie-information-panel.component.html index 449dd09c4..3f838d9ee 100644 --- a/src/Ombi/ClientApp/src/app/media-details/components/movie/panels/movie-information-panel.component.html +++ b/src/Ombi/ClientApp/src/app/media-details/components/movie/panels/movie-information-panel.component.html @@ -9,7 +9,7 @@
{{'Common.NotAvailable' | translate}}
- +
{{'MediaDetails.RequestStatus' | translate }}
{{'Common.ProcessingRequest' | translate}}
@@ -18,16 +18,28 @@
{{'Common.NotRequested' | translate}}
+ +
+ {{'Requests.RequestedBy' | translate }} +
{{request.requestedUser.userAlias}}
+
+ +
+ {{'Requests.RequestDate' | translate }} +
{{request.requestedDate | date}}
+
+ +
{{'MediaDetails.Quality' | translate }}:
{{movie.quality | quality}}
-
+
{{'MediaDetails.RootFolderOverride' | translate }}
{{request.rootPathOverrideTitle}}
-
+
{{'MediaDetails.QualityOverride' | translate }}
{{request.qualityOverrideTitle}}
@@ -81,7 +93,7 @@
-
+
{{'MediaDetails.Keywords' | translate }}: diff --git a/src/Ombi/ClientApp/src/app/media-details/components/shared/request-behalf/request-behalf.component.html b/src/Ombi/ClientApp/src/app/media-details/components/shared/request-behalf/request-behalf.component.html new file mode 100644 index 000000000..db7acde9d --- /dev/null +++ b/src/Ombi/ClientApp/src/app/media-details/components/shared/request-behalf/request-behalf.component.html @@ -0,0 +1,22 @@ +

{{ 'MediaDetails.RequestOnBehalf' | translate}}

+
+
+ + + + + {{option.username}} + + + +
+
+
+ + +
\ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/media-details/components/shared/request-behalf/request-behalf.component.ts b/src/Ombi/ClientApp/src/app/media-details/components/shared/request-behalf/request-behalf.component.ts new file mode 100644 index 000000000..da684db06 --- /dev/null +++ b/src/Ombi/ClientApp/src/app/media-details/components/shared/request-behalf/request-behalf.component.ts @@ -0,0 +1,52 @@ +import { Component, Inject, OnInit } from "@angular/core"; +import { IDenyDialogData } from "../interfaces/interfaces"; +import { MatDialogRef, MAT_DIALOG_DATA } from "@angular/material/dialog"; +import { RequestService, MessageService, IdentityService } from "../../../../services"; +import { RequestType, IRequestEngineResult, IUserDropdown } from "../../../../interfaces"; +import { FormControl } from "@angular/forms"; +import { Observable } from "rxjs"; +import { filter, map, startWith } from "rxjs/operators"; + + + +@Component({ + selector: "request-behalf", + templateUrl: "./request-behalf.component.html", +}) +export class RequestBehalfComponent implements OnInit { + constructor( + public dialogRef: MatDialogRef, + public identity: IdentityService) { } + + public myControl = new FormControl(); + public options: IUserDropdown[]; + public filteredOptions: Observable; + public userId: string; + + public async ngOnInit() { + this.options = await this.identity.getUsersDropdown().toPromise(); + this.filteredOptions = this.myControl.valueChanges + .pipe( + startWith(''), + map(value => this._filter(value)) + ); + } + + public request() { + this.dialogRef.close(this.myControl.value); + } + + public onNoClick(): void { + this.dialogRef.close(); + } + + public displayFn(user: IUserDropdown): string { + return user?.username ? user.username : ''; + } + + private _filter(value: string|IUserDropdown): IUserDropdown[] { + const filterValue = typeof value === 'string' ? value.toLowerCase() : value.username.toLowerCase(); + + return this.options.filter(option => option.username.toLowerCase().includes(filterValue)); + } +} diff --git a/src/Ombi/ClientApp/src/app/media-details/components/tv/panels/tv-admin-panel/tv-admin-panel.component.html b/src/Ombi/ClientApp/src/app/media-details/components/tv/panels/tv-admin-panel/tv-admin-panel.component.html deleted file mode 100644 index 473f0e97b..000000000 --- a/src/Ombi/ClientApp/src/app/media-details/components/tv/panels/tv-admin-panel/tv-admin-panel.component.html +++ /dev/null @@ -1,3 +0,0 @@ -
- -
\ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/media-details/components/tv/panels/tv-admin-panel/tv-admin-panel.component.ts b/src/Ombi/ClientApp/src/app/media-details/components/tv/panels/tv-admin-panel/tv-admin-panel.component.ts deleted file mode 100644 index 40ca64e1c..000000000 --- a/src/Ombi/ClientApp/src/app/media-details/components/tv/panels/tv-admin-panel/tv-admin-panel.component.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { Component, Input, OnInit, EventEmitter, Output } from "@angular/core"; -import { RadarrService, SonarrService } from "../../../../../services"; -import { IRadarrProfile, IRadarrRootFolder, IAdvancedData, ITvRequests, ISonarrProfile, ISonarrRootFolder } from "../../../../../interfaces"; -import { MatDialog } from "@angular/material/dialog"; - -import { RequestServiceV2 } from "../../../../../services/requestV2.service"; -import { MovieAdvancedOptionsComponent } from "../../../movie/panels/movie-advanced-options/movie-advanced-options.component"; - -@Component({ - templateUrl: "./tv-admin-panel.component.html", - selector: "tv-admin-panel", -}) -export class TvAdminPanelComponent implements OnInit { - - @Input() public tv: ITvRequests; - @Output() public advancedOptionsChanged = new EventEmitter(); - @Output() public sonarrEnabledChange = new EventEmitter(); - - public sonarrEnabled: boolean; - public radarrProfiles: IRadarrProfile[]; - public selectedRadarrProfile: IRadarrProfile; - public radarrRootFolders: IRadarrRootFolder[]; - public selectRadarrRootFolders: IRadarrRootFolder; - - - public sonarrProfiles: ISonarrProfile[]; - public sonarrRootFolders: ISonarrRootFolder[]; - - constructor(private sonarrService: SonarrService, private requestService: RequestServiceV2, private dialog: MatDialog) { } - - public async ngOnInit() { - this.sonarrEnabled = await this.sonarrService.isEnabled(); - if (this.sonarrEnabled) { - this.sonarrService.getQualityProfilesWithoutSettings() - .subscribe(x => { - this.sonarrProfiles = x; - this.setQualityOverrides(); - }); - this.sonarrService.getRootFoldersWithoutSettings() - .subscribe(x => { - this.sonarrRootFolders = x; - this.setRootFolderOverrides(); - }); - } - - this.sonarrEnabledChange.emit(this.sonarrEnabled); - } - - public async openAdvancedOptions() { - const dialog = this.dialog.open(MovieAdvancedOptionsComponent, { width: "700px", data: { profiles: this.sonarrProfiles, rootFolders: this.sonarrRootFolders }, panelClass: 'modal-panel' }) - await dialog.afterClosed().subscribe(async result => { - if (result) { - // get the name and ids - result.rootFolder = result.rootFolders.filter(f => f.id === +result.rootFolderId)[0]; - result.profile = result.profiles.filter(f => f.id === +result.profileId)[0]; - await this.requestService.updateTvAdvancedOptions({ qualityOverride: result.profileId, rootPathOverride: result.rootFolderId, requestId: this.tv.id }).toPromise(); - this.advancedOptionsChanged.emit(result); - } - }); - } - - private setQualityOverrides(): void { - if (this.sonarrProfiles) { - const profile = this.sonarrProfiles.filter((p) => { - return p.id === this.tv.qualityOverride; - }); - if (profile.length > 0) { - this.tv.qualityOverrideTitle = profile[0].name; - } - } - } - - private setRootFolderOverrides(): void { - if (this.sonarrRootFolders) { - const path = this.sonarrRootFolders.filter((folder) => { - return folder.id === this.tv.rootFolder; - }); - if (path.length > 0) { - this.tv.rootPathOverrideTitle = path[0].path; - } - } - } -} diff --git a/src/Ombi/ClientApp/src/app/media-details/components/tv/panels/tv-advanced-options/tv-advanced-options.component.html b/src/Ombi/ClientApp/src/app/media-details/components/tv/panels/tv-advanced-options/tv-advanced-options.component.html new file mode 100644 index 000000000..5724a35bf --- /dev/null +++ b/src/Ombi/ClientApp/src/app/media-details/components/tv/panels/tv-advanced-options/tv-advanced-options.component.html @@ -0,0 +1,23 @@ +

+ + Advanced Options

+
+ + {{'MediaDetails.QualityProfilesSelect' | translate }} + + {{profile.name}} + + +
+
+ + {{'MediaDetails.RootFolderSelect' | translate }} + + {{profile.path}} + + +
+
+ + +
\ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/media-details/components/tv/panels/tv-advanced-options/tv-advanced-options.component.ts b/src/Ombi/ClientApp/src/app/media-details/components/tv/panels/tv-advanced-options/tv-advanced-options.component.ts new file mode 100644 index 000000000..7846b69df --- /dev/null +++ b/src/Ombi/ClientApp/src/app/media-details/components/tv/panels/tv-advanced-options/tv-advanced-options.component.ts @@ -0,0 +1,55 @@ +import { Component, Inject, OnInit } from "@angular/core"; +import { MatDialogRef, MAT_DIALOG_DATA } from "@angular/material/dialog"; +import { IAdvancedData, ISonarrProfile, ISonarrRootFolder } from "../../../../../interfaces"; +import { SonarrService } from "../../../../../services"; + +@Component({ + templateUrl: "./tv-advanced-options.component.html", + selector: "tv-advanced-options", +}) +export class TvAdvancedOptionsComponent implements OnInit { + + public sonarrProfiles: ISonarrProfile[]; + public sonarrRootFolders: ISonarrRootFolder[]; + + constructor(public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: IAdvancedData, + private sonarrService: SonarrService + ) { + } + + + public async ngOnInit() { + this.sonarrService.getQualityProfilesWithoutSettings().subscribe(c => { + this.sonarrProfiles = c; + this.data.profiles = c; + this.setQualityOverrides(); + }); + this.sonarrService.getRootFoldersWithoutSettings().subscribe(c => { + this.sonarrRootFolders = c; + this.data.rootFolders = c; + this.setRootFolderOverrides(); + }); + } + + private setQualityOverrides(): void { + if (this.sonarrProfiles) { + const profile = this.sonarrProfiles.filter((p) => { + return p.id === this.data.tvRequest.qualityOverride; + }); + if (profile.length > 0) { + this.data.movieRequest.qualityOverrideTitle = profile[0].name; + } + } + } + + private setRootFolderOverrides(): void { + if (this.sonarrRootFolders) { + const path = this.sonarrRootFolders.filter((folder) => { + return folder.id === this.data.tvRequest.rootFolder; + }); + if (path.length > 0) { + this.data.movieRequest.rootPathOverrideTitle = path[0].path; + } + } + } +} diff --git a/src/Ombi/ClientApp/src/app/media-details/components/tv/panels/tv-information-panel/tv-information-panel.component.html b/src/Ombi/ClientApp/src/app/media-details/components/tv/panels/tv-information-panel/tv-information-panel.component.html index fb3c1c65e..d9d2b3a83 100644 --- a/src/Ombi/ClientApp/src/app/media-details/components/tv/panels/tv-information-panel/tv-information-panel.component.html +++ b/src/Ombi/ClientApp/src/app/media-details/components/tv/panels/tv-information-panel/tv-information-panel.component.html @@ -11,11 +11,11 @@
-
+
{{'MediaDetails.RootFolderOverride' | translate }}
{{request.rootPathOverrideTitle}}
-
+
{{'MediaDetails.QualityOverride' | translate }}
{{request.qualityOverrideTitle}}
diff --git a/src/Ombi/ClientApp/src/app/media-details/components/tv/tv-details.component.html b/src/Ombi/ClientApp/src/app/media-details/components/tv/tv-details.component.html index 0df2ba1fb..211c4a374 100644 --- a/src/Ombi/ClientApp/src/app/media-details/components/tv/tv-details.component.html +++ b/src/Ombi/ClientApp/src/app/media-details/components/tv/tv-details.component.html @@ -2,127 +2,145 @@
-
-
-

{{ 'MediaDetails.NotEnoughInfo' | translate }}

+
+
+

+

{{ 'MediaDetails.NotEnoughInfo' | translate }}

+
-
- - - -
- + -
-
-
+
- + - -
+
+
+
- - + -
+ +
-
+ + - +
- - +
- + -
-
- -
-
- - - - - - - - - - - - + + -
+ +
+ +
+ + + + + +
+
-
-
- - - {{tv.overview}} +
+ + + -
-
- +
+ +
+
+
+ + + {{tv.overview}} + + +
+
+ +
+ +
+
-
-
-
-
+
+
- + -
+
-
-
- -
- - - - - Requests - - - - - - +
+
+ +
+ + + + + Requests + + + + -
+ +
-
+
-
+
-
-
-
-
-
+
+
+ + +
+
\ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/media-details/components/tv/tv-details.component.ts b/src/Ombi/ClientApp/src/app/media-details/components/tv/tv-details.component.ts index ef86fbdce..8ed41e078 100644 --- a/src/Ombi/ClientApp/src/app/media-details/components/tv/tv-details.component.ts +++ b/src/Ombi/ClientApp/src/app/media-details/components/tv/tv-details.component.ts @@ -5,10 +5,13 @@ import { DomSanitizer } from "@angular/platform-browser"; import { ISearchTvResultV2 } from "../../../interfaces/ISearchTvResultV2"; import { MatDialog } from "@angular/material/dialog"; import { YoutubeTrailerComponent } from "../shared/youtube-trailer.component"; -import { EpisodeRequestComponent } from "../../../shared/episode-request/episode-request.component"; -import { IAdvancedData, IChildRequests, ISonarrProfile, ISonarrRootFolder, ITvRequests, RequestType } from "../../../interfaces"; +import { EpisodeRequestComponent, EpisodeRequestData } from "../../../shared/episode-request/episode-request.component"; +import { IAdvancedData, IChildRequests, ITvRequests, RequestType } from "../../../interfaces"; import { AuthService } from "../../../auth/auth.service"; import { NewIssueComponent } from "../shared/new-issue/new-issue.component"; +import { TvAdvancedOptionsComponent } from "./panels/tv-advanced-options/tv-advanced-options.component"; +import { RequestServiceV2 } from "../../../services/requestV2.service"; +import { RequestBehalfComponent } from "../shared/request-behalf/request-behalf.component"; @Component({ templateUrl: "./tv-details.component.html", @@ -30,6 +33,7 @@ export class TvDetailsComponent implements OnInit { constructor(private searchService: SearchV2Service, private route: ActivatedRoute, private sanitizer: DomSanitizer, private imageService: ImageService, public dialog: MatDialog, public messageService: MessageService, private requestService: RequestService, + private requestService2: RequestServiceV2, private auth: AuthService, private sonarrService: SonarrService) { this.route.params.subscribe((params: any) => { this.tvdbId = params.tvdbId; @@ -44,6 +48,11 @@ export class TvDetailsComponent implements OnInit { public async load() { this.isAdmin = this.auth.hasRole("admin") || this.auth.hasRole("poweruser"); + + if (this.isAdmin) { + this.showAdvanced = await this.sonarrService.isEnabled(); + } + if (this.fromSearch) { this.tv = await this.searchService.getTvInfoWithMovieDbId(this.tvdbId); this.tvdbId = this.tv.id; @@ -60,8 +69,8 @@ export class TvDetailsComponent implements OnInit { this.tv.background = this.sanitizer.bypassSecurityTrustStyle("url(" + tvBanner + ")"); } - public async request() { - this.dialog.open(EpisodeRequestComponent, { width: "800px", data: this.tv, panelClass: 'modal-panel' }) + public async request(userId: string) { + this.dialog.open(EpisodeRequestComponent, { width: "800px", data: { series: this.tv, requestOnBehalf: userId }, panelClass: 'modal-panel' }) } public async issue() { @@ -81,6 +90,28 @@ export class TvDetailsComponent implements OnInit { }); } + public async openAdvancedOptions() { + const dialog = this.dialog.open(TvAdvancedOptionsComponent, { width: "700px", data: { tvRequest: this.showRequest }, panelClass: 'modal-panel' }) + await dialog.afterClosed().subscribe(async result => { + if (result) { + // get the name and ids + result.rootFolder = result.rootFolders.filter(f => f.id === +result.rootFolderId)[0]; + result.profile = result.profiles.filter(f => f.id === +result.profileId)[0]; + await this.requestService2.updateTvAdvancedOptions({ qualityOverride: result.profileId, rootPathOverride: result.rootFolderId, requestId: this.tv.id }).toPromise(); + this.setAdvancedOptions(result); + } + }); + } + + public async openRequestOnBehalf() { + const dialog = this.dialog.open(RequestBehalfComponent, { width: "700px", panelClass: 'modal-panel' }) + await dialog.afterClosed().subscribe(async result => { + if (result) { + await this.request(result.id); + } + }); + } + public setAdvancedOptions(data: IAdvancedData) { this.advancedOptions = data; console.log(this.advancedOptions); diff --git a/src/Ombi/ClientApp/src/app/media-details/media-details.component.scss b/src/Ombi/ClientApp/src/app/media-details/media-details.component.scss index f3c655c6f..c9fc9f522 100644 --- a/src/Ombi/ClientApp/src/app/media-details/media-details.component.scss +++ b/src/Ombi/ClientApp/src/app/media-details/media-details.component.scss @@ -222,4 +222,8 @@ .no-info { text-align: center; padding-top: 15%; +} + +.content-end { + text-align: end; } \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/media-details/media-details.module.ts b/src/Ombi/ClientApp/src/app/media-details/media-details.module.ts index 2544a4935..67c0436ad 100644 --- a/src/Ombi/ClientApp/src/app/media-details/media-details.module.ts +++ b/src/Ombi/ClientApp/src/app/media-details/media-details.module.ts @@ -1,8 +1,6 @@ import { NgModule } from "@angular/core"; import { RouterModule, Routes } from "@angular/router"; -import { SearchService, RequestService, RadarrService } from "../services"; - import {CarouselModule} from 'primeng/carousel'; import { SharedModule } from "../shared/shared.module"; @@ -13,6 +11,7 @@ import { PipeModule } from "../pipes/pipe.module"; import * as fromComponents from './components'; import { AuthGuard } from "../auth/auth.guard"; import { ArtistDetailsComponent } from "./components/artist/artist-details.component"; +import { ReactiveFormsModule } from "@angular/forms"; const routes: Routes = [ @@ -25,6 +24,7 @@ const routes: Routes = [ imports: [ RouterModule.forChild(routes), SharedModule, + ReactiveFormsModule, PipeModule, CarouselModule, ], diff --git a/src/Ombi/ClientApp/src/app/services/identity.service.ts b/src/Ombi/ClientApp/src/app/services/identity.service.ts index 9d2b4f8c6..f25a21b73 100644 --- a/src/Ombi/ClientApp/src/app/services/identity.service.ts +++ b/src/Ombi/ClientApp/src/app/services/identity.service.ts @@ -4,7 +4,7 @@ import { Injectable, Inject } from "@angular/core"; import { HttpClient } from "@angular/common/http"; import { Observable } from "rxjs"; -import { ICheckbox, ICreateWizardUser, IIdentityResult, INotificationPreferences, IResetPasswordToken, IUpdateLocalUser, IUser, IWizardUserResult } from "../interfaces"; +import { ICheckbox, ICreateWizardUser, IIdentityResult, INotificationPreferences, IResetPasswordToken, IUpdateLocalUser, IUser, IUserDropdown, IWizardUserResult } from "../interfaces"; import { ServiceHelpers } from "./service.helpers"; @Injectable() @@ -32,6 +32,10 @@ export class IdentityService extends ServiceHelpers { return this.http.get(`${this.url}Users`, {headers: this.headers}); } + public getUsersDropdown(): Observable { + return this.http.get(`${this.url}dropdown/Users`, {headers: this.headers}); + } + public getAllAvailableClaims(): Observable { return this.http.get(`${this.url}Claims`, {headers: this.headers}); } diff --git a/src/Ombi/ClientApp/src/app/shared/episode-request/episode-request.component.html b/src/Ombi/ClientApp/src/app/shared/episode-request/episode-request.component.html index f57f014c9..099ff99e8 100644 --- a/src/Ombi/ClientApp/src/app/shared/episode-request/episode-request.component.html +++ b/src/Ombi/ClientApp/src/app/shared/episode-request/episode-request.component.html @@ -16,7 +16,7 @@
-
+
@@ -25,7 +25,7 @@ Season {{season.seasonNumber}} - Description + diff --git a/src/Ombi/ClientApp/src/app/shared/episode-request/episode-request.component.ts b/src/Ombi/ClientApp/src/app/shared/episode-request/episode-request.component.ts index 2f311b172..4e9015051 100644 --- a/src/Ombi/ClientApp/src/app/shared/episode-request/episode-request.component.ts +++ b/src/Ombi/ClientApp/src/app/shared/episode-request/episode-request.component.ts @@ -4,8 +4,12 @@ import { MatDialogRef, MAT_DIALOG_DATA } from "@angular/material/dialog"; import { ISearchTvResultV2 } from "../../interfaces/ISearchTvResultV2"; import { RequestService, MessageService } from "../../services"; import { ITvRequestViewModel, ISeasonsViewModel, IEpisodesRequests, INewSeasonRequests } from "../../interfaces"; +import { ThousandShortPipe } from "../../pipes/ThousandShortPipe"; - +export interface EpisodeRequestData { + series: ISearchTvResultV2; + requestOnBehalf: string | undefined; +} @Component({ selector: "episode-request", templateUrl: "episode-request.component.html", @@ -14,7 +18,7 @@ export class EpisodeRequestComponent implements OnInit { public loading: boolean; - constructor(public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public series: ISearchTvResultV2, + constructor(public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: EpisodeRequestData, private requestService: RequestService, private notificationService: MessageService) { } public ngOnInit() { @@ -25,24 +29,27 @@ export class EpisodeRequestComponent implements OnInit { public async submitRequests() { // Make sure something has been selected - const selected = this.series.seasonRequests.some((season) => { + const selected = this.data.series.seasonRequests.some((season) => { return season.episodes.some((ep) => { return ep.selected; }); }); debugger; - if (!selected && !this.series.requestAll && !this.series.firstSeason && !this.series.latestSeason) { + if (!selected && !this.data.series.requestAll && !this.data.series.firstSeason && !this.data.series.latestSeason) { this.notificationService.send("You need to select some episodes!", "OK"); return; } - this.series.requested = true; + this.data.series.requested = true; - const viewModel = { firstSeason: this.series.firstSeason, latestSeason: this.series.latestSeason, requestAll: this.series.requestAll, tvDbId: this.series.id }; + const viewModel = { + firstSeason: this.data.series.firstSeason, latestSeason: this.data.series.latestSeason, requestAll: this.data.series.requestAll, tvDbId: this.data.series.id, + requestOnBehalf: this.data.requestOnBehalf + }; viewModel.seasons = []; - this.series.seasonRequests.forEach((season) => { + this.data.series.seasonRequests.forEach((season) => { const seasonsViewModel = { seasonNumber: season.seasonNumber, episodes: [] }; - if (!this.series.latestSeason && !this.series.requestAll && !this.series.firstSeason) { + if (!this.data.series.latestSeason && !this.data.series.requestAll && !this.data.series.firstSeason) { season.episodes.forEach(ep => { if (ep.selected) { ep.requested = true; @@ -57,9 +64,9 @@ export class EpisodeRequestComponent implements OnInit { if (requestResult.result) { this.notificationService.send( - `Request for ${this.series.title} has been added successfully`); + `Request for ${this.data.series.title} has been added successfully`); - this.series.seasonRequests.forEach((season) => { + this.data.series.seasonRequests.forEach((season) => { season.episodes.forEach((ep) => { ep.selected = false; }); @@ -90,17 +97,17 @@ export class EpisodeRequestComponent implements OnInit { } public async requestAllSeasons() { - this.series.requestAll = true; + this.data.series.requestAll = true; await this.submitRequests(); } public async requestFirstSeason() { - this.series.firstSeason = true; + this.data.series.firstSeason = true; await this.submitRequests(); } public async requestLatestSeason() { - this.series.latestSeason = true; + this.data.series.latestSeason = true; await this.submitRequests(); } } diff --git a/src/Ombi/ClientApp/src/styles/shared.scss b/src/Ombi/ClientApp/src/styles/shared.scss index c743d06a9..27bfa6c7d 100644 --- a/src/Ombi/ClientApp/src/styles/shared.scss +++ b/src/Ombi/ClientApp/src/styles/shared.scss @@ -128,9 +128,9 @@ table { border: 1px solid rgba(0, 0, 0, 0.18); } -::ng-deep .mat-toolbar.mat-primary { - margin-bottom: 0.5em; -} +// ::ng-deep .mat-toolbar.mat-primary { +// margin-bottom: 0.5em; +// } ::ng-deep .dark .mat-form-field.mat-focused .mat-form-field-label { color: $accent-dark; diff --git a/src/Ombi/Controllers/V1/IdentityController.cs b/src/Ombi/Controllers/V1/IdentityController.cs index b348c1d06..2300c0482 100644 --- a/src/Ombi/Controllers/V1/IdentityController.cs +++ b/src/Ombi/Controllers/V1/IdentityController.cs @@ -275,6 +275,31 @@ namespace Ombi.Controllers.V1 return model.OrderBy(x => x.UserName); } + /// + /// Gets all users for dropdown purposes. + /// + /// Basic Information about all users + [HttpGet("dropdown/Users")] + [PowerUser] + public async Task> GetAllUsersDropdown() + { + var users = await UserManager.Users.Where(x => x.UserType != UserType.SystemUser) + .ToListAsync(); + + var model = new List(); + + foreach (var user in users) + { + model.Add(new UserViewModelDropdown + { + Id = user.Id, + Username = user.UserName + }); + } + + return model.OrderBy(x => x.Username); + } + /// /// Gets the current logged in user. /// diff --git a/src/Ombi/wwwroot/translations/en.json b/src/Ombi/wwwroot/translations/en.json index 665811489..0c133c2ee 100644 --- a/src/Ombi/wwwroot/translations/en.json +++ b/src/Ombi/wwwroot/translations/en.json @@ -271,7 +271,11 @@ "AllSeasonsTooltip": "This will request every season for this show", "FirstSeasonTooltip": "This will only request the First Season for this show", "LatestSeasonTooltip": "This will only request the Latest Season for this show" - } + }, + "SonarrConfiguration": "Sonarr Configuration", + "RadarrConfiguration": "Radarr Configuration", + "RequestOnBehalf": "Request on behalf of", + "PleaseSelectUser": "Please select a user" }, "Discovery": { "PopularTab": "Popular",