update
parent
12084a659c
commit
d379ffff9b
|
@ -1 +1,2 @@
|
|||
node_modules
|
||||
.parcel-cache
|
|
@ -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
3
go.mod
|
@ -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
2
go.sum
|
@ -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
276
gterm.go
|
@ -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 := >erm{}
|
||||
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 >erm{
|
||||
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"`
|
||||
}
|
||||
|
|
48
index.html
48
index.html
|
@ -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>
|
|
@ -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();
|
||||
};
|
||||
};
|
||||
})();
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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 |
|
@ -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
|
@ -1,3 +0,0 @@
|
|||
> 1%
|
||||
last 2 versions
|
||||
not dead
|
|
@ -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'
|
||||
}
|
||||
}
|
|
@ -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?
|
|
@ -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/).
|
|
@ -1,5 +0,0 @@
|
|||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
]
|
||||
}
|
|
@ -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 |
|
@ -1,6 +0,0 @@
|
|||
package public
|
||||
|
||||
import "embed"
|
||||
|
||||
//go:embed css js favicon.ico
|
||||
var FS embed.FS
|
|
@ -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>
|
|
@ -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 |
|
@ -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>
|
|
@ -1,4 +0,0 @@
|
|||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
|
||||
createApp(App).mount('#app')
|
|
@ -1,8 +0,0 @@
|
|||
module.exports = {
|
||||
publicPath: "",
|
||||
outputDir: "../public",
|
||||
indexPath: "../index.html",
|
||||
devServer: {
|
||||
proxy: 'http://localhost:8000'
|
||||
}
|
||||
}
|
8951
src/yarn.lock
8951
src/yarn.lock
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue