Merge branch '15814-wb2-secrets' refs #15814
[arvados.git] / services / workbench2 / 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 { DefaultVirtualCodeSnippet } from 'components/default-code-snippet/default-virtual-code-snippet';
22 import { Process } from 'store/processes/process';
23 import shellescape from 'shell-escape';
24 import CopyResultToClipboard from 'components/copy-to-clipboard/copy-result-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: 0,
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         height: `calc(100% - ${theme.spacing.unit * 6}px)`,
46         padding: theme.spacing.unit * 1.0,
47         paddingTop: 0,
48         '&:last-child': {
49             paddingBottom: theme.spacing.unit * 1,
50         }
51     },
52     title: {
53         overflow: 'hidden',
54         paddingTop: theme.spacing.unit * 0.5,
55         color: theme.customs.colors.greyD,
56         fontSize: '1.875rem'
57     },
58 });
59
60 interface ProcessCmdCardDataProps {
61   process: Process;
62   onCopy: (text: string) => void;
63 }
64
65 type ProcessCmdCardProps = ProcessCmdCardDataProps & WithStyles<CssRules> & MPVPanelProps;
66
67 export const ProcessCmdCard = withStyles(styles)(
68   ({
69     process,
70     onCopy,
71     classes,
72     doHidePanel,
73   }: ProcessCmdCardProps) => {
74
75     const formatLine = (lines: string[], index: number): string => {
76       // Escape each arg separately
77       let line = shellescape([lines[index]])
78       // Indent lines after the first
79       const indent = index > 0 ? '  ' : '';
80       // Add backslash "escaped linebreak"
81       const lineBreak = lines.length > 1 && index < lines.length - 1 ? ' \\' : '';
82
83       return `${indent}${line}${lineBreak}`;
84     };
85
86     const formatClipboardText = (command: string[]) => (): string => (
87       command.map((v) =>
88         shellescape([v]) // Escape each arg separately
89       ).join(' ')
90     );
91
92     return (
93       <Card className={classes.card}>
94         <CardHeader
95           className={classes.header}
96           classes={{
97             content: classes.title,
98             avatar: classes.avatar,
99           }}
100           avatar={<CommandIcon className={classes.iconHeader} />}
101           title={
102             <Typography noWrap variant="h6" color="inherit">
103               Command
104             </Typography>
105           }
106           action={
107             <Grid container direction="row" alignItems="center">
108               <Grid item>
109                 <Tooltip title="Copy link to clipboard" disableFocusListener>
110                   <IconButton>
111                     <CopyResultToClipboard
112                       getText={formatClipboardText(process.containerRequest.command)}
113                       onCopy={() => onCopy("Command copied to clipboard")}
114                     >
115                       <CopyIcon />
116                     </CopyResultToClipboard>
117                   </IconButton>
118                 </Tooltip>
119               </Grid>
120               <Grid item>
121                 {doHidePanel && (
122                   <Tooltip
123                     title={`Close Command Panel`}
124                     disableFocusListener
125                   >
126                     <IconButton onClick={doHidePanel}>
127                       <CloseIcon />
128                     </IconButton>
129                   </Tooltip>
130                 )}
131               </Grid>
132             </Grid>
133           }
134         />
135         <CardContent className={classes.content}>
136           <DefaultVirtualCodeSnippet
137             lines={process.containerRequest.command}
138             lineFormatter={formatLine}
139             linked
140           />
141         </CardContent>
142       </Card>
143     );
144   }
145 );