From 9a7cfcb0971924a70cae03caab7de7a0e5dfac06 Mon Sep 17 00:00:00 2001 From: hexxa Date: Tue, 28 Dec 2021 23:18:08 +0800 Subject: [PATCH] feat(fe): enable error logger and reporting --- public/static/css/white.css | 23 +++++- src/client/web/src/common/log_error.ts | 30 ++++++-- src/client/web/src/components/layout/rows.tsx | 17 +++-- .../web/src/components/pane_settings.tsx | 71 ++++++++++++++++++- src/client/web/src/components/panel_files.tsx | 10 +-- .../web/src/components/state_updater.ts | 13 ++-- src/client/web/src/i18n/en_US.ts | 4 ++ src/client/web/src/i18n/zh_CN.ts | 4 ++ src/client/web/src/worker/upload_mgr.ts | 15 ++-- src/client/web/src/worker/uploader.ts | 1 - 10 files changed, 153 insertions(+), 35 deletions(-) diff --git a/public/static/css/white.css b/public/static/css/white.css index f824a49..183dd88 100644 --- a/public/static/css/white.css +++ b/public/static/css/white.css @@ -270,12 +270,33 @@ padding: 1rem 0 1rem 0; } -.theme-default .upload-item .error { +.theme-default .error { font-size: 1.4rem; padding: 1rem; color: #f1c40f; margin: 1rem 0 0 0; background-color: #2c3e50; + overflow-wrap: break-word; +} + +.theme-default .error-inline { + font-size: 1.4rem; + padding: 1rem; + color: #f1c40f; + margin: 1rem 0 0 0; + background-color: #2c3e50; + white-space: nowrap; + overflow: auto; +} + +.theme-default .error { + font-size: 1.4rem; + padding: 1rem; + color: #f1c40f; + margin: 1rem 0 0 0; + background-color: #2c3e50; + + overflow: hidden; } .theme-default #sharing-list .info { diff --git a/src/client/web/src/common/log_error.ts b/src/client/web/src/common/log_error.ts index 1fac2e8..55ec1e4 100644 --- a/src/client/web/src/common/log_error.ts +++ b/src/client/web/src/common/log_error.ts @@ -6,6 +6,7 @@ import { ISettingsClient } from "../client"; import { SettingsClient } from "../client/settings"; import { ICoreState } from "../components/core_state"; import { updater } from "../components/state_updater"; +import { alertMsg } from "./env"; const errorVer = "0.0.1"; const cookieKeyClErrs = "qs_cli_errs"; @@ -13,6 +14,7 @@ const cookieKeyClErrs = "qs_cli_errs"; export interface ClientErrorV001 { version: string; error: string; + timestamp: string; state: ICoreState; } @@ -22,9 +24,10 @@ export interface IErrorLogger { error: (msg: string) => null | Error; report: () => Promise; readErrs: () => Map; + truncate: () => void; } -export class ErrorLog { +export class SimpleErrorLogger { private client: ISettingsClient; private storage: ILocalStorage = Storage(); @@ -45,9 +48,18 @@ export class ErrorLog { }; readErrs = (): Map => { - const errsStr = this.storage.get(cookieKeyClErrs); - const errsObj = JSON.parse(errsStr); - return Map(errsObj); + try { + const errsStr = this.storage.get(cookieKeyClErrs); + if (errsStr === "") { + return Map(); + } + + const errsObj = JSON.parse(errsStr); + return Map(errsObj); + } catch (e: any) { + this.truncate(); // reset + } + return Map(); }; private writeErrs = (errs: Map) => { @@ -62,6 +74,7 @@ export class ErrorLog { const clientErr: ClientErrorV001 = { version: errorVer, error: msg, + timestamp: `${Date.now()}`, state: updater().props, }; let errs = this.readErrs(); @@ -88,17 +101,20 @@ export class ErrorLog { } } - // truncate errors - this.writeErrs(Map()); + this.truncate(); } catch (e: any) { return Error(e); } return null; }; + + truncate = () => { + this.writeErrs(Map()); + }; } -const errorLogger = new ErrorLog(new SettingsClient("")); +const errorLogger = new SimpleErrorLogger(new SettingsClient("")); export const ErrorLogger = (): IErrorLogger => { return errorLogger; }; diff --git a/src/client/web/src/components/layout/rows.tsx b/src/client/web/src/components/layout/rows.tsx index ed74754..1657665 100644 --- a/src/client/web/src/components/layout/rows.tsx +++ b/src/client/web/src/components/layout/rows.tsx @@ -98,12 +98,8 @@ export class Rows extends React.Component { } ); - return ( -
+ const orderByList = + sortBtns.size > 0 ? (
{ childrenStyles={List([{ flex: "0 0 auto" }, { flex: "0 0 auto" }])} />
+ ) : null; + + return ( +
+ {orderByList} {bodyRows}
); diff --git a/src/client/web/src/components/pane_settings.tsx b/src/client/web/src/components/pane_settings.tsx index 12842fc..e81c1c6 100644 --- a/src/client/web/src/components/pane_settings.tsx +++ b/src/client/web/src/components/pane_settings.tsx @@ -6,9 +6,11 @@ import { ICoreState, UIProps, MsgProps } from "./core_state"; import { LoginProps } from "./pane_login"; import { Flexbox } from "./layout/flexbox"; import { updater } from "./state_updater"; -import { alertMsg } from "../common/env"; +import { alertMsg, confirmMsg } from "../common/env"; import { Container } from "./layout/container"; import { Card } from "./layout/card"; +import { Rows, Row } from "./layout/rows"; +import { ClientErrorV001, ErrorLogger } from "../common/log_error"; export interface Props { login: LoginProps; msg: MsgProps; @@ -138,12 +140,53 @@ export class PaneSettings extends React.Component { }); }; + truncateErrors = () => { + if (confirmMsg(this.props.msg.pkg.get("op.confirm"))) { + ErrorLogger().truncate(); + } + }; + + reportErrors = () => { + if (confirmMsg(this.props.msg.pkg.get("op.confirm"))) { + ErrorLogger().report(); + } + }; + + prepareErrorRows = (): List => { + let errRows = List(); + + ErrorLogger() + .readErrs() + .forEach((clientErr: ClientErrorV001, sign: string) => { + const elem = ( +
+
{JSON.stringify(clientErr)}
+
+
+ ); + const val = clientErr; + const sortVals = List([]); + + errRows = errRows.push({ + elem, + val, + sortVals, + }); + }); + + return errRows; + }; + render() { + const errRows = this.prepareErrorRows(); + return (
-
{this.props.msg.pkg.get("user.profile")}
+
+ {this.props.msg.pkg.get("user.profile")} +
@@ -372,6 +415,30 @@ export class PaneSettings extends React.Component {
+ + + {this.props.msg.pkg.get("error.report.title")} + , + + + + + , + ])} + childrenStyles={List([{}, { justifyContent: "flex-end" }])} + /> + +
+ + +
+ {/*
{ }; setView = (opt: string) => { - if (opt === "rows" || opt === "table") { - updater().setControlOption(filesViewCtrl, opt); - this.props.update(updater().updateUI); + if (opt !== "rows" && opt !== "table") { + ErrorLogger().error(`FilesPanel:setView: unknown view ${opt}`); return; } - // TODO: log error + updater().setControlOption(filesViewCtrl, opt); + this.props.update(updater().updateUI); }; render() { diff --git a/src/client/web/src/components/state_updater.ts b/src/client/web/src/components/state_updater.ts index 42f83ea..4fe5ff8 100644 --- a/src/client/web/src/components/state_updater.ts +++ b/src/client/web/src/components/state_updater.ts @@ -35,6 +35,7 @@ import { controlName as panelTabs } from "./root_frame"; import { settingsTabsCtrl } from "./dialog_settings"; import { settingsDialogCtrl } from "./layers"; import { errUpdater, errServer } from "../common/errors"; +import { ErrorLogger } from "../common/log_error"; import { MsgPackage, isValidLanPack } from "../i18n/msger"; @@ -135,8 +136,11 @@ export class Updater { refreshUploadings = async (): Promise => { const luResp = await this.filesClient.listUploadings(); if (luResp.status !== 200) { - // TODO: log error - console.error(luResp.data); + // this method is called for authed users + // other status codes are unexpected, including 401 + ErrorLogger().error( + `refreshUploadings: unexpected response ${luResp.status} ${luResp.data}` + ); return errServer; } @@ -767,16 +771,15 @@ export class Updater { const url = this.props.login.preferences.lanPackURL; if (url === "") { const lan = this.props.login.preferences.lan; - if (lan == "en_US" || lan == "zh_CN") { + if (lan === "en_US" || lan === "zh_CN") { // fallback to build-in language pack this.props.msg.lan = lan; this.props.msg.pkg = MsgPackage.get(lan); } else { - // fallback to english + ErrorLogger().error(`syncLan: unexpected lan ${lan}`); this.props.msg.lan = "en_US"; this.props.msg.pkg = MsgPackage.get("en_US"); } - // TODO: should warning here return ""; } diff --git a/src/client/web/src/i18n/en_US.ts b/src/client/web/src/i18n/en_US.ts index 4893143..d64b8c9 100644 --- a/src/client/web/src/i18n/en_US.ts +++ b/src/client/web/src/i18n/en_US.ts @@ -126,4 +126,8 @@ export const msgs: Map = Map({ "item.modTime": "Mod Time", "item.size": "Size", "item.progress": "Progress", + "error.report.title": "Error Report", + "op.truncate": "Truncate", + "op.submit": "Submit", + "term.time": "Time", }); diff --git a/src/client/web/src/i18n/zh_CN.ts b/src/client/web/src/i18n/zh_CN.ts index 9807db3..265cb89 100644 --- a/src/client/web/src/i18n/zh_CN.ts +++ b/src/client/web/src/i18n/zh_CN.ts @@ -123,4 +123,8 @@ export const msgs: Map = Map({ "item.modTime": "修改时间", "item.size": "大小", "item.progress": "进度", + "error.report.title": "报告错误", + "op.truncate": "清空", + "op.submit": "提交", + "term.time": "时间", }); diff --git a/src/client/web/src/worker/upload_mgr.ts b/src/client/web/src/worker/upload_mgr.ts index 6845572..4e1b261 100644 --- a/src/client/web/src/worker/upload_mgr.ts +++ b/src/client/web/src/worker/upload_mgr.ts @@ -12,6 +12,7 @@ import { UploadState, } from "./interface"; import { errUploadMgr } from "../common/errors"; +import { ErrorLogger } from "../common/log_error"; const win: Window = self as any; @@ -197,8 +198,7 @@ export class UploadMgr { err: errResp.err, }); } else { - // TODO: refine this - console.error(`uploading ${errResp.filePath} may already be deleted`); + ErrorLogger().error(`respHandler: entry not found ${errResp.err}`); } this.statusCb(this.infos.toMap(), false); @@ -225,17 +225,16 @@ export class UploadMgr { this.statusCb(this.infos.toMap(), false); } } else { - // TODO: refine this - console.error( - `respHandler: may already be deleted: file(${ - infoResp.filePath - }) infos(${this.infos.toObject()})` + ErrorLogger().error( + `respHandler: entry(uploadInfoKind) not found ${infoResp.err}` ); } break; default: - console.error(`respHandler: response kind not found: ${resp}`); + ErrorLogger().error( + `respHandler: unknown kind: ${JSON.stringify(resp)}` + ); } }; } diff --git a/src/client/web/src/worker/uploader.ts b/src/client/web/src/worker/uploader.ts index fa875a3..67dd9ef 100644 --- a/src/client/web/src/worker/uploader.ts +++ b/src/client/web/src/worker/uploader.ts @@ -2,7 +2,6 @@ import { FilesClient } from "../client/files"; import { IFilesClient, Response, isFatalErr } from "../client"; // TODO: get settings from server -// TODO: move chunk copying to worker const defaultChunkLen = 1024 * 1024 * 1; const speedDownRatio = 0.5; const speedUpRatio = 1.05;