pull/470/head
tidusjar 8 years ago
parent a19e81d1f8
commit c764d6557a

@ -1,35 +1,34 @@
#region Copyright #region Copyright
// /************************************************************************ // /************************************************************************
// Copyright (c) 2016 Jamie Rees // Copyright (c) 2016 Jamie Rees
// File: UserProperties.cs // File: UserProperties.cs
// Created By: Jamie Rees // Created By: Jamie Rees
// //
// Permission is hereby granted, free of charge, to any person obtaining // Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the // a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including // "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish, // without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to // distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to // permit persons to whom the Software is furnished to do so, subject to
// the following conditions: // the following conditions:
// //
// The above copyright notice and this permission notice shall be // The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software. // included in all copies or substantial portions of the Software.
// //
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// 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
namespace PlexRequests.Core.Models namespace PlexRequests.Core.Models
{ {
public class UserProperties public class UserProperties
{ {
public string EmailAddress { get; set; } public string EmailAddress { get; set; }
public bool NotifyOnRelease { get; set; } public string UserAlias { get; set; }
public bool NotifyOnApprove { get; set; } }
}
} }

@ -1,183 +1,191 @@
#region Copyright #region Copyright
// /************************************************************************ // /************************************************************************
// Copyright (c) 2016 Jamie Rees // Copyright (c) 2016 Jamie Rees
// File: UserMapper.cs // File: UserMapper.cs
// Created By: Jamie Rees // Created By: Jamie Rees
// //
// Permission is hereby granted, free of charge, to any person obtaining // Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the // a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including // "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish, // without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to // distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to // permit persons to whom the Software is furnished to do so, subject to
// the following conditions: // the following conditions:
// //
// The above copyright notice and this permission notice shall be // The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software. // included in all copies or substantial portions of the Software.
// //
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/ // ************************************************************************/
#endregion #endregion
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Security; using System.Security;
using System.Threading.Tasks;
using Nancy;
using Nancy.Authentication.Forms; using Nancy;
using Nancy.Security; using Nancy.Authentication.Forms;
using Nancy.Security;
using PlexRequests.Core.Models;
using PlexRequests.Helpers; using PlexRequests.Core.Models;
using PlexRequests.Store; using PlexRequests.Helpers;
using PlexRequests.Store.Repository; using PlexRequests.Store;
using PlexRequests.Store.Repository;
namespace PlexRequests.Core
{ namespace PlexRequests.Core
public class UserMapper : IUserMapper, ICustomUserMapper {
{ public class UserMapper : IUserMapper, ICustomUserMapper
public UserMapper(IRepository<UsersModel> repo) {
{ public UserMapper(IRepository<UsersModel> repo)
Repo = repo; {
} Repo = repo;
private static IRepository<UsersModel> Repo { get; set; } }
public IUserIdentity GetUserFromIdentifier(Guid identifier, NancyContext context) private static IRepository<UsersModel> Repo { get; set; }
{ public IUserIdentity GetUserFromIdentifier(Guid identifier, NancyContext context)
var user = Repo.Get(identifier.ToString()); {
var user = Repo.Get(identifier.ToString());
if (user == null)
{ if (user == null)
return null; {
} return null;
}
return new UserIdentity
{ return new UserIdentity
UserName = user.UserName, {
Claims = ByteConverterHelper.ReturnObject<string[]>(user.Claims) UserName = user.UserName,
}; Claims = ByteConverterHelper.ReturnObject<string[]>(user.Claims)
} };
}
public Guid? ValidateUser(string username, string password)
{ public Guid? ValidateUser(string username, string password)
var users = Repo.GetAll(); {
var users = Repo.GetAll();
foreach (var u in users)
{ foreach (var u in users)
if (username == u.UserName) {
{ if (username == u.UserName)
var passwordMatch = PasswordHasher.VerifyPassword(password, u.Salt, u.Hash); {
if (passwordMatch) var passwordMatch = PasswordHasher.VerifyPassword(password, u.Salt, u.Hash);
{ if (passwordMatch)
return new Guid(u.UserGuid); {
} return new Guid(u.UserGuid);
} }
} }
return null; }
} return null;
}
public UsersModel EditUser(UsersModel user)
{ public UsersModel EditUser(UsersModel user)
var existingUser = Repo.Get(user.UserGuid); {
var existingUser = Repo.Get(user.UserGuid);
user.Id = existingUser.Id;
user.UserGuid = existingUser.UserGuid; user.Id = existingUser.Id;
Repo.Update(user); user.UserGuid = existingUser.UserGuid;
return user; Repo.Update(user);
} return user;
}
public bool DoUsersExist()
{ public bool DoUsersExist()
var users = Repo.GetAll(); {
var users = Repo.GetAll();
return users.Any();
} return users.Any();
}
private Guid? CreateUser(string username, string password, string[] claims = default(string[]))
{ private Guid? CreateUser(string username, string password, string[] claims = default(string[]), UserProperties properties = null)
var salt = PasswordHasher.GenerateSalt(); {
var salt = PasswordHasher.GenerateSalt();
var userModel = new UsersModel
{ var userModel = new UsersModel
UserName = username, {
UserGuid = Guid.NewGuid().ToString(), UserName = username,
Salt = salt, UserGuid = Guid.NewGuid().ToString(),
Hash = PasswordHasher.ComputeHash(password, salt), Salt = salt,
Claims = ByteConverterHelper.ReturnBytes(claims), Hash = PasswordHasher.ComputeHash(password, salt),
UserProperties = ByteConverterHelper.ReturnBytes(new UserProperties()) Claims = ByteConverterHelper.ReturnBytes(claims),
}; UserProperties = ByteConverterHelper.ReturnBytes(properties ?? new UserProperties())
Repo.Insert(userModel); };
Repo.Insert(userModel);
var userRecord = Repo.Get(userModel.UserGuid);
var userRecord = Repo.Get(userModel.UserGuid);
return new Guid(userRecord.UserGuid);
} return new Guid(userRecord.UserGuid);
}
public Guid? CreateAdmin(string username, string password)
{ public Guid? CreateAdmin(string username, string password, UserProperties properties = null)
return CreateUser(username, password, new[] { UserClaims.User, UserClaims.PowerUser, UserClaims.Admin }); {
} return CreateUser(username, password, new[] { UserClaims.User, UserClaims.PowerUser, UserClaims.Admin }, properties);
}
public Guid? CreatePowerUser(string username, string password)
{ public Guid? CreatePowerUser(string username, string password, UserProperties properties = null)
return CreateUser(username, password, new[] { UserClaims.User, UserClaims.PowerUser }); {
} return CreateUser(username, password, new[] { UserClaims.User, UserClaims.PowerUser }, properties);
}
public Guid? CreateRegularUser(string username, string password)
{ public Guid? CreateRegularUser(string username, string password, UserProperties properties = null)
return CreateUser(username, password, new[] { UserClaims.User }); {
} return CreateUser(username, password, new[] { UserClaims.User }, properties);
}
public bool UpdatePassword(string username, string oldPassword, string newPassword)
{ public bool UpdatePassword(string username, string oldPassword, string newPassword)
var users = Repo.GetAll(); {
var userToChange = users.FirstOrDefault(x => x.UserName == username); var users = Repo.GetAll();
if (userToChange == null) var userToChange = users.FirstOrDefault(x => x.UserName == username);
return false; if (userToChange == null)
return false;
var passwordMatch = PasswordHasher.VerifyPassword(oldPassword, userToChange.Salt, userToChange.Hash);
if (!passwordMatch) var passwordMatch = PasswordHasher.VerifyPassword(oldPassword, userToChange.Salt, userToChange.Hash);
{ if (!passwordMatch)
throw new SecurityException("Password does not match"); {
} throw new SecurityException("Password does not match");
}
var newSalt = PasswordHasher.GenerateSalt();
var newHash = PasswordHasher.ComputeHash(newPassword, newSalt); var newSalt = PasswordHasher.GenerateSalt();
var newHash = PasswordHasher.ComputeHash(newPassword, newSalt);
userToChange.Hash = newHash;
userToChange.Salt = newSalt; userToChange.Hash = newHash;
userToChange.Salt = newSalt;
return Repo.Update(userToChange);
} return Repo.Update(userToChange);
}
public IEnumerable<UsersModel> GetUsers()
{ public async Task<IEnumerable<UsersModel>> GetUsersAsync()
return Repo.GetAll(); {
} return await Repo.GetAllAsync();
}
public UsersModel GetUser(Guid userId)
{ public IEnumerable<UsersModel> GetUsers()
var user = Repo.Get(userId.ToString()); {
return user; return Repo.GetAll();
} }
}
public UsersModel GetUser(Guid userId)
public interface ICustomUserMapper {
{ var user = Repo.Get(userId.ToString());
IEnumerable<UsersModel> GetUsers(); return user;
UsersModel GetUser(Guid userId); }
UsersModel EditUser(UsersModel user); }
bool DoUsersExist();
Guid? ValidateUser(string username, string password); public interface ICustomUserMapper
bool UpdatePassword(string username, string oldPassword, string newPassword); {
Guid? CreateAdmin(string username, string password); IEnumerable<UsersModel> GetUsers();
Guid? CreatePowerUser(string username, string password); Task<IEnumerable<UsersModel>> GetUsersAsync();
Guid? CreateRegularUser(string username, string password); UsersModel GetUser(Guid userId);
UsersModel EditUser(UsersModel user);
} bool DoUsersExist();
} Guid? ValidateUser(string username, string password);
bool UpdatePassword(string username, string oldPassword, string newPassword);
Guid? CreateAdmin(string username, string password, UserProperties properties = null);
Guid? CreatePowerUser(string username, string password, UserProperties properties = null);
Guid? CreateRegularUser(string username, string password, UserProperties properties = null);
}
}

