1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
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";
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';
34 type CssRules = 'root' | 'adminRoot' | 'gridItem' | 'label' | 'readOnlyValue' | 'title' | 'description' | 'actions' | 'content';
36 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
42 // ...theme.mixins.gutters()
50 color: theme.palette.grey['600']
59 color: theme.palette.grey["600"]
63 justifyContent: 'flex-end'
66 // reserve space for the tab bar
67 height: `calc(100% - ${theme.spacing.unit * 7}px)`,
71 export interface UserProfilePanelRootActionProps {
72 openSetupShellAccount: (uuid: string) => void;
73 loginAs: (uuid: string) => void;
74 openDeactivateDialog: (uuid: string) => void;
77 export interface UserProfilePanelRootDataProps {
87 { key: '', value: '' },
88 { key: 'Bio-informatician', value: 'Bio-informatician' },
89 { key: 'Data Scientist', value: 'Data Scientist' },
90 { key: 'Analyst', value: 'Analyst' },
91 { key: 'Researcher', value: 'Researcher' },
92 { key: 'Software Developer', value: 'Software Developer' },
93 { key: 'System Administrator', value: 'System Administrator' },
94 { key: 'Other', value: 'Other' }
97 type UserProfilePanelRootProps = InjectedFormProps<{}> & UserProfilePanelRootActionProps & UserProfilePanelRootDataProps & WithStyles<CssRules>;
99 export enum UserProfileGroupsColumnNames {
101 PERMISSION = "Permission",
102 VISIBLE = "Visible to other members",
114 export const userProfileGroupsColumns: DataColumns<string> = [
116 name: UserProfileGroupsColumnNames.NAME,
119 filters: createTree(),
120 render: uuid => <ResourceLinkHead uuid={uuid} />
123 name: UserProfileGroupsColumnNames.PERMISSION,
126 filters: createTree(),
127 render: uuid => <ResourceLinkHeadPermissionLevel uuid={uuid} />
130 name: UserProfileGroupsColumnNames.VISIBLE,
133 filters: createTree(),
134 render: uuid => <ResourceLinkTailIsVisible uuid={uuid} />
137 name: UserProfileGroupsColumnNames.UUID,
140 filters: createTree(),
141 render: uuid => <ResourceLinkHeadUuid uuid={uuid} />
144 name: UserProfileGroupsColumnNames.REMOVE,
147 filters: createTree(),
148 render: uuid => <ResourceLinkDelete uuid={uuid} />
152 const ReadOnlyField = withStyles(styles)(
153 (props: ({ label: string, input: {value: string} }) & WithStyles<CssRules> ) => (
154 <Grid item xs={12} data-cy="field">
155 <Typography className={props.classes.label}>
158 <Typography className={props.classes.readOnlyValue} data-cy="value">
165 export const UserProfilePanelRoot = withStyles(styles)(
166 class extends React.Component<UserProfilePanelRootProps> {
171 componentDidMount() {
172 this.setState({ value: TABS.PROFILE});
176 return <Paper className={this.props.classes.root}>
177 <Tabs value={this.state.value} onChange={this.handleChange} variant={"fullWidth"}>
178 <Tab label={TABS.PROFILE} value={TABS.PROFILE} />
179 <Tab label={TABS.GROUPS} value={TABS.GROUPS} />
180 {this.props.isAdmin && <Tab label={TABS.ADMIN} value={TABS.ADMIN} />}
182 {this.state.value === TABS.PROFILE &&
184 <form onSubmit={this.props.handleSubmit} data-cy="profile-form">
185 <Grid container spacing={24}>
186 <Grid item className={this.props.classes.gridItem} sm={6} xs={12} data-cy="firstName">
190 component={ReadOnlyField as any}
194 <Grid item className={this.props.classes.gridItem} sm={6} xs={12} data-cy="lastName">
198 component={ReadOnlyField as any}
202 <Grid item className={this.props.classes.gridItem} sm={6} xs={12} data-cy="email">
206 component={ReadOnlyField as any}
210 <Grid item className={this.props.classes.gridItem} sm={6} xs={12} data-cy="username">
214 component={ReadOnlyField as any}
218 <Grid item className={this.props.classes.gridItem} sm={6} xs={12}>
221 name="prefs.profile.organization"
222 component={TextField as any}
223 disabled={!this.props.isAdmin && !this.props.isSelf}
226 <Grid item className={this.props.classes.gridItem} sm={6} xs={12}>
228 label="E-mail at Organization"
229 name="prefs.profile.organization_email"
230 component={TextField as any}
231 disabled={!this.props.isAdmin && !this.props.isSelf}
234 <Grid item className={this.props.classes.gridItem} sm={6} xs={12}>
235 <InputLabel className={this.props.classes.label} htmlFor="prefs.profile.role">Role</InputLabel>
237 id="prefs.profile.role"
238 name="prefs.profile.role"
239 component={NativeSelectField as any}
241 disabled={!this.props.isAdmin && !this.props.isSelf}
244 <Grid item className={this.props.classes.gridItem} sm={6} xs={12}>
247 name="prefs.profile.website_url"
248 component={TextField as any}
249 disabled={!this.props.isAdmin && !this.props.isSelf}
253 <Grid container direction="row" justify="flex-end">
254 <Button color="primary" onClick={this.props.reset} disabled={this.props.isPristine}>Discard changes</Button>
259 disabled={this.props.isPristine || this.props.invalid || this.props.submitting}>
268 {this.state.value === TABS.GROUPS &&
269 <div className={this.props.classes.content}>
271 id={USER_PROFILE_PANEL_ID}
272 data-cy="user-profile-groups-data-explorer"
274 onRowDoubleClick={noop}
276 contextMenuColumn={false}
282 dataTableDefaultView={
283 <DataTableDefaultView
285 messages={['Group list is empty.']} />
288 {this.props.isAdmin && this.state.value === TABS.ADMIN &&
289 <Paper elevation={0} className={this.props.classes.adminRoot}>
295 alignItems={'center'}>
297 <Typography variant="h6" className={this.props.classes.title}>
300 <Typography variant="body1" className={this.props.classes.description}>
301 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.
304 <Grid item sm={'auto'} xs={12}>
305 <Button variant="contained"
307 onClick={() => {this.props.openSetupShellAccount(this.props.initialValues.uuid)}}
320 alignItems={'center'}>
322 <Typography variant="h6" className={this.props.classes.title}>
325 <Typography variant="body1" className={this.props.classes.description}>
326 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.
329 <Grid item sm={'auto'} xs={12}>
330 <Button variant="contained"
332 onClick={() => {this.props.openDeactivateDialog(this.props.initialValues.uuid)}}
345 alignItems={'center'}>
347 <Typography variant="h6" className={this.props.classes.title}>
350 <Typography variant="body1" className={this.props.classes.description}>
351 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.
354 <Grid item sm={'auto'} xs={12}>
355 <Button variant="contained"
357 onClick={() => {this.props.loginAs(this.props.initialValues.uuid)}}
369 handleChange = (event: React.MouseEvent<HTMLElement>, value: number) => {
370 this.setState({ value });