21720:
[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 { CustomStyleRulesCallback } from 'common/custom-theme';
7 import { Card, CardHeader, IconButton, CardContent, Tooltip, Typography, Grid } from '@mui/material';
8 import { WithStyles } from '@mui/styles';
9 import withStyles from '@mui/styles/withStyles';
10 import { ArvadosTheme } from 'common/custom-theme';
11 import { CloseIcon, CommandIcon, CopyIcon } from 'components/icon/icon';
12 import { MPVPanelProps } from 'components/multi-panel-view/multi-panel-view';
13 import { DefaultVirtualCodeSnippet } from 'components/default-code-snippet/default-virtual-code-snippet';
14 import { Process } from 'store/processes/process';
15 import shellescape from 'shell-escape';
16 import CopyResultToClipboard from 'components/copy-to-clipboard/copy-result-to-clipboard';
17
18 type CssRules = 'card' | 'content' | 'title' | 'header' | 'avatar' | 'iconHeader';
19
20 const styles: CustomStyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
21     card: {
22         height: '100%'
23     },
24     header: {
25         paddingTop: theme.spacing(1),
26         paddingBottom: 0,
27     },
28     iconHeader: {
29         fontSize: '1.875rem',
30         color: theme.customs.colors.greyL,
31     },
32     avatar: {
33         alignSelf: 'flex-start',
34         paddingTop: theme.spacing(0.5)
35     },
36     content: {
37         height: `calc(100% - ${theme.spacing(6)})`,
38         padding: theme.spacing(1),
39         paddingTop: 0,
40         '&:last-child': {
41             paddingBottom: theme.spacing(1),
42         }
43     },
44     title: {
45         overflow: 'hidden',
46         paddingTop: theme.spacing(0.5),
47         color: theme.customs.colors.greyD,
48         fontSize: '1.875rem'
49     },
50 });
51
52 interface ProcessCmdCardDataProps {
53   process: Process;
54   onCopy: (text: string) => void;
55 }
56
57 type ProcessCmdCardProps = ProcessCmdCardDataProps & WithStyles<CssRules> & MPVPanelProps;
58
59 export const ProcessCmdCard = withStyles(styles)(
60   ({
61     process,
62     onCopy,
63     classes,
64     doHidePanel,
65   }: ProcessCmdCardProps) => {
66
67     const formatLine = (lines: string[], index: number): string => {
68       // Escape each arg separately
69       let line = shellescape([lines[index]])
70       // Indent lines after the first
71       const indent = index > 0 ? '  ' : '';
72       // Add backslash "escaped linebreak"
73       const lineBreak = lines.length > 1 && index < lines.length - 1 ? ' \\' : '';
74
75       return `${indent}${line}${lineBreak}`;
76     };
77
78     const formatClipboardText = (command: string[]) => (): string => (
79       command.map((v) =>
80         shellescape([v]) // Escape each arg separately
81       ).join(' ')
82     );
83
84     return (
85       <Card className={classes.card}>
86         <CardHeader
87           className={classes.header}
88           classes={{
89             content: classes.title,
90             avatar: classes.avatar,
91           }}
92           avatar={<CommandIcon className={classes.iconHeader} />}
93           title={
94             <Typography noWrap variant="h6" color="inherit">
95               Command
96             </Typography>
97           }
98           action={
99             <Grid container direction="row" alignItems="center">
100               <Grid item>
101                 <Tooltip title="Copy link to clipboard" disableFocusListener>
102                   <IconButton size="large">
103                     <CopyResultToClipboard
104                       getText={formatClipboardText(process.containerRequest.command)}
105                       onCopy={() => onCopy("Command copied to clipboard")}
106                     >
107                       <CopyIcon />
108                     </CopyResultToClipboard>
109                   </IconButton>
110                 </Tooltip>
111               </Grid>
112               <Grid item>
113                 {doHidePanel && (
114                   <Tooltip
115                     title={`Close Command Panel`}
116                     disableFocusListener
117                   >
118                     <IconButton onClick={doHidePanel} size="large">
119                       <CloseIcon />
120                     </IconButton>
121                   </Tooltip>
122                 )}
123               </Grid>
124             </Grid>
125           }
126         />
127         <CardContent className={classes.content}>
128           <DefaultVirtualCodeSnippet
129             lines={process.containerRequest.command}
130             lineFormatter={formatLine}
131             linked
132           />
133         </CardContent>
134       </Card>
135     );
136   }
137 );