From 594e6b4e008f3aadc4bddd0e77e8af469fd57832 Mon Sep 17 00:00:00 2001 From: Domingo Dirutigliano Date: Sun, 24 Sep 2023 17:05:55 +0200 Subject: [PATCH] firewall Frontend Improvements --- backend/utils/__init__.py | 2 +- frontend/package.json | 2 + frontend/src/components/Firewall/utils.ts | 4 +- frontend/src/js/utils.tsx | 4 +- .../pages/Firewall/DndListHandle.module.scss | 24 ++ frontend/src/pages/Firewall/index.tsx | 245 +++++++++++++++++- frontend/src/pages/NFRegex/index.tsx | 15 +- frontend/src/pages/PortHijack/index.tsx | 15 +- frontend/src/pages/RegexProxy/index.tsx | 11 + frontend/yarn.lock | 93 ++++++- 10 files changed, 389 insertions(+), 26 deletions(-) create mode 100644 frontend/src/pages/Firewall/DndListHandle.module.scss diff --git a/backend/utils/__init__.py b/backend/utils/__init__.py index 788ed11..644f6b7 100644 --- a/backend/utils/__init__.py +++ b/backend/utils/__init__.py @@ -15,7 +15,7 @@ ON_DOCKER = len(sys.argv) > 1 and sys.argv[1] == "DOCKER" DEBUG = len(sys.argv) > 1 and sys.argv[1] == "DEBUG" FIREGEX_PORT = int(os.getenv("PORT","4444")) JWT_ALGORITHM: str = "HS256" -API_VERSION = "2.0.0" +API_VERSION = "2.2.0" PortType = Annotated[int, Path(gt=0, lt=65536)] diff --git a/frontend/package.json b/frontend/package.json index 747515a..44f4f96 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -4,6 +4,7 @@ "private": true, "dependencies": { "@emotion/react": "^11.11.0", + "@hello-pangea/dnd": "^16.3.0", "@mantine/core": "^6.0.13", "@mantine/form": "^6.0.13", "@mantine/hooks": "^6.0.13", @@ -21,6 +22,7 @@ "@types/react": "^18.0.12", "@types/react-dom": "^18.0.5", "buffer": "^6.0.3", + "clsx": "^2.0.0", "react": "^18.1.0", "react-dom": "^18.1.0", "react-icons": "^4.4.0", diff --git a/frontend/src/components/Firewall/utils.ts b/frontend/src/components/Firewall/utils.ts index b51e7a7..a84dcbb 100644 --- a/frontend/src/components/Firewall/utils.ts +++ b/frontend/src/components/Firewall/utils.ts @@ -50,7 +50,7 @@ export type ServerResponseListed = { } export const rulesQueryKey = ["firewall","rules"] -export const firewallRulesQuery = () => useQuery({queryKey:rulesQueryKey, queryFn:firewall.rules}) +export const firewallRulesQuery = () => useQuery({queryKey:rulesQueryKey, queryFn:firewall.rules, refetchInterval: false}) export const firewall = { rules: async() => { @@ -72,7 +72,7 @@ export const firewall = { const { status } = await postapi(`firewall/rule/${rule_id}/rename`,{ name }) as ServerResponse; return status === "ok"?undefined:status }, - servicesadd: async (data:RuleAddForm) => { + ruleset: async (data:RuleAddForm) => { return await postapi("firewall/rules/set", data) as ServerResponseListed; } } \ No newline at end of file diff --git a/frontend/src/js/utils.tsx b/frontend/src/js/utils.tsx index 355e429..a19c0c0 100644 --- a/frontend/src/js/utils.tsx +++ b/frontend/src/js/utils.tsx @@ -97,8 +97,8 @@ export function getapiobject(){ export function HomeRedirector(){ const section = sessionStorage.getItem("home_section") - const path = section?`/${section}`:`/nfregex` - return + const path = section?`/${section}`:`/firewall` + return } export function fireUpdateRequest(){ //TODO: change me: specify what to update diff --git a/frontend/src/pages/Firewall/DndListHandle.module.scss b/frontend/src/pages/Firewall/DndListHandle.module.scss new file mode 100644 index 0000000..c9fc138 --- /dev/null +++ b/frontend/src/pages/Firewall/DndListHandle.module.scss @@ -0,0 +1,24 @@ +.item { + display: flex; + align-items: center; + border-radius: var(--mantine-radius-md); + border: rem(1px) solid light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-5)); + padding: var(--mantine-spacing-sm) var(--mantine-spacing-xl); + padding-left: calc(var(--mantine-spacing-xl) - var(--mantine-spacing-md)); + background-color: light-dark(var(--mantine-color-white), var(--mantine-color-dark-5)); + margin-bottom: var(--mantine-spacing-sm); +} + +.itemDragging { + box-shadow: var(--mantine-shadow-sm); +} + +.dragHandle { + display: flex; + align-items: center; + justify-content: center; + height: 100%; + color: light-dark(var(--mantine-color-gray-6), var(--mantine-color-dark-1)); + padding-left: var(--mantine-spacing-md); + padding-right: var(--mantine-spacing-md); +} \ No newline at end of file diff --git a/frontend/src/pages/Firewall/index.tsx b/frontend/src/pages/Firewall/index.tsx index f4671f1..0a9b868 100644 --- a/frontend/src/pages/Firewall/index.tsx +++ b/frontend/src/pages/Firewall/index.tsx @@ -1,42 +1,261 @@ -import { ActionIcon, Badge, LoadingOverlay, Space, Title, Tooltip } from "@mantine/core" +import { ActionIcon, Badge, LoadingOverlay, NativeSelect, SegmentedControl, Space, Switch, Title, Tooltip } from "@mantine/core" import { useEffect, useState } from "react"; import { BsPlusLg } from "react-icons/bs" -import { firewall, firewallRulesQuery } from "../../components/Firewall/utils"; -import { errorNotify, getErrorMessage } from "../../js/utils"; - +import { rem, Text } from '@mantine/core'; +import { ActionType, Rule, firewall, firewallRulesQuery } from "../../components/Firewall/utils"; +import cx from 'clsx' +import { errorNotify, getErrorMessage, okNotify } from "../../js/utils"; +import { useListState } from '@mantine/hooks'; +import { DragDropContext, Droppable, Draggable } from '@hello-pangea/dnd'; +import { TbGripVertical, TbReload } from "react-icons/tb"; +import classes from './DndListHandle.module.scss'; +import { useQueryClient } from "@tanstack/react-query"; +import { TiTick } from "react-icons/ti"; +import YesNoModal from "../../components/YesNoModal"; + /* + { + "rules": [ + { + "active": true, + "name": "R1", + "proto": "tcp", + "ip_src": "0.0.0.0/0", + "ip_dst": "0.0.0.0/0", + "port_src_from": 1, + "port_dst_from": 3030, + "port_src_to": 65535, + "port_dst_to": 3030, + "action": "reject", + "mode": "I" + }, + { + "active": true, + "name": "R2", + "proto": "tcp", + "ip_src": "0.0.0.0/0", + "ip_dst": "0.0.0.0/0", + "port_src_from": 1, + "port_dst_from": 3030, + "port_src_to": 65535, + "port_dst_to": 3030, + "action": "drop", + "mode": "O" + }, + { + "active": false, + "name": "R3", + "proto": "udp", + "ip_src": "192.168.0.1/24", + "ip_dst": "0.0.0.0/0", + "port_src_from": 1, + "port_dst_from": 2020, + "port_src_to": 65535, + "port_dst_to": 2020, + "action": "drop", + "mode": "I" + }, + { + "active": true, + "name": "R4", + "proto": "any", + "ip_src": "::/0", + "ip_dst": "fdfd::ffff:123/64", + "port_src_from": 1, + "port_dst_from": 1, + "port_src_to": 1, + "port_dst_to": 1, + "action": "accept", + "mode": "I" + } + ], + "policy": "accept" +} + */ export const Firewall = () => { const [tooltipAddOpened, setTooltipAddOpened] = useState(false); + const [tooltipRefreshOpened, setTooltipRefreshOpened] = useState(false); + const [tooltipApplyOpened, setTooltipApplyOpened] = useState(false); const [open, setOpen] = useState(false); - + const [currentPolicy, setCurrentPolicy] = useState(ActionType.ACCEPT) + const queryClient = useQueryClient() const rules = firewallRulesQuery() - + const [state, handlers] = useListState([]); + const [enableFwModal, setEnableFwModal] = useState(false) + const [applyChangeModal, setApplyChangeModal] = useState(false) useEffect(()=> { if(rules.isError) errorNotify("Firewall Update failed!", getErrorMessage(rules.error)) },[rules.isError]) + useEffect(()=> { + if(!rules.isLoading && rules.isSuccess) + handlers.setState(JSON.parse(JSON.stringify(rules.data?.rules??[]))) + },[rules.isSuccess, rules.isLoading]) + + const fwEnabled = rules.data?.enabled??false + const valuesChanged = JSON.stringify(rules.data?.rules) != JSON.stringify(state) || rules.data?.policy != currentPolicy + + const enableFirewall = () => { + if (valuesChanged){ + applyChangesRaw().then(()=>enableFirewallRaw()) + }else{ + enableFirewallRaw() + } + } + + const enableFirewallRaw = () => { + return firewall.enable() + .then(()=>okNotify("Firewall enabled", "The firewall has been enabled")) + .catch((e)=>errorNotify("Firewall enable failed!", getErrorMessage(e))) + } + + const disableFirewallRaw = () => { + return firewall.disable() + .then(()=>okNotify("Firewall disabled", "The firewall has been disabled")) + .catch((e)=>errorNotify("Firewall disable failed!", getErrorMessage(e))) + } + + const switchState = () => { + if (fwEnabled) + disableFirewallRaw() + else + if ([ActionType.REJECT, ActionType.DROP].includes(currentPolicy) || valuesChanged){ + setEnableFwModal(true) + }else{ + enableFirewall() + } + } + + const applyChanges = () => { + if (fwEnabled && rules.data?.policy == ActionType.ACCEPT && [ActionType.REJECT, ActionType.DROP].includes(currentPolicy)){ + setApplyChangeModal(true) + }else{ + applyChangesRaw() + } + } + + const applyChangesRaw = () => { + return firewall.ruleset({rules:state, policy:currentPolicy}) + .then(()=>okNotify("Firewall rules applied", "The firewall rules has been applied")) + .catch((e)=>errorNotify("Firewall rules apply failed!", getErrorMessage(e))) + } + + const items = state.map((item, index) => ( + + {(provided, snapshot) => ( +
+
+ +
+ + +
+ {item.name} + + {JSON.stringify(item)} + +
+
+ )} +
+ )); + + return <> +
- Firewall Rules -
+ Firewall Rules
+ Enabled: + + Policy: + + setCurrentPolicy(value as ActionType)} + /> + Rules: {rules.isLoading?0:rules.data?.rules.length} - Policy: {rules.isLoading?"unknown":rules.data?.policy} - - Enabled: {rules.isLoading?"?":(rules.data?.enabled?"🟢":"🔴")} - setOpen(true)} size="lg" radius="md" variant="filled" onFocus={() => setTooltipAddOpened(false)} onBlur={() => setTooltipAddOpened(false)} onMouseEnter={() => setTooltipAddOpened(true)} onMouseLeave={() => setTooltipAddOpened(false)}> + + + queryClient.invalidateQueries(["firewall"])} size="lg" radius="md" variant="filled" + loading={rules.isFetching} + onFocus={() => setTooltipRefreshOpened(false)} onBlur={() => setTooltipRefreshOpened(false)} + onMouseEnter={() => setTooltipRefreshOpened(true)} onMouseLeave={() => setTooltipRefreshOpened(false)}> + + + + setTooltipApplyOpened(false)} onBlur={() => setTooltipApplyOpened(false)} + onMouseEnter={() => setTooltipApplyOpened(true)} onMouseLeave={() => setTooltipApplyOpened(false)} + disabled={!valuesChanged} + > +
- + + + + handlers.reorder({ from: source.index, to: destination?.index || 0 }) + } + > + + {(provided) => ( +
+ {items} + {provided.placeholder} +
+ )} +
+
+ + setEnableFwModal(false) } + action={enableFirewall} + opened={enableFwModal} + /> + + setApplyChangeModal(false) } + action={applyChangesRaw} + opened={applyChangeModal} + /> + } \ No newline at end of file diff --git a/frontend/src/pages/NFRegex/index.tsx b/frontend/src/pages/NFRegex/index.tsx index 78bf252..e1236bc 100644 --- a/frontend/src/pages/NFRegex/index.tsx +++ b/frontend/src/pages/NFRegex/index.tsx @@ -7,6 +7,8 @@ import { nfregexServiceQuery } from '../../components/NFRegex/utils'; import { errorNotify, getErrorMessage } from '../../js/utils'; import AddNewService from '../../components/NFRegex/AddNewService'; import AddNewRegex from '../../components/AddNewRegex'; +import { useQueryClient } from '@tanstack/react-query'; +import { TbReload } from 'react-icons/tb'; function NFRegex({ children }: { children: any }) { @@ -14,16 +16,16 @@ function NFRegex({ children }: { children: any }) { const navigator = useNavigate() const [open, setOpen] = useState(false); const {srv} = useParams() + const queryClient = useQueryClient() + const [tooltipRefreshOpened, setTooltipRefreshOpened] = useState(false); const [tooltipAddServOpened, setTooltipAddServOpened] = useState(false); const [tooltipAddOpened, setTooltipAddOpened] = useState(false); const services = nfregexServiceQuery() useEffect(()=> { - if(services.isError){ + if(services.isError) errorNotify("NFRegex Update failed!", getErrorMessage(services.error)) - } - },[services.isError]) const closeModal = () => {setOpen(false);} @@ -51,6 +53,13 @@ function NFRegex({ children }: { children: any }) { onMouseEnter={() => setTooltipAddOpened(true)} onMouseLeave={() => setTooltipAddOpened(false)}> } + + + queryClient.invalidateQueries(["nfregex"])} size="lg" radius="md" variant="filled" + loading={services.isFetching} + onFocus={() => setTooltipRefreshOpened(false)} onBlur={() => setTooltipRefreshOpened(false)} + onMouseEnter={() => setTooltipRefreshOpened(true)} onMouseLeave={() => setTooltipRefreshOpened(false)}> +
{srv?null:<> diff --git a/frontend/src/pages/PortHijack/index.tsx b/frontend/src/pages/PortHijack/index.tsx index dc469a4..6d01a2b 100644 --- a/frontend/src/pages/PortHijack/index.tsx +++ b/frontend/src/pages/PortHijack/index.tsx @@ -5,6 +5,8 @@ import ServiceRow from '../../components/PortHijack/ServiceRow'; import { porthijackServiceQuery } from '../../components/PortHijack/utils'; import { errorNotify, getErrorMessage } from '../../js/utils'; import AddNewService from '../../components/PortHijack/AddNewService'; +import { useQueryClient } from '@tanstack/react-query'; +import { TbReload } from 'react-icons/tb'; function PortHijack() { @@ -12,14 +14,15 @@ function PortHijack() { const [open, setOpen] = useState(false); const [tooltipAddServOpened, setTooltipAddServOpened] = useState(false); const [tooltipAddOpened, setTooltipAddOpened] = useState(false); + const queryClient = useQueryClient() + const [tooltipRefreshOpened, setTooltipRefreshOpened] = useState(false); const services = porthijackServiceQuery() useEffect(()=>{ - if(services.isError){ + if(services.isError) errorNotify("Porthijack Update failed!", getErrorMessage(services.error)) - } },[services.isError]) const closeModal = () => {setOpen(false);} @@ -36,6 +39,13 @@ function PortHijack() { onFocus={() => setTooltipAddOpened(false)} onBlur={() => setTooltipAddOpened(false)} onMouseEnter={() => setTooltipAddOpened(true)} onMouseLeave={() => setTooltipAddOpened(false)}> + + + queryClient.invalidateQueries(["porthijack"])} size="lg" radius="md" variant="filled" + loading={services.isFetching} + onFocus={() => setTooltipRefreshOpened(false)} onBlur={() => setTooltipRefreshOpened(false)} + onMouseEnter={() => setTooltipRefreshOpened(true)} onMouseLeave={() => setTooltipRefreshOpened(false)}> +
@@ -52,7 +62,6 @@ function PortHijack() { }
- } diff --git a/frontend/src/pages/RegexProxy/index.tsx b/frontend/src/pages/RegexProxy/index.tsx index 121f0e4..906c42d 100644 --- a/frontend/src/pages/RegexProxy/index.tsx +++ b/frontend/src/pages/RegexProxy/index.tsx @@ -7,6 +7,8 @@ import { regexproxyServiceQuery } from '../../components/RegexProxy/utils'; import { errorNotify, getErrorMessage } from '../../js/utils'; import AddNewService from '../../components/RegexProxy/AddNewService'; import AddNewRegex from '../../components/AddNewRegex'; +import { useQueryClient } from '@tanstack/react-query'; +import { TbReload } from 'react-icons/tb'; function RegexProxy({ children }: { children: any }) { @@ -16,6 +18,8 @@ function RegexProxy({ children }: { children: any }) { const {srv} = useParams() const [tooltipAddServOpened, setTooltipAddServOpened] = useState(false); const [tooltipAddOpened, setTooltipAddOpened] = useState(false); + const queryClient = useQueryClient() + const [tooltipRefreshOpened, setTooltipRefreshOpened] = useState(false); const services = regexproxyServiceQuery() @@ -50,6 +54,13 @@ function RegexProxy({ children }: { children: any }) { onMouseEnter={() => setTooltipAddOpened(true)} onMouseLeave={() => setTooltipAddOpened(false)}> } + + + queryClient.invalidateQueries(["regexproxy"])} size="lg" radius="md" variant="filled" + loading={services.isFetching} + onFocus={() => setTooltipRefreshOpened(false)} onBlur={() => setTooltipRefreshOpened(false)} + onMouseEnter={() => setTooltipRefreshOpened(true)} onMouseLeave={() => setTooltipRefreshOpened(false)}> +
{srv?null:<> diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 4e0eea8..fb562c6 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -188,6 +188,13 @@ dependencies: regenerator-runtime "^0.13.11" +"@babel/runtime@^7.12.1", "@babel/runtime@^7.22.5": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.15.tgz#38f46494ccf6cf020bd4eed7124b425e83e523b8" + integrity sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/template@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.5.tgz#0c8c4d944509875849bd0344ff0050756eefc6ec" @@ -448,6 +455,19 @@ aria-hidden "^1.1.3" tabbable "^6.0.1" +"@hello-pangea/dnd@^16.3.0": + version "16.3.0" + resolved "https://registry.yarnpkg.com/@hello-pangea/dnd/-/dnd-16.3.0.tgz#3776212f812df4e8e69c42831ec8ab7ff3a087d6" + integrity sha512-RYQ/K8shtJoyNPvFWz0gfXIK7HF3P3mL9UZFGMuHB0ljRSXVgMjVFI/FxcZmakMzw6tO7NflWLriwTNBow/4vw== + dependencies: + "@babel/runtime" "^7.22.5" + css-box-model "^1.2.1" + memoize-one "^6.0.0" + raf-schd "^4.0.3" + react-redux "^8.1.1" + redux "^4.2.1" + use-memo-one "^1.1.3" + "@jest/expect-utils@^29.5.0": version "29.5.0" resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.5.0.tgz#f74fad6b6e20f924582dc8ecbf2cb800fe43a036" @@ -873,6 +893,14 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.1.tgz#aa22750962f3bf0e79d753d3cc067f010c95f194" integrity sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA== +"@types/hoist-non-react-statics@^3.3.1": + version "3.3.2" + resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#dc1e9ded53375d37603c479cc12c693b0878aa2a" + integrity sha512-YIQtIg4PKr7ZyqNPZObpxfHsHEmuB8dXCxd6qVcGuQVDK2bpsF7bYNnBJ4Nn7giuACZg+WewExgrtAJ3XnA4Xw== + dependencies: + "@types/react" "*" + hoist-non-react-statics "^3.3.0" + "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0": version "2.0.4" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44" @@ -956,6 +984,11 @@ dependencies: "@types/jest" "*" +"@types/use-sync-external-store@^0.0.3": + version "0.0.3" + resolved "https://registry.yarnpkg.com/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz#b6725d5f4af24ace33b36fafd295136e75509f43" + integrity sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA== + "@types/yargs-parser@*": version "21.0.0" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b" @@ -1166,6 +1199,11 @@ clsx@1.1.1: resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188" integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA== +clsx@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.0.0.tgz#12658f3fd98fafe62075595a5c30e43d18f3d00b" + integrity sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q== + color-convert@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" @@ -1223,6 +1261,13 @@ cosmiconfig@^8.1.3: parse-json "^5.0.0" path-type "^4.0.0" +css-box-model@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/css-box-model/-/css-box-model-1.2.1.tgz#59951d3b81fd6b2074a62d49444415b0d2b4d7c1" + integrity sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw== + dependencies: + tiny-invariant "^1.0.6" + css.escape@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb" @@ -1556,7 +1601,7 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" -hoist-non-react-statics@^3.3.1: +hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== @@ -1900,6 +1945,11 @@ lz-string@^1.5.0: resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.5.0.tgz#c1ab50f77887b712621201ba9fd4e3a6ed099941" integrity sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ== +memoize-one@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-6.0.0.tgz#b2591b871ed82948aee4727dc6abceeeac8c1045" + integrity sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw== + micromatch@^4.0.4: version "4.0.5" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" @@ -2044,6 +2094,11 @@ prop-types@^15.6.2: object-assign "^4.1.1" react-is "^16.13.1" +raf-schd@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/raf-schd/-/raf-schd-4.0.3.tgz#5d6c34ef46f8b2a0e880a8fcdb743efc5bfdbc1a" + integrity sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ== + react-dom@^18.1.0: version "18.2.0" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d" @@ -2072,6 +2127,18 @@ react-is@^18.0.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== +react-redux@^8.1.1: + version "8.1.2" + resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-8.1.2.tgz#9076bbc6b60f746659ad6d51cb05de9c5e1e9188" + integrity sha512-xJKYI189VwfsFc4CJvHqHlDrzyFTY/3vZACbE+rr/zQ34Xx1wQfB4OTOSeOSNrF6BDVe8OOdxIrAnMGXA3ggfw== + dependencies: + "@babel/runtime" "^7.12.1" + "@types/hoist-non-react-statics" "^3.3.1" + "@types/use-sync-external-store" "^0.0.3" + hoist-non-react-statics "^3.3.2" + react-is "^18.0.0" + use-sync-external-store "^1.0.0" + react-refresh@^0.14.0: version "0.14.0" resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.0.tgz#4e02825378a5f227079554d4284889354e5f553e" @@ -2161,11 +2228,23 @@ redent@^3.0.0: indent-string "^4.0.0" strip-indent "^3.0.0" +redux@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.1.tgz#c08f4306826c49b5e9dc901dee0452ea8fce6197" + integrity sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w== + dependencies: + "@babel/runtime" "^7.9.2" + regenerator-runtime@^0.13.11: version "0.13.11" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== +regenerator-runtime@^0.14.0: + version "0.14.0" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz#5e19d68eb12d486f797e15a3c6a918f7cec5eb45" + integrity sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA== + regexp.prototype.flags@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz#fe7ce25e7e4cca8db37b6634c8a2c7009199b9cb" @@ -2326,6 +2405,11 @@ tabbable@^6.0.1: resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-6.2.0.tgz#732fb62bc0175cfcec257330be187dcfba1f3b97" integrity sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew== +tiny-invariant@^1.0.6: + version "1.3.1" + resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.1.tgz#8560808c916ef02ecfd55e66090df23a4b7aa642" + integrity sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw== + to-fast-properties@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" @@ -2385,6 +2469,11 @@ use-latest@^1.2.1: dependencies: use-isomorphic-layout-effect "^1.1.1" +use-memo-one@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/use-memo-one/-/use-memo-one-1.1.3.tgz#2fd2e43a2169eabc7496960ace8c79efef975e99" + integrity sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ== + use-sidecar@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.1.2.tgz#2f43126ba2d7d7e117aa5855e5d8f0276dfe73c2" @@ -2393,7 +2482,7 @@ use-sidecar@^1.1.2: detect-node-es "^1.1.0" tslib "^2.0.0" -use-sync-external-store@^1.2.0: +use-sync-external-store@^1.0.0, use-sync-external-store@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==