const apiClientAuthorizationsMatch = Routes.matchApiClientAuthorizationsRoute(pathname);
const myAccountMatch = Routes.matchMyAccountRoute(pathname);
const linkAccountMatch = Routes.matchLinkAccountRoute(pathname);
- const userMatch = Routes.matchUsersRoute(pathname);
+ const usersMatch = Routes.matchUsersRoute(pathname);
+ const userProfileMatch = Routes.matchUserProfileRoute(pathname);
const groupsMatch = Routes.matchGroupsRoute(pathname);
const groupDetailsMatch = Routes.matchGroupDetailsRoute(pathname);
const linksMatch = Routes.matchLinksRoute(pathname);
} else if (apiClientAuthorizationsMatch) {
store.dispatch(WorkbenchActions.loadApiClientAuthorizations);
} else if (myAccountMatch) {
- store.dispatch(WorkbenchActions.loadMyAccount);
+ store.dispatch(WorkbenchActions.loadUserProfile());
} else if (linkAccountMatch) {
store.dispatch(WorkbenchActions.loadLinkAccount);
- } else if (userMatch) {
+ } else if (usersMatch) {
store.dispatch(WorkbenchActions.loadUsers);
+ } else if (userProfileMatch) {
+ store.dispatch(WorkbenchActions.loadUserProfile(userProfileMatch.params.id));
} else if (groupsMatch) {
store.dispatch(WorkbenchActions.loadGroupsPanel);
} else if (groupDetailsMatch) {
LINK_ACCOUNT: '/link_account',
KEEP_SERVICES: `/keep-services`,
USERS: '/users',
+ USER_PROFILE: `/user/:id(${RESOURCE_UUID_PATTERN})`,
API_CLIENT_AUTHORIZATIONS: `/api_client_authorizations`,
GROUPS: '/groups',
GROUP_DETAILS: `/group/:id(${RESOURCE_UUID_PATTERN})`,
export const matchUsersRoute = (route: string) =>
matchPath(route, { path: Routes.USERS });
+export const matchUserProfileRoute = (route: string) =>
+ matchPath<ResourceRouteParams>(route, { path: Routes.USER_PROFILE });
+
export const matchApiClientAuthorizationsRoute = (route: string) =>
matchPath(route, { path: Routes.API_CLIENT_AUTHORIZATIONS });
import { ResourceKind } from 'models/resource';
import { GroupResource } from 'models/group';
import { extractUuidKind } from 'models/resource';
+import { UserResource } from 'models/user';
export const BREADCRUMBS = 'breadcrumbs';
}
};
-export const GROUPS_PANEL_LABEL = 'Groups';
-
export const setGroupsBreadcrumbs = () =>
- setBreadcrumbs([{ label: GROUPS_PANEL_LABEL }]);
+ setBreadcrumbs([{ label: SidePanelTreeCategory.GROUPS }]);
export const setGroupDetailsBreadcrumbs = (groupUuid: string) =>
- (dispatch: Dispatch, getState: () => RootState) => {
+ async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
const group = getResource<GroupResource>(groupUuid)(getState().resources);
const breadcrumbs: ResourceBreadcrumb[] = [
- { label: GROUPS_PANEL_LABEL, uuid: GROUPS_PANEL_LABEL },
- { label: group ? group.name : groupUuid, uuid: groupUuid },
+ { label: SidePanelTreeCategory.GROUPS, uuid: SidePanelTreeCategory.GROUPS },
+ { label: group ? group.name : (await services.groupsService.get(groupUuid)).name, uuid: groupUuid },
];
dispatch(setBreadcrumbs(breadcrumbs));
};
+
+export const USERS_PANEL_LABEL = 'Users';
+
+export const setUsersBreadcrumbs = () =>
+ setBreadcrumbs([{ label: USERS_PANEL_LABEL, uuid: USERS_PANEL_LABEL }]);
+
+export const setUserProfileBreadcrumbs = (userUuid: string) =>
+ async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+
+ const user = getResource<UserResource>(userUuid)(getState().resources);
+
+ const breadcrumbs: ResourceBreadcrumb[] = [
+ { label: USERS_PANEL_LABEL, uuid: USERS_PANEL_LABEL },
+ { label: user ? user.username : (await services.userService.get(userUuid)).username, uuid: userUuid },
+ ];
+
+ dispatch(setBreadcrumbs(breadcrumbs));
+
+ };
+
+export const MY_ACCOUNT_PANEL_LABEL = 'My Account';
+
+export const setMyAccountBreadcrumbs = () =>
+ async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ dispatch(setBreadcrumbs([
+ { label: MY_ACCOUNT_PANEL_LABEL, uuid: MY_ACCOUNT_PANEL_LABEL },
+ ]));
+ };
import { LinkResource } from 'models/link';
import { deleteResources, updateResources } from 'store/resources/resources-actions';
import { openSharingDialog } from 'store/sharing-dialog/sharing-dialog-actions';
-// import { UserProfileGroupsActions } from 'store/user-profile/user-profile-actions';
+import { UserProfileGroupsActions } from 'store/user-profile/user-profile-actions';
export const GROUP_DETAILS_MEMBERS_PANEL_ID = 'groupDetailsMembersPanel';
export const GROUP_DETAILS_PERMISSIONS_PANEL_ID = 'groupDetailsPermissionsPanel';
});
dispatch<any>(deleteResources([uuid]));
dispatch(GroupMembersPanelActions.REQUEST_ITEMS());
- // dispatch(UserProfileGroupsActions.REQUEST_ITEMS());
+ dispatch(UserProfileGroupsActions.REQUEST_ITEMS());
dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Removed.', hideDuration: 2000, kind: SnackbarKind.SUCCESS }));
};
import { ServiceRepository } from 'services/services';
import { pluginConfig } from 'plugins';
import { snackbarActions, SnackbarKind } from 'store/snackbar/snackbar-actions';
+import { USERS_PANEL_LABEL, MY_ACCOUNT_PANEL_LABEL } from 'store/breadcrumbs/breadcrumbs-actions';
const navigationNotAvailable = (id: string) =>
snackbarActions.OPEN_SNACKBAR({
case SidePanelTreeCategory.ALL_PROCESSES:
dispatch(navigateToAllProcesses);
return;
+ case USERS_PANEL_LABEL:
+ dispatch(navigateToUsers);
+ return;
+ case MY_ACCOUNT_PANEL_LABEL:
+ dispatch(navigateToMyAccount);
+ return;
}
dispatch(navigationNotAvailable(uuid));
import { keepServicesReducer } from 'store/keep-services/keep-services-reducer';
import { UserMiddlewareService } from 'store/users/user-panel-middleware-service';
import { USERS_PANEL_ID } from 'store/users/users-actions';
+import { UserProfileGroupsMiddlewareService } from 'store/user-profile/user-profile-groups-middleware-service';
+import { USER_PROFILE_PANEL_ID } from 'store/user-profile/user-profile-actions'
import { GroupsPanelMiddlewareService } from 'store/groups-panel/groups-panel-middleware-service';
import { GROUPS_PANEL_ID } from 'store/groups-panel/groups-panel-actions';
import { GroupDetailsPanelMembersMiddlewareService } from 'store/group-details-panel/group-details-panel-members-middleware-service';
const userPanelMiddleware = dataExplorerMiddleware(
new UserMiddlewareService(services, USERS_PANEL_ID)
);
+ const userProfileGroupsMiddleware = dataExplorerMiddleware(
+ new UserProfileGroupsMiddlewareService(services, USER_PROFILE_PANEL_ID)
+ );
const groupsPanelMiddleware = dataExplorerMiddleware(
new GroupsPanelMiddlewareService(services, GROUPS_PANEL_ID)
);
sharedWithMePanelMiddleware,
workflowPanelMiddleware,
userPanelMiddleware,
+ userProfileGroupsMiddleware,
groupsPanelMiddleware,
groupDetailsPanelMembersMiddleware,
groupDetailsPanelPermissionsMiddleware,
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+import { RootState } from "store/store";
+import { Dispatch } from 'redux';
+import { reset } from "redux-form";
+import { ServiceRepository } from "services/services";
+import { bindDataExplorerActions } from "store/data-explorer/data-explorer-action";
+import { propertiesActions } from 'store/properties/properties-actions';
+import { getProperty } from 'store/properties/properties';
+import { snackbarActions, SnackbarKind } from "store/snackbar/snackbar-actions";
+import { updateResources } from "store/resources/resources-actions";
+import { dialogActions } from "store/dialog/dialog-actions";
+
+export const USER_PROFILE_PANEL_ID = 'userProfilePanel';
+export const USER_PROFILE_FORM = 'userProfileForm';
+export const DEACTIVATE_DIALOG = 'deactivateDialog';
+
+export const UserProfileGroupsActions = bindDataExplorerActions(USER_PROFILE_PANEL_ID);
+
+export const getCurrentUserProfilePanelUuid = getProperty<string>(USER_PROFILE_PANEL_ID);
+
+export const loadUserProfilePanel = (userUuid?: string) =>
+ async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ // Get user uuid from route or use current user uuid
+ const uuid = userUuid || getState().auth.user?.uuid;
+ if (uuid) {
+ const user = await services.userService.get(uuid);
+ dispatch(updateResources([user]));
+ await dispatch(propertiesActions.SET_PROPERTY({ key: USER_PROFILE_PANEL_ID, value: uuid }));
+ dispatch(UserProfileGroupsActions.REQUEST_ITEMS());
+ }
+ }
+
+export const saveEditedUser = (resource: any) =>
+ async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
+ try {
+ const user = await services.userService.update(resource.uuid, resource);
+ dispatch(updateResources([user]));
+ dispatch(reset(USER_PROFILE_FORM));
+ dispatch(snackbarActions.OPEN_SNACKBAR({ message: "Profile has been updated.", hideDuration: 2000, kind: SnackbarKind.SUCCESS }));
+ } catch (e) {
+ dispatch(snackbarActions.OPEN_SNACKBAR({
+ message: "Could not update profile",
+ kind: SnackbarKind.ERROR,
+ }));
+ }
+ };
+
+export const openDeactivateDialog = (uuid: string) =>
+ (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
+ dispatch(dialogActions.OPEN_DIALOG({
+ id: DEACTIVATE_DIALOG,
+ data: {
+ title: 'Deactivate user',
+ text: 'Are you sure you want to deactivate this user?',
+ confirmButtonLabel: 'Deactvate',
+ uuid
+ }
+ }));
+}
+
+export const unsetup = (uuid: string) =>
+ async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ try {
+ const user = await services.userService.unsetup(uuid);
+ dispatch(updateResources([user]));
+ dispatch(snackbarActions.OPEN_SNACKBAR({
+ message: "User has been deactivated.",
+ hideDuration: 2000,
+ kind: SnackbarKind.SUCCESS
+ }));
+ } catch (e) {
+ dispatch(snackbarActions.OPEN_SNACKBAR({
+ message: "Could not deactivate user",
+ kind: SnackbarKind.ERROR,
+ }));
+ }
+ };
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { ServiceRepository } from 'services/services';
+import { MiddlewareAPI, Dispatch } from 'redux';
+import { DataExplorerMiddlewareService, listResultsToDataExplorerItemsMeta } from 'store/data-explorer/data-explorer-middleware-service';
+import { RootState } from 'store/store';
+import { progressIndicatorActions } from "store/progress-indicator/progress-indicator-actions";
+import { getCurrentUserProfilePanelUuid, UserProfileGroupsActions } from 'store/user-profile/user-profile-actions';
+import { updateResources } from 'store/resources/resources-actions';
+import { FilterBuilder } from 'services/api/filter-builder';
+import { LinkClass } from 'models/link';
+import { ResourceKind } from 'models/resource';
+
+export class UserProfileGroupsMiddlewareService extends DataExplorerMiddlewareService {
+ constructor(private services: ServiceRepository, id: string) {
+ super(id);
+ }
+
+ async requestItems(api: MiddlewareAPI<Dispatch, RootState>) {
+ const state = api.getState();
+ const userUuid = getCurrentUserProfilePanelUuid(state.properties);
+ try {
+ api.dispatch(progressIndicatorActions.START_WORKING(this.getId()));
+
+ // Get user
+ const user = await this.services.userService.get(userUuid || '');
+ api.dispatch(updateResources([user]));
+
+ // Get user's group memberships
+ const groupMembershipLinks = await this.services.permissionService.list({
+ filters: new FilterBuilder()
+ .addEqual('tail_uuid', userUuid)
+ .addEqual('link_class', LinkClass.PERMISSION)
+ .addEqual('head_kind', ResourceKind.GROUP)
+ .getFilters()
+ });
+ api.dispatch(updateResources(groupMembershipLinks.items));
+
+ // Get user's groups details
+ const groups = await this.services.groupsService.list({
+ filters: new FilterBuilder()
+ .addIn('uuid', groupMembershipLinks.items
+ .map(item => item.headUuid))
+ .getFilters(),
+ count: "none"
+ });
+ api.dispatch(updateResources(groups.items));
+
+ api.dispatch(UserProfileGroupsActions.SET_ITEMS({
+ ...listResultsToDataExplorerItemsMeta(groupMembershipLinks),
+ items: groupMembershipLinks.items.map(item => item.uuid),
+ }));
+ } catch {
+ // api.dispatch(couldNotFetchUsers());
+ } finally {
+ api.dispatch(progressIndicatorActions.STOP_WORKING(this.getId()));
+ }
+ }
+}
import { getUserUuid } from "common/getuser";
import { ServiceRepository } from "services/services";
import { dialogActions } from 'store/dialog/dialog-actions';
-import { startSubmit, reset } from "redux-form";
+import { startSubmit, reset, initialize, stopSubmit } from "redux-form";
import { snackbarActions, SnackbarKind } from 'store/snackbar/snackbar-actions';
import { UserResource } from "models/user";
import { getResource } from 'store/resources/resources';
import { navigateTo, navigateToUsers, navigateToRootProject } from "store/navigation/navigation-action";
import { authActions } from 'store/auth/auth-action';
import { getTokenV2 } from "models/api-client-authorization";
+import { AddLoginFormData, VIRTUAL_MACHINE_ADD_LOGIN_GROUPS_FIELD, VIRTUAL_MACHINE_ADD_LOGIN_USER_FIELD } from "store/virtual-machines/virtual-machines-actions";
+import { PermissionLevel } from "models/permission";
+import { updateResources } from "store/resources/resources-actions";
export const USERS_PANEL_ID = 'usersPanel';
export const USER_ATTRIBUTES_DIALOG = 'userAttributesDialog';
groupVirtualMachine: string;
}
-export interface SetupShellAccountFormDialogData {
- email: string;
- virtualMachineName: string;
- groupVirtualMachine: string;
-}
+export const userBindedActions = bindDataExplorerActions(USERS_PANEL_ID);
export const openUserAttributes = (uuid: string) =>
(dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
const { resources } = getState();
const user = getResource<UserResource>(uuid)(resources);
const virtualMachines = await services.virtualMachineService.list();
- dispatch(dialogActions.CLOSE_DIALOG({ id: USER_MANAGEMENT_DIALOG }));
- dispatch(dialogActions.OPEN_DIALOG({ id: SETUP_SHELL_ACCOUNT_DIALOG, data: { user, ...virtualMachines } }));
+ dispatch(initialize(SETUP_SHELL_ACCOUNT_DIALOG, {[VIRTUAL_MACHINE_ADD_LOGIN_USER_FIELD]: user, [VIRTUAL_MACHINE_ADD_LOGIN_GROUPS_FIELD]: []}));
+ dispatch(dialogActions.OPEN_DIALOG({ id: SETUP_SHELL_ACCOUNT_DIALOG, data: virtualMachines }));
};
export const loginAs = (uuid: string) =>
dispatch<any>(navigateTo(uuid));
};
-
export const createUser = (user: UserCreateFormDialogData) =>
async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
dispatch(startSubmit(USER_CREATE_FORM_NAME));
}
};
-
-export const setupUserVM = (setupData: SetupShellAccountFormDialogData) =>
+export const setupUserVM = (setupData: AddLoginFormData) =>
async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
- dispatch(startSubmit(USER_CREATE_FORM_NAME));
+ dispatch(startSubmit(SETUP_SHELL_ACCOUNT_DIALOG));
try {
- // TODO: make correct API call
- // const setupResult = await services.userService.setup({ ...setupData });
+ const userResource = await services.userService.get(setupData.user.uuid);
+
+ const resources = await services.userService.setup(setupData.user.uuid);
+ dispatch(updateResources(resources.items));
+
+ const permission = await services.permissionService.create({
+ headUuid: setupData.vmUuid,
+ tailUuid: userResource.uuid,
+ name: PermissionLevel.CAN_LOGIN,
+ properties: {
+ username: userResource.username,
+ groups: setupData.groups,
+ }
+ });
+ dispatch(updateResources([permission]));
+
dispatch(dialogActions.CLOSE_DIALOG({ id: SETUP_SHELL_ACCOUNT_DIALOG }));
dispatch(reset(SETUP_SHELL_ACCOUNT_DIALOG));
dispatch(snackbarActions.OPEN_SNACKBAR({ message: "User has been added to VM.", hideDuration: 2000, kind: SnackbarKind.SUCCESS }));
- dispatch<any>(loadUsersPanel());
- dispatch(userBindedActions.REQUEST_ITEMS());
} catch (e) {
- return;
+ dispatch(stopSubmit(SETUP_SHELL_ACCOUNT_DIALOG));
+ dispatch(snackbarActions.OPEN_SNACKBAR({ message: e.message, hideDuration: 2000, kind: SnackbarKind.ERROR }));
}
};
return newActivity;
};
-export const userBindedActions = bindDataExplorerActions(USERS_PANEL_ID);
-
-export const loadUsersData = () =>
- async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
- await services.userService.list({ count: "none" });
- };
-
export const loadUsersPanel = () =>
(dispatch: Dispatch) => {
dispatch(userBindedActions.REQUEST_ITEMS());
dispatch(updateResources([permission]));
} else {
const permission = await services.permissionService.create({
- headUuid: vmUuid,
+ headUuid: vmUuid,
tailUuid: userResource.uuid,
name: PermissionLevel.CAN_LOGIN,
properties: {
setProcessBreadcrumbs,
setSharedWithMeBreadcrumbs,
setSidePanelBreadcrumbs,
- setTrashBreadcrumbs
+ setTrashBreadcrumbs,
+ setUsersBreadcrumbs,
+ setMyAccountBreadcrumbs,
+ setUserProfileBreadcrumbs,
} from 'store/breadcrumbs/breadcrumbs-actions';
import { navigateTo, navigateToRootProject } from 'store/navigation/navigation-action';
import { MoveToFormDialogData } from 'store/move-to-dialog/move-to-dialog';
import { CopyFormDialogData } from 'store/copy-dialog/copy-dialog';
import { loadWorkflowPanel, workflowPanelActions } from 'store/workflow-panel/workflow-panel-actions';
import { loadSshKeysPanel } from 'store/auth/auth-action-ssh';
-import { loadMyAccountPanel } from 'store/my-account/my-account-panel-actions';
import { loadLinkAccountPanel, linkAccountPanelActions } from 'store/link-account-panel/link-account-panel-actions';
import { loadSiteManagerPanel } from 'store/auth/auth-action-session';
import { workflowPanelColumns } from 'views/workflow-panel/workflow-panel-view';
import { loadRepositoriesPanel } from 'store/repositories/repositories-actions';
import { loadKeepServicesPanel } from 'store/keep-services/keep-services-actions';
import { loadUsersPanel, userBindedActions } from 'store/users/users-actions';
+import * as userProfilePanelActions from 'store/user-profile/user-profile-actions';
import { linkPanelActions, loadLinkPanel } from 'store/link-panel/link-panel-actions';
import { linkPanelColumns } from 'views/link-panel/link-panel-root';
import { userPanelColumns } from 'views/user-panel/user-panel';
import { collectionPanelFilesAction } from '../collection-panel/collection-panel-files/collection-panel-files-actions';
import { createTree } from 'models/tree';
import { AdminMenuIcon } from 'components/icon/icon';
+import { userProfileGroupsColumns } from 'views/user-profile-panel/user-profile-panel-root';
export const WORKBENCH_LOADING_SCREEN = 'workbenchLoadingScreen';
dispatch(groupPanelActions.GroupsPanelActions.SET_COLUMNS({ columns: groupsPanelColumns }));
dispatch(groupDetailsPanelActions.GroupMembersPanelActions.SET_COLUMNS({ columns: groupDetailsMembersPanelColumns }));
dispatch(groupDetailsPanelActions.GroupPermissionsPanelActions.SET_COLUMNS({ columns: groupDetailsPermissionsPanelColumns }));
+ dispatch(userProfilePanelActions.UserProfileGroupsActions.SET_COLUMNS({ columns: userProfileGroupsColumns }));
dispatch(linkPanelActions.SET_COLUMNS({ columns: linkPanelColumns }));
dispatch(apiClientAuthorizationsActions.SET_COLUMNS({ columns: apiClientAuthorizationPanelColumns }));
dispatch(collectionsContentAddressActions.SET_COLUMNS({ columns: collectionContentAddressPanelColumns }));
await dispatch(loadSiteManagerPanel());
});
-export const loadMyAccount = handleFirstTimeLoad(
- (dispatch: Dispatch<any>) => {
- dispatch(loadMyAccountPanel());
- });
+export const loadUserProfile = (userUuid?: string) =>
+ handleFirstTimeLoad(
+ (dispatch: Dispatch<any>) => {
+ if (userUuid) {
+ dispatch(setUserProfileBreadcrumbs(userUuid));
+ dispatch(userProfilePanelActions.loadUserProfilePanel(userUuid));
+ } else {
+ dispatch(setMyAccountBreadcrumbs());
+ dispatch(userProfilePanelActions.loadUserProfilePanel());
+ }
+ }
+ );
export const loadLinkAccount = handleFirstTimeLoad(
(dispatch: Dispatch<any>) => {
export const loadUsers = handleFirstTimeLoad(
async (dispatch: Dispatch<any>) => {
await dispatch(loadUsersPanel());
- dispatch(setBreadcrumbs([{ label: 'Users' }]));
+ dispatch(setUsersBreadcrumbs());
});
export const loadApiClientAuthorizations = handleFirstTimeLoad(
import { FormDialog } from 'components/form-dialog/form-dialog';
import { TextField } from 'components/text-field/text-field';
import { VirtualMachinesResource } from 'models/virtual-machines';
-import { USER_LENGTH_VALIDATION, CHOOSE_VM_VALIDATION } from 'validators/validators';
+import { CHOOSE_VM_VALIDATION } from 'validators/validators';
import { InputLabel } from '@material-ui/core';
import { NativeSelectField } from 'components/select-field/select-field';
-import { SetupShellAccountFormDialogData, SETUP_SHELL_ACCOUNT_DIALOG, setupUserVM } from 'store/users/users-actions';
+import { SETUP_SHELL_ACCOUNT_DIALOG, setupUserVM } from 'store/users/users-actions';
import { UserResource } from 'models/user';
+import { VIRTUAL_MACHINE_ADD_LOGIN_USER_FIELD, VIRTUAL_MACHINE_ADD_LOGIN_VM_FIELD, VIRTUAL_MACHINE_ADD_LOGIN_GROUPS_FIELD, AddLoginFormData } from 'store/virtual-machines/virtual-machines-actions';
+import { GroupArrayInput } from 'views-components/virtual-machines-dialog/group-array-input';
export const SetupShellAccountDialog = compose(
withDialog(SETUP_SHELL_ACCOUNT_DIALOG),
- reduxForm<SetupShellAccountFormDialogData>({
+ reduxForm<AddLoginFormData>({
form: SETUP_SHELL_ACCOUNT_DIALOG,
onSubmit: (data, dispatch) => {
dispatch(setupUserVM(data));
/>
);
-interface UserProps {
- data: {
- user: UserResource;
- };
-}
-
interface VirtualMachinesProps {
data: {
items: VirtualMachinesResource[];
items: VirtualMachinesResource[];
}
-const UserEmailField = ({ data }: UserProps) =>
+const UserNameField = () =>
<span>
+ <InputLabel>VM Login</InputLabel>
<Field
- name='email'
+ name={`${VIRTUAL_MACHINE_ADD_LOGIN_USER_FIELD}.username`}
component={TextField as any}
- disabled
- label={data.user.email} /></span>;
+ disabled /></span>;
const UserVirtualMachineField = ({ data }: VirtualMachinesProps) =>
<div style={{ marginBottom: '21px' }}>
<InputLabel>Virtual Machine</InputLabel>
<Field
- name='virtualMachine'
+ name={VIRTUAL_MACHINE_ADD_LOGIN_VM_FIELD}
component={NativeSelectField as any}
validate={CHOOSE_VM_VALIDATION}
items={getVirtualMachinesList(data.items)} />
</div>;
const UserGroupsVirtualMachineField = () =>
- <Field
- name='groups'
- component={TextField as any}
- validate={USER_LENGTH_VALIDATION}
- label="Groups for virtual machine (comma separated list)" />;
+ <GroupArrayInput
+ name={VIRTUAL_MACHINE_ADD_LOGIN_GROUPS_FIELD}
+ input={{id:"Add groups to VM login (eg: docker, sudo)", disabled:false}}
+ required={false}
+ />
const getVirtualMachinesList = (virtualMachines: VirtualMachinesResource[]) =>
- [{ key: "", value: "" }].concat(virtualMachines.map(it => ({ key: it.hostname, value: it.hostname })));
+ [{ key: "", value: "" }].concat(virtualMachines.map(it => ({ key: it.uuid, value: it.hostname })));
-type SetupShellAccountDialogComponentProps = WithDialogProps<{}> & InjectedFormProps<SetupShellAccountFormDialogData>;
+type SetupShellAccountDialogComponentProps = WithDialogProps<{}> & InjectedFormProps<AddLoginFormData>;
const SetupShellAccountFormFields = (props: SetupShellAccountDialogComponentProps) =>
<>
- <UserEmailField data={props.data as DataProps} />
+ <UserNameField />
<UserVirtualMachineField data={props.data as DataProps} />
<UserGroupsVirtualMachineField />
</>;
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { Dispatch, compose } from 'redux';
+import { connect } from "react-redux";
+import { ConfirmationDialog } from "components/confirmation-dialog/confirmation-dialog";
+import { withDialog, WithDialogProps } from "store/dialog/with-dialog";
+import { unsetup, DEACTIVATE_DIALOG } from 'store/user-profile/user-profile-actions';
+
+const mapDispatchToProps = (dispatch: Dispatch, props: WithDialogProps<any>) => ({
+ onConfirm: () => {
+ props.closeDialog();
+ dispatch<any>(unsetup(props.data.uuid));
+ }
+});
+
+export const DeactivateDialog = compose(
+ withDialog(DEACTIVATE_DIALOG),
+ connect(null, mapDispatchToProps)
+)(ConfirmationDialog);
import { withDialog } from 'store/dialog/with-dialog';
import { WithStyles, withStyles } from '@material-ui/core/styles';
import { ArvadosTheme } from 'common/custom-theme';
-import { USER_MANAGEMENT_DIALOG, openSetupShellAccount, loginAs } from "store/users/users-actions";
+import { USER_MANAGEMENT_DIALOG } from "store/users/users-actions";
+import { openSetupShellAccount, loginAs } from 'store/users/users-actions';
import { getUserDisplayName } from "models/user";
type CssRules = 'spacing';
+++ /dev/null
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-import React from 'react';
-import { Field, InjectedFormProps, WrappedFieldProps } from "redux-form";
-import { TextField } from "components/text-field/text-field";
-import { NativeSelectField } from "components/select-field/select-field";
-import {
- StyleRulesCallback,
- WithStyles,
- withStyles,
- Card,
- CardContent,
- Button,
- Typography,
- Grid,
- InputLabel
-} from '@material-ui/core';
-import { ArvadosTheme } from 'common/custom-theme';
-import { User } from "models/user";
-import { MY_ACCOUNT_VALIDATION } from "validators/validators";
-
-type CssRules = 'root' | 'gridItem' | 'label' | 'title' | 'actions';
-
-const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
- root: {
- width: '100%',
- overflow: 'auto'
- },
- gridItem: {
- height: 45,
- marginBottom: 20
- },
- label: {
- fontSize: '0.675rem'
- },
- title: {
- marginBottom: theme.spacing.unit * 3,
- color: theme.palette.grey["600"]
- },
- actions: {
- display: 'flex',
- justifyContent: 'flex-end'
- }
-});
-
-export interface MyAccountPanelRootActionProps { }
-
-export interface MyAccountPanelRootDataProps {
- isPristine: boolean;
- isValid: boolean;
- initialValues?: User;
- localCluster: string;
-}
-
-const RoleTypes = [
- { key: 'Bio-informatician', value: 'Bio-informatician' },
- { key: 'Data Scientist', value: 'Data Scientist' },
- { key: 'Analyst', value: 'Analyst' },
- { key: 'Researcher', value: 'Researcher' },
- { key: 'Software Developer', value: 'Software Developer' },
- { key: 'System Administrator', value: 'System Administrator' },
- { key: 'Other', value: 'Other' }
-];
-
-type MyAccountPanelRootProps = InjectedFormProps<MyAccountPanelRootActionProps> & MyAccountPanelRootDataProps & WithStyles<CssRules>;
-
-type LocalClusterProp = { localCluster: string };
-const renderField: React.ComponentType<WrappedFieldProps & LocalClusterProp> = ({ input, localCluster }) => (
- <span>{localCluster === input.value.substring(0, 5) ? "" : "federated"} user {input.value}</span>
-);
-
-export const MyAccountPanelRoot = withStyles(styles)(
- ({ classes, isValid, handleSubmit, reset, isPristine, invalid, submitting, localCluster }: MyAccountPanelRootProps) => {
- return <Card className={classes.root}>
- <CardContent>
- <Typography variant="title" className={classes.title}>
- Logged in as <Field name="uuid" component={renderField} localCluster={localCluster} />
- </Typography>
- <form onSubmit={handleSubmit}>
- <Grid container spacing={24}>
- <Grid item className={classes.gridItem} sm={6} xs={12}>
- <Field
- label="First name"
- name="firstName"
- component={TextField as any}
- disabled
- />
- </Grid>
- <Grid item className={classes.gridItem} sm={6} xs={12}>
- <Field
- label="Last name"
- name="lastName"
- component={TextField as any}
- disabled
- />
- </Grid>
- <Grid item className={classes.gridItem} sm={6} xs={12}>
- <Field
- label="E-mail"
- name="email"
- component={TextField as any}
- disabled
- />
- </Grid>
- <Grid item className={classes.gridItem} sm={6} xs={12}>
- <Field
- label="Username"
- name="username"
- component={TextField as any}
- disabled
- />
- </Grid>
- <Grid item className={classes.gridItem} sm={6} xs={12}>
- <Field
- label="Organization"
- name="prefs.profile.organization"
- component={TextField as any}
- validate={MY_ACCOUNT_VALIDATION}
- required
- />
- </Grid>
- <Grid item className={classes.gridItem} sm={6} xs={12}>
- <Field
- label="E-mail at Organization"
- name="prefs.profile.organization_email"
- component={TextField as any}
- validate={MY_ACCOUNT_VALIDATION}
- required
- />
- </Grid>
- <Grid item className={classes.gridItem} sm={6} xs={12}>
- <InputLabel className={classes.label} htmlFor="prefs.profile.role">Role</InputLabel>
- <Field
- id="prefs.profile.role"
- name="prefs.profile.role"
- component={NativeSelectField as any}
- items={RoleTypes}
- />
- </Grid>
- <Grid item className={classes.gridItem} sm={6} xs={12}>
- <Field
- label="Website"
- name="prefs.profile.website_url"
- component={TextField as any}
- />
- </Grid>
- <Grid container direction="row" justify="flex-end" >
- <Button color="primary" onClick={reset} disabled={isPristine}>Discard changes</Button>
- <Button
- color="primary"
- variant="contained"
- type="submit"
- disabled={isPristine || invalid || submitting}>
- Save changes
- </Button>
- </Grid>
- </Grid>
- </form >
- </CardContent >
- </Card >;
- }
-);
+++ /dev/null
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-import { RootState } from 'store/store';
-import { compose } from 'redux';
-import { reduxForm, isPristine, isValid } from 'redux-form';
-import { connect } from 'react-redux';
-import { saveEditedUser } from 'store/my-account/my-account-panel-actions';
-import { MyAccountPanelRoot, MyAccountPanelRootDataProps } from 'views/my-account-panel/my-account-panel-root';
-import { MY_ACCOUNT_FORM } from "store/my-account/my-account-panel-actions";
-
-const mapStateToProps = (state: RootState): MyAccountPanelRootDataProps => ({
- isPristine: isPristine(MY_ACCOUNT_FORM)(state),
- isValid: isValid(MY_ACCOUNT_FORM)(state),
- initialValues: state.auth.user,
- localCluster: state.auth.localCluster
-});
-
-export const MyAccountPanel = compose(
- connect(mapStateToProps),
- reduxForm({
- form: MY_ACCOUNT_FORM,
- onSubmit: (data, dispatch) => {
- dispatch(saveEditedUser(data));
- }
- }))(MyAccountPanelRoot);
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import React from 'react';
+import { Field, InjectedFormProps } from "redux-form";
+import { TextField } from "components/text-field/text-field";
+import { DataExplorer } from "views-components/data-explorer/data-explorer";
+import { NativeSelectField } from "components/select-field/select-field";
+import {
+ StyleRulesCallback,
+ WithStyles,
+ withStyles,
+ Card,
+ CardContent,
+ Button,
+ Typography,
+ Grid,
+ InputLabel,
+ Tabs, Tab,
+ Paper
+} from '@material-ui/core';
+import { ArvadosTheme } from 'common/custom-theme';
+import { User } from "models/user";
+import { DataTableDefaultView } from 'components/data-table-default-view/data-table-default-view';
+import { MY_ACCOUNT_VALIDATION } from "validators/validators";
+import { USER_PROFILE_PANEL_ID } from 'store/user-profile/user-profile-actions';
+import { noop } from 'lodash';
+import { GroupsIcon } from 'components/icon/icon';
+import { DataColumns } from 'components/data-table/data-table';
+import { ResourceLinkHeadUuid, ResourceLinkHeadPermissionLevel, ResourceLinkHead, ResourceLinkDelete, ResourceLinkTailIsVisible } from 'views-components/data-explorer/renderers';
+import { createTree } from 'models/tree';
+
+type CssRules = 'root' | 'adminRoot' | 'gridItem' | 'label' | 'title' | 'description' | 'actions' | 'content';
+
+const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
+ root: {
+ width: '100%',
+ overflow: 'auto'
+ },
+ adminRoot: {
+ // ...theme.mixins.gutters()
+ },
+ gridItem: {
+ height: 45,
+ marginBottom: 20
+ },
+ label: {
+ fontSize: '0.675rem'
+ },
+ title: {
+ fontSize: '1.1rem',
+ },
+ description: {
+ color: theme.palette.grey["600"]
+ },
+ actions: {
+ display: 'flex',
+ justifyContent: 'flex-end'
+ },
+ content: {
+ // reserve space for the tab bar
+ height: `calc(100% - ${theme.spacing.unit * 7}px)`,
+ }
+});
+
+export interface UserProfilePanelRootActionProps {
+ openSetupShellAccount: (uuid: string) => void;
+ loginAs: (uuid: string) => void;
+ openDeactivateDialog: (uuid: string) => void;
+}
+
+export interface UserProfilePanelRootDataProps {
+ isPristine: boolean;
+ isValid: boolean;
+ initialValues?: User;
+ localCluster: string;
+}
+
+const RoleTypes = [
+ { key: 'Bio-informatician', value: 'Bio-informatician' },
+ { key: 'Data Scientist', value: 'Data Scientist' },
+ { key: 'Analyst', value: 'Analyst' },
+ { key: 'Researcher', value: 'Researcher' },
+ { key: 'Software Developer', value: 'Software Developer' },
+ { key: 'System Administrator', value: 'System Administrator' },
+ { key: 'Other', value: 'Other' }
+];
+
+type UserProfilePanelRootProps = InjectedFormProps<{}> & UserProfilePanelRootActionProps & UserProfilePanelRootDataProps & WithStyles<CssRules>;
+
+// type LocalClusterProp = { localCluster: string };
+// const renderField: React.ComponentType<WrappedFieldProps & LocalClusterProp> = ({ input, localCluster }) => (
+// <span>{localCluster === input.value.substring(0, 5) ? "" : "federated"} user {input.value}</span>
+// );
+
+export enum UserProfileGroupsColumnNames {
+ NAME = "Name",
+ PERMISSION = "Permission",
+ VISIBLE = "Visible to other members",
+ UUID = "UUID",
+ REMOVE = "Remove",
+}
+
+export const userProfileGroupsColumns: DataColumns<string> = [
+ {
+ name: UserProfileGroupsColumnNames.NAME,
+ selected: true,
+ configurable: true,
+ filters: createTree(),
+ render: uuid => <ResourceLinkHead uuid={uuid} />
+ },
+ {
+ name: UserProfileGroupsColumnNames.PERMISSION,
+ selected: true,
+ configurable: true,
+ filters: createTree(),
+ render: uuid => <ResourceLinkHeadPermissionLevel uuid={uuid} />
+ },
+ {
+ name: UserProfileGroupsColumnNames.VISIBLE,
+ selected: true,
+ configurable: true,
+ filters: createTree(),
+ render: uuid => <ResourceLinkTailIsVisible uuid={uuid} />
+ },
+ {
+ name: UserProfileGroupsColumnNames.UUID,
+ selected: true,
+ configurable: true,
+ filters: createTree(),
+ render: uuid => <ResourceLinkHeadUuid uuid={uuid} />
+ },
+ {
+ name: UserProfileGroupsColumnNames.REMOVE,
+ selected: true,
+ configurable: true,
+ filters: createTree(),
+ render: uuid => <ResourceLinkDelete uuid={uuid} />
+ },
+];
+
+export const UserProfilePanelRoot = withStyles(styles)(
+ class extends React.Component<UserProfilePanelRootProps> {
+ state = {
+ value: 0,
+ };
+
+ componentDidMount() {
+ this.setState({ value: 0 });
+ }
+
+ render() {
+ return <Paper className={this.props.classes.root}>
+ {/* <Typography variant="title" className={this.props.classes.title}>
+ Logged in as <Field name="uuid" component={renderField} localCluster={this.props.localCluster} />
+ </Typography> */}
+ <Tabs value={this.state.value} onChange={this.handleChange} fullWidth>
+ <Tab label="PROFILE" />
+ <Tab label="GROUPS" />
+ <Tab label="ADMIN" />
+ </Tabs>
+ {this.state.value === 0 &&
+ // <Card className={this.props.classes.root}>
+ <CardContent>
+ <form onSubmit={this.props.handleSubmit}>
+ <Grid container spacing={24}>
+ <Grid item className={this.props.classes.gridItem} sm={6} xs={12}>
+ <Field
+ label="First name"
+ name="firstName"
+ component={TextField as any}
+ disabled
+ />
+ </Grid>
+ <Grid item className={this.props.classes.gridItem} sm={6} xs={12}>
+ <Field
+ label="Last name"
+ name="lastName"
+ component={TextField as any}
+ disabled
+ />
+ </Grid>
+ <Grid item className={this.props.classes.gridItem} sm={6} xs={12}>
+ <Field
+ label="E-mail"
+ name="email"
+ component={TextField as any}
+ disabled
+ />
+ </Grid>
+ <Grid item className={this.props.classes.gridItem} sm={6} xs={12}>
+ <Field
+ label="Username"
+ name="username"
+ component={TextField as any}
+ disabled
+ />
+ </Grid>
+ <Grid item className={this.props.classes.gridItem} sm={6} xs={12}>
+ <Field
+ label="Organization"
+ name="prefs.profile.organization"
+ component={TextField as any}
+ validate={MY_ACCOUNT_VALIDATION}
+ required
+ />
+ </Grid>
+ <Grid item className={this.props.classes.gridItem} sm={6} xs={12}>
+ <Field
+ label="E-mail at Organization"
+ name="prefs.profile.organization_email"
+ component={TextField as any}
+ validate={MY_ACCOUNT_VALIDATION}
+ required
+ />
+ </Grid>
+ <Grid item className={this.props.classes.gridItem} sm={6} xs={12}>
+ <InputLabel className={this.props.classes.label} htmlFor="prefs.profile.role">Role</InputLabel>
+ <Field
+ id="prefs.profile.role"
+ name="prefs.profile.role"
+ component={NativeSelectField as any}
+ items={RoleTypes}
+ />
+ </Grid>
+ <Grid item className={this.props.classes.gridItem} sm={6} xs={12}>
+ <Field
+ label="Website"
+ name="prefs.profile.website_url"
+ component={TextField as any}
+ />
+ </Grid>
+ <Grid item sm={12}>
+ <Grid container direction="row" justify="flex-end">
+ <Button color="primary" onClick={this.props.reset} disabled={this.props.isPristine}>Discard changes</Button>
+ <Button
+ color="primary"
+ variant="contained"
+ type="submit"
+ disabled={this.props.isPristine || this.props.invalid || this.props.submitting}>
+ Save changes
+ </Button>
+ </Grid>
+ </Grid>
+ </Grid>
+ </form >
+ </CardContent>
+ // </Card>
+ }
+ {this.state.value === 1 &&
+ <div className={this.props.classes.content}>
+ <DataExplorer
+ id={USER_PROFILE_PANEL_ID}
+ onRowClick={noop}
+ onRowDoubleClick={noop}
+ // onContextMenu={this.handleContextMenu}
+ contextMenuColumn={false}
+ hideColumnSelector
+ hideSearchInput
+ paperProps={{
+ elevation: 0,
+ }}
+ dataTableDefaultView={
+ <DataTableDefaultView
+ icon={GroupsIcon}
+ messages={['Group list is empty.']} />
+ } />
+ </div>}
+ {this.state.value === 2 &&
+ <Paper elevation={0} className={this.props.classes.adminRoot}>
+ <Card elevation={0}>
+ <CardContent>
+ <Grid container
+ direction="row"
+ justify={'flex-end'}
+ alignItems={'center'}>
+ <Grid item xs>
+ <Typography variant="h6" className={this.props.classes.title}>
+ Setup Account
+ </Typography>
+ <Typography variant="body1" className={this.props.classes.description}>
+ 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.
+ </Typography>
+ </Grid>
+ <Grid item sm={'auto'} xs={12}>
+ <Button variant="contained"
+ color="primary"
+ onClick={() => {this.props.openSetupShellAccount(this.props.initialValues.uuid)}}
+ disabled={false}>
+ Setup Account
+ </Button>
+ </Grid>
+ </Grid>
+ </CardContent>
+ </Card>
+ <Card elevation={0}>
+ <CardContent>
+ <Grid container
+ direction="row"
+ justify={'flex-end'}
+ alignItems={'center'}>
+ <Grid item xs>
+ <Typography variant="h6" className={this.props.classes.title}>
+ Deactivate
+ </Typography>
+ <Typography variant="body1" className={this.props.classes.description}>
+ 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.
+ </Typography>
+ </Grid>
+ <Grid item sm={'auto'} xs={12}>
+ <Button variant="contained"
+ color="primary"
+ onClick={() => {this.props.openDeactivateDialog(this.props.initialValues.uuid)}}
+ disabled={false}>
+ Deactivate
+ </Button>
+ </Grid>
+ </Grid>
+ </CardContent>
+ </Card>
+ <Card elevation={0}>
+ <CardContent>
+ <Grid container
+ direction="row"
+ justify={'flex-end'}
+ alignItems={'center'}>
+ <Grid item xs>
+ <Typography variant="h6" className={this.props.classes.title}>
+ Log In
+ </Typography>
+ <Typography variant="body1" className={this.props.classes.description}>
+ 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.
+ </Typography>
+ </Grid>
+ <Grid item sm={'auto'} xs={12}>
+ <Button variant="contained"
+ color="primary"
+ onClick={() => {this.props.loginAs(this.props.initialValues.uuid)}}
+ disabled={false}>
+ Log In
+ </Button>
+ </Grid>
+ </Grid>
+ </CardContent>
+ </Card>
+ </Paper>}
+ </Paper >;
+ }
+
+ handleChange = (event: React.MouseEvent<HTMLElement>, value: number) => {
+ this.setState({ value });
+ }
+
+ handleContextMenu = (event: React.MouseEvent<HTMLElement>, resourceUuid: string) => {
+ // const resource = getResource<UserResource>(resourceUuid)(this.props.resources);
+ // if (resource) {
+ // this.props.onContextMenu(event, {
+ // name: '',
+ // uuid: resource.uuid,
+ // ownerUuid: resource.ownerUuid,
+ // kind: resource.kind,
+ // menuKind: ContextMenuKind.USER
+ // });
+ // }
+ }
+ }
+);
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { RootState } from 'store/store';
+import { compose, Dispatch } from 'redux';
+import { reduxForm, isPristine, isValid } from 'redux-form';
+import { connect } from 'react-redux';
+import { saveEditedUser } from 'store/user-profile/user-profile-actions';
+import { UserProfilePanelRoot, UserProfilePanelRootDataProps } from 'views/user-profile-panel/user-profile-panel-root';
+import { openDeactivateDialog, USER_PROFILE_FORM } from "store/user-profile/user-profile-actions";
+import { matchUserProfileRoute } from 'routes/routes';
+import { UserResource } from 'models/user';
+import { getResource } from 'store/resources/resources';
+import { openSetupShellAccount, loginAs } from 'store/users/users-actions';
+
+const mapStateToProps = (state: RootState): UserProfilePanelRootDataProps => {
+ const pathname = state.router.location ? state.router.location.pathname : '';
+ const match = matchUserProfileRoute(pathname);
+ const uuid = match ? match.params.id : state.auth.user?.uuid || '';
+ // get user resource
+ const user = getResource<UserResource>(uuid)(state.resources);
+ // const subprocesses = getSubprocesses(uuid)(resources);
+
+ return {
+
+ isPristine: isPristine(USER_PROFILE_FORM)(state),
+ isValid: isValid(USER_PROFILE_FORM)(state),
+ initialValues: user,
+ localCluster: state.auth.localCluster
+}};
+
+const mapDispatchToProps = (dispatch: Dispatch) => ({
+ openSetupShellAccount: (uuid: string) => dispatch<any>(openSetupShellAccount(uuid)),
+ loginAs: (uuid: string) => dispatch<any>(loginAs(uuid)),
+ openDeactivateDialog: (uuid: string) => dispatch<any>(openDeactivateDialog(uuid)),
+});
+
+export const UserProfilePanel = compose(
+ connect(mapStateToProps, mapDispatchToProps),
+ reduxForm({
+ form: USER_PROFILE_FORM,
+ onSubmit: (data, dispatch) => {
+ dispatch(saveEditedUser(data));
+ }
+ }))(UserProfilePanelRoot);
import { SshKeyPanel } from 'views/ssh-key-panel/ssh-key-panel';
import { SshKeyAdminPanel } from 'views/ssh-key-panel/ssh-key-admin-panel';
import { SiteManagerPanel } from "views/site-manager-panel/site-manager-panel";
-import { MyAccountPanel } from 'views/my-account-panel/my-account-panel';
+import { UserProfilePanel } from 'views/user-profile-panel/user-profile-panel';
import { SharingDialog } from 'views-components/sharing-dialog/sharing-dialog';
import { NotFoundDialog } from 'views-components/not-found-dialog/not-found-dialog';
import { AdvancedTabDialog } from 'views-components/advanced-tab-dialog/advanced-tab-dialog';
import { CreateUserDialog } from 'views-components/dialog-forms/create-user-dialog';
import { HelpApiClientAuthorizationDialog } from 'views-components/api-client-authorizations-dialog/help-dialog';
import { UserManageDialog } from 'views-components/user-dialog/manage-dialog';
+import { DeactivateDialog } from 'views-components/user-dialog/deactivate-dialog';
import { SetupShellAccountDialog } from 'views-components/dialog-forms/setup-shell-account-dialog';
import { GroupsPanel } from 'views/groups-panel/groups-panel';
import { RemoveGroupDialog } from 'views-components/groups-dialog/remove-dialog';
<Route path={Routes.KEEP_SERVICES} component={KeepServicePanel} />
<Route path={Routes.USERS} component={UserPanel} />
<Route path={Routes.API_CLIENT_AUTHORIZATIONS} component={ApiClientAuthorizationPanel} />
- <Route path={Routes.MY_ACCOUNT} component={MyAccountPanel} />
+ <Route path={Routes.MY_ACCOUNT} component={UserProfilePanel} />
+ <Route path={Routes.USER_PROFILE} component={UserProfilePanel} />
<Route path={Routes.GROUPS} component={GroupsPanel} />
<Route path={Routes.GROUP_DETAILS} component={GroupDetailsPanel} />
<Route path={Routes.LINKS} component={LinkPanel} />
<UpdateProjectDialog />
<UserAttributesDialog />
<UserManageDialog />
+ <DeactivateDialog />
<VirtualMachineAttributesDialog />
<FedLogin />
<WebDavS3InfoDialog />