| 1 | + | import React, { FunctionComponent, useState } from 'react'; |
| 2 | + | import { append, filter, propOr } from 'ramda'; |
| 3 | + | import { graphql, createFragmentContainer } from 'react-relay'; |
| 4 | + | import * as R from 'ramda'; |
| 5 | + | import makeStyles from '@mui/styles/makeStyles'; |
| 6 | + | import { QueryRenderer } from '../../../../relay/environment'; |
| 7 | + | import ListLines from '../../../../components/list_lines/ListLines'; |
| 8 | + | import ContainerStixCyberObservablesLines, { |
| 9 | + | containerStixCyberObservablesLinesQuery, |
| 10 | + | } from './ContainerStixCyberObservablesLines'; |
| 11 | + | import { |
| 12 | + | buildViewParamsFromUrlAndStorage, |
| 13 | + | convertFilters, saveViewParameters, |
| 14 | + | } from '../../../../utils/ListParameters'; |
| 15 | + | import StixCyberObservablesRightBar from '../../observations/stix_cyber_observables/StixCyberObservablesRightBar'; |
| 16 | + | import ToolBar from '../../data/ToolBar'; |
| 17 | + | import { defaultValue } from '../../../../utils/Graph'; |
| 18 | + | import { UserContext } from '../../../../utils/Security'; |
| 19 | + | import useLocalStorage from '../../../../utils/hooks/useLocalStorage'; |
| 20 | + | import { isUniqFilter } from '../lists/Filters'; |
| 21 | + | import { Theme } from '../../../../components/Theme'; |
| 22 | + | import { Filters } from '../../../../components/list_lines'; |
| 23 | + | import { ModuleHelper } from '../../../../utils/platformModulesHelper'; |
| 24 | + | import { |
| 25 | + | ContainerStixCyberObservablesLinesQuery$data, |
| 26 | + | } from './__generated__/ContainerStixCyberObservablesLinesQuery.graphql'; |
| 27 | + | import { |
| 28 | + | StixCyberObservableLine_node$data, |
| 29 | + | } from '../../observations/stix_cyber_observables/__generated__/StixCyberObservableLine_node.graphql'; |
| 30 | + | |
| 31 | + | const useStyles = makeStyles<Theme>(() => ({ |
| 32 | + | container: { |
| 33 | + | margin: '20px 0 0 0', |
| 34 | + | padding: '0 260px 90px 0', |
| 35 | + | }, |
| 36 | + | })); |
| 37 | + | |
| 38 | + | interface ContainerStixCyberObservablesComponentProps { |
| 39 | + | container: HTMLElement, |
| 40 | + | history: History, |
| 41 | + | location: Location, |
| 42 | + | } |
| 43 | + | |
| 44 | + | const LOCAL_STORAGE_KEY = 'view-container-stix-cyber-observables'; |
| 45 | + | |
| 46 | + | const ContainerStixCyberObservablesComponent: FunctionComponent<ContainerStixCyberObservablesComponentProps> = ({ container, history, location }) => { |
| 47 | + | const classes = useStyles(); |
| 48 | + | |
| 49 | + | const params = buildViewParamsFromUrlAndStorage( |
| 50 | + | history, |
| 51 | + | location, |
| 52 | + | `view-container-${container.id}-stix-observables`, |
| 53 | + | ); |
| 54 | + | |
| 55 | + | const [viewStorage, setViewStorage] = useLocalStorage(LOCAL_STORAGE_KEY, { |
| 56 | + | numberOfElements: { number: 0, symbol: '', original: 0 }, |
| 57 | + | filters: R.propOr({}, 'filters', params) as Filters, |
| 58 | + | searchTerm: propOr('', 'searchTerm', params), |
| 59 | + | sortBy: propOr('created_at', 'sortBy', params), |
| 60 | + | orderAsc: propOr(false, 'orderAsc', params), |
| 61 | + | openExports: false, |
| 62 | + | }); |
| 63 | + | const [types, setTypes] = useState<string[]>(propOr([], 'types', params)); |
| 64 | + | const [selectedElements, setSelectedElements] = useState<Record<string, StixCyberObservableLine_node$data>>({}); |
| 65 | + | const [deSelectedElements, setDeSelectedElements] = useState<Record<string, StixCyberObservableLine_node$data>>({}); |
| 66 | + | const [selectAll, setSelectAll] = useState<boolean>(false); |
| 67 | + | |
| 68 | + | const { numberOfElements, filters, searchTerm, sortBy, orderAsc, openExports } = viewStorage; |
| 69 | + | |
| 70 | + | const finalFilters = convertFilters(filters); |
| 71 | + | const paginationOptions = { |
| 72 | + | types: types.length > 0 ? types : ['Stix-Cyber-Observable'], |
| 73 | + | search: searchTerm, |
| 74 | + | filters: finalFilters, |
| 75 | + | orderBy: sortBy, |
| 76 | + | orderMode: orderAsc ? 'asc' : 'desc', |
| 77 | + | }; |
| 78 | + | const exportFilters = { |
| 79 | + | containedBy: [{ id: container.id, value: defaultValue(container) }], |
| 80 | + | entity_type: |
| 81 | + | types.length > 0 ? R.map((n) => ({ id: n, value: n }), types) : [], |
| 82 | + | ...filters, |
| 83 | + | }; |
| 84 | + | const exportFinalFilters = convertFilters(exportFilters); |
| 85 | + | const exportPaginationOptions = { |
| 86 | + | filters: exportFinalFilters, |
| 87 | + | orderBy: sortBy, |
| 88 | + | orderMode: orderAsc ? 'asc' : 'desc', |
| 89 | + | search: searchTerm, |
| 90 | + | }; |
| 91 | + | let numberOfSelectedElements = Object.keys(selectedElements || {}).length; |
| 92 | + | if (selectAll) { |
| 93 | + | numberOfSelectedElements = numberOfElements.original; |
| 94 | + | } |
| 95 | + | const backgroundTaskFilters = { |
| 96 | + | containedBy: [{ id: container.id, value: defaultValue(container) }], |
| 97 | + | entity_type: |
| 98 | + | types.length > 0 |
| 99 | + | ? R.map((n) => ({ id: n, value: n }), types) |
| 100 | + | : [{ id: 'Stix-Cyber-Observable', value: 'Stix-Cyber-Observable' }], |
| 101 | + | ...filters, |
| 102 | + | }; |
| 103 | + | |
| 104 | + | const saveView = () => { |
| 105 | + | saveViewParameters( |
| 106 | + | history, |
| 107 | + | location, |
| 108 | + | `view-container-${container.id}-stix-observables`, |
| 109 | + | viewStorage, |
| 110 | + | ); |
| 111 | + | }; |
| 112 | + | |
| 113 | + | const handleSearch = (value: string) => { |
| 114 | + | setViewStorage((c) => ({ ...c, searchTerm: value })); |
| 115 | + | saveView(); |
| 116 | + | }; |
| 117 | + | |
| 118 | + | const handleSort = (field: string, ordA: boolean) => { |
| 119 | + | setViewStorage((c) => ({ ...c, sortBy: field, orderAsc: ordA })); |
| 120 | + | saveView(); |
| 121 | + | }; |
| 122 | + | |
| 123 | + | const handleToggleExports = () => { |
| 124 | + | setViewStorage((c) => ({ ...c, openExports: !openExports })); |
| 125 | + | }; |
| 126 | + | |
| 127 | + | const handleToggle = (type: string) => { |
| 128 | + | if (types.includes(type)) { |
| 129 | + | setTypes(filter((x) => x !== type, types)); |
| 130 | + | saveView(); |
| 131 | + | } else { |
| 132 | + | setTypes(append(type, types)); |
| 133 | + | saveView(); |
| 134 | + | } |
| 135 | + | }; |
| 136 | + | |
| 137 | + | const handleClear = () => { |
| 138 | + | setTypes([]); |
| 139 | + | saveView(); |
| 140 | + | }; |
| 141 | + | |
| 142 | + | const handleToggleSelectEntity = (entity: StixCyberObservableLine_node$data, event: React.SyntheticEvent) => { |
| 143 | + | event.stopPropagation(); |
| 144 | + | event.preventDefault(); |
| 145 | + | if (entity.id in (selectedElements)) { |
| 146 | + | const newSelectedElements = R.omit([entity.id], selectedElements); |
| 147 | + | setSelectAll(false); |
| 148 | + | setSelectedElements(newSelectedElements); |
| 149 | + | } else if (selectAll && entity.id in (deSelectedElements)) { |
| 150 | + | const newDeSelectedElements = R.omit([entity.id], deSelectedElements); |
| 151 | + | setDeSelectedElements(newDeSelectedElements); |
| 152 | + | } else if (selectAll) { |
| 153 | + | const newDeSelectedElements = R.assoc( |
| 154 | + | entity.id, |
| 155 | + | entity, |
| 156 | + | deSelectedElements, |
| 157 | + | ); |
| 158 | + | setDeSelectedElements(newDeSelectedElements); |
| 159 | + | } else { |
| 160 | + | const newSelectedElements = R.assoc( |
| 161 | + | entity.id, |
| 162 | + | entity, |
| 163 | + | selectedElements, |
| 164 | + | ); |
| 165 | + | setSelectAll(false); |
| 166 | + | setSelectedElements(newSelectedElements); |
| 167 | + | } |
| 168 | + | }; |
| 169 | + | |
| 170 | + | const handleToggleSelectAll = () => { |
| 171 | + | setSelectAll(!selectAll); |
| 172 | + | setSelectedElements({}); |
| 173 | + | setDeSelectedElements({}); |
| 174 | + | }; |
| 175 | + | |
| 176 | + | const handleClearSelectedElements = () => { |
| 177 | + | setSelectAll(false); |
| 178 | + | setSelectedElements({}); |
| 179 | + | setDeSelectedElements({}); |
| 180 | + | }; |
| 181 | + | |
| 182 | + | const handleAddFilter = (key: string, id: string, value: string, event: React.SyntheticEvent | null = null) => { |
| 183 | + | if (event) { |
| 184 | + | event.stopPropagation(); |
| 185 | + | event.preventDefault(); |
| 186 | + | } |
| 187 | + | if (filters[key] && filters[key].length > 0) { |
| 188 | + | setViewStorage((c) => ({ ...c, |
| 189 | + | filters: R.assoc( |
| 190 | + | key, |
| 191 | + | isUniqFilter(key) |
| 192 | + | ? [{ id, value }] |
| 193 | + | : R.uniqBy(R.prop('id'), [ |
| 194 | + | { id, value }, |
| 195 | + | ...filters[key], |
| 196 | + | ]), |
| 197 | + | filters, |
| 198 | + | ), |
| 199 | + | })); |
| 200 | + | saveView(); |
| 201 | + | } else { |
| 202 | + | setViewStorage((c) => ({ ...c, |
| 203 | + | filters: R.assoc(key, [{ id, value }], filters), |
| 204 | + | })); |
| 205 | + | saveView(); |
| 206 | + | } |
| 207 | + | }; |
| 208 | + | |
| 209 | + | const handleRemoveFilter = (key: string) => { |
| 210 | + | setViewStorage((c) => ({ ...c, |
| 211 | + | filters: R.dissoc(key, filters), |
| 212 | + | })); |
| 213 | + | saveView(); |
| 214 | + | }; |
| 215 | + | |
| 216 | + | const setNumberOfElements = (num: number) => { |
| 217 | + | setNumberOfElements(num); |
| 218 | + | }; |
| 219 | + | |
| 220 | + | // eslint-disable-next-line class-methods-use-this |
| 221 | + | const buildColumns = (helper: ModuleHelper | undefined) => { |
| 222 | + | const isRuntimeSort = helper?.isRuntimeFieldEnable('RUNTIME_SORTING'); |
| 223 | + | return { |
| 224 | + | entity_type: { |
| 225 | + | label: 'Type', |
| 226 | + | width: '15%', |
| 227 | + | isSortable: true, |
| 228 | + | }, |
| 229 | + | observable_value: { |
| 230 | + | label: 'Value', |
| 231 | + | width: '30%', |
| 232 | + | isSortable: isRuntimeSort, |
| 233 | + | }, |
| 234 | + | objectLabel: { |
| 235 | + | label: 'Labels', |
| 236 | + | width: '20%', |
| 237 | + | isSortable: false, |
| 238 | + | }, |
| 239 | + | createdBy: { |
| 240 | + | label: 'Creator', |
| 241 | + | width: '15%', |
| 242 | + | isSortable: isRuntimeSort, |
| 243 | + | }, |
| 244 | + | created_at: { |
| 245 | + | label: 'Creation date', |
| 246 | + | width: '10%', |
| 247 | + | isSortable: true, |
| 248 | + | }, |
| 249 | + | objectMarking: { |
| 250 | + | label: 'Marking', |
| 251 | + | isSortable: isRuntimeSort, |
| 252 | + | }, |
| 253 | + | }; |
| 254 | + | }; |
| 255 | + | |
| 256 | + | return ( |
| 257 | + | <UserContext.Consumer> |
| 258 | + | {({ helper }) => ( |
| 259 | + | <div className={classes.container}> |
| 260 | + | <ListLines |
| 261 | + | sortBy={sortBy} |
| 262 | + | orderAsc={orderAsc} |
| 263 | + | dataColumns={buildColumns(helper)} |
| 264 | + | handleSort={handleSort} |
| 265 | + | handleSearch={handleSearch.bind} |
| 266 | + | secondaryAction={true} |
| 267 | + | numberOfElements={numberOfElements} |
| 268 | + | handleAddFilter={handleAddFilter} |
| 269 | + | handleRemoveFilter={handleRemoveFilter} |
| 270 | + | handleToggleSelectAll={handleToggleSelectAll} |
| 271 | + | selectAll={selectAll} |
| 272 | + | iconExtension={true} |
| 273 | + | handleToggleExports={handleToggleExports} |
| 274 | + | exportEntityType="Stix-Cyber-Observable" |
| 275 | + | openExports={openExports} |
| 276 | + | exportContext={`of-container-${container.id}`} |
| 277 | + | filters={filters} |
| 278 | + | availableFilterKeys={[ |
| 279 | + | 'labelledBy', |
| 280 | + | 'markedBy', |
| 281 | + | 'created_at_start_date', |
| 282 | + | 'created_at_end_date', |
| 283 | + | 'x_opencti_score', |
| 284 | + | 'createdBy', |
| 285 | + | 'sightedBy', |
| 286 | + | ]} |
| 287 | + | paginationOptions={exportPaginationOptions} |
| 288 | + | > |
| 289 | + | <QueryRenderer |
| 290 | + | query={containerStixCyberObservablesLinesQuery} |
| 291 | + | variables={{ |
| 292 | + | id: container.id, |
| 293 | + | count: 25, |
| 294 | + | ...paginationOptions, |
| 295 | + | }} |
| 296 | + | render={({ props }: { props: ContainerStixCyberObservablesLinesQuery$data }) => ( |
| 297 | + | <ContainerStixCyberObservablesLines |
| 298 | + | container={props ? props.container : null} |
| 299 | + | paginationOptions={paginationOptions} |
| 300 | + | dataColumns={buildColumns(helper)} |
| 301 | + | initialLoading={props === null} |
| 302 | + | setNumberOfElements={setNumberOfElements} |
| 303 | + | onTypesChange={handleToggle} |
| 304 | + | openExports={openExports} |
| 305 | + | selectedElements={selectedElements} |
| 306 | + | deSelectedElements={deSelectedElements} |
| 307 | + | onToggleEntity={handleToggleSelectEntity} |
| 308 | + | selectAll={selectAll} |
| 309 | + | /> |
| 310 | + | )} |
| 311 | + | /> |
| 312 | + | </ListLines> |
| 313 | + | <ToolBar |
| 314 | + | selectedElements={selectedElements} |
| 315 | + | deSelectedElements={deSelectedElements} |
| 316 | + | numberOfSelectedElements={numberOfSelectedElements} |
| 317 | + | selectAll={selectAll} |
| 318 | + | search={searchTerm} |
| 319 | + | filters={backgroundTaskFilters} |
| 320 | + | handleClearSelectedElements={handleClearSelectedElements} |
| 321 | + | variant="large" |
| 322 | + | container={container} |
| 323 | + | /> |
| 324 | + | <StixCyberObservablesRightBar |
| 325 | + | types={types} |
| 326 | + | handleToggle={handleToggle} |
| 327 | + | handleClear={handleClear} |
| 328 | + | openExports={openExports} |
| 329 | + | /> |
| 330 | + | </div> |
| 331 | + | )} |
| 332 | + | </UserContext.Consumer> |
| 333 | + | ); |
| 334 | + | }; |
| 335 | + | |
| 336 | + | const ContainerStixCyberObservables = createFragmentContainer( |
| 337 | + | ContainerStixCyberObservablesComponent, |
| 338 | + | { |
| 339 | + | container: graphql` |
| 340 | + | fragment ContainerStixCyberObservables_container on Container { |
| 341 | + | id |
| 342 | + | ... on Report { |
| 343 | + | name |
| 344 | + | } |
| 345 | + | ... on Grouping { |
| 346 | + | name |
| 347 | + | } |
| 348 | + | ... on Note { |
| 349 | + | attribute_abstract |
| 350 | + | content |
| 351 | + | } |
| 352 | + | ... on Opinion { |
| 353 | + | opinion |
| 354 | + | } |
| 355 | + | ... on ObservedData { |
| 356 | + | name |
| 357 | + | first_observed |
| 358 | + | last_observed |
| 359 | + | } |
| 360 | + | ...ContainerHeader_container |
| 361 | + | } |
| 362 | + | `, |
| 363 | + | }, |
| 364 | + | ); |
| 365 | + | |
| 366 | + | export default ContainerStixCyberObservables; |
| 367 | + | |