From: Pawel Kowalczyk Date: Mon, 3 Dec 2018 12:32:53 +0000 (+0100) Subject: conflicts X-Git-Tag: 1.4.0~97^2~2 X-Git-Url: https://git.arvados.org/arvados-workbench2.git/commitdiff_plain/cc72c29b709759a4498ad232e3f0374e857c7a62?hp=8695e9d621330e691fbcc4bbc1d600d91bae2ac6 conflicts Feature #14498 Arvados-DCO-1.1-Signed-off-by: Pawel Kowalczyk --- diff --git a/src/index.tsx b/src/index.tsx index 79525a8a..d8385967 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -52,6 +52,7 @@ import { repositoryActionSet } from '~/views-components/context-menu/action-sets 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()}]`); @@ -70,6 +71,7 @@ addMenuActionSet(ContextMenuKind.PROCESS_RESOURCE, processResourceActionSet); addMenuActionSet(ContextMenuKind.TRASH, trashActionSet); addMenuActionSet(ContextMenuKind.REPOSITORY, repositoryActionSet); addMenuActionSet(ContextMenuKind.SSH_KEY, sshKeyActionSet); +addMenuActionSet(ContextMenuKind.VIRTUAL_MACHINE, virtualMachineActionSet); addMenuActionSet(ContextMenuKind.KEEP_SERVICE, keepServiceActionSet); fetchConfig() diff --git a/src/models/virtual-machines.ts b/src/models/virtual-machines.ts index 0652c350..85d0a565 100644 --- a/src/models/virtual-machines.ts +++ b/src/models/virtual-machines.ts @@ -8,11 +8,16 @@ export interface VirtualMachinesResource extends Resource { 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 diff --git a/src/services/auth-service/auth-service.ts b/src/services/auth-service/auth-service.ts index 69e3a79d..98c03215 100644 --- a/src/services/auth-service/auth-service.ts +++ b/src/services/auth-service/auth-service.ts @@ -4,7 +4,7 @@ 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'; @@ -61,7 +61,7 @@ export class AuthService { 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 } diff --git a/src/store/advanced-tab/advanced-tab.ts b/src/store/advanced-tab/advanced-tab.ts index c48011d2..d9dabe5c 100644 --- a/src/store/advanced-tab/advanced-tab.ts +++ b/src/store/advanced-tab/advanced-tab.ts @@ -16,14 +16,18 @@ import { ServiceRepository } from '~/services/services'; 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 | string; + user: UserResource | string; pythonHeader: string; pythonExample: string; cliGetHeader: string; @@ -55,50 +59,137 @@ enum RepositoryData { } 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, getState: () => RootState, services: ServiceRepository) => { const kind = extractUuidKind(uuid); switch (kind) { case ResourceKind.COLLECTION: const { data: dataCollection, metadata: metaCollection, user: userCollection } = await dispatch(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(initAdvancedTabDialog(advanceDataCollection)); break; case ResourceKind.PROCESS: const { data: dataProcess, metadata: metaProcess, user: userProcess } = await dispatch(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(initAdvancedTabDialog(advancedDataProcess)); break; case ResourceKind.PROJECT: const { data: dataProject, metadata: metaProject, user: userProject } = await dispatch(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(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(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(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(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(initAdvancedTabDialog(advanceDataKeepService)); break; default: @@ -121,21 +212,23 @@ const getDataForAdvancedTab = (uuid: string) => 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 | 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), }; }; @@ -306,6 +399,20 @@ const sshKeyApiResponse = (apiResponse: SshKeyResource) => { 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, diff --git a/src/store/auth/auth-action.ts b/src/store/auth/auth-action.ts index 28559b1a..e1b36f82 100644 --- a/src/store/auth/auth-action.ts +++ b/src/store/auth/auth-action.ts @@ -94,9 +94,9 @@ export const openSshKeyCreateDialog = () => dialogActions.OPEN_DIALOG({ id: SSH_ 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 } })); }; diff --git a/src/store/context-menu/context-menu-actions.ts b/src/store/context-menu/context-menu-actions.ts index 2c533d66..d56a3fb5 100644 --- a/src/store/context-menu/context-menu-actions.ts +++ b/src/store/context-menu/context-menu-actions.ts @@ -15,6 +15,7 @@ import { extractUuidKind, ResourceKind } from '~/models/resource'; 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({ @@ -64,39 +65,47 @@ export const openCollectionFilesContextMenu = (event: React.MouseEvent, index: number, repository: RepositoryResource) => +export const openRepositoryContextMenu = (event: React.MouseEvent, repository: RepositoryResource) => (dispatch: Dispatch, getState: () => RootState) => { - dispatch(openContextMenu(event, { - name: '', - uuid: repository.uuid, - ownerUuid: repository.ownerUuid, - kind: ResourceKind.REPOSITORY, - menuKind: ContextMenuKind.REPOSITORY, - index - })); + dispatch(openContextMenu(event, { + name: '', + uuid: repository.uuid, + ownerUuid: repository.ownerUuid, + kind: ResourceKind.REPOSITORY, + menuKind: ContextMenuKind.REPOSITORY + })); + }; + +export const openVirtualMachinesContextMenu = (event: React.MouseEvent, repository: VirtualMachinesResource) => + (dispatch: Dispatch, getState: () => RootState) => { + dispatch(openContextMenu(event, { + name: '', + uuid: repository.uuid, + ownerUuid: repository.ownerUuid, + kind: ResourceKind.VIRTUAL_MACHINE, + menuKind: ContextMenuKind.VIRTUAL_MACHINE + })); }; -export const openSshKeyContextMenu = (event: React.MouseEvent, index: number, sshKey: SshKeyResource) => +export const openSshKeyContextMenu = (event: React.MouseEvent, sshKey: SshKeyResource) => (dispatch: Dispatch) => { dispatch(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, index: number, keepService: KeepServiceResource) => +export const openKeepServiceContextMenu = (event: React.MouseEvent, keepService: KeepServiceResource) => (dispatch: Dispatch) => { dispatch(openContextMenu(event, { name: '', uuid: keepService.uuid, ownerUuid: keepService.ownerUuid, kind: ResourceKind.KEEP_SERVICE, - menuKind: ContextMenuKind.KEEP_SERVICE, - index + menuKind: ContextMenuKind.KEEP_SERVICE })); }; diff --git a/src/store/keep-services/keep-services-actions.ts b/src/store/keep-services/keep-services-actions.ts index 1de6802e..54a7c3fe 100644 --- a/src/store/keep-services/keep-services-actions.ts +++ b/src/store/keep-services/keep-services-actions.ts @@ -39,9 +39,9 @@ export const loadKeepServicesPanel = () => } }; -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 } })); }; diff --git a/src/store/repositories/repositories-actions.ts b/src/store/repositories/repositories-actions.ts index a672738f..61caa769 100644 --- a/src/store/repositories/repositories-actions.ts +++ b/src/store/repositories/repositories-actions.ts @@ -32,9 +32,9 @@ export const openRepositoriesSampleGitDialog = () => 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 } })); }; @@ -84,7 +84,7 @@ export const removeRepository = (uuid: string) => 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(loadRepositoriesData()); }; diff --git a/src/store/virtual-machines/virtual-machines-actions.ts b/src/store/virtual-machines/virtual-machines-actions.ts index 9bd79884..c95277b3 100644 --- a/src/store/virtual-machines/virtual-machines-actions.ts +++ b/src/store/virtual-machines/virtual-machines-actions.ts @@ -9,33 +9,42 @@ import { navigateToVirtualMachines } from "../navigation/navigation-action"; 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(), SET_VIRTUAL_MACHINES: ofType>(), - SET_LOGINS: ofType(), + SET_LOGINS: ofType(), SET_LINKS: ofType>() }); export type VirtualMachineActions = UnionOf; 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(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(loadRequestedDate()); @@ -48,6 +57,8 @@ export const loadVirtualMachinesData = () => }); 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 = () => @@ -57,6 +68,27 @@ export const saveRequestedDate = () => dispatch(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(loadVirtualMachinesData()); + }; + const virtualMachinesBindedActions = bindDataExplorerActions(VIRTUAL_MACHINES_PANEL); export const loadVirtualMachinesPanel = () => diff --git a/src/store/virtual-machines/virtual-machines-reducer.ts b/src/store/virtual-machines/virtual-machines-reducer.ts index fa28417e..475ad752 100644 --- a/src/store/virtual-machines/virtual-machines-reducer.ts +++ b/src/store/virtual-machines/virtual-machines-reducer.ts @@ -4,12 +4,12 @@ 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; - logins: VirtualMachinesLoginsResource[]; + logins: VirtualMachineLogins; links: ListResults; } @@ -22,7 +22,10 @@ const initialState: VirtualMachines = { itemsAvailable: 0, items: [] }, - logins: [], + logins: { + kind: '', + items: [] + }, links: { kind: '', offset: 0, diff --git a/src/views-components/context-menu/action-sets/keep-service-action-set.ts b/src/views-components/context-menu/action-sets/keep-service-action-set.ts index 5d27e4e9..807a3abf 100644 --- a/src/views-components/context-menu/action-sets/keep-service-action-set.ts +++ b/src/views-components/context-menu/action-sets/keep-service-action-set.ts @@ -10,14 +10,14 @@ import { AdvancedIcon, RemoveIcon, AttributesIcon } from "~/components/icon/icon export const keepServiceActionSet: ContextMenuActionSet = [[{ name: "Attributes", icon: AttributesIcon, - execute: (dispatch, { index }) => { - dispatch(openKeepServiceAttributesDialog(index!)); + execute: (dispatch, { uuid }) => { + dispatch(openKeepServiceAttributesDialog(uuid)); } }, { name: "Advanced", icon: AdvancedIcon, - execute: (dispatch, { uuid, index }) => { - dispatch(openAdvancedTabDialog(uuid, index)); + execute: (dispatch, { uuid }) => { + dispatch(openAdvancedTabDialog(uuid)); } }, { name: "Remove", diff --git a/src/views-components/context-menu/action-sets/repository-action-set.ts b/src/views-components/context-menu/action-sets/repository-action-set.ts index 22f6bee1..82c2f2b8 100644 --- a/src/views-components/context-menu/action-sets/repository-action-set.ts +++ b/src/views-components/context-menu/action-sets/repository-action-set.ts @@ -11,8 +11,8 @@ import { openSharingDialog } from "~/store/sharing-dialog/sharing-dialog-actions export const repositoryActionSet: ContextMenuActionSet = [[{ name: "Attributes", icon: AttributesIcon, - execute: (dispatch, { index }) => { - dispatch(openRepositoryAttributes(index!)); + execute: (dispatch, { uuid }) => { + dispatch(openRepositoryAttributes(uuid)); } }, { name: "Share", @@ -24,7 +24,7 @@ export const repositoryActionSet: ContextMenuActionSet = [[{ name: "Advanced", icon: AdvancedIcon, execute: (dispatch, resource) => { - dispatch(openAdvancedTabDialog(resource.uuid, resource.index)); + dispatch(openAdvancedTabDialog(resource.uuid)); } }, { name: "Remove", diff --git a/src/views-components/context-menu/action-sets/ssh-key-action-set.ts b/src/views-components/context-menu/action-sets/ssh-key-action-set.ts index 6e86b2bc..0ce0c431 100644 --- a/src/views-components/context-menu/action-sets/ssh-key-action-set.ts +++ b/src/views-components/context-menu/action-sets/ssh-key-action-set.ts @@ -10,14 +10,14 @@ import { openAdvancedTabDialog } from '~/store/advanced-tab/advanced-tab'; export const sshKeyActionSet: ContextMenuActionSet = [[{ name: "Attributes", icon: AttributesIcon, - execute: (dispatch, { index }) => { - dispatch(openSshKeyAttributesDialog(index!)); + execute: (dispatch, { uuid }) => { + dispatch(openSshKeyAttributesDialog(uuid)); } }, { name: "Advanced", icon: AdvancedIcon, - execute: (dispatch, { uuid, index }) => { - dispatch(openAdvancedTabDialog(uuid, index)); + execute: (dispatch, { uuid }) => { + dispatch(openAdvancedTabDialog(uuid)); } }, { name: "Remove", diff --git a/src/views-components/context-menu/action-sets/virtual-machine-action-set.ts b/src/views-components/context-menu/action-sets/virtual-machine-action-set.ts new file mode 100644 index 00000000..ea274af1 --- /dev/null +++ b/src/views-components/context-menu/action-sets/virtual-machine-action-set.ts @@ -0,0 +1,28 @@ +// 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(openVirtualMachineAttributes(uuid)); + } +}, { + name: "Advanced", + icon: AdvancedIcon, + execute: (dispatch, { uuid }) => { + dispatch(openAdvancedTabDialog(uuid)); + } +}, { + name: "Remove", + icon: RemoveIcon, + execute: (dispatch, { uuid }) => { + dispatch(openRemoveVirtualMachineDialog(uuid)); + } +}]]; diff --git a/src/views-components/context-menu/context-menu.tsx b/src/views-components/context-menu/context-menu.tsx index 211881ca..5f321bfe 100644 --- a/src/views-components/context-menu/context-menu.tsx +++ b/src/views-components/context-menu/context-menu.tsx @@ -71,5 +71,6 @@ export enum ContextMenuKind { PROCESS_LOGS = "ProcessLogs", REPOSITORY = "Repository", SSH_KEY = "SshKey", + VIRTUAL_MACHINE = "VirtualMachine", KEEP_SERVICE = "KeepService" } diff --git a/src/views-components/repository-remove-dialog/repository-remove-dialog.ts b/src/views-components/repository-remove-dialog/repository-remove-dialog.ts index 148e78bd..ca51c849 100644 --- a/src/views-components/repository-remove-dialog/repository-remove-dialog.ts +++ b/src/views-components/repository-remove-dialog/repository-remove-dialog.ts @@ -1,20 +1,21 @@ // 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) => ({ +const mapDispatchToProps = (dispatch: Dispatch, props: WithDialogProps) => ({ onConfirm: () => { props.closeDialog(); dispatch(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 diff --git a/src/views-components/virtual-machines-dialog/attributes-dialog.tsx b/src/views-components/virtual-machines-dialog/attributes-dialog.tsx new file mode 100644 index 00000000..05c1b0a0 --- /dev/null +++ b/src/views-components/virtual-machines-dialog/attributes-dialog.tsx @@ -0,0 +1,89 @@ +// 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((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; + +export const VirtualMachineAttributesDialog = compose( + withDialog(VIRTUAL_MACHINE_ATTRIBUTES_DIALOG), + styles)( + (props: WithDialogProps & VirtualMachineAttributesProps) => + + Attributes + + + {props.data.virtualMachineData && attributes(props.data.virtualMachineData, props.classes)} + + + + + + + ); + +const attributes = (virtualMachine: VirtualMachinesResource, classes: any) => { + const { uuid, ownerUuid, createdAt, modifiedAt, modifiedByClientUuid, modifiedByUserUuid, hostname } = virtualMachine; + return ( + + + + Hostname + Owner uuid + Created at + Modified at + Modified by user uuid + Modified by client uuid + uuid + + + {hostname} + {ownerUuid} + {createdAt} + {modifiedAt} + {modifiedByUserUuid} + {modifiedByClientUuid} + {uuid} + + + + ); +}; diff --git a/src/views-components/virtual-machines-dialog/remove-dialog.tsx b/src/views-components/virtual-machines-dialog/remove-dialog.tsx new file mode 100644 index 00000000..11ab9c43 --- /dev/null +++ b/src/views-components/virtual-machines-dialog/remove-dialog.tsx @@ -0,0 +1,21 @@ +// 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) => ({ + onConfirm: () => { + props.closeDialog(); + dispatch(removeVirtualMachine(props.data.uuid)); + } +}); + +export const RemoveVirtualMachineDialog = compose( + withDialog(VIRTUAL_MACHINE_REMOVE_DIALOG), + connect(null, mapDispatchToProps) +)(ConfirmationDialog); \ No newline at end of file diff --git a/src/views/keep-service-panel/keep-service-panel-root.tsx b/src/views/keep-service-panel/keep-service-panel-root.tsx index 57193d35..8c266b61 100644 --- a/src/views/keep-service-panel/keep-service-panel-root.tsx +++ b/src/views/keep-service-panel/keep-service-panel-root.tsx @@ -23,7 +23,7 @@ const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ }); export interface KeepServicePanelRootActionProps { - openRowOptions: (event: React.MouseEvent, index: number, keepService: KeepServiceResource) => void; + openRowOptions: (event: React.MouseEvent, keepService: KeepServiceResource) => void; } export interface KeepServicePanelRootDataProps { @@ -72,7 +72,7 @@ export const KeepServicePanelRoot = withStyles(styles)( {keepService.serviceType} - openRowOptions(event, index, keepService)}> + openRowOptions(event, keepService)}> diff --git a/src/views/keep-service-panel/keep-service-panel.tsx b/src/views/keep-service-panel/keep-service-panel.tsx index 2c6323b0..a11cee0b 100644 --- a/src/views/keep-service-panel/keep-service-panel.tsx +++ b/src/views/keep-service-panel/keep-service-panel.tsx @@ -21,8 +21,8 @@ const mapStateToProps = (state: RootState): KeepServicePanelRootDataProps => { }; const mapDispatchToProps = (dispatch: Dispatch): KeepServicePanelRootActionProps => ({ - openRowOptions: (event, index, keepService) => { - dispatch(openKeepServiceContextMenu(event, index, keepService)); + openRowOptions: (event, keepService) => { + dispatch(openKeepServiceContextMenu(event, keepService)); } }); diff --git a/src/views/repositories-panel/repositories-panel.tsx b/src/views/repositories-panel/repositories-panel.tsx index cfe59f0d..c7016f62 100644 --- a/src/views/repositories-panel/repositories-panel.tsx +++ b/src/views/repositories-panel/repositories-panel.tsx @@ -66,8 +66,8 @@ const mapStateToProps = (state: RootState) => { const mapDispatchToProps = (dispatch: Dispatch): Pick => ({ loadRepositories: () => dispatch(loadRepositoriesData()), - onOptionsMenuOpen: (event, index, repository) => { - dispatch(openRepositoryContextMenu(event, index, repository)); + onOptionsMenuOpen: (event, repository) => { + dispatch(openRepositoryContextMenu(event, repository)); }, openRepositoriesSampleGitDialog: () => dispatch(openRepositoriesSampleGitDialog()), openRepositoryCreateDialog: () => dispatch(openRepositoryCreateDialog()) @@ -75,7 +75,7 @@ const mapDispatchToProps = (dispatch: Dispatch): Pick void; - onOptionsMenuOpen: (event: React.MouseEvent, index: number, repository: RepositoryResource) => void; + onOptionsMenuOpen: (event: React.MouseEvent, repository: RepositoryResource) => void; openRepositoriesSampleGitDialog: () => void; openRepositoryCreateDialog: () => void; } @@ -137,7 +137,7 @@ export const RepositoriesPanel = compose( {repository.cloneUrls.join("\n")} - onOptionsMenuOpen(event, index, repository)} className={classes.moreOptionsButton}> + onOptionsMenuOpen(event, repository)} className={classes.moreOptionsButton}> diff --git a/src/views/ssh-key-panel/ssh-key-panel-root.tsx b/src/views/ssh-key-panel/ssh-key-panel-root.tsx index 869662dd..2cdad07d 100644 --- a/src/views/ssh-key-panel/ssh-key-panel-root.tsx +++ b/src/views/ssh-key-panel/ssh-key-panel-root.tsx @@ -38,7 +38,7 @@ const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ export interface SshKeyPanelRootActionProps { openSshKeyCreateDialog: () => void; - openRowOptions: (event: React.MouseEvent, index: number, sshKey: SshKeyResource) => void; + openRowOptions: (event: React.MouseEvent, sshKey: SshKeyResource) => void; openPublicKeyDialog: (name: string, publicKey: string) => void; } @@ -102,7 +102,7 @@ export const SshKeyPanelRoot = withStyles(styles)( - openRowOptions(event, index, sshKey)}> + openRowOptions(event, sshKey)}> diff --git a/src/views/ssh-key-panel/ssh-key-panel.tsx b/src/views/ssh-key-panel/ssh-key-panel.tsx index c7e3516e..4e800296 100644 --- a/src/views/ssh-key-panel/ssh-key-panel.tsx +++ b/src/views/ssh-key-panel/ssh-key-panel.tsx @@ -20,8 +20,8 @@ const mapDispatchToProps = (dispatch: Dispatch): SshKeyPanelRootActionProps => ( openSshKeyCreateDialog: () => { dispatch(openSshKeyCreateDialog()); }, - openRowOptions: (event, index, sshKey) => { - dispatch(openSshKeyContextMenu(event, index, sshKey)); + openRowOptions: (event, sshKey) => { + dispatch(openSshKeyContextMenu(event, sshKey)); }, openPublicKeyDialog: (name: string, publicKey: string) => { dispatch(openPublicKeyDialog(name, publicKey)); diff --git a/src/views/virtual-machine-panel/virtual-machine-panel.tsx b/src/views/virtual-machine-panel/virtual-machine-panel.tsx index c94c3a74..5dbd3f09 100644 --- a/src/views/virtual-machine-panel/virtual-machine-panel.tsx +++ b/src/views/virtual-machine-panel/virtual-machine-panel.tsx @@ -4,20 +4,21 @@ 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 = (theme: ArvadosTheme) => ({ button: { @@ -55,31 +56,47 @@ const styles: StyleRulesCallback = (theme: ArvadosTheme) => ({ 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 => ({ + saveRequestedDate: () => dispatch(saveRequestedDate()), + loadVirtualMachinesData: () => dispatch(loadVirtualMachinesData()), + onOptionsMenuOpen: (event, virtualMachine) => { + dispatch(openVirtualMachinesContextMenu(event, virtualMachine)); + }, +}); interface VirtualMachinesPanelDataProps { requestedDate: string; virtualMachines: ListResults; - logins: VirtualMachinesLoginsResource[]; + logins: VirtualMachineLogins; links: ListResults; + isAdmin: boolean; } interface VirtualMachinesPanelActionProps { saveRequestedDate: () => void; loadVirtualMachinesData: () => string; + onOptionsMenuOpen: (event: React.MouseEvent, virtualMachine: VirtualMachinesResource) => void; } type VirtualMachineProps = VirtualMachinesPanelActionProps & VirtualMachinesPanelDataProps & WithStyles; @@ -93,12 +110,12 @@ export const VirtualMachinePanel = compose( } render() { - const { virtualMachines, links } = this.props; + const { virtualMachines, links, isAdmin } = this.props; return ( - {virtualMachines.itemsAvailable === 0 && } + {!isAdmin && virtualMachines.itemsAvailable > 0 && } {virtualMachines.itemsAvailable > 0 && links.itemsAvailable > 0 && } - {} + {!isAdmin && } ); } @@ -131,53 +148,87 @@ const CardContentWithVirtualMachines = (props: VirtualMachineProps) => -
- - {props.requestedDate && - - A request for shell access was sent on {props.requestedDate} - } -
- - - - - Host name - Login name - Command line - Web shell - - - - {props.virtualMachines.items.map((it, index) => - - {it.hostname} - {getUsername(props.links, it)} - ssh {getUsername(props.links, it)}@shell.arvados - - - Log in as {getUsername(props.links, it)} - - - - )} - -
+ {props.isAdmin ? {adminVirtualMachinesTable(props)} + : +
+ + {props.requestedDate && + + A request for shell access was sent on {props.requestedDate} + } +
+ + {userVirtualMachinesTable(props)} +
+ }
; -const getUsername = (links: ListResults, virtualMachine: VirtualMachinesResource) => { - const link = links.items.find((item: any) => item.headUuid === virtualMachine.uuid); - return link.properties.username || undefined; +const userVirtualMachinesTable = (props: VirtualMachineProps) => + + + + Host name + Login name + Command line + Web shell + + + + {props.virtualMachines.items.map((it, index) => + + {it.hostname} + {getUsername(props.links)} + ssh {getUsername(props.links)}@{it.hostname}.arvados + + + Log in as {getUsername(props.links)} + + + + )} + +
; + +const adminVirtualMachinesTable = (props: VirtualMachineProps) => + + + + Uuid + Host name + Logins + + + + + {props.logins.items.length > 0 && props.virtualMachines.items.map((it, index) => + + {it.uuid} + {it.hostname} + ["{props.logins.items[0].username}"] + + + props.onOptionsMenuOpen(event, it)} className={props.classes.moreOptionsButton}> + + + + + + )} + +
; + +const getUsername = (links: ListResults) => { + return links.items[0].properties.username; }; const CardSSHSection = (props: VirtualMachineProps) => diff --git a/src/views/workbench/workbench.tsx b/src/views/workbench/workbench.tsx index dd4f802c..2d17fad9 100644 --- a/src/views/workbench/workbench.tsx +++ b/src/views/workbench/workbench.tsx @@ -62,6 +62,8 @@ import { RemoveKeepServiceDialog } from '~/views-components/keep-services-dialog 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'; @@ -170,6 +172,7 @@ export const WorkbenchPanel = + @@ -179,5 +182,6 @@ export const WorkbenchPanel = + ); \ No newline at end of file