mirror of
https://github.com/grafana/grafana.git
synced 2024-11-26 02:40:26 -06:00
Login: Angular to React (#18116)
* 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
This commit is contained in:
parent
93ecf63e70
commit
91a911b64e
135
public/app/core/components/Login/ChangePassword.tsx
Normal file
135
public/app/core/components/Login/ChangePassword.tsx
Normal file
@ -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
public/app/core/components/Login/LoginCtrl.tsx
Normal file
162
public/app/core/components/Login/LoginCtrl.tsx
Normal file
@ -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)
|
||||
);
|
120
public/app/core/components/Login/LoginForm.tsx
Normal file
120
public/app/core/components/Login/LoginForm.tsx
Normal file
@ -0,0 +1,120 @@
|
||||
import React, { PureComponent, SyntheticEvent, ChangeEvent } from 'react';
|
||||
import { FormModel } from './LoginCtrl';
|
||||
|
||||
interface Props {
|
||||
displayForgotPassword: boolean;
|
||||
onChange?: (valid: boolean) => void;
|
||||
onSubmit: (data: FormModel) => void;
|
||||
isLoggingIn: boolean;
|
||||
passwordHint: string;
|
||||
loginHint: string;
|
||||
}
|
||||
|
||||
interface State {
|
||||
user: string;
|
||||
password: string;
|
||||
email: string;
|
||||
valid: boolean;
|
||||
}
|
||||
|
||||
export class LoginForm extends PureComponent<Props, State> {
|
||||
private userInput: HTMLInputElement;
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
user: '',
|
||||
password: '',
|
||||
email: '',
|
||||
valid: false,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.userInput.focus();
|
||||
}
|
||||
onSubmit = (e: SyntheticEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
const { user, password, email } = this.state;
|
||||
if (this.state.valid) {
|
||||
this.props.onSubmit({ user, password, email });
|
||||
}
|
||||
};
|
||||
|
||||
onChangePassword = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({
|
||||
password: e.target.value,
|
||||
valid: this.validate(this.state.user, e.target.value),
|
||||
});
|
||||
};
|
||||
|
||||
onChangeUsername = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({
|
||||
user: e.target.value,
|
||||
valid: this.validate(e.target.value, this.state.password),
|
||||
});
|
||||
};
|
||||
|
||||
validate(user: string, password: string) {
|
||||
return user.length > 0 && password.length > 0;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<form name="loginForm" className="login-form-group gf-form-group">
|
||||
<div className="login-form">
|
||||
<input
|
||||
ref={input => {
|
||||
this.userInput = input;
|
||||
}}
|
||||
type="text"
|
||||
name="user"
|
||||
className="gf-form-input login-form-input"
|
||||
required
|
||||
placeholder={this.props.loginHint}
|
||||
aria-label="Username input field"
|
||||
onChange={this.onChangeUsername}
|
||||
/>
|
||||
</div>
|
||||
<div className="login-form">
|
||||
<input
|
||||
type="password"
|
||||
name="password"
|
||||
className="gf-form-input login-form-input"
|
||||
required
|
||||
ng-model="formModel.password"
|
||||
id="inputPassword"
|
||||
placeholder={this.props.passwordHint}
|
||||
aria-label="Password input field"
|
||||
onChange={this.onChangePassword}
|
||||
/>
|
||||
</div>
|
||||
<div className="login-button-group">
|
||||
{!this.props.isLoggingIn ? (
|
||||
<button
|
||||
type="submit"
|
||||
aria-label="Login button"
|
||||
className={`btn btn-large p-x-2 ${this.state.valid ? 'btn-primary' : 'btn-inverse'}`}
|
||||
onClick={this.onSubmit}
|
||||
disabled={!this.state.valid}
|
||||
>
|
||||
Log In
|
||||
</button>
|
||||
) : (
|
||||
<button type="submit" className="btn btn-large p-x-2 btn-inverse btn-loading">
|
||||
Logging In<span>.</span>
|
||||
<span>.</span>
|
||||
<span>.</span>
|
||||
</button>
|
||||
)}
|
||||
|
||||
{this.props.displayForgotPassword ? (
|
||||
<div className="small login-button-forgot-password">
|
||||
<a href="user/password/send-reset-email">Forgot your password?</a>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
}
|
81
public/app/core/components/Login/LoginPage.tsx
Normal file
81
public/app/core/components/Login/LoginPage.tsx
Normal file
@ -0,0 +1,81 @@
|
||||
import React, { FC } from 'react';
|
||||
import { UserSignup } from './UserSignup';
|
||||
import { LoginServiceButtons } from './LoginServiceButtons';
|
||||
import LoginCtrl from './LoginCtrl';
|
||||
import { LoginForm } from './LoginForm';
|
||||
import { ChangePassword } from './ChangePassword';
|
||||
import { CSSTransition } from 'react-transition-group';
|
||||
|
||||
export const LoginPage: FC = () => {
|
||||
return (
|
||||
<div className="login container">
|
||||
<div className="login-content">
|
||||
<div className="login-branding">
|
||||
<img className="logo-icon" src="public/img/grafana_icon.svg" alt="Grafana" />
|
||||
<div className="logo-wordmark" />
|
||||
</div>
|
||||
<LoginCtrl>
|
||||
{({
|
||||
loginHint,
|
||||
passwordHint,
|
||||
isOauthEnabled,
|
||||
ldapEnabled,
|
||||
authProxyEnabled,
|
||||
disableLoginForm,
|
||||
disableUserSignUp,
|
||||
login,
|
||||
isLoggingIn,
|
||||
changePassword,
|
||||
skipPasswordChange,
|
||||
isChangingPassword,
|
||||
}) => (
|
||||
<div className="login-outer-box">
|
||||
<div className={`login-inner-box ${isChangingPassword ? 'hidden' : ''}`} id="login-view">
|
||||
{!disableLoginForm ? (
|
||||
<LoginForm
|
||||
displayForgotPassword={!(ldapEnabled || authProxyEnabled)}
|
||||
onSubmit={login}
|
||||
loginHint={loginHint}
|
||||
passwordHint={passwordHint}
|
||||
isLoggingIn={isLoggingIn}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{isOauthEnabled ? (
|
||||
<>
|
||||
<div className="text-center login-divider">
|
||||
<div>
|
||||
<div className="login-divider-line" />
|
||||
</div>
|
||||
<div>
|
||||
<span className="login-divider-text">{disableLoginForm ? null : <span>or</span>}</span>
|
||||
</div>
|
||||
<div>
|
||||
<div className="login-divider-line" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="clearfix" />
|
||||
|
||||
<LoginServiceButtons />
|
||||
</>
|
||||
) : null}
|
||||
{!disableUserSignUp ? <UserSignup /> : null}
|
||||
</div>
|
||||
<CSSTransition
|
||||
appear={true}
|
||||
mountOnEnter={true}
|
||||
in={isChangingPassword}
|
||||
timeout={250}
|
||||
classNames="login-inner-box"
|
||||
>
|
||||
<ChangePassword onSubmit={changePassword} onSkip={skipPasswordChange} focus={isChangingPassword} />
|
||||
</CSSTransition>
|
||||
</div>
|
||||
)}
|
||||
</LoginCtrl>
|
||||
|
||||
<div className="clearfix" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
67
public/app/core/components/Login/LoginServiceButtons.tsx
Normal file
67
public/app/core/components/Login/LoginServiceButtons.tsx
Normal file
@ -0,0 +1,67 @@
|
||||
import React from 'react';
|
||||
import config from 'app/core/config';
|
||||
|
||||
const loginServices: () => LoginServices = () => ({
|
||||
saml: {
|
||||
enabled: config.samlEnabled,
|
||||
name: 'SAML',
|
||||
className: 'github',
|
||||
icon: 'key',
|
||||
},
|
||||
google: {
|
||||
enabled: config.oauth.google,
|
||||
name: 'Google',
|
||||
},
|
||||
github: {
|
||||
enabled: config.oauth.github,
|
||||
name: 'GitHub',
|
||||
},
|
||||
gitlab: {
|
||||
enabled: config.oauth.gitlab,
|
||||
name: 'GitLab',
|
||||
},
|
||||
grafanacom: {
|
||||
enabled: config.oauth.grafana_com,
|
||||
name: 'Grafana.com',
|
||||
hrefName: 'grafana_com',
|
||||
icon: 'grafana_com',
|
||||
},
|
||||
oauth: {
|
||||
enabled: config.oauth.generic_oauth,
|
||||
name: 'OAuth',
|
||||
icon: 'sign-in',
|
||||
hrefName: 'generic_oauth',
|
||||
},
|
||||
});
|
||||
|
||||
export interface LoginService {
|
||||
enabled: boolean;
|
||||
name: string;
|
||||
hrefName?: string;
|
||||
icon?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export interface LoginServices {
|
||||
[key: string]: LoginService;
|
||||
}
|
||||
|
||||
export const LoginServiceButtons = () => {
|
||||
const keyNames = Object.keys(loginServices());
|
||||
const serviceElements = keyNames.map(key => {
|
||||
const service: LoginService = loginServices()[key];
|
||||
return service.enabled ? (
|
||||
<a
|
||||
key={key}
|
||||
className={`btn btn-medium btn-service btn-service--${service.className || key} login-btn`}
|
||||
href={`login/${service.hrefName ? service.hrefName : key}`}
|
||||
target="_self"
|
||||
>
|
||||
<i className={`btn-service-icon fa fa-${service.icon ? service.icon : key}`} />
|
||||
Sign in with {service.name}
|
||||
</a>
|
||||
) : null;
|
||||
});
|
||||
|
||||
return <div className="login-oauth text-center">{serviceElements}</div>;
|
||||
};
|
12
public/app/core/components/Login/UserSignup.tsx
Normal file
12
public/app/core/components/Login/UserSignup.tsx
Normal file
@ -0,0 +1,12 @@
|
||||
import React, { FC } from 'react';
|
||||
|
||||
export const UserSignup: FC<{}> = () => {
|
||||
return (
|
||||
<div className="login-signup-box">
|
||||
<div className="login-signup-title p-r-1">New to Grafana?</div>
|
||||
<a href="signup" className="btn btn-medium btn-signup btn-p-x-2">
|
||||
Sign Up
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -1,5 +1,4 @@
|
||||
import './json_editor_ctrl';
|
||||
import './login_ctrl';
|
||||
import './invited_ctrl';
|
||||
import './signup_ctrl';
|
||||
import './reset_password_ctrl';
|
||||
|
@ -1,145 +0,0 @@
|
||||
import _ from 'lodash';
|
||||
import coreModule from '../core_module';
|
||||
import config from 'app/core/config';
|
||||
import { BackendSrv } from '../services/backend_srv';
|
||||
|
||||
export class LoginCtrl {
|
||||
/** @ngInject */
|
||||
constructor($scope: any, backendSrv: BackendSrv, $location: any) {
|
||||
$scope.formModel = {
|
||||
user: '',
|
||||
email: '',
|
||||
password: '',
|
||||
};
|
||||
|
||||
$scope.command = {};
|
||||
$scope.result = '';
|
||||
$scope.loggingIn = false;
|
||||
|
||||
$scope.oauth = config.oauth;
|
||||
$scope.oauthEnabled = _.keys(config.oauth).length > 0;
|
||||
$scope.ldapEnabled = config.ldapEnabled;
|
||||
$scope.authProxyEnabled = config.authProxyEnabled;
|
||||
$scope.samlEnabled = config.samlEnabled;
|
||||
|
||||
$scope.disableLoginForm = config.disableLoginForm;
|
||||
$scope.disableUserSignUp = config.disableUserSignUp;
|
||||
$scope.loginHint = config.loginHint;
|
||||
$scope.passwordHint = config.passwordHint;
|
||||
|
||||
$scope.loginMode = true;
|
||||
$scope.submitBtnText = 'Log in';
|
||||
|
||||
$scope.init = () => {
|
||||
$scope.$watch('loginMode', $scope.loginModeChanged);
|
||||
|
||||
if (config.loginError) {
|
||||
$scope.appEvent('alert-warning', ['Login Failed', config.loginError]);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.submit = () => {
|
||||
if ($scope.loginMode) {
|
||||
$scope.login();
|
||||
} else {
|
||||
$scope.signUp();
|
||||
}
|
||||
};
|
||||
|
||||
$scope.changeView = () => {
|
||||
const loginView = document.querySelector('#login-view');
|
||||
const changePasswordView = document.querySelector('#change-password-view');
|
||||
|
||||
loginView.className += ' add';
|
||||
setTimeout(() => {
|
||||
loginView.className += ' hidden';
|
||||
}, 250);
|
||||
setTimeout(() => {
|
||||
changePasswordView.classList.remove('hidden');
|
||||
}, 251);
|
||||
setTimeout(() => {
|
||||
changePasswordView.classList.remove('remove');
|
||||
}, 301);
|
||||
|
||||
setTimeout(() => {
|
||||
document.getElementById('newPassword').focus();
|
||||
}, 400);
|
||||
};
|
||||
|
||||
$scope.changePassword = () => {
|
||||
$scope.command.oldPassword = 'admin';
|
||||
|
||||
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(() => {
|
||||
$scope.toGrafana();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.skip = () => {
|
||||
$scope.toGrafana();
|
||||
};
|
||||
|
||||
$scope.loginModeChanged = (newValue: boolean) => {
|
||||
$scope.submitBtnText = newValue ? 'Log in' : 'Sign up';
|
||||
};
|
||||
|
||||
$scope.signUp = () => {
|
||||
if (!$scope.loginForm.$valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
backendSrv.post('/api/user/signup', $scope.formModel).then((result: any) => {
|
||||
if (result.status === 'SignUpCreated') {
|
||||
$location.path('/signup').search({ email: $scope.formModel.email });
|
||||
} else {
|
||||
window.location.href = config.appSubUrl + '/';
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$scope.login = () => {
|
||||
delete $scope.loginError;
|
||||
|
||||
if (!$scope.loginForm.$valid) {
|
||||
return;
|
||||
}
|
||||
$scope.loggingIn = true;
|
||||
|
||||
backendSrv
|
||||
.post('/login', $scope.formModel)
|
||||
.then((result: any) => {
|
||||
$scope.result = result;
|
||||
|
||||
if ($scope.formModel.password !== 'admin' || $scope.ldapEnabled || $scope.authProxyEnabled) {
|
||||
$scope.toGrafana();
|
||||
return;
|
||||
} else {
|
||||
$scope.changeView();
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
$scope.loggingIn = false;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.toGrafana = () => {
|
||||
const params = $location.search();
|
||||
|
||||
if (params.redirect && params.redirect[0] === '/') {
|
||||
window.location.href = config.appSubUrl + params.redirect;
|
||||
} else if ($scope.result.redirectUrl) {
|
||||
window.location.href = $scope.result.redirectUrl;
|
||||
} else {
|
||||
window.location.href = config.appSubUrl + '/';
|
||||
}
|
||||
};
|
||||
|
||||
$scope.init();
|
||||
}
|
||||
}
|
||||
|
||||
coreModule.controller('LoginCtrl', LoginCtrl);
|
@ -1,115 +0,0 @@
|
||||
<div class="login container">
|
||||
<div class="login-content">
|
||||
<div class="login-branding">
|
||||
<img class="logo-icon" src="public/img/grafana_icon.svg" alt="Grafana" />
|
||||
<div class="logo-wordmark" />
|
||||
</div>
|
||||
<div class="login-outer-box">
|
||||
<div class="login-inner-box" id="login-view">
|
||||
<form name="loginForm" class="login-form-group gf-form-group" ng-hide="disableLoginForm">
|
||||
<div class="login-form">
|
||||
<input type="text" name="username" class="gf-form-input login-form-input" required ng-model='formModel.user' placeholder={{loginHint}} aria-label="Username input field"
|
||||
autofocus autofill-event-fix>
|
||||
</div>
|
||||
<div class="login-form">
|
||||
<input type="password" name="password" class="gf-form-input login-form-input" required ng-model="formModel.password" id="inputPassword"
|
||||
placeholder="{{passwordHint}}" aria-label="Password input field">
|
||||
</div>
|
||||
<div class="login-button-group">
|
||||
<button type="submit" aria-label="Login button" class="btn btn-large p-x-2" ng-if="!loggingIn" ng-click="submit();" ng-class="{'btn-inverse': !loginForm.$valid, 'btn-primary': loginForm.$valid}">
|
||||
Log In
|
||||
</button>
|
||||
<button type="submit" class="btn btn-large p-x-2 btn-inverse btn-loading" ng-if="loggingIn">
|
||||
Logging In<span>.</span><span>.</span><span>.</span>
|
||||
</button>
|
||||
<div class="small login-button-forgot-password" ng-hide="ldapEnabled || authProxyEnabled">
|
||||
<a href="user/password/send-reset-email">
|
||||
Forgot your password?
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div class="text-center login-divider" ng-show="oauthEnabled">
|
||||
<div>
|
||||
<div class="login-divider-line">
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span class="login-divider-text">
|
||||
<span ng-hide="disableLoginForm">or</span>
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<div class="login-divider-line">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
<a class="btn btn-medium btn-service btn-service--github login-btn" href="login/saml" target="_self" ng-if="samlEnabled">
|
||||
<i class="btn-service-icon fa fa-key"></i>
|
||||
Sign in with SAML
|
||||
</a>
|
||||
<div class="login-oauth text-center" ng-show="oauthEnabled">
|
||||
<a class="btn btn-medium btn-service btn-service--google login-btn" href="login/google" target="_self" ng-if="oauth.google">
|
||||
<i class="btn-service-icon fa fa-google"></i>
|
||||
Sign in with Google
|
||||
</a>
|
||||
<a class="btn btn-medium btn-service btn-service--github login-btn" href="login/github" target="_self" ng-if="oauth.github">
|
||||
<i class="btn-service-icon fa fa-github"></i>
|
||||
Sign in with GitHub
|
||||
</a>
|
||||
<a class="btn btn-medium btn-service btn-service--gitlab login-btn" href="login/gitlab" target="_self" ng-if="oauth.gitlab">
|
||||
<i class="btn-service-icon fa fa-gitlab"></i>
|
||||
Sign in with GitLab
|
||||
</a>
|
||||
<a class="btn btn-medium btn-service btn-service--grafanacom login-btn" href="login/grafana_com" target="_self"
|
||||
ng-if="oauth.grafana_com">
|
||||
<i class="btn-service-icon"></i>
|
||||
Sign in with Grafana.com
|
||||
</a>
|
||||
<a class="btn btn-medium btn-service btn-service--oauth login-btn" href="login/generic_oauth" target="_self"
|
||||
ng-if="oauth.generic_oauth">
|
||||
<i class="btn-service-icon fa fa-sign-in"></i>
|
||||
Sign in with {{oauth.generic_oauth.name}}
|
||||
</a>
|
||||
</div>
|
||||
<div class="login-signup-box" ng-show="!disableUserSignUp">
|
||||
<div class="login-signup-title p-r-1">
|
||||
New to Grafana?
|
||||
</div>
|
||||
<a href="signup" class="btn btn-medium btn-signup btn-p-x-2">
|
||||
Sign Up
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="login-inner-box remove hidden" id="change-password-view">
|
||||
<div class="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 class="login-form-group gf-form-group">
|
||||
<div class="login-form">
|
||||
<input type="password" id="newPassword" name="newPassword" class="gf-form-input login-form-input" required ng-model='command.newPassword'
|
||||
placeholder="New password">
|
||||
</div>
|
||||
<div class="login-form">
|
||||
<input type="password" name="confirmNew" class="gf-form-input login-form-input" required ng-model="command.confirmNew" placeholder="Confirm new password">
|
||||
</div>
|
||||
<div class="login-button-group login-button-group--right text-right">
|
||||
<a class="btn btn-link" ng-click="skip();">
|
||||
Skip
|
||||
<info-popover mode="small-padding">
|
||||
If you skip you will be prompted to change password next time you login.
|
||||
</info-popover>
|
||||
</a>
|
||||
<button type="submit" class="btn btn-large p-x-2" ng-click="changePassword();" ng-class="{'btn-inverse': !loginForm.$valid, 'btn-primary': loginForm.$valid}">
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -30,6 +30,7 @@ import { route, ILocationProvider } from 'angular';
|
||||
|
||||
// Types
|
||||
import { DashboardRouteInfo } from 'app/types';
|
||||
import { LoginPage } from 'app/core/components/Login/LoginPage';
|
||||
|
||||
/** @ngInject */
|
||||
export function setupAngularRoutes($routeProvider: route.IRouteProvider, $locationProvider: ILocationProvider) {
|
||||
@ -285,8 +286,10 @@ export function setupAngularRoutes($routeProvider: route.IRouteProvider, $locati
|
||||
})
|
||||
// LOGIN / SIGNUP
|
||||
.when('/login', {
|
||||
templateUrl: 'public/app/partials/login.html',
|
||||
controller: 'LoginCtrl',
|
||||
template: '<react-container/>',
|
||||
resolve: {
|
||||
component: () => LoginPage,
|
||||
},
|
||||
pageClass: 'login-page sidemenu-hidden',
|
||||
})
|
||||
.when('/invite/:code', {
|
||||
|
@ -20,7 +20,6 @@ $login-border: #8daac5;
|
||||
|
||||
& .btn-primary {
|
||||
@include buttonBackground(#ff6600, #bc3e06);
|
||||
height: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
@ -162,18 +161,17 @@ select:-webkit-autofill:focus {
|
||||
transform: tranlate(0px, 0px);
|
||||
transition: 0.25s ease;
|
||||
|
||||
&.add {
|
||||
transform: translate(0px, -320px);
|
||||
&.hidden {
|
||||
display: none;
|
||||
}
|
||||
&.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.remove {
|
||||
&-enter {
|
||||
transform: translate(0px, 320px);
|
||||
&.hidden {
|
||||
display: none;
|
||||
}
|
||||
display: flex;
|
||||
}
|
||||
|
||||
&-enter-active {
|
||||
transform: translate(0px, 0px);
|
||||
}
|
||||
}
|
||||
|
||||
@ -319,15 +317,15 @@ select:-webkit-autofill:focus {
|
||||
}
|
||||
}
|
||||
|
||||
.login-button-group {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.login-inner-box {
|
||||
width: 55%;
|
||||
padding: $space-md 56px;
|
||||
}
|
||||
|
||||
.login-button-group {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.login-button-forgot-password {
|
||||
padding-top: 0;
|
||||
padding-left: 10px;
|
||||
|
Loading…
Reference in New Issue
Block a user