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 } 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';
+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 = (state: RootState) => {
+const mapStateToProps = ({ virtualMachines, auth }: RootState) => {
return {
- requestedDate: state.virtualMachines.date,
- virtualMachines: state.virtualMachines.virtualMachines,
- logins: state.virtualMachines.logins,
- links: state.virtualMachines.links
+ requestedDate: virtualMachines.date,
+ isAdmin: auth.user!.isAdmin,
+ logins: virtualMachines.logins,
+ ...virtualMachines
};
};
-const mapDispatchToProps = (dispatch: Dispatch) => ({
+const mapDispatchToProps = (dispatch: Dispatch): Pick<VirtualMachinesPanelActionProps, 'loadVirtualMachinesData' | 'saveRequestedDate' | 'onOptionsMenuOpen'> => ({
saveRequestedDate: () => dispatch<any>(saveRequestedDate()),
- loadVirtualMachinesData: () => dispatch<any>(loadVirtualMachinesData())
+ loadVirtualMachinesData: () => dispatch<any>(loadVirtualMachinesData()),
+ onOptionsMenuOpen: (event, virtualMachine) => {
+ dispatch<any>(openVirtualMachinesContextMenu(event, virtualMachine));
+ },
});
interface VirtualMachinesPanelDataProps {
requestedDate: string;
virtualMachines: ListResults<any>;
- logins: VirtualMachinesLoginsResource[];
+ logins: VirtualMachineLogins;
links: ListResults<any>;
+ isAdmin: boolean;
}
interface VirtualMachinesPanelActionProps {
saveRequestedDate: () => void;
loadVirtualMachinesData: () => string;
+ onOptionsMenuOpen: (event: React.MouseEvent<HTMLElement>, virtualMachine: VirtualMachinesResource) => void;
}
type VirtualMachineProps = VirtualMachinesPanelActionProps & VirtualMachinesPanelDataProps & WithStyles<CssRules>;
}
render() {
- const { classes, saveRequestedDate, requestedDate, virtualMachines, links } = this.props;
+ const { virtualMachines, links, isAdmin } = this.props;
return (
<Grid container spacing={16}>
- {virtualMachines.itemsAvailable === 0 && cardContentWithNoVirtualMachines(requestedDate, saveRequestedDate, classes)}
- {virtualMachines.itemsAvailable > 0 && links.itemsAvailable > 0 && cardContentWithVirtualMachines(requestedDate, saveRequestedDate, virtualMachines, links, classes)}
- {cardSSHSection(classes)}
+ {!isAdmin && virtualMachines.itemsAvailable > 0 && <CardContentWithNoVirtualMachines {...this.props} />}
+ {virtualMachines.itemsAvailable > 0 && links.itemsAvailable > 0 && <CardContentWithVirtualMachines {...this.props} />}
+ {!isAdmin && <CardSSHSection {...this.props} />}
</Grid>
);
}
}
);
-const cardContentWithNoVirtualMachines = (requestedDate: string, saveRequestedDate: () => void, classes: any) =>
+const CardContentWithNoVirtualMachines = (props: VirtualMachineProps) =>
<Grid item xs={12}>
<Card>
- <CardContent className={classes.cardWithoutMachines}>
+ <CardContent className={props.classes.cardWithoutMachines}>
<Grid item xs={6}>
<Typography variant="body2">
You do not have access to any virtual machines. Some Arvados features require using the command line. You may request access to a hosted virtual machine with the command line shell.
</Typography>
</Grid>
- <Grid item xs={6} className={classes.rightAlign}>
- <Button variant="contained" color="primary" className={classes.button} onClick={saveRequestedDate}>
+ <Grid item xs={6} className={props.classes.rightAlign}>
+ <Button variant="contained" color="primary" className={props.classes.button} onClick={props.saveRequestedDate}>
SEND REQUEST FOR SHELL ACCESS
</Button>
- {requestedDate &&
+ {props.requestedDate &&
<Typography variant="body1">
- A request for shell access was sent on {requestedDate}
+ A request for shell access was sent on {props.requestedDate}
</Typography>}
</Grid>
</CardContent>
</Card>
</Grid>;
-const cardContentWithVirtualMachines = (requestedDate: string, saveRequestedDate: () => void, virtualMachines: ListResults<any>, links: ListResults<any>, classes: any) =>
+const CardContentWithVirtualMachines = (props: VirtualMachineProps) =>
<Grid item xs={12}>
<Card>
<CardContent>
- <div className={classes.rightAlign}>
- <Button variant="contained" color="primary" className={classes.button} onClick={saveRequestedDate}>
- SEND REQUEST FOR SHELL ACCESS
- </Button>
- {requestedDate &&
- <Typography variant="body1">
- A request for shell access was sent on {requestedDate}
- </Typography>}
- </div>
- <div className={classes.icon}>
- <a href="https://doc.arvados.org/user/getting_started/vm-login-with-webshell.html" target="_blank" className={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>
- {virtualMachines.items.map((it, index) =>
- <TableRow key={index}>
- <TableCell>{it.hostname}</TableCell>
- <TableCell>{getUsername(links, it)}</TableCell>
- <TableCell>ssh {getUsername(links, it)}@shell.arvados</TableCell>
- <TableCell>
- <a href={`https://workbench.c97qk.arvadosapi.com${it.href}/webshell/${getUsername(links, it)}`} target="_blank" className={classes.link}>
- Log in as {getUsername(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: any) => {
- console.log(links);
- 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 = (classes: any) =>
+const CardSSHSection = (props: VirtualMachineProps) =>
<Grid item xs={12}>
<Card>
<CardContent>
<Typography variant="body2">
- In order to access virtual machines using SSH, <Link to={Routes.SSH_KEYS} className={classes.link}>add an SSH key to your account</Link> and add a section like this to your SSH configuration file ( ~/.ssh/config):
+ In order to access virtual machines using SSH, <Link to={Routes.SSH_KEYS} className={props.classes.link}>add an SSH key to your account</Link> and add a section like this to your SSH configuration file ( ~/.ssh/config):
</Typography>
<DefaultCodeSnippet
- className={classes.codeSnippet}
+ className={props.classes.codeSnippet}
lines={[textSSH]} />
</CardContent>
</Card>