conflicts
authorPawel Kowalczyk <pawel.kowalczyk@contractors.roche.com>
Mon, 3 Dec 2018 12:32:53 +0000 (13:32 +0100)
committerPawel Kowalczyk <pawel.kowalczyk@contractors.roche.com>
Mon, 3 Dec 2018 12:32:53 +0000 (13:32 +0100)
Feature #14498

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

25 files changed:
src/index.tsx
src/models/virtual-machines.ts
src/services/auth-service/auth-service.ts
src/store/advanced-tab/advanced-tab.ts
src/store/auth/auth-action.ts
src/store/context-menu/context-menu-actions.ts
src/store/keep-services/keep-services-actions.ts
src/store/repositories/repositories-actions.ts
src/store/virtual-machines/virtual-machines-actions.ts
src/store/virtual-machines/virtual-machines-reducer.ts
src/views-components/context-menu/action-sets/keep-service-action-set.ts
src/views-components/context-menu/action-sets/repository-action-set.ts
src/views-components/context-menu/action-sets/ssh-key-action-set.ts
src/views-components/context-menu/action-sets/virtual-machine-action-set.ts [new file with mode: 0644]
src/views-components/context-menu/context-menu.tsx
src/views-components/repository-remove-dialog/repository-remove-dialog.ts
src/views-components/virtual-machines-dialog/attributes-dialog.tsx [new file with mode: 0644]
src/views-components/virtual-machines-dialog/remove-dialog.tsx [new file with mode: 0644]
src/views/keep-service-panel/keep-service-panel-root.tsx
src/views/keep-service-panel/keep-service-panel.tsx
src/views/repositories-panel/repositories-panel.tsx
src/views/ssh-key-panel/ssh-key-panel-root.tsx
src/views/ssh-key-panel/ssh-key-panel.tsx
src/views/virtual-machine-panel/virtual-machine-panel.tsx
src/views/workbench/workbench.tsx

index 79525a8a6387de7deb9001c8b414b941154a6886..d83859675fc11e6f78b9b4fee37a4314fa1bccf0 100644 (file)
@@ -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()
index 0652c35017e4626289b08569b3979cf795cc6ee1..85d0a565c07bf18ab34b67386008357be07d3a91 100644 (file)
@@ -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
index 69e3a79d6a3ffc26db9a10214a3da89ea020f57e..98c0321598090b11002375823fba7ffa1a374aee 100644 (file)
@@ -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 }
index c48011d2094f182a5c80ba0682e62d18c14c2285..d9dabe5ce5b8f646f7127f266181c5ea4953fb9e 100644 (file)
@@ -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<LinkResource> | 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<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:
@@ -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<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),
     };
 };
 
@@ -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,
index 28559b1a7ac126feac5adfb7c15170fa2f28295d..e1b36f823e3f1082952a00abad459567c56e948b 100644 (file)
@@ -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 } }));
     };
 
index 2c533d66b394b553249755ebd1df84f6daf7ddac..d56a3fb5ae520bd3bb1309c14e4f9656bf8182d2 100644 (file)
@@ -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<HTMLEleme
         }));
     };
 
-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
         }));
     };
 
index 1de6802e7e37c1e8257305e8220289cc02614acc..54a7c3fe87161b6488d0060c576ae7e7dbcd2af5 100644 (file)
@@ -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 } }));
     };
 
index a672738fd2edf18c36237f3e20c490a1100fdf73..61caa769f1ea32746608f3a4f570a86ddbb4db38 100644 (file)
@@ -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<any>(loadRepositoriesData());
     };
 
index 9bd79884ff470e86e4bb9d08c79d53c5867989c3..c95277b3a4c412149d4d2f3606a21f0bf3fc699f 100644 (file)
@@ -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<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());
@@ -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<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 = () =>
index fa28417efec42c0d6a021bb4257ede748aed564d..475ad7523896eeb2f63bd96efe92bfa3ce44ea8a 100644 (file)
@@ -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<any>;
-    logins: VirtualMachinesLoginsResource[];
+    logins: VirtualMachineLogins;
     links: ListResults<any>;
 }
 
@@ -22,7 +22,10 @@ const initialState: VirtualMachines = {
         itemsAvailable: 0,
         items: []
     },
-    logins: [],
+    logins: {
+        kind: '',
+        items: []
+    },
     links: {
         kind: '',
         offset: 0,
index 5d27e4e9670407a43a833c05da3794e20582d2c7..807a3abf59783a73f3c56bcef40c69fc28c0af2e 100644 (file)
@@ -10,14 +10,14 @@ import { AdvancedIcon, RemoveIcon, AttributesIcon } from "~/components/icon/icon
 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",
index 22f6bee135c71f6b11fb6faf21b4af1fe27ed4f6..82c2f2b8e04909b861d561ff59557c89ae973a1c 100644 (file)
@@ -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<any>(openRepositoryAttributes(index!));
+    execute: (dispatch, { uuid }) => {
+        dispatch<any>(openRepositoryAttributes(uuid));
     }
 }, {
     name: "Share",
@@ -24,7 +24,7 @@ export const repositoryActionSet: ContextMenuActionSet = [[{
     name: "Advanced",
     icon: AdvancedIcon,
     execute: (dispatch, resource) => {
-        dispatch<any>(openAdvancedTabDialog(resource.uuid, resource.index));
+        dispatch<any>(openAdvancedTabDialog(resource.uuid));
     }
 }, {
     name: "Remove",
index 6e86b2bc17a2044d05e3ab3eb320e0cbc8fb0e0c..0ce0c43118549b9107a17f8e9c3bf515b53f50a3 100644 (file)
@@ -10,14 +10,14 @@ import { openAdvancedTabDialog } from '~/store/advanced-tab/advanced-tab';
 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",
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 (file)
index 0000000..ea274af
--- /dev/null
@@ -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<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));
+    }
+}]];
index 211881ca599954728c2cb22a1a8b62a744ca8bb9..5f321bfe72f9f292542b86225d521a14207234d9 100644 (file)
@@ -71,5 +71,6 @@ export enum ContextMenuKind {
     PROCESS_LOGS = "ProcessLogs",
     REPOSITORY = "Repository",
     SSH_KEY = "SshKey",
+    VIRTUAL_MACHINE = "VirtualMachine",
     KEEP_SERVICE = "KeepService"
 }
index 148e78bdf361034688537032c200cc57ef27c8df..ca51c8495fb841d6a1cf98a713a748db5f61dd52 100644 (file)
@@ -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<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
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 (file)
index 0000000..05c1b0a
--- /dev/null
@@ -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<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>
+    );
+};
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 (file)
index 0000000..11ab9c4
--- /dev/null
@@ -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<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
index 57193d355af336ea9c1baaeebde8abcd2ba29a77..8c266b616016d8d48318364103c96274106c7722 100644 (file)
@@ -23,7 +23,7 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
 });
 
 export interface KeepServicePanelRootActionProps {
-    openRowOptions: (event: React.MouseEvent<HTMLElement>, index: number, keepService: KeepServiceResource) => void;
+    openRowOptions: (event: React.MouseEvent<HTMLElement>, keepService: KeepServiceResource) => void;
 }
 
 export interface KeepServicePanelRootDataProps {
@@ -72,7 +72,7 @@ export const KeepServicePanelRoot = withStyles(styles)(
                                         <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>
index 2c6323b079b90e10efbf15c6974ecfce299fab42..a11cee0b22b7a780edcabfa2656ee8d0160848c9 100644 (file)
@@ -21,8 +21,8 @@ const mapStateToProps = (state: RootState): KeepServicePanelRootDataProps => {
 };
 
 const mapDispatchToProps = (dispatch: Dispatch): KeepServicePanelRootActionProps => ({
-    openRowOptions: (event, index, keepService) => {
-        dispatch<any>(openKeepServiceContextMenu(event, index, keepService));
+    openRowOptions: (event, keepService) => {
+        dispatch<any>(openKeepServiceContextMenu(event, keepService));
     }
 });
 
index cfe59f0d26d772c0ddf9f227e2b5a6cc60d8ea2d..c7016f6298848355be16a206b7c4ce66ac9a029e 100644 (file)
@@ -66,8 +66,8 @@ const mapStateToProps = (state: RootState) => {
 
 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())
@@ -75,7 +75,7 @@ const mapDispatchToProps = (dispatch: Dispatch): Pick<RepositoriesActionProps, '
 
 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;
 }
@@ -137,7 +137,7 @@ export const RepositoriesPanel = compose(
                                                 <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>
index 869662dd0150f07e8fe07e11a6f0f2fcebe4c71e..2cdad07dd14fb774ca9260d87b1c4a2285c40e52 100644 (file)
@@ -38,7 +38,7 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
 
 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;
 }
 
@@ -102,7 +102,7 @@ export const SshKeyPanelRoot = withStyles(styles)(
                                     </TableCell>
                                     <TableCell>
                                         <Tooltip title="More options" disableFocusListener>
-                                            <IconButton onClick={event => openRowOptions(event, index, sshKey)}>
+                                            <IconButton onClick={event => openRowOptions(event, sshKey)}>
                                                 <MoreOptionsIcon />
                                             </IconButton>
                                         </Tooltip>
index c7e3516e0ab9cf2bede1b08df4256ab8c2821d55..4e800296d076fb3f368ac9d98959d3d00520a18b 100644 (file)
@@ -20,8 +20,8 @@ const mapDispatchToProps = (dispatch: Dispatch): SshKeyPanelRootActionProps => (
     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));
index c94c3a74bb956a2506be60cfb762f1ad6f354571..5dbd3f0965aa6336d2893fd7faf48ecba75e43e7 100644 (file)
@@ -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<CssRules> = (theme: ArvadosTheme) => ({
     button: {
@@ -55,31 +56,47 @@ const styles: StyleRulesCallback<CssRules> = (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<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>;
@@ -93,12 +110,12 @@ export const VirtualMachinePanel = compose(
             }
 
             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>
                 );
             }
@@ -131,53 +148,87 @@ const CardContentWithVirtualMachines = (props: VirtualMachineProps) =>
     <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) =>
index dd4f802c54b335e8dad86d6c2001fa02df4137ad..2d17fad9629d80aabdf7c493155012c33bc38302 100644 (file)
@@ -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 =
             <RemoveProcessDialog />
             <RemoveRepositoryDialog />
             <RemoveSshKeyDialog />
+            <RemoveVirtualMachineDialog />
             <RenameFileDialog />
             <RepositoryAttributesDialog />
             <RepositoriesSampleGitDialog />
@@ -179,5 +182,6 @@ export const WorkbenchPanel =
             <UpdateCollectionDialog />
             <UpdateProcessDialog />
             <UpdateProjectDialog />
+            <VirtualMachineAttributesDialog />
         </Grid>
     );
\ No newline at end of file