mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
ForgottenPassword: Move view to login screen (#25366)
* Add forgot password to login screen * Add ForgottenPassword component * Add spacing * Generate new emails and handle resetCode * Fix animation and small UX issues * Extract LoginLayout and add route for reset mail * Reset email template * Add ChangePasswordPage * Remove resetCode * Move style into variable * Fix strict null
This commit is contained in:
parent
1bde4de827
commit
f5de4f1fb1
@ -1,10 +1,10 @@
|
|||||||
import React, { FC, SyntheticEvent } from 'react';
|
import React, { FC, SyntheticEvent } from 'react';
|
||||||
import { Tooltip, Form, Field, Input, VerticalGroup, Button, LinkButton } from '@grafana/ui';
|
import { Tooltip, Form, Field, Input, VerticalGroup, Button, LinkButton } from '@grafana/ui';
|
||||||
import { selectors } from '@grafana/e2e-selectors';
|
import { selectors } from '@grafana/e2e-selectors';
|
||||||
import { submitButton } from './LoginForm';
|
import { submitButton } from '../Login/LoginForm';
|
||||||
interface Props {
|
interface Props {
|
||||||
onSubmit: (pw: string) => void;
|
onSubmit: (pw: string) => void;
|
||||||
onSkip: (event?: SyntheticEvent) => void;
|
onSkip?: (event?: SyntheticEvent) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PasswordDTO {
|
interface PasswordDTO {
|
||||||
@ -44,14 +44,17 @@ export const ChangePassword: FC<Props> = ({ onSubmit, onSkip }) => {
|
|||||||
<Button type="submit" className={submitButton}>
|
<Button type="submit" className={submitButton}>
|
||||||
Submit
|
Submit
|
||||||
</Button>
|
</Button>
|
||||||
<Tooltip
|
|
||||||
content="If you skip you will be prompted to change password next time you login."
|
{onSkip && (
|
||||||
placement="bottom"
|
<Tooltip
|
||||||
>
|
content="If you skip you will be prompted to change password next time you login."
|
||||||
<LinkButton variant="link" onClick={onSkip} aria-label={selectors.pages.Login.skip}>
|
placement="bottom"
|
||||||
Skip
|
>
|
||||||
</LinkButton>
|
<LinkButton variant="link" onClick={onSkip} aria-label={selectors.pages.Login.skip}>
|
||||||
</Tooltip>
|
Skip
|
||||||
|
</LinkButton>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
</VerticalGroup>
|
</VerticalGroup>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
@ -0,0 +1,16 @@
|
|||||||
|
import React, { FC } from 'react';
|
||||||
|
import { LoginLayout, InnerBox } from '../Login/LoginLayout';
|
||||||
|
import { ChangePassword } from './ChangePassword';
|
||||||
|
import LoginCtrl from '../Login/LoginCtrl';
|
||||||
|
|
||||||
|
export const ChangePasswordPage: FC = () => {
|
||||||
|
return (
|
||||||
|
<LoginLayout>
|
||||||
|
<InnerBox>
|
||||||
|
<LoginCtrl>{({ changePassword }) => <ChangePassword onSubmit={changePassword} />}</LoginCtrl>
|
||||||
|
</InnerBox>
|
||||||
|
</LoginLayout>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ChangePasswordPage;
|
@ -0,0 +1,66 @@
|
|||||||
|
import React, { FC, useState } from 'react';
|
||||||
|
import { Form, Field, Input, Button, Legend, Container, useStyles, HorizontalGroup, LinkButton } from '@grafana/ui';
|
||||||
|
import { getBackendSrv } from '@grafana/runtime';
|
||||||
|
import { css } from 'emotion';
|
||||||
|
import { GrafanaTheme } from '@grafana/data';
|
||||||
|
|
||||||
|
interface EmailDTO {
|
||||||
|
userOrEmail: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const paragraphStyles = (theme: GrafanaTheme) => css`
|
||||||
|
color: ${theme.colors.formDescription};
|
||||||
|
font-size: ${theme.typography.size.sm};
|
||||||
|
font-weight: ${theme.typography.weight.regular};
|
||||||
|
margin-top: ${theme.spacing.sm};
|
||||||
|
display: block;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const ForgottenPassword: FC = () => {
|
||||||
|
const [emailSent, setEmailSent] = useState(false);
|
||||||
|
const styles = useStyles(paragraphStyles);
|
||||||
|
|
||||||
|
const sendEmail = async (formModel: EmailDTO) => {
|
||||||
|
const res = await getBackendSrv().post('/api/user/password/send-reset-email', formModel);
|
||||||
|
if (res) {
|
||||||
|
setEmailSent(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (emailSent) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<p>An email with a reset link has been sent to the email address. You should receive it shortly.</p>
|
||||||
|
<Container margin="md" />
|
||||||
|
<LinkButton variant="primary" href="/login">
|
||||||
|
Back to login
|
||||||
|
</LinkButton>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Form onSubmit={sendEmail}>
|
||||||
|
{({ register, errors }) => (
|
||||||
|
<>
|
||||||
|
<Legend>Reset password</Legend>
|
||||||
|
<Field
|
||||||
|
label="User"
|
||||||
|
description="Enter your informaton to get a reset link sent to you"
|
||||||
|
invalid={!!errors.userOrEmail}
|
||||||
|
error={errors?.userOrEmail?.message}
|
||||||
|
>
|
||||||
|
<Input placeholder="Email or username" name="userOrEmail" ref={register({ required: true })} />
|
||||||
|
</Field>
|
||||||
|
<HorizontalGroup>
|
||||||
|
<Button>Send reset email</Button>
|
||||||
|
<LinkButton variant="link" href="/login">
|
||||||
|
Back to login
|
||||||
|
</LinkButton>
|
||||||
|
</HorizontalGroup>
|
||||||
|
|
||||||
|
<p className={styles}>Did you forget your username or email? Contact your Grafana administrator.</p>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,14 @@
|
|||||||
|
import React, { FC } from 'react';
|
||||||
|
|
||||||
|
import { LoginLayout, InnerBox } from '../Login/LoginLayout';
|
||||||
|
import { ForgottenPassword } from './ForgottenPassword';
|
||||||
|
|
||||||
|
export const SendResetMailPage: FC = () => (
|
||||||
|
<LoginLayout>
|
||||||
|
<InnerBox>
|
||||||
|
<ForgottenPassword />
|
||||||
|
</InnerBox>
|
||||||
|
</LoginLayout>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default SendResetMailPage;
|
@ -63,12 +63,26 @@ export class LoginCtrl extends PureComponent<Props, State> {
|
|||||||
confirmNew: password,
|
confirmNew: password,
|
||||||
oldPassword: 'admin',
|
oldPassword: 'admin',
|
||||||
};
|
};
|
||||||
|
if (!this.props.routeParams.code) {
|
||||||
|
getBackendSrv()
|
||||||
|
.put('/api/user/password', pw)
|
||||||
|
.then(() => {
|
||||||
|
this.toGrafana();
|
||||||
|
})
|
||||||
|
.catch((err: any) => console.log(err));
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetModel = {
|
||||||
|
code: this.props.routeParams.code,
|
||||||
|
newPassword: password,
|
||||||
|
confirmPassword: password,
|
||||||
|
};
|
||||||
|
|
||||||
getBackendSrv()
|
getBackendSrv()
|
||||||
.put('/api/user/password', pw)
|
.post('/api/user/password/reset', resetModel)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.toGrafana();
|
this.toGrafana();
|
||||||
})
|
});
|
||||||
.catch((err: any) => console.log(err));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
login = (formModel: FormModel) => {
|
login = (formModel: FormModel) => {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { FC } from 'react';
|
import React, { FC, ReactElement } from 'react';
|
||||||
import { selectors } from '@grafana/e2e-selectors';
|
import { selectors } from '@grafana/e2e-selectors';
|
||||||
|
|
||||||
import { FormModel } from './LoginCtrl';
|
import { FormModel } from './LoginCtrl';
|
||||||
@ -6,19 +6,13 @@ import { Button, Form, Input, Field } from '@grafana/ui';
|
|||||||
import { css } from 'emotion';
|
import { css } from 'emotion';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
displayForgotPassword: boolean;
|
children: ReactElement;
|
||||||
onSubmit: (data: FormModel) => void;
|
onSubmit: (data: FormModel) => void;
|
||||||
isLoggingIn: boolean;
|
isLoggingIn: boolean;
|
||||||
passwordHint: string;
|
passwordHint: string;
|
||||||
loginHint: string;
|
loginHint: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const forgottenPasswordStyles = css`
|
|
||||||
display: inline-block;
|
|
||||||
margin-top: 16px;
|
|
||||||
float: right;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const wrapperStyles = css`
|
const wrapperStyles = css`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding-bottom: 16px;
|
padding-bottom: 16px;
|
||||||
@ -29,7 +23,7 @@ export const submitButton = css`
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const LoginForm: FC<Props> = ({ displayForgotPassword, onSubmit, isLoggingIn, passwordHint, loginHint }) => {
|
export const LoginForm: FC<Props> = ({ children, onSubmit, isLoggingIn, passwordHint, loginHint }) => {
|
||||||
return (
|
return (
|
||||||
<div className={wrapperStyles}>
|
<div className={wrapperStyles}>
|
||||||
<Form onSubmit={onSubmit} validateOn="onChange">
|
<Form onSubmit={onSubmit} validateOn="onChange">
|
||||||
@ -56,11 +50,7 @@ export const LoginForm: FC<Props> = ({ displayForgotPassword, onSubmit, isLoggin
|
|||||||
<Button aria-label={selectors.pages.Login.submit} className={submitButton} disabled={isLoggingIn}>
|
<Button aria-label={selectors.pages.Login.submit} className={submitButton} disabled={isLoggingIn}>
|
||||||
{isLoggingIn ? 'Logging in...' : 'Log in'}
|
{isLoggingIn ? 'Logging in...' : 'Log in'}
|
||||||
</Button>
|
</Button>
|
||||||
{displayForgotPassword && (
|
{children}
|
||||||
<a className={forgottenPasswordStyles} href="user/password/send-reset-email">
|
|
||||||
Forgot your password?
|
|
||||||
</a>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Form>
|
</Form>
|
||||||
|
123
public/app/core/components/Login/LoginLayout.tsx
Normal file
123
public/app/core/components/Login/LoginLayout.tsx
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
import React, { FC } from 'react';
|
||||||
|
import { cx, css, keyframes } from 'emotion';
|
||||||
|
import { useStyles } from '@grafana/ui';
|
||||||
|
import { Branding } from '../Branding/Branding';
|
||||||
|
import { GrafanaTheme } from '@grafana/data';
|
||||||
|
import { Footer } from '../Footer/Footer';
|
||||||
|
|
||||||
|
interface InnerBoxProps {
|
||||||
|
enterAnimation?: boolean;
|
||||||
|
}
|
||||||
|
export const InnerBox: FC<InnerBoxProps> = ({ children, enterAnimation = true }) => {
|
||||||
|
const loginStyles = useStyles(getLoginStyles);
|
||||||
|
return <div className={cx(loginStyles.loginInnerBox, enterAnimation && loginStyles.enterAnimation)}>{children}</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const LoginLayout: FC = ({ children }) => {
|
||||||
|
const loginStyles = useStyles(getLoginStyles);
|
||||||
|
return (
|
||||||
|
<Branding.LoginBackground className={loginStyles.container}>
|
||||||
|
<div className={cx(loginStyles.loginContent, Branding.LoginBoxBackground())}>
|
||||||
|
<div className={loginStyles.loginLogoWrapper}>
|
||||||
|
<Branding.LoginLogo className={loginStyles.loginLogo} />
|
||||||
|
<div className={loginStyles.titleWrapper}>
|
||||||
|
<h1 className={loginStyles.mainTitle}>{Branding.LoginTitle}</h1>
|
||||||
|
<h3 className={loginStyles.subTitle}>{Branding.GetLoginSubTitle()}</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={loginStyles.loginOuterBox}>{children}</div>
|
||||||
|
</div>
|
||||||
|
<Footer />
|
||||||
|
</Branding.LoginBackground>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const flyInAnimation = keyframes`
|
||||||
|
from{
|
||||||
|
opacity: 0;
|
||||||
|
transform: translate(-60px, 0px);
|
||||||
|
}
|
||||||
|
|
||||||
|
to{
|
||||||
|
opacity: 1;
|
||||||
|
transform: translate(0px, 0px);
|
||||||
|
}`;
|
||||||
|
|
||||||
|
export const getLoginStyles = (theme: GrafanaTheme) => {
|
||||||
|
return {
|
||||||
|
container: css`
|
||||||
|
min-height: 100vh;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
min-width: 100%;
|
||||||
|
margin-left: 0;
|
||||||
|
background-color: $black;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
`,
|
||||||
|
submitButton: css`
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
`,
|
||||||
|
loginLogo: css`
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
`,
|
||||||
|
loginLogoWrapper: css`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: ${theme.spacing.lg};
|
||||||
|
`,
|
||||||
|
titleWrapper: css`
|
||||||
|
text-align: center;
|
||||||
|
`,
|
||||||
|
mainTitle: css`
|
||||||
|
font-size: '32px';
|
||||||
|
`,
|
||||||
|
subTitle: css`
|
||||||
|
font-size: ${theme.typography.size.md};
|
||||||
|
color: ${theme.colors.textSemiWeak};
|
||||||
|
`,
|
||||||
|
loginContent: css`
|
||||||
|
max-width: 550px;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 1;
|
||||||
|
min-height: 320px;
|
||||||
|
border-radius: 3px;
|
||||||
|
padding: 20px 0;
|
||||||
|
`,
|
||||||
|
loginOuterBox: css`
|
||||||
|
display: flex;
|
||||||
|
overflow-y: hidden;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
`,
|
||||||
|
loginInnerBox: css`
|
||||||
|
padding: ${theme.spacing.xl};
|
||||||
|
@media (max-width: 320px) {
|
||||||
|
padding: ${theme.spacing.lg};
|
||||||
|
}
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-grow: 1;
|
||||||
|
max-width: 415px;
|
||||||
|
width: 100%;
|
||||||
|
transform: translate(0px, 0px);
|
||||||
|
transition: 0.25s ease;
|
||||||
|
`,
|
||||||
|
enterAnimation: css`
|
||||||
|
animation: ${flyInAnimation} ease-out 0.2s;
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
};
|
@ -1,162 +1,77 @@
|
|||||||
// Libraries
|
// Libraries
|
||||||
import React, { FC } from 'react';
|
import React, { FC } from 'react';
|
||||||
import { cx, keyframes, css } from 'emotion';
|
import { css } from 'emotion';
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import { UserSignup } from './UserSignup';
|
import { UserSignup } from './UserSignup';
|
||||||
import { LoginServiceButtons } from './LoginServiceButtons';
|
import { LoginServiceButtons } from './LoginServiceButtons';
|
||||||
import LoginCtrl from './LoginCtrl';
|
import LoginCtrl from './LoginCtrl';
|
||||||
import { LoginForm } from './LoginForm';
|
import { LoginForm } from './LoginForm';
|
||||||
import { ChangePassword } from './ChangePassword';
|
import { ChangePassword } from '../ForgottenPassword/ChangePassword';
|
||||||
import { Branding } from 'app/core/components/Branding/Branding';
|
import { HorizontalGroup, LinkButton } from '@grafana/ui';
|
||||||
import { useStyles } from '@grafana/ui';
|
import { LoginLayout, InnerBox } from './LoginLayout';
|
||||||
import { GrafanaTheme } from '@grafana/data';
|
|
||||||
import { Footer } from '../Footer/Footer';
|
const forgottenPasswordStyles = css`
|
||||||
|
padding: 0;
|
||||||
|
margin-top: 4px;
|
||||||
|
`;
|
||||||
|
|
||||||
export const LoginPage: FC = () => {
|
export const LoginPage: FC = () => {
|
||||||
document.title = Branding.AppTitle;
|
|
||||||
const loginStyles = useStyles(getLoginStyles);
|
|
||||||
return (
|
return (
|
||||||
<Branding.LoginBackground className={loginStyles.container}>
|
<LoginLayout>
|
||||||
<div className={cx(loginStyles.loginContent, Branding.LoginBoxBackground())}>
|
<LoginCtrl>
|
||||||
<div className={loginStyles.loginLogoWrapper}>
|
{({
|
||||||
<Branding.LoginLogo className={loginStyles.loginLogo} />
|
loginHint,
|
||||||
<div className={loginStyles.titleWrapper}>
|
passwordHint,
|
||||||
<h1 className={loginStyles.mainTitle}>{Branding.LoginTitle}</h1>
|
ldapEnabled,
|
||||||
<h3 className={loginStyles.subTitle}>{Branding.GetLoginSubTitle()}</h3>
|
authProxyEnabled,
|
||||||
</div>
|
disableLoginForm,
|
||||||
</div>
|
disableUserSignUp,
|
||||||
<LoginCtrl>
|
login,
|
||||||
{({
|
isLoggingIn,
|
||||||
loginHint,
|
changePassword,
|
||||||
passwordHint,
|
skipPasswordChange,
|
||||||
ldapEnabled,
|
isChangingPassword,
|
||||||
authProxyEnabled,
|
}) => (
|
||||||
disableLoginForm,
|
<>
|
||||||
disableUserSignUp,
|
{!isChangingPassword && (
|
||||||
login,
|
<InnerBox>
|
||||||
isLoggingIn,
|
{!disableLoginForm && (
|
||||||
changePassword,
|
<>
|
||||||
skipPasswordChange,
|
|
||||||
isChangingPassword,
|
|
||||||
}) => (
|
|
||||||
<div className={loginStyles.loginOuterBox}>
|
|
||||||
{!isChangingPassword && (
|
|
||||||
<div className={`${loginStyles.loginInnerBox} ${isChangingPassword ? 'hidden' : ''}`} id="login-view">
|
|
||||||
{!disableLoginForm && (
|
|
||||||
<LoginForm
|
<LoginForm
|
||||||
displayForgotPassword={!(ldapEnabled || authProxyEnabled)}
|
|
||||||
onSubmit={login}
|
onSubmit={login}
|
||||||
loginHint={loginHint}
|
loginHint={loginHint}
|
||||||
passwordHint={passwordHint}
|
passwordHint={passwordHint}
|
||||||
isLoggingIn={isLoggingIn}
|
isLoggingIn={isLoggingIn}
|
||||||
/>
|
>
|
||||||
)}
|
{!(ldapEnabled || authProxyEnabled) ? (
|
||||||
|
<HorizontalGroup justify="flex-end">
|
||||||
<LoginServiceButtons />
|
<LinkButton
|
||||||
{!disableUserSignUp && <UserSignup />}
|
className={forgottenPasswordStyles}
|
||||||
</div>
|
variant="link"
|
||||||
)}
|
href="/user/password/send-reset-email"
|
||||||
|
>
|
||||||
{isChangingPassword && (
|
Forgot your password?
|
||||||
<div className={cx(loginStyles.loginInnerBox, loginStyles.enterAnimation)}>
|
</LinkButton>
|
||||||
<ChangePassword onSubmit={changePassword} onSkip={skipPasswordChange as any} />
|
</HorizontalGroup>
|
||||||
</div>
|
) : (
|
||||||
)}
|
<></>
|
||||||
</div>
|
)}
|
||||||
)}
|
</LoginForm>
|
||||||
</LoginCtrl>
|
</>
|
||||||
</div>
|
)}
|
||||||
|
<LoginServiceButtons />
|
||||||
<Footer />
|
{!disableUserSignUp && <UserSignup />}
|
||||||
</Branding.LoginBackground>
|
</InnerBox>
|
||||||
|
)}
|
||||||
|
{isChangingPassword && (
|
||||||
|
<InnerBox>
|
||||||
|
<ChangePassword onSubmit={changePassword} onSkip={() => skipPasswordChange()} />
|
||||||
|
</InnerBox>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</LoginCtrl>
|
||||||
|
</LoginLayout>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const flyInAnimation = keyframes`
|
|
||||||
from{
|
|
||||||
transform: translate(-400px, 0px);
|
|
||||||
}
|
|
||||||
|
|
||||||
to{
|
|
||||||
transform: translate(0px, 0px);
|
|
||||||
}`;
|
|
||||||
|
|
||||||
export const getLoginStyles = (theme: GrafanaTheme) => {
|
|
||||||
return {
|
|
||||||
container: css`
|
|
||||||
min-height: 100vh;
|
|
||||||
background-position: center;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
min-width: 100%;
|
|
||||||
margin-left: 0;
|
|
||||||
background-color: $black;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
`,
|
|
||||||
submitButton: css`
|
|
||||||
justify-content: center;
|
|
||||||
width: 100%;
|
|
||||||
`,
|
|
||||||
loginLogo: css`
|
|
||||||
width: 100%;
|
|
||||||
max-width: 100px;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
`,
|
|
||||||
loginLogoWrapper: css`
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
flex-direction: column;
|
|
||||||
padding: ${theme.spacing.lg};
|
|
||||||
`,
|
|
||||||
titleWrapper: css`
|
|
||||||
text-align: center;
|
|
||||||
`,
|
|
||||||
mainTitle: css`
|
|
||||||
font-size: '32px';
|
|
||||||
`,
|
|
||||||
subTitle: css`
|
|
||||||
font-size: ${theme.typography.size.md};
|
|
||||||
color: ${theme.colors.textSemiWeak};
|
|
||||||
`,
|
|
||||||
loginContent: css`
|
|
||||||
max-width: 550px;
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
align-items: stretch;
|
|
||||||
flex-direction: column;
|
|
||||||
position: relative;
|
|
||||||
justify-content: center;
|
|
||||||
z-index: 1;
|
|
||||||
min-height: 320px;
|
|
||||||
border-radius: 3px;
|
|
||||||
padding: 20px 0;
|
|
||||||
`,
|
|
||||||
loginOuterBox: css`
|
|
||||||
display: flex;
|
|
||||||
overflow-y: hidden;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
`,
|
|
||||||
loginInnerBox: css`
|
|
||||||
padding: ${theme.spacing.xl};
|
|
||||||
@media (max-width: 320px) {
|
|
||||||
padding: ${theme.spacing.lg};
|
|
||||||
}
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
flex-grow: 1;
|
|
||||||
max-width: 415px;
|
|
||||||
width: 100%;
|
|
||||||
transform: translate(0px, 0px);
|
|
||||||
transition: 0.25s ease;
|
|
||||||
`,
|
|
||||||
enterAnimation: css`
|
|
||||||
animation: ${flyInAnimation} ease-out 0.2s;
|
|
||||||
`,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
@ -1,13 +1,25 @@
|
|||||||
import React, { FC } from 'react';
|
import React, { FC } from 'react';
|
||||||
import { LinkButton, HorizontalGroup } from '@grafana/ui';
|
import { LinkButton, VerticalGroup } from '@grafana/ui';
|
||||||
|
import { css } from 'emotion';
|
||||||
|
|
||||||
export const UserSignup: FC<{}> = () => {
|
export const UserSignup: FC<{}> = () => {
|
||||||
return (
|
return (
|
||||||
<HorizontalGroup justify="flex-start">
|
<VerticalGroup
|
||||||
<LinkButton href="signup" variant="secondary">
|
className={css`
|
||||||
|
margin-top: 8px;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<span>New to Grafana?</span>
|
||||||
|
<LinkButton
|
||||||
|
className={css`
|
||||||
|
width: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
`}
|
||||||
|
href="signup"
|
||||||
|
variant="secondary"
|
||||||
|
>
|
||||||
Sign Up
|
Sign Up
|
||||||
</LinkButton>
|
</LinkButton>
|
||||||
<span>New to Grafana?</span>
|
</VerticalGroup>
|
||||||
</HorizontalGroup>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -438,14 +438,28 @@ export function setupAngularRoutes($routeProvider: route.IRouteProvider, $locati
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
.when('/user/password/send-reset-email', {
|
.when('/user/password/send-reset-email', {
|
||||||
templateUrl: 'public/app/partials/reset_password.html',
|
template: '<react-container />',
|
||||||
controller: 'ResetPasswordCtrl',
|
resolve: {
|
||||||
//@ts-ignore
|
component: () =>
|
||||||
|
SafeDynamicImport(
|
||||||
|
import(
|
||||||
|
/* webpackChunkName: "SendResetMailPage" */ 'app/core/components/ForgottenPassword/SendResetMailPage'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
},
|
||||||
|
// @ts-ignore
|
||||||
pageClass: 'sidemenu-hidden',
|
pageClass: 'sidemenu-hidden',
|
||||||
})
|
})
|
||||||
.when('/user/password/reset', {
|
.when('/user/password/reset', {
|
||||||
templateUrl: 'public/app/partials/reset_password.html',
|
template: '<react-container />',
|
||||||
controller: 'ResetPasswordCtrl',
|
resolve: {
|
||||||
|
component: () =>
|
||||||
|
SafeDynamicImport(
|
||||||
|
import(
|
||||||
|
/* webpackChunkName: "ChangePasswordPage" */ 'app/core/components/ForgottenPassword/ChangePasswordPage'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
},
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
pageClass: 'sidemenu-hidden',
|
pageClass: 'sidemenu-hidden',
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user