爱上开源之DockerUI-xterm.js实现Web控制台

前言

xterm.js是模拟终端产品中最为市场推崇的,较多的开源项目在实现Shell模拟终端都是用了这个产品。 在DockerUI中,dockerUI提供了类似Shell的功能,可以在DockerUI里直接连接到容器里,执行容器的终端命令, 类似于在docker环境下,执行docker container exec -it 这样的命令行功能。 在DockerUI里也使用了xterm.js这个项目来实现了WEB方式的模拟控制台终端。

先看看效果图

注意看清楚哟, 和Xshell长的很像,但是是WEB方式实现的。

今天这篇文章,就来谈谈xterm.js在DockerUI里的具体实现Web控制台的过程。

引入xterm.js

 说实在话,虽然这个xterm.js确实在此类产品中的名气确实最大,但是其官方网站上的文档和资料就真的是匹配不上这个江湖地位了, 文档基本没有, 只能看源码进行猜和试错。 我们DockerUI里对xterm.js的集成,基本上全部都是自己试出来的。

ROOT_RES_URL + "/static/plugins/xterm/lib/xterm.js",
ROOT_RES_URL + "/static/plugins/xterm/lib/xterm-addon-fit.js"
ROOT_RES_URL + "/static/plugins/xterm/css/xterm.css"

DockerUI项目没有使用node.js的开发架构, 是使用的CubeUI的前台开发架构,基于EasyUI的前台框架改造而来, 属于类似layui的开发方式,都是原生态的js+html的前后台分离方式。 但是xterm.js的官网里都是基于npm的方式,不适用于这里。

实例化Terminate对象

在DockerUI里把这个Terminate创建过程进行封装,封装一个方法来实现


function createTerminate(target, onKey, rows, cols){
    rows = rows || 36;
    cols = cols || 80;
    let term ;
    term = new Terminal({

        rendererType: "canvas", //渲染类型
        convertEol: true, //启用时,光标将设置为下一行的开头
        scrollback: 100, //终端中的回滚量
        disableStdin: false, //是否应禁用输入。
        cursorStyle: "underline", //光标样式
        cursorBlink: true, //光标闪烁
        cols: cols,
        rows: rows,

        theme: {
            foreground: "#14e264", //字体
            background: "#002833", //背景色
            cursor: "help", //设置光标
            lineHeight: 16
        },
        bellStyle:'sound',
        rightClickSelectsWord:true,
        screenReaderMode:true,
        allowProposedApi: true,
        LogLevel: 'debug',
        tabStopWidth: 4,
    });

    term.onKey((event) => {
        if(onKey){
            onKey.call(term, event.key, event.domEvent)
        }
    });

    term.open(target);

    //term.open(document.getElementById('container-terminal'));
    term.writeln('Welcome to web-console of docker.ui');
    term.writeln('This is a local terminal emulation, without a real terminal in the back-end.');
    term.writeln('Type some keys and commands to play around. Press the key "ctrl-Z" to exit the console');

    term.focus()

    return term
}

在这一段代码里, 把传入的target对象进行绑定,根据传入的rows,cols产生一个Terminal对象,并且绑定了Terminal的onkey事件,当Terminal对象里有输入时触发事件。如果和后天Docker容器的通信的监听事件绑定到一起,这样Terminal有输入事件,输入字符后,把对应字符转换成命令行,发送到Docker通信的通道里,Docker容器通过通道收到WebConsole发送过来的命令,进行处理,处理后通过通道返回执行结果给Ternimal, Terminal通过调用write或者writeln方法,把返回结果显示到webconsole里即可。  这个方案完全可行。

开始实现和Docker容器的通信

很明显这里是个双向通信的通道方式, 通过Web实现双向通信最佳的方案当然就是webSocket的方式了, 所以首先实现go后端的http调用支持websocket。  dockerUI项目的Http服务这块都不是用的http的原生服务,有兴趣的可以关注我的其他的文章,很多文章都谈到了这点, 同样DockerUI的http服务也是使用了fasthttp作为底层服务。  在fasthttp里实现支持websocket的handler。

func InitWsRouter(router *routing.Router, routerGroup *routing.RouteGroup) {
	routerGroup.Any("/echo", EchoWSHandler)
	routerGroup.Any("/exec", ExecWSHandler)
}


func ExecWSHandler(ctx *routing.Context) error {
	execId := string(ctx.FormValue("id"))

	if util4go.IsEmpty(execId) {
		fasthttputil.Result.Fail("没有设置需要执行的ID").Response(ctx.RequestCtx)
		return nil
	}

	ri, err := getRequestInfo4WS("/docker-api-ws", ctx)
	if err != nil {
		return err
	}

	err = upgrader.Upgrade(ctx.RequestCtx, func(ws *websocket.Conn) {
		defer ws.Close()
		ctx.Request.Header.Del("Origin")
		hijackExecStartOperation(ws, ri.host, execId, ri.version)
	})

	if err != nil {
		if _, ok := err.(websocket.HandshakeError); ok {
			Logger.Info(err)
		}
		return err
	}

	ctx.Response.Header.Set("Sec-WebSocket-Protocol", ri.version)

	return nil
}

前台通过websocket调用此REST API,然后和Terminal交互


            let url = context + "/docker-api-ws/exec?id="+response.ExecID;

            ws = $.app.websocket(
                url,
                function(e){
                    console.log(e);
                    term = createTerminate(document.getElementById("container-terminal-"+response.ExecID),
                        function(key, ev){
                            //ws.send(key)
                            if(!sendWs(ws, key)){
                                alert('控制端已经失去通信,请重新打开');
                            }
                    });

                    if(fn){
                        fn()
                    }

                    wses.push(ws);

                }, function (e) {
                    term.write(e.data)
                    console.log(e);
                }, function (e) {
                    console.log(e);
                    closeConsoleDg(dgId, false);
                }, function (e) {
                    console.log(e);
                    $.app.show("错误消息{0}".format(e.reason))
                }, [local_node.node_host, local_node.node_port, local_node.node_version])

 看看最终的效果

20220814_213300

https://live.csdn.net/v/231793

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

inthirties

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值