mirror of
https://github.com/wavetermdev/backup.git
synced 2026-08-05 13:57:07 -07:00
can preview lines inside of history view. also implement view in context
This commit is contained in:
+124
-15
@@ -5,15 +5,18 @@ import {If, For, When, Otherwise, Choose} from "tsx-control-statements/component
|
||||
import {sprintf} from "sprintf-js";
|
||||
import {boundMethod} from "autobind-decorator";
|
||||
import cn from "classnames";
|
||||
import {GlobalModel, GlobalCommandRunner} from "./model";
|
||||
import {HistoryItem, RemotePtrType} from "./types";
|
||||
import {GlobalModel, GlobalCommandRunner, Cmd} from "./model";
|
||||
import {HistoryItem, RemotePtrType, LineType, CmdDataType} from "./types";
|
||||
import dayjs from "dayjs";
|
||||
import localizedFormat from 'dayjs/plugin/localizedFormat';
|
||||
import {Line} from "./linecomps";
|
||||
|
||||
dayjs.extend(localizedFormat)
|
||||
|
||||
const PageSize = 50;
|
||||
type OV<V> = mobx.IObservableValue<V>;
|
||||
type OArr<V> = mobx.IObservableArray<V>;
|
||||
type OMap<K,V> = mobx.ObservableMap<K,V>;
|
||||
type CV<V> = mobx.IComputedValue<V>;
|
||||
|
||||
function isBlank(s : string) {
|
||||
return (s == null || s == "");
|
||||
@@ -61,6 +64,10 @@ function formatSSName(snames : Record<string, string>, scrnames : Record<string,
|
||||
|
||||
@mobxReact.observer
|
||||
class HistoryView extends React.Component<{}, {}> {
|
||||
tableRef : React.RefObject<any> = React.createRef();
|
||||
tableWidth : OV<number> = mobx.observable.box(0, {name: "tableWidth"});
|
||||
tableRszObs : ResizeObserver;
|
||||
|
||||
@boundMethod
|
||||
clickCloseHandler() : void {
|
||||
GlobalModel.historyViewModel.closeView();
|
||||
@@ -115,7 +122,7 @@ class HistoryView extends React.Component<{}, {}> {
|
||||
return;
|
||||
}
|
||||
else {
|
||||
for (let i=0; i<hvm.items.length && i<PageSize; i++) {
|
||||
for (let i=0; i<hvm.items.length; i++) {
|
||||
hvm.selectedItems.set(hvm.items[i].historyid, true);
|
||||
}
|
||||
}
|
||||
@@ -136,6 +143,37 @@ class HistoryView extends React.Component<{}, {}> {
|
||||
GlobalModel.historyViewModel.setActiveItem(historyId);
|
||||
}
|
||||
}
|
||||
|
||||
checkWidth() {
|
||||
if (this.tableRef.current != null) {
|
||||
mobx.action(() => {
|
||||
this.tableWidth.set(this.tableRef.current.offsetWidth);
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleTableResize() {
|
||||
this.checkWidth();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (this.tableRef.current != null) {
|
||||
this.tableRszObs = new ResizeObserver(this.handleTableResize.bind(this));
|
||||
this.tableRszObs.observe(this.tableRef.current);
|
||||
}
|
||||
this.checkWidth();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.tableRszObs != null) {
|
||||
this.tableRszObs.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.checkWidth();
|
||||
}
|
||||
|
||||
render() {
|
||||
let isHidden = (GlobalModel.activeMainView.get() != "history");
|
||||
@@ -150,11 +188,7 @@ class HistoryView extends React.Component<{}, {}> {
|
||||
let snames = GlobalModel.getSessionNames();
|
||||
let rnames = GlobalModel.getRemoteNames();
|
||||
let scrnames = GlobalModel.getScreenNames();
|
||||
let hasMore = false;
|
||||
if (items.length > PageSize) {
|
||||
items = items.slice(0, PageSize);
|
||||
hasMore = true;
|
||||
}
|
||||
let hasMore = hvm.hasMore.get();
|
||||
let offset = hvm.offset.get();
|
||||
let numSelected = hvm.selectedItems.size;
|
||||
let controlCheckboxIcon = "fa-sharp fa-regular fa-square";
|
||||
@@ -164,7 +198,12 @@ class HistoryView extends React.Component<{}, {}> {
|
||||
if (numSelected > 0 && numSelected == items.length) {
|
||||
controlCheckboxIcon = "fa-sharp fa-regular fa-square-check";
|
||||
}
|
||||
let activeItem = hvm.activeItem.get();
|
||||
let activeItemId = hvm.activeItem.get();
|
||||
let activeItem = hvm.getHistoryItemById(activeItemId);
|
||||
let activeLine : LineType = null;
|
||||
if (activeItem != null) {
|
||||
activeLine = hvm.getLineById(activeItem.lineid);
|
||||
}
|
||||
return (
|
||||
<div className={cn("history-view", "alt-view", {"is-hidden": isHidden})}>
|
||||
<div className="close-button" onClick={this.clickCloseHandler}><i className="fa-sharp fa-solid fa-xmark"></i></div>
|
||||
@@ -196,7 +235,7 @@ class HistoryView extends React.Component<{}, {}> {
|
||||
<div className="btn-spacer"/>
|
||||
<div className={cn("showing-btn", {"is-disabled": !hasMore})} onClick={hasMore ? this.handleNext : null}><i className="fa-sharp fa-solid fa-chevron-right"/></div>
|
||||
</div>
|
||||
<table className="history-table" cellSpacing="0" cellPadding="0" border={0}>
|
||||
<table className="history-table" cellSpacing="0" cellPadding="0" border={0} ref={this.tableRef}>
|
||||
<tbody>
|
||||
<For index="idx" each="item" of={items}>
|
||||
<tr key={item.historyid} className={cn("history-item", {"is-selected": hvm.selectedItems.get(item.historyid)})}>
|
||||
@@ -221,13 +260,13 @@ class HistoryView extends React.Component<{}, {}> {
|
||||
{formatRemoteName(rnames, item.remote)}
|
||||
</td>
|
||||
<td className="cmdstr" onClick={() => this.activateItem(item.historyid)}>
|
||||
{item.cmdstr}
|
||||
<div className="cmdstr-content">{item.cmdstr}</div>
|
||||
</td>
|
||||
</tr>
|
||||
<If condition={activeItem == item.historyid}>
|
||||
<If condition={activeItemId == item.historyid}>
|
||||
<tr className="active-history-item">
|
||||
<td colSpan={10}>
|
||||
<line sw={hvm.specialLineContainer} line={null} width={600} staticRender={true} visible={null} onHeightChange={null} overrideCollapsed={null} topBorder={false} renderMode="normal"/>
|
||||
<td colSpan={6}>
|
||||
<LineContainer key={activeItemId} historyId={activeItemId} width={this.tableWidth.get()}/>
|
||||
</td>
|
||||
</tr>
|
||||
</If>
|
||||
@@ -244,5 +283,75 @@ class HistoryView extends React.Component<{}, {}> {
|
||||
}
|
||||
}
|
||||
|
||||
class LineContainer extends React.Component<{historyId : string, width : number}, {}> {
|
||||
line : LineType;
|
||||
cmd : Cmd;
|
||||
historyItem : HistoryItem;
|
||||
visible : OV<boolean> = mobx.observable.box(true);
|
||||
overrideCollapsed : OV<boolean> = mobx.observable.box(false);
|
||||
|
||||
constructor(props : any) {
|
||||
super(props);
|
||||
let hvm = GlobalModel.historyViewModel;
|
||||
this.historyItem = hvm.getHistoryItemById(props.historyId);
|
||||
if (this.historyItem == null) {
|
||||
return;
|
||||
}
|
||||
this.line = hvm.getLineById(this.historyItem.lineid);
|
||||
this.cmd = hvm.getCmdById(this.historyItem.cmdid);
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
handleHeightChange(lineNum : number, newHeight : number, oldHeight : number) : void {
|
||||
return;
|
||||
}
|
||||
|
||||
@boundMethod
|
||||
viewInContext() {
|
||||
let screen = GlobalModel.getScreenById(this.historyItem.sessionid, this.historyItem.screenid);
|
||||
if (screen == null) {
|
||||
return null;
|
||||
}
|
||||
GlobalModel.historyViewModel.closeView();
|
||||
GlobalCommandRunner.lineView(screen.sessionId, screen.screenId, this.line.linenum);
|
||||
}
|
||||
|
||||
render() {
|
||||
let hvm = GlobalModel.historyViewModel;
|
||||
if (this.historyItem == null || this.props.width == 0) {
|
||||
return null;
|
||||
}
|
||||
if (this.line == null) {
|
||||
return <div className="line-container no-line"><div>[no line data]</div></div>;
|
||||
}
|
||||
let width = this.props.width;
|
||||
width = width - 50;
|
||||
if (width < 400) {
|
||||
width = 400;
|
||||
}
|
||||
let session = GlobalModel.getSessionById(this.historyItem.sessionid);
|
||||
let screen = GlobalModel.getScreenById(this.historyItem.sessionid, this.historyItem.screenid);
|
||||
let ssStr = "";
|
||||
let canViewInContext = false;
|
||||
if (session != null && screen != null) {
|
||||
ssStr = sprintf("#%s[%s]", session.name.get(), screen.name.get());
|
||||
canViewInContext = true;
|
||||
}
|
||||
return (
|
||||
<div className="line-container">
|
||||
<If condition={canViewInContext}>
|
||||
<div className="line-context">
|
||||
<div title="View in Context" className="vic-btn" onClick={this.viewInContext}><i className="fa-sharp fa-solid fa-right"/> {ssStr}</div>
|
||||
</div>
|
||||
</If>
|
||||
<If condition={session == null}>
|
||||
<div className="no-line-context"/>
|
||||
</If>
|
||||
<Line sw={hvm.specialLineContainer} line={this.line} width={width} staticRender={false} visible={this.visible} onHeightChange={this.handleHeightChange} overrideCollapsed={this.overrideCollapsed} topBorder={false} renderMode="normal"/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export {HistoryView};
|
||||
|
||||
+93
-11
@@ -163,7 +163,6 @@ class Cmd {
|
||||
remoteId : string;
|
||||
cmdId : string;
|
||||
data : OV<CmdDataType>;
|
||||
watching : boolean = false;
|
||||
|
||||
constructor(cmd : CmdDataType) {
|
||||
this.sessionId = cmd.sessionid;
|
||||
@@ -1639,17 +1638,47 @@ type LineFocusType = {
|
||||
class SpecialHistoryViewLineContainer {
|
||||
historyItem : HistoryItem;
|
||||
renderer : RendererModel;
|
||||
cmd : Cmd;
|
||||
|
||||
constructor(hitem : HistoryItem) {
|
||||
this.historyItem = hitem;
|
||||
}
|
||||
|
||||
getCmd(line : LineType) : Cmd {
|
||||
return null;
|
||||
if (this.cmd == null) {
|
||||
this.cmd = GlobalModel.historyViewModel.getCmdById(line.cmdid);
|
||||
}
|
||||
return this.cmd;
|
||||
}
|
||||
|
||||
loadTerminalRenderer(elem : Element, line : LineType, cmd : Cmd, width : number) : void {
|
||||
this.unloadRenderer(null);
|
||||
let cmdId = cmd.cmdId;
|
||||
let termWrap = this.getRenderer(cmdId);
|
||||
if (termWrap != null) {
|
||||
console.log("term-wrap already exists for", line.windowid, cmdId);
|
||||
return;
|
||||
}
|
||||
let cols = windowWidthToCols(width, GlobalModel.termFontSize.get());
|
||||
let usedRows = GlobalModel.getTUR(line.sessionid, cmdId, cols);
|
||||
if (line.contentheight != null && line.contentheight != -1) {
|
||||
usedRows = line.contentheight;
|
||||
}
|
||||
let termContext = {sessionId: line.sessionid, screenId: "(historyview)", windowId: line.windowid, cmdId: cmdId, lineId : line.lineid, lineNum: line.linenum};
|
||||
termWrap = new TermWrap(elem, {
|
||||
termContext: termContext,
|
||||
usedRows: usedRows,
|
||||
termOpts: cmd.getTermOpts(),
|
||||
winSize: {height: 0, width: width},
|
||||
dataHandler: null,
|
||||
focusHandler: null,
|
||||
isRunning: cmd.isRunning(),
|
||||
customKeyHandler: null,
|
||||
fontSize: GlobalModel.termFontSize.get(),
|
||||
noSetTUR: true,
|
||||
});
|
||||
this.renderer = termWrap;
|
||||
return;
|
||||
}
|
||||
|
||||
loadImageRenderer(imageDivElem : any, line : LineType, cmd : Cmd) : ImageRendererModel {
|
||||
@@ -1719,12 +1748,17 @@ const HistoryPageSize = 50;
|
||||
|
||||
class HistoryViewModel {
|
||||
items : OArr<HistoryItem> = mobx.observable.array([], {name: "HistoryItems"});
|
||||
hasMore : OV<boolean> = mobx.observable.box(false, {name: "historyview-hasmore"});
|
||||
offset : OV<number> = mobx.observable.box(0, {name: "historyview-offset"});
|
||||
searchText : OV<string> = mobx.observable.box("", {name: "historyview-searchtext"});
|
||||
activeSearchText : string = null;
|
||||
selectedItems : OMap<string, boolean> = mobx.observable.map({}, {name: "historyview-selectedItems"});
|
||||
deleteActive : OV<boolean> = mobx.observable.box(false, {name: "historyview-deleteActive"});
|
||||
activeItem : OV<string> = mobx.observable.box(null, {name: "historyview-activeItem"});
|
||||
|
||||
historyItemLines : LineType[] = [];
|
||||
historyItemCmds : CmdDataType[] = [];
|
||||
|
||||
specialLineContainer : SpecialHistoryViewLineContainer;
|
||||
|
||||
constructor() {
|
||||
@@ -1736,19 +1770,50 @@ class HistoryViewModel {
|
||||
})();
|
||||
}
|
||||
|
||||
getLineById(lineId : string) : LineType {
|
||||
if (isBlank(lineId)) {
|
||||
return null;
|
||||
}
|
||||
for (let i=0; i<this.historyItemLines.length; i++) {
|
||||
let line = this.historyItemLines[i];
|
||||
if (line.lineid == lineId) {
|
||||
return line;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
getCmdById(cmdId : string) : Cmd {
|
||||
if (isBlank(cmdId)) {
|
||||
return null;
|
||||
}
|
||||
for (let i=0; i<this.historyItemCmds.length; i++) {
|
||||
let cmd = this.historyItemCmds[i];
|
||||
if (cmd.cmdid == cmdId) {
|
||||
return new Cmd(cmd);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
getHistoryItemById(historyId : string) : HistoryItem {
|
||||
if (isBlank(historyId)) {
|
||||
return null;
|
||||
}
|
||||
for (let i=0; i<this.items.length; i++) {
|
||||
let hitem = this.items[i];
|
||||
if (hitem.historyid == historyId) {
|
||||
return hitem;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
setActiveItem(historyId : string) {
|
||||
if (this.activeItem.get() == historyId) {
|
||||
return;
|
||||
}
|
||||
let hitem : HistoryItem = null;
|
||||
if (historyId != null) {
|
||||
for (let i=0; i<this.items.length; i++) {
|
||||
if (this.items[i].historyid == historyId) {
|
||||
hitem = this.items[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
let hitem = this.getHistoryItemById(historyId);
|
||||
mobx.action(() => {
|
||||
if (hitem == null) {
|
||||
this.activeItem.set(null);
|
||||
@@ -1795,9 +1860,12 @@ class HistoryViewModel {
|
||||
|
||||
submitSearch() : void {
|
||||
mobx.action(() => {
|
||||
this.hasMore.set(false);
|
||||
this.items.replace([]);
|
||||
this.offset.set(0);
|
||||
this.activeSearchText = this.searchText.get();
|
||||
this.historyItemLines = [];
|
||||
this.historyItemCmds = [];
|
||||
})();
|
||||
GlobalCommandRunner.historyView({offset: 0, searchText: this.activeSearchText});
|
||||
}
|
||||
@@ -1813,8 +1881,11 @@ class HistoryViewModel {
|
||||
showHistoryView(data : HistoryViewDataType) : void {
|
||||
mobx.action(() => {
|
||||
GlobalModel.activeMainView.set("history");
|
||||
this.hasMore.set(data.hasmore);
|
||||
this.items.replace(data.items || []);
|
||||
this.offset.set(data.offset);
|
||||
this.historyItemLines = (data.lines ?? []);
|
||||
this.historyItemCmds = (data.cmds ?? []);
|
||||
this.selectedItems.clear();
|
||||
})();
|
||||
}
|
||||
@@ -2890,6 +2961,17 @@ class CommandRunner {
|
||||
GlobalModel.submitCommand("screen", null, [screen], {"nohist": "1"}, false);
|
||||
}
|
||||
|
||||
lineView(sessionId : string, screenId : string, lineNum : number) {
|
||||
let screen = GlobalModel.getScreenById(sessionId, screenId);
|
||||
if (screen != null) {
|
||||
let sw = screen.getActiveSW();
|
||||
if (sw != null) {
|
||||
sw.setAnchorFields(lineNum, 0, "line:view");
|
||||
}
|
||||
}
|
||||
GlobalModel.submitCommand("line", "view", [sessionId, screenId, String(lineNum)], {"nohist": "1"}, false);
|
||||
}
|
||||
|
||||
createNewSession() {
|
||||
GlobalModel.submitCommand("session", "open", null, {"nohist": "1"}, false);
|
||||
}
|
||||
|
||||
+45
-4
@@ -307,8 +307,44 @@ body::-webkit-scrollbar {
|
||||
|
||||
tr.active-history-item {
|
||||
td {
|
||||
padding: 10px;
|
||||
background-color: blue;
|
||||
padding-right: 10px;
|
||||
|
||||
.line-container {
|
||||
padding: 0px 10px 10px 10px;
|
||||
overflow-x: auto;
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
.line-context {
|
||||
.mono-font(12px);
|
||||
margin-left: 20px;
|
||||
margin-bottom: 10px;
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
.vic-btn {
|
||||
cursor: pointer;
|
||||
color: #ccc;
|
||||
|
||||
&:hover {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.no-line-context {
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
.line-container.no-line {
|
||||
.mono-font(12px);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -400,13 +436,18 @@ body::-webkit-scrollbar {
|
||||
.mono-font(12px);
|
||||
color: white;
|
||||
background-color: black;
|
||||
flex: 1 0 auto;
|
||||
flex: 1 0 0;
|
||||
padding-left: 20px;
|
||||
border-radius: 3px;
|
||||
white-space: pre;
|
||||
max-height: 64px;
|
||||
overflow-y: auto;
|
||||
cursor: pointer;
|
||||
min-width: 300px;
|
||||
overflow-y: auto;
|
||||
|
||||
.cmdstr-content {
|
||||
overflow-x: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+7
-2
@@ -29,7 +29,8 @@ type TermWrapOpts = {
|
||||
dataHandler? : (data : string, termWrap : TermWrap) => void,
|
||||
isRunning : boolean,
|
||||
customKeyHandler? : (event : any, termWrap : TermWrap) => boolean,
|
||||
fontSize: number,
|
||||
fontSize : number,
|
||||
noSetTUR? : boolean,
|
||||
};
|
||||
|
||||
// cmd-instance
|
||||
@@ -50,6 +51,7 @@ class TermWrap {
|
||||
focusHandler : (focus : boolean) => void;
|
||||
isRunning : boolean;
|
||||
fontSize : number;
|
||||
noSetTUR : boolean;
|
||||
|
||||
constructor(elem : Element, opts : TermWrapOpts) {
|
||||
opts = opts ?? ({} as any);
|
||||
@@ -60,6 +62,7 @@ class TermWrap {
|
||||
this.focusHandler = opts.focusHandler;
|
||||
this.isRunning = opts.isRunning;
|
||||
this.fontSize = opts.fontSize;
|
||||
this.noSetTUR = !!opts.noSetTUR;
|
||||
if (this.flexRows) {
|
||||
this.atRowMax = false;
|
||||
this.usedRows = mobx.observable.box(opts.usedRows ?? (opts.isRunning ? 1 : 0), {name: "term-usedrows"});
|
||||
@@ -218,7 +221,9 @@ class TermWrap {
|
||||
return;
|
||||
}
|
||||
this.usedRows.set(tur);
|
||||
GlobalModel.setTUR(termContext, this.termSize, tur);
|
||||
if (!this.noSetTUR) {
|
||||
GlobalModel.setTUR(termContext, this.termSize, tur);
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
|
||||
+3
-1
@@ -290,9 +290,11 @@ type ModelUpdateType = {
|
||||
};
|
||||
|
||||
type HistoryViewDataType = {
|
||||
totalcount : number,
|
||||
offset : number,
|
||||
items : HistoryItem[],
|
||||
lines : LineType[],
|
||||
cmds : CmdDataType[],
|
||||
hasmore : boolean,
|
||||
};
|
||||
|
||||
type BookmarkType = {
|
||||
|
||||
Reference in New Issue
Block a user