+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import React from 'react';
+import {
+ StyleRulesCallback,
+ WithStyles,
+ withStyles,
+ Card,
+ CardHeader,
+ IconButton,
+ CardContent,
+ Tooltip,
+ Typography,
+ Grid,
+} from '@material-ui/core';
+import { ArvadosTheme } from 'common/custom-theme';
+import { CloseIcon, CommandIcon, CopyIcon } from 'components/icon/icon';
+import { MPVPanelProps } from 'components/multi-panel-view/multi-panel-view';
+import { DefaultCodeSnippet } from 'components/default-code-snippet/default-code-snippet';
+import { Process } from 'store/processes/process';
+import shellescape from 'shell-escape';
+import CopyToClipboard from 'react-copy-to-clipboard';
+
+type CssRules = 'card' | 'content' | 'title' | 'header' | 'avatar' | 'iconHeader';
+
+const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
+ card: {
+ height: '100%'
+ },
+ header: {
+ paddingTop: theme.spacing.unit,
+ paddingBottom: theme.spacing.unit,
+ },
+ iconHeader: {
+ fontSize: '1.875rem',
+ color: theme.customs.colors.green700,
+ },
+ avatar: {
+ alignSelf: 'flex-start',
+ paddingTop: theme.spacing.unit * 0.5
+ },
+ content: {
+ padding: theme.spacing.unit * 1.0,
+ paddingTop: theme.spacing.unit * 0.5,
+ '&:last-child': {
+ paddingBottom: theme.spacing.unit * 1,
+ }
+ },
+ title: {
+ overflow: 'hidden',
+ paddingTop: theme.spacing.unit * 0.5
+ },
+});
+
+interface ProcessCmdCardDataProps {
+ process: Process;
+ onCopy: (text: string) => void;
+}
+
+type ProcessCmdCardProps = ProcessCmdCardDataProps & WithStyles<CssRules> & MPVPanelProps;
+
+export const ProcessCmdCard = withStyles(styles)(
+ ({
+ process,
+ onCopy,
+ classes,
+ doHidePanel,
+ }: ProcessCmdCardProps) => {
+ const command = process.containerRequest.command.map((v) =>
+ shellescape([v]) // Escape each arg separately
+ );
+
+ let formattedCommand = [...command];
+ formattedCommand.forEach((item, i, arr) => {
+ // Indent lines after the first
+ const indent = i > 0 ? ' ' : '';
+ // Escape newlines on every non-last arg when there are multiple lines
+ const lineBreak = arr.length > 1 && i < arr.length - 1 ? ' \\' : '';
+ arr[i] = `${indent}${item}${lineBreak}`;
+ });
+
+ return (
+ <Card className={classes.card}>
+ <CardHeader
+ className={classes.header}
+ classes={{
+ content: classes.title,
+ avatar: classes.avatar,
+ }}
+ avatar={<CommandIcon className={classes.iconHeader} />}
+ title={
+ <Typography noWrap variant="h6" color="inherit">
+ Command
+ </Typography>
+ }
+ action={
+ <Grid container direction="row" alignItems="center">
+ <Grid item>
+ <Tooltip title="Copy to clipboard" disableFocusListener>
+ <IconButton>
+ <CopyToClipboard
+ text={command.join(" ")}
+ onCopy={() => onCopy("Command copied to clipboard")}
+ >
+ <CopyIcon />
+ </CopyToClipboard>
+ </IconButton>
+ </Tooltip>
+ </Grid>
+ <Grid item>
+ {doHidePanel && (
+ <Tooltip
+ title={`Close Command Panel`}
+ disableFocusListener
+ >
+ <IconButton onClick={doHidePanel}>
+ <CloseIcon />
+ </IconButton>
+ </Tooltip>
+ )}
+ </Grid>
+ </Grid>
+ }
+ />
+ <CardContent className={classes.content}>
+ <DefaultCodeSnippet lines={formattedCommand} linked />
+ </CardContent>
+ </Card>
+ );
+ }
+);