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