< template >
< div class = "h-100" >
< v -toolbar flat height = "38" class = "toolbar-border-bottom" >
< v -text -field
style = "max-width: 300px"
dense
solo
flat
class = "search-field caption"
v - model = "query" hide - details
@ keypress . enter = "loadUsers"
placeholder = "Filter by email" >
< template v -slot : prepend -inner >
< v -icon class = "mt-1" small > search < / v - i c o n >
< / template >
< / v - t e x t - f i e l d >
< v -spacer > < / v - s p a c e r >
< x -btn outlined tooltip = "Reload roles"
color = "primary"
small
: disabled = "loading"
@ click = "loadUsers"
v - ge = "['roles','reload']"
@ click . prevent >
< v -icon small left > refresh < / v - i c o n >
Reload
< / x - b t n >
< x -btn
v - if = "_isUIAllowed('newUser')"
outlined tooltip = "Add new role"
color = "primary"
small
: disabled = "loading"
@ click = "addUser"
v - ge = "['roles','add new']" >
< v -icon small left > mdi - plus < / v - i c o n >
New User
< / x - b t n >
< / v - t o o l b a r >
< v -card style = "height:calc(100% - 38px)" >
< v -container style = "height: 100%" fluid >
<!-- < div class = "d-flex d-100 justify-center" > -- >
< v -row style = "height:100%" >
< v -col cols = "12" class = "h-100" >
< v -card class = "h-100 elevation-0" >
< v -row style = "height:100%" >
< v -col offset = "2" :cols ="8" class = "h-100" >
<!-- < v -card class = "h-100 px-4 py-2" > -- >
<!-- < v -row >
< v -col >
< / v - c o l >
< v -col class = "flex-shrink-1 flex-grow-0" > < h4 class = "text-center text-capitalize mt-2 d-100"
style = "min-width:100px" > User List < / h4 > < / v - c o l >
< v -col > < / v - c o l >
< / v - r o w > - - >
< v -data -table dense v -if = " users "
: headers = "[{},{},{},{}]"
hide - default - header
: hide - default - footer = "count < limit"
: options . sync = "options"
: items = "users"
: items - per - page . sync = "limit"
: server - items - length = "count" >
< template v -slot :header ="{props:{headers}}" >
< thead >
< tr class = "text-left" >
<!-- < th > # < / th > -- >
< th class = "font-weight-regular caption" >
< v -icon small > mdi - email - outline < / v - i c o n >
Email
< / th >
< th class = "font-weight-regular caption" >
< v -icon small > mdi - drama - masks < / v - i c o n >
Roles
< / th >
< th class = "font-weight-regular caption" >
<!-- < v -icon small class = "mt-n1" > mdi - cursor - default - outline < / v - i c o n > - - >
Actions
< / th >
< / tr >
< / thead >
< / template >
< template v -slot :item ="{item,index}" >
< tr @ click = "selectedUser = item" >
<!-- < td >
< v -radio -group dense hide -details v-model ="selectedUserIndex" class="mt-n2" >
< v -radio :value ="index"
> < / v - r a d i o >
< / v - r a d i o - g r o u p >
< / td > -- >
< td > { { item . email } } < / td >
< td >
<!-- { { item . roles } } -- >
< v -chip class = "mr-1" v -for = " role in ( item.roles ? item.roles.split ( ' , ' ) : [ ] ) " :key ="role"
: color = "rolesColors[role]" >
{ { role } }
< / v - c h i p >
<!-- < v -edit -dialog - - >
<!-- > -- >
<!-- < div - - >
<!-- : title = "item.roles" -- >
<!-- style = "width:180px;overflow:hidden;white-space: nowrap;text-overflow:ellipsis" > { { -- >
<!-- item . roles -- >
<!-- } } -- >
<!-- < / div > -- >
<!-- < template v -slot : input > -- >
<!-- < v -text -field - - >
<!-- v - model = "item.roles" -- >
<!-- label = "Edit" -- >
<!-- single - line -- >
<!-- > < / v - t e x t - f i e l d > - - >
<!-- < / template > -- >
<!-- < / v - e d i t - d i a l o g > - - >
< / td >
< td >
< x -icon tooltip = "Edit User" iconClass = ""
color = "primary"
@ click . prevent . stop = "invite_token = null; selectedUser = item; userEditDialog = true"
small > mdi - pencil - outline
< / x - i c o n >
< x -icon v -if = " ! item.project_id " tooltip = "Add user to project"
color = "primary"
@ click = "inviteUser(item.email)"
small > mdi - plus
< / x - i c o n >
< x -icon
v - else
tooltip = "Remove user from project" class = "ml-2" color = "error"
@ click . prevent . stop = "deleteId = item.id; deleteItem = item.id;showConfirmDlg = true"
small > mdi - delete - outline
< / x - i c o n >
< x -icon v -if = " item.invite_token " tooltip = "Resend invite email" iconClass = "mt-n1"
color = "primary"
@ click . prevent . stop = "rensendInvite(item.id)"
small > mdi - email - send - outline
< / x - i c o n >
< x -icon v -if = " item.invite_token " tooltip = "Copy invite url" iconClass = ""
color = "primary"
@ click . prevent . stop = "clipboard(getInviteUrl(item.invite_token)); $toast.success('Invite url copied to clipboard').goAway(3000)"
small > mdi - content - copy
< / x - i c o n >
< / td >
< / tr >
< / template >
< / v - d a t a - t a b l e >
< div class = "mt-10 text-center" >
< x -btn
v - if = "_isUIAllowed('newUser')"
outlined tooltip = "Add new user"
color = "primary"
small
: disabled = "loading"
@ click = "addUser"
v - ge = "['roles','add new']" >
< v -icon small left > mdi - plus < / v - i c o n >
New User
< / x - b t n >
< / div >
<!-- < / v - c a r d > - - >
< / v - c o l >
<!-- < v -col cols = "5" v-if ="selectedUser && _isUIAllowed('editUser')" style="height:100%; overflow: auto" >
< v -card class = "px-4 py-2" style = "min-height: 100%" >
< h4 class = "text-center text-capitalize mt-2 d-100 grey--text text-darken-1" > User Info < / h4 >
< v -toolbar flat height = "38" class = "toolbar-border-bottom" >
< v -spacer > < / v - s p a c e r >
< x -btn outlined tooltip = "Save Changes"
color = "primary"
small
@ click = "saveUser"
: disabled = "loading || !edited || !valid"
btn . class = "text-capitalize"
v - ge = "['rows','save']"
>
< v -icon small left > save < / v - i c o n >
Save < span v-if ="!selectedUser.id" > & Invite < / span >
< / x - b t n >
< / v - t o o l b a r >
< v -form v-model ="valid" >
< v -row class = "mt-3" >
& lt ; ! & ndash ; < v -col cols = "12" class = "edit-header" > & ndash ; & gt ;
& lt ; ! & ndash ; < / v - c o l > & n d a s h ; & g t ;
< v -col cols = "12" >
< v -text -field
: disabled = "!!selectedUser.id"
auto
dense
v - model = "selectedUser.email"
: rules = "emailRules"
@ input = "edited=true"
outlined label = "Email" > < / v - t e x t - f i e l d >
< / v - c o l >
< v -col cols = "12" >
< v -combobox
class = "role-select"
outlined
hide - details
v - model = "selectedRoles"
: items = "roles"
@ change = "edited = true"
label = "Select User roles"
multiple
chips
dense
deletable - chips
color = "warning"
> < / v - c o m b o b o x >
< / v - c o l >
< / v - r o w >
< / v - f o r m >
< / v - c a r d >
< / v - c o l > - - >
< / v - r o w >
< / v - c a r d >
< / v - c o l >
< / v - r o w >
< table v-if ="false" class="mx-auto users-table" style="min-width:700px" >
< thead >
< tr >
< th > < / th >
< th > Email < / th >
< th > Roles < / th >
< th > < / th >
< / tr >
< / thead >
< tbody >
< tr v-for ="user in users" >
< td >
< v -icon x -large > mdi - account - outline < / v - i c o n >
< / td >
< td class = "px-1 py-1" align = "top" >
< v -text -field
solo
class = "elevation-0"
disabled
: value = "user.email"
hide - details
dense > < / v - t e x t - f i e l d >
< / td >
< td class = "px-1 py-1" >
< set -list -checkbox -cell v-model ="user.roles" :values ="roles" > < / set -list -checkbox -cell >
<!-- < v -text -field outlined hide -details dense > < / v - t e x t - f i e l d > - - >
<!-- < v -combobox
solo
: value = "user.roles.split(',')"
class = "elevation-0"
: items = "roles"
hide - selected
multiple
small - chips
hide - details
dense
>
< / v - c o m b o b o x > - - >
< / td >
< td align = "middle" >
< v -icon large > mdi - close < / v - i c o n >
< v -icon large > mdi - save < / v - i c o n >
< / td >
< / tr >
< tr >
< td >
< v -icon x -large > mdi - account - outline < / v - i c o n >
< / td >
< td class = "px-1 py-1" align = "top" >
< v -text -field
solo
class = "elevation-0"
hide - details
dense > < / v - t e x t - f i e l d >
< / td >
< td class = "px-1 py-1" >
< set -list -checkbox -cell :values ="roles" > < / s e t - l i s t - c h e c k b o x - c e l l >
<!-- < v -text -field outlined hide -details dense > < / v - t e x t - f i e l d > - - >
<!-- < v -combobox
solo
class = "elevation-0"
: items = "roles"
hide - selected
multiple
small - chips
hide - details
dense
>
< / v - c o m b o b o x > - - >
< / td >
< td align = "middle" >
< v -icon large > mdi - account - plus < / v - i c o n >
< / td >
< / tr >
< / tbody >
< / table >
< / v - c o n t a i n e r >
< / v - c a r d >
< dlg -label -submit -cancel
type = "primary"
: actions - mtd = "confirmDelete"
heading = "Do you want to remove the user from project?"
: dialog - show = "showConfirmDlg"
> < / d l g - l a b e l - s u b m i t - c a n c e l >
< v -dialog v-model ="userEditDialog" :width="invite_token ? 700 :480" @close="invite_token = null" >
< v -card v-if ="selectedUser" class="px-15 py-5 " style="min-height: 100%" >
< h4 class = "text-center text-capitalize mt-2 d-100 display-1" >
< template v-if ="invite_token" > Copy Invite Token < / template >
< template v -else -if = " selectedUser.id " > Edit User < / template >
< template v-else > Invite < / template >
< / h4 >
< div v-if ="invite_token" class="mt-6 align-center" >
<!-- < div class = "d-flex" >
< v -alert
type = "success"
outlined
>
< div class = "ellipsis d-100" >
{ { inviteUrl } }
< / div >
< / v - a l e r t >
< x -icon icon.class = " ml -3 mt -n3 "
@ click = "clipboard(inviteUrl); $toast.success('Copied invite url to clipboard').goAway(3000)" >
mdi - content - copy
< / x - i c o n >
< / div > -- >
< v -alert
type = "success"
outlined
v - ripple
@ click = "clipboard(inviteUrl); $toast.success('Copied invite url to clipboard').goAway(3000)"
class = "pointer"
>
< template v -slot : append >
< v -icon color = "green" class = "ml-2" > mdi - content - copy < / v - i c o n >
< / template >
< div class = "ellipsis d-100" >
{ { inviteUrl } }
< / div >
< / v - a l e r t >
< p class = "caption grey--text mt-3" > Looks like you have not configured mailer yet ! < br > Please copy above
invite
link and send it to { { invite _token && invite _token . email } } . < / p >
< / div >
< template v-else >
< v -form v-model ="valid" @submit.prevent="saveUser" >
< v -row class = "mt-4" >
< v -col cols = "12" >
< v -text -field
: disabled = "!!selectedUser.id"
filled
dense
ref = "email"
v - model = "selectedUser.email"
: rules = "emailRules"
@ input = "edited=true"
label = "Email" > < / v - t e x t - f i e l d >
< / v - c o l >
< v -col cols = "12" >
< v -combobox
filled
class = "role-select"
hide - details
v - model = "selectedRoles"
: items = "roles"
@ change = "edited = true"
label = "Select User roles"
multiple
dense
deletable - chips
>
< template v -slot :selection ="{item}" >
< v -chip :color ="rolesColors[item]" > { { item } } < / v - c h i p >
< / template >
< / v - c o m b o b o x >
< / v - c o l >
< / v - r o w >
< / v - f o r m >
< v -card -actions class = "justify-center" >
< x -btn tooltip = "Save Changes"
color = "primary"
@ click = "saveUser"
btn . class = "mt-5 mb-3 pr-5"
v - ge = "['rows','save']"
>
< v -icon small left > { { selectedUser . id ? 'save' : 'mdi-send' } } < / v - i c o n >
{ { selectedUser . id ? 'Save' : 'Invite' } }
< / x - b t n >
< / v - c a r d - a c t i o n s >
< / template >
< / v - c a r d >
< / v - d i a l o g >
< / div >
< / template >
< script >
import SetListCheckboxCell from "@/components/project/spreadsheet/editableCell/setListCheckboxCell" ;
import { enumColor as colors , enumColor } from "@/components/project/spreadsheet/helpers/colors" ;
import DlgLabelSubmitCancel from "@/components/utils/dlgLabelSubmitCancel" ;
export default {
name : "user-management" ,
components : { DlgLabelSubmitCancel , SetListCheckboxCell } ,
data : ( ) => ( {
deleteItem : null ,
invite _token : null ,
userEditDialog : false ,
limit : 10 ,
showConfirmDlg : false ,
users : null ,
count : 0 ,
options : { } ,
loading : false ,
selectedUser : null ,
roles : [ ] ,
query : '' ,
deleteId : null ,
edited : false ,
valid : null ,
emailRules : [
v => ! ! v || 'E-mail is required' ,
v => / ^ ( ( [ ^ < > ( ) [ \ ] \ . , ; : \ s @ \ "]+(\.[^<>()[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\" ] { 2 , } ) $ / i . test ( v ) || 'E-mail must be valid'
] ,
userList : [ ]
} ) ,
async created ( ) {
await this . loadUsers ( ) ;
await this . loadRoles ( ) ;
} ,
methods : {
simpleAnim ( ) {
var count = 30 ;
var defaults = {
origin : { y : 0.7 } ,
zIndex : 9999999
} ;
function fire ( particleRatio , opts ) {
window . confetti ( Object . assign ( { } , defaults , opts , {
particleCount : Math . floor ( count * particleRatio ) ,
} ) ) ;
}
fire ( 0.25 , {
spread : 26 ,
startVelocity : 55 ,
} ) ;
fire ( 0.2 , {
spread : 60 ,
} ) ;
fire ( 0.35 , {
spread : 100 ,
decay : 0.91 ,
scalar : 0.8
} ) ;
fire ( 0.1 , {
spread : 120 ,
startVelocity : 25 ,
decay : 0.92 ,
scalar : 1.2
} ) ;
fire ( 0.1 , {
spread : 120 ,
startVelocity : 45 ,
} ) ;
} ,
getInviteUrl ( token ) {
return token ? ` ${ location . origin } ${ location . pathname } #/user/authentication/signup/ ${ token } ` : null ;
} ,
clipboard ( str ) {
const el = document . createElement ( 'textarea' ) ;
el . addEventListener ( 'focusin' , e => e . stopPropagation ( ) ) ;
el . value = str ;
document . body . appendChild ( el ) ;
el . select ( ) ;
document . execCommand ( 'copy' ) ;
document . body . removeChild ( el ) ;
} ,
async rensendInvite ( id ) {
try {
await this . $axios . post ( '/admin/resendInvite/' + id , null , {
headers : {
'xc-auth' : this . $store . state . users . token
}
} ) ;
this . $toast . success ( 'Invite email sent successfully' ) . goAway ( 3000 ) ;
await this . loadUsers ( ) ;
} catch ( e ) {
this . $toast . error ( e . response . data . msg ) . goAway ( 3000 ) ;
}
} ,
async loadUsers ( ) {
try {
const { sortBy , sortDesc , page = 1 , itemsPerPage = 20 } = this . options ;
const data = ( await this . $axios . get ( '/admin' , {
headers : {
'xc-auth' : this . $store . state . users . token
} ,
params : {
limit : itemsPerPage ,
offset : ( page - 1 ) * itemsPerPage ,
query : this . query ,
project _id : this . $route . params . project _id
}
} ) ) . data ;
this . count = data . count ;
this . users = data . list ;
if ( ! this . selectedUser && this . users && this . users [ 0 ] ) {
this . selectedUserIndex = 0 ;
}
} catch ( e ) {
console . log ( e )
}
} ,
async loadRoles ( ) {
try {
this . roles = ( await this . $axios . get ( '/admin/roles' , {
headers : {
'xc-auth' : this . $store . state . users . token
} ,
params : {
project _id : this . $route . params . project _id
}
} ) ) . data . map ( role => role . title ) . filter ( role => role !== 'guest' ) ;
} catch ( e ) {
console . log ( e )
}
} ,
async deleteUser ( id ) {
try {
await this . $axios . delete ( '/admin/' + id , {
params : {
project _id : this . $route . params . project _id ,
email : this . deleteItem . email
} ,
headers : {
'xc-auth' : this . $store . state . users . token
}
} ) ;
this . $toast . success ( 'Successfully removed the user from project' ) . goAway ( 3000 ) ;
await this . loadUsers ( ) ;
} catch ( e ) {
this . $toast . error ( e . response . data . msg ) . goAway ( 3000 ) ;
}
} ,
async confirmDelete ( hideDialog ) {
if ( hideDialog ) {
this . showConfirmDlg = false ;
return
}
await this . deleteUser ( this . deleteId ) ;
this . showConfirmDlg = false ;
} ,
addUser ( ) {
this . invite _token = null ;
this . selectedUser = {
roles : 'creator,editor'
}
this . userEditDialog = true
} ,
async inviteUser ( email ) {
try {
await this . $axios . post ( '/admin' , { email , project _id : this . $route . params . project _id } , {
headers : {
'xc-auth' : this . $store . state . users . token
}
} ) ;
this . $toast . success ( 'Successfully added user to project' ) . goAway ( 3000 ) ;
await this . loadUsers ( ) ;
} catch ( e ) {
this . $toast . error ( e . response . data . msg ) . goAway ( 3000 ) ;
}
} ,
async saveUser ( ) {
if ( this . loading || ! this . valid || ! this . selectedUser ) {
return
}
if ( ! this . edited ) {
this . userEditDialog = false ;
}
try {
let data ;
if ( this . selectedUser . id ) {
await this . $axios . put ( '/admin/' + this . selectedUser . id , {
roles : this . selectedUser . roles ,
email : this . selectedUser . email ,
project _id : this . $route . params . project _id
} , {
headers : {
'xc-auth' : this . $store . state . users . token
}
} ) ;
} else {
data = await this . $axios . post ( '/admin' , {
... this . selectedUser ,
project _id : this . $route . params . project _id
} , {
headers : {
'xc-auth' : this . $store . state . users . token
}
} ) ;
}
this . $toast . success ( 'Successfully updated the user details' ) . goAway ( 3000 ) ;
await this . loadUsers ( ) ;
if ( data && data . data && data . data . invite _token ) {
this . invite _token = data . data ;
this . simpleAnim ( ) ;
return ;
}
} catch ( e ) {
this . $toast . error ( e . response . data . msg ) . goAway ( 3000 ) ;
}
this . userEditDialog = false ;
await this . loadUsers ( ) ;
}
} ,
watch : {
options : {
async handler ( ) {
await this . loadUsers ( ) ;
} ,
deep : true ,
} ,
userEditDialog ( v ) {
if ( v && ( this . selectedUser && ! this . selectedUser . id ) ) {
this . $nextTick ( ( ) => {
setTimeout ( ( ) => {
this . $refs . email . $el . querySelector ( 'input' ) . focus ( ) ;
} , 100 )
} )
}
}
} ,
computed : {
inviteUrl ( ) {
return this . invite _token ? ` ${ location . origin } ${ location . pathname } #/user/authentication/signup/ ${ this . invite _token . invite _token } ` : null ;
} ,
rolesColors ( ) {
const colors = this . $store . state . windows . darkTheme ? enumColor . dark : enumColor . light ;
return this . roles . reduce ( ( o , r , i ) => {
o [ r ] = colors [ i % colors . length ] ;
return o ;
} , { } )
} ,
selectedRoles : {
get ( ) {
return this . selectedUser && this . selectedUser . roles ? this . selectedUser . roles . split ( ',' ) : [ ] ;
} , set ( roles ) {
if ( this . selectedUser ) {
this . selectedUser . roles = roles . filter ( Boolean ) . join ( ',' ) ;
}
}
} ,
selectedUserIndex : {
get ( ) {
return this . users ? this . users . findIndex ( u => u . email === this . selectedUser . email ) : - 1 ;
} , set ( i ) {
this . selectedUser = this . users [ i ] ;
}
}
}
}
< / script >
< style scoped lang = "scss" >
: : v - deep {
. v - alert _ _wrapper > . v - icon {
align - self : center ;
}
tbody tr : nth - of - type ( odd ) {
background - color : transparent ;
}
. search - field . v - text - field > . v - input _ _control , . search - field . v - text - field > . v - input _ _control > . v - input _ _slot {
min - height : auto ;
}
. role - select {
. v - select _ _selections {
//padding-bottom: 0 !important;
min - height : auto ! important ;
. v - chip {
margin - top : 6 px ;
margin - bottom : 2 px ;
}
}
}
}
. users - table {
td : first - child {
width : 50 px
}
td : last - child {
width : 50 px
}
}
< / style >
<!--
/ * *
* @ copyright Copyright ( c ) 2021 , Xgene Cloud Ltd
*
* @ author Naveen MR < oof1lab @ gmail.com >
* @ author Pranav C Balan < pranavxc @ gmail.com >
*
* @ license GNU AGPL version 3 or any later version
*
* This program is free software : you can redistribute it and / or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation , either version 3 of the
* License , or ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU Affero General Public License for more details .
*
* You should have received a copy of the GNU Affero General Public License
* along with this program . If not , see < http : / / www.gnu.org / licenses / > .
*
* /
-- >