username: string;
prefs: UserPrefs;
isAdmin: boolean;
+ isActive: boolean;
}
export const getUserFullname = (user?: User) => {
export const USER_UUID_KEY = 'userUuid';
export const USER_OWNER_UUID_KEY = 'userOwnerUuid';
export const USER_IS_ADMIN = 'isAdmin';
+export const USER_IS_ACTIVE = 'isActive';
export const USER_USERNAME = 'username';
export const USER_PREFS = 'prefs';
uuid: string;
owner_uuid: string;
is_admin: boolean;
+ is_active: boolean;
username: string;
prefs: UserPrefs;
}
return localStorage.getItem(USER_IS_ADMIN) === 'true';
}
+ public getIsActive(): boolean {
+ console.log(`uia ${localStorage.getItem(USER_IS_ACTIVE)}`)
+ return localStorage.getItem(USER_IS_ACTIVE) === 'true';
+ }
+
public getUser(): User | undefined {
const email = localStorage.getItem(USER_EMAIL_KEY);
const firstName = localStorage.getItem(USER_FIRST_NAME_KEY);
const uuid = this.getUuid();
const ownerUuid = this.getOwnerUuid();
const isAdmin = this.getIsAdmin();
+ const isActive = this.getIsActive();
const username = localStorage.getItem(USER_USERNAME);
const prefs = JSON.parse(localStorage.getItem(USER_PREFS) || '{"profile": {}}');
+ console.log(`leg! ${isActive}`)
+
return email && firstName && lastName && uuid && ownerUuid && username && prefs
- ? { email, firstName, lastName, uuid, ownerUuid, isAdmin, username, prefs }
+ ? { email, firstName, lastName, uuid, ownerUuid, isAdmin, isActive, username, prefs }
: undefined;
}
localStorage.setItem(USER_UUID_KEY, user.uuid);
localStorage.setItem(USER_OWNER_UUID_KEY, user.ownerUuid);
localStorage.setItem(USER_IS_ADMIN, JSON.stringify(user.isAdmin));
+ localStorage.setItem(USER_IS_ACTIVE, JSON.stringify(user.isActive));
localStorage.setItem(USER_USERNAME, user.username);
localStorage.setItem(USER_PREFS, JSON.stringify(user.prefs));
}
localStorage.removeItem(USER_UUID_KEY);
localStorage.removeItem(USER_OWNER_UUID_KEY);
localStorage.removeItem(USER_IS_ADMIN);
+ localStorage.removeItem(USER_IS_ACTIVE);
localStorage.removeItem(USER_USERNAME);
localStorage.removeItem(USER_PREFS);
}
uuid: resp.data.uuid,
ownerUuid: resp.data.owner_uuid,
isAdmin: resp.data.is_admin,
+ isActive: resp.data.is_active,
username: resp.data.username,
prefs
};
ownerUuid: user.owner_uuid,
email: user.email,
isAdmin: user.is_admin,
+ isActive: user.is_active,
username: user.username,
prefs: user.prefs
},
USER_LAST_NAME_KEY,
USER_OWNER_UUID_KEY,
USER_UUID_KEY,
- USER_IS_ADMIN, USER_USERNAME, USER_PREFS
+ USER_IS_ADMIN,
+ USER_IS_ACTIVE,
+ USER_USERNAME,
+ USER_PREFS
} from "~/services/auth-service/auth-service";
import 'jest-localstorage-mock';
localStorage.setItem(USER_USERNAME, "username");
localStorage.setItem(USER_PREFS, JSON.stringify({}));
localStorage.setItem(USER_OWNER_UUID_KEY, "ownerUuid");
- localStorage.setItem(USER_IS_ADMIN, JSON.stringify("false"));
+ localStorage.setItem(USER_IS_ADMIN, JSON.stringify(false));
+ localStorage.setItem(USER_IS_ACTIVE, JSON.stringify(true));
const config: any = {
rootUrl: "https://zzzzz.arvadosapi.com",
ownerUuid: "ownerUuid",
username: "username",
prefs: {},
- isAdmin: false
+ isAdmin: false,
+ isActive: true
}
});
});
ownerUuid: "ownerUuid",
username: "username",
prefs: {},
- isAdmin: false
+ isAdmin: false,
+ isActive: true
};
const state = reducer(initialState, authActions.INIT({ user, token: "token" }));
expect(state).toEqual({
ownerUuid: "ownerUuid",
username: "username",
prefs: {},
- isAdmin: false
+ isAdmin: false,
+ isActive: true
};
const state = reducer(initialState, authActions.USER_DETAILS_SUCCESS(user));
ownerUuid: "ownerUuid",
username: "username",
prefs: {},
- isAdmin: false
+ isAdmin: false,
+ isActive: true
}
});
});
<MenuItem disabled>
{getUserFullname(user)} {user.uuid.substr(0, 5) !== localCluster && `(${user.uuid.substr(0, 5)})`}
</MenuItem>
- <MenuItem onClick={() => dispatch(openUserVirtualMachines())}>Virtual Machines</MenuItem>
- {!user.isAdmin && <MenuItem onClick={() => dispatch(openRepositoriesPanel())}>Repositories</MenuItem>}
- <MenuItem onClick={() => dispatch(openCurrentTokenDialog)}>Current token</MenuItem>
- <MenuItem onClick={() => dispatch(navigateToSshKeysUser)}>Ssh Keys</MenuItem>
- <MenuItem onClick={() => dispatch(navigateToSiteManager)}>Site Manager</MenuItem>
- <MenuItem onClick={() => dispatch(navigateToMyAccount)}>My account</MenuItem>
+ {user.isActive ? <>
+ <MenuItem onClick={() => dispatch(openUserVirtualMachines())}>Virtual Machines</MenuItem>
+ {!user.isAdmin && <MenuItem onClick={() => dispatch(openRepositoriesPanel())}>Repositories</MenuItem>}
+ <MenuItem onClick={() => dispatch(openCurrentTokenDialog)}>Current token</MenuItem>
+ <MenuItem onClick={() => dispatch(navigateToSshKeysUser)}>Ssh Keys</MenuItem>
+ <MenuItem onClick={() => dispatch(navigateToSiteManager)}>Site Manager</MenuItem>
+ <MenuItem onClick={() => dispatch(navigateToMyAccount)}>My account</MenuItem>)
+ </> : null}
<MenuItem>
<a href={`${workbenchURL.replace(/\/$/, "")}/${wb1URL(currentRoute)}?api_token=${apiToken}`}
className={classes.link}>
xs={6}
container
alignItems="center">
- {props.user && <SearchBar />}
+ {props.user && props.user.isActive && <SearchBar />}
</Grid>
<Grid
item
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from 'react';
+import { connect, DispatchProp } from 'react-redux';
+import { Grid, Typography, Button, Select } from '@material-ui/core';
+import { StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core/styles';
+import { login, authActions } from '~/store/auth/auth-action';
+import { ArvadosTheme } from '~/common/custom-theme';
+import { RootState } from '~/store/store';
+import * as classNames from 'classnames';
+
+type CssRules = 'root' | 'container' | 'title' | 'content' | 'content__bolder' | 'button';
+
+const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
+ root: {
+ position: 'relative',
+ backgroundColor: theme.palette.grey["200"],
+ '&::after': {
+ content: `''`,
+ position: 'absolute',
+ top: 0,
+ left: 0,
+ bottom: 0,
+ right: 0,
+ background: 'url("arvados-logo-big.png") no-repeat center center',
+ opacity: 0.2,
+ }
+ },
+ container: {
+ width: '560px',
+ zIndex: 10
+ },
+ title: {
+ marginBottom: theme.spacing.unit * 6,
+ color: theme.palette.grey["800"]
+ },
+ content: {
+ marginBottom: theme.spacing.unit * 3,
+ lineHeight: '1.2rem',
+ color: theme.palette.grey["800"]
+ },
+ 'content__bolder': {
+ fontWeight: 'bolder'
+ },
+ button: {
+ boxShadow: 'none'
+ }
+});
+
+type LoginPanelProps = DispatchProp<any> & WithStyles<CssRules> & {
+ remoteHosts: { [key: string]: string },
+ homeCluster: string,
+ uuidPrefix: string
+};
+
+export const InactivePanel = withStyles(styles)(
+ connect((state: RootState) => ({
+ remoteHosts: state.auth.remoteHosts,
+ homeCluster: state.auth.homeCluster,
+ uuidPrefix: state.auth.localCluster
+ }))(({ classes, dispatch, remoteHosts, homeCluster, uuidPrefix }: LoginPanelProps) =>
+ <Grid container justify="center" alignItems="center"
+ className={classes.root}
+ style={{ marginTop: 56, overflowY: "auto", height: "100%" }}>
+ <Grid item className={classes.container}>
+ <Typography variant='h6' align="center" className={classes.title}>
+ Hi! You're logged in, but...
+ </Typography>
+ <Typography>
+ Your account is inactive.
+
+ An administrator must activate your account before you can get any further.
+ </Typography>
+ </Grid>
+ </Grid >
+ ));
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from 'react';
+import { connect, DispatchProp } from 'react-redux';
+import { Grid, Typography, Button, Select, FormControl } from '@material-ui/core';
+import { StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core/styles';
+import { login, authActions } from '~/store/auth/auth-action';
+import { ArvadosTheme } from '~/common/custom-theme';
+import { RootState } from '~/store/store';
+import * as classNames from 'classnames';
+
+type CssRules = 'root' | 'container' | 'title' | 'content' | 'content__bolder' | 'button';
+
+const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
+ root: {
+ position: 'relative',
+ backgroundColor: theme.palette.grey["200"],
+ '&::after': {
+ content: `''`,
+ position: 'absolute',
+ top: 0,
+ left: 0,
+ bottom: 0,
+ right: 0,
+ background: 'url("arvados-logo-big.png") no-repeat center center',
+ opacity: 0.2,
+ }
+ },
+ container: {
+ width: '560px',
+ zIndex: 10
+ },
+ title: {
+ marginBottom: theme.spacing.unit * 6,
+ color: theme.palette.grey["800"]
+ },
+ content: {
+ marginBottom: theme.spacing.unit * 3,
+ lineHeight: '1.2rem',
+ color: theme.palette.grey["800"]
+ },
+ 'content__bolder': {
+ fontWeight: 'bolder'
+ },
+ button: {
+ boxShadow: 'none'
+ }
+});
+
+type LoginPanelProps = DispatchProp<any> & WithStyles<CssRules> & {
+ remoteHosts: { [key: string]: string },
+ homeCluster: string,
+ uuidPrefix: string
+};
+
+export const LoginPanel = withStyles(styles)(
+ connect((state: RootState) => ({
+ remoteHosts: state.auth.remoteHosts,
+ homeCluster: state.auth.homeCluster,
+ uuidPrefix: state.auth.localCluster
+ }))(({ classes, dispatch, remoteHosts, homeCluster, uuidPrefix }: LoginPanelProps) =>
+ <Grid container justify="center" alignItems="center"
+ className={classes.root}
+ style={{ marginTop: 56, overflowY: "auto" }}>
+ <Grid item className={classes.container}>
+ <Typography variant='h6' align="center" className={classes.title}>
+ Welcome to the Arvados Workbench
+ </Typography>
+ <Typography className={classes.content}>
+ The "Log in" button below will show you a Google sign-in page.
+ After you assure Google that you want to log in here with your Google account, you will be redirected back here to Arvados Workbench.
+ </Typography>
+ <Typography className={classes.content}>
+ If you have never used Arvados Workbench before, logging in for the first time will automatically create a new account.
+ </Typography>
+ <Typography variant='body1' className={classNames(classes.content, classes.content__bolder)}>
+ IMPORTANT: Please keep in mind to store exploratory data only but not any information used for clinical decision making.
+ </Typography>
+ <Typography className={classes.content}>
+ Arvados Workbench uses your name and email address only for identification, and does not retrieve any other personal information from Google.
+ </Typography>
+
+ {Object.keys(remoteHosts).length > 1 &&
+ <Typography component="div" align="right">
+ <label>Please select the cluster that hosts your user account:</label>
+ <Select native value={homeCluster} style={{ margin: "1em" }}
+ onChange={(event) => dispatch(authActions.SET_HOME_CLUSTER(event.target.value))}>
+ {Object.keys(remoteHosts).map((k) => <option key={k} value={k}>{k}</option>)}
+ </Select>
+ </Typography>}
+
+ <Typography component="div" align="right">
+ <Button variant="contained" color="primary" style={{ margin: "1em" }} className={classes.button}
+ onClick={() => dispatch(login(uuidPrefix, remoteHosts[homeCluster]))}>
+ Log in to {uuidPrefix}
+ {uuidPrefix !== homeCluster &&
+ <span> with user from {homeCluster}</span>}
+ </Button>
+ </Typography>
+ </Grid>
+ </Grid >
+ ));
}))(({ classes, dispatch, remoteHosts, homeCluster, uuidPrefix }: LoginPanelProps) =>
<Grid container justify="center" alignItems="center"
className={classes.root}
- style={{ marginTop: 56, overflowY: "auto" }}>
+ style={{ marginTop: 56, overflowY: "auto", height: "100%" }}>
<Grid item className={classes.container}>
<Typography variant='h6' align="center" className={classes.title}>
Welcome to the Arvados Workbench
import { ArvadosTheme } from '~/common/custom-theme';
import { WorkbenchPanel } from '~/views/workbench/workbench';
import { LoginPanel } from '~/views/login-panel/login-panel';
+import { InactivePanel } from '~/views/inactive-panel/inactive-panel';
import { WorkbenchLoadingScreen } from '~/views/workbench/workbench-loading-screen';
import { MainAppBar } from '~/views-components/main-app-bar/main-app-bar';
{working ? <LinearProgress color="secondary" /> : null}
</MainAppBar>
<Grid container direction="column" className={classes.root}>
- {user ? <WorkbenchPanel /> : <LoginPanel />}
+ {user ? (user.isActive ? <WorkbenchPanel /> : <InactivePanel />) : <LoginPanel />}
</Grid>
</>
);