Refactor ModelUpdate to set up for decoupling sstore (#280)

This PR changes ModelUpdate mechanism from a statically-typed struct to an interface, allowing us to define the update mechanism and the update types separately. This sets us up to move app logic and update mechanisms into separate packages. Ultimately, sstore will only define low-level persistence logic.
This commit is contained in:
Evan Simkowitz
2024-02-09 17:19:44 -08:00
committed by GitHub
parent 7a5afccab3
commit d319e72609
15 changed files with 1082 additions and 861 deletions
+10 -15
View File
@@ -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;
}
+293 -246
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -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) {
+36 -15
View File
@@ -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,
+53 -69
View File
@@ -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<any> {
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<any> {
}
function fetchJsonData(resp: any, ctErr: boolean): Promise<any> {
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<any> {
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<DataType> {
@@ -113,31 +112,28 @@ function genMergeSimpleData<T extends ISimpleDataType>(
if (dataArr == null || dataArr.length == 0) {
return;
}
let objMap: Record<string, T> = {};
for (let i = 0; i < objs.length; i++) {
let obj = objs[i];
let id = idFn(obj);
const objMap: Record<string, T> = {};
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<ObjType extends IObjType<DataType>, DataType extends IData
if (dataArr == null || dataArr.length == 0) {
return;
}
let objMap: Record<string, ObjType> = {};
for (let i = 0; i < objs.length; i++) {
let obj = objs[i];
let id = objIdFn(obj);
const objMap: Record<string, ObjType> = {};
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<ObjType extends IObjType<DataType>, 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<ObjType extends IObjType<DataType>, 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<ObjType extends IObjType<DataType>, 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<number>) {
// @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;
}
File diff suppressed because it is too large Load Diff
+2 -3
View File
@@ -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
+16 -11
View File
@@ -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)
}
}
+3 -2
View File
@@ -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")
}
+6 -5
View File
@@ -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
}
+79 -58
View File
@@ -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
}
+59 -8
View File
@@ -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) {
+45 -142
View File
@@ -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)
+234
View File
@@ -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"
}