big updates to remoteinstance, push changes through stack

This commit is contained in:
sawka 2022-08-24 02:14:16 -07:00
parent 51f7b0798b
commit 4f686e891b
7 changed files with 411 additions and 230 deletions

View File

@ -18,7 +18,9 @@ CREATE TABLE session (
CREATE TABLE window (
sessionid varchar(36) NOT NULL,
windowid varchar(36) NOT NULL,
curremote varchar(50) NOT NULL,
curremoteowneruserid varchar(36) NOT NULL,
curremoteid varchar(36) NOT NULL,
curremotename varchar(50) NOT NULL,
winopts json NOT NULL,
owneruserid varchar(36) NOT NULL,
sharemode varchar(12) NOT NULL,
@ -52,8 +54,8 @@ CREATE TABLE remote_instance (
name varchar(50) NOT NULL,
sessionid varchar(36) NOT NULL,
windowid varchar(36) NOT NULL,
remoteowneruserid varchar(36) NOT NULL,
remoteid varchar(36) NOT NULL,
sessionscope boolean NOT NULL,
state json NOT NULL
);
@ -98,7 +100,6 @@ CREATE TABLE cmd (
donepk json NOT NULL,
runout json NOT NULL,
usedrows int NOT NULL,
prompt text NOT NULL,
PRIMARY KEY (sessionid, cmdid)
);

View File

@ -18,7 +18,9 @@ CREATE TABLE session (
CREATE TABLE window (
sessionid varchar(36) NOT NULL,
windowid varchar(36) NOT NULL,
curremote varchar(50) NOT NULL,
curremoteowneruserid varchar(36) NOT NULL,
curremoteid varchar(36) NOT NULL,
curremotename varchar(50) NOT NULL,
winopts json NOT NULL,
owneruserid varchar(36) NOT NULL,
sharemode varchar(12) NOT NULL,
@ -49,8 +51,8 @@ CREATE TABLE remote_instance (
name varchar(50) NOT NULL,
sessionid varchar(36) NOT NULL,
windowid varchar(36) NOT NULL,
remoteowneruserid varchar(36) NOT NULL,
remoteid varchar(36) NOT NULL,
sessionscope boolean NOT NULL,
state json NOT NULL
);
CREATE TABLE line (
@ -92,7 +94,6 @@ CREATE TABLE cmd (
donepk json NOT NULL,
runout json NOT NULL,
usedrows int NOT NULL,
prompt text NOT NULL,
PRIMARY KEY (sessionid, cmdid)
);
CREATE TABLE history (

View File

@ -39,9 +39,9 @@ type resolvedIds struct {
SessionId string
ScreenId string
WindowId string
RemoteId string
RemoteName string
RemotePtr sstore.RemotePtrType
RemoteState *sstore.RemoteState
RemoteDisplayName string
}
func SubMetaCmd(cmd string) string {
@ -208,22 +208,59 @@ func resolveScreenId(ctx context.Context, pk *scpacket.FeCommandPacketType, sess
return resolveSessionScreen(ctx, sessionId, screenArg)
}
// returns (remoteName, remoteId, state, err)
func resolveRemote(ctx context.Context, remoteName string, sessionId string, windowId string) (string, string, *sstore.RemoteState, error) {
if remoteName == "" {
return "", "", nil, nil
// returns (remoteuserref, remoteref, name, error)
func parseFullRemoteRef(fullRemoteRef string) (string, string, string, error) {
if strings.HasPrefix(fullRemoteRef, "[") && strings.HasSuffix(fullRemoteRef, "]") {
fullRemoteRef = fullRemoteRef[1 : len(fullRemoteRef)-1]
}
remoteId, state, err := sstore.GetRemoteState(ctx, remoteName, sessionId, windowId)
fields := strings.Split(fullRemoteRef, ":")
if len(fields) > 3 {
return "", "", "", fmt.Errorf("invalid remote format '%s'", fullRemoteRef)
}
if len(fields) == 1 {
return "", fields[0], "", nil
}
if len(fields) == 2 {
if strings.HasPrefix(fields[0], "@") {
return fields[0], fields[1], "", nil
}
return "", fields[0], fields[1], nil
}
return fields[0], fields[1], fields[2], nil
}
// returns (remoteDisplayName, remoteptr, state, err)
func resolveRemote(ctx context.Context, fullRemoteRef string, sessionId string, windowId string) (string, *sstore.RemotePtrType, *sstore.RemoteState, error) {
if fullRemoteRef == "" {
return "", nil, nil, nil
}
userRef, remoteRef, remoteName, err := parseFullRemoteRef(fullRemoteRef)
if err != nil {
return "", "", nil, fmt.Errorf("cannot resolve remote '%s': %w", remoteName, err)
return "", nil, nil, err
}
if userRef != "" {
return "", nil, nil, fmt.Errorf("invalid remote '%s', cannot resolve remote userid '%s'", fullRemoteRef, userRef)
}
rstate := remote.ResolveRemoteRef(remoteRef)
if rstate == nil {
return "", nil, nil, fmt.Errorf("cannot resolve remote '%s': not found", fullRemoteRef)
}
rptr := sstore.RemotePtrType{RemoteId: rstate.RemoteId, Name: remoteName}
state, err := sstore.GetRemoteState(ctx, sessionId, windowId, rptr)
if err != nil {
return "", nil, nil, fmt.Errorf("cannot resolve remote state '%s': %w", fullRemoteRef, err)
}
rname := rstate.RemoteCanonicalName
if rstate.RemoteAlias != "" {
rname = rstate.RemoteAlias
}
if rptr.Name != "" {
rname = fmt.Sprintf("%s:%s", rname, rptr.Name)
}
if state == nil {
state, err = remote.GetDefaultRemoteStateById(remoteId)
if err != nil {
return "", "", nil, fmt.Errorf("cannot resolve remote '%s': %w", remoteName, err)
return rname, &rptr, rstate.DefaultState, nil
}
}
return remoteName, remoteId, state, nil
return rname, &rptr, state, nil
}
func resolveIds(ctx context.Context, pk *scpacket.FeCommandPacketType, rtype int) (resolvedIds, error) {
@ -261,13 +298,16 @@ func resolveIds(ctx context.Context, pk *scpacket.FeCommandPacketType, rtype int
}
}
if (rtype&R_Remote)+(rtype&R_RemoteOpt) > 0 {
rtn.RemoteName, rtn.RemoteId, rtn.RemoteState, err = resolveRemote(ctx, pk.Kwargs["remote"], rtn.SessionId, rtn.WindowId)
rname, rptr, rstate, err := resolveRemote(ctx, pk.Kwargs["remote"], rtn.SessionId, rtn.WindowId)
if err != nil {
return rtn, err
}
if rtn.RemoteId == "" && (rtype&R_Remote) > 0 {
if rptr == nil && (rtype&R_Remote) > 0 {
return rtn, fmt.Errorf("no remote")
}
rtn.RemoteDisplayName = rname
rtn.RemotePtr = *rptr
rtn.RemoteState = rstate
}
return rtn, nil
}
@ -289,7 +329,7 @@ func RunCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.U
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)
cmd, err := remote.RunCommand(ctx, cmdId, ids.RemotePtr.RemoteId, ids.RemoteState, runPacket)
if err != nil {
return nil, err
}
@ -297,7 +337,7 @@ func RunCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.U
if err != nil {
return nil, err
}
return sstore.LineUpdate{Line: rtnLine, Cmd: cmd}, nil
return sstore.ModelUpdate{Line: rtnLine, Cmd: cmd}, nil
}
func addToHistory(ctx context.Context, pk *scpacket.FeCommandPacketType, update sstore.UpdatePacket, hadError bool) error {
@ -467,7 +507,7 @@ func UnSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore
if err != nil {
return nil, err
}
curRemote := remote.GetRemoteById(ids.RemoteId)
curRemote := remote.GetRemoteById(ids.RemotePtr.RemoteId)
if curRemote == nil {
return nil, fmt.Errorf("invalid remote, cannot unset")
}
@ -489,18 +529,14 @@ func UnSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore
}
state := *ids.RemoteState
state.Env0 = shexec.MakeEnv0(envMap)
remote, err := sstore.UpdateRemoteState(ctx, ids.RemoteName, ids.SessionId, ids.WindowId, ids.RemoteId, state)
remote, err := sstore.UpdateRemoteState(ctx, ids.SessionId, ids.WindowId, ids.RemotePtr, state)
if err != nil {
return nil, err
}
update := sstore.WindowUpdate{
Window: sstore.WindowType{
SessionId: ids.SessionId,
WindowId: ids.WindowId,
Remotes: []*sstore.RemoteInstance{remote},
},
update := sstore.ModelUpdate{
Sessions: sstore.MakeSessionsUpdateForRemote(ids.SessionId, remote),
Info: &sstore.InfoMsgType{
InfoMsg: fmt.Sprintf("[%s] unset vars: %s", ids.RemoteName, makeSetVarsStr(unsetVars)),
InfoMsg: fmt.Sprintf("[%s] unset vars: %s", ids.RemoteDisplayName, makeSetVarsStr(unsetVars)),
TimeoutMs: 2000,
},
}
@ -513,9 +549,9 @@ func RemoteCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstor
if err != nil {
return nil, err
}
curRemote := remote.GetRemoteById(ids.RemoteId)
curRemote := remote.GetRemoteById(ids.RemotePtr.RemoteId)
if curRemote == nil {
return nil, fmt.Errorf("invalid remote [%s] (not found)", ids.RemoteName)
return nil, fmt.Errorf("invalid remote '%s' (not found)", ids.RemoteDisplayName)
}
state := curRemote.GetRemoteState()
var buf bytes.Buffer
@ -530,9 +566,9 @@ func RemoteCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstor
buf.WriteString(fmt.Sprintf(" %-15s %s\n", "cwd", ids.RemoteState.Cwd))
}
output := buf.String()
return sstore.InfoUpdate{
return sstore.ModelUpdate{
Info: &sstore.InfoMsgType{
InfoTitle: fmt.Sprintf("show remote '%s' info", ids.RemoteName),
InfoTitle: fmt.Sprintf("show remote '%s' info", ids.RemoteDisplayName),
InfoLines: splitLinesForInfo(output),
},
}, nil
@ -559,7 +595,7 @@ func SetEnvCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstor
if err != nil {
return nil, err
}
curRemote := remote.GetRemoteById(ids.RemoteId)
curRemote := remote.GetRemoteById(ids.RemotePtr.RemoteId)
if curRemote == nil {
return nil, fmt.Errorf("invalid remote, cannot setenv")
}
@ -576,9 +612,9 @@ func SetEnvCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstor
line := fmt.Sprintf("%s=%s", varName, shellescape.Quote(varVal))
infoLines = append(infoLines, line)
}
update := sstore.InfoUpdate{
update := sstore.ModelUpdate{
Info: &sstore.InfoMsgType{
InfoTitle: fmt.Sprintf("environment for [%s] remote", ids.RemoteName),
InfoTitle: fmt.Sprintf("environment for [%s] remote", ids.RemoteDisplayName),
InfoLines: infoLines,
},
}
@ -597,18 +633,14 @@ func SetEnvCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstor
}
state := *ids.RemoteState
state.Env0 = shexec.MakeEnv0(envMap)
remote, err := sstore.UpdateRemoteState(ctx, ids.RemoteName, ids.SessionId, ids.WindowId, ids.RemoteId, state)
remote, err := sstore.UpdateRemoteState(ctx, ids.SessionId, ids.WindowId, ids.RemotePtr, state)
if err != nil {
return nil, err
}
update := sstore.WindowUpdate{
Window: sstore.WindowType{
SessionId: ids.SessionId,
WindowId: ids.WindowId,
Remotes: []*sstore.RemoteInstance{remote},
},
update := sstore.ModelUpdate{
Sessions: sstore.MakeSessionsUpdateForRemote(ids.SessionId, remote),
Info: &sstore.InfoMsgType{
InfoMsg: fmt.Sprintf("[%s] set vars: %s", ids.RemoteName, makeSetVarsStr(setVars)),
InfoMsg: fmt.Sprintf("[%s] set vars: %s", ids.RemoteDisplayName, makeSetVarsStr(setVars)),
TimeoutMs: 2000,
},
}
@ -624,23 +656,22 @@ func CrCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.Up
if newRemote == "" {
return nil, nil
}
remoteName, remoteId, _, err := resolveRemote(ctx, newRemote, ids.SessionId, ids.WindowId)
fmt.Printf("found: name[%s] id[%s] err[%v]\n", remoteName, remoteId, err)
remoteName, rptr, _, err := resolveRemote(ctx, newRemote, ids.SessionId, ids.WindowId)
if err != nil {
return nil, err
}
if remoteId == "" {
return nil, fmt.Errorf("/cr error: remote not found")
if rptr == nil {
return nil, fmt.Errorf("/cr error: remote '%s' not found", newRemote)
}
err = sstore.UpdateCurRemote(ctx, ids.SessionId, ids.WindowId, remoteName)
err = sstore.UpdateCurRemote(ctx, ids.SessionId, ids.WindowId, *rptr)
if err != nil {
return nil, fmt.Errorf("/cr error: cannot update curremote: %w", err)
}
update := sstore.WindowUpdate{
update := sstore.ModelUpdate{
Window: sstore.WindowType{
SessionId: ids.SessionId,
WindowId: ids.WindowId,
CurRemote: remoteName,
CurRemote: *rptr,
},
Info: &sstore.InfoMsgType{
InfoMsg: fmt.Sprintf("current remote = %s", remoteName),
@ -656,7 +687,7 @@ func CdCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.Up
return nil, fmt.Errorf("/cd error: %w", err)
}
newDir := firstArg(pk)
curRemote := remote.GetRemoteById(ids.RemoteId)
curRemote := remote.GetRemoteById(ids.RemotePtr.RemoteId)
if curRemote == nil {
return nil, fmt.Errorf("invalid remote, cannot change directory")
}
@ -667,9 +698,9 @@ func CdCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.Up
return nil, fmt.Errorf("remote state is not available")
}
if newDir == "" {
return sstore.InfoUpdate{
return sstore.ModelUpdate{
Info: &sstore.InfoMsgType{
InfoMsg: fmt.Sprintf("[%s] current directory = %s", ids.RemoteName, ids.RemoteState.Cwd),
InfoMsg: fmt.Sprintf("[%s] current directory = %s", ids.RemoteDisplayName, ids.RemoteState.Cwd),
},
}, nil
}
@ -699,18 +730,14 @@ func CdCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.Up
}
state := *ids.RemoteState
state.Cwd = newDir
remote, err := sstore.UpdateRemoteState(ctx, ids.RemoteName, ids.SessionId, ids.WindowId, ids.RemoteId, state)
remote, err := sstore.UpdateRemoteState(ctx, ids.SessionId, ids.WindowId, ids.RemotePtr, state)
if err != nil {
return nil, err
}
update := sstore.WindowUpdate{
Window: sstore.WindowType{
SessionId: ids.SessionId,
WindowId: ids.WindowId,
Remotes: []*sstore.RemoteInstance{remote},
},
update := sstore.ModelUpdate{
Sessions: sstore.MakeSessionsUpdateForRemote(ids.SessionId, remote),
Info: &sstore.InfoMsgType{
InfoMsg: fmt.Sprintf("[%s] current directory = %s", ids.RemoteName, newDir),
InfoMsg: fmt.Sprintf("[%s] current directory = %s", ids.RemoteDisplayName, newDir),
TimeoutMs: 2000,
},
}
@ -778,7 +805,7 @@ func makeInfoFromComps(compType string, comps []string, hasMore bool) sstore.Upd
if len(comps) == 0 {
comps = []string{"(no completions)"}
}
update := sstore.InfoUpdate{
update := sstore.ModelUpdate{
Info: &sstore.InfoMsgType{
InfoTitle: fmt.Sprintf("%s completions", compType),
InfoComps: comps,
@ -798,7 +825,7 @@ func makeInsertUpdateFromComps(pos int64, prefix string, comps []string, hasMore
}
insertChars := lcp[len(prefix):]
clu := &sstore.CmdLineType{InsertChars: insertChars, InsertPos: pos}
return sstore.InfoUpdate{CmdLine: clu}
return sstore.ModelUpdate{CmdLine: clu}
}
func longestPrefix(root string, comps []string) string {
@ -864,7 +891,7 @@ func doCompGen(ctx context.Context, ids resolvedIds, prefix string, compType str
return nil, false, fmt.Errorf("/compgen invalid remote state")
}
cgPacket.Cwd = ids.RemoteState.Cwd
curRemote := remote.GetRemoteById(ids.RemoteId)
curRemote := remote.GetRemoteById(ids.RemotePtr.RemoteId)
if curRemote == nil {
return nil, false, fmt.Errorf("invalid remote, cannot execute command")
}
@ -938,7 +965,7 @@ func CommentCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ssto
if err != nil {
return nil, err
}
return sstore.LineUpdate{Line: rtnLine}, nil
return sstore.ModelUpdate{Line: rtnLine}, nil
}
func SessionCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
@ -961,7 +988,7 @@ func SessionCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ssto
if err != nil {
return nil, err
}
return sstore.SessionUpdate{ActiveSessionId: sessionId}, nil
return sstore.ModelUpdate{ActiveSessionId: sessionId}, nil
}
func splitLinesForInfo(str string) []string {

View File

@ -1,6 +1,7 @@
package remote
import (
"bytes"
"context"
"encoding/base64"
"errors"
@ -16,6 +17,7 @@ import (
"time"
"github.com/creack/pty"
"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"
@ -116,6 +118,28 @@ func GetRemoteById(remoteId string) *MShellProc {
return GlobalStore.Map[remoteId]
}
func ResolveRemoteRef(remoteRef string) *RemoteState {
GlobalStore.Lock.Lock()
defer GlobalStore.Lock.Unlock()
_, err := uuid.Parse(remoteRef)
if err == nil {
msh := GlobalStore.Map[remoteRef]
if msh != nil {
state := msh.GetRemoteState()
return &state
}
return nil
}
for _, msh := range GlobalStore.Map {
if msh.Remote.RemoteAlias == remoteRef || msh.Remote.RemoteCanonicalName == remoteRef {
state := msh.GetRemoteState()
return &state
}
}
return nil
}
func unquoteDQBashString(str string) (string, bool) {
if len(str) < 2 {
return str, false
@ -175,19 +199,24 @@ func (proc *MShellProc) GetRemoteState() RemoteState {
if proc.Err != nil {
state.ErrorStr = proc.Err.Error()
}
local := (proc.Remote.SSHOpts == nil || proc.Remote.SSHOpts.Local)
vars := make(map[string]string)
vars["user"] = proc.Remote.RemoteUser
vars["bestuser"] = vars["user"]
vars["host"] = proc.Remote.RemoteHost
vars["shorthost"] = makeShortHost(proc.Remote.RemoteHost)
if proc.Remote.RemoteSudo {
vars["sudo"] = "1"
}
vars["alias"] = proc.Remote.RemoteAlias
vars["cname"] = proc.Remote.RemoteCanonicalName
vars["physicalid"] = proc.Remote.PhysicalId
vars["remoteid"] = proc.Remote.RemoteId
vars["status"] = proc.Status
vars["type"] = proc.Remote.RemoteType
if proc.Remote.RemoteSudo {
vars["sudo"] = "1"
}
if local {
vars["local"] = "1"
}
if proc.ServerProc != nil && proc.ServerProc.InitPk != nil {
state.DefaultState = &sstore.RemoteState{
Cwd: proc.ServerProc.InitPk.Cwd,
@ -195,11 +224,23 @@ func (proc *MShellProc) GetRemoteState() RemoteState {
}
vars["home"] = proc.ServerProc.InitPk.HomeDir
vars["remoteuser"] = proc.ServerProc.InitPk.User
vars["bestuser"] = vars["remoteuser"]
vars["remotehost"] = proc.ServerProc.InitPk.HostName
vars["remoteshorthost"] = makeShortHost(proc.ServerProc.InitPk.HostName)
if proc.Remote.SSHOpts == nil || proc.Remote.SSHOpts.SSHHost == "" {
vars["local"] = "1"
vars["besthost"] = vars["remotehost"]
vars["bestshorthost"] = vars["remoteshorthost"]
}
if local && proc.Remote.RemoteSudo {
vars["bestuser"] = "sudo"
} else if proc.Remote.RemoteSudo {
vars["bestuser"] = "sudo@" + vars["bestuser"]
}
if local {
vars["bestname"] = vars["bestuser"] + "@local"
vars["bestshortname"] = vars["bestuser"] + "@local"
} else {
vars["bestname"] = vars["bestuser"] + "@" + vars["besthost"]
vars["bestshortname"] = vars["bestuser"] + "@" + vars["bestshorthost"]
}
state.RemoteVars = vars
return state
@ -238,8 +279,8 @@ func MakeMShell(r *sstore.RemoteType) *MShellProc {
}
func convertSSHOpts(opts *sstore.SSHOpts) shexec.SSHOpts {
if opts == nil {
return shexec.SSHOpts{}
if opts == nil || opts.Local {
opts = &sstore.SSHOpts{}
}
return shexec.SSHOpts{
SSHHost: opts.SSHHost,
@ -347,6 +388,19 @@ func (msh *MShellProc) GetDefaultState() *sstore.RemoteState {
return &sstore.RemoteState{Cwd: msh.ServerProc.InitPk.HomeDir, Env0: msh.ServerProc.InitPk.Env0}
}
func replaceHomePath(pathStr string, homeDir string) string {
if homeDir == "" {
return pathStr
}
if pathStr == homeDir {
return "~"
}
if strings.HasPrefix(pathStr, homeDir+"/") {
return "~" + pathStr[len(homeDir):]
}
return pathStr
}
func (msh *MShellProc) ExpandHomeDir(pathStr string) (string, error) {
if pathStr != "~" && !strings.HasPrefix(pathStr, "~/") {
return pathStr, nil
@ -526,7 +580,7 @@ func (msh *MShellProc) notifyHangups_nolock() {
if err != nil {
continue
}
update := sstore.LineUpdate{Cmd: cmd}
update := sstore.ModelUpdate{Cmd: cmd}
sstore.MainBus.SendUpdate(ck.GetSessionId(), update)
}
msh.RunningCmds = nil
@ -602,16 +656,79 @@ func (runner *MShellProc) ProcessPackets() {
}
}
func EvalPromptEsc(escCode string, vars map[string]string, state sstore.RemoteState) string {
if escCode == "d" {
now := time.Now()
return now.Format("Mon Jan 02")
// returns number of chars (including braces) for brace-expr
func getBracedStr(runeStr []rune) int {
if len(runeStr) < 3 {
return 0
}
if runeStr[0] != '{' {
return 0
}
for i := 1; i < len(runeStr); i++ {
if runeStr[i] == '}' {
if i == 1 { // cannot have {}
return 0
}
return i + 1
}
}
return 0
}
func isDigit(r rune) bool {
return r >= '0' && r <= '9' // just check ascii digits (not unicode)
}
func EvalPrompt(promptFmt string, vars map[string]string, state *sstore.RemoteState) string {
var buf bytes.Buffer
promptRunes := []rune(promptFmt)
for i := 0; i < len(promptRunes); i++ {
ch := promptRunes[i]
if ch == '\\' && i != len(promptRunes)-1 {
nextCh := promptRunes[i+1]
if nextCh == 'x' || nextCh == 'y' {
nr := getBracedStr(promptRunes[i+2:])
if nr > 0 {
escCode := string(promptRunes[i+1 : i+1+nr+1]) // start at "x" or "y", extend nr+1 runes
escStr := evalPromptEsc(escCode, vars, state)
buf.WriteString(escStr)
i += nr + 1
continue
} else {
buf.WriteRune(ch) // invalid escape, so just write ch and move on
continue
}
} else if isDigit(nextCh) {
if len(promptRunes) >= i+4 && isDigit(promptRunes[i+2]) && isDigit(promptRunes[i+3]) {
i += 3
escStr := evalPromptEsc(string(promptRunes[i+1:i+4]), vars, state)
buf.WriteString(escStr)
continue
} else {
buf.WriteRune(ch) // invalid escape, so just write ch and move on
continue
}
} else {
i += 1
escStr := evalPromptEsc(string(nextCh), vars, state)
buf.WriteString(escStr)
continue
}
}
buf.WriteRune(ch)
}
return buf.String()
}
func evalPromptEsc(escCode string, vars map[string]string, state *sstore.RemoteState) string {
if strings.HasPrefix(escCode, "x{") && strings.HasSuffix(escCode, "}") {
varName := escCode[2 : len(escCode)-1]
return vars[varName]
}
if strings.HasPrefix(escCode, "y{") && strings.HasSuffix(escCode, "}") {
if state == nil {
return ""
}
varName := escCode[2 : len(escCode)-1]
varMap := shexec.ParseEnv0(state.Env0)
return varMap[varName]
@ -622,50 +739,26 @@ func EvalPromptEsc(escCode string, vars map[string]string, state sstore.RemoteSt
if escCode == "H" {
return vars["remotehost"]
}
if escCode == "j" {
return "0"
}
if escCode == "l" {
return "(l)"
}
if escCode == "s" {
return "mshell"
}
if escCode == "t" {
now := time.Now()
return now.Format("15:04:05")
}
if escCode == "T" {
now := time.Now()
return now.Format("03:04:05")
}
if escCode == "@" {
now := time.Now()
return now.Format("03:04:05PM")
}
if escCode == "u" {
return vars["remoteuser"]
}
if escCode == "v" {
return "0.1"
}
if escCode == "V" {
return "0"
}
if escCode == "w" {
return state.Cwd
if state == nil {
return "?"
}
return replaceHomePath(state.Cwd, vars["home"])
}
if escCode == "W" {
return path.Base(state.Cwd)
if state == nil {
return "?"
}
if escCode == "!" {
return "(!)"
}
if escCode == "#" {
return "(#)"
return path.Base(replaceHomePath(state.Cwd, vars["home"]))
}
if escCode == "$" {
if vars["remoteuser"] == "root" {
if vars["remoteuser"] == "root" || vars["sudo"] == "1" {
return "#"
} else {
return "$"
@ -700,5 +793,7 @@ func EvalPromptEsc(escCode string, vars map[string]string, state sstore.RemoteSt
if escCode == "]" {
return ""
}
// we don't support date/time escapes (d, t, T, @), version escapes (v, V), cmd number (#, !), terminal device (l), jobs (j)
return "(" + escCode + ")"
}

View File

@ -178,7 +178,7 @@ func GetAllSessions(ctx context.Context) ([]*SessionType, error) {
}
screen.Windows = append(screen.Windows, sw)
}
query = `SELECT * FROM remote_instance WHERE sessionscope`
query = `SELECT * FROM remote_instance`
var ris []*RemoteInstance
tx.SelectWrap(&ris, query)
for _, ri := range ris {
@ -195,22 +195,19 @@ func GetAllSessions(ctx context.Context) ([]*SessionType, error) {
func GetWindowById(ctx context.Context, sessionId string, windowId string) (*WindowType, error) {
var rtnWindow *WindowType
err := WithTx(ctx, func(tx *TxWrap) error {
var window WindowType
query := `SELECT * FROM window WHERE sessionid = ? AND windowid = ?`
found := tx.GetWrap(&window, query, sessionId, windowId)
if !found {
m := tx.GetMap(query, sessionId, windowId)
if m == nil {
return nil
}
rtnWindow = &window
rtnWindow = WindowFromMap(m)
query = `SELECT * FROM line WHERE sessionid = ? AND windowid = ?`
tx.SelectWrap(&window.Lines, query, sessionId, windowId)
tx.SelectWrap(&rtnWindow.Lines, query, sessionId, windowId)
query = `SELECT * FROM cmd WHERE cmdid IN (SELECT cmdid FROM line WHERE sessionid = ? AND windowid = ?)`
cmdMaps := tx.SelectMaps(query, sessionId, windowId)
for _, m := range cmdMaps {
window.Cmds = append(window.Cmds, CmdFromMap(m))
rtnWindow.Cmds = append(rtnWindow.Cmds, CmdFromMap(m))
}
query = `SELECT * FROM remote_instance WHERE sessionid = ? AND windowid = ? AND NOT sessionscope`
tx.SelectWrap(&window.Remotes, query, sessionId, windowId)
return nil
})
return rtnWindow, err
@ -258,7 +255,7 @@ func GetSessionByName(ctx context.Context, name string) (*SessionType, error) {
// also creates default window, returns sessionId
// if sessionName == "", it will be generated
func InsertSessionWithName(ctx context.Context, sessionName string, activate bool) (*SessionUpdate, error) {
func InsertSessionWithName(ctx context.Context, sessionName string, activate bool) (UpdatePacket, error) {
newSessionId := uuid.New().String()
txErr := WithTx(ctx, func(tx *TxWrap) error {
names := tx.SelectStrings(`SELECT name FROM session`)
@ -279,7 +276,7 @@ func InsertSessionWithName(ctx context.Context, sessionName string, activate boo
if err != nil {
return nil, err
}
update := &SessionUpdate{
update := ModelUpdate{
Sessions: []*SessionType{session},
}
if activate {
@ -328,7 +325,11 @@ func InsertScreen(ctx context.Context, sessionId string, screenName string, acti
if !tx.Exists(query, sessionId) {
return fmt.Errorf("cannot create screen, no session found")
}
newWindowId := txCreateWindow(tx, sessionId)
remoteId := tx.GetString(`SELECT remoteid FROM remote WHERE remotealias = ?`, LocalRemoteAlias)
if remoteId == "" {
return fmt.Errorf("cannot create screen, no local remote found")
}
newWindowId := txCreateWindow(tx, sessionId, RemotePtrType{RemoteId: remoteId})
maxScreenIdx := tx.GetInt(`SELECT COALESCE(max(screenidx), 0) FROM screen WHERE sessionid = ?`, sessionId)
screenNames := tx.SelectStrings(`SELECT name FROM screen WHERE sessionid = ?`, sessionId)
screenName = fmtUniqueName(screenName, "s%d", maxScreenIdx+1, screenNames)
@ -377,11 +378,20 @@ func GetScreenById(ctx context.Context, sessionId string, screenId string) (*Scr
return rtnScreen, nil
}
func txCreateWindow(tx *TxWrap, sessionId string) string {
windowId := uuid.New().String()
query := `INSERT INTO window (sessionid, windowid, curremote, winopts, shareopts, owneruserid, sharemode) VALUES (?, ?, ?, ?, ?, '', 'local')`
tx.ExecWrap(query, sessionId, windowId, LocalRemoteName, WindowOptsType{}, WindowShareOptsType{})
return windowId
func txCreateWindow(tx *TxWrap, sessionId string, curRemote RemotePtrType) string {
w := &WindowType{
SessionId: sessionId,
WindowId: uuid.New().String(),
CurRemote: curRemote,
WinOpts: WindowOptsType{},
ShareMode: ShareModeLocal,
ShareOpts: WindowShareOptsType{},
}
wmap := w.ToMap()
query := `INSERT INTO window ( sessionid, windowid, curremoteowneruserid, curremoteid, curremotename, winopts, owneruserid, sharemode, shareopts)
VALUES (:sessionid,:windowid,:curremoteowneruserid,:curremoteid,:curremotename,:winopts,:owneruserid,:sharemode,:shareopts)`
tx.NamedExecWrap(query, wmap)
return w.WindowId
}
func InsertLine(ctx context.Context, line *LineType, cmd *CmdType) error {
@ -404,8 +414,8 @@ func InsertLine(ctx context.Context, line *LineType, cmd *CmdType) error {
if cmd != nil {
cmdMap := cmd.ToMap()
query = `
INSERT INTO cmd ( sessionid, cmdid, remoteid, cmdstr, remotestate, termopts, status, startpk, donepk, runout, usedrows, prompt)
VALUES (:sessionid,:cmdid,:remoteid,:cmdstr,:remotestate,:termopts,:status,:startpk,:donepk,:runout,:usedrows,:prompt)
INSERT INTO cmd ( sessionid, cmdid, remoteid, cmdstr, remotestate, termopts, status, startpk, donepk, runout, usedrows)
VALUES (:sessionid,:cmdid,:remoteid,:cmdstr,:remotestate,:termopts,:status,:startpk,:donepk,:runout,:usedrows)
`
tx.NamedExecWrap(query, cmdMap)
}
@ -448,7 +458,7 @@ func UpdateCmdDonePk(ctx context.Context, donePk *packet.CmdDonePacketType) (Upd
if rtnCmd == nil {
return nil, fmt.Errorf("cmd data not found for ck[%s]", donePk.CK)
}
return LineUpdate{Cmd: rtnCmd}, nil
return ModelUpdate{Cmd: rtnCmd}, nil
}
func AppendCmdErrorPk(ctx context.Context, errPk *packet.CmdErrorPacketType) error {
@ -548,67 +558,80 @@ func DeleteScreen(ctx context.Context, sessionId string, screenId string) (Updat
return update, nil
}
func GetRemoteState(ctx context.Context, rname string, sessionId string, windowId string) (string, *RemoteState, error) {
var remoteId string
func GetRemoteState(ctx context.Context, sessionId string, windowId string, remotePtr RemotePtrType) (*RemoteState, error) {
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)
query := `SELECT * FROM remote_instance WHERE sessionid = ? AND windowid = ? AND remoteowneruserid = ? AND remoteid = ? AND name = ?`
found := tx.GetWrap(&ri, query, sessionId, windowId, remotePtr.OwnerUserId, remotePtr.RemoteId, remotePtr.Name)
if found {
remoteId = ri.RemoteId
remoteState = &ri.State
return nil
}
query = `SELECT remoteid FROM remote WHERE remotealias = ? OR remotecanonicalname = ?`
remoteId = tx.GetString(query, rname, rname)
if remoteId == "" {
return fmt.Errorf("remote not found", rname)
}
return nil
})
return remoteId, remoteState, txErr
return remoteState, txErr
}
func UpdateRemoteState(ctx context.Context, rname string, sessionId string, windowId string, remoteId string, state RemoteState) (*RemoteInstance, error) {
var ri RemoteInstance
txErr := WithTx(ctx, func(tx *TxWrap) error {
func validateSessionWindow(tx *TxWrap, sessionId string, windowId string) error {
if windowId == "" {
query := `SELECT sessionid FROM session WHERE sessionid = ?`
if !tx.Exists(query, sessionId) {
return fmt.Errorf("no session found")
}
return nil
} else {
query := `SELECT windowid FROM window WHERE sessionid = ? AND windowid = ?`
if !tx.Exists(query, sessionId, windowId) {
return fmt.Errorf("cannot update remote instance cwd, no window found")
return fmt.Errorf("no window found")
}
query = `SELECT * FROM remote_instance WHERE sessionid = ? AND windowid = ? AND name = ?`
found := tx.GetWrap(&ri, query, sessionId, windowId, rname)
return nil
}
}
func UpdateRemoteState(ctx context.Context, sessionId string, windowId string, remotePtr RemotePtrType, state RemoteState) (*RemoteInstance, error) {
if remotePtr.IsSessionScope() {
windowId = ""
}
var ri RemoteInstance
txErr := WithTx(ctx, func(tx *TxWrap) error {
err := validateSessionWindow(tx, sessionId, windowId)
if err != nil {
return fmt.Errorf("cannot update remote instance cwd: %w", err)
}
query := `SELECT * FROM remote_instance WHERE sessionid = ? AND windowid = ? AND remoteowneruserid = ? AND remoteid = ? AND name = ?`
found := tx.GetWrap(&ri, query, sessionId, windowId, remotePtr.OwnerUserId, remotePtr.RemoteId, remotePtr.Name)
if !found {
ri = RemoteInstance{
RIId: uuid.New().String(),
Name: rname,
Name: remotePtr.Name,
SessionId: sessionId,
WindowId: windowId,
RemoteId: remoteId,
SessionScope: (windowId == ""),
RemoteOwnerUserId: remotePtr.OwnerUserId,
RemoteId: remotePtr.RemoteId,
State: state,
}
query = `INSERT INTO remote_instance (riid, name, sessionid, windowid, remoteid, sessionscope, state) VALUES (:riid, :name, :sessionid, :windowid, :remoteid, :sessionscope, :state)`
query = `INSERT INTO remote_instance ( riid, name, sessionid, windowid, remoteowneruserid, remoteid, state)
VALUES (:riid,:name,:sessionid,:windowid,:remoteowneruserid,:remoteid,:state)`
tx.NamedExecWrap(query, ri)
return nil
}
query = `UPDATE remote_instance SET state = ? WHERE sessionid = ? AND windowid = ? AND name = ?`
query = `UPDATE remote_instance SET state = ? WHERE sessionid = ? AND windowid = ? AND remoteowneruserid = ? AND remoteid = ? AND name = ?`
ri.State = state
tx.ExecWrap(query, ri.State, ri.SessionId, ri.WindowId, ri.Name)
tx.ExecWrap(query, ri.State, ri.SessionId, ri.WindowId, remotePtr.OwnerUserId, remotePtr.RemoteId, remotePtr.Name)
return nil
})
return &ri, txErr
}
func UpdateCurRemote(ctx context.Context, sessionId string, windowId string, remoteName string) error {
func UpdateCurRemote(ctx context.Context, sessionId string, windowId string, remotePtr RemotePtrType) error {
txErr := WithTx(ctx, func(tx *TxWrap) error {
query := `SELECT windowid FROM window WHERE sessionid = ? AND windowid = ?`
if !tx.Exists(query, sessionId, windowId) {
return fmt.Errorf("cannot update curremote, no window found")
return fmt.Errorf("cannot update curremote: no window found")
}
query = `UPDATE window SET curremote = ? WHERE sessionid = ? AND windowid = ?`
tx.ExecWrap(query, remoteName, sessionId, windowId)
query = `UPDATE window SET curremoteowneruserid = ?, curremoteid = ?, curremotename = ? WHERE sessionid = ? AND windowid = ?`
tx.ExecWrap(query, remotePtr.OwnerUserId, remotePtr.RemoteId, remotePtr.Name, sessionId, windowId)
return nil
})
return txErr

View File

@ -12,6 +12,7 @@ import (
"os"
"os/user"
"path"
"strings"
"sync"
"time"
@ -30,7 +31,7 @@ const DBFileName = "sh2.db"
const DefaultSessionName = "default"
const DefaultWindowName = "default"
const LocalRemoteName = "local"
const LocalRemoteAlias = "local"
const DefaultScreenWindowName = "w1"
const DefaultCwd = "~"
@ -132,22 +133,78 @@ func (opts WindowShareOptsType) Value() (driver.Value, error) {
return quickValueJson(opts)
}
type RemotePtrType struct {
OwnerUserId string `json:"owneruserid"`
RemoteId string `json:"remoteid"`
Name string `json:"name"`
}
func (r RemotePtrType) IsSessionScope() bool {
return strings.HasPrefix(r.Name, "*")
}
func (r RemotePtrType) MakeFullRemoteRef() string {
if r.RemoteId == "" {
return ""
}
if r.OwnerUserId == "" && r.Name == "" {
return r.RemoteId
}
if r.OwnerUserId != "" && r.Name == "" {
return fmt.Sprintf("@%s:%s", r.OwnerUserId, r.RemoteId)
}
if r.OwnerUserId == "" && r.Name != "" {
return fmt.Sprintf("%s:%s", r.RemoteId, r.Name)
}
return fmt.Sprintf("@%s:%s:%s", r.OwnerUserId, r.RemoteId, r.Name)
}
type WindowType struct {
SessionId string `json:"sessionid"`
WindowId string `json:"windowid"`
CurRemote string `json:"curremote"`
CurRemote RemotePtrType `json:"curremote"`
WinOpts WindowOptsType `json:"winopts"`
OwnerUserId string `json:"owneruserid"`
ShareMode string `json:"sharemode"`
ShareOpts WindowShareOptsType `json:"shareopts"`
Lines []*LineType `json:"lines"`
Cmds []*CmdType `json:"cmds"`
Remotes []*RemoteInstance `json:"remotes"`
// only for updates
Remove bool `json:"remove,omitempty"`
}
func (w *WindowType) ToMap() map[string]interface{} {
rtn := make(map[string]interface{})
rtn["sessionid"] = w.SessionId
rtn["windowid"] = w.WindowId
rtn["curremoteowneruserid"] = w.CurRemote.OwnerUserId
rtn["curremoteid"] = w.CurRemote.RemoteId
rtn["curremotename"] = w.CurRemote.Name
rtn["winopts"] = quickJson(w.WinOpts)
rtn["owneruserid"] = w.OwnerUserId
rtn["sharemode"] = w.ShareMode
rtn["shareopts"] = quickJson(w.ShareOpts)
return rtn
}
func WindowFromMap(m map[string]interface{}) *WindowType {
if len(m) == 0 {
return nil
}
var w WindowType
quickSetStr(&w.SessionId, m, "sessionid")
quickSetStr(&w.WindowId, m, "windowid")
quickSetStr(&w.CurRemote.OwnerUserId, m, "curremoteowneruserid")
quickSetStr(&w.CurRemote.RemoteId, m, "curremoteid")
quickSetStr(&w.CurRemote.Name, m, "curremotename")
quickSetJson(&w.WinOpts, m, "winopts")
quickSetStr(&w.OwnerUserId, m, "owneruserid")
quickSetStr(&w.ShareMode, m, "sharemode")
quickSetJson(&w.ShareOpts, m, "shareopts")
return &w
}
type ScreenOptsType struct {
TabColor string `json:"tabcolor"`
}
@ -261,8 +318,8 @@ type RemoteInstance struct {
Name string `json:"name"`
SessionId string `json:"sessionid"`
WindowId string `json:"windowid"`
RemoteOwnerUserId string `json:"remoteowneruserid"`
RemoteId string `json:"remoteid"`
SessionScope bool `json:"sessionscope"`
State RemoteState `json:"state"`
// only for updates
@ -292,7 +349,6 @@ type SSHOpts struct {
type RemoteOptsType struct {
Color string `json:"color"`
Prompt string `json:"prompt"`
}
func (opts *RemoteOptsType) Scan(val interface{}) error {
@ -341,7 +397,6 @@ type CmdType struct {
DonePk *packet.CmdDonePacketType `json:"donepk"`
UsedRows int64 `json:"usedrows"`
RunOut []packet.PacketType `json:"runout"`
Prompt string `json:"prompt"`
Remove bool `json:"remove"`
}
@ -397,7 +452,6 @@ func (cmd *CmdType) ToMap() map[string]interface{} {
rtn["donepk"] = quickJson(cmd.DonePk)
rtn["runout"] = quickJson(cmd.RunOut)
rtn["usedrows"] = cmd.UsedRows
rtn["prompt"] = cmd.Prompt
return rtn
}
@ -417,7 +471,6 @@ func CmdFromMap(m map[string]interface{}) *CmdType {
quickSetJson(&cmd.DonePk, m, "donepk")
quickSetJson(&cmd.RunOut, m, "runout")
quickSetInt64(&cmd.UsedRows, m, "usedrows")
quickSetStr(&cmd.Prompt, m, "prompt")
return &cmd
}
@ -488,7 +541,7 @@ func EnsureLocalRemote(ctx context.Context) error {
RemoteId: uuid.New().String(),
PhysicalId: physicalId,
RemoteType: RemoteTypeSsh,
RemoteAlias: LocalRemoteName,
RemoteAlias: LocalRemoteAlias,
RemoteCanonicalName: fmt.Sprintf("%s@%s", user.Username, hostName),
RemoteSudo: false,
RemoteUser: user.Username,
@ -521,6 +574,7 @@ func AddTest01Remote(ctx context.Context) error {
RemoteUser: "ubuntu",
RemoteHost: "test01.ec2",
SSHOpts: &SSHOpts{
Local: false,
SSHHost: "test01.ec2",
SSHUser: "ubuntu",
SSHIdentity: "/Users/mike/aws/mfmt.pem",
@ -552,6 +606,7 @@ func AddTest02Remote(ctx context.Context) error {
RemoteUser: "test2",
RemoteHost: "test01.ec2",
SSHOpts: &SSHOpts{
Local: false,
SSHHost: "test01.ec2",
SSHUser: "test2",
},

View File

@ -5,12 +5,7 @@ import "sync"
var MainBus *UpdateBus = MakeUpdateBus()
const PtyDataUpdateStr = "pty"
const SessionUpdateStr = "session"
const WindowUpdateStr = "window"
const CmdUpdateStr = "cmd"
const LineCmdUpdateStr = "line+cmd"
const InfoUpdateStr = "info"
const CompGenUpdateStr = "compgen"
const ModelUpdateStr = "model"
type UpdatePacket interface {
UpdateType() string
@ -28,56 +23,40 @@ func (PtyDataUpdate) UpdateType() string {
return PtyDataUpdateStr
}
type WindowUpdate struct {
Window WindowType `json:"window"`
Info *InfoMsgType `json:"info,omitempty"`
}
func (WindowUpdate) UpdateType() string {
return WindowUpdateStr
}
type SessionUpdate struct {
type ModelUpdate struct {
Sessions []*SessionType `json:"sessions"`
ActiveSessionId string `json:"activesessionid,omitempty"`
Window WindowType `json:"window"`
Line *LineType `json:"line"`
Cmd *CmdType `json:"cmd,omitempty"`
CmdLine *CmdLineType `json:"cmdline,omitempty"`
Info *InfoMsgType `json:"info,omitempty"`
}
func (SessionUpdate) UpdateType() string {
return SessionUpdateStr
func (ModelUpdate) UpdateType() string {
return ModelUpdateStr
}
func MakeSingleSessionUpdate(sessionId string) (*SessionUpdate, *SessionType) {
func MakeSingleSessionUpdate(sessionId string) (ModelUpdate, *SessionType) {
session := &SessionType{
SessionId: sessionId,
NotifyNum: -1,
}
update := &SessionUpdate{
update := ModelUpdate{
Sessions: []*SessionType{session},
}
return update, session
}
type LineUpdate struct {
Line *LineType `json:"line"`
Cmd *CmdType `json:"cmd,omitempty"`
Remove bool `json:"remove,omitempty"`
Info *InfoMsgType `json:"info,omitempty"`
}
func (LineUpdate) UpdateType() string {
return LineCmdUpdateStr
}
func ReadLineCmdIdFromUpdate(update UpdatePacket) (string, string) {
lineUpdate, ok := update.(LineUpdate)
modelUpdate, ok := update.(ModelUpdate)
if !ok {
return "", ""
}
if lineUpdate.Line == nil {
if modelUpdate.Line == nil {
return "", ""
}
return lineUpdate.Line.LineId, lineUpdate.Line.CmdId
return modelUpdate.Line.LineId, modelUpdate.Line.CmdId
}
type InfoMsgType struct {
@ -95,15 +74,6 @@ type CmdLineType struct {
InsertPos int64 `json:"insertpos"`
}
type InfoUpdate struct {
Info *InfoMsgType `json:"info,omitempty"`
CmdLine *CmdLineType `json:"cmdline,omitempty"`
}
func (InfoUpdate) UpdateType() string {
return InfoUpdateStr
}
type UpdateChannel struct {
SessionId string
ClientId string
@ -167,3 +137,12 @@ func (bus *UpdateBus) SendUpdate(sessionId string, update interface{}) {
}
}
}
func MakeSessionsUpdateForRemote(sessionId string, ri *RemoteInstance) []*SessionType {
return []*SessionType{
&SessionType{
SessionId: sessionId,
Remotes: []*RemoteInstance{ri},
},
}
}