mirror of
https://github.com/wavetermdev/waveterm.git
synced 2025-01-19 21:11:32 +01:00
Connect With Non-Terminal Widgets (#304)
- Adds connection buttons for previews - Makes it possible for graphs and previews to connect on backend (without a terminal open to connection) - Changes the wsh install message
This commit is contained in:
parent
63cfe1d279
commit
226bc4ee6f
@ -353,12 +353,21 @@ const ChangeConnectionBlockModal = React.memo(
|
|||||||
}) => {
|
}) => {
|
||||||
const [connSelected, setConnSelected] = React.useState("");
|
const [connSelected, setConnSelected] = React.useState("");
|
||||||
const changeConnModalOpen = jotai.useAtomValue(changeConnModalAtom);
|
const changeConnModalOpen = jotai.useAtomValue(changeConnModalAtom);
|
||||||
|
const [blockData] = WOS.useWaveObjectValue<Block>(WOS.makeORef("block", blockId));
|
||||||
const changeConnection = React.useCallback(
|
const changeConnection = React.useCallback(
|
||||||
async (connName: string) => {
|
async (connName: string) => {
|
||||||
|
const oldCwd = blockData?.meta?.file ?? "";
|
||||||
|
let newCwd: string;
|
||||||
|
if (oldCwd == "") {
|
||||||
|
newCwd = "";
|
||||||
|
} else {
|
||||||
|
newCwd = "~";
|
||||||
|
}
|
||||||
await WshServer.SetMetaCommand({
|
await WshServer.SetMetaCommand({
|
||||||
oref: WOS.makeORef("block", blockId),
|
oref: WOS.makeORef("block", blockId),
|
||||||
meta: { connection: connName },
|
meta: { connection: connName, file: newCwd },
|
||||||
});
|
});
|
||||||
|
await services.BlockService.EnsureConnection(blockId).catch((e) => console.log(e));
|
||||||
await WshServer.ControllerRestartCommand({ blockid: blockId });
|
await WshServer.ControllerRestartCommand({ blockid: blockId });
|
||||||
},
|
},
|
||||||
[blockId]
|
[blockId]
|
||||||
|
@ -7,6 +7,9 @@ import * as WOS from "./wos";
|
|||||||
|
|
||||||
// blockservice.BlockService (block)
|
// blockservice.BlockService (block)
|
||||||
class BlockServiceType {
|
class BlockServiceType {
|
||||||
|
EnsureConnection(arg2: string): Promise<void> {
|
||||||
|
return WOS.callBackendService("block", "EnsureConnection", Array.from(arguments))
|
||||||
|
}
|
||||||
GetControllerStatus(arg2: string): Promise<BlockControllerRuntimeStatus> {
|
GetControllerStatus(arg2: string): Promise<BlockControllerRuntimeStatus> {
|
||||||
return WOS.callBackendService("block", "GetControllerStatus", Array.from(arguments))
|
return WOS.callBackendService("block", "GetControllerStatus", Array.from(arguments))
|
||||||
}
|
}
|
||||||
|
@ -52,6 +52,7 @@ export class PreviewModel implements ViewModel {
|
|||||||
previewTextRef: React.RefObject<HTMLDivElement>;
|
previewTextRef: React.RefObject<HTMLDivElement>;
|
||||||
editMode: jotai.Atom<boolean>;
|
editMode: jotai.Atom<boolean>;
|
||||||
canPreview: jotai.PrimitiveAtom<boolean>;
|
canPreview: jotai.PrimitiveAtom<boolean>;
|
||||||
|
manageConnection: jotai.Atom<boolean>;
|
||||||
|
|
||||||
fileName: jotai.Atom<string>;
|
fileName: jotai.Atom<string>;
|
||||||
connection: jotai.Atom<string>;
|
connection: jotai.Atom<string>;
|
||||||
@ -81,6 +82,7 @@ export class PreviewModel implements ViewModel {
|
|||||||
this.ceReadOnly = jotai.atom(true);
|
this.ceReadOnly = jotai.atom(true);
|
||||||
this.canPreview = jotai.atom(false);
|
this.canPreview = jotai.atom(false);
|
||||||
this.openFileModal = jotai.atom(false);
|
this.openFileModal = jotai.atom(false);
|
||||||
|
this.manageConnection = jotai.atom(true);
|
||||||
this.blockAtom = WOS.getWaveObjectAtom<Block>(`block:${blockId}`);
|
this.blockAtom = WOS.getWaveObjectAtom<Block>(`block:${blockId}`);
|
||||||
this.viewIcon = jotai.atom((get) => {
|
this.viewIcon = jotai.atom((get) => {
|
||||||
let blockData = get(this.blockAtom);
|
let blockData = get(this.blockAtom);
|
||||||
|
@ -19,11 +19,13 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/kevinburke/ssh_config"
|
"github.com/kevinburke/ssh_config"
|
||||||
|
"github.com/skeema/knownhosts"
|
||||||
"github.com/wavetermdev/thenextwave/pkg/remote"
|
"github.com/wavetermdev/thenextwave/pkg/remote"
|
||||||
"github.com/wavetermdev/thenextwave/pkg/userinput"
|
"github.com/wavetermdev/thenextwave/pkg/userinput"
|
||||||
"github.com/wavetermdev/thenextwave/pkg/util/shellutil"
|
"github.com/wavetermdev/thenextwave/pkg/util/shellutil"
|
||||||
"github.com/wavetermdev/thenextwave/pkg/util/utilfn"
|
"github.com/wavetermdev/thenextwave/pkg/util/utilfn"
|
||||||
"github.com/wavetermdev/thenextwave/pkg/wavebase"
|
"github.com/wavetermdev/thenextwave/pkg/wavebase"
|
||||||
|
"github.com/wavetermdev/thenextwave/pkg/waveobj"
|
||||||
"github.com/wavetermdev/thenextwave/pkg/wps"
|
"github.com/wavetermdev/thenextwave/pkg/wps"
|
||||||
"github.com/wavetermdev/thenextwave/pkg/wshrpc"
|
"github.com/wavetermdev/thenextwave/pkg/wshrpc"
|
||||||
"github.com/wavetermdev/thenextwave/pkg/wshutil"
|
"github.com/wavetermdev/thenextwave/pkg/wshutil"
|
||||||
@ -84,7 +86,7 @@ func (conn *SSHConn) FireConnChangeEvent() {
|
|||||||
},
|
},
|
||||||
Data: status,
|
Data: status,
|
||||||
}
|
}
|
||||||
log.Printf("connstatus change %q => %s\n", conn.GetName(), status.Status)
|
log.Printf("sending event: %+#v", event)
|
||||||
wps.Broker.Publish(event)
|
wps.Broker.Publish(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -241,7 +243,7 @@ func (conn *SSHConn) StartConnServer() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (conn *SSHConn) checkAndInstallWsh(ctx context.Context) error {
|
func (conn *SSHConn) checkAndInstallWsh(ctx context.Context, clientDisplayName string) error {
|
||||||
client := conn.GetClient()
|
client := conn.GetClient()
|
||||||
if client == nil {
|
if client == nil {
|
||||||
return fmt.Errorf("client is nil")
|
return fmt.Errorf("client is nil")
|
||||||
@ -252,27 +254,33 @@ func (conn *SSHConn) checkAndInstallWsh(ctx context.Context) error {
|
|||||||
if err == nil && clientVersion == expectedVersion {
|
if err == nil && clientVersion == expectedVersion {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// TODO add some progress to SSHConn about install status
|
|
||||||
var queryText string
|
var queryText string
|
||||||
var title string
|
var title string
|
||||||
if err != nil {
|
if err != nil {
|
||||||
queryText = "Waveterm requires `wsh` shell extensions installed on your client to ensure a seamless experience. Would you like to install them?"
|
queryText = fmt.Sprintf("Wave requires Wave Shell Extensions to be \n"+
|
||||||
title = "Install Wsh Shell Extensions"
|
"installed on `%s` \n"+
|
||||||
|
"to ensure a seamless experience. \n\n"+
|
||||||
|
"Would you like to install them?", clientDisplayName)
|
||||||
|
title = "Install Wave Shell Extensions"
|
||||||
} else {
|
} else {
|
||||||
queryText = fmt.Sprintf("Waveterm requires `wsh` shell extensions installed on your client to be updated from %s to %s. Would you like to update?", clientVersion, expectedVersion)
|
queryText = fmt.Sprintf("Wave requires the Wave Shell Extensions \n"+
|
||||||
title = "Update Wsh Shell Extensions"
|
"installed on `%s` \n"+
|
||||||
|
"to be updated from %s to %s. \n\n"+
|
||||||
|
"Would you like to update?", clientDisplayName, clientVersion, expectedVersion)
|
||||||
|
title = "Update Wave Shell Extensions"
|
||||||
}
|
}
|
||||||
request := &userinput.UserInputRequest{
|
request := &userinput.UserInputRequest{
|
||||||
ResponseType: "confirm",
|
ResponseType: "confirm",
|
||||||
QueryText: queryText,
|
QueryText: queryText,
|
||||||
Title: title,
|
Title: title,
|
||||||
|
Markdown: true,
|
||||||
CheckBoxMsg: "Don't show me this again",
|
CheckBoxMsg: "Don't show me this again",
|
||||||
}
|
}
|
||||||
response, err := userinput.GetUserInput(ctx, request)
|
response, err := userinput.GetUserInput(ctx, request)
|
||||||
if err != nil || !response.Confirm {
|
if err != nil || !response.Confirm {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.Printf("attempting to install wsh to `%s@%s`", client.User(), client.RemoteAddr().String())
|
log.Printf("attempting to install wsh to `%s`", clientDisplayName)
|
||||||
clientOs, err := remote.GetClientOs(client)
|
clientOs, err := remote.GetClientOs(client)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -346,6 +354,8 @@ func (conn *SSHConn) connectInternal(ctx context.Context) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
fmtAddr := knownhosts.Normalize(fmt.Sprintf("%s@%s", client.User(), client.RemoteAddr().String()))
|
||||||
|
clientDisplayName := fmt.Sprintf("%s (%s)", conn.GetName(), fmtAddr)
|
||||||
conn.WithLock(func() {
|
conn.WithLock(func() {
|
||||||
conn.Client = client
|
conn.Client = client
|
||||||
})
|
})
|
||||||
@ -353,7 +363,7 @@ func (conn *SSHConn) connectInternal(ctx context.Context) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
installErr := conn.checkAndInstallWsh(ctx)
|
installErr := conn.checkAndInstallWsh(ctx, clientDisplayName)
|
||||||
if installErr != nil {
|
if installErr != nil {
|
||||||
return fmt.Errorf("conncontroller %s wsh install error: %v", conn.GetName(), installErr)
|
return fmt.Errorf("conncontroller %s wsh install error: %v", conn.GetName(), installErr)
|
||||||
}
|
}
|
||||||
@ -408,6 +418,48 @@ func GetConn(ctx context.Context, opts *remote.SSHOpts, shouldConnect bool) *SSH
|
|||||||
return conn
|
return conn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Convenience function for ensuring a connection is established
|
||||||
|
func EnsureConnection(ctx context.Context, blockData *waveobj.Block) error {
|
||||||
|
connectionName := blockData.Meta.GetString(waveobj.MetaKey_Connection, "")
|
||||||
|
if connectionName == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
credentialCtx, cancelFunc := context.WithTimeout(context.Background(), 60*time.Second)
|
||||||
|
defer cancelFunc()
|
||||||
|
|
||||||
|
opts, err := remote.ParseOpts(connectionName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
conn := GetConn(credentialCtx, opts, true)
|
||||||
|
statusChan := make(chan string, 1)
|
||||||
|
go func() {
|
||||||
|
// we need to wait for connected/disconnected/error
|
||||||
|
// to ensure the connection has been established before
|
||||||
|
// continuing in the original thread
|
||||||
|
for {
|
||||||
|
// GetStatus has a lock which makes this reasonable to loop over
|
||||||
|
status := conn.GetStatus()
|
||||||
|
if credentialCtx.Err() != nil {
|
||||||
|
// prevent infinite loop from context
|
||||||
|
statusChan <- Status_Error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if status == Status_Connected || status == Status_Disconnected || status == Status_Error {
|
||||||
|
statusChan <- status
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
status := <-statusChan
|
||||||
|
if status == Status_Error {
|
||||||
|
return fmt.Errorf("connection error: %v", conn.Error)
|
||||||
|
} else if status == Status_Disconnected {
|
||||||
|
return fmt.Errorf("disconnected: %v", conn.Error)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func DisconnectClient(opts *remote.SSHOpts) error {
|
func DisconnectClient(opts *remote.SSHOpts) error {
|
||||||
conn := getConnInternal(opts)
|
conn := getConnInternal(opts)
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
|
|
||||||
"github.com/wavetermdev/thenextwave/pkg/blockcontroller"
|
"github.com/wavetermdev/thenextwave/pkg/blockcontroller"
|
||||||
"github.com/wavetermdev/thenextwave/pkg/filestore"
|
"github.com/wavetermdev/thenextwave/pkg/filestore"
|
||||||
|
"github.com/wavetermdev/thenextwave/pkg/remote/conncontroller"
|
||||||
"github.com/wavetermdev/thenextwave/pkg/tsgen/tsgenmeta"
|
"github.com/wavetermdev/thenextwave/pkg/tsgen/tsgenmeta"
|
||||||
"github.com/wavetermdev/thenextwave/pkg/waveobj"
|
"github.com/wavetermdev/thenextwave/pkg/waveobj"
|
||||||
"github.com/wavetermdev/thenextwave/pkg/wshrpc"
|
"github.com/wavetermdev/thenextwave/pkg/wshrpc"
|
||||||
@ -83,3 +84,11 @@ func (bs *BlockService) SaveWaveAiData(ctx context.Context, blockId string, hist
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (bs *BlockService) EnsureConnection(ctx context.Context, blockId string) error {
|
||||||
|
block, err := wstore.DBMustGet[*waveobj.Block](ctx, blockId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return conncontroller.EnsureConnection(ctx, block)
|
||||||
|
}
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/wavetermdev/thenextwave/pkg/blockcontroller"
|
"github.com/wavetermdev/thenextwave/pkg/blockcontroller"
|
||||||
|
"github.com/wavetermdev/thenextwave/pkg/remote/conncontroller"
|
||||||
"github.com/wavetermdev/thenextwave/pkg/tsgen/tsgenmeta"
|
"github.com/wavetermdev/thenextwave/pkg/tsgen/tsgenmeta"
|
||||||
"github.com/wavetermdev/thenextwave/pkg/waveobj"
|
"github.com/wavetermdev/thenextwave/pkg/waveobj"
|
||||||
"github.com/wavetermdev/thenextwave/pkg/wcore"
|
"github.com/wavetermdev/thenextwave/pkg/wcore"
|
||||||
@ -133,6 +134,14 @@ func (svc *ObjectService) SetActiveTab(uiContext waveobj.UIContext, tabId string
|
|||||||
return nil, fmt.Errorf("error getting tab: %w", err)
|
return nil, fmt.Errorf("error getting tab: %w", err)
|
||||||
}
|
}
|
||||||
for _, blockId := range tab.BlockIds {
|
for _, blockId := range tab.BlockIds {
|
||||||
|
blockData, err := wstore.DBMustGet[*waveobj.Block](ctx, blockId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error getting block: %w", err)
|
||||||
|
}
|
||||||
|
err = conncontroller.EnsureConnection(ctx, blockData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to ensure connection: %v", err)
|
||||||
|
}
|
||||||
blockErr := blockcontroller.StartBlockController(ctx, tabId, blockId)
|
blockErr := blockcontroller.StartBlockController(ctx, tabId, blockId)
|
||||||
if blockErr != nil {
|
if blockErr != nil {
|
||||||
// we don't want to fail the set active tab operation if a block controller fails to start
|
// we don't want to fail the set active tab operation if a block controller fails to start
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/wavetermdev/thenextwave/pkg/blockcontroller"
|
"github.com/wavetermdev/thenextwave/pkg/blockcontroller"
|
||||||
|
"github.com/wavetermdev/thenextwave/pkg/remote/conncontroller"
|
||||||
"github.com/wavetermdev/thenextwave/pkg/waveobj"
|
"github.com/wavetermdev/thenextwave/pkg/waveobj"
|
||||||
"github.com/wavetermdev/thenextwave/pkg/wps"
|
"github.com/wavetermdev/thenextwave/pkg/wps"
|
||||||
"github.com/wavetermdev/thenextwave/pkg/wshrpc"
|
"github.com/wavetermdev/thenextwave/pkg/wshrpc"
|
||||||
@ -173,6 +174,10 @@ func CreateBlock(ctx context.Context, tabId string, blockDef *waveobj.BlockDef,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error creating block: %w", err)
|
return nil, fmt.Errorf("error creating block: %w", err)
|
||||||
}
|
}
|
||||||
|
err = conncontroller.EnsureConnection(ctx, blockData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to ensure connection: %v", err)
|
||||||
|
}
|
||||||
controllerName := blockData.Meta.GetString(waveobj.MetaKey_Controller, "")
|
controllerName := blockData.Meta.GetString(waveobj.MetaKey_Controller, "")
|
||||||
if controllerName != "" {
|
if controllerName != "" {
|
||||||
err = blockcontroller.StartBlockController(ctx, tabId, blockData.OID)
|
err = blockcontroller.StartBlockController(ctx, tabId, blockData.OID)
|
||||||
|
Loading…
Reference in New Issue
Block a user