Projects STRLCPY opencti Commits c2d21700
🤬
Revision indexing in progress... (symbol navigation in revisions will be accurate after indexed)
  • ■ ■ ■ ■ ■ ■
    opencti-platform/opencti-front/.eslintrc.json
    skipped 18 lines
    19 19   "react/no-unused-prop-types": 0,
    20 20   "react/prop-types": 0,
    21 21   "max-classes-per-file": ["error", 2],
    22  - "no-unused-vars": ["warn"],
    23 22   "object-curly-newline": "off",
    24 23   "arrow-body-style": "off",
    25 24   "max-len": [
    skipped 3 lines
    29 28   "ignoreRegExpLiterals": true,
    30 29   "ignoreStrings": true,
    31 30   "ignoreTemplateLiterals": true
     31 + }
     32 + ],
     33 + "@typescript-eslint/naming-convention": ["error", {
     34 + "selector": "variable",
     35 + "format": ["camelCase", "UPPER_CASE"],
     36 + "leadingUnderscore": "allow",
     37 + "trailingUnderscore": "allow",
     38 + "filter": {
     39 + "regex": "/([^_]*)/",
     40 + "match": true
     41 + }
     42 + }],
     43 + "no-unused-vars": [
     44 + "error",
     45 + {
     46 + "varsIgnorePattern": "_+"
     47 + }
     48 + ],
     49 + "@typescript-eslint/no-unused-vars": [
     50 + "error",
     51 + {
     52 + "varsIgnorePattern": "_+"
    32 53   }
    33 54   ]
    34 55   }
    skipped 2 lines
  • ■ ■ ■ ■ ■
    opencti-platform/opencti-front/src/components/list_lines/ListLinesContent.js
    skipped 31 lines
    32 32   }
    33 33   
    34 34   componentDidUpdate(prevProps) {
    35  - const diff = R.symmetricDifferenceWith(
    36  - (x, y) => x.node.id === y.node.id,
     35 + const diff = !R.equals(
    37 36   this.props.dataList,
    38 37   prevProps.dataList,
    39 38   );
    skipped 18 lines
    58 57   if (this.props.selectAll !== prevProps.selectAll) {
    59 58   selection = true;
    60 59   }
    61  - if (diff.length > 0 || diffBookmark.length > 0 || selection) {
     60 + if (diff || diffBookmark.length > 0 || selection) {
    62 61   this.listRef.forceUpdateGrid();
    63 62   }
    64 63   }
    skipped 193 lines
  • ■ ■ ■ ■ ■ ■
    opencti-platform/opencti-front/src/private/components/entities/Cities.js
    1  -import React, { Component } from 'react';
    2  -import * as PropTypes from 'prop-types';
    3  -import { withRouter } from 'react-router-dom';
    4  -import * as R from 'ramda';
    5  -import { QueryRenderer } from '../../../relay/environment';
    6  -import {
    7  - buildViewParamsFromUrlAndStorage,
    8  - convertFilters,
    9  - saveViewParameters,
    10  -} from '../../../utils/ListParameters';
    11  -import inject18n from '../../../components/i18n';
    12  -import ListLines from '../../../components/list_lines/ListLines';
    13  -import CitiesLines, { citiesLinesQuery } from './cities/CitiesLines';
    14  -import CityCreation from './cities/CityCreation';
    15  -import Security, { KNOWLEDGE_KNUPDATE } from '../../../utils/Security';
    16  -import { isUniqFilter } from '../common/lists/Filters';
    17  - 
    18  -class Cities extends Component {
    19  - constructor(props) {
    20  - super(props);
    21  - const params = buildViewParamsFromUrlAndStorage(
    22  - props.history,
    23  - props.location,
    24  - 'view-cities',
    25  - );
    26  - this.state = {
    27  - sortBy: R.propOr('name', 'sortBy', params),
    28  - orderAsc: R.propOr(true, 'orderAsc', params),
    29  - searchTerm: R.propOr('', 'searchTerm', params),
    30  - view: R.propOr('lines', 'view', params),
    31  - openExports: false,
    32  - filters: R.propOr({}, 'filters', params),
    33  - numberOfElements: { number: 0, symbol: '' },
    34  - };
    35  - }
    36  - 
    37  - saveView() {
    38  - saveViewParameters(
    39  - this.props.history,
    40  - this.props.location,
    41  - 'view-cities',
    42  - this.state,
    43  - );
    44  - }
    45  - 
    46  - handleSearch(value) {
    47  - this.setState({ searchTerm: value }, () => this.saveView());
    48  - }
    49  - 
    50  - handleSort(field, orderAsc) {
    51  - this.setState({ sortBy: field, orderAsc }, () => this.saveView());
    52  - }
    53  - 
    54  - handleToggleExports() {
    55  - this.setState({ openExports: !this.state.openExports });
    56  - }
    57  - 
    58  - handleAddFilter(key, id, value, event = null) {
    59  - if (event) {
    60  - event.stopPropagation();
    61  - event.preventDefault();
    62  - }
    63  - if (this.state.filters[key] && this.state.filters[key].length > 0) {
    64  - this.setState(
    65  - {
    66  - filters: R.assoc(
    67  - key,
    68  - isUniqFilter(key)
    69  - ? [{ id, value }]
    70  - : R.uniqBy(R.prop('id'), [
    71  - { id, value },
    72  - ...this.state.filters[key],
    73  - ]),
    74  - this.state.filters,
    75  - ),
    76  - },
    77  - () => this.saveView(),
    78  - );
    79  - } else {
    80  - this.setState(
    81  - {
    82  - filters: R.assoc(key, [{ id, value }], this.state.filters),
    83  - },
    84  - () => this.saveView(),
    85  - );
    86  - }
    87  - }
    88  - 
    89  - handleRemoveFilter(key) {
    90  - this.setState({ filters: R.dissoc(key, this.state.filters) }, () => this.saveView());
    91  - }
    92  - 
    93  - setNumberOfElements(numberOfElements) {
    94  - this.setState({ numberOfElements });
    95  - }
    96  - 
    97  - renderLines(paginationOptions) {
    98  - const {
    99  - sortBy,
    100  - orderAsc,
    101  - searchTerm,
    102  - filters,
    103  - openExports,
    104  - numberOfElements,
    105  - } = this.state;
    106  - const dataColumns = {
    107  - name: {
    108  - label: 'Name',
    109  - width: '60%',
    110  - isSortable: true,
    111  - },
    112  - created: {
    113  - label: 'Creation date',
    114  - width: '15%',
    115  - isSortable: true,
    116  - },
    117  - modified: {
    118  - label: 'Modification date',
    119  - width: '15%',
    120  - isSortable: true,
    121  - },
    122  - };
    123  - return (
    124  - <ListLines
    125  - sortBy={sortBy}
    126  - orderAsc={orderAsc}
    127  - dataColumns={dataColumns}
    128  - handleSort={this.handleSort.bind(this)}
    129  - handleSearch={this.handleSearch.bind(this)}
    130  - handleAddFilter={this.handleAddFilter.bind(this)}
    131  - handleRemoveFilter={this.handleRemoveFilter.bind(this)}
    132  - handleToggleExports={this.handleToggleExports.bind(this)}
    133  - openExports={openExports}
    134  - exportEntityType="City"
    135  - keyword={searchTerm}
    136  - filters={filters}
    137  - paginationOptions={paginationOptions}
    138  - numberOfElements={numberOfElements}
    139  - availableFilterKeys={[
    140  - 'created_start_date',
    141  - 'created_end_date',
    142  - 'createdBy',
    143  - ]}
    144  - >
    145  - <QueryRenderer
    146  - query={citiesLinesQuery}
    147  - variables={{ count: 25, ...paginationOptions }}
    148  - render={({ props }) => (
    149  - <CitiesLines
    150  - data={props}
    151  - paginationOptions={paginationOptions}
    152  - dataColumns={dataColumns}
    153  - initialLoading={props === null}
    154  - setNumberOfElements={this.setNumberOfElements.bind(this)}
    155  - />
    156  - )}
    157  - />
    158  - </ListLines>
    159  - );
    160  - }
    161  - 
    162  - render() {
    163  - const { view, sortBy, orderAsc, searchTerm, filters } = this.state;
    164  - const finalFilters = convertFilters(filters);
    165  - const paginationOptions = {
    166  - search: searchTerm,
    167  - orderBy: sortBy,
    168  - orderMode: orderAsc ? 'asc' : 'desc',
    169  - filters: finalFilters,
    170  - };
    171  - return (
    172  - <div>
    173  - {view === 'lines' ? this.renderLines(paginationOptions) : ''}
    174  - <Security needs={[KNOWLEDGE_KNUPDATE]}>
    175  - <CityCreation paginationOptions={paginationOptions} />
    176  - </Security>
    177  - </div>
    178  - );
    179  - }
    180  -}
    181  - 
    182  -Cities.propTypes = {
    183  - t: PropTypes.func,
    184  - history: PropTypes.object,
    185  - location: PropTypes.object,
    186  -};
    187  - 
    188  -export default R.compose(inject18n, withRouter)(Cities);
    189  - 
  • ■ ■ ■ ■ ■ ■
    opencti-platform/opencti-front/src/private/components/entities/Cities.tsx
     1 +import React, { FunctionComponent } from 'react';
     2 +import { convertFilters } from '../../../utils/ListParameters';
     3 +import ListLines from '../../../components/list_lines/ListLines';
     4 +import CitiesLines, { citiesLinesQuery } from './cities/CitiesLines';
     5 +import CityCreation from './cities/CityCreation';
     6 +import Security, { KNOWLEDGE_KNUPDATE } from '../../../utils/Security';
     7 +import useLocalStorage, { localStorageToPaginationOptions } from '../../../utils/hooks/useLocalStorage';
     8 +import {
     9 + CitiesLinesPaginationQuery,
     10 + CitiesLinesPaginationQuery$variables,
     11 +} from './cities/__generated__/CitiesLinesPaginationQuery.graphql';
     12 +import type { Filters } from '../../../components/list_lines';
     13 +import useQueryLoading from '../../../utils/hooks/useQueryLoading';
     14 + 
     15 +const Cities: FunctionComponent = () => {
     16 + const [viewStorage, _, storageHelpers] = useLocalStorage('view-cities', {
     17 + sortBy: 'name',
     18 + orderAsc: true,
     19 + searchTerm: '',
     20 + openExports: false,
     21 + filters: {},
     22 + numberOfElements: { number: 0, symbol: '' },
     23 + });
     24 + const finalFilters = convertFilters(viewStorage.filters) as unknown as Filters;
     25 + const paginationOptions = localStorageToPaginationOptions<CitiesLinesPaginationQuery$variables>({ ...viewStorage, filters: finalFilters, count: 25 });
     26 + 
     27 + const renderLines = () => {
     28 + const {
     29 + sortBy,
     30 + orderAsc,
     31 + searchTerm,
     32 + filters,
     33 + openExports,
     34 + numberOfElements,
     35 + } = viewStorage;
     36 + const dataColumns = {
     37 + name: {
     38 + label: 'Name',
     39 + width: '60%',
     40 + isSortable: true,
     41 + },
     42 + created: {
     43 + label: 'Creation date',
     44 + width: '15%',
     45 + isSortable: true,
     46 + },
     47 + modified: {
     48 + label: 'Modification date',
     49 + width: '15%',
     50 + isSortable: true,
     51 + },
     52 + };
     53 + 
     54 + const queryRef = useQueryLoading<CitiesLinesPaginationQuery>(citiesLinesQuery, paginationOptions);
     55 + 
     56 + return (
     57 + <ListLines
     58 + sortBy={sortBy}
     59 + orderAsc={orderAsc}
     60 + dataColumns={dataColumns}
     61 + handleSort={storageHelpers.handleSort}
     62 + handleSearch={storageHelpers.handleSearch}
     63 + handleAddFilter={storageHelpers.handleAddFilter}
     64 + handleRemoveFilter={storageHelpers.handleRemoveFilter}
     65 + handleToggleExports={storageHelpers.handleToggleExports}
     66 + openExports={openExports}
     67 + exportEntityType="City"
     68 + keyword={searchTerm}
     69 + filters={filters}
     70 + paginationOptions={paginationOptions}
     71 + numberOfElements={numberOfElements}
     72 + availableFilterKeys={[
     73 + 'created_start_date',
     74 + 'created_end_date',
     75 + 'createdBy',
     76 + ]}
     77 + >
     78 + {queryRef && (
     79 + <CitiesLines
     80 + queryRef={queryRef}
     81 + paginationOptions={paginationOptions}
     82 + dataColumns={dataColumns}
     83 + setNumberOfElements={storageHelpers.handleSetNumberOfElements}
     84 + />
     85 + )}
     86 + </ListLines>
     87 + );
     88 + };
     89 + 
     90 + return (
     91 + <div>
     92 + {renderLines()}
     93 + <Security needs={[KNOWLEDGE_KNUPDATE]}>
     94 + <CityCreation paginationOptions={paginationOptions} />
     95 + </Security>
     96 + </div>
     97 + );
     98 +};
     99 + 
     100 +export default Cities;
     101 + 
  • ■ ■ ■ ■ ■ ■
    opencti-platform/opencti-front/src/private/components/entities/cities/CitiesLines.js
    1  -import React, { Component } from 'react';
    2  -import * as PropTypes from 'prop-types';
    3  -import { graphql, createPaginationContainer } from 'react-relay';
    4  -import { pathOr } from 'ramda';
    5  -import ListLinesContent from '../../../../components/list_lines/ListLinesContent';
    6  -import { CityLine, CityLineDummy } from './CityLine';
    7  -import { setNumberOfElements } from '../../../../utils/Number';
    8  - 
    9  -const nbOfRowsToLoad = 50;
    10  - 
    11  -class CitiesLines extends Component {
    12  - componentDidUpdate(prevProps) {
    13  - setNumberOfElements(
    14  - prevProps,
    15  - this.props,
    16  - 'cities',
    17  - this.props.setNumberOfElements.bind(this),
    18  - );
    19  - }
    20  - 
    21  - render() {
    22  - const { initialLoading, dataColumns, relay, paginationOptions } = this.props;
    23  - return (
    24  - <ListLinesContent
    25  - initialLoading={initialLoading}
    26  - loadMore={relay.loadMore.bind(this)}
    27  - hasMore={relay.hasMore.bind(this)}
    28  - isLoading={relay.isLoading.bind(this)}
    29  - dataList={pathOr([], ['cities', 'edges'], this.props.data)}
    30  - globalCount={pathOr(
    31  - nbOfRowsToLoad,
    32  - ['cities', 'pageInfo', 'globalCount'],
    33  - this.props.data,
    34  - )}
    35  - LineComponent={<CityLine />}
    36  - DummyLineComponent={<CityLineDummy />}
    37  - dataColumns={dataColumns}
    38  - nbOfRowsToLoad={nbOfRowsToLoad}
    39  - paginationOptions={paginationOptions}
    40  - />
    41  - );
    42  - }
    43  -}
    44  - 
    45  -CitiesLines.propTypes = {
    46  - classes: PropTypes.object,
    47  - paginationOptions: PropTypes.object,
    48  - dataColumns: PropTypes.object.isRequired,
    49  - data: PropTypes.object,
    50  - relay: PropTypes.object,
    51  - initialLoading: PropTypes.bool,
    52  - setNumberOfElements: PropTypes.func,
    53  -};
    54  - 
    55  -export const citiesLinesQuery = graphql`
    56  - query CitiesLinesPaginationQuery(
    57  - $search: String
    58  - $count: Int!
    59  - $cursor: ID
    60  - $orderBy: CitiesOrdering
    61  - $orderMode: OrderingMode
    62  - $filters: [CitiesFiltering]
    63  - ) {
    64  - ...CitiesLines_data
    65  - @arguments(
    66  - search: $search
    67  - count: $count
    68  - cursor: $cursor
    69  - orderBy: $orderBy
    70  - orderMode: $orderMode
    71  - filters: $filters
    72  - )
    73  - }
    74  -`;
    75  - 
    76  -export default createPaginationContainer(
    77  - CitiesLines,
    78  - {
    79  - data: graphql`
    80  - fragment CitiesLines_data on Query
    81  - @argumentDefinitions(
    82  - search: { type: "String" }
    83  - count: { type: "Int", defaultValue: 25 }
    84  - cursor: { type: "ID" }
    85  - orderBy: { type: "CitiesOrdering", defaultValue: name }
    86  - orderMode: { type: "OrderingMode", defaultValue: asc }
    87  - filters: { type: "[CitiesFiltering]" }
    88  - ) {
    89  - cities(
    90  - search: $search
    91  - first: $count
    92  - after: $cursor
    93  - orderBy: $orderBy
    94  - orderMode: $orderMode
    95  - filters: $filters
    96  - ) @connection(key: "Pagination_cities") {
    97  - edges {
    98  - node {
    99  - id
    100  - name
    101  - description
    102  - ...CityLine_node
    103  - }
    104  - }
    105  - pageInfo {
    106  - endCursor
    107  - hasNextPage
    108  - globalCount
    109  - }
    110  - }
    111  - }
    112  - `,
    113  - },
    114  - {
    115  - direction: 'forward',
    116  - getConnectionFromProps(props) {
    117  - return props.data && props.data.cities;
    118  - },
    119  - getFragmentVariables(prevVars, totalCount) {
    120  - return {
    121  - ...prevVars,
    122  - count: totalCount,
    123  - };
    124  - },
    125  - getVariables(props, { count, cursor }, fragmentVariables) {
    126  - return {
    127  - search: fragmentVariables.search,
    128  - count,
    129  - cursor,
    130  - orderBy: fragmentVariables.orderBy,
    131  - orderMode: fragmentVariables.orderMode,
    132  - filters: fragmentVariables.filters,
    133  - };
    134  - },
    135  - query: citiesLinesQuery,
    136  - },
    137  -);
    138  - 
  • ■ ■ ■ ■ ■ ■
    opencti-platform/opencti-front/src/private/components/entities/cities/CitiesLines.tsx
     1 +import React, { FunctionComponent } from 'react';
     2 +import { graphql, PreloadedQuery } from 'react-relay';
     3 +import ListLinesContent from '../../../../components/list_lines/ListLinesContent';
     4 +import { CityLine, CityLineDummy } from './CityLine';
     5 +import { DataColumns } from '../../../../components/list_lines';
     6 +import type { UseLocalStorage } from '../../../../utils/hooks/useLocalStorage';
     7 +import {
     8 + CitiesLinesPaginationQuery,
     9 + CitiesLinesPaginationQuery$variables,
     10 +} from './__generated__/CitiesLinesPaginationQuery.graphql';
     11 +import { CitiesLines_data$key } from './__generated__/CitiesLines_data.graphql';
     12 +import usePreloadedPaginationFragment from '../../../../utils/hooks/usePreloadedPaginationFragment';
     13 + 
     14 +const nbOfRowsToLoad = 50;
     15 + 
     16 +interface CitiesLinesProps {
     17 + paginationOptions?: CitiesLinesPaginationQuery$variables,
     18 + dataColumns: DataColumns,
     19 + queryRef: PreloadedQuery<CitiesLinesPaginationQuery>,
     20 + setNumberOfElements: UseLocalStorage[2]['handleSetNumberOfElements'],
     21 +}
     22 + 
     23 +export const citiesLinesQuery = graphql`
     24 + query CitiesLinesPaginationQuery(
     25 + $search: String
     26 + $count: Int
     27 + $cursor: ID
     28 + $orderBy: CitiesOrdering
     29 + $orderMode: OrderingMode
     30 + $filters: [CitiesFiltering]
     31 + ) {
     32 + ...CitiesLines_data
     33 + @arguments(
     34 + search: $search
     35 + count: $count
     36 + cursor: $cursor
     37 + orderBy: $orderBy
     38 + orderMode: $orderMode
     39 + filters: $filters
     40 + )
     41 + }
     42 +`;
     43 + 
     44 +const citiesLinesFragment = graphql`
     45 + fragment CitiesLines_data on Query
     46 + @argumentDefinitions(
     47 + search: { type: "String" }
     48 + count: { type: "Int" }
     49 + cursor: { type: "ID" }
     50 + orderBy: { type: "CitiesOrdering" }
     51 + orderMode: { type: "OrderingMode" }
     52 + filters: { type: "[CitiesFiltering]" }
     53 + ) @refetchable(queryName: "CitiesLinesRefetchQuery") {
     54 + cities(
     55 + search: $search
     56 + first: $count
     57 + after: $cursor
     58 + orderBy: $orderBy
     59 + orderMode: $orderMode
     60 + filters: $filters
     61 + ) @connection(key: "Pagination_cities") {
     62 + edges {
     63 + node {
     64 + id
     65 + name
     66 + description
     67 + ...CityLine_node
     68 + }
     69 + }
     70 + pageInfo {
     71 + endCursor
     72 + hasNextPage
     73 + globalCount
     74 + }
     75 + }
     76 + }
     77 +`;
     78 + 
     79 +const CitiesLines: FunctionComponent<CitiesLinesProps> = ({ setNumberOfElements, dataColumns, queryRef, paginationOptions }) => {
     80 + const {
     81 + data,
     82 + hasMore,
     83 + loadMore,
     84 + isLoadingMore,
     85 + } = usePreloadedPaginationFragment<CitiesLinesPaginationQuery, CitiesLines_data$key>({
     86 + linesQuery: citiesLinesQuery,
     87 + linesFragment: citiesLinesFragment,
     88 + queryRef,
     89 + nodePath: ['cities', 'edges'],
     90 + setNumberOfElements,
     91 + });
     92 + 
     93 + return (
     94 + <ListLinesContent
     95 + initialLoading={!data}
     96 + hasMore={hasMore}
     97 + loadMore={loadMore}
     98 + isLoading={isLoadingMore}
     99 + dataList={data?.cities?.edges ?? []}
     100 + globalCount={data?.cities?.pageInfo?.globalCount ?? nbOfRowsToLoad}
     101 + LineComponent={CityLine}
     102 + DummyLineComponent={CityLineDummy}
     103 + dataColumns={dataColumns}
     104 + nbOfRowsToLoad={nbOfRowsToLoad}
     105 + paginationOptions={paginationOptions}
     106 + />
     107 + );
     108 +};
     109 + 
     110 +export default CitiesLines;
     111 + 
  • ■ ■ ■ ■ ■ ■
    opencti-platform/opencti-front/src/private/components/entities/cities/CityCreation.js
    1  -import React, { Component } from 'react';
    2  -import * as PropTypes from 'prop-types';
    3  -import { Formik, Form, Field } from 'formik';
    4  -import withStyles from '@mui/styles/withStyles';
    5  -import Drawer from '@mui/material/Drawer';
    6  -import Typography from '@mui/material/Typography';
    7  -import Button from '@mui/material/Button';
    8  -import IconButton from '@mui/material/IconButton';
    9  -import Fab from '@mui/material/Fab';
    10  -import { Add, Close } from '@mui/icons-material';
    11  -import * as Yup from 'yup';
    12  -import { graphql } from 'react-relay';
    13  -import { ConnectionHandler } from 'relay-runtime';
    14  -import * as R from 'ramda';
    15  -import inject18n from '../../../../components/i18n';
    16  -import { commitMutation } from '../../../../relay/environment';
    17  -import TextField from '../../../../components/TextField';
    18  -import CreatedByField from '../../common/form/CreatedByField';
    19  -import ObjectMarkingField from '../../common/form/ObjectMarkingField';
    20  -import MarkDownField from '../../../../components/MarkDownField';
    21  -import ExternalReferencesField from '../../common/form/ExternalReferencesField';
    22  - 
    23  -const styles = (theme) => ({
    24  - drawerPaper: {
    25  - minHeight: '100vh',
    26  - width: '50%',
    27  - position: 'fixed',
    28  - transition: theme.transitions.create('width', {
    29  - easing: theme.transitions.easing.sharp,
    30  - duration: theme.transitions.duration.enteringScreen,
    31  - }),
    32  - padding: 0,
    33  - },
    34  - createButton: {
    35  - position: 'fixed',
    36  - bottom: 30,
    37  - right: 30,
    38  - },
    39  - buttons: {
    40  - marginTop: 20,
    41  - textAlign: 'right',
    42  - },
    43  - button: {
    44  - marginLeft: theme.spacing(2),
    45  - },
    46  - header: {
    47  - backgroundColor: theme.palette.background.nav,
    48  - padding: '20px 20px 20px 60px',
    49  - },
    50  - closeButton: {
    51  - position: 'absolute',
    52  - top: 12,
    53  - left: 5,
    54  - color: 'inherit',
    55  - },
    56  - importButton: {
    57  - position: 'absolute',
    58  - top: 15,
    59  - right: 20,
    60  - },
    61  - container: {
    62  - padding: '10px 20px 20px 20px',
    63  - },
    64  -});
    65  - 
    66  -const cityMutation = graphql`
    67  - mutation CityCreationMutation($input: CityAddInput!) {
    68  - cityAdd(input: $input) {
    69  - ...CityLine_node
    70  - }
    71  - }
    72  -`;
    73  - 
    74  -const cityValidation = (t) => Yup.object().shape({
    75  - name: Yup.string().required(t('This field is required')),
    76  - description: Yup.string().nullable(),
    77  - latitude: Yup.number()
    78  - .typeError(t('This field must be a number'))
    79  - .nullable(),
    80  - longitude: Yup.number()
    81  - .typeError(t('This field must be a number'))
    82  - .nullable(),
    83  -});
    84  - 
    85  -const sharedUpdater = (store, userId, paginationOptions, newEdge) => {
    86  - const userProxy = store.get(userId);
    87  - const conn = ConnectionHandler.getConnection(
    88  - userProxy,
    89  - 'Pagination_cities',
    90  - paginationOptions,
    91  - );
    92  - ConnectionHandler.insertEdgeBefore(conn, newEdge);
    93  -};
    94  - 
    95  -class CityCreation extends Component {
    96  - constructor(props) {
    97  - super(props);
    98  - this.state = { open: false };
    99  - }
    100  - 
    101  - handleOpen() {
    102  - this.setState({ open: true });
    103  - }
    104  - 
    105  - handleClose() {
    106  - this.setState({ open: false });
    107  - }
    108  - 
    109  - onSubmit(values, { setSubmitting, resetForm }) {
    110  - const finalValues = R.pipe(
    111  - R.assoc('latitude', parseFloat(values.latitude)),
    112  - R.assoc('longitude', parseFloat(values.longitude)),
    113  - R.assoc('createdBy', values.createdBy?.value),
    114  - R.assoc('objectMarking', R.pluck('value', values.objectMarking)),
    115  - R.assoc('externalReferences', R.pluck('value', values.externalReferences)),
    116  - )(values);
    117  - commitMutation({
    118  - mutation: cityMutation,
    119  - variables: {
    120  - input: finalValues,
    121  - },
    122  - updater: (store) => {
    123  - const payload = store.getRootField('cityAdd');
    124  - const newEdge = payload.setLinkedRecord(payload, 'node'); // Creation of the pagination container.
    125  - const container = store.getRoot();
    126  - sharedUpdater(
    127  - store,
    128  - container.getDataID(),
    129  - this.props.paginationOptions,
    130  - newEdge,
    131  - );
    132  - },
    133  - setSubmitting,
    134  - onCompleted: () => {
    135  - setSubmitting(false);
    136  - resetForm();
    137  - this.handleClose();
    138  - },
    139  - });
    140  - }
    141  - 
    142  - onReset() {
    143  - this.handleClose();
    144  - }
    145  - 
    146  - render() {
    147  - const { t, classes } = this.props;
    148  - return (
    149  - <div>
    150  - <Fab
    151  - onClick={this.handleOpen.bind(this)}
    152  - color="secondary"
    153  - aria-label="Add"
    154  - className={classes.createButton}
    155  - >
    156  - <Add />
    157  - </Fab>
    158  - <Drawer
    159  - open={this.state.open}
    160  - anchor="right"
    161  - elevation={1}
    162  - sx={{ zIndex: 1202 }}
    163  - classes={{ paper: classes.drawerPaper }}
    164  - onClose={this.handleClose.bind(this)}
    165  - >
    166  - <div className={classes.header}>
    167  - <IconButton
    168  - aria-label="Close"
    169  - className={classes.closeButton}
    170  - onClick={this.handleClose.bind(this)}
    171  - size="large"
    172  - color="primary"
    173  - >
    174  - <Close fontSize="small" color="primary" />
    175  - </IconButton>
    176  - <Typography variant="h6">{t('Create a city')}</Typography>
    177  - </div>
    178  - <div className={classes.container}>
    179  - <Formik
    180  - initialValues={{
    181  - name: '',
    182  - description: '',
    183  - latitude: '',
    184  - longitude: '',
    185  - createdBy: '',
    186  - objectMarking: [],
    187  - externalReferences: [],
    188  - }}
    189  - validationSchema={cityValidation(t)}
    190  - onSubmit={this.onSubmit.bind(this)}
    191  - onReset={this.onReset.bind(this)}
    192  - >
    193  - {({
    194  - submitForm,
    195  - handleReset,
    196  - isSubmitting,
    197  - setFieldValue,
    198  - values,
    199  - }) => (
    200  - <Form style={{ margin: '20px 0 20px 0' }}>
    201  - <Field
    202  - component={TextField}
    203  - variant="standard"
    204  - name="name"
    205  - label={t('Name')}
    206  - fullWidth={true}
    207  - detectDuplicate={['City']}
    208  - />
    209  - <Field
    210  - component={MarkDownField}
    211  - name="description"
    212  - label={t('Description')}
    213  - fullWidth={true}
    214  - multiline={true}
    215  - rows={4}
    216  - style={{ marginTop: 20 }}
    217  - />
    218  - <Field
    219  - component={TextField}
    220  - variant="standard"
    221  - name="latitude"
    222  - label={t('Latitude')}
    223  - fullWidth={true}
    224  - style={{ marginTop: 20 }}
    225  - />
    226  - <Field
    227  - component={TextField}
    228  - variant="standard"
    229  - name="longitude"
    230  - label={t('Longitude')}
    231  - fullWidth={true}
    232  - style={{ marginTop: 20 }}
    233  - />
    234  - <CreatedByField
    235  - name="createdBy"
    236  - style={{ marginTop: 20, width: '100%' }}
    237  - setFieldValue={setFieldValue}
    238  - />
    239  - <ObjectMarkingField
    240  - name="objectMarking"
    241  - style={{ marginTop: 20, width: '100%' }}
    242  - />
    243  - <ExternalReferencesField
    244  - name="externalReferences"
    245  - style={{ marginTop: 20, width: '100%' }}
    246  - setFieldValue={setFieldValue}
    247  - values={values.externalReferences}
    248  - />
    249  - <div className={classes.buttons}>
    250  - <Button
    251  - variant="contained"
    252  - onClick={handleReset}
    253  - disabled={isSubmitting}
    254  - classes={{ root: classes.button }}
    255  - >
    256  - {t('Cancel')}
    257  - </Button>
    258  - <Button
    259  - variant="contained"
    260  - color="secondary"
    261  - onClick={submitForm}
    262  - disabled={isSubmitting}
    263  - classes={{ root: classes.button }}
    264  - >
    265  - {t('Create')}
    266  - </Button>
    267  - </div>
    268  - </Form>
    269  - )}
    270  - </Formik>
    271  - </div>
    272  - </Drawer>
    273  - </div>
    274  - );
    275  - }
    276  -}
    277  - 
    278  -CityCreation.propTypes = {
    279  - paginationOptions: PropTypes.object,
    280  - classes: PropTypes.object,
    281  - theme: PropTypes.object,
    282  - t: PropTypes.func,
    283  -};
    284  - 
    285  -export default R.compose(
    286  - inject18n,
    287  - withStyles(styles, { withTheme: true }),
    288  -)(CityCreation);
    289  - 
  • ■ ■ ■ ■ ■ ■
    opencti-platform/opencti-front/src/private/components/entities/cities/CityCreation.tsx
     1 +import React, { useState } from 'react';
     2 +import { Field, Form, Formik } from 'formik';
     3 +import Drawer from '@mui/material/Drawer';
     4 +import Typography from '@mui/material/Typography';
     5 +import Button from '@mui/material/Button';
     6 +import IconButton from '@mui/material/IconButton';
     7 +import Fab from '@mui/material/Fab';
     8 +import { Add, Close } from '@mui/icons-material';
     9 +import * as Yup from 'yup';
     10 +import { graphql, useMutation } from 'react-relay';
     11 +import * as R from 'ramda';
     12 +import makeStyles from '@mui/styles/makeStyles';
     13 +import { FormikConfig } from 'formik/dist/types';
     14 +import { Optionals } from 'yup/es/types';
     15 +import { useFormatter } from '../../../../components/i18n';
     16 +import TextField from '../../../../components/TextField';
     17 +import CreatedByField from '../../common/form/CreatedByField';
     18 +import ObjectMarkingField from '../../common/form/ObjectMarkingField';
     19 +import MarkDownField from '../../../../components/MarkDownField';
     20 +import ExternalReferencesField from '../../common/form/ExternalReferencesField';
     21 +import { Theme } from '../../../../components/Theme';
     22 +import { insertNode } from '../../../../utils/Store';
     23 +import { CitiesLinesPaginationQuery$variables } from './__generated__/CitiesLinesPaginationQuery.graphql';
     24 + 
     25 +const useStyles = makeStyles<Theme>((theme) => ({
     26 + drawerPaper: {
     27 + minHeight: '100vh',
     28 + width: '50%',
     29 + position: 'fixed',
     30 + transition: theme.transitions.create('width', {
     31 + easing: theme.transitions.easing.sharp,
     32 + duration: theme.transitions.duration.enteringScreen,
     33 + }),
     34 + padding: 0,
     35 + },
     36 + createButton: {
     37 + position: 'fixed',
     38 + bottom: 30,
     39 + right: 30,
     40 + },
     41 + buttons: {
     42 + marginTop: 20,
     43 + textAlign: 'right',
     44 + },
     45 + button: {
     46 + marginLeft: theme.spacing(2),
     47 + },
     48 + header: {
     49 + backgroundColor: theme.palette.background.nav,
     50 + padding: '20px 20px 20px 60px',
     51 + },
     52 + closeButton: {
     53 + position: 'absolute',
     54 + top: 12,
     55 + left: 5,
     56 + color: 'inherit',
     57 + },
     58 + importButton: {
     59 + position: 'absolute',
     60 + top: 15,
     61 + right: 20,
     62 + },
     63 + container: {
     64 + padding: '10px 20px 20px 20px',
     65 + },
     66 +}));
     67 + 
     68 +const cityMutation = graphql`
     69 + mutation CityCreationMutation($input: CityAddInput!) {
     70 + cityAdd(input: $input) {
     71 + ...CityLine_node
     72 + }
     73 + }
     74 +`;
     75 + 
     76 +const cityValidation = (t: (v: string) => string) => Yup.object().shape({
     77 + name: Yup.string().required(t('This field is required')),
     78 + description: Yup.string().nullable(),
     79 + latitude: Yup.number()
     80 + .typeError(t('This field must be a number'))
     81 + .nullable(),
     82 + longitude: Yup.number()
     83 + .typeError(t('This field must be a number'))
     84 + .nullable(),
     85 +});
     86 + 
     87 +interface CityAddInput {
     88 + latitude: string
     89 + longitude: string
     90 + createdBy?: { value: string, label?: string }
     91 + objectMarking: { value: string }[]
     92 + externalReferences: { value: string }[]
     93 +}
     94 + 
     95 +const CityCreation = ({ paginationOptions } : { paginationOptions: CitiesLinesPaginationQuery$variables }) => {
     96 + const classes = useStyles();
     97 + const { t } = useFormatter();
     98 + 
     99 + const [open, setOpen] = useState<boolean>(false);
     100 + const handleOpen = () => setOpen(true);
     101 + const handleClose = () => setOpen(false);
     102 + 
     103 + const [commit] = useMutation(cityMutation);
     104 + 
     105 + const onSubmit: FormikConfig<CityAddInput>['onSubmit'] = (values, { setSubmitting, resetForm }) => {
     106 + const finalValues = R.pipe(
     107 + R.assoc('latitude', parseFloat(values.latitude)),
     108 + R.assoc('longitude', parseFloat(values.longitude)),
     109 + R.assoc('createdBy', values.createdBy?.value),
     110 + R.assoc('objectMarking', R.pluck('value', values.objectMarking)),
     111 + R.assoc('externalReferences', R.pluck('value', values.externalReferences)),
     112 + )(values);
     113 + commit({
     114 + variables: {
     115 + input: finalValues,
     116 + },
     117 + updater: (store) => {
     118 + try {
     119 + console.log('paginationOptions', paginationOptions);
     120 + insertNode(store, 'Pagination_cities', {
     121 + filters: paginationOptions.filters,
     122 + orderBy: paginationOptions.orderBy,
     123 + orderMode: paginationOptions.orderMode,
     124 + search: paginationOptions.search,
     125 + }, 'cityAdd');
     126 + } catch (e) {
     127 + console.log('Error', e);
     128 + }
     129 + },
     130 + onCompleted: () => {
     131 + setSubmitting(false);
     132 + resetForm();
     133 + handleClose();
     134 + },
     135 + });
     136 + };
     137 + 
     138 + return (
     139 + <div>
     140 + <Fab
     141 + onClick={handleOpen}
     142 + color="secondary"
     143 + aria-label="Add"
     144 + className={classes.createButton}
     145 + >
     146 + <Add />
     147 + </Fab>
     148 + <Drawer
     149 + open={open}
     150 + anchor="right"
     151 + elevation={1}
     152 + sx={{ zIndex: 1202 }}
     153 + classes={{ paper: classes.drawerPaper }}
     154 + onClose={handleClose}
     155 + >
     156 + <div className={classes.header}>
     157 + <IconButton
     158 + aria-label="Close"
     159 + className={classes.closeButton}
     160 + onClick={handleClose}
     161 + size="large"
     162 + color="primary"
     163 + >
     164 + <Close fontSize="small" color="primary" />
     165 + </IconButton>
     166 + <Typography variant="h6">{t('Create a city')}</Typography>
     167 + </div>
     168 + <div className={classes.container}>
     169 + <Formik
     170 + initialValues={{
     171 + name: '',
     172 + description: '',
     173 + latitude: '',
     174 + longitude: '',
     175 + createdBy: { value: '', label: '' },
     176 + objectMarking: [],
     177 + externalReferences: [],
     178 + }}
     179 + validationSchema={cityValidation(t)}
     180 + onSubmit={onSubmit}
     181 + onReset={handleClose}
     182 + >
     183 + {({
     184 + submitForm,
     185 + handleReset,
     186 + isSubmitting,
     187 + setFieldValue,
     188 + values,
     189 + }) => (
     190 + <Form style={{ margin: '20px 0 20px 0' }}>
     191 + <Field
     192 + component={TextField}
     193 + variant="standard"
     194 + name="name"
     195 + label={t('Name')}
     196 + fullWidth={true}
     197 + detectDuplicate={['City']}
     198 + />
     199 + <Field
     200 + component={MarkDownField}
     201 + name="description"
     202 + label={t('Description')}
     203 + fullWidth={true}
     204 + multiline={true}
     205 + rows={4}
     206 + style={{ marginTop: 20 }}
     207 + />
     208 + <Field
     209 + component={TextField}
     210 + variant="standard"
     211 + name="latitude"
     212 + label={t('Latitude')}
     213 + fullWidth={true}
     214 + style={{ marginTop: 20 }}
     215 + />
     216 + <Field
     217 + component={TextField}
     218 + variant="standard"
     219 + name="longitude"
     220 + label={t('Longitude')}
     221 + fullWidth={true}
     222 + style={{ marginTop: 20 }}
     223 + />
     224 + <CreatedByField
     225 + name="createdBy"
     226 + style={{ marginTop: 20, width: '100%' }}
     227 + setFieldValue={setFieldValue}
     228 + />
     229 + <ObjectMarkingField
     230 + name="objectMarking"
     231 + style={{ marginTop: 20, width: '100%' }}
     232 + />
     233 + <ExternalReferencesField
     234 + name="externalReferences"
     235 + style={{ marginTop: 20, width: '100%' }}
     236 + setFieldValue={setFieldValue}
     237 + values={values.externalReferences}
     238 + />
     239 + <div className={classes.buttons}>
     240 + <Button
     241 + variant="contained"
     242 + onClick={handleReset}
     243 + disabled={isSubmitting}
     244 + classes={{ root: classes.button }}
     245 + >
     246 + {t('Cancel')}
     247 + </Button>
     248 + <Button
     249 + variant="contained"
     250 + color="secondary"
     251 + onClick={submitForm}
     252 + disabled={isSubmitting}
     253 + classes={{ root: classes.button }}
     254 + >
     255 + {t('Create')}
     256 + </Button>
     257 + </div>
     258 + </Form>
     259 + )}
     260 + </Formik>
     261 + </div>
     262 + </Drawer>
     263 + </div>
     264 + );
     265 +};
     266 + 
     267 +export default CityCreation;
     268 + 
  • ■ ■ ■ ■ ■ ■
    opencti-platform/opencti-front/src/private/components/entities/cities/CityLine.js
    1  -import React, { Component } from 'react';
    2  -import * as PropTypes from 'prop-types';
    3  -import { Link } from 'react-router-dom';
    4  -import { graphql, createFragmentContainer } from 'react-relay';
    5  -import withStyles from '@mui/styles/withStyles';
    6  -import ListItem from '@mui/material/ListItem';
    7  -import ListItemIcon from '@mui/material/ListItemIcon';
    8  -import ListItemText from '@mui/material/ListItemText';
    9  -import { KeyboardArrowRightOutlined } from '@mui/icons-material';
    10  -import { CityVariantOutline } from 'mdi-material-ui';
    11  -import { compose } from 'ramda';
    12  -import Skeleton from '@mui/material/Skeleton';
    13  -import inject18n from '../../../../components/i18n';
    14  - 
    15  -const styles = (theme) => ({
    16  - item: {
    17  - paddingLeft: 10,
    18  - height: 50,
    19  - },
    20  - itemIcon: {
    21  - color: theme.palette.primary.main,
    22  - },
    23  - bodyItem: {
    24  - height: 20,
    25  - fontSize: 13,
    26  - float: 'left',
    27  - whiteSpace: 'nowrap',
    28  - overflow: 'hidden',
    29  - textOverflow: 'ellipsis',
    30  - paddingRight: 5,
    31  - },
    32  - goIcon: {
    33  - position: 'absolute',
    34  - right: -10,
    35  - },
    36  - itemIconDisabled: {
    37  - color: theme.palette.grey[700],
    38  - },
    39  - placeholder: {
    40  - display: 'inline-block',
    41  - height: '1em',
    42  - backgroundColor: theme.palette.grey[700],
    43  - },
    44  -});
    45  - 
    46  -class CityLineComponent extends Component {
    47  - render() {
    48  - const { fd, classes, dataColumns, node } = this.props;
    49  - return (
    50  - <ListItem
    51  - classes={{ root: classes.item }}
    52  - divider={true}
    53  - button={true}
    54  - component={Link}
    55  - to={`/dashboard/entities/cities/${node.id}`}
    56  - >
    57  - <ListItemIcon classes={{ root: classes.itemIcon }}>
    58  - <CityVariantOutline />
    59  - </ListItemIcon>
    60  - <ListItemText
    61  - primary={
    62  - <div>
    63  - <div
    64  - className={classes.bodyItem}
    65  - style={{ width: dataColumns.name.width }}
    66  - >
    67  - {node.name}
    68  - </div>
    69  - <div
    70  - className={classes.bodyItem}
    71  - style={{ width: dataColumns.created.width }}
    72  - >
    73  - {fd(node.created)}
    74  - </div>
    75  - <div
    76  - className={classes.bodyItem}
    77  - style={{ width: dataColumns.modified.width }}
    78  - >
    79  - {fd(node.modified)}
    80  - </div>
    81  - </div>
    82  - }
    83  - />
    84  - <ListItemIcon classes={{ root: classes.goIcon }}>
    85  - <KeyboardArrowRightOutlined />
    86  - </ListItemIcon>
    87  - </ListItem>
    88  - );
    89  - }
    90  -}
    91  - 
    92  -CityLineComponent.propTypes = {
    93  - dataColumns: PropTypes.object,
    94  - node: PropTypes.object,
    95  - classes: PropTypes.object,
    96  - fd: PropTypes.func,
    97  -};
    98  - 
    99  -const CityLineFragment = createFragmentContainer(CityLineComponent, {
    100  - node: graphql`
    101  - fragment CityLine_node on City {
    102  - id
    103  - name
    104  - created
    105  - modified
    106  - }
    107  - `,
    108  -});
    109  - 
    110  -export const CityLine = compose(
    111  - inject18n,
    112  - withStyles(styles),
    113  -)(CityLineFragment);
    114  - 
    115  -class CityLineDummyComponent extends Component {
    116  - render() {
    117  - const { classes, dataColumns } = this.props;
    118  - return (
    119  - <ListItem classes={{ root: classes.item }} divider={true}>
    120  - <ListItemIcon classes={{ root: classes.itemIcon }}>
    121  - <Skeleton
    122  - animation="wave"
    123  - variant="circular"
    124  - width={30}
    125  - height={30}
    126  - />
    127  - </ListItemIcon>
    128  - <ListItemText
    129  - primary={
    130  - <div>
    131  - <div
    132  - className={classes.bodyItem}
    133  - style={{ width: dataColumns.name.width }}
    134  - >
    135  - <Skeleton
    136  - animation="wave"
    137  - variant="rectangular"
    138  - width="90%"
    139  - height="100%"
    140  - />
    141  - </div>
    142  - <div
    143  - className={classes.bodyItem}
    144  - style={{ width: dataColumns.created.width }}
    145  - >
    146  - <Skeleton
    147  - animation="wave"
    148  - variant="rectangular"
    149  - width={140}
    150  - height="100%"
    151  - />
    152  - </div>
    153  - <div
    154  - className={classes.bodyItem}
    155  - style={{ width: dataColumns.modified.width }}
    156  - >
    157  - <Skeleton
    158  - animation="wave"
    159  - variant="rectangular"
    160  - width={140}
    161  - height="100%"
    162  - />
    163  - </div>
    164  - </div>
    165  - }
    166  - />
    167  - <ListItemIcon classes={{ root: classes.goIcon }}>
    168  - <KeyboardArrowRightOutlined />
    169  - </ListItemIcon>
    170  - </ListItem>
    171  - );
    172  - }
    173  -}
    174  - 
    175  -CityLineDummyComponent.propTypes = {
    176  - dataColumns: PropTypes.object,
    177  - classes: PropTypes.object,
    178  -};
    179  - 
    180  -export const CityLineDummy = compose(
    181  - inject18n,
    182  - withStyles(styles),
    183  -)(CityLineDummyComponent);
    184  - 
  • ■ ■ ■ ■ ■ ■
    opencti-platform/opencti-front/src/private/components/entities/cities/CityLine.tsx
     1 +import React, { FunctionComponent } from 'react';
     2 +import { Link } from 'react-router-dom';
     3 +import { graphql, useFragment } from 'react-relay';
     4 +import ListItem from '@mui/material/ListItem';
     5 +import ListItemIcon from '@mui/material/ListItemIcon';
     6 +import ListItemText from '@mui/material/ListItemText';
     7 +import { KeyboardArrowRightOutlined } from '@mui/icons-material';
     8 +import { CityVariantOutline } from 'mdi-material-ui';
     9 +import Skeleton from '@mui/material/Skeleton';
     10 +import makeStyles from '@mui/styles/makeStyles';
     11 +import { useFormatter } from '../../../../components/i18n';
     12 +import { Theme } from '../../../../components/Theme';
     13 +import { DataColumns } from '../../../../components/list_lines';
     14 +import { CityLine_node$key } from './__generated__/CityLine_node.graphql';
     15 + 
     16 +const useStyles = makeStyles<Theme>((theme) => ({
     17 + item: {
     18 + paddingLeft: 10,
     19 + height: 50,
     20 + },
     21 + itemIcon: {
     22 + color: theme.palette.primary.main,
     23 + },
     24 + bodyItem: {
     25 + height: 20,
     26 + fontSize: 13,
     27 + float: 'left',
     28 + whiteSpace: 'nowrap',
     29 + overflow: 'hidden',
     30 + textOverflow: 'ellipsis',
     31 + paddingRight: 5,
     32 + },
     33 + goIcon: {
     34 + position: 'absolute',
     35 + right: -10,
     36 + },
     37 + itemIconDisabled: {
     38 + color: theme.palette.grey?.[700],
     39 + },
     40 + placeholder: {
     41 + display: 'inline-block',
     42 + height: '1em',
     43 + backgroundColor: theme.palette.grey?.[700],
     44 + },
     45 +}));
     46 + 
     47 +interface CityLineComponentProps {
     48 + dataColumns: DataColumns,
     49 + node: CityLine_node$key,
     50 +}
     51 + 
     52 +const cityFragment = graphql`
     53 + fragment CityLine_node on City {
     54 + id
     55 + name
     56 + created
     57 + modified
     58 + }
     59 +`;
     60 + 
     61 +export const CityLine: FunctionComponent<CityLineComponentProps> = ({ dataColumns, node }) => {
     62 + const classes = useStyles();
     63 + const { fd } = useFormatter();
     64 + 
     65 + const data = useFragment(cityFragment, node);
     66 + 
     67 + return (
     68 + <ListItem
     69 + classes={{ root: classes.item }}
     70 + divider={true}
     71 + button={true}
     72 + component={Link}
     73 + to={`/dashboard/entities/cities/${data.id}`}
     74 + >
     75 + <ListItemIcon classes={{ root: classes.itemIcon }}>
     76 + <CityVariantOutline />
     77 + </ListItemIcon>
     78 + <ListItemText
     79 + primary={
     80 + <div>
     81 + <div
     82 + className={classes.bodyItem}
     83 + style={{ width: dataColumns.name.width }}
     84 + >
     85 + {data.name}
     86 + </div>
     87 + <div
     88 + className={classes.bodyItem}
     89 + style={{ width: dataColumns.created.width }}
     90 + >
     91 + {fd(data.created)}
     92 + </div>
     93 + <div
     94 + className={classes.bodyItem}
     95 + style={{ width: dataColumns.modified.width }}
     96 + >
     97 + {fd(data.modified)}
     98 + </div>
     99 + </div>
     100 + }
     101 + />
     102 + <ListItemIcon classes={{ root: classes.goIcon }}>
     103 + <KeyboardArrowRightOutlined />
     104 + </ListItemIcon>
     105 + </ListItem>
     106 + );
     107 +};
     108 + 
     109 +export const CityLineDummy = ({ dataColumns }: { dataColumns: DataColumns }) => {
     110 + const classes = useStyles();
     111 + return (
     112 + <ListItem classes={{ root: classes.item }} divider={true}>
     113 + <ListItemIcon classes={{ root: classes.itemIcon }}>
     114 + <Skeleton
     115 + animation="wave"
     116 + variant="circular"
     117 + width={30}
     118 + height={30}
     119 + />
     120 + </ListItemIcon>
     121 + <ListItemText
     122 + primary={
     123 + <div>
     124 + <div
     125 + className={classes.bodyItem}
     126 + style={{ width: dataColumns.name.width }}
     127 + >
     128 + <Skeleton
     129 + animation="wave"
     130 + variant="rectangular"
     131 + width="90%"
     132 + height="100%"
     133 + />
     134 + </div>
     135 + <div
     136 + className={classes.bodyItem}
     137 + style={{ width: dataColumns.created.width }}
     138 + >
     139 + <Skeleton
     140 + animation="wave"
     141 + variant="rectangular"
     142 + width={140}
     143 + height="100%"
     144 + />
     145 + </div>
     146 + <div
     147 + className={classes.bodyItem}
     148 + style={{ width: dataColumns.modified.width }}
     149 + >
     150 + <Skeleton
     151 + animation="wave"
     152 + variant="rectangular"
     153 + width={140}
     154 + height="100%"
     155 + />
     156 + </div>
     157 + </div>
     158 + }
     159 + />
     160 + <ListItemIcon classes={{ root: classes.goIcon }}>
     161 + <KeyboardArrowRightOutlined />
     162 + </ListItemIcon>
     163 + </ListItem>
     164 + );
     165 +};
     166 + 
  • ■ ■ ■ ■ ■
    opencti-platform/opencti-front/src/private/components/events/StixSightingRelationships.tsx
    skipped 6 lines
    7 7  } from './stix_sighting_relationships/StixSightingRelationshipsLines';
    8 8  import { convertFilters } from '../../../utils/ListParameters';
    9 9  import useLocalStorage, { localStorageToPaginationOptions } from '../../../utils/hooks/useLocalStorage';
    10  -import { isUniqFilter } from '../common/lists/Filters';
    11 10  import { Filters, PaginationOptions } from '../../../components/list_lines';
    12 11   
    13 12  const dataColumns = {
    skipped 46 lines
    60 59  const LOCAL_STORAGE_KEY = 'view-stix-sighting-relationships';
    61 60   
    62 61  const StixSightingRelationships = () => {
    63  - const [viewStorage, setViewStorage] = useLocalStorage(LOCAL_STORAGE_KEY, {
     62 + const [viewStorage, _, storageHelpers] = useLocalStorage(LOCAL_STORAGE_KEY, {
    64 63   numberOfElements: { number: 0, symbol: '' },
    65 64   filters: {} as Filters,
    66 65   searchTerm: '',
    skipped 11 lines
    78 77   openExports,
    79 78   } = viewStorage;
    80 79   
    81  - const handleRemoveFilter = (key: string) => setViewStorage((c) => ({ ...c, filters: R.dissoc(key, c.filters) }));
    82  - 
    83  - const handleSearch = (value: string) => setViewStorage((c) => ({ ...c, searchTerm: value }));
    84  - 
    85  - const handleSort = (field: string, order: boolean) => setViewStorage((c) => ({
    86  - ...c,
    87  - sortBy: field,
    88  - orderAsc: order,
    89  - }));
    90  - 
    91  - const handleAddFilter = (key: string, id: string, value: Record<string, unknown>, event: KeyboardEvent) => {
    92  - if (event) {
    93  - event.stopPropagation();
    94  - event.preventDefault();
    95  - }
    96  - if ((filters[key]?.length ?? 0) > 0) {
    97  - setViewStorage((c) => ({
    98  - ...c,
    99  - filters: {
    100  - ...c.filters,
    101  - [key]: isUniqFilter(key)
    102  - ? [{ id, value }]
    103  - : [
    104  - ...(c.filters[key].filter((f) => f.id !== id) ?? []),
    105  - {
    106  - id,
    107  - value,
    108  - },
    109  - ],
    110  - },
    111  - }));
    112  - } else {
    113  - setViewStorage((c) => ({ ...c, filters: R.assoc(key, [{ id, value }], c.filters) }));
    114  - }
    115  - };
    116  - 
    117 80   const renderLines = (paginationOptions: PaginationOptions) => (
    118 81   <ListLines
    119 82   sortBy={sortBy}
    120 83   orderAsc={orderAsc}
    121 84   dataColumns={dataColumns}
    122  - handleSort={handleSort}
    123  - handleSearch={handleSearch}
    124  - handleAddFilter={handleAddFilter}
    125  - handleRemoveFilter={handleRemoveFilter}
    126  - handleToggleExports={() => setViewStorage((c) => ({ ...c, openExports: !c.openExports }))}
     85 + handleSort={storageHelpers.handleSort}
     86 + handleSearch={storageHelpers.handleSearch}
     87 + handleAddFilter={storageHelpers.handleAddFilter}
     88 + handleRemoveFilter={storageHelpers.handleRemoveFilter}
     89 + handleToggleExports={storageHelpers.handleToggleExports}
    127 90   openExports={openExports}
    128 91   exportEntityType="stix-sighting-relationship"
    129 92   keyword={searchTerm}
    skipped 23 lines
    153 116   paginationOptions={paginationOptions}
    154 117   dataColumns={dataColumns}
    155 118   initialLoading={props === null}
    156  - onLabelClick={handleAddFilter}
    157  - setNumberOfElements={(value: { number: number, symbol: string }) => setViewStorage((c) => ({
    158  - ...c,
    159  - numberOfElements: value,
    160  - }))}
     119 + onLabelClick={storageHelpers.handleAddFilter}
     120 + setNumberOfElements={storageHelpers.handleSetNumberOfElements}
    161 121   />
    162 122   )}
    163 123   />
    skipped 1 lines
    165 125   );
    166 126   
    167 127   let toSightingId = null;
    168  - let processedFilters = filters;
     128 + let processedFilters = filters as Filters;
    169 129   if (filters?.toSightingId) {
    170 130   toSightingId = R.head(filters.toSightingId)?.id;
    171  - processedFilters = R.dissoc('toSightingId', processedFilters);
     131 + processedFilters = R.dissoc<Filters, string>('toSightingId', processedFilters);
    172 132   }
    173 133   const finalFilters = convertFilters(processedFilters) as unknown as Filters;
    174 134   const paginationOptions = localStorageToPaginationOptions({ ...viewStorage, toId: toSightingId, filters: finalFilters });
    skipped 7 lines
  • ■ ■ ■ ■ ■ ■
    opencti-platform/opencti-front/src/utils/hooks/useForceUpdate.ts
     1 +import { useCallback, useState } from 'react';
     2 + 
     3 +const useForceUpdate = () => {
     4 + const [, setValue] = useState<object>({});
     5 + 
     6 + return useCallback((): void => {
     7 + setValue({});
     8 + }, []);
     9 +};
     10 + 
     11 +export default useForceUpdate;
     12 + 
  • ■ ■ ■ ■ ■
    opencti-platform/opencti-front/src/utils/hooks/useLocalStorage.ts
    1  -import { Dispatch, SetStateAction, useState } from 'react';
     1 +import React, { Dispatch, SetStateAction, useState } from 'react';
     2 +import * as R from 'ramda';
    2 3  import { isEmptyField } from '../utils';
    3 4  import { Filters, OrderMode, PaginationOptions } from '../../components/list_lines';
     5 +import { isUniqFilter } from '../../private/components/common/lists/Filters';
    4 6   
    5 7  export interface LocalStorage {
    6 8   numberOfElements?: { number: number, symbol: string },
    skipped 4 lines
    11 13   openExports?: boolean,
    12 14  }
    13 15   
    14  -export const localStorageToPaginationOptions = <T extends LocalStorage>({ searchTerm, filters, sortBy, orderAsc, ...props }: T): PaginationOptions => ({
    15  - ...props,
    16  - search: searchTerm,
    17  - orderMode: orderAsc ? OrderMode.asc : OrderMode.desc,
    18  - orderBy: sortBy,
     16 +export const localStorageToPaginationOptions = <U>({
     17 + searchTerm,
    19 18   filters,
    20  -});
     19 + sortBy,
     20 + orderAsc,
     21 + ...props
     22 +}: LocalStorage & Omit<U, 'filters'>): unknown extends U ? PaginationOptions : U => ({
     23 + ...props,
     24 + search: searchTerm,
     25 + orderMode: orderAsc ? OrderMode.asc : OrderMode.desc,
     26 + orderBy: sortBy,
     27 + filters,
     28 + }) as unknown extends U ? PaginationOptions : U;
    21 29   
    22  -const useLocalStorage = <T = LocalStorage>(key: string, initialValue: T): [value: T, setValue: Dispatch<SetStateAction<T>>] => {
     30 +export type UseLocalStorage = [value: LocalStorage,
     31 + setValue: Dispatch<SetStateAction<LocalStorage>>,
     32 + helpers: {
     33 + handleSearch: (value: string) => void,
     34 + handleRemoveFilter: (key: string) => void,
     35 + handleSort: (field: string, order: boolean) => void
     36 + handleAddFilter: (k: string, id: string, value: Record<string, unknown>, event: React.KeyboardEvent) => void
     37 + handleToggleExports: () => void,
     38 + handleSetNumberOfElements: (value: { number?: number, symbol?: string }) => void,
     39 + },
     40 +];
     41 + 
     42 +const useLocalStorage = (key: string, initialValue: LocalStorage): UseLocalStorage => {
    23 43   // State to store our value
    24 44   // Pass initial state function to useState so logic is only executed once
    25  - const [storedValue, setStoredValue] = useState<T>(() => {
     45 + const [storedValue, setStoredValue] = useState<LocalStorage>(() => {
    26 46   if (typeof window === 'undefined') {
    27 47   return initialValue;
    28 48   }
    skipped 1 lines
    30 50   // Get from local storage by key
    31 51   const item = window.localStorage.getItem(key);
    32 52   // Parse stored json or if none return initialValue
    33  - const value: T = item ? JSON.parse(item) : null;
    34  - return isEmptyField<T>(value) ? initialValue : value;
     53 + const value: LocalStorage = item ? JSON.parse(item) : null;
     54 + return isEmptyField<LocalStorage>(value) ? initialValue : value;
    35 55   } catch (error) {
    36 56   // If error also return initialValue
    37 57   throw Error('Error while initializing values in local storage');
    skipped 1 lines
    39 59   });
    40 60   // Return a wrapped version of useState's setter function that ...
    41 61   // ... persists the new value to localStorage.
    42  - const setValue = (value: T | ((val: T) => T)) => {
     62 + const setValue = (value: LocalStorage | ((val: LocalStorage) => LocalStorage)) => {
    43 63   try {
    44 64   // Allow value to be a function so we have same API as useState
    45 65   const valueToStore = value instanceof Function ? value(storedValue) : value;
    skipped 8 lines
    54 74   throw Error('Error while setting values in local storage');
    55 75   }
    56 76   };
    57  - return [storedValue, setValue];
     77 + 
     78 + const helpers = {
     79 + handleSearch: (value: string) => setValue((c) => ({ ...c, searchTerm: value })),
     80 + handleRemoveFilter: (value: string) => setValue((c) => ({ ...c, filters: R.dissoc<Filters, string>(value, c.filters as Filters) })),
     81 + handleSort: (field: string, order: boolean) => setValue((c) => ({
     82 + ...c,
     83 + sortBy: field,
     84 + orderAsc: order,
     85 + })),
     86 + handleAddFilter: (k: string, id: string, value: Record<string, unknown>, event: React.KeyboardEvent) => {
     87 + if (event) {
     88 + event.stopPropagation();
     89 + event.preventDefault();
     90 + }
     91 + if ((storedValue?.filters?.[k]?.length ?? 0) > 0) {
     92 + setValue((c) => ({
     93 + ...c,
     94 + filters: {
     95 + ...c.filters,
     96 + [k]: isUniqFilter(k)
     97 + ? [{ id, value }]
     98 + : [
     99 + ...(c.filters?.[k].filter((f) => f.id !== id) ?? []),
     100 + {
     101 + id,
     102 + value,
     103 + },
     104 + ],
     105 + },
     106 + }));
     107 + } else {
     108 + setValue((c) => ({ ...c, filters: R.assoc(k, [{ id, value }], c.filters) }));
     109 + }
     110 + },
     111 + handleToggleExports: () => setValue((c) => ({ ...c, openExports: !c.openExports })),
     112 + handleSetNumberOfElements: ({ number, symbol }: { number?: number, symbol?: string }) => setValue((c) => ({
     113 + ...c,
     114 + numberOfElements: {
     115 + ...c.numberOfElements,
     116 + ...(number ? { number } : { number: 0 }),
     117 + ...(symbol ? { symbol } : { symbol: '' }),
     118 + },
     119 + })),
     120 + };
     121 + 
     122 + return [storedValue, setValue, helpers];
    58 123  };
    59 124   
    60 125  export default useLocalStorage;
    skipped 1 lines
  • ■ ■ ■ ■ ■ ■
    opencti-platform/opencti-front/src/utils/hooks/usePreloadedPaginationFragment.ts
     1 +import { usePaginationFragment, usePreloadedQuery } from 'react-relay';
     2 +import { useEffect } from 'react';
     3 +import { PreloadedQuery } from 'react-relay/relay-hooks/EntryPointTypes';
     4 +import { GraphQLTaggedNode, OperationType } from 'relay-runtime';
     5 +import { KeyType } from 'react-relay/relay-hooks/helpers';
     6 +import { UseLocalStorage } from './useLocalStorage';
     7 + 
     8 +interface UsePreloadedPaginationFragment<QueryType extends OperationType> {
     9 + queryRef: PreloadedQuery<QueryType>
     10 + linesQuery: GraphQLTaggedNode
     11 + linesFragment: GraphQLTaggedNode
     12 + nodePath: string[]
     13 + setNumberOfElements?: UseLocalStorage[2]['handleSetNumberOfElements']
     14 +}
     15 + 
     16 +const usePreloadedPaginationFragment = <QueryType extends OperationType, FragmentKey extends KeyType>({
     17 + queryRef,
     18 + linesQuery,
     19 + linesFragment,
     20 + nodePath,
     21 + setNumberOfElements,
     22 +}: UsePreloadedPaginationFragment<QueryType>) => {
     23 + const queryData = usePreloadedQuery(linesQuery, queryRef) as FragmentKey;
     24 + const {
     25 + data,
     26 + hasNext,
     27 + loadNext,
     28 + isLoadingNext,
     29 + } = usePaginationFragment<QueryType, FragmentKey>(linesFragment, queryData);
     30 + useEffect(() => {
     31 + const deep_value = nodePath.reduce((a, v) => a[v as keyof object], data as object) as unknown[];
     32 + if (setNumberOfElements && deep_value) {
     33 + setNumberOfElements({ number: deep_value.length });
     34 + }
     35 + }, [data]);
     36 + 
     37 + return {
     38 + data,
     39 + hasMore: () => hasNext,
     40 + isLoadingMore: () => isLoadingNext,
     41 + loadMore: loadNext,
     42 + };
     43 +};
     44 + 
     45 +export default usePreloadedPaginationFragment;
     46 + 
  • ■ ■ ■ ■ ■ ■
    opencti-platform/opencti-front/src/utils/hooks/useQueryLoading.ts
     1 +import { GraphQLTaggedNode, PreloadableConcreteRequest, useQueryLoader, VariablesOf } from 'react-relay';
     2 +import { OperationType } from 'relay-runtime';
     3 +import { useEffect, useRef } from 'react';
     4 +import { equals } from 'ramda';
     5 + 
     6 +const useQueryLoading = <T extends OperationType>(
     7 + query: GraphQLTaggedNode | PreloadableConcreteRequest<T>,
     8 + variables: VariablesOf<T> = {},
     9 +) => {
     10 + const [queryRef, loadQuery] = useQueryLoader<T>(query);
     11 + const varRef = useRef(variables);
     12 + if (!equals(variables, varRef.current)) {
     13 + varRef.current = variables;
     14 + }
     15 + console.log('Variables', JSON.stringify(variables));
     16 + // refetch when variables change
     17 + useEffect(() => {
     18 + loadQuery(variables);
     19 + }, [varRef.current]);
     20 + 
     21 + return queryRef;
     22 +};
     23 + 
     24 +export default useQueryLoading;
     25 + 
Please wait...
Page is in error, reload to recover