mirror of
https://github.com/documize/community.git
synced 2025-07-21 14:19:43 +02:00
add/remove group membership
This commit is contained in:
parent
0b5ed8fd9e
commit
ed11c0ad11
17 changed files with 287 additions and 87 deletions
|
@ -94,7 +94,7 @@ func (h *Handler) Sync(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// User list from Documize
|
// User list from Documize
|
||||||
dmzUsers, err := h.Store.User.GetUsersForOrganization(ctx)
|
dmzUsers, err := h.Store.User.GetUsersForOrganization(ctx, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
result.Message = "Error: unable to fetch Documize users"
|
result.Message = "Error: unable to fetch Documize users"
|
||||||
result.IsError = true
|
result.IsError = true
|
||||||
|
|
|
@ -141,3 +141,25 @@ func (s Scope) LeaveGroup(ctx domain.RequestContext, groupID, userID string) (er
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetMembers returns members for every group.
|
||||||
|
// Useful when you need to bulk fetch membership records
|
||||||
|
// for subsequent processing.
|
||||||
|
func (s Scope) GetMembers(ctx domain.RequestContext) (r []group.Record, err error) {
|
||||||
|
err = s.Runtime.Db.Select(&r,
|
||||||
|
`SELECT a.id, a.orgid, a.roleid, a.userid, b.role as name, b.purpose
|
||||||
|
FROM rolemember a, role b
|
||||||
|
WHERE a.orgid=? AND a.roleid=b.refid
|
||||||
|
ORDER BY a.userid`,
|
||||||
|
ctx.OrgID)
|
||||||
|
|
||||||
|
if err == sql.ErrNoRows || len(r) == 0 {
|
||||||
|
err = nil
|
||||||
|
r = []group.Record{}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
err = errors.Wrap(err, "select group members")
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
|
@ -110,7 +110,7 @@ type UserStorer interface {
|
||||||
GetByToken(ctx RequestContext, token string) (u user.User, err error)
|
GetByToken(ctx RequestContext, token string) (u user.User, err error)
|
||||||
GetBySerial(ctx RequestContext, serial string) (u user.User, err error)
|
GetBySerial(ctx RequestContext, serial string) (u user.User, err error)
|
||||||
GetActiveUsersForOrganization(ctx RequestContext) (u []user.User, err error)
|
GetActiveUsersForOrganization(ctx RequestContext) (u []user.User, err error)
|
||||||
GetUsersForOrganization(ctx RequestContext) (u []user.User, err error)
|
GetUsersForOrganization(ctx RequestContext, filter string) (u []user.User, err error)
|
||||||
GetSpaceUsers(ctx RequestContext, spaceID string) (u []user.User, err error)
|
GetSpaceUsers(ctx RequestContext, spaceID string) (u []user.User, err error)
|
||||||
GetUsersForSpaces(ctx RequestContext, spaces []string) (u []user.User, err error)
|
GetUsersForSpaces(ctx RequestContext, spaces []string) (u []user.User, err error)
|
||||||
UpdateUser(ctx RequestContext, u user.User) (err error)
|
UpdateUser(ctx RequestContext, u user.User) (err error)
|
||||||
|
@ -277,6 +277,7 @@ type GroupStorer interface {
|
||||||
Update(ctx RequestContext, g group.Group) (err error)
|
Update(ctx RequestContext, g group.Group) (err error)
|
||||||
Delete(ctx RequestContext, refID string) (rows int64, err error)
|
Delete(ctx RequestContext, refID string) (rows int64, err error)
|
||||||
GetGroupMembers(ctx RequestContext, groupID string) (m []group.Member, err error)
|
GetGroupMembers(ctx RequestContext, groupID string) (m []group.Member, err error)
|
||||||
|
GetMembers(ctx RequestContext) (r []group.Record, err error)
|
||||||
JoinGroup(ctx RequestContext, groupID, userID string) (err error)
|
JoinGroup(ctx RequestContext, groupID, userID string) (err error)
|
||||||
LeaveGroup(ctx RequestContext, groupID, userID string) (err error)
|
LeaveGroup(ctx RequestContext, groupID, userID string) (err error)
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,7 @@ import (
|
||||||
"github.com/documize/community/domain/organization"
|
"github.com/documize/community/domain/organization"
|
||||||
"github.com/documize/community/model/account"
|
"github.com/documize/community/model/account"
|
||||||
"github.com/documize/community/model/audit"
|
"github.com/documize/community/model/audit"
|
||||||
|
"github.com/documize/community/model/group"
|
||||||
"github.com/documize/community/model/user"
|
"github.com/documize/community/model/user"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -228,6 +229,8 @@ func (h *Handler) GetOrganizationUsers(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
filter := request.Query(r, "filter")
|
||||||
|
|
||||||
active, err := strconv.ParseBool(request.Query(r, "active"))
|
active, err := strconv.ParseBool(request.Query(r, "active"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
active = false
|
active = false
|
||||||
|
@ -243,20 +246,33 @@ func (h *Handler) GetOrganizationUsers(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
u, err = h.Store.User.GetUsersForOrganization(ctx)
|
u, err = h.Store.User.GetUsersForOrganization(ctx, filter)
|
||||||
if err != nil && err != sql.ErrNoRows {
|
if err != nil && err != sql.ErrNoRows {
|
||||||
response.WriteServerError(w, method, err)
|
response.WriteServerError(w, method, err)
|
||||||
h.Runtime.Log.Error(method, err)
|
h.Runtime.Log.Error(method, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// prefetch all group membership records
|
||||||
if len(u) == 0 {
|
groups, err := h.Store.Group.GetMembers(ctx)
|
||||||
u = []user.User{}
|
if err != nil && err != sql.ErrNoRows {
|
||||||
|
response.WriteServerError(w, method, err)
|
||||||
|
h.Runtime.Log.Error(method, err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// for each user...
|
||||||
for i := range u {
|
for i := range u {
|
||||||
|
// 1. attach user accounts
|
||||||
AttachUserAccounts(ctx, *h.Store, ctx.OrgID, &u[i])
|
AttachUserAccounts(ctx, *h.Store, ctx.OrgID, &u[i])
|
||||||
|
|
||||||
|
// 2. attach user groups
|
||||||
|
u[i].Groups = []group.Record{}
|
||||||
|
for j := range groups {
|
||||||
|
if groups[j].UserID == u[i].RefID {
|
||||||
|
u[i].Groups = append(u[i].Groups, groups[j])
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
response.WriteJSON(w, u)
|
response.WriteJSON(w, u)
|
||||||
|
|
|
@ -118,6 +118,11 @@ func (s Scope) GetActiveUsersForOrganization(ctx domain.RequestContext) (u []use
|
||||||
ORDER BY u.firstname,u.lastname`,
|
ORDER BY u.firstname,u.lastname`,
|
||||||
ctx.OrgID)
|
ctx.OrgID)
|
||||||
|
|
||||||
|
if err == sql.ErrNoRows || len(u) == 0 {
|
||||||
|
err = nil
|
||||||
|
u = []user.User{}
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = errors.Wrap(err, fmt.Sprintf("get active users by org %s", ctx.OrgID))
|
err = errors.Wrap(err, fmt.Sprintf("get active users by org %s", ctx.OrgID))
|
||||||
}
|
}
|
||||||
|
@ -127,13 +132,24 @@ func (s Scope) GetActiveUsersForOrganization(ctx domain.RequestContext) (u []use
|
||||||
|
|
||||||
// GetUsersForOrganization returns a slice containing all of the user records for the organizaiton
|
// GetUsersForOrganization returns a slice containing all of the user records for the organizaiton
|
||||||
// identified in the Persister.
|
// identified in the Persister.
|
||||||
func (s Scope) GetUsersForOrganization(ctx domain.RequestContext) (u []user.User, err error) {
|
func (s Scope) GetUsersForOrganization(ctx domain.RequestContext, filter string) (u []user.User, err error) {
|
||||||
|
filter = strings.TrimSpace(strings.ToLower(filter))
|
||||||
|
likeQuery := ""
|
||||||
|
if len(filter) > 0 {
|
||||||
|
likeQuery = " AND (LOWER(u.firstname) LIKE '%" + filter + "%' OR LOWER(u.lastname) LIKE '%" + filter + "%' OR LOWER(u.email) LIKE '%" + filter + "%') "
|
||||||
|
}
|
||||||
|
|
||||||
err = s.Runtime.Db.Select(&u,
|
err = s.Runtime.Db.Select(&u,
|
||||||
`SELECT u.id, u.refid, u.firstname, u.lastname, u.email, u.initials, u.password, u.salt, u.reset, u.created, u.revised,
|
`SELECT u.id, u.refid, u.firstname, u.lastname, u.email, u.initials, u.password, u.salt, u.reset, u.created, u.revised,
|
||||||
u.global, a.active, a.editor, a.admin, a.users as viewusers
|
u.global, a.active, a.editor, a.admin, a.users as viewusers
|
||||||
FROM user u, account a
|
FROM user u, account a
|
||||||
WHERE u.refid=a.userid AND a.orgid=?
|
WHERE u.refid=a.userid AND a.orgid=? `+likeQuery+
|
||||||
ORDER BY u.firstname, u.lastname`, ctx.OrgID)
|
`ORDER BY u.firstname, u.lastname LIMIT 100`, ctx.OrgID)
|
||||||
|
|
||||||
|
if err == sql.ErrNoRows || len(u) == 0 {
|
||||||
|
err = nil
|
||||||
|
u = []user.User{}
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = errors.Wrap(err, fmt.Sprintf(" get users for org %s", ctx.OrgID))
|
err = errors.Wrap(err, fmt.Sprintf(" get users for org %s", ctx.OrgID))
|
||||||
|
|
|
@ -10,12 +10,14 @@
|
||||||
// https://documize.com
|
// https://documize.com
|
||||||
|
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import Component from '@ember/component';
|
import { inject as service } from '@ember/service';
|
||||||
import { schedule, debounce } from '@ember/runloop';
|
import { schedule, debounce } from '@ember/runloop';
|
||||||
|
import Component from '@ember/component';
|
||||||
import AuthProvider from '../../mixins/auth';
|
import AuthProvider from '../../mixins/auth';
|
||||||
import ModalMixin from '../../mixins/modal';
|
import ModalMixin from '../../mixins/modal';
|
||||||
|
|
||||||
export default Component.extend(AuthProvider, ModalMixin, {
|
export default Component.extend(AuthProvider, ModalMixin, {
|
||||||
|
groupSvc: service('group'),
|
||||||
editUser: null,
|
editUser: null,
|
||||||
deleteUser: null,
|
deleteUser: null,
|
||||||
filter: '',
|
filter: '',
|
||||||
|
@ -25,13 +27,16 @@ export default Component.extend(AuthProvider, ModalMixin, {
|
||||||
init() {
|
init() {
|
||||||
this._super(...arguments);
|
this._super(...arguments);
|
||||||
this.password = {};
|
this.password = {};
|
||||||
this.filteredUsers = [];
|
|
||||||
this.selectedUsers = [];
|
this.selectedUsers = [];
|
||||||
},
|
},
|
||||||
|
|
||||||
didReceiveAttrs() {
|
didReceiveAttrs() {
|
||||||
this._super(...arguments);
|
this._super(...arguments);
|
||||||
|
|
||||||
|
this.get('groupSvc').getAll().then((groups) => {
|
||||||
|
this.set('groups', groups);
|
||||||
|
});
|
||||||
|
|
||||||
let users = this.get('users');
|
let users = this.get('users');
|
||||||
|
|
||||||
users.forEach(user => {
|
users.forEach(user => {
|
||||||
|
@ -40,7 +45,6 @@ export default Component.extend(AuthProvider, ModalMixin, {
|
||||||
});
|
});
|
||||||
|
|
||||||
this.set('users', users);
|
this.set('users', users);
|
||||||
this.set('filteredUsers', users);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onKeywordChange: function () {
|
onKeywordChange: function () {
|
||||||
|
@ -48,17 +52,7 @@ export default Component.extend(AuthProvider, ModalMixin, {
|
||||||
}.observes('filter'),
|
}.observes('filter'),
|
||||||
|
|
||||||
filterUsers() {
|
filterUsers() {
|
||||||
let users = this.get('users');
|
this.get('onFilter')(this.get('filter'));
|
||||||
let filteredUsers = [];
|
|
||||||
let filter = this.get('filter').toLowerCase();
|
|
||||||
|
|
||||||
users.forEach(user => {
|
|
||||||
if (user.get('fullname').toLowerCase().includes(filter) || user.get('email').toLowerCase().includes(filter)) {
|
|
||||||
filteredUsers.pushObject(user);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.set('filteredUsers', filteredUsers);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
|
@ -184,6 +178,44 @@ export default Component.extend(AuthProvider, ModalMixin, {
|
||||||
this.set('hasSelectedUsers', false);
|
this.set('hasSelectedUsers', false);
|
||||||
|
|
||||||
this.modalClose('#admin-user-delete-modal');
|
this.modalClose('#admin-user-delete-modal');
|
||||||
|
},
|
||||||
|
|
||||||
|
onShowGroupsModal(userId) {
|
||||||
|
let user = this.get('users').findBy('id', userId);
|
||||||
|
this.set('selectedUser', user);
|
||||||
|
|
||||||
|
let userGroups = user.get('groups');
|
||||||
|
|
||||||
|
// mark up groups user belongs to...
|
||||||
|
let groups = this.get('groups');
|
||||||
|
groups.forEach((g) => {
|
||||||
|
console.log(userGroups);
|
||||||
|
let hasGroup = userGroups.findBy('roleId', g.get('id'));
|
||||||
|
g.set('isMember', is.not.undefined(hasGroup));
|
||||||
|
})
|
||||||
|
this.set('groups', groups);
|
||||||
|
|
||||||
|
this.modalOpen("#group-member-modal", {"show": true});
|
||||||
|
},
|
||||||
|
|
||||||
|
onLeaveGroup(groupId) {
|
||||||
|
let userId = this.get('selectedUser.id');
|
||||||
|
let group = this.get('groups').findBy('id', groupId);
|
||||||
|
group.set('isMember', false);
|
||||||
|
|
||||||
|
this.get('groupSvc').leave(groupId, userId).then(() => {
|
||||||
|
this.filterUsers();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onJoinGroup(groupId) {
|
||||||
|
let userId = this.get('selectedUser.id');
|
||||||
|
let group = this.get('groups').findBy('id', groupId);
|
||||||
|
group.set('isMember', true);
|
||||||
|
|
||||||
|
this.get('groupSvc').join(groupId, userId).then(() => {
|
||||||
|
this.filterUsers();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -12,7 +12,6 @@
|
||||||
import { computed } from '@ember/object';
|
import { computed } from '@ember/object';
|
||||||
import Model from 'ember-data/model';
|
import Model from 'ember-data/model';
|
||||||
import attr from 'ember-data/attr';
|
import attr from 'ember-data/attr';
|
||||||
// import { belongsTo, hasMany } from 'ember-data/relationships';
|
|
||||||
|
|
||||||
export default Model.extend({
|
export default Model.extend({
|
||||||
firstname: attr('string'),
|
firstname: attr('string'),
|
||||||
|
@ -25,6 +24,7 @@ export default Model.extend({
|
||||||
viewUsers: attr('boolean', { defaultValue: false }),
|
viewUsers: attr('boolean', { defaultValue: false }),
|
||||||
global: attr('boolean', { defaultValue: false }),
|
global: attr('boolean', { defaultValue: false }),
|
||||||
accounts: attr(),
|
accounts: attr(),
|
||||||
|
groups: attr(),
|
||||||
created: attr(),
|
created: attr(),
|
||||||
revised: attr(),
|
revised: attr(),
|
||||||
|
|
||||||
|
|
|
@ -15,8 +15,8 @@ import Controller from '@ember/controller';
|
||||||
export default Controller.extend({
|
export default Controller.extend({
|
||||||
userService: service('user'),
|
userService: service('user'),
|
||||||
|
|
||||||
loadUsers() {
|
loadUsers(filter) {
|
||||||
this.get('userService').getComplete().then((users) => {
|
this.get('userService').getComplete(filter).then((users) => {
|
||||||
this.set('model', users);
|
this.set('model', users);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -30,26 +30,28 @@ export default Controller.extend({
|
||||||
|
|
||||||
onAddUsers(list) {
|
onAddUsers(list) {
|
||||||
return this.get('userService').addBulk(list).then(() => {
|
return this.get('userService').addBulk(list).then(() => {
|
||||||
this.loadUsers();
|
this.loadUsers('');
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
onDelete(userId) {
|
onDelete(userId) {
|
||||||
this.get('userService').remove(userId).then( () => {
|
this.get('userService').remove(userId).then( () => {
|
||||||
this.loadUsers();
|
this.loadUsers('');
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
onSave(user) {
|
onSave(user) {
|
||||||
this.get('userService').save(user).then(() => {
|
this.get('userService').save(user).then(() => {
|
||||||
this.get('userService').getComplete().then((users) => {
|
this.loadUsers('');
|
||||||
this.set('model', users);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
onPassword(user, password) {
|
onPassword(user, password) {
|
||||||
this.get('userService').updatePassword(user.id, password);
|
this.get('userService').updatePassword(user.id, password);
|
||||||
|
},
|
||||||
|
|
||||||
|
onFilter(filter) {
|
||||||
|
this.loadUsers(filter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -30,12 +30,12 @@ export default Route.extend(AuthenticatedRouteMixin, {
|
||||||
return new EmberPromise((resolve) => {
|
return new EmberPromise((resolve) => {
|
||||||
if (this.get('appMeta.authProvider') == constants.AuthProvider.Keycloak) {
|
if (this.get('appMeta.authProvider') == constants.AuthProvider.Keycloak) {
|
||||||
this.get('global').syncExternalUsers().then(() => {
|
this.get('global').syncExternalUsers().then(() => {
|
||||||
this.get('userService').getComplete().then((users) =>{
|
this.get('userService').getComplete('').then((users) =>{
|
||||||
resolve(users);
|
resolve(users);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.get('userService').getComplete().then((users) => {
|
this.get('userService').getComplete('').then((users) => {
|
||||||
resolve(users);
|
resolve(users);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
{{customize/user-admin
|
{{customize/user-admin users=model
|
||||||
onAddUser=(action 'onAddUser')
|
onAddUser=(action 'onAddUser')
|
||||||
onAddUsers=(action 'onAddUsers')}}
|
onAddUsers=(action 'onAddUsers')}}
|
||||||
|
|
||||||
{{customize/user-list users=model
|
{{customize/user-list users=model
|
||||||
|
onFilter=(action "onFilter")
|
||||||
onDelete=(action "onDelete")
|
onDelete=(action "onDelete")
|
||||||
onSave=(action "onSave")
|
onSave=(action "onSave")
|
||||||
onPassword=(action "onPassword")}}
|
onPassword=(action "onPassword")}}
|
||||||
|
|
|
@ -64,8 +64,13 @@ export default Service.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
// Returns all active and inactive users for organization.
|
// Returns all active and inactive users for organization.
|
||||||
getComplete() {
|
// Only available for admins and limits results to max. 100 users.
|
||||||
return this.get('ajax').request(`users?active=0`).then((response) => {
|
// Takes filter for user search criteria.
|
||||||
|
getComplete(filter) {
|
||||||
|
filter = filter.trim();
|
||||||
|
if (filter.length > 0) filter = encodeURIComponent(filter);
|
||||||
|
|
||||||
|
return this.get('ajax').request(`users?active=0&filter=${filter}`).then((response) => {
|
||||||
return response.map((obj) => {
|
return response.map((obj) => {
|
||||||
let data = this.get('store').normalize('user', obj);
|
let data = this.get('store').normalize('user', obj);
|
||||||
return this.get('store').push(data);
|
return this.get('store').push(data);
|
||||||
|
|
|
@ -11,16 +11,37 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-table {
|
.user-table {
|
||||||
.name {
|
tbody tr td, thead tr th {
|
||||||
font-size: 1rem;
|
border-top: none !important;
|
||||||
color: $color-off-black;
|
border-bottom: none !important;
|
||||||
margin: 0 0 0 30px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.email {
|
.name {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
font-weight: bold;
|
||||||
|
color: $color-link;
|
||||||
|
margin: 0 0 0 10px;
|
||||||
|
display: inline-block;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
> .email {
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
|
color: $color-off-black;
|
||||||
|
margin: 0;
|
||||||
|
display: inline-block;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.groups {
|
||||||
|
cursor: pointer;
|
||||||
|
margin: 5px 0 0 10px;
|
||||||
|
font-size: 1rem;
|
||||||
color: $color-gray;
|
color: $color-gray;
|
||||||
margin: 0 0 0 30px;
|
|
||||||
|
&:hover {
|
||||||
|
color: $color-link;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.inactive-user
|
.inactive-user
|
||||||
|
@ -76,6 +97,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// used for group admin
|
||||||
> .group-users-members {
|
> .group-users-members {
|
||||||
> .item {
|
> .item {
|
||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
|
@ -86,4 +108,20 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// used for user admin
|
||||||
|
> .group-membership {
|
||||||
|
> .item {
|
||||||
|
margin: 10px 0;
|
||||||
|
|
||||||
|
> .group-name {
|
||||||
|
color: $color-primary;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
|
||||||
|
> .group-purpose {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="view-customize">
|
<div class="view-customize">
|
||||||
<h1 class="admin-heading">Users</h1>
|
<h1 class="admin-heading">Users</h1>
|
||||||
<h2 class="sub-heading">Set basic information, passwords and permissions for {{model.length}} users</h2>
|
<h2 class="sub-heading">Set basic information, passwords and permissions for {{users.length}} users</h2>
|
||||||
|
|
||||||
{{#if isAuthProviderDocumize}}
|
{{#if isAuthProviderDocumize}}
|
||||||
<div class="btn btn-success mt-3 mb-3" {{action 'onOpenUserModal'}}>Add user</div>
|
<div class="btn btn-success mt-3 mb-3" {{action 'onOpenUserModal'}}>Add user</div>
|
||||||
|
|
|
@ -115,7 +115,7 @@
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="group-members-search">Search for group members, non-members</label>
|
<label for="group-members-search">Search for group members, non-members</label>
|
||||||
{{input id="group-members-search" type="text" class="form-control mousetrap" placeholder="Search members and users..." value=searchText key-up=(action 'onSearch')}}
|
{{input id="group-members-search" type="text" class="form-control mousetrap" placeholder="Search members and users..." value=searchText key-up=(action 'onSearch')}}
|
||||||
<small class="form-text text-muted">matches firstname, lastname, email</small>
|
<small class="form-text text-muted">search firstname, lastname, email</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="view-customize">
|
<div class="view-customize">
|
||||||
<div class="group-users-members my-5">
|
<div class="group-users-members my-5">
|
||||||
|
@ -124,7 +124,7 @@
|
||||||
<div class="row item">
|
<div class="row item">
|
||||||
<div class="col-10 fullname">{{member.fullname}}</div>
|
<div class="col-10 fullname">{{member.fullname}}</div>
|
||||||
<div class="col-2 text-right">
|
<div class="col-2 text-right">
|
||||||
<button class="btn btn-danger" {{action 'onLeaveGroup' member.userId}}>Leave</button>
|
<button class="btn btn-danger" {{action 'onLeaveGroup' member.userId}}>Remove</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
@ -135,9 +135,9 @@
|
||||||
<div class="col-10 fullname">{{user.firstname}} {{user.lastname}}</div>
|
<div class="col-10 fullname">{{user.firstname}} {{user.lastname}}</div>
|
||||||
<div class="col-2 text-right">
|
<div class="col-2 text-right">
|
||||||
{{#if user.isMember}}
|
{{#if user.isMember}}
|
||||||
<button class="btn btn-danger" {{action 'onLeaveGroup' user.id}}>Leave</button>
|
<button class="btn btn-danger" {{action 'onLeaveGroup' user.id}}>Remove</button>
|
||||||
{{else}}
|
{{else}}
|
||||||
<button class="btn btn-success" {{action 'onJoinGroup' user.id}}>Join</button>
|
<button class="btn btn-success" {{action 'onJoinGroup' user.id}}>Add</button>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,37 +1,45 @@
|
||||||
<div class="view-customize mb-5">
|
<div class="view-customize my-5">
|
||||||
<h3>Users</h3>
|
|
||||||
<table class="table table-hover table-responsive user-table">
|
<div class="my-2">
|
||||||
|
<span class="font-weight-bold">Spaces</span>
|
||||||
|
<span class="text-muted"> — can add spaces, both personal and shared with others</span>
|
||||||
|
</div>
|
||||||
|
<div class="my-2">
|
||||||
|
<span class="font-weight-bold">Visible</span>
|
||||||
|
<span class="text-muted"> — can see names of users and groups, can disable for external users like customers/partners</span>
|
||||||
|
</div>
|
||||||
|
<div class="my-2">
|
||||||
|
<span class="font-weight-bold">Admin</span>
|
||||||
|
<span class="text-muted"> — can manage all aspects of Documize, like this screen</span>
|
||||||
|
</div>
|
||||||
|
<div class="mt-2 mb-4">
|
||||||
|
<span class="font-weight-bold">Active</span>
|
||||||
|
<span class="text-muted"> — can login and use Documize</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group mt-5 mb-3">
|
||||||
|
{{focus-input type="text" class="form-control" placeholder="filter users" value=filter}}
|
||||||
|
<small class="form-text text-muted">search firstname, lastname, email</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class="table table-responsive table-borderless user-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{input type="text" class="form-control" placeholder="filter users" value=filter}}</th>
|
<th class="text-muted">
|
||||||
<th class="no-width">Add Space</th>
|
{{#if hasSelectedUsers}}
|
||||||
<th class="no-width">View Users</th>
|
<button id="bulk-delete-users" type="button" class="btn btn-danger" data-toggle="modal" data-target="#admin-user-delete-modal" data-backdrop="static">Delete selected users</button>
|
||||||
|
{{/if}}
|
||||||
|
</th>
|
||||||
|
<th class="no-width">Spaces</th>
|
||||||
|
<th class="no-width">Visible</th>
|
||||||
<th class="no-width">Admin</th>
|
<th class="no-width">Admin</th>
|
||||||
<th class="no-width">Active</th>
|
<th class="no-width">Active</th>
|
||||||
<th class="no-width">
|
<th class="no-width">
|
||||||
{{#if hasSelectedUsers}}
|
|
||||||
<button id="bulk-delete-users" type="button" class="btn btn-danger btn-sm" data-toggle="modal" data-target="#admin-user-delete-modal" data-backdrop="static">Delete</button>
|
|
||||||
|
|
||||||
<div id="admin-user-delete-modal" class="modal" tabindex="-1" role="dialog">
|
|
||||||
<div class="modal-dialog" role="document">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">Delete Users</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<p>Are you sure you want to delete selected users?</p>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">Cancel</button>
|
|
||||||
<button type="button" class="btn btn-success" onclick={{action 'onBulkDelete'}}>Delete</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{{#each filteredUsers key="id" as |user|}}
|
{{#each users key="id" as |user|}}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="{{unless user.active 'inactive-user'}} {{if user.admin 'admin-user'}}">
|
<td class="{{unless user.active 'inactive-user'}} {{if user.admin 'admin-user'}}">
|
||||||
<div class="d-inline-block align-top">
|
<div class="d-inline-block align-top">
|
||||||
|
@ -44,8 +52,16 @@
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
<div class="d-inline-block">
|
<div class="d-inline-block">
|
||||||
<div class="name d-inline-block">{{ user.fullname }}</div>
|
<div class="name" {{action "onShowEdit" user.id}}>{{user.fullname}}<div class="email"> ({{user.email}})</div></div>
|
||||||
<div class="email">{{ user.email }}</div>
|
<div class="groups" {{action "onShowGroupsModal" user.id}}>
|
||||||
|
{{#each user.groups as |group|}}
|
||||||
|
<span class="group">
|
||||||
|
{{group.name}}{{#if (not-eq group user.groups.lastObject)}}, {{/if}}
|
||||||
|
</span>
|
||||||
|
{{else}}
|
||||||
|
<span class="group"><no groups></span>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="no-width text-center">
|
<td class="no-width text-center">
|
||||||
|
@ -81,19 +97,11 @@
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</td>
|
</td>
|
||||||
<td class="no-width text-center">
|
<td class="no-width text-center">
|
||||||
{{#if user.me}}
|
{{#unless user.me}}
|
||||||
<div class="edit-button-{{user.id}} button-icon-gray" title="Edit" {{action "onShowEdit" user.id}}>
|
<div class="delete-button-{{user.id}} button-icon-red button-icon-small" title="Delete" {{action "onShowDelete" user.id}}>
|
||||||
<i class="material-icons">edit</i>
|
|
||||||
</div>
|
|
||||||
{{else}}
|
|
||||||
<div class="edit-button-{{user.id}} button-icon-gray" title="Edit" {{action "onShowEdit" user.id}}>
|
|
||||||
<i class="material-icons">edit</i>
|
|
||||||
</div>
|
|
||||||
<div class="button-icon-gap"></div>
|
|
||||||
<div class="delete-button-{{user.id}} button-icon-danger" title="Delete" {{action "onShowDelete" user.id}}>
|
|
||||||
<i class="material-icons">delete</i>
|
<i class="material-icons">delete</i>
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/unless}}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
@ -151,3 +159,50 @@
|
||||||
<p>Are you sure you want to delete {{deleteUser.fullname}}?</p>
|
<p>Are you sure you want to delete {{deleteUser.fullname}}?</p>
|
||||||
{{/ui/ui-dialog}}
|
{{/ui/ui-dialog}}
|
||||||
|
|
||||||
|
<div id="admin-user-delete-modal" class="modal" tabindex="-1" role="dialog">
|
||||||
|
<div class="modal-dialog" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">Delete Users</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>Are you sure you want to delete selected users?</p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">Cancel</button>
|
||||||
|
<button type="button" class="btn btn-success" onclick={{action 'onBulkDelete'}}>Delete</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="group-member-modal" class="modal" tabindex="-1" role="dialog">
|
||||||
|
<div class="modal-dialog modal-lg" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">{{selectedUser.fullname}}</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="view-customize">
|
||||||
|
<div class="group-membership my-5">
|
||||||
|
{{#each groups as |group|}}
|
||||||
|
<div class="row item">
|
||||||
|
<div class="col-10 group-name">{{group.name}}
|
||||||
|
{{#if group.purpose}}
|
||||||
|
<span class="text-muted group-purpose"> — {{group.purpose}}</span>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
<div class="col-2 text-right">
|
||||||
|
{{#if group.isMember}}
|
||||||
|
<button class="btn btn-danger" {{action 'onLeaveGroup' group.id}}>Leave</button>
|
||||||
|
{{else}}
|
||||||
|
<button class="btn btn-success" {{action 'onJoinGroup' group.id}}>Join</button>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -31,3 +31,13 @@ type Member struct {
|
||||||
Firstname string `json:"firstname"` //read-only info
|
Firstname string `json:"firstname"` //read-only info
|
||||||
Lastname string `json:"lastname"` //read-only info
|
Lastname string `json:"lastname"` //read-only info
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Record details user membership of a user group.
|
||||||
|
type Record struct {
|
||||||
|
ID uint64 `json:"id"`
|
||||||
|
OrgID string `json:"orgId"`
|
||||||
|
RoleID string `json:"roleId"`
|
||||||
|
UserID string `json:"userId"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Purpose string `json:"purpose"`
|
||||||
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ import (
|
||||||
|
|
||||||
"github.com/documize/community/model"
|
"github.com/documize/community/model"
|
||||||
"github.com/documize/community/model/account"
|
"github.com/documize/community/model/account"
|
||||||
|
"github.com/documize/community/model/group"
|
||||||
)
|
)
|
||||||
|
|
||||||
// User defines a login.
|
// User defines a login.
|
||||||
|
@ -34,6 +35,7 @@ type User struct {
|
||||||
Salt string `json:"-"`
|
Salt string `json:"-"`
|
||||||
Reset string `json:"-"`
|
Reset string `json:"-"`
|
||||||
Accounts []account.Account `json:"accounts"`
|
Accounts []account.Account `json:"accounts"`
|
||||||
|
Groups []group.Record `json:"groups"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProtectSecrets blanks sensitive data.
|
// ProtectSecrets blanks sensitive data.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue