Merge branch '22217-packer-keypair-type'
[arvados.git] / services / workbench2 / 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 { 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';
16
17 type CssRules = 'root';
18
19 const styles: CustomStyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
20     root: {
21         width: '100%',
22         overflow: 'auto',
23         display: 'flex'
24     }
25 });
26
27 export interface LinkAccountPanelRootDataProps {
28     targetUser?: UserResource;
29     userToLink?: UserResource;
30     remoteHostsConfig: { [key: string]: Config };
31     hasRemoteHosts: boolean;
32     localCluster: string;
33     loginCluster: string;
34     status: LinkAccountPanelStatus;
35     error: LinkAccountPanelError;
36     selectedCluster?: string;
37     isProcessing: boolean;
38 }
39
40 export interface LinkAccountPanelRootActionProps {
41     startLinking: (type: LinkAccountType) => void;
42     cancelLinking: () => void;
43     linkAccount: () => void;
44     setSelectedCluster: (cluster: string) => void;
45 }
46
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>);
50     if (showCluster) {
51         const homeCluster = user.uuid.substring(0, 5);
52         disp.push(<span> hosted on cluster <b>{homeCluster}</b> and </span>);
53     }
54     if (showCreatedAt) {
55         disp.push(<span> created on <b>{formatDate(user.createdAt)}</b></span>);
56     }
57     return disp;
58 }
59
60 function isLocalUser(uuid: string, localCluster: string) {
61     return uuid.substring(0, 5) === localCluster;
62 }
63
64 type LinkAccountPanelRootProps = LinkAccountPanelRootDataProps & LinkAccountPanelRootActionProps & WithStyles<CssRules>;
65
66 export const LinkAccountPanelRoot = withStyles(styles)(
67     ({ classes, targetUser, userToLink, status, isProcessing, error, startLinking, cancelLinking, linkAccount,
68         remoteHostsConfig, hasRemoteHosts, selectedCluster, setSelectedCluster, localCluster, loginCluster }: LinkAccountPanelRootProps) => {
69
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}>
75                     <Grid item xs={12}>
76                         If you would like to link this account to another one, please contact your administrator.
77                     </Grid>
78                 </Grid>
79             </CardContent></Card>;
80         }
81         return (
82             <Card className={classes.root}><CardContent>
83                 { isProcessing && <Grid container item direction="column" alignContent="center" spacing={3}>
84                     <Grid item>
85                         Loading user info. Please wait.
86                     </Grid>
87                     <Grid item style={{ alignSelf: 'center' }}>
88                         <CircularProgress />
89                     </Grid>
90                 </Grid> }
91
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}>
96                             <Grid item>
97                                 You are currently logged in as {displayUser(targetUser, true)}
98                             </Grid>
99                             <Grid item>
100                                 You can link Arvados accounts. After linking, either login will take you to the same account.
101                             </Grid >
102                         </Grid>
103                         <Grid container item direction="row" spacing={3}>
104                             <Grid item>
105                                 <Button disabled={!targetUser.isActive} color="primary" variant="contained" onClick={() => startLinking(LinkAccountType.ADD_OTHER_LOGIN)}>
106                                     Add another login to this account
107                                 </Button>
108                             </Grid>
109                             <Grid item>
110                                 <Button color="primary" variant="contained" onClick={() => startLinking(LinkAccountType.ACCESS_OTHER_ACCOUNT)}>
111                                     Use this login to access another account
112                                 </Button>
113                             </Grid>
114                         </Grid>
115                         {hasRemoteHosts && selectedCluster && <Grid container item direction="column" spacing={3}>
116                             <Grid item>
117                                 You can also link {displayUser(targetUser, false)} with an account from a remote cluster.
118                             </Grid>
119                             <Grid item>
120                                 Please select the cluster that hosts the account you want to link with:
121                                 <Select
122                                     variant="standard"
123                                     id="remoteHostsDropdown"
124                                     native
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)}
129                                 </Select>
130                             </Grid>
131                             <Grid item>
132                                 <Button color="primary" variant="contained" onClick={() => startLinking(LinkAccountType.ACCESS_OTHER_REMOTE_ACCOUNT)}>
133                                     Link with an account on&nbsp;{hasRemoteHosts ? <label>{selectedCluster} </label> : null}
134                                 </Button>
135                             </Grid>
136                         </Grid>}
137                     </Grid>
138                     : <Grid container spacing={3}>
139                         <Grid container item direction="column" spacing={3}>
140                             <Grid item>
141                                 You are currently logged in as {displayUser(targetUser, true, true)}
142                             </Grid>
143                             { targetUser.isActive
144                             ? (loginCluster === ""
145                                 ? <> <Grid item>
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>.
150                                 </Grid >
151                                 <Grid item>
152                                     <Button color="primary" variant="contained" onClick={() => startLinking(LinkAccountType.ADD_LOCAL_TO_REMOTE)}>
153                                         Link an account from {localCluster} to this account
154                                     </Button>
155                                 </Grid></>
156                                 : <Grid item>Please visit cluster
157                                     <a href={remoteHostsConfig[loginCluster].workbench2Url + "/link_account"}>{loginCluster}</a> to perform account linking.
158                                 </Grid> )
159                             : <Grid item>
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.
163                             </Grid> }
164                         </Grid>
165                     </Grid> }
166                 </div> }
167
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}>
171                             <Grid item>
172                                 Clicking 'Link accounts' will link {displayUser(userToLink, true, !isLocalUser(targetUser.uuid, localCluster))} to {displayUser(targetUser, true, !isLocalUser(targetUser.uuid, localCluster))}.
173                             </Grid>
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)}.
176                             </Grid>}
177                             <Grid item>
178                                 Any object owned by {displayUser(userToLink)} will be transfered to {displayUser(targetUser)}.
179                             </Grid>
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.
182                             </Grid>}
183                         </Grid>}
184                         {error === LinkAccountPanelError.NON_ADMIN && <Grid item>
185                             Cannot link admin account {displayUser(userToLink)} to non-admin account {displayUser(targetUser)}.
186                         </Grid>}
187                         {error === LinkAccountPanelError.SAME_USER && <Grid item>
188                             Cannot link {displayUser(targetUser)} to the same account.
189                         </Grid>}
190                         {error === LinkAccountPanelError.INACTIVE && <Grid item>
191                             Cannot link account {displayUser(userToLink)} to inactive account {displayUser(targetUser)}.
192                         </Grid>}
193                         <Grid container item direction="row" spacing={3}>
194                             <Grid item>
195                                 <Button variant="contained" onClick={() => cancelLinking()}>
196                                     Cancel
197                                 </Button>
198                             </Grid>
199                             <Grid item>
200                                 <Button disabled={status === LinkAccountPanelStatus.ERROR} color="primary" variant="contained" onClick={() => linkAccount()}>
201                                     Link accounts
202                                 </Button>
203                             </Grid>
204                         </Grid>
205                     </Grid>}
206             </CardContent></Card>
207         );
208     });