big checkpoint on cmd screen migration

This commit is contained in:
sawka 2023-03-20 19:22:52 -07:00
parent e8ffd4b4b1
commit e5490df56c
9 changed files with 183 additions and 95 deletions

View File

@ -387,6 +387,14 @@ electron.ipcMain.on("restart-server", (event) => {
return;
});
electron.ipcMain.on("reload-window", (event) => {
if (MainWindow != null) {
MainWindow.reload();
}
event.returnValue = true;
return;
});
function getContextMenu() : any {
let menu = new electron.Menu();
let menuItem = new electron.MenuItem({label: "Testing", click: () => console.log("click testing!")});

View File

@ -29,7 +29,7 @@ function isBlank(s : string) : boolean {
}
function getLineId(line : LineType) : string {
return sprintf("%s-%s-%s", line.sessionid, line.screenid, line.lineid);
return sprintf("%s-%s", line.screenid, line.lineid);
}
function makeFullRemoteRef(ownerName : string, remoteRef : string, name : string) : string {
@ -179,7 +179,7 @@ class LineCmd extends React.Component<{screen : LineContainerModel, line : LineT
}
let {line} = this.props;
this.rtnStateDiffFetched = true;
let usp = new URLSearchParams({sessionid: line.sessionid, cmdid: line.cmdid});
let usp = new URLSearchParams({screenid: line.screenid, cmdid: line.cmdid});
let url = GlobalModel.getBaseHostPort() + "/api/rtnstate?" + usp.toString();
let fetchHeaders = GlobalModel.getFetchHeaders();
fetch(url, {headers: fetchHeaders}).then((resp) => {

View File

@ -8,7 +8,7 @@ import cn from "classnames";
import {debounce, throttle} from "throttle-debounce";
import {v4 as uuidv4} from "uuid";
import dayjs from "dayjs";
import type {SessionDataType, LineType, CmdDataType, RemoteType, RemoteStateType, RemoteInstanceType, RemotePtrType, HistoryItem, HistoryQueryOpts, RemoteEditType, FeStateType, ContextMenuOpts, BookmarkType, RenderModeType} from "./types";
import type {SessionDataType, LineType, CmdDataType, RemoteType, RemoteStateType, RemoteInstanceType, RemotePtrType, HistoryItem, HistoryQueryOpts, RemoteEditType, FeStateType, ContextMenuOpts, BookmarkType, RenderModeType, ClientMigrationInfo} from "./types";
import type * as T from "./types";
import localizedFormat from 'dayjs/plugin/localizedFormat';
import {GlobalModel, GlobalCommandRunner, Session, Cmd, ScreenLines, Screen, riToRPtr, windowWidthToCols, windowHeightToRows, termHeightFromRows, termWidthFromCols, TabColors, RemoteColors} from "./model";
@ -1642,7 +1642,7 @@ function getLineDateStr(todayDate : string, yesterdayDate : string, ts : number)
}
@mobxReact.observer
class LinesView extends React.Component<{screen : Screen, width : number, lines : LineType[], renderMode : RenderModeType}, {}> {
class LinesView extends React.Component<{sessionId : string, screen : Screen, width : number, lines : LineType[], renderMode : RenderModeType}, {}> {
rszObs : any;
linesRef : React.RefObject<any>;
staticRender : OV<boolean> = mobx.observable.box(true, {name: "static-render"});
@ -2158,7 +2158,7 @@ class ScreenWindowView extends React.Component<{screen : Screen}, {}> {
</div>
</div>
<If condition={lines.length > 0}>
<LinesView screen={screen} width={this.width.get()} lines={lines} renderMode={renderMode}/>
<LinesView sessionId={screen.sessionId} screen={screen} width={this.width.get()} lines={lines} renderMode={renderMode}/>
</If>
<If condition={lines.length == 0}>
<div key="window-empty" className="window-empty">
@ -2701,6 +2701,56 @@ class DisconnectedModal extends React.Component<{}, {}> {
}
}
@mobxReact.observer
class ClientStopModal extends React.Component<{}, {}> {
@boundMethod
refreshClient() {
GlobalModel.refreshClient();
}
render() {
let model = GlobalModel;
let cdata = model.clientData.get();
let mdata : ClientMigrationInfo = (cdata != null ? cdata.migration : null);
let title = "Client Not Ready";
if (mdata != null) {
title = "Migrating Data";
}
return (
<div className="prompt-modal client-stop-modal modal is-active">
<div className="modal-background"></div>
<div className="modal-content">
<div className="message-header">
<div className="modal-title">[prompt] {title}</div>
</div>
<div className="inner-content">
<If condition={cdata == null}>
<div>Cannot get client data.</div>
</If>
<If condition={cdata != null && cdata.cmdstoretype == "session"}>
<div>Client database is being migrated to the latest version, please wait.</div>
<If condition={mdata != null}>
<div className="progress-container">
<progress className="progress is-primary" value={mdata.migrationpos} max={mdata.migrationtotal}>{mdata.migrationpos}</progress>
</div>
<div className="progress-text">{mdata.migrationpos}/{mdata.migrationtotal}</div>
</If>
</If>
</div>
<footer>
<button onClick={this.refreshClient} className="button">
<span className="icon">
<i className="fa-sharp fa-solid fa-rotate"/>
</span>
<span>Hard Refresh Client</span>
</button>
</footer>
</div>
</div>
);
}
}
@mobxReact.observer
class LoadingSpinner extends React.Component<{}, {}> {
render() {
@ -2856,6 +2906,8 @@ class WelcomeModal extends React.Component<{}, {}> {
@mobxReact.observer
class Main extends React.Component<{}, {}> {
dcWait : OV<boolean> = mobx.observable.box(false, {name: "dcWait"});
constructor(props : any) {
super(props);
}
@ -2887,10 +2939,44 @@ class Main extends React.Component<{}, {}> {
}
}
@boundMethod
updateDcWait(val : boolean) : void {
mobx.action(() => {
this.dcWait.set(val);
})();
}
render() {
let screenSettingsModal = GlobalModel.screenSettingsModal.get();
let sessionSettingsModal = GlobalModel.sessionSettingsModal.get();
let lineSettingsModal = GlobalModel.lineSettingsModal.get();
let disconnected = !GlobalModel.ws.open.get() || !GlobalModel.localServerRunning.get();
let hasClientStop = GlobalModel.getHasClientStop();
let dcWait = this.dcWait.get();
if (disconnected || hasClientStop) {
if (!dcWait) {
setTimeout(() => this.updateDcWait(true), 1500);
}
return (
<div id="main" onContextMenu={this.handleContextMenu}>
<div className="main-content">
<MainSideBar/>
<div className="session-view"/>
</div>
<If condition={dcWait}>
<If condition={disconnected}>
<DisconnectedModal/>
</If>
<If condition={!disconnected && hasClientStop}>
<ClientStopModal/>
</If>
</If>
</div>
);
}
if (dcWait) {
setTimeout(() => this.updateDcWait(false), 0);
}
return (
<div id="main" onContextMenu={this.handleContextMenu}>
<div className="main-content">
@ -2899,9 +2985,6 @@ class Main extends React.Component<{}, {}> {
<HistoryView/>
<BookmarksView/>
</div>
<If condition={!GlobalModel.ws.open.get() || !GlobalModel.localServerRunning.get()}>
<DisconnectedModal/>
</If>
<AlertModal/>
<If condition={GlobalModel.welcomeModalOpen.get()}>
<WelcomeModal/>

View File

@ -5,7 +5,7 @@ import {debounce} from "throttle-debounce";
import {handleJsonFetchResponse, base64ToArray, genMergeData, genMergeDataMap, genMergeSimpleData, boundInt, isModKeyPress} from "./util";
import {TermWrap} from "./term";
import {v4 as uuidv4} from "uuid";
import type {SessionDataType, LineType, RemoteType, HistoryItem, RemoteInstanceType, RemotePtrType, CmdDataType, FeCmdPacketType, TermOptsType, RemoteStateType, ScreenDataType, ScreenOptsType, PtyDataUpdateType, ModelUpdateType, UpdateMessage, InfoType, CmdLineUpdateType, UIContextType, HistoryInfoType, HistoryQueryOpts, FeInputPacketType, TermWinSize, RemoteInputPacketType, FeStateType, ContextMenuOpts, RendererContext, RendererModel, PtyDataType, BookmarkType, ClientDataType, HistoryViewDataType, AlertMessageType, HistorySearchParams, FocusTypeStrs, ScreenLinesType, HistoryTypeStrs, RendererPluginType, WindowSize} from "./types";
import type {SessionDataType, LineType, RemoteType, HistoryItem, RemoteInstanceType, RemotePtrType, CmdDataType, FeCmdPacketType, TermOptsType, RemoteStateType, ScreenDataType, ScreenOptsType, PtyDataUpdateType, ModelUpdateType, UpdateMessage, InfoType, CmdLineUpdateType, UIContextType, HistoryInfoType, HistoryQueryOpts, FeInputPacketType, TermWinSize, RemoteInputPacketType, FeStateType, ContextMenuOpts, RendererContext, RendererModel, PtyDataType, BookmarkType, ClientDataType, HistoryViewDataType, AlertMessageType, HistorySearchParams, FocusTypeStrs, ScreenLinesType, HistoryTypeStrs, RendererPluginType, WindowSize, ClientMigrationInfo} from "./types";
import {WSControl} from "./ws";
import {measureText, getMonoFontSize} from "./textmeasure";
import dayjs from "dayjs";
@ -69,7 +69,6 @@ function getRendererType(line : LineType) : "terminal" | "plugin" {
function getRendererContext(line : LineType) : RendererContext {
return {
sessionId: line.sessionid,
screenId: line.screenid,
cmdId: line.cmdid,
lineId: line.lineid,
@ -156,6 +155,7 @@ type ElectronApi = {
getAuthKey : () => string,
getLocalServerStatus : () => boolean,
restartLocalServer : () => boolean,
reloadWindow : () => void,
onTCmd : (callback : (mods : KeyModsType) => void) => void,
onICmd : (callback : (mods : KeyModsType) => void) => void,
onLCmd : (callback : (mods : KeyModsType) => void) => void,
@ -175,10 +175,6 @@ function getApi() : ElectronApi {
return (window as any).api;
}
function getLineId(line : LineType) : string {
return sprintf("%s-%s-%s", line.sessionid, line.screenid, line.lineid);
}
// clean empty string
function ces(s : string) {
if (s == "") {
@ -188,7 +184,6 @@ function ces(s : string) {
}
class Cmd {
sessionId : string;
screenId : string;
remote : RemotePtrType;
remoteId : string;
@ -196,7 +191,6 @@ class Cmd {
data : OV<CmdDataType>;
constructor(cmd : CmdDataType) {
this.sessionId = cmd.sessionid;
this.screenId = cmd.screenid;
this.cmdId = cmd.cmdid;
this.remote = cmd.remote;
@ -295,7 +289,7 @@ class Cmd {
handleInputChunk(data : string) : void {
let inputPacket : FeInputPacketType = {
type: "feinput",
ck: this.sessionId + "/" + this.cmdId,
ck: this.screenId + "/" + this.cmdId,
remote: this.remote,
inputdata64: btoa(data),
};
@ -1745,7 +1739,7 @@ class SpecialHistoryViewLineContainer {
if (line.contentheight != null && line.contentheight != -1) {
usedRows = line.contentheight;
}
let termContext = {sessionId: line.sessionid, screenId: line.screenid, cmdId: cmdId, lineId : line.lineid, lineNum: line.linenum};
let termContext = {screenId: line.screenid, cmdId: cmdId, lineId : line.lineid, lineNum: line.linenum};
termWrap = new TermWrap(elem, {
termContext: termContext,
usedRows: usedRows,
@ -2371,6 +2365,7 @@ class Model {
bookmarksModel : BookmarksModel;
historyViewModel : HistoryViewModel;
clientData : OV<ClientDataType> = mobx.observable.box(null, {name: "clientData"});
clientMigrationInfo : OV<ClientMigrationInfo> = mobx.observable.box(null, {name: "clientMigrationInfo"});
constructor() {
this.clientId = getApi().getId();
@ -2413,6 +2408,10 @@ class Model {
setTimeout(() => this.getClientDataLoop(1), 10);
}
refreshClient() : void {
getApi().reloadWindow();
}
registerRendererPlugin(plugin : RendererPluginType) {
if (isBlank(plugin.name)) {
throw new Error("invalid plugin, no name");
@ -2427,6 +2426,17 @@ class Model {
this.rendererPlugins.push(plugin);
}
getHasClientStop() : boolean {
if (this.clientData.get() == null) {
return true;
}
let cdata = this.clientData.get();
if (cdata.cmdstoretype == "session") {
return true;
}
return false;
}
getRendererPluginByName(name : string) : RendererPluginType {
for (let i=0; i<this.rendererPlugins.length; i++) {
let plugin = this.rendererPlugins[i];
@ -2560,12 +2570,12 @@ class Model {
}
getContentHeight(context : RendererContext) : number {
let key = context.sessionId + "/" + context.cmdId;
let key = context.screenId + "/" + context.cmdId;
return this.termUsedRowsCache[key];
}
setContentHeight(context : RendererContext, height : number) : void {
let key = context.sessionId + "/" + context.cmdId;
let key = context.screenId + "/" + context.cmdId;
this.termUsedRowsCache[key] = height;
GlobalCommandRunner.setTermUsedRows(context, height);
}
@ -2735,26 +2745,24 @@ class Model {
this.removeScreenLinesByScreenId(mods.removed[i]);
}
}
if ("sessions" in update) {
if ("sessions" in update || "activesessionid" in update) {
if (update.connect) {
this.sessionList.clear();
}
let oldActiveScreen = this.getActiveScreen();
let [oldActiveSessionId, oldActiveScreenId] = this.getActiveIds();
genMergeData(this.sessionList, update.sessions, (s : Session) => s.sessionId, (sdata : SessionDataType) => sdata.sessionid, (sdata : SessionDataType) => new Session(sdata), (s : Session) => s.sessionIdx.get());
if (!("activesessionid" in update)) {
let newActiveScreen = this.getActiveScreen();
if (oldActiveScreen != newActiveScreen) {
if (newActiveScreen == null) {
this._activateScreen(this.activeSessionId.get(), null, oldActiveScreen);
}
else {
this._activateScreen(newActiveScreen.sessionId, newActiveScreen.screenId, oldActiveScreen);
}
if ("activesessionid" in update) {
let newSessionId = update.activesessionid;
if (this.activeSessionId.get() != newSessionId) {
this.activeSessionId.set(newSessionId);
}
}
}
if ("activesessionid" in update) {
this._activateSession(update.activesessionid);
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);
@ -2946,17 +2954,18 @@ class Model {
getClientDataLoop(loopNum : number) : void {
this.getClientData();
if (this.clientData.get() != null) {
let clientStop = this.getHasClientStop()
if (this.clientData.get() != null && !clientStop) {
return;
}
let timeoutMs = 1000;
if (loopNum > 5) {
if (!clientStop && loopNum > 5) {
timeoutMs = 3000;
}
if (loopNum > 10) {
if (!clientStop && loopNum > 10) {
timeoutMs = 10000;
}
if (loopNum > 15) {
if (!clientStop && loopNum > 15) {
timeoutMs = 30000;
}
setTimeout(() => this.getClientDataLoop(loopNum+1), timeoutMs);
@ -2969,6 +2978,7 @@ class Model {
mobx.action(() => {
let clientData = data.data;
this.clientData.set(clientData);
this.clientMigrationInfo.set(clientData.migration);
})();
}).catch((err) => {
this.errorHandler("calling get-client-data", err, true);
@ -3032,45 +3042,14 @@ class Model {
return this.submitCommandPacket(pk, interactive)
}
_activateSession(sessionId : string) {
mobx.action(() => {
this.activeMainView.set("session");
})();
let oldActiveSession = this.getActiveSession();
if (oldActiveSession != null && oldActiveSession.sessionId == sessionId) {
return;
}
let newSession = this.getSessionById(sessionId);
if (newSession == null) {
return;
}
this._activateScreen(sessionId, newSession.activeScreenId.get());
}
_activateScreen(sessionId : string, screenId : string, oldActiveScreen? : Screen) {
mobx.action(() => {
this.activeMainView.set("session");
})();
if (!oldActiveScreen) {
oldActiveScreen = this.getActiveScreen();
}
if (oldActiveScreen && oldActiveScreen.sessionId == sessionId && oldActiveScreen.screenId == screenId) {
return;
}
mobx.action(() => {
this.deactivateScreenLines();
let curSessionId = this.activeSessionId.get();
if (curSessionId != sessionId) {
this.activeSessionId.set(sessionId);
}
this.getActiveSession().activeScreenId.set(screenId);
})();
let curScreen = this.getActiveScreen();
if (curScreen == null) {
this.ws.watchScreen(sessionId, null);
return;
}
this.ws.watchScreen(curScreen.sessionId, curScreen.screenId);
// returns [sessionId, screenId]
getActiveIds() : [string, string] {
let activeSession = this.getActiveSession();
let activeScreen = this.getActiveScreen();
return [
(activeSession == null ? null : activeSession.sessionId),
(activeScreen == null ? null : activeScreen.screenId),
];
}
_loadScreenLinesAsync(newWin : ScreenLines) {
@ -3130,10 +3109,6 @@ class Model {
}
getCmd(line : LineType) : Cmd {
let session = this.getSessionById(line.sessionid);
if (session == null) {
return null;
}
let slines = this.getScreenLinesById(line.screenid);
if (slines == null) {
return null;
@ -3338,7 +3313,6 @@ class CommandRunner {
setTermUsedRows(termContext : RendererContext, height : number) {
let kwargs : Record<string, string> = {};
kwargs["session"] = termContext.sessionId;
kwargs["screen"] = termContext.screenId;
kwargs["hohist"] = "1";
let posargs = [String(termContext.lineNum), String(height)];
@ -3466,8 +3440,8 @@ function _getPtyDataFromUrl(url : string) : Promise<PtyDataType> {
});
}
function getPtyData(sessionId : string, cmdId : string) : Promise<PtyDataType> {
let url = sprintf(GlobalModel.getBaseHostPort() + "/api/ptyout?sessionid=%s&cmdid=%s", sessionId, cmdId);
function getPtyData(screenId : string, cmdId : string) : Promise<PtyDataType> {
let url = sprintf(GlobalModel.getBaseHostPort() + "/api/ptyout?screenid=%s&cmdid=%s", screenId, cmdId);
return _getPtyDataFromUrl(url);
}

View File

@ -6,6 +6,7 @@ contextBridge.exposeInMainWorld("api", {
getAuthKey: () => ipcRenderer.sendSync("get-authkey"),
getLocalServerStatus: () => ipcRenderer.sendSync("local-server-status"),
restartLocalServer: () => ipcRenderer.sendSync("restart-server"),
reloadWindow: () => ipcRenderer.sendSync("reload-window"),
onTCmd: (callback) => ipcRenderer.on("t-cmd", callback),
onICmd: (callback) => ipcRenderer.on("i-cmd", callback),
onLCmd: (callback) => ipcRenderer.on("l-cmd", callback),

View File

@ -1746,7 +1746,7 @@ body .xterm .xterm-viewport {
}
.line.line-invalid {
color: #000;
color: @term-white;
margin-left: 5px;
}
@ -2421,9 +2421,7 @@ input[type=checkbox] {
}
}
}
}
.disconnected-modal {
.inner-content {
.ws-log {
padding: 5px;
@ -2439,6 +2437,26 @@ input[type=checkbox] {
}
}
.modal.prompt-modal.client-stop-modal {
footer {
justify-content: center;
}
.inner-content {
display: flex;
flex-direction: column;
padding: 20px;
.progress-container {
margin-top: 20px;
}
.progress-text {
color: @term-white;
align-self: center;
font-size: 12px;
}
}
}
.remote-status {
font-size: 8px;
margin-right: 5px;

View File

@ -72,7 +72,7 @@ class SimpleBlobRendererModel {
mobx.action(() => {
this.loading.set(true);
})();
let rtnp = getPtyData(this.context.sessionId, this.context.cmdId);
let rtnp = getPtyData(this.context.screenId, this.context.cmdId);
rtnp.then((ptydata) => {
setTimeout(() => {
this.ptyData = ptydata;
@ -96,7 +96,6 @@ class SimpleBlobRendererModel {
function contextFromLine(line : LineType) : RendererContext {
return {
sessionId: line.sessionid,
screenId: line.screenid,
cmdId: line.cmdid,
lineId: line.lineid,

View File

@ -278,7 +278,7 @@ class TermWrap {
}
else {
let termContext = this.getRendererContext();
rtnp = getPtyData(termContext.sessionId, termContext.cmdId);
rtnp = getPtyData(termContext.screenId, termContext.cmdId);
}
rtnp.then((ptydata) => {
setTimeout(() => {

View File

@ -23,7 +23,6 @@ type SessionDataType = {
};
type LineType = {
sessionid : string,
screenid : string,
userid : string,
lineid : string,
@ -217,7 +216,6 @@ type CmdDoneInfoType = {
};
type CmdDataType = {
sessionid : string,
screenid : string,
cmdid : string,
remote : RemotePtrType,
@ -234,7 +232,6 @@ type CmdDataType = {
};
type PtyDataUpdateType = {
sessionid : string,
screenid : string,
cmdid : string,
remoteid : string,
@ -344,7 +341,6 @@ type ContextMenuOpts = {
type UpdateMessage = PtyDataUpdateType | ModelUpdateType;
type RendererContext = {
sessionId : string,
screenId : string,
cmdId : string,
lineId : string,
@ -423,6 +419,15 @@ type ClientDataType = {
clientid : string,
userid : string,
feopts : FeOptsType;
cmdstoretype : "session" | "screen";
migration? : ClientMigrationInfo;
};
type ClientMigrationInfo = {
migrationtype : string,
migrationpos : number,
migrationtotal : number,
migrationdone : boolean,
};
type PlaybookType = {
@ -463,4 +468,4 @@ type HistorySearchParams = {
type RenderModeType = "normal" | "collapsed";
export type {SessionDataType, LineType, RemoteType, RemoteStateType, RemoteInstanceType, HistoryItem, CmdRemoteStateType, FeCmdPacketType, TermOptsType, CmdStartPacketType, CmdDataType, ScreenDataType, ScreenOptsType, PtyDataUpdateType, ModelUpdateType, UpdateMessage, InfoType, CmdLineUpdateType, RemotePtrType, UIContextType, HistoryInfoType, HistoryQueryOpts, WatchScreenPacketType, TermWinSize, FeInputPacketType, RemoteInputPacketType, RemoteEditType, FeStateType, ContextMenuOpts, RendererContext, WindowSize, RendererModel, PtyDataType, BookmarkType, ClientDataType, PlaybookType, PlaybookEntryType, HistoryViewDataType, RenderModeType, AlertMessageType, HistorySearchParams, ScreenLinesType, FocusTypeStrs, HistoryTypeStrs, RendererOpts, RendererPluginType, SimpleBlobRendererComponent, RendererModelContainerApi, RendererModelInitializeParams, RendererOptsUpdate};
export type {SessionDataType, LineType, RemoteType, RemoteStateType, RemoteInstanceType, HistoryItem, CmdRemoteStateType, FeCmdPacketType, TermOptsType, CmdStartPacketType, CmdDataType, ScreenDataType, ScreenOptsType, PtyDataUpdateType, ModelUpdateType, UpdateMessage, InfoType, CmdLineUpdateType, RemotePtrType, UIContextType, HistoryInfoType, HistoryQueryOpts, WatchScreenPacketType, TermWinSize, FeInputPacketType, RemoteInputPacketType, RemoteEditType, FeStateType, ContextMenuOpts, RendererContext, WindowSize, RendererModel, PtyDataType, BookmarkType, ClientDataType, PlaybookType, PlaybookEntryType, HistoryViewDataType, RenderModeType, AlertMessageType, HistorySearchParams, ScreenLinesType, FocusTypeStrs, HistoryTypeStrs, RendererOpts, RendererPluginType, SimpleBlobRendererComponent, RendererModelContainerApi, RendererModelInitializeParams, RendererOptsUpdate, ClientMigrationInfo};