16029: Merge branch 'master' into 16029-cypress-testing
authorLucas Di Pentima <lucas@di-pentima.com.ar>
Mon, 6 Apr 2020 20:30:45 +0000 (17:30 -0300)
committerLucas Di Pentima <lucas@di-pentima.com.ar>
Mon, 6 Apr 2020 20:30:45 +0000 (17:30 -0300)
12 files changed:
src/common/config.ts
src/components/column-selector/column-selector.tsx
src/components/data-table-filters/data-table-filters-popover.test.tsx [new file with mode: 0644]
src/components/data-table-filters/data-table-filters-popover.tsx
src/components/data-table-filters/data-table-filters-tree.tsx
src/components/data-table-filters/data-table-filters.test.tsx [deleted file]
src/components/data-table-filters/data-table-filters.tsx
src/components/data-table/data-table.test.tsx
src/components/tree/tree.tsx
src/views-components/user-dialog/attributes-dialog.tsx
src/views/virtual-machine-panel/virtual-machine-user-panel.tsx
yarn.lock

index 58fa13ae62e6ee946ada2ab931ee54064a8c6e8f..0a13f4e11664b7fe9fc945ac30e236682eb43572 100644 (file)
@@ -53,6 +53,7 @@ export interface ClusterConfigJSON {
         WelcomePageHTML: string;
         InactivePageHTML: string;
         SSHHelpPageHTML: string;
+        SSHHelpHostSuffix: string;
         SiteName: string;
     };
     Login: {
@@ -122,7 +123,7 @@ export const fetchConfig = () => {
             return Axios.get<ClusterConfigJSON>(getClusterConfigURL(workbenchConfig.API_HOST)).then(async response => {
                 const clusterConfigJSON = response.data;
                 const apiRevision = await getApiRevision(clusterConfigJSON.Services.Controller.ExternalURL);
-                const config = {...buildConfig(clusterConfigJSON), apiRevision};
+                const config = { ...buildConfig(clusterConfigJSON), apiRevision };
                 const warnLocalConfig = (varName: string) => console.warn(
                     `A value for ${varName} was found in ${WORKBENCH_CONFIG_URL}. To use the Arvados centralized configuration instead, \
 remove the entire ${varName} entry from ${WORKBENCH_CONFIG_URL}`);
@@ -181,6 +182,7 @@ export const mockClusterConfigJSON = (config: Partial<ClusterConfigJSON>): Clust
         WelcomePageHTML: "",
         InactivePageHTML: "",
         SSHHelpPageHTML: "",
+        SSHHelpHostSuffix: "",
         SiteName: "",
     },
     Login: {
index ccff61811b6303dacf9a8362e4c3853391d768b2..566bb9d61b5b8e5b3021e2838e10234a853729cc 100644 (file)
@@ -55,8 +55,8 @@ export const ColumnSelector = withStyles(styles)(
 );
 
 export const ColumnSelectorTrigger = (props: IconButtonProps) =>
-    <Tooltip title="Filters">
+    <Tooltip disableFocusListener title="Select columns">
         <IconButton {...props}>
-            <MenuIcon aria-label="Filters" />
+            <MenuIcon aria-label="Select columns" />
         </IconButton>
     </Tooltip>;
diff --git a/src/components/data-table-filters/data-table-filters-popover.test.tsx b/src/components/data-table-filters/data-table-filters-popover.test.tsx
new file mode 100644 (file)
index 0000000..f7bd00f
--- /dev/null
@@ -0,0 +1,24 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from "react";
+import { mount, configure } from "enzyme";
+import { DataTableFiltersPopover } from "./data-table-filters-popover";
+import * as Adapter from 'enzyme-adapter-react-16';
+import { Checkbox, IconButton } from "@material-ui/core";
+import { getInitialProcessStatusFilters } from "~/store/resource-type-filters/resource-type-filters"
+
+configure({ adapter: new Adapter() });
+
+describe("<DataTableFiltersPopover />", () => {
+    it("renders filters according to their state", () => {
+        // 1st filter (All) is selected, the rest aren't.
+        const filters = getInitialProcessStatusFilters()
+
+        const dataTableFilter = mount(<DataTableFiltersPopover name="" filters={filters} />);
+        dataTableFilter.find(IconButton).simulate("click");
+        expect(dataTableFilter.find(Checkbox).at(0).prop("checked")).toBeTruthy();
+        expect(dataTableFilter.find(Checkbox).at(1).prop("checked")).toBeFalsy();
+    });
+});
index 670afa95e415dc693830f0b9e5d87dc2f91e3729..456d237580f2592adfbcc237809e961124fdbde6 100644 (file)
@@ -108,7 +108,7 @@ export const DataTableFiltersPopover = withStyles(styles)(
                     : f.selected
                 );
             return <>
-                <Tooltip title='Filters'>
+                <Tooltip disableFocusListener title='Filters'>
                     <ButtonBase
                         className={classnames([classes.root, { [classes.active]: isActive }])}
                         component="span"
@@ -139,7 +139,15 @@ export const DataTableFiltersPopover = withStyles(styles)(
                             mutuallyExclusive={this.props.mutuallyExclusive}
                             onChange={filters => {
                                 this.setState({ filters });
+                                if (this.props.mutuallyExclusive) {
+                                    const { onChange } = this.props;
+                                    if (onChange) {
+                                        onChange(filters);
+                                    }
+                                    this.setState({ anchorEl: undefined });
+                                }
                             }} />
+                        {this.props.mutuallyExclusive ||
                         <CardActions>
                             <Button
                                 color="primary"
@@ -156,6 +164,7 @@ export const DataTableFiltersPopover = withStyles(styles)(
                                 Cancel
                             </Button>
                         </CardActions >
+                        }
                     </Card>
                 </Popover>
             </>;
index d964012dcefa348a7182660bca2e9d27985a5675..5f0d5e3915383db4acd3ab564d0d193b8acf9114 100644 (file)
@@ -37,12 +37,14 @@ export class DataTableFiltersTree extends React.Component<DataTableFilterProps>
             render={renderItem}
             showSelection
             useRadioButtons={this.props.mutuallyExclusive}
-            toggleItemRadioButton={this.toggleRadioButtonFilter}
             disableRipple
             onContextMenu={noop}
-            toggleItemActive={noop}
+            toggleItemActive={
+                this.props.mutuallyExclusive
+                    ? this.toggleRadioButtonFilter
+                    : this.toggleFilter
+            }
             toggleItemOpen={this.toggleOpen}
-            toggleItemSelection={this.toggleFilter}
         />;
     }
 
@@ -50,7 +52,7 @@ export class DataTableFiltersTree extends React.Component<DataTableFilterProps>
      * Handler for when a tree item is toggled via a radio button.
      * Ensures mutual exclusivity among filter tree items.
      */
-    toggleRadioButtonFilter = (item: TreeItem<DataTableFilterItem>) => {
+    toggleRadioButtonFilter = (_: any, item: TreeItem<DataTableFilterItem>) => {
         const { onChange = noop } = this.props;
 
         // If the filter is already selected, do nothing.
diff --git a/src/components/data-table-filters/data-table-filters.test.tsx b/src/components/data-table-filters/data-table-filters.test.tsx
deleted file mode 100644 (file)
index dc1969f..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-import * as React from "react";
-import { mount, configure } from "enzyme";
-import { DataTableFilters } from "./data-table-filters";
-import * as Adapter from 'enzyme-adapter-react-16';
-import { Checkbox, ButtonBase } from "@material-ui/core";
-
-configure({ adapter: new Adapter() });
-
-describe("<DataTableFilter />", () => {
-    it("renders filters according to their state", () => {
-        const filters = [{
-            name: "Filter 1",
-            selected: true
-        }, {
-            name: "Filter 2",
-            selected: false
-        }];
-        const dataTableFilter = mount(<DataTableFilters name="" filters={filters} />);
-        dataTableFilter.find(ButtonBase).simulate("click");
-        expect(dataTableFilter.find(Checkbox).at(0).prop("checked")).toBeTruthy();
-        expect(dataTableFilter.find(Checkbox).at(1).prop("checked")).toBeFalsy();
-    });
-});
index a57f29aac44805b3abb24aad5128447b635b32b3..ed7b30e759078918460d1f30cedecc88eb027eb1 100644 (file)
@@ -2,207 +2,7 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import * as React from "react";
-import {
-    WithStyles,
-    withStyles,
-    ButtonBase,
-    StyleRulesCallback,
-    Theme,
-    Popover,
-    List,
-    ListItem,
-    Checkbox,
-    ListItemText,
-    Button,
-    Card,
-    CardActions,
-    Typography,
-    CardContent,
-    Tooltip
-} from "@material-ui/core";
-import * as classnames from "classnames";
-import { DefaultTransformOrigin } from "../popover/helpers";
-import { createTree, initTreeNode, mapTree } from '~/models/tree';
-import { DataTableFilters as DataTableFiltersModel, DataTableFiltersTree } from "./data-table-filters-tree";
-import { pipe } from 'lodash/fp';
-import { setNode } from '~/models/tree';
-
-export type CssRules = "root" | "icon" | "active" | "checkbox";
-
-const styles: StyleRulesCallback<CssRules> = (theme: Theme) => ({
-    root: {
-        cursor: "pointer",
-        display: "inline-flex",
-        justifyContent: "flex-start",
-        flexDirection: "inherit",
-        alignItems: "center",
-        "&:hover": {
-            color: theme.palette.text.primary,
-        },
-        "&:focus": {
-            color: theme.palette.text.primary,
-        },
-    },
-    active: {
-        color: theme.palette.text.primary,
-        '& $icon': {
-            opacity: 1,
-        },
-    },
-    icon: {
-        marginRight: 4,
-        marginLeft: 4,
-        opacity: 0.7,
-        userSelect: "none",
-        width: 16
-    },
-    checkbox: {
-        width: 24,
-        height: 24
-    }
-});
-
 export interface DataTableFilterItem {
     name: string;
     selected: boolean;
 }
-
-export interface DataTableFilterProps {
-    name: string;
-    filters: DataTableFilterItem[];
-    onChange?: (filters: DataTableFilterItem[]) => void;
-}
-
-interface DataTableFilterState {
-    anchorEl?: HTMLElement;
-    filters: DataTableFilterItem[];
-    prevFilters: DataTableFilterItem[];
-    filtersTree: DataTableFiltersModel;
-}
-
-const filters: DataTableFiltersModel = pipe(
-    createTree,
-    setNode(initTreeNode({ id: 'Project', value: { name: 'Project' } })),
-    setNode(initTreeNode({ id: 'Process', value: { name: 'Process' } })),
-    setNode(initTreeNode({ id: 'Data collection', value: { name: 'Data collection' } })),
-    setNode(initTreeNode({ id: 'General', parent: 'Data collection', value: { name: 'General' } })),
-    setNode(initTreeNode({ id: 'Output', parent: 'Data collection', value: { name: 'Output' } })),
-    setNode(initTreeNode({ id: 'Log', parent: 'Data collection', value: { name: 'Log' } })),
-    mapTree(node => ({...node, selected: true})),
-)();
-
-export const DataTableFilters = withStyles(styles)(
-    class extends React.Component<DataTableFilterProps & WithStyles<CssRules>, DataTableFilterState> {
-        state: DataTableFilterState = {
-            anchorEl: undefined,
-            filters: [],
-            prevFilters: [],
-            filtersTree: filters,
-        };
-        icon = React.createRef<HTMLElement>();
-
-        render() {
-            const { name, classes, children } = this.props;
-            const isActive = this.state.filters.some(f => f.selected);
-            return <>
-                <Tooltip title='Filters'>
-                    <ButtonBase
-                        className={classnames([classes.root, { [classes.active]: isActive }])}
-                        component="span"
-                        onClick={this.open}
-                        disableRipple>
-                        {children}
-                        <i className={classnames(["fas fa-filter", classes.icon])}
-                            data-fa-transform="shrink-3"
-                            ref={this.icon} />
-                    </ButtonBase>
-                </Tooltip>
-                <Popover
-                    anchorEl={this.state.anchorEl}
-                    open={!!this.state.anchorEl}
-                    anchorOrigin={DefaultTransformOrigin}
-                    transformOrigin={DefaultTransformOrigin}
-                    onClose={this.cancel}>
-                    <Card>
-                        <CardContent>
-                            <Typography variant="caption">
-                                {name}
-                            </Typography>
-                        </CardContent>
-                        <List dense>
-                            {this.state.filters.map((filter, index) =>
-                                <ListItem
-                                    key={index}>
-                                    <Checkbox
-                                        onClick={this.toggleFilter(filter)}
-                                        color="primary"
-                                        checked={filter.selected}
-                                        className={classes.checkbox} />
-                                    <ListItemText>
-                                        {filter.name}
-                                    </ListItemText>
-                                </ListItem>
-                            )}
-                        </List>
-                        <DataTableFiltersTree
-                            filters={this.state.filtersTree}
-                            onChange={filtersTree => this.setState({ filtersTree })} />
-                        <CardActions>
-                            <Button
-                                color="primary"
-                                variant='contained'
-                                size="small"
-                                onClick={this.submit}>
-                                Ok
-                            </Button>
-                            <Button
-                                color="primary"
-                                variant="outlined"
-                                size="small"
-                                onClick={this.cancel}>
-                                Cancel
-                            </Button>
-                        </CardActions >
-                    </Card>
-                </Popover>
-            </>;
-        }
-
-        static getDerivedStateFromProps(props: DataTableFilterProps, state: DataTableFilterState): DataTableFilterState {
-            return props.filters !== state.prevFilters
-                ? { ...state, filters: props.filters, prevFilters: props.filters }
-                : state;
-        }
-
-        open = () => {
-            this.setState({ anchorEl: this.icon.current || undefined });
-        }
-
-        submit = () => {
-            const { onChange } = this.props;
-            if (onChange) {
-                onChange(this.state.filters);
-            }
-            this.setState({ anchorEl: undefined });
-        }
-
-        cancel = () => {
-            this.setState(prev => ({
-                ...prev,
-                filters: prev.prevFilters,
-                anchorEl: undefined
-            }));
-        }
-
-        toggleFilter = (toggledFilter: DataTableFilterItem) => () => {
-            this.setState(prev => ({
-                ...prev,
-                filters: prev.filters.map(filter =>
-                    filter === toggledFilter
-                        ? { ...filter, selected: !filter.selected }
-                        : filter)
-            }));
-        }
-    }
-);
index d0b83b969695d00dfc4e31170e0d072f112e9055..3e4cc2123146fa0ff1bc098c3b80465bae56ac8f 100644 (file)
@@ -8,7 +8,6 @@ import { pipe } from 'lodash/fp';
 import { TableHead, TableCell, Typography, TableBody, Button, TableSortLabel } from "@material-ui/core";
 import * as Adapter from "enzyme-adapter-react-16";
 import { DataTable, DataColumns } from "./data-table";
-import { DataTableFilters } from "~/components/data-table-filters/data-table-filters";
 import { SortDirection, createDataColumn } from "./data-column";
 import { DataTableFiltersPopover } from '~/components/data-table-filters/data-table-filters-popover';
 import { createTree, setNode, initTreeNode } from '~/models/tree';
@@ -162,7 +161,7 @@ describe("<DataTable />", () => {
         expect(onSortToggle).toHaveBeenCalledWith(columns[0]);
     });
 
-    it("does not display <DataTableFilter /> if there is no filters provided", () => {
+    it("does not display <DataTableFiltersPopover /> if there is no filters provided", () => {
         const columns: DataColumns<string> = [{
             name: "Column 1",
             sortDirection: SortDirection.ASC,
@@ -180,7 +179,7 @@ describe("<DataTable />", () => {
             onRowDoubleClick={jest.fn()}
             onSortToggle={jest.fn()}
             onContextMenu={jest.fn()} />);
-        expect(dataTable.find(DataTableFilters)).toHaveLength(0);
+        expect(dataTable.find(DataTableFiltersPopover)).toHaveLength(0);
     });
 
     it("passes filter props to <DataTableFiltersPopover />", () => {
@@ -209,24 +208,4 @@ describe("<DataTable />", () => {
         dataTable.find(DataTableFiltersPopover).prop("onChange")([]);
         expect(onFiltersChange).toHaveBeenCalledWith([], columns[0]);
     });
-
-    // it("shows default view if there is no items", () => {
-    //     const columns: DataColumns<string> = [
-    //         createDataColumn({
-    //             name: "Column 1",
-    //             render: () => <span />,
-    //             selected: true,
-    //             configurable: true
-    //         }),
-    //     ];
-    //     const dataTable = mount(<DataTable
-    //         columns={columns}
-    //         items={[]}
-    //         onFiltersChange={jest.fn()}
-    //         onRowClick={jest.fn()}
-    //         onRowDoubleClick={jest.fn()}
-    //         onContextMenu={jest.fn()}
-    //         onSortToggle={jest.fn()} />);
-    //     expect(dataTable.find(DataTableDefaultView)).toHaveLength(1);
-    // });
 });
index 67858e9b59b3ae077f692a5d975eb38019407348..76fbf011eb3face1a3bffe266db9b55e7fe1781e 100644 (file)
@@ -98,15 +98,9 @@ export interface TreeProps<T> {
     /**
      * When set to true use radio buttons instead of checkboxes for item selection.
      * This does not guarantee radio group behavior (i.e item mutual exclusivity).
-     * Any item selection logic must be done in the toggleItemRadioButton callback prop.
+     * Any item selection logic must be done in the toggleItemActive callback prop.
      */
     useRadioButtons?: boolean;
-
-    /**
-     * Called when selection of an item in the tree is toggled via a radio button.
-     * Use this callback prop to implement any selection logic (i.e item mutual exclusivity).
-     */
-    toggleItemRadioButton?: (item: TreeItem<T>) => void;
 }
 
 export const Tree = withStyles(styles)(
@@ -151,8 +145,7 @@ export const Tree = withStyles(styles)(
                                 <Radio
                                     checked={it.selected}
                                     className={classes.checkbox}
-                                    color="primary"
-                                    onChange={this.handleRadioButtonChange(it)} />}
+                                    color="primary" />}
                             <div className={renderContainer}>
                                 {render(it, level)}
                             </div>
@@ -207,16 +200,6 @@ export const Tree = withStyles(styles)(
                 : undefined;
         }
 
-        handleRadioButtonChange = (item: TreeItem<T>) => {
-            const { toggleItemRadioButton } = this.props;
-            return toggleItemRadioButton
-                ? (event: React.ChangeEvent<HTMLInputElement>, checked: boolean) => {
-                    event.stopPropagation();
-                    toggleItemRadioButton(item);
-                }
-                : undefined;
-        }
-
         handleToggleItemOpen = (item: TreeItem<T>) => (event: React.MouseEvent<HTMLElement>) => {
             event.stopPropagation();
             this.props.toggleItemOpen(event, item);
index 67b267aeba16e14b182734b4f27ec20b1f360577..df44d62c52f12fe842c5abcfbc249366a6a2097a 100644 (file)
@@ -61,35 +61,38 @@ export const UserAttributesDialog = compose(
     );
 
 const attributes = (user: UserResource, classes: any) => {
-    const { uuid, ownerUuid, createdAt, modifiedAt, modifiedByClientUuid, modifiedByUserUuid, firstName, lastName, href, username } = user;
+    const { uuid, ownerUuid, createdAt, modifiedAt, modifiedByClientUuid, modifiedByUserUuid,
+        firstName, lastName, username, email, isActive, isAdmin } = user;
     return (
         <span>
             <Grid container direction="row">
                 <Grid item xs={5} className={classes.rightContainer}>
+                    {uuid && <Grid item>Uuid</Grid>}
                     {firstName && <Grid item>First name</Grid>}
                     {lastName && <Grid item>Last name</Grid>}
-                    {ownerUuid && <Grid item>Owner uuid</Grid>}
+                    {email && <Grid item>Email</Grid>}
+                    {username && <Grid item>Username</Grid>}
+                    {isActive && <Grid item>Is active</Grid>}
+                    {isAdmin && <Grid item>Is admin</Grid>}
                     {createdAt && <Grid item>Created at</Grid>}
                     {modifiedAt && <Grid item>Modified at</Grid>}
+                    {ownerUuid && <Grid item>Owner uuid</Grid>}
                     {modifiedByUserUuid && <Grid item>Modified by user uuid</Grid>}
                     {modifiedByClientUuid && <Grid item>Modified by client uuid</Grid>}
-                    {uuid && <Grid item>uuid</Grid>}
-                    {href && <Grid item>Href</Grid>}
-                    {username && <Grid item>Username</Grid>}
-                    {username && <Grid item>Username</Grid>}
                 </Grid>
                 <Grid item xs={7} className={classes.leftContainer}>
+                    <Grid item>{uuid}</Grid>
                     <Grid item>{firstName}</Grid>
                     <Grid item>{lastName}</Grid>
-                    <Grid item>{ownerUuid}</Grid>
+                    <Grid item>{email}</Grid>
+                    <Grid item>{username}</Grid>
+                    <Grid item>{isActive}</Grid>
+                    <Grid item>{isAdmin}</Grid>
                     <Grid item>{createdAt}</Grid>
                     <Grid item>{modifiedAt}</Grid>
+                    <Grid item>{ownerUuid}</Grid>
                     <Grid item>{modifiedByUserUuid}</Grid>
                     <Grid item>{modifiedByClientUuid}</Grid>
-                    <Grid item>{uuid}</Grid>
-                    <Grid item>{href}</Grid>
-                    <Grid item>{username}</Grid>
-                    <Grid item>{username}</Grid>
                 </Grid>
             </Grid>
         </span>
index a641ec63cd8b8c2a687e6fb68755ad7f2ecb17e2..49d880e5e2d87df537b10b5dc704132e8f8436de 100644 (file)
@@ -12,6 +12,7 @@ import { saveRequestedDate, loadVirtualMachinesUserData } from '~/store/virtual-
 import { RootState } from '~/store/store';
 import { ListResults } from '~/services/common-service/common-service';
 import { HelpIcon } from '~/components/icon/icon';
+// import * as CopyToClipboard from 'react-copy-to-clipboard';
 
 type CssRules = 'button' | 'codeSnippet' | 'link' | 'linkIcon' | 'rightAlign' | 'cardWithoutMachines' | 'icon';
 
@@ -59,6 +60,7 @@ const mapStateToProps = (state: RootState) => {
         requestedDate: state.virtualMachines.date,
         userUuid: state.auth.user!.uuid,
         helpText: state.auth.config.clusterConfig.Workbench.SSHHelpPageHTML,
+        hostSuffix: state.auth.config.clusterConfig.Workbench.SSHHelpHostSuffix || "",
         webShell: state.auth.config.clusterConfig.Services.WebShell.ExternalURL,
         ...state.virtualMachines
     };
@@ -75,6 +77,7 @@ interface VirtualMachinesPanelDataProps {
     userUuid: string;
     links: ListResults<any>;
     helpText: string;
+    hostSuffix: string;
     webShell: string;
 }
 
@@ -166,25 +169,30 @@ const virtualMachinesTable = (props: VirtualMachineProps) =>
             </TableRow>
         </TableHead>
         <TableBody>
-            {props.virtualMachines.items.map((it, index) =>
-                <TableRow key={index}>
-                    <TableCell>{it.hostname}</TableCell>
-                    <TableCell>{getUsername(props.links, props.userUuid)}</TableCell>
-                    <TableCell>ssh {getUsername(props.links, props.userUuid)}@{it.hostname}</TableCell>
-                    {props.webShell !== "" && <TableCell>
-                        <a href={`${props.webShell}${it.href}/webshell/${getUsername(props.links, props.userUuid)}`} target="_blank" className={props.classes.link}>
-                            Log in as {getUsername(props.links, props.userUuid)}
-                        </a>
-                    </TableCell>}
-                </TableRow>
-            )}
+            {props.virtualMachines.items.map(it =>
+                props.links.items.map(lk => {
+                    if (lk.tailUuid === props.userUuid) {
+                        const username = lk.properties.username;
+                        const command = `ssh ${username}@${it.hostname}${props.hostSuffix}`;
+                        return <TableRow key={lk.uuid}>
+                            <TableCell>{it.hostname}</TableCell>
+                            <TableCell>{username}</TableCell>
+                            <TableCell>
+                                {command}
+                            </TableCell>
+                            {props.webShell !== "" && <TableCell>
+                                <a href={`${props.webShell}${it.href}/webshell/${username}`} target="_blank" className={props.classes.link}>
+                                    Log in as {username}
+                                </a>
+                            </TableCell>}
+                        </TableRow>;
+                    }
+                    return;
+                }
+                ))}
         </TableBody>
     </Table>;
 
-const getUsername = (links: ListResults<any>, userUuid: string) => {
-    return links.items.map(it => it.tailUuid === userUuid ? it.properties.username : '');
-};
-
 const CardSSHSection = (props: VirtualMachineProps) =>
     <Grid item xs={12}>
         <Card>
index 1d7ce6efb90db7040f32dadad5ed24614c40bc83..917a0b255c67681aac2a6777209d511afb739582 100644 (file)
--- a/yarn.lock
+++ b/yarn.lock
@@ -619,19 +619,14 @@ acorn@^4.0.3, acorn@^4.0.4:
   integrity sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=
 
 acorn@^5.0.0, acorn@^5.5.3:
-  version "5.7.3"
-  resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.3.tgz#67aa231bf8812974b85235a96771eb6bd07ea279"
-  integrity sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==
+  version "5.7.4"
+  resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.4.tgz#3e8d8a9947d0599a1796d10225d7432f4a4acf5e"
+  integrity sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==
 
-acorn@^6.0.1:
-  version "6.4.0"
-  resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.0.tgz#b659d2ffbafa24baf5db1cdbb2c94a983ecd2784"
-  integrity sha512-gac8OEcQ2Li1dxIEWGZzsp2BitJxwkwcOm0zHAJLcPJaVvm58FRnk6RkuLRpU1EujipU2ZFODv2P9DLMfnV8mw==
-
-acorn@^6.2.1:
-  version "6.3.0"
-  resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.3.0.tgz#0087509119ffa4fc0a0041d1e93a417e68cb856e"
-  integrity sha512-/czfa8BwS88b9gWQVhc8eknunSA2DoJpJyTQkhheIf5E48u1N0R4q/YxxsAeqRrmK9TQ/uYfgLDfZo91UlANIA==
+acorn@^6.0.1, acorn@^6.2.1:
+  version "6.4.1"
+  resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.1.tgz#531e58ba3f51b9dacb9a6646ca4debf5b14ca474"
+  integrity sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==
 
 address@1.0.3:
   version "1.0.3"
@@ -4818,13 +4813,14 @@ handle-thing@^2.0.0:
   integrity sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ==
 
 handlebars@^4.0.3:
-  version "4.7.2"
-  resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.2.tgz#01127b3840156a0927058779482031afe0e730d7"
-  integrity sha512-4PwqDL2laXtTWZghzzCtunQUTLbo31pcCJrd/B/9JP8XbhVzpS5ZXuKqlOzsd1rtcaLo4KqAn8nl8mkknS4MHw==
+  version "4.7.6"
+  resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.6.tgz#d4c05c1baf90e9945f77aa68a7a219aa4a7df74e"
+  integrity sha512-1f2BACcBfiwAfStCKZNrUCgqNZkGsAT7UM3kkYtXuLo0KnaVfjKOyf7PRzB6++aK9STyT1Pd2ZCPe3EGOXleXA==
   dependencies:
+    minimist "^1.2.5"
     neo-async "^2.6.0"
-    optimist "^0.6.1"
     source-map "^0.6.1"
+    wordwrap "^1.0.0"
   optionalDependencies:
     uglify-js "^3.1.4"
 
@@ -7165,15 +7161,10 @@ minimist@0.0.8:
   resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
   integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=
 
-minimist@1.2.0, minimist@^1.1.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
-  integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=
-
-minimist@~0.0.1:
-  version "0.0.10"
-  resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf"
-  integrity sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=
+minimist@^1.1.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5:
+  version "1.2.5"
+  resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
+  integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
 
 mississippi@^2.0.0:
   version "2.0.0"
@@ -7744,14 +7735,6 @@ opn@^5.1.0, opn@^5.5.0:
   dependencies:
     is-wsl "^1.1.0"
 
-optimist@^0.6.1:
-  version "0.6.1"
-  resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686"
-  integrity sha1-2j6nRob6IaGaERwybpDrFaAZZoY=
-  dependencies:
-    minimist "~0.0.1"
-    wordwrap "~0.0.2"
-
 optionator@^0.8.1:
   version "0.8.3"
   resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495"
@@ -11512,10 +11495,10 @@ wordwrap@0.0.2:
   resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f"
   integrity sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=
 
-wordwrap@~0.0.2:
-  version "0.0.3"
-  resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107"
-  integrity sha1-o9XabNXAvAAI03I0u68b7WMFkQc=
+wordwrap@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
+  integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=
 
 worker-farm@^1.3.1, worker-farm@^1.5.2, worker-farm@^1.7.0:
   version "1.7.0"