Merge pull request #543 from tidusjar/dev

Dev
This commit is contained in:
Jamie 2016-09-18 22:01:48 +01:00 committed by GitHub
commit 962e663fcd
25 changed files with 404 additions and 70 deletions

View file

@ -151,7 +151,7 @@ namespace PlexRequests.Core
var passwordMatch = PasswordHasher.VerifyPassword(oldPassword, userToChange.Salt, userToChange.Hash); var passwordMatch = PasswordHasher.VerifyPassword(oldPassword, userToChange.Salt, userToChange.Hash);
if (!passwordMatch) if (!passwordMatch)
{ {
throw new SecurityException("Password does not match"); throw new SecurityException("Incorrect password.");
} }
var newSalt = PasswordHasher.GenerateSalt(); var newSalt = PasswordHasher.GenerateSalt();

View file

@ -46,6 +46,7 @@ namespace PlexRequests.Helpers.Tests
{ {
yield return new TestCaseData("hello!").Returns("5a8dd3ad0756a93ded72b823b19dd877").SetName("Hello"); yield return new TestCaseData("hello!").Returns("5a8dd3ad0756a93ded72b823b19dd877").SetName("Hello");
yield return new TestCaseData("0111111").Returns("9549d400a68633435918290085f06293").SetName("Number"); yield return new TestCaseData("0111111").Returns("9549d400a68633435918290085f06293").SetName("Number");
yield return new TestCaseData("tidusjar").Returns("f47201e0103fa37ca82ce5243cebb9c7").SetName("tidusjar");
yield return new TestCaseData("hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!").Returns("26d0d126918fbecfc0fa4a63070d314c") yield return new TestCaseData("hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!hello!").Returns("26d0d126918fbecfc0fa4a63070d314c")
.SetName("Long string"); .SetName("Long string");
} }

View file

@ -65,7 +65,7 @@ namespace PlexRequests.Helpers.Tests
"ProviderId", "ImdbId", "TvDbId", "Overview", "Title", "PosterPath", "ReleaseDate", "Type", "ProviderId", "ImdbId", "TvDbId", "Overview", "Title", "PosterPath", "ReleaseDate", "Type",
"Status", "Approved", "RequestedBy", "RequestedDate", "Available", "Issues", "OtherMessage", "AdminNote", "Status", "Approved", "RequestedBy", "RequestedDate", "Available", "Issues", "OtherMessage", "AdminNote",
"SeasonList", "SeasonCount", "SeasonsRequested", "MusicBrainzId", "RequestedUsers","ArtistName", "SeasonList", "SeasonCount", "SeasonsRequested", "MusicBrainzId", "RequestedUsers","ArtistName",
"ArtistId","IssueId","Episodes","AllUsers","CanApprove","Id" "ArtistId","IssueId","Episodes", "Denied", "DeniedReason", "AllUsers","CanApprove","Id",
}).SetName("Requested Model"); }).SetName("Requested Model");
} }
} }

View file

