diff --git a/src/models/input.ts b/src/models/input.ts index db35b20dc..f4283220c 100644 --- a/src/models/input.ts +++ b/src/models/input.ts @@ -13,10 +13,10 @@ import { HistoryQueryOpts, HistoryTypeStrs, OpenAICmdInfoChatMessageType, + OV, + StrWithPos, } from "../types/types"; -import { StrWithPos } from "../types/types"; import * as appconst from "../app/appconst"; -import { OV } from "../types/types"; import { Model } from "./model"; import { GlobalCommandRunner } from "./global"; @@ -207,7 +207,6 @@ class InputModel { this.historyQueryOpts.set(opts); let bestIndex = this.findBestNewIndex(oldItem); setTimeout(() => this.setHistoryIndex(bestIndex, true), 10); - return; })(); } @@ -624,13 +623,17 @@ class InputModel { } openAIAssistantChat(): void { - this.aIChatShow.set(true); - this.setAIChatFocus(); + mobx.action(() => { + this.aIChatShow.set(true); + this.setAIChatFocus(); + })(); } closeAIAssistantChat(): void { - this.aIChatShow.set(false); - this.giveFocus(); + mobx.action(() => { + this.aIChatShow.set(false); + this.giveFocus(); + })(); } clearAIAssistantChat(): void { @@ -721,14 +724,6 @@ class InputModel { setCurLine(val: string): void { let hidx = this.historyIndex.get(); mobx.action(() => { - // if (val == "\" ") { - // this.setInputMode("comment"); - // val = ""; - // } - // if (val == "//") { - // this.setInputMode("global"); - // val = ""; - // } if (this.modHistory.length <= hidx) { this.modHistory.length = hidx + 1; } diff --git a/src/models/model.ts b/src/models/model.ts index 70172b36c..af73dea5d 100644 --- a/src/models/model.ts +++ b/src/models/model.ts @@ -22,7 +22,6 @@ import { FeCmdPacketType, ScreenDataType, PtyDataUpdateType, - ModelUpdateType, UpdateMessage, InfoType, StrWithPos, @@ -40,15 +39,18 @@ import { CmdInputTextPacketType, FileInfoType, ExtFile, - HistorySearchParams, - LineStateType, + OV, + OArr, + OMap, + CV, + ScreenNumRunningCommandsUpdateType, + ScreenStatusIndicatorUpdateType, } from "../types/types"; import { WSControl } from "./ws"; import { cmdStatusIsRunning } from "../app/line/lineutil"; import * as appconst from "../app/appconst"; import { remotePtrToString, cmdPacketString } from "../util/modelutil"; import { checkKeyPressed, adaptFromReactOrNativeKeyEvent, setKeyUtilPlatform } from "../util/keyutil"; -import { OV, OArr, OMap, CV } from "../types/types"; import { Session } from "./session"; import { ScreenLines } from "./screenlines"; import { InputModel } from "./input"; @@ -187,7 +189,7 @@ class Model { this.isDev = getApi().getIsDev(); this.authKey = getApi().getAuthKey(); this.ws = new WSControl(this.getBaseWsHostPort(), this.clientId, this.authKey, (message: any) => { - let interactive = message?.interactive ?? false; + const interactive = message?.interactive ?? false; this.runUpdate(message, interactive); }); this.ws.reconnect(); @@ -200,17 +202,17 @@ class Model { this.remotesModel = new RemotesModel(this); this.modalsModel = new ModalsModel(); this.mainSidebarModel = new MainSidebarModel(this); - let isWaveSrvRunning = getApi().getWaveSrvStatus(); + const isWaveSrvRunning = getApi().getWaveSrvStatus(); this.waveSrvRunning = mobx.observable.box(isWaveSrvRunning, { name: "model-wavesrv-running", }); this.platform = this.getPlatform(); this.termFontSize = mobx.computed(() => { - let cdata = this.clientData.get(); - if (cdata == null || cdata.feopts == null || cdata.feopts.termfontsize == null) { + const cdata = this.clientData.get(); + if (cdata?.feopts?.termfontsize == null) { return appconst.DefaultTermFontSize; } - let fontSize = Math.ceil(cdata.feopts.termfontsize); + const fontSize = Math.ceil(cdata.feopts.termfontsize); if (fontSize < appconst.MinFontSize) { return appconst.MinFontSize; } @@ -265,11 +267,11 @@ class Model { } needsTos(): boolean { - let cdata = this.clientData.get(); + const cdata = this.clientData.get(); if (cdata == null) { return false; } - return cdata.clientopts == null || !cdata.clientopts.acceptedtos; + return !cdata.clientopts?.acceptedtos; } refreshClient(): void { @@ -288,7 +290,7 @@ class Model { refocus() { // givefocus() give back focus to cmd or input - let activeScreen = this.getActiveScreen(); + const activeScreen = this.getActiveScreen(); if (screen == null) { return; } @@ -296,8 +298,8 @@ class Model { } getWebSharedScreens(): Screen[] { - let rtn: Screen[] = []; - for (let screen of this.screenMap.values()) { + const rtn: Screen[] = []; + for (const screen of this.screenMap.values()) { if (screen.shareMode.get() == "web") { rtn.push(screen); } @@ -309,7 +311,7 @@ class Model { if (this.clientData.get() == null) { return true; } - let cdata = this.clientData.get(); + const cdata = this.clientData.get(); if (cdata.cmdstoretype == "session") { return true; } @@ -318,8 +320,8 @@ class Model { showAlert(alertMessage: AlertMessageType): Promise { if (alertMessage.confirmflag != null) { - let cdata = this.clientData.get(); - let noConfirm = cdata.clientopts?.confirmflags?.[alertMessage.confirmflag]; + const cdata = this.clientData.get(); + const noConfirm = cdata.clientopts?.confirmflags?.[alertMessage.confirmflag]; if (noConfirm) { return Promise.resolve(true); } @@ -328,7 +330,7 @@ class Model { this.alertMessage.set(alertMessage); this.modalsModel.pushModal(appconst.ALERT); })(); - let prtn = new Promise((resolve, reject) => { + const prtn = new Promise((resolve, reject) => { this.alertPromiseResolver = resolve; }); return prtn; @@ -405,7 +407,7 @@ class Model { } docKeyDownHandler(e: KeyboardEvent) { - let waveEvent = adaptFromReactOrNativeKeyEvent(e); + const waveEvent = adaptFromReactOrNativeKeyEvent(e); if (isModKeyPress(e)) { return; } @@ -447,7 +449,7 @@ class Model { if (this.clearModals()) { return; } - let inputModel = this.inputModel; + const inputModel = this.inputModel; inputModel.toggleInfoMsg(); if (inputModel.inputMode.get() != null) { inputModel.resetInputMode(); @@ -460,9 +462,9 @@ class Model { } if (this.activeMainView.get() == "session" && checkKeyPressed(waveEvent, "Cmd:Ctrl:s")) { e.preventDefault(); - let activeScreen = this.getActiveScreen(); + const activeScreen = this.getActiveScreen(); if (activeScreen != null) { - let isSidebarOpen = activeScreen.isSidebarOpen(); + const isSidebarOpen = activeScreen.isSidebarOpen(); if (isSidebarOpen) { GlobalCommandRunner.screenSidebarClose(); } else { @@ -471,7 +473,7 @@ class Model { } } if (checkKeyPressed(waveEvent, "Cmd:d")) { - let ranDelete = this.deleteActiveLine(); + const ranDelete = this.deleteActiveLine(); if (ranDelete) { e.preventDefault(); } @@ -479,22 +481,22 @@ class Model { } deleteActiveLine(): boolean { - let activeScreen = this.getActiveScreen(); + const activeScreen = this.getActiveScreen(); if (activeScreen == null || activeScreen.getFocusType() != "cmd") { return false; } - let selectedLine = activeScreen.selectedLine.get(); + const selectedLine = activeScreen.selectedLine.get(); if (selectedLine == null || selectedLine <= 0) { return false; } - let line = activeScreen.getLineByNum(selectedLine); + const line = activeScreen.getLineByNum(selectedLine); if (line == null) { return false; } - let cmd = activeScreen.getCmd(line); + const cmd = activeScreen.getCmd(line); if (cmd != null) { if (cmd.isRunning()) { - let info: InfoType = { infomsg: "Cannot delete a running command" }; + const info: InfoType = { infomsg: "Cannot delete a running command" }; this.inputModel.flashInfoMsg(info, 2000); return false; } @@ -507,11 +509,11 @@ class Model { if (this.activeMainView.get() != "session") { return; } - let activeScreen = this.getActiveScreen(); + const activeScreen = this.getActiveScreen(); if (activeScreen == null) { return; } - let rtnp = this.showAlert({ + const rtnp = this.showAlert({ message: "Are you sure you want to delete this screen?", confirm: true, }); @@ -527,7 +529,7 @@ class Model { if (this.activeMainView.get() != "session") { return; } - let activeScreen = this.getActiveScreen(); + const activeScreen = this.getActiveScreen(); if (activeScreen == null) { return; } @@ -536,7 +538,7 @@ class Model { GlobalCommandRunner.lineRestart("E", true); } else { // restart selected line - let selectedLine = activeScreen.selectedLine.get(); + const selectedLine = activeScreen.selectedLine.get(); if (selectedLine == null || selectedLine == 0) { return; } @@ -585,7 +587,7 @@ class Model { } getCurRemoteInstance(): RemoteInstanceType { - let screen = this.getActiveScreen(); + const screen = this.getActiveScreen(); if (screen == null) { return null; } @@ -603,12 +605,12 @@ class Model { } getContentHeight(context: RendererContext): number { - let key = context.screenId + "/" + context.lineId; + const key = context.screenId + "/" + context.lineId; return this.termUsedRowsCache[key]; } setContentHeight(context: RendererContext, height: number): void { - let key = context.screenId + "/" + context.lineId; + const key = context.screenId + "/" + context.lineId; this.termUsedRowsCache[key] = height; GlobalCommandRunner.setTermUsedRows(context, height); } @@ -622,7 +624,7 @@ class Model { } getUIContext(): UIContextType { - let rtn: UIContextType = { + const rtn: UIContextType = { sessionid: null, screenid: null, remote: null, @@ -630,10 +632,10 @@ class Model { linenum: null, build: appconst.VERSION + " " + appconst.BUILD, }; - let session = this.getActiveSession(); + const session = this.getActiveSession(); if (session != null) { rtn.sessionid = session.sessionId; - let screen = session.getActiveScreen(); + const screen = session.getActiveScreen(); if (screen != null) { rtn.screenid = screen.screenId; rtn.remote = screen.curRemote.get(); @@ -653,7 +655,7 @@ class Model { } onLCmd(e: any, mods: KeyModsType) { - let screen = this.getActiveScreen(); + const screen = this.getActiveScreen(); if (screen != null) { GlobalCommandRunner.screenSetFocus("cmd"); } @@ -671,11 +673,11 @@ class Model { if (this.inputModel.hasFocus()) { return { cmdInputFocus: true }; } - let lineElem: any = document.activeElement.closest(".line[data-lineid]"); + const lineElem: any = document.activeElement.closest(".line[data-lineid]"); if (lineElem == null) { return { cmdInputFocus: false }; } - let lineNum = parseInt(lineElem.dataset.linenum); + const lineNum = parseInt(lineElem.dataset.linenum); return { cmdInputFocus: false, lineid: lineElem.dataset.lineid, @@ -685,17 +687,17 @@ class Model { } cmdStatusUpdate(screenId: string, lineId: string, origStatus: string, newStatus: string) { - let wasRunning = cmdStatusIsRunning(origStatus); - let isRunning = cmdStatusIsRunning(newStatus); + const wasRunning = cmdStatusIsRunning(origStatus); + const isRunning = cmdStatusIsRunning(newStatus); if (wasRunning && !isRunning) { - let ptr = this.getActiveLine(screenId, lineId); + const ptr = this.getActiveLine(screenId, lineId); if (ptr != null) { - let screen = ptr.screen; - let renderer = screen.getRenderer(lineId); + const screen = ptr.screen; + const renderer = screen.getRenderer(lineId); if (renderer != null) { renderer.setIsDone(); } - let term = screen.getTermWrap(lineId); + const term = screen.getTermWrap(lineId); if (term != null) { term.cmdDone(); } @@ -747,21 +749,25 @@ class Model { runUpdate(genUpdate: UpdateMessage, interactive: boolean) { mobx.action(() => { - let oldContext = this.getUIContext(); + const oldContext = this.getUIContext(); try { this.runUpdate_internal(genUpdate, oldContext, interactive); } catch (e) { - console.log("error running update", e, genUpdate); + console.warn("error running update", e, genUpdate); throw e; } - let newContext = this.getUIContext(); + const newContext = this.getUIContext(); if (oldContext.sessionid != newContext.sessionid || oldContext.screenid != newContext.screenid) { this.inputModel.resetInput(); - if ("cmdline" in genUpdate) { - // TODO a bit of a hack since this update gets applied in runUpdate_internal. - // we then undo that update with the resetInput, and then redo it with the line below - // not sure how else to handle this for now though - this.inputModel.updateCmdLine(genUpdate.cmdline); + if (!("ptydata64" in genUpdate)) { + const reversedGenUpdate = genUpdate.slice().reverse(); + const lastCmdLine = reversedGenUpdate.find((update) => "cmdline" in update); + if (lastCmdLine) { + // TODO a bit of a hack since this update gets applied in runUpdate_internal. + // we then undo that update with the resetInput, and then redo it with the line below + // not sure how else to handle this for now though + this.inputModel.updateCmdLine(lastCmdLine.cmdline); + } } } else if (remotePtrToString(oldContext.remote) != remotePtrToString(newContext.remote)) { this.inputModel.resetHistory(); @@ -769,145 +775,189 @@ class Model { })(); } + updateScreens(screens: ScreenDataType[]): void { + const mods = genMergeDataMap( + this.screenMap, + screens, + (s: Screen) => s.screenId, + (sdata: ScreenDataType) => sdata.screenid, + (sdata: ScreenDataType) => new Screen(sdata, this) + ); + for (const screenId of mods.removed) { + this.removeScreenLinesByScreenId(screenId); + } + } + + updateSessions(sessions: SessionDataType[]): void { + genMergeData( + this.sessionList, + sessions, + (s: Session) => s.sessionId, + (sdata: SessionDataType) => sdata.sessionid, + (sdata: SessionDataType) => new Session(sdata, this), + (s: Session) => s.sessionIdx.get() + ); + } + + updateActiveSession(sessionId: string): void { + const [oldActiveSessionId, oldActiveScreenId] = this.getActiveIds(); + + if (sessionId != null) { + const newSessionId = sessionId; + if (this.activeSessionId.get() != newSessionId) { + this.activeSessionId.set(newSessionId); + } + } + const [newActiveSessionId, newActiveScreenId] = this.getActiveIds(); + if (oldActiveSessionId != newActiveSessionId || oldActiveScreenId != newActiveScreenId) { + this.activeMainView.set("session"); + this.deactivateScreenLines(); + this.ws.watchScreen(newActiveSessionId, newActiveScreenId); + } + } + + updateScreenNumRunningCommands(numRunningCommandUpdates: ScreenNumRunningCommandsUpdateType[]) { + for (const update of numRunningCommandUpdates) { + this.getScreenById_single(update.screenid)?.setNumRunningCmds(update.num); + } + } + + updateScreenStatusIndicators(screenStatusIndicators: ScreenStatusIndicatorUpdateType[]) { + for (const update of screenStatusIndicators) { + this.getScreenById_single(update.screenid)?.setStatusIndicator(update.status); + } + } + runUpdate_internal(genUpdate: UpdateMessage, uiContext: UIContextType, interactive: boolean) { if ("ptydata64" in genUpdate) { - let ptyMsg: PtyDataUpdateType = genUpdate; + const ptyMsg: PtyDataUpdateType = genUpdate; if (isBlank(ptyMsg.remoteid)) { // regular update this.updatePtyData(ptyMsg); } else { // remote update - let ptyData = base64ToArray(ptyMsg.ptydata64); + const ptyData = base64ToArray(ptyMsg.ptydata64); this.remotesModel.receiveData(ptyMsg.remoteid, ptyMsg.ptypos, ptyData); } return; } - let update: ModelUpdateType = genUpdate; - if ("screens" in update) { - if (update.connect) { - this.screenMap.clear(); - } - let mods = genMergeDataMap( - this.screenMap, - update.screens, - (s: Screen) => s.screenId, - (sdata: ScreenDataType) => sdata.screenid, - (sdata: ScreenDataType) => new Screen(sdata, this) - ); - for (const screenId of mods.removed) { - this.removeScreenLinesByScreenId(screenId); - } - } - if ("sessions" in update || "activesessionid" in update) { - if (update.connect) { - this.sessionList.clear(); - } - let [oldActiveSessionId, oldActiveScreenId] = this.getActiveIds(); - genMergeData( - this.sessionList, - update.sessions, - (s: Session) => s.sessionId, - (sdata: SessionDataType) => sdata.sessionid, - (sdata: SessionDataType) => new Session(sdata, this), - (s: Session) => s.sessionIdx.get() - ); - if ("activesessionid" in update) { - let newSessionId = update.activesessionid; - if (this.activeSessionId.get() != newSessionId) { - this.activeSessionId.set(newSessionId); + let showedRemotesModal = false; + genUpdate.forEach((update) => { + if (update.connect != null) { + if (update.connect.screens != null) { + this.screenMap.clear(); + this.updateScreens(update.connect.screens); } + if (update.connect.sessions != null) { + this.sessionList.clear(); + this.updateSessions(update.connect.sessions); + } + if (update.connect.remotes != null) { + this.remotes.clear(); + this.updateRemotes(update.connect.remotes); + } + if (update.connect.activesessionid != null) { + this.updateActiveSession(update.connect.activesessionid); + } + if (update.connect.screennumrunningcommands != null) { + this.updateScreenNumRunningCommands(update.connect.screennumrunningcommands); + } + if (update.connect.screenstatusindicators != null) { + this.updateScreenStatusIndicators(update.connect.screenstatusindicators); + } + + this.sessionListLoaded.set(true); + this.remotesLoaded.set(true); + } else if (update.screen != null) { + this.updateScreens([update.screen]); + } else if (update.session != null) { + this.updateSessions([update.session]); + } else if (update.activesessionid != null) { + this.updateActiveSession(update.activesessionid); + } else if (update.line != null) { + this.addLineCmd(update.line.line, update.line.cmd, interactive); + } else if (update.cmd != null) { + this.updateCmd(update.cmd); + } else if (update.screenlines != null) { + this.updateScreenLines(update.screenlines, false); + } else if (update.remote != null) { + this.updateRemotes([update.remote]); + // This code's purpose is to show view remote connection modal when a new connection is added + if (!showedRemotesModal && this.remotesModel.recentConnAddedState.get()) { + showedRemotesModal = true; + this.remotesModel.openReadModal(update.remote.remoteid); + } + } else if (update.mainview != null) { + switch (update.mainview.mainview) { + case "session": + this.activeMainView.set("session"); + break; + case "history": + if (update.mainview.historyview != null) { + this.historyViewModel.showHistoryView(update.mainview.historyview); + } else { + console.warn("invalid historyview in update:", update.mainview); + } + break; + case "bookmarks": + if (update.mainview.bookmarksview != null) { + this.bookmarksModel.showBookmarksView( + update.mainview.bookmarksview?.bookmarks ?? [], + update.mainview.bookmarksview?.selectedbookmark + ); + } else { + console.warn("invalid bookmarksview in update:", update.mainview); + } + break; + case "plugins": + this.pluginsModel.showPluginsView(); + break; + default: + console.warn("invalid mainview in update:", update.mainview); + } + } else if (update.bookmarks != null) { + if (update.bookmarks.bookmarks != null) { + this.bookmarksModel.mergeBookmarks(update.bookmarks.bookmarks); + } + } else if (update.clientdata != null) { + this.clientData.set(update.clientdata); + } else if (update.cmdline != null) { + this.inputModel.updateCmdLine(update.cmdline); + } else if (update.openaicmdinfochat != null) { + this.inputModel.setOpenAICmdInfoChat(update.openaicmdinfochat); + } else if (update.screenstatusindicator != null) { + this.updateScreenStatusIndicators([update.screenstatusindicator]); + } else if (update.screennumrunningcommands != null) { + this.updateScreenNumRunningCommands([update.screennumrunningcommands]); + } else if (update.userinputrequest != null) { + let userInputRequest: UserInputRequest = update.userinputrequest; + this.modalsModel.pushModal(appconst.USER_INPUT, userInputRequest); + } else if (interactive) { + if (update.info != null) { + const info: InfoType = update.info; + this.inputModel.flashInfoMsg(info, info.timeoutms); + } else if (update.remoteview != null) { + const rview: RemoteViewType = update.remoteview; + if (rview.remoteedit != null) { + this.remotesModel.openEditModal({ ...rview.remoteedit }); + } + } else if (update.alertmessage != null) { + const alertMessage: AlertMessageType = update.alertmessage; + this.showAlert(alertMessage); + } else if (update.history != null) { + if ( + uiContext.sessionid == update.history.sessionid && + uiContext.screenid == update.history.screenid + ) { + this.inputModel.setHistoryInfo(update.history); + } + } else if (this.isDev) { + console.log("did not match update", update); + } + } else if (this.isDev) { + console.log("did not match update", update); } - let [newActiveSessionId, newActiveScreenId] = this.getActiveIds(); - if (oldActiveSessionId != newActiveSessionId || oldActiveScreenId != newActiveScreenId) { - this.activeMainView.set("session"); - this.deactivateScreenLines(); - this.ws.watchScreen(newActiveSessionId, newActiveScreenId); - } - } - if ("line" in update) { - this.addLineCmd(update.line, update.cmd, interactive); - } else if ("cmd" in update) { - this.updateCmd(update.cmd); - } - if ("lines" in update) { - for (const line of update.lines) { - this.addLineCmd(line, null, interactive); - } - } - if ("screenlines" in update) { - this.updateScreenLines(update.screenlines, false); - } - if ("remotes" in update) { - if (update.connect) { - this.remotes.clear(); - } - this.updateRemotes(update.remotes); - // This code's purpose is to show view remote connection modal when a new connection is added - if (update.remotes?.length && this.remotesModel.recentConnAddedState.get()) { - this.remotesModel.openReadModal(update.remotes[0].remoteid); - } - } - if ("mainview" in update) { - if (update.mainview == "plugins") { - this.pluginsModel.showPluginsView(); - } else if (update.mainview == "bookmarks") { - this.bookmarksModel.showBookmarksView(update.bookmarks, update.selectedbookmark); - } else if (update.mainview == "session") { - this.activeMainView.set("session"); - } else if (update.mainview == "history") { - this.historyViewModel.showHistoryView(update.historyviewdata); - } else { - console.log("invalid mainview in update:", update.mainview); - } - } else if ("bookmarks" in update) { - this.bookmarksModel.mergeBookmarks(update.bookmarks); - } - if ("clientdata" in update) { - this.clientData.set(update.clientdata); - } - if (interactive && "info" in update) { - let info: InfoType = update.info; - this.inputModel.flashInfoMsg(info, info.timeoutms); - } - if (interactive && "remoteview" in update) { - let rview: RemoteViewType = update.remoteview; - if (rview.remoteedit != null) { - this.remotesModel.openEditModal({ ...rview.remoteedit }); - } - } - if (interactive && "alertmessage" in update) { - let alertMessage: AlertMessageType = update.alertmessage; - this.showAlert(alertMessage); - } - if ("cmdline" in update) { - this.inputModel.updateCmdLine(update.cmdline); - } - if (interactive && "history" in update) { - if (uiContext.sessionid == update.history.sessionid && uiContext.screenid == update.history.screenid) { - this.inputModel.setHistoryInfo(update.history); - } - } - if ("connect" in update) { - this.sessionListLoaded.set(true); - this.remotesLoaded.set(true); - } - if ("openaicmdinfochat" in update) { - this.inputModel.setOpenAICmdInfoChat(update.openaicmdinfochat); - } - if ("screenstatusindicators" in update) { - for (const indicator of update.screenstatusindicators) { - this.getScreenById_single(indicator.screenid)?.setStatusIndicator(indicator.status); - } - } - if ("screennumrunningcommands" in update) { - for (const snc of update.screennumrunningcommands) { - this.getScreenById_single(snc.screenid)?.setNumRunningCmds(snc.num); - } - } - if ("userinputrequest" in update) { - let userInputRequest: UserInputRequest = update.userinputrequest; - this.modalsModel.pushModal(appconst.USER_INPUT, userInputRequest); - } + }); } updateRemotes(remotes: RemoteType[]): void { @@ -919,7 +969,7 @@ class Model { } getSessionNames(): Record { - let rtn: Record = {}; + const rtn: Record = {}; for (const session of this.sessionList) { rtn[session.sessionId] = session.name.get(); } @@ -927,8 +977,8 @@ class Model { } getScreenNames(): Record { - let rtn: Record = {}; - for (let screen of this.screenMap.values()) { + const rtn: Record = {}; + for (const screen of this.screenMap.values()) { rtn[screen.screenId] = screen.name.get(); } return rtn; @@ -958,13 +1008,13 @@ class Model { updateScreenLines(slines: ScreenLinesType, load: boolean) { mobx.action(() => { - let existingWin = this.screenLines.get(slines.screenid); + const existingWin = this.screenLines.get(slines.screenid); if (existingWin == null) { if (!load) { console.log("cannot update screen-lines that does not exist", slines.screenid); return; } - let newWindow = new ScreenLines(slines.screenid); + const newWindow = new ScreenLines(slines.screenid); this.screenLines.set(slines.screenid, newWindow); newWindow.updateData(slines, load); } else { @@ -989,8 +1039,8 @@ class Model { } getSessionScreens(sessionId: string): Screen[] { - let rtn: Screen[] = []; - for (let screen of this.screenMap.values()) { + const rtn: Screen[] = []; + for (const screen of this.screenMap.values()) { if (screen.sessionId == sessionId) { rtn.push(screen); } @@ -999,7 +1049,7 @@ class Model { } getScreenLinesForActiveScreen(): ScreenLines { - let screen = this.getActiveScreen(); + const screen = this.getActiveScreen(); if (screen == null) { return null; } @@ -1007,7 +1057,7 @@ class Model { } getActiveScreen(): Screen { - let session = this.getActiveSession(); + const session = this.getActiveSession(); if (session == null) { return null; } @@ -1018,11 +1068,11 @@ class Model { if (cmd == null || !cmd.restarted) { return; } - let screen = this.screenMap.get(cmd.screenid); + const screen = this.screenMap.get(cmd.screenid); if (screen == null) { return; } - let termWrap = screen.getTermWrap(cmd.lineid); + const termWrap = screen.getTermWrap(cmd.lineid); if (termWrap == null) { return; } @@ -1030,7 +1080,7 @@ class Model { } addLineCmd(line: LineType, cmd: CmdDataType, interactive: boolean) { - let slines = this.getScreenLinesById(line.screenid); + const slines = this.getScreenLinesById(line.screenid); if (slines == null) { return; } @@ -1039,7 +1089,7 @@ class Model { } updateCmd(cmd: CmdDataType) { - let slines = this.screenLines.get(cmd.screenid); + const slines = this.screenLines.get(cmd.screenid); if (slines != null) { slines.updateCmd(cmd); } @@ -1050,12 +1100,12 @@ class Model { if (update == null || "ptydata64" in update) { return false; } - return update.info != null || update.history != null; + return update.some((u) => u.info != null || u.history != null); } getClientDataLoop(loopNum: number): void { this.getClientData(); - let clientStop = this.getHasClientStop(); + const clientStop = this.getHasClientStop(); if (this.clientData.get() != null && !clientStop) { return; } @@ -1073,13 +1123,13 @@ class Model { } getClientData(): void { - let url = new URL(this.getBaseHostPort() + "/api/get-client-data"); - let fetchHeaders = this.getFetchHeaders(); + const url = new URL(this.getBaseHostPort() + "/api/get-client-data"); + const fetchHeaders = this.getFetchHeaders(); fetch(url, { method: "post", body: null, headers: fetchHeaders }) .then((resp) => handleJsonFetchResponse(url, resp)) .then((data) => { mobx.action(() => { - let clientData: ClientDataType = data.data; + const clientData: ClientDataType = data.data; this.clientData.set(clientData); })(); }) @@ -1096,10 +1146,10 @@ class Model { } } // adding cmdStr for debugging only (easily filter run-command calls in the network tab of debugger) - let cmdStr = cmdPk.metacmd + (cmdPk.metasubcmd ? ":" + cmdPk.metasubcmd : ""); - let url = new URL(this.getBaseHostPort() + "/api/run-command?cmd=" + cmdStr); - let fetchHeaders = this.getFetchHeaders(); - let prtn = fetch(url, { + const cmdStr = cmdPk.metacmd + (cmdPk.metasubcmd ? ":" + cmdPk.metasubcmd : ""); + const url = new URL(this.getBaseHostPort() + "/api/run-command?cmd=" + cmdStr); + const fetchHeaders = this.getFetchHeaders(); + const prtn = fetch(url, { method: "post", body: JSON.stringify(cmdPk), headers: fetchHeaders, @@ -1107,7 +1157,7 @@ class Model { .then((resp) => handleJsonFetchResponse(url, resp)) .then((data) => { mobx.action(() => { - let update = data.data; + const update = data.data; if (update != null) { this.runUpdate(update, interactive); } @@ -1135,7 +1185,7 @@ class Model { kwargs: Record, interactive: boolean ): Promise { - let pk: FeCmdPacketType = { + const pk: FeCmdPacketType = { type: "fecmd", metacmd: metaCmd, metasubcmd: metaSubCmd, @@ -1157,9 +1207,9 @@ class Model { } submitChatInfoCommand(chatMsg: string, curLineStr: string, clear: boolean): Promise { - let commandStr = "/chat " + chatMsg; - let interactive = false; - let pk: FeCmdPacketType = { + const commandStr = "/chat " + chatMsg; + const interactive = false; + const pk: FeCmdPacketType = { type: "fecmd", metacmd: "eval", args: [commandStr], @@ -1179,7 +1229,7 @@ class Model { } submitRawCommand(cmdStr: string, addToHistory: boolean, interactive: boolean): Promise { - let pk: FeCmdPacketType = { + const pk: FeCmdPacketType = { type: "fecmd", metacmd: "eval", args: [cmdStr], @@ -1196,16 +1246,16 @@ class Model { // returns [sessionId, screenId] getActiveIds(): [string, string] { - let activeSession = this.getActiveSession(); - let activeScreen = this.getActiveScreen(); + const activeSession = this.getActiveSession(); + const activeScreen = this.getActiveScreen(); return [activeSession?.sessionId, activeScreen?.screenId]; } _loadScreenLinesAsync(newWin: ScreenLines) { this.screenLines.set(newWin.screenId, newWin); - let usp = new URLSearchParams({ screenid: newWin.screenId }); - let url = new URL(this.getBaseHostPort() + "/api/get-screen-lines?" + usp.toString()); - let fetchHeaders = this.getFetchHeaders(); + const usp = new URLSearchParams({ screenid: newWin.screenId }); + const url = new URL(this.getBaseHostPort() + "/api/get-screen-lines?" + usp.toString()); + const fetchHeaders = this.getFetchHeaders(); fetch(url, { headers: fetchHeaders }) .then((resp) => handleJsonFetchResponse(url, resp)) .then((data) => { @@ -1213,7 +1263,7 @@ class Model { console.log("null screen-lines returned from get-screen-lines"); return; } - let slines: ScreenLinesType = data.data; + const slines: ScreenLinesType = data.data; this.updateScreenLines(slines, true); }) .catch((err) => { @@ -1222,7 +1272,7 @@ class Model { } loadScreenLines(screenId: string): ScreenLines { - let newWin = new ScreenLines(screenId); + const newWin = new ScreenLines(screenId); setTimeout(() => this._loadScreenLinesAsync(newWin), 0); return newWin; } @@ -1235,7 +1285,7 @@ class Model { } getRemoteNames(): Record { - let rtn: Record = {}; + const rtn: Record = {}; for (const remote of this.remotes) { if (!isBlank(remote.remotealias)) { rtn[remote.remoteid] = remote.remotealias; @@ -1260,7 +1310,7 @@ class Model { } getCmdByScreenLine(screenId: string, lineId: string): Cmd { - let slines = this.getScreenLinesById(screenId); + const slines = this.getScreenLinesById(screenId); if (slines == null) { return null; } @@ -1268,14 +1318,14 @@ class Model { } getActiveLine(screenId: string, lineid: string): SWLinePtr { - let slines = this.screenLines.get(screenId); + const slines = this.screenLines.get(screenId); if (slines == null) { return null; } if (!slines.loaded.get()) { return null; } - let cmd = slines.getCmd(lineid); + const cmd = slines.getCmd(lineid); if (cmd == null) { return null; } @@ -1289,12 +1339,12 @@ class Model { if (line == null) { return null; } - let screen = this.getScreenById_single(slines.screenId); + const screen = this.getScreenById_single(slines.screenId); return { line: line, slines: slines, screen: screen }; } updatePtyData(ptyMsg: PtyDataUpdateType): void { - let linePtr = this.getActiveLine(ptyMsg.screenid, ptyMsg.lineid); + const linePtr = this.getActiveLine(ptyMsg.screenid, ptyMsg.lineid); if (linePtr != null) { linePtr.screen.updatePtyData(ptyMsg); } @@ -1320,7 +1370,7 @@ class Model { } sendCmdInputText(screenId: string, sp: StrWithPos) { - let pk: CmdInputTextPacketType = { + const pk: CmdInputTextPacketType = { type: "cmdinputtext", seqnum: this.getNextPacketSeqNum(), screenid: screenId, @@ -1334,7 +1384,7 @@ class Model { } resolveRemoteIdToRef(remoteId: string) { - let remote = this.getRemote(remoteId); + const remote = this.getRemote(remoteId); if (remote == null) { return "[unknown]"; } @@ -1345,7 +1395,7 @@ class Model { } resolveRemoteIdToFullRef(remoteId: string) { - let remote = this.getRemote(remoteId); + const remote = this.getRemote(remoteId); if (remote == null) { return "[unknown]"; } @@ -1356,17 +1406,17 @@ class Model { } readRemoteFile(screenId: string, lineId: string, path: string): Promise { - let urlParams = { + const urlParams = { screenid: screenId, lineid: lineId, path: path, }; - let usp = new URLSearchParams(urlParams); - let url = new URL(this.getBaseHostPort() + "/api/read-file?" + usp.toString()); - let fetchHeaders = this.getFetchHeaders(); + const usp = new URLSearchParams(urlParams); + const url = new URL(this.getBaseHostPort() + "/api/read-file?" + usp.toString()); + const fetchHeaders = this.getFetchHeaders(); let fileInfo: FileInfoType = null; let badResponseStr: string = null; - let prtn = fetch(url, { method: "get", headers: fetchHeaders }) + const prtn = fetch(url, { method: "get", headers: fetchHeaders }) .then((resp) => { if (!resp.ok) { badResponseStr = sprintf( @@ -1381,14 +1431,14 @@ class Model { }) .then((blobOrText: any) => { if (blobOrText instanceof Blob) { - let blob: Blob = blobOrText; - let file = new File([blob], fileInfo.name, { type: blob.type, lastModified: fileInfo.modts }); - let isWriteable = (fileInfo.perm & 0o222) > 0; // checks for unix permission "w" bits + const blob: Blob = blobOrText; + const file = new File([blob], fileInfo.name, { type: blob.type, lastModified: fileInfo.modts }); + const isWriteable = (fileInfo.perm & 0o222) > 0; // checks for unix permission "w" bits (file as any).readOnly = !isWriteable; (file as any).notFound = !!fileInfo.notfound; return file as ExtFile; } else { - let textError: string = blobOrText; + const textError: string = blobOrText; if (textError == null || textError.length == 0) { throw new Error(badResponseStr); } @@ -1398,7 +1448,7 @@ class Model { return prtn; } - writeRemoteFile( + async writeRemoteFile( screenId: string, lineId: string, path: string, @@ -1406,24 +1456,21 @@ class Model { opts?: { useTemp?: boolean } ): Promise { opts = opts || {}; - let params = { + const params = { screenid: screenId, lineid: lineId, path: path, usetemp: !!opts.useTemp, }; - let formData = new FormData(); + const formData = new FormData(); formData.append("params", JSON.stringify(params)); - let blob = new Blob([data], { type: "application/octet-stream" }); + const blob = new Blob([data], { type: "application/octet-stream" }); formData.append("data", blob); - let url = new URL(this.getBaseHostPort() + "/api/write-file"); - let fetchHeaders = this.getFetchHeaders(); - let prtn = fetch(url, { method: "post", headers: fetchHeaders, body: formData }); - return prtn - .then((resp) => handleJsonFetchResponse(url, resp)) - .then((_) => { - return; - }); + const url = new URL(this.getBaseHostPort() + "/api/write-file"); + const fetchHeaders = this.getFetchHeaders(); + const prtn = fetch(url, { method: "post", headers: fetchHeaders, body: formData }); + const resp = await prtn; + const _ = await handleJsonFetchResponse(url, resp); } } diff --git a/src/models/model_old.ts b/src/models/model_old.ts-deprecated similarity index 100% rename from src/models/model_old.ts rename to src/models/model_old.ts-deprecated diff --git a/src/models/screenlines.ts b/src/models/screenlines.ts index 5e29baa0e..d0fea3c95 100644 --- a/src/models/screenlines.ts +++ b/src/models/screenlines.ts @@ -139,7 +139,7 @@ class ScreenLines { return; } let lineIdx = 0; - for (lineIdx = 0; lineIdx < lines.length; lineIdx++) { + for (lineIdx; lineIdx < lines.length; lineIdx++) { let lineId = lines[lineIdx].lineid; let curTs = lines[lineIdx].ts; if (lineId == line.lineid) { diff --git a/src/types/types.ts b/src/types/types.ts index 05eb47ae7..f92bd8825 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -29,7 +29,6 @@ type SessionDataType = { // for updates remove?: boolean; - full?: boolean; }; type LineStateType = { [k: string]: any }; @@ -92,7 +91,6 @@ type ScreenDataType = { anchor: { anchorline: number; anchoroffset: number }; // for updates - full?: boolean; remove?: boolean; }; @@ -260,6 +258,11 @@ type CmdDataType = { restarted?: boolean; }; +type LineUpdateType = { + line: LineType; + cmd: CmdDataType; +}; + type PtyDataUpdateType = { screenid: string; lineid: string; @@ -309,30 +312,47 @@ type ScreenNumRunningCommandsUpdateType = { num: number; }; +type ConnectUpdateType = { + sessions: SessionDataType[]; + screens: ScreenDataType[]; + remotes: RemoteType[]; + screenstatusindicators: ScreenStatusIndicatorUpdateType[]; + screennumrunningcommands: ScreenNumRunningCommandsUpdateType[]; + activesessionid: string; +}; + +type BookmarksUpdateType = { + bookmarks: BookmarkType[]; + selectedbookmark: string; +}; + +type MainViewUpdateType = { + mainview: string; + historyview?: HistoryViewDataType; + bookmarksview?: BookmarksUpdateType; +}; + type ModelUpdateType = { interactive: boolean; - sessions?: SessionDataType[]; + session?: SessionDataType; activesessionid?: string; - screens?: ScreenDataType[]; + screen?: ScreenDataType; screenlines?: ScreenLinesType; - line?: LineType; - lines?: LineType[]; + line?: LineUpdateType; cmd?: CmdDataType; info?: InfoType; cmdline?: StrWithPos; - remotes?: RemoteType[]; + remote?: RemoteType; history?: HistoryInfoType; - connect?: boolean; - mainview?: string; - bookmarks?: BookmarkType[]; - selectedbookmark?: string; + connect?: ConnectUpdateType; + mainview?: MainViewUpdateType; + bookmarks?: BookmarksUpdateType; clientdata?: ClientDataType; - historyviewdata?: HistoryViewDataType; remoteview?: RemoteViewType; openaicmdinfochat?: OpenAICmdInfoChatMessageType[]; alertmessage?: AlertMessageType; - screenstatusindicators?: ScreenStatusIndicatorUpdateType[]; - screennumrunningcommands?: ScreenNumRunningCommandsUpdateType[]; + screenstatusindicator?: ScreenStatusIndicatorUpdateType; + screennumrunningcommands?: ScreenNumRunningCommandsUpdateType; userinputrequest?: UserInputRequest; }; @@ -415,7 +435,7 @@ type ContextMenuOpts = { showCut?: boolean; }; -type UpdateMessage = PtyDataUpdateType | ModelUpdateType; +type UpdateMessage = PtyDataUpdateType | ModelUpdateType[]; type RendererContext = { screenId: string; @@ -866,6 +886,7 @@ export type { CmdInputTextPacketType, OpenAICmdInfoChatMessageType, ScreenStatusIndicatorUpdateType, + ScreenNumRunningCommandsUpdateType, OV, OArr, OMap, diff --git a/src/util/util.ts b/src/util/util.ts index baed8faa0..1d94516be 100644 --- a/src/util/util.ts +++ b/src/util/util.ts @@ -1,4 +1,4 @@ -// Copyright 2023, Command Line Inc. +// Copyright 2024, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 import * as mobx from "mobx"; @@ -17,7 +17,7 @@ function isBlank(s: string): boolean { } function handleNotOkResp(resp: any, url: URL): Promise { - let errMsg = sprintf( + const errMsg = sprintf( "Bad status code response from fetch '%s': code=%d %s", url.toString(), resp.status, @@ -41,18 +41,18 @@ function handleNotOkResp(resp: any, url: URL): Promise { } function fetchJsonData(resp: any, ctErr: boolean): Promise { - let contentType = resp.headers.get("Content-Type"); - if (contentType != null && contentType.startsWith("application/json")) { + const contentType = resp.headers.get("Content-Type"); + if (contentType?.startsWith("application/json")) { return resp.text().then((textData) => { let rtnData: any = null; try { rtnData = JSON.parse(textData); } catch (err) { - let errMsg = sprintf("Unparseable JSON: " + err.message); - let rtnErr = new Error(errMsg); + const errMsg = sprintf("Unparseable JSON: " + err.message); + const rtnErr = new Error(errMsg); throw rtnErr; } - if (rtnData != null && rtnData.error) { + if (rtnData?.error) { throw new Error(rtnData.error); } return rtnData; @@ -67,23 +67,23 @@ function handleJsonFetchResponse(url: URL, resp: any): Promise { if (!resp.ok) { return handleNotOkResp(resp, url); } - let rtnData = fetchJsonData(resp, true); + const rtnData = fetchJsonData(resp, true); return rtnData; } function base64ToString(b64: string): string { - let stringBytes = base64.toByteArray(b64); + const stringBytes = base64.toByteArray(b64); return new TextDecoder().decode(stringBytes); } function stringToBase64(input: string): string { - let stringBytes = new TextEncoder().encode(input); + const stringBytes = new TextEncoder().encode(input); return base64.fromByteArray(stringBytes); } function base64ToArray(b64: string): Uint8Array { - let rawStr = atob(b64); - let rtnArr = new Uint8Array(new ArrayBuffer(rawStr.length)); + const rawStr = atob(b64); + const rtnArr = new Uint8Array(new ArrayBuffer(rawStr.length)); for (let i = 0; i < rawStr.length; i++) { rtnArr[i] = rawStr.charCodeAt(i); } @@ -92,7 +92,6 @@ function base64ToArray(b64: string): Uint8Array { interface IDataType { remove?: boolean; - full?: boolean; } interface IObjType { @@ -113,31 +112,28 @@ function genMergeSimpleData( if (dataArr == null || dataArr.length == 0) { return; } - let objMap: Record = {}; - for (let i = 0; i < objs.length; i++) { - let obj = objs[i]; - let id = idFn(obj); + const objMap: Record = {}; + for (const obj of objs) { + const id = idFn(obj); objMap[id] = obj; } - for (let i = 0; i < dataArr.length; i++) { - let dataItem = dataArr[i]; + for (const dataItem of dataArr) { if (dataItem == null) { console.log("genMergeSimpleData, null item"); console.trace(); } - let id = idFn(dataItem); + const id = idFn(dataItem); if (dataItem.remove) { delete objMap[id]; - continue; } else { objMap[id] = dataItem; } } - let newObjs = Object.values(objMap); + const newObjs = Object.values(objMap); if (sortIdxFn) { newObjs.sort((a, b) => { - let astr = sortIdxFn(a); - let bstr = sortIdxFn(b); + const astr = sortIdxFn(a); + const bstr = sortIdxFn(b); return astr.localeCompare(bstr); }); } @@ -155,20 +151,18 @@ function genMergeData, DataType extends IData if (dataArr == null || dataArr.length == 0) { return; } - let objMap: Record = {}; - for (let i = 0; i < objs.length; i++) { - let obj = objs[i]; - let id = objIdFn(obj); + const objMap: Record = {}; + for (const obj of objs) { + const id = objIdFn(obj); objMap[id] = obj; } - for (let i = 0; i < dataArr.length; i++) { - let dataItem = dataArr[i]; + for (const dataItem of dataArr) { if (dataItem == null) { console.log("genMergeData, null item"); console.trace(); continue; } - let id = dataIdFn(dataItem); + const id = dataIdFn(dataItem); let obj = objMap[id]; if (dataItem.remove) { if (obj != null) { @@ -178,17 +172,13 @@ function genMergeData, DataType extends IData continue; } if (obj == null) { - if (!dataItem.full) { - console.log("cannot create object, dataitem is not full", objs, dataItem); - continue; - } obj = ctorFn(dataItem); objMap[id] = obj; continue; } obj.mergeData(dataItem); } - let newObjs = Object.values(objMap); + const newObjs = Object.values(objMap); if (sortIdxFn) { newObjs.sort((a, b) => { return sortIdxFn(a) - sortIdxFn(b); @@ -204,18 +194,17 @@ function genMergeDataMap, DataType extends ID dataIdFn: (data: DataType) => string, ctorFn: (data: DataType) => ObjType ): { added: string[]; removed: string[] } { - let rtn: { added: string[]; removed: string[] } = { added: [], removed: [] }; + const rtn: { added: string[]; removed: string[] } = { added: [], removed: [] }; if (dataArr == null || dataArr.length == 0) { return rtn; } - for (let i = 0; i < dataArr.length; i++) { - let dataItem = dataArr[i]; + for (const dataItem of dataArr) { if (dataItem == null) { console.log("genMergeDataMap, null item"); console.trace(); continue; } - let id = dataIdFn(dataItem); + const id = dataIdFn(dataItem); let obj = objMap.get(id); if (dataItem.remove) { if (obj != null) { @@ -226,10 +215,6 @@ function genMergeDataMap, DataType extends ID continue; } if (obj == null) { - if (!dataItem.full) { - console.log("cannot create object, dataitem is not full", dataItem); - continue; - } obj = ctorFn(dataItem); objMap.set(id, obj); rtn.added.push(id); @@ -262,23 +247,23 @@ function incObs(inum: mobx.IObservableValue) { // @check:font function loadFonts() { - let jbmFontNormal = new FontFace("JetBrains Mono", "url('public/fonts/jetbrains-mono-v13-latin-regular.woff2')", { + const jbmFontNormal = new FontFace("JetBrains Mono", "url('public/fonts/jetbrains-mono-v13-latin-regular.woff2')", { style: "normal", weight: "400", }); - let jbmFont200 = new FontFace("JetBrains Mono", "url('public/fonts/jetbrains-mono-v13-latin-200.woff2')", { + const jbmFont200 = new FontFace("JetBrains Mono", "url('public/fonts/jetbrains-mono-v13-latin-200.woff2')", { style: "normal", weight: "200", }); - let jbmFont700 = new FontFace("JetBrains Mono", "url('public/fonts/jetbrains-mono-v13-latin-700.woff2')", { + const jbmFont700 = new FontFace("JetBrains Mono", "url('public/fonts/jetbrains-mono-v13-latin-700.woff2')", { style: "normal", weight: "700", }); - let faFont = new FontFace("FontAwesome", "url(public/fonts/fontawesome-webfont-4.7.woff2)", { + const faFont = new FontFace("FontAwesome", "url(public/fonts/fontawesome-webfont-4.7.woff2)", { style: "normal", weight: "normal", }); - let docFonts: any = document.fonts; // work around ts typing issue + const docFonts: any = document.fonts; // work around ts typing issue docFonts.add(jbmFontNormal); docFonts.add(jbmFont200); docFonts.add(jbmFont700); @@ -296,13 +281,13 @@ function getTodayStr(): string { } function getYesterdayStr(): string { - let d = new Date(); + const d = new Date(); d.setDate(d.getDate() - 1); return getDateStr(d); } function getDateStr(d: Date): string { - let yearStr = String(d.getFullYear()); + const yearStr = String(d.getFullYear()); let monthStr = String(d.getMonth() + 1); if (monthStr.length == 1) { monthStr = "0" + monthStr; @@ -311,31 +296,30 @@ function getDateStr(d: Date): string { if (dayStr.length == 1) { dayStr = "0" + dayStr; } - let dowStr = DOW_STRS[d.getDay()]; + const dowStr = DOW_STRS[d.getDay()]; return dowStr + " " + yearStr + "-" + monthStr + "-" + dayStr; } function getRemoteConnVal(r: RemoteType): number { - if (r.status == "connected") { - return 1; + switch (r.status) { + case "connected": + return 1; + case "connecting": + return 2; + case "disconnected": + return 3; + case "error": + return 4; + default: + return 5; } - if (r.status == "connecting") { - return 2; - } - if (r.status == "disconnected") { - return 3; - } - if (r.status == "error") { - return 4; - } - return 5; } function sortAndFilterRemotes(origRemotes: RemoteType[]): RemoteType[] { - let remotes = origRemotes.filter((r) => !r.archived); + const remotes = origRemotes.filter((r) => !r.archived); remotes.sort((a, b) => { - let connValA = getRemoteConnVal(a); - let connValB = getRemoteConnVal(b); + const connValA = getRemoteConnVal(a); + const connValB = getRemoteConnVal(b); if (connValA != connValB) { return connValA - connValB; } @@ -372,7 +356,7 @@ function openLink(url: string): void { window.open(url, "_blank"); } -function getColorRGB(colorInput) { +function getColorRGB(colorInput: string) { const tempElement = document.createElement("div"); tempElement.style.color = colorInput; document.body.appendChild(tempElement); @@ -399,7 +383,7 @@ function getRemoteName(remote: RemoteType): string { if (remote == null) { return ""; } - let { remotealias, remotecanonicalname } = remote; + const { remotealias, remotecanonicalname } = remote; return remotealias ? `${remotealias} [${remotecanonicalname}]` : remotecanonicalname; } diff --git a/wavesrv/pkg/cmdrunner/cmdrunner.go b/wavesrv/pkg/cmdrunner/cmdrunner.go index e4c4d9df9..7bf428914 100644 --- a/wavesrv/pkg/cmdrunner/cmdrunner.go +++ b/wavesrv/pkg/cmdrunner/cmdrunner.go @@ -515,7 +515,7 @@ func SyncCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore. if err != nil { return nil, err } - update.Interactive = pk.Interactive + sstore.AddInteractiveUpdate(update, pk.Interactive) sstore.MainBus.SendScreenUpdate(ids.ScreenId, update) return nil, nil } @@ -623,7 +623,7 @@ func RunCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.U if err != nil { return nil, err } - update.Interactive = pk.Interactive + sstore.AddInteractiveUpdate(update, pk.Interactive) // this update is sent asynchronously for timing issues. the cmd update comes async as well // so if we return this directly it sometimes gets evaluated first. by pushing it on the MainBus // it ensures it happens after the command creation event. @@ -729,9 +729,9 @@ func EvalCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore. if resolveErr == nil { screen, sidebarErr := implementRunInSidebar(ctx, ids.ScreenId, historyContext.LineId) if sidebarErr == nil { - modelUpdate.UpdateScreen(screen) + sstore.AddScreenUpdate(modelUpdate, screen) } else { - modelUpdate.AddInfoError(fmt.Sprintf("cannot move command to sidebar: %v", sidebarErr)) + sstore.AddInfoMsgUpdateError(modelUpdate, fmt.Sprintf("cannot move command to sidebar: %v", sidebarErr)) } } } @@ -775,9 +775,8 @@ func ScreenArchiveCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) if err != nil { return nil, fmt.Errorf("/screen:archive cannot get updated screen obj: %v", err) } - update := &sstore.ModelUpdate{ - Screens: []*sstore.ScreenType{screen}, - } + update := &sstore.ModelUpdate{} + sstore.AddUpdate(update, *screen) return update, nil } } @@ -798,7 +797,7 @@ func ScreenDeleteCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) if screenId == "" { return nil, fmt.Errorf("/screen:delete no active screen or screen arg passed") } - update, err := sstore.DeleteScreen(ctx, screenId, false) + update, err := sstore.DeleteScreen(ctx, screenId, false, nil) if err != nil { return nil, err } @@ -853,13 +852,14 @@ func ScreenReorderCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) } // Prepare the update packet to send back to the client - update := &sstore.ModelUpdate{ - Screens: screens, - Info: &sstore.InfoMsgType{ - InfoMsg: "screen indices updated successfully", - TimeoutMs: 2000, - }, + update := &sstore.ModelUpdate{} + for _, screen := range screens { + sstore.AddUpdate(update, *screen) } + sstore.AddUpdate(update, sstore.InfoMsgType{ + InfoMsg: "screen indices updated successfully", + TimeoutMs: 2000, + }) return update, nil } @@ -968,13 +968,13 @@ func ScreenSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ss if !setNonAnchor { return nil, nil } - update := &sstore.ModelUpdate{ - Screens: []*sstore.ScreenType{screen}, - Info: &sstore.InfoMsgType{ - InfoMsg: fmt.Sprintf("screen updated %s", formatStrs(varsUpdated, "and", false)), - TimeoutMs: 2000, - }, - } + + update := &sstore.ModelUpdate{} + sstore.AddUpdate(update, *screen) + sstore.AddUpdate(update, sstore.InfoMsgType{ + InfoMsg: fmt.Sprintf("screen updated %s", formatStrs(varsUpdated, "and", false)), + TimeoutMs: 2000, + }) return update, nil } @@ -1043,7 +1043,9 @@ func SidebarOpenCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) ( if err != nil { return nil, err } - return &sstore.ModelUpdate{Screens: []*sstore.ScreenType{screen}}, nil + update := &sstore.ModelUpdate{} + sstore.AddUpdate(update, *screen) + return update, nil } func SidebarCloseCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { @@ -1055,7 +1057,9 @@ func SidebarCloseCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) if err != nil { return nil, err } - return &sstore.ModelUpdate{Screens: []*sstore.ScreenType{screen}}, nil + update := &sstore.ModelUpdate{} + sstore.AddUpdate(update, *screen) + return update, nil } func SidebarAddCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { @@ -1083,7 +1087,9 @@ func SidebarAddCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (s if err != nil { return nil, fmt.Errorf("/%s error updating screenviewopts: %v", GetCmdStr(pk), err) } - return &sstore.ModelUpdate{Screens: []*sstore.ScreenType{screen}}, nil + update := &sstore.ModelUpdate{} + sstore.AddUpdate(update, *screen) + return update, nil } func SidebarRemoveCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { @@ -1105,7 +1111,25 @@ func SidebarRemoveCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) if err != nil { return nil, fmt.Errorf("/%s error updating screenviewopts: %v", GetCmdStr(pk), err) } - return &sstore.ModelUpdate{Screens: []*sstore.ScreenType{screen}}, nil + update := &sstore.ModelUpdate{} + sstore.AddUpdate(update, *screen) + return update, nil +} + +func createRemoteViewRemoteIdUpdate(remoteId string) sstore.UpdatePacket { + update := &sstore.ModelUpdate{} + sstore.AddUpdate(update, sstore.RemoteViewType{ + PtyRemoteId: remoteId, + }) + return update +} + +func createRemoteViewRemoteEditUpdate(redit *sstore.RemoteEditType) sstore.UpdatePacket { + update := &sstore.ModelUpdate{} + sstore.AddUpdate(update, sstore.RemoteViewType{ + RemoteEdit: redit, + }) + return update } func prettyPrintByteSize(size int64) string { @@ -1622,7 +1646,7 @@ func CopyFileCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sst // TODO tricky error since the command was a success, but we can't show the output return nil, err } - update.Interactive = pk.Interactive + sstore.AddInteractiveUpdate(update, pk.Interactive) if destRemote != ConnectedRemote && destRemoteId != nil && !destRemoteId.RState.IsConnected() { writeStringToPty(ctx, cmd, fmt.Sprintf("Attempting to autoconnect to remote %v\r\n", destRemote), &outputPos) err = destRemoteId.MShell.TryAutoConnect() @@ -1662,11 +1686,7 @@ func RemoteInstallCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) } mshell := ids.Remote.MShell go mshell.RunInstall() - return &sstore.ModelUpdate{ - RemoteView: &sstore.RemoteViewType{ - PtyRemoteId: ids.Remote.RemotePtr.RemoteId, - }, - }, nil + return createRemoteViewRemoteIdUpdate(ids.Remote.RemotePtr.RemoteId), nil } func RemoteInstallCancelCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { @@ -1676,11 +1696,7 @@ func RemoteInstallCancelCommand(ctx context.Context, pk *scpacket.FeCommandPacke } mshell := ids.Remote.MShell go mshell.CancelInstall() - return &sstore.ModelUpdate{ - RemoteView: &sstore.RemoteViewType{ - PtyRemoteId: ids.Remote.RemotePtr.RemoteId, - }, - }, nil + return createRemoteViewRemoteIdUpdate(ids.Remote.RemotePtr.RemoteId), nil } func RemoteConnectCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { @@ -1689,11 +1705,7 @@ func RemoteConnectCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) return nil, err } go ids.Remote.MShell.Launch(true) - return &sstore.ModelUpdate{ - RemoteView: &sstore.RemoteViewType{ - PtyRemoteId: ids.Remote.RemotePtr.RemoteId, - }, - }, nil + return createRemoteViewRemoteIdUpdate(ids.Remote.RemotePtr.RemoteId), nil } func RemoteDisconnectCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { @@ -1703,11 +1715,7 @@ func RemoteDisconnectCommand(ctx context.Context, pk *scpacket.FeCommandPacketTy } force := resolveBool(pk.Kwargs["force"], false) go ids.Remote.MShell.Disconnect(force) - return &sstore.ModelUpdate{ - RemoteView: &sstore.RemoteViewType{ - PtyRemoteId: ids.Remote.RemotePtr.RemoteId, - }, - }, nil + return createRemoteViewRemoteIdUpdate(ids.Remote.RemotePtr.RemoteId), nil } func makeRemoteEditUpdate_new(err error) sstore.UpdatePacket { @@ -1717,12 +1725,7 @@ func makeRemoteEditUpdate_new(err error) sstore.UpdatePacket { if err != nil { redit.ErrorStr = err.Error() } - update := &sstore.ModelUpdate{ - RemoteView: &sstore.RemoteViewType{ - RemoteEdit: redit, - }, - } - return update + return createRemoteViewRemoteEditUpdate(redit) } func makeRemoteEditErrorReturn_new(visual bool, err error) (sstore.UpdatePacket, error) { @@ -1744,12 +1747,7 @@ func makeRemoteEditUpdate_edit(ids resolvedIds, err error) sstore.UpdatePacket { if err != nil { redit.ErrorStr = err.Error() } - update := &sstore.ModelUpdate{ - RemoteView: &sstore.RemoteViewType{ - RemoteEdit: redit, - }, - } - return update + return createRemoteViewRemoteEditUpdate(redit) } func makeRemoteEditErrorReturn_edit(ids resolvedIds, visual bool, err error) (sstore.UpdatePacket, error) { @@ -1961,11 +1959,7 @@ func RemoteNewCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ss return nil, fmt.Errorf("cannot create remote %q: %v", r.RemoteCanonicalName, err) } // SUCCESS - return &sstore.ModelUpdate{ - RemoteView: &sstore.RemoteViewType{ - PtyRemoteId: r.RemoteId, - }, - }, nil + return createRemoteViewRemoteIdUpdate(r.RemoteId), nil } func RemoteSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { @@ -1990,18 +1984,13 @@ func RemoteSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ss return makeRemoteEditErrorReturn_edit(ids, visualEdit, fmt.Errorf("/remote:new error updating remote: %v", err)) } if visualEdit { - return &sstore.ModelUpdate{ - RemoteView: &sstore.RemoteViewType{ - PtyRemoteId: ids.Remote.RemoteCopy.RemoteId, - }, - }, nil - } - update := &sstore.ModelUpdate{ - Info: &sstore.InfoMsgType{ - InfoMsg: fmt.Sprintf("remote %q updated", ids.Remote.DisplayName), - TimeoutMs: 2000, - }, + return createRemoteViewRemoteIdUpdate(ids.Remote.RemoteCopy.RemoteId), nil } + update := &sstore.ModelUpdate{} + sstore.AddUpdate(update, sstore.InfoMsgType{ + InfoMsg: fmt.Sprintf("remote %q updated", ids.Remote.DisplayName), + TimeoutMs: 2000, + }) return update, nil } @@ -2011,11 +2000,7 @@ func RemoteShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (s return nil, err } state := ids.Remote.RState - return &sstore.ModelUpdate{ - RemoteView: &sstore.RemoteViewType{ - PtyRemoteId: state.RemoteId, - }, - }, nil + return createRemoteViewRemoteIdUpdate(state.RemoteId), nil } func RemoteShowAllCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { @@ -2030,11 +2015,11 @@ func RemoteShowAllCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) } buf.WriteString(fmt.Sprintf("%-12s %-5s %8s %s\n", rstate.Status, rstate.RemoteType, rstate.RemoteId[0:8], name)) } - return &sstore.ModelUpdate{ - RemoteView: &sstore.RemoteViewType{ - RemoteShowAll: true, - }, - }, nil + update := &sstore.ModelUpdate{} + sstore.AddUpdate(update, sstore.RemoteViewType{ + RemoteShowAll: true, + }) + return update, nil } func resolveSshConfigPatterns(configFiles []string) ([]string, error) { @@ -2330,16 +2315,17 @@ func RemoteConfigParseCommand(ctx context.Context, pk *scpacket.FeCommandPacketT visualEdit := resolveBool(pk.Kwargs["visual"], false) if visualEdit { update := &sstore.ModelUpdate{} - update.AlertMessage = &sstore.AlertMessageType{ + sstore.AddUpdate(update, sstore.AlertMessageType{ Title: "SSH Config Import", Message: outMsg, Markdown: true, - } + }) return update, nil } else { update := &sstore.ModelUpdate{} - update.Info = &sstore.InfoMsgType{} - update.Info.InfoMsg = outMsg + sstore.AddUpdate(update, sstore.InfoMsgType{ + InfoMsg: outMsg, + }) return update, nil } } @@ -2363,12 +2349,12 @@ func ScreenShowAllCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) outStr := fmt.Sprintf("%-30s %s %s\n", screen.Name+archivedStr, screen.ScreenId, screenIdxStr) buf.WriteString(outStr) } - return &sstore.ModelUpdate{ - Info: &sstore.InfoMsgType{ - InfoTitle: fmt.Sprintf("all screens for session"), - InfoLines: splitLinesForInfo(buf.String()), - }, - }, nil + update := &sstore.ModelUpdate{} + sstore.AddUpdate(update, sstore.InfoMsgType{ + InfoTitle: fmt.Sprintf("all screens for session"), + InfoLines: splitLinesForInfo(buf.String()), + }) + return update, nil } func ScreenResetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { @@ -2402,8 +2388,8 @@ func ScreenResetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) ( // TODO tricky error since the command was a success, but we can't show the output return nil, err } - update.Interactive = pk.Interactive - update.Sessions = []*sstore.SessionType{sessionUpdate} + sstore.AddInteractiveUpdate(update, pk.Interactive) + sstore.AddUpdate(update, sessionUpdate) return update, nil } @@ -2427,7 +2413,7 @@ func RemoteArchiveCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) if err != nil { return nil, fmt.Errorf("cannot get updated screen: %w", err) } - update.Screens = []*sstore.ScreenType{screen} + sstore.AddUpdate(update, *screen) return update, nil } @@ -2476,11 +2462,10 @@ func crShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType, ids re } buf.WriteString(fmt.Sprintf("%-30s %-50s (default)\n", msh.GetDisplayName(), cwdStr)) } - update := &sstore.ModelUpdate{ - Info: &sstore.InfoMsgType{ - InfoLines: splitLinesForInfo(buf.String()), - }, - } + update := &sstore.ModelUpdate{} + sstore.AddUpdate(update, sstore.InfoMsgType{ + InfoLines: splitLinesForInfo(buf.String()), + }) return update, nil } @@ -2834,7 +2819,7 @@ func OpenAICommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstor if resolveBool(pk.Kwargs["cmdinfo"], false) { if promptStr == "" { // this is requesting an update without wanting an openai query - update, err := sstore.UpdateWithCurrentOpenAICmdInfoChat(cmd.ScreenId) + update, err := sstore.UpdateWithCurrentOpenAICmdInfoChat(cmd.ScreenId, nil) if err != nil { return nil, fmt.Errorf("error getting update for CmdInfoChat %v", err) } @@ -2879,7 +2864,9 @@ func OpenAICommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstor // ignore error again (nothing to do) log.Printf("openai error updating screen selected line: %v\n", err) } - update := &sstore.ModelUpdate{Line: line, Cmd: cmd, Screens: []*sstore.ScreenType{screen}} + update := &sstore.ModelUpdate{} + sstore.AddLineUpdate(update, line, cmd) + sstore.AddUpdate(update, *screen) return update, nil } @@ -2912,10 +2899,9 @@ func CrCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.Up if err != nil { return nil, fmt.Errorf("/%s error: cannot resolve screen for update: %w", GetCmdStr(pk), err) } - update := &sstore.ModelUpdate{ - Screens: []*sstore.ScreenType{screen}, - Interactive: pk.Interactive, - } + update := &sstore.ModelUpdate{} + sstore.AddUpdate(update, *screen) + sstore.AddInteractiveUpdate(update, pk.Interactive) return update, nil } outputStr := fmt.Sprintf("connected to %s", GetFullRemoteDisplayName(rptr, rstate)) @@ -2929,7 +2915,7 @@ func CrCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.Up // TODO tricky error since the command was a success, but we can't show the output return nil, err } - update.Interactive = pk.Interactive + sstore.AddInteractiveUpdate(update, pk.Interactive) return update, nil } @@ -3011,11 +2997,9 @@ func addLineForCmd(ctx context.Context, metaCmd string, shouldFocus bool, ids re log.Printf("%s error updating screen selected line: %v\n", metaCmd, err) } } - update := &sstore.ModelUpdate{ - Line: rtnLine, - Cmd: cmd, - Screens: []*sstore.ScreenType{screen}, - } + update := &sstore.ModelUpdate{} + sstore.AddLineUpdate(update, rtnLine, cmd) + sstore.AddUpdate(update, *screen) sstore.IncrementNumRunningCmds_Update(update, cmd.ScreenId, 1) updateHistoryContext(ctx, rtnLine, cmd, cmd.FeState) return update, nil @@ -3057,13 +3041,12 @@ func makeInfoFromComps(compType string, comps []string, hasMore bool) sstore.Upd if len(comps) == 0 { comps = []string{"(no completions)"} } - update := &sstore.ModelUpdate{ - Info: &sstore.InfoMsgType{ - InfoTitle: fmt.Sprintf("%s completions", compType), - InfoComps: comps, - InfoCompsMore: hasMore, - }, - } + update := &sstore.ModelUpdate{} + sstore.AddUpdate(update, sstore.InfoMsgType{ + InfoTitle: fmt.Sprintf("%s completions", compType), + InfoComps: comps, + InfoCompsMore: hasMore, + }) return update } @@ -3199,9 +3182,8 @@ func CompGenCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ssto if newSP == nil || cmdSP == *newSP { return nil, nil } - update := &sstore.ModelUpdate{ - CmdLine: &utilfn.StrWithPos{Str: newSP.Str, Pos: newSP.Pos}, - } + update := &sstore.ModelUpdate{} + sstore.AddCmdLineUpdate(update, utilfn.StrWithPos{Str: newSP.Str, Pos: newSP.Pos}) return update, nil } @@ -3227,7 +3209,9 @@ func CommentCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ssto // ignore error again (nothing to do) log.Printf("/comment error updating screen selected line: %v\n", err) } - update := &sstore.ModelUpdate{Line: rtnLine, Screens: []*sstore.ScreenType{screen}} + update := &sstore.ModelUpdate{} + sstore.AddLineUpdate(update, rtnLine, nil) + sstore.AddUpdate(update, *screen) return update, nil } @@ -3410,9 +3394,9 @@ func SessionArchiveCommand(ctx context.Context, pk *scpacket.FeCommandPacketType if err != nil { return nil, fmt.Errorf("cannot archive session: %v", err) } - update.Info = &sstore.InfoMsgType{ + sstore.AddUpdate(update, sstore.InfoMsgType{ InfoMsg: "session archived", - } + }) return update, nil } else { activate := resolveBool(pk.Kwargs["activate"], false) @@ -3420,9 +3404,9 @@ func SessionArchiveCommand(ctx context.Context, pk *scpacket.FeCommandPacketType if err != nil { return nil, fmt.Errorf("cannot un-archive session: %v", err) } - update.Info = &sstore.InfoMsgType{ + sstore.AddUpdate(update, sstore.InfoMsgType{ InfoMsg: "session un-archived", - } + }) return update, nil } } @@ -3463,12 +3447,12 @@ func SessionShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) ( buf.WriteString(fmt.Sprintf(" %-15s %d\n", "cmds", stats.NumCmds)) buf.WriteString(fmt.Sprintf(" %-15s %0.2fM\n", "disksize", float64(stats.DiskStats.TotalSize)/1000000)) buf.WriteString(fmt.Sprintf(" %-15s %s\n", "disk-location", stats.DiskStats.Location)) - return &sstore.ModelUpdate{ - Info: &sstore.InfoMsgType{ - InfoTitle: "session info", - InfoLines: splitLinesForInfo(buf.String()), - }, - }, nil + update := &sstore.ModelUpdate{} + sstore.AddUpdate(update, sstore.InfoMsgType{ + InfoTitle: "session info", + InfoLines: splitLinesForInfo(buf.String()), + }) + return update, nil } func SessionShowAllCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { @@ -3489,12 +3473,12 @@ func SessionShowAllCommand(ctx context.Context, pk *scpacket.FeCommandPacketType outStr := fmt.Sprintf("%-30s %s %s\n", session.Name+archivedStr, session.SessionId, sessionIdxStr) buf.WriteString(outStr) } - return &sstore.ModelUpdate{ - Info: &sstore.InfoMsgType{ - InfoTitle: "all sessions", - InfoLines: splitLinesForInfo(buf.String()), - }, - }, nil + update := &sstore.ModelUpdate{} + sstore.AddUpdate(update, sstore.InfoMsgType{ + InfoTitle: "all sessions", + InfoLines: splitLinesForInfo(buf.String()), + }) + return update, nil } func SessionSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { @@ -3514,21 +3498,17 @@ func SessionSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (s return nil, fmt.Errorf("setting session name: %v", err) } varsUpdated = append(varsUpdated, "name") - } - if pk.Kwargs["pos"] != "" { - } if len(varsUpdated) == 0 { return nil, fmt.Errorf("/session:set no updates, can set %s", formatStrs([]string{"name", "pos"}, "or", false)) } bareSession, err := sstore.GetBareSessionById(ctx, ids.SessionId) - update := &sstore.ModelUpdate{ - Sessions: []*sstore.SessionType{bareSession}, - Info: &sstore.InfoMsgType{ - InfoMsg: fmt.Sprintf("session updated %s", formatStrs(varsUpdated, "and", false)), - TimeoutMs: 2000, - }, - } + update := &sstore.ModelUpdate{} + sstore.AddUpdate(update, *bareSession) + sstore.AddUpdate(update, sstore.InfoMsgType{ + InfoMsg: fmt.Sprintf("session updated %s", formatStrs(varsUpdated, "and", false)), + TimeoutMs: 2000, + }) return update, nil } @@ -3549,14 +3529,12 @@ func SessionCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ssto if err != nil { return nil, err } - - update := &sstore.ModelUpdate{ - ActiveSessionId: ritem.Id, - Info: &sstore.InfoMsgType{ - InfoMsg: fmt.Sprintf("switched to session %q", ritem.Name), - TimeoutMs: 2000, - }, - } + update := &sstore.ModelUpdate{} + sstore.AddUpdate(update, (sstore.ActiveSessionIdUpdate)(ritem.Id)) + sstore.AddUpdate(update, sstore.InfoMsgType{ + InfoMsg: fmt.Sprintf("switched to session %q", ritem.Name), + TimeoutMs: 2000, + }) // Reset the status indicator for the new active screen session, err := sstore.GetSessionById(ctx, ritem.Id) @@ -3611,8 +3589,8 @@ func RemoteResetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) ( // TODO tricky error since the command was a success, but we can't show the output return nil, err } - update.Interactive = pk.Interactive - update.Sessions = sstore.MakeSessionsUpdateForRemote(ids.SessionId, remoteInst) + sstore.AddInteractiveUpdate(update, pk.Interactive) + sstore.AddUpdate(update, sstore.MakeSessionUpdateForRemote(ids.SessionId, remoteInst)) return update, nil } @@ -3626,20 +3604,20 @@ func ClearCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore if err != nil { return nil, fmt.Errorf("clearing screen (archiving): %v", err) } - update.Info = &sstore.InfoMsgType{ + sstore.AddUpdate(update, sstore.InfoMsgType{ InfoMsg: fmt.Sprintf("screen cleared (all lines archived)"), TimeoutMs: 2000, - } + }) return update, nil } else { update, err := sstore.DeleteScreenLines(ctx, ids.ScreenId) if err != nil { return nil, fmt.Errorf("clearing screen: %v", err) } - update.Info = &sstore.InfoMsgType{ + sstore.AddUpdate(update, sstore.InfoMsgType{ InfoMsg: fmt.Sprintf("screen cleared"), TimeoutMs: 2000, - } + }) return update, nil } @@ -3751,10 +3729,8 @@ func HistoryViewAllCommand(ctx context.Context, pk *scpacket.FeCommandPacketType } hvdata.Lines = lines hvdata.Cmds = cmds - update := &sstore.ModelUpdate{ - HistoryViewData: hvdata, - MainView: sstore.MainViewHistory, - } + update := &sstore.ModelUpdate{} + sstore.AddUpdate(update, &sstore.MainViewUpdate{MainView: sstore.MainViewHistory, HistoryView: hvdata}) return update, nil } @@ -3800,13 +3776,13 @@ func HistoryCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ssto sstore.UpdateActivityWrap(ctx, sstore.ActivityUpdate{HistoryView: 1}, "history") } update := &sstore.ModelUpdate{} - update.History = &sstore.HistoryInfoType{ + sstore.AddUpdate(update, sstore.HistoryInfoType{ HistoryType: htype, SessionId: ids.SessionId, ScreenId: ids.ScreenId, Items: hresult.Items, Show: show, - } + }) return update, nil } @@ -3993,18 +3969,16 @@ func LineRestartCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) ( return nil, fmt.Errorf("error getting updated line/cmd: %w", err) } cmd.Restarted = true - update := &sstore.ModelUpdate{ - Line: line, - Cmd: cmd, - Interactive: pk.Interactive, - } + update := &sstore.ModelUpdate{} + sstore.AddLineUpdate(update, line, cmd) + sstore.AddInteractiveUpdate(update, pk.Interactive) screen, focusErr := focusScreenLine(ctx, ids.ScreenId, line.LineNum) if focusErr != nil { // not a fatal error, so just log log.Printf("error focusing screen line: %v\n", focusErr) } if screen != nil { - update.Screens = []*sstore.ScreenType{screen} + sstore.AddUpdate(update, *screen) } return update, nil } @@ -4083,13 +4057,12 @@ func LineSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ssto if err != nil { return nil, fmt.Errorf("/line:set cannot retrieve updated line: %v", err) } - update := &sstore.ModelUpdate{ - Line: updatedLine, - Info: &sstore.InfoMsgType{ - InfoMsg: fmt.Sprintf("line updated %s", formatStrs(varsUpdated, "and", false)), - TimeoutMs: 2000, - }, - } + update := &sstore.ModelUpdate{} + sstore.AddLineUpdate(update, updatedLine, nil) + sstore.AddUpdate(update, sstore.InfoMsgType{ + InfoMsg: fmt.Sprintf("line updated %s", formatStrs(varsUpdated, "and", false)), + TimeoutMs: 2000, + }) return update, nil } @@ -4135,7 +4108,7 @@ func LineViewCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sst if err != nil { return nil, err } - update.Screens = []*sstore.ScreenType{screen} + sstore.AddUpdate(update, *screen) } return update, nil } @@ -4151,10 +4124,12 @@ func BookmarksShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) return nil, fmt.Errorf("cannot retrieve bookmarks: %v", err) } sstore.UpdateActivityWrap(ctx, sstore.ActivityUpdate{BookmarksView: 1}, "bookmarks") - update := &sstore.ModelUpdate{ - MainView: sstore.MainViewBookmarks, - Bookmarks: bms, - } + update := &sstore.ModelUpdate{} + + sstore.AddUpdate(update, &sstore.MainViewUpdate{ + MainView: sstore.MainViewBookmarks, + BookmarksView: &sstore.BookmarksUpdate{Bookmarks: bms}, + }) return update, nil } @@ -4188,12 +4163,11 @@ func BookmarkSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) ( if err != nil { return nil, fmt.Errorf("error retrieving edited bookmark: %v", err) } - return &sstore.ModelUpdate{ - Info: &sstore.InfoMsgType{ - InfoMsg: "bookmark edited", - }, - Bookmarks: []*sstore.BookmarkType{bm}, - }, nil + bms := []*sstore.BookmarkType{bm} + update := &sstore.ModelUpdate{} + sstore.AddBookmarksUpdate(update, bms, nil) + sstore.AddUpdate(update, sstore.InfoMsgUpdate("bookmark edited")) + return update, nil } func BookmarkDeleteCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { @@ -4212,13 +4186,11 @@ func BookmarkDeleteCommand(ctx context.Context, pk *scpacket.FeCommandPacketType if err != nil { return nil, fmt.Errorf("error deleting bookmark: %v", err) } - bm := &sstore.BookmarkType{BookmarkId: bookmarkId, Remove: true} - return &sstore.ModelUpdate{ - Info: &sstore.InfoMsgType{ - InfoMsg: "bookmark deleted", - }, - Bookmarks: []*sstore.BookmarkType{bm}, - }, nil + update := &sstore.ModelUpdate{} + bms := []*sstore.BookmarkType{{BookmarkId: bookmarkId, Remove: true}} + sstore.AddBookmarksUpdate(update, bms, nil) + sstore.AddUpdate(update, sstore.InfoMsgUpdate("bookmark deleted")) + return update, nil } func LineBookmarkCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { @@ -4267,11 +4239,11 @@ func LineBookmarkCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) newBmId = newBm.BookmarkId } bms, err := sstore.GetBookmarks(ctx, "") - update := &sstore.ModelUpdate{ - MainView: sstore.MainViewBookmarks, - Bookmarks: bms, - SelectedBookmark: newBmId, - } + update := &sstore.ModelUpdate{} + sstore.AddUpdate(update, &sstore.MainViewUpdate{ + MainView: sstore.MainViewBookmarks, + BookmarksView: &sstore.BookmarksUpdate{Bookmarks: bms, SelectedBookmark: newBmId}, + }) return update, nil } @@ -4317,7 +4289,9 @@ func LineStarCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sst // no line (which is strange given we checked for it above). just return a nop. return nil, nil } - return &sstore.ModelUpdate{Line: lineObj}, nil + update := &sstore.ModelUpdate{} + sstore.AddLineUpdate(update, lineObj, nil) + return update, nil } func LineArchiveCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { @@ -4352,7 +4326,9 @@ func LineArchiveCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) ( // no line (which is strange given we checked for it above). just return a nop. return nil, nil } - return &sstore.ModelUpdate{Line: lineObj}, nil + update := &sstore.ModelUpdate{} + sstore.AddLineUpdate(update, lineObj, nil) + return update, nil } func LineDeleteCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { @@ -4380,18 +4356,16 @@ func LineDeleteCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (s } update := &sstore.ModelUpdate{} for _, lineId := range lineIds { - lineObj := &sstore.LineType{ - ScreenId: ids.ScreenId, - LineId: lineId, - Remove: true, - } - update.Lines = append(update.Lines, lineObj) + line := &sstore.LineType{ScreenId: ids.ScreenId, LineId: lineId, Remove: true} + sstore.AddLineUpdate(update, line, nil) } screen, err := sstore.FixupScreenSelectedLine(ctx, ids.ScreenId) if err != nil { return nil, fmt.Errorf("/line:delete error fixing up screen: %v", err) } - update.Screens = []*sstore.ScreenType{screen} + if screen != nil { + sstore.AddUpdate(update, *screen) + } return update, nil } @@ -4474,12 +4448,11 @@ func LineShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sst stateStr = stateStr[0:77] + "..." } buf.WriteString(fmt.Sprintf(" %-15s %s\n", "state", stateStr)) - update := &sstore.ModelUpdate{ - Info: &sstore.InfoMsgType{ - InfoTitle: fmt.Sprintf("line %d info", line.LineNum), - InfoLines: splitLinesForInfo(buf.String()), - }, - } + update := &sstore.ModelUpdate{} + sstore.AddUpdate(update, sstore.InfoMsgType{ + InfoTitle: fmt.Sprintf("line %d info", line.LineNum), + InfoLines: splitLinesForInfo(buf.String()), + }) return update, nil } @@ -4569,12 +4542,11 @@ func ViewStatCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sst modeStr = modeStr[len(modeStr)-9:] } buf.WriteString(fmt.Sprintf(" %-15s %s\n", "perms", modeStr)) - update := &sstore.ModelUpdate{ - Info: &sstore.InfoMsgType{ - InfoTitle: fmt.Sprintf("view stat %q", streamPk.Path), - InfoLines: splitLinesForInfo(buf.String()), - }, - } + update := &sstore.ModelUpdate{} + sstore.AddUpdate(update, sstore.InfoMsgType{ + InfoTitle: fmt.Sprintf("view stat %q", streamPk.Path), + InfoLines: splitLinesForInfo(buf.String()), + }) return update, nil } @@ -4631,12 +4603,11 @@ func ViewTestCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sst buf.Write(dataPk.Data) } buf.WriteString(fmt.Sprintf("\n\ntotal packets: %d\n", numPackets)) - update := &sstore.ModelUpdate{ - Info: &sstore.InfoMsgType{ - InfoTitle: fmt.Sprintf("view file %q", streamPk.Path), - InfoLines: splitLinesForInfo(buf.String()), - }, - } + update := &sstore.ModelUpdate{} + sstore.AddUpdate(update, sstore.InfoMsgType{ + InfoTitle: fmt.Sprintf("view file %q", streamPk.Path), + InfoLines: splitLinesForInfo(buf.String()), + }) return update, nil } @@ -4679,7 +4650,7 @@ func CodeEditCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sst // TODO tricky error since the command was a success, but we can't show the output return nil, err } - update.Interactive = pk.Interactive + sstore.AddInteractiveUpdate(update, pk.Interactive) return update, nil } @@ -4710,7 +4681,7 @@ func CSVViewCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ssto // TODO tricky error since the command was a success, but we can't show the output return nil, err } - update.Interactive = pk.Interactive + sstore.AddInteractiveUpdate(update, pk.Interactive) return update, nil } @@ -4741,7 +4712,7 @@ func ImageViewCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ss // TODO tricky error since the command was a success, but we can't show the output return nil, err } - update.Interactive = pk.Interactive + sstore.AddInteractiveUpdate(update, pk.Interactive) return update, nil } @@ -4772,7 +4743,7 @@ func MarkdownViewCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) // TODO tricky error since the command was a success, but we can't show the output return nil, err } - update.Interactive = pk.Interactive + sstore.AddInteractiveUpdate(update, pk.Interactive) return update, nil } @@ -4836,11 +4807,10 @@ func EditTestCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sst if donePk.Error != "" { return nil, fmt.Errorf("/edit:test %s", donePk.Error) } - update := &sstore.ModelUpdate{ - Info: &sstore.InfoMsgType{ - InfoTitle: fmt.Sprintf("edit test, wrote %q", writePk.Path), - }, - } + update := &sstore.ModelUpdate{} + sstore.AddUpdate(update, sstore.InfoMsgType{ + InfoTitle: fmt.Sprintf("edit test, wrote %q", writePk.Path), + }) return update, nil } @@ -4903,11 +4873,8 @@ func SignalCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstor if err != nil { return nil, fmt.Errorf("cannot send signal: %v", err) } - update := &sstore.ModelUpdate{ - Info: &sstore.InfoMsgType{ - InfoMsg: fmt.Sprintf("sent line %s signal %s", lineArg, sigArg), - }, - } + update := &sstore.ModelUpdate{} + sstore.AddUpdate(update, sstore.InfoMsgUpdate("sent line %s signal %s", lineArg, sigArg)) return update, nil } @@ -4941,11 +4908,8 @@ func ClientCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstor func ClientNotifyUpdateWriterCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { pcloud.ResetUpdateWriterNumFailures() sstore.NotifyUpdateWriter() - update := &sstore.ModelUpdate{ - Info: &sstore.InfoMsgType{ - InfoMsg: fmt.Sprintf("notified update writer"), - }, - } + update := &sstore.ModelUpdate{} + sstore.AddUpdate(update, sstore.InfoMsgUpdate("notified update writer")) return update, nil } @@ -4971,9 +4935,8 @@ func ClientAcceptTosCommand(ctx context.Context, pk *scpacket.FeCommandPacketTyp if err != nil { return nil, fmt.Errorf("cannot retrieve updated client data: %v", err) } - update := &sstore.ModelUpdate{ - ClientData: clientData, - } + update := &sstore.ModelUpdate{} + sstore.AddUpdate(update, *clientData) return update, nil } @@ -5021,9 +4984,8 @@ func ClientConfirmFlagCommand(ctx context.Context, pk *scpacket.FeCommandPacketT return nil, fmt.Errorf("cannot retrieve updated client data: %v", err) } - update := &sstore.ModelUpdate{ - ClientData: clientData, - } + update := &sstore.ModelUpdate{} + sstore.AddUpdate(update, *clientData) return update, nil } @@ -5077,9 +5039,8 @@ func ClientSetSidebarCommand(ctx context.Context, pk *scpacket.FeCommandPacketTy return nil, fmt.Errorf("cannot retrieve updated client data: %v", err) } - update := &sstore.ModelUpdate{ - ClientData: clientData, - } + update := &sstore.ModelUpdate{} + sstore.AddUpdate(update, *clientData) return update, nil } @@ -5227,13 +5188,12 @@ func ClientSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ss if err != nil { return nil, fmt.Errorf("cannot retrieve updated client data: %v", err) } - update := &sstore.ModelUpdate{ - Info: &sstore.InfoMsgType{ - InfoMsg: fmt.Sprintf("client updated %s", formatStrs(varsUpdated, "and", false)), - TimeoutMs: 2000, - }, - ClientData: clientData, - } + update := &sstore.ModelUpdate{} + sstore.AddUpdate(update, *clientData) + sstore.AddUpdate(update, sstore.InfoMsgType{ + InfoMsg: fmt.Sprintf("client updated %s", formatStrs(varsUpdated, "and", false)), + TimeoutMs: 2000, + }) return update, nil } @@ -5259,12 +5219,12 @@ func ClientShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (s buf.WriteString(fmt.Sprintf(" %-15s %s\n", "client-version", clientVersion)) buf.WriteString(fmt.Sprintf(" %-15s %s %s\n", "server-version", scbase.WaveVersion, scbase.BuildTime)) buf.WriteString(fmt.Sprintf(" %-15s %s (%s)\n", "arch", scbase.ClientArch(), scbase.UnameKernelRelease())) - update := &sstore.ModelUpdate{ - Info: &sstore.InfoMsgType{ - InfoTitle: fmt.Sprintf("client info"), - InfoLines: splitLinesForInfo(buf.String()), - }, - } + update := &sstore.ModelUpdate{} + sstore.AddUpdate(update, sstore.InfoMsgType{ + InfoTitle: fmt.Sprintf("client info"), + InfoLines: splitLinesForInfo(buf.String()), + }) + return update, nil } @@ -5318,7 +5278,7 @@ func TelemetryOnCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) ( return nil, fmt.Errorf("cannot retrieve updated client data: %v", err) } update := sstore.InfoMsgUpdate("telemetry is now on") - update.ClientData = clientData + sstore.AddUpdate(update, *clientData) return update, nil } @@ -5339,7 +5299,7 @@ func TelemetryOffCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) return nil, fmt.Errorf("cannot retrieve updated client data: %v", err) } update := sstore.InfoMsgUpdate("telemetry is now off") - update.ClientData = clientData + sstore.AddUpdate(update, *clientData) return update, nil } @@ -5350,12 +5310,11 @@ func TelemetryShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) } var buf bytes.Buffer buf.WriteString(fmt.Sprintf(" %-15s %s\n", "telemetry", boolToStr(clientData.ClientOpts.NoTelemetry, "off", "on"))) - update := &sstore.ModelUpdate{ - Info: &sstore.InfoMsgType{ - InfoTitle: fmt.Sprintf("telemetry info"), - InfoLines: splitLinesForInfo(buf.String()), - }, - } + update := &sstore.ModelUpdate{} + sstore.AddUpdate(update, sstore.InfoMsgType{ + InfoTitle: fmt.Sprintf("telemetry info"), + InfoLines: splitLinesForInfo(buf.String()), + }) return update, nil } @@ -5427,7 +5386,7 @@ func ReleaseCheckOnCommand(ctx context.Context, pk *scpacket.FeCommandPacketType return nil, fmt.Errorf("cannot retrieve updated client data: %v", err) } update := sstore.InfoMsgUpdate("automatic release checking is now on") - update.ClientData = clientData + sstore.AddUpdate(update, *clientData) return update, nil } @@ -5448,7 +5407,7 @@ func ReleaseCheckOffCommand(ctx context.Context, pk *scpacket.FeCommandPacketTyp return nil, fmt.Errorf("cannot retrieve updated client data: %v", err) } update := sstore.InfoMsgUpdate("automatic release checking is now off") - update.ClientData = clientData + sstore.AddUpdate(update, *clientData) return update, nil } @@ -5471,7 +5430,7 @@ func ReleaseCheckCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) } update := sstore.InfoMsgUpdate(rsp) - update.ClientData = clientData + sstore.AddUpdate(update, *clientData) return update, nil } diff --git a/wavesrv/pkg/releasechecker/releasechecker.go b/wavesrv/pkg/releasechecker/releasechecker.go index 75104c46f..6d13feaa4 100644 --- a/wavesrv/pkg/releasechecker/releasechecker.go +++ b/wavesrv/pkg/releasechecker/releasechecker.go @@ -66,9 +66,8 @@ func CheckNewRelease(ctx context.Context, force bool) (ReleaseCheckResult, error return Failure, fmt.Errorf("error getting updated client data: %w", err) } - update := &sstore.ModelUpdate{ - ClientData: clientData, - } + update := &sstore.ModelUpdate{} + sstore.AddUpdate(update, *clientData) sstore.MainBus.SendUpdate(update) return Success, nil diff --git a/wavesrv/pkg/remote/remote.go b/wavesrv/pkg/remote/remote.go index 0691a16a3..dbe1df472 100644 --- a/wavesrv/pkg/remote/remote.go +++ b/wavesrv/pkg/remote/remote.go @@ -678,18 +678,19 @@ func (msh *MShellProc) GetRemoteRuntimeState() RemoteRuntimeState { func (msh *MShellProc) NotifyRemoteUpdate() { rstate := msh.GetRemoteRuntimeState() - update := &sstore.ModelUpdate{Remotes: []RemoteRuntimeState{rstate}} + update := &sstore.ModelUpdate{} + sstore.AddUpdate(update, rstate) sstore.MainBus.SendUpdate(update) } -func GetAllRemoteRuntimeState() []RemoteRuntimeState { +func GetAllRemoteRuntimeState() []*RemoteRuntimeState { GlobalStore.Lock.Lock() defer GlobalStore.Lock.Unlock() - var rtn []RemoteRuntimeState + var rtn []*RemoteRuntimeState for _, proc := range GlobalStore.Map { state := proc.GetRemoteRuntimeState() - rtn = append(rtn, state) + rtn = append(rtn, &state) } return rtn } @@ -1997,7 +1998,8 @@ func (msh *MShellProc) notifyHangups_nolock() { if err != nil { continue } - update := &sstore.ModelUpdate{Cmd: cmd} + update := &sstore.ModelUpdate{} + sstore.AddUpdate(update, *cmd) sstore.MainBus.SendScreenUpdate(ck.GetGroupId(), update) go pushNumRunningCmdsUpdate(&ck, -1) } @@ -2027,7 +2029,7 @@ func (msh *MShellProc) handleCmdDonePacket(donePk *packet.CmdDonePacketType) { // fall-through (nothing to do) } if screen != nil { - update.Screens = []*sstore.ScreenType{screen} + sstore.AddUpdate(update, *screen) } rct := msh.GetRunningCmd(donePk.CK) var statePtr *sstore.ShellStatePtr @@ -2039,7 +2041,7 @@ func (msh *MShellProc) handleCmdDonePacket(donePk *packet.CmdDonePacketType) { // fall-through (nothing to do) } if remoteInst != nil { - update.Sessions = sstore.MakeSessionsUpdateForRemote(rct.SessionId, remoteInst) + sstore.AddUpdate(update, sstore.MakeSessionUpdateForRemote(rct.SessionId, remoteInst)) } statePtr = &sstore.ShellStatePtr{BaseHash: donePk.FinalState.GetHashVal(false)} } else if donePk.FinalStateDiff != nil && rct != nil { @@ -2059,7 +2061,7 @@ func (msh *MShellProc) handleCmdDonePacket(donePk *packet.CmdDonePacketType) { // fall-through (nothing to do) } if remoteInst != nil { - update.Sessions = sstore.MakeSessionsUpdateForRemote(rct.SessionId, remoteInst) + sstore.AddUpdate(update, sstore.MakeSessionUpdateForRemote(rct.SessionId, remoteInst)) } diffHashArr := append(([]string)(nil), donePk.FinalStateDiff.DiffHashArr...) diffHashArr = append(diffHashArr, donePk.FinalStateDiff.GetHashVal(false)) @@ -2102,9 +2104,10 @@ func (msh *MShellProc) handleCmdFinalPacket(finalPk *packet.CmdFinalPacketType) log.Printf("error getting cmd(2) in handleCmdFinalPacket (not found)\n") return } - update := &sstore.ModelUpdate{Cmd: rtnCmd} + update := &sstore.ModelUpdate{} + sstore.AddUpdate(update, *rtnCmd) if screen != nil { - update.Screens = []*sstore.ScreenType{screen} + sstore.AddUpdate(update, *screen) } go pushNumRunningCmdsUpdate(&finalPk.CK, -1) sstore.MainBus.SendUpdate(update) @@ -2172,7 +2175,9 @@ func (msh *MShellProc) makeHandleCmdFinalPacketClosure(finalPk *packet.CmdFinalP func sendScreenUpdates(screens []*sstore.ScreenType) { for _, screen := range screens { - sstore.MainBus.SendUpdate(&sstore.ModelUpdate{Screens: []*sstore.ScreenType{screen}}) + update := &sstore.ModelUpdate{} + sstore.AddUpdate(update, *screen) + sstore.MainBus.SendUpdate(update) } } diff --git a/wavesrv/pkg/remote/sshclient.go b/wavesrv/pkg/remote/sshclient.go index 4cb05c38b..f664ac33a 100644 --- a/wavesrv/pkg/remote/sshclient.go +++ b/wavesrv/pkg/remote/sshclient.go @@ -388,11 +388,12 @@ func createHostKeyCallback(opts *sstore.SSHOpts) (ssh.HostKeyCallback, error) { "%s\n\n"+ "**Offending Keys** \n"+ "%s", key.Type(), correctKeyFingerprint, strings.Join(bulletListKnownHosts, " \n"), strings.Join(offendingKeysFmt, " \n")) - update := &sstore.ModelUpdate{AlertMessage: &sstore.AlertMessageType{ + update := &sstore.ModelUpdate{} + sstore.AddUpdate(update, sstore.AlertMessageType{ Markdown: true, Title: "Known Hosts Key Changed", Message: alertText, - }} + }) sstore.MainBus.SendUpdate(update) return fmt.Errorf("remote host identification has changed") } diff --git a/wavesrv/pkg/scws/scws.go b/wavesrv/pkg/scws/scws.go index 9cff87226..f7d3c1b01 100644 --- a/wavesrv/pkg/scws/scws.go +++ b/wavesrv/pkg/scws/scws.go @@ -162,16 +162,17 @@ func (ws *WSState) ReplaceShell(shell *wsshell.WSShell) { func (ws *WSState) handleConnection() error { ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) defer cancelFn() - update, err := sstore.GetAllSessions(ctx) + connectUpdate, err := sstore.GetConnectUpdate(ctx) if err != nil { return fmt.Errorf("getting sessions: %w", err) } remotes := remote.GetAllRemoteRuntimeState() - update.Remotes = remotes + connectUpdate.Remotes = remotes // restore status indicators - update.ScreenStatusIndicators, update.ScreenNumRunningCommands = sstore.GetCurrentIndicatorState() - update.Connect = true - err = ws.Shell.WriteJson(update) + connectUpdate.ScreenStatusIndicators, connectUpdate.ScreenNumRunningCommands = sstore.GetCurrentIndicatorState() + mu := &sstore.ModelUpdate{} + sstore.AddUpdate(mu, *connectUpdate) + err = ws.Shell.WriteJson(mu) if err != nil { return err } diff --git a/wavesrv/pkg/sstore/dbops.go b/wavesrv/pkg/sstore/dbops.go index 0b8ca3d79..e8c9d5509 100644 --- a/wavesrv/pkg/sstore/dbops.go +++ b/wavesrv/pkg/sstore/dbops.go @@ -453,20 +453,32 @@ func GetBareSessionById(ctx context.Context, sessionId string) (*SessionType, er return &rtn, nil } -func GetAllSessions(ctx context.Context) (*ModelUpdate, error) { - return WithTxRtn(ctx, func(tx *TxWrap) (*ModelUpdate, error) { - update := &ModelUpdate{} - query := `SELECT * FROM session ORDER BY archived, sessionidx, archivedts` - tx.Select(&update.Sessions, query) +const getAllSessionsQuery = `SELECT * FROM session ORDER BY archived, sessionidx, archivedts` + +// Gets all sessions, including archived +func GetAllSessions(ctx context.Context) ([]*SessionType, error) { + return WithTxRtn(ctx, func(tx *TxWrap) ([]*SessionType, error) { + rtn := []*SessionType{} + tx.Select(&rtn, getAllSessionsQuery) + return rtn, nil + }) +} + +// Get all sessions and screens, including remotes +func GetConnectUpdate(ctx context.Context) (*ConnectUpdate, error) { + return WithTxRtn(ctx, func(tx *TxWrap) (*ConnectUpdate, error) { + update := &ConnectUpdate{} + sessions := []*SessionType{} + tx.Select(&sessions, getAllSessionsQuery) sessionMap := make(map[string]*SessionType) - for _, session := range update.Sessions { + for _, session := range sessions { sessionMap[session.SessionId] = session - session.Full = true + update.Sessions = append(update.Sessions, session) } - query = `SELECT * FROM screen ORDER BY archived, screenidx, archivedts` - update.Screens = dbutil.SelectMapsGen[*ScreenType](tx, query) - for _, screen := range update.Screens { - screen.Full = true + query := `SELECT * FROM screen ORDER BY archived, screenidx, archivedts` + screens := dbutil.SelectMapsGen[*ScreenType](tx, query) + for _, screen := range screens { + update.Screens = append(update.Screens, screen) } query = `SELECT * FROM remote_instance` riArr := dbutil.SelectMapsGen[*RemoteInstance](tx, query) @@ -502,19 +514,15 @@ func GetSessionScreens(ctx context.Context, sessionId string) ([]*ScreenType, er return WithTxRtn(ctx, func(tx *TxWrap) ([]*ScreenType, error) { query := `SELECT * FROM screen WHERE sessionid = ? ORDER BY archived, screenidx, archivedts` rtn := dbutil.SelectMapsGen[*ScreenType](tx, query, sessionId) - for _, screen := range rtn { - screen.Full = true - } return rtn, nil }) } func GetSessionById(ctx context.Context, id string) (*SessionType, error) { - allSessionsUpdate, err := GetAllSessions(ctx) + allSessions, err := GetAllSessions(ctx) if err != nil { return nil, err } - allSessions := allSessionsUpdate.Sessions for _, session := range allSessions { if session.SessionId == id { return session, nil @@ -569,7 +577,11 @@ func InsertSessionWithName(ctx context.Context, sessionName string, activate boo if err != nil { return err } - newScreen = screenUpdate.Screens[0] + screenUpdateItems := GetUpdateItems[ScreenType](screenUpdate) + if len(screenUpdateItems) < 1 { + return fmt.Errorf("no screen update items") + } + newScreen = screenUpdateItems[0] if activate { query = `UPDATE client SET activesessionid = ?` tx.Exec(query, newSessionId) @@ -583,12 +595,11 @@ func InsertSessionWithName(ctx context.Context, sessionName string, activate boo if err != nil { return nil, err } - update := &ModelUpdate{ - Sessions: []*SessionType{session}, - Screens: []*ScreenType{newScreen}, - } + update := &ModelUpdate{} + AddUpdate(update, *session) + AddUpdate(update, *newScreen) if activate { - update.ActiveSessionId = newSessionId + AddUpdate(update, ActiveSessionIdUpdate(newSessionId)) } return update, nil } @@ -742,14 +753,15 @@ func InsertScreen(ctx context.Context, sessionId string, origScreenName string, if err != nil { return nil, err } - update := &ModelUpdate{Screens: []*ScreenType{newScreen}} + update := &ModelUpdate{} + AddUpdate(update, *newScreen) if activate { bareSession, err := GetBareSessionById(ctx, sessionId) if err != nil { return nil, txErr } - update.Sessions = []*SessionType{bareSession} - update.OpenAICmdInfoChat = ScreenMemGetCmdInfoChat(newScreenId).Messages + AddUpdate(update, *bareSession) + UpdateWithCurrentOpenAICmdInfoChat(newScreenId, update) } return update, nil } @@ -758,7 +770,6 @@ func GetScreenById(ctx context.Context, screenId string) (*ScreenType, error) { return WithTxRtn(ctx, func(tx *TxWrap) (*ScreenType, error) { query := `SELECT * FROM screen WHERE screenid = ?` screen := dbutil.GetMapGen[*ScreenType](tx, query, screenId) - screen.Full = true return screen, nil }) } @@ -866,17 +877,18 @@ func GetCmdByScreenId(ctx context.Context, screenId string, lineId string) (*Cmd func UpdateWithClearOpenAICmdInfo(screenId string) (*ModelUpdate, error) { ScreenMemClearCmdInfoChat(screenId) - return UpdateWithCurrentOpenAICmdInfoChat(screenId) + return UpdateWithCurrentOpenAICmdInfoChat(screenId, nil) } func UpdateWithAddNewOpenAICmdInfoPacket(ctx context.Context, screenId string, pk *packet.OpenAICmdInfoChatMessage) (*ModelUpdate, error) { ScreenMemAddCmdInfoChatMessage(screenId, pk) - return UpdateWithCurrentOpenAICmdInfoChat(screenId) + return UpdateWithCurrentOpenAICmdInfoChat(screenId, nil) } -func UpdateWithCurrentOpenAICmdInfoChat(screenId string) (*ModelUpdate, error) { - cmdInfoUpdate := ScreenMemGetCmdInfoChat(screenId).Messages - return &ModelUpdate{OpenAICmdInfoChat: cmdInfoUpdate}, nil +func UpdateWithCurrentOpenAICmdInfoChat(screenId string, update *ModelUpdate) (*ModelUpdate, error) { + ret := &ModelUpdate{} + AddOpenAICmdInfoChatUpdate(ret, ScreenMemGetCmdInfoChat(screenId).Messages) + return ret, nil } func UpdateWithUpdateOpenAICmdInfoPacket(ctx context.Context, screenId string, messageID int, pk *packet.OpenAICmdInfoChatMessage) (*ModelUpdate, error) { @@ -884,7 +896,7 @@ func UpdateWithUpdateOpenAICmdInfoPacket(ctx context.Context, screenId string, m if err != nil { return nil, err } - return UpdateWithCurrentOpenAICmdInfoChat(screenId) + return UpdateWithCurrentOpenAICmdInfoChat(screenId, nil) } func UpdateCmdForRestart(ctx context.Context, ck base.CommandKey, ts int64, cmdPid int, remotePid int, termOpts *TermOpts) error { @@ -935,7 +947,8 @@ func UpdateCmdDoneInfo(ctx context.Context, ck base.CommandKey, donePk *packet.C return nil, fmt.Errorf("cmd data not found for ck[%s]", ck) } - update := &ModelUpdate{Cmd: rtnCmd} + update := &ModelUpdate{} + AddUpdate(update, *rtnCmd) // Update in-memory screen indicator status var indicator StatusIndicatorLevel @@ -1101,11 +1114,13 @@ func SwitchScreenById(ctx context.Context, sessionId string, screenId string) (* if err != nil { return nil, err } - update := &ModelUpdate{ActiveSessionId: sessionId, Sessions: []*SessionType{bareSession}} + update := &ModelUpdate{} + AddUpdate(update, (ActiveSessionIdUpdate)(sessionId)) + AddUpdate(update, *bareSession) memState := GetScreenMemState(screenId) if memState != nil { - update.CmdLine = &memState.CmdInputText - update.OpenAICmdInfoChat = ScreenMemGetCmdInfoChat(screenId).Messages + AddCmdLineUpdate(update, memState.CmdInputText) + UpdateWithCurrentOpenAICmdInfoChat(screenId, update) // Clear any previous status indicator for this screen err := ResetStatusIndicator_Update(update, screenId) @@ -1173,13 +1188,14 @@ func ArchiveScreen(ctx context.Context, sessionId string, screenId string) (Upda if err != nil { return nil, fmt.Errorf("cannot retrive archived screen: %w", err) } - update := &ModelUpdate{Screens: []*ScreenType{newScreen}} + update := &ModelUpdate{} + AddUpdate(update, *newScreen) if isActive { bareSession, err := GetBareSessionById(ctx, sessionId) if err != nil { return nil, err } - update.Sessions = []*SessionType{bareSession} + AddUpdate(update, *bareSession) } return update, nil } @@ -1199,7 +1215,7 @@ func UnArchiveScreen(ctx context.Context, sessionId string, screenId string) err } // if sessionDel is passed, we do *not* delete the screen directory (session delete will handle that) -func DeleteScreen(ctx context.Context, screenId string, sessionDel bool) (*ModelUpdate, error) { +func DeleteScreen(ctx context.Context, screenId string, sessionDel bool, update *ModelUpdate) (*ModelUpdate, error) { var sessionId string var isActive bool var screenTombstone *ScreenTombstoneType @@ -1259,14 +1275,17 @@ func DeleteScreen(ctx context.Context, screenId string, sessionDel bool) (*Model if !sessionDel { GoDeleteScreenDirs(screenId) } - update := &ModelUpdate{ScreenTombstones: []*ScreenTombstoneType{screenTombstone}} - update.Screens = []*ScreenType{{SessionId: sessionId, ScreenId: screenId, Remove: true}} + if update == nil { + update = &ModelUpdate{} + } + AddUpdate(update, *screenTombstone) + AddUpdate(update, ScreenType{SessionId: sessionId, ScreenId: screenId, Remove: true}) if isActive { bareSession, err := GetBareSessionById(ctx, sessionId) if err != nil { return nil, err } - update.Sessions = []*SessionType{bareSession} + AddUpdate(update, *bareSession) } return update, nil } @@ -1516,7 +1535,9 @@ func ArchiveScreenLines(ctx context.Context, screenId string) (*ModelUpdate, err if err != nil { return nil, err } - return &ModelUpdate{ScreenLines: screenLines}, nil + ret := &ModelUpdate{} + AddUpdate(ret, *screenLines) + return ret, nil } func DeleteScreenLines(ctx context.Context, screenId string) (*ModelUpdate, error) { @@ -1558,7 +1579,10 @@ func DeleteScreenLines(ctx context.Context, screenId string) (*ModelUpdate, erro } screenLines.Lines = append(screenLines.Lines, line) } - return &ModelUpdate{Screens: []*ScreenType{screen}, ScreenLines: screenLines}, nil + ret := &ModelUpdate{} + AddUpdate(ret, *screen) + AddUpdate(ret, *screenLines) + return ret, nil } func GetRunningScreenCmds(ctx context.Context, screenId string) ([]*CmdType, error) { @@ -1621,16 +1645,10 @@ func DeleteSession(ctx context.Context, sessionId string) (UpdatePacket, error) query := `SELECT screenid FROM screen WHERE sessionid = ?` screenIds = tx.SelectStrings(query, sessionId) for _, screenId := range screenIds { - screenUpdate, err := DeleteScreen(tx.Context(), screenId, true) + _, err := DeleteScreen(tx.Context(), screenId, true, update) if err != nil { return fmt.Errorf("error deleting screen[%s]: %v", screenId, err) } - if len(screenUpdate.Screens) > 0 { - update.Screens = append(update.Screens, screenUpdate.Screens...) - } - if len(screenUpdate.ScreenTombstones) > 0 { - update.ScreenTombstones = append(update.ScreenTombstones, screenUpdate.ScreenTombstones...) - } } query = `DELETE FROM session WHERE sessionid = ?` tx.Exec(query, sessionId) @@ -1650,10 +1668,12 @@ func DeleteSession(ctx context.Context, sessionId string) (UpdatePacket, error) } GoDeleteScreenDirs(screenIds...) if newActiveSessionId != "" { - update.ActiveSessionId = newActiveSessionId + AddUpdate(update, (ActiveSessionIdUpdate)(newActiveSessionId)) + } + AddUpdate(update, SessionType{SessionId: sessionId, Remove: true}) + if sessionTombstone != nil { + AddUpdate(update, *sessionTombstone) } - update.Sessions = append(update.Sessions, &SessionType{SessionId: sessionId, Remove: true}) - update.SessionTombstones = []*SessionTombstoneType{sessionTombstone} return update, nil } @@ -1705,10 +1725,10 @@ func ArchiveSession(ctx context.Context, sessionId string) (*ModelUpdate, error) bareSession, _ := GetBareSessionById(ctx, sessionId) update := &ModelUpdate{} if bareSession != nil { - update.Sessions = append(update.Sessions, bareSession) + AddUpdate(update, *bareSession) } if newActiveSessionId != "" { - update.ActiveSessionId = newActiveSessionId + AddUpdate(update, (ActiveSessionIdUpdate)(newActiveSessionId)) } return update, nil } @@ -1740,11 +1760,12 @@ func UnArchiveSession(ctx context.Context, sessionId string, activate bool) (*Mo } bareSession, _ := GetBareSessionById(ctx, sessionId) update := &ModelUpdate{} + if bareSession != nil { - update.Sessions = append(update.Sessions, bareSession) + AddUpdate(update, *bareSession) } if activate { - update.ActiveSessionId = sessionId + AddUpdate(update, (ActiveSessionIdUpdate)(sessionId)) } return update, nil } diff --git a/wavesrv/pkg/sstore/sstore.go b/wavesrv/pkg/sstore/sstore.go index e2c42e8a6..e9df8e734 100644 --- a/wavesrv/pkg/sstore/sstore.go +++ b/wavesrv/pkg/sstore/sstore.go @@ -336,6 +336,10 @@ func (cdata *ClientData) Clean() *ClientData { return &rtn } +func (ClientData) UpdateType() string { + return "clientdata" +} + type SessionType struct { SessionId string `json:"sessionid"` Name string `json:"name"` @@ -349,7 +353,17 @@ type SessionType struct { // only for updates Remove bool `json:"remove,omitempty"` - Full bool `json:"full,omitempty"` +} + +func (SessionType) UpdateType() string { + return "session" +} + +func MakeSessionUpdateForRemote(sessionId string, ri *RemoteInstance) SessionType { + return SessionType{ + SessionId: sessionId, + Remotes: []*RemoteInstance{ri}, + } } type SessionTombstoneType struct { @@ -360,6 +374,10 @@ type SessionTombstoneType struct { func (SessionTombstoneType) UseDBMap() {} +func (SessionTombstoneType) UpdateType() string { + return "sessiontombstone" +} + type SessionStatsType struct { SessionId string `json:"sessionid"` NumScreens int `json:"numscreens"` @@ -429,6 +447,10 @@ type ScreenLinesType struct { func (ScreenLinesType) UseDBMap() {} +func (ScreenLinesType) UpdateType() string { + return "screenlines" +} + type ScreenWebShareOpts struct { ShareName string `json:"sharename"` ViewKey string `json:"viewkey"` @@ -476,9 +498,7 @@ type ScreenType struct { ArchivedTs int64 `json:"archivedts,omitempty"` // only for updates - Full bool `json:"full,omitempty"` - Remove bool `json:"remove,omitempty"` - StatusIndicator string `json:"statusindicator,omitempty"` + Remove bool `json:"remove,omitempty"` } func (s *ScreenType) ToMap() map[string]interface{} { @@ -526,6 +546,24 @@ func (s *ScreenType) FromMap(m map[string]interface{}) bool { return true } +func (ScreenType) UpdateType() string { + return "screen" +} + +func AddScreenUpdate(update *ModelUpdate, newScreen *ScreenType) { + if newScreen == nil { + return + } + screenUpdates := GetUpdateItems[ScreenType](update) + for _, screenUpdate := range screenUpdates { + if screenUpdate.ScreenId == newScreen.ScreenId { + screenUpdate = newScreen + return + } + } + AddUpdate(update, newScreen) +} + type ScreenTombstoneType struct { ScreenId string `json:"screenid"` SessionId string `json:"sessionid"` @@ -536,6 +574,10 @@ type ScreenTombstoneType struct { func (ScreenTombstoneType) UseDBMap() {} +func (ScreenTombstoneType) UpdateType() string { + return "screentombstone" +} + const ( LayoutFull = "full" ) @@ -1015,6 +1057,10 @@ func (state RemoteRuntimeState) ExpandHomeDir(pathStr string) (string, error) { return path.Join(homeDir, pathStr[2:]), nil } +func (RemoteRuntimeState) UpdateType() string { + return "remote" +} + type RemoteType struct { RemoteId string `json:"remoteid"` RemoteType string `json:"remotetype"` @@ -1079,6 +1125,10 @@ type CmdType struct { Restarted bool `json:"restarted,omitempty"` // not persisted to DB } +func (CmdType) UpdateType() string { + return "cmd" +} + func (r *RemoteType) ToMap() map[string]interface{} { rtn := make(map[string]interface{}) rtn["remoteid"] = r.RemoteId @@ -1456,10 +1506,10 @@ func SetStatusIndicatorLevel_Update(ctx context.Context, update *ModelUpdate, sc } } - update.ScreenStatusIndicators = []*ScreenStatusIndicatorType{{ + AddUpdate(update, ScreenStatusIndicatorType{ ScreenId: screenId, Status: newStatus, - }} + }) return nil } @@ -1489,10 +1539,11 @@ func ResetStatusIndicator(screenId string) error { func IncrementNumRunningCmds_Update(update *ModelUpdate, screenId string, delta int) { newNum := ScreenMemIncrementNumRunningCommands(screenId, delta) log.Printf("IncrementNumRunningCmds_Update: screenId=%s, newNum=%d\n", screenId, newNum) - update.ScreenNumRunningCommands = []*ScreenNumRunningCommandsType{{ + AddUpdate(update, ScreenNumRunningCommandsType{ ScreenId: screenId, Num: newNum, - }} + }) + } func IncrementNumRunningCmds(screenId string, delta int) { diff --git a/wavesrv/pkg/sstore/updatebus.go b/wavesrv/pkg/sstore/updatebus.go index 3bc5c2237..aedcb756b 100644 --- a/wavesrv/pkg/sstore/updatebus.go +++ b/wavesrv/pkg/sstore/updatebus.go @@ -5,14 +5,13 @@ package sstore import ( "context" + "encoding/json" "fmt" "log" "sync" "time" "github.com/google/uuid" - "github.com/wavetermdev/waveterm/waveshell/pkg/packet" - "github.com/wavetermdev/waveterm/waveshell/pkg/utilfn" "github.com/wavetermdev/waveterm/wavesrv/pkg/scpacket" ) @@ -23,6 +22,7 @@ const ModelUpdateStr = "model" const UpdateChSize = 100 type UpdatePacket interface { + // The key to use when marshalling to JSON and interpreting in the client UpdateType() string Clean() } @@ -42,136 +42,61 @@ func (*PtyDataUpdate) UpdateType() string { func (pdu *PtyDataUpdate) Clean() {} -type ModelUpdate struct { - Sessions []*SessionType `json:"sessions,omitempty"` - ActiveSessionId string `json:"activesessionid,omitempty"` - Screens []*ScreenType `json:"screens,omitempty"` - ScreenLines *ScreenLinesType `json:"screenlines,omitempty"` - Line *LineType `json:"line,omitempty"` - Lines []*LineType `json:"lines,omitempty"` - Cmd *CmdType `json:"cmd,omitempty"` - CmdLine *utilfn.StrWithPos `json:"cmdline,omitempty"` - Info *InfoMsgType `json:"info,omitempty"` - ClearInfo bool `json:"clearinfo,omitempty"` - Remotes []RemoteRuntimeState `json:"remotes,omitempty"` - History *HistoryInfoType `json:"history,omitempty"` - Interactive bool `json:"interactive"` - Connect bool `json:"connect,omitempty"` - MainView string `json:"mainview,omitempty"` - Bookmarks []*BookmarkType `json:"bookmarks,omitempty"` - SelectedBookmark string `json:"selectedbookmark,omitempty"` - HistoryViewData *HistoryViewData `json:"historyviewdata,omitempty"` - ClientData *ClientData `json:"clientdata,omitempty"` - RemoteView *RemoteViewType `json:"remoteview,omitempty"` - ScreenTombstones []*ScreenTombstoneType `json:"screentombstones,omitempty"` - SessionTombstones []*SessionTombstoneType `json:"sessiontombstones,omitempty"` - OpenAICmdInfoChat []*packet.OpenAICmdInfoChatMessage `json:"openaicmdinfochat,omitempty"` - AlertMessage *AlertMessageType `json:"alertmessage,omitempty"` - ScreenStatusIndicators []*ScreenStatusIndicatorType `json:"screenstatusindicators,omitempty"` - ScreenNumRunningCommands []*ScreenNumRunningCommandsType `json:"screennumrunningcommands,omitempty"` - UserInputRequest *UserInputRequestType `json:"userinputrequest,omitempty"` -} +// A collection of independent model updates to be sent to the client. Will be evaluated in order on the client. +type ModelUpdate []*ModelUpdateItem func (*ModelUpdate) UpdateType() string { return ModelUpdateStr } +func (mu *ModelUpdate) MarshalJSON() ([]byte, error) { + rtn := make([]map[string]any, 0) + for _, u := range *mu { + m := make(map[string]any) + m[(*u).UpdateType()] = u + rtn = append(rtn, m) + } + return json.Marshal(rtn) +} + +// An interface for all model updates +type ModelUpdateItem interface { + // The key to use when marshalling to JSON and interpreting in the client + UpdateType() string +} + +// Clean the ClientData in an update, if present func (update *ModelUpdate) Clean() { if update == nil { return } - update.ClientData = update.ClientData.Clean() + clientDataUpdates := GetUpdateItems[ClientData](update) + if len(clientDataUpdates) > 0 { + lastUpdate := clientDataUpdates[len(clientDataUpdates)-1] + lastUpdate.Clean() + } } -func (update *ModelUpdate) UpdateScreen(newScreen *ScreenType) { - if newScreen == nil { - return +func (update *ModelUpdate) append(item *ModelUpdateItem) { + *update = append(*update, item) +} + +// Add a collection of model updates to the update +func AddUpdate(update *ModelUpdate, item ...ModelUpdateItem) { + for _, i := range item { + update.append(&i) } - for idx, screen := range update.Screens { - if screen.ScreenId == newScreen.ScreenId { - update.Screens[idx] = newScreen - return +} + +// Returns the items in the update that are of type I +func GetUpdateItems[I ModelUpdateItem](update *ModelUpdate) []*I { + ret := make([]*I, 0) + for _, item := range *update { + if i, ok := (*item).(I); ok { + ret = append(ret, &i) } } - update.Screens = append(update.Screens, newScreen) -} - -// only sets InfoError if InfoError is not already set -func (update *ModelUpdate) AddInfoError(errStr string) { - if update.Info == nil { - update.Info = &InfoMsgType{} - } - if update.Info.InfoError == "" { - update.Info.InfoError = errStr - } -} - -type RemoteViewType struct { - RemoteShowAll bool `json:"remoteshowall,omitempty"` - PtyRemoteId string `json:"ptyremoteid,omitempty"` - RemoteEdit *RemoteEditType `json:"remoteedit,omitempty"` -} - -func InfoMsgUpdate(infoMsgFmt string, args ...interface{}) *ModelUpdate { - msg := fmt.Sprintf(infoMsgFmt, args...) - return &ModelUpdate{ - Info: &InfoMsgType{InfoMsg: msg}, - } -} - -type HistoryViewData struct { - Items []*HistoryItemType `json:"items"` - Offset int `json:"offset"` - RawOffset int `json:"rawoffset"` - NextRawOffset int `json:"nextrawoffset"` - HasMore bool `json:"hasmore"` - Lines []*LineType `json:"lines"` - Cmds []*CmdType `json:"cmds"` -} - -type RemoteEditType struct { - RemoteEdit bool `json:"remoteedit"` - RemoteId string `json:"remoteid,omitempty"` - ErrorStr string `json:"errorstr,omitempty"` - InfoStr string `json:"infostr,omitempty"` - KeyStr string `json:"keystr,omitempty"` - HasPassword bool `json:"haspassword,omitempty"` -} - -type AlertMessageType struct { - Title string `json:"title,omitempty"` - Message string `json:"message"` - Confirm bool `json:"confirm,omitempty"` - Markdown bool `json:"markdown,omitempty"` -} - -type InfoMsgType struct { - InfoTitle string `json:"infotitle"` - InfoError string `json:"infoerror,omitempty"` - InfoMsg string `json:"infomsg,omitempty"` - InfoMsgHtml bool `json:"infomsghtml,omitempty"` - WebShareLink bool `json:"websharelink,omitempty"` - InfoComps []string `json:"infocomps,omitempty"` - InfoCompsMore bool `json:"infocompssmore,omitempty"` - InfoLines []string `json:"infolines,omitempty"` - TimeoutMs int64 `json:"timeoutms,omitempty"` -} - -type HistoryInfoType struct { - HistoryType string `json:"historytype"` - SessionId string `json:"sessionid,omitempty"` - ScreenId string `json:"screenid,omitempty"` - Items []*HistoryItemType `json:"items"` - Show bool `json:"show"` -} - -type UserInputRequestType struct { - RequestId string `json:"requestid"` - QueryText string `json:"querytext"` - ResponseType string `json:"responsetype"` - Title string `json:"title"` - Markdown bool `json:"markdown"` - TimeoutMs int `json:"timeoutms"` + return ret } type UpdateChannel struct { @@ -267,29 +192,6 @@ func (bus *UpdateBus) SendScreenUpdate(screenId string, update UpdatePacket) { } } -func MakeSessionsUpdateForRemote(sessionId string, ri *RemoteInstance) []*SessionType { - return []*SessionType{ - { - SessionId: sessionId, - Remotes: []*RemoteInstance{ri}, - }, - } -} - -type BookmarksViewType struct { - Bookmarks []*BookmarkType `json:"bookmarks"` -} - -type ScreenStatusIndicatorType struct { - ScreenId string `json:"screenid"` - Status StatusIndicatorLevel `json:"status"` -} - -type ScreenNumRunningCommandsType struct { - ScreenId string `json:"screenid"` - Num int `json:"num"` -} - func (bus *UpdateBus) registerUserInputChannel() (string, chan *scpacket.UserInputResponsePacketType) { bus.Lock.Lock() defer bus.Lock.Unlock() @@ -323,7 +225,8 @@ func (bus *UpdateBus) GetUserInput(ctx context.Context, userInputRequest *UserIn userInputRequest.RequestId = id deadline, _ := ctx.Deadline() userInputRequest.TimeoutMs = int(time.Until(deadline).Milliseconds()) - 500 - update := &ModelUpdate{UserInputRequest: userInputRequest} + update := &ModelUpdate{} + AddUpdate(update, *userInputRequest) bus.SendUpdate(update) log.Printf("test: %+v", userInputRequest) diff --git a/wavesrv/pkg/sstore/updatetypes.go b/wavesrv/pkg/sstore/updatetypes.go new file mode 100644 index 000000000..0a8eb03da --- /dev/null +++ b/wavesrv/pkg/sstore/updatetypes.go @@ -0,0 +1,234 @@ +package sstore + +import ( + "fmt" + + "github.com/wavetermdev/waveterm/waveshell/pkg/packet" + "github.com/wavetermdev/waveterm/waveshell/pkg/utilfn" +) + +type ActiveSessionIdUpdate string + +func (ActiveSessionIdUpdate) UpdateType() string { + return "activesessionid" +} + +type LineUpdate struct { + Line LineType `json:"line"` + Cmd CmdType `json:"cmd,omitempty"` +} + +func (LineUpdate) UpdateType() string { + return "line" +} + +func AddLineUpdate(update *ModelUpdate, newLine *LineType, newCmd *CmdType) { + if newLine == nil { + return + } + newLineUpdate := LineUpdate{ + Line: *newLine, + } + if newCmd != nil { + newLineUpdate.Cmd = *newCmd + } + AddUpdate(update, newLineUpdate) +} + +type CmdLineUpdate utilfn.StrWithPos + +func (CmdLineUpdate) UpdateType() string { + return "cmdline" +} + +func AddCmdLineUpdate(update *ModelUpdate, cmdLine utilfn.StrWithPos) { + AddUpdate(update, CmdLineUpdate(cmdLine)) +} + +type InfoMsgType struct { + InfoTitle string `json:"infotitle"` + InfoError string `json:"infoerror,omitempty"` + InfoMsg string `json:"infomsg,omitempty"` + InfoMsgHtml bool `json:"infomsghtml,omitempty"` + WebShareLink bool `json:"websharelink,omitempty"` + InfoComps []string `json:"infocomps,omitempty"` + InfoCompsMore bool `json:"infocompssmore,omitempty"` + InfoLines []string `json:"infolines,omitempty"` + TimeoutMs int64 `json:"timeoutms,omitempty"` +} + +func (InfoMsgType) UpdateType() string { + return "info" +} + +func InfoMsgUpdate(infoMsgFmt string, args ...interface{}) *ModelUpdate { + msg := fmt.Sprintf(infoMsgFmt, args...) + ret := &ModelUpdate{} + newInfoUpdate := InfoMsgType{InfoMsg: msg} + AddUpdate(ret, newInfoUpdate) + return ret +} + +// only sets InfoError if InfoError is not already set +func AddInfoMsgUpdateError(update *ModelUpdate, errStr string) { + infoUpdates := GetUpdateItems[InfoMsgType](update) + + if len(infoUpdates) > 0 { + lastUpdate := infoUpdates[len(infoUpdates)-1] + if lastUpdate.InfoError == "" { + lastUpdate.InfoError = errStr + return + } + } else { + AddUpdate(update, InfoMsgType{InfoError: errStr}) + } +} + +type ClearInfoUpdate bool + +func (ClearInfoUpdate) UpdateType() string { + return "clearinfo" +} + +type HistoryInfoType struct { + HistoryType string `json:"historytype"` + SessionId string `json:"sessionid,omitempty"` + ScreenId string `json:"screenid,omitempty"` + Items []*HistoryItemType `json:"items"` + Show bool `json:"show"` +} + +func (HistoryInfoType) UpdateType() string { + return "history" +} + +type InteractiveUpdate bool + +func (InteractiveUpdate) UpdateType() string { + return "interactive" +} + +func AddInteractiveUpdate(update *ModelUpdate, interactive bool) { + AddUpdate(update, InteractiveUpdate(interactive)) +} + +type ConnectUpdate struct { + Sessions []*SessionType `json:"sessions,omitempty"` + Screens []*ScreenType `json:"screens,omitempty"` + Remotes []*RemoteRuntimeState `json:"remotes,omitempty"` + ScreenStatusIndicators []*ScreenStatusIndicatorType `json:"screenstatusindicators,omitempty"` + ScreenNumRunningCommands []*ScreenNumRunningCommandsType `json:"screennumrunningcommands,omitempty"` + ActiveSessionId string `json:"activesessionid,omitempty"` +} + +func (ConnectUpdate) UpdateType() string { + return "connect" +} + +type MainViewUpdate struct { + MainView string `json:"mainview"` + HistoryView *HistoryViewData `json:"historyview,omitempty"` + BookmarksView *BookmarksUpdate `json:"bookmarksview,omitempty"` +} + +func (MainViewUpdate) UpdateType() string { + return "mainview" +} + +type BookmarksUpdate struct { + Bookmarks []*BookmarkType `json:"bookmarks"` + SelectedBookmark string `json:"selectedbookmark,omitempty"` +} + +func (BookmarksUpdate) UpdateType() string { + return "bookmarks" +} + +func AddBookmarksUpdate(update *ModelUpdate, bookmarks []*BookmarkType, selectedBookmark *string) { + if selectedBookmark == nil { + AddUpdate(update, BookmarksUpdate{Bookmarks: bookmarks}) + } else { + AddUpdate(update, BookmarksUpdate{Bookmarks: bookmarks, SelectedBookmark: *selectedBookmark}) + } +} + +type HistoryViewData struct { + Items []*HistoryItemType `json:"items"` + Offset int `json:"offset"` + RawOffset int `json:"rawoffset"` + NextRawOffset int `json:"nextrawoffset"` + HasMore bool `json:"hasmore"` + Lines []*LineType `json:"lines"` + Cmds []*CmdType `json:"cmds"` +} + +type RemoteEditType struct { + RemoteEdit bool `json:"remoteedit"` + RemoteId string `json:"remoteid,omitempty"` + ErrorStr string `json:"errorstr,omitempty"` + InfoStr string `json:"infostr,omitempty"` + KeyStr string `json:"keystr,omitempty"` + HasPassword bool `json:"haspassword,omitempty"` +} + +type RemoteViewType struct { + RemoteShowAll bool `json:"remoteshowall,omitempty"` + PtyRemoteId string `json:"ptyremoteid,omitempty"` + RemoteEdit *RemoteEditType `json:"remoteedit,omitempty"` +} + +func (RemoteViewType) UpdateType() string { + return "remoteview" +} + +type OpenAICmdInfoChatUpdate []*packet.OpenAICmdInfoChatMessage + +func (OpenAICmdInfoChatUpdate) UpdateType() string { + return "openaicmdinfochat" +} + +func AddOpenAICmdInfoChatUpdate(update *ModelUpdate, chatMessages []*packet.OpenAICmdInfoChatMessage) { + AddUpdate(update, OpenAICmdInfoChatUpdate(chatMessages)) +} + +type AlertMessageType struct { + Title string `json:"title,omitempty"` + Message string `json:"message"` + Confirm bool `json:"confirm,omitempty"` + Markdown bool `json:"markdown,omitempty"` +} + +func (AlertMessageType) UpdateType() string { + return "alertmessage" +} + +type ScreenStatusIndicatorType struct { + ScreenId string `json:"screenid"` + Status StatusIndicatorLevel `json:"status"` +} + +func (ScreenStatusIndicatorType) UpdateType() string { + return "screenstatusindicator" +} + +type ScreenNumRunningCommandsType struct { + ScreenId string `json:"screenid"` + Num int `json:"num"` +} + +func (ScreenNumRunningCommandsType) UpdateType() string { + return "screennumrunningcommands" +} + +type UserInputRequestType struct { + RequestId string `json:"requestid"` + QueryText string `json:"querytext"` + ResponseType string `json:"responsetype"` + Title string `json:"title"` + Markdown bool `json:"markdown"` + TimeoutMs int `json:"timeoutms"` +} + +func (UserInputRequestType) UpdateType() string { + return "userinputrequest" +}