mirror of
https://github.com/getredash/redash.git
synced 2026-05-09 21:02:27 -04:00
* Create React version for the EmailSettingsWarning * Migrate the Create User Page * Migrate UserProfile to React * Add /users/me to the routes (Percy ftw) * Fix UserShow test spec * Remove Error Messages component * Show invitation link if email server not setup (#3519) * return invite link to client if e-mail server is not set up * add a couple of tests to make sure invite links are only returned when neccessary * show invite link when e-mail is not configured * remove "an e-mail has been sent" when there's no e-mail configured * return invite_url in re-invites as well. Also refactor to reuse the code. * Use CreateUserDialog instead of Page * Render invite link on Resend Invitation click * Add email validation to DynamicForm * Fix EmailWarning position + update user list with user creation success * Fix console error on UserProfile * Redirect from /users/new + rename createUser -> showCreateUserDialog * Use alert instead of toastr for user creation errors * Remove logic from CreateUserDialog * CR * Use Promise.reject instead of throw to avoid console error
256 lines
6.7 KiB
JavaScript
256 lines
6.7 KiB
JavaScript
import React, { Fragment } from 'react';
|
|
import Alert from 'antd/lib/alert';
|
|
import Button from 'antd/lib/button';
|
|
import Form from 'antd/lib/form';
|
|
import Modal from 'antd/lib/modal';
|
|
import { User } from '@/services/user';
|
|
import { currentUser } from '@/services/auth';
|
|
import { absoluteUrl } from '@/services/utils';
|
|
import { UserProfile } from '../proptypes';
|
|
import { DynamicForm } from '../dynamic-form/DynamicForm';
|
|
import ChangePasswordDialog from './ChangePasswordDialog';
|
|
import InputWithCopy from '../InputWithCopy';
|
|
|
|
export default class UserEdit extends React.Component {
|
|
static propTypes = {
|
|
user: UserProfile.isRequired,
|
|
};
|
|
|
|
constructor(props) {
|
|
super(props);
|
|
this.state = {
|
|
user: this.props.user,
|
|
regeneratingApiKey: false,
|
|
sendingPasswordEmail: false,
|
|
resendingInvitation: false,
|
|
togglingUser: false,
|
|
};
|
|
}
|
|
|
|
changePassword = () => {
|
|
ChangePasswordDialog.showModal({ user: this.props.user });
|
|
};
|
|
|
|
sendPasswordReset = () => {
|
|
this.setState({ sendingPasswordEmail: true });
|
|
|
|
User.sendPasswordReset(this.state.user).then((passwordLink) => {
|
|
this.setState({ passwordLink });
|
|
}).finally(() => {
|
|
this.setState({ sendingPasswordEmail: false });
|
|
});
|
|
};
|
|
|
|
resendInvitation = () => {
|
|
this.setState({ resendingInvitation: true });
|
|
|
|
User.resendInvitation(this.state.user).then((passwordLink) => {
|
|
this.setState({ passwordLink });
|
|
}).finally(() => {
|
|
this.setState({ resendingInvitation: false });
|
|
});
|
|
};
|
|
|
|
regenerateApiKey = () => {
|
|
const doRegenerate = () => {
|
|
this.setState({ regeneratingApiKey: true });
|
|
User.regenerateApiKey(this.state.user).then((apiKey) => {
|
|
if (apiKey) {
|
|
const { user } = this.state;
|
|
this.setState({ user: { ...user, apiKey } });
|
|
}
|
|
}).finally(() => {
|
|
this.setState({ regeneratingApiKey: false });
|
|
});
|
|
};
|
|
|
|
Modal.confirm({
|
|
title: 'Regenerate API Key',
|
|
content: 'Are you sure you want to regenerate?',
|
|
okText: 'Regenerate',
|
|
onOk: doRegenerate,
|
|
maskClosable: true,
|
|
autoFocusButton: null,
|
|
});
|
|
};
|
|
|
|
toggleUser = () => {
|
|
const { user } = this.state;
|
|
const toggleUser = user.isDisabled ? User.enableUser : User.disableUser;
|
|
|
|
this.setState({ togglingUser: true });
|
|
toggleUser(user).then((data) => {
|
|
if (data) {
|
|
this.setState({ user: User.convertUserInfo(data.data) });
|
|
}
|
|
}).finally(() => {
|
|
this.setState({ togglingUser: false });
|
|
});
|
|
};
|
|
|
|
saveUser = (values, successCallback, errorCallback) => {
|
|
const data = {
|
|
id: this.props.user.id,
|
|
...values,
|
|
};
|
|
|
|
User.save(data, (user) => {
|
|
successCallback('Saved.');
|
|
this.setState({ user: User.convertUserInfo(user) });
|
|
}, (error = {}) => {
|
|
errorCallback(error.data && error.data.message || 'Failed saving.');
|
|
});
|
|
};
|
|
|
|
renderBasicInfoForm() {
|
|
const { user } = this.state;
|
|
const formFields = [
|
|
{
|
|
name: 'name',
|
|
title: 'Name',
|
|
type: 'text',
|
|
initialValue: user.name,
|
|
},
|
|
{
|
|
name: 'email',
|
|
title: 'Email',
|
|
type: 'email',
|
|
initialValue: user.email,
|
|
},
|
|
].map(field => ({ ...field, readOnly: user.isDisabled, required: true }));
|
|
|
|
return (
|
|
<DynamicForm
|
|
fields={formFields}
|
|
onSubmit={this.saveUser}
|
|
hideSubmitButton={user.isDisabled}
|
|
/>
|
|
);
|
|
}
|
|
|
|
renderApiKey() {
|
|
const { user, regeneratingApiKey } = this.state;
|
|
|
|
return (
|
|
<Form layout="vertical">
|
|
<hr />
|
|
<Form.Item label="API Key" className="m-b-10">
|
|
<InputWithCopy id="apiKey" value={user.apiKey} data-test="ApiKey" readOnly />
|
|
</Form.Item>
|
|
<Button
|
|
className="w-100"
|
|
onClick={this.regenerateApiKey}
|
|
loading={regeneratingApiKey}
|
|
data-test="RegenerateApiKey"
|
|
>
|
|
Regenerate
|
|
</Button>
|
|
</Form>
|
|
);
|
|
}
|
|
|
|
renderPasswordLinkAlert() {
|
|
const { user, passwordLink } = this.state;
|
|
|
|
return (
|
|
<Alert
|
|
message="Email not sent!"
|
|
description={(
|
|
<Fragment>
|
|
<p>
|
|
The mail server is not configured, please send the following link
|
|
to <b>{user.name}</b>:
|
|
</p>
|
|
<InputWithCopy value={absoluteUrl(passwordLink)} readOnly />
|
|
</Fragment>
|
|
)}
|
|
type="warning"
|
|
className="m-t-20"
|
|
afterClose={() => { this.setState({ passwordLink: null }); }}
|
|
closable
|
|
/>
|
|
);
|
|
}
|
|
|
|
renderResendInvitation() {
|
|
return (
|
|
<Button
|
|
className="w-100 m-t-10"
|
|
onClick={this.resendInvitation}
|
|
loading={this.state.resendingInvitation}
|
|
>
|
|
Resend Invitation
|
|
</Button>
|
|
);
|
|
}
|
|
|
|
renderSendPasswordReset() {
|
|
const { sendingPasswordEmail } = this.state;
|
|
|
|
return (
|
|
<Fragment>
|
|
<Button
|
|
className="w-100 m-t-10"
|
|
onClick={this.sendPasswordReset}
|
|
loading={sendingPasswordEmail}
|
|
>
|
|
Send Password Reset Email
|
|
</Button>
|
|
</Fragment>
|
|
);
|
|
}
|
|
|
|
rendertoggleUser() {
|
|
const { user, togglingUser } = this.state;
|
|
|
|
return user.isDisabled ? (
|
|
<Button className="w-100 m-t-10" type="primary" onClick={this.toggleUser} loading={togglingUser}>
|
|
Enable User
|
|
</Button>
|
|
) : (
|
|
<Button className="w-100 m-t-10" type="danger" onClick={this.toggleUser} loading={togglingUser}>
|
|
Disable User
|
|
</Button>
|
|
);
|
|
}
|
|
|
|
render() {
|
|
const { user, passwordLink } = this.state;
|
|
|
|
return (
|
|
<div className="col-md-4 col-md-offset-4">
|
|
<img
|
|
alt="Profile"
|
|
src={user.profileImageUrl}
|
|
className="profile__image"
|
|
width="40"
|
|
/>
|
|
<h3 className="profile__h3">{user.name}</h3>
|
|
<hr />
|
|
{this.renderBasicInfoForm()}
|
|
{!user.isDisabled && (
|
|
<Fragment>
|
|
{this.renderApiKey()}
|
|
<hr />
|
|
<h5>Password</h5>
|
|
{user.id === currentUser.id && (
|
|
<Button className="w-100 m-t-10" onClick={this.changePassword} data-test="ChangePassword">
|
|
Change Password
|
|
</Button>
|
|
)}
|
|
{(currentUser.isAdmin && user.id !== currentUser.id) && (
|
|
<Fragment>
|
|
{user.isInvitationPending ?
|
|
this.renderResendInvitation() : this.renderSendPasswordReset()}
|
|
{passwordLink && this.renderPasswordLinkAlert()}
|
|
</Fragment>
|
|
)}
|
|
</Fragment>
|
|
)}
|
|
<hr />
|
|
{currentUser.isAdmin && user.id !== currentUser.id && this.rendertoggleUser()}
|
|
</div>
|
|
);
|
|
}
|
|
}
|