diff --git a/frontend/src/components/DrawerInterior.tsx b/frontend/src/components/DrawerInterior.tsx index 55b76dba..98642e90 100644 --- a/frontend/src/components/DrawerInterior.tsx +++ b/frontend/src/components/DrawerInterior.tsx @@ -27,7 +27,7 @@ import { FilterAlt, Save } from '@mui/icons-material'; -import { FacetFilter, SearchBar, TaggedArrayInput } from 'components'; +import { FacetFilter, TaggedArrayInput } from 'components'; import { ContextType } from '../context/SearchProvider'; import { useAuthContext } from '../context'; import { useSavedSearchContext } from 'context/SavedSearchContext'; @@ -62,7 +62,6 @@ export const DrawerInterior: React.FC = (props) => { removeFilter, facets, clearFilters, - searchTerm, setSearchTerm, initialFilters } = props; @@ -177,18 +176,7 @@ export const DrawerInterior: React.FC = (props) => { -
- { - setSearchTerm(value, { - shouldClearFilters: false, - autocompleteResults: false - }); - }} - /> -
+ {clearFilters && ( diff --git a/frontend/src/components/FilterDrawerV2.tsx b/frontend/src/components/FilterDrawerV2.tsx index b1451186..20bfe0a4 100644 --- a/frontend/src/components/FilterDrawerV2.tsx +++ b/frontend/src/components/FilterDrawerV2.tsx @@ -50,6 +50,8 @@ export const FilterDrawer: FC< addFilter={addFilter} removeFilter={removeFilter} filters={filters} + setSearchTerm={setSearchTerm} + searchTerm={searchTerm} /> {matchPath( ['/inventory', '/inventory/domains', '/inventory/vulnerabilities'], diff --git a/frontend/src/components/RegionAndOrganizationFilters.tsx b/frontend/src/components/RegionAndOrganizationFilters.tsx index 6b6a7161..1f0d2035 100644 --- a/frontend/src/components/RegionAndOrganizationFilters.tsx +++ b/frontend/src/components/RegionAndOrganizationFilters.tsx @@ -5,6 +5,7 @@ import { AccordionDetails, AccordionSummary, Autocomplete, + Box, Button, Checkbox, Divider, @@ -18,6 +19,8 @@ import { import { ExpandMore } from '@mui/icons-material'; import { useStaticsContext } from 'context/StaticsContext'; import { REGIONAL_USER_CAN_SEARCH_OTHER_REGIONS } from 'hooks/useUserTypeFilters'; +import { SearchBar } from './SearchBar'; +import { useHistory, useLocation } from 'react-router-dom'; const GLOBAL_ADMIN = 3; const REGIONAL_ADMIN = 2; @@ -48,11 +51,19 @@ interface RegionAndOrganizationFiltersProps { filterType: 'all' | 'any' | 'none' ) => void; filters: any[]; + setSearchTerm: (s: string, opts?: any) => void; + searchTerm: string; } export const RegionAndOrganizationFilters: React.FC< RegionAndOrganizationFiltersProps -> = ({ addFilter, removeFilter, filters }) => { +> = ({ + addFilter, + removeFilter, + filters, + searchTerm: domainSearchTerm, + setSearchTerm: setDomainSearchTerm +}) => { const { setShowMaps, user, apiPost } = useAuthContext(); const { regions } = useStaticsContext(); @@ -156,6 +167,8 @@ export const RegionAndOrganizationFilters: React.FC< }, [regionFilterValues] ); + const history = useHistory(); + const location = useLocation(); const handleAddOrganization = (org: OrganizationShallow) => { if (org) { @@ -179,6 +192,24 @@ export const RegionAndOrganizationFilters: React.FC< return ( <> + + { + if (location.pathname !== '/inventory') { + history.push(`/inventory?q=${value}`); + setDomainSearchTerm(value, { + shouldClearFilters: false, + refresh: true + }); + } + setDomainSearchTerm(value, { + shouldClearFilters: false + }); + }} + /> + +
+
+
+ +
+ +
+
+
+
diff --git a/frontend/src/pages/Search/FilterDrawer.tsx b/frontend/src/pages/Search/FilterDrawer.tsx deleted file mode 100644 index 942f8372..00000000 --- a/frontend/src/pages/Search/FilterDrawer.tsx +++ /dev/null @@ -1,497 +0,0 @@ -import React, { useEffect, useMemo, useState } from 'react'; -import { - AccordionDetails, - Accordion as MuiAccordion, - AccordionSummary as MuiAccordionSummary, - IconButton, - Paper -} from '@mui/material'; -import { DataGrid } from '@mui/x-data-grid'; -import { classes, StyledWrapper } from './Styling/filterDrawerStyle'; -import { - Delete, - ExpandMore, - FiberManualRecordRounded -} from '@mui/icons-material'; -import { FaFilter } from 'react-icons/fa'; -import { SearchBar } from 'components'; -import { TaggedArrayInput, FacetFilter } from 'components'; -import { ContextType } from '../../context/SearchProvider'; -import { SavedSearch } from '../../types/saved-search'; -import { useAuthContext } from '../../context'; -import { useHistory, useLocation } from 'react-router-dom'; -import { withSearch } from '@elastic/react-search-ui'; - -interface Props { - addFilter: ContextType['addFilter']; - removeFilter: ContextType['removeFilter']; - filters: ContextType['filters']; - facets: ContextType['facets']; - clearFilters: ContextType['clearFilters']; - updateSearchTerm: (term: string) => void; - searchTerm: ContextType['searchTerm']; - setSearchTerm: ContextType['setSearchTerm']; -} - -const FiltersApplied: React.FC = () => { - return ( -
- Filters Applied -
- ); -}; - -const Accordion = MuiAccordion; -const AccordionSummary = MuiAccordionSummary; - -export const FilterDrawer: React.FC = (props) => { - const { - filters, - addFilter, - removeFilter, - facets, - clearFilters, - searchTerm, - setSearchTerm - } = props; - const { apiGet, apiDelete } = useAuthContext(); - const [savedSearches, setSavedSearches] = useState([]); - const [savedSearchCount, setSavedSearchCount] = useState(0); - const history = useHistory(); - const location = useLocation(); - - useEffect(() => { - const fetchSearches = async () => { - try { - const response = await apiGet('/saved-searches'); - setSavedSearches(response.result); - setSavedSearchCount(response.result.length); - } catch (error) { - console.error('Error fetching searches:', error); - } - }; - fetchSearches(); - }, [apiGet]); - - const deleteSearch = async (id: string) => { - try { - await apiDelete(`/saved-searches/${id}`, { body: {} }); - const updatedSearches = await apiGet('/saved-searches'); // Get current saved searches - setSavedSearches(updatedSearches.result); // Update the saved searches - setSavedSearchCount(updatedSearches.result.length); // Update the count - } catch (e) { - console.log(e); - } - }; - - const filtersByColumn = useMemo( - () => - filters.reduce( - (allFilters, nextFilter) => ({ - ...allFilters, - [nextFilter.field]: nextFilter.values - }), - {} as Record - ), - [filters] - ); - - const portFacet: any[] = facets['services.port'] - ? facets['services.port'][0].data - : []; - - const fromDomainFacet: any[] = facets['fromRootDomain'] - ? facets['fromRootDomain'][0].data - : []; - - const cveFacet: any[] = facets['vulnerabilities.cve'] - ? facets['vulnerabilities.cve'][0].data - : []; - - const severityFacet: any[] = facets['vulnerabilities.severity'] - ? facets['vulnerabilities.severity'][0].data - : []; - - // Always show all severities - for (const value of ['Critical', 'High', 'Medium', 'Low']) { - if (!severityFacet.find((severity) => value === severity.value)) - severityFacet.push({ value, count: 0 }); - } - - return ( - -
- { - if (location.pathname !== '/inventory') - history.push('/inventory?q=' + value); - setSearchTerm(value, { - shouldClearFilters: false, - autocompleteResults: false - }); - }} - /> -
-
-
-

Filter

-
- {clearFilters && ( -
- -
- )} -
- - } - classes={{ - root: classes.root2, - content: classes.content, - disabled: classes.disabled2, - expanded: classes.expanded2 - }} - > -
IP(s)
- {filtersByColumn['ip']?.length > 0 && } -
- - addFilter('ip', value, 'any')} - onRemoveTag={(value) => removeFilter('ip', value, 'any')} - /> - -
- - } - classes={{ - root: classes.root2, - content: classes.content, - disabled: classes.disabled2, - expanded: classes.expanded2 - }} - > -
Domain(s)
- {filtersByColumn['name']?.length > 0 && } -
- - addFilter('name', value, 'any')} - onRemoveTag={(value) => removeFilter('name', value, 'any')} - /> - -
- {fromDomainFacet.length > 0 && ( - - } - classes={{ - root: classes.root2, - content: classes.content, - disabled: classes.disabled2, - expanded: classes.expanded2 - }} - > -
Root Domain(s)
- {filtersByColumn['fromRootDomain']?.length > 0 && ( - - )} -
- - addFilter('fromRootDomain', value, 'any')} - onDeselect={(value) => - removeFilter('fromRootDomain', value, 'any') - } - /> - -
- )} - {portFacet.length > 0 && ( - - } - classes={{ - root: classes.root2, - content: classes.content, - disabled: classes.disabled2, - expanded: classes.expanded2 - }} - > -
Port(s)
- {filtersByColumn['services.port']?.length > 0 && } -
- - addFilter('services.port', value, 'any')} - onDeselect={(value) => - removeFilter('services.port', value, 'any') - } - /> - -
- )} - {cveFacet.length > 0 && ( - - } - classes={{ - root: classes.root2, - content: classes.content, - disabled: classes.disabled2, - expanded: classes.expanded2 - }} - > -
CVE(s)
- {filtersByColumn['vulnerabilities.cve']?.length > 0 && ( - - )} -
- - - addFilter('vulnerabilities.cve', value, 'any') - } - onDeselect={(value) => - removeFilter('vulnerabilities.cve', value, 'any') - } - /> - -
- )} - {severityFacet.length > 0 && ( - - } - classes={{ - root: classes.root2, - content: classes.content, - disabled: classes.disabled2, - expanded: classes.expanded2 - }} - > -
Severity
- {filtersByColumn['vulnerabilities.severity']?.length > 0 && ( - - )} -
- - - addFilter('vulnerabilities.severity', value, 'any') - } - onDeselect={(value) => - removeFilter('vulnerabilities.severity', value, 'any') - } - /> - -
- )} - - } - classes={{ - root: classes.root2, - content: classes.content, - disabled: classes.disabled2, - expanded: classes.expanded2 - }} - > -
-

