diff --git a/deepfence_frontend/apps/dashboard/package.json b/deepfence_frontend/apps/dashboard/package.json index 0b421f28da..1fb7253378 100644 --- a/deepfence_frontend/apps/dashboard/package.json +++ b/deepfence_frontend/apps/dashboard/package.json @@ -20,6 +20,7 @@ "@testing-library/jest-dom": "^5.16.5", "@testing-library/user-event": "^14.4.3", "classnames": "^2.3.2", + "lodash-es": "^4.17.21", "react": "^18.2.0", "react-dom": "^18.2.0", "react-icons": "^4.7.1", @@ -32,6 +33,7 @@ "@openapitools/openapi-generator-cli": "^2.5.2", "@playwright/test": "^1.28.1", "@testing-library/react": "^13.4.0", + "@types/lodash-es": "^4.17.6", "@types/react": "^18.0.24", "@types/react-dom": "^18.0.8", "@types/testing-library__jest-dom": "^5.14.5", diff --git a/deepfence_frontend/apps/dashboard/src/features/onboard/components/ConnectorHeader.tsx b/deepfence_frontend/apps/dashboard/src/features/onboard/components/ConnectorHeader.tsx index 7296b6c1cc..ecb3d507a5 100644 --- a/deepfence_frontend/apps/dashboard/src/features/onboard/components/ConnectorHeader.tsx +++ b/deepfence_frontend/apps/dashboard/src/features/onboard/components/ConnectorHeader.tsx @@ -6,28 +6,33 @@ import { Breadcrumb, BreadcrumbLink, Typography } from 'ui-components'; type ConnectorHeaderProps = { title: string; description: string; + endComponent?: JSX.Element; }; const canRoute = (pathname: string) => { const path = { addConnector: '/onboard/connectors/add-connectors', - scanResult: '', + configureScan: '', viewResult: '', }; - if (pathname.includes('view-scan-results')) { - path.viewResult = '/onboard/view-scan-results'; - path.scanResult = '/onboard/scan-infrastructure'; - } else if (pathname.includes('scan-infrastructure')) { - path.scanResult = '/onboard/scan-infrastructure'; + if (pathname.includes('view-summary')) { + path.viewResult = '/onboard/scan/view-summary'; + path.configureScan = '#'; + } else if (pathname.includes('scan/configure')) { + path.configureScan = '#'; path.viewResult = '#'; } else if (pathname.includes('connectors')) { - path.scanResult = '#'; + path.configureScan = '#'; path.viewResult = '#'; } return path; }; -export const ConnectorHeader = ({ title, description }: ConnectorHeaderProps) => { +export const ConnectorHeader = ({ + title, + description, + endComponent, +}: ConnectorHeaderProps) => { const location = useLocation(); const isAddConnectorRoutePath = () => { @@ -38,15 +43,18 @@ export const ConnectorHeader = ({ title, description }: ConnectorHeaderProps) => }; const isScanRoutePath = () => { - return location.pathname.includes('scan-infrastructure'); + return ( + location.pathname.startsWith('/onboard/scan/choose') || + location.pathname.startsWith('/onboard/scan/configure') + ); }; - const isViewResultsRoutePath = () => { - return location.pathname.includes('view-scan-results'); + const isViewScanSummaryRoutePath = () => { + return location.pathname.includes('scan/view-summary'); }; return ( -
+
} transparent> @@ -61,7 +69,7 @@ export const ConnectorHeader = ({ title, description }: ConnectorHeaderProps) => View Scan Results @@ -81,12 +89,17 @@ export const ConnectorHeader = ({ title, description }: ConnectorHeaderProps) =>
-

{title}

-

- {description} -

+
+
+

{title}

+

+ {description} +

+
+ {endComponent ?
{endComponent}
: null} +
); }; diff --git a/deepfence_frontend/apps/dashboard/src/features/onboard/components/connectors/registries/AmazonECRConnectionForm.tsx b/deepfence_frontend/apps/dashboard/src/features/onboard/components/connectors/registries/AmazonECRConnectionForm.tsx index 027da86397..92f5fa4c5b 100644 --- a/deepfence_frontend/apps/dashboard/src/features/onboard/components/connectors/registries/AmazonECRConnectionForm.tsx +++ b/deepfence_frontend/apps/dashboard/src/features/onboard/components/connectors/registries/AmazonECRConnectionForm.tsx @@ -66,8 +66,8 @@ export const AmazonECRConnectorForm = () => { placeholder="AWS Region" />
- diff --git a/deepfence_frontend/apps/dashboard/src/features/onboard/pages/AmazonECRConnector.tsx b/deepfence_frontend/apps/dashboard/src/features/onboard/pages/AmazonECRConnector.tsx index cabd5f067b..66d84a78c1 100644 --- a/deepfence_frontend/apps/dashboard/src/features/onboard/pages/AmazonECRConnector.tsx +++ b/deepfence_frontend/apps/dashboard/src/features/onboard/pages/AmazonECRConnector.tsx @@ -15,7 +15,7 @@ export const AmazonECRConnector = () => {
diff --git a/deepfence_frontend/apps/dashboard/src/features/onboard/pages/AzureConnector.tsx b/deepfence_frontend/apps/dashboard/src/features/onboard/pages/AzureConnector.tsx index 6cc84c68fe..b1c56e82ac 100644 --- a/deepfence_frontend/apps/dashboard/src/features/onboard/pages/AzureConnector.tsx +++ b/deepfence_frontend/apps/dashboard/src/features/onboard/pages/AzureConnector.tsx @@ -15,7 +15,7 @@ export const AzureConnector = () => { /> ); diff --git a/deepfence_frontend/apps/dashboard/src/features/onboard/pages/ChooseScan.tsx b/deepfence_frontend/apps/dashboard/src/features/onboard/pages/ChooseScan.tsx new file mode 100644 index 0000000000..5376da1744 --- /dev/null +++ b/deepfence_frontend/apps/dashboard/src/features/onboard/pages/ChooseScan.tsx @@ -0,0 +1,141 @@ +import { HiSwitchHorizontal } from 'react-icons/hi'; +import { Button, Card, Separator, Typography } from 'ui-components'; + +import LogoAws from '@/assets/logo-aws.svg'; +import LogoAwsWhite from '@/assets/logo-aws-white.svg'; +import { ConnectorHeader } from '@/features/onboard/components/ConnectorHeader'; +import { useTheme } from '@/theme/ThemeContext'; +import { usePageNavigation } from '@/utils/usePageNavigation'; + +type ScanTypeListProps = { + scanType: string; + description: string; + lastScaned: string; + buttonText: string; + redirect: string; +}; + +const scanTypes: ScanTypeListProps[] = [ + { + scanType: 'Vulnerability Scan', + description: `A few words about the compliance scan and why you need to use it.`, + lastScaned: '3:00pm on 11/22/2022', + buttonText: 'Configure Vulnerability Scan', + redirect: '/vulnerability', + }, + { + scanType: 'Compliance Scan', + description: `A few words about the compliance scan and why you need to use it.`, + lastScaned: '3:00pm on 11/22/2022', + buttonText: 'Configure Compliance Scan', + redirect: '/compliance', + }, + { + scanType: 'Secrets Scan', + description: `A few words about the compliance scan and why you need to use it.`, + lastScaned: '3:00pm on 11/22/2022', + buttonText: 'Configure Secret Scan', + redirect: '/secret', + }, +]; + +const SelectedAccount = () => { + const { mode } = useTheme(); + const { navigate } = usePageNavigation(); + return ( +
+ + logo + +
+ + Amazon Web Services (AWS) + + + Account Id: 22222 + +
+
+ +
+
+ ); +}; + +const ScanType = () => { + const { navigate } = usePageNavigation(); + const goNext = (path: string) => { + navigate(path); + }; + + return ( +
+ {scanTypes.map( + ({ + scanType, + description, + lastScaned, + buttonText, + redirect, + }: ScanTypeListProps) => { + return ( + +

+ {scanType} +

+ +

+ {description} +

+
+ Last scan: {lastScaned} +
+ +
+ ); + }, + )} +
+ ); +}; +export const ChooseScan = () => { + const { goBack } = usePageNavigation(); + return ( + <> + + + + + + ); +}; diff --git a/deepfence_frontend/apps/dashboard/src/features/onboard/pages/ComplianceScanConfigure.tsx b/deepfence_frontend/apps/dashboard/src/features/onboard/pages/ComplianceScanConfigure.tsx new file mode 100644 index 0000000000..70ae264f6b --- /dev/null +++ b/deepfence_frontend/apps/dashboard/src/features/onboard/pages/ComplianceScanConfigure.tsx @@ -0,0 +1,361 @@ +import { filter, find, isEmpty } from 'lodash-es'; +import { useEffect, useMemo, useState } from 'react'; +import { HiBan, HiLightBulb, HiMinusCircle, HiPlusCircle } from 'react-icons/hi'; +import { + Button, + createColumnHelper, + getRowSelectionColumn, + Switch, + Table, + Tabs, + Tooltip, + Typography, +} from 'ui-components'; + +import { ConnectorHeader } from '@/features/onboard/components/ConnectorHeader'; +import { usePageNavigation } from '@/utils/usePageNavigation'; + +type ColumnType = { + id: number; + test_category: string; + test_desc: string; + status: string; + is_enabled: boolean; +}; +const complianceTableData = [ + { + cloud_provider: 'aws', + compliance_check_type: 'cis', + id: 1, + is_enabled: true, + test_category: 'CloudWatch', + test_desc: + '4.1 Ensure a log metric filter and alarm exist for unauthorized API calls', + test_number: 'control.cis_v140_4_1', + status: 'Active', + }, + { + cloud_provider: 'aws', + compliance_check_type: 'cis', + id: 2, + is_enabled: true, + test_category: 'CloudWatch', + test_desc: + '4.2 Ensure a log metric filter and alarm exist for Management Console sign-in without MFA', + test_number: 'control.cis_v140_4_2', + status: 'Active', + }, + { + cloud_provider: 'aws', + compliance_check_type: 'cis', + id: 3, + is_enabled: true, + test_category: 'CloudWatch', + test_desc: + "4.3 Ensure a log metric filter and alarm exist for usage of 'root' account", + test_number: 'control.cis_v140_4_3', + status: 'Active', + }, + { + cloud_provider: 'aws', + compliance_check_type: 'cis', + id: 4, + is_enabled: true, + test_category: 'CloudWatch', + test_desc: '4.4 Ensure a log metric filter and alarm exist for IAM policy changes', + test_number: 'control.cis_v140_4_4', + status: 'Active', + }, + { + cloud_provider: 'aws', + compliance_check_type: 'cis', + id: 5, + is_enabled: true, + test_category: 'CloudWatch', + test_desc: + '4.5 Ensure a log metric filter and alarm exist for CloudTrail configuration changes', + test_number: 'control.cis_v140_4_5', + status: 'Active', + }, + { + cloud_provider: 'aws', + compliance_check_type: 'cis', + id: 6, + is_enabled: true, + test_category: 'CloudWatch', + test_desc: + '4.6 Ensure a log metric filter and alarm exist for AWS Management Console authentication failures', + test_number: 'control.cis_v140_4_6', + status: 'Active', + }, + { + cloud_provider: 'aws', + compliance_check_type: 'cis', + id: 7, + is_enabled: true, + test_category: 'CloudWatch', + test_desc: + '4.7 Ensure a log metric filter and alarm exist for disabling or scheduled deletion of customer created CMKs', + test_number: 'control.cis_v140_4_7', + status: 'Active', + }, + { + cloud_provider: 'aws', + compliance_check_type: 'cis', + id: 8, + is_enabled: true, + test_category: 'CloudWatch', + test_desc: + '4.8 Ensure a log metric filter and alarm exist for S3 bucket policy changes', + test_number: 'control.cis_v140_4_8', + status: 'Active', + }, + { + cloud_provider: 'aws', + compliance_check_type: 'cis', + id: 9, + is_enabled: true, + test_category: 'CloudWatch', + test_desc: + '4.9 Ensure a log metric filter and alarm exist for AWS Config configuration changes', + test_number: 'control.cis_v140_4_9', + status: 'Active', + }, + { + cloud_provider: 'aws', + compliance_check_type: 'cis', + id: 10, + is_enabled: true, + test_category: 'CloudWatch', + test_desc: + '4.10 Ensure a log metric filter and alarm exist for security group changes', + test_number: 'control.cis_v140_4_10', + status: 'Active', + }, + { + cloud_provider: 'aws', + compliance_check_type: 'cis', + id: 11, + is_enabled: true, + test_category: 'CloudWatch', + test_desc: + '4.11 Ensure a log metric filter and alarm exist for changes to Network Access Control Lists (NACL)', + test_number: 'control.cis_v140_4_11', + status: 'Active', + }, +]; + +const ComplianceTable = () => { + const columnHelper = createColumnHelper(); + const [tableData, setTableData] = useState(complianceTableData); + + const [rowSelectionState, setRowSelectionState] = useState({}); + + const onToggleChange = (rowData: any, flag: boolean) => { + setTableData((data) => { + data[rowData.row.index].is_enabled = flag; + + return [...data]; + }); + }; + const columns = useMemo( + () => [ + getRowSelectionColumn(columnHelper, { + maxSize: 10, + }), + columnHelper.accessor('id', { + cell: (info) => info.getValue(), + header: () => '#', + maxSize: 20, + }), + columnHelper.accessor((row) => row.test_category, { + id: 'category', + cell: (info) => info.getValue(), + header: () => Category, + maxSize: 50, + }), + columnHelper.accessor('test_desc', { + header: () => 'Description', + cell: (info) => info.renderValue(), + minSize: 500, + }), + columnHelper.accessor('status', { + header: () => 'Status', + cell: (info) => info.renderValue(), + maxSize: 50, + }), + columnHelper.accessor('is_enabled', { + header: () => 'Enabled', + cell: (info) => ( + { + onToggleChange(info, e); + }} + /> + ), + maxSize: 50, + }), + ], + [], + ); + return ( +
+
+ {isEmpty(rowSelectionState) ? ( + No rows selected + ) : ( +
+ + +
+ )} +
+ + + ); +}; + +const scanType = ['CIS', 'GDPR', 'HIPPA', 'PIC', 'SOC2', 'NIST']; + +type TabsType = { + label: string; + value: string; +}; + +const hasTypeSelected = (prevTabs: TabsType[], value: string) => { + return find(prevTabs, ['value', value]); +}; + +const SelectedAccountComponent = ({ + type, + accounts, +}: { + type: string; + accounts: string[]; +}) => { + return ( + + {accounts.length > 0 ? `Account: ${type} / ${accounts[0]}` : null} +   + {accounts.length > 1 && ( + + + +{accounts.length - 1} more + + + )} + + ); +}; + +export const ComplianceScanConfigure = () => { + const { goBack } = usePageNavigation(); + const [selectedTab, setSelectedTab] = useState(''); + const [tabs, setTabs] = useState([]); + const { navigate } = usePageNavigation(); + + const onScanTypeSelection = (name: string) => { + setTabs((prevTabs) => { + const found = hasTypeSelected(prevTabs, name); + if (found) { + return [...filter(prevTabs, (tab: TabsType) => tab.value !== found.value)]; + } else { + return [ + ...prevTabs, + { + label: name, + value: name, + }, + ]; + } + }); + }; + + useEffect(() => { + // set selected tab by last compliance type + if (tabs.length > 0) { + setSelectedTab(tabs[tabs.length - 1].value); + } else { + setSelectedTab(''); + } + }, [tabs]); + + return ( + <> + + } + /> +
+ {scanType.map((type) => ( + + ))} + +
+
+ {selectedTab === '' ? ( +

Please select at least one compliance type to start your scan.

+ ) : ( + setSelectedTab(v)}> +
+ +
+
+ )} +
+ + + ); +}; diff --git a/deepfence_frontend/apps/dashboard/src/features/onboard/pages/DockerConnector.tsx b/deepfence_frontend/apps/dashboard/src/features/onboard/pages/DockerConnector.tsx index 2f145325f5..8fc1eeec7b 100644 --- a/deepfence_frontend/apps/dashboard/src/features/onboard/pages/DockerConnector.tsx +++ b/deepfence_frontend/apps/dashboard/src/features/onboard/pages/DockerConnector.tsx @@ -16,7 +16,7 @@ export const DockerConnector = () => { ); diff --git a/deepfence_frontend/apps/dashboard/src/features/onboard/pages/GCPConnector.tsx b/deepfence_frontend/apps/dashboard/src/features/onboard/pages/GCPConnector.tsx index a51361ada7..5fe16bbd40 100644 --- a/deepfence_frontend/apps/dashboard/src/features/onboard/pages/GCPConnector.tsx +++ b/deepfence_frontend/apps/dashboard/src/features/onboard/pages/GCPConnector.tsx @@ -16,7 +16,7 @@ export const GCPConnector = () => { ); diff --git a/deepfence_frontend/apps/dashboard/src/features/onboard/pages/K8sConnector.tsx b/deepfence_frontend/apps/dashboard/src/features/onboard/pages/K8sConnector.tsx index aed78fc391..de7a3c5f99 100644 --- a/deepfence_frontend/apps/dashboard/src/features/onboard/pages/K8sConnector.tsx +++ b/deepfence_frontend/apps/dashboard/src/features/onboard/pages/K8sConnector.tsx @@ -18,7 +18,7 @@ export const K8sConnector = () => {
diff --git a/deepfence_frontend/apps/dashboard/src/features/onboard/pages/LinuxConnector.tsx b/deepfence_frontend/apps/dashboard/src/features/onboard/pages/LinuxConnector.tsx index d6a2fa0310..88383be4f1 100644 --- a/deepfence_frontend/apps/dashboard/src/features/onboard/pages/LinuxConnector.tsx +++ b/deepfence_frontend/apps/dashboard/src/features/onboard/pages/LinuxConnector.tsx @@ -16,7 +16,7 @@ export const LinuxConnector = () => { ); diff --git a/deepfence_frontend/apps/dashboard/src/features/onboard/pages/SecretScanConfigure.tsx b/deepfence_frontend/apps/dashboard/src/features/onboard/pages/SecretScanConfigure.tsx new file mode 100644 index 0000000000..da8f34abb2 --- /dev/null +++ b/deepfence_frontend/apps/dashboard/src/features/onboard/pages/SecretScanConfigure.tsx @@ -0,0 +1,26 @@ +import { Button } from 'ui-components'; + +import { ConnectorHeader } from '@/features/onboard/components/ConnectorHeader'; +import { usePageNavigation } from '@/utils/usePageNavigation'; + +export const SecretScanConfigure = () => { + const { goBack } = usePageNavigation(); + return ( + <> + +
+
+ +
+ + + + ); +}; diff --git a/deepfence_frontend/apps/dashboard/src/features/onboard/pages/VulnerabilityScanConfigure.tsx b/deepfence_frontend/apps/dashboard/src/features/onboard/pages/VulnerabilityScanConfigure.tsx new file mode 100644 index 0000000000..f773dfe42d --- /dev/null +++ b/deepfence_frontend/apps/dashboard/src/features/onboard/pages/VulnerabilityScanConfigure.tsx @@ -0,0 +1,164 @@ +import { cloneDeepWith } from 'lodash-es'; +import { useEffect, useState } from 'react'; +import { Button, Checkbox, Switch, Typography } from 'ui-components'; + +import { ConnectorHeader } from '@/features/onboard/components/ConnectorHeader'; +import { usePageNavigation } from '@/utils/usePageNavigation'; + +const packages = [ + { + name: 'OS Packages', + checked: false, + }, + { + name: 'Java', + checked: false, + }, + { + name: 'Javascript', + checked: false, + }, + { + name: 'Rust', + checked: false, + }, + { + name: 'GoLang', + checked: false, + }, + { + name: 'Ruby', + checked: false, + }, + { + name: 'Python', + checked: false, + }, + { + name: 'PHP', + checked: false, + }, + { + name: 'Dotnet', + checked: false, + }, +]; + +export const VulnerabilityScanConfigure = () => { + const { goBack } = usePageNavigation(); + const [isSelectAll, setIsSelectAll] = useState(false); + const [pkgs, setSelectPackages] = useState(packages); + + useEffect(() => { + if (isSelectAll) { + const newPkgs = pkgs.map((pkg) => { + pkg.checked = true; + return pkg; + }); + setSelectPackages(newPkgs); + } + }, [isSelectAll]); + + // select all switch + const onSwitchChange = (checked: boolean) => { + setIsSelectAll(checked); + if (!checked) { + const newPkgs = pkgs.map((pkg) => { + pkg.checked = false; + return pkg; + }); + setSelectPackages(newPkgs); + } + }; + + // packages checkbox + const onPackageCheckedChange = ( + pkg: { + name: string; + checked: boolean; + }, + checked: boolean, + ) => { + if (checked === false) { + setIsSelectAll(false); + } else if (pkgs.filter((pkg) => pkg.checked === true).length === pkgs.length - 1) { + setIsSelectAll(true); + } + setSelectPackages((state) => { + const _newState = cloneDeepWith(state, (value) => { + if (value.name === pkg.name) { + return { + ...value, + checked, + }; + } + return undefined; + }); + + return _newState; + }); + }; + return ( + <> + +
+
+
+ Packages +
+ +
+
+ +
+ +
+ {pkgs.map((pkg) => { + return ( + { + onPackageCheckedChange(pkg, checked); + }} + /> + ); + })} +
+
+ +
+
+ Advanced Options +
+ +
+ + +
+
+ + + ); +}; diff --git a/deepfence_frontend/apps/dashboard/src/features/onboard/pages/connectors/AddConnectors.tsx b/deepfence_frontend/apps/dashboard/src/features/onboard/pages/connectors/AddConnectors.tsx index 45285cdd8c..e7fd27a2f8 100644 --- a/deepfence_frontend/apps/dashboard/src/features/onboard/pages/connectors/AddConnectors.tsx +++ b/deepfence_frontend/apps/dashboard/src/features/onboard/pages/connectors/AddConnectors.tsx @@ -126,12 +126,12 @@ const Host = () => { { icon: LogoDocker, label: 'Docker Container', - path: 'docker', + path: 'host/docker', }, { icon: LogoLinux, label: 'Linux Bare-Metal/VM', - path: 'host-linux', + path: 'host/linux', }, ]; diff --git a/deepfence_frontend/apps/dashboard/src/routes/private.tsx b/deepfence_frontend/apps/dashboard/src/routes/private.tsx index 7286907e65..df32bbd196 100644 --- a/deepfence_frontend/apps/dashboard/src/routes/private.tsx +++ b/deepfence_frontend/apps/dashboard/src/routes/private.tsx @@ -11,12 +11,16 @@ import { import { AmazonECRConnector } from '@/features/onboard/pages/AmazonECRConnector'; import { AWSConnector } from '@/features/onboard/pages/AWSConnector'; import { AzureConnector } from '@/features/onboard/pages/AzureConnector'; +import { ChooseScan } from '@/features/onboard/pages/ChooseScan'; +import { ComplianceScanConfigure } from '@/features/onboard/pages/ComplianceScanConfigure'; import { AddConnector } from '@/features/onboard/pages/connectors/AddConnectors'; import { MyConnectors } from '@/features/onboard/pages/connectors/MyConnectors'; import { DockerConnector } from '@/features/onboard/pages/DockerConnector'; import { GCPConnector } from '@/features/onboard/pages/GCPConnector'; import { K8sConnector } from '@/features/onboard/pages/K8sConnector'; import { LinuxConnector } from '@/features/onboard/pages/LinuxConnector'; +import { SecretScanConfigure } from '@/features/onboard/pages/SecretScanConfigure'; +import { VulnerabilityScanConfigure } from '@/features/onboard/pages/VulnerabilityScanConfigure'; export const privateRoutes: RouteObject[] = [ { @@ -60,11 +64,11 @@ export const privateRoutes: RouteObject[] = [ element: , }, { - path: 'docker', + path: 'host/docker', element: , }, { - path: 'host-linux', + path: 'host/linux', element: , }, { @@ -73,6 +77,27 @@ export const privateRoutes: RouteObject[] = [ }, ], }, + { + path: 'scan', + children: [ + { + path: 'choose', + element: , + }, + { + path: 'configure/compliance', + element: , + }, + { + path: 'configure/vulnerability', + element: , + }, + { + path: 'configure/secret', + element: , + }, + ], + }, ], }, ]; diff --git a/deepfence_frontend/pnpm-lock.yaml b/deepfence_frontend/pnpm-lock.yaml index 07ee02c626..72a8863703 100644 --- a/deepfence_frontend/pnpm-lock.yaml +++ b/deepfence_frontend/pnpm-lock.yaml @@ -17,6 +17,7 @@ importers: '@testing-library/jest-dom': ^5.16.5 '@testing-library/react': ^13.4.0 '@testing-library/user-event': ^14.4.3 + '@types/lodash-es': ^4.17.6 '@types/react': ^18.0.24 '@types/react-dom': ^18.0.8 '@types/testing-library__jest-dom': ^5.14.5 @@ -33,6 +34,7 @@ importers: eslint-plugin-prettier: ^4.2.1 eslint-plugin-react: ^7.31.11 eslint-plugin-simple-import-sort: ^8.0.0 + lodash-es: ^4.17.21 msw: ^0.49.1 playwright: 1.28.1 postcss: ^8.4.19 @@ -55,6 +57,7 @@ importers: '@testing-library/jest-dom': 5.16.5 '@testing-library/user-event': 14.4.3 classnames: 2.3.2 + lodash-es: 4.17.21 react: 18.2.0 react-dom: 18.2.0_react@18.2.0 react-icons: 4.7.1_react@18.2.0 @@ -66,6 +69,7 @@ importers: '@openapitools/openapi-generator-cli': 2.5.2 '@playwright/test': 1.28.1 '@testing-library/react': 13.4.0_biqbaboplfbrettd7655fr4n2y + '@types/lodash-es': 4.17.6 '@types/react': 18.0.25 '@types/react-dom': 18.0.9 '@types/testing-library__jest-dom': 5.14.5