import * as React from "react"; import * as mobxReact from "mobx-react"; import * as mobx from "mobx"; import {sprintf} from "sprintf-js"; import {boundMethod} from "autobind-decorator"; import {debounce} from "throttle-debounce"; import {v4 as uuidv4} from "uuid"; import dayjs from "dayjs"; import {If, For, When, Otherwise, Choose} from "tsx-control-statements/components"; import cn from "classnames"; import {TermWrap} from "./term"; import type {SessionDataType, LineType, CmdDataType, RemoteType, RemoteStateType, RemoteInstanceType, RemotePtrType, HistoryItem, HistoryQueryOpts, RemoteEditType} from "./types"; import localizedFormat from 'dayjs/plugin/localizedFormat'; import {GlobalModel, GlobalCommandRunner, Session, Cmd, Window, Screen, ScreenWindow, riToRPtr, widthToCols, termWidthFromCols, termHeightFromRows} from "./model"; dayjs.extend(localizedFormat) const RemotePtyRows = 8; const RemotePtyCols = 80; const PasswordUnchangedSentinel = "--unchanged--"; const RemoteColors = ["red", "green", "yellow", "blue", "magenta", "cyan", "white", "orange"]; type InterObsValue = { sessionid : string, windowid : string, lineid : string, cmdid : string, visible : mobx.IObservableValue, timeoutid? : any, }; let globalLineWeakMap = new WeakMap(); function isBlank(s : string) : boolean { return (s == null || s == ""); } function windowLinesDOMId(windowid : string) { return "window-lines-" + windowid; } function scrollDiv(div : any, amt : number) { if (div == null) { return; } let newScrollTop = div.scrollTop + amt; if (newScrollTop < 0) { newScrollTop = 0; } div.scrollTo({top: newScrollTop, behavior: "smooth"}); } function pageSize(div : any) : number { if (div == null) { return 300; } let size = div.clientHeight; if (size > 500) { size = size - 100; } else if (size > 200) { size = size - 30; } return size; } function interObsCallback(entries) { let now = Date.now(); entries.forEach((entry) => { let line = globalLineWeakMap.get(entry.target); if ((line.timeoutid != null) && (line.visible.get() == entry.isIntersecting)) { clearTimeout(line.timeoutid); line.timeoutid = null; return; } if (line.visible.get() != entry.isIntersecting && line.timeoutid == null) { line.timeoutid = setTimeout(() => { line.timeoutid = null; mobx.action(() => { line.visible.set(entry.isIntersecting); })(); }, 250); return; } }); } function getLineId(line : LineType) : string { return sprintf("%s-%s-%s", line.sessionid, line.windowid, line.lineid); } function makeFullRemoteRef(ownerName : string, remoteRef : string, name : string) : string { if (isBlank(ownerName) && isBlank(name)) { return remoteRef; } if (!isBlank(ownerName) && isBlank(name)) { return ownerName + ":" + remoteRef; } if (isBlank(ownerName) && !isBlank(name)) { return remoteRef + ":" + name; } return ownerName + ":" + remoteRef + ":" + name; } function getRemoteStr(rptr : RemotePtrType) : string { if (rptr == null || isBlank(rptr.remoteid)) { return "(invalid remote)"; } let username = (isBlank(rptr.ownerid) ? null : GlobalModel.resolveUserIdToName(rptr.ownerid)); let remoteRef = GlobalModel.resolveRemoteIdToRef(rptr.remoteid); let fullRef = makeFullRemoteRef(username, remoteRef, rptr.name); return fullRef; } function replaceHomePath(path : string, homeDir : string) : string { if (path == homeDir) { return "~"; } if (path.startsWith(homeDir + "/")) { return "~" + path.substr(homeDir.length); } return path; } function getCwdStr(remote : RemoteType, state : RemoteStateType) : string { if ((state == null || state.cwd == null) && remote != null) { return "~"; } let cwd = "(unknown)"; if (state && state.cwd) { cwd = state.cwd; } if (remote && remote.remotevars.home) { cwd = replaceHomePath(cwd, remote.remotevars.home) } return cwd; } function getLineDateStr(ts : number) : string { let lineDate = new Date(ts); let nowDate = new Date(); if (nowDate.getFullYear() != lineDate.getFullYear()) { return dayjs(lineDate).format("ddd L LTS"); } else if (nowDate.getMonth() != lineDate.getMonth() || nowDate.getDate() != lineDate.getDate()) { let yesterdayDate = (new Date()); yesterdayDate.setDate(yesterdayDate.getDate()-1); if (yesterdayDate.getMonth() == lineDate.getMonth() && yesterdayDate.getDate() == lineDate.getDate()) { return "Yesterday " + dayjs(lineDate).format("LTS");; } return dayjs(lineDate).format("ddd L LTS"); } else { return dayjs(ts).format("LTS"); } } @mobxReact.observer class LineText extends React.Component<{sw : ScreenWindow, line : LineType}, {}> { render() { let line = this.props.line; let formattedTime = getLineDateStr(line.ts); return (
S
{line.userid}
{formattedTime}
{line.text}
); } } @mobxReact.observer class Prompt extends React.Component<{rptr : RemotePtrType, rstate : RemoteStateType}, {}> { render() { let remote : RemoteType = null; if (this.props.rptr && !isBlank(this.props.rptr.remoteid)) { remote = GlobalModel.getRemote(this.props.rptr.remoteid); } let remoteStr = getRemoteStr(this.props.rptr); let cwd = getCwdStr(remote, this.props.rstate); let isRoot = false; if (remote && remote.remotevars) { if (remote.remotevars["sudo"] || remote.remotevars["bestuser"] == "root") { isRoot = true; } } let className = (isRoot ? "term-bright-red" : "term-bright-green"); return ( [{remoteStr}] {cwd} {isRoot ? "#" : "$"} ); } } @mobxReact.observer class LineCmd extends React.Component<{sw : ScreenWindow, line : LineType, width : number, interObs : IntersectionObserver, initVis : boolean}, {}> { termLoaded : mobx.IObservableValue = mobx.observable.box(false); lineRef : React.RefObject = React.createRef(); iobsVal : InterObsValue = null; autorunDisposer : () => void = null; constructor(props) { super(props); let line = props.line; let ival : InterObsValue = { sessionid: line.sessionid, windowid: line.windowid, lineid: line.lineid, cmdid: line.cmdid, visible: mobx.observable.box(this.props.initVis), }; this.iobsVal = ival; } visibilityChanged(vis : boolean) : void { let curVis = this.termLoaded.get(); if (vis && !curVis) { this.loadTerminal(); } else if (!vis && curVis) { this.unloadTerminal(); } } loadTerminal() : void { let {sw, line} = this.props; let model = GlobalModel; let cmd = model.getCmd(line); if (cmd == null) { return; } let termId = "term-" + getLineId(line); let termElem = document.getElementById(termId); if (termElem == null) { console.log("cannot load terminal, no term elem found", termId); return; } sw.connectElem(termElem, cmd, this.props.width); mobx.action(() => this.termLoaded.set(true))(); } unloadTerminal() : void { let {sw, line} = this.props; let model = GlobalModel; let cmd = model.getCmd(line); if (cmd == null) { return; } let termId = "term-" + getLineId(line); sw.disconnectElem(line.cmdid); mobx.action(() => this.termLoaded.set(false))(); let termElem = document.getElementById(termId); if (termElem != null) { termElem.replaceChildren(); } } componentDidMount() { let {line} = this.props; if (this.lineRef.current == null || this.props.interObs == null) { console.log("LineCmd lineRef current is null or interObs is null", line, this.lineRef.current, this.props.interObs); } else { globalLineWeakMap.set(this.lineRef.current, this.iobsVal); this.props.interObs.observe(this.lineRef.current); this.autorunDisposer = mobx.autorun(() => { let vis = this.iobsVal.visible.get(); this.visibilityChanged(vis); }); } } componentWillUnmount() { let {sw, line} = this.props; let model = GlobalModel; if (this.termLoaded.get()) { sw.disconnectElem(line.cmdid); } if (this.lineRef.current != null && this.props.interObs != null) { this.props.interObs.unobserve(this.lineRef.current); } if (this.autorunDisposer != null) { this.autorunDisposer(); } } scrollIntoView() { let lineElem = document.getElementById("line-" + getLineId(this.props.line)); lineElem.scrollIntoView({block: "end"}); } @boundMethod doRefresh() { let {sw, line} = this.props; let model = GlobalModel; let termWrap = sw.getTermWrap(line.cmdid); if (termWrap != null) { termWrap.reloadTerminal(500); } } renderCmdText(cmd : Cmd, remote : RemoteType) : any { if (cmd == null) { return (
(cmd not found)
); } let remoteStr = getRemoteStr(cmd.remote); let cwd = getCwdStr(remote, cmd.getRemoteState()); return (
{cmd.getSingleLineCmdText()}
); } @boundMethod clickTermBlock(e : any) { let {sw, line} = this.props; let model = GlobalModel; let termWrap = sw.getTermWrap(line.cmdid); if (termWrap != null) { termWrap.terminal.focus(); } } render() { let {sw, line, width} = this.props; let model = GlobalModel; let lineid = line.lineid; let formattedTime = getLineDateStr(line.ts); let cmd = model.getCmd(line); if (cmd == null) { return (
[cmd not found '{line.cmdid}']
); } let termLoaded = this.termLoaded.get(); let usedRows = sw.getUsedRows(cmd, width); let termHeight = termHeightFromRows(usedRows); let remote = model.getRemote(cmd.remoteId); let status = cmd.getStatus(); let termOpts = cmd.getTermOpts(); let isFocused = sw.getIsFocused(line.cmdid); let lineNumStr = (line.linenumtemp ? "~" : "") + String(line.linenum); return (
{lineNumStr}
{line.userid}
{formattedTime}
{line.cmdid} ({termOpts.rows}x{termOpts.cols})
{this.renderCmdText(cmd, remote)}
(loading)
); } } @mobxReact.observer class Line extends React.Component<{sw : ScreenWindow, line : LineType, width : number, interObs : IntersectionObserver, initVis : boolean}, {}> { render() { let line = this.props.line; if (line.linetype == "text") { return ; } if (line.linetype == "cmd") { return ; } return
[invalid line type '{line.linetype}']
; } } @mobxReact.observer class TextAreaInput extends React.Component<{}, {}> { lastTab : boolean = false; lastHistoryUpDown : boolean = false; lastTabCurLine : mobx.IObservableValue = mobx.observable.box(null); componentDidMount() { let input = document.getElementById("main-cmd-input"); if (input != null) { input.focus(); } } isModKeyPress(e : any) { return e.code.match(/^(Control|Meta|Alt|Shift)(Left|Right)$/); } getLinePos(elem : any) : {numLines : number, linePos : number} { let numLines = elem.value.split("\n").length; let linePos = elem.value.substr(0, elem.selectionStart).split("\n").length; return {numLines, linePos}; } @mobx.action @boundMethod onKeyDown(e : any) { mobx.action(() => { if (this.isModKeyPress(e)) { return; } let model = GlobalModel; let inputModel = model.inputModel; let win = model.getActiveWindow(); let ctrlMod = e.getModifierState("Control") || e.getModifierState("Meta") || e.getModifierState("Shift"); let curLine = inputModel.getCurLine(); let lastTab = this.lastTab; this.lastTab = (e.code == "Tab"); let lastHist = this.lastHistoryUpDown; this.lastHistoryUpDown = false; if (e.code == "Tab") { e.preventDefault(); if (lastTab) { GlobalModel.submitCommand("compgen", null, [curLine], {"comppos": String(curLine.length), "compshow": "1", "nohist": "1"}, true); return; } else { GlobalModel.submitCommand("compgen", null, [curLine], {"comppos": String(curLine.length), "nohist": "1"}, true); return; } } if (e.code == "Enter") { e.preventDefault(); if (!ctrlMod) { setTimeout(() => GlobalModel.inputModel.uiSubmitCommand(), 0); return; } e.target.setRangeText("\n", e.target.selectionStart, e.target.selectionEnd, "end"); GlobalModel.inputModel.setCurLine(e.target.value); return; } if (e.code == "Escape") { e.preventDefault(); GlobalModel.inputModel.toggleInfoMsg(); return; } if (e.code == "KeyC" && e.getModifierState("Control")) { e.preventDefault(); inputModel.resetInput(); return; } if (e.code == "KeyR" && e.getModifierState("Control")) { e.preventDefault(); inputModel.openHistory(); return; } if (e.code == "ArrowUp" || e.code == "ArrowDown") { if (!inputModel.isHistoryLoaded()) { if (e.code == "ArrowUp") { this.lastHistoryUpDown = true; inputModel.loadHistory(false, 1, "window"); } return; } // invisible history movement let linePos = this.getLinePos(e.target); if (e.code == "ArrowUp") { if (!lastHist && linePos.linePos > 1) { // regular arrow return; } e.preventDefault(); inputModel.moveHistorySelection(1); this.lastHistoryUpDown = true; return; } if (e.code == "ArrowDown") { if (!lastHist && linePos.linePos < linePos.numLines) { // regular arrow return; } e.preventDefault(); inputModel.moveHistorySelection(-1); this.lastHistoryUpDown = true; return; } } if (e.code == "PageUp" || e.code == "PageDown") { e.preventDefault(); let infoScroll = inputModel.hasScrollingInfoMsg(); if (infoScroll) { let div = document.querySelector(".cmd-input-info"); let amt = pageSize(div); scrollDiv(div, (e.code == "PageUp" ? -amt : amt)); } else { let win = GlobalModel.getActiveWindow(); if (win == null) { return; } let id = windowLinesDOMId(win.windowId); let div = document.getElementById(id); let amt = pageSize(div); scrollDiv(div, (e.code == "PageUp" ? -amt : amt)); } } // console.log(e.code, e.keyCode, e.key, event.which, ctrlMod, e); })(); } @boundMethod onChange(e : any) { mobx.action(() => { GlobalModel.inputModel.setCurLine(e.target.value); })(); } @boundMethod onHistoryKeyDown(e : any) { let inputModel = GlobalModel.inputModel; if (e.code == "Escape") { e.preventDefault(); inputModel.resetHistory(); return; } if (e.code == "Enter") { e.preventDefault(); inputModel.grabSelectedHistoryItem(); return; } if (e.code == "KeyC" && e.getModifierState("Control")) { e.preventDefault(); inputModel.resetInput(); return; } if (e.code == "KeyM" && (e.getModifierState("Meta") || e.getModifierState("Control"))) { e.preventDefault(); let opts = mobx.toJS(inputModel.historyQueryOpts.get()); opts.includeMeta = !opts.includeMeta; inputModel.setHistoryQueryOpts(opts); return; } if (e.code == "KeyR" && ((e.getModifierState("Meta") || e.getModifierState("Control")) && !e.getModifierState("Shift"))) { console.log("meta-r"); e.preventDefault(); let opts = mobx.toJS(inputModel.historyQueryOpts.get()); if (opts.limitRemote) { opts.limitRemote = false; opts.limitRemoteInstance = false; } else { opts.limitRemote = true; opts.limitRemoteInstance = true; } inputModel.setHistoryQueryOpts(opts); return; } if (e.code == "KeyS" && (e.getModifierState("Meta") || e.getModifierState("Control"))) { e.preventDefault(); let opts = mobx.toJS(inputModel.historyQueryOpts.get()); let htype = opts.queryType; if (htype == "window") { htype = "session"; } else if (htype == "session") { htype = "global"; } else { htype = "window"; } inputModel.setHistoryType(htype); return; } if (e.code == "Tab") { e.preventDefault(); return; } if (e.code == "ArrowUp" || e.code == "ArrowDown") { e.preventDefault(); inputModel.moveHistorySelection(e.code == "ArrowUp" ? 1 : -1); return; } if (e.code == "PageUp" || e.code == "PageDown") { e.preventDefault(); inputModel.moveHistorySelection(e.code == "PageUp" ? 10 : -10); return; } } @boundMethod handleHistoryInput(e : any) { let inputModel = GlobalModel.inputModel; mobx.action(() => { let opts = mobx.toJS(inputModel.historyQueryOpts.get()); opts.queryStr = e.target.value; inputModel.setHistoryQueryOpts(opts); })(); } @boundMethod handleMainFocus(e : any) { let inputModel = GlobalModel.inputModel; if (inputModel.historyShow.get()) { e.preventDefault(); inputModel.giveFocus(); } else { inputModel.setPhysicalInputFocused(true); } } @boundMethod handleMainBlur(e : any) { GlobalModel.inputModel.setPhysicalInputFocused(false); } @boundMethod handleHistoryFocus(e : any) { let inputModel = GlobalModel.inputModel; if (!inputModel.historyShow.get()) { e.preventDefault(); inputModel.giveFocus(); } else { inputModel.setPhysicalInputFocused(true); } } @boundMethod handleHistoryBlur(e : any) { GlobalModel.inputModel.setPhysicalInputFocused(false); } render() { let model = GlobalModel; let inputModel = model.inputModel; let curLine = inputModel.getCurLine(); let numLines = curLine.split("\n").length; let displayLines = numLines; if (displayLines > 5) { displayLines = 5; } let disabled = inputModel.historyShow.get(); if (disabled) { displayLines = 1; } return (
); } } @mobxReact.observer class InfoRemoteShowAll extends React.Component<{}, {}> { clickRow(remoteId : string) : void { GlobalCommandRunner.showRemote(remoteId); } render() { let inputModel = GlobalModel.inputModel; let infoMsg = inputModel.infoMsg.get(); if (infoMsg == null || !infoMsg.remoteshowall) { return null; } let remotes = GlobalModel.remotes ?? []; let remote : RemoteType = null; let idx : number = 0; remotes = sortAndFilterRemotes(remotes); return (
show all remotes
this.clickRow(remote.remoteid)}>
status id alias user@host connectmode
{remote.status}
{remote.remoteid.substr(0, 8)} {isBlank(remote.remotealias) ? "-" : remote.remotealias} {remote.remotecanonicalname} {remote.connectmode}
); } } @mobxReact.observer class InfoRemoteShow extends React.Component<{}, {}> { getRemoteTypeStr(remote : RemoteType) : string { let mshellStr = ""; if (!isBlank(remote.mshellversion)) { mshellStr = "mshell=" + remote.mshellversion; } if (!isBlank(remote.uname)) { if (mshellStr != "") { mshellStr += " "; } mshellStr += "uname=\"" + remote.uname + "\""; } if (mshellStr == "") { return remote.remotetype; } return remote.remotetype + " (" + mshellStr + ")"; } @boundMethod connectRemote(remoteId : string) { GlobalCommandRunner.connectRemote(remoteId); } @boundMethod disconnectRemote(remoteId : string) { GlobalCommandRunner.disconnectRemote(remoteId); } @boundMethod installRemote(remoteId : string) { GlobalCommandRunner.installRemote(remoteId); } @boundMethod cancelInstall(remoteId : string) { GlobalCommandRunner.installCancelRemote(remoteId); } @boundMethod editRemote(remoteId : string) { GlobalCommandRunner.openEditRemote(remoteId); } renderConnectButton(remote : RemoteType) : any { if (remote.status == "connected" || remote.status == "connecting") { return
this.disconnectRemote(remote.remoteid)} className="text-button disconnect-button">[disconnect remote]
} else { return
this.connectRemote(remote.remoteid)} className="text-button connect-button">[connect remote]
} } renderEditButton(remote : RemoteType) : any { return
this.editRemote(remote.remoteid)} className="text-button">[edit remote]
} renderInstallButton(remote : RemoteType) : any { if (remote.status == "connected" || remote.status == "connecting") { return "(must disconnect to install)"; } if (remote.installstatus == "disconnected" || remote.installstatus == "error") { return
this.installRemote(remote.remoteid)} className="text-button connect-button">[run install]
} if (remote.installstatus == "connecting") { return
this.cancelInstall(remote.remoteid)} className="text-button disconnect-button">[cancel install]
} return null; } renderInstallStatus(remote : RemoteType) : any { let statusStr : string = null; if (remote.installstatus == "disconnected") { if (remote.needsmshellupgrade) { statusStr = "needs upgrade" } } else { statusStr = remote.installstatus; } if (statusStr == null) { return null; } let installButton = this.renderInstallButton(remote); return (
install-status
{statusStr} | {this.renderInstallButton(remote)}
); } @boundMethod clickTermBlock(e : any) { let inputModel = GlobalModel.inputModel; if (inputModel.remoteTermWrap != null) { inputModel.remoteTermWrap.terminal.focus(); } } getCanonicalNameDisplayWithPort(remote : RemoteType) { if (isBlank(remote.remotevars.port) || remote.remotevars.port == "22") { return remote.remotecanonicalname; } return remote.remotecanonicalname + " (port " + remote.remotevars.port + ")"; } render() { let inputModel = GlobalModel.inputModel; let infoMsg = inputModel.infoMsg.get(); let ptyRemoteId = (infoMsg == null ? null : infoMsg.ptyremoteid); let isTermFocused = (inputModel.remoteTermWrap == null ? false : inputModel.remoteTermWrap.isFocused.get()); let remote : RemoteType; if (ptyRemoteId != null) { remote = GlobalModel.getRemote(ptyRemoteId); } if (ptyRemoteId == null || remote == null) { return ( <>
); } return ( <>
show remote [{remote.remotecanonicalname}]
remoteid
{remote.remoteid} | {this.renderEditButton(remote)}
type
{this.getRemoteTypeStr(remote)}
canonicalname
{this.getCanonicalNameDisplayWithPort(remote)}
alias
{isBlank(remote.remotealias) ? "-" : remote.remotealias}
connectmode
{remote.connectmode}
status
{remote.status} | {this.renderConnectButton(remote)}
error
{remote.errorstr}
{this.renderInstallStatus(remote)}
install error
{remote.installerrorstr}
input is only allowed while status is 'connecting'
); } } @mobxReact.observer class InfoRemoteEdit extends React.Component<{}, {}> { alias : mobx.IObservableValue; hostName : mobx.IObservableValue; keyStr : mobx.IObservableValue; portStr : mobx.IObservableValue; passwordStr : mobx.IObservableValue; colorStr : mobx.IObservableValue; connectMode : mobx.IObservableValue; sudoBool : mobx.IObservableValue; autoInstallBool : mobx.IObservableValue; authMode : mobx.IObservableValue; archiveConfirm : mobx.IObservableValue = mobx.observable.box(false); constructor(props) { super(props); this.resetForm(); } getEditAuthMode(redit : RemoteEditType) : string { if (!isBlank(redit.keystr) && redit.haspassword) { return "key+pw"; } else if (!isBlank(redit.keystr)) { return "key"; } else if (redit.haspassword) { return "pw"; } else { return "none"; } } resetForm() { let redit = this.getRemoteEdit(); let remote = this.getEditingRemote(); if (redit == null) { return; } let isEditMode = !isBlank(redit.remoteid); if (isEditMode && remote == null) { return; } // not editable this.hostName = mobx.observable.box(""); this.portStr = mobx.observable.box(""); this.sudoBool = mobx.observable.box(false); // editable if (isEditMode) { this.authMode = mobx.observable.box(this.getEditAuthMode(redit)); this.alias = mobx.observable.box(remote.remotealias ?? ""); this.passwordStr = mobx.observable.box(redit.haspassword ? PasswordUnchangedSentinel : ""); this.keyStr = mobx.observable.box(redit.keystr ?? ""); this.colorStr = mobx.observable.box(remote.remotevars["color"] ?? ""); this.connectMode = mobx.observable.box(remote.connectmode); this.autoInstallBool = mobx.observable.box(remote.autoinstall); } else { this.authMode = mobx.observable.box("none"); this.alias = mobx.observable.box(""); this.passwordStr = mobx.observable.box(""); this.keyStr = mobx.observable.box(""); this.colorStr = mobx.observable.box(""); this.connectMode = mobx.observable.box("startup"); this.autoInstallBool = mobx.observable.box(true); } } canResetPw() : boolean { let redit = this.getRemoteEdit(); if (redit == null) { return false; } return redit.haspassword && this.passwordStr.get() != PasswordUnchangedSentinel; } @boundMethod resetPw() : void { mobx.action(() => { this.passwordStr.set(PasswordUnchangedSentinel); })(); } @boundMethod updateArchiveConfirm(e : any) : void { mobx.action(() => { this.archiveConfirm.set(e.target.checked); })(); } @boundMethod doArchiveRemote(e : any) { e.preventDefault(); if (!this.archiveConfirm.get()) { return; } let redit = this.getRemoteEdit(); if (redit == null || isBlank(redit.remoteid)) { return; } GlobalCommandRunner.archiveRemote(redit.remoteid); } @boundMethod doSubmitRemote() { let redit = this.getRemoteEdit(); let isEditing = !isBlank(redit.remoteid); let cname = this.hostName.get(); let kwargs : Record = {}; let authMode = this.authMode.get(); if (!isEditing) { if (this.sudoBool.get()) { kwargs["sudo"] = "1"; } } kwargs["alias"] = this.alias.get(); kwargs["color"] = this.colorStr.get(); if (authMode == "key" || authMode == "key+pw") { kwargs["key"] = this.keyStr.get(); } else { kwargs["key"] = ""; } if (authMode == "pw" || authMode == "key+pw") { kwargs["password"] = this.passwordStr.get(); } else { kwargs["password"] = "" } kwargs["connectmode"] = this.connectMode.get(); kwargs["autoinstall"] = (this.autoInstallBool.get() ? "1" : "0"); kwargs["visual"] = "1"; kwargs["submit"] = "1"; console.log("submit remote", (isEditing ? redit.remoteid : cname), kwargs); mobx.action(() => { if (isEditing) { GlobalCommandRunner.editRemote(redit.remoteid, kwargs); } else { GlobalCommandRunner.createRemote(cname, kwargs); } })(); } @boundMethod doCancel() { mobx.action(() => { this.resetForm(); GlobalModel.inputModel.clearInfoMsg(true); })(); } @boundMethod keyDownCreateRemote(e : any) { if (e.code == "Enter") { this.doSubmitRemote(); } } @boundMethod keyDownCancel(e : any) { if (e.code == "Enter") { this.doCancel(); } } @boundMethod onChangeAlias(e : any) { mobx.action(() => { this.alias.set(e.target.value); })(); } @boundMethod onChangeHostName(e : any) { mobx.action(() => { this.hostName.set(e.target.value); })(); } @boundMethod onChangeKeyStr(e : any) { mobx.action(() => { this.keyStr.set(e.target.value); })(); } @boundMethod onChangePortStr(e : any) { mobx.action(() => { this.portStr.set(e.target.value); })(); } @boundMethod onChangePasswordStr(e : any) { mobx.action(() => { this.passwordStr.set(e.target.value); })(); } @boundMethod onFocusPasswordStr(e : any) { if (this.passwordStr.get() == PasswordUnchangedSentinel) { e.target.select(); } } @boundMethod onChangeColorStr(e : any) { mobx.action(() => { this.colorStr.set(e.target.value); })(); } @boundMethod onChangeConnectMode(e : any) { mobx.action(() => { this.connectMode.set(e.target.value); })(); } @boundMethod onChangeAuthMode(e : any) { mobx.action(() => { this.authMode.set(e.target.value); })(); } @boundMethod onChangeSudo(e : any) { mobx.action(() => { this.sudoBool.set(e.target.checked); })(); } @boundMethod onChangeAutoInstall(e : any) { mobx.action(() => { this.autoInstallBool.set(e.target.checked); })(); } getRemoteEdit() : RemoteEditType { let inputModel = GlobalModel.inputModel; let infoMsg = inputModel.infoMsg.get(); if (infoMsg == null) { return null; } return infoMsg.remoteedit; } getEditingRemote() : RemoteType { let inputModel = GlobalModel.inputModel; let infoMsg = inputModel.infoMsg.get(); if (infoMsg == null) { return null; } let redit = infoMsg.remoteedit; if (redit == null || isBlank(redit.remoteid)) { return null; } let remote = GlobalModel.getRemote(redit.remoteid); return remote; } remoteCName() : string { let redit = this.getRemoteEdit(); if (isBlank(redit.remoteid)) { // new-mode let hostName = this.hostName.get(); if (hostName == "") { return "[no host]"; } if (hostName.indexOf("@") == -1) { hostName = "[no user]@" + hostName; } if (!hostName.startsWith("sudo@") && this.sudoBool.get()) { return "sudo@" + hostName; } return hostName; } else { let remote = this.getEditingRemote(); if (remote == null) { return "[no remote]"; } return remote.remotecanonicalname; } } render() { let inputModel = GlobalModel.inputModel; let infoMsg = inputModel.infoMsg.get(); if (infoMsg == null || !infoMsg.remoteedit) { return null; } let redit = infoMsg.remoteedit; if (!redit.remoteedit) { return null; } let isEditMode = !isBlank(redit.remoteid); let remote = this.getEditingRemote(); if (isEditMode && remote == null) { return (
cannot edit, remote {redit.remoteid} not found
); } let colorStr : string = null; return (
add new remote '{this.remoteCName()}' edit remote '{this.remoteCName()}'
type
ssh
user@host
port
user@host
{remote.remotecanonicalname}  (port {remote.remotevars.port})
alias
authmode
ssh keyfile
ssh password
sudo
connectmode
autoinstall
color
{redit.errorstr}
{redit.infostr}
); } } @mobxReact.observer class InfoMsg extends React.Component<{}, {}> { getAfterSlash(s : string) : string { if (s.startsWith("^/")) { return s.substr(1); } let slashIdx = s.lastIndexOf("/"); if (slashIdx == s.length-1) { slashIdx = s.lastIndexOf("/", slashIdx-1); } if (slashIdx == -1) { return s; } return s.substr(slashIdx+1); } render() { let model = GlobalModel; let inputModel = model.inputModel; let infoMsg = inputModel.infoMsg.get(); let infoShow = inputModel.infoShow.get(); let line : string = null; let istr : string = null; let idx : number = 0; let titleStr = null; let remoteEditKey = "inforemoteedit"; if (infoMsg != null) { titleStr = infoMsg.infotitle; if (infoMsg.remoteedit != null) { remoteEditKey += (infoMsg.remoteedit.remoteid == null ? "-new" : "-" + infoMsg.remoteedit.remoteid); } } return (
{titleStr}
{infoMsg.infomsg}
{line == "" ? " " : line}
0}>
{this.getAfterSlash(istr)}
...
[error] {infoMsg.infoerror}
); } } @mobxReact.observer class HistoryInfo extends React.Component<{}, {}> { lastClickHNum : string = null; lastClickTs : number = 0; containingText : mobx.IObservableValue = mobx.observable.box(""); componentDidMount() { let inputModel = GlobalModel.inputModel; let hitem = inputModel.getHistorySelectedItem(); if (hitem == null) { hitem = inputModel.getFirstHistoryItem(); } if (hitem != null) { inputModel.scrollHistoryItemIntoView(hitem.historynum); } } @boundMethod handleItemClick(hitem : HistoryItem) { let inputModel = GlobalModel.inputModel; let selItem = inputModel.getHistorySelectedItem(); if (this.lastClickHNum == hitem.historynum && selItem != null && selItem.historynum == hitem.historynum) { inputModel.grabSelectedHistoryItem(); return; } inputModel.giveFocus(); inputModel.setHistorySelectionNum(hitem.historynum); let now = Date.now(); this.lastClickHNum = hitem.historynum; this.lastClickTs = now; setTimeout(() => { if (this.lastClickTs == now) { this.lastClickHNum = null; this.lastClickTs = 0; } }, 3000); } renderRemote(hitem : HistoryItem) : any { if (hitem.remote == null || isBlank(hitem.remote.remoteid)) { return sprintf("%-15s ", "") } let r = GlobalModel.getRemote(hitem.remote.remoteid); if (r == null) { return sprintf("%-15s ", "???") } let rname = ""; if (!isBlank(r.remotealias)) { rname = r.remotealias; } else { rname = r.remotecanonicalname; } if (!isBlank(hitem.remote.name)) { rname = rname + ":" + hitem.remote.name; } let rtn = sprintf("%-15s ", "[" + rname + "]") return rtn; } renderHItem(hitem : HistoryItem, opts : HistoryQueryOpts, isSelected : boolean) : any { let lines = hitem.cmdstr.split("\n"); let line : string = ""; let idx = 0; let limitRemote = opts.limitRemote; let sessionStr = ""; if (opts.queryType == "global") { if (!isBlank(hitem.sessionid)) { let s = GlobalModel.getSessionById(hitem.sessionid); if (s != null) { sessionStr = s.name.get(); if (sessionStr.indexOf(" ") != -1) { sessionStr = "[" + sessionStr + "]"; } sessionStr = sprintf("#%-15s ", sessionStr); } } } return (
this.handleItemClick(hitem)}>
{(isSelected ? "*" : " ")}{sprintf("%5s", hitem.historynum)} {opts.queryType == "global" ? sessionStr : ""}{!limitRemote ? this.renderRemote(hitem) : ""} {lines[0]}
{line}
); } @boundMethod handleClose() { GlobalModel.inputModel.toggleInfoMsg(); } render() { let inputModel = GlobalModel.inputModel; let idx : number = 0; let selItem = inputModel.getHistorySelectedItem(); let hitems = inputModel.getFilteredHistoryItems(); hitems = hitems.slice().reverse(); let hitem : HistoryItem = null; let opts = inputModel.historyQueryOpts.get(); return (
history
[for {opts.queryType} ⌘S]
[containing '{opts.queryStr}']
[{opts.limitRemote ? "this" : "any"} remote ⌘R]
[{opts.includeMeta ? "" : "no "}metacmds ⌘M]
(ESC)
[no history] 0}> {this.renderHItem(hitem, opts, (hitem == selItem))}
); } } @mobxReact.observer class CmdInput extends React.Component<{}, {}> { @boundMethod onInfoToggle() : void { GlobalModel.inputModel.toggleInfoMsg(); return; } render() { let model = GlobalModel; let inputModel = model.inputModel; let win = GlobalModel.getActiveWindow(); let ri : RemoteInstanceType = null; let rptr : RemotePtrType = null; if (win != null) { ri = win.getCurRemoteInstance(); rptr = win.curRemote.get(); } let remote : RemoteType = null; let remoteState : RemoteStateType = null; if (ri != null) { remote = GlobalModel.getRemote(ri.remoteid); remoteState = ri.state; } let remoteStr = getRemoteStr(rptr); let cwdStr = getCwdStr(remote, remoteState); let infoShow = inputModel.infoShow.get(); let historyShow = !infoShow && inputModel.historyShow.get(); let infoMsg = inputModel.infoMsg.get(); let hasInfo = (infoMsg != null); let remoteShow = (infoMsg != null && !isBlank(infoMsg.ptyremoteid)); let focusVal = inputModel.physicalInputFocused.get(); return (
{remoteStr}
); } } // sw is not null @mobxReact.observer class ScreenWindowView extends React.Component<{sw : ScreenWindow}, {}> { mutObs : any; rszObs : any; interObs : IntersectionObserver; randomId : string; lastHeight : number = null; width : mobx.IObservableValue = mobx.observable.box(0); setWidth_debounced : (width : number) => void; constructor(props : any) { super(props); this.setWidth_debounced = debounce(1000, this.setWidth.bind(this)); } setWidth(width : number) : void { mobx.action(() => { this.width.set(width); let {sw} = this.props; let cols = widthToCols(width); if (sw == null || cols == 0) { return; } sw.colsCallback(cols); })(); } scrollToBottom(reason : string) { let elem = document.getElementById(this.getLinesDOMId()); if (elem == null) { return; } let oldST = elem.scrollTop; elem.scrollTop = elem.scrollHeight; // console.log("scroll-elem", oldST, elem.scrollHeight, elem.scrollTop, elem.scrollLeft, elem); } @boundMethod scrollHandler(event : any) { let {sw} = this.props; let target = event.target; let atBottom = (target.scrollTop + 30 > (target.scrollHeight - target.offsetHeight)); if (sw.shouldFollow.get() != atBottom) { mobx.action(() => sw.shouldFollow.set(atBottom))(); } // console.log("scroll-handler (sw)>", atBottom, target.scrollTop, target.scrollHeight, event); } componentDidMount() { let elem = document.getElementById(this.getLinesDOMId()); if (elem != null) { this.mutObs = new MutationObserver(this.handleDomMutation.bind(this)); this.mutObs.observe(elem, {childList: true}); elem.addEventListener("termresize", this.handleTermResize); let {sw} = this.props; if (sw.shouldFollow.get()) { setTimeout(() => this.scrollToBottom("mount"), 0); } this.interObs = new IntersectionObserver(interObsCallback, { root: elem, rootMargin: "800px", threshold: 0.0, }); } let wvElem = document.getElementById(this.getWindowViewDOMId()); if (wvElem != null) { let width = wvElem.offsetWidth; this.setWidth(width); this.rszObs = new ResizeObserver(this.handleResize.bind(this)); this.rszObs.observe(wvElem); } } componentWillUnmount() { if (this.mutObs) { this.mutObs.disconnect(); } if (this.rszObs) { this.rszObs.disconnect(); } if (this.interObs) { this.interObs.disconnect(); } } handleResize(entries : any) { if (entries.length == 0) { return; } let entry = entries[0]; let width = entry.target.offsetWidth; this.setWidth_debounced(width); if (this.lastHeight == null) { this.lastHeight = entry.target.offsetHeight; return; } if (this.lastHeight != entry.target.offsetHeight) { this.lastHeight = entry.target.offsetHeight; this.doConditionalScrollToBottom("resize-height"); } } handleDomMutation(mutations, mutObs) { this.doConditionalScrollToBottom("mut"); } doConditionalScrollToBottom(reason : string) { let {sw} = this.props; if (sw.shouldFollow.get()) { setTimeout(() => this.scrollToBottom(reason), 0); } } getWindow() : Window { let {sw} = this.props; let win = GlobalModel.getWindowById(sw.sessionId, sw.windowId); if (win == null) { win = GlobalModel.loadWindow(sw.sessionId, sw.windowId); } return win; } getLinesDOMId() { return windowLinesDOMId(this.getWindowId()); } @boundMethod handleTermResize(e : any) { let {sw} = this.props; if (sw.shouldFollow.get()) { setTimeout(() => this.scrollToBottom("termresize"), 0); } } getWindowViewStyle() : any { // return {width: "100%", height: "100%"}; return {position: "absolute", width: "100%", height: "100%", overflowX: "hidden"}; } getWindowId() : string { let {sw} = this.props; return sw.windowId; } getWindowViewDOMId() { return sprintf("window-view-%s", this.getWindowId()); } renderError(message : string) { let {sw} = this.props; return (
{sw.name.get()}{sw.shouldFollow.get() ? "*" : ""}
{message}
); } render() { let {sw} = this.props; let win = this.getWindow(); if (win == null || !win.loaded.get()) { return this.renderError("(loading)"); } if (win.loadError.get() != null) { return this.renderError(sprintf("(%s)", win.loadError.get())); } if (this.width.get() == 0) { return this.renderError(""); } let idx = 0; let line : LineType = null; let screen = GlobalModel.getScreenById(sw.sessionId, sw.screenId); let session = GlobalModel.getSessionById(sw.sessionId); let linesStyle : any = {}; if (win.lines.length == 0) { linesStyle.display = "none"; } let isActive = sw.isActive(); return (
{sw.name.get()}  
win.lines.length-1-7}/>
[session="{session.name.get()}" screen="{screen.name.get()}" window="{sw.name.get()}"]
); } } @mobxReact.observer class ScreenView extends React.Component<{screen : Screen}, {}> { render() { let {screen} = this.props; let sw : ScreenWindow = null; if (screen != null) { sw = screen.getActiveSW(); } if (screen == null || sw == null) { return (
(no screen or window)
); } return (
); } } @mobxReact.observer class ScreenTabs extends React.Component<{session : Session}, {}> { @boundMethod handleNewScreen() { let {session} = this.props; GlobalCommandRunner.createNewScreen(); } @boundMethod handleSwitchScreen(screenId : string) { let {session} = this.props; if (session == null) { return; } if (session.activeScreenId.get() == screenId) { return; } let screen = session.getScreenById(screenId); if (screen == null) { return; } GlobalCommandRunner.switchScreen(screenId); } handleContextMenu(e : any, screenId : string) : void { e.preventDefault(); console.log("handle context menu!", screenId); let model = GlobalModel; model.contextScreen(e, screenId); } render() { let {session} = this.props; if (session == null) { return null; } let screen : Screen = null; let index = 0; return (
this.handleSwitchScreen(screen.screenId)} onContextMenu={(event) => this.handleContextMenu(event, screen.screenId)}> {screen.name.get()}
⌘{index+1}
+
); } } @mobxReact.observer class SessionView extends React.Component<{}, {}> { render() { let model = GlobalModel; let session = model.getActiveSession(); if (session == null) { return
(no active session)
; } let activeScreen = session.getActiveScreen(); return (
); } } function getConnVal(r : RemoteType) : number { if (r.status == "connected") { return 1; } if (r.status == "init" || r.status == "disconnected") { return 2; } if (r.status == "error") { return 3; } return 4; } @mobxReact.observer class RemoteStatusLight extends React.Component<{remote : RemoteType}, {}> { render() { let remote = this.props.remote; let status = "error"; let wfp = false; if (remote != null) { status = remote.status; wfp = remote.waitingforpassword; } let icon = "fa-circle" if (status == "connecting") { icon = (wfp ? "fa-key" : "fa-refresh"); } return ( ); } } @mobxReact.observer class MainSideBar extends React.Component<{}, {}> { collapsed : mobx.IObservableValue = mobx.observable.box(false); @boundMethod toggleCollapsed() { mobx.action(() => { this.collapsed.set(!this.collapsed.get()); })(); } handleSessionClick(sessionId : string) { GlobalCommandRunner.switchSession(sessionId); } handleNewSession() { GlobalCommandRunner.createNewSession(); } clickRemotes() { GlobalCommandRunner.showAllRemotes(); } remoteDisplayName(remote : RemoteType) : any { if (!isBlank(remote.remotealias)) { return ( <> {remote.remotealias} {remote.remotecanonicalname} ); } return ({remote.remotecanonicalname}); } clickRemote(remote : RemoteType) { GlobalCommandRunner.showRemote(remote.remoteid); } @boundMethod handleAddRemote() : void { GlobalCommandRunner.openCreateRemote(); } render() { let model = GlobalModel; let activeSessionId = model.activeSessionId.get(); let activeWindow = model.getActiveWindow(); let activeRemoteId : string = null; if (activeWindow != null) { let rptr = activeWindow.curRemote.get(); if (rptr != null && !isBlank(rptr.remoteid)) { activeRemoteId = rptr.remoteid; } } let session : Session = null; let remotes = model.remotes ?? []; let remote : RemoteType = null; let idx : number = 0; remotes = sortAndFilterRemotes(remotes); return ( ); } } function sortAndFilterRemotes(origRemotes : RemoteType[]) : RemoteType[] { let remotes = origRemotes.filter((r) => !r.archived); remotes.sort((a, b) => { let connValA = getConnVal(a); let connValB = getConnVal(b); if (connValA != connValB) { return connValA - connValB; } return a.remoteidx - b.remoteidx; }); return remotes; } @mobxReact.observer class AddRemoteModal extends React.Component<{}, {}> { @boundMethod handleModalClose() : void { mobx.action(() => { GlobalModel.addRemoteModalOpen.set(false); })(); } render() { return (

Add Remote

hello
); } } @mobxReact.observer class RemoteModal extends React.Component<{}, {}> { @boundMethod handleModalClose() : void { mobx.action(() => { GlobalModel.remotesModalOpen.set(false); })(); } @boundMethod handleAddRemote() : void { mobx.action(() => { GlobalModel.addRemoteModalOpen.set(true); })(); } render() { let model = GlobalModel; let remotes = sortAndFilterRemotes(model.remotes); let remote : RemoteType = null; return (

Remotes

Status Alias User@Host Connect
{remote.remotealias} - {remote.remotecanonicalname} {remote.connectmode}
); } } @mobxReact.observer class Main extends React.Component<{}, {}> { constructor(props : any) { super(props); } render() { return (

ScriptHaus

); } } export {Main};