feat(logs): fetch logs with correct log level

see also #14
This commit is contained in:
Haishan 2019-05-14 00:30:36 +08:00
parent 882b168082
commit 16e61f1533
20 changed files with 716 additions and 542 deletions

View file

@ -49,8 +49,7 @@
"redux-logger": "^3.0.6", "redux-logger": "^3.0.6",
"redux-thunk": "^2.2.0", "redux-thunk": "^2.2.0",
"regenerator-runtime": "^0.13.2", "regenerator-runtime": "^0.13.2",
"reselect": "^4.0.0", "reselect": "^4.0.0"
"whatwg-fetch": "^3.0.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.1.5", "@babel/core": "^7.1.5",
@ -68,7 +67,7 @@
"css-loader": "^2.0.1", "css-loader": "^2.0.1",
"cssnano": "^4.1.7", "cssnano": "^4.1.7",
"eslint": "^5.13.0", "eslint": "^5.13.0",
"eslint-config-react-app": "^3.0.8", "eslint-config-react-app": "^4.0.1",
"eslint-import-resolver-webpack": "^0.11.0", "eslint-import-resolver-webpack": "^0.11.0",
"eslint-plugin-flowtype": "^3.4.2", "eslint-plugin-flowtype": "^3.4.2",
"eslint-plugin-import": "^2.17.2", "eslint-plugin-import": "^2.17.2",
@ -78,14 +77,14 @@
"eslint-plugin-react-hooks": "^1.0.1", "eslint-plugin-react-hooks": "^1.0.1",
"file-loader": "^3.0.0", "file-loader": "^3.0.0",
"html-webpack-plugin": "^3.2.0", "html-webpack-plugin": "^3.2.0",
"husky": "^1.3.0", "husky": "^2.2.0",
"lint-staged": "^8.1.3", "lint-staged": "^8.1.3",
"mini-css-extract-plugin": "^0.6.0", "mini-css-extract-plugin": "^0.6.0",
"postcss-extend-rule": "^2.0.0", "postcss-extend-rule": "^2.0.0",
"postcss-import": "^12.0.1", "postcss-import": "^12.0.1",
"postcss-loader": "^3.0.0", "postcss-loader": "^3.0.0",
"postcss-nested": "^4.1.2", "postcss-nested": "^4.1.2",
"prettier": "^1.16.4", "prettier": "^1.17.1",
"react-hot-loader": "^4.6.3", "react-hot-loader": "^4.6.3",
"style-loader": "^0.23.0", "style-loader": "^0.23.0",
"svg-sprite-loader": "^4.1.2", "svg-sprite-loader": "^4.1.2",

View file

@ -56,20 +56,49 @@ function pump(reader, appendLog) {
}); });
} }
const apiConfigSnapshot = {};
let controller;
function fetchLogs(apiConfig, appendLog) { function fetchLogs(apiConfig, appendLog) {
if (fetched) return; if (
controller &&
(apiConfigSnapshot.hostname !== apiConfig.hostname ||
apiConfigSnapshot.port !== apiConfig.port ||
apiConfigSnapshot.secret !== apiConfig.secret ||
apiConfigSnapshot.logLevel !== apiConfig.logLevel)
) {
controller.abort();
} else if (fetched) {
return;
}
fetched = true; fetched = true;
apiConfigSnapshot.hostname = apiConfig.hostname;
apiConfigSnapshot.port = apiConfig.port;
apiConfigSnapshot.secret = apiConfig.secret;
apiConfigSnapshot.logLevel = apiConfig.logLevel;
controller = new AbortController();
const signal = controller.signal;
const { url, init } = getURLAndInit(apiConfig); const { url, init } = getURLAndInit(apiConfig);
fetch(url + endpoint, init) fetch(url + endpoint + '?level=' + apiConfig.logLevel, {
.then(response => { ...init,
signal
}).then(
response => {
const reader = response.body.getReader(); const reader = response.body.getReader();
pump(reader, appendLog); pump(reader, appendLog);
}) },
.catch(err => { err => {
fetched = false; fetched = false;
if (signal.aborted) return;
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log('GET /logs error', err); console.log('GET /logs error:', err.message);
}); }
);
} }
export { fetchLogs }; export { fetchLogs };

