mirror of
https://github.com/wavetermdev/waveterm.git
synced 2024-12-22 16:48:23 +01:00
updates, switch to new fecmd packet to run all UI commands through
This commit is contained in:
parent
5b1eb383e3
commit
6807547ff7
@ -10,7 +10,6 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@ -18,7 +17,7 @@ import (
|
||||
"github.com/google/uuid"
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/scripthaus-dev/mshell/pkg/packet"
|
||||
"github.com/scripthaus-dev/sh2-server/pkg/cmdrunner"
|
||||
"github.com/scripthaus-dev/sh2-server/pkg/remote"
|
||||
"github.com/scripthaus-dev/sh2-server/pkg/scbase"
|
||||
"github.com/scripthaus-dev/sh2-server/pkg/scpacket"
|
||||
@ -153,69 +152,6 @@ func HandleGetAllSessions(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// params: name
|
||||
func HandleCreateSession(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin"))
|
||||
w.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||
w.Header().Set("Vary", "Origin")
|
||||
w.Header().Set("Cache-Control", "no-cache")
|
||||
qvals := r.URL.Query()
|
||||
name := qvals.Get("name")
|
||||
sessionId, err := sstore.InsertSessionWithName(r.Context(), name)
|
||||
if err != nil {
|
||||
WriteJsonError(w, fmt.Errorf("inserting session: %w", err))
|
||||
return
|
||||
}
|
||||
session, err := sstore.GetSessionById(r.Context(), sessionId)
|
||||
if err != nil {
|
||||
WriteJsonError(w, fmt.Errorf("getting new session: %w", err))
|
||||
return
|
||||
}
|
||||
WriteJsonSuccess(w, session)
|
||||
return
|
||||
}
|
||||
|
||||
// params: sessionid, name
|
||||
func HandleCreateWindow(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin"))
|
||||
w.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||
w.Header().Set("Vary", "Origin")
|
||||
w.Header().Set("Cache-Control", "no-cache")
|
||||
qvals := r.URL.Query()
|
||||
sessionId := qvals.Get("sessionid")
|
||||
if _, err := uuid.Parse(sessionId); err != nil {
|
||||
WriteJsonError(w, fmt.Errorf("invalid sessionid: %w", err))
|
||||
return
|
||||
}
|
||||
name := qvals.Get("name")
|
||||
fmt.Printf("insert-window %s\n", name)
|
||||
return
|
||||
}
|
||||
|
||||
// params: sessionid, name, activate
|
||||
func HandleCreateScreen(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin"))
|
||||
w.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||
w.Header().Set("Vary", "Origin")
|
||||
w.Header().Set("Cache-Control", "no-cache")
|
||||
qvals := r.URL.Query()
|
||||
sessionId := qvals.Get("sessionid")
|
||||
if _, err := uuid.Parse(sessionId); err != nil {
|
||||
WriteJsonError(w, fmt.Errorf("invalid sessionid: %w", err))
|
||||
return
|
||||
}
|
||||
name := qvals.Get("name")
|
||||
activateStr := qvals.Get("activate")
|
||||
activate := activateStr != ""
|
||||
screenId, err := sstore.InsertScreen(r.Context(), sessionId, name, activate)
|
||||
if err != nil {
|
||||
WriteJsonError(w, fmt.Errorf("inserting screen: %w", err))
|
||||
return
|
||||
}
|
||||
WriteJsonSuccess(w, screenId)
|
||||
return
|
||||
}
|
||||
|
||||
// params: [none]
|
||||
func HandleGetRemotes(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin"))
|
||||
@ -304,7 +240,7 @@ func HandleGetPtyOut(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func WriteJsonError(w http.ResponseWriter, errVal error) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(500)
|
||||
w.WriteHeader(200)
|
||||
errMap := make(map[string]interface{})
|
||||
errMap["error"] = errVal.Error()
|
||||
barr, _ := json.Marshal(errMap)
|
||||
@ -329,11 +265,6 @@ func WriteJsonSuccess(w http.ResponseWriter, data interface{}) {
|
||||
return
|
||||
}
|
||||
|
||||
type runCommandResponse struct {
|
||||
Line *sstore.LineType `json:"line"`
|
||||
Cmd *sstore.CmdType `json:"cmd"`
|
||||
}
|
||||
|
||||
func HandleRunCommand(w http.ResponseWriter, r *http.Request) {
|
||||
defer func() {
|
||||
r := recover()
|
||||
@ -360,127 +291,15 @@ func HandleRunCommand(w http.ResponseWriter, r *http.Request) {
|
||||
WriteJsonError(w, fmt.Errorf("error decoding json: %w", err))
|
||||
return
|
||||
}
|
||||
if _, err = uuid.Parse(commandPk.SessionId); err != nil {
|
||||
WriteJsonError(w, fmt.Errorf("invalid sessionid '%s': %w", commandPk.SessionId, err))
|
||||
return
|
||||
}
|
||||
resp, err := ProcessFeCommandPacket(r.Context(), &commandPk)
|
||||
update, err := cmdrunner.HandleCommand(r.Context(), &commandPk)
|
||||
if err != nil {
|
||||
WriteJsonError(w, err)
|
||||
return
|
||||
}
|
||||
WriteJsonSuccess(w, resp)
|
||||
WriteJsonSuccess(w, update)
|
||||
return
|
||||
}
|
||||
|
||||
func ProcessFeCommandPacket(ctx context.Context, pk *scpacket.FeCommandPacketType) (*runCommandResponse, error) {
|
||||
// parse metacmd
|
||||
commandStr := strings.TrimSpace(pk.CmdStr)
|
||||
if commandStr == "" {
|
||||
return nil, fmt.Errorf("invalid emtpty command")
|
||||
}
|
||||
metaCmd := ""
|
||||
metaSubCmd := ""
|
||||
if commandStr == "cd" || strings.HasPrefix(commandStr, "cd ") {
|
||||
metaCmd = "cd"
|
||||
commandStr = strings.TrimSpace(commandStr[2:])
|
||||
} else if commandStr[0] == '/' {
|
||||
spaceIdx := strings.Index(commandStr, " ")
|
||||
if spaceIdx == -1 {
|
||||
metaCmd = commandStr[1:]
|
||||
commandStr = ""
|
||||
} else {
|
||||
metaCmd = commandStr[1:spaceIdx]
|
||||
commandStr = strings.TrimSpace(commandStr[spaceIdx+1:])
|
||||
}
|
||||
colonIdx := strings.Index(metaCmd, ":")
|
||||
if colonIdx != -1 {
|
||||
metaCmd, metaSubCmd = metaCmd[0:colonIdx], metaCmd[colonIdx+1:]
|
||||
}
|
||||
if metaCmd == "" {
|
||||
return nil, fmt.Errorf("invalid command, got bare '/', with no command")
|
||||
}
|
||||
}
|
||||
|
||||
// execute metacmd
|
||||
if metaCmd == "comment" {
|
||||
text := commandStr
|
||||
rtnLine, err := sstore.AddCommentLine(ctx, pk.SessionId, pk.WindowId, pk.UserId, text)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &runCommandResponse{Line: rtnLine}, nil
|
||||
} else if metaCmd == "cd" {
|
||||
newDir := commandStr
|
||||
cdPacket := packet.MakeCdPacket()
|
||||
cdPacket.ReqId = uuid.New().String()
|
||||
cdPacket.Dir = newDir
|
||||
localRemote := remote.GetRemoteById(pk.RemoteState.RemoteId)
|
||||
if localRemote == nil {
|
||||
return nil, fmt.Errorf("invalid remote, cannot execute command")
|
||||
}
|
||||
resp, err := localRemote.PacketRpc(ctx, cdPacket)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fmt.Printf("GOT cd RESP: %v\n", resp)
|
||||
return nil, nil
|
||||
} else if metaCmd == "s" || metaCmd == "screen" {
|
||||
err := RunScreenCmd(ctx, pk.SessionId, pk.ScreenId, metaSubCmd, commandStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
} else if metaCmd == "" {
|
||||
cmdId := uuid.New().String()
|
||||
cmd, err := remote.RunCommand(ctx, pk, cmdId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rtnLine, err := sstore.AddCmdLine(ctx, pk.SessionId, pk.WindowId, pk.UserId, cmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &runCommandResponse{Line: rtnLine, Cmd: cmd}, nil
|
||||
} else {
|
||||
fmt.Printf("INVALID metacmd '%s'\n", metaCmd)
|
||||
return nil, fmt.Errorf("Invalid command type /%s", metaCmd)
|
||||
}
|
||||
}
|
||||
|
||||
func RunScreenCmd(ctx context.Context, sessionId string, screenId string, subCmd string, commandStr string) error {
|
||||
if subCmd == "close" {
|
||||
err := sstore.DeleteScreen(ctx, sessionId, screenId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if subCmd != "" {
|
||||
return fmt.Errorf("invalid /screen subcommand '%s'", subCmd)
|
||||
}
|
||||
if commandStr == "" {
|
||||
return fmt.Errorf("usage /screen [screen-name|screen-index|screen-id], no param specified")
|
||||
}
|
||||
screens, err := sstore.GetSessionScreens(ctx, sessionId)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not retreive screens for session=%s", sessionId)
|
||||
}
|
||||
screenNum, err := strconv.Atoi(commandStr)
|
||||
if err == nil {
|
||||
if screenNum < 1 || screenNum > len(screens) {
|
||||
return fmt.Errorf("could not switch to screen #%d (out of range), valid screens 1-%d", screenNum, len(screens))
|
||||
}
|
||||
return sstore.SwitchScreenById(ctx, sessionId, screens[screenNum-1].ScreenId)
|
||||
}
|
||||
for _, screen := range screens {
|
||||
if screen.ScreenId == commandStr || screen.Name == commandStr {
|
||||
return sstore.SwitchScreenById(ctx, sessionId, screen.ScreenId)
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("could not switch to screen '%s' (name/id not found)", commandStr)
|
||||
}
|
||||
|
||||
// /api/start-session
|
||||
// returns:
|
||||
// * userid
|
||||
@ -620,11 +439,8 @@ func main() {
|
||||
gr := mux.NewRouter()
|
||||
gr.HandleFunc("/api/ptyout", HandleGetPtyOut)
|
||||
gr.HandleFunc("/api/get-all-sessions", HandleGetAllSessions)
|
||||
gr.HandleFunc("/api/create-session", HandleCreateSession)
|
||||
gr.HandleFunc("/api/get-window", HandleGetWindow)
|
||||
gr.HandleFunc("/api/get-remotes", HandleGetRemotes)
|
||||
gr.HandleFunc("/api/create-window", HandleCreateWindow)
|
||||
gr.HandleFunc("/api/create-screen", HandleCreateScreen)
|
||||
gr.HandleFunc("/api/run-command", HandleRunCommand).Methods("GET", "POST", "OPTIONS")
|
||||
server := &http.Server{
|
||||
Addr: MainServerAddr,
|
||||
|
367
pkg/cmdrunner/cmdrunner.go
Normal file
367
pkg/cmdrunner/cmdrunner.go
Normal file
@ -0,0 +1,367 @@
|
||||
package cmdrunner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/scripthaus-dev/mshell/pkg/base"
|
||||
"github.com/scripthaus-dev/mshell/pkg/packet"
|
||||
"github.com/scripthaus-dev/sh2-server/pkg/remote"
|
||||
"github.com/scripthaus-dev/sh2-server/pkg/scpacket"
|
||||
"github.com/scripthaus-dev/sh2-server/pkg/sstore"
|
||||
)
|
||||
|
||||
const DefaultUserId = "sawka"
|
||||
|
||||
const (
|
||||
R_Session = 1
|
||||
R_Screen = 2
|
||||
R_Window = 4
|
||||
R_Remote = 8
|
||||
R_SessionOpt = 16
|
||||
R_ScreenOpt = 32
|
||||
R_WindowOpt = 64
|
||||
R_RemoteOpt = 128
|
||||
)
|
||||
|
||||
type resolvedIds struct {
|
||||
SessionId string
|
||||
ScreenId string
|
||||
WindowId string
|
||||
RemoteId string
|
||||
RemoteState *sstore.RemoteState
|
||||
}
|
||||
|
||||
func SubMetaCmd(cmd string) string {
|
||||
switch cmd {
|
||||
case "s":
|
||||
return "screen"
|
||||
case "w":
|
||||
return "window"
|
||||
case "r":
|
||||
return "run"
|
||||
case "c":
|
||||
return "comment"
|
||||
case "e":
|
||||
return "eval"
|
||||
default:
|
||||
return cmd
|
||||
}
|
||||
}
|
||||
|
||||
func firstArg(pk *scpacket.FeCommandPacketType) string {
|
||||
if len(pk.Args) == 0 {
|
||||
return ""
|
||||
}
|
||||
return pk.Args[0]
|
||||
}
|
||||
|
||||
func resolveBool(arg string, def bool) bool {
|
||||
if arg == "" {
|
||||
return def
|
||||
}
|
||||
if arg == "0" || arg == "false" {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func resolveSessionScreen(ctx context.Context, sessionId string, screenArg string) (string, error) {
|
||||
screens, err := sstore.GetSessionScreens(ctx, sessionId)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not retreive screens for session=%s", sessionId)
|
||||
}
|
||||
screenNum, err := strconv.Atoi(screenArg)
|
||||
if err == nil {
|
||||
if screenNum < 1 || screenNum > len(screens) {
|
||||
return "", fmt.Errorf("could not resolve screen #%d (out of range), valid screens 1-%d", screenNum, len(screens))
|
||||
}
|
||||
return screens[screenNum-1].ScreenId, nil
|
||||
}
|
||||
for _, screen := range screens {
|
||||
if screen.ScreenId == screenArg || screen.Name == screenArg {
|
||||
return screen.ScreenId, nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("could not resolve screen '%s' (name/id not found)", screenArg)
|
||||
}
|
||||
|
||||
func resolveSessionId(pk *scpacket.FeCommandPacketType) (string, error) {
|
||||
sessionId := pk.Kwargs["session"]
|
||||
if sessionId == "" {
|
||||
return "", nil
|
||||
}
|
||||
if _, err := uuid.Parse(sessionId); err != nil {
|
||||
return "", fmt.Errorf("invalid sessionid '%s'", sessionId)
|
||||
}
|
||||
return sessionId, nil
|
||||
}
|
||||
|
||||
func resolveWindowId(pk *scpacket.FeCommandPacketType, sessionId string) (string, error) {
|
||||
windowId := pk.Kwargs["window"]
|
||||
if windowId == "" {
|
||||
return "", nil
|
||||
}
|
||||
if _, err := uuid.Parse(windowId); err != nil {
|
||||
return "", fmt.Errorf("invalid windowid '%s'", windowId)
|
||||
}
|
||||
return windowId, nil
|
||||
}
|
||||
|
||||
func resolveScreenId(ctx context.Context, pk *scpacket.FeCommandPacketType, sessionId string) (string, error) {
|
||||
screenArg := pk.Kwargs["screen"]
|
||||
if screenArg == "" {
|
||||
return "", nil
|
||||
}
|
||||
if _, err := uuid.Parse(screenArg); err == nil {
|
||||
return screenArg, nil
|
||||
}
|
||||
if sessionId == "" {
|
||||
return "", fmt.Errorf("cannot resolve screen without session")
|
||||
}
|
||||
return resolveSessionScreen(ctx, sessionId, screenArg)
|
||||
}
|
||||
|
||||
func resolveRemote(ctx context.Context, pk *scpacket.FeCommandPacketType, sessionId string, windowId string) (string, *sstore.RemoteState, error) {
|
||||
remoteArg := pk.Kwargs["remote"]
|
||||
if remoteArg == "" {
|
||||
return "", nil, nil
|
||||
}
|
||||
remoteId, state, err := sstore.GetRemoteState(ctx, remoteArg, sessionId, windowId)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("cannot resolve remote '%s': %w", remoteArg, err)
|
||||
}
|
||||
if state == nil {
|
||||
state, err = remote.GetDefaultRemoteStateById(remoteId)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("cannot resolve remote '%s': %w", remoteArg, err)
|
||||
}
|
||||
}
|
||||
return remoteId, state, nil
|
||||
}
|
||||
|
||||
func resolveIds(ctx context.Context, pk *scpacket.FeCommandPacketType, rtype int) (resolvedIds, error) {
|
||||
rtn := resolvedIds{}
|
||||
if rtype == 0 {
|
||||
return rtn, nil
|
||||
}
|
||||
var err error
|
||||
if (rtype&R_Session)+(rtype&R_SessionOpt) > 0 {
|
||||
rtn.SessionId, err = resolveSessionId(pk)
|
||||
if err != nil {
|
||||
return rtn, err
|
||||
}
|
||||
if rtn.SessionId == "" && (rtype&R_Session) > 0 {
|
||||
return rtn, fmt.Errorf("no session")
|
||||
}
|
||||
}
|
||||
if (rtype&R_Window)+(rtype&R_WindowOpt) > 0 {
|
||||
rtn.WindowId, err = resolveWindowId(pk, rtn.SessionId)
|
||||
if err != nil {
|
||||
return rtn, err
|
||||
}
|
||||
if rtn.WindowId == "" && (rtype&R_Window) > 0 {
|
||||
return rtn, fmt.Errorf("no window")
|
||||
}
|
||||
|
||||
}
|
||||
if (rtype&R_Screen)+(rtype&R_ScreenOpt) > 0 {
|
||||
rtn.ScreenId, err = resolveScreenId(ctx, pk, rtn.SessionId)
|
||||
if err != nil {
|
||||
return rtn, err
|
||||
}
|
||||
if rtn.ScreenId == "" && (rtype&R_Screen) > 0 {
|
||||
return rtn, fmt.Errorf("no screen")
|
||||
}
|
||||
}
|
||||
if (rtype&R_Remote)+(rtype&R_RemoteOpt) > 0 {
|
||||
rtn.RemoteId, rtn.RemoteState, err = resolveRemote(ctx, pk, rtn.SessionId, rtn.WindowId)
|
||||
if err != nil {
|
||||
return rtn, err
|
||||
}
|
||||
if rtn.RemoteId == "" && (rtype&R_Remote) > 0 {
|
||||
return rtn, fmt.Errorf("no remote")
|
||||
}
|
||||
}
|
||||
return rtn, nil
|
||||
}
|
||||
|
||||
func HandleCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
|
||||
switch SubMetaCmd(pk.MetaCmd) {
|
||||
case "run":
|
||||
return RunCommand(ctx, pk)
|
||||
|
||||
case "eval":
|
||||
return EvalCommand(ctx, pk)
|
||||
|
||||
case "screen":
|
||||
return ScreenCommand(ctx, pk)
|
||||
|
||||
case "comment":
|
||||
return CommentCommand(ctx, pk)
|
||||
|
||||
case "cd":
|
||||
return CdCommand(ctx, pk)
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid command '/%s', no handler", pk.MetaCmd)
|
||||
}
|
||||
}
|
||||
|
||||
func RunCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
|
||||
ids, err := resolveIds(ctx, pk, R_Session|R_Window|R_Remote)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("/run error: %w", err)
|
||||
}
|
||||
cmdId := uuid.New().String()
|
||||
cmdStr := firstArg(pk)
|
||||
runPacket := packet.MakeRunPacket()
|
||||
runPacket.ReqId = uuid.New().String()
|
||||
runPacket.CK = base.MakeCommandKey(ids.SessionId, cmdId)
|
||||
runPacket.Cwd = ids.RemoteState.Cwd
|
||||
runPacket.Env = nil
|
||||
runPacket.UsePty = true
|
||||
runPacket.TermOpts = &packet.TermOpts{Rows: remote.DefaultTermRows, Cols: remote.DefaultTermCols, Term: remote.DefaultTerm}
|
||||
runPacket.Command = strings.TrimSpace(cmdStr)
|
||||
cmd, err := remote.RunCommand(ctx, cmdId, ids.RemoteId, ids.RemoteState, runPacket)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rtnLine, err := sstore.AddCmdLine(ctx, ids.SessionId, ids.WindowId, DefaultUserId, cmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return sstore.LineUpdate{Line: rtnLine, Cmd: cmd}, nil
|
||||
}
|
||||
|
||||
func EvalCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
|
||||
if len(pk.Args) == 0 {
|
||||
return nil, fmt.Errorf("usage: /eval [command], no command passed to eval")
|
||||
}
|
||||
// parse metacmd
|
||||
commandStr := strings.TrimSpace(pk.Args[0])
|
||||
if commandStr == "" {
|
||||
return nil, fmt.Errorf("/eval, invalid emtpty command")
|
||||
}
|
||||
metaCmd := ""
|
||||
metaSubCmd := ""
|
||||
if commandStr == "cd" || strings.HasPrefix(commandStr, "cd ") {
|
||||
metaCmd = "cd"
|
||||
commandStr = strings.TrimSpace(commandStr[2:])
|
||||
} else if commandStr[0] == '/' {
|
||||
spaceIdx := strings.Index(commandStr, " ")
|
||||
if spaceIdx == -1 {
|
||||
metaCmd = commandStr[1:]
|
||||
commandStr = ""
|
||||
} else {
|
||||
metaCmd = commandStr[1:spaceIdx]
|
||||
commandStr = strings.TrimSpace(commandStr[spaceIdx+1:])
|
||||
}
|
||||
colonIdx := strings.Index(metaCmd, ":")
|
||||
if colonIdx != -1 {
|
||||
metaCmd, metaSubCmd = metaCmd[0:colonIdx], metaCmd[colonIdx+1:]
|
||||
}
|
||||
if metaCmd == "" {
|
||||
return nil, fmt.Errorf("invalid command, got bare '/', with no command")
|
||||
}
|
||||
}
|
||||
if metaCmd == "" {
|
||||
metaCmd = "run"
|
||||
}
|
||||
var args []string
|
||||
if metaCmd == "run" || metaCmd == "comment" {
|
||||
args = []string{commandStr}
|
||||
} else {
|
||||
args = strings.Fields(commandStr)
|
||||
}
|
||||
newPk := &scpacket.FeCommandPacketType{
|
||||
MetaCmd: metaCmd,
|
||||
MetaSubCmd: metaSubCmd,
|
||||
Args: args,
|
||||
Kwargs: pk.Kwargs,
|
||||
}
|
||||
return HandleCommand(ctx, newPk)
|
||||
}
|
||||
|
||||
func ScreenCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
|
||||
if pk.MetaSubCmd == "close" {
|
||||
ids, err := resolveIds(ctx, pk, R_Session|R_Screen)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("/screen:close cannot close screen: %w", err)
|
||||
}
|
||||
err = sstore.DeleteScreen(ctx, ids.SessionId, ids.ScreenId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
if pk.MetaSubCmd == "open" || pk.MetaSubCmd == "new" {
|
||||
ids, err := resolveIds(ctx, pk, R_Session)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("/screen:open cannot open screen: %w", err)
|
||||
}
|
||||
activate := resolveBool(pk.Kwargs["activate"], true)
|
||||
_, err = sstore.InsertScreen(ctx, ids.SessionId, pk.Kwargs["name"], activate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
if pk.MetaSubCmd != "" {
|
||||
return nil, fmt.Errorf("invalid /screen subcommand '%s'", pk.MetaSubCmd)
|
||||
}
|
||||
ids, err := resolveIds(ctx, pk, R_Session)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("/screen cannot switch to screen: %w", err)
|
||||
}
|
||||
firstArg := firstArg(pk)
|
||||
if firstArg == "" {
|
||||
return nil, fmt.Errorf("usage /screen [screen-name|screen-index|screen-id], no param specified")
|
||||
}
|
||||
screenIdArg, err := resolveSessionScreen(ctx, ids.SessionId, firstArg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sstore.SwitchScreenById(ctx, ids.SessionId, screenIdArg)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func CdCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
|
||||
ids, err := resolveIds(ctx, pk, R_Session|R_Window|R_Remote)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("/cd error: %w", err)
|
||||
}
|
||||
newDir := firstArg(pk)
|
||||
cdPacket := packet.MakeCdPacket()
|
||||
cdPacket.ReqId = uuid.New().String()
|
||||
cdPacket.Dir = newDir
|
||||
localRemote := remote.GetRemoteById(ids.RemoteId)
|
||||
if localRemote == nil {
|
||||
return nil, fmt.Errorf("invalid remote, cannot execute command")
|
||||
}
|
||||
resp, err := localRemote.PacketRpc(ctx, cdPacket)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fmt.Printf("GOT cd RESP: %v\n", resp)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func CommentCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
|
||||
ids, err := resolveIds(ctx, pk, R_Session|R_Window)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("/comment error: %w", err)
|
||||
}
|
||||
text := firstArg(pk)
|
||||
if strings.TrimSpace(text) == "" {
|
||||
return nil, fmt.Errorf("cannot post empty comment")
|
||||
}
|
||||
rtnLine, err := sstore.AddCommentLine(ctx, ids.SessionId, ids.WindowId, DefaultUserId, text)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return sstore.LineUpdate{Line: rtnLine}, nil
|
||||
}
|
@ -6,14 +6,11 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/scripthaus-dev/mshell/pkg/base"
|
||||
"github.com/scripthaus-dev/mshell/pkg/packet"
|
||||
"github.com/scripthaus-dev/mshell/pkg/shexec"
|
||||
"github.com/scripthaus-dev/sh2-server/pkg/scpacket"
|
||||
"github.com/scripthaus-dev/sh2-server/pkg/sstore"
|
||||
)
|
||||
|
||||
@ -123,6 +120,21 @@ func GetAllRemoteState() []RemoteState {
|
||||
return rtn
|
||||
}
|
||||
|
||||
func GetDefaultRemoteStateById(remoteId string) (*sstore.RemoteState, error) {
|
||||
remote := GetRemoteById(remoteId)
|
||||
if remote == nil {
|
||||
return nil, fmt.Errorf("remote not found")
|
||||
}
|
||||
if !remote.IsConnected() {
|
||||
return nil, fmt.Errorf("remote not connected")
|
||||
}
|
||||
state := remote.GetDefaultState()
|
||||
if state == nil {
|
||||
return nil, fmt.Errorf("could not get default remote state")
|
||||
}
|
||||
return state, nil
|
||||
}
|
||||
|
||||
func MakeMShell(r *sstore.RemoteType) *MShellProc {
|
||||
rtn := &MShellProc{Lock: &sync.Mutex{}, Remote: r, Status: StatusInit}
|
||||
return rtn
|
||||
@ -168,6 +180,15 @@ func (msh *MShellProc) IsConnected() bool {
|
||||
return msh.Status == StatusConnected
|
||||
}
|
||||
|
||||
func (msh *MShellProc) GetDefaultState() *sstore.RemoteState {
|
||||
msh.Lock.Lock()
|
||||
defer msh.Lock.Unlock()
|
||||
if msh.ServerProc == nil || msh.ServerProc.InitPk == nil {
|
||||
return nil
|
||||
}
|
||||
return &sstore.RemoteState{Cwd: msh.ServerProc.InitPk.HomeDir}
|
||||
}
|
||||
|
||||
func (msh *MShellProc) IsCmdRunning(ck base.CommandKey) bool {
|
||||
msh.Lock.Lock()
|
||||
defer msh.Lock.Unlock()
|
||||
@ -193,30 +214,21 @@ func (msh *MShellProc) SendInput(pk *packet.InputPacketType) error {
|
||||
return msh.ServerProc.Input.SendPacket(dataPk)
|
||||
}
|
||||
|
||||
func convertRemoteState(rs scpacket.RemoteState) sstore.RemoteState {
|
||||
return sstore.RemoteState{Cwd: rs.Cwd}
|
||||
}
|
||||
|
||||
func makeTermOpts() sstore.TermOpts {
|
||||
return sstore.TermOpts{Rows: DefaultTermRows, Cols: DefaultTermCols, FlexRows: true}
|
||||
}
|
||||
|
||||
func RunCommand(ctx context.Context, pk *scpacket.FeCommandPacketType, cmdId string) (*sstore.CmdType, error) {
|
||||
msh := GetRemoteById(pk.RemoteState.RemoteId)
|
||||
func RunCommand(ctx context.Context, cmdId string, remoteId string, remoteState *sstore.RemoteState, runPacket *packet.RunPacketType) (*sstore.CmdType, error) {
|
||||
msh := GetRemoteById(remoteId)
|
||||
if msh == nil {
|
||||
return nil, fmt.Errorf("no remote id=%s found", pk.RemoteState.RemoteId)
|
||||
return nil, fmt.Errorf("no remote id=%s found", remoteId)
|
||||
}
|
||||
if !msh.IsConnected() {
|
||||
return nil, fmt.Errorf("remote '%s' is not connected", msh.Remote.RemoteName)
|
||||
return nil, fmt.Errorf("remote '%s' is not connected", remoteId)
|
||||
}
|
||||
if remoteState == nil {
|
||||
return nil, fmt.Errorf("no remote state passed to RunCommand")
|
||||
}
|
||||
runPacket := packet.MakeRunPacket()
|
||||
runPacket.ReqId = uuid.New().String()
|
||||
runPacket.CK = base.MakeCommandKey(pk.SessionId, cmdId)
|
||||
runPacket.Cwd = pk.RemoteState.Cwd
|
||||
runPacket.Env = nil
|
||||
runPacket.UsePty = true
|
||||
runPacket.TermOpts = &packet.TermOpts{Rows: DefaultTermRows, Cols: DefaultTermCols, Term: DefaultTerm}
|
||||
runPacket.Command = strings.TrimSpace(pk.CmdStr)
|
||||
fmt.Printf("RUN-CMD> %s reqid=%s (msh=%v)\n", runPacket.CK, runPacket.ReqId, msh.Remote)
|
||||
msh.ServerProc.Output.RegisterRpc(runPacket.ReqId)
|
||||
err := shexec.SendRunPacketAndRunData(ctx, msh.ServerProc.Input, runPacket)
|
||||
@ -240,11 +252,11 @@ func RunCommand(ctx context.Context, pk *scpacket.FeCommandPacketType, cmdId str
|
||||
status = sstore.CmdStatusDetached
|
||||
}
|
||||
cmd := &sstore.CmdType{
|
||||
SessionId: pk.SessionId,
|
||||
SessionId: runPacket.CK.GetSessionId(),
|
||||
CmdId: startPk.CK.GetCmdId(),
|
||||
CmdStr: runPacket.Command,
|
||||
RemoteId: msh.Remote.RemoteId,
|
||||
RemoteState: convertRemoteState(pk.RemoteState),
|
||||
RemoteState: *remoteState,
|
||||
TermOpts: makeTermOpts(),
|
||||
Status: status,
|
||||
StartPk: startPk,
|
||||
|
@ -16,13 +16,11 @@ type RemoteState struct {
|
||||
}
|
||||
|
||||
type FeCommandPacketType struct {
|
||||
Type string `json:"type"`
|
||||
SessionId string `json:"sessionid"`
|
||||
ScreenId string `json:"screenid"`
|
||||
WindowId string `json:"windowid"`
|
||||
UserId string `json:"userid"`
|
||||
CmdStr string `json:"cmdstr"`
|
||||
RemoteState RemoteState `json:"remotestate"`
|
||||
Type string `json:"type"`
|
||||
MetaCmd string `json:"metacmd"`
|
||||
MetaSubCmd string `json:"metasubcmd,omitempty"`
|
||||
Args []string `json:"args,omitempty"`
|
||||
Kwargs map[string]string `json:"kwargs,omitempty"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@ -468,3 +468,25 @@ func DeleteScreen(ctx context.Context, sessionId string, screenId string) error
|
||||
MainBus.SendUpdate("", update)
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetRemoteState(ctx context.Context, rname string, sessionId string, windowId string) (string, *RemoteState, error) {
|
||||
var remoteId string
|
||||
var remoteState *RemoteState
|
||||
txErr := WithTx(ctx, func(tx *TxWrap) error {
|
||||
var ri RemoteInstance
|
||||
query := `SELECT * FROM remote_instance WHERE sessionid = ? AND windowid = ? AND name = ?`
|
||||
found := tx.GetWrap(&ri, query, sessionId, windowId, rname)
|
||||
if found {
|
||||
remoteId = ri.RemoteId
|
||||
remoteState = &ri.State
|
||||
return nil
|
||||
}
|
||||
query = `SELECT remoteid FROM remote WHERE remotename = ?`
|
||||
remoteId = tx.GetString(query, rname)
|
||||
if remoteId == "" {
|
||||
return fmt.Errorf("remote not found", rname)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return remoteId, remoteState, txErr
|
||||
}
|
||||
|
@ -4,6 +4,16 @@ import "sync"
|
||||
|
||||
var MainBus *UpdateBus = MakeUpdateBus()
|
||||
|
||||
const PtyDataUpdateStr = "pty"
|
||||
const SessionUpdateStr = "session"
|
||||
const WindowUpdateStr = "window"
|
||||
const CmdUpdateStr = "cmd"
|
||||
const LineCmdUpdateStr = "line+cmd"
|
||||
|
||||
type UpdatePacket interface {
|
||||
UpdateType() string
|
||||
}
|
||||
|
||||
type UpdateCmd struct {
|
||||
CmdId string
|
||||
Status string
|
||||
@ -17,15 +27,27 @@ type PtyDataUpdate struct {
|
||||
PtyDataLen int64 `json:"ptydatalen"`
|
||||
}
|
||||
|
||||
func (PtyDataUpdate) UpdateType() string {
|
||||
return PtyDataUpdateStr
|
||||
}
|
||||
|
||||
type WindowUpdate struct {
|
||||
Window WindowType `json:"window"`
|
||||
Remove bool `json:"remove,omitempty"`
|
||||
}
|
||||
|
||||
func (WindowUpdate) WindowUpdate() string {
|
||||
return WindowUpdateStr
|
||||
}
|
||||
|
||||
type SessionUpdate struct {
|
||||
Sessions []*SessionType `json:"sessions"`
|
||||
}
|
||||
|
||||
func (SessionUpdate) UpdateType() string {
|
||||
return SessionUpdateStr
|
||||
}
|
||||
|
||||
func MakeSingleSessionUpdate(sessionId string) (*SessionUpdate, *SessionType) {
|
||||
session := &SessionType{
|
||||
SessionId: sessionId,
|
||||
@ -37,9 +59,14 @@ func MakeSingleSessionUpdate(sessionId string) (*SessionUpdate, *SessionType) {
|
||||
return update, session
|
||||
}
|
||||
|
||||
type CmdUpdate struct {
|
||||
Cmd CmdType `json:"cmd"`
|
||||
Remove bool `json:"remove,omitempty"`
|
||||
type LineUpdate struct {
|
||||
Line *LineType `json:"line"`
|
||||
Cmd *CmdType `json:"cmd,omitempty"`
|
||||
Remove bool `json:"remove,omitempty"`
|
||||
}
|
||||
|
||||
func (LineUpdate) UpdateType() string {
|
||||
return LineCmdUpdateStr
|
||||
}
|
||||
|
||||
type UpdateChannel struct {
|
||||
|
Loading…
Reference in New Issue
Block a user