Path: blob/trunk/javascript/grid-ui/src/components/Node/Node.tsx
3987 views
// Licensed to the Software Freedom Conservancy (SFC) under one1// or more contributor license agreements. See the NOTICE file2// distributed with this work for additional information3// regarding copyright ownership. The SFC licenses this file4// to you under the Apache License, Version 2.0 (the5// "License"); you may not use this file except in compliance6// with the License. You may obtain a copy of the License at7//8// http://www.apache.org/licenses/LICENSE-2.09//10// Unless required by applicable law or agreed to in writing,11// software distributed under the License is distributed on an12// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY13// KIND, either express or implied. See the License for the14// specific language governing permissions and limitations15// under the License.1617import { Box, Card, CardContent, Dialog, DialogActions, DialogContent, DialogTitle, Grid, IconButton, Typography, Button, keyframes, styled } from '@mui/material'18import React, { useState, useRef } from 'react'19import { Videocam as VideocamIcon } from '@mui/icons-material'20import NodeDetailsDialog from './NodeDetailsDialog'21import NodeLoad from './NodeLoad'22import Stereotypes from './Stereotypes'23import OsLogo from '../common/OsLogo'24import LiveView from '../LiveView/LiveView'2526const pulse = keyframes`270% {28box-shadow: 0 0 0 0 rgba(25, 118, 210, 0.7);29transform: scale(1);30}3150% {32box-shadow: 0 0 0 5px rgba(25, 118, 210, 0);33transform: scale(1.05);34}35100% {36box-shadow: 0 0 0 0 rgba(25, 118, 210, 0);37transform: scale(1);38}39`4041const LiveIconButton = styled(IconButton)(({ theme }) => ({42marginLeft: theme.spacing(1),43position: 'relative',44'&::after': {45content: '""',46position: 'absolute',47width: '100%',48height: '100%',49borderRadius: '50%',50animation: `${pulse} 2s infinite`,51zIndex: 052}53}))5455function getVncUrl(session, origin) {56try {57const parsed = JSON.parse(session.capabilities)58let vnc = parsed['se:vnc'] ?? ''59if (vnc.length > 0) {60try {61const url = new URL(origin)62const vncUrl = new URL(vnc)63url.pathname = vncUrl.pathname64url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:'65return url.href66} catch (error) {67console.log(error)68return ''69}70}71return ''72} catch (e) {73return ''74}75}7677function Node (props) {78const { node, sessions = [], origin } = props79const [liveViewSessionId, setLiveViewSessionId] = useState('')80const liveViewRef = useRef<{ disconnect: () => void }>(null)8182const vncSession = sessions.find(session => {83try {84const capabilities = JSON.parse(session.capabilities)85return capabilities['se:vnc'] !== undefined && capabilities['se:vnc'] !== ''86} catch (e) {87return false88}89})9091const handleLiveViewIconClick = () => {92if (vncSession) {93setLiveViewSessionId(vncSession.id)94}95}9697const handleDialogClose = () => {98if (liveViewRef.current) {99liveViewRef.current.disconnect()100}101setLiveViewSessionId('')102}103const getCardStyle = (status: string) => {104const baseStyle = {105height: '100%',106flexGrow: 1107}108109if (status === 'DOWN') {110return {111...baseStyle,112opacity: 0.6,113border: '2px solid',114borderColor: 'error.main',115bgcolor: 'action.disabledBackground'116}117}118119if (status === 'DRAINING') {120return {121...baseStyle,122border: '2px solid',123borderColor: 'warning.main',124bgcolor: 'action.hover'125}126}127128return baseStyle129}130131return (132<>133<Card sx={getCardStyle(node.status)}>134<CardContent sx={{ pl: 2, pr: 1 }}>135<Grid136container137justifyContent="space-between"138spacing={1}139>140<Grid item xs={10}>141<Typography142color="textPrimary"143gutterBottom144variant="h6"145>146<Box fontWeight="fontWeightBold" mr={1} display="inline">147URI:148</Box>149{node.uri}150</Typography>151</Grid>152<Grid item xs={2}>153<Typography154color="textPrimary"155gutterBottom156variant="h6"157>158<OsLogo osName={node.osInfo.name}/>159<NodeDetailsDialog node={node}/>160</Typography>161</Grid>162<Grid item xs={12}>163<Box display="flex" alignItems="center">164<Stereotypes stereotypes={node.slotStereotypes}/>165{vncSession && (166<LiveIconButton onClick={handleLiveViewIconClick} size='medium' color="primary">167<VideocamIcon data-testid="VideocamIcon" />168</LiveIconButton>169)}170</Box>171</Grid>172<Grid item xs={12}>173<NodeLoad node={node}/>174</Grid>175</Grid>176</CardContent>177</Card>178{vncSession && liveViewSessionId && (179<Dialog180onClose={handleDialogClose}181aria-labelledby='live-view-dialog'182open={liveViewSessionId === vncSession.id}183fullWidth184maxWidth='xl'185fullScreen186>187<DialogTitle id='live-view-dialog'>188<Typography gutterBottom component='span' sx={{ paddingX: '10px' }}>189<Box fontWeight='fontWeightBold' mr={1} display='inline'>190Node Session Live View191</Box>192{node.uri}193</Typography>194</DialogTitle>195<DialogContent dividers sx={{ height: '600px', bgcolor: 'background.default', p: 0 }}>196<LiveView197ref={liveViewRef as any}198url={getVncUrl(vncSession, origin)}199scaleViewport200onClose={handleDialogClose}201/>202</DialogContent>203<DialogActions>204<Button onClick={handleDialogClose} color='primary' variant='contained'>205Close206</Button>207</DialogActions>208</Dialog>209)}210</>211)212}213214export default Node215216217