diff --git a/cmpSnipper/cmpSnipper.go b/cmpSnipper/cmpSnipper.go index 4d46b2f..3b236ad 100644 --- a/cmpSnipper/cmpSnipper.go +++ b/cmpSnipper/cmpSnipper.go @@ -18,12 +18,15 @@ HttpCliDebug uint32 HttpCliSslVerify bool + CliCur *cmpWbsCliStruct CliList []cmpWbsCliStruct cmpSnipperHttpServ cmpSnipperHttpServ Rand4d int + Ping2List []Ping2Struct + PlugList []cmpPluginStruct SshList []cmpSshCliStruct diff --git a/cmpSnipper/cmpSnipperHttpWsHandle.go b/cmpSnipper/cmpSnipperHttpWsHandle.go index 73098f5..6fce4fe 100644 --- a/cmpSnipper/cmpSnipperHttpWsHandle.go +++ b/cmpSnipper/cmpSnipperHttpWsHandle.go @@ -3,6 +3,7 @@ import ( "time" + "sync" "net/http" "github.com/gorilla/websocket" @@ -11,6 +12,7 @@ type cmpWbsCliStruct struct { W http.ResponseWriter Connect *websocket.Conn + Mutex sync.Mutex HostPort string } @@ -44,9 +46,12 @@ app.Log("New client: %s", HostPort) + var Mutex sync.Mutex + var cmpWbsCli cmpWbsCliStruct = cmpWbsCliStruct{ w, conn, + Mutex, HostPort, } @@ -107,14 +112,16 @@ } } else { out["error"] = "Unknown snip" - app.Log("Unknow snip:", inp["snip"]) + app.Log("Unknow snip: %s", inp["snip"]) } - - err = conn.WriteJSON(out) +/* + err = conn.Write-JSON(out) if err != nil { app.Log(err) } +*/ + app.HttpWebSockSendRawTo(cmpWbsCli, out) // for } @@ -122,21 +129,34 @@ return } - func (app *App) HttpWebSockSendTo(cmpWbsCli cmpWbsCliStruct, snip string, result map[string]interface{}) { + func (app *App) HttpWebSockSendRawTo(cmpWbsCli cmpWbsCliStruct, raw map[string]interface{}) { var err error - out := map[string]interface{}{ - "snip": snip, - "result": result, - } + app.Log("LOCK") - err = cmpWbsCli.Connect.WriteJSON(out) + cmpWbsCli.Mutex.Lock() + + defer cmpWbsCli.Mutex.Unlock() + + err = cmpWbsCli.Connect.WriteJSON(raw) if err != nil { app.Log(err) return } + return + + } + + func (app *App) HttpWebSockSendTo(cmpWbsCli cmpWbsCliStruct, snip string, result map[string]interface{}) { + out := map[string]interface{}{ + "snip": snip, + "result": result, + } + + app.HttpWebSockSendRawTo(cmpWbsCli, out) + return } diff --git a/cmpSnipper/cmpSnipperSnip.go b/cmpSnipper/cmpSnipperSnip.go index 76ac646..8c2a730 100644 --- a/cmpSnipper/cmpSnipperSnip.go +++ b/cmpSnipper/cmpSnipperSnip.go @@ -39,8 +39,12 @@ reflect.ValueOf(ifcIM), } + app.CliCur = &cmpWbsCli + refOL := refMth.Call(refIL) + // app.CliCur = nil ??? + infOI := refOL[0].Interface() app.Log("Interface output item %+v", infOI) diff --git a/cmpSnipper/cmpSnipperSnipLeftMenu.go b/cmpSnipper/cmpSnipperSnipLeftMenu.go index e872313..7d08e4c 100644 --- a/cmpSnipper/cmpSnipperSnipLeftMenu.go +++ b/cmpSnipper/cmpSnipperSnipLeftMenu.go @@ -11,12 +11,14 @@ mainMenuFile, err := app.ConfGetStr("mainMenuFile") if err != nil { + app.Log("Can't get config.mainMenuFile") return nil, err } data, err := app.FileReadLimit(mainMenuFile, 1048576) if err != nil { + app.Log("Can't read file '%s'", mainMenuFile) return nil, err } diff --git a/cmpSnipper/cmpSnipperSnipPing.go b/cmpSnipper/cmpSnipperSnipPing.go index 7393a80..a334930 100644 --- a/cmpSnipper/cmpSnipperSnipPing.go +++ b/cmpSnipper/cmpSnipperSnipPing.go @@ -2,6 +2,9 @@ package cmpSnipper import ( + "io" + "sync" + "strconv" "os/exec" "context" "time" @@ -9,6 +12,12 @@ "syscall" ) + type Ping2Struct struct { + Addr string + Win string + Pid int + } + func (app *App) PingSnip(parm any) (any, error) { // log.Println("PingSnip") @@ -68,3 +77,357 @@ "stderr" : stderr.String(), }, nil } + + +func (app *App) copyAndCapture(r io.Reader, strWin string) ([]byte, error) { + var out []byte + buf := make([]byte, 1024, 1024) + for { + n, err := r.Read(buf[:]) + if n > 0 { + d := buf[:n] + out = append(out, d...) + str := strings.Trim(string(d), "\n\r") + + app.Log("Std: %s", str) + + app.Ping2SendStr(str) + } + if err != nil { + // Read returns io.EOF at the end of file, which is not an error for us + if err == io.EOF { + err = nil + } + return out, err + } + } +} + + func (app *App) Ping2SendObj(obj map[string]interface{}) (error) { + if app.CliCur == nil { + app.Log("No cli") + return nil + } + + app.HttpWebSockSendTo(*app.CliCur, "ping2Data", obj) + + return nil + } + + func (app *App) Ping2SendStr(str string) (error) { + return app.Ping2SendObj(map[string]interface{}{ + "data" : "stdout", + "string": str, + }) + } + + + + +func (app *App) ping2Capture(r io.Reader, strWin string) ([]byte, error) { + var out []byte + buf := make([]byte, 1024, 1024) + for { + n, err := r.Read(buf[:]) + if n > 0 { + d := buf[:n] + out = append(out, d...) + str := strings.Trim(string(d), "\n\r") + + app.Log("Std: %s", str) + + app.Ping2SendStr(str) + } + if err != nil { + // Read returns io.EOF at the end of file, which is not an error for us + if err == io.EOF { + err = nil + } + return out, err + } + } +} + + + func (app *App) Ping2ExecStart(strAddr string, strWin string) (any, error) { + var Ping2Free *Ping2Struct = nil + + for key, val := range app.Ping2List { + if val.Pid == 0 { + Ping2Free = &val + break + } + + if val.Addr == strAddr { + app.Ping2List[key].Win = strWin + + return map[string]interface{}{ + "stutus" : "success", + }, nil + } + } + + if Ping2Free == nil { + var NewPing2Struct Ping2Struct + Ping2Free = &NewPing2Struct + app.Ping2List = append(app.Ping2List, NewPing2Struct) + } + + var err error + + ctx, cancel := context.WithTimeout(context.Background(), 100 * time.Second) + + defer cancel() + + cmd := exec.CommandContext(ctx, "ping", "-c", "40", strAddr) + + var stdout, stderr []byte + stdoutIn, _ := cmd.StdoutPipe() + stderrIn, _ := cmd.StderrPipe() + + var exitCode int; + + err = cmd.Start() + + if err != nil { + app.Log(err) + return nil, err + } + + Ping2Free.Addr = strAddr + Ping2Free.Win = strWin + Ping2Free.Pid = cmd.Process.Pid + + go app.Ping2SendObj(map[string]interface{}{ + "data" : "procid", + "procid": Ping2Free.Pid, + }) + + var wg sync.WaitGroup + wg.Add(1) + go func() { + stdout, err = app.ping2Capture(stdoutIn, strWin) + wg.Done() + }() + + stderr, err = app.ping2Capture(stderrIn, strWin) + + wg.Wait() + + err = cmd.Wait() + + if err != nil { + // try to get the exit code + if exitError, ok := err.(*exec.ExitError); ok { + ws := exitError.Sys().(syscall.WaitStatus) + exitCode = ws.ExitStatus() + } else { + // This will happen (in OSX) if `name` is not available in $PATH, + // in this situation, exit code could not be get, and stderr will be + // empty string very likely, so we use the default fail code, and format err + // to string and set to stderr + + app.Log("Could not get exit code for failed program") + exitCode = 255 + } + } else { + // success, exitCode should be 0 if go is ok + ws := cmd.ProcessState.Sys().(syscall.WaitStatus) + exitCode = ws.ExitStatus() + } + + app.Log("Exit code %s", exitCode) + + if false { /* ??? */ + outStr, errStr := string(stdout), string(stderr) + app.Log("\nout:\n%s\nerr:\n%s\n", outStr, errStr) + } + + Ping2Free.Pid = 0 + + return map[string]interface{}{ + "stutus" : "error", + "error" : "Invalid IPv4 address", + }, nil + } + + func (app *App) Ping2ExecSign(strAddr string, strWin string, strSign string) (any, error) { + for _, val := range app.Ping2List { + if val.Pid == 0 { + continue + } + + if val.Addr != strAddr { + continue + } + + + var err error + + ctx, cancel := context.WithTimeout(context.Background(), 100 * time.Second) + + defer cancel() + + pidStr := strconv.Itoa(val.Pid) + + app.Log("kill %s", pidStr) + + cmd := exec.CommandContext(ctx, "kill", strSign, pidStr) + + var exitCode int; + + err = cmd.Run() + + if err != nil { + // try to get the exit code + if exitError, ok := err.(*exec.ExitError); ok { + ws := exitError.Sys().(syscall.WaitStatus) + exitCode = ws.ExitStatus() + } else { + // This will happen (in OSX) if `name` is not available in $PATH, + // in this situation, exit code could not be get, and stderr will be + // empty string very likely, so we use the default fail code, and format err + // to string and set to stderr + + app.Log("Could not get exit code for failed program") + exitCode = 255 + } + } else { + // success, exitCode should be 0 if go is ok + ws := cmd.ProcessState.Sys().(syscall.WaitStatus) + exitCode = ws.ExitStatus() + } + + app.Log("Exit code %s", exitCode) + + break + } + + + return map[string]interface{}{ + "stutus" : "success", + }, nil + } + + func (app *App) Ping2ExecStop(strAddr string, strWin string) (any, error) { + return app.Ping2ExecSign(strAddr, strWin, "-SIGINT") + } + + func (app *App) Ping2ExecStat(strAddr string, strWin string) (any, error) { + return app.Ping2ExecSign(strAddr, strWin, "-SIGQUIT") + } + + + func (app *App) Ping2ExecSnip(parm any) (any, error) { + var err error + var ok bool + + var ifcAddr interface{} + var ifcCmd interface{} + var ifcWin interface{} + + ifcAddr, err = app.IfcGet(parm, "InputArgument", "addr") + + if err != nil { + return map[string]interface{}{ + "stutus" : "error", + "error" : "Unset IPv4 address", + }, nil + } + + ifcCmd, err = app.IfcGet(parm, "InputArgument", "cmd") + + if err != nil { + return map[string]interface{}{ + "stutus" : "error", + "error" : "Unset command", + }, nil + } + + ifcWin, err = app.IfcGet(parm, "InputArgument", "win") + + if err != nil { + return map[string]interface{}{ + "stutus" : "error", + "error" : "Unset window", + }, nil + } + + var strAddr string + var strCmd string + var strWin string + + strAddr, ok = ifcAddr.(string) + + if !ok { + return map[string]interface{}{ + "stutus" : "error", + "error" : "Invalid IPv4 address type", + }, nil + } + + strCmd, ok = ifcCmd.(string) + + if !ok { + return map[string]interface{}{ + "stutus" : "error", + "error" : "Invalid command type", + }, nil + } + + strWin, ok = ifcWin.(string) + + if !ok { + intWin, ok := ifcWin.(float64) + + if !ok { + return map[string]interface{}{ + "stutus" : "error", + "error" : "Invalid window type", + }, nil + } + + strWin = strconv.FormatFloat(intWin, 'f', -1, 32) + } + + + switch(strAddr) { + case "1.1.1.1": + break; + case "8.8.8.8": + break; + case "8.8.4.4": + break; + case "9.9.9.9": + break; + + default: + return map[string]interface{}{ + "stutus" : "error", + "error" : "Invalid IPv4 address", + }, nil + } + + switch(strCmd) { + case "start": + go app.Ping2ExecStart(strAddr, strWin) + + return map[string]interface{}{ + "status" : "success", + }, nil + + case "stop": + return app.Ping2ExecStop(strAddr, strWin) + + case "stat": + return app.Ping2ExecStat(strAddr, strWin) + + default: + return map[string]interface{}{ + "stutus" : "error", + "error" : "Invalid command", + }, nil + } + + return nil, nil + } diff --git a/go.mod b/go.mod index 13c65e0..82b9067 100644 --- a/go.mod +++ b/go.mod @@ -5,8 +5,8 @@ go 1.20 require ( github.com/andybalholm/brotli v1.1.0 github.com/gorilla/websocket v1.5.1 - golang.org/x/crypto v0.14.0 - golang.org/x/net v0.17.0 + golang.org/x/crypto v0.23.0 + golang.org/x/net v0.25.0 ) -require golang.org/x/sys v0.13.0 // indirect +require golang.org/x/sys v0.20.0 // indirect diff --git a/go.sum b/go.sum index 1a1142e..bb0add0 100644 --- a/go.sum +++ b/go.sum @@ -4,7 +4,13 @@ github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/ github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/static/default.js b/static/default.js index 6ddc1c3..be6bb44 100644 --- a/static/default.js +++ b/static/default.js @@ -76,9 +76,7 @@ contentReq(page, parm) { console.log("contentReq", page); - if(page == "trmShow") { - this.trmShow(); - } + (this[page])(); } @@ -276,6 +274,204 @@ // console.log(window.location) } + //========================== + + ping2AddrSet(w, addr) { + this.blobPing2Wind[w].nodeAddr.innerHTML = addr; + this.blobPing2Wind[w].nodeAddrList.style.visibility = "hidden"; + } + + ping2Addr(w) { + if(!this.blobPing2Wind[w].nodeAddrList) { + this.blobPing2Wind[w].nodeAddrList = dcrt({ + tag : "div", + style: "display:block; visibility:hidden; position:absolute; top:32px; left:110px; background-color:#222; overflow:auto; border:1px solid grey; padding:15px;", + parn : this.blobPing2Wind[w].nodeRoot + }); + + for(var i = 0; i < this.listPing2Addr.length; i++) { + dcrt({ + tag : "div", + parn : this.blobPing2Wind[w].nodeAddrList, + click: this.ping2AddrSet.bind(this, w, this.listPing2Addr[i]), + html : this.listPing2Addr[i] + }); + } + } + + if(this.blobPing2Wind[w].nodeAddrList.style.visibility == "hidden") { + this.blobPing2Wind[w].nodeAddrList.style.visibility = "visible"; + } + else { + this.blobPing2Wind[w].nodeAddrList.style.visibility = "hidden"; + } + } + + ping2Snip(w, cmd) { + if(!cmd) { + return; + } + + var obj = { + win : w + "", + cmd : cmd, + addr : this.blobPing2Wind[w].nodeAddr.innerHTML + }; + + return this.reqSnip("ping2Exec", obj); + } + + ping2Blob(w, sty) { + this.blobPing2Wind[w] = { + list : [] + }; + + this.blobPing2Wind[w].nodeRoot = dcrt({ + tag : "div", + style: sty, + parn : this.nodePing2Body + }); + + this.blobPing2Wind[w].nodeAddr = dcrt({ + tag : "div", + style: "display:block; position:absolute; top:-2px; left:125px;", + click: this.ping2Addr.bind(this, w), + html : "8.8.8.8" + }); + + this.blobPing2Wind[w].nodeHead = dcrt({ + tag : "div", + style: "display:block; position:absolute; top:1px; height:32px; left:1px; right:1px; overflow:auto; border:0px solid red; padding:0;", + parn : this.blobPing2Wind[w].nodeRoot, + child: [ + { + tag : "div", + style: "display:block; position:absolute; top:0px; left:1px;", + click: this.ping2Snip.bind(this, w, "start"), + html : '' + }, + { + tag : "div", + style: "display:block; position:absolute; top:0px; left:25px;", + click: this.ping2Snip.bind(this, w, "stat"), + html : '' + }, + { + tag : "div", + style: "display:block; position:absolute; top:0px; left:50px;", + click: this.ping2Snip.bind(this, w, "stop"), + html : '' + }, + { + tag : "div", + style: "display:block; position:absolute; top:0px; left:100px;", + click: this.ping2Addr.bind(this, w), + html : '' + }, + this.blobPing2Wind[w].nodeAddr + ] + }); + + this.blobPing2Wind[w].nodeBody = dcrt({ + tag : "div", + style: "display:block; position:absolute; top:26px; bottom:1px; left:1px; right:1px; overflow:auto; border:1px solid grey;", + parn : this.blobPing2Wind[w].nodeRoot + }); + + } + + ping2StartReq() { + this.nodeDataHeadRoot.innerHTML = ""; + this.nodeDataBodyRoot.innerHTML = ""; + + if(!this.nodePing2Head) { + this.listPing2Addr = [ + "1.1.1.1", + "8.8.8.8", + "8.8.4.4", + "9.9.9.9" + ]; + + this.blobPing2Wind = []; + + this.nodePing2Head = dcrt({ + tag : "h4", + parn : this.nodeDataHeadRoot, + style: "margin:4px;", + html : "Ping2 Start request" + }); + + this.nodePing2Body = dcrt({ + tag : "div", + style: "display:block; position:relative; width:100%; height:100%; overflow:auto;", + parn : this.nodeDataBodyRoot + }); + + this.ping2Blob(0, "display:block; position:absolute; top:1px; bottom:1px; left:1px; right:calc(50% - 2px); overflow:auto; border:1px solid grey;"); + + this.ping2Blob(1, "display:block; position:absolute; top:1px; bottom:calc(50% - 2px); left:calc(50% + 3px); right:0; overflow:auto; border:1px solid grey;"); + + this.ping2Blob(2, "display:block; position:absolute; top:calc(50% - 1px); bottom:1px; left:calc(50% + 3px); right:0; overflow:auto; border:1px solid grey;"); + + this.nodePing2BodyList = []; + } + + this.nodeDataHeadRoot.appendChild(this.nodePing2Head); + this.nodeDataBodyRoot.appendChild(this.nodePing2Body); + + this.reqSnip("ping2Exec", {win: 0, cmd: "start", addr: "1.1.1.1"}); + this.reqSnip("ping2Exec", {win: 1, cmd: "start", addr: "8.8.8.8"}); + this.reqSnip("ping2Exec", {win: 2, cmd: "start", addr: "9.9.9.9"}); + + return; + } + + ping2StartRcv(obj) { + // console.log(obj); + this.nodePing2Head.innerHTML = "Ping2 starting"; + } + + + ping2StopReq() { + return this.reqSnip("ping2Stop"); + } + + ping2StopRcv(obj) { + // console.log(obj); + } + + ping2StatReq() { + return this.reqSnip("ping2Stat"); + } + + ping2StatRcv(obj) { + // console.log(obj); + } + + ping2DataRcv(obj) { + if(obj.data == "procid") { + this.nodePing2Head.innerHTML = "Ping2 started " + obj.procid; + return; + } + + var w = 0; + + this.blobPing2Wind[w].list.push(dcrt({ + tag : "pre", + style: "margin:0;", + parn : this.blobPing2Wind[w].nodeBody, + html : obj.string + })); + + this.blobPing2Wind[w].nodeBody.scrollTop = this.blobPing2Wind[w].nodeBody.scrollTopMax; + + if(this.blobPing2Wind[w].list.length > 100) { + var node = this.blobPing2Wind[w].list.shift(); + if(node) + node.remove(); + } + } + // END class }