修改默认ssh接入为原生+xterm.js

This commit is contained in:
dushixiang 2021-01-27 23:52:48 +08:00 committed by dushixiang
parent 8eb11a73a7
commit b13ae6b049
4 changed files with 196 additions and 10 deletions

View File

@ -50,9 +50,10 @@ func (w *NextWriter) Read() ([]byte, int, error) {
}
const (
Data = "data"
Resize = "resize"
Closed = "closed"
Connected = "connected"
Data = "data"
Resize = "resize"
Closed = "closed"
)
type Message struct {
@ -61,8 +62,8 @@ type Message struct {
}
type WindowSize struct {
Height int `json:"height"`
Width int `json:"width"`
Cols int `json:"cols"`
Rows int `json:"rows"`
}
func SSHEndpoint(c echo.Context) error {
@ -136,8 +137,8 @@ func SSHEndpoint(c echo.Context) error {
}
msg := Message{
Type: Data,
Content: "Connect to server successfully.",
Type: Connected,
Content: "Connect to server successfully.\r\n",
}
_ = WriteMessage(ws, msg)
@ -199,7 +200,7 @@ func SSHEndpoint(c echo.Context) error {
logrus.Warnf("解析SSH会话窗口大小失败: %v", err)
continue
}
if err := session.WindowChange(winSize.Height, winSize.Height); err != nil {
if err := session.WindowChange(winSize.Rows, winSize.Cols); err != nil {
logrus.Warnf("更改SSH会话窗口大小失败: %v", err)
continue
}

View File

@ -40,6 +40,7 @@ import {isEmpty, NT_PACKAGE} from "./utils/utils";
import {isAdmin} from "./service/permission";
import UserGroup from "./components/user/UserGroup";
import LoginLog from "./components/session/LoginLog";
import AccessSSH from "./components/access/AccessSSH";
const {Footer, Sider} = Layout;
@ -66,7 +67,7 @@ class App extends Component {
componentDidMount() {
let hash = window.location.hash;
let current = hash.replace('#/', '');
if(isEmpty(current)){
if (isEmpty(current)) {
current = 'dashboard';
}
this.setCurrent(current);
@ -113,6 +114,7 @@ class App extends Component {
<Switch>
<Route path="/access" component={Access}/>
<Route path="/access-ssh" component={AccessSSH}/>
<Route path="/login"><Login updateUser={this.updateUser}/></Route>
<Route path="/">

View File

@ -0,0 +1,179 @@
import React, {Component} from 'react';
import "xterm/css/xterm.css"
import {Terminal} from "xterm";
import qs from "qs";
import {wsServer} from "../../common/constants";
import "./Console.css"
import {getToken} from "../../utils/utils";
import {FitAddon} from 'xterm-addon-fit';
class AccessSSH extends Component {
state = {
width: window.innerWidth,
height: window.innerHeight,
term: undefined,
webSocket: undefined,
fitAddon: undefined
};
componentDidMount() {
let urlParams = new URLSearchParams(this.props.location.search);
let assetId = urlParams.get('assetId');
let params = {
'width': this.state.width,
'height': this.state.height,
'assetId': assetId
};
let paramStr = qs.stringify(params);
let term = new Terminal({
fontFamily: 'monaco, Consolas, "Lucida Console", monospace',
fontSize: 14,
theme: {
background: '#1b1b1b',
lineHeight: 17
},
rightClickSelectsWord: true,
});
term.open(this.refs.terminal);
const fitAddon = new FitAddon();
term.loadAddon(fitAddon);
fitAddon.fit();
term.focus();
term.writeln('Trying to connect to the server ...');
term.onSelectionChange(async () => {
let selection = term.getSelection();
this.setState({
selection: selection
})
if (navigator.clipboard) {
await navigator.clipboard.writeText(selection);
}
});
term.attachCustomKeyEventHandler((e) => {
if (e.ctrlKey && e.key === 'c' && this.state.selection) {
return false;
}
return !(e.ctrlKey && e.key === 'v');
});
term.onData(data => {
let webSocket = this.state.webSocket;
if (webSocket !== undefined) {
webSocket.send(JSON.stringify({type: 'data', content: data}));
}
});
let token = getToken();
let webSocket = new WebSocket(wsServer + '/ssh?X-Auth-Token=' + token + '&' + paramStr);
let pingInterval;
webSocket.onopen = (e => {
pingInterval = setInterval(() => {
webSocket.send(JSON.stringify({type: 'ping'}))
}, 5000);
let terminalSize = {
cols: term.cols,
rows: term.rows
}
webSocket.send(JSON.stringify({type: 'resize', content: JSON.stringify(terminalSize)}));
});
webSocket.onerror = (e) => {
term.writeln("Failed to connect to server.");
}
webSocket.onclose = (e) => {
term.writeln("Connection is closed.");
if (pingInterval) {
clearInterval(pingInterval);
}
}
webSocket.onmessage = (e) => {
let msg = JSON.parse(e.data);
switch (msg['type']) {
case 'connected':
console.log(msg['content'])
this.onWindowResize();
break;
case 'data':
term.write(msg['content']);
break;
case 'closed':
term.writeln(`\x1B[1;3;31m${msg['content']}\x1B[0m `)
webSocket.close();
break;
default:
break;
}
}
this.setState({
term: term,
webSocket: webSocket,
fitAddon: fitAddon
});
window.addEventListener('resize', this.onWindowResize);
}
componentWillUnmount() {
let webSocket = this.state.webSocket;
if (webSocket) {
webSocket.close()
}
}
terminalSize() {
return {
cols: Math.floor(this.state.width / 7.5),
rows: Math.floor(window.innerHeight / 17),
}
}
onWindowResize = (e) => {
let term = this.state.term;
let fitAddon = this.state.fitAddon;
let webSocket = this.state.webSocket;
this.setState({
width: window.innerWidth,
height: window.innerHeight,
}, () => {
if (webSocket && webSocket.readyState === WebSocket.OPEN) {
fitAddon.fit();
term.focus();
let terminalSize = {
cols: term.cols,
rows: term.rows
}
webSocket.send(JSON.stringify({type: 'resize', content: JSON.stringify(terminalSize)}));
}
});
};
render() {
return (
<div>
<div ref='terminal' id='terminal' style={{
height: this.state.height,
width: this.state.width,
overflowX: 'hidden',
overflowY: 'hidden'
}}/>
</div>
);
}
}
export default AccessSSH;

View File

@ -319,7 +319,11 @@ class Asset extends Component {
if (result.code === 1) {
if (result.data === true) {
message.success({content: '检测完成,您访问的资产在线,即将打开窗口进行访问。', key: id, duration: 3});
window.open(`#/access?assetsId=${id}&protocol=${protocol}`);
if(protocol === 'ssh'){
window.open(`#/access-ssh?assetId=${id}`);
}else {
window.open(`#/access?assetsId=${id}&protocol=${protocol}`);
}
} else {
message.warn('您访问的资产未在线,请确认网络状态。', 10);
}