| // Licensed to the Software Freedom Conservancy (SFC) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The SFC licenses this file |
| // to you under the Apache License, Version 2.0 (the |
| // "License"); you may not use this file except in compliance |
| // with the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, |
| // software distributed under the License is distributed on an |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| // KIND, either express or implied. See the License for the |
| // specific language governing permissions and limitations |
| // under the License. |
| |
| import React, { useEffect, useState, useImperativeHandle, forwardRef } from 'react' |
| import RFB from '@novnc/novnc/lib/rfb' |
| import PasswordDialog from './PasswordDialog' |
| import MuiAlert, { AlertProps } from '@mui/material/Alert' |
| import Snackbar from '@mui/material/Snackbar' |
| |
| const Alert = React.forwardRef<HTMLDivElement, AlertProps>(function Alert ( |
| props, |
| ref |
| ) { |
| return <MuiAlert elevation={6} ref={ref} variant='filled' {...props} /> |
| }) |
| |
| const LiveView = forwardRef((props, ref) => { |
| let canvas: any = null |
| |
| const [open, setOpen] = useState(false) |
| const [message, setMessage] = useState('') |
| const [rfb, setRfb] = useState<RFB>(null) |
| const [openErrorAlert, setOpenErrorAlert] = useState(false) |
| const [openSuccessAlert, setOpenSuccessAlert] = useState(false) |
| |
| const handlePasswordDialog = (state: boolean): void => { |
| setOpen(state) |
| } |
| |
| const disconnect = () => { |
| if (!rfb) { |
| return |
| } |
| rfb.disconnect() |
| setRfb(null) |
| } |
| |
| useImperativeHandle(ref, () => ({ |
| disconnect |
| })) |
| |
| const connect = () => { |
| disconnect() |
| |
| if (!canvas) { |
| return |
| } |
| |
| const newRfb = new RFB.default(canvas, props.url, {}) |
| newRfb.scaleViewport = props.scaleViewport |
| newRfb.background = 'rgb(247,248,248)' |
| newRfb.addEventListener('credentialsrequired', handleCredentials) |
| newRfb.addEventListener('securityfailure', securityFailed) |
| newRfb.addEventListener('connect', connectedToServer) |
| newRfb.addEventListener('disconnect', disconnect) |
| setRfb(newRfb) |
| } |
| |
| const registerChild = ref => { |
| canvas = ref |
| } |
| |
| useEffect(() => { |
| connect() |
| return () => { |
| disconnect() |
| } |
| // eslint-disable-next-line |
| }, []) |
| |
| useEffect(() => { |
| if (rfb) { |
| rfb.scaleViewport = props.scaleViewport |
| } |
| }) |
| |
| const securityFailed = (event: any) => { |
| let errorMessage |
| if ('reason' in event.detail) { |
| errorMessage = |
| 'Connection has been rejected with reason: ' + event.detail.reason |
| } else { |
| errorMessage = 'New connection has been rejected' |
| } |
| setMessage(errorMessage) |
| setOpenErrorAlert(true) |
| } |
| |
| const handleCredentials = () => { |
| handlePasswordDialog(true) |
| } |
| |
| const connectedToServer = () => { |
| setOpenSuccessAlert(true) |
| } |
| |
| const handleCredentialsEntered = (password: string) => { |
| rfb.sendCredentials({ username: '', password: password }) |
| setOpen(false) |
| } |
| |
| const handlePasswordDialogClose = () => { |
| disconnect() |
| props.onClose() |
| } |
| |
| const handleMouseEnter = () => { |
| if (!rfb) { |
| return |
| } |
| rfb.focus() |
| } |
| |
| const handleMouseLeave = () => { |
| if (!rfb) { |
| return |
| } |
| rfb.blur() |
| } |
| |
| const handleClose = (event?: React.SyntheticEvent | Event, |
| reason?: string) => { |
| if (reason === 'clickaway') { |
| return |
| } |
| setOpenErrorAlert(false) |
| disconnect() |
| props.onClose() |
| } |
| |
| return ( |
| <div |
| style={ |
| { |
| width: '100%', |
| height: '100%' |
| } |
| } |
| ref={registerChild} |
| onMouseEnter={handleMouseEnter} |
| onMouseLeave={handleMouseLeave} |
| > |
| <PasswordDialog |
| title='LiveView (VNC) Password' |
| open={open} |
| openDialog={handlePasswordDialog} |
| onConfirm={handleCredentialsEntered} |
| onCancel={handlePasswordDialogClose} |
| /> |
| <Snackbar |
| open={openErrorAlert} |
| anchorOrigin={{ vertical: 'top', horizontal: 'center' }} |
| autoHideDuration={6000} |
| onClose={handleClose} |
| > |
| <Alert severity='error' sx={{ width: '100%' }}> |
| {message} |
| </Alert> |
| </Snackbar> |
| <Snackbar |
| open={openSuccessAlert} |
| anchorOrigin={{ vertical: 'top', horizontal: 'center' }} |
| autoHideDuration={4000} |
| onClose={() => setOpenSuccessAlert(false)} |
| > |
| <Alert severity='success' sx={{ width: '100%' }}> |
| Connected successfully! |
| </Alert> |
| </Snackbar> |
| </div> |
| ) |
| }) |
| |
| export default LiveView |