| 
									
										
										
										
											2021-04-15 20:21:06 +08:00
										 |  |  | import React, { FC, PureComponent } from 'react'; | 
					
						
							| 
									
										
										
										
											2021-04-22 18:19:41 +08:00
										 |  |  | import { AccessControlAction, UserDTO } from 'app/types'; | 
					
						
							| 
									
										
										
										
											2021-04-15 20:21:06 +08:00
										 |  |  | import { css, cx } from '@emotion/css'; | 
					
						
							| 
									
										
										
										
											2020-01-14 00:10:19 +08:00
										 |  |  | import { config } from 'app/core/config'; | 
					
						
							|  |  |  | import { GrafanaTheme } from '@grafana/data'; | 
					
						
							| 
									
										
										
										
											2021-04-15 20:21:06 +08:00
										 |  |  | import { Button, ConfirmButton, ConfirmModal, Input, LegacyInputStatus, stylesFactory } from '@grafana/ui'; | 
					
						
							| 
									
										
										
										
											2021-04-22 18:19:41 +08:00
										 |  |  | import { contextSrv } from 'app/core/core'; | 
					
						
							| 
									
										
										
										
											2020-01-14 00:10:19 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | interface Props { | 
					
						
							|  |  |  |   user: UserDTO; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   onUserUpdate: (user: UserDTO) => void; | 
					
						
							|  |  |  |   onUserDelete: (userId: number) => void; | 
					
						
							|  |  |  |   onUserDisable: (userId: number) => void; | 
					
						
							|  |  |  |   onUserEnable: (userId: number) => void; | 
					
						
							|  |  |  |   onPasswordChange(password: string): void; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | interface State { | 
					
						
							|  |  |  |   isLoading: boolean; | 
					
						
							|  |  |  |   showDeleteModal: boolean; | 
					
						
							|  |  |  |   showDisableModal: boolean; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export class UserProfile extends PureComponent<Props, State> { | 
					
						
							|  |  |  |   state = { | 
					
						
							|  |  |  |     isLoading: false, | 
					
						
							|  |  |  |     showDeleteModal: false, | 
					
						
							|  |  |  |     showDisableModal: false, | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   showDeleteUserModal = (show: boolean) => () => { | 
					
						
							|  |  |  |     this.setState({ showDeleteModal: show }); | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   showDisableUserModal = (show: boolean) => () => { | 
					
						
							|  |  |  |     this.setState({ showDisableModal: show }); | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   onUserDelete = () => { | 
					
						
							|  |  |  |     const { user, onUserDelete } = this.props; | 
					
						
							|  |  |  |     onUserDelete(user.id); | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   onUserDisable = () => { | 
					
						
							|  |  |  |     const { user, onUserDisable } = this.props; | 
					
						
							|  |  |  |     onUserDisable(user.id); | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   onUserEnable = () => { | 
					
						
							|  |  |  |     const { user, onUserEnable } = this.props; | 
					
						
							|  |  |  |     onUserEnable(user.id); | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   onUserNameChange = (newValue: string) => { | 
					
						
							|  |  |  |     const { user, onUserUpdate } = this.props; | 
					
						
							|  |  |  |     onUserUpdate({ | 
					
						
							|  |  |  |       ...user, | 
					
						
							|  |  |  |       name: newValue, | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   onUserEmailChange = (newValue: string) => { | 
					
						
							|  |  |  |     const { user, onUserUpdate } = this.props; | 
					
						
							|  |  |  |     onUserUpdate({ | 
					
						
							|  |  |  |       ...user, | 
					
						
							|  |  |  |       email: newValue, | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   onUserLoginChange = (newValue: string) => { | 
					
						
							|  |  |  |     const { user, onUserUpdate } = this.props; | 
					
						
							|  |  |  |     onUserUpdate({ | 
					
						
							|  |  |  |       ...user, | 
					
						
							|  |  |  |       login: newValue, | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   onPasswordChange = (newValue: string) => { | 
					
						
							|  |  |  |     this.props.onPasswordChange(newValue); | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   render() { | 
					
						
							|  |  |  |     const { user } = this.props; | 
					
						
							|  |  |  |     const { showDeleteModal, showDisableModal } = this.state; | 
					
						
							| 
									
										
										
										
											2020-04-10 14:23:27 +08:00
										 |  |  |     const authSource = user.authLabels?.length && user.authLabels[0]; | 
					
						
							|  |  |  |     const lockMessage = authSource ? `Synced via ${authSource}` : ''; | 
					
						
							| 
									
										
										
										
											2020-01-14 00:10:19 +08:00
										 |  |  |     const styles = getStyles(config.theme); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-22 18:19:41 +08:00
										 |  |  |     const editLocked = user.isExternal || !contextSrv.hasPermission(AccessControlAction.UsersWrite); | 
					
						
							|  |  |  |     const passwordChangeLocked = user.isExternal || !contextSrv.hasPermission(AccessControlAction.UsersPasswordUpdate); | 
					
						
							|  |  |  |     const canDelete = contextSrv.hasPermission(AccessControlAction.UsersDelete); | 
					
						
							|  |  |  |     const canDisable = contextSrv.hasPermission(AccessControlAction.UsersDisable); | 
					
						
							|  |  |  |     const canEnable = contextSrv.hasPermission(AccessControlAction.UsersEnable); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-14 00:10:19 +08:00
										 |  |  |     return ( | 
					
						
							|  |  |  |       <> | 
					
						
							|  |  |  |         <h3 className="page-heading">User information</h3> | 
					
						
							|  |  |  |         <div className="gf-form-group"> | 
					
						
							|  |  |  |           <div className="gf-form"> | 
					
						
							|  |  |  |             <table className="filter-table form-inline"> | 
					
						
							|  |  |  |               <tbody> | 
					
						
							|  |  |  |                 <UserProfileRow | 
					
						
							|  |  |  |                   label="Name" | 
					
						
							|  |  |  |                   value={user.name} | 
					
						
							| 
									
										
										
										
											2021-04-22 18:19:41 +08:00
										 |  |  |                   locked={editLocked} | 
					
						
							| 
									
										
										
										
											2020-01-14 00:10:19 +08:00
										 |  |  |                   lockMessage={lockMessage} | 
					
						
							|  |  |  |                   onChange={this.onUserNameChange} | 
					
						
							|  |  |  |                 /> | 
					
						
							|  |  |  |                 <UserProfileRow | 
					
						
							|  |  |  |                   label="Email" | 
					
						
							|  |  |  |                   value={user.email} | 
					
						
							| 
									
										
										
										
											2021-04-22 18:19:41 +08:00
										 |  |  |                   locked={editLocked} | 
					
						
							| 
									
										
										
										
											2020-01-14 00:10:19 +08:00
										 |  |  |                   lockMessage={lockMessage} | 
					
						
							|  |  |  |                   onChange={this.onUserEmailChange} | 
					
						
							|  |  |  |                 /> | 
					
						
							|  |  |  |                 <UserProfileRow | 
					
						
							|  |  |  |                   label="Username" | 
					
						
							|  |  |  |                   value={user.login} | 
					
						
							| 
									
										
										
										
											2021-04-22 18:19:41 +08:00
										 |  |  |                   locked={editLocked} | 
					
						
							| 
									
										
										
										
											2020-01-14 00:10:19 +08:00
										 |  |  |                   lockMessage={lockMessage} | 
					
						
							|  |  |  |                   onChange={this.onUserLoginChange} | 
					
						
							|  |  |  |                 /> | 
					
						
							|  |  |  |                 <UserProfileRow | 
					
						
							|  |  |  |                   label="Password" | 
					
						
							|  |  |  |                   value="********" | 
					
						
							|  |  |  |                   inputType="password" | 
					
						
							| 
									
										
										
										
											2021-04-22 18:19:41 +08:00
										 |  |  |                   locked={passwordChangeLocked} | 
					
						
							| 
									
										
										
										
											2020-01-14 00:10:19 +08:00
										 |  |  |                   lockMessage={lockMessage} | 
					
						
							|  |  |  |                   onChange={this.onPasswordChange} | 
					
						
							|  |  |  |                 /> | 
					
						
							|  |  |  |               </tbody> | 
					
						
							|  |  |  |             </table> | 
					
						
							|  |  |  |           </div> | 
					
						
							|  |  |  |           <div className={styles.buttonRow}> | 
					
						
							| 
									
										
										
										
											2021-04-22 18:19:41 +08:00
										 |  |  |             {canDelete && ( | 
					
						
							|  |  |  |               <> | 
					
						
							|  |  |  |                 <Button variant="destructive" onClick={this.showDeleteUserModal(true)}> | 
					
						
							|  |  |  |                   Delete user | 
					
						
							|  |  |  |                 </Button> | 
					
						
							|  |  |  |                 <ConfirmModal | 
					
						
							|  |  |  |                   isOpen={showDeleteModal} | 
					
						
							|  |  |  |                   title="Delete user" | 
					
						
							|  |  |  |                   body="Are you sure you want to delete this user?" | 
					
						
							|  |  |  |                   confirmText="Delete user" | 
					
						
							|  |  |  |                   onConfirm={this.onUserDelete} | 
					
						
							|  |  |  |                   onDismiss={this.showDeleteUserModal(false)} | 
					
						
							|  |  |  |                 /> | 
					
						
							|  |  |  |               </> | 
					
						
							|  |  |  |             )} | 
					
						
							|  |  |  |             {user.isDisabled && canEnable && ( | 
					
						
							| 
									
										
										
										
											2020-03-26 18:50:27 +08:00
										 |  |  |               <Button variant="secondary" onClick={this.onUserEnable}> | 
					
						
							| 
									
										
										
										
											2021-03-30 03:52:40 +08:00
										 |  |  |                 Enable user | 
					
						
							| 
									
										
										
										
											2020-03-26 18:50:27 +08:00
										 |  |  |               </Button> | 
					
						
							| 
									
										
										
										
											2020-01-14 00:10:19 +08:00
										 |  |  |             )} | 
					
						
							| 
									
										
										
										
											2021-04-22 18:19:41 +08:00
										 |  |  |             {!user.isDisabled && canDisable && ( | 
					
						
							|  |  |  |               <> | 
					
						
							|  |  |  |                 <Button variant="secondary" onClick={this.showDisableUserModal(true)}> | 
					
						
							|  |  |  |                   Disable user | 
					
						
							|  |  |  |                 </Button> | 
					
						
							|  |  |  |                 <ConfirmModal | 
					
						
							|  |  |  |                   isOpen={showDisableModal} | 
					
						
							|  |  |  |                   title="Disable user" | 
					
						
							|  |  |  |                   body="Are you sure you want to disable this user?" | 
					
						
							|  |  |  |                   confirmText="Disable user" | 
					
						
							|  |  |  |                   onConfirm={this.onUserDisable} | 
					
						
							|  |  |  |                   onDismiss={this.showDisableUserModal(false)} | 
					
						
							|  |  |  |                 /> | 
					
						
							|  |  |  |               </> | 
					
						
							|  |  |  |             )} | 
					
						
							| 
									
										
										
										
											2020-01-14 00:10:19 +08:00
										 |  |  |           </div> | 
					
						
							|  |  |  |         </div> | 
					
						
							|  |  |  |       </> | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const getStyles = stylesFactory((theme: GrafanaTheme) => { | 
					
						
							|  |  |  |   return { | 
					
						
							|  |  |  |     buttonRow: css`
 | 
					
						
							|  |  |  |       margin-top: 0.8rem; | 
					
						
							|  |  |  |       > * { | 
					
						
							|  |  |  |         margin-right: 16px; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     `,
 | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | interface UserProfileRowProps { | 
					
						
							|  |  |  |   label: string; | 
					
						
							|  |  |  |   value?: string; | 
					
						
							|  |  |  |   locked?: boolean; | 
					
						
							|  |  |  |   lockMessage?: string; | 
					
						
							|  |  |  |   inputType?: string; | 
					
						
							|  |  |  |   onChange?: (value: string) => void; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | interface UserProfileRowState { | 
					
						
							|  |  |  |   value: string; | 
					
						
							|  |  |  |   editing: boolean; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export class UserProfileRow extends PureComponent<UserProfileRowProps, UserProfileRowState> { | 
					
						
							| 
									
										
										
										
											2021-04-15 20:21:06 +08:00
										 |  |  |   inputElem?: HTMLInputElement; | 
					
						
							| 
									
										
										
										
											2020-01-14 00:10:19 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   static defaultProps: Partial<UserProfileRowProps> = { | 
					
						
							|  |  |  |     value: '', | 
					
						
							|  |  |  |     locked: false, | 
					
						
							|  |  |  |     lockMessage: '', | 
					
						
							|  |  |  |     inputType: 'text', | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   state = { | 
					
						
							|  |  |  |     editing: false, | 
					
						
							|  |  |  |     value: this.props.value || '', | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   setInputElem = (elem: any) => { | 
					
						
							|  |  |  |     this.inputElem = elem; | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   onEditClick = () => { | 
					
						
							|  |  |  |     if (this.props.inputType === 'password') { | 
					
						
							|  |  |  |       // Reset value for password field
 | 
					
						
							|  |  |  |       this.setState({ editing: true, value: '' }, this.focusInput); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       this.setState({ editing: true }, this.focusInput); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   onCancelClick = () => { | 
					
						
							|  |  |  |     this.setState({ editing: false, value: this.props.value || '' }); | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-02 22:18:06 +08:00
										 |  |  |   onInputChange = (event: React.ChangeEvent<HTMLInputElement>, status?: LegacyInputStatus) => { | 
					
						
							|  |  |  |     if (status === LegacyInputStatus.Invalid) { | 
					
						
							| 
									
										
										
										
											2020-01-14 00:10:19 +08:00
										 |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     this.setState({ value: event.target.value }); | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-02 22:18:06 +08:00
										 |  |  |   onInputBlur = (event: React.FocusEvent<HTMLInputElement>, status?: LegacyInputStatus) => { | 
					
						
							|  |  |  |     if (status === LegacyInputStatus.Invalid) { | 
					
						
							| 
									
										
										
										
											2020-01-14 00:10:19 +08:00
										 |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     this.setState({ value: event.target.value }); | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   focusInput = () => { | 
					
						
							|  |  |  |     if (this.inputElem && this.inputElem.focus) { | 
					
						
							|  |  |  |       this.inputElem.focus(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   onSave = () => { | 
					
						
							|  |  |  |     if (this.props.onChange) { | 
					
						
							|  |  |  |       this.props.onChange(this.state.value); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   render() { | 
					
						
							|  |  |  |     const { label, locked, lockMessage, inputType } = this.props; | 
					
						
							|  |  |  |     const { value } = this.state; | 
					
						
							|  |  |  |     const labelClass = cx( | 
					
						
							|  |  |  |       'width-16', | 
					
						
							|  |  |  |       css`
 | 
					
						
							|  |  |  |         font-weight: 500; | 
					
						
							|  |  |  |       `
 | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     const editButtonContainerClass = cx('pull-right'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (locked) { | 
					
						
							|  |  |  |       return <LockedRow label={label} value={value} lockMessage={lockMessage} />; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return ( | 
					
						
							|  |  |  |       <tr> | 
					
						
							|  |  |  |         <td className={labelClass}>{label}</td> | 
					
						
							|  |  |  |         <td className="width-25" colSpan={2}> | 
					
						
							|  |  |  |           {this.state.editing ? ( | 
					
						
							| 
									
										
										
										
											2020-04-03 16:04:19 +08:00
										 |  |  |             <Input | 
					
						
							| 
									
										
										
										
											2020-01-14 00:10:19 +08:00
										 |  |  |               type={inputType} | 
					
						
							|  |  |  |               defaultValue={value} | 
					
						
							|  |  |  |               onBlur={this.onInputBlur} | 
					
						
							|  |  |  |               onChange={this.onInputChange} | 
					
						
							| 
									
										
										
										
											2020-04-01 23:36:08 +08:00
										 |  |  |               ref={this.setInputElem} | 
					
						
							| 
									
										
										
										
											2020-04-28 05:28:24 +08:00
										 |  |  |               width={30} | 
					
						
							| 
									
										
										
										
											2020-01-14 00:10:19 +08:00
										 |  |  |             /> | 
					
						
							|  |  |  |           ) : ( | 
					
						
							|  |  |  |             <span>{this.props.value}</span> | 
					
						
							|  |  |  |           )} | 
					
						
							|  |  |  |         </td> | 
					
						
							|  |  |  |         <td> | 
					
						
							|  |  |  |           <div className={editButtonContainerClass}> | 
					
						
							|  |  |  |             <ConfirmButton | 
					
						
							|  |  |  |               confirmText="Save" | 
					
						
							|  |  |  |               onClick={this.onEditClick} | 
					
						
							|  |  |  |               onConfirm={this.onSave} | 
					
						
							|  |  |  |               onCancel={this.onCancelClick} | 
					
						
							|  |  |  |             > | 
					
						
							|  |  |  |               Edit | 
					
						
							|  |  |  |             </ConfirmButton> | 
					
						
							|  |  |  |           </div> | 
					
						
							|  |  |  |         </td> | 
					
						
							|  |  |  |       </tr> | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | interface LockedRowProps { | 
					
						
							|  |  |  |   label: string; | 
					
						
							|  |  |  |   value?: any; | 
					
						
							|  |  |  |   lockMessage?: string; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export const LockedRow: FC<LockedRowProps> = ({ label, value, lockMessage }) => { | 
					
						
							|  |  |  |   const lockMessageClass = cx( | 
					
						
							|  |  |  |     'pull-right', | 
					
						
							|  |  |  |     css`
 | 
					
						
							|  |  |  |       font-style: italic; | 
					
						
							|  |  |  |       margin-right: 0.6rem; | 
					
						
							|  |  |  |     `
 | 
					
						
							|  |  |  |   ); | 
					
						
							|  |  |  |   const labelClass = cx( | 
					
						
							|  |  |  |     'width-16', | 
					
						
							|  |  |  |     css`
 | 
					
						
							|  |  |  |       font-weight: 500; | 
					
						
							|  |  |  |     `
 | 
					
						
							|  |  |  |   ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return ( | 
					
						
							|  |  |  |     <tr> | 
					
						
							|  |  |  |       <td className={labelClass}>{label}</td> | 
					
						
							|  |  |  |       <td className="width-25" colSpan={2}> | 
					
						
							|  |  |  |         {value} | 
					
						
							|  |  |  |       </td> | 
					
						
							|  |  |  |       <td> | 
					
						
							|  |  |  |         <span className={lockMessageClass}>{lockMessage}</span> | 
					
						
							|  |  |  |       </td> | 
					
						
							|  |  |  |     </tr> | 
					
						
							|  |  |  |   ); | 
					
						
							|  |  |  | }; |