mirror of
https://github.com/wavetermdev/backup.git
synced 2026-08-05 13:57:07 -07:00
checkpoint, moving to model
This commit is contained in:
+49
-44
@@ -9,10 +9,14 @@ import cn from "classnames"
|
||||
import {TermWrap} from "./term";
|
||||
import type {SessionDataType, LineType, CmdDataType, RemoteType} from "./types";
|
||||
import localizedFormat from 'dayjs/plugin/localizedFormat';
|
||||
import {GlobalMode, Cmd, Window} from "./model";
|
||||
import {GlobalModel, Session, Cmd, Window} from "./model";
|
||||
|
||||
dayjs.extend(localizedFormat)
|
||||
|
||||
function getLineId(line : LineType) : string {
|
||||
return sprintf("%s-%s-%s", line.sessionid, line.windowid, line.lineid);
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class LineMeta extends React.Component<{line : LineType}, {}> {
|
||||
render() {
|
||||
@@ -79,10 +83,11 @@ class LineCmd extends React.Component<{line : LineType}, {}> {
|
||||
componentDidMount() {
|
||||
let {line} = this.props;
|
||||
let model = GlobalModel;
|
||||
let termElem = document.getElementById("term-" + getLineId(line));
|
||||
let termWrap = session.getTermWrapByLine(line);
|
||||
termWrap.changeSizeCallback = this.props.changeSizeCallback;
|
||||
termWrap.connectToElem(termElem);
|
||||
let cmd = model.getCmd(line);
|
||||
if (cmd != null) {
|
||||
let termElem = document.getElementById("term-" + getLineId(line));
|
||||
cmd.connectToElem(termElem);
|
||||
}
|
||||
if (line.isnew) {
|
||||
setTimeout(() => this.scrollIntoView(), 100);
|
||||
line.isnew = false;
|
||||
@@ -96,9 +101,11 @@ class LineCmd extends React.Component<{line : LineType}, {}> {
|
||||
|
||||
@boundMethod
|
||||
doRefresh() {
|
||||
let {session, line} = this.props;
|
||||
let termWrap = session.getTermWrapByLine(line);
|
||||
termWrap.reloadTerminal(true, 500);
|
||||
let model = GlobalModel;
|
||||
let cmd = model.getCmd(this.props.line);
|
||||
if (cmd != null) {
|
||||
cmd.reloadTerminal(true, 500);
|
||||
}
|
||||
}
|
||||
|
||||
replaceHomePath(path : string, homeDir : string) : string {
|
||||
@@ -136,40 +143,36 @@ class LineCmd extends React.Component<{line : LineType}, {}> {
|
||||
}
|
||||
}
|
||||
let cwd = "(unknown)";
|
||||
if (cmd.remotestate && cmd.remotestate.cwd) {
|
||||
cwd = cmd.remotestate.cwd;
|
||||
let remoteState = cmd.getRemoteState();
|
||||
if (remoteState && remoteState.cwd) {
|
||||
cwd = remoteState.cwd;
|
||||
}
|
||||
if (remote.remotevars.home) {
|
||||
cwd = this.replaceHomePath(cwd, remote.remotevars.home)
|
||||
}
|
||||
return (
|
||||
<div className="metapart-mono cmdtext">
|
||||
<span className="term-bright-green">[{promptStr} {cwd}]</span> {this.singleLineCmdText(cmd.cmdstr)}
|
||||
<span className="term-bright-green">[{promptStr} {cwd}]</span> {cmd.getSingleLineCmdText()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
let {session, line} = this.props;
|
||||
let {line} = this.props;
|
||||
let model = GlobalModel;
|
||||
let lineid = line.lineid.toString();
|
||||
let running = false;
|
||||
let detached = false;
|
||||
let rows = 0;
|
||||
let cols = 0;
|
||||
let termWrap = session.getTermWrapByLine(line);
|
||||
let renderVersion = termWrap.getRenderVersion();
|
||||
termWrap.resizeToContent();
|
||||
let termSize = termWrap.getSize();
|
||||
let formattedTime = getLineDateStr(line.ts);
|
||||
let cellHeightPx = 17;
|
||||
let totalHeight = cellHeightPx * termWrap.usedRows;
|
||||
let cmd : CmdDataType = session.getCmd(line.cmdid);
|
||||
let remote : RemoteType = null;
|
||||
if (cmd != null) {
|
||||
remote = session.getRemote(cmd.remoteid);
|
||||
running = (cmd.status == "running");
|
||||
detached = (cmd.status == "detached");
|
||||
let cmd = model.getCmd(line);
|
||||
if (cmd == null) {
|
||||
return <div className="line line-invalid">[cmd not found '{line.cmdid}']</div>;
|
||||
}
|
||||
let cellHeightPx = 17;
|
||||
let totalHeight = cellHeightPx * cmd.usedRows.get();
|
||||
let remote = model.getRemote(cmd.remoteId);
|
||||
let status = cmd.getStatus();
|
||||
let running = (status == "running");
|
||||
let detached = (status == "detached");
|
||||
let termOpts = cmd.getTermOpts();
|
||||
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})}>
|
||||
@@ -183,12 +186,11 @@ class LineCmd extends React.Component<{line : LineType}, {}> {
|
||||
<div className="meta">
|
||||
<div className="metapart-mono" style={{display: "none"}}>
|
||||
{line.cmdid}
|
||||
<If condition={termSize.rows > 0}>({termSize.rows}x{termSize.cols})</If>
|
||||
{termWrap.ptyPos} bytes, v{renderVersion}
|
||||
({termOpts.rows}x{termOpts.cols})
|
||||
</div>
|
||||
{this.renderCmdText(cmd, remote)}
|
||||
</div>
|
||||
<div className={cn("terminal-wrapper", {"focus": termWrap.isFocused.get()})} style={{overflowY: "hidden"}}>
|
||||
<div className={cn("terminal-wrapper", {"focus": cmd.isFocused.get()})} style={{overflowY: "hidden"}}>
|
||||
<div className="terminal" id={"term-" + getLineId(line)} data-cmdid={line.cmdid} style={{height: totalHeight}}></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -201,7 +203,7 @@ class LineCmd extends React.Component<{line : LineType}, {}> {
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class Line extends React.Component<{line : LineType, session : Session, changeSizeCallback? : (term : TermWrap) => void}, {}> {
|
||||
class Line extends React.Component<{line : LineType, changeSizeCallback? : (term : TermWrap) => void}, {}> {
|
||||
render() {
|
||||
let line = this.props.line;
|
||||
if (line.linetype == "text") {
|
||||
@@ -215,7 +217,7 @@ class Line extends React.Component<{line : LineType, session : Session, changeSi
|
||||
}
|
||||
|
||||
@mobxReact.observer
|
||||
class CmdInput extends React.Component<{windowid : string}, {}> {
|
||||
class CmdInput extends React.Component<{}, {}> {
|
||||
historyIndex : mobx.IObservableValue<number> = mobx.observable.box(0, {name: "history-index"});
|
||||
modHistory : mobx.IObservableArray<string> = mobx.observable.array([""], {name: "mod-history"});
|
||||
|
||||
@@ -223,7 +225,7 @@ class CmdInput extends React.Component<{windowid : string}, {}> {
|
||||
onKeyDown(e : any) {
|
||||
mobx.action(() => {
|
||||
let model = GlobalModel;
|
||||
let win = getActiveWindow();
|
||||
let win = model.getActiveWindow();
|
||||
let ctrlMod = e.getModifierState("Control") || e.getModifierState("Meta") || e.getModifierState("Shift");
|
||||
if (e.code == "Enter" && !ctrlMod) {
|
||||
e.preventDefault();
|
||||
@@ -270,6 +272,10 @@ class CmdInput extends React.Component<{windowid : string}, {}> {
|
||||
if (hidx < this.modHistory.length && this.modHistory[hidx] != null) {
|
||||
return this.modHistory[hidx];
|
||||
}
|
||||
let win = model.getActiveWindow();
|
||||
if (win == null) {
|
||||
return "";
|
||||
}
|
||||
let hitem = win.getHistoryItem(-hidx);
|
||||
if (hitem == null) {
|
||||
return "";
|
||||
@@ -292,12 +298,11 @@ class CmdInput extends React.Component<{windowid : string}, {}> {
|
||||
|
||||
@boundMethod
|
||||
doSubmitCmd() {
|
||||
let {windowid} = this.props;
|
||||
let model = GlobalModel;
|
||||
let commandStr = this.getCurLine();
|
||||
let hitem = {cmdtext: commandStr};
|
||||
this.clearCurLine();
|
||||
model.submitCommand(windowid, commandStr);
|
||||
model.submitCommand(commandStr);
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -343,10 +348,9 @@ class SessionView extends React.Component<{}, {}> {
|
||||
@boundMethod
|
||||
changeSizeCallback(term : TermWrap) {
|
||||
if (this.shouldFollow.get()) {
|
||||
let session = this.props.session;
|
||||
let window = session.getActiveWindow();
|
||||
let window = GlobalModel.getActiveWindow();
|
||||
let lines = window.lines;
|
||||
if (lines == null) {
|
||||
if (lines == null || lines.length == 0) {
|
||||
return;
|
||||
}
|
||||
let lastLine = lines[lines.length-1];
|
||||
@@ -359,7 +363,7 @@ class SessionView extends React.Component<{}, {}> {
|
||||
let model = GlobalModel;
|
||||
let win = model.getActiveWindow();
|
||||
if (win == null) {
|
||||
return <div className="session-view">(no active window {session.activeWindowId.get()})</div>;
|
||||
return <div className="session-view">(no active window)</div>;
|
||||
}
|
||||
if (!win.linesLoaded.get()) {
|
||||
return <div className="session-view">(loading)</div>;
|
||||
@@ -373,7 +377,7 @@ class SessionView extends React.Component<{}, {}> {
|
||||
<Line key={line.lineid} line={line} changeSizeCallback={this.changeSizeCallback}/>
|
||||
</For>
|
||||
</div>
|
||||
<CmdInput windowid={win.windowid}/>
|
||||
<CmdInput/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -397,6 +401,7 @@ class MainSideBar extends React.Component<{}, {}> {
|
||||
render() {
|
||||
let model = GlobalModel;
|
||||
let curSessionId = model.curSessionId.get();
|
||||
let session : Session = null;
|
||||
return (
|
||||
<div className={cn("main-sidebar", {"collapsed": this.collapsed.get()})}>
|
||||
<div className="collapse-container">
|
||||
@@ -410,12 +415,12 @@ class MainSideBar extends React.Component<{}, {}> {
|
||||
Private Sessions
|
||||
</p>
|
||||
<ul className="menu-list">
|
||||
<If condition={!model.sessionListLoaded()}>
|
||||
<If condition={!model.sessionListLoaded.get()}>
|
||||
<li><a>(loading)</a></li>
|
||||
</If>
|
||||
<If condition={model.sessionListLoaded()}>
|
||||
<If condition={model.sessionListLoaded.get()}>
|
||||
<For each="session" of={model.sessionList}>
|
||||
<li key={session.sessionid}><a className={cn({"is-active": curSessionId == session.sessionid})} onClick={() => this.handleSessionClick(session.sessionid)}>#{session.name}</a></li>
|
||||
<li key={session.sessionId}><a className={cn({"is-active": curSessionId == session.sessionId})} onClick={() => this.handleSessionClick(session.sessionId)}>#{session.name.get()}</a></li>
|
||||
</For>
|
||||
<li className="new-session"><a className="new-session"><i className="fa fa-plus"/> New Session</a></li>
|
||||
</If>
|
||||
|
||||
+256
-18
@@ -4,25 +4,71 @@ import {boundMethod} from "autobind-decorator";
|
||||
import {handleJsonFetchResponse} from "./util";
|
||||
import {TermWrap} from "./term";
|
||||
import {v4 as uuidv4} from "uuid";
|
||||
import type {SessionDataType, WindowDataType, LineType, RemoteType, HistoryItem, RemoteInstanceType, CmdDataType, FeCmdPacketType} from "./types";
|
||||
import type {SessionDataType, WindowDataType, LineType, RemoteType, HistoryItem, RemoteInstanceType, CmdDataType, FeCmdPacketType, TermOptsType, RemoteStateType} from "./types";
|
||||
import {WSControl} from "./ws";
|
||||
|
||||
type OV<V> = mobx.IObservableValue<V>;
|
||||
type OArr<V> = mobx.IObservableArray<V>;
|
||||
|
||||
function isBlank(s : string) {
|
||||
return (s == null || s == "");
|
||||
}
|
||||
|
||||
class Cmd {
|
||||
sessionId : string;
|
||||
windowId : string;
|
||||
remoteId : string;
|
||||
cmdId : string;
|
||||
data : OV<CmdDataType>;
|
||||
|
||||
terminal : any;
|
||||
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>;
|
||||
|
||||
constructor(cmd : CmdDataType) {
|
||||
constructor(cmd : CmdDataType, windowId : string) {
|
||||
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"});
|
||||
}
|
||||
}
|
||||
|
||||
connectToElem(elem : Element) {
|
||||
this.termWrap.connectToElem(elem, {
|
||||
setFocus: this.setFocus.bind(this),
|
||||
handleKey: this.handleKey.bind(this),
|
||||
});
|
||||
}
|
||||
|
||||
reloadTerminal(startTail : boolean, delayMs : number) {
|
||||
if (this.termWrap == null) {
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
setCmd(cmd : CmdDataType) {
|
||||
@@ -31,6 +77,40 @@ class Cmd {
|
||||
});
|
||||
}
|
||||
|
||||
getStatus() : string {
|
||||
return this.data.get().status;
|
||||
}
|
||||
|
||||
getTermOpts() : TermOptsType {
|
||||
return this.data.get().termopts;
|
||||
}
|
||||
|
||||
getCmdStr() : string {
|
||||
return this.data.get().cmdstr;
|
||||
}
|
||||
|
||||
getRemoteState() : RemoteStateType {
|
||||
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);
|
||||
GlobalModel.termChangeSize(this.sessionId, this.windowId, this.cmdId, oldUsedRows, tur);
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
||||
getSingleLineCmdText() {
|
||||
let cmdText = this.data.get().cmdstr;
|
||||
if (cmdText == null) {
|
||||
@@ -46,9 +126,46 @@ class Cmd {
|
||||
}
|
||||
return cmdText;
|
||||
}
|
||||
|
||||
isRunning() : boolean {
|
||||
let data = this.data.get();
|
||||
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()) {
|
||||
return;
|
||||
}
|
||||
let data = this.data.get();
|
||||
let inputPacket = {
|
||||
type: "input",
|
||||
ck: this.sessionId + "/" + this.cmdId,
|
||||
inputdata: btoa(event.key),
|
||||
remoteid: this.remoteId,
|
||||
};
|
||||
GlobalModel.sendInputPacket(inputPacket);
|
||||
}
|
||||
};
|
||||
|
||||
class Window {
|
||||
sessionId : string;
|
||||
windowId : string;
|
||||
name : OV<string>;
|
||||
curRemote : OV<string>;
|
||||
@@ -56,20 +173,47 @@ class Window {
|
||||
lines : OArr<LineType> = mobx.observable.array([]);
|
||||
linesLoaded : OV<boolean> = mobx.observable.box(false);
|
||||
history : any[] = [];
|
||||
cmds : Record<string, Cmd> = {};
|
||||
|
||||
constructor(wdata : WindowDataType) {
|
||||
this.sessionId = wdata.sessionid;
|
||||
this.windowId = wdata.windowid;
|
||||
this.name = mobx.observable.box(wdata.name);
|
||||
this.curRemote = mobx.observable.box(wdata.curremote);
|
||||
}
|
||||
|
||||
|
||||
getNumHistoryItems() : number {
|
||||
return 0;
|
||||
}
|
||||
|
||||
getHistoryItem() : any {
|
||||
getHistoryItem(hnum : number) : any {
|
||||
return null
|
||||
}
|
||||
|
||||
updateWindow(win : WindowDataType, isActive : boolean) {
|
||||
mobx.action(() => {
|
||||
if (!isBlank(win.name)) {
|
||||
this.name.set(win.name)
|
||||
}
|
||||
if (!isBlank(win.curremote)) {
|
||||
this.curRemote.set(win.curremote);
|
||||
}
|
||||
if (!isActive) {
|
||||
return;
|
||||
}
|
||||
this.linesLoaded.set(true);
|
||||
this.lines.replace(win.lines || []);
|
||||
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);
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
getCmd(cmdId : string) {
|
||||
return this.cmds[cmdId];
|
||||
}
|
||||
};
|
||||
|
||||
class Session {
|
||||
@@ -92,18 +236,41 @@ class Session {
|
||||
this.curWindowId = mobx.observable.box((wins.length == 0 ? null : wins[0].windowId));
|
||||
}
|
||||
|
||||
getActiveWindow() : Window {
|
||||
let cwin = this.curWindowId.get();
|
||||
if (cwin == null) {
|
||||
updateWindow(win : WindowDataType, isActive : boolean) {
|
||||
mobx.action(() => {
|
||||
for (let i=0; i<this.windows.length; i++) {
|
||||
let foundWin = this.windows[i];
|
||||
if (foundWin.windowId != win.windowid) {
|
||||
continue;
|
||||
}
|
||||
if (win.remove) {
|
||||
this.windows.splice(i, 1);
|
||||
return;
|
||||
}
|
||||
foundWin.updateWindow(win, isActive);
|
||||
return;
|
||||
}
|
||||
let newWindow = new Window(win);
|
||||
newWindow.updateWindow(win, isActive);
|
||||
this.windows.push(newWindow);
|
||||
})();
|
||||
}
|
||||
|
||||
getWindowById(windowId : string) : Window {
|
||||
if (windowId == null) {
|
||||
return null;
|
||||
}
|
||||
for (let i=0; i<this.windows.length; i++) {
|
||||
if (this.windows[i].windowId == cwin) {
|
||||
if (this.windows[i].windowId == windowId) {
|
||||
return this.windows[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
getActiveWindow() : Window {
|
||||
return this.getWindowById(this.curWindowId.get());
|
||||
}
|
||||
}
|
||||
|
||||
class Model {
|
||||
@@ -111,11 +278,13 @@ class Model {
|
||||
curSessionId : OV<string> = mobx.observable.box(null);
|
||||
sessionListLoaded : OV<boolean> = mobx.observable.box(false);
|
||||
sessionList : OArr<Session> = mobx.observable.array([], {name: "SessionList"});
|
||||
cmds : Record<string, Cmd> = {};
|
||||
ws : WSControl;
|
||||
remotes : OArr<RemoteType> = mobx.observable.array([], {deep: false});
|
||||
remotesLoaded : OV<boolean> = mobx.observable.box(false);
|
||||
|
||||
constructor() {
|
||||
this.clientId = uuidv4();
|
||||
this.loadRemotes();
|
||||
this.loadSessionList();
|
||||
this.ws = new WSControl(this.clientId, this.onMessage.bind(this))
|
||||
this.ws.reconnect();
|
||||
@@ -129,12 +298,15 @@ class Model {
|
||||
}
|
||||
|
||||
getActiveSession() : Session {
|
||||
let sid = this.curSessionId.get();
|
||||
if (sid == null) {
|
||||
return this.getSessionById(this.curSessionId.get());
|
||||
}
|
||||
|
||||
getSessionById(sessionId : string) : Session {
|
||||
if (sessionId == null) {
|
||||
return null;
|
||||
}
|
||||
for (let i=0; i<this.sessionList.length; i++) {
|
||||
if (this.sessionList[i].sessionId == sid) {
|
||||
if (this.sessionList[i].sessionId == sessionId) {
|
||||
return this.sessionList[i];
|
||||
}
|
||||
}
|
||||
@@ -149,11 +321,16 @@ class Model {
|
||||
return session.getActiveWindow();
|
||||
}
|
||||
|
||||
getCmd(cmdId : string) : Cmd {
|
||||
return this.cmds[cmdId];
|
||||
submitCommand(cmdStr : string) {
|
||||
}
|
||||
|
||||
submitCommand(windowId : string, cmdStr : string) {
|
||||
updateWindow(win : WindowDataType) {
|
||||
let session = this.getSessionById(win.sessionid);
|
||||
if (session == null) {
|
||||
return;
|
||||
}
|
||||
let isActive = (win.sessionid == this.curSessionId.get());
|
||||
session.updateWindow(win, isActive);
|
||||
}
|
||||
|
||||
loadSessionList() {
|
||||
@@ -174,12 +351,72 @@ class Model {
|
||||
this.sessionList.replace(slist);
|
||||
this.sessionListLoaded.set(true)
|
||||
this.curSessionId.set(defaultSessionId);
|
||||
let win = this.getActiveWindow();
|
||||
if (win != null) {
|
||||
this.loadWindow(win.sessionId, win.windowId);
|
||||
}
|
||||
})();
|
||||
}).catch((err) => {
|
||||
console.log("error getting session list");
|
||||
this.errorHandler("getting session list", err);
|
||||
});
|
||||
}
|
||||
|
||||
loadWindow(sessionId : string, windowId : string) {
|
||||
let usp = new URLSearchParams({sessionid: sessionId, windowid: windowId});
|
||||
let url = new URL(sprintf("http://localhost:8080/api/get-window?") + usp.toString());
|
||||
fetch(url).then((resp) => handleJsonFetchResponse(url, resp)).then((data) => {
|
||||
if (data.data == null) {
|
||||
console.log("null window returned from get-window");
|
||||
return;
|
||||
}
|
||||
this.updateWindow(data.data);
|
||||
return;
|
||||
}).catch((err) => {
|
||||
this.errorHandler(sprintf("getting window=%s", windowId), err);
|
||||
});
|
||||
}
|
||||
|
||||
loadRemotes() {
|
||||
let url = new URL("http://localhost:8080/api/get-remotes");
|
||||
fetch(url).then((resp) => handleJsonFetchResponse(url, resp)).then((data) => {
|
||||
mobx.action(() => {
|
||||
this.remotes.replace(data.data || [])
|
||||
this.remotesLoaded.set(true);
|
||||
})();
|
||||
}).catch((err) => {
|
||||
this.errorHandler("calling get-remotes", err)
|
||||
});
|
||||
}
|
||||
|
||||
getRemote(remoteId : string) : RemoteType {
|
||||
for (let i=0; i<this.remotes.length; i++) {
|
||||
if (this.remotes[i].remoteid == remoteId) {
|
||||
return this.remotes[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
getCmd(line : LineType) : Cmd {
|
||||
let session = this.getSessionById(line.sessionid);
|
||||
if (session == null) {
|
||||
return null;
|
||||
}
|
||||
let window = session.getWindowById(line.windowid);
|
||||
if (window == null) {
|
||||
return null;
|
||||
}
|
||||
return window.getCmd(line.cmdid);
|
||||
}
|
||||
|
||||
termChangeSize(sessionId : string, windowId : string, cmdId : string, oldUsedRows : number, newUsedRows : number) {
|
||||
console.log("change-size", sessionId + "/" + windowId + "/" + cmdId, oldUsedRows, "=>", newUsedRows);
|
||||
}
|
||||
|
||||
errorHandler(str : string, err : any) {
|
||||
console.log("[error]", str, err);
|
||||
}
|
||||
|
||||
sendInputPacket(inputPacket : any) {
|
||||
this.ws.pushMessage(inputPacket);
|
||||
}
|
||||
@@ -191,7 +428,7 @@ if ((window as any).GlobalModal == null) {
|
||||
}
|
||||
GlobalModel = (window as any).GlobalModel;
|
||||
|
||||
export {Model, Window, GlobalModel, Cmd};
|
||||
export {Model, Session, Window, GlobalModel, Cmd};
|
||||
|
||||
|
||||
// GlobalWS.registerAndSendGetCmd(getCmdPacket, (dataPacket) => {
|
||||
@@ -228,3 +465,4 @@ reloadTerminal(startTail : boolean, delayMs : number) {
|
||||
return this.cmdStatus == "running" || this.cmdStatus == "detached";
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
+1
-1
@@ -156,7 +156,7 @@ class Session {
|
||||
this.loadWindowLines(windowid);
|
||||
}
|
||||
|
||||
submitCommand(windowid : string, commandStr : string) {
|
||||
submitCommand(windowId : string, commandStr : string) {
|
||||
let url = sprintf("http://localhost:8080/api/run-command");
|
||||
let data : FeCmdPacketType = {type: "fecmd", sessionid: this.sessionId, windowid: windowid, cmdstr: commandStr, userid: GlobalUser, remotestate: null};
|
||||
let curWindow = this.getCurWindow();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import * as mobx from "mobx";
|
||||
import * as React from "react";
|
||||
import {createRoot} from 'react-dom/client';
|
||||
import {sprintf} from "sprintf-js";
|
||||
@@ -17,4 +18,6 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
root.render(reactElem);
|
||||
});
|
||||
|
||||
(window as any).mobx = mobx;
|
||||
|
||||
console.log("SCRIPTHAUS", VERSION)
|
||||
|
||||
+21
-110
@@ -4,6 +4,7 @@ import {sprintf} from "sprintf-js";
|
||||
import {boundMethod} from "autobind-decorator";
|
||||
import {v4 as uuidv4} from "uuid";
|
||||
import {GlobalModel} from "./model";
|
||||
import type {TermOptsType} from "./types";
|
||||
|
||||
function loadPtyOut(term : Terminal, sessionId : string, cmdId : string, delayMs : number, callback?: (number) => void) {
|
||||
term.clear()
|
||||
@@ -18,86 +19,35 @@ function loadPtyOut(term : Terminal, sessionId : string, cmdId : string, delayMs
|
||||
});
|
||||
}
|
||||
|
||||
type TermEventHandler = {
|
||||
setFocus : (focus : boolean) => void,
|
||||
handleKey : (event : any) => void,
|
||||
};
|
||||
|
||||
class TermWrap {
|
||||
terminal : any;
|
||||
termId : string;
|
||||
sessionId : string;
|
||||
cmdId : string;
|
||||
ptyPos : number = 0;
|
||||
runPos : number = 0;
|
||||
runData : string = "";
|
||||
renderVersion : mobx.IObservableValue<number> = mobx.observable.box(1, {name: "renderVersion"});
|
||||
isFocused : mobx.IObservableValue<boolean> = mobx.observable.box(false, {name: "focus"});
|
||||
flexRows : boolean = true;
|
||||
maxRows : number = 25;
|
||||
atRowMax : boolean = false;
|
||||
initialized : boolean = false;
|
||||
changeSizeCallback : (TermWrap) => void = null;
|
||||
usedRows : number;
|
||||
flexRows : boolean;
|
||||
|
||||
tailReqId : string;
|
||||
cmdStatus : string;
|
||||
remoteId : string;
|
||||
|
||||
constructor(sessionId : string, cmdId : string, remoteId : string, status : string) {
|
||||
this.termId = uuidv4();
|
||||
this.sessionId = sessionId;
|
||||
this.cmdId = cmdId;
|
||||
this.remoteId = remoteId;
|
||||
this.cmdStatus = status;
|
||||
this.terminal = new Terminal({rows: 25, cols: 80, theme: {foreground: "#d3d7cf"}});
|
||||
constructor(termOpts : TermOptsType) {
|
||||
this.terminal = new Terminal({rows: termOpts.rows, cols: termOpts.cols, theme: {foreground: "#d3d7cf"}});
|
||||
this.flexRows = termOpts.flexrows;
|
||||
this.usedRows = 2;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
}
|
||||
|
||||
isRunning() : boolean {
|
||||
return this.cmdStatus == "running" || this.cmdStatus == "detached";
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
onKeyHandler(event : any) {
|
||||
console.log("onkey", event);
|
||||
if (!this.isRunning()) {
|
||||
return;
|
||||
}
|
||||
let inputPacket = {
|
||||
type: "input",
|
||||
ck: this.sessionId + "/" + this.cmdId,
|
||||
inputdata: btoa(event.key),
|
||||
remoteid: this.remoteId,
|
||||
};
|
||||
GlobalModel.sendInputPacket(inputPacket);
|
||||
}
|
||||
|
||||
// datalen is passed because data could be utf-8 and data.length is not the actual *byte* length
|
||||
updatePtyData(pos : number, data : string, 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.terminal.write(data, () => {
|
||||
mobx.action(() => {
|
||||
this.resizeToContent();
|
||||
this.incRenderVersion();
|
||||
})();
|
||||
});
|
||||
}
|
||||
|
||||
resizeToContent() {
|
||||
if (this.atRowMax) {
|
||||
return;
|
||||
}
|
||||
getTermUsedRows() : number {
|
||||
let term = this.terminal;
|
||||
let termBuf = term._core.buffer;
|
||||
let termNumLines = termBuf.lines.length;
|
||||
let termYPos = termBuf.y;
|
||||
let usedRows = 2;
|
||||
if (termNumLines > term.rows) {
|
||||
this.usedRows = term.rows;
|
||||
this.atRowMax = true;
|
||||
return;
|
||||
if (termNumLines >= term.rows) {
|
||||
return term.rows;
|
||||
}
|
||||
let usedRows = 2;
|
||||
if (termYPos >= usedRows) {
|
||||
usedRows = termYPos + 1;
|
||||
}
|
||||
@@ -107,58 +57,19 @@ class TermWrap {
|
||||
usedRows = i+1;
|
||||
}
|
||||
}
|
||||
if (this.usedRows != usedRows) {
|
||||
this.usedRows = usedRows;
|
||||
if (this.changeSizeCallback != null) {
|
||||
let cb = this.changeSizeCallback;
|
||||
setTimeout(() => cb(this), 0);
|
||||
}
|
||||
}
|
||||
return;
|
||||
return usedRows;
|
||||
}
|
||||
|
||||
setSize(rows : number, cols : number, flexRows : boolean) {
|
||||
this.flexRows = true;
|
||||
this.maxRows = rows;
|
||||
if (!flexRows) {
|
||||
this.terminal.resize(rows, cols);
|
||||
setTimeout(() => this.incRenderVersion(), 10);
|
||||
return;
|
||||
}
|
||||
this.resizeToContent();
|
||||
}
|
||||
|
||||
getSize() : {rows : number, cols : number} {
|
||||
return {rows: this.terminal.rows, cols: this.terminal.cols};
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
setFocus(val : boolean) {
|
||||
mobx.action(() => this.isFocused.set(val))();
|
||||
}
|
||||
|
||||
getRenderVersion() : number {
|
||||
return this.renderVersion.get();
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
incRenderVersion() {
|
||||
mobx.action(() => this.renderVersion.set(this.renderVersion.get() + 1))();
|
||||
}
|
||||
|
||||
connectToElem(elem : Element) {
|
||||
connectToElem(elem : Element, eventHandler : TermEventHandler) {
|
||||
this.terminal.open(elem);
|
||||
if (this.isRunning()) {
|
||||
if (eventHandler != null) {
|
||||
this.terminal.textarea.addEventListener("focus", () => {
|
||||
this.setFocus(true);
|
||||
eventHandler.setFocus(true);
|
||||
});
|
||||
this.terminal.textarea.addEventListener("blur", () => {
|
||||
this.setFocus(false);
|
||||
eventHandler.setFocus(false);
|
||||
});
|
||||
this.terminal.onKey(this.onKeyHandler);
|
||||
}
|
||||
else {
|
||||
this.terminal.onKey(this.onKeyHandler);
|
||||
this.terminal.onKey(eventHandler.handleKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+7
-1
@@ -5,6 +5,7 @@ type SessionDataType = {
|
||||
name : string,
|
||||
windows : WindowDataType[],
|
||||
cmds : CmdDataType[],
|
||||
remove : boolean,
|
||||
};
|
||||
|
||||
type LineType = {
|
||||
@@ -40,6 +41,7 @@ type RemoteInstanceType = {
|
||||
remoteid : string,
|
||||
sessionscope : boolean,
|
||||
state : RemoteStateType,
|
||||
version : number,
|
||||
}
|
||||
|
||||
type WindowDataType = {
|
||||
@@ -48,11 +50,15 @@ type WindowDataType = {
|
||||
name : string,
|
||||
curremote : string,
|
||||
lines : LineType[],
|
||||
history : HistoryItem[],
|
||||
cmds : CmdDataType[],
|
||||
remotes : RemoteInstanceType[],
|
||||
version : number,
|
||||
remove : boolean,
|
||||
};
|
||||
|
||||
type HistoryItem = {
|
||||
cmdtext : string,
|
||||
cmdstr : string,
|
||||
};
|
||||
|
||||
type CmdRemoteStateType = {
|
||||
|
||||
Reference in New Issue
Block a user