15088: Adds federated account linking
[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 * as React from 'react';
6 import {
7     StyleRulesCallback,
8     WithStyles,
9     withStyles,
10     Card,
11     CardContent,
12     Button,
13     Grid,
14     Select
15 } from '@material-ui/core';
16 import { ArvadosTheme } from '~/common/custom-theme';
17 import { UserResource } from "~/models/user";
18 import { LinkAccountType } from "~/models/link-account";
19 import { formatDate } from "~/common/formatters";
20 import { LinkAccountPanelStatus, LinkAccountPanelError } from "~/store/link-account-panel/link-account-panel-reducer";
21
22 type CssRules = 'root';// | 'gridItem' | 'label' | 'title' | 'actions';
23
24 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
25     root: {
26         width: '100%',
27         overflow: 'auto'
28     }
29 });
30
31 export interface LinkAccountPanelRootDataProps {
32     targetUser?: UserResource;
33     userToLink?: UserResource;
34     remoteHosts:  { [key: string]: string };
35     hasRemoteHosts: boolean;
36     status : LinkAccountPanelStatus;
37     error: LinkAccountPanelError;
38     selectedCluster?: string;
39 }
40
41 export interface LinkAccountPanelRootActionProps {
42     startLinking: (type: LinkAccountType) => void;
43     cancelLinking: () => void;
44     linkAccount: () => void;
45     setSelectedCluster: (cluster: string) => void;
46 }
47
48 function displayUser(user: UserResource, showCreatedAt: boolean = false, showCluster: boolean = false) {
49     const disp = [];
50     disp.push(<span><b>{user.email}</b> ({user.username}, {user.uuid})</span>);
51     if (showCluster) {
52         const homeCluster = user.uuid.substr(0,5);
53         disp.push(<span> hosted on cluster <b>{homeCluster}</b> and </span>);
54     }
55     if (showCreatedAt) {
56         disp.push(<span> created on <b>{formatDate(user.createdAt, true)}</b></span>);
57     }
58     return disp;
59 }
60
61 type LinkAccountPanelRootProps = LinkAccountPanelRootDataProps & LinkAccountPanelRootActionProps & WithStyles<CssRules>;
62
63 export const LinkAccountPanelRoot = withStyles(styles) (
64     ({classes, targetUser, userToLink, status, error, startLinking, cancelLinking, linkAccount,
65       remoteHosts, hasRemoteHosts, selectedCluster, setSelectedCluster}: LinkAccountPanelRootProps) => {
66         return <Card className={classes.root}>
67             <CardContent>
68             { status === LinkAccountPanelStatus.INITIAL && targetUser &&
69             <Grid container spacing={24}>
70                 <Grid container item direction="column" spacing={24}>
71                     <Grid item>
72                         You are currently logged in as {displayUser(targetUser, true, hasRemoteHosts)}
73                     </Grid>
74                     <Grid item>
75                         You can link Arvados accounts. After linking, either login will take you to the same account.
76                     </Grid >
77                     {hasRemoteHosts && selectedCluster && <Grid item>
78                         Please select the cluster that hosts the account you want to link with:
79                             <Select id="remoteHostsDropdown" native defaultValue={selectedCluster} style={{ marginLeft: "1em" }}
80                                 onChange={(event) => setSelectedCluster(event.target.value)}>
81                                 {Object.keys(remoteHosts).map((k) => <option key={k} value={k}>{k}</option>)}
82                             </Select>
83                     </Grid> }
84                 </Grid>
85                 <Grid container item direction="row" spacing={24}>
86                     <Grid item>
87                         <Button disabled={!targetUser.isActive} color="primary" variant="contained" onClick={() => startLinking(LinkAccountType.ADD_OTHER_LOGIN)}>
88                             Add another login&nbsp;{hasRemoteHosts ? <label> from {selectedCluster} </label> : null}&nbsp;to this account
89                         </Button>
90                     </Grid>
91                     <Grid item>
92                         <Button color="primary" variant="contained" onClick={() => startLinking(LinkAccountType.ACCESS_OTHER_ACCOUNT)}>
93                             Use this login to access another account&nbsp;{hasRemoteHosts ? <label> on {selectedCluster} </label> : null}
94                         </Button>
95                     </Grid>
96                 </Grid>
97             </Grid> }
98             { (status === LinkAccountPanelStatus.LINKING || status === LinkAccountPanelStatus.ERROR) && userToLink && targetUser &&
99             <Grid container spacing={24}>
100                 { status === LinkAccountPanelStatus.LINKING && <Grid container item direction="column" spacing={24}>
101                     <Grid item>
102                         Clicking 'Link accounts' will link {displayUser(userToLink, true, hasRemoteHosts)} to {displayUser(targetUser, true, hasRemoteHosts)}.
103                     </Grid>
104                     <Grid item>
105                         After linking, logging in as {displayUser(userToLink)} will log you into the same account as {displayUser(targetUser)}.
106                     </Grid>
107                     <Grid item>
108                        Any object owned by {displayUser(userToLink)} will be transfered to {displayUser(targetUser)}.
109                     </Grid>
110                 </Grid> }
111                 { error === LinkAccountPanelError.NON_ADMIN && <Grid item>
112                     Cannot link admin account {displayUser(userToLink)} to non-admin account {displayUser(targetUser)}.
113                 </Grid> }
114                 { error === LinkAccountPanelError.SAME_USER && <Grid item>
115                     Cannot link {displayUser(targetUser)} to the same account.
116                 </Grid> }
117                 { error === LinkAccountPanelError.INACTIVE && <Grid item>
118                     Cannot link account {displayUser(userToLink)} to inactive account {displayUser(targetUser)}.
119                 </Grid> }
120                 <Grid container item direction="row" spacing={24}>
121                     <Grid item>
122                         <Button variant="contained" onClick={() => cancelLinking()}>
123                             Cancel
124                         </Button>
125                     </Grid>
126                     <Grid item>
127                         <Button disabled={status === LinkAccountPanelStatus.ERROR} color="primary" variant="contained" onClick={() => linkAccount()}>
128                             Link accounts
129                         </Button>
130                     </Grid>
131                 </Grid>
132             </Grid> }
133             </CardContent>
134         </Card> ;
135 });