@ -0,0 +1,3 @@
(function() {
module = angular.module('PlexRequests', []);
}());

@ -0,0 +1,36 @@
(function () {
var controller = function ($scope, userManagementService) {
$scope.user = {}; // The local user to create
$scope.users = []; // list of users
$scope.error = false;
$scope.errorMessage = {};
$scope.getUsers = function () {
$scope.users = userManagementService.getUsers()
.then(function (data) {
$scope.users = data.data;
});
};
$scope.addUser = function () {
if ($scope.users.length === 0) {
$scope.getUsers();
}
userManagementService.addUser($scope.user).then(function (data) {
if (data.message) {
$scope.error = true;
$scope.errorMessage = data.message;
} else {
$scope.users.push(data);
$scope.user = {};
}
});
};
}
angular.module('PlexRequests').controller('userManagementController', ["$scope", "userManagementService", controller]);
}());

@ -0,0 +1,29 @@
(function () {
var userManagementService = function ($http) {
var getUsers = function () {
return $http.get('/usermanagement/users');
};
var addUser = function (user) {
return $http({
url: '/usermanagement/createuser',
method: "POST",
data: $.param(user),
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
}
});
}
return {
getUsers: getUsers,
addUser: addUser
};
}
angular.module('PlexRequests').factory('userManagementService', ["$http", userManagementService]);
}());

@ -0,0 +1,44 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: EmptyViewBase.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 Nancy.ViewEngines.Razor;
namespace PlexRequests.UI.Helpers
{
public class AngularViewBase : NancyRazorViewBase
{
public AngularViewBase()
{
Layout = "Shared/_AngularLayout.cshtml";
}
public override void Execute()
{
}
}
}

@ -58,17 +58,18 @@ namespace PlexRequests.UI.Helpers
settings.ThemeName = Themes.PlexTheme; settings.ThemeName = Themes.PlexTheme;
} }
if (settings.ThemeName == "PlexBootstrap.css") settings.ThemeName = Themes.PlexTheme; if (settings.ThemeName == "PlexBootstrap.css") settings.ThemeName = Themes.PlexTheme;
if (settings.ThemeName == "OriginalBootstrap.css") settings.ThemeName = Themes.OriginalTheme; if (settings.ThemeName == "OriginalBootstrap.css") settings.ThemeName = Themes.OriginalTheme;
sb.AppendLine($"<link rel=\"stylesheet\" href=\"{content}/Content/bootstrap.css\" type=\"text/css\"/>"); sb.AppendLine($"<link rel=\"stylesheet\" href=\"{content}/Content/bootstrap.css\" type=\"text/css\"/>");
sb.AppendLine($"<link rel=\"stylesheet\" href=\"{content}/Content/font-awesome.css\" type=\"text/css\"/>"); sb.AppendLine($"<link rel=\"stylesheet\" href=\"{content}/Content/font-awesome.css\" type=\"text/css\"/>");
sb.AppendLine($"<link rel=\"stylesheet\" href=\"{content}/Content/pace.min.css\" type=\"text/css\"/>"); sb.AppendLine($"<link rel=\"stylesheet\" href=\"{content}/Content/pace.min.css\" type=\"text/css\"/>");
sb.AppendLine($"<link rel=\"stylesheet\" href=\"{content}/Content/awesome-bootstrap-checkbox.css\" type=\"text/css\"/>"); sb.AppendLine($"<link rel=\"stylesheet\" href=\"{content}/Content/awesome-bootstrap-checkbox.css\" type=\"text/css\"/>");
sb.AppendLine($"<link rel=\"stylesheet\" href=\"{content}/Content/base.css\" type=\"text/css\"/>"); sb.AppendLine($"<link rel=\"stylesheet\" href=\"{content}/Content/base.css\" type=\"text/css\"/>");
sb.AppendLine($"<link rel=\"stylesheet\" href=\"{content}/Content/Themes/{settings.ThemeName}\" type=\"text/css\"/>"); sb.AppendLine($"<link rel=\"stylesheet\" href=\"{content}/Content/Themes/{settings.ThemeName}\" type=\"text/css\"/>");
sb.AppendLine($"<link rel=\"stylesheet\" href=\"{content}/Content/datepicker.min.css\" type=\"text/css\"/>"); sb.AppendLine($"<link rel=\"stylesheet\" href=\"{content}/Content/datepicker.min.css\" type=\"text/css\"/>");
sb.AppendLine($"<script src=\"{content}/Content/jquery-2.2.1.min.js\"></script>"); sb.AppendLine($"<script src=\"{content}/Content/jquery-2.2.1.min.js\"></script>");
sb.AppendLine($"<script src=\"{content}/Content/app/app.js\"></script>");
sb.AppendLine($"<script src=\"{content}/Content/handlebars.min.js\"></script>"); sb.AppendLine($"<script src=\"{content}/Content/handlebars.min.js\"></script>");
sb.AppendLine($"<script src=\"{content}/Content/bootstrap.min.js\"></script>"); sb.AppendLine($"<script src=\"{content}/Content/bootstrap.min.js\"></script>");
sb.AppendLine($"<script src=\"{content}/Content/bootstrap-notify.min.js\"></script>"); sb.AppendLine($"<script src=\"{content}/Content/bootstrap-notify.min.js\"></script>");
@ -104,8 +105,8 @@ namespace PlexRequests.UI.Helpers
sb.AppendLine($"<script src=\"{content}/Content/requests-1.7.js\" type=\"text/javascript\"></script>"); sb.AppendLine($"<script src=\"{content}/Content/requests-1.7.js\" type=\"text/javascript\"></script>");
return helper.Raw(sb.ToString()); return helper.Raw(sb.ToString());
} }
public static IHtmlString LoadIssueAssets(this HtmlHelpers helper) public static IHtmlString LoadIssueAssets(this HtmlHelpers helper)
{ {
var sb = new StringBuilder(); var sb = new StringBuilder();
@ -116,8 +117,8 @@ namespace PlexRequests.UI.Helpers
sb.AppendLine($"<script src=\"{content}/Content/issues.js\" type=\"text/javascript\"></script>"); sb.AppendLine($"<script src=\"{content}/Content/issues.js\" type=\"text/javascript\"></script>");
return helper.Raw(sb.ToString()); return helper.Raw(sb.ToString());
} }
public static IHtmlString LoadIssueDetailsAssets(this HtmlHelpers helper) public static IHtmlString LoadIssueDetailsAssets(this HtmlHelpers helper)
{ {
var assetLocation = GetBaseUrl(); var assetLocation = GetBaseUrl();
@ -147,8 +148,8 @@ namespace PlexRequests.UI.Helpers
if (!settings.CollectAnalyticData) if (!settings.CollectAnalyticData)
{ {
return helper.Raw(string.Empty); return helper.Raw(string.Empty);
} }
var assetLocation = GetBaseUrl(); var assetLocation = GetBaseUrl();
var content = GetContentUrl(assetLocation); var content = GetContentUrl(assetLocation);
@ -164,19 +165,19 @@ namespace PlexRequests.UI.Helpers
if (!string.IsNullOrEmpty(content)) if (!string.IsNullOrEmpty(content))
{ {
url = $"/{content}{url}"; url = $"/{content}{url}";
} }
if (context.Request.Path == url) if (context.Request.Path == url)
{ {
returnString = $"<a class=\"list-group-item active\" href=\"{url}\">{title}</a>"; returnString = $"<a class=\"list-group-item active\" href=\"{url}\">{title}</a>";
} }
else else
{ {
returnString = $"<a class=\"list-group-item\" href=\"{url}\">{title}</a>"; returnString = $"<a class=\"list-group-item\" href=\"{url}\">{title}</a>";
} }
return helper.Raw(returnString); return helper.Raw(returnString);
} }
public static IHtmlString GetNavbarUrl(this HtmlHelpers helper, NancyContext context, string url, string title, string fontIcon) public static IHtmlString GetNavbarUrl(this HtmlHelpers helper, NancyContext context, string url, string title, string fontIcon)
{ {
var returnString = string.Empty; var returnString = string.Empty;
@ -184,19 +185,19 @@ namespace PlexRequests.UI.Helpers
if (!string.IsNullOrEmpty(content)) if (!string.IsNullOrEmpty(content))
{ {
url = $"/{content}{url}"; url = $"/{content}{url}";
} }
if (context.Request.Path == url) if (context.Request.Path == url)
{ {
returnString = $"<li class=\"active\"><a href=\"{url}\"><i class=\"fa fa-{fontIcon}\"></i> {title}</a></li>"; returnString = $"<li class=\"active\"><a href=\"{url}\"><i class=\"fa fa-{fontIcon}\"></i> {title}</a></li>";
} }
else else
{ {
returnString = $"<li><a href=\"{url}\"><i class=\"fa fa-{fontIcon}\"></i> {title}</a></li>"; returnString = $"<li><a href=\"{url}\"><i class=\"fa fa-{fontIcon}\"></i> {title}</a></li>";
} }
return helper.Raw(returnString); return helper.Raw(returnString);
} }
public static IHtmlString GetNavbarUrl(this HtmlHelpers helper, NancyContext context, string url, string title, string fontIcon, string extraHtml) public static IHtmlString GetNavbarUrl(this HtmlHelpers helper, NancyContext context, string url, string title, string fontIcon, string extraHtml)
{ {
var returnString = string.Empty; var returnString = string.Empty;
@ -204,12 +205,12 @@ namespace PlexRequests.UI.Helpers
if (!string.IsNullOrEmpty(content)) if (!string.IsNullOrEmpty(content))
{ {
url = $"/{content}{url}"; url = $"/{content}{url}";
} }
if (context.Request.Path == url) if (context.Request.Path == url)
{ {
returnString = $"<li class=\"active\"><a href=\"{url}\"><i class=\"fa fa-{fontIcon}\"></i> {title} {extraHtml}</a></li>"; returnString = $"<li class=\"active\"><a href=\"{url}\"><i class=\"fa fa-{fontIcon}\"></i> {title} {extraHtml}</a></li>";
} }
else else
{ {
returnString = $"<li><a href=\"{url}\"><i class=\"fa fa-{fontIcon}\"></i> {title} {extraHtml}</a></li>"; returnString = $"<li><a href=\"{url}\"><i class=\"fa fa-{fontIcon}\"></i> {title} {extraHtml}</a></li>";
@ -218,33 +219,33 @@ namespace PlexRequests.UI.Helpers
return helper.Raw(returnString); return helper.Raw(returnString);
} }
public static IHtmlString GetBaseUrl(this HtmlHelpers helper) public static IHtmlString GetBaseUrl(this HtmlHelpers helper)
{ {
return helper.Raw(GetBaseUrl()); return helper.Raw(GetBaseUrl());
} }
private static string GetBaseUrl() private static string GetBaseUrl()
{ {
return GetSettings().BaseUrl; return GetSettings().BaseUrl;
} }
private static PlexRequestSettings GetSettings() private static PlexRequestSettings GetSettings()
{ {
var returnValue = Cache.GetOrSet(CacheKeys.GetPlexRequestSettings, () => var returnValue = Cache.GetOrSet(CacheKeys.GetPlexRequestSettings, () =>
{ {
var settings = Locator.Resolve<ISettingsService<PlexRequestSettings>>().GetSettings(); var settings = Locator.Resolve<ISettingsService<PlexRequestSettings>>().GetSettings();
return settings; return settings;
}); });
return returnValue; return returnValue;
} }
private static string GetLinkUrl(string assetLocation) private static string GetLinkUrl(string assetLocation)
{ {
return string.IsNullOrEmpty(assetLocation) ? string.Empty : $"{assetLocation}"; return string.IsNullOrEmpty(assetLocation) ? string.Empty : $"{assetLocation}";
} }
private static string GetContentUrl(string assetLocation) private static string GetContentUrl(string assetLocation)
{ {
return string.IsNullOrEmpty(assetLocation) ? string.Empty : $"/{assetLocation}"; return string.IsNullOrEmpty(assetLocation) ? string.Empty : $"/{assetLocation}";
} }
} }
} }

