github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/ui/src/views/login/loginPage.tsx (about)

     1  // Copyright 2018 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  import classNames from "classnames";
    12  import React from "react";
    13  import Helmet from "react-helmet";
    14  import { connect } from "react-redux";
    15  import { RouteComponentProps, withRouter } from "react-router-dom";
    16  
    17  import { doLogin, LoginAPIState } from "src/redux/login";
    18  import { AdminUIState } from "src/redux/state";
    19  import * as docsURL from "src/util/docs";
    20  import { trustIcon } from "src/util/trust";
    21  import InfoBox from "src/views/shared/components/infoBox";
    22  
    23  import logo from "assets/crdb.png";
    24  import docsIcon from "!!raw-loader!assets/docs.svg";
    25  import "./loginPage.styl";
    26  
    27  interface LoginPageProps {
    28    loginState: LoginAPIState;
    29    handleLogin: (username: string, password: string) => Promise<void>;
    30  }
    31  
    32  interface LoginPageState {
    33    username: string;
    34    password: string;
    35  }
    36  
    37  class LoginPage extends React.Component<LoginPageProps & RouteComponentProps, LoginPageState> {
    38    constructor(props: LoginPageProps & RouteComponentProps) {
    39      super(props);
    40      this.state = {
    41        username: "",
    42        password: "",
    43      };
    44      // TODO(vilterp): focus username field on mount
    45    }
    46  
    47    handleUpdateUsername = (evt: React.FormEvent<{ value: string }>) => {
    48      this.setState({
    49        username: evt.currentTarget.value,
    50      });
    51    }
    52  
    53    handleUpdatePassword = (evt: React.FormEvent<{ value: string }>) => {
    54      this.setState({
    55        password: evt.currentTarget.value,
    56      });
    57    }
    58  
    59    handleSubmit = (evt: React.FormEvent<any>) => {
    60      const { location, history, handleLogin} = this.props;
    61      const { username, password } = this.state;
    62      evt.preventDefault();
    63  
    64      handleLogin(username, password)
    65        .then(() => {
    66          const params = new URLSearchParams(location.search);
    67          if (params.has("redirectTo")) {
    68            history.push(params.get("redirectTo"));
    69          } else {
    70            history.push("/");
    71          }
    72        });
    73    }
    74  
    75    renderError() {
    76      const { error } = this.props.loginState;
    77  
    78      if (!error) {
    79        return null;
    80      }
    81  
    82      let message = "Invalid username or password.";
    83      if (error.message !== "Unauthorized") {
    84          message = error.message;
    85      }
    86      return (
    87        <div className="login-page__error">Unable to log in: { message }</div>
    88      );
    89    }
    90  
    91    render() {
    92      const inputClasses = classNames("input-text", {
    93        "input-text--error": !!this.props.loginState.error,
    94      });
    95  
    96      return (
    97        <div className="login-page">
    98          <Helmet title="Login" />
    99          <div className="content">
   100            <section className="section login-page__info">
   101              <img className="logo" alt="CockroachDB" src={logo} />
   102              <InfoBox>
   103                <h4 className="login-note-box__heading">Note:</h4>
   104                <p>
   105                  A user with a password is required to log in to the UI
   106                  on secure clusters.
   107                </p>
   108                <p className="login-note-box__blurb">
   109                  Create a user with this SQL command:
   110                </p>
   111                <pre className="login-note-box__sql-command">
   112                  <span className="sql-keyword">CREATE USER</span>
   113                  {" "}craig{" "}
   114                  <span className="sql-keyword">WITH PASSWORD</span>
   115                  {" "}
   116                  <span className="sql-string">'cockroach'</span>
   117                  <span className="sql-keyword">;</span>
   118                </pre>
   119                <p className="aside">
   120                  <a href={docsURL.adminUILoginNoVersion} className="login-docs-link" target="_blank">
   121                    <span className="login-docs-link__icon" dangerouslySetInnerHTML={trustIcon(docsIcon)} />
   122                    <span className="login-docs-link__text">Read more about configuring login</span>
   123                  </a>
   124                </p>
   125              </InfoBox>
   126            </section>
   127            <section className="section login-page__form">
   128              <div className="form-container">
   129                <h1 className="base-heading heading">Log in to the Web UI</h1>
   130                {this.renderError()}
   131                <form onSubmit={this.handleSubmit} className="form-internal" method="post">
   132                  <input
   133                    type="text"
   134                    name="username"
   135                    className={inputClasses}
   136                    onChange={this.handleUpdateUsername}
   137                    value={this.state.username}
   138                    placeholder="Username"
   139                  />
   140                  <input
   141                    type="password"
   142                    name="password"
   143                    className={inputClasses}
   144                    onChange={this.handleUpdatePassword}
   145                    value={this.state.password}
   146                    placeholder="Password"
   147                  />
   148                  <input
   149                    type="submit"
   150                    className="submit-button"
   151                    disabled={this.props.loginState.inProgress}
   152                    value={this.props.loginState.inProgress ? "Logging in..." : "Log In"}
   153                  />
   154                </form>
   155              </div>
   156            </section>
   157          </div>
   158        </div>
   159      );
   160    }
   161  }
   162  
   163  // tslint:disable-next-line:variable-name
   164  const LoginPageConnected = withRouter(connect(
   165    (state: AdminUIState) => {
   166      return {
   167        loginState: state.login,
   168        location: state.router.location,
   169      };
   170    },
   171    (dispatch) => ({
   172      handleLogin: (username: string, password: string) => {
   173        return dispatch(doLogin(username, password));
   174      },
   175    }),
   176  )(LoginPage));
   177  
   178  export default LoginPageConnected;