conflicts
authorPawel Kowalczyk <pawel.kowalczyk@contractors.roche.com>
Fri, 7 Dec 2018 09:21:57 +0000 (10:21 +0100)
committerPawel Kowalczyk <pawel.kowalczyk@contractors.roche.com>
Fri, 7 Dec 2018 09:21:57 +0000 (10:21 +0100)
Feature #14504

Arvados-DCO-1.1-Signed-off-by: Pawel Kowalczyk <pawel.kowalczyk@contractors.roche.com>

14 files changed:
1  2 
src/index.tsx
src/models/user.ts
src/routes/route-change-handlers.ts
src/routes/routes.ts
src/store/advanced-tab/advanced-tab.ts
src/store/navigation/navigation-action.ts
src/store/repositories/repositories-actions.ts
src/store/store.ts
src/store/workbench/workbench-actions.ts
src/validators/validators.tsx
src/views-components/context-menu/context-menu.tsx
src/views-components/main-app-bar/account-menu.tsx
src/views-components/main-content-bar/main-content-bar.tsx
src/views/workbench/workbench.tsx

diff --cc src/index.tsx
index 96df16cf965cbace73126bd8f1904c7eaca5bdc4,fbd6c9a88e1bb8ac18d66bd3ff4c681f38ab89ee..87af8f1deb5ecd8b22e2ce270e27f48c8569c582
@@@ -53,7 -53,7 +53,8 @@@ import { sshKeyActionSet } from '~/view
  import { keepServiceActionSet } from '~/views-components/context-menu/action-sets/keep-service-action-set';
  import { loadVocabulary } from '~/store/vocabulary/vocabulary-actions';
  import { virtualMachineActionSet } from '~/views-components/context-menu/action-sets/virtual-machine-action-set';
 +import { userActionSet } from '~/views-components/context-menu/action-sets/user-action-set';
+ import { computeNodeActionSet } from '~/views-components/context-menu/action-sets/compute-node-action-set';
  
  console.log(`Starting arvados [${getBuildInfo()}]`);
  
