Merge branch 'DotNetCore' of https://github.com/tidusjar/PlexRequests.Net into DotNetCore

pull/1520/head
Jamie.Rees 7 years ago
commit 6e23d155de

Binary file not shown.

Before

Width:  |  Height:  |  Size: 361 KiB

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 577 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 11 KiB

@ -471,7 +471,7 @@
<Content Include="Content\helpers\bootbox.min.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\images\emby-logo-dark.jpg">
<Content Include="Content\images\emby-logo-dark.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\images\emby-logo.png">

@ -36,7 +36,7 @@
<div class="form-group">
<div class="row">
<a href="#" id="embyImg">
<img class="wizard-img" src="@formAction/Content/images/emby-logo-dark.jpg" />
<img class="wizard-img" src="@formAction/Content/images/emby-logo-dark.png" />
</a>
</div>
<div class="row">

@ -15,5 +15,6 @@ namespace Ombi.Core.Engine.Interfaces
Task RemoveMovieRequest(int requestId);
Task<MovieRequests> UpdateMovieRequest(MovieRequests request);
Task<RequestEngineResult> ApproveMovie(MovieRequests request);
}
}

@ -13,10 +13,13 @@ namespace Ombi.Core.Engine.Interfaces
Task<RequestEngineResult> RequestTvShow(SearchTvShowViewModel tv);
Task<IEnumerable<TvRequests>> SearchTvRequest(string search);
Task<IEnumerable<TreeNode<TvRequests, List<ChildRequests>>>> SearchTvRequestTree(string search);
Task<TvRequests> UpdateTvRequest(TvRequests request);
Task<IEnumerable<TreeNode<TvRequests, List<ChildRequests>>>> GetRequestsTreeNode(int count, int position);
Task<IEnumerable<ChildRequests>> GetAllChldren(int tvId);
Task<ChildRequests> UpdateChildRequest(ChildRequests request);
Task RemoveTvChild(int requestId);
Task<RequestEngineResult> ApproveChildRequest(ChildRequests request);
}
}

@ -141,6 +141,42 @@ namespace Ombi.Core.Engine
return results;
}
/// <summary>
/// This is the method that is triggered by pressing Approve on the requests page
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
public async Task<RequestEngineResult> ApproveMovie(MovieRequests request)
{
if (request.Approved)
{
var result = await Sender.Send(request);
if (result.Success && result.MovieSent)
{
return new RequestEngineResult
{
RequestAdded = true
};
}
if (!result.Success)
{
Logger.LogWarning("Tried auto sending movie but failed. Message: {0}", result.Message);
return new RequestEngineResult
{
Message = result.Message,
ErrorMessage = result.Message,
RequestAdded = false
};
}
// If there are no providers then it's successful but movie has not been sent
}
return new RequestEngineResult
{
RequestAdded = true
};
}
/// <summary>
/// Updates the movie request.
/// </summary>

@ -11,4 +11,13 @@ namespace Ombi.Core.Engine
public bool Leaf { get; set; }
public bool Expanded { get; set; }
}
public class TreeNode<T,U>
{
public string Label { get; set; }
public T Data { get; set; }
public List<TreeNode<U>> Children { get; set; }
public bool Leaf { get; set; }
public bool Expanded { get; set; }
}
}

