From 15bc0f69a8367a57fa1bf263e615285349ad4ab9 Mon Sep 17 00:00:00 2001 From: Haishan Date: Sun, 13 Sep 2020 16:34:18 +0800 Subject: [PATCH] feat: multi backends management --- .eslintrc.yml | 1 + package.json | 24 ++-- src/components/APIConfig.module.css | 2 +- src/components/APIConfig.tsx | 24 +++- src/components/APIDiscovery.module.css | 2 +- src/components/BackendList.module.css | 99 ++++++++++++++ src/components/BackendList.tsx | 147 ++++++++++++++++++++ src/components/Config.js | 8 +- src/components/ErrorBoundary.js | 40 +----- src/components/Root.js | 58 +++++--- src/components/TrafficChart.js | 3 +- src/store/app.js | 110 +++++++++++---- src/store/configs.js | 33 ++--- src/store/index.js | 4 + yarn.lock | 180 +++++++++++++------------ 15 files changed, 517 insertions(+), 218 deletions(-) create mode 100644 src/components/BackendList.module.css create mode 100644 src/components/BackendList.tsx diff --git a/.eslintrc.yml b/.eslintrc.yml index c98f60c..de8d215 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -27,6 +27,7 @@ rules: '@typescript-eslint/explicit-function-return-type': 'off' '@typescript-eslint/no-explicit-any': 'off' '@typescript-eslint/camelcase': 'off' + 'no-use-before-define': 'off' '@typescript-eslint/no-unused-vars': - 'error' - argsIgnorePattern: '^_' diff --git a/package.json b/package.json index 64d17ee..3f70b43 100644 --- a/package.json +++ b/package.json @@ -34,14 +34,14 @@ "dependencies": { "@babel/runtime": "^7.11.2", "@hsjs/react-cache": "0.0.0-alpha.aa94237", - "@sentry/browser": "^5.22.3", + "@sentry/browser": "^5.23.0", "chart.js": "^2.9.2", "clsx": "^1.1.0", "core-js": "^3.6.2", "date-fns": "^2.16.0", - "framer-motion": "^2.6.7", + "framer-motion": "^2.6.13", "history": "^5.0.0", - "immer": "^7.0.8", + "immer": "^7.0.9", "invariant": "^2.2.4", "lodash-es": "^4.17.14", "memoize-one": "^5.1.1", @@ -52,7 +52,7 @@ "react-feather": "^2.0.3", "react-icons": "^3.10.0", "react-modal": "^3.11.1", - "react-query": "^2.15.4", + "react-query": "^2.17.0", "react-router": "6.0.0-beta.0", "react-router-dom": "6.0.0-beta.0", "react-switch": "^5.0.1", @@ -92,37 +92,37 @@ "copy-webpack-plugin": "^6.0.4", "css-loader": "^4.3.0", "cssnano": "^4.1.7", - "eslint": "^7.6.0", + "eslint": "^7.9.0", "eslint-config-airbnb-base": "^14.1.0", "eslint-config-prettier": "^6.11.0", "eslint-config-react-app": "^5.2.1", "eslint-import-resolver-webpack": "^0.12.2", - "eslint-plugin-flowtype": "^5.1.0", + "eslint-plugin-flowtype": "^5.2.0", "eslint-plugin-import": "^2.22.0", - "eslint-plugin-jest": "^24.0.0", + "eslint-plugin-jest": "^24.0.1", "eslint-plugin-jsx-a11y": "^6.3.1", "eslint-plugin-react": "^7.20.6", - "eslint-plugin-react-hooks": "^4.0.8", + "eslint-plugin-react-hooks": "^4.1.2", "eslint-plugin-simple-import-sort": "^5.0.3", "file-loader": "^6.0.0", "fork-ts-checker-notifier-webpack-plugin": "^3.0.0", - "fork-ts-checker-webpack-plugin": "^5.0.14", + "fork-ts-checker-webpack-plugin": "^5.2.0", "html-webpack-plugin": "^4.3.0", "husky": "^4.3.0", "lint-staged": "^10.2.13", - "mini-css-extract-plugin": "^0.11.0", + "mini-css-extract-plugin": "^0.11.2", "postcss": "^7.0.32", "postcss-custom-media": "^7.0.8", "postcss-extend-rule": "^3.0.0", "postcss-import": "^12.0.1", - "postcss-loader": "^4.0.0", + "postcss-loader": "^4.0.1", "postcss-nested": "^4.2.3", "postcss-simple-vars": "^5.0.2", "prettier": "^2.1.1", "react-refresh": "^0.8.2", "resize-observer-polyfill": "^1.5.1", "style-loader": "^1.2.1", - "terser-webpack-plugin": "^4.1.0", + "terser-webpack-plugin": "^4.2.0", "ts-loader": "^8.0.3", "typescript": "^4.0.2", "webpack": "^4.44.1", diff --git a/src/components/APIConfig.module.css b/src/components/APIConfig.module.css index 1092aed..afc2d53 100644 --- a/src/components/APIConfig.module.css +++ b/src/components/APIConfig.module.css @@ -20,7 +20,7 @@ } .body { - padding: 30px 0 0; + padding: 15px 0 0; } .hostnamePort { diff --git a/src/components/APIConfig.tsx b/src/components/APIConfig.tsx index 88bec8b..3befa5c 100644 --- a/src/components/APIConfig.tsx +++ b/src/components/APIConfig.tsx @@ -1,8 +1,9 @@ import * as React from 'react'; import { fetchConfigs } from 'src/api/configs'; +import { BackendList } from 'src/components/BackendList'; import { ClashAPIConfig } from 'src/types'; -import { getClashAPIConfig, updateClashAPIConfig } from '../store/app'; +import { addClashAPIConfig, getClashAPIConfig } from '../store/app'; import s0 from './APIConfig.module.css'; import Button from './Button'; import Field from './Field'; @@ -16,9 +17,9 @@ const mapState = (s) => ({ apiConfig: getClashAPIConfig(s), }); -function APIConfig({ apiConfig, dispatch }) { - const [baseURL, setBaseURL] = useState(apiConfig.baseURL); - const [secret, setSecret] = useState(apiConfig.secret); +function APIConfig({ dispatch }) { + const [baseURL, setBaseURL] = useState(''); + const [secret, setSecret] = useState(''); const [errMsg, setErrMsg] = useState(''); const userTouchedFlagRef = useRef(false); @@ -47,14 +48,21 @@ function APIConfig({ apiConfig, dispatch }) { if (ret[0] !== Ok) { setErrMsg(ret[1]); } else { - dispatch(updateClashAPIConfig({ baseURL, secret })); + dispatch(addClashAPIConfig({ baseURL, secret })); } }); }, [baseURL, secret, dispatch]); const handleContentOnKeyDown = useCallback( - (e) => { + (e: React.KeyboardEvent) => { + if ( + e.target instanceof Element && + (!e.target.tagName || e.target.tagName.toUpperCase() !== 'INPUT') + ) { + return; + } if (e.key !== 'Enter') return; + onConfirm(); }, [onConfirm] @@ -90,8 +98,10 @@ function APIConfig({ apiConfig, dispatch }) {
{errMsg ? errMsg : null}
-
+
+
); } diff --git a/src/components/APIDiscovery.module.css b/src/components/APIDiscovery.module.css index f2aaf71..6c1295a 100644 --- a/src/components/APIDiscovery.module.css +++ b/src/components/APIDiscovery.module.css @@ -12,11 +12,11 @@ display: flex; justify-content: center; + overflow-y: auto; } .container { position: relative; - top: 10%; margin-left: 20px; margin-right: 20px; } diff --git a/src/components/BackendList.module.css b/src/components/BackendList.module.css new file mode 100644 index 0000000..1de1972 --- /dev/null +++ b/src/components/BackendList.module.css @@ -0,0 +1,99 @@ +.ul { + position: relative; + margin: 0; + padding: 0; + list-style: none; + line-height: 1.8; + + --width-max-content: 230px; +} + +.li { + position: relative; + margin: 5px 0; + padding: 10px 0; + border-radius: 10px; + display: grid; + place-content: center; + grid-template-columns: 40px 1fr 40px; + grid-template-rows: 30px; + grid-template-areas: 'close url .'; + column-gap: 10px; +} + +.li:hover { + background-color: var(--bg-near-transparent); +} + +.close { + opacity: 0; + grid-area: close; + place-self: center; +} + +.li:hover .close, +.li:hover .eye { + opacity: 1; +} +.close:focus, +.eye:focus { + opacity: 1; +} + +.hasSecret { + grid-template-rows: repeat(2, 30px); + grid-template-areas: + 'close url .' + 'close secret eye'; +} + +.url { + grid-area: url; +} +.secret { + grid-area: secret; +} +.eye { + grid-area: eye; + opacity: 0; + place-self: center; + cursor: pointer; +} + +.url, +.secret { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.btn { + outline: none; + appearance: none; + border: 1px solid transparent; + background-color: transparent; + color: inherit; + display: flex; + align-items: center; + padding: 5px; + border-radius: 100px; +} +.btn:focus { + border-color: var(--color-focus-blue); +} +.btn:hover:enabled { + background-color: var(--color-focus-blue); +} +.btn:active:enabled { + transform: scale(0.97); +} +.btn:disabled { + color: var(--color-text-secondary); +} + +.url { + cursor: pointer; +} +.url:hover { + color: var(--color-text-highlight); +} diff --git a/src/components/BackendList.tsx b/src/components/BackendList.tsx new file mode 100644 index 0000000..a0c993f --- /dev/null +++ b/src/components/BackendList.tsx @@ -0,0 +1,147 @@ +import cx from 'clsx'; +import * as React from 'react'; +import { Eye, EyeOff, X as Close } from 'react-feather'; +import { useToggle } from 'src/hooks/basic'; +import { + getClashAPIConfigs, + getSelectedClashAPIConfigIndex, +} from 'src/store/app'; +import { ClashAPIConfig } from 'src/types'; + +import s from './BackendList.module.css'; +import { connect, useStoreActions } from './StateProvider'; + +type Config = ClashAPIConfig & { addedAt: number }; + +const mapState = (s) => ({ + apiConfigs: getClashAPIConfigs(s), + selectedClashAPIConfigIndex: getSelectedClashAPIConfigIndex(s), +}); + +export const BackendList = connect(mapState)(BackendListImpl); + +function BackendListImpl({ + apiConfigs, + selectedClashAPIConfigIndex, +}: { + apiConfigs: Config[]; + selectedClashAPIConfigIndex: number; +}) { + const { + app: { removeClashAPIConfig, selectClashAPIConfig }, + } = useStoreActions(); + + const onRemove = React.useCallback( + (conf: ClashAPIConfig) => { + removeClashAPIConfig(conf); + }, + [removeClashAPIConfig] + ); + const onSelect = React.useCallback( + (conf: ClashAPIConfig) => { + selectClashAPIConfig(conf); + }, + [selectClashAPIConfig] + ); + + return ( + <> + + + ); +} + +function Item({ + baseURL, + secret, + disableRemove, + onRemove, + onSelect, +}: { + baseURL: string; + secret: string; + disableRemove: boolean; + onRemove: (x: ClashAPIConfig) => void; + onSelect: (x: ClashAPIConfig) => void; +}) { + const [show, toggle] = useToggle(); + const Icon = show ? EyeOff : Eye; + + const handleTap = React.useCallback((e: React.KeyboardEvent) => { + e.stopPropagation(); + }, []); + + return ( + <> + + onSelect({ baseURL, secret })} + onKeyUp={handleTap} + > + {baseURL} + + + {secret ? ( + <> + {show ? secret : '***'} + + + + ) : null} + + ); +} + +function Button({ + children, + onClick, + className, + disabled, +}: { + children: React.ReactNode; + + onClick?: (e: React.MouseEvent) => unknown; + className: string; + disabled?: boolean; +}) { + return ( + + ); +} diff --git a/src/components/Config.js b/src/components/Config.js index d5850d1..f14f7d9 100644 --- a/src/components/Config.js +++ b/src/components/Config.js @@ -2,12 +2,12 @@ import PropTypes from 'prop-types'; import React from 'react'; import { - clearStorage, getClashAPIConfig, getLatencyTestUrl, getSelectedChartStyleIndex, } from '../store/app'; import { fetchConfigs, getConfigs, updateConfigs } from '../store/configs'; +import { openModal } from '../store/modals'; import Button from './Button'; import s0 from './Config.module.css'; import ContentHeader from './ContentHeader'; @@ -104,6 +104,10 @@ function ConfigImpl({ refConfigs.current = configs; }, [configs]); + const openAPIConfigModal = useCallback(() => { + dispatch(openModal('apiConfig')); + }, [dispatch]); + const setConfigState = useCallback( (name, val) => { setConfigStateInternal({ @@ -256,7 +260,7 @@ function ConfigImpl({
Action
-
diff --git a/src/components/ErrorBoundary.js b/src/components/ErrorBoundary.js index cc3898e..ff49e1e 100644 --- a/src/components/ErrorBoundary.js +++ b/src/components/ErrorBoundary.js @@ -1,13 +1,10 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; +// import { getSentry } from '../misc/sentry'; import { deriveMessageFromError } from '../misc/errors'; -import { getSentry } from '../misc/sentry'; import ErrorBoundaryFallback from './ErrorBoundaryFallback'; -// XXX this is no Hook equivalents for componentDidCatch -// we have to use class for now - class ErrorBoundary extends Component { static propTypes = { children: PropTypes.node, @@ -15,44 +12,13 @@ class ErrorBoundary extends Component { state = { error: null }; - loadSentry = async () => { - if (this.sentry) return this.sentry; - const x = await getSentry(); - this.sentry = x; - return this.sentry; - }; - - // static getDerivedStateFromError(error) { - // return { error }; - // } - - componentDidMount() { - // this.loadSentry(); + static getDerivedStateFromError(error) { + return { error }; } - componentDidCatch(error, _info) { - this.setState({ 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 = () => { - this.loadSentry().then((Sentry) => Sentry.showReportDialog()); - }; - render() { if (this.state.error) { const { message, detail } = deriveMessageFromError(this.state.error); - //render fallback UI return ; } else { return this.props.children; diff --git a/src/components/Root.js b/src/components/Root.js index 6c13163..8c06100 100644 --- a/src/components/Root.js +++ b/src/components/Root.js @@ -1,11 +1,12 @@ import './Root.css'; import React, { lazy, Suspense } from 'react'; -import { HashRouter as Router, Route, Routes } from 'react-router-dom'; +import { HashRouter as Router, useRoutes } from 'react-router-dom'; import { RecoilRoot } from 'recoil'; import { About } from 'src/components/about/About'; import { actions, initialState } from '../store'; +import APIConfig from './APIConfig'; import APIDiscovery from './APIDiscovery'; import ErrorBoundary from './ErrorBoundary'; import Home from './Home'; @@ -52,33 +53,50 @@ const Rules = lazy(() => ); const routes = [ - ['home', '/', ], - ['connections', '/connections', ], - ['configs', '/configs', ], - ['logs', '/logs', ], - ['proxies', '/proxies', ], - ['rules', '/rules', ], - ['about', '/about', ], - __DEV__ ? ['style', '/style', ] : false, + { path: '/', element: }, + { path: '/connections', element: }, + { path: '/configs', element: }, + { path: '/logs', element: }, + { path: '/proxies', element: }, + { path: '/rules', element: }, + { path: '/about', element: }, + __DEV__ ? { path: '/style', element: } : false, ].filter(Boolean); +function RouteInnerApp() { + return useRoutes(routes); +} + +function SideBarApp() { + return ( + <> + + +
+ }> + + +
+ + ); +} + +function App() { + return useRoutes([ + { path: '/backend', element: }, + { path: '*', element: }, + ]); +} + const Root = () => (
- - -
- }> - - {routes.map(([key, path, element]) => ( - - ))} - - -
+ }> + +
diff --git a/src/components/TrafficChart.js b/src/components/TrafficChart.js index 18bda77..bcfd4dc 100644 --- a/src/components/TrafficChart.js +++ b/src/components/TrafficChart.js @@ -25,8 +25,7 @@ export default connect(mapState)(TrafficChart); function TrafficChart({ apiConfig, selectedChartStyleIndex }) { const Chart = chartJSResource.read(); - const { hostname, port, secret } = apiConfig; - const traffic = fetchData({ hostname, port, secret }); + const traffic = fetchData(apiConfig); const data = useMemo( () => ({ labels: traffic.labels, diff --git a/src/store/app.js b/src/store/app.js index dc0e269..84ef96b 100644 --- a/src/store/app.js +++ b/src/store/app.js @@ -1,4 +1,4 @@ -import { clearState, loadState, saveState } from '../misc/storage'; +import { loadState, saveState } from '../misc/storage'; import { debounce, trimTrailingSlash } from '../misc/utils'; import { fetchConfigs } from './configs'; import { closeModal } from './modals'; @@ -7,6 +7,9 @@ export const getClashAPIConfig = (s) => { const idx = s.app.selectedClashAPIConfigIndex; return s.app.clashAPIConfigs[idx]; }; +export const getSelectedClashAPIConfigIndex = (s) => + s.app.selectedClashAPIConfigIndex; +export const getClashAPIConfigs = (s) => s.app.clashAPIConfigs; export const getTheme = (s) => s.app.theme; export const getSelectedChartStyleIndex = (s) => s.app.selectedChartStyleIndex; export const getLatencyTestUrl = (s) => s.app.latencyTestUrl; @@ -17,6 +20,66 @@ export const getAutoCloseOldConns = (s) => s.app.autoCloseOldConns; const saveStateDebounced = debounce(saveState, 600); +function findClashAPIConfigIndex(getState, { baseURL, secret }) { + const arr = getClashAPIConfigs(getState()); + for (let i = 0; i < arr.length; i++) { + const x = arr[i]; + if (x.baseURL === baseURL && x.secret === secret) return i; + } +} + +export function addClashAPIConfig({ baseURL, secret }) { + return async (dispatch, getState) => { + const idx = findClashAPIConfigIndex(getState, { baseURL, secret }); + // already exists + if (idx) return; + + const clashAPIConfig = { baseURL, secret, addedAt: Date.now() }; + dispatch('addClashAPIConfig', (s) => { + s.app.clashAPIConfigs.push(clashAPIConfig); + }); + // side effect + saveState(getState().app); + }; +} + +export function removeClashAPIConfig({ baseURL, secret }) { + return async (dispatch, getState) => { + const idx = findClashAPIConfigIndex(getState, { baseURL, secret }); + dispatch('removeClashAPIConfig', (s) => { + s.app.clashAPIConfigs = [ + ...s.app.clashAPIConfigs.slice(0, idx), + ...s.app.clashAPIConfigs.slice(idx + 1), + ]; + }); + // side effect + saveState(getState().app); + }; +} + +export function selectClashAPIConfig({ baseURL, secret }) { + return async (dispatch, getState) => { + const idx = findClashAPIConfigIndex(getState, { baseURL, secret }); + const curr = getSelectedClashAPIConfigIndex(getState()); + if (curr !== idx) { + dispatch('selectClashAPIConfig', (s) => { + s.app.selectedClashAPIConfigIndex = idx; + }); + } + // side effect + saveState(getState().app); + + // manual clean up is too complex + // we just reload the app + try { + window.location.reload(); + } catch (err) { + // ignore + } + }; +} + +// unused export function updateClashAPIConfig({ baseURL, secret }) { return async (dispatch, getState) => { const clashAPIConfig = { baseURL, secret }; @@ -55,15 +118,6 @@ export function switchTheme() { }; } -export function clearStorage() { - clearState(); - try { - window.location.reload(); - } catch (err) { - // ignore - } -} - export function selectChartStyleIndex(selectedChartStyleIndex) { return (dispatch, getState) => { dispatch('appSelectChartStyleIndex', (s) => { @@ -97,6 +151,7 @@ export function updateCollapsibleIsOpen(prefix, name, v) { const defaultClashAPIConfig = { baseURL: 'http://127.0.0.1:7892', secret: '', + addedAt: 0, }; // type Theme = 'light' | 'dark'; const defaultState = { @@ -133,25 +188,24 @@ export function initialState() { const query = parseConfigQueryString(); const conf = s.clashAPIConfigs[s.selectedClashAPIConfigIndex]; - const url = new URL(conf.baseURL); - if (query.hostname) { - url.hostname = query.hostname; - } - if (query.port) { - url.port = query.port; - } - // url.href is a stringifier and it appends a trailing slash - // that is not we want - conf.baseURL = trimTrailingSlash(url.href); - - if (query.secret) { - conf.secret = query.secret; - } - - if (query.theme) { - if (query.theme === 'dark' || query.theme === 'light') { - s.theme = query.theme; + if (conf) { + const url = new URL(conf.baseURL); + if (query.hostname) { + url.hostname = query.hostname; } + if (query.port) { + url.port = query.port; + } + // url.href is a stringifier and it appends a trailing slash + // that is not we want + conf.baseURL = trimTrailingSlash(url.href); + if (query.secret) { + conf.secret = query.secret; + } + } + + if (query.theme === 'dark' || query.theme === 'light') { + s.theme = query.theme; } // set initial theme setTheme(s.theme); diff --git a/src/store/configs.js b/src/store/configs.js index f114cef..bcd4ac8 100644 --- a/src/store/configs.js +++ b/src/store/configs.js @@ -2,8 +2,8 @@ import * as configsAPI from '../api/configs'; import * as trafficAPI from '../api/traffic'; import { openModal } from './modals'; -export const getConfigs = s => s.configs.configs; -export const getLogLevel = s => s.configs.configs['log-level']; +export const getConfigs = (s) => s.configs.configs; +export const getLogLevel = (s) => s.configs.configs['log-level']; export function fetchConfigs(apiConfig) { return async (dispatch, getState) => { @@ -11,25 +11,20 @@ export function fetchConfigs(apiConfig) { try { res = await configsAPI.fetchConfigs(apiConfig); } catch (err) { - // eslint-disable-next-line no-console - console.log('Error fetch configs', err); + // TypeError and AbortError dispatch(openModal('apiConfig')); return; } if (!res.ok) { - if (res.status === 404 || res.status === 401) { - dispatch(openModal('apiConfig')); - } else { - // eslint-disable-next-line no-console - console.log('Error fetch configs', res.statusText); - } + console.log('Error fetch configs', res.statusText); + dispatch(openModal('apiConfig')); return; } const payload = await res.json(); - dispatch('store/configs#fetchConfigs', s => { + dispatch('store/configs#fetchConfigs', (s) => { s.configs.configs = payload; }); @@ -47,25 +42,25 @@ export function fetchConfigs(apiConfig) { } function markHaveFetchedConfig() { - return dispatch => { - dispatch('store/configs#markHaveFetchedConfig', s => { + return (dispatch) => { + dispatch('store/configs#markHaveFetchedConfig', (s) => { s.configs.haveFetchedConfig = true; }); }; } export function updateConfigs(apiConfig, partialConfg) { - return async dispatch => { + return async (dispatch) => { configsAPI .updateConfigs(apiConfig, partialConfg) .then( - res => { + (res) => { if (res.ok === false) { // eslint-disable-next-line no-console console.log('Error update configs', res.statusText); } }, - err => { + (err) => { // eslint-disable-next-line no-console console.log('Error update configs', err); throw err; @@ -75,7 +70,7 @@ export function updateConfigs(apiConfig, partialConfg) { dispatch(fetchConfigs(apiConfig)); }); - dispatch('storeConfigsOptimisticUpdateConfigs', s => { + dispatch('storeConfigsOptimisticUpdateConfigs', (s) => { s.configs.configs = { ...s.configs.configs, ...partialConfg }; }); }; @@ -88,7 +83,7 @@ export const initialState = { 'redir-port': 0, 'allow-lan': false, mode: 'Rule', - 'log-level': 'info' + 'log-level': 'info', }, - haveFetchedConfig: false + haveFetchedConfig: false, }; diff --git a/src/store/index.js b/src/store/index.js index bd4e7a9..4fc8e4c 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -1,6 +1,8 @@ import { initialState as app, + removeClashAPIConfig, selectChartStyleIndex, + selectClashAPIConfig, updateAppConfig, updateCollapsibleIsOpen, } from './app'; @@ -24,6 +26,8 @@ export const actions = { app: { updateCollapsibleIsOpen, updateAppConfig, + removeClashAPIConfig, + selectClashAPIConfig, }, proxies: proxiesActions, }; diff --git a/yarn.lock b/yarn.lock index f747e9b..0150869 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1021,7 +1021,7 @@ dependencies: regenerator-runtime "^0.13.4" -"@babel/runtime@^7.11.2", "@babel/runtime@^7.8.4": +"@babel/runtime@^7.11.2", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4": version "7.11.2" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.11.2.tgz#f549c13c754cc40b87644b9fa9f09a6a95fe0736" integrity sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw== @@ -1218,56 +1218,56 @@ schema-utils "^2.6.5" source-map "^0.7.3" -"@sentry/browser@^5.22.3": - version "5.22.3" - resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-5.22.3.tgz#7a64bd1cf01bf393741a3e4bf35f82aa927f5b4e" - integrity sha512-2TzE/CoBa5ZkvxJizDdi1Iz1ldmXSJpFQ1mL07PIXBjCt0Wxf+WOuFSj5IP4L40XHfJE5gU8wEvSH0VDR8nXtA== +"@sentry/browser@^5.23.0": + version "5.23.0" + resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-5.23.0.tgz#fc3a7a435a4524aa949f91d442435efda153fc0a" + integrity sha512-lBBHb/NFDOy1K5E/noDkgaibTtxp8F8gmAaVhhpGvOjlcBp1wzNJhWRePYKWgjJ7yFudxGi4Qbferdhm9RwzbA== dependencies: - "@sentry/core" "5.22.3" - "@sentry/types" "5.22.3" - "@sentry/utils" "5.22.3" + "@sentry/core" "5.23.0" + "@sentry/types" "5.23.0" + "@sentry/utils" "5.23.0" tslib "^1.9.3" -"@sentry/core@5.22.3": - version "5.22.3" - resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.22.3.tgz#030f435f2b518f282ba8bd954dac90cd70888bd7" - integrity sha512-eGL5uUarw3o4i9QUb9JoFHnhriPpWCaqeaIBB06HUpdcvhrjoowcKZj1+WPec5lFg5XusE35vez7z/FPzmJUDw== +"@sentry/core@5.23.0": + version "5.23.0" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.23.0.tgz#4d12ee4f5593e66fa5ffde0f9e9164a5468e5cec" + integrity sha512-K8Wp/g1opaauKJh2w5Z1Vw/YdudHQgH6Ng5fBazHZxA7zB9R8EbVKDsjy8XEcyHsWB7fTSlYX/7coqmZNOADdg== dependencies: - "@sentry/hub" "5.22.3" - "@sentry/minimal" "5.22.3" - "@sentry/types" "5.22.3" - "@sentry/utils" "5.22.3" + "@sentry/hub" "5.23.0" + "@sentry/minimal" "5.23.0" + "@sentry/types" "5.23.0" + "@sentry/utils" "5.23.0" tslib "^1.9.3" -"@sentry/hub@5.22.3": - version "5.22.3" - resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.22.3.tgz#08309a70d2ea8d5e313d05840c1711f34f2fffe5" - integrity sha512-INo47m6N5HFEs/7GMP9cqxOIt7rmRxdERunA3H2L37owjcr77MwHVeeJ9yawRS6FMtbWXplgWTyTIWIYOuqVbw== +"@sentry/hub@5.23.0": + version "5.23.0" + resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.23.0.tgz#7350c2971fafdb9f883f0629fd1b35d2288cd6e1" + integrity sha512-P0sevLI9qAQc1J+AcHzNXwj83aG3GKiABVQJp0rgCUMtrXqLawa+j8pOHg8p7QWroHM7TKDMKeny9WemXBgzBQ== dependencies: - "@sentry/types" "5.22.3" - "@sentry/utils" "5.22.3" + "@sentry/types" "5.23.0" + "@sentry/utils" "5.23.0" tslib "^1.9.3" -"@sentry/minimal@5.22.3": - version "5.22.3" - resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.22.3.tgz#706e4029ae5494123d3875c658ba8911aa5cc440" - integrity sha512-HoINpYnVYCpNjn2XIPIlqH5o4BAITpTljXjtAftOx6Hzj+Opjg8tR8PWliyKDvkXPpc4kXK9D6TpEDw8MO0wZA== +"@sentry/minimal@5.23.0": + version "5.23.0" + resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.23.0.tgz#93df781a98f0b334425f68b1fa0f4e1a86d8fa45" + integrity sha512-/w/B7ShMVu/tLI0/A5X+w6GfdZIQdFQihWyIK1vXaYS5NS6biGI3K6DcACuMrD/h4BsqlfgdXSOHHrmCJcyCXQ== dependencies: - "@sentry/hub" "5.22.3" - "@sentry/types" "5.22.3" + "@sentry/hub" "5.23.0" + "@sentry/types" "5.23.0" tslib "^1.9.3" -"@sentry/types@5.22.3": - version "5.22.3" - resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.22.3.tgz#d1d547b30ee8bd7771fa893af74c4f3d71f0fd18" - integrity sha512-cv+VWK0YFgCVDvD1/HrrBWOWYG3MLuCUJRBTkV/Opdy7nkdNjhCAJQrEyMM9zX0sac8FKWKOHT0sykNh8KgmYw== +"@sentry/types@5.23.0": + version "5.23.0" + resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.23.0.tgz#935701637c2d5b1c123ac292bc253bddf451b076" + integrity sha512-PbN5MVWxrq05sZ707lc8lleV0xSsI6jWr9h9snvbAuMjcauE0lmdWmjoWKY3PAz2s1mGYFh55kIo8SmQuVwbYg== -"@sentry/utils@5.22.3": - version "5.22.3" - resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.22.3.tgz#e3bda3e789239eb16d436f768daa12829f33d18f" - integrity sha512-AHNryXMBvIkIE+GQxTlmhBXD0Ksh+5w1SwM5qi6AttH+1qjWLvV6WB4+4pvVvEoS8t5F+WaVUZPQLmCCWp6zKw== +"@sentry/utils@5.23.0": + version "5.23.0" + resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.23.0.tgz#5e15f684b5a8f9c86e4ba15d81101eac7d6c240b" + integrity sha512-D5gQDM0wEjKxhE+YNvCuCHo/6JuaORF2/3aOhoJBR+dy9EACRspg7kp3+9KF44xd2HVEXkSVCJkv8/+sHePYRQ== dependencies: - "@sentry/types" "5.22.3" + "@sentry/types" "5.23.0" tslib "^1.9.3" "@types/anymatch@*": @@ -3665,7 +3665,7 @@ eslint-module-utils@^2.6.0: debug "^2.6.9" pkg-dir "^2.0.0" -eslint-plugin-flowtype@^5.1.0: +eslint-plugin-flowtype@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/eslint-plugin-flowtype/-/eslint-plugin-flowtype-5.2.0.tgz#a4bef5dc18f9b2bdb41569a4ab05d73805a3d261" integrity sha512-z7ULdTxuhlRJcEe1MVljePXricuPOrsWfScRXFhNzVD5dmTHWjIF57AxD0e7AbEoLSbjSsaA5S+hCg43WvpXJQ== @@ -3692,10 +3692,10 @@ eslint-plugin-import@^2.22.0: resolve "^1.17.0" tsconfig-paths "^3.9.0" -eslint-plugin-jest@^24.0.0: - version "24.0.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-24.0.0.tgz#6b1c460c529104c7d16d889e76fe708b281c4d14" - integrity sha512-a0G7hSDbuBCW4PNT6MVpAyfnGbUDOqxzOyhR6wT2BIBnR7MhvfAqd6KKfsTjX+Z3gxzIHiEsihzdClU4cSc6qQ== +eslint-plugin-jest@^24.0.1: + version "24.0.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-24.0.1.tgz#ad5e091d47cf895e15dc115e18f98471135a334f" + integrity sha512-8tYFDqOHGr7vVfdVYspmlV4sRBTylrM4gSLgkGKlO6F+djDOEJ+tEU7I50smUs7AIvFnNZutXUQAMgI9s9N6xQ== dependencies: "@typescript-eslint/experimental-utils" "^4.0.1" @@ -3716,10 +3716,10 @@ eslint-plugin-jsx-a11y@^6.3.1: jsx-ast-utils "^2.4.1" language-tags "^1.0.5" -eslint-plugin-react-hooks@^4.0.8: - version "4.1.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.1.0.tgz#6323fbd5e650e84b2987ba76370523a60f4e7925" - integrity sha512-36zilUcDwDReiORXmcmTc6rRumu9JIM3WjSvV0nclHoUQ0CNrX866EwONvLR/UqaeqFutbAnVu8PEmctdo2SRQ== +eslint-plugin-react-hooks@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.1.2.tgz#2eb53731d11c95826ef7a7272303eabb5c9a271e" + integrity sha512-ykUeqkGyUGgwTtk78C0o8UG2fzwmgJ0qxBGPp2WqRKsTwcLuVf01kTDRAtOsd4u6whX2XOC8749n2vPydP82fg== eslint-plugin-react@^7.20.6: version "7.20.6" @@ -3781,10 +3781,10 @@ eslint-visitor-keys@^2.0.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8" integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ== -eslint@^7.6.0: - version "7.8.1" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.8.1.tgz#e59de3573fb6a5be8ff526c791571646d124a8fa" - integrity sha512-/2rX2pfhyUG0y+A123d0ccXtMm7DV7sH1m3lk9nk2DZ2LReq39FXHueR9xZwshE5MdfSf0xunSaMWRqyIA6M1w== +eslint@^7.9.0: + version "7.9.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.9.0.tgz#522aeccc5c3a19017cf0cb46ebfd660a79acf337" + integrity sha512-V6QyhX21+uXp4T+3nrNfI3hQNBDa/P8ga7LoQOenwrlEFXrEnUEE+ok1dMtaS3b6rmLXhT1TkTIsG75HMLbknA== dependencies: "@babel/code-frame" "^7.0.0" "@eslint/eslintrc" "^0.1.3" @@ -4243,10 +4243,10 @@ fork-ts-checker-webpack-plugin@1.5.0: tapable "^1.0.0" worker-rpc "^0.1.0" -fork-ts-checker-webpack-plugin@^5.0.14: - version "5.1.0" - resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-5.1.0.tgz#586fbee24aeea950c53bab529e32017f543e71cf" - integrity sha512-vuKyEjSLGbhQbEr5bifXXOkr9iV73L6n72mHoHIv7okvrf7O7z6RKeplM6C6ATPsukoQivij+Ba1vcptL60Z2g== +fork-ts-checker-webpack-plugin@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-5.2.0.tgz#780a14fb0da02a892baedd7aa0d089a1eda7b3e5" + integrity sha512-NEKcI0+osT5bBFZ1SFGzJMQETjQWZrSvMO1g0nAR/w0t328Z41eN8BJEIZyFCl2HsuiJpa9AN474Nh2qLVwGLQ== dependencies: "@babel/code-frame" "^7.8.3" "@types/json-schema" "^7.0.5" @@ -4272,14 +4272,14 @@ fragment-cache@^0.2.1: dependencies: map-cache "^0.2.2" -framer-motion@^2.6.7: - version "2.6.7" - resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-2.6.7.tgz#f01685750573ab24fec8b53ac48a05e8fec6deef" - integrity sha512-szwqKCH/yvMtaFmtphP3np/1/5HMDbPWfmAbVMXDvofC3qDi4+XsxVnI60egCnmYGxPeE5t4JFbZjAqOZy4HPg== +framer-motion@^2.6.13: + version "2.6.13" + resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-2.6.13.tgz#cc26faf53bffb2d9a558bc71a3adaf3c437c0e55" + integrity sha512-ScEdiDyAT+kH51axqxFcHA9RLnJ8TpMQBf8HwcXBOt6wDVptHdewEnE3T7semz+7OY0sj0RCsUDhQaWo78aMqQ== dependencies: framesync "^4.1.0" hey-listen "^1.0.8" - popmotion "9.0.0-rc.13" + popmotion "9.0.0-rc.14" style-value-types "^3.1.9" tslib "^1.10.0" optionalDependencies: @@ -4829,10 +4829,10 @@ immer@1.10.0: resolved "https://registry.yarnpkg.com/immer/-/immer-1.10.0.tgz#bad67605ba9c810275d91e1c2a47d4582e98286d" integrity sha512-O3sR1/opvCDGLEVcvrGTMtLac8GJ5IwZC4puPrLuRj3l7ICKvkmA0vGuU9OW8mV9WIBRnaxp5GJh9IEAaNOoYg== -immer@^7.0.8: - version "7.0.8" - resolved "https://registry.yarnpkg.com/immer/-/immer-7.0.8.tgz#41dcbc5669a76500d017bef3ad0d03ce0a1d7c1e" - integrity sha512-XnpIN8PXBBaOD43U8Z17qg6RQiKQYGDGGCIbz1ixmLGwBkSWwmrmx5X7d+hTtXDM8ur7m5OdLE0PiO+y5RB3pw== +immer@^7.0.9: + version "7.0.9" + resolved "https://registry.yarnpkg.com/immer/-/immer-7.0.9.tgz#28e7552c21d39dd76feccd2b800b7bc86ee4a62e" + integrity sha512-Vs/gxoM4DqNAYR7pugIxi0Xc8XAun/uy7AQu4fLLqaTBHxjOP9pJ266Q9MWA/ly4z6rAFZbvViOtihxUZ7O28A== import-fresh@^2.0.0: version "2.0.0" @@ -5760,10 +5760,10 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== -mini-css-extract-plugin@^0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.11.0.tgz#3918953075109d4ca204bf1e8a393a78d3cc821f" - integrity sha512-dVWGuWJlQw2lZxsxBI3hOsoxg1k3DruLR0foHQLSkQMfk+qLJbv9dUk8fjmjWQKN9ef2n54ehA2FjClAsQhrWQ== +mini-css-extract-plugin@^0.11.2: + version "0.11.2" + resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.11.2.tgz#e3af4d5e04fbcaaf11838ab230510073060b37bf" + integrity sha512-h2LknfX4U1kScXxH8xE9LCOqT5B+068EAj36qicMb8l4dqdJoyHcmWmpd+ueyZfgu/POvIn+teoUnTtei2ikug== dependencies: loader-utils "^1.1.0" normalize-url "1.9.1" @@ -6554,10 +6554,10 @@ please-upgrade-node@^3.2.0: dependencies: semver-compare "^1.0.0" -popmotion@9.0.0-rc.13: - version "9.0.0-rc.13" - resolved "https://registry.yarnpkg.com/popmotion/-/popmotion-9.0.0-rc.13.tgz#0280968fb0dc3bd273cc3dbd4da24b01b6cfbcce" - integrity sha512-M8Ksx2THYrAUrptE5Ydd3jazQAMoXxqpClR/K8xK87v4UrJUTCb39kNar/ucJ4Rs7qPqPdVu+iqPKOJhpMylXQ== +popmotion@9.0.0-rc.14: + version "9.0.0-rc.14" + resolved "https://registry.yarnpkg.com/popmotion/-/popmotion-9.0.0-rc.14.tgz#e57351b7b85a3e42b7a16affbbd440138797c11f" + integrity sha512-zdMw1OSKjFBH+KKpZx7P+cGSUb3QCqg5QD12f6llucUeEFT+SDZYxvTY09JI23ZcJyzxgKFT1anbLq0eZ9bj3g== dependencies: framesync "^4.1.0" hey-listen "^1.0.8" @@ -6652,10 +6652,10 @@ postcss-import@^12.0.1: read-cache "^1.0.0" resolve "^1.1.7" -postcss-loader@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-4.0.0.tgz#bfd04521d2a2db69cb9e8a7cee0a5ae704e751dc" - integrity sha512-LdpfM9yCVFeJzofnaFvLf3g9oMuH2mIIqOcu81n6JHxzRNBl78GHiYWUJ5gf4c7A7VZiBCeWwfVAMw/mQCAM3Q== +postcss-loader@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-4.0.1.tgz#d1a2cf443005bcd65e856d3881204886646398d4" + integrity sha512-m+2fe21cs/1Oz4Yds90al5uqVSc0yJRhYSfCRnsnVLt3z0QoNPpqLdgW7CGVWmlUlKEGL9vmq+P4hHS6Orb5DA== dependencies: cosmiconfig "^7.0.0" klona "^2.0.3" @@ -7239,10 +7239,12 @@ react-modal@^3.11.1: react-lifecycles-compat "^3.0.0" warning "^4.0.3" -react-query@^2.15.4: - version "2.15.4" - resolved "https://registry.yarnpkg.com/react-query/-/react-query-2.15.4.tgz#0447510fc30aebba5a4b99879ec4b7155d9b3f20" - integrity sha512-gEF2Ebe1MtiyLKEaVEIlRMl6rwvP/RVwP+Xf68vhXDfD91IeZkkW7plRjoaHa0/NeUPM1AgTHb1Z7NtqaYDXjA== +react-query@^2.17.0: + version "2.17.0" + resolved "https://registry.yarnpkg.com/react-query/-/react-query-2.17.0.tgz#ac6c1a17398ab86c65008d7a9a4c0d5385626d10" + integrity sha512-YtJ6Oot1vHyAHmllPdINEc0+3NnkjIWUcXZBUiScf+MpcRbzn8zBTc+QLF8dta+Zfm/HPHz1fFMYjAmTOSc/zw== + dependencies: + "@babel/runtime" "^7.5.5" react-refresh@^0.8.2: version "0.8.3" @@ -8383,19 +8385,19 @@ terser-webpack-plugin@^1.4.3: webpack-sources "^1.4.0" worker-farm "^1.7.0" -terser-webpack-plugin@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-4.1.0.tgz#6e9d6ae4e1a900d88ddce8da6a47507ea61f44bc" - integrity sha512-0ZWDPIP8BtEDZdChbufcXUigOYk6dOX/P/X0hWxqDDcVAQLb8Yy/0FAaemSfax3PAA67+DJR778oz8qVbmy4hA== +terser-webpack-plugin@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-4.2.0.tgz#6240a71101a55c6823d84e11c8fff380b9dfd26f" + integrity sha512-Wi0YFbWKG8gBXhbJmrMusRcoXl/C9U5BzIPC2Tn3Si0hejGhhIh0gPf9rEfOCxwigzRPLC8PXv42qDiRTocMXg== dependencies: cacache "^15.0.5" find-cache-dir "^3.3.1" jest-worker "^26.3.0" p-limit "^3.0.2" - schema-utils "^2.6.6" + schema-utils "^2.7.1" serialize-javascript "^4.0.0" source-map "^0.6.1" - terser "^5.0.0" + terser "^5.3.0" webpack-sources "^1.4.3" terser@^4.1.2: @@ -8416,10 +8418,10 @@ terser@^4.6.3: source-map "~0.6.1" source-map-support "~0.5.12" -terser@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.0.0.tgz#269640e4e92f15d628de1e5f01c4c61e1ba3d765" - integrity sha512-olH2DwGINoSuEpSGd+BsPuAQaA3OrHnHnFL/rDB2TVNc3srUbz/rq/j2BlF4zDXI+JqAvGr86bIm1R2cJgZ3FA== +terser@^5.3.0: + version "5.3.1" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.3.1.tgz#f50fe20ab48b15234fe9bdd86b10148ad5fca787" + integrity sha512-yD80f4hdwCWTH5mojzxe1q8bN1oJbsK/vfJGLcPZM/fl+/jItIVNKhFIHqqR71OipFWMLgj3Kc+GIp6CeIqfnA== dependencies: commander "^2.20.0" source-map "~0.6.1"