使用React模拟一个终端(XShell)

import React from 'react';
import { Button } from 'antd';
import { AuthDiv } from 'components';
import { Terminal } from 'xterm';
import { FitAddon } from 'xterm-addon-fit';
import FileManager from './FileManager';
import { http, X_TOKEN } from 'libs';
import 'xterm/css/xterm.css';
import styles from './index.module.css';


class WebSSH extends React.Component {
  constructor(props) {
    super(props);
    this.id = props.match.params.id;
    this.socket = null;
    this.term = new Terminal();
    this.container = null;
    this.input = null;
    this.state = {
      visible: false,
      uploading: false,
      managerDisabled: true,
      host: {},
      percent: 0
    }
  }

  componentDidMount() {
    this._fetch();
    const fitPlugin = new FitAddon();
    this.term.loadAddon(fitPlugin);
    const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
    this.socket = new WebSocket(`${protocol}//${window.location.host}/api/ws/ssh/${this.id}/?x-token=${X_TOKEN}`);
    this.socket.onmessage = e => this._read_as_text(e.data);
    this.socket.onopen = () => {
      this.term.open(this.container);
      this.term.focus();
      fitPlugin.fit();
    };
    this.socket.onclose = e => {
      if (e.code === 3333) {
        window.location.href = "about:blank";
        window.close()
      } else {
        setTimeout(() => this.term.write('\r\nConnection is closed.\r\n'), 200)
      }
    };
    this.term.onData(data => this.socket.send(JSON.stringify({data})));
    this.term.onResize(({cols, rows}) => {
      this.socket.send(JSON.stringify({resize: [cols, rows]}))
    });
    window.onresize = () => fitPlugin.fit()
  }

  _read_as_text = (data) => {
    const reader = new window.FileReader();
    reader.onload = () => this.term.write(reader.result);
    reader.readAsText(data, 'utf-8')
  };

  handleShow = () => {
    this.setState({visible: !this.state.visible})
  };

  _fetch = () => {
    http.get(`/api/host/?id=${this.id}`)
      .then(res => {
        document.title = res.name;
        this.setState({host: res, managerDisabled: false})
      })
  };

  render() {
    const {host, visible, managerDisabled} = this.state;
    return (
      <div className={styles.container}>
        <div className={styles.header}>
          <div>{host.name} | {host.username}@{host.hostname}:{host.port}</div>
          <AuthDiv auth="host.console.manager">
            <Button disabled={managerDisabled} type="primary" icon="folder-open"
                    onClick={this.handleShow}>文件管理器</Button>
          </AuthDiv>
        </div>
        <div className={styles.terminal}>
          <div ref={ref => this.container = ref}/>
        </div>
        <FileManager id={this.id} visible={visible} onClose={this.handleShow}/>
      </div>
    )
  }
}

export default WebSSH

package.json:

{
  "name": "terminal",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@antv/data-set": "^0.10.2",
    "ace-builds": "^1.4.7",
    "antd": "^3.25.0",
    "axios": "^0.21.1",
    "bizcharts": "^3.5.6",
    "history": "^4.10.1",
    "http-proxy-middleware": "^0.20.0",
    "lodash": "^4.17.19",
    "mobx": "^5.15.0",
    "mobx-react": "^6.1.4",
    "moment": "^2.24.0",
    "react": "^16.11.0",
    "react-ace": "^8.0.0",
    "react-dom": "^16.11.0",
    "react-router-dom": "^5.1.2",
    "react-scripts": "3.2.0",
    "xterm": "^4.6.0",
    "xterm-addon-fit": "^0.4.0"
  },
  "scripts": {
    "start": "react-app-rewired start",
    "build": "GENERATE_SOURCEMAP=false react-app-rewired build",
    "test": "react-app-rewired test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
  "devDependencies": {
    "@babel/plugin-proposal-decorators": "^7.7.0",
    "babel-plugin-import": "^1.12.2",
    "customize-cra": "^0.8.0",
    "react-app-rewired": "^2.1.5"
  }
}

 

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值