动态指令增加录屏
This commit is contained in:
parent
d72ab4e21e
commit
b4421b1b56
2
main.go
2
main.go
@ -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())
|
||||||
|
@ -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 {
|
||||||
|
@ -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 {
|
||||||
|
@ -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()),
|
||||||
}
|
}
|
||||||
|
@ -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",
|
||||||
|
@ -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>
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user