feat: support view rules
This commit is contained in:
parent
89289f8e3b
commit
6987f4f25b
11 changed files with 193 additions and 9 deletions
|
@ -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
8
src/api/rules.js
Normal 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);
|
||||||
|
}
|
|
@ -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 = () => {
|
||||||
|
|
|
@ -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
45
src/components/Rule.js
Normal 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;
|
38
src/components/Rule.module.scss
Normal file
38
src/components/Rule.module.scss
Normal 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
34
src/components/Rules.js
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
|
@ -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>
|
||||||
|
|
|
@ -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
49
src/ducks/rules.js
Normal 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
1
src/svg/command.svg
Normal 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 |
Loading…
Reference in a new issue