Merge pull request #927 from tidusjar/dev

Dev
This commit is contained in:
Jamie 2017-01-13 15:20:23 +00:00 committed by GitHub
commit 50f86ee566
13 changed files with 465 additions and 108 deletions

View file

@ -35,5 +35,23 @@ namespace Ombi.Core.SettingModels
/// The CSS name of the theme we want /// The CSS name of the theme we want
/// </summary> /// </summary>
public string ThemeName { get; set; } public string ThemeName { get; set; }
/// <summary>
/// Admin Only.
/// </summary>
/// <value>
/// The default filter.
/// </value>
public int DefaultFilter { get; set; }
/// <summary>
/// Admin only.
/// </summary>
/// <value>
/// The default sort.
/// </value>
public int DefaultSort { get; set; }
public int DefaultLang { get; set; }
} }
} }

View file

@ -82,6 +82,19 @@ namespace Ombi.Helpers
if (descriptionAttributes == null) return string.Empty; if (descriptionAttributes == null) return string.Empty;
return (descriptionAttributes.Length > 0) ? descriptionAttributes[0].Name : value.ToString(); return (descriptionAttributes.Length > 0) ? descriptionAttributes[0].Name : value.ToString();
} }
public static string GetDisplayDescription(T value)
{
var fieldInfo = value.GetType().GetField(value.ToString());
var descriptionAttributes = fieldInfo.GetCustomAttributes(
typeof(DisplayAttribute), false) as DisplayAttribute[];
if (descriptionAttributes[0].ResourceType != null)
return LookupResource(descriptionAttributes[0].ResourceType, descriptionAttributes[0].Description);
if (descriptionAttributes == null) return string.Empty;
return (descriptionAttributes.Length > 0) ? descriptionAttributes[0].Description : value.ToString();
}
public static T GetValueFromName(string name) public static T GetValueFromName(string name)
{ {

View file

@ -16,40 +16,50 @@ var base = $('#baseUrl').text();
var tvLoaded = false; var tvLoaded = false;
var albumLoaded = false; var albumLoaded = false;
var isAdmin = $('#isAdmin').text(); var isAdmin = $('#isAdmin').text() === "True";
var defaultFiler = isAdmin == 'True' ? '.approved-fase' : 'all';
var adminFilter = $('#adminFilter').text();
var adminFilterText = $('#adminFilterText').text();
var adminSort = $('#adminSort').text();
var adminSortText = $('#adminSortText').text();
var defaultFilterText = $('#defaultFilterText').text();
var defaultSortText = $('#defaultSortText').text();
$("#filterText").text(isAdmin ? adminFilterText : defaultFilterText);
$("#sortText").text(isAdmin ? adminSortText : defaultSortText);
// Loop through all the filters to set the correct one to checked
selectCheckboxes('.filter', isAdmin ? adminFilterText : defaultFilterText);
selectCheckboxes('.sort', isAdmin ? adminSortText : defaultSortText);
function selectCheckboxes(className, text) {
$(className).each(function (i, obj) {
var $element = $(obj.children[0]);
if (obj.text === " " + text) {
$element.removeClass('fa-square-o').addClass('fa-check-square');
} else {
if ($element.hasClass('fa-check-square')) {
$element.removeClass('fa-check-square').addClass('fa-square-o');
}
}
});
}
var mixItUpDefault = { var mixItUpDefault = {
animation: { enable: true }, animation: { enable: true },
load: { load: {
filter: defaultFiler, filter: isAdmin ? adminFilter : 'all',
sort: 'requestorder:desc' sort: isAdmin ? adminSort : 'requestorder:desc'
}, },
layout: { layout: {
display: 'block' display: 'block'
}, },
callbacks: { callbacks: {
onMixStart: function (state, futureState) { onMixStart: function (state, futureState) {
//futureState.activeSort // sort
//futureState.activeFilter // next filter
// The below is TODO for saving the users filter and sort order
//var url = createBaseUrl(base, '/requests/UpdateFilters');
//$.ajax({
// type: 'post',
// url: url,
// data: {sort:futureState.activeSort, filter:futureState.activeFilte},
// dataType: "json",
// success: function (response) {
// console.log("saved filter and sort order");
// },
// error: function (e) {
// console.log(e);
// generateNotify("Something went wrong saving your filter!", "danger");
// }
//});
$('.mix', this).removeAttr('data-bound').removeData('bound'); // fix for animation issues in other tabs $('.mix', this).removeAttr('data-bound').removeData('bound'); // fix for animation issues in other tabs
} }
} }
@ -295,6 +305,9 @@ $('.sort', '.dropdown-menu').click(function (e) {
var $this = $(this); var $this = $(this);
$('.fa-check-square', $this.parents('.dropdown-menu:first')).removeClass('fa-check-square').addClass('fa-square-o'); $('.fa-check-square', $this.parents('.dropdown-menu:first')).removeClass('fa-check-square').addClass('fa-square-o');
$this.children('.fa').first().removeClass('fa-square-o').addClass('fa-check-square'); $this.children('.fa').first().removeClass('fa-square-o').addClass('fa-check-square');
$("#sortText").fadeOut(function () {
$(this).text($this.text().trim());
}).fadeIn();
}); });

View file

@ -0,0 +1,97 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2017 Jamie Rees
// File: CustomizationViewModel.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.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using Ombi.Core.SettingModels;
using Ombi.UI.Models.UI;
namespace Ombi.UI.Models.Admin
{
public class CustomizationViewModel
{
public CustomizationSettings Settings { get; set; }
public List<Dropdown<Languages>> LanguageDropdown { get; } = new List<Dropdown<Languages>>();
public List<Dropdown<FilterOptions>> FilterOptions { get; } = new List<Dropdown<FilterOptions>>();
public List<Dropdown<SortOptions>> SortOptions { get; set; } = new List<Dropdown<SortOptions>>();
}
public enum FilterOptions
{
[Display(Name = "all", Description = "All")]
All,
[Display(Name = ".approved-true", Description = "Approved")]
Approved,
[Display(Name = ".approved-false", Description = "Not Approved")]
NotApproved,
[Display(Name = ".available-true", Description = "Available")]
Available,
[Display(Name = ".available-false", Description = "Not Available")]
NotAvailable,
[Display(Name = ".released-true", Description = "Released")]
Released,
[Display(Name = ".released-false", Description = "Not Released")]
NotReleased
}
public enum SortOptions
{
[Display(Name = "requestorder:desc", Description = "Latest Requests")]
LatestRequests,
[Display(Name = "requestorder:asc", Description = "Oldest Requests")]
OldestRequests,
[Display(Name = "releaseorder:desc", Description = "Latest Releases")]
LatestRelease,
[Display(Name = "releaseorder:asc", Description = "Oldest Releases")]
OldestRelease
}
public enum Languages
{
[Display(ResourceType = typeof(Resources.UI), Name = "Layout_English")]
en,
[Display(ResourceType = typeof(Resources.UI), Name = "Layout_French")]
fr,
[Display(ResourceType = typeof(Resources.UI), Name = "Layout_Dutch")]
nl,
[Display(ResourceType = typeof(Resources.UI), Name = "Layout_Spanish")]
es,
[Display(ResourceType = typeof(Resources.UI), Name = "Layout_German")]
de,
[Display(ResourceType = typeof(Resources.UI), Name = "Layout_Danish")]
da,
[Display(ResourceType = typeof(Resources.UI), Name = "Layout_Portuguese")]
pt,
[Display(ResourceType = typeof(Resources.UI), Name = "Layout_Swedish")]
sv,
[Display(ResourceType = typeof(Resources.UI), Name = "Layout_Italian")]
it
}
}

View file

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

View file

@ -0,0 +1,35 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2017 Jamie Rees
// File: Dropdown.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 Ombi.UI.Models.UI
{
public class Dropdown<T>
{
public bool Selected { get; set; }
public string Name { get; set; }
public T Value { get; set; }
}
}

View file

@ -1,4 +1,5 @@
#region Copyright #region Copyright
// /************************************************************************ // /************************************************************************
// Copyright (c) 2016 Jamie Rees // Copyright (c) 2016 Jamie Rees
// File: CustomizationModule.cs // File: CustomizationModule.cs
@ -23,43 +24,94 @@
// 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.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Nancy; using Nancy;
using Nancy.ModelBinding; using Nancy.ModelBinding;
using Nancy.Responses.Negotiation; using Nancy.Responses.Negotiation;
using Ombi.Core; using Ombi.Core;
using Ombi.Core.SettingModels; using Ombi.Core.SettingModels;
using Ombi.Helpers;
using Ombi.Helpers.Permissions; using Ombi.Helpers.Permissions;
using Ombi.UI.Models; using Ombi.UI.Models;
using Ombi.UI.Models.Admin;
using Ombi.UI.Models.UI;
using TMDbLib.Utilities;
using ISecurityExtensions = Ombi.Core.ISecurityExtensions; using ISecurityExtensions = Ombi.Core.ISecurityExtensions;
namespace Ombi.UI.Modules.Admin namespace Ombi.UI.Modules.Admin
{ {
public class CustomizationModule : BaseModule public class CustomizationModule : BaseModule
{ {
public CustomizationModule(ISettingsService<PlexRequestSettings> settingsService, ISettingsService<CustomizationSettings> cust, ISecurityExtensions security) : base("admin", settingsService, security) public CustomizationModule(ISettingsService<PlexRequestSettings> settingsService,
ISettingsService<CustomizationSettings> cust, ISecurityExtensions security)
: base("admin", settingsService, security)
{ {
Before += (ctx) => Security.AdminLoginRedirect(Permissions.Administrator, ctx); Before += (ctx) => Security.AdminLoginRedirect(Permissions.Administrator, ctx);
Settings = cust; Settings = cust;
Get["/customization", true] = async (x,ct) => await Index(); Get["/customization", true] = async (x, ct) => await Index();
Post["/customization", true] = async (x,ct) => await Save(); Post["/customization", true] = async (x, ct) => await Save();
} }
private ISettingsService<CustomizationSettings> Settings { get; } private ISettingsService<CustomizationSettings> Settings { get; }
private async Task<Negotiator> Index() private async Task<Negotiator> Index()
{ {
var model = await Settings.GetSettingsAsync(); var model = await Settings.GetSettingsAsync();
return View["customization", model]; var viewModel = new CustomizationViewModel
{
Settings = model,
SortOptions = new List<Dropdown<SortOptions>>()
};
foreach (var value in EnumHelper<SortOptions>.GetValues(SortOptions.LatestRelease))
{
viewModel.SortOptions.Add(new Dropdown<SortOptions>
{
Value = value,
Name = EnumHelper<SortOptions>.GetDisplayDescription(value),
Selected = model.DefaultSort == (int) value
});
}
foreach (var value in EnumHelper<FilterOptions>.GetValues(FilterOptions.Available))
{
viewModel.FilterOptions.Add(new Dropdown<FilterOptions>
{
Value = value,
Name = EnumHelper<FilterOptions>.GetDisplayDescription(value),
Selected = model.DefaultFilter == (int) value
});
}
foreach (var value in EnumHelper<Languages>.GetValues(Languages.en))
{
viewModel.LanguageDropdown.Add(new Dropdown<Languages>
{
Value = value,
Name = EnumHelper<Languages>.GetDisplayValue(value),
Selected = model.DefaultLang == (int) value
});
}
return View["customization", viewModel];
} }
private async Task<Response> Save() private async Task<Response> Save()
{ {
try
{
var model = this.Bind<CustomizationSettings>(); var model = this.Bind<CustomizationSettings>();
var result = await Settings.SaveSettingsAsync(model); var result = await Settings.SaveSettingsAsync(model);
@ -67,6 +119,12 @@ namespace Ombi.UI.Modules.Admin
return Response.AsJson(result return Response.AsJson(result
? new JsonResponseModel { Result = true } ? new JsonResponseModel { Result = true }
: new JsonResponseModel { Result = false, Message = "We could not save to the database, please try again" }); : new JsonResponseModel { Result = false, Message = "We could not save to the database, please try again" });
}
catch (Exception e)
{
throw;
}
} }
} }
} }

View file

@ -43,6 +43,8 @@ using Ombi.Services.Interfaces;
using Ombi.Services.Notification; using Ombi.Services.Notification;
using Ombi.Store; using Ombi.Store;
using Ombi.UI.Models; using Ombi.UI.Models;
using Ombi.UI.Models.Admin;
using Ombi.UI.Models.Requests;
using Action = Ombi.Helpers.Analytics.Action; using Action = Ombi.Helpers.Analytics.Action;
using ISecurityExtensions = Ombi.Core.ISecurityExtensions; using ISecurityExtensions = Ombi.Core.ISecurityExtensions;
@ -64,7 +66,8 @@ namespace Ombi.UI.Modules
ICacheProvider cache, ICacheProvider cache,
IAnalytics an, IAnalytics an,
INotificationEngine engine, INotificationEngine engine,
ISecurityExtensions security) : base("requests", prSettings, security) ISecurityExtensions security,
ISettingsService<CustomizationSettings> customSettings) : base("requests", prSettings, security)
{ {
Service = service; Service = service;
PrSettings = prSettings; PrSettings = prSettings;
@ -79,6 +82,7 @@ namespace Ombi.UI.Modules
Cache = cache; Cache = cache;
Analytics = an; Analytics = an;
NotificationEngine = engine; NotificationEngine = engine;
CustomizationSettings = customSettings;
Get["/", true] = async (x, ct) => await LoadRequests(); Get["/", true] = async (x, ct) => await LoadRequests();
Get["/movies", true] = async (x, ct) => await GetMovies(); Get["/movies", true] = async (x, ct) => await GetMovies();
@ -92,7 +96,7 @@ namespace Ombi.UI.Modules
Post["/changeavailability", true] = async (x, ct) => await ChangeRequestAvailability((int)Request.Form.Id, (bool)Request.Form.Available); Post["/changeavailability", true] = async (x, ct) => await ChangeRequestAvailability((int)Request.Form.Id, (bool)Request.Form.Available);
Post["/UpdateFilters", true] = async (x, ct) => await UpdateFilters(); Get["/UpdateFilters", true] = async (x, ct) => await GetFilterAndSortSettings();
} }
private static Logger Log = LogManager.GetCurrentClassLogger(); private static Logger Log = LogManager.GetCurrentClassLogger();
@ -104,6 +108,7 @@ namespace Ombi.UI.Modules
private ISettingsService<SonarrSettings> SonarrSettings { get; } private ISettingsService<SonarrSettings> SonarrSettings { get; }
private ISettingsService<SickRageSettings> SickRageSettings { get; } private ISettingsService<SickRageSettings> SickRageSettings { get; }
private ISettingsService<CouchPotatoSettings> CpSettings { get; } private ISettingsService<CouchPotatoSettings> CpSettings { get; }
private ISettingsService<CustomizationSettings> CustomizationSettings { get; }
private ISonarrApi SonarrApi { get; } private ISonarrApi SonarrApi { get; }
private ISickRageApi SickRageApi { get; } private ISickRageApi SickRageApi { get; }
private ICouchPotatoApi CpApi { get; } private ICouchPotatoApi CpApi { get; }
@ -113,7 +118,9 @@ namespace Ombi.UI.Modules
private async Task<Negotiator> LoadRequests() private async Task<Negotiator> LoadRequests()
{ {
var settings = await PrSettings.GetSettingsAsync(); var settings = await PrSettings.GetSettingsAsync();
return View["Index", settings]; var custom = await CustomizationSettings.GetSettingsAsync();
return View["Index", new RequestsIndexViewModel { CustomizationSettings = custom, PlexRequestSettings = settings }];
} }
private async Task<Response> GetMovies() private async Task<Response> GetMovies()
@ -223,7 +230,7 @@ namespace Ombi.UI.Modules
} }
catch (Exception e) catch (Exception e)
{ {
Log.Info(e); Log.Info(e);
} }
} }
@ -312,7 +319,7 @@ namespace Ombi.UI.Modules
return Response.AsJson(new JsonResponseModel { Result = true }); return Response.AsJson(new JsonResponseModel { Result = true });
} }
Analytics.TrackEventAsync(Category.Requests, Action.Delete, "Delete Request", Username, CookieHelper.GetAnalyticClientId(Cookies)); Analytics.TrackEventAsync(Category.Requests, Action.Delete, "Delete Request", Username, CookieHelper.GetAnalyticClientId(Cookies));
var currentEntity = await Service.GetAsync(requestid); var currentEntity = await Service.GetAsync(requestid);
@ -408,11 +415,20 @@ namespace Ombi.UI.Modules
: new { Result = false, Available = false, Message = "Could not update the availability, please try again or check the logs" }); : new { Result = false, Available = false, Message = "Could not update the availability, please try again or check the logs" });
} }
private async Task<Response> UpdateFilters() private async Task<Response> GetFilterAndSortSettings()
{ {
var s = await CustomizationSettings.GetSettingsAsync();
var sortVal = EnumHelper<SortOptions>.GetDisplayValue((SortOptions)s.DefaultSort);
var filterVal = EnumHelper<FilterOptions>.GetDisplayValue((FilterOptions)s.DefaultFilter);
return Response.AsJson(""); var vm = new
{
DefaultSort = sortVal,
DefaultFilter = filterVal
};
return Response.AsJson(vm);
} }
} }
} }

