feat: support view rules

This commit is contained in:
Haishan 2019-01-04 00:22:51 +08:00
parent 89289f8e3b
commit 6987f4f25b
11 changed files with 193 additions and 9 deletions

View file

@ -37,6 +37,7 @@
"chart.js": "^2.7.3", "chart.js": "^2.7.3",
"classnames": "^2.2.6", "classnames": "^2.2.6",
"history": "^4.7.2", "history": "^4.7.2",
"invariant": "^2.2.4",
"memoize-one": "^5.0.0", "memoize-one": "^5.0.0",
"modern-normalize": "^0.5.0", "modern-normalize": "^0.5.0",
"prop-types": "^15.5.10", "prop-types": "^15.5.10",

8
src/api/rules.js Normal file
View file

@ -0,0 +1,8 @@
import { getURLAndInit } from 'm/request-helper';
const endpoint = '/rules';
export async function fetchRules(apiConfig) {
const { url, init } = getURLAndInit(apiConfig);
return await fetch(url + endpoint, init);
}

View file

@ -25,15 +25,17 @@ class ErrorBoundary extends Component {
} }
componentDidCatch(error, errorInfo) { componentDidCatch(error, errorInfo) {
this.setState({ error }); // eslint-disable-next-line no-console
this.loadSentry().then(Sentry => { console.log(error, errorInfo);
Sentry.withScope(scope => { // this.setState({ error });
Object.keys(errorInfo).forEach(key => { // this.loadSentry().then(Sentry => {
scope.setExtra(key, errorInfo[key]); // Sentry.withScope(scope => {
}); // Object.keys(errorInfo).forEach(key => {
Sentry.captureException(error); // scope.setExtra(key, errorInfo[key]);
}); // });
}); // Sentry.captureException(error);
// });
// });
} }
showReportDialog = () => { showReportDialog = () => {

View file

@ -9,6 +9,7 @@ import SideBar from 'c/SideBar';
import Home from 'c/Home'; import Home from 'c/Home';
import Logs from 'c/Logs'; import Logs from 'c/Logs';
import Proxies from 'c/Proxies'; import Proxies from 'c/Proxies';
import Rules from 'c/Rules';
import Config from 'c/Config'; import Config from 'c/Config';
import APIDiscovery from 'c/APIDiscovery'; import APIDiscovery from 'c/APIDiscovery';
@ -36,6 +37,7 @@ const Root = () => (
<Route exact path="/configs" render={() => <Config />} /> <Route exact path="/configs" render={() => <Config />} />
<Route exact path="/logs" render={() => <Logs />} /> <Route exact path="/logs" render={() => <Logs />} />
<Route exact path="/proxies" render={() => <Proxies />} /> <Route exact path="/proxies" render={() => <Proxies />} />
<Route exact path="/rules" render={() => <Rules />} />
</div> </div>
</div> </div>
</Router> </Router>

45
src/components/Rule.js Normal file
View file

@ -0,0 +1,45 @@
import React from 'react';
import PropTypes from 'prop-types';
import s0 from './Rule.module.scss';
const colorMap = {
_default: '#59caf9',
DIRECT: '#f5bc41',
REJECT: '#cb3166'
};
function getStyleFor({ proxy }) {
let color = colorMap._default;
if (colorMap[proxy]) {
color = colorMap[proxy];
}
return { color };
}
function Rule({ type, payload, proxy, id }) {
const styleProxy = getStyleFor({ proxy });
return (
<div className={s0.rule}>
<div className={s0.left}>{id}</div>
<div>
<div className={s0.b}>{payload}</div>
<div className={s0.a}>
<div className={s0.type}>{type}</div>
<div className={s0.proxy} style={styleProxy}>
{proxy}
</div>
</div>
</div>
</div>
);
}
Rule.propTypes = {
id: PropTypes.number,
type: PropTypes.string,
payload: PropTypes.string,
proxy: PropTypes.string
};
export default Rule;

View file

@ -0,0 +1,38 @@
.rule {
display: flex;
align-items: center;
padding: 10px 40px;
}
.left {
width: 40px;
padding-right: 15px;
color: var(--color-text-secondary);
opacity: 0.4;
}
.a {
display: flex;
align-items: center;
font-size: 12px;
opacity: 0.8;
}
.b {
padding: 10px 0;
font-family: 'Roboto Mono', Menlo, monospace;
font-size: 19px;
}
.type {
width: 110px;
}
.proxy {
// background: #f5bc41;
// background: #eee;
// color: #eee;
// padding: 5px;
// border-radius: 5px;
}

34
src/components/Rules.js Normal file
View file

@ -0,0 +1,34 @@
import React, { useEffect } from 'react';
import { useActions, useStoreState } from 'm/store';
import ContentHeader from 'c/ContentHeader';
import Rule from 'c/Rule';
import { getRules, fetchRules } from 'd/rules';
const mapStateToProps = s => ({
rules: getRules(s)
});
const actions = {
fetchRules
};
export default function Rules() {
const { fetchRules } = useActions(actions);
useEffect(() => {
fetchRules();
}, []);
const { rules } = useStoreState(mapStateToProps);
return (
<div>
<ContentHeader title="Rules" />
<div style={{ paddingBottom: 30 }}>
{rules.map(r => {
return <Rule key={r.id} {...r} />;
})}
</div>
</div>
);
}

View file

@ -11,6 +11,7 @@ import activity from 's/activity.svg';
import settings from 's/settings.svg'; import settings from 's/settings.svg';
import globe from 's/globe.svg'; import globe from 's/globe.svg';
import file from 's/file.svg'; import file from 's/file.svg';
import command from 's/command.svg';
import yacd from 's/yacd.svg'; import yacd from 's/yacd.svg';
import moon from 's/moon.svg'; import moon from 's/moon.svg';
@ -51,6 +52,7 @@ function SideBar() {
<div className={s.rows}> <div className={s.rows}>
<SideBarRow to="/" iconId={activity.id} labelText="Overview" /> <SideBarRow to="/" iconId={activity.id} labelText="Overview" />
<SideBarRow to="/proxies" iconId={globe.id} labelText="Proxies" /> <SideBarRow to="/proxies" iconId={globe.id} labelText="Proxies" />
<SideBarRow to="/rules" iconId={command.id} labelText="Rules" />
<SideBarRow to="/configs" iconId={settings.id} labelText="Config" /> <SideBarRow to="/configs" iconId={settings.id} labelText="Config" />
<SideBarRow to="/logs" iconId={file.id} labelText="Logs" /> <SideBarRow to="/logs" iconId={file.id} labelText="Logs" />
</div> </div>

View file

@ -2,11 +2,13 @@ import { combineReducers } from 'redux';
import app from './app'; import app from './app';
import modals from './modals'; import modals from './modals';
import proxies from './proxies'; import proxies from './proxies';
import rules from './rules';
import configs from './configs'; import configs from './configs';
export default combineReducers({ export default combineReducers({
app, app,
modals, modals,
proxies, proxies,
rules,
configs configs
}); });

49
src/ducks/rules.js Normal file
View file

@ -0,0 +1,49 @@
import * as rulesAPI from 'a/rules';
import { getClashAPIConfig } from 'd/app';
import invariant from 'invariant';
// import { createSelector } from 'reselect';
export const getRules = s => s.rules.allRules;
const CompletedFetchRules = 'rules/CompletedFetchRules';
export function fetchRules() {
return async (dispatch, getState) => {
const apiSetup = getClashAPIConfig(getState());
const res = await rulesAPI.fetchRules(apiSetup);
const json = await res.json();
invariant(
json.rules && json.rules.length >= 0,
'there is no valid rules list in the rules API response'
);
// attach an id
const allRules = json.rules.map((r, i) => {
r.id = i;
return r;
});
dispatch({
type: CompletedFetchRules,
payload: { allRules }
});
};
}
// {"type":"FINAL","payload":"","proxy":"Proxy"}
// {"type":"IPCIDR","payload":"172.16.0.0/12","proxy":"DIRECT"}
const initialState = {
// filteredRules: [],
allRules: []
};
export default function reducer(state = initialState, { type, payload }) {
switch (type) {
case CompletedFetchRules: {
return { ...state, ...payload };
}
default:
return state;
}
}

1
src/svg/command.svg Normal file
View file

@ -0,0 +1 @@
<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"><path d="M18 3a3 3 0 0 0-3 3v12a3 3 0 0 0 3 3 3 3 0 0 0 3-3 3 3 0 0 0-3-3H6a3 3 0 0 0-3 3 3 3 0 0 0 3 3 3 3 0 0 0 3-3V6a3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3h12a3 3 0 0 0 3-3 3 3 0 0 0-3-3z"></path></svg>

After

Width:  |  Height:  |  Size: 390 B