From 50203a69343093af5aeb2c935904bc8f3b6dc81c Mon Sep 17 00:00:00 2001 From: Red J Adaya Date: Wed, 24 Apr 2024 14:22:35 +0800 Subject: [PATCH 1/9] Simplified terminal theming (#570) * save work * reusable StyleBlock component * StyleBlock in elements dir * root level * ability to inherit root styles * change prop from classname to selector * selector should always be :root * remove selector prop from StyleBlock * working * cleanup * loadThemeStyles doesn't have to be async * revert changes in tabs2.less * remove old implementation * cleanup * remove file from another branch * fix issue where line in history view doesn't reflect the terminal theme * add key and value validation * add label to tab settings terminal theme dropdown * save work * save work * save work * working * trigger componentDidUpdate when switching tabs and sessions * cleanup * save work * save work * use UpdatePacket for theme changes as well * make methods cohesive * use themes coming from backend * reload terminal when styel block is unmounted and mounted * fix validation * re-render terminal when theme is updated * remove test styles * cleanup * more cleanup * revert unneeded change * more cleanup * fix type * more cleanup * render style blocks in the header instead of body using portal * add ability to reuse and dispose TermThemes instance and file watcher * remove comment * minor change * separate filewatcher as singleton * do not render app when term theme style blocks aren't rendered first * only render main when termstyles have been rendered already * add comment * use DoUpdate to send themes to front-end * support to watch subdirectories * added support for watch subdirectories * make watcher more flexible so it can be closed anywhere * cleanup * undo the app/main split * use TermThemesType in creating initial value for Themes field * simplify code * fix issue where dropdown label doesn't float when the theme selected is Inherit * remove unsed var * start watcher in main, merge themes (don't overwrite) on event. * ensure terminal-themes directory is created on startup * ah, wait for termThemes to be set (the connect packet needs to have been processed to proceed with rendering) --- src/app/app.tsx | 107 ++++++++++------- src/app/clientsettings/clientsettings.tsx | 11 +- src/app/common/elements/index.tsx | 1 + src/app/common/elements/termstyle.tsx | 133 ++++++++++++++++++++++ src/app/common/modals/screensettings.tsx | 11 +- src/app/common/modals/sessionsettings.tsx | 6 +- src/app/history/history.tsx | 5 - src/app/workspace/screen/screenview.tsx | 6 +- src/app/workspace/workspaceview.tsx | 68 +---------- src/models/commandrunner.ts | 21 +++- src/models/model.ts | 109 +++++------------- src/plugins/terminal/term.ts | 8 +- src/types/custom.d.ts | 12 +- src/util/themeutil.ts | 9 +- wavesrv/cmd/main-server.go | 16 ++- wavesrv/go.mod | 1 + wavesrv/go.sum | 3 + wavesrv/pkg/cmdrunner/cmdrunner.go | 16 +-- wavesrv/pkg/configstore/filewatcher.go | 102 +++++++++++++++++ wavesrv/pkg/configstore/termthemes.go | 83 ++++++++++++++ wavesrv/pkg/scbase/scbase.go | 7 +- wavesrv/pkg/scws/scws.go | 6 + wavesrv/pkg/sstore/sstore.go | 8 +- wavesrv/pkg/sstore/updatetypes.go | 2 + 24 files changed, 511 insertions(+), 240 deletions(-) create mode 100644 src/app/common/elements/termstyle.tsx create mode 100644 wavesrv/pkg/configstore/filewatcher.go create mode 100644 wavesrv/pkg/configstore/termthemes.go diff --git a/src/app/app.tsx b/src/app/app.tsx index d66cda185..b9e1a126d 100644 --- a/src/app/app.tsx +++ b/src/app/app.tsx @@ -4,10 +4,10 @@ import * as React from "react"; import * as mobxReact from "mobx-react"; import * as mobx from "mobx"; +import cn from "classnames"; + import { boundMethod } from "autobind-decorator"; import { If } from "tsx-control-statements/components"; -import dayjs from "dayjs"; -import localizedFormat from "dayjs/plugin/localizedFormat"; import { GlobalModel } from "@/models"; import { isBlank } from "@/util/util"; import { WorkspaceView } from "./workspace/workspaceview"; @@ -22,15 +22,15 @@ import { DisconnectedModal, ClientStopModal } from "@/modals"; import { ModalsProvider } from "@/modals/provider"; import { Button } from "@/elements"; import { ErrorBoundary } from "@/common/error/errorboundary"; -import cn from "classnames"; -import "./app.less"; +import { TermStyleList } from "@/elements"; -dayjs.extend(localizedFormat); +import "./app.less"; @mobxReact.observer class App extends React.Component<{}, {}> { dcWait: OV = mobx.observable.box(false, { name: "dcWait" }); mainContentRef: React.RefObject = React.createRef(); + termThemesLoaded: OV = mobx.observable.box(false, { name: "termThemesLoaded" }); constructor(props: {}) { super(props); @@ -80,6 +80,13 @@ class App extends React.Component<{}, {}> { rightSidebarModel.saveState(width, false); } + @boundMethod + handleTermThemesRendered() { + mobx.action(() => { + this.termThemesLoaded.set(true); + })(); + } + render() { const remotesModel = GlobalModel.remotesModel; const disconnected = !GlobalModel.ws.open.get() || !GlobalModel.waveSrvRunning.get(); @@ -90,7 +97,8 @@ class App extends React.Component<{}, {}> { // Previously, this is done in sidebar.tsx but it causes flicker when clientData is null cos screen-view shifts around. // Doing it here fixes the flicker cos app is not rendered until clientData is populated. - if (clientData == null) { + // wait for termThemes as well (this actually means that the "connect" packet has been received) + if (clientData == null || GlobalModel.termThemes.get() == null) { return null; } @@ -118,52 +126,65 @@ class App extends React.Component<{}, {}> { if (dcWait) { setTimeout(() => this.updateDcWait(false), 0); } + // used to force a full reload of the application const renderVersion = GlobalModel.renderVersion.get(); const mainSidebarCollapsed = GlobalModel.mainSidebarModel.getCollapsed(); const rightSidebarCollapsed = GlobalModel.rightSidebarModel.getCollapsed(); const activeMainView = GlobalModel.activeMainView.get(); const lightDarkClass = GlobalModel.isDarkTheme.get() ? "is-dark" : "is-light"; + const mainClassName = cn( + "platform-" + platform, + { + "mainsidebar-collapsed": mainSidebarCollapsed, + "rightsidebar-collapsed": rightSidebarCollapsed, + }, + lightDarkClass + ); return ( -
- -
-
-
- logo + <> + +
+ + +
+
+
+ logo +
+
+ + +
+ +
+
+
+ + + + + + + + + +
-
-
- -
- -
-
-
- - - - - - - - - - + +
- -
+ ); } } diff --git a/src/app/clientsettings/clientsettings.tsx b/src/app/clientsettings/clientsettings.tsx index fa3325d11..dd53cd3ad 100644 --- a/src/app/clientsettings/clientsettings.tsx +++ b/src/app/clientsettings/clientsettings.tsx @@ -76,14 +76,13 @@ class ClientSettingsView extends React.Component<{ model: RemotesModel }, { hove @boundMethod handleChangeTermTheme(theme: string): void { - // For global terminal theme, the key is global, otherwise it's either + // For root terminal theme, the key is root, otherwise it's either // sessionId or screenId. - const currTheme = GlobalModel.getTermTheme()["global"]; + const currTheme = GlobalModel.getTermThemeSettings()["root"]; if (currTheme == theme) { return; } - - const prtn = GlobalCommandRunner.setGlobalTermTheme(theme, false); + const prtn = GlobalCommandRunner.setRootTermTheme(theme, false); commandRtnHandler(prtn, this.errorMessage); } @@ -207,8 +206,8 @@ class ClientSettingsView extends React.Component<{ model: RemotesModel }, { hove const curFontSize = GlobalModel.getTermFontSize(); const curFontFamily = GlobalModel.getTermFontFamily(); const curTheme = GlobalModel.getThemeSource(); - const termThemes = getTermThemes(GlobalModel.termThemes, "Wave Default"); - const currTermTheme = GlobalModel.getTermTheme()["global"] ?? termThemes[0].label; + const termThemes = getTermThemes(GlobalModel.termThemes.get(), "Wave Default"); + const currTermTheme = GlobalModel.getTermThemeSettings()["root"] ?? termThemes[0].label; return ( diff --git a/src/app/common/elements/index.tsx b/src/app/common/elements/index.tsx index cf87b853e..0b1704992 100644 --- a/src/app/common/elements/index.tsx +++ b/src/app/common/elements/index.tsx @@ -18,4 +18,5 @@ export { Toggle } from "./toggle"; export { Tooltip } from "./tooltip"; export { TabIcon } from "./tabicon"; export { DatePicker } from "./datepicker"; +export { TermStyleList } from "./termstyle"; export { CopyButton } from "./copybutton"; diff --git a/src/app/common/elements/termstyle.tsx b/src/app/common/elements/termstyle.tsx new file mode 100644 index 000000000..900ce87ba --- /dev/null +++ b/src/app/common/elements/termstyle.tsx @@ -0,0 +1,133 @@ +// Copyright 2023, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +import * as React from "react"; +import * as mobxReact from "mobx-react"; +import { GlobalModel } from "@/models"; +import ReactDOM from "react-dom"; +import { For } from "tsx-control-statements/components"; +import * as mobx from "mobx"; + +const VALID_CSS_VARIABLES = [ + "--term-black", + "--term-red", + "--term-green", + "--term-yellow", + "--term-blue", + "--term-magenta", + "--term-cyan", + "--term-white", + "--term-bright-black", + "--term-bright-red", + "--term-bright-green", + "--term-bright-yellow", + "--term-bright-blue", + "--term-bright-magenta", + "--term-bright-cyan", + "--term-bright-white", + "--term-gray", + "--term-cmdtext", + "--term-foreground", + "--term-background", + "--term-selection-background", + "--term-cursor-accent", +]; + +@mobxReact.observer +class TermStyle extends React.Component<{ + themeName: string; + selector: string; +}> { + componentDidMount() { + GlobalModel.bumpTermRenderVersion(); + } + + componentWillUnmount() { + GlobalModel.bumpTermRenderVersion(); + } + + componentDidUpdate(prevProps) { + if (prevProps.themeName !== this.props.themeName || prevProps.selector !== this.props.selector) { + GlobalModel.bumpTermRenderVersion(); + } + } + + isValidCSSColor(color) { + const element = document.createElement("div"); + element.style.color = color; + return element.style.color !== ""; + } + + camelCaseToKebabCase(str) { + return str.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase(); + } + + getStyleRules() { + const { selector, themeName } = this.props; + const termThemeOptions = GlobalModel.getTermThemes(); + if (!(themeName in termThemeOptions)) { + return null; + } + const theme = termThemeOptions[themeName]; + if (!theme) { + return null; + } + const styleProperties = Object.entries(theme) + .filter(([key, value]) => { + const cssVarName = `--term-${this.camelCaseToKebabCase(key)}`; + return VALID_CSS_VARIABLES.includes(cssVarName) && this.isValidCSSColor(value); + }) + .map(([key, value]) => `--term-${key}: ${value};`) + .join(" "); + + if (!styleProperties) { + return null; + } + return `${selector} { ${styleProperties} }`; + } + + render() { + const styleRules = this.getStyleRules(); + if (!styleRules) { + return null; + } + return ReactDOM.createPortal(, document.head); + } +} + +@mobxReact.observer +class TermStyleList extends React.Component<{ onRendered: () => void }, {}> { + componentDidMount(): void { + this.props.onRendered(); + } + + getSelector(themeKey: string) { + const sessions = GlobalModel.getSessionNames(); + const screens = GlobalModel.getScreenNames(); + + if (themeKey === "root") { + return ":root"; + } else if (themeKey in screens) { + return `.main-content [data-screenid="${themeKey}"]`; + } else if (themeKey in sessions) { + return `.main-content [data-sessionid="${themeKey}"]`; + } + + return null; + } + + render() { + const termTheme = GlobalModel.getTermThemeSettings(); + const themeKey = null; + + return ( + <> + + + + + ); + } +} + +export { TermStyleList }; diff --git a/src/app/common/modals/screensettings.tsx b/src/app/common/modals/screensettings.tsx index 04418b289..eb89fc6eb 100644 --- a/src/app/common/modals/screensettings.tsx +++ b/src/app/common/modals/screensettings.tsx @@ -156,7 +156,7 @@ class ScreenSettingsModal extends React.Component<{}, {}> { @boundMethod handleChangeTermTheme(theme: string): void { - const currTheme = GlobalModel.getTermTheme()[this.screenId]; + const currTheme = GlobalModel.getTermThemeSettings()[this.screenId]; if (currTheme == theme) { return; } @@ -175,13 +175,8 @@ class ScreenSettingsModal extends React.Component<{}, {}> { if (screen == null) { return null; } - let color: string = null; - let icon: string = null; - let index: number = 0; - const curRemote = GlobalModel.getRemote(GlobalModel.getActiveScreen().getCurRemoteInstance().remoteid); - const termThemes = getTermThemes(GlobalModel.termThemes); - const currTermTheme = GlobalModel.getTermTheme()[this.screenId] ?? termThemes[0].label; - + const termThemes = getTermThemes(GlobalModel.termThemes.get()); + const currTermTheme = GlobalModel.getTermThemeSettings()[this.screenId] ?? termThemes[0].label; return ( diff --git a/src/app/common/modals/sessionsettings.tsx b/src/app/common/modals/sessionsettings.tsx index a208353a5..837a3414d 100644 --- a/src/app/common/modals/sessionsettings.tsx +++ b/src/app/common/modals/sessionsettings.tsx @@ -80,7 +80,7 @@ class SessionSettingsModal extends React.Component<{}, {}> { @boundMethod handleChangeTermTheme(theme: string): void { - const currTheme = GlobalModel.getTermTheme()[this.sessionId]; + const currTheme = GlobalModel.getTermThemeSettings()[this.sessionId]; if (currTheme == theme) { return; } @@ -99,8 +99,8 @@ class SessionSettingsModal extends React.Component<{}, {}> { if (this.session == null) { return null; } - const termThemes = getTermThemes(GlobalModel.termThemes); - const currTermTheme = GlobalModel.getTermTheme()[this.sessionId] ?? termThemes[0].label; + const termThemes = getTermThemes(GlobalModel.termThemes.get()); + const currTermTheme = GlobalModel.getTermThemeSettings()[this.sessionId] ?? termThemes[0].label; return ( diff --git a/src/app/history/history.tsx b/src/app/history/history.tsx index 7aecebd07..ae0a69928 100644 --- a/src/app/history/history.tsx +++ b/src/app/history/history.tsx @@ -649,11 +649,6 @@ class LineContainer extends React.Component<{ historyId: string; width: number } this.line = hvm.getLineById(this.historyItem.lineid); } - componentDidMount(): void { - GlobalModel.bumpTermRenderVersion(); - GlobalModel.termThemeSrcEl.set(null); - } - @boundMethod handleHeightChange(lineNum: number, newHeight: number, oldHeight: number): void { return; diff --git a/src/app/workspace/screen/screenview.tsx b/src/app/workspace/screen/screenview.tsx index fb9517aa7..481779fa0 100644 --- a/src/app/workspace/screen/screenview.tsx +++ b/src/app/workspace/screen/screenview.tsx @@ -184,10 +184,12 @@ class ScreenView extends React.Component<{ session: Session; screen: Screen }, { winWidth = screenWidth - realWidth + "px"; sidebarWidth = realWidth - MagicLayout.ScreenSidebarWidthPadding + "px"; } + const termRenderVersion = GlobalModel.termRenderVersion.get(); + return ( -
+
{ @boundMethod handleChangeTermTheme(theme: string): void { const { screenId } = this.props.screen; - const currTheme = GlobalModel.getTermTheme()[screenId]; + const currTheme = GlobalModel.getTermThemeSettings()[screenId]; if (currTheme == theme) { return; } @@ -155,8 +155,8 @@ class TabSettings extends React.Component<{ screen: Screen }, {}> { render() { const { screen } = this.props; const rptr = screen.curRemote.get(); - const termThemes = getTermThemes(GlobalModel.termThemes); - const currTermTheme = GlobalModel.getTermTheme()[screen.screenId] ?? termThemes[0].label; + const termThemes = getTermThemes(GlobalModel.termThemes.get()); + const currTermTheme = GlobalModel.getTermThemeSettings()[screen.screenId] ?? termThemes[0].label; return (
@@ -212,59 +212,6 @@ class TabSettings extends React.Component<{ screen: Screen }, {}> { @mobxReact.observer class WorkspaceView extends React.Component<{}, {}> { sessionRef = React.createRef(); - theme: string; - themeReactionDisposer: mobx.IReactionDisposer; - - componentDidMount() { - this.setupThemeReaction(); - } - - componentDidUpdate() { - this.setupThemeReaction(); - } - - setupThemeReaction() { - if (this.themeReactionDisposer) { - this.themeReactionDisposer(); - } - - // This handles session and screen-level terminal theming. - // Ideally, screen-level theming should be handled in the inner-level component, but - // the frequent mounting and unmounting of the screen view make it really difficult to work. - this.themeReactionDisposer = mobx.reaction( - () => { - return { - termTheme: GlobalModel.getTermTheme(), - session: GlobalModel.getActiveSession(), - screen: GlobalModel.getActiveScreen(), - }; - }, - ({ termTheme, session, screen }) => { - let currTheme = termTheme[session.sessionId]; - if (termTheme[screen.screenId]) { - currTheme = termTheme[screen.screenId]; - } - if (session && currTheme !== this.theme && this.sessionRef.current) { - const reset = currTheme == null; - const theme = currTheme ?? this.theme; - const themeSrcEl = reset ? null : this.sessionRef.current; - const rtn = GlobalModel.updateTermTheme(this.sessionRef.current, theme, reset); - rtn.then(() => { - GlobalModel.termThemeSrcEl.set(themeSrcEl); - }).then(() => { - GlobalModel.bumpTermRenderVersion(); - }); - this.theme = currTheme; - } - } - ); - } - - componentWillUnmount() { - if (this.themeReactionDisposer) { - this.themeReactionDisposer(); - } - } @boundMethod toggleTabSettings() { @@ -281,15 +228,14 @@ class WorkspaceView extends React.Component<{}, {}> { sessionId = session.sessionId; activeScreen = session.getActiveScreen(); } - const isHidden = GlobalModel.activeMainView.get() != "session"; const mainSidebarModel = GlobalModel.mainSidebarModel; - const termRenderVersion = GlobalModel.termRenderVersion.get(); const showTabSettings = GlobalModel.tabSettingsOpen.get(); return (
{
- + diff --git a/src/models/commandrunner.ts b/src/models/commandrunner.ts index 054888425..e4a0e8494 100644 --- a/src/models/commandrunner.ts +++ b/src/models/commandrunner.ts @@ -381,29 +381,40 @@ class CommandRunner { return GlobalModel.submitCommand("client", "set", null, kwargs, interactive); } - setGlobalTermTheme(theme: string, interactive: boolean): Promise { + setRootTermTheme(theme: string, interactive: boolean): Promise { + let ftheme = theme; + if (ftheme == "inherit") { + ftheme = ""; + } let kwargs = { nohist: "1", - termtheme: theme, + termtheme: ftheme, }; return GlobalModel.submitCommand("client", "set", null, kwargs, interactive); } setSessionTermTheme(sessionId: string, name: string, interactive: boolean): Promise { - console.log("setSessionTermTheme-------"); + let fname = name; + if (name == "inherit") { + fname = ""; + } let kwargs = { nohist: "1", id: sessionId, - name: name, + name: fname, }; return GlobalModel.submitCommand("session", "termtheme", null, kwargs, interactive); } setScreenTermTheme(screenId: string, name: string, interactive: boolean): Promise { + let fname = name; + if (name == "inherit") { + fname = ""; + } let kwargs = { nohist: "1", id: screenId, - name: name, + name: fname, }; return GlobalModel.submitCommand("screen", "termtheme", null, kwargs, interactive); } diff --git a/src/models/model.ts b/src/models/model.ts index 3ba346db3..348f88f6b 100644 --- a/src/models/model.ts +++ b/src/models/model.ts @@ -137,22 +137,16 @@ class Model { renderVersion: OV = mobx.observable.box(0, { name: "renderVersion", }); - appUpdateStatus = mobx.observable.box(getApi().getAppUpdateStatus(), { name: "appUpdateStatus", }); - - termThemes: OMap = mobx.observable.array([], { + termThemes: OV = mobx.observable.box(null, { name: "terminalThemes", deep: false, }); - termThemeSrcEl: OV = mobx.observable.box(null, { - name: "termThemeSrcEl", - }); termRenderVersion: OV = mobx.observable.box(0, { name: "termRenderVersion", }); - currGlobalTermTheme: string; private constructor() { this.clientId = getApi().getId(); @@ -166,7 +160,6 @@ class Model { this.ws.reconnect(); this.keybindManager = new KeybindManager(this); this.readConfigKeybindings(); - this.fetchTerminalThemes(); this.initSystemKeybindings(); this.initAppKeybindings(); this.inputModel = new InputModel(this); @@ -239,42 +232,6 @@ class Model { } } - fetchTerminalThemes() { - const url = new URL(this.getBaseHostPort() + "/config/terminal-themes"); - fetch(url, { method: "get", body: null, headers: this.getFetchHeaders() }) - .then((resp) => { - if (resp.status == 404) { - return []; - } else if (!resp.ok) { - util.handleNotOkResp(resp, url); - } - return resp.json(); - }) - .then((themes) => { - const tt = themes.map((theme) => theme.name.split(".")[0]); - this.termThemes.replace(tt); - }); - } - - updateTermTheme(element: HTMLElement, themeFileName: string, reset: boolean) { - const url = new URL(this.getBaseHostPort() + `/config/terminal-themes/${themeFileName}.json`); - return fetch(url, { method: "get", body: null, headers: this.getFetchHeaders() }) - .then((resp) => resp.json()) - .then((themeVars: TermThemeType) => { - Object.keys(themeVars).forEach((key) => { - if (reset) { - this.resetStyleVar(element, `--term-${key}`); - } else { - this.resetStyleVar(element, `--term-${key}`); - this.setStyleVar(element, `--term-${key}`, themeVars[key]); - } - }); - }) - .catch((error) => { - console.error(`error applying theme: ${themeFileName}`, error); - }); - } - bumpTermRenderVersion() { mobx.action(() => { this.termRenderVersion.set(this.termRenderVersion.get() + 1); @@ -486,10 +443,10 @@ class Model { } } - getTermTheme(): TermThemeType { + getTermThemeSettings(): TermThemeSettingsType { let cdata = this.clientData.get(); - if (cdata?.feopts?.termtheme) { - return mobx.toJS(cdata.feopts.termtheme); + if (cdata?.feopts?.termthemesettings) { + return mobx.toJS(cdata.feopts.termthemesettings); } return {}; } @@ -937,6 +894,27 @@ class Model { } } + mergeTermThemes(termThemes: TermThemesType) { + mobx.action(() => { + if (this.termThemes.get() == null) { + this.termThemes.set(termThemes); + return; + } + for (const [themeName, theme] of Object.entries(termThemes)) { + if (theme == null) { + delete this.termThemes.get()[themeName]; + continue; + } + this.termThemes.get()[themeName] = theme; + } + })(); + this.bumpTermRenderVersion(); + } + + getTermThemes(): TermThemesType { + return this.termThemes.get(); + } + updateScreenStatusIndicators(screenStatusIndicators: ScreenStatusIndicatorUpdateType[]) { for (const update of screenStatusIndicators) { this.getScreenById_single(update.screenid)?.setStatusIndicator(update.status); @@ -983,7 +961,7 @@ class Model { if (update.connect.screenstatusindicators != null) { this.updateScreenStatusIndicators(update.connect.screenstatusindicators); } - + this.mergeTermThemes(update.connect.termthemes ?? {}); this.sessionListLoaded.set(true); this.remotesLoaded.set(true); } else if (update.screen != null) { @@ -1056,6 +1034,8 @@ class Model { } else if (update.userinputrequest != null) { const userInputRequest: UserInputRequest = update.userinputrequest; this.modalsModel.pushModal(appconst.USER_INPUT, userInputRequest); + } else if (update.termthemes != null) { + this.mergeTermThemes(update.termthemes); } else if (update.sessiontombstone != null || update.screentombstone != null) { // nothing (ignore) } else { @@ -1309,9 +1289,6 @@ class Model { newTheme = appconst.DefaultTheme; } const themeUpdated = newTheme != this.getThemeSource(); - const oldTermTheme = this.getTermTheme(); - const newTermTheme = clientData?.feopts?.termtheme; - const ttUpdated = this.termThemeUpdated(newTermTheme, oldTermTheme); mobx.action(() => { this.clientData.set(clientData); })(); @@ -1332,36 +1309,6 @@ class Model { getApi().setNativeThemeSource(newTheme); this.bumpRenderVersion(); } - // Only for global terminal theme. For session and screen terminal theme, - // they are handled in workspace view. - if (newTermTheme) { - const el = document.documentElement; - const globaltt = newTermTheme["global"] ?? this.currGlobalTermTheme; - const reset = newTermTheme["global"] == null; - if (globaltt) { - const rtn = this.updateTermTheme(el, globaltt, reset); - rtn.then(() => { - if (ttUpdated) { - this.bumpTermRenderVersion(); - } - }); - this.currGlobalTermTheme = globaltt; - } - } - } - - termThemeUpdated(newTermTheme, oldTermTheme) { - for (const key in oldTermTheme) { - if (!(key in newTermTheme)) { - return true; - } - } - for (const key in newTermTheme) { - if (!oldTermTheme[key] || oldTermTheme[key] !== newTermTheme[key]) { - return true; - } - } - return false; } submitCommandPacket(cmdPk: FeCmdPacketType, interactive: boolean): Promise { diff --git a/src/plugins/terminal/term.ts b/src/plugins/terminal/term.ts index 486a466b7..93f8e55f2 100644 --- a/src/plugins/terminal/term.ts +++ b/src/plugins/terminal/term.ts @@ -51,10 +51,9 @@ type TermWrapOpts = { onUpdateContentHeight: (termContext: RendererContext, height: number) => void; }; -function getThemeFromCSSVars(themeSrcEl: HTMLElement): ITheme { +function getThemeFromCSSVars(el: Element): ITheme { const theme: ITheme = {}; - const tse = themeSrcEl ?? document.documentElement; - let rootStyle = getComputedStyle(tse); + const rootStyle = getComputedStyle(el); theme.foreground = rootStyle.getPropertyValue("--term-foreground"); theme.background = rootStyle.getPropertyValue("--term-background"); theme.black = rootStyle.getPropertyValue("--term-black"); @@ -131,8 +130,7 @@ class TermWrap { let cols = windowWidthToCols(opts.winSize.width, opts.fontSize); this.termSize = { rows: opts.termOpts.rows, cols: cols }; } - const themeSrcEl = GlobalModel.termThemeSrcEl.get(); - let theme = getThemeFromCSSVars(themeSrcEl); + let theme = getThemeFromCSSVars(this.connectedElem); this.terminal = new Terminal({ rows: this.termSize.rows, cols: this.termSize.cols, diff --git a/src/types/custom.d.ts b/src/types/custom.d.ts index 34b04375b..1546e0cd7 100644 --- a/src/types/custom.d.ts +++ b/src/types/custom.d.ts @@ -345,6 +345,7 @@ declare global { screenstatusindicators: ScreenStatusIndicatorUpdateType[]; screennumrunningcommands: ScreenNumRunningCommandsUpdateType[]; activesessionid: string; + termthemes: TermThemesType; }; type BookmarksUpdateType = { @@ -386,6 +387,13 @@ declare global { userinputrequest?: UserInputRequest; screentombstone?: any; sessiontombstone?: any; + termthemes?: TermThemesType; + }; + + type TermThemesType = { + [key: string]: { + [innerKey: string]: string; + }; }; type HistoryViewDataType = { @@ -581,7 +589,7 @@ declare global { data: Uint8Array; }; - type TermThemeType = { + type TermThemeSettingsType = { [k: string]: string | null; }; @@ -589,7 +597,7 @@ declare global { termfontsize: number; termfontfamily: string; theme: NativeThemeSource; - termtheme: TermThemeType; + termthemesettings: TermThemeSettingsType; }; type ConfirmFlagsType = { diff --git a/src/util/themeutil.ts b/src/util/themeutil.ts index ceb93ebe3..5433b12b1 100644 --- a/src/util/themeutil.ts +++ b/src/util/themeutil.ts @@ -1,10 +1,13 @@ -function getTermThemes(termThemes: string[], noneLabel = "Inherit"): DropdownItem[] { +function getTermThemes(termThemeOptions: string[], noneLabel = "Inherit"): DropdownItem[] { + if (!termThemeOptions) { + return []; + } const tt: DropdownItem[] = []; tt.push({ label: noneLabel, - value: null, + value: "inherit", }); - for (const themeName of termThemes) { + for (const themeName of Object.keys(termThemeOptions)) { tt.push({ label: themeName, value: themeName, diff --git a/wavesrv/cmd/main-server.go b/wavesrv/cmd/main-server.go index ee2059403..aff0d6e81 100644 --- a/wavesrv/cmd/main-server.go +++ b/wavesrv/cmd/main-server.go @@ -37,6 +37,7 @@ import ( "github.com/wavetermdev/waveterm/waveshell/pkg/wlog" "github.com/wavetermdev/waveterm/wavesrv/pkg/bufferedpipe" "github.com/wavetermdev/waveterm/wavesrv/pkg/cmdrunner" + "github.com/wavetermdev/waveterm/wavesrv/pkg/configstore" "github.com/wavetermdev/waveterm/wavesrv/pkg/ephemeral" "github.com/wavetermdev/waveterm/wavesrv/pkg/pcloud" "github.com/wavetermdev/waveterm/wavesrv/pkg/releasechecker" @@ -156,6 +157,7 @@ func HandleWs(w http.ResponseWriter, r *http.Request) { removeWSStateAfterTimeout(clientId, stateConnectTime, WSStateReconnectTime) }() log.Printf("WebSocket opened %s %s\n", state.ClientId, shell.RemoteAddr) + state.RunWSRead() } @@ -976,6 +978,10 @@ func doShutdown(reason string) { log.Printf("[wave] closing db connection\n") sstore.CloseDB() log.Printf("[wave] *** shutting down local server\n") + watcher := configstore.GetWatcher() + if watcher != nil { + watcher.Close() + } time.Sleep(1 * time.Second) syscall.Kill(syscall.Getpid(), syscall.SIGINT) time.Sleep(5 * time.Second) @@ -1016,6 +1022,13 @@ func configDirHandler(w http.ResponseWriter, r *http.Request) { w.Write(dirListJson) } +func configWatcher() { + watcher := configstore.GetWatcher() + if watcher != nil { + watcher.Start() + } +} + func startupActivityUpdate() { activity := telemetry.ActivityUpdate{ NumConns: remote.NumRemotes(), @@ -1080,7 +1093,7 @@ func main() { log.Printf("[error] %v\n", err) return } - _, err = scbase.EnsureConfigDir() + _, err = scbase.EnsureConfigDirs() if err != nil { log.Printf("[error] ensuring config directory: %v\n", err) return @@ -1120,6 +1133,7 @@ func main() { startupActivityUpdate() installSignalHandlers() go telemetryLoop() + go configWatcher() go stdinReadWatch() go runWebSocketServer() go func() { diff --git a/wavesrv/go.mod b/wavesrv/go.mod index 252b64f29..e95b03fae 100644 --- a/wavesrv/go.mod +++ b/wavesrv/go.mod @@ -8,6 +8,7 @@ require ( github.com/alessio/shellescape v1.4.1 github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2 github.com/creack/pty v1.1.18 + github.com/fsnotify/fsnotify v1.6.0 github.com/golang-migrate/migrate/v4 v4.16.2 github.com/google/go-github/v60 v60.0.0 github.com/google/uuid v1.3.0 diff --git a/wavesrv/go.sum b/wavesrv/go.sum index 13d2e2ce6..ca70668e5 100644 --- a/wavesrv/go.sum +++ b/wavesrv/go.sum @@ -9,6 +9,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA= github.com/frankban/quicktest v1.14.5/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/golang-migrate/migrate/v4 v4.16.2 h1:8coYbMKUyInrFk1lfGfRovTLAW7PhWp8qQDT2iKfuoA= @@ -63,6 +65,7 @@ golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= diff --git a/wavesrv/pkg/cmdrunner/cmdrunner.go b/wavesrv/pkg/cmdrunner/cmdrunner.go index fd4c0ac11..96d29e2a4 100644 --- a/wavesrv/pkg/cmdrunner/cmdrunner.go +++ b/wavesrv/pkg/cmdrunner/cmdrunner.go @@ -3640,13 +3640,13 @@ func TermSetThemeCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) } themeName, themeNameOk := pk.Kwargs["name"] feOpts := clientData.FeOpts - if feOpts.TermTheme == nil { - feOpts.TermTheme = make(map[string]string) + if feOpts.TermThemeSettings == nil { + feOpts.TermThemeSettings = make(map[string]string) } if themeNameOk && themeName != "" { - feOpts.TermTheme[id] = themeName + feOpts.TermThemeSettings[id] = themeName } else { - delete(feOpts.TermTheme, id) + delete(feOpts.TermThemeSettings, id) } err = sstore.UpdateClientFeOpts(ctx, feOpts) if err != nil { @@ -5856,13 +5856,13 @@ func ClientSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sc } if termthemeStr, found := pk.Kwargs["termtheme"]; found { feOpts := clientData.FeOpts - if feOpts.TermTheme == nil { - feOpts.TermTheme = make(map[string]string) + if feOpts.TermThemeSettings == nil { + feOpts.TermThemeSettings = make(map[string]string) } if termthemeStr == "" { - delete(feOpts.TermTheme, "global") + delete(feOpts.TermThemeSettings, "root") } else { - feOpts.TermTheme["global"] = termthemeStr + feOpts.TermThemeSettings["root"] = termthemeStr } err = sstore.UpdateClientFeOpts(ctx, feOpts) if err != nil { diff --git a/wavesrv/pkg/configstore/filewatcher.go b/wavesrv/pkg/configstore/filewatcher.go new file mode 100644 index 000000000..7ad944ab7 --- /dev/null +++ b/wavesrv/pkg/configstore/filewatcher.go @@ -0,0 +1,102 @@ +package configstore + +import ( + "log" + "os" + "path/filepath" + "sync" + + "github.com/fsnotify/fsnotify" + "github.com/wavetermdev/waveterm/wavesrv/pkg/scbus" +) + +var instance *Watcher +var once sync.Once + +type Watcher struct { + watcher *fsnotify.Watcher + mutex sync.Mutex +} + +// GetWatcher returns the singleton instance of the Watcher +func GetWatcher() *Watcher { + once.Do(func() { + watcher, err := fsnotify.NewWatcher() + if err != nil { + log.Printf("failed to create file watcher: %v", err) + return + } + instance = &Watcher{watcher: watcher} + log.Printf("started config watcher: %v\n", configDirAbsPath) + if err := instance.addPath(configDirAbsPath); err != nil { + log.Printf("failed to add path %s to watcher: %v", configDirAbsPath, err) + return + } + }) + return instance +} + +// addPath adds the specified path and all its subdirectories to the watcher +func (w *Watcher) addPath(path string) error { + return filepath.Walk(path, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + if err := w.watcher.Add(path); err != nil { + return err + } + log.Printf("added to watcher: %s", path) + } + return nil + }) +} + +func (w *Watcher) Start() { + for { + select { + case event, ok := <-w.watcher.Events: + if !ok { + return + } + w.handleEvent(event) + case err, ok := <-w.watcher.Errors: + if !ok { + return + } + log.Println("watcher error:", err) + } + } +} + +func (w *Watcher) Close() { + w.mutex.Lock() + defer w.mutex.Unlock() + if w.watcher != nil { + w.watcher.Close() + w.watcher = nil + log.Println("file watcher closed.") + } +} + +func (w *Watcher) handleEvent(event fsnotify.Event) { + config := make(ConfigReturn) + fileName, normalizedPath := getNameAndPath(event) + + if event.Op&fsnotify.Write == fsnotify.Write || event.Op&fsnotify.Create == fsnotify.Create || event.Op&fsnotify.Rename == fsnotify.Rename { + content, err := readFileContents(normalizedPath) + if err != nil { + log.Printf("error reading file %s: %v", normalizedPath, err) + return + } + config[fileName] = content + } + + if event.Op&fsnotify.Remove == fsnotify.Remove { + config[fileName] = nil + } + + update := scbus.MakeUpdatePacket() + update.AddUpdate(config) + scbus.MainUpdateBus.DoUpdate(update) +} diff --git a/wavesrv/pkg/configstore/termthemes.go b/wavesrv/pkg/configstore/termthemes.go new file mode 100644 index 000000000..bfb44fded --- /dev/null +++ b/wavesrv/pkg/configstore/termthemes.go @@ -0,0 +1,83 @@ +package configstore + +import ( + "encoding/json" + "errors" + "log" + "os" + "path" + "path/filepath" + + "github.com/fsnotify/fsnotify" + "github.com/wavetermdev/waveterm/wavesrv/pkg/scbase" +) + +const ConfigReturnTypeStr = "termthemes" +const configDir = "config/terminal-themes/" + +var configDirAbsPath = path.Join(scbase.GetWaveHomeDir(), configDir) + +type ConfigReturn map[string]map[string]string + +func (tt ConfigReturn) GetType() string { + return ConfigReturnTypeStr +} + +func getNameAndPath(event fsnotify.Event) (string, string) { + filePath := event.Name + fileName := filepath.Base(filePath) + + // Normalize the file path for consistency across platforms + normalizedPath := filepath.ToSlash(filePath) + return fileName, normalizedPath +} + +// readFileContents reads and unmarshals the JSON content from a file. +func readFileContents(filePath string) (map[string]string, error) { + data, err := os.ReadFile(filePath) + if err != nil { + return nil, err + } + var content map[string]string + if err := json.Unmarshal(data, &content); err != nil { + return nil, err + } + return content, nil +} + +// ScanConfigs reads all JSON files in the specified directory and its subdirectories. +func ScanConfigs() (ConfigReturn, error) { + config := make(ConfigReturn) + + if _, err := os.Stat(configDirAbsPath); errors.Is(err, os.ErrNotExist) { + log.Printf("directory does not exist: %s", configDirAbsPath) + return ConfigReturn{}, nil + } + + err := filepath.Walk(configDirAbsPath, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if !info.IsDir() && filepath.Ext(info.Name()) == ".json" { + content, err := readFileContents(path) + if err != nil { + log.Printf("error reading file %s: %v", path, err) + return nil // continue walking despite error in reading file + } + // Use the relative path from the directory as the key to store themes + relPath, err := filepath.Rel(configDirAbsPath, path) + if err != nil { + log.Printf("error getting relative file path %s: %v", path, err) + return nil // continue walking despite error in getting relative path + } + config[relPath] = content + } + return nil + }) + + if err != nil { + return nil, err + } + + return config, nil +} diff --git a/wavesrv/pkg/scbase/scbase.go b/wavesrv/pkg/scbase/scbase.go index 99f998803..67c11bf28 100644 --- a/wavesrv/pkg/scbase/scbase.go +++ b/wavesrv/pkg/scbase/scbase.go @@ -234,7 +234,7 @@ func GetScreensDir() string { return sdir } -func EnsureConfigDir() (string, error) { +func EnsureConfigDirs() (string, error) { scHome := GetWaveHomeDir() configDir := path.Join(scHome, "config") err := ensureDir(configDir) @@ -250,6 +250,11 @@ func EnsureConfigDir() (string, error) { keybindingsFileObj.WriteString("[]\n") keybindingsFileObj.Close() } + terminalThemesDir := path.Join(configDir, "terminal-themes") + err = ensureDir(terminalThemesDir) + if err != nil { + return "", err + } return configDir, nil } diff --git a/wavesrv/pkg/scws/scws.go b/wavesrv/pkg/scws/scws.go index 2e4df20f1..37f4d7a3f 100644 --- a/wavesrv/pkg/scws/scws.go +++ b/wavesrv/pkg/scws/scws.go @@ -13,6 +13,7 @@ import ( "github.com/google/uuid" "github.com/wavetermdev/waveterm/waveshell/pkg/packet" + "github.com/wavetermdev/waveterm/wavesrv/pkg/configstore" "github.com/wavetermdev/waveterm/wavesrv/pkg/mapqueue" "github.com/wavetermdev/waveterm/wavesrv/pkg/remote" "github.com/wavetermdev/waveterm/wavesrv/pkg/scbus" @@ -165,6 +166,11 @@ func (ws *WSState) handleConnection() error { connectUpdate.Remotes = remotes // restore status indicators connectUpdate.ScreenStatusIndicators, connectUpdate.ScreenNumRunningCommands = sstore.GetCurrentIndicatorState() + configs, err := configstore.ScanConfigs() + if err != nil { + return fmt.Errorf("getting configs: %w", err) + } + connectUpdate.TermThemes = &configs mu := scbus.MakeUpdatePacket() mu.AddUpdate(*connectUpdate) err = ws.Shell.WriteJson(mu) diff --git a/wavesrv/pkg/sstore/sstore.go b/wavesrv/pkg/sstore/sstore.go index 2cc6a2a79..c0a5b5e4b 100644 --- a/wavesrv/pkg/sstore/sstore.go +++ b/wavesrv/pkg/sstore/sstore.go @@ -250,10 +250,10 @@ type ClientOptsType struct { } type FeOptsType struct { - TermFontSize int `json:"termfontsize,omitempty"` - TermFontFamily string `json:"termfontfamily,omitempty"` - Theme string `json:"theme,omitempty"` - TermTheme map[string]string `json:"termtheme"` + TermFontSize int `json:"termfontsize,omitempty"` + TermFontFamily string `json:"termfontfamily,omitempty"` + Theme string `json:"theme,omitempty"` + TermThemeSettings map[string]string `json:"termthemesettings"` } type ReleaseInfoType struct { diff --git a/wavesrv/pkg/sstore/updatetypes.go b/wavesrv/pkg/sstore/updatetypes.go index 2b777f421..2291cfada 100644 --- a/wavesrv/pkg/sstore/updatetypes.go +++ b/wavesrv/pkg/sstore/updatetypes.go @@ -8,6 +8,7 @@ import ( "github.com/wavetermdev/waveterm/waveshell/pkg/packet" "github.com/wavetermdev/waveterm/waveshell/pkg/utilfn" + "github.com/wavetermdev/waveterm/wavesrv/pkg/configstore" "github.com/wavetermdev/waveterm/wavesrv/pkg/scbus" ) @@ -104,6 +105,7 @@ type ConnectUpdate struct { ScreenStatusIndicators []*ScreenStatusIndicatorType `json:"screenstatusindicators,omitempty"` ScreenNumRunningCommands []*ScreenNumRunningCommandsType `json:"screennumrunningcommands,omitempty"` ActiveSessionId string `json:"activesessionid,omitempty"` + TermThemes *configstore.ConfigReturn `json:"termthemes,omitempty"` } func (ConnectUpdate) GetType() string { From 0649a73d84b6af09ebed5f9b57bfe973beca707a Mon Sep 17 00:00:00 2001 From: Mike Sawka Date: Thu, 25 Apr 2024 11:17:52 -0700 Subject: [PATCH 2/9] bump wave version to v0.7.3, bump waveshell version to v0.7 (#604) --- package.json | 2 +- scripthaus.md | 6 +++--- waveshell/pkg/base/base.go | 2 +- wavesrv/pkg/scbase/scbase.go | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 6cafd41a2..1dffa8ef7 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ }, "productName": "Wave", "description": "An Open-Source, AI-Native, Terminal Built for Seamless Workflows", - "version": "0.7.2", + "version": "0.7.3", "main": "dist/emain.js", "license": "Apache-2.0", "repository": { diff --git a/scripthaus.md b/scripthaus.md index 21785bc9c..96c9c4fae 100644 --- a/scripthaus.md +++ b/scripthaus.md @@ -44,7 +44,7 @@ rm -rf bin/ rm -rf build/ node_modules/.bin/webpack --env prod WAVESRV_VERSION=$(node -e 'console.log(require("./version.js"))') -WAVESHELL_VERSION=v0.6 +WAVESHELL_VERSION=v0.7 GO_LDFLAGS="-s -w -X main.BuildTime=$(date +'%Y%m%d%H%M')" function buildWaveShell { (cd waveshell; CGO_ENABLED=0 GOOS=$1 GOARCH=$2 go build -ldflags="$GO_LDFLAGS" -o ../bin/mshell/mshell-$WAVESHELL_VERSION-$1.$2 main-waveshell.go) @@ -69,7 +69,7 @@ rm -rf bin/ rm -rf build/ node_modules/.bin/webpack --env prod WAVESRV_VERSION=$(node -e 'console.log(require("./version.js"))') -WAVESHELL_VERSION=v0.6 +WAVESHELL_VERSION=v0.7 GO_LDFLAGS="-s -w -X main.BuildTime=$(date +'%Y%m%d%H%M')" function buildWaveShell { (cd waveshell; CGO_ENABLED=0 GOOS=$1 GOARCH=$2 go build -ldflags="$GO_LDFLAGS" -o ../bin/mshell/mshell-$WAVESHELL_VERSION-$1.$2 main-waveshell.go) @@ -96,7 +96,7 @@ CGO_ENABLED=1 go build -tags "osusergo,netgo,sqlite_omit_load_extension" -ldflag ```bash # @scripthaus command fullbuild-waveshell set -e -WAVESHELL_VERSION=v0.6 +WAVESHELL_VERSION=v0.7 GO_LDFLAGS="-s -w -X main.BuildTime=$(date +'%Y%m%d%H%M')" function buildWaveShell { (cd waveshell; CGO_ENABLED=0 GOOS=$1 GOARCH=$2 go build -ldflags="$GO_LDFLAGS" -o ../bin/mshell/mshell-$WAVESHELL_VERSION-$1.$2 main-waveshell.go) diff --git a/waveshell/pkg/base/base.go b/waveshell/pkg/base/base.go index 71cb12a20..5f85f30bb 100644 --- a/waveshell/pkg/base/base.go +++ b/waveshell/pkg/base/base.go @@ -29,7 +29,7 @@ const SSHCommandVarName = "SSH_COMMAND" const MShellDebugVarName = "MSHELL_DEBUG" const SessionsDirBaseName = "sessions" const RcFilesDirBaseName = "rcfiles" -const MShellVersion = "v0.6.0" +const MShellVersion = "v0.7.0" const RemoteIdFile = "remoteid" const DefaultMShellInstallBinDir = "/opt/mshell/bin" const LogFileName = "mshell.log" diff --git a/wavesrv/pkg/scbase/scbase.go b/wavesrv/pkg/scbase/scbase.go index 67c11bf28..60daad85e 100644 --- a/wavesrv/pkg/scbase/scbase.go +++ b/wavesrv/pkg/scbase/scbase.go @@ -36,7 +36,7 @@ const WaveDirName = ".waveterm" // must match emain.ts const WaveDevDirName = ".waveterm-dev" // must match emain.ts const WaveAppPathVarName = "WAVETERM_APP_PATH" const WaveAuthKeyFileName = "waveterm.authkey" -const MShellVersion = "v0.6.0" // must match base.MShellVersion +const MShellVersion = "v0.7.0" // must match base.MShellVersion // initialized by InitialzeWaveAuthKey (called by main-server) var WaveAuthKey string From 35037ac1f15983846334abeec4757142299bd9f2 Mon Sep 17 00:00:00 2001 From: Red J Adaya Date: Fri, 26 Apr 2024 04:29:24 +0800 Subject: [PATCH 3/9] add padding around remote status warning (#598) --- src/app/workspace/cmdinput/cmdinput.less | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/app/workspace/cmdinput/cmdinput.less b/src/app/workspace/cmdinput/cmdinput.less index d013b6a39..11d292230 100644 --- a/src/app/workspace/cmdinput/cmdinput.less +++ b/src/app/workspace/cmdinput/cmdinput.less @@ -31,6 +31,8 @@ flex-direction: row; color: var(--app-warning-color); align-items: center; + padding: var(--termpad) calc(var(--termpad) * 2) 0 calc(var(--termpad) * 2); + margin-left: 2px; .wave-button, .button { From 12e267ad81e580f233b7e60ea6ef9b132f799ee4 Mon Sep 17 00:00:00 2001 From: Red J Adaya Date: Fri, 26 Apr 2024 04:36:11 +0800 Subject: [PATCH 4/9] Truncate dropdown value (#602) * truncate dropdown value * more defensive programming for width --- src/app/common/elements/dropdown.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/app/common/elements/dropdown.tsx b/src/app/common/elements/dropdown.tsx index 9adbad315..306fe8588 100644 --- a/src/app/common/elements/dropdown.tsx +++ b/src/app/common/elements/dropdown.tsx @@ -258,7 +258,11 @@ class Dropdown extends React.Component { document.getElementById("app")! ) : null; - + let selectedOptionLabelStyle = {}; + const wrapperClientWidth = this.wrapperRef.current?.clientWidth; + if ((wrapperClientWidth ?? 0) > 0) { + selectedOptionLabelStyle["width"] = Math.max(wrapperClientWidth - 55, 0); + } return (
{
{selectedOptionLabel}
From 6e28151dad37cb3d7226230d2acde34cdb8ad9be Mon Sep 17 00:00:00 2001 From: Mike Sawka Date: Thu, 25 Apr 2024 14:03:38 -0700 Subject: [PATCH 5/9] update dependencies (including electron v30) (#605) * remove console.logs * update dependencies --- package.json | 12 +- src/plugins/code/code.tsx | 2 - yarn.lock | 682 ++++++++++++++++++++------------------ 3 files changed, 366 insertions(+), 330 deletions(-) diff --git a/package.json b/package.json index 1dffa8ef7..3f9c9466a 100644 --- a/package.json +++ b/package.json @@ -32,10 +32,10 @@ "electron-squirrel-startup": "^1.0.0", "electron-updater": "^6.1.8", "framer-motion": "^10.16.16", - "lexical": "^0.14.3", + "lexical": "0.14.5", "mobx": "6.12", "mobx-react": "^7.5.0", - "monaco-editor": "^0.44.0", + "monaco-editor": "0.48.0", "mustache": "^4.2.0", "node-fetch": "^3.2.10", "overlayscrollbars": "^2.6.1", @@ -83,8 +83,8 @@ "babel-loader": "^9.1.3", "babel-plugin-jsx-control-statements": "^4.1.2", "copy-webpack-plugin": "^12.0.0", - "css-loader": "^6.7.1", - "electron": "^29.0.1", + "css-loader": "^7.1.0", + "electron": "^30.0.1", "electron-builder": "^24.13.3", "electron-builder-squirrel-windows": "^24.13.3", "file-loader": "^6.2.0", @@ -96,7 +96,7 @@ "prettier": "^2.8.8", "raw-loader": "^4.0.2", "react-split-it": "^2.0.0", - "style-loader": "^3.3.1", + "style-loader": "4.0.0", "typescript": "^5.0.0", "webpack": "^5.73.0", "webpack-bundle-analyzer": "^4.10.1", @@ -108,4 +108,4 @@ "scripts": { "postinstall": "electron-builder install-app-deps" } -} \ No newline at end of file +} diff --git a/src/plugins/code/code.tsx b/src/plugins/code/code.tsx index 8724b614e..21742e227 100644 --- a/src/plugins/code/code.tsx +++ b/src/plugins/code/code.tsx @@ -229,7 +229,6 @@ class SourceCodeRenderer extends React.Component< }, 2000); editor.onKeyDown((e: MonacoTypes.IKeyboardEvent) => { const waveEvent = adaptFromReactOrNativeKeyEvent(e.browserEvent); - console.log("keydown?", waveEvent); if ( GlobalModel.keybindManager.checkKeysPressed(waveEvent, [ "codeedit:save", @@ -568,7 +567,6 @@ class SourceCodeRenderer extends React.Component< ); const theme = `wave-theme-${GlobalModel.isDarkTheme.get() ? "dark" : "light"}`; - console.log("lineis selected:", lineIsSelected.get()); return (
diff --git a/yarn.lock b/yarn.lock index 365eced06..e3d1f8710 100644 --- a/yarn.lock +++ b/yarn.lock @@ -52,7 +52,12 @@ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.23.2.tgz#6a12ced93455827037bfb5ed8492820d60fc32cc" integrity sha512-0S9TQMmDHlqAZ2ITT95irXKfxN9bncq8ZCoJhun3nHL/lLUxd2NKBJYoNGWH7S0hz6fRQwWlAWn/ILM0C70KZQ== -"@babel/compat-data@^7.22.6", "@babel/compat-data@^7.23.3", "@babel/compat-data@^7.23.5", "@babel/compat-data@^7.24.1": +"@babel/compat-data@^7.22.6", "@babel/compat-data@^7.23.5", "@babel/compat-data@^7.24.4": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.24.4.tgz#6f102372e9094f25d908ca0d34fc74c74606059a" + integrity sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ== + +"@babel/compat-data@^7.23.3": version "7.24.1" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.24.1.tgz#31c1f66435f2a9c329bb5716a6d6186c516c3742" integrity sha512-Pc65opHDliVpRHuKfzI+gSA4zcgr65O4cl64fFJIWEEh8JoHIHh0Oez1Eo8Arz8zq/JhgKodQaxEwUPRtZylVA== @@ -79,17 +84,17 @@ semver "^6.3.1" "@babel/core@^7.18.2": - version "7.24.3" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.3.tgz#568864247ea10fbd4eff04dda1e05f9e2ea985c3" - integrity sha512-5FcvN1JHw2sHJChotgx8Ek0lyuh4kCKelgMTTqhYJJtloNvUfpAFMeNQUtdlIaktwrSV9LtCdqwk48wL2wBacQ== + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.4.tgz#1f758428e88e0d8c563874741bc4ffc4f71a4717" + integrity sha512-MBVlMXP+kkl5394RBLSxxk/iLTeVGuXTV3cIDXavPpMMqnSnt6apKgan/U8O3USWZCWZT/TbgfEpKa4uMgN4Dg== dependencies: "@ampproject/remapping" "^2.2.0" "@babel/code-frame" "^7.24.2" - "@babel/generator" "^7.24.1" + "@babel/generator" "^7.24.4" "@babel/helper-compilation-targets" "^7.23.6" "@babel/helper-module-transforms" "^7.23.3" - "@babel/helpers" "^7.24.1" - "@babel/parser" "^7.24.1" + "@babel/helpers" "^7.24.4" + "@babel/parser" "^7.24.4" "@babel/template" "^7.24.0" "@babel/traverse" "^7.24.1" "@babel/types" "^7.24.0" @@ -119,10 +124,10 @@ "@jridgewell/trace-mapping" "^0.3.17" jsesc "^2.5.1" -"@babel/generator@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.24.1.tgz#e67e06f68568a4ebf194d1c6014235344f0476d0" - integrity sha512-DfCRfZsBcrPEHUfuBMgbJ1Ut01Y/itOs+hY2nFLgqsqXd52/iSiVq5TITtUasIUgm+IIKdY2/1I7auiQOEeC9A== +"@babel/generator@^7.24.1", "@babel/generator@^7.24.4": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.24.4.tgz#1fc55532b88adf952025d5d2d1e71f946cb1c498" + integrity sha512-Xd6+v6SnjWVx/nus+y0l1sxMOTOMBkyL4+BIdbALyatQnAe/SRVjANeDPSCYaX+i1iJmuGSKf3Z+E+V/va1Hvw== dependencies: "@babel/types" "^7.24.0" "@jridgewell/gen-mapping" "^0.3.5" @@ -169,7 +174,7 @@ "@babel/helper-split-export-declaration" "^7.22.6" semver "^6.3.1" -"@babel/helper-create-class-features-plugin@^7.22.15", "@babel/helper-create-class-features-plugin@^7.23.5", "@babel/helper-create-class-features-plugin@^7.24.1": +"@babel/helper-create-class-features-plugin@^7.22.15", "@babel/helper-create-class-features-plugin@^7.23.5": version "7.24.1" resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.1.tgz#db58bf57137b623b916e24874ab7188d93d7f68f" integrity sha512-1yJa9dX9g//V6fDebXoEfEsxkZHk3Hcbm+zLhyu6qVgYFLvmTALTeV+jNU9e5RnYtioBrGEOdoI2joMSNQ/+aA== @@ -184,6 +189,21 @@ "@babel/helper-split-export-declaration" "^7.22.6" semver "^6.3.1" +"@babel/helper-create-class-features-plugin@^7.24.1", "@babel/helper-create-class-features-plugin@^7.24.4": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.4.tgz#c806f73788a6800a5cfbbc04d2df7ee4d927cce3" + integrity sha512-lG75yeuUSVu0pIcbhiYMXBXANHrpUPaOfu7ryAzskCgKUHuAxRQI5ssrtmF0X9UXldPlvT0XM/A4F44OXRt6iQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-member-expression-to-functions" "^7.23.0" + "@babel/helper-optimise-call-expression" "^7.22.5" + "@babel/helper-replace-supers" "^7.24.1" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + semver "^6.3.1" + "@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.22.15", "@babel/helper-create-regexp-features-plugin@^7.22.5": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz#5ee90093914ea09639b01c711db0d6775e558be1" @@ -204,10 +224,10 @@ lodash.debounce "^4.0.8" resolve "^1.14.2" -"@babel/helper-define-polyfill-provider@^0.6.1": - version "0.6.1" - resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.1.tgz#fadc63f0c2ff3c8d02ed905dcea747c5b0fb74fd" - integrity sha512-o7SDgTJuvx5vLKD6SFvkydkSMBvahDKGiNJzG22IZYXhiqoe9efY7zocICBgzHV4IRg5wdgl2nEL/tulKIEIbA== +"@babel/helper-define-polyfill-provider@^0.6.1", "@babel/helper-define-polyfill-provider@^0.6.2": + version "0.6.2" + resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.2.tgz#18594f789c3594acb24cfdb4a7f7b7d2e8bd912d" + integrity sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ== dependencies: "@babel/helper-compilation-targets" "^7.22.6" "@babel/helper-plugin-utils" "^7.22.5" @@ -363,10 +383,10 @@ "@babel/traverse" "^7.23.2" "@babel/types" "^7.23.0" -"@babel/helpers@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.24.1.tgz#183e44714b9eba36c3038e442516587b1e0a1a94" - integrity sha512-BpU09QqEe6ZCHuIHFphEFgvNSrubve1FtyMton26ekZ85gRGi6LrTF7zArARp2YvyFxloeiRmtSCq5sjh1WqIg== +"@babel/helpers@^7.24.4": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.24.4.tgz#dc00907fd0d95da74563c142ef4cd21f2cb856b6" + integrity sha512-FewdlZbSiwaVGlgT1DPANDuCHaDMiOo+D/IDYRFYjHOuv66xMSJ7fQwwODwRNAPkADIO/z1EoF/l2BCWlWABDw== dependencies: "@babel/template" "^7.24.0" "@babel/traverse" "^7.24.1" @@ -401,10 +421,18 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.6.tgz#ba1c9e512bda72a47e285ae42aff9d2a635a9e3b" integrity sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ== -"@babel/parser@^7.24.0", "@babel/parser@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.1.tgz#1e416d3627393fab1cb5b0f2f1796a100ae9133a" - integrity sha512-Zo9c7N3xdOIQrNip7Lc9wvRPzlRtovHVE4lkz8WEDr7uYh/GMQhSiIgFxGIArRHYdJE5kxtZjAf8rT0xhdLCzg== +"@babel/parser@^7.24.0", "@babel/parser@^7.24.1", "@babel/parser@^7.24.4": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.4.tgz#234487a110d89ad5a3ed4a8a566c36b9453e8c88" + integrity sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg== + +"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.24.4": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.24.4.tgz#6125f0158543fb4edf1c22f322f3db67f21cb3e1" + integrity sha512-qpl6vOOEEzTLLcsuqYYo8yDtrTocmu2xkGvgNebvPjT9DTtfFYGmgDqY+rBYXNlqL4s9qLDn6xkrJv4RxAPiTA== + dependencies: + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-plugin-utils" "^7.24.0" "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.22.15": version "7.23.3" @@ -721,10 +749,10 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-block-scoping@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.1.tgz#27af183d7f6dad890531256c7a45019df768ac1f" - integrity sha512-h71T2QQvDgM2SmT29UYU6ozjMlAt7s7CSs5Hvy8f8cf/GM/Z4a2zMfN+fjVGaieeCrXR3EdQl6C4gQG+OgmbKw== +"@babel/plugin-transform-block-scoping@^7.24.4": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.4.tgz#28f5c010b66fbb8ccdeef853bef1935c434d7012" + integrity sha512-nIFUZIpGKDf9O9ttyRXpHFpKC+X3Y5mtshZONuEUYBomAKoM4y029Jr+uB1bHGPhNmK8YXHevDtKDOLmtRrp6g== dependencies: "@babel/helper-plugin-utils" "^7.24.0" @@ -753,12 +781,12 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-class-static-block" "^7.14.5" -"@babel/plugin-transform-class-static-block@^7.24.1": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.1.tgz#4e37efcca1d9f2fcb908d1bae8b56b4b6e9e1cb6" - integrity sha512-FUHlKCn6J3ERiu8Dv+4eoz7w8+kFLSyeVG4vDAikwADGjUCoHw/JHokyGtr8OR4UjpwPVivyF+h8Q5iv/JmrtA== +"@babel/plugin-transform-class-static-block@^7.24.4": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.4.tgz#1a4653c0cf8ac46441ec406dece6e9bc590356a4" + integrity sha512-B8q7Pz870Hz/q9UgP8InNpY01CSLDSCyqX7zcRuv3FcPl87A2G17lASroHWaCtbdIcbYzOZ7kWmXFKbijMSmFg== dependencies: - "@babel/helper-create-class-features-plugin" "^7.24.1" + "@babel/helper-create-class-features-plugin" "^7.24.4" "@babel/helper-plugin-utils" "^7.24.0" "@babel/plugin-syntax-class-static-block" "^7.14.5" @@ -1512,14 +1540,15 @@ "@babel/helper-plugin-utils" "^7.24.0" "@babel/preset-env@^7.18.2": - version "7.24.3" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.24.3.tgz#f3f138c844ffeeac372597b29c51b5259e8323a3" - integrity sha512-fSk430k5c2ff8536JcPvPWK4tZDwehWLGlBp0wrsBUjZVdeQV6lePbwKWZaZfK2vnh/1kQX1PzAJWsnBmVgGJA== + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.24.4.tgz#46dbbcd608771373b88f956ffb67d471dce0d23b" + integrity sha512-7Kl6cSmYkak0FK/FXjSEnLJ1N9T/WA2RkMhu17gZ/dsxKJUuTYNIylahPTzqpLyJN4WhDif8X0XK1R8Wsguo/A== dependencies: - "@babel/compat-data" "^7.24.1" + "@babel/compat-data" "^7.24.4" "@babel/helper-compilation-targets" "^7.23.6" "@babel/helper-plugin-utils" "^7.24.0" "@babel/helper-validator-option" "^7.23.5" + "@babel/plugin-bugfix-firefox-class-in-computed-class-key" "^7.24.4" "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.24.1" "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.24.1" "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly" "^7.24.1" @@ -1546,9 +1575,9 @@ "@babel/plugin-transform-async-generator-functions" "^7.24.3" "@babel/plugin-transform-async-to-generator" "^7.24.1" "@babel/plugin-transform-block-scoped-functions" "^7.24.1" - "@babel/plugin-transform-block-scoping" "^7.24.1" + "@babel/plugin-transform-block-scoping" "^7.24.4" "@babel/plugin-transform-class-properties" "^7.24.1" - "@babel/plugin-transform-class-static-block" "^7.24.1" + "@babel/plugin-transform-class-static-block" "^7.24.4" "@babel/plugin-transform-classes" "^7.24.1" "@babel/plugin-transform-computed-properties" "^7.24.1" "@babel/plugin-transform-destructuring" "^7.24.1" @@ -1743,20 +1772,13 @@ resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310" integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== -"@babel/runtime@^7.12.5": +"@babel/runtime@^7.12.5", "@babel/runtime@^7.8.4": version "7.24.4" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.4.tgz#de795accd698007a66ba44add6cc86542aff1edd" integrity sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA== dependencies: regenerator-runtime "^0.14.0" -"@babel/runtime@^7.8.4": - version "7.24.1" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.1.tgz#431f9a794d173b53720e69a6464abc6f0e2a5c57" - integrity sha512-+BIznRzyqBf+2wCTxcKE3wDjfGeCoVE61KSHGpkzqrLi8qxqFwBeUFyId2cxkTmm55fzDGnm0+yCxaxygrLUnQ== - dependencies: - regenerator-runtime "^0.14.0" - "@babel/template@^7.22.15", "@babel/template@^7.24.0": version "7.24.0" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.0.tgz#c6a524aa93a4a05d66aaf31654258fae69d87d50" @@ -2029,193 +2051,206 @@ resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b" integrity sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A== -"@lexical/clipboard@0.14.3": - version "0.14.3" - resolved "https://registry.yarnpkg.com/@lexical/clipboard/-/clipboard-0.14.3.tgz#c759fddb384fbda7ecfd1a2e9dd9a304ee08ee76" - integrity sha512-kMasHJQCNSSdD6US8XF/GJEZAgdmIUIoqwcV/7Q8jVUICYT53bcr+Rh7RxL+1c7ZpJE2rXg5KTELsUPGjs0uwA== +"@lexical/clipboard@0.14.5": + version "0.14.5" + resolved "https://registry.yarnpkg.com/@lexical/clipboard/-/clipboard-0.14.5.tgz#81e39f02cbf252c43bb4286330b8c3063718a369" + integrity sha512-22xbagoQ8jiwImRtMcRl3+pojsiqF0cSfMXbjsHc5fPAq3ULf8OvAMkiSWEOxGQA6I6VIHX30+HtwZ7TgdPJ7A== dependencies: - "@lexical/html" "0.14.3" - "@lexical/list" "0.14.3" - "@lexical/selection" "0.14.3" - "@lexical/utils" "0.14.3" - lexical "0.14.3" + "@lexical/html" "0.14.5" + "@lexical/list" "0.14.5" + "@lexical/selection" "0.14.5" + "@lexical/utils" "0.14.5" + lexical "0.14.5" -"@lexical/code@0.14.3": - version "0.14.3" - resolved "https://registry.yarnpkg.com/@lexical/code/-/code-0.14.3.tgz#be7b7ebef5de9db3a88d939492084cda99f6f7c2" - integrity sha512-eBhs+TsJ5z7Vg/0e77bau86lN7R5nqO7effkPNNndn0XV2VSDpjMF+PTj4Cd1peenFlfqVivBr9gdewDrvPQng== +"@lexical/code@0.14.5": + version "0.14.5" + resolved "https://registry.yarnpkg.com/@lexical/code/-/code-0.14.5.tgz#7a26d76f636937cd31b92984d23b03c3390afb6e" + integrity sha512-eBZ5GMx2VDg7tC085qCD2+hzwGm5b6M/b4LXiPW0In6/SmJIDnEOppSz7jmHezWkLIGL2xK43gw1oqTY9igwug== dependencies: - "@lexical/utils" "0.14.3" - lexical "0.14.3" + "@lexical/utils" "0.14.5" + lexical "0.14.5" prismjs "^1.27.0" -"@lexical/dragon@0.14.3": - version "0.14.3" - resolved "https://registry.yarnpkg.com/@lexical/dragon/-/dragon-0.14.3.tgz#f9fb313daa04be4d04ead5fdd3cb46c54793955c" - integrity sha512-GTnt5a5Zs1f3q5Z9tC63VPzCFNAG+37ySHO+mQpVqlTsDmwSeJzFKGZyxq81tZXsKaXQZ4llc9K6I1f/XJoypw== +"@lexical/devtools-core@0.14.5": + version "0.14.5" + resolved "https://registry.yarnpkg.com/@lexical/devtools-core/-/devtools-core-0.14.5.tgz#4d9744652b75a6276552c3ca37fd8df7fbf798ed" + integrity sha512-4yTZ8Q9sDkvA5n96wEstru2NonAJ6T/zuSTcYizddwDJr56tzanSdJUFbEIG6G3ankqbKMRYNetupD/Ks3sXEg== dependencies: - lexical "0.14.3" + "@lexical/html" "0.14.5" + "@lexical/link" "0.14.5" + "@lexical/mark" "0.14.5" + "@lexical/table" "0.14.5" + "@lexical/utils" "0.14.5" + lexical "0.14.5" -"@lexical/hashtag@0.14.3": - version "0.14.3" - resolved "https://registry.yarnpkg.com/@lexical/hashtag/-/hashtag-0.14.3.tgz#c8ff80bfbbae901bbeb5e9685c27a0a10dac80d6" - integrity sha512-BlMhegitxNscJyM0QGjnzpt7QQaiftVf80dqfiVGdgFJi9hS4wrYEsPpA7jlsZG5Q46DSw/zMRp3tpHfdU6TCQ== +"@lexical/dragon@0.14.5": + version "0.14.5" + resolved "https://registry.yarnpkg.com/@lexical/dragon/-/dragon-0.14.5.tgz#86b409a3e8e5978e7670bf0b5b22638bde8d8287" + integrity sha512-p+rybaKGcxC8SCerQaMxRf+GcD+0YEXiv8WHx4DaxrTnHdn+8gapFpwe9Sxjmga/6BqeLa3rF/fis3zN3oyMlg== dependencies: - "@lexical/utils" "0.14.3" - lexical "0.14.3" + lexical "0.14.5" -"@lexical/history@0.14.3": - version "0.14.3" - resolved "https://registry.yarnpkg.com/@lexical/history/-/history-0.14.3.tgz#792252dc8932ff48139b9f4ca8d8c0e8b789cdde" - integrity sha512-I5Ssaz+uRYsFmqN5WfKCyTkPPV1CTnEQ21vuKp8PVI4hBdlIy5aJdeQXbQhg0BdCtQVSjpm7WRGMk5ATiAXLPw== +"@lexical/hashtag@0.14.5": + version "0.14.5" + resolved "https://registry.yarnpkg.com/@lexical/hashtag/-/hashtag-0.14.5.tgz#5c5324f47843c36c2614bd8c4ae465a3e3123497" + integrity sha512-jfIFZRm99EIAOsztgFBodyR8Rn/6TI7ee5HonBH6xFY439DheQxTaWDP0Y1SeL7iiu8d3ak2+AXvne1kBziR2A== dependencies: - "@lexical/utils" "0.14.3" - lexical "0.14.3" + "@lexical/utils" "0.14.5" + lexical "0.14.5" -"@lexical/html@0.14.3": - version "0.14.3" - resolved "https://registry.yarnpkg.com/@lexical/html/-/html-0.14.3.tgz#fe960afdc94232d5cec13a070bbb965b6d4bce66" - integrity sha512-ID4RdHdOXv2qIg6cqNhbYiqgcV5aEJFAV+zZ14CMpxPlW71tiRlmy/Pp4WqCFgjnZ2GZRq34+kag+cT2H69ILQ== +"@lexical/history@0.14.5": + version "0.14.5" + resolved "https://registry.yarnpkg.com/@lexical/history/-/history-0.14.5.tgz#3f17049bb0cb558198c3ce2bd84d4ed20ea81d16" + integrity sha512-Img2hPZ5QA0Sm2Y3HcHqK4qqluabhJrOm93vtOnk7eQU0JLTjFnprPIzRiKnNLpjbasJI6Be5z/3pI4LNIpIvw== dependencies: - "@lexical/selection" "0.14.3" - "@lexical/utils" "0.14.3" - lexical "0.14.3" + "@lexical/utils" "0.14.5" + lexical "0.14.5" -"@lexical/link@0.14.3": - version "0.14.3" - resolved "https://registry.yarnpkg.com/@lexical/link/-/link-0.14.3.tgz#7320f5eba82f451da9449a4b8c57fa60341938cb" - integrity sha512-txhuzcx2OfOtZ/fy9cgauDGW1gi2vSU0iQdde4i0UP2KK4ltioA9eFkjqAacGiPvwJ8w2CZV9q5Ck4DgFAKQ7w== +"@lexical/html@0.14.5": + version "0.14.5" + resolved "https://registry.yarnpkg.com/@lexical/html/-/html-0.14.5.tgz#7191626849583cf71899c8150114a4010414a1dd" + integrity sha512-HITDaKld+039OGsEbNpZ16ykmuspptRuaN8UFGfy4Y/isVzF3V3DmgXtIuUe47S4jaXVSbCZG18o//om1ytkTw== dependencies: - "@lexical/utils" "0.14.3" - lexical "0.14.3" + "@lexical/selection" "0.14.5" + "@lexical/utils" "0.14.5" + lexical "0.14.5" -"@lexical/list@0.14.3": - version "0.14.3" - resolved "https://registry.yarnpkg.com/@lexical/list/-/list-0.14.3.tgz#1b587e2c807465d1b50d0f09aedda58b7591a958" - integrity sha512-d9ZiEkZ34DpzBNq2GkedJpXF8sIxSQvHOGhNbVvTuBvgDcCwbmXL0KY4k+xu+jMScRO/3oR7C6YZpZT3GaUO+Q== +"@lexical/link@0.14.5": + version "0.14.5" + resolved "https://registry.yarnpkg.com/@lexical/link/-/link-0.14.5.tgz#3f20c50c04f59dffb12e7032080e0e1fff1c762d" + integrity sha512-NnMWRnMtigSBzM1zDSCzvwPPEOyelYy4Jlk9Iqq0KpRnzo248HAotMUTaYdMfWRgGIdPzflYZH5UhZJOAhH+qg== dependencies: - "@lexical/utils" "0.14.3" - lexical "0.14.3" + "@lexical/utils" "0.14.5" + lexical "0.14.5" -"@lexical/mark@0.14.3": - version "0.14.3" - resolved "https://registry.yarnpkg.com/@lexical/mark/-/mark-0.14.3.tgz#b4f1606b9887e7b7f5ead57e325b7317d4662c0c" - integrity sha512-HegYMuiCazmM4XXVUzteA5bOFEiWxeIZSMK98rCV7t5czYlQmgaV5PWIT5/wLnSgrJA6apa02JHLINE9CuUHlw== +"@lexical/list@0.14.5": + version "0.14.5" + resolved "https://registry.yarnpkg.com/@lexical/list/-/list-0.14.5.tgz#d92daadfffc6a514ea20efc1ff2d7abced49c333" + integrity sha512-kVD7FCbtbT5noydQQ6+AcBjkQS2cLb071uoDiKX+EHzDko08b8xdD63r1rqnj2kOvYlsNLVtf5yy6Cv4xNxWDw== dependencies: - "@lexical/utils" "0.14.3" - lexical "0.14.3" + "@lexical/utils" "0.14.5" + lexical "0.14.5" -"@lexical/markdown@0.14.3": - version "0.14.3" - resolved "https://registry.yarnpkg.com/@lexical/markdown/-/markdown-0.14.3.tgz#051ace914eafcc1c81e7834b09396a491a97be10" - integrity sha512-G97Twk0qq5Mkj7S95fFODN6D7nBZsHiXgd2QeCZQ+qbrItEsjEsM0vCtVBELpZzyl700ExfIJCA9eHrq28VNxw== +"@lexical/mark@0.14.5": + version "0.14.5" + resolved "https://registry.yarnpkg.com/@lexical/mark/-/mark-0.14.5.tgz#9902edd4a040ec1c1abbb2cbed3e0a04cf68a865" + integrity sha512-Z8YTHLrKpNHkCPATd3bzJhkbOnK0/gpZtjxphn+JvhgLOvmHIWCPS+HixQn10RJbcCAnja6QuhfsbgmP+c2eKA== dependencies: - "@lexical/code" "0.14.3" - "@lexical/link" "0.14.3" - "@lexical/list" "0.14.3" - "@lexical/rich-text" "0.14.3" - "@lexical/text" "0.14.3" - "@lexical/utils" "0.14.3" - lexical "0.14.3" + "@lexical/utils" "0.14.5" + lexical "0.14.5" -"@lexical/offset@0.14.3": - version "0.14.3" - resolved "https://registry.yarnpkg.com/@lexical/offset/-/offset-0.14.3.tgz#53dfa5ca3d32c3b02a036d43bf195cf37fbcf68c" - integrity sha512-xzyHLED9N3VPsLSpxs235W1xnh1xLl0SFqLLN9fkZs4fBLPtoPrzfYjjTMx6KgRPCa96GauAMsAaKn+JWHaD4g== +"@lexical/markdown@0.14.5": + version "0.14.5" + resolved "https://registry.yarnpkg.com/@lexical/markdown/-/markdown-0.14.5.tgz#c84f2cd7208d6739a32ac3bdbd02260eff222084" + integrity sha512-lLVU2Vaj0cvh8lv8NBuxIhMLGuSroXf6Ls2CH81nN+eafL5X8yKGb2ae9EUdKxxppBKzZJxfe+phUlLgAqgVeg== dependencies: - lexical "0.14.3" + "@lexical/code" "0.14.5" + "@lexical/link" "0.14.5" + "@lexical/list" "0.14.5" + "@lexical/rich-text" "0.14.5" + "@lexical/text" "0.14.5" + "@lexical/utils" "0.14.5" + lexical "0.14.5" -"@lexical/overflow@0.14.3": - version "0.14.3" - resolved "https://registry.yarnpkg.com/@lexical/overflow/-/overflow-0.14.3.tgz#addb108ca69c12f5058c0ba705e1b9f789112bed" - integrity sha512-2PabHT5vCtfN1lx2d3j1AW6naGJEcjLyUxEMrPzqNZ8IDGuLbD3uRi/wS8evmFLgKkF5mqRnPPlpwGbqGg+qUw== +"@lexical/offset@0.14.5": + version "0.14.5" + resolved "https://registry.yarnpkg.com/@lexical/offset/-/offset-0.14.5.tgz#bdba9f76bedb4e0a0fc1bf06850ffdbd09e8b239" + integrity sha512-oUBr7SQhLHc0/SImyizgBXnfvmmh41i1nnaWJ1kflgXRXPpW1OxnFsuVB8EGKrc5nToxfrcwl6iryuDyJVrQ7g== dependencies: - lexical "0.14.3" + lexical "0.14.5" -"@lexical/plain-text@0.14.3": - version "0.14.3" - resolved "https://registry.yarnpkg.com/@lexical/plain-text/-/plain-text-0.14.3.tgz#9039f8635b0d79d32d3af0f95f2bbaa5e0256f86" - integrity sha512-Ct3sQmhc34Iuj0YWT5dlLzTcuCLAMx7uaLKb0lxb7A6bcUBPfC1eBv2KtILZ9eW/GEUCMTqYEnmixTY7vPR9AA== +"@lexical/overflow@0.14.5": + version "0.14.5" + resolved "https://registry.yarnpkg.com/@lexical/overflow/-/overflow-0.14.5.tgz#a96cb614244a4638032f048528af5373426a8417" + integrity sha512-mZSQID6GTxSrnx+SeUqmyB8OZUTHolXqm0Ck2L27fRIIUQGZTXR9+CrV4+t2jNFK3brTo2POB95xwBq+O463hA== dependencies: - "@lexical/clipboard" "0.14.3" - "@lexical/selection" "0.14.3" - "@lexical/utils" "0.14.3" - lexical "0.14.3" + lexical "0.14.5" + +"@lexical/plain-text@0.14.5": + version "0.14.5" + resolved "https://registry.yarnpkg.com/@lexical/plain-text/-/plain-text-0.14.5.tgz#71cd8309a43fcf727892a3e78edd52ea796299bf" + integrity sha512-i0NiJ1RZ/990nArZcKcQOG+0SxO8ErUDT+QDCGOoGGqG02pQf+UuiLVWW9GdD+5unA7eRQDUza10MMyzsV+MJA== + dependencies: + "@lexical/clipboard" "0.14.5" + "@lexical/selection" "0.14.5" + "@lexical/utils" "0.14.5" + lexical "0.14.5" "@lexical/react@^0.14.3": - version "0.14.3" - resolved "https://registry.yarnpkg.com/@lexical/react/-/react-0.14.3.tgz#eacbfc6bd8237aaafbc73daa655c0585b44c8256" - integrity sha512-sUgF7dStJTYvkS14QzlpB5XJ5p498JDSEBSADRsf0KOJsTINAQhh27vXuS8/I2FH3FanonH/RrLwSildL/FnzA== + version "0.14.5" + resolved "https://registry.yarnpkg.com/@lexical/react/-/react-0.14.5.tgz#ff24ad5782876b5499f48d711965b27339dd4265" + integrity sha512-dn7J07nxG6CZqm5jhLjhkQlJWMQrdm4BGTEF6/MYog5uUUwqDwBdVnZ3hwadibupAmNT7+Xia+4vrp0oJWM1lQ== dependencies: - "@lexical/clipboard" "0.14.3" - "@lexical/code" "0.14.3" - "@lexical/dragon" "0.14.3" - "@lexical/hashtag" "0.14.3" - "@lexical/history" "0.14.3" - "@lexical/link" "0.14.3" - "@lexical/list" "0.14.3" - "@lexical/mark" "0.14.3" - "@lexical/markdown" "0.14.3" - "@lexical/overflow" "0.14.3" - "@lexical/plain-text" "0.14.3" - "@lexical/rich-text" "0.14.3" - "@lexical/selection" "0.14.3" - "@lexical/table" "0.14.3" - "@lexical/text" "0.14.3" - "@lexical/utils" "0.14.3" - "@lexical/yjs" "0.14.3" - lexical "0.14.3" + "@lexical/clipboard" "0.14.5" + "@lexical/code" "0.14.5" + "@lexical/devtools-core" "0.14.5" + "@lexical/dragon" "0.14.5" + "@lexical/hashtag" "0.14.5" + "@lexical/history" "0.14.5" + "@lexical/link" "0.14.5" + "@lexical/list" "0.14.5" + "@lexical/mark" "0.14.5" + "@lexical/markdown" "0.14.5" + "@lexical/overflow" "0.14.5" + "@lexical/plain-text" "0.14.5" + "@lexical/rich-text" "0.14.5" + "@lexical/selection" "0.14.5" + "@lexical/table" "0.14.5" + "@lexical/text" "0.14.5" + "@lexical/utils" "0.14.5" + "@lexical/yjs" "0.14.5" + lexical "0.14.5" react-error-boundary "^3.1.4" -"@lexical/rich-text@0.14.3": - version "0.14.3" - resolved "https://registry.yarnpkg.com/@lexical/rich-text/-/rich-text-0.14.3.tgz#92a26e5092af387c550c094df7a8353a8318f95b" - integrity sha512-o8wGvRDyPSRcfb6bauF5lzK5u/kzCW+hAQq0ExM1e8p4GHDb0vwz9DA6NH5D0BPHb2fUgknwClHOoJX95WUA8A== +"@lexical/rich-text@0.14.5": + version "0.14.5" + resolved "https://registry.yarnpkg.com/@lexical/rich-text/-/rich-text-0.14.5.tgz#8d475b9b8ec6fc48452f19e118ab59e401dab014" + integrity sha512-hLZ8oBrc4ZuYK3KbviV0pUW1R9CvsN8dLTOdYpW5hxvCMDI6UFrtRmaURQY96M7JSYQsDMrtyKyFuID3RwOR1w== dependencies: - "@lexical/clipboard" "0.14.3" - "@lexical/selection" "0.14.3" - "@lexical/utils" "0.14.3" - lexical "0.14.3" + "@lexical/clipboard" "0.14.5" + "@lexical/selection" "0.14.5" + "@lexical/utils" "0.14.5" + lexical "0.14.5" -"@lexical/selection@0.14.3": - version "0.14.3" - resolved "https://registry.yarnpkg.com/@lexical/selection/-/selection-0.14.3.tgz#79b81dd8a9afeb442e180644dcde82d708b2c069" - integrity sha512-43EmqG6flLqFJJNZ7GCxFlx3qXy7osB3AQBgxKTthWtQeBrJPdgacctL1jhO7etTIQWP5C1DExy3opDLVKyDjg== +"@lexical/selection@0.14.5": + version "0.14.5" + resolved "https://registry.yarnpkg.com/@lexical/selection/-/selection-0.14.5.tgz#716c00267cd322b272128579ff295141b676abe3" + integrity sha512-uK4X1wOSnlq2xvIIludnPb6i+grtV4IR7Y1Dg7ZGFJfk1q5FWuS9iA3iVjZbSiehgbZef5nDCPRez9WN/F5krA== dependencies: - lexical "0.14.3" + lexical "0.14.5" -"@lexical/table@0.14.3": - version "0.14.3" - resolved "https://registry.yarnpkg.com/@lexical/table/-/table-0.14.3.tgz#84df456c0565db2d18ef4e5fe2aa65cc1bff1b19" - integrity sha512-9btpU2lfAE34ucIqlMu5RiSVlxREXY7Zp+s26oFsXNoNPhW57iND96TrqwYo9FJl/6zXXfvqYxnUEcUD2dLgwQ== +"@lexical/table@0.14.5": + version "0.14.5" + resolved "https://registry.yarnpkg.com/@lexical/table/-/table-0.14.5.tgz#1baf71d0919f7b498565bcba78e0ea0418b30ba0" + integrity sha512-K+R1w6KL9jIf9gKcXP1x3gPQxaVf+u9rjidKAZptgZYH/O4aLnE7MR+nrLFUYYw0NPOOgYTFxJOk9OW500TtKA== dependencies: - "@lexical/utils" "0.14.3" - lexical "0.14.3" + "@lexical/utils" "0.14.5" + lexical "0.14.5" -"@lexical/text@0.14.3": - version "0.14.3" - resolved "https://registry.yarnpkg.com/@lexical/text/-/text-0.14.3.tgz#8f868a6954e566db6348d541b68d024a739a3b88" - integrity sha512-7+B9KkA37iHTlPqt6GHdfBIoaA9dQfhKrQNP9+422/CO/adCru4S94yNxiHXFq7iCvgucfuFop9M8jOfqLQbBQ== +"@lexical/text@0.14.5": + version "0.14.5" + resolved "https://registry.yarnpkg.com/@lexical/text/-/text-0.14.5.tgz#4b4c4ab536f4f4db775ff3f5d8066ff8fdb03be3" + integrity sha512-qcoORBgy3MD1xmmm5hE248HmL3BJLU/+qGvJz7Ei/9Fh5p2+PIYoL90KRcOP6Pp3pDs3ocydb+YcCxLg9L+OOQ== dependencies: - lexical "0.14.3" + lexical "0.14.5" -"@lexical/utils@0.14.3": - version "0.14.3" - resolved "https://registry.yarnpkg.com/@lexical/utils/-/utils-0.14.3.tgz#dc5fe87282f77ad40b46b5d4aee148a9843ad939" - integrity sha512-coqG2AO7QhJCM0xBlYvtETjl0il9u4HQRuc8ye3j8jMfNadVvVVWO3Fodmm/8FTPyJuxIij1Ruma9zqhlAbN6Q== +"@lexical/utils@0.14.5": + version "0.14.5" + resolved "https://registry.yarnpkg.com/@lexical/utils/-/utils-0.14.5.tgz#a77db6c735303089769fd43165c1c198fdb33924" + integrity sha512-KoO63Y5lsgMxcLLIUC/Gwiof4BoKODY5i0NGUhUez/zGq4vCdXp+1DVJF7gmmvg9/vx0J16IrTcr/SAoAnhSFg== dependencies: - "@lexical/list" "0.14.3" - "@lexical/selection" "0.14.3" - "@lexical/table" "0.14.3" - lexical "0.14.3" + "@lexical/list" "0.14.5" + "@lexical/selection" "0.14.5" + "@lexical/table" "0.14.5" + lexical "0.14.5" -"@lexical/yjs@0.14.3": - version "0.14.3" - resolved "https://registry.yarnpkg.com/@lexical/yjs/-/yjs-0.14.3.tgz#ac3773c4db2f589e5940ef9a409cc6f11e4c1fab" - integrity sha512-Ju+PQJg4NjQoNzfPlQKa6A71sjgGWj5lL4cbe+4xlNoknfK3NApVeznOi3xAM7rUCr6fPBAjzF9/uwfMXR451g== +"@lexical/yjs@0.14.5": + version "0.14.5" + resolved "https://registry.yarnpkg.com/@lexical/yjs/-/yjs-0.14.5.tgz#e75979ac8b0aae6fd9f6106806bf9ca8a6f6973a" + integrity sha512-Y9dMA/B0tlkQLRUmwnfkPKOOaFQSFSp257pDoQr5Gnpx1OjZWGbbesPn4h2dFhGeLme41nznGZNwxR5nH6lGaw== dependencies: - "@lexical/offset" "0.14.3" - lexical "0.14.3" + "@lexical/offset" "0.14.5" + lexical "0.14.5" "@malept/cross-spawn-promise@^1.1.0": version "1.1.1" @@ -2302,10 +2337,10 @@ resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== -"@polka/url@^1.0.0-next.20": - version "1.0.0-next.23" - resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.23.tgz#498e41218ab3b6a1419c735e5c6ae2c5ed609b6c" - integrity sha512-C16M+IYz0rgRhWZdCmK+h58JMv8vijAA61gmz2rspCSwKwzBebpdcsiUmwrtJRdphuY30i6BSLEOP8ppbNLyLg== +"@polka/url@^1.0.0-next.24": + version "1.0.0-next.25" + resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.25.tgz#f077fdc0b5d0078d30893396ff4827a13f99e817" + integrity sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ== "@sindresorhus/is@^4.0.0": version "4.6.0" @@ -2441,23 +2476,23 @@ integrity sha512-S+DsD/qDqp50Z4dqt5tZFMWA3sRu0OOT/grMQuq/z/52jPEKJB+b9t+YSH8Ms55vCJOJ0DxuYldJpYrJLMG5ew== "@tanstack/match-sorter-utils@^8.8.4": - version "8.11.8" - resolved "https://registry.yarnpkg.com/@tanstack/match-sorter-utils/-/match-sorter-utils-8.11.8.tgz#9132c2a21cf18ca2f0071b604ddadb7a66e73367" - integrity sha512-3VPh0SYMGCa5dWQEqNab87UpCMk+ANWHDP4ALs5PeEW9EpfTAbrezzaOk/OiM52IESViefkoAOYuxdoa04p6aA== + version "8.15.1" + resolved "https://registry.yarnpkg.com/@tanstack/match-sorter-utils/-/match-sorter-utils-8.15.1.tgz#715e028ff43cf79ece10bd5a757047a1016c3bba" + integrity sha512-PnVV3d2poenUM31ZbZi/yXkBu3J7kd5k2u51CGwwNojag451AjTH9N6n41yjXz2fpLeewleyLBmNS6+HcGDlXw== dependencies: - remove-accents "0.4.2" + remove-accents "0.5.0" "@tanstack/react-table@^8.10.3": - version "8.15.0" - resolved "https://registry.yarnpkg.com/@tanstack/react-table/-/react-table-8.15.0.tgz#668ceb9f396d33409165d5b9bee7734b657061c6" - integrity sha512-8K4RSROUtXUtfiezV6Ehl8z99axFrkQnxXi0vjWBJv3Tsm5x4EyrgXI7d2tOOMoANykKZLB6S1sGZGemoMRt7Q== + version "8.16.0" + resolved "https://registry.yarnpkg.com/@tanstack/react-table/-/react-table-8.16.0.tgz#92151210ff99d6925353d7a2205735d9c31af48c" + integrity sha512-rKRjnt8ostqN2fercRVOIH/dq7MAmOENCMvVlKx6P9Iokhh6woBGnIZEkqsY/vEJf1jN3TqLOb34xQGLVRuhAg== dependencies: - "@tanstack/table-core" "8.14.0" + "@tanstack/table-core" "8.16.0" -"@tanstack/table-core@8.14.0": - version "8.14.0" - resolved "https://registry.yarnpkg.com/@tanstack/table-core/-/table-core-8.14.0.tgz#4c9fe8b74949bb0ffe4ac8b1937beaff1f3a19de" - integrity sha512-wDhpKJahGHWhmRt4RxtV3pES63CoeadljGWS/xeS9OJr1HBl2NB+OO44ht3sxDH5j5TRDAbQzC0NvSlsUfn7lQ== +"@tanstack/table-core@8.16.0": + version "8.16.0" + resolved "https://registry.yarnpkg.com/@tanstack/table-core/-/table-core-8.16.0.tgz#7b58018dd3cec8e0015fe22d6bb24d18d33c891f" + integrity sha512-dCG8vQGk4js5v88/k83tTedWOwjGnIyONrKpHpfmSJB8jwFHl8GSu1sBBxbtACVAPtAQgwNxl0rw1d3RqRM1Tg== "@tootallnate/once@2": version "2.0.0" @@ -2650,7 +2685,7 @@ dependencies: "@types/node" "*" -"@types/node@*", "@types/node@^20.11.0", "@types/node@^20.9.0": +"@types/node@*", "@types/node@^20.9.0": version "20.11.30" resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.30.tgz#9c33467fc23167a347e73834f788f4b9f399d66f" integrity sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw== @@ -2664,6 +2699,13 @@ dependencies: undici-types "~5.26.4" +"@types/node@^20.11.0": + version "20.12.7" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.12.7.tgz#04080362fa3dd6c5822061aa3124f5c152cff384" + integrity sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg== + dependencies: + undici-types "~5.26.4" + "@types/papaparse@^5.3.10": version "5.3.14" resolved "https://registry.yarnpkg.com/@types/papaparse/-/papaparse-5.3.14.tgz#345cc2a675a90106ff1dc33b95500dfb30748031" @@ -2695,9 +2737,9 @@ integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== "@types/react@^18.0.12": - version "18.2.73" - resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.73.tgz#0579548ad122660d99e00499d22e33b81e73ed94" - integrity sha512-XcGdod0Jjv84HOC7N5ziY3x+qL0AfmubvKOZ9hJjJ2yd5EE+KYjWhdOjt387e9HPheHkdggF9atTifMRtyAaRA== + version "18.3.0" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.0.tgz#2e6ac50dea2f68f774b20f1bd536ef82365cd64a" + integrity sha512-DiUcKjzE6soLyln8NNZmyhcQjVv+WsUIFSqetMN0p8927OztKT4VTfFTqsbAi5oAGIcgOmOajlfBqyptDDjZRw== dependencies: "@types/prop-types" "*" csstype "^3.0.2" @@ -2979,16 +3021,11 @@ acorn-import-assertions@^1.9.0: integrity sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA== acorn-walk@^8.0.0: - version "8.2.0" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" - integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== + version "8.3.2" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.2.tgz#7703af9415f1b6db9315d6895503862e231d34aa" + integrity sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A== -acorn@^8.0.4: - version "8.10.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5" - integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== - -acorn@^8.7.1, acorn@^8.8.2: +acorn@^8.0.4, acorn@^8.7.1, acorn@^8.8.2: version "8.11.3" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== @@ -3260,12 +3297,12 @@ babel-plugin-jsx-control-statements@^4.1.2: "@babel/core" "^7.1.2" babel-plugin-polyfill-corejs2@^0.4.10: - version "0.4.10" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.10.tgz#276f41710b03a64f6467433cab72cbc2653c38b1" - integrity sha512-rpIuu//y5OX6jVU+a5BCn1R5RSZYWAl2Nar76iwaOdycqb6JPxediskWFMMl7stfwNJR4b7eiQvh5fB5TEQJTQ== + version "0.4.11" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz#30320dfe3ffe1a336c15afdcdafd6fd615b25e33" + integrity sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q== dependencies: "@babel/compat-data" "^7.22.6" - "@babel/helper-define-polyfill-provider" "^0.6.1" + "@babel/helper-define-polyfill-provider" "^0.6.2" semver "^6.3.1" babel-plugin-polyfill-corejs2@^0.4.5: @@ -3301,11 +3338,11 @@ babel-plugin-polyfill-regenerator@^0.5.2: "@babel/helper-define-polyfill-provider" "^0.4.3" babel-plugin-polyfill-regenerator@^0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.1.tgz#4f08ef4c62c7a7f66a35ed4c0d75e30506acc6be" - integrity sha512-JfTApdE++cgcTWjsiCQlLyFBMbTUft9ja17saCc93lgV33h4tuCVj7tlvu//qpLwaG+3yEz7/KhahGrUMkVq9g== + version "0.6.2" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.2.tgz#addc47e240edd1da1058ebda03021f382bba785e" + integrity sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg== dependencies: - "@babel/helper-define-polyfill-provider" "^0.6.1" + "@babel/helper-define-polyfill-provider" "^0.6.2" bail@^2.0.0: version "2.0.2" @@ -3573,9 +3610,9 @@ camelcase@^6.2.0: integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== caniuse-lite@^1.0.30001587: - version "1.0.30001600" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001600.tgz#93a3ee17a35aa6a9f0c6ef1b2ab49507d1ab9079" - integrity sha512-+2S9/2JFhYmYaDpZvo0lKkfvuKIglrx68MwOBqMGHhQsNkLjB5xtc/TGoEPs+MxjSyN/72qer2g97nzR641mOQ== + version "1.0.30001612" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001612.tgz#d34248b4ec1f117b70b24ad9ee04c90e0b8a14ae" + integrity sha512-lFgnZ07UhaCcsSZgWW0K5j4e69dK1u/ltrL9lTUiFOwNHs12S3UMIEYgBV0Z6C6hRDev7iRnMzzYmKabYdXF9g== ccount@^2.0.0: version "2.0.1" @@ -3923,7 +3960,14 @@ copy-webpack-plugin@^12.0.0: schema-utils "^4.2.0" serialize-javascript "^6.0.2" -core-js-compat@^3.31.0, core-js-compat@^3.33.1, core-js-compat@^3.36.1: +core-js-compat@^3.31.0, core-js-compat@^3.36.1: + version "3.37.0" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.37.0.tgz#d9570e544163779bb4dff1031c7972f44918dc73" + integrity sha512-vYq4L+T8aS5UuFg4UwDhc7YNRWVeVZwltad9C/jV3R2LgVOpS9BDr7l/WL6BN0dbV3k1XejPTHqqEzJgsa0frA== + dependencies: + browserslist "^4.23.0" + +core-js-compat@^3.33.1: version "3.36.1" resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.36.1.tgz#1818695d72c99c25d621dca94e6883e190cea3c8" integrity sha512-Dk997v9ZCt3X/npqzyGdTlq6t7lDBhZwGvV94PKzDArjp7BTRm7WlDAXYd/OWdeFHO8OChQYRJNJvUCqCbrtKA== @@ -3984,16 +4028,16 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.3: shebang-command "^2.0.0" which "^2.0.1" -css-loader@^6.7.1: - version "6.10.0" - resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.10.0.tgz#7c172b270ec7b833951b52c348861206b184a4b7" - integrity sha512-LTSA/jWbwdMlk+rhmElbDR2vbtQoTBPr7fkJE+mxrHj+7ru0hUmHafDRzWIjIHTwpitWVaqY2/UWGRca3yUgRw== +css-loader@^7.1.0: + version "7.1.1" + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-7.1.1.tgz#de4163c0cb765c03d7957eb9e0a49c7f354948c7" + integrity sha512-OxIR5P2mjO1PSXk44bWuQ8XtMK4dpEqpIyERCx3ewOo3I8EmbcxMPUc5ScLtQfgXtOojoMv57So4V/C02HQLsw== dependencies: icss-utils "^5.1.0" postcss "^8.4.33" - postcss-modules-extract-imports "^3.0.0" - postcss-modules-local-by-default "^4.0.4" - postcss-modules-scope "^3.1.1" + postcss-modules-extract-imports "^3.1.0" + postcss-modules-local-by-default "^4.0.5" + postcss-modules-scope "^3.2.0" postcss-modules-values "^4.0.0" postcss-value-parser "^4.2.0" semver "^7.5.4" @@ -4278,9 +4322,9 @@ domhandler@^5.0.2, domhandler@^5.0.3: domelementtype "^2.3.0" dompurify@^3.0.2: - version "3.0.11" - resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.0.11.tgz#c163f5816eaac6aeef35dae2b77fca0504564efe" - integrity sha512-Fan4uMuyB26gFV3ovPoEoQbxRRPfTu3CvImyZnhGq5fsIEO+gEFLp45ISFt+kQBWsK5ulDdT0oV28jS1UrwQLg== + version "3.1.0" + resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.1.0.tgz#8c6b9fe986969a33aa4686bd829cbe8e14dd9445" + integrity sha512-yoU4rhgPKCo+p5UrWWWNKiIq+ToGqmVVhk0PmMYBK4kRsR3/qhemNFL8f6CFmBd4gMwm3F4T7HBoydP5uY07fA== domutils@^3.0.1: version "3.1.0" @@ -4379,9 +4423,9 @@ electron-squirrel-startup@^1.0.0: debug "^2.2.0" electron-to-chromium@^1.4.668: - version "1.4.717" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.717.tgz#99db370cae8cd090d5b01f8748e9ad369924d0f8" - integrity sha512-6Fmg8QkkumNOwuZ/5mIbMU9WI3H2fmn5ajcVya64I5Yr5CcNmO7vcLt0Y7c96DCiMO5/9G+4sI2r6eEvdg1F7A== + version "1.4.749" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.749.tgz#9869e2e258141da26a2272b58264584c3461279d" + integrity sha512-LRMMrM9ITOvue0PoBrvNIraVmuDbJV5QC9ierz/z5VilMdPOVMjOtpICNld3PuXuTZ3CHH/UPxX9gHhAPwi+0Q== electron-updater@^6.1.8: version "6.1.8" @@ -4406,10 +4450,10 @@ electron@*: "@types/node" "^18.11.18" extract-zip "^2.0.1" -electron@^29.0.1: - version "29.1.5" - resolved "https://registry.yarnpkg.com/electron/-/electron-29.1.5.tgz#b745b4d201c1ac9f84d6aa034126288dde34d5a1" - integrity sha512-1uWGRw/ffA62lcrklxGUgVxVtOHojsg/nwsYr+/F9cVjipZJn8iPv/ABGIIexhmUqWcho8BqfTJ4osCBa29gBg== +electron@^30.0.1: + version "30.0.1" + resolved "https://registry.yarnpkg.com/electron/-/electron-30.0.1.tgz#2caf0eb7ed591b9b9842b522421bcae3aa8293d6" + integrity sha512-iwxkI/n2wBd29NH7TH0ZY8aWGzCoKpzJz+D10u7aGSJi1TV6d4MSM3rWyKvT/UkAHkTKOEgYfUyCa2vWQm8L0g== dependencies: "@electron/get" "^2.0.0" "@types/node" "^20.9.0" @@ -5557,11 +5601,6 @@ is-plain-object@^2.0.4: dependencies: isobject "^3.0.1" -is-plain-object@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" - integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== - is-stream@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" @@ -5769,10 +5808,10 @@ less@^4.1.2: needle "^3.1.0" source-map "~0.6.0" -lexical@0.14.3, lexical@^0.14.3: - version "0.14.3" - resolved "https://registry.yarnpkg.com/lexical/-/lexical-0.14.3.tgz#81c41a4c585100192f6d330e81cc6013bf326f5e" - integrity sha512-LaWSKj6OpvJ+bdfQA2AybEzho0YoWfAdRGkuCtPNYd/uf7IHyoEwCFQsIBvWCQF23saDgE1NONR4uiwl6iaJ9g== +lexical@0.14.5: + version "0.14.5" + resolved "https://registry.yarnpkg.com/lexical/-/lexical-0.14.5.tgz#4c9578e97ad239411a12cc92eff3108105a72316" + integrity sha512-ouV7Gyr9+3WT3WTrCgRAD3iZnlJWfs2/kBl2x3J2Q3X9uCWJn/zn21fQ8G1EUHlu0dvXPBmdk9hXb/FjTClt6Q== lines-and-columns@^1.1.6: version "1.2.4" @@ -6509,9 +6548,9 @@ mimic-response@^3.1.0: integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== mini-css-extract-plugin@^2.6.0: - version "2.8.1" - resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-2.8.1.tgz#75245f3f30ce3a56dbdd478084df6fe475f02dc7" - integrity sha512-/1HDlyFRxWIZPI1ZpgqlZ8jMw/1Dp/dl3P0L1jtZ+zVcHqwPhGwaJwKL00WVgfnBy6PWCde9W65or7IIETImuA== + version "2.9.0" + resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.0.tgz#c73a1327ccf466f69026ac22a8e8fd707b78a235" + integrity sha512-Zs1YsZVfemekSZG+44vBsYTLQORkPMwnlv+aehcxK/NLKC+EGhDB39/YePYYqx/sTk6NnYpuqikhSn7+JIevTA== dependencies: schema-utils "^4.0.0" tapable "^2.2.1" @@ -6636,19 +6675,19 @@ mobx-react@^7.5.0: mobx-react-lite "^3.4.0" mobx@6.12: - version "6.12.1" - resolved "https://registry.yarnpkg.com/mobx/-/mobx-6.12.1.tgz#78a32cfe6e1a2339426e284f8c5d90b0a5396fc5" - integrity sha512-HN5/fmWCnePVI4u3Y487x3G0LIQ+qnQjowylhVxXPvBmbwottKm8R7GrsYAqljzekZUj+LoBcAUp3l8/3EPCGQ== + version "6.12.3" + resolved "https://registry.yarnpkg.com/mobx/-/mobx-6.12.3.tgz#b6a0fde4268116be602d50bffb32f1b90a8fb077" + integrity sha512-c8NKkO4R2lShkSXZ2Ongj1ycjugjzFFo/UswHBnS62y07DMcTc9Rvo03/3nRyszIvwPNljlkd4S828zIBv/piw== -monaco-editor@^0.44.0: - version "0.44.0" - resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.44.0.tgz#3c0fe3655923bbf7dd647057302070b5095b6c59" - integrity sha512-5SmjNStN6bSuSE5WPT2ZV+iYn1/yI9sd4Igtk23ChvqB7kDk9lZbB9F5frsuvpB+2njdIeGGFf2G4gbE6rCC9Q== +monaco-editor@0.48.0: + version "0.48.0" + resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.48.0.tgz#9e54541bbe0ba3f2bb238477d5b981a282205ea0" + integrity sha512-goSDElNqFfw7iDHMg8WDATkfcyeLTNpBHQpO8incK6p5qZt5G/1j41X0xdGzpIkGojGXM+QiRQyLjnfDVvrpwA== -mrmime@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/mrmime/-/mrmime-1.0.1.tgz#5f90c825fad4bdd41dc914eff5d1a8cfdaf24f27" - integrity sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw== +mrmime@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mrmime/-/mrmime-2.0.0.tgz#151082a6e06e59a9a39b46b3e14d5cfe92b3abb4" + integrity sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw== ms@2.0.0: version "2.0.0" @@ -6895,14 +6934,14 @@ ora@^5.1.0: wcwidth "^1.0.1" overlayscrollbars-react@^0.5.5: - version "0.5.5" - resolved "https://registry.yarnpkg.com/overlayscrollbars-react/-/overlayscrollbars-react-0.5.5.tgz#983fcff9928cd12fff78351573fdb79f463ea414" - integrity sha512-PakK1QEV/PAi4XniiTykcSeyoBmfDvgv2uBQ290IaY5ThrwvWg3Zk3Z39hosJYkyrS4mJ0zuIWtlHX4AKd2nZQ== + version "0.5.6" + resolved "https://registry.yarnpkg.com/overlayscrollbars-react/-/overlayscrollbars-react-0.5.6.tgz#e9779f9fc2c1a3288570a45c83f8e42518bfb8c1" + integrity sha512-E5To04bL5brn9GVCZ36SnfGanxa2I2MDkWoa4Cjo5wol7l+diAgi4DBc983V7l2nOk/OLJ6Feg4kySspQEGDBw== overlayscrollbars@^2.6.1: - version "2.6.1" - resolved "https://registry.yarnpkg.com/overlayscrollbars/-/overlayscrollbars-2.6.1.tgz#50210de1d869ed0bf027de11cf705f5b9591a097" - integrity sha512-V+ZAqWMYMyGBJNRDEcdRC7Ch+WT9RBx9hY8bfJSMyFObQeJoecs1Vqg7ZAzBVcpN6sCUXFAZldCbeySwmmD0RA== + version "2.7.3" + resolved "https://registry.yarnpkg.com/overlayscrollbars/-/overlayscrollbars-2.7.3.tgz#68c18096c563391e37a2c7ca7463fbd03a945271" + integrity sha512-HmNo8RPtuGUjBhUbVpZBHH7SHci5iSAdg5zSekCZVsjzaM6z8MIr3F9RXrzf4y7m+fOY0nx0+y0emr1fqQmfoA== p-cancelable@^2.0.0: version "2.1.1" @@ -7104,24 +7143,24 @@ portfinder@^1.0.28: debug "^3.2.7" mkdirp "^0.5.6" -postcss-modules-extract-imports@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz#cda1f047c0ae80c97dbe28c3e76a43b88025741d" - integrity sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw== +postcss-modules-extract-imports@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz#b4497cb85a9c0c4b5aabeb759bb25e8d89f15002" + integrity sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q== -postcss-modules-local-by-default@^4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.4.tgz#7cbed92abd312b94aaea85b68226d3dec39a14e6" - integrity sha512-L4QzMnOdVwRm1Qb8m4x8jsZzKAaPAgrUF1r/hjDR2Xj7R+8Zsf97jAlSQzWtKx5YNiNGN8QxmPFIc/sh+RQl+Q== +postcss-modules-local-by-default@^4.0.5: + version "4.0.5" + resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.5.tgz#f1b9bd757a8edf4d8556e8d0f4f894260e3df78f" + integrity sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw== dependencies: icss-utils "^5.0.0" postcss-selector-parser "^6.0.2" postcss-value-parser "^4.1.0" -postcss-modules-scope@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.1.1.tgz#32cfab55e84887c079a19bbb215e721d683ef134" - integrity sha512-uZgqzdTleelWjzJY+Fhti6F3C9iF1JR/dODLs/JDefozYcKTBCdD8BIl6nNPbTbcLnGrk56hzwZC2DaGNvYjzA== +postcss-modules-scope@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.2.0.tgz#a43d28289a169ce2c15c00c4e64c0858e43457d5" + integrity sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ== dependencies: postcss-selector-parser "^6.0.4" @@ -7282,12 +7321,12 @@ raw-loader@^4.0.2: schema-utils "^3.0.0" react-dom@^18.1.0: - version "18.2.0" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d" - integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g== + version "18.3.0" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.3.0.tgz#98a3a1cc4e471d517c2a084f38ab1d58d02cada7" + integrity sha512-zaKdLBftQJnvb7FtDIpZtsAIb2MZU087RM8bRDZU8LVCCFYjPTsDZJNFUWPcVz3HFSN1n/caxi0ca4B/aaVQGQ== dependencies: loose-envify "^1.1.0" - scheduler "^0.23.0" + scheduler "^0.23.1" react-error-boundary@^3.1.4: version "3.1.4" @@ -7325,9 +7364,9 @@ react-split-it@^2.0.0: prop-types "^15.8.1" react@^18.1.0: - version "18.2.0" - resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" - integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ== + version "18.3.0" + resolved "https://registry.yarnpkg.com/react/-/react-18.3.0.tgz#84386d0a36fdf5ef50fa5755b7812bdfb76194a5" + integrity sha512-RPutkJftSAldDibyrjuku7q11d3oy6wKOyPe5K1HA/HwwrXcEqBdHsLypkC2FFYjP7bPUa6gbzSBhw4sY2JcDg== dependencies: loose-envify "^1.1.0" @@ -7488,10 +7527,10 @@ remark@^15.0.1: remark-stringify "^11.0.0" unified "^11.0.0" -remove-accents@0.4.2: - version "0.4.2" - resolved "https://registry.yarnpkg.com/remove-accents/-/remove-accents-0.4.2.tgz#0a43d3aaae1e80db919e07ae254b285d9e1c7bb5" - integrity sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA== +remove-accents@0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/remove-accents/-/remove-accents-0.5.0.tgz#77991f37ba212afba162e375b627631315bed687" + integrity sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A== require-directory@^2.1.1: version "2.1.1" @@ -7648,10 +7687,10 @@ sax@^1.2.4: resolved "https://registry.yarnpkg.com/sax/-/sax-1.3.0.tgz#a5dbe77db3be05c9d1ee7785dbd3ea9de51593d0" integrity sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA== -scheduler@^0.23.0: - version "0.23.0" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe" - integrity sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw== +scheduler@^0.23.1: + version "0.23.1" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.1.tgz#ef964a7936d7cbe8f7bc0d38fc479a823aed2923" + integrity sha512-5GKS5JGfiah1O38Vfa9srZE4s3wdHbwjlCrvIookrg2FO9aIwKLOJXuJQFlEfNcVSOXuaL2hzDeY20uVXcUtrw== dependencies: loose-envify "^1.1.0" @@ -7853,12 +7892,12 @@ simple-update-notifier@2.0.0: semver "^7.5.3" sirv@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/sirv/-/sirv-2.0.3.tgz#ca5868b87205a74bef62a469ed0296abceccd446" - integrity sha512-O9jm9BsID1P+0HOi81VpXPoDxYP374pkOLzACAoyUQ/3OUVndNpsz6wMnY2z+yOxzbllCKZrM+9QrWsv4THnyA== + version "2.0.4" + resolved "https://registry.yarnpkg.com/sirv/-/sirv-2.0.4.tgz#5dd9a725c578e34e449f332703eb2a74e46a29b0" + integrity sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ== dependencies: - "@polka/url" "^1.0.0-next.20" - mrmime "^1.0.0" + "@polka/url" "^1.0.0-next.24" + mrmime "^2.0.0" totalist "^3.0.0" slash@^2.0.0: @@ -8066,10 +8105,10 @@ strip-final-newline@^2.0.0: resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== -style-loader@^3.3.1: - version "3.3.4" - resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-3.3.4.tgz#f30f786c36db03a45cbd55b6a70d930c479090e7" - integrity sha512-0WqXzrsMTyb8yjZJHDqwmnwRJvhALK9LfRtRc6B4UTWe8AijYLZYZ9thuJTZc2VfQWINADW/j+LiJnfy2RoC1w== +style-loader@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-4.0.0.tgz#0ea96e468f43c69600011e0589cb05c44f3b17a5" + integrity sha512-1V4WqhhZZgjVAVJyt7TdDPZoPBPNHbekX4fWnCJL1yQukhCeZhJySUL+gL9y6sNdN95uEOS83Y55SqHcP7MzLA== style-to-object@^1.0.0: version "1.0.5" @@ -8287,9 +8326,9 @@ type-is@~1.6.18: mime-types "~2.1.24" typescript@^5.0.0: - version "5.4.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.3.tgz#5c6fedd4c87bee01cd7a528a30145521f8e0feff" - integrity sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg== + version "5.4.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.5.tgz#42ccef2c571fdbd0f6718b1d1f5e6e5ef006f611" + integrity sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ== typescript@^5.3.3: version "5.3.3" @@ -8528,9 +8567,9 @@ web-streams-polyfill@^3.0.3: integrity sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q== webpack-bundle-analyzer@^4.10.1: - version "4.10.1" - resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.1.tgz#84b7473b630a7b8c21c741f81d8fe4593208b454" - integrity sha512-s3P7pgexgT/HTUSYgxJyn28A+99mmLq4HsJepMPzu0R8ImJc52QNqaFYW1Z2z2uIb1/J3eYgaAWVpaC+v/1aAQ== + version "4.10.2" + resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.2.tgz#633af2862c213730be3dbdf40456db171b60d5bd" + integrity sha512-vJptkMm9pk5si4Bv922ZbKLV8UTT4zib4FPgXMhgzUny0bfDDkLXAVQs3ly3fS4/TN9ROFtb0NFrm04UXFE/Vw== dependencies: "@discoveryjs/json-ext" "0.5.7" acorn "^8.0.4" @@ -8540,7 +8579,6 @@ webpack-bundle-analyzer@^4.10.1: escape-string-regexp "^4.0.0" gzip-size "^6.0.0" html-escaper "^2.0.2" - is-plain-object "^5.0.0" opener "^1.5.2" picocolors "^1.0.0" sirv "^2.0.3" From 21d0dd076b8787d2bac435620a8efb2f0f157786 Mon Sep 17 00:00:00 2001 From: Mike Sawka Date: Thu, 25 Apr 2024 15:07:41 -0700 Subject: [PATCH 6/9] increase max websocket read limit. protect frontend from exceeding the read limit. limit input size to 10k characters. (#606) --- src/app/appconst.ts | 2 ++ src/app/workspace/cmdinput/textareainput.tsx | 4 +++- src/models/ws.ts | 9 ++++++++- wavesrv/cmd/main-server.go | 1 - wavesrv/pkg/wsshell/wsshell.go | 2 +- 5 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/app/appconst.ts b/src/app/appconst.ts index 062b36e9b..a395519d0 100644 --- a/src/app/appconst.ts +++ b/src/app/appconst.ts @@ -47,6 +47,8 @@ export const TabIcons = [ "file", ]; +export const MaxWebSocketSendSize = 64 * 1024 - 100; + // @ts-ignore export const VERSION = __WAVETERM_VERSION__; // @ts-ignore diff --git a/src/app/workspace/cmdinput/textareainput.tsx b/src/app/workspace/cmdinput/textareainput.tsx index 66760d393..60c1f90e8 100644 --- a/src/app/workspace/cmdinput/textareainput.tsx +++ b/src/app/workspace/cmdinput/textareainput.tsx @@ -13,6 +13,7 @@ import { getMonoFontSize } from "@/util/textmeasure"; import * as appconst from "@/app/appconst"; type OV = mobx.IObservableValue; +const MaxInputLength = 10 * 1024; function pageSize(div: any): number { if (div == null) { @@ -616,7 +617,7 @@ class TextAreaInput extends React.Component<{ screen: Screen; onHeightChange: () - + @@ -637,6 +638,7 @@ class TextAreaInput extends React.Component<{ screen: Screen; onHeightChange: () onChange={this.onChange} onSelect={this.onSelect} placeholder="Type here..." + maxLength={MaxInputLength} className={cn("textarea", { "display-disabled": auxViewFocused })} > appconst.MaxWebSocketSendSize) { + console.log("ws message too large", byteSize, data.type, msg.substring(0, 100)); + return; + } + this.wsConn.send(msg); } pushMessage(data: any) { diff --git a/wavesrv/cmd/main-server.go b/wavesrv/cmd/main-server.go index aff0d6e81..3709ed46e 100644 --- a/wavesrv/cmd/main-server.go +++ b/wavesrv/cmd/main-server.go @@ -990,7 +990,6 @@ func doShutdown(reason string) { } func configDirHandler(w http.ResponseWriter, r *http.Request) { - log.Printf("running?") configPath := r.URL.Path configFullPath := path.Join(scbase.GetWaveHomeDir(), configPath) dirFile, err := os.Open(configFullPath) diff --git a/wavesrv/pkg/wsshell/wsshell.go b/wavesrv/pkg/wsshell/wsshell.go index 48031df59..f9a72b1cf 100644 --- a/wavesrv/pkg/wsshell/wsshell.go +++ b/wavesrv/pkg/wsshell/wsshell.go @@ -119,7 +119,7 @@ func (ws *WSShell) ReadPump() { defer func() { ws.Conn.Close() }() - ws.Conn.SetReadLimit(4096) + ws.Conn.SetReadLimit(64 * 1024) ws.Conn.SetReadDeadline(time.Now().Add(readWait)) for { _, message, err := ws.Conn.ReadMessage() From fcf8e4ed443d9360b5b217fe240bb0e82428cea4 Mon Sep 17 00:00:00 2001 From: Mike Sawka Date: Thu, 25 Apr 2024 15:35:48 -0700 Subject: [PATCH 7/9] ignore ptmx error (#608) --- waveshell/pkg/shellapi/shellapi.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/waveshell/pkg/shellapi/shellapi.go b/waveshell/pkg/shellapi/shellapi.go index c948a56ee..db758758b 100644 --- a/waveshell/pkg/shellapi/shellapi.go +++ b/waveshell/pkg/shellapi/shellapi.go @@ -197,11 +197,7 @@ func StreamCommandWithExtraFd(ctx context.Context, ecmd *exec.Cmd, outputCh chan go func() { // ignore error (/dev/ptmx has read error when process is done) defer outputWg.Done() - err := utilfn.CopyToChannel(outputCh, cmdPty) - if err != nil { - errStr := fmt.Sprintf("\r\nerror reading from pty: %v\r\n", err) - outputCh <- []byte(errStr) - } + utilfn.CopyToChannel(outputCh, cmdPty) }() go func() { defer outputWg.Done() From 5e3243564bccd0407500c1099ac9133fe99136a7 Mon Sep 17 00:00:00 2001 From: Cole Lashley Date: Thu, 25 Apr 2024 16:14:37 -0700 Subject: [PATCH 8/9] Open AI Timeout setting and aliases for AI settings (#590) * added ai timeout setting * addressed review comments * fixed baseurl gating for telemetry * updated copy * addressed review comments * removed prefix for client:show and added units to timeout * changed timeout to use ms precision --- src/app/clientsettings/clientsettings.tsx | 22 ++++++ src/models/commandrunner.ts | 4 + src/types/custom.d.ts | 1 + wavesrv/pkg/cmdrunner/cmdrunner.go | 90 +++++++++++++++-------- wavesrv/pkg/sstore/sstore.go | 3 + 5 files changed, 90 insertions(+), 30 deletions(-) diff --git a/src/app/clientsettings/clientsettings.tsx b/src/app/clientsettings/clientsettings.tsx index dd53cd3ad..db0e73d07 100644 --- a/src/app/clientsettings/clientsettings.tsx +++ b/src/app/clientsettings/clientsettings.tsx @@ -157,6 +157,12 @@ class ClientSettingsView extends React.Component<{ model: RemotesModel }, { hove commandRtnHandler(prtn, this.errorMessage); } + @boundMethod + inlineUpdateOpenAITimeout(newTimeout: string): void { + const prtn = GlobalCommandRunner.setClientOpenAISettings({ timeout: newTimeout }); + commandRtnHandler(prtn, this.errorMessage); + } + @boundMethod setErrorMessage(msg: string): void { mobx.action(() => { @@ -203,6 +209,9 @@ class ClientSettingsView extends React.Component<{ model: RemotesModel }, { hove const maxTokensStr = String( openAIOpts.maxtokens == null || openAIOpts.maxtokens == 0 ? 1000 : openAIOpts.maxtokens ); + const aiTimeoutStr = String( + openAIOpts.timeout == null || openAIOpts.timeout == 0 ? 10 : openAIOpts.timeout / 1000 + ); const curFontSize = GlobalModel.getTermFontSize(); const curFontFamily = GlobalModel.getTermFontFamily(); const curTheme = GlobalModel.getThemeSource(); @@ -342,6 +351,19 @@ class ClientSettingsView extends React.Component<{ model: RemotesModel }, { hove />
+
+
AI Timeout (seconds)
+
+ +
+
Global Hotkey
diff --git a/src/models/commandrunner.ts b/src/models/commandrunner.ts index e4a0e8494..48fa3dc1d 100644 --- a/src/models/commandrunner.ts +++ b/src/models/commandrunner.ts @@ -424,6 +424,7 @@ class CommandRunner { apitoken?: string; maxtokens?: string; baseurl?: string; + timeout?: string; }): Promise { let kwargs = { nohist: "1", @@ -440,6 +441,9 @@ class CommandRunner { if (opts.baseurl != null) { kwargs["openaibaseurl"] = opts.baseurl; } + if (opts.timeout != null) { + kwargs["openaitimeout"] = opts.timeout; + } return GlobalModel.submitCommand("client", "set", null, kwargs, false); } diff --git a/src/types/custom.d.ts b/src/types/custom.d.ts index 1546e0cd7..7a9db4acb 100644 --- a/src/types/custom.d.ts +++ b/src/types/custom.d.ts @@ -659,6 +659,7 @@ declare global { maxtokens?: number; maxchoices?: number; baseurl?: string; + timeout?: number; }; type PlaybookType = { diff --git a/wavesrv/pkg/cmdrunner/cmdrunner.go b/wavesrv/pkg/cmdrunner/cmdrunner.go index 96d29e2a4..6ba5a56ee 100644 --- a/wavesrv/pkg/cmdrunner/cmdrunner.go +++ b/wavesrv/pkg/cmdrunner/cmdrunner.go @@ -84,9 +84,9 @@ const TermFontSizeMax = 24 const TsFormatStr = "2006-01-02 15:04:05" -const OpenAIPacketTimeout = 10 * time.Second +const OpenAIPacketTimeout = 10 * 1000 * time.Millisecond const OpenAIStreamTimeout = 5 * time.Minute -const OpenAICloudCompletionTelemetryOffErrorMsg = "To ensure responsible usage and prevent misuse, Wave AI requires telemetry to be enabled when using its free AI features.\n\nIf you prefer not to enable telemetry, you can still access Wave AI's features by providing your own OpenAI API key in the Settings menu. Please note that when using your personal API key, requests will be sent directly to the OpenAI API without being proxied through Wave's servers.\n\nIf you wish to continue using Wave AI's free features, you can easily enable telemetry by running the '/telemetry:on' command in the terminal. This will allow you to access the free AI features while helping to protect the platform from abuse." +const OpenAICloudCompletionTelemetryOffErrorMsg = "To ensure responsible usage and prevent misuse, Wave AI requires telemetry to be enabled when using its free AI features.\n\nIf you prefer not to enable telemetry, you can still access Wave AI's features by providing your own OpenAI API key or AI Base URL in the Settings menu. Please note that when using your personal API key, requests will be sent directly to the OpenAI API or the API that you specified with the AI Base URL, without being proxied through Wave's servers.\n\nIf you wish to continue using Wave AI's free features, you can easily enable telemetry by running the '/telemetry:on' command in the terminal. This will allow you to access the free AI features while helping to protect the platform from abuse." const ( KwArgRenderer = "renderer" @@ -2693,8 +2693,6 @@ func getCmdInfoEngineeredPrompt(userQuery string, curLineStr string, shellType s } func doOpenAICmdInfoCompletion(cmd *sstore.CmdType, clientId string, opts *sstore.OpenAIOptsType, prompt []packet.OpenAIPromptMessageType, curLineStr string) { - var hadError bool - log.Println("had error: ", hadError) ctx, cancelFn := context.WithTimeout(context.Background(), OpenAIStreamTimeout) defer cancelFn() defer func() { @@ -2702,7 +2700,6 @@ func doOpenAICmdInfoCompletion(cmd *sstore.CmdType, clientId string, opts *sstor if r != nil { panicMsg := fmt.Sprintf("panic: %v", r) log.Printf("panic in doOpenAICompletion: %s\n", panicMsg) - hadError = true } }() var ch chan *packet.OpenAIPacketType @@ -2730,12 +2727,15 @@ func doOpenAICmdInfoCompletion(cmd *sstore.CmdType, clientId string, opts *sstor return } writePacketToUpdateBus(ctx, cmd, asstMessagePk) + packetTimeout := OpenAIPacketTimeout + if opts.Timeout >= 0 { + packetTimeout = time.Duration(opts.Timeout) * time.Millisecond + } doneWaitingForPackets := false for !doneWaitingForPackets { select { - case <-time.After(OpenAIPacketTimeout): + case <-time.After(packetTimeout): // timeout reading from channel - hadError = true doneWaitingForPackets = true asstOutputPk.Error = "timeout waiting for server response" updateAsstResponseAndWriteToUpdateBus(ctx, cmd, asstMessagePk, asstOutputMessageID) @@ -2743,7 +2743,6 @@ func doOpenAICmdInfoCompletion(cmd *sstore.CmdType, clientId string, opts *sstor if ok { // got a packet if pk.Error != "" { - hadError = true asstOutputPk.Error = pk.Error } if pk.Model != "" && pk.Index == 0 { @@ -2823,10 +2822,14 @@ func doOpenAIStreamCompletion(cmd *sstore.CmdType, clientId string, opts *sstore writeErrorToPty(cmd, fmt.Sprintf("error calling OpenAI API: %v", err), outputPos) return } + packetTimeout := OpenAIPacketTimeout + if opts.Timeout >= 0 { + packetTimeout = time.Duration(opts.Timeout) * time.Millisecond + } doneWaitingForPackets := false for !doneWaitingForPackets { select { - case <-time.After(OpenAIPacketTimeout): + case <-time.After(packetTimeout): // timeout reading from channel hadError = true pk := openai.CreateErrorPacket(fmt.Sprintf("timeout waiting for server response")) @@ -2895,7 +2898,7 @@ func OpenAICommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus return nil, fmt.Errorf("error retrieving client open ai options") } opts := clientData.OpenAIOpts - if opts.APIToken == "" { + if opts.APIToken == "" && opts.BaseURL == "" { if clientData.ClientOpts.NoTelemetry { return nil, fmt.Errorf(OpenAICloudCompletionTelemetryOffErrorMsg) } @@ -5798,6 +5801,15 @@ func validateFontFamily(fontFamily string) error { return nil } +func CheckOptionAlias(kwargs map[string]string, aliases ...string) (string, bool) { + for _, alias := range aliases { + if val, found := kwargs[alias]; found { + return val, found + } + } + return "", false +} + func ClientSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { clientData, err := sstore.EnsureClientData(ctx) if err != nil { @@ -5870,7 +5882,7 @@ func ClientSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sc } varsUpdated = append(varsUpdated, "termtheme") } - if apiToken, found := pk.Kwargs["openaiapitoken"]; found { + if apiToken, found := CheckOptionAlias(pk.Kwargs, "openaiapitoken", "aiapitoken"); found { err = validateOpenAIAPIToken(apiToken) if err != nil { return nil, err @@ -5884,10 +5896,10 @@ func ClientSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sc aiOpts.APIToken = apiToken err = sstore.UpdateClientOpenAIOpts(ctx, *aiOpts) if err != nil { - return nil, fmt.Errorf("error updating client openai api token: %v", err) + return nil, fmt.Errorf("error updating client ai api token: %v", err) } } - if aiModel, found := pk.Kwargs["openaimodel"]; found { + if aiModel, found := CheckOptionAlias(pk.Kwargs, "openaimodel", "aimodel"); found { err = validateOpenAIModel(aiModel) if err != nil { return nil, err @@ -5901,16 +5913,16 @@ func ClientSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sc aiOpts.Model = aiModel err = sstore.UpdateClientOpenAIOpts(ctx, *aiOpts) if err != nil { - return nil, fmt.Errorf("error updating client openai model: %v", err) + return nil, fmt.Errorf("error updating client ai model: %v", err) } } - if maxTokensStr, found := pk.Kwargs["openaimaxtokens"]; found { + if maxTokensStr, found := CheckOptionAlias(pk.Kwargs, "openaimaxtokens", "aimaxtokens"); found { maxTokens, err := strconv.Atoi(maxTokensStr) if err != nil { - return nil, fmt.Errorf("error updating client openai maxtokens, invalid number: %v", err) + return nil, fmt.Errorf("error updating client ai maxtokens, invalid number: %v", err) } if maxTokens < 0 || maxTokens > 1000000 { - return nil, fmt.Errorf("error updating client openai maxtokens, out of range: %d", maxTokens) + return nil, fmt.Errorf("error updating client ai maxtokens, out of range: %d", maxTokens) } varsUpdated = append(varsUpdated, "openaimaxtokens") aiOpts := clientData.OpenAIOpts @@ -5921,16 +5933,16 @@ func ClientSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sc aiOpts.MaxTokens = maxTokens err = sstore.UpdateClientOpenAIOpts(ctx, *aiOpts) if err != nil { - return nil, fmt.Errorf("error updating client openai maxtokens: %v", err) + return nil, fmt.Errorf("error updating client ai maxtokens: %v", err) } } - if maxChoicesStr, found := pk.Kwargs["openaimaxchoices"]; found { + if maxChoicesStr, found := CheckOptionAlias(pk.Kwargs, "openaimaxchoices", "aimaxchoices"); found { maxChoices, err := strconv.Atoi(maxChoicesStr) if err != nil { - return nil, fmt.Errorf("error updating client openai maxchoices, invalid number: %v", err) + return nil, fmt.Errorf("error updating client ai maxchoices, invalid number: %v", err) } if maxChoices < 0 || maxChoices > 10 { - return nil, fmt.Errorf("error updating client openai maxchoices, out of range: %d", maxChoices) + return nil, fmt.Errorf("error updating client ai maxchoices, out of range: %d", maxChoices) } varsUpdated = append(varsUpdated, "openaimaxchoices") aiOpts := clientData.OpenAIOpts @@ -5941,10 +5953,10 @@ func ClientSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sc aiOpts.MaxChoices = maxChoices err = sstore.UpdateClientOpenAIOpts(ctx, *aiOpts) if err != nil { - return nil, fmt.Errorf("error updating client openai maxchoices: %v", err) + return nil, fmt.Errorf("error updating client ai maxchoices: %v", err) } } - if aiBaseURL, found := pk.Kwargs["openaibaseurl"]; found { + if aiBaseURL, found := CheckOptionAlias(pk.Kwargs, "openaibaseurl", "aibaseurl"); found { aiOpts := clientData.OpenAIOpts if aiOpts == nil { aiOpts = &sstore.OpenAIOptsType{} @@ -5954,7 +5966,24 @@ func ClientSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sc varsUpdated = append(varsUpdated, "openaibaseurl") err = sstore.UpdateClientOpenAIOpts(ctx, *aiOpts) if err != nil { - return nil, fmt.Errorf("error updating client openai base url: %v", err) + return nil, fmt.Errorf("error updating client ai base url: %v", err) + } + } + if aiTimeoutStr, found := CheckOptionAlias(pk.Kwargs, "openaitimeout", "aitimeout"); found { + aiTimeout, err := strconv.ParseFloat(aiTimeoutStr, 64) + if err != nil { + return nil, fmt.Errorf("error updating client ai timeout, invalid number: %v", err) + } + aiOpts := clientData.OpenAIOpts + if aiOpts == nil { + aiOpts = &sstore.OpenAIOptsType{} + clientData.OpenAIOpts = aiOpts + } + aiOpts.Timeout = int(aiTimeout * 1000) + varsUpdated = append(varsUpdated, "openaitimeout") + err = sstore.UpdateClientOpenAIOpts(ctx, *aiOpts) + if err != nil { + return nil, fmt.Errorf("error updating client ai timeout: %v", err) } } if webglStr, found := pk.Kwargs["webgl"]; found { @@ -5968,7 +5997,7 @@ func ClientSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sc varsUpdated = append(varsUpdated, "webgl") } if len(varsUpdated) == 0 { - return nil, fmt.Errorf("/client:set requires a value to set: %s", formatStrs([]string{"termfontsize", "termfontfamily", "openaiapitoken", "openaimodel", "openaibaseurl", "openaimaxtokens", "openaimaxchoices", "webgl"}, "or", false)) + return nil, fmt.Errorf("/client:set requires a value to set: %s", formatStrs([]string{"termfontsize", "termfontfamily", "openaiapitoken", "openaimodel", "openaibaseurl", "openaimaxtokens", "openaimaxchoices", "openaitimeout", "webgl"}, "or", false)) } clientData, err = sstore.EnsureClientData(ctx) if err != nil { @@ -6008,11 +6037,12 @@ func ClientShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (s buf.WriteString(fmt.Sprintf(" %-15s %d\n", "termfontsize", clientData.FeOpts.TermFontSize)) buf.WriteString(fmt.Sprintf(" %-15s %s\n", "termfontfamily", clientData.FeOpts.TermFontFamily)) buf.WriteString(fmt.Sprintf(" %-15s %s\n", "termfontfamily", clientData.FeOpts.Theme)) - buf.WriteString(fmt.Sprintf(" %-15s %s\n", "openaiapitoken", clientData.OpenAIOpts.APIToken)) - buf.WriteString(fmt.Sprintf(" %-15s %s\n", "openaimodel", clientData.OpenAIOpts.Model)) - buf.WriteString(fmt.Sprintf(" %-15s %d\n", "openaimaxtokens", clientData.OpenAIOpts.MaxTokens)) - buf.WriteString(fmt.Sprintf(" %-15s %d\n", "openaimaxchoices", clientData.OpenAIOpts.MaxChoices)) - buf.WriteString(fmt.Sprintf(" %-15s %s\n", "openaibaseurl", clientData.OpenAIOpts.BaseURL)) + buf.WriteString(fmt.Sprintf(" %-15s %s\n", "aiapitoken", clientData.OpenAIOpts.APIToken)) + buf.WriteString(fmt.Sprintf(" %-15s %s\n", "aimodel", clientData.OpenAIOpts.Model)) + buf.WriteString(fmt.Sprintf(" %-15s %d\n", "aimaxtokens", clientData.OpenAIOpts.MaxTokens)) + buf.WriteString(fmt.Sprintf(" %-15s %d\n", "aimaxchoices", clientData.OpenAIOpts.MaxChoices)) + buf.WriteString(fmt.Sprintf(" %-15s %s\n", "aibaseurl", clientData.OpenAIOpts.BaseURL)) + buf.WriteString(fmt.Sprintf(" %-15s %ss\n", "aitimeout", strconv.FormatFloat((float64(clientData.OpenAIOpts.Timeout)/1000.0), 'f', -1, 64))) update := scbus.MakeUpdatePacket() update.AddUpdate(sstore.InfoMsgType{ InfoTitle: fmt.Sprintf("client info"), diff --git a/wavesrv/pkg/sstore/sstore.go b/wavesrv/pkg/sstore/sstore.go index c0a5b5e4b..fb5c3d641 100644 --- a/wavesrv/pkg/sstore/sstore.go +++ b/wavesrv/pkg/sstore/sstore.go @@ -289,6 +289,8 @@ func (cdata *ClientData) Clean() *ClientData { Model: cdata.OpenAIOpts.Model, MaxTokens: cdata.OpenAIOpts.MaxTokens, MaxChoices: cdata.OpenAIOpts.MaxChoices, + Timeout: cdata.OpenAIOpts.Timeout, + BaseURL: cdata.OpenAIOpts.BaseURL, // omit API Token } if cdata.OpenAIOpts.APIToken != "" { @@ -736,6 +738,7 @@ type OpenAIOptsType struct { BaseURL string `json:"baseurl,omitempty"` MaxTokens int `json:"maxtokens,omitempty"` MaxChoices int `json:"maxchoices,omitempty"` + Timeout int `json:"timeout,omitempty"` } const ( From a449cec33a00c2b14c1f834b7773374799a3cb31 Mon Sep 17 00:00:00 2001 From: Sylvie Crowe <107814465+oneirocosm@users.noreply.github.com> Date: Thu, 25 Apr 2024 18:19:43 -0700 Subject: [PATCH 9/9] Sudo Config Gui (#603) * feat: add gui elements to configure ssh pw cache This adds a dropdown for on/off/notimeout, a number entry box for a timeout value, and a toggle for clearing when the computer sleeps. * fix: improve password timeout entry This makes the password timeout more consistent by using an inline settings element. It also creates the inline settings element to parse the input. * feat: turn sudo password caching on and off * feat: use configurable sudo timeout This makes it possible to control how long waveterm stores your sudo password. Note that if it changes, it immediately clears the cached passwords. * fix: clear existing sudo passwords if switched off When the sudo password store state is changed to "off", all existing passwords must immediately be cleared automatically. * feat: allow clearing sudo passwords on suspend This option makes it so the sudo passwords will be cleared when the computer falls asleep. It will never be used in the case where the password is set to never time out. * feat: allow notimeout to prevent sudo pw clear This option allows the sudo timeout to be ignored while it is selected. * feat: adjust current deadline based on user config This allows the deadline to update as changes to the config are happening. * fix: reject a sudopwtimeout of 0 on the backend * fix: use the default sudoPwTimeout for empty input * fix: specify the timeout length is minutes * fix: store sudopwtimeout in ms instead of minutes * fix: formatting the default sudo timeout By changing the order of operations, this no longer shows up as NaN if the default is used. * refactor: consolidate inlinesettingstextedit This removes the number variant and combines them into the same class with an option to switch between the two behaviors. * refactor: consolidate textfield and numberfield This removes the number variant of textfield. The textfield component can now act as a numberfield when the optional isNumber prop is true. --- src/app/appconst.ts | 2 + src/app/clientsettings/clientsettings.tsx | 66 +++++++++++++++++ src/app/common/elements/index.tsx | 1 - .../elements/inlinesettingstextedit.tsx | 7 ++ src/app/common/elements/numberfield.tsx | 39 ----------- src/app/common/elements/textfield.tsx | 7 +- src/app/common/modals/createremoteconn.tsx | 5 +- src/electron/emain.ts | 13 ++++ src/models/commandrunner.ts | 25 +++++++ src/models/model.ts | 16 +++++ src/types/custom.d.ts | 3 + wavesrv/cmd/main-server.go | 28 ++++++++ wavesrv/pkg/cmdrunner/cmdrunner.go | 70 ++++++++++++++++++- wavesrv/pkg/remote/remote.go | 41 +++++++++-- wavesrv/pkg/sstore/sstore.go | 17 +++-- 15 files changed, 286 insertions(+), 54 deletions(-) delete mode 100644 src/app/common/elements/numberfield.tsx diff --git a/src/app/appconst.ts b/src/app/appconst.ts index a395519d0..0b5179337 100644 --- a/src/app/appconst.ts +++ b/src/app/appconst.ts @@ -46,6 +46,8 @@ export const TabIcons = [ "heart", "file", ]; +export const DefaultSudoPwStore = "on"; +export const DefaultSudoPwTimeoutMs = 5 * 60 * 1000; export const MaxWebSocketSendSize = 64 * 1024 - 100; diff --git a/src/app/clientsettings/clientsettings.tsx b/src/app/clientsettings/clientsettings.tsx index db0e73d07..fa243c059 100644 --- a/src/app/clientsettings/clientsettings.tsx +++ b/src/app/clientsettings/clientsettings.tsx @@ -197,6 +197,35 @@ class ClientSettingsView extends React.Component<{ model: RemotesModel }, { hove GlobalModel.clientSettingsViewModel.closeView(); } + @boundMethod + getSudoPwStoreOptions(): DropdownItem[] { + const sudoCacheSources: DropdownItem[] = []; + sudoCacheSources.push({ label: "On", value: "on" }); + sudoCacheSources.push({ label: "Off", value: "off" }); + sudoCacheSources.push({ label: "On Without Timeout", value: "notimeout" }); + return sudoCacheSources; + } + + @boundMethod + handleChangeSudoPwStoreConfig(store: string) { + const prtn = GlobalCommandRunner.setSudoPwStore(store); + commandRtnHandler(prtn, this.errorMessage); + } + + @boundMethod + handleChangeSudoPwTimeoutConfig(timeout: string) { + if (Number(timeout) != 0) { + const prtn = GlobalCommandRunner.setSudoPwTimeout(timeout); + commandRtnHandler(prtn, this.errorMessage); + } + } + + @boundMethod + handleChangeSudoPwClearOnSleepConfig(clearOnSleep: boolean) { + const prtn = GlobalCommandRunner.setSudoPwClearOnSleep(clearOnSleep); + commandRtnHandler(prtn, this.errorMessage); + } + render() { const isHidden = GlobalModel.activeMainView.get() != "clientsettings"; if (isHidden) { @@ -217,6 +246,9 @@ class ClientSettingsView extends React.Component<{ model: RemotesModel }, { hove const curTheme = GlobalModel.getThemeSource(); const termThemes = getTermThemes(GlobalModel.termThemes.get(), "Wave Default"); const currTermTheme = GlobalModel.getTermThemeSettings()["root"] ?? termThemes[0].label; + const curSudoPwStore = GlobalModel.getSudoPwStore(); + const curSudoPwTimeout = String(GlobalModel.getSudoPwTimeout()); + const curSudoPwClearOnSleep = GlobalModel.getSudoPwClearOnSleep(); return ( @@ -375,6 +407,40 @@ class ClientSettingsView extends React.Component<{ model: RemotesModel }, { hove />
+
+
Remember Sudo Password
+
+ +
+
+
+
Sudo Timeout (Minutes)
+
+ +
+
+
+
Clear Sudo Password on Sleep
+
+ +
+
diff --git a/src/app/common/elements/index.tsx b/src/app/common/elements/index.tsx index 0b1704992..185cc2748 100644 --- a/src/app/common/elements/index.tsx +++ b/src/app/common/elements/index.tsx @@ -8,7 +8,6 @@ export { InputDecoration } from "./inputdecoration"; export { LinkButton } from "./linkbutton"; export { Markdown } from "./markdown"; export { Modal } from "./modal"; -export { NumberField } from "./numberfield"; export { PasswordField } from "./passwordfield"; export { ResizableSidebar } from "./resizablesidebar"; export { SettingsError } from "./settingserror"; diff --git a/src/app/common/elements/inlinesettingstextedit.tsx b/src/app/common/elements/inlinesettingstextedit.tsx index 7f2768e6b..8968d6066 100644 --- a/src/app/common/elements/inlinesettingstextedit.tsx +++ b/src/app/common/elements/inlinesettingstextedit.tsx @@ -22,6 +22,7 @@ class InlineSettingsTextEdit extends React.Component< maxLength: number; placeholder: string; showIcon?: boolean; + isNumber?: boolean; }, {} > { @@ -46,6 +47,12 @@ class InlineSettingsTextEdit extends React.Component< @boundMethod handleChangeText(e: any): void { + const isNumber = this.props.isNumber ?? false; + const value = e.target.value; + if (isNumber && value !== "" && !/^\d*$/.test(value)) { + return; + } + mobx.action(() => { this.tempText.set(e.target.value); })(); diff --git a/src/app/common/elements/numberfield.tsx b/src/app/common/elements/numberfield.tsx deleted file mode 100644 index 1a40d6847..000000000 --- a/src/app/common/elements/numberfield.tsx +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2023, Command Line Inc. -// SPDX-License-Identifier: Apache-2.0 - -import * as React from "react"; -import { boundMethod } from "autobind-decorator"; - -import { TextField } from "./textfield"; - -class NumberField extends TextField { - @boundMethod - handleInputChange(e: React.ChangeEvent) { - const { required, onChange } = this.props; - const inputValue = e.target.value; - - // Allow only numeric input - if (inputValue === "" || /^\d*$/.test(inputValue)) { - // Update the internal state only if the component is not controlled. - if (this.props.value === undefined) { - const isError = required ? inputValue.trim() === "" : false; - - this.setState({ - internalValue: inputValue, - error: isError, - hasContent: Boolean(inputValue), - }); - } - - onChange && onChange(inputValue); - } - } - - render() { - // Use the render method from TextField but add the onKeyDown handler - const renderedTextField = super.render(); - return React.cloneElement(renderedTextField); - } -} - -export { NumberField }; diff --git a/src/app/common/elements/textfield.tsx b/src/app/common/elements/textfield.tsx index 960611e84..6a37b0eb6 100644 --- a/src/app/common/elements/textfield.tsx +++ b/src/app/common/elements/textfield.tsx @@ -27,6 +27,7 @@ interface TextFieldProps { maxLength?: number; autoFocus?: boolean; disabled?: boolean; + isNumber?: boolean; } interface TextFieldState { @@ -108,9 +109,13 @@ class TextField extends React.Component { @boundMethod handleInputChange(e: React.ChangeEvent) { - const { required, onChange } = this.props; + const { required, onChange, isNumber } = this.props; const inputValue = e.target.value; + if (isNumber && inputValue !== "" && !/^\d*$/.test(inputValue)) { + return; + } + // Check if value is empty and the field is required if (required && !inputValue) { this.setState({ error: true, hasContent: false }); diff --git a/src/app/common/modals/createremoteconn.tsx b/src/app/common/modals/createremoteconn.tsx index 55872df56..7b181d677 100644 --- a/src/app/common/modals/createremoteconn.tsx +++ b/src/app/common/modals/createremoteconn.tsx @@ -7,7 +7,7 @@ import * as mobx from "mobx"; import { boundMethod } from "autobind-decorator"; import { If } from "tsx-control-statements/components"; import { GlobalModel, GlobalCommandRunner, RemotesModel } from "@/models"; -import { Modal, TextField, NumberField, InputDecoration, Dropdown, PasswordField, Tooltip } from "@/elements"; +import { Modal, TextField, InputDecoration, Dropdown, PasswordField, Tooltip } from "@/elements"; import * as util from "@/util/util"; import "./createremoteconn.less"; @@ -236,11 +236,12 @@ class CreateRemoteConnModal extends React.Component<{}, {}> { />
- diff --git a/src/electron/emain.ts b/src/electron/emain.ts index c98fac3eb..f7e84d38f 100644 --- a/src/electron/emain.ts +++ b/src/electron/emain.ts @@ -419,6 +419,17 @@ function mainResizeHandler(_: any, win: Electron.BrowserWindow) { }); } +function mainPowerHandler(status: string) { + const url = new URL(getBaseHostPort() + "/api/power-monitor"); + const fetchHeaders = getFetchHeaders(); + const body = { status: status }; + fetch(url, { method: "post", body: JSON.stringify(body), headers: fetchHeaders }) + .then((resp) => handleJsonFetchResponse(url, resp)) + .catch((err) => { + console.log("error setting power monitor state", err); + }); +} + function calcBounds(clientData: ClientDataType): Electron.Rectangle { const primaryDisplay = electron.screen.getPrimaryDisplay(); const pdBounds = primaryDisplay.bounds; @@ -946,3 +957,5 @@ function configureAutoUpdater(enabled: boolean) { } }); })(); + +electron.powerMonitor.on("suspend", () => mainPowerHandler("suspend")); diff --git a/src/models/commandrunner.ts b/src/models/commandrunner.ts index 48fa3dc1d..d0b585c4a 100644 --- a/src/models/commandrunner.ts +++ b/src/models/commandrunner.ts @@ -467,6 +467,31 @@ class CommandRunner { return GlobalModel.submitCommand("client", "setrightsidebar", null, kwargs, false); } + setSudoPwStore(store: string): Promise { + let kwargs = { + nohist: "1", + sudopwstore: store, + }; + return GlobalModel.submitCommand("client", "set", null, kwargs, false); + } + + setSudoPwTimeout(timeout: string): Promise { + let kwargs = { + nohist: "1", + sudopwtimeout: timeout, + }; + return GlobalModel.submitCommand("client", "set", null, kwargs, false); + } + + setSudoPwClearOnSleep(clear: boolean): Promise { + let kwargs = { + nohist: "1", + sudopwclearonsleep: String(clear), + }; + console.log(kwargs); + return GlobalModel.submitCommand("client", "set", null, kwargs, false); + } + editBookmark(bookmarkId: string, desc: string, cmdstr: string) { let kwargs = { nohist: "1", diff --git a/src/models/model.ts b/src/models/model.ts index 348f88f6b..361f729d7 100644 --- a/src/models/model.ts +++ b/src/models/model.ts @@ -455,6 +455,22 @@ class Model { return this.termFontSize.get(); } + getSudoPwStore(): string { + let cdata = this.clientData.get(); + return cdata?.feopts?.sudopwstore ?? appconst.DefaultSudoPwStore; + } + + getSudoPwTimeout(): number { + let cdata = this.clientData.get(); + const sudoPwTimeoutMs = cdata?.feopts?.sudopwtimeoutms ?? appconst.DefaultSudoPwTimeoutMs; + return sudoPwTimeoutMs / 1000 / 60; + } + + getSudoPwClearOnSleep(): boolean { + let cdata = this.clientData.get(); + return !cdata?.feopts?.nosudopwclearonsleep; + } + updateTermFontSizeVars() { let lhe = this.recomputeLineHeightEnv(); mobx.action(() => { diff --git a/src/types/custom.d.ts b/src/types/custom.d.ts index 7a9db4acb..7789fce4c 100644 --- a/src/types/custom.d.ts +++ b/src/types/custom.d.ts @@ -598,6 +598,9 @@ declare global { termfontfamily: string; theme: NativeThemeSource; termthemesettings: TermThemeSettingsType; + sudopwstore: "on" | "off" | "notimeout"; + sudopwtimeoutms: number; + nosudopwclearonsleep: boolean; }; type ConfirmFlagsType = { diff --git a/wavesrv/cmd/main-server.go b/wavesrv/cmd/main-server.go index 3709ed46e..ef07a022e 100644 --- a/wavesrv/cmd/main-server.go +++ b/wavesrv/cmd/main-server.go @@ -209,6 +209,33 @@ func HandleSetWinSize(w http.ResponseWriter, r *http.Request) { WriteJsonSuccess(w, true) } +func HandlePowerMonitor(w http.ResponseWriter, r *http.Request) { + decoder := json.NewDecoder(r.Body) + var body sstore.PowerMonitorEventType + err := decoder.Decode(&body) + if err != nil { + WriteJsonError(w, fmt.Errorf(ErrorDecodingJson, err)) + return + } + cdata, err := sstore.EnsureClientData(r.Context()) + if err != nil { + WriteJsonError(w, err) + return + } + switch body.Status { + case "suspend": + if !cdata.FeOpts.NoSudoPwClearOnSleep && cdata.FeOpts.SudoPwStore != "notimeout" { + for _, proc := range remote.GetRemoteMap() { + proc.ClearCachedSudoPw() + } + } + WriteJsonSuccess(w, true) + default: + WriteJsonError(w, fmt.Errorf("unknown status: %s", body.Status)) + return + } +} + // params: fg, active, open func HandleLogActiveState(w http.ResponseWriter, r *http.Request) { decoder := json.NewDecoder(r.Body) @@ -1149,6 +1176,7 @@ func main() { gr.HandleFunc(bufferedpipe.BufferedPipeGetterUrl, AuthKeyWrapAllowHmac(bufferedpipe.HandleGetBufferedPipeOutput)) gr.HandleFunc("/api/get-client-data", AuthKeyWrap(HandleGetClientData)) gr.HandleFunc("/api/set-winsize", AuthKeyWrap(HandleSetWinSize)) + gr.HandleFunc("/api/power-monitor", AuthKeyWrap(HandlePowerMonitor)) gr.HandleFunc("/api/log-active-state", AuthKeyWrap(HandleLogActiveState)) gr.HandleFunc("/api/read-file", AuthKeyWrapAllowHmac(HandleReadFile)) gr.HandleFunc("/api/write-file", AuthKeyWrap(HandleWriteFile)).Methods("POST") diff --git a/wavesrv/pkg/cmdrunner/cmdrunner.go b/wavesrv/pkg/cmdrunner/cmdrunner.go index 6ba5a56ee..3e31f6383 100644 --- a/wavesrv/pkg/cmdrunner/cmdrunner.go +++ b/wavesrv/pkg/cmdrunner/cmdrunner.go @@ -642,10 +642,17 @@ func RunCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.Up } runPacket.Command = strings.TrimSpace(cmdStr) runPacket.ReturnState = resolveBool(pk.Kwargs["rtnstate"], isRtnStateCmd) + + clientData, err := sstore.EnsureClientData(ctx) + if err != nil { + return nil, fmt.Errorf("cannot retrieve client data: %v", err) + } + feOpts := clientData.FeOpts + if sudoArg, ok := pk.Kwargs[KwArgSudo]; ok { - runPacket.IsSudo = resolveBool(sudoArg, false) + runPacket.IsSudo = resolveBool(sudoArg, false) && feOpts.SudoPwStore != "off" } else { - runPacket.IsSudo = IsSudoCommand(cmdStr) + runPacket.IsSudo = IsSudoCommand(cmdStr) && feOpts.SudoPwStore != "off" } rcOpts := remote.RunCommandOpts{ SessionId: ids.SessionId, @@ -5810,6 +5817,13 @@ func CheckOptionAlias(kwargs map[string]string, aliases ...string) (string, bool return "", false } +func validateSudoPwStore(config string) error { + if utilfn.ContainsStr([]string{"on", "off", "notimeout"}, config) { + return nil + } + return fmt.Errorf("%s is not a config option", config) +} + func ClientSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (scbus.UpdatePacket, error) { clientData, err := sstore.EnsureClientData(ctx) if err != nil { @@ -5996,6 +6010,58 @@ func ClientSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sc } varsUpdated = append(varsUpdated, "webgl") } + if sudoPwStoreStr, found := pk.Kwargs["sudopwstore"]; found { + err := validateSudoPwStore(sudoPwStoreStr) + if err != nil { + return nil, fmt.Errorf("invalid sudo pw store, must be \"on\", \"off\", \"notimeout\": %v", err) + } + feOpts := clientData.FeOpts + feOpts.SudoPwStore = strings.ToLower(sudoPwStoreStr) + err = sstore.UpdateClientFeOpts(ctx, feOpts) + if err != nil { + return nil, fmt.Errorf("error updating client feopts: %v", err) + } + // clear all sudo pw if turning off + if feOpts.SudoPwStore == "off" { + for _, proc := range remote.GetRemoteMap() { + proc.ClearCachedSudoPw() + } + } + varsUpdated = append(varsUpdated, "sudopwstore") + } + if sudoPwTimeoutStr, found := pk.Kwargs["sudopwtimeout"]; found { + oldPwTimeout := clientData.FeOpts.SudoPwTimeoutMs / 1000 / 60 // ms to minutes + if oldPwTimeout == 0 { + oldPwTimeout = sstore.DefaultSudoTimeout + } + newSudoPwTimeout, err := resolveNonNegInt(sudoPwTimeoutStr, sstore.DefaultSudoTimeout) + if err != nil { + return nil, fmt.Errorf("invalid sudo pw timeout, must be a number greater than 0: %v", err) + } + if newSudoPwTimeout == 0 { + return nil, fmt.Errorf("invalid sudo pw timeout, must be a number greater than 0") + } + feOpts := clientData.FeOpts + feOpts.SudoPwTimeoutMs = newSudoPwTimeout * 60 * 1000 // minutes to ms + err = sstore.UpdateClientFeOpts(ctx, feOpts) + if err != nil { + return nil, fmt.Errorf("error updating client feopts: %v", err) + } + for _, proc := range remote.GetRemoteMap() { + proc.ChangeSudoTimeout(int64(newSudoPwTimeout - oldPwTimeout)) + } + varsUpdated = append(varsUpdated, "sudopwtimeout") + } + if sudoPwClearOnSleepStr, found := pk.Kwargs["sudopwclearonsleep"]; found { + newSudoPwClearOnSleep := resolveBool(sudoPwClearOnSleepStr, true) + feOpts := clientData.FeOpts + feOpts.NoSudoPwClearOnSleep = !newSudoPwClearOnSleep + err = sstore.UpdateClientFeOpts(ctx, feOpts) + if err != nil { + return nil, fmt.Errorf("error updating client feopts: %v", err) + } + varsUpdated = append(varsUpdated, "sudopwclearonsleep") + } if len(varsUpdated) == 0 { return nil, fmt.Errorf("/client:set requires a value to set: %s", formatStrs([]string{"termfontsize", "termfontfamily", "openaiapitoken", "openaimodel", "openaibaseurl", "openaimaxtokens", "openaimaxchoices", "openaitimeout", "webgl"}, "or", false)) } diff --git a/wavesrv/pkg/remote/remote.go b/wavesrv/pkg/remote/remote.go index cd16910d0..c6a002424 100644 --- a/wavesrv/pkg/remote/remote.go +++ b/wavesrv/pkg/remote/remote.go @@ -2626,11 +2626,21 @@ func sendScreenUpdates(screens []*sstore.ScreenType) { } } -func (msh *MShellProc) startSudoPwClearChecker() { +func (msh *MShellProc) startSudoPwClearChecker(clientData *sstore.ClientData) { + ctx, cancelFn := context.WithCancel(context.Background()) + defer cancelFn() + sudoPwStore := clientData.FeOpts.SudoPwStore for { + clientData, err := sstore.EnsureClientData(ctx) + if err != nil { + log.Printf("*error: cannot obtain client data in sudo pw loop. using fallback: %v", err) + } else { + sudoPwStore = clientData.FeOpts.SudoPwStore + } + shouldExit := false msh.WithLock(func() { - if msh.sudoClearDeadline > 0 && time.Now().Unix() > msh.sudoClearDeadline { + if msh.sudoClearDeadline > 0 && time.Now().Unix() > msh.sudoClearDeadline && sudoPwStore != "notimeout" { msh.sudoPw = nil msh.sudoClearDeadline = 0 } @@ -2668,13 +2678,25 @@ func (msh *MShellProc) sendSudoPassword(sudoPk *packet.SudoRequestPacketType) er } rawSecret = []byte(guiResponse.Text) } - //new + + ctx, cancelFn := context.WithCancel(context.Background()) + defer cancelFn() + clientData, err := sstore.EnsureClientData(ctx) + if err != nil { + return fmt.Errorf("*error: cannot obtain client data: %v", err) + } + sudoPwTimeout := clientData.FeOpts.SudoPwTimeoutMs / 1000 / 60 + if sudoPwTimeout == 0 { + // 0 maps to default + sudoPwTimeout = sstore.DefaultSudoTimeout + } + pwTimeoutDur := time.Duration(sudoPwTimeout) * time.Minute msh.WithLock(func() { msh.sudoPw = rawSecret if msh.sudoClearDeadline == 0 { - go msh.startSudoPwClearChecker() + go msh.startSudoPwClearChecker(clientData) } - msh.sudoClearDeadline = time.Now().Add(SudoTimeoutTime).Unix() + msh.sudoClearDeadline = time.Now().Add(pwTimeoutDur).Unix() }) srvPrivKey, err := ecdh.P256().GenerateKey(rand.Reader) @@ -2767,6 +2789,15 @@ func (msh *MShellProc) ClearCachedSudoPw() { }) } +func (msh *MShellProc) ChangeSudoTimeout(deltaTime int64) { + msh.WithLock(func() { + if msh.sudoClearDeadline != 0 { + updated := msh.sudoClearDeadline + deltaTime*60 + msh.sudoClearDeadline = max(0, updated) + } + }) +} + func (msh *MShellProc) ProcessPackets() { defer msh.WithLock(func() { if msh.Status == StatusConnected { diff --git a/wavesrv/pkg/sstore/sstore.go b/wavesrv/pkg/sstore/sstore.go index fb5c3d641..4aec42f68 100644 --- a/wavesrv/pkg/sstore/sstore.go +++ b/wavesrv/pkg/sstore/sstore.go @@ -43,6 +43,7 @@ const DBWALFileNameBackup = "backup.waveterm.db-wal" const MaxWebShareLineCount = 50 const MaxWebShareScreenCount = 3 const MaxLineStateSize = 4 * 1024 // 4k for now, can raise if needed +const DefaultSudoTimeout = 5 const DefaultSessionName = "default" const LocalRemoteAlias = "local" @@ -232,6 +233,10 @@ type ClientWinSizeType struct { FullScreen bool `json:"fullscreen,omitempty"` } +type PowerMonitorEventType struct { + Status string `json:"status"` +} + type SidebarValueType struct { Collapsed bool `json:"collapsed"` Width int `json:"width"` @@ -250,10 +255,14 @@ type ClientOptsType struct { } type FeOptsType struct { - TermFontSize int `json:"termfontsize,omitempty"` - TermFontFamily string `json:"termfontfamily,omitempty"` - Theme string `json:"theme,omitempty"` - TermThemeSettings map[string]string `json:"termthemesettings"` + TermFontSize int `json:"termfontsize,omitempty"` + TermFontFamily string `json:"termfontfamily,omitempty"` + Theme string `json:"theme,omitempty"` + TermThemeSettings map[string]string `json:"termthemesettings"` + SudoPwStore string `json:"sudopwstore,omitempty"` + SudoPwTimeoutMs int `json:"sudopwtimeoutms,omitempty"` + SudoPwTimeout int `json:"sudopwtimeout,omitempty"` + NoSudoPwClearOnSleep bool `json:"nosudopwclearonsleep,omitempty"` } type ReleaseInfoType struct {