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",
|
||||
"classnames": "^2.2.6",
|
||||
"history": "^4.7.2",
|
||||
"invariant": "^2.2.4",
|
||||
"memoize-one": "^5.0.0",
|
||||
"modern-normalize": "^0.5.0",
|
||||
"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) {
|
||||
this.setState({ error });
|
||||
this.loadSentry().then(Sentry => {
|
||||
Sentry.withScope(scope => {
|
||||
Object.keys(errorInfo).forEach(key => {
|
||||
scope.setExtra(key, errorInfo[key]);
|
||||
});
|
||||
Sentry.captureException(error);
|
||||
});
|
||||
});
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error, errorInfo);
|
||||
// this.setState({ error });
|
||||
// this.loadSentry().then(Sentry => {
|
||||
// Sentry.withScope(scope => {
|
||||
// Object.keys(errorInfo).forEach(key => {
|
||||
// scope.setExtra(key, errorInfo[key]);
|
||||
// });
|
||||
// Sentry.captureException(error);
|
||||
// });
|
||||
// });
|
||||
}
|
||||
|
||||
showReportDialog = () => {
|
||||
|
|
|
@ -9,6 +9,7 @@ import SideBar from 'c/SideBar';
|
|||
import Home from 'c/Home';
|
||||
import Logs from 'c/Logs';
|
||||
import Proxies from 'c/Proxies';
|
||||
import Rules from 'c/Rules';
|
||||
import Config from 'c/Config';
|
||||
|
||||
import APIDiscovery from 'c/APIDiscovery';
|
||||
|
@ -36,6 +37,7 @@ const Root = () => (
|
|||
<Route exact path="/configs" render={() => <Config />} />
|
||||
<Route exact path="/logs" render={() => <Logs />} />
|
||||
<Route exact path="/proxies" render={() => <Proxies />} />
|
||||
<Route exact path="/rules" render={() => <Rules />} />
|
||||
</div>
|
||||
</div>
|
||||
</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 globe from 's/globe.svg';
|
||||
import file from 's/file.svg';
|
||||
import command from 's/command.svg';
|
||||
import yacd from 's/yacd.svg';
|
||||
import moon from 's/moon.svg';
|
||||
|
||||
|
@ -51,6 +52,7 @@ function SideBar() {
|
|||
<div className={s.rows}>
|
||||
<SideBarRow to="/" iconId={activity.id} labelText="Overview" />
|
||||
<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="/logs" iconId={file.id} labelText="Logs" />
|
||||
</div>
|
||||
|
|
|
@ -2,11 +2,13 @@ import { combineReducers } from 'redux';
|
|||
import app from './app';
|
||||
import modals from './modals';
|
||||
import proxies from './proxies';
|
||||
import rules from './rules';
|
||||
import configs from './configs';
|
||||
|
||||
export default combineReducers({
|
||||
app,
|
||||
modals,
|
||||
proxies,
|
||||
rules,
|
||||
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