@ -1,20 +1,19 @@
using System; namespace PlexRequests.UI.Models
namespace PlexRequests.UI
{ {
public class UserManagementUsersViewModel public class UserManagementUsersViewModel
{ {
public string Username{get;set;} public string Username { get; set; }
public string Claims{get;set;} public string Claims { get; set; }
public int Id {get;set;} public int Id { get; set; }
public string Alias {get;set;} public string Alias { get; set; }
public UserType Type { get; set;} public UserType Type { get; set; }
} public string EmailAddress { get; set; }
}
public enum UserType public enum UserType
{ {
PlexUser, PlexUser,
LocalUser LocalUser
} }
} }

@ -1,10 +1,14 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Nancy; using Nancy;
using Nancy.Responses.Negotiation; using Nancy.Responses.Negotiation;
using Nancy.Security; using Nancy.Security;
using PlexRequests.Api.Interfaces;
using PlexRequests.Core; using PlexRequests.Core;
using PlexRequests.Core.Models;
using PlexRequests.Core.SettingModels; using PlexRequests.Core.SettingModels;
using PlexRequests.Helpers; using PlexRequests.Helpers;
using PlexRequests.UI.Models; using PlexRequests.UI.Models;
@ -13,57 +17,88 @@ namespace PlexRequests.UI.Modules
{ {
public class UserManagementModule : BaseModule public class UserManagementModule : BaseModule
{ {
public UserManagementModule(ISettingsService<PlexRequestSettings> pr, ICustomUserMapper m) : base("usermanagement",pr) public UserManagementModule(ISettingsService<PlexRequestSettings> pr, ICustomUserMapper m, IPlexApi plexApi, ISettingsService<AuthenticationSettings> auth) : base("usermanagement", pr)
{ {
this.RequiresClaims(UserClaims.Admin); //this.RequiresClaims(UserClaims.Admin);
Get["/"] = x => Load();
Get["/users"] = x => LoadUsers();
UserMapper = m; UserMapper = m;
PlexApi = plexApi;
AuthSettings = auth;
Get["/"] = x => Load();
Get["/users", true] = async (x, ct) => await LoadUsers();
Post["/createuser"] = x => CreateUser(Request.Form["userName"].ToString(), Request.Form["password"].ToString());
} }
private ICustomUserMapper UserMapper { get; } private ICustomUserMapper UserMapper { get; }
private IPlexApi PlexApi { get; }
private ISettingsService<AuthenticationSettings> AuthSettings { get; }
private Negotiator Load() private Negotiator Load()
{ {
return View["Index"]; return View["Index"];
} }
private Response LoadUsers() private async Task<Response> LoadUsers()
{ {
var users = UserMapper.GetUsers(); var localUsers = await UserMapper.GetUsersAsync();
var model = new List<UserManagementUsersViewModel>(); var model = new List<UserManagementUsersViewModel>();
foreach (var user in users) foreach (var user in localUsers)
{ {
var claims = ByteConverterHelper.ReturnObject<string[]>(user.Claims);
var claimsString = string.Join(", ", claims);
var userProps = ByteConverterHelper.ReturnObject<UserProperties>(user.UserProperties);
model.Add(new UserManagementUsersViewModel model.Add(new UserManagementUsersViewModel
{ {
//Claims = ByteConverterHelper.ReturnObject<string[]>(user.Claims), Claims = claimsString,
Claims = "test",
Id = user.Id,
Username = user.UserName, Username = user.UserName,
//Type = UserType.LocalUser Type = UserType.LocalUser,
EmailAddress = userProps.EmailAddress
}); });
} }
return Response.AsJson(users);
var authSettings = await AuthSettings.GetSettingsAsync();
if (!string.IsNullOrEmpty(authSettings.PlexAuthToken))
{
//Get Plex Users
var plexUsers = PlexApi.GetUsers(authSettings.PlexAuthToken);
foreach (var u in plexUsers.User)
{
model.Add(new UserManagementUsersViewModel
{
Username = u.Username,
Type = UserType.PlexUser,
//Alias =
Claims = "Requestor",
EmailAddress = u.Email
});
}
}
return Response.AsJson(model);
} }
//private Response CreateUser(string username, string password, string claims) private Response CreateUser(string username, string password)
//{ {
// if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password)) if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password))
// { {
// return Response.AsJson(new JsonResponseModel return Response.AsJson(new JsonResponseModel
// { {
// Result = true, Result = true,
// Message = "Please enter in a valid Username and Password" Message = "Please enter in a valid Username and Password"
// }); });
// } }
// var user = UserMapper.CreateUser(username, password, new string[] {claims}); var user = UserMapper.CreateRegularUser(username, password);
// if (user.HasValue) if (user.HasValue)
// { {
// return Response.AsJson(new JsonResponseModel {Result = true}); return Response.AsJson(user);
// } }
// return Response.AsJson(new JsonResponseModel {Result = false, Message = "Could not save user"}); return Response.AsJson(new JsonResponseModel { Result = false, Message = "Could not save user" });
//} }
} }
} }

