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

@ -1,35 +1,34 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: UserProperties.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.Core.Models
{
public class UserProperties
{
public string EmailAddress { get; set; }
public bool NotifyOnRelease { get; set; }
public bool NotifyOnApprove { get; set; }
}
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: UserProperties.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.Core.Models
{
public class UserProperties
{
public string EmailAddress { get; set; }
public string UserAlias { get; set; }
}
}

@ -1,183 +1,191 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: UserMapper.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.Linq;
using System.Security;
using Nancy;
using Nancy.Authentication.Forms;
using Nancy.Security;
using PlexRequests.Core.Models;
using PlexRequests.Helpers;
using PlexRequests.Store;
using PlexRequests.Store.Repository;
namespace PlexRequests.Core
{
public class UserMapper : IUserMapper, ICustomUserMapper
{
public UserMapper(IRepository<UsersModel> repo)
{
Repo = repo;
}
private static IRepository<UsersModel> Repo { get; set; }
public IUserIdentity GetUserFromIdentifier(Guid identifier, NancyContext context)
{
var user = Repo.Get(identifier.ToString());
if (user == null)
{
return null;
}
return new UserIdentity
{
UserName = user.UserName,
Claims = ByteConverterHelper.ReturnObject<string[]>(user.Claims)
};
}
public Guid? ValidateUser(string username, string password)
{
var users = Repo.GetAll();
foreach (var u in users)
{
if (username == u.UserName)
{
var passwordMatch = PasswordHasher.VerifyPassword(password, u.Salt, u.Hash);
if (passwordMatch)
{
return new Guid(u.UserGuid);
}
}
}
return null;
}
public UsersModel EditUser(UsersModel user)
{
var existingUser = Repo.Get(user.UserGuid);
user.Id = existingUser.Id;
user.UserGuid = existingUser.UserGuid;
Repo.Update(user);
return user;
}
public bool DoUsersExist()
{
var users = Repo.GetAll();
return users.Any();
}
private Guid? CreateUser(string username, string password, string[] claims = default(string[]))
{
var salt = PasswordHasher.GenerateSalt();
var userModel = new UsersModel
{
UserName = username,
UserGuid = Guid.NewGuid().ToString(),
Salt = salt,
Hash = PasswordHasher.ComputeHash(password, salt),
Claims = ByteConverterHelper.ReturnBytes(claims),
UserProperties = ByteConverterHelper.ReturnBytes(new UserProperties())
};
Repo.Insert(userModel);
var userRecord = Repo.Get(userModel.UserGuid);
return new Guid(userRecord.UserGuid);
}
public Guid? CreateAdmin(string username, string password)
{
return CreateUser(username, password, new[] { UserClaims.User, UserClaims.PowerUser, UserClaims.Admin });
}
public Guid? CreatePowerUser(string username, string password)
{
return CreateUser(username, password, new[] { UserClaims.User, UserClaims.PowerUser });
}
public Guid? CreateRegularUser(string username, string password)
{
return CreateUser(username, password, new[] { UserClaims.User });
}
public bool UpdatePassword(string username, string oldPassword, string newPassword)
{
var users = Repo.GetAll();
var userToChange = users.FirstOrDefault(x => x.UserName == username);
if (userToChange == null)
return false;
var passwordMatch = PasswordHasher.VerifyPassword(oldPassword, userToChange.Salt, userToChange.Hash);
if (!passwordMatch)
{
throw new SecurityException("Password does not match");
}
var newSalt = PasswordHasher.GenerateSalt();
var newHash = PasswordHasher.ComputeHash(newPassword, newSalt);
userToChange.Hash = newHash;
userToChange.Salt = newSalt;
return Repo.Update(userToChange);
}
public IEnumerable<UsersModel> GetUsers()
{
return Repo.GetAll();
}
public UsersModel GetUser(Guid userId)
{
var user = Repo.Get(userId.ToString());
return user;
}
}
public interface ICustomUserMapper
{
IEnumerable<UsersModel> GetUsers();
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);
Guid? CreatePowerUser(string username, string password);
Guid? CreateRegularUser(string username, string password);
}
}
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: UserMapper.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.Linq;
using System.Security;
using System.Threading.Tasks;
using Nancy;
using Nancy.Authentication.Forms;
using Nancy.Security;
using PlexRequests.Core.Models;
using PlexRequests.Helpers;
using PlexRequests.Store;
using PlexRequests.Store.Repository;
namespace PlexRequests.Core
{
public class UserMapper : IUserMapper, ICustomUserMapper
{
public UserMapper(IRepository<UsersModel> repo)
{
Repo = repo;
}
private static IRepository<UsersModel> Repo { get; set; }
public IUserIdentity GetUserFromIdentifier(Guid identifier, NancyContext context)
{
var user = Repo.Get(identifier.ToString());
if (user == null)
{
return null;
}
return new UserIdentity
{
UserName = user.UserName,
Claims = ByteConverterHelper.ReturnObject<string[]>(user.Claims)
};
}
public Guid? ValidateUser(string username, string password)
{
var users = Repo.GetAll();
foreach (var u in users)
{
if (username == u.UserName)
{
var passwordMatch = PasswordHasher.VerifyPassword(password, u.Salt, u.Hash);
if (passwordMatch)
{
return new Guid(u.UserGuid);
}
}
}
return null;
}
public UsersModel EditUser(UsersModel user)
{
var existingUser = Repo.Get(user.UserGuid);
user.Id = existingUser.Id;
user.UserGuid = existingUser.UserGuid;
Repo.Update(user);
return user;
}
public bool DoUsersExist()
{
var users = Repo.GetAll();
return users.Any();
}
private Guid? CreateUser(string username, string password, string[] claims = default(string[]), UserProperties properties = null)
{
var salt = PasswordHasher.GenerateSalt();
var userModel = new UsersModel
{
UserName = username,
UserGuid = Guid.NewGuid().ToString(),
Salt = salt,
Hash = PasswordHasher.ComputeHash(password, salt),
Claims = ByteConverterHelper.ReturnBytes(claims),
UserProperties = ByteConverterHelper.ReturnBytes(properties ?? new UserProperties())
};
Repo.Insert(userModel);
var userRecord = Repo.Get(userModel.UserGuid);
return new Guid(userRecord.UserGuid);
}
public Guid? CreateAdmin(string username, string password, UserProperties properties = null)
{
return CreateUser(username, password, new[] { UserClaims.User, UserClaims.PowerUser, UserClaims.Admin }, properties);
}
public Guid? CreatePowerUser(string username, string password, UserProperties properties = null)
{
return CreateUser(username, password, new[] { UserClaims.User, UserClaims.PowerUser }, properties);
}
public Guid? CreateRegularUser(string username, string password, UserProperties properties = null)
{
return CreateUser(username, password, new[] { UserClaims.User }, properties);
}
public bool UpdatePassword(string username, string oldPassword, string newPassword)
{
var users = Repo.GetAll();
var userToChange = users.FirstOrDefault(x => x.UserName == username);
if (userToChange == null)
return false;
var passwordMatch = PasswordHasher.VerifyPassword(oldPassword, userToChange.Salt, userToChange.Hash);
if (!passwordMatch)
{
throw new SecurityException("Password does not match");
}
var newSalt = PasswordHasher.GenerateSalt();
var newHash = PasswordHasher.ComputeHash(newPassword, newSalt);
userToChange.Hash = newHash;
userToChange.Salt = newSalt;
return Repo.Update(userToChange);
}
public async Task<IEnumerable<UsersModel>> GetUsersAsync()
{
return await Repo.GetAllAsync();
}
public IEnumerable<UsersModel> GetUsers()
{
return Repo.GetAll();
}
public UsersModel GetUser(Guid userId)
{
var user = Repo.Get(userId.ToString());
return user;
}
}
public interface ICustomUserMapper
{
IEnumerable<UsersModel> GetUsers();
Task<IEnumerable<UsersModel>> GetUsersAsync();
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;
}
if (settings.ThemeName == "PlexBootstrap.css") settings.ThemeName = Themes.PlexTheme;
if (settings.ThemeName == "OriginalBootstrap.css") settings.ThemeName = Themes.OriginalTheme;
sb.AppendLine($"<link rel=\"stylesheet\" href=\"{content}/Content/bootstrap.css\" type=\"text/css\"/>");
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/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/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/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/app/app.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-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>");
return helper.Raw(sb.ToString());
}
}
public static IHtmlString LoadIssueAssets(this HtmlHelpers helper)
{
var sb = new StringBuilder();
@ -116,8 +117,8 @@ namespace PlexRequests.UI.Helpers
sb.AppendLine($"<script src=\"{content}/Content/issues.js\" type=\"text/javascript\"></script>");
return helper.Raw(sb.ToString());
}
}
public static IHtmlString LoadIssueDetailsAssets(this HtmlHelpers helper)
{
var assetLocation = GetBaseUrl();
@ -147,8 +148,8 @@ namespace PlexRequests.UI.Helpers
if (!settings.CollectAnalyticData)
{
return helper.Raw(string.Empty);
}
}
var assetLocation = GetBaseUrl();
var content = GetContentUrl(assetLocation);
@ -164,19 +165,19 @@ namespace PlexRequests.UI.Helpers
if (!string.IsNullOrEmpty(content))
{
url = $"/{content}{url}";
}
}
if (context.Request.Path == url)
{
returnString = $"<a class=\"list-group-item active\" href=\"{url}\">{title}</a>";
}
}
else
{
returnString = $"<a class=\"list-group-item\" href=\"{url}\">{title}</a>";
}
return helper.Raw(returnString);
}
}
public static IHtmlString GetNavbarUrl(this HtmlHelpers helper, NancyContext context, string url, string title, string fontIcon)
{
var returnString = string.Empty;
@ -184,19 +185,19 @@ namespace PlexRequests.UI.Helpers
if (!string.IsNullOrEmpty(content))
{
url = $"/{content}{url}";
}
}
if (context.Request.Path == url)
{
returnString = $"<li class=\"active\"><a href=\"{url}\"><i class=\"fa fa-{fontIcon}\"></i> {title}</a></li>";
}
}
else
{
returnString = $"<li><a href=\"{url}\"><i class=\"fa fa-{fontIcon}\"></i> {title}</a></li>";
}
return helper.Raw(returnString);
}
}
public static IHtmlString GetNavbarUrl(this HtmlHelpers helper, NancyContext context, string url, string title, string fontIcon, string extraHtml)
{
var returnString = string.Empty;
@ -204,12 +205,12 @@ namespace PlexRequests.UI.Helpers
if (!string.IsNullOrEmpty(content))
{
url = $"/{content}{url}";
}
}
if (context.Request.Path == url)
{
returnString = $"<li class=\"active\"><a href=\"{url}\"><i class=\"fa fa-{fontIcon}\"></i> {title} {extraHtml}</a></li>";
}
}
else
{
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);
}
public static IHtmlString GetBaseUrl(this HtmlHelpers helper)
{
return helper.Raw(GetBaseUrl());
}
public static IHtmlString GetBaseUrl(this HtmlHelpers helper)
{
return helper.Raw(GetBaseUrl());
}
private static string GetBaseUrl()
{
return GetSettings().BaseUrl;
}
private static PlexRequestSettings GetSettings()
{
{
var returnValue = Cache.GetOrSet(CacheKeys.GetPlexRequestSettings, () =>
{
{
var settings = Locator.Resolve<ISettingsService<PlexRequestSettings>>().GetSettings();
return settings;
});
return returnValue;
}
private static string GetLinkUrl(string assetLocation)
{
return string.IsNullOrEmpty(assetLocation) ? string.Empty : $"{assetLocation}";
}
private static string GetContentUrl(string assetLocation)
{
return string.IsNullOrEmpty(assetLocation) ? string.Empty : $"/{assetLocation}";
}
private static string GetLinkUrl(string assetLocation)
{
return string.IsNullOrEmpty(assetLocation) ? string.Empty : $"{assetLocation}";
}
private static string GetContentUrl(string assetLocation)
{
return string.IsNullOrEmpty(assetLocation) ? string.Empty : $"/{assetLocation}";
}
}
}

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

