diff --git a/PlexRequests.Core/CacheKeys.cs b/PlexRequests.Core/CacheKeys.cs index 102d05766..d10bb0bb2 100644 --- a/PlexRequests.Core/CacheKeys.cs +++ b/PlexRequests.Core/CacheKeys.cs @@ -40,5 +40,7 @@ namespace PlexRequests.Core public const string CouchPotatoQualityProfiles = "CouchPotatoQualityProfiles"; public const string CouchPotatoQueued = "CouchPotatoQueued"; + + public const string GetBaseUrl = "GetBaseUrl"; } } \ No newline at end of file diff --git a/PlexRequests.UI/Content/requests.js b/PlexRequests.UI/Content/requests.js index ebe30fd8b..f36967c7c 100644 --- a/PlexRequests.UI/Content/requests.js +++ b/PlexRequests.UI/Content/requests.js @@ -11,7 +11,7 @@ var searchTemplate = Handlebars.compile(searchSource); var albumTemplate = Handlebars.compile(albumSource); var movieTimer = 0; var tvimer = 0; -var base = $('#baseUrl').val(); +var base = $('#baseUrl').text(); var mixItUpDefault = { animation: { enable: true }, @@ -381,16 +381,20 @@ $(document).on("click", ".change", function (e) { generateNotify("Success! Availibility changed.", "info"); var button = $("button[custom-availibility='" + buttonId + "']"); var icon = $('#availableIcon' + buttonId); + var approvedIcon = $("#"+buttonId + "notapproved"); if (response.available) { button.text("Mark Unavailable"); button.val("false"); button.prop("class", "btn btn-sm btn-info-outline change"); icon.prop("class", "fa fa-check"); + approvedIcon.prop("class", "fa fa-check"); + } else { button.text("Mark Available"); button.prop("class", "btn btn-sm btn-success-outline change"); icon.prop("class", "fa fa-times"); + approvedIcon.prop("class", "fa fa-times"); button.val("true"); } } diff --git a/PlexRequests.UI/Content/search.js b/PlexRequests.UI/Content/search.js index b4ae49fcf..e92b32454 100644 --- a/PlexRequests.UI/Content/search.js +++ b/PlexRequests.UI/Content/search.js @@ -5,6 +5,8 @@ return opts.inverse(this); }); + + $(function () { var searchSource = $("#search-template").html(); @@ -12,6 +14,8 @@ $(function () { var searchTemplate = Handlebars.compile(searchSource); var musicTemplate = Handlebars.compile(musicSource); + var base = $('#baseUrl').text(); + var searchTimer = 0; // fix for selecting a default tab @@ -21,7 +25,7 @@ $(function () { } $('a[data-toggle="tab"]').on('shown.bs.tab', function (e) { - focusSearch($($(e.target).attr('href'))) + focusSearch($($(e.target).attr('href'))); }); focusSearch($('li.active a', '#nav-tabs').first().attr('href')); @@ -172,15 +176,18 @@ $(function () { function movieSearch() { var query = $("#movieSearchContent").val(); - query ? getMovies("/search/movie/" + query) : resetMovies(); + var url = createBaseUrl(base, '/search/movie/'); + query ? getMovies(url + query) : resetMovies(); } function moviesComingSoon() { - getMovies("/search/movie/upcoming"); + var url = createBaseUrl(base, '/search/movie/upcoming'); + getMovies(url); } function moviesInTheaters() { - getMovies("/search/movie/playing"); + var url = createBaseUrl(base, '/search/movie/playing'); + getMovies(url); } function getMovies(url) { @@ -209,7 +216,9 @@ $(function () { function tvSearch() { var query = $("#tvSearchContent").val(); - query ? getTvShows("/search/tv/" + query) : resetTvShows(); + + var url = createBaseUrl(base, '/search/tv/'); + query ? getTvShows(url + query) : resetTvShows(); } function getTvShows(url) { @@ -236,8 +245,9 @@ $(function () { } function musicSearch() { + var url = createBaseUrl(base, '/search/music/'); var query = $("#musicSearchContent").val(); - query ? getMusic("/search/music/" + query) : resetMusic(); + query ? getMusic(url + query) : resetMusic(); } function getMusic(url) { @@ -266,7 +276,9 @@ $(function () { } function getCoverArt(artistId) { - $.ajax("/search/music/coverart/" + artistId).success(function (result) { + + var url = createBaseUrl(base, '/search/music/coverart/'); + $.ajax(url + artistId).success(function (result) { if (result) { $('#' + artistId + "imageDiv").html(" "); } diff --git a/PlexRequests.UI/Helpers/BaseUrlHelper.cs b/PlexRequests.UI/Helpers/BaseUrlHelper.cs index ddeb1fb7b..c9a3e30f2 100644 --- a/PlexRequests.UI/Helpers/BaseUrlHelper.cs +++ b/PlexRequests.UI/Helpers/BaseUrlHelper.cs @@ -32,12 +32,20 @@ using Nancy.ViewEngines.Razor; using PlexRequests.Core; using PlexRequests.Core.SettingModels; +using PlexRequests.Helpers; namespace PlexRequests.UI.Helpers { public static class BaseUrlHelper { - private static ServiceLocator Locator => ServiceLocator.Instance; + static BaseUrlHelper() + { + Locator = ServiceLocator.Instance; + Cache = Locator.Resolve(); + } + private static ICacheProvider Cache { get; } + private static ServiceLocator Locator { get; } + public static IHtmlString LoadAssets(this HtmlHelpers helper) { var sb = new StringBuilder(); @@ -117,6 +125,26 @@ namespace PlexRequests.UI.Helpers returnString = $"{title}"; } + return helper.Raw(returnString); + } + + public static IHtmlString GetNavbarUrl(this HtmlHelpers helper, NancyContext context, string url, string title, string fontIcon) + { + var returnString = string.Empty; + var content = GetLinkUrl(GetBaseUrl()); + if (!string.IsNullOrEmpty(content)) + { + url = $"/{content}{url}"; + } + if (context.Request.Path == url) + { + returnString = $" {title}"; + } + else + { + returnString = $" {title}"; + } + return helper.Raw(returnString); } @@ -125,11 +153,15 @@ namespace PlexRequests.UI.Helpers return helper.Raw(GetBaseUrl()); } - private static string GetBaseUrl() - { - var settings = Locator.Resolve>().GetSettings(); - var assetLocation = settings.BaseUrl; - return assetLocation; + private static string GetBaseUrl() + { + var returnValue = Cache.GetOrSet(CacheKeys.GetBaseUrl, () => + { + var settings = Locator.Resolve>().GetSettings(); + var assetLocation = settings.BaseUrl; + return assetLocation; + }); + return returnValue; } private static string GetLinkUrl(string assetLocation) diff --git a/PlexRequests.UI/Helpers/ServiceLocator.cs b/PlexRequests.UI/Helpers/ServiceLocator.cs index c957bacbb..860cd150b 100644 --- a/PlexRequests.UI/Helpers/ServiceLocator.cs +++ b/PlexRequests.UI/Helpers/ServiceLocator.cs @@ -44,11 +44,7 @@ namespace PlexRequests.UI.Helpers } public T Resolve() where T : class { - if (Container != null) - { - return Container.Resolve(); - } - return null; + return Container?.Resolve(); } } } \ No newline at end of file diff --git a/PlexRequests.UI/Modules/AdminModule.cs b/PlexRequests.UI/Modules/AdminModule.cs index 9dc93cb38..1650c4ed4 100644 --- a/PlexRequests.UI/Modules/AdminModule.cs +++ b/PlexRequests.UI/Modules/AdminModule.cs @@ -595,6 +595,11 @@ namespace PlexRequests.UI.Modules private Response GetCpProfiles() { var settings = this.Bind(); + var valid = this.Validate(settings); + if (!valid.IsValid) + { + return Response.AsJson(valid.SendJsonError()); + } var profiles = CpApi.GetProfiles(settings.FullUri, settings.ApiKey); // set the cache diff --git a/PlexRequests.UI/Modules/ApplicationTesterModule.cs b/PlexRequests.UI/Modules/ApplicationTesterModule.cs index 1a59fab2b..69e2df08d 100644 --- a/PlexRequests.UI/Modules/ApplicationTesterModule.cs +++ b/PlexRequests.UI/Modules/ApplicationTesterModule.cs @@ -29,12 +29,13 @@ using System; using Nancy; using Nancy.ModelBinding; using Nancy.Security; - +using Nancy.Validation; using NLog; using PlexRequests.Api.Interfaces; using PlexRequests.Core; using PlexRequests.Core.SettingModels; +using PlexRequests.UI.Helpers; using PlexRequests.UI.Models; namespace PlexRequests.UI.Modules @@ -73,6 +74,11 @@ namespace PlexRequests.UI.Modules private Response CouchPotatoTest() { var couchPotatoSettings = this.Bind(); + var valid = this.Validate(couchPotatoSettings); + if (!valid.IsValid) + { + return Response.AsJson(valid.SendJsonError()); + } try { var status = CpApi.GetStatus(couchPotatoSettings.FullUri, couchPotatoSettings.ApiKey); @@ -97,6 +103,11 @@ namespace PlexRequests.UI.Modules private Response SonarrTest() { var sonarrSettings = this.Bind(); + var valid = this.Validate(sonarrSettings); + if (!valid.IsValid) + { + return Response.AsJson(valid.SendJsonError()); + } try { var status = SonarrApi.SystemStatus(sonarrSettings.ApiKey, sonarrSettings.FullUri); @@ -121,6 +132,11 @@ namespace PlexRequests.UI.Modules private Response PlexTest() { var plexSettings = this.Bind(); + var valid = this.Validate(plexSettings); + if (!valid.IsValid) + { + return Response.AsJson(valid.SendJsonError()); + } var settings = AuthSettings.GetSettings(); if (settings?.PlexAuthToken == null) { @@ -150,7 +166,11 @@ namespace PlexRequests.UI.Modules private Response SickRageTest() { var sickRageSettings = this.Bind(); - + var valid = this.Validate(sickRageSettings); + if (!valid.IsValid) + { + return Response.AsJson(valid.SendJsonError()); + } try { var status = SickRageApi.Ping(sickRageSettings.ApiKey, sickRageSettings.FullUri); @@ -175,6 +195,11 @@ namespace PlexRequests.UI.Modules private Response HeadphonesTest() { var settings = this.Bind(); + var valid = this.Validate(settings); + if (!valid.IsValid) + { + return Response.AsJson(valid.SendJsonError()); + } try { var result = HeadphonesApi.GetVersion(settings.ApiKey, settings.FullUri); diff --git a/PlexRequests.UI/Modules/LoginModule.cs b/PlexRequests.UI/Modules/LoginModule.cs index 63b500b6a..83616a80b 100644 --- a/PlexRequests.UI/Modules/LoginModule.cs +++ b/PlexRequests.UI/Modules/LoginModule.cs @@ -75,6 +75,11 @@ namespace PlexRequests.UI.Modules } Session[SessionKeys.UsernameKey] = username; Session[SessionKeys.ClientDateTimeOffsetKey] = dtOffset; + if (!string.IsNullOrEmpty(BaseUrl)) + { + + return this.LoginAndRedirect(userId.Value, expiry, $"/{BaseUrl}"); + } return this.LoginAndRedirect(userId.Value, expiry); }; diff --git a/PlexRequests.UI/PlexRequests.UI.csproj b/PlexRequests.UI/PlexRequests.UI.csproj index 93a345ed1..024b63942 100644 --- a/PlexRequests.UI/PlexRequests.UI.csproj +++ b/PlexRequests.UI/PlexRequests.UI.csproj @@ -178,6 +178,7 @@ + diff --git a/PlexRequests.UI/Validators/HeadphonesValidator.cs b/PlexRequests.UI/Validators/HeadphonesValidator.cs new file mode 100644 index 000000000..5dccd85a4 --- /dev/null +++ b/PlexRequests.UI/Validators/HeadphonesValidator.cs @@ -0,0 +1,42 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: SonarrValidator.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 FluentValidation; + +using PlexRequests.Core.SettingModels; + +namespace PlexRequests.UI.Validators +{ + public class HeadphonesValidator : AbstractValidator + { + public HeadphonesValidator() + { + RuleFor(request => request.Ip).NotEmpty().WithMessage("You must specify a IP/Host name."); + RuleFor(request => request.Port).NotEmpty().WithMessage("You must specify a Port."); + RuleFor(request => request.ApiKey).NotEmpty().WithMessage("You must specify a Api Key."); + } + } +} \ No newline at end of file diff --git a/PlexRequests.UI/Views/Admin/Authentication.cshtml b/PlexRequests.UI/Views/Admin/Authentication.cshtml index da2cea63f..d223db1b8 100644 --- a/PlexRequests.UI/Views/Admin/Authentication.cshtml +++ b/PlexRequests.UI/Views/Admin/Authentication.cshtml @@ -122,11 +122,11 @@ $('#requestToken').click(function (e) { e.preventDefault(); - var url = createBaseUrl(base, "admin/requestauth"); + var url = createBaseUrl(base, "requestauth"); var $form = $("#mainForm"); $.ajax({ type: $form.prop("method"), - url: url, + url: "requestauth", data: $form.serialize(), dataType: "json", success: function (response) { @@ -149,11 +149,11 @@ function loadUserList() { $('#users').html(""); - var url = "admin/getusers"; + var url = "getusers"; url = createBaseUrl(base, url); $.ajax({ type: "Get", - url: url, + url: "getusers", dataType: "json", success: function (response) { if (response.length > 1) { diff --git a/PlexRequests.UI/Views/Admin/CouchPotato.cshtml b/PlexRequests.UI/Views/Admin/CouchPotato.cshtml index bf1251c2b..fb54a6c33 100644 --- a/PlexRequests.UI/Views/Admin/CouchPotato.cshtml +++ b/PlexRequests.UI/Views/Admin/CouchPotato.cshtml @@ -116,7 +116,7 @@ var qualitySelected = '@Model.ProfileId'; var $form = $("#mainForm"); - var url = 'cpprofiles'; + var url = '/admin/cpprofiles'; url = createBaseUrl(baseUrl, url); $.ajax({ type: $form.prop("method"), @@ -144,14 +144,17 @@ $('#getProfiles').click(function (e) { e.preventDefault(); var $form = $("#mainForm"); - var url = "admin/cpprofiles"; - url = createBaseUrl(baseUrl, url); + var url = createBaseUrl(baseUrl, "/admin/cpprofiles"); $.ajax({ type: $form.prop("method"), data: $form.serialize(), url: url, dataType: "json", success: function (response) { + if (response.message) { + generateNotify(response.message, "warning"); + return; + } response.list.forEach(function (result) { $("#select").append("" + result.label + ""); }); @@ -167,7 +170,6 @@ e.preventDefault(); var $form = $("#mainForm"); var url = createBaseUrl(baseUrl,"/test/cp"); - url = createBaseUrl(baseUrl, url); $.ajax({ type: $form.prop("method"), url: url, diff --git a/PlexRequests.UI/Views/Admin/Sickrage.cshtml b/PlexRequests.UI/Views/Admin/Sickrage.cshtml index 111f525f2..e2415f74e 100644 --- a/PlexRequests.UI/Views/Admin/Sickrage.cshtml +++ b/PlexRequests.UI/Views/Admin/Sickrage.cshtml @@ -148,13 +148,18 @@ $('#testSickRage').click(function (e) { e.preventDefault(); + var qualityProfile = $("#profiles option:selected").val(); + var $form = $("#mainForm"); + var data = $form.serialize(); + data = data + "&qualityProfile=" + qualityProfile; + var url = createBaseUrl(base, '/test/sickrage'); $.ajax({ type: $form.prop("method"), url: url, - data: $form.serialize(), + data: data, dataType: "json", success: function (response) { console.log(response); diff --git a/PlexRequests.UI/Views/Admin/Sonarr.cshtml b/PlexRequests.UI/Views/Admin/Sonarr.cshtml index dbbbef068..6ca64285d 100644 --- a/PlexRequests.UI/Views/Admin/Sonarr.cshtml +++ b/PlexRequests.UI/Views/Admin/Sonarr.cshtml @@ -224,16 +224,22 @@ } }); }); + var base = '@Html.GetBaseUrl()'; $('#testSonarr').click(function (e) { e.preventDefault(); + var qualityProfile = $("#profiles option:selected").val(); + var $form = $("#mainForm"); + + var data = $form.serialize(); + data = data + "&qualityProfile=" + qualityProfile; var url = createBaseUrl(base, '/test/sonarr'); $.ajax({ type: $form.prop("method"), url: url, - data: $form.serialize(), + data: data, dataType: "json", success: function (response) { console.log(response); diff --git a/PlexRequests.UI/Views/Requests/Index.cshtml b/PlexRequests.UI/Views/Requests/Index.cshtml index 16d713bb3..b6fe26214 100644 --- a/PlexRequests.UI/Views/Requests/Index.cshtml +++ b/PlexRequests.UI/Views/Requests/Index.cshtml @@ -411,7 +411,6 @@ - @Html.LoadRequestAssets() diff --git a/PlexRequests.UI/Views/Search/Index.cshtml b/PlexRequests.UI/Views/Search/Index.cshtml index ea9d2ba8b..9293f05d9 100644 --- a/PlexRequests.UI/Views/Search/Index.cshtml +++ b/PlexRequests.UI/Views/Search/Index.cshtml @@ -1,4 +1,12 @@ @using PlexRequests.UI.Helpers +@{ + var baseUrl = Html.GetBaseUrl(); + var url = string.Empty; + if (!string.IsNullOrEmpty(baseUrl.ToHtmlString())) + { + url = "/" + baseUrl.ToHtmlString(); + } +} Search Want to watch something that is not currently on Plex?! No problem! Just search for it below and request it! @@ -19,7 +27,7 @@ } - + @if (Model.SearchForMovies) @@ -123,7 +131,7 @@ {{overview}} - + {{#if_eq available true}} Available @@ -188,7 +196,7 @@ {{overview}} - + {{#if_eq available true}} Available diff --git a/PlexRequests.UI/Views/Shared/_Layout.cshtml b/PlexRequests.UI/Views/Shared/_Layout.cshtml index 50126d584..a61aae9c3 100644 --- a/PlexRequests.UI/Views/Shared/_Layout.cshtml +++ b/PlexRequests.UI/Views/Shared/_Layout.cshtml @@ -3,11 +3,21 @@ @using PlexRequests.UI.Helpers @using PlexRequests.UI.Models @inherits Nancy.ViewEngines.Razor.NancyRazorViewBase +@{ + var baseUrl = Html.GetBaseUrl(); + var url = string.Empty; + if (!string.IsNullOrEmpty(baseUrl.ToHtmlString())) + { + url = "/" + baseUrl.ToHtmlString(); + } +} +@baseUrl.ToHtmlString() Plex Requests + @Html.LoadAssets() @@ -22,53 +32,36 @@ - Plex Requests + Plex Requests - @if (Context.Request.Path == "/search") - { - Search - } - else - { - Search - } - - - @if (Context.Request.Path == "/requests") - { - Requests - } - else - { - Requests - } + @Html.GetNavbarUrl(Context, "/search", "Search", "search") + @Html.GetNavbarUrl(Context, "/requests", "Requests", "plus-circle") - - + @if (!Context.CurrentUser.IsAuthenticated()) { - Admin + Admin } else { Admin - Settings - Change password + Settings + Change password - Logout + Logout } @if (Context.Request.Session[SessionKeys.UsernameKey] != null) { - Logout + Logout } @@ -78,11 +71,11 @@ @RenderBody() - - + + - +
{{overview}}