From 2a5857bc3d31551a8db84515452e7d8b92e9cc12 Mon Sep 17 00:00:00 2001 From: Sylvie Crowe <107814465+oneirocosm@users.noreply.github.com> Date: Thu, 7 Mar 2024 22:37:00 -0800 Subject: [PATCH] SSH UI Quick Fixes (#408) * fix: set golbal ssh config to correct path This adds the missing "etc" directory to the path for the global config file. * chore: update auth mode tooltip This just changes the text to be slightly more accurate to the current behavior. * feat: add box to disable waveshell install modal This hooks in to the existing don't show this again code that pops up when creating a modal. * refactor: remove install modal in remote creation There used to be a modal that popped up while installing a remote that informed the user that waveshell gets installed on their remote. Since we have a new modal that pops up at the time of install, the older modal can be removed. * fix: allow user to cancel ssh dial The new ssh code broke dial for invalid urls since the context did not cancel the dial or any associated user input. This change reconnects the context along with the context for installing waveshell. * style: widen the rconndetail modal The rconndetail modal is currently narrower than the xtermjs element which results in awkward scrolling if a line is long. This change makes the width auto so it can size itself as needed. * add a max-width for safety --- src/app/common/elements/index.tsx | 1 - .../elements/showwaveshellinstallprompt.tsx | 28 ----- src/app/common/modals/createremoteconn.tsx | 33 ++---- src/app/common/modals/userinput.less | 10 +- src/app/common/modals/userinput.tsx | 47 +++++--- .../common/modals/viewremoteconndetail.less | 3 +- src/app/connections/connections.tsx | 9 +- src/types/custom.d.ts | 2 + wavesrv/pkg/cmdrunner/cmdrunner.go | 2 +- wavesrv/pkg/remote/remote.go | 112 ++++++++++++------ wavesrv/pkg/remote/sshclient.go | 47 +++++--- wavesrv/pkg/userinput/userinput.go | 12 +- 12 files changed, 165 insertions(+), 141 deletions(-) delete mode 100644 src/app/common/elements/showwaveshellinstallprompt.tsx diff --git a/src/app/common/elements/index.tsx b/src/app/common/elements/index.tsx index 30895855a..7096d3284 100644 --- a/src/app/common/elements/index.tsx +++ b/src/app/common/elements/index.tsx @@ -13,7 +13,6 @@ export { NumberField } from "./numberfield"; export { PasswordField } from "./passwordfield"; export { ResizableSidebar } from "./resizablesidebar"; export { SettingsError } from "./settingserror"; -export { ShowWaveShellInstallPrompt } from "./showwaveshellinstallprompt"; export { Status } from "./status"; export { TextField } from "./textfield"; export { Toggle } from "./toggle"; diff --git a/src/app/common/elements/showwaveshellinstallprompt.tsx b/src/app/common/elements/showwaveshellinstallprompt.tsx deleted file mode 100644 index 647cd74d4..000000000 --- a/src/app/common/elements/showwaveshellinstallprompt.tsx +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2023, Command Line Inc. -// SPDX-License-Identifier: Apache-2.0 - -import { GlobalModel } from "@/models"; -import * as appconst from "@/app/appconst"; - -function ShowWaveShellInstallPrompt(callbackFn: () => void) { - let message: string = ` -In order to use Wave's advanced features like unified history and persistent sessions, Wave installs a small, open-source helper program called WaveShell on your remote machine. WaveShell does not open any external ports and only communicates with your *local* Wave terminal instance over ssh. For more information please see [the docs](https://docs.waveterm.dev/reference/waveshell). - `; - message = message.trim(); - let prtn = GlobalModel.showAlert({ - message: message, - confirm: true, - markdown: true, - confirmflag: appconst.ConfirmKey_HideShellPrompt, - }); - prtn.then((confirm) => { - if (!confirm) { - return; - } - if (callbackFn) { - callbackFn(); - } - }); -} - -export { ShowWaveShellInstallPrompt }; diff --git a/src/app/common/modals/createremoteconn.tsx b/src/app/common/modals/createremoteconn.tsx index 297e67973..56f4e6ab5 100644 --- a/src/app/common/modals/createremoteconn.tsx +++ b/src/app/common/modals/createremoteconn.tsx @@ -7,16 +7,7 @@ import * as mobx from "mobx"; import { boundMethod } from "autobind-decorator"; import { If } from "tsx-control-statements/components"; import { GlobalModel, GlobalCommandRunner, RemotesModel } from "@/models"; -import { - Modal, - TextField, - NumberField, - InputDecoration, - Dropdown, - PasswordField, - Tooltip, - ShowWaveShellInstallPrompt, -} from "@/elements"; +import { Modal, TextField, NumberField, InputDecoration, Dropdown, PasswordField, Tooltip } from "@/elements"; import * as util from "@/util/util"; import "./createremoteconn.less"; @@ -73,12 +64,7 @@ class CreateRemoteConnModal extends React.Component<{}, {}> { } @boundMethod - handleOk(): void { - ShowWaveShellInstallPrompt(this.submitRemote); - } - - @boundMethod - submitRemote(): void { + handleSubmitRemote(): void { mobx.action(() => { this.errorStr.set(null); })(); @@ -275,7 +261,7 @@ class CreateRemoteConnModal extends React.Component<{}, {}> { { value: "none", label: "none" }, { value: "key", label: "key" }, { value: "password", label: "password" }, - { value: "key+password", label: "key+password" }, + { value: "key+password", label: "key+passphrase" }, ]} value={this.tempAuthMode.get()} onChange={(val: string) => { @@ -288,17 +274,18 @@ class CreateRemoteConnModal extends React.Component<{}, {}> { message={ } @@ -374,7 +361,7 @@ class CreateRemoteConnModal extends React.Component<{}, {}> {
Error: {this.getErrorStr()}
- + ); } diff --git a/src/app/common/modals/userinput.less b/src/app/common/modals/userinput.less index f86c11ca0..fd6b3e6fa 100644 --- a/src/app/common/modals/userinput.less +++ b/src/app/common/modals/userinput.less @@ -3,10 +3,14 @@ .wave-modal-content { .wave-modal-body { - padding: 20px 20px; + padding: 0px 20px 0px 20px; - .userinput-query { - margin-bottom: 10px; + .wave-modal-dialog { + padding: 20px 0px 20px 0px; + + .userinput-query { + margin-bottom: 10px; + } } } } diff --git a/src/app/common/modals/userinput.tsx b/src/app/common/modals/userinput.tsx index 47a3c55a8..19e0bbee0 100644 --- a/src/app/common/modals/userinput.tsx +++ b/src/app/common/modals/userinput.tsx @@ -1,7 +1,7 @@ import * as React from "react"; import { GlobalModel } from "@/models"; import { Choose, When, If } from "tsx-control-statements/components"; -import { Modal, PasswordField, Markdown } from "@/elements"; +import { Modal, PasswordField, Markdown, Checkbox } from "@/elements"; import { checkKeyPressed, adaptFromReactOrNativeKeyEvent } from "@/util/keyutil"; import "./userinput.less"; @@ -9,6 +9,7 @@ import "./userinput.less"; export const UserInputModal = (userInputRequest: UserInputRequest) => { const [responseText, setResponseText] = React.useState(""); const [countdown, setCountdown] = React.useState(Math.floor(userInputRequest.timeoutms / 1000)); + const checkboxStatus = React.useRef(false); const handleSendCancel = React.useCallback(() => { GlobalModel.sendUserInput({ @@ -24,16 +25,19 @@ export const UserInputModal = (userInputRequest: UserInputRequest) => { type: "userinputresp", requestid: userInputRequest.requestid, text: responseText, + checkboxstat: checkboxStatus.current, }); GlobalModel.remotesModel.closeModal(); }, [responseText, userInputRequest]); const handleSendConfirm = React.useCallback( (response: boolean) => { + console.log(`checkbox ${checkboxStatus}\n\n`); GlobalModel.sendUserInput({ type: "userinputresp", requestid: userInputRequest.requestid, confirm: response, + checkboxstat: checkboxStatus.current, }); GlobalModel.remotesModel.closeModal(); }, @@ -71,23 +75,32 @@ export const UserInputModal = (userInputRequest: UserInputRequest) => {
-
- - - - {userInputRequest.querytext} +
+
+ + + + {userInputRequest.querytext} +
+ + + handleTextKeyDown(e)} + /> + +
- - - handleTextKeyDown(e)} - /> - - + + (checkboxStatus.current = !checkboxStatus.current)} + label={userInputRequest.checkboxmsg} + className="checkbox-text" + /> +
diff --git a/src/app/common/modals/viewremoteconndetail.less b/src/app/common/modals/viewremoteconndetail.less index aee472d89..44d474178 100644 --- a/src/app/common/modals/viewremoteconndetail.less +++ b/src/app/common/modals/viewremoteconndetail.less @@ -1,5 +1,6 @@ .rconndetail-modal { - width: 631px; + width: auto; + max-width: 80vw; min-height: 565px; .wave-modal-content { diff --git a/src/app/connections/connections.tsx b/src/app/connections/connections.tsx index f5c715b8f..9cc734620 100644 --- a/src/app/connections/connections.tsx +++ b/src/app/connections/connections.tsx @@ -8,7 +8,7 @@ import { boundMethod } from "autobind-decorator"; import { If, For } from "tsx-control-statements/components"; import cn from "classnames"; import { GlobalModel, RemotesModel, GlobalCommandRunner } from "@/models"; -import { Button, Status, ShowWaveShellInstallPrompt } from "@/common/elements"; +import { Button, Status } from "@/common/elements"; import * as util from "@/util/util"; import "./connections.less"; @@ -71,14 +71,9 @@ class ConnectionsView extends React.Component<{ model: RemotesModel }, { hovered GlobalModel.remotesModel.openAddModal({ remoteedit: true }); } - @boundMethod - importSshConfig(): void { - GlobalCommandRunner.importSshConfig(); - } - @boundMethod handleImportSshConfig(): void { - ShowWaveShellInstallPrompt(this.importSshConfig); + GlobalCommandRunner.importSshConfig(); } @boundMethod diff --git a/src/types/custom.d.ts b/src/types/custom.d.ts index 0c5018b87..4395ca3bc 100644 --- a/src/types/custom.d.ts +++ b/src/types/custom.d.ts @@ -666,6 +666,7 @@ declare global { title: string; markdown: boolean; timeoutms: number; + checkboxmsg: string; }; type UserInputResponsePacket = { @@ -674,6 +675,7 @@ declare global { text?: string; confirm?: boolean; errormsg?: string; + checkboxstat?: boolean; }; type RenderModeType = "normal" | "collapsed" | "expanded"; diff --git a/wavesrv/pkg/cmdrunner/cmdrunner.go b/wavesrv/pkg/cmdrunner/cmdrunner.go index 9291203aa..3af7379ba 100644 --- a/wavesrv/pkg/cmdrunner/cmdrunner.go +++ b/wavesrv/pkg/cmdrunner/cmdrunner.go @@ -2202,7 +2202,7 @@ func NewHostInfo(hostName string) (*HostInfoType, error) { func RemoteConfigParseCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { home := base.GetHomeDir() localConfig := filepath.Join(home, ".ssh", "config") - systemConfig := filepath.Join("/", "ssh", "config") + systemConfig := filepath.Join("/etc", "ssh", "config") sshConfigFiles := []string{localConfig, systemConfig} ssh_config.ReloadConfigs() hostPatterns, hostPatternsErr := resolveSshConfigPatterns(sshConfigFiles) diff --git a/wavesrv/pkg/remote/remote.go b/wavesrv/pkg/remote/remote.go index 9d51f9d73..07f61dec9 100644 --- a/wavesrv/pkg/remote/remote.go +++ b/wavesrv/pkg/remote/remote.go @@ -1202,14 +1202,54 @@ func (msh *MShellProc) RunInstall(autoInstall bool) { msh.WriteToPtyBuffer("*error: cannot install on archived remote\n") return } - if autoInstall { + + var makeClientCtx context.Context + var makeClientCancelFn context.CancelFunc + msh.WithLock(func() { + makeClientCtx, makeClientCancelFn = context.WithCancel(context.Background()) + msh.MakeClientCancelFn = makeClientCancelFn + msh.MakeClientDeadline = nil + go msh.NotifyRemoteUpdate() + }) + defer makeClientCancelFn() + clientData, err := sstore.EnsureClientData(makeClientCtx) + if err != nil { + msh.WriteToPtyBuffer("*error: cannot obtain client data: %v", err) + return + } + hideShellPrompt := clientData.ClientOpts.ConfirmFlags["hideshellprompt"] + baseStatus := msh.GetStatus() + + if baseStatus == StatusConnected { + ctx, cancelFn := context.WithTimeout(makeClientCtx, 60*time.Second) + defer cancelFn() + request := &userinput.UserInputRequestType{ + ResponseType: "confirm", + QueryText: "Waveshell is running on your connection and must be restarted to re-install. Would you like to continue?", + Title: "Restart Waveshell", + } + response, err := userinput.GetUserInput(ctx, scbus.MainRpcBus, request) + if err != nil { + if err == context.Canceled { + msh.WriteToPtyBuffer("installation canceled by user\n") + } else { + msh.WriteToPtyBuffer("timed out waiting for user input\n") + } + return + } + if !response.Confirm { + msh.WriteToPtyBuffer("installation canceled by user\n") + return + } + } else if !hideShellPrompt { + ctx, cancelFn := context.WithTimeout(makeClientCtx, 60*time.Second) + defer cancelFn() request := &userinput.UserInputRequestType{ ResponseType: "confirm", QueryText: "Waveshell must be reinstalled on the connection to continue. Would you like to install it?", Title: "Install Waveshell", + CheckBoxMsg: "Don't show me this again", } - ctx, cancelFn := context.WithTimeout(context.Background(), 60*time.Second) - defer cancelFn() response, err := userinput.GetUserInput(ctx, scbus.MainRpcBus, request) if err != nil { var errMsg error @@ -1234,28 +1274,24 @@ func (msh *MShellProc) RunInstall(autoInstall bool) { }) return } - } - baseStatus := msh.GetStatus() - if baseStatus == StatusConnected { - request := &userinput.UserInputRequestType{ - ResponseType: "confirm", - QueryText: "Waveshell is running on your connection and must be restarted to re-install. Would you like to continue?", - Title: "Restart Waveshell", - } - ctx, cancelFn := context.WithTimeout(context.Background(), 60*time.Second) - defer cancelFn() - response, err := userinput.GetUserInput(ctx, scbus.MainRpcBus, request) - if err != nil { - if err == context.Canceled { - msh.WriteToPtyBuffer("installation canceled by user\n") - } else { - msh.WriteToPtyBuffer("timed out waiting for user input\n") + if response.CheckboxStat { + clientData.ClientOpts.ConfirmFlags["hideshellprompt"] = true + err = sstore.SetClientOpts(makeClientCtx, clientData.ClientOpts) + if err != nil { + msh.WriteToPtyBuffer("*error, %s\n", err) + msh.setErrorStatus(err) + return } - return - } - if !response.Confirm { - msh.WriteToPtyBuffer("installation canceled by user\n") - return + + //reload updated clientdata before sending + clientData, err = sstore.EnsureClientData(makeClientCtx) + if err != nil { + msh.WriteToPtyBuffer("*error, %s\n", err) + msh.setErrorStatus(err) + return + } + update := scbus.MakeUpdatePacket() + update.AddUpdate(*clientData) } } curStatus := msh.GetInstallStatus() @@ -1267,14 +1303,14 @@ func (msh *MShellProc) RunInstall(autoInstall bool) { msh.WriteToPtyBuffer("*error: cannot install on a local remote\n") return } - _, err := shellapi.MakeShellApi(packet.ShellType_bash) + _, err = shellapi.MakeShellApi(packet.ShellType_bash) if err != nil { msh.WriteToPtyBuffer("*error: %v\n", err) return } if msh.Client == nil { remoteDisplayName := fmt.Sprintf("%s [%s]", remoteCopy.RemoteAlias, remoteCopy.RemoteCanonicalName) - client, err := ConnectToClient(remoteCopy.SSHOpts, remoteDisplayName) + client, err := ConnectToClient(makeClientCtx, remoteCopy.SSHOpts, remoteDisplayName) if err != nil { statusErr := fmt.Errorf("ssh cannot connect to client: %w", err) msh.setInstallErrorStatus(statusErr) @@ -1481,7 +1517,7 @@ func (msh *MShellProc) getActiveShellTypes(ctx context.Context) ([]string, error return utilfn.CombineStrArrays(rtn, activeShells), nil } -func (msh *MShellProc) createWaveshellSession(remoteCopy sstore.RemoteType) (shexec.ConnInterface, error) { +func (msh *MShellProc) createWaveshellSession(clientCtx context.Context, remoteCopy sstore.RemoteType) (shexec.ConnInterface, error) { msh.WithLock(func() { msh.Err = nil msh.ErrNoInitPk = false @@ -1510,7 +1546,7 @@ func (msh *MShellProc) createWaveshellSession(remoteCopy sstore.RemoteType) (she wsSession = shexec.CmdWrap{Cmd: ecmd} } else if msh.Client == nil { remoteDisplayName := fmt.Sprintf("%s [%s]", remoteCopy.RemoteAlias, remoteCopy.RemoteCanonicalName) - client, err := ConnectToClient(remoteCopy.SSHOpts, remoteDisplayName) + client, err := ConnectToClient(clientCtx, remoteCopy.SSHOpts, remoteDisplayName) if err != nil { return nil, fmt.Errorf("ssh cannot connect to client: %w", err) } @@ -1559,16 +1595,6 @@ func (NewLauncher) Launch(msh *MShellProc, interactive bool) { msh.WriteToPtyBuffer("remote is trying to install, cancel install before trying to connect again\n") return } - msh.WriteToPtyBuffer("connecting to %s...\n", remoteCopy.RemoteCanonicalName) - wsSession, err := msh.createWaveshellSession(remoteCopy) - if err != nil { - msh.WriteToPtyBuffer("*error, %s\n", err.Error()) - msh.setErrorStatus(err) - msh.WithLock(func() { - msh.Client = nil - }) - return - } var makeClientCtx context.Context var makeClientCancelFn context.CancelFunc msh.WithLock(func() { @@ -1578,6 +1604,16 @@ func (NewLauncher) Launch(msh *MShellProc, interactive bool) { go msh.NotifyRemoteUpdate() }) defer makeClientCancelFn() + msh.WriteToPtyBuffer("connecting to %s...\n", remoteCopy.RemoteCanonicalName) + wsSession, err := msh.createWaveshellSession(makeClientCtx, remoteCopy) + if err != nil { + msh.WriteToPtyBuffer("*error, %s\n", err.Error()) + msh.setErrorStatus(err) + msh.WithLock(func() { + msh.Client = nil + }) + return + } cproc, err := shexec.MakeClientProc(makeClientCtx, wsSession) msh.WithLock(func() { msh.MakeClientCancelFn = nil diff --git a/wavesrv/pkg/remote/sshclient.go b/wavesrv/pkg/remote/sshclient.go index ed770ca27..7eff78ef3 100644 --- a/wavesrv/pkg/remote/sshclient.go +++ b/wavesrv/pkg/remote/sshclient.go @@ -65,7 +65,7 @@ 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(sshKeywords *SshKeywords, passphrase string) func() ([]ssh.Signer, error) { +func createPublicKeyCallback(connCtx context.Context, sshKeywords *SshKeywords, passphrase string) func() ([]ssh.Signer, error) { var identityFiles []string existingKeys := make(map[string][]byte) @@ -124,7 +124,7 @@ func createPublicKeyCallback(sshKeywords *SshKeywords, passphrase string) func() QueryText: fmt.Sprintf("Enter passphrase for the SSH key: %s", identityFile), Title: "Publickey Auth + Passphrase", } - ctx, cancelFn := context.WithTimeout(context.Background(), 60*time.Second) + ctx, cancelFn := context.WithTimeout(connCtx, 60*time.Second) defer cancelFn() response, err := userinput.GetUserInput(ctx, scbus.MainRpcBus, request) if err != nil { @@ -150,11 +150,11 @@ func createDefaultPasswordCallbackPrompt(password string) func() (secret string, } } -func createInteractivePasswordCallbackPrompt(remoteDisplayName string) func() (secret string, err error) { +func createInteractivePasswordCallbackPrompt(connCtx context.Context, remoteDisplayName string) func() (secret string, err error) { return func() (secret string, err error) { // limited to 15 seconds for some reason. this should be investigated more // in the future - ctx, cancelFn := context.WithTimeout(context.Background(), 60*time.Second) + ctx, cancelFn := context.WithTimeout(connCtx, 60*time.Second) defer cancelFn() queryText := fmt.Sprintf( "Password Authentication requested from connection \n"+ @@ -174,13 +174,13 @@ func createInteractivePasswordCallbackPrompt(remoteDisplayName string) func() (s } } -func createCombinedPasswordCallbackPrompt(password string, remoteDisplayName string) func() (secret string, err error) { +func createCombinedPasswordCallbackPrompt(connCtx context.Context, password string, remoteDisplayName string) func() (secret string, err error) { var once sync.Once return func() (secret string, err error) { var prompt func() (secret string, err error) once.Do(func() { prompt = createDefaultPasswordCallbackPrompt(password) }) if prompt == nil { - prompt = createInteractivePasswordCallbackPrompt(remoteDisplayName) + prompt = createInteractivePasswordCallbackPrompt(connCtx, remoteDisplayName) } return prompt() } @@ -199,14 +199,14 @@ func createNaiveKbdInteractiveChallenge(password string) func(name, instruction } } -func createInteractiveKbdInteractiveChallenge(remoteName string) func(name, instruction string, questions []string, echos []bool) (answers []string, err error) { +func createInteractiveKbdInteractiveChallenge(connCtx context.Context, remoteName string) func(name, instruction string, questions []string, echos []bool) (answers []string, err error) { return func(name, instruction string, questions []string, echos []bool) (answers []string, err error) { if len(questions) != len(echos) { return nil, fmt.Errorf("bad response from server: questions has len %d, echos has len %d", len(questions), len(echos)) } for i, question := range questions { echo := echos[i] - answer, err := promptChallengeQuestion(question, echo, remoteName) + answer, err := promptChallengeQuestion(connCtx, question, echo, remoteName) if err != nil { return nil, err } @@ -216,10 +216,10 @@ func createInteractiveKbdInteractiveChallenge(remoteName string) func(name, inst } } -func promptChallengeQuestion(question string, echo bool, remoteName string) (answer string, err error) { +func promptChallengeQuestion(connCtx context.Context, question string, echo bool, remoteName string) (answer string, err error) { // limited to 15 seconds for some reason. this should be investigated more // in the future - ctx, cancelFn := context.WithTimeout(context.Background(), 60*time.Second) + ctx, cancelFn := context.WithTimeout(connCtx, 60*time.Second) defer cancelFn() queryText := fmt.Sprintf( "Keyboard Interactive Authentication requested from connection \n"+ @@ -238,13 +238,13 @@ func promptChallengeQuestion(question string, echo bool, remoteName string) (ans return response.Text, nil } -func createCombinedKbdInteractiveChallenge(password string, remoteName string) ssh.KeyboardInteractiveChallenge { +func createCombinedKbdInteractiveChallenge(connCtx context.Context, password string, remoteName string) ssh.KeyboardInteractiveChallenge { var once sync.Once return func(name, instruction string, questions []string, echos []bool) (answers []string, err error) { var challenge ssh.KeyboardInteractiveChallenge once.Do(func() { challenge = createNaiveKbdInteractiveChallenge(password) }) if challenge == nil { - challenge = createInteractiveKbdInteractiveChallenge(remoteName) + challenge = createInteractiveKbdInteractiveChallenge(connCtx, remoteName) } return challenge(name, instruction, questions, echos) } @@ -505,7 +505,20 @@ func createHostKeyCallback(opts *sstore.SSHOpts) (ssh.HostKeyCallback, error) { return waveHostKeyCallback, nil } -func ConnectToClient(opts *sstore.SSHOpts, remoteDisplayName string) (*ssh.Client, error) { +func DialContext(ctx context.Context, network string, addr string, config *ssh.ClientConfig) (*ssh.Client, error) { + d := net.Dialer{Timeout: config.Timeout} + conn, err := d.DialContext(ctx, network, addr) + if err != nil { + return nil, err + } + c, chans, reqs, err := ssh.NewClientConn(conn, addr, config) + if err != nil { + return nil, err + } + return ssh.NewClient(c, chans, reqs), nil +} + +func ConnectToClient(connCtx context.Context, opts *sstore.SSHOpts, remoteDisplayName string) (*ssh.Client, error) { sshConfigKeywords, err := findSshConfigKeywords(opts.SSHHost) if err != nil { return nil, err @@ -516,9 +529,9 @@ func ConnectToClient(opts *sstore.SSHOpts, remoteDisplayName string) (*ssh.Clien return nil, err } - publicKeyCallback := ssh.PublicKeysCallback(createPublicKeyCallback(sshKeywords, opts.SSHPassword)) - keyboardInteractive := ssh.KeyboardInteractive(createCombinedKbdInteractiveChallenge(opts.SSHPassword, remoteDisplayName)) - passwordCallback := ssh.PasswordCallback(createCombinedPasswordCallbackPrompt(opts.SSHPassword, remoteDisplayName)) + publicKeyCallback := ssh.PublicKeysCallback(createPublicKeyCallback(connCtx, sshKeywords, opts.SSHPassword)) + keyboardInteractive := ssh.KeyboardInteractive(createCombinedKbdInteractiveChallenge(connCtx, opts.SSHPassword, remoteDisplayName)) + passwordCallback := ssh.PasswordCallback(createCombinedPasswordCallbackPrompt(connCtx, opts.SSHPassword, remoteDisplayName)) // batch mode turns off interactive input. this means the number of // attemtps must drop to 1 with this setup @@ -566,7 +579,7 @@ func ConnectToClient(opts *sstore.SSHOpts, remoteDisplayName string) (*ssh.Clien HostKeyCallback: hostKeyCallback, } networkAddr := sshKeywords.HostName + ":" + sshKeywords.Port - return ssh.Dial("tcp", networkAddr, clientConfig) + return DialContext(connCtx, "tcp", networkAddr, clientConfig) } type SshKeywords struct { diff --git a/wavesrv/pkg/userinput/userinput.go b/wavesrv/pkg/userinput/userinput.go index 0ab0fe410..50212acb6 100644 --- a/wavesrv/pkg/userinput/userinput.go +++ b/wavesrv/pkg/userinput/userinput.go @@ -21,6 +21,7 @@ type UserInputRequestType struct { Title string `json:"title"` Markdown bool `json:"markdown"` TimeoutMs int `json:"timeoutms"` + CheckBoxMsg string `json:"checkboxmsg"` } func (*UserInputRequestType) GetType() string { @@ -39,11 +40,12 @@ const UserInputResponsePacketStr = "userinputresp" // An RpcResponse for user input requests type UserInputResponsePacketType struct { - Type string `json:"type"` - RequestId string `json:"requestid"` - Text string `json:"text,omitempty"` - Confirm bool `json:"confirm,omitempty"` - ErrorMsg string `json:"errormsg,omitempty"` + Type string `json:"type"` + RequestId string `json:"requestid"` + Text string `json:"text,omitempty"` + Confirm bool `json:"confirm,omitempty"` + ErrorMsg string `json:"errormsg,omitempty"` + CheckboxStat bool `json:"checkboxstat,omitempty"` } func (*UserInputResponsePacketType) GetType() string {