Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
SeleniumHQ
GitHub Repository: SeleniumHQ/Selenium
Path: blob/trunk/javascript/grid-ui/src/components/Node/Node.tsx
3987 views
1
// Licensed to the Software Freedom Conservancy (SFC) under one
2
// or more contributor license agreements. See the NOTICE file
3
// distributed with this work for additional information
4
// regarding copyright ownership. The SFC licenses this file
5
// to you under the Apache License, Version 2.0 (the
6
// "License"); you may not use this file except in compliance
7
// with the License. You may obtain a copy of the License at
8
//
9
// http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing,
12
// software distributed under the License is distributed on an
13
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
// KIND, either express or implied. See the License for the
15
// specific language governing permissions and limitations
16
// under the License.
17
18
import { Box, Card, CardContent, Dialog, DialogActions, DialogContent, DialogTitle, Grid, IconButton, Typography, Button, keyframes, styled } from '@mui/material'
19
import React, { useState, useRef } from 'react'
20
import { Videocam as VideocamIcon } from '@mui/icons-material'
21
import NodeDetailsDialog from './NodeDetailsDialog'
22
import NodeLoad from './NodeLoad'
23
import Stereotypes from './Stereotypes'
24
import OsLogo from '../common/OsLogo'
25
import LiveView from '../LiveView/LiveView'
26
27
const pulse = keyframes`
28
0% {
29
box-shadow: 0 0 0 0 rgba(25, 118, 210, 0.7);
30
transform: scale(1);
31
}
32
50% {
33
box-shadow: 0 0 0 5px rgba(25, 118, 210, 0);
34
transform: scale(1.05);
35
}
36
100% {
37
box-shadow: 0 0 0 0 rgba(25, 118, 210, 0);
38
transform: scale(1);
39
}
40
`
41
42
const LiveIconButton = styled(IconButton)(({ theme }) => ({
43
marginLeft: theme.spacing(1),
44
position: 'relative',
45
'&::after': {
46
content: '""',
47
position: 'absolute',
48
width: '100%',
49
height: '100%',
50
borderRadius: '50%',
51
animation: `${pulse} 2s infinite`,
52
zIndex: 0
53
}
54
}))
55
56
function getVncUrl(session, origin) {
57
try {
58
const parsed = JSON.parse(session.capabilities)
59
let vnc = parsed['se:vnc'] ?? ''
60
if (vnc.length > 0) {
61
try {
62
const url = new URL(origin)
63
const vncUrl = new URL(vnc)
64
url.pathname = vncUrl.pathname
65
url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:'
66
return url.href
67
} catch (error) {
68
console.log(error)
69
return ''
70
}
71
}
72
return ''
73
} catch (e) {
74
return ''
75
}
76
}
77
78
function Node (props) {
79
const { node, sessions = [], origin } = props
80
const [liveViewSessionId, setLiveViewSessionId] = useState('')
81
const liveViewRef = useRef<{ disconnect: () => void }>(null)
82
83
const vncSession = sessions.find(session => {
84
try {
85
const capabilities = JSON.parse(session.capabilities)
86
return capabilities['se:vnc'] !== undefined && capabilities['se:vnc'] !== ''
87
} catch (e) {
88
return false
89
}
90
})
91
92
const handleLiveViewIconClick = () => {
93
if (vncSession) {
94
setLiveViewSessionId(vncSession.id)
95
}
96
}
97
98
const handleDialogClose = () => {
99
if (liveViewRef.current) {
100
liveViewRef.current.disconnect()
101
}
102
setLiveViewSessionId('')
103
}
104
const getCardStyle = (status: string) => {
105
const baseStyle = {
106
height: '100%',
107
flexGrow: 1
108
}
109
110
if (status === 'DOWN') {
111
return {
112
...baseStyle,
113
opacity: 0.6,
114
border: '2px solid',
115
borderColor: 'error.main',
116
bgcolor: 'action.disabledBackground'
117
}
118
}
119
120
if (status === 'DRAINING') {
121
return {
122
...baseStyle,
123
border: '2px solid',
124
borderColor: 'warning.main',
125
bgcolor: 'action.hover'
126
}
127
}
128
129
return baseStyle
130
}
131
132
return (
133
<>
134
<Card sx={getCardStyle(node.status)}>
135
<CardContent sx={{ pl: 2, pr: 1 }}>
136
<Grid
137
container
138
justifyContent="space-between"
139
spacing={1}
140
>
141
<Grid item xs={10}>
142
<Typography
143
color="textPrimary"
144
gutterBottom
145
variant="h6"
146
>
147
<Box fontWeight="fontWeightBold" mr={1} display="inline">
148
URI:
149
</Box>
150
{node.uri}
151
</Typography>
152
</Grid>
153
<Grid item xs={2}>
154
<Typography
155
color="textPrimary"
156
gutterBottom
157
variant="h6"
158
>
159
<OsLogo osName={node.osInfo.name}/>
160
<NodeDetailsDialog node={node}/>
161
</Typography>
162
</Grid>
163
<Grid item xs={12}>
164
<Box display="flex" alignItems="center">
165
<Stereotypes stereotypes={node.slotStereotypes}/>
166
{vncSession && (
167
<LiveIconButton onClick={handleLiveViewIconClick} size='medium' color="primary">
168
<VideocamIcon data-testid="VideocamIcon" />
169
</LiveIconButton>
170
)}
171
</Box>
172
</Grid>
173
<Grid item xs={12}>
174
<NodeLoad node={node}/>
175
</Grid>
176
</Grid>
177
</CardContent>
178
</Card>
179
{vncSession && liveViewSessionId && (
180
<Dialog
181
onClose={handleDialogClose}
182
aria-labelledby='live-view-dialog'
183
open={liveViewSessionId === vncSession.id}
184
fullWidth
185
maxWidth='xl'
186
fullScreen
187
>
188
<DialogTitle id='live-view-dialog'>
189
<Typography gutterBottom component='span' sx={{ paddingX: '10px' }}>
190
<Box fontWeight='fontWeightBold' mr={1} display='inline'>
191
Node Session Live View
192
</Box>
193
{node.uri}
194
</Typography>
195
</DialogTitle>
196
<DialogContent dividers sx={{ height: '600px', bgcolor: 'background.default', p: 0 }}>
197
<LiveView
198
ref={liveViewRef as any}
199
url={getVncUrl(vncSession, origin)}
200
scaleViewport
201
onClose={handleDialogClose}
202
/>
203
</DialogContent>
204
<DialogActions>
205
<Button onClick={handleDialogClose} color='primary' variant='contained'>
206
Close
207
</Button>
208
</DialogActions>
209
</Dialog>
210
)}
211
</>
212
)
213
}
214
215
export default Node
216
217