@ -37,7 +37,7 @@ namespace Ombi.Core.Engine
private INotificationHelper NotificationHelper { get; }
private ITvMazeApi TvApi { get; }
private IMapper Mapper { get; }
private ITvSender TvSender {get;}
private ITvSender TvSender { get; }
private IAuditRepository Audit { get; }
public async Task<RequestEngineResult> RequestTvShow(SearchTvShowViewModel tv)
@ -51,7 +51,7 @@ namespace Ombi.Core.Engine
.CreateChild(tv, user.Id);
await tvBuilder.BuildEpisodes(tv);
var ruleResults = await RunRequestRules(tvBuilder.ChildRequest);
var results = ruleResults as RuleResult[] ?? ruleResults.ToArray();
if (results.Any(x => !x.Success))
@ -122,6 +122,12 @@ namespace Ombi.Core.Engine
return allRequests;
}
public async Task<IEnumerable<TreeNode<TvRequests, List<ChildRequests>>>> GetRequestsTreeNode(int count, int position)
{
var allRequests = await TvRepository.Get().Skip(position).Take(count).ToListAsync();
return ParseIntoTreeNode(allRequests);
}
public async Task<IEnumerable<TvRequests>> GetRequests()
{
var allRequests = TvRepository.Get();
@ -132,7 +138,7 @@ namespace Ombi.Core.Engine
{
return await TvRepository.GetChild().Where(x => x.ParentRequestId == tvId).ToListAsync();
}
public async Task<IEnumerable<TvRequests>> SearchTvRequest(string search)
{
var allRequests = TvRepository.Get();
@ -140,21 +146,42 @@ namespace Ombi.Core.Engine
return results;
}
public async Task<IEnumerable<TreeNode<TvRequests, List<ChildRequests>>>> SearchTvRequestTree(string search)
{
var allRequests = TvRepository.Get();
var results = await allRequests.Where(x => x.Title.Contains(search, CompareOptions.IgnoreCase)).ToListAsync();
return ParseIntoTreeNode(results);
}
public async Task<TvRequests> UpdateTvRequest(TvRequests request)
{
await Audit.Record(AuditType.Updated, AuditArea.TvRequest, $"Updated Request {request.Title}", Username);
var allRequests = TvRepository.Get();
var results = await allRequests.FirstOrDefaultAsync(x => x.Id == request.Id);
// TODO need to check if we need to approve any child requests since they may have updated
await TvRepository.Update(results);
return results;
}
public async Task<RequestEngineResult> ApproveChildRequest(ChildRequests request)
{
if (request.Approved)
{
await Audit.Record(AuditType.Approved, AuditArea.TvRequest, $"Approved Request {request.Title}", Username);
// Autosend
await TvSender.SendToSonarr(request);
}
await TvRepository.UpdateChild(request);
return new RequestEngineResult
{
RequestAdded = true
};
}
public async Task<ChildRequests> UpdateChildRequest(ChildRequests request)
{
await Audit.Record(AuditType.Updated, AuditArea.TvRequest, $"Updated Request {request.Title}", Username);
await TvRepository.UpdateChild(request);
return request;
}
@ -164,7 +191,7 @@ namespace Ombi.Core.Engine
var request = await TvRepository.GetChild().FirstOrDefaultAsync(x => x.Id == requestId);
var all = TvRepository.Db.TvRequests.Include(x => x.ChildRequests);
var parent = all.FirstOrDefault(x => x.Id == request.ParentRequestId);
// Is this the only child? If so delete the parent
if (parent.ChildRequests.Count <= 1)
{
@ -201,6 +228,41 @@ namespace Ombi.Core.Engine
return await AfterRequest(model.ChildRequests.FirstOrDefault());
}
private static List<TreeNode<TvRequests, List<ChildRequests>>> ParseIntoTreeNode(IEnumerable<TvRequests> result)
{
var node = new List<TreeNode<TvRequests, List<ChildRequests>>>();
foreach (var value in result)
{
node.Add(new TreeNode<TvRequests, List<ChildRequests>>
{
Data = value,
Children = new List<TreeNode<List<ChildRequests>>>
{
new TreeNode<List<ChildRequests>>
{
Data = SortEpisodes(value.ChildRequests),
Leaf = true
}
}
});
}
return node;
}
private static List<ChildRequests> SortEpisodes(List<ChildRequests> items)
{
foreach (var value in items)
{
foreach (var requests in value.SeasonRequests)
{
requests.Episodes.OrderBy(x => x.EpisodeNumber);
}
}
return items;
}
private async Task<RequestEngineResult> AfterRequest(ChildRequests model)
{
var sendRuleResult = await RunSpecificRule(model, SpecificRules.CanSendNotification);
@ -209,7 +271,7 @@ namespace Ombi.Core.Engine
NotificationHelper.NewRequest(model);
}
if(model.Approved)
if (model.Approved)
{
// Autosend
await TvSender.SendToSonarr(model);
@ -221,8 +283,8 @@ namespace Ombi.Core.Engine
//public async Task<IEnumerable<TvRequests>> GetApprovedRequests()
//{
// var allRequests = TvRepository.Get();
//}
//public async Task<IEnumerable<TvRequests>> GetNewRequests()

@ -19,13 +19,14 @@ using Ombi.Store.Entities.Requests;
using Ombi.Store.Repository.Requests;
using Ombi.Store.Entities;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
namespace Ombi.Core.Engine
{
public class TvSearchEngine : BaseMediaEngine, ITvSearchEngine
{
public TvSearchEngine(IPrincipal identity, IRequestServiceMain service, ITvMazeApi tvMaze, IMapper mapper, ISettingsService<PlexSettings> plexSettings,
ISettingsService<EmbySettings> embySettings, IPlexContentRepository repo, ITraktApi trakt, IRuleEvaluator r, UserManager<OmbiUser> um)
ISettingsService<EmbySettings> embySettings, IPlexContentRepository repo, IEmbyContentRepository embyRepo, ITraktApi trakt, IRuleEvaluator r, UserManager<OmbiUser> um)
: base(identity, service, r, um)
{
TvMazeApi = tvMaze;
@ -34,6 +35,7 @@ namespace Ombi.Core.Engine
EmbySettings = embySettings;
PlexContentRepo = repo;
TraktApi = trakt;
EmbyContentRepo = embyRepo;
}
private ITvMazeApi TvMazeApi { get; }
@ -41,6 +43,7 @@ namespace Ombi.Core.Engine
private ISettingsService<PlexSettings> PlexSettings { get; }
private ISettingsService<EmbySettings> EmbySettings { get; }
private IPlexContentRepository PlexContentRepo { get; }
private IEmbyContentRepository EmbyContentRepo { get; }
private ITraktApi TraktApi { get; }
public async Task<IEnumerable<SearchTvShowViewModel>> Search(string searchTerm)
@ -175,11 +178,31 @@ namespace Ombi.Core.Engine
{
if (embySettings.Enable)
{
//var embyShow = EmbyChecker.GetTvShow(embyCached.ToArray(), t.show.name, t.show.premiered?.Substring(0, 4), providerId);
//if (embyShow != null)
//{
// viewT.Available = true;
//}
var content = await EmbyContentRepo.Get(item.Id.ToString());
if (content != null)
{
item.Available = true;
}
// Let's go through the episodes now
if (item.SeasonRequests.Any())
{
var allEpisodes = EmbyContentRepo.GetAllEpisodes().Include(x => x.Series);
foreach (var season in item.SeasonRequests)
{
foreach (var episode in season.Episodes)
{
var epExists = await allEpisodes.FirstOrDefaultAsync(x =>
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber && item.Id.ToString() == x.Series.ProviderId);
if (epExists != null)
{
episode.Available = true;
}
}
}
}
}
if (plexSettings.Enable)
{
@ -190,6 +213,24 @@ namespace Ombi.Core.Engine
item.Available = true;
item.PlexUrl = content.Url;
}
// Let's go through the episodes now
if (item.SeasonRequests.Any())
{
var allEpisodes = PlexContentRepo.GetAllEpisodes();
foreach (var season in item.SeasonRequests)
{
foreach (var episode in season.Episodes)
{
var epExists = await allEpisodes.FirstOrDefaultAsync(x =>
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber);
if (epExists != null)
{
episode.Available = true;
}
}
}
}
}
if (item.Id > 0)

@ -77,6 +77,12 @@ namespace Ombi.Schedule.Jobs.Emby
foreach (var ep in allEpisodes.Items)
{
if (ep.LocationType.Equals("Virtual", StringComparison.CurrentCultureIgnoreCase))
{
// This means that we don't actully have the file, it's just Emby being nice and showing future stuff
continue;
}
var epInfo = await _api.GetEpisodeInformation(ep.Id, server.ApiKey, server.AdministratorId, server.FullUri);
if (epInfo?.ProviderIds?.Tvdb == null)
{

@ -108,31 +108,8 @@ namespace Ombi.Schedule.Jobs.Ombi
}
else
{
// Linux
if (desc.Contains("ubuntu", CompareOptions.IgnoreCase))
{
// Ubuntu
Logger.LogInformation(LoggingEvents.Updater, "We are ubuntu");
download = updates.Downloads.FirstOrDefault(x => x.Name.Contains("ubuntu", CompareOptions.IgnoreCase));
}
else if (desc.Contains("debian", CompareOptions.IgnoreCase))
{
// Debian
Logger.LogInformation(LoggingEvents.Updater, "We are debian");
download = updates.Downloads.FirstOrDefault(x => x.Name.Contains("debian", CompareOptions.IgnoreCase));
}
else if (desc.Contains("centos", CompareOptions.IgnoreCase))
{
// Centos
Logger.LogInformation(LoggingEvents.Updater, "We are centos");
download = updates.Downloads.FirstOrDefault(x => x.Name.Contains("centos",
CompareOptions.IgnoreCase));
}
else
{
return;
}
Logger.LogInformation(LoggingEvents.Updater, "We are linux");
download = updates.Downloads.FirstOrDefault(x => x.Name.Contains("linux", CompareOptions.IgnoreCase));
}
if (download == null)
{
@ -201,7 +178,7 @@ namespace Ombi.Schedule.Jobs.Ombi
{
UseShellExecute = false,
CreateNoWindow = true,
FileName = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location),"TempUpdate",$"Ombi.Updater{updaterExtension}"),
FileName = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "TempUpdate", $"Ombi.Updater{updaterExtension}"),
Arguments = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) + " " + extension
};
using (var proc = new Process { StartInfo = start })
@ -212,7 +189,7 @@ namespace Ombi.Schedule.Jobs.Ombi
}
}
catch (Exception e)
{
{
Ctx.WriteLine(e);
throw;
}

@ -14,6 +14,7 @@ namespace Ombi.Store.Entities
Url,
Port,
FanartTv,
TheMovieDb
TheMovieDb,
StoragePath
}
}

