mirror of
https://github.com/documize/community.git
synced 2025-07-20 13:49:42 +02:00
add bulk users
This commit is contained in:
parent
7e6d6366da
commit
0b5ed8fd9e
11 changed files with 627 additions and 410 deletions
|
@ -13,6 +13,7 @@ package user
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"encoding/csv"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
@ -207,7 +208,6 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
|
||||||
go mailer.InviteNewUser(userModel.Email, inviter.Fullname(), url, userModel.Email, requestedPassword)
|
go mailer.InviteNewUser(userModel.Email, inviter.Fullname(), url, userModel.Email, requestedPassword)
|
||||||
|
|
||||||
h.Runtime.Log.Info(fmt.Sprintf("%s invited by %s on %s", userModel.Email, inviter.Email, ctx.AppURL))
|
h.Runtime.Log.Info(fmt.Sprintf("%s invited by %s on %s", userModel.Email, inviter.Email, ctx.AppURL))
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
mailer := mail.Mailer{Runtime: h.Runtime, Store: h.Store, Context: ctx}
|
mailer := mail.Mailer{Runtime: h.Runtime, Store: h.Store, Context: ctx}
|
||||||
go mailer.InviteExistingUser(userModel.Email, inviter.Fullname(), ctx.GetAppURL(""))
|
go mailer.InviteExistingUser(userModel.Email, inviter.Fullname(), ctx.GetAppURL(""))
|
||||||
|
@ -594,7 +594,7 @@ func (h *Handler) ForgotPassword(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
// ResetPassword stores the newly chosen password for the user.
|
// ResetPassword stores the newly chosen password for the user.
|
||||||
func (h *Handler) ResetPassword(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) ResetPassword(w http.ResponseWriter, r *http.Request) {
|
||||||
method := "user.ForgotUserPassword"
|
method := "user.ResetPassword"
|
||||||
ctx := domain.GetRequestContext(r)
|
ctx := domain.GetRequestContext(r)
|
||||||
ctx.Subdomain = organization.GetSubdomainFromHost(r)
|
ctx.Subdomain = organization.GetSubdomainFromHost(r)
|
||||||
|
|
||||||
|
@ -669,3 +669,166 @@ func (h *Handler) MatchUsers(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
response.WriteJSON(w, u)
|
response.WriteJSON(w, u)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BulkImport imports comma-delimited list of users:
|
||||||
|
// firstname, lastname, email
|
||||||
|
func (h *Handler) BulkImport(w http.ResponseWriter, r *http.Request) {
|
||||||
|
method := "user.BulkImport"
|
||||||
|
ctx := domain.GetRequestContext(r)
|
||||||
|
|
||||||
|
if !ctx.Administrator {
|
||||||
|
response.WriteForbiddenError(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer streamutil.Close(r.Body)
|
||||||
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
response.WriteBadRequestError(w, method, "text")
|
||||||
|
h.Runtime.Log.Error(method, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
usersList := string(body)
|
||||||
|
|
||||||
|
cr := csv.NewReader(strings.NewReader(usersList))
|
||||||
|
cr.TrimLeadingSpace = true
|
||||||
|
cr.FieldsPerRecord = 3
|
||||||
|
|
||||||
|
records, err := cr.ReadAll()
|
||||||
|
if err != nil {
|
||||||
|
response.WriteServerError(w, method, err)
|
||||||
|
h.Runtime.Log.Error(method, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Transaction, err = h.Runtime.Db.Beginx()
|
||||||
|
if err != nil {
|
||||||
|
response.WriteServerError(w, method, err)
|
||||||
|
h.Runtime.Log.Error(method, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
inviter, err := h.Store.User.Get(ctx, ctx.UserID)
|
||||||
|
if err != nil {
|
||||||
|
response.WriteServerError(w, method, err)
|
||||||
|
h.Runtime.Log.Error(method, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range records {
|
||||||
|
userModel := user.User{}
|
||||||
|
userModel.Firstname = strings.TrimSpace(v[0])
|
||||||
|
userModel.Lastname = strings.TrimSpace(v[1])
|
||||||
|
userModel.Email = strings.ToLower(strings.TrimSpace(v[2]))
|
||||||
|
|
||||||
|
if len(userModel.Email) == 0 || len(userModel.Firstname) == 0 || len(userModel.Lastname) == 0 {
|
||||||
|
h.Runtime.Log.Info(method + " missing firstname, lastname, or email")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
userModel.Initials = stringutil.MakeInitials(userModel.Firstname, userModel.Lastname)
|
||||||
|
requestedPassword := secrets.GenerateRandomPassword()
|
||||||
|
userModel.Salt = secrets.GenerateSalt()
|
||||||
|
userModel.Password = secrets.GeneratePassword(requestedPassword, userModel.Salt)
|
||||||
|
|
||||||
|
// only create account if not dupe
|
||||||
|
addUser := true
|
||||||
|
addAccount := true
|
||||||
|
var userID string
|
||||||
|
|
||||||
|
userDupe, err := h.Store.User.GetByEmail(ctx, userModel.Email)
|
||||||
|
if err != nil && err != sql.ErrNoRows {
|
||||||
|
h.Runtime.Log.Error(method, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if userModel.Email == userDupe.Email {
|
||||||
|
addUser = false
|
||||||
|
userID = userDupe.RefID
|
||||||
|
h.Runtime.Log.Info("Dupe user found, will not add " + userModel.Email)
|
||||||
|
}
|
||||||
|
|
||||||
|
if addUser {
|
||||||
|
userID = uniqueid.Generate()
|
||||||
|
userModel.RefID = userID
|
||||||
|
|
||||||
|
err = h.Store.User.Add(ctx, userModel)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Transaction.Rollback()
|
||||||
|
response.WriteServerError(w, method, err)
|
||||||
|
h.Runtime.Log.Error(method, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.Runtime.Log.Info("Adding user")
|
||||||
|
} else {
|
||||||
|
AttachUserAccounts(ctx, *h.Store, ctx.OrgID, &userDupe)
|
||||||
|
|
||||||
|
for _, a := range userDupe.Accounts {
|
||||||
|
if a.OrgID == ctx.OrgID {
|
||||||
|
addAccount = false
|
||||||
|
h.Runtime.Log.Info("Dupe account found, will not add")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// set up user account for the org
|
||||||
|
if addAccount {
|
||||||
|
var a account.Account
|
||||||
|
a.RefID = uniqueid.Generate()
|
||||||
|
a.UserID = userID
|
||||||
|
a.OrgID = ctx.OrgID
|
||||||
|
a.Editor = true
|
||||||
|
a.Admin = false
|
||||||
|
a.Active = true
|
||||||
|
|
||||||
|
err = h.Store.Account.Add(ctx, a)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Transaction.Rollback()
|
||||||
|
response.WriteServerError(w, method, err)
|
||||||
|
h.Runtime.Log.Error(method, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if addUser {
|
||||||
|
event.Handler().Publish(string(event.TypeAddUser))
|
||||||
|
h.Store.Audit.Record(ctx, audit.EventTypeUserAdd)
|
||||||
|
}
|
||||||
|
|
||||||
|
if addAccount {
|
||||||
|
event.Handler().Publish(string(event.TypeAddAccount))
|
||||||
|
h.Store.Audit.Record(ctx, audit.EventTypeAccountAdd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we did not add user or give them access (account) then we error back
|
||||||
|
if !addUser && !addAccount {
|
||||||
|
h.Runtime.Log.Info(method + " duplicate user not added")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invite new user and prepare invitation email (that contains SSO link)
|
||||||
|
if addUser && addAccount {
|
||||||
|
size := len(requestedPassword)
|
||||||
|
|
||||||
|
auth := fmt.Sprintf("%s:%s:%s", ctx.AppURL, userModel.Email, requestedPassword[:size])
|
||||||
|
encrypted := secrets.EncodeBase64([]byte(auth))
|
||||||
|
|
||||||
|
url := fmt.Sprintf("%s/%s", ctx.GetAppURL("auth/sso"), url.QueryEscape(string(encrypted)))
|
||||||
|
mailer := mail.Mailer{Runtime: h.Runtime, Store: h.Store, Context: ctx}
|
||||||
|
go mailer.InviteNewUser(userModel.Email, inviter.Fullname(), url, userModel.Email, requestedPassword)
|
||||||
|
|
||||||
|
h.Runtime.Log.Info(fmt.Sprintf("%s invited by %s on %s", userModel.Email, inviter.Email, ctx.AppURL))
|
||||||
|
} else {
|
||||||
|
mailer := mail.Mailer{Runtime: h.Runtime, Store: h.Store, Context: ctx}
|
||||||
|
go mailer.InviteExistingUser(userModel.Email, inviter.Fullname(), ctx.GetAppURL(""))
|
||||||
|
|
||||||
|
h.Runtime.Log.Info(fmt.Sprintf("%s is giving access to an existing user %s", inviter.Email, userModel.Email))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Transaction.Commit()
|
||||||
|
|
||||||
|
response.WriteEmpty(w)
|
||||||
|
}
|
||||||
|
|
|
@ -10,180 +10,64 @@
|
||||||
// https://documize.com
|
// https://documize.com
|
||||||
|
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import Component from '@ember/component';
|
|
||||||
import { schedule, debounce } from '@ember/runloop';
|
|
||||||
import AuthProvider from '../../mixins/auth';
|
import AuthProvider from '../../mixins/auth';
|
||||||
import ModalMixin from '../../mixins/modal';
|
import ModalMixin from '../../mixins/modal';
|
||||||
|
import Component from '@ember/component';
|
||||||
|
|
||||||
export default Component.extend(AuthProvider, ModalMixin, {
|
export default Component.extend(AuthProvider, ModalMixin, {
|
||||||
editUser: null,
|
bulkUsers: '',
|
||||||
deleteUser: null,
|
newUser: null,
|
||||||
filter: '',
|
|
||||||
hasSelectedUsers: false,
|
|
||||||
showDeleteDialog: false,
|
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
this._super(...arguments);
|
this._super(...arguments);
|
||||||
this.password = {};
|
this.set('newUser', { firstname: '', lastname: '', email: '', active: true });
|
||||||
this.filteredUsers = [];
|
|
||||||
this.selectedUsers = [];
|
|
||||||
},
|
|
||||||
|
|
||||||
didReceiveAttrs() {
|
|
||||||
this._super(...arguments);
|
|
||||||
|
|
||||||
let users = this.get('users');
|
|
||||||
|
|
||||||
users.forEach(user => {
|
|
||||||
user.set('me', user.get('id') === this.get('session.session.authenticated.user.id'));
|
|
||||||
user.set('selected', false);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.set('users', users);
|
|
||||||
this.set('filteredUsers', users);
|
|
||||||
},
|
|
||||||
|
|
||||||
onKeywordChange: function () {
|
|
||||||
debounce(this, this.filterUsers, 350);
|
|
||||||
}.observes('filter'),
|
|
||||||
|
|
||||||
filterUsers() {
|
|
||||||
let users = this.get('users');
|
|
||||||
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: {
|
||||||
toggleSelect(user) {
|
onOpenUserModal() {
|
||||||
user.set('selected', !user.get('selected'));
|
this.modalOpen("#add-user-modal", {"show": true}, '#newUserFirstname');
|
||||||
|
|
||||||
let su = this.get('selectedUsers');
|
|
||||||
if (user.get('selected')) {
|
|
||||||
su.push(user.get('id'));
|
|
||||||
} else {
|
|
||||||
su = _.reject(su, function(id){ return id === user.get('id') });
|
|
||||||
}
|
|
||||||
|
|
||||||
this.set('selectedUsers', su);
|
|
||||||
this.set('hasSelectedUsers', su.length > 0);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleActive(id) {
|
onAddUser() {
|
||||||
let user = this.users.findBy("id", id);
|
if (is.empty(this.get('newUser.firstname'))) {
|
||||||
user.set('active', !user.get('active'));
|
$("#newUserFirstname").addClass('is-invalid').focus();
|
||||||
let cb = this.get('onSave');
|
|
||||||
cb(user);
|
|
||||||
},
|
|
||||||
|
|
||||||
toggleEditor(id) {
|
|
||||||
let user = this.users.findBy("id", id);
|
|
||||||
user.set('editor', !user.get('editor'));
|
|
||||||
let cb = this.get('onSave');
|
|
||||||
cb(user);
|
|
||||||
},
|
|
||||||
|
|
||||||
toggleAdmin(id) {
|
|
||||||
let user = this.users.findBy("id", id);
|
|
||||||
user.set('admin', !user.get('admin'));
|
|
||||||
let cb = this.get('onSave');
|
|
||||||
cb(user);
|
|
||||||
},
|
|
||||||
|
|
||||||
toggleUsers(id) {
|
|
||||||
let user = this.users.findBy("id", id);
|
|
||||||
user.set('viewUsers', !user.get('viewUsers'));
|
|
||||||
let cb = this.get('onSave');
|
|
||||||
cb(user);
|
|
||||||
},
|
|
||||||
|
|
||||||
onShowEdit(id) {
|
|
||||||
let user = this.users.findBy("id", id);
|
|
||||||
let userCopy = user.getProperties('id', 'created', 'revised', 'firstname', 'lastname', 'email', 'initials', 'active', 'editor', 'admin', 'viewUsers', 'accounts');
|
|
||||||
|
|
||||||
this.set('editUser', userCopy);
|
|
||||||
this.set('password', {
|
|
||||||
password: "",
|
|
||||||
confirmation: ""
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#edit-user-modal').on('show.bs.modal', function(event) { // eslint-disable-line no-unused-vars
|
|
||||||
schedule('afterRender', () => {
|
|
||||||
$("#edit-firstname").focus();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#edit-user-modal').modal('dispose');
|
|
||||||
$('#edit-user-modal').modal({show: true});
|
|
||||||
},
|
|
||||||
|
|
||||||
onUpdate() {
|
|
||||||
let user = this.get('editUser');
|
|
||||||
let password = this.get('password');
|
|
||||||
|
|
||||||
if (is.empty(user.firstname)) {
|
|
||||||
$("#edit-firstname").addClass("is-invalid").focus();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (is.empty(user.lastname)) {
|
$("#newUserFirstname").removeClass('is-invalid');
|
||||||
$("#edit-lastname").addClass("is-invalid").focus();
|
|
||||||
|
if (is.empty(this.get('newUser.lastname'))) {
|
||||||
|
$("#newUserLastname").addClass('is-invalid').focus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (is.empty(user.email) || is.not.email(user.email)) {
|
$("#newUserLastname").removeClass('is-invalid');
|
||||||
$("#edit-email").addClass("is-invalid").focus();
|
|
||||||
|
if (is.empty(this.get('newUser.email')) || is.not.email(this.get('newUser.email'))) {
|
||||||
|
$("#newUserEmail").addClass('is-invalid').focus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
$("#newUserEmail").removeClass('is-invalid');
|
||||||
|
|
||||||
$('#edit-user-modal').modal('hide');
|
let user = this.get('newUser');
|
||||||
$('#edit-user-modal').modal('dispose');
|
|
||||||
|
|
||||||
let cb = this.get('onSave');
|
this.get('onAddUser')(user).then(() => {
|
||||||
cb(user);
|
this.set('newUser', { firstname: '', lastname: '', email: '', active: true });
|
||||||
|
|
||||||
if (is.not.empty(password.password) && is.not.empty(password.confirmation) &&
|
|
||||||
password.password === password.confirmation) {
|
|
||||||
|
|
||||||
let pw = this.get('onPassword');
|
|
||||||
pw(user, password.password);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onShowDelete(id) {
|
|
||||||
this.set('deleteUser', this.users.findBy("id", id));
|
|
||||||
this.set('showDeleteDialog', true);
|
|
||||||
},
|
|
||||||
|
|
||||||
onDelete() {
|
|
||||||
this.set('showDeleteDialog', false);
|
|
||||||
|
|
||||||
this.set('selectedUsers', []);
|
|
||||||
this.set('hasSelectedUsers', false);
|
|
||||||
|
|
||||||
let cb = this.get('onDelete');
|
|
||||||
cb(this.get('deleteUser.id'));
|
|
||||||
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
|
|
||||||
onBulkDelete() {
|
|
||||||
let su = this.get('selectedUsers');
|
|
||||||
|
|
||||||
su.forEach(userId => {
|
|
||||||
let cb = this.get('onDelete');
|
|
||||||
cb(userId);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.set('selectedUsers', []);
|
this.modalClose("#add-user-modal");
|
||||||
this.set('hasSelectedUsers', false);
|
},
|
||||||
|
|
||||||
this.modalClose('#admin-user-delete-modal');
|
onAddUsers() {
|
||||||
|
if (is.empty(this.get('bulkUsers'))) {
|
||||||
|
$("#bulkUsers").addClass('is-invalid').focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$("#bulkUsers").removeClass('is-invalid');
|
||||||
|
|
||||||
|
this.get('onAddUsers')(this.get('bulkUsers')).then(() => {
|
||||||
|
this.set('bulkUsers', '');
|
||||||
|
});
|
||||||
|
|
||||||
|
this.modalClose("#add-user-modal");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
189
gui/app/components/customize/user-list.js
Normal file
189
gui/app/components/customize/user-list.js
Normal file
|
@ -0,0 +1,189 @@
|
||||||
|
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
|
||||||
|
//
|
||||||
|
// This software (Documize Community Edition) is licensed under
|
||||||
|
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
|
||||||
|
//
|
||||||
|
// You can operate outside the AGPL restrictions by purchasing
|
||||||
|
// Documize Enterprise Edition and obtaining a commercial license
|
||||||
|
// by contacting <sales@documize.com>.
|
||||||
|
//
|
||||||
|
// https://documize.com
|
||||||
|
|
||||||
|
import $ from 'jquery';
|
||||||
|
import Component from '@ember/component';
|
||||||
|
import { schedule, debounce } from '@ember/runloop';
|
||||||
|
import AuthProvider from '../../mixins/auth';
|
||||||
|
import ModalMixin from '../../mixins/modal';
|
||||||
|
|
||||||
|
export default Component.extend(AuthProvider, ModalMixin, {
|
||||||
|
editUser: null,
|
||||||
|
deleteUser: null,
|
||||||
|
filter: '',
|
||||||
|
hasSelectedUsers: false,
|
||||||
|
showDeleteDialog: false,
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this._super(...arguments);
|
||||||
|
this.password = {};
|
||||||
|
this.filteredUsers = [];
|
||||||
|
this.selectedUsers = [];
|
||||||
|
},
|
||||||
|
|
||||||
|
didReceiveAttrs() {
|
||||||
|
this._super(...arguments);
|
||||||
|
|
||||||
|
let users = this.get('users');
|
||||||
|
|
||||||
|
users.forEach(user => {
|
||||||
|
user.set('me', user.get('id') === this.get('session.session.authenticated.user.id'));
|
||||||
|
user.set('selected', false);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.set('users', users);
|
||||||
|
this.set('filteredUsers', users);
|
||||||
|
},
|
||||||
|
|
||||||
|
onKeywordChange: function () {
|
||||||
|
debounce(this, this.filterUsers, 350);
|
||||||
|
}.observes('filter'),
|
||||||
|
|
||||||
|
filterUsers() {
|
||||||
|
let users = this.get('users');
|
||||||
|
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: {
|
||||||
|
toggleSelect(user) {
|
||||||
|
user.set('selected', !user.get('selected'));
|
||||||
|
|
||||||
|
let su = this.get('selectedUsers');
|
||||||
|
if (user.get('selected')) {
|
||||||
|
su.push(user.get('id'));
|
||||||
|
} else {
|
||||||
|
su = _.reject(su, function(id){ return id === user.get('id') });
|
||||||
|
}
|
||||||
|
|
||||||
|
this.set('selectedUsers', su);
|
||||||
|
this.set('hasSelectedUsers', su.length > 0);
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleActive(id) {
|
||||||
|
let user = this.users.findBy("id", id);
|
||||||
|
user.set('active', !user.get('active'));
|
||||||
|
let cb = this.get('onSave');
|
||||||
|
cb(user);
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleEditor(id) {
|
||||||
|
let user = this.users.findBy("id", id);
|
||||||
|
user.set('editor', !user.get('editor'));
|
||||||
|
let cb = this.get('onSave');
|
||||||
|
cb(user);
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleAdmin(id) {
|
||||||
|
let user = this.users.findBy("id", id);
|
||||||
|
user.set('admin', !user.get('admin'));
|
||||||
|
let cb = this.get('onSave');
|
||||||
|
cb(user);
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleUsers(id) {
|
||||||
|
let user = this.users.findBy("id", id);
|
||||||
|
user.set('viewUsers', !user.get('viewUsers'));
|
||||||
|
let cb = this.get('onSave');
|
||||||
|
cb(user);
|
||||||
|
},
|
||||||
|
|
||||||
|
onShowEdit(id) {
|
||||||
|
let user = this.users.findBy("id", id);
|
||||||
|
let userCopy = user.getProperties('id', 'created', 'revised', 'firstname', 'lastname', 'email', 'initials', 'active', 'editor', 'admin', 'viewUsers', 'accounts');
|
||||||
|
|
||||||
|
this.set('editUser', userCopy);
|
||||||
|
this.set('password', {
|
||||||
|
password: "",
|
||||||
|
confirmation: ""
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#edit-user-modal').on('show.bs.modal', function(event) { // eslint-disable-line no-unused-vars
|
||||||
|
schedule('afterRender', () => {
|
||||||
|
$("#edit-firstname").focus();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#edit-user-modal').modal('dispose');
|
||||||
|
$('#edit-user-modal').modal({show: true});
|
||||||
|
},
|
||||||
|
|
||||||
|
onUpdate() {
|
||||||
|
let user = this.get('editUser');
|
||||||
|
let password = this.get('password');
|
||||||
|
|
||||||
|
if (is.empty(user.firstname)) {
|
||||||
|
$("#edit-firstname").addClass("is-invalid").focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (is.empty(user.lastname)) {
|
||||||
|
$("#edit-lastname").addClass("is-invalid").focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (is.empty(user.email) || is.not.email(user.email)) {
|
||||||
|
$("#edit-email").addClass("is-invalid").focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#edit-user-modal').modal('hide');
|
||||||
|
$('#edit-user-modal').modal('dispose');
|
||||||
|
|
||||||
|
let cb = this.get('onSave');
|
||||||
|
cb(user);
|
||||||
|
|
||||||
|
if (is.not.empty(password.password) && is.not.empty(password.confirmation) &&
|
||||||
|
password.password === password.confirmation) {
|
||||||
|
|
||||||
|
let pw = this.get('onPassword');
|
||||||
|
pw(user, password.password);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onShowDelete(id) {
|
||||||
|
this.set('deleteUser', this.users.findBy("id", id));
|
||||||
|
this.set('showDeleteDialog', true);
|
||||||
|
},
|
||||||
|
|
||||||
|
onDelete() {
|
||||||
|
this.set('showDeleteDialog', false);
|
||||||
|
|
||||||
|
this.set('selectedUsers', []);
|
||||||
|
this.set('hasSelectedUsers', false);
|
||||||
|
|
||||||
|
let cb = this.get('onDelete');
|
||||||
|
cb(this.get('deleteUser.id'));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
onBulkDelete() {
|
||||||
|
let su = this.get('selectedUsers');
|
||||||
|
|
||||||
|
su.forEach(userId => {
|
||||||
|
let cb = this.get('onDelete');
|
||||||
|
cb(userId);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.set('selectedUsers', []);
|
||||||
|
this.set('hasSelectedUsers', false);
|
||||||
|
|
||||||
|
this.modalClose('#admin-user-delete-modal');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
|
@ -1,58 +0,0 @@
|
||||||
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
|
|
||||||
//
|
|
||||||
// This software (Documize Community Edition) is licensed under
|
|
||||||
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
|
|
||||||
//
|
|
||||||
// You can operate outside the AGPL restrictions by purchasing
|
|
||||||
// Documize Enterprise Edition and obtaining a commercial license
|
|
||||||
// by contacting <sales@documize.com>.
|
|
||||||
//
|
|
||||||
// https://documize.com
|
|
||||||
|
|
||||||
import $ from 'jquery';
|
|
||||||
import { empty, and } from '@ember/object/computed';
|
|
||||||
import Component from '@ember/component';
|
|
||||||
import { isEmpty } from '@ember/utils';
|
|
||||||
import { get, set } from '@ember/object';
|
|
||||||
import AuthProvider from '../../mixins/auth';
|
|
||||||
|
|
||||||
export default Component.extend(AuthProvider, {
|
|
||||||
firstnameEmpty: empty('newUser.firstname'),
|
|
||||||
lastnameEmpty: empty('newUser.lastname'),
|
|
||||||
emailEmpty: empty('newUser.email'),
|
|
||||||
hasFirstnameEmptyError: and('firstnameEmpty', 'firstnameError'),
|
|
||||||
hasLastnameEmptyError: and('lastnameEmpty', 'lastnameError'),
|
|
||||||
hasEmailEmptyError: and('emailEmpty', 'emailError'),
|
|
||||||
|
|
||||||
init() {
|
|
||||||
this._super(...arguments);
|
|
||||||
this.newUser = { firstname: "", lastname: "", email: "", active: true };
|
|
||||||
},
|
|
||||||
|
|
||||||
actions: {
|
|
||||||
add() {
|
|
||||||
if (isEmpty(this.get('newUser.firstname'))) {
|
|
||||||
set(this, 'firstnameError', true);
|
|
||||||
return $("#newUserFirstname").focus();
|
|
||||||
}
|
|
||||||
if (isEmpty(this.get('newUser.lastname'))) {
|
|
||||||
set(this, 'lastnameError', true);
|
|
||||||
return $("#newUserLastname").focus();
|
|
||||||
}
|
|
||||||
if (isEmpty(this.get('newUser.email')) || is.not.email(this.get('newUser.email'))) {
|
|
||||||
set(this, 'emailError', true);
|
|
||||||
return $("#newUserEmail").focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
let user = get(this, 'newUser');
|
|
||||||
|
|
||||||
get(this, 'add')(user).then(() => {
|
|
||||||
this.set('newUser', { firstname: "", lastname: "", email: "", active: true });
|
|
||||||
set(this, 'firstnameError', false);
|
|
||||||
set(this, 'lastnameError', false);
|
|
||||||
set(this, 'emailError', false);
|
|
||||||
$("#newUserFirstname").focus();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -9,48 +9,41 @@
|
||||||
//
|
//
|
||||||
// https://documize.com
|
// https://documize.com
|
||||||
|
|
||||||
import { set } from '@ember/object';
|
|
||||||
import { inject as service } from '@ember/service';
|
import { inject as service } from '@ember/service';
|
||||||
import Controller from '@ember/controller';
|
import Controller from '@ember/controller';
|
||||||
|
|
||||||
export default Controller.extend({
|
export default Controller.extend({
|
||||||
userService: service('user'),
|
userService: service('user'),
|
||||||
|
|
||||||
init() {
|
loadUsers() {
|
||||||
this._super(...arguments);
|
this.get('userService').getComplete().then((users) => {
|
||||||
this.newUser = { firstname: "", lastname: "", email: "", active: true };
|
this.set('model', users);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
add(user) {
|
onAddUser(user) {
|
||||||
set(this, 'newUser', user);
|
return this.get('userService').add(user).then((user) => {
|
||||||
|
|
||||||
return this.get('userService')
|
|
||||||
.add(this.get('newUser'))
|
|
||||||
.then((user) => {
|
|
||||||
this.get('model').pushObject(user);
|
this.get('model').pushObject(user);
|
||||||
})
|
});
|
||||||
.catch(function (error) {
|
},
|
||||||
let msg = error.status === 409 ? 'Unable to add duplicate user' : 'Unable to add user';
|
|
||||||
this.showNotification(msg);
|
onAddUsers(list) {
|
||||||
|
return this.get('userService').addBulk(list).then(() => {
|
||||||
|
this.loadUsers();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
onDelete(userId) {
|
onDelete(userId) {
|
||||||
let self = this;
|
this.get('userService').remove(userId).then( () => {
|
||||||
this.get('userService').remove(userId).then(function () {
|
this.loadUsers();
|
||||||
self.get('userService').getComplete().then(function (users) {
|
|
||||||
self.set('model', users);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
onSave(user) {
|
onSave(user) {
|
||||||
let self = this;
|
this.get('userService').save(user).then(() => {
|
||||||
this.get('userService').save(user).then(function () {
|
this.get('userService').getComplete().then((users) => {
|
||||||
|
this.set('model', users);
|
||||||
self.get('userService').getComplete().then(function (users) {
|
|
||||||
self.set('model', users);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,12 +1,8 @@
|
||||||
<div class="row">
|
{{customize/user-admin
|
||||||
<div class="col">
|
onAddUser=(action 'onAddUser')
|
||||||
<div class="view-customize">
|
onAddUsers=(action 'onAddUsers')}}
|
||||||
<h1 class="admin-heading">Users</h1>
|
|
||||||
<h2 class="sub-heading">Set basic information, passwords and permissions for {{model.length}} users</h2>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{customize/user-settings add=(action 'add')}}
|
{{customize/user-list users=model
|
||||||
|
onDelete=(action "onDelete")
|
||||||
{{customize/user-admin users=model onDelete=(action "onDelete") onSave=(action "onSave") onPassword=(action "onPassword")}}
|
onSave=(action "onSave")
|
||||||
|
onPassword=(action "onPassword")}}
|
||||||
|
|
|
@ -31,6 +31,16 @@ export default Service.extend({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Adds comma-delim list of users (firstname, lastname, email).
|
||||||
|
addBulk(list) {
|
||||||
|
return this.get('ajax').request(`users/import`, {
|
||||||
|
type: 'POST',
|
||||||
|
data: list,
|
||||||
|
contentType: 'text'
|
||||||
|
}).then(() => {
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
// Returns user model for specified user id.
|
// Returns user model for specified user id.
|
||||||
getUser(userId) {
|
getUser(userId) {
|
||||||
let url = `users/${userId}`;
|
let url = `users/${userId}`;
|
||||||
|
|
|
@ -1,153 +1,56 @@
|
||||||
<div class="view-customize mb-5">
|
|
||||||
<h3>Users</h3>
|
|
||||||
<table class="table table-hover table-responsive user-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>{{input type="text" class="form-control" placeholder="filter users" value=filter}}</th>
|
|
||||||
<th class="no-width">Add Space</th>
|
|
||||||
<th class="no-width">View Users</th>
|
|
||||||
<th class="no-width">Admin</th>
|
|
||||||
<th class="no-width">Active</th>
|
|
||||||
<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>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{{#each filteredUsers key="id" as |user|}}
|
|
||||||
<tr>
|
|
||||||
<td class="{{unless user.active 'inactive-user'}} {{if user.admin 'admin-user'}}">
|
|
||||||
<div class="d-inline-block align-top">
|
|
||||||
{{#if user.me}}
|
|
||||||
<i class="material-icons color-gray">check_box_outline_blank</i>
|
|
||||||
{{else if user.selected}}
|
|
||||||
<i class="material-icons checkbox" {{action 'toggleSelect' user}}>check_box</i>
|
|
||||||
{{else}}
|
|
||||||
<i class="material-icons checkbox" {{action 'toggleSelect' user}}>check_box_outline_blank</i>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
<div class="d-inline-block">
|
|
||||||
<div class="name d-inline-block">{{ user.fullname }}</div>
|
|
||||||
<div class="email">{{ user.email }}</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td class="no-width text-center">
|
|
||||||
{{#if user.editor}}
|
|
||||||
<i class="material-icons checkbox" {{action 'toggleEditor' user.id}}>check_box</i>
|
|
||||||
{{else}}
|
|
||||||
<i class="material-icons checkbox" {{action 'toggleEditor' user.id}}>check_box_outline_blank</i>
|
|
||||||
{{/if}}
|
|
||||||
</td>
|
|
||||||
<td class="no-width text-center">
|
|
||||||
{{#if user.viewUsers}}
|
|
||||||
<i class="material-icons checkbox" {{action 'toggleUsers' user.id}}>check_box</i>
|
|
||||||
{{else}}
|
|
||||||
<i class="material-icons checkbox" {{action 'toggleUsers' user.id}}>check_box_outline_blank</i>
|
|
||||||
{{/if}}
|
|
||||||
</td>
|
|
||||||
<td class="no-width text-center">
|
|
||||||
{{#if user.me}}
|
|
||||||
<i class="material-icons color-gray">check_box</i>
|
|
||||||
{{else if user.admin}}
|
|
||||||
<i class="material-icons checkbox" {{action 'toggleAdmin' user.id}}>check_box</i>
|
|
||||||
{{else}}
|
|
||||||
<i class="material-icons checkbox" {{action 'toggleAdmin' user.id}}>check_box_outline_blank</i>
|
|
||||||
{{/if}}
|
|
||||||
</td>
|
|
||||||
<td class="no-width text-center">
|
|
||||||
{{#if user.me}}
|
|
||||||
<i class="material-icons color-gray">check_box</i>
|
|
||||||
{{else if user.active}}
|
|
||||||
<i class="material-icons checkbox" {{action 'toggleActive' user.id}}>check_box</i>
|
|
||||||
{{else}}
|
|
||||||
<i class="material-icons checkbox" {{action 'toggleActive' user.id}}>check_box_outline_blank</i>
|
|
||||||
{{/if}}
|
|
||||||
</td>
|
|
||||||
<td class="no-width text-center">
|
|
||||||
{{#if user.me}}
|
|
||||||
<div class="edit-button-{{user.id}} button-icon-gray" title="Edit" {{action "onShowEdit" 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>
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{{/each}}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="edit-user-modal" class="modal" tabindex="-1" role="dialog">
|
|
||||||
<div class="modal-dialog" role="document">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">User {{editUser.firstname}} {{editUser.lastname}}</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<form>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="edit-firstname">Firstname</label>
|
|
||||||
{{input id="edit-firstname" class="form-control" type="text" value=editUser.firstname}}
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="edit-lastname">Lastname</label>
|
|
||||||
{{input id="edit-lastname" type="text" class="form-control" value=editUser.lastname}}
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="edit-email">Email</label>
|
|
||||||
{{input id="edit-email" type="text" class="form-control" value=editUser.email}}
|
|
||||||
</div>
|
|
||||||
{{#if isAuthProviderDocumize}}
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6">
|
<div class="col">
|
||||||
|
<div class="view-customize">
|
||||||
|
<h1 class="admin-heading">Users</h1>
|
||||||
|
<h2 class="sub-heading">Set basic information, passwords and permissions for {{model.length}} users</h2>
|
||||||
|
|
||||||
|
{{#if isAuthProviderDocumize}}
|
||||||
|
<div class="btn btn-success mt-3 mb-3" {{action 'onOpenUserModal'}}>Add user</div>
|
||||||
|
<div id="add-user-modal" class="modal" tabindex="-1" role="dialog">
|
||||||
|
<div class="modal-dialog modal-lg" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">Add User</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form onsubmit={{action 'onAddUser'}}>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="col">
|
||||||
|
<label for="newUserFirstname">Firstname</label>
|
||||||
|
{{input id="newUserFirstname" type="text" class="form-control" placeholder="Firstname" value=newUser.firstname}}
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<label for="newUserLastname">Lastname</label>
|
||||||
|
{{input id="newUserLastname" type="text" class="form-control" placeholder="Lastname" value=newUser.lastname}}
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<label for="newUserEmail">Lastname</label>
|
||||||
|
{{input id="newUserEmail" type="email" class="form-control" placeholder="Email" value=newUser.email}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="col mt-3 text-right">
|
||||||
|
<button type="submit" class="btn btn-success" {{action 'onAddUser'}}>Add user</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<form onsubmit={{action 'onAddUser'}}>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="edit-password">Password</label>
|
<label for="edit-group-desc">Bulk create users</label>
|
||||||
<div class="tip">Optional new password</div>
|
{{textarea id="bulkUsers" value=bulkUsers class="form-control" rows="5" placeholder="firstname, lastname, email"}}
|
||||||
{{input id="edit-password" type="password" class="form-control" value=password.password}}
|
<small class="form-text text-muted">Comma-delimited list: firstname, lastname, email</small>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="text-right">
|
||||||
|
<button type="submit" class="btn btn-success" {{action 'onAddUsers'}}>Add users</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="edit-confirmPassword">Confirm Password</label>
|
|
||||||
<div class="tip">Confirm new password</div>
|
|
||||||
{{input id="edit-confirmPassword" type="password" class="form-control" value=password.confirmation}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer mt-4">
|
||||||
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">Cancel</button>
|
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">Close</button>
|
||||||
<button type="button" class="btn btn-success" onclick={{action 'onUpdate'}}>Save</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
{{#ui/ui-dialog title="Delete User" confirmCaption="Delete" buttonType="btn-danger" show=showDeleteDialog onAction=(action 'onDelete')}}
|
</div>
|
||||||
<p>Are you sure you want to delete {{deleteUser.fullname}}?</p>
|
</div>
|
||||||
{{/ui/ui-dialog}}
|
</div>
|
||||||
|
|
||||||
|
|
153
gui/app/templates/components/customize/user-list.hbs
Normal file
153
gui/app/templates/components/customize/user-list.hbs
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
<div class="view-customize mb-5">
|
||||||
|
<h3>Users</h3>
|
||||||
|
<table class="table table-hover table-responsive user-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{{input type="text" class="form-control" placeholder="filter users" value=filter}}</th>
|
||||||
|
<th class="no-width">Add Space</th>
|
||||||
|
<th class="no-width">View Users</th>
|
||||||
|
<th class="no-width">Admin</th>
|
||||||
|
<th class="no-width">Active</th>
|
||||||
|
<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>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{#each filteredUsers key="id" as |user|}}
|
||||||
|
<tr>
|
||||||
|
<td class="{{unless user.active 'inactive-user'}} {{if user.admin 'admin-user'}}">
|
||||||
|
<div class="d-inline-block align-top">
|
||||||
|
{{#if user.me}}
|
||||||
|
<i class="material-icons color-gray">check_box_outline_blank</i>
|
||||||
|
{{else if user.selected}}
|
||||||
|
<i class="material-icons checkbox" {{action 'toggleSelect' user}}>check_box</i>
|
||||||
|
{{else}}
|
||||||
|
<i class="material-icons checkbox" {{action 'toggleSelect' user}}>check_box_outline_blank</i>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
<div class="d-inline-block">
|
||||||
|
<div class="name d-inline-block">{{ user.fullname }}</div>
|
||||||
|
<div class="email">{{ user.email }}</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="no-width text-center">
|
||||||
|
{{#if user.editor}}
|
||||||
|
<i class="material-icons checkbox" {{action 'toggleEditor' user.id}}>check_box</i>
|
||||||
|
{{else}}
|
||||||
|
<i class="material-icons checkbox" {{action 'toggleEditor' user.id}}>check_box_outline_blank</i>
|
||||||
|
{{/if}}
|
||||||
|
</td>
|
||||||
|
<td class="no-width text-center">
|
||||||
|
{{#if user.viewUsers}}
|
||||||
|
<i class="material-icons checkbox" {{action 'toggleUsers' user.id}}>check_box</i>
|
||||||
|
{{else}}
|
||||||
|
<i class="material-icons checkbox" {{action 'toggleUsers' user.id}}>check_box_outline_blank</i>
|
||||||
|
{{/if}}
|
||||||
|
</td>
|
||||||
|
<td class="no-width text-center">
|
||||||
|
{{#if user.me}}
|
||||||
|
<i class="material-icons color-gray">check_box</i>
|
||||||
|
{{else if user.admin}}
|
||||||
|
<i class="material-icons checkbox" {{action 'toggleAdmin' user.id}}>check_box</i>
|
||||||
|
{{else}}
|
||||||
|
<i class="material-icons checkbox" {{action 'toggleAdmin' user.id}}>check_box_outline_blank</i>
|
||||||
|
{{/if}}
|
||||||
|
</td>
|
||||||
|
<td class="no-width text-center">
|
||||||
|
{{#if user.me}}
|
||||||
|
<i class="material-icons color-gray">check_box</i>
|
||||||
|
{{else if user.active}}
|
||||||
|
<i class="material-icons checkbox" {{action 'toggleActive' user.id}}>check_box</i>
|
||||||
|
{{else}}
|
||||||
|
<i class="material-icons checkbox" {{action 'toggleActive' user.id}}>check_box_outline_blank</i>
|
||||||
|
{{/if}}
|
||||||
|
</td>
|
||||||
|
<td class="no-width text-center">
|
||||||
|
{{#if user.me}}
|
||||||
|
<div class="edit-button-{{user.id}} button-icon-gray" title="Edit" {{action "onShowEdit" 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>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{/each}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="edit-user-modal" class="modal" tabindex="-1" role="dialog">
|
||||||
|
<div class="modal-dialog" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">User {{editUser.firstname}} {{editUser.lastname}}</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="edit-firstname">Firstname</label>
|
||||||
|
{{input id="edit-firstname" class="form-control" type="text" value=editUser.firstname}}
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="edit-lastname">Lastname</label>
|
||||||
|
{{input id="edit-lastname" type="text" class="form-control" value=editUser.lastname}}
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="edit-email">Email</label>
|
||||||
|
{{input id="edit-email" type="text" class="form-control" value=editUser.email}}
|
||||||
|
</div>
|
||||||
|
{{#if isAuthProviderDocumize}}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="edit-password">Password</label>
|
||||||
|
<div class="tip">Optional new password</div>
|
||||||
|
{{input id="edit-password" type="password" class="form-control" value=password.password}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="edit-confirmPassword">Confirm Password</label>
|
||||||
|
<div class="tip">Confirm new password</div>
|
||||||
|
{{input id="edit-confirmPassword" type="password" class="form-control" value=password.confirmation}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
</form>
|
||||||
|
</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 'onUpdate'}}>Save</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{#ui/ui-dialog title="Delete User" confirmCaption="Delete" buttonType="btn-danger" show=showDeleteDialog onAction=(action 'onDelete')}}
|
||||||
|
<p>Are you sure you want to delete {{deleteUser.fullname}}?</p>
|
||||||
|
{{/ui/ui-dialog}}
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
{{#if isAuthProviderDocumize}}
|
|
||||||
<div class="view-customize mt-5 mb-5">
|
|
||||||
<h3>Add User</h3>
|
|
||||||
<form class="form-inline">
|
|
||||||
<label class="sr-only" for="newUserFirstname">Firstname</label>
|
|
||||||
{{focus-input id="newUserFirstname" type="text" value=newUser.firstname placeholder="Firstname" class=(if hasFirstnameEmptyError 'form-control mb-2 mr-sm-4 mb-sm-0 is-invalid' 'form-control mb-2 mr-sm-4 mb-sm-0')}}
|
|
||||||
|
|
||||||
<label class="sr-only" for="newUserLastname">Lastname</label>
|
|
||||||
{{input id="newUserLastname" type="text" value=newUser.lastname placeholder="Lastname" class=(if hasLastnameEmptyError 'form-control mb-2 mr-sm-4 mb-sm-0 is-invalid' 'form-control mb-2 mr-sm-4 mb-sm-0')}}
|
|
||||||
|
|
||||||
<label class="sr-only" for="newEmail">Email</label>
|
|
||||||
{{input id="newEmail" type="email" value=newUser.email placeholder="Email" class=(if hasEmailEmptyError 'form-control mb-2 mr-sm-4 mb-sm-0 is-invalid' 'form-control mb-2 mr-sm-4 mb-sm-0')}}
|
|
||||||
|
|
||||||
<button type="submit" class="btn btn-success" {{action 'add'}}>Add</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
|
@ -151,6 +151,7 @@ func RegisterEndpoints(rt *env.Runtime, s *domain.Store) {
|
||||||
Add(rt, RoutePrefixPrivate, "users/{userID}", []string{"DELETE", "OPTIONS"}, nil, user.Delete)
|
Add(rt, RoutePrefixPrivate, "users/{userID}", []string{"DELETE", "OPTIONS"}, nil, user.Delete)
|
||||||
Add(rt, RoutePrefixPrivate, "users/sync", []string{"GET", "OPTIONS"}, nil, keycloak.Sync)
|
Add(rt, RoutePrefixPrivate, "users/sync", []string{"GET", "OPTIONS"}, nil, keycloak.Sync)
|
||||||
Add(rt, RoutePrefixPrivate, "users/match", []string{"POST", "OPTIONS"}, nil, user.MatchUsers)
|
Add(rt, RoutePrefixPrivate, "users/match", []string{"POST", "OPTIONS"}, nil, user.MatchUsers)
|
||||||
|
Add(rt, RoutePrefixPrivate, "users/import", []string{"POST", "OPTIONS"}, nil, user.BulkImport)
|
||||||
|
|
||||||
Add(rt, RoutePrefixPrivate, "search", []string{"POST", "OPTIONS"}, nil, document.SearchDocuments)
|
Add(rt, RoutePrefixPrivate, "search", []string{"POST", "OPTIONS"}, nil, document.SearchDocuments)
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue