Merge branch '17754-federated-acct-merge'. Closes #17754.
[arvados-workbench2.git] / src / views / link-account-panel / link-account-panel-root.tsx
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 import React from 'react';
6 import {
7     StyleRulesCallback,
8     WithStyles,
9     withStyles,
10     Card,
11     CardContent,
12     Button,
13     Grid,
14     Select,
15     CircularProgress
16 } from '@material-ui/core';
17 import { ArvadosTheme } from 'common/custom-theme';
18 import { UserResource } from "models/user";
19 import { LinkAccountType } from "models/link-account";
20 import { formatDate } from "common/formatters";
21 import { LinkAccountPanelStatus, LinkAccountPanelError } from "store/link-account-panel/link-account-panel-reducer";
22 import { Config } from 'common/config';
23
24 type CssRules = 'root';
25
26 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
27     root: {
28         width: '100%',
29         overflow: 'auto',
30         display: 'flex'
31     }
32 });
33
34 export interface LinkAccountPanelRootDataProps {
35     targetUser?: UserResource;
36     userToLink?: UserResource;
37     remoteHostsConfig: { [key: string]: Config };
38     hasRemoteHosts: boolean;
39     localCluster: string;
40     loginCluster: string;
41     status: LinkAccountPanelStatus;
42     error: LinkAccountPanelError;
43     selectedCluster?: string;
44     isProcessing: boolean;
45 }
46
47 export interface LinkAccountPanelRootActionProps {
48     startLinking: (type: LinkAccountType) => void;
49     cancelLinking: () => void;
50     linkAccount: () => void;
51     setSelectedCluster: (cluster: string) => void;
52 }
53
54 function displayUser(user: UserResource, showCreatedAt: boolean = false, showCluster: boolean = false) {
55     const disp: JSX.Element[] = [];
56     disp.push(<span><b>{user.email}</b> ({user.username}, {user.uuid})</span>);
57     if (showCluster) {
58         const homeCluster = user.uuid.substring(0, 5);
59         disp.push(<span> hosted on cluster <b>{homeCluster}</b> and </span>);
60     }
61     if (showCreatedAt) {
62         disp.push(<span> created on <b>{formatDate(user.createdAt)}</b></span>);
63     }
64     return disp;
65 }
66
67 function isLocalUser(uuid: string, localCluster: string) {
68     return uuid.substring(0, 5) === localCluster;
69 }
70
71 type LinkAccountPanelRootProps = LinkAccountPanelRootDataProps & LinkAccountPanelRootActionProps & WithStyles<CssRules>;
72
73 export const LinkAccountPanelRoot = withStyles(styles)(
74     ({ classes, targetUser, userToLink, status, isProcessing, error, startLinking, cancelLinking, linkAccount,
75         remoteHostsConfig, hasRemoteHosts, selectedCluster, setSelectedCluster, localCluster, loginCluster }: LinkAccountPanelRootProps) => {
76
77         // If a LoginFederation is configured, the self-serve account linking is not
78         // currently available.
79         if (loginCluster !== "") {
80             return <Card className={classes.root}><CardContent>
81                 <Grid container spacing={16}>
82                     <Grid item xs={12}>
83                         If you would like to link this account to another one, please contact your administrator.
84                     </Grid>
85                 </Grid>
86             </CardContent></Card>;
87         }
88         return <Card className={classes.root}><CardContent>
89             { isProcessing && <Grid container item direction="column" alignContent="center" spacing={24}>
90                 <Grid item>
91                     Loading user info. Please wait.
92                 </Grid>
93                 <Grid item style={{ alignSelf: 'center' }}>
94                     <CircularProgress />
95                 </Grid>
96             </Grid> }
97
98             { !isProcessing && status === LinkAccountPanelStatus.INITIAL && targetUser && <div>
99                 { isLocalUser(targetUser.uuid, localCluster)
100                 ? <Grid container spacing={24}>
101                     <Grid container item direction="column" spacing={24}>
102                         <Grid item>
103                             You are currently logged in as {displayUser(targetUser, true)}
104                         </Grid>
105                         <Grid item>
106                             You can link Arvados accounts. After linking, either login will take you to the same account.
107                         </Grid >
108                     </Grid>
109                     <Grid container item direction="row" spacing={24}>
110                         <Grid item>
111                             <Button disabled={!targetUser.isActive} color="primary" variant="contained" onClick={() => startLinking(LinkAccountType.ADD_OTHER_LOGIN)}>
112                                 Add another login to this account
113                             </Button>
114                         </Grid>
115                         <Grid item>
116                             <Button color="primary" variant="contained" onClick={() => startLinking(LinkAccountType.ACCESS_OTHER_ACCOUNT)}>
117                                 Use this login to access another account
118                             </Button>
119                         </Grid>
120                     </Grid>
121                     {hasRemoteHosts && selectedCluster && <Grid container item direction="column" spacing={24}>
122                         <Grid item>
123                             You can also link {displayUser(targetUser, false)} with an account from a remote cluster.
124                         </Grid>
125                         <Grid item>
126                             Please select the cluster that hosts the account you want to link with:
127                             <Select id="remoteHostsDropdown" native defaultValue={selectedCluster} style={{ marginLeft: "1em" }}
128                                 onChange={(event) => setSelectedCluster(event.target.value)}>
129                                 {Object.keys(remoteHostsConfig).map((k) => k !== localCluster ? <option key={k} value={k}>{k}</option> : null)}
130                             </Select>
131                         </Grid>
132                         <Grid item>
133                             <Button color="primary" variant="contained" onClick={() => startLinking(LinkAccountType.ACCESS_OTHER_REMOTE_ACCOUNT)}>
134                                 Link with an account on&nbsp;{hasRemoteHosts ? <label>{selectedCluster} </label> : null}
135                             </Button>
136                         </Grid>
137                     </Grid>}
138                 </Grid>
139                 : <Grid container spacing={24}>
140                     <Grid container item direction="column" spacing={24}>
141                         <Grid item>
142                             You are currently logged in as {displayUser(targetUser, true, true)}
143                         </Grid>
144                         { targetUser.isActive
145                         ? (loginCluster === ""
146                             ? <> <Grid item>
147                                 This a remote account. You can link a local Arvados account to this one.
148                                 After linking, you can access the local account's data by logging into the
149                                 <b>{localCluster}</b> cluster as user <b>{targetUser.email}</b>
150                                 from <b>{targetUser.uuid.substring(0, 5)}</b>.
151                             </Grid >
152                             <Grid item>
153                                 <Button color="primary" variant="contained" onClick={() => startLinking(LinkAccountType.ADD_LOCAL_TO_REMOTE)}>
154                                     Link an account from {localCluster} to this account
155                                 </Button>
156                             </Grid></>
157                             : <Grid item>Please visit cluster
158                                 <a href={remoteHostsConfig[loginCluster].workbench2Url + "/link_account"}>{loginCluster}</a> to perform account linking.
159                             </Grid> )
160                         : <Grid item>
161                             This an inactive remote account. An administrator must activate your
162                             account before you can proceed.  After your accounts is activated,
163                             you can link a local Arvados account hosted by the <b>{localCluster}</b> cluster to this one.
164                         </Grid> }
165                     </Grid>
166                 </Grid> }
167             </div> }
168
169             {!isProcessing && (status === LinkAccountPanelStatus.LINKING || status === LinkAccountPanelStatus.ERROR) && userToLink && targetUser &&
170                 <Grid container spacing={24}>
171                     {status === LinkAccountPanelStatus.LINKING && <Grid container item direction="column" spacing={24}>
172                         <Grid item>
173                             Clicking 'Link accounts' will link {displayUser(userToLink, true, !isLocalUser(targetUser.uuid, localCluster))} to {displayUser(targetUser, true, !isLocalUser(targetUser.uuid, localCluster))}.
174                         </Grid>
175                         {(isLocalUser(targetUser.uuid, localCluster)) && <Grid item>
176                             After linking, logging in as {displayUser(userToLink)} will log you into the same account as {displayUser(targetUser)}.
177                         </Grid>}
178                         <Grid item>
179                             Any object owned by {displayUser(userToLink)} will be transfered to {displayUser(targetUser)}.
180                         </Grid>
181                         {!isLocalUser(targetUser.uuid, localCluster) && <Grid item>
182                             You can access <b>{userToLink.email}</b> data by logging into <b>{localCluster}</b> with the <b>{targetUser.email}</b> account.
183                         </Grid>}
184                     </Grid>}
185                     {error === LinkAccountPanelError.NON_ADMIN && <Grid item>
186                         Cannot link admin account {displayUser(userToLink)} to non-admin account {displayUser(targetUser)}.
187                     </Grid>}
188                     {error === LinkAccountPanelError.SAME_USER && <Grid item>
189                         Cannot link {displayUser(targetUser)} to the same account.
190                     </Grid>}
191                     {error === LinkAccountPanelError.INACTIVE && <Grid item>
192                         Cannot link account {displayUser(userToLink)} to inactive account {displayUser(targetUser)}.
193                     </Grid>}
194                     <Grid container item direction="row" spacing={24}>
195                         <Grid item>
196                             <Button variant="contained" onClick={() => cancelLinking()}>
197                                 Cancel
198                             </Button>
199                         </Grid>
200                         <Grid item>
201                             <Button disabled={status === LinkAccountPanelStatus.ERROR} color="primary" variant="contained" onClick={() => linkAccount()}>
202                                 Link accounts
203                             </Button>
204                         </Grid>
205                     </Grid>
206                 </Grid>}
207         </CardContent></Card>;
208     });