@ -27,6 +27,9 @@
<ul *ngIf="hasRole('Admin') || hasRole('PowerUser')" class="nav navbar-nav">
<li id="UserManagement" [routerLinkActive]="['active']"><a [routerLink]="['/usermanagement']"><i class="fa fa-user"></i> User Management</a></li>
</ul>
<ul class="nav navbar-nav">
<li><a href="https://www.paypal.me/PlexRequestsNet" target="_blank"><i class="fa fa-heart" style="color:red"></i> Donate!</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">

@ -14,6 +14,12 @@ export interface IPlexLibraries {
mediaContainer: IMediaContainer;
}
export interface IPlexLibResponse {
successful: boolean;
message: string;
data: IPlexLibraries;
}
export interface IMediaContainer {
directory: IDirectory[];
}

@ -137,4 +137,5 @@ export interface IEpisodesRequests {
available: boolean;
requested: boolean;
approved: boolean;
selected: boolean; // This is for the UI only
}

@ -18,7 +18,7 @@
<h3><i class="fa fa-bell-o"></i> <b>Notice</b></h3>
<span [innerHtml]="landingPageSettings.noticeText"></span>
</div>
<div>
<div *ngIf="mediaServerStatus">
<div *ngIf="mediaServerStatus.fullyAvailable">
<h3 class="online"><i class="fa fa-check-circle "></i> Currently Online</h3>
<span>The media server is currently online</span>

@ -29,6 +29,8 @@ export class LandingPageComponent implements OnInit {
});
this.landingPageService.getServerStatus().subscribe(x => this.mediaServerStatus = x);
this.landingPageService.getServerStatus().subscribe(x => {
this.mediaServerStatus = x;
});
}
}

@ -9,7 +9,7 @@ include the remember me checkbox
<div class="card card-container">
<!-- <img class="profile-img-card" src="//lh3.googleusercontent.com/-6V8xOA6M7BA/AAAAAAAAAAI/AAAAAAAAAAA/rzlHcD0KYwo/photo.jpg?sz=120" alt="" /> -->
<div *ngIf="!customizationSettings.logo"><img id="profile-img" class="profile-img-card" src="/images/ms-icon-150x150.png" /></div>
<div *ngIf="customizationSettings.logo"><img id="profile-img" class="profile-img-card" [src]="customizationSettings.logo" /></div>
<div *ngIf="customizationSettings.logo"><img id="profile-img" class="profile-img-custom" [src]="customizationSettings.logo" /></div>
<p id="profile-name" class="profile-name-card"></p>
<form class="form-signin" novalidate [formGroup]="form" (ngSubmit)="onSubmit(form)">