@ -180,8 +180,9 @@
<Compile Include="Helpers\ContravariantBindingResolver.cs" /> <Compile Include="Helpers\ContravariantBindingResolver.cs" />
<Compile Include="Helpers\CultureHelper.cs" /> <Compile Include="Helpers\CultureHelper.cs" />
<Compile Include="Helpers\CustomHtmlHelper.cs" /> <Compile Include="Helpers\CustomHtmlHelper.cs" />
<Compile Include="Helpers\HeadphonesSender.cs" />
<Compile Include="Helpers\EmptyViewBase.cs" /> <Compile Include="Helpers\EmptyViewBase.cs" />
<Compile Include="Helpers\HeadphonesSender.cs" />
<Compile Include="Helpers\AngularViewBase.cs" />
<Compile Include="Helpers\ServiceLocator.cs" /> <Compile Include="Helpers\ServiceLocator.cs" />
<Compile Include="Helpers\StringHelper.cs" /> <Compile Include="Helpers\StringHelper.cs" />
<Compile Include="Helpers\Themes.cs" /> <Compile Include="Helpers\Themes.cs" />
@ -246,6 +247,9 @@
<Compile Include="Validators\RequestedModelValidator.cs" /> <Compile Include="Validators\RequestedModelValidator.cs" />
<Compile Include="Validators\SickRageValidator.cs" /> <Compile Include="Validators\SickRageValidator.cs" />
<Compile Include="Validators\SonarrValidator.cs" /> <Compile Include="Validators\SonarrValidator.cs" />
<Content Include="Content\app\app.js">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Content\awesome-bootstrap-checkbox.css"> <Content Include="Content\awesome-bootstrap-checkbox.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
@ -298,6 +302,9 @@
<Content Include="Content\issues.js"> <Content Include="Content\issues.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="Content\app\services\userManagement\userManagementService.js">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Content\swagger\backbone-min.js"> <Content Include="Content\swagger\backbone-min.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
@ -494,6 +501,9 @@
<None Include="sqlite3.dll"> <None Include="sqlite3.dll">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None> </None>
<Content Include="Content\app\controllers\userManagement\userManagementController.js">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="web.config"> <Content Include="web.config">
<SubType>Designer</SubType> <SubType>Designer</SubType>
</Content> </Content>
@ -596,6 +606,18 @@
<Content Include="Views\Admin\SchedulerSettings.cshtml"> <Content Include="Views\Admin\SchedulerSettings.cshtml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content> </Content>
<Content Include="Views\Shared\Partial\_Navbar.cshtml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Views\Shared\Partial\_Head.cshtml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Views\Shared\Partial\_LayoutScripts.cshtml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Views\Shared\_AngularLayout.cshtml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<None Include="Web.Debug.config"> <None Include="Web.Debug.config">
<DependentUpon>web.config</DependentUpon> <DependentUpon>web.config</DependentUpon>
</None> </None>
@ -698,6 +720,8 @@
<Target Name="AfterBuild"> <Target Name="AfterBuild">
</Target> </Target>
--> -->
<ItemGroup /> <ItemGroup>
<Folder Include="NewFolder1\" />
</ItemGroup>
<Import Project="..\packages\Nancy.Viewengines.Razor.1.4.3\build\Nancy.ViewEngines.Razor.targets" Condition="Exists('..\packages\Nancy.Viewengines.Razor.1.4.3\build\Nancy.ViewEngines.Razor.targets')" /> <Import Project="..\packages\Nancy.Viewengines.Razor.1.4.3\build\Nancy.ViewEngines.Razor.targets" Condition="Exists('..\packages\Nancy.Viewengines.Razor.1.4.3\build\Nancy.ViewEngines.Razor.targets')" />
</Project> </Project>