@ -35,7 +35,7 @@ namespace PlexRequests.Helpers
{ {
using (var md5 = MD5.Create()) using (var md5 = MD5.Create())
{ {
var inputBytes = Encoding.ASCII.GetBytes(input); var inputBytes = Encoding.UTF8.GetBytes(input);
var hash = md5.ComputeHash(inputBytes); var hash = md5.ComputeHash(inputBytes);
var sb = new StringBuilder(); var sb = new StringBuilder();

View file

@ -92,8 +92,12 @@ namespace PlexRequests.Services.Jobs
{ {
var movies = Cache.Get<CouchPotatoMovies>(CacheKeys.CouchPotatoQueued); var movies = Cache.Get<CouchPotatoMovies>(CacheKeys.CouchPotatoQueued);
var items = movies?.movies?.Select(x => x.info?.tmdb_id).Cast<int>().ToArray(); var items = movies?.movies?.Select(x => x.info?.tmdb_id);
return items ?? new int[] { }; if(items != null)
{
return items.Cast<int>().ToArray();
}
return new int[] { };
} }
catch (Exception e) catch (Exception e)
{ {

View file

@ -195,9 +195,18 @@ namespace PlexRequests.Services.Jobs
public PlexMovie GetMovie(PlexMovie[] plexMovies, string title, string year, string providerId = null) public PlexMovie GetMovie(PlexMovie[] plexMovies, string title, string year, string providerId = null)
{ {
if (plexMovies.Length == 0)
{
return null;
}
var advanced = !string.IsNullOrEmpty(providerId); var advanced = !string.IsNullOrEmpty(providerId);
foreach (var movie in plexMovies) foreach (var movie in plexMovies)
{ {
if (string.IsNullOrEmpty(movie.Title) || string.IsNullOrEmpty(movie.ReleaseYear))
{
continue;
}
if (advanced) if (advanced)
{ {
if (!string.IsNullOrEmpty(movie.ProviderId) && if (!string.IsNullOrEmpty(movie.ProviderId) &&

View file

@ -27,7 +27,7 @@ namespace PlexRequests.Store
public string Status { get; set; } public string Status { get; set; }
public bool Approved { get; set; } public bool Approved { get; set; }
[Obsolete("Use RequestedUsers")] [Obsolete("Use RequestedUsers")] //TODO remove this obsolete property
public string RequestedBy { get; set; } public string RequestedBy { get; set; }
public DateTime RequestedDate { get; set; } public DateTime RequestedDate { get; set; }
@ -44,6 +44,8 @@ namespace PlexRequests.Store
public string ArtistId { get; set; } public string ArtistId { get; set; }
public int IssueId { get; set; } public int IssueId { get; set; }
public List<EpisodesModel> Episodes { get; set; } public List<EpisodesModel> Episodes { get; set; }
public bool Denied { get; set; }
public string DeniedReason { get; set; }
[JsonIgnore] [JsonIgnore]
public List<string> AllUsers public List<string> AllUsers

View file

@ -373,6 +373,17 @@ $('#noteModal').on('show.bs.modal', function (event) {
requestField.val(id); // Add ID to the hidden field requestField.val(id); // Add ID to the hidden field
}); });
// Update deny reason modal
$('#denyReasonModal').on('show.bs.modal', function (event) {
var button = $(event.relatedTarget); // Button that triggered the modal
var id = button.data('identifier'); // Extract info from data-* attributes
var modal = $(this);
modal.find('.denySaveReason').val(id); // Add ID to the button
var requestField = modal.find('input');
requestField.val(id); // Add ID to the hidden field
});
// Delete // Delete
$(document).on("click", ".delete", function (e) { $(document).on("click", ".delete", function (e) {
e.preventDefault(); e.preventDefault();
@ -403,28 +414,85 @@ $(document).on("click", ".delete", function (e) {
// Approve single request // Approve single request
$(document).on("click", ".approve", function (e) { $(document).on("click", ".approve", function (e) {
e.preventDefault(); e.preventDefault();
var $this = $(this); var $self = $(this);
var $form = $this.parents('form').first(); var $form = $self.parents('form').first();
if ($this.text() === " Loading...") { if ($self.text() === " Loading...") {
return; return;
} }
loadingButton($this.attr('id'), "success"); loadingButton($self.attr('id'), "success");
approveRequest($form, null, function () { approveRequest($form, null, function () {
$("#" + $this.attr('id') + "notapproved").prop("class", "fa fa-check"); $("#" + $self.attr('id') + "notapproved").prop("class", "fa fa-check");
var $group = $this.parent('.btn-split');
var $group = $self.parent('.btn-split');
if ($group.length > 0) { if ($group.length > 0) {
$group.remove(); $group.remove();
} }
else { else {
$this.remove(); $self.remove();
} }
}); });
}); });
// Deny single request
$(document).on("click", ".deny", function (e) {
e.preventDefault();
var $self = $(this);
var $form = $self.parents('form').first();
if ($self.text() === " Loading...") {
return;
}
loadingButton($self.attr('id')+"deny", "success");
denyRequest($form, function () {
// Remove the form
$("#" + "deny" + $self.attr('id')).remove();
// remove the approve button
var id = $self.attr("custom-button");
$("#" + id).remove();
var $group = $self.parent('.btn-split');
if ($group.length > 0) {
$group.remove();
}
else {
$self.remove();
}
});
});
// Deny single request with reason (modal)
$(document).on("click", ".denySaveReason", function (e) {
var comment = $("#denyReason").val();
e.preventDefault();
var $form = $("#denyReasonForm");
var data = $form.serialize();
data = data + "&reason=" + comment;
$.ajax({
type: $form.prop("method"),
url: $form.prop("action"),
data: data,
dataType: "json",
success: function (response) {
if (checkJsonResponse(response)) {
generateNotify(response.message, "success");
$("#denyReasonModal").modal("hide");
}
},
error: function (e) {
console.log(e);
generateNotify("Something went wrong!", "danger");
}
});
});
$(document).on("click", ".approve-with-quality", function (e) { $(document).on("click", ".approve-with-quality", function (e) {
e.preventDefault(); e.preventDefault();
var $this = $(this); var $this = $(this);
@ -524,6 +592,35 @@ function approveRequest($form, qualityId, successCallback) {
}); });
} }
function denyRequest($form, successCallback) {
var formData = $form.serialize();
$.ajax({
type: $form.prop('method'),
url: $form.prop('action'),
data: formData,
dataType: "json",
success: function (response) {
if (checkJsonResponse(response)) {
if (response.message) {
generateNotify(response.message, "success");
} else {
generateNotify("Success! Request Approved.", "success");
}
if (successCallback) {
successCallback();
}
}
},
error: function (e) {
console.log(e);
generateNotify("Something went wrong!", "danger");
}
});
}
function mixItUpConfig(activeState) { function mixItUpConfig(activeState) {
var conf = mixItUpDefault; var conf = mixItUpDefault;
@ -556,6 +653,11 @@ function movieLoad() {
var html = searchTemplate(context); var html = searchTemplate(context);
$ml.append(html); $ml.append(html);
}); });
$('.customTooltip').tooltipster({
contentCloning: true
});
} }
else { else {
$ml.html(noResultsHtml.format("movie")); $ml.html(noResultsHtml.format("movie"));
@ -658,6 +760,8 @@ function buildRequestContext(result, type) {
hasQualities: result.qualities && result.qualities.length > 0, hasQualities: result.qualities && result.qualities.length > 0,
artist: result.artistName, artist: result.artistName,
musicBrainzId: result.musicBrainzId, musicBrainzId: result.musicBrainzId,
denied: result.denied,
deniedReason: result.deniedReason,
}; };
return context; return context;

View file

@ -467,7 +467,8 @@ $(function () {
available: result.available, available: result.available,
episodes: result.episodes, episodes: result.episodes,
tvFullyAvailable: result.tvFullyAvailable, tvFullyAvailable: result.tvFullyAvailable,
url: result.plexUrl url: result.plexUrl,
tvPartialAvailable : result.tvPartialAvailable
}; };
return context; return context;
} }

View file

@ -35,6 +35,7 @@ using PlexRequests.Api.Models.Sonarr;
using PlexRequests.Core.SettingModels; using PlexRequests.Core.SettingModels;
using PlexRequests.Store; using PlexRequests.Store;
using System.Linq; using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using PlexRequests.Helpers.Exceptions; using PlexRequests.Helpers.Exceptions;
@ -93,7 +94,7 @@ namespace PlexRequests.UI.Helpers
// Get the series that was just added // Get the series that was just added
series = await GetSonarrSeries(sonarrSettings, model.ProviderId); series = await GetSonarrSeries(sonarrSettings, model.ProviderId);
series.monitored = false; // Un-monitor the series series.monitored = true; // We want to make sure we are monitoring the series
// Un-monitor all seasons // Un-monitor all seasons
foreach (var season in series.seasons) foreach (var season in series.seasons)
@ -112,6 +113,12 @@ namespace PlexRequests.UI.Helpers
} }
if (series != null) if (series != null)
{
var requestAll = model.SeasonsRequested.Equals("All", StringComparison.CurrentCultureIgnoreCase);
var first = model.SeasonsRequested.Equals("First", StringComparison.CurrentCultureIgnoreCase);
var latest = model.SeasonsRequested.Equals("Latest", StringComparison.CurrentCultureIgnoreCase);
if (model.SeasonList.Any())
{ {
// Monitor the seasons that we have chosen // Monitor the seasons that we have chosen
foreach (var season in series.seasons) foreach (var season in series.seasons)
@ -121,6 +128,29 @@ namespace PlexRequests.UI.Helpers
season.monitored = true; season.monitored = true;
} }
} }
}
if (requestAll)
{
// Monitor all seasons
foreach (var season in series.seasons)
{
season.monitored = true;
}
}
if (first)
{
var firstSeries = series?.seasons?.OrderBy(x => x.seasonNumber)?.FirstOrDefault() ?? new Season();
firstSeries.monitored = true;
}
if (latest)
{
var lastSeries = series?.seasons?.OrderByDescending(x => x.seasonNumber)?.FirstOrDefault() ?? new Season();
lastSeries.monitored = true;
}
// Update the series in sonarr with the new monitored status // Update the series in sonarr with the new monitored status
SonarrApi.UpdateSeries(series, sonarrSettings.ApiKey, sonarrSettings.FullUri); SonarrApi.UpdateSeries(series, sonarrSettings.ApiKey, sonarrSettings.FullUri);
@ -230,9 +260,22 @@ namespace PlexRequests.UI.Helpers
var internalEpisodeIds = new List<int>(); var internalEpisodeIds = new List<int>();
var tasks = new List<Task>(); var tasks = new List<Task>();
var requestedEpisodes = model.Episodes;
foreach (var r in episodes) foreach (var r in episodes)
{ {
if (r.hasFile || !model.SeasonList.Contains(r.seasonNumber)) // If it already has the file, there is no point in updating it if (r.hasFile) // If it already has the file, there is no point in updating it
{
continue;
}
var epComparison = new EpisodesModel
{
EpisodeNumber = r.episodeNumber,
SeasonNumber = r.seasonNumber
};
// Make sure we are looking for the right episode and season
if (!requestedEpisodes.Contains(epComparison))
{ {
continue; continue;
} }

View file

@ -55,5 +55,7 @@ namespace PlexRequests.UI.Models
public QualityModel[] Qualities { get; set; } public QualityModel[] Qualities { get; set; }
public string ArtistName { get; set; } public string ArtistName { get; set; }
public Store.EpisodesModel[] Episodes { get; set; } public Store.EpisodesModel[] Episodes { get; set; }
public bool Denied { get; set; }
public string DeniedReason { get; set; }
} }
} }

View file

@ -175,16 +175,16 @@ namespace PlexRequests.UI.Modules
Get["/emailnotification"] = _ => EmailNotifications(); Get["/emailnotification"] = _ => EmailNotifications();
Post["/emailnotification"] = _ => SaveEmailNotifications(); Post["/emailnotification"] = _ => SaveEmailNotifications();
Post["/testemailnotification"] = _ => TestEmailNotifications(); Post["/testemailnotification", true] = async (x, ct) => await TestEmailNotifications();
Get["/status", true] = async (x, ct) => await Status(); Get["/status", true] = async (x, ct) => await Status();
Get["/pushbulletnotification"] = _ => PushbulletNotifications(); Get["/pushbulletnotification"] = _ => PushbulletNotifications();
Post["/pushbulletnotification"] = _ => SavePushbulletNotifications(); Post["/pushbulletnotification"] = _ => SavePushbulletNotifications();
Post["/testpushbulletnotification"] = _ => TestPushbulletNotifications(); Post["/testpushbulletnotification", true] = async (x, ct) => await TestPushbulletNotifications();
Get["/pushovernotification"] = _ => PushoverNotifications(); Get["/pushovernotification"] = _ => PushoverNotifications();
Post["/pushovernotification"] = _ => SavePushoverNotifications(); Post["/pushovernotification"] = _ => SavePushoverNotifications();
Post["/testpushovernotification"] = _ => TestPushoverNotifications(); Post["/testpushovernotification", true] = async (x, ct) => await TestPushoverNotifications();
Get["/logs"] = _ => Logs(); Get["/logs"] = _ => Logs();
Get["/loglevel"] = _ => GetLogLevels(); Get["/loglevel"] = _ => GetLogLevels();
@ -198,7 +198,7 @@ namespace PlexRequests.UI.Modules
Post["/autoupdate"] = x => AutoUpdate(); Post["/autoupdate"] = x => AutoUpdate();
Post["/testslacknotification"] = _ => TestSlackNotification(); Post["/testslacknotification", true] = async (x,ct) => await TestSlackNotification();
Get["/slacknotification"] = _ => SlackNotifications(); Get["/slacknotification"] = _ => SlackNotifications();
Post["/slacknotification"] = _ => SaveSlackNotifications(); Post["/slacknotification"] = _ => SaveSlackNotifications();
@ -477,7 +477,7 @@ namespace PlexRequests.UI.Modules
return View["EmailNotifications", settings]; return View["EmailNotifications", settings];
} }
private Response TestEmailNotifications() private async Task<Response> TestEmailNotifications()
{ {
var settings = this.Bind<EmailNotificationSettings>(); var settings = this.Bind<EmailNotificationSettings>();
var valid = this.Validate(settings); var valid = this.Validate(settings);
@ -485,6 +485,7 @@ namespace PlexRequests.UI.Modules
{ {
return Response.AsJson(valid.SendJsonError()); return Response.AsJson(valid.SendJsonError());
} }
var currentSettings = await EmailService.GetSettingsAsync();
var notificationModel = new NotificationModel var notificationModel = new NotificationModel
{ {
NotificationType = NotificationType.Test, NotificationType = NotificationType.Test,
@ -502,9 +503,12 @@ namespace PlexRequests.UI.Modules
Log.Error("Failed to subscribe and publish test Email Notification"); Log.Error("Failed to subscribe and publish test Email Notification");
} }
finally finally
{
if (!currentSettings.Enabled)
{ {
NotificationService.UnSubscribe(new EmailMessageNotification(EmailService)); NotificationService.UnSubscribe(new EmailMessageNotification(EmailService));
} }
}
return Response.AsJson(new JsonResponseModel { Result = true, Message = "Successfully sent a test Email Notification!" }); return Response.AsJson(new JsonResponseModel { Result = true, Message = "Successfully sent a test Email Notification!" });
} }
@ -595,7 +599,7 @@ namespace PlexRequests.UI.Modules
: new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." }); : new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." });
} }
private Response TestPushbulletNotifications() private async Task<Response> TestPushbulletNotifications()
{ {
var settings = this.Bind<PushbulletNotificationSettings>(); var settings = this.Bind<PushbulletNotificationSettings>();
var valid = this.Validate(settings); var valid = this.Validate(settings);
@ -608,6 +612,7 @@ namespace PlexRequests.UI.Modules
NotificationType = NotificationType.Test, NotificationType = NotificationType.Test,
DateTime = DateTime.Now DateTime = DateTime.Now
}; };
var currentSettings = await PushbulletService.GetSettingsAsync();
try try
{ {
NotificationService.Subscribe(new PushbulletNotification(PushbulletApi, PushbulletService)); NotificationService.Subscribe(new PushbulletNotification(PushbulletApi, PushbulletService));
@ -620,9 +625,12 @@ namespace PlexRequests.UI.Modules
Log.Error("Failed to subscribe and publish test Pushbullet Notification"); Log.Error("Failed to subscribe and publish test Pushbullet Notification");
} }
finally finally
{
if (!currentSettings.Enabled)
{ {
NotificationService.UnSubscribe(new PushbulletNotification(PushbulletApi, PushbulletService)); NotificationService.UnSubscribe(new PushbulletNotification(PushbulletApi, PushbulletService));
} }
}
return Response.AsJson(new JsonResponseModel { Result = true, Message = "Successfully sent a test Pushbullet Notification!" }); return Response.AsJson(new JsonResponseModel { Result = true, Message = "Successfully sent a test Pushbullet Notification!" });
} }
@ -657,7 +665,7 @@ namespace PlexRequests.UI.Modules
: new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." }); : new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." });
} }
private Response TestPushoverNotifications() private async Task<Response> TestPushoverNotifications()
{ {
var settings = this.Bind<PushoverNotificationSettings>(); var settings = this.Bind<PushoverNotificationSettings>();
var valid = this.Validate(settings); var valid = this.Validate(settings);
@ -670,6 +678,7 @@ namespace PlexRequests.UI.Modules
NotificationType = NotificationType.Test, NotificationType = NotificationType.Test,
DateTime = DateTime.Now DateTime = DateTime.Now
}; };
var currentSettings = await PushbulletService.GetSettingsAsync();
try try
{ {
NotificationService.Subscribe(new PushoverNotification(PushoverApi, PushoverService)); NotificationService.Subscribe(new PushoverNotification(PushoverApi, PushoverService));
@ -682,9 +691,12 @@ namespace PlexRequests.UI.Modules
Log.Error("Failed to subscribe and publish test Pushover Notification"); Log.Error("Failed to subscribe and publish test Pushover Notification");
} }
finally finally
{
if (!currentSettings.Enabled)
{ {
NotificationService.UnSubscribe(new PushoverNotification(PushoverApi, PushoverService)); NotificationService.UnSubscribe(new PushoverNotification(PushoverApi, PushoverService));
} }
}
return Response.AsJson(new JsonResponseModel { Result = true, Message = "Successfully sent a test Pushover Notification!" }); return Response.AsJson(new JsonResponseModel { Result = true, Message = "Successfully sent a test Pushover Notification!" });
} }
@ -803,7 +815,7 @@ namespace PlexRequests.UI.Modules
return Response.AsJson(apiKey); return Response.AsJson(apiKey);
} }
private Response TestSlackNotification() private async Task<Response> TestSlackNotification()
{ {
var settings = this.BindAndValidate<SlackNotificationSettings>(); var settings = this.BindAndValidate<SlackNotificationSettings>();
if (!ModelValidationResult.IsValid) if (!ModelValidationResult.IsValid)
@ -815,11 +827,13 @@ namespace PlexRequests.UI.Modules
NotificationType = NotificationType.Test, NotificationType = NotificationType.Test,
DateTime = DateTime.Now DateTime = DateTime.Now
}; };
var currentSlackSettings = await SlackSettings.GetSettingsAsync();
try try
{ {
NotificationService.Subscribe(new SlackNotification(SlackApi, SlackSettings)); NotificationService.Subscribe(new SlackNotification(SlackApi, SlackSettings));
settings.Enabled = true; settings.Enabled = true;
NotificationService.Publish(notificationModel, settings); await NotificationService.Publish(notificationModel, settings);
Log.Info("Sent slack notification test"); Log.Info("Sent slack notification test");
} }
catch (Exception e) catch (Exception e)
@ -827,9 +841,12 @@ namespace PlexRequests.UI.Modules
Log.Error(e, "Failed to subscribe and publish test Slack Notification"); Log.Error(e, "Failed to subscribe and publish test Slack Notification");
} }
finally finally
{
if (!currentSlackSettings.Enabled)
{ {
NotificationService.UnSubscribe(new SlackNotification(SlackApi, SlackSettings)); NotificationService.UnSubscribe(new SlackNotification(SlackApi, SlackSettings));
} }
}
return Response.AsJson(new JsonResponseModel { Result = true, Message = "Successfully sent a test Slack Notification! If you do not receive it please check the logs." }); return Response.AsJson(new JsonResponseModel { Result = true, Message = "Successfully sent a test Slack Notification! If you do not receive it please check the logs." });
} }

View file

@ -36,6 +36,7 @@ using Newtonsoft.Json;
using PlexRequests.Core; using PlexRequests.Core;
using PlexRequests.Core.SettingModels; using PlexRequests.Core.SettingModels;
using PlexRequests.Helpers;
namespace PlexRequests.UI.Modules namespace PlexRequests.UI.Modules
{ {
@ -45,6 +46,9 @@ namespace PlexRequests.UI.Modules
ISettingsService<PlexSettings> plexSettings, ISettingsService<CouchPotatoSettings> cp, ISettingsService<PlexSettings> plexSettings, ISettingsService<CouchPotatoSettings> cp,
ISettingsService<SonarrSettings> sonarr, ISettingsService<SickRageSettings> sr, ISettingsService<HeadphonesSettings> hp) : base("api", pr) ISettingsService<SonarrSettings> sonarr, ISettingsService<SickRageSettings> sr, ISettingsService<HeadphonesSettings> hp) : base("api", pr)
{ {
Get["GetVersion", "/version"] = x => GetVersion();
Get["GetAuthSettings", "/settings/authentication"] = x => GetAuthSettings(); Get["GetAuthSettings", "/settings/authentication"] = x => GetAuthSettings();
Post["PostAuthSettings", "/settings/authentication"] = x => PostAuthSettings(); Post["PostAuthSettings", "/settings/authentication"] = x => PostAuthSettings();
@ -83,6 +87,12 @@ namespace PlexRequests.UI.Modules
private ISettingsService<SickRageSettings> SickRageSettings { get; } private ISettingsService<SickRageSettings> SickRageSettings { get; }
private ISettingsService<HeadphonesSettings> HeadphonesSettings { get; } private ISettingsService<HeadphonesSettings> HeadphonesSettings { get; }
private Response GetVersion()
{
return ReturnReponse(AssemblyHelper.GetProductVersion());
}
private Response GetPrSettings() private Response GetPrSettings()
{ {
var model = new ApiModel<PlexRequestSettings>(); var model = new ApiModel<PlexRequestSettings>();

View file

@ -65,6 +65,7 @@ namespace PlexRequests.UI.Modules
HeadphoneApi = hpApi; HeadphoneApi = hpApi;
Post["/approve", true] = async (x, ct) => await Approve((int)Request.Form.requestid, (string)Request.Form.qualityId); Post["/approve", true] = async (x, ct) => await Approve((int)Request.Form.requestid, (string)Request.Form.qualityId);
Post["/deny", true] = async (x, ct) => await DenyRequest((int)Request.Form.requestid, (string)Request.Form.reason);
Post["/approveall", true] = async (x, ct) => await ApproveAll(); Post["/approveall", true] = async (x, ct) => await ApproveAll();
Post["/approveallmovies", true] = async (x, ct) => await ApproveAllMovies(); Post["/approveallmovies", true] = async (x, ct) => await ApproveAllMovies();
Post["/approvealltvshows", true] = async (x, ct) => await ApproveAllTVShows(); Post["/approvealltvshows", true] = async (x, ct) => await ApproveAllTVShows();
@ -491,6 +492,24 @@ namespace PlexRequests.UI.Modules
} }
} }
private async Task<Response> DenyRequest(int requestId, string reason)
{
// Get the request from the DB
var request = await Service.GetAsync(requestId);
// Deny it
request.Denied = true;
request.DeniedReason = reason;
// Update the new value
var result = await Service.UpdateRequestAsync(request);
return result
? Response.AsJson(new JsonResponseModel { Result = true, Message = "Request has been denied" })
: Response.AsJson(new JsonResponseModel { Result = false, Message = "An error happened, could not update the DB" });
}
private bool SendMovie(CouchPotatoSettings settings, RequestedModel r, ICouchPotatoApi cp) private bool SendMovie(CouchPotatoSettings settings, RequestedModel r, ICouchPotatoApi cp)
{ {
Log.Info("Adding movie to CP : {0}", r.Title); Log.Info("Adding movie to CP : {0}", r.Title);

View file

@ -53,14 +53,15 @@ namespace PlexRequests.UI.Modules
private Response CheckAuth() private Response CheckAuth()
{ {
var settings = PlexRequestSettings.GetSettings(); var settings = PlexRequestSettings.GetSettings();
var baseUrl = settings.BaseUrl;
// Have we been through the wizard? // Have we been through the wizard?
if (!settings.Wizard) if (!settings.Wizard)
{ {
return Context.GetRedirect("~/wizard"); return Context.GetRedirect(string.IsNullOrEmpty(baseUrl) ? "~/wizard" : $"~/{baseUrl}/wizard");
} }
var baseUrl = settings.BaseUrl;
var redirectPath = string.IsNullOrEmpty(baseUrl) ? "~/userlogin" : $"~/{baseUrl}/userlogin"; var redirectPath = string.IsNullOrEmpty(baseUrl) ? "~/userlogin" : $"~/{baseUrl}/userlogin";
return Session[SessionKeys.UsernameKey] == null return Session[SessionKeys.UsernameKey] == null

View file

@ -1,4 +1,5 @@
#region Copyright #region Copyright
// /************************************************************************ // /************************************************************************
// Copyright (c) 2016 Jamie Rees // Copyright (c) 2016 Jamie Rees
// File: LoginModule.cs // File: LoginModule.cs
@ -23,10 +24,12 @@
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/ // ************************************************************************/
#endregion #endregion
using System; using System;
using System.Dynamic; using System.Dynamic;
using System.Security;
using Nancy; using Nancy;
using Nancy.Authentication.Forms; using Nancy.Authentication.Forms;
using Nancy.Extensions; using Nancy.Extensions;
@ -43,7 +46,8 @@ namespace PlexRequests.UI.Modules
{ {
public class LoginModule : BaseModule public class LoginModule : BaseModule
{ {
public LoginModule(ISettingsService<PlexRequestSettings> pr, ICustomUserMapper m, IResourceLinker linker) : base(pr) public LoginModule(ISettingsService<PlexRequestSettings> pr, ICustomUserMapper m, IResourceLinker linker)
: base(pr)
{ {
UserMapper = m; UserMapper = m;
Get["/login"] = _ => Get["/login"] = _ =>
@ -61,7 +65,14 @@ namespace PlexRequests.UI.Modules
return View["Index", model]; return View["Index", model];
}; };
Get["/logout"] = x => this.LogoutAndRedirect(!string.IsNullOrEmpty(BaseUrl) ? $"~/{BaseUrl}/" : "~/"); Get["/logout"] = x =>
{
if (Session[SessionKeys.UsernameKey] != null)
{
Session.Delete(SessionKeys.UsernameKey);
}
return this.LogoutAndRedirect(!string.IsNullOrEmpty(BaseUrl) ? $"~/{BaseUrl}/" : "~/");
};
Post["/login"] = x => Post["/login"] = x =>
{ {
@ -74,7 +85,10 @@ namespace PlexRequests.UI.Modules
if (userId == null) if (userId == null)
{ {
return Context.GetRedirect(!string.IsNullOrEmpty(BaseUrl) ? $"~/{BaseUrl}/login?error=true&username=" + username : "~/login?error=true&username=" + username); return
Context.GetRedirect(!string.IsNullOrEmpty(BaseUrl)
? $"~/{BaseUrl}/login?error=true&username=" + username
: "~/login?error=true&username=" + username);
} }
DateTime? expiry = null; DateTime? expiry = null;
if (Request.Form.RememberMe.HasValue) if (Request.Form.RememberMe.HasValue)
@ -106,7 +120,10 @@ namespace PlexRequests.UI.Modules
var exists = UserMapper.DoUsersExist(); var exists = UserMapper.DoUsersExist();
if (exists) if (exists)
{ {
return Context.GetRedirect(!string.IsNullOrEmpty(BaseUrl) ? $"~/{BaseUrl}/register?error=true" : "~/register?error=true"); return
Context.GetRedirect(!string.IsNullOrEmpty(BaseUrl)
? $"~/{BaseUrl}/register?error=true"
: "~/register?error=true");
} }
var userId = UserMapper.CreateAdmin(username, Request.Form.Password); var userId = UserMapper.CreateAdmin(username, Request.Form.Password);
Session[SessionKeys.UsernameKey] = username; Session[SessionKeys.UsernameKey] = username;
@ -116,6 +133,7 @@ namespace PlexRequests.UI.Modules
Get["/changepassword"] = _ => ChangePassword(); Get["/changepassword"] = _ => ChangePassword();
Post["/changepassword"] = _ => ChangePasswordPost(); Post["/changepassword"] = _ => ChangePasswordPost();
} }
private ICustomUserMapper UserMapper { get; } private ICustomUserMapper UserMapper { get; }
private Negotiator ChangePassword() private Negotiator ChangePassword()
@ -141,7 +159,8 @@ namespace PlexRequests.UI.Modules
{ {
return Response.AsJson(new JsonResponseModel { Message = "The passwords do not match", Result = false }); return Response.AsJson(new JsonResponseModel { Message = "The passwords do not match", Result = false });
} }
try
{
var result = UserMapper.UpdatePassword(username, oldPass, newPassword); var result = UserMapper.UpdatePassword(username, oldPass, newPassword);
if (result) if (result)
{ {
@ -150,5 +169,10 @@ namespace PlexRequests.UI.Modules
return Response.AsJson(new JsonResponseModel { Message = "Could not update the password in the database", Result = false }); return Response.AsJson(new JsonResponseModel { Message = "Could not update the password in the database", Result = false });
} }
catch (SecurityException e)
{
return Response.AsJson(new JsonResponseModel { Message = e.ToString(), Result = false });
}
}
} }
} }

View file

@ -178,6 +178,8 @@ namespace PlexRequests.UI.Modules
Available = movie.Available, Available = movie.Available,
Admin = IsAdmin, Admin = IsAdmin,
IssueId = movie.IssueId, IssueId = movie.IssueId,
Denied = movie.Denied,
DeniedReason = movie.DeniedReason,
Qualities = qualities.ToArray() Qualities = qualities.ToArray()
}).ToList(); }).ToList();
@ -249,6 +251,8 @@ namespace PlexRequests.UI.Modules
Available = tv.Available, Available = tv.Available,
Admin = IsAdmin, Admin = IsAdmin,
IssueId = tv.IssueId, IssueId = tv.IssueId,
Denied = tv.Denied,
DeniedReason = tv.DeniedReason,
TvSeriesRequestType = tv.SeasonsRequested, TvSeriesRequestType = tv.SeasonsRequested,
Qualities = qualities.ToArray(), Qualities = qualities.ToArray(),
Episodes = tv.Episodes.ToArray(), Episodes = tv.Episodes.ToArray(),
@ -290,6 +294,8 @@ namespace PlexRequests.UI.Modules
Available = album.Available, Available = album.Available,
Admin = IsAdmin, Admin = IsAdmin,
IssueId = album.IssueId, IssueId = album.IssueId,
Denied = album.Denied,
DeniedReason = album.DeniedReason,
TvSeriesRequestType = album.SeasonsRequested, TvSeriesRequestType = album.SeasonsRequested,
MusicBrainzId = album.MusicBrainzId, MusicBrainzId = album.MusicBrainzId,
ArtistName = album.ArtistName ArtistName = album.ArtistName

View file

@ -657,7 +657,11 @@ namespace PlexRequests.UI.Modules
return await AddUserToRequest(existingRequest, settings, fullShowName, true); return await AddUserToRequest(existingRequest, settings, fullShowName, true);
} }
// We have an episode that has not yet been requested, let's continue else
{
// We no episodes to approve
return Response.AsJson(new JsonResponseModel { Result = false, Message = $"{fullShowName} {Resources.UI.Search_AlreadyInPlex}" });
}
} }
else if (model.SeasonList.Except(existingRequest.SeasonList).Any()) else if (model.SeasonList.Except(existingRequest.SeasonList).Any())
{ {
@ -1033,7 +1037,8 @@ namespace PlexRequests.UI.Modules
Name = ep.name, Name = ep.name,
EpisodeId = ep.id EpisodeId = ep.id
}); });
}return model; }
return model;
} }

View file

@ -216,7 +216,7 @@ namespace PlexRequests.UI.Modules
private bool IsUserInDeniedList(string username, AuthenticationSettings settings) private bool IsUserInDeniedList(string username, AuthenticationSettings settings)
{ {
return settings.DeniedUserList.Any(x => x.Equals(username)); return settings.DeniedUserList.Any(x => x.Equals(username, StringComparison.CurrentCultureIgnoreCase));
} }
} }
} }

View file

@ -64,6 +64,7 @@ namespace PlexRequests.UI.Modules
a.TrackEventAsync(Category.Wizard, Action.Start, "Started the wizard", Username, CookieHelper.GetAnalyticClientId(Cookies)); a.TrackEventAsync(Category.Wizard, Action.Start, "Started the wizard", Username, CookieHelper.GetAnalyticClientId(Cookies));
var settings = await PlexRequestSettings.GetSettingsAsync(); var settings = await PlexRequestSettings.GetSettingsAsync();
if (settings.Wizard) if (settings.Wizard)
{ {
return Context.GetRedirect("~/search"); return Context.GetRedirect("~/search");

View file

@ -171,7 +171,7 @@
generateNotify("Success!", "success"); generateNotify("Success!", "success");
$('#ApiKey').val(response.apiKey); $('#ApiKey').val(response.apiKey);
} else { } else {
generateNotify(response.message, "warning"); generateNotify("Could not automatically get the API key", "warning");
} }
}, },
error: function (e) { error: function (e) {

View file

@ -54,7 +54,7 @@
<p class="form-group">Notice Message</p> <p class="form-group">Notice Message</p>
<div class="form-group"> <div class="form-group">
<div> <div>
<input type="text" class="form-control-custom form-control " id="NoticeMessage" name="NoticeMessage" placeholder="e.g. Plex will be down for maintaince" value="@Model.NoticeMessage"> <textarea rows="4" type="text" class="form-control-custom form-control " id="NoticeMessage" name="NoticeMessage" placeholder="e.g. Plex will be down for maintaince (HTML is allowed)" value="@Model.NoticeMessage"></textarea>
</div> </div>
</div> </div>

View file

@ -1,4 +1,5 @@
@using Nancy.Security @using Nancy.Security
@using Nancy.Security
@using PlexRequests.UI.Helpers @using PlexRequests.UI.Helpers
@using PlexRequests.UI.Resources @using PlexRequests.UI.Resources
@{ @{
@ -170,12 +171,21 @@
<span class="label label-success">{{status}}</span> <span class="label label-success">{{status}}</span>
</div> </div>
<br /> <br />
{{#if denied}}
<div>
Denied: <i style="color:red;" class="fa fa-check"></i>
{{#if deniedReason}}
<span class="customTooltip" title="{{deniedReason}}"><i class="fa fa-info-circle"></i></span>
{{/if}}
</div>
{{/if}}
{{#if_eq releaseDate "01/01/0001 00:00:00"}} <!--TheTVDB didn't provide any premier info--> {{#if_eq releaseDate "01/01/0001 00:00:00"}} <!--TheTVDB didn't provide any premier info-->
<div>@UI.Requests_ReleaseDate: {{releaseDate}}</div> <div>@UI.Requests_ReleaseDate: {{releaseDate}}</div>
{{else}} {{else}}
<div>@UI.Requests_ReleaseDate: {{releaseDate}}</div> <div>@UI.Requests_ReleaseDate: {{releaseDate}}</div>
{{/if_eq}} {{/if_eq}}
{{#unless denied}}
<div> <div>
@UI.Common_Approved: @UI.Common_Approved:
{{#if_eq approved false}} {{#if_eq approved false}}
@ -185,6 +195,7 @@
<i class="fa fa-check"></i> <i class="fa fa-check"></i>
{{/if_eq}} {{/if_eq}}
</div> </div>
{{/unless}}
<div> <div>
@UI.Requests_Available @UI.Requests_Available
{{#if_eq available false}} {{#if_eq available false}}
@ -216,6 +227,7 @@
</div> </div>
<div class="col-sm-3 col-sm-push-3"> <div class="col-sm-3 col-sm-push-3">
{{#if_eq admin true}} {{#if_eq admin true}}
{{#if_eq approved false}} {{#if_eq approved false}}
<form method="POST" action="@formAction/approval/approve" id="approve{{requestId}}"> <form method="POST" action="@formAction/approval/approve" id="approve{{requestId}}">
<input name="requestId" type="text" value="{{requestId}}" hidden="hidden" /> <input name="requestId" type="text" value="{{requestId}}" hidden="hidden" />
@ -236,6 +248,22 @@
<button id="{{requestId}}" custom-button="{{requestId}}" style="text-align: right" class="btn btn-sm btn-success-outline approve" type="submit"><i class="fa fa-plus"></i> @UI.Common_Approve</button> <button id="{{requestId}}" custom-button="{{requestId}}" style="text-align: right" class="btn btn-sm btn-success-outline approve" type="submit"><i class="fa fa-plus"></i> @UI.Common_Approve</button>
{{/if_eq}} {{/if_eq}}
</form> </form>
{{#unless denied}}
<form method="POST" action="@formAction/approval/deny" id="deny{{requestId}}">
<input name="requestId" type="text" value="{{requestId}}" hidden="hidden" />
<input name="reason" type="text" hidden="hidden" />
<div class="btn-group btn-split">
<button type="button" class="btn btn-sm btn-danger-outline deny" id="{{requestId}}deny" custom-button="{{requestId}}"><i class="fa fa-times"></i> Deny</button>
<button type="button" class="btn btn-danger-outline dropdown-toggle" id="{{requestId}}denyToggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="caret"></span>
<span class="sr-only">@UI.Requests_ToggleDropdown</span>
</button>
<ul class="dropdown-menu">
<li><a class="deny-with-reason" id="denyReason{{requestId}}" href="#" data-toggle="modal" data-identifier="{{requestId}}" data-target="#denyReasonModal">Deny with a reason</a></li>
</ul>
</div>
</form>
{{/unless}}
{{/if_eq}} {{/if_eq}}
<form method="POST" action="@formAction/requests/delete" id="delete{{requestId}}"> <form method="POST" action="@formAction/requests/delete" id="delete{{requestId}}">
<input name="Id" type="text" value="{{requestId}}" hidden="hidden" /> <input name="Id" type="text" value="{{requestId}}" hidden="hidden" />
@ -324,6 +352,12 @@
<div>@UI.Requests_RequestedBy: {{requestedUsers}}</div> <div>@UI.Requests_RequestedBy: {{requestedUsers}}</div>
{{/if}} {{/if}}
<div>@UI.Requests_RequestedDate: {{requestedDate}}</div> <div>@UI.Requests_RequestedDate: {{requestedDate}}</div>
{{#if denied}}
<div>Denied: <i class="fa fa-times"></i></div>
{{#if deniedReason}}
<div>Reason: {{deniedReason}}</div>
{{/if}}
{{/if}}
</div> </div>
<div class="col-sm-2 col-sm-push-3"> <div class="col-sm-2 col-sm-push-3">
{{#if_eq admin true}} {{#if_eq admin true}}
@ -332,6 +366,20 @@
<input name="requestId" type="text" value="{{requestId}}" hidden="hidden" /> <input name="requestId" type="text" value="{{requestId}}" hidden="hidden" />
<button id="{{requestId}}" custom-button="{{requestId}}" style="text-align: right" class="btn btn-sm btn-success-outline approve" type="submit"><i class="fa fa-plus"></i> @UI.Common_Approve</button> <button id="{{requestId}}" custom-button="{{requestId}}" style="text-align: right" class="btn btn-sm btn-success-outline approve" type="submit"><i class="fa fa-plus"></i> @UI.Common_Approve</button>
</form> </form>
<form method="POST" action="@formAction/approval/deny" id="deny{{requestId}}">
<input name="requestId" type="text" value="{{requestId}}" hidden="hidden" />
<input name="reason" type="text" hidden="hidden" />
<div class="btn-group btn-split">
<button type="button" class="btn btn-sm btn-danger-outline deny" id="{{requestId}}deny" custom-button="{{requestId}}"><i class="fa fa-times"></i> Deny</button>
<button type="button" class="btn btn-danger-outline dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="caret"></span>
<span class="sr-only">@UI.Requests_ToggleDropdown</span>
</button>
<ul class="dropdown-menu">
<li><a class="deny-with-reason" id="denyReason{{requestId}}" href="#" data-toggle="modal" data-identifier="{{requestId}}" data-target="#denyReasonModal">Deny with a reason</a></li>
</ul>
</div>
</form>
{{/if_eq}} {{/if_eq}}
<form method="POST" action="@formAction/requests/delete" id="delete{{requestId}}"> <form method="POST" action="@formAction/requests/delete" id="delete{{requestId}}">
<input name="Id" type="text" value="{{requestId}}" hidden="hidden" /> <input name="Id" type="text" value="{{requestId}}" hidden="hidden" />
@ -393,6 +441,27 @@
</div> </div>
</div> </div>
<div class="modal fade" id="denyReasonModal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true"><i class="fa fa-times"></i></button>
<h4 class="modal-title">Deny with a reason</h4>
</div>
<form method="POST" action="@formAction/approval/deny" id="denyReasonForm">
<div class="modal-body">
<input name="requestId" class="requestId" type="text" hidden="hidden" value="" />
<textarea class="form-control form-control-custom" rows="3" id="denyReason" name="denyReason"></textarea>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger-outline" data-dismiss="modal">@UI.Common_Close</button>
<button type="button" class="btn btn-primary-outline denySaveReason" data-dismiss="modal">@UI.Common_Save</button>
</div>
</form>
</div>
</div>
</div>
@Html.LoadRequestAssets() @Html.LoadRequestAssets()

View file

@ -166,6 +166,18 @@
<h4>{{title}} ({{year}})</h4> <h4>{{title}} ({{year}})</h4>
</a> </a>
{{/if_eq}} {{/if_eq}}
{{#if_eq type "tv"}}
{{#if available}}
<span class="label label-success">Available</span>
{{else}}
<span class="label label-danger">Not Available</span>
{{/if}}
{{#if requested}}
<span class="label label-success">Requested</span>
{{/if}}
<br />
<br />
{{/if_eq}}
</div> </div>
<p>{{overview}}</p> <p>{{overview}}</p>
</div> </div>
@ -176,6 +188,7 @@
{{#if_eq available true}} {{#if_eq available true}}
<button style="text-align: right" class="btn btn-success-outline disabled" disabled><i class="fa fa-check"></i> @UI.Search_Available</button> <button style="text-align: right" class="btn btn-success-outline disabled" disabled><i class="fa fa-check"></i> @UI.Search_Available</button>
<br /> <br />
<br />
<a style="text-align: right" class="btn btn-sm btn-primary-outline" href="{{url}}" target="_blank"><i class="fa fa-eye"></i> @UI.Search_ViewInPlex</a> <a style="text-align: right" class="btn btn-sm btn-primary-outline" href="{{url}}" target="_blank"><i class="fa fa-eye"></i> @UI.Search_ViewInPlex</a>
{{else}} {{else}}
{{#if_eq requested true}} {{#if_eq requested true}}
@ -189,7 +202,6 @@
{{#if_eq tvFullyAvailable true}} {{#if_eq tvFullyAvailable true}}
@*//TODO Not used yet*@ @*//TODO Not used yet*@
<button style="text-align: right" class="btn btn-success-outline disabled" disabled><i class="fa fa-check"></i> @UI.Search_Available</button><br /> <button style="text-align: right" class="btn btn-success-outline disabled" disabled><i class="fa fa-check"></i> @UI.Search_Available</button><br />
<a style="text-align: right" class="btn btn-sm btn-primary-outline" href="{{url}}" target="_blank"><i class="fa fa-eye"></i> @UI.Search_ViewInPlex</a>
{{else}} {{else}}
<div class="dropdown"> <div class="dropdown">
<button id="{{id}}" class="btn btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true"> <button id="{{id}}" class="btn btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
@ -204,6 +216,10 @@
<li><a id="EpisodeSelect" data-identifier="{{id}}" data-toggle="modal" data-target="#episodesModal" href="#">@UI.Search_SelectEpisode...</a></li> <li><a id="EpisodeSelect" data-identifier="{{id}}" data-toggle="modal" data-target="#episodesModal" href="#">@UI.Search_SelectEpisode...</a></li>
</ul> </ul>
</div> </div>
{{#if available}}
<br />
<a style="text-align: right" class="btn btn-sm btn-primary-outline" href="{{url}}" target="_blank"><i class="fa fa-eye"></i> @UI.Search_ViewInPlex</a>
{{/if}}
{{/if_eq}} {{/if_eq}}
{{/if_eq}} {{/if_eq}}

View file

@ -3,9 +3,9 @@ configuration: Release
assembly_info: assembly_info:
patch: true patch: true
file: '**\AssemblyInfo.*' file: '**\AssemblyInfo.*'
assembly_version: '1.9.1' assembly_version: '1.9.2'
assembly_file_version: '{version}' assembly_file_version: '{version}'
assembly_informational_version: '1.9.1' assembly_informational_version: '1.9.2'
before_build: before_build:
- cmd: appveyor-retry nuget restore - cmd: appveyor-retry nuget restore
build: build: