]> git.arvados.org - arvados.git/blob - services/workbench2/src/views/process-panel/process-log-card.tsx
Merge branch '22226-empty-output-panic'
[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, isProcessQueued } 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            ), isProcessQueued(process) ? 20000 : (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 });