View file

@ -118,6 +118,7 @@
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.ComponentModel.DataAnnotations" />
<Reference Include="System.Configuration" /> <Reference Include="System.Configuration" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
<Reference Include="System.Data" /> <Reference Include="System.Data" />
@ -233,6 +234,7 @@
<Compile Include="ModelDataProviders\UserUpdateViewModelDataProvider.cs" /> <Compile Include="ModelDataProviders\UserUpdateViewModelDataProvider.cs" />
<Compile Include="ModelDataProviders\RequestedModelDataProvider.cs" /> <Compile Include="ModelDataProviders\RequestedModelDataProvider.cs" />
<Compile Include="Models\AboutAdminViewModel.cs" /> <Compile Include="Models\AboutAdminViewModel.cs" />
<Compile Include="Models\Admin\CustomizationViewModel.cs" />
<Compile Include="Models\DatatablesModel.cs" /> <Compile Include="Models\DatatablesModel.cs" />
<Compile Include="Models\EpisodeListViewModel.cs" /> <Compile Include="Models\EpisodeListViewModel.cs" />
<Compile Include="Models\EpisodeRequestModel.cs" /> <Compile Include="Models\EpisodeRequestModel.cs" />
@ -243,10 +245,12 @@
<Compile Include="Models\LandingPageViewModel.cs" /> <Compile Include="Models\LandingPageViewModel.cs" />
<Compile Include="Models\MovieSearchType.cs" /> <Compile Include="Models\MovieSearchType.cs" />
<Compile Include="Models\QualityModel.cs" /> <Compile Include="Models\QualityModel.cs" />
<Compile Include="Models\Requests\RequestsIndexViewModel.cs" />
<Compile Include="Models\ScheduledJobsViewModel.cs" /> <Compile Include="Models\ScheduledJobsViewModel.cs" />
<Compile Include="Models\SearchViewModel.cs" /> <Compile Include="Models\SearchViewModel.cs" />
<Compile Include="Models\SearchMusicViewModel.cs" /> <Compile Include="Models\SearchMusicViewModel.cs" />
<Compile Include="Models\SearchMovieViewModel.cs" /> <Compile Include="Models\SearchMovieViewModel.cs" />
<Compile Include="Models\UI\Dropdown.cs" />
<Compile Include="Models\UserManagement\DeleteUserViewModel.cs" /> <Compile Include="Models\UserManagement\DeleteUserViewModel.cs" />
<Compile Include="Models\UserManagement\UserUpdateViewModel.cs" /> <Compile Include="Models\UserManagement\UserUpdateViewModel.cs" />
<Compile Include="Modules\Admin\AboutModule.cs" /> <Compile Include="Modules\Admin\AboutModule.cs" />

