context-menu-for-VMs
authorPawel Kowalczyk <pawel.kowalczyk@contractors.roche.com>
Fri, 30 Nov 2018 10:19:36 +0000 (11:19 +0100)
committerPawel Kowalczyk <pawel.kowalczyk@contractors.roche.com>
Fri, 30 Nov 2018 10:19:36 +0000 (11:19 +0100)
Feature #14498

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

15 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/context-menu/context-menu-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/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/virtual-machine-panel/virtual-machine-panel.tsx
src/views/workbench/workbench.tsx

index 801a56a1d382a3ac589af3bb732a085190567b1c..3ff8088cc411e89ee3c45d4220d887cdb420704a 100644 (file)
@@ -51,6 +51,7 @@ import { initAdvanceFormProjectsTree } from '~/store/search-bar/search-bar-actio
 import { repositoryActionSet } from '~/views-components/context-menu/action-sets/repository-action-set';
 import { sshKeyActionSet } from '~/views-components/context-menu/action-sets/ssh-key-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()}]`);
 
@@ -69,6 +70,7 @@ addMenuActionSet(ContextMenuKind.PROCESS_RESOURCE, processResourceActionSet);
 addMenuActionSet(ContextMenuKind.TRASH, trashActionSet);
 addMenuActionSet(ContextMenuKind.REPOSITORY, repositoryActionSet);
 addMenuActionSet(ContextMenuKind.SSH_KEY, sshKeyActionSet);
+addMenuActionSet(ContextMenuKind.VIRTUAL_MACHINE, virtualMachineActionSet);
 
 fetchConfig()
     .then(({ config, apiHost }) => {
index 0652c35017e4626289b08569b3979cf795cc6ee1..acc084a3ea0b37601faa777cbe758d49cbbc80c3 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;
+}
+
+export interface VirtualMachineLogins {
+    kind: string;
+    items: VirtualMachinesLoginsItems[];
 }
\ No newline at end of file
index 2c0c83d47d4ce1f8664bb1eda23b828db56343e6..b512fb24c32d0e435e2f4be78d918c9fc49e1d8d 100644 (file)
@@ -61,8 +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();
-        console.log(isAdmin);
+        const isAdmin = this.getIsAdmin();   
 
         return email && firstName && lastName && uuid && ownerUuid
             ? { email, firstName, lastName, uuid, ownerUuid, isAdmin }
index b3c5164c5561e8d3e8104b6a4d700e9c904a21df..2b7e883da915407b4148383e96d14073fdc23207 100644 (file)
@@ -16,6 +16,7 @@ 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';
 
 export const ADVANCED_TAB_DIALOG = 'advancedTabDialog';
 
@@ -54,12 +55,17 @@ enum RepositoryData {
 }
 
 enum SshKeyData {
-    SSH_KEY = 'authorized_keys',
+    SSH_KEY = 'authorized_key',
     CREATED_AT = 'created_at'
 }
 
-type AdvanceResourceKind = CollectionData | ProcessData | ProjectData | RepositoryData | SshKeyData;
-type AdvanceResourcePrefix = GroupContentsResourcePrefix | 'repositories' | 'authorized_keys';
+enum VirtualMachineData {
+    VIRTUAL_MACHINE = 'virtual_machine',
+    CREATED_AT = 'created_at'
+}
+
+type AdvanceResourceKind = CollectionData | ProcessData | ProjectData | RepositoryData | SshKeyData | VirtualMachineData;
+type AdvanceResourcePrefix = GroupContentsResourcePrefix | 'repositories' | 'authorized_keys' | 'virtual_machines';
 
 export const openAdvancedTabDialog = (uuid: string, index?: number) =>
     async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
@@ -90,6 +96,11 @@ export const openAdvancedTabDialog = (uuid: string, index?: number) =>
                 const advanceDataSshKey: AdvancedTabDialogData = advancedTabData(uuid, '', '', sshKeyApiResponse, dataSshKey, SshKeyData.SSH_KEY, 'authorized_keys', SshKeyData.CREATED_AT, dataSshKey.createdAt);
                 dispatch<any>(initAdvancedTabDialog(advanceDataSshKey));
                 break;
+            case ResourceKind.VIRTUAL_MACHINE:
+                const dataVirtualMachine = getState().virtualMachines.virtualMachines.items[index!];
+                const advanceDataVirtualMachine: AdvancedTabDialogData = advancedTabData(uuid, '', '', virtualMachineApiResponse, dataVirtualMachine, VirtualMachineData.VIRTUAL_MACHINE, 'virtual_machines', VirtualMachineData.CREATED_AT, dataVirtualMachine.createdAt);
+                dispatch<any>(initAdvancedTabDialog(advanceDataVirtualMachine));
+                break;
             default:
                 dispatch(snackbarActions.OPEN_SNACKBAR({ message: "Could not open advanced tab for this resource.", hideDuration: 2000, kind: SnackbarKind.ERROR }));
         }
