Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ui: style login page #26066

Merged
merged 12 commits into from
Jun 7, 2018
1 change: 1 addition & 0 deletions pkg/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -1928,6 +1928,7 @@ func serveUIAssets(fileServer http.Handler, cfg Config) http.Handler {
tmplArgs := ui.IndexHTMLArgs{
ExperimentalUseLogin: cfg.EnableWebSessionAuthentication,
LoginEnabled: cfg.RequireWebSession(),
Tag: build.GetInfo().Tag,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like this is what broke the ServeIndexHTML test in CI. It's pretty brittle — asserts the exact HTML that comes out. Not sure how to make that always pass, since the build tag will keep changing… Maybe can just sprintf it into the expected HTML.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests updated.

Version: build.VersionPrefix(),
}
loggedInUser, ok := request.Context().Value(webSessionUserKey{}).(string)
Expand Down
9 changes: 6 additions & 3 deletions pkg/server/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -998,7 +998,8 @@ func TestServeIndexHTML(t *testing.T) {
expected := fmt.Sprintf(
htmlTemplate,
fmt.Sprintf(
`{"ExperimentalUseLogin":false,"LoginEnabled":false,"LoggedInUser":null,"Version":"%s"}`,
`{"ExperimentalUseLogin":false,"LoginEnabled":false,"LoggedInUser":null,"Tag":"%s","Version":"%s"}`,
build.GetInfo().Tag,
build.VersionPrefix(),
),
)
Expand Down Expand Up @@ -1028,14 +1029,16 @@ func TestServeIndexHTML(t *testing.T) {
{
loggedInClient,
fmt.Sprintf(
`{"ExperimentalUseLogin":true,"LoginEnabled":true,"LoggedInUser":"authentic_user","Version":"%s"}`,
`{"ExperimentalUseLogin":true,"LoginEnabled":true,"LoggedInUser":"authentic_user","Tag":"%s","Version":"%s"}`,
build.GetInfo().Tag,
build.VersionPrefix(),
),
},
{
loggedOutClient,
fmt.Sprintf(
`{"ExperimentalUseLogin":true,"LoginEnabled":true,"LoggedInUser":null,"Version":"%s"}`,
`{"ExperimentalUseLogin":true,"LoginEnabled":true,"LoggedInUser":null,"Tag":"%s","Version":"%s"}`,
build.GetInfo().Tag,
build.VersionPrefix(),
),
},
Expand Down
Binary file added pkg/ui/assets/crdb.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions pkg/ui/assets/docs.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
38 changes: 38 additions & 0 deletions pkg/ui/assets/unlocked.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion pkg/ui/ccl/src/routes/visualization.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import ClusterOverview from "src/views/cluster/containers/clusterOverview";

export const CLUSTERVIZ_ROOT = "/overview/map";

export default function(): JSX.Element {
export default function createNodeMapRoutes(): JSX.Element {
return (
<Route path="overview" component={ ClusterOverview } >
<IndexRedirect to="list" />
Expand Down
2 changes: 1 addition & 1 deletion pkg/ui/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ ReactDOM.render(
<Provider store={store}>
<Router history={history}>
{ /* login */}
{ loginRoutes() }
{ loginRoutes(store) }

<Route path="/" component={Layout}>
<IndexRedirect to="overview" />
Expand Down
24 changes: 18 additions & 6 deletions pkg/ui/src/redux/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { createSelector } from "reselect";
import { createPath } from "src/hacks/createPath";
import { userLogin, userLogout } from "src/util/api";
import { AdminUIState } from "src/redux/state";
import { LOGIN_PAGE } from "src/routes/login";
import { LOGIN_PAGE, LOGOUT_PAGE } from "src/routes/login";
import { cockroach } from "src/js/protos";
import { getDataFromServer } from "src/util/dataFromServer";

Expand Down Expand Up @@ -100,8 +100,20 @@ export const selectLoginState = createSelector(
},
);

function shouldRedirect(location: Location) {
if (!location) {
return false;
}

if (location.pathname === LOGOUT_PAGE) {
return false;
}

return true;
}

export function getLoginPage(location: Location) {
const query = !location ? undefined : {
const query = !shouldRedirect(location) ? undefined : {
redirectTo: createPath({
pathname: location.pathname,
search: location.search,
Expand All @@ -119,7 +131,7 @@ export function getLoginPage(location: Location) {

export interface LoginAPIState {
loggedInUser: string;
error: string;
error: Error;
inProgress: boolean;
}

Expand Down Expand Up @@ -153,10 +165,10 @@ function loginSuccess(loggedInUser: string): LoginSuccessAction {

interface LoginFailureAction extends Action {
type: typeof LOGIN_FAILURE;
error: string;
error: Error;
}

function loginFailure(error: string): LoginFailureAction {
function loginFailure(error: Error): LoginFailureAction {
return {
type: LOGIN_FAILURE,
error,
Expand All @@ -180,7 +192,7 @@ export function doLogin(username: string, password: string): ThunkAction<Promise
return userLogin(loginReq)
.then(
() => { dispatch(loginSuccess(username)); },
(err) => { dispatch(loginFailure(err.toString())); },
(err) => { dispatch(loginFailure(err)); },
);
};
}
Expand Down
21 changes: 19 additions & 2 deletions pkg/ui/src/routes/login.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,29 @@
import React from "react";
import { Route } from "react-router";
import { Store } from "redux";

import { doLogout, selectLoginState } from "src/redux/login";
import { AdminUIState } from "src/redux/state";
import LoginPage from "src/views/login/loginPage";

export const LOGIN_PAGE = "/login";
export const LOGOUT_PAGE = "/logout";

export default function createLoginRoutes(store: Store<AdminUIState>): JSX.Element {
function handleLogout(_nextState: any, replace: (route: string) => {}) {
const loginState = selectLoginState(store.getState());

if (!loginState.loggedInUser()) {
return replace(LOGIN_PAGE);
}

store.dispatch(doLogout());
}

export default function(): JSX.Element {
return (
<Route path={LOGIN_PAGE} component={ LoginPage } />
<React.Fragment>
<Route path={LOGIN_PAGE} component={ LoginPage } />
<Route path={LOGOUT_PAGE} onEnter={ handleLogout } />
</React.Fragment>
);
}
2 changes: 1 addition & 1 deletion pkg/ui/src/routes/visualization.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class NodesWrapper extends React.Component<{}, {}> {
}
}

export default function(): JSX.Element {
export default function createClusterOverviewRoutes(): JSX.Element {
return (
<Route path="overview" component={ ClusterOverview } >
<IndexRedirect to="list" />
Expand Down
1 change: 1 addition & 0 deletions pkg/ui/src/util/dataFromServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export interface DataFromServer {
ExperimentalUseLogin: boolean;
LoginEnabled: boolean;
LoggedInUser: string;
Tag: string;
Version: string;
}

Expand Down
28 changes: 19 additions & 9 deletions pkg/ui/src/views/app/components/layoutSidebar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ import { Link } from "react-router";

import { AdminUIState } from "src/redux/state";
import { selectLoginState, LoginState, doLogout } from "src/redux/login";
import { LOGOUT_PAGE } from "src/routes/login";
import { cockroachIcon } from "src/views/shared/components/icons";
import { trustIcon } from "src/util/trust";

import homeIcon from "!!raw-loader!assets/home.svg";
import metricsIcon from "!!raw-loader!assets/metrics.svg";
import databasesIcon from "!!raw-loader!assets/databases.svg";
import jobsIcon from "!!raw-loader!assets/jobs.svg";
import unlockedIcon from "!!raw-loader!assets/unlocked.svg";

interface IconLinkProps {
icon: string;
Expand Down Expand Up @@ -69,7 +71,13 @@ function LoginIndicator({ loginState, handleLogout }: LoginIndicatorProps) {
}

if (!loginState.loginEnabled()) {
return (<div className="login-indicator">Insecure mode</div>);
return (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see this indicator when I start a cluster in insecure mode. Just blank space above the cockroach icon.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure you had the environment variable set? I am seeing it...

<li className="login-indicator login-indicator--insecure">
<div className="image-container"
dangerouslySetInnerHTML={trustIcon(unlockedIcon)}/>
<div>Insecure mode</div>
</li>
);
}

const user = loginState.loggedInUser();
Expand All @@ -78,11 +86,15 @@ function LoginIndicator({ loginState, handleLogout }: LoginIndicatorProps) {
}

return (
<div className="login-indicator">
Logged in as {user}
<br />
<button onClick={handleLogout}>Logout</button>
</div>
<li className="login-indicator">
<div
className="login-indicator__initial"
title={`Signed in as ${user}`}
>
{ user[0] }
</div>
<Link to={LOGOUT_PAGE} onClick={handleLogout}>Sign Out</Link>
</li>
);
}

Expand Down Expand Up @@ -113,10 +125,8 @@ export default class Sidebar extends React.Component {
<IconLink to="/databases" icon={databasesIcon} title="Databases" activeFor="/database" />
<IconLink to="/jobs" icon={jobsIcon} title="Jobs" />
</ul>
<div className="logged-in-user">
<LoginIndicatorConnected />
</div>
<ul className="navigation-bar__list navigation-bar__list--bottom">
<LoginIndicatorConnected />
<IconLink to="/" icon={cockroachIcon} className="cockroach" />
</ul>
</nav>
Expand Down
102 changes: 102 additions & 0 deletions pkg/ui/src/views/login/loginPage.styl
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
@require "~styl/base/palette.styl"

.login-page
display flex
align-items center
justify-content center
height 100%
width 100%
position absolute
background-color white

.content
border thin solid $table-border-color
position relative

&__info.section
width 486px
display inline-block
padding 24px

&__form.section
background-color $table-border-color
width 486px
display inline-block
padding 108px 53px 138px

.heading
text-transform none
letter-spacing 1px
margin-bottom 24px

.aside
margin-top 12px
line-height 24px
letter-spacing 1px
color $body-color
width 350px

.version
color $body-color
position absolute
left 12px
top 12px

.version-tag
color #54b30e

.logo
height 32px

.docs-link
display block
margin-top 32px
text-decoration none
color $link-color

&__icon
position relative
top 6px

&__text
font-size 17px
line-height 30px
margin-left 6px

.input-text
width 100%
font-size 14px
line-height 17px
padding 12px 18px
vertical-align middle
border 1px solid $button-border-color
border-radius 3px
margin 12px 0
color $body-color
display block

&--error
border-color $alert-color

.submit-button
width 100%
text-transform uppercase
font-size 14px
letter-spacing 2px
line-height 17px
padding 12px 24px
vertical-align middle
border 0px none
border-radius 3px
margin 24px 0
color $background-color
background-color $link-color
cursor pointer
display block

&:disabled
background-color #90b8ef

&__error
color $alert-color
margin 12px 0
Loading