--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from 'react';
+import { connect } from 'react-redux';
+import { Grid, 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 { compose, Dispatch } from 'redux';
+import { loadVirtualMachinesAdminData } from '~/store/virtual-machines/virtual-machines-actions';
+import { RootState } from '~/store/store';
+import { ListResults } from '~/services/common-service/common-service';
+import { MoreOptionsIcon } from '~/components/icon/icon';
+import { VirtualMachineLogins, VirtualMachinesResource } from '~/models/virtual-machines';
+import { openVirtualMachinesContextMenu } from '~/store/context-menu/context-menu-actions';
+
+type CssRules = 'moreOptionsButton' | 'moreOptions';
+
+const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
+ moreOptionsButton: {
+ padding: 0
+ },
+ moreOptions: {
+ textAlign: 'right',
+ '&:last-child': {
+ paddingRight: 0
+ }
+ },
+});
+
+const mapStateToProps = ({ virtualMachines }: RootState) => {
+ return {
+ logins: virtualMachines.logins,
+ ...virtualMachines
+ };
+};
+
+const mapDispatchToProps = (dispatch: Dispatch): Pick<VirtualMachinesPanelActionProps, 'loadVirtualMachinesData' | 'onOptionsMenuOpen'> => ({
+ loadVirtualMachinesData: () => dispatch<any>(loadVirtualMachinesAdminData()),
+ onOptionsMenuOpen: (event, virtualMachine) => {
+ dispatch<any>(openVirtualMachinesContextMenu(event, virtualMachine));
+ },
+});
+
+interface VirtualMachinesPanelDataProps {
+ virtualMachines: ListResults<any>;
+ logins: VirtualMachineLogins;
+}
+
+interface VirtualMachinesPanelActionProps {
+ loadVirtualMachinesData: () => string;
+ onOptionsMenuOpen: (event: React.MouseEvent<HTMLElement>, virtualMachine: VirtualMachinesResource) => void;
+}
+
+type VirtualMachineProps = VirtualMachinesPanelActionProps & VirtualMachinesPanelDataProps & WithStyles<CssRules>;
+
+export const VirtualMachineAdminPanel = compose(
+ withStyles(styles),
+ connect(mapStateToProps, mapDispatchToProps))(
+ class extends React.Component<VirtualMachineProps> {
+ componentDidMount() {
+ this.props.loadVirtualMachinesData();
+ }
+
+ render() {
+ const { virtualMachines } = this.props;
+ return (
+ <Grid container spacing={16}>
+ {virtualMachines.itemsAvailable > 0 && <CardContentWithVirtualMachines {...this.props} />}
+ </Grid>
+ );
+ }
+ }
+ );
+
+const CardContentWithVirtualMachines = (props: VirtualMachineProps) =>
+ <Grid item xs={12}>
+ <Card>
+ <CardContent>
+ {virtualMachinesTable(props)}
+ </CardContent>
+ </Card>
+ </Grid>;
+
+const virtualMachinesTable = (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>;