master
Evan Chen 2021-11-16 23:57:32 +08:00
parent 12084a659c
commit d379ffff9b
31 changed files with 4956 additions and 9242 deletions

3
.gitignore vendored
View File

@ -1 +1,2 @@
node_modules
node_modules
.parcel-cache

View File

@ -1,29 +1,14 @@
package main
import (
_ "embed"
"flag"
"net/http"
"os"
"os/exec"
"strings"
"github.com/creack/pty"
"github.com/gorilla/websocket"
"kumoly.io/lib/klog"
"kumoly.io/lib/ksrv"
"kumoly.io/lib/ksrv/engine"
"kumoly.io/tools/goshell/public"
"kumoly.io/tools/gterm"
)
//go:embed index.html
var index string
var tmpl *engine.Engine
var servePublic = http.FileServer(http.FS(public.FS))
var (
flagAppName string
flagAppName string = "gterm"
flagAddr string
flagShell string
)
@ -37,59 +22,7 @@ func main() {
flag.Parse()
server := ksrv.New()
g := gterm.New()
mux := http.NewServeMux()
tmpl = engine.Must(engine.New("").Parse(index))
mux.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {
if r.URL.String() == "/" {
tmpl.Execute(rw, App{flagAppName})
return
}
file, err := public.FS.Open(strings.TrimPrefix(r.URL.String(), "/"))
if err != nil {
klog.Debug(err)
tmpl.Execute(rw, App{flagAppName})
return
}
stat, err := file.Stat()
if err != nil || stat.IsDir() {
klog.Debug(err)
tmpl.Execute(rw, App{flagAppName})
return
}
servePublic.ServeHTTP(rw, r)
})
server.Handle(mux).Listen(flagAddr).Serve()
}
type App struct {
AppName string
}
type windowSize struct {
Rows uint16 `json:"rows"`
Cols uint16 `json:"cols"`
X uint16
Y uint16
}
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
}
func handleWebsocket(w http.ResponseWriter, r *http.Request) {
l := klog.Sub(ksrv.GetIP(r))
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
l.Error("Unable to upgrade connection, err: ", err)
return
}
cmd := exec.Command("/bin/bash", "-l")
cmd.Env = append(os.Environ(), "TERM=xterm")
pty.Winsize
server.Handle(g).Listen(flagAddr).Serve()
}

3
go.mod
View File

@ -1,10 +1,11 @@
module kumoly.io/tools/goshell
module kumoly.io/tools/gterm
go 1.17
require (
github.com/creack/pty v1.1.17
github.com/gorilla/websocket v1.4.2
github.com/rs/xid v1.3.0
kumoly.io/lib/klog v0.0.8
kumoly.io/lib/ksrv v0.0.2-0.20211112060911-0d61b343a298
)

2
go.sum
View File

@ -4,6 +4,8 @@ github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0U
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/rs/xid v1.3.0 h1:6NjYksEUlhurdVehpc7S7dk6DAmcKv8V9gG0FsVN2U4=
github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b h1:1VkfZQv42XQlA/jchYumAnv1UPo6RgF9rJFkTgZIxO4=
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

276
gterm.go
View File