@@@ -74,7 -74,7 +75,8 @@@ addMenuActionSet(ContextMenuKind.REPOSI
  addMenuActionSet(ContextMenuKind.SSH_KEY, sshKeyActionSet);
  addMenuActionSet(ContextMenuKind.VIRTUAL_MACHINE, virtualMachineActionSet);
  addMenuActionSet(ContextMenuKind.KEEP_SERVICE, keepServiceActionSet);
 +addMenuActionSet(ContextMenuKind.USER, userActionSet);
+ addMenuActionSet(ContextMenuKind.NODE, computeNodeActionSet);
  
  fetchConfig()
      .then(({ config, apiHost }) => {
Simple merge
index 1cea993f59761d36aed45939b491dc7b800e9300,68de3107f1a82bb219fb49e883c27682c486902f..5b281b830dc1f38ac695a3ecf3e75afa7fa8e31e
@@@ -24,54 -15,57 +15,60 @@@ export const addRouteChangeHandlers = (
  };
  
  const handleLocationChange = (store: RootStore) => ({ pathname }: Location) => {
-     const rootMatch = matchRootRoute(pathname);
-     const projectMatch = matchProjectRoute(pathname);
-     const collectionMatch = matchCollectionRoute(pathname);
-     const favoriteMatch = matchFavoritesRoute(pathname);
-     const trashMatch = matchTrashRoute(pathname);
-     const processMatch = matchProcessRoute(pathname);
-     const processLogMatch = matchProcessLogRoute(pathname);
-     const repositoryMatch = matchRepositoriesRoute(pathname);
-     const searchResultsMatch = matchSearchResultsRoute(pathname);
-     const sharedWithMeMatch = matchSharedWithMeRoute(pathname);
-     const runProcessMatch = matchRunProcessRoute(pathname);
-     const virtualMachineMatch = matchVirtualMachineRoute(pathname);
-     const workflowMatch = matchWorkflowRoute(pathname);
-     const sshKeysMatch = matchSshKeysRoute(pathname);
-     const keepServicesMatch = matchKeepServicesRoute(pathname);
-     const userMatch = matchUsersRoute(pathname);
+     const rootMatch = Routes.matchRootRoute(pathname);
+     const projectMatch = Routes.matchProjectRoute(pathname);
+     const collectionMatch = Routes.matchCollectionRoute(pathname);
+     const favoriteMatch = Routes.matchFavoritesRoute(pathname);
+     const trashMatch = Routes.matchTrashRoute(pathname);
+     const processMatch = Routes.matchProcessRoute(pathname);
+     const processLogMatch = Routes.matchProcessLogRoute(pathname);
+     const repositoryMatch = Routes.matchRepositoriesRoute(pathname);
+     const searchResultsMatch = Routes.matchSearchResultsRoute(pathname);
+     const sharedWithMeMatch = Routes.matchSharedWithMeRoute(pathname);
+     const runProcessMatch = Routes.matchRunProcessRoute(pathname);
+     const virtualMachineMatch = Routes.matchVirtualMachineRoute(pathname);
+     const workflowMatch = Routes.matchWorkflowRoute(pathname);
+     const sshKeysMatch = Routes.matchSshKeysRoute(pathname);
+     const keepServicesMatch = Routes.matchKeepServicesRoute(pathname);
+     const computeNodesMatch = Routes.matchComputeNodesRoute(pathname);
+     const myAccountMatch = Routes.matchMyAccountRoute(pathname);
++    const userMatch = Routes.matchUsersRoute(pathname);
  
      if (projectMatch) {
-         store.dispatch(loadProject(projectMatch.params.id));
+         store.dispatch(WorkbenchActions.loadProject(projectMatch.params.id));
      } else if (collectionMatch) {
-         store.dispatch(loadCollection(collectionMatch.params.id));
+         store.dispatch(WorkbenchActions.loadCollection(collectionMatch.params.id));
      } else if (favoriteMatch) {
-         store.dispatch(loadFavorites());
+         store.dispatch(WorkbenchActions.loadFavorites());
      } else if (trashMatch) {
-         store.dispatch(loadTrash());
+         store.dispatch(WorkbenchActions.loadTrash());
      } else if (processMatch) {
-         store.dispatch(loadProcess(processMatch.params.id));
+         store.dispatch(WorkbenchActions.loadProcess(processMatch.params.id));
      } else if (processLogMatch) {
-         store.dispatch(loadProcessLog(processLogMatch.params.id));
+         store.dispatch(WorkbenchActions.loadProcessLog(processLogMatch.params.id));
      } else if (rootMatch) {
          store.dispatch(navigateToRootProject);
      } else if (sharedWithMeMatch) {
-         store.dispatch(loadSharedWithMe);
+         store.dispatch(WorkbenchActions.loadSharedWithMe);
      } else if (runProcessMatch) {
-         store.dispatch(loadRunProcess);
+         store.dispatch(WorkbenchActions.loadRunProcess);
      } else if (workflowMatch) {
-         store.dispatch(loadWorkflow);
+         store.dispatch(WorkbenchActions.loadWorkflow);
      } else if (searchResultsMatch) {
-         store.dispatch(loadSearchResults);
+         store.dispatch(WorkbenchActions.loadSearchResults);
      } else if (virtualMachineMatch) {
-         store.dispatch(loadVirtualMachines);
+         store.dispatch(WorkbenchActions.loadVirtualMachines);
      } else if(repositoryMatch) {
-         store.dispatch(loadRepositories);
+         store.dispatch(WorkbenchActions.loadRepositories);
      } else if (sshKeysMatch) {
-         store.dispatch(loadSshKeys);
+         store.dispatch(WorkbenchActions.loadSshKeys);
      } else if (keepServicesMatch) {
-         store.dispatch(loadKeepServices);
-     } else if (userMatch) {
-         store.dispatch(loadUsers);
+         store.dispatch(WorkbenchActions.loadKeepServices);
+     } else if (computeNodesMatch) {
+         store.dispatch(WorkbenchActions.loadComputeNodes);
+     } else if (myAccountMatch) {
+         store.dispatch(WorkbenchActions.loadMyAccount);
++    }else if (userMatch) {
++        store.dispatch(WorkbenchActions.loadUsers);
      }
  };
index 2c4337df95a5c2a41d919a2c9cb2669e42b4ae2d,71d920ab3e4d984dcf126a5aaf6b439431dde9d7..dabb9bf579884824c35150e410a0cbe673cc862a
@@@ -23,8 -23,9 +23,10 @@@ export const Routes = 
      WORKFLOWS: '/workflows',
      SEARCH_RESULTS: '/search-results',
      SSH_KEYS: `/ssh-keys`,
+     MY_ACCOUNT: '/my-account',
      KEEP_SERVICES: `/keep-services`,
 -    COMPUTE_NODES: `/nodes`
++    COMPUTE_NODES: `/nodes`,
 +    USERS: '/users'
  };
  
  export const getResourceUrl = (uuid: string) => {
@@@ -94,5 -98,5 +99,8 @@@ export const matchMyAccountRoute = (rou
  export const matchKeepServicesRoute = (route: string) =>
      matchPath(route, { path: Routes.KEEP_SERVICES });
  
 -    matchPath(route, { path: Routes.COMPUTE_NODES });
 +export const matchUsersRoute = (route: string) =>
 +    matchPath(route, { path: Routes.USERS });
++
+ export const matchComputeNodesRoute = (route: string) =>
++    matchPath(route, { path: Routes.COMPUTE_NODES });
index 67d139460b17f2d6caee4be2e386c429d7537e4d,6b20f8b328a2ac555552031d5732834a6912fdc8..4da4d7e14ca233c437d99cc7bbd15e1aa26add08
@@@ -73,7 -74,7 +74,8 @@@ enum ResourcePrefix 
      AUTORIZED_KEYS = 'authorized_keys',
      VIRTUAL_MACHINES = 'virtual_machines',
      KEEP_SERVICES = 'keep_services',
-     USERS = 'users'
++    USERS = 'users',
+     COMPUTE_NODES = 'nodes'
  }
  
  enum KeepServiceData {
      CREATED_AT = 'created_at'
  }
  
- type AdvanceResourceKind = CollectionData | ProcessData | ProjectData | RepositoryData | SshKeyData | VirtualMachineData | KeepServiceData | UserData;
 +enum UserData {
 +    USER = 'user',
 +    USERNAME = 'username'
 +}
 +
 -type AdvanceResourceKind = CollectionData | ProcessData | ProjectData | RepositoryData | SshKeyData | VirtualMachineData | KeepServiceData | ComputeNodeData;
+ enum ComputeNodeData {
+     COMPUTE_NODE = 'node',
+     PROPERTIES = 'properties'
+ }
++type AdvanceResourceKind = CollectionData | ProcessData | ProjectData | RepositoryData | SshKeyData | VirtualMachineData | KeepServiceData | ComputeNodeData | UserData;
  type AdvanceResourcePrefix = GroupContentsResourcePrefix | ResourcePrefix;
- type AdvanceResponseData = ContainerRequestResource | ProjectResource | CollectionResource | RepositoryResource | SshKeyResource | VirtualMachinesResource | KeepServiceResource | UserResource | undefined;
 -type AdvanceResponseData = ContainerRequestResource | ProjectResource | CollectionResource | RepositoryResource | SshKeyResource | VirtualMachinesResource | KeepServiceResource | NodeResource | undefined;
++type AdvanceResponseData = ContainerRequestResource | ProjectResource | CollectionResource | RepositoryResource | SshKeyResource | VirtualMachinesResource | KeepServiceResource | NodeResource | UserResource | undefined;
  
  export const openAdvancedTabDialog = (uuid: string) =>
      async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
                  });
                  dispatch<any>(initAdvancedTabDialog(advanceDataKeepService));
                  break;
 +            case ResourceKind.USER:
 +                const { resources } = getState();
 +                const data = getResource<UserResource>(uuid)(resources);
 +                const metadata = await services.linkService.list({
 +                    filters: new FilterBuilder()
 +                        .addEqual('headUuid', uuid)
 +                        .getFilters()
 +                });
 +                const advanceDataUser = advancedTabData({
 +                    uuid,
 +                    metadata,
 +                    user: '',
 +                    apiResponseKind: userApiResponse,
 +                    data,
 +                    resourceKind: UserData.USER,
 +                    resourcePrefix: ResourcePrefix.USERS,
 +                    resourceKindProperty: UserData.USERNAME,
 +                    property: data!.username
 +                });
 +                dispatch<any>(initAdvancedTabDialog(advanceDataUser));
 +                break;
+             case ResourceKind.NODE:
+                 const dataComputeNode = getState().computeNodes.find(node => node.uuid === uuid);
+                 const advanceDataComputeNode = advancedTabData({
+                     uuid,
+                     metadata: '',
+                     user: '',
+                     apiResponseKind: computeNodeApiResponse,
+                     data: dataComputeNode,
+                     resourceKind: ComputeNodeData.COMPUTE_NODE,
+                     resourcePrefix: ResourcePrefix.COMPUTE_NODES,
+                     resourceKindProperty: ComputeNodeData.PROPERTIES,
+                     property: dataComputeNode!.properties
+                 });
+                 dispatch<any>(initAdvancedTabDialog(advanceDataComputeNode));
+                 break;
              default:
                  dispatch(snackbarActions.OPEN_SNACKBAR({ message: "Could not open advanced tab for this resource.", hideDuration: 2000, kind: SnackbarKind.ERROR }));
          }