@ -2,7 +2,7 @@
@using Nancy.Session @using Nancy.Session
@using PlexRequests.UI.Helpers @using PlexRequests.UI.Helpers
@using PlexRequests.UI.Models @using PlexRequests.UI.Models
@inherits Nancy.ViewEngines.Razor.NancyRazorViewBase @inherits Nancy.ViewEngines.Razor.NancyRazorViewBase<dynamic>
@{ @{
var baseUrl = Html.GetBaseUrl(); var baseUrl = Html.GetBaseUrl();
var url = string.Empty; var url = string.Empty;

@ -0,0 +1,27 @@
@using Nancy.Security
@using Nancy.Session
@using PlexRequests.UI.Helpers
@using PlexRequests.UI.Models
@using PlexRequests.UI.Resources
@inherits Nancy.ViewEngines.Razor.NancyRazorViewBase<dynamic>
@{
var baseUrl = Html.GetBaseUrl();
var url = string.Empty;
if (!string.IsNullOrEmpty(baseUrl.ToHtmlString()))
{
url = "/" + baseUrl.ToHtmlString();
}
}
<div hidden="hidden" id="baseUrl">@baseUrl.ToHtmlString()</div>
<head>
<title>@UI.Layout_Title</title>
<!-- Styles -->
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js" type="text/JavaScript"></script>
@Html.LoadAnalytics()
@Html.LoadAssets()
</head>

@ -0,0 +1,83 @@
@using Nancy.Security
@using Nancy.Session
@using PlexRequests.UI.Helpers
@using PlexRequests.UI.Models
@using PlexRequests.UI.Resources
@inherits Nancy.ViewEngines.Razor.NancyRazorViewBase<dynamic>
<script>
var urlBase = '@Html.GetBaseUrl()';
$(function () {
// Check for update
var url = createBaseUrl(urlBase, '/updatechecker');
$.ajax({
type: "GET",
url: url,
dataType: "json",
success: function (response) {
if (response.updateAvailable) {
var status = createBaseUrl(urlBase, '/admin/status');
$('#updateAvailable').html("<i class='fa fa-cloud-download' aria-hidden='true'></i> @UI.Layout_UpdateAvailablePart1 <a style='color: white' href='" + status + "'>@UI.Layout_UpdateAvailablePart2</a>");
$('#updateAvailable').removeAttr("hidden");
$('body').addClass('update-available');
}
},
error: function (e) {
console.log(e);
}
});
// End Check for update
// Scroller
$(document).on('scroll', function () {
if ($(window).scrollTop() > 100) {
$('.scroll-top-wrapper').addClass('show');
} else {
$('.scroll-top-wrapper').removeClass('show');
}
});
$('.scroll-top-wrapper').on('click', scrollToTop);
// End Scroller
// Get Issue count
var issueUrl = createBaseUrl(urlBase, '/issues/issuecount');
$.ajax({
type: "GET",
url: issueUrl,
dataType: "json",
success: function (response) {
if (response) {
if (response > 0)
$('#issueCount').addClass("badge");
$('#issueCount').html(+response);
}
},
error: function (e) {
console.log(e);
}
});
// End issue count
$('#donate').click(function () {
ga('send', 'event', 'Navbar', 'Donate', 'Donate Clicked');
});
});
function scrollToTop() {
verticalOffset = typeof (verticalOffset) != 'undefined' ? verticalOffset : 0;
element = $('body');
offset = element.offset();
offsetTop = offset.top;
$('html, body').animate({ scrollTop: offsetTop }, 500, 'linear');
}
</script>

@ -0,0 +1,94 @@
@using Nancy.Security
@using Nancy.Session
@using Nancy;
@using PlexRequests.UI.Helpers
@using PlexRequests.UI.Models
@using PlexRequests.UI.Resources
@inherits Nancy.ViewEngines.Razor.NancyRazorViewBase
@{
var baseUrl = Html.GetBaseUrl();
var url = string.Empty;
if (!string.IsNullOrEmpty(baseUrl.ToHtmlString()))
{
url = "/" + baseUrl.ToHtmlString();
}
}
<nav class="navbar navbar-default">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="@url/search">@UI.Layout_Title</a>
</div>
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
@Html.GetNavbarUrl(Context, "/search", UI.Layout_Search, "search")
@Html.GetNavbarUrl(Context, "/requests", UI.Layout_Requests, "plus-circle")
@Html.GetNavbarUrl(Context, "/issues", UI.Layout_Issues, "exclamation", "<span id=\"issueCount\"></span>")
@if (Context.CurrentUser.IsAuthenticated()) // TODO replace with IsAdmin
{
<li><a id="donate" href="https://www.paypal.me/PlexRequestsNet" target="_blank"><i class="fa fa-heart" style="color: red"></i> @UI.Layout_Donate</a></li>
}
</ul>
<ul class="nav navbar-nav navbar-right">
@if (!Context.CurrentUser.IsAuthenticated() && Context.Request.Session[SessionKeys.UsernameKey] == null) // TODO replace with IsAdmin
{
<li><a href="@url/login?redirect=@Context.Request.Path"><i class="fa fa-user"></i> @UI.Layout_Admin</a></li>
}
@if (Context.CurrentUser.IsAuthenticated()) // TODO replace with IsAdmin
{
<li><a>@UI.Layout_Welcome @Context.Request.Session[SessionKeys.UsernameKey]</a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false"><i class="fa fa-user"></i> @UI.Layout_Admin <span class="caret"></span></a>
<ul class="dropdown-menu" role="menu">
<li><a href="@url/admin"><i class="fa fa-cog"></i> @UI.Layout_Settings</a></li>
<li><a href="@url/changepassword"><i class="fa fa-key"></i> @UI.Layout_ChangePassword</a></li>
<li class="divider"></li>
<li><a href="@url/logout"><i class="fa fa-sign-out"></i> @UI.Layout_Logout</a></li>
</ul>
</li>
}
@if (Context.Request.Session[SessionKeys.UsernameKey] != null && !Context.CurrentUser.IsAuthenticated())
{
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false"><i class="fa fa-user"></i> @UI.Layout_Welcome @Context.Request.Session[SessionKeys.UsernameKey] <span class="caret"></span></a>
<ul class="dropdown-menu" role="menu">
<li><a href="@url/login?redirect=@Context.Request.Path"><i class="fa fa-user"></i> @UI.Layout_Admin</a></li>
<li class="divider"></li>
<li><a href="@url/userlogin/logout"><i class="fa fa-sign-out"></i> @UI.Layout_Logout</a></li>
</ul>
</li>
}
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false"><i class="fa fa-language" aria-hidden="true"><span class="caret"></span></i></a>
<ul class="dropdown-menu" role="menu">
<li><a href="@url/culture?l=en&u=@Context.Request.Path">@UI.Layout_English</a></li>
<li><a href="@url/culture?l=nl&u=@Context.Request.Path">@UI.Layout_Dutch</a></li>
<li><a href="@url/culture?l=es&u=@Context.Request.Path">@UI.Layout_Spanish</a></li>
<li><a href="@url/culture?l=de&u=@Context.Request.Path">@UI.Layout_German</a></li>
<li><a href="@url/culture?l=da&u=@Context.Request.Path">@UI.Layout_Danish</a></li>
<li><a href="@url/culture?l=pt&u=@Context.Request.Path">@UI.Layout_Portuguese</a></li>
<li><a href="@url/culture?l=sv&u=@Context.Request.Path">@UI.Layout_Swedish</a></li>
<li><a href="@url/culture?l=it&u=@Context.Request.Path">@UI.Layout_Italian</a></li>
</ul>
<li/>
</ul>
</div>
</div>
<div id="updateAvailable" hidden="hidden"></div>
</nav>

@ -0,0 +1,23 @@
@using PlexRequests.UI.Helpers
@inherits Nancy.ViewEngines.Razor.NancyRazorViewBase
<html ng-app="PlexRequests">
@Html.Partial("Shared/Partial/_Head")
<body>
@Html.Partial("Shared/Partial/_Navbar")
<div class="container">
@RenderBody()
</div>
<div class="scroll-top-wrapper ">
<span class="scroll-top-inner">
<i class="fa fa-2x fa-arrow-circle-up"></i>
</span>
</div>
</body>
@Html.GetInformationalVersion()
</html>
@Html.Partial("Shared/Partial/_LayoutScripts")

@ -1,191 +1,23 @@
@using Nancy.Security @using PlexRequests.UI.Helpers
@using Nancy.Session
@using PlexRequests.UI.Helpers
@using PlexRequests.UI.Models
@using PlexRequests.UI.Resources
@inherits Nancy.ViewEngines.Razor.NancyRazorViewBase @inherits Nancy.ViewEngines.Razor.NancyRazorViewBase
@{
var baseUrl = Html.GetBaseUrl();
var url = string.Empty;
if (!string.IsNullOrEmpty(baseUrl.ToHtmlString()))
{
url = "/" + baseUrl.ToHtmlString();
}
}
<html> <html>
<div hidden="hidden" id="baseUrl">@baseUrl.ToHtmlString()</div> @Html.Partial("Shared/Partial/_Head")
<head>
<title>@UI.Layout_Title</title>
<!-- Styles -->
<meta name="viewport" content="width=device-width, initial-scale=1">
@Html.LoadAnalytics()
@Html.LoadAssets()
</head>
<body>
<nav class="navbar navbar-default">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="@url/search">@UI.Layout_Title</a>
</div>
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
@Html.GetNavbarUrl(Context, "/search", UI.Layout_Search, "search")
@Html.GetNavbarUrl(Context, "/requests", UI.Layout_Requests, "plus-circle")
@Html.GetNavbarUrl(Context, "/issues", UI.Layout_Issues, "exclamation", "<span id=\"issueCount\"></span>")
@if (Context.CurrentUser.IsAuthenticated()) // TODO replace with IsAdmin
{
<li><a id="donate" href="https://www.paypal.me/PlexRequestsNet" target="_blank"><i class="fa fa-heart" style="color: red"></i> @UI.Layout_Donate</a></li>
}
</ul>
<ul class="nav navbar-nav navbar-right">
@if (!Context.CurrentUser.IsAuthenticated() && Context.Request.Session[SessionKeys.UsernameKey] == null) // TODO replace with IsAdmin
{
<li><a href="@url/login?redirect=@Context.Request.Path"><i class="fa fa-user"></i> @UI.Layout_Admin</a></li>
}
@if (Context.CurrentUser.IsAuthenticated()) // TODO replace with IsAdmin
{
<li><a>@UI.Layout_Welcome @Context.Request.Session[SessionKeys.UsernameKey]</a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false"><i class="fa fa-user"></i> @UI.Layout_Admin <span class="caret"></span></a>
<ul class="dropdown-menu" role="menu">
<li><a href="@url/admin"><i class="fa fa-cog"></i> @UI.Layout_Settings</a></li>
<li><a href="@url/changepassword"><i class="fa fa-key"></i> @UI.Layout_ChangePassword</a></li>
<li class="divider"></li> <body>
<li><a href="@url/logout"><i class="fa fa-sign-out"></i> @UI.Layout_Logout</a></li>
</ul>
</li>
}
@if (Context.Request.Session[SessionKeys.UsernameKey] != null && !Context.CurrentUser.IsAuthenticated())
{
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false"><i class="fa fa-user"></i> @UI.Layout_Welcome @Context.Request.Session[SessionKeys.UsernameKey] <span class="caret"></span></a>
<ul class="dropdown-menu" role="menu">
<li><a href="@url/login?redirect=@Context.Request.Path"><i class="fa fa-user"></i> @UI.Layout_Admin</a></li>
<li class="divider"></li>
<li><a href="@url/userlogin/logout"><i class="fa fa-sign-out"></i> @UI.Layout_Logout</a></li>
</ul>
</li>
} @Html.Partial("Shared/Partial/_Navbar")
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false"><i class="fa fa-language" aria-hidden="true"><span class="caret"></span></i></a> <div class="container">
<ul class="dropdown-menu" role="menu"> @RenderBody()
<li><a href="@url/culture?l=en&u=@Context.Request.Path">@UI.Layout_English</a></li>
<li><a href="@url/culture?l=nl&u=@Context.Request.Path">@UI.Layout_Dutch</a></li>
<li><a href="@url/culture?l=es&u=@Context.Request.Path">@UI.Layout_Spanish</a></li>
<li><a href="@url/culture?l=de&u=@Context.Request.Path">@UI.Layout_German</a></li>
<li><a href="@url/culture?l=da&u=@Context.Request.Path">@UI.Layout_Danish</a></li>
<li><a href="@url/culture?l=pt&u=@Context.Request.Path">@UI.Layout_Portuguese</a></li>
<li><a href="@url/culture?l=sv&u=@Context.Request.Path">@UI.Layout_Swedish</a></li>
<li><a href="@url/culture?l=it&u=@Context.Request.Path">@UI.Layout_Italian</a></li>
</ul>
<li/>
</ul>
</div>
</div> </div>
<div id="updateAvailable" hidden="hidden"></div> <div class="scroll-top-wrapper ">
</nav> <span class="scroll-top-inner">
<div class="container">
@RenderBody()
</div>
<div class="scroll-top-wrapper ">
<span class="scroll-top-inner">
<i class="fa fa-2x fa-arrow-circle-up"></i> <i class="fa fa-2x fa-arrow-circle-up"></i>
</span> </span>
</div> </div>
</body> </body>
@Html.GetInformationalVersion();
</html>
<script>
$(function () {
var urlBase = '@Html.GetBaseUrl()';
// Check for update @Html.GetInformationalVersion()
var url = createBaseUrl(urlBase, '/updatechecker');
$.ajax({
type: "GET",
url: url,
dataType: "json",
success: function (response) {
if (response.updateAvailable) {
var status = createBaseUrl(urlBase, '/admin/status');
$('#updateAvailable').html("<i class='fa fa-cloud-download' aria-hidden='true'></i> @UI.Layout_UpdateAvailablePart1 <a style='color: white' href='" + status + "'>@UI.Layout_UpdateAvailablePart2</a>");
$('#updateAvailable').removeAttr("hidden");
$('body').addClass('update-available');
}
},
error: function (e) {
console.log(e);
}
});
// End Check for update
</html>
// Scroller @Html.Partial("Shared/Partial/_LayoutScripts")
$(document).on('scroll', function () {
if ($(window).scrollTop() > 100) {
$('.scroll-top-wrapper').addClass('show');
} else {
$('.scroll-top-wrapper').removeClass('show');
}
});
$('.scroll-top-wrapper').on('click', scrollToTop);
// End Scroller
// Get Issue count
var issueUrl = createBaseUrl(urlBase, '/issues/issuecount');
$.ajax({
type: "GET",
url: issueUrl,
dataType: "json",
success: function (response) {
if (response) {
if (response > 0)
$('#issueCount').addClass("badge");
$('#issueCount').html(+response);
}
},
error: function (e) {
console.log(e);
}
});
// End issue count
$('#donate').click(function () {
ga('send', 'event', 'Navbar', 'Donate', 'Donate Clicked');
});
});
function scrollToTop() {
verticalOffset = typeof (verticalOffset) != 'undefined' ? verticalOffset : 0;
element = $('body');
offset = element.offset();
offsetTop = offset.top;
$('html, body').animate({ scrollTop: offsetTop }, 500, 'linear');
}
</script>

@ -1,4 +1,7 @@
@using PlexRequests.UI.Helpers @using PlexRequests.UI.Helpers
@inherits PlexRequests.UI.Helpers.AngularViewBase
@Html.LoadTableAssets() @Html.LoadTableAssets()
@{ @{
var baseUrl = Html.GetBaseUrl().ToHtmlString(); var baseUrl = Html.GetBaseUrl().ToHtmlString();
@ -8,41 +11,53 @@
url = "/" + baseUrl; url = "/" + baseUrl;
} }
} }
<script src="/Content/app/controllers/userManagement/userManagementController.js"></script>
<script src="/Content/app/services/userManagement/userManagementService.js"></script>
<h2>User Management</h2> <div ng-controller="userManagementController" ng-init="getUsers()">
<button class="btn btn-success-outline" type="submit">Create User <div class="fa fa-plus"/></button> <br>
<br> <br>
<br>
<fieldset> <fieldset>
<table id="example" class="table table-striped table-hover table-responsive"> <table class="table table-striped table-hover table-responsive">
<thead> <thead>
<tr> <tr>
<th>Id</th> <th>Id</th>
<th>Username</th> <th>Username</th>
<th>Permissions</th> <th>Email</th>
</tr> <th>User T</th>
</tr>
</thead> </thead>
<tbody>
<tr ng-repeat="u in users">
<td>
{{u.username}}
</td>
<td>
{{u.emailAddress}}
</td>
<td>
{{u.claims}}
</td>
<td>
{{u.type == 0 ? 'Local User' : 'Plex User'}}
{{u.type}}
</td>
</tr>
</tbody>
</table> </table>
</fieldset>
<script>
var baseUrl = '@Html.GetBaseUrl()'; <form ng-submit="addUser()">
<div class="form-group">
<input id="username" type="text" placeholder="user" ng-model="user.username" class="form-control-custom"/>
</div>
<div class="form-group">
<input id="password" type="password" placeholder="password" ng-model="user.password" class="form-control-custom"/>
</div>
<input type="submit" class="btn btn-success-outline" value="Add"/>
</form>
</fieldset>
</div>
var url = createBaseUrl(baseUrl, "usermanagement/users");
$('#example').DataTable({
"ajax": url,
"columns": [
{ "data": "id" },
{ "data": "username" },
{ "data": "claims" },
//{ "data": "type" }
],
"order": [[1, "desc"]]
});
</script>
Loading…
Cancel
Save