动态指令增加录屏

This commit is contained in:
dushixiang 2021-02-07 17:46:47 +08:00 committed by dushixiang
parent d72ab4e21e
commit b4421b1b56
9 changed files with 61 additions and 90 deletions

View File

@ -22,7 +22,7 @@ import (
"time" "time"
) )
const Version = "v0.2.0" const Version = "v0.1.2"
func main() { func main() {
log.Fatal(Run()) log.Fatal(Run())

View File

@ -2,7 +2,6 @@ package api
import ( import (
"encoding/json" "encoding/json"
"fmt"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
"github.com/pkg/sftp" "github.com/pkg/sftp"
@ -155,12 +154,8 @@ func SSHEndpoint(c echo.Context) (err error) {
_ = WriteMessage(ws, msg) _ = WriteMessage(ws, msg)
quitChan := make(chan bool) quitChan := make(chan bool)
recordingChan := make(chan string, 1024)
go ReadMessage(nextTerminal, recordingChan, quitChan, ws) go ReadMessage(nextTerminal, quitChan, ws)
go Recoding(nextTerminal, recordingChan, quitChan)
go Monitor(sessionId, recordingChan, quitChan)
for { for {
_, message, err := ws.ReadMessage() _, message, err := ws.ReadMessage()
@ -207,7 +202,7 @@ func SSHEndpoint(c echo.Context) (err error) {
return err return err
} }
func ReadMessage(nextTerminal *term.NextTerminal, stdoutChan chan string, quitChan chan bool, ws *websocket.Conn) { func ReadMessage(nextTerminal *term.NextTerminal, quitChan chan bool, ws *websocket.Conn) {
var quit bool var quit bool
for { for {
@ -227,8 +222,6 @@ func ReadMessage(nextTerminal *term.NextTerminal, stdoutChan chan string, quitCh
} }
if n > 0 { if n > 0 {
s := string(p) s := string(p)
// 发送一份数据到队列中
stdoutChan <- s
msg := Message{ msg := Message{
Type: Data, Type: Data,
Content: s, Content: s,
@ -240,52 +233,6 @@ func ReadMessage(nextTerminal *term.NextTerminal, stdoutChan chan string, quitCh
} }
} }
func Recoding(nextTerminal *term.NextTerminal, recordingChan chan string, quitChan chan bool) {
var quit bool
var s string
for {
select {
case quit = <-quitChan:
if quit {
fmt.Println("退出录屏")
return
}
case s = <-recordingChan:
_ = nextTerminal.Recorder.WriteData(s)
default:
}
}
}
func Monitor(sessionId string, recordingChan chan string, quitChan chan bool) {
var quit bool
var s string
for {
select {
case quit = <-quitChan:
if quit {
fmt.Println("退出监控")
return
}
case s = <-recordingChan:
msg := Message{
Type: Data,
Content: s,
}
observable, ok := global.Store.Get(sessionId)
if ok {
for i := 0; i < len(observable.Observers); i++ {
_ = WriteMessage(observable.Observers[i].WebSocket, msg)
}
}
default:
}
}
}
func WriteMessage(ws *websocket.Conn, msg Message) error { func WriteMessage(ws *websocket.Conn, msg Message) error {
message, err := json.Marshal(msg) message, err := json.Marshal(msg)
if err != nil { if err != nil {

View File

@ -75,7 +75,14 @@ func (ret *NextTerminal) Write(p []byte) (int, error) {
} }
func (ret *NextTerminal) Read() ([]byte, int, error) { func (ret *NextTerminal) Read() ([]byte, int, error) {
return ret.NextWriter.Read() bytes, n, err := ret.NextWriter.Read()
if err != nil {
return nil, 0, err
}
if ret.Recorder != nil && n > 0 {
_ = ret.Recorder.WriteData(string(bytes))
}
return bytes, n, nil
} }
func (ret *NextTerminal) Close() error { func (ret *NextTerminal) Close() error {

View File

@ -108,8 +108,8 @@ func CreateRecording(recordingPath string, h int, w int) (*Recorder, error) {
header := &Header{ header := &Header{
Title: "", Title: "",
Version: 2, Version: 2,
Height: h, Height: 42,
Width: w, Width: 150,
Env: Env{Shell: "/bin/bash", Term: "xterm-256color"}, Env: Env{Shell: "/bin/bash", Term: "xterm-256color"},
Timestamp: int(time.Now().Unix()), Timestamp: int(time.Now().Unix()),
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "next-terminal", "name": "next-terminal",
"version": "0.2.0", "version": "0.1.2",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@ant-design/icons": "^4.3.0", "@ant-design/icons": "^4.3.0",

View File

@ -12,7 +12,7 @@
</style> </style>
</head> </head>
<body> <body>
<asciinema-player id='asciinema-player' src=""></asciinema-player> <asciinema-player id='asciinema-player' src="" rows="42" cols="150"></asciinema-player>
<script src="asciinema-player.js"></script> <script src="asciinema-player.js"></script>
</body> </body>

View File

@ -4,8 +4,10 @@ import {Terminal} from "xterm";
import qs from "qs"; import qs from "qs";
import {wsServer} from "../../common/constants"; import {wsServer} from "../../common/constants";
import "./Console.css" import "./Console.css"
import {getToken} from "../../utils/utils"; import {getToken, isEmpty} from "../../utils/utils";
import {FitAddon} from 'xterm-addon-fit' import {FitAddon} from 'xterm-addon-fit'
import request from "../../common/request";
import {message} from "antd";
class Console extends Component { class Console extends Component {
@ -18,45 +20,33 @@ class Console extends Component {
fitAddon: undefined fitAddon: undefined
}; };
componentDidMount() { componentDidMount = async () => {
let command = this.props.command; let command = this.props.command;
let assetId = this.props.assetId; let assetId = this.props.assetId;
let width = this.props.width; let width = this.props.width;
let height = this.props.height; let height = this.props.height;
let sessionId = await this.createSession(assetId);
let params = { if (isEmpty(sessionId)) {
'width': width, return;
'height': height,
'assetId': assetId
};
let paramStr = qs.stringify(params);
const ua = navigator.userAgent.toLowerCase();
let lineHeight = 1;
if (ua.includes('windows')) {
lineHeight = 1.1;
} }
let term = new Terminal({ let term = new Terminal({
fontFamily: 'monaco, Consolas, "Lucida Console", monospace', fontFamily: 'monaco, Consolas, "Lucida Console", monospace',
fontSize: 14, fontSize: 14,
lineHeight: lineHeight,
theme: { theme: {
background: '#1b1b1b' background: '#1b1b1b'
}, },
rightClickSelectsWord: true, rightClickSelectsWord: true,
}); });
let fitAddon = new FitAddon();
term.loadAddon(fitAddon);
term.open(this.refs.terminal); 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.writeln('Trying to connect to the server ...');
term.onResize(e => {
});
term.onData(data => { term.onData(data => {
let webSocket = this.state.webSocket; let webSocket = this.state.webSocket;
@ -66,8 +56,16 @@ class Console extends Component {
}); });
let token = getToken(); let token = getToken();
let params = {
'cols': term.cols,
'rows': term.rows,
'sessionId': sessionId,
'X-Auth-Token': token
};
let webSocket = new WebSocket(wsServer + '/ssh?X-Auth-Token=' + token + '&' + paramStr); let paramStr = qs.stringify(params);
let webSocket = new WebSocket(wsServer + '/ssh?' + paramStr);
this.props.appendWebsocket({'id': assetId, 'ws': webSocket}); this.props.appendWebsocket({'id': assetId, 'ws': webSocket});
@ -86,6 +84,10 @@ class Console extends Component {
webSocket.onmessage = (e) => { webSocket.onmessage = (e) => {
let msg = JSON.parse(e.data); let msg = JSON.parse(e.data);
switch (msg['type']) { switch (msg['type']) {
case 'connected':
term.clear();
this.updateSessionStatus(sessionId);
break;
case 'data': case 'data':
term.write(msg['content']); term.write(msg['content']);
break; break;
@ -129,6 +131,22 @@ class Console extends Component {
} }
} }
async createSession(assetsId) {
let result = await request.post(`/sessions?assetId=${assetsId}&mode=naive`);
if (result['code'] !== 1) {
this.showMessage(result['message']);
return null;
}
return result['data']['id'];
}
updateSessionStatus = async (sessionId) => {
let result = await request.post(`/sessions/${sessionId}/connect`);
if (result['code'] !== 1) {
message.error(result['message']);
}
}
onWindowResize = (e) => { onWindowResize = (e) => {
let term = this.state.term; let term = this.state.term;
let fitAddon = this.state.fitAddon; let fitAddon = this.state.fitAddon;

View File

@ -105,7 +105,6 @@ class Term extends Component {
let msg = JSON.parse(e.data); let msg = JSON.parse(e.data);
switch (msg['type']) { switch (msg['type']) {
case 'connected': case 'connected':
// term.write(msg['content'])
term.clear(); term.clear();
this.updateSessionStatus(sessionId); this.updateSessionStatus(sessionId);
break; break;

View File

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