[ARVADOS-WORKBENCH2] created: 1.3.0-358-gd4f479b1
Git user
git at public.curoverse.com
Tue Feb 12 16:41:07 EST 2019
at d4f479b1c2bd4bb67d5e69f792b503f375d6dfc9 (commit)
commit d4f479b1c2bd4bb67d5e69f792b503f375d6dfc9
Author: Peter Amstutz <pamstutz at veritasgenetics.com>
Date: Tue Feb 12 16:24:56 2019 -0500
14720: Add UUID to My account page. Layout fixes.
Makes a number of UI improvements related to logging in as a federated
user.
* My account page includes UUID
* Replace "identityUrl" in user model with "username"
* Rework layout of "My Account" page to reflow properly when view is
narrow
* Rework layout of login page, add scrolling when view is short
Arvados-DCO-1.1-Signed-off-by: Peter Amstutz <pamstutz at veritasgenetics.com>
diff --git a/src/models/user.ts b/src/models/user.ts
index a7b8458b..6477dc53 100644
--- a/src/models/user.ts
+++ b/src/models/user.ts
@@ -20,7 +20,7 @@ export interface User {
lastName: string;
uuid: string;
ownerUuid: string;
- identityUrl: string;
+ username: string;
prefs: UserPrefs;
isAdmin: boolean;
}
@@ -35,10 +35,9 @@ export interface UserResource extends Resource {
username: string;
firstName: string;
lastName: string;
- identityUrl: string;
isAdmin: boolean;
prefs: UserPrefs;
defaultOwnerUuid: string;
isActive: boolean;
writableBy: string[];
-}
\ No newline at end of file
+}
diff --git a/src/services/auth-service/auth-service.ts b/src/services/auth-service/auth-service.ts
index fff7d577..11e3e776 100644
--- a/src/services/auth-service/auth-service.ts
+++ b/src/services/auth-service/auth-service.ts
@@ -17,7 +17,7 @@ export const USER_LAST_NAME_KEY = 'userLastName';
export const USER_UUID_KEY = 'userUuid';
export const USER_OWNER_UUID_KEY = 'userOwnerUuid';
export const USER_IS_ADMIN = 'isAdmin';
-export const USER_IDENTITY_URL = 'identityUrl';
+export const USER_USERNAME = 'username';
export const USER_PREFS = 'prefs';
export interface UserDetailsResponse {
@@ -27,7 +27,7 @@ export interface UserDetailsResponse {
uuid: string;
owner_uuid: string;
is_admin: boolean;
- identity_url: string;
+ username: string;
prefs: UserPrefs;
}
@@ -69,11 +69,11 @@ export class AuthService {
const uuid = this.getUuid();
const ownerUuid = this.getOwnerUuid();
const isAdmin = this.getIsAdmin();
- const identityUrl = localStorage.getItem(USER_IDENTITY_URL);
+ const username = localStorage.getItem(USER_USERNAME);
const prefs = JSON.parse(localStorage.getItem(USER_PREFS) || '{"profile": {}}');
- return email && firstName && lastName && uuid && ownerUuid && identityUrl && prefs
- ? { email, firstName, lastName, uuid, ownerUuid, isAdmin, identityUrl, prefs }
+ return email && firstName && lastName && uuid && ownerUuid && username && prefs
+ ? { email, firstName, lastName, uuid, ownerUuid, isAdmin, username, prefs }
: undefined;
}
@@ -84,7 +84,7 @@ export class AuthService {
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_IDENTITY_URL, user.identityUrl);
+ localStorage.setItem(USER_USERNAME, user.username);
localStorage.setItem(USER_PREFS, JSON.stringify(user.prefs));
}
@@ -95,7 +95,7 @@ export class AuthService {
localStorage.removeItem(USER_UUID_KEY);
localStorage.removeItem(USER_OWNER_UUID_KEY);
localStorage.removeItem(USER_IS_ADMIN);
- localStorage.removeItem(USER_IDENTITY_URL);
+ localStorage.removeItem(USER_USERNAME);
localStorage.removeItem(USER_PREFS);
}
@@ -125,7 +125,7 @@ export class AuthService {
uuid: resp.data.uuid,
ownerUuid: resp.data.owner_uuid,
isAdmin: resp.data.is_admin,
- identityUrl: resp.data.identity_url,
+ username: resp.data.username,
prefs
};
})
diff --git a/src/store/advanced-tab/advanced-tab.tsx b/src/store/advanced-tab/advanced-tab.tsx
index 3d10e54a..b1f0f983 100644
--- a/src/store/advanced-tab/advanced-tab.tsx
+++ b/src/store/advanced-tab/advanced-tab.tsx
@@ -554,7 +554,7 @@ const keepServiceApiResponse = (apiResponse: KeepServiceResource) => {
const userApiResponse = (apiResponse: UserResource) => {
const {
uuid, ownerUuid, createdAt, modifiedAt, modifiedByClientUuid, modifiedByUserUuid,
- email, firstName, lastName, identityUrl, isActive, isAdmin, prefs, defaultOwnerUuid, username
+ email, firstName, lastName, username, isActive, isAdmin, prefs, defaultOwnerUuid,
} = apiResponse;
const response = `
"uuid": "${uuid}",
@@ -566,7 +566,7 @@ const userApiResponse = (apiResponse: UserResource) => {
"email": "${email}",
"first_name": "${firstName}",
"last_name": "${stringify(lastName)}",
-"identity_url": "${identityUrl}",
+"username": "${username}",
"is_active": "${isActive},
"is_admin": "${isAdmin},
"prefs": "${stringifyObject(prefs)},
@@ -646,4 +646,4 @@ const linkApiResponse = (apiResponse: LinkResource) => {
"properties": "${JSON.stringify(properties, null, 2)}"`;
return <span style={{ marginLeft: '-15px' }}>{'{'} {response} {'\n'} <span style={{ marginLeft: '-15px' }}>{'}'}</span></span>;
-};
\ No newline at end of file
+};
diff --git a/src/store/auth/auth-action-session.ts b/src/store/auth/auth-action-session.ts
index b32e2050..986230ed 100644
--- a/src/store/auth/auth-action-session.ts
+++ b/src/store/auth/auth-action-session.ts
@@ -92,7 +92,7 @@ const clusterLogin = async (clusterId: string, baseUrl: string, activeSession: S
ownerUuid: user.owner_uuid,
email: user.email,
isAdmin: user.is_admin,
- identityUrl: user.identity_url,
+ username: user.username,
prefs: user.prefs
},
token: saltedToken
diff --git a/src/store/auth/auth-action.test.ts b/src/store/auth/auth-action.test.ts
index e5775a49..2e36b107 100644
--- a/src/store/auth/auth-action.test.ts
+++ b/src/store/auth/auth-action.test.ts
@@ -11,7 +11,7 @@ import {
USER_LAST_NAME_KEY,
USER_OWNER_UUID_KEY,
USER_UUID_KEY,
- USER_IS_ADMIN, USER_IDENTITY_URL, USER_PREFS
+ USER_IS_ADMIN, USER_USERNAME, USER_PREFS
} from "~/services/auth-service/auth-service";
import 'jest-localstorage-mock';
@@ -25,8 +25,8 @@ describe('auth-actions', () => {
let reducer: (state: AuthState | undefined, action: AuthAction) => any;
let store: RootStore;
const actions: ApiActions = {
- progressFn: (id: string, working: boolean) => {},
- errorFn: (id: string, message: string) => {}
+ progressFn: (id: string, working: boolean) => { },
+ errorFn: (id: string, message: string) => { }
};
beforeEach(() => {
@@ -42,7 +42,7 @@ describe('auth-actions', () => {
localStorage.setItem(USER_FIRST_NAME_KEY, "John");
localStorage.setItem(USER_LAST_NAME_KEY, "Doe");
localStorage.setItem(USER_UUID_KEY, "uuid");
- localStorage.setItem(USER_IDENTITY_URL, "identityUrl");
+ 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"));
@@ -83,7 +83,7 @@ describe('auth-actions', () => {
lastName: "Doe",
uuid: "uuid",
ownerUuid: "ownerUuid",
- identityUrl: "identityUrl",
+ username: "username",
prefs: {},
isAdmin: false
}
@@ -111,5 +111,3 @@ describe('auth-actions', () => {
});
*/
});
-
-
diff --git a/src/store/auth/auth-reducer.test.ts b/src/store/auth/auth-reducer.test.ts
index 773f9f82..f527edec 100644
--- a/src/store/auth/auth-reducer.test.ts
+++ b/src/store/auth/auth-reducer.test.ts
@@ -13,8 +13,8 @@ import { ApiActions } from "~/services/api/api-actions";
describe('auth-reducer', () => {
let reducer: (state: AuthState | undefined, action: AuthAction) => any;
const actions: ApiActions = {
- progressFn: (id: string, working: boolean) => {},
- errorFn: (id: string, message: string) => {}
+ progressFn: (id: string, working: boolean) => { },
+ errorFn: (id: string, message: string) => { }
};
beforeAll(() => {
@@ -30,7 +30,7 @@ describe('auth-reducer', () => {
lastName: "Doe",
uuid: "uuid",
ownerUuid: "ownerUuid",
- identityUrl: "identityUrl",
+ username: "username",
prefs: {},
isAdmin: false
};
@@ -64,7 +64,7 @@ describe('auth-reducer', () => {
lastName: "Doe",
uuid: "uuid",
ownerUuid: "ownerUuid",
- identityUrl: "identityUrl",
+ username: "username",
prefs: {},
isAdmin: false
};
@@ -80,7 +80,7 @@ describe('auth-reducer', () => {
lastName: "Doe",
uuid: "uuid",
ownerUuid: "ownerUuid",
- identityUrl: "identityUrl",
+ username: "username",
prefs: {},
isAdmin: false
}
diff --git a/src/views-components/main-app-bar/account-menu.tsx b/src/views-components/main-app-bar/account-menu.tsx
index b50ee336..2aec77e2 100644
--- a/src/views-components/main-app-bar/account-menu.tsx
+++ b/src/views-components/main-app-bar/account-menu.tsx
@@ -3,7 +3,7 @@
// SPDX-License-Identifier: AGPL-3.0
import * as React from "react";
-import { MenuItem } from "@material-ui/core";
+import { MenuItem, Divider } from "@material-ui/core";
import { User, getUserFullname } from "~/models/user";
import { DropdownMenu } from "~/components/dropdown-menu/dropdown-menu";
import { UserPanelIcon } from "~/components/icon/icon";
@@ -37,8 +37,8 @@ export const AccountMenu = connect(mapStateToProps)(
id="account-menu"
title="Account Management"
key={currentRoute}>
- <MenuItem>
- {getUserFullname(user)} ({user.uuid.substr(0, 5)})
+ <MenuItem disabled>
+ {getUserFullname(user)}
</MenuItem>
<MenuItem onClick={() => dispatch(openUserVirtualMachines())}>Virtual Machines</MenuItem>
{!user.isAdmin && <MenuItem onClick={() => dispatch(openRepositoriesPanel())}>Repositories</MenuItem>}
@@ -46,6 +46,7 @@ export const AccountMenu = connect(mapStateToProps)(
<MenuItem onClick={() => dispatch(navigateToSshKeysUser)}>Ssh Keys</MenuItem>
<MenuItem onClick={() => dispatch(navigateToSiteManager)}>Site Manager</MenuItem>
<MenuItem onClick={() => dispatch(navigateToMyAccount)}>My account</MenuItem>
+ <Divider />
<MenuItem onClick={() => dispatch(logout())}>Logout</MenuItem>
</DropdownMenu>
: null);
diff --git a/src/views-components/user-dialog/attributes-dialog.tsx b/src/views-components/user-dialog/attributes-dialog.tsx
index 11594f5e..67b267ae 100644
--- a/src/views-components/user-dialog/attributes-dialog.tsx
+++ b/src/views-components/user-dialog/attributes-dialog.tsx
@@ -61,7 +61,7 @@ export const UserAttributesDialog = compose(
);
const attributes = (user: UserResource, classes: any) => {
- const { uuid, ownerUuid, createdAt, modifiedAt, modifiedByClientUuid, modifiedByUserUuid, firstName, lastName, href, identityUrl, username } = user;
+ const { uuid, ownerUuid, createdAt, modifiedAt, modifiedByClientUuid, modifiedByUserUuid, firstName, lastName, href, username } = user;
return (
<span>
<Grid container direction="row">
@@ -75,7 +75,7 @@ const attributes = (user: UserResource, classes: any) => {
{modifiedByClientUuid && <Grid item>Modified by client uuid</Grid>}
{uuid && <Grid item>uuid</Grid>}
{href && <Grid item>Href</Grid>}
- {identityUrl && <Grid item>Identity url</Grid>}
+ {username && <Grid item>Username</Grid>}
{username && <Grid item>Username</Grid>}
</Grid>
<Grid item xs={7} className={classes.leftContainer}>
@@ -88,7 +88,7 @@ const attributes = (user: UserResource, classes: any) => {
<Grid item>{modifiedByClientUuid}</Grid>
<Grid item>{uuid}</Grid>
<Grid item>{href}</Grid>
- <Grid item>{identityUrl}</Grid>
+ <Grid item>{username}</Grid>
<Grid item>{username}</Grid>
</Grid>
</Grid>
diff --git a/src/views/login-panel/login-panel.tsx b/src/views/login-panel/login-panel.tsx
index 12c7d6c7..eac4034b 100644
--- a/src/views/login-panel/login-panel.tsx
+++ b/src/views/login-panel/login-panel.tsx
@@ -61,7 +61,9 @@ export const LoginPanel = withStyles(styles)(
homeCluster: state.auth.homeCluster,
uuidPrefix: state.auth.localCluster
}))(({ classes, dispatch, remoteHosts, homeCluster, uuidPrefix }: LoginPanelProps) =>
- <Grid container direction="column" item xs alignItems="center" justify="center" className={classes.root}>
+ <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
@@ -98,5 +100,5 @@ export const LoginPanel = withStyles(styles)(
</Button>
</Typography>
</Grid>
- </Grid>
+ </Grid >
));
diff --git a/src/views/my-account-panel/my-account-panel-root.tsx b/src/views/my-account-panel/my-account-panel-root.tsx
index 819a16a4..e728c4eb 100644
--- a/src/views/my-account-panel/my-account-panel-root.tsx
+++ b/src/views/my-account-panel/my-account-panel-root.tsx
@@ -19,7 +19,7 @@ import {
} from '@material-ui/core';
import { ArvadosTheme } from '~/common/custom-theme';
import { User } from "~/models/user";
-import { MY_ACCOUNT_VALIDATION} from "~/validators/validators";
+import { MY_ACCOUNT_VALIDATION } from "~/validators/validators";
type CssRules = 'root' | 'gridItem' | 'label' | 'title' | 'actions';
@@ -45,7 +45,7 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
}
});
-export interface MyAccountPanelRootActionProps {}
+export interface MyAccountPanelRootActionProps { }
export interface MyAccountPanelRootDataProps {
isPristine: boolean;
@@ -54,13 +54,13 @@ export interface MyAccountPanelRootDataProps {
}
const RoleTypes = [
- {key: 'Bio-informatician', value: 'Bio-informatician'},
- {key: 'Data Scientist', value: 'Data Scientist'},
- {key: 'Analyst', value: 'Analyst'},
- {key: 'Researcher', value: 'Researcher'},
- {key: 'Software Developer', value: 'Software Developer'},
- {key: 'System Administrator', value: 'System Administrator'},
- {key: 'Other', value: 'Other'}
+ { key: 'Bio-informatician', value: 'Bio-informatician' },
+ { key: 'Data Scientist', value: 'Data Scientist' },
+ { key: 'Analyst', value: 'Analyst' },
+ { key: 'Researcher', value: 'Researcher' },
+ { key: 'Software Developer', value: 'Software Developer' },
+ { key: 'System Administrator', value: 'System Administrator' },
+ { key: 'Other', value: 'Other' }
];
type MyAccountPanelRootProps = InjectedFormProps<MyAccountPanelRootActionProps> & MyAccountPanelRootDataProps & WithStyles<CssRules>;
@@ -69,93 +69,96 @@ export const MyAccountPanelRoot = withStyles(styles)(
({ classes, isValid, handleSubmit, reset, isPristine, invalid, submitting }: MyAccountPanelRootProps) => {
return <Card className={classes.root}>
<CardContent>
- <Typography variant='h6' className={classes.title}>User profile</Typography>
<form onSubmit={handleSubmit}>
- <Grid container direction="row" spacing={24}>
- <Grid item xs={6}>
- <Grid item className={classes.gridItem}>
- <Field
- label="E-mail"
- name="email"
- component={TextField}
- disabled
- />
- </Grid>
- <Grid item className={classes.gridItem}>
- <Field
- label="First name"
- name="firstName"
- component={TextField}
- disabled
- />
- </Grid>
- <Grid item className={classes.gridItem}>
- <Field
- label="Identity URL"
- name="identityUrl"
- component={TextField}
- disabled
- />
- </Grid>
- <Grid item className={classes.gridItem}>
- <Field
- label="Organization"
- name="prefs.profile.organization"
- component={TextField}
- validate={MY_ACCOUNT_VALIDATION}
- required
- />
- </Grid>
- <Grid item className={classes.gridItem}>
- <Field
- label="Website"
- name="prefs.profile.website_url"
- component={TextField}
- />
- </Grid>
- <Grid item className={classes.gridItem}>
- <InputLabel className={classes.label} htmlFor="prefs.profile.role">Role</InputLabel>
- <Field
- id="prefs.profile.role"
- name="prefs.profile.role"
- component={NativeSelectField}
- items={RoleTypes}
- />
- </Grid>
+ <Grid container spacing={24}>
+ <Grid item xs={6} />
+ <Grid item className={classes.gridItem} sm={6} xs={12}>
+ <Field
+ label="UUID"
+ name="uuid"
+ component={TextField}
+ disabled
+ />
</Grid>
- <Grid item xs={6}>
- <Grid item className={classes.gridItem} />
- <Grid item className={classes.gridItem}>
- <Field
- label="Last name"
- name="lastName"
- component={TextField}
- disabled
- />
- </Grid>
- <Grid item className={classes.gridItem} />
- <Grid item className={classes.gridItem}>
- <Field
- label="E-mail at Organization"
- name="prefs.profile.organization_email"
- component={TextField}
- validate={MY_ACCOUNT_VALIDATION}
- required
- />
- </Grid>
+ <Grid item className={classes.gridItem} sm={6} xs={12}>
+ <Field
+ label="First name"
+ name="firstName"
+ component={TextField}
+ disabled
+ />
</Grid>
- <Grid item xs={12} className={classes.actions}>
+ <Grid item className={classes.gridItem} sm={6} xs={12}>
+ <Field
+ label="Last name"
+ name="lastName"
+ component={TextField}
+ disabled
+ />
+ </Grid>
+ <Grid item className={classes.gridItem} sm={6} xs={12}>
+ <Field
+ label="E-mail"
+ name="email"
+ component={TextField}
+ disabled
+ />
+ </Grid>
+ <Grid item className={classes.gridItem} sm={6} xs={12}>
+ <Field
+ label="Username"
+ name="username"
+ component={TextField}
+ disabled
+ />
+ </Grid>
+ <Grid item className={classes.gridItem} sm={6} xs={12}>
+ <Field
+ label="Organization"
+ name="prefs.profile.organization"
+ component={TextField}
+ validate={MY_ACCOUNT_VALIDATION}
+ required
+ />
+ </Grid>
+ <Grid item className={classes.gridItem} sm={6} xs={12}>
+ <Field
+ label="E-mail at Organization"
+ name="prefs.profile.organization_email"
+ component={TextField}
+ validate={MY_ACCOUNT_VALIDATION}
+ required
+ />
+ </Grid>
+ <Grid item className={classes.gridItem} sm={6} xs={12}>
+ <InputLabel className={classes.label} htmlFor="prefs.profile.role">Role</InputLabel>
+ <Field
+ id="prefs.profile.role"
+ name="prefs.profile.role"
+ component={NativeSelectField}
+ items={RoleTypes}
+ />
+ </Grid>
+ <Grid item className={classes.gridItem} sm={6} xs={12}>
+ <Field
+ label="Website"
+ name="prefs.profile.website_url"
+ component={TextField}
+ />
+ </Grid>
+ <Grid container direction="row" justify="flex-end" >
<Button color="primary" onClick={reset} disabled={isPristine}>Discard changes</Button>
<Button
color="primary"
variant="contained"
type="submit"
disabled={isPristine || invalid || submitting}>
- Save changes
- </Button>
+ Save changes
+ </Button>
</Grid>
</Grid>
- </form>
- </CardContent>
- </Card>;}
-);
\ No newline at end of file
+ </form >
+ </CardContent >
+ </Card >;
+ }
+);
commit dd664ff641e178a3efb9b2a58d08e5e84db0f0b5
Author: Peter Amstutz <pamstutz at veritasgenetics.com>
Date: Mon Feb 11 17:49:44 2019 -0500
14720: Better login button, tell you what cluster you're logging in to.
Arvados-DCO-1.1-Signed-off-by: Peter Amstutz <pamstutz at veritasgenetics.com>
diff --git a/src/index.tsx b/src/index.tsx
index f5eedc73..d0eb7d3b 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -60,7 +60,6 @@ import { groupActionSet } from '~/views-components/context-menu/action-sets/grou
import { groupMemberActionSet } from '~/views-components/context-menu/action-sets/group-member-action-set';
import { linkActionSet } from '~/views-components/context-menu/action-sets/link-action-set';
import { loadFileViewersConfig } from '~/store/file-viewers/file-viewers-actions';
-import { setRemoteHosts } from '~/views/login-panel/login-panel';
console.log(`Starting arvados [${getBuildInfo()}]`);
@@ -106,8 +105,6 @@ fetchConfig()
store.dispatch(initAuth(config));
store.dispatch(setBuildInfo());
store.dispatch(setCurrentTokenDialogApiHost(apiHost));
- store.dispatch(setUuidPrefix(config.uuidPrefix));
- store.dispatch(setRemoteHosts(config.remoteHosts));
store.dispatch(loadVocabulary);
store.dispatch(loadFileViewersConfig);
diff --git a/src/store/auth/auth-action.ts b/src/store/auth/auth-action.ts
index 43d1be82..cc1a5a0a 100644
--- a/src/store/auth/auth-action.ts
+++ b/src/store/auth/auth-action.ts
@@ -17,12 +17,14 @@ export const authActions = unionize({
SAVE_API_TOKEN: ofType<string>(),
LOGIN: {},
LOGOUT: {},
+ CONFIG: ofType<{ uuidPrefix: string, remoteHosts: { [key: string]: string } }>(),
INIT: ofType<{ user: User, token: string }>(),
USER_DETAILS_REQUEST: {},
USER_DETAILS_SUCCESS: ofType<User>(),
SET_SSH_KEYS: ofType<SshKeyResource[]>(),
ADD_SSH_KEY: ofType<SshKeyResource>(),
REMOVE_SSH_KEY: ofType<string>(),
+ SET_HOME_CLUSTER: ofType<string>(),
SET_SESSIONS: ofType<Session[]>(),
ADD_SESSION: ofType<Session>(),
REMOVE_SESSION: ofType<string>(),
@@ -48,6 +50,7 @@ export const initAuth = (config: Config) => (dispatch: Dispatch, getState: () =>
if (token) {
setAuthorizationHeader(services, token);
}
+ dispatch(authActions.CONFIG({ uuidPrefix: config.uuidPrefix, remoteHosts: config.remoteHosts }));
if (token && user) {
dispatch(authActions.INIT({ user, token }));
dispatch<any>(initSessions(services.authService, config, user));
diff --git a/src/store/auth/auth-reducer.ts b/src/store/auth/auth-reducer.ts
index a2822f10..0504dd48 100644
--- a/src/store/auth/auth-reducer.ts
+++ b/src/store/auth/auth-reducer.ts
@@ -13,40 +13,54 @@ export interface AuthState {
apiToken?: string;
sshKeys: SshKeyResource[];
sessions: Session[];
+ localCluster: string;
+ homeCluster: string;
+ remoteHosts: { [key: string]: string };
}
const initialState: AuthState = {
user: undefined,
apiToken: undefined,
sshKeys: [],
- sessions: []
+ sessions: [],
+ localCluster: "",
+ homeCluster: "",
+ remoteHosts: {}
};
export const authReducer = (services: ServiceRepository) => (state = initialState, action: AuthAction) => {
return authActions.match(action, {
SAVE_API_TOKEN: (token: string) => {
- return {...state, apiToken: token};
+ return { ...state, apiToken: token };
+ },
+ CONFIG: ({ uuidPrefix, remoteHosts }) => {
+ return {
+ ...state, localCluster: uuidPrefix, remoteHosts, homeCluster: uuidPrefix
+ };
},
INIT: ({ user, token }) => {
- return { ...state, user, apiToken: token };
+ return { ...state, user, apiToken: token, homeCluster: user.uuid.substr(0, 5) };
},
LOGIN: () => {
return state;
},
LOGOUT: () => {
- return {...state, apiToken: undefined};
+ return { ...state, apiToken: undefined };
},
USER_DETAILS_SUCCESS: (user: User) => {
- return {...state, user};
+ return { ...state, user };
},
SET_SSH_KEYS: (sshKeys: SshKeyResource[]) => {
- return {...state, sshKeys};
+ return { ...state, sshKeys };
},
ADD_SSH_KEY: (sshKey: SshKeyResource) => {
return { ...state, sshKeys: state.sshKeys.concat(sshKey) };
},
REMOVE_SSH_KEY: (uuid: string) => {
- return { ...state, sshKeys: state.sshKeys.filter((sshKey) => sshKey.uuid !== uuid )};
+ return { ...state, sshKeys: state.sshKeys.filter((sshKey) => sshKey.uuid !== uuid) };
+ },
+ SET_HOME_CLUSTER: (homeCluster: string) => {
+ return { ...state, homeCluster };
},
SET_SESSIONS: (sessions: Session[]) => {
return { ...state, sessions };
@@ -59,14 +73,16 @@ export const authReducer = (services: ServiceRepository) => (state = initialStat
...state,
sessions: state.sessions.filter(
session => session.clusterId !== clusterId
- )};
+ )
+ };
},
UPDATE_SESSION: (session: Session) => {
return {
...state,
sessions: state.sessions.map(
s => s.clusterId === session.clusterId ? session : s
- )};
+ )
+ };
},
default: () => state
});
diff --git a/src/views/login-panel/login-panel.tsx b/src/views/login-panel/login-panel.tsx
index 48c6e163..12c7d6c7 100644
--- a/src/views/login-panel/login-panel.tsx
+++ b/src/views/login-panel/login-panel.tsx
@@ -4,13 +4,11 @@
import * as React from 'react';
import { connect, DispatchProp } from 'react-redux';
-import { Grid, Typography, Button } from '@material-ui/core';
+import { Grid, Typography, Button, Select, FormControl } from '@material-ui/core';
import { StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core/styles';
-import { login } from '~/store/auth/auth-action';
+import { login, authActions } from '~/store/auth/auth-action';
import { ArvadosTheme } from '~/common/custom-theme';
import { RootState } from '~/store/store';
-import { getProperty } from '~/store/properties/properties';
-import { propertiesActions } from '~/store/properties/properties-actions';
import * as classNames from 'classnames';
type CssRules = 'root' | 'container' | 'title' | 'content' | 'content__bolder' | 'button';
@@ -52,24 +50,16 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
});
type LoginPanelProps = DispatchProp<any> & WithStyles<CssRules> & {
- remoteHosts: any,
+ remoteHosts: { [key: string]: string },
homeCluster: string,
uuidPrefix: string
};
-export const REMOTE_HOSTS_NAME = 'remoteHosts';
-export const HOME_CLUSTER_NAME = 'homeCluster';
-export const setRemoteHosts = (remoteHosts: any) =>
- propertiesActions.SET_PROPERTY({ key: REMOTE_HOSTS_NAME, value: remoteHosts });
-
-export const setHomeCluster = (homeCluster: string) =>
- propertiesActions.SET_PROPERTY({ key: HOME_CLUSTER_NAME, value: homeCluster });
-
export const LoginPanel = withStyles(styles)(
connect((state: RootState) => ({
- remoteHosts: state.properties.remoteHosts,
- homeCluster: state.properties.homeCluster,
- uuidPrefix: state.properties.uuidPrefix
+ remoteHosts: state.auth.remoteHosts,
+ homeCluster: state.auth.homeCluster,
+ uuidPrefix: state.auth.localCluster
}))(({ classes, dispatch, remoteHosts, homeCluster, uuidPrefix }: LoginPanelProps) =>
<Grid container direction="column" item xs alignItems="center" justify="center" className={classes.root}>
<Grid item className={classes.container}>
@@ -90,20 +80,22 @@ export const LoginPanel = withStyles(styles)(
Arvados Workbench uses your name and email address only for identification, and does not retrieve any other personal information from Google.
</Typography>
- <Typography className={classes.content}>
- <form>
- <label>
- Choose your home cluster:
- <select value={homeCluster} onChange={(event) => dispatch(setHomeCluster(event.target.value))}>
- {Object.keys(remoteHosts).map((k) => <option key={k} value={k}>{k}</option>)}
- </select>
- </label>
- </form>
- </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" className={classes.button} onClick={() => dispatch(login(uuidPrefix, remoteHosts[homeCluster]))}>
- Log in
- </Button>
+ <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>
diff --git a/src/views/main-panel/main-panel.tsx b/src/views/main-panel/main-panel.tsx
index 087163dc..5009f129 100644
--- a/src/views/main-panel/main-panel.tsx
+++ b/src/views/main-panel/main-panel.tsx
@@ -14,7 +14,7 @@ const mapStateToProps = (state: RootState): MainPanelRootDataProps => {
working: isSystemWorking(state.progressIndicator),
loading: isWorkbenchLoading(state),
buildInfo: state.appInfo.buildInfo,
- uuidPrefix: state.properties.uuidPrefix
+ uuidPrefix: state.auth.localCluster
};
};
commit 0da5b926cb7aa461e8d45d86d5fc423d90e768a1
Author: Peter Amstutz <pamstutz at veritasgenetics.com>
Date: Fri Feb 8 17:06:38 2019 -0500
14720: Federated login chooser wip
Arvados-DCO-1.1-Signed-off-by: Peter Amstutz <pamstutz at veritasgenetics.com>
diff --git a/src/index.tsx b/src/index.tsx
index 1561c3ff..f5eedc73 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -60,6 +60,7 @@ import { groupActionSet } from '~/views-components/context-menu/action-sets/grou
import { groupMemberActionSet } from '~/views-components/context-menu/action-sets/group-member-action-set';
import { linkActionSet } from '~/views-components/context-menu/action-sets/link-action-set';
import { loadFileViewersConfig } from '~/store/file-viewers/file-viewers-actions';
+import { setRemoteHosts } from '~/views/login-panel/login-panel';
console.log(`Starting arvados [${getBuildInfo()}]`);
@@ -106,6 +107,7 @@ fetchConfig()
store.dispatch(setBuildInfo());
store.dispatch(setCurrentTokenDialogApiHost(apiHost));
store.dispatch(setUuidPrefix(config.uuidPrefix));
+ store.dispatch(setRemoteHosts(config.remoteHosts));
store.dispatch(loadVocabulary);
store.dispatch(loadFileViewersConfig);
diff --git a/src/services/auth-service/auth-service.ts b/src/services/auth-service/auth-service.ts
index 6fe27c8d..fff7d577 100644
--- a/src/services/auth-service/auth-service.ts
+++ b/src/services/auth-service/auth-service.ts
@@ -99,9 +99,10 @@ export class AuthService {
localStorage.removeItem(USER_PREFS);
}
- public login() {
+ public login(uuidPrefix: string, homeCluster: string) {
const currentUrl = `${window.location.protocol}//${window.location.host}/token`;
- window.location.assign(`${this.baseUrl || ""}/login?return_to=${currentUrl}`);
+ // window.location.assign(`${this.baseUrl || ""}/login?return_to=${currentUrl}`);
+ window.location.assign(`https://${homeCluster}/login?remote=${uuidPrefix}&return_to=${currentUrl}`);
}
public logout() {
@@ -116,7 +117,7 @@ export class AuthService {
.get<UserDetailsResponse>('/users/current')
.then(resp => {
this.actions.progressFn(reqId, false);
- const prefs = resp.data.prefs.profile ? resp.data.prefs : { profile: {}};
+ const prefs = resp.data.prefs.profile ? resp.data.prefs : { profile: {} };
return {
email: resp.data.email,
firstName: resp.data.first_name,
diff --git a/src/store/auth/auth-action.ts b/src/store/auth/auth-action.ts
index 6c54a5c9..43d1be82 100644
--- a/src/store/auth/auth-action.ts
+++ b/src/store/auth/auth-action.ts
@@ -60,8 +60,8 @@ export const saveApiToken = (token: string) => (dispatch: Dispatch, getState: ()
dispatch(authActions.SAVE_API_TOKEN(token));
};
-export const login = () => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
- services.authService.login();
+export const login = (uuidPrefix: string, homeCluster: string) => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ services.authService.login(uuidPrefix, homeCluster);
dispatch(authActions.LOGIN());
};
diff --git a/src/views-components/main-app-bar/account-menu.tsx b/src/views-components/main-app-bar/account-menu.tsx
index 6c1e46c5..b50ee336 100644
--- a/src/views-components/main-app-bar/account-menu.tsx
+++ b/src/views-components/main-app-bar/account-menu.tsx
@@ -38,7 +38,7 @@ export const AccountMenu = connect(mapStateToProps)(
title="Account Management"
key={currentRoute}>
<MenuItem>
- {getUserFullname(user)}
+ {getUserFullname(user)} ({user.uuid.substr(0, 5)})
</MenuItem>
<MenuItem onClick={() => dispatch(openUserVirtualMachines())}>Virtual Machines</MenuItem>
{!user.isAdmin && <MenuItem onClick={() => dispatch(openRepositoriesPanel())}>Repositories</MenuItem>}
diff --git a/src/views-components/main-app-bar/anonymous-menu.tsx b/src/views-components/main-app-bar/anonymous-menu.tsx
index 6f77a524..5ffc4215 100644
--- a/src/views-components/main-app-bar/anonymous-menu.tsx
+++ b/src/views-components/main-app-bar/anonymous-menu.tsx
@@ -11,6 +11,6 @@ export const AnonymousMenu = connect()(
({ dispatch }: DispatchProp<any>) =>
<Button
color="inherit"
- onClick={() => dispatch(login())}>
+ onClick={() => dispatch(login("", ""))}>
Sign in
</Button>);
diff --git a/src/views-components/main-app-bar/main-app-bar.tsx b/src/views-components/main-app-bar/main-app-bar.tsx
index ff3bee61..d2a05837 100644
--- a/src/views-components/main-app-bar/main-app-bar.tsx
+++ b/src/views-components/main-app-bar/main-app-bar.tsx
@@ -31,6 +31,7 @@ interface MainAppBarDataProps {
user?: User;
buildInfo?: string;
children?: ReactNode;
+ uuidPrefix: string;
}
export type MainAppBarProps = MainAppBarDataProps & WithStyles<CssRules>;
@@ -43,7 +44,7 @@ export const MainAppBar = withStyles(styles)(
<Grid container item xs={3} direction="column" justify="center">
<Typography variant='h6' color="inherit" noWrap>
<Link to={Routes.ROOT} className={props.classes.link}>
- arvados workbench
+ arvados workbench ({props.uuidPrefix})
</Link>
</Typography>
<Typography variant="caption" color="inherit">{props.buildInfo}</Typography>
@@ -53,7 +54,7 @@ export const MainAppBar = withStyles(styles)(
xs={6}
container
alignItems="center">
- {props.user && <SearchBar /> }
+ {props.user && <SearchBar />}
</Grid>
<Grid
item
diff --git a/src/views/login-panel/login-panel.tsx b/src/views/login-panel/login-panel.tsx
index 151311d0..48c6e163 100644
--- a/src/views/login-panel/login-panel.tsx
+++ b/src/views/login-panel/login-panel.tsx
@@ -8,6 +8,9 @@ import { Grid, Typography, Button } from '@material-ui/core';
import { StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core/styles';
import { login } from '~/store/auth/auth-action';
import { ArvadosTheme } from '~/common/custom-theme';
+import { RootState } from '~/store/store';
+import { getProperty } from '~/store/properties/properties';
+import { propertiesActions } from '~/store/properties/properties-actions';
import * as classNames from 'classnames';
type CssRules = 'root' | 'container' | 'title' | 'content' | 'content__bolder' | 'button';
@@ -48,33 +51,60 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
}
});
-type LoginPanelProps = DispatchProp<any> & WithStyles<CssRules>;
+type LoginPanelProps = DispatchProp<any> & WithStyles<CssRules> & {
+ remoteHosts: any,
+ homeCluster: string,
+ uuidPrefix: string
+};
-export const LoginPanel = withStyles(styles)(connect()(
- ({ classes, dispatch }: LoginPanelProps) =>
- <Grid container direction="column" item xs alignItems="center" justify="center" className={classes.root}>
- <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>
- <Typography component="div" align="right">
- <Button variant="contained" color="primary" className={classes.button} onClick={() => dispatch(login())}>
- Log in
- </Button>
- </Typography>
+export const REMOTE_HOSTS_NAME = 'remoteHosts';
+export const HOME_CLUSTER_NAME = 'homeCluster';
+export const setRemoteHosts = (remoteHosts: any) =>
+ propertiesActions.SET_PROPERTY({ key: REMOTE_HOSTS_NAME, value: remoteHosts });
+
+export const setHomeCluster = (homeCluster: string) =>
+ propertiesActions.SET_PROPERTY({ key: HOME_CLUSTER_NAME, value: homeCluster });
+
+export const LoginPanel = withStyles(styles)(
+ connect((state: RootState) => ({
+ remoteHosts: state.properties.remoteHosts,
+ homeCluster: state.properties.homeCluster,
+ uuidPrefix: state.properties.uuidPrefix
+ }))(({ classes, dispatch, remoteHosts, homeCluster, uuidPrefix }: LoginPanelProps) =>
+ <Grid container direction="column" item xs alignItems="center" justify="center" className={classes.root}>
+ <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>
+
+ <Typography className={classes.content}>
+ <form>
+ <label>
+ Choose your home cluster:
+ <select value={homeCluster} onChange={(event) => dispatch(setHomeCluster(event.target.value))}>
+ {Object.keys(remoteHosts).map((k) => <option key={k} value={k}>{k}</option>)}
+ </select>
+ </label>
+ </form>
+ </Typography>
+ <Typography component="div" align="right">
+ <Button variant="contained" color="primary" className={classes.button} onClick={() => dispatch(login(uuidPrefix, remoteHosts[homeCluster]))}>
+ Log in
+ </Button>
+ </Typography>
+ </Grid>
</Grid>
- </Grid>
-));
+ ));
diff --git a/src/views/main-panel/main-panel-root.tsx b/src/views/main-panel/main-panel-root.tsx
index 15149dce..b96e3cc8 100644
--- a/src/views/main-panel/main-panel-root.tsx
+++ b/src/views/main-panel/main-panel-root.tsx
@@ -26,22 +26,24 @@ export interface MainPanelRootDataProps {
working: boolean;
loading: boolean;
buildInfo: string;
+ uuidPrefix: string;
}
type MainPanelRootProps = MainPanelRootDataProps & WithStyles<CssRules>;
export const MainPanelRoot = withStyles(styles)(
- ({ classes, loading, working, user, buildInfo }: MainPanelRootProps) =>
- loading
+ ({ classes, loading, working, user, buildInfo, uuidPrefix }: MainPanelRootProps) =>
+ loading
? <WorkbenchLoadingScreen />
: <>
<MainAppBar
user={user}
- buildInfo={buildInfo}>
+ buildInfo={buildInfo}
+ uuidPrefix={uuidPrefix}>
{working ? <LinearProgress color="secondary" /> : null}
</MainAppBar>
<Grid container direction="column" className={classes.root}>
{user ? <WorkbenchPanel /> : <LoginPanel />}
</Grid>
</>
-);
\ No newline at end of file
+);
diff --git a/src/views/main-panel/main-panel.tsx b/src/views/main-panel/main-panel.tsx
index 7592afb3..087163dc 100644
--- a/src/views/main-panel/main-panel.tsx
+++ b/src/views/main-panel/main-panel.tsx
@@ -13,10 +13,11 @@ const mapStateToProps = (state: RootState): MainPanelRootDataProps => {
user: state.auth.user,
working: isSystemWorking(state.progressIndicator),
loading: isWorkbenchLoading(state),
- buildInfo: state.appInfo.buildInfo
+ buildInfo: state.appInfo.buildInfo,
+ uuidPrefix: state.properties.uuidPrefix
};
};
const mapDispatchToProps = null;
-export const MainPanel = connect(mapStateToProps, mapDispatchToProps)(MainPanelRoot);
\ No newline at end of file
+export const MainPanel = connect(mapStateToProps, mapDispatchToProps)(MainPanelRoot);
-----------------------------------------------------------------------
hooks/post-receive
--
More information about the arvados-commits
mailing list