@ -1,33 +1,287 @@
package gterm
import (
"bytes"
_ "embed"
"encoding/json"
"fmt"
"net/http"
"os"
"os/exec"
"strings"
"sync"
"time"
"github.com/creack/pty"
"github.com/gorilla/websocket"
"github.com/rs/xid"
"kumoly.io/lib/klog"
"kumoly.io/lib/ksrv"
"kumoly.io/lib/ksrv/engine"
"kumoly.io/tools/gterm/public"
)
var log = klog.Sub("gterm")
//go:embed index.html
var index string
type Config struct {
AllowIP string
var tmpl *engine.Engine
var servePublic = http.FileServer(http.FS(public.FS))
var pool map[string]chan struct{}
func init() {
tmpl = engine.Must(engine.New("").Parse(index))
pool = map[string]chan struct{}{}
}
type GTerm struct {
}
func init() {
AppName string
// Arguments is a list of strings to pass as arguments to the specified Command
Args []string
// Command is the path to the binary we should create a TTY for
Cmd string
// Envs env pairs to pass to the command
Envs []string
// ErrorLimit defines the number of consecutive errors that can happen
// before a connection is considered unusable
ErrorLimit int
// Timeout defines the maximum duration between which a ping and pong
// cycle should be tolerated, beyond this the connection should be deemed dead
Timeout time.Duration
BufferSize int
}
func New() *GTerm {
g := &GTerm{}
ksrv.New()
return g
// if g.Cmd == "" {
// g.Cmd = "bash"
// }
// if len(g.Envs) == 0 {
// g.Envs = os.Environ()
// }
// if g.Timeout < time.Second {
// g.Timeout = 20 * time.Second
// }
// if g.ErrorLimit == 0 {
// g.ErrorLimit = 10
// }
// if g.BufferSize == 0 {
// g.BufferSize = 512
// }
return &GTerm{
AppName: "GTERM",
Cmd: "bash",
Envs: os.Environ(),
Timeout: 20 * time.Second,
ErrorLimit: 10,
BufferSize: 512,
}
}
func (g *GTerm) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if strings.HasPrefix(r.URL.String(), "/ws") {
g.WS(w, r)
} else {
g.App(w, r)
}
}
func (g *GTerm) BaseEndpoint(w http.ResponseWriter, r *http.Request)
type App struct {
AppName string
}
func (g *GTerm) App(w http.ResponseWriter, r *http.Request) {
if r.URL.String() == "/" {
tmpl.Execute(w, App{g.AppName})
return
}
file, err := public.FS.Open(strings.TrimPrefix(r.URL.String(), "/"))
if err != nil {
// tmpl.Execute(w, App{g.AppName})
http.NotFound(w, r)
return
}
stat, err := file.Stat()
if err != nil || stat.IsDir() {
// tmpl.Execute(w, App{g.AppName})
http.NotFound(w, r)
return
}
servePublic.ServeHTTP(w, r)
}
func (g *GTerm) WS(w http.ResponseWriter, r *http.Request) {
id := ksrv.GetIP(r) + " : " + xid.New().String()
pool[id] = make(chan struct{}, 1)
defer func() {
close(pool[id])
delete(pool, id)
}()
l := klog.Sub(id)
l.Info("connection established.")
upgrader := websocket.Upgrader{
HandshakeTimeout: 0,
ReadBufferSize: g.BufferSize,
WriteBufferSize: g.BufferSize,
}
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
l.Error(err)
return
}
cmd := exec.Command(g.Cmd, g.Args...)
cmd.Env = g.Envs
tty, err := pty.Start(cmd)
if err != nil {
l.Error(err)
conn.WriteMessage(websocket.TextMessage, []byte(err.Error()))
}
defer func() {
if err := cmd.Process.Kill(); err != nil {
l.Error(err)
}
if _, err := cmd.Process.Wait(); err != nil {
l.Error(err)
}
if err := tty.Close(); err != nil {
l.Error(err)
}
if err := conn.Close(); err != nil {
l.Error(err)
}
}()
var connectionClosed bool
var waiter sync.WaitGroup
waiter.Add(1)
// this is a keep-alive loop that ensures connection does not hang-up itself
lastPongTime := time.Now()
conn.SetPongHandler(func(msg string) error {
lastPongTime = time.Now()
return nil
})
go func() {
for {
if err := conn.WriteMessage(websocket.PingMessage, []byte("keepalive")); err != nil {
l.Warn("failed to write ping message")
return
}
time.Sleep(g.Timeout / 2)
if time.Since(lastPongTime) > g.Timeout {
l.Warn("failed to get response from ping, triggering disconnect now...")
waiter.Done()
return
}
l.Debug("received response from ping successfully")
}
}()
// tty >> xterm.js
go func() {
errorCounter := 0
for {
// consider the connection closed/errored out so that the socket handler
// can be terminated - this frees up memory so the service doesn't get
// overloaded
if errorCounter > g.ErrorLimit {
waiter.Done()
break
}
buffer := make([]byte, g.BufferSize)
readLength, err := tty.Read(buffer)
if err != nil {
l.Warn("failed to read from tty: ", err)
if err := conn.WriteMessage(websocket.TextMessage, []byte("bye!")); err != nil {
l.Warn("failed to send termination message from tty to xterm.js: ", err)
}
waiter.Done()
return
}
if err := conn.WriteMessage(websocket.BinaryMessage, buffer[:readLength]); err != nil {
l.Warn("failed to send ", readLength, " bytes from tty to xterm.js")
errorCounter++
continue
}
l.Debug("sent message of size ", readLength, " bytes from tty to xterm.js")
errorCounter = 0
}
}()
// tty << xterm.js
go func() {
for {
// data processing
messageType, data, err := conn.ReadMessage()
if err != nil {
if !connectionClosed {
l.Warn("failed to get next reader: ", err)
}
return
}
dataLength := len(data)
dataBuffer := bytes.Trim(data, "\x00")
dataType, ok := WebsocketMessageType[messageType]
if !ok {
dataType = "uunknown"
}
l.Info(fmt.Sprintf("received %s (type: %v) message of size %v byte(s) from xterm.js with key sequence: %v", dataType, messageType, dataLength, dataBuffer))
// process
if dataLength == -1 { // invalid
l.Warn("failed to get the correct number of bytes read, ignoring message")
continue
}
// handle resizing
if messageType == websocket.BinaryMessage {
if dataBuffer[0] == 1 {
ttySize := &TTYSize{}
resizeMessage := bytes.Trim(dataBuffer[1:], " \n\r\t\x00\x01")
if err := json.Unmarshal(resizeMessage, ttySize); err != nil {
l.Warn(fmt.Sprintf("failed to unmarshal received resize message '%s': %s", string(resizeMessage), err))
continue
}
l.InfoF(klog.H{"rows": ttySize.Rows, "columns": ttySize.Cols}, "resizing tty...")
if err := pty.Setsize(tty, &pty.Winsize{
Rows: ttySize.Rows,
Cols: ttySize.Cols,
}); err != nil {
l.Warn("failed to resize tty, error: ", err)
}
continue
}
}
// write to tty
bytesWritten, err := tty.Write(dataBuffer)
if err != nil {
l.Warn(fmt.Sprintf("failed to write %v bytes to tty: %s", len(dataBuffer), err))
continue
}
l.Debug(bytesWritten, " bytes written to tty...")
}
}()
waiter.Wait()
l.Info("closing connection...")
connectionClosed = true
}
var WebsocketMessageType = map[int]string{
websocket.BinaryMessage: "binary",
websocket.TextMessage: "text",
websocket.CloseMessage: "close",
websocket.PingMessage: "ping",
websocket.PongMessage: "pong",
}
// TTYSize represents a JSON structure to be sent by the frontend
// xterm.js implementation to the xterm.js websocket handler
type TTYSize struct {
Cols uint16 `json:"cols"`
Rows uint16 `json:"rows"`
X uint16 `json:"x"`
Y uint16 `json:"y"`
}

