diff --git a/PlexRequests.Core/ISecurityExtensions.cs b/PlexRequests.Core/ISecurityExtensions.cs index 10db3ccfe..25fd68334 100644 --- a/PlexRequests.Core/ISecurityExtensions.cs +++ b/PlexRequests.Core/ISecurityExtensions.cs @@ -1,6 +1,7 @@ using System; using Nancy; using Nancy.Security; +using Nancy.Session; using PlexRequests.Helpers.Permissions; namespace PlexRequests.Core @@ -29,6 +30,6 @@ Response HasAnyPermissionsRedirect(NancyContext context, string routeName, HttpS /// /// The username. /// null if we cannot find a user - string GetUsername(string username); + string GetUsername(string username, ISession session); } } \ No newline at end of file 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/PlexRequests.Core.csproj b/PlexRequests.Core/PlexRequests.Core.csproj index f2f79067b..20e3e74fe 100644 --- a/PlexRequests.Core/PlexRequests.Core.csproj +++ b/PlexRequests.Core/PlexRequests.Core.csproj @@ -122,6 +122,7 @@ + diff --git a/PlexRequests.Core/SecurityExtensions.cs b/PlexRequests.Core/SecurityExtensions.cs index 38972cf22..4a237007e 100644 --- a/PlexRequests.Core/SecurityExtensions.cs +++ b/PlexRequests.Core/SecurityExtensions.cs @@ -30,6 +30,7 @@ using Nancy.Linker; using Nancy.Responses; using Nancy.Security; +using Nancy.Session; using PlexRequests.Core.Models; using PlexRequests.Helpers; using PlexRequests.Helpers.Permissions; @@ -91,7 +92,7 @@ public bool IsNormalUser(IUserIdentity user) /// /// The username. /// null if we cannot find a user - public string GetUsername(string username) + public string GetUsername(string username, ISession session) { var plexUser = PlexUsers.GetUserByUsername(username); if (plexUser != null) @@ -119,7 +120,11 @@ public string GetUsername(string username) return dbUser.UserName; } } - return null; + + // could be a local user + var localName = session[SessionKeys.UsernameKey]; + + return localName as string; } diff --git a/PlexRequests.Core/SettingModels/CustomizationSettings.cs b/PlexRequests.Core/SettingModels/CustomizationSettings.cs new file mode 100644 index 000000000..9c0d47a65 --- /dev/null +++ b/PlexRequests.Core/SettingModels/CustomizationSettings.cs @@ -0,0 +1,39 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: NewsletterSettings.cs +// Created By: Jim MacKenzie +// +// 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.Core.SettingModels +{ + public class CustomizationSettings : Settings + { + public string ApplicationName { get; set; } + + /// + /// The CSS name of the theme we want + /// + public string ThemeName { get; set; } + } +} \ No newline at end of file diff --git a/PlexRequests.Core/SettingModels/EmailNotificationSettings.cs b/PlexRequests.Core/SettingModels/EmailNotificationSettings.cs index 138c59a8c..a8069644a 100644 --- a/PlexRequests.Core/SettingModels/EmailNotificationSettings.cs +++ b/PlexRequests.Core/SettingModels/EmailNotificationSettings.cs @@ -33,7 +33,6 @@ public sealed class EmailNotificationSettings : NotificationSettings public int EmailPort { get; set; } public string EmailSender { get; set; } public string EmailUsername { get; set; } - public bool Enabled { get; set; } public bool Authentication { get; set; } public bool EnableUserEmailNotifications { get; set; } public string RecipientEmail { get; set; } diff --git a/PlexRequests.Core/SettingModels/NotificationSettings.cs b/PlexRequests.Core/SettingModels/NotificationSettings.cs index 1a808244e..58a1128f0 100644 --- a/PlexRequests.Core/SettingModels/NotificationSettings.cs +++ b/PlexRequests.Core/SettingModels/NotificationSettings.cs @@ -45,6 +45,9 @@ public NotificationSettings() }; } + + public bool Enabled { get; set; } + public List Message { get; set; } } diff --git a/PlexRequests.Core/SettingModels/PushBulletNotificationSettings.cs b/PlexRequests.Core/SettingModels/PushBulletNotificationSettings.cs index 7b3b59a82..b65c5f1b5 100644 --- a/PlexRequests.Core/SettingModels/PushBulletNotificationSettings.cs +++ b/PlexRequests.Core/SettingModels/PushBulletNotificationSettings.cs @@ -30,6 +30,5 @@ public sealed class PushbulletNotificationSettings : NotificationSettings { public string AccessToken { get; set; } public string DeviceIdentifier { get; set; } - public bool Enabled { get; set; } } } \ No newline at end of file diff --git a/PlexRequests.Core/SettingModels/PushoverNotificationSettings.cs b/PlexRequests.Core/SettingModels/PushoverNotificationSettings.cs index 0c3e0b976..276a5c96c 100644 --- a/PlexRequests.Core/SettingModels/PushoverNotificationSettings.cs +++ b/PlexRequests.Core/SettingModels/PushoverNotificationSettings.cs @@ -29,7 +29,6 @@ namespace PlexRequests.Core.SettingModels public sealed class PushoverNotificationSettings : NotificationSettings { public string AccessToken { get; set; } - public bool Enabled { get; set; } public string UserToken { get; set; } } } \ No newline at end of file diff --git a/PlexRequests.Core/SettingModels/SlackNotificationSettings.cs b/PlexRequests.Core/SettingModels/SlackNotificationSettings.cs index 8e529d618..80a26ab7e 100644 --- a/PlexRequests.Core/SettingModels/SlackNotificationSettings.cs +++ b/PlexRequests.Core/SettingModels/SlackNotificationSettings.cs @@ -6,7 +6,6 @@ namespace PlexRequests.Core.SettingModels { public sealed class SlackNotificationSettings : NotificationSettings { - public bool Enabled { get; set; } public string WebhookUrl { get; set; } public string Channel { get; set; } public string Username { get; set; } 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 @@ private StatusModel GetAppveyorRelease(Branches branch) 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/Bootstrapper.cs b/PlexRequests.UI/Bootstrapper.cs index 7dbb5765a..484854713 100644 --- a/PlexRequests.UI/Bootstrapper.cs +++ b/PlexRequests.UI/Bootstrapper.cs @@ -31,13 +31,11 @@ using Mono.Data.Sqlite; using Nancy; -using Nancy.Authentication.Forms; using Nancy.Bootstrapper; using Nancy.Bootstrappers.Ninject; using Nancy.Conventions; using Nancy.Cryptography; using Nancy.Diagnostics; -using Nancy.Hosting.Self; using Nancy.Session; using PlexRequests.Api.Interfaces; @@ -53,6 +51,7 @@ using Ninject; using PlexRequests.UI.Authentication; +using Nancy.Hosting.Self; namespace PlexRequests.UI { @@ -105,9 +104,6 @@ protected override void ApplicationStartup(IKernel container, IPipelines pipelin ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls; ServicePointManager.ServerCertificateValidationCallback += (sender, certificate, chain, sslPolicyErrors) => true; - - SubscribeAllObservers(container); - } #if DEBUG @@ -142,39 +138,6 @@ protected override void ConfigureConventions(NancyConventions nancyConventions) protected override DiagnosticsConfiguration DiagnosticsConfiguration => new DiagnosticsConfiguration { Password = @"password" }; - private void SubscribeAllObservers(IKernel container) - { - var notificationService = container.Get(); - - var emailSettingsService = container.Get>(); - var emailSettings = emailSettingsService.GetSettings(); - if (emailSettings.Enabled) - { - notificationService.Subscribe(new EmailMessageNotification(emailSettingsService)); - } - - var pushbulletService = container.Get>(); - var pushbulletSettings = pushbulletService.GetSettings(); - if (pushbulletSettings.Enabled) - { - notificationService.Subscribe(new PushbulletNotification(container.Get(), pushbulletService)); - } - - var pushoverService = container.Get>(); - var pushoverSettings = pushoverService.GetSettings(); - if (pushoverSettings.Enabled) - { - notificationService.Subscribe(new PushoverNotification(container.Get(), pushoverService)); - } - - var slackService = container.Get>(); - var slackSettings = slackService.GetSettings(); - if (slackSettings.Enabled) - { - notificationService.Subscribe(new SlackNotification(container.Get(), slackService)); - } - } - protected override void RequestStartup(IKernel container, IPipelines pipelines, NancyContext context) { //CORS Enable 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 513dbd553..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; @@ -66,7 +67,7 @@ public static IHtmlString LoadAssets(this HtmlHelpers helper) var assetLocation = GetBaseUrl(); var content = GetContentUrl(assetLocation); - var settings = GetSettings(); + var settings = GetCustomizationSettings(); if (string.IsNullOrEmpty(settings.ThemeName)) { settings.ThemeName = Themes.PlexTheme; @@ -233,6 +234,16 @@ public static IHtmlString LoadUserManagementAssets(this HtmlHelpers helper) 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) { @@ -328,6 +339,11 @@ public static IHtmlString GetBaseUrl(this HtmlHelpers helper) return helper.Raw(GetBaseUrl()); } + public static IHtmlString GetApplicationName(this HtmlHelpers helper) + { + return helper.Raw(GetCustomizationSettings().ApplicationName); + } + private static string GetBaseUrl() { return GetSettings().BaseUrl; @@ -343,6 +359,16 @@ private static PlexRequestSettings GetSettings() return returnValue; } + private static CustomizationSettings GetCustomizationSettings() + { + var returnValue = Cache.GetOrSet(CacheKeys.GetPlexRequestSettings, () => + { + var settings = Locator.Resolve>().GetSettings(); + return settings; + }); + return returnValue; + } + private static string GetLinkUrl(string assetLocation) { return string.IsNullOrEmpty(assetLocation) ? string.Empty : $"{assetLocation}"; 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/Modules/Admin/CustomizationModule.cs b/PlexRequests.UI/Modules/Admin/CustomizationModule.cs new file mode 100644 index 000000000..be009396d --- /dev/null +++ b/PlexRequests.UI/Modules/Admin/CustomizationModule.cs @@ -0,0 +1,72 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: CustomizationModule.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.Threading.Tasks; +using Nancy; +using Nancy.ModelBinding; +using Nancy.Responses.Negotiation; +using PlexRequests.Core; +using PlexRequests.Core.SettingModels; +using PlexRequests.Helpers.Permissions; +using PlexRequests.UI.Models; +using ISecurityExtensions = PlexRequests.Core.ISecurityExtensions; + +namespace PlexRequests.UI.Modules.Admin +{ + public class CustomizationModule : BaseModule + { + public CustomizationModule(ISettingsService settingsService, ISettingsService cust, ISecurityExtensions security) : base("admin", settingsService, security) + { + Before += (ctx) => Security.AdminLoginRedirect(Permissions.Administrator, ctx); + + Settings = cust; + + Get["/customization", true] = async (x,ct) => await Index(); + Post["/customization", true] = async (x,ct) => await Save(); + } + + private ISettingsService Settings { get; } + + private async Task Index() + { + var model = await Settings.GetSettingsAsync(); + + return View["customization", model]; + } + + private async Task Save() + { + var model = this.Bind(); + + var result = await Settings.SaveSettingsAsync(model); + + return Response.AsJson(result + ? new JsonResponseModel { Result = true } + : new JsonResponseModel { Result = false, Message = "We could not save to the database, please try again" }); + } + } +} \ No newline at end of file diff --git a/PlexRequests.UI/Modules/BaseModule.cs b/PlexRequests.UI/Modules/BaseModule.cs index 81427c924..477d2d848 100644 --- a/PlexRequests.UI/Modules/BaseModule.cs +++ b/PlexRequests.UI/Modules/BaseModule.cs @@ -112,7 +112,7 @@ protected string Username { try { - var username = Security.GetUsername(User.UserName); + var username = Security.GetUsername(User.UserName, Session); if (string.IsNullOrEmpty(username)) { return Session[SessionKeys.UsernameKey].ToString(); diff --git a/PlexRequests.UI/Modules/DonationLinkModule.cs b/PlexRequests.UI/Modules/DonationLinkModule.cs index 3bb4f8f1b..39168dad7 100644 --- a/PlexRequests.UI/Modules/DonationLinkModule.cs +++ b/PlexRequests.UI/Modules/DonationLinkModule.cs @@ -18,7 +18,6 @@ public class DonationLinkModule : BaseAuthModule public DonationLinkModule(ICacheProvider provider, ISettingsService pr, ISecurityExtensions security) : base("customDonation", pr, security) { Cache = provider; - Get["/", true] = async (x, ct) => await GetCustomDonationUrl(pr); } @@ -31,14 +30,12 @@ private async Task GetCustomDonationUrl(ISettingsService LoginUser() loginGuid = Guid.Parse(dbUser.UserGuid); } + if (loginGuid != Guid.Empty) + { + if (!settings.UserAuthentication)// Do not need to auth make admin use login screen for now TODO remove this + { + if (dbUser != null) + { + var perms = (Permissions) dbUser.Permissions; + if (perms.HasFlag(Permissions.Administrator)) + { + var uri = Linker.BuildRelativeUri(Context, "UserLoginIndex"); + Session["TempMessage"] = Resources.UI.UserLogin_AdminUsePassword; + return Response.AsRedirect(uri.ToString()); + } + } + if (plexLocal != null) + { + var perms = (Permissions)plexLocal.Permissions; + if (perms.HasFlag(Permissions.Administrator)) + { + var uri = Linker.BuildRelativeUri(Context, "UserLoginIndex"); + Session["TempMessage"] = Resources.UI.UserLogin_AdminUsePassword; + return Response.AsRedirect(uri.ToString()); + } + } + } + } + if(loginGuid == Guid.Empty && settings.UserAuthentication) { var defaultSettings = UserManagementSettings.GetSettings(); diff --git a/PlexRequests.UI/PlexRequests.UI.csproj b/PlexRequests.UI/PlexRequests.UI.csproj index e8489f12c..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,8 @@ + + @@ -367,6 +374,9 @@ datepicker.css Always + + PreserveNewest + PreserveNewest @@ -748,6 +758,9 @@ Always + + Always + PreserveNewest @@ -757,6 +770,9 @@ Always + + Always + web.config diff --git a/PlexRequests.UI/Resources/UI.resx b/PlexRequests.UI/Resources/UI.resx index a4433781f..fc5b4be3b 100644 --- a/PlexRequests.UI/Resources/UI.resx +++ b/PlexRequests.UI/Resources/UI.resx @@ -473,4 +473,7 @@ User Management - + + If you are an administrator, please use the other login page + + \ No newline at end of file diff --git a/PlexRequests.UI/Resources/UI1.Designer.cs b/PlexRequests.UI/Resources/UI1.Designer.cs index a5cf28b1f..fa19b86d6 100644 --- a/PlexRequests.UI/Resources/UI1.Designer.cs +++ b/PlexRequests.UI/Resources/UI1.Designer.cs @@ -223,7 +223,7 @@ public static string Layout_Admin { } /// - /// Looks up a localized string similar to A background process is currently running, so there might be some unexpected behavior. This shouldn't take too long.. + /// Looks up a localized string similar to Currently we are indexing all of the available tv shows and movies on the Plex server, so there might be some unexpected behavior. This shouldn't take too long.. /// public static string Layout_CacherRunning { get { @@ -1113,6 +1113,15 @@ public static string Search_WeeklyRequestLimitTVShow { } } + /// + /// Looks up a localized string similar to If you are an administrator, please use the other login page. + /// + public static string UserLogin_AdminUsePassword { + get { + return ResourceManager.GetString("UserLogin_AdminUsePassword", resourceCulture); + } + } + /// /// Looks up a localized string similar to Incorrect User or Password. /// diff --git a/PlexRequests.UI/Startup.cs b/PlexRequests.UI/Startup.cs index e4d4bf107..8f3542a29 100644 --- a/PlexRequests.UI/Startup.cs +++ b/PlexRequests.UI/Startup.cs @@ -28,13 +28,17 @@ using System.Diagnostics; using Ninject; using Ninject.Planning.Bindings.Resolvers; - +using Ninject.Syntax; using NLog; using Owin; +using PlexRequests.Api.Interfaces; using PlexRequests.Core; using PlexRequests.Core.Migration; +using PlexRequests.Core.SettingModels; +using PlexRequests.Services.Interfaces; using PlexRequests.Services.Jobs; +using PlexRequests.Services.Notification; using PlexRequests.Store.Models; using PlexRequests.Store.Repository; using PlexRequests.UI.Helpers; @@ -89,10 +93,8 @@ public void Configuration(IAppBuilder app) jobSettings.Update(scheduledJobse); } scheduler.StartScheduler(); - - - + SubscribeAllObservers(kernel); } catch (Exception exception) @@ -101,5 +103,37 @@ public void Configuration(IAppBuilder app) throw; } } + + private void SubscribeAllObservers(IResolutionRoot container) + { + var notificationService = container.Get(); + + var emailSettingsService = container.Get>(); + var emailSettings = emailSettingsService.GetSettings(); + SubScribeOvserver(emailSettings, notificationService ,new EmailMessageNotification(emailSettingsService)); + + + var pushbulletService = container.Get>(); + var pushbulletSettings = pushbulletService.GetSettings(); + SubScribeOvserver(pushbulletSettings, notificationService, new PushbulletNotification(container.Get(), pushbulletService)); + + + var pushoverService = container.Get>(); + var pushoverSettings = pushoverService.GetSettings(); + SubScribeOvserver(pushoverSettings, notificationService, new PushoverNotification(container.Get(), pushoverService)); + + var slackService = container.Get>(); + var slackSettings = slackService.GetSettings(); + SubScribeOvserver(slackSettings, notificationService, new SlackNotification(container.Get(), slackService)); + } + + private void SubScribeOvserver(T settings, INotificationService notificationService, INotification notification) + where T : NotificationSettings + { + if (settings.Enabled) + { + notificationService.Subscribe(notification); + } + } } } \ No newline at end of file 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/Views/Admin/Settings.cshtml b/PlexRequests.UI/Views/Admin/Settings.cshtml index 653e3d734..fb27fee20 100644 --- a/PlexRequests.UI/Views/Admin/Settings.cshtml +++ b/PlexRequests.UI/Views/Admin/Settings.cshtml @@ -19,18 +19,6 @@ { formAction = "/" + baseUrl.ToHtmlString() + formAction; } - var plexTheme = string.Empty; - var originalTheme = string.Empty; - - if (!string.IsNullOrEmpty(Model.ThemeName)) - { - plexTheme = Model.ThemeName.Equals(Themes.PlexTheme) ? "selected=\"selected\"" : string.Empty; - originalTheme = Model.ThemeName.Equals(Themes.OriginalTheme) ? "selected=\"selected\"" : string.Empty; - } - else - { - plexTheme = "selected=\"selected\""; - } }
@@ -69,16 +57,6 @@
- -
- -
- -
-
@Html.Checkbox(Model.SearchForMovies,"SearchForMovies","Search for Movies") diff --git a/PlexRequests.UI/Views/Customization/Customization.cshtml b/PlexRequests.UI/Views/Customization/Customization.cshtml new file mode 100644 index 000000000..d0e2d4720 --- /dev/null +++ b/PlexRequests.UI/Views/Customization/Customization.cshtml @@ -0,0 +1,89 @@ +@using PlexRequests.UI.Helpers +@inherits Nancy.ViewEngines.Razor.NancyRazorViewBase +@Html.Partial("Shared/Partial/_Sidebar") +@{ + var plexTheme = string.Empty; + var originalTheme = string.Empty; + + if (!string.IsNullOrEmpty(Model.ThemeName)) + { + plexTheme = Model.ThemeName.Equals(Themes.PlexTheme) ? "selected=\"selected\"" : string.Empty; + originalTheme = Model.ThemeName.Equals(Themes.OriginalTheme) ? "selected=\"selected\"" : string.Empty; + } + else + { + plexTheme = "selected=\"selected\""; + } +} +
+ +
+ Customization Settings + + + +
+ + +
+ +
+
+ +
+ +
+ +
+
+ +
+
+ +
+
+ +
+ +
+ + + + \ No newline at end of file diff --git a/PlexRequests.UI/Views/Search/Index.cshtml b/PlexRequests.UI/Views/Search/Index.cshtml index 64446a264..b565ba234 100644 --- a/PlexRequests.UI/Views/Search/Index.cshtml +++ b/PlexRequests.UI/Views/Search/Index.cshtml @@ -37,11 +37,6 @@ } - - diff --git a/PlexRequests.UI/Views/Shared/Partial/_Head.cshtml b/PlexRequests.UI/Views/Shared/Partial/_Head.cshtml index a57044559..3524f3379 100644 --- a/PlexRequests.UI/Views/Shared/Partial/_Head.cshtml +++ b/PlexRequests.UI/Views/Shared/Partial/_Head.cshtml @@ -12,13 +12,19 @@ { url = "/" + baseUrl.ToHtmlString(); } + var title = UI.Layout_Title; + var customName = Html.GetApplicationName().ToHtmlString(); + if (!string.IsNullOrEmpty(customName)) + { + title = customName; + } } - @UI.Layout_Title + @title diff --git a/PlexRequests.UI/Views/Shared/Partial/_Navbar.cshtml b/PlexRequests.UI/Views/Shared/Partial/_Navbar.cshtml index cf7b42dc1..f94fa0881 100644 --- a/PlexRequests.UI/Views/Shared/Partial/_Navbar.cshtml +++ b/PlexRequests.UI/Views/Shared/Partial/_Navbar.cshtml @@ -15,6 +15,13 @@ { url = "/" + baseUrl.ToHtmlString(); } + + var title = UI.Layout_Title; + var customName = Html.GetApplicationName().ToHtmlString(); + if (!string.IsNullOrEmpty(customName)) + { + title = customName; + } } @@ -28,7 +35,7 @@ - @UI.Layout_Title + @title