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