release v1.2.0
This commit is contained in:
@ -7,13 +7,17 @@ import {getToken, isEmpty} from "../../utils/utils";
|
||||
import {FitAddon} from 'xterm-addon-fit';
|
||||
import "./Access.css"
|
||||
import request from "../../common/request";
|
||||
import {Affix, Button, Col, Drawer, message, Modal, Row} from "antd";
|
||||
import {AppstoreTwoTone, ExclamationCircleOutlined} from "@ant-design/icons";
|
||||
import {Affix, Button, Drawer, Dropdown, Menu, message, Modal, Tooltip} from "antd";
|
||||
import {CodeOutlined, ExclamationCircleOutlined, FolderOutlined, LineChartOutlined} from "@ant-design/icons";
|
||||
import Draggable from "react-draggable";
|
||||
import FileSystem from "./FileSystem";
|
||||
import FileSystem from "../devops/FileSystem";
|
||||
import Stats from "./Stats";
|
||||
import Message from "./Message";
|
||||
|
||||
class Term extends Component {
|
||||
|
||||
statsRef = undefined;
|
||||
|
||||
state = {
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight,
|
||||
@ -21,7 +25,9 @@ class Term extends Component {
|
||||
webSocket: undefined,
|
||||
fitAddon: undefined,
|
||||
sessionId: undefined,
|
||||
enterBtnIndex: 1001
|
||||
session: {},
|
||||
enterBtnIndex: 1001,
|
||||
commands: []
|
||||
};
|
||||
|
||||
componentDidMount = async () => {
|
||||
@ -30,7 +36,11 @@ class Term extends Component {
|
||||
let assetId = urlParams.get('assetId');
|
||||
document.title = urlParams.get('assetName');
|
||||
|
||||
let sessionId = await this.createSession(assetId);
|
||||
let session = await this.createSession(assetId);
|
||||
if (!session) {
|
||||
return;
|
||||
}
|
||||
let sessionId = session['id'];
|
||||
if (isEmpty(sessionId)) {
|
||||
return;
|
||||
}
|
||||
@ -38,14 +48,13 @@ class Term extends Component {
|
||||
let term = new Terminal({
|
||||
fontFamily: 'monaco, Consolas, "Lucida Console", monospace',
|
||||
fontSize: 15,
|
||||
// theme: {
|
||||
// background: '#1b1b1b',
|
||||
// lineHeight: 17
|
||||
// },
|
||||
theme: {
|
||||
background: '#1b1b1b'
|
||||
},
|
||||
rightClickSelectsWord: true,
|
||||
});
|
||||
|
||||
term.open(this.refs.terminal);
|
||||
term.open(document.getElementById('terminal'));
|
||||
const fitAddon = new FitAddon();
|
||||
term.loadAddon(fitAddon);
|
||||
fitAddon.fit();
|
||||
@ -55,6 +64,7 @@ class Term extends Component {
|
||||
|
||||
term.onSelectionChange(async () => {
|
||||
let selection = term.getSelection();
|
||||
console.log(`selection: [${selection}]`);
|
||||
this.setState({
|
||||
selection: selection
|
||||
})
|
||||
@ -73,7 +83,7 @@ class Term extends Component {
|
||||
term.onData(data => {
|
||||
let webSocket = this.state.webSocket;
|
||||
if (webSocket !== undefined) {
|
||||
webSocket.send(JSON.stringify({type: 'data', content: data}));
|
||||
webSocket.send(new Message(Message.Data, data).toString());
|
||||
}
|
||||
});
|
||||
|
||||
@ -92,8 +102,8 @@ class Term extends Component {
|
||||
let pingInterval;
|
||||
webSocket.onopen = (e => {
|
||||
pingInterval = setInterval(() => {
|
||||
webSocket.send(JSON.stringify({type: 'ping'}))
|
||||
}, 5000);
|
||||
webSocket.send(new Message(Message.Ping, "").toString());
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
webSocket.onerror = (e) => {
|
||||
@ -107,16 +117,17 @@ class Term extends Component {
|
||||
}
|
||||
|
||||
webSocket.onmessage = (e) => {
|
||||
let msg = JSON.parse(e.data);
|
||||
let msg = Message.parse(e.data);
|
||||
switch (msg['type']) {
|
||||
case 'connected':
|
||||
case Message.Connected:
|
||||
term.clear();
|
||||
this.updateSessionStatus(sessionId);
|
||||
this.getCommands();
|
||||
break;
|
||||
case 'data':
|
||||
case Message.Data:
|
||||
term.write(msg['content']);
|
||||
break;
|
||||
case 'closed':
|
||||
case Message.Closed:
|
||||
term.writeln(`\x1B[1;3;31m${msg['content']}\x1B[0m `)
|
||||
webSocket.close();
|
||||
break;
|
||||
@ -129,10 +140,14 @@ class Term extends Component {
|
||||
term: term,
|
||||
webSocket: webSocket,
|
||||
fitAddon: fitAddon,
|
||||
sessionId: sessionId
|
||||
sessionId: sessionId,
|
||||
session: session
|
||||
});
|
||||
|
||||
window.addEventListener('resize', this.onWindowResize);
|
||||
window.onunload = function () {
|
||||
webSocket.close();
|
||||
};
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
@ -142,6 +157,17 @@ class Term extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
getCommands = async () => {
|
||||
let result = await request.get('/commands');
|
||||
if (result.code !== 1) {
|
||||
message.error(result.message);
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
commands: result['data']
|
||||
})
|
||||
}
|
||||
|
||||
showMessage(msg) {
|
||||
message.destroy();
|
||||
Modal.confirm({
|
||||
@ -164,9 +190,9 @@ class Term extends Component {
|
||||
let result = await request.post(`/sessions?assetId=${assetsId}&mode=naive`);
|
||||
if (result['code'] !== 1) {
|
||||
this.showMessage(result['message']);
|
||||
return null;
|
||||
return undefined;
|
||||
}
|
||||
return result['data']['id'];
|
||||
return result['data'];
|
||||
}
|
||||
|
||||
updateSessionStatus = async (sessionId) => {
|
||||
@ -194,30 +220,62 @@ class Term extends Component {
|
||||
}, () => {
|
||||
if (webSocket && webSocket.readyState === WebSocket.OPEN) {
|
||||
fitAddon.fit();
|
||||
term.focus();
|
||||
this.focus();
|
||||
let terminalSize = {
|
||||
cols: term.cols,
|
||||
rows: term.rows
|
||||
}
|
||||
webSocket.send(JSON.stringify({type: 'resize', content: JSON.stringify(terminalSize)}));
|
||||
webSocket.send(new Message(Message.Resize, window.btoa(JSON.stringify(terminalSize))).toString());
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
writeCommand = (command) => {
|
||||
let webSocket = this.state.webSocket;
|
||||
if (webSocket !== undefined) {
|
||||
webSocket.send(new Message(Message.Data, command));
|
||||
}
|
||||
this.focus();
|
||||
}
|
||||
|
||||
focus = () => {
|
||||
let term = this.state.term;
|
||||
if (term) {
|
||||
term.focus();
|
||||
}
|
||||
}
|
||||
|
||||
onRef = (statsRef) => {
|
||||
this.statsRef = statsRef;
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
const cmdMenuItems = this.state.commands.map(item => {
|
||||
return <Tooltip placement="left" title={item['content']} color='blue' key={'t-' + item['id']}>
|
||||
<Menu.Item onClick={() => {
|
||||
this.writeCommand(item['content'])
|
||||
}} key={'i-' + item['id']}>{item['name']}</Menu.Item>
|
||||
</Tooltip>;
|
||||
});
|
||||
|
||||
const cmdMenu = (
|
||||
<Menu>
|
||||
{cmdMenuItems}
|
||||
</Menu>
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div ref='terminal' id='terminal' style={{
|
||||
<div id='terminal' style={{
|
||||
height: this.state.height,
|
||||
width: this.state.width,
|
||||
backgroundColor: 'black',
|
||||
overflowX: 'hidden',
|
||||
overflowY: 'hidden',
|
||||
backgroundColor: '#1b1b1b'
|
||||
}}/>
|
||||
|
||||
<Draggable>
|
||||
<Affix style={{position: 'absolute', top: 50, right: 50, zIndex: this.state.enterBtnIndex}}>
|
||||
<Button icon={<AppstoreTwoTone/>} onClick={() => {
|
||||
<Button icon={<FolderOutlined/>} onClick={() => {
|
||||
this.setState({
|
||||
fileSystemVisible: true,
|
||||
enterBtnIndex: 999, // xterm.js 输入框的zIndex是1000,在弹出文件管理页面后要隐藏此按钮
|
||||
@ -226,6 +284,28 @@ class Term extends Component {
|
||||
</Affix>
|
||||
</Draggable>
|
||||
|
||||
<Draggable>
|
||||
<Affix style={{position: 'absolute', top: 50, right: 100, zIndex: this.state.enterBtnIndex}}>
|
||||
<Dropdown overlay={cmdMenu} trigger={['click']} placement="bottomLeft">
|
||||
<Button icon={<CodeOutlined/>}/>
|
||||
</Dropdown>
|
||||
</Affix>
|
||||
</Draggable>
|
||||
|
||||
<Draggable>
|
||||
<Affix style={{position: 'absolute', top: 100, right: 100, zIndex: this.state.enterBtnIndex}}>
|
||||
<Button icon={<LineChartOutlined/>} onClick={() => {
|
||||
this.setState({
|
||||
statsVisible: true,
|
||||
enterBtnIndex: 999, // xterm.js 输入框的zIndex是1000,在弹出文件管理页面后要隐藏此按钮
|
||||
});
|
||||
if (this.statsRef) {
|
||||
this.statsRef.addInterval();
|
||||
}
|
||||
}}/>
|
||||
</Affix>
|
||||
</Draggable>
|
||||
|
||||
<Drawer
|
||||
title={'会话详情'}
|
||||
placement="right"
|
||||
@ -237,16 +317,39 @@ class Term extends Component {
|
||||
fileSystemVisible: false,
|
||||
enterBtnIndex: 1001, // xterm.js 输入框的zIndex是1000,在隐藏文件管理页面后要显示此按钮
|
||||
});
|
||||
this.focus();
|
||||
}}
|
||||
visible={this.state.fileSystemVisible}
|
||||
>
|
||||
<FileSystem
|
||||
storageId={this.state.sessionId}
|
||||
storageType={'sessions'}
|
||||
upload={this.state.session['upload'] === '1'}
|
||||
download={this.state.session['download'] === '1'}
|
||||
delete={this.state.session['delete'] === '1'}
|
||||
rename={this.state.session['rename'] === '1'}
|
||||
edit={this.state.session['edit'] === '1'}
|
||||
minHeight={window.innerHeight - 103}/>
|
||||
</Drawer>
|
||||
|
||||
|
||||
<Row style={{marginTop: 10}}>
|
||||
<Col span={24}>
|
||||
<FileSystem sessionId={this.state.sessionId}/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Drawer
|
||||
title={'状态信息'}
|
||||
placement="right"
|
||||
width={window.innerWidth * 0.8}
|
||||
closable={true}
|
||||
onClose={() => {
|
||||
this.setState({
|
||||
statsVisible: false,
|
||||
enterBtnIndex: 1001, // xterm.js 输入框的zIndex是1000,在隐藏文件管理页面后要显示此按钮
|
||||
});
|
||||
this.focus();
|
||||
if (this.statsRef) {
|
||||
this.statsRef.delInterval();
|
||||
}
|
||||
}}
|
||||
visible={this.state.statsVisible}
|
||||
>
|
||||
<Stats sessionId={this.state.sessionId} onRef={this.onRef}/>
|
||||
</Drawer>
|
||||
</div>
|
||||
);
|
||||
|
Reference in New Issue
Block a user