updates, switch to new fecmd packet to run all UI commands through

This commit is contained in:
sawka 2022-07-15 17:37:32 -07:00
parent 5b1eb383e3
commit 6807547ff7
6 changed files with 461 additions and 219 deletions

View File

@ -10,7 +10,6 @@ import (
"net/http" "net/http"
"os" "os"
"runtime/debug" "runtime/debug"
"strconv"
"strings" "strings"
"sync" "sync"
"time" "time"
@ -18,7 +17,7 @@ import (
"github.com/google/uuid" "github.com/google/uuid"
"github.com/gorilla/mux" "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/remote"
"github.com/scripthaus-dev/sh2-server/pkg/scbase" "github.com/scripthaus-dev/sh2-server/pkg/scbase"
"github.com/scripthaus-dev/sh2-server/pkg/scpacket" "github.com/scripthaus-dev/sh2-server/pkg/scpacket"
@ -153,69 +152,6 @@ func HandleGetAllSessions(w http.ResponseWriter, r *http.Request) {
return 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] // params: [none]
func HandleGetRemotes(w http.ResponseWriter, r *http.Request) { func HandleGetRemotes(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin")) 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) { func WriteJsonError(w http.ResponseWriter, errVal error) {
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
w.WriteHeader(500) w.WriteHeader(200)
errMap := make(map[string]interface{}) errMap := make(map[string]interface{})
errMap["error"] = errVal.Error() errMap["error"] = errVal.Error()
barr, _ := json.Marshal(errMap) barr, _ := json.Marshal(errMap)
@ -329,11 +265,6 @@ func WriteJsonSuccess(w http.ResponseWriter, data interface{}) {
return return
} }
type runCommandResponse struct {
Line *sstore.LineType `json:"line"`
Cmd *sstore.CmdType `json:"cmd"`
}
func HandleRunCommand(w http.ResponseWriter, r *http.Request) { func HandleRunCommand(w http.ResponseWriter, r *http.Request) {
defer func() { defer func() {
r := recover() r := recover()
@ -360,127 +291,15 @@ func HandleRunCommand(w http.ResponseWriter, r *http.Request) {
WriteJsonError(w, fmt.Errorf("error decoding json: %w", err)) WriteJsonError(w, fmt.Errorf("error decoding json: %w", err))
return return
} }
if _, err = uuid.Parse(commandPk.SessionId); err != nil { update, err := cmdrunner.HandleCommand(r.Context(), &commandPk)
WriteJsonError(w, fmt.Errorf("invalid sessionid '%s': %w", commandPk.SessionId, err))
return
}
resp, err := ProcessFeCommandPacket(r.Context(), &commandPk)
if err != nil { if err != nil {
WriteJsonError(w, err) WriteJsonError(w, err)
return return
} }
WriteJsonSuccess(w, resp) WriteJsonSuccess(w, update)
return 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 // /api/start-session
// returns: // returns:
// * userid // * userid
@ -620,11 +439,8 @@ func main() {
gr := mux.NewRouter() gr := mux.NewRouter()
gr.HandleFunc("/api/ptyout", HandleGetPtyOut) gr.HandleFunc("/api/ptyout", HandleGetPtyOut)
gr.HandleFunc("/api/get-all-sessions", HandleGetAllSessions) gr.HandleFunc("/api/get-all-sessions", HandleGetAllSessions)
gr.HandleFunc("/api/create-session", HandleCreateSession)
gr.HandleFunc("/api/get-window", HandleGetWindow) gr.HandleFunc("/api/get-window", HandleGetWindow)
gr.HandleFunc("/api/get-remotes", HandleGetRemotes) 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") gr.HandleFunc("/api/run-command", HandleRunCommand).Methods("GET", "POST", "OPTIONS")
server := &http.Server{ server := &http.Server{
Addr: MainServerAddr, Addr: MainServerAddr,

367
pkg/cmdrunner/cmdrunner.go Normal file
View 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
}

View File

@ -6,14 +6,11 @@ import (
"errors" "errors"
"fmt" "fmt"
"os/exec" "os/exec"
"strings"
"sync" "sync"
"github.com/google/uuid"
"github.com/scripthaus-dev/mshell/pkg/base" "github.com/scripthaus-dev/mshell/pkg/base"
"github.com/scripthaus-dev/mshell/pkg/packet" "github.com/scripthaus-dev/mshell/pkg/packet"
"github.com/scripthaus-dev/mshell/pkg/shexec" "github.com/scripthaus-dev/mshell/pkg/shexec"
"github.com/scripthaus-dev/sh2-server/pkg/scpacket"
"github.com/scripthaus-dev/sh2-server/pkg/sstore" "github.com/scripthaus-dev/sh2-server/pkg/sstore"
) )
@ -123,6 +120,21 @@ func GetAllRemoteState() []RemoteState {
return rtn 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 { func MakeMShell(r *sstore.RemoteType) *MShellProc {
rtn := &MShellProc{Lock: &sync.Mutex{}, Remote: r, Status: StatusInit} rtn := &MShellProc{Lock: &sync.Mutex{}, Remote: r, Status: StatusInit}
return rtn return rtn
@ -168,6 +180,15 @@ func (msh *MShellProc) IsConnected() bool {
return msh.Status == StatusConnected 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 { func (msh *MShellProc) IsCmdRunning(ck base.CommandKey) bool {
msh.Lock.Lock() msh.Lock.Lock()
defer msh.Lock.Unlock() defer msh.Lock.Unlock()
@ -193,30 +214,21 @@ func (msh *MShellProc) SendInput(pk *packet.InputPacketType) error {
return msh.ServerProc.Input.SendPacket(dataPk) return msh.ServerProc.Input.SendPacket(dataPk)
} }
func convertRemoteState(rs scpacket.RemoteState) sstore.RemoteState {
return sstore.RemoteState{Cwd: rs.Cwd}
}
func makeTermOpts() sstore.TermOpts { func makeTermOpts() sstore.TermOpts {
return sstore.TermOpts{Rows: DefaultTermRows, Cols: DefaultTermCols, FlexRows: true} return sstore.TermOpts{Rows: DefaultTermRows, Cols: DefaultTermCols, FlexRows: true}
} }
func RunCommand(ctx context.Context, pk *scpacket.FeCommandPacketType, cmdId string) (*sstore.CmdType, error) { func RunCommand(ctx context.Context, cmdId string, remoteId string, remoteState *sstore.RemoteState, runPacket *packet.RunPacketType) (*sstore.CmdType, error) {
msh := GetRemoteById(pk.RemoteState.RemoteId) msh := GetRemoteById(remoteId)
if msh == nil { 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() { 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) fmt.Printf("RUN-CMD> %s reqid=%s (msh=%v)\n", runPacket.CK, runPacket.ReqId, msh.Remote)
msh.ServerProc.Output.RegisterRpc(runPacket.ReqId) msh.ServerProc.Output.RegisterRpc(runPacket.ReqId)
err := shexec.SendRunPacketAndRunData(ctx, msh.ServerProc.Input, runPacket) 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 status = sstore.CmdStatusDetached
} }
cmd := &sstore.CmdType{ cmd := &sstore.CmdType{
SessionId: pk.SessionId, SessionId: runPacket.CK.GetSessionId(),
CmdId: startPk.CK.GetCmdId(), CmdId: startPk.CK.GetCmdId(),
CmdStr: runPacket.Command, CmdStr: runPacket.Command,
RemoteId: msh.Remote.RemoteId, RemoteId: msh.Remote.RemoteId,
RemoteState: convertRemoteState(pk.RemoteState), RemoteState: *remoteState,
TermOpts: makeTermOpts(), TermOpts: makeTermOpts(),
Status: status, Status: status,
StartPk: startPk, StartPk: startPk,

View File

@ -16,13 +16,11 @@ type RemoteState struct {
} }
type FeCommandPacketType struct { type FeCommandPacketType struct {
Type string `json:"type"` Type string `json:"type"`
SessionId string `json:"sessionid"` MetaCmd string `json:"metacmd"`
ScreenId string `json:"screenid"` MetaSubCmd string `json:"metasubcmd,omitempty"`
WindowId string `json:"windowid"` Args []string `json:"args,omitempty"`
UserId string `json:"userid"` Kwargs map[string]string `json:"kwargs,omitempty"`
CmdStr string `json:"cmdstr"`
RemoteState RemoteState `json:"remotestate"`
} }
func init() { func init() {

View File

@ -468,3 +468,25 @@ func DeleteScreen(ctx context.Context, sessionId string, screenId string) error
MainBus.SendUpdate("", update) MainBus.SendUpdate("", update)
return nil 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
}

View File

@ -4,6 +4,16 @@ import "sync"
var MainBus *UpdateBus = MakeUpdateBus() 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 { type UpdateCmd struct {
CmdId string CmdId string
Status string Status string
@ -17,15 +27,27 @@ type PtyDataUpdate struct {
PtyDataLen int64 `json:"ptydatalen"` PtyDataLen int64 `json:"ptydatalen"`
} }
func (PtyDataUpdate) UpdateType() string {
return PtyDataUpdateStr
}
type WindowUpdate struct { type WindowUpdate struct {
Window WindowType `json:"window"` Window WindowType `json:"window"`
Remove bool `json:"remove,omitempty"` Remove bool `json:"remove,omitempty"`
} }
func (WindowUpdate) WindowUpdate() string {
return WindowUpdateStr
}
type SessionUpdate struct { type SessionUpdate struct {
Sessions []*SessionType `json:"sessions"` Sessions []*SessionType `json:"sessions"`
} }
func (SessionUpdate) UpdateType() string {
return SessionUpdateStr
}
func MakeSingleSessionUpdate(sessionId string) (*SessionUpdate, *SessionType) { func MakeSingleSessionUpdate(sessionId string) (*SessionUpdate, *SessionType) {
session := &SessionType{ session := &SessionType{
SessionId: sessionId, SessionId: sessionId,
@ -37,9 +59,14 @@ func MakeSingleSessionUpdate(sessionId string) (*SessionUpdate, *SessionType) {
return update, session return update, session
} }
type CmdUpdate struct { type LineUpdate struct {
Cmd CmdType `json:"cmd"` Line *LineType `json:"line"`
Remove bool `json:"remove,omitempty"` Cmd *CmdType `json:"cmd,omitempty"`
Remove bool `json:"remove,omitempty"`
}
func (LineUpdate) UpdateType() string {
return LineCmdUpdateStr
} }
type UpdateChannel struct { type UpdateChannel struct {