2022-06-08 02:25:35 +02:00
|
|
|
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";
|
2022-07-14 08:11:45 +02:00
|
|
|
import {v4 as uuidv4} from "uuid";
|
2022-07-05 07:37:45 +02:00
|
|
|
import dayjs from 'dayjs'
|
2022-06-08 02:25:35 +02:00
|
|
|
import {If, For, When, Otherwise, Choose} from "tsx-control-statements/components";
|
|
|
|
import cn from "classnames"
|
2022-06-17 01:34:46 +02:00
|
|
|
import {TermWrap} from "./term";
|
2022-08-11 20:49:46 +02:00
|
|
|
import type {SessionDataType, LineType, CmdDataType, RemoteType, RemoteStateType, RemoteInstanceType} from "./types";
|
2022-06-18 02:54:14 +02:00
|
|
|
import localizedFormat from 'dayjs/plugin/localizedFormat';
|
2022-07-16 02:38:58 +02:00
|
|
|
import {GlobalModel, GlobalInput, Session, Cmd, Window, Screen, ScreenWindow} from "./model";
|
2022-06-18 02:54:14 +02:00
|
|
|
|
|
|
|
dayjs.extend(localizedFormat)
|
2022-06-13 20:12:39 +02:00
|
|
|
|
2022-07-12 02:55:03 +02:00
|
|
|
function getLineId(line : LineType) : string {
|
|
|
|
return sprintf("%s-%s-%s", line.sessionid, line.windowid, line.lineid);
|
|
|
|
}
|
|
|
|
|
2022-08-11 03:35:18 +02:00
|
|
|
function getRemoteStr(remote : RemoteType) : string {
|
|
|
|
if (remote == null) {
|
|
|
|
return "(no remote)";
|
|
|
|
}
|
|
|
|
if (remote.remotevars.local) {
|
|
|
|
return sprintf("%s@%s", remote.remotevars.remoteuser, "local")
|
|
|
|
}
|
|
|
|
else if (remote.remotevars.remotehost) {
|
|
|
|
return sprintf("%s@%s", remote.remotevars.remoteuser, remote.remotevars.remotehost);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
let host = remote.remotevars.host || "unknown";
|
|
|
|
if (remote.remotevars.user) {
|
|
|
|
return sprintf("%s@%s", remote.remotevars.user, host)
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return host;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2022-06-18 02:54:14 +02:00
|
|
|
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");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-08 02:25:35 +02:00
|
|
|
@mobxReact.observer
|
2022-07-13 09:44:19 +02:00
|
|
|
class LineText extends React.Component<{sw : ScreenWindow, line : LineType}, {}> {
|
2022-06-08 02:25:35 +02:00
|
|
|
render() {
|
|
|
|
let line = this.props.line;
|
2022-06-18 02:54:14 +02:00
|
|
|
let formattedTime = getLineDateStr(line.ts);
|
2022-06-08 02:25:35 +02:00
|
|
|
return (
|
|
|
|
<div className="line line-text">
|
|
|
|
<div className="avatar">
|
2022-06-17 01:34:46 +02:00
|
|
|
S
|
2022-06-08 02:25:35 +02:00
|
|
|
</div>
|
|
|
|
<div className="line-content">
|
|
|
|
<div className="meta">
|
|
|
|
<div className="user">{line.userid}</div>
|
2022-06-18 02:54:14 +02:00
|
|
|
<div className="ts">{formattedTime}</div>
|
2022-06-08 02:25:35 +02:00
|
|
|
</div>
|
|
|
|
<div className="text">
|
|
|
|
{line.text}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@mobxReact.observer
|
2022-07-14 09:54:31 +02:00
|
|
|
class LineCmd extends React.Component<{sw : ScreenWindow, line : LineType, width: number}, {}> {
|
2022-07-13 09:44:19 +02:00
|
|
|
termLoaded : mobx.IObservableValue<boolean> = mobx.observable.box(false);
|
|
|
|
|
2022-06-16 09:31:54 +02:00
|
|
|
constructor(props) {
|
|
|
|
super(props);
|
|
|
|
}
|
2022-06-08 02:25:35 +02:00
|
|
|
|
|
|
|
componentDidMount() {
|
2022-07-13 09:44:19 +02:00
|
|
|
let {sw, line} = this.props;
|
2022-07-11 23:43:18 +02:00
|
|
|
let model = GlobalModel;
|
2022-07-12 02:55:03 +02:00
|
|
|
let cmd = model.getCmd(line);
|
|
|
|
if (cmd != null) {
|
|
|
|
let termElem = document.getElementById("term-" + getLineId(line));
|
2022-07-14 09:54:31 +02:00
|
|
|
cmd.connectElem(termElem, sw.screenId, sw.windowId, this.props.width);
|
2022-07-13 09:44:19 +02:00
|
|
|
mobx.action(() => this.termLoaded.set(true))();
|
2022-07-12 02:55:03 +02:00
|
|
|
}
|
2022-06-15 01:02:20 +02:00
|
|
|
}
|
|
|
|
|
2022-07-12 07:43:58 +02:00
|
|
|
componentWillUnmount() {
|
2022-07-13 09:44:19 +02:00
|
|
|
let {sw, line} = this.props;
|
2022-07-12 07:43:58 +02:00
|
|
|
let model = GlobalModel;
|
|
|
|
let cmd = model.getCmd(line);
|
|
|
|
if (cmd != null) {
|
2022-07-13 09:44:19 +02:00
|
|
|
cmd.disconnectElem(sw.screenId, sw.windowId);
|
2022-07-12 07:43:58 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-18 02:54:14 +02:00
|
|
|
scrollIntoView() {
|
|
|
|
let lineElem = document.getElementById("line-" + getLineId(this.props.line));
|
|
|
|
lineElem.scrollIntoView({block: "end"});
|
2022-06-08 02:25:35 +02:00
|
|
|
}
|
2022-06-13 20:12:39 +02:00
|
|
|
|
|
|
|
@boundMethod
|
|
|
|
doRefresh() {
|
2022-07-13 09:44:19 +02:00
|
|
|
let {sw, line} = this.props;
|
2022-07-12 02:55:03 +02:00
|
|
|
let model = GlobalModel;
|
2022-07-13 09:44:19 +02:00
|
|
|
let cmd = model.getCmd(line);
|
2022-07-12 02:55:03 +02:00
|
|
|
if (cmd != null) {
|
2022-07-13 09:44:19 +02:00
|
|
|
let termWrap = cmd.getTermWrap(sw.screenId, sw.windowId);
|
|
|
|
if (termWrap != null) {
|
|
|
|
termWrap.reloadTerminal(500);
|
|
|
|
}
|
2022-07-12 02:55:03 +02:00
|
|
|
}
|
2022-06-15 01:02:20 +02:00
|
|
|
}
|
|
|
|
|
2022-07-11 23:43:18 +02:00
|
|
|
renderCmdText(cmd : Cmd, remote : RemoteType) : any {
|
2022-07-07 22:27:44 +02:00
|
|
|
if (cmd == null) {
|
|
|
|
return (
|
|
|
|
<div className="metapart-mono cmdtext">
|
|
|
|
<span className="term-bright-green">(cmd not found)</span>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
2022-08-11 03:35:18 +02:00
|
|
|
let promptStr = getRemoteStr(remote);
|
|
|
|
let cwd = getCwdStr(remote, cmd.getRemoteState());
|
2022-07-07 22:27:44 +02:00
|
|
|
return (
|
|
|
|
<div className="metapart-mono cmdtext">
|
2022-07-12 02:55:03 +02:00
|
|
|
<span className="term-bright-green">[{promptStr} {cwd}]</span> {cmd.getSingleLineCmdText()}
|
2022-07-07 22:27:44 +02:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
2022-06-08 02:25:35 +02:00
|
|
|
|
|
|
|
render() {
|
2022-07-13 09:44:19 +02:00
|
|
|
let {sw, line} = this.props;
|
2022-07-12 02:55:03 +02:00
|
|
|
let model = GlobalModel;
|
2022-06-08 02:25:35 +02:00
|
|
|
let lineid = line.lineid.toString();
|
2022-06-18 02:54:14 +02:00
|
|
|
let formattedTime = getLineDateStr(line.ts);
|
2022-07-12 02:55:03 +02:00
|
|
|
let cmd = model.getCmd(line);
|
|
|
|
if (cmd == null) {
|
|
|
|
return <div className="line line-invalid">[cmd not found '{line.cmdid}']</div>;
|
2022-07-07 22:27:44 +02:00
|
|
|
}
|
2022-07-13 09:44:19 +02:00
|
|
|
let termLoaded = this.termLoaded.get();
|
2022-07-13 10:03:17 +02:00
|
|
|
let cellHeightPx = 16;
|
2022-07-14 09:54:31 +02:00
|
|
|
let cellWidthPx = 8;
|
|
|
|
let termWidth = Math.max(Math.trunc((this.props.width - 20)/cellWidthPx), 10);
|
2022-07-13 09:44:19 +02:00
|
|
|
let usedRows = cmd.getUsedRows(sw.screenId, sw.windowId);
|
|
|
|
let totalHeight = cellHeightPx * usedRows;
|
2022-07-12 02:55:03 +02:00
|
|
|
let remote = model.getRemote(cmd.remoteId);
|
|
|
|
let status = cmd.getStatus();
|
|
|
|
let running = (status == "running");
|
|
|
|
let detached = (status == "detached");
|
|
|
|
let termOpts = cmd.getTermOpts();
|
2022-07-13 09:44:19 +02:00
|
|
|
let isFocused = cmd.getIsFocused(sw.screenId, sw.windowId);
|
2022-06-08 02:25:35 +02:00
|
|
|
return (
|
2022-07-14 09:54:31 +02:00
|
|
|
<div className={cn("line", "line-cmd", {"focus": isFocused})} id={"line-" + getLineId(line)}>
|
|
|
|
<div className="line-header">
|
|
|
|
<div className={cn("avatar",{"num4": lineid.length == 4}, {"num5": lineid.length >= 5}, {"running": running}, {"detached": detached})} onClick={this.doRefresh}>
|
|
|
|
{lineid}
|
2022-06-18 02:54:14 +02:00
|
|
|
</div>
|
2022-07-14 09:54:31 +02:00
|
|
|
<div className="meta-wrap">
|
|
|
|
<div className="meta">
|
|
|
|
<div className="user" style={{display: "none"}}>{line.userid}</div>
|
|
|
|
<div className="ts">{formattedTime}</div>
|
|
|
|
</div>
|
|
|
|
<div className="meta">
|
|
|
|
<div className="metapart-mono" style={{display: "none"}}>
|
|
|
|
{line.cmdid}
|
|
|
|
({termOpts.rows}x{termOpts.cols})
|
|
|
|
</div>
|
|
|
|
{this.renderCmdText(cmd, remote)}
|
2022-06-18 02:54:14 +02:00
|
|
|
</div>
|
2022-06-08 02:25:35 +02:00
|
|
|
</div>
|
|
|
|
</div>
|
2022-07-14 09:54:31 +02:00
|
|
|
<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>
|
2022-06-08 02:25:35 +02:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@mobxReact.observer
|
2022-07-14 09:54:31 +02:00
|
|
|
class Line extends React.Component<{sw : ScreenWindow, line : LineType, width : number}, {}> {
|
2022-06-08 02:25:35 +02:00
|
|
|
render() {
|
|
|
|
let line = this.props.line;
|
|
|
|
if (line.linetype == "text") {
|
|
|
|
return <LineText {...this.props}/>;
|
|
|
|
}
|
|
|
|
if (line.linetype == "cmd") {
|
|
|
|
return <LineCmd {...this.props}/>;
|
|
|
|
}
|
|
|
|
return <div className="line line-invalid">[invalid line type '{line.linetype}']</div>;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@mobxReact.observer
|
2022-07-12 02:55:03 +02:00
|
|
|
class CmdInput extends React.Component<{}, {}> {
|
2022-08-11 03:35:18 +02:00
|
|
|
lastTabCurLine : mobx.IObservableValue<string> = mobx.observable.box(null);
|
2022-06-08 02:25:35 +02:00
|
|
|
|
|
|
|
@mobx.action @boundMethod
|
|
|
|
onKeyDown(e : any) {
|
|
|
|
mobx.action(() => {
|
2022-07-11 23:43:18 +02:00
|
|
|
let model = GlobalModel;
|
2022-08-11 03:35:18 +02:00
|
|
|
let inputModel = model.inputModel;
|
2022-07-12 02:55:03 +02:00
|
|
|
let win = model.getActiveWindow();
|
2022-06-08 02:25:35 +02:00
|
|
|
let ctrlMod = e.getModifierState("Control") || e.getModifierState("Meta") || e.getModifierState("Shift");
|
2022-08-11 03:35:18 +02:00
|
|
|
let curLine = inputModel.getCurLine();
|
|
|
|
let ltCurLine = this.lastTabCurLine.get();
|
|
|
|
if (e.code == "Tab") {
|
|
|
|
e.preventDefault();
|
|
|
|
let lastTab = (ltCurLine != null && curLine == ltCurLine);
|
|
|
|
if (lastTab) {
|
2022-08-12 08:46:52 +02:00
|
|
|
GlobalModel.submitCommand("compgen", null, [curLine], {"comppos": String(curLine.length), "compshow": "1", "nohist": "1"});
|
2022-08-11 03:35:18 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
this.lastTabCurLine.set(curLine);
|
2022-08-12 08:46:52 +02:00
|
|
|
GlobalModel.submitCommand("compgen", null, [curLine], {"comppos": String(curLine.length), "nohist": "1"});
|
2022-08-11 03:35:18 +02:00
|
|
|
GlobalModel.clearInfoMsg(true);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (ltCurLine != null && curLine != ltCurLine) {
|
|
|
|
this.lastTabCurLine.set(null);
|
|
|
|
}
|
2022-06-08 02:25:35 +02:00
|
|
|
if (e.code == "Enter" && !ctrlMod) {
|
|
|
|
e.preventDefault();
|
2022-06-13 20:12:39 +02:00
|
|
|
setTimeout(() => this.doSubmitCmd(), 0);
|
2022-06-08 02:25:35 +02:00
|
|
|
return;
|
|
|
|
}
|
2022-08-11 03:35:18 +02:00
|
|
|
if (e.code == "Escape") {
|
2022-08-09 01:22:36 +02:00
|
|
|
e.preventDefault();
|
2022-08-11 03:35:18 +02:00
|
|
|
GlobalModel.toggleInfoMsg();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (e.code == "KeyC" && e.getModifierState("Control")) {
|
|
|
|
e.preventDefault();
|
|
|
|
inputModel.clearCurLine();
|
2022-08-09 01:22:36 +02:00
|
|
|
return;
|
|
|
|
}
|
2022-06-21 01:06:37 +02:00
|
|
|
if (e.code == "ArrowUp") {
|
|
|
|
e.preventDefault();
|
2022-08-11 03:35:18 +02:00
|
|
|
inputModel.prevHistoryItem();
|
2022-06-21 01:06:37 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (e.code == "ArrowDown") {
|
|
|
|
e.preventDefault();
|
2022-08-11 03:35:18 +02:00
|
|
|
inputModel.nextHistoryItem();
|
2022-06-21 01:06:37 +02:00
|
|
|
return;
|
|
|
|
}
|
2022-06-13 20:12:39 +02:00
|
|
|
// console.log(e.code, e.keyCode, e.key, event.which, ctrlMod, e);
|
2022-06-08 02:25:35 +02:00
|
|
|
})();
|
|
|
|
}
|
|
|
|
|
|
|
|
@boundMethod
|
|
|
|
onChange(e : any) {
|
|
|
|
mobx.action(() => {
|
2022-08-11 03:35:18 +02:00
|
|
|
GlobalModel.inputModel.setCurLine(e.target.value);
|
2022-06-08 02:25:35 +02:00
|
|
|
})();
|
|
|
|
}
|
2022-06-13 20:12:39 +02:00
|
|
|
|
|
|
|
@boundMethod
|
|
|
|
doSubmitCmd() {
|
2022-07-11 23:43:18 +02:00
|
|
|
let model = GlobalModel;
|
2022-08-11 03:35:18 +02:00
|
|
|
let inputModel = model.inputModel;
|
|
|
|
let commandStr = inputModel.getCurLine();
|
2022-06-21 01:06:37 +02:00
|
|
|
let hitem = {cmdtext: commandStr};
|
2022-08-11 03:35:18 +02:00
|
|
|
inputModel.clearCurLine();
|
|
|
|
GlobalModel.clearInfoMsg(true);
|
2022-08-12 08:46:52 +02:00
|
|
|
model.submitRawCommand(commandStr, true);
|
2022-06-13 20:12:39 +02:00
|
|
|
}
|
2022-08-11 19:22:43 +02:00
|
|
|
|
|
|
|
getAfterSlash(s : string) : string {
|
|
|
|
let slashIdx = s.lastIndexOf("/");
|
|
|
|
if (slashIdx == s.length-1) {
|
|
|
|
slashIdx = s.lastIndexOf("/", slashIdx-1);
|
|
|
|
}
|
|
|
|
if (slashIdx == -1) {
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
return s.substr(slashIdx+1);
|
|
|
|
}
|
2022-06-08 02:25:35 +02:00
|
|
|
|
|
|
|
render() {
|
2022-08-11 03:35:18 +02:00
|
|
|
let model = GlobalModel;
|
|
|
|
let inputModel = model.inputModel;
|
|
|
|
let curLine = inputModel.getCurLine();
|
|
|
|
let win = GlobalModel.getActiveWindow();
|
|
|
|
let ri : RemoteInstanceType = null;
|
|
|
|
if (win != null) {
|
|
|
|
ri = win.getCurRemoteInstance();
|
|
|
|
}
|
|
|
|
let remote : RemoteType = null;
|
|
|
|
let remoteState : RemoteStateType = null;
|
|
|
|
if (ri != null) {
|
|
|
|
remote = GlobalModel.getRemote(ri.remoteid);
|
|
|
|
remoteState = ri.state;
|
|
|
|
}
|
|
|
|
let promptStr = getRemoteStr(remote);
|
|
|
|
let cwdStr = getCwdStr(remote, remoteState);
|
|
|
|
let infoMsg = GlobalModel.infoMsg.get();
|
|
|
|
let infoShow = GlobalModel.infoShow.get();
|
|
|
|
let istr : string = null;
|
|
|
|
let istrIdx : number = 0;
|
2022-06-08 02:25:35 +02:00
|
|
|
return (
|
2022-08-11 03:35:18 +02:00
|
|
|
<div className={cn("box cmd-input has-background-black", {"has-info": infoShow})}>
|
|
|
|
<div className="cmd-input-info" style={{display: (infoShow ? "block" : "none")}}>
|
|
|
|
<If condition={infoMsg && infoMsg.infotitle != null}>
|
|
|
|
<div className="info-title">
|
|
|
|
{infoMsg.infotitle}
|
|
|
|
</div>
|
|
|
|
</If>
|
|
|
|
<If condition={infoMsg && infoMsg.infomsg != null}>
|
|
|
|
<div className="info-msg">
|
|
|
|
{infoMsg.infomsg}
|
|
|
|
</div>
|
|
|
|
</If>
|
2022-08-11 19:22:43 +02:00
|
|
|
<If condition={infoMsg && infoMsg.infocomps != null && infoMsg.infocomps.length > 0}>
|
|
|
|
<div className="info-comps">
|
|
|
|
<For each="istr" index="istrIdx" of={infoMsg.infocomps}>
|
|
|
|
<div key={istrIdx} className="info-comp">
|
|
|
|
{this.getAfterSlash(istr)}
|
2022-08-11 03:35:18 +02:00
|
|
|
</div>
|
|
|
|
</For>
|
2022-08-11 19:22:43 +02:00
|
|
|
<If condition={infoMsg.infocompsmore}>
|
|
|
|
<div key="more" className="info-comp">
|
2022-08-11 03:35:18 +02:00
|
|
|
...
|
|
|
|
</div>
|
|
|
|
</If>
|
|
|
|
</div>
|
|
|
|
</If>
|
|
|
|
<If condition={infoMsg && infoMsg.infoerror != null}>
|
|
|
|
<div className="info-error">
|
|
|
|
{infoMsg.infoerror}
|
|
|
|
</div>
|
|
|
|
</If>
|
|
|
|
</div>
|
2022-06-08 02:25:35 +02:00
|
|
|
<div className="cmd-input-context">
|
|
|
|
<div className="has-text-white">
|
2022-08-11 03:35:18 +02:00
|
|
|
<span className="bold term-bright-green">[{promptStr} {cwdStr}]</span>
|
2022-06-08 02:25:35 +02:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div className="cmd-input-field field has-addons">
|
|
|
|
<div className="control cmd-quick-context">
|
2022-08-11 03:35:18 +02:00
|
|
|
<div className="button is-static">{promptStr}</div>
|
2022-06-08 02:25:35 +02:00
|
|
|
</div>
|
|
|
|
<div className="control cmd-input-control is-expanded">
|
2022-07-08 22:45:14 +02:00
|
|
|
<textarea id="main-cmd-input" value={curLine} onKeyDown={this.onKeyDown} onChange={this.onChange} className="input"></textarea>
|
2022-06-08 02:25:35 +02:00
|
|
|
</div>
|
|
|
|
<div className="control cmd-exec">
|
2022-06-13 20:12:39 +02:00
|
|
|
<div onClick={this.doSubmitCmd} className="button">
|
2022-06-08 02:25:35 +02:00
|
|
|
<span className="icon">
|
|
|
|
<i className="fa fa-rocket"/>
|
|
|
|
</span>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@mobxReact.observer
|
2022-07-13 08:29:39 +02:00
|
|
|
class ScreenWindowView extends React.Component<{sw : ScreenWindow}, {}> {
|
2022-07-12 07:43:58 +02:00
|
|
|
mutObs : any;
|
2022-07-14 09:54:31 +02:00
|
|
|
rszObs : any
|
2022-07-14 08:11:45 +02:00
|
|
|
randomId : string;
|
2022-07-14 09:54:31 +02:00
|
|
|
width : mobx.IObservableValue<number> = mobx.observable.box(0);
|
2022-08-11 19:41:08 +02:00
|
|
|
lastHeight : number = null;
|
2022-06-18 02:54:14 +02:00
|
|
|
|
2022-07-14 08:11:45 +02:00
|
|
|
scrollToBottom(reason : string) {
|
2022-07-14 09:54:31 +02:00
|
|
|
let elem = document.getElementById(this.getLinesDOMId());
|
2022-07-14 08:11:45 +02:00
|
|
|
if (elem == null) {
|
|
|
|
return;
|
|
|
|
}
|
2022-07-12 07:43:58 +02:00
|
|
|
let oldST = elem.scrollTop;
|
|
|
|
elem.scrollTop = elem.scrollHeight;
|
|
|
|
// console.log("scroll-elem", oldST, elem.scrollHeight, elem.scrollTop, elem.scrollLeft, elem);
|
|
|
|
}
|
|
|
|
|
2022-06-18 02:54:14 +02:00
|
|
|
@boundMethod
|
|
|
|
scrollHandler(event : any) {
|
2022-07-13 08:29:39 +02:00
|
|
|
let {sw} = this.props;
|
2022-06-18 02:54:14 +02:00
|
|
|
let target = event.target;
|
|
|
|
let atBottom = (target.scrollTop + 30 > (target.scrollHeight - target.offsetHeight));
|
2022-07-13 08:29:39 +02:00
|
|
|
if (sw && sw.shouldFollow.get() != atBottom) {
|
|
|
|
mobx.action(() => sw.shouldFollow.set(atBottom));
|
2022-07-12 07:43:58 +02:00
|
|
|
}
|
|
|
|
// console.log("scroll-handler>", atBottom, target.scrollTop, target.scrollHeight);
|
|
|
|
}
|
|
|
|
|
|
|
|
componentDidMount() {
|
2022-07-14 09:54:31 +02:00
|
|
|
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 && sw.shouldFollow.get()) {
|
|
|
|
setTimeout(() => this.scrollToBottom("mount"), 0);
|
|
|
|
}
|
2022-07-12 07:43:58 +02:00
|
|
|
}
|
2022-07-14 09:54:31 +02:00
|
|
|
let wvElem = document.getElementById(this.getWindowViewDOMId());
|
|
|
|
if (wvElem != null) {
|
|
|
|
this.rszObs = new ResizeObserver(this.handleResize.bind(this));
|
|
|
|
this.rszObs.observe(wvElem);
|
2022-07-14 08:11:45 +02:00
|
|
|
}
|
2022-07-12 07:43:58 +02:00
|
|
|
}
|
|
|
|
|
2022-07-14 09:54:31 +02:00
|
|
|
updateWidth(width : number) {
|
|
|
|
mobx.action(() => {
|
|
|
|
this.width.set(width);
|
|
|
|
})();
|
|
|
|
}
|
|
|
|
|
2022-07-12 07:43:58 +02:00
|
|
|
componentWillUnmount() {
|
2022-07-14 08:11:45 +02:00
|
|
|
if (this.mutObs) {
|
|
|
|
this.mutObs.disconnect();
|
|
|
|
}
|
2022-07-14 09:54:31 +02:00
|
|
|
if (this.rszObs) {
|
|
|
|
this.rszObs.disconnect();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
handleResize(entries : any) {
|
|
|
|
if (entries.length == 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
let entry = entries[0];
|
|
|
|
let width = entry.target.offsetWidth;
|
|
|
|
this.updateWidth(width);
|
2022-08-11 19:41:08 +02:00
|
|
|
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");
|
|
|
|
}
|
2022-07-12 07:43:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
handleDomMutation(mutations, mutObs) {
|
2022-08-11 19:41:08 +02:00
|
|
|
this.doConditionalScrollToBottom("mut");
|
|
|
|
}
|
|
|
|
|
|
|
|
doConditionalScrollToBottom(reason : string) {
|
2022-07-13 08:29:39 +02:00
|
|
|
let {sw} = this.props;
|
|
|
|
if (sw && sw.shouldFollow.get()) {
|
2022-08-11 19:41:08 +02:00
|
|
|
setTimeout(() => this.scrollToBottom(reason), 0);
|
2022-07-12 07:43:58 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
getWindow() : Window {
|
2022-07-13 08:29:39 +02:00
|
|
|
let {sw} = this.props;
|
2022-07-15 03:41:49 +02:00
|
|
|
if (sw == null) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
let win = GlobalModel.getWindowById(sw.sessionId, sw.windowId);
|
|
|
|
if (win == null) {
|
|
|
|
win = GlobalModel.loadWindow(sw.sessionId, sw.windowId);
|
|
|
|
}
|
|
|
|
return win;
|
2022-07-12 07:43:58 +02:00
|
|
|
}
|
|
|
|
|
2022-07-14 09:54:31 +02:00
|
|
|
getLinesDOMId() {
|
|
|
|
return "window-lines-" + this.getWindowId();
|
2022-06-18 02:54:14 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@boundMethod
|
2022-07-12 07:43:58 +02:00
|
|
|
handleTermResize(e : any) {
|
2022-07-13 08:29:39 +02:00
|
|
|
let {sw} = this.props;
|
|
|
|
if (sw && sw.shouldFollow.get()) {
|
2022-07-14 08:11:45 +02:00
|
|
|
setTimeout(() => this.scrollToBottom("termresize"), 0);
|
2022-06-18 02:54:14 +02:00
|
|
|
}
|
|
|
|
}
|
2022-07-12 07:43:58 +02:00
|
|
|
|
2022-07-13 08:29:39 +02:00
|
|
|
getWindowViewStyle() : any {
|
2022-07-14 09:54:31 +02:00
|
|
|
// return {width: "100%", height: "100%"};
|
|
|
|
return {position: "absolute", width: "100%", height: "100%", overflowX: "hidden"};
|
|
|
|
}
|
|
|
|
|
|
|
|
getWindowId() : string {
|
|
|
|
let {sw} = this.props;
|
|
|
|
if (sw == null) {
|
|
|
|
if (!this.randomId) {
|
|
|
|
this.randomId = uuidv4();
|
|
|
|
}
|
|
|
|
return this.randomId;
|
|
|
|
}
|
|
|
|
return sw.windowId;
|
|
|
|
}
|
|
|
|
|
|
|
|
getWindowViewDOMId() {
|
|
|
|
return sprintf("window-view-%s", this.getWindowId());
|
2022-07-13 08:29:39 +02:00
|
|
|
}
|
|
|
|
|
2022-07-12 07:43:58 +02:00
|
|
|
renderError(message : string) {
|
2022-07-14 08:11:45 +02:00
|
|
|
let {sw} = this.props;
|
2022-07-12 07:43:58 +02:00
|
|
|
return (
|
2022-07-14 09:54:31 +02:00
|
|
|
<div className="window-view" style={this.getWindowViewStyle()} id={this.getWindowViewDOMId()}>
|
2022-07-14 08:11:45 +02:00
|
|
|
<div key="window-tag" className="window-tag">
|
|
|
|
<If condition={sw != null}>
|
|
|
|
<span>{sw.name.get()}{sw.shouldFollow.get() ? "*" : ""}</span>
|
|
|
|
</If>
|
|
|
|
</div>
|
2022-07-14 09:54:31 +02:00
|
|
|
<div key="lines" className="lines" id={this.getLinesDOMId()}></div>
|
2022-07-14 08:11:45 +02:00
|
|
|
<div key="window-empty" className="window-empty">
|
|
|
|
<div>{message}</div>
|
2022-07-12 07:43:58 +02:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
2022-06-18 02:54:14 +02:00
|
|
|
|
2022-06-08 02:25:35 +02:00
|
|
|
render() {
|
2022-07-13 08:29:39 +02:00
|
|
|
let {sw} = this.props;
|
|
|
|
if (sw == null) {
|
|
|
|
return this.renderError("(no screen window)");
|
2022-07-02 22:32:25 +02:00
|
|
|
}
|
2022-07-13 08:29:39 +02:00
|
|
|
let win = this.getWindow();
|
2022-07-15 03:41:49 +02:00
|
|
|
if (win == null || !win.loaded.get()) {
|
2022-07-12 07:43:58 +02:00
|
|
|
return this.renderError("(loading)");
|
2022-07-02 22:32:25 +02:00
|
|
|
}
|
2022-07-15 03:41:49 +02:00
|
|
|
if (win.loadError.get() != null) {
|
|
|
|
return this.renderError(sprintf("(%s)", win.loadError.get()));
|
|
|
|
}
|
2022-07-14 09:54:31 +02:00
|
|
|
if (this.width.get() == 0) {
|
|
|
|
return this.renderError("");
|
|
|
|
}
|
2022-07-05 07:18:36 +02:00
|
|
|
let idx = 0;
|
2022-07-05 07:37:45 +02:00
|
|
|
let line : LineType = null;
|
2022-07-14 08:11:45 +02:00
|
|
|
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";
|
|
|
|
}
|
2022-06-08 02:25:35 +02:00
|
|
|
return (
|
2022-07-14 09:54:31 +02:00
|
|
|
<div className="window-view" style={this.getWindowViewStyle()} id={this.getWindowViewDOMId()}>
|
2022-07-14 08:11:45 +02:00
|
|
|
<div key="window-tag" className="window-tag">
|
|
|
|
<span>{sw.name.get()}{sw.shouldFollow.get() ? "*" : ""}</span>
|
|
|
|
</div>
|
2022-07-14 09:54:31 +02:00
|
|
|
<div key="lines" className="lines" onScroll={this.scrollHandler} id={this.getLinesDOMId()} style={linesStyle}>
|
2022-07-11 23:43:18 +02:00
|
|
|
<For each="line" of={win.lines} index="idx">
|
2022-07-14 09:54:31 +02:00
|
|
|
<Line key={line.lineid} line={line} sw={sw} width={this.width.get()}/>
|
2022-06-08 02:25:35 +02:00
|
|
|
</For>
|
|
|
|
</div>
|
2022-07-14 08:11:45 +02:00
|
|
|
<If condition={win.lines.length == 0}>
|
|
|
|
<div key="window-empty" className="window-empty">
|
|
|
|
<div><code>[session="{session.name.get()}" screen="{screen.name.get()}" window="{sw.name.get()}"]</code></div>
|
|
|
|
</div>
|
|
|
|
</If>
|
2022-07-12 07:43:58 +02:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-13 08:29:39 +02:00
|
|
|
@mobxReact.observer
|
|
|
|
class ScreenView extends React.Component<{screen : Screen}, {}> {
|
|
|
|
render() {
|
|
|
|
let {screen} = this.props;
|
|
|
|
if (screen == null) {
|
|
|
|
return (
|
|
|
|
<div className="screen-view">
|
|
|
|
(no screen)
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
let sw = screen.getActiveSW();
|
|
|
|
return (
|
|
|
|
<div className="screen-view">
|
2022-07-14 08:11:45 +02:00
|
|
|
<ScreenWindowView key={sw.windowId} sw={sw}/>
|
2022-07-13 08:29:39 +02:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@mobxReact.observer
|
2022-07-14 08:11:45 +02:00
|
|
|
class ScreenTabs extends React.Component<{session : Session}, {}> {
|
|
|
|
@boundMethod
|
|
|
|
handleNewScreen() {
|
|
|
|
let {session} = this.props;
|
2022-07-16 02:38:58 +02:00
|
|
|
GlobalInput.createNewScreen();
|
2022-07-14 08:11:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@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;
|
|
|
|
}
|
2022-07-16 02:38:58 +02:00
|
|
|
GlobalInput.switchScreen(screenId);
|
2022-07-14 08:11:45 +02:00
|
|
|
}
|
2022-08-09 01:22:36 +02:00
|
|
|
|
|
|
|
handleContextMenu(e : any, screenId : string) : void {
|
|
|
|
e.preventDefault();
|
|
|
|
console.log("handle context menu!", screenId);
|
|
|
|
let model = GlobalModel;
|
2022-08-11 20:49:46 +02:00
|
|
|
model.contextScreen(e, screenId);
|
2022-08-09 01:22:36 +02:00
|
|
|
}
|
2022-07-14 08:11:45 +02:00
|
|
|
|
2022-07-13 08:29:39 +02:00
|
|
|
render() {
|
2022-07-14 08:11:45 +02:00
|
|
|
let {session} = this.props;
|
2022-07-13 08:29:39 +02:00
|
|
|
if (session == null) {
|
|
|
|
return null;
|
|
|
|
}
|
2022-07-14 08:11:45 +02:00
|
|
|
let screen : Screen = null;
|
2022-07-15 03:41:49 +02:00
|
|
|
let index = 0;
|
2022-07-13 08:29:39 +02:00
|
|
|
return (
|
|
|
|
<div className="screen-tabs">
|
2022-07-15 03:41:49 +02:00
|
|
|
<For each="screen" index="index" of={session.screens}>
|
2022-08-09 01:22:36 +02:00
|
|
|
<div key={screen.screenId} className={cn("screen-tab", {"is-active": session.activeScreenId.get() == screen.screenId})} onClick={() => this.handleSwitchScreen(screen.screenId)} onContextMenu={(event) => this.handleContextMenu(event, screen.screenId)}>
|
2022-07-14 08:11:45 +02:00
|
|
|
{screen.name.get()}
|
2022-07-15 03:41:49 +02:00
|
|
|
<If condition={index+1 <= 9}>
|
|
|
|
<div className="tab-index">⌘{index+1}</div>
|
|
|
|
</If>
|
2022-07-14 08:11:45 +02:00
|
|
|
</div>
|
|
|
|
</For>
|
|
|
|
<div key="new-screen" className="screen-tab new-screen" onClick={this.handleNewScreen}>
|
|
|
|
+
|
|
|
|
</div>
|
2022-07-13 08:29:39 +02:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-12 07:43:58 +02:00
|
|
|
@mobxReact.observer
|
|
|
|
class SessionView extends React.Component<{}, {}> {
|
|
|
|
render() {
|
|
|
|
let model = GlobalModel;
|
|
|
|
let session = model.getActiveSession();
|
|
|
|
if (session == null) {
|
|
|
|
return <div className="session-view">(no active session)</div>;
|
|
|
|
}
|
2022-07-13 08:29:39 +02:00
|
|
|
let activeScreen = session.getActiveScreen();
|
2022-07-12 07:43:58 +02:00
|
|
|
return (
|
|
|
|
<div className="session-view">
|
2022-07-13 08:29:39 +02:00
|
|
|
<ScreenView screen={activeScreen}/>
|
2022-07-14 08:11:45 +02:00
|
|
|
<ScreenTabs session={session}/>
|
2022-07-12 02:55:03 +02:00
|
|
|
<CmdInput/>
|
2022-06-08 02:25:35 +02:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-20 22:03:20 +02:00
|
|
|
@mobxReact.observer
|
|
|
|
class MainSideBar extends React.Component<{}, {}> {
|
|
|
|
collapsed : mobx.IObservableValue<boolean> = mobx.observable.box(false);
|
|
|
|
|
|
|
|
@boundMethod
|
|
|
|
toggleCollapsed() {
|
|
|
|
mobx.action(() => {
|
|
|
|
this.collapsed.set(!this.collapsed.get());
|
|
|
|
})();
|
|
|
|
}
|
2022-07-08 22:23:00 +02:00
|
|
|
|
|
|
|
handleSessionClick(sessionId : string) {
|
2022-08-09 01:22:36 +02:00
|
|
|
GlobalInput.switchSession(sessionId);
|
|
|
|
}
|
|
|
|
|
|
|
|
handleNewSession() {
|
|
|
|
GlobalInput.createNewSession();
|
2022-07-08 22:23:00 +02:00
|
|
|
}
|
2022-07-09 10:37:19 +02:00
|
|
|
|
2022-06-20 22:03:20 +02:00
|
|
|
render() {
|
2022-07-11 23:43:18 +02:00
|
|
|
let model = GlobalModel;
|
2022-07-13 23:16:47 +02:00
|
|
|
let activeSessionId = model.activeSessionId.get();
|
2022-07-12 02:55:03 +02:00
|
|
|
let session : Session = null;
|
2022-06-20 22:03:20 +02:00
|
|
|
return (
|
|
|
|
<div className={cn("main-sidebar", {"collapsed": this.collapsed.get()})}>
|
|
|
|
<div className="collapse-container">
|
|
|
|
<div className="arrow-container" onClick={this.toggleCollapsed}>
|
|
|
|
<If condition={!this.collapsed.get()}><i className="fa fa-arrow-left"/></If>
|
|
|
|
<If condition={this.collapsed.get()}><i className="fa fa-arrow-right"/></If>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div className="menu">
|
|
|
|
<p className="menu-label">
|
2022-07-08 22:01:37 +02:00
|
|
|
Private Sessions
|
2022-06-20 22:03:20 +02:00
|
|
|
</p>
|
|
|
|
<ul className="menu-list">
|
2022-07-12 02:55:03 +02:00
|
|
|
<If condition={!model.sessionListLoaded.get()}>
|
2022-07-11 23:43:18 +02:00
|
|
|
<li><a>(loading)</a></li>
|
|
|
|
</If>
|
2022-07-12 02:55:03 +02:00
|
|
|
<If condition={model.sessionListLoaded.get()}>
|
2022-07-11 23:43:18 +02:00
|
|
|
<For each="session" of={model.sessionList}>
|
2022-07-13 23:16:47 +02:00
|
|
|
<li key={session.sessionId}><a className={cn({"is-active": activeSessionId == session.sessionId})} onClick={() => this.handleSessionClick(session.sessionId)}>#{session.name.get()}</a></li>
|
2022-07-11 23:43:18 +02:00
|
|
|
</For>
|
2022-08-09 01:22:36 +02:00
|
|
|
<li className="new-session"><a className="new-session" onClick={() => this.handleNewSession()}><i className="fa fa-plus"/> New Session</a></li>
|
2022-07-11 23:43:18 +02:00
|
|
|
</If>
|
2022-06-20 22:03:20 +02:00
|
|
|
</ul>
|
|
|
|
<p className="menu-label">
|
2022-07-08 22:01:37 +02:00
|
|
|
Shared Sessions
|
2022-06-20 22:03:20 +02:00
|
|
|
</p>
|
|
|
|
<ul className="menu-list">
|
2022-07-08 22:01:37 +02:00
|
|
|
<li><a>#server-status</a></li>
|
|
|
|
<li><a className="activity">#bug-3458 <div className="tag is-link">3</div></a></li>
|
|
|
|
<li><a>#dev-build</a></li>
|
2022-06-20 22:03:20 +02:00
|
|
|
<li className="new-session"><a className="new-session"><i className="fa fa-plus"/> New Session</a></li>
|
|
|
|
</ul>
|
|
|
|
<p className="menu-label">
|
|
|
|
Direct Messages
|
|
|
|
</p>
|
|
|
|
<ul className="menu-list">
|
|
|
|
<li><a>
|
|
|
|
<i className="user-status status fa fa-circle"/>
|
|
|
|
<img className="avatar" src="https://i.pravatar.cc/48?img=4"/>
|
|
|
|
Mike S <span className="sub-label">you</span>
|
|
|
|
</a></li>
|
|
|
|
<li><a>
|
|
|
|
<i className="user-status status offline fa fa-circle"/>
|
|
|
|
<img className="avatar" src="https://i.pravatar.cc/48?img=8"/>
|
|
|
|
Matt P
|
|
|
|
</a></li>
|
|
|
|
<li><a>
|
|
|
|
<i className="user-status status offline fa fa-circle"/>
|
|
|
|
<img className="avatar" src="https://i.pravatar.cc/48?img=12"/>
|
|
|
|
Adam B
|
|
|
|
</a></li>
|
|
|
|
<li><a className="activity">
|
|
|
|
<i className="user-status status fa fa-circle"/>
|
2022-06-21 01:06:37 +02:00
|
|
|
<img className="avatar" src="https://i.pravatar.cc/48?img=5"/>
|
2022-06-20 22:03:20 +02:00
|
|
|
Michelle T <div className="tag is-link">2</div>
|
|
|
|
</a></li>
|
|
|
|
</ul>
|
|
|
|
<div className="spacer"></div>
|
|
|
|
<p className="menu-label">
|
|
|
|
Remotes
|
|
|
|
</p>
|
|
|
|
<ul className="menu-list">
|
|
|
|
<li><a><i className="status fa fa-circle"/>local</a></li>
|
|
|
|
<li><a><i className="status fa fa-circle"/>local-sudo</a></li>
|
|
|
|
<li><a><i className="status offline fa fa-circle"/>mike@app01.ec2</a></li>
|
|
|
|
<li><a><i className="status fa fa-circle"/>mike@test01.ec2</a></li>
|
|
|
|
<li><a><i className="status offline fa fa-circle"/>root@app01.ec2</a></li>
|
|
|
|
</ul>
|
|
|
|
<div className="bottom-spacer"></div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-16 03:12:22 +02:00
|
|
|
@mobxReact.observer
|
2022-06-17 00:51:17 +02:00
|
|
|
class Main extends React.Component<{}, {}> {
|
2022-06-16 03:12:22 +02:00
|
|
|
constructor(props : any) {
|
|
|
|
super(props);
|
|
|
|
}
|
|
|
|
|
|
|
|
render() {
|
|
|
|
return (
|
2022-06-20 22:03:20 +02:00
|
|
|
<div id="main">
|
2022-06-16 03:12:22 +02:00
|
|
|
<h1 className="title scripthaus-logo-small">
|
|
|
|
<div className="title-cursor">█</div>
|
|
|
|
ScriptHaus
|
|
|
|
</h1>
|
2022-06-20 22:03:20 +02:00
|
|
|
<div className="main-content">
|
|
|
|
<MainSideBar/>
|
2022-07-11 23:43:18 +02:00
|
|
|
<SessionView/>
|
2022-06-20 22:03:20 +02:00
|
|
|
</div>
|
2022-06-16 03:12:22 +02:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-08 02:25:35 +02:00
|
|
|
|
|
|
|
export {Main};
|
2022-07-07 22:27:44 +02:00
|
|
|
|