[ARVADOS-WORKBENCH2] created: 1.2.0-937-g3a3de86

Git user git at public.curoverse.com
Sat Nov 24 16:25:08 EST 2018


        at  3a3de86b86ef60fc86f1190d42bc8a2471ab5276 (commit)


commit 3a3de86b86ef60fc86f1190d42bc8a2471ab5276
Author: Janicki Artur <artur.janicki at contractors.roche.com>
Date:   Sat Nov 24 22:24:54 2018 +0100

    add table view, actions and dialogs
    
    Feature #14528_table_view_and_actions
    
    Arvados-DCO-1.1-Signed-off-by: Janicki Artur <artur.janicki at contractors.roche.com>

diff --git a/src/components/icon/icon.tsx b/src/components/icon/icon.tsx
index b46195d..a0f58be 100644
--- a/src/components/icon/icon.tsx
+++ b/src/components/icon/icon.tsx
@@ -50,6 +50,7 @@ import SettingsEthernet from '@material-ui/icons/SettingsEthernet';
 import Star from '@material-ui/icons/Star';
 import StarBorder from '@material-ui/icons/StarBorder';
 import Warning from '@material-ui/icons/Warning';
+import VpnKey from '@material-ui/icons/VpnKey';
 
 export type IconType = React.SFC<{ className?: string, style?: object }>;
 
@@ -74,6 +75,7 @@ export const HelpIcon: IconType = (props) => <Help {...props} />;
 export const HelpOutlineIcon: IconType = (props) => <HelpOutline {...props} />;
 export const ImportContactsIcon: IconType = (props) => <ImportContacts {...props} />;
 export const InputIcon: IconType = (props) => <InsertDriveFile {...props} />;
+export const KeyIcon: IconType = (props) => <VpnKey {...props} />;
 export const LogIcon: IconType = (props) => <SettingsEthernet {...props} />;
 export const MailIcon: IconType = (props) => <Mail {...props} />;
 export const MoreOptionsIcon: IconType = (props) => <MoreVert {...props} />;
diff --git a/src/index.tsx b/src/index.tsx
index 922720a..88fd229 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -49,6 +49,7 @@ import { DragDropContextProvider } from 'react-dnd';
 import HTML5Backend from 'react-dnd-html5-backend';
 import { initAdvanceFormProjectsTree } from '~/store/search-bar/search-bar-actions';
 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';
 
 console.log(`Starting arvados [${getBuildInfo()}]`);
 
@@ -66,6 +67,7 @@ addMenuActionSet(ContextMenuKind.PROCESS, processActionSet);
 addMenuActionSet(ContextMenuKind.PROCESS_RESOURCE, processResourceActionSet);
 addMenuActionSet(ContextMenuKind.TRASH, trashActionSet);
 addMenuActionSet(ContextMenuKind.REPOSITORY, repositoryActionSet);
