X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/5c8a5dd07252ed2ac431c53fccc9e2fb649c3014..671aae429289611510af1facbc59c96c318293a2:/src/views/process-panel/process-log-card.tsx

diff --git a/src/views/process-panel/process-log-card.tsx b/src/views/process-panel/process-log-card.tsx
index b87bb6e475..4890c726f4 100644
--- a/src/views/process-panel/process-log-card.tsx
+++ b/src/views/process-panel/process-log-card.tsx
@@ -2,7 +2,7 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import React from 'react';
+import React, { useState } from 'react';
 import {
     StyleRulesCallback,
     WithStyles,
@@ -19,8 +19,14 @@ import { ArvadosTheme } from 'common/custom-theme';
 import {
     CloseIcon,
     CollectionIcon,
+    CopyIcon,
     LogIcon,
-    MaximizeIcon
+    MaximizeIcon,
+    UnMaximizeIcon,
+    TextDecreaseIcon,
+    TextIncreaseIcon,
+    WordWrapOffIcon,
+    WordWrapOnIcon,
 } from 'components/icon/icon';
 import { Process } from 'store/processes/process';
 import { MPVPanelProps } from 'components/multi-panel-view/multi-panel-view';
@@ -31,17 +37,28 @@ import {
 import { ProcessLogCodeSnippet } from 'views/process-panel/process-log-code-snippet';
 import { DefaultView } from 'components/default-view/default-view';
 import { CodeSnippetDataProps } from 'components/code-snippet/code-snippet';
+import CopyToClipboard from 'react-copy-to-clipboard';
 
-type CssRules = 'card' | 'content' | 'title' | 'iconHeader';
+type CssRules = 'card' | 'content' | 'title' | 'iconHeader' | 'header' | 'root' | 'logViewer' | 'logViewerContainer';
 
 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
     card: {
         height: '100%'
     },
+    header: {
+        paddingTop: theme.spacing.unit,
+        paddingBottom: theme.spacing.unit,
+    },
     content: {
-        '&:last-child': {
-            paddingBottom: theme.spacing.unit * 2,
-        }
+        padding: theme.spacing.unit * 0,
+        height: '100%',
+    },
+    logViewer: {
+        height: '100%',
+        overflowY: 'scroll', // Required for MacOS's Safari -- See #19687
+    },
+    logViewerContainer: {
+        height: '100%',
     },
     title: {
         overflow: 'hidden',
@@ -51,6 +68,9 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
         fontSize: '1.875rem',
         color: theme.customs.colors.green700
     },
+    root: {
+        height: '100%',
+    },
 });
 
 export interface ProcessLogsCardDataProps {
@@ -62,6 +82,7 @@ export interface ProcessLogsCardDataProps {
 export interface ProcessLogsCardActionProps {
     onLogFilterChange: (filter: FilterOption) => void;
     navigateToLog: (uuid: string) => void;
+    onCopy: (text: string) => void;
 }
 
 type ProcessLogsCardProps = ProcessLogsCardDataProps
@@ -71,17 +92,53 @@ type ProcessLogsCardProps = ProcessLogsCardDataProps
     & MPVPanelProps;
 
 export const ProcessLogsCard = withStyles(styles)(
-    ({ classes, process, filters, selectedFilter, lines, onLogFilterChange, navigateToLog,
-        doHidePanel, doMaximizePanel, panelMaximized, panelName }: ProcessLogsCardProps) =>
-        <Grid item xs={12}>
+    ({ classes, process, filters, selectedFilter, lines,
+        onLogFilterChange, navigateToLog, onCopy,
+        doHidePanel, doMaximizePanel, doUnMaximizePanel, panelMaximized, panelName }: ProcessLogsCardProps) => {
+        const [wordWrap, setWordWrap] = useState<boolean>(true);
+        const [fontSize, setFontSize] = useState<number>(3);
+        const fontBaseSize = 10;
+        const fontStepSize = 1;
+
+        return <Grid item className={classes.root} xs={12}>
             <Card className={classes.card}>
-                <CardHeader
+                <CardHeader className={classes.header}
                     avatar={<LogIcon className={classes.iconHeader} />}
                     action={<Grid container direction='row' alignItems='center'>
                         <Grid item>
                             <ProcessLogForm selectedFilter={selectedFilter}
                                 filters={filters} onChange={onLogFilterChange} />
                         </Grid>
+                        <Grid item>
+                            <Tooltip title="Decrease font size" disableFocusListener>
+                                <IconButton onClick={() => fontSize > 1 && setFontSize(fontSize-1)}>
+                                    <TextDecreaseIcon />
+                                </IconButton>
+                            </Tooltip>
+                        </Grid>
+                        <Grid item>
+                            <Tooltip title="Increase font size" disableFocusListener>
+                                <IconButton onClick={() => fontSize < 5 && setFontSize(fontSize+1)}>
+                                    <TextIncreaseIcon />
+                                </IconButton>
+                            </Tooltip>
+                        </Grid>
+                        <Grid item>
+                            <Tooltip title="Copy to clipboard" disableFocusListener>
+                                <IconButton>
+                                    <CopyToClipboard text={lines.join()} onCopy={() => onCopy("Log copied to clipboard")}>
+                                        <CopyIcon />
+                                    </CopyToClipboard>
+                                </IconButton>
+                            </Tooltip>
+                        </Grid>
+                        <Grid item>
+                            <Tooltip title={`${wordWrap ? 'Disable' : 'Enable'} word wrapping`} disableFocusListener>
+                                <IconButton onClick={() => setWordWrap(!wordWrap)}>
+                                    {wordWrap ? <WordWrapOffIcon /> : <WordWrapOnIcon />}
+                                </IconButton>
+                            </Tooltip>
+                        </Grid>
                         <Grid item>
                             <Tooltip title="Go to Log collection" disableFocusListener>
                                 <IconButton onClick={() => navigateToLog(process.containerRequest.logUuid!)}>
@@ -89,15 +146,18 @@ export const ProcessLogsCard = withStyles(styles)(
                                 </IconButton>
                             </Tooltip>
                         </Grid>
+                        { doUnMaximizePanel && panelMaximized &&
+                        <Tooltip title={`Unmaximize ${panelName || 'panel'}`} disableFocusListener>
+                            <IconButton onClick={doUnMaximizePanel}><UnMaximizeIcon /></IconButton>
+                        </Tooltip> }
                         { doMaximizePanel && !panelMaximized &&
                         <Tooltip title={`Maximize ${panelName || 'panel'}`} disableFocusListener>
                             <IconButton onClick={doMaximizePanel}><MaximizeIcon /></IconButton>
                         </Tooltip> }
-                        { doHidePanel && <Grid item>
-                            <Tooltip title={`Close ${panelName || 'panel'}`} disableFocusListener>
-                                <IconButton onClick={doHidePanel}><CloseIcon /></IconButton>
-                            </Tooltip>
-                        </Grid> }
+                        { doHidePanel &&
+                        <Tooltip title={`Close ${panelName || 'panel'}`} disableFocusListener>
+                            <IconButton disabled={panelMaximized} onClick={doHidePanel}><CloseIcon /></IconButton>
+                        </Tooltip> }
                     </Grid>}
                     title={
                         <Typography noWrap variant='h6' className={classes.title}>
@@ -107,11 +167,12 @@ export const ProcessLogsCard = withStyles(styles)(
                 <CardContent className={classes.content}>
                     {lines.length > 0
                         ? < Grid
+                            className={classes.logViewerContainer}
                             container
                             spacing={24}
                             direction='column'>
-                            <Grid item xs>
-                                <ProcessLogCodeSnippet lines={lines} />
+                            <Grid className={classes.logViewer} item xs>
+                                <ProcessLogCodeSnippet fontSize={fontBaseSize+(fontStepSize*fontSize)} wordWrap={wordWrap} lines={lines} />
                             </Grid>
                         </Grid>
                         : <DefaultView
@@ -121,5 +182,5 @@ export const ProcessLogsCard = withStyles(styles)(
                 </CardContent>
             </Card>
         </Grid >
-);
+});
 
