FileViewersConfigURL: string;
WelcomePageHTML: string;
};
+ Login: {
+ LoginCluster: string;
+ };
}
export class Config {
workbench2Url: string;
vocabularyUrl: string;
fileViewersConfigUrl: string;
+ loginCluster: string;
clusterConfig: ClusterConfigJSON;
}
config.workbench2Url = clusterConfigJSON.Services.Workbench2.ExternalURL;
config.workbenchUrl = clusterConfigJSON.Services.Workbench1.ExternalURL;
config.keepWebServiceUrl = clusterConfigJSON.Services.WebDAV.ExternalURL;
+ config.loginCluster = clusterConfigJSON.Login.LoginCluster;
config.clusterConfig = clusterConfigJSON;
mapRemoteHosts(clusterConfigJSON, config);
FileViewersConfigURL: "",
WelcomePageHTML: "",
},
+ Login: {
+ LoginCluster: "",
+ },
...config
});
workbench2Url: "",
vocabularyUrl: "",
fileViewersConfigUrl: "",
+ loginCluster: "",
clusterConfig: mockClusterConfigJSON({}),
...config
});
localStorage.removeItem(USER_PREFS);
}
- public login(uuidPrefix: string, homeCluster: string, remoteHosts: { [key: string]: string }) {
+ public login(uuidPrefix: string, homeCluster: string, loginCluster: string, remoteHosts: { [key: string]: string }) {
const currentUrl = `${window.location.protocol}//${window.location.host}/token`;
const homeClusterHost = remoteHosts[homeCluster];
- window.location.assign(`https://${homeClusterHost}/login?${uuidPrefix !== homeCluster ? "remote=" + uuidPrefix + "&" : ""}return_to=${currentUrl}`);
+ window.location.assign(`https://${homeClusterHost}/login?${(uuidPrefix !== homeCluster && homeCluster !== loginCluster) ? "remote=" + uuidPrefix + "&" : ""}return_to=${currentUrl}`);
}
public logout() {
active: true,
status: SessionStatus.VALIDATED
} as Session;
- const localSessions = this.getSessions();
+ const localSessions = this.getSessions().map(s => ({
+ ...s,
+ active: false,
+ status: SessionStatus.INVALIDATED
+ }));
+
const cfgSessions = Object.keys(cfg.remoteHosts).map(clusterId => {
const remoteHost = cfg.remoteHosts[clusterId];
return {
} as Session;
});
const sessions = [currentSession]
+ .concat(cfgSessions)
.concat(localSessions)
- .concat(cfgSessions);
+ .filter((r: Session) => r.clusterId !== "*");
const uniqSessions = uniqBy(sessions, 'clusterId');
const sessions = authService.buildSessions(config, user);
authService.saveSessions(sessions);
dispatch(authActions.SET_SESSIONS(sessions));
+ dispatch(validateSessions());
};
export const loadSiteManagerPanel = () =>
import createBrowserHistory from "history/createBrowserHistory";
import { mockConfig } from '~/common/config';
import { ApiActions } from "~/services/api/api-actions";
-import { ACCOUNT_LINK_STATUS_KEY} from '~/services/link-account-service/link-account-service';
+import { ACCOUNT_LINK_STATUS_KEY } from '~/services/link-account-service/link-account-service';
describe('auth-actions', () => {
let reducer: (state: AuthState | undefined, action: AuthAction) => any;
sshKeys: [],
homeCluster: "zzzzz",
localCluster: "zzzzz",
- remoteHostsConfig: {},
+ loginCluster: undefined,
+ remoteHostsConfig: {
+ "zzzzz": {
+ "remoteHosts": {
+ "xc59z": "xc59z.arvadosapi.com",
+ },
+ "rootUrl": "https://zzzzz.arvadosapi.com",
+ "uuidPrefix": "zzzzz",
+ },
+ },
remoteHosts: {
zzzzz: "zzzzz.arvadosapi.com",
xc59z: "xc59z.arvadosapi.com"
"email": "",
"loggedIn": false,
"remoteHost": "xc59z.arvadosapi.com",
- "status": 0,
+ "status": 1,
"token": "",
"username": ""
}],
setAuthorizationHeader(services, token);
}
dispatch(authActions.CONFIG({ config }));
- dispatch(authActions.SET_HOME_CLUSTER(homeCluster || config.uuidPrefix));
+ dispatch(authActions.SET_HOME_CLUSTER(config.loginCluster || homeCluster || config.uuidPrefix));
if (token && user) {
dispatch(authActions.INIT({ user, token }));
dispatch<any>(initSessions(services.authService, config, user));
const remoteConfig = new Config();
remoteConfig.uuidPrefix = response.data.ClusterID;
remoteConfig.workbench2Url = response.data.Services.Workbench2.ExternalURL;
+ remoteConfig.loginCluster = response.data.Login.LoginCluster;
mapRemoteHosts(response.data, remoteConfig);
- dispatch(authActions.REMOTE_CLUSTER_CONFIG({ config: remoteConfig}));
+ dispatch(authActions.REMOTE_CLUSTER_CONFIG({ config: remoteConfig }));
});
});
};
dispatch(authActions.SAVE_USER(user));
};
-export const login = (uuidPrefix: string, homeCluster: string, remoteHosts: { [key: string]: string }) => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
- services.authService.login(uuidPrefix, homeCluster, remoteHosts);
- dispatch(authActions.LOGIN());
-};
+export const login = (uuidPrefix: string, homeCluster: string, loginCluster: string,
+ remoteHosts: { [key: string]: string }) => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ services.authService.login(uuidPrefix, homeCluster, loginCluster, remoteHosts);
+ dispatch(authActions.LOGIN());
+ };
export const logout = (deleteLinkData: boolean = false) => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
if (deleteLinkData) {
sessions: [],
homeCluster: "zzzzz",
localCluster: "",
+ loginCluster: "",
remoteHosts: {},
remoteHostsConfig: {}
});
sessions: [],
homeCluster: "",
localCluster: "",
+ loginCluster: "",
remoteHosts: {},
remoteHostsConfig: {}
});
sessions: [],
homeCluster: "",
localCluster: "",
+ loginCluster: "",
remoteHosts: {},
remoteHostsConfig: {},
user: {
sessions: Session[];
localCluster: string;
homeCluster: string;
+ loginCluster: string;
remoteHosts: { [key: string]: string };
remoteHostsConfig: { [key: string]: Config };
}
sessions: [],
localCluster: "",
homeCluster: "",
+ loginCluster: "",
remoteHosts: {},
remoteHostsConfig: {}
};
return { ...state, apiToken: token };
},
SAVE_USER: (user: UserResource) => {
- return { ...state, user};
+ return { ...state, user };
},
CONFIG: ({ config }) => {
return {
...state,
localCluster: config.uuidPrefix,
remoteHosts: { ...config.remoteHosts, [config.uuidPrefix]: new URL(config.rootUrl).host },
- homeCluster: config.uuidPrefix
+ homeCluster: config.loginCluster || config.uuidPrefix,
+ loginCluster: config.loginCluster,
+ remoteHostsConfig: { ...state.remoteHostsConfig, [config.uuidPrefix]: config }
};
},
REMOTE_CLUSTER_CONFIG: ({ config }) => {
export const linkAccountPanelActions = unionize({
LINK_INIT: ofType<{
- targetUser: UserResource | undefined }>(),
+ targetUser: UserResource | undefined
+ }>(),
LINK_LOAD: ofType<{
originatingUser: OriginatingUser | undefined,
targetUser: UserResource | undefined,
targetUserToken: string | undefined,
userToLink: UserResource | undefined,
- userToLinkToken: string | undefined }>(),
+ userToLinkToken: string | undefined
+ }>(),
LINK_INVALID: ofType<{
originatingUser: OriginatingUser | undefined,
targetUser: UserResource | undefined,
userToLink: UserResource | undefined,
- error: LinkAccountPanelError }>(),
+ error: LinkAccountPanelError
+ }>(),
SET_SELECTED_CLUSTER: ofType<{
- selectedCluster: string }>(),
+ selectedCluster: string
+ }>(),
SET_IS_PROCESSING: ofType<{
- isProcessing: boolean}>(),
+ isProcessing: boolean
+ }>(),
HAS_SESSION_DATA: {}
});
dispatch(checkForLinkStatus());
// Continue loading the link account panel
- dispatch(setBreadcrumbs([{ label: 'Link account'}]));
+ dispatch(setBreadcrumbs([{ label: 'Link account' }]));
const curUser = getState().auth.user;
const curToken = getState().auth.apiToken;
if (curUser && curToken) {
originatingUser: params.originatingUser,
targetUser: params.targetUser,
userToLink: params.userToLink,
- error}));
+ error
+ }));
return;
}
}
export const startLinking = (t: LinkAccountType) =>
(dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
- const accountToLink = {type: t, userUuid: services.authService.getUuid(), token: services.authService.getApiToken()} as AccountToLink;
+ const accountToLink = { type: t, userUuid: services.authService.getUuid(), token: services.authService.getApiToken() } as AccountToLink;
services.linkAccountService.saveAccountToLink(accountToLink);
const auth = getState().auth;
- const isLocalUser = auth.user!.uuid.substring(0,5) === auth.localCluster;
+ const isLocalUser = auth.user!.uuid.substring(0, 5) === auth.localCluster;
let homeCluster = auth.localCluster;
if (isLocalUser && t === LinkAccountType.ACCESS_OTHER_REMOTE_ACCOUNT) {
homeCluster = getState().linkAccountPanel.selectedCluster!;
}
dispatch(logout());
- dispatch(login(auth.localCluster, homeCluster, auth.remoteHosts));
+ dispatch(login(auth.localCluster, homeCluster, auth.loginCluster, auth.remoteHosts));
};
export const getAccountLinkData = () =>
services.linkAccountService.saveLinkOpStatus(LinkAccountStatus.SUCCESS);
location.reload();
}
- catch(e) {
+ catch (e) {
// If the link operation fails, delete the previously made project
try {
setAuthorizationHeader(services, linkState.targetUserToken);
throw e;
}
}
- };
\ No newline at end of file
+ };
export const ResourceCluster = (props: { uuid: string }) => {
const CLUSTER_ID_LENGTH = 5;
- const pos = props.uuid.indexOf('-');
+ const pos = props.uuid.length > CLUSTER_ID_LENGTH ? props.uuid.indexOf('-') : 5;
const clusterId = pos >= CLUSTER_ID_LENGTH ? props.uuid.substr(0, pos) : '';
- const ci = pos >= CLUSTER_ID_LENGTH ? (props.uuid.charCodeAt(0) + props.uuid.charCodeAt(1)) % clusterColors.length : 0;
- return <Typography>
- <span style={{
- backgroundColor: clusterColors[ci][0],
- color: clusterColors[ci][1],
- padding: "2px 7px",
- borderRadius: 3
- }}>{clusterId}</span>
- </Typography>;
+ const ci = pos >= CLUSTER_ID_LENGTH ? (((((
+ (props.uuid.charCodeAt(0) * props.uuid.charCodeAt(1))
+ + props.uuid.charCodeAt(2))
+ * props.uuid.charCodeAt(3))
+ + props.uuid.charCodeAt(4))) % clusterColors.length) : 0;
+ return <span style={{
+ backgroundColor: clusterColors[ci][0],
+ color: clusterColors[ci][1],
+ padding: "2px 7px",
+ borderRadius: 3
+ }}>{clusterId}</span>;
};
export const ComputeNodeInfo = withResourceData('info', renderNodeInfo);
({ dispatch }: DispatchProp<any>) =>
<Button
color="inherit"
- onClick={() => dispatch(login("", "", {}))}>
+ onClick={() => dispatch(login("", "", "", {}))}>
Sign in
</Button>);
import { LinkAccountType } from "~/models/link-account";
import { formatDate } from "~/common/formatters";
import { LinkAccountPanelStatus, LinkAccountPanelError } from "~/store/link-account-panel/link-account-panel-reducer";
+import { Config } from '~/common/config';
type CssRules = 'root';
export interface LinkAccountPanelRootDataProps {
targetUser?: UserResource;
userToLink?: UserResource;
- remoteHosts: { [key: string]: string };
+ remoteHostsConfig: { [key: string]: Config };
hasRemoteHosts: boolean;
localCluster: string;
- status : LinkAccountPanelStatus;
+ loginCluster: string;
+ status: LinkAccountPanelStatus;
error: LinkAccountPanelError;
selectedCluster?: string;
isProcessing: boolean;
const disp = [];
disp.push(<span><b>{user.email}</b> ({user.username}, {user.uuid})</span>);
if (showCluster) {
- const homeCluster = user.uuid.substr(0,5);
+ const homeCluster = user.uuid.substr(0, 5);
disp.push(<span> hosted on cluster <b>{homeCluster}</b> and </span>);
}
if (showCreatedAt) {
type LinkAccountPanelRootProps = LinkAccountPanelRootDataProps & LinkAccountPanelRootActionProps & WithStyles<CssRules>;
-export const LinkAccountPanelRoot = withStyles(styles) (
- ({classes, targetUser, userToLink, status, isProcessing, error, startLinking, cancelLinking, linkAccount,
- remoteHosts, hasRemoteHosts, selectedCluster, setSelectedCluster, localCluster}: LinkAccountPanelRootProps) => {
+export const LinkAccountPanelRoot = withStyles(styles)(
+ ({ classes, targetUser, userToLink, status, isProcessing, error, startLinking, cancelLinking, linkAccount,
+ remoteHostsConfig, hasRemoteHosts, selectedCluster, setSelectedCluster, localCluster, loginCluster }: LinkAccountPanelRootProps) => {
return <Card className={classes.root}>
<CardContent>
- { isProcessing && <Grid container item direction="column" alignContent="center" spacing={24}>
- <Grid item>
- Loading user info. Please wait.
- </Grid>
- <Grid item style={{ alignSelf: 'center' }}>
- <CircularProgress/>
- </Grid>
- </Grid> }
- { !isProcessing && status === LinkAccountPanelStatus.INITIAL && targetUser && <div>
- { isLocalUser(targetUser.uuid, localCluster) ? <Grid container spacing={24}>
- <Grid container item direction="column" spacing={24}>
- <Grid item>
- You are currently logged in as {displayUser(targetUser, true)}
- </Grid>
- <Grid item>
- You can link Arvados accounts. After linking, either login will take you to the same account.
- </Grid >
+ {isProcessing && <Grid container item direction="column" alignContent="center" spacing={24}>
+ <Grid item>
+ Loading user info. Please wait.
+ </Grid>
+ <Grid item style={{ alignSelf: 'center' }}>
+ <CircularProgress />
</Grid>
- <Grid container item direction="row" spacing={24}>
- <Grid item>
- <Button disabled={!targetUser.isActive} color="primary" variant="contained" onClick={() => startLinking(LinkAccountType.ADD_OTHER_LOGIN)}>
- Add another login to this account
- </Button>
- </Grid>
- <Grid item>
- <Button color="primary" variant="contained" onClick={() => startLinking(LinkAccountType.ACCESS_OTHER_ACCOUNT)}>
- Use this login to access another account
- </Button>
+ </Grid>}
+ {!isProcessing && status === LinkAccountPanelStatus.INITIAL && targetUser && <div>
+ {isLocalUser(targetUser.uuid, localCluster) ? <Grid container spacing={24}>
+ <Grid container item direction="column" spacing={24}>
+ <Grid item>
+ You are currently logged in as {displayUser(targetUser, true)}
+ </Grid>
+ <Grid item>
+ You can link Arvados accounts. After linking, either login will take you to the same account.
+ </Grid >
</Grid>
- </Grid>
- { hasRemoteHosts && selectedCluster && <Grid container item direction="column" spacing={24}>
- <Grid item>
- You can also link {displayUser(targetUser, false)} with an account from a remote cluster.
+ <Grid container item direction="row" spacing={24}>
+ <Grid item>
+ <Button disabled={!targetUser.isActive} color="primary" variant="contained" onClick={() => startLinking(LinkAccountType.ADD_OTHER_LOGIN)}>
+ Add another login to this account
+ </Button>
+ </Grid>
+ <Grid item>
+ <Button color="primary" variant="contained" onClick={() => startLinking(LinkAccountType.ACCESS_OTHER_ACCOUNT)}>
+ Use this login to access another account
+ </Button>
+ </Grid>
</Grid>
- <Grid item>
- Please select the cluster that hosts the account you want to link with:
- <Select id="remoteHostsDropdown" native defaultValue={selectedCluster} style={{ marginLeft: "1em" }}
+ {hasRemoteHosts && selectedCluster && <Grid container item direction="column" spacing={24}>
+ <Grid item>
+ You can also link {displayUser(targetUser, false)} with an account from a remote cluster.
+ </Grid>
+ <Grid item>
+ Please select the cluster that hosts the account you want to link with:
+ <Select id="remoteHostsDropdown" native defaultValue={selectedCluster} style={{ marginLeft: "1em" }}
onChange={(event) => setSelectedCluster(event.target.value)}>
- {Object.keys(remoteHosts).map((k) => k !== localCluster ? <option key={k} value={k}>{k}</option> : null)}
+ {Object.keys(remoteHostsConfig).map((k) => k !== localCluster ? <option key={k} value={k}>{k}</option> : null)}
</Select>
</Grid>
- <Grid item>
- <Button color="primary" variant="contained" onClick={() => startLinking(LinkAccountType.ACCESS_OTHER_REMOTE_ACCOUNT)}>
- Link with an account on {hasRemoteHosts ? <label>{selectedCluster} </label> : null}
- </Button>
- </Grid>
- </Grid> }
- </Grid> :
- <Grid container spacing={24}>
- <Grid container item direction="column" spacing={24}>
- <Grid item>
- You are currently logged in as {displayUser(targetUser, true, true)}
+ <Grid item>
+ <Button color="primary" variant="contained" onClick={() => startLinking(LinkAccountType.ACCESS_OTHER_REMOTE_ACCOUNT)}>
+ Link with an account on {hasRemoteHosts ? <label>{selectedCluster} </label> : null}
+ </Button>
+ </Grid>
+ </Grid>}
+ </Grid> :
+ <Grid container spacing={24}>
+ <Grid container item direction="column" spacing={24}>
+ <Grid item>
+ You are currently logged in as {displayUser(targetUser, true, true)}
+ </Grid>
+ {targetUser.isActive ?
+ (loginCluster === "" ?
+ <> <Grid item>
+ This a remote account. You can link a local Arvados account to this one.
+ After linking, you can access the local account's data by logging into the
+ <b>{localCluster}</b> cluster as user <b>{targetUser.email}</b>
+ from <b>{targetUser.uuid.substr(0, 5)}</b>.
+ </Grid >
+ <Grid item>
+ <Button color="primary" variant="contained" onClick={() => startLinking(LinkAccountType.ADD_LOCAL_TO_REMOTE)}>
+ Link an account from {localCluster} to this account
+ </Button>
+ </Grid> </>
+ : <Grid item>Please visit cluster
+ <a href={remoteHostsConfig[loginCluster].workbench2Url + "/link_account"}>{loginCluster}</a>
+ to perform account linking.</Grid>
+ )
+ : <Grid item>
+ This an inactive remote account. An administrator must activate your
+ account before you can proceed. After your accounts is activated,
+ you can link a local Arvados account hosted by the <b>{localCluster}</b>
+ cluster to this one.
+ </Grid >}
+ </Grid>
+ </Grid>}
+ </div>}
+ {!isProcessing && (status === LinkAccountPanelStatus.LINKING || status === LinkAccountPanelStatus.ERROR) && userToLink && targetUser &&
+ <Grid container spacing={24}>
+ {status === LinkAccountPanelStatus.LINKING && <Grid container item direction="column" spacing={24}>
+ <Grid item>
+ Clicking 'Link accounts' will link {displayUser(userToLink, true, !isLocalUser(targetUser.uuid, localCluster))} to {displayUser(targetUser, true, !isLocalUser(targetUser.uuid, localCluster))}.
+ </Grid>
+ {(isLocalUser(targetUser.uuid, localCluster)) && <Grid item>
+ After linking, logging in as {displayUser(userToLink)} will log you into the same account as {displayUser(targetUser)}.
+ </Grid>}
+ <Grid item>
+ Any object owned by {displayUser(userToLink)} will be transfered to {displayUser(targetUser)}.
+ </Grid>
+ {!isLocalUser(targetUser.uuid, localCluster) && <Grid item>
+ You can access <b>{userToLink.email}</b> data by logging into <b>{localCluster}</b> with the <b>{targetUser.email}</b> account.
+ </Grid>}
+ </Grid>}
+ {error === LinkAccountPanelError.NON_ADMIN && <Grid item>
+ Cannot link admin account {displayUser(userToLink)} to non-admin account {displayUser(targetUser)}.
+ </Grid>}
+ {error === LinkAccountPanelError.SAME_USER && <Grid item>
+ Cannot link {displayUser(targetUser)} to the same account.
+ </Grid>}
+ {error === LinkAccountPanelError.INACTIVE && <Grid item>
+ Cannot link account {displayUser(userToLink)} to inactive account {displayUser(targetUser)}.
+ </Grid>}
+ <Grid container item direction="row" spacing={24}>
+ <Grid item>
+ <Button variant="contained" onClick={() => cancelLinking()}>
+ Cancel
+ </Button>
+ </Grid>
+ <Grid item>
+ <Button disabled={status === LinkAccountPanelStatus.ERROR} color="primary" variant="contained" onClick={() => linkAccount()}>
+ Link accounts
+ </Button>
+ </Grid>
</Grid>
- {targetUser.isActive ? <> <Grid item>
- This a remote account. You can link a local Arvados account to this one. After linking, you can access the local account's data by logging into the <b>{localCluster}</b> cluster as user <b>{targetUser.email}</b> from <b>{targetUser.uuid.substr(0,5)}</b>.
- </Grid >
- <Grid item>
- <Button color="primary" variant="contained" onClick={() => startLinking(LinkAccountType.ADD_LOCAL_TO_REMOTE)}>
- Link an account from {localCluster} to this account
- </Button>
- </Grid> </>
- : <Grid item>
- This an inactive remote account. An administrator must activate your account before you can proceed. After your accounts is activated, you can link a local Arvados account hosted by the <b>{localCluster}</b> cluster to this one.
- </Grid >}
- </Grid>
- </Grid>}
- </div> }
- { !isProcessing && (status === LinkAccountPanelStatus.LINKING || status === LinkAccountPanelStatus.ERROR) && userToLink && targetUser &&
- <Grid container spacing={24}>
- { status === LinkAccountPanelStatus.LINKING && <Grid container item direction="column" spacing={24}>
- <Grid item>
- Clicking 'Link accounts' will link {displayUser(userToLink, true, !isLocalUser(targetUser.uuid, localCluster))} to {displayUser(targetUser, true, !isLocalUser(targetUser.uuid, localCluster))}.
- </Grid>
- { (isLocalUser(targetUser.uuid, localCluster)) && <Grid item>
- After linking, logging in as {displayUser(userToLink)} will log you into the same account as {displayUser(targetUser)}.
- </Grid> }
- <Grid item>
- Any object owned by {displayUser(userToLink)} will be transfered to {displayUser(targetUser)}.
- </Grid>
- { !isLocalUser(targetUser.uuid, localCluster) && <Grid item>
- You can access <b>{userToLink.email}</b> data by logging into <b>{localCluster}</b> with the <b>{targetUser.email}</b> account.
- </Grid> }
- </Grid> }
- { error === LinkAccountPanelError.NON_ADMIN && <Grid item>
- Cannot link admin account {displayUser(userToLink)} to non-admin account {displayUser(targetUser)}.
- </Grid> }
- { error === LinkAccountPanelError.SAME_USER && <Grid item>
- Cannot link {displayUser(targetUser)} to the same account.
- </Grid> }
- { error === LinkAccountPanelError.INACTIVE && <Grid item>
- Cannot link account {displayUser(userToLink)} to inactive account {displayUser(targetUser)}.
- </Grid> }
- <Grid container item direction="row" spacing={24}>
- <Grid item>
- <Button variant="contained" onClick={() => cancelLinking()}>
- Cancel
- </Button>
- </Grid>
- <Grid item>
- <Button disabled={status === LinkAccountPanelStatus.ERROR} color="primary" variant="contained" onClick={() => linkAccount()}>
- Link accounts
- </Button>
- </Grid>
- </Grid>
- </Grid> }
- </CardContent>
- </Card>;
-});
\ No newline at end of file
+ </Grid>}
+ </CardContent>
+ </Card>;
+ });
const mapStateToProps = (state: RootState): LinkAccountPanelRootDataProps => {
return {
- remoteHosts: state.auth.remoteHosts,
- hasRemoteHosts: Object.keys(state.auth.remoteHosts).length > 1,
+ remoteHostsConfig: state.auth.remoteHostsConfig,
+ hasRemoteHosts: Object.keys(state.auth.remoteHosts).length > 1 && state.auth.loginCluster === "",
selectedCluster: state.linkAccountPanel.selectedCluster,
localCluster: state.auth.localCluster,
+ loginCluster: state.auth.loginCluster,
targetUser: state.linkAccountPanel.targetUser,
userToLink: state.linkAccountPanel.userToLink,
status: state.linkAccountPanel.status,
startLinking: (type: LinkAccountType) => dispatch<any>(startLinking(type)),
cancelLinking: () => dispatch<any>(cancelLinking(true)),
linkAccount: () => dispatch<any>(linkAccount()),
- setSelectedCluster: (selectedCluster: string) => dispatch<any>(linkAccountPanelActions.SET_SELECTED_CLUSTER({selectedCluster}))
+ setSelectedCluster: (selectedCluster: string) => dispatch<any>(linkAccountPanelActions.SET_SELECTED_CLUSTER({ selectedCluster }))
});
export const LinkAccountPanel = connect(mapStateToProps, mapDispatchToProps)(LinkAccountPanelRoot);
remoteHosts: { [key: string]: string },
homeCluster: string,
uuidPrefix: string,
+ loginCluster: string,
welcomePage: string
};
remoteHosts: state.auth.remoteHosts,
homeCluster: state.auth.homeCluster,
uuidPrefix: state.auth.localCluster,
+ loginCluster: state.auth.loginCluster,
welcomePage: state.config.clusterConfig.Workbench.WelcomePageHTML
- }))(({ classes, dispatch, remoteHosts, homeCluster, uuidPrefix, welcomePage }: LoginPanelProps) =>
+ }))(({ classes, dispatch, remoteHosts, homeCluster, uuidPrefix, loginCluster, welcomePage }: LoginPanelProps) =>
<Grid container justify="center" alignItems="center"
className={classes.root}
style={{ marginTop: 56, overflowY: "auto", height: "100%" }}>
<Typography>
<div dangerouslySetInnerHTML={{ __html: welcomePage }} style={{ margin: "1em" }} />
</Typography>
- {Object.keys(remoteHosts).length > 1 &&
+ {Object.keys(remoteHosts).length > 1 && loginCluster === "" &&
+
<Typography component="div" align="right">
<label>Please select the cluster that hosts your user account:</label>
<Select native value={homeCluster} style={{ margin: "1em" }}
<Typography component="div" align="right">
<Button variant="contained" color="primary" style={{ margin: "1em" }} className={classes.button}
- onClick={() => dispatch(login(uuidPrefix, homeCluster, remoteHosts))}>
- Log in to {uuidPrefix}
- {uuidPrefix !== homeCluster &&
- <span> with user from {homeCluster}</span>}
+ onClick={() => dispatch(login(uuidPrefix, homeCluster, loginCluster, remoteHosts))}>
+ Log in
+ {uuidPrefix !== homeCluster && loginCluster !== homeCluster &&
+ <span> to {uuidPrefix} with user from {homeCluster}</span>}
</Button>
</Typography>
</Grid>
import { createTree } from '~/models/tree';
import { getInitialResourceTypeFilters } from '~/store/resource-type-filters/resource-type-filters';
import { SearchResultsPanelProps } from "./search-results-panel";
+import { Routes } from '~/routes/routes';
+import { Link } from 'react-router-dom';
+import { StyleRulesCallback, withStyles, WithStyles } from '@material-ui/core';
+import { ArvadosTheme } from '~/common/custom-theme';
export enum SearchResultsPanelColumnNames {
CLUSTER = "Cluster",
LAST_MODIFIED = "Last modified"
}
+export type CssRules = 'siteManagerLink';
+
+const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
+ siteManagerLink: {
+ marginRight: theme.spacing.unit * 2,
+ float: 'right'
+ }
+});
+
export interface WorkflowPanelFilter extends DataTableFilterItem {
type: ResourceKind | ContainerRequestState;
}
}
];
-export const SearchResultsPanelView = (props: SearchResultsPanelProps) => {
- const homeCluster = props.user.uuid.substr(0, 5);
- return <DataExplorer
- id={SEARCH_RESULTS_PANEL_ID}
- onRowClick={props.onItemClick}
- onRowDoubleClick={props.onItemDoubleClick}
- onContextMenu={props.onContextMenu}
- contextMenuColumn={false}
- hideSearchInput
- title={
- props.localCluster === homeCluster ?
- <div>Searching clusters: {props.sessions.filter((ss) => ss.loggedIn).map((ss) => <span key={ss.clusterId}> {ss.clusterId}</span>)}</div> :
- <div>Searching local cluster {props.localCluster} only. To search multiple clusters, <a href={props.remoteHostsConfig[homeCluster] && props.remoteHostsConfig[homeCluster].workbench2Url}> start from your home Workbench.</a></div>
- }
- />;
-};
+export const SearchResultsPanelView = withStyles(styles, { withTheme: true })(
+ (props: SearchResultsPanelProps & WithStyles<CssRules, true>) => {
+ const homeCluster = props.user.uuid.substr(0, 5);
+ const loggedIn = props.sessions.filter((ss) => ss.loggedIn);
+ return <DataExplorer
+ id={SEARCH_RESULTS_PANEL_ID}
+ onRowClick={props.onItemClick}
+ onRowDoubleClick={props.onItemDoubleClick}
+ onContextMenu={props.onContextMenu}
+ contextMenuColumn={false}
+ hideSearchInput
+ title={
+ <div>
+ {loggedIn.length === 1 ?
+ <span>Searching local cluster <ResourceCluster uuid={props.localCluster} /></span>
+ : <span>Searching clusters: {loggedIn.map((ss) => <span key={ss.clusterId}>
+ <a href={props.remoteHostsConfig[ss.clusterId].workbench2Url} style={{ textDecoration: 'none' }}> <ResourceCluster uuid={ss.clusterId} /></a></span>)}</span>}
+ {loggedIn.length === 1 && props.localCluster !== homeCluster ?
+ <span>To search multiple clusters, <a href={props.remoteHostsConfig[homeCluster] && props.remoteHostsConfig[homeCluster].workbench2Url}> start from your home Workbench.</a></span>
+ : <span style={{ marginLeft: "2em" }}>Use <Link to={Routes.SITE_MANAGER} >Site Manager</Link> to manage which clusters will be searched.</span>}
+ </div >
+ }
+ />;
+ });
import { TextField } from "~/components/text-field/text-field";
import { addSession } from "~/store/auth/auth-action-session";
import { SITE_MANAGER_REMOTE_HOST_VALIDATION } from "~/validators/validators";
+import { Config } from '~/common/config';
+import { ResourceCluster } from '~/views-components/data-explorer/renderers';
type CssRules = 'root' | 'link' | 'buttonContainer' | 'table' | 'tableRow' |
'remoteSiteInfo' | 'buttonAdd' | 'buttonLoggedIn' | 'buttonLoggedOut' |
const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
root: {
- width: '100%',
- overflow: 'auto'
+ width: '100%',
+ overflow: 'auto'
},
link: {
color: theme.palette.primary.main,
export interface SiteManagerPanelRootDataProps {
sessions: Session[];
+ remoteHostsConfig: { [key: string]: Config };
}
type SiteManagerPanelRootProps = SiteManagerPanelRootDataProps & SiteManagerPanelRootActionProps & WithStyles<CssRules> & InjectedFormProps;
};
export const SiteManagerPanelRoot = compose(
- reduxForm<{remoteHost: string}>({
+ reduxForm<{ remoteHost: string }>({
form: SITE_MANAGER_FORM_NAME,
touchOnBlur: false,
onSubmit: (data, dispatch) => {
}
}),
withStyles(styles))
- (({ classes, sessions, handleSubmit, toggleSession }: SiteManagerPanelRootProps) =>
+ (({ classes, sessions, handleSubmit, toggleSession, remoteHostsConfig }: SiteManagerPanelRootProps) =>
<Card className={classes.root}>
<CardContent>
<Grid container direction="row">
<Grid item xs={12}>
- <Typography paragraph={true} >
+ <Typography paragraph={true} >
You can log in to multiple Arvados sites here, then use the multi-site search page to search collections and projects on all sites at once.
- </Typography>
+ </Typography>
</Grid>
</Grid>
<Grid item xs={12}>
<TableHead>
<TableRow className={classes.tableRow}>
<TableCell>Cluster ID</TableCell>
+ <TableCell>Host</TableCell>
<TableCell>Username</TableCell>
<TableCell>Email</TableCell>
<TableCell>Status</TableCell>
{sessions.map((session, index) => {
const validating = session.status === SessionStatus.BEING_VALIDATED;
return <TableRow key={index} className={classes.tableRow}>
- <TableCell>{session.clusterId}</TableCell>
- <TableCell>{validating ? <CircularProgress size={20}/> : session.username}</TableCell>
- <TableCell>{validating ? <CircularProgress size={20}/> : session.email}</TableCell>
+ <TableCell>{remoteHostsConfig[session.clusterId] ?
+ <a href={remoteHostsConfig[session.clusterId].workbench2Url} style={{ textDecoration: 'none' }}> <ResourceCluster uuid={session.clusterId} /></a>
+ : session.clusterId}</TableCell>
+ <TableCell>{session.remoteHost}</TableCell>
+ <TableCell>{validating ? <CircularProgress size={20} /> : session.username}</TableCell>
+ <TableCell>{validating ? <CircularProgress size={20} /> : session.email}</TableCell>
<TableCell className={classes.statusCell}>
<Button fullWidth
disabled={validating || session.status === SessionStatus.INVALIDATED || session.active}
<form onSubmit={handleSubmit}>
<Grid container direction="row">
<Grid item xs={12}>
- <Typography paragraph={true} className={classes.remoteSiteInfo}>
+ <Typography paragraph={true} className={classes.remoteSiteInfo}>
To add a remote Arvados site, paste the remote site's host here (see "ARVADOS_API_HOST" on the "current token" page).
- </Typography>
+ </Typography>
</Grid>
<Grid item xs={8}>
<Field
placeholder="zzzz.arvadosapi.com"
margin="normal"
label="New cluster"
- autoFocus/>
+ autoFocus />
</Grid>
<Grid item xs={3}>
<Button type="submit" variant="contained" color="primary"
const mapStateToProps = (state: RootState): SiteManagerPanelRootDataProps => {
return {
- sessions: state.auth.sessions
+ sessions: state.auth.sessions,
+ remoteHostsConfig: state.auth.remoteHostsConfig
};
};
console.log(`Cluster ${k} does not define workbench2Url. Federated login / cross-site linking to ${k} is unavailable. Tell the admin of ${k} to set Services->Workbench2->ExternalURL in config.yml.`);
return;
}
- return <iframe key={k} src={`${remoteHostsConfig[k].workbench2Url}/fedtoken?api_token=${getSaltedToken(k, tokenUuid, token)}`} style={{
+ const fedtoken = (remoteHostsConfig[k].loginCluster === localCluster)
+ ? apiToken : getSaltedToken(k, tokenUuid, token);
+ return <iframe key={k} src={`${remoteHostsConfig[k].workbench2Url}/fedtoken?api_token=${fedtoken}`} style={{
height: 0,
width: 0,
visibility: "hidden"