Finishing off the user management page #218 #359 #195

pull/687/head
Jamie.Rees 8 years ago
parent 55f1309140
commit c064bc6d44

@ -76,6 +76,9 @@ namespace PlexRequests.Core.Migration.Migrations
con.AlterTable("Users", "ADD", "Permissions", true, "INTEGER"); con.AlterTable("Users", "ADD", "Permissions", true, "INTEGER");
con.AlterTable("Users", "ADD", "Features", true, "INTEGER"); con.AlterTable("Users", "ADD", "Features", true, "INTEGER");
con.AlterTable("PlexUsers", "ADD", "Permissions", true, "INTEGER");
con.AlterTable("PlexUsers", "ADD", "Features", true, "INTEGER");
//https://image.tmdb.org/t/p/w150/https://image.tmdb.org/t/p/w150//aqhAqttDq7zgsTaBHtCD8wmTk6k.jpg //https://image.tmdb.org/t/p/w150/https://image.tmdb.org/t/p/w150//aqhAqttDq7zgsTaBHtCD8wmTk6k.jpg
// UI = https://image.tmdb.org/t/p/w150/{{posterPath}} // UI = https://image.tmdb.org/t/p/w150/{{posterPath}}

@ -50,5 +50,15 @@ namespace PlexRequests.Helpers.Permissions
[Display(Name = "Read Only User")] [Display(Name = "Read Only User")]
ReadOnlyUser = 32, ReadOnlyUser = 32,
[Display(Name = "Auto Approve Movie Requests")]
AutoApproveMovie = 64,
[Display(Name = "Auto Approve TV Show Requests")]
AutoApproveTv = 128,
[Display(Name = "Auto Approve Album Requests")]
AutoApproveAlbum = 256
} }
} }

@ -471,10 +471,10 @@ namespace PlexRequests.Services.Jobs
{ {
var sections = PlexApi.GetLibrarySections(plexSettings.PlexAuthToken, plexSettings.FullUri); var sections = PlexApi.GetLibrarySections(plexSettings.PlexAuthToken, plexSettings.FullUri);
List<PlexSearch> libs = new List<PlexSearch>(); var libs = new List<PlexSearch>();
if (sections != null) if (sections != null)
{ {
foreach (var dir in sections.Directories) foreach (var dir in sections.Directories ?? new List<Directory>())
{ {
var lib = PlexApi.GetLibrary(plexSettings.PlexAuthToken, plexSettings.FullUri, dir.Key); var lib = PlexApi.GetLibrary(plexSettings.PlexAuthToken, plexSettings.FullUri, dir.Key);
if (lib != null) if (lib != null)

@ -24,11 +24,17 @@
// 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 Dapper.Contrib.Extensions;
namespace PlexRequests.Store.Models namespace PlexRequests.Store.Models
{ {
[Table(nameof(PlexUsers))]
public class PlexUsers : Entity public class PlexUsers : Entity
{ {
public int PlexUserId { get; set; } public string PlexUserId { get; set; }
public string UserAlias { get; set; } public string UserAlias { get; set; }
public int Permissions { get; set; }
public int Features { get; set; }
} }
} }

@ -114,8 +114,10 @@ CREATE UNIQUE INDEX IF NOT EXISTS RequestLimit_Id ON RequestLimit (Id);
CREATE TABLE IF NOT EXISTS PlexUsers CREATE TABLE IF NOT EXISTS PlexUsers
( (
Id INTEGER PRIMARY KEY AUTOINCREMENT, Id INTEGER PRIMARY KEY AUTOINCREMENT,
PlexUserId INTEGER NOT NULL, PlexUserId varchar(100) NOT NULL,
UserAlias varchar(100) NOT NULL UserAlias varchar(100) NOT NULL,
Permissions INTEGER,
Features INTEGER
); );
CREATE UNIQUE INDEX IF NOT EXISTS PlexUsers_Id ON PlexUsers (Id); CREATE UNIQUE INDEX IF NOT EXISTS PlexUsers_Id ON PlexUsers (Id);

@ -120,29 +120,30 @@
$scope.updateUser = function () { $scope.updateUser = function () {
var u = $scope.selectedUser; var u = $scope.selectedUser;
userManagementService.updateUser(u.id, u.permissions, u.alias, u.emailAddress) userManagementService.updateUser(u.id, u.permissions, u.features, u.alias, u.emailAddress)
.then(function (data) { .then(function success(data) {
if (data) { if (data.data) {
$scope.selectedUser = data; $scope.selectedUser = data.data;
if (open) { closeSidebar();
open = false; return successCallback("Updated User", "success");
$("#wrapper").toggleClass("toggled");
} }
return successCallback("Updated User", "success"); }, function errorCallback(response) {
} successCallback(response, "danger");
}); });
} }
$scope.deleteUser = function () { $scope.deleteUser = function () {
var u = $scope.selectedUser; var u = $scope.selectedUser;
var result = userManagementService.deleteUser(u.id); userManagementService.deleteUser(u.id)
.then(function sucess(data) {
result.success(function (data) { if (data.data.result) {
if (data.result) {
removeUser(u.id, true); removeUser(u.id, true);
closeSidebar();
return successCallback("Deleted User", "success"); return successCallback("Deleted User", "success");
} }
}, function errorCallback(response) {
successCallback(response, "danger");
}); });
} }
@ -170,6 +171,13 @@
$scope.selectedUser = null; $scope.selectedUser = null;
} }
} }
function closeSidebar() {
if (open) {
open = false;
$("#wrapper").toggleClass("toggled");
}
}
} }
function successCallback(message, type) { function successCallback(message, type) {

@ -28,11 +28,11 @@
return $http.get('/usermanagement/permissions'); return $http.get('/usermanagement/permissions');
} }
var updateUser = function (id, permissions, alias, email) { var updateUser = function (id, permissions, features, alias, email) {
return $http({ return $http({
url: '/usermanagement/updateUser', url: '/usermanagement/updateUser',
method: "POST", method: "POST",
data: { id: id, permissions: permissions, alias: alias, emailAddress: email } data: { id: id, permissions: permissions, features: features, alias: alias, emailAddress: email },
}); });
} }

