feat: support rule provider

This commit is contained in:
Haishan 2020-07-01 22:06:26 +08:00
parent 55e928a87f
commit 32bed273c8
21 changed files with 539 additions and 213 deletions

View file

@ -34,7 +34,7 @@
"dependencies": {
"@babel/runtime": "^7.10.3",
"@hsjs/react-cache": "0.0.0-alpha.aa94237",
"@sentry/browser": "^5.18.1",
"@sentry/browser": "^5.19.0",
"chart.js": "^2.9.2",
"clsx": "^1.1.0",
"core-js": "^3.6.2",
@ -52,10 +52,11 @@
"react-feather": "^2.0.3",
"react-icons": "^3.10.0",
"react-modal": "^3.11.1",
"react-query": "^2.4.13",
"react-router": "^6.0.0-alpha.1",
"react-router-dom": "^6.0.0-alpha.1",
"react-switch": "^5.0.1",
"react-table": "^7.2.1",
"react-table": "^7.2.2",
"react-tabs": "^3.1.0",
"react-tiny-fab": "^3.5.0",
"react-window": "^1.8.5",
@ -77,7 +78,9 @@
"@babel/preset-typescript": "^7.10.1",
"@hsjs/react-refresh-webpack-plugin": "^0.1.3",
"@pmmmwh/react-refresh-webpack-plugin": "^0.2.0",
"@types/invariant": "^2.2.33",
"@types/jest": "^26.0.3",
"@types/lodash-es": "^4.17.3",
"@types/react": "^16.9.41",
"@types/react-dom": "^16.9.8",
"@typescript-eslint/eslint-plugin": "^3.4.0",
@ -86,10 +89,10 @@
"babel-eslint": "10.x",
"babel-loader": "^8.0.5",
"clean-webpack-plugin": "^3.0.0",
"copy-webpack-plugin": "^6.0.2",
"copy-webpack-plugin": "^6.0.3",
"css-loader": "^3.4.2",
"cssnano": "^4.1.7",
"eslint": "^7.3.1",
"eslint": "^7.4.0",
"eslint-config-airbnb-base": "^14.1.0",
"eslint-config-prettier": "^6.11.0",
"eslint-config-react-app": "^5.2.1",
@ -98,12 +101,12 @@
"eslint-plugin-import": "^2.22.0",
"eslint-plugin-jest": "^23.17.1",
"eslint-plugin-jsx-a11y": "^6.3.1",
"eslint-plugin-react": "7.x",
"eslint-plugin-react": "^7.20.3",
"eslint-plugin-react-hooks": "^4.0.0",
"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.5",
"fork-ts-checker-webpack-plugin": "^5.0.6",
"html-webpack-plugin": "^4.3.0",
"husky": "^4.0.0",
"lint-staged": "^10.2.11",
@ -120,7 +123,7 @@
"style-loader": "^1.2.1",
"terser-webpack-plugin": "^3.0.6",
"ts-loader": "^7.0.5",
"typescript": "^3.9.5",
"typescript": "^3.9.6",
"webpack": "^4.41.6",
"webpack-bundle-analyzer": "^3.6.0",
"webpack-cli": "^3.3.12",

74
src/api/rule-provider.ts Normal file
View file

@ -0,0 +1,74 @@
import { getURLAndInit } from 'src/misc/request-helper';
import { ClashAPIConfig } from 'src/types';
export type RuleProvider = RuleProviderAPIItem & { idx: number };
export type RuleProviderAPIItem = {
behavior: string;
name: string;
ruleCount: number;
type: 'Rule';
// example value "2020-06-30T16:23:01.44143802+08:00"
updatedAt: string;
vehicleType: 'HTTP' | 'File';
};
type RuleProviderAPIData = {
providers: Record<string, RuleProviderAPIItem>;
};
function normalizeAPIResponse(data: RuleProviderAPIData) {
const providers = data.providers;
const names = Object.keys(providers);
const byName: Record<string, RuleProvider> = {};
// attach an idx to each item
for (let i = 0; i < names.length; i++) {
const name = names[i];
byName[name] = { ...providers[name], idx: i };
}
return { byName, names };
}
export async function fetchRuleProviders(
endpoint: string,
apiConfig: ClashAPIConfig
) {
const { url, init } = getURLAndInit(apiConfig);
let data = { providers: {} };
try {
const res = await fetch(url + endpoint, init);
if (res.ok) {
data = await res.json();
}
} catch (err) {
// log and ignore
// eslint-disable-next-line no-console
console.log('failed to GET /providers/rules', err);
}
return normalizeAPIResponse(data);
}
export async function refreshRuleProviderByName({
name,
apiConfig,
}: {
name: string;
apiConfig: ClashAPIConfig;
}) {
const { url, init } = getURLAndInit(apiConfig);
try {
const res = await fetch(url + `/providers/rules/${name}`, {
method: 'PUT',
...init,
});
return res.ok;
} catch (err) {
// log and ignore
// eslint-disable-next-line no-console
console.log('failed to PUT /providers/rules/:name', err);
return false;
}
}

View file

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

41
src/api/rules.ts Normal file
View file

@ -0,0 +1,41 @@
import invariant from 'invariant';
import { getURLAndInit } from 'src/misc/request-helper';
import { ClashAPIConfig } from 'src/types';
// const endpoint = '/rules';
type RuleItem = RuleAPIItem & { id: number };
type RuleAPIItem = {
type: string;
payload: string;
proxy: string;
};
function normalizeAPIResponse(json: {
rules: Array<RuleAPIItem>;
}): Array<RuleItem> {
invariant(
json.rules && json.rules.length >= 0,
'there is no valid rules list in the rules API response'
);
// attach an id
return json.rules.map((r: RuleAPIItem, i: number) => ({ ...r, id: i }));
}
export async function fetchRules(endpoint: string, apiConfig: ClashAPIConfig) {
let json = { rules: [] };
try {
const { url, init } = getURLAndInit(apiConfig);
const res = await fetch(url + endpoint, init);
if (res.ok) {
json = await res.json();
}
} catch (err) {
// log and ignore
// eslint-disable-next-line no-console
console.log('failed to fetch rules', err);
}
return normalizeAPIResponse(json);
}

View file

@ -43,6 +43,10 @@
}
}
.btn:disabled {
opacity: 0.5;
}
.btnStart {
margin-right: 5px;
display: inline-flex;

View file

@ -16,6 +16,7 @@ type ButtonInternalProps = {
type ButtonProps = {
isLoading?: boolean;
onClick?: (e: React.MouseEvent<HTMLButtonElement>) => unknown;
disabled?: boolean;
kind?: 'primary' | 'minimal';
className?: string;
} & ButtonInternalProps;
@ -23,6 +24,7 @@ type ButtonProps = {
function Button(props: ButtonProps, ref: React.Ref<HTMLButtonElement>) {
const {
onClick,
disabled = false,
isLoading,
kind = 'primary',
className,
@ -43,7 +45,12 @@ function Button(props: ButtonProps, ref: React.Ref<HTMLButtonElement>) {
className
);
return (
<button className={btnClassName} ref={ref} onClick={internalOnClick}>
<button
className={btnClassName}
ref={ref}
onClick={internalOnClick}
disabled={disabled}
>
{isLoading ? (
<>
<span

View file

@ -1,6 +0,0 @@
import { getSearchText, updateSearchText } from '../store/rules';
import Search from './Search';
import { connect } from './StateProvider';
const mapState = (s) => ({ searchText: getSearchText(s), updateSearchText });
export default connect(mapState)(Search);

View file

@ -1,27 +1,63 @@
import React from 'react';
import { RotateCw } from 'react-feather';
import { areEqual, FixedSizeList as List } from 'react-window';
import { queryCache, useQuery } from 'react-query';
import { areEqual, VariableSizeList } from 'react-window';
import { useRecoilState } from 'recoil';
import { fetchRuleProviders } from 'src/api/rule-provider';
import { fetchRules } from 'src/api/rules';
import { RuleProviderItem } from 'src/components/rules/RuleProviderItem';
import { TextFilter } from 'src/components/rules/TextFilter';
import { ruleFilterText } from 'src/store/rules';
import useRemainingViewPortHeight from '../hooks/useRemainingViewPortHeight';
import { getClashAPIConfig } from '../store/app';
import { fetchRules, fetchRulesOnce, getRules } from '../store/rules';
import ContentHeader from './ContentHeader';
import Rule from './Rule';
import RuleSearch from './RuleSearch';
import s from './Rules.module.css';
import { Fab, position as fabPosition } from './shared/Fab';
import { connect } from './StateProvider';
const { memo, useEffect, useMemo, useCallback } = React;
const { memo, useMemo, useCallback } = React;
const paddingBottom = 30;
function itemKey(index, data) {
const item = data[index];
function itemKey(index, { rules, provider }) {
const providerQty = provider.names.length;
if (index < providerQty) {
return provider.names[index];
}
const item = rules[index - providerQty];
return item.id;
}
function getItemSizeFactory({ provider }) {
return function getItemSize(idx) {
const providerQty = provider.names.length;
if (idx < providerQty) {
// provider
return 90;
}
// rule
return 80;
};
}
const Row = memo(({ index, style, data }) => {
const r = data[index];
const { rules, provider, apiConfig } = data;
const providerQty = provider.names.length;
if (index < providerQty) {
const name = provider.names[index];
const item = provider.byName[name];
return (
<div style={style} className={s.RuleProviderItemWrapper}>
<RuleProviderItem apiConfig={apiConfig} {...item} />
</div>
);
}
const r = rules[index - providerQty];
return (
<div style={style}>
<Rule {...r} />
@ -31,42 +67,75 @@ const Row = memo(({ index, style, data }) => {
const mapState = (s) => ({
apiConfig: getClashAPIConfig(s),
rules: getRules(s),
});
export default connect(mapState)(Rules);
function Rules({ dispatch, apiConfig, rules }) {
const fetchRulesHooked = useCallback(() => {
dispatch(fetchRules(apiConfig));
}, [apiConfig, dispatch]);
useEffect(() => {
dispatch(fetchRulesOnce(apiConfig));
}, [dispatch, apiConfig]);
function useRuleAndProvider(apiConfig) {
const { data: rules } = useQuery(['/rules', apiConfig], fetchRules, {
suspense: true,
});
const { data: provider } = useQuery(
['/providers/rules', apiConfig],
fetchRuleProviders,
{ suspense: true }
);
const [filterText] = useRecoilState(ruleFilterText);
if (filterText === '') {
return { rules, provider };
} else {
const f = filterText.toLowerCase();
return {
rules: rules.filter((r) => r.payload.toLowerCase().indexOf(f) >= 0),
provider: {
byName: provider.byName,
names: provider.names.filter((t) => t.toLowerCase().indexOf(f) >= 0),
},
};
}
}
function useInvalidateQueries() {
return useCallback(() => {
queryCache.invalidateQueries('/rules');
queryCache.invalidateQueries('/providers/rules');
}, []);
}
function Rules({ apiConfig }) {
const [refRulesContainer, containerHeight] = useRemainingViewPortHeight();
const refreshIcon = useMemo(() => <RotateCw width={16} />, []);
const { rules, provider } = useRuleAndProvider(apiConfig);
const invalidateQueries = useInvalidateQueries();
const getItemSize = getItemSizeFactory({ rules, provider });
return (
<div>
<ContentHeader title="Rules" />
<RuleSearch />
<div className={s.header}>
<ContentHeader title="Rules" />
<TextFilter />
</div>
<div ref={refRulesContainer} style={{ paddingBottom }}>
<List
<VariableSizeList
height={containerHeight - paddingBottom}
width="100%"
itemCount={rules.length}
itemSize={80}
itemData={rules}
itemCount={rules.length + provider.names.length}
itemSize={getItemSize}
itemData={{ rules, provider, apiConfig }}
itemKey={itemKey}
>
{Row}
</List>
</VariableSizeList>
</div>
<Fab
icon={refreshIcon}
text="Refresh"
onClick={fetchRulesHooked}
position={fabPosition}
onClick={invalidateQueries}
/>
</div>
);

View file

@ -1 +1,21 @@
/* */
.header {
display: grid;
grid-template-columns: 1fr minmax(auto, 330px);
align-items: center;
/*
* the content header has some padding
* we need to apply some right padding to this container then
*/
padding-right: 15px;
@media (--breakpoint-not-small) {
padding-right: 40px;
}
}
.RuleProviderItemWrapper {
padding: 6px 15px;
@media (--breakpoint-not-small) {
padding: 10px 40px;
}
}

View file

@ -1,6 +1,11 @@
import produce, * as immer from 'immer';
import React from 'react';
// in logs store we update logs in place
// outside of immer produce
// this is just workaround
immer.setAutoFreeze(false);
const {
createContext,
memo,

View file

@ -1,28 +1,11 @@
import debounce from 'lodash-es/debounce';
import * as React from 'react';
import { useRecoilState } from 'recoil';
import { useTextInut } from 'src/hooks/useTextInput';
import { proxyFilterText } from '../../store/proxies';
import shared from '../shared.module.css';
const { useCallback, useState, useMemo } = React;
export function TextFilter() {
const [, setTextGlobal] = useRecoilState(proxyFilterText);
const [text, setText] = useState('');
const setTextDebounced = useMemo(() => debounce(setTextGlobal, 300), [
setTextGlobal,
]);
const onChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
setText(e.target.value);
setTextDebounced(e.target.value);
},
[setTextDebounced]
);
const [onChange, text] = useTextInut(proxyFilterText);
return (
<input
className={shared.input}

View file

@ -0,0 +1,43 @@
.RuleProviderItem {
display: grid;
grid-template-columns: 40px 1fr 46px;
height: 100%;
}
.left {
display: inline-flex;
align-items: center;
color: var(--color-text-secondary);
opacity: 0.4;
}
.middle {
display: grid;
grid-template-rows: 1fr auto auto;
align-items: center;
}
.gray {
color: #777;
}
.refreshButtonWrapper {
display: grid;
place-items: center;
}
.rotate {
display: inline-flex;
}
.isRotating {
animation: rotating 3s infinite linear;
}
@keyframes rotating {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

View file

@ -0,0 +1,71 @@
import cx from 'clsx';
import { formatDistance } from 'date-fns';
import * as React from 'react';
import { RotateCw } from 'react-feather';
import { queryCache, useMutation } from 'react-query';
import { refreshRuleProviderByName } from 'src/api/rule-provider';
import Button from 'src/components/Button';
import { SectionNameType } from 'src/components/shared/Basic';
import { ClashAPIConfig } from 'src/types';
import s from './RuleProviderItem.module.css';
function useRefresh(
name: string,
apiConfig: ClashAPIConfig
): [(ev: React.MouseEvent<HTMLButtonElement>) => unknown, boolean] {
const [mutate, { isLoading }] = useMutation(refreshRuleProviderByName, {
onSuccess: () => {
queryCache.invalidateQueries('/providers/rules');
},
});
const onClickRefreshButton = (ev: React.MouseEvent<HTMLButtonElement>) => {
ev.preventDefault();
mutate({ name, apiConfig });
};
return [onClickRefreshButton, isLoading];
}
function RotatableRotateCw({ isRotating }: { isRotating: boolean }) {
const cls = cx(s.rotate, {
[s.isRotating]: isRotating,
});
return (
<span className={cls}>
<RotateCw width={16} />
</span>
);
}
export function RuleProviderItem({
idx,
name,
vehicleType,
behavior,
updatedAt,
ruleCount,
apiConfig,
}) {
const [onClickRefreshButton, isRefreshing] = useRefresh(name, apiConfig);
const timeAgo = formatDistance(new Date(updatedAt), new Date());
return (
<div className={s.RuleProviderItem}>
<span className={s.left}>{idx}</span>
<div className={s.middle}>
<SectionNameType name={name} type={`${vehicleType} / ${behavior}`} />
<div className={s.gray}>
{ruleCount < 2 ? `${ruleCount} rule` : `${ruleCount} rules`}
</div>
<small className={s.gray}>Updated {timeAgo} ago</small>
</div>
<span className={s.refreshButtonWrapper}>
<Button onClick={onClickRefreshButton} disabled={isRefreshing}>
<RotatableRotateCw isRotating={isRefreshing} />
</Button>
</span>
</div>
);
}

View file

@ -0,0 +1,18 @@
import * as React from 'react';
import { useTextInut } from 'src/hooks/useTextInput';
import { ruleFilterText } from 'src/store/rules';
import shared from '../shared.module.css';
export function TextFilter() {
const [onChange, text] = useTextInut(ruleFilterText);
return (
<input
className={shared.input}
type="text"
value={text}
onChange={onChange}
placeholder="Filter"
/>
);
}

23
src/hooks/useTextInput.ts Normal file
View file

@ -0,0 +1,23 @@
import debounce from 'lodash-es/debounce';
import * as React from 'react';
import { RecoilState, useRecoilState } from 'recoil';
const { useCallback, useState, useMemo } = React;
export function useTextInut(
x: RecoilState<string>
): [(e: React.ChangeEvent<HTMLInputElement>) => void, string] {
const [, setTextGlobal] = useRecoilState(x);
const [text, setText] = useState('');
const setTextDebounced = useMemo(() => debounce(setTextGlobal, 300), [
setTextGlobal,
]);
const onChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
setText(e.target.value);
setTextDebounced(e.target.value);
},
[setTextDebounced]
);
return [onChange, text];
}

View file

@ -8,14 +8,12 @@ import { initialState as configs } from './configs';
import { initialState as logs } from './logs';
import { initialState as modals } from './modals';
import { actions as proxiesActions, initialState as proxies } from './proxies';
import { initialState as rules } from './rules';
export const initialState = {
app: app(),
modals,
configs,
proxies,
rules,
logs,
};

View file

@ -2,9 +2,9 @@ import { createSelector } from 'reselect';
const LogSize = 300;
const getLogs = s => s.logs.logs;
const getTail = s => s.logs.tail;
export const getSearchText = s => s.logs.searchText;
const getLogs = (s) => s.logs.logs;
const getTail = (s) => s.logs.tail;
export const getSearchText = (s) => s.logs.searchText;
export const getLogsForDisplay = createSelector(
getLogs,
getTail,
@ -21,13 +21,13 @@ export const getLogsForDisplay = createSelector(
}
if (searchText === '') return x;
return x.filter(r => r.payload.toLowerCase().indexOf(searchText) >= 0);
return x.filter((r) => r.payload.toLowerCase().indexOf(searchText) >= 0);
}
);
export function updateSearchText(text) {
return dispatch => {
dispatch('logsUpdateSearchText', s => {
return (dispatch) => {
dispatch('logsUpdateSearchText', (s) => {
s.logs.searchText = text.toLowerCase();
});
};
@ -42,7 +42,7 @@ export function appendLog(log) {
// mutate intentionally for performance
logs[tail] = log;
dispatch('logsAppendLog', s => {
dispatch('logsAppendLog', (s) => {
s.logs.tail = tail;
});
};
@ -52,5 +52,5 @@ export const initialState = {
searchText: '',
logs: [],
// tail's initial value must be -1
tail: -1
tail: -1,
};

View file

@ -1,58 +1,6 @@
import invariant from 'invariant';
import { createSelector } from 'reselect';
import { atom } from 'recoil';
import * as rulesAPI from '../api/rules';
export const getAllRules = (s) => s.rules.allRules;
export const getSearchText = (s) => s.rules.searchText;
export const getRules = createSelector(
getSearchText,
getAllRules,
(searchText, allRules) => {
if (searchText === '') return allRules;
return allRules.filter((r) => r.payload.indexOf(searchText) >= 0);
}
);
export function updateSearchText(text) {
return (dispatch) => {
dispatch('rulesUpdateSearchText', (s) => {
s.rules.searchText = text.toLowerCase();
});
};
}
export function fetchRules(apiConfig) {
return async (dispatch) => {
const res = await rulesAPI.fetchRules(apiConfig);
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('rulesFetchRules', (s) => {
s.rules.allRules = allRules;
});
};
}
export function fetchRulesOnce(apiConfig) {
return async (dispatch, getState) => {
const allRules = getAllRules(getState());
if (allRules.length === 0) return await dispatch(fetchRules(apiConfig));
};
}
// {"type":"FINAL","payload":"","proxy":"Proxy"}
// {"type":"IPCIDR","payload":"172.16.0.0/12","proxy":"DIRECT"}
export const initialState = {
// filteredRules: [],
allRules: [],
searchText: '',
};
export const ruleFilterText = atom({
key: 'ruleFilterText',
default: '',
});

View file

@ -0,0 +1,5 @@
export type ClashAPIConfig = {
hostname: string;
port: number;
secret?: string;
};

View file

@ -1,7 +1,8 @@
{
"compilerOptions": {
"baseUrl": ".",
"allowSyntheticDefaultImports": false,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"target": "es5",
"downlevelIteration": true,
"importHelpers": true,
@ -15,7 +16,7 @@
"forceConsistentCasingInFileNames": true,
"experimentalDecorators": true,
"noImplicitAny": false,
"noUnusedParameters": true,
"noUnusedParameters": false,
"noImplicitReturns": true,
"noImplicitThis": true,
"noUnusedLocals": true,

185
yarn.lock
View file

@ -1176,61 +1176,56 @@
style-value-types "^3.1.7"
tslib "^1.10.0"
"@scarf/scarf@^1.0.4":
version "1.0.6"
resolved "https://registry.yarnpkg.com/@scarf/scarf/-/scarf-1.0.6.tgz#52011dfb19187b53b75b7b6eac20da0810ddd88f"
integrity sha512-y4+DuXrAd1W5UIY3zTcsosi/1GyYT8k5jGnZ/wG7UUHVrU+MHlH4Mp87KK2/lvMW4+H7HVcdB+aJhqywgXksjA==
"@sentry/browser@^5.18.1":
version "5.18.1"
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-5.18.1.tgz#6a2ebe8f4ba88b2e98ccc91442e1dcb37b2df988"
integrity sha512-U1w0d5kRMsfzMYwWn4+awDKfBEI5lxhHa0bMChSpj5z/dWiz/e0mikZ9gCoF+ZNqkXJ92l/3r9gRz+SIsn5ZoA==
"@sentry/browser@^5.19.0":
version "5.19.0"
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-5.19.0.tgz#9189b6633fe45e54325e40b39345d9eabd171e4a"
integrity sha512-Cz8PnzC5NGfpHIGCmHLgA6iDEBVELwo4Il/iFweXjs4+VL4biw62lI32Q4iLCCpmX0t5UvrWBOnju2v8zuFIjA==
dependencies:
"@sentry/core" "5.18.1"
"@sentry/types" "5.18.1"
"@sentry/utils" "5.18.1"
"@sentry/core" "5.19.0"
"@sentry/types" "5.19.0"
"@sentry/utils" "5.19.0"
tslib "^1.9.3"
"@sentry/core@5.18.1":
version "5.18.1"
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.18.1.tgz#c2aa7ef9054e372d006d32234969711234d2bb02"
integrity sha512-nC2aK6gwVIBVysmtdFHxYJyuChIHtkv7TnvmwgA5788L/HWo7E3R+Rd8Tf2npvp/aP+kmNITNbc5CIIqwGPaqQ==
"@sentry/core@5.19.0":
version "5.19.0"
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.19.0.tgz#31b08a0b46ae1ee6863447225b401ac2a777774c"
integrity sha512-ry1Zms6jrVQPEwmfywItyUhOgabPrykd8stR1x/u2t1YiISWlR813fE5nzdwgW5mxEitXz/ikTwvrfK3egsDWQ==
dependencies:
"@sentry/hub" "5.18.1"
"@sentry/minimal" "5.18.1"
"@sentry/types" "5.18.1"
"@sentry/utils" "5.18.1"
"@sentry/hub" "5.19.0"
"@sentry/minimal" "5.19.0"
"@sentry/types" "5.19.0"
"@sentry/utils" "5.19.0"
tslib "^1.9.3"
"@sentry/hub@5.18.1":
version "5.18.1"
resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.18.1.tgz#4c2f642e29a320885692b902fba89e57a9906e64"
integrity sha512-dFnaj1fQRT74EhoF8MXJ23K3svt11zEF6CS3cdMrkSzfRbAHjyza7KT2AJHUeF6gtH2BZzqsSw+FnfAke0HGIg==
"@sentry/hub@5.19.0":
version "5.19.0"
resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.19.0.tgz#f38e7745a4980d9fa6c5baeca5605e7e6fecd5ac"
integrity sha512-UFaQLa1XAa02ZcxUmN9GdDpGs3vHK1LpOyYooimX8ttWa4KAkMuP+O5uXH1TJabry6o/cRwYisg2k6PBSy8gxw==
dependencies:
"@sentry/types" "5.18.1"
"@sentry/utils" "5.18.1"
"@sentry/types" "5.19.0"
"@sentry/utils" "5.19.0"
tslib "^1.9.3"
"@sentry/minimal@5.18.1":
version "5.18.1"
resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.18.1.tgz#8de01e87c5f5c6e74b707849202150cd4b316ee0"
integrity sha512-St2bjcZ5FFiH+bYkWoEPlEb0w38YSvftnjJTvZyk05SCdsF7HkGfoBeFmztwBf1VLQPYt3ojny14L6KDAvOTpw==
"@sentry/minimal@5.19.0":
version "5.19.0"
resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.19.0.tgz#aa5a700618608ea79d270280fe77f04bbb44ed5a"
integrity sha512-3FHgirwOuOMF4VlwHboYObPT9c0S9b9y5FW0DGgNt8sJPezS00VaJti/38ZwOHQJ4fJ6ubt/Y3Kz0eBTVxMCCQ==
dependencies:
"@sentry/hub" "5.18.1"
"@sentry/types" "5.18.1"
"@sentry/hub" "5.19.0"
"@sentry/types" "5.19.0"
tslib "^1.9.3"
"@sentry/types@5.18.1":
version "5.18.1"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.18.1.tgz#9d72254262f28e966b06371c5b3833de8f0253b8"
integrity sha512-y5YTkRFC4Y7r4GHrvin6aZLBpQIGdMZRq78f/s7IIEZrmWYbVKsK4dyJht6pOsUdEaxeYpsu3okIA0bqmthSJA==
"@sentry/types@5.19.0":
version "5.19.0"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.19.0.tgz#30c6a05040b644d90337ef8b50d9d59c0872744d"
integrity sha512-NlHLS9mwCEimKUA5vClAwVhXFLsqSF3VJEXU+K61fF6NZx8zfJi/HTrIBtoM4iNSAt9o4XLQatC1liIpBC06tg==
"@sentry/utils@5.18.1":
version "5.18.1"
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.18.1.tgz#c9880056793ae77d651db0dae76a08a8a0b31eac"
integrity sha512-P4lt6NauCBWASaP6R5kfOmc24imbD32G5FeWqK7vHftIphOJ0X7OZfh93DJPs3e5RIvW3YCywUsa7MpTH5/ClA==
"@sentry/utils@5.19.0":
version "5.19.0"
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.19.0.tgz#0e01f84aa67a1cf2ec71faab670230918ea7e9aa"
integrity sha512-EU/T9aJrI8isexRnyDx5InNw/HjSQ0nKI7YWdiwfFrJusqQ/uisnCGK7vw6gGHDgiAHMXW3TJ38NHpNBin6y7Q==
dependencies:
"@sentry/types" "5.18.1"
"@sentry/types" "5.19.0"
tslib "^1.9.3"
"@types/anymatch@*":
@ -1267,6 +1262,11 @@
resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-5.0.0.tgz#7532440c138605ced1b555935c3115ddd20e8bef"
integrity sha512-q95SP4FdkmF0CwO0F2q0H6ZgudsApaY/yCtAQNRn1gduef5fGpyEphzy0YCq/N0UFvDSnLg5V8jFK/YGXlDiCw==
"@types/invariant@^2.2.33":
version "2.2.33"
resolved "https://registry.yarnpkg.com/@types/invariant/-/invariant-2.2.33.tgz#ec5eec29c63bf5e4ca164e9feb3ef7337cdcbadb"
integrity sha512-/jUNmS8d4bCKdqslfxW6dg/9Gksfzxz67IYfqApHn+HvHlMVXwYv2zpTDnS/yaK9BB0i0GlBTaYci0EFE62Hmw==
"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0":
version "2.0.2"
resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.2.tgz#79d7a78bad4219f4c03d6557a1c72d9ca6ba62d5"
@ -1305,6 +1305,18 @@
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4=
"@types/lodash-es@^4.17.3":
version "4.17.3"
resolved "https://registry.yarnpkg.com/@types/lodash-es/-/lodash-es-4.17.3.tgz#87eb0b3673b076b8ee655f1890260a136af09a2d"
integrity sha512-iHI0i7ZAL1qepz1Y7f3EKg/zUMDwDfTzitx+AlHhJJvXwenP682ZyGbgPSc5Ej3eEAKVbNWKFuwOadCj5vBbYQ==
dependencies:
"@types/lodash" "*"
"@types/lodash@*":
version "4.14.157"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.157.tgz#fdac1c52448861dfde1a2e1515dbc46e54926dc8"
integrity sha512-Ft5BNFmv2pHDgxV5JDsndOWTRJ+56zte0ZpYLowp03tW+K+t8u8YMOzAnpuqPgzX6WO1XpDIUm7u04M8vdDiVQ==
"@types/minimatch@*":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
@ -2737,21 +2749,21 @@ copy-descriptor@^0.1.0:
resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=
copy-webpack-plugin@^6.0.2:
version "6.0.2"
resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-6.0.2.tgz#10efc6ad219a61acbf2f5fb50af83da38431bc34"
integrity sha512-9Gm8X0c6eXlKnmltMPFCBeGOKjtcRIyTt4VaO3k1TkNgVTe5Ov2lYsYVuyLp0kp8DItO3apewflM+1GYgh6V2Q==
copy-webpack-plugin@^6.0.3:
version "6.0.3"
resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-6.0.3.tgz#2b3d2bfc6861b96432a65f0149720adbd902040b"
integrity sha512-q5m6Vz4elsuyVEIUXr7wJdIdePWTubsqVbEMvf1WQnHGv0Q+9yPRu7MtYFPt+GBOXRav9lvIINifTQ1vSCs+eA==
dependencies:
cacache "^15.0.4"
fast-glob "^3.2.2"
fast-glob "^3.2.4"
find-cache-dir "^3.3.1"
glob-parent "^5.1.1"
globby "^11.0.1"
loader-utils "^2.0.0"
normalize-path "^3.0.0"
p-limit "^2.3.0"
p-limit "^3.0.1"
schema-utils "^2.7.0"
serialize-javascript "^3.1.0"
serialize-javascript "^4.0.0"
webpack-sources "^1.4.3"
core-js-compat@^3.6.2:
@ -3616,10 +3628,10 @@ eslint-plugin-react-hooks@^4.0.0:
resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.0.5.tgz#4879003aa38e5d05d0312175beb6e4a1f617bfcf"
integrity sha512-3YLSjoArsE2rUwL8li4Yxx1SUg3DQWp+78N3bcJQGWVZckcp+yeQGsap/MSq05+thJk57o+Ww4PtZukXGL02TQ==
eslint-plugin-react@7.x:
version "7.20.2"
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.20.2.tgz#b0d72abcd94c59c842338aa09c800808219ea77d"
integrity sha512-J3BdtsPNbcF/CG9HdyLx7jEtC7tuODODGldkS9P1zU2WMoHPdcsN2enUopgIaec5f9eYhSFI5zQAaWA/dgv2zw==
eslint-plugin-react@^7.20.3:
version "7.20.3"
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.20.3.tgz#0590525e7eb83890ce71f73c2cf836284ad8c2f1"
integrity sha512-txbo090buDeyV0ugF3YMWrzLIUqpYTsWSDZV9xLSmExE1P/Kmgg9++PD931r+KEWS66O1c9R4srLVVHmeHpoAg==
dependencies:
array-includes "^3.1.1"
array.prototype.flatmap "^1.2.3"
@ -3676,10 +3688,10 @@ eslint-visitor-keys@^1.2.0:
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.2.0.tgz#74415ac884874495f78ec2a97349525344c981fa"
integrity sha512-WFb4ihckKil6hu3Dp798xdzSfddwKKU3+nGniKF6HfeW6OLd2OUDEPP7TcHtB5+QXOKg2s6B2DaMPE1Nn/kxKQ==
eslint@^7.3.1:
version "7.3.1"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.3.1.tgz#76392bd7e44468d046149ba128d1566c59acbe19"
integrity sha512-cQC/xj9bhWUcyi/RuMbRtC3I0eW8MH0jhRELSvpKYkWep3C6YZ2OkvcvJVUeO6gcunABmzptbXBuDoXsjHmfTA==
eslint@^7.4.0:
version "7.4.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.4.0.tgz#4e35a2697e6c1972f9d6ef2b690ad319f80f206f"
integrity sha512-gU+lxhlPHu45H3JkEGgYhWhkR9wLHHEXC9FbWFnTlEkbKyZKWgWRLgf61E8zWmBuI6g5xKBph9ltg3NtZMVF8g==
dependencies:
"@babel/code-frame" "^7.0.0"
ajv "^6.10.0"
@ -3917,7 +3929,7 @@ fast-glob@^2.0.2:
merge2 "^1.2.3"
micromatch "^3.1.10"
fast-glob@^3.1.1, fast-glob@^3.2.2:
fast-glob@^3.1.1:
version "3.2.2"
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.2.tgz#ade1a9d91148965d4bf7c51f72e1ca662d32e63d"
integrity sha512-UDV82o4uQyljznxwMxyVRJgZZt3O5wENYojjzbaGEGZgeOxkLFf+V4cnUD+krzb2F72E18RhamkMZ7AdeggF7A==
@ -3929,6 +3941,18 @@ fast-glob@^3.1.1, fast-glob@^3.2.2:
micromatch "^4.0.2"
picomatch "^2.2.1"
fast-glob@^3.2.4:
version "3.2.4"
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.4.tgz#d20aefbf99579383e7f3cc66529158c9b98554d3"
integrity sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ==
dependencies:
"@nodelib/fs.stat" "^2.0.2"
"@nodelib/fs.walk" "^1.2.3"
glob-parent "^5.1.0"
merge2 "^1.3.0"
micromatch "^4.0.2"
picomatch "^2.2.1"
fast-json-stable-stringify@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
@ -4137,10 +4161,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.5:
version "5.0.5"
resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-5.0.5.tgz#4cf77f925008464db4670750a6e9266a4d25a988"
integrity sha512-2vpP+irspLOpZ1pcH3K3yR76MWXVSOYKTGwhwFQGnWjOs5MxzPN+dyDh1tNoaD5DUFP5kr4lGQURE9Qk+DpiUQ==
fork-ts-checker-webpack-plugin@^5.0.6:
version "5.0.6"
resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-5.0.6.tgz#02af713af02e47b338a3992279209bc65d34c773"
integrity sha512-8h4S7WANr69Resw+tMd5U23xdbVPsT+VOeMKQhUaKhkGeMm0fNf7ObJPP81WG+tsmy4LK4ayIf0JXBY+hy6vMw==
dependencies:
"@babel/code-frame" "^7.8.3"
chalk "^2.4.1"
@ -6144,7 +6168,7 @@ p-limit@^1.1.0:
dependencies:
p-try "^1.0.0"
p-limit@^2.0.0, p-limit@^2.2.0, p-limit@^2.3.0:
p-limit@^2.0.0, p-limit@^2.2.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"
integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==
@ -7136,6 +7160,13 @@ react-modal@^3.11.1:
react-lifecycles-compat "^3.0.0"
warning "^4.0.3"
react-query@^2.4.13:
version "2.4.13"
resolved "https://registry.yarnpkg.com/react-query/-/react-query-2.4.13.tgz#478f4f32a8c6b56de0e7bb6eb3cd2baa812e1ebb"
integrity sha512-PFUmChSy3kph+r0c+oz7HgBiKi98bb1D8FeeBZ1qiuuGXUJYhWCvOST+lOgwtLiBbT1wdoNjObFC35jpHbSvUQ==
dependencies:
ts-toolbelt "^6.9.4"
react-refresh@^0.8.2:
version "0.8.3"
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f"
@ -7164,12 +7195,10 @@ react-switch@^5.0.1:
dependencies:
prop-types "^15.6.2"
react-table@^7.2.1:
version "7.2.1"
resolved "https://registry.yarnpkg.com/react-table/-/react-table-7.2.1.tgz#d85f5a59b6441ccd9e0acddc93c288c54a7de7ee"
integrity sha512-cICU3w5yFWTDluz9yFePziEQXGt3PK5R1Va3InP7qMGnZOKm8kv0GiDMli+VOqE1uM1dBYPb9BEad15up1ac6g==
dependencies:
"@scarf/scarf" "^1.0.4"
react-table@^7.2.2:
version "7.2.2"
resolved "https://registry.yarnpkg.com/react-table/-/react-table-7.2.2.tgz#2978a903f0e367e1ceab25d60a6a40ae23498f77"
integrity sha512-JBE3QUyRQJkHRU72NKo7HRGtSGcrzIcVLDbsoZI+KhWkFEZ2t9UfpqeHBdbc3R5RRJZ6qC7+NzZZxJPSYWKPBQ==
react-tabs@^3.1.0:
version "3.1.1"
@ -7654,13 +7683,6 @@ serialize-javascript@^2.1.2:
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-2.1.2.tgz#ecec53b0e0317bdc95ef76ab7074b7384785fa61"
integrity sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==
serialize-javascript@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-3.1.0.tgz#8bf3a9170712664ef2561b44b691eafe399214ea"
integrity sha512-JIJT1DGiWmIKhzRsG91aS6Ze4sFUrYbltlkg2onR5OrnNM02Kl/hnY/T4FN2omvyeBbQmMJv+K4cPOpGzOTFBg==
dependencies:
randombytes "^2.1.0"
serialize-javascript@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-4.0.0.tgz#b525e1238489a5ecfc42afacc3fe99e666f4b1aa"
@ -8418,6 +8440,11 @@ ts-loader@^7.0.5:
micromatch "^4.0.0"
semver "^6.0.0"
ts-toolbelt@^6.9.4:
version "6.9.9"
resolved "https://registry.yarnpkg.com/ts-toolbelt/-/ts-toolbelt-6.9.9.tgz#e6cfd8ec7d425d2a06bda3b4fe9577ceaf2abda8"
integrity sha512-5a8k6qfbrL54N4Dw+i7M6kldrbjgDWb5Vit8DnT+gwThhvqMg8KtxLE5Vmnft+geIgaSOfNJyAcnmmlflS+Vdg==
tsconfig-paths@^3.9.0:
version "3.9.0"
resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz#098547a6c4448807e8fcb8eae081064ee9a3c90b"
@ -8485,10 +8512,10 @@ typeface-roboto-mono@^0.0.75:
resolved "https://registry.yarnpkg.com/typeface-roboto-mono/-/typeface-roboto-mono-0.0.75.tgz#00c4543662066a837c014c251ee5140fb66a5ad8"
integrity sha512-dYfyXd6HrKyMC/PuBAAtay0tZKsBrzxIW/fBY325vLxFfi/IDKSuyTkWxkU4lyZV6KPHetFnJ661PNXzz2FS/w==
typescript@^3.9.5:
version "3.9.5"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.5.tgz#586f0dba300cde8be52dd1ac4f7e1009c1b13f36"
integrity sha512-hSAifV3k+i6lEoCJ2k6R2Z/rp/H3+8sdmcn5NrS3/3kE7+RyZXm9aqvxWqjEXHAd8b0pShatpcdMTvEdvAJltQ==
typescript@^3.9.6:
version "3.9.6"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.6.tgz#8f3e0198a34c3ae17091b35571d3afd31999365a"
integrity sha512-Pspx3oKAPJtjNwE92YS05HQoY7z2SFyOpHo9MqJor3BXAGNaPUs83CuVp9VISFkSjyRfiTpmKuAYGJB7S7hOxw==
unicode-canonical-property-names-ecmascript@^1.0.4:
version "1.0.4"