Skip to content

Commit

Permalink
Login: Angular to React (grafana#18116)
Browse files Browse the repository at this point in the history
* Migrating login services

* Add user signup

* Remove lodash

* Remove media query and extarct LoginServices

* Add React LoginCtrl

* Handle location with Redux and start form validation

* Fix proposal

* Add basic validation

* Fix validation

* Remove state from controller

* Extract login forms

* Fix things up

* Add change password and LoginPage

* Add React page and route to it

* Make redux connection work

* Add validation for password change

* Change pws request

* Fix feedback

* Fix feedback

* LoginPage to FC

* Move onSkip to a method

* Experimenting with animations

* Make animations work

* Add input focus

* Fix focus problem and clean animation

* Working change password request

* Add routing with window.location instead of Redux

* Fix a bit of feedback

* Move config to LoginCtrl

* Make buttons same size

* Change way of validating

* Update changePassword and remove angular controller

* Remove some console.logs

* Split onChange

* Remove className

* Fix animation, onChange and remove config.loginError code

* Add loginError appEvent

* Make flex and add previosuly removed media query
  • Loading branch information
Tobias Skarhed authored Aug 13, 2019
1 parent 93ecf63 commit 91a911b
Show file tree
Hide file tree
Showing 11 changed files with 594 additions and 277 deletions.
135 changes: 135 additions & 0 deletions public/app/core/components/Login/ChangePassword.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import React, { PureComponent, SyntheticEvent, ChangeEvent } from 'react';
import { Tooltip } from '@grafana/ui';
import appEvents from 'app/core/app_events';

interface Props {
onSubmit: (pw: string) => void;
onSkip: Function;
focus?: boolean;
}

interface State {
newPassword: string;
confirmNew: string;
valid: boolean;
}

export class ChangePassword extends PureComponent<Props, State> {
private userInput: HTMLInputElement;
constructor(props: Props) {
super(props);
this.state = {
newPassword: '',
confirmNew: '',
valid: false,
};
}

componentDidUpdate(prevProps: Props) {
if (!prevProps.focus && this.props.focus) {
this.focus();
}
}

focus() {
this.userInput.focus();
}

onSubmit = (e: SyntheticEvent) => {
e.preventDefault();

const { newPassword, valid } = this.state;
if (valid) {
this.props.onSubmit(newPassword);
} else {
appEvents.emit('alert-warning', ['New passwords do not match', '']);
}
};

onNewPasswordChange = (e: ChangeEvent<HTMLInputElement>) => {
this.setState({
newPassword: e.target.value,
valid: this.validate('newPassword', e.target.value),
});
};

onConfirmPasswordChange = (e: ChangeEvent<HTMLInputElement>) => {
this.setState({
confirmNew: e.target.value,
valid: this.validate('confirmNew', e.target.value),
});
};

onSkip = (e: SyntheticEvent) => {
this.props.onSkip();
};

validate(changed: string, pw: string) {
if (changed === 'newPassword') {
return this.state.confirmNew === pw;
} else if (changed === 'confirmNew') {
return this.state.newPassword === pw;
}
return false;
}

render() {
return (
<div className="login-inner-box" id="change-password-view">
<div className="text-left login-change-password-info">
<h5>Change Password</h5>
Before you can get started with awesome dashboards we need you to make your account more secure by changing
your password.
<br />
You can change your password again later.
</div>
<form className="login-form-group gf-form-group">
<div className="login-form">
<input
type="password"
id="newPassword"
name="newPassword"
className="gf-form-input login-form-input"
required
placeholder="New password"
onChange={this.onNewPasswordChange}
ref={input => {
this.userInput = input;
}}
/>
</div>
<div className="login-form">
<input
type="password"
name="confirmNew"
className="gf-form-input login-form-input"
required
ng-model="command.confirmNew"
placeholder="Confirm new password"
onChange={this.onConfirmPasswordChange}
/>
</div>
<div className="login-button-group login-button-group--right text-right">
<Tooltip
placement="bottom"
content="If you skip you will be prompted to change password next time you login."
>
<a className="btn btn-link" onClick={this.onSkip}>
Skip
</a>
</Tooltip>

<button
type="submit"
className={`btn btn-large p-x-2 ${this.state.valid ? 'btn-primary' : 'btn-inverse'}`}
onClick={this.onSubmit}
disabled={!this.state.valid}
>
Save
</button>
</div>
</form>
</div>
);
}
}
162 changes: 162 additions & 0 deletions public/app/core/components/Login/LoginCtrl.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import React from 'react';
import config from 'app/core/config';

import { updateLocation } from 'app/core/actions';
import { connect } from 'react-redux';
import { StoreState } from 'app/types';
import { PureComponent } from 'react';
import { getBackendSrv } from '@grafana/runtime';
import { hot } from 'react-hot-loader';
import appEvents from 'app/core/app_events';

const isOauthEnabled = () => Object.keys(config.oauth).length > 0;

export interface FormModel {
user: string;
password: string;
email: string;
}
interface Props {
routeParams?: any;
updateLocation?: typeof updateLocation;
children: (props: {
isLoggingIn: boolean;
changePassword: (pw: string) => void;
isChangingPassword: boolean;
skipPasswordChange: Function;
login: (data: FormModel) => void;
disableLoginForm: boolean;
ldapEnabled: boolean;
authProxyEnabled: boolean;
disableUserSignUp: boolean;
isOauthEnabled: boolean;
loginHint: string;
passwordHint: string;
}) => JSX.Element;
}

interface State {
isLoggingIn: boolean;
isChangingPassword: boolean;
}

export class LoginCtrl extends PureComponent<Props, State> {
result: any = {};
constructor(props: Props) {
super(props);
this.state = {
isLoggingIn: false,
isChangingPassword: false,
};

if (config.loginError) {
appEvents.emit('alert-warning', ['Login Failed', config.loginError]);
}
}

changePassword = (password: string) => {
const pw = {
newPassword: password,
confirmNew: password,
oldPassword: 'admin',
};
getBackendSrv()
.put('/api/user/password', pw)
.then(() => {
this.toGrafana();
})
.catch((err: any) => console.log(err));
};

login = (formModel: FormModel) => {
this.setState({
isLoggingIn: true,
});

getBackendSrv()
.post('/login', formModel)
.then((result: any) => {
this.result = result;
if (formModel.password !== 'admin' || config.ldapEnabled || config.authProxyEnabled) {
this.toGrafana();
return;
} else {
this.changeView();
}
})
.catch(() => {
this.setState({
isLoggingIn: false,
});
});
};

changeView = () => {
this.setState({
isChangingPassword: true,
});
};

toGrafana = () => {
const params = this.props.routeParams;
// Use window.location.href to force page reload
if (params.redirect && params.redirect[0] === '/') {
window.location.href = config.appSubUrl + params.redirect;

// this.props.updateLocation({
// path: config.appSubUrl + params.redirect,
// });
} else if (this.result.redirectUrl) {
window.location.href = config.appSubUrl + params.redirect;

// this.props.updateLocation({
// path: this.result.redirectUrl,
// });
} else {
window.location.href = config.appSubUrl + '/';

// this.props.updateLocation({
// path: '/',
// });
}
};

render() {
const { children } = this.props;
const { isLoggingIn, isChangingPassword } = this.state;
const { login, toGrafana, changePassword } = this;
const { loginHint, passwordHint, disableLoginForm, ldapEnabled, authProxyEnabled, disableUserSignUp } = config;

return (
<>
{children({
isOauthEnabled: isOauthEnabled(),
loginHint,
passwordHint,
disableLoginForm,
ldapEnabled,
authProxyEnabled,
disableUserSignUp,
login,
isLoggingIn,
changePassword,
skipPasswordChange: toGrafana,
isChangingPassword,
})}
</>
);
}
}

export const mapStateToProps = (state: StoreState) => ({
routeParams: state.location.routeParams,
});

const mapDispatchToProps = { updateLocation };

export default hot(module)(
connect(
mapStateToProps,
mapDispatchToProps
)(LoginCtrl)
);
Loading

0 comments on commit 91a911b

Please sign in to comment.