Saved Searches

-
-
- - - - {savedSearches.length > 0 ? ( - ({ ...search }))} - rowCount={savedSearchCount} - columns={[ - { - field: 'name', - headerName: 'Name', - flex: 1, - width: 100, - description: 'Name', - renderCell: (cellValues) => { - const applyFilter = () => { - if (clearFilters) clearFilters(); - localStorage.setItem( - 'savedSearch', - JSON.stringify(cellValues.row) - ); - setSearchTerm(cellValues.row.searchTerm, { - shouldClearFilters: false, - autocompleteResults: false - }); - if (location.pathname !== '/inventory') - history.push( - '/inventory?q=' + cellValues.row.searchTerm - ); - - // Apply the filters - cellValues.row.filters.forEach((filter) => { - filter.values.forEach((value) => { - addFilter(filter.field, value, 'any'); - }); - }); - }; - return ( -
{ - if (e.key === 'Enter') { - applyFilter(); - } - }} - style={{ - cursor: 'pointer', - textAlign: 'left', - width: '100%' - }} - > - {cellValues.value} -
- ); - } - }, - { - field: 'actions', - headerName: '', - flex: 0.1, - renderCell: (cellValues) => { - const searchId = cellValues.id.toString(); - return ( -
- { - e.stopPropagation(); - deleteSearch(searchId); - }} - tabIndex={0} - onKeyDown={(e) => { - if (e.key === 'Enter') { - deleteSearch(searchId); - } - }} - > - - -
- ); - } - } - ]} - initialState={{ - pagination: { - paginationModel: { - pageSize: 5 - } - } - }} - pageSizeOptions={[5, 10]} - disableRowSelectionOnClick - sx={{ - disableColumnfilter: 'true', - '& .MuiDataGrid-row:hover': { - cursor: 'pointer' - } - }} - /> - ) : ( -
No Saved Searches
- )} -
-
-
-
-
- ); -}; - -export const FilterDrawerWithSearch = withSearch( - ({ searchTerm, setSearchTerm }: ContextType) => ({ - searchTerm, - setSearchTerm - }) -)(FilterDrawer); diff --git a/frontend/src/pages/Search/FilterTags.tsx b/frontend/src/pages/Search/FilterTags.tsx index 1ed86cae..72afea18 100644 --- a/frontend/src/pages/Search/FilterTags.tsx +++ b/frontend/src/pages/Search/FilterTags.tsx @@ -43,6 +43,38 @@ const FIELD_TO_LABEL_MAP: FieldToLabelMap = { }, trimAfter: 10 }, + 'vulnerabilities.severity': { + labelAccessor: (t) => { + return 'Severity'; + }, + filterValueAccssor(t) { + return t; + } + }, + ip: { + labelAccessor: (t) => { + return 'IP'; + }, + filterValueAccssor(t) { + return t; + } + }, + name: { + labelAccessor: (t) => { + return 'Name'; + }, + filterValueAccssor(t) { + return t; + } + }, + fromRootDomain: { + labelAccessor: (t) => { + return 'Root Domain(s)'; + }, + filterValueAccssor(t) { + return t; + } + }, organizationId: { labelAccessor: (t) => { return 'Organization'; @@ -52,6 +84,14 @@ const FIELD_TO_LABEL_MAP: FieldToLabelMap = { }, trimAfter: 2 }, + query: { + labelAccessor: (t) => { + return 'Query'; + }, + filterValueAccssor(t) { + return t; + } + }, 'services.port': { labelAccessor: (t) => { return 'Port'; @@ -75,6 +115,7 @@ const FIELD_TO_LABEL_MAP: FieldToLabelMap = { type FlatFilters = { field: string; label: string; + onClear?: () => void; value: any; values: any[]; type: 'all' | 'none' | 'any'; @@ -131,6 +172,11 @@ export const FilterTags: React.FC = ({ filters, removeFilter }) => { } onDelete={() => { + if (filter.onClear) { + console.log('custom clear'); + filter.onClear(); + return; + } filter.values.forEach((val) => { removeFilter(filter.field, val, filter.type); }); diff --git a/frontend/src/pages/Search/Inventory.tsx b/frontend/src/pages/Search/Inventory.tsx index 1dce0c9c..53eaf28b 100644 --- a/frontend/src/pages/Search/Inventory.tsx +++ b/frontend/src/pages/Search/Inventory.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef, useState } from 'react'; +import React, { useEffect, useMemo, useRef, useState } from 'react'; import { classes, Root } from './Styling/dashboardStyle'; import { Subnav } from 'components'; import { ResultCard } from './ResultCard'; @@ -73,7 +73,7 @@ export const DashboardUI: React.FC = ( // Could be used for validation purposes in new dialogue // const { savedSearches } = useSavedSearchContext(); - const advanceFiltersReq = filters.length > 1; //Prevents a user from saving a search without advanced filters + const advanceFiltersReq = filters.length > 1 || searchTerm !== ''; //Prevents a user from saving a search without advanced filters const search: | (SavedSearch & { @@ -120,11 +120,9 @@ export const DashboardUI: React.FC = ( useEffect(() => { if (props.location.search === '') { // Search on initial load - setSearchTerm('', { shouldClearFilters: false }); } return () => { localStorage.removeItem('savedSearch'); - setSearchTerm('', { shouldClearFilters: false }); }; }, [setSearchTerm, props.location.search]); @@ -157,6 +155,22 @@ export const DashboardUI: React.FC = ( } }; + const filtersToDisplay = useMemo(() => { + if (searchTerm !== '') { + return [ + ...filters, + { + field: 'query', + values: [searchTerm], + onClear: () => setSearchTerm('', { shouldClearFilters: false }) + } + ]; + } + return filters; + }, [filters, searchTerm, setSearchTerm]); + + console.log(filtersToDisplay); + return ( = ( justifyContent="center" > - +