View file

@ -1,14 +1,14 @@
@using Ombi.UI.Helpers @using Ombi.UI.Helpers
@inherits Nancy.ViewEngines.Razor.NancyRazorViewBase<Ombi.Core.SettingModels.CustomizationSettings> @inherits Nancy.ViewEngines.Razor.NancyRazorViewBase<Ombi.UI.Models.Admin.CustomizationViewModel>
@Html.Partial("Shared/Partial/_Sidebar") @Html.Partial("Shared/Partial/_Sidebar")
@{ @{
var plexTheme = string.Empty; var plexTheme = string.Empty;
var originalTheme = string.Empty; var originalTheme = string.Empty;
if (!string.IsNullOrEmpty(Model.ThemeName)) if (!string.IsNullOrEmpty(Model.Settings.ThemeName))
{ {
plexTheme = Model.ThemeName.Equals(Themes.PlexTheme) ? "selected=\"selected\"" : string.Empty; plexTheme = Model.Settings.ThemeName.Equals(Themes.PlexTheme) ? "selected=\"selected\"" : string.Empty;
originalTheme = Model.ThemeName.Equals(Themes.OriginalTheme) ? "selected=\"selected\"" : string.Empty; originalTheme = Model.Settings.ThemeName.Equals(Themes.OriginalTheme) ? "selected=\"selected\"" : string.Empty;
} }
else else
{ {
@ -26,7 +26,7 @@
<label for="ApplicationName" class="control-label">Application Name</label> <label for="ApplicationName" class="control-label">Application Name</label>
<div> <div>
<input type="text" class="form-control form-control-custom " id="ApplicationName" name="ApplicationName" placeholder="Application Name" value="@Model.ApplicationName"> <input type="text" class="form-control form-control-custom " id="ApplicationName" name="ApplicationName" placeholder="Application Name" value="@Model.Settings.ApplicationName">
</div> </div>
</div> </div>
@ -39,6 +39,69 @@
</select> </select>
</div> </div>
</div> </div>
<div class="form-group">
<label for="lang" class="control-label">Default Language</label>
<div id="langSelect">
<select class="form-control form-control-custom" id="lang">
@foreach (var l in Model.LanguageDropdown)
{
var enumVal = (int)l.Value;
if (l.Selected)
{
<option selected="selected" class="form-control form-control-custom" value="@enumVal">@l.Name</option>
}
else
{
<option class="form-control form-control-custom" value="@enumVal">@l.Name</option>
}
}
</select>
</div>
</div>
<div class="form-group">
<label for="sort" class="control-label">Default Admin Sort Order</label>
<div id="sortSelect">
<select class="form-control form-control-custom" id="sort">
@foreach (var l in Model.SortOptions)
{
var enumVal = (int) l.Value;
if (l.Selected)
{
<option selected="selected" class="form-control form-control-custom" value="@enumVal">@l.Name</option>
}
else
{
<option class="form-control form-control-custom" value="@enumVal">@l.Name</option>
}
}
</select>
</div>
</div>
<div class="form-group">
<label for="filter" class="control-label">Default Admin Filter</label>
<div id="filterSelect">
<select class="form-control form-control-custom" id="filter">
@foreach (var l in Model.FilterOptions)
{
var enumVal = (int)l.Value;
if (l.Selected)
{
<option selected="selected" class="form-control form-control-custom" value="@enumVal">@l.Name</option>
}
else
{
<option class="form-control form-control-custom" value="@enumVal">@l.Name</option>
}
}
</select>
</div>
</div>
<div class="form-group"> <div class="form-group">
<div> <div>
@ -60,10 +123,17 @@
e.preventDefault(); e.preventDefault();
var theme = $("#themes option:selected").val(); var theme = $("#themes option:selected").val();
var lang = $('#langSelect option:selected').val();
var sort = $('#sortSelect option:selected').val();
var filter = $('#filterSelect option:selected').val();
var $form = $("#mainForm"); var $form = $("#mainForm");
var data = $form.serialize(); var data = $form.serialize();
data = data + "&themeName=" + theme; data = data +
"&themeName=" + theme +
"&DefaultLang=" + lang +
"&DefaultSort=" + sort +
"&DefaultFilter=" + filter;
$.ajax({ $.ajax({
type: $form.prop("method"), type: $form.prop("method"),

View file

@ -1,8 +1,12 @@
@using Nancy.Security @using Nancy.Security
@using Nancy.Security @using Nancy.Security
@using Ombi.Helpers
@using Ombi.Helpers.Permissions @using Ombi.Helpers.Permissions
@using Ombi.UI.Helpers @using Ombi.UI.Helpers
@using Ombi.UI.Models.Admin
@using Ombi.UI.Resources @using Ombi.UI.Resources
@inherits Nancy.ViewEngines.Razor.NancyRazorViewBase<Ombi.UI.Models.Requests.RequestsIndexViewModel>
@{ @{
var baseUrl = Html.GetBaseUrl(); var baseUrl = Html.GetBaseUrl();
var formAction = string.Empty; var formAction = string.Empty;
@ -11,29 +15,42 @@
{ {
formAction = "/" + baseUrl.ToHtmlString(); formAction = "/" + baseUrl.ToHtmlString();
} }
var sortText = EnumHelper<SortOptions>.GetDisplayDescription((SortOptions) Model.CustomizationSettings.DefaultSort);
var filterText = EnumHelper<FilterOptions>.GetDisplayDescription((FilterOptions) Model.CustomizationSettings.DefaultFilter);
var defaultFilter = EnumHelper<FilterOptions>.GetDisplayValue((FilterOptions) Model.CustomizationSettings.DefaultFilter);
var defaultSort = EnumHelper<SortOptions>.GetDisplayValue((SortOptions) Model.CustomizationSettings.DefaultSort);
} }
<div> <div>
<div hidden="hidden" id="isAdmin" >@isAdmin</div> <div hidden="hidden" id="isAdmin">@isAdmin</div>
<div hidden="hidden" id="adminFilter">@defaultFilter</div>
<div hidden="hidden" id="adminFilterText">@filterText</div>
<div hidden="hidden" id="adminSort">@defaultSort</div>
<div hidden="hidden" id="adminSortText">@sortText</div>
<div hidden="hidden" id="defaultSortText">@UI.Requests_Order_LatestRequests</div>
<div hidden="hidden" id="defaultFilterText">@UI.Requests_Filter_All</div>
<h1>@UI.Requests_Title</h1> <h1>@UI.Requests_Title</h1>
<h4>@UI.Requests_Paragraph</h4> <h4>@UI.Requests_Paragraph</h4>
<br/> <br />
<!-- Nav tabs --> <!-- Nav tabs -->
<ul id="nav-tabs" class="nav nav-tabs" role="tablist"> <ul id="nav-tabs" class="nav nav-tabs" role="tablist">
@if (Model.SearchForMovies) @if (Model.PlexRequestSettings.SearchForMovies)
{ {
<li role="presentation" class="active"><a href="#MoviesTab" aria-controls="home" role="tab" data-toggle="tab"><i class="fa fa-film"></i> @UI.Requests_MoviesTabTitle</a></li> <li role="presentation" class="active"><a href="#MoviesTab" aria-controls="home" role="tab" data-toggle="tab"><i class="fa fa-film"></i> @UI.Requests_MoviesTabTitle</a></li>
} }
@if (Model.SearchForTvShows) @if (Model.PlexRequestSettings.SearchForTvShows)
{ {
<li role="presentation"><a href="#TvShowTab" aria-controls="profile" role="tab" data-toggle="tab"><i class="fa fa-television"></i>@UI.Requests_TvShowTabTitle</a></li> <li role="presentation"><a href="#TvShowTab" aria-controls="profile" role="tab" data-toggle="tab"><i class="fa fa-television"></i>@UI.Requests_TvShowTabTitle</a></li>
} }
@if (Model.SearchForMusic) @if (Model.PlexRequestSettings.SearchForMusic)
{ {
<li role="presentation"><a href="#MusicTab" aria-controls="profile" role="tab" data-toggle="tab"><i class="fa fa-music"></i> @UI.Requests_AlbumsTabTitle</a></li> <li role="presentation"><a href="#MusicTab" aria-controls="profile" role="tab" data-toggle="tab"><i class="fa fa-music"></i> @UI.Requests_AlbumsTabTitle</a></li>
} }
</ul> </ul>
<br/> <br />
<!-- Tab panes --> <!-- Tab panes -->
<div class="tab-content contentList"> <div class="tab-content contentList">
@ -43,53 +60,33 @@
<div class="btn-group btn-group-separated"> <div class="btn-group btn-group-separated">
@if (isAdmin) @if (isAdmin)
{ {
@if (Model.SearchForMovies) @if (Model.PlexRequestSettings.SearchForMovies)
{ {
<button id="deleteMovies" class="btn btn-warning-outline delete-category" type="submit"><i class="fa fa-trash"></i> @UI.Requests_DeleteMovies</button> <button id="deleteMovies" class="btn btn-warning-outline delete-category" type="submit"><i class="fa fa-trash"></i> @UI.Requests_DeleteMovies</button>
<button id="approveMovies" class="btn btn-success-outline approve-category" type="submit"><i class="fa fa-plus"></i> @UI.Requests_ApproveMovies</button> <button id="approveMovies" class="btn btn-success-outline approve-category" type="submit"><i class="fa fa-plus"></i> @UI.Requests_ApproveMovies</button>
} }
@if (Model.SearchForTvShows) @if (Model.PlexRequestSettings.SearchForTvShows)
{ {
<button id="deleteTVShows" class="btn btn-warning-outline delete-category" type="submit" style="display: none;"><i class="fa fa-trash"></i> @UI.Requests_DeleteTVShows</button> <button id="deleteTVShows" class="btn btn-warning-outline delete-category" type="submit" style="display: none;"><i class="fa fa-trash"></i> @UI.Requests_DeleteTVShows</button>
<button id="approveTVShows" class="btn btn-success-outline approve-category" type="submit" style="display: none;"><i class="fa fa-plus"></i> @UI.Requests_ApproveTvShows</button> <button id="approveTVShows" class="btn btn-success-outline approve-category" type="submit" style="display: none;"><i class="fa fa-plus"></i> @UI.Requests_ApproveTvShows</button>
} }
@if (Model.SearchForMusic) @if (Model.PlexRequestSettings.SearchForMusic)
{ {
<button id="deleteMusic" class="btn btn-warning-outline delete-category" type="submit" style="display: none;"><i class="fa fa-trash"></i> @UI.Requests_DeleteMusic</button> <button id="deleteMusic" class="btn btn-warning-outline delete-category" type="submit" style="display: none;"><i class="fa fa-trash"></i> @UI.Requests_DeleteMusic</button>
<button id="approveMusic" class="btn btn-success-outline approve-category" type="submit" style="display: none;"><i class="fa fa-plus"></i> @UI.Requests_ApproveMusic</button> <button id="approveMusic" class="btn btn-success-outline approve-category" type="submit" style="display: none;"><i class="fa fa-plus"></i> @UI.Requests_ApproveMusic</button>
} }
} }
</div> </div>
<div class="btn-group"> <div class="btn-group">
<a href="#" class="btn btn-primary-outline dropdown-toggle" data-toggle="dropdown" aria-expanded="false"> <a href="#" class="btn btn-primary-outline dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
@if (isAdmin) <span id="filterText">@UI.Requests_Filter_All</span>
{
<span id="filterText">@UI.Requests_Filter_NotApproved</span>
}
else
{
<span id="filterText">@UI.Requests_Filter_All</span>
}
<i class="fa fa-filter"></i> <i class="fa fa-filter"></i>
</a> </a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
@if (!isAdmin)
{ <li><a href="#" class="filter" data-filter="all"><i class="fa fa-check-square"></i> @UI.Requests_Filter_All</a></li>
<li><a href="#" class="filter" data-filter="all"><i class="fa fa-check-square"></i> @UI.Requests_Filter_All</a></li>
}
else
{
<li><a href="#" class="filter" data-filter="all"><i class="fa fa-square-o"></i> @UI.Requests_Filter_All</a></li>
}
<li><a href="#" class="filter" data-filter=".approved-true"><i class="fa fa-square-o"></i> @UI.Requests_Filter_Approved</a></li> <li><a href="#" class="filter" data-filter=".approved-true"><i class="fa fa-square-o"></i> @UI.Requests_Filter_Approved</a></li>
@if (isAdmin) <li><a href="#" class="filter" data-filter=".approved-false"><i class="fa fa-square-o"></i> @UI.Requests_Filter_NotApproved</a></li>
{
<li><a href="#" class="filter" data-filter=".approved-false"><i class="fa fa-check-square"></i> @UI.Requests_Filter_NotApproved</a></li>
}
else
{
<li><a href="#" class="filter" data-filter=".approved-false"><i class="fa fa-square-o"></i> @UI.Requests_Filter_NotApproved</a></li>
}
<li><a href="#" class="filter" data-filter=".available-true"><i class="fa fa-square-o"></i> @UI.Requests_Filter_Available</a></li> <li><a href="#" class="filter" data-filter=".available-true"><i class="fa fa-square-o"></i> @UI.Requests_Filter_Available</a></li>
<li><a href="#" class="filter" data-filter=".available-false"><i class="fa fa-square-o"></i> @UI.Requests_Filter_NotAvailable</a></li> <li><a href="#" class="filter" data-filter=".available-false"><i class="fa fa-square-o"></i> @UI.Requests_Filter_NotAvailable</a></li>
<li><a href="#" class="filter" data-filter=".released-true"><i class="fa fa-square-o"></i> @UI.Requests_Filter_Released</a></li> <li><a href="#" class="filter" data-filter=".released-true"><i class="fa fa-square-o"></i> @UI.Requests_Filter_Released</a></li>
@ -98,7 +95,7 @@
</div> </div>
<div class="btn-group"> <div class="btn-group">
<a href="#" class="btn btn-primary-outline dropdown-toggle" data-toggle="dropdown" aria-expanded="false"> <a href="#" class="btn btn-primary-outline dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
@UI.Requests_Order <span id="sortText">@UI.Requests_Order</span>
<i class="fa fa-sort"></i> <i class="fa fa-sort"></i>
</a> </a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
@ -111,40 +108,40 @@
</div> </div>
</div> </div>
</div> </div>
@if (Model.SearchForMovies) @if (Model.PlexRequestSettings.SearchForMovies)
{ {
<!-- Movie tab --> <!-- Movie tab -->
<div role="tabpanel" class="tab-pane active" id="MoviesTab"> <div role="tabpanel" class="tab-pane active" id="MoviesTab">
<br/> <br />
<br/> <br />
<!-- Movie content --> <!-- Movie content -->
<div id="movieList"> <div id="movieList">
</div> </div>
</div> </div>
} }
@if (Model.SearchForTvShows) @if (Model.PlexRequestSettings.SearchForTvShows)
{ {
<!-- TV tab --> <!-- TV tab -->
<div role="tabpanel" class="tab-pane" id="TvShowTab"> <div role="tabpanel" class="tab-pane" id="TvShowTab">
<br/> <br />
<br/> <br />
<!-- TV content --> <!-- TV content -->
<div id="tvList"> <div id="tvList">
</div> </div>
</div> </div>
} }
@if (Model.SearchForMusic) @if (Model.PlexRequestSettings.SearchForMusic)
{ {
<!-- Music tab --> <!-- Music tab -->
<div role="tabpanel" class="tab-pane" id="MusicTab"> <div role="tabpanel" class="tab-pane" id="MusicTab">
<br/> <br />
<br/> <br />
<!-- TV content --> <!-- TV content -->
<div id="musicList"> <div id="musicList">
</div> </div>
@ -202,12 +199,12 @@
<span class="label label-success">{{status}}</span> <span class="label label-success">{{status}}</span>
{{#if denied}} {{#if denied}}
<div> <div>
Denied: <i style="color:red;" class="fa fa-check"></i> Denied: <i style="color:red;" class="fa fa-check"></i>
{{#if deniedReason}} {{#if deniedReason}}
<span class="customTooltip" title="{{deniedReason}}"><i class="fa fa-info-circle"></i></span> <span class="customTooltip" title="{{deniedReason}}"><i class="fa fa-info-circle"></i></span>
{{/if}} {{/if}}
</div> </div>
{{/if}} {{/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>
@ -234,7 +231,7 @@
<i id="availableIcon{{requestId}}" class="fa fa-check"></i> <i id="availableIcon{{requestId}}" class="fa fa-check"></i>
{{/if_eq}} {{/if_eq}}
</div> </div>
{{#if_eq type "tv"}} {{#if_eq type "tv"}}
{{#if episodes}} {{#if episodes}}
Episodes: <span class="customTooltip" data-tooltip-content="#{{requestId}}toolTipContent"><i class="fa fa-info-circle"></i></span> Episodes: <span class="customTooltip" data-tooltip-content="#{{requestId}}toolTipContent"><i class="fa fa-info-circle"></i></span>
@ -284,7 +281,7 @@
<input name="reason" type="text" hidden="hidden" /> <input name="reason" type="text" hidden="hidden" />
<div class="btn-group btn-split"> <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-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"> <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="caret"></span>
<span class="sr-only">@UI.Requests_ToggleDropdown</span> <span class="sr-only">@UI.Requests_ToggleDropdown</span>
</button> </button>

View file

@ -1,4 +1,4 @@
ab@using Nancy.Security @using Nancy.Security
@using Nancy.Session @using Nancy.Session
@using Nancy; @using Nancy;
@using Ombi.Helpers @using Ombi.Helpers

View file

@ -13,9 +13,11 @@ ____
Here some of the features Ombi has: Here some of the features Ombi has:
* All your users to Request Movies, TV Shows (Whole series, whole seaons or even single episodes!) and Albums * All your users to Request Movies, TV Shows (Whole series, whole seaons or even single episodes!) and Albums
* Easily manage your requests * Easily manage your requests
* User mangement system (supports plex.tv accounts and local accounts) [NEW] * User mangement system (supports plex.tv accounts and local accounts) [NEW]
* Sending newsletters [NEW] * Sending newsletters [NEW]
* Fault Queue for requests (Buffer requests if Sonar/Couchpotato/SickRage is offline) [NEW] * Fault Queue for requests (Buffer requests if Sonar/Couchpotato/SickRage is offline) [NEW]
* Allow your users to report issues and manage them seperatly * Allow your users to report issues and manage them seperatly
* A landing page that will give you the availability of your Plex server and also add custom notification text to inform your users of downtime. * A landing page that will give you the availability of your Plex server and also add custom notification text to inform your users of downtime.
* Allow your users to get notifications! * Allow your users to get notifications!
@ -70,16 +72,13 @@ Looking for a Docker Image? Well [rogueosb](https://github.com/rogueosb/) has cr
We are looking for any contributions to the project! Just pick up a task, if you have any questions ask and i'll get straight on it! We are looking for any contributions to the project! Just pick up a task, if you have any questions ask and i'll get straight on it!
Please feel free to submit a pull request! Please feed free to submit a pull request!
# Donation # Donation
If you feel like donating you can [here!](https://paypal.me/PlexRequestsNet) If you feel like donating you can [here!](https://paypal.me/PlexRequestsNet)
### A massive thanks to everyone for all their help! ### A massive thanks to everyone for all their help!
## [Feathub](http://feathub.com/tidusjar/Ombi)
[![Feature Requests](http://feathub.com/tidusjar/Ombi?format=svg)](http://feathub.com/tidusjar/Ombi)
## Stats ## Stats
[![Throughput Graph](https://graphs.waffle.io/tidusjar/Ombi/throughput.svg)](https://waffle.io/tidusjar/Ombi/metrics/throughput) [![Throughput Graph](https://graphs.waffle.io/tidusjar/PlexRequests.Net/throughput.svg)](https://waffle.io/tidusjar/PlexRequests.Net/metrics/throughput)