d42fb6e197dcd271be3b0dd8f4cb5aaee534d007
[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 enum TABS {
107     PROFILE = "PROFILE",
108     GROUPS = "GROUPS",
109     ADMIN = "ADMIN",
110
111 }
112
113 export const userProfileGroupsColumns: DataColumns<string> = [
114     {
115         name: UserProfileGroupsColumnNames.NAME,
116         selected: true,
117         configurable: true,
118         filters: createTree(),
119         render: uuid => <ResourceLinkHead uuid={uuid} />
120     },
121     {
122         name: UserProfileGroupsColumnNames.PERMISSION,
123         selected: true,
124         configurable: true,
125         filters: createTree(),
126         render: uuid => <ResourceLinkHeadPermissionLevel uuid={uuid} />
127     },
128     {
129         name: UserProfileGroupsColumnNames.VISIBLE,
130         selected: true,
131         configurable: true,
132         filters: createTree(),
133         render: uuid => <ResourceLinkTailIsVisible uuid={uuid} />
134     },
135     {
136         name: UserProfileGroupsColumnNames.UUID,
137         selected: true,
138         configurable: true,
139         filters: createTree(),
140         render: uuid => <ResourceLinkHeadUuid uuid={uuid} />
141     },
142     {
143         name: UserProfileGroupsColumnNames.REMOVE,
144         selected: true,
145         configurable: true,
146         filters: createTree(),
147         render: uuid => <ResourceLinkDelete uuid={uuid} />
148     },
149 ];
150
151 const ReadOnlyField = withStyles(styles)(
152     (props: ({ label: string, input: {value: string} }) & WithStyles<CssRules> ) => (
153         <Grid item xs={12}>
154             <Typography className={props.classes.label}>
155                 {props.label}
156             </Typography>
157             <Typography className={props.classes.readOnlyValue}>
158                 {props.input.value}
159             </Typography>
160         </Grid>
161     )
162 );
163
164 export const UserProfilePanelRoot = withStyles(styles)(
165     class extends React.Component<UserProfilePanelRootProps> {
166         state = {
167             value: TABS.PROFILE,
168         };
169
170         componentDidMount() {
171             this.setState({ value: TABS.PROFILE});
172         }
173
174         render() {
175             return <Paper className={this.props.classes.root}>
176                 <Tabs value={this.state.value} onChange={this.handleChange} variant={"fullWidth"}>
177                     <Tab label={TABS.PROFILE} value={TABS.PROFILE} />
178                     <Tab label={TABS.GROUPS} value={TABS.GROUPS} />
179                     {this.props.isAdmin && <Tab label={TABS.ADMIN} value={TABS.ADMIN} />}
180                 </Tabs>
181                 {this.state.value === TABS.PROFILE &&
182                     <CardContent>
183                         <form onSubmit={this.props.handleSubmit}>
184                             <Grid container spacing={24}>
185                                 <Grid item className={this.props.classes.gridItem} sm={6} xs={12}>
186                                     <Field
187                                         label="First name"
188                                         name="firstName"
189                                         component={ReadOnlyField as any}
190                                         disabled
191                                     />
192                                 </Grid>
193                                 <Grid item className={this.props.classes.gridItem} sm={6} xs={12}>
194                                     <Field
195                                         label="Last name"
196                                         name="lastName"
197                                         component={ReadOnlyField as any}
198                                         disabled
199                                     />
200                                 </Grid>
201                                 <Grid item className={this.props.classes.gridItem} sm={6} xs={12}>
202                                     <Field
203                                         label="E-mail"
204                                         name="email"
205                                         component={ReadOnlyField as any}
206                                         disabled
207                                     />
208                                 </Grid>
209                                 <Grid item className={this.props.classes.gridItem} sm={6} xs={12}>
210                                     <Field
211                                         label="Username"
212                                         name="username"
213                                         component={ReadOnlyField as any}
214                                         disabled
215                                     />
216                                 </Grid>
217                                 <Grid item className={this.props.classes.gridItem} sm={6} xs={12}>
218                                     <Field
219                                         label="Organization"
220                                         name="prefs.profile.organization"
221                                         component={TextField as any}
222                                         validate={MY_ACCOUNT_VALIDATION}
223                                         disabled={!this.props.isAdmin && !this.props.isSelf}
224                                     />
225                                 </Grid>
226                                 <Grid item className={this.props.classes.gridItem} sm={6} xs={12}>
227                                     <Field
228                                         label="E-mail at Organization"
229                                         name="prefs.profile.organization_email"
230                                         component={TextField as any}
231                                         validate={MY_ACCOUNT_VALIDATION}
232                                         disabled={!this.props.isAdmin && !this.props.isSelf}
233                                     />
234                                 </Grid>
235                                 <Grid item className={this.props.classes.gridItem} sm={6} xs={12}>
236                                     <InputLabel className={this.props.classes.label} htmlFor="prefs.profile.role">Role</InputLabel>
237                                     <Field
238                                         id="prefs.profile.role"
239                                         name="prefs.profile.role"
240                                         component={NativeSelectField as any}
241                                         items={RoleTypes}
242                                         disabled={!this.props.isAdmin && !this.props.isSelf}
243                                     />
244                                 </Grid>
245                                 <Grid item className={this.props.classes.gridItem} sm={6} xs={12}>
246                                     <Field
247                                         label="Website"
248                                         name="prefs.profile.website_url"
249                                         component={TextField as any}
250                                         disabled={!this.props.isAdmin && !this.props.isSelf}
251                                     />
252                                 </Grid>
253                                 <Grid item sm={12}>
254                                     <Grid container direction="row" justify="flex-end">
255                                         <Button color="primary" onClick={this.props.reset} disabled={this.props.isPristine}>Discard changes</Button>
256                                         <Button
257                                             color="primary"
258                                             variant="contained"
259                                             type="submit"
260                                             disabled={this.props.isPristine || this.props.invalid || this.props.submitting}>
261                                             Save changes
262                                         </Button>
263                                     </Grid>
264                                 </Grid>
265                             </Grid>
266                         </form >
267                     </CardContent>
268                 }
269                 {this.state.value === TABS.GROUPS &&
270                     <div className={this.props.classes.content}>
271                         <DataExplorer
272                                 id={USER_PROFILE_PANEL_ID}
273                                 onRowClick={noop}
274                                 onRowDoubleClick={noop}
275                                 contextMenuColumn={false}
276                                 hideColumnSelector
277                                 hideSearchInput
278                                 paperProps={{
279                                     elevation: 0,
280                                 }}
281                                 dataTableDefaultView={
282                                     <DataTableDefaultView
283                                         icon={GroupsIcon}
284                                         messages={['Group list is empty.']} />
285                                 } />
286                     </div>}
287                 {this.props.isAdmin && this.state.value === TABS.ADMIN &&
288                     <Paper elevation={0} className={this.props.classes.adminRoot}>
289                         <Card elevation={0}>
290                             <CardContent>
291                                 <Grid container
292                                     direction="row"
293                                     justify={'flex-end'}
294                                     alignItems={'center'}>
295                                     <Grid item xs>
296                                         <Typography variant="h6" className={this.props.classes.title}>
297                                             Setup Account
298                                         </Typography>
299                                         <Typography variant="body1" className={this.props.classes.description}>
300                                             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.
301                                         </Typography>
302                                     </Grid>
303                                     <Grid item sm={'auto'} xs={12}>
304                                         <Button variant="contained"
305                                             color="primary"
306                                             onClick={() => {this.props.openSetupShellAccount(this.props.initialValues.uuid)}}
307                                             disabled={false}>
308                                             Setup Account
309                                         </Button>
310                                     </Grid>
311                                 </Grid>
312                             </CardContent>
313                         </Card>
314                         <Card elevation={0}>
315                             <CardContent>
316                                 <Grid container
317                                     direction="row"
318                                     justify={'flex-end'}
319                                     alignItems={'center'}>
320                                     <Grid item xs>
321                                         <Typography variant="h6" className={this.props.classes.title}>
322                                             Deactivate
323                                         </Typography>
324                                         <Typography variant="body1" className={this.props.classes.description}>
325                                             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.
326                                         </Typography>
327                                     </Grid>
328                                     <Grid item sm={'auto'} xs={12}>
329                                         <Button variant="contained"
330                                             color="primary"
331                                             onClick={() => {this.props.openDeactivateDialog(this.props.initialValues.uuid)}}
332                                             disabled={false}>
333                                             Deactivate
334                                         </Button>
335                                     </Grid>
336                                 </Grid>
337                             </CardContent>
338                         </Card>
339                         <Card elevation={0}>
340                             <CardContent>
341                                 <Grid container
342                                     direction="row"
343                                     justify={'flex-end'}
344                                     alignItems={'center'}>
345                                     <Grid item xs>
346                                         <Typography variant="h6" className={this.props.classes.title}>
347                                             Log In
348                                         </Typography>
349                                         <Typography variant="body1" className={this.props.classes.description}>
350                                             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.
351                                         </Typography>
352                                     </Grid>
353                                     <Grid item sm={'auto'} xs={12}>
354                                         <Button variant="contained"
355                                             color="primary"
356                                             onClick={() => {this.props.loginAs(this.props.initialValues.uuid)}}
357                                             disabled={false}>
358                                             Log In
359                                         </Button>
360                                     </Grid>
361                                 </Grid>
362                             </CardContent>
363                         </Card>
364                     </Paper>}
365             </Paper >;
366         }
367
368         handleChange = (event: React.MouseEvent<HTMLElement>, value: number) => {
369             this.setState({ value });
370         }
371
372     }
373 );