2176b3235b5854278ec0a72324dec28f6750bfbc
[arvados-workbench2.git] / src / views / user-profile-panel / user-profile-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 { Field, InjectedFormProps } from "redux-form";
7 import { TextField } from "components/text-field/text-field";
8 import { DataExplorer } from "views-components/data-explorer/data-explorer";
9 import { NativeSelectField } from "components/select-field/select-field";
10 import {
11     StyleRulesCallback,
12     WithStyles,
13     withStyles,
14     Card,
15     CardContent,
16     Button,
17     Typography,
18     Grid,
19     InputLabel,
20     Tabs, Tab,
21     Paper
22 } from '@material-ui/core';
23 import { ArvadosTheme } from 'common/custom-theme';
24 import { User } from "models/user";
25 import { DataTableDefaultView } from 'components/data-table-default-view/data-table-default-view';
26 import { MY_ACCOUNT_VALIDATION } from "validators/validators";
27 import { USER_PROFILE_PANEL_ID } from 'store/user-profile/user-profile-actions';
28 import { noop } from 'lodash';
29 import { GroupsIcon } from 'components/icon/icon';
30 import { DataColumns } from 'components/data-table/data-table';
31 import { ResourceLinkHeadUuid, ResourceLinkHeadPermissionLevel, ResourceLinkHead, ResourceLinkDelete, ResourceLinkTailIsVisible } from 'views-components/data-explorer/renderers';
32 import { createTree } from 'models/tree';
33
34 type CssRules = 'root' | 'adminRoot' | 'gridItem' | 'label' | 'readOnlyValue' | 'title' | 'description' | 'actions' | 'content';
35
36 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
37     root: {
38         width: '100%',
39         overflow: 'auto'
40     },
41     adminRoot: {
42         // ...theme.mixins.gutters()
43     },
44     gridItem: {
45         height: 45,
46         marginBottom: 20
47     },
48     label: {
49         fontSize: '0.675rem',
50         color: theme.palette.grey['600']
51     },
52     readOnlyValue: {
53         fontSize: '0.875rem',
54     },
55     title: {
56         fontSize: '1.1rem',
57     },
58     description: {
59         color: theme.palette.grey["600"]
60     },
61     actions: {
62         display: 'flex',
63         justifyContent: 'flex-end'
64     },
65     content: {
66         // reserve space for the tab bar
67         height: `calc(100% - ${theme.spacing.unit * 7}px)`,
68     }
69 });
70
71 export interface UserProfilePanelRootActionProps {
72     openSetupShellAccount: (uuid: string) => void;
73     loginAs: (uuid: string) => void;
74     openDeactivateDialog: (uuid: string) => void;
75 }
76
77 export interface UserProfilePanelRootDataProps {
78     isAdmin: boolean;
79     isSelf: boolean;
80     isPristine: boolean;
81     isValid: boolean;
82     initialValues?: User;
83     localCluster: string;
84 }
85
86 const RoleTypes = [
87     { key: 'Bio-informatician', value: 'Bio-informatician' },
88     { key: 'Data Scientist', value: 'Data Scientist' },
89     { key: 'Analyst', value: 'Analyst' },
90     { key: 'Researcher', value: 'Researcher' },
91     { key: 'Software Developer', value: 'Software Developer' },
92     { key: 'System Administrator', value: 'System Administrator' },
93     { key: 'Other', value: 'Other' }
94 ];
95
96 type UserProfilePanelRootProps = InjectedFormProps<{}> & UserProfilePanelRootActionProps & UserProfilePanelRootDataProps & WithStyles<CssRules>;
97
98 export enum UserProfileGroupsColumnNames {
99     NAME = "Name",
100     PERMISSION = "Permission",
101     VISIBLE = "Visible to other members",
102     UUID = "UUID",
103     REMOVE = "Remove",
104 }
105
106 export const userProfileGroupsColumns: DataColumns<string> = [
107     {
108         name: UserProfileGroupsColumnNames.NAME,
109         selected: true,
110         configurable: true,
111         filters: createTree(),
112         render: uuid => <ResourceLinkHead uuid={uuid} />
113     },
114     {
115         name: UserProfileGroupsColumnNames.PERMISSION,
116         selected: true,
117         configurable: true,
118         filters: createTree(),
119         render: uuid => <ResourceLinkHeadPermissionLevel uuid={uuid} />
120     },
121     {
122         name: UserProfileGroupsColumnNames.VISIBLE,
123         selected: true,
124         configurable: true,
125         filters: createTree(),
126         render: uuid => <ResourceLinkTailIsVisible uuid={uuid} />
127     },
128     {
129         name: UserProfileGroupsColumnNames.UUID,
130         selected: true,
131         configurable: true,
132         filters: createTree(),
133         render: uuid => <ResourceLinkHeadUuid uuid={uuid} />
134     },
135     {
136         name: UserProfileGroupsColumnNames.REMOVE,
137         selected: true,
138         configurable: true,
139         filters: createTree(),
140         render: uuid => <ResourceLinkDelete uuid={uuid} />
141     },
142 ];
143
144 const ReadOnlyField = withStyles(styles)(
145     (props: ({ label: string, input: {value: string} }) & WithStyles<CssRules> ) => (
146         <Grid item xs={12}>
147             <Typography className={props.classes.label}>
148                 {props.label}
149             </Typography>
150             <Typography className={props.classes.readOnlyValue}>
151                 {props.input.value}
152             </Typography>
153         </Grid>
154     )
155 );
156
157 export const UserProfilePanelRoot = withStyles(styles)(
158     class extends React.Component<UserProfilePanelRootProps> {
159         state = {
160             value: 0,
161         };
162
163         componentDidMount() {
164             this.setState({ value: 0 });
165         }
166
167         render() {
168             return <Paper className={this.props.classes.root}>
169                 <Tabs value={this.state.value} onChange={this.handleChange} variant={"fullWidth"}>
170                     <Tab label="PROFILE" />
171                     <Tab label="GROUPS" />
172                     <Tab label="ADMIN" disabled={!this.props.isAdmin} />
173                 </Tabs>
174                 {this.state.value === 0 &&
175                     <CardContent>
176                         <form onSubmit={this.props.handleSubmit}>
177                             <Grid container spacing={24}>
178                                 <Grid item className={this.props.classes.gridItem} sm={6} xs={12}>
179                                     <Field
180                                         label="First name"
181                                         name="firstName"
182                                         component={ReadOnlyField as any}
183                                         disabled
184                                     />
185                                 </Grid>
186                                 <Grid item className={this.props.classes.gridItem} sm={6} xs={12}>
187                                     <Field
188                                         label="Last name"
189                                         name="lastName"
190                                         component={ReadOnlyField as any}
191                                         disabled
192                                     />
193                                 </Grid>
194                                 <Grid item className={this.props.classes.gridItem} sm={6} xs={12}>
195                                     <Field
196                                         label="E-mail"
197                                         name="email"
198                                         component={ReadOnlyField as any}
199                                         disabled
200                                     />
201                                 </Grid>
202                                 <Grid item className={this.props.classes.gridItem} sm={6} xs={12}>
203                                     <Field
204                                         label="Username"
205                                         name="username"
206                                         component={ReadOnlyField as any}
207                                         disabled
208                                     />
209                                 </Grid>
210                                 <Grid item className={this.props.classes.gridItem} sm={6} xs={12}>
211                                     <Field
212                                         label="Organization"
213                                         name="prefs.profile.organization"
214                                         component={TextField as any}
215                                         validate={MY_ACCOUNT_VALIDATION}
216                                         disabled={!this.props.isAdmin && !this.props.isSelf}
217                                     />
218                                 </Grid>
219                                 <Grid item className={this.props.classes.gridItem} sm={6} xs={12}>
220                                     <Field
221                                         label="E-mail at Organization"
222                                         name="prefs.profile.organization_email"
223                                         component={TextField as any}
224                                         validate={MY_ACCOUNT_VALIDATION}
225                                         disabled={!this.props.isAdmin && !this.props.isSelf}
226                                     />
227                                 </Grid>
228                                 <Grid item className={this.props.classes.gridItem} sm={6} xs={12}>
229                                     <InputLabel className={this.props.classes.label} htmlFor="prefs.profile.role">Role</InputLabel>
230                                     <Field
231                                         id="prefs.profile.role"
232                                         name="prefs.profile.role"
233                                         component={NativeSelectField as any}
234                                         items={RoleTypes}
235                                         disabled={!this.props.isAdmin && !this.props.isSelf}
236                                     />
237                                 </Grid>
238                                 <Grid item className={this.props.classes.gridItem} sm={6} xs={12}>
239                                     <Field
240                                         label="Website"
241                                         name="prefs.profile.website_url"
242                                         component={TextField as any}
243                                         disabled={!this.props.isAdmin && !this.props.isSelf}
244                                     />
245                                 </Grid>
246                                 <Grid item sm={12}>
247                                     <Grid container direction="row" justify="flex-end">
248                                         <Button color="primary" onClick={this.props.reset} disabled={this.props.isPristine}>Discard changes</Button>
249                                         <Button
250                                             color="primary"
251                                             variant="contained"
252                                             type="submit"
253                                             disabled={this.props.isPristine || this.props.invalid || this.props.submitting}>
254                                             Save changes
255                                         </Button>
256                                     </Grid>
257                                 </Grid>
258                             </Grid>
259                         </form >
260                     </CardContent>
261                 }
262                 {this.state.value === 1 &&
263                     <div className={this.props.classes.content}>
264                         <DataExplorer
265                                 id={USER_PROFILE_PANEL_ID}
266                                 onRowClick={noop}
267                                 onRowDoubleClick={noop}
268                                 contextMenuColumn={false}
269                                 hideColumnSelector
270                                 hideSearchInput
271                                 paperProps={{
272                                     elevation: 0,
273                                 }}
274                                 dataTableDefaultView={
275                                     <DataTableDefaultView
276                                         icon={GroupsIcon}
277                                         messages={['Group list is empty.']} />
278                                 } />
279                     </div>}
280                 {this.state.value === 2 &&
281                     <Paper elevation={0} className={this.props.classes.adminRoot}>
282                         <Card elevation={0}>
283                             <CardContent>
284                                 <Grid container
285                                     direction="row"
286                                     justify={'flex-end'}
287                                     alignItems={'center'}>
288                                     <Grid item xs>
289                                         <Typography variant="h6" className={this.props.classes.title}>
290                                             Setup Account
291                                         </Typography>
292                                         <Typography variant="body1" className={this.props.classes.description}>
293                                             This button sets up a user. After setup, they will be able use Arvados. This dialog box also allows you to optionally set up a shell account for this user. The login name is automatically generated from the user's e-mail address.
294                                         </Typography>
295                                     </Grid>
296                                     <Grid item sm={'auto'} xs={12}>
297                                         <Button variant="contained"
298                                             color="primary"
299                                             onClick={() => {this.props.openSetupShellAccount(this.props.initialValues.uuid)}}
300                                             disabled={false}>
301                                             Setup Account
302                                         </Button>
303                                     </Grid>
304                                 </Grid>
305                             </CardContent>
306                         </Card>
307                         <Card elevation={0}>
308                             <CardContent>
309                                 <Grid container
310                                     direction="row"
311                                     justify={'flex-end'}
312                                     alignItems={'center'}>
313                                     <Grid item xs>
314                                         <Typography variant="h6" className={this.props.classes.title}>
315                                             Deactivate
316                                         </Typography>
317                                         <Typography variant="body1" className={this.props.classes.description}>
318                                             As an admin, you can deactivate and reset this user. This will remove all repository/VM permissions for the user. If you "setup" the user again, the user will have to sign the user agreement again. You may also want to reassign data ownership.
319                                         </Typography>
320                                     </Grid>
321                                     <Grid item sm={'auto'} xs={12}>
322                                         <Button variant="contained"
323                                             color="primary"
324                                             onClick={() => {this.props.openDeactivateDialog(this.props.initialValues.uuid)}}
325                                             disabled={false}>
326                                             Deactivate
327                                         </Button>
328                                     </Grid>
329                                 </Grid>
330                             </CardContent>
331                         </Card>
332                         <Card elevation={0}>
333                             <CardContent>
334                                 <Grid container
335                                     direction="row"
336                                     justify={'flex-end'}
337                                     alignItems={'center'}>
338                                     <Grid item xs>
339                                         <Typography variant="h6" className={this.props.classes.title}>
340                                             Log In
341                                         </Typography>
342                                         <Typography variant="body1" className={this.props.classes.description}>
343                                             As an admin, you can log in as this user. When you’ve finished, you will need to log out and log in again with your own account.
344                                         </Typography>
345                                     </Grid>
346                                     <Grid item sm={'auto'} xs={12}>
347                                         <Button variant="contained"
348                                             color="primary"
349                                             onClick={() => {this.props.loginAs(this.props.initialValues.uuid)}}
350                                             disabled={false}>
351                                             Log In
352                                         </Button>
353                                     </Grid>
354                                 </Grid>
355                             </CardContent>
356                         </Card>
357                     </Paper>}
358             </Paper >;
359         }
360
361         handleChange = (event: React.MouseEvent<HTMLElement>, value: number) => {
362             this.setState({ value });
363         }
364
365     }
366 );