feat(fe): enable error logger and reporting
This commit is contained in:
parent
3cb711b37e
commit
9a7cfcb097
10 changed files with 153 additions and 35 deletions
|
@ -270,12 +270,33 @@
|
||||||
padding: 1rem 0 1rem 0;
|
padding: 1rem 0 1rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-default .upload-item .error {
|
.theme-default .error {
|
||||||
font-size: 1.4rem;
|
font-size: 1.4rem;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
color: #f1c40f;
|
color: #f1c40f;
|
||||||
margin: 1rem 0 0 0;
|
margin: 1rem 0 0 0;
|
||||||
background-color: #2c3e50;
|
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 {
|
.theme-default #sharing-list .info {
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { ISettingsClient } from "../client";
|
||||||
import { SettingsClient } from "../client/settings";
|
import { SettingsClient } from "../client/settings";
|
||||||
import { ICoreState } from "../components/core_state";
|
import { ICoreState } from "../components/core_state";
|
||||||
import { updater } from "../components/state_updater";
|
import { updater } from "../components/state_updater";
|
||||||
|
import { alertMsg } from "./env";
|
||||||
|
|
||||||
const errorVer = "0.0.1";
|
const errorVer = "0.0.1";
|
||||||
const cookieKeyClErrs = "qs_cli_errs";
|
const cookieKeyClErrs = "qs_cli_errs";
|
||||||
|
@ -13,6 +14,7 @@ const cookieKeyClErrs = "qs_cli_errs";
|
||||||
export interface ClientErrorV001 {
|
export interface ClientErrorV001 {
|
||||||
version: string;
|
version: string;
|
||||||
error: string;
|
error: string;
|
||||||
|
timestamp: string;
|
||||||
state: ICoreState;
|
state: ICoreState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,9 +24,10 @@ export interface IErrorLogger {
|
||||||
error: (msg: string) => null | Error;
|
error: (msg: string) => null | Error;
|
||||||
report: () => Promise<null | Error>;
|
report: () => Promise<null | Error>;
|
||||||
readErrs: () => Map<string, ClientErrorV001>;
|
readErrs: () => Map<string, ClientErrorV001>;
|
||||||
|
truncate: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ErrorLog {
|
export class SimpleErrorLogger {
|
||||||
private client: ISettingsClient;
|
private client: ISettingsClient;
|
||||||
private storage: ILocalStorage = Storage();
|
private storage: ILocalStorage = Storage();
|
||||||
|
|
||||||
|
@ -45,9 +48,18 @@ export class ErrorLog {
|
||||||
};
|
};
|
||||||
|
|
||||||
readErrs = (): Map<string, ClientErrorV001> => {
|
readErrs = (): Map<string, ClientErrorV001> => {
|
||||||
const errsStr = this.storage.get(cookieKeyClErrs);
|
try {
|
||||||
const errsObj = JSON.parse(errsStr);
|
const errsStr = this.storage.get(cookieKeyClErrs);
|
||||||
return Map(errsObj);
|
if (errsStr === "") {
|
||||||
|
return Map();
|
||||||
|
}
|
||||||
|
|
||||||
|
const errsObj = JSON.parse(errsStr);
|
||||||
|
return Map(errsObj);
|
||||||
|
} catch (e: any) {
|
||||||
|
this.truncate(); // reset
|
||||||
|
}
|
||||||
|
return Map();
|
||||||
};
|
};
|
||||||
|
|
||||||
private writeErrs = (errs: Map<string, ClientErrorV001>) => {
|
private writeErrs = (errs: Map<string, ClientErrorV001>) => {
|
||||||
|
@ -62,6 +74,7 @@ export class ErrorLog {
|
||||||
const clientErr: ClientErrorV001 = {
|
const clientErr: ClientErrorV001 = {
|
||||||
version: errorVer,
|
version: errorVer,
|
||||||
error: msg,
|
error: msg,
|
||||||
|
timestamp: `${Date.now()}`,
|
||||||
state: updater().props,
|
state: updater().props,
|
||||||
};
|
};
|
||||||
let errs = this.readErrs();
|
let errs = this.readErrs();
|
||||||
|
@ -88,17 +101,20 @@ export class ErrorLog {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// truncate errors
|
this.truncate();
|
||||||
this.writeErrs(Map());
|
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
return Error(e);
|
return Error(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
truncate = () => {
|
||||||
|
this.writeErrs(Map());
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const errorLogger = new ErrorLog(new SettingsClient(""));
|
const errorLogger = new SimpleErrorLogger(new SettingsClient(""));
|
||||||
export const ErrorLogger = (): IErrorLogger => {
|
export const ErrorLogger = (): IErrorLogger => {
|
||||||
return errorLogger;
|
return errorLogger;
|
||||||
};
|
};
|
||||||
|
|
|
@ -98,12 +98,8 @@ export class Rows extends React.Component<Props, State, {}> {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
const orderByList =
|
||||||
<div
|
sortBtns.size > 0 ? (
|
||||||
id={this.props.id}
|
|
||||||
style={this.props.style}
|
|
||||||
className={this.props.className}
|
|
||||||
>
|
|
||||||
<div className="margin-b-l">
|
<div className="margin-b-l">
|
||||||
<Flexbox
|
<Flexbox
|
||||||
children={List([
|
children={List([
|
||||||
|
@ -116,6 +112,15 @@ export class Rows extends React.Component<Props, State, {}> {
|
||||||
childrenStyles={List([{ flex: "0 0 auto" }, { flex: "0 0 auto" }])}
|
childrenStyles={List([{ flex: "0 0 auto" }, { flex: "0 0 auto" }])}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
) : null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
id={this.props.id}
|
||||||
|
style={this.props.style}
|
||||||
|
className={this.props.className}
|
||||||
|
>
|
||||||
|
{orderByList}
|
||||||
{bodyRows}
|
{bodyRows}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -6,9 +6,11 @@ import { ICoreState, UIProps, MsgProps } from "./core_state";
|
||||||
import { LoginProps } from "./pane_login";
|
import { LoginProps } from "./pane_login";
|
||||||
import { Flexbox } from "./layout/flexbox";
|
import { Flexbox } from "./layout/flexbox";
|
||||||
import { updater } from "./state_updater";
|
import { updater } from "./state_updater";
|
||||||
import { alertMsg } from "../common/env";
|
import { alertMsg, confirmMsg } from "../common/env";
|
||||||
import { Container } from "./layout/container";
|
import { Container } from "./layout/container";
|
||||||
import { Card } from "./layout/card";
|
import { Card } from "./layout/card";
|
||||||
|
import { Rows, Row } from "./layout/rows";
|
||||||
|
import { ClientErrorV001, ErrorLogger } from "../common/log_error";
|
||||||
export interface Props {
|
export interface Props {
|
||||||
login: LoginProps;
|
login: LoginProps;
|
||||||
msg: MsgProps;
|
msg: MsgProps;
|
||||||
|
@ -138,12 +140,53 @@ export class PaneSettings extends React.Component<Props, State, {}> {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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<Row> => {
|
||||||
|
let errRows = List<Row>();
|
||||||
|
|
||||||
|
ErrorLogger()
|
||||||
|
.readErrs()
|
||||||
|
.forEach((clientErr: ClientErrorV001, sign: string) => {
|
||||||
|
const elem = (
|
||||||
|
<div>
|
||||||
|
<div className="error-inline">{JSON.stringify(clientErr)}</div>
|
||||||
|
<div className="hr"></div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
const val = clientErr;
|
||||||
|
const sortVals = List<string>([]);
|
||||||
|
|
||||||
|
errRows = errRows.push({
|
||||||
|
elem,
|
||||||
|
val,
|
||||||
|
sortVals,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return errRows;
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const errRows = this.prepareErrorRows();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="pane-settings">
|
<div id="pane-settings">
|
||||||
<Container>
|
<Container>
|
||||||
<div id="profile">
|
<div id="profile">
|
||||||
<h5 className="pane-title">{this.props.msg.pkg.get("user.profile")}</h5>
|
<h5 className="pane-title">
|
||||||
|
{this.props.msg.pkg.get("user.profile")}
|
||||||
|
</h5>
|
||||||
|
|
||||||
<div className="hr"></div>
|
<div className="hr"></div>
|
||||||
|
|
||||||
|
@ -372,6 +415,30 @@ export class PaneSettings extends React.Component<Props, State, {}> {
|
||||||
</div>
|
</div>
|
||||||
</Container>
|
</Container>
|
||||||
|
|
||||||
|
<Container>
|
||||||
|
<Flexbox
|
||||||
|
children={List([
|
||||||
|
<h5 className="pane-title">
|
||||||
|
{this.props.msg.pkg.get("error.report.title")}
|
||||||
|
</h5>,
|
||||||
|
|
||||||
|
<span>
|
||||||
|
<button className="margin-r-m" onClick={this.reportErrors}>
|
||||||
|
{this.props.msg.pkg.get("op.submit")}
|
||||||
|
</button>
|
||||||
|
<button onClick={this.truncateErrors}>
|
||||||
|
{this.props.msg.pkg.get("op.truncate")}
|
||||||
|
</button>
|
||||||
|
</span>,
|
||||||
|
])}
|
||||||
|
childrenStyles={List([{}, { justifyContent: "flex-end" }])}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="hr"></div>
|
||||||
|
|
||||||
|
<Rows rows={errRows} sortKeys={List([])} />
|
||||||
|
</Container>
|
||||||
|
|
||||||
{/* <div className="hr"></div>
|
{/* <div className="hr"></div>
|
||||||
<div>
|
<div>
|
||||||
<Flexbox
|
<Flexbox
|
||||||
|
|
|
@ -8,11 +8,11 @@ import { RiArchiveDrawerFill } from "@react-icons/all-files/ri/RiArchiveDrawerFi
|
||||||
import { RiFile2Fill } from "@react-icons/all-files/ri/RiFile2Fill";
|
import { RiFile2Fill } from "@react-icons/all-files/ri/RiFile2Fill";
|
||||||
import { RiFileList2Fill } from "@react-icons/all-files/ri/RiFileList2Fill";
|
import { RiFileList2Fill } from "@react-icons/all-files/ri/RiFileList2Fill";
|
||||||
import { RiCheckboxFill } from "@react-icons/all-files/ri/RiCheckboxFill";
|
import { RiCheckboxFill } from "@react-icons/all-files/ri/RiCheckboxFill";
|
||||||
import { RiCheckboxBlankFill } from "@react-icons/all-files/ri/RiCheckboxBlankFill";
|
|
||||||
import { RiInformationFill } from "@react-icons/all-files/ri/RiInformationFill";
|
import { RiInformationFill } from "@react-icons/all-files/ri/RiInformationFill";
|
||||||
import { BiTable } from "@react-icons/all-files/bi/BiTable";
|
import { BiTable } from "@react-icons/all-files/bi/BiTable";
|
||||||
import { BiListUl } from "@react-icons/all-files/bi/BiListUl";
|
import { BiListUl } from "@react-icons/all-files/bi/BiListUl";
|
||||||
|
|
||||||
|
import { ErrorLogger } from "../common/log_error";
|
||||||
import { alertMsg, confirmMsg } from "../common/env";
|
import { alertMsg, confirmMsg } from "../common/env";
|
||||||
import { getErrMsg } from "../common/utils";
|
import { getErrMsg } from "../common/utils";
|
||||||
import { updater } from "./state_updater";
|
import { updater } from "./state_updater";
|
||||||
|
@ -699,12 +699,12 @@ export class FilesPanel extends React.Component<Props, State, {}> {
|
||||||
};
|
};
|
||||||
|
|
||||||
setView = (opt: string) => {
|
setView = (opt: string) => {
|
||||||
if (opt === "rows" || opt === "table") {
|
if (opt !== "rows" && opt !== "table") {
|
||||||
updater().setControlOption(filesViewCtrl, opt);
|
ErrorLogger().error(`FilesPanel:setView: unknown view ${opt}`);
|
||||||
this.props.update(updater().updateUI);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// TODO: log error
|
updater().setControlOption(filesViewCtrl, opt);
|
||||||
|
this.props.update(updater().updateUI);
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
|
@ -35,6 +35,7 @@ import { controlName as panelTabs } from "./root_frame";
|
||||||
import { settingsTabsCtrl } from "./dialog_settings";
|
import { settingsTabsCtrl } from "./dialog_settings";
|
||||||
import { settingsDialogCtrl } from "./layers";
|
import { settingsDialogCtrl } from "./layers";
|
||||||
import { errUpdater, errServer } from "../common/errors";
|
import { errUpdater, errServer } from "../common/errors";
|
||||||
|
import { ErrorLogger } from "../common/log_error";
|
||||||
|
|
||||||
import { MsgPackage, isValidLanPack } from "../i18n/msger";
|
import { MsgPackage, isValidLanPack } from "../i18n/msger";
|
||||||
|
|
||||||
|
@ -135,8 +136,11 @@ export class Updater {
|
||||||
refreshUploadings = async (): Promise<string> => {
|
refreshUploadings = async (): Promise<string> => {
|
||||||
const luResp = await this.filesClient.listUploadings();
|
const luResp = await this.filesClient.listUploadings();
|
||||||
if (luResp.status !== 200) {
|
if (luResp.status !== 200) {
|
||||||
// TODO: log error
|
// this method is called for authed users
|
||||||
console.error(luResp.data);
|
// other status codes are unexpected, including 401
|
||||||
|
ErrorLogger().error(
|
||||||
|
`refreshUploadings: unexpected response ${luResp.status} ${luResp.data}`
|
||||||
|
);
|
||||||
return errServer;
|
return errServer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -767,16 +771,15 @@ export class Updater {
|
||||||
const url = this.props.login.preferences.lanPackURL;
|
const url = this.props.login.preferences.lanPackURL;
|
||||||
if (url === "") {
|
if (url === "") {
|
||||||
const lan = this.props.login.preferences.lan;
|
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
|
// fallback to build-in language pack
|
||||||
this.props.msg.lan = lan;
|
this.props.msg.lan = lan;
|
||||||
this.props.msg.pkg = MsgPackage.get(lan);
|
this.props.msg.pkg = MsgPackage.get(lan);
|
||||||
} else {
|
} else {
|
||||||
// fallback to english
|
ErrorLogger().error(`syncLan: unexpected lan ${lan}`);
|
||||||
this.props.msg.lan = "en_US";
|
this.props.msg.lan = "en_US";
|
||||||
this.props.msg.pkg = MsgPackage.get("en_US");
|
this.props.msg.pkg = MsgPackage.get("en_US");
|
||||||
}
|
}
|
||||||
// TODO: should warning here
|
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -126,4 +126,8 @@ export const msgs: Map<string, string> = Map({
|
||||||
"item.modTime": "Mod Time",
|
"item.modTime": "Mod Time",
|
||||||
"item.size": "Size",
|
"item.size": "Size",
|
||||||
"item.progress": "Progress",
|
"item.progress": "Progress",
|
||||||
|
"error.report.title": "Error Report",
|
||||||
|
"op.truncate": "Truncate",
|
||||||
|
"op.submit": "Submit",
|
||||||
|
"term.time": "Time",
|
||||||
});
|
});
|
||||||
|
|
|
@ -123,4 +123,8 @@ export const msgs: Map<string, string> = Map({
|
||||||
"item.modTime": "修改时间",
|
"item.modTime": "修改时间",
|
||||||
"item.size": "大小",
|
"item.size": "大小",
|
||||||
"item.progress": "进度",
|
"item.progress": "进度",
|
||||||
|
"error.report.title": "报告错误",
|
||||||
|
"op.truncate": "清空",
|
||||||
|
"op.submit": "提交",
|
||||||
|
"term.time": "时间",
|
||||||
});
|
});
|
||||||
|
|
|
@ -12,6 +12,7 @@ import {
|
||||||
UploadState,
|
UploadState,
|
||||||
} from "./interface";
|
} from "./interface";
|
||||||
import { errUploadMgr } from "../common/errors";
|
import { errUploadMgr } from "../common/errors";
|
||||||
|
import { ErrorLogger } from "../common/log_error";
|
||||||
|
|
||||||
const win: Window = self as any;
|
const win: Window = self as any;
|
||||||
|
|
||||||
|
@ -197,8 +198,7 @@ export class UploadMgr {
|
||||||
err: errResp.err,
|
err: errResp.err,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// TODO: refine this
|
ErrorLogger().error(`respHandler: entry not found ${errResp.err}`);
|
||||||
console.error(`uploading ${errResp.filePath} may already be deleted`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.statusCb(this.infos.toMap(), false);
|
this.statusCb(this.infos.toMap(), false);
|
||||||
|
@ -225,17 +225,16 @@ export class UploadMgr {
|
||||||
this.statusCb(this.infos.toMap(), false);
|
this.statusCb(this.infos.toMap(), false);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// TODO: refine this
|
ErrorLogger().error(
|
||||||
console.error(
|
`respHandler: entry(uploadInfoKind) not found ${infoResp.err}`
|
||||||
`respHandler: may already be deleted: file(${
|
|
||||||
infoResp.filePath
|
|
||||||
}) infos(${this.infos.toObject()})`
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
console.error(`respHandler: response kind not found: ${resp}`);
|
ErrorLogger().error(
|
||||||
|
`respHandler: unknown kind: ${JSON.stringify(resp)}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ import { FilesClient } from "../client/files";
|
||||||
import { IFilesClient, Response, isFatalErr } from "../client";
|
import { IFilesClient, Response, isFatalErr } from "../client";
|
||||||
|
|
||||||
// TODO: get settings from server
|
// TODO: get settings from server
|
||||||
// TODO: move chunk copying to worker
|
|
||||||
const defaultChunkLen = 1024 * 1024 * 1;
|
const defaultChunkLen = 1024 * 1024 * 1;
|
||||||
const speedDownRatio = 0.5;
|
const speedDownRatio = 0.5;
|
||||||
const speedUpRatio = 1.05;
|
const speedUpRatio = 1.05;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue