import React, {Component} from 'react'; import Guacamole from 'guacamole-common-js'; import {Affix, Button, Col, Drawer, Dropdown, Form, Input, Menu, message, Modal, Row} from 'antd' import qs from "qs"; import request from "../../common/request"; import {wsServer} from "../../common/constants"; import { AppstoreTwoTone, CopyTwoTone, DesktopOutlined, ExclamationCircleOutlined, ExpandOutlined } from '@ant-design/icons'; import {exitFull, getToken, isEmpty, requestFullScreen} from "../../utils/utils"; import './Access.css' import Draggable from 'react-draggable'; import FileSystem from "./FileSystem"; const {TextArea} = Input; const STATE_IDLE = 0; const STATE_CONNECTING = 1; const STATE_WAITING = 2; const STATE_CONNECTED = 3; const STATE_DISCONNECTING = 4; const STATE_DISCONNECTED = 5; class Access extends Component { clipboardFormRef = React.createRef(); state = { sessionId: '', client: {}, clientState: STATE_IDLE, clipboardVisible: false, clipboardText: '', containerOverflow: 'hidden', containerWidth: 0, containerHeight: 0, uploadAction: '', uploadHeaders: {}, keyboard: {}, protocol: '', confirmLoading: false, uploadVisible: false, uploadLoading: false, startTime: new Date(), fullScreen: false, fullScreenBtnText: '进入全屏' }; async componentDidMount() { let urlParams = new URLSearchParams(this.props.location.search); let assetId = urlParams.get('assetId'); document.title = urlParams.get('assetName'); let protocol = urlParams.get('protocol'); let sessionId = await this.createSession(assetId); if (isEmpty(sessionId)) { return; } this.setState({ sessionId: sessionId, protocol: protocol }); this.renderDisplay(sessionId, protocol); window.addEventListener('resize', this.onWindowResize); window.onfocus = this.onWindowFocus; } componentWillUnmount() { if (this.state.client) { this.state.client.disconnect(); } } sendClipboard(data) { let writer; // Create stream with proper mimetype const stream = this.state.client.createClipboardStream(data.type); // Send data as a string if it is stored as a string if (typeof data.data === 'string') { writer = new Guacamole.StringWriter(stream); writer.sendText(data.data); writer.sendEnd(); } else { // Write File/Blob asynchronously writer = new Guacamole.BlobWriter(stream); writer.oncomplete = function clipboardSent() { writer.sendEnd(); }; // Begin sending data writer.sendBlob(data.data); } this.setState({ clipboardText: data.data }) if (this.state.protocol === 'ssh') { if (data.data && data.data.length > 0) { message.info('您输入的内容已复制到远程服务器上,使用右键将自动粘贴。'); } } else { if (data.data && data.data.length > 0) { message.info('您输入的内容已复制到远程服务器上'); } } } onTunnelStateChange = (state) => { if (state === Guacamole.Tunnel.State.CLOSED) { console.log('web socket 已关闭'); } }; updateSessionStatus = async (sessionId) => { let result = await request.post(`/sessions/${sessionId}/connect`); if (result.code !== 1) { message.error(result.message); } } onClientStateChange = (state) => { this.setState({ clientState: state }); switch (state) { case STATE_IDLE: message.destroy(); message.loading('正在初始化中...', 0); break; case STATE_CONNECTING: message.destroy(); message.loading('正在努力连接中...', 0); break; case STATE_WAITING: message.destroy(); message.loading('正在等待服务器响应...', 0); break; case STATE_CONNECTED: this.onWindowResize(null); message.destroy(); message.success('连接成功'); // 向后台发送请求,更新会话的状态 this.updateSessionStatus(this.state.sessionId).then(_ => { }) break; case STATE_DISCONNECTING: message.destroy(); message.loading('正在关闭连接...', 0); break; case STATE_DISCONNECTED: message.destroy(); message.error('连接关闭'); break; default: break; } }; onError = (status) => { console.log('通道异常。', status); switch (status.code) { case 256: this.showMessage('未支持的访问'); break; case 512: this.showMessage('远程服务异常'); break; case 513: this.showMessage('服务器忙碌'); break; case 514: this.showMessage('服务器连接超时'); break; case 515: this.showMessage('远程服务异常'); break; case 516: this.showMessage('资源未找到'); break; case 517: this.showMessage('资源冲突'); break; case 518: this.showMessage('资源已关闭'); break; case 519: if (new Date().getTime() - this.state.startTime.getTime() <= 1000 * 30) { this.showMessage('认证失败'); } else { this.showMessage('远程服务未找到'); } break; case 520: this.showMessage('远程服务不可用'); break; case 521: this.showMessage('会话冲突'); break; case 522: this.showMessage('会话连接超时'); break; case 523: this.showMessage('会话已关闭'); break; case 768: this.showMessage('网络不可达'); break; case 769: this.showMessage('服务器密码验证失败'); break; case 771: this.showMessage('客户端被禁止'); break; case 776: this.showMessage('客户端连接超时'); break; case 781: this.showMessage('客户端异常'); break; case 783: this.showMessage('错误的请求类型'); break; case 800: this.showMessage('会话不存在'); break; case 801: this.showMessage('创建隧道失败'); break; case 802: this.showMessage('管理员强制断开了此会话'); break; default: this.showMessage('未知错误。'); } }; showMessage(msg) { message.destroy(); Modal.confirm({ title: '提示', icon: , content: msg, centered: true, okText: '重新连接', cancelText: '关闭页面', onOk() { window.location.reload(); }, onCancel() { window.close(); }, }); } clientClipboardReceived = (stream, mimetype) => { let reader; // If the received data is text, read it as a simple string if (/^text\//.exec(mimetype)) { reader = new Guacamole.StringReader(stream); // Assemble received data into a single string let data = ''; reader.ontext = function textReceived(text) { data += text; }; // Set clipboard contents once stream is finished reader.onend = async () => { message.info('您选择的内容已复制到您的粘贴板中,在右侧的输入框中可同时查看到。'); this.setState({ clipboardText: data }); if (navigator.clipboard) { await navigator.clipboard.writeText(data); } }; } // Otherwise read the clipboard data as a Blob else { reader = new Guacamole.BlobReader(stream, mimetype); reader.onend = () => { this.setState({ clipboardText: reader.getBlob() }) } } }; onKeyDown = (keysym) => { if (this.state.clipboardVisible || this.state.fileSystemVisible) { return true; } this.state.client.sendKeyEvent(1, keysym); if (keysym === 65288) { return false; } }; onKeyUp = (keysym) => { this.state.client.sendKeyEvent(0, keysym); }; fullScreen = () => { let fs = this.state.fullScreen; if (fs) { exitFull(); this.setState({ fullScreen: false, fullScreenBtnText: '进入全屏' }) } else { requestFullScreen(document.documentElement); this.setState({ fullScreen: true, fullScreenBtnText: '退出全屏' }) } } async createSession(assetsId) { let result = await request.post(`/sessions?assetId=${assetsId}&mode=guacd`); if (result['code'] !== 1) { this.showMessage(result['message']); return null; } return result['data']['id']; } async renderDisplay(sessionId, protocol) { let tunnel = new Guacamole.WebSocketTunnel(wsServer + '/tunnel'); tunnel.onstatechange = this.onTunnelStateChange; // Get new client instance let client = new Guacamole.Client(tunnel); // 设置虚拟机剪贴板内容 client.sendClipboard = this.sendClipboard; // 处理从虚拟机收到的剪贴板内容 client.onclipboard = this.clientClipboardReceived; // 处理客户端的状态变化事件 client.onstatechange = this.onClientStateChange; client.onerror = this.onError; // Get display div from document const display = document.getElementById("display"); // Add client to display div const element = client.getDisplay().getElement(); display.appendChild(element); let width = window.innerWidth; let height = window.innerHeight; let dpi = 96; if (protocol === 'ssh' || protocol === 'telnet') { dpi = dpi * 2; } let token = getToken(); let params = { 'sessionId': sessionId, 'width': width, 'height': height, 'dpi': dpi, 'X-Auth-Token': token }; let paramStr = qs.stringify(params); // Connect client.connect(paramStr); // Disconnect on close window.onunload = function () { client.disconnect(); }; // Mouse const mouse = new Guacamole.Mouse(element); mouse.onmousedown = mouse.onmouseup = function (mouseState) { client.sendMouseState(mouseState); }; mouse.onmousemove = function (mouseState) { if (protocol === 'ssh' || protocol === 'telnet') { mouseState.x = mouseState.x * 2; mouseState.y = mouseState.y * 2; client.sendMouseState(mouseState); } else { client.sendMouseState(mouseState); } }; // Keyboard const keyboard = new Guacamole.Keyboard(document); keyboard.onkeydown = this.onKeyDown; keyboard.onkeyup = this.onKeyUp; this.setState({ client: client, containerWidth: width, containerHeight: height, keyboard: keyboard, }); } onWindowResize = (e) => { if (this.state.client) { const display = this.state.client.getDisplay(); const width = window.innerWidth; const height = window.innerHeight; if (this.state.protocol === 'ssh' || this.state.protocol === 'telnet') { let r = 2; display.scale(1 / r); this.state.client.sendSize(width * r, height * r); } else { this.state.client.sendSize(width, height); } this.setState({ containerWidth: width, containerHeight: height, }) this.resize(this.state.sessionId, width, height).then(_ => { }); } }; resize = async (sessionId, width, height) => { let result = await request.post(`/sessions/${sessionId}/resize?width=${width}&height=${height}`); if (result.code !== 1) { message.error(result.message); } } onWindowFocus = (e) => { if (navigator.clipboard && this.state.clientState === STATE_CONNECTED) { navigator.clipboard.readText().then((text) => { this.sendClipboard({ 'data': text, 'type': 'text/plain' }); }) } }; onPaste = (e) => { const cbd = e.clipboardData; const ua = window.navigator.userAgent; // 如果是 Safari 直接 return if (!(e.clipboardData && e.clipboardData.items)) { return; } // Mac平台下Chrome49版本以下 复制Finder中的文件的Bug Hack掉 if (cbd.items && cbd.items.length === 2 && cbd.items[0].kind === "string" && cbd.items[1].kind === "file" && cbd.types && cbd.types.length === 2 && cbd.types[0] === "text/plain" && cbd.types[1] === "Files" && ua.match(/Macintosh/i) && Number(ua.match(/Chrome\/(\d{2})/i)[1]) < 49) { return; } for (let i = 0; i < cbd.items.length; i++) { let item = cbd.items[i]; if (item.kind === "file") { let blob = item.getAsFile(); if (blob.size === 0) { return; } // blob 就是从剪切板获得的文件 可以进行上传或其他操作 } else if (item.kind === 'string') { item.getAsString((str) => { this.sendClipboard({ 'data': str, 'type': 'text/plain' }); }) } } }; sendCombinationKey = (keys) => { for (let i = 0; i < keys.length; i++) { this.state.client.sendKeyEvent(1, keys[i]); } for (let j = 0; j < keys.length; j++) { this.state.client.sendKeyEvent(0, keys[j]); } } render() { const menu = ( this.sendCombinationKey(['65507', '65513', '65535'])}>Ctrl+Alt+Delete this.sendCombinationKey(['65507', '65513', '65288'])}>Ctrl+Alt+Backspace this.sendCombinationKey(['65515', '100'])}>Windows+D this.sendCombinationKey(['65515', '101'])}>Windows+E this.sendCombinationKey(['65515', '114'])}>Windows+R this.sendCombinationKey(['65515', '120'])}>Windows+X this.sendCombinationKey(['65515'])}>Windows ); return (