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()}]`);
addMenuActionSet(ContextMenuKind.TRASH, trashActionSet);
addMenuActionSet(ContextMenuKind.REPOSITORY, repositoryActionSet);
addMenuActionSet(ContextMenuKind.SSH_KEY, sshKeyActionSet);
+addMenuActionSet(ContextMenuKind.VIRTUAL_MACHINE, virtualMachineActionSet);
fetchConfig()
.then(({ config, apiHost }) => {
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
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 }
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';
}
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) => {
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 }));
}
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,
"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
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 }>(),
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) =>
async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Removing ...' }));
await services.repositoriesService.delete(uuid);
- dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Removed.', hideDuration: 2000 }));
+ dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Removed.', hideDuration: 2000, kind: SnackbarKind.SUCCESS }));
dispatch<any>(loadRepositoriesData());
};
import { bindDataExplorerActions } from '~/store/data-explorer/data-explorer-action';
import { formatDate } from "~/common/formatters";
import { unionize, ofType, UnionOf } from "~/common/unionize";
-import { VirtualMachinesLoginsResource } from '~/models/virtual-machines';
+import { VirtualMachineLogins } from '~/models/virtual-machines';
import { FilterBuilder } from "~/services/api/filter-builder";
import { ListResults } from "~/services/common-service/common-resource-service";
+import { dialogActions } from '~/store/dialog/dialog-actions';
+import { snackbarActions, SnackbarKind } from '~/store/snackbar/snackbar-actions';
export const virtualMachinesActions = unionize({
SET_REQUESTED_DATE: ofType<string>(),
SET_VIRTUAL_MACHINES: ofType<ListResults<any>>(),
- SET_LOGINS: ofType<VirtualMachinesLoginsResource[]>(),
+ SET_LOGINS: ofType<VirtualMachineLogins>(),
SET_LINKS: ofType<ListResults<any>>()
});
export type VirtualMachineActions = UnionOf<typeof virtualMachinesActions>;
export const VIRTUAL_MACHINES_PANEL = 'virtualMachinesPanel';
+export const VIRTUAL_MACHINE_ATTRIBUTES_DIALOG = 'virtualMachineAttributesDialog';
+export const VIRTUAL_MACHINE_REMOVE_DIALOG = 'virtualMachineRemoveDialog';
export const openVirtualMachines = () =>
async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
dispatch<any>(navigateToVirtualMachines);
};
+export const openVirtualMachineAttributes = (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();
});
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 = () =>
dispatch<any>(loadRequestedDate());
};
+export const openRemoveVirtualMachineDialog = (uuid: string) =>
+ (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ dispatch(dialogActions.OPEN_DIALOG({
+ id: VIRTUAL_MACHINE_REMOVE_DIALOG,
+ data: {
+ title: 'Remove virtual machine',
+ text: 'Are you sure you want to remove this virtual machine?',
+ confirmButtonLabel: 'Remove',
+ uuid
+ }
+ }));
+ };
+
+export const removeVirtualMachine = (uuid: string) =>
+ async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Removing ...' }));
+ await services.virtualMachineService.delete(uuid);
+ dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Removed.', hideDuration: 2000, kind: SnackbarKind.SUCCESS }));
+ dispatch<any>(loadVirtualMachinesData());
+ };
+
const virtualMachinesBindedActions = bindDataExplorerActions(VIRTUAL_MACHINES_PANEL);
export const loadVirtualMachinesPanel = () =>
import { virtualMachinesActions, VirtualMachineActions } from '~/store/virtual-machines/virtual-machines-actions';
import { ListResults } from '~/services/common-service/common-resource-service';
-import { VirtualMachinesLoginsResource } from '~/models/virtual-machines';
+import { VirtualMachineLogins } from '~/models/virtual-machines';
interface VirtualMachines {
date: string;
virtualMachines: ListResults<any>;
- logins: VirtualMachinesLoginsResource[];
+ logins: VirtualMachineLogins;
links: ListResults<any>;
}
itemsAvailable: 0,
items: []
},
- logins: [],
+ logins: {
+ kind: '',
+ items: []
+ },
links: {
kind: '',
offset: 0,
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { ContextMenuActionSet } from "~/views-components/context-menu/context-menu-action-set";
+import { AdvancedIcon, RemoveIcon, AttributesIcon } from "~/components/icon/icon";
+import { openAdvancedTabDialog } from '~/store/advanced-tab/advanced-tab';
+import { openVirtualMachineAttributes, openRemoveVirtualMachineDialog } from "~/store/virtual-machines/virtual-machines-actions";
+
+export const virtualMachineActionSet: ContextMenuActionSet = [[{
+ name: "Attributes",
+ icon: AttributesIcon,
+ execute: (dispatch, { 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));
+ }
+}]];
PROCESS_RESOURCE = 'ProcessResource',
PROCESS_LOGS = "ProcessLogs",
REPOSITORY = "Repository",
- SSH_KEY = "SshKey"
+ SSH_KEY = "SshKey",
+ VIRTUAL_MACHINE = "VirtualMachine"
}
// Copyright (C) The Arvados Authors. All rights reserved.
//
// SPDX-License-Identifier: AGPL-3.0
+
import { Dispatch, compose } from 'redux';
import { connect } from "react-redux";
import { ConfirmationDialog } from "~/components/confirmation-dialog/confirmation-dialog";
import { withDialog, WithDialogProps } from "~/store/dialog/with-dialog";
import { removeRepository, REPOSITORY_REMOVE_DIALOG } from '~/store/repositories/repositories-actions';
- const mapDispatchToProps = (dispatch: Dispatch, props: WithDialogProps<any>) => ({
+const mapDispatchToProps = (dispatch: Dispatch, props: WithDialogProps<any>) => ({
onConfirm: () => {
props.closeDialog();
dispatch<any>(removeRepository(props.data.uuid));
}
});
- export const RemoveRepositoryDialog = compose(
+export const RemoveRepositoryDialog = compose(
withDialog(REPOSITORY_REMOVE_DIALOG),
connect(null, mapDispatchToProps)
)(ConfirmationDialog);
\ No newline at end of file
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from "react";
+import { Dialog, DialogTitle, DialogContent, DialogActions, Button, Typography, Grid } from "@material-ui/core";
+import { WithDialogProps } from "~/store/dialog/with-dialog";
+import { withDialog } from '~/store/dialog/with-dialog';
+import { VIRTUAL_MACHINE_ATTRIBUTES_DIALOG } from "~/store/virtual-machines/virtual-machines-actions";
+import { 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>
+ );
+};
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { Dispatch, compose } from 'redux';
+import { connect } from "react-redux";
+import { ConfirmationDialog } from "~/components/confirmation-dialog/confirmation-dialog";
+import { withDialog, WithDialogProps } from "~/store/dialog/with-dialog";
+import { VIRTUAL_MACHINE_REMOVE_DIALOG, removeVirtualMachine } from '~/store/virtual-machines/virtual-machines-actions';
+
+const mapDispatchToProps = (dispatch: Dispatch, props: WithDialogProps<any>) => ({
+ onConfirm: () => {
+ props.closeDialog();
+ dispatch<any>(removeVirtualMachine(props.data.uuid));
+ }
+});
+
+export const RemoveVirtualMachineDialog = compose(
+ withDialog(VIRTUAL_MACHINE_REMOVE_DIALOG),
+ connect(null, mapDispatchToProps)
+)(ConfirmationDialog);
\ No newline at end of file
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: {
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;
}
interface VirtualMachinesPanelActionProps {
saveRequestedDate: () => void;
loadVirtualMachinesData: () => string;
+ onOptionsMenuOpen: (event: React.MouseEvent<HTMLElement>, index: number, virtualMachine: VirtualMachinesResource) => void;
}
type VirtualMachineProps = VirtualMachinesPanelActionProps & VirtualMachinesPanelDataProps & WithStyles<CssRules>;
}
render() {
- const { virtualMachines, links } = this.props;
+ const { virtualMachines, links, isAdmin } = this.props;
return (
<Grid container spacing={16}>
{virtualMachines.itemsAvailable === 0 && <CardContentWithNoVirtualMachines {...this.props} />}
{virtualMachines.itemsAvailable > 0 && links.itemsAvailable > 0 && <CardContentWithVirtualMachines {...this.props} />}
- {<CardSSHSection {...this.props} />}
+ {!isAdmin && <CardSSHSection {...this.props} />}
</Grid>
);
}
<Grid item xs={12}>
<Card>
<CardContent>
- <div className={props.classes.rightAlign}>
- <Button variant="contained" color="primary" className={props.classes.button} onClick={props.saveRequestedDate}>
- SEND REQUEST FOR SHELL ACCESS
- </Button>
- {props.requestedDate &&
- <Typography variant="body1">
- A request for shell access was sent on {props.requestedDate}
- </Typography>}
- </div>
- <div className={props.classes.icon}>
- <a href="https://doc.arvados.org/user/getting_started/vm-login-with-webshell.html" target="_blank" className={props.classes.linkIcon}>
- <Tooltip title="Access VM using webshell">
- <HelpIcon />
- </Tooltip>
- </a>
- </div>
- {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>;
<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>
)}
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';
<RemoveProcessDialog />
<RemoveRepositoryDialog />
<RemoveSshKeyDialog />
+ <RemoveVirtualMachineDialog />
<RenameFileDialog />
<RepositoryAttributesDialog />
<RepositoriesSampleGitDialog />
<UpdateCollectionDialog />
<UpdateProcessDialog />
<UpdateProjectDialog />
+ <VirtualMachineAttributesDialog />
</Grid>
);
\ No newline at end of file