diff --git a/spug_web/src/pages/exec/task/ExecConsole.js b/spug_web/src/pages/exec/task/ExecConsole.js
index f26dced..54cfa7f 100644
--- a/spug_web/src/pages/exec/task/ExecConsole.js
+++ b/spug_web/src/pages/exec/task/ExecConsole.js
@@ -14,9 +14,9 @@ import {
FullscreenExitOutlined
} from '@ant-design/icons';
import { Modal, Collapse, Tooltip } from 'antd';
-import { X_TOKEN } from 'libs';
import OutView from './OutView';
-import styles from './index.module.css';
+import { X_TOKEN } from 'libs';
+import styles from './index.module.less';
import store from './store';
@@ -25,9 +25,11 @@ class ExecConsole extends React.Component {
constructor(props) {
super(props);
this.socket = null;
- this.elements = {};
+ this.terms = {};
+ this.outputs = {};
this.state = {
- data: {}
+ activeKey: Object.keys(store.outputs)[0],
+ isFullscreen: false
}
}
@@ -36,19 +38,31 @@ class ExecConsole extends React.Component {
this.socket = new WebSocket(`${protocol}//${window.location.host}/api/ws/exec/${store.token}/?x-token=${X_TOKEN}`);
this.socket.onopen = () => {
this.socket.send('ok');
- for (let item of Object.values(store.outputs)) {
- item['system'].push('### Waiting for schedule\n')
- }
};
this.socket.onmessage = e => {
if (e.data === 'pong') {
this.socket.send('ping')
} else {
- const {key, data, type, status} = JSON.parse(e.data);
- if (status !== undefined) {
- store.outputs[key]['status'] = status
- } else if (data) {
- store.outputs[key][type].push(data);
+ const {key, data, status} = JSON.parse(e.data);
+ if (status !== undefined) store.outputs[key].status = status;
+ if (data) {
+ if (this.terms[key]) {
+ this.terms[key].write(data)
+ } else if (this.outputs[key]) {
+ this.outputs[key] += data
+ } else {
+ this.outputs[key] = data
+ }
+ }
+ }
+ };
+ this.socket.onerror = () => {
+ for (let key of Object.keys(store.outputs)) {
+ store.outputs[key]['status'] = 'websocket error'
+ if (this.terms[key]) {
+ this.terms[key].write('Websocket connection failed!')
+ } else {
+ this.outputs[key] = 'Websocket connection failed!'
}
}
}
@@ -59,36 +73,37 @@ class ExecConsole extends React.Component {
store.isFullscreen = false;
}
- genExtra = (outputs) => {
- let latest, icon;
- if (outputs['status'] === -2) {
+ genExtra = (status) => {
+ if (status === -2) {
return ;
- } else if (outputs['status'] === 0) {
- latest = outputs['info'][outputs['info'].length - 1];
- icon =
+ } else if (status === 0) {
+ return
} else {
- latest = outputs['error'][outputs['error'].length - 1]
- icon =
-
-
+ return (
+
+
+
+ )
}
- return (
-
- )
};
+ handleUpdate = (data) => {
+ this.setState(data, () => {
+ const key = this.state.activeKey;
+ if (key && this.terms[key]) setTimeout(this.terms[key].fit)
+ })
+ }
+
render() {
+ const {isFullscreen, activeKey} = this.state;
return (
执行控制台,
- store.isFullscreen = !store.isFullscreen}>
- {store.isFullscreen ?
:
}
+
this.handleUpdate({isFullscreen: !isFullscreen})}>
+ {isFullscreen ? : }
]}
footer={null}
@@ -97,15 +112,16 @@ class ExecConsole extends React.Component {
maskClosable={false}>
this.handleUpdate({activeKey: key})}
expandIcon={({isActive}) => }>
{Object.entries(store.outputs).map(([key, item], index) => (
- {item['title']}}
- extra={this.genExtra(item)}>
-
+ {item['title']}} extra={this.genExtra(item.status)}>
+ this.outputs[key]}
+ setTerm={term => this.terms[key] = term}/>
))}
diff --git a/spug_web/src/pages/exec/task/OutView.js b/spug_web/src/pages/exec/task/OutView.js
index 35894e9..061eb87 100644
--- a/spug_web/src/pages/exec/task/OutView.js
+++ b/spug_web/src/pages/exec/task/OutView.js
@@ -3,36 +3,33 @@
* Copyright (c)
* Released under the AGPL-3.0 License.
*/
-import React from 'react';
-import { toJS } from 'mobx';
-import { observer } from 'mobx-react';
-import styles from './index.module.css';
-import store from './store';
+import React, { useEffect, useRef } from 'react';
+import { FitAddon } from 'xterm-addon-fit';
+import { Terminal } from 'xterm';
-@observer
-class OutView extends React.Component {
- constructor(props) {
- super(props);
- this.el = null;
- }
+function OutView(props) {
+ const el = useRef()
- componentDidUpdate(prevProps, prevState, snapshot) {
- setTimeout(() => {
- if (this.el) this.el.scrollTop = this.el.scrollHeight
- }, 100)
- }
+ useEffect(() => {
+ const fitPlugin = new FitAddon()
+ const term = new Terminal({disableStdin: true})
+ term.loadAddon(fitPlugin)
+ term.setOption('theme', {background: '#fff', foreground: '#000', selection: '#999'})
+ term.open(el.current)
+ const data = props.getOutput()
+ if (data) term.write(data)
+ term.fit = () => {
+ const dimensions = fitPlugin.proposeDimensions()
+ if (dimensions.cols && dimensions.rows) fitPlugin.fit()
+ }
+ props.setTerm(term)
+ fitPlugin.fit()
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [])
- render() {
- const outputs = toJS(this.props.outputs);
- const maxHeight = store.isFullscreen ? 500 : 300;
- return (
- this.el = ref} className={styles.console} style={{maxHeight}}>
-
{outputs['system']}
-
{outputs['info']}
-
{outputs['error']}
-
- )
- }
+ return (
+
+ )
}
export default OutView
\ No newline at end of file
diff --git a/spug_web/src/pages/exec/task/index.js b/spug_web/src/pages/exec/task/index.js
index f821f12..c6e8c67 100644
--- a/spug_web/src/pages/exec/task/index.js
+++ b/spug_web/src/pages/exec/task/index.js
@@ -53,7 +53,7 @@ class TaskIndex extends React.Component {
icon={}
onClick={() => store.showHost = true}>从主机列表中选择
- this.setState({body})}/>
+ this.setState({body})}/>
diff --git a/spug_web/src/pages/exec/task/index.module.css b/spug_web/src/pages/exec/task/index.module.css
deleted file mode 100644
index 1230594..0000000
--- a/spug_web/src/pages/exec/task/index.module.css
+++ /dev/null
@@ -1,37 +0,0 @@
-.collapse :global(.ant-collapse-content-box) {
- padding: 0;
-}
-
-.fullscreen {
- position: absolute;
- top: 0;
- right: 0;
- display: block;
- width: 56px;
- height: 56px;
- line-height: 56px;
- text-align: center;
- cursor: pointer;
- color: rgba(0, 0, 0, .45);
- margin-right: 56px;
-}
-.fullscreen:hover {
- color: #000;
-}
-
-.console {
- padding: 10px 15px;
- overflow: scroll;
-}
-
-.header {
- overflow: hidden;
- text-overflow: ellipsis;
- max-width: 400px;
- padding-right: 20px;
- margin: 0
-}
-
-pre {
- margin: 0;
-}
\ No newline at end of file
diff --git a/spug_web/src/pages/exec/task/index.module.less b/spug_web/src/pages/exec/task/index.module.less
new file mode 100644
index 0000000..5fb30ab
--- /dev/null
+++ b/spug_web/src/pages/exec/task/index.module.less
@@ -0,0 +1,33 @@
+.collapse :global(.ant-collapse-content-box) {
+ padding: 0;
+}
+
+.fullscreen {
+ position: absolute;
+ top: 0;
+ right: 0;
+ display: block;
+ width: 56px;
+ height: 56px;
+ line-height: 56px;
+ text-align: center;
+ cursor: pointer;
+ color: rgba(0, 0, 0, .45);
+ margin-right: 56px;
+}
+
+.fullscreen:hover {
+ color: #000;
+}
+
+.header {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ max-width: 400px;
+ padding-right: 20px;
+ margin: 0
+}
+
+pre {
+ margin: 0;
+}
\ No newline at end of file
diff --git a/spug_web/src/pages/exec/task/store.js b/spug_web/src/pages/exec/task/store.js
index b8b0d8d..b05d4d8 100644
--- a/spug_web/src/pages/exec/task/store.js
+++ b/spug_web/src/pages/exec/task/store.js
@@ -10,7 +10,6 @@ class Store {
@observable outputs = {};
@observable host_ids = [];
@observable token = null;
- @observable isFullscreen = false;
@observable showHost = false;
@observable showConsole = false;
@observable showTemplate = false;
@@ -33,9 +32,6 @@ class Store {
const key = `${host.hostname}:${host.port}`;
this.outputs[key] = {
title: `${host.name}(${key})`,
- system: ['### Establishing communication\n'],
- info: [],
- error: [],
status: -2
}
}