18559: Fix user profile form initialization & cleanup
[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' | '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     },
51     title: {
52         fontSize: '1.1rem',
53     },
54     description: {
55         color: theme.palette.grey["600"]
56     },
57     actions: {
58         display: 'flex',
59         justifyContent: 'flex-end'
60     },
61     content: {
62         // reserve space for the tab bar
63         height: `calc(100% - ${theme.spacing.unit * 7}px)`,
64     }
65 });
66
67 export interface UserProfilePanelRootActionProps {
68     openSetupShellAccount: (uuid: string) => void;
69     loginAs: (uuid: string) => void;
70     openDeactivateDialog: (uuid: string) => void;
71 }
72
73 export interface UserProfilePanelRootDataProps {
74     isPristine: boolean;
75     isValid: boolean;
76     initialValues?: User;
77     localCluster: string;
78 }
79
80 const RoleTypes = [
81     { key: 'Bio-informatician', value: 'Bio-informatician' },
82     { key: 'Data Scientist', value: 'Data Scientist' },
83     { key: 'Analyst', value: 'Analyst' },
84     { key: 'Researcher', value: 'Researcher' },
85     { key: 'Software Developer', value: 'Software Developer' },
86     { key: 'System Administrator', value: 'System Administrator' },
87     { key: 'Other', value: 'Other' }
88 ];
89
90 type UserProfilePanelRootProps = InjectedFormProps<{}> & UserProfilePanelRootActionProps & UserProfilePanelRootDataProps & WithStyles<CssRules>;
91
92 export enum UserProfileGroupsColumnNames {
93     NAME = "Name",
94     PERMISSION = "Permission",
95     VISIBLE = "Visible to other members",
96     UUID = "UUID",
97     REMOVE = "Remove",
98 }
99
100 export const userProfileGroupsColumns: DataColumns<string> = [
101     {
102         name: UserProfileGroupsColumnNames.NAME,
103         selected: true,
104         configurable: true,
105         filters: createTree(),
106         render: uuid => <ResourceLinkHead uuid={uuid} />
107     },
108     {
109         name: UserProfileGroupsColumnNames.PERMISSION,
110         selected: true,
111         configurable: true,
112         filters: createTree(),
113         render: uuid => <ResourceLinkHeadPermissionLevel uuid={uuid} />
114     },
115     {
116         name: UserProfileGroupsColumnNames.VISIBLE,
117         selected: true,
118         configurable: true,
119         filters: createTree(),
120         render: uuid => <ResourceLinkTailIsVisible uuid={uuid} />
121     },
122     {
123         name: UserProfileGroupsColumnNames.UUID,
124         selected: true,
125         configurable: true,
126         filters: createTree(),
127         render: uuid => <ResourceLinkHeadUuid uuid={uuid} />
128     },
129     {
130         name: UserProfileGroupsColumnNames.REMOVE,
131         selected: true,
132         configurable: true,
133         filters: createTree(),
134         render: uuid => <ResourceLinkDelete uuid={uuid} />
135     },
136 ];
137
138 export const UserProfilePanelRoot = withStyles(styles)(
139     class extends React.Component<UserProfilePanelRootProps> {
140         state = {
141             value: 0,
142         };
143
144         componentDidMount() {
145             this.setState({ value: 0 });
146         }
147
148         render() {
149             return <Paper className={this.props.classes.root}>
150                 <Tabs value={this.state.value} onChange={this.handleChange} variant={"fullWidth"}>
151                     <Tab label="PROFILE" />
152                     <Tab label="GROUPS" />
153                     <Tab label="ADMIN" />
154                 </Tabs>
155                 {this.state.value === 0 &&
156                     // <Card className={this.props.classes.root}>
157                         <CardContent>
158                             <form onSubmit={this.props.handleSubmit}>
159                                 <Grid container spacing={24}>
160                                     <Grid item className={this.props.classes.gridItem} sm={6} xs={12}>
161                                         <Field
162                                             label="First name"
163                                             name="firstName"
164                                             component={TextField as any}
165                                             disabled
166                                         />
167                                     </Grid>
168                                     <Grid item className={this.props.classes.gridItem} sm={6} xs={12}>
169                                         <Field
170                                             label="Last name"
171                                             name="lastName"
172                                             component={TextField as any}
173                                             disabled
174                                         />
175                                     </Grid>
176                                     <Grid item className={this.props.classes.gridItem} sm={6} xs={12}>
177                                         <Field
178                                             label="E-mail"
179                                             name="email"
180                                             component={TextField as any}
181                                             disabled
182                                         />
183                                     </Grid>
184                                     <Grid item className={this.props.classes.gridItem} sm={6} xs={12}>
185                                         <Field
186                                             label="Username"
187                                             name="username"
188                                             component={TextField as any}
189                                             disabled
190                                         />
191                                     </Grid>
192                                     <Grid item className={this.props.classes.gridItem} sm={6} xs={12}>
193                                         <Field
194                                             label="Organization"
195                                             name="prefs.profile.organization"
196                                             component={TextField as any}
197                                             validate={MY_ACCOUNT_VALIDATION}
198                                             required
199                                         />
200                                     </Grid>
201                                     <Grid item className={this.props.classes.gridItem} sm={6} xs={12}>
202                                         <Field
203                                             label="E-mail at Organization"
204                                             name="prefs.profile.organization_email"
205                                             component={TextField as any}
206                                             validate={MY_ACCOUNT_VALIDATION}
207                                             required
208                                         />
209                                     </Grid>
210                                     <Grid item className={this.props.classes.gridItem} sm={6} xs={12}>
211                                         <InputLabel className={this.props.classes.label} htmlFor="prefs.profile.role">Role</InputLabel>
212                                         <Field
213                                             id="prefs.profile.role"
214                                             name="prefs.profile.role"
215                                             component={NativeSelectField as any}
216                                             items={RoleTypes}
217                                         />
218                                     </Grid>
219                                     <Grid item className={this.props.classes.gridItem} sm={6} xs={12}>
220                                         <Field
221                                             label="Website"
222                                             name="prefs.profile.website_url"
223                                             component={TextField as any}
224                                         />
225                                     </Grid>
226                                     <Grid item sm={12}>
227                                         <Grid container direction="row" justify="flex-end">
228                                             <Button color="primary" onClick={this.props.reset} disabled={this.props.isPristine}>Discard changes</Button>
229                                             <Button
230                                                 color="primary"
231                                                 variant="contained"
232                                                 type="submit"
233                                                 disabled={this.props.isPristine || this.props.invalid || this.props.submitting}>
234                                                 Save changes
235                                             </Button>
236                                         </Grid>
237                                     </Grid>
238                                 </Grid>
239                             </form >
240                         </CardContent>
241                     // </Card>
242                 }
243                 {this.state.value === 1 &&
244                     <div className={this.props.classes.content}>
245                         <DataExplorer
246                                 id={USER_PROFILE_PANEL_ID}
247                                 onRowClick={noop}
248                                 onRowDoubleClick={noop}
249                                 contextMenuColumn={false}
250                                 hideColumnSelector
251                                 hideSearchInput
252                                 paperProps={{
253                                     elevation: 0,
254                                 }}
255                                 dataTableDefaultView={
256                                     <DataTableDefaultView
257                                         icon={GroupsIcon}
258                                         messages={['Group list is empty.']} />
259                                 } />
260                     </div>}
261                 {this.state.value === 2 &&
262                     <Paper elevation={0} className={this.props.classes.adminRoot}>
263                         <Card elevation={0}>
264                             <CardContent>
265                                 <Grid container
266                                     direction="row"
267                                     justify={'flex-end'}
268                                     alignItems={'center'}>
269                                     <Grid item xs>
270                                         <Typography variant="h6" className={this.props.classes.title}>
271                                             Setup Account
272                                         </Typography>
273                                         <Typography variant="body1" className={this.props.classes.description}>
274                                             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.
275                                         </Typography>
276                                     </Grid>
277                                     <Grid item sm={'auto'} xs={12}>
278                                         <Button variant="contained"
279                                             color="primary"
280                                             onClick={() => {this.props.openSetupShellAccount(this.props.initialValues.uuid)}}
281                                             disabled={false}>
282                                             Setup Account
283                                         </Button>
284                                     </Grid>
285                                 </Grid>
286                             </CardContent>
287                         </Card>
288                         <Card elevation={0}>
289                             <CardContent>
290                                 <Grid container
291                                     direction="row"
292                                     justify={'flex-end'}
293                                     alignItems={'center'}>
294                                     <Grid item xs>
295                                         <Typography variant="h6" className={this.props.classes.title}>
296                                             Deactivate
297                                         </Typography>
298                                         <Typography variant="body1" className={this.props.classes.description}>
299                                             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.
300                                         </Typography>
301                                     </Grid>
302                                     <Grid item sm={'auto'} xs={12}>
303                                         <Button variant="contained"
304                                             color="primary"
305                                             onClick={() => {this.props.openDeactivateDialog(this.props.initialValues.uuid)}}
306                                             disabled={false}>
307                                             Deactivate
308                                         </Button>
309                                     </Grid>
310                                 </Grid>
311                             </CardContent>
312                         </Card>
313                         <Card elevation={0}>
314                             <CardContent>
315                                 <Grid container
316                                     direction="row"
317                                     justify={'flex-end'}
318                                     alignItems={'center'}>
319                                     <Grid item xs>
320                                         <Typography variant="h6" className={this.props.classes.title}>
321                                             Log In
322                                         </Typography>
323                                         <Typography variant="body1" className={this.props.classes.description}>
324                                             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.
325                                         </Typography>
326                                     </Grid>
327                                     <Grid item sm={'auto'} xs={12}>
328                                         <Button variant="contained"
329                                             color="primary"
330                                             onClick={() => {this.props.loginAs(this.props.initialValues.uuid)}}
331                                             disabled={false}>
332                                             Log In
333                                         </Button>
334                                     </Grid>
335                                 </Grid>
336                             </CardContent>
337                         </Card>
338                     </Paper>}
339             </Paper >;
340         }
341
342         handleChange = (event: React.MouseEvent<HTMLElement>, value: number) => {
343             this.setState({ value });
344         }
345
346     }
347 );