From e966485ef613e6db47181472ee8ed6a7c287b9ad Mon Sep 17 00:00:00 2001 From: Bonsai8863 <131906254+Bonsai8863@users.noreply.github.com> Date: Thu, 20 Jun 2024 12:43:07 -0400 Subject: [PATCH] [frontend] Add page titles to Data pages WIP - Adding Titles to pages WIP - Updating Titles WIP - Updating Titles for Functional Components only. Updated Titles. Some class component pages have not been updated. Revert "Updated Titles. Some class component pages have not been updated." This reverts commit babb1cd21ac8afbdda0e1abd2c7339981e14a9db. Updated Titles. Some class component pages have not been updated. Resolving Merge Conflicts WIP - Converting class components to functional components WIP - pushing current updates. Fix merge issues Fix Roles.tsx to be able to compile Add latest version of Narratives.tsx Replace key 'FAB_REPLACED' with 'FAB_REPLACEMENT' Convert KillChainPhases to functional component Updated Sectors. Need to update SectorLine and SectorsLines. WIP - converting IngestionRss WIP - finished IngestionRSS conversion WIP WIP - Modified IngestionTaxiis WIP editing Connectors and Tasks. WIP - one more to convert WIP - modifying Streams.tsx and ImportContent.tsx. WIP Convert Connectors back to JSX functional component WIP - updated Streams Convert Stream to functional Finished Import Content Remove changes from PR 7753 Remove changes from PR 7753 (pt 2) Remove changes from PR 7753 (pt 3) Remove changes from PR 7753 (pt 4) Remove duplicate Data titles Remove duplicate Data titles (pt 3) Remove sectors.jsx Add translations Convert Sectors closer to original component Remove variant from breadcrumbs --- .../opencti-front/lang/front/de.json | 4 + .../opencti-front/lang/front/en.json | 4 + .../opencti-front/lang/front/es.json | 4 + .../opencti-front/lang/front/fr.json | 4 + .../opencti-front/lang/front/ja.json | 4 + .../opencti-front/lang/front/ko.json | 4 + .../opencti-front/lang/front/zh.json | 4 + .../private/components/data/Connectors.jsx | 84 +- .../src/private/components/data/Stream.jsx | 152 ---- .../src/private/components/data/Stream.tsx | 143 ++++ .../components/data/import/ImportContent.jsx | 802 +++++++++--------- .../private/components/entities/Sectors.jsx | 119 +-- 12 files changed, 684 insertions(+), 644 deletions(-) delete mode 100644 opencti-platform/opencti-front/src/private/components/data/Stream.jsx create mode 100644 opencti-platform/opencti-front/src/private/components/data/Stream.tsx diff --git a/opencti-platform/opencti-front/lang/front/de.json b/opencti-platform/opencti-front/lang/front/de.json index 7d260d41bad5..774e0c7724c6 100644 --- a/opencti-platform/opencti-front/lang/front/de.json +++ b/opencti-platform/opencti-front/lang/front/de.json @@ -594,6 +594,7 @@ "Data import and analyst workbenches": "Datenimport und Analysten-Workbenches", "Data sharing": "Gemeinsame Nutzung von Daten", "Data sharing configuration": "Konfiguration der gemeinsamen Datennutzung", + "Data Sharing: Live Streams | Data": "Datenaustausch: Live-Streams | Daten", "Data source": "Datenquelle", "Data sources": "Datenquellen", "Data type": "Datentyp", @@ -1315,6 +1316,7 @@ "Import knowledge": "Wissen importieren", "Import successfully asked": "Import erfolgreich abgefragt", "Import type": "Art des Imports", + "Import: Import | Data": "Import: Import | Daten", "Important notice: your action is required!": "Wichtiger Hinweis: Ihr Handeln ist erforderlich!", "Imports": "Importe", "In all the database": "In der gesamten Datenbank", @@ -1385,6 +1387,7 @@ "Ingested entities": "Verschluckte Entitäten", "Ingestion": "Ingestion", "INGESTION_MANAGER": "Ingestion-Manager", + "Ingestion: Connectors | Data": "Aufnahme: Konnektoren | Daten", "Initial score": "Anfangspunktzahl", "Initiator": "Initiator", "innovator": "innovator", @@ -2324,6 +2327,7 @@ "Secondary motivations": "Sekundäre Motivationen", "Sector": "Sektor", "Sectors": "Sektoren", + "Sectors | Entities": "Branchen | Entitäten", "Sectors and organizations": "Sektoren und Organisationen", "Security": "Sicherheit", "See all entities created by user": "Alle vom Benutzer erstellten Entitäten anzeigen", diff --git a/opencti-platform/opencti-front/lang/front/en.json b/opencti-platform/opencti-front/lang/front/en.json index 7161031945a1..f400a4f01b0d 100644 --- a/opencti-platform/opencti-front/lang/front/en.json +++ b/opencti-platform/opencti-front/lang/front/en.json @@ -594,6 +594,7 @@ "Data import and analyst workbenches": "Data import and analyst workbenches", "Data sharing": "Data sharing", "Data sharing configuration": "Data sharing configuration", + "Data Sharing: Live Streams | Data": "Data Sharing: Live Streams | Data", "Data source": "Data source", "Data sources": "Data sources", "Data type": "Data type", @@ -1315,6 +1316,7 @@ "Import knowledge": "Import knowledge", "Import successfully asked": "Import successfully asked", "Import type": "Import type", + "Import: Import | Data": "Import: Import | Data", "Important notice: your action is required!": "Important notice: your action is required!", "Imports": "Imports", "In all the database": "In all the database", @@ -1385,6 +1387,7 @@ "Ingested entities": "Ingested entities", "Ingestion": "Ingestion", "INGESTION_MANAGER": "Ingestion manager", + "Ingestion: Connectors | Data": "Ingestion: Connectors | Data", "Initial score": "Initial score", "Initiator": "Initiator", "innovator": "innovator", @@ -2324,6 +2327,7 @@ "Secondary motivations": "Secondary motivations", "Sector": "Sector", "Sectors": "Sectors", + "Sectors | Entities": "Sectors | Entities", "Sectors and organizations": "Sectors and organizations", "Security": "Security", "See all entities created by user": "See all entities created by user", diff --git a/opencti-platform/opencti-front/lang/front/es.json b/opencti-platform/opencti-front/lang/front/es.json index 275937f3efd4..03ccb3a7a299 100644 --- a/opencti-platform/opencti-front/lang/front/es.json +++ b/opencti-platform/opencti-front/lang/front/es.json @@ -594,6 +594,7 @@ "Data import and analyst workbenches": "Bancos de trabajo de importación de datos y de análisis", "Data sharing": "Compartición de datos", "Data sharing configuration": "Configuración de compartición de datos", + "Data Sharing: Live Streams | Data": "Intercambio de datos: transmisiones en vivo | Datos", "Data source": "Fuente de datos", "Data sources": "Fuentes de datos", "Data type": "Tipo de datos", @@ -1315,6 +1316,7 @@ "Import knowledge": "Importar conocimiento", "Import successfully asked": "Importación solicitada exitosamente", "Import type": "Tipo de importación", + "Import: Import | Data": "Importar: Importar | Datos", "Important notice: your action is required!": "Aviso importante: ¡su acción es necesaria!", "Imports": "Importaciones", "In all the database": "En toda la base de datos", @@ -1385,6 +1387,7 @@ "Ingested entities": "Entidades ingestadas", "Ingestion": "Ingestión", "INGESTION_MANAGER": "Gestor del ingestion", + "Ingestion: Connectors | Data": "Ingestión: Conectores | Datos", "Initial score": "Puntuación inicial", "Initiator": "Iniciador", "innovator": "Innovadora", @@ -2324,6 +2327,7 @@ "Secondary motivations": "Motivaciones secundarias", "Sector": "Sector", "Sectors": "Sectores", + "Sectors | Entities": "Sectores | Entidades", "Sectors and organizations": "Sectores y organizaciones", "Security": "Seguridad", "See all entities created by user": "Ver todas las entidades creadas por el usuario", diff --git a/opencti-platform/opencti-front/lang/front/fr.json b/opencti-platform/opencti-front/lang/front/fr.json index ea7a0e50058e..139733390782 100644 --- a/opencti-platform/opencti-front/lang/front/fr.json +++ b/opencti-platform/opencti-front/lang/front/fr.json @@ -594,6 +594,7 @@ "Data import and analyst workbenches": "Importation de données et postes de travail des analystes", "Data sharing": "Partage de données", "Data sharing configuration": "Configuration du partage des données", + "Data Sharing: Live Streams | Data": "Partage de données : flux en direct | Données", "Data source": "Source de données", "Data sources": "Sources de données", "Data type": "Type de données", @@ -1315,6 +1316,7 @@ "Import knowledge": "Importer des connaissances", "Import successfully asked": "L'import a été demandé", "Import type": "Type d'import", + "Import: Import | Data": "Importation : Import | Data", "Important notice: your action is required!": "Avis important : votre action est requise !", "Imports": "Importations", "In all the database": "Dans toute la base", @@ -1385,6 +1387,7 @@ "Ingested entities": "Entités stockées", "Ingestion": "Ingestion", "INGESTION_MANAGER": "Manager d'ingestion", + "Ingestion: Connectors | Data": "Ingestion : Connecteurs | Données", "Initial score": "Score initial", "Initiator": "Initiateur", "innovator": "innovateur", @@ -2324,6 +2327,7 @@ "Secondary motivations": "Motivations secondaires", "Sector": "Secteur", "Sectors": "Secteurs", + "Sectors | Entities": "Secteurs | Entités", "Sectors and organizations": "Secteurs et organisations", "Security": "Sécurité", "See all entities created by user": "Voir toutes les entités crées par l'utilisateur", diff --git a/opencti-platform/opencti-front/lang/front/ja.json b/opencti-platform/opencti-front/lang/front/ja.json index 7b13e7235677..c0173652a939 100644 --- a/opencti-platform/opencti-front/lang/front/ja.json +++ b/opencti-platform/opencti-front/lang/front/ja.json @@ -594,6 +594,7 @@ "Data import and analyst workbenches": "データインポートとアナリストワークベンチ", "Data sharing": "データ共有", "Data sharing configuration": "データ共有設定", + "Data Sharing: Live Streams | Data": "データ共有:ライブストリーム|データ", "Data source": "データソース", "Data sources": "データソース", "Data type": "データ型", @@ -1315,6 +1316,7 @@ "Import knowledge": "知識をインポート", "Import successfully asked": "インポート要求を正常に受け付けました", "Import type": "インポート種別", + "Import: Import | Data": "インポート:インポート|データ", "Important notice: your action is required!": "重要なお知らせ:あなたのアクションが必要です!", "Imports": "インポート", "In all the database": "すべてのデータベースで", @@ -1385,6 +1387,7 @@ "Ingested entities": "取り込まれたエンティティ", "Ingestion": "摂取", "INGESTION_MANAGER": "インジェストマネージャー", + "Ingestion: Connectors | Data": "摂取:コネクタ|データ", "Initial score": "初期スコア", "Initiator": "イニシエータ", "innovator": "革新的", @@ -2324,6 +2327,7 @@ "Secondary motivations": "二次的動機", "Sector": "セクター", "Sectors": "セクター", + "Sectors | Entities": "エンティティ", "Sectors and organizations": "セクターと組織", "Security": "安全", "See all entities created by user": "ユーザーが作成したすべてのエンティティを見る", diff --git a/opencti-platform/opencti-front/lang/front/ko.json b/opencti-platform/opencti-front/lang/front/ko.json index 4623f77e88aa..0b554a53eb14 100644 --- a/opencti-platform/opencti-front/lang/front/ko.json +++ b/opencti-platform/opencti-front/lang/front/ko.json @@ -594,6 +594,7 @@ "Data import and analyst workbenches": "데이터 가져오기 및 분석가 작업대", "Data sharing": "데이터 공유", "Data sharing configuration": "데이터 공유 구성", + "Data Sharing: Live Streams | Data": "데이터 공유: 실시간 스트리밍 | 데이터", "Data source": "데이터 소스", "Data sources": "데이터 소스", "Data type": "데이터 유형", @@ -1315,6 +1316,7 @@ "Import knowledge": "지식 가져오기", "Import successfully asked": "가져오기가 성공적으로 요청됨", "Import type": "가져오기 유형", + "Import: Import | Data": "가져오기: 가져오기 | 데이터", "Important notice: your action is required!": "중요 공지: 귀하의 조치가 필요합니다!", "Imports": "Imports", "In all the database": "모든 데이터베이스에서", @@ -1385,6 +1387,7 @@ "Ingested entities": "수집된 엔터티", "Ingestion": "수집", "INGESTION_MANAGER": "수집 관리자", + "Ingestion: Connectors | Data": "섭취: 커넥터 | 데이터", "Initial score": "초기 점수", "Initiator": "발기인", "innovator": "혁신가", @@ -2324,6 +2327,7 @@ "Secondary motivations": "부차적 동기", "Sector": "부문", "Sectors": "부문", + "Sectors | Entities": "분야 | 법인", "Sectors and organizations": "부문 및 조직", "Security": "보안", "See all entities created by user": "사용자가 만든 모든 엔터티 보기", diff --git a/opencti-platform/opencti-front/lang/front/zh.json b/opencti-platform/opencti-front/lang/front/zh.json index a1ccce25ac00..82bbb3a7a202 100644 --- a/opencti-platform/opencti-front/lang/front/zh.json +++ b/opencti-platform/opencti-front/lang/front/zh.json @@ -594,6 +594,7 @@ "Data import and analyst workbenches": "数据导入和分析工作台", "Data sharing": "数据共享", "Data sharing configuration": "数据共享配置", + "Data Sharing: Live Streams | Data": "数据共享:实时流|数据", "Data source": "数据源", "Data sources": "数据源", "Data type": "数据类型", @@ -1315,6 +1316,7 @@ "Import knowledge": "导入知识", "Import successfully asked": "请求导入成功", "Import type": "导入类型", + "Import: Import | Data": "导入:导入|数据", "Important notice: your action is required!": "重要提示:需要您执行操作!", "Imports": "导入", "In all the database": "在所有数据库中", @@ -1385,6 +1387,7 @@ "Ingested entities": "引入的实体", "Ingestion": "接入", "INGESTION_MANAGER": "提取管理器", + "Ingestion: Connectors | Data": "摄取:连接器|数据", "Initial score": "初始分数", "Initiator": "发起者", "innovator": "创新者", @@ -2324,6 +2327,7 @@ "Secondary motivations": "次要动机", "Sector": "部门", "Sectors": "部门", + "Sectors | Entities": "部门|实体", "Sectors and organizations": "部门和组织", "Security": "安全", "See all entities created by user": "查看用户创建的所有实体", diff --git a/opencti-platform/opencti-front/src/private/components/data/Connectors.jsx b/opencti-platform/opencti-front/src/private/components/data/Connectors.jsx index efc67d0a20b8..a666299ff2b7 100644 --- a/opencti-platform/opencti-front/src/private/components/data/Connectors.jsx +++ b/opencti-platform/opencti-front/src/private/components/data/Connectors.jsx @@ -1,58 +1,50 @@ -import React, { Component } from 'react'; -import * as PropTypes from 'prop-types'; -import { compose } from 'ramda'; -import withStyles from '@mui/styles/withStyles'; +import React from 'react'; +import makeStyles from '@mui/styles/makeStyles'; import IngestionMenu from './IngestionMenu'; -import inject18n from '../../../components/i18n'; +import { useFormatter } from '../../../components/i18n'; import { QueryRenderer } from '../../../relay/environment'; import WorkersStatus, { workersStatusQuery } from './connectors/WorkersStatus'; import ConnectorsStatus, { connectorsStatusQuery } from './connectors/ConnectorsStatus'; -import Loader from '../../../components/Loader'; +import Loader, { LoaderVariant } from '../../../components/Loader'; import Breadcrumbs from '../../../components/Breadcrumbs'; +import useConnectedDocumentModifier from '../../../utils/hooks/useConnectedDocumentModifier'; -const styles = () => ({ +const useStyles = makeStyles(() => ({ container: { + margin: 0, padding: '0 200px 50px 0', }, -}); +})); -class Connectors extends Component { - render() { - const { t, classes } = this.props; - return ( -
- - - { - if (props) { - return ; - } - return
 
; - }} - /> - { - if (props) { - return ; - } - return ; - }} - /> -
- ); - } -} - -Connectors.propTypes = { - classes: PropTypes.object, - t: PropTypes.func, - connectorsStatus: PropTypes.array, +const Connectors = () => { + const classes = useStyles(); + const { t_i18n } = useFormatter(); + const { setTitle } = useConnectedDocumentModifier(); + setTitle(t_i18n('Ingestion: Connectors | Data')); + return ( +
+ + + { + if (props) { + return ; + } + return ; + }} + /> + { + if (props) { + return ; + } + return ; + }} + /> +
+ ); }; -export default compose( - inject18n, - withStyles(styles, { withTheme: true }), -)(Connectors); +export default Connectors; diff --git a/opencti-platform/opencti-front/src/private/components/data/Stream.jsx b/opencti-platform/opencti-front/src/private/components/data/Stream.jsx deleted file mode 100644 index fbf42638c736..000000000000 --- a/opencti-platform/opencti-front/src/private/components/data/Stream.jsx +++ /dev/null @@ -1,152 +0,0 @@ -import React, { Component } from 'react'; -import * as PropTypes from 'prop-types'; -import { compose, propOr } from 'ramda'; -import withTheme from '@mui/styles/withTheme'; -import withStyles from '@mui/styles/withStyles'; -import { QueryRenderer } from '../../../relay/environment'; -import { buildViewParamsFromUrlAndStorage, saveViewParameters } from '../../../utils/ListParameters'; -import inject18n from '../../../components/i18n'; -import ListLines from '../../../components/list_lines/ListLines'; -import StreamLines, { StreamLinesQuery } from './stream/StreamLines'; -import StreamCollectionCreation from './stream/StreamCollectionCreation'; -import SharingMenu from './SharingMenu'; -import withRouter from '../../../utils/compat_router/withRouter'; -import Breadcrumbs from '../../../components/Breadcrumbs'; -import { TAXIIAPI_SETCOLLECTIONS } from '../../../utils/hooks/useGranted'; -import Security from '../../../utils/Security'; - -const styles = () => ({ - container: { - margin: 0, - padding: '0 200px 50px 0', - }, -}); - -const LOCAL_STORAGE_KEY = 'stream'; - -class Stream extends Component { - constructor(props) { - super(props); - const params = buildViewParamsFromUrlAndStorage( - props.navigate, - props.location, - LOCAL_STORAGE_KEY, - ); - this.state = { - sortBy: propOr('name', 'sortBy', params), - orderAsc: propOr(true, 'orderAsc', params), - searchTerm: propOr('', 'searchTerm', params), - view: propOr('lines', 'view', params), - }; - } - - saveView() { - saveViewParameters( - this.props.navigate, - this.props.location, - LOCAL_STORAGE_KEY, - this.state, - ); - } - - handleSearch(value) { - this.setState({ searchTerm: value }, () => this.saveView()); - } - - handleSort(field, orderAsc) { - this.setState({ sortBy: field, orderAsc }, () => this.saveView()); - } - - renderLines(paginationOptions) { - const { sortBy, orderAsc, searchTerm } = this.state; - const dataColumns = { - name: { - label: 'Name', - width: '10%', - isSortable: true, - }, - description: { - label: 'Description', - width: '20%', - isSortable: false, - }, - id: { - label: 'Stream ID', - width: '15%', - isSortable: true, - }, - stream_public: { - label: 'Public', - width: '8%', - isSortable: true, - }, - stream_live: { - label: 'Status', - width: '10%', - isSortable: true, - }, - filters: { - label: 'Filters', - width: '35%', - }, - }; - return ( - - ( - - )} - /> - - ); - } - - render() { - const { classes, t } = this.props; - const { view, sortBy, orderAsc, searchTerm } = this.state; - const paginationOptions = { - search: searchTerm, - orderBy: sortBy, - orderMode: orderAsc ? 'asc' : 'desc', - }; - return ( -
- - - {view === 'lines' ? this.renderLines(paginationOptions) : ''} - - - -
- ); - } -} - -Stream.propTypes = { - t: PropTypes.func, - navigate: PropTypes.func, - location: PropTypes.object, -}; - -export default compose( - inject18n, - withTheme, - withRouter, - withStyles(styles), -)(Stream); diff --git a/opencti-platform/opencti-front/src/private/components/data/Stream.tsx b/opencti-platform/opencti-front/src/private/components/data/Stream.tsx new file mode 100644 index 000000000000..ee9ce289ab96 --- /dev/null +++ b/opencti-platform/opencti-front/src/private/components/data/Stream.tsx @@ -0,0 +1,143 @@ +import React, { useEffect, useState } from 'react'; +import makeStyles from '@mui/styles/makeStyles'; +import { useLocation, useNavigate } from 'react-router-dom'; +import { buildViewParamsFromUrlAndStorage, saveViewParameters } from '../../../utils/ListParameters'; +import { QueryRenderer } from '../../../relay/environment'; +import { useFormatter } from '../../../components/i18n'; +import ListLines from '../../../components/list_lines/ListLines'; +import StreamLines, { StreamLinesQuery } from './stream/StreamLines'; +import StreamCollectionCreation from './stream/StreamCollectionCreation'; +import SharingMenu from './SharingMenu'; +import Breadcrumbs from '../../../components/Breadcrumbs'; +import { TAXIIAPI_SETCOLLECTIONS } from '../../../utils/hooks/useGranted'; +import Security from '../../../utils/Security'; +import useConnectedDocumentModifier from '../../../utils/hooks/useConnectedDocumentModifier'; +import { OrderMode, PaginationOptions } from '../../../components/list_lines'; +import { StreamLinesPaginationQuery$data } from './stream/__generated__/StreamLinesPaginationQuery.graphql'; + +const LOCAL_STORAGE_KEY = 'stream'; + +const useStyles = makeStyles(() => ({ + container: { + margin: 0, + padding: '0 200px 50px 0', + }, +})); + +const Stream = () => { + const { t_i18n } = useFormatter(); + const classes = useStyles(); + const { setTitle } = useConnectedDocumentModifier(); + setTitle(t_i18n('Data Sharing: Live Streams | Data')); + const navigate = useNavigate(); + const location = useLocation(); + const params = buildViewParamsFromUrlAndStorage( + navigate, + location, + LOCAL_STORAGE_KEY, + ); + const [streamState, setStreamState] = useState<{ orderAsc: boolean, searchTerm: string, view: string, sortBy: string }>({ + orderAsc: params.orderAsc !== false, + searchTerm: params.searchTerm ?? '', + view: params.view ?? 'lines', + sortBy: params.sortBy ?? 'name', + }); + + function saveView() { + saveViewParameters( + navigate, + location, + LOCAL_STORAGE_KEY, + streamState, + ); + } + + function handleSearch(value: string) { + setStreamState({ ...streamState, searchTerm: value }); + } + function handleSort(field: string, orderAsc: boolean) { + setStreamState({ ...streamState, sortBy: field, orderAsc }); + } + + useEffect(() => { + saveView(); + }, [streamState]); + + function renderLines(paginationOptions: PaginationOptions) { + const { searchTerm, sortBy, orderAsc, view } = streamState; + const dataColumns = { + name: { + label: 'Name', + width: '10%', + isSortable: true, + }, + description: { + label: 'Description', + width: '20%', + isSortable: false, + }, + id: { + label: 'Stream ID', + width: '15%', + isSortable: true, + }, + stream_public: { + label: 'Public', + width: '8%', + isSortable: true, + }, + stream_live: { + label: 'Status', + width: '10%', + isSortable: true, + }, + filters: { + label: 'Filters', + width: '35%', + }, + }; + return ( + + ( + + )} + /> + + ); + } + const paginationOptions: PaginationOptions = { + search: streamState.searchTerm, + orderBy: streamState.sortBy, + orderMode: streamState.orderAsc ? OrderMode.asc : OrderMode.desc, + }; + return ( +
+ + + {streamState.view === 'lines' ? renderLines(paginationOptions) : ''} + + + +
+ ); +}; + +export default Stream; diff --git a/opencti-platform/opencti-front/src/private/components/data/import/ImportContent.jsx b/opencti-platform/opencti-front/src/private/components/data/import/ImportContent.jsx index 8d6603a0b5c0..1b1bb9d72f2d 100644 --- a/opencti-platform/opencti-front/src/private/components/data/import/ImportContent.jsx +++ b/opencti-platform/opencti-front/src/private/components/data/import/ImportContent.jsx @@ -1,5 +1,4 @@ -import React, { Component } from 'react'; -import * as PropTypes from 'prop-types'; +import React, { useEffect, useState } from 'react'; import * as R from 'ramda'; import { createRefetchContainer, graphql } from 'react-relay'; import { interval } from 'rxjs'; @@ -8,11 +7,14 @@ import Typography from '@mui/material/Typography'; import List from '@mui/material/List'; import Paper from '@mui/material/Paper'; import Grid from '@mui/material/Grid'; -import { Add, ArrowDropDown, ArrowDropUp } from '@mui/icons-material'; +import { Add, ArrowDropDown, ArrowDropUp, Extension } from '@mui/icons-material'; import ListItemIcon from '@mui/material/ListItemIcon'; import ListItemText from '@mui/material/ListItemText'; import ListItem from '@mui/material/ListItem'; +import ListItemButton from '@mui/material/ListItemButton'; import ListItemSecondaryAction from '@mui/material/ListItemSecondaryAction'; +import Link from '@mui/material/Link'; +import Tooltip from '@mui/material/Tooltip'; import { Field, Form, Formik } from 'formik'; import Dialog from '@mui/material/Dialog'; import DialogTitle from '@mui/material/DialogTitle'; @@ -22,13 +24,14 @@ import DialogActions from '@mui/material/DialogActions'; import Button from '@mui/material/Button'; import * as Yup from 'yup'; import Fab from '@mui/material/Fab'; +import { makeStyles } from '@mui/styles'; import ImportMenu from '../ImportMenu'; import ObjectMarkingField from '../../common/form/ObjectMarkingField'; import SelectField from '../../../../components/fields/SelectField'; import { TEN_SECONDS } from '../../../../utils/Time'; import { fileManagerAskJobImportMutation, scopesConn } from '../../common/files/FileManager'; import FileLine from '../../common/files/FileLine'; -import inject18n from '../../../../components/i18n'; +import inject18n, { useFormatter } from '../../../../components/i18n'; import FileUploader from '../../common/files/FileUploader'; import { commitMutation, MESSAGING$ } from '../../../../relay/environment'; import WorkbenchFileLine from '../../common/files/workbench/WorkbenchFileLine'; @@ -39,17 +42,12 @@ import { fieldSpacingContainerStyle } from '../../../../utils/field'; import withRouter from '../../../../utils/compat_router/withRouter'; import Breadcrumbs from '../../../../components/Breadcrumbs'; import { resolveHasUserChoiceParsedCsvMapper } from '../../../../utils/csvMapperUtils'; +import useConnectedDocumentModifier from '../../../../utils/hooks/useConnectedDocumentModifier'; +import { truncate } from '../../../../utils/String'; const interval$ = interval(TEN_SECONDS); -const styles = (theme) => ({ - container: { - margin: 0, - }, - title: { - float: 'left', - textTransform: 'uppercase', - }, +const useStyles = makeStyles(() => ({ gridContainer: { marginBottom: 20, }, @@ -62,32 +60,15 @@ const styles = (theme) => ({ paddingLeft: 10, height: 50, }, - buttons: { - marginTop: 20, - textAlign: 'right', - }, - button: { - marginLeft: theme.spacing(2), - }, - linesContainer: { - marginTop: 10, - }, itemHead: { paddingLeft: 10, textTransform: 'uppercase', }, - bodyItem: { - height: '100%', - fontSize: 13, - }, - itemIcon: { - color: theme.palette.primary.main, - }, createButton: { position: 'fixed', bottom: 30, }, -}); +})); const inlineStylesHeaders = { iconSort: { @@ -183,71 +164,72 @@ const importValidation = (t, configurations) => { return Yup.object().shape(shape); }; -class ImportContentComponent extends Component { - constructor(props) { - super(props); - this.state = { - fileToImport: null, - fileToValidate: null, - displayCreate: false, - sortBy: 'name', - orderAsc: true, - selectedConnector: null, - hasUserChoiceCsvMapper: false, - }; - } +const ImportContentComponent = (props) => { + const classes = useStyles(); + const { t_i18n } = useFormatter(); + const { setTitle } = useConnectedDocumentModifier(); + setTitle(t_i18n('Import: Import | Data')); + const [selectedConnector, setSelectedConnector] = useState(null); + const { importContent, connectorsImport, relay, nsdt, importFiles, pendingFiles } = props; + const [sortBy, setSortBy] = useState(); + const [fileToImport, setFileToImport] = useState(null); + const [fileToValidate, setFileToValidate] = useState(null); + const [displayCreate, setDisplayCreate] = useState(false); + const [orderAsc, setOrderAsc] = useState(false); + const [hasUserChoiceCsvMapper, sethasUserChoiceCsvMapper] = useState(false); - componentDidMount() { - this.subscription = interval$.subscribe(() => { - this.props.relay.refetch(); + useEffect(() => { + const subscription = interval$.subscribe(() => { + relay.refetch({ + id: importContent.id, + entityType: importContent.entity_type, + }); }); - } - - componentWillUnmount() { - this.subscription.unsubscribe(); - } + return () => { + subscription.unsubscribe(); + }; + }); - handleSetCsvMapper(_, csvMapper) { + const handleSetCsvMapper = (_, csvMapper) => { const parsedCsvMapper = JSON.parse(csvMapper); const parsedRepresentations = JSON.parse(parsedCsvMapper.representations); const selectedCsvMapper = { ...parsedCsvMapper, representations: [...parsedRepresentations], }; - this.setState({ hasUserChoiceCsvMapper: resolveHasUserChoiceParsedCsvMapper(selectedCsvMapper) }); - } - handleOpenImport(file) { - this.setState({ fileToImport: file }); - } + sethasUserChoiceCsvMapper(resolveHasUserChoiceParsedCsvMapper(selectedCsvMapper)); + }; - handleCloseImport() { - this.setState({ - fileToImport: null, - }); - } + const handleOpenImport = (file) => { + setFileToImport(file); + }; - handleOpenValidate(file) { - this.setState({ fileToValidate: file }); - } + const handleCloseImport = () => { + setFileToImport(null); + }; - handleCloseValidate() { - this.setState({ fileToValidate: null }); - } + const handleOpenValidate = (file) => { + setFileToValidate(file); + }; - handleOpenCreate() { - this.setState({ displayCreate: true }); - } + const handleCloseValidate = () => { + setFileToValidate(null); + }; - handleCloseCreate() { - this.setState({ displayCreate: false }); - } + const handleOpenCreate = () => { + setDisplayCreate(true); + }; + + const handleCloseCreate = () => { + setDisplayCreate(false); + }; - onSubmitImport(values, { setSubmitting, resetForm }) { + const onSubmitImport = (values, { setSubmitting, resetForm }) => { const { connector_id, configuration, objectMarking } = values; let config = configuration; // Dynamically inject the markings chosen by the user into the csv mapper. - const isCsvConnector = this.state.selectedConnector?.name === 'ImportCsv'; + const isCsvConnector = selectedConnector?.name === 'ImportCsv'; if (isCsvConnector && configuration && objectMarking) { const parsedConfig = JSON.parse(configuration); if (typeof parsedConfig === 'object') { @@ -258,47 +240,49 @@ class ImportContentComponent extends Component { commitMutation({ mutation: fileManagerAskJobImportMutation, variables: { - fileName: this.state.fileToImport.id, + fileName: fileToImport.id, connectorId: connector_id, configuration: config, }, onCompleted: () => { setSubmitting(false); resetForm(); - this.handleCloseImport(); - MESSAGING$.notifySuccess(this.props.t('Import successfully asked')); + handleCloseImport(); + MESSAGING$.notifySuccess(t_i18n('Import successfully asked')); }, }); - } + }; - onSubmitValidate(values, { setSubmitting, resetForm }) { + const onSubmitValidate = (values, { setSubmitting, resetForm }) => { commitMutation({ mutation: fileManagerAskJobImportMutation, variables: { - fileName: this.state.fileToValidate.id, + fileName: fileToValidate.id, connectorId: values.connector_id, bypassValidation: true, }, onCompleted: () => { setSubmitting(false); resetForm(); - this.handleCloseValidate(); - MESSAGING$.notifySuccess(this.props.t('Import successfully asked')); + handleCloseValidate(); + MESSAGING$.notifySuccess(t_i18n('Import successfully asked')); }, }); - } + }; - onCreateWorkbenchCompleted() { - this.props.relay.refetch(); - } + const onCreateWorkbenchCompleted = () => { + relay.refetch(); + }; - reverseBy(field) { - this.setState({ sortBy: field, orderAsc: !this.state.orderAsc }); - } + const reverseBy = (field) => { + return ( + setSortBy(field), + setOrderAsc(!orderAsc) + ); + }; - SortHeader(field, label, isSortable) { - const { t } = this.props; - const sortComponent = this.state.orderAsc ? ( + const SortHeader = (field, label, isSortable) => { + const sortComponent = orderAsc ? ( ) : ( @@ -307,232 +291,287 @@ class ImportContentComponent extends Component { return (
- {t(label)} - {this.state.sortBy === field ? sortComponent : ''} + {t_i18n(label)} + {sortBy === field ? sortComponent : ''}
); } return (
- {t(label)} + {t_i18n(label)}
); - } - - render() { - const { - classes, - t, - importFiles, - pendingFiles, - connectorsImport, - relay, - isNewImportScreensEnabled, - } = this.props; - const { edges: importFilesEdges } = importFiles; - const { edges: pendingFilesEdges } = pendingFiles; - const { fileToImport, fileToValidate, displayCreate } = this.state; - const connectors = connectorsImport.filter((n) => !n.only_contextual); // Can be null but not empty - const importConnsPerFormat = scopesConn(connectors); - const handleSelectConnector = (_, value) => { - const selectedConnector = connectors.find((c) => c.id === value); - this.setState({ selectedConnector }); - }; - const invalidCsvMapper = this.state.selectedConnector?.name === 'ImportCsv' - && this.state.selectedConnector?.configurations?.length === 0; - return ( -
- - {isNewImportScreensEnabled && } - - -
- - {t('Uploaded files')} - -
- relay.refetch()} - size="medium" - /> - relay.refetch()} - size="medium" - /> -
-
- - {importFilesEdges.length ? ( - - {importFilesEdges.map((file) => file?.node && ( - !n.only_contextual); // Can be null but not empty + const importConnsPerFormat = scopesConn(connectors); + const handleSelectConnector = (_, value) => { + setSelectedConnector(connectors.find((c) => c.id === value)); + }; + const invalidCsvMapper = selectedConnector?.name === 'ImportCsv' + && selectedConnector?.configurations?.length === 0; + return ( +
+ + + + +
+ + {t_i18n('Uploaded files')} + +
+ relay.refetch()} + size="medium" + /> + relay.refetch()} + size="medium" + /> +
+
+ + {importFilesEdges.length ? ( + + {importFilesEdges.map((file) => file?.node && ( + - ))} - - ) : ( -
+ ))} + + ) : ( +
+ + {t_i18n('No file for the moment')} + +
+ )} + +
+ + + + {t_i18n('Enabled import connectors')} + + + {connectors.length ? ( + + {connectors.map((connector) => { + const connectorScope = connector.connector_scope.join(','); + return ( + + + + + + + + + + {connector.updated_at && ( + + )} + + ); + })} + + ) : ( +
+ + {t_i18n('No enrichment connectors on this platform')} + +
+ )} +
+
+ +
+ + {t_i18n('Analyst workbenches')} + + + + + - {t('No file for the moment')} - -
- )} -
-
- - -
- - {t('Analyst workbenches')} - - - - - -   - - - - {this.SortHeader('name', 'Name', false)} - {this.SortHeader('creator_name', 'Creator', false)} - {this.SortHeader('labels', 'Labels', false)} - {this.SortHeader( - 'lastModified', - 'Modification date', - false, - )} -
+ + + + {SortHeader('name', 'Name', false)} + {SortHeader('creator_name', 'Creator', false)} + {SortHeader('labels', 'Labels', false)} + {SortHeader( + 'lastModified', + 'Modification date', + false, + )} +
} - /> -   - - {pendingFilesEdges.map((file) => ( - +   + + {pendingFilesEdges.map((file) => ( + - ))} - - -
- + handleOpenImport={handleOpenValidate} + /> + ))} +
+
+
-
- - {({ submitForm, handleReset, isSubmitting, setFieldValue, isValid }) => ( -
- handleReset()} - fullWidth={true} - > - {`${t('Launch an import')}`} - - - {connectors.map((connector) => { - const disabled = !fileToImport + +
+ + {({ submitForm, handleReset, isSubmitting, setFieldValue, isValid }) => ( + + handleReset()} + fullWidth={true} + > + {`${t_i18n('Launch an import')}`} + + + {connectors.map((connector) => { + const disabled = !fileToImport || (connector.connector_scope.length > 0 && !R.includes( fileToImport.metaData.mimetype, connector.connector_scope, )); + return ( + + {connector.name} + + ); + })} + + {selectedConnector?.configurations?.length > 0 + ? + {selectedConnector.configurations?.map((config) => { return ( - {connector.name} + {config.name} ); })} - {this.state.selectedConnector?.configurations?.length > 0 - ? - {this.state.selectedConnector.configurations?.map((config) => { - return ( - - {config.name} - - ); - })} - - : + : } - {this.state.selectedConnector?.name === 'ImportCsv' - && this.state.hasUserChoiceCsvMapper + {selectedConnector?.name === 'ImportCsv' + && hasUserChoiceCsvMapper && ( <> ) } - - - - - - - - )} - - - {({ submitForm, handleReset, isSubmitting }) => ( -
- - {t('Validate and send for import')} - - - {connectors.map((connector, i) => { - const disabled = !fileToValidate + + + + + + +
+ )} +
+ + {({ submitForm, handleReset, isSubmitting }) => ( +
+ + {t_i18n('Validate and send for import')} + + + {connectors.map((connector, i) => { + const disabled = !fileToValidate || (connector.connector_scope.length > 0 && !R.includes( fileToValidate.metaData.mimetype, connector.connector_scope, )); - return ( - - {connector.name} - - ); - })} - - - - - - - -
- )} -
- -
- - - + return ( + + {connector.name} + + ); + })} +
+
+ + + + +
+ + )} +
+
- ); - } -} - -ImportContentComponent.propTypes = { - connectorsImport: PropTypes.array, - importFiles: PropTypes.object, - pendingFiles: PropTypes.object, - classes: PropTypes.object, - t: PropTypes.func, - nsdt: PropTypes.func, + + + +
+ ); }; const ImportContent = createRefetchContainer( @@ -658,4 +686,4 @@ const ImportContent = createRefetchContainer( importContentQuery, ); -export default R.compose(inject18n, withStyles(styles), withRouter)(ImportContent); +export default R.compose(inject18n, withStyles(useStyles), withRouter)(ImportContent); diff --git a/opencti-platform/opencti-front/src/private/components/entities/Sectors.jsx b/opencti-platform/opencti-front/src/private/components/entities/Sectors.jsx index e78ac4e40d8c..696b359d4767 100644 --- a/opencti-platform/opencti-front/src/private/components/entities/Sectors.jsx +++ b/opencti-platform/opencti-front/src/private/components/entities/Sectors.jsx @@ -1,81 +1,82 @@ -import React, { Component } from 'react'; +import React, { useState, useEffect } from 'react'; import * as PropTypes from 'prop-types'; -import { compose, propOr } from 'ramda'; +import { useNavigate, useLocation } from 'react-router-dom'; import { QueryRenderer } from '../../../relay/environment'; import { buildViewParamsFromUrlAndStorage, saveViewParameters } from '../../../utils/ListParameters'; -import inject18n from '../../../components/i18n'; +import { useFormatter } from '../../../components/i18n'; import SectorsLines, { sectorsLinesQuery } from './sectors/SectorsLines'; import SectorCreation from './sectors/SectorCreation'; import SearchInput from '../../../components/SearchInput'; import Security from '../../../utils/Security'; import { KNOWLEDGE_KNUPDATE } from '../../../utils/hooks/useGranted'; -import withRouter from '../../../utils/compat_router/withRouter'; import Breadcrumbs from '../../../components/Breadcrumbs'; +import useConnectedDocumentModifier from '../../../utils/hooks/useConnectedDocumentModifier'; const LOCAL_STORAGE_KEY = 'sectors'; -class Sectors extends Component { - constructor(props) { - super(props); - const params = buildViewParamsFromUrlAndStorage( - props.navigate, - props.location, - LOCAL_STORAGE_KEY, - ); - this.state = { - searchTerm: propOr('', 'searchTerm', params), - openExports: false, - }; - } +const Sectors = () => { + const { t_i18n } = useFormatter(); + const { setTitle } = useConnectedDocumentModifier(); + setTitle(t_i18n('Sectors | Entities')); + const navigate = useNavigate(); + const location = useLocation(); + const params = buildViewParamsFromUrlAndStorage( + navigate, + location, + LOCAL_STORAGE_KEY, + ); - saveView() { + const [sectorsState, setSectorsState] = useState({ + searchTerm: params.searchTerm ?? '', + openExports: false, + }); + + const saveView = () => { saveViewParameters( - this.props.navigate, - this.props.location, + navigate, + location, LOCAL_STORAGE_KEY, - this.state, + sectorsState, ); - } + }; - handleSearch(value) { - this.setState({ searchTerm: value }, () => this.saveView()); - } + const handleSearch = (value) => { + setSectorsState({ ...sectorsState, + searchTerm: value, + }); + }; - handleToggleExports() { - this.setState({ openExports: !this.state.openExports }); - } + useEffect(() => { + saveView(); + }, [sectorsState]); - render() { - const { searchTerm } = this.state; - const { t } = this.props; - return ( - <> - -
- -
- - - -
-
-
- ( - - )} + return ( + <> + +
+ - - ); - } -} +
+ + + +
+
+
+ ( + + )} + /> + + ); +}; Sectors.propTypes = { t: PropTypes.func, @@ -83,4 +84,4 @@ Sectors.propTypes = { location: PropTypes.object, }; -export default compose(inject18n, withRouter)(Sectors); +export default Sectors;