diff --git a/public/app/core/components/Login/ChangePassword.tsx b/public/app/core/components/ForgottenPassword/ChangePassword.tsx similarity index 75% rename from public/app/core/components/Login/ChangePassword.tsx rename to public/app/core/components/ForgottenPassword/ChangePassword.tsx index 47cac53f63a..dda6ced168f 100644 --- a/public/app/core/components/Login/ChangePassword.tsx +++ b/public/app/core/components/ForgottenPassword/ChangePassword.tsx @@ -1,10 +1,10 @@ import React, { FC, SyntheticEvent } from 'react'; import { Tooltip, Form, Field, Input, VerticalGroup, Button, LinkButton } from '@grafana/ui'; import { selectors } from '@grafana/e2e-selectors'; -import { submitButton } from './LoginForm'; +import { submitButton } from '../Login/LoginForm'; interface Props { onSubmit: (pw: string) => void; - onSkip: (event?: SyntheticEvent) => void; + onSkip?: (event?: SyntheticEvent) => void; } interface PasswordDTO { @@ -44,14 +44,17 @@ export const ChangePassword: FC = ({ onSubmit, onSkip }) => { - - - Skip - - + + {onSkip && ( + + + Skip + + + )} )} diff --git a/public/app/core/components/ForgottenPassword/ChangePasswordPage.tsx b/public/app/core/components/ForgottenPassword/ChangePasswordPage.tsx new file mode 100644 index 00000000000..f734bb3ffb5 --- /dev/null +++ b/public/app/core/components/ForgottenPassword/ChangePasswordPage.tsx @@ -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 ( + + + {({ changePassword }) => } + + + ); +}; + +export default ChangePasswordPage; diff --git a/public/app/core/components/ForgottenPassword/ForgottenPassword.tsx b/public/app/core/components/ForgottenPassword/ForgottenPassword.tsx new file mode 100644 index 00000000000..e0c1eb4b82b --- /dev/null +++ b/public/app/core/components/ForgottenPassword/ForgottenPassword.tsx @@ -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 ( +
+

An email with a reset link has been sent to the email address. You should receive it shortly.

+ + + Back to login + +
+ ); + } + return ( +
+ {({ register, errors }) => ( + <> + Reset password + + + + + + + Back to login + + + +

Did you forget your username or email? Contact your Grafana administrator.

+ + )} +
+ ); +}; diff --git a/public/app/core/components/ForgottenPassword/SendResetMailPage.tsx b/public/app/core/components/ForgottenPassword/SendResetMailPage.tsx new file mode 100644 index 00000000000..1ab5a36ff35 --- /dev/null +++ b/public/app/core/components/ForgottenPassword/SendResetMailPage.tsx @@ -0,0 +1,14 @@ +import React, { FC } from 'react'; + +import { LoginLayout, InnerBox } from '../Login/LoginLayout'; +import { ForgottenPassword } from './ForgottenPassword'; + +export const SendResetMailPage: FC = () => ( + + + + + +); + +export default SendResetMailPage; diff --git a/public/app/core/components/Login/LoginCtrl.tsx b/public/app/core/components/Login/LoginCtrl.tsx index da854447d8d..391fbde17ed 100644 --- a/public/app/core/components/Login/LoginCtrl.tsx +++ b/public/app/core/components/Login/LoginCtrl.tsx @@ -63,12 +63,26 @@ export class LoginCtrl extends PureComponent { confirmNew: password, 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() - .put('/api/user/password', pw) + .post('/api/user/password/reset', resetModel) .then(() => { this.toGrafana(); - }) - .catch((err: any) => console.log(err)); + }); }; login = (formModel: FormModel) => { diff --git a/public/app/core/components/Login/LoginForm.tsx b/public/app/core/components/Login/LoginForm.tsx index 7426c7eea5b..8b3eae33059 100644 --- a/public/app/core/components/Login/LoginForm.tsx +++ b/public/app/core/components/Login/LoginForm.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react'; +import React, { FC, ReactElement } from 'react'; import { selectors } from '@grafana/e2e-selectors'; import { FormModel } from './LoginCtrl'; @@ -6,19 +6,13 @@ import { Button, Form, Input, Field } from '@grafana/ui'; import { css } from 'emotion'; interface Props { - displayForgotPassword: boolean; + children: ReactElement; onSubmit: (data: FormModel) => void; isLoggingIn: boolean; passwordHint: string; loginHint: string; } -const forgottenPasswordStyles = css` - display: inline-block; - margin-top: 16px; - float: right; -`; - const wrapperStyles = css` width: 100%; padding-bottom: 16px; @@ -29,7 +23,7 @@ export const submitButton = css` width: 100%; `; -export const LoginForm: FC = ({ displayForgotPassword, onSubmit, isLoggingIn, passwordHint, loginHint }) => { +export const LoginForm: FC = ({ children, onSubmit, isLoggingIn, passwordHint, loginHint }) => { return (
@@ -56,11 +50,7 @@ export const LoginForm: FC = ({ displayForgotPassword, onSubmit, isLoggin - {displayForgotPassword && ( - - Forgot your password? - - )} + {children} )} diff --git a/public/app/core/components/Login/LoginLayout.tsx b/public/app/core/components/Login/LoginLayout.tsx new file mode 100644 index 00000000000..8a00e39bff4 --- /dev/null +++ b/public/app/core/components/Login/LoginLayout.tsx @@ -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 = ({ children, enterAnimation = true }) => { + const loginStyles = useStyles(getLoginStyles); + return
{children}
; +}; + +export const LoginLayout: FC = ({ children }) => { + const loginStyles = useStyles(getLoginStyles); + return ( + +
+
+ +
+

{Branding.LoginTitle}

+

{Branding.GetLoginSubTitle()}

+
+
+
{children}
+
+