mirror of
https://github.com/grafana/grafana.git
synced 2025-01-16 11:42:35 -06:00
ChangePassword: Rewrite change password page to react (#17811)
* ChangePassword to React, created PasswordInput component, attempting UserProvider wrapper component, adding flex to btn row * UserAPI interface, force classes on PasswordInput, remove api call from ChangePassword * refactored out form * clean up * removed unnecessary bind, added loading state and loading component to change password form * should be OR * arrow funcs instead of binds, inline-block instead of flex, isSaving instead of isLoading, disabled button instead of spinner * inline-flex on the react btn * change state instatiatiation
This commit is contained in:
parent
b1126cb0ed
commit
874b8abcc0
@ -120,7 +120,7 @@ const getButtonStyles = (theme: GrafanaTheme, size: ButtonSize, variant: ButtonV
|
||||
return {
|
||||
button: css`
|
||||
label: button;
|
||||
display: flex;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
font-weight: ${fontWeight};
|
||||
font-size: ${fontSize};
|
||||
|
20
public/app/core/components/PasswordInput/PasswordInput.tsx
Normal file
20
public/app/core/components/PasswordInput/PasswordInput.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import React, { ChangeEvent, forwardRef } from 'react';
|
||||
import { Input, FormLabel } from '@grafana/ui';
|
||||
|
||||
export interface Props {
|
||||
label: string;
|
||||
value: string | undefined;
|
||||
onChange: (value: string) => void;
|
||||
}
|
||||
|
||||
export const PasswordInput = forwardRef<HTMLInputElement, Props>((props, ref) => (
|
||||
<>
|
||||
<FormLabel className="width-8">{props.label}</FormLabel>
|
||||
<Input
|
||||
className="gf-form-input max-width-22"
|
||||
type="password"
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => props.onChange(event.target.value)}
|
||||
value={props.value}
|
||||
/>
|
||||
</>
|
||||
));
|
51
public/app/core/utils/UserProvider.tsx
Normal file
51
public/app/core/utils/UserProvider.tsx
Normal file
@ -0,0 +1,51 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
|
||||
export interface UserAPI {
|
||||
changePassword: (ChangePassword: ChangePasswordFields) => void;
|
||||
}
|
||||
|
||||
interface LoadingStates {
|
||||
changePassword: boolean;
|
||||
}
|
||||
|
||||
export interface ChangePasswordFields {
|
||||
oldPassword: string;
|
||||
newPassword: string;
|
||||
confirmNew: string;
|
||||
}
|
||||
|
||||
export interface Props {
|
||||
children: (api: UserAPI, states: LoadingStates) => JSX.Element;
|
||||
}
|
||||
|
||||
export interface State {
|
||||
loadingStates: LoadingStates;
|
||||
}
|
||||
|
||||
export class UserProvider extends PureComponent<Props, State> {
|
||||
state: State = {
|
||||
loadingStates: {
|
||||
changePassword: false,
|
||||
},
|
||||
};
|
||||
|
||||
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 } });
|
||||
};
|
||||
|
||||
render() {
|
||||
const { children } = this.props;
|
||||
const { loadingStates } = this.state;
|
||||
|
||||
const api = {
|
||||
changePassword: this.changePassword,
|
||||
};
|
||||
|
||||
return <>{children(api, loadingStates)}</>;
|
||||
}
|
||||
}
|
||||
|
||||
export default UserProvider;
|
@ -1,29 +0,0 @@
|
||||
import angular from 'angular';
|
||||
import config from 'app/core/config';
|
||||
|
||||
export class ChangePasswordCtrl {
|
||||
/** @ngInject */
|
||||
constructor($scope, backendSrv, $location, navModelSrv) {
|
||||
$scope.command = {};
|
||||
$scope.authProxyEnabled = config.authProxyEnabled;
|
||||
$scope.ldapEnabled = config.ldapEnabled;
|
||||
$scope.navModel = navModelSrv.getNav('profile', 'change-password', 0);
|
||||
|
||||
$scope.changePassword = () => {
|
||||
if (!$scope.userForm.$valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($scope.command.newPassword !== $scope.command.confirmNew) {
|
||||
$scope.appEvent('alert-warning', ['New passwords do not match', '']);
|
||||
return;
|
||||
}
|
||||
|
||||
backendSrv.put('/api/user/password', $scope.command).then(() => {
|
||||
$location.path('profile');
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
angular.module('grafana.controllers').controller('ChangePasswordCtrl', ChangePasswordCtrl);
|
79
public/app/features/profile/ChangePasswordForm.tsx
Normal file
79
public/app/features/profile/ChangePasswordForm.tsx
Normal file
@ -0,0 +1,79 @@
|
||||
import React, { PureComponent, MouseEvent } from 'react';
|
||||
import config from 'app/core/config';
|
||||
import { Button, LinkButton } from '@grafana/ui';
|
||||
import { ChangePasswordFields } from 'app/core/utils/UserProvider';
|
||||
import { PasswordInput } from 'app/core/components/PasswordInput/PasswordInput';
|
||||
|
||||
export interface Props {
|
||||
isSaving: boolean;
|
||||
onChangePassword: (payload: ChangePasswordFields) => void;
|
||||
}
|
||||
|
||||
export interface State {
|
||||
oldPassword: string;
|
||||
newPassword: string;
|
||||
confirmNew: string;
|
||||
}
|
||||
|
||||
export class ChangePasswordForm extends PureComponent<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
oldPassword: '',
|
||||
newPassword: '',
|
||||
confirmNew: '',
|
||||
};
|
||||
}
|
||||
|
||||
onOldPasswordChange = (oldPassword: string) => {
|
||||
this.setState({ oldPassword });
|
||||
};
|
||||
|
||||
onNewPasswordChange = (newPassword: string) => {
|
||||
this.setState({ newPassword });
|
||||
};
|
||||
|
||||
onConfirmPasswordChange = (confirmNew: string) => {
|
||||
this.setState({ confirmNew });
|
||||
};
|
||||
|
||||
onSubmitChangePassword = (event: MouseEvent<HTMLInputElement>) => {
|
||||
event.preventDefault();
|
||||
this.props.onChangePassword({ ...this.state });
|
||||
};
|
||||
|
||||
render() {
|
||||
const { oldPassword, newPassword, confirmNew } = this.state;
|
||||
const { isSaving } = this.props;
|
||||
const { ldapEnabled, authProxyEnabled } = config;
|
||||
|
||||
if (ldapEnabled || authProxyEnabled) {
|
||||
return <p>You cannot change password when ldap or auth proxy authentication is enabled.</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<form name="userForm" className="gf-form-group">
|
||||
<div className="gf-form max-width-30">
|
||||
<PasswordInput label="Old Password" onChange={this.onOldPasswordChange} value={oldPassword} />
|
||||
</div>
|
||||
<div className="gf-form max-width-30">
|
||||
<PasswordInput label="New Password" onChange={this.onNewPasswordChange} value={newPassword} />
|
||||
</div>
|
||||
<div className="gf-form max-width-30">
|
||||
<PasswordInput label="Confirm Password" onChange={this.onConfirmPasswordChange} value={confirmNew} />
|
||||
</div>
|
||||
<div className="gf-form-button-row">
|
||||
<Button variant="primary" onClick={this.onSubmitChangePassword} disabled={isSaving}>
|
||||
Change Password
|
||||
</Button>
|
||||
<LinkButton variant="transparent" href={`${config.appSubUrl}/profile`}>
|
||||
Cancel
|
||||
</LinkButton>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ChangePasswordForm;
|
46
public/app/features/profile/ChangePasswordPage.tsx
Normal file
46
public/app/features/profile/ChangePasswordPage.tsx
Normal file
@ -0,0 +1,46 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { hot } from 'react-hot-loader';
|
||||
import { connect } from 'react-redux';
|
||||
import { StoreState } from 'app/types';
|
||||
import { NavModel } from '@grafana/data';
|
||||
import { getNavModel } from 'app/core/selectors/navModel';
|
||||
import { UserProvider } from 'app/core/utils/UserProvider';
|
||||
import Page from 'app/core/components/Page/Page';
|
||||
import { ChangePasswordForm } from './ChangePasswordForm';
|
||||
|
||||
export interface Props {
|
||||
navModel: NavModel;
|
||||
}
|
||||
|
||||
export class ChangePasswordPage extends PureComponent<Props> {
|
||||
render() {
|
||||
const { navModel } = this.props;
|
||||
return (
|
||||
<Page navModel={navModel}>
|
||||
<UserProvider>
|
||||
{({ changePassword }, states) => (
|
||||
<Page.Contents>
|
||||
<h3 className="page-sub-heading">Change Your Password</h3>
|
||||
<ChangePasswordForm onChangePassword={changePassword} isSaving={states.changePassword} />
|
||||
</Page.Contents>
|
||||
)}
|
||||
</UserProvider>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps(state: StoreState) {
|
||||
return {
|
||||
navModel: getNavModel(state.navIndex, `change-password`),
|
||||
};
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {};
|
||||
|
||||
export default hot(module)(
|
||||
connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(ChangePasswordPage)
|
||||
);
|
@ -1,3 +1,2 @@
|
||||
import './ProfileCtrl';
|
||||
import './ChangePasswordCtrl';
|
||||
import './PrefControlCtrl';
|
||||
|
@ -1,35 +0,0 @@
|
||||
<page-header model="navModel"></page-header>
|
||||
|
||||
<div class="page-container page-body">
|
||||
<h3 class="page-sub-heading">
|
||||
Change your password
|
||||
</h3>
|
||||
|
||||
<div ng-if="ldapEnabled || authProxyEnabled">
|
||||
You cannot change password when ldap or auth proxy authentication is enabled.
|
||||
</div>
|
||||
|
||||
<form name="userForm" class="gf-form-group" ng-hide="ldapEnabled || authProxyEnabled">
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-10">Old Password</span>
|
||||
<input class="gf-form-input max-width-21" type="password" required ng-model="command.oldPassword">
|
||||
</div>
|
||||
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-10">New Password</span>
|
||||
<input class="gf-form-input max-width-21" type="password" required ng-minlength="4" ng-model="command.newPassword">
|
||||
</div>
|
||||
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-10">Confirm Password</span>
|
||||
<input class="gf-form-input max-width-21" type="password" required ng-minlength="4" ng-model="command.confirmNew">
|
||||
</div>
|
||||
|
||||
<div class="gf-form-button-row">
|
||||
<button type="submit" class="btn btn-primary" ng-click="changePassword()">Change Password</button>
|
||||
<a class="btn-text" href="profile">Cancel</a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
@ -3,6 +3,7 @@ import './ReactContainer';
|
||||
import { applyRouteRegistrationHandlers } from './registry';
|
||||
|
||||
// Pages
|
||||
import ChangePasswordPage from 'app/features/profile/ChangePasswordPage';
|
||||
import ServerStats from 'app/features/admin/ServerStats';
|
||||
import AlertRuleList from 'app/features/alerting/AlertRuleList';
|
||||
import TeamPages from 'app/features/teams/TeamPages';
|
||||
@ -230,8 +231,10 @@ export function setupAngularRoutes($routeProvider: route.IRouteProvider, $locati
|
||||
controllerAs: 'ctrl',
|
||||
})
|
||||
.when('/profile/password', {
|
||||
templateUrl: 'public/app/features/profile/partials/change_password.html',
|
||||
controller: 'ChangePasswordCtrl',
|
||||
template: '<react-container />',
|
||||
resolve: {
|
||||
component: () => ChangePasswordPage,
|
||||
},
|
||||
})
|
||||
.when('/profile/select-org', {
|
||||
templateUrl: 'public/app/features/org/partials/select_org.html',
|
||||
|
Loading…
Reference in New Issue
Block a user