Merge branch 'develop' of https://github.com/tidusjar/ombi into develop

This commit is contained in:
TidusJar 2019-01-21 13:16:47 +00:00
commit 732873acda
23 changed files with 1531 additions and 342 deletions

View file

@ -83,7 +83,8 @@ namespace Ombi.Core.Engine
Approved = false,
RequestedUserId = userDetails.Id,
Background = movieInfo.BackdropPath,
LangCode = model.LanguageCode
LangCode = model.LanguageCode,
RequestedByAlias = model.RequestedByAlias
};
var usDates = movieInfo.ReleaseDates?.Results?.FirstOrDefault(x => x.IsoCode == "US");

View file

@ -83,7 +83,8 @@ namespace Ombi.Core.Engine
Title = album.title,
Disk = album.images?.FirstOrDefault(x => x.coverType.Equals("disc"))?.url,
Cover = album.images?.FirstOrDefault(x => x.coverType.Equals("cover"))?.url,
ForeignArtistId = album?.artist?.foreignArtistId ?? string.Empty
ForeignArtistId = album?.artist?.foreignArtistId ?? string.Empty,
RequestedByAlias = model.RequestedByAlias
};
if (requestModel.Cover.IsNullOrEmpty())
{

View file

@ -72,6 +72,7 @@ namespace Ombi.Core.Helpers
SeasonRequests = new List<SeasonRequests>(),
Title = ShowInfo.name,
ReleaseYear = FirstAir,
RequestedByAlias = model.RequestedByAlias,
SeriesType = ShowInfo.genres.Any( s => s.Equals("Anime", StringComparison.InvariantCultureIgnoreCase)) ? SeriesType.Anime : SeriesType.Standard
};

View file

@ -24,11 +24,20 @@
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using Newtonsoft.Json;
namespace Ombi.Core.Models.Requests
{
public class MovieRequestViewModel
{
public int TheMovieDbId { get; set; }
public string LanguageCode { get; set; } = "en";
/// <summary>
/// This is only set from a HTTP Header
/// </summary>
[JsonIgnore]
public string RequestedByAlias { get; set; }
}
}

View file

@ -3,5 +3,6 @@
public class MusicAlbumRequestViewModel
{
public string ForeignAlbumId { get; set; }
public string RequestedByAlias { get; set; }
}
}

View file

@ -1,4 +1,5 @@
using System.Collections.Generic;
using Newtonsoft.Json;
namespace Ombi.Core.Models.Requests
{
@ -9,6 +10,8 @@ namespace Ombi.Core.Models.Requests
public bool FirstSeason { get; set; }
public int TvDbId { get; set; }
public List<SeasonsViewModel> Seasons { get; set; } = new List<SeasonsViewModel>();
[JsonIgnore]
public string RequestedByAlias { get; set; }
}
public class SeasonsViewModel

View file

@ -34,7 +34,7 @@ namespace Ombi.Store.Entities
public bool IsEmbyConnect => UserType == UserType.EmbyUser && EmbyConnectUserId.HasValue();
[NotMapped]
public string UserAlias => string.IsNullOrEmpty(Alias) ? UserName : Alias;
public virtual string UserAlias => string.IsNullOrEmpty(Alias) ? UserName : Alias;
[NotMapped]
public bool EmailLogin { get; set; }

View file

@ -17,6 +17,7 @@ namespace Ombi.Store.Entities.Requests
public DateTime MarkedAsDenied { get; set; }
public string DeniedReason { get; set; }
public RequestType RequestType { get; set; }
public string RequestedByAlias { get; set; }
[ForeignKey(nameof(RequestedUserId))]
public OmbiUser RequestedUser { get; set; }

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,40 @@
using Microsoft.EntityFrameworkCore.Migrations;
namespace Ombi.Store.Migrations
{
public partial class RequestedByAlias : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "RequestedByAlias",
table: "MovieRequests",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "RequestedByAlias",
table: "ChildRequests",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "RequestedByAlias",
table: "AlbumRequests",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "RequestedByAlias",
table: "MovieRequests");
migrationBuilder.DropColumn(
name: "RequestedByAlias",
table: "ChildRequests");
migrationBuilder.DropColumn(
name: "RequestedByAlias",
table: "AlbumRequests");
}
}
}

View file

@ -14,7 +14,7 @@ namespace Ombi.Store.Migrations
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.2.0-rtm-35687");
.HasAnnotation("ProductVersion", "2.2.1-servicing-10028");
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
@ -583,6 +583,8 @@ namespace Ombi.Store.Migrations
b.Property<int>("RequestType");
b.Property<string>("RequestedByAlias");
b.Property<DateTime>("RequestedDate");
b.Property<string>("RequestedUserId");
@ -621,6 +623,8 @@ namespace Ombi.Store.Migrations
b.Property<int>("RequestType");
b.Property<string>("RequestedByAlias");
b.Property<DateTime>("RequestedDate");
b.Property<string>("RequestedUserId");
@ -749,6 +753,8 @@ namespace Ombi.Store.Migrations
b.Property<int>("RequestType");
b.Property<string>("RequestedByAlias");
b.Property<DateTime>("RequestedDate");
b.Property<string>("RequestedUserId");

View file

