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()}]`);
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 }) => {
};
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);
}
};
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) => {
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 });
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 }));
}
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;
};
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);
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' &&
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';
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 => {
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;
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];
SSH_KEY = "SshKey",
VIRTUAL_MACHINE = "VirtualMachine",
KEEP_SERVICE = "KeepService",
- USER = "User"
++ USER = "User",
+ NODE = "Node"
}
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;
<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);
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) => ({
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';
<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>