+addMenuActionSet(ContextMenuKind.SSH_KEY, sshKeyActionSet);
 
 fetchConfig()
     .then(({ config, apiHost }) => {
diff --git a/src/models/resource.ts b/src/models/resource.ts
index 520520f..5fa6179 100644
--- a/src/models/resource.ts
+++ b/src/models/resource.ts
@@ -29,6 +29,7 @@ export enum ResourceKind {
     PROCESS = "arvados#containerRequest",
     PROJECT = "arvados#group",
     REPOSITORY = "arvados#repository",
+    SSH_KEY = "arvados#authorizedKeys",
     USER = "arvados#user",
     WORKFLOW = "arvados#workflow",
     NONE = "arvados#none"
diff --git a/src/store/auth/auth-action.ts b/src/store/auth/auth-action.ts
index 3658c58..28559b1 100644
--- a/src/store/auth/auth-action.ts
+++ b/src/store/auth/auth-action.ts
@@ -4,7 +4,7 @@
 
 import { ofType, unionize, UnionOf } from '~/common/unionize';
 import { Dispatch } from "redux";
-import { reset, stopSubmit } from 'redux-form';
+import { reset, stopSubmit, startSubmit } from 'redux-form';
 import { AxiosInstance } from "axios";
 import { RootState } from "../store";
 import { snackbarActions } from '~/store/snackbar/snackbar-actions';
@@ -23,10 +23,14 @@ export const authActions = unionize({
     USER_DETAILS_REQUEST: {},
     USER_DETAILS_SUCCESS: ofType<User>(),
     SET_SSH_KEYS: ofType<SshKeyResource[]>(),
-    ADD_SSH_KEY: ofType<SshKeyResource>()
+    ADD_SSH_KEY: ofType<SshKeyResource>(),
+    REMOVE_SSH_KEY: ofType<string>()
 });
 
 export const SSH_KEY_CREATE_FORM_NAME = 'sshKeyCreateFormName';
+export const SSH_KEY_PUBLIC_KEY_DIALOG = 'sshKeyPublicKeyDialog';
+export const SSH_KEY_REMOVE_DIALOG = 'sshKeyRemoveDialog';
+export const SSH_KEY_ATTRIBUTES_DIALOG = 'sshKeyAttributesDialog';
 
 export interface SshKeyCreateFormDialogData {
     publicKey: string;
@@ -87,20 +91,51 @@ export const getUserDetails = () => (dispatch: Dispatch, getState: () => RootSta
 
 export const openSshKeyCreateDialog = () => dialogActions.OPEN_DIALOG({ id: SSH_KEY_CREATE_FORM_NAME, data: {} });
 
+export const openPublicKeyDialog = (name: string, publicKey: string) =>
+    dialogActions.OPEN_DIALOG({ id: SSH_KEY_PUBLIC_KEY_DIALOG, data: { name, publicKey } });
+
+export const openSshKeyAttributesDialog = (index: number) =>
+    (dispatch: Dispatch, getState: () => RootState) => {
+        const sshKey = getState().auth.sshKeys[index];
+        dispatch(dialogActions.OPEN_DIALOG({ id: SSH_KEY_ATTRIBUTES_DIALOG, data: { sshKey } }));
+    };
+
+export const openSshKeyRemoveDialog = (uuid: string) =>
+    (dispatch: Dispatch, getState: () => RootState) => {
+        dispatch(dialogActions.OPEN_DIALOG({
+            id: SSH_KEY_REMOVE_DIALOG,
+            data: {
+                title: 'Remove public key',
+                text: 'Are you sure you want to remove this public key?',
+                confirmButtonLabel: 'Remove',
+                uuid
+            }
+        }));
+    };
+
+export const removeSshKey = (uuid: string) =>
+    async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+        dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Removing ...' }));
+        await services.authorizedKeysService.delete(uuid);
+        dispatch(authActions.REMOVE_SSH_KEY(uuid));
+        dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Public Key has been successfully removed.', hideDuration: 2000 }));
+    };
+
 export const createSshKey = (data: SshKeyCreateFormDialogData) =>
     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+        const userUuid = getState().auth.user!.uuid;
+        const { name, publicKey } = data;
+        dispatch(startSubmit(SSH_KEY_CREATE_FORM_NAME));
         try {
-            const userUuid = getState().auth.user!.uuid;
-            const { name, publicKey } = data;
             const newSshKey = await services.authorizedKeysService.create({
-                name, 
+                name,
                 publicKey,
                 keyType: KeyType.SSH,
                 authorizedUserUuid: userUuid
             });
+            dispatch(authActions.ADD_SSH_KEY(newSshKey));
             dispatch(dialogActions.CLOSE_DIALOG({ id: SSH_KEY_CREATE_FORM_NAME }));
             dispatch(reset(SSH_KEY_CREATE_FORM_NAME));
-            dispatch(authActions.ADD_SSH_KEY(newSshKey));
             dispatch(snackbarActions.OPEN_SNACKBAR({
                 message: "Public key has been successfully created.",
                 hideDuration: 2000
diff --git a/src/store/auth/auth-reducer.ts b/src/store/auth/auth-reducer.ts
index 8f234da..a8e4340 100644
--- a/src/store/auth/auth-reducer.ts
+++ b/src/store/auth/auth-reducer.ts
@@ -10,7 +10,7 @@ import { SshKeyResource } from '~/models/ssh-key';
 export interface AuthState {
     user?: User;
     apiToken?: string;
-    sshKeys?: SshKeyResource[];
+    sshKeys: SshKeyResource[];
 }
 
 const initialState: AuthState = {
@@ -19,13 +19,13 @@ const initialState: AuthState = {
     sshKeys: []
 };
 
-export const authReducer = (services: ServiceRepository) => (state: AuthState = initialState, action: AuthAction) => {
+export const authReducer = (services: ServiceRepository) => (state = initialState, action: AuthAction) => {
     return authActions.match(action, {
         SAVE_API_TOKEN: (token: string) => {
             return {...state, apiToken: token};
         },
         INIT: ({ user, token }) => {
-            return { user, apiToken: token };
+            return { ...state, user, apiToken: token };
         },
         LOGIN: () => {
             return state;
@@ -40,7 +40,10 @@ export const authReducer = (services: ServiceRepository) => (state: AuthState =
             return {...state, sshKeys};
         },
         ADD_SSH_KEY: (sshKey: SshKeyResource) => {
-            return { ...state, sshKeys: state.sshKeys!.concat(sshKey) };
+            return { ...state, sshKeys: state.sshKeys.concat(sshKey) };
+        },
+        REMOVE_SSH_KEY: (uuid: string) => {
+            return { ...state, sshKeys: state.sshKeys.filter((sshKey) => sshKey.uuid !== uuid )};
         },
         default: () => state
     });
diff --git a/src/store/context-menu/context-menu-actions.ts b/src/store/context-menu/context-menu-actions.ts
index 596ac87..0a6b5a8 100644
--- a/src/store/context-menu/context-menu-actions.ts
+++ b/src/store/context-menu/context-menu-actions.ts
@@ -14,6 +14,7 @@ import { isSidePanelTreeCategory } from '~/store/side-panel-tree/side-panel-tree
 import { extractUuidKind, ResourceKind } from '~/models/resource';
 import { Process } from '~/store/processes/process';
 import { RepositoryResource } from '~/models/repositories';
+import { SshKeyResource } from '~/models/ssh-key';
 
 export const contextMenuActions = unionize({
     OPEN_CONTEXT_MENU: ofType<{ position: ContextMenuPosition, resource: ContextMenuResource }>(),
@@ -73,6 +74,18 @@ export const openRepositoryContextMenu = (event: React.MouseEvent<HTMLElement>,
             }));
     };
 
+export const openSshKeyContextMenu = (event: React.MouseEvent<HTMLElement>, index: number, sshKey: SshKeyResource) =>
+    (dispatch: Dispatch) => {
+        dispatch<any>(openContextMenu(event, {
+            name: '',
+            uuid: sshKey.uuid,
+            ownerUuid: sshKey.ownerUuid,
+            kind: ResourceKind.SSH_KEY,
+            menuKind: ContextMenuKind.SSH_KEY,
+            index
+        }));
+    };
+
 export const openRootProjectContextMenu = (event: React.MouseEvent<HTMLElement>, projectUuid: string) =>
     (dispatch: Dispatch, getState: () => RootState) => {
         const res = getResource<UserResource>(projectUuid)(getState().resources);
diff --git a/src/views-components/context-menu/action-sets/ssh-key-action-set.ts b/src/views-components/context-menu/action-sets/ssh-key-action-set.ts
new file mode 100644
index 0000000..3fa2f16
--- /dev/null
+++ b/src/views-components/context-menu/action-sets/ssh-key-action-set.ts
@@ -0,0 +1,27 @@
+// 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 { openSshKeyRemoveDialog, openSshKeyAttributesDialog } from '~/store/auth/auth-action';
+
+export const sshKeyActionSet: ContextMenuActionSet = [[{
+    name: "Attributes",
+    icon: AttributesIcon,
+    execute: (dispatch, { index }) => {
+        dispatch<any>(openSshKeyAttributesDialog(index!));
+    }
+}, {
+    name: "Advanced",
+    icon: AdvancedIcon,
+    execute: (dispatch, { uuid, index }) => {
+        // ToDo
+    }
+}, {
+    name: "Remove",
+    icon: RemoveIcon,
+    execute: (dispatch, { uuid }) => {
+        dispatch<any>(openSshKeyRemoveDialog(uuid));
+    }
+}]];
diff --git a/src/views-components/context-menu/context-menu.tsx b/src/views-components/context-menu/context-menu.tsx
index 30ecc98..af5aaa9 100644
--- a/src/views-components/context-menu/context-menu.tsx
+++ b/src/views-components/context-menu/context-menu.tsx
@@ -69,5 +69,6 @@ export enum ContextMenuKind {
     PROCESS = "Process",
     PROCESS_RESOURCE = 'ProcessResource',
     PROCESS_LOGS = "ProcessLogs",
-    REPOSITORY = "Repository"
+    REPOSITORY = "Repository",
+    SSH_KEY = "SshKey"
 }
diff --git a/src/views-components/ssh-keys-dialog/attributes-dialog.tsx b/src/views-components/ssh-keys-dialog/attributes-dialog.tsx
new file mode 100644
index 0000000..0a2a681
--- /dev/null
+++ b/src/views-components/ssh-keys-dialog/attributes-dialog.tsx
@@ -0,0 +1,65 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from "react";
+import { compose } from 'redux';
+import { withStyles, Dialog, DialogTitle, DialogContent, DialogActions, Button, StyleRulesCallback, WithStyles, Typography, Grid } from '@material-ui/core';
+import { WithDialogProps, withDialog } from "~/store/dialog/with-dialog";
+import { SSH_KEY_ATTRIBUTES_DIALOG } from '~/store/auth/auth-action';
+import { ArvadosTheme } from '~/common/custom-theme';
+import { SshKeyResource } from "~/models/ssh-key";
+
+type CssRules = 'root';
+
+const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
+    root: {
+        fontSize: '0.875rem',
+        '& div:nth-child(odd)': {
+            textAlign: 'right',
+            color: theme.palette.grey["500"]
+        }
+    }
+});
+
+interface AttributesSshKeyDialogDataProps {
+    sshKey: SshKeyResource;
+}
+
+export const AttributesSshKeyDialog = compose(
+    withDialog(SSH_KEY_ATTRIBUTES_DIALOG),
+    withStyles(styles))(
+        ({ open, closeDialog, data, classes }: WithDialogProps<AttributesSshKeyDialogDataProps> & WithStyles<CssRules>) =>
+            <Dialog open={open}
+                onClose={closeDialog}
+                fullWidth
+                maxWidth='sm'>
+                <DialogTitle>Attributes</DialogTitle>
+                <DialogContent>
+                    {data.sshKey && <Grid container direction="row" spacing={16} className={classes.root}>
+                        <Grid item xs={5}>Name</Grid>
+                        <Grid item xs={7}>{data.sshKey.name}</Grid>
+                        <Grid item xs={5}>Owner uuid</Grid>
+                        <Grid item xs={7}>{data.sshKey.ownerUuid}</Grid>
+                        <Grid item xs={5}>Created at</Grid>
+                        <Grid item xs={7}>{data.sshKey.createdAt}</Grid>
+                        <Grid item xs={5}>Modified at</Grid>
+                        <Grid item xs={7}>{data.sshKey.modifiedAt}</Grid>
+                        <Grid item xs={5}>Modified by user uuid</Grid>
+                        <Grid item xs={7}>{data.sshKey.modifiedByUserUuid}</Grid>
+                        <Grid item xs={5}>Modified by client uuid</Grid>
+                        <Grid item xs={7}>{data.sshKey.modifiedByClientUuid}</Grid>
+                        <Grid item xs={5}>uuid</Grid>
+                        <Grid item xs={7}>{data.sshKey.uuid}</Grid>
+                    </Grid>}
+                </DialogContent>
+                <DialogActions>
+                    <Button
+                        variant='flat'
+                        color='primary'
+                        onClick={closeDialog}>
+                        Close
+                    </Button>
+                </DialogActions>
+            </Dialog>
+    );
\ No newline at end of file
diff --git a/src/views-components/ssh-keys-dialog/public-key-dialog.tsx b/src/views-components/ssh-keys-dialog/public-key-dialog.tsx
new file mode 100644
index 0000000..77c6cfd
--- /dev/null
+++ b/src/views-components/ssh-keys-dialog/public-key-dialog.tsx
@@ -0,0 +1,55 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from "react";
+import { compose } from 'redux';
+import { withStyles, Dialog, DialogTitle, DialogContent, DialogActions, Button, StyleRulesCallback, WithStyles } from '@material-ui/core';
+import { WithDialogProps, withDialog } from "~/store/dialog/with-dialog";
+import { SSH_KEY_PUBLIC_KEY_DIALOG } from '~/store/auth/auth-action';
+import { ArvadosTheme } from '~/common/custom-theme';
+import { DefaultCodeSnippet } from '~/components/default-code-snippet/default-code-snippet';
+
+type CssRules = 'codeSnippet';
+
+const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
+    codeSnippet: {
+        borderRadius: theme.spacing.unit * 0.5,
+        border: '1px solid',
+        borderColor: theme.palette.grey["400"],
+        '& pre': {
+            wordWrap: 'break-word',
+            whiteSpace: 'pre-wrap'
+        }
+    },
+});
+
+interface PublicKeyDialogDataProps {
+    name: string;
+    publicKey: string;
+}
+
+export const PublicKeyDialog = compose(
+    withDialog(SSH_KEY_PUBLIC_KEY_DIALOG),
+    withStyles(styles))(
+        ({ open, closeDialog, data, classes }: WithDialogProps<PublicKeyDialogDataProps> & WithStyles<CssRules>) =>
+            <Dialog open={open}
+                onClose={closeDialog}
+                fullWidth
+                maxWidth='sm'>
+                <DialogTitle>{data.name} - SSH Key</DialogTitle>
+                <DialogContent>
+                    {data && data.publicKey && <DefaultCodeSnippet
+                        className={classes.codeSnippet}
+                        lines={data.publicKey.split(' ')} />}
+                </DialogContent>
+                <DialogActions>
+                    <Button
+                        variant='flat'
+                        color='primary'
+                        onClick={closeDialog}>
+                        Close
+                    </Button>
+                </DialogActions>
+            </Dialog>
+    );
\ No newline at end of file
diff --git a/src/views-components/ssh-keys-dialog/remove-dialog.tsx b/src/views-components/ssh-keys-dialog/remove-dialog.tsx
new file mode 100644
index 0000000..8077f21
--- /dev/null
+++ b/src/views-components/ssh-keys-dialog/remove-dialog.tsx
@@ -0,0 +1,20 @@
+// 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 { SSH_KEY_REMOVE_DIALOG, removeSshKey } from '~/store/auth/auth-action';
+
+const mapDispatchToProps = (dispatch: Dispatch, props: WithDialogProps<any>) => ({
+    onConfirm: () => {
+        props.closeDialog();
+        dispatch<any>(removeSshKey(props.data.uuid));
+    }
+});
+
+export const RemoveSshKeyDialog = compose(
+    withDialog(SSH_KEY_REMOVE_DIALOG),
+    connect(null, mapDispatchToProps)
+)(ConfirmationDialog);
\ No newline at end of file
diff --git a/src/views/ssh-key-panel/ssh-key-panel-root.tsx b/src/views/ssh-key-panel/ssh-key-panel-root.tsx
index f752228..869662d 100644
--- a/src/views/ssh-key-panel/ssh-key-panel-root.tsx
+++ b/src/views/ssh-key-panel/ssh-key-panel-root.tsx
@@ -3,53 +3,114 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import * as React from 'react';
-import { StyleRulesCallback, WithStyles, withStyles, Card, CardContent, Button, Typography } from '@material-ui/core';
+import { StyleRulesCallback, WithStyles, withStyles, Card, CardContent, Button, Typography, Grid, Table, TableHead, TableRow, TableCell, TableBody, Tooltip, IconButton } from '@material-ui/core';
 import { ArvadosTheme } from '~/common/custom-theme';
 import { SshKeyResource } from '~/models/ssh-key';
+import { AddIcon, MoreOptionsIcon, KeyIcon } from '~/components/icon/icon';
 
-
-type CssRules = 'root' | 'link';
+type CssRules = 'root' | 'link' | 'buttonContainer' | 'table' | 'tableRow' | 'keyIcon';
 
 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     root: {
-       width: '100%'
+       width: '100%',
+       overflow: 'auto'
     },
     link: {
         color: theme.palette.primary.main,
         textDecoration: 'none',
         margin: '0px 4px'
+    },
+    buttonContainer: {
+        textAlign: 'right'
+    },
+    table: {
+        marginTop: theme.spacing.unit
+    },
+    tableRow: {
+        '& td, th': {
+            whiteSpace: 'nowrap'
+        }
+    },
+    keyIcon: {
+        color: theme.palette.primary.main
     }
 });
 
 export interface SshKeyPanelRootActionProps {
-    onClick: () => void;
+    openSshKeyCreateDialog: () => void;
+    openRowOptions: (event: React.MouseEvent<HTMLElement>, index: number, sshKey: SshKeyResource) => void;
+    openPublicKeyDialog: (name: string, publicKey: string) => void;
 }
 
 export interface SshKeyPanelRootDataProps {
-    sshKeys?: SshKeyResource[];
+    sshKeys: SshKeyResource[];
+    hasKeys: boolean;
 }
 
 type SshKeyPanelRootProps = SshKeyPanelRootDataProps & SshKeyPanelRootActionProps & WithStyles<CssRules>;
 
 export const SshKeyPanelRoot = withStyles(styles)(
-    ({ classes, sshKeys, onClick }: SshKeyPanelRootProps) =>
+    ({ classes, sshKeys, openSshKeyCreateDialog, openPublicKeyDialog, hasKeys, openRowOptions }: SshKeyPanelRootProps) =>
         <Card className={classes.root}>
             <CardContent>
-                <Typography variant='body1' paragraph={true}>
-                    You have not yet set up an SSH public key for use with Arvados.
-                    <a href='https://doc.arvados.org/user/getting_started/ssh-access-unix.html' target='blank' className={classes.link}>
-                        Learn more.
-                    </a>
-                </Typography>
-                <Typography variant='body1' paragraph={true}>
-                    When you have an SSH key you would like to use, add it using button below.
-                </Typography>
-                <Button
-                    onClick={onClick}
-                    color="primary"
-                    variant="contained">
-                    Add New Ssh Key
-                </Button>
+                <Grid container direction="row">
+                    <Grid item xs={8}>
+                        { !hasKeys && <Typography variant='body1' paragraph={true} >
+                            You have not yet set up an SSH public key for use with Arvados.
+                            <a href='https://doc.arvados.org/user/getting_started/ssh-access-unix.html'
+                                target='blank' className={classes.link}>
+                                Learn more.
+                            </a>
+                        </Typography>}
+                        { !hasKeys && <Typography variant='body1' paragraph={true}>
+                            When you have an SSH key you would like to use, add it using button below.
+                        </Typography> }
+                    </Grid>
+                    <Grid item xs={4} className={classes.buttonContainer}>
+                        <Button onClick={openSshKeyCreateDialog} color="primary" variant="contained">
+                            <AddIcon /> Add New Ssh Key
+                        </Button>
+                    </Grid>
+                </Grid>
+                <Grid item xs={12}>
+                    {hasKeys && <Table className={classes.table}>
+                        <TableHead>
+                            <TableRow className={classes.tableRow}>
+                                <TableCell>Name</TableCell>
+                                <TableCell>UUID</TableCell>
+                                <TableCell>Authorized user</TableCell>
+                                <TableCell>Expires at</TableCell>
+                                <TableCell>Key type</TableCell>
+                                <TableCell>Public Key</TableCell>
+                                <TableCell />
+                            </TableRow>
+                        </TableHead>
+                        <TableBody>
+                            {sshKeys.map((sshKey, index) =>
+                                <TableRow key={index} className={classes.tableRow}>
+                                    <TableCell>{sshKey.name}</TableCell>
+                                    <TableCell>{sshKey.uuid}</TableCell>
+                                    <TableCell>{sshKey.authorizedUserUuid}</TableCell>
+                                    <TableCell>{sshKey.expiresAt || '(none)'}</TableCell>
+                                    <TableCell>{sshKey.keyType}</TableCell>
+                                    <TableCell>
+                                        <Tooltip title="Public Key" disableFocusListener>
+                                            <IconButton onClick={() => openPublicKeyDialog(sshKey.name, sshKey.publicKey)}>
+                                                <KeyIcon className={classes.keyIcon} />
+                                            </IconButton>
+                                        </Tooltip>
+                                    </TableCell>
+                                    <TableCell>
+                                        <Tooltip title="More options" disableFocusListener>
+                                            <IconButton onClick={event => openRowOptions(event, index, sshKey)}>
+                                                <MoreOptionsIcon />
+                                            </IconButton>
+                                        </Tooltip>
+                                    </TableCell>
+                                </TableRow>)}
+                        </TableBody>
+                    </Table>}
+                </Grid>
             </CardContent>
         </Card>
     );
\ No newline at end of file
diff --git a/src/views/ssh-key-panel/ssh-key-panel.tsx b/src/views/ssh-key-panel/ssh-key-panel.tsx
index f600677..c7e3516 100644
--- a/src/views/ssh-key-panel/ssh-key-panel.tsx
+++ b/src/views/ssh-key-panel/ssh-key-panel.tsx
@@ -5,18 +5,26 @@
 import { RootState } from '~/store/store';
 import { Dispatch } from 'redux';
 import { connect } from 'react-redux';
+import { openSshKeyCreateDialog, openPublicKeyDialog } from '~/store/auth/auth-action';
+import { openSshKeyContextMenu } from '~/store/context-menu/context-menu-actions';
 import { SshKeyPanelRoot, SshKeyPanelRootDataProps, SshKeyPanelRootActionProps } from '~/views/ssh-key-panel/ssh-key-panel-root';
-import { openSshKeyCreateDialog } from '~/store/auth/auth-action';
 
 const mapStateToProps = (state: RootState): SshKeyPanelRootDataProps => {
     return {
-        sshKeys: state.auth.sshKeys
+        sshKeys: state.auth.sshKeys,
+        hasKeys: state.auth.sshKeys!.length > 0
     };
 };
 
 const mapDispatchToProps = (dispatch: Dispatch): SshKeyPanelRootActionProps => ({
-    onClick: () => {
-        dispatch(openSshKeyCreateDialog());
+    openSshKeyCreateDialog: () => {
+        dispatch<any>(openSshKeyCreateDialog());
+    },
+    openRowOptions: (event, index, sshKey) => {
+        dispatch<any>(openSshKeyContextMenu(event, index, sshKey));
+    },
+    openPublicKeyDialog: (name: string, publicKey: string) => {
+        dispatch<any>(openPublicKeyDialog(name, publicKey));
     }
 });
 
diff --git a/src/views/workbench/workbench.tsx b/src/views/workbench/workbench.tsx
index ebdf57c..84c8e24 100644
--- a/src/views/workbench/workbench.tsx
+++ b/src/views/workbench/workbench.tsx
@@ -55,6 +55,9 @@ import { RepositoryAttributesDialog } from '~/views-components/repository-attrib
 import { CreateRepositoryDialog } from '~/views-components/dialog-forms/create-repository-dialog';
 import { RemoveRepositoryDialog } from '~/views-components/repository-remove-dialog/repository-remove-dialog';
 import { CreateSshKeyDialog } from '~/views-components/dialog-forms/create-ssh-key-dialog';
+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';
 
 type CssRules = 'root' | 'container' | 'splitter' | 'asidePanel' | 'contentWrapper' | 'content';
 
@@ -135,6 +138,7 @@ export const WorkbenchPanel =
                 <DetailsPanel />
             </Grid>
             <AdvancedTabDialog />
+            <AttributesSshKeyDialog />
             <ChangeWorkflowDialog />
             <ContextMenu />
             <CopyCollectionDialog />
@@ -150,12 +154,14 @@ export const WorkbenchPanel =
             <MoveProcessDialog />
             <MoveProjectDialog />
             <MultipleFilesRemoveDialog />
+            <PublicKeyDialog />
             <PartialCopyCollectionDialog />
             <ProcessCommandDialog />
             <ProcessInputDialog />
             <ProjectPropertiesDialog />
             <RemoveProcessDialog />
             <RemoveRepositoryDialog />
+            <RemoveSshKeyDialog />
             <RenameFileDialog />
             <RepositoryAttributesDialog />
             <RepositoriesSampleGitDialog />

-----------------------------------------------------------------------


hooks/post-receive
-- 




More information about the arvados-commits mailing list