From e0356dc2e8a9ad87fe5719cec79f4db6fa9b4ec2 Mon Sep 17 00:00:00 2001 From: Sylvia Crowe Date: Tue, 3 Dec 2024 11:31:44 -0800 Subject: [PATCH 01/14] feat: add hidden to remove conn from dropdown --- frontend/app/block/blockframe.tsx | 6 ++++-- frontend/types/gotypes.d.ts | 1 + pkg/wshrpc/wshrpctypes.go | 1 + 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/frontend/app/block/blockframe.tsx b/frontend/app/block/blockframe.tsx index b3e93d22fb..94141689c2 100644 --- a/frontend/app/block/blockframe.tsx +++ b/frontend/app/block/blockframe.tsx @@ -570,6 +570,8 @@ const ChangeConnectionBlockModal = React.memo( const allConnStatus = jotai.useAtomValue(atoms.allConnStatus); const [rowIndex, setRowIndex] = React.useState(0); const connStatusMap = new Map(); + const fullConfig = jotai.useAtomValue(atoms.fullConfigAtom); + const connectionsConfig = fullConfig.connections; let maxActiveConnNum = 1; for (const conn of allConnStatus) { if (conn.activeconnnum > maxActiveConnNum) { @@ -640,7 +642,7 @@ const ChangeConnectionBlockModal = React.memo( if (conn === connSelected) { createNew = false; } - if (conn.includes(connSelected)) { + if (conn.includes(connSelected) && connectionsConfig[conn]?.hidden != true) { filteredList.push(conn); } } @@ -649,7 +651,7 @@ const ChangeConnectionBlockModal = React.memo( if (conn === connSelected) { createNew = false; } - if (conn.includes(connSelected)) { + if (conn.includes(connSelected) && connectionsConfig[conn]?.hidden != true) { filteredWslList.push(conn); } } diff --git a/frontend/types/gotypes.d.ts b/frontend/types/gotypes.d.ts index d68fd93d6f..45ad15f423 100644 --- a/frontend/types/gotypes.d.ts +++ b/frontend/types/gotypes.d.ts @@ -278,6 +278,7 @@ declare global { type ConnKeywords = { wshenabled?: boolean; askbeforewshinstall?: boolean; + hidden?: boolean; "ssh:user"?: string; "ssh:hostname"?: string; "ssh:port"?: string; diff --git a/pkg/wshrpc/wshrpctypes.go b/pkg/wshrpc/wshrpctypes.go index 517ef33e02..22e2a730cf 100644 --- a/pkg/wshrpc/wshrpctypes.go +++ b/pkg/wshrpc/wshrpctypes.go @@ -454,6 +454,7 @@ type CommandRemoteWriteFileData struct { type ConnKeywords struct { WshEnabled *bool `json:"wshenabled,omitempty"` AskBeforeWshInstall *bool `json:"askbeforewshinstall,omitempty"` + Hidden *bool `json:"hidden,omitempty"` SshUser string `json:"ssh:user,omitempty"` SshHostName string `json:"ssh:hostname,omitempty"` From 47caba0d85067a859993a2a8a2347b3432af3e8b Mon Sep 17 00:00:00 2001 From: Sylvia Crowe Date: Tue, 3 Dec 2024 12:34:35 -0800 Subject: [PATCH 02/14] feat: add terminal theming to connections config --- frontend/app/view/term/term.tsx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/frontend/app/view/term/term.tsx b/frontend/app/view/term/term.tsx index 66474b5fbb..3efb675e6b 100644 --- a/frontend/app/view/term/term.tsx +++ b/frontend/app/view/term/term.tsx @@ -543,17 +543,19 @@ const TerminalView = ({ blockId, model }: TerminalViewProps) => { const [blockData] = WOS.useWaveObjectValue(WOS.makeORef("block", blockId)); const termSettingsAtom = useSettingsPrefixAtom("term"); const termSettings = jotai.useAtomValue(termSettingsAtom); + const fullConfig = jotai.useAtomValue(atoms.fullConfigAtom); + const connName = blockData.meta?.connection; let termMode = blockData?.meta?.["term:mode"] ?? "term"; if (termMode != "term" && termMode != "vdom") { termMode = "term"; } const termModeRef = React.useRef(termMode); - const termFontSize = jotai.useAtomValue(model.fontSizeAtom); - React.useEffect(() => { - const fullConfig = globalStore.get(atoms.fullConfigAtom); - const termThemeName = globalStore.get(model.termThemeNameAtom); + const connConfig = fullConfig.connections; + const termFontSize = connConfig[connName]?.["term:fontsize"] ?? jotai.useAtomValue(model.fontSizeAtom); + const termFontFamily = connConfig[connName]?.["term:fontfamily"] ?? termSettings?.["term:fontfamily"] ?? "Hack"; + const termThemeName = connConfig[connName]?.["term:themename"] ?? jotai.useAtomValue(model.termThemeNameAtom); const [termTheme, _] = computeTheme(fullConfig, termThemeName); let termScrollback = 1000; if (termSettings?.["term:scrollback"]) { @@ -575,7 +577,7 @@ const TerminalView = ({ blockId, model }: TerminalViewProps) => { { theme: termTheme, fontSize: termFontSize, - fontFamily: termSettings?.["term:fontfamily"] ?? "Hack", + fontFamily: termFontFamily, drawBoldTextInBrightColors: false, fontWeight: "normal", fontWeightBold: "bold", @@ -603,7 +605,7 @@ const TerminalView = ({ blockId, model }: TerminalViewProps) => { termWrap.dispose(); rszObs.disconnect(); }; - }, [blockId, termSettings, termFontSize]); + }, [blockId, termSettings, connName, fullConfig]); React.useEffect(() => { if (termModeRef.current == "vdom" && termMode == "term") { From 15755a81344f20e4d2986a40685acd9c277cecf3 Mon Sep 17 00:00:00 2001 From: Sylvia Crowe Date: Wed, 4 Dec 2024 01:09:26 -0800 Subject: [PATCH 03/14] feat: naive approach at integrate conn term themes --- frontend/app/view/term/term.tsx | 10 ++++++---- frontend/types/gotypes.d.ts | 4 ++++ pkg/wshrpc/wshrpctypes.go | 5 +++++ 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/frontend/app/view/term/term.tsx b/frontend/app/view/term/term.tsx index 3efb675e6b..44cd3d9305 100644 --- a/frontend/app/view/term/term.tsx +++ b/frontend/app/view/term/term.tsx @@ -545,6 +545,11 @@ const TerminalView = ({ blockId, model }: TerminalViewProps) => { const termSettings = jotai.useAtomValue(termSettingsAtom); const fullConfig = jotai.useAtomValue(atoms.fullConfigAtom); const connName = blockData.meta?.connection; + const connConfig = fullConfig.connections; + const globalFontSize = jotai.useAtomValue(model.fontSizeAtom); + const globalTermThemeName = jotai.useAtomValue(model.termThemeNameAtom); + const termFontSize = connConfig[connName]?.["term:fontsize"] ?? globalFontSize; + const termThemeName = connConfig[connName]?.["term:theme"] ?? globalTermThemeName; let termMode = blockData?.meta?.["term:mode"] ?? "term"; if (termMode != "term" && termMode != "vdom") { termMode = "term"; @@ -552,10 +557,7 @@ const TerminalView = ({ blockId, model }: TerminalViewProps) => { const termModeRef = React.useRef(termMode); React.useEffect(() => { - const connConfig = fullConfig.connections; - const termFontSize = connConfig[connName]?.["term:fontsize"] ?? jotai.useAtomValue(model.fontSizeAtom); const termFontFamily = connConfig[connName]?.["term:fontfamily"] ?? termSettings?.["term:fontfamily"] ?? "Hack"; - const termThemeName = connConfig[connName]?.["term:themename"] ?? jotai.useAtomValue(model.termThemeNameAtom); const [termTheme, _] = computeTheme(fullConfig, termThemeName); let termScrollback = 1000; if (termSettings?.["term:scrollback"]) { @@ -605,7 +607,7 @@ const TerminalView = ({ blockId, model }: TerminalViewProps) => { termWrap.dispose(); rszObs.disconnect(); }; - }, [blockId, termSettings, connName, fullConfig]); + }, [blockId, termSettings, connName, fullConfig, termFontSize, termThemeName]); React.useEffect(() => { if (termModeRef.current == "vdom" && termMode == "term") { diff --git a/frontend/types/gotypes.d.ts b/frontend/types/gotypes.d.ts index 45ad15f423..34d44012cd 100644 --- a/frontend/types/gotypes.d.ts +++ b/frontend/types/gotypes.d.ts @@ -279,6 +279,10 @@ declare global { wshenabled?: boolean; askbeforewshinstall?: boolean; hidden?: boolean; + "term:*"?: boolean; + "term:fontsize"?: number; + "term:fontfamily"?: string; + "term:theme"?: string; "ssh:user"?: string; "ssh:hostname"?: string; "ssh:port"?: string; diff --git a/pkg/wshrpc/wshrpctypes.go b/pkg/wshrpc/wshrpctypes.go index 22e2a730cf..ae1db05649 100644 --- a/pkg/wshrpc/wshrpctypes.go +++ b/pkg/wshrpc/wshrpctypes.go @@ -456,6 +456,11 @@ type ConnKeywords struct { AskBeforeWshInstall *bool `json:"askbeforewshinstall,omitempty"` Hidden *bool `json:"hidden,omitempty"` + TermClear bool `json:"term:*,omitempty"` + TermFontSize float64 `json:"term:fontsize,omitempty"` + TermFontFamily string `json:"term:fontfamily,omitempty"` + TermTheme string `json:"term:theme,omitempty"` + SshUser string `json:"ssh:user,omitempty"` SshHostName string `json:"ssh:hostname,omitempty"` SshPort string `json:"ssh:port,omitempty"` From aae27037c1f28b69106fb5e1ce2283179f05f1b3 Mon Sep 17 00:00:00 2001 From: Sylvia Crowe Date: Wed, 4 Dec 2024 11:44:31 -0800 Subject: [PATCH 04/14] feat: save successful connections and details --- pkg/remote/conncontroller/conncontroller.go | 48 ++++++++++++++++++++- pkg/remote/sshclient.go | 19 ++++++-- 2 files changed, 62 insertions(+), 5 deletions(-) diff --git a/pkg/remote/conncontroller/conncontroller.go b/pkg/remote/conncontroller/conncontroller.go index 32d2d02c5f..dfa4768c23 100644 --- a/pkg/remote/conncontroller/conncontroller.go +++ b/pkg/remote/conncontroller/conncontroller.go @@ -454,7 +454,39 @@ func (conn *SSHConn) Connect(ctx context.Context, connFlags *wshrpc.ConnKeywords } }) conn.FireConnChangeEvent() - return err + if err != nil { + return err + } + + // logic for saving connection and potential flags (we only save once a connection has been made successfully) + // at the moment, identity files is the only saved flag + var identityFiles []string + existingConfig := wconfig.ReadFullConfig() + existingConnection, ok := existingConfig.Connections[conn.GetName()] + if ok { + identityFiles = existingConnection.SshIdentityFile + } + if err != nil { + // i do not consider this a critical failure + log.Printf("config read error: unable to save connection %s: %v", conn.GetName(), err) + } + + meta := make(map[string]any) + if connFlags.SshIdentityFile != nil { + for _, identityFile := range connFlags.SshIdentityFile { + if utilfn.ContainsStr(identityFiles, identityFile) { + continue + } + identityFiles = append(identityFiles, connFlags.SshIdentityFile...) + } + meta["ssh:identityfile"] = identityFiles + } + err = wconfig.SetConnectionsConfigValue(conn.GetName(), meta) + if err != nil { + // i do not consider this a critical failure + log.Printf("config write error: unable to save connection %s: %v", conn.GetName(), err) + } + return nil } func (conn *SSHConn) WithLock(fn func()) { @@ -661,6 +693,9 @@ func GetConnectionsList() ([]string, error) { hasConnected = append(hasConnected, stat.Connection) } } + + fromInternal := GetConnectionsFromInternalConfig() + fromConfig, err := GetConnectionsFromConfig() if err != nil { return nil, err @@ -670,7 +705,7 @@ func GetConnectionsList() ([]string, error) { alreadyUsed := make(map[string]struct{}) var connList []string - for _, subList := range [][]string{currentlyRunning, hasConnected, fromConfig} { + for _, subList := range [][]string{currentlyRunning, hasConnected, fromInternal, fromConfig} { for _, pattern := range subList { if _, used := alreadyUsed[pattern]; !used { connList = append(connList, pattern) @@ -682,6 +717,15 @@ func GetConnectionsList() ([]string, error) { return connList, nil } +func GetConnectionsFromInternalConfig() []string { + var internalNames []string + config := wconfig.ReadFullConfig() + for internalName := range config.Connections { + internalNames = append(internalNames, internalName) + } + return internalNames +} + func GetConnectionsFromConfig() ([]string, error) { home := wavebase.GetHomeDir() localConfig := filepath.Join(home, ".ssh", "config") diff --git a/pkg/remote/sshclient.go b/pkg/remote/sshclient.go index 134873073a..bcc4291c25 100644 --- a/pkg/remote/sshclient.go +++ b/pkg/remote/sshclient.go @@ -27,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/wconfig" "github.com/wavetermdev/waveterm/pkg/wshrpc" "golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh/agent" @@ -649,7 +650,13 @@ func ConnectToClient(connCtx context.Context, opts *SSHOpts, currentClient *ssh. connFlags.SshHostName = opts.SSHHost connFlags.SshPort = fmt.Sprintf("%d", opts.SSHPort) - sshKeywords, err := combineSshKeywords(connFlags, sshConfigKeywords) + rawName := opts.String() + savedKeywords, ok := wconfig.ReadFullConfig().Connections[rawName] + if !ok { + savedKeywords = wshrpc.ConnKeywords{} + } + + sshKeywords, err := combineSshKeywords(connFlags, sshConfigKeywords, &savedKeywords) if err != nil { return nil, debugInfo.JumpNum, ConnectionError{ConnectionDebugInfo: debugInfo, Err: err} } @@ -685,7 +692,7 @@ func ConnectToClient(connCtx context.Context, opts *SSHOpts, currentClient *ssh. return client, debugInfo.JumpNum, nil } -func combineSshKeywords(userProvidedOpts *wshrpc.ConnKeywords, configKeywords *wshrpc.ConnKeywords) (*wshrpc.ConnKeywords, error) { +func combineSshKeywords(userProvidedOpts *wshrpc.ConnKeywords, configKeywords *wshrpc.ConnKeywords, savedKeywords *wshrpc.ConnKeywords) (*wshrpc.ConnKeywords, error) { sshKeywords := &wshrpc.ConnKeywords{} if userProvidedOpts.SshUser != "" { @@ -716,7 +723,13 @@ func combineSshKeywords(userProvidedOpts *wshrpc.ConnKeywords, configKeywords *w sshKeywords.SshPort = "22" } - sshKeywords.SshIdentityFile = append(userProvidedOpts.SshIdentityFile, configKeywords.SshIdentityFile...) + // use internal config ones + if savedKeywords != nil { + sshKeywords.SshIdentityFile = append(sshKeywords.SshIdentityFile, savedKeywords.SshIdentityFile...) + } + + sshKeywords.SshIdentityFile = append(sshKeywords.SshIdentityFile, userProvidedOpts.SshIdentityFile...) + sshKeywords.SshIdentityFile = append(sshKeywords.SshIdentityFile, configKeywords.SshIdentityFile...) // these are not officially supported in the waveterm frontend but can be configured // in ssh config files From 3cbe6e930250b10eae3fdd200a5b69f75d291dbf Mon Sep 17 00:00:00 2001 From: Sylvia Crowe Date: Wed, 4 Dec 2024 12:12:56 -0800 Subject: [PATCH 05/14] feat: add an edit connections button --- frontend/app/block/blockframe.tsx | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/frontend/app/block/blockframe.tsx b/frontend/app/block/blockframe.tsx index 94141689c2..6187ac51fc 100644 --- a/frontend/app/block/blockframe.tsx +++ b/frontend/app/block/blockframe.tsx @@ -16,6 +16,8 @@ import { TypeAheadModal } from "@/app/modals/typeaheadmodal"; import { ContextMenuModel } from "@/app/store/contextmenu"; import { atoms, + createBlock, + getApi, getBlockComponentModel, getConnStatusAtom, getHostName, @@ -738,9 +740,28 @@ const ChangeConnectionBlockModal = React.memo( }; return item; }); + const connectionsEditItem: SuggestionConnectionItem = { + status: "disconnected", + icon: "gear", + iconColor: "var(--grey-text-color", + value: "Edit Connections", + label: "Edit Connections", + onSelect: () => { + util.fireAndForget(async () => { + const path = `${getApi().getConfigDir()}/connections.json`; + const blockDef: BlockDef = { + meta: { + view: "preview", + file: path, + }, + }; + await createBlock(blockDef, false, true); + }); + }, + }; const remoteSuggestions: SuggestionConnectionScope = { headerText: "Remote", - items: remoteItems, + items: [...remoteItems, connectionsEditItem], }; let suggestions: Array = []; From e8240c7f5be0625e95523ff994e31ff756d5d2e0 Mon Sep 17 00:00:00 2001 From: Sylvia Crowe Date: Wed, 4 Dec 2024 15:49:24 -0800 Subject: [PATCH 06/14] feat: parse connections in override config atom --- frontend/app/store/global.ts | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/frontend/app/store/global.ts b/frontend/app/store/global.ts index 3f931266f9..9f1a9a38d3 100644 --- a/frontend/app/store/global.ts +++ b/frontend/app/store/global.ts @@ -244,6 +244,21 @@ function useBlockMetaKeyAtom(blockId: string, key: T): return useAtomValue(getBlockMetaKeyAtom(blockId, key)); } +function getConnConfigKeyAtom(connName: string, key: T): Atom { + let connCache = getSingleConnAtomCache(connName); + const keyAtomName = "#conn-" + key; + let keyAtom = connCache.get(keyAtomName); + if (keyAtom != null) { + return keyAtom; + } + keyAtom = atom((get) => { + let fullConfig = get(atoms.fullConfigAtom); + return fullConfig.connections[connName]?.[key]; + }); + connCache.set(keyAtomName, keyAtom); + return keyAtom; +} + const settingsAtomCache = new Map>(); function getOverrideConfigAtom(blockId: string, key: T): Atom { @@ -259,6 +274,13 @@ function getOverrideConfigAtom(blockId: string, ke if (metaKeyVal != null) { return metaKeyVal; } + const connNameAtom = getBlockMetaKeyAtom(blockId, "connection"); + const connName = get(connNameAtom); + const connConfigKeyAtom = getConnConfigKeyAtom(connName, key as any); + const connConfigKeyVal = get(connConfigKeyAtom); + if (connConfigKeyVal != null) { + return connConfigKeyVal; + } const settingsKeyAtom = getSettingsKeyAtom(key); const settingsVal = get(settingsKeyAtom); if (settingsVal != null) { @@ -320,6 +342,15 @@ function getSingleBlockAtomCache(blockId: string): Map> { return blockCache; } +function getSingleConnAtomCache(connName: string): Map> { + let blockCache = blockAtomCache.get(connName); + if (blockCache == null) { + blockCache = new Map>(); + blockAtomCache.set(connName, blockCache); + } + return blockCache; +} + function useBlockAtom(blockId: string, name: string, makeFn: () => Atom): Atom { const blockCache = getSingleBlockAtomCache(blockId); let atom = blockCache.get(name); From 161a1aaecbeca9abc8e35eef58f81cc2c4188860 Mon Sep 17 00:00:00 2001 From: Sylvia Crowe Date: Wed, 4 Dec 2024 16:10:10 -0800 Subject: [PATCH 07/14] fix: remove unnecessary default connections --- pkg/wconfig/defaultconfig/connections.json | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 pkg/wconfig/defaultconfig/connections.json diff --git a/pkg/wconfig/defaultconfig/connections.json b/pkg/wconfig/defaultconfig/connections.json deleted file mode 100644 index 5165dbae86..0000000000 --- a/pkg/wconfig/defaultconfig/connections.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "askbeforewshinstall": true -} From 26069bb30d054bea1a55668c530d5764060faa49 Mon Sep 17 00:00:00 2001 From: Sylvia Crowe Date: Wed, 4 Dec 2024 16:32:34 -0800 Subject: [PATCH 08/14] fix: revert term.tsx changes --- frontend/app/view/term/term.tsx | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/frontend/app/view/term/term.tsx b/frontend/app/view/term/term.tsx index fd2634f684..d4236c92df 100644 --- a/frontend/app/view/term/term.tsx +++ b/frontend/app/view/term/term.tsx @@ -718,21 +718,17 @@ const TerminalView = ({ blockId, model }: TerminalViewProps) => { const [blockData] = WOS.useWaveObjectValue(WOS.makeORef("block", blockId)); const termSettingsAtom = useSettingsPrefixAtom("term"); const termSettings = jotai.useAtomValue(termSettingsAtom); - const fullConfig = jotai.useAtomValue(atoms.fullConfigAtom); - const connName = blockData.meta?.connection; - const connConfig = fullConfig.connections; - const globalFontSize = jotai.useAtomValue(model.fontSizeAtom); - const globalTermThemeName = jotai.useAtomValue(model.termThemeNameAtom); - const termFontSize = connConfig[connName]?.["term:fontsize"] ?? globalFontSize; - const termThemeName = connConfig[connName]?.["term:theme"] ?? globalTermThemeName; let termMode = blockData?.meta?.["term:mode"] ?? "term"; if (termMode != "term" && termMode != "vdom") { termMode = "term"; } const termModeRef = React.useRef(termMode); + const termFontSize = jotai.useAtomValue(model.fontSizeAtom); + React.useEffect(() => { - const termFontFamily = connConfig[connName]?.["term:fontfamily"] ?? termSettings?.["term:fontfamily"] ?? "Hack"; + const fullConfig = globalStore.get(atoms.fullConfigAtom); + const termThemeName = globalStore.get(model.termThemeNameAtom); const [termTheme, _] = computeTheme(fullConfig, termThemeName); let termScrollback = 1000; if (termSettings?.["term:scrollback"]) { @@ -754,7 +750,7 @@ const TerminalView = ({ blockId, model }: TerminalViewProps) => { { theme: termTheme, fontSize: termFontSize, - fontFamily: termFontFamily, + fontFamily: termSettings?.["term:fontfamily"] ?? "Hack", drawBoldTextInBrightColors: false, fontWeight: "normal", fontWeightBold: "bold", @@ -788,7 +784,7 @@ const TerminalView = ({ blockId, model }: TerminalViewProps) => { termWrap.dispose(); rszObs.disconnect(); }; - }, [blockId, termSettings, connName, fullConfig, termFontSize, termThemeName]); + }, [blockId, termSettings, termFontSize]); React.useEffect(() => { if (termModeRef.current == "vdom" && termMode == "term") { From 87dc03ac1bee504f5beb560a7c0a22b653b81ebe Mon Sep 17 00:00:00 2001 From: Sylvia Crowe Date: Wed, 4 Dec 2024 17:51:44 -0800 Subject: [PATCH 09/14] fix: reintegrate connection themes --- frontend/app/view/term/term.tsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/frontend/app/view/term/term.tsx b/frontend/app/view/term/term.tsx index d4236c92df..c383bd5d08 100644 --- a/frontend/app/view/term/term.tsx +++ b/frontend/app/view/term/term.tsx @@ -221,7 +221,10 @@ class TermViewModel { const blockData = get(this.blockAtom); const fsSettingsAtom = getSettingsKeyAtom("term:fontsize"); const settingsFontSize = get(fsSettingsAtom); - const rtnFontSize = blockData?.meta?.["term:fontsize"] ?? settingsFontSize ?? 12; + const connName = blockData?.meta?.connection; + const fullConfig = get(atoms.fullConfigAtom); + const connFontSize = fullConfig?.connections?.[connName]?.["term:fontsize"]; + const rtnFontSize = blockData?.meta?.["term:fontsize"] ?? connFontSize ?? settingsFontSize ?? 12; if (typeof rtnFontSize != "number" || isNaN(rtnFontSize) || rtnFontSize < 4 || rtnFontSize > 64) { return 12; } @@ -725,6 +728,8 @@ const TerminalView = ({ blockId, model }: TerminalViewProps) => { const termModeRef = React.useRef(termMode); const termFontSize = jotai.useAtomValue(model.fontSizeAtom); + const fullConfig = globalStore.get(atoms.fullConfigAtom); + const connFontFamily = fullConfig.connections?.[blockData?.meta?.connection]?.["term:fontfamily"]; React.useEffect(() => { const fullConfig = globalStore.get(atoms.fullConfigAtom); @@ -750,7 +755,7 @@ const TerminalView = ({ blockId, model }: TerminalViewProps) => { { theme: termTheme, fontSize: termFontSize, - fontFamily: termSettings?.["term:fontfamily"] ?? "Hack", + fontFamily: termSettings?.["term:fontfamily"] ?? connFontFamily ?? "Hack", drawBoldTextInBrightColors: false, fontWeight: "normal", fontWeightBold: "bold", @@ -784,7 +789,7 @@ const TerminalView = ({ blockId, model }: TerminalViewProps) => { termWrap.dispose(); rszObs.disconnect(); }; - }, [blockId, termSettings, termFontSize]); + }, [blockId, termSettings, termFontSize, connFontFamily]); React.useEffect(() => { if (termModeRef.current == "vdom" && termMode == "term") { From de432458ba8216ee3670f17cc945e1e85546c81b Mon Sep 17 00:00:00 2001 From: Sylvia Crowe Date: Wed, 4 Dec 2024 17:54:34 -0800 Subject: [PATCH 10/14] fix: close dropdown for edit connections option --- frontend/app/block/blockframe.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/app/block/blockframe.tsx b/frontend/app/block/blockframe.tsx index 905646bfd3..f082375d12 100644 --- a/frontend/app/block/blockframe.tsx +++ b/frontend/app/block/blockframe.tsx @@ -746,6 +746,7 @@ const ChangeConnectionBlockModal = React.memo( label: "Edit Connections", onSelect: () => { util.fireAndForget(async () => { + globalStore.set(changeConnModalAtom, false); const path = `${getApi().getConfigDir()}/connections.json`; const blockDef: BlockDef = { meta: { From bf8076a1165ff9a796c1e32d50cd872478569be0 Mon Sep 17 00:00:00 2001 From: Sylvia Crowe Date: Wed, 4 Dec 2024 18:18:50 -0800 Subject: [PATCH 11/14] feat: add nowsh icon --- frontend/app/block/blockframe.tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/frontend/app/block/blockframe.tsx b/frontend/app/block/blockframe.tsx index f082375d12..ef49947fcb 100644 --- a/frontend/app/block/blockframe.tsx +++ b/frontend/app/block/blockframe.tsx @@ -184,6 +184,9 @@ const BlockFrame_Header = ({ const prevMagifiedState = React.useRef(magnified); const manageConnection = util.useAtomValueSafe(viewModel?.manageConnection); const dragHandleRef = preview ? null : nodeModel.dragHandleRef; + const connName = blockData?.meta?.connection; + const allSettings = jotai.useAtomValue(atoms.fullConfigAtom); + const wshEnabled = allSettings?.connections?.[connName]?.wshenabled ?? true; React.useEffect(() => { if (!magnified || preview || prevMagifiedState.current) { @@ -241,6 +244,11 @@ const BlockFrame_Header = ({ ); } + const wshInstallButton: IconButtonDecl = { + elemtype: "iconbutton", + icon: "link-slash", + title: "wsh is not installed for this connection", + }; return (
@@ -258,6 +266,9 @@ const BlockFrame_Header = ({ changeConnModalAtom={changeConnModalAtom} /> )} + {manageConnection && !wshEnabled && ( + + )}
{headerTextElems}
{endIconsElem}
From 7a42ad1970b7007243de71b0bfe3fb92ffb58432 Mon Sep 17 00:00:00 2001 From: Sylvia Crowe Date: Wed, 4 Dec 2024 18:36:45 -0800 Subject: [PATCH 12/14] refactor: add prefixes to new connection config --- frontend/app/block/blockframe.tsx | 6 +++--- frontend/types/gotypes.d.ts | 6 +++--- pkg/remote/conncontroller/conncontroller.go | 10 +++++----- pkg/wshrpc/wshrpctypes.go | 7 ++++--- 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/frontend/app/block/blockframe.tsx b/frontend/app/block/blockframe.tsx index ef49947fcb..da237d25f5 100644 --- a/frontend/app/block/blockframe.tsx +++ b/frontend/app/block/blockframe.tsx @@ -186,7 +186,7 @@ const BlockFrame_Header = ({ const dragHandleRef = preview ? null : nodeModel.dragHandleRef; const connName = blockData?.meta?.connection; const allSettings = jotai.useAtomValue(atoms.fullConfigAtom); - const wshEnabled = allSettings?.connections?.[connName]?.wshenabled ?? true; + const wshEnabled = allSettings?.connections?.[connName]?.["conn:wshenabled"] ?? true; React.useEffect(() => { if (!magnified || preview || prevMagifiedState.current) { @@ -653,7 +653,7 @@ const ChangeConnectionBlockModal = React.memo( if (conn === connSelected) { createNew = false; } - if (conn.includes(connSelected) && connectionsConfig[conn]?.hidden != true) { + if (conn.includes(connSelected) && connectionsConfig[conn]?.["display:hidden"] != true) { filteredList.push(conn); } } @@ -662,7 +662,7 @@ const ChangeConnectionBlockModal = React.memo( if (conn === connSelected) { createNew = false; } - if (conn.includes(connSelected) && connectionsConfig[conn]?.hidden != true) { + if (conn.includes(connSelected) && connectionsConfig[conn]?.["display:hidden"] != true) { filteredWslList.push(conn); } } diff --git a/frontend/types/gotypes.d.ts b/frontend/types/gotypes.d.ts index 9e4669ee98..fdc58192b0 100644 --- a/frontend/types/gotypes.d.ts +++ b/frontend/types/gotypes.d.ts @@ -277,9 +277,9 @@ declare global { // wshrpc.ConnKeywords type ConnKeywords = { - wshenabled?: boolean; - askbeforewshinstall?: boolean; - hidden?: boolean; + "conn:wshenabled"?: boolean; + "conn:askbeforewshinstall"?: boolean; + "display:hidden"?: boolean; "term:*"?: boolean; "term:fontsize"?: number; "term:fontfamily"?: string; diff --git a/pkg/remote/conncontroller/conncontroller.go b/pkg/remote/conncontroller/conncontroller.go index dfa4768c23..182180f0c7 100644 --- a/pkg/remote/conncontroller/conncontroller.go +++ b/pkg/remote/conncontroller/conncontroller.go @@ -342,7 +342,7 @@ func (conn *SSHConn) CheckAndInstallWsh(ctx context.Context, clientDisplayName s } if !response.Confirm { meta := make(map[string]any) - meta["wshenabled"] = false + meta["conn:wshenabled"] = false err = wconfig.SetConnectionsConfigValue(conn.GetName(), meta) if err != nil { log.Printf("warning: error writing to connections file: %v", err) @@ -516,11 +516,11 @@ func (conn *SSHConn) connectInternal(ctx context.Context, connFlags *wshrpc.Conn askBeforeInstall := config.Settings.ConnAskBeforeWshInstall connSettings, ok := config.Connections[conn.GetName()] if ok { - if connSettings.WshEnabled != nil { - enableWsh = *connSettings.WshEnabled + if connSettings.ConnWshEnabled != nil { + enableWsh = *connSettings.ConnWshEnabled } - if connSettings.AskBeforeWshInstall != nil { - askBeforeInstall = *connSettings.AskBeforeWshInstall + if connSettings.ConnAskBeforeWshInstall != nil { + askBeforeInstall = *connSettings.ConnAskBeforeWshInstall } } if enableWsh { diff --git a/pkg/wshrpc/wshrpctypes.go b/pkg/wshrpc/wshrpctypes.go index 78df0313c1..da99535bf7 100644 --- a/pkg/wshrpc/wshrpctypes.go +++ b/pkg/wshrpc/wshrpctypes.go @@ -452,9 +452,10 @@ type CommandRemoteWriteFileData struct { } type ConnKeywords struct { - WshEnabled *bool `json:"wshenabled,omitempty"` - AskBeforeWshInstall *bool `json:"askbeforewshinstall,omitempty"` - Hidden *bool `json:"hidden,omitempty"` + ConnWshEnabled *bool `json:"conn:wshenabled,omitempty"` + ConnAskBeforeWshInstall *bool `json:"conn:askbeforewshinstall,omitempty"` + + Hidden *bool `json:"display:hidden,omitempty"` TermClear bool `json:"term:*,omitempty"` TermFontSize float64 `json:"term:fontsize,omitempty"` From c0a4eff2e11e3f05972cd0fe5efb904fe0a96cee Mon Sep 17 00:00:00 2001 From: Sylvia Crowe Date: Wed, 4 Dec 2024 19:19:55 -0800 Subject: [PATCH 13/14] feat: sometimes filter out connections without wsh --- frontend/app/block/blockframe.tsx | 16 ++++++++++++++-- frontend/app/view/preview/preview.tsx | 2 ++ frontend/app/view/sysinfo/sysinfo.tsx | 4 +++- frontend/app/view/term/term.tsx | 4 +++- frontend/types/custom.d.ts | 1 + 5 files changed, 23 insertions(+), 4 deletions(-) diff --git a/frontend/app/block/blockframe.tsx b/frontend/app/block/blockframe.tsx index da237d25f5..04d9723bb1 100644 --- a/frontend/app/block/blockframe.tsx +++ b/frontend/app/block/blockframe.tsx @@ -583,6 +583,8 @@ const ChangeConnectionBlockModal = React.memo( const connStatusMap = new Map(); const fullConfig = jotai.useAtomValue(atoms.fullConfigAtom); const connectionsConfig = fullConfig.connections; + let filterOutNowsh = util.useAtomValueSafe(viewModel.filterOutNowsh) || true; + let maxActiveConnNum = 1; for (const conn of allConnStatus) { if (conn.activeconnnum > maxActiveConnNum) { @@ -653,7 +655,12 @@ const ChangeConnectionBlockModal = React.memo( if (conn === connSelected) { createNew = false; } - if (conn.includes(connSelected) && connectionsConfig[conn]?.["display:hidden"] != true) { + if ( + conn.includes(connSelected) && + connectionsConfig[conn]?.["display:hidden"] != true && + (connectionsConfig[conn]?.["conn:wshenabled"] != false || !filterOutNowsh) + // != false is necessary because of defaults + ) { filteredList.push(conn); } } @@ -662,7 +669,12 @@ const ChangeConnectionBlockModal = React.memo( if (conn === connSelected) { createNew = false; } - if (conn.includes(connSelected) && connectionsConfig[conn]?.["display:hidden"] != true) { + if ( + conn.includes(connSelected) && + connectionsConfig[conn]?.["display:hidden"] != true && + (connectionsConfig[conn]?.["conn:wshenabled"] != false || !filterOutNowsh) + // != false is necessary because of defaults + ) { filteredWslList.push(conn); } } diff --git a/frontend/app/view/preview/preview.tsx b/frontend/app/view/preview/preview.tsx index 3cb55316c2..77ebd58984 100644 --- a/frontend/app/view/preview/preview.tsx +++ b/frontend/app/view/preview/preview.tsx @@ -121,6 +121,7 @@ export class PreviewModel implements ViewModel { loadableSpecializedView: Atom>; manageConnection: Atom; connStatus: Atom; + filterOutNowsh?: Atom; metaFilePath: Atom; statFilePath: Atom>; @@ -164,6 +165,7 @@ export class PreviewModel implements ViewModel { this.manageConnection = atom(true); this.blockAtom = WOS.getWaveObjectAtom(`block:${blockId}`); this.markdownShowToc = atom(false); + this.filterOutNowsh = atom(true); this.monacoRef = createRef(); this.viewIcon = atom((get) => { const blockData = get(this.blockAtom); diff --git a/frontend/app/view/sysinfo/sysinfo.tsx b/frontend/app/view/sysinfo/sysinfo.tsx index 787be2ff07..ec2ba93be8 100644 --- a/frontend/app/view/sysinfo/sysinfo.tsx +++ b/frontend/app/view/sysinfo/sysinfo.tsx @@ -91,7 +91,7 @@ function convertWaveEventToDataItem(event: WaveEvent): DataItem { return dataItem; } -class SysinfoViewModel { +class SysinfoViewModel implements ViewModel { viewType: string; blockAtom: jotai.Atom; termMode: jotai.Atom; @@ -109,6 +109,7 @@ class SysinfoViewModel { metrics: jotai.Atom; connection: jotai.Atom; manageConnection: jotai.Atom; + filterOutNowsh: jotai.Atom; connStatus: jotai.Atom; plotMetaAtom: jotai.PrimitiveAtom>; endIconButtons: jotai.Atom; @@ -176,6 +177,7 @@ class SysinfoViewModel { }); this.plotMetaAtom = jotai.atom(new Map(Object.entries(DefaultPlotMeta))); this.manageConnection = jotai.atom(true); + this.filterOutNowsh = jotai.atom(true); this.loadingAtom = jotai.atom(true); this.numPoints = jotai.atom((get) => { const blockData = get(this.blockAtom); diff --git a/frontend/app/view/term/term.tsx b/frontend/app/view/term/term.tsx index c383bd5d08..fb429f7b79 100644 --- a/frontend/app/view/term/term.tsx +++ b/frontend/app/view/term/term.tsx @@ -41,7 +41,7 @@ type InitialLoadDataType = { heldData: Uint8Array[]; }; -class TermViewModel { +class TermViewModel implements ViewModel { viewType: string; nodeModel: BlockNodeModel; connected: boolean; @@ -54,6 +54,7 @@ class TermViewModel { viewText: jotai.Atom; blockBg: jotai.Atom; manageConnection: jotai.Atom; + filterOutNowsh?: jotai.Atom; connStatus: jotai.Atom; termWshClient: TermWshClient; vdomBlockId: jotai.Atom; @@ -196,6 +197,7 @@ class TermViewModel { } return true; }); + this.filterOutNowsh = jotai.atom(false); this.termThemeNameAtom = useBlockAtom(blockId, "termthemeatom", () => { return jotai.atom((get) => { return get(getOverrideConfigAtom(this.blockId, "term:theme")) ?? DefaultTermTheme; diff --git a/frontend/types/custom.d.ts b/frontend/types/custom.d.ts index cf532fb866..49dd498030 100644 --- a/frontend/types/custom.d.ts +++ b/frontend/types/custom.d.ts @@ -237,6 +237,7 @@ declare global { blockBg?: jotai.Atom; manageConnection?: jotai.Atom; noPadding?: jotai.Atom; + filterOutNowsh?: jotai.Atom; onBack?: () => void; onForward?: () => void; From 69b612370e8388cbc0c15c41e5e1a4e705fbfe00 Mon Sep 17 00:00:00 2001 From: Sylvia Crowe Date: Wed, 4 Dec 2024 23:07:05 -0800 Subject: [PATCH 14/14] feat: add display order sorting of conn dropdown --- frontend/app/block/blockframe.tsx | 11 ++++++++++- frontend/types/gotypes.d.ts | 1 + pkg/wshrpc/wshrpctypes.go | 3 ++- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/frontend/app/block/blockframe.tsx b/frontend/app/block/blockframe.tsx index 04d9723bb1..843ae7fe93 100644 --- a/frontend/app/block/blockframe.tsx +++ b/frontend/app/block/blockframe.tsx @@ -781,9 +781,18 @@ const ChangeConnectionBlockModal = React.memo( }); }, }; + const sortedRemoteItems = remoteItems.sort( + (itemA: SuggestionConnectionItem, itemB: SuggestionConnectionItem) => { + const connNameA = itemA.value; + const connNameB = itemB.value; + const valueA = connectionsConfig[connNameA]?.["display:order"] ?? 0; + const valueB = connectionsConfig[connNameB]?.["display:order"] ?? 0; + return valueA - valueB; + } + ); const remoteSuggestions: SuggestionConnectionScope = { headerText: "Remote", - items: [...remoteItems, connectionsEditItem], + items: [...sortedRemoteItems, connectionsEditItem], }; let suggestions: Array = []; diff --git a/frontend/types/gotypes.d.ts b/frontend/types/gotypes.d.ts index fdc58192b0..f72be05b32 100644 --- a/frontend/types/gotypes.d.ts +++ b/frontend/types/gotypes.d.ts @@ -280,6 +280,7 @@ declare global { "conn:wshenabled"?: boolean; "conn:askbeforewshinstall"?: boolean; "display:hidden"?: boolean; + "display:order"?: number; "term:*"?: boolean; "term:fontsize"?: number; "term:fontfamily"?: string; diff --git a/pkg/wshrpc/wshrpctypes.go b/pkg/wshrpc/wshrpctypes.go index da99535bf7..61cddbd2da 100644 --- a/pkg/wshrpc/wshrpctypes.go +++ b/pkg/wshrpc/wshrpctypes.go @@ -455,7 +455,8 @@ type ConnKeywords struct { ConnWshEnabled *bool `json:"conn:wshenabled,omitempty"` ConnAskBeforeWshInstall *bool `json:"conn:askbeforewshinstall,omitempty"` - Hidden *bool `json:"display:hidden,omitempty"` + DisplayHidden *bool `json:"display:hidden,omitempty"` + DisplayOrder float32 `json:"display:order,omitempty"` TermClear bool `json:"term:*,omitempty"` TermFontSize float64 `json:"term:fontsize,omitempty"`