1
0
Fork 0
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:
sauls8t 2018-03-01 10:58:55 +00:00
parent 7e6d6366da
commit 0b5ed8fd9e
11 changed files with 627 additions and 410 deletions

View file

@ -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)
}

View file

@ -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");
} }
} }
}); });

View 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');
}
}
});

View file

@ -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();
});
}
}
});

View file

@ -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);
}); });
}); });
}, },

View file

@ -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")}}

View file

@ -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}`;

View file

@ -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>

View 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}}

View file

@ -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}}

View file

@ -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)