commit d03c481b335034206824a140894d456f423d3252 Author: root Date: Fri Jan 26 07:11:35 2024 +1200 Initial 2024-01-26 diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..2a884c7 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,5 @@ + + [*] + end_of_line = lf + indent_style = tab + tab_width = 4 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9e95b2f --- /dev/null +++ b/.gitignore @@ -0,0 +1,29 @@ +**/*_ +**/.#* +**/0* +**/1* +**/2* +**/3* +**/4* +**/5* +**/6* +**/7* +**/8* +**/9* + +**/*-test.go + +**/*.so +**/*.log* +**/*log +**/LOG* + +**/main +**/main2 + +static2/** +cache/** +data/** +old/** +outer/** +secret/** diff --git a/cmpSnipper/cmpSnipper.go b/cmpSnipper/cmpSnipper.go new file mode 100644 index 0000000..16d67f7 --- /dev/null +++ b/cmpSnipper/cmpSnipper.go @@ -0,0 +1,122 @@ + + package cmpSnipper + + import ( + "errors" + "plugin" + ) + + type cmpPluginStruct struct { + Name string + Plug *plugin.Plugin + } + + + type App struct { + Conf interface{} + ErrIsDir error // = errors.New("Is directory") + + CliList []cmpWbsCliStruct + + cmpSnipperHttpServ cmpSnipperHttpServ + + Rand4d int + + PlugList []cmpPluginStruct + + SshList []cmpSshCliStruct + } + + func (app *App) PlugInit(name string, file string) { + qqq := map[string]interface{}{ + "Aaa": "Bbb", + } + + var plug cmpPluginStruct + + var p *plugin.Plugin + + p, err := plugin.Open(file) + + if err != nil { + app.Log(err) + return + } + + plug.Name = name + plug.Plug = p + + plugInitReq, err := plug.Plug.Lookup("CmpPlug1Init") + + if err != nil { + app.Log(err) + return + } + + plugInitFnc := plugInitReq.(func(int, func(any, ...interface{}), map[string]interface{}) interface{}) + + plugInitRes := plugInitFnc(33, app.Log, qqq) + + app.Log("%+v", plugInitRes) + + app.PlugList = append(app.PlugList, plug) + } + + func (app *App) PlugCall(pnam string, fnam string) { + for _, plug := range app.PlugList { + if plug.Name != pnam { + continue + } + + plugFuncReq, err := plug.Plug.Lookup(fnam) + + if err != nil { + app.Log(err) + return + } + + plugFuncFnc := plugFuncReq.(func() error) + + plugFuncRes := plugFuncFnc() + + app.Log("%+v", plugFuncRes) + } + } + + func (app *App) Prep() (error) { + app.ErrIsDir = errors.New("Is directory") + + app.LogSetup() + + var err = app.ConfLoadFile("secret/zest.json"); + + if err != nil { + return err + } + + return nil + } + + func (app *App) Proc() error { + var err error // = make(chan error, 1) + + var term = make(chan bool, 1) + + go app.WaitSignal(term) + + err = app.HttpServStart() + + if err != nil { + return err + } + + <-term + + app.HttpServStop() + + app.Log("Terminating") + + return nil + } + + // end diff --git a/cmpSnipper/cmpSnipperConf.go b/cmpSnipper/cmpSnipperConf.go new file mode 100644 index 0000000..b89f3ca --- /dev/null +++ b/cmpSnipper/cmpSnipperConf.go @@ -0,0 +1,103 @@ + + package cmpSnipper + + import ( + "fmt" + "errors" + "strconv" + "encoding/json" + ) + + func (app *App) IfcGet(ifc interface{}, arr ...string) (interface{}, error) { + var cnt = len(arr); + var idx string + + for j := 0; j < cnt; j++ { + idx = arr[j] + + switch v := ifc.(type) { + case string: + app.Log("Convert string to ifc") + ifc = ifc.(interface{}) + + case map[string]interface{}: + // app.Log("Valid ifc " + idx) + ifc = v[idx]; + + default: + app.Log("Invalid type variable %s %T", idx, ifc) + return nil, errors.New("Invalid type variable"); + } + } + + if ifc == nil { + return nil, errors.New("Undefine variable " + idx); + } + + // app.Log("Found ifc %s %T", idx, ifc) + + return ifc, nil; + } + + func (app *App) ConfGetIfc(arr ...string) (interface{}, error) { + return app.IfcGet(app.Conf, arr...); + } + + func (app *App) ConfGetStr(arr ...string) (string, error) { + ifc, err := app.ConfGetIfc(arr...); + + if err != nil { + return "", err; + } + + switch ifc.(type) { + case string: + return ifc.(string), nil; + case float64: + f := ifc.(float64) + u := uint64(f) + return strconv.FormatUint(u, 10), nil; + default: + return "", errors.New("Invalid type value !s"); + } + + return "", errors.New("Invalid type value"); + } + + func (app *App) ConfGetArr(arr ...string) ([]interface{}, error) { + ifc, err := app.ConfGetIfc(arr...); + + if err != nil { + return nil, err; + } + + switch v := ifc.(type) { + case []interface{}: + return v, nil + default: + fmt.Printf("Invalid type value %+q\n", ifc) + return nil, errors.New("Invalid type value !s"); + } + + return nil, errors.New("Invalid type value"); + } + + func (app *App) ConfLoadFile(file string) error { + data, err := app.FileReadLimit(file, 1048576); + + if err != nil { + app.Log(err) + return err + } + + // fmt.Println(string(data)); + + err = json.Unmarshal(data, &app.Conf) + + if err != nil { + app.Log(err) + return err + } + + return nil; + } diff --git a/cmpSnipper/cmpSnipperHttpServ.go b/cmpSnipper/cmpSnipperHttpServ.go new file mode 100644 index 0000000..0b0e6d7 --- /dev/null +++ b/cmpSnipper/cmpSnipperHttpServ.go @@ -0,0 +1,84 @@ + + package cmpSnipper + + import ( + "net" + "net/http" + ) + + type tcpKeepAliveListener struct { + *net.TCPListener + } + + type cmpSnipperHttpServ struct { + HttpListener net.Listener + } + + func (app *App) HttpServStart() error { + var err error; + + // var subjHttpPortString string = "7070"; + // var subjHttpPortUint16 uint16 = subjConf.Http.Port; + + host, err := app.ConfGetStr("http", "host") + + // fmt.Printf("Success load : %+v\n", host) + + if err != nil { + app.Log(err) + return err + } + + port, err := app.ConfGetStr("http", "port") + + // fmt.Printf("Success load : %+v\n", port) + + if err != nil { + app.Log(err) + return err + } + + // if subjHttpPortUint16 == 0 || subjHttpPortString != strconv.FormatUint(uint64(subjHttpPortUint16), 10) { + // fmt.Printf("Invalid http.port %s != %d\n", subjHttpPortString, subjHttpPortUint16) + // app.Log(".Fatal"); + // } + + + httpFileServer := http.FileServer(http.Dir("static")) + http.Handle("/go-prefix/static/", http.StripPrefix("/go-prefix/static/", httpFileServer)) + + http.HandleFunc("/" , app.HttpReq); + http.HandleFunc("/go-prefix/ws" , app.HttpWebSockHand) + + var addr = host + ":" + port + + server := &http.Server{Addr: addr} + + app.Log("Listenning: '%s'", port); + + app.cmpSnipperHttpServ.HttpListener, err = net.Listen("tcp", addr) + + // defer app.HttpServClose() + + if err != nil { + app.Log(err) + return err + } + + go func() { + err = server.Serve(tcpKeepAliveListener{app.cmpSnipperHttpServ.HttpListener.(*net.TCPListener)}) + + if err == nil { + app.Log(err) + return // err + } + }() + + return nil + } + + func (app *App) HttpServStop() { + app.cmpSnipperHttpServ.HttpListener.Close() + + app.Log("Server Closed") + } diff --git a/cmpSnipper/cmpSnipperHttpUtil.go b/cmpSnipper/cmpSnipperHttpUtil.go new file mode 100644 index 0000000..a628f02 --- /dev/null +++ b/cmpSnipper/cmpSnipperHttpUtil.go @@ -0,0 +1,245 @@ + + package cmpSnipper + + import ( + "os" + "strings" + "encoding/json" + "net/http" + ) + + func (app *App) HttpRespText(w http.ResponseWriter, data []byte) { + w.Write(data) + + return + } + + func (app *App) HttpRespJson(w http.ResponseWriter, data any) { + w.Header().Set("Content-Type", "application/json") + + jsonResp, err := json.MarshalIndent(data, "", "\t") + + if err != nil { + app.Log("Error happened in JSON marshal. Err: %s", err) + return + } + + app.HttpRespText(w, jsonResp) + } + + func HttpRespCodeText(w http.ResponseWriter, req *http.Request, code int, cont string, mesg any) { + w.WriteHeader(code) + + w.Header().Set("Content-Type", cont) + + + } + + func (app *App) HttpRespForbidden(w http.ResponseWriter, req *http.Request, mesg string) { + w.WriteHeader(http.StatusForbidden) + + resp := make(map[string]any) + resp["code"] = 403 + resp["status"] = "Forbidden" + resp["message"] = mesg + + app.HttpRespJson(w, resp) + } + + + + func (app *App) HttpReqDir1(w http.ResponseWriter, req *http.Request, targ string, path string) { + resp := map[string]interface{}{ + "path" : path, + "directory": targ, + "content" : nil, + } + + entries, err := os.ReadDir(targ) + + if err != nil { + app.Log(err) + app.HttpRespForbidden(w, req, "Server error") + } + + var slice []interface{}; + + for _, e := range entries { + slice = append(slice, map[string]interface{}{ + "name": e.Name(), + }) + + // fmt.Println(e.Name()); + } + + resp["content"] = slice + + app.HttpRespJson(w, resp) + + return + } + + func (app *App) HttpReqDir(w http.ResponseWriter, req *http.Request, targ string, path string) { + finx := path + "/index.html" + + data, err := app.FileReadLimit(finx, 1048576); + + if os.IsNotExist(err) { + app.HttpReqDir1(w, req, targ, path) + return + } + + if err == app.ErrIsDir { + app.HttpReqDir1(w, req, targ, path) + return + } + + if err != nil { + app.Log("Err: %s", err) + app.HttpRespForbidden(w, req, "Server error") + return + } + + w.Header().Set("Content-Type", "text/html") + + app.HttpRespText(w, data) + + return + } + + func (app *App) HttpReq(w http.ResponseWriter, req *http.Request) { + // https://go.dev/src/net/http/request.go + +// fmt.Printf("Request: %+v\n", req) + + var auri = (strings.Split(req.RequestURI, "/")) + auri[0] = "/" + + var aidx = 0 + + app.Log("Auri: %+v", auri) + + var locTarg map[string]interface{} + var locEnd bool + + locList, err := app.ConfGetArr("http", "location") + + if err != nil { + app.HttpRespForbidden(w, req, "Invalid http.location") + return + } + + for uid, uri := range auri { + locEnd = false + + for _, locIfc := range locList { + var locMap map[string]interface{}; + + switch v := locIfc.(type) { + case map[string]interface{}: + locMap = v; + } + + if locMap == nil { + app.Log("Not Found") + continue + } + + locUri := locMap["uri"] + + if 1 == 0 { +// fmt.Printf("Found: %+v\n", locUri) + continue + } + + if locUri != uri { +// fmt.Printf(" %s != %s\n", locUri, uri) + continue + } + +// fmt.Printf("Found: %s\n", uri) + + locTarg = locMap + locEnd = true + aidx = uid + + switch v := locMap["list"].(type) { + case []interface{}: + locList = v; +// fmt.Printf("Found list\n"); + } + } + + if !locEnd { + break + } + } +/* + fmt.Printf("Targ %+v\n", locTarg); + fmt.Printf("Targ %+v\n", locEnd ); + fmt.Printf("Targ %s \n", locTarg["uri"] ); + fmt.Printf("Targ %+v\n", auri[(aidx+1):]); +*/ + switch locTarg["type"] { + case "json": + targ := locTarg["targ"].(string) + + app.HttpRespJson(w, locTarg[targ]) + + return + + case "func": + targ := locTarg["targ"].(string) + parm := locTarg["parm"] + + resp, err := app.CallFuncByName(targ, parm) + + if err != nil { + app.Log("Err: %s", err) + break + } + + app.HttpRespJson(w, resp) + + return + + case "directory": + targ := locTarg["targ"].(string) + + path := targ + "/" + strings.Join(auri[(aidx+1):], "/") + + app.Log("Location: %s", path) + + data, err := app.FileReadLimit(path, 1048576); + + if os.IsNotExist(err) { + break + } + + if err == app.ErrIsDir { + app.HttpReqDir(w, req, targ, path) + return + } + + if err != nil { + app.Log("Err: %s", err) + app.HttpRespForbidden(w, req, "Server error") + return + } + + w.Header().Set("Content-Type", "text/html") + + app.HttpRespText(w, data) + + return + + case "redirect": + http.Redirect(w, req, locTarg["targ"].(string), int(locTarg["code"].(float64))) + + return + +// case "ws": + } + + app.HttpRespForbidden(w, req, "Invalid type") + } diff --git a/cmpSnipper/cmpSnipperHttpWsHandle.go b/cmpSnipper/cmpSnipperHttpWsHandle.go new file mode 100644 index 0000000..73098f5 --- /dev/null +++ b/cmpSnipper/cmpSnipperHttpWsHandle.go @@ -0,0 +1,168 @@ + + package cmpSnipper + + import ( + "time" + "net/http" + + "github.com/gorilla/websocket" + ) + + type cmpWbsCliStruct struct { + W http.ResponseWriter + Connect *websocket.Conn + HostPort string + } + + var upgrader = websocket.Upgrader{ + ReadBufferSize: 1024, + WriteBufferSize: 1024, + HandshakeTimeout: 60 * time.Second, + } + + func (app *App) HttpWebSockHand(w http.ResponseWriter, req *http.Request) { + conn, err := upgrader.Upgrade(w, req, nil) + + defer conn.Close() + + if err != nil { + app.Log(err) + return + } + + app.Log("HttpWebSockHand:for") + + HostPort := req.Header.Get("X-Real-Ip") + + if HostPort == "" { + HostPort = req.Header.Get("X-Forwarded-For") + } + + if HostPort == "" { + HostPort = req.RemoteAddr + } + + app.Log("New client: %s", HostPort) + + var cmpWbsCli cmpWbsCliStruct = cmpWbsCliStruct{ + w, + conn, + HostPort, + } + + + app.CliList = append(app.CliList, cmpWbsCli) + + app.Log(app.CliList) + + for i, cli := range app.CliList { + app.Log("Client %s, %s", i, cli.HostPort) + } + + for { + var inp = map[string]interface{}{} + var out = map[string]interface{}{ + "snip": "", + "result": nil, + } + + err := conn.ReadJSON(&inp) + + if err != nil { + app.Log("Read JSON: %s", err) + app.Log(err) + + var list []cmpWbsCliStruct + + for _, ii := range app.CliList { + if ii.HostPort == HostPort { + continue + } + + list = append(list, ii) + } + + app.CliList = list + + return + } + + app.Log("Received message: %+v", inp) + + out["snip"] = inp["snip"] + + // app.Log("Copy snip OK") + + var snip string; + + snip = inp["snip"].(string) + + // app.Log("Look snip") + + if app.SnipExists(snip) { + out["result"], err = app.SnipCall(cmpWbsCli, snip, inp) + + if err != nil { + out["error"] = "Server error" + } + } else { + out["error"] = "Unknown snip" + app.Log("Unknow snip:", inp["snip"]) + } + + err = conn.WriteJSON(out) + + if err != nil { + app.Log(err) + } + + // for + } + + return + } + + func (app *App) HttpWebSockSendTo(cmpWbsCli cmpWbsCliStruct, snip string, result map[string]interface{}) { + var err error + + out := map[string]interface{}{ + "snip": snip, + "result": result, + } + + err = cmpWbsCli.Connect.WriteJSON(out) + + if err != nil { + app.Log(err) + return + } + + return + } + + func (app *App) HttpWebSockSendAll(snip string, result map[string]interface{}) { + var err error + + app.Log("Send to all") + + out := map[string]interface{}{ + "snip": snip, + "result": result, + } + + for _, ii := range app.CliList { + if ii.HostPort == "" { + break + } + + err = ii.Connect.WriteJSON(out) + + if err != nil { + app.Log(err) + continue + } + } + + return + } + diff --git a/cmpSnipper/cmpSnipperLog.go b/cmpSnipper/cmpSnipperLog.go new file mode 100644 index 0000000..0aa6848 --- /dev/null +++ b/cmpSnipper/cmpSnipperLog.go @@ -0,0 +1,57 @@ + + package cmpSnipper + + import ( + "fmt" + "log" + "time" + "encoding/json" + ) + + func (app *App) LogMod(arg interface{}) string { + var str string + + switch t := arg.(type) { + case nil: + str = /* "N:" + */ "null" + + case string: + str = /* "S:" + */ t + + case error: + str = /* "E:" + */ t.Error() + + case byte: + str = /* "B:" + */ string(t) + + case []byte: + str = /* "A:" + */ string(t) + + case interface{}: + obyte, _ := json.MarshalIndent(arg, "", "\t") + str = /* "J:" + */ string(obyte) + + default: + str = /* "U:" + */ fmt.Sprintf("Unknown type %T", t) + } + + return str + } + + func (app *App) Log(farg any, iargs ...interface{}) { + var str = app.LogMod(farg) + + log.SetPrefix(time.Now().UTC().Format("2006-01-02 15:04:05") + ": ") + + oargs := make([]interface{}, len(iargs)) + + for k, arg := range iargs { + oargs[k] = app.LogMod(arg) + } + + log.Output(2, fmt.Sprintf(str + "\n", oargs...)) + } + + func (app *App) LogSetup() { + log.SetFlags( log.Lshortfile ); + } diff --git a/cmpSnipper/cmpSnipperSignal.go b/cmpSnipper/cmpSnipperSignal.go new file mode 100644 index 0000000..81187ec --- /dev/null +++ b/cmpSnipper/cmpSnipperSignal.go @@ -0,0 +1,84 @@ + + package cmpSnipper + + import ( + "os" + "os/signal" + "syscall" + ) + + func (app *App) WaitSignal(term chan bool) { + var sigreq = make(chan os.Signal, 1) + + for true { + signal.Notify(sigreq) + sigrcv := <-sigreq + + // app.Log("Got signal: %s", sigrcv) + + if sigrcv == syscall.SIGTERM { + term <- true + // continue + return + } + + if sigrcv == syscall.SIGINT { + term <- true + // continue + return + } + + if sigrcv == syscall.SIGQUIT { + term <- true + // continue + return + } + + if sigrcv == syscall.SIGUSR1 { + app.Log("Got signal: %s", sigrcv) + continue + } + + if sigrcv == syscall.SIGUSR2 { + app.Log("Got signal: %s", sigrcv) + + app.Log("Rand 4d: %d", app.Rand4d) + + continue + } + + // 1) SIGHUP + // 2) SIGINT + // 3) SIGQUIT + // 4) SIGILL + // 5) SIGTRAP + // 6) SIGABRT + // 7) SIGBUS + // 8) SIGFPE + // 9) SIGKILL + // 10) SIGUSR1 + // 11) SIGSEGV + // 12) SIGUSR2 + // 13) SIGPIPE + // 14) SIGALRM + // 15) SIGTERM + // 16) SIGSTKFLT + // 17) SIGCHLD + // 18) SIGCONT + // 19) SIGSTOP + // 20) SIGTSTP + // 21) SIGTTIN + // 22) SIGTTOU + // 23) SIGURG + // 24) SIGXCPU + // 25) SIGXFSZ + // 26) SIGVTALRM + // 27) SIGPROF + // 28) SIGWINCH + // 29) SIGIO + // 30) SIGPWR + // 31) SIGSYS + + app.Log("Got signal: %s", sigrcv) + } + } diff --git a/cmpSnipper/cmpSnipperSnip.go b/cmpSnipper/cmpSnipperSnip.go new file mode 100644 index 0000000..76ac646 --- /dev/null +++ b/cmpSnipper/cmpSnipperSnip.go @@ -0,0 +1,78 @@ + + package cmpSnipper + + import ( + "reflect" + "errors" + ) + + func (app *App) SnipExists(funcName string) bool { + snipName := app.UpperFirst(funcName) + "Snip" + refCls := reflect.ValueOf(app) + refMth := refCls.MethodByName(snipName) + + if !refMth.IsValid() { + return false + } + + return true + } + + func (app *App) SnipCall(cmpWbsCli cmpWbsCliStruct, fnName string, ifcII interface{}) (interface{}, error) { + snName := app.UpperFirst(fnName) + "Snip" + refCls := reflect.ValueOf(app) + refMth := refCls.MethodByName(snName) + + if !refMth.IsValid() { + app.Log("Call method \"%s\" not found", snName) + return nil, errors.New("Call method not founnd") + } + + app.Log("Call method \"%s\"", snName) + + ifcIM := map[string]interface{}{ + "InputArgument" : ifcII, + "HostPort" : cmpWbsCli.HostPort, + } + + refIL := []reflect.Value{ + reflect.ValueOf(ifcIM), + } + + refOL := refMth.Call(refIL) + + infOI := refOL[0].Interface() + + app.Log("Interface output item %+v", infOI) + + return infOI, nil + } + + func (app *App) SnipGetCli(parm any) (cmpWbsCliStruct, error) { + var err error + var cmpWbsCli cmpWbsCliStruct + + HostPortIfc, err := app.IfcGet(parm.(map[string]interface{}), "HostPort") + + if err != nil { + app.Log("Invalid iterface") + return cmpWbsCli, err + } + + HostPort, ok := HostPortIfc.(string) + + if !ok { + app.Log("Invalid string") + return cmpWbsCli, errors.New("Can't covert to string") + } + + for _, ii := range app.CliList { + if HostPort != ii.HostPort { + continue + } + + return ii, nil + } + + return cmpWbsCli, errors.New("Client " + HostPort + " not found") + } diff --git a/cmpSnipper/cmpSnipperSnipCliList.go b/cmpSnipper/cmpSnipperSnipCliList.go new file mode 100644 index 0000000..0eeacce --- /dev/null +++ b/cmpSnipper/cmpSnipperSnipCliList.go @@ -0,0 +1,12 @@ + + package cmpSnipper + + func (app *App) CliListSnip(parm any) (any, error) { + // app.Log("") + + ret := map[string]interface{}{ + "list" : app.CliList, + } + + return ret, nil + } diff --git a/cmpSnipper/cmpSnipperSnipLeftMenu.go b/cmpSnipper/cmpSnipperSnipLeftMenu.go new file mode 100644 index 0000000..e872313 --- /dev/null +++ b/cmpSnipper/cmpSnipperSnipLeftMenu.go @@ -0,0 +1,32 @@ + + package cmpSnipper + + import ( + "encoding/json" + ) + + func (app *App) LeftMenuSnip(parm any) (any, error) { + // app.Log("Read data") + + mainMenuFile, err := app.ConfGetStr("mainMenuFile") + + if err != nil { + return nil, err + } + + data, err := app.FileReadLimit(mainMenuFile, 1048576) + + if err != nil { + return nil, err + } + + var res = map[string]interface{}{} + + err = json.Unmarshal(data, &res) + + if err != nil { + return nil, err + } + + return res, nil + } diff --git a/cmpSnipper/cmpSnipperSnipPing.go b/cmpSnipper/cmpSnipperSnipPing.go new file mode 100644 index 0000000..7393a80 --- /dev/null +++ b/cmpSnipper/cmpSnipperSnipPing.go @@ -0,0 +1,70 @@ + + package cmpSnipper + + import ( + "os/exec" + "context" + "time" + "strings" + "syscall" + ) + + func (app *App) PingSnip(parm any) (any, error) { + // log.Println("PingSnip") + + path, err := exec.LookPath("ping") + + app.Log("Prog:", path) + + if err != nil { + return map[string]interface{}{ + "retval" : 127, + "error" : "No prog", + }, nil + } + + ctx, cancel := context.WithTimeout(context.Background(), 10 * time.Second) + + defer cancel() + + cmd := exec.CommandContext(ctx, "ping", "-c", "4", "8.8.8.8") + + cmd.Stdin = strings.NewReader("") + + var stdout strings.Builder + cmd.Stdout = &stdout + + var stderr strings.Builder + cmd.Stderr = &stderr + + 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() + } + + return map[string]interface{}{ + "retval" : exitCode, + "error" : err, + "stdout" : stdout.String(), + "stderr" : stderr.String(), + }, nil + } diff --git a/cmpSnipper/cmpSnipperSnipRand4d.go b/cmpSnipper/cmpSnipperSnipRand4d.go new file mode 100644 index 0000000..bb8e2db --- /dev/null +++ b/cmpSnipper/cmpSnipperSnipRand4d.go @@ -0,0 +1,16 @@ + + package cmpSnipper + + import ( + "math/rand" + ) + + func (app *App) Rand4dSnip(parm any) (any, error) { + // app.Log("Rand4dSnip") + + app.Rand4d = rand.Intn(10000) + 1000 + + return map[string]interface{}{ + "rand4d" : app.Rand4d, + }, nil + } diff --git a/cmpSnipper/cmpSnipperUtil.go b/cmpSnipper/cmpSnipperUtil.go new file mode 100644 index 0000000..7af4966 --- /dev/null +++ b/cmpSnipper/cmpSnipperUtil.go @@ -0,0 +1,87 @@ + + package cmpSnipper + + import ( + "os" + "errors" + "strings" + + "reflect" + ) + + func (app *App) FileReadLimit(file string, mSize int64) ([]byte, error) { + info, err := os.Stat(file) + + if err != nil { + return nil, err + } + + if info.IsDir() { + return nil, app.ErrIsDir + } + + fSize := info.Size() + + if fSize > mSize { + return nil, errors.New("File is big") + } + + fd, err := os.Open(file) + + if err != nil { + return nil, err + } + + defer fd.Close() + + var rata = make([]byte, fSize); + + rSize, err := fd.Read(rata); + + if fSize != int64(rSize) { + return nil, errors.New("Can't read full file size") + } + + return rata, nil + } + + func (app *App) UpperFirst(si string) string { + bi := []byte(si) + ss := strings.Title(string(bi[0])) + bo := bi + bo[0] = ss[0] + + return string(bo) + } + + func (app *App) CallFuncByName(funcName string, ifcIL ...interface{}) ([]interface{}, error) { + refCls := reflect.ValueOf(app) + refMth := refCls.MethodByName(funcName) + + if !refMth.IsValid() { + app.Log("Call method \"%s\" not found", funcName) + return nil, errors.New("Call method not founnd") + } + + app.Log("Call method \"%s\"", funcName) + + refIL := make([]reflect.Value, len(ifcIL)) + + for i, ifcII := range ifcIL { + refIL[i] = reflect.ValueOf(ifcII) + } + + // fmt.Println("reflect input list", refIL); + + refOL := refMth.Call(refIL) + + infOL := make([]interface{}, len(refOL)) + + for i, refOI := range refOL { + infOL[i] = refOI.Interface() + } + + app.Log("Interface output list %+v", infOL) + + return infOL, nil + } diff --git a/cmpSnipper/cmpSnippetHttpCli.go b/cmpSnipper/cmpSnippetHttpCli.go new file mode 100644 index 0000000..ed4fd49 --- /dev/null +++ b/cmpSnipper/cmpSnippetHttpCli.go @@ -0,0 +1,196 @@ + + package cmpSnipper + + import ( +// "crypto/md5" + "net/http" +// "os" + "io" +// "fmt" + "bufio" +// "io/ioutil" + "bytes" + "strings" +// "golang.org/x/net/html" +// "net/url" + "encoding/json" + "compress/gzip" + "compress/flate" + "github.com/andybalholm/brotli" + ) + + func (app *App) HttpGet(url string) (map[string]interface{}) { + ret := map[string]interface{}{ + "Error" : nil , + "RequestUrl" : url , + "StatusCode" : 0 , + "ContentRaw" : nil , + "ContentJson" : nil , + "Header" : nil , + "Content-Encoding" : "" , + "Content-Type" : "" , + "Content-Length" : "" , + } + + app.Log(url) + + cli := &http.Client{} + + ifcRawHead, err := app.ConfGetIfc("httpTest", "head") + + if err != nil { + app.Log(err) + ret["Error"] = err + return ret + } + + mapIfcHead := ifcRawHead.(map[string]interface{}) + + mapStrHead := make(map[string][]string) + + for key, val := range mapIfcHead { + mapStrHead[key] = []string{ val.(string) } + } + + req, err := http.NewRequest("GET", url, nil) + + req.Header = mapStrHead + + res, err := cli.Do(req) + + if err != nil { + app.Log(err) + ret["Error"] = err + return ret + } + + defer res.Body.Close() + + if err != nil { + app.Log(err) + ret["Error"] = err + return ret + } + + ret["StatusCode" ] = res.StatusCode + ret["Content-Length"] = res.ContentLength + ret["Header" ] = res.Header + + // app.Log("Response status: %s", res.Status) + // app.Log("Header: %+v", res.Header) + + // app.Log("TransferEncoding: %+v", res.TransferEncoding) + // app.Log("ContentLength: %+v", res.ContentLength) + + var tmpl []string + + tmpl = res.Header["Content-Encoding"] + if len(tmpl) > 0 { + ret["Content-Encoding"] = tmpl[0] + } + + tmpl = res.Header["Content-Type"] + if len(tmpl) > 0 { + tmpl = strings.Split(tmpl[0], ";") + + if len(tmpl) > 0 { + ret["Content-Type"] = tmpl[0] + } + } + + // Content-Disposition: attachment; name="fieldName"; filename="myfile.txt" + + + out := new(bytes.Buffer) + var encReader io.Reader + + switch ret["Content-Encoding"] { + case "": + encReader = bufio.NewReader(res.Body) + case "br": + encReader = brotli.NewReader(res.Body) + case "gzip": + encReader, err = gzip.NewReader(res.Body) + case "deflate": + encReader = flate.NewReader(res.Body) + default: + app.Log("Unknown Content-Encoding %s", ret["Content-Encoding"]) + encReader = bufio.NewReader(res.Body) + } + + if err != nil { + app.Log(err) + ret["Error"] = err + return ret + } + + size, err := out.ReadFrom(encReader) + + if err != nil { + app.Log(err) + ret["Error"] = err + return ret + } + + app.Log("Read %s", size) + + var contRaw []byte = out.Bytes() + ret["ContentRaw"] = contRaw + + if ret["Content-Type"] == "application/json" { + var contJson interface{} + + err = json.Unmarshal(contRaw, &contJson) + + if err != nil { + app.Log(err) + ret["Error"] = err + return ret + } + + ret["ContentJson"] = contJson + } + + return ret + } + + func (app *App) CmpHttpTestSnip(parm any) (any, error) { + var err error + + cli, err := app.SnipGetCli(parm) + + if err != nil { + return map[string]interface{}{ + "status": "error", + "error" : "Not client found", + }, err + } + + url, err := app.ConfGetStr("httpTest", "url") + + if err != nil { + return map[string]interface{}{ + "status": "error", + "error" : "Not test URL", + }, err + } + + go func() { + var a map[string]interface{} + + a = app.HttpGet(url) + + app.Log(a) + + app.HttpWebSockSendTo(cli, "cmpHttpTest", map[string]interface{}{ + "status" : "response", + "response": a, + }) + }() + + + return map[string]interface{}{ + "status" : "request", + "request": url, + }, nil + } diff --git a/cmpSnipper/cmpSnippetSshCli.go b/cmpSnipper/cmpSnippetSshCli.go new file mode 100644 index 0000000..4897d7d --- /dev/null +++ b/cmpSnipper/cmpSnippetSshCli.go @@ -0,0 +1,288 @@ + + package cmpSnipper + + import ( + "io" + "bufio" + "strings" + "net" + "golang.org/x/crypto/ssh" + ) + + type cmpSshCliStruct struct { + Host string + Connect *ssh.Client + Session *ssh.Session + Output *bufio.Reader + } + + + func (app *App) SshAddHostKey(host string, remote net.Addr, pubKey ssh.PublicKey) error { + app.Log(pubKey) + return nil + } + + func (app *App) SshConnReal(cmpSshCli *cmpSshCliStruct, host string, keyfile string) error { + var err error + + cmpSshCli.Host = host + + key, err := app.FileReadLimit(keyfile, 1048576) + + if err != nil { + app.Log(err) + return err + } + + signer, err := ssh.ParsePrivateKey(key) + + if err != nil { + app.Log(err) + return err + } + + config := &ssh.ClientConfig{ + User: "root", + Auth: []ssh.AuthMethod{ + // HostKeyCallback: InsecureIgnoreHostKey(), + // ssh.Password("yourpassword"), + ssh.PublicKeys(signer), + }, +// HostKeyCallback: ssh.FixedHostKey(hostKey), + HostKeyCallback: ssh.HostKeyCallback(func(host string, remote net.Addr, key ssh.PublicKey) error { + app.SshAddHostKey(host, remote, key) + + return nil + }), + } + + cmpSshCli.Connect, err = ssh.Dial("tcp", cmpSshCli.Host, config) + + if err != nil { + app.Log(err) + return err + } + + return nil + } + + func (app *App) SshSession(cmpSshCli *cmpSshCliStruct) error { + var err error + + cmpSshCli.Session, err = cmpSshCli.Connect.NewSession() + + if err != nil { + app.Log(err) + return err + } + + // defer cmpSshCli.Session.Close() + + modes := ssh.TerminalModes{ + ssh.ECHO: 0, + } + + err = cmpSshCli.Session.RequestPty("xterm", 50, 80, modes) + + if err != nil { + app.Log(err) + return err + } + + // sshIn, err := session.StdinPipe() + // if err != nil { + // app.Log(err) + // return err + // } + + sshOut, err := cmpSshCli.Session.StdoutPipe() + + if err != nil { + app.Log(err) + return err + } + + sshErr, err := cmpSshCli.Session.StderrPipe() + + if err != nil { + app.Log(err) + return err + } + + output := io.MultiReader(sshOut, sshErr) + + cmpSshCli.Output = bufio.NewReader(output) + + return nil + } + + func (app *App) SshConn(host string, keyfile string) (int, error) { + var err error + + // app.Log(url) + + var cmpSshCli cmpSshCliStruct + + err = app.SshConnReal(&cmpSshCli, host, keyfile) + + if err != nil { + return -1, err + } + + err = app.SshSession(&cmpSshCli) + + if err != nil { + return -1, err + } + + app.SshList = append(app.SshList, cmpSshCli) + + app.Log("Append new") + + return len(app.SshList)-1, nil + } + + func (app *App) SshExec(cmpWbsCli cmpWbsCliStruct, idx int, cma []string) (error) { + var err error + + cmd := strings.Join(cma, " ") + + cmpSshCli := app.SshList[idx] + + // app.Log("idx: '%s', '%s'", idx, cmpSshCli.Host) + + err = cmpSshCli.Session.Start(cmd) + + if err != nil { + app.Log(err) + return err + } + + res := map[string]interface{}{ + "string": nil, + } + + var bli []byte + var sli string + + for { + bli, _, err = cmpSshCli.Output.ReadLine() + + if err != nil { + app.Log("Complete") + break + } + + sli = string(bli) + + app.Log(sli) + + res["string"] = sli + + app.HttpWebSockSendTo(cmpWbsCli, "cmpSshTest", res) + } + + cmpSshCli.Session.Wait() + + app.Log("Free resource SshList[" + string(idx) + "]") + + cmpSshCli.Session.Close() + + cmpSshCli.Connect.Close() + + + app.Log("Remove SshList[" + string(idx) + "]") + + var list []cmpSshCliStruct + + for i, li := range app.SshList { + if i == idx { + continue + } + + list = append(list, li) + } + + app.SshList = list + + app.Log("End") + + // done <- true + + return nil + } + + func (app *App) CmpSshTestSnip(parm any) (any, error) { + var err error + + // var ifcIM map[string]interface{} = parm + + cli, err := app.SnipGetCli(parm) + + // app.Log(cli) + + if err != nil { + return map[string]interface{}{ + "string": "Not client found", + }, err + } + + + host, err := app.ConfGetStr("sshTestHost", "HostPort") + + if err != nil { + return map[string]interface{}{ + "string": "Not set sshTestHost.HostPort", + }, err + } + + keyfile, err := app.ConfGetStr("sshTestHost", "PrivateKeyFile") + + if err != nil { + return map[string]interface{}{ + "string": "Not set sshTestHost.PrivateKeyFile", + }, err + } + + cmi, err := app.ConfGetIfc("sshTestHost", "Command") + + if err != nil { + return map[string]interface{}{ + "string": "Not set sshTestHost.Command", + }, err + } + + cml := cmi.([]interface{}) + + cma := make([]string, len(cml)) + + for i, v := range cml { + cma[i] = v.(string) + } + + /* + if cma[0] == "none" { + return map[string]interface{}{ + "string": "Can't get command", + }, err + } + */ + + idx, err := app.SshConn(host, keyfile) + + if err != nil { + return map[string]interface{}{ + "string": "Connection to '" + host + "' failed", + }, err + } + + go func() { + app.SshExec(cli, idx, cma) + }() + + app.Log("Wait") + + return map[string]interface{}{ + "string": "Launch: " + strings.Join(cma, " "), + }, nil + } diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..13c65e0 --- /dev/null +++ b/go.mod @@ -0,0 +1,12 @@ +module t/z + +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 +) + +require golang.org/x/sys v0.13.0 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..1a1142e --- /dev/null +++ b/go.sum @@ -0,0 +1,10 @@ +github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= +github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= +github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= +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/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/main.go b/main.go new file mode 100644 index 0000000..9c6054c --- /dev/null +++ b/main.go @@ -0,0 +1,28 @@ + + package main + + import ( + // "fmt" + "os" + + "t/z/cmpSnipper" + ) + + func main() { + var app cmpSnipper.App; + var err error + + err = app.Prep() + + if err != nil { + os.Exit(1) + } + + err = app.Proc() + + if err != nil { + os.Exit(1) + } + + os.Exit(0) + } diff --git a/plug/CmpPlug1/CmpPlug1.go b/plug/CmpPlug1/CmpPlug1.go new file mode 100644 index 0000000..88c20ee --- /dev/null +++ b/plug/CmpPlug1/CmpPlug1.go @@ -0,0 +1,34 @@ + + package main + + import ( + "fmt" + +// "net/html" + ) + + type CmpPlug struct { + Log func(any, ...interface{}) + } + + var plg CmpPlug + + func CmpPlug1Init(in int, f func(any, ...interface{}), ifc map[string]interface{} ) interface{} { + plg.Log = f + + ifc["Ccc"] = "Ddd" + + fmt.Printf("This is CmpPlug1: %d\n", in) + + return ifc + } + + func CmpPlug1Some() error { + plg.Log("This is CmpPlug1Some") + + return nil + } + + func main() { + fmt.Print(23) + } diff --git a/static/cmpSwoPage.css b/static/cmpSwoPage.css new file mode 100644 index 0000000..e396f05 --- /dev/null +++ b/static/cmpSwoPage.css @@ -0,0 +1,558 @@ +body { + color:#ccc; + background-color:#222; + font-size: 20px; +} + +@media (display-mode: fullscreen) { + body { + color:#ccc; + background-color:#444; + } +} + + +button { + background-color:transparent; + border:1px solid grey; + border-radius:3px; + color: white; + width:24px; + height:24px; + font-size:18px; +} +button:hover { + background-color:transparent; + border:1px solid white; + color: white; +} + + +/* +input[type="checkbox"]:checked, +input[type="checkbox"]:not(:checked), +input[type="radio"]:checked, +input[type="radio"]:not(:checked) +{ + position: absolute; + left: -9999px; +} +input[type="checkbox"]:checked + label, +input[type="checkbox"]:not(:checked) + label, +input[type="radio"]:checked + label, +input[type="radio"]:not(:checked) + label { + // display: inline-block; + position: relative; + padding-left: 28px; + line-height: 20px; + cursor: pointer; +} +input[type="checkbox"]:checked + label:before, +input[type="checkbox"]:not(:checked) + label:before, +input[type="radio"]:checked + label:before, +input[type="radio"]:not(:checked) + label:before { + content: ""; + position: absolute; + left: 0px; + top: 0px; + width: 18px; + height: 18px; + border: 1px solid green; + background-color: #444; +} +input[type="checkbox"]:checked + label:before, +input[type="checkbox"]:not(:checked) + label:before { + border-radius: 4px; +} +input[type="radio"]:checked + label:before, +input[type="radio"]:not(:checked) + label:before { + border-radius: 100%; +} +input[type="checkbox"]:checked + label:after, +input[type="checkbox"]:not(:checked) + label:after, +input[type="radio"]:checked + label:after, +input[type="radio"]:not(:checked) + label:after { + content: ""; + position: absolute; + -webkit-transition: all 0.2s ease; + -moz-transition: all 0.2s ease; + -o-transition: all 0.2s ease; + transition: all 0.2s ease; +} +input[type="checkbox"]:checked + label:after, +input[type="checkbox"]:not(:checked) + label:after { + left: 3px; + top: 4px; + width: 10px; + height: 5px; + border-radius: 1px; + border-left: 4px solid #ccc; + border-bottom: 4px solid #ccc; + -webkit-transform: rotate(-45deg); + -moz-transform: rotate(-45deg); + -o-transform: rotate(-45deg); + -ms-transform: rotate(-45deg); + transform: rotate(-45deg); +} +input[type="radio"]:checked + label:after, +input[type="radio"]:not(:checked) + label:after { + left: 5px; + top: 5px; + width: 10px; + height: 10px; + border-radius: 100%; + background-color: #e145a3; +} +input[type="checkbox"]:not(:checked) + label:after, +input[type="radio"]:not(:checked) + label:after { + opacity: 0; +} +input[type="checkbox"]:checked + label:after, +input[type="radio"]:checked + label:after { + opacity: 1; +} + +*/ + + +table { + width:100%; + padding: 0px; +} + +table tr.odd:nth-child(2n) { + background: #2f2f2f; +} + +table tr.odd:hover { + background: #282828; +} + +table tbody.odd:nth-child(2n) { + background: #2f2f2f; +} + +table tbody.odd:hover { + background: #282828; +} + +table tr td { + padding:4px; +} + +a { color: #37a4a4; text-decoration:none; } +a:visited { color: #37a4a4; text-decoration:none; } + +#bodyDiv { + position:absolute; + top:0; + left:0; + right:0; + bottom:0; + border:0px solid black; +} + +#topper { + position:absolute; + top:0; + left:0; + right:0; + height:32px; + border:0px solid grey; + overflow:hidden; +} + +#topperTab { + vertical-align:top; + color:#222; + background: {SWS_BGCOLOR}; +} + +#topperTabLeft { + width:33%; +} + +#topperTabCenter { + width:33%; + text-align:center; +} + +#topperTabRight { + width:33%; + text-align:right; +} + +#footer { + background-color: #222; + position:absolute; + left:0; + right:0; + bottom:0; + height:32px; + overflow:hidden; + border:0px solid grey; +} + +#footerTab { + margin:0; + padding:0; + vertical-align:top; + border:0px solid grey; +} + +#footerTabLeft { + width:33%; +} +#footerTabCenter { + width:33%; + text-align:center; +} +#footerTabRight { + width:33%; + text-align:right; +} + +/***********************/ + +#leftMenu { + position:absolute; + top:32px; + left:0px; + width:142px; + bottom:32px; + border:0px solid grey; + padding:0px; + overflow:auto; +} + +#leftMenu input[type=button].leftMenuButton { + white-space: normal; + padding:4px 14px 4px 14px; + border:1px solid grey; + border-radius:4px; + background: #333; + color:#eee; + display:block; + width:138px; + margin:4px; +} + +#leftMenu button.leftMenuButton { + white-space: normal; + padding:4px 14px 4px 14px; + border:1px solid grey; + border-radius:4px; + background: #333; + color:#eee; + display:block; + width:138px; + height:100%; + margin:4px; + font-size:12px; +} + +#leftMenu a.leftMenuLink { + white-space: normal; + padding:4px 14px 4px 14px; + border:0px solid grey; + border-radius:4px; + display:block; + text-align:center; +} + + +#leftMenu div { + position:relative; +} + +#leftMenu .menuNotifyLab { + border: 1px solid grey; + border-radius: 4px; + width: 6px; + height: 6px; + background-color: red; + position: absolute; + bottom: 8px; + right: 8px; +} + +#leftMenu .menuNotifyLabAnim1 { + animation-duration: 0.8s; + animation-name: menuNotifyLabAnim1f; + animation-timing-function: linear; + animation-direction: alternate; +} + +@keyframes menuNotifyLabAnim1f { + from { + bottom: 500px; + } + + to { + bottom: 8px; + } +} + +#leftMenu .menuNotifyLabAnim2 { + animation-duration: 0.6s; + animation-name: menuNotifyLabAnim2f; + animation-timing-function: linear; +} + +@keyframes menuNotifyLabAnim2f { + from { + width: 6px; + height: 6px; + + bottom: 8px; + right: 8px; + } + + 50% { + width: 12px; + height: 12px; + + bottom: 5px; + right: 5px; + } + + to { + width: 6px; + height: 6px; + + bottom: 8px; + right: 8px; + } +} + +#leftMenu .menuNotifyLabHide { + opacity:0; + animation-duration: 0.6s; + animation-name: menuNotifyLabAnim3f; + animation-timing-function: linear; +} + +@keyframes menuNotifyLabAnim3f { + from { + width: 6px; + height: 6px; + + bottom: 8px; + right: 8px; + + opacity:1; + } + + to { + width: 12px; + height: 12px; + + bottom: 5px; + right: 5px; + + opacity:0; + } +} + +.leftMenuBlock { + display: block; + position: relative; + margin-top:4px; + margin-bottom:8px; + padding-bottom:8px; +} + +div.leftMenuBlockName { + display: block; +} + +button.leftMenuBlockName { + display: block; + border:0; +} + +.leftMenuBlockNameButton { + position: absolute; + top: 1px; + right:1px; + + border:1px solid green; + border-radius:12px; + padding:0px; + width :24px; + height :24px; + font-size:12px; +} + +.leftMenuBlockHide > .leftMenuBlockBody { + display: none; +} + +.leftMenuBlockBody { + display: block; +} + +/*******************************/ + +#mainRoot { + position:absolute; + top:32px; + left:150px; + right:0; + bottom:32px; + border:1px solid grey; + overflow:hidden; +} + + +input[type=text] { + background: #333; + color:#eee; +} + +input[type=button] { + padding:4px 14px 4px 14px; + border:1px solid grey; + border-radius:4px; + background: #333; + color:#eee; +} + +.headWord { + display:inline-block; + overflow:hidden; +} + +.footerWord { + display:inline-block; + overflow:hidden; + padding:0 2px 0 2px; +} + + + +/*********/ + +#page { + z-index:20; + position:fixed; + top:32px; + left:2px; + right:2px; + bottom:0; + border:1px solid #000; + border-radius:4px; + background:#222; + padding:6px; +} + +#pageBody { + border:0px solid red; + overflow:auto; + width:100%; + height:100%; +} + +#topRight { + z-index:10; + position:fixed; + background:#000; + top:-4px; + left:76%; + right:80px; + bottom:0px; + overflow:auto; + border:1px solid #000; + border-radius:4px; + padding:8px; + opacity:0.8; +} + +#topRight:hover { + z-index:50; + border:1px solid green; +} + +#topCent { + z-index:10; + position:fixed; + background:#222; + top:0; + left:20%; + right:20%; + border:1px solid #222; + padding:8px; +} + +#topCent:hover { + z-index:50; +} + +#topLeft { + z-index:10; + position:fixed; + background:#000; + top:-6px; + left:-2px; + right:80%; + border:1px solid #444; + padding:8px; +} + +#topLeft:hover { + z-index:50; + border:1px solid green; +} + +#srvDate { + animation-duration: 3s; + animation-name: an1; + letter-spacing: normal; +} + +#srvTime { + animation-duration: 3s; + animation-name: an1; + letter-spacing: normal; +} + +@keyframes an1 { + from { + letter-spacing: 8px; + font-size: 32px; + } + + to { + letter-spacing: normal; + font-size: inhrerit; + } +} + + +/*******/ + +.buttonIco { + border:1px solid grey; + border-radius:4px; + padding:2px; + margin:0; + min-width:54px; + background-color:transparent; + color:white; + font-size:16px; +} + +/*******/ + +#diag { + z-index:50; + position:fixed; + border:1px solid red; + border-radius:8px; + background-color:#222; + top:20px; + left:20px; + min-width:640px; + min-height:480px; + overflow:hidden; +} diff --git a/static/cmpSwoPage.js b/static/cmpSwoPage.js new file mode 100644 index 0000000..0905592 --- /dev/null +++ b/static/cmpSwoPage.js @@ -0,0 +1,809 @@ + class cmpSwoPage { + constructor() { + this.cmpSwoOnConnect = []; + this.cmpSwoOnDisconnect = []; + + this.cmpOnLoad(); + } + + cmpSwoPageInit() { + this.snip = {}; + + if(!this.sws_sta_prefix) + this.sws_sta_prefix = "go-prefix/static/"; + + if(!this.sws_dyn_prefix) + this.sws_dyn_prefix = "go-prefix/ws"; + + this.connect(); + + + this.git = null; + + this.cmpSwoTaskListInit(); + + this.srvTime = 0; + } + + connect() { + var serv = ""; + + if(window.location.protocol == "https:") + serv = "wss://"; + else + serv = "ws://" + + serv += window.location.host + "/" + this.sws_dyn_prefix ; + + this.wsrv = new WebSocket(serv); + + this.wsrv.onopen = this.onopen.bind (this); + this.wsrv.onclose = this.onclose.bind (this); + this.wsrv.onmessage = this.onmessage.bind (this); + this.wsrv.onerror = this.onerror.bind (this); + + this.WSState(1); + } + + onopen(evt) { + console.log("Connected to WebSocket server"); + + for(var i = 0; i < this.cmpSwoOnConnect.length; i++) { + (this.cmpSwoOnConnect[i])(); + } + + if(this.pprm == "only-connect") + return; + + // this.gitInfoReq(); + + // this.leftMenuReq(); + + // this.contentReq(); + } + + onclose(evt) { + for(var i = 0; i < this.cmpSwoOnDisconnect.length; i++) { + (this.cmpSwoOnDisconnect[i])(); + } + + console.log("Disconnected"); + + console.log("Reason: ", evt); + + if(!this.connInterval) { + setTimeout(this.connect.bind(this), 100); + } + } + + onmessage(evt) { + // console.log('Retrieved data from server: ' + evt.data); + + var obj = null; + + if(!evt.data) { + return console.log("Empty answer"); + } + + try { + obj = JSON.parse(evt.data); + } catch(e) { + console.log(evt); + console.log(e); + } + + if(!obj) { + console.log("Invalid JSON"); + + return; + } + + if(!obj.snip) { + console.log("Undefine/empty obj.snip", obj); + return; + } + + if(obj.task) { + console.log("Recv snip: " + obj.snip, "Tasked"); + + if(this.cmpSwoTaskListProc(obj) == 1) { + return; + } + } + + if(obj.snip != "swoTick1000") { + console.log("Recv snip: " + obj.snip, obj); + } + + if(obj.errors) { + console.log("Error: ", obj.errors); + } + + if(!obj.result) { + return console.log("Empty result"); + } + + if(this.snip[obj.snip]) { + console.log("Call this.snip[" + obj.snip + "].rcv"); + return (this.snip[obj.snip].rcv).bind(this, obj.result); + } + + do { + var snipFn = obj.snip + "Rcv"; + + if(typeof(this[snipFn]) != "function") + break; + + console.log("Call this[" + snipFn + "]"); + // console.log(this[snipFn]) + + return this[snipFn](obj.result); + } while(0); + + return console.log("Unknown snip '" + obj.snip + "'"); + } + + onerror(evt, e) { + console.log('Error occured: ', evt.data); + console.log(evt); + console.log(e); + } + + // Util + + setFavicon(i) { + var link = document.querySelector("link[rel~='icon']"); + + if (!link) { + link = document.createElement('link'); + link.rel = 'icon'; + document.head.appendChild(link); + } +/* + if(i) + link.href = '/go-prefix/static/img/umbrella-color.png'; + else + link.href = '/go-prefix/static/img/umbrella-black.png'; + + console.log(link.href); +*/ + } + + WSState(z) { + if(!this.wsrv) { + console.log("WebSocket is NULL"); + return 1; + } + + var m = ""; + + switch(this.wsrv.readyState) { + case this.wsrv.CONNECTING : m = "WebSocket state is CONNECTING" ; break; + case this.wsrv.OPEN : m = "WebSocket state is OPEN" ; break; + case this.wsrv.CLOSING : m = "WebSocket state is CLOSING" ; break; + case this.wsrv.CLOSED : m = "WebSocket state is CLOSED" ; break; + default : m = "WebSocket state is UNKNOWN" ; break; + } + + if(z) + console.log(m); + + if(this.wsrv.readyState == this.wsrv.OPEN) { + this.setFavicon(0); + + return 0; + } + + this.setFavicon(1); + + return 1; + } + + readySnip() { + if(!this.wsrv) { + console.log("WebSocket is NULL"); + return false; + } + + if(this.wsrv.readyState == this.wsrv.OPEN) + return true; + + return false; + } + + reqSnip(snip, obj) { + if(this.WSState()) { + return this.WSState(1); + } + + if(obj) { + obj.snip = snip; + } + else { + obj = { + snip: snip, + }; + } + + console.log("Send snip: " + obj.snip, obj); + + var str = JSON.stringify(obj); + + return this.wsrv.send(str); + } + + + gitInfoReq() { + return this.reqSnip("gitInfo"); + } + + gitInfoRcv(obj) { + var n; + + if(!this.git) { + this.git = obj; + } + + if(obj.gitStatus) { + if((n = document.getElementById("gitStatus"))) { + n.innerHTML = obj.gitStatus; + } + + if((n = document.getElementById("gitTitl"))) { + n.setAttribute("title", obj.gitStatus); + n.style.color = (obj.gitStatus == "treeClean") ? "#86de74" : "#ff3b6b"; + } + } + + if(obj.gitTimeStamp) { + if((n = document.getElementById("gitDate"))) { + n.innerHTML = unixtimeToYYYYMMDD(obj.gitTimeStamp); + + if(obj.gitTimeStamp != this.git.gitTimeStamp) + n.style.color = "#ffff64"; + else + n.style.color = "#86de74"; + } + + if((n = document.getElementById("gitTime"))) { + n.innerHTML = unixtimeToHHMMSS(obj.gitTimeStamp); + + if(obj.gitTimeStamp != this.git.gitTimeStamp) + n.style.color = "#ffff64"; + else + n.style.color = "#86de74"; + } + } + + if(obj.gitCommit && (n = document.getElementById("gitCHID"))) { + n.innerHTML = obj.gitCommit.substr(0, 8); + + if(obj.gitTimeStamp != this.git.gitTimeStamp) + n.style.color = "#ffff64"; + else + n.style.color = "#86de74"; + } + + if(obj.gitComment && (n = document.getElementById("gitComm"))) { + n.innerHTML = obj.gitComment; + + if(obj.gitTimeStamp != this.git.gitTimeStamp) + n.style.color = "#ffff64"; + else + n.style.color = "#86de74"; + } + } + + swoTick1000Rcv(obj) { + var tzOff = checkTZ(0); + + obj.ymd = unixtimeToYYYYMMDD(obj["time"] + tzOff); + obj.hms = unixtimeToHHMMSS (obj["time"] + tzOff); + + this.srvTime = obj["time"]; + + setNodeText("srvDate", obj, "ymd", 0); + setNodeText("srvTime", obj, "hms", 0); + } + + timeReStyle(sta) { + var n1 = document.getElementById("srvDate"); + var n2 = document.getElementById("srvTime"); + + if(sta == "connect") { + if(n1) n1.style.color = "#86de74"; + if(n2) n2.style.color = "#86de74"; + } + else { + if(n1) n1.style.color = "#ff3b6b"; + if(n2) n2.style.color = "#ff3b6b"; + } + + return; + } + + // Logic + + swoSystemctlRestartSelfServiceReq() { + this.reqSnip("swoSystemctlRestartSelfService"); + } + + swoSystemctlRestartSelfServiceRcv() { + ; + } + + rand4dReq() { + this.reqSnip("rand4d"); + } + + rand4dRcv(obj) { + return setNodeText("srvRand", obj, "rand4d"); + } + + pingReq() { + return this.reqSnip("ping"); + } + + pingRcv(obj) { + console.log("pingRcv", obj); + } + +/****************/ + + leftMenuNameClick(obj) { + if(!obj) + return console.log("No obj"); + + var nodeButton = null; + var nodeBlock = null; + var node = null; + + // console.log(button); + + if(obj.parentNode) { + node = obj; + } + else if(obj?.target?.parentNode) { + node = obj?.target; + } + + var mid = node.getAttribute("mid"); + + if(!mid) + console.log("Can't get mid"); + + if(!this.leftMenu.list[mid]) + console.log("Invalid mid"); + + nodeBlock = this.leftMenu.list[mid].nodeBlock; + nodeButton = this.leftMenu.list[mid].nodeButt; + + if(!nodeBlock) + return console.log("Invalid nodeBlock"); + + if(!nodeBlock.classList) + return console.log("Invalid nodeBlock.classList"); + + if(!nodeBlock.classList.contains("leftMenuBlock")) + return console.log("nodeBlock is not leftMenuBlock"); + + if(nodeBlock.classList.contains("leftMenuBlockHide")) { + nodeBlock.classList.remove("leftMenuBlockHide"); + nodeButton.innerHTML = "-"; + } + else { + nodeBlock.classList.add("leftMenuBlockHide"); + nodeButton.innerHTML = "+"; + } + + return; + } + + leftMenuItemLinkRend(obj, parn) { + var link = dcrt({ + tag : "a" , + parn : parn , + id : obj?.id , + class: "leftMenuLink" , + href : obj.href , + html : obj.html + }); + } + + leftMenuItemRend(obj, parn) { + if(obj.type == "link") + return this.leftMenuItemLinkRend(obj, parn); + + var cli = null; + + if(this[obj.func]) { + cli = this[obj.func].bind(this, obj.parm); + } + else { + console.log("Invalid func '" + obj.func + "'"); + } + + var cont = null; + + if(obj.parn) { + cont = dcrt({ + tag : "div", + parn : parn, + id : obj.parn + }); + } + else { + cont = parn; + } + + var but = dcrt({ + tag : "button" , + parn : cont , + id : obj?.id , + class: "leftMenuButton" , + click: cli , + html : obj.html + }); + + // but.setAttribute("onclick", "window.page." + obj.list[i].func + "();"); + } + + leftMenuBlockRend(obj) { + if(!this.leftMenuNodeRoot) { + return console.log("No .leftMenuNodeRoot"); + } + + var id = this.leftMenu.list.length; + + var nodeBlock = dcrt({ + tag : "div", + parn : this.leftMenuNodeRoot, + class: "leftMenuBlock" + }); + + if(!obj.view) + nodeBlock.classList.add("leftMenuBlockHide"); + + var nodeButt = dcrt({ + mid : id , + tag : "button" , + parn : nodeBlock , + class: "leftMenuBlockNameButton", + click: this.leftMenuNameClick.bind(this), + html : "+" + }); + + var nodeName = dcrt({ + mid : id , + tag : "button" , + parn : nodeBlock , + class: "leftMenuBlockName" , + click: this.leftMenuNameClick.bind(this), + html : obj.name + }); + + var nodeBody = dcrt({ + tag : "div", + parn : nodeBlock, + class: "leftMenuBlockBody" + }); + + this.leftMenu.list[id] = { + nodeBlock : nodeBlock , + nodeButt : nodeButt , + nodeName : nodeName , + nodeBody : nodeBody + } + + for(var i = 0; i < obj.list.length; i++) { + this.leftMenuItemRend(obj.list[i], nodeBody); + } + + return; + } + + leftMenuReq() { + this.reqSnip("leftMenu"); + } + + leftMenuRcv(obj) { + this.leftMenuNodeRoot = document.getElementById("leftMenu"); + + if(!this.leftMenuNodeRoot) { + return console.log("No .leftMenuNodeRoot"); + } + + this.leftMenuNodeRoot.innerHTML = ""; + + this.leftMenu = { + list: [] + } + + if(!obj.list) { + return console.log("No obj.list"); + } + + for(var i = 0; i < obj.list.length; i++) { + this.leftMenuBlockRend(obj.list[i]); + } + + if(obj.defaultPage) { + var parm = {}; + + if(obj.defaultPageParm) + parm = obj.defaultPageParm; + + this.pageContent = obj.defaultPage; + + this.contentReq(obj.defaultPage, parm); + } + } + +/****************/ + + swoTaskListRcv(obj) { + console.log(obj); + } + + swoTaskListReq() { + this.reqSnip("swoTaskList"); + } + + swoLockListRcv(obj) { + console.log(obj); + } + + swoLockListReq() { + this.reqSnip("swoLockList"); + } + + swoSetLogFileNameReq() { + this.reqSnip("swoSetLogFileName"); + } + + swoSetLogFileNameRcv(obj) { + console.log(obj); + } + +/****************/ + + cmpSwoTaskListInit() { + this.cmpSwoTaskList = []; + + this.cmpSwoTaskListNode = document.getElementById("cmpSwoTaskList"); + + if(!this.cmpSwoTaskListNode) { + console.log("No cmpSwoTaskList"); + } + + return; + } + + cmpSwoTaskListRendItem(name, obj) { + if(!this.cmpSwoTaskListNode) { + return; + } + + this.cmpSwoTaskList[name].node = dcrt({ + tag : "span", + parn : this.cmpSwoTaskListNode, + html : "[" + obj.snip + "]" + }); + + return; + } + + cmpSwoTaskListProc(obj) { + // if(!this.cmpSwoTaskListNode) { + // return 1; + // } + + var name = obj.task.task; + + if(obj.task.stat == "work") { + var tbj = { + snip: obj.snip, + node: null + }; + + this.cmpSwoTaskList[name] = tbj; + + // console.log("Append cmpSwoTaskList node " + name); + this.cmpSwoTaskListRendItem(name, obj); + + return 1; + } + + if(obj.task.stat == "complete") { + if(this.cmpSwoTaskList[name]?.node) { + // console.log("Remove cmpSwoTaskList node " + name + " by complete"); + this.cmpSwoTaskList[name].node.remove(); + } + + return 0; + } + + if(obj.task.stat == "lock") { + if(this.cmpSwoTaskList[name]?.node) { + // console.log("Remove cmpSwoTaskList node " + name + " by lock"); + this.cmpSwoTaskList[name].node.remove(); + } + + // console.log(obj); + + var cause = obj?.lock?.cause; + + if(cause) { + alert("Заблокировано: " + cause); + } + else { + alert("Заблокировано"); + } + + return 0; + } + +// if(obj.task.stat == "error") { +// ; +// +// return 0; +// } + + console.log("Unknown obj.task.stat: " + obj.task.stat); + console.log(obj); + + return; + } + + cmpOnLoad() { + console.log("OnLoad") + + this.nodeDataHeadRoot = document.getElementById("tblheadroot"); + this.nodeDataBodyRoot = document.getElementById("tblbodyroot"); + + dcrt({ + tag : "h4", + html : "Default page title", + style: "margin:4px;", + parn : this.nodeDataHeadRoot + }); + + dcrt({ + tag : "span", + html : "Default page data", + parn : this.nodeDataBodyRoot + }); + } + + cmpSshTestReq() { + this.nodeDataHeadRoot.innerHTML = ""; + this.nodeDataBodyRoot.innerHTML = ""; + + if(!this.nodeSshTestHead) { + this.nodeSshTestHead = dcrt({ + tag : "h4", + style: "margin:4px;", + html : "Test SSH", + parn : this.nodeDataHeadRoot + }); + + this.nodeSshTestBody = dcrt({ + tag : "tbody" + }); + + this.nodeSshTestRoot = dcrt({ + tag : "table", + parn : this.nodeDataBodyRoot, + child: [ this.nodeSshTestBody ] + }); + } + else { + this.nodeDataHeadRoot.appendChild(this.nodeSshTestHead) + this.nodeDataBodyRoot.appendChild(this.nodeSshTestRoot) + this.nodeSshTestBody.innerHTML = ""; + } + + this.reqSnip("cmpSshTest"); + } + + cmpSshTestRcv(obj) { + dcrt({ + tag : "tr", + parn : this.nodeSshTestBody, + child: [{ + tag : "td", + html : obj.string + }] + }); + } + + cmpHttpTestReq() { + this.nodeDataHeadRoot.innerHTML = ""; + this.nodeDataBodyRoot.innerHTML = ""; + + if(!this.nodeHttpTestHead) { + this.nodeHttpTestHead = dcrt({ + tag : "h4", + style: "margin:4px;", + html : "Test HTTP", + parn : this.nodeDataHeadRoot + }); + + this.nodeHttpTestBody = dcrt({ + tag : "tbody" + }); + + this.nodeHttpTestRoot = dcrt({ + tag : "table", + parn : this.nodeDataBodyRoot, + child: [ this.nodeHttpTestBody ] + }); + } + else { + this.nodeDataHeadRoot.appendChild(this.nodeHttpTestHead) + this.nodeDataBodyRoot.appendChild(this.nodeHttpTestRoot) + this.nodeHttpTestBody.innerHTML = ""; + } + + this.reqSnip("cmpHttpTest"); + } + + cmpHttpTestRcv(obj) { + if(obj.status == "error") { + dcrt({ + tag : "tr", + parn : this.nodeHttpTestBody, + child: [{ + tag : "td", + html : obj.error + }] + }); + + return; + } + + if(obj.status == "request") { + dcrt({ + tag : "tr", + parn : this.nodeHttpTestBody, + child: [{ + tag : "td", + html : obj.request + }] + }); + + return; + } + + if(obj.status == "response") { + dcrt({ + tag : "tr", + parn : this.nodeHttpTestBody, + child: [{ + tag : "td", + child: [{ + tag : "pre", + html : JSON.stringify(obj.response, null, 2) + }] + }] + }); + + return; + } + + dcrt({ + tag : "tr", + parn : this.nodeHttpTestBody, + child: [{ + tag : "td", + html : "Unknown status: '" + obj.status + "'" + }] + }); + + console.log(obj.status, obj) + + return; + } + + // END class + } diff --git a/static/cmpSwoUtil.js b/static/cmpSwoUtil.js new file mode 100644 index 0000000..01e1a5f --- /dev/null +++ b/static/cmpSwoUtil.js @@ -0,0 +1,343 @@ + + function unixtimeToYYYYMMDD(s) { + var n; + + if(s) + n = new Date(s * 1000); + else + n = new Date(); + + var m = n.getMonth()+1; + var d = n.getDate(); + + return (n.getFullYear()) + "-" + (m < 10 ? ("0" + m) : m) + "-" + (d < 10 ? ("0" + d) : d); + } + + function unixtimeToHHMMSS(s) { + var n; + + if(s) + n = new Date(s * 1000); + else + n = new Date(); + + var h = n.getHours(); + var m = n.getMinutes(); + var s = n.getSeconds(); + + return (h < 10 ? ("0" + h) : h) + ":" + (m < 10 ? ("0" + m) : m) + ":" + (s < 10 ? ("0" + s) : s); + } + + function checkTZ(z) { + if((new Date()).getTimezoneOffset()) + return 0; + + if(z) + console.log("Auto correct TimeZone"); + + return 12 * 60 * 60; + } + + function setNodeText(nn, obj, idx, q) { + var n = null; + + if(typeof q == "undefined") + q = 1; + + switch(typeof nn) { + case "string": + n = document.getElementById(nn); + break; + + case "object": + if(nn.nodeType != 1) + return console.log("Invalid node object"); + + n = nn; + + break; + + default: + return console.log("Invalid node type"); + } + + if(!n) { + if(q == 1) + return console.log("No node '" + nn + "'"); + + return; + } + + if(!obj[idx]) + return; + + n.innerHTML = obj[idx]; + + return; + } + + + function dpst(no, p) { + var n = null; + + do { + if(p.before) { + n = p.before; + break; + } + + if(p.after) { + n = p.after; + break; + } + + if(p.parn) { + n = p.parn; + break; + } + + return; + } while(0); + + if(typeof(n) == "string") { + n = document.getElementById(n); + + if(!n) + return false; + } + + if(n.nodeType !== 1) { + console.log("Invalid node type"); + return false; + } + + if(p.before) { + n.parentNode.insertBefore(no, n); + return true; + } + + if(p.after) { + if(p.after.nextSibling) + n.parentNode.insertBefore(no, n.nextSibling); + else + n.parentNode.appendChild(no); + + return true; + } + + if(p.parn) { + n.appendChild(no); + return true; + } + + return false; + } + + function dcrt(p) { + var at = ""; + var no = null; + var spc = { + "tag" : 1, + "html" : 1, + "parn" : 1, + "child" : 1, + "class" : 1, + + "after" : 1, + "before" : 1, + + "click" : 1, + "keyup" : 1, + "keypress" : 1, + "change" : 1, + "load" : 1, + + "src" : 1 + } + + if(!p.tag) { + console.log("Empty .tag"); + return null; + } + + no = document.createElement(p.tag); + + for(at in p) { + if(spc[at]) + continue; + + no.setAttribute(at, p[at]); + } + + if(p.class) + no.className = p.class; + + dpst(no, p); + + if(p.html) { + no.innerHTML = p.html; + } + + if(p.click) { + no.addEventListener("click", p.click, false); + } + + if(p.keyup) { + no.addEventListener("keyup", p.keyup, false); + } + + if(p.keypress) { + no.addEventListener("keypress", p.keypress, false); + } + + if(p.change) { + no.addEventListener("change", p.change, false); + } + + if(p.load) { + no.addEventListener("load", p.load, false); + // no.onload = p.load; + } + + if(p.src) { + no.setAttribute("src", p.src); + // no.src = p.src; + } + + if(!p.child) + return no; + + var ch = null; + + for(at = 0; at < p.child.length; at++) { + if(!p.child[at]) + continue; + + if(!p.child[at].nodeType) + ch = dcrt(p.child[at]); + else + ch = p.child[at] + + if(!ch) + continue; + + no.appendChild(ch); + } + + return no; + } + + class diag { + constructor(parm) { + this.buildBody(parm); + } + + buildBody(parm) { + var top = 0.8 * (window.innerHeight- 480)/2; + var left = 1.0 * (window.innerWidth - 640)/2; + + this.diagRoot = dcrt({ + tag : "div", + parn : "body", + id : "diag", + style: "display:none; top:"+top+"px; left:"+left+"px;" + }); + + this.diagHead = dcrt({ + tag : "div", + style: "position:absolute; top:0px; left:-2px; right:-2px; height:20px; background:#000; padding:10px; font-weight:bold;", + html : "DIALOG", + parn : this.diagRoot + }); + + this.diagBody = dcrt({ + tag : "div", + style: "position:absolute; left:0; right:0; top:40px; bottom:40px; padding:6px; border:0px solid white;", + parn : this.diagRoot, + // child: parm.child + }); + + this.diagFloor= dcrt({ + tag : "div", + style: "position:absolute; left:-2px; right:-2px; bottom:0px; height:20px; background:#000; padding:10px;", + parn : this.diagRoot + }); + + do { + if(!parm.head) + break; + + if(!parm.head.html) + break; + + this.diagHead.innerHTML = parm.head.html; + } while(0); + + do { + if(!parm.body) + break; + + if(!parm.body.node) + break; + + parm.body.node.parn = this.diagBody; + + dcrt(parm.body.node); + } while(0); + + do { + if(!parm.floor) + break; + + if(!parm.floor.node) + break; + + parm.floor.node.parn = this.diagFloor; + + dcrt(parm.floor.node); + } while(0); + + this.diagHead.addEventListener('mousedown', this.diagDraggable.bind(this), false); + } + + diagDraggable(e) { + var e = e || window.event; + + if(window.diag) + window.diag.diagRoot.style.zIndex = 49; + + window.diag = this; + + this.diagInnerX = e.clientX + window.pageXOffset - this.diagRoot.offsetLeft; + this.diagInnerY = e.clientY + window.pageYOffset - this.diagRoot.offsetTop; + + this.diagHandMove = this.diagMove .bind(this); + this.diagHandMouseUp = this.diagMouseUp.bind(this); + + window.addEventListener('mousemove', this.diagHandMove , false); + window.addEventListener('mouseup' , this.diagHandMouseUp, true); + } + + diagMouseUp(e) { + window.removeEventListener('mousemove', this.diagHandMove); + window.removeEventListener('mouseup' , this.diagHandMouseUp); + } + + diagMove(e) { + this.diagRoot.style.zIndex = 50; + this.diagRoot.style.position = 'fixed'; + this.diagRoot.style.left = e.clientX + window.pageXOffset - this.diagInnerX + 'px'; + this.diagRoot.style.top = e.clientY + window.pageYOffset - this.diagInnerY + 'px'; + } + + show() { + this.diagRoot.style.display = "block"; + } + + hide() { + this.diagRoot.style.display = "none"; + } + + // CLASS + }; + diff --git a/static/default.css b/static/default.css new file mode 100644 index 0000000..84d335a --- /dev/null +++ b/static/default.css @@ -0,0 +1,333 @@ + +body { + color:#ccc; + background-color:#222; + font-size: 20px; + font-family: monospace; +} + +@media (display-mode: fullscreen) { + body { + color:#ccc; + background-color:#444; + } +} + + +input[type="checkbox"]:checked, +input[type="checkbox"]:not(:checked), +input[type="radio"]:checked, +input[type="radio"]:not(:checked) +{ + position: absolute; + left: -9999px; +} +input[type="checkbox"]:checked + label, +input[type="checkbox"]:not(:checked) + label, +input[type="radio"]:checked + label, +input[type="radio"]:not(:checked) + label { + display: inline-block; + position: relative; + padding-left: 28px; + line-height: 20px; + cursor: pointer; +} +input[type="checkbox"]:checked + label:before, +input[type="checkbox"]:not(:checked) + label:before, +input[type="radio"]:checked + label:before, +input[type="radio"]:not(:checked) + label:before { + content: ""; + position: absolute; + left: 0px; + top: 0px; + width: 18px; + height: 18px; + border: 1px solid green; + background-color: #444; +} +input[type="checkbox"]:checked + label:before, +input[type="checkbox"]:not(:checked) + label:before { + border-radius: 4px; +} +input[type="radio"]:checked + label:before, +input[type="radio"]:not(:checked) + label:before { + border-radius: 100%; +} +input[type="checkbox"]:checked + label:after, +input[type="checkbox"]:not(:checked) + label:after, +input[type="radio"]:checked + label:after, +input[type="radio"]:not(:checked) + label:after { + content: ""; + position: absolute; + -webkit-transition: all 0.2s ease; + -moz-transition: all 0.2s ease; + -o-transition: all 0.2s ease; + transition: all 0.2s ease; +} +input[type="checkbox"]:checked + label:after, +input[type="checkbox"]:not(:checked) + label:after { + left: 3px; + top: 4px; + width: 10px; + height: 5px; + border-radius: 1px; + border-left: 4px solid #ccc; + border-bottom: 4px solid #ccc; + -webkit-transform: rotate(-45deg); + -moz-transform: rotate(-45deg); + -o-transform: rotate(-45deg); + -ms-transform: rotate(-45deg); + transform: rotate(-45deg); +} +input[type="radio"]:checked + label:after, +input[type="radio"]:not(:checked) + label:after { + left: 5px; + top: 5px; + width: 10px; + height: 10px; + border-radius: 100%; + background-color: #e145a3; +} +input[type="checkbox"]:not(:checked) + label:after, +input[type="radio"]:not(:checked) + label:after { + opacity: 0; +} +input[type="checkbox"]:checked + label:after, +input[type="radio"]:checked + label:after { + opacity: 1; +} + + + + +table { + width:100%; + padding: 4px; +} + +table tr.odd:nth-child(2n) { + background: #2f2f2f; +} + +table tr.odd:hover { + background: #282828; +} + +table tr td { + padding:4px; +} + +a { color: #37a4a4; text-decoration:none; } +a:visited { color: #37a4a4; text-decoration:none; } + +input[type=text] { + background: #333; + color:#eee; +} + +input[type=button] { + padding:4px 14px 4px 14px; + border:1px solid grey; + border-radius:4px; + background: #333; + color:#eee; +} + +.headWord { + display:inline-block; + overflow:hidden; +} + +.lftButn { + padding:4px 14px 4px 14px; + border:1px solid grey; + border-radius:4px; + background: #333; + color:#eee; + display:block; + width:100%; + margin:4px; +} + +/*********/ + +#page { + z-index:20; + position:fixed; + top:32px; + left:2px; + right:2px; + bottom:0; + border:1px solid #000; + border-radius:4px; + background:#222; + padding:6px; +} + +#pageBody { + border:0px solid red; + overflow:auto; + width:100%; + height:100%; +} + +#topRight { + z-index:10; + position:fixed; + background:#000; + top:-4px; + left:76%; + right:80px; + bottom:0px; + overflow:auto; + border:1px solid #000; + border-radius:4px; + padding:8px; + opacity:0.8; +} + +#topRight:hover { + z-index:50; + border:1px solid green; +} + +#topCent { + z-index:10; + position:fixed; + background:#222; + top:0; + left:20%; + right:20%; + border:1px solid #222; + padding:8px; +} + +#topCent:hover { + z-index:50; +} + +#topLeft { + z-index:10; + position:fixed; + background:#000; + top:-6px; + left:-2px; + right:80%; + border:1px solid #444; + padding:8px; +} + +#topLeft:hover { + z-index:50; + border:1px solid green; +} + +/*******/ + +#diag { + z-index:50; + position:fixed; + border:1px solid red; + border-radius:8px; + background-color:#222; + top:20px; + left:20px; + min-width:640px; + min-height:480px; + overflow:hidden; +} + +/*******/ + +#cmpKernelVanilaNodeRoot { + border:1px solid grey; + position:absolute; + top:16px; + right:4px; + width:300px; + height:90px; + overflow:hidden; +} + +#cmpKernelVanilaTitle { + padding-left:32px; + background-image: url(/go-prefix/static/img/tux-favicon.png); + background-repeat:no-repeat; + background-size:16px; + background-position: 8px 2px; +} + +#cmpKernelVanilaHead { + font-size:16px; + border-bottom:1px solid grey; +} + +#cmpKernelVanilaHeadButton { + border:0px solid grey; + position:absolute; + top:0; + right:-24px; + + padding:0px; + padding-right:0px; + margin:0; + background-color:transparent; + color:white; + font-size:16px; +} +#cmpKernelVanilaNodeRoot:hover #cmpKernelVanilaHeadButton { + right:8px; + transform: rotate(-180deg); + transition: right 1s, transform 1s; +} + +#cmpTinkoffCurrencyNodeRoot { + border:1px solid grey; + position:absolute; + top:110px; + right:4px; + width:300px; + height:90px; + overflow:hidden; +} + +#cmpTinkoffCurrencyTitle { + padding-left:32px; + background-image: url(/go-prefix/static/img/tkf-favicon-32x32.png); + background-repeat:no-repeat; + background-size:16px; + background-position: 8px 2px; +} + +#cmpTinkoffCurrencyHead { + font-size:16px; + border-bottom:1px solid grey; +} + +#cmpTinkoffCurrencyHeadButton { + border:0px solid grey; + position:absolute; + top:0; + right:-24px; + + padding:0px; + padding-right:0px; + margin:0; + background-color:transparent; + color:white; + font-size:16px; +} +#cmpTinkoffCurrencyNodeRoot:hover #cmpTinkoffCurrencyHeadButton { + right:8px; + transform: rotate(-270deg); + transition: right 1s, transform 1s; +} + +#cmpTinkoffCurrencyButton { + width: 100%; + height: 100%; + background-image: url(/go-prefix/static/img/tkf-logo-tinkoff.ru.png); + background-repeat:no-repeat; + background-size:32px; + background-position: 8px 2px; +} diff --git a/static/default.js b/static/default.js new file mode 100644 index 0000000..6ddc1c3 --- /dev/null +++ b/static/default.js @@ -0,0 +1,299 @@ + +// import { modTrm } from "/go-prefix/static/modTrm.js"; +// import { modYdl } from "/go-prefix/static/modYdl.js"; +// import { modNeverc } from "/go-prefix/static/modNeverc.js"; + + class cmpPage extends cmpSwoPage { + constructor() { + super(); + +// Object.assign(this, modTrm); +// Object.assign(this, modYdl); +// Object.assign(this, modNeverc); + + { + this.tblheadroot = document.getElementById("tblheadroot"); + this.tblbodyroot = document.getElementById("tblbodyroot"); + + this.tblbody = document.getElementById("tblbody"); + } + + // this.trmInitOnce(); + // this.ydlInitOnce(); + + this.cmpSwoOnConnect.push( this.timeReStyle.bind(this, "connect") ); + this.cmpSwoOnConnect.push( this.leftMenuReq.bind(this) ); + // this.cmpSwoOnConnect.push( this.trmConnect.bind(this) ); + // this.cmpSwoOnConnect.push( this.ydlConnect.bind(this) ); + this.cmpSwoOnDisconnect.push( this.timeReStyle.bind(this, "disconnect") ); + + this.cmpSwoPageInit(); + + this.offLineInit(); + + ; + } + + + setHeadHeight(ii) { + this.tblheadroot.style.bottom = ii + "px"; + this.tblbodyroot.style.top = (ii+1) + "px"; + } + + setHead(node) { + if(this.tblheadview) + this.tblheadview.style.display = "none"; + + this.tblheadview = node; + this.tblheadroot.appendChild(node); + this.tblheadview.style.display = ""; + } + + setBody(node) { + if(this.tblbodyview) + this.tblbodyview.style.display = "none"; + + this.tblbodyview = node; + this.tblbodyroot.appendChild(node); + this.tblbodyview.style.display = ""; + } + + // Util + + setNode(nn, obj, idx) { + var n = document.getElementById(nn); + + if(!n) + return console.log("No node '" + nn + "'"); + + n.innerHTML = obj[idx]; + + return; + } + + // Logic + + contentReq(page, parm) { + console.log("contentReq", page); + + if(page == "trmShow") { + this.trmShow(); + } + } + + + + + rndTabHead(parn, hdr) { + var hdrTb = document.getElementById("tblhtr"); + + if(!hdrTb) + return; + + hdrTb.innerHTML = ""; + + for(var n = parn?.firstChild?.firstChild, i = 0; n; n = n.nextSibling, i++) { + var sta = "border-right:1px solid grey; border-bottom:1px solid grey; "; + + if(hdr[i].child) { + sta = ""; + } + + var th = dcrt({ + tag : "th", + parn : hdrTb, + style: sta + "width:" + (n.offsetWidth) + "px;", + child: hdr[i].child ? hdr[i].child : null + }); + + if(hdr[i].html) { + th.innerHTML = hdr[i].html; + } + } + + var n = document.getElementById("tblbodyroot"); + + if(n) { + n.style.top = (hdrTb.offsetHeight + 6) + "px"; + } + + return; + } + + + // + + + // + + getSpeeddialElemReq() { + return this.reqSnip("getSpeeddialElem", null); + } + + getSpeeddialElemRcv(obj) { + var i = 0; + + for(i = 0; i < obj.length; i++) { + this.rndSpeeddialElem(obj[i]); + } + } + + // + + offLineInit() { + if(1) + return; + + this.spdNodeLeftMenu = dcrt({ + tag : "div", + parn : "mainRoot", + style: "width:320px; position:relative; height:calc(100% - 10px); top:5px; left:5px; border:1px solid grey;" + }); + + this.spdNodeLeftMenuScrTop = dcrt({ + tag : "div", + parn : this.spdNodeLeftMenu, + style: "width:100%; margin:0px; height:40px; border-bottom:1px solid grey;" + }); + + this.spdNodeLeftMenuBody = dcrt({ + tag : "div", + parn : this.spdNodeLeftMenu, + style: "width:100%; margin:0px; height:calc(100% - 80px); overflow-x:hidden; scroll-x:auto;" + }) + + this.spdNodeLeftMenuScrBottom = dcrt({ + tag : "div", + parn : this.spdNodeLeftMenu, + style: "width:100%; margin:0px; height:40px; border-top:1px solid grey;" + }); + + return; + } + + rndSpeeddialElem(obj) { + if(obj.type != "local") + return; + + var lab = dcrt({ + tag : "div", + style: "padding:16px;", + html : obj.label ? obj.label : obj.ip4v + }); + + var com = dcrt({ + tag : "div", + style: "color:#888; padding-left:32px; margin-right:32px;", + html : (obj.label ? obj.ip4v : "") + (obj.xport ? (" / " + obj.xport) : "") + }); + + var zzz = null; + + if(obj.xport == "A0") { + zzz = this.rndECtl(); + } + + dcrt({ + tag : "div", + parn : this.spdNodeLeftMenuBody, + style: "position:relative; width:calc(100%-10px); margin:5px; height:100px; border:1px solid grey;", + child: [ + lab, + com, + zzz + ] + }); + } + + rndECtl() { + if(!this.listECtl) + this.listECtl = []; + + var id = this.listECtl.legth; + + this.listECtl[id] = {}; + + this.listECtl[id].nodePoint = dcrt({ + tag : "div", + style: "position:absolute; top:2px; width:0px; height:0px; border-radius:17px; border:16px solid;" + }); + + this.listECtl[id].nodePoint.style.left = "2px"; + this.listECtl[id].nodePoint.style.borderColor = "#a33"; + + this.listECtl[id].nodeLabel = dcrt({ + tag : "div" + }); + + this.listECtl[id].nodeRoot = dcrt({ + tag : "div", + style: "position:absolute; top:32px; right:32px; width:84px; height:36px; border:1px solid grey; border-radius:18px;", + click: this.clickECtl.bind(this, id), + child: [ + this.listECtl[id].nodePoint, + this.listECtl[id].nodeLabel + ] + }); + + return this.listECtl[id].nodeRoot; + } + + clickECtl(id) { + var style = this.listECtl[id].nodePoint.style; + + if(style.left) { + style.left = ""; + style.right = "2px"; + style.borderColor = "#3a3"; + } + else { + style.left = "2px"; + style.right = ""; + style.borderColor = "#a33"; + } + + + return; + } + + //========================== + + urlMod(parm) { + if(!parm?.urlMod.url) + return console.log("No .url"); + + if(!parm?.urlMod.title) + return console.log("No .title"); + + // const nextState = parm?.urlMod.nextState; // { additionalInformation: 'Updated the URL with JS' }; + + // This will create a new entry in the browser's history, without reloading + window.history.pushState(parm?.urlMod.nextState, parm?.urlMod.title, parm.urlMod.url); + + // This will replace the current entry in the browser's history, without reloading + window.history.replaceState(parm?.urlMod.nextState, parm?.urlMod.title, parm.urlMod.url); + + // console.log(window.location) + } + + // END class + } + + + window.diag = null; + function diagMove(e) { + window.diag.diagMove(e); + } + function diagMouseUp(e) { + window.diag.diagRoot.style.zIndex = 50; + window.removeEventListener('mousemove', diagMove, false); + } + + var page = null; + + document.addEventListener("DOMContentLoaded", function(event) { + page = new cmpPage(); + window.page = page; + }); + +// END diff --git a/static/img/favicon.png b/static/img/favicon.png new file mode 100644 index 0000000..7e625c9 Binary files /dev/null and b/static/img/favicon.png differ diff --git a/static/img/tkf-favicon-32x32.png b/static/img/tkf-favicon-32x32.png new file mode 100644 index 0000000..825af2e Binary files /dev/null and b/static/img/tkf-favicon-32x32.png differ diff --git a/static/img/tux-favicon.png b/static/img/tux-favicon.png new file mode 100644 index 0000000..e5fba67 Binary files /dev/null and b/static/img/tux-favicon.png differ diff --git a/static/img/umbrella-black.png b/static/img/umbrella-black.png new file mode 100644 index 0000000..a1d981e Binary files /dev/null and b/static/img/umbrella-black.png differ diff --git a/static/img/umbrella-color.png b/static/img/umbrella-color.png new file mode 100644 index 0000000..10b21c3 Binary files /dev/null and b/static/img/umbrella-color.png differ diff --git a/static/index.html b/static/index.html new file mode 100644 index 0000000..5b93bd1 --- /dev/null +++ b/static/index.html @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + +
+
+ + +
+   + +   + + +
USER
+
+
+
+
+
+ +
+
+
+ +
diff --git a/static/index2.html b/static/index2.html new file mode 100644 index 0000000..5b93bd1 --- /dev/null +++ b/static/index2.html @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + +
+
+ + +
+   + +   + + +
USER
+
+
+
+
+
+ +
+
+
+ +