@@@ -470,26 -465,27 +492,51 @@@ const keepServiceApiResponse = (apiResp
      return response;
  };
  
 +const userApiResponse = (apiResponse: UserResource) => {
 +    const {
 +        uuid, ownerUuid, createdAt, modifiedAt, modifiedByClientUuid, modifiedByUserUuid,
 +        email, firstName, lastName, identityUrl, isActive, isAdmin, prefs, defaultOwnerUuid, username
 +    } = apiResponse;
 +    const response = `"uuid": "${uuid}",
 +"owner_uuid": "${ownerUuid}",
 +"created_at": "${createdAt}",
 +"modified_by_client_uuid": ${stringify(modifiedByClientUuid)},
 +"modified_by_user_uuid": ${stringify(modifiedByUserUuid)},
 +"modified_at": ${stringify(modifiedAt)},
 +"email": "${email}",
 +"first_name": "${firstName}",
 +"last_name": "${stringify(lastName)}",
 +"identity_url": "${identityUrl}",
 +"is_active": "${isActive},
 +"is_admin": "${isAdmin},
 +"prefs": "${stringifyObject(prefs)},
 +"default_owner_uuid": "${defaultOwnerUuid},
 +"username": "${username}"`;
 +
++    return response;
++};
++
+ const computeNodeApiResponse = (apiResponse: NodeResource) => {
+     const {
+         uuid, slotNumber, hostname, domain, ipAddress, firstPingAt, lastPingAt, jobUuid,
+         ownerUuid, createdAt, modifiedAt, modifiedByClientUuid, modifiedByUserUuid,
+         properties, info
+     } = apiResponse;
+     const response = `"uuid": "${uuid}",
+ "owner_uuid": "${ownerUuid}",
+ "modified_by_client_uuid": ${stringify(modifiedByClientUuid)},
+ "modified_by_user_uuid": ${stringify(modifiedByUserUuid)},
+ "modified_at": ${stringify(modifiedAt)},
+ "created_at": "${createdAt}",
+ "slot_number": "${stringify(slotNumber)}",
+ "hostname": "${stringify(hostname)}",
+ "domain": "${stringify(domain)}",
+ "ip_address": "${stringify(ipAddress)}",
+ "first_ping_at": "${stringify(firstPingAt)}",
+ "last_ping_at": "${stringify(lastPingAt)}",
+ "job_uuid": "${stringify(jobUuid)}",
+ "properties": "${JSON.stringify(properties, null, 4)}",
+ "info": "${JSON.stringify(info, null, 4)}"`;
      return response;
  };
index 4ee22108eb9fdb0e9f16217345144b0b73f2b461,a3652726f1d2dfda5b51d8c1f5200ebf1bbd6b71..bae16891ffb4ec9016b64479ebd97b94b7dc6538
@@@ -68,6 -68,8 +68,10 @@@ export const navigateToRepositories = p
  
  export const navigateToSshKeys= push(Routes.SSH_KEYS);
  
+ export const navigateToMyAccount = push(Routes.MY_ACCOUNT);
  export const navigateToKeepServices = push(Routes.KEEP_SERVICES);
  
- export const navigateToUsers = push(Routes.USERS);
 -export const navigateToComputeNodes = push(Routes.COMPUTE_NODES);
++export const navigateToComputeNodes = push(Routes.COMPUTE_NODES);
++
++export const navigateToUsers = push(Routes.USERS);
index d04775fba6ab0af6a6fe5e82bd3f1c4f7c8bfa81,321a19b6f25182072d405e29e8b308457b4162df..1862d6f0e42eb658ea5c564b54cc401ffc4e1586
@@@ -46,8 -46,7 +46,9 @@@ import { resourcesDataReducer } from "~
  import { virtualMachinesReducer } from "~/store/virtual-machines/virtual-machines-reducer";
  import { repositoriesReducer } from '~/store/repositories/repositories-reducer';
  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 { computeNodesReducer } from '~/store/compute-nodes/compute-nodes-reducer';
  
  const composeEnhancers =
      (process.env.NODE_ENV === 'development' &&
index e7fbd1463b2158997f46af493368853e01608225,49c50f288982c2140243948b2ccc17dcda46aa3b..d33bdafb656e4a49065b8c9d631d0220d840e932
@@@ -57,8 -58,7 +58,9 @@@ import { searchResultsPanelColumns } fr
  import { loadVirtualMachinesPanel } from '~/store/virtual-machines/virtual-machines-actions';
  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 { userPanelColumns } from '~/views/user-panel/user-panel';
+ import { loadComputeNodesPanel } from '~/store/compute-nodes/compute-nodes-actions';
  
  export const WORKBENCH_LOADING_SCREEN = 'workbenchLoadingScreen';
  
@@@ -130,10 -130,7 +132,10 @@@ export const loadProject = (uuid: strin
              const userUuid = services.authService.getUuid();
              dispatch(setIsProjectPanelTrashed(false));
              if (userUuid) {
-                 if (extractUuidKind(uuid) === ResourceKind.USER && userUuid!==uuid) {
 -                if (userUuid !== uuid) {
++                if (extractUuidKind(uuid) === ResourceKind.USER && userUuid !== uuid) {
 +                    // Load another users home projects
 +                    dispatch(finishLoadingProject(uuid));
 +                } else if (userUuid !== uuid) {
                      const match = await loadGroupContentsResource({ uuid, userUuid, services });
                      match({
                          OWNED: async project => {
@@@ -421,12 -423,11 +428,17 @@@ export const loadKeepServices = handleF
          await dispatch(loadKeepServicesPanel());
      });
  
 +export const loadUsers = handleFirstTimeLoad(
 +    async (dispatch: Dispatch<any>) => {
 +        await dispatch(loadUsersPanel());
 +        dispatch(setBreadcrumbs([{ label: 'Users' }]));
 +    });
 +
+ export const loadComputeNodes = handleFirstTimeLoad(
+     async (dispatch: Dispatch<any>) => {
+         await dispatch(loadComputeNodesPanel());
+     });
  const finishLoadingProject = (project: GroupContentsResource | string) =>
      async (dispatch: Dispatch<any>) => {
          const uuid = typeof project === 'string' ? project : project.uuid;
index 464f190072b06544be1c4b00702f2f12b89a0a77,a3f5df2e22d24df4f0c5eb7892298994451432c4..30fa36bfeb9ece62a3ee3a46f19764bf9487a357
@@@ -24,8 -24,7 +24,10 @@@ export const PROCESS_NAME_VALIDATION = 
  
  export const REPOSITORY_NAME_VALIDATION = [require, maxLength(255)];
  
 +export const USER_EMAIL_VALIDATION = [require, maxLength(255)];
 +export const USER_LENGTH_VALIDATION = [maxLength(255)];
 +
  export const SSH_KEY_PUBLIC_VALIDATION = [require, isRsaKey, maxLength(1024)];
  export const SSH_KEY_NAME_VALIDATION = [require, maxLength(255)];
+ export const MY_ACCOUNT_VALIDATION = [require];
index 29b8afbcb853c83355d8faaaa8d9d59fabd0a313,3fa1ab30d83d5d994e7b94bbe49b356deee74009..68a48ee3fdd5c4f5c2a3c6913a97ad101a718910
@@@ -73,5 -73,5 +73,6 @@@ export enum ContextMenuKind 
      SSH_KEY = "SshKey",
      VIRTUAL_MACHINE = "VirtualMachine",
      KEEP_SERVICE = "KeepService",
-     USER = "User"
++    USER = "User",
+     NODE = "Node"
  }
index 889b51df068d524e658cfba0a46fe16f32d83ade,ee726f3d75ec5a603acbf80c0708be9085b59316..412f849703ff5dc54204b646823211c5f9549c42
@@@ -12,9 -12,8 +12,9 @@@ import { logout } from '~/store/auth/au
  import { RootState } from "~/store/store";
  import { openCurrentTokenDialog } from '~/store/current-token-dialog/current-token-dialog-actions';
  import { openRepositoriesPanel } from "~/store/repositories/repositories-actions";
- import { navigateToSshKeys, navigateToKeepServices } from '~/store/navigation/navigation-action';
+ import { navigateToSshKeys, navigateToKeepServices, navigateToComputeNodes, navigateToMyAccount } from '~/store/navigation/navigation-action';
  import { openVirtualMachines } from "~/store/virtual-machines/virtual-machines-actions";
 +import { navigateToUsers } from '~/store/navigation/navigation-action';
  
  interface AccountMenuProps {
      user?: User;
@@@ -38,9 -37,9 +38,10 @@@ export const AccountMenu = connect(mapS
                  <MenuItem onClick={() => dispatch(openRepositoriesPanel())}>Repositories</MenuItem>
                  <MenuItem onClick={() => dispatch(openCurrentTokenDialog)}>Current token</MenuItem>
                  <MenuItem onClick={() => dispatch(navigateToSshKeys)}>Ssh Keys</MenuItem>
-                 {user.isAdmin && <MenuItem onClick={() => dispatch(navigateToKeepServices)}>Keep Services</MenuItem>}
-                 <MenuItem>My account</MenuItem>
 +                <MenuItem onClick={() => dispatch(navigateToUsers)}>Users</MenuItem>
+                 { user.isAdmin && <MenuItem onClick={() => dispatch(navigateToKeepServices)}>Keep Services</MenuItem> }
+                 { user.isAdmin && <MenuItem onClick={() => dispatch(navigateToComputeNodes)}>Compute Nodes</MenuItem> }
+                 <MenuItem onClick={() => dispatch(navigateToMyAccount)}>My account</MenuItem>
                  <MenuItem onClick={() => dispatch(logout())}>Logout</MenuItem>
              </DropdownMenu>
              : null);
index fe681451a7353da2042a635db364bcfca54dd567,78b79a8ed46bf0f70737fa412178d421fa0562fb..7022fc5e3527b23470c107209ef074f9080c17c5
@@@ -18,9 -18,9 +18,10 @@@ interface MainContentBarProps 
  
  const isButtonVisible = ({ router }: RootState) => {
      const pathname = router.location ? router.location.pathname : '';
-     return !matchWorkflowRoute(pathname) && !matchVirtualMachineRoute(pathname) &&
-         !matchRepositoriesRoute(pathname) && !matchSshKeysRoute(pathname) && !matchKeepServicesRoute(pathname) &&
-         !matchUsersRoute(pathname);
+     return !Routes.matchWorkflowRoute(pathname) && !Routes.matchVirtualMachineRoute(pathname) &&
+         !Routes.matchRepositoriesRoute(pathname) && !Routes.matchSshKeysRoute(pathname) &&
 -        !Routes.matchKeepServicesRoute(pathname) && !Routes.matchComputeNodesRoute(pathname);
++        !Routes.matchKeepServicesRoute(pathname) && !Routes.matchComputeNodesRoute(pathname) && 
++        !Routes.matchUsersRoute(pathname);
  };
  
  export const MainContentBar = connect((state: RootState) => ({
index 5a6ba97de4a28569a577526182044025b320e2e3,5efffa19ac26b705233cd24c3fb4e08578a6c32f..e0ec14f13937bd6313524dff1f47916edd2a16ba
@@@ -63,10 -68,6 +68,9 @@@ import { AttributesComputeNodeDialog } 
  import { AttributesKeepServiceDialog } from '~/views-components/keep-services-dialog/attributes-dialog';
  import { AttributesSshKeyDialog } from '~/views-components/ssh-keys-dialog/attributes-dialog';
  import { VirtualMachineAttributesDialog } from '~/views-components/virtual-machines-dialog/attributes-dialog';
- import { RemoveVirtualMachineDialog } from '~/views-components/virtual-machines-dialog/remove-dialog';
 +import { UserPanel } from '~/views/user-panel/user-panel';
 +import { UserAttributesDialog } from '~/views-components/user-dialog/attributes-dialog';
 +import { CreateUserDialog } from '~/views-components/dialog-forms/create-user-dialog';
  
  type CssRules = 'root' | 'container' | 'splitter' | 'asidePanel' | 'contentWrapper' | 'content';
  
@@@ -140,7 -141,8 +144,9 @@@ export const WorkbenchPanel 
                                  <Route path={Routes.REPOSITORIES} component={RepositoriesPanel} />
                                  <Route path={Routes.SSH_KEYS} component={SshKeyPanel} />
                                  <Route path={Routes.KEEP_SERVICES} component={KeepServicePanel} />
 +                                <Route path={Routes.USERS} component={UserPanel} />
+                                 <Route path={Routes.COMPUTE_NODES} component={ComputeNodePanel} />
+                                 <Route path={Routes.MY_ACCOUNT} component={MyAccountPanel} />
                              </Switch>
                          </Grid>
                      </Grid>