View file

@ -7,6 +7,8 @@ import APIConfig from 'c/APIConfig';
import { closeModal } from 'd/modals'; import { closeModal } from 'd/modals';
import { fetchConfigs } from 'd/configs'; import { fetchConfigs } from 'd/configs';
import { DOES_NOT_SUPPORT_FETCH, errors } from '../misc/errors';
import s0 from './APIDiscovery.module.css'; import s0 from './APIDiscovery.module.css';
const mapStateToProps = s => ({ const mapStateToProps = s => ({
@ -19,11 +21,18 @@ const actions = {
}; };
export default function APIDiscovery() { export default function APIDiscovery() {
if (!window.fetch) {
const { detail } = errors[DOES_NOT_SUPPORT_FETCH];
const err = new Error(detail);
err.code = DOES_NOT_SUPPORT_FETCH;
throw err;
}
const { modals } = useStoreState(mapStateToProps); const { modals } = useStoreState(mapStateToProps);
const { closeModal, fetchConfigs } = useActions(actions); const { closeModal, fetchConfigs } = useActions(actions);
useEffect(() => { useEffect(() => {
fetchConfigs(); fetchConfigs();
}, []); }, [fetchConfigs]);
return ( return (
<Modal <Modal

View file

@ -74,7 +74,7 @@ export default function ConfigContainer() {
const { configs } = useStoreState(mapStateToProps); const { configs } = useStoreState(mapStateToProps);
useEffect(() => { useEffect(() => {
fetchConfigs(); fetchConfigs();
}, []); }, [fetchConfigs]);
return <Config configs={configs} />; return <Config configs={configs} />;
} }

View file

@ -2,6 +2,7 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { getSentry } from '../misc/sentry'; import { getSentry } from '../misc/sentry';
import ErrorBoundaryFallback from 'c/ErrorBoundaryFallback'; import ErrorBoundaryFallback from 'c/ErrorBoundaryFallback';
import { deriveMessageFromError } from '../misc/errors';
// XXX this is no Hook equivalents for componentDidCatch // XXX this is no Hook equivalents for componentDidCatch
// we have to use class for now // we have to use class for now
@ -20,13 +21,18 @@ class ErrorBoundary extends Component {
return this.sentry; return this.sentry;
}; };
// static getDerivedStateFromError(error) {
// return { error };
// }
componentDidMount() { componentDidMount() {
// this.loadSentry(); // this.loadSentry();
} }
componentDidCatch(error, errorInfo) { componentDidCatch(error, _info) {
this.setState({ error });
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log(error, errorInfo); // console.log(error, errorInfo);
// this.setState({ error }); // this.setState({ error });
// this.loadSentry().then(Sentry => { // this.loadSentry().then(Sentry => {
// Sentry.withScope(scope => { // Sentry.withScope(scope => {
@ -44,9 +50,9 @@ class ErrorBoundary extends Component {
render() { render() {
if (this.state.error) { if (this.state.error) {
const { message, detail } = deriveMessageFromError(this.state.error);
//render fallback UI //render fallback UI
// return <a onClick={this.showReportDialog}>Report feedback</a>; return <ErrorBoundaryFallback message={message} detail={detail} />;
return <ErrorBoundaryFallback />;
} else { } else {
return this.props.children; return this.props.children;
} }

View file

@ -1,22 +1,22 @@
import React from 'react'; import React from 'react';
import Icon from 'c/Icon'; import PropTypes from 'prop-types';
import SvgYacd from './SvgYacd'; import SvgYacd from './SvgYacd';
import github from 's/github.svg'; import SvgGithub from './SvgGithub';
import s0 from './ErrorBoundaryFallback.module.css'; import s0 from './ErrorBoundaryFallback.module.css';
const yacdRepoIssueUrl = 'https://github.com/haishanh/yacd/issues'; const yacdRepoIssueUrl = 'https://github.com/haishanh/yacd/issues';
function ErrorBoundaryFallback() { function ErrorBoundaryFallback({ message, detail }) {
return ( return (
<div className={s0.root}> <div className={s0.root}>
<div className={s0.yacd}> <div className={s0.yacd}>
<SvgYacd width={150} height={150} /> <SvgYacd width={150} height={150} />
</div> </div>
<h1>Oops, something went wrong!</h1> {message ? <h1>{message}</h1> : null}
{detail ? <p>{detail}</p> : null}
<p> <p>
If you think this is a bug, reporting this at{' '}
<a className={s0.link} href={yacdRepoIssueUrl}> <a className={s0.link} href={yacdRepoIssueUrl}>
<Icon id={github.id} width={16} height={16} /> <SvgGithub width={16} height={16} />
haishanh/yacd haishanh/yacd
</a> </a>
</p> </p>
@ -24,4 +24,8 @@ function ErrorBoundaryFallback() {
); );
} }
ErrorBoundaryFallback.propTypes = {
message: PropTypes.string
};
export default ErrorBoundaryFallback; export default ErrorBoundaryFallback;

View file

@ -22,9 +22,16 @@
} }
.link { .link {
display: inline-flex;
align-items: center;
color: var(--color-text-secondary); color: var(--color-text-secondary);
&:hover, &:hover,
&:active { &:active {
color: #387cec; color: #387cec;
} }
svg {
margin-right: 5px;
}
} }

View file

@ -12,6 +12,7 @@ import useRemainingViewPortHeight from '../hooks/useRemainingViewPortHeight';
import { fetchLogs } from '../api/logs'; import { fetchLogs } from '../api/logs';
import LogSearch from './LogSearch'; import LogSearch from './LogSearch';
import { getLogsForDisplay, appendLog } from 'd/logs'; import { getLogsForDisplay, appendLog } from 'd/logs';
import { getLogLevel } from 'd/configs';
import s0 from 'c/Logs.module.css'; import s0 from 'c/Logs.module.css';
const paddingBottom = 30; const paddingBottom = 30;
@ -66,10 +67,11 @@ export default function Logs() {
const { hostname, port, secret } = useStoreState(getClashAPIConfig); const { hostname, port, secret } = useStoreState(getClashAPIConfig);
const { appendLog } = useActions(actions); const { appendLog } = useActions(actions);
const logs = useStoreState(getLogsForDisplay); const logs = useStoreState(getLogsForDisplay);
const logLevel = useStoreState(getLogLevel);
useEffect(() => { useEffect(() => {
fetchLogs({ hostname, port, secret }, appendLog); fetchLogs({ hostname, port, secret, logLevel }, appendLog);
}, [hostname, port, secret]); }, [hostname, port, secret, logLevel, appendLog]);
const [refLogsContainer, containerHeight] = useRemainingViewPortHeight(); const [refLogsContainer, containerHeight] = useRemainingViewPortHeight();
return ( return (

View file

@ -26,13 +26,12 @@ const actions = {
export default function Proxies() { export default function Proxies() {
const { fetchProxies, requestDelayAll } = useActions(actions); const { fetchProxies, requestDelayAll } = useActions(actions);
const fn = async () => { useEffect(() => {
(async () => {
await fetchProxies(); await fetchProxies();
await requestDelayAll(); await requestDelayAll();
}; })();
useEffect(() => { }, [fetchProxies, requestDelayAll]);
fn();
}, []);
const { groupNames } = useStoreState(mapStateToProps); const { groupNames } = useStoreState(mapStateToProps);
return ( return (

View file

@ -41,7 +41,7 @@ export default function Rules() {
const { rules } = useStoreState(mapStateToProps); const { rules } = useStoreState(mapStateToProps);
useEffect(() => { useEffect(() => {
fetchRulesOnce(); fetchRulesOnce();
}, []); }, [fetchRulesOnce]);
const [refRulesContainer, containerHeight] = useRemainingViewPortHeight(); const [refRulesContainer, containerHeight] = useRemainingViewPortHeight();
return ( return (

View file

@ -0,0 +1,25 @@
import React from 'react';
import PropTypes from 'prop-types';
export default function SvgGithub({ width = 24, height = 24 } = {}) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width={width}
height={height}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22" />
</svg>
);
}
SvgGithub.propTypes = {
width: PropTypes.number,
height: PropTypes.number
};

View file

@ -35,7 +35,7 @@ export default function TrafficChart({ id }) {
} }
] ]
}), }),
[] [id]
); );
const eleId = 'chart-' + id; const eleId = 'chart-' + id;

View file

@ -8,6 +8,7 @@ const OptimisticUpdateConfigs = 'configs/OptimisticUpdateConfigs';
const MarkHaveFetchedConfig = 'configs/MarkHaveFetchedConfig'; const MarkHaveFetchedConfig = 'configs/MarkHaveFetchedConfig';
export const getConfigs = s => s.configs; export const getConfigs = s => s.configs;
export const getLogLevel = s => s.configs['log-level'];
export function fetchConfigs() { export function fetchConfigs() {
return async (dispatch, getState) => { return async (dispatch, getState) => {
@ -103,7 +104,7 @@ const initialState = {
'redir-port': 0, 'redir-port': 0,
'allow-lan': false, 'allow-lan': false,
mode: 'Rule', mode: 'Rule',
'log-level': 'info', 'log-level': 'silent',
///// /////
haveFetchedConfig: false haveFetchedConfig: false

View file

@ -24,5 +24,5 @@ export default function useLineChart(
unsubscribe && unsubscribe(); unsubscribe && unsubscribe();
c.destroy(); c.destroy();
}; };
}, [Chart, elementId, data, subscription]); }, [Chart, elementId, data, subscription, extraChartOptions]);
} }

View file

@ -21,7 +21,7 @@ export default function useRemainingViewPortHeight() {
return () => { return () => {
window.removeEventListener('resize', updateContainerHeight); window.removeEventListener('resize', updateContainerHeight);
}; };
}, []); }, [updateContainerHeight]);
return [refRulesContainer, containerHeight]; return [refRulesContainer, containerHeight];
} }

19
src/misc/errors.js Normal file
View file

@ -0,0 +1,19 @@
export const DOES_NOT_SUPPORT_FETCH = 0;
export const errors = {
[DOES_NOT_SUPPORT_FETCH]: {
message: 'Browser not supported!',
detail: 'This browser does not support "fetch", please choose another one.'
},
default: {
message: 'Oops, something went wrong!'
}
};
export function deriveMessageFromError(err) {
const { code } = err;
if (typeof code === 'number') {
return errors[code];
}
return errors.default;
}

View file

@ -35,7 +35,7 @@ function bindActions(actions, dispatch) {
export function useActions(actions) { export function useActions(actions) {
const { dispatch } = useStore(); const { dispatch } = useStore();
return useMemo(() => bindActions(actions, dispatch), [actions]); return useMemo(() => bindActions(actions, dispatch), [actions, dispatch]);
} }
export function useStoreState(selector) { export function useStoreState(selector) {
@ -52,6 +52,6 @@ export function useStoreState(selector) {
compStateCurr = compStateNext; compStateCurr = compStateNext;
setCompState(compStateNext); setCompState(compStateNext);
}); });
}, []); }, [compState, selector, store]);
return compState; return compState;
} }

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-github"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path></svg>

Before

Width:  |  Height:  |  Size: 527 B

View file

@ -27,7 +27,7 @@ const svgSpriteRule = {
// ---- entry // ---- entry
const entry = { const entry = {
app: ['whatwg-fetch', './src/app.js'] app: ['./src/app.js']
}; };
// ---- output // ---- output

1077
yarn.lock

File diff suppressed because it is too large Load diff