When client is disconnected change log to show last 50 lines of wavesrv.log (#210)

* improved disconnected modal

* wrap pre with div

* make number of a lines param a constant

* revert app.tsx

* use tail command and capture the output instead

* reset TabSwitcherModal
This commit is contained in:
Red J Adaya 2024-01-05 02:13:40 +08:00 committed by GitHub
parent d1319c0a2c
commit b2a1bb3818
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 128 additions and 66 deletions

View File

@ -68,7 +68,6 @@ class App extends React.Component<{}, {}> {
}
render() {
let clientSettingsModal = GlobalModel.clientSettingsModal.get();
let remotesModel = GlobalModel.remotesModel;
let disconnected = !GlobalModel.ws.open.get() || !GlobalModel.waveSrvRunning.get();
let hasClientStop = GlobalModel.getHasClientStop();

View File

@ -1085,16 +1085,18 @@ class Dropdown extends React.Component<DropdownProps, DropdownState> {
}
interface ModalHeaderProps {
onClose: () => void;
onClose?: () => void;
title: string;
}
const ModalHeader: React.FC<ModalHeaderProps> = ({ onClose, title }) => (
<div className="wave-modal-header">
{<div className="wave-modal-title">{title}</div>}
<IconButton theme="secondary" variant="ghost" onClick={onClose}>
<i className="fa-sharp fa-solid fa-xmark"></i>
</IconButton>
<If condition={onClose}>
<IconButton theme="secondary" variant="ghost" onClick={onClose}>
<i className="fa-sharp fa-solid fa-xmark"></i>
</IconButton>
</If>
</div>
);

View File

@ -17,24 +17,46 @@
}
.disconnected-modal {
.modal-content {
footer {
.footer-text-link {
color: @term-white;
cursor: pointer;
.wave-modal-content {
.wave-modal-body {
padding: 0;
.modal-content {
footer {
.footer-text-link {
color: @term-white;
cursor: pointer;
}
}
}
.inner-content {
.log {
height: 335px;
margin-bottom: 20px;
overflow: auto;
&::-webkit-scrollbar-track,
&::-webkit-scrollbar-thumb,
&::-webkit-scrollbar-corner {
display: none;
}
&:hover::-webkit-scrollbar-thumb {
display: block;
}
pre {
color: @term-white;
background-color: @term-black;
}
}
}
}
}
.inner-content {
.ws-log {
padding: 5px;
background-color: @term-black;
height: 250px;
overflow: auto;
.ws-logline {
color: @term-white;
.wave-modal-footer {
button:first-child {
color: @term-green;
}
}
}

View File

@ -27,10 +27,9 @@ import {
import * as util from "../../../util/util";
import * as textmeasure from "../../../util/textmeasure";
import { ClientDataType } from "../../../types/types";
import { Session, Screen } from "../../../model/model";
import { Screen } from "../../../model/model";
import { ReactComponent as SquareIcon } from "../../assets/icons/tab/square.svg";
import { ReactComponent as WarningIcon } from "../../assets/icons/line/triangle-exclamation.svg";
import shield from "../../assets/icons/shield_check.svg";
import help from "../../assets/icons/help_filled.svg";
import github from "../../assets/icons/github.svg";
@ -48,6 +47,7 @@ type OArr<V> = mobx.IObservableArray<V>;
const RemotePtyRows = 9;
const RemotePtyCols = 80;
const NumOfLines = 50;
const PasswordUnchangedSentinel = "--unchanged--";
@mobxReact.observer
@ -70,7 +70,8 @@ class ModalsProvider extends React.Component {
@mobxReact.observer
class DisconnectedModal extends React.Component<{}, {}> {
logRef: any = React.createRef();
showLog: mobx.IObservableValue<boolean> = mobx.observable.box(false);
logs: mobx.IObservableValue<string> = mobx.observable.box("");
logInterval: NodeJS.Timeout = null;
@boundMethod
restartServer() {
@ -83,8 +84,16 @@ class DisconnectedModal extends React.Component<{}, {}> {
}
componentDidMount() {
if (this.logRef.current != null) {
this.logRef.current.scrollTop = this.logRef.current.scrollHeight;
this.fetchLogs();
this.logInterval = setInterval(() => {
this.fetchLogs();
}, 5000);
}
componentWillUnmount() {
if (this.logInterval) {
clearInterval(this.logInterval);
}
}
@ -94,58 +103,52 @@ class DisconnectedModal extends React.Component<{}, {}> {
}
}
@boundMethod
handleShowLog(): void {
mobx.action(() => {
this.showLog.set(!this.showLog.get());
})();
fetchLogs() {
GlobalModel.getLastLogs(
NumOfLines,
mobx.action((logs) => {
this.logs.set(logs);
if (this.logRef.current != null) {
this.logRef.current.scrollTop = this.logRef.current.scrollHeight;
}
})
);
}
render() {
let model = GlobalModel;
let logLine: string = null;
let idx: number = 0;
return (
<div className="prompt-modal disconnected-modal modal is-active">
<div className="modal-background"></div>
<div className="modal-content">
<div className="message-header">
<div className="modal-title">Wave Client Disconnected</div>
</div>
<If condition={this.showLog.get()}>
<Modal className="disconnected-modal">
<Modal.Header title="Wave Client Disconnected" />
<div className="wave-modal-body">
<div className="modal-content">
<div className="inner-content">
<div className="ws-log" ref={this.logRef}>
<For each="logLine" index="idx" of={GlobalModel.ws.wsLog}>
<div key={idx} className="ws-logline">
{logLine}
</div>
</For>
<div className="log" ref={this.logRef}>
<pre>{this.logs.get()}</pre>
</div>
</div>
</If>
<footer>
<div className="footer-text-link" style={{ marginLeft: 10 }} onClick={this.handleShowLog}>
<If condition={!this.showLog.get()}>
<i className="fa-sharp fa-solid fa-plus" /> Show Log
</If>
<If condition={this.showLog.get()}>
<i className="fa-sharp fa-solid fa-minus" /> Hide Log
</If>
</div>
<div className="flex-spacer" />
<button onClick={this.tryReconnect} className="button">
</div>
</div>
<div className="wave-modal-footer">
<Button
theme="secondary"
onClick={this.tryReconnect}
leftIcon={
<span className="icon">
<i className="fa-sharp fa-solid fa-rotate" />
</span>
<span>Try Reconnect</span>
</button>
<button onClick={this.restartServer} className="button is-danger" style={{ marginLeft: 10 }}>
<WarningIcon className="icon" />
<span>Restart Server</span>
</button>
</footer>
}
>
Try Reconnect
</Button>
<Button
theme="secondary"
onClick={this.restartServer}
leftIcon={<i className="fa-sharp fa-solid fa-triangle-exclamation"></i>}
>
Restart Server
</Button>
</div>
</div>
</Modal>
);
}
}

View File

@ -485,6 +485,33 @@ electron.ipcMain.on("reload-window", (event) => {
return;
});
electron.ipcMain.on("get-last-logs", async (event, numberOfLines) => {
try {
const logPath = path.join(getWaveHomeDir(), "wavesrv.log");
const lastLines = await readLastLinesOfFile(logPath, numberOfLines);
event.reply("last-logs", lastLines);
} catch (err) {
console.error("Error reading log file:", err);
event.reply("last-logs", "Error reading log file.");
}
});
function readLastLinesOfFile(filePath, lineCount) {
return new Promise((resolve, reject) => {
child_process.exec(`tail -n ${lineCount} "${filePath}"`, (err, stdout, stderr) => {
if (err) {
reject(err.message);
return;
}
if (stderr) {
reject(stderr);
return;
}
resolve(stdout);
});
});
}
function getContextMenu(): any {
let menu = new electron.Menu();
let menuItem = new electron.MenuItem({ label: "Testing", click: () => console.log("click testing!") });

View File

@ -6,6 +6,10 @@ contextBridge.exposeInMainWorld("api", {
getIsDev: () => ipcRenderer.sendSync("get-isdev"),
getAuthKey: () => ipcRenderer.sendSync("get-authkey"),
getWaveSrvStatus: () => ipcRenderer.sendSync("wavesrv-status"),
getLastLogs: (numberOfLines, callback) => {
ipcRenderer.send("get-last-logs", numberOfLines);
ipcRenderer.once("last-logs", (event, data) => callback(data));
},
restartWaveSrv: () => ipcRenderer.sendSync("restart-server"),
reloadWindow: () => ipcRenderer.sendSync("reload-window"),
onTCmd: (callback) => ipcRenderer.on("t-cmd", callback),

View File

@ -208,6 +208,7 @@ type ElectronApi = {
contextScreen: (screenOpts: { screenId: string }, position: { x: number; y: number }) => void;
contextEditMenu: (position: { x: number; y: number }, opts: ContextMenuOpts) => void;
onWaveSrvStatusChange: (callback: (status: boolean, pid: number) => void) => void;
getLastLogs: (numOfLines: number, callback: (logs: any) => void) => void;
};
function getApi(): ElectronApi {
@ -3476,6 +3477,10 @@ class Model {
})();
}
getLastLogs(numbOfLines: number, cb: (logs: any) => void): void {
getApi().getLastLogs(numbOfLines, cb);
}
getContentHeight(context: RendererContext): number {
let key = context.screenId + "/" + context.lineId;
return this.termUsedRowsCache[key];