diff --git a/cmd/generatego/main-generatego.go b/cmd/generatego/main-generatego.go index f127b70ba..885a34093 100644 --- a/cmd/generatego/main-generatego.go +++ b/cmd/generatego/main-generatego.go @@ -27,10 +27,8 @@ func GenerateWshClient() error { "github.com/wavetermdev/waveterm/pkg/wshutil", "github.com/wavetermdev/waveterm/pkg/wshrpc", "github.com/wavetermdev/waveterm/pkg/waveobj", - "github.com/wavetermdev/waveterm/pkg/wconfig", "github.com/wavetermdev/waveterm/pkg/wps", "github.com/wavetermdev/waveterm/pkg/vdom", - "github.com/wavetermdev/waveterm/pkg/telemetry", }) wshDeclMap := wshrpc.GenerateWshCommandDeclMap() for _, key := range utilfn.GetOrderedMapKeys(wshDeclMap) { diff --git a/cmd/server/main-server.go b/cmd/server/main-server.go index f5a5442d9..8a7aca7fc 100644 --- a/cmd/server/main-server.go +++ b/cmd/server/main-server.go @@ -113,7 +113,7 @@ func telemetryLoop() { } func panicTelemetryHandler() { - activity := telemetry.ActivityUpdate{NumPanics: 1} + activity := wshrpc.ActivityUpdate{NumPanics: 1} err := telemetry.UpdateActivity(context.Background(), activity) if err != nil { log.Printf("error updating activity (panicTelemetryHandler): %v\n", err) @@ -137,7 +137,7 @@ func sendTelemetryWrapper() { } func beforeSendActivityUpdate(ctx context.Context) { - activity := telemetry.ActivityUpdate{} + activity := wshrpc.ActivityUpdate{} activity.NumTabs, _ = wstore.DBGetCount[*waveobj.Tab](ctx) activity.NumBlocks, _ = wstore.DBGetCount[*waveobj.Block](ctx) activity.Blocks, _ = wstore.DBGetBlockViewCounts(ctx) @@ -153,7 +153,7 @@ func beforeSendActivityUpdate(ctx context.Context) { func startupActivityUpdate() { ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) defer cancelFn() - activity := telemetry.ActivityUpdate{Startup: 1} + activity := wshrpc.ActivityUpdate{Startup: 1} err := telemetry.UpdateActivity(ctx, activity) // set at least one record into activity (don't use go routine wrap here) if err != nil { log.Printf("error updating startup activity: %v\n", err) @@ -163,7 +163,7 @@ func startupActivityUpdate() { func shutdownActivityUpdate() { ctx, cancelFn := context.WithTimeout(context.Background(), 1*time.Second) defer cancelFn() - activity := telemetry.ActivityUpdate{Shutdown: 1} + activity := wshrpc.ActivityUpdate{Shutdown: 1} err := telemetry.UpdateActivity(ctx, activity) // do NOT use the go routine wrap here (this needs to be synchronous) if err != nil { log.Printf("error updating shutdown activity: %v\n", err) diff --git a/cmd/wsh/cmd/wshcmd-conn.go b/cmd/wsh/cmd/wshcmd-conn.go index bdfebcb55..52d76ef67 100644 --- a/cmd/wsh/cmd/wshcmd-conn.go +++ b/cmd/wsh/cmd/wshcmd-conn.go @@ -167,7 +167,7 @@ func connConnectRun(cmd *cobra.Command, args []string) error { if err := validateConnectionName(connName); err != nil { return err } - err := wshclient.ConnConnectCommand(RpcClient, connName, &wshrpc.RpcOpts{Timeout: 60000}) + err := wshclient.ConnConnectCommand(RpcClient, wshrpc.ConnRequest{Host: connName}, &wshrpc.RpcOpts{Timeout: 60000}) if err != nil { return fmt.Errorf("connecting connection: %w", err) } diff --git a/cmd/wsh/cmd/wshcmd-setconfig.go b/cmd/wsh/cmd/wshcmd-setconfig.go index b076d89bb..77fd84e70 100644 --- a/cmd/wsh/cmd/wshcmd-setconfig.go +++ b/cmd/wsh/cmd/wshcmd-setconfig.go @@ -7,7 +7,6 @@ import ( "fmt" "github.com/spf13/cobra" - "github.com/wavetermdev/waveterm/pkg/wconfig" "github.com/wavetermdev/waveterm/pkg/wshrpc" "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient" ) @@ -34,7 +33,7 @@ func setConfigRun(cmd *cobra.Command, args []string) (rtnErr error) { if err != nil { return err } - commandData := wconfig.MetaSettingsType{MetaMapType: meta} + commandData := wshrpc.MetaSettingsType{MetaMapType: meta} err = wshclient.SetConfigCommand(RpcClient, commandData, &wshrpc.RpcOpts{Timeout: 2000}) if err != nil { return fmt.Errorf("setting config: %w", err) diff --git a/cmd/wsh/cmd/wshcmd-ssh.go b/cmd/wsh/cmd/wshcmd-ssh.go index daa7cdf84..51833fc2a 100644 --- a/cmd/wsh/cmd/wshcmd-ssh.go +++ b/cmd/wsh/cmd/wshcmd-ssh.go @@ -12,6 +12,8 @@ import ( "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient" ) +var identityFiles []string + var sshCmd = &cobra.Command{ Use: "ssh", Short: "connect this terminal to a remote host", @@ -21,6 +23,7 @@ var sshCmd = &cobra.Command{ } func init() { + sshCmd.Flags().StringArrayVarP(&identityFiles, "identityfile", "i", []string{}, "add an identity file for publickey authentication") rootCmd.AddCommand(sshCmd) } @@ -34,6 +37,16 @@ func sshRun(cmd *cobra.Command, args []string) (rtnErr error) { if blockId == "" { return fmt.Errorf("cannot determine blockid (not in JWT)") } + // first, make a connection independent of the block + connOpts := wshrpc.ConnRequest{ + Host: sshArg, + Keywords: wshrpc.ConnKeywords{ + SshIdentityFile: identityFiles, + }, + } + wshclient.ConnConnectCommand(RpcClient, connOpts, nil) + + // now, with that made, it will be straightforward to connect data := wshrpc.CommandSetMetaData{ ORef: waveobj.MakeORef(waveobj.OType_Block, blockId), Meta: map[string]any{ diff --git a/frontend/app/block/blockframe.tsx b/frontend/app/block/blockframe.tsx index 16c59db14..b3e93d22f 100644 --- a/frontend/app/block/blockframe.tsx +++ b/frontend/app/block/blockframe.tsx @@ -341,7 +341,7 @@ const ConnStatusOverlay = React.memo( }, [width, connStatus, setShowError]); const handleTryReconnect = React.useCallback(() => { - const prtn = RpcApi.ConnConnectCommand(TabRpcClient, connName, { timeout: 60000 }); + const prtn = RpcApi.ConnConnectCommand(TabRpcClient, { host: connName }, { timeout: 60000 }); prtn.catch((e) => console.log("error reconnecting", connName, e)); }, [connName]); @@ -673,7 +673,11 @@ const ChangeConnectionBlockModal = React.memo( label: `Reconnect to ${connStatus.connection}`, value: "", onSelect: async (_: string) => { - const prtn = RpcApi.ConnConnectCommand(TabRpcClient, connStatus.connection, { timeout: 60000 }); + const prtn = RpcApi.ConnConnectCommand( + TabRpcClient, + { host: connStatus.connection }, + { timeout: 60000 } + ); prtn.catch((e) => console.log("error reconnecting", connStatus.connection, e)); }, }; diff --git a/frontend/app/modals/userinputmodal.scss b/frontend/app/modals/userinputmodal.scss index 6cf2f5595..6ec38c048 100644 --- a/frontend/app/modals/userinputmodal.scss +++ b/frontend/app/modals/userinputmodal.scss @@ -45,11 +45,17 @@ .userinput-checkbox-container { display: flex; - align-items: center; + flex-direction: column; gap: 6px; - .userinput-checkbox { - accent-color: var(--accent-color); + .userinput-checkbox-row { + display: flex; + align-items: center; + gap: 6px; + + .userinput-checkbox { + accent-color: var(--accent-color); + } } } } diff --git a/frontend/app/modals/userinputmodal.tsx b/frontend/app/modals/userinputmodal.tsx index 5721d78de..daeec9755 100644 --- a/frontend/app/modals/userinputmodal.tsx +++ b/frontend/app/modals/userinputmodal.tsx @@ -15,7 +15,7 @@ const UserInputModal = (userInputRequest: UserInputRequest) => { const [countdown, setCountdown] = useState(Math.floor(userInputRequest.timeoutms / 1000)); const checkboxRef = useRef(); - const handleSendCancel = useCallback(() => { + const handleSendErrResponse = useCallback(() => { UserInputService.SendUserInputResponse({ type: "userinputresp", requestid: userInputRequest.requestid, @@ -29,20 +29,24 @@ const UserInputModal = (userInputRequest: UserInputRequest) => { type: "userinputresp", requestid: userInputRequest.requestid, text: responseText, - checkboxstat: checkboxRef.current?.checked ?? false, + checkboxstat: checkboxRef?.current?.checked ?? false, }); modalsModel.popModal(); }, [responseText, userInputRequest]); + console.log("bar"); - const handleSendConfirm = useCallback(() => { - UserInputService.SendUserInputResponse({ - type: "userinputresp", - requestid: userInputRequest.requestid, - confirm: true, - checkboxstat: checkboxRef.current?.checked ?? false, - }); - modalsModel.popModal(); - }, [userInputRequest]); + const handleSendConfirm = useCallback( + (response: boolean) => { + UserInputService.SendUserInputResponse({ + type: "userinputresp", + requestid: userInputRequest.requestid, + confirm: response, + checkboxstat: checkboxRef?.current?.checked ?? false, + }); + modalsModel.popModal(); + }, + [userInputRequest] + ); const handleSubmit = useCallback(() => { switch (userInputRequest.responsetype) { @@ -50,15 +54,16 @@ const UserInputModal = (userInputRequest: UserInputRequest) => { handleSendText(); break; case "confirm": - handleSendConfirm(); + handleSendConfirm(true); break; } }, [handleSendConfirm, handleSendText, userInputRequest.responsetype]); + console.log("baz"); const handleKeyDown = useCallback( (waveEvent: WaveKeyboardEvent): boolean => { if (keyutil.checkKeyPressed(waveEvent, "Escape")) { - handleSendCancel(); + handleSendErrResponse(); return; } if (keyutil.checkKeyPressed(waveEvent, "Enter")) { @@ -66,7 +71,7 @@ const UserInputModal = (userInputRequest: UserInputRequest) => { return true; } }, - [handleSendCancel, handleSubmit] + [handleSendErrResponse, handleSubmit] ); const queryText = useMemo(() => { @@ -75,6 +80,7 @@ const UserInputModal = (userInputRequest: UserInputRequest) => { } return {userInputRequest.querytext}; }, [userInputRequest.markdown, userInputRequest.querytext]); + console.log("foobarbaz"); const inputBox = useMemo(() => { if (userInputRequest.responsetype === "confirm") { @@ -92,6 +98,7 @@ const UserInputModal = (userInputRequest: UserInputRequest) => { /> ); }, [userInputRequest.responsetype, userInputRequest.publictext, responseText, handleKeyDown, setResponseText]); + console.log("mem1"); const optionalCheckbox = useMemo(() => { if (userInputRequest.checkboxmsg == "") { @@ -99,22 +106,25 @@ const UserInputModal = (userInputRequest: UserInputRequest) => { } return (
- - +
+ + +
); }, []); + console.log("mem2"); useEffect(() => { let timeout: ReturnType; if (countdown <= 0) { timeout = setTimeout(() => { - handleSendCancel(); + handleSendErrResponse(); }, 300); } else { timeout = setTimeout(() => { @@ -123,9 +133,28 @@ const UserInputModal = (userInputRequest: UserInputRequest) => { } return () => clearTimeout(timeout); }, [countdown]); + console.log("count"); + + const handleNegativeResponse = useCallback(() => { + switch (userInputRequest.responsetype) { + case "text": + handleSendErrResponse(); + break; + case "confirm": + handleSendConfirm(false); + break; + } + }, [userInputRequest.responsetype, handleSendErrResponse, handleSendConfirm]); + console.log("before end"); return ( - handleSubmit()} onCancel={() => handleSendCancel()} onClose={() => handleSendCancel()}> + handleSubmit()} + onCancel={() => handleNegativeResponse()} + onClose={() => handleSendErrResponse()} + okLabel={userInputRequest.oklabel} + cancelLabel={userInputRequest.cancellabel} + >
{userInputRequest.title + ` (${countdown}s)`}
{queryText} diff --git a/frontend/app/store/wshclientapi.ts b/frontend/app/store/wshclientapi.ts index 7c8b693fc..93bcd906a 100644 --- a/frontend/app/store/wshclientapi.ts +++ b/frontend/app/store/wshclientapi.ts @@ -28,7 +28,7 @@ class RpcApiType { } // command "connconnect" [call] - ConnConnectCommand(client: WshClient, data: string, opts?: RpcOpts): Promise { + ConnConnectCommand(client: WshClient, data: ConnRequest, opts?: RpcOpts): Promise { return client.wshRpcCall("connconnect", data, opts); } diff --git a/frontend/types/gotypes.d.ts b/frontend/types/gotypes.d.ts index 1dca3f680..188f895b4 100644 --- a/frontend/types/gotypes.d.ts +++ b/frontend/types/gotypes.d.ts @@ -5,7 +5,7 @@ declare global { - // telemetry.ActivityDisplayType + // wshrpc.ActivityDisplayType type ActivityDisplayType = { width: number; height: number; @@ -13,7 +13,7 @@ declare global { internal?: boolean; }; - // telemetry.ActivityUpdate + // wshrpc.ActivityUpdate type ActivityUpdate = { fgminutes?: number; activeminutes?: number; @@ -275,9 +275,36 @@ declare global { err: string; }; + // wshrpc.ConnKeywords + type ConnKeywords = { + wshenabled?: boolean; + askbeforewshinstall?: boolean; + "ssh:user"?: string; + "ssh:hostname"?: string; + "ssh:port"?: string; + "ssh:identityfile"?: string[]; + "ssh:batchmode"?: boolean; + "ssh:pubkeyauthentication"?: boolean; + "ssh:passwordauthentication"?: boolean; + "ssh:kbdinteractiveauthentication"?: boolean; + "ssh:preferredauthentications"?: string[]; + "ssh:addkeystoagent"?: boolean; + "ssh:identityagent"?: string; + "ssh:proxyjump"?: string[]; + "ssh:userknownhostsfile"?: string[]; + "ssh:globalknownhostsfile"?: string[]; + }; + + // wshrpc.ConnRequest + type ConnRequest = { + host: string; + keywords?: ConnKeywords; + }; + // wshrpc.ConnStatus type ConnStatus = { status: string; + wshenabled: boolean; connection: string; connected: boolean; hasconnected: boolean; @@ -337,9 +364,11 @@ declare global { type FullConfigType = { settings: SettingsType; mimetypes: {[key: string]: MimeTypeConfigType}; + defaultwidgets: {[key: string]: WidgetConfigType}; widgets: {[key: string]: WidgetConfigType}; presets: {[key: string]: MetaType}; termthemes: {[key: string]: TermThemeType}; + connections: {[key: string]: ConnKeywords}; configerrors: ConfigError[]; }; @@ -608,6 +637,7 @@ declare global { "telemetry:enabled"?: boolean; "conn:*"?: boolean; "conn:askbeforewshinstall"?: boolean; + "conn:wshenabled"?: boolean; }; // waveobj.StickerClickOptsType @@ -701,6 +731,8 @@ declare global { timeoutms: number; checkboxmsg: string; publictext: boolean; + oklabel?: string; + cancellabel?: string; }; // userinput.UserInputResponse diff --git a/pkg/blockcontroller/blockcontroller.go b/pkg/blockcontroller/blockcontroller.go index befaf8517..ac1672c38 100644 --- a/pkg/blockcontroller/blockcontroller.go +++ b/pkg/blockcontroller/blockcontroller.go @@ -296,7 +296,7 @@ func (bc *BlockController) DoRunShellCommand(rc *RunShellOpts, blockMeta waveobj if err != nil { return err } - conn := conncontroller.GetConn(credentialCtx, opts, false) + conn := conncontroller.GetConn(credentialCtx, opts, false, &wshrpc.ConnKeywords{}) connStatus := conn.DeriveConnStatus() if connStatus.Status != conncontroller.Status_Connected { return fmt.Errorf("not connected, cannot start shellproc") @@ -538,7 +538,7 @@ func CheckConnStatus(blockId string) error { if err != nil { return fmt.Errorf("error parsing connection name: %w", err) } - conn := conncontroller.GetConn(context.Background(), opts, false) + conn := conncontroller.GetConn(context.Background(), opts, false, &wshrpc.ConnKeywords{}) connStatus := conn.DeriveConnStatus() if connStatus.Status != conncontroller.Status_Connected { return fmt.Errorf("not connected: %s", connStatus.Status) diff --git a/pkg/remote/conncontroller/conncontroller.go b/pkg/remote/conncontroller/conncontroller.go index b03507e2e..32d2d02c5 100644 --- a/pkg/remote/conncontroller/conncontroller.go +++ b/pkg/remote/conncontroller/conncontroller.go @@ -52,6 +52,7 @@ var activeConnCounter = &atomic.Int32{} type SSHConn struct { Lock *sync.Mutex Status string + WshEnabled *atomic.Bool Opts *remote.SSHOpts Client *ssh.Client SockName string @@ -290,6 +291,12 @@ type WshInstallOpts struct { NoUserPrompt bool } +type WshInstallSkipError struct{} + +func (wise *WshInstallSkipError) Error() string { + return "skipping wsh installation" +} + func (conn *SSHConn) CheckAndInstallWsh(ctx context.Context, clientDisplayName string, opts *WshInstallOpts) error { if opts == nil { opts = &WshInstallOpts{} @@ -325,12 +332,23 @@ func (conn *SSHConn) CheckAndInstallWsh(ctx context.Context, clientDisplayName s QueryText: queryText, Title: title, Markdown: true, - CheckBoxMsg: "Don't show me this again", + CheckBoxMsg: "Automatically install for all connections", + OkLabel: "Install wsh", + CancelLabel: "No wsh", } response, err := userinput.GetUserInput(ctx, request) - if err != nil || !response.Confirm { + if err != nil { return err } + if !response.Confirm { + meta := make(map[string]any) + meta["wshenabled"] = false + err = wconfig.SetConnectionsConfigValue(conn.GetName(), meta) + if err != nil { + log.Printf("warning: error writing to connections file: %v", err) + } + return &WshInstallSkipError{} + } if response.CheckboxStat { meta := waveobj.MetaMapType{ wconfig.ConfigKey_ConnAskBeforeWshInstall: false, @@ -371,7 +389,7 @@ func (conn *SSHConn) Reconnect(ctx context.Context) error { if err != nil { return err } - return conn.Connect(ctx) + return conn.Connect(ctx, &wshrpc.ConnKeywords{}) } func (conn *SSHConn) WaitForConnect(ctx context.Context) error { @@ -399,7 +417,7 @@ func (conn *SSHConn) WaitForConnect(ctx context.Context) error { } // does not return an error since that error is stored inside of SSHConn -func (conn *SSHConn) Connect(ctx context.Context) error { +func (conn *SSHConn) Connect(ctx context.Context, connFlags *wshrpc.ConnKeywords) error { var connectAllowed bool conn.WithLock(func() { if conn.Status == Status_Connecting || conn.Status == Status_Connected { @@ -415,13 +433,13 @@ func (conn *SSHConn) Connect(ctx context.Context) error { return fmt.Errorf("cannot connect to %q when status is %q", conn.GetName(), conn.GetStatus()) } conn.FireConnChangeEvent() - err := conn.connectInternal(ctx) + err := conn.connectInternal(ctx, connFlags) conn.WithLock(func() { if err != nil { conn.Status = Status_Error conn.Error = err.Error() conn.close_nolock() - telemetry.GoUpdateActivityWrap(telemetry.ActivityUpdate{ + telemetry.GoUpdateActivityWrap(wshrpc.ActivityUpdate{ Conn: map[string]int{"ssh:connecterror": 1}, }, "ssh-connconnect") } else { @@ -430,7 +448,7 @@ func (conn *SSHConn) Connect(ctx context.Context) error { if conn.ActiveConnNum == 0 { conn.ActiveConnNum = int(activeConnCounter.Add(1)) } - telemetry.GoUpdateActivityWrap(telemetry.ActivityUpdate{ + telemetry.GoUpdateActivityWrap(wshrpc.ActivityUpdate{ Conn: map[string]int{"ssh:connect": 1}, }, "ssh-connconnect") } @@ -445,8 +463,8 @@ func (conn *SSHConn) WithLock(fn func()) { fn() } -func (conn *SSHConn) connectInternal(ctx context.Context) error { - client, _, err := remote.ConnectToClient(ctx, conn.Opts, nil, 0) +func (conn *SSHConn) connectInternal(ctx context.Context, connFlags *wshrpc.ConnKeywords) error { + client, _, err := remote.ConnectToClient(ctx, conn.Opts, nil, 0, connFlags) if err != nil { log.Printf("error: failed to connect to client %s: %s\n", conn.GetName(), err) return err @@ -462,15 +480,40 @@ func (conn *SSHConn) connectInternal(ctx context.Context) error { return err } config := wconfig.ReadFullConfig() - installErr := conn.CheckAndInstallWsh(ctx, clientDisplayName, &WshInstallOpts{NoUserPrompt: !config.Settings.ConnAskBeforeWshInstall}) - if installErr != nil { - log.Printf("error: unable to install wsh shell extensions for %s: %v\n", conn.GetName(), err) - return fmt.Errorf("conncontroller %s wsh install error: %v", conn.GetName(), installErr) + enableWsh := config.Settings.ConnWshEnabled + askBeforeInstall := config.Settings.ConnAskBeforeWshInstall + connSettings, ok := config.Connections[conn.GetName()] + if ok { + if connSettings.WshEnabled != nil { + enableWsh = *connSettings.WshEnabled + } + if connSettings.AskBeforeWshInstall != nil { + askBeforeInstall = *connSettings.AskBeforeWshInstall + } } - csErr := conn.StartConnServer() - if csErr != nil { - log.Printf("error: unable to start conn server for %s: %v\n", conn.GetName(), csErr) - return fmt.Errorf("conncontroller %s start wsh connserver error: %v", conn.GetName(), csErr) + if enableWsh { + installErr := conn.CheckAndInstallWsh(ctx, clientDisplayName, &WshInstallOpts{NoUserPrompt: !askBeforeInstall}) + if errors.Is(installErr, &WshInstallSkipError{}) { + // skips are not true errors + conn.WithLock(func() { + conn.WshEnabled.Store(false) + }) + } else if installErr != nil { + log.Printf("error: unable to install wsh shell extensions for %s: %v\n", conn.GetName(), err) + return fmt.Errorf("conncontroller %s wsh install error: %v", conn.GetName(), installErr) + } else { + conn.WshEnabled.Store(true) + } + + if conn.WshEnabled.Load() { + csErr := conn.StartConnServer() + if csErr != nil { + log.Printf("error: unable to start conn server for %s: %v\n", conn.GetName(), csErr) + return fmt.Errorf("conncontroller %s start wsh connserver error: %v", conn.GetName(), csErr) + } + } + } else { + conn.WshEnabled.Store(false) } conn.HasWaiter.Store(true) go conn.waitForDisconnect() @@ -504,16 +547,16 @@ func getConnInternal(opts *remote.SSHOpts) *SSHConn { defer globalLock.Unlock() rtn := clientControllerMap[*opts] if rtn == nil { - rtn = &SSHConn{Lock: &sync.Mutex{}, Status: Status_Init, Opts: opts, HasWaiter: &atomic.Bool{}} + rtn = &SSHConn{Lock: &sync.Mutex{}, Status: Status_Init, WshEnabled: &atomic.Bool{}, Opts: opts, HasWaiter: &atomic.Bool{}} clientControllerMap[*opts] = rtn } return rtn } -func GetConn(ctx context.Context, opts *remote.SSHOpts, shouldConnect bool) *SSHConn { +func GetConn(ctx context.Context, opts *remote.SSHOpts, shouldConnect bool, connFlags *wshrpc.ConnKeywords) *SSHConn { conn := getConnInternal(opts) if conn.Client == nil && shouldConnect { - conn.Connect(ctx) + conn.Connect(ctx, connFlags) } return conn } @@ -527,7 +570,7 @@ func EnsureConnection(ctx context.Context, connName string) error { if err != nil { return fmt.Errorf("error parsing connection name: %w", err) } - conn := GetConn(ctx, connOpts, false) + conn := GetConn(ctx, connOpts, false, &wshrpc.ConnKeywords{}) if conn == nil { return fmt.Errorf("connection not found: %s", connName) } @@ -538,7 +581,7 @@ func EnsureConnection(ctx context.Context, connName string) error { case Status_Connecting: return conn.WaitForConnect(ctx) case Status_Init, Status_Disconnected: - return conn.Connect(ctx) + return conn.Connect(ctx, &wshrpc.ConnKeywords{}) case Status_Error: return fmt.Errorf("connection error: %s", connStatus.Error) default: diff --git a/pkg/remote/sshclient.go b/pkg/remote/sshclient.go index 69fe5cb10..134873073 100644 --- a/pkg/remote/sshclient.go +++ b/pkg/remote/sshclient.go @@ -17,7 +17,6 @@ import ( "os/exec" "os/user" "path/filepath" - "strconv" "strings" "sync" "time" @@ -28,6 +27,7 @@ import ( "github.com/wavetermdev/waveterm/pkg/userinput" "github.com/wavetermdev/waveterm/pkg/util/shellutil" "github.com/wavetermdev/waveterm/pkg/wavebase" + "github.com/wavetermdev/waveterm/pkg/wshrpc" "golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh/agent" xknownhosts "golang.org/x/crypto/ssh/knownhosts" @@ -101,13 +101,13 @@ func createDummySigner() ([]ssh.Signer, error) { // they were successes. An error in this function prevents any other // keys from being attempted. But if there's an error because of a dummy // file, the library can still try again with a new key. -func createPublicKeyCallback(connCtx context.Context, sshKeywords *SshKeywords, authSockSignersExt []ssh.Signer, agentClient agent.ExtendedAgent, debugInfo *ConnectionDebugInfo) func() ([]ssh.Signer, error) { +func createPublicKeyCallback(connCtx context.Context, sshKeywords *wshrpc.ConnKeywords, authSockSignersExt []ssh.Signer, agentClient agent.ExtendedAgent, debugInfo *ConnectionDebugInfo) func() ([]ssh.Signer, error) { var identityFiles []string existingKeys := make(map[string][]byte) // checking the file early prevents us from needing to send a // dummy signer if there's a problem with the signer - for _, identityFile := range sshKeywords.IdentityFile { + for _, identityFile := range sshKeywords.SshIdentityFile { filePath, err := wavebase.ExpandHomeDir(identityFile) if err != nil { continue @@ -151,7 +151,7 @@ func createPublicKeyCallback(connCtx context.Context, sshKeywords *SshKeywords, if err == nil { signer, err := ssh.NewSignerFromKey(unencryptedPrivateKey) if err == nil { - if sshKeywords.AddKeysToAgent && agentClient != nil { + if sshKeywords.SshAddKeysToAgent && agentClient != nil { agentClient.Add(agent.AddedKey{ PrivateKey: unencryptedPrivateKey, }) @@ -165,7 +165,7 @@ func createPublicKeyCallback(connCtx context.Context, sshKeywords *SshKeywords, } // batch mode deactivates user input - if sshKeywords.BatchMode { + if sshKeywords.SshBatchMode { // skip this key and try with the next return createDummySigner() } @@ -194,7 +194,7 @@ func createPublicKeyCallback(connCtx context.Context, sshKeywords *SshKeywords, // skip this key and try with the next return createDummySigner() } - if sshKeywords.AddKeysToAgent && agentClient != nil { + if sshKeywords.SshAddKeysToAgent && agentClient != nil { agentClient.Add(agent.AddedKey{ PrivateKey: unencryptedPrivateKey, }) @@ -333,7 +333,14 @@ func createUnknownKeyVerifier(knownHostsFile string, hostname string, remote str return func() (*userinput.UserInputResponse, error) { ctx, cancelFn := context.WithTimeout(context.Background(), 60*time.Second) defer cancelFn() - return userinput.GetUserInput(ctx, request) + resp, err := userinput.GetUserInput(ctx, request) + if err != nil { + return nil, err + } + if !resp.Confirm { + return nil, fmt.Errorf("user selected no") + } + return resp, nil } } @@ -357,7 +364,14 @@ func createMissingKnownHostsVerifier(knownHostsFile string, hostname string, rem return func() (*userinput.UserInputResponse, error) { ctx, cancelFn := context.WithTimeout(context.Background(), 60*time.Second) defer cancelFn() - return userinput.GetUserInput(ctx, request) + resp, err := userinput.GetUserInput(ctx, request) + if err != nil { + return nil, err + } + if !resp.Confirm { + return nil, fmt.Errorf("user selected no") + } + return resp, nil } } @@ -370,9 +384,9 @@ func lineContainsMatch(line []byte, matches [][]byte) bool { return false } -func createHostKeyCallback(sshKeywords *SshKeywords) (ssh.HostKeyCallback, HostKeyAlgorithms, error) { - globalKnownHostsFiles := sshKeywords.GlobalKnownHostsFile - userKnownHostsFiles := sshKeywords.UserKnownHostsFile +func createHostKeyCallback(sshKeywords *wshrpc.ConnKeywords) (ssh.HostKeyCallback, HostKeyAlgorithms, error) { + globalKnownHostsFiles := sshKeywords.SshGlobalKnownHostsFile + userKnownHostsFiles := sshKeywords.SshUserKnownHostsFile osUser, err := user.Current() if err != nil { @@ -536,12 +550,12 @@ func createHostKeyCallback(sshKeywords *SshKeywords) (ssh.HostKeyCallback, HostK return waveHostKeyCallback, hostKeyAlgorithms, nil } -func createClientConfig(connCtx context.Context, sshKeywords *SshKeywords, debugInfo *ConnectionDebugInfo) (*ssh.ClientConfig, error) { - remoteName := sshKeywords.User + "@" + xknownhosts.Normalize(sshKeywords.HostName+":"+sshKeywords.Port) +func createClientConfig(connCtx context.Context, sshKeywords *wshrpc.ConnKeywords, debugInfo *ConnectionDebugInfo) (*ssh.ClientConfig, error) { + remoteName := sshKeywords.SshUser + "@" + xknownhosts.Normalize(sshKeywords.SshHostName+":"+sshKeywords.SshPort) var authSockSigners []ssh.Signer var agentClient agent.ExtendedAgent - conn, err := net.Dial("unix", sshKeywords.IdentityAgent) + conn, err := net.Dial("unix", sshKeywords.SshIdentityAgent) if err != nil { log.Printf("Failed to open Identity Agent Socket: %v", err) } else { @@ -555,20 +569,20 @@ func createClientConfig(connCtx context.Context, sshKeywords *SshKeywords, debug // exclude gssapi-with-mic and hostbased until implemented authMethodMap := map[string]ssh.AuthMethod{ - "publickey": ssh.RetryableAuthMethod(publicKeyCallback, len(sshKeywords.IdentityFile)+len(authSockSigners)), + "publickey": ssh.RetryableAuthMethod(publicKeyCallback, len(sshKeywords.SshIdentityFile)+len(authSockSigners)), "keyboard-interactive": ssh.RetryableAuthMethod(keyboardInteractive, 1), "password": ssh.RetryableAuthMethod(passwordCallback, 1), } // note: batch mode turns off interactive input authMethodActiveMap := map[string]bool{ - "publickey": sshKeywords.PubkeyAuthentication, - "keyboard-interactive": sshKeywords.KbdInteractiveAuthentication && !sshKeywords.BatchMode, - "password": sshKeywords.PasswordAuthentication && !sshKeywords.BatchMode, + "publickey": sshKeywords.SshPubkeyAuthentication, + "keyboard-interactive": sshKeywords.SshKbdInteractiveAuthentication && !sshKeywords.SshBatchMode, + "password": sshKeywords.SshPasswordAuthentication && !sshKeywords.SshBatchMode, } var authMethods []ssh.AuthMethod - for _, authMethodName := range sshKeywords.PreferredAuthentications { + for _, authMethodName := range sshKeywords.SshPreferredAuthentications { authMethodActive, ok := authMethodActiveMap[authMethodName] if !ok || !authMethodActive { continue @@ -585,9 +599,9 @@ func createClientConfig(connCtx context.Context, sshKeywords *SshKeywords, debug return nil, err } - networkAddr := sshKeywords.HostName + ":" + sshKeywords.Port + networkAddr := sshKeywords.SshHostName + ":" + sshKeywords.SshPort return &ssh.ClientConfig{ - User: sshKeywords.User, + User: sshKeywords.SshUser, Auth: authMethods, HostKeyCallback: hostKeyCallback, HostKeyAlgorithms: hostKeyAlgorithms(networkAddr), @@ -616,7 +630,7 @@ func connectInternal(ctx context.Context, networkAddr string, clientConfig *ssh. return ssh.NewClient(c, chans, reqs), nil } -func ConnectToClient(connCtx context.Context, opts *SSHOpts, currentClient *ssh.Client, jumpNum int32) (*ssh.Client, int32, error) { +func ConnectToClient(connCtx context.Context, opts *SSHOpts, currentClient *ssh.Client, jumpNum int32, connFlags *wshrpc.ConnKeywords) (*ssh.Client, int32, error) { debugInfo := &ConnectionDebugInfo{ CurrentClient: currentClient, NextOpts: opts, @@ -631,12 +645,16 @@ func ConnectToClient(connCtx context.Context, opts *SSHOpts, currentClient *ssh. return nil, debugInfo.JumpNum, ConnectionError{ConnectionDebugInfo: debugInfo, Err: err} } - sshKeywords, err := combineSshKeywords(opts, sshConfigKeywords) + connFlags.SshUser = opts.SSHUser + connFlags.SshHostName = opts.SSHHost + connFlags.SshPort = fmt.Sprintf("%d", opts.SSHPort) + + sshKeywords, err := combineSshKeywords(connFlags, sshConfigKeywords) if err != nil { return nil, debugInfo.JumpNum, ConnectionError{ConnectionDebugInfo: debugInfo, Err: err} } - for _, proxyName := range sshKeywords.ProxyJump { + for _, proxyName := range sshKeywords.SshProxyJump { proxyOpts, err := ParseOpts(proxyName) if err != nil { return nil, debugInfo.JumpNum, ConnectionError{ConnectionDebugInfo: debugInfo, Err: err} @@ -647,7 +665,8 @@ func ConnectToClient(connCtx context.Context, opts *SSHOpts, currentClient *ssh. jumpNum += 1 } - debugInfo.CurrentClient, jumpNum, err = ConnectToClient(connCtx, proxyOpts, debugInfo.CurrentClient, jumpNum) + // do not apply supplied keywords to proxies - ssh config must be used for that + debugInfo.CurrentClient, jumpNum, err = ConnectToClient(connCtx, proxyOpts, debugInfo.CurrentClient, jumpNum, &wshrpc.ConnKeywords{}) if err != nil { // do not add a context on a recursive call // (this can cause a recursive nested context that's arbitrarily deep) @@ -658,7 +677,7 @@ func ConnectToClient(connCtx context.Context, opts *SSHOpts, currentClient *ssh. if err != nil { return nil, debugInfo.JumpNum, ConnectionError{ConnectionDebugInfo: debugInfo, Err: err} } - networkAddr := sshKeywords.HostName + ":" + sshKeywords.Port + networkAddr := sshKeywords.SshHostName + ":" + sshKeywords.SshPort client, err := connectInternal(connCtx, networkAddr, clientConfig, debugInfo.CurrentClient) if err != nil { return client, debugInfo.JumpNum, ConnectionError{ConnectionDebugInfo: debugInfo, Err: err} @@ -666,68 +685,51 @@ func ConnectToClient(connCtx context.Context, opts *SSHOpts, currentClient *ssh. return client, debugInfo.JumpNum, nil } -type SshKeywords struct { - User string - HostName string - Port string - IdentityFile []string - BatchMode bool - PubkeyAuthentication bool - PasswordAuthentication bool - KbdInteractiveAuthentication bool - PreferredAuthentications []string - AddKeysToAgent bool - IdentityAgent string - ProxyJump []string - UserKnownHostsFile []string - GlobalKnownHostsFile []string -} +func combineSshKeywords(userProvidedOpts *wshrpc.ConnKeywords, configKeywords *wshrpc.ConnKeywords) (*wshrpc.ConnKeywords, error) { + sshKeywords := &wshrpc.ConnKeywords{} -func combineSshKeywords(opts *SSHOpts, configKeywords *SshKeywords) (*SshKeywords, error) { - sshKeywords := &SshKeywords{} - - if opts.SSHUser != "" { - sshKeywords.User = opts.SSHUser - } else if configKeywords.User != "" { - sshKeywords.User = configKeywords.User + if userProvidedOpts.SshUser != "" { + sshKeywords.SshUser = userProvidedOpts.SshUser + } else if configKeywords.SshUser != "" { + sshKeywords.SshUser = configKeywords.SshUser } else { user, err := user.Current() if err != nil { return nil, fmt.Errorf("failed to get user for ssh: %+v", err) } - sshKeywords.User = user.Username + sshKeywords.SshUser = user.Username } // we have to check the host value because of the weird way // we store the pattern as the hostname for imported remotes - if configKeywords.HostName != "" { - sshKeywords.HostName = configKeywords.HostName + if configKeywords.SshHostName != "" { + sshKeywords.SshHostName = configKeywords.SshHostName } else { - sshKeywords.HostName = opts.SSHHost + sshKeywords.SshHostName = userProvidedOpts.SshHostName } - if opts.SSHPort != 0 && opts.SSHPort != 22 { - sshKeywords.Port = strconv.Itoa(opts.SSHPort) - } else if configKeywords.Port != "" && configKeywords.Port != "22" { - sshKeywords.Port = configKeywords.Port + if userProvidedOpts.SshPort != "0" && userProvidedOpts.SshPort != "22" { + sshKeywords.SshPort = userProvidedOpts.SshPort + } else if configKeywords.SshPort != "" && configKeywords.SshPort != "22" { + sshKeywords.SshPort = configKeywords.SshPort } else { - sshKeywords.Port = "22" + sshKeywords.SshPort = "22" } - sshKeywords.IdentityFile = configKeywords.IdentityFile + sshKeywords.SshIdentityFile = append(userProvidedOpts.SshIdentityFile, configKeywords.SshIdentityFile...) // these are not officially supported in the waveterm frontend but can be configured // in ssh config files - sshKeywords.BatchMode = configKeywords.BatchMode - sshKeywords.PubkeyAuthentication = configKeywords.PubkeyAuthentication - sshKeywords.PasswordAuthentication = configKeywords.PasswordAuthentication - sshKeywords.KbdInteractiveAuthentication = configKeywords.KbdInteractiveAuthentication - sshKeywords.PreferredAuthentications = configKeywords.PreferredAuthentications - sshKeywords.AddKeysToAgent = configKeywords.AddKeysToAgent - sshKeywords.IdentityAgent = configKeywords.IdentityAgent - sshKeywords.ProxyJump = configKeywords.ProxyJump - sshKeywords.UserKnownHostsFile = configKeywords.UserKnownHostsFile - sshKeywords.GlobalKnownHostsFile = configKeywords.GlobalKnownHostsFile + sshKeywords.SshBatchMode = configKeywords.SshBatchMode + sshKeywords.SshPubkeyAuthentication = configKeywords.SshPubkeyAuthentication + sshKeywords.SshPasswordAuthentication = configKeywords.SshPasswordAuthentication + sshKeywords.SshKbdInteractiveAuthentication = configKeywords.SshKbdInteractiveAuthentication + sshKeywords.SshPreferredAuthentications = configKeywords.SshPreferredAuthentications + sshKeywords.SshAddKeysToAgent = configKeywords.SshAddKeysToAgent + sshKeywords.SshIdentityAgent = configKeywords.SshIdentityAgent + sshKeywords.SshProxyJump = configKeywords.SshProxyJump + sshKeywords.SshUserKnownHostsFile = configKeywords.SshUserKnownHostsFile + sshKeywords.SshGlobalKnownHostsFile = configKeywords.SshGlobalKnownHostsFile return sshKeywords, nil } @@ -735,59 +737,60 @@ func combineSshKeywords(opts *SSHOpts, configKeywords *SshKeywords) (*SshKeyword // note that a `var == "yes"` will default to false // but `var != "no"` will default to true // when given unexpected strings -func findSshConfigKeywords(hostPattern string) (*SshKeywords, error) { +func findSshConfigKeywords(hostPattern string) (*wshrpc.ConnKeywords, error) { WaveSshConfigUserSettings().ReloadConfigs() - sshKeywords := &SshKeywords{} + sshKeywords := &wshrpc.ConnKeywords{} var err error + //config := wconfig.ReadFullConfig() userRaw, err := WaveSshConfigUserSettings().GetStrict(hostPattern, "User") if err != nil { return nil, err } - sshKeywords.User = trimquotes.TryTrimQuotes(userRaw) + sshKeywords.SshUser = trimquotes.TryTrimQuotes(userRaw) hostNameRaw, err := WaveSshConfigUserSettings().GetStrict(hostPattern, "HostName") if err != nil { return nil, err } - sshKeywords.HostName = trimquotes.TryTrimQuotes(hostNameRaw) + sshKeywords.SshHostName = trimquotes.TryTrimQuotes(hostNameRaw) portRaw, err := WaveSshConfigUserSettings().GetStrict(hostPattern, "Port") if err != nil { return nil, err } - sshKeywords.Port = trimquotes.TryTrimQuotes(portRaw) + sshKeywords.SshPort = trimquotes.TryTrimQuotes(portRaw) identityFileRaw := WaveSshConfigUserSettings().GetAll(hostPattern, "IdentityFile") for i := 0; i < len(identityFileRaw); i++ { identityFileRaw[i] = trimquotes.TryTrimQuotes(identityFileRaw[i]) } - sshKeywords.IdentityFile = identityFileRaw + sshKeywords.SshIdentityFile = identityFileRaw batchModeRaw, err := WaveSshConfigUserSettings().GetStrict(hostPattern, "BatchMode") if err != nil { return nil, err } - sshKeywords.BatchMode = (strings.ToLower(trimquotes.TryTrimQuotes(batchModeRaw)) == "yes") + sshKeywords.SshBatchMode = (strings.ToLower(trimquotes.TryTrimQuotes(batchModeRaw)) == "yes") // we currently do not support host-bound or unbound but will use yes when they are selected pubkeyAuthenticationRaw, err := WaveSshConfigUserSettings().GetStrict(hostPattern, "PubkeyAuthentication") if err != nil { return nil, err } - sshKeywords.PubkeyAuthentication = (strings.ToLower(trimquotes.TryTrimQuotes(pubkeyAuthenticationRaw)) != "no") + sshKeywords.SshPubkeyAuthentication = (strings.ToLower(trimquotes.TryTrimQuotes(pubkeyAuthenticationRaw)) != "no") passwordAuthenticationRaw, err := WaveSshConfigUserSettings().GetStrict(hostPattern, "PasswordAuthentication") if err != nil { return nil, err } - sshKeywords.PasswordAuthentication = (strings.ToLower(trimquotes.TryTrimQuotes(passwordAuthenticationRaw)) != "no") + sshKeywords.SshPasswordAuthentication = (strings.ToLower(trimquotes.TryTrimQuotes(passwordAuthenticationRaw)) != "no") kbdInteractiveAuthenticationRaw, err := WaveSshConfigUserSettings().GetStrict(hostPattern, "KbdInteractiveAuthentication") if err != nil { return nil, err } - sshKeywords.KbdInteractiveAuthentication = (strings.ToLower(trimquotes.TryTrimQuotes(kbdInteractiveAuthenticationRaw)) != "no") + sshKeywords.SshKbdInteractiveAuthentication = (strings.ToLower(trimquotes.TryTrimQuotes(kbdInteractiveAuthenticationRaw)) != "no") // these are parsed as a single string and must be separated // these are case sensitive in openssh so they are here too @@ -795,12 +798,12 @@ func findSshConfigKeywords(hostPattern string) (*SshKeywords, error) { if err != nil { return nil, err } - sshKeywords.PreferredAuthentications = strings.Split(trimquotes.TryTrimQuotes(preferredAuthenticationsRaw), ",") + sshKeywords.SshPreferredAuthentications = strings.Split(trimquotes.TryTrimQuotes(preferredAuthenticationsRaw), ",") addKeysToAgentRaw, err := WaveSshConfigUserSettings().GetStrict(hostPattern, "AddKeysToAgent") if err != nil { return nil, err } - sshKeywords.AddKeysToAgent = (strings.ToLower(trimquotes.TryTrimQuotes(addKeysToAgentRaw)) == "yes") + sshKeywords.SshAddKeysToAgent = (strings.ToLower(trimquotes.TryTrimQuotes(addKeysToAgentRaw)) == "yes") identityAgentRaw, err := WaveSshConfigUserSettings().GetStrict(hostPattern, "IdentityAgent") if err != nil { @@ -815,7 +818,7 @@ func findSshConfigKeywords(hostPattern string) (*SshKeywords, error) { if err != nil { return nil, err } - sshKeywords.IdentityAgent = agentPath + sshKeywords.SshIdentityAgent = agentPath } else { log.Printf("unable to find SSH_AUTH_SOCK: %v\n", err) } @@ -824,7 +827,7 @@ func findSshConfigKeywords(hostPattern string) (*SshKeywords, error) { if err != nil { return nil, err } - sshKeywords.IdentityAgent = agentPath + sshKeywords.SshIdentityAgent = agentPath } proxyJumpRaw, err := WaveSshConfigUserSettings().GetStrict(hostPattern, "ProxyJump") @@ -837,12 +840,12 @@ func findSshConfigKeywords(hostPattern string) (*SshKeywords, error) { if proxyJumpName == "" || strings.ToLower(proxyJumpName) == "none" { continue } - sshKeywords.ProxyJump = append(sshKeywords.ProxyJump, proxyJumpName) + sshKeywords.SshProxyJump = append(sshKeywords.SshProxyJump, proxyJumpName) } rawUserKnownHostsFile, _ := WaveSshConfigUserSettings().GetStrict(hostPattern, "UserKnownHostsFile") - sshKeywords.UserKnownHostsFile = strings.Fields(rawUserKnownHostsFile) // TODO - smarter splitting escaped spaces and quotes + sshKeywords.SshUserKnownHostsFile = strings.Fields(rawUserKnownHostsFile) // TODO - smarter splitting escaped spaces and quotes rawGlobalKnownHostsFile, _ := WaveSshConfigUserSettings().GetStrict(hostPattern, "GlobalKnownHostsFile") - sshKeywords.GlobalKnownHostsFile = strings.Fields(rawGlobalKnownHostsFile) // TODO - smarter splitting escaped spaces and quotes + sshKeywords.SshGlobalKnownHostsFile = strings.Fields(rawGlobalKnownHostsFile) // TODO - smarter splitting escaped spaces and quotes return sshKeywords, nil } diff --git a/pkg/shellexec/shellexec.go b/pkg/shellexec/shellexec.go index dd76af838..b6316f867 100644 --- a/pkg/shellexec/shellexec.go +++ b/pkg/shellexec/shellexec.go @@ -237,6 +237,47 @@ func StartWslShellProc(ctx context.Context, termSize waveobj.TermSize, cmdStr st func StartRemoteShellProc(termSize waveobj.TermSize, cmdStr string, cmdOpts CommandOptsType, conn *conncontroller.SSHConn) (*ShellProc, error) { client := conn.GetClient() + if !conn.WshEnabled.Load() { + // no wsh code + session, err := client.NewSession() + if err != nil { + return nil, err + } + + remoteStdinRead, remoteStdinWriteOurs, err := os.Pipe() + if err != nil { + return nil, err + } + + remoteStdoutReadOurs, remoteStdoutWrite, err := os.Pipe() + if err != nil { + return nil, err + } + + pipePty := &PipePty{ + remoteStdinWrite: remoteStdinWriteOurs, + remoteStdoutRead: remoteStdoutReadOurs, + } + if termSize.Rows == 0 || termSize.Cols == 0 { + termSize.Rows = shellutil.DefaultTermRows + termSize.Cols = shellutil.DefaultTermCols + } + if termSize.Rows <= 0 || termSize.Cols <= 0 { + return nil, fmt.Errorf("invalid term size: %v", termSize) + } + session.Stdin = remoteStdinRead + session.Stdout = remoteStdoutWrite + session.Stderr = remoteStdoutWrite + + session.RequestPty("xterm-256color", termSize.Rows, termSize.Cols, nil) + sessionWrap := SessionWrap{session, "", pipePty, pipePty} + err = session.Shell() + if err != nil { + pipePty.Close() + return nil, err + } + return &ShellProc{Cmd: sessionWrap, ConnName: conn.GetName(), CloseOnce: &sync.Once{}, DoneCh: make(chan any)}, nil + } shellPath := cmdOpts.ShellPath if shellPath == "" { remoteShellPath, err := remote.DetectShell(client) diff --git a/pkg/telemetry/telemetry.go b/pkg/telemetry/telemetry.go index 9acda99f5..4356498a5 100644 --- a/pkg/telemetry/telemetry.go +++ b/pkg/telemetry/telemetry.go @@ -14,41 +14,12 @@ import ( "github.com/wavetermdev/waveterm/pkg/util/dbutil" "github.com/wavetermdev/waveterm/pkg/wavebase" "github.com/wavetermdev/waveterm/pkg/wconfig" + "github.com/wavetermdev/waveterm/pkg/wshrpc" "github.com/wavetermdev/waveterm/pkg/wstore" ) const MaxTzNameLen = 50 -type ActivityDisplayType struct { - Width int `json:"width"` - Height int `json:"height"` - DPR float64 `json:"dpr"` - Internal bool `json:"internal,omitempty"` -} - -type ActivityUpdate struct { - FgMinutes int `json:"fgminutes,omitempty"` - ActiveMinutes int `json:"activeminutes,omitempty"` - OpenMinutes int `json:"openminutes,omitempty"` - NumTabs int `json:"numtabs,omitempty"` - NewTab int `json:"newtab,omitempty"` - NumBlocks int `json:"numblocks,omitempty"` - NumWindows int `json:"numwindows,omitempty"` - NumSSHConn int `json:"numsshconn,omitempty"` - NumWSLConn int `json:"numwslconn,omitempty"` - NumMagnify int `json:"nummagnify,omitempty"` - NumPanics int `json:"numpanics,omitempty"` - Startup int `json:"startup,omitempty"` - Shutdown int `json:"shutdown,omitempty"` - SetTabTheme int `json:"settabtheme,omitempty"` - BuildTime string `json:"buildtime,omitempty"` - Displays []ActivityDisplayType `json:"displays,omitempty"` - Renderers map[string]int `json:"renderers,omitempty"` - Blocks map[string]int `json:"blocks,omitempty"` - WshCmds map[string]int `json:"wshcmds,omitempty"` - Conn map[string]int `json:"conn,omitempty"` -} - type ActivityType struct { Day string `json:"day"` Uploaded bool `json:"-"` @@ -62,25 +33,25 @@ type ActivityType struct { } type TelemetryData struct { - ActiveMinutes int `json:"activeminutes"` - FgMinutes int `json:"fgminutes"` - OpenMinutes int `json:"openminutes"` - NumTabs int `json:"numtabs"` - NumBlocks int `json:"numblocks,omitempty"` - NumWindows int `json:"numwindows,omitempty"` - NumSSHConn int `json:"numsshconn,omitempty"` - NumWSLConn int `json:"numwslconn,omitempty"` - NumMagnify int `json:"nummagnify,omitempty"` - NewTab int `json:"newtab"` - NumStartup int `json:"numstartup,omitempty"` - NumShutdown int `json:"numshutdown,omitempty"` - NumPanics int `json:"numpanics,omitempty"` - SetTabTheme int `json:"settabtheme,omitempty"` - Displays []ActivityDisplayType `json:"displays,omitempty"` - Renderers map[string]int `json:"renderers,omitempty"` - Blocks map[string]int `json:"blocks,omitempty"` - WshCmds map[string]int `json:"wshcmds,omitempty"` - Conn map[string]int `json:"conn,omitempty"` + ActiveMinutes int `json:"activeminutes"` + FgMinutes int `json:"fgminutes"` + OpenMinutes int `json:"openminutes"` + NumTabs int `json:"numtabs"` + NumBlocks int `json:"numblocks,omitempty"` + NumWindows int `json:"numwindows,omitempty"` + NumSSHConn int `json:"numsshconn,omitempty"` + NumWSLConn int `json:"numwslconn,omitempty"` + NumMagnify int `json:"nummagnify,omitempty"` + NewTab int `json:"newtab"` + NumStartup int `json:"numstartup,omitempty"` + NumShutdown int `json:"numshutdown,omitempty"` + NumPanics int `json:"numpanics,omitempty"` + SetTabTheme int `json:"settabtheme,omitempty"` + Displays []wshrpc.ActivityDisplayType `json:"displays,omitempty"` + Renderers map[string]int `json:"renderers,omitempty"` + Blocks map[string]int `json:"blocks,omitempty"` + WshCmds map[string]int `json:"wshcmds,omitempty"` + Conn map[string]int `json:"conn,omitempty"` } func (tdata TelemetryData) Value() (driver.Value, error) { @@ -107,7 +78,7 @@ func AutoUpdateChannel() string { } // Wraps UpdateCurrentActivity, spawns goroutine, and logs errors -func GoUpdateActivityWrap(update ActivityUpdate, debugStr string) { +func GoUpdateActivityWrap(update wshrpc.ActivityUpdate, debugStr string) { go func() { defer panichandler.PanicHandlerNoTelemetry("GoUpdateActivityWrap") ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) @@ -120,7 +91,7 @@ func GoUpdateActivityWrap(update ActivityUpdate, debugStr string) { }() } -func UpdateActivity(ctx context.Context, update ActivityUpdate) error { +func UpdateActivity(ctx context.Context, update wshrpc.ActivityUpdate) error { now := time.Now() dayStr := daystr.GetCurDayStr() txErr := wstore.WithTx(ctx, func(tx *wstore.TxWrap) error { diff --git a/pkg/tsgen/tsgen.go b/pkg/tsgen/tsgen.go index bf17a2f41..252d9db45 100644 --- a/pkg/tsgen/tsgen.go +++ b/pkg/tsgen/tsgen.go @@ -61,7 +61,7 @@ var contextRType = reflect.TypeOf((*context.Context)(nil)).Elem() var errorRType = reflect.TypeOf((*error)(nil)).Elem() var anyRType = reflect.TypeOf((*interface{})(nil)).Elem() var metaRType = reflect.TypeOf((*waveobj.MetaMapType)(nil)).Elem() -var metaSettingsType = reflect.TypeOf((*wconfig.MetaSettingsType)(nil)).Elem() +var metaSettingsType = reflect.TypeOf((*wshrpc.MetaSettingsType)(nil)).Elem() var uiContextRType = reflect.TypeOf((*waveobj.UIContext)(nil)).Elem() var waveObjRType = reflect.TypeOf((*waveobj.WaveObj)(nil)).Elem() var updatesRtnRType = reflect.TypeOf(waveobj.UpdatesRtnType{}) diff --git a/pkg/userinput/userinput.go b/pkg/userinput/userinput.go index 0ebf9dd0b..b6c99c735 100644 --- a/pkg/userinput/userinput.go +++ b/pkg/userinput/userinput.go @@ -25,6 +25,8 @@ type UserInputRequest struct { TimeoutMs int `json:"timeoutms"` CheckBoxMsg string `json:"checkboxmsg"` PublicText bool `json:"publictext"` + OkLabel string `json:"oklabel,omitempty"` + CancelLabel string `json:"cancellabel,omitempty"` } type UserInputResponse struct { diff --git a/pkg/wconfig/defaultconfig/settings.json b/pkg/wconfig/defaultconfig/settings.json index 634aebcd2..fa8d98f88 100644 --- a/pkg/wconfig/defaultconfig/settings.json +++ b/pkg/wconfig/defaultconfig/settings.json @@ -7,6 +7,7 @@ "autoupdate:installonquit": true, "autoupdate:intervalms": 3600000, "conn:askbeforewshinstall": true, + "conn:wshenabled": true, "editor:minimapenabled": true, "web:defaulturl": "https://github.com/wavetermdev/waveterm", "web:defaultsearch": "https://www.google.com/search?q={query}", diff --git a/pkg/wconfig/metaconsts.go b/pkg/wconfig/metaconsts.go index 31d769b21..3c5ea6c80 100644 --- a/pkg/wconfig/metaconsts.go +++ b/pkg/wconfig/metaconsts.go @@ -71,5 +71,6 @@ const ( ConfigKey_ConnClear = "conn:*" ConfigKey_ConnAskBeforeWshInstall = "conn:askbeforewshinstall" + ConfigKey_ConnWshEnabled = "conn:wshenabled" ) diff --git a/pkg/wconfig/settingsconfig.go b/pkg/wconfig/settingsconfig.go index e139c2f2f..de046b578 100644 --- a/pkg/wconfig/settingsconfig.go +++ b/pkg/wconfig/settingsconfig.go @@ -19,9 +19,11 @@ import ( "github.com/wavetermdev/waveterm/pkg/wavebase" "github.com/wavetermdev/waveterm/pkg/waveobj" "github.com/wavetermdev/waveterm/pkg/wconfig/defaultconfig" + "github.com/wavetermdev/waveterm/pkg/wshrpc" ) const SettingsFile = "settings.json" +const ConnectionsFile = "connections.json" const AnySchema = ` { @@ -30,25 +32,6 @@ const AnySchema = ` } ` -type MetaSettingsType struct { - waveobj.MetaMapType -} - -func (m *MetaSettingsType) UnmarshalJSON(data []byte) error { - var metaMap waveobj.MetaMapType - decoder := json.NewDecoder(bytes.NewReader(data)) - decoder.UseNumber() - if err := decoder.Decode(&metaMap); err != nil { - return err - } - *m = MetaSettingsType{MetaMapType: metaMap} - return nil -} - -func (m MetaSettingsType) MarshalJSON() ([]byte, error) { - return json.Marshal(m.MetaMapType) -} - type SettingsType struct { AiClear bool `json:"ai:*,omitempty"` AiPreset string `json:"ai:preset,omitempty"` @@ -115,6 +98,7 @@ type SettingsType struct { ConnClear bool `json:"conn:*,omitempty"` ConnAskBeforeWshInstall bool `json:"conn:askbeforewshinstall,omitempty"` + ConnWshEnabled bool `json:"conn:wshenabled,omitempty"` } type ConfigError struct { @@ -123,12 +107,14 @@ type ConfigError struct { } type FullConfigType struct { - Settings SettingsType `json:"settings" merge:"meta"` - MimeTypes map[string]MimeTypeConfigType `json:"mimetypes"` - Widgets map[string]WidgetConfigType `json:"widgets"` - Presets map[string]waveobj.MetaMapType `json:"presets"` - TermThemes map[string]TermThemeType `json:"termthemes"` - ConfigErrors []ConfigError `json:"configerrors" configfile:"-"` + Settings SettingsType `json:"settings" merge:"meta"` + MimeTypes map[string]MimeTypeConfigType `json:"mimetypes"` + DefaultWidgets map[string]WidgetConfigType `json:"defaultwidgets"` + Widgets map[string]WidgetConfigType `json:"widgets"` + Presets map[string]waveobj.MetaMapType `json:"presets"` + TermThemes map[string]TermThemeType `json:"termthemes"` + Connections map[string]wshrpc.ConnKeywords `json:"connections"` + ConfigErrors []ConfigError `json:"configerrors" configfile:"-"` } func goBackWS(barr []byte, offset int) int { @@ -401,12 +387,12 @@ func reindentJson(barr []byte, indentStr string) []byte { if barr[0] != '{' && barr[0] != '[' { return barr } - if bytes.Contains(barr, []byte("\n")) { + if !bytes.Contains(barr, []byte("\n")) { return barr } outputLines := bytes.Split(barr, []byte("\n")) for i, line := range outputLines { - if i == 0 || i == len(outputLines)-1 { + if i == 0 { continue } outputLines[i] = append([]byte(indentStr), line...) @@ -509,6 +495,25 @@ func SetBaseConfigValue(toMerge waveobj.MetaMapType) error { return WriteWaveHomeConfigFile(SettingsFile, m) } +func SetConnectionsConfigValue(connName string, toMerge waveobj.MetaMapType) error { + m, cerrs := ReadWaveHomeConfigFile(ConnectionsFile) + if len(cerrs) > 0 { + return fmt.Errorf("error reading config file: %v", cerrs[0]) + } + if m == nil { + m = make(waveobj.MetaMapType) + } + connData := m.GetMap(connName) + if connData == nil { + connData = make(waveobj.MetaMapType) + } + for configKey, val := range toMerge { + connData[configKey] = val + } + m[connName] = connData + return WriteWaveHomeConfigFile(ConnectionsFile, m) +} + type WidgetConfigType struct { DisplayOrder float64 `json:"display:order,omitempty"` Icon string `json:"icon,omitempty"` diff --git a/pkg/wcore/wcore.go b/pkg/wcore/wcore.go index 2e90e37d3..ce96bcb57 100644 --- a/pkg/wcore/wcore.go +++ b/pkg/wcore/wcore.go @@ -16,6 +16,7 @@ import ( "github.com/wavetermdev/waveterm/pkg/telemetry" "github.com/wavetermdev/waveterm/pkg/waveobj" "github.com/wavetermdev/waveterm/pkg/wps" + "github.com/wavetermdev/waveterm/pkg/wshrpc" "github.com/wavetermdev/waveterm/pkg/wstore" ) @@ -119,7 +120,7 @@ func CreateTab(ctx context.Context, windowId string, tabName string, activateTab return "", fmt.Errorf("error setting active tab: %w", err) } } - telemetry.GoUpdateActivityWrap(telemetry.ActivityUpdate{NewTab: 1}, "createtab") + telemetry.GoUpdateActivityWrap(wshrpc.ActivityUpdate{NewTab: 1}, "createtab") return tab.OID, nil } @@ -282,7 +283,7 @@ func CreateBlock(ctx context.Context, tabId string, blockDef *waveobj.BlockDef, } tctx, cancelFn := context.WithTimeout(context.Background(), 2*time.Second) defer cancelFn() - telemetry.UpdateActivity(tctx, telemetry.ActivityUpdate{ + telemetry.UpdateActivity(tctx, wshrpc.ActivityUpdate{ Renderers: map[string]int{blockView: 1}, }) }() diff --git a/pkg/wshrpc/wshclient/wshclient.go b/pkg/wshrpc/wshclient/wshclient.go index cf943b436..cf24f95e9 100644 --- a/pkg/wshrpc/wshclient/wshclient.go +++ b/pkg/wshrpc/wshclient/wshclient.go @@ -9,14 +9,12 @@ import ( "github.com/wavetermdev/waveterm/pkg/wshutil" "github.com/wavetermdev/waveterm/pkg/wshrpc" "github.com/wavetermdev/waveterm/pkg/waveobj" - "github.com/wavetermdev/waveterm/pkg/wconfig" "github.com/wavetermdev/waveterm/pkg/wps" "github.com/wavetermdev/waveterm/pkg/vdom" - "github.com/wavetermdev/waveterm/pkg/telemetry" ) // command "activity", wshserver.ActivityCommand -func ActivityCommand(w *wshutil.WshRpc, data telemetry.ActivityUpdate, opts *wshrpc.RpcOpts) error { +func ActivityCommand(w *wshutil.WshRpc, data wshrpc.ActivityUpdate, opts *wshrpc.RpcOpts) error { _, err := sendRpcRequestCallHelper[any](w, "activity", data, opts) return err } @@ -40,7 +38,7 @@ func BlockInfoCommand(w *wshutil.WshRpc, data string, opts *wshrpc.RpcOpts) (*ws } // command "connconnect", wshserver.ConnConnectCommand -func ConnConnectCommand(w *wshutil.WshRpc, data string, opts *wshrpc.RpcOpts) error { +func ConnConnectCommand(w *wshutil.WshRpc, data wshrpc.ConnRequest, opts *wshrpc.RpcOpts) error { _, err := sendRpcRequestCallHelper[any](w, "connconnect", data, opts) return err } @@ -290,7 +288,7 @@ func RouteUnannounceCommand(w *wshutil.WshRpc, opts *wshrpc.RpcOpts) error { } // command "setconfig", wshserver.SetConfigCommand -func SetConfigCommand(w *wshutil.WshRpc, data wconfig.MetaSettingsType, opts *wshrpc.RpcOpts) error { +func SetConfigCommand(w *wshutil.WshRpc, data wshrpc.MetaSettingsType, opts *wshrpc.RpcOpts) error { _, err := sendRpcRequestCallHelper[any](w, "setconfig", data, opts) return err } diff --git a/pkg/wshrpc/wshrpctypes.go b/pkg/wshrpc/wshrpctypes.go index 949b11c46..646b04c54 100644 --- a/pkg/wshrpc/wshrpctypes.go +++ b/pkg/wshrpc/wshrpctypes.go @@ -5,17 +5,17 @@ package wshrpc import ( + "bytes" "context" + "encoding/json" "log" "os" "reflect" "github.com/wavetermdev/waveterm/pkg/filestore" "github.com/wavetermdev/waveterm/pkg/ijson" - "github.com/wavetermdev/waveterm/pkg/telemetry" "github.com/wavetermdev/waveterm/pkg/vdom" "github.com/wavetermdev/waveterm/pkg/waveobj" - "github.com/wavetermdev/waveterm/pkg/wconfig" "github.com/wavetermdev/waveterm/pkg/wps" ) @@ -133,11 +133,11 @@ type WshRpcInterface interface { StreamWaveAiCommand(ctx context.Context, request OpenAiStreamRequest) chan RespOrErrorUnion[OpenAIPacketType] StreamCpuDataCommand(ctx context.Context, request CpuDataRequest) chan RespOrErrorUnion[TimeSeriesData] TestCommand(ctx context.Context, data string) error - SetConfigCommand(ctx context.Context, data wconfig.MetaSettingsType) error + SetConfigCommand(ctx context.Context, data MetaSettingsType) error BlockInfoCommand(ctx context.Context, blockId string) (*BlockInfoData, error) WaveInfoCommand(ctx context.Context) (*WaveInfoData, error) WshActivityCommand(ct context.Context, data map[string]int) error - ActivityCommand(ctx context.Context, data telemetry.ActivityUpdate) error + ActivityCommand(ctx context.Context, data ActivityUpdate) error GetVarCommand(ctx context.Context, data CommandVarData) (*CommandVarResponseData, error) SetVarCommand(ctx context.Context, data CommandVarData) error @@ -146,7 +146,7 @@ type WshRpcInterface interface { WslStatusCommand(ctx context.Context) ([]ConnStatus, error) ConnEnsureCommand(ctx context.Context, connName string) error ConnReinstallWshCommand(ctx context.Context, connName string) error - ConnConnectCommand(ctx context.Context, connName string) error + ConnConnectCommand(ctx context.Context, connRequest ConnRequest) error ConnDisconnectCommand(ctx context.Context, connName string) error ConnListCommand(ctx context.Context) ([]string, error) WslListCommand(ctx context.Context) ([]string, error) @@ -440,6 +440,31 @@ type CommandRemoteWriteFileData struct { CreateMode os.FileMode `json:"createmode,omitempty"` } +type ConnKeywords struct { + WshEnabled *bool `json:"wshenabled,omitempty"` + AskBeforeWshInstall *bool `json:"askbeforewshinstall,omitempty"` + + SshUser string `json:"ssh:user,omitempty"` + SshHostName string `json:"ssh:hostname,omitempty"` + SshPort string `json:"ssh:port,omitempty"` + SshIdentityFile []string `json:"ssh:identityfile,omitempty"` + SshBatchMode bool `json:"ssh:batchmode,omitempty"` + SshPubkeyAuthentication bool `json:"ssh:pubkeyauthentication,omitempty"` + SshPasswordAuthentication bool `json:"ssh:passwordauthentication,omitempty"` + SshKbdInteractiveAuthentication bool `json:"ssh:kbdinteractiveauthentication,omitempty"` + SshPreferredAuthentications []string `json:"ssh:preferredauthentications,omitempty"` + SshAddKeysToAgent bool `json:"ssh:addkeystoagent,omitempty"` + SshIdentityAgent string `json:"ssh:identityagent,omitempty"` + SshProxyJump []string `json:"ssh:proxyjump,omitempty"` + SshUserKnownHostsFile []string `json:"ssh:userknownhostsfile,omitempty"` + SshGlobalKnownHostsFile []string `json:"ssh:globalknownhostsfile,omitempty"` +} + +type ConnRequest struct { + Host string `json:"host"` + Keywords ConnKeywords `json:"keywords,omitempty"` +} + const ( TimeSeries_Cpu = "cpu" ) @@ -449,8 +474,28 @@ type TimeSeriesData struct { Values map[string]float64 `json:"values"` } +type MetaSettingsType struct { + waveobj.MetaMapType +} + +func (m *MetaSettingsType) UnmarshalJSON(data []byte) error { + var metaMap waveobj.MetaMapType + decoder := json.NewDecoder(bytes.NewReader(data)) + decoder.UseNumber() + if err := decoder.Decode(&metaMap); err != nil { + return err + } + *m = MetaSettingsType{MetaMapType: metaMap} + return nil +} + +func (m MetaSettingsType) MarshalJSON() ([]byte, error) { + return json.Marshal(m.MetaMapType) +} + type ConnStatus struct { Status string `json:"status"` + WshEnabled bool `json:"wshenabled"` Connection string `json:"connection"` Connected bool `json:"connected"` HasConnected bool `json:"hasconnected"` // true if it has *ever* connected successfully @@ -522,3 +567,33 @@ type CommandVarResponseData struct { Val string `json:"val"` Exists bool `json:"exists"` } + +type ActivityDisplayType struct { + Width int `json:"width"` + Height int `json:"height"` + DPR float64 `json:"dpr"` + Internal bool `json:"internal,omitempty"` +} + +type ActivityUpdate struct { + FgMinutes int `json:"fgminutes,omitempty"` + ActiveMinutes int `json:"activeminutes,omitempty"` + OpenMinutes int `json:"openminutes,omitempty"` + NumTabs int `json:"numtabs,omitempty"` + NewTab int `json:"newtab,omitempty"` + NumBlocks int `json:"numblocks,omitempty"` + NumWindows int `json:"numwindows,omitempty"` + NumSSHConn int `json:"numsshconn,omitempty"` + NumWSLConn int `json:"numwslconn,omitempty"` + NumMagnify int `json:"nummagnify,omitempty"` + NumPanics int `json:"numpanics,omitempty"` + Startup int `json:"startup,omitempty"` + Shutdown int `json:"shutdown,omitempty"` + SetTabTheme int `json:"settabtheme,omitempty"` + BuildTime string `json:"buildtime,omitempty"` + Displays []ActivityDisplayType `json:"displays,omitempty"` + Renderers map[string]int `json:"renderers,omitempty"` + Blocks map[string]int `json:"blocks,omitempty"` + WshCmds map[string]int `json:"wshcmds,omitempty"` + Conn map[string]int `json:"conn,omitempty"` +} diff --git a/pkg/wshrpc/wshserver/wshserver.go b/pkg/wshrpc/wshserver/wshserver.go index c8f69afa6..d1e290d7a 100644 --- a/pkg/wshrpc/wshserver/wshserver.go +++ b/pkg/wshrpc/wshserver/wshserver.go @@ -587,7 +587,7 @@ func (ws *WshServer) EventReadHistoryCommand(ctx context.Context, data wshrpc.Co return events, nil } -func (ws *WshServer) SetConfigCommand(ctx context.Context, data wconfig.MetaSettingsType) error { +func (ws *WshServer) SetConfigCommand(ctx context.Context, data wshrpc.MetaSettingsType) error { log.Printf("SETCONFIG: %v\n", data) return wconfig.SetBaseConfigValue(data.MetaMapType) } @@ -623,14 +623,15 @@ func (ws *WshServer) ConnDisconnectCommand(ctx context.Context, connName string) if err != nil { return fmt.Errorf("error parsing connection name: %w", err) } - conn := conncontroller.GetConn(ctx, connOpts, false) + conn := conncontroller.GetConn(ctx, connOpts, false, &wshrpc.ConnKeywords{}) if conn == nil { return fmt.Errorf("connection not found: %s", connName) } return conn.Close() } -func (ws *WshServer) ConnConnectCommand(ctx context.Context, connName string) error { +func (ws *WshServer) ConnConnectCommand(ctx context.Context, connRequest wshrpc.ConnRequest) error { + connName := connRequest.Host if strings.HasPrefix(connName, "wsl://") { distroName := strings.TrimPrefix(connName, "wsl://") conn := wsl.GetWslConn(ctx, distroName, false) @@ -643,11 +644,11 @@ func (ws *WshServer) ConnConnectCommand(ctx context.Context, connName string) er if err != nil { return fmt.Errorf("error parsing connection name: %w", err) } - conn := conncontroller.GetConn(ctx, connOpts, false) + conn := conncontroller.GetConn(ctx, connOpts, false, &connRequest.Keywords) if conn == nil { return fmt.Errorf("connection not found: %s", connName) } - return conn.Connect(ctx) + return conn.Connect(ctx, &connRequest.Keywords) } func (ws *WshServer) ConnReinstallWshCommand(ctx context.Context, connName string) error { @@ -663,7 +664,7 @@ func (ws *WshServer) ConnReinstallWshCommand(ctx context.Context, connName strin if err != nil { return fmt.Errorf("error parsing connection name: %w", err) } - conn := conncontroller.GetConn(ctx, connOpts, false) + conn := conncontroller.GetConn(ctx, connOpts, false, &wshrpc.ConnKeywords{}) if conn == nil { return fmt.Errorf("connection not found: %s", connName) } @@ -753,14 +754,14 @@ func (ws *WshServer) WshActivityCommand(ctx context.Context, data map[string]int delete(data, key) } } - activityUpdate := telemetry.ActivityUpdate{ + activityUpdate := wshrpc.ActivityUpdate{ WshCmds: data, } telemetry.GoUpdateActivityWrap(activityUpdate, "wsh-activity") return nil } -func (ws *WshServer) ActivityCommand(ctx context.Context, activity telemetry.ActivityUpdate) error { +func (ws *WshServer) ActivityCommand(ctx context.Context, activity wshrpc.ActivityUpdate) error { telemetry.GoUpdateActivityWrap(activity, "wshrpc-activity") return nil } diff --git a/pkg/wsl/wsl.go b/pkg/wsl/wsl.go index eb364144e..331e58a46 100644 --- a/pkg/wsl/wsl.go +++ b/pkg/wsl/wsl.go @@ -394,7 +394,7 @@ func (conn *WslConn) Connect(ctx context.Context) error { conn.Status = Status_Error conn.Error = err.Error() conn.close_nolock() - telemetry.GoUpdateActivityWrap(telemetry.ActivityUpdate{ + telemetry.GoUpdateActivityWrap(wshrpc.ActivityUpdate{ Conn: map[string]int{"wsl:connecterror": 1}, }, "wsl-connconnect") } else { @@ -403,7 +403,7 @@ func (conn *WslConn) Connect(ctx context.Context) error { if conn.ActiveConnNum == 0 { conn.ActiveConnNum = int(activeConnCounter.Add(1)) } - telemetry.GoUpdateActivityWrap(telemetry.ActivityUpdate{ + telemetry.GoUpdateActivityWrap(wshrpc.ActivityUpdate{ Conn: map[string]int{"wsl:connect": 1}, }, "wsl-connconnect") }