Merge branch '21128-toolbar-context-menu'
[arvados-workbench2.git] / src / views / process-panel / process-log-card.tsx
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 import React, { useState } from 'react';
6 import {
7     StyleRulesCallback,
8     WithStyles,
9     withStyles,
10     Card,
11     CardHeader,
12     IconButton,
13     CardContent,
14     Tooltip,
15     Grid,
16     Typography,
17 } from '@material-ui/core';
18 import { useAsyncInterval } from 'common/use-async-interval';
19 import { ArvadosTheme } from 'common/custom-theme';
20 import {
21     CloseIcon,
22     CollectionIcon,
23     CopyIcon,
24     LogIcon,
25     MaximizeIcon,
26     UnMaximizeIcon,
27     TextDecreaseIcon,
28     TextIncreaseIcon,
29     WordWrapOffIcon,
30     WordWrapOnIcon,
31 } from 'components/icon/icon';
32 import { Process, isProcessRunning } from 'store/processes/process';
33 import { MPVPanelProps } from 'components/multi-panel-view/multi-panel-view';
34 import {
35     FilterOption,
36     ProcessLogForm
37 } from 'views/process-panel/process-log-form';
38 import { ProcessLogCodeSnippet } from 'views/process-panel/process-log-code-snippet';
39 import { DefaultView } from 'components/default-view/default-view';
40 import { CodeSnippetDataProps } from 'components/code-snippet/code-snippet';
41 import CopyToClipboard from 'react-copy-to-clipboard';
42
43 type CssRules = 'card' | 'content' | 'title' | 'iconHeader' | 'header' | 'root' | 'logViewer' | 'logViewerContainer';
44
45 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
46     card: {
47         height: '100%'
48     },
49     header: {
50         paddingTop: theme.spacing.unit,
51         paddingBottom: theme.spacing.unit,
52     },
53     content: {
54         padding: theme.spacing.unit * 0,
55         height: '100%',
56     },
57     logViewer: {
58         height: '100%',
59         overflowY: 'scroll', // Required for MacOS's Safari -- See #19687
60     },
61     logViewerContainer: {
62         height: '100%',
63     },
64     title: {
65         overflow: 'hidden',
66         paddingTop: theme.spacing.unit * 0.5,
67         color: theme.customs.colors.greyD
68     },
69     iconHeader: {
70         fontSize: '1.875rem',
71         color: theme.customs.colors.greyL
72     },
73     root: {
74         height: '100%',
75     },
76 });
77
78 export interface ProcessLogsCardDataProps {
79     process: Process;
80     selectedFilter: FilterOption;
81     filters: FilterOption[];
82 }
83
84 export interface ProcessLogsCardActionProps {
85     onLogFilterChange: (filter: FilterOption) => void;
86     navigateToLog: (uuid: string) => void;
87     onCopy: (text: string) => void;
88     pollProcessLogs: (processUuid: string) => Promise<void>;
89 }
90
91 type ProcessLogsCardProps = ProcessLogsCardDataProps
92     & ProcessLogsCardActionProps
93     & CodeSnippetDataProps
94     & WithStyles<CssRules>
95     & MPVPanelProps;
96
97 export const ProcessLogsCard = withStyles(styles)(
98     ({ classes, process, filters, selectedFilter, lines,
99         onLogFilterChange, navigateToLog, onCopy, pollProcessLogs,
100         doHidePanel, doMaximizePanel, doUnMaximizePanel, panelMaximized, panelName }: ProcessLogsCardProps) => {
101         const [wordWrap, setWordWrap] = useState<boolean>(true);
102         const [fontSize, setFontSize] = useState<number>(3);
103         const fontBaseSize = 10;
104         const fontStepSize = 1;
105
106         useAsyncInterval(() => (
107             pollProcessLogs(process.containerRequest.uuid)
108         ), isProcessRunning(process) ? 2000 : null);
109
110         return <Grid item className={classes.root} xs={12}>
111             <Card className={classes.card}>
112                 <CardHeader className={classes.header}
113                     avatar={<LogIcon className={classes.iconHeader} />}
114                     action={<Grid container direction='row' alignItems='center'>
115                         <Grid item>
116                             <ProcessLogForm selectedFilter={selectedFilter}
117                                 filters={filters} onChange={onLogFilterChange} />
118                         </Grid>
119                         <Grid item>
120                             <Tooltip title="Decrease font size" disableFocusListener>
121                                 <IconButton onClick={() => fontSize > 1 && setFontSize(fontSize-1)}>
122                                     <TextDecreaseIcon />
123                                 </IconButton>
124                             </Tooltip>
125                         </Grid>
126                         <Grid item>
127                             <Tooltip title="Increase font size" disableFocusListener>
128                                 <IconButton onClick={() => fontSize < 5 && setFontSize(fontSize+1)}>
129                                     <TextIncreaseIcon />
130                                 </IconButton>
131                             </Tooltip>
132                         </Grid>
133                         <Grid item>
134                             <Tooltip title="Copy to clipboard" disableFocusListener>
135                                 <IconButton>
136                                     <CopyToClipboard text={lines.join()} onCopy={() => onCopy("Log copied to clipboard")}>
137                                         <CopyIcon />
138                                     </CopyToClipboard>
139                                 </IconButton>
140                             </Tooltip>
141                         </Grid>
142                         <Grid item>
143                             <Tooltip title={`${wordWrap ? 'Disable' : 'Enable'} word wrapping`} disableFocusListener>
144                                 <IconButton onClick={() => setWordWrap(!wordWrap)}>
145                                     {wordWrap ? <WordWrapOffIcon /> : <WordWrapOnIcon />}
146                                 </IconButton>
147                             </Tooltip>
148                         </Grid>
149                         <Grid item>
150                             <Tooltip title="Go to Log collection" disableFocusListener>
151                                 <IconButton onClick={() => navigateToLog(process.containerRequest.logUuid!)}>
152                                     <CollectionIcon />
153                                 </IconButton>
154                             </Tooltip>
155                         </Grid>
156                         { doUnMaximizePanel && panelMaximized &&
157                         <Tooltip title={`Unmaximize ${panelName || 'panel'}`} disableFocusListener>
158                             <IconButton onClick={doUnMaximizePanel}><UnMaximizeIcon /></IconButton>
159                         </Tooltip> }
160                         { doMaximizePanel && !panelMaximized &&
161                         <Tooltip title={`Maximize ${panelName || 'panel'}`} disableFocusListener>
162                             <IconButton onClick={doMaximizePanel}><MaximizeIcon /></IconButton>
163                         </Tooltip> }
164                         { doHidePanel &&
165                         <Tooltip title={`Close ${panelName || 'panel'}`} disableFocusListener>
166                             <IconButton disabled={panelMaximized} onClick={doHidePanel}><CloseIcon /></IconButton>
167                         </Tooltip> }
168                     </Grid>}
169                     title={
170                         <Typography noWrap variant='h6' className={classes.title}>
171                             Logs
172                         </Typography>}
173                 />
174                 <CardContent className={classes.content}>
175                     {lines.length > 0
176                         ? < Grid
177                             className={classes.logViewerContainer}
178                             container
179                             spacing={24}
180                             direction='column'>
181                             <Grid className={classes.logViewer} item xs>
182                                 <ProcessLogCodeSnippet fontSize={fontBaseSize+(fontStepSize*fontSize)} wordWrap={wordWrap} lines={lines} />
183                             </Grid>
184                         </Grid>
185                         : <DefaultView
186                             icon={LogIcon}
187                             messages={['No logs yet']} />
188                     }
189                 </CardContent>
190             </Card>
191         </Grid >
192 });