1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
5 import React from 'react';
6 import { CustomStyleRulesCallback } from 'common/custom-theme';
7 import { Card, CardContent, Button, Grid, Select, CircularProgress } from '@mui/material';
8 import { WithStyles } from '@mui/styles';
9 import withStyles from '@mui/styles/withStyles';
10 import { ArvadosTheme } from 'common/custom-theme';
11 import { UserResource } from "models/user";
12 import { LinkAccountType } from "models/link-account";
13 import { formatDate } from "common/formatters";
14 import { LinkAccountPanelStatus, LinkAccountPanelError } from "store/link-account-panel/link-account-panel-reducer";
15 import { Config } from 'common/config';
17 type CssRules = 'root';
19 const styles: CustomStyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
27 export interface LinkAccountPanelRootDataProps {
28 targetUser?: UserResource;
29 userToLink?: UserResource;
30 remoteHostsConfig: { [key: string]: Config };
31 hasRemoteHosts: boolean;
34 status: LinkAccountPanelStatus;
35 error: LinkAccountPanelError;
36 selectedCluster?: string;
37 isProcessing: boolean;
40 export interface LinkAccountPanelRootActionProps {
41 startLinking: (type: LinkAccountType) => void;
42 cancelLinking: () => void;
43 linkAccount: () => void;
44 setSelectedCluster: (cluster: string) => void;
47 function displayUser(user: UserResource, showCreatedAt: boolean = false, showCluster: boolean = false) {
48 const disp: JSX.Element[] = [];
49 disp.push(<span><b>{user.email}</b> ({user.username}, {user.uuid})</span>);
51 const homeCluster = user.uuid.substring(0, 5);
52 disp.push(<span> hosted on cluster <b>{homeCluster}</b> and </span>);
55 disp.push(<span> created on <b>{formatDate(user.createdAt)}</b></span>);
60 function isLocalUser(uuid: string, localCluster: string) {
61 return uuid.substring(0, 5) === localCluster;
64 type LinkAccountPanelRootProps = LinkAccountPanelRootDataProps & LinkAccountPanelRootActionProps & WithStyles<CssRules>;
66 export const LinkAccountPanelRoot = withStyles(styles)(
67 ({ classes, targetUser, userToLink, status, isProcessing, error, startLinking, cancelLinking, linkAccount,
68 remoteHostsConfig, hasRemoteHosts, selectedCluster, setSelectedCluster, localCluster, loginCluster }: LinkAccountPanelRootProps) => {
70 // If a LoginFederation is configured, the self-serve account linking is not
71 // currently available.
72 if (loginCluster !== "") {
73 return <Card className={classes.root}><CardContent>
74 <Grid container spacing={2}>
76 If you would like to link this account to another one, please contact your administrator.
79 </CardContent></Card>;
82 <Card className={classes.root}><CardContent>
83 { isProcessing && <Grid container item direction="column" alignContent="center" spacing={3}>
85 Loading user info. Please wait.
87 <Grid item style={{ alignSelf: 'center' }}>
92 { !isProcessing && status === LinkAccountPanelStatus.INITIAL && targetUser && <div>
93 { isLocalUser(targetUser.uuid, localCluster)
94 ? <Grid container spacing={3}>
95 <Grid container item direction="column" spacing={3}>
97 You are currently logged in as {displayUser(targetUser, true)}
100 You can link Arvados accounts. After linking, either login will take you to the same account.
103 <Grid container item direction="row" spacing={3}>
105 <Button disabled={!targetUser.isActive} color="primary" variant="contained" onClick={() => startLinking(LinkAccountType.ADD_OTHER_LOGIN)}>
106 Add another login to this account
110 <Button color="primary" variant="contained" onClick={() => startLinking(LinkAccountType.ACCESS_OTHER_ACCOUNT)}>
111 Use this login to access another account
115 {hasRemoteHosts && selectedCluster && <Grid container item direction="column" spacing={3}>
117 You can also link {displayUser(targetUser, false)} with an account from a remote cluster.
120 Please select the cluster that hosts the account you want to link with:
123 id="remoteHostsDropdown"
125 defaultValue={selectedCluster}
126 style={{ marginLeft: "1em" }}
127 onChange={(event) => setSelectedCluster(event.target.value)}>
128 {Object.keys(remoteHostsConfig).map((k) => k !== localCluster ? <option key={k} value={k}>{k}</option> : null)}
132 <Button color="primary" variant="contained" onClick={() => startLinking(LinkAccountType.ACCESS_OTHER_REMOTE_ACCOUNT)}>
133 Link with an account on {hasRemoteHosts ? <label>{selectedCluster} </label> : null}
138 : <Grid container spacing={3}>
139 <Grid container item direction="column" spacing={3}>
141 You are currently logged in as {displayUser(targetUser, true, true)}
143 { targetUser.isActive
144 ? (loginCluster === ""
146 This a remote account. You can link a local Arvados account to this one.
147 After linking, you can access the local account's data by logging into the
148 <b>{localCluster}</b> cluster as user <b>{targetUser.email}</b>
149 from <b>{targetUser.uuid.substring(0, 5)}</b>.
152 <Button color="primary" variant="contained" onClick={() => startLinking(LinkAccountType.ADD_LOCAL_TO_REMOTE)}>
153 Link an account from {localCluster} to this account
156 : <Grid item>Please visit cluster
157 <a href={remoteHostsConfig[loginCluster].workbench2Url + "/link_account"}>{loginCluster}</a> to perform account linking.
160 This an inactive remote account. An administrator must activate your
161 account before you can proceed. After your accounts is activated,
162 you can link a local Arvados account hosted by the <b>{localCluster}</b> cluster to this one.
168 {!isProcessing && (status === LinkAccountPanelStatus.LINKING || status === LinkAccountPanelStatus.ERROR) && userToLink && targetUser &&
169 <Grid container spacing={3}>
170 {status === LinkAccountPanelStatus.LINKING && <Grid container item direction="column" spacing={3}>
172 Clicking 'Link accounts' will link {displayUser(userToLink, true, !isLocalUser(targetUser.uuid, localCluster))} to {displayUser(targetUser, true, !isLocalUser(targetUser.uuid, localCluster))}.
174 {(isLocalUser(targetUser.uuid, localCluster)) && <Grid item>
175 After linking, logging in as {displayUser(userToLink)} will log you into the same account as {displayUser(targetUser)}.
178 Any object owned by {displayUser(userToLink)} will be transfered to {displayUser(targetUser)}.
180 {!isLocalUser(targetUser.uuid, localCluster) && <Grid item>
181 You can access <b>{userToLink.email}</b> data by logging into <b>{localCluster}</b> with the <b>{targetUser.email}</b> account.
184 {error === LinkAccountPanelError.NON_ADMIN && <Grid item>
185 Cannot link admin account {displayUser(userToLink)} to non-admin account {displayUser(targetUser)}.
187 {error === LinkAccountPanelError.SAME_USER && <Grid item>
188 Cannot link {displayUser(targetUser)} to the same account.
190 {error === LinkAccountPanelError.INACTIVE && <Grid item>
191 Cannot link account {displayUser(userToLink)} to inactive account {displayUser(targetUser)}.
193 <Grid container item direction="row" spacing={3}>
195 <Button variant="contained" onClick={() => cancelLinking()}>
200 <Button disabled={status === LinkAccountPanelStatus.ERROR} color="primary" variant="contained" onClick={() => linkAccount()}>
206 </CardContent></Card>