mirror of
https://github.com/grafana/grafana.git
synced 2025-02-04 04:31:00 -06:00
Rewrite user profile edit to react (#17917)
* rewrite user profile edit to react (#17525) * disableLogin change, still need to fix tooltip * left out disable form for other auth * PR changes - wrapper to render, userId instead of bool, optional user in state, change provider child param order * moved directive to angular_wrappers * catch api error * finally * move user arg back to end- optional * optional type sig
This commit is contained in:
parent
50058de6aa
commit
e3e2cd82d7
@ -11,6 +11,7 @@ import { ColorPicker, SeriesColorPickerPopoverWithTheme, SecretFormField, DataLi
|
||||
import { FunctionEditor } from 'app/plugins/datasource/graphite/FunctionEditor';
|
||||
import { SearchField } from './components/search/SearchField';
|
||||
import { GraphContextMenu } from 'app/plugins/panel/graph/GraphContextMenu';
|
||||
import ReactProfileWrapper from 'app/features/profile/ReactProfileWrapper';
|
||||
|
||||
export function registerAngularDirectives() {
|
||||
react2AngularDirective('sidemenu', SideMenu, []);
|
||||
@ -87,4 +88,6 @@ export function registerAngularDirectives() {
|
||||
'suggestions',
|
||||
['onChange', { watchDepth: 'reference', wrapApply: true }],
|
||||
]);
|
||||
|
||||
react2AngularDirective('reactProfileWrapper', ReactProfileWrapper, []);
|
||||
}
|
||||
|
@ -1,12 +1,17 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { User } from 'app/types';
|
||||
|
||||
export interface UserAPI {
|
||||
changePassword: (ChangePassword: ChangePasswordFields) => void;
|
||||
changePassword: (changePassword: ChangePasswordFields) => void;
|
||||
updateUserProfile: (profile: ProfileUpdateFields) => void;
|
||||
loadUser: () => void;
|
||||
}
|
||||
|
||||
interface LoadingStates {
|
||||
changePassword: boolean;
|
||||
loadUser: boolean;
|
||||
updateUserProfile: boolean;
|
||||
}
|
||||
|
||||
export interface ChangePasswordFields {
|
||||
@ -15,11 +20,19 @@ export interface ChangePasswordFields {
|
||||
confirmNew: string;
|
||||
}
|
||||
|
||||
export interface ProfileUpdateFields {
|
||||
name: string;
|
||||
email: string;
|
||||
login: string;
|
||||
}
|
||||
|
||||
export interface Props {
|
||||
children: (api: UserAPI, states: LoadingStates) => JSX.Element;
|
||||
userId?: number; // passed, will load user on mount
|
||||
children: (api: UserAPI, states: LoadingStates, user?: User) => JSX.Element;
|
||||
}
|
||||
|
||||
export interface State {
|
||||
user?: User;
|
||||
loadingStates: LoadingStates;
|
||||
}
|
||||
|
||||
@ -27,24 +40,55 @@ export class UserProvider extends PureComponent<Props, State> {
|
||||
state: State = {
|
||||
loadingStates: {
|
||||
changePassword: false,
|
||||
loadUser: true,
|
||||
updateUserProfile: false,
|
||||
},
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
if (this.props.userId) {
|
||||
this.loadUser();
|
||||
}
|
||||
}
|
||||
|
||||
changePassword = async (payload: ChangePasswordFields) => {
|
||||
this.setState({ loadingStates: { ...this.state.loadingStates, changePassword: true } });
|
||||
await getBackendSrv().put('/api/user/password', payload);
|
||||
this.setState({ loadingStates: { ...this.state.loadingStates, changePassword: false } });
|
||||
};
|
||||
|
||||
loadUser = async () => {
|
||||
this.setState({
|
||||
loadingStates: { ...this.state.loadingStates, loadUser: true },
|
||||
});
|
||||
const user = await getBackendSrv().get('/api/user');
|
||||
this.setState({ user, loadingStates: { ...this.state.loadingStates, loadUser: Object.keys(user).length === 0 } });
|
||||
};
|
||||
|
||||
updateUserProfile = async (payload: ProfileUpdateFields) => {
|
||||
this.setState({ loadingStates: { ...this.state.loadingStates, updateUserProfile: true } });
|
||||
await getBackendSrv()
|
||||
.put('/api/user', payload)
|
||||
.then(() => {
|
||||
this.loadUser();
|
||||
})
|
||||
.catch(e => console.log(e))
|
||||
.finally(() => {
|
||||
this.setState({ loadingStates: { ...this.state.loadingStates, updateUserProfile: false } });
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { children } = this.props;
|
||||
const { loadingStates } = this.state;
|
||||
const { loadingStates, user } = this.state;
|
||||
|
||||
const api = {
|
||||
changePassword: this.changePassword,
|
||||
loadUser: this.loadUser,
|
||||
updateUserProfile: this.updateUserProfile,
|
||||
};
|
||||
|
||||
return <>{children(api, loadingStates)}</>;
|
||||
return <>{children(api, loadingStates, user)}</>;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,15 +16,11 @@ export interface State {
|
||||
}
|
||||
|
||||
export class ChangePasswordForm extends PureComponent<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
oldPassword: '',
|
||||
newPassword: '',
|
||||
confirmNew: '',
|
||||
};
|
||||
}
|
||||
state: State = {
|
||||
oldPassword: '',
|
||||
newPassword: '',
|
||||
confirmNew: '',
|
||||
};
|
||||
|
||||
onOldPasswordChange = (oldPassword: string) => {
|
||||
this.setState({ oldPassword });
|
||||
|
@ -1,4 +0,0 @@
|
||||
import { react2AngularDirective } from 'app/core/utils/react2angular';
|
||||
import { SharedPreferences } from 'app/core/components/SharedPreferences/SharedPreferences';
|
||||
|
||||
react2AngularDirective('prefsControl', SharedPreferences, ['resourceUri']);
|
@ -3,7 +3,6 @@ import { coreModule, NavModelSrv } from 'app/core/core';
|
||||
import { dateTime } from '@grafana/data';
|
||||
import { UserSession } from 'app/types';
|
||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
||||
import { ILocationService } from 'angular';
|
||||
|
||||
export class ProfileCtrl {
|
||||
user: any;
|
||||
@ -18,26 +17,13 @@ export class ProfileCtrl {
|
||||
navModel: any;
|
||||
|
||||
/** @ngInject */
|
||||
constructor(
|
||||
private backendSrv: BackendSrv,
|
||||
private contextSrv: any,
|
||||
private $location: ILocationService,
|
||||
navModelSrv: NavModelSrv
|
||||
) {
|
||||
this.getUser();
|
||||
constructor(private backendSrv: BackendSrv, navModelSrv: NavModelSrv) {
|
||||
this.getUserSessions();
|
||||
this.getUserTeams();
|
||||
this.getUserOrgs();
|
||||
this.navModel = navModelSrv.getNav('profile', 'profile-settings', 0);
|
||||
}
|
||||
|
||||
getUser() {
|
||||
this.backendSrv.get('/api/user').then((user: any) => {
|
||||
this.user = user;
|
||||
this.user.theme = user.theme || 'dark';
|
||||
});
|
||||
}
|
||||
|
||||
getUserSessions() {
|
||||
this.backendSrv.get('/api/user/auth-tokens').then((sessions: UserSession[]) => {
|
||||
sessions.reverse();
|
||||
@ -103,19 +89,6 @@ export class ProfileCtrl {
|
||||
window.location.href = config.appSubUrl + '/profile';
|
||||
});
|
||||
}
|
||||
|
||||
update() {
|
||||
if (!this.userForm.$valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.backendSrv.put('/api/user/', this.user).then(() => {
|
||||
this.contextSrv.user.name = this.user.name || this.user.login;
|
||||
if (this.oldTheme !== this.user.theme) {
|
||||
window.location.href = config.appSubUrl + this.$location.path();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
coreModule.controller('ProfileCtrl', ProfileCtrl);
|
||||
|
26
public/app/features/profile/ReactProfileWrapper.tsx
Normal file
26
public/app/features/profile/ReactProfileWrapper.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import { UserProvider } from 'app/core/utils/UserProvider';
|
||||
import { UserProfileEditForm } from './UserProfileEditForm';
|
||||
import { SharedPreferences } from 'app/core/components/SharedPreferences/SharedPreferences';
|
||||
import { config } from '@grafana/runtime';
|
||||
|
||||
export const ReactProfileWrapper = () => (
|
||||
<UserProvider userId={config.bootData.user.id}>
|
||||
{(api, states, user) => {
|
||||
return (
|
||||
<>
|
||||
{!states.loadUser && (
|
||||
<UserProfileEditForm
|
||||
updateProfile={api.updateUserProfile}
|
||||
isSavingUser={states.updateUserProfile}
|
||||
user={user}
|
||||
/>
|
||||
)}
|
||||
<SharedPreferences resourceUri="user" />
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</UserProvider>
|
||||
);
|
||||
|
||||
export default ReactProfileWrapper;
|
105
public/app/features/profile/UserProfileEditForm.tsx
Normal file
105
public/app/features/profile/UserProfileEditForm.tsx
Normal file
@ -0,0 +1,105 @@
|
||||
import React, { PureComponent, ChangeEvent, MouseEvent } from 'react';
|
||||
import { Button, FormLabel, Input, Tooltip } from '@grafana/ui';
|
||||
import { User } from 'app/types';
|
||||
import config from 'app/core/config';
|
||||
import { ProfileUpdateFields } from 'app/core/utils/UserProvider';
|
||||
|
||||
export interface Props {
|
||||
user: User;
|
||||
isSavingUser: boolean;
|
||||
updateProfile: (payload: ProfileUpdateFields) => void;
|
||||
}
|
||||
|
||||
export interface State {
|
||||
name: string;
|
||||
email: string;
|
||||
login: string;
|
||||
}
|
||||
|
||||
export class UserProfileEditForm extends PureComponent<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
const {
|
||||
user: { name, email, login },
|
||||
} = this.props;
|
||||
|
||||
this.state = {
|
||||
name,
|
||||
email,
|
||||
login,
|
||||
};
|
||||
}
|
||||
|
||||
onNameChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({ name: event.target.value });
|
||||
};
|
||||
|
||||
onEmailChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({ email: event.target.value });
|
||||
};
|
||||
|
||||
onLoginChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({ login: event.target.value });
|
||||
};
|
||||
|
||||
onSubmitProfileUpdate = (event: MouseEvent<HTMLInputElement>) => {
|
||||
event.preventDefault();
|
||||
this.props.updateProfile({ ...this.state });
|
||||
};
|
||||
|
||||
render() {
|
||||
const { name, email, login } = this.state;
|
||||
const { isSavingUser } = this.props;
|
||||
const { disableLoginForm } = config;
|
||||
|
||||
return (
|
||||
<>
|
||||
<h3 className="page-sub-heading">Edit Profile</h3>
|
||||
<form name="userForm" className="gf-form-group">
|
||||
<div className="gf-form max-width-30">
|
||||
<FormLabel className="width-8">Name</FormLabel>
|
||||
<Input className="gf-form-input max-width-22" type="text" onChange={this.onNameChange} value={name} />
|
||||
</div>
|
||||
<div className="gf-form max-width-30">
|
||||
<FormLabel className="width-8">Email</FormLabel>
|
||||
<Input
|
||||
className="gf-form-input max-width-22"
|
||||
type="text"
|
||||
onChange={this.onEmailChange}
|
||||
value={email}
|
||||
disabled={disableLoginForm}
|
||||
/>
|
||||
{disableLoginForm && (
|
||||
<Tooltip content="Login Details Locked - managed in another system.">
|
||||
<i className="fa fa-lock gf-form-icon--right-absolute" />
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
<div className="gf-form max-width-30">
|
||||
<FormLabel className="width-8">Username</FormLabel>
|
||||
<Input
|
||||
className="gf-form-input max-width-22"
|
||||
type="text"
|
||||
onChange={this.onLoginChange}
|
||||
value={login}
|
||||
disabled={disableLoginForm}
|
||||
/>
|
||||
{disableLoginForm && (
|
||||
<Tooltip content="Login Details Locked - managed in another system.">
|
||||
<i className="fa fa-lock gf-form-icon--right-absolute" />
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
<div className="gf-form-button-row">
|
||||
<Button variant="primary" onClick={this.onSubmitProfileUpdate} disabled={isSavingUser}>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default UserProfileEditForm;
|
@ -1,2 +1 @@
|
||||
import './ProfileCtrl';
|
||||
import './PrefControlCtrl';
|
||||
|
@ -1,49 +1,7 @@
|
||||
<page-header model="ctrl.navModel"></page-header>
|
||||
|
||||
<div class="page-container page-body">
|
||||
<h3 class="page-sub-heading">User Profile</h3>
|
||||
|
||||
<form name="ctrl.userForm" class="gf-form-group">
|
||||
<div class="gf-form max-width-30">
|
||||
<span class="gf-form-label width-8">Name</span>
|
||||
<input class="gf-form-input max-width-22" type="text" required ng-model="ctrl.user.name" />
|
||||
</div>
|
||||
<div class="gf-form max-width-30">
|
||||
<span class="gf-form-label width-8">Email</span>
|
||||
<input
|
||||
class="gf-form-input max-width-22"
|
||||
type="email"
|
||||
ng-readonly="ctrl.readonlyLoginFields"
|
||||
required
|
||||
ng-model="ctrl.user.email"
|
||||
/>
|
||||
<i
|
||||
ng-if="ctrl.readonlyLoginFields"
|
||||
class="fa fa-lock gf-form-icon--right-absolute"
|
||||
bs-tooltip="'Login Details Locked - managed in another system.'"
|
||||
></i>
|
||||
</div>
|
||||
<div class="gf-form max-width-30">
|
||||
<span class="gf-form-label width-8">Username</span>
|
||||
<input
|
||||
class="gf-form-input max-width-22"
|
||||
type="text"
|
||||
ng-readonly="ctrl.readonlyLoginFields"
|
||||
required
|
||||
ng-model="ctrl.user.login"
|
||||
/>
|
||||
<i
|
||||
ng-if="ctrl.readonlyLoginFields"
|
||||
class="fa fa-lock gf-form-icon--right-absolute"
|
||||
bs-tooltip="'Login Details Locked - managed in another system.'"
|
||||
></i>
|
||||
</div>
|
||||
<div class="gf-form-button-row">
|
||||
<button type="submit" class="btn btn-primary" ng-click="ctrl.update()">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<prefs-control resource-uri="'user'"></prefs-control>
|
||||
<react-profile-wrapper></react-profile-wrapper>
|
||||
|
||||
<h3 class="page-heading" ng-show="ctrl.showTeamsList">Teams</h3>
|
||||
<div class="gf-form-group" ng-show="ctrl.showTeamsList">
|
||||
|
@ -67,6 +67,8 @@ describe('Functions', () => {
|
||||
label: '',
|
||||
avatarUrl: '',
|
||||
login: '',
|
||||
name: '',
|
||||
email: '',
|
||||
};
|
||||
|
||||
instance.onAddUserToTeam();
|
||||
|
@ -16,6 +16,8 @@ export interface User {
|
||||
label: string;
|
||||
avatarUrl: string;
|
||||
login: string;
|
||||
email: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface Invitee {
|
||||
|
Loading…
Reference in New Issue
Block a user