From 621d98a3bae9324da0fab9b9fa4104ee33f248b5 Mon Sep 17 00:00:00 2001 From: "A. Jard" Date: Fri, 13 Sep 2024 21:30:56 +0200 Subject: [PATCH] [frontend] Align design and fix new data table (#7906) Co-authored-by: Laurent Bonnet Co-authored-by: Adrien Servel Co-authored-by: Valentin Bouzin Co-authored-by: Gwendoline Favre-Felix Co-authored-by: Cathia Archidoit Co-authored-by: Landry Trebon --- .../opencti-front/lang/front/de.json | 2 + .../opencti-front/lang/front/en.json | 2 + .../opencti-front/lang/front/es.json | 2 + .../opencti-front/lang/front/fr.json | 2 + .../opencti-front/lang/front/ja.json | 2 + .../opencti-front/lang/front/ko.json | 2 + .../opencti-front/lang/front/zh.json | 2 + .../src/components/Breadcrumbs.tsx | 6 +- .../components/CreateEntityControlledDial.tsx | 8 +- .../src/components/FieldOrEmpty.tsx | 2 +- .../src/components/FilterIconButton.tsx | 10 +- .../components/FilterIconButtonContainer.tsx | 2 +- .../src/components/ItemEntityType.tsx | 4 +- .../src/components/ItemMarkings.jsx | 6 +- .../src/components/ItemOpenVocab.tsx | 117 +----- .../src/components/ItemSeverity.tsx | 2 +- .../opencti-front/src/components/Loader.tsx | 3 +- .../dashboard/WidgetListCoreObjects.tsx | 14 +- .../src/components/dataGrid/DataTable.tsx | 30 +- .../components/dataGrid/DataTableFilters.tsx | 62 ++-- .../dataGrid/DataTablePagination.tsx | 199 +++++----- .../dataGrid/DataTableWithoutFragment.tsx | 1 + .../dataGrid/components/DataTableBody.tsx | 23 +- .../components/DataTableComponent.tsx | 36 +- .../dataGrid/components/DataTableHeader.tsx | 45 ++- .../dataGrid/components/DataTableHeaders.tsx | 25 +- .../dataGrid/components/DataTableLine.tsx | 112 +++--- .../src/components/dataGrid/dataTableTypes.ts | 11 +- .../components/dataGrid/dataTableUtils.tsx | 54 ++- .../src/components/list_cards/ListCards.jsx | 25 +- .../src/components/list_lines/ListLines.jsx | 1 + .../nestedMenu/NestedMenuButton.tsx | 299 +++++++++++++++ .../src/private/components/Search.tsx | 1 + .../private/components/analyses/Reports.tsx | 1 - .../private/components/arsenal/Malwares.tsx | 2 +- .../arsenal/channels/ChannelEdition.jsx | 2 +- .../components/arsenal/tools/ToolEdition.jsx | 2 +- .../vulnerabilities/VulnerabilityDetails.jsx | 29 +- .../vulnerabilities/VulnerabilityEdition.jsx | 2 +- .../cases/case_incidents/CaseIncident.tsx | 108 +++++- .../components/cases/case_rfis/CaseRfi.tsx | 115 +++++- .../components/cases/case_rfts/CaseRft.tsx | 113 +++++- .../components/cases/tasks/CaseTasksLine.tsx | 191 ++++++++++ .../components/cases/tasks/CaseTasksLines.tsx | 189 +++------- .../components/cases/tasks/TaskPopover.tsx | 8 +- .../ContainerAddStixCoreObjectsInLine.tsx | 105 ++++-- .../components/common/drawer/Drawer.tsx | 16 +- .../stix_core_objects/StixCoreObjectsList.jsx | 10 +- ...StixCoreRelationshipCreationFromEntity.tsx | 2 + .../StixDomainObjectEdition.jsx | 2 +- .../StixNestedRefRelationshipEdition.jsx | 2 +- .../components/data/DataTableToolBar.jsx | 9 +- .../entities/events/EventEdition.jsx | 2 +- .../individuals/IndividualEdition.jsx | 2 +- .../organizations/OrganizationEdition.jsx | 2 +- .../entities/sectors/SectorEdition.jsx | 2 +- .../entities/systems/SystemEdition.jsx | 2 +- .../events/incidents/IncidentDetails.tsx | 1 + .../observed_data/ObservedDataEdition.jsx | 2 +- .../StixSightingRelationship.tsx | 2 +- .../components/observations/Artifacts.tsx | 57 +-- .../components/observations/Indicators.tsx | 9 +- .../observations/StixCyberObservables.tsx | 271 ++++++-------- .../indicators/IndicatorEdition.jsx | 2 +- .../indicators/IndicatorObservables.jsx | 60 +-- .../StixCyberObservableLine.jsx | 347 +++--------------- .../StixCyberObservablesLines.jsx | 195 +++------- .../private/components/settings/Labels.jsx | 2 +- .../settings/retention/RetentionPopover.jsx | 3 +- .../components/settings/roles/RolePopover.jsx | 3 +- .../components/techniques/Narratives.tsx | 15 +- .../attack_patterns/AttackPatternEdition.jsx | 2 +- .../CourseOfActionEdition.jsx | 2 +- .../narratives/NarrativeEdition.jsx | 2 +- .../private/components/threats/Campaigns.tsx | 4 +- .../components/threats/IntrusionSets.tsx | 4 +- .../components/threats/ThreatActorsGroup.tsx | 4 +- .../threats/ThreatActorsIndividual.tsx | 2 +- .../threats/campaigns/CampaignEdition.jsx | 2 +- .../intrusion_sets/IntrusionSetEdition.jsx | 2 +- .../ThreatActorGroupEdition.jsx | 2 +- .../ThreatActorGroupLocation.jsx | 96 +++-- .../AddIndividualsThreatActorIndividual.tsx | 24 +- .../AddPersonasThreatActorIndividual.tsx | 25 +- .../ThreatActorIndividualDetailsChips.tsx | 73 ++-- .../ThreatActorIndividualLocation.jsx | 2 +- .../components/workspaces/Workspaces.tsx | 10 - .../public_dashboards/PublicDashboards.tsx | 10 - .../PublicStixCoreObjectsList.tsx | 12 +- .../src/utils/filters/filtersUtils.tsx | 2 +- .../opencti-front/src/utils/utils.ts | 10 + .../model/form/reportForm.pageModel.ts | 2 +- 92 files changed, 1792 insertions(+), 1499 deletions(-) create mode 100644 opencti-platform/opencti-front/src/components/nestedMenu/NestedMenuButton.tsx create mode 100644 opencti-platform/opencti-front/src/private/components/cases/tasks/CaseTasksLine.tsx diff --git a/opencti-platform/opencti-front/lang/front/de.json b/opencti-platform/opencti-front/lang/front/de.json index cef37fecda8b..75f5af678401 100644 --- a/opencti-platform/opencti-front/lang/front/de.json +++ b/opencti-platform/opencti-front/lang/front/de.json @@ -407,6 +407,7 @@ "Copy disabled: too many selected elements (maximum number of elements for a copy: ": "Kopieren deaktiviert: zu viele ausgewählte Elemente (maximale Anzahl von Elementen für eine Kopie:", "Copy link": "Link kopieren", "Copy public link": "Öffentlichen Link kopieren", + "Copy to clipboard": "In die Zwischenablage kopieren", "Copy uri to clipboard for your csv client": "Kopiere uri in die Zwischenablage für deinen csv Client", "Copy uri to clipboard for your Taxii client": "Kopiere uri in die Zwischenablage für deinen Taxii-Client", "Copy/paste text content": "Kopieren/Einfügen von Textinhalten", @@ -2284,6 +2285,7 @@ "roles": "rollen", "Rolling time": "Rollierende Zeit", "Rolling time (in minutes)": "Rollende Zeit (in Minuten)", + "Rows per page": "Zeilen pro Seite", "Rows per page:": "Zeilen pro Seite:", "RSS Feed URL": "RSS-Feed-URL", "RSS feed URL": "RSS-Feed-URL", diff --git a/opencti-platform/opencti-front/lang/front/en.json b/opencti-platform/opencti-front/lang/front/en.json index a39c1c87aea1..a48713d70ad1 100644 --- a/opencti-platform/opencti-front/lang/front/en.json +++ b/opencti-platform/opencti-front/lang/front/en.json @@ -407,6 +407,7 @@ "Copy disabled: too many selected elements (maximum number of elements for a copy: ": "Copy disabled: too many selected elements (maximum number of elements for a copy: ", "Copy link": "Copy link", "Copy public link": "Copy public link", + "Copy to clipboard": "Copy to clipboard", "Copy uri to clipboard for your csv client": "Copy uri to clipboard for your csv client", "Copy uri to clipboard for your Taxii client": "Copy uri to clipboard for your Taxii client", "Copy/paste text content": "Copy/paste text content", @@ -2284,6 +2285,7 @@ "roles": "roles", "Rolling time": "Rolling time", "Rolling time (in minutes)": "Rolling time (in minutes)", + "Rows per page": "Rows per page", "Rows per page:": "Rows per page:", "RSS Feed URL": "RSS Feed URL", "RSS feed URL": "RSS feed URL", diff --git a/opencti-platform/opencti-front/lang/front/es.json b/opencti-platform/opencti-front/lang/front/es.json index d0781f55e9dd..906b9a17f8c1 100644 --- a/opencti-platform/opencti-front/lang/front/es.json +++ b/opencti-platform/opencti-front/lang/front/es.json @@ -407,6 +407,7 @@ "Copy disabled: too many selected elements (maximum number of elements for a copy: ": "Copia deshabilitada: demasiados elementos seleccionados (número máximo de elementos para una copia: ", "Copy link": "Copiar enlace", "Copy public link": "Copiar enlace público", + "Copy to clipboard": "Copiar al portapapeles", "Copy uri to clipboard for your csv client": "Copiar uri al portapapeles para su cliente csv", "Copy uri to clipboard for your Taxii client": "Copiar uri al portapapeles para su cliente Taxii", "Copy/paste text content": "Copiar o pegar contenido textual", @@ -2284,6 +2285,7 @@ "roles": "Roles", "Rolling time": "Intervalo de consulta", "Rolling time (in minutes)": "Intervalo de consulta (en minutos)", + "Rows per page": "Filas por página", "Rows per page:": "Filas por página:", "RSS Feed URL": "URL RSS", "RSS feed URL": "URL del canal RSS", diff --git a/opencti-platform/opencti-front/lang/front/fr.json b/opencti-platform/opencti-front/lang/front/fr.json index 6341c22d2b47..a2ee4ff5803c 100644 --- a/opencti-platform/opencti-front/lang/front/fr.json +++ b/opencti-platform/opencti-front/lang/front/fr.json @@ -407,6 +407,7 @@ "Copy disabled: too many selected elements (maximum number of elements for a copy: ": "Copie désactivée : trop d’éléments sélectionnés (nombre maximum d'éléments pour une copie: ", "Copy link": "Copier le lien", "Copy public link": "Copier le lien public", + "Copy to clipboard": "Copier dans le presse-papiers", "Copy uri to clipboard for your csv client": "Copier l'uri dans le presse-papier pour votre client csv", "Copy uri to clipboard for your Taxii client": "Copier l'uri dans le presse-papier pour votre client Taxii", "Copy/paste text content": "Copier/coller du contenu textuel", @@ -2284,6 +2285,7 @@ "roles": "Rôles", "Rolling time": "Intervalle de requête", "Rolling time (in minutes)": "Intervalle de requête (en minutes)", + "Rows per page": "Lignes par page", "Rows per page:": "Lignes par page :", "RSS Feed URL": "URL du flux RSS", "RSS feed URL": "URL du flux RSS", diff --git a/opencti-platform/opencti-front/lang/front/ja.json b/opencti-platform/opencti-front/lang/front/ja.json index 4aae1ae8ff7b..985a242811f1 100644 --- a/opencti-platform/opencti-front/lang/front/ja.json +++ b/opencti-platform/opencti-front/lang/front/ja.json @@ -407,6 +407,7 @@ "Copy disabled: too many selected elements (maximum number of elements for a copy: ": "コピー無効:選択した要素が多すぎます(コピーの要素の最大数:", "Copy link": "リンクをコピー", "Copy public link": "公開リンクをコピー", + "Copy to clipboard": "クリップボードにコピー", "Copy uri to clipboard for your csv client": "csvクライアント用にuriをクリップボードにコピーする", "Copy uri to clipboard for your Taxii client": "TaxiiクライアントのURIをクリップボードにコピーする", "Copy/paste text content": "コンテンツのコピー/ペースト", @@ -2284,6 +2285,7 @@ "roles": "ロール", "Rolling time": "問い合わせ間隔", "Rolling time (in minutes)": "問い合わせ間隔(分)", + "Rows per page": "ページあたりの行数", "Rows per page:": "ページあたりの行数", "RSS Feed URL": "RSSフィードURL", "RSS feed URL": "RSSフィードURL", diff --git a/opencti-platform/opencti-front/lang/front/ko.json b/opencti-platform/opencti-front/lang/front/ko.json index 424b98b084f0..805513ea86d9 100644 --- a/opencti-platform/opencti-front/lang/front/ko.json +++ b/opencti-platform/opencti-front/lang/front/ko.json @@ -407,6 +407,7 @@ "Copy disabled: too many selected elements (maximum number of elements for a copy: ": "복사 불가: 선택된 요소가 너무 많습니다 (복사 가능한 최대 요소 수: ", "Copy link": "링크 복사", "Copy public link": "공개 링크 복사", + "Copy to clipboard": "클립보드에 복사", "Copy uri to clipboard for your csv client": "CSV 클라이언트를 위한 URI를 클립보드에 복사", "Copy uri to clipboard for your Taxii client": "Taxii 클라이언트를 위한 URI를 클립보드에 복사", "Copy/paste text content": "텍스트 내용 복사/붙여넣기", @@ -2284,6 +2285,7 @@ "roles": "역할", "Rolling time": "롤링 시간", "Rolling time (in minutes)": "롤링 시간 (분)", + "Rows per page": "페이지당 행 수", "Rows per page:": "페이지당 행 수", "RSS Feed URL": "RSS 피드 URL", "RSS feed URL": "RSS 피드 URL", diff --git a/opencti-platform/opencti-front/lang/front/zh.json b/opencti-platform/opencti-front/lang/front/zh.json index 9d794061d8e4..cc29fd3bb521 100644 --- a/opencti-platform/opencti-front/lang/front/zh.json +++ b/opencti-platform/opencti-front/lang/front/zh.json @@ -407,6 +407,7 @@ "Copy disabled: too many selected elements (maximum number of elements for a copy: ": "复制已关闭:选取元素过多(复制的最大元素数:", "Copy link": "复制链接", "Copy public link": "复制公共链接", + "Copy to clipboard": "复制到剪贴板", "Copy uri to clipboard for your csv client": "将 uri 复制到剪贴板,供 csv 客户端使用", "Copy uri to clipboard for your Taxii client": "为 Taxii 客户端复制 uri 到剪贴板", "Copy/paste text content": "复制/粘贴文本内容", @@ -2284,6 +2285,7 @@ "roles": "角色", "Rolling time": "查询间隔", "Rolling time (in minutes)": "查询间隔(分钟)", + "Rows per page": "每页行数", "Rows per page:": "每页行数:", "RSS Feed URL": "RSS 源 URL", "RSS feed URL": "RSS 源 URL", diff --git a/opencti-platform/opencti-front/src/components/Breadcrumbs.tsx b/opencti-platform/opencti-front/src/components/Breadcrumbs.tsx index 1afe806b1843..ccae9173da64 100644 --- a/opencti-platform/opencti-front/src/components/Breadcrumbs.tsx +++ b/opencti-platform/opencti-front/src/components/Breadcrumbs.tsx @@ -3,6 +3,7 @@ import MUIBreadcrumbs from '@mui/material/Breadcrumbs'; import { Link } from 'react-router-dom'; import Typography from '@mui/material/Typography'; import makeStyles from '@mui/styles/makeStyles'; +import { Theme } from '@mui/material/styles/createTheme'; import { truncate } from '../utils/String'; interface element { @@ -18,10 +19,9 @@ interface BreadcrumbsProps { // Deprecated - https://mui.com/system/styles/basics/ // Do not use it for new code. -const useStyles = makeStyles(() => ({ +const useStyles = makeStyles((theme) => ({ breadcrumbsList: { - marginTop: -5, - marginBottom: 25, + marginBottom: theme.spacing(2), }, breadcrumbsObject: { marginTop: -5, diff --git a/opencti-platform/opencti-front/src/components/CreateEntityControlledDial.tsx b/opencti-platform/opencti-front/src/components/CreateEntityControlledDial.tsx index aef8b065d464..7599ba9662b3 100644 --- a/opencti-platform/opencti-front/src/components/CreateEntityControlledDial.tsx +++ b/opencti-platform/opencti-front/src/components/CreateEntityControlledDial.tsx @@ -1,5 +1,7 @@ import { Button } from '@mui/material'; import React, { FunctionComponent } from 'react'; +import { useTheme } from '@mui/styles'; +import { Theme } from '@mui/material/styles/createTheme'; import { DrawerControlledDialProps } from '../private/components/common/drawer/Drawer'; import { useFormatter } from './i18n'; @@ -19,6 +21,7 @@ const CreateEntityControlledDial: FunctionComponent { + const theme = useTheme(); const { t_i18n } = useFormatter(); const buttonValue = t_i18n('', { id: 'Create ...', @@ -32,10 +35,7 @@ const CreateEntityControlledDial: FunctionComponent
{buttonValue} diff --git a/opencti-platform/opencti-front/src/components/FieldOrEmpty.tsx b/opencti-platform/opencti-front/src/components/FieldOrEmpty.tsx index 632009c9481f..1dd899f96659 100644 --- a/opencti-platform/opencti-front/src/components/FieldOrEmpty.tsx +++ b/opencti-platform/opencti-front/src/components/FieldOrEmpty.tsx @@ -6,7 +6,7 @@ interface FieldOrEmptyProps { children: React.ReactNode; } -const FieldOrEmpty = ({ source, children }: FieldOrEmptyProps) => { +const FieldOrEmpty = ({ source, children }: FieldOrEmptyProps) => { return <>{isNotEmptyField(source) ? children : '-'}; // render the children if source is defined }; export default FieldOrEmpty; diff --git a/opencti-platform/opencti-front/src/components/FilterIconButton.tsx b/opencti-platform/opencti-front/src/components/FilterIconButton.tsx index a39ca5f67af7..bc1ab3295df7 100644 --- a/opencti-platform/opencti-front/src/components/FilterIconButton.tsx +++ b/opencti-platform/opencti-front/src/components/FilterIconButton.tsx @@ -120,10 +120,12 @@ const FilterIconButton: FunctionComponent = ({ const setHasRenderedRef = () => { hasRenderedRef.current = true; }; - const displayedFilters = filters ? { - ...filters, - filters: - filters.filters.filter((f) => !availableFilterKeys || availableFilterKeys?.some((k) => f.key === k)) } : undefined; + const displayedFilters = filters + ? { + ...filters, + filters: + filters.filters.filter((f) => !availableFilterKeys || availableFilterKeys?.some((k) => f.key === k)), + } : undefined; if (displayedFilters && isFilterGroupNotEmpty(displayedFilters)) { // to avoid running the FiltersRepresentatives query if filters are empty return ( = ({ const { t_i18n } = useFormatter(); const rootStyle = inList ? classes.chipInList : classes.chip; - const { isRelationship: checkIsRelationship } = useSchema(); - const isRelationship = checkIsRelationship(entityType); + const isRelationship = t_i18n(`relationship_${entityType}`) !== `relationship_${entityType}`; const { palette: { mode } } = useTheme(); const theme = mode === 'dark' diff --git a/opencti-platform/opencti-front/src/components/ItemMarkings.jsx b/opencti-platform/opencti-front/src/components/ItemMarkings.jsx index b222e5aa804a..62c164dac549 100644 --- a/opencti-platform/opencti-front/src/components/ItemMarkings.jsx +++ b/opencti-platform/opencti-front/src/components/ItemMarkings.jsx @@ -93,7 +93,7 @@ const inlineStylesLight = { const StyledBadge = styled(Badge)(() => ({ '& .MuiBadge-badge': { - right: 9, + right: 8, top: 4, }, })); @@ -280,11 +280,11 @@ const ItemMarkings = ({ variant, markingDefinitions, limit, onClick }) => { } placement="bottom" > - +
{R.take(limit, markings).map((markingDefinition) => renderChip(markingDefinition))} - +
); }; diff --git a/opencti-platform/opencti-front/src/components/ItemOpenVocab.tsx b/opencti-platform/opencti-front/src/components/ItemOpenVocab.tsx index 81af6ac4f3e8..36b10f27cbd6 100644 --- a/opencti-platform/opencti-front/src/components/ItemOpenVocab.tsx +++ b/opencti-platform/opencti-front/src/components/ItemOpenVocab.tsx @@ -1,15 +1,9 @@ import React, { FunctionComponent } from 'react'; import makeStyles from '@mui/styles/makeStyles'; import Chip from '@mui/material/Chip'; -import Tooltip from '@mui/material/Tooltip'; import { InformationOutline } from 'mdi-material-ui'; -import { graphql, usePreloadedQuery } from 'react-relay'; -import { PreloadedQuery } from 'react-relay/relay-hooks/EntryPointTypes'; import { useFormatter } from './i18n'; import type { Theme } from './Theme'; -import useVocabularyCategory from '../utils/hooks/useVocabularyCategory'; -import { ItemOpenVocabQuery } from './__generated__/ItemOpenVocabQuery.graphql'; -import useQueryLoading from '../utils/hooks/useQueryLoading'; import ItemSeverity from './ItemSeverity'; import ItemPriority from './ItemPriority'; @@ -60,131 +54,48 @@ interface ItemOpenVocabProps { small?: boolean; hideEmpty?: boolean; displayMode?: 'chip' | 'span'; - queryRef: PreloadedQuery; } -const itemOpenVocabQuery = graphql` - query ItemOpenVocabQuery($category: VocabularyCategory) { - vocabularies(category: $category) { - edges { - node { - name - description - } - } - } - } -`; - -const ItemOpenVocabDummy = ({ - small = true, - displayMode = 'span', -}: { - small?: boolean; - displayMode?: 'chip' | 'span'; -}) => { - const classes = useStyles(); - const { t_i18n } = useFormatter(); - if (displayMode === 'chip') { - return ( - - - - ); - } - return ( - -
-        {t_i18n('Unknown')}
-      
- - - -
- ); -}; -const ItemOpenVocabComponent: FunctionComponent = ({ +const ItemOpenVocab: FunctionComponent = ({ type, value, small = true, hideEmpty = true, displayMode = 'span', - queryRef, }) => { const classes = useStyles(); const { t_i18n } = useFormatter(); - const { vocabularies } = usePreloadedQuery( - itemOpenVocabQuery, - queryRef, - ); - let description = null; - if (value) { - const openVocabList = (vocabularies?.edges ?? []).map(({ node }) => node); - const openVocab = openVocabList.find((n) => n.name === value); - description = openVocab?.description ? openVocab.description : null; - } + if (displayMode === 'chip') { let chip = ( ); - if (type === 'case_severity_ov') { + if (type === 'case_severity_ov' || type === 'incident_severity_ov') { chip = ; } else if (type === 'case_priority_ov') { chip = ; } - return !description && hideEmpty ? ( + return hideEmpty ? ( chip ) : ( - - {chip} - + {chip} ); } + const preClass = small ? classes.smallPre : classes.pre; const iconClass = small ? classes.smallIcon : classes.icon; - const tooltip = ( - - - - ); + return (
{value || t_i18n('Unknown')}
- {!description && hideEmpty ? '' : tooltip} -
- ); -}; - -const ItemOpenVocab: FunctionComponent> = ( - props, -) => { - const { typeToCategory } = useVocabularyCategory(); - const queryRef = useQueryLoading(itemOpenVocabQuery, { - category: typeToCategory(props.type), - }); - return ( - <> - {queryRef && ( - - } - > - - + {hideEmpty ? '' : ( + )} - +
); }; diff --git a/opencti-platform/opencti-front/src/components/ItemSeverity.tsx b/opencti-platform/opencti-front/src/components/ItemSeverity.tsx index 4ec178d5374b..7078293d6dc5 100644 --- a/opencti-platform/opencti-front/src/components/ItemSeverity.tsx +++ b/opencti-platform/opencti-front/src/components/ItemSeverity.tsx @@ -54,7 +54,7 @@ interface ItemSeverityProps { } const computeSeverityStyle = (severity: string | undefined | null) => { - switch (severity) { + switch (severity?.toLowerCase()) { case 'low': return inlineStyles.green; case 'medium': diff --git a/opencti-platform/opencti-front/src/components/Loader.tsx b/opencti-platform/opencti-front/src/components/Loader.tsx index 3bb357d12c7a..7e69794487d1 100644 --- a/opencti-platform/opencti-front/src/components/Loader.tsx +++ b/opencti-platform/opencti-front/src/components/Loader.tsx @@ -64,7 +64,8 @@ const Loader: FunctionComponent = ({ const { settings } = useContext(UserContext); - const hasFiligranLoader = theme && (isNotEmptyField(settings?.enterprise_edition) || !settings?.platform_whitemark); + // if you have EE and whitemark set, you can remove the loader + const hasFiligranLoader = theme && !(isNotEmptyField(settings?.enterprise_edition) && settings?.platform_whitemark); if (variant === 'inline') { return ( diff --git a/opencti-platform/opencti-front/src/components/dashboard/WidgetListCoreObjects.tsx b/opencti-platform/opencti-front/src/components/dashboard/WidgetListCoreObjects.tsx index 28b611642fba..d569b37f0b6b 100644 --- a/opencti-platform/opencti-front/src/components/dashboard/WidgetListCoreObjects.tsx +++ b/opencti-platform/opencti-front/src/components/dashboard/WidgetListCoreObjects.tsx @@ -1,20 +1,22 @@ -import React, { forwardRef, MutableRefObject } from 'react'; +import React from 'react'; import { v4 as uuid } from 'uuid'; import DataTableWithoutFragment from '../dataGrid/DataTableWithoutFragment'; -import { DataTableVariant } from '../dataGrid/dataTableTypes'; +import { DataTableProps, DataTableVariant } from '../dataGrid/dataTableTypes'; interface WidgetListCoreObjectsProps { // eslint-disable-next-line @typescript-eslint/no-explicit-any data: any[] dateAttribute: string publicWidget?: boolean + rootRef: DataTableProps['rootRef'] } -const WidgetListCoreObjects = forwardRef(({ +const WidgetListCoreObjects = ({ data, dateAttribute, publicWidget = false, -}, ref) => ( + rootRef, +}: WidgetListCoreObjectsProps) => ( )?.current} + rootRef={rootRef} /> -)); +); WidgetListCoreObjects.displayName = 'WidgetListCoreObjects'; diff --git a/opencti-platform/opencti-front/src/components/dataGrid/DataTable.tsx b/opencti-platform/opencti-front/src/components/dataGrid/DataTable.tsx index 0697f175b4fe..4f58dea6f14f 100644 --- a/opencti-platform/opencti-front/src/components/dataGrid/DataTable.tsx +++ b/opencti-platform/opencti-front/src/components/dataGrid/DataTable.tsx @@ -26,6 +26,7 @@ type OCTIDataTableProps = Pick & { lineFragment: GraphQLTaggedNode preloadedPaginationProps: UsePreloadedPaginationFragment, @@ -49,6 +51,7 @@ type OCTIDataTableProps = Pick { exportContext, entityTypes, toolbarFilters, + handleCopy, variant = DataTableVariant.default, additionalHeaderButtons, currentView, + hideSearch, hideFilters, taskScope, } = props; @@ -140,14 +145,21 @@ const DataTable = (props: OCTIDataTableProps) => {
- + {!hideSearch && ( + + )} {!hideFilters && ( { /> )}
- {!hideFilters ? ( + {!hideFilters && ( - ) : (
)} + )} )} dataTableToolBarComponent={( @@ -189,6 +202,7 @@ const DataTable = (props: OCTIDataTableProps) => { filters={toolbarFilters} handleClearSelectedElements={handleClearSelectedElements} taskScope={taskScope} + handleCopy={handleCopy} />
)} diff --git a/opencti-platform/opencti-front/src/components/dataGrid/DataTableFilters.tsx b/opencti-platform/opencti-front/src/components/dataGrid/DataTableFilters.tsx index b5ffffb0bc09..d73b8c10a5a5 100644 --- a/opencti-platform/opencti-front/src/components/dataGrid/DataTableFilters.tsx +++ b/opencti-platform/opencti-front/src/components/dataGrid/DataTableFilters.tsx @@ -1,6 +1,5 @@ import Filters from '@components/common/lists/Filters'; import React, { useState } from 'react'; -import makeStyles from '@mui/styles/makeStyles'; import Tooltip from '@mui/material/Tooltip'; import { FileDownloadOutlined, SettingsOutlined } from '@mui/icons-material'; import ToggleButton from '@mui/material/ToggleButton'; @@ -18,6 +17,8 @@ import MenuItem from '@mui/material/MenuItem'; import DialogActions from '@mui/material/DialogActions'; import Button from '@mui/material/Button'; import Dialog from '@mui/material/Dialog'; +import { useTheme } from '@mui/styles'; +import { Theme } from '@mui/material/styles/createTheme'; import FilterIconButton from '../FilterIconButton'; import { useFormatter } from '../i18n'; import { DataTableDisplayFiltersProps, DataTableFiltersProps, DataTableVariant } from './dataTableTypes'; @@ -29,42 +30,26 @@ import Security from '../../utils/Security'; import { KNOWLEDGE_KNGETEXPORT } from '../../utils/hooks/useGranted'; import { ExportContext } from '../../utils/ExportContextProvider'; import Transition from '../Transition'; - -// Deprecated - https://mui.com/system/styles/basics/ -// Do not use it for new code. -const useStyles = makeStyles(() => ({ - filterContainer: { - minHeight: 10, - }, - filterInliner: { - display: 'inline-flex', - justifyContent: 'space-between', - flex: 1, - }, - filterHeaderContainer: { - display: 'inline-grid', - gridAutoFlow: 'column', - marginLeft: 10, - gap: 10, - }, - viewsAligner: { - display: 'flex', - alignItems: 'center', - }, -})); +import DataTablePagination from './DataTablePagination'; +import { isFilterGroupNotEmpty } from '../../utils/filters/filtersUtils'; export const DataTableDisplayFilters = ({ availableFilterKeys, availableRelationFilterTypes, additionalFilterKeys, + availableEntityTypes, entityTypes, }: DataTableDisplayFiltersProps) => { - const classes = useStyles(); + const theme = useTheme(); const { storageKey, initialValues, variant } = useDataTableContext(); const { helpers, viewStorage: { filters } } = usePaginationLocalStorage(storageKey, initialValues, variant !== DataTableVariant.default); + if (!isFilterGroupNotEmpty(filters)) { + return null; + } + return ( -
+
{ + const theme = useTheme(); const { t_i18n } = useFormatter(); - const [openSettings, setOpenSettings] = useState(false); const { @@ -104,6 +90,8 @@ const DataTableFilters = ({ redirectionModeEnabled, variant, createButton, + page, + setPage, } = useDataTableContext(); const { helpers, @@ -112,8 +100,6 @@ const DataTableFilters = ({ const { selectedElements } = useEntityToggle(storageKey); - const classes = useStyles(); - const exportDisabled = !exportContext || (numberOfElements && ((Object.keys(selectedElements).length > export_max_size && numberOfElements.number > export_max_size) @@ -123,8 +109,13 @@ const DataTableFilters = ({ return ( {availableFilterKeys && availableFilterKeys.length > 0 && ( -
-
+
+
-
+
+ {(variant === DataTableVariant.default) && ( + + )} >, numberOfElements?: NumberOfElements, }) => { + const theme = useTheme(); const { t_i18n } = useFormatter(); const { @@ -59,113 +61,130 @@ const DataTablePagination = ({ } }, [page, pageSize]); - const [anchorEl, setAnchorEl] = useState(null); const [_, setLocalStorageColumns] = useDataTableLocalStorage(`${storageKey}_columns`, {}, true, true); + const resetTable = () => { + setLocalStorageColumns({}); + resetColumns(); + helpers.handleAddProperty('pageSize', '25'); + }; + const nestedMenuOptions = [ + { + value: 'menu-reset', + label: t_i18n('Reset table'), + onClick: () => resetTable(), + menuLevel: 0, + }, + { + value: 'menu-rows-per-page', + label: t_i18n('Rows per page'), + menuLevel: 0, + nestedOptions: [ + { + value: '10', + onClick: () => helpers.handleAddProperty('pageSize', '10'), + selected: pageSize === '10', + menuLevel: 1, + }, + { + value: '25', + onClick: () => helpers.handleAddProperty('pageSize', '25'), + selected: !pageSize || pageSize === '25', + menuLevel: 1, + }, + { + value: '50', + onClick: () => helpers.handleAddProperty('pageSize', '50'), + selected: pageSize === '50', + menuLevel: 1, + }, + { + value: '100', + onClick: () => helpers.handleAddProperty('pageSize', '100'), + selected: pageSize === '100', + menuLevel: 1, + }, + ], + }, + ]; + return ( -
-
- {t_i18n('Rows per page:')} - -
-
- + + + {`${numberOfElements.original}`}{' '} + {t_i18n('entitie(s)')} +
+ } > - - - - + + -
- setAnchorEl(null)}> - { - setLocalStorageColumns({}); - resetColumns(); - setAnchorEl(null); - }} - > - {t_i18n('Reset table')} - - -
+ + } + options={nestedMenuOptions} + menuLevels={2} + /> + ); }; diff --git a/opencti-platform/opencti-front/src/components/dataGrid/DataTableWithoutFragment.tsx b/opencti-platform/opencti-front/src/components/dataGrid/DataTableWithoutFragment.tsx index 9cd6f4275232..eed09484a253 100644 --- a/opencti-platform/opencti-front/src/components/dataGrid/DataTableWithoutFragment.tsx +++ b/opencti-platform/opencti-front/src/components/dataGrid/DataTableWithoutFragment.tsx @@ -14,6 +14,7 @@ type OCTIDataTableProps = Pick & { data: unknown, diff --git a/opencti-platform/opencti-front/src/components/dataGrid/components/DataTableBody.tsx b/opencti-platform/opencti-front/src/components/dataGrid/components/DataTableBody.tsx index 6e03267789db..05640f9912ee 100644 --- a/opencti-platform/opencti-front/src/components/dataGrid/components/DataTableBody.tsx +++ b/opencti-platform/opencti-front/src/components/dataGrid/components/DataTableBody.tsx @@ -9,6 +9,7 @@ import { ColumnSizeVars, DataTableBodyProps, DataTableLineProps, DataTableVarian import DataTableLine, { DataTableLinesDummy } from './DataTableLine'; import { SELECT_COLUMN_SIZE } from './DataTableHeader'; import { useDataTableToggle } from '../dataTableHooks'; +import { throttle } from '../../../utils/utils'; // Deprecated - https://mui.com/system/styles/basics/ // Do not use it for new code. @@ -47,6 +48,7 @@ const DataTableBody = ({ variant, useDataTable, resolvePath, + actions, } = useDataTableContext(); const { data: queryData, isLoading, loadMore, hasMore } = useDataTable(dataQueryArgs); @@ -66,6 +68,9 @@ const DataTableBody = ({ // TABLE HANDLING const [resize, setResize] = useState(false); + const resizeObserver = useRef(new ResizeObserver(throttle(() => { + setResize(true); + }, 200))); const [computeState, setComputeState] = useState(null); const containerRef = useRef(null); @@ -74,7 +79,7 @@ const DataTableBody = ({ const startsWithSelect = columns.at(0)?.id === 'select'; const endsWithNavigate = columns.at(-1)?.id === 'navigate'; - let storedSize = SELECT_COLUMN_SIZE; + let storedSize = (endsWithNavigate || actions) ? SELECT_COLUMN_SIZE : 0; if (startsWithSelect) { storedSize += SELECT_COLUMN_SIZE; } @@ -142,7 +147,7 @@ const DataTableBody = ({ colSizes['--table-height'] = rootRef.offsetHeight - 42; // SIZE OF CONTAINER - Nb Elements - Line Size } else { const rootSize = (document.getElementById('root')?.offsetHeight ?? 0) - settingsMessagesBannerHeight; - const filterRemoval = (hasFilterComponent && document.getElementById('filter-container')?.children.length) ? 260 : 220; + const filterRemoval = (hasFilterComponent && document.getElementById('filter-container')?.children.length) ? 230 : 200; const tabsRemoval = document.getElementById('tabs-container')?.children.length ? 50 : 0; colSizes['--table-height'] = rootSize - filterRemoval - tabsRemoval; } @@ -202,20 +207,20 @@ const DataTableBody = ({ } }, 200); + window.addEventListener('resize', handleResize); + window.addEventListener('storage', handleStorage); + if (rootRef) resizeObserver.current.observe(rootRef); let observer: MutationObserver | undefined; - if (hasFilterComponent) { - window.addEventListener('resize', handleResize); - window.addEventListener('storage', handleStorage); + const elementToObserve = document.getElementById('filter-container'); + if (elementToObserve) { observer = new MutationObserver(() => setResize(true)); - const elementToObserve = document.getElementById('filter-container'); - if (elementToObserve) { - observer.observe(elementToObserve, { childList: true }); - } + observer.observe(elementToObserve, { childList: true }); } return () => { window.removeEventListener('resize', handleResize); window.removeEventListener('storage', handleStorage); + resizeObserver.current.disconnect(); if (hasFilterComponent && observer) { observer.disconnect(); } diff --git a/opencti-platform/opencti-front/src/components/dataGrid/components/DataTableComponent.tsx b/opencti-platform/opencti-front/src/components/dataGrid/components/DataTableComponent.tsx index 1c63b3631c5f..7b75c5c29158 100644 --- a/opencti-platform/opencti-front/src/components/dataGrid/components/DataTableComponent.tsx +++ b/opencti-platform/opencti-front/src/components/dataGrid/components/DataTableComponent.tsx @@ -6,7 +6,6 @@ import { DataTableContext, defaultColumnsMap } from '../dataTableUtils'; import { DataTableColumn, DataTableColumns, DataTableContextProps, DataTableProps, DataTableVariant, LocalStorageColumns } from '../dataTableTypes'; import DataTableHeaders from './DataTableHeaders'; import { SELECT_COLUMN_SIZE } from './DataTableHeader'; -import DataTablePagination from '../DataTablePagination'; const DataTableComponent = ({ dataColumns, @@ -43,6 +42,7 @@ const DataTableComponent = ({ disableLineSelection, disableToolBar, disableSelectAll, + selectOnLineClick, onLineClick, }: DataTableProps) => { const localStorageColumns = useDataTableLocalStorage(`${storageKey}_columns`, {}, true)[0]; @@ -59,12 +59,16 @@ const DataTableComponent = ({ ...(currentColumn?.size ? { size: currentColumn?.size } : {}), }); }), - ...(actions ? [] : [{ id: 'navigate', visible: true } as DataTableColumn]), + // inject "navigate" action (chevron) if navigable and no specific actions defined + ...((disableNavigation || actions) ? [] : [{ id: 'navigate', visible: true } as DataTableColumn]), ]; const [columns, setColumns] = useState(columnsInitialState); - const clientWidth = document.getElementsByTagName('main')[0].clientWidth - 46; + // main tag only exists in the app, we fallback to root element for public dashboards + const mainElement = document.getElementsByTagName('main')[0]; + const rootElement = document.getElementById('root'); + const clientWidth = (mainElement ?? rootElement).clientWidth - 46; const temporaryColumnsSize: { [key: string]: number } = { '--header-select-size': SELECT_COLUMN_SIZE, @@ -76,7 +80,7 @@ const DataTableComponent = ({ }; columns.forEach((col) => { if (col.visible && col.percentWidth) { - const size = col.percentWidth * (clientWidth / 100); + const size = col.percentWidth * ((clientWidth - 2 * SELECT_COLUMN_SIZE) / 100) - 2; // 2 is spacing temporaryColumnsSize[`--header-${col.id}-size`] = size; temporaryColumnsSize[`--col-${col.id}-size`] = size; } @@ -84,7 +88,8 @@ const DataTableComponent = ({ // QUERY PART const [page, setPage] = useState(1); - const currentPageSize = pageSize ? Number.parseInt(pageSize, 10) : 25; + const defaultPageSize = variant === DataTableVariant.default ? 25 : Number.MAX_SAFE_INTEGER; + const currentPageSize = pageSize ? Number.parseInt(pageSize, 10) : defaultPageSize; const pageStart = useMemo(() => { return page ? (page - 1) * currentPageSize : 0; }, [page, currentPageSize]); @@ -120,7 +125,10 @@ const DataTableComponent = ({ disableNavigation, disableToolBar, disableSelectAll, + selectOnLineClick, onLineClick, + page, + setPage, } as DataTableContextProps} >
@@ -137,19 +145,7 @@ const DataTableComponent = ({
))}
-
- {(variant === DataTableVariant.default) && ( - - )} + <> @@ -159,7 +155,7 @@ const DataTableComponent = ({ orderAsc={orderAsc} dataTableToolBarComponent={dataTableToolBarComponent} /> - {} + {}
)} > @@ -178,7 +174,7 @@ const DataTableComponent = ({ dataTableHeaderRef={dataTableHeaderRef} /> -
+ ); }; diff --git a/opencti-platform/opencti-front/src/components/dataGrid/components/DataTableHeader.tsx b/opencti-platform/opencti-front/src/components/dataGrid/components/DataTableHeader.tsx index 469bc6da2f8d..548a4bf7f06f 100644 --- a/opencti-platform/opencti-front/src/components/dataGrid/components/DataTableHeader.tsx +++ b/opencti-platform/opencti-front/src/components/dataGrid/components/DataTableHeader.tsx @@ -1,11 +1,10 @@ import React, { FunctionComponent } from 'react'; -import { ArrowDropDown, ArrowDropUp } from '@mui/icons-material'; +import { ArrowDropDown, ArrowDropUp, MoreVert } from '@mui/icons-material'; import IconButton from '@mui/material/IconButton'; import SimpleDraggrable from 'react-draggable'; import makeStyles from '@mui/styles/makeStyles'; import { createStyles } from '@mui/styles'; import { Theme as MuiTheme } from '@mui/material/styles/createTheme'; -import { UnfoldMoreIcon } from 'filigran-icon'; import Tooltip from '@mui/material/Tooltip'; import { useDataTableContext } from '../dataTableUtils'; import { DataTableColumn, DataTableHeaderProps, DataTableVariant, LocalStorageColumns } from '../dataTableTypes'; @@ -20,45 +19,51 @@ const useStyles = makeStyles((theme) => c display: 'flex', width: ({ column }) => `calc(var(--header-${column?.id}-size) * 1px)`, fontWeight: 'bold', - justifyContent: 'center', + justifyContent: 'space-between', alignItems: 'center', - height: 40, '& .react-draggable-dragging': { - background: theme.palette.secondary.main, + backgroundColor: theme.palette.primary.main, }, '&:hover': { '& $draggable': { - background: theme.palette.secondary.main, + backgroundColor: theme.palette.primary.main, + }, + '& $icon': { + visibility: 'visible', }, }, }, headerAligner: { - paddingLeft: 8, - paddingRight: 8, + paddingLeft: theme.spacing(1), + paddingRight: theme.spacing(1), display: 'flex', alignItems: 'center', whiteSpace: 'nowrap', overflow: 'hidden', + flexGrow: 1, cursor: ({ column: { isSortable } }) => (isSortable ? 'pointer' : 'unset'), }, - aligner: { flexGrow: 1 }, draggable: { position: 'absolute', - top: 0, + top: '8px', right: 3, - height: '100%', - width: 4, + height: theme.spacing(4), + width: 10, + paddingLeft: 4, + paddingRight: 4, + backgroundClip: 'content-box', borderRadius: 2, cursor: 'col-resize', - userSelect: 'none', - touchAction: 'none', - zIndex: 999, + }, + icon: { + visibility: 'hidden', }, })); const DataTableHeader: FunctionComponent = ({ column, setAnchorEl, + isActive, setActiveColumn, setLocalStorageColumns, containerRef, @@ -94,7 +99,9 @@ const DataTableHeader: FunctionComponent = ({ } }} > - {t_i18n(column.label)} + + {t_i18n(column.label).toUpperCase()} + {sortBy && (orderAsc ? : )}
<> @@ -102,10 +109,14 @@ const DataTableHeader: FunctionComponent = ({ <> { setActiveColumn(column); setAnchorEl(e.currentTarget); }} + style={{ + visibility: isActive ? 'visible' : undefined, + }} sx={{ marginRight: 1, opacity: 0.5, @@ -115,7 +126,7 @@ const DataTableHeader: FunctionComponent = ({ }, }} > - + )} diff --git a/opencti-platform/opencti-front/src/components/dataGrid/components/DataTableHeaders.tsx b/opencti-platform/opencti-front/src/components/dataGrid/components/DataTableHeaders.tsx index 14cb69db07b7..195f0e39d562 100644 --- a/opencti-platform/opencti-front/src/components/dataGrid/components/DataTableHeaders.tsx +++ b/opencti-platform/opencti-front/src/components/dataGrid/components/DataTableHeaders.tsx @@ -1,5 +1,4 @@ import React, { FunctionComponent, useMemo, useState } from 'react'; -import makeStyles from '@mui/styles/makeStyles'; import Checkbox from '@mui/material/Checkbox'; import { DragIndicatorOutlined } from '@mui/icons-material'; import Menu from '@mui/material/Menu'; @@ -12,16 +11,6 @@ import DataTableHeader from './DataTableHeader'; import { useDataTableContext } from '../dataTableUtils'; import type { Theme } from '../../Theme'; -// Deprecated - https://mui.com/system/styles/basics/ -// Do not use it for new code. -const useStyles = makeStyles({ - headersContainer: { - display: 'flex', - width: 'calc(var(--header-table-size) * 1px)', - }, - aligner: { flexGrow: 1 }, -}); - const DataTableHeaders: FunctionComponent = ({ containerRef, effectiveColumns, @@ -29,7 +18,6 @@ const DataTableHeaders: FunctionComponent = ({ sortBy, orderAsc, }) => { - const classes = useStyles(); const theme = useTheme(); const { storageKey, @@ -76,14 +64,22 @@ const DataTableHeaders: FunctionComponent = ({ const ordonableColumns = useMemo(() => effectiveColumns.filter(({ id }) => !['select', 'navigate'].includes(id)), [columns]); return (
0 || selectAll) && !disableSelectAll ? theme.palette.background.accent : 'unset', + }} > {effectiveColumns.some(({ id }) => id === 'select') && (
0 || selectAll) && !disableSelectAll ? theme.palette.background.accent : 'unset', }} > = ({ key={column.id} column={column} setAnchorEl={setAnchorEl} + isActive={activeColumn?.id === column.id} setActiveColumn={setActiveColumn} setLocalStorageColumns={setLocalStorageColumns} containerRef={containerRef} diff --git a/opencti-platform/opencti-front/src/components/dataGrid/components/DataTableLine.tsx b/opencti-platform/opencti-front/src/components/dataGrid/components/DataTableLine.tsx index 41de81fd795d..d238bad8c3f7 100644 --- a/opencti-platform/opencti-front/src/components/dataGrid/components/DataTableLine.tsx +++ b/opencti-platform/opencti-front/src/components/dataGrid/components/DataTableLine.tsx @@ -14,11 +14,11 @@ import { getMainRepresentative } from '../../../utils/defaultRepresentatives'; // Deprecated - https://mui.com/system/styles/basics/ // Do not use it for new code. -const useStyles = makeStyles((theme) => createStyles({ +const useStyles = makeStyles((theme) => createStyles({ cellContainer: ({ cell }) => ({ display: 'flex', width: `calc(var(--col-${cell?.id}-size) * 1px)`, - height: '50px', + height: theme.spacing(6), alignItems: 'center', overflow: 'hidden', textOverflow: 'ellipsis', @@ -31,15 +31,16 @@ const useStyles = makeStyles ({ + row: ({ clickable }) => ({ display: 'flex', - borderTop: `1px solid ${theme.palette.background.accent}`, - '&:hover': navigable ? { + borderBottom: `1px solid ${theme.palette.divider}`, + '&:hover': clickable ? { backgroundColor: theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, .1)' @@ -58,7 +59,7 @@ export const DataTableLineDummy = () => { ))} @@ -71,7 +72,9 @@ export const DataTableLineDummy = () => { ); }; -export const DataTableLinesDummy = ({ number = 10 }: { number?: number }) => Array(number).fill(0).map((_, idx) => ()); +export const DataTableLinesDummy = ({ number = 10 }: { number?: number }) => { + return Array(Math.min(number, 25)).fill(0).map((_, idx) => ()); +}; const DataTableCell = ({ cell, @@ -112,6 +115,7 @@ const DataTableLine = ({ actions, disableNavigation, onLineClick, + selectOnLineClick, variant, } = useDataTableContext(); const data = useLineData(row); @@ -121,15 +125,10 @@ const DataTableLine = ({ link = `${link}/${redirectionMode}`; } - const navigable = !disableNavigation && !onLineClick; - const internalOnClick = () => { - if (onLineClick) { - onLineClick(data); - } else if (navigable) { - navigate(link); - } - }; - const classes = useStyles({ navigable }); + const navigable = !disableNavigation && !onLineClick && !selectOnLineClick; + const clickable = !!(navigable || selectOnLineClick || onLineClick); + + const classes = useStyles({ clickable }); const { selectAll, @@ -139,13 +138,38 @@ const DataTableLine = ({ } = useDataTableToggle(storageKey); const startsWithSelect = effectiveColumns.at(0)?.id === 'select'; + const endWithNavigate = effectiveColumns.at(-1)?.id === 'navigate'; + + const handleSelectLine = (event: React.MouseEvent) => { + event.preventDefault(); + event.stopPropagation(); + if (event.shiftKey) { + onToggleShiftEntity(index, data, event); + } else { + onToggleEntity(data, event); + } + }; + + const handleRowClick = (event: React.MouseEvent) => { + event.preventDefault(); + event.stopPropagation(); + if (selectOnLineClick) { + handleSelectLine(event); + } else if (onLineClick) { + onLineClick(data); + } else if (navigable) { + navigate(link); + } + }; + return (
{startsWithSelect && ( @@ -158,15 +182,7 @@ const DataTableLine = ({ > { - event.preventDefault(); - event.stopPropagation(); - if (event.shiftKey) { - onToggleShiftEntity(index, data, event); - } else { - onToggleEntity(data, event); - } - }} + onClick={handleSelectLine} checked={ (selectAll && !((data.id || 'id') in (deSelectedElements || {}))) @@ -175,7 +191,7 @@ const DataTableLine = ({ />
)} - {effectiveColumns.slice(startsWithSelect ? 1 : 0, actions ? undefined : -1).map((column) => ( + {effectiveColumns.slice(startsWithSelect ? 1 : 0, (actions || disableNavigation) ? undefined : -1).map((column) => ( ))} -
{ - if (actions && navigable) { - e.preventDefault(); - e.stopPropagation(); - } - }} - > - {actions && actions(data)} - {effectiveColumns.at(-1)?.id === 'navigate' && ( - navigate(link)}> - - - )} -
+ {(actions || endWithNavigate) && ( +
+ {actions && actions(data)} + {endWithNavigate && ( + navigate(link)}> + + + )} +
+ )}
); }; diff --git a/opencti-platform/opencti-front/src/components/dataGrid/dataTableTypes.ts b/opencti-platform/opencti-front/src/components/dataGrid/dataTableTypes.ts index a4a0bb05264b..34ef4bebff04 100644 --- a/opencti-platform/opencti-front/src/components/dataGrid/dataTableTypes.ts +++ b/opencti-platform/opencti-front/src/components/dataGrid/dataTableTypes.ts @@ -60,7 +60,7 @@ export interface DataTableContextProps { numberOfSelectedElements: number onToggleEntity: ( entity: any, - _?: React.SyntheticEvent, + _?: React.MouseEvent, forceRemove?: any[], ) => void handleClearSelectedElements: () => void @@ -85,7 +85,10 @@ export interface DataTableContextProps { disableNavigation: DataTableProps['disableNavigation'] disableToolBar: DataTableProps['disableToolBar'] disableSelectAll: DataTableProps['disableSelectAll'] + selectOnLineClick: DataTableProps['selectOnLineClick'] onLineClick: DataTableProps['onLineClick'] + page: number + setPage:Dispatch> } export interface DataTableProps { @@ -94,6 +97,7 @@ export interface DataTableProps { storageKey: string initialValues: LocalStorage toolbarFilters?: FilterGroup + handleCopy?: () => void lineFragment?: GraphQLTaggedNode dataQueryArgs: any availableFilterKeys?: string[] | undefined; @@ -126,6 +130,7 @@ export interface DataTableProps { disableLineSelection?: boolean disableToolBar?: boolean disableSelectAll?: boolean + selectOnLineClick?: boolean onLineClick?: (line: any) => void } @@ -149,6 +154,7 @@ export interface DataTableDisplayFiltersProps { additionalFilterKeys?: string[] availableRelationFilterTypes?: Record | undefined availableFilterKeys?: string[] | undefined; + availableEntityTypes?: string[] paginationOptions: any } @@ -180,6 +186,7 @@ export interface DataTableHeaderProps { containerRef?: MutableRefObject sortBy: boolean orderAsc: boolean + isActive?: boolean } export interface DataTableLineProps { @@ -188,7 +195,7 @@ export interface DataTableLineProps { effectiveColumns: DataTableColumns storageHelpers: DataTableProps['storageHelpers'] index: number - onToggleShiftEntity: (currentIndex: number, currentEntity: { id: string }, event?: React.SyntheticEvent) => void + onToggleShiftEntity: (currentIndex: number, currentEntity: { id: string }, event?: React.MouseEvent) => void } export interface DataTableCellProps { diff --git a/opencti-platform/opencti-front/src/components/dataGrid/dataTableUtils.tsx b/opencti-platform/opencti-front/src/components/dataGrid/dataTableUtils.tsx index 27811954eef5..df5f5ce17d9e 100644 --- a/opencti-platform/opencti-front/src/components/dataGrid/dataTableUtils.tsx +++ b/opencti-platform/opencti-front/src/components/dataGrid/dataTableUtils.tsx @@ -19,6 +19,7 @@ import { getMainRepresentative } from '../../utils/defaultRepresentatives'; import ItemEntityType from '../ItemEntityType'; import ItemOpenVocab from '../ItemOpenVocab'; import ItemBoolean from '../ItemBoolean'; +import ItemSeverity from '../ItemSeverity'; const MAGICAL_SIZE = 0.113; @@ -55,7 +56,7 @@ const useStyles = makeStyles((theme) => ({ float: 'left', width: 120, textTransform: 'uppercase', - borderRadius: '0', + borderRadius: 4, }, chip: { fontSize: 13, @@ -226,14 +227,14 @@ const defaultColumns: DataTableProps['dataColumns'] = { label: 'Original creation date', percentWidth: 15, isSortable: true, - render: ({ created }, { fd }) => fd(created), + render: ({ created }, helpers) => defaultRender(helpers.fd(created), helpers), }, created_at: { id: 'created_at', label: 'Platform creation date', percentWidth: 15, isSortable: true, - render: ({ created_at }, { fd }) => fd(created_at), + render: ({ created_at }, helpers) => defaultRender(helpers.fd(created_at), helpers), }, createdBy: { id: 'createdBy', @@ -655,9 +656,9 @@ const defaultColumns: DataTableProps['dataColumns'] = { percentWidth: 10, isSortable: true, render: ({ severity }, { t_i18n }) => ( - ), @@ -820,8 +821,11 @@ const defaultColumns: DataTableProps['dataColumns'] = { label: 'CVSS3 - Severity', percentWidth: 15, isSortable: true, - render: ({ x_opencti_cvss_base_severity }) => ( - <>{x_opencti_cvss_base_severity} + render: ({ x_opencti_cvss_base_severity }, { t_i18n }) => ( + ), }, x_opencti_organization_type: { @@ -860,6 +864,42 @@ const defaultColumns: DataTableProps['dataColumns'] = { /> ), }, + file_name: { + id: 'file_name', + label: 'File name', + percentWidth: 12, + isSortable: false, + render: (data, { column: { size } }) => { + const file = (data.importFiles?.edges && data.importFiles.edges.length > 0) + ? data.importFiles.edges[0]?.node + : { name: 'N/A', metaData: { mimetype: 'N/A' }, size: 0 }; + return (<>{truncate(file?.name, size * MAGICAL_SIZE)}); + }, + }, + file_mime_type: { + id: 'file_mime_type', + label: 'Mime/Type', + percentWidth: 8, + isSortable: false, + render: (data, { column: { size } }) => { + const file = (data.importFiles?.edges && data.importFiles.edges.length > 0) + ? data.importFiles.edges[0]?.node + : { name: 'N/A', metaData: { mimetype: 'N/A' }, size: 0 }; + return (<>{truncate(file?.metaData?.mimetype, size * MAGICAL_SIZE)}); + }, + }, + file_size: { + id: 'file_size', + label: 'File size', + percentWidth: 8, + isSortable: false, + render: (data, { b }) => { + const file = (data.importFiles?.edges && data.importFiles.edges.length > 0) + ? data.importFiles.edges[0]?.node + : { name: 'N/A', metaData: { mimetype: 'N/A' }, size: 0 }; + return (<>{b(file?.size)}); + }, + }, }; export const defaultColumnsMap = new Map>(Object.entries(defaultColumns)); diff --git a/opencti-platform/opencti-front/src/components/list_cards/ListCards.jsx b/opencti-platform/opencti-front/src/components/list_cards/ListCards.jsx index 9e5a5ae3fe0c..5111233c4327 100644 --- a/opencti-platform/opencti-front/src/components/list_cards/ListCards.jsx +++ b/opencti-platform/opencti-front/src/components/list_cards/ListCards.jsx @@ -21,13 +21,12 @@ import { KNOWLEDGE_KNGETEXPORT } from '../../utils/hooks/useGranted'; import FilterIconButton from '../FilterIconButton'; import { export_max_size } from '../../utils/utils'; -const styles = () => ({ +const styles = (theme) => ({ parameters: { display: 'flex', alignItems: 'center', - gap: 10, - marginTop: -10, - paddingBottom: 10, + gap: theme.spacing(1), + marginBottom: theme.spacing(2), flexWrap: 'wrap', }, cardsContainer: { @@ -40,9 +39,6 @@ const styles = () => ({ filler: { flex: 'auto', }, - views: { - marginTop: -5, - }, }); class ListCards extends Component { @@ -142,9 +138,9 @@ class ListCards extends Component { )}
-
+
{numberOfElements && ( -
+
{`${numberOfElements.number}${numberOfElements.symbol}`}{' '} {t('entitie(s)')}
@@ -163,7 +159,6 @@ class ListCards extends Component { handleChangeView(value); } }} - style={{ margin: '0 0 0 5px' }} > {typeof handleChangeView === 'function' && ( @@ -211,13 +206,13 @@ class ListCards extends Component { )} - {/* - * Passing in createButton because cannot use hooks here. - * More permanent solution once FAB_REPLACEMENT is completed. - */} - {createButton} )} + {/* + * Passing in createButton because cannot use hooks here. + * More permanent solution once FAB_REPLACEMENT is completed. + */} + {createButton}
({ alignItems: 'center', }, headerItemText: { + marginRight: theme.spacing(1), whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', diff --git a/opencti-platform/opencti-front/src/components/nestedMenu/NestedMenuButton.tsx b/opencti-platform/opencti-front/src/components/nestedMenu/NestedMenuButton.tsx new file mode 100644 index 000000000000..71a81e01b006 --- /dev/null +++ b/opencti-platform/opencti-front/src/components/nestedMenu/NestedMenuButton.tsx @@ -0,0 +1,299 @@ +import * as React from 'react'; +import Button from '@mui/material/Button'; +import MenuItem from '@mui/material/MenuItem'; +import { Box, ClickAwayListener, Grow, MenuList, Paper, Popper, Typography } from '@mui/material'; +import ChevronRight from '@mui/icons-material/ChevronRight'; +import { ButtonProps } from '@mui/material/Button/Button'; + +// This function checks whether a point (x, y) is on the left or right side of a line formed by two points (px, py) and (qx, qy). +// If the result is negative, the point is on the right side of the line. If positive, it's on the left side. +// It helps us determine if a point is on the same side as a vertex of the triangle when compared to its edges. +const sign = ( + px: number, + py: number, + qx: number, + qy: number, + rx: number, + ry: number, +) => { + return (px - rx) * (qy - ry) - (qx - rx) * (py - ry); +}; + +// This function checks if a point (x, y) is inside a triangle formed by three points (x1, y1), (x2, y2), and (x3, y3). +const pointInTriangle = ( + currentMouseCoordinates: Array, + triangleCoordinates: Array>, +) => { + const [[x1, y1], [x2, y2], [x3, y3]] = triangleCoordinates; + const [x, y] = currentMouseCoordinates; + + const b1 = sign(x, y, x1, y1, x2, y2) <= 0; + const b2 = sign(x, y, x2, y2, x3, y3) <= 0; + const b3 = sign(x, y, x3, y3, x1, y1) <= 0; + // If all signs are the same (either all negative or all positive), the point is inside the triangle. + return b1 === b2 && b2 === b3; +}; + +type Option = { + value: string; + menuLevel: number; + label?: string; // if not present, value is used + onClick?: () => void; // individual click handler + selected?: boolean; + nestedOptions? : Option[]; +}; + +type NestedMenuProps = { + menuButtonChildren?: React.ReactNode; + menuButtonProps?: ButtonProps; + options: Array
+ } + > + + + )} ); case 'originOfTheCase': diff --git a/opencti-platform/opencti-front/src/private/components/cases/case_rfis/CaseRfi.tsx b/opencti-platform/opencti-front/src/private/components/cases/case_rfis/CaseRfi.tsx index f59b9a1659aa..ad7b279789f2 100644 --- a/opencti-platform/opencti-front/src/private/components/cases/case_rfis/CaseRfi.tsx +++ b/opencti-platform/opencti-front/src/private/components/cases/case_rfis/CaseRfi.tsx @@ -1,8 +1,13 @@ import Grid from '@mui/material/Grid'; -import React from 'react'; +import makeStyles from '@mui/styles/makeStyles'; +import React, { useRef } from 'react'; import { useFragment } from 'react-relay'; +import Typography from '@mui/material/Typography'; +import Paper from '@mui/material/Paper'; +import useHelper from 'src/utils/hooks/useHelper'; import { convertMarkings } from '../../../../utils/edition'; import { KNOWLEDGE_KNUPDATE } from '../../../../utils/hooks/useGranted'; +import useQueryLoading from '../../../../utils/hooks/useQueryLoading'; import Security from '../../../../utils/Security'; import StixCoreObjectExternalReferences from '../../analyses/external_references/StixCoreObjectExternalReferences'; import StixCoreObjectOrStixCoreRelationshipNotes from '../../analyses/notes/StixCoreObjectOrStixCoreRelationshipNotes'; @@ -10,14 +15,29 @@ import ContainerStixObjectsOrStixRelationships from '../../common/containers/Con import StixCoreObjectLatestHistory from '../../common/stix_core_objects/StixCoreObjectLatestHistory'; import StixDomainObjectOverview from '../../common/stix_domain_objects/StixDomainObjectOverview'; import { CaseUtils_case$key } from '../__generated__/CaseUtils_case.graphql'; -import CaseTasksLines from '../tasks/CaseTasksLines'; +import CaseTasksLines, { caseTasksLinesQuery } from '../tasks/CaseTasksLines'; import { caseFragment } from '../CaseUtils'; import CaseRfiDetails from './CaseRfiDetails'; import CaseRfiEdition from './CaseRfiEdition'; import { useFormatter } from '../../../../components/i18n'; -import useHelper from '../../../../utils/hooks/useHelper'; +import { usePaginationLocalStorage } from '../../../../utils/hooks/useLocalStorage'; +import { CaseTasksLinesQuery, CaseTasksLinesQuery$variables } from '../tasks/__generated__/CaseTasksLinesQuery.graphql'; +import ListLines from '../../../../components/list_lines/ListLines'; +import { tasksDataColumns } from '../tasks/TasksLine'; +import { CaseTasksLineDummy } from '../tasks/CaseTasksLine'; +import { isFilterGroupNotEmpty, useRemoveIdAndIncorrectKeysFromFilterGroupObject } from '../../../../utils/filters/filtersUtils'; +import { FilterGroup } from '../../../../utils/filters/filtersHelpers-types'; import useOverviewLayoutCustomization from '../../../../utils/hooks/useOverviewLayoutCustomization'; -import { useGetCurrentUserAccessRight } from '../../../../utils/authorizedMembers'; + +// Deprecated - https://mui.com/system/styles/basics/ +// Do not use it for new code. +const useStyles = makeStyles(() => ({ + paper: { + margin: '10px 0 0 0', + padding: 0, + borderRadius: 4, + }, +})); interface CaseRfiProps { caseRfiData: CaseUtils_case$key; @@ -25,17 +45,47 @@ interface CaseRfiProps { } const CaseRfi: React.FC = ({ caseRfiData, enableReferences }) => { + const classes = useStyles(); const { t_i18n } = useFormatter(); + const ref = useRef(null); const caseRfi = useFragment(caseFragment, caseRfiData); const { isFeatureEnable } = useHelper(); const isFABReplaced = isFeatureEnable('FAB_REPLACEMENT'); - const { canEdit } = useGetCurrentUserAccessRight(caseRfi.currentUserAccessRight); const overviewLayoutCustomization = useOverviewLayoutCustomization(caseRfi.entity_type); + const LOCAL_STORAGE_KEY = `cases-${caseRfi.id}-caseTask`; + const { viewStorage, helpers, paginationOptions } = usePaginationLocalStorage( + LOCAL_STORAGE_KEY, + { + searchTerm: '', + sortBy: 'name', + orderAsc: true, + }, + ); + const { sortBy, orderAsc, filters } = viewStorage; + + const userFilters = useRemoveIdAndIncorrectKeysFromFilterGroupObject(filters, ['Case-Rfi']); + const contextTaskFilters: FilterGroup = { + mode: 'and', + filters: [ + { key: 'entity_type', operator: 'eq', mode: 'or', values: ['Task'] }, + { key: 'objects', operator: 'eq', mode: 'or', values: [caseRfi.id] }, + ], + filterGroups: userFilters && isFilterGroupNotEmpty(userFilters) ? [userFilters] : [], + }; + + const queryTaskPaginationOptions = { + ...paginationOptions, + filters: contextTaskFilters, + } as unknown as CaseTasksLinesQuery$variables; + const queryRef = useQueryLoading( + caseTasksLinesQuery, + queryTaskPaginationOptions, + ); return ( <> @@ -60,12 +110,51 @@ const CaseRfi: React.FC = ({ caseRfiData, enableReferences }) => { ); case 'task': return ( - - + + {queryRef && ( + + + {t_i18n('Tasks')} + + + + {Array(20) + .fill(0) + .map((_, idx) => ( + + ))} + + +
+ } + > + + + )} ); case 'originOfTheCase': @@ -134,7 +223,7 @@ const CaseRfi: React.FC = ({ caseRfiData, enableReferences }) => { } {!isFABReplaced && ( - + )} diff --git a/opencti-platform/opencti-front/src/private/components/cases/case_rfts/CaseRft.tsx b/opencti-platform/opencti-front/src/private/components/cases/case_rfts/CaseRft.tsx index 43712f961ec7..7531b70f4580 100644 --- a/opencti-platform/opencti-front/src/private/components/cases/case_rfts/CaseRft.tsx +++ b/opencti-platform/opencti-front/src/private/components/cases/case_rfts/CaseRft.tsx @@ -1,23 +1,43 @@ import Grid from '@mui/material/Grid'; -import React from 'react'; +import makeStyles from '@mui/styles/makeStyles'; +import React, { useRef } from 'react'; import { useFragment } from 'react-relay'; +import Typography from '@mui/material/Typography'; +import Paper from '@mui/material/Paper'; +import useHelper from 'src/utils/hooks/useHelper'; import { convertMarkings } from '../../../../utils/edition'; import { KNOWLEDGE_KNUPDATE } from '../../../../utils/hooks/useGranted'; +import useQueryLoading from '../../../../utils/hooks/useQueryLoading'; import Security from '../../../../utils/Security'; import StixCoreObjectExternalReferences from '../../analyses/external_references/StixCoreObjectExternalReferences'; import StixCoreObjectOrStixCoreRelationshipNotes from '../../analyses/notes/StixCoreObjectOrStixCoreRelationshipNotes'; import ContainerStixObjectsOrStixRelationships from '../../common/containers/ContainerStixObjectsOrStixRelationships'; import StixCoreObjectLatestHistory from '../../common/stix_core_objects/StixCoreObjectLatestHistory'; import StixDomainObjectOverview from '../../common/stix_domain_objects/StixDomainObjectOverview'; +import { CaseTasksLinesQuery, CaseTasksLinesQuery$variables } from '../tasks/__generated__/CaseTasksLinesQuery.graphql'; import { CaseUtils_case$key } from '../__generated__/CaseUtils_case.graphql'; -import CaseTasksLines from '../tasks/CaseTasksLines'; +import CaseTasksLines, { caseTasksLinesQuery } from '../tasks/CaseTasksLines'; import { caseFragment } from '../CaseUtils'; import CaseRftDetails from './CaseRftDetails'; import CaseRftEdition from './CaseRftEdition'; import { useFormatter } from '../../../../components/i18n'; -import useHelper from '../../../../utils/hooks/useHelper'; +import { usePaginationLocalStorage } from '../../../../utils/hooks/useLocalStorage'; +import ListLines from '../../../../components/list_lines/ListLines'; +import { tasksDataColumns } from '../tasks/TasksLine'; +import { CaseTasksLineDummy } from '../tasks/CaseTasksLine'; +import { isFilterGroupNotEmpty, useRemoveIdAndIncorrectKeysFromFilterGroupObject } from '../../../../utils/filters/filtersUtils'; +import { FilterGroup } from '../../../../utils/filters/filtersHelpers-types'; import useOverviewLayoutCustomization from '../../../../utils/hooks/useOverviewLayoutCustomization'; -import { useGetCurrentUserAccessRight } from '../../../../utils/authorizedMembers'; + +// Deprecated - https://mui.com/system/styles/basics/ +// Do not use it for new code. +const useStyles = makeStyles(() => ({ + paper: { + margin: '10px 0 0 0', + padding: 0, + borderRadius: 4, + }, +})); interface CaseRftProps { caseRftData: CaseUtils_case$key; @@ -25,17 +45,45 @@ interface CaseRftProps { } const CaseRft: React.FC = ({ caseRftData, enableReferences }) => { + const classes = useStyles(); const { t_i18n } = useFormatter(); const { isFeatureEnable } = useHelper(); const isFABReplaced = isFeatureEnable('FAB_REPLACEMENT'); + const ref = useRef(null); const caseRft = useFragment(caseFragment, caseRftData); const overviewLayoutCustomization = useOverviewLayoutCustomization(caseRft.entity_type); - const { canEdit } = useGetCurrentUserAccessRight(caseRft.currentUserAccessRight); + const LOCAL_STORAGE_KEY_CASE_TASKS = `cases-${caseRft.id}-caseTask`; + const { viewStorage, helpers, paginationOptions } = usePaginationLocalStorage( + LOCAL_STORAGE_KEY_CASE_TASKS, + { + searchTerm: '', + sortBy: 'name', + orderAsc: true, + }, + ); + const { sortBy, orderAsc, filters } = viewStorage; + const userFilters = useRemoveIdAndIncorrectKeysFromFilterGroupObject(filters, ['Case-Rft']); + const contextTaskFilters: FilterGroup = { + mode: 'and', + filters: [ + { key: 'entity_type', operator: 'eq', mode: 'or', values: ['Task'] }, + { key: 'objects', operator: 'eq', mode: 'or', values: [caseRft.id] }, + ], + filterGroups: userFilters && isFilterGroupNotEmpty(userFilters) ? [userFilters] : [], + }; + const queryTaskPaginationOptions = { + ...paginationOptions, + filters: contextTaskFilters, + } as unknown as CaseTasksLinesQuery$variables; + const queryRef = useQueryLoading( + caseTasksLinesQuery, + queryTaskPaginationOptions, + ); return ( <> @@ -60,12 +108,51 @@ const CaseRft: React.FC = ({ caseRftData, enableReferences }) => { ); case 'task': return ( - - + + {queryRef && ( + + + {t_i18n('Tasks')} + + + + {Array(20) + .fill(0) + .map((_, idx) => ( + + ))} + + +
+ } + > + + + )} ); case 'originOfTheCase': @@ -134,7 +221,7 @@ const CaseRft: React.FC = ({ caseRftData, enableReferences }) => { } {!isFABReplaced && ( - + )} diff --git a/opencti-platform/opencti-front/src/private/components/cases/tasks/CaseTasksLine.tsx b/opencti-platform/opencti-front/src/private/components/cases/tasks/CaseTasksLine.tsx new file mode 100644 index 000000000000..31a169fb35db --- /dev/null +++ b/opencti-platform/opencti-front/src/private/components/cases/tasks/CaseTasksLine.tsx @@ -0,0 +1,191 @@ +import ListItem from '@mui/material/ListItem'; +import ListItemIcon from '@mui/material/ListItemIcon'; +import ListItemText from '@mui/material/ListItemText'; +import makeStyles from '@mui/styles/makeStyles'; +import React, { FunctionComponent, useState } from 'react'; +import { graphql, useFragment } from 'react-relay'; +import ListItemSecondaryAction from '@mui/material/ListItemSecondaryAction'; +import Skeleton from '@mui/material/Skeleton'; +import IconButton from '@mui/material/IconButton'; +import MoreVert from '@mui/icons-material/MoreVert'; +import Drawer from '@components/common/drawer/Drawer'; +import CaseTaskOverview from '@components/cases/tasks/CaseTaskOverview'; +import { NorthEastOutlined } from '@mui/icons-material'; +import { Link } from 'react-router-dom'; +import ItemIcon from '../../../../components/ItemIcon'; +import type { Theme } from '../../../../components/Theme'; +import { tasksDataColumns } from './TasksLine'; +import { useFormatter } from '../../../../components/i18n'; +import { CaseTasksLine_data$key } from './__generated__/CaseTasksLine_data.graphql'; +import TaskPopover from './TaskPopover'; +import { CaseTasksLinesQuery$variables } from './__generated__/CaseTasksLinesQuery.graphql'; + +// Deprecated - https://mui.com/system/styles/basics/ +// Do not use it for new code. +const useStyles = makeStyles((theme) => ({ + item: { + paddingLeft: 15, + height: 50, + }, + itemIcon: { + color: theme.palette.primary.main, + minWidth: 52, + }, + bodyItem: { + height: 20, + fontSize: 13, + float: 'left', + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis', + paddingRight: 10, + }, +})); + +const CaseTaskFragment = graphql` + fragment CaseTasksLine_data on Task { + id + standard_id + name + due_date + description + workflowEnabled + objectMarking { + definition + definition_type + id + } + objectLabel { + id + value + color + } + objectAssignee { + entity_type + id + name + } + status { + template { + name + color + } + } + ...CaseTaskOverview_task + } +`; + +interface CaseTasksLineProps { + node: CaseTasksLine_data$key; + entityId?: string; + paginationOptions: CaseTasksLinesQuery$variables; + enableReferences: boolean; +} + +export const CaseTasksLine: FunctionComponent = ({ + node, + entityId, + paginationOptions, + enableReferences, +}) => { + const classes = useStyles(); + const { fld } = useFormatter(); + const task = useFragment(CaseTaskFragment, node); + const [open, setOpen] = useState(false); + return ( + <> + setOpen(true)} + > + + + + + {Object.values(tasksDataColumns).map((value) => ( +
+ {value.render?.(task, { fld, classes })} +
+ ))} + + } + /> + + + +
+ setOpen(false)} + header={ + + + + } + > + + + + ); +}; + +export const CaseTasksLineDummy = () => { + const classes = useStyles(); + return ( + + + + + + {Object.values(tasksDataColumns).map((value) => ( +
+ +
+ ))} +
+ } + /> + + + + + + + ); +}; diff --git a/opencti-platform/opencti-front/src/private/components/cases/tasks/CaseTasksLines.tsx b/opencti-platform/opencti-front/src/private/components/cases/tasks/CaseTasksLines.tsx index b1b907890ee3..733332bb1ad1 100644 --- a/opencti-platform/opencti-front/src/private/components/cases/tasks/CaseTasksLines.tsx +++ b/opencti-platform/opencti-front/src/private/components/cases/tasks/CaseTasksLines.tsx @@ -1,4 +1,4 @@ -import { AddOutlined, ContentPasteGoOutlined, NorthEastOutlined } from '@mui/icons-material'; +import { AddOutlined, ContentPasteGoOutlined } from '@mui/icons-material'; import Button from '@mui/material/Button'; import Dialog from '@mui/material/Dialog'; import DialogContent from '@mui/material/DialogContent'; @@ -9,39 +9,34 @@ import Tooltip from '@mui/material/Tooltip'; import Typography from '@mui/material/Typography'; import makeStyles from '@mui/styles/makeStyles'; import { Form, Formik } from 'formik'; -import React, { FunctionComponent, useRef, useState } from 'react'; -import { graphql } from 'react-relay'; +import React, { FunctionComponent, MutableRefObject, useState } from 'react'; +import { graphql, PreloadedQuery } from 'react-relay'; +import { GridTypeMap } from '@mui/material'; import Drawer from '@components/common/drawer/Drawer'; -import { TasksLine_node$data } from '@components/cases/tasks/__generated__/TasksLine_node.graphql'; -import { CaseTasksLines_data$data } from '@components/cases/tasks/__generated__/CaseTasksLines_data.graphql'; -import CaseTaskOverview from '@components/cases/tasks/CaseTaskOverview'; -import { Link } from 'react-router-dom'; -import { CaseTaskOverview_task$key } from '@components/cases/tasks/__generated__/CaseTaskOverview_task.graphql'; import { useFormatter } from '../../../../components/i18n'; import type { Theme } from '../../../../components/Theme'; import { handleErrorInForm } from '../../../../relay/environment'; -import { UsePreloadedPaginationFragment } from '../../../../utils/hooks/usePreloadedPaginationFragment'; +import usePreloadedPaginationFragment from '../../../../utils/hooks/usePreloadedPaginationFragment'; import CaseTemplateField from '../../common/form/CaseTemplateField'; import { Option } from '../../common/form/ReferenceField'; import CaseTaskCreation from './CaseTaskCreation'; -import { caseSetTemplateQuery, CaseTaskFragment, generateConnectionId } from '../CaseUtils'; +import { caseSetTemplateQuery, generateConnectionId } from '../CaseUtils'; +import ListLinesContent from '../../../../components/list_lines/ListLinesContent'; +import ListLines from '../../../../components/list_lines/ListLines'; +import { CaseTasksLine } from './CaseTasksLine'; +import { tasksDataColumns } from './TasksLine'; +import { CaseTasksLines_data$key } from './__generated__/CaseTasksLines_data.graphql'; import { CaseTasksLinesQuery, CaseTasksLinesQuery$variables } from './__generated__/CaseTasksLinesQuery.graphql'; -import DataTable from '../../../../components/dataGrid/DataTable'; -import { usePaginationLocalStorage } from '../../../../utils/hooks/useLocalStorage'; -import { isFilterGroupNotEmpty, useRemoveIdAndIncorrectKeysFromFilterGroupObject } from '../../../../utils/filters/filtersUtils'; -import { DataTableProps, DataTableVariant } from '../../../../components/dataGrid/dataTableTypes'; -import ItemDueDate from '../../../../components/ItemDueDate'; -import useQueryLoading from '../../../../utils/hooks/useQueryLoading'; import useApiMutation from '../../../../utils/hooks/useApiMutation'; -import { FilterGroup } from '../../../../utils/filters/filtersHelpers-types'; // Deprecated - https://mui.com/system/styles/basics/ // Do not use it for new code. const useStyles = makeStyles((theme) => ({ paper: { - margin: '-4px 0 0 0', + margin: '-5px 0 0 0', padding: 0, borderRadius: 4, + overflowY: 'inherit', }, createButton: { float: 'left', @@ -67,7 +62,6 @@ export const caseTasksLinesQuery = graphql` $cursor: ID $orderBy: TasksOrdering $orderMode: OrderingMode - $search: String ) { ...CaseTasksLines_data @arguments( @@ -76,7 +70,6 @@ export const caseTasksLinesQuery = graphql` cursor: $cursor orderBy: $orderBy orderMode: $orderMode - search: $search ) } `; @@ -89,7 +82,6 @@ const caseTasksLinesFragment = graphql` cursor: { type: "ID" } orderBy: { type: "TasksOrdering" } orderMode: { type: "OrderingMode", defaultValue: desc } - search: { type: "String" } ) @refetchable(queryName: "TasksRefetch") { tasks( @@ -98,101 +90,53 @@ const caseTasksLinesFragment = graphql` after: $cursor orderBy: $orderBy orderMode: $orderMode - search: $search ) @connection(key: "Pagination_tasks") { edges { node { - ...CaseUtilsTasksLine_data - ...CaseTaskOverview_task + ...CaseTasksLine_data } } - pageInfo { - globalCount - } } } `; interface CaseTasksLinesProps { - caseId: string - defaultMarkings?: Option[] + caseId: string; + queryRef: PreloadedQuery; + paginationOptions: CaseTasksLinesQuery$variables; + defaultMarkings?: Option[]; + sortBy: string | undefined; + orderAsc: boolean | undefined; + handleSort?: (field: string, order: boolean) => void; + containerRef: MutableRefObject; enableReferences: boolean; } const CaseTasksLines: FunctionComponent = ({ caseId, + queryRef, + paginationOptions, defaultMarkings, + sortBy, + orderAsc, + handleSort, + containerRef, enableReferences, }) => { const classes = useStyles(); const { t_i18n } = useFormatter(); - const [open, setOpen] = useState(false); const [openCaseTemplate, setOpenCaseTemplate] = useState(false); - - const [task, setTask] = useState(); - const handleOpen = () => setOpen(true); const handleClose = () => setOpen(false); - const [commit] = useApiMutation(caseSetTemplateQuery); - - const LOCAL_STORAGE_KEY_CASE_TASKS = `cases-${caseId}-caseTask`; - const initialValues = { - searchTerm: '', - sortBy: 'created', - orderAsc: false, - }; - // TASKS - const { viewStorage: { filters }, helpers, paginationOptions } = usePaginationLocalStorage( - LOCAL_STORAGE_KEY_CASE_TASKS, - initialValues, - true, - ); - const userFilters = useRemoveIdAndIncorrectKeysFromFilterGroupObject(filters, ['Case']); - const contextTaskFilters: FilterGroup = { - mode: 'and', - filters: [ - { key: 'entity_type', operator: 'eq', mode: 'or', values: ['Task'] }, - { key: 'objects', operator: 'eq', mode: 'or', values: [caseId] }, - ], - filterGroups: userFilters && isFilterGroupNotEmpty(userFilters) ? [userFilters] : [], - }; - - const queryTaskPaginationOptions = { - ...paginationOptions, - filters: contextTaskFilters, - } as unknown as CaseTasksLinesQuery$variables; - - const queryRef = useQueryLoading( - caseTasksLinesQuery, - queryTaskPaginationOptions, - ); - - const preloadedPaginationProps = { + const { data } = usePreloadedPaginationFragment({ + queryRef, linesQuery: caseTasksLinesQuery, linesFragment: caseTasksLinesFragment, - queryRef, - nodePath: ['tasks', 'pageInfo', 'globalCount'], - setNumberOfElements: helpers.handleSetNumberOfElements, - } as UsePreloadedPaginationFragment; - - const dataColumns: DataTableProps['dataColumns'] = { - name: { percentWidth: 35 }, - due_date: { - label: 'Due Date', - percentWidth: 15, - isSortable: true, - render: (t: TasksLine_node$data) => ( - - ), - }, - objectAssignee: { percentWidth: 20 }, - objectLabel: { percentWidth: 20 }, - x_opencti_workflow_id: { percentWidth: 10 }, - }; - - const ref = useRef(null); + }); + const { count: _, ...tasksFilters } = paginationOptions; return (
= ({ classes={{ root: classes.createButton }} size="large" > - + @@ -221,28 +165,9 @@ const CaseTasksLines: FunctionComponent = ({ classes={{ root: classes.applyButton }} size="large" > - + -
- -
- {(queryRef && ref.current) && ( - data.tasks?.edges?.map((n) => n?.node)} - storageKey={LOCAL_STORAGE_KEY_CASE_TASKS} - initialValues={initialValues} - toolbarFilters={contextTaskFilters} - preloadedPaginationProps={preloadedPaginationProps} - lineFragment={CaseTaskFragment} - variant={DataTableVariant.inline} - rootRef={ref.current} - onLineClick={(line: CaseTaskOverview_task$key & { name: string, id: string }) => setTask(line)} - /> - )} -
-
= ({ connections: [ generateConnectionId({ key: 'Pagination_tasks', - params: queryTaskPaginationOptions, + params: tasksFilters, }), ], }, @@ -318,30 +243,32 @@ const CaseTasksLines: FunctionComponent = ({ - {task && ( - setTask(undefined)} - header={ - - - - } +
+ + - - - )} + false} + entityId={caseId} + paginationOptions={tasksFilters} + containerRef={containerRef} + enableReferences={enableReferences} + /> + +
); }; diff --git a/opencti-platform/opencti-front/src/private/components/cases/tasks/TaskPopover.tsx b/opencti-platform/opencti-front/src/private/components/cases/tasks/TaskPopover.tsx index 59bbf5f88731..6f6e6bc0fe59 100644 --- a/opencti-platform/opencti-front/src/private/components/cases/tasks/TaskPopover.tsx +++ b/opencti-platform/opencti-front/src/private/components/cases/tasks/TaskPopover.tsx @@ -62,12 +62,16 @@ const TaskPopover = ({ { id }, ); const handleOpen = (event: React.SyntheticEvent) => { + event.preventDefault(); + event.stopPropagation(); setAnchorEl(event.currentTarget); }; const handleClose = () => { setAnchorEl(null); }; - const handleOpenEdit = () => { + const handleOpenEdit = (event: React.SyntheticEvent) => { + event.preventDefault(); + event.stopPropagation(); setDisplayEdit(true); handleClose(); }; @@ -104,7 +108,7 @@ const TaskPopover = ({ }); }; return ( -
+
e.stopPropagation()}> {variant === 'inLine' ? ( void @@ -22,11 +26,13 @@ interface ControlledDialProps { } const ControlledDial = ({ onOpen, title }: ControlledDialProps) => { + const theme = useTheme(); return (
- } - /> - - - - - ); -}; - -export const StixCyberObservableLine = createFragmentContainer( - StixCyberObservableLineComponent, - { - node: graphql` - fragment StixCyberObservableLine_node on StixCyberObservable { +// eslint-disable-next-line import/prefer-default-export +export const stixCyberObservableLineFragment = graphql` + fragment StixCyberObservableLine_node on StixCyberObservable { + id + entity_type + parent_types + observable_value + created_at + createdBy { + ... on Identity { id + name entity_type - parent_types - observable_value - created_at - createdBy { - ... on Identity { - id + } + } + ... on IPv4Addr { + countries { + edges { + node { name - entity_type - } - } - ... on IPv4Addr { - countries { - edges { - node { - name - x_opencti_aliases - } - } + x_opencti_aliases } } - ... on IPv6Addr { - countries { - edges { - node { - name - x_opencti_aliases - } - } + } + } + ... on IPv6Addr { + countries { + edges { + node { + name + x_opencti_aliases } } - objectMarking { - id - definition - x_opencti_order - x_opencti_color - } - objectLabel { - id - value - color - } - creators { - id - name - } } - `, - }, -); - -export const StixCyberObservableLineDummy = (props) => { - const classes = useStyles(); - const { dataColumns } = props; - return ( - - - - - - - - -
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- } - /> - - - - - ); -}; + } + objectMarking { + id + definition + x_opencti_order + x_opencti_color + } + objectLabel { + id + value + color + } + creators { + id + name + } + } +`; diff --git a/opencti-platform/opencti-front/src/private/components/observations/stix_cyber_observables/StixCyberObservablesLines.jsx b/opencti-platform/opencti-front/src/private/components/observations/stix_cyber_observables/StixCyberObservablesLines.jsx index 25597e8557b6..d94de949668a 100644 --- a/opencti-platform/opencti-front/src/private/components/observations/stix_cyber_observables/StixCyberObservablesLines.jsx +++ b/opencti-platform/opencti-front/src/private/components/observations/stix_cyber_observables/StixCyberObservablesLines.jsx @@ -1,79 +1,4 @@ -import React, { Component } from 'react'; -import * as PropTypes from 'prop-types'; -import { graphql, createPaginationContainer } from 'react-relay'; -import { pathOr } from 'ramda'; -import ListLinesContent from '../../../../components/list_lines/ListLinesContent'; -import { StixCyberObservableLine, StixCyberObservableLineDummy } from './StixCyberObservableLine'; -import { setNumberOfElements } from '../../../../utils/Number'; - -const nbOfRowsToLoad = 50; - -class StixCyberObservablesLines extends Component { - componentDidUpdate(prevProps) { - setNumberOfElements( - prevProps, - this.props, - 'stixCyberObservables', - this.props.setNumberOfElements.bind(this), - ); - } - - render() { - const { - initialLoading, - dataColumns, - relay, - onLabelClick, - onToggleEntity, - selectedElements, - deSelectedElements, - selectAll, - } = this.props; - return ( - } - DummyLineComponent={} - dataColumns={dataColumns} - nbOfRowsToLoad={nbOfRowsToLoad} - onLabelClick={onLabelClick.bind(this)} - selectedElements={selectedElements} - deSelectedElements={deSelectedElements} - selectAll={selectAll} - onToggleEntity={onToggleEntity.bind(this)} - /> - ); - } -} - -StixCyberObservablesLines.propTypes = { - classes: PropTypes.object, - paginationOptions: PropTypes.object, - dataColumns: PropTypes.object.isRequired, - data: PropTypes.object, - relay: PropTypes.object, - stixCyberObservables: PropTypes.object, - initialLoading: PropTypes.bool, - onLabelClick: PropTypes.func, - setNumberOfElements: PropTypes.func, - onToggleEntity: PropTypes.func, - selectedElements: PropTypes.object, - deSelectedElements: PropTypes.object, - selectAll: PropTypes.bool, -}; +import { graphql } from 'react-relay'; export const stixCyberObservablesLinesSubTypesQuery = graphql` query StixCyberObservablesLinesSubTypesQuery($type: String!) { @@ -150,79 +75,51 @@ export const stixCyberObservablesLinesSearchQuery = graphql` } `; -export default createPaginationContainer( - StixCyberObservablesLines, - { - data: graphql` - fragment StixCyberObservablesLines_data on Query - @argumentDefinitions( - types: { type: "[String]" } - search: { type: "String" } - count: { type: "Int", defaultValue: 25 } - cursor: { type: "ID" } - orderBy: { - type: "StixCyberObservablesOrdering" - defaultValue: created_at - } - orderMode: { type: "OrderingMode", defaultValue: asc } - filters: { type: "FilterGroup" } - ) { - stixCyberObservables( - types: $types - search: $search - first: $count - after: $cursor - orderBy: $orderBy - orderMode: $orderMode - filters: $filters - ) @connection(key: "Pagination_stixCyberObservables") { - edges { - node { - id - standard_id - entity_type - observable_value - created_at - objectMarking { - id - definition - x_opencti_order - x_opencti_color - } - ...StixCyberObservableLine_node - } - } - pageInfo { - endCursor - hasNextPage - globalCount +export const stixCyberObservablesLinesFragment = graphql` + fragment StixCyberObservablesLines_data on Query + @argumentDefinitions( + types: { type: "[String]" } + search: { type: "String" } + count: { type: "Int", defaultValue: 25 } + cursor: { type: "ID" } + orderBy: { + type: "StixCyberObservablesOrdering" + defaultValue: created_at + } + orderMode: { type: "OrderingMode", defaultValue: asc } + filters: { type: "FilterGroup" } + ) + @refetchable(queryName: "StixCyberObservablesLinesRefetchQuery") { + stixCyberObservables( + types: $types + search: $search + first: $count + after: $cursor + orderBy: $orderBy + orderMode: $orderMode + filters: $filters + ) @connection(key: "Pagination_stixCyberObservables") { + edges { + node { + id + standard_id + entity_type + observable_value + created_at + objectMarking { + id + definition + x_opencti_order + x_opencti_color } + ...StixCyberObservableLine_node } } - `, - }, - { - direction: 'forward', - getConnectionFromProps(props) { - return props.data && props.data.stixCyberObservables; - }, - getFragmentVariables(prevVars, totalCount) { - return { - ...prevVars, - count: totalCount, - }; - }, - getVariables(props, { count, cursor }, fragmentVariables) { - return { - types: fragmentVariables.types, - search: fragmentVariables.search, - count, - cursor, - orderBy: fragmentVariables.orderBy, - orderMode: fragmentVariables.orderMode, - filters: fragmentVariables.filters, - }; - }, - query: stixCyberObservablesLinesQuery, - }, -); + pageInfo { + endCursor + hasNextPage + globalCount + } + } + } + `; diff --git a/opencti-platform/opencti-front/src/private/components/settings/Labels.jsx b/opencti-platform/opencti-front/src/private/components/settings/Labels.jsx index e2a51614bf33..0b6eaae41c79 100644 --- a/opencti-platform/opencti-front/src/private/components/settings/Labels.jsx +++ b/opencti-platform/opencti-front/src/private/components/settings/Labels.jsx @@ -15,7 +15,7 @@ import { emptyFilterGroup, useBuildEntityTypeBasedFilterContext } from '../../.. const useStyles = makeStyles(() => ({ container: { margin: 0, - padding: '0 200px 0 0', + padding: '0 180px 0 0', maxWidth: '100%', }, })); diff --git a/opencti-platform/opencti-front/src/private/components/settings/retention/RetentionPopover.jsx b/opencti-platform/opencti-front/src/private/components/settings/retention/RetentionPopover.jsx index 41d2226a5428..412b188d1295 100644 --- a/opencti-platform/opencti-front/src/private/components/settings/retention/RetentionPopover.jsx +++ b/opencti-platform/opencti-front/src/private/components/settings/retention/RetentionPopover.jsx @@ -15,7 +15,6 @@ import Slide from '@mui/material/Slide'; import MoreVert from '@mui/icons-material/MoreVert'; import inject18n from '../../../../components/i18n'; import { commitMutation, QueryRenderer } from '../../../../relay/environment'; -import Loader from '../../../../components/Loader'; import RetentionEdition from './RetentionEdition'; import { deleteNode } from '../../../../utils/store'; @@ -147,7 +146,7 @@ class RetentionPopover extends Component { /> ); } - return ; + return null; }} /> ); } - return ; + return null; }} /> { keyword={searchTerm} />
{ enableSubEntityLines={true} /> + {isFABReplaced && ( + + + + )}
@@ -177,7 +184,7 @@ const Narratives: FunctionComponent = () => { value="lines" aria-label="lines" > - + ), @@ -188,7 +195,7 @@ const Narratives: FunctionComponent = () => { aria-label="subEntityLines" style={{ height: 36 }} > - + )]} diff --git a/opencti-platform/opencti-front/src/private/components/techniques/attack_patterns/AttackPatternEdition.jsx b/opencti-platform/opencti-front/src/private/components/techniques/attack_patterns/AttackPatternEdition.jsx index 37854abe9e43..be38d9eceebb 100644 --- a/opencti-platform/opencti-front/src/private/components/techniques/attack_patterns/AttackPatternEdition.jsx +++ b/opencti-platform/opencti-front/src/private/components/techniques/attack_patterns/AttackPatternEdition.jsx @@ -43,7 +43,7 @@ class AttackPatternEdition extends Component { /> ); } - return ; + return ; }} /> ); diff --git a/opencti-platform/opencti-front/src/private/components/techniques/courses_of_action/CourseOfActionEdition.jsx b/opencti-platform/opencti-front/src/private/components/techniques/courses_of_action/CourseOfActionEdition.jsx index b4b78108d555..6ed0699da333 100644 --- a/opencti-platform/opencti-front/src/private/components/techniques/courses_of_action/CourseOfActionEdition.jsx +++ b/opencti-platform/opencti-front/src/private/components/techniques/courses_of_action/CourseOfActionEdition.jsx @@ -44,7 +44,7 @@ class CourseOfActionEdition extends Component { /> ); } - return ; + return ; }} /> ); diff --git a/opencti-platform/opencti-front/src/private/components/techniques/narratives/NarrativeEdition.jsx b/opencti-platform/opencti-front/src/private/components/techniques/narratives/NarrativeEdition.jsx index 597e44f2690a..3c6f59fb61a1 100644 --- a/opencti-platform/opencti-front/src/private/components/techniques/narratives/NarrativeEdition.jsx +++ b/opencti-platform/opencti-front/src/private/components/techniques/narratives/NarrativeEdition.jsx @@ -44,7 +44,7 @@ class NarrativeEdition extends Component { /> ); } - return ; + return ; }} /> ); diff --git a/opencti-platform/opencti-front/src/private/components/threats/Campaigns.tsx b/opencti-platform/opencti-front/src/private/components/threats/Campaigns.tsx index 507b2bbf7011..60d9d13e4e00 100644 --- a/opencti-platform/opencti-front/src/private/components/threats/Campaigns.tsx +++ b/opencti-platform/opencti-front/src/private/components/threats/Campaigns.tsx @@ -176,9 +176,9 @@ const Campaigns = () => { ), - ( + ( - + ), ]} diff --git a/opencti-platform/opencti-front/src/private/components/threats/IntrusionSets.tsx b/opencti-platform/opencti-front/src/private/components/threats/IntrusionSets.tsx index ed4b1b2591e7..48da9600576b 100644 --- a/opencti-platform/opencti-front/src/private/components/threats/IntrusionSets.tsx +++ b/opencti-platform/opencti-front/src/private/components/threats/IntrusionSets.tsx @@ -168,9 +168,9 @@ const IntrusionSets = () => { ), - ( + ( - + ), ]} diff --git a/opencti-platform/opencti-front/src/private/components/threats/ThreatActorsGroup.tsx b/opencti-platform/opencti-front/src/private/components/threats/ThreatActorsGroup.tsx index 663585dc1202..8ce31867f46e 100644 --- a/opencti-platform/opencti-front/src/private/components/threats/ThreatActorsGroup.tsx +++ b/opencti-platform/opencti-front/src/private/components/threats/ThreatActorsGroup.tsx @@ -172,9 +172,9 @@ const ThreatActorsGroup = () => { ), - ( + ( - + ), ]} diff --git a/opencti-platform/opencti-front/src/private/components/threats/ThreatActorsIndividual.tsx b/opencti-platform/opencti-front/src/private/components/threats/ThreatActorsIndividual.tsx index e8c84082f1d8..8c30bd778f75 100644 --- a/opencti-platform/opencti-front/src/private/components/threats/ThreatActorsIndividual.tsx +++ b/opencti-platform/opencti-front/src/private/components/threats/ThreatActorsIndividual.tsx @@ -177,7 +177,7 @@ const ThreatActorsIndividual = () => { ), ( - + ), ]} diff --git a/opencti-platform/opencti-front/src/private/components/threats/campaigns/CampaignEdition.jsx b/opencti-platform/opencti-front/src/private/components/threats/campaigns/CampaignEdition.jsx index 25b8af0562f0..ad5f0e2cfeab 100644 --- a/opencti-platform/opencti-front/src/private/components/threats/campaigns/CampaignEdition.jsx +++ b/opencti-platform/opencti-front/src/private/components/threats/campaigns/CampaignEdition.jsx @@ -54,7 +54,7 @@ class CampaignEdition extends Component { /> ); } - return ; + return ; }} /> ); diff --git a/opencti-platform/opencti-front/src/private/components/threats/intrusion_sets/IntrusionSetEdition.jsx b/opencti-platform/opencti-front/src/private/components/threats/intrusion_sets/IntrusionSetEdition.jsx index 4d5c928ca3b7..dbd5342e5533 100644 --- a/opencti-platform/opencti-front/src/private/components/threats/intrusion_sets/IntrusionSetEdition.jsx +++ b/opencti-platform/opencti-front/src/private/components/threats/intrusion_sets/IntrusionSetEdition.jsx @@ -44,7 +44,7 @@ class IntrusionSetEdition extends Component { /> ); } - return ; + return ; }} /> ); diff --git a/opencti-platform/opencti-front/src/private/components/threats/threat_actors_group/ThreatActorGroupEdition.jsx b/opencti-platform/opencti-front/src/private/components/threats/threat_actors_group/ThreatActorGroupEdition.jsx index 234eed6da3ff..af79982e4da0 100644 --- a/opencti-platform/opencti-front/src/private/components/threats/threat_actors_group/ThreatActorGroupEdition.jsx +++ b/opencti-platform/opencti-front/src/private/components/threats/threat_actors_group/ThreatActorGroupEdition.jsx @@ -53,7 +53,7 @@ class ThreatActorGroupEdition extends Component { /> ); } - return ; + return ; }} /> ); diff --git a/opencti-platform/opencti-front/src/private/components/threats/threat_actors_group/ThreatActorGroupLocation.jsx b/opencti-platform/opencti-front/src/private/components/threats/threat_actors_group/ThreatActorGroupLocation.jsx index 80c9194b76a0..86897be3f4c0 100644 --- a/opencti-platform/opencti-front/src/private/components/threats/threat_actors_group/ThreatActorGroupLocation.jsx +++ b/opencti-platform/opencti-front/src/private/components/threats/threat_actors_group/ThreatActorGroupLocation.jsx @@ -21,6 +21,7 @@ import Security from '../../../../utils/Security'; import { KNOWLEDGE_KNUPDATE } from '../../../../utils/hooks/useGranted'; import AddLocationsThreatActorGroup from './AddLocationsThreatActorGroup'; import { addLocationsThreatActorGroupMutationRelationDelete } from './AddLocationsThreatActorGroupLines'; +import FieldOrEmpty from '../../../../components/FieldOrEmpty'; class ThreatActorGroupLocationsComponent extends Component { removeLocation(locationEdge) { @@ -60,62 +61,59 @@ class ThreatActorGroupLocationsComponent extends Component { />
- - {threatActorGroup.locations.edges.length === 0 && ( - - - - )} - {threatActorGroup.locations.edges.map((locationEdge) => { - const { types } = locationEdge; - const location = locationEdge.node; - const link = resolveLink(location.entity_type); - const flag = location.entity_type === 'Country' + + + {threatActorGroup.locations.edges.map((locationEdge) => { + const { types } = locationEdge; + const location = locationEdge.node; + const link = resolveLink(location.entity_type); + const flag = location.entity_type === 'Country' && R.head( (location.x_opencti_aliases ?? []).filter( (n) => n?.length === 2, ), ); - return ( - - + return ( + - {flag ? ( - {location.name} - ) : ( - - )} + + {flag ? ( + {location.name} + ) : ( + + )} + - - - {types.includes('manual') ? ( - - - this.removeLocation(locationEdge)} - size="large" - > - - - - - ) : } - - ); - })} - + + {types.includes('manual') ? ( + + + this.removeLocation(locationEdge)} + size="large" + > + + + + + ) : } + + ); + })} + + ); } diff --git a/opencti-platform/opencti-front/src/private/components/threats/threat_actors_individual/AddIndividualsThreatActorIndividual.tsx b/opencti-platform/opencti-front/src/private/components/threats/threat_actors_individual/AddIndividualsThreatActorIndividual.tsx index 77f3ad2c63b2..d61fe4c1d24c 100644 --- a/opencti-platform/opencti-front/src/private/components/threats/threat_actors_individual/AddIndividualsThreatActorIndividual.tsx +++ b/opencti-platform/opencti-front/src/private/components/threats/threat_actors_individual/AddIndividualsThreatActorIndividual.tsx @@ -35,25 +35,15 @@ AddIndividualsThreatActorIndividualProps count: 50, }, ); - const getRelationships = () => { - const relations = []; - for (const { node } of threatActorIndividual.stixCoreRelationships?.edges ?? []) { - const { relationship_type } = node ?? {}; - if (relationship_type === 'impersonates') relations.push(node); - } - return relations; - }; return (
- {(getRelationships().length > 0) && ( - - - - )} + + + { - const relations = []; - for (const { node } of threatActorIndividual.stixCoreRelationships?.edges ?? []) { - const { relationship_type } = node ?? {}; - if (relationship_type === 'known-as') relations.push(node); - } - return relations; - }; - return (
- {(getRelationships().length > 0) && ( - - - - )} + + + 0 ? { marginBottom: '20px' } : {}}> -
- {(nodes.length > 0) && ( - - {title} - )} - } + return ( +
+
- - -
-
- {(nodes.length > 0) && ( - nodes.map(({ id, to }) => ( - - )) - )} -
); + + +
+
+ + { + nodes.map(({ id, to }) => ( + + )) + } + +
+ ); }; export default ThreatActorIndividualDetailsChips; diff --git a/opencti-platform/opencti-front/src/private/components/threats/threat_actors_individual/ThreatActorIndividualLocation.jsx b/opencti-platform/opencti-front/src/private/components/threats/threat_actors_individual/ThreatActorIndividualLocation.jsx index 38841e9587a8..563ef891864b 100644 --- a/opencti-platform/opencti-front/src/private/components/threats/threat_actors_individual/ThreatActorIndividualLocation.jsx +++ b/opencti-platform/opencti-front/src/private/components/threats/threat_actors_individual/ThreatActorIndividualLocation.jsx @@ -63,7 +63,7 @@ class ThreatActorIndividualLocationsComponent extends Component { />
- + {threatActorIndividual.locations.edges.map((locationEdge) => { const { types } = locationEdge; diff --git a/opencti-platform/opencti-front/src/private/components/workspaces/Workspaces.tsx b/opencti-platform/opencti-front/src/private/components/workspaces/Workspaces.tsx index a68f76aaaed1..b6e6c9a5d447 100644 --- a/opencti-platform/opencti-front/src/private/components/workspaces/Workspaces.tsx +++ b/opencti-platform/opencti-front/src/private/components/workspaces/Workspaces.tsx @@ -2,9 +2,6 @@ import React, { FunctionComponent } from 'react'; import useHelper from 'src/utils/hooks/useHelper'; import { WorkspacesLinesPaginationQuery, WorkspacesLinesPaginationQuery$variables } from '@components/workspaces/__generated__/WorkspacesLinesPaginationQuery.graphql'; import { WorkspaceLineDummy, workspaceLineFragment } from '@components/workspaces/WorkspaceLine'; -import ToggleButton from '@mui/material/ToggleButton'; -import Tooltip from '@mui/material/Tooltip'; -import { ViewListOutlined } from '@mui/icons-material'; import { WorkspacesLines_data$data } from '@components/workspaces/__generated__/WorkspacesLines_data.graphql'; import WorkspacePopover from '@components/workspaces/WorkspacePopover'; import ListLines from '../../../components/list_lines/ListLines'; @@ -130,13 +127,6 @@ const Workspaces: FunctionComponent = ({ lineFragment={workspaceLineFragment} entityTypes={['Workspace']} searchContextFinal={{ entityTypes: ['Workspace'] }} - additionalHeaderButtons={[ - - - - - , - ]} createButton={isFeatureEnable('FAB_REPLACEMENT') && ( { lineFragment={publicDashboardFragment} entityTypes={['PublicDashboard']} searchContextFinal={{ entityTypes: ['PublicDashboard'] }} - additionalHeaderButtons={[ - - - - - , - ]} actions={(row) => ( dateAttribute: string + rootRef: DataTableProps['rootRef'] } const PublicStixCoreObjectsListComponent = ({ queryRef, dateAttribute, + rootRef, }: PublicStixCoreObjectsListComponentProps) => { const { publicStixCoreObjects } = usePreloadedQuery( publicStixCoreObjectsListQuery, @@ -221,12 +224,15 @@ const PublicStixCoreObjectsListComponent = ({ data={[...publicStixCoreObjects.edges]} dateAttribute={dateAttribute} publicWidget + rootRef={rootRef} /> ); } return ; }; +PublicStixCoreObjectsListComponent.displayName = 'PublicStixCoreObjectsListComponent'; + const PublicStixCoreObjectsList = ({ uriKey, widget, @@ -248,16 +254,20 @@ const PublicStixCoreObjectsList = ({ const dateAttribute = dataSelection[0].date_attribute ?? 'created_at'; + const rootRef = useRef(null); + return ( {queryRef ? ( }> ) : ( diff --git a/opencti-platform/opencti-front/src/utils/filters/filtersUtils.tsx b/opencti-platform/opencti-front/src/utils/filters/filtersUtils.tsx index c2fd13d4bd50..724fa99e4d93 100644 --- a/opencti-platform/opencti-front/src/utils/filters/filtersUtils.tsx +++ b/opencti-platform/opencti-front/src/utils/filters/filtersUtils.tsx @@ -96,7 +96,7 @@ export const stixFilters = [ // utilities export const isFilterGroupNotEmpty = (filterGroup?: FilterGroup | null) => { - return ( + return !!( filterGroup && (filterGroup.filters?.length > 0 || filterGroup.filterGroups?.length > 0) ); diff --git a/opencti-platform/opencti-front/src/utils/utils.ts b/opencti-platform/opencti-front/src/utils/utils.ts index 33f9d78bc471..7437fbc56f37 100644 --- a/opencti-platform/opencti-front/src/utils/utils.ts +++ b/opencti-platform/opencti-front/src/utils/utils.ts @@ -90,3 +90,13 @@ export const getPaddingRight = (locationPath: string, entityId: string, entityTy } return paddingRight; }; + +export const throttle = (callback: (...a: unknown[]) => unknown, wait: number) => { + let timeoutId: number; + return (...args: unknown[]) => { + window.clearTimeout(timeoutId); + timeoutId = window.setTimeout(() => { + callback(...args); + }, wait); + }; +}; diff --git a/opencti-platform/opencti-front/tests_e2e/model/form/reportForm.pageModel.ts b/opencti-platform/opencti-front/tests_e2e/model/form/reportForm.pageModel.ts index f5d4b2302c5f..bc0f751e60d8 100644 --- a/opencti-platform/opencti-front/tests_e2e/model/form/reportForm.pageModel.ts +++ b/opencti-platform/opencti-front/tests_e2e/model/form/reportForm.pageModel.ts @@ -6,7 +6,7 @@ import TextFieldPageModel from '../field/TextField.pageModel'; import FileFieldPageModel from '../field/FileField.pageModel'; export default class ReportFormPage { - nameField = new TextFieldPageModel(this.page, 'Name', 'text'); + nameField = new TextFieldPageModel(this.page, 'Name', 'text-no-label'); contentField = new TextFieldPageModel(this.page, 'Content', 'rich-content'); descriptionField = new TextFieldPageModel(this.page, 'Description', 'text-area');