@ -1,10 +1,14 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Nancy;
using Nancy.Responses.Negotiation;
using Nancy.Security;
using PlexRequests.Api.Interfaces;
using PlexRequests.Core;
using PlexRequests.Core.Models;
using PlexRequests.Core.SettingModels;
using PlexRequests.Helpers;
using PlexRequests.UI.Models;
@ -13,57 +17,88 @@ namespace PlexRequests.UI.Modules
{
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);
Get["/"] = x => Load();
//this.RequiresClaims(UserClaims.Admin);
Get["/users"] = x => LoadUsers();
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 IPlexApi PlexApi { get; }
private ISettingsService<AuthenticationSettings> AuthSettings { get; }
private Negotiator Load()
{
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>();
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
{
//Claims = ByteConverterHelper.ReturnObject<string[]>(user.Claims),
Claims = "test",
Id = user.Id,
Claims = claimsString,
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)
//{
// if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password))
// {
// return Response.AsJson(new JsonResponseModel
// {
// Result = true,
// Message = "Please enter in a valid Username and Password"
// });
// }
// var user = UserMapper.CreateUser(username, password, new string[] {claims});
// if (user.HasValue)
// {
// return Response.AsJson(new JsonResponseModel {Result = true});
// }
// return Response.AsJson(new JsonResponseModel {Result = false, Message = "Could not save user"});
//}
private Response CreateUser(string username, string password)
{
if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password))
{
return Response.AsJson(new JsonResponseModel
{
Result = true,
Message = "Please enter in a valid Username and Password"
});
}
var user = UserMapper.CreateRegularUser(username, password);
if (user.HasValue)
{
return Response.AsJson(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\CultureHelper.cs" />
<Compile Include="Helpers\CustomHtmlHelper.cs" />
<Compile Include="Helpers\HeadphonesSender.cs" />
<Compile Include="Helpers\EmptyViewBase.cs" />
<Compile Include="Helpers\HeadphonesSender.cs" />
<Compile Include="Helpers\AngularViewBase.cs" />
<Compile Include="Helpers\ServiceLocator.cs" />
<Compile Include="Helpers\StringHelper.cs" />
<Compile Include="Helpers\Themes.cs" />
@ -246,6 +247,9 @@
<Compile Include="Validators\RequestedModelValidator.cs" />
<Compile Include="Validators\SickRageValidator.cs" />
<Compile Include="Validators\SonarrValidator.cs" />
<Content Include="Content\app\app.js">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Content\awesome-bootstrap-checkbox.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@ -298,6 +302,9 @@
<Content Include="Content\issues.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\app\services\userManagement\userManagementService.js">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Content\swagger\backbone-min.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@ -494,6 +501,9 @@
<None Include="sqlite3.dll">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<Content Include="Content\app\controllers\userManagement\userManagementController.js">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="web.config">
<SubType>Designer</SubType>
</Content>
@ -596,6 +606,18 @@
<Content Include="Views\Admin\SchedulerSettings.cshtml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</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">
<DependentUpon>web.config</DependentUpon>
</None>
@ -698,6 +720,8 @@
<Target Name="AfterBuild">
</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')" />
</Project>

@ -2,7 +2,7 @@
@using Nancy.Session
@using PlexRequests.UI.Helpers
@using PlexRequests.UI.Models
@inherits Nancy.ViewEngines.Razor.NancyRazorViewBase
@inherits Nancy.ViewEngines.Razor.NancyRazorViewBase<dynamic>
@{
var baseUrl = Html.GetBaseUrl();
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 Nancy.Session
@using PlexRequests.UI.Helpers
@using PlexRequests.UI.Models
@using PlexRequests.UI.Resources
@using PlexRequests.UI.Helpers
@inherits Nancy.ViewEngines.Razor.NancyRazorViewBase
@{
var baseUrl = Html.GetBaseUrl();
var url = string.Empty;
if (!string.IsNullOrEmpty(baseUrl.ToHtmlString()))
{
url = "/" + baseUrl.ToHtmlString();
}
}
<html>
<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">
@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>
@Html.Partial("Shared/Partial/_Head")
<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>
<body>
}
<li class="dropdown">
@Html.Partial("Shared/Partial/_Navbar")
<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 class="container">
@RenderBody()
</div>
<div id="updateAvailable" hidden="hidden"></div>
</nav>
<div class="container">
@RenderBody()
</div>
<div class="scroll-top-wrapper ">
<span class="scroll-top-inner">
<div class="scroll-top-wrapper ">
<span class="scroll-top-inner">
<i class="fa fa-2x fa-arrow-circle-up"></i>
</span>
</div>
</div>
</body>
@Html.GetInformationalVersion();
</html>
<script>
$(function () {
var urlBase = '@Html.GetBaseUrl()';
// 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
@Html.GetInformationalVersion()
// 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>
</html>
@Html.Partial("Shared/Partial/_LayoutScripts")

@ -1,4 +1,7 @@
@using PlexRequests.UI.Helpers
@inherits PlexRequests.UI.Helpers.AngularViewBase
@Html.LoadTableAssets()
@{
var baseUrl = Html.GetBaseUrl().ToHtmlString();
@ -8,41 +11,53 @@
url = "/" + baseUrl;
}
}
<h2>User Management</h2>
<button class="btn btn-success-outline" type="submit">Create User <div class="fa fa-plus"/></button>
<br>
<br>
<script src="/Content/app/controllers/userManagement/userManagementController.js"></script>
<script src="/Content/app/services/userManagement/userManagementService.js"></script>
<div ng-controller="userManagementController" ng-init="getUsers()">
<br>
<br>
<fieldset>
<table id="example" class="table table-striped table-hover table-responsive">
<table class="table table-striped table-hover table-responsive">
<thead>
<tr>
<th>Id</th>
<th>Username</th>
<th>Permissions</th>
</tr>
<tr>
<th>Id</th>
<th>Username</th>
<th>Email</th>
<th>User T</th>
</tr>
</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>
</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