import { sshKeyActionSet } from '~/views-components/context-menu/action-sets/ssh-key-action-set';
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';
console.log(`Starting arvados [${getBuildInfo()}]`);
addMenuActionSet(ContextMenuKind.TRASH, trashActionSet);
addMenuActionSet(ContextMenuKind.REPOSITORY, repositoryActionSet);
addMenuActionSet(ContextMenuKind.SSH_KEY, sshKeyActionSet);
+addMenuActionSet(ContextMenuKind.VIRTUAL_MACHINE, virtualMachineActionSet);
addMenuActionSet(ContextMenuKind.KEEP_SERVICE, keepServiceActionSet);
fetchConfig()
hostname: string;
}
-export interface VirtualMachinesLoginsResource {
+export interface VirtualMachinesLoginsItems {
hostname: string;
username: string;
public_key: string;
- user_uuid: string;
- virtual_machine_uuid: string;
- authorized_key_uuid: string;
+ userUuid: string;
+ virtualMachineUuid: string;
+ authorizedKeyUuid: string;
+}
+
+export interface VirtualMachineLogins {
+ kind: string;
+ items: VirtualMachinesLoginsItems[];
}
\ No newline at end of file
import { User } from "~/models/user";
import { AxiosInstance } from "axios";
-import { ApiActions, ProgressFn } from "~/services/api/api-actions";
+import { ApiActions } from "~/services/api/api-actions";
import * as uuid from "uuid/v4";
export const API_TOKEN_KEY = 'apiToken';
const lastName = localStorage.getItem(USER_LAST_NAME_KEY);
const uuid = this.getUuid();
const ownerUuid = this.getOwnerUuid();
- const isAdmin = this.getIsAdmin();
+ const isAdmin = this.getIsAdmin();
return email && firstName && lastName && uuid && ownerUuid
? { email, firstName, lastName, uuid, ownerUuid, isAdmin }
import { FilterBuilder } from '~/services/api/filter-builder';
import { RepositoryResource } from '~/models/repositories';
import { SshKeyResource } from '~/models/ssh-key';
+import { VirtualMachinesResource } from '~/models/virtual-machines';
+import { UserResource } from '~/models/user';
+import { ListResults } from '~/services/common-service/common-resource-service';
+import { LinkResource } from '~/models/link';
import { KeepServiceResource } from '~/models/keep-services';
export const ADVANCED_TAB_DIALOG = 'advancedTabDialog';
interface AdvancedTabDialogData {
apiResponse: any;
- metadata: any;
- user: string;
+ metadata: ListResults<LinkResource> | string;
+ user: UserResource | string;
pythonHeader: string;
pythonExample: string;
cliGetHeader: string;
}
enum SshKeyData {
- SSH_KEY = 'authorized_keys',
+ SSH_KEY = 'authorized_key',
CREATED_AT = 'created_at'
}
+enum VirtualMachineData {
+ VIRTUAL_MACHINE = 'virtual_machine',
+ CREATED_AT = 'created_at'
+}
+
+enum ResourcePrefix {
+ REPOSITORIES = 'repositories',
+ AUTORIZED_KEYS = 'authorized_keys',
+ VIRTUAL_MACHINES = 'virtual_machines',
+ KEEP_SERVICES = 'keep_services'
+}
+
enum KeepServiceData {
KEEP_SERVICE = 'keep_services',
CREATED_AT = 'created_at'
}
-type AdvanceResourceKind = CollectionData | ProcessData | ProjectData | RepositoryData | SshKeyData | KeepServiceData;
-type AdvanceResourcePrefix = GroupContentsResourcePrefix | 'repositories' | 'authorized_keys' | 'keep_services';
+type AdvanceResourceKind = CollectionData | ProcessData | ProjectData | RepositoryData | SshKeyData | VirtualMachineData | KeepServiceData;
+type AdvanceResourcePrefix = GroupContentsResourcePrefix | ResourcePrefix;
-export const openAdvancedTabDialog = (uuid: string, index?: number) =>
+export const openAdvancedTabDialog = (uuid: string) =>
async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
const kind = extractUuidKind(uuid);
switch (kind) {
case ResourceKind.COLLECTION:
const { data: dataCollection, metadata: metaCollection, user: userCollection } = await dispatch<any>(getDataForAdvancedTab(uuid));
- const advanceDataCollection: AdvancedTabDialogData = advancedTabData(uuid, metaCollection, userCollection, collectionApiResponse, dataCollection, CollectionData.COLLECTION, GroupContentsResourcePrefix.COLLECTION, CollectionData.STORAGE_CLASSES_CONFIRMED, dataCollection.storageClassesConfirmed);
+ const advanceDataCollection: AdvancedTabDialogData = advancedTabData({
+ uuid,
+ metadata: metaCollection,
+ user: userCollection,
+ apiResponseKind: collectionApiResponse,
+ data: dataCollection,
+ resourceKind: CollectionData.COLLECTION,
+ resourcePrefix: GroupContentsResourcePrefix.COLLECTION,
+ resourceKindProperty: CollectionData.STORAGE_CLASSES_CONFIRMED,
+ property: dataCollection.storageClassesConfirmed
+ });
dispatch<any>(initAdvancedTabDialog(advanceDataCollection));
break;
case ResourceKind.PROCESS:
const { data: dataProcess, metadata: metaProcess, user: userProcess } = await dispatch<any>(getDataForAdvancedTab(uuid));
- const advancedDataProcess: AdvancedTabDialogData = advancedTabData(uuid, metaProcess, userProcess, containerRequestApiResponse, dataProcess, ProcessData.CONTAINER_REQUEST, GroupContentsResourcePrefix.PROCESS, ProcessData.OUTPUT_NAME, dataProcess.outputName);
+ const advancedDataProcess: AdvancedTabDialogData = advancedTabData({
+ uuid,
+ metadata: metaProcess,
+ user: userProcess,
+ apiResponseKind: containerRequestApiResponse,
+ data: dataProcess,
+ resourceKind: ProcessData.CONTAINER_REQUEST,
+ resourcePrefix: GroupContentsResourcePrefix.PROCESS,
+ resourceKindProperty: ProcessData.OUTPUT_NAME,
+ property: dataProcess.outputName
+ });
dispatch<any>(initAdvancedTabDialog(advancedDataProcess));
break;
case ResourceKind.PROJECT:
const { data: dataProject, metadata: metaProject, user: userProject } = await dispatch<any>(getDataForAdvancedTab(uuid));
- const advanceDataProject: AdvancedTabDialogData = advancedTabData(uuid, metaProject, userProject, groupRequestApiResponse, dataProject, ProjectData.GROUP, GroupContentsResourcePrefix.PROJECT, ProjectData.DELETE_AT, dataProject.deleteAt);
+ const advanceDataProject: AdvancedTabDialogData = advancedTabData({
+ uuid,
+ metadata: metaProject,
+ user: userProject,
+ apiResponseKind: groupRequestApiResponse,
+ data: dataProject,
+ resourceKind: ProjectData.GROUP,
+ resourcePrefix: GroupContentsResourcePrefix.PROJECT,
+ resourceKindProperty: ProjectData.DELETE_AT,
+ property: dataProject.deleteAt
+ });
dispatch<any>(initAdvancedTabDialog(advanceDataProject));
break;
case ResourceKind.REPOSITORY:
- const dataRepository = getState().repositories.items[index!];
- const advanceDataRepository: AdvancedTabDialogData = advancedTabData(uuid, '', '', repositoryApiResponse, dataRepository, RepositoryData.REPOSITORY, 'repositories', RepositoryData.CREATED_AT, dataRepository.createdAt);
+ const dataRepository = getState().repositories.items.find(it => it.uuid === uuid);
+ const advanceDataRepository: AdvancedTabDialogData = advancedTabData({
+ uuid,
+ metadata: '',
+ user: '',
+ apiResponseKind: repositoryApiResponse,
+ data: dataRepository,
+ resourceKind: RepositoryData.REPOSITORY,
+ resourcePrefix: ResourcePrefix.REPOSITORIES,
+ resourceKindProperty: RepositoryData.CREATED_AT,
+ property: dataRepository!.createdAt
+ });
dispatch<any>(initAdvancedTabDialog(advanceDataRepository));
break;
case ResourceKind.SSH_KEY:
- const dataSshKey = getState().auth.sshKeys[index!];
- const advanceDataSshKey: AdvancedTabDialogData = advancedTabData(uuid, '', '', sshKeyApiResponse, dataSshKey, SshKeyData.SSH_KEY, 'authorized_keys', SshKeyData.CREATED_AT, dataSshKey.createdAt);
+ const dataSshKey = getState().auth.sshKeys.find(it => it.uuid === uuid);
+ const advanceDataSshKey: AdvancedTabDialogData = advancedTabData({
+ uuid,
+ metadata: '',
+ user: '',
+ apiResponseKind: sshKeyApiResponse,
+ data: dataSshKey,
+ resourceKind: SshKeyData.SSH_KEY,
+ resourcePrefix: ResourcePrefix.AUTORIZED_KEYS,
+ resourceKindProperty: SshKeyData.CREATED_AT,
+ property: dataSshKey!.createdAt
+ });
dispatch<any>(initAdvancedTabDialog(advanceDataSshKey));
break;
+ case ResourceKind.VIRTUAL_MACHINE:
+ const dataVirtualMachine = getState().virtualMachines.virtualMachines.items.find(it => it.uuid === uuid);
+ const advanceDataVirtualMachine: AdvancedTabDialogData = advancedTabData({
+ uuid,
+ metadata: '',
+ user: '',
+ apiResponseKind: virtualMachineApiResponse,
+ data: dataVirtualMachine,
+ resourceKind: VirtualMachineData.VIRTUAL_MACHINE,
+ resourcePrefix: ResourcePrefix.VIRTUAL_MACHINES,
+ resourceKindProperty: VirtualMachineData.CREATED_AT,
+ property: dataVirtualMachine.createdAt
+ });
+ dispatch<any>(initAdvancedTabDialog(advanceDataVirtualMachine));
+ break;
case ResourceKind.KEEP_SERVICE:
- const dataKeepService = getState().keepServices[index!];
- const advanceDataKeepService: AdvancedTabDialogData = advancedTabData(uuid, '', '', keepServiceApiResponse, dataKeepService, KeepServiceData.KEEP_SERVICE, 'keep_services', KeepServiceData.CREATED_AT, dataKeepService.createdAt);
+ const dataKeepService = getState().keepServices.find(it => it.uuid === uuid);
+ const advanceDataKeepService: AdvancedTabDialogData = advancedTabData({
+ uuid,
+ metadata: '',
+ user: '',
+ apiResponseKind: keepServiceApiResponse,
+ data: dataKeepService,
+ resourceKind: KeepServiceData.KEEP_SERVICE,
+ resourcePrefix: ResourcePrefix.KEEP_SERVICES,
+ resourceKindProperty: KeepServiceData.CREATED_AT,
+ property: dataKeepService!.createdAt
+ });
dispatch<any>(initAdvancedTabDialog(advanceDataKeepService));
break;
default:
const initAdvancedTabDialog = (data: AdvancedTabDialogData) => dialogActions.OPEN_DIALOG({ id: ADVANCED_TAB_DIALOG, data });
-const advancedTabData = (uuid: string, metadata: any, user: any, apiResponseKind: any, data: any, resourceKind: AdvanceResourceKind,
- resourcePrefix: AdvanceResourcePrefix, resourceKindProperty: AdvanceResourceKind, property: any) => {
+const advancedTabData = (args: {
+ uuid: string, metadata: ListResults<LinkResource> | string, user: UserResource | string, apiResponseKind: any, data: any, resourceKind: AdvanceResourceKind,
+ resourcePrefix: AdvanceResourcePrefix, resourceKindProperty: AdvanceResourceKind, property: any
+}) => {
return {
- uuid,
- user,
- metadata,
- apiResponse: apiResponseKind(data),
- pythonHeader: pythonHeader(resourceKind),
- pythonExample: pythonExample(uuid, resourcePrefix),
- cliGetHeader: cliGetHeader(resourceKind),
- cliGetExample: cliGetExample(uuid, resourceKind),
- cliUpdateHeader: cliUpdateHeader(resourceKind, resourceKindProperty),
- cliUpdateExample: cliUpdateExample(uuid, resourceKind, property, resourceKindProperty),
- curlHeader: curlHeader(resourceKind, resourceKindProperty),
- curlExample: curlExample(uuid, resourcePrefix, property, resourceKind, resourceKindProperty),
+ uuid: args.uuid,
+ user: args.user,
+ metadata: args.metadata,
+ apiResponse: args.apiResponseKind(args.data),
+ pythonHeader: pythonHeader(args.resourceKind),
+ pythonExample: pythonExample(args.uuid, args.resourcePrefix),
+ cliGetHeader: cliGetHeader(args.resourceKind),
+ cliGetExample: cliGetExample(args.uuid, args.resourceKind),
+ cliUpdateHeader: cliUpdateHeader(args.resourceKind, args.resourceKindProperty),
+ cliUpdateExample: cliUpdateExample(args.uuid, args.resourceKind, args.property, args.resourceKindProperty),
+ curlHeader: curlHeader(args.resourceKind, args.resourceKindProperty),
+ curlExample: curlExample(args.uuid, args.resourcePrefix, args.property, args.resourceKind, args.resourceKindProperty),
};
};
return response;
};
+const virtualMachineApiResponse = (apiResponse: VirtualMachinesResource) => {
+ const { uuid, ownerUuid, createdAt, modifiedAt, modifiedByClientUuid, modifiedByUserUuid, hostname } = apiResponse;
+ const response = `"hostname": ${stringify(hostname)},
+"uuid": "${uuid}",
+"owner_uuid": "${ownerUuid}",
+"modified_by_client_uuid": ${stringify(modifiedByClientUuid)},
+"modified_by_user_uuid": ${stringify(modifiedByUserUuid)},
+"modified_at": ${stringify(modifiedAt)},
+"modified_at": ${stringify(modifiedAt)},
+"created_at": "${createdAt}"`;
+
+ return response;
+};
+
const keepServiceApiResponse = (apiResponse: KeepServiceResource) => {
const {
uuid, readOnly, serviceHost, servicePort, serviceSslFlag, serviceType,
export const openPublicKeyDialog = (name: string, publicKey: string) =>
dialogActions.OPEN_DIALOG({ id: SSH_KEY_PUBLIC_KEY_DIALOG, data: { name, publicKey } });
-export const openSshKeyAttributesDialog = (index: number) =>
+export const openSshKeyAttributesDialog = (uuid: string) =>
(dispatch: Dispatch, getState: () => RootState) => {
- const sshKey = getState().auth.sshKeys[index];
+ const sshKey = getState().auth.sshKeys.find(it => it.uuid === uuid);
dispatch(dialogActions.OPEN_DIALOG({ id: SSH_KEY_ATTRIBUTES_DIALOG, data: { sshKey } }));
};
import { Process } from '~/store/processes/process';
import { RepositoryResource } from '~/models/repositories';
import { SshKeyResource } from '~/models/ssh-key';
+import { VirtualMachinesResource } from '~/models/virtual-machines';
import { KeepServiceResource } from '~/models/keep-services';
export const contextMenuActions = unionize({
}));
};
-export const openRepositoryContextMenu = (event: React.MouseEvent<HTMLElement>, index: number, repository: RepositoryResource) =>
+export const openRepositoryContextMenu = (event: React.MouseEvent<HTMLElement>, repository: RepositoryResource) =>
(dispatch: Dispatch, getState: () => RootState) => {
- dispatch<any>(openContextMenu(event, {
- name: '',
- uuid: repository.uuid,
- ownerUuid: repository.ownerUuid,
- kind: ResourceKind.REPOSITORY,
- menuKind: ContextMenuKind.REPOSITORY,
- index
- }));
+ dispatch<any>(openContextMenu(event, {
+ name: '',
+ uuid: repository.uuid,
+ ownerUuid: repository.ownerUuid,
+ kind: ResourceKind.REPOSITORY,
+ menuKind: ContextMenuKind.REPOSITORY
+ }));
+ };
+
+export const openVirtualMachinesContextMenu = (event: React.MouseEvent<HTMLElement>, repository: VirtualMachinesResource) =>
+ (dispatch: Dispatch, getState: () => RootState) => {
+ dispatch<any>(openContextMenu(event, {
+ name: '',
+ uuid: repository.uuid,
+ ownerUuid: repository.ownerUuid,
+ kind: ResourceKind.VIRTUAL_MACHINE,
+ menuKind: ContextMenuKind.VIRTUAL_MACHINE
+ }));
};
-export const openSshKeyContextMenu = (event: React.MouseEvent<HTMLElement>, index: number, sshKey: SshKeyResource) =>
+export const openSshKeyContextMenu = (event: React.MouseEvent<HTMLElement>, sshKey: SshKeyResource) =>
(dispatch: Dispatch) => {
dispatch<any>(openContextMenu(event, {
name: '',
uuid: sshKey.uuid,
ownerUuid: sshKey.ownerUuid,
kind: ResourceKind.SSH_KEY,
- menuKind: ContextMenuKind.SSH_KEY,
- index
+ menuKind: ContextMenuKind.SSH_KEY
}));
};
-export const openKeepServiceContextMenu = (event: React.MouseEvent<HTMLElement>, index: number, keepService: KeepServiceResource) =>
+export const openKeepServiceContextMenu = (event: React.MouseEvent<HTMLElement>, keepService: KeepServiceResource) =>
(dispatch: Dispatch) => {
dispatch<any>(openContextMenu(event, {
name: '',
uuid: keepService.uuid,
ownerUuid: keepService.ownerUuid,
kind: ResourceKind.KEEP_SERVICE,
- menuKind: ContextMenuKind.KEEP_SERVICE,
- index
+ menuKind: ContextMenuKind.KEEP_SERVICE
}));
};
}
};
-export const openKeepServiceAttributesDialog = (index: number) =>
+export const openKeepServiceAttributesDialog = (uuid: string) =>
(dispatch: Dispatch, getState: () => RootState) => {
- const keepService = getState().keepServices[index];
+ const keepService = getState().keepServices.find(it => it.uuid === uuid);
dispatch(dialogActions.OPEN_DIALOG({ id: KEEP_SERVICE_ATTRIBUTES_DIALOG, data: { keepService } }));
};
dispatch(dialogActions.OPEN_DIALOG({ id: REPOSITORIES_SAMPLE_GIT_DIALOG, data: { uuidPrefix } }));
};
-export const openRepositoryAttributes = (index: number) =>
+export const openRepositoryAttributes = (uuid: string) =>
(dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
- const repositoryData = getState().repositories.items[index];
+ const repositoryData = getState().repositories.items.find(it => it.uuid === uuid);
dispatch(dialogActions.OPEN_DIALOG({ id: REPOSITORY_ATTRIBUTES_DIALOG, data: { repositoryData } }));
};
async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Removing ...' }));
await services.repositoriesService.delete(uuid);
- dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Removed.', hideDuration: 2000 }));
+ dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Removed.', hideDuration: 2000, kind: SnackbarKind.SUCCESS }));
dispatch<any>(loadRepositoriesData());
};
import { bindDataExplorerActions } from '~/store/data-explorer/data-explorer-action';
import { formatDate } from "~/common/formatters";
import { unionize, ofType, UnionOf } from "~/common/unionize";
-import { VirtualMachinesLoginsResource } from '~/models/virtual-machines';
+import { VirtualMachineLogins } from '~/models/virtual-machines';
import { FilterBuilder } from "~/services/api/filter-builder";
import { ListResults } from "~/services/common-service/common-resource-service";
+import { dialogActions } from '~/store/dialog/dialog-actions';
+import { snackbarActions, SnackbarKind } from '~/store/snackbar/snackbar-actions';
export const virtualMachinesActions = unionize({
SET_REQUESTED_DATE: ofType<string>(),
SET_VIRTUAL_MACHINES: ofType<ListResults<any>>(),
- SET_LOGINS: ofType<VirtualMachinesLoginsResource[]>(),
+ SET_LOGINS: ofType<VirtualMachineLogins>(),
SET_LINKS: ofType<ListResults<any>>()
});
export type VirtualMachineActions = UnionOf<typeof virtualMachinesActions>;
export const VIRTUAL_MACHINES_PANEL = 'virtualMachinesPanel';
+export const VIRTUAL_MACHINE_ATTRIBUTES_DIALOG = 'virtualMachineAttributesDialog';
+export const VIRTUAL_MACHINE_REMOVE_DIALOG = 'virtualMachineRemoveDialog';
export const openVirtualMachines = () =>
async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
dispatch<any>(navigateToVirtualMachines);
};
+export const openVirtualMachineAttributes = (uuid: string) =>
+ async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ const virtualMachineData = getState().virtualMachines.virtualMachines.items.find(it => it.uuid === uuid);
+ dispatch(dialogActions.OPEN_DIALOG({ id: VIRTUAL_MACHINE_ATTRIBUTES_DIALOG, data: { virtualMachineData } }));
+ };
+
const loadRequestedDate = () =>
(dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
const date = services.virtualMachineService.getRequestedDate();
dispatch(virtualMachinesActions.SET_REQUESTED_DATE(date));
};
-
export const loadVirtualMachinesData = () =>
async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
dispatch<any>(loadRequestedDate());
});
dispatch(virtualMachinesActions.SET_VIRTUAL_MACHINES(virtualMachines));
dispatch(virtualMachinesActions.SET_LINKS(links));
+ const getAllLogins = await services.virtualMachineService.getAllLogins();
+ dispatch(virtualMachinesActions.SET_LOGINS(getAllLogins));
};
export const saveRequestedDate = () =>
dispatch<any>(loadRequestedDate());
};
+export const openRemoveVirtualMachineDialog = (uuid: string) =>
+ (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ dispatch(dialogActions.OPEN_DIALOG({
+ id: VIRTUAL_MACHINE_REMOVE_DIALOG,
+ data: {
+ title: 'Remove virtual machine',
+ text: 'Are you sure you want to remove this virtual machine?',
+ confirmButtonLabel: 'Remove',
+ uuid
+ }
+ }));
+ };
+
+export const removeVirtualMachine = (uuid: string) =>
+ async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Removing ...' }));
+ await services.virtualMachineService.delete(uuid);
+ dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Removed.', hideDuration: 2000, kind: SnackbarKind.SUCCESS }));
+ dispatch<any>(loadVirtualMachinesData());
+ };
+
const virtualMachinesBindedActions = bindDataExplorerActions(VIRTUAL_MACHINES_PANEL);
export const loadVirtualMachinesPanel = () =>
import { virtualMachinesActions, VirtualMachineActions } from '~/store/virtual-machines/virtual-machines-actions';
import { ListResults } from '~/services/common-service/common-resource-service';
-import { VirtualMachinesLoginsResource } from '~/models/virtual-machines';
+import { VirtualMachineLogins } from '~/models/virtual-machines';
interface VirtualMachines {
date: string;
virtualMachines: ListResults<any>;
- logins: VirtualMachinesLoginsResource[];
+ logins: VirtualMachineLogins;
links: ListResults<any>;
}
itemsAvailable: 0,
items: []
},
- logins: [],
+ logins: {
+ kind: '',
+ items: []
+ },
links: {
kind: '',
offset: 0,
export const keepServiceActionSet: ContextMenuActionSet = [[{
name: "Attributes",
icon: AttributesIcon,
- execute: (dispatch, { index }) => {
- dispatch<any>(openKeepServiceAttributesDialog(index!));
+ execute: (dispatch, { uuid }) => {
+ dispatch<any>(openKeepServiceAttributesDialog(uuid));
}
}, {
name: "Advanced",
icon: AdvancedIcon,
- execute: (dispatch, { uuid, index }) => {
- dispatch<any>(openAdvancedTabDialog(uuid, index));
+ execute: (dispatch, { uuid }) => {
+ dispatch<any>(openAdvancedTabDialog(uuid));
}
}, {
name: "Remove",
export const repositoryActionSet: ContextMenuActionSet = [[{
name: "Attributes",
icon: AttributesIcon,
- execute: (dispatch, { index }) => {
- dispatch<any>(openRepositoryAttributes(index!));
+ execute: (dispatch, { uuid }) => {
+ dispatch<any>(openRepositoryAttributes(uuid));
}
}, {
name: "Share",
name: "Advanced",
icon: AdvancedIcon,
execute: (dispatch, resource) => {
- dispatch<any>(openAdvancedTabDialog(resource.uuid, resource.index));
+ dispatch<any>(openAdvancedTabDialog(resource.uuid));
}
}, {
name: "Remove",
export const sshKeyActionSet: ContextMenuActionSet = [[{
name: "Attributes",
icon: AttributesIcon,
- execute: (dispatch, { index }) => {
- dispatch<any>(openSshKeyAttributesDialog(index!));
+ execute: (dispatch, { uuid }) => {
+ dispatch<any>(openSshKeyAttributesDialog(uuid));
}
}, {
name: "Advanced",
icon: AdvancedIcon,
- execute: (dispatch, { uuid, index }) => {
- dispatch<any>(openAdvancedTabDialog(uuid, index));
+ execute: (dispatch, { uuid }) => {
+ dispatch<any>(openAdvancedTabDialog(uuid));
}
}, {
name: "Remove",
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { ContextMenuActionSet } from "~/views-components/context-menu/context-menu-action-set";
+import { AdvancedIcon, RemoveIcon, AttributesIcon } from "~/components/icon/icon";
+import { openAdvancedTabDialog } from '~/store/advanced-tab/advanced-tab';
+import { openVirtualMachineAttributes, openRemoveVirtualMachineDialog } from "~/store/virtual-machines/virtual-machines-actions";
+
+export const virtualMachineActionSet: ContextMenuActionSet = [[{
+ name: "Attributes",
+ icon: AttributesIcon,
+ execute: (dispatch, { uuid }) => {
+ dispatch<any>(openVirtualMachineAttributes(uuid));
+ }
+}, {
+ name: "Advanced",
+ icon: AdvancedIcon,
+ execute: (dispatch, { uuid }) => {
+ dispatch<any>(openAdvancedTabDialog(uuid));
+ }
+}, {
+ name: "Remove",
+ icon: RemoveIcon,
+ execute: (dispatch, { uuid }) => {
+ dispatch<any>(openRemoveVirtualMachineDialog(uuid));
+ }
+}]];
PROCESS_LOGS = "ProcessLogs",
REPOSITORY = "Repository",
SSH_KEY = "SshKey",
+ VIRTUAL_MACHINE = "VirtualMachine",
KEEP_SERVICE = "KeepService"
}
// 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 { removeRepository, REPOSITORY_REMOVE_DIALOG } from '~/store/repositories/repositories-actions';
- const mapDispatchToProps = (dispatch: Dispatch, props: WithDialogProps<any>) => ({
+const mapDispatchToProps = (dispatch: Dispatch, props: WithDialogProps<any>) => ({
onConfirm: () => {
props.closeDialog();
dispatch<any>(removeRepository(props.data.uuid));
}
});
- export const RemoveRepositoryDialog = compose(
+export const RemoveRepositoryDialog = compose(
withDialog(REPOSITORY_REMOVE_DIALOG),
connect(null, mapDispatchToProps)
)(ConfirmationDialog);
\ No newline at end of file
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from "react";
+import { Dialog, DialogTitle, DialogContent, DialogActions, Button, Typography, Grid } from "@material-ui/core";
+import { WithDialogProps } from "~/store/dialog/with-dialog";
+import { withDialog } from '~/store/dialog/with-dialog';
+import { VIRTUAL_MACHINE_ATTRIBUTES_DIALOG } from "~/store/virtual-machines/virtual-machines-actions";
+import { WithStyles, withStyles } from '@material-ui/core/styles';
+import { ArvadosTheme } from '~/common/custom-theme';
+import { compose } from "redux";
+import { VirtualMachinesResource } from "~/models/virtual-machines";
+
+type CssRules = 'rightContainer' | 'leftContainer' | 'spacing';
+
+const styles = withStyles<CssRules>((theme: ArvadosTheme) => ({
+ rightContainer: {
+ textAlign: 'right',
+ paddingRight: theme.spacing.unit * 2,
+ color: theme.palette.grey["500"]
+ },
+ leftContainer: {
+ textAlign: 'left',
+ paddingLeft: theme.spacing.unit * 2
+ },
+ spacing: {
+ paddingTop: theme.spacing.unit * 2
+ },
+}));
+
+interface VirtualMachineAttributesDataProps {
+ virtualMachineData: VirtualMachinesResource;
+}
+
+type VirtualMachineAttributesProps = VirtualMachineAttributesDataProps & WithStyles<CssRules>;
+
+export const VirtualMachineAttributesDialog = compose(
+ withDialog(VIRTUAL_MACHINE_ATTRIBUTES_DIALOG),
+ styles)(
+ (props: WithDialogProps<VirtualMachineAttributesProps> & VirtualMachineAttributesProps) =>
+ <Dialog open={props.open}
+ onClose={props.closeDialog}
+ fullWidth
+ maxWidth="sm">
+ <DialogTitle>Attributes</DialogTitle>
+ <DialogContent>
+ <Typography variant="body2" className={props.classes.spacing}>
+ {props.data.virtualMachineData && attributes(props.data.virtualMachineData, props.classes)}
+ </Typography>
+ </DialogContent>
+ <DialogActions>
+ <Button
+ variant='flat'
+ color='primary'
+ onClick={props.closeDialog}>
+ Close
+ </Button>
+ </DialogActions>
+ </Dialog>
+ );
+
+const attributes = (virtualMachine: VirtualMachinesResource, classes: any) => {
+ const { uuid, ownerUuid, createdAt, modifiedAt, modifiedByClientUuid, modifiedByUserUuid, hostname } = virtualMachine;
+ return (
+ <span>
+ <Grid container direction="row">
+ <Grid item xs={5} className={classes.rightContainer}>
+ <Grid item>Hostname</Grid>
+ <Grid item>Owner uuid</Grid>
+ <Grid item>Created at</Grid>
+ <Grid item>Modified at</Grid>
+ <Grid item>Modified by user uuid</Grid>
+ <Grid item>Modified by client uuid</Grid>
+ <Grid item>uuid</Grid>
+ </Grid>
+ <Grid item xs={7} className={classes.leftContainer}>
+ <Grid item>{hostname}</Grid>
+ <Grid item>{ownerUuid}</Grid>
+ <Grid item>{createdAt}</Grid>
+ <Grid item>{modifiedAt}</Grid>
+ <Grid item>{modifiedByUserUuid}</Grid>
+ <Grid item>{modifiedByClientUuid}</Grid>
+ <Grid item>{uuid}</Grid>
+ </Grid>
+ </Grid>
+ </span>
+ );
+};
--- /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 { VIRTUAL_MACHINE_REMOVE_DIALOG, removeVirtualMachine } from '~/store/virtual-machines/virtual-machines-actions';
+
+const mapDispatchToProps = (dispatch: Dispatch, props: WithDialogProps<any>) => ({
+ onConfirm: () => {
+ props.closeDialog();
+ dispatch<any>(removeVirtualMachine(props.data.uuid));
+ }
+});
+
+export const RemoveVirtualMachineDialog = compose(
+ withDialog(VIRTUAL_MACHINE_REMOVE_DIALOG),
+ connect(null, mapDispatchToProps)
+)(ConfirmationDialog);
\ No newline at end of file
});
export interface KeepServicePanelRootActionProps {
- openRowOptions: (event: React.MouseEvent<HTMLElement>, index: number, keepService: KeepServiceResource) => void;
+ openRowOptions: (event: React.MouseEvent<HTMLElement>, keepService: KeepServiceResource) => void;
}
export interface KeepServicePanelRootDataProps {
<TableCell>{keepService.serviceType}</TableCell>
<TableCell>
<Tooltip title="More options" disableFocusListener>
- <IconButton onClick={event => openRowOptions(event, index, keepService)}>
+ <IconButton onClick={event => openRowOptions(event, keepService)}>
<MoreOptionsIcon />
</IconButton>
</Tooltip>
};
const mapDispatchToProps = (dispatch: Dispatch): KeepServicePanelRootActionProps => ({
- openRowOptions: (event, index, keepService) => {
- dispatch<any>(openKeepServiceContextMenu(event, index, keepService));
+ openRowOptions: (event, keepService) => {
+ dispatch<any>(openKeepServiceContextMenu(event, keepService));
}
});
const mapDispatchToProps = (dispatch: Dispatch): Pick<RepositoriesActionProps, 'onOptionsMenuOpen' | 'loadRepositories' | 'openRepositoriesSampleGitDialog' | 'openRepositoryCreateDialog'> => ({
loadRepositories: () => dispatch<any>(loadRepositoriesData()),
- onOptionsMenuOpen: (event, index, repository) => {
- dispatch<any>(openRepositoryContextMenu(event, index, repository));
+ onOptionsMenuOpen: (event, repository) => {
+ dispatch<any>(openRepositoryContextMenu(event, repository));
},
openRepositoriesSampleGitDialog: () => dispatch<any>(openRepositoriesSampleGitDialog()),
openRepositoryCreateDialog: () => dispatch<any>(openRepositoryCreateDialog())
interface RepositoriesActionProps {
loadRepositories: () => void;
- onOptionsMenuOpen: (event: React.MouseEvent<HTMLElement>, index: number, repository: RepositoryResource) => void;
+ onOptionsMenuOpen: (event: React.MouseEvent<HTMLElement>, repository: RepositoryResource) => void;
openRepositoriesSampleGitDialog: () => void;
openRepositoryCreateDialog: () => void;
}
<TableCell className={classes.cloneUrls}>{repository.cloneUrls.join("\n")}</TableCell>
<TableCell className={classes.moreOptions}>
<Tooltip title="More options" disableFocusListener>
- <IconButton onClick={event => onOptionsMenuOpen(event, index, repository)} className={classes.moreOptionsButton}>
+ <IconButton onClick={event => onOptionsMenuOpen(event, repository)} className={classes.moreOptionsButton}>
<MoreOptionsIcon />
</IconButton>
</Tooltip>
export interface SshKeyPanelRootActionProps {
openSshKeyCreateDialog: () => void;
- openRowOptions: (event: React.MouseEvent<HTMLElement>, index: number, sshKey: SshKeyResource) => void;
+ openRowOptions: (event: React.MouseEvent<HTMLElement>, sshKey: SshKeyResource) => void;
openPublicKeyDialog: (name: string, publicKey: string) => void;
}
</TableCell>
<TableCell>
<Tooltip title="More options" disableFocusListener>
- <IconButton onClick={event => openRowOptions(event, index, sshKey)}>
+ <IconButton onClick={event => openRowOptions(event, sshKey)}>
<MoreOptionsIcon />
</IconButton>
</Tooltip>
openSshKeyCreateDialog: () => {
dispatch<any>(openSshKeyCreateDialog());
},
- openRowOptions: (event, index, sshKey) => {
- dispatch<any>(openSshKeyContextMenu(event, index, sshKey));
+ openRowOptions: (event, sshKey) => {
+ dispatch<any>(openSshKeyContextMenu(event, sshKey));
},
openPublicKeyDialog: (name: string, publicKey: string) => {
dispatch<any>(openPublicKeyDialog(name, publicKey));
import * as React from 'react';
import { connect } from 'react-redux';
-import { Grid, Typography, Button, Card, CardContent, TableBody, TableCell, TableHead, TableRow, Table, Tooltip } from '@material-ui/core';
+import { Grid, Typography, Button, Card, CardContent, TableBody, TableCell, TableHead, TableRow, Table, Tooltip, IconButton } from '@material-ui/core';
import { StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core/styles';
import { ArvadosTheme } from '~/common/custom-theme';
import { DefaultCodeSnippet } from '~/components/default-code-snippet/default-code-snippet';
import { Link } from 'react-router-dom';
-import { Dispatch, compose } from 'redux';
+import { compose, Dispatch } from 'redux';
import { saveRequestedDate, loadVirtualMachinesData } from '~/store/virtual-machines/virtual-machines-actions';
import { RootState } from '~/store/store';
import { ListResults } from '~/services/common-service/common-resource-service';
-import { HelpIcon } from '~/components/icon/icon';
-import { VirtualMachinesLoginsResource, VirtualMachinesResource } from '~/models/virtual-machines';
+import { HelpIcon, MoreOptionsIcon } from '~/components/icon/icon';
+import { VirtualMachineLogins, VirtualMachinesResource } from '~/models/virtual-machines';
import { Routes } from '~/routes/routes';
+import { openVirtualMachinesContextMenu } from '~/store/context-menu/context-menu-actions';
-type CssRules = 'button' | 'codeSnippet' | 'link' | 'linkIcon' | 'rightAlign' | 'cardWithoutMachines' | 'icon';
+type CssRules = 'button' | 'codeSnippet' | 'link' | 'linkIcon' | 'rightAlign' | 'cardWithoutMachines' | 'icon' | 'moreOptionsButton' | 'moreOptions';
const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
button: {
icon: {
textAlign: "right",
marginTop: theme.spacing.unit
- }
+ },
+ moreOptionsButton: {
+ padding: 0
+ },
+ moreOptions: {
+ textAlign: 'right',
+ '&:last-child': {
+ paddingRight: 0
+ }
+ },
});
-const mapStateToProps = ({ virtualMachines }: RootState) => {
+const mapStateToProps = ({ virtualMachines, auth }: RootState) => {
return {
requestedDate: virtualMachines.date,
+ isAdmin: auth.user!.isAdmin,
+ logins: virtualMachines.logins,
...virtualMachines
};
};
-const mapDispatchToProps = {
- saveRequestedDate,
- loadVirtualMachinesData
-};
+const mapDispatchToProps = (dispatch: Dispatch): Pick<VirtualMachinesPanelActionProps, 'loadVirtualMachinesData' | 'saveRequestedDate' | 'onOptionsMenuOpen'> => ({
+ saveRequestedDate: () => dispatch<any>(saveRequestedDate()),
+ loadVirtualMachinesData: () => dispatch<any>(loadVirtualMachinesData()),
+ onOptionsMenuOpen: (event, virtualMachine) => {
+ dispatch<any>(openVirtualMachinesContextMenu(event, virtualMachine));
+ },
+});
interface VirtualMachinesPanelDataProps {
requestedDate: string;
virtualMachines: ListResults<any>;
- logins: VirtualMachinesLoginsResource[];
+ logins: VirtualMachineLogins;
links: ListResults<any>;
+ isAdmin: boolean;
}
interface VirtualMachinesPanelActionProps {
saveRequestedDate: () => void;
loadVirtualMachinesData: () => string;
+ onOptionsMenuOpen: (event: React.MouseEvent<HTMLElement>, virtualMachine: VirtualMachinesResource) => void;
}
type VirtualMachineProps = VirtualMachinesPanelActionProps & VirtualMachinesPanelDataProps & WithStyles<CssRules>;
}
render() {
- const { virtualMachines, links } = this.props;
+ const { virtualMachines, links, isAdmin } = this.props;
return (
<Grid container spacing={16}>
- {virtualMachines.itemsAvailable === 0 && <CardContentWithNoVirtualMachines {...this.props} />}
+ {!isAdmin && virtualMachines.itemsAvailable > 0 && <CardContentWithNoVirtualMachines {...this.props} />}
{virtualMachines.itemsAvailable > 0 && links.itemsAvailable > 0 && <CardContentWithVirtualMachines {...this.props} />}
- {<CardSSHSection {...this.props} />}
+ {!isAdmin && <CardSSHSection {...this.props} />}
</Grid>
);
}
<Grid item xs={12}>
<Card>
<CardContent>
- <div className={props.classes.rightAlign}>
- <Button variant="contained" color="primary" className={props.classes.button} onClick={props.saveRequestedDate}>
- SEND REQUEST FOR SHELL ACCESS
- </Button>
- {props.requestedDate &&
- <Typography variant="body1">
- A request for shell access was sent on {props.requestedDate}
- </Typography>}
- </div>
- <div className={props.classes.icon}>
- <a href="https://doc.arvados.org/user/getting_started/vm-login-with-webshell.html" target="_blank" className={props.classes.linkIcon}>
- <Tooltip title="Access VM using webshell">
- <HelpIcon />
- </Tooltip>
- </a>
- </div>
- <Table>
- <TableHead>
- <TableRow>
- <TableCell>Host name</TableCell>
- <TableCell>Login name</TableCell>
- <TableCell>Command line</TableCell>
- <TableCell>Web shell</TableCell>
- </TableRow>
- </TableHead>
- <TableBody>
- {props.virtualMachines.items.map((it, index) =>
- <TableRow key={index}>
- <TableCell>{it.hostname}</TableCell>
- <TableCell>{getUsername(props.links, it)}</TableCell>
- <TableCell>ssh {getUsername(props.links, it)}@shell.arvados</TableCell>
- <TableCell>
- <a href={`https://workbench.c97qk.arvadosapi.com${it.href}/webshell/${getUsername(props.links, it)}`} target="_blank" className={props.classes.link}>
- Log in as {getUsername(props.links, it)}
- </a>
- </TableCell>
- </TableRow>
- )}
- </TableBody>
- </Table>
+ {props.isAdmin ? <span>{adminVirtualMachinesTable(props)}</span>
+ : <span>
+ <div className={props.classes.rightAlign}>
+ <Button variant="contained" color="primary" className={props.classes.button} onClick={props.saveRequestedDate}>
+ SEND REQUEST FOR SHELL ACCESS
+ </Button>
+ {props.requestedDate &&
+ <Typography variant="body1">
+ A request for shell access was sent on {props.requestedDate}
+ </Typography>}
+ </div>
+ <div className={props.classes.icon}>
+ <a href="https://doc.arvados.org/user/getting_started/vm-login-with-webshell.html" target="_blank" className={props.classes.linkIcon}>
+ <Tooltip title="Access VM using webshell">
+ <HelpIcon />
+ </Tooltip>
+ </a>
+ </div>
+ {userVirtualMachinesTable(props)}
+ </span>
+ }
</CardContent>
</Card>
</Grid>;
-const getUsername = (links: ListResults<any>, virtualMachine: VirtualMachinesResource) => {
- const link = links.items.find((item: any) => item.headUuid === virtualMachine.uuid);
- return link.properties.username || undefined;
+const userVirtualMachinesTable = (props: VirtualMachineProps) =>
+ <Table>
+ <TableHead>
+ <TableRow>
+ <TableCell>Host name</TableCell>
+ <TableCell>Login name</TableCell>
+ <TableCell>Command line</TableCell>
+ <TableCell>Web shell</TableCell>
+ </TableRow>
+ </TableHead>
+ <TableBody>
+ {props.virtualMachines.items.map((it, index) =>
+ <TableRow key={index}>
+ <TableCell>{it.hostname}</TableCell>
+ <TableCell>{getUsername(props.links)}</TableCell>
+ <TableCell>ssh {getUsername(props.links)}@{it.hostname}.arvados</TableCell>
+ <TableCell>
+ <a href={`https://workbench.c97qk.arvadosapi.com${it.href}/webshell/${getUsername(props.links)}`} target="_blank" className={props.classes.link}>
+ Log in as {getUsername(props.links)}
+ </a>
+ </TableCell>
+ </TableRow>
+ )}
+ </TableBody>
+ </Table>;
+
+const adminVirtualMachinesTable = (props: VirtualMachineProps) =>
+ <Table>
+ <TableHead>
+ <TableRow>
+ <TableCell>Uuid</TableCell>
+ <TableCell>Host name</TableCell>
+ <TableCell>Logins</TableCell>
+ <TableCell />
+ </TableRow>
+ </TableHead>
+ <TableBody>
+ {props.logins.items.length > 0 && props.virtualMachines.items.map((it, index) =>
+ <TableRow key={index}>
+ <TableCell>{it.uuid}</TableCell>
+ <TableCell>{it.hostname}</TableCell>
+ <TableCell>["{props.logins.items[0].username}"]</TableCell>
+ <TableCell className={props.classes.moreOptions}>
+ <Tooltip title="More options" disableFocusListener>
+ <IconButton onClick={event => props.onOptionsMenuOpen(event, it)} className={props.classes.moreOptionsButton}>
+ <MoreOptionsIcon />
+ </IconButton>
+ </Tooltip>
+ </TableCell>
+ </TableRow>
+ )}
+ </TableBody>
+ </Table>;
+
+const getUsername = (links: ListResults<any>) => {
+ return links.items[0].properties.username;
};
const CardSSHSection = (props: VirtualMachineProps) =>
import { RemoveSshKeyDialog } from '~/views-components/ssh-keys-dialog/remove-dialog';
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';
type CssRules = 'root' | 'container' | 'splitter' | 'asidePanel' | 'contentWrapper' | 'content';
<RemoveProcessDialog />
<RemoveRepositoryDialog />
<RemoveSshKeyDialog />
+ <RemoveVirtualMachineDialog />
<RenameFileDialog />
<RepositoryAttributesDialog />
<RepositoriesSampleGitDialog />
<UpdateCollectionDialog />
<UpdateProcessDialog />
<UpdateProjectDialog />
+ <VirtualMachineAttributesDialog />
</Grid>
);
\ No newline at end of file