@ -14,7 +14,6 @@ body, html {
height: 100% !important;
background-repeat: no-repeat !important;
background-image: linear-gradient(rgb(104, 145, 162), rgb(12, 97, 33)) !important;
}
landingDiv {
@ -52,138 +51,147 @@ img.bg {
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
width:100%;
width: 100%;
}
.btn {
font-weight: 700;
height: 36px;
-moz-user-select: none;
-webkit-user-select: none;
user-select: none;
cursor: default;
}
/*
.btn {
font-weight: 700;
height: 36px;
-moz-user-select: none;
-webkit-user-select: none;
user-select: none;
cursor: default;
}
/*
* Card component
*/
.card {
/*background-image: linear-gradient(rgb(104, 145, 162), rgb(12, 97, 33)) !important;*/
background-color: $bg-colour;
/* just in case there no content*/
padding: 20px 25px 30px;
margin: 0 auto 25px;
margin-top: 50px;
/* shadows and rounded borders */
-moz-border-radius: 2px;
-webkit-border-radius: 2px;
border-radius: 2px;
/*-moz-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
.card {
/*background-image: linear-gradient(rgb(104, 145, 162), rgb(12, 97, 33)) !important;*/
background-color: $bg-colour;
/* just in case there no content*/
padding: 20px 25px 30px;
margin: 0 auto 25px;
margin-top: 50px;
/* shadows and rounded borders */
-moz-border-radius: 2px;
-webkit-border-radius: 2px;
border-radius: 2px;
/*-moz-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
-webkit-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);*/
}
.profile-img-card {
width: 96px;
height: 96px;
margin: 0 auto 10px;
display: block;
-moz-border-radius: 50%;
-webkit-border-radius: 50%;
border-radius: 50%;
}
/*
}
.profile-img-custom {
width: 100%;
margin: 0 auto 10px;
display: block;
-moz-border-radius: 50%;
-webkit-border-radius: 50%;
border-radius: 50%;
}
.profile-img-card {
width: 96px;
height: 96px;
margin: 0 auto 10px;
display: block;
-moz-border-radius: 50%;
-webkit-border-radius: 50%;
border-radius: 50%;
}
/*
* Form styles
*/
.profile-name-card {
font-size: 16px;
font-weight: bold;
text-align: center;
margin: 10px 0 0;
min-height: 1em;
}
.form-control {
color: black;
background-color: white !important;
}
.reauth-email {
display: block;
color: white;
line-height: 2;
margin-bottom: 10px;
font-size: 14px;
text-align: center;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
.form-signin #inputEmail,
.form-signin #inputPassword {
direction: ltr;
height: 44px;
font-size: 16px;
}
.form-signin input[type=email],
.form-signin input[type=password],
.form-signin input[type=text],
.form-signin button {
width: 100%;
display: block;
margin-bottom: 10px;
z-index: 1;
position: relative;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
.btn.btn-signin {
/*background-color: #4d90fe; */
background-color: rgb(104, 145, 162);
/* background-color: linear-gradient(rgb(104, 145, 162), rgb(12, 97, 33));*/
padding: 0px;
font-weight: 700;
font-size: 14px;
height: 36px;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
border-radius: 3px;
border: none;
-o-transition: all 0.218s;
-moz-transition: all 0.218s;
-webkit-transition: all 0.218s;
transition: all 0.218s;
}
.btn.btn-signin:hover,
.btn.btn-signin:active,
.btn.btn-signin:focus {
background-color: rgb(12, 97, 33);
}
.forgot-password {
color: rgb(104, 145, 162);
text-align:center;
}
.forgot-password:hover,
.forgot-password:active,
.forgot-password:focus {
color: rgb(12, 97, 33);
}
// Placeholders
.profile-name-card {
font-size: 16px;
font-weight: bold;
text-align: center;
margin: 10px 0 0;
min-height: 1em;
}
.form-control {
color: black;
background-color: white !important;
}
.reauth-email {
display: block;
color: white;
line-height: 2;
margin-bottom: 10px;
font-size: 14px;
text-align: center;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
.form-signin #inputEmail,
.form-signin #inputPassword {
direction: ltr;
height: 44px;
font-size: 16px;
}
.form-signin input[type=email],
.form-signin input[type=password],
.form-signin input[type=text],
.form-signin button {
width: 100%;
display: block;
margin-bottom: 10px;
z-index: 1;
position: relative;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
.btn.btn-signin {
/*background-color: #4d90fe; */
background-color: rgb(104, 145, 162);
/* background-color: linear-gradient(rgb(104, 145, 162), rgb(12, 97, 33));*/
padding: 0px;
font-weight: 700;
font-size: 14px;
height: 36px;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
border-radius: 3px;
border: none;
-o-transition: all 0.218s;
-moz-transition: all 0.218s;
-webkit-transition: all 0.218s;
transition: all 0.218s;
}
.btn.btn-signin:hover,
.btn.btn-signin:active,
.btn.btn-signin:focus {
background-color: rgb(12, 97, 33);
}
.forgot-password {
color: rgb(104, 145, 162);
text-align: center;
}
.forgot-password:hover,
.forgot-password:active,
.forgot-password:focus {
color: rgb(12, 97, 33);
}
// Placeholders
::-webkit-input-placeholder { /* WebKit, Blink, Edge */
color: $placeholder-colour;
@ -205,4 +213,4 @@ img.bg {
::-ms-input-placeholder { /* Microsoft Edge */
color: $placeholder-colour;
}
}

@ -1,16 +1,11 @@
import { Component, OnDestroy, OnInit } from "@angular/core";
import { Component, OnInit } from "@angular/core";
import "rxjs/add/operator/debounceTime";
import "rxjs/add/operator/distinctUntilChanged";
import "rxjs/add/operator/map";
import "rxjs/add/operator/takeUntil";
import { Subject } from "rxjs/Subject";
import "rxjs/add/operator/debounceTime";
import "rxjs/add/operator/distinctUntilChanged";
import "rxjs/add/operator/map";
import { AuthService } from "../auth/auth.service";
import { RequestService } from "../services";
import { NotificationService, RequestService } from "../services";
import { IMovieRequests } from "../interfaces";
@ -18,7 +13,7 @@ import { IMovieRequests } from "../interfaces";
selector: "movie-requests",
templateUrl: "./movierequests.component.html",
})
export class MovieRequestsComponent implements OnInit, OnDestroy {
export class MovieRequestsComponent implements OnInit {
public movieRequests: IMovieRequests[];
public searchChanged: Subject<string> = new Subject<string>();
@ -29,14 +24,12 @@ export class MovieRequestsComponent implements OnInit, OnDestroy {
private currentlyLoaded: number;
private amountToLoad: number;
private subscriptions = new Subject<void>();
constructor(private requestService: RequestService,
private auth: AuthService) {
private auth: AuthService,
private notificationService: NotificationService) {
this.searchChanged
.debounceTime(600) // Wait Xms afterthe last event before emitting last event
.debounceTime(600) // Wait Xms after the last event before emitting last event
.distinctUntilChanged() // only emit if value is different from previous value
.takeUntil(this.subscriptions)
.subscribe(x => {
this.searchText = x as string;
if (this.searchText === "") {
@ -44,7 +37,6 @@ export class MovieRequestsComponent implements OnInit, OnDestroy {
return;
}
this.requestService.searchMovieRequests(this.searchText)
.takeUntil(this.subscriptions)
.subscribe(m => this.movieRequests = m);
});
}
@ -79,7 +71,7 @@ export class MovieRequestsComponent implements OnInit, OnDestroy {
public approve(request: IMovieRequests) {
request.approved = true;
request.denied = false;
this.updateRequest(request);
this.approveRequest(request);
}
public deny(request: IMovieRequests) {
@ -88,14 +80,8 @@ export class MovieRequestsComponent implements OnInit, OnDestroy {
this.updateRequest(request);
}
public ngOnDestroy() {
this.subscriptions.next();
this.subscriptions.complete();
}
private loadRequests(amountToLoad: number, currentlyLoaded: number) {
this.requestService.getMovieRequests(amountToLoad, currentlyLoaded + 1)
.takeUntil(this.subscriptions)
.subscribe(x => {
this.movieRequests.push.apply(this.movieRequests, x);
this.currentlyLoaded = currentlyLoaded + amountToLoad;
@ -104,13 +90,25 @@ export class MovieRequestsComponent implements OnInit, OnDestroy {
private updateRequest(request: IMovieRequests) {
this.requestService.updateMovieRequest(request)
.takeUntil(this.subscriptions)
.subscribe(x => request = x);
}
private approveRequest(request: IMovieRequests) {
this.requestService.approveMovie(request)
.subscribe(x => {
if (x.requestAdded) {
this.notificationService.success("Request Approved",
`Request for ${request.title} has been approved successfully`);
} else {
this.notificationService.warning("Request Approved", x.message ? x.message : x.errorMessage);
request.approved = false;
}
});
}
private loadInit() {
this.requestService.getMovieRequests(this.amountToLoad, 0)
.takeUntil(this.subscriptions)
.subscribe(x => this.movieRequests = x);
}

@ -10,13 +10,13 @@
<div class="col-md-1 col-md-push-9" *ngIf="isAdmin">
<form>
<button style="text-align: right" *ngIf="child.CanApprove" (click)="approve(child)" class="btn btn-sm btn-success-outline" type="submit"><i class="fa fa-plus"></i> Approve</button>
<button style="text-align: right" *ngIf="child.canApprove && !child.approved" (click)="approve(child)" class="btn btn-sm btn-success-outline" type="submit"><i class="fa fa-plus"></i> Approve</button>
</form>
<form>
<button type="button" (click)="deny(child)" class="btn btn-sm btn-danger-outline deny"><i class="fa fa-times"></i> Deny</button>
</form>
<form>
<button type="button" (click)="deny(child)" class="btn btn-sm btn-danger-outline deny"><i class="fa fa-times"></i> Remove</button>
<button type="button" (click)="removeRequest(child)" class="btn btn-sm btn-danger-outline deny"><i class="fa fa-times"></i> Remove</button>
</form>
</div>
</div>
@ -68,7 +68,7 @@
<span *ngIf="ep.available" class="label label-success">Available</span>
<span *ngIf="ep.approved && !ep.available" class="label label-info">Processing Request</span>
<div *ngIf="!ep.approved">
<div *ngIf="!ep.available"><span class="label label-warning">Pending Approval</span></div>
<div *ngIf="!ep.available"><span class="label label-warning">Pending Approval</span></div>
</div>
</td>
</tr>

@ -1,6 +1,6 @@
import { Component, Input } from "@angular/core";
import { IChildRequests, IEpisodesRequests } from "../interfaces";
import { RequestService } from "../services";
import { NotificationService, RequestService } from "../services";
@Component({
selector:"tvrequests-children",
@ -9,7 +9,9 @@ import { RequestService } from "../services";
export class TvRequestChildrenComponent {
@Input() public childRequests: IChildRequests[];
@Input() public isAdmin: boolean;
constructor(private requestService: RequestService ) { }
constructor(private requestService: RequestService,
private notificationService: NotificationService) { }
public removeRequest(request: IChildRequests) {
this.requestService.deleteChild(request)
.subscribe();
@ -36,8 +38,21 @@ export class TvRequestChildrenComponent {
public approve(request: IChildRequests) {
request.approved = true;
request.denied = false;
this.requestService.updateChild(request)
.subscribe();
request.seasonRequests.forEach((season) => {
season.episodes.forEach((ep) => {
ep.approved = true;
});
});
this.requestService.approveChild(request)
.subscribe(x => {
if (x.requestAdded) {
this.notificationService.success("Request Approved",
`Request has been approved successfully`);
} else {
this.notificationService.warning("Request Approved", x.message ? x.message : x.errorMessage);
request.approved = false;
}
});
}
public denySeasonRequest(request: IChildRequests) {

@ -19,11 +19,6 @@
(scrolled)="loadMore()">-->
<div>
<p-treeTable [value]="tvRequests">
<!--<p-column>
<ng-template let-col let-node="rowData" pTemplate="body">
<img src="{{node.data.data.banner}}" class="img-responsive poster" width="150" />
</ng-template>
</p-column>-->
<p-column>
<ng-template let-col let-node="rowData" pTemplate="header">
@ -63,7 +58,7 @@
</div>
<!--This is the section that holds the child seasons if they want to specify specific episodes-->
<div *ngIf="node.leaf">
<tvrequests-children [childRequests]="node.data" [isAdmin] ="isAdmin"></tvrequests-children>
<tvrequests-children [childRequests]="node.data" [isAdmin] ="isAdmin" ></tvrequests-children>
</div>
</ng-template>
</p-column>

@ -1,8 +1,7 @@
import { Component, OnDestroy, OnInit, ViewEncapsulation } from "@angular/core";
import { Component, OnInit } from "@angular/core";
import "rxjs/add/operator/debounceTime";
import "rxjs/add/operator/distinctUntilChanged";
import "rxjs/add/operator/map";
import "rxjs/add/operator/takeUntil";
import { Subject } from "rxjs/Subject";
import "rxjs/add/operator/debounceTime";
@ -13,18 +12,14 @@ import { AuthService } from "../auth/auth.service";
import { RequestService } from "../services";
import { TreeNode } from "primeng/primeng";
import { IChildRequests, IEpisodesRequests, INewSeasonRequests, ITvRequests } from "../interfaces";
import { ITvRequests } from "../interfaces";
@Component({
selector: "tv-requests",
templateUrl: "./tvrequests.component.html",
styleUrls: ["./tvrequests.component.scss"],
//Was required to turn off encapsulation since CSS only should be overridden for this component
//However when encapsulation is on angular injects prefixes to all classes so css selectors
//Stop working
encapsulation: ViewEncapsulation.None,
})
export class TvRequestsComponent implements OnInit, OnDestroy {
export class TvRequestsComponent implements OnInit {
public tvRequests: TreeNode[];
public searchChanged = new Subject<string>();
@ -35,23 +30,20 @@ export class TvRequestsComponent implements OnInit, OnDestroy {
private currentlyLoaded: number;
private amountToLoad: number;
private subscriptions = new Subject<void>();
constructor(private requestService: RequestService,
private auth: AuthService) {
this.searchChanged
.debounceTime(600) // Wait Xms afterthe last event before emitting last event
.distinctUntilChanged() // only emit if value is different from previous value
.takeUntil(this.subscriptions)
.subscribe(x => {
this.searchText = x as string;
if (this.searchText === "") {
this.resetSearch();
return;
}
this.requestService.searchTvRequests(this.searchText)
.takeUntil(this.subscriptions)
.subscribe(m => this.tvRequests = this.transformData(m));
this.requestService.searchTvRequestsTree(this.searchText)
.subscribe(m => this.tvRequests = m);
});
}
public openClosestTab(el: any) {
@ -78,30 +70,7 @@ export class TvRequestsComponent implements OnInit, OnDestroy {
}
}
}
public transformData(data: ITvRequests[]): TreeNode[] {
const temp: TreeNode[] = [];
data.forEach((value) => {
temp.push({
data: value,
children: [{
data: this.fixEpisodeSort(value.childRequests), leaf: true,
}],
leaf: false,
});
}, this);
return <TreeNode[]>temp;
}
public fixEpisodeSort(items: IChildRequests[]) {
items.forEach((value) => {
value.seasonRequests.forEach((requests: INewSeasonRequests) => {
requests.episodes.sort((a: IEpisodesRequests, b: IEpisodesRequests) => {
return a.episodeNumber - b.episodeNumber;
});
});
});
return items;
}
public ngOnInit() {
this.amountToLoad = 1000;
this.currentlyLoaded = 5;
@ -116,10 +85,9 @@ export class TvRequestsComponent implements OnInit, OnDestroy {
//if you scroll really quickly then you start getting duplicates of movies
//since it's async and some subsequent results return first and then incrementer
//is increased so you see movies which had already been gotten show up...
this.requestService.getTvRequests(this.amountToLoad, this.currentlyLoaded + 1)
.takeUntil(this.subscriptions)
this.requestService.getTvRequestsTree(this.amountToLoad, this.currentlyLoaded + 1)
.subscribe(x => {
this.tvRequests.push.apply(this.tvRequests, this.transformData(x));
this.tvRequests = x;
this.currentlyLoaded = this.currentlyLoaded + this.amountToLoad;
});
}
@ -128,87 +96,15 @@ export class TvRequestsComponent implements OnInit, OnDestroy {
this.searchChanged.next(text.target.value);
}
public removeRequest(request: ITvRequests) {
this.requestService.removeTvRequest(request);
this.removeRequestFromUi(request);
}
public changeAvailability(request: IChildRequests, available: boolean) {
request.available = available;
//this.updateRequest(request);
}
//Was already here but not sure what's using it...'
//public approve(request: IChildRequests) {
// request.approved = true;
// request.denied = false;
// //this.updateRequest(request);
//}
public approve(request: IChildRequests) {
request.approved = true;
request.denied = false;
this.requestService.updateChild(request)
.subscribe();
}
//Was already here but not sure what's using it...'
//public deny(request: IChildRequests) {
// request.approved = false;
// request.denied = true;
// //this.updateRequest(request);
//}
public deny(request: IChildRequests) {
request.approved = false;
request.denied = true;
this.requestService.updateChild(request)
.subscribe();
}
public approveSeasonRequest(request: IChildRequests) {
request.approved = true;
request.denied = false;
this.requestService.updateTvRequest(this.selectedSeason)
.subscribe();
}
public denySeasonRequest(request: IChildRequests) {
request.approved = false;
request.denied = true;
this.requestService.updateTvRequest(this.selectedSeason)
.subscribe();
}
public showChildren(request: ITvRequests) {
this.selectedSeason = request;
this.showChildDialogue = true;
}
public getColour(ep: IEpisodesRequests): string {
if (ep.available) {
return "lime";
}
if (ep.approved) {
return "#00c0ff";
}
return "white";
}
public ngOnDestroy() {
this.subscriptions.next();
this.subscriptions.complete();
}
//private updateRequest(request: ITvRequests) {
// this.requestService.updateTvRequest(request)
// .takeUntil(this.subscriptions)
// .subscribe(x => request = x);
//}
private loadInit() {
this.requestService.getTvRequests(this.amountToLoad, 0)
.takeUntil(this.subscriptions)
this.requestService.getTvRequestsTree(this.amountToLoad, 0)
.subscribe(x => {
this.tvRequests = this.transformData(x);
this.tvRequests = x;
});
}
@ -216,11 +112,4 @@ export class TvRequestsComponent implements OnInit, OnDestroy {
this.currentlyLoaded = 5;
this.loadInit();
}
private removeRequestFromUi(key: ITvRequests) {
const index = this.tvRequests.findIndex(x => x.data === key);
if (index > -1) {
this.tvRequests.splice(index, 1);
}
}
}

@ -55,14 +55,15 @@
</td>
<td>
<ng-template [ngIf]="ep.available"><span class="label label-success">Available</span></ng-template>
<ng-template [ngIf]="ep.approved && !ep.available"><span class="label label-info">Processing Request</span></ng-template>
<ng-template [ngIf]="ep.requested && !ep.approved && !ep.available"><span class="label label-warning">Pending Approval</span></ng-template>
<ng-template [ngIf]="ep.approved && !ep.available "><span class="label label-info">Processing Request</span></ng-template>
<ng-template [ngIf]="ep.selected"><span class="label label-info">Selected</span></ng-template>
<ng-template [ngIf]="ep.requested && !ep.approved && !ep.available && !ep.selected"><span class="label label-warning">Pending Approval</span></ng-template>
<ng-template [ngIf]="!ep.requested && !ep.available"><span class="label label-danger">Not Requested</span></ng-template>
</td>
<td>
<button (click)="addRequest(ep)" [disabled]="ep.available || ep.requested || ep.approved" class="btn btn-sm btn-primary-outline">Add Request</button>
<button *ngIf="!ep.selected" (click)="addRequest(ep)" [disabled]="ep.available || ep.requested || ep.approved" class="btn btn-sm btn-primary-outline">Select</button>
<button *ngIf="ep.selected" (click)="removeRequest(ep)" class="btn btn-sm btn-primary-outline">Unselect</button>
</td>
</tr>
</tbody>

@ -54,10 +54,12 @@ export class SeriesInformationComponent implements OnInit, OnDestroy {
public addRequest(episode: IEpisodesRequests) {
episode.requested = true;
episode.selected = true;
}
public removeRequest(episode: IEpisodesRequests) {
episode.requested = false;
episode.selected = false;
}
public ngOnDestroy() {

@ -59,7 +59,7 @@
<span *ngIf="node.data.firstAired" class="label label-info" target="_blank">Air Date: {{node.data.firstAired | date: 'dd/MM/yyyy'}}</span>
<ng-template [ngIf]="result.available"><span class="label label-success">Available</span></ng-template>
<ng-template [ngIf]="node.data.available"><span class="label label-success">Available</span></ng-template>

@ -6,7 +6,7 @@ import { Observable } from "rxjs/Rx";
import { ServiceAuthHelpers } from "../service.helpers";
import { IPlexAuthentication, IPlexLibraries, IPlexServerViewModel } from "../../interfaces";
import { IPlexAuthentication, IPlexLibResponse, IPlexServerViewModel } from "../../interfaces";
import { IPlexServer } from "../../interfaces";
@Injectable()
@ -23,7 +23,7 @@ export class PlexService extends ServiceAuthHelpers {
return this.http.post(`${this.url}servers`, JSON.stringify({ login, password }), { headers: this.headers }).map(this.extractData);
}
public getLibraries(plexSettings: IPlexServer): Observable<IPlexLibraries> {
public getLibraries(plexSettings: IPlexServer): Observable<IPlexLibResponse> {
return this.http.post(`${this.url}Libraries`, JSON.stringify(plexSettings), { headers: this.headers }).map(this.extractData).catch(this.handleError);
}

@ -3,6 +3,7 @@ import { Http } from "@angular/http";
import { AuthHttp } from "angular2-jwt";
import { Observable } from "rxjs/Rx";
import { TreeNode } from "primeng/primeng";
import { IRequestEngineResult } from "../interfaces";
import { IChildRequests, IMovieRequests, IRequestCountModel, IRequestGrid, ITvRequests } from "../interfaces";
import { ISearchMovieResult } from "../interfaces";
@ -23,6 +24,10 @@ export class RequestService extends ServiceAuthHelpers {
return this.http.post(`${this.url}TV/`, JSON.stringify(tv), { headers: this.headers }).map(this.extractData);
}
public approveMovie(movie: IMovieRequests): Observable<IRequestEngineResult> {
return this.http.post(`${this.url}Movie/Approve`, JSON.stringify(movie), { headers: this.headers }).map(this.extractData);
}
public getMovieRequests(count: number, position: number): Observable<IMovieRequests[]> {
return this.http.get(`${this.url}movie/${count}/${position}`).map(this.extractData);
}
@ -44,6 +49,11 @@ export class RequestService extends ServiceAuthHelpers {
.catch(this.handleError);
}
public getTvRequestsTree(count: number, position: number): Observable<TreeNode[]> {
return this.http.get(`${this.url}tv/${count}/${position}/tree`).map(this.extractData)
.catch(this.handleError);
}
public getChildRequests(requestId: number): Observable<IChildRequests[]> {
return this.http.get(`${this.url}tv/${requestId}/child`).map(this.extractData)
.catch(this.handleError);
@ -51,6 +61,10 @@ export class RequestService extends ServiceAuthHelpers {
public searchTvRequests(search: string): Observable<ITvRequests[]> {
return this.http.get(`${this.url}tv/search/${search}`).map(this.extractData);
}
public searchTvRequestsTree(search: string): Observable<TreeNode[]> {
return this.http.get(`${this.url}tv/search/${search}/tree`).map(this.extractData);
}
public removeTvRequest(request: ITvRequests) {
@ -62,6 +76,9 @@ export class RequestService extends ServiceAuthHelpers {
}
public updateChild(child: IChildRequests): Observable<IChildRequests> {
return this.http.put(`${this.url}tv/child`, JSON.stringify(child), { headers: this.headers }).map(this.extractData);
}
public approveChild(child: IChildRequests): Observable<IRequestEngineResult> {
return this.http.post(`${this.url}tv/child/approve`, JSON.stringify(child), { headers: this.headers }).map(this.extractData);
}
public deleteChild(child: IChildRequests): Observable<IChildRequests> {
return this.http.delete(`${this.url}tv/child/${child.id}`, { headers: this.headers }).map(this.extractData);

@ -1,4 +1,5 @@
import { Headers, Http, Response } from "@angular/http";
import "rxjs/add/observable/throw";
import { Observable } from "rxjs/Observable";
import { AuthHttp } from "angular2-jwt";

@ -24,7 +24,7 @@
<ng-template ngbTabContent>
<br />
<br />
<div class="col-md-2 " style="float: right;">
<div class="col-md-2" style="float: right;">
<button type="submit" (click)="removeServer(server)" class="btn btn-danger-outline">Remove Server</button>
</div>
<br />
@ -76,23 +76,20 @@
</div>
<div class="form-group">
<div>
<button id="testPlex" type="submit" (click)="test()" class="btn btn-primary-outline">Test Connectivity <div id="spinner"></div></button>
<button id="testEmby" type="submit" (click)="test()" class="btn btn-primary-outline">Test Connectivity <div id="spinner"></div></button>
</div>
</div>
</div>
</ng-template>
</ngb-tab>
</div>
</ngb-tabset>
<div class="row">
<div class="col-md-1 col-md-push-5">
<div class="col-md-12">
<div class="form-group">
<div>
<button (click)="save()" type="submit" id="save" class="btn btn-primary-outline">Submit</button>
</div>
</div>
</div>
</div>
</fieldset>
</div>

@ -39,7 +39,7 @@ export class EmbyComponent implements OnInit {
public test(server: IEmbyServer) {
this.testerService.embyTest(server).subscribe(x => {
if (x) {
if (x === true) {
this.notificationService.success("Connected", `Successfully connected to the Emby server ${server.name}!`);
} else {
this.notificationService.error("Connected", `We could not connect to the Emby server ${server.name}!`);

@ -58,11 +58,10 @@ export class PushoverComponent implements OnInit {
this.testerService.pushoverTest(form.value).subscribe(x => {
if (x) {
this.notificationService.success("Successful", "Successfully sent a Pushbullet message, please check the discord channel");
this.notificationService.success("Successful", "Successfully sent a Pushover message");
} else {
this.notificationService.success("Error", "There was an error when sending the Pushbullet message. Please check your settings");
this.notificationService.success("Error", "There was an error when sending the Pushover message. Please check your settings");
}
});
}
}

@ -60,7 +60,7 @@ export class PlexComponent implements OnInit, OnDestroy {
public testPlex(server: IPlexServer) {
this.testerService.plexTest(server).subscribe(x => {
if (x) {
if (x === true) {
this.notificationService.success("Connected", `Successfully connected to the Plex server ${server.name}!`);
} else {
this.notificationService.error("Connected", `We could not connect to the Plex server ${server.name}!`);
@ -90,15 +90,19 @@ export class PlexComponent implements OnInit, OnDestroy {
}
this.plexService.getLibraries(server).subscribe(x => {
server.plexSelectedLibraries = [];
x.mediaContainer.directory.forEach((item, index) => {
const lib: IPlexLibrariesSettings = {
key: item.key,
title: item.title,
enabled: false,
};
server.plexSelectedLibraries.push(lib);
});
},
if (x.successful) {
x.data.mediaContainer.directory.forEach((item) => {
const lib: IPlexLibrariesSettings = {
key: item.key,
title: item.title,
enabled: false,
};
server.plexSelectedLibraries.push(lib);
});
} else {
this.notificationService.error("Error", x.message);
}
},
err => { this.notificationService.error("Error", err); });
}

@ -7,7 +7,7 @@
<div class="form-group">
<div class="row">
<a (click)="emby()" id="embyImg">
<img class="wizard-img" src="/images/emby-logo-dark.jpg" />
<img class="wizard-img" src="/images/emby-logo-dark.png" />
</a>
</div>
<div class="row">

@ -4,6 +4,7 @@ using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Ombi.Api.Plex;
using Ombi.Api.Plex.Models;
using Ombi.Attributes;
@ -18,14 +19,17 @@ namespace Ombi.Controllers.External
[Produces("application/json")]
public class PlexController : Controller
{
public PlexController(IPlexApi plexApi, ISettingsService<PlexSettings> plexSettings)
public PlexController(IPlexApi plexApi, ISettingsService<PlexSettings> plexSettings,
ILogger<PlexController> logger)
{
PlexApi = plexApi;
PlexSettings = plexSettings;
_log = logger;
}
private IPlexApi PlexApi { get; }
private ISettingsService<PlexSettings> PlexSettings { get; }
private readonly ILogger<PlexController> _log;
/// <summary>
/// Signs into the Plex API.
@ -88,11 +92,29 @@ namespace Ombi.Controllers.External
/// <param name="settings">The settings.</param>
/// <returns></returns>
[HttpPost("Libraries")]
public async Task<PlexContainer> GetPlexLibraries([FromBody] PlexServers settings)
public async Task<PlexLibrariesResponse> GetPlexLibraries([FromBody] PlexServers settings)
{
var libs = await PlexApi.GetLibrarySections(settings.PlexAuthToken, settings.FullUri);
try
{
var libs = await PlexApi.GetLibrarySections(settings.PlexAuthToken, settings.FullUri);
return new PlexLibrariesResponse
{
Successful = true,
Data = libs
};
}
catch (Exception e)
{
_log.LogWarning(e,"Error thrown when attempting to obtain the plex libs");
return libs;
var message = e.InnerException != null ? $"{e.Message} - {e.InnerException.Message}" : e.Message;
return new PlexLibrariesResponse
{
Successful = false,
Message = message
};
}
}
/// <summary>

@ -36,58 +36,55 @@ namespace Ombi.Controllers
{
var model = new MediaSeverAvailibilityViewModel();
model.ServersAvailable = 3;
model.ServersUnavailable = 1;
return model;
//var plex = await _plexSettings.GetSettingsAsync();
//if (plex.Enable)
//{
// foreach (var s in plex.Servers)
// {
// try
// {
// var result = await _plexApi.GetStatus(s.PlexAuthToken, s.FullUri);
// if (!string.IsNullOrEmpty(result.MediaContainer?.version))
// {
// model.ServersAvailable++;
// }
// else
// {
// model.ServersUnavailable++;
// }
// }
// catch (Exception)
// {
// model.ServersUnavailable++;
// }
// }
//}
var plex = await _plexSettings.GetSettingsAsync();
if (plex.Enable)
{
foreach (var s in plex.Servers)
{
try
{
var result = await _plexApi.GetStatus(s.PlexAuthToken, s.FullUri);
if (!string.IsNullOrEmpty(result.MediaContainer?.version))
{
model.ServersAvailable++;
}
else
{
model.ServersUnavailable++;
}
}
catch (Exception)
{
model.ServersUnavailable++;
}
}
}
//var emby = await _embySettings.GetSettingsAsync();
//if (emby.Enable)
//{
// foreach (var server in emby.Servers)
// {
// try
// {
// var result = await _embyApi.GetUsers(server.FullUri, server.ApiKey);
// if (result.Any())
// {
// model.ServersAvailable++;
// }
// else
// {
// model.ServersUnavailable++;
// }
// }
// catch (Exception)
// {
// model.ServersUnavailable++;
// }
// }
//}
//return model;
var emby = await _embySettings.GetSettingsAsync();
if (emby.Enable)
{
foreach (var server in emby.Servers)
{
try
{
var result = await _embyApi.GetUsers(server.FullUri, server.ApiKey);
if (result.Any())
{
model.ServersAvailable++;
}
else
{
model.ServersUnavailable++;
}
}
catch (Exception)
{
model.ServersUnavailable++;
}
}
}
return model;
}
}
}

@ -89,6 +89,29 @@ namespace Ombi.Controllers
return await MovieRequestEngine.UpdateMovieRequest(model);
}
/// <summary>
/// Updates the specified movie request.
/// </summary>
/// <param name="model">The model.</param>
/// <returns></returns>
[HttpPost("movie/approve")]
public async Task<RequestEngineResult> ApproveMovie([FromBody] MovieRequests model)
{
return await MovieRequestEngine.ApproveMovie(model);
}
/// <summary>
/// Gets the tv requests.
/// </summary>
/// <param name="count">The count of items you want to return.</param>
/// <param name="position">The position.</param>
/// <returns></returns>
[HttpGet("tv/{count:int}/{position:int}/tree")]
public async Task<IEnumerable<TreeNode<TvRequests, List<ChildRequests>>>> GetTvRequestsTree(int count, int position)
{
return await TvRequestEngine.GetRequestsTreeNode(count, position);
}
/// <summary>
/// Gets the tv requests.
/// </summary>
@ -98,16 +121,7 @@ namespace Ombi.Controllers
[HttpGet("tv/{count:int}/{position:int}")]
public async Task<IEnumerable<TvRequests>> GetTvRequests(int count, int position)
{
try
{
return await TvRequestEngine.GetRequests(count, position);
}
catch (System.Exception e)
{
Debug.WriteLine(e.Message);
throw;
}
return await TvRequestEngine.GetRequests(count, position);
}
/// <summary>
@ -142,6 +156,17 @@ namespace Ombi.Controllers
return await TvRequestEngine.SearchTvRequest(searchTerm);
}
/// <summary>
/// Searches for a specific tv request
/// </summary>
/// <param name="searchTerm">The search term.</param>
/// <returns></returns>
[HttpGet("tv/search/{searchTerm}/tree")]
public async Task<IEnumerable<TreeNode<TvRequests, List<ChildRequests>>>> SearchTvTree(string searchTerm)
{
return await TvRequestEngine.SearchTvRequestTree(searchTerm);
}
/// <summary>
/// Deletes the a specific tv request
/// </summary>
@ -175,6 +200,17 @@ namespace Ombi.Controllers
return await TvRequestEngine.UpdateChildRequest(child);
}
/// <summary>
/// Updates the a specific child request
/// </summary>
/// <param name="child">The model.</param>
/// <returns></returns>
[HttpPost("tv/child/approve")]
public async Task<RequestEngineResult> ApproveChild([FromBody] ChildRequests child)
{
return await TvRequestEngine.ApproveChildRequest(child);
}
/// <summary>
/// Deletes the a specific tv request
/// </summary>

@ -141,7 +141,7 @@ namespace Ombi.Controllers
public async Task<bool> PlexSettings([FromBody]PlexSettings plex)
{
var result = await Save(plex);
if (result)
if (result && plex.Enable)
{
BackgroundJob.Enqueue(() => _plexContentCacher.CacheContent());
}
@ -166,14 +166,17 @@ namespace Ombi.Controllers
[HttpPost("emby")]
public async Task<bool> EmbySettings([FromBody]EmbySettings emby)
{
foreach (var server in emby.Servers)
if (emby.Enable)
{
var users = await _embyApi.GetUsers(server.FullUri, server.ApiKey);
var admin = users.FirstOrDefault(x => x.Policy.IsAdministrator);
server.AdministratorId = admin?.Id;
foreach (var server in emby.Servers)
{
var users = await _embyApi.GetUsers(server.FullUri, server.ApiKey);
var admin = users.FirstOrDefault(x => x.Policy.IsAdministrator);
server.AdministratorId = admin?.Id;
}
}
var result = await Save(emby);
if (result)
if (result && emby.Enable)
{
BackgroundJob.Enqueue(() => _embyContentCacher.Start());
}

@ -0,0 +1,38 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2017 Jamie Rees
// File: PlexLibrariesResponse.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using Ombi.Api.Plex.Models;
namespace Ombi.Models.External
{
public class PlexLibrariesResponse
{
public PlexContainer Data { get; set; }
public bool Successful { get; set; }
public string Message { get; set; }
}
}

@ -24,12 +24,16 @@
<ItemGroup>
<!-- Files not to show in IDE -->
<Compile Remove="Styles\**" />
<Compile Remove="wwwroot\dist\**" />
<!-- Files not to publish (note that the 'dist' subfolders are re-added below) -->
<Content Remove="ClientApp\**" />
<Content Remove="Styles\**" />
<Content Remove="wwwroot\dist\**" />
<EmbeddedResource Remove="Styles\**" />
<EmbeddedResource Remove="wwwroot\dist\**" />
<None Remove="Styles\**" />
<None Remove="wwwroot\dist\**" />
</ItemGroup>
@ -77,11 +81,6 @@
<ProjectReference Include="..\Ombi.Updater\Ombi.Updater.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Styles\" />
</ItemGroup>
<ItemGroup>
<None Include="wwwroot\loading.css" />
</ItemGroup>

@ -19,11 +19,13 @@ namespace Ombi
int port = 0;
string host = string.Empty;
string storagePath = string.Empty;
Parser.Default.ParseArguments<Options>(args)
.WithParsed(o =>
{
port = o.Port;
host = o.Host;
storagePath = o.StoragePath;
});
UrlArgs = $"{host}:{port}";
@ -46,7 +48,7 @@ namespace Ombi
Type = ConfigurationTypes.Port,
Value = "5000"
};
ctx.ApplicationConfigurations.Add(url);
ctx.ApplicationConfigurations.Add(dbPort);
ctx.SaveChanges();
@ -87,5 +89,10 @@ namespace Ombi
[Option('p', "port", Required = false, HelpText = "The port, default is 5000", Default = 5000)]
public int Port { get; set; }
[Option('s', "storage", Required = false, HelpText = "Storage path, where we save the logs and database")]
public string StoragePath { get; set; }
}
}

@ -168,7 +168,12 @@ namespace Ombi
app.UseAuthentication();
//ApiKeyMiddlewear(app, serviceProvider);
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
c.ShowJsonEditor();
});
app.UseMvc(routes =>
{
routes.MapRoute(
@ -179,11 +184,6 @@ namespace Ombi
name: "spa-fallback",
defaults: new { controller = "Home", action = "Index" });
});
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
c.ShowJsonEditor();
});
}
private static void ApiKeyMiddlewear(IApplicationBuilder app, IServiceProvider serviceProvider)

@ -23,6 +23,9 @@
true,
"allow-new"
],
"no-trailing-whitespace": [
false
],
"max-classes-per-file": [
false
],

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1022 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 180 B

After

Width:  |  Height:  |  Size: 86 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 178 B

After

Width:  |  Height:  |  Size: 74 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 B

After

Width:  |  Height:  |  Size: 111 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 B

After

Width:  |  Height:  |  Size: 81 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 111 B

After

Width:  |  Height:  |  Size: 88 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 B

After

Width:  |  Height:  |  Size: 88 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 B

After

Width:  |  Height:  |  Size: 110 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 B

After

Width:  |  Height:  |  Size: 86 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Loading…
Cancel
Save