@ -75,6 +75,8 @@ namespace PlexRequests.UI.Models
public string Id { get; set; } public string Id { get; set; }
[JsonProperty("permissions")] [JsonProperty("permissions")]
public List<CheckBox> Permissions { get; set; } public List<CheckBox> Permissions { get; set; }
[JsonProperty("features")]
public List<CheckBox> Features { get; set; }
public string Alias { get; set; } public string Alias { get; set; }
public string EmailAddress { get; set; } public string EmailAddress { get; set; }
} }

@ -6,15 +6,16 @@ using System.Threading.Tasks;
using Nancy; using Nancy;
using Nancy.Extensions; using Nancy.Extensions;
using Nancy.Responses.Negotiation; using Nancy.Responses.Negotiation;
using Nancy.Security;
using Newtonsoft.Json; using Newtonsoft.Json;
using PlexRequests.Api.Interfaces; using PlexRequests.Api.Interfaces;
using PlexRequests.Api.Models.Plex;
using PlexRequests.Core; using PlexRequests.Core;
using PlexRequests.Core.Models; using PlexRequests.Core.Models;
using PlexRequests.Core.SettingModels; using PlexRequests.Core.SettingModels;
using PlexRequests.Helpers; using PlexRequests.Helpers;
using PlexRequests.Helpers.Permissions; using PlexRequests.Helpers.Permissions;
using PlexRequests.Store; using PlexRequests.Store;
using PlexRequests.Store.Models;
using PlexRequests.Store.Repository; using PlexRequests.Store.Repository;
using PlexRequests.UI.Models; using PlexRequests.UI.Models;
@ -22,15 +23,16 @@ namespace PlexRequests.UI.Modules
{ {
public class UserManagementModule : BaseModule public class UserManagementModule : BaseModule
{ {
public UserManagementModule(ISettingsService<PlexRequestSettings> pr, ICustomUserMapper m, IPlexApi plexApi, ISettingsService<PlexSettings> plex, IRepository<UserLogins> userLogins) : base("usermanagement", pr) public UserManagementModule(ISettingsService<PlexRequestSettings> pr, ICustomUserMapper m, IPlexApi plexApi, ISettingsService<PlexSettings> plex, IRepository<UserLogins> userLogins, IRepository<PlexUsers> plexRepo) : base("usermanagement", pr)
{ {
#if !DEBUG #if !DEBUG
this.RequiresAnyClaim(UserClaims.Admin); Before += (ctx) => Security.AdminLoginRedirect(Permissions.Administrator, ctx);
#endif #endif
UserMapper = m; UserMapper = m;
PlexApi = plexApi; PlexApi = plexApi;
PlexSettings = plex; PlexSettings = plex;
UserLoginsRepo = userLogins; UserLoginsRepo = userLogins;
PlexUsersRepository = plexRepo;
Get["/"] = x => Load(); Get["/"] = x => Load();
@ -40,7 +42,7 @@ namespace PlexRequests.UI.Modules
Get["/plex/{id}", true] = async (x, ct) => await PlexDetails(x.id); Get["/plex/{id}", true] = async (x, ct) => await PlexDetails(x.id);
Get["/permissions"] = x => GetEnum<Permissions>(); Get["/permissions"] = x => GetEnum<Permissions>();
Get["/features"] = x => GetEnum<Features>(); Get["/features"] = x => GetEnum<Features>();
Post["/updateuser"] = x => UpdateUser(); Post["/updateuser", true] = async (x, ct) => await UpdateUser();
Post["/deleteuser"] = x => DeleteUser(); Post["/deleteuser"] = x => DeleteUser();
} }
@ -48,6 +50,7 @@ namespace PlexRequests.UI.Modules
private IPlexApi PlexApi { get; } private IPlexApi PlexApi { get; }
private ISettingsService<PlexSettings> PlexSettings { get; } private ISettingsService<PlexSettings> PlexSettings { get; }
private IRepository<UserLogins> UserLoginsRepo { get; } private IRepository<UserLogins> UserLoginsRepo { get; }
private IRepository<PlexUsers> PlexUsersRepository { get; }
private Negotiator Load() private Negotiator Load()
{ {
@ -57,13 +60,14 @@ namespace PlexRequests.UI.Modules
private async Task<Response> LoadUsers() private async Task<Response> LoadUsers()
{ {
var localUsers = await UserMapper.GetUsersAsync(); var localUsers = await UserMapper.GetUsersAsync();
var plexDbUsers = await PlexUsersRepository.GetAllAsync();
var model = new List<UserManagementUsersViewModel>(); var model = new List<UserManagementUsersViewModel>();
var usersDb = UserLoginsRepo.GetAll().ToList(); var userLogins = UserLoginsRepo.GetAll().ToList();
foreach (var user in localUsers) foreach (var user in localUsers)
{ {
var userDb = usersDb.FirstOrDefault(x => x.UserId == user.UserGuid); var userDb = userLogins.FirstOrDefault(x => x.UserId == user.UserGuid);
model.Add(MapLocalUser(user, userDb?.LastLoggedIn ?? DateTime.MinValue)); model.Add(MapLocalUser(user, userDb?.LastLoggedIn ?? DateTime.MinValue));
} }
@ -75,20 +79,18 @@ namespace PlexRequests.UI.Modules
foreach (var u in plexUsers.User) foreach (var u in plexUsers.User)
{ {
var userDb = usersDb.FirstOrDefault(x => x.UserId == u.Id); var dbUser = plexDbUsers.FirstOrDefault(x => x.PlexUserId == u.Id);
model.Add(new UserManagementUsersViewModel var userDb = userLogins.FirstOrDefault(x => x.UserId == u.Id);
// We don't have the user in the database yet
if (dbUser == null)
{ {
Username = u.Username, model.Add(MapPlexUser(u, null, userDb?.LastLoggedIn ?? DateTime.MinValue));
Type = UserType.PlexUser, }
Id = u.Id, else
FeaturesFormattedString = "Requestor", {
EmailAddress = u.Email, // The Plex User is in the database
PlexInfo = new UserManagementPlexInformation model.Add(MapPlexUser(u, dbUser, userDb?.LastLoggedIn ?? DateTime.MinValue));
{ }
Thumb = u.Thumb
},
LastLoggedIn = userDb?.LastLoggedIn ?? DateTime.MinValue,
});
} }
} }
return Response.AsJson(model); return Response.AsJson(model);
@ -128,7 +130,7 @@ namespace PlexRequests.UI.Modules
permissionsVal += f; permissionsVal += f;
} }
var user = UserMapper.CreateUser(model.Username, model.Password, featuresVal, permissionsVal, new UserProperties { EmailAddress = model.EmailAddress }); var user = UserMapper.CreateUser(model.Username, model.Password, permissionsVal, featuresVal, new UserProperties { EmailAddress = model.EmailAddress });
if (user.HasValue) if (user.HasValue)
{ {
return Response.AsJson(MapLocalUser(UserMapper.GetUser(user.Value), DateTime.MinValue)); return Response.AsJson(MapLocalUser(UserMapper.GetUser(user.Value), DateTime.MinValue));
@ -137,7 +139,7 @@ namespace PlexRequests.UI.Modules
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Could not save user" }); return Response.AsJson(new JsonResponseModel { Result = false, Message = "Could not save user" });
} }
private Response UpdateUser() private async Task<Response> UpdateUser()
{ {
var body = Request.Body.AsString(); var body = Request.Body.AsString();
if (string.IsNullOrEmpty(body)) if (string.IsNullOrEmpty(body))
@ -156,22 +158,68 @@ namespace PlexRequests.UI.Modules
}); });
} }
var val = model.Permissions.Where(c => c.Selected).Sum(c => c.Value); var permissionsValue = model.Permissions.Where(c => c.Selected).Sum(c => c.Value);
var featuresValue = model.Features.Where(c => c.Selected).Sum(c => c.Value);
Guid outId;
Guid.TryParse(model.Id, out outId);
var localUser = UserMapper.GetUser(outId);
// Update Local User
if (localUser != null)
{
localUser.Permissions = permissionsValue;
localUser.Features = featuresValue;
var currentProps = ByteConverterHelper.ReturnObject<UserProperties>(localUser.UserProperties);
currentProps.UserAlias = model.Alias;
currentProps.EmailAddress = model.EmailAddress;
localUser.UserProperties = ByteConverterHelper.ReturnBytes(currentProps);
var user = UserMapper.EditUser(localUser);
var dbUser = UserLoginsRepo.GetAll().FirstOrDefault(x => x.UserId == user.UserGuid);
var retUser = MapLocalUser(user, dbUser?.LastLoggedIn ?? DateTime.MinValue);
return Response.AsJson(retUser);
}
var plexSettings = await PlexSettings.GetSettingsAsync();
var plexDbUsers = await PlexUsersRepository.GetAllAsync();
var plexUsers = PlexApi.GetUsers(plexSettings.PlexAuthToken);
var plexDbUser = plexDbUsers.FirstOrDefault(x => x.PlexUserId == model.Id);
var plexUser = plexUsers.User.FirstOrDefault(x => x.Id == model.Id);
var userLogin = UserLoginsRepo.GetAll().FirstOrDefault(x => x.UserId == model.Id);
if (plexDbUser != null && plexUser != null)
{
// We have a user in the DB for this Plex Account
plexDbUser.Permissions = permissionsValue;
plexDbUser.Features = featuresValue;
var userFound = UserMapper.GetUser(new Guid(model.Id)); plexDbUser.UserAlias = model.Alias;
userFound.Permissions = val; await PlexUsersRepository.UpdateAsync(plexDbUser);
var retUser = MapPlexUser(plexUser, plexDbUser, userLogin?.LastLoggedIn ?? DateTime.MinValue);
return Response.AsJson(retUser);
}
var currentProps = ByteConverterHelper.ReturnObject<UserProperties>(userFound.UserProperties); // We have a Plex Account but he's not in the DB
currentProps.UserAlias = model.Alias; if (plexUser != null)
currentProps.EmailAddress = model.EmailAddress; {
var user = new PlexUsers
{
Permissions = permissionsValue,
Features = featuresValue,
UserAlias = model.Alias,
PlexUserId = plexUser.Id
};
userFound.UserProperties = ByteConverterHelper.ReturnBytes(currentProps); await PlexUsersRepository.InsertAsync(user);
var user = UserMapper.EditUser(userFound); var retUser = MapPlexUser(plexUser, user, userLogin?.LastLoggedIn ?? DateTime.MinValue);
var dbUser = UserLoginsRepo.GetAll().FirstOrDefault(x => x.UserId == user.UserGuid); return Response.AsJson(retUser);
var retUser = MapLocalUser(user, dbUser?.LastLoggedIn ?? DateTime.MinValue); }
return Response.AsJson(retUser); return null; // We should never end up here.
} }
private Response DeleteUser() private Response DeleteUser()
@ -248,8 +296,8 @@ namespace PlexRequests.UI.Modules
private UserManagementUsersViewModel MapLocalUser(UsersModel user, DateTime lastLoggedIn) private UserManagementUsersViewModel MapLocalUser(UsersModel user, DateTime lastLoggedIn)
{ {
var features = (Features) user.Features; var features = (Features)user.Features;
var permissions = (Permissions) user.Permissions; var permissions = (Permissions)user.Permissions;
var userProps = ByteConverterHelper.ReturnObject<UserProperties>(user.UserProperties); var userProps = ByteConverterHelper.ReturnObject<UserProperties>(user.UserProperties);
@ -297,6 +345,64 @@ namespace PlexRequests.UI.Modules
return m; return m;
} }
private UserManagementUsersViewModel MapPlexUser(UserFriends plexInfo, PlexUsers dbUser, DateTime lastLoggedIn)
{
if (dbUser == null)
{
dbUser = new PlexUsers();
}
var features = (Features)dbUser?.Features;
var permissions = (Permissions)dbUser?.Permissions;
var m = new UserManagementUsersViewModel
{
Id = plexInfo.Id,
PermissionsFormattedString = permissions == 0 ? "None" : permissions.ToString(),
FeaturesFormattedString = features.ToString(),
Username = plexInfo.Username,
Type = UserType.PlexUser,
EmailAddress = plexInfo.Email,
Alias = dbUser?.UserAlias ?? string.Empty,
LastLoggedIn = lastLoggedIn,
PlexInfo = new UserManagementPlexInformation
{
Thumb = plexInfo.Thumb
},
};
// Add permissions
foreach (var p in Enum.GetValues(typeof(Permissions)))
{
var perm = (Permissions)p;
var displayValue = EnumHelper<Permissions>.GetDisplayValue(perm);
var pm = new CheckBox
{
Name = displayValue,
Selected = permissions.HasFlag(perm),
Value = (int)perm
};
m.Permissions.Add(pm);
}
// Add features
foreach (var p in Enum.GetValues(typeof(Features)))
{
var perm = (Features)p;
var displayValue = EnumHelper<Features>.GetDisplayValue(perm);
var pm = new CheckBox
{
Name = displayValue,
Selected = features.HasFlag(perm),
Value = (int)perm
};
m.Features.Add(pm);
}
return m;
}
} }
} }

