output users + roles

This commit is contained in:
2020-04-24 12:46:25 +02:00
parent a44cc4b04d
commit 76e95d34f3
18 changed files with 430 additions and 99 deletions
+23 -60
View File
@@ -1,6 +1,7 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Raven.Client.Documents;
using Raven.Client.Documents.Session;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
@@ -18,96 +19,58 @@ namespace zero.Core.Api
protected UserManager<User> UserManager { get; private set; }
protected RoleManager<UserRole> RoleManager { get; private set; }
private ClaimsPrincipal Principal => HttpContextAccessor.HttpContext?.User;
public UserRolesApi(IDocumentStore raven, IHttpContextAccessor httpContextAccessor, UserManager<User> userManager)
public UserRolesApi(IDocumentStore raven, IHttpContextAccessor httpContextAccessor, UserManager<User> userManager, RoleManager<UserRole> roleManager)
{
Raven = raven;
HttpContextAccessor = httpContextAccessor;
UserManager = userManager;
RoleManager = roleManager;
}
/// <inheritdoc />
public async Task<User> GetUser()
public async Task<IList<UserRole>> GetAll()
{
User user = await UserManager.GetUserAsync(HttpContextAccessor.HttpContext.User);
return user;
using (IAsyncDocumentSession session = Raven.OpenAsyncSession())
{
return await session.Query<UserRole>().OrderBy(x => x.Sort).ThenBy(x => x.Name).ToListAsync();
}
}
/// <inheritdoc />
public async Task<User> GetUserById(string id)
public async Task<UserRole> GetById(string id)
{
User user = await UserManager.FindByIdAsync(id);
return user;
return await RoleManager.FindByIdAsync(id);
}
/// <inheritdoc />
public async Task<User> GetUserByEmail(string email)
{
User user = await UserManager.FindByEmailAsync(email);
return user;
}
/// <inheritdoc />
public bool IsSuper()
{
return Principal.HasClaim(Constants.Auth.Claims.IsSuper, PermissionsValue.True);
}
/// <inheritdoc />
public bool IsAdmin()
{
return Principal.HasClaim(Constants.Auth.Claims.Role, "administrator"); // TODO use constant (in setup as well)
}
/// <inheritdoc />
public IList<Permission> GetPermissions(string prefix = null)
{
return Principal.Claims
.Where(claim => claim.Type == Constants.Auth.Claims.Permission && (prefix == null || claim.Value.StartsWith(prefix)))
.Select(claim => new Permission(claim, prefix))
.ToList();
}
//public IList<Permission> GetPermissions(string prefix = null)
//{
// return Principal.Claims
// .Where(claim => claim.Type == Constants.Auth.Claims.Permission && (prefix == null || claim.Value.StartsWith(prefix)))
// .Select(claim => new Permission(claim, prefix))
// .ToList();
//}
}
public interface IUserRolesApi
{
/// <summary>
/// Get currently logged-in user
/// Get all user roles
/// </summary>
Task<User> GetUser();
Task<IList<UserRole>> GetAll();
/// <summary>
/// Find user by id
/// Get role by id
/// </summary>
Task<User> GetUserById(string id);
/// <summary>
/// Find user by email
/// </summary>
Task<User> GetUserByEmail(string email);
/// <summary>
/// Whether the current user is the super user who created the zero instance
/// </summary>
bool IsSuper();
/// <summary>
/// Whether the current user belongs to the administrator role (will always return false if this role gets deleted)
/// </summary>
bool IsAdmin();
/// <summary>
/// Get all permissions for the current user with an optional prefix
/// </summary>
IList<Permission> GetPermissions(string prefix = null);
Task<UserRole> GetById(string id);
}
}
+1 -7
View File
@@ -299,7 +299,7 @@
.ui-table-cell
{
display: inline-block;
display: inline-flex;
align-items: center;
flex: 1 1 5%;
position: relative;
@@ -439,10 +439,4 @@
}
}
}
.ui-table .flag
{
position: relative;
top: 2px;
}
</style>
+16
View File
@@ -0,0 +1,16 @@
import Axios from 'axios';
export default {
// get role by id
getById(id)
{
return Axios.get('userRoles/getById', { params: { id } }).then(res => Promise.resolve(res.data));
},
// get all roles
getAll()
{
return Axios.get('userRoles/getAll').then(res => Promise.resolve(res.data));
}
};
+38 -10
View File
@@ -1,13 +1,17 @@
import { map as _map, find as _find } from 'underscore';
import { map as _map, find as _find, isArray as _isArray } from 'underscore';
const alias = zero.alias.sections.settings;
const section = _find(zero.sections, section => section.alias === alias);
let routes = [];
const detailPages = [];
detailPages[zero.alias.settings.users] = 'user';
detailPages[zero.alias.settings.users] = [
{ view: 'user' },
{ view: 'role', path: 'role' }
];
detailPages[zero.alias.settings.countries] = 'country';
if (section)
{
zero.settingsAreas.forEach(group => group.items.forEach(area =>
@@ -25,14 +29,38 @@ if (section)
// add details page
if (detailPages[area.alias])
{
routes.push({
path: area.url + '/edit/:id',
name: alias + '-' + area.alias + '-edit',
component: () => import(`zero/pages/${alias}/${detailPages[area.alias]}`),
props: true,
meta: {
name: [area.name, section.name]
}
var config = detailPages[area.alias];
var details = [];
if (typeof config === 'string')
{
details.push({
view: config,
path: 'edit'
});
}
else if (_isArray(config))
{
details = config;
}
else if (typeof config === 'object')
{
details.push(config);
}
details.forEach(detail =>
{
const path = detail.path || 'edit';
routes.push({
path: area.url + '/' + path + '/:id',
name: alias + '-' + area.alias + '-' + path,
component: () => import(`zero/pages/${alias}/${detail.view}`),
props: true,
meta: {
name: [area.name, section.name]
}
});
});
}
}));
+128 -7
View File
@@ -5,9 +5,21 @@
<ui-button type="light" label="Add role" icon="fth-plus" />
<!--<ui-table-filter :filter="false" />-->
</ui-header-bar>
<div class="ui-blank-box">
<router-link :to="{ name: 'settings-users-edit', params: { id: _user.id } }">{{_user.name}}</router-link>
<!--<ui-table :config="tableConfig" />-->
<h2 class="ui-headline users-group-headline" v-localize="'@user.roles'"></h2>
<div class="users-roles">
<router-link v-for="role in roles" :to="getRoleLink(role)" class="users-role">
<i class="users-role-icon" :class="role.icon"></i>
<strong>{{role.name}}</strong>
<span class="users-role-minor" v-localize="{ key: role.countClaims !== 1 ? '@user.count_permissions' : '@user.one_permission', tokens: { count: role.countClaims }}"></span>
</router-link>
</div>
</div>
<div class="ui-blank-box">
<h2 class="ui-headline users-group-headline" v-localize="'@user.users'"></h2>
<ui-table v-model="usersConfig" />
</div>
</div>
</template>
@@ -15,27 +27,136 @@
<script>
import AuthApi from 'zero/services/auth.js'
import UserRolesApi from 'zero/resources/userRoles.js';
import UsersApi from 'zero/resources/users.js';
export default {
data: () => ({
_user: null
roles: [],
usersConfig: {}
}),
created()
{
this._user = AuthApi.user;
this.usersConfig = {
labelPrefix: '@user.fields.',
search: null,
columns: {
avatar: {
label: '',
as: 'html',
render: item => `<img src=${item.avatar} class="users-list-avatar">`,
width: 70,
link: this.getUserLink
},
name: {
label: '@ui.name',
as: 'text',
bold: true,
link: this.getUserLink
},
email: 'text',
roles: 'text',
isActive: {
as: 'bool',
width: 200
}
},
items: UsersApi.getAll
};
UserRolesApi.getAll().then(items =>
{
this.roles = items;
console.info(JSON.parse(JSON.stringify(items)));
});
},
methods: {
onBack()
getRoleLink(item)
{
this.$router.go(-1);
}
return {
name: zero.alias.sections.settings + '-' + zero.alias.settings.users + '-role',
params: { id: item.id }
};
},
getUserLink(item)
{
return {
name: zero.alias.sections.settings + '-' + zero.alias.settings.users + '-edit',
params: { id: item.id }
};
},
}
}
</script>
<style lang="scss">
h2.users-group-headline
{
margin-bottom: 30px;
}
.users-list-avatar
{
border-radius: 50px;
}
.users .ui-table-cell[table-field="avatar"]
{
padding: 12px;
padding-left: 20px;
}
.users .ui-table-cell[table-field="name"]
{
border-left-color: transparent;
}
.users-roles
{
display: grid;
grid-gap: var(--padding);
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
align-items: stretch;
margin-bottom: calc(var(--padding) * 2);
}
a.users-role
{
display: flex;
flex-direction: column;
background: var(--color-box);
border-radius: var(--radius);
padding: var(--padding-s) var(--padding);
text-align: center;
color: var(--color-fg);
font-size: var(--font-size);
line-height: 1.5;
transition: box-shadow 0.2s ease;
&:hover
{
box-shadow: 0 0 20px var(--color-shadow);
}
}
.users-role-minor
{
color: var(--color-fg-mid);
}
.users-role-icon
{
font-size: 26px;
text-align: center;
display: inline-block;
margin: 0 auto var(--padding-s);
position: relative;
}
</style>
+40 -8
View File
@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Threading.Tasks;
using zero.Core;
using zero.Core.Api;
@@ -33,7 +34,7 @@ namespace zero.Web.Controllers
}
protected async Task<IActionResult> As<T, TTarget>(T model) where TTarget : class, new() where T : IZeroEntity
protected IActionResult As<T, TTarget>(T model) where TTarget : class, new() where T : IZeroEntity
{
if (model == null)
{
@@ -47,18 +48,49 @@ namespace zero.Web.Controllers
TTarget result = Mapper.Map<T, TTarget>(model);
//if (result is EditModel)
//{
// (result as EditModel).Meta = new EditModelMeta()
// {
// Token = await Token.Get(model)
// };
//}
return Json(result);
}
protected IActionResult As<T, TTarget>(IEnumerable<T> model) where TTarget : class, new() where T : IZeroEntity
{
if (model == null)
{
return new StatusCodeResult(404);
}
IList<TTarget> result = new List<TTarget>();
foreach (T item in model)
{
result.Add(Mapper.Map<T, TTarget>(item));
}
return Json(result);
}
protected IActionResult As<T, TTarget>(ListResult<T> model) where TTarget : class, new() where T : IZeroEntity
{
if (model == null)
{
return new StatusCodeResult(404);
}
IList<TTarget> list = new List<TTarget>();
foreach (T item in model.Items)
{
list.Add(Mapper.Map<T, TTarget>(item));
}
return Json(new ListResult<TTarget>(list, model.TotalItems, model.Page, model.PageSize)
{
Statistics = model.Statistics
});
}
protected TTarget Map<T, TTarget>(T model) where TTarget : class, new()
{
return Mapper.Map<T, TTarget>(model);
+4 -3
View File
@@ -28,7 +28,7 @@ namespace zero.Web.Controllers
[AddToken]
public async Task<IActionResult> GetById([FromQuery] string id)
{
return await As<Country, CountryEditModel>(await Api.GetById(id));
return As<Country, CountryEditModel>(await Api.GetById(id));
}
@@ -37,7 +37,7 @@ namespace zero.Web.Controllers
/// </summary>
public async Task<IActionResult> GetAll([FromQuery] ListQuery<Country> query)
{
return Json(await Api.GetByQuery("en-US", query));
return As<Country, CountryListModel>(await Api.GetByQuery("en-US", query));
}
@@ -48,7 +48,8 @@ namespace zero.Web.Controllers
[ZeroAuthorize(Permissions.Settings.Countries, PermissionsValue.Write)]
public async Task<IActionResult> Save([FromBody] CountryEditModel model)
{
return Json(await Api.Save(Mapper.Map<CountryEditModel, Country>(model)));
Country country = Mapper.Map(model, await Api.GetById(model.Id));
return Json(await Api.Save(country));
}
@@ -0,0 +1,40 @@
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
using zero.Core;
using zero.Core.Api;
using zero.Core.Entities;
using zero.Core.Identity;
using zero.Web.Mapper;
using zero.Web.Models;
namespace zero.Web.Controllers
{
[ZeroAuthorize(Permissions.Settings.Users, PermissionsValue.Read)]
public class UserRolesController : BackofficeController
{
private IUserRolesApi Api { get; set; }
public UserRolesController(IZeroConfiguration config, IUserRolesApi api, IMapper mapper, IToken token) : base(config, mapper, token)
{
Api = api;
}
/// <summary>
/// Get role by id
/// </summary>
public async Task<IActionResult> GetById([FromQuery] string id)
{
return As<UserRole, UserRoleEditModel>(await Api.GetById(id));
}
/// <summary>
/// Get all roles
/// </summary>
public async Task<IActionResult> GetAll()
{
return As<UserRole, UserRoleListModel>(await Api.GetAll());
}
}
}
+2 -2
View File
@@ -25,7 +25,7 @@ namespace zero.Web.Controllers
/// </summary>
public async Task<IActionResult> GetById([FromQuery] string id)
{
return await As<User, UserEditModel>(await Api.GetUserById(id));
return As<User, UserEditModel>(await Api.GetUserById(id));
}
@@ -34,7 +34,7 @@ namespace zero.Web.Controllers
/// </summary>
public async Task<IActionResult> GetAll([FromQuery] ListQuery<User> query)
{
return Json(await Api.GetByQuery(query, "zero.applications.1-A"));
return As<User, UserListModel>(await Api.GetByQuery(query, "zero.applications.1-A"));
}
}
}
+9
View File
@@ -29,6 +29,15 @@ namespace zero.Web.Mapper
target.LanguageId = source.LanguageId;
target.Code = source.Code;
});
config.CreateMap<Country, CountryListModel>((source, target) =>
{
target.Id = source.Id;
target.Name = source.Name;
target.IsActive = source.IsActive;
target.IsPreferred = source.IsPreferred;
target.Code = source.Code;
});
}
}
}
+40 -1
View File
@@ -1,4 +1,5 @@
using zero.Core.Entities;
using System;
using zero.Core.Entities;
using zero.Web.Models;
namespace zero.Web.Mapper
@@ -36,6 +37,44 @@ namespace zero.Web.Mapper
target.Claims = source.Claims;
target.LockoutEnd = source.LockoutEnd;
});
config.CreateMap<User, UserListModel>((source, target) =>
{
target.Id = source.Id;
target.Name = source.Name;
target.IsActive = source.IsActive && (!source.LockoutEnabled || !source.LockoutEnd.HasValue);
target.Email = source.Email;
target.Avatar = "http://localhost:14051/media/UserAvatars/09eb6d4d41894a44a9585b94bf9cff41.jpg?width=50&height=50&mode=crop"; // TODO //source.Avatar?.Source;
target.Roles = String.Join(", ", source.Roles); // TODO get name from alias
});
config.CreateMap<UserRole, UserRoleEditModel>((source, target) =>
{
target.Id = source.Id;
target.Name = source.Name;
target.IsActive = source.IsActive;
target.CreatedDate = source.CreatedDate;
target.Description = source.Description;
target.Icon = source.Icon;
target.Claims = source.Claims;
});
config.CreateMap<UserRoleEditModel, UserRole>((source, target) =>
{
target.Name = source.Name;
target.IsActive = source.IsActive;
target.Description = source.Description;
target.Icon = source.Icon;
target.Claims = source.Claims;
});
config.CreateMap<UserRole, UserRoleListModel>((source, target) =>
{
target.Id = source.Id;
target.Name = source.Name;
target.CountClaims = source.Claims.Count;
target.Icon = source.Icon;
});
}
}
}
+15
View File
@@ -0,0 +1,15 @@
using System;
namespace zero.Web.Models
{
public class CountryListModel : ListModel
{
public string Name { get; set; }
public bool IsActive { get; set; }
public bool IsPreferred { get; set; }
public string Code { get; set; }
}
}
+10
View File
@@ -0,0 +1,10 @@
namespace zero.Web.Models
{
public abstract class ListModel
{
/// <summary>
/// Id of the entity
/// </summary>
public string Id { get; set; }
}
}
+15
View File
@@ -0,0 +1,15 @@
namespace zero.Web.Models
{
public class UserListModel : ListModel
{
public string Name { get; set; }
public bool IsActive { get; set; }
public string Email { get; set; }
public string Roles { get; set; }
public string Avatar { get; set; }
}
}
+21
View File
@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using zero.Core.Entities;
namespace zero.Web.Models
{
public class UserRoleEditModel : EditModel
{
public string Name { get; set; }
public bool IsActive { get; set; }
public DateTimeOffset CreatedDate { get; set; }
public string Description { get; set; }
public string Icon { get; set; }
public List<IUserClaim> Claims { get; set; }
}
}
+11
View File
@@ -0,0 +1,11 @@
namespace zero.Web.Models
{
public class UserRoleListModel : ListModel
{
public string Name { get; set; }
public int CountClaims { get; set; }
public string Icon { get; set; }
}
}
@@ -113,6 +113,10 @@
"user": {
"name": "User",
"users": "Users",
"roles": "Roles",
"count_permissions": "{count} permissions",
"one_permission": "1 permission",
"fields": {
"name_placeholder": "Enter your real name or a username",
"email": "Email",
@@ -125,7 +129,8 @@
"roles": "Roles",
"roles_text": "Add default permissions from roles",
"sections": "Sections",
"sections_text": "Specify access to backoffice sections"
"sections_text": "Specify access to backoffice sections",
"isActive": "Can login"
}
},
+11
View File
@@ -97,6 +97,17 @@
}
:root
{
--color-accent-yellow: #ffd100;
}
.color-yellow
{
color: var(--color-accent-yellow);
}
// font sizes
$font-size: 14px;