@ -1,71 +1,71 @@
using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features.Authentication;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Moq;
using Ombi.Api.Emby;
using Ombi.Api.Plex;
using Ombi.Core.Authentication;
using Ombi.Core.Settings;
using Ombi.Core.Settings.Models.External;
using Ombi.Models.Identity;
using Ombi.Store.Context;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
//using System;
//using Microsoft.AspNetCore.Builder;
//using Microsoft.AspNetCore.Hosting;
//using Microsoft.AspNetCore.Http;
//using Microsoft.AspNetCore.Http.Features.Authentication;
//using Microsoft.AspNetCore.Identity;
//using Microsoft.Extensions.DependencyInjection;
//using Microsoft.Extensions.Options;
//using Moq;
//using Ombi.Api.Emby;
//using Ombi.Api.Plex;
//using Ombi.Core.Authentication;
//using Ombi.Core.Settings;
//using Ombi.Core.Settings.Models.External;
//using Ombi.Models.Identity;
//using Ombi.Store.Context;
//using Ombi.Store.Entities;
//using Ombi.Store.Repository;
namespace Ombi.Tests
{
public class TestStartup
{
public IServiceProvider ConfigureServices(IServiceCollection services)
{
var _plexApi = new Mock<IPlexApi>();
var _embyApi = new Mock<IEmbyApi>();
var _tokenSettings = new Mock<IOptions<TokenAuthentication>>();
var _embySettings = new Mock<ISettingsService<EmbySettings>>();
var _plexSettings = new Mock<ISettingsService<PlexSettings>>();
var audit = new Mock<IAuditRepository>();
var tokenRepo = new Mock<ITokenRepository>();
//namespace Ombi.Tests
//{
// public class TestStartup
// {
// public IServiceProvider ConfigureServices(IServiceCollection services)
// {
// var _plexApi = new Mock<IPlexApi>();
// var _embyApi = new Mock<IEmbyApi>();
// var _tokenSettings = new Mock<IOptions<TokenAuthentication>>();
// var _embySettings = new Mock<ISettingsService<EmbySettings>>();
// var _plexSettings = new Mock<ISettingsService<PlexSettings>>();
// var audit = new Mock<IAuditRepository>();
// var tokenRepo = new Mock<ITokenRepository>();
services.AddEntityFrameworkInMemoryDatabase()
.AddDbContext<OmbiContext>();
services.AddIdentity<OmbiUser, IdentityRole>()
.AddEntityFrameworkStores<OmbiContext>().AddUserManager<OmbiUserManager>();
// services.AddEntityFrameworkInMemoryDatabase()
// .AddDbContext<OmbiContext>();
// services.AddIdentity<OmbiUser, IdentityRole>()
// .AddEntityFrameworkStores<OmbiContext>().AddUserManager<OmbiUserManager>();
services.AddTransient(x => _plexApi.Object);
services.AddTransient(x => _embyApi.Object);
services.AddTransient(x => _tokenSettings.Object);
services.AddTransient(x => _embySettings.Object);
services.AddTransient(x => _plexSettings.Object);
services.AddTransient(x => audit.Object);
services.AddTransient(x => tokenRepo.Object);
// Taken from https://github.com/aspnet/MusicStore/blob/dev/test/MusicStore.Test/ManageControllerTest.cs (and modified)
var context = new DefaultHttpContext();
context.Features.Set<IHttpAuthenticationFeature>(new HttpAuthenticationFeature());
services.AddSingleton<IHttpContextAccessor>(h => new HttpContextAccessor { HttpContext = context });
// services.AddTransient(x => _plexApi.Object);
// services.AddTransient(x => _embyApi.Object);
// services.AddTransient(x => _tokenSettings.Object);
// services.AddTransient(x => _embySettings.Object);
// services.AddTransient(x => _plexSettings.Object);
// services.AddTransient(x => audit.Object);
// services.AddTransient(x => tokenRepo.Object);
// // Taken from https://github.com/aspnet/MusicStore/blob/dev/test/MusicStore.Test/ManageControllerTest.cs (and modified)
// var context = new DefaultHttpContext();
// context.Features.Set<IHttpAuthenticationFeature>(new HttpAuthenticationFeature());
// services.AddSingleton<IHttpContextAccessor>(h => new HttpContextAccessor { HttpContext = context });
services.Configure<IdentityOptions>(options =>
{
options.Password.RequireDigit = false;
options.Password.RequiredLength = 1;
options.Password.RequireLowercase = false;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = false;
options.User.AllowedUserNameCharacters = string.Empty;
});
// services.Configure<IdentityOptions>(options =>
// {
// options.Password.RequireDigit = false;
// options.Password.RequiredLength = 1;
// options.Password.RequireLowercase = false;
// options.Password.RequireNonAlphanumeric = false;
// options.Password.RequireUppercase = false;
// options.User.AllowedUserNameCharacters = string.Empty;
// });
return services.BuildServiceProvider();
// return services.BuildServiceProvider();
}
// }
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// public void Configure(IApplicationBuilder app, IHostingEnvironment env)
// {
}
}
}
// }
// }
//}

View file

@ -87,6 +87,7 @@ export interface IBaseRequest {
requestedUser: IUser;
canApprove: boolean;
title: string;
requestedByAlias: string;
}
export interface ITvRequests {

View file

@ -1,36 +1,39 @@
<div class="form-group">
<div class="input-group">
<input type="text" id="search" class="form-control form-control-custom searchwidth" placeholder="Search" (keyup)="search($event)">
<input type="text" id="search" class="form-control form-control-custom searchwidth" placeholder="Search"
(keyup)="search($event)">
<span class="input-group-btn">
<button id="filterBtn" class="btn btn-sm btn-info-outline" (click)="filterDisplay = !filterDisplay">
<i class="fa fa-filter"></i> {{ 'Requests.Filter' | translate }}
</button>
<button class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true"
aria-expanded="true">
<button class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="true">
<i class="fa fa-sort"></i> {{ 'Requests.Sort' | translate }}
<span class="caret"></span>
</button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenu2">
<li>
<a (click)="setOrder(OrderType.RequestedDateAsc, $event)">{{ 'Requests.SortRequestDateAsc' | translate }}
<a (click)="setOrder(OrderType.RequestedDateAsc, $event)">{{ 'Requests.SortRequestDateAsc' |
translate }}
</a>
<a class="active" (click)="setOrder(OrderType.RequestedDateDesc, $event)">{{ 'Requests.SortRequestDateDesc' | translate }}
<a class="active" (click)="setOrder(OrderType.RequestedDateDesc, $event)">{{
'Requests.SortRequestDateDesc' | translate }}
</a>
<a (click)="setOrder(OrderType.TitleAsc, $event)">{{ 'Requests.SortTitleAsc' | translate}}
</a>
<a (click)="setOrder(OrderType.TitleDesc, $event)">{{ 'Requests.SortTitleDesc' | translate}}
</a>
<a (click)="setOrder(OrderType.StatusAsc, $event)">{{ 'Requests.SortStatusAsc' | translate}}
</a>
<a (click)="setOrder(OrderType.StatusDesc, $event)">{{ 'Requests.SortStatusDesc' | translate}}
</a>
</li>
@ -58,16 +61,20 @@
<div class="col-sm-5 small-padding">
<div>
<a href="http://www.imdb.com/title/{{request.imdbId}}/" target="_blank">
<h4 class="request-title">{{request.title}} ({{request.releaseDate | amLocal | amDateFormat: 'YYYY'}})</h4>
<h4 class="request-title">{{request.title}} ({{request.releaseDate | amLocal | amDateFormat:
'YYYY'}})</h4>
</a>
</div>
<br />
<div class="request-info">
<div class="request-by">
<span>{{ 'Requests.RequestedBy' | translate }} </span>
<span *ngIf="!isAdmin">{{request.requestedUser.userName}}</span>
<span *ngIf="isAdmin && request.requestedUser.alias">{{request.requestedUser.alias}}</span>
<span *ngIf="isAdmin && !request.requestedUser.alias">{{request.requestedUser.userName}}</span>
<span *ngIf="request.requestedByAlias">{{request.requestedByAlias}}</span>
<span *ngIf="!request.requestedByAlias">
<span *ngIf="!isAdmin">{{request.requestedUser.userName}}</span>
<span *ngIf="isAdmin && request.requestedUser.alias">{{request.requestedUser.alias}}</span>
<span *ngIf="isAdmin && !request.requestedUser.alias">{{request.requestedUser.userName}}</span>
</span>
</div>
<div class="request-status">
<span>{{ 'Requests.Status' | translate }} </span>
@ -77,13 +84,14 @@
<div class="requested-status">
<span>{{ 'Requests.RequestStatus' | translate }} </span>
<span *ngIf="request.available" class="label label-success" id="availableLabel" [translate]="'Common.Available'"></span>
<span *ngIf="request.approved && !request.available" id="processingRequestLabel" class="label label-info" [translate]="'Common.ProcessingRequest'"></span>
<span *ngIf="request.approved && !request.available" id="processingRequestLabel" class="label label-info"
[translate]="'Common.ProcessingRequest'"></span>
<span *ngIf="request.denied" class="label label-danger" id="requestDeclinedLabel" [translate]="'Common.RequestDenied'"></span>
<span *ngIf="request.deniedReason" title="{{request.deniedReason}}">
<i class="fa fa-info-circle"></i>
</span>
<span *ngIf="!request.approved && !request.availble && !request.denied" id="pendingApprovalLabel" class="label label-warning"
[translate]="'Common.PendingApproval'"></span>
<span *ngIf="!request.approved && !request.availble && !request.denied" id="pendingApprovalLabel"
class="label label-warning" [translate]="'Common.PendingApproval'"></span>
</div>
<div *ngIf="request.denied" id="requestDenied">
@ -93,16 +101,21 @@
</div>
<div id="releaseDate">{{ 'Requests.TheatricalRelease' | translate: {date: request.releaseDate | amLocal | amDateFormat: 'LL'} }}</div>
<div *ngIf="request.digitalReleaseDate" id="digitalReleaseDate">{{ 'Requests.DigitalRelease' | translate: {date: request.digitalReleaseDate | amLocal | amDateFormat: 'LL'} }}</div>
<div id="requestedDate">{{ 'Requests.RequestDate' | translate }} {{request.requestedDate | amLocal | amDateFormat: 'LL'}}</div>
<div id="releaseDate">{{ 'Requests.TheatricalRelease' | translate: {date: request.releaseDate |
amLocal | amDateFormat: 'LL'} }}</div>
<div *ngIf="request.digitalReleaseDate" id="digitalReleaseDate">{{ 'Requests.DigitalRelease' |
translate: {date: request.digitalReleaseDate | amLocal | amDateFormat: 'LL'} }}</div>
<div id="requestedDate">{{ 'Requests.RequestDate' | translate }} {{request.requestedDate | amLocal
| amDateFormat: 'LL'}}</div>
<br />
</div>
<div *ngIf="isAdmin">
<div *ngIf="request.qualityOverrideTitle" class="quality-override">{{ 'Requests.QualityOverride' | translate }}
<div *ngIf="request.qualityOverrideTitle" class="quality-override">{{ 'Requests.QualityOverride' |
translate }}
<span>{{request.qualityOverrideTitle}} </span>
</div>
<div *ngIf="request.rootPathOverrideTitle" class="root-override">{{ 'Requests.RootFolderOverride' | translate }}
<div *ngIf="request.rootPathOverrideTitle" class="root-override">{{ 'Requests.RootFolderOverride' |
translate }}
<span>{{request.rootPathOverrideTitle}} </span>
</div>
</div>
@ -112,10 +125,12 @@
<div class="row">
<div class="col-md-2 col-md-push-6">
<a *ngIf="request.showSubscribe && !request.subscribed" style="color:white" (click)="subscribe(request)" pTooltip="Subscribe for notifications">
<a *ngIf="request.showSubscribe && !request.subscribed" style="color:white" (click)="subscribe(request)"
pTooltip="Subscribe for notifications">
<i class="fa fa-rss"></i>
</a>
<a *ngIf="request.showSubscribe && request.subscribed" style="color:red" (click)="unSubscribe(request)" pTooltip="Unsubscribe notification">
<a *ngIf="request.showSubscribe && request.subscribed" style="color:red" (click)="unSubscribe(request)"
pTooltip="Unsubscribe notification">
<i class="fa fa-rss"></i>
</a>
</div>
@ -123,7 +138,8 @@
<div *ngIf="isAdmin">
<div *ngIf="!request.approved" id="approveBtn">
<form>
<button (click)="approve(request)" style="text-align: right" class="btn btn-sm btn-success-outline approve" type="submit">
<button (click)="approve(request)" style="text-align: right" class="btn btn-sm btn-success-outline approve"
type="submit">
<i class="fa fa-plus"></i> {{ 'Common.Approve' | translate }}
</button>
</form>
@ -133,7 +149,8 @@
<button type="button" class="btn btn-sm btn-warning-outline">
<i class="fa fa-plus"></i> {{ 'Requests.ChangeRootFolder' | translate }}
</button>
<button type="button" class="btn btn-warning-outline dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<button type="button" class="btn btn-warning-outline dropdown-toggle" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="false">
<span class="caret"></span>
<span class="sr-only">Toggle Dropdown</span>
</button>
@ -149,7 +166,8 @@
<button type="button" class="btn btn-sm btn-warning-outline">
<i class="fa fa-plus"></i> {{ 'Requests.ChangeQualityProfile' | translate }}
</button>
<button type="button" class="btn btn-warning-outline dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<button type="button" class="btn btn-warning-outline dropdown-toggle" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="false">
<span class="caret"></span>
<span class="sr-only">Toggle Dropdown</span>
</button>
@ -166,15 +184,15 @@
</button>
</div>
</div>
<form id="markBtnGroup">
<button id="unavailableBtn" *ngIf="request.available" (click)="changeAvailability(request, false)" style="text-align: right"
value="false" class="btn btn-sm btn-info-outline change">
<button id="unavailableBtn" *ngIf="request.available" (click)="changeAvailability(request, false)"
style="text-align: right" value="false" class="btn btn-sm btn-info-outline change">
<i class="fa fa-minus"></i> {{ 'Requests.MarkUnavailable' | translate }}
</button>
<button id="availableBtn" *ngIf="!request.available" (click)="changeAvailability(request, true)" style="text-align: right"
value="true" class="btn btn-sm btn-success-outline change">
<button id="availableBtn" *ngIf="!request.available" (click)="changeAvailability(request, true)"
style="text-align: right" value="true" class="btn btn-sm btn-success-outline change">
<i class="fa fa-plus"></i> {{ 'Requests.MarkAvailable' | translate }}
</button>
</form>
@ -190,8 +208,8 @@
</form>
</div>
<div class="dropdown" *ngIf="issueCategories && issuesEnabled" id="issuesBtn">
<button class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true"
aria-expanded="true">
<button class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="true">
<i class="fa fa-plus"></i> {{ 'Requests.ReportIssue' | translate }}
<span class="caret"></span>
</button>
@ -204,8 +222,8 @@
</div>
</div>
<br/>
<br/>
<br />
<br />
@ -216,11 +234,11 @@
</div>
<p-dialog *ngIf="requestToDeny" header="Deny Request '{{requestToDeny.title}}''" [(visible)]="denyDisplay" [draggable]="false">
<span>Please enter a rejection reason, the user will be notified of this:</span>
<textarea [(ngModel)]="rejectionReason" class="form-control-custom form-control"></textarea>
<span>Please enter a rejection reason, the user will be notified of this:</span>
<textarea [(ngModel)]="rejectionReason" class="form-control-custom form-control"></textarea>
<p-footer>
<button type="button" (click)="denyRequest();" label="Reject" class="btn btn-success">Deny</button>
<button type="button"(click)="denyDisplay=false" label="Close" class="btn btn-danger">Close</button>
<button type="button" (click)="denyDisplay=false" label="Close" class="btn btn-danger">Close</button>
</p-footer>
</p-dialog>

View file

@ -1,36 +1,39 @@
<div class="form-group">
<div class="input-group">
<input type="text" id="search" class="form-control form-control-custom searchwidth" placeholder="Search" (keyup)="search($event)">
<input type="text" id="search" class="form-control form-control-custom searchwidth" placeholder="Search"
(keyup)="search($event)">
<span class="input-group-btn">
<button id="filterBtn" class="btn btn-sm btn-info-outline" (click)="filterDisplay = !filterDisplay">
<i class="fa fa-filter"></i> {{ 'Requests.Filter' | translate }}
</button>
<button class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true"
aria-expanded="true">
<button class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="true">
<i class="fa fa-sort"></i> {{ 'Requests.Sort' | translate }}
<span class="caret"></span>
</button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenu2">
<li>
<a (click)="setOrder(OrderType.RequestedDateAsc, $event)">{{ 'Requests.SortRequestDateAsc' | translate }}
<a (click)="setOrder(OrderType.RequestedDateAsc, $event)">{{ 'Requests.SortRequestDateAsc' |
translate }}
</a>
<a class="active" (click)="setOrder(OrderType.RequestedDateDesc, $event)">{{ 'Requests.SortRequestDateDesc' | translate }}
<a class="active" (click)="setOrder(OrderType.RequestedDateDesc, $event)">{{
'Requests.SortRequestDateDesc' | translate }}
</a>
<a (click)="setOrder(OrderType.TitleAsc, $event)">{{ 'Requests.SortTitleAsc' | translate}}
</a>
<a (click)="setOrder(OrderType.TitleDesc, $event)">{{ 'Requests.SortTitleDesc' | translate}}
</a>
<a (click)="setOrder(OrderType.StatusAsc, $event)">{{ 'Requests.SortStatusAsc' | translate}}
</a>
<a (click)="setOrder(OrderType.StatusDesc, $event)">{{ 'Requests.SortStatusDesc' | translate}}
</a>
</li>
@ -45,7 +48,7 @@
<div class="col-md-12">
<div *ngFor="let request of albumRequests" class="col-md-4">
<div *ngFor="let request of albumRequests" class="col-md-4">
<div class="row">
<div class="album-bg backdrop" [style.background-image]="request.background"></div>
<div class="album-tint" style="background-image: linear-gradient(to bottom, rgba(0,0,0,0.6) 0%,rgba(0,0,0,0.6) 100%);"></div>
@ -53,15 +56,15 @@
<div class="col-sm-12 small-padding">
<img *ngIf="request.disk" class="img-responsive poster album-cover" src="{{request.disk}}" alt="poster">
</div>
<div class="col-sm-12 small-padding">
<div>
<h4>
<a href="" target="_blank">
{{request.title | truncate: 36}}
{{request.title | truncate: 36}}
</a>
</h4>
<h5>
<a href="">
@ -69,25 +72,29 @@
</a>
</h5>
</div>
<div class="request-info">
<div class="request-by">
<span>{{ 'Requests.RequestedBy' | translate }} </span>
<span *ngIf="!isAdmin">{{request.requestedUser.userName}}</span>
<span *ngIf="isAdmin && request.requestedUser.alias">{{request.requestedUser.alias}}</span>
<span *ngIf="isAdmin && !request.requestedUser.alias">{{request.requestedUser.userName}}</span>
<span *ngIf="request.requestedByAlias">{{request.requestedByAlias}}</span>
<span *ngIf="!request.requestedByAlias">
<span *ngIf="!isAdmin">{{request.requestedUser.userName}}</span>
<span *ngIf="isAdmin && request.requestedUser.alias">{{request.requestedUser.alias}}</span>
<span *ngIf="isAdmin && !request.requestedUser.alias">{{request.requestedUser.userName}}</span>
</span>
</div>
<div class="requested-status">
<span>{{ 'Requests.RequestStatus' | translate }} </span>
<span *ngIf="request.available" class="label label-success" id="availableLabel" [translate]="'Common.Available'"></span>
<span *ngIf="request.approved && !request.available" id="processingRequestLabel" class="label label-info" [translate]="'Common.ProcessingRequest'"></span>
<span *ngIf="request.approved && !request.available" id="processingRequestLabel" class="label label-info"
[translate]="'Common.ProcessingRequest'"></span>
<span *ngIf="request.denied" class="label label-danger" id="requestDeclinedLabel" [translate]="'Common.RequestDenied'"></span>
<span *ngIf="request.deniedReason" title="{{request.deniedReason}}">
<i class="fa fa-info-circle"></i>
</span>
<span *ngIf="!request.approved && !request.availble && !request.denied" id="pendingApprovalLabel" class="label label-warning"
[translate]="'Common.PendingApproval'"></span>
<span *ngIf="!request.approved && !request.availble && !request.denied" id="pendingApprovalLabel"
class="label label-warning" [translate]="'Common.PendingApproval'"></span>
</div>
<div *ngIf="request.denied" id="requestDenied">
@ -97,8 +104,10 @@
</div>
<div id="releaseDate">{{ 'Requests.ReleaseDate' | translate: {date: request.releaseDate | amLocal | amDateFormat: 'LL'} }}</div>
<div id="requestedDate">{{ 'Requests.RequestDate' | translate }} {{request.requestedDate | amLocal | amDateFormat: 'LL'}}</div>
<div id="releaseDate">{{ 'Requests.ReleaseDate' | translate: {date: request.releaseDate | amLocal |
amDateFormat: 'LL'} }}</div>
<div id="requestedDate">{{ 'Requests.RequestDate' | translate }} {{request.requestedDate | amLocal
| amDateFormat: 'LL'}}</div>
<br />
</div>
<!-- <div *ngIf="isAdmin">
@ -125,8 +134,9 @@
</div> -->
<div *ngIf="isAdmin">
<div *ngIf="!request.approved" id="approveBtn">
<form class="col-md-6">
<button (click)="approve(request)" style="text-align: right" class="btn btn-sm btn-success-outline approve" type="submit">
<form class="col-md-6">
<button (click)="approve(request)" style="text-align: right" class="btn btn-sm btn-success-outline approve"
type="submit">
<i class="fa fa-plus"></i> {{ 'Common.Approve' | translate }}
</button>
</form>
@ -169,15 +179,15 @@
</button>
</div>
</div>
<form id="markBtnGroup">
<button id="unavailableBtn" *ngIf="request.available" (click)="changeAvailability(request, false)" style="text-align: right"
value="false" class="btn btn-sm btn-info-outline change">
<button id="unavailableBtn" *ngIf="request.available" (click)="changeAvailability(request, false)"
style="text-align: right" value="false" class="btn btn-sm btn-info-outline change">
<i class="fa fa-minus"></i> {{ 'Requests.MarkUnavailable' | translate }}
</button>
<button id="availableBtn" *ngIf="!request.available" (click)="changeAvailability(request, true)" style="text-align: right"
value="true" class="btn btn-sm btn-success-outline change">
<button id="availableBtn" *ngIf="!request.available" (click)="changeAvailability(request, true)"
style="text-align: right" value="true" class="btn btn-sm btn-success-outline change">
<i class="fa fa-plus"></i> {{ 'Requests.MarkAvailable' | translate }}
</button>
</form>
@ -193,8 +203,8 @@
</form>
</div>
<div class="dropdown" *ngIf="issueCategories && issuesEnabled" id="issuesBtn">
<button class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true"
aria-expanded="true">
<button class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="true">
<i class="fa fa-plus"></i> {{ 'Requests.ReportIssue' | translate }}
<span class="caret"></span>
</button>
@ -207,8 +217,8 @@
</div>
</div>
<br/>
<br/>
<br />
<br />
@ -272,8 +282,8 @@
<p-dialog *ngIf="requestToDeny" header="Deny Request '{{requestToDeny.title}}''" [(visible)]="denyDisplay" [draggable]="false">
<span>Please enter a rejection reason, the user will be notified of this:</span>
<textarea [(ngModel)]="rejectionReason" class="form-control-custom form-control"></textarea>
<p-footer>
<button type="button" (click)="denyRequest();" label="Reject" class="btn btn-success">Deny</button>
<button type="button"(click)="denyDisplay=false" label="Close" class="btn btn-danger">Close</button>
</p-footer>
</p-dialog>
<p-footer>
<button type="button" (click)="denyRequest();" label="Reject" class="btn btn-success">Deny</button>
<button type="button" (click)="denyDisplay=false" label="Close" class="btn btn-danger">Close</button>
</p-footer>
</p-dialog>

View file

@ -5,31 +5,43 @@
<div class="col-md-2">
<span [translate]="'Requests.RequestedBy'"></span>
<span *ngIf="!isAdmin">{{child.requestedUser.userName}}</span>
<span *ngIf="isAdmin && child.requestedUser.alias">{{child.requestedUser.alias}}</span>
<span *ngIf="isAdmin && !child.requestedUser.alias">{{child.requestedUser.userName}}</span>
<span *ngIf="child.requestedByAlias">{{child.requestedByAlias}}</span>
<span *ngIf="!child.requestedByAlias">
<span *ngIf="!isAdmin">{{child.requestedUser.userName}}</span>
<span *ngIf="isAdmin && child.requestedUser.alias">{{child.requestedUser.alias}}</span>
<span *ngIf="isAdmin && !child.requestedUser.alias">{{child.requestedUser.userName}}</span>
</span>
</div>
<div class="col-md-1 col-md-push-9">
<button id="subscribeBtn" *ngIf="child.showSubscribe && !child.subscribed" (click)="subscribe(child)" class="btn btn-sm btn-primary-outline" pTooltip="Subscribe for notifications" type="submit"><i class="fa fa-rss"></i> Subscribe</button>
<button id="subscribeBtn" *ngIf="child.showSubscribe && child.subscribed" (click)="unSubscribe(child)" class="btn btn-sm btn-danger-outline" pTooltip="UnSubscribe for notifications" type="submit"><i class="fa fa-rss"></i> UnSubscribe</button>
<div *ngIf="isAdmin">
<button id="approveBtn" *ngIf="child.canApprove && !child.approved" (click)="approve(child)" class="btn btn-sm btn-success-outline" type="submit"><i class="fa fa-plus"></i> {{ 'Common.Approve' | translate }}</button>
<button id="unavailableBtn" *ngIf="child.available" (click)="changeAvailability(child, false)" style="text-align: right" value="false" class="btn btn-sm btn-info-outline change"><i class="fa fa-minus"></i> {{ 'Requests.MarkUnavailable' | translate }}</button>
<button id="availableBtn" *ngIf="!child.available" (click)="changeAvailability(child, true)" style="text-align: right" value="true" class="btn btn-sm btn-success-outline change"><i class="fa fa-plus"></i> {{ 'Requests.MarkAvailable' | translate }}</button>
<button id="subscribeBtn" *ngIf="child.showSubscribe && !child.subscribed" (click)="subscribe(child)"
class="btn btn-sm btn-primary-outline" pTooltip="Subscribe for notifications" type="submit"><i
class="fa fa-rss"></i> Subscribe</button>
<button id="subscribeBtn" *ngIf="child.showSubscribe && child.subscribed" (click)="unSubscribe(child)"
class="btn btn-sm btn-danger-outline" pTooltip="UnSubscribe for notifications" type="submit"><i
class="fa fa-rss"></i> UnSubscribe</button>
<div *ngIf="isAdmin">
<button id="approveBtn" *ngIf="child.canApprove && !child.approved" (click)="approve(child)" class="btn btn-sm btn-success-outline"
type="submit"><i class="fa fa-plus"></i> {{ 'Common.Approve' | translate }}</button>
<button id="unavailableBtn" *ngIf="child.available" (click)="changeAvailability(child, false)"
style="text-align: right" value="false" class="btn btn-sm btn-info-outline change"><i class="fa fa-minus"></i>
{{ 'Requests.MarkUnavailable' | translate }}</button>
<button id="availableBtn" *ngIf="!child.available" (click)="changeAvailability(child, true)" style="text-align: right"
value="true" class="btn btn-sm btn-success-outline change"><i class="fa fa-plus"></i> {{
'Requests.MarkAvailable' | translate }}</button>
<button id="denyBtn" *ngIf="!child.denied" type="button" (click)="deny(child)" class="btn btn-sm btn-danger-outline deny">
<i class="fa fa-times"></i> {{ 'Requests.Deny' | translate }}</button>
</div>
<div *ngIf="isAdmin || isRequestUser(child)">
<button id="removeBtn" type="button" (click)="removeRequest(child)" class="btn btn-sm btn-danger-outline deny"><i class="fa fa-times"></i> {{ 'Requests.Remove' | translate }}</button>
</div>
<div *ngIf="isAdmin || isRequestUser(child)">
<button id="removeBtn" type="button" (click)="removeRequest(child)" class="btn btn-sm btn-danger-outline deny"><i
class="fa fa-times"></i> {{ 'Requests.Remove' | translate }}</button>
</div>
</div>
</div>
<div class="col-md-12">
@ -77,15 +89,19 @@
{{ep.airDate | amLocal | amDateFormat: 'L' }}
</td>
<td>
<span *ngIf="child.denied" class="label label-danger" id="deniedLabel" [translate]="'Common.Denied'">
<i style="color:red;" class="fa fa-check" pTooltip="{{child.deniedReason}}"></i>
<span *ngIf="child.denied" class="label label-danger" id="deniedLabel"
[translate]="'Common.Denied'">
<i style="color:red;" class="fa fa-check" pTooltip="{{child.deniedReason}}"></i>
</span>
<span *ngIf="!child.denied && ep.available" class="label label-success" id="availableLabel" [translate]="'Common.Available'"></span>
<span *ngIf="!child.denied &&ep.approved && !ep.available" class="label label-info" id="processingRequestLabel" [translate]="'Common.ProcessingRequest'"></span>
<span *ngIf="!child.denied && ep.available" class="label label-success" id="availableLabel"
[translate]="'Common.Available'"></span>
<span *ngIf="!child.denied &&ep.approved && !ep.available" class="label label-info"
id="processingRequestLabel" [translate]="'Common.ProcessingRequest'"></span>
<div *ngIf="!child.denied && !ep.approved">
<div *ngIf="!ep.available"><span class="label label-warning" id="pendingApprovalLabel" [translate]="'Common.PendingApproval'"></span></div>
<div *ngIf="!ep.available"><span class="label label-warning" id="pendingApprovalLabel"
[translate]="'Common.PendingApproval'"></span></div>
</div>
</td>
</tr>
</tbody>
@ -105,8 +121,8 @@
<p-dialog *ngIf="requestToDeny" header="Deny Request '{{requestToDeny.title}}''" [(visible)]="denyDisplay" [draggable]="false">
<span>Please enter a rejection reason, the user will be notified of this:</span>
<textarea [(ngModel)]="rejectionReason" class="form-control-custom form-control"></textarea>
<p-footer>
<button type="button" (click)="denyRequest();" label="Reject" class="btn btn-success">Deny</button>
<button type="button"(click)="denyDisplay=false" label="Close" class="btn btn-danger">Close</button>
</p-footer>
</p-dialog>
<p-footer>
<button type="button" (click)="denyRequest();" label="Reject" class="btn btn-success">Deny</button>
<button type="button" (click)="denyDisplay=false" label="Close" class="btn btn-danger">Close</button>
</p-footer>
</p-dialog>

View file

@ -1,66 +0,0 @@
<div class="modal-header">
<h3>Add A Friend!</h3>
</div>
<div class="modal-body">
<p>You can invite a user to share your Plex Library here. The invited user will be asked to confirm friendship.</p>
<p>Please note that this user will not appear in your Ombi Users since they have not accepted the Plex Invite, as soon as they accept
the Plex invite then the User Importer job will run (if enabled) and add the user into Ombi.
</p>
<div *ngIf="plexServers">
<form novalidate [formGroup]="form" (ngSubmit)="onSubmit(form)" style="padding-top:5%;">
<div class="form-group">
<label for="username" class="control-label">Username/Email</label>
<input type="text" class="form-control form-control-custom " id="username" name="username" p formControlName="username" [ngClass]="{'form-error': form.get('username').hasError('required')}">
<small *ngIf="form.get('username').hasError('required')" class="error-text">The Username/Email is required</small>
</div>
<div class="form-group">
<label for="select" class="control-label">Select a Server</label>
<div id="profiles">
<select formControlName="selectedServer" (change)="selected()" class="form-control form-control-custom" id="select" [ngClass]="{'form-error': form.get('selectedServer').hasError('required')}">
<option *ngFor="let server of plexServers" value="{{server.machineId}}">{{server.serverName}}</option>
</select>
</div>
<small *ngIf="form.get('selectedServer').hasError('required')" class="error-text">You need to select a server!</small>
</div>
<div *ngIf="plexLibs" class="form-group">
<label for="select" class="control-label">Libraries to share</label>
<div class="form-group">
<div class="checkbox">
<input type="checkbox" id="selectAll" formControlName="allLibsSelected">
<label for="selectAll">All</label>
</div>
</div>
<div *ngIf="!form.value.allLibsSelected">
<div *ngFor="let lib of plexLibs">
<div class="col-md-4">
<div class="checkbox">
<input type="checkbox" id="{{lib.id}}" value={{lib.id}} (change)="checkedLib($event.target.checked, $event.target.value)">
<label for="{{lib.id}}">{{lib.title}}</label>
</div>
</div>
</div>
</div>
</div>
<br>
<br>
<br>
</form>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary-outline" (click)="onSubmit(form)" [disabled]="form.invalid">Add</button>
<button type="button" class="btn btn-danger-outline" (click)="activeModal.close('Close click')">Close</button>
</div>

View file

@ -1,84 +0,0 @@
import { Component, Input, OnInit } from "@angular/core";
import { FormBuilder, FormGroup, Validators } from "@angular/forms";
import { NgbActiveModal } from "@ng-bootstrap/ng-bootstrap";
import { NotificationService, PlexService } from "../services";
import { IPlexSection, IPlexServersAdd } from "../interfaces";
@Component({
selector: "ngbd-modal-content",
templateUrl: "./addplexuser.component.html",
})
export class AddPlexUserComponent implements OnInit {
@Input() public name: string;
public plexServers: IPlexServersAdd[];
public plexLibs: IPlexSection[];
public libsSelected: number[] = [];
public form: FormGroup;
constructor(public activeModal: NgbActiveModal,
private plexService: PlexService,
private notificationService: NotificationService,
private fb: FormBuilder) {
}
public ngOnInit(): void {
this.form = this.fb.group({
selectedServer: [null, Validators.required],
allLibsSelected: [true],
username:[null, Validators.required],
});
this.getServers();
}
public getServers() {
this.plexService.getServersFromSettings().subscribe(x => {
if (x.success) {
this.plexServers = x.servers;
}
});
}
public getPlexLibs(machineId: string) {
this.plexService.getLibrariesFromSettings(machineId).subscribe(x => {
if (x.successful) {
this.plexLibs = x.data;
}
});
}
public selected() {
this.getPlexLibs(this.form.value.selectedServer);
}
public checkedLib(checked: boolean, value: number) {
if(checked) {
this.libsSelected.push(value);
} else {
this.libsSelected = this.libsSelected.filter(v => v !== value);
}
}
public onSubmit(form: FormGroup) {
if (form.invalid) {
this.notificationService.error("Please check your entered values");
return;
}
const libs = form.value.allLibsSelected ? [] : this.libsSelected;
this.plexService.addUserToServer({ username: form.value.username, machineIdentifier: form.value.selectedServer, libsSelected: libs }).subscribe(x => {
if (x.success) {
this.notificationService.success("User added to Plex");
} else {
this.notificationService.error(x.error);
}
this.activeModal.close();
});
}
}

View file

@ -5,9 +5,7 @@
<button type="button" class="btn btn-success-outline" data-test="adduserbtn" [routerLink]="['/usermanagement/user']">Add User To Ombi</button>
<button type="button" style="float:right;" class="btn btn-primary-outline"(click)="showBulkEdit = !showBulkEdit" [disabled]="!hasChecked()">Bulk Edit</button>
<div *ngIf="plexEnabled">
<button type="button" style="float:right;" class="btn btn-success-outline" (click)="open()">Add Plex Friend</button>
</div>
<!-- Table -->
<table class="table table-striped table-hover table-responsive table-condensed table-usermanagement">
<thead>

View file

@ -1,9 +1,7 @@
import { Component, OnInit } from "@angular/core";
import { NgbModal } from "@ng-bootstrap/ng-bootstrap";
import { ICheckbox, ICustomizationSettings, IEmailNotificationSettings, IUser } from "../interfaces";
import { IdentityService, NotificationService, SettingsService } from "../services";
import { AddPlexUserComponent } from "./addplexuser.component";
@Component({
templateUrl: "./usermanagement.component.html",
@ -27,8 +25,7 @@ export class UserManagementComponent implements OnInit {
constructor(private identityService: IdentityService,
private settingsService: SettingsService,
private notificationService: NotificationService,
private plexSettings: SettingsService,
private modalService: NgbModal) { }
private plexSettings: SettingsService) { }
public ngOnInit() {
this.users = [];
@ -43,11 +40,6 @@ export class UserManagementComponent implements OnInit {
this.settingsService.getEmailNotificationSettings().subscribe(x => this.emailSettings = x);
}
public open() {
const modalRef = this.modalService.open(AddPlexUserComponent, {container:"ombi", backdropClass:"custom-modal-backdrop", windowClass:"window"});
modalRef.componentInstance.name = "World";
}
public welcomeEmail(user: IUser) {
if (!user.emailAddress) {
this.notificationService.error("The user needs an email address.");

View file

@ -16,7 +16,6 @@ import { IdentityService, PlexService, RadarrService, SonarrService } from "../s
import { AuthGuard } from "../auth/auth.guard";
import { OrderModule } from "ngx-order-pipe";
import { AddPlexUserComponent } from "./addplexuser.component";
import { SharedModule } from "../shared/shared.module";
@ -45,12 +44,8 @@ const routes: Routes = [
declarations: [
UserManagementComponent,
UpdateDetailsComponent,
AddPlexUserComponent,
UserManagementUserComponent,
],
entryComponents:[
AddPlexUserComponent,
],
exports: [
RouterModule,
],

View file

@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Authorization;
using System;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Ombi.Core.Engine;
using Ombi.Core.Models.Requests;
@ -11,6 +12,7 @@ using Ombi.Core.Models;
using Ombi.Core.Models.UI;
using Ombi.Store.Entities;
using ILogger = Microsoft.Extensions.Logging.ILogger;
using System.Linq;
namespace Ombi.Controllers
{
@ -76,6 +78,7 @@ namespace Ombi.Controllers
[HttpPost]
public async Task<RequestEngineResult> RequestAlbum([FromBody] MusicAlbumRequestViewModel album)
{
album.RequestedByAlias = GetApiAlias();
var result = await _engine.RequestAlbum(album);
if (result.Result)
{
@ -168,5 +171,17 @@ namespace Ombi.Controllers
{
return await _engine.GetRemainingRequests();
}
private string GetApiAlias()
{
// Make sure this only applies when using the API KEY
if (HttpContext.Request.Headers.Keys.Contains("ApiKey", StringComparer.InvariantCultureIgnoreCase))
{
if (HttpContext.Request.Headers.TryGetValue("ApiAlias", out var apiAlias))
{
return apiAlias;
}
}
return null;
}
}
}

View file

@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Authorization;
using System;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Ombi.Core.Engine;
using Ombi.Core.Engine.Interfaces;
@ -8,12 +9,14 @@ using System.Collections.Generic;
using System.Threading.Tasks;
using Ombi.Store.Entities.Requests;
using System.Diagnostics;
using System.Linq;
using Microsoft.Extensions.Logging;
using Ombi.Attributes;
using Ombi.Core.Models.UI;
using Ombi.Models;
using Ombi.Store.Entities;
using Ombi.Core.Models;
using Ombi.Helpers;
namespace Ombi.Controllers
{
@ -82,6 +85,7 @@ namespace Ombi.Controllers
[HttpPost("movie")]
public async Task<RequestEngineResult> RequestMovie([FromBody] MovieRequestViewModel movie)
{
movie.RequestedByAlias = GetApiAlias();
var result = await MovieRequestEngine.RequestMovie(movie);
if (result.Result)
{
@ -277,6 +281,7 @@ namespace Ombi.Controllers
[HttpPost("tv")]
public async Task<RequestEngineResult> RequestTv([FromBody] TvRequestViewModel tv)
{
tv.RequestedByAlias = GetApiAlias();
var result = await TvRequestEngine.RequestTvShow(tv);
if (result.Result)
{
@ -521,5 +526,19 @@ namespace Ombi.Controllers
{
return await TvRequestEngine.GetRemainingRequests();
}
private string GetApiAlias()
{
// Make sure this only applies when using the API KEY
if (HttpContext.Request.Headers.Keys.Contains("ApiKey", StringComparer.InvariantCultureIgnoreCase))
{
if (HttpContext.Request.Headers.TryGetValue("ApiAlias", out var apiAlias))
{
return apiAlias;
}
}
return null;
}
}
}