@ -470,4 +470,7 @@
<data name="Layout_CacherRunning" xml:space="preserve"> <data name="Layout_CacherRunning" xml:space="preserve">
<value>A background process is currently running, so there might be some unexpected behavior. This shouldn't take too long.</value> <value>A background process is currently running, so there might be some unexpected behavior. This shouldn't take too long.</value>
</data> </data>
<data name="Layout_Usermanagement" xml:space="preserve">
<value>User Management</value>
</data>
</root> </root>

@ -402,6 +402,15 @@ namespace PlexRequests.UI.Resources {
} }
} }
/// <summary>
/// Looks up a localized string similar to User Management.
/// </summary>
public static string Layout_Usermanagement {
get {
return ResourceManager.GetString("Layout_Usermanagement", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Welcome. /// Looks up a localized string similar to Welcome.
/// </summary> /// </summary>

@ -35,6 +35,10 @@
@Html.GetNavbarUrl(Context, "/search", UI.Layout_Search, "search") @Html.GetNavbarUrl(Context, "/search", UI.Layout_Search, "search")
@Html.GetNavbarUrl(Context, "/requests", UI.Layout_Requests, "plus-circle") @Html.GetNavbarUrl(Context, "/requests", UI.Layout_Requests, "plus-circle")
@Html.GetNavbarUrl(Context, "/issues", UI.Layout_Issues, "exclamation", "<span id=\"issueCount\"></span>") @Html.GetNavbarUrl(Context, "/issues", UI.Layout_Issues, "exclamation", "<span id=\"issueCount\"></span>")
@if (Html.IsAdmin())
{
@Html.GetNavbarUrl(Context, "/usermanagement", UI.Layout_Usermanagement, "users")
}
@*@if (Context.CurrentUser.IsAuthenticated()) // TODO replace with IsAdmin*@ @*@if (Context.CurrentUser.IsAuthenticated()) // TODO replace with IsAdmin*@
@if (Html.IsAdmin()) @if (Html.IsAdmin())
{ {

@ -15,30 +15,31 @@
<div ng-show="selectedUser.emailAddress"> <div ng-show="selectedUser.emailAddress">
<strong>Email Address: </strong><span ng-bind="selectedUser.emailAddress"></span> <strong>Email Address: </strong><span ng-bind="selectedUser.emailAddress"></span>
</div> </div>
<div>
<strong>Permissions: </strong><span ng-bind="selectedUser.permissionsFormattedString"></span>
</div>
<div>
<strong>Features: </strong><span ng-bind="selectedUser.featuresFormattedString"></span>
</div>
<div> <div>
<strong>User Type: </strong><span ng-bind="selectedUser.type === 1 ? 'Local User' : 'Plex User'"></span> <strong>User Type: </strong><span ng-bind="selectedUser.type === 1 ? 'Local User' : 'Plex User'"></span>
</div> </div>
<br /> <br />
<br /> <br />
<div ng-show="selectedUser.type === 1"> <div ng-show="selectedUser">
<!--Edit--> <!--Edit-->
<strong>Modify Roles:</strong> <strong>Modify Permissions:</strong>
<!--Load all claims--> <!--Load all permissions-->
<div class="checkbox" ng-repeat="p in selectedUser.permissions"> <div class="checkbox" ng-repeat="p in selectedUser.permissions">
<input id="permissionsCheckbox_{{$id}}" class="checkbox-custom" name="permissions[]" ng-checked="p.selected" ng-model="p.selected" type="checkbox" value="{{p.value}}" /> <input id="permissionsCheckbox_{{$id}}" class="checkbox-custom" name="permissions[]" ng-checked="p.selected" ng-model="p.selected" type="checkbox" value="{{p.value}}" />
<label for="permissionsCheckbox_{{$id}}">{{p.name}}</label> <label for="permissionsCheckbox_{{$id}}">{{p.name}}</label>
</div> </div>
<strong>Modify Features:</strong>
<!--Load all features-->
<div class="checkbox" ng-repeat="p in selectedUser.features">
<input id="featuresCheckbox_{{$id}}" class="checkbox-custom" name="features[]" ng-checked="p.selected" ng-model="p.selected" type="checkbox" value="{{p.value}}" />
<label for="featuresCheckbox_{{$id}}">{{p.name}}</label>
</div>
<strong>Email Address</strong> <strong>Email Address</strong>
<div class="form-group"> <div class="form-group">
<input id="emailAddress" type="email" ng-model="selectedUser.emailAddress" class="form-control form-control-custom" /> <input id="emailAddress" type="email" ng-model="selectedUser.emailAddress" ng-disabled="selectedUser.type === 0" class="form-control form-control-custom" />
</div> </div>
<strong>Alias</strong> <strong>Alias</strong>

Loading…
Cancel
Save