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
2887 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
height: '100%',
106
flexGrow: 1,
107
opacity: status === 'DOWN' ? 0.25 : 1,
108
bgcolor: (status === 'DOWN' || status === 'DRAINING') ? 'grey.A100' : ''
109
})
110
111
return (
112
<>
113
<Card sx={getCardStyle(node.status)}>
114
<CardContent sx={{ pl: 2, pr: 1 }}>
115
<Grid
116
container
117
justifyContent="space-between"
118
spacing={1}
119
>
120
<Grid item xs={10}>
121
<Typography
122
color="textPrimary"
123
gutterBottom
124
variant="h6"
125
>
126
<Box fontWeight="fontWeightBold" mr={1} display="inline">
127
URI:
128
</Box>
129
{node.uri}
130
</Typography>
131
</Grid>
132
<Grid item xs={2}>
133
<Typography
134
color="textPrimary"
135
gutterBottom
136
variant="h6"
137
>
138
<OsLogo osName={node.osInfo.name}/>
139
<NodeDetailsDialog node={node}/>
140
</Typography>
141
</Grid>
142
<Grid item xs={12}>
143
<Box display="flex" alignItems="center">
144
<Stereotypes stereotypes={node.slotStereotypes}/>
145
{vncSession && (
146
<LiveIconButton onClick={handleLiveViewIconClick} size='medium' color="primary">
147
<VideocamIcon data-testid="VideocamIcon" />
148
</LiveIconButton>
149
)}
150
</Box>
151
</Grid>
152
<Grid item xs={12}>
153
<NodeLoad node={node}/>
154
</Grid>
155
</Grid>
156
</CardContent>
157
</Card>
158
{vncSession && liveViewSessionId && (
159
<Dialog
160
onClose={handleDialogClose}
161
aria-labelledby='live-view-dialog'
162
open={liveViewSessionId === vncSession.id}
163
fullWidth
164
maxWidth='xl'
165
fullScreen
166
>
167
<DialogTitle id='live-view-dialog'>
168
<Typography gutterBottom component='span' sx={{ paddingX: '10px' }}>
169
<Box fontWeight='fontWeightBold' mr={1} display='inline'>
170
Node Session Live View
171
</Box>
172
{node.uri}
173
</Typography>
174
</DialogTitle>
175
<DialogContent dividers sx={{ height: '600px' }}>
176
<LiveView
177
ref={liveViewRef as any}
178
url={getVncUrl(vncSession, origin)}
179
scaleViewport
180
onClose={handleDialogClose}
181
/>
182
</DialogContent>
183
<DialogActions>
184
<Button onClick={handleDialogClose} color='primary' variant='contained'>
185
Close
186
</Button>
187
</DialogActions>
188
</Dialog>
189
)}
190
</>
191
)
192
}
193
194
export default Node
195
196