<Typography
component="div"
className={classNames(classes.root, className)}>
- {
- lines.map((line: string, index: number) => {
- return <Typography key={index} className={apiResponse ? classes.space : className} component="pre">{line}</Typography>;
- })
- }
+ <Typography className={apiResponse ? classes.space : className} component="pre">
+ {lines.join('\n')}
+ </Typography>
</Typography>
- );
\ No newline at end of file
+ );
+++ /dev/null
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-import { dialogActions } from 'store/dialog/dialog-actions';
-import { RootState } from 'store/store';
-import { Dispatch } from 'redux';
-import { getProcess } from 'store/processes/process';
-import shellescape from 'shell-escape';
-
-export const PROCESS_COMMAND_DIALOG_NAME = 'processCommandDialog';
-
-export interface ProcessCommandDialogData {
- command: string;
- processName: string;
-}
-
-export const openProcessCommandDialog = (processUuid: string) =>
- (dispatch: Dispatch<any>, getState: () => RootState) => {
- const process = getProcess(processUuid)(getState().resources);
- if (process) {
- const data: ProcessCommandDialogData = {
- command: shellescape(process.containerRequest.command),
- processName: process.containerRequest.name,
- };
- dispatch(dialogActions.OPEN_DIALOG({ id: PROCESS_COMMAND_DIALOG_NAME, data }));
- }
- };
import { snackbarActions, SnackbarKind } from "store/snackbar/snackbar-actions";
import { openProcessInputDialog } from "store/processes/process-input-actions";
import { navigateToOutput } from "store/process-panel/process-panel-actions";
-import { openProcessCommandDialog } from "store/processes/process-command-actions";
import { openAdvancedTabDialog } from "store/advanced-tab/advanced-tab";
import { TogglePublicFavoriteAction } from "../actions/public-favorite-action";
import { togglePublicFavorite } from "store/public-favorites/public-favorites-actions";
}
}
},
- {
- icon: CommandIcon,
- name: "Command",
- execute: (dispatch, resource) => {
- dispatch<any>(openProcessCommandDialog(resource.uuid));
- }
- },
{
icon: DetailsIcon,
name: "View details",
});
}
},
-]];
\ No newline at end of file
+]];
+++ /dev/null
-// Copyright (C) The Arvados Authors. All rights reserved.
-//
-// SPDX-License-Identifier: AGPL-3.0
-
-import React from "react";
-import { Dialog, DialogActions, Button, StyleRulesCallback, WithStyles, withStyles, Tooltip, IconButton, CardHeader } from '@material-ui/core';
-import { withDialog } from "store/dialog/with-dialog";
-import { PROCESS_COMMAND_DIALOG_NAME } from 'store/processes/process-command-actions';
-import { WithDialogProps } from 'store/dialog/with-dialog';
-import { ProcessCommandDialogData } from 'store/processes/process-command-actions';
-import { DefaultCodeSnippet } from "components/default-code-snippet/default-code-snippet";
-import { compose } from 'redux';
-import CopyToClipboard from "react-copy-to-clipboard";
-import { CopyIcon } from 'components/icon/icon';
-
-type CssRules = 'codeSnippet' | 'copyToClipboard';
-
-const styles: StyleRulesCallback<CssRules> = theme => ({
- codeSnippet: {
- marginLeft: theme.spacing.unit * 3,
- marginRight: theme.spacing.unit * 3,
- },
- copyToClipboard: {
- marginRight: theme.spacing.unit,
- }
-});
-
-export const ProcessCommandDialog = compose(
- withDialog(PROCESS_COMMAND_DIALOG_NAME),
- withStyles(styles),
-)(
- (props: WithDialogProps<ProcessCommandDialogData> & WithStyles<CssRules>) =>
- <Dialog
- open={props.open}
- maxWidth="md"
- onClose={props.closeDialog}
- style={{ alignSelf: 'stretch' }}>
- <CardHeader
- title={`Command - ${props.data.processName}`}
- action={
- <Tooltip title="Copy to clipboard">
- <CopyToClipboard text={props.data.command}>
- <IconButton className={props.classes.copyToClipboard}>
- <CopyIcon />
- </IconButton>
- </CopyToClipboard>
- </Tooltip>
- } />
- <DefaultCodeSnippet
- className={props.classes.codeSnippet}
- lines={[props.data.command]} />
- <DialogActions>
- <Button
- variant='text'
- color='primary'
- onClick={props.closeDialog}>
- Close
- </Button>
- </DialogActions>
- </Dialog>
-);
\ No newline at end of file
--- /dev/null
+// 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
+ },
+});
+
+export 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} />
+ </CardContent>
+ </Card>
+ );
+ }
+);
import { getProcessPanelLogs, ProcessLogsPanel } from 'store/process-logs-panel/process-logs-panel';
import { ProcessLogsCard } from './process-log-card';
import { FilterOption } from 'views/process-panel/process-log-form';
+import { ProcessCmdCard } from './process-cmd-card';
type CssRules = 'root';
cancelProcess: (uuid: string) => void;
onLogFilterChange: (filter: FilterOption) => void;
navigateToLog: (uuid: string) => void;
- onLogCopyToClipboard: (uuid: string) => void;
+ onCopyToClipboard: (uuid: string) => void;
}
export type ProcessPanelRootProps = ProcessPanelRootDataProps & ProcessPanelRootActionProps & WithStyles<CssRules>;
const panelsData: MPVPanelState[] = [
{name: "Details"},
+ {name: "Command"},
{name: "Logs", visible: true},
{name: "Subprocesses"},
];
cancelProcess={props.cancelProcess}
/>
</MPVPanelContent>
+ <MPVPanelContent forwardProps xs="auto" data-cy="process-cmd">
+ <ProcessCmdCard
+ onCopy={props.onCopyToClipboard}
+ process={process} />
+ </MPVPanelContent>
<MPVPanelContent forwardProps xs maxHeight='50%' data-cy="process-logs">
<ProcessLogsCard
- onCopy={props.onLogCopyToClipboard}
+ onCopy={props.onCopyToClipboard}
process={process}
lines={getProcessPanelLogs(processLogsPanel)}
selectedFilter={{
};
const mapDispatchToProps = (dispatch: Dispatch): ProcessPanelRootActionProps => ({
- onLogCopyToClipboard: (message: string) => {
+ onCopyToClipboard: (message: string) => {
dispatch<any>(snackbarActions.OPEN_SNACKBAR({
message,
hideDuration: 2000,
import { MoveCollectionDialog } from 'views-components/dialog-forms/move-collection-dialog';
import { FilesUploadCollectionDialog } from 'views-components/dialog-forms/files-upload-collection-dialog';
import { PartialCopyCollectionDialog } from 'views-components/dialog-forms/partial-copy-collection-dialog';
-import { ProcessCommandDialog } from 'views-components/process-command-dialog/process-command-dialog';
import { RemoveProcessDialog } from 'views-components/process-remove-dialog/process-remove-dialog';
import { MainContentBar } from 'views-components/main-content-bar/main-content-bar';
import { Grid } from '@material-ui/core';
<PublicKeyDialog />
<PartialCopyCollectionDialog />
<PartialCopyToCollectionDialog />
- <ProcessCommandDialog />
<ProcessInputDialog />
<RestoreCollectionVersionDialog />
<RemoveApiClientAuthorizationDialog />