Merge branch '21128-toolbar-context-menu'
[arvados-workbench2.git] / src / views / process-panel / process-cmd-card.tsx
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 import React from 'react';
6 import {
7     StyleRulesCallback,
8     WithStyles,
9     withStyles,
10     Card,
11     CardHeader,
12     IconButton,
13     CardContent,
14     Tooltip,
15     Typography,
16     Grid,
17 } from '@material-ui/core';
18 import { ArvadosTheme } from 'common/custom-theme';
19 import { CloseIcon, CommandIcon, CopyIcon } from 'components/icon/icon';
20 import { MPVPanelProps } from 'components/multi-panel-view/multi-panel-view';
21 import { DefaultCodeSnippet } from 'components/default-code-snippet/default-code-snippet';
22 import { Process } from 'store/processes/process';
23 import shellescape from 'shell-escape';
24 import CopyToClipboard from 'react-copy-to-clipboard';
25
26 type CssRules = 'card' | 'content' | 'title' | 'header' | 'avatar' | 'iconHeader';
27
28 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
29     card: {
30         height: '100%'
31     },
32     header: {
33         paddingTop: theme.spacing.unit,
34         paddingBottom: theme.spacing.unit,
35     },
36     iconHeader: {
37         fontSize: '1.875rem',
38         color: theme.customs.colors.greyL,
39     },
40     avatar: {
41         alignSelf: 'flex-start',
42         paddingTop: theme.spacing.unit * 0.5
43     },
44     content: {
45         padding: theme.spacing.unit * 1.0,
46         paddingTop: theme.spacing.unit * 0.5,
47         '&:last-child': {
48             paddingBottom: theme.spacing.unit * 1,
49         }
50     },
51     title: {
52         overflow: 'hidden',
53         paddingTop: theme.spacing.unit * 0.5,
54         color: theme.customs.colors.greyD,
55         fontSize: '1.875rem'  
56     },
57 });
58
59 interface ProcessCmdCardDataProps {
60   process: Process;
61   onCopy: (text: string) => void;
62 }
63
64 type ProcessCmdCardProps = ProcessCmdCardDataProps & WithStyles<CssRules> & MPVPanelProps;
65
66 export const ProcessCmdCard = withStyles(styles)(
67   ({
68     process,
69     onCopy,
70     classes,
71     doHidePanel,
72   }: ProcessCmdCardProps) => {
73     const command = process.containerRequest.command.map((v) =>
74       shellescape([v]) // Escape each arg separately
75     );
76
77     let formattedCommand = [...command];
78     formattedCommand.forEach((item, i, arr) => {
79       // Indent lines after the first
80       const indent = i > 0 ? '  ' : '';
81       // Escape newlines on every non-last arg when there are multiple lines
82       const lineBreak = arr.length > 1 && i < arr.length - 1 ? ' \\' : '';
83       arr[i] = `${indent}${item}${lineBreak}`;
84     });
85
86     return (
87       <Card className={classes.card}>
88         <CardHeader
89           className={classes.header}
90           classes={{
91             content: classes.title,
92             avatar: classes.avatar,
93           }}
94           avatar={<CommandIcon className={classes.iconHeader} />}
95           title={
96             <Typography noWrap variant="h6" color="inherit">
97               Command
98             </Typography>
99           }
100           action={
101             <Grid container direction="row" alignItems="center">
102               <Grid item>
103                 <Tooltip title="Copy to clipboard" disableFocusListener>
104                   <IconButton>
105                     <CopyToClipboard
106                       text={command.join(" ")}
107                       onCopy={() => onCopy("Command copied to clipboard")}
108                     >
109                       <CopyIcon />
110                     </CopyToClipboard>
111                   </IconButton>
112                 </Tooltip>
113               </Grid>
114               <Grid item>
115                 {doHidePanel && (
116                   <Tooltip
117                     title={`Close Command Panel`}
118                     disableFocusListener
119                   >
120                     <IconButton onClick={doHidePanel}>
121                       <CloseIcon />
122                     </IconButton>
123                   </Tooltip>
124                 )}
125               </Grid>
126             </Grid>
127           }
128         />
129         <CardContent className={classes.content}>
130           <DefaultCodeSnippet lines={formattedCommand} linked />
131         </CardContent>
132       </Card>
133     );
134   }
135 );