Retry Without Wsh on Fail (#1406)

Adds the ability for connections to continue without wsh if they fail.
This involves creating a menu that warns the user that wsh could not be
used.
This commit is contained in:
Sylvie Crowe 2024-12-06 10:11:38 -08:00 committed by GitHub
parent 9f10be5629
commit 6dfc85b324
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 268 additions and 93 deletions

View File

@ -16,11 +16,18 @@ The easiest way to access connections is to click the <i className="fa-sharp fa-
## What are wsh Shell Extensions? ## What are wsh Shell Extensions?
`wsh` is a small program that helps manage waveterm regardless of which machine you are currently connected to. In order to not interrupt the normal flow of the remote session, we install it on your remote machine at `~/.waveterm/bin/wsh`. Then, when wave connects to your connection (and only when wave connects to your connection), `~/.waveterm/bin` is added to your `PATH` for that individual session. For more info on what `wsh` is capable of, see [wsh command](/wsh). And if you wish to view the source code of `wsh`, you can find it [here](https://github.com/wavetermdev/waveterm/tree/main/cmd/wsh). `wsh` is a small program that helps manage waveterm regardless of which machine you are currently connected to. It is always included on your host machine, but you also have the option to install it when connecting to a remote machine. If it is installed on the remote machine, it is installed at `~/.waveterm/bin/wsh`. Then, when wave connects to your connection (and only when wave connects to your connection), `~/.waveterm/bin` is added to your `PATH` for that individual session. For more info on what `wsh` is capable of, see [wsh command](/wsh). And if you wish to view the source code of `wsh`, you can find it [here](https://github.com/wavetermdev/waveterm/tree/main/cmd/wsh).
With `wsh` installed, you have the ability to view certain widgets from the remote machine as if it were your host. In addition, `wsh` can be used to influence the widgets across various machines. As a very simple example, you can close a widget on the host machine by using the `wsh` command in a terminal window on a remote machine. For more information on what you can accomplish with `wsh`, take a look [here](/wsh).
## Add a New Connection to the Dropdown ## Add a New Connection to the Dropdown
The SSH values that are loaded into the dropdown by default are obtained by parsing your `~/.ssh/config` and `/etc/ssh/ssh_config` files. Adding a new connection is as simple as adding a new `Host` to one of these files, typically the `~/.ssh/config` file. The SSH values that are loaded into the dropdown by default are obtained by parsing the internal `config/connections.json` file in addition to your `~/.ssh/config` and `/etc/ssh/ssh_config` files. Adding a new connection can be added in a couple ways:
- adding a new `Host` to one of your ssh config files, typically the `~/.ssh/config` file
- adding a new entry in the internal `config/connections.json` file
- manually typing your connection into the connection box (if this successfully connects, the connection will be added to the internal `config/connections.json` file)
- use `wsh ssh [user]@[host]` in your terminal (if this successfully connects, the connection will be added to the internal `config/connections.json` file)
WSL values are added by searching the installed WSL distributions as they appear in the Windows Registry. WSL values are added by searching the installed WSL distributions as they appear in the Windows Registry.
@ -55,6 +62,20 @@ Host myhost
You would then be able to access this connection with `myhost` or `username@myhost`. And if you wanted to manually specify a port such as port 2222, you could do that by either adding `Port 2222` to the config file or connecting to `username@myhost:2222`. You would then be able to access this connection with `myhost` or `username@myhost`. And if you wanted to manually specify a port such as port 2222, you could do that by either adding `Port 2222` to the config file or connecting to `username@myhost:2222`.
## Internal SSH Configuration
In addition to the regular ssh config file, wave also has its own config file to manage separate variables. These include
| Keyword | Description |
|---------|-------------|
| conn:wshenabled | This boolean allows wsh to be used for your connection, if it is set to `false`, `wsh` will never be used for that connection. It defaults to `true`.|
| conn:askbeforewshinstall | This boolean is used to prompt the user before installing wsh. If it is set to false, `wsh` will automatically be installed instead without prompting. It defaults to `true`.|
| display:hidden | This boolean hides the connection from the dropdown list. It defaults to `false` |
| display:order | This float determines the order of connections in the connection dropdown. It defaults to `0`.|
| term:fontsize | This int can be used to override the terminal font size for blocks using this connection. The block metadata takes priority over this setting. It defaults to null which means the global setting will be used instead. |
| term:fontfamily | This string can be used to specify a terminal font family for blocks using this connection. The block metadata takes priority over this setting. It defaults to null which means the global setting will be used instead. |
| term:theme | This string can be used to specify a terminal theme for blocks using this connection. The block metadata takes priority over this setting. It defaults to null which means the global setting will be used instead. |
| ssh:identityfile | A list of strings containing the paths to identity files that will be used. If a `wsh ssh` command using the `-i` flag is successful, the identity file will automatically be added here. |
## Managing Connections with the CLI ## Managing Connections with the CLI
The `wsh` command gives some commands specifically for interacting with the connections. You can view these [here](/wsh#conn). The `wsh` command gives some commands specifically for interacting with the connections. You can view these [here](/wsh#conn).

View File

@ -298,7 +298,7 @@ This will delete the block with the specified id.
wsh ssh [user@host] wsh ssh [user@host]
``` ```
This will use Wave's internal ssh implementation to connect to the specified remote machine. This will use Wave's internal ssh implementation to connect to the specified remote machine. The `-i` flag can be used to specify a path to an identity file.
--- ---

View File

@ -166,6 +166,7 @@
flex: 1 2 auto; flex: 1 2 auto;
overflow: hidden; overflow: hidden;
padding-right: 4px; padding-right: 4px;
@include mixins.ellipsis()
} }
.connecting-svg { .connecting-svg {

View File

@ -185,8 +185,11 @@ const BlockFrame_Header = ({
const manageConnection = util.useAtomValueSafe(viewModel?.manageConnection); const manageConnection = util.useAtomValueSafe(viewModel?.manageConnection);
const dragHandleRef = preview ? null : nodeModel.dragHandleRef; const dragHandleRef = preview ? null : nodeModel.dragHandleRef;
const connName = blockData?.meta?.connection; const connName = blockData?.meta?.connection;
const allSettings = jotai.useAtomValue(atoms.fullConfigAtom); const connStatus = util.useAtomValueSafe(getConnStatusAtom(connName));
const wshEnabled = allSettings?.connections?.[connName]?.["conn:wshenabled"] ?? true; const wshEnabled =
(connName &&
(connStatus?.status == "connecting" || (connStatus?.wshenabled && connStatus?.status == "connected"))) ??
true;
React.useEffect(() => { React.useEffect(() => {
if (!magnified || preview || prevMagifiedState.current) { if (!magnified || preview || prevMagifiedState.current) {
@ -342,6 +345,8 @@ const ConnStatusOverlay = React.memo(
const [overlayRefCallback, _, domRect] = useDimensionsWithCallbackRef(30); const [overlayRefCallback, _, domRect] = useDimensionsWithCallbackRef(30);
const width = domRect?.width; const width = domRect?.width;
const [showError, setShowError] = React.useState(false); const [showError, setShowError] = React.useState(false);
const fullConfig = jotai.useAtomValue(atoms.fullConfigAtom);
const [showWshError, setShowWshError] = React.useState(false);
React.useEffect(() => { React.useEffect(() => {
if (width) { if (width) {
@ -356,12 +361,40 @@ const ConnStatusOverlay = React.memo(
prtn.catch((e) => console.log("error reconnecting", connName, e)); prtn.catch((e) => console.log("error reconnecting", connName, e));
}, [connName]); }, [connName]);
const handleDisableWsh = React.useCallback(async () => {
// using unknown is a hack. we need proper types for the
// connection config on the frontend
const metamaptype: unknown = {
"conn:wshenabled": false,
};
const data: ConnConfigRequest = {
host: connName,
metamaptype: metamaptype,
};
try {
await RpcApi.SetConnectionsConfigCommand(TabRpcClient, data);
} catch (e) {
console.log("problem setting connection config: ", e);
}
}, [connName]);
const handleRemoveWshError = React.useCallback(async () => {
try {
await RpcApi.DismissWshFailCommand(TabRpcClient, connName);
} catch (e) {
console.log("unable to dismiss wsh error: ", e);
}
}, [connName]);
let statusText = `Disconnected from "${connName}"`; let statusText = `Disconnected from "${connName}"`;
let showReconnect = true; let showReconnect = true;
if (connStatus.status == "connecting") { if (connStatus.status == "connecting") {
statusText = `Connecting to "${connName}"...`; statusText = `Connecting to "${connName}"...`;
showReconnect = false; showReconnect = false;
} }
if (connStatus.status == "connected") {
showReconnect = false;
}
let reconDisplay = null; let reconDisplay = null;
let reconClassName = "outlined grey"; let reconClassName = "outlined grey";
if (width && width < 350) { if (width && width < 350) {
@ -373,18 +406,37 @@ const ConnStatusOverlay = React.memo(
} }
const showIcon = connStatus.status != "connecting"; const showIcon = connStatus.status != "connecting";
if (isLayoutMode || connStatus.status == "connected" || connModalOpen) { const wshConfigEnabled = fullConfig?.connections?.[connName]?.["conn:wshenabled"] ?? true;
React.useEffect(() => {
const showWshErrorTemp =
connStatus.status == "connected" &&
connStatus.wsherror &&
connStatus.wsherror != "" &&
wshConfigEnabled;
setShowWshError(showWshErrorTemp);
}, [connStatus, wshConfigEnabled]);
if (!showWshError && (isLayoutMode || connStatus.status == "connected" || connModalOpen)) {
return null; return null;
} }
return ( return (
<div className="connstatus-overlay" ref={overlayRefCallback}> <div className="connstatus-overlay" ref={overlayRefCallback}>
<div className="connstatus-content"> <div className="connstatus-content">
<div className={clsx("connstatus-status-icon-wrapper", { "has-error": showError })}> <div className={clsx("connstatus-status-icon-wrapper", { "has-error": showError || showWshError })}>
{showIcon && <i className="fa-solid fa-triangle-exclamation"></i>} {showIcon && <i className="fa-solid fa-triangle-exclamation"></i>}
<div className="connstatus-status"> <div className="connstatus-status">
<div className="connstatus-status-text">{statusText}</div> <div className="connstatus-status-text">{statusText}</div>
{showError ? <div className="connstatus-error">error: {connStatus.error}</div> : null} {showError ? <div className="connstatus-error">error: {connStatus.error}</div> : null}
{showWshError ? (
<div className="connstatus-error">unable to use wsh: {connStatus.wsherror}</div>
) : null}
{showWshError && (
<Button className={reconClassName} onClick={handleDisableWsh}>
always disable wsh
</Button>
)}
</div> </div>
</div> </div>
{showReconnect ? ( {showReconnect ? (
@ -394,6 +446,11 @@ const ConnStatusOverlay = React.memo(
</Button> </Button>
</div> </div>
) : null} ) : null}
{showWshError ? (
<div className="connstatus-actions">
<Button className={`fa-xmark fa-solid ${reconClassName}`} onClick={handleRemoveWshError} />
</div>
) : null}
</div> </div>
</div> </div>
); );

View File

@ -92,6 +92,11 @@ class RpcApiType {
return client.wshRpcCall("deletesubblock", data, opts); return client.wshRpcCall("deletesubblock", data, opts);
} }
// command "dismisswshfail" [call]
DismissWshFailCommand(client: WshClient, data: string, opts?: RpcOpts): Promise<void> {
return client.wshRpcCall("dismisswshfail", data, opts);
}
// command "dispose" [call] // command "dispose" [call]
DisposeCommand(client: WshClient, data: CommandDisposeData, opts?: RpcOpts): Promise<void> { DisposeCommand(client: WshClient, data: CommandDisposeData, opts?: RpcOpts): Promise<void> {
return client.wshRpcCall("dispose", data, opts); return client.wshRpcCall("dispose", data, opts);
@ -262,6 +267,11 @@ class RpcApiType {
return client.wshRpcCall("setconfig", data, opts); return client.wshRpcCall("setconfig", data, opts);
} }
// command "setconnectionsconfig" [call]
SetConnectionsConfigCommand(client: WshClient, data: ConnConfigRequest, opts?: RpcOpts): Promise<void> {
return client.wshRpcCall("setconnectionsconfig", data, opts);
}
// command "setmeta" [call] // command "setmeta" [call]
SetMetaCommand(client: WshClient, data: CommandSetMetaData, opts?: RpcOpts): Promise<void> { SetMetaCommand(client: WshClient, data: CommandSetMetaData, opts?: RpcOpts): Promise<void> {
return client.wshRpcCall("setmeta", data, opts); return client.wshRpcCall("setmeta", data, opts);

View File

@ -278,6 +278,12 @@ declare global {
err: string; err: string;
}; };
// wshrpc.ConnConfigRequest
type ConnConfigRequest = {
host: string;
metamaptype: MetaType;
};
// wshrpc.ConnKeywords // wshrpc.ConnKeywords
type ConnKeywords = { type ConnKeywords = {
"conn:wshenabled"?: boolean; "conn:wshenabled"?: boolean;
@ -319,6 +325,7 @@ declare global {
hasconnected: boolean; hasconnected: boolean;
activeconnnum: number; activeconnnum: number;
error?: string; error?: string;
wsherror?: string;
}; };
// wshrpc.CpuDataRequest // wshrpc.CpuDataRequest

View File

@ -354,7 +354,26 @@ func (bc *BlockController) DoRunShellCommand(rc *RunShellOpts, blockMeta waveobj
} }
cmdOpts.Env[wshutil.WaveJwtTokenVarName] = jwtStr cmdOpts.Env[wshutil.WaveJwtTokenVarName] = jwtStr
} }
if !conn.WshEnabled.Load() {
shellProc, err = shellexec.StartRemoteShellProcNoWsh(rc.TermSize, cmdStr, cmdOpts, conn)
if err != nil {
return err
}
} else {
shellProc, err = shellexec.StartRemoteShellProc(rc.TermSize, cmdStr, cmdOpts, conn) shellProc, err = shellexec.StartRemoteShellProc(rc.TermSize, cmdStr, cmdOpts, conn)
if err != nil {
conn.WithLock(func() {
conn.WshError = err.Error()
})
conn.WshEnabled.Store(false)
log.Printf("error starting remote shell proc with wsh: %v", err)
log.Print("attempting install without wsh")
shellProc, err = shellexec.StartRemoteShellProcNoWsh(rc.TermSize, cmdStr, cmdOpts, conn)
if err != nil {
return err
}
}
}
if err != nil { if err != nil {
return err return err
} }

View File

@ -59,6 +59,7 @@ type SSHConn struct {
DomainSockListener net.Listener DomainSockListener net.Listener
ConnController *ssh.Session ConnController *ssh.Session
Error string Error string
WshError string
HasWaiter *atomic.Bool HasWaiter *atomic.Bool
LastConnectTime int64 LastConnectTime int64
ActiveConnNum int ActiveConnNum int
@ -94,10 +95,12 @@ func (conn *SSHConn) DeriveConnStatus() wshrpc.ConnStatus {
return wshrpc.ConnStatus{ return wshrpc.ConnStatus{
Status: conn.Status, Status: conn.Status,
Connected: conn.Status == Status_Connected, Connected: conn.Status == Status_Connected,
WshEnabled: conn.WshEnabled.Load(),
Connection: conn.Opts.String(), Connection: conn.Opts.String(),
HasConnected: (conn.LastConnectTime > 0), HasConnected: (conn.LastConnectTime > 0),
ActiveConnNum: conn.ActiveConnNum, ActiveConnNum: conn.ActiveConnNum,
Error: conn.Error, Error: conn.Error,
WshError: conn.WshError,
} }
} }
@ -532,7 +535,11 @@ func (conn *SSHConn) connectInternal(ctx context.Context, connFlags *wshrpc.Conn
}) })
} else if installErr != nil { } else if installErr != nil {
log.Printf("error: unable to install wsh shell extensions for %s: %v\n", conn.GetName(), err) 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) log.Print("attempting to run with nowsh instead")
conn.WithLock(func() {
conn.WshError = installErr.Error()
})
conn.WshEnabled.Store(false)
} else { } else {
conn.WshEnabled.Store(true) conn.WshEnabled.Store(true)
} }
@ -541,7 +548,12 @@ func (conn *SSHConn) connectInternal(ctx context.Context, connFlags *wshrpc.Conn
csErr := conn.StartConnServer() csErr := conn.StartConnServer()
if csErr != nil { if csErr != nil {
log.Printf("error: unable to start conn server for %s: %v\n", conn.GetName(), csErr) 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) log.Print("attempting to run with nowsh instead")
conn.WithLock(func() {
conn.WshError = csErr.Error()
})
conn.WshEnabled.Store(false)
//return fmt.Errorf("conncontroller %s start wsh connserver error: %v", conn.GetName(), csErr)
} }
} }
} else { } else {

View File

@ -236,10 +236,8 @@ func StartWslShellProc(ctx context.Context, termSize waveobj.TermSize, cmdStr st
return &ShellProc{Cmd: cmdWrap, ConnName: conn.GetName(), CloseOnce: &sync.Once{}, DoneCh: make(chan any)}, nil return &ShellProc{Cmd: cmdWrap, ConnName: conn.GetName(), CloseOnce: &sync.Once{}, DoneCh: make(chan any)}, nil
} }
func StartRemoteShellProc(termSize waveobj.TermSize, cmdStr string, cmdOpts CommandOptsType, conn *conncontroller.SSHConn) (*ShellProc, error) { func StartRemoteShellProcNoWsh(termSize waveobj.TermSize, cmdStr string, cmdOpts CommandOptsType, conn *conncontroller.SSHConn) (*ShellProc, error) {
client := conn.GetClient() client := conn.GetClient()
if !conn.WshEnabled.Load() {
// no wsh code
session, err := client.NewSession() session, err := client.NewSession()
if err != nil { if err != nil {
return nil, err return nil, err
@ -278,7 +276,10 @@ func StartRemoteShellProc(termSize waveobj.TermSize, cmdStr string, cmdOpts Comm
return nil, err return nil, err
} }
return &ShellProc{Cmd: sessionWrap, ConnName: conn.GetName(), CloseOnce: &sync.Once{}, DoneCh: make(chan any)}, nil return &ShellProc{Cmd: sessionWrap, ConnName: conn.GetName(), CloseOnce: &sync.Once{}, DoneCh: make(chan any)}, nil
} }
func StartRemoteShellProc(termSize waveobj.TermSize, cmdStr string, cmdOpts CommandOptsType, conn *conncontroller.SSHConn) (*ShellProc, error) {
client := conn.GetClient()
shellPath := cmdOpts.ShellPath shellPath := cmdOpts.ShellPath
if shellPath == "" { if shellPath == "" {
remoteShellPath, err := remote.DetectShell(client) remoteShellPath, err := remote.DetectShell(client)

View File

@ -115,6 +115,12 @@ func DeleteSubBlockCommand(w *wshutil.WshRpc, data wshrpc.CommandDeleteBlockData
return err return err
} }
// command "dismisswshfail", wshserver.DismissWshFailCommand
func DismissWshFailCommand(w *wshutil.WshRpc, data string, opts *wshrpc.RpcOpts) error {
_, err := sendRpcRequestCallHelper[any](w, "dismisswshfail", data, opts)
return err
}
// command "dispose", wshserver.DisposeCommand // command "dispose", wshserver.DisposeCommand
func DisposeCommand(w *wshutil.WshRpc, data wshrpc.CommandDisposeData, opts *wshrpc.RpcOpts) error { func DisposeCommand(w *wshutil.WshRpc, data wshrpc.CommandDisposeData, opts *wshrpc.RpcOpts) error {
_, err := sendRpcRequestCallHelper[any](w, "dispose", data, opts) _, err := sendRpcRequestCallHelper[any](w, "dispose", data, opts)
@ -317,6 +323,12 @@ func SetConfigCommand(w *wshutil.WshRpc, data wshrpc.MetaSettingsType, opts *wsh
return err return err
} }
// command "setconnectionsconfig", wshserver.SetConnectionsConfigCommand
func SetConnectionsConfigCommand(w *wshutil.WshRpc, data wshrpc.ConnConfigRequest, opts *wshrpc.RpcOpts) error {
_, err := sendRpcRequestCallHelper[any](w, "setconnectionsconfig", data, opts)
return err
}
// command "setmeta", wshserver.SetMetaCommand // command "setmeta", wshserver.SetMetaCommand
func SetMetaCommand(w *wshutil.WshRpc, data wshrpc.CommandSetMetaData, opts *wshrpc.RpcOpts) error { func SetMetaCommand(w *wshutil.WshRpc, data wshrpc.CommandSetMetaData, opts *wshrpc.RpcOpts) error {
_, err := sendRpcRequestCallHelper[any](w, "setmeta", data, opts) _, err := sendRpcRequestCallHelper[any](w, "setmeta", data, opts)

View File

@ -59,6 +59,8 @@ const (
Command_StreamWaveAi = "streamwaveai" Command_StreamWaveAi = "streamwaveai"
Command_StreamCpuData = "streamcpudata" Command_StreamCpuData = "streamcpudata"
Command_Test = "test" Command_Test = "test"
Command_SetConfig = "setconfig"
Command_SetConnectionsConfig = "connectionsconfig"
Command_RemoteStreamFile = "remotestreamfile" Command_RemoteStreamFile = "remotestreamfile"
Command_RemoteFileInfo = "remotefileinfo" Command_RemoteFileInfo = "remotefileinfo"
Command_RemoteFileTouch = "remotefiletouch" Command_RemoteFileTouch = "remotefiletouch"
@ -81,6 +83,7 @@ const (
Command_ConnList = "connlist" Command_ConnList = "connlist"
Command_WslList = "wsllist" Command_WslList = "wsllist"
Command_WslDefaultDistro = "wsldefaultdistro" Command_WslDefaultDistro = "wsldefaultdistro"
Command_DismissWshFail = "dismisswshfail"
Command_WorkspaceList = "workspacelist" Command_WorkspaceList = "workspacelist"
@ -139,6 +142,7 @@ type WshRpcInterface interface {
StreamCpuDataCommand(ctx context.Context, request CpuDataRequest) chan RespOrErrorUnion[TimeSeriesData] StreamCpuDataCommand(ctx context.Context, request CpuDataRequest) chan RespOrErrorUnion[TimeSeriesData]
TestCommand(ctx context.Context, data string) error TestCommand(ctx context.Context, data string) error
SetConfigCommand(ctx context.Context, data MetaSettingsType) error SetConfigCommand(ctx context.Context, data MetaSettingsType) error
SetConnectionsConfigCommand(ctx context.Context, data ConnConfigRequest) error
BlockInfoCommand(ctx context.Context, blockId string) (*BlockInfoData, error) BlockInfoCommand(ctx context.Context, blockId string) (*BlockInfoData, error)
WaveInfoCommand(ctx context.Context) (*WaveInfoData, error) WaveInfoCommand(ctx context.Context) (*WaveInfoData, error)
WshActivityCommand(ct context.Context, data map[string]int) error WshActivityCommand(ct context.Context, data map[string]int) error
@ -156,6 +160,7 @@ type WshRpcInterface interface {
ConnListCommand(ctx context.Context) ([]string, error) ConnListCommand(ctx context.Context) ([]string, error)
WslListCommand(ctx context.Context) ([]string, error) WslListCommand(ctx context.Context) ([]string, error)
WslDefaultDistroCommand(ctx context.Context) (string, error) WslDefaultDistroCommand(ctx context.Context) (string, error)
DismissWshFailCommand(ctx context.Context, connName string) error
// eventrecv is special, it's handled internally by WshRpc with EventListener // eventrecv is special, it's handled internally by WshRpc with EventListener
EventRecvCommand(ctx context.Context, data wps.WaveEvent) error EventRecvCommand(ctx context.Context, data wps.WaveEvent) error
@ -512,6 +517,11 @@ func (m MetaSettingsType) MarshalJSON() ([]byte, error) {
return json.Marshal(m.MetaMapType) return json.Marshal(m.MetaMapType)
} }
type ConnConfigRequest struct {
Host string `json:"host"`
MetaMapType waveobj.MetaMapType `json:"metamaptype"`
}
type ConnStatus struct { type ConnStatus struct {
Status string `json:"status"` Status string `json:"status"`
WshEnabled bool `json:"wshenabled"` WshEnabled bool `json:"wshenabled"`
@ -520,6 +530,7 @@ type ConnStatus struct {
HasConnected bool `json:"hasconnected"` // true if it has *ever* connected successfully HasConnected bool `json:"hasconnected"` // true if it has *ever* connected successfully
ActiveConnNum int `json:"activeconnnum"` ActiveConnNum int `json:"activeconnnum"`
Error string `json:"error,omitempty"` Error string `json:"error,omitempty"`
WshError string `json:"wsherror,omitempty"`
} }
type WebSelectorOpts struct { type WebSelectorOpts struct {

View File

@ -575,6 +575,11 @@ func (ws *WshServer) SetConfigCommand(ctx context.Context, data wshrpc.MetaSetti
return wconfig.SetBaseConfigValue(data.MetaMapType) return wconfig.SetBaseConfigValue(data.MetaMapType)
} }
func (ws *WshServer) SetConnectionsConfigCommand(ctx context.Context, data wshrpc.ConnConfigRequest) error {
log.Printf("SET CONNECTIONS CONFIG: %v\n", data)
return wconfig.SetConnectionsConfigValue(data.Host, data.MetaMapType)
}
func (ws *WshServer) ConnStatusCommand(ctx context.Context) ([]wshrpc.ConnStatus, error) { func (ws *WshServer) ConnStatusCommand(ctx context.Context) ([]wshrpc.ConnStatus, error) {
rtn := conncontroller.GetAllConnStatus() rtn := conncontroller.GetAllConnStatus()
return rtn, nil return rtn, nil
@ -685,6 +690,25 @@ func (ws *WshServer) WslDefaultDistroCommand(ctx context.Context) (string, error
return distro.Name(), nil return distro.Name(), nil
} }
/**
* Dismisses the WshFail Command in runtime memory on the backend
*/
func (ws *WshServer) DismissWshFailCommand(ctx context.Context, connName string) error {
opts, err := remote.ParseOpts(connName)
if err != nil {
return err
}
conn := conncontroller.GetConn(ctx, opts, false, nil)
if conn == nil {
return fmt.Errorf("connection %s not found", connName)
}
conn.WithLock(func() {
conn.WshError = ""
})
conn.FireConnChangeEvent()
return nil
}
func (ws *WshServer) BlockInfoCommand(ctx context.Context, blockId string) (*wshrpc.BlockInfoData, error) { func (ws *WshServer) BlockInfoCommand(ctx context.Context, blockId string) (*wshrpc.BlockInfoData, error) {
blockData, err := wstore.DBMustGet[*waveobj.Block](ctx, blockId) blockData, err := wstore.DBMustGet[*waveobj.Block](ctx, blockId)
if err != nil { if err != nil {