From b4b0222c9d6b03e0ca7f5eb3bad395f6f7d37d5b Mon Sep 17 00:00:00 2001 From: Sylvie Crowe <107814465+oneirocosm@users.noreply.github.com> Date: Thu, 5 Dec 2024 10:02:07 -0800 Subject: [PATCH 1/5] New Connections Configs (#1383) This adds the following connections changes: - connections can be hidden from the dropdown in our internal connections.json config - `wsh ssh` -i will write identity files to the internal connections.json config for that connection - the internal connections.json config will also be used to get identity files when connecting - the internal connections.json config allows setting theme, fontsize, and font for specific connections - successful connections (including those using wsh ssh) are saved to the internal connections.json config - the connections.json config will be used to help pre-populate the dropdown list - adds an item to the dropdown to edit the connections config in an ephemeral block --------- Co-authored-by: Evan Simkowitz --- frontend/app/block/blockframe.tsx | 62 ++++++++++++++++++++- frontend/app/store/global.ts | 31 +++++++++++ frontend/app/view/preview/preview.tsx | 2 + frontend/app/view/sysinfo/sysinfo.tsx | 4 +- frontend/app/view/term/term.tsx | 15 +++-- frontend/types/custom.d.ts | 1 + frontend/types/gotypes.d.ts | 10 +++- pkg/remote/conncontroller/conncontroller.go | 58 ++++++++++++++++--- pkg/remote/sshclient.go | 19 ++++++- pkg/wconfig/defaultconfig/connections.json | 3 - pkg/wshrpc/wshrpctypes.go | 12 +++- 11 files changed, 192 insertions(+), 25 deletions(-) delete mode 100644 pkg/wconfig/defaultconfig/connections.json diff --git a/frontend/app/block/blockframe.tsx b/frontend/app/block/blockframe.tsx index 949108f0c..843ae7fe9 100644 --- a/frontend/app/block/blockframe.tsx +++ b/frontend/app/block/blockframe.tsx @@ -15,6 +15,8 @@ import { TypeAheadModal } from "@/app/modals/typeaheadmodal"; import { ContextMenuModel } from "@/app/store/contextmenu"; import { atoms, + createBlock, + getApi, getBlockComponentModel, getConnStatusAtom, getHostName, @@ -182,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]?.["conn:wshenabled"] ?? true; React.useEffect(() => { if (!magnified || preview || prevMagifiedState.current) { @@ -239,6 +244,11 @@ const BlockFrame_Header = ({ ); } + const wshInstallButton: IconButtonDecl = { + elemtype: "iconbutton", + icon: "link-slash", + title: "wsh is not installed for this connection", + }; return (
@@ -256,6 +266,9 @@ const BlockFrame_Header = ({ changeConnModalAtom={changeConnModalAtom} /> )} + {manageConnection && !wshEnabled && ( + + )}
{headerTextElems}
{endIconsElem}
@@ -568,6 +581,10 @@ 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 filterOutNowsh = util.useAtomValueSafe(viewModel.filterOutNowsh) || true; + let maxActiveConnNum = 1; for (const conn of allConnStatus) { if (conn.activeconnnum > maxActiveConnNum) { @@ -638,7 +655,12 @@ const ChangeConnectionBlockModal = React.memo( if (conn === connSelected) { createNew = false; } - if (conn.includes(connSelected)) { + if ( + conn.includes(connSelected) && + connectionsConfig[conn]?.["display:hidden"] != true && + (connectionsConfig[conn]?.["conn:wshenabled"] != false || !filterOutNowsh) + // != false is necessary because of defaults + ) { filteredList.push(conn); } } @@ -647,7 +669,12 @@ const ChangeConnectionBlockModal = React.memo( if (conn === connSelected) { createNew = false; } - if (conn.includes(connSelected)) { + if ( + conn.includes(connSelected) && + connectionsConfig[conn]?.["display:hidden"] != true && + (connectionsConfig[conn]?.["conn:wshenabled"] != false || !filterOutNowsh) + // != false is necessary because of defaults + ) { filteredWslList.push(conn); } } @@ -734,9 +761,38 @@ 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 () => { + globalStore.set(changeConnModalAtom, false); + const path = `${getApi().getConfigDir()}/connections.json`; + const blockDef: BlockDef = { + meta: { + view: "preview", + file: path, + }, + }; + await createBlock(blockDef, false, true); + }); + }, + }; + 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, + items: [...sortedRemoteItems, connectionsEditItem], }; let suggestions: Array = []; diff --git a/frontend/app/store/global.ts b/frontend/app/store/global.ts index 74ee256e4..21791b391 100644 --- a/frontend/app/store/global.ts +++ b/frontend/app/store/global.ts @@ -246,6 +246,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 { @@ -261,6 +276,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) { @@ -322,6 +344,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); diff --git a/frontend/app/view/preview/preview.tsx b/frontend/app/view/preview/preview.tsx index 3cb55316c..77ebd5898 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 787be2ff0..ec2ba93be 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 d4236c92d..fb429f7b7 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; @@ -221,7 +223,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 +730,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 +757,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 +791,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") { diff --git a/frontend/types/custom.d.ts b/frontend/types/custom.d.ts index cf532fb86..49dd49803 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; diff --git a/frontend/types/gotypes.d.ts b/frontend/types/gotypes.d.ts index e059da2d5..f72be05b3 100644 --- a/frontend/types/gotypes.d.ts +++ b/frontend/types/gotypes.d.ts @@ -277,8 +277,14 @@ declare global { // wshrpc.ConnKeywords type ConnKeywords = { - wshenabled?: boolean; - askbeforewshinstall?: boolean; + "conn:wshenabled"?: boolean; + "conn:askbeforewshinstall"?: boolean; + "display:hidden"?: boolean; + "display:order"?: number; + "term:*"?: boolean; + "term:fontsize"?: number; + "term:fontfamily"?: string; + "term:theme"?: string; "ssh:user"?: string; "ssh:hostname"?: string; "ssh:port"?: string; diff --git a/pkg/remote/conncontroller/conncontroller.go b/pkg/remote/conncontroller/conncontroller.go index 32d2d02c5..182180f0c 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) @@ -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()) { @@ -484,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 { @@ -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 134873073..bcc4291c2 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 diff --git a/pkg/wconfig/defaultconfig/connections.json b/pkg/wconfig/defaultconfig/connections.json deleted file mode 100644 index 5165dbae8..000000000 --- a/pkg/wconfig/defaultconfig/connections.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "askbeforewshinstall": true -} diff --git a/pkg/wshrpc/wshrpctypes.go b/pkg/wshrpc/wshrpctypes.go index 5733c2a9e..61cddbd2d 100644 --- a/pkg/wshrpc/wshrpctypes.go +++ b/pkg/wshrpc/wshrpctypes.go @@ -452,8 +452,16 @@ type CommandRemoteWriteFileData struct { } type ConnKeywords struct { - WshEnabled *bool `json:"wshenabled,omitempty"` - AskBeforeWshInstall *bool `json:"askbeforewshinstall,omitempty"` + ConnWshEnabled *bool `json:"conn:wshenabled,omitempty"` + ConnAskBeforeWshInstall *bool `json:"conn:askbeforewshinstall,omitempty"` + + DisplayHidden *bool `json:"display:hidden,omitempty"` + DisplayOrder float32 `json:"display:order,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"` From 942eeaa8e9905e5f9cdcdd35a4449127ce861cc6 Mon Sep 17 00:00:00 2001 From: Mike Sawka Date: Thu, 5 Dec 2024 10:27:08 -0800 Subject: [PATCH 2/5] wsh setbg + updated tab background docs (#1394) --- cmd/wsh/cmd/csscolormap.go | 147 ++++++++++++++++++++++++++++ cmd/wsh/cmd/wshcmd-setbg.go | 186 ++++++++++++++++++++++++++++++++++++ docs/docs/customization.mdx | 35 +++++++ docs/docs/presets.mdx | 111 ++++++++++++++++++--- docs/docs/wsh.mdx | 68 +++++++++++++ 5 files changed, 536 insertions(+), 11 deletions(-) create mode 100644 cmd/wsh/cmd/csscolormap.go create mode 100644 cmd/wsh/cmd/wshcmd-setbg.go diff --git a/cmd/wsh/cmd/csscolormap.go b/cmd/wsh/cmd/csscolormap.go new file mode 100644 index 000000000..f2b52485e --- /dev/null +++ b/cmd/wsh/cmd/csscolormap.go @@ -0,0 +1,147 @@ +// Copyright 2024, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +package cmd + +var CssColorNames = map[string]bool{ + "aliceblue": true, + "antiquewhite": true, + "aqua": true, + "aquamarine": true, + "azure": true, + "beige": true, + "bisque": true, + "black": true, + "blanchedalmond": true, + "blue": true, + "blueviolet": true, + "brown": true, + "burlywood": true, + "cadetblue": true, + "chartreuse": true, + "chocolate": true, + "coral": true, + "cornflowerblue": true, + "cornsilk": true, + "crimson": true, + "cyan": true, + "darkblue": true, + "darkcyan": true, + "darkgoldenrod": true, + "darkgray": true, + "darkgreen": true, + "darkkhaki": true, + "darkmagenta": true, + "darkolivegreen": true, + "darkorange": true, + "darkorchid": true, + "darkred": true, + "darksalmon": true, + "darkseagreen": true, + "darkslateblue": true, + "darkslategray": true, + "darkturquoise": true, + "darkviolet": true, + "deeppink": true, + "deepskyblue": true, + "dimgray": true, + "dodgerblue": true, + "firebrick": true, + "floralwhite": true, + "forestgreen": true, + "fuchsia": true, + "gainsboro": true, + "ghostwhite": true, + "gold": true, + "goldenrod": true, + "gray": true, + "green": true, + "greenyellow": true, + "honeydew": true, + "hotpink": true, + "indianred": true, + "indigo": true, + "ivory": true, + "khaki": true, + "lavender": true, + "lavenderblush": true, + "lawngreen": true, + "lemonchiffon": true, + "lightblue": true, + "lightcoral": true, + "lightcyan": true, + "lightgoldenrodyellow": true, + "lightgray": true, + "lightgreen": true, + "lightpink": true, + "lightsalmon": true, + "lightseagreen": true, + "lightskyblue": true, + "lightslategray": true, + "lightsteelblue": true, + "lightyellow": true, + "lime": true, + "limegreen": true, + "linen": true, + "magenta": true, + "maroon": true, + "mediumaquamarine": true, + "mediumblue": true, + "mediumorchid": true, + "mediumpurple": true, + "mediumseagreen": true, + "mediumslateblue": true, + "mediumspringgreen": true, + "mediumturquoise": true, + "mediumvioletred": true, + "midnightblue": true, + "mintcream": true, + "mistyrose": true, + "moccasin": true, + "navajowhite": true, + "navy": true, + "oldlace": true, + "olive": true, + "olivedrab": true, + "orange": true, + "orangered": true, + "orchid": true, + "palegoldenrod": true, + "palegreen": true, + "paleturquoise": true, + "palevioletred": true, + "papayawhip": true, + "peachpuff": true, + "peru": true, + "pink": true, + "plum": true, + "powderblue": true, + "purple": true, + "red": true, + "rosybrown": true, + "royalblue": true, + "saddlebrown": true, + "salmon": true, + "sandybrown": true, + "seagreen": true, + "seashell": true, + "sienna": true, + "silver": true, + "skyblue": true, + "slateblue": true, + "slategray": true, + "snow": true, + "springgreen": true, + "steelblue": true, + "tan": true, + "teal": true, + "thistle": true, + "tomato": true, + "turquoise": true, + "violet": true, + "wheat": true, + "white": true, + "whitesmoke": true, + "yellow": true, + "yellowgreen": true, +} diff --git a/cmd/wsh/cmd/wshcmd-setbg.go b/cmd/wsh/cmd/wshcmd-setbg.go new file mode 100644 index 000000000..b40e2fcf8 --- /dev/null +++ b/cmd/wsh/cmd/wshcmd-setbg.go @@ -0,0 +1,186 @@ +// Copyright 2024, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +package cmd + +import ( + "encoding/hex" + "encoding/json" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/spf13/cobra" + "github.com/wavetermdev/waveterm/pkg/util/utilfn" + "github.com/wavetermdev/waveterm/pkg/wavebase" + "github.com/wavetermdev/waveterm/pkg/wshrpc" + "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient" +) + +var setBgCmd = &cobra.Command{ + Use: "setbg [--opacity value] [--tile|--center] [--scale value] (image-path|\"#color\"|color-name)", + Short: "set background image or color for a tab", + Long: `Set a background image or color for a tab. Colors can be specified as: + - A quoted hex value like "#ff0000" (quotes required to prevent # being interpreted as a shell comment) + - A CSS color name like "blue" or "forestgreen" +Or provide a path to a supported image file (jpg, png, gif, webp, or svg). + +You can also: + - Use --clear to remove the background + - Use --opacity without other arguments to change just the opacity + - Use --center for centered images without scaling (good for logos) + - Use --scale with --center to control image size + - Use --print to see the metadata without applying it`, + RunE: setBgRun, + PreRunE: preRunSetupRpcClient, +} + +var ( + setBgOpacity float64 + setBgTile bool + setBgCenter bool + setBgSize string + setBgClear bool + setBgPrint bool +) + +func init() { + rootCmd.AddCommand(setBgCmd) + setBgCmd.Flags().Float64Var(&setBgOpacity, "opacity", 0.5, "background opacity (0.0-1.0)") + setBgCmd.Flags().BoolVar(&setBgTile, "tile", false, "tile the background image") + setBgCmd.Flags().BoolVar(&setBgCenter, "center", false, "center the image without scaling") + setBgCmd.Flags().StringVar(&setBgSize, "size", "auto", "size for centered images (px, %, or auto)") + setBgCmd.Flags().BoolVar(&setBgClear, "clear", false, "clear the background") + setBgCmd.Flags().BoolVar(&setBgPrint, "print", false, "print the metadata without applying it") + + // Make tile and center mutually exclusive + setBgCmd.MarkFlagsMutuallyExclusive("tile", "center") +} + +func validateHexColor(color string) error { + if !strings.HasPrefix(color, "#") { + return fmt.Errorf("color must start with #") + } + colorHex := color[1:] + if len(colorHex) != 6 && len(colorHex) != 8 { + return fmt.Errorf("color must be in #RRGGBB or #RRGGBBAA format") + } + _, err := hex.DecodeString(colorHex) + if err != nil { + return fmt.Errorf("invalid hex color: %v", err) + } + return nil +} + +func setBgRun(cmd *cobra.Command, args []string) (rtnErr error) { + defer func() { + sendActivity("setbg", rtnErr == nil) + }() + + // Create base metadata + meta := map[string]interface{}{} + + // Handle opacity-only change or clear + if len(args) == 0 { + if !cmd.Flags().Changed("opacity") && !setBgClear { + OutputHelpMessage(cmd) + return fmt.Errorf("setbg requires an image path or color value") + } + if setBgOpacity < 0 || setBgOpacity > 1 { + return fmt.Errorf("opacity must be between 0.0 and 1.0") + } + if cmd.Flags().Changed("opacity") { + meta["bg:opacity"] = setBgOpacity + } + } else if len(args) > 1 { + OutputHelpMessage(cmd) + return fmt.Errorf("too many arguments") + } else { + // Handle background setting + meta["bg:*"] = true + if setBgOpacity < 0 || setBgOpacity > 1 { + return fmt.Errorf("opacity must be between 0.0 and 1.0") + } + meta["bg:opacity"] = setBgOpacity + + input := args[0] + var bgStyle string + + // Check for hex color + if strings.HasPrefix(input, "#") { + if err := validateHexColor(input); err != nil { + return err + } + bgStyle = input + } else if CssColorNames[strings.ToLower(input)] { + // Handle CSS color name + bgStyle = strings.ToLower(input) + } else { + // Handle image input + absPath, err := filepath.Abs(wavebase.ExpandHomeDirSafe(input)) + if err != nil { + return fmt.Errorf("resolving image path: %v", err) + } + + fileInfo, err := os.Stat(absPath) + if err != nil { + return fmt.Errorf("cannot access image file: %v", err) + } + if fileInfo.IsDir() { + return fmt.Errorf("path is a directory, not an image file") + } + + mimeType := utilfn.DetectMimeType(absPath, fileInfo, true) + switch mimeType { + case "image/jpeg", "image/png", "image/gif", "image/webp", "image/svg+xml": + // Valid image type + default: + return fmt.Errorf("file does not appear to be a valid image (detected type: %s)", mimeType) + } + + // Create URL-safe path + escapedPath := strings.ReplaceAll(absPath, "'", "\\'") + bgStyle = fmt.Sprintf("url('%s')", escapedPath) + + switch { + case setBgTile: + bgStyle += " repeat" + case setBgCenter: + bgStyle += fmt.Sprintf(" no-repeat center/%s", setBgSize) + default: + bgStyle += " center/cover no-repeat" + } + } + + meta["bg"] = bgStyle + } + + if setBgPrint { + jsonBytes, err := json.MarshalIndent(meta, "", " ") + if err != nil { + return fmt.Errorf("error formatting metadata: %v", err) + } + WriteStdout(string(jsonBytes) + "\n") + return nil + } + + // Resolve tab reference + oRef, err := resolveSimpleId("tab") + if err != nil { + return err + } + + // Send RPC request + setMetaWshCmd := wshrpc.CommandSetMetaData{ + ORef: *oRef, + Meta: meta, + } + err = wshclient.SetMetaCommand(RpcClient, setMetaWshCmd, &wshrpc.RpcOpts{Timeout: 2000}) + if err != nil { + return fmt.Errorf("setting background: %v", err) + } + + WriteStdout("background set\n") + return nil +} diff --git a/docs/docs/customization.mdx b/docs/docs/customization.mdx index ad5f93603..ff3d30803 100644 --- a/docs/docs/customization.mdx +++ b/docs/docs/customization.mdx @@ -65,6 +65,41 @@ You can also suppress the help widgets in the bottom right by setting the config
+## Tab Backgrounds + +Wave supports powerful custom backgrounds for your tabs using images, patterns, gradients, and colors. The quickest way to set an image background is using the `wsh setbg` command: + +```bash +# Set an image background with 50% opacity (default) +wsh setbg ~/pictures/background.jpg + +# Set a color background (use quotes to prevent # being interpreted as a shell comment) +wsh setbg "#ff0000" # hex color +wsh setbg forestgreen # CSS color name + +# Adjust opacity +wsh setbg --opacity 0.3 ~/pictures/light-pattern.png +wsh setbg --opacity 0.7 # change only opacity of current background + +# Image positioning options +wsh setbg --tile ~/pictures/texture.png # create tiled pattern +wsh setbg --center ~/pictures/logo.png # center without scaling +wsh setbg --center --size 200px ~/pictures/logo.png # center with specific size (px, %, auto) + +# Remove background +wsh setbg --clear +``` + +You can use any JPEG, PNG, GIF, WebP, or SVG image as your background. The `--center` option is particularly useful for logos or icons where you want to maintain the original size. + +To preview the metadata for any background without applying it, use the `--print` flag: + +```bash +wsh setbg --print "#ff0000" +``` + +For more advanced customization options including gradients, colors, and saving your own background presets, check out our [Background Configuration](/presets#background-configurations) documentation. + ## Presets For more advanced customization, to set up multiple AI models, and your own tab backgrounds, check out our [Presets Documentation](./presets). diff --git a/docs/docs/presets.mdx b/docs/docs/presets.mdx index 6b2dd5611..31b15115e 100644 --- a/docs/docs/presets.mdx +++ b/docs/docs/presets.mdx @@ -18,7 +18,7 @@ wsh editconfig presets.json ::: -### File format +## File format Presets follow the following format: @@ -61,7 +61,7 @@ A complete example of a preset for an AI model is the following: } ``` -### Preset type +## Preset Types The type of the preset determines where it can be discovered in the app. Currently, the two types that will be discovered in the app are `bg` and `ai`. @@ -90,7 +90,7 @@ Configs in a preset are applied in order to override the default config values, ::: -#### AI configurations +## AI Configurations | Key Name | Type | Function | | ------------- | ------ | -------------------------------------------------------------------------------------------------- | @@ -106,15 +106,104 @@ Configs in a preset are applied in order to override the default config values, | ai:maxtokens | int | max tokens to pass to API | | ai:timeoutms | int | timeout (in milliseconds) for AI calls | -#### Background configurations + -| Key Name | Type | Function | -| -------------------- | ------ | ----------------------------------------------------------------------------------------------- | -| bg:\* | bool | reset all existing bg keys | -| bg:opacity | float | the opacity of the background | -| bg:blendmode | string | the [blend mode](https://developer.mozilla.org/en-US/docs/Web/CSS/blend-mode) of the background | -| bg:bordercolor | string | the color of the border | -| bg:activebordercolor | string | the color of the border when a block is active | +## Background Configurations + +Wave's background system harnesses the full power of CSS backgrounds, letting you create rich visual effects through the "background" attribute. You can apply solid colors, gradients (both linear and radial), images, and even blend multiple elements together. The system offers preset configurations while maintaining the flexibility of pure CSS, making it both powerful and easy to use. + +| Key Name | Type | Function | +| -------------------- | ------ | ------------------------------------------------------------------------------------------------------- | +| bg:\* | bool | reset all existing bg keys (recommended to prevent any existing background settings from carrying over) | +| bg | string | CSS `background` attribute for the tab (supports colors, gradients images, etc.) | +| bg:opacity | float | the opacity of the background (defaults to 0.5) | +| bg:blendmode | string | the [blend mode](https://developer.mozilla.org/en-US/docs/Web/CSS/blend-mode) of the background | +| bg:bordercolor | string | the color of the border when a block is not active (rarely used) | +| bg:activebordercolor | string | the color of the border when a block is active | + +### Simple solid color with opacity: + +```json +{ + "bg@blue": { + "display:name": "Blue", + "bg:*": true, + "bg": "blue", + "bg:opacity": 0.3, + "bg:activebordercolor": "rgba(0, 0, 255, 1.0)" + } +} +``` + +### Complex gradient combining multiple effects: + +```json +{ + "bg@duskhorizon": { + "display:name": "Dusk Horizon", + "bg:*": true, + "bg": "linear-gradient(0deg, rgba(128,0,0,1) 0%, rgba(204,85,0,0.7) 20%, rgba(255,140,0,0.6) 45%, rgba(160,90,160,0.5) 65%, rgba(60,60,120,1) 100%), radial-gradient(circle at 30% 30%, rgba(255,255,255,0.1), transparent 60%), radial-gradient(circle at 70% 70%, rgba(255,255,255,0.05), transparent 70%)", + "bg:opacity": 0.9, + "bg:blendmode": "overlay" + } +} +``` + +### Repeating pattern background: + +```json +{ + "bg@pattern": { + "display:name": "Diamond Pattern", + "bg:*": true, + "bg": "url('/path/to/pattern.png') repeat", + "bg:opacity": 0.15 + } +} +``` + +### Full-cover background image: + +```json +{ + "bg@cover": { + "display:name": "Ocean Scene", + "bg:*": true, + "bg": "url('/path/to/ocean.jpg') center/cover no-repeat", + "bg:opacity": 0.2 + } +} +``` + +Replace image URLs with your own assets. All examples use reduced opacity to work well with dark themes. + +:::info +Background images can be specified using URLs or local file paths. While URLs are supported, it's recommended to download and serve images locally for better reliability. For local files, you must use absolute paths or paths starting with `~` (e.g. `~/Downloads/background.png`). The system will automatically rewrite local paths to ensure proper access. We support all common web image formats: PNG, JPEG/JPG, WebP, GIF, and SVG. +::: + +:::tip +You can use `wsh setbg --print` to help generate the JSON for your background presets. For example: + +```bash +# Preview the metadata for a gradient background + wsh setbg --print "#ff0000" +{ + "bg:*": true, + "bg": "#ff0000", + "bg:opacity": 0.5 +} + +# Preview a centered logo configuration +wsh setbg --print --center --opacity 0.3 ~/logo.png +{ + "bg:*": true, + "bg": "url('/absolute/path/to/logo.png') no-repeat center/auto", + "bg:opacity": 0.3 +} +``` + +Copy the output and use it as a starting point for your preset configuration, just add the required `display:name` field! +::: #### Unset a default value diff --git a/docs/docs/wsh.mdx b/docs/docs/wsh.mdx index a2f27abff..6766e78f7 100644 --- a/docs/docs/wsh.mdx +++ b/docs/docs/wsh.mdx @@ -145,6 +145,74 @@ wsh editconfig widgets.json --- +## setbg + +The `setbg` command allows you to set a background image or color for the current tab with various customization options. + +```bash +wsh setbg [--opacity value] [--tile|--center] [--size value] (image-path|"#color"|color-name) +``` + +You can set a background using: + +- An image file (displayed as cover, tiled, or centered) +- A hex color (must be quoted like "#ff0000") +- A CSS color name (like "blue" or "forestgreen") + +Flags: + +- `--opacity value` - set the background opacity (0.0-1.0, default 0.5) +- `--tile` - tile the background image instead of using cover mode +- `--center` - center the image without scaling (good for logos) +- `--size` - size for centered images (px, %, or auto) +- `--clear` - remove the background +- `--print` - show the metadata without applying it + +Supported image formats: JPEG, PNG, GIF, WebP, and SVG. + +Examples: + +```bash +# Set an image background with default settings +wsh setbg ~/pictures/background.jpg + +# Set a background with custom opacity +wsh setbg --opacity 0.3 ~/pictures/light-pattern.png + +# Set a tiled background +wsh setbg --tile --opacity 0.2 ~/pictures/texture.png + +# Center an image (good for logos) +wsh setbg --center ~/pictures/logo.png +wsh setbg --center --size 200px ~/pictures/logo.png + +# Set color backgrounds +wsh setbg "#ff0000" # hex color (requires quotes) +wsh setbg forestgreen # CSS color name + +# Change just the opacity of current background +wsh setbg --opacity 0.7 + +# Remove background +wsh setbg --clear + +# Preview the metadata +wsh setbg --print "#ff0000" +``` + +The command validates that: + +- Color values are valid hex codes or CSS color names +- Image paths point to accessible, supported image files +- The opacity value is between 0.0 and 1.0 +- The center and tile options are not used together + +:::tip +Use `--print` to preview the metadata for any background configuration without applying it. You can then copy this JSON representation to use as a [Background Preset](/presets#background-configurations) +::: + +--- + ## run The `run` command creates a new terminal command block and executes a specified command within it. The command can be provided either as arguments after `--` or using the `-c` flag. Unless the `-x` or `-X` flags are passed, commands can be re-executed by pressing `Enter` once the command has finished running. From 7386fc19f7fb0ea58a2dccc377f979bcea569316 Mon Sep 17 00:00:00 2001 From: Mike Sawka Date: Thu, 5 Dec 2024 10:35:54 -0800 Subject: [PATCH 3/5] activity update + dont allow empty workspace names (#1393) --- cmd/server/main-server.go | 1 + docs/docs/telemetry.mdx | 3 +++ emain/emain.ts | 2 -- frontend/app/tab/workspaceswitcher.tsx | 12 ++++++++++-- frontend/types/gotypes.d.ts | 9 ++++++--- pkg/telemetry/telemetry.go | 10 ++++++++++ pkg/waveai/waveai.go | 2 ++ pkg/waveobj/wtype.go | 6 +++--- pkg/wshrpc/wshrpctypes.go | 3 +++ pkg/wstore/wstore_dbops.go | 16 ++++++++++++++++ 10 files changed, 54 insertions(+), 10 deletions(-) diff --git a/cmd/server/main-server.go b/cmd/server/main-server.go index f433c6015..0d32a8def 100644 --- a/cmd/server/main-server.go +++ b/cmd/server/main-server.go @@ -143,6 +143,7 @@ func beforeSendActivityUpdate(ctx context.Context) { activity.NumWindows, _ = wstore.DBGetCount[*waveobj.Window](ctx) activity.NumSSHConn = conncontroller.GetNumSSHHasConnected() activity.NumWSLConn = wsl.GetNumWSLHasConnected() + activity.NumWSNamed, activity.NumWS, _ = wstore.DBGetWSCounts(ctx) err := telemetry.UpdateActivity(ctx, activity) if err != nil { log.Printf("error updating before activity: %v\n", err) diff --git a/docs/docs/telemetry.mdx b/docs/docs/telemetry.mdx index d9c0b8a87..6715ca01c 100644 --- a/docs/docs/telemetry.mdx +++ b/docs/docs/telemetry.mdx @@ -51,12 +51,15 @@ When telemetry is active, we collect the following data. It is stored in the `te | NumTabs | The number of existing tabs open on a given day. | | NewTab | The number of new tabs created on a given day | | NumWindows | The number of existing windows open on a given day. | +| NumWS | The number of existing workspaces on a given day. | +| NumWSNamed | The number of named workspaces on a give day. | | NewTab | The number of new tabs opened on a given day. | | NumStartup | The number of times waveterm has been started on a given day. | | NumShutdown | The number of times waveterm has been shut down on a given day. | | SetTabTheme | The number of times the tab theme is changed from the context menu | | NumMagnify | The number of times any block is magnified | | NumPanics | The number of backend (golang) panics caught in the current day | +| NumAIReqs | The number of AI requests made in the current day | | NumSSHConn | The number of distinct SSH connections that have been made to distinct hosts | | NumWSLConns | The number of distinct WSL connections that have been made to distinct distros | | Renderers | The number of new block views of each type are open on a given day. | diff --git a/emain/emain.ts b/emain/emain.ts index 92ca38acb..9fedeb32d 100644 --- a/emain/emain.ts +++ b/emain/emain.ts @@ -410,7 +410,6 @@ electron.ipcMain.on("set-window-init-status", (event, status: "ready" | "wave-re return; } if (status === "ready") { - console.log("initResolve"); tabView.initResolve(); if (tabView.savedInitOpts) { console.log("savedInitOpts"); @@ -419,7 +418,6 @@ electron.ipcMain.on("set-window-init-status", (event, status: "ready" | "wave-re console.log("no-savedInitOpts"); } } else if (status === "wave-ready") { - console.log("waveReadyResolve"); tabView.waveReadyResolve(); } }); diff --git a/frontend/app/tab/workspaceswitcher.tsx b/frontend/app/tab/workspaceswitcher.tsx index 784126914..80944ef8e 100644 --- a/frontend/app/tab/workspaceswitcher.tsx +++ b/frontend/app/tab/workspaceswitcher.tsx @@ -110,7 +110,13 @@ const ColorAndIconSelector = memo( return (
- + { fireAndForget(async () => { - setObjectValue({ ...newWorkspace, otype: "workspace" }, undefined, true); + if (newWorkspace.name != "") { + setObjectValue({ ...newWorkspace, otype: "workspace" }, undefined, true); + } setWorkspaceEntry({ ...workspaceEntry, workspace: newWorkspace }); }); }, []); diff --git a/frontend/types/gotypes.d.ts b/frontend/types/gotypes.d.ts index f72be05b3..c3d4d7db4 100644 --- a/frontend/types/gotypes.d.ts +++ b/frontend/types/gotypes.d.ts @@ -22,10 +22,13 @@ declare global { newtab?: number; numblocks?: number; numwindows?: number; + numws?: number; + numwsnamed?: number; numsshconn?: number; numwslconn?: number; nummagnify?: number; numpanics?: number; + numaireqs?: number; startup?: number; shutdown?: number; settabtheme?: number; @@ -1127,9 +1130,9 @@ declare global { // waveobj.Workspace type Workspace = WaveObj & { - name: string; - icon: string; - color: string; + name?: string; + icon?: string; + color?: string; tabids: string[]; pinnedtabids: string[]; activetabid: string; diff --git a/pkg/telemetry/telemetry.go b/pkg/telemetry/telemetry.go index 4356498a5..b15d9c97d 100644 --- a/pkg/telemetry/telemetry.go +++ b/pkg/telemetry/telemetry.go @@ -39,6 +39,8 @@ type TelemetryData struct { NumTabs int `json:"numtabs"` NumBlocks int `json:"numblocks,omitempty"` NumWindows int `json:"numwindows,omitempty"` + NumWS int `json:"numws,omitempty"` + NumWSNamed int `json:"numwsnamed,omitempty"` NumSSHConn int `json:"numsshconn,omitempty"` NumWSLConn int `json:"numwslconn,omitempty"` NumMagnify int `json:"nummagnify,omitempty"` @@ -46,6 +48,7 @@ type TelemetryData struct { NumStartup int `json:"numstartup,omitempty"` NumShutdown int `json:"numshutdown,omitempty"` NumPanics int `json:"numpanics,omitempty"` + NumAIReqs int `json:"numaireqs,omitempty"` SetTabTheme int `json:"settabtheme,omitempty"` Displays []wshrpc.ActivityDisplayType `json:"displays,omitempty"` Renderers map[string]int `json:"renderers,omitempty"` @@ -116,6 +119,7 @@ func UpdateActivity(ctx context.Context, update wshrpc.ActivityUpdate) error { tdata.SetTabTheme += update.SetTabTheme tdata.NumMagnify += update.NumMagnify tdata.NumPanics += update.NumPanics + tdata.NumAIReqs += update.NumAIReqs if update.NumTabs > 0 { tdata.NumTabs = update.NumTabs } @@ -125,6 +129,12 @@ func UpdateActivity(ctx context.Context, update wshrpc.ActivityUpdate) error { if update.NumWindows > 0 { tdata.NumWindows = update.NumWindows } + if update.NumWS > 0 { + tdata.NumWS = update.NumWS + } + if update.NumWSNamed > 0 { + tdata.NumWSNamed = update.NumWSNamed + } if update.NumSSHConn > 0 && update.NumSSHConn > tdata.NumSSHConn { tdata.NumSSHConn = update.NumSSHConn } diff --git a/pkg/waveai/waveai.go b/pkg/waveai/waveai.go index 94f0a4ca1..5f5d61edd 100644 --- a/pkg/waveai/waveai.go +++ b/pkg/waveai/waveai.go @@ -8,6 +8,7 @@ import ( "log" "time" + "github.com/wavetermdev/waveterm/pkg/telemetry" "github.com/wavetermdev/waveterm/pkg/wshrpc" ) @@ -63,6 +64,7 @@ func makeAIError(err error) wshrpc.RespOrErrorUnion[wshrpc.OpenAIPacketType] { } func RunAICommand(ctx context.Context, request wshrpc.OpenAiStreamRequest) chan wshrpc.RespOrErrorUnion[wshrpc.OpenAIPacketType] { + telemetry.GoUpdateActivityWrap(wshrpc.ActivityUpdate{NumAIReqs: 1}, "RunAICommand") if request.Opts.APIType == ApiType_Anthropic { endpoint := request.Opts.BaseURL if endpoint == "" { diff --git a/pkg/waveobj/wtype.go b/pkg/waveobj/wtype.go index e186e8f60..9fa4bb3d1 100644 --- a/pkg/waveobj/wtype.go +++ b/pkg/waveobj/wtype.go @@ -167,9 +167,9 @@ type ActiveTabUpdate struct { type Workspace struct { OID string `json:"oid"` Version int `json:"version"` - Name string `json:"name"` - Icon string `json:"icon"` - Color string `json:"color"` + Name string `json:"name,omitempty"` + Icon string `json:"icon,omitempty"` + Color string `json:"color,omitempty"` TabIds []string `json:"tabids"` PinnedTabIds []string `json:"pinnedtabids"` ActiveTabId string `json:"activetabid"` diff --git a/pkg/wshrpc/wshrpctypes.go b/pkg/wshrpc/wshrpctypes.go index 61cddbd2d..334ddd18e 100644 --- a/pkg/wshrpc/wshrpctypes.go +++ b/pkg/wshrpc/wshrpctypes.go @@ -608,10 +608,13 @@ type ActivityUpdate struct { NewTab int `json:"newtab,omitempty"` NumBlocks int `json:"numblocks,omitempty"` NumWindows int `json:"numwindows,omitempty"` + NumWS int `json:"numws,omitempty"` + NumWSNamed int `json:"numwsnamed,omitempty"` NumSSHConn int `json:"numsshconn,omitempty"` NumWSLConn int `json:"numwslconn,omitempty"` NumMagnify int `json:"nummagnify,omitempty"` NumPanics int `json:"numpanics,omitempty"` + NumAIReqs int `json:"numaireqs,omitempty"` Startup int `json:"startup,omitempty"` Shutdown int `json:"shutdown,omitempty"` SetTabTheme int `json:"settabtheme,omitempty"` diff --git a/pkg/wstore/wstore_dbops.go b/pkg/wstore/wstore_dbops.go index cb5f573c2..794214ef2 100644 --- a/pkg/wstore/wstore_dbops.go +++ b/pkg/wstore/wstore_dbops.go @@ -45,6 +45,22 @@ func DBGetCount[T waveobj.WaveObj](ctx context.Context) (int, error) { }) } +// returns (num named workespaces, num total workspaces, error) +func DBGetWSCounts(ctx context.Context) (int, int, error) { + var named, total int + err := WithTx(ctx, func(tx *TxWrap) error { + query := `SELECT count(*) FROM db_workspace WHERE COALESCE(json_extract(data, '$.name'), '') <> ''` + named = tx.GetInt(query) + query = `SELECT count(*) FROM db_workspace` + total = tx.GetInt(query) + return nil + }) + if err != nil { + return 0, 0, err + } + return named, total, nil +} + var viewRe = regexp.MustCompile(`^[a-z0-9]{1,20}$`) func DBGetBlockViewCounts(ctx context.Context) (map[string]int, error) { From b22dd07929bf3ad65cd72c1635ddff179eb60142 Mon Sep 17 00:00:00 2001 From: Evan Simkowitz Date: Thu, 5 Dec 2024 15:11:17 -0500 Subject: [PATCH 4/5] Fix null ref in getConnConfigKeyAtom (#1396) --- frontend/app/store/global.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/app/store/global.ts b/frontend/app/store/global.ts index 21791b391..4f20994a6 100644 --- a/frontend/app/store/global.ts +++ b/frontend/app/store/global.ts @@ -255,7 +255,7 @@ function getConnConfigKeyAtom(connName: string, ke } keyAtom = atom((get) => { let fullConfig = get(atoms.fullConfigAtom); - return fullConfig.connections[connName]?.[key]; + return fullConfig.connections?.[connName]?.[key]; }); connCache.set(keyAtomName, keyAtom); return keyAtom; From 8ec3c3f3305554454ebc117a01322aef2affeea1 Mon Sep 17 00:00:00 2001 From: Evan Simkowitz Date: Thu, 5 Dec 2024 15:13:45 -0500 Subject: [PATCH 5/5] Update Go toolchain to 1.23.4 (#1395) Because Electron now has a macOS 11 minimum supported version, we have no blockers to adopting the latest Go toolchain. This also lets us use the new `range` features in Go, among other things. --- .github/workflows/build-helper.yml | 2 +- .github/workflows/testdriver.yml | 2 +- go.mod | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-helper.yml b/.github/workflows/build-helper.yml index a38ea73d5..96ce7d55b 100644 --- a/.github/workflows/build-helper.yml +++ b/.github/workflows/build-helper.yml @@ -10,7 +10,7 @@ on: - "v[0-9]+.[0-9]+.[0-9]+*" workflow_dispatch: env: - GO_VERSION: "1.22" + GO_VERSION: "1.23" NODE_VERSION: 22 jobs: build-app: diff --git a/.github/workflows/testdriver.yml b/.github/workflows/testdriver.yml index 47d428df6..2a9cbb37c 100644 --- a/.github/workflows/testdriver.yml +++ b/.github/workflows/testdriver.yml @@ -23,7 +23,7 @@ on: workflow_dispatch: null env: - GO_VERSION: "1.22" + GO_VERSION: "1.23" NODE_VERSION: 22 permissions: diff --git a/go.mod b/go.mod index bba3a9b84..4d008bed6 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/wavetermdev/waveterm -go 1.22.4 +go 1.23.4 require ( github.com/alexflint/go-filemutex v1.3.0