Skip to content

Commit 1a365f4

Browse files
authored
Merge branch 'trunk' into release-preparation-4.33.0
2 parents 7c8c5df + 9f3c923 commit 1a365f4

File tree

32 files changed

+3268
-126
lines changed

32 files changed

+3268
-126
lines changed

.github/workflows/ci-grid-ui.yml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
name: CI - Grid UI
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- 'javascript/grid-ui/**'
7+
push:
8+
branches:
9+
- trunk
10+
paths:
11+
- 'javascript/grid-ui/**'
12+
workflow_dispatch:
13+
workflow_call:
14+
15+
jobs:
16+
grid-ui-tests:
17+
runs-on: ubuntu-latest
18+
name: Grid UI Component Tests
19+
steps:
20+
- name: Checkout source tree
21+
uses: actions/checkout@v4
22+
with:
23+
fetch-depth: 50
24+
25+
- name: Setup Node.js
26+
uses: actions/setup-node@v4
27+
with:
28+
node-version: '18'
29+
cache: 'npm'
30+
cache-dependency-path: 'javascript/grid-ui/package.json'
31+
32+
- name: Install dependencies
33+
working-directory: javascript/grid-ui
34+
run: npm install
35+
36+
- name: Run component tests
37+
working-directory: javascript/grid-ui
38+
run: npm test

common/extensions/BUILD.bazel

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ exports_files(
3232
"webextensions-selenium-example.xpi",
3333
"webextensions-selenium-example.zip",
3434
"webextensions-selenium-example-unsigned.zip",
35+
"webextensions-selenium-example.crx",
36+
"webextensions-selenium-example",
37+
"webextensions-selenium-example-signed",
3538
],
3639
visibility = [
3740
"//java/test/org/openqa/selenium/firefox:__pkg__",

javascript/grid-ui/jest.config.cjs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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+
module.exports = {
19+
preset: 'ts-jest',
20+
testEnvironment: 'jsdom',
21+
testMatch: ['<rootDir>/src/tests/**/*.test.tsx'],
22+
transform: {
23+
'^.+\\.(ts|tsx)$': 'ts-jest',
24+
'^.+\\.(js|jsx)$': 'ts-jest'
25+
},
26+
moduleNameMapper: {
27+
'\\.(css|less|scss|sass)$': '<rootDir>/src/tests/__mocks__/styleMock.js',
28+
'\\.(jpg|jpeg|png|gif|svg)$': '<rootDir>/src/tests/__mocks__/styleMock.js'
29+
},
30+
transformIgnorePatterns: [
31+
'node_modules/(?!(pretty-ms|parse-ms)/)'
32+
],
33+
setupFilesAfterEnv: [
34+
'<rootDir>/src/setupTests.tsx',
35+
'<rootDir>/src/tests/setup-jest.js'
36+
],
37+
// Suppress act() warnings from Material-UI components
38+
testEnvironmentOptions: {
39+
suppressConsole: true
40+
}
41+
};

javascript/grid-ui/package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@
2727
"source-map-explorer": "2.5.3"
2828
},
2929
"scripts": {
30-
"build": "bazel build //javascript/grid-ui:bundle"
30+
"build": "bazel build //javascript/grid-ui:bundle",
31+
"test": "jest --config jest.config.cjs"
3132
},
3233
"homepage": "./ui",
3334
"browser": {
@@ -47,11 +48,13 @@
4748
]
4849
},
4950
"devDependencies": {
50-
"@babel/preset-react": "7.26.3",
5151
"@testing-library/jest-dom": "6.6.3",
5252
"@testing-library/react": "14.3.1",
5353
"@testing-library/user-event": "14.5.2",
5454
"esbuild": "0.24.2",
55+
"jest": "^29.7.0",
56+
"jest-environment-jsdom": "^29.7.0",
57+
"ts-jest": "^29.3.4",
5558
"ts-standard": "12.0.2",
5659
"typescript": "5.7.2"
5760
},

javascript/grid-ui/src/components/Node/Node.tsx

Lines changed: 121 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,92 @@
1515
// specific language governing permissions and limitations
1616
// under the License.
1717

18-
import { Box, Card, CardContent, Grid, Typography } from '@mui/material'
19-
import React from 'react'
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'
2021
import NodeDetailsDialog from './NodeDetailsDialog'
2122
import NodeLoad from './NodeLoad'
2223
import Stereotypes from './Stereotypes'
2324
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+
}
2477

2578
function Node (props) {
26-
const { 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+
}
27104
const getCardStyle = (status: string) => ({
28105
height: '100%',
29106
flexGrow: 1,
@@ -32,6 +109,7 @@ function Node (props) {
32109
})
33110

34111
return (
112+
<>
35113
<Card sx={getCardStyle(node.status)}>
36114
<CardContent sx={{ pl: 2, pr: 1 }}>
37115
<Grid
@@ -62,14 +140,54 @@ function Node (props) {
62140
</Typography>
63141
</Grid>
64142
<Grid item xs={12}>
143+
<Box display="flex" alignItems="center">
65144
<Stereotypes stereotypes={node.slotStereotypes}/>
145+
{vncSession && (
146+
<LiveIconButton onClick={handleLiveViewIconClick} size='medium' color="primary">
147+
<VideocamIcon data-testid="VideocamIcon" />
148+
</LiveIconButton>
149+
)}
150+
</Box>
66151
</Grid>
67152
<Grid item xs={12}>
68153
<NodeLoad node={node}/>
69154
</Grid>
70155
</Grid>
71156
</CardContent>
72157
</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+
</>
73191
)
74192
}
75193

0 commit comments

Comments
 (0)