@@ -110,7 +121,7 @@ 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, 
+const advancedTabData = (uuid: string, metadata: any, user: any, apiResponseKind: any, data: any, resourceKind: AdvanceResourceKind,
     resourcePrefix: AdvanceResourcePrefix, resourceKindProperty: AdvanceResourceKind, property: any) => {
     return {
         uuid,
@@ -293,4 +304,16 @@ const sshKeyApiResponse = (apiResponse: SshKeyResource) => {
 "created_at": "${createdAt}",
 "expires_at": "${expiresAt}"`;
     return response;
+};
+
+const virtualMachineApiResponse = (apiResponse: VirtualMachinesResource) => {
+    const { uuid, ownerUuid, createdAt, modifiedAt, modifiedByClientUuid, modifiedByUserUuid, hostname } = apiResponse;
+    const response = `"uuid": "${uuid}",
+"owner_uuid": "${ownerUuid}",
+"modified_by_client_uuid": ${stringify(modifiedByClientUuid)},
+"modified_by_user_uuid": ${stringify(modifiedByUserUuid)},
+"modified_at": ${stringify(modifiedAt)},
+"hostname": ${stringify(hostname)},
+"created_at": "${createdAt}"`;
+    return response;
 };
\ No newline at end of file
index 5631a5e85cf9880b99fde0afd22f339a55ee734f..0dbae18b991510f10f3a3cbb6e8642edd420f931 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';
 
 export const contextMenuActions = unionize({
     OPEN_CONTEXT_MENU: ofType<{ position: ContextMenuPosition, resource: ContextMenuResource }>(),
@@ -64,14 +65,26 @@ export const openCollectionFilesContextMenu = (event: React.MouseEvent<HTMLEleme
 
 export const openRepositoryContextMenu = (event: React.MouseEvent<HTMLElement>, index: number, 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,
+            index
+        }));
+    };
+
+export const openVirtualMachinesContextMenu = (event: React.MouseEvent<HTMLElement>, index: number, 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,
+            index
+        }));
     };
 
 export const openSshKeyContextMenu = (event: React.MouseEvent<HTMLElement>, index: number, sshKey: SshKeyResource) =>
index a672738fd2edf18c36237f3e20c490a1100fdf73..d6172899936b45e3dd1d4cc19eb0ae42d548ba56 100644 (file)
@@ -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 6f59c9109ef597b1961ee73a3dc275b909183875..bb942bb6dcf03a43810c3a84fb413a3a4e5f671e 100644 (file)
@@ -9,26 +9,36 @@ 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 = (index: number) =>
+    async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+        const virtualMachineData = getState().virtualMachines.virtualMachines.items[index];
+        dispatch(dialogActions.OPEN_DIALOG({ id: VIRTUAL_MACHINE_ATTRIBUTES_DIALOG, data: { virtualMachineData } }));
+    };
+
 const loadRequestedDate = () =>
     (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
         const date = services.virtualMachineService.getRequestedDate();
@@ -47,9 +57,8 @@ export const loadVirtualMachinesData = () =>
         });
         dispatch(virtualMachinesActions.SET_VIRTUAL_MACHINES(virtualMachines));
         dispatch(virtualMachinesActions.SET_LINKS(links));
-        const getAllLogins = await services.virtualMachineService.getAllLogins();
-        console.log(getAllLogins);  
-        dispatch(virtualMachinesActions.SET_LOGINS(getAllLogins));
+        const logins = await services.virtualMachineService.logins(virtualMachinesUuids[0]);
+        dispatch(virtualMachinesActions.SET_LOGINS(logins));
     };
 
 export const saveRequestedDate = () =>
@@ -59,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,
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..bb04b8c
--- /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, { index }) => {
+        dispatch<any>(openVirtualMachineAttributes(index!));
+    }
+}, {
+    name: "Advanced",
+    icon: AdvancedIcon,
+    execute: (dispatch, { uuid, index }) => {
+        dispatch<any>(openAdvancedTabDialog(uuid, index));
+    }
+}, {
+    name: "Remove",
+    icon: RemoveIcon,
+    execute: (dispatch, { uuid }) => {
+        dispatch<any>(openRemoveVirtualMachineDialog(uuid));
+    }
+}]];
index af5aaa929c43f14623c9f8c6e85d43ddeb4ebf7f..d08798f773beef21fd7e29dec533b71a6d69eedd 100644 (file)
@@ -70,5 +70,6 @@ export enum ContextMenuKind {
     PROCESS_RESOURCE = 'ProcessResource',
     PROCESS_LOGS = "ProcessLogs",
     REPOSITORY = "Repository",
-    SSH_KEY = "SshKey"
+    SSH_KEY = "SshKey",
+    VIRTUAL_MACHINE = "VirtualMachine"
 }
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..78b58da
--- /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 { StyleRulesCallback, 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: StyleRulesCallback<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),
+    withStyles(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 504910e0f738537c7392d8c5d485210ced3b88b3..6c8955448c64d3a9c1b699d5c04f53a0289af70b 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 { 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, AddIcon } 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,26 +56,39 @@ 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, 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, index, virtualMachine) => {
+        dispatch<any>(openVirtualMachinesContextMenu(event, index, virtualMachine));
+    },
+});
 
 interface VirtualMachinesPanelDataProps {
     requestedDate: string;
     virtualMachines: ListResults<any>;
-    logins: VirtualMachinesLoginsResource[];
+    logins: VirtualMachineLogins;
     links: ListResults<any>;
     isAdmin: boolean;
 }
@@ -82,6 +96,7 @@ interface VirtualMachinesPanelDataProps {
 interface VirtualMachinesPanelActionProps {
     saveRequestedDate: () => void;
     loadVirtualMachinesData: () => string;
+    onOptionsMenuOpen: (event: React.MouseEvent<HTMLElement>, index: number, virtualMachine: VirtualMachinesResource) => void;
 }
 
 type VirtualMachineProps = VirtualMachinesPanelActionProps & VirtualMachinesPanelDataProps & WithStyles<CssRules>;
@@ -95,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} />}
                         {virtualMachines.itemsAvailable > 0 && links.itemsAvailable > 0 && <CardContentWithVirtualMachines {...this.props} />}
-                        {<CardSSHSection {...this.props} />}
+                        {!isAdmin && <CardSSHSection {...this.props} />}
                     </Grid>
                 );
             }
@@ -133,24 +148,35 @@ 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>
-                {console.log(props.isAdmin)}
-                {props.isAdmin ? adminVirtualMachinesTable(props) : userVirtualMachinesTable(props)}
+                {props.isAdmin ?
+                    <span>
+                        <div className={props.classes.rightAlign}>
+                            <Button variant="contained" color="primary" className={props.classes.button} onClick={props.saveRequestedDate}>
+                                <AddIcon /> NEW VIRTUAL MACHINE
+                            </Button>
+                        </div>
+                        {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>;
@@ -188,19 +214,21 @@ const adminVirtualMachinesTable = (props: VirtualMachineProps) =>
                 <TableCell>Uuid</TableCell>
                 <TableCell>Host name</TableCell>
                 <TableCell>Logins</TableCell>
-                <TableCell/>
+                <TableCell />
             </TableRow>
         </TableHead>
         <TableBody>
-            {props.virtualMachines.items.map((it, index) =>
+            {props.logins.items.length > 0 && props.virtualMachines.items.map((it, index) =>
                 <TableRow key={index}>
                     <TableCell>{it.uuid}</TableCell>
-                    <TableCell>shell</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>{props.logins.items[0].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, index, it)} className={props.classes.moreOptionsButton}>
+                                <MoreOptionsIcon />
+                            </IconButton>
+                        </Tooltip>
                     </TableCell>
                 </TableRow>
             )}
index 3914f64632e716bfe0aa4aca8265ae42a6a6bc93..3fc514af87daf45de5fb6de6858946878e07c22e 100644 (file)
@@ -59,6 +59,8 @@ import { CreateSshKeyDialog } from '~/views-components/dialog-forms/create-ssh-k
 import { PublicKeyDialog } from '~/views-components/ssh-keys-dialog/public-key-dialog';
 import { RemoveSshKeyDialog } from '~/views-components/ssh-keys-dialog/remove-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';
 
@@ -164,6 +166,7 @@ export const WorkbenchPanel =
             <RemoveProcessDialog />
             <RemoveRepositoryDialog />
             <RemoveSshKeyDialog />
+            <RemoveVirtualMachineDialog />
             <RenameFileDialog />
             <RepositoryAttributesDialog />
             <RepositoriesSampleGitDialog />
@@ -173,5 +176,6 @@ export const WorkbenchPanel =
             <UpdateCollectionDialog />
             <UpdateProcessDialog />
             <UpdateProjectDialog />
+            <VirtualMachineAttributesDialog />
         </Grid>
     );
\ No newline at end of file