mirror of
https://github.com/wavetermdev/backup.git
synced 2026-08-05 13:57:07 -07:00
add cmdinstances to allow windows/cmds to show in multiple screenwindows
This commit is contained in:
+23
-13
@@ -51,7 +51,7 @@ function getLineDateStr(ts : number) : string {
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class LineText extends React.Component<{line : LineType}, {}> {
|
||||
class LineText extends React.Component<{sw : ScreenWindow, line : LineType}, {}> {
|
||||
render() {
|
||||
let line = this.props.line;
|
||||
let formattedTime = getLineDateStr(line.ts);
|
||||
@@ -75,27 +75,30 @@ class LineText extends React.Component<{line : LineType}, {}> {
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class LineCmd extends React.Component<{line : LineType}, {}> {
|
||||
class LineCmd extends React.Component<{sw : ScreenWindow, line : LineType}, {}> {
|
||||
termLoaded : mobx.IObservableValue<boolean> = mobx.observable.box(false);
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
let {line} = this.props;
|
||||
let {sw, line} = this.props;
|
||||
let model = GlobalModel;
|
||||
let cmd = model.getCmd(line);
|
||||
if (cmd != null) {
|
||||
let termElem = document.getElementById("term-" + getLineId(line));
|
||||
cmd.connectElem(termElem);
|
||||
cmd.connectElem(termElem, sw.screenId, sw.windowId);
|
||||
mobx.action(() => this.termLoaded.set(true))();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
let {line} = this.props;
|
||||
let {sw, line} = this.props;
|
||||
let model = GlobalModel;
|
||||
let cmd = model.getCmd(line);
|
||||
if (cmd != null) {
|
||||
cmd.disconnectElem();
|
||||
cmd.disconnectElem(sw.screenId, sw.windowId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,10 +109,14 @@ class LineCmd extends React.Component<{line : LineType}, {}> {
|
||||
|
||||
@boundMethod
|
||||
doRefresh() {
|
||||
let {sw, line} = this.props;
|
||||
let model = GlobalModel;
|
||||
let cmd = model.getCmd(this.props.line);
|
||||
let cmd = model.getCmd(line);
|
||||
if (cmd != null) {
|
||||
cmd.reloadTerminal(500);
|
||||
let termWrap = cmd.getTermWrap(sw.screenId, sw.windowId);
|
||||
if (termWrap != null) {
|
||||
termWrap.reloadTerminal(500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,7 +170,7 @@ class LineCmd extends React.Component<{line : LineType}, {}> {
|
||||
}
|
||||
|
||||
render() {
|
||||
let {line} = this.props;
|
||||
let {sw, line} = this.props;
|
||||
let model = GlobalModel;
|
||||
let lineid = line.lineid.toString();
|
||||
let formattedTime = getLineDateStr(line.ts);
|
||||
@@ -171,13 +178,16 @@ class LineCmd extends React.Component<{line : LineType}, {}> {
|
||||
if (cmd == null) {
|
||||
return <div className="line line-invalid">[cmd not found '{line.cmdid}']</div>;
|
||||
}
|
||||
let termLoaded = this.termLoaded.get();
|
||||
let cellHeightPx = 17;
|
||||
let totalHeight = cellHeightPx * cmd.usedRows.get();
|
||||
let usedRows = cmd.getUsedRows(sw.screenId, sw.windowId);
|
||||
let totalHeight = cellHeightPx * usedRows;
|
||||
let remote = model.getRemote(cmd.remoteId);
|
||||
let status = cmd.getStatus();
|
||||
let running = (status == "running");
|
||||
let detached = (status == "detached");
|
||||
let termOpts = cmd.getTermOpts();
|
||||
let isFocused = cmd.getIsFocused(sw.screenId, sw.windowId);
|
||||
return (
|
||||
<div className="line line-cmd" id={"line-" + getLineId(line)}>
|
||||
<div className={cn("avatar",{"num4": lineid.length == 4}, {"num5": lineid.length >= 5}, {"running": running}, {"detached": detached})}>
|
||||
@@ -195,7 +205,7 @@ class LineCmd extends React.Component<{line : LineType}, {}> {
|
||||
</div>
|
||||
{this.renderCmdText(cmd, remote)}
|
||||
</div>
|
||||
<div className={cn("terminal-wrapper", {"focus": cmd.isFocused.get()})} style={{overflowY: "hidden"}}>
|
||||
<div className={cn("terminal-wrapper", {"focus": isFocused})} style={{overflowY: "hidden"}}>
|
||||
<div className="terminal" id={"term-" + getLineId(line)} data-cmdid={line.cmdid} style={{height: totalHeight}}></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -208,7 +218,7 @@ class LineCmd extends React.Component<{line : LineType}, {}> {
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class Line extends React.Component<{line : LineType}, {}> {
|
||||
class Line extends React.Component<{sw : ScreenWindow, line : LineType}, {}> {
|
||||
render() {
|
||||
let line = this.props.line;
|
||||
if (line.linetype == "text") {
|
||||
@@ -429,7 +439,7 @@ class ScreenWindowView extends React.Component<{sw : ScreenWindow}, {}> {
|
||||
<div className="window-view" style={this.getWindowViewStyle()}>
|
||||
<div className="lines" onScroll={this.scrollHandler} id={this.getLinesId()}>
|
||||
<For each="line" of={win.lines} index="idx">
|
||||
<Line key={line.lineid} line={line}/>
|
||||
<Line key={line.lineid} line={line} sw={sw}/>
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+44
-99
@@ -36,72 +36,61 @@ function ces(s : string) {
|
||||
|
||||
class Cmd {
|
||||
sessionId : string;
|
||||
windowId : string;
|
||||
remoteId : string;
|
||||
cmdId : string;
|
||||
data : OV<CmdDataType>;
|
||||
|
||||
termWrap : TermWrap;
|
||||
ptyPos : number = 0;
|
||||
atRowMax : boolean = false;
|
||||
usedRowsUpdated : () => void = null;
|
||||
watching : boolean = false;
|
||||
isFocused : OV<boolean> = mobx.observable.box(false, {name: "focus"});
|
||||
usedRows : OV<number>;
|
||||
connectedElem : Element;
|
||||
instances : Record<string, TermWrap> = {};
|
||||
|
||||
constructor(cmd : CmdDataType, windowId : string) {
|
||||
constructor(cmd : CmdDataType) {
|
||||
this.sessionId = cmd.sessionid;
|
||||
this.windowId = windowId;
|
||||
this.cmdId = cmd.cmdid;
|
||||
this.remoteId = cmd.remoteid;
|
||||
this.data = mobx.observable.box(cmd, {deep: false});
|
||||
if (cmd.termopts.flexrows) {
|
||||
this.atRowMax = false;
|
||||
this.usedRows = mobx.observable.box(2, {name: "usedRows"});
|
||||
}
|
||||
else {
|
||||
this.atRowMax = true;
|
||||
this.usedRows = mobx.observable.box(cmd.termopts.rows, {name: "usedRows"});
|
||||
}
|
||||
}
|
||||
|
||||
disconnectElem() {
|
||||
this.connectedElem = null;
|
||||
}
|
||||
|
||||
connectElem(elem : Element) {
|
||||
if (this.connectedElem != null) {
|
||||
console.log("WARNING element already connected to cmd", this.cmdId, this.connectedElem);
|
||||
}
|
||||
this.connectedElem = elem;
|
||||
if (this.termWrap == null) {
|
||||
this.termWrap = new TermWrap(this.getTermOpts());
|
||||
this.reloadTerminal(0);
|
||||
}
|
||||
this.termWrap.connectToElem(elem, {
|
||||
setFocus: this.setFocus.bind(this),
|
||||
handleKey: this.handleKey.bind(this),
|
||||
});
|
||||
}
|
||||
|
||||
reloadTerminal(delayMs : number) {
|
||||
if (this.termWrap == null) {
|
||||
connectElem(elem : Element, screenId : string, windowId : string) {
|
||||
let termWrap = this.getTermWrap(screenId, windowId);
|
||||
if (termWrap != null) {
|
||||
console.log("term-wrap already exists for", screenId, windowId);
|
||||
return;
|
||||
}
|
||||
this.termWrap.terminal.clear();
|
||||
let url = sprintf("http://localhost:8080/api/ptyout?sessionid=%s&cmdid=%s", this.sessionId, this.cmdId);
|
||||
fetch(url).then((resp) => {
|
||||
if (!resp.ok) {
|
||||
throw new Error(sprintf("Bad fetch response for /api/ptyout: %d %s", resp.status, resp.statusText));
|
||||
}
|
||||
return resp.arrayBuffer()
|
||||
}).then((buf) => {
|
||||
setTimeout(() => {
|
||||
this.ptyPos = 0;
|
||||
this.updatePtyData(0, new Uint8Array(buf), buf.byteLength);
|
||||
}, delayMs);
|
||||
});
|
||||
termWrap = new TermWrap(elem, this.sessionId, this.cmdId, 0, this.getTermOpts(), this.handleKey);
|
||||
this.instances[screenId + "/" + windowId] = termWrap;
|
||||
return;
|
||||
}
|
||||
|
||||
disconnectElem(screenId : string, windowId : string) {
|
||||
let key = screenId + "/" + windowId;
|
||||
let termWrap = this.instances[key];
|
||||
if (termWrap != null) {
|
||||
termWrap.dispose();
|
||||
delete this.instances[key];
|
||||
}
|
||||
}
|
||||
|
||||
getTermWrap(screenId : string, windowId : string) : TermWrap {
|
||||
return this.instances[screenId + "/" + windowId];
|
||||
}
|
||||
|
||||
getUsedRows(screenId : string, windowId : string) : number {
|
||||
let termOpts = this.getTermOpts();
|
||||
if (!termOpts.flexrows) {
|
||||
return termOpts.rows;
|
||||
}
|
||||
let termWrap = this.getTermWrap(screenId, windowId);
|
||||
if (termWrap == null) {
|
||||
return 2;
|
||||
}
|
||||
return termWrap.usedRows.get();
|
||||
}
|
||||
|
||||
getIsFocused(screenId : string, windowId : string) : boolean {
|
||||
let termWrap = this.getTermWrap(screenId, windowId);
|
||||
if (termWrap == null) {
|
||||
return false;
|
||||
}
|
||||
return termWrap.isFocused.get();
|
||||
}
|
||||
|
||||
setCmd(cmd : CmdDataType) {
|
||||
@@ -126,34 +115,6 @@ class Cmd {
|
||||
return this.data.get().remotestate;
|
||||
}
|
||||
|
||||
updateUsedRows() {
|
||||
if (this.atRowMax) {
|
||||
return;
|
||||
}
|
||||
let tur = this.termWrap.getTermUsedRows();
|
||||
if (tur >= this.termWrap.terminal.rows) {
|
||||
this.atRowMax = true;
|
||||
}
|
||||
if (tur > this.usedRows.get()) {
|
||||
mobx.action(() => {
|
||||
let data = this.data.get();
|
||||
let oldUsedRows = this.usedRows.get();
|
||||
this.usedRows.set(tur);
|
||||
if (this.connectedElem) {
|
||||
let resizeEvent = new CustomEvent("termresize", {
|
||||
bubbles: true,
|
||||
detail: {
|
||||
cmdId: this.cmdId,
|
||||
oldUsedRows: oldUsedRows,
|
||||
newUsedRows: tur,
|
||||
},
|
||||
});
|
||||
this.connectedElem.dispatchEvent(resizeEvent);
|
||||
}
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
||||
getSingleLineCmdText() {
|
||||
let cmdText = this.data.get().cmdstr;
|
||||
if (cmdText == null) {
|
||||
@@ -175,22 +136,6 @@ class Cmd {
|
||||
return data.status == "running" || data.status == "detached";
|
||||
}
|
||||
|
||||
updatePtyData(pos : number, data : string | Uint8Array, datalen : number) {
|
||||
if (pos != this.ptyPos) {
|
||||
throw new Error(sprintf("invalid pty-update, data-pos[%d] does not match term-pos[%d]", pos, this.ptyPos));
|
||||
}
|
||||
this.ptyPos += datalen;
|
||||
this.termWrap.terminal.write(data, () => {
|
||||
this.updateUsedRows();
|
||||
});
|
||||
}
|
||||
|
||||
setFocus(focus : boolean) {
|
||||
mobx.action(() => {
|
||||
this.isFocused.set(focus);
|
||||
})();
|
||||
}
|
||||
|
||||
handleKey(event : any) {
|
||||
console.log("onkey", event);
|
||||
if (!this.isRunning()) {
|
||||
@@ -315,7 +260,7 @@ class Window {
|
||||
this.history = win.history || [];
|
||||
let cmds = win.cmds || [];
|
||||
for (let i=0; i<cmds.length; i++) {
|
||||
this.cmds[cmds[i].cmdid] = new Cmd(cmds[i], this.windowId);
|
||||
this.cmds[cmds[i].cmdid] = new Cmd(cmds[i]);
|
||||
}
|
||||
})();
|
||||
}
|
||||
@@ -360,7 +305,7 @@ class Window {
|
||||
}
|
||||
mobx.action(() => {
|
||||
if (cmd != null) {
|
||||
this.cmds[cmd.cmdid] = new Cmd(cmd, this.windowId);
|
||||
this.cmds[cmd.cmdid] = new Cmd(cmd);
|
||||
}
|
||||
let lines = this.lines;
|
||||
let lineIdx = 0;
|
||||
|
||||
+140
-22
@@ -19,28 +19,75 @@ function loadPtyOut(term : Terminal, sessionId : string, cmdId : string, delayMs
|
||||
});
|
||||
}
|
||||
|
||||
type TermEventHandler = {
|
||||
setFocus : (focus : boolean) => void,
|
||||
handleKey : (event : any) => void,
|
||||
};
|
||||
type DataUpdate = {
|
||||
data : Uint8Array,
|
||||
pos : number,
|
||||
}
|
||||
|
||||
// cmd-instance
|
||||
class TermWrap {
|
||||
terminal : any;
|
||||
usedRows : number;
|
||||
sessionId : string;
|
||||
cmdId : string;
|
||||
atRowMax : boolean;
|
||||
usedRows : mobx.IObservableValue<number>;
|
||||
isFocused : mobx.IObservableValue<boolean> = mobx.observable.box(false, {name: "focus"});
|
||||
flexRows : boolean;
|
||||
|
||||
tailReqId : string;
|
||||
cmdStatus : string;
|
||||
remoteId : string;
|
||||
connectedElem : Element;
|
||||
ptyPos : number = 0;
|
||||
reloading : boolean = false;
|
||||
dataUpdates : DataUpdate[] = [];
|
||||
loadError : mobx.IObservableValue<boolean> = mobx.observable.box(false);
|
||||
|
||||
constructor(termOpts : TermOptsType) {
|
||||
constructor(elem : Element, sessionId : string, cmdId : string, usedRows : number, termOpts : TermOptsType, keyHandler : (event : any) => void) {
|
||||
this.sessionId = sessionId;
|
||||
this.cmdId = cmdId;
|
||||
this.connectedElem = elem;
|
||||
this.flexRows = termOpts.flexrows ?? false;
|
||||
if (this.flexRows) {
|
||||
this.atRowMax = false;
|
||||
this.usedRows = mobx.observable.box(usedRows || 2);
|
||||
}
|
||||
else {
|
||||
this.atRowMax = true;
|
||||
this.usedRows = mobx.observable.box(termOpts.rows);
|
||||
}
|
||||
this.terminal = new Terminal({rows: termOpts.rows, cols: termOpts.cols, theme: {foreground: "#d3d7cf"}});
|
||||
this.flexRows = termOpts.flexrows;
|
||||
this.usedRows = 2;
|
||||
this.terminal.open(elem);
|
||||
if (keyHandler != null) {
|
||||
this.terminal.onKey(keyHandler);
|
||||
}
|
||||
this.terminal.textarea.addEventListener("focus", () => {
|
||||
this.setFocus(true);
|
||||
});
|
||||
this.terminal.textarea.addEventListener("blur", () => {
|
||||
this.setFocus(false);
|
||||
});
|
||||
this.reloadTerminal(0);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
if (this.terminal != null) {
|
||||
this.terminal.dispose();
|
||||
this.terminal = null;
|
||||
}
|
||||
}
|
||||
|
||||
disconnectElem() {
|
||||
this.connectedElem = null;
|
||||
}
|
||||
|
||||
setFocus(focus : boolean) {
|
||||
mobx.action(() => {
|
||||
this.isFocused.set(focus);
|
||||
})();
|
||||
}
|
||||
|
||||
getTermUsedRows() : number {
|
||||
let term = this.terminal;
|
||||
if (term == null) {
|
||||
return 0;
|
||||
}
|
||||
let termBuf = term._core.buffer;
|
||||
let termNumLines = termBuf.lines.length;
|
||||
let termYPos = termBuf.y;
|
||||
@@ -60,17 +107,88 @@ class TermWrap {
|
||||
return usedRows;
|
||||
}
|
||||
|
||||
connectToElem(elem : Element, eventHandler : TermEventHandler) {
|
||||
this.terminal.open(elem);
|
||||
if (eventHandler != null) {
|
||||
this.terminal.textarea.addEventListener("focus", () => {
|
||||
eventHandler.setFocus(true);
|
||||
});
|
||||
this.terminal.textarea.addEventListener("blur", () => {
|
||||
eventHandler.setFocus(false);
|
||||
});
|
||||
this.terminal.onKey(eventHandler.handleKey);
|
||||
updateUsedRows() {
|
||||
if (this.terminal == null) {
|
||||
return;
|
||||
}
|
||||
if (this.atRowMax) {
|
||||
return;
|
||||
}
|
||||
let tur = this.getTermUsedRows();
|
||||
if (tur >= this.terminal.rows) {
|
||||
this.atRowMax = true;
|
||||
}
|
||||
if (tur <= this.usedRows.get()) {
|
||||
return;
|
||||
}
|
||||
mobx.action(() => {
|
||||
let oldUsedRows = this.usedRows.get();
|
||||
this.usedRows.set(tur);
|
||||
if (this.connectedElem) {
|
||||
let resizeEvent = new CustomEvent("termresize", {
|
||||
bubbles: true,
|
||||
detail: {
|
||||
cmdId: this.cmdId,
|
||||
oldUsedRows: oldUsedRows,
|
||||
newUsedRows: tur,
|
||||
},
|
||||
});
|
||||
this.connectedElem.dispatchEvent(resizeEvent);
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
reloadTerminal(delayMs : number) {
|
||||
if (this.terminal == null) {
|
||||
return;
|
||||
}
|
||||
this.reloading = true;
|
||||
this.terminal.clear();
|
||||
let url = sprintf("http://localhost:8080/api/ptyout?sessionid=%s&cmdid=%s", this.sessionId, this.cmdId);
|
||||
fetch(url).then((resp) => {
|
||||
if (!resp.ok) {
|
||||
mobx.action(() => { this.loadError.set(true); })();
|
||||
this.dataUpdates = [];
|
||||
throw new Error(sprintf("Bad fetch response for /api/ptyout: %d %s", resp.status, resp.statusText));
|
||||
}
|
||||
return resp.arrayBuffer()
|
||||
}).then((buf) => {
|
||||
setTimeout(() => {
|
||||
this.reloading = false;
|
||||
this.ptyPos = 0;
|
||||
this.updatePtyData(0, new Uint8Array(buf));
|
||||
for (let i=0; i<this.dataUpdates.length; i++) {
|
||||
this.updatePtyData(this.dataUpdates[i].pos, this.dataUpdates[i].data);
|
||||
}
|
||||
this.dataUpdates = [];
|
||||
}, delayMs);
|
||||
});
|
||||
}
|
||||
|
||||
updatePtyData(pos : number, data : Uint8Array) {
|
||||
if (this.loadError.get()) {
|
||||
return;
|
||||
}
|
||||
if (this.reloading) {
|
||||
this.dataUpdates.push({data: data, pos: pos});
|
||||
return;
|
||||
}
|
||||
if (pos > this.ptyPos) {
|
||||
throw new Error(sprintf("invalid pty-update, data-pos[%d] does not match term-pos[%d]", pos, this.ptyPos));
|
||||
}
|
||||
if (pos < this.ptyPos) {
|
||||
let diff = this.ptyPos - pos;
|
||||
if (diff >= data.length) {
|
||||
// already contains all the data
|
||||
return;
|
||||
}
|
||||
data = data.slice(diff);
|
||||
pos += diff;
|
||||
}
|
||||
this.ptyPos += data.length;
|
||||
this.terminal.write(data, () => {
|
||||
this.updateUsedRows();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user