diff --git a/PlexRequests.Core/IStatusChecker.cs b/PlexRequests.Core/IStatusChecker.cs index eec365b68..52bf0ece3 100644 --- a/PlexRequests.Core/IStatusChecker.cs +++ b/PlexRequests.Core/IStatusChecker.cs @@ -7,5 +7,6 @@ namespace PlexRequests.Core public interface IStatusChecker { Task GetStatus(); + Task ReportBug(string title, string body); } } \ No newline at end of file diff --git a/PlexRequests.Core/StatusChecker/StatusChecker.cs b/PlexRequests.Core/StatusChecker/StatusChecker.cs index 94e33b6b6..16e5aeee1 100644 --- a/PlexRequests.Core/StatusChecker/StatusChecker.cs +++ b/PlexRequests.Core/StatusChecker/StatusChecker.cs @@ -179,5 +179,16 @@ namespace PlexRequests.Core.StatusChecker return model; } + + public async Task ReportBug(string title, string body) + { + var issue = new NewIssue(title) + { + Body = body + }; + var result = await Git.Issue.Create(Owner, RepoName, issue); + + return result; + } } } \ No newline at end of file diff --git a/PlexRequests.Helpers/OperatingSystemHelper.cs b/PlexRequests.Helpers/OperatingSystemHelper.cs new file mode 100644 index 000000000..9c47b9bd9 --- /dev/null +++ b/PlexRequests.Helpers/OperatingSystemHelper.cs @@ -0,0 +1,76 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: OperatingSystemHelper.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 +namespace PlexRequests.Helpers +{ + public static class OperatingSystemHelper + { + public static string GetOs() + { + var os = System.Environment.OSVersion; + var osVersion = os.Version; + if (osVersion.Major.Equals(10)) + { + return "Windows 10/Windows Server 2016"; + } + if (osVersion.Major.Equals(6)) + { + if (osVersion.Minor.Equals(3)) + { + return "Windows 8.1/Windows Server 2012 R2"; + } + if (osVersion.Minor.Equals(2)) + { + return "Windows 8/Windows Server 2012"; + } + if (osVersion.Minor.Equals(1)) + { + return "Windows 7/Windows Server 2008 R2"; + } + if (osVersion.Minor.Equals(0)) + { + return "Windows Vista/Windows Server 2008"; + } + } + if (osVersion.Major.Equals(5)) + { + if (osVersion.Minor.Equals(2)) + { + return "Windows XP 64-Bit Edition/Windows Server 2003"; + } + if (osVersion.Minor.Equals(1)) + { + return "Windows XP"; + } + if (osVersion.Minor.Equals(0)) + { + return "Windows 2000"; + } + } + return os.VersionString; + } + } +} \ No newline at end of file diff --git a/PlexRequests.Helpers/PlexRequests.Helpers.csproj b/PlexRequests.Helpers/PlexRequests.Helpers.csproj index 5563d5da5..bdb65a017 100644 --- a/PlexRequests.Helpers/PlexRequests.Helpers.csproj +++ b/PlexRequests.Helpers/PlexRequests.Helpers.csproj @@ -83,6 +83,7 @@ + diff --git a/PlexRequests.UI/Content/helpers/bootbox.min.js b/PlexRequests.UI/Content/helpers/bootbox.min.js new file mode 100644 index 000000000..0dc0cbd5f --- /dev/null +++ b/PlexRequests.UI/Content/helpers/bootbox.min.js @@ -0,0 +1,6 @@ +/** + * bootbox.js v4.4.0 + * + * http://bootboxjs.com/license.txt + */ +!function(a,b){"use strict";"function"==typeof define&&define.amd?define(["jquery"],b):"object"==typeof exports?module.exports=b(require("jquery")):a.bootbox=b(a.jQuery)}(this,function a(b,c){"use strict";function d(a){var b=q[o.locale];return b?b[a]:q.en[a]}function e(a,c,d){a.stopPropagation(),a.preventDefault();var e=b.isFunction(d)&&d.call(c,a)===!1;e||c.modal("hide")}function f(a){var b,c=0;for(b in a)c++;return c}function g(a,c){var d=0;b.each(a,function(a,b){c(a,b,d++)})}function h(a){var c,d;if("object"!=typeof a)throw new Error("Please supply an object of options");if(!a.message)throw new Error("Please specify a message");return a=b.extend({},o,a),a.buttons||(a.buttons={}),c=a.buttons,d=f(c),g(c,function(a,e,f){if(b.isFunction(e)&&(e=c[a]={callback:e}),"object"!==b.type(e))throw new Error("button with key "+a+" must be an object");e.label||(e.label=a),e.className||(e.className=2>=d&&f===d-1?"btn-primary":"btn-default")}),a}function i(a,b){var c=a.length,d={};if(1>c||c>2)throw new Error("Invalid argument length");return 2===c||"string"==typeof a[0]?(d[b[0]]=a[0],d[b[1]]=a[1]):d=a[0],d}function j(a,c,d){return b.extend(!0,{},a,i(c,d))}function k(a,b,c,d){var e={className:"bootbox-"+a,buttons:l.apply(null,b)};return m(j(e,d,c),b)}function l(){for(var a={},b=0,c=arguments.length;c>b;b++){var e=arguments[b],f=e.toLowerCase(),g=e.toUpperCase();a[f]={label:d(g)}}return a}function m(a,b){var d={};return g(b,function(a,b){d[b]=!0}),g(a.buttons,function(a){if(d[a]===c)throw new Error("button key "+a+" is not allowed (options are "+b.join("\n")+")")}),a}var n={dialog:"",header:"",footer:"",closeButton:"",form:"
",inputs:{text:"",textarea:"",email:"",select:"",checkbox:"
",date:"",time:"",number:"",password:""}},o={locale:"en",backdrop:"static",animate:!0,className:null,closeButton:!0,show:!0,container:"body"},p={};p.alert=function(){var a;if(a=k("alert",["ok"],["message","callback"],arguments),a.callback&&!b.isFunction(a.callback))throw new Error("alert requires callback property to be a function when provided");return a.buttons.ok.callback=a.onEscape=function(){return b.isFunction(a.callback)?a.callback.call(this):!0},p.dialog(a)},p.confirm=function(){var a;if(a=k("confirm",["cancel","confirm"],["message","callback"],arguments),a.buttons.cancel.callback=a.onEscape=function(){return a.callback.call(this,!1)},a.buttons.confirm.callback=function(){return a.callback.call(this,!0)},!b.isFunction(a.callback))throw new Error("confirm requires a callback");return p.dialog(a)},p.prompt=function(){var a,d,e,f,h,i,k;if(f=b(n.form),d={className:"bootbox-prompt",buttons:l("cancel","confirm"),value:"",inputType:"text"},a=m(j(d,arguments,["title","callback"]),["cancel","confirm"]),i=a.show===c?!0:a.show,a.message=f,a.buttons.cancel.callback=a.onEscape=function(){return a.callback.call(this,null)},a.buttons.confirm.callback=function(){var c;switch(a.inputType){case"text":case"textarea":case"email":case"select":case"date":case"time":case"number":case"password":c=h.val();break;case"checkbox":var d=h.find("input:checked");c=[],g(d,function(a,d){c.push(b(d).val())})}return a.callback.call(this,c)},a.show=!1,!a.title)throw new Error("prompt requires a title");if(!b.isFunction(a.callback))throw new Error("prompt requires a callback");if(!n.inputs[a.inputType])throw new Error("invalid prompt type");switch(h=b(n.inputs[a.inputType]),a.inputType){case"text":case"textarea":case"email":case"date":case"time":case"number":case"password":h.val(a.value);break;case"select":var o={};if(k=a.inputOptions||[],!b.isArray(k))throw new Error("Please pass an array of input options");if(!k.length)throw new Error("prompt with select requires options");g(k,function(a,d){var e=h;if(d.value===c||d.text===c)throw new Error("given options in wrong format");d.group&&(o[d.group]||(o[d.group]=b("").attr("label",d.group)),e=o[d.group]),e.append("")}),g(o,function(a,b){h.append(b)}),h.val(a.value);break;case"checkbox":var q=b.isArray(a.value)?a.value:[a.value];if(k=a.inputOptions||[],!k.length)throw new Error("prompt with checkbox requires options");if(!k[0].value||!k[0].text)throw new Error("given options in wrong format");h=b("
"),g(k,function(c,d){var e=b(n.inputs[a.inputType]);e.find("input").attr("value",d.value),e.find("label").append(d.text),g(q,function(a,b){b===d.value&&e.find("input").prop("checked",!0)}),h.append(e)})}return a.placeholder&&h.attr("placeholder",a.placeholder),a.pattern&&h.attr("pattern",a.pattern),a.maxlength&&h.attr("maxlength",a.maxlength),f.append(h),f.on("submit",function(a){a.preventDefault(),a.stopPropagation(),e.find(".btn-primary").click()}),e=p.dialog(a),e.off("shown.bs.modal"),e.on("shown.bs.modal",function(){h.focus()}),i===!0&&e.modal("show"),e},p.dialog=function(a){a=h(a);var d=b(n.dialog),f=d.find(".modal-dialog"),i=d.find(".modal-body"),j=a.buttons,k="",l={onEscape:a.onEscape};if(b.fn.modal===c)throw new Error("$.fn.modal is not defined; please double check you have included the Bootstrap JavaScript library. See http://getbootstrap.com/javascript/ for more details.");if(g(j,function(a,b){k+="",l[a]=b.callback}),i.find(".bootbox-body").html(a.message),a.animate===!0&&d.addClass("fade"),a.className&&d.addClass(a.className),"large"===a.size?f.addClass("modal-lg"):"small"===a.size&&f.addClass("modal-sm"),a.title&&i.before(n.header),a.closeButton){var m=b(n.closeButton);a.title?d.find(".modal-header").prepend(m):m.css("margin-top","-10px").prependTo(i)}return a.title&&d.find(".modal-title").html(a.title),k.length&&(i.after(n.footer),d.find(".modal-footer").html(k)),d.on("hidden.bs.modal",function(a){a.target===this&&d.remove()}),d.on("shown.bs.modal",function(){d.find(".btn-primary:first").focus()}),"static"!==a.backdrop&&d.on("click.dismiss.bs.modal",function(a){d.children(".modal-backdrop").length&&(a.currentTarget=d.children(".modal-backdrop").get(0)),a.target===a.currentTarget&&d.trigger("escape.close.bb")}),d.on("escape.close.bb",function(a){l.onEscape&&e(a,d,l.onEscape)}),d.on("click",".modal-footer button",function(a){var c=b(this).data("bb-handler");e(a,d,l[c])}),d.on("click",".bootbox-close-button",function(a){e(a,d,l.onEscape)}),d.on("keyup",function(a){27===a.which&&d.trigger("escape.close.bb")}),b(a.container).append(d),d.modal({backdrop:a.backdrop?"static":!1,keyboard:!1,show:!1}),a.show&&d.modal("show"),d},p.setDefaults=function(){var a={};2===arguments.length?a[arguments[0]]=arguments[1]:a=arguments[0],b.extend(o,a)},p.hideAll=function(){return b(".bootbox").modal("hide"),p};var q={bg_BG:{OK:"Ок",CANCEL:"Отказ",CONFIRM:"Потвърждавам"},br:{OK:"OK",CANCEL:"Cancelar",CONFIRM:"Sim"},cs:{OK:"OK",CANCEL:"Zrušit",CONFIRM:"Potvrdit"},da:{OK:"OK",CANCEL:"Annuller",CONFIRM:"Accepter"},de:{OK:"OK",CANCEL:"Abbrechen",CONFIRM:"Akzeptieren"},el:{OK:"Εντάξει",CANCEL:"Ακύρωση",CONFIRM:"Επιβεβαίωση"},en:{OK:"OK",CANCEL:"Cancel",CONFIRM:"OK"},es:{OK:"OK",CANCEL:"Cancelar",CONFIRM:"Aceptar"},et:{OK:"OK",CANCEL:"Katkesta",CONFIRM:"OK"},fa:{OK:"قبول",CANCEL:"لغو",CONFIRM:"تایید"},fi:{OK:"OK",CANCEL:"Peruuta",CONFIRM:"OK"},fr:{OK:"OK",CANCEL:"Annuler",CONFIRM:"D'accord"},he:{OK:"אישור",CANCEL:"ביטול",CONFIRM:"אישור"},hu:{OK:"OK",CANCEL:"Mégsem",CONFIRM:"Megerősít"},hr:{OK:"OK",CANCEL:"Odustani",CONFIRM:"Potvrdi"},id:{OK:"OK",CANCEL:"Batal",CONFIRM:"OK"},it:{OK:"OK",CANCEL:"Annulla",CONFIRM:"Conferma"},ja:{OK:"OK",CANCEL:"キャンセル",CONFIRM:"確認"},lt:{OK:"Gerai",CANCEL:"Atšaukti",CONFIRM:"Patvirtinti"},lv:{OK:"Labi",CANCEL:"Atcelt",CONFIRM:"Apstiprināt"},nl:{OK:"OK",CANCEL:"Annuleren",CONFIRM:"Accepteren"},no:{OK:"OK",CANCEL:"Avbryt",CONFIRM:"OK"},pl:{OK:"OK",CANCEL:"Anuluj",CONFIRM:"Potwierdź"},pt:{OK:"OK",CANCEL:"Cancelar",CONFIRM:"Confirmar"},ru:{OK:"OK",CANCEL:"Отмена",CONFIRM:"Применить"},sq:{OK:"OK",CANCEL:"Anulo",CONFIRM:"Prano"},sv:{OK:"OK",CANCEL:"Avbryt",CONFIRM:"OK"},th:{OK:"ตกลง",CANCEL:"ยกเลิก",CONFIRM:"ยืนยัน"},tr:{OK:"Tamam",CANCEL:"İptal",CONFIRM:"Onayla"},zh_CN:{OK:"OK",CANCEL:"取消",CONFIRM:"确认"},zh_TW:{OK:"OK",CANCEL:"取消",CONFIRM:"確認"}};return p.addLocale=function(a,c){return b.each(["OK","CANCEL","CONFIRM"],function(a,b){if(!c[b])throw new Error("Please supply a translation for '"+b+"'")}),q[a]={OK:c.OK,CANCEL:c.CANCEL,CONFIRM:c.CONFIRM},p},p.removeLocale=function(a){return delete q[a],p},p.setLocale=function(a){return p.setDefaults("locale",a)},p.init=function(c){return a(c||b)},p}); \ No newline at end of file diff --git a/PlexRequests.UI/Helpers/BaseUrlHelper.cs b/PlexRequests.UI/Helpers/BaseUrlHelper.cs index 7541ca73b..04d627b3f 100644 --- a/PlexRequests.UI/Helpers/BaseUrlHelper.cs +++ b/PlexRequests.UI/Helpers/BaseUrlHelper.cs @@ -26,6 +26,7 @@ #endregion using System.Collections.Generic; using System.Text; +using System.Web.UI.WebControls; using Nancy; using Nancy.ViewEngines.Razor; @@ -233,6 +234,16 @@ namespace PlexRequests.UI.Helpers return helper.Raw(sb.ToString()); } + public static IHtmlString LoadAsset(this HtmlHelpers helper, string contentPath, bool javascript) + { + var assetLocation = GetBaseUrl(); + var content = GetContentUrl(assetLocation); + if (javascript) + { + return helper.Raw($""); + } + return helper.Raw($""); + } public static IHtmlString LoadTableAssets(this HtmlHelpers helper) { diff --git a/PlexRequests.UI/Models/AboutAdminViewModel.cs b/PlexRequests.UI/Models/AboutAdminViewModel.cs new file mode 100644 index 000000000..f84dd358b --- /dev/null +++ b/PlexRequests.UI/Models/AboutAdminViewModel.cs @@ -0,0 +1,37 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: AboutAdminViewModel.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 +namespace PlexRequests.UI.Models +{ + public class AboutAdminViewModel + { + public string Os { get; set; } // Windows/Mono + public string SystemVersion { get; set; } // Windows 10/ mono 4.2.5 + public string ApplicationVersion { get; set; } // File Version + public string Branch { get; set; } + public string LogLevel { get; set; } + } +} \ No newline at end of file diff --git a/PlexRequests.UI/Modules/Admin/AboutModule.cs b/PlexRequests.UI/Modules/Admin/AboutModule.cs new file mode 100644 index 000000000..9f75c1c8b --- /dev/null +++ b/PlexRequests.UI/Modules/Admin/AboutModule.cs @@ -0,0 +1,115 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: AboutModule.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 System; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using Nancy; +using Nancy.Responses.Negotiation; +using NLog; +using PlexRequests.Core; +using PlexRequests.Core.SettingModels; +using PlexRequests.Helpers; +using PlexRequests.Helpers.Permissions; +using PlexRequests.UI.Models; +using TMDbLib.Utilities; +using ISecurityExtensions = PlexRequests.Core.ISecurityExtensions; + +namespace PlexRequests.UI.Modules.Admin +{ + public class AboutModule : BaseModule + { + public AboutModule(ISettingsService settingsService, + ISettingsService systemService, ISecurityExtensions security, + IStatusChecker statusChecker) : base("admin", settingsService, security) + { + Before += (ctx) => Security.AdminLoginRedirect(Permissions.Administrator, ctx); + + SettingsService = systemService; + StatusChecker = statusChecker; + + Get["/about", true] = async (x,ct) => await Index(); + Post["/about", true] = async (x,ct) => await ReportIssue(); + } + + private ISettingsService SettingsService { get; } + private IStatusChecker StatusChecker { get; } + + + private async Task Index() + { + var vm = new AboutAdminViewModel(); + + var systemSettings = await SettingsService.GetSettingsAsync(); + + var type = Type.GetType("Mono.Runtime"); + if (type != null) // mono + { + vm.Os = "Mono"; + var displayName = type.GetMethod("GetDisplayName", BindingFlags.NonPublic | BindingFlags.Static); + if (displayName != null) + { + + vm.SystemVersion = displayName.Invoke(null, null).ToString(); + } + } + else + { + // Windows + vm.Os = OperatingSystemHelper.GetOs(); + vm.SystemVersion = Environment.Version.ToString(); + } + + vm.ApplicationVersion = AssemblyHelper.GetFileVersion(); + vm.Branch = EnumHelper.GetDisplayValue(systemSettings.Branch); + vm.LogLevel = LogManager.Configuration.LoggingRules.FirstOrDefault(x => x.NameMatches("database"))?.Levels?.FirstOrDefault()?.Name ?? "Unknown"; + + return View["About", vm]; + } + + private async Task ReportIssue() + { + var title = Request.Form["title"]; + var body = Request.Form["body"]; + + if (string.IsNullOrEmpty(title) || string.IsNullOrEmpty(body)) + { + return + Response.AsJson( + new + { + result = false, + message = "The title or issue body is empty! Please give me a bit more detail :)" + }); + } + + var result = await StatusChecker.ReportBug(title,body); + return Response.AsJson(new {result = true, url = result.HtmlUrl.ToString()}); + } + } +} \ No newline at end of file diff --git a/PlexRequests.UI/PlexRequests.UI.csproj b/PlexRequests.UI/PlexRequests.UI.csproj index 81263e146..0a0cadbc7 100644 --- a/PlexRequests.UI/PlexRequests.UI.csproj +++ b/PlexRequests.UI/PlexRequests.UI.csproj @@ -105,6 +105,10 @@ ..\packages\NLog.4.3.6\lib\net45\NLog.dll True + + ..\packages\Octokit.0.19.0\lib\net45\Octokit.dll + True + ..\packages\RestSharp.105.2.3\lib\net45\RestSharp.dll True @@ -228,6 +232,7 @@ + @@ -244,6 +249,7 @@ + @@ -368,6 +374,9 @@ datepicker.css Always + + PreserveNewest + PreserveNewest @@ -749,6 +758,9 @@ Always + + Always + PreserveNewest diff --git a/PlexRequests.UI/Views/About/About.cshtml b/PlexRequests.UI/Views/About/About.cshtml new file mode 100644 index 000000000..5b873b623 --- /dev/null +++ b/PlexRequests.UI/Views/About/About.cshtml @@ -0,0 +1,111 @@ +@using PlexRequests.UI.Helpers +@inherits Nancy.ViewEngines.Razor.NancyRazorViewBase +@Html.Partial("Shared/Partial/_Sidebar") +@Html.LoadAsset("/Content/helpers/bootbox.min.js", true) + +
+
+ About + + +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ + +
+ +
+
+ +
+
+ + +
+
+ + \ No newline at end of file diff --git a/PlexRequests.UI/packages.config b/PlexRequests.UI/packages.config index eaca70f4a..5968f8795 100644 --- a/PlexRequests.UI/packages.config +++ b/PlexRequests.UI/packages.config @@ -35,6 +35,7 @@ +