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' | 'title' | 'description' | 'actions' | 'content';
36 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
42 // ...theme.mixins.gutters()
55 color: theme.palette.grey["600"]
59 justifyContent: 'flex-end'
62 // reserve space for the tab bar
63 height: `calc(100% - ${theme.spacing.unit * 7}px)`,
67 export interface UserProfilePanelRootActionProps {
68 openSetupShellAccount: (uuid: string) => void;
69 loginAs: (uuid: string) => void;
70 openDeactivateDialog: (uuid: string) => void;
73 export interface UserProfilePanelRootDataProps {
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' }
90 type UserProfilePanelRootProps = InjectedFormProps<{}> & UserProfilePanelRootActionProps & UserProfilePanelRootDataProps & WithStyles<CssRules>;
92 // type LocalClusterProp = { localCluster: string };
93 // const renderField: React.ComponentType<WrappedFieldProps & LocalClusterProp> = ({ input, localCluster }) => (
94 // <span>{localCluster === input.value.substring(0, 5) ? "" : "federated"} user {input.value}</span>
97 export enum UserProfileGroupsColumnNames {
99 PERMISSION = "Permission",
100 VISIBLE = "Visible to other members",
105 export const userProfileGroupsColumns: DataColumns<string> = [
107 name: UserProfileGroupsColumnNames.NAME,
110 filters: createTree(),
111 render: uuid => <ResourceLinkHead uuid={uuid} />
114 name: UserProfileGroupsColumnNames.PERMISSION,
117 filters: createTree(),
118 render: uuid => <ResourceLinkHeadPermissionLevel uuid={uuid} />
121 name: UserProfileGroupsColumnNames.VISIBLE,
124 filters: createTree(),
125 render: uuid => <ResourceLinkTailIsVisible uuid={uuid} />
128 name: UserProfileGroupsColumnNames.UUID,
131 filters: createTree(),
132 render: uuid => <ResourceLinkHeadUuid uuid={uuid} />
135 name: UserProfileGroupsColumnNames.REMOVE,
138 filters: createTree(),
139 render: uuid => <ResourceLinkDelete uuid={uuid} />
143 export const UserProfilePanelRoot = withStyles(styles)(
144 class extends React.Component<UserProfilePanelRootProps> {
149 componentDidMount() {
150 this.setState({ value: 0 });
154 return <Paper className={this.props.classes.root}>
155 {/* <Typography variant="title" className={this.props.classes.title}>
156 Logged in as <Field name="uuid" component={renderField} localCluster={this.props.localCluster} />
158 <Tabs value={this.state.value} onChange={this.handleChange} fullWidth>
159 <Tab label="PROFILE" />
160 <Tab label="GROUPS" />
161 <Tab label="ADMIN" />
163 {this.state.value === 0 &&
164 // <Card className={this.props.classes.root}>
166 <form onSubmit={this.props.handleSubmit}>
167 <Grid container spacing={24}>
168 <Grid item className={this.props.classes.gridItem} sm={6} xs={12}>
172 component={TextField as any}
176 <Grid item className={this.props.classes.gridItem} sm={6} xs={12}>
180 component={TextField as any}
184 <Grid item className={this.props.classes.gridItem} sm={6} xs={12}>
188 component={TextField as any}
192 <Grid item className={this.props.classes.gridItem} sm={6} xs={12}>
196 component={TextField as any}
200 <Grid item className={this.props.classes.gridItem} sm={6} xs={12}>
203 name="prefs.profile.organization"
204 component={TextField as any}
205 validate={MY_ACCOUNT_VALIDATION}
209 <Grid item className={this.props.classes.gridItem} sm={6} xs={12}>
211 label="E-mail at Organization"
212 name="prefs.profile.organization_email"
213 component={TextField as any}
214 validate={MY_ACCOUNT_VALIDATION}
218 <Grid item className={this.props.classes.gridItem} sm={6} xs={12}>
219 <InputLabel className={this.props.classes.label} htmlFor="prefs.profile.role">Role</InputLabel>
221 id="prefs.profile.role"
222 name="prefs.profile.role"
223 component={NativeSelectField as any}
227 <Grid item className={this.props.classes.gridItem} sm={6} xs={12}>
230 name="prefs.profile.website_url"
231 component={TextField as any}
235 <Grid container direction="row" justify="flex-end">
236 <Button color="primary" onClick={this.props.reset} disabled={this.props.isPristine}>Discard changes</Button>
241 disabled={this.props.isPristine || this.props.invalid || this.props.submitting}>
251 {this.state.value === 1 &&
252 <div className={this.props.classes.content}>
254 id={USER_PROFILE_PANEL_ID}
256 onRowDoubleClick={noop}
257 // onContextMenu={this.handleContextMenu}
258 contextMenuColumn={false}
264 dataTableDefaultView={
265 <DataTableDefaultView
267 messages={['Group list is empty.']} />
270 {this.state.value === 2 &&
271 <Paper elevation={0} className={this.props.classes.adminRoot}>
277 alignItems={'center'}>
279 <Typography variant="h6" className={this.props.classes.title}>
282 <Typography variant="body1" className={this.props.classes.description}>
283 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.
286 <Grid item sm={'auto'} xs={12}>
287 <Button variant="contained"
289 onClick={() => {this.props.openSetupShellAccount(this.props.initialValues.uuid)}}
302 alignItems={'center'}>
304 <Typography variant="h6" className={this.props.classes.title}>
307 <Typography variant="body1" className={this.props.classes.description}>
308 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.
311 <Grid item sm={'auto'} xs={12}>
312 <Button variant="contained"
314 onClick={() => {this.props.openDeactivateDialog(this.props.initialValues.uuid)}}
327 alignItems={'center'}>
329 <Typography variant="h6" className={this.props.classes.title}>
332 <Typography variant="body1" className={this.props.classes.description}>
333 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.
336 <Grid item sm={'auto'} xs={12}>
337 <Button variant="contained"
339 onClick={() => {this.props.loginAs(this.props.initialValues.uuid)}}
351 handleChange = (event: React.MouseEvent<HTMLElement>, value: number) => {
352 this.setState({ value });
355 handleContextMenu = (event: React.MouseEvent<HTMLElement>, resourceUuid: string) => {
356 // const resource = getResource<UserResource>(resourceUuid)(this.props.resources);
358 // this.props.onContextMenu(event, {
360 // uuid: resource.uuid,
361 // ownerUuid: resource.ownerUuid,
362 // kind: resource.kind,
363 // menuKind: ContextMenuKind.USER