View File

@ -1 +1,47 @@
<!DOCTYPE html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="favicon.ico"><title>{{.AppName}}</title><link href="css/app.0e433876.css" rel="preload" as="style"><link href="css/chunk-vendors.78de0e90.css" rel="preload" as="style"><link href="js/app.5bd01a68.js" rel="preload" as="script"><link href="js/chunk-vendors.3d58276e.js" rel="preload" as="script"><link href="css/chunk-vendors.78de0e90.css" rel="stylesheet"><link href="css/app.0e433876.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but {{.AppName}} doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="js/chunk-vendors.3d58276e.js"></script><script src="js/app.5bd01a68.js"></script></body></html>
<!DOCTYPE html>
<html>
<head>
<title>{{.AppName}}</title>
<style>
html::-webkit-scrollbar,
body::-webkit-scrollbar,
div::-webkit-scrollbar {
display: none;
width: 0;
}
html,
body {
margin: 0;
overflow: hidden;
padding: 0;
}
div#terminal {
height: 100%;
left: 0;
position: absolute;
top: 0;
width: 100%;
}
div#terminal div {
height: 100%;
}
.xterm-viewport,
.xterm-screen {
height: 100%;
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<div id="terminal"></div>
<script src="./main.js"></script>
</body>
</html>

56
main.js Normal file
View File

@ -0,0 +1,56 @@
import { Terminal } from 'xterm'
import { AttachAddon } from 'xterm-addon-attach';
import { FitAddon } from 'xterm-addon-fit';
import { SerializeAddon } from "xterm-addon-serialize";
import { Unicode11Addon } from 'xterm-addon-unicode11';
import { WebLinksAddon } from 'xterm-addon-web-links';
import 'xterm/css/xterm.css'
(function() {
var terminal = new Terminal({
screenKeys: true,
useStyle: true,
cursorBlink: true,
fullscreenWin: true,
maximizeWin: true,
screenReaderMode: true,
cols: 128,
});
terminal.open(document.getElementById("terminal"));
var protocol = (location.protocol === "https:") ? "wss://" : "ws://";
var url = protocol + location.host + "/ws"
var ws = new WebSocket(url);
var attachAddon = new AttachAddon.AttachAddon(ws);
var fitAddon = new FitAddon.FitAddon();
terminal.loadAddon(fitAddon);
var webLinksAddon = new WebLinksAddon.WebLinksAddon();
terminal.loadAddon(webLinksAddon);
var unicode11Addon = new Unicode11Addon.Unicode11Addon();
terminal.loadAddon(unicode11Addon);
var serializeAddon = new SerializeAddon.SerializeAddon();
terminal.loadAddon(serializeAddon);
ws.onclose = function(event) {
console.log(event);
terminal.write('\r\n\nconnection has been terminated from the server-side (hit refresh to restart)\n')
};
ws.onopen = function() {
terminal.loadAddon(attachAddon);
terminal._initialized = true;
terminal.focus();
setTimeout(function() {fitAddon.fit()});
terminal.onResize(function(event) {
var rows = event.rows;
var cols = event.cols;
var size = JSON.stringify({cols: cols, rows: rows + 1});
var send = new TextEncoder().encode("\x01" + size);
console.log('resizing to', size);
ws.send(send);
});
terminal.onTitleChange(function(event) {
console.log(event);
});
window.onresize = function() {
fitAddon.fit();
};
};
})();

17
package.json Normal file
View File

@ -0,0 +1,17 @@
{
"name": "gterm",
"version": "1.0.0",
"main": "index.js",
"repository": "git@kumoly.io:tools/gterm.git",
"author": "Evan Chen <evanchen@kumoly.io>",
"license": "MIT",
"dependencies": {
"parcel": "^2.0.1",
"xterm": "^4.15.0",
"xterm-addon-attach": "^0.6.0",
"xterm-addon-fit": "^0.5.0",
"xterm-addon-serialize": "^0.6.1",
"xterm-addon-unicode11": "^0.3.0",
"xterm-addon-web-links": "^0.4.0"
}
}

View File

@ -1 +0,0 @@
.xterm{position:relative;-moz-user-select:none;user-select:none;-ms-user-select:none;-webkit-user-select:none}.xterm.focus,.xterm:focus{outline:none}.xterm .xterm-helpers{position:absolute;top:0;z-index:5}.xterm .xterm-helper-textarea{padding:0;border:0;margin:0;position:absolute;opacity:0;left:-9999em;top:0;width:0;height:0;z-index:-5;white-space:nowrap;overflow:hidden;resize:none}.xterm .composition-view{background:#000;color:#fff;display:none;position:absolute;white-space:nowrap;z-index:1}.xterm .composition-view.active{display:block}.xterm .xterm-viewport{background-color:#000;overflow-y:scroll;cursor:default;position:absolute;right:0;left:0;top:0;bottom:0}.xterm .xterm-screen{position:relative}.xterm .xterm-screen canvas{position:absolute;left:0;top:0}.xterm .xterm-scroll-area{visibility:hidden}.xterm-char-measure-element{display:inline-block;visibility:hidden;position:absolute;top:0;left:-9999em;line-height:normal}.xterm{cursor:text}.xterm.enable-mouse-events{cursor:default}.xterm.xterm-cursor-pointer{cursor:pointer}.xterm.column-select.focus{cursor:crosshair}.xterm .xterm-accessibility,.xterm .xterm-message{position:absolute;left:0;top:0;bottom:0;right:0;z-index:10;color:transparent}.xterm .live-region{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}.xterm-dim{opacity:.5}.xterm-underline{text-decoration:underline}.xterm-strikethrough{text-decoration:line-through}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -1,2 +0,0 @@
(function(e){function t(t){for(var r,c,a=t[0],i=t[1],p=t[2],l=0,s=[];l<a.length;l++)c=a[l],Object.prototype.hasOwnProperty.call(o,c)&&o[c]&&s.push(o[c][0]),o[c]=0;for(r in i)Object.prototype.hasOwnProperty.call(i,r)&&(e[r]=i[r]);f&&f(t);while(s.length)s.shift()();return u.push.apply(u,p||[]),n()}function n(){for(var e,t=0;t<u.length;t++){for(var n=u[t],r=!0,a=1;a<n.length;a++){var i=n[a];0!==o[i]&&(r=!1)}r&&(u.splice(t--,1),e=c(c.s=n[0]))}return e}var r={},o={app:0},u=[];function c(t){if(r[t])return r[t].exports;var n=r[t]={i:t,l:!1,exports:{}};return e[t].call(n.exports,n,n.exports,c),n.l=!0,n.exports}c.m=e,c.c=r,c.d=function(e,t,n){c.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},c.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},c.t=function(e,t){if(1&t&&(e=c(e)),8&t)return e;if(4&t&&"object"===typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(c.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)c.d(n,r,function(t){return e[t]}.bind(null,r));return n},c.n=function(e){var t=e&&e.__esModule?function(){return e["default"]}:function(){return e};return c.d(t,"a",t),t},c.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},c.p="";var a=window["webpackJsonp"]=window["webpackJsonp"]||[],i=a.push.bind(a);a.push=t,a=a.slice();for(var p=0;p<a.length;p++)t(a[p]);var f=i;u.push([0,"chunk-vendors"]),n()})({0:function(e,t,n){e.exports=n("56d7")},"0ae4":function(e,t,n){},"56d7":function(e,t,n){"use strict";n.r(t);n("e260"),n("e6cf"),n("cca6"),n("a79d");var r=n("7a23");function o(e,t,n,o,u,c){var a=Object(r["e"])("XTerm");return Object(r["d"])(),Object(r["b"])(a)}function u(e,t,n,o,u,c){return Object(r["d"])(),Object(r["c"])("h1",null,"TEST")}var c={},a=(n("9a49"),n("6b0d")),i=n.n(a);const p=i()(c,[["render",u]]);var f=p,l={name:"App",components:{XTerm:f}};const s=i()(l,[["render",o]]);var d=s;Object(r["a"])(d).mount("#app")},"9a49":function(e,t,n){"use strict";n("0ae4")}});
//# sourceMappingURL=app.5bd01a68.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,3 +0,0 @@
> 1%
last 2 versions
not dead

View File

@ -1,17 +0,0 @@
module.exports = {
root: true,
env: {
node: true
},
'extends': [
'plugin:vue/vue3-essential',
'eslint:recommended'
],
parserOptions: {
parser: 'babel-eslint'
},
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
}
}

23
src/.gitignore vendored
View File

@ -1,23 +0,0 @@
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@ -1,24 +0,0 @@
# src
## Project setup
```
yarn install
```
### Compiles and hot-reloads for development
```
yarn serve
```
### Compiles and minifies for production
```
yarn build
```
### Lints and fixes files
```
yarn lint
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).

View File

@ -1,5 +0,0 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}

View File

@ -1,29 +0,0 @@
{
"name": "src",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"core-js": "^3.6.5",
"vue": "^3.0.0",
"xterm": "^4.15.0",
"xterm-addon-fit": "^0.5.0",
"xterm-addon-search": "^0.8.1"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"@vue/compiler-sfc": "^3.0.0",
"babel-eslint": "^10.1.0",
"eslint": "^6.7.2",
"eslint-plugin-vue": "^7.0.0",
"sass": "^1.26.5",
"sass-loader": "^8.0.2",
"vue-cli-plugin-pug": "~2.0.0"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -1,6 +0,0 @@
package public
import "embed"
//go:embed css js favicon.ico
var FS embed.FS

View File

@ -1,17 +0,0 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>{{.AppName}}</title>
</head>
<body>
<noscript>
<strong>We're sorry but {{.AppName}} doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

View File

@ -1,17 +0,0 @@
<template lang="pug">
XTerm
</template>
<script>
import XTerm from './components/XTerm.vue'
export default {
name: 'App',
components: {
XTerm
}
}
</script>
<style lang="scss">
</style>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

View File

@ -1,46 +0,0 @@
<template lang="pug">
.console#terminal
</template>
<script>
import {Terminal} from 'xterm'
import {FitAddon} from 'xterm-addon-fit'
export default {
data () {
return {
term: null,
terminalSocket: null,
fitaddon: null
}
},
mounted(){
console.log("mounted")
let terminalContainer = document.getElementById('terminal')
this.term = new Terminal()
this.fitaddon = new FitAddon()
this.term.loadAddon(this.fitaddon)
this.term.open(terminalContainer)
this.fitaddon.fit()
this.term.write('Hello from \x1B[1;3;31mxterm.js\x1B[0m $ ')
console.log(this.term)
},
Unmounted () {
this.term.dispose()
},
methods:{
}
}
</script>
<style lang="scss">
@import '~xterm/css/xterm.css';
.console {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
</style>

View File

@ -1,4 +0,0 @@
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')

View File

@ -1,8 +0,0 @@
module.exports = {
publicPath: "",
outputDir: "../public",
indexPath: "../index.html",
devServer: {
proxy: 'http://localhost:8000'
}
}

File diff suppressed because it is too large Load Diff

4561
yarn.lock Normal file

File diff suppressed because it is too large Load Diff