diff --git a/opencti-platform/opencti-front/src/components/dashboard/WidgetBookmarks.tsx b/opencti-platform/opencti-front/src/components/dashboard/WidgetBookmarks.tsx new file mode 100644 index 000000000000..2c5bfb5df2d2 --- /dev/null +++ b/opencti-platform/opencti-front/src/components/dashboard/WidgetBookmarks.tsx @@ -0,0 +1,83 @@ +import Grid from '@mui/material/Grid'; +import Card from '@mui/material/Card'; +import CardActionArea from '@mui/material/CardActionArea'; +import { Link } from 'react-router-dom'; +import CardHeader from '@mui/material/CardHeader'; +import Avatar from '@mui/material/Avatar'; +import React from 'react'; +import { useTheme } from '@mui/styles'; +import ItemIcon from '../ItemIcon'; +import { resolveLink } from '../../utils/Entity'; +import { useFormatter } from '../i18n'; +import type { Theme } from '../Theme'; + +interface WidgetBookmarksProps { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + bookmarks: any[] +} + +const WidgetBookmarks = ({ bookmarks }: WidgetBookmarksProps) => { + const theme = useTheme(); + const { t_i18n, fsd } = useFormatter(); + + return ( +
+ + {bookmarks.map((bookmarkEdge) => { + const bookmark = bookmarkEdge.node; + const link = resolveLink(bookmark.entity_type); + return ( + + + + + + + )} + title={bookmark.name} + subheader={`${t_i18n('Updated on')} ${fsd(bookmark.modified)}`} + /> + + + + ); + })} + +
+ ); +}; + +export default WidgetBookmarks; diff --git a/opencti-platform/opencti-front/src/components/dashboard/WidgetContainer.tsx b/opencti-platform/opencti-front/src/components/dashboard/WidgetContainer.tsx index 1b5d06d28ccc..2c918c67b1a3 100644 --- a/opencti-platform/opencti-front/src/components/dashboard/WidgetContainer.tsx +++ b/opencti-platform/opencti-front/src/components/dashboard/WidgetContainer.tsx @@ -16,7 +16,7 @@ const useStyles = makeStyles({ interface WidgetContainerProps { children: ReactNode height?: CSSProperties['height'] - title: string + title?: string variant: string withoutTitle?: boolean } diff --git a/opencti-platform/opencti-front/src/components/dashboard/WidgetDistributionList.tsx b/opencti-platform/opencti-front/src/components/dashboard/WidgetDistributionList.tsx new file mode 100644 index 000000000000..3abb52ebba95 --- /dev/null +++ b/opencti-platform/opencti-front/src/components/dashboard/WidgetDistributionList.tsx @@ -0,0 +1,114 @@ +import List from '@mui/material/List'; +import { Link } from 'react-router-dom'; +import ListItemIcon from '@mui/material/ListItemIcon'; +import ListItemText from '@mui/material/ListItemText'; +import React from 'react'; +import { useTheme } from '@mui/styles'; +import { ListItemButton } from '@mui/material'; +import ItemIcon from '../ItemIcon'; +import { computeLink } from '../../utils/Entity'; +import type { Theme } from '../Theme'; +import { useFormatter } from '../i18n'; + +interface WidgetDistributionListProps { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + data: any[] + hasSettingAccess?: boolean + overflow?: string +} + +const WidgetDistributionList = ({ + data, + hasSettingAccess = false, + overflow = 'auto', +}: WidgetDistributionListProps) => { + const theme = useTheme(); + const { n } = useFormatter(); + + return ( +
+ + {data.map((entry, key) => { + let link: string | null = null; + if (entry.type !== 'User' || hasSettingAccess) { + link = entry.id ? computeLink(entry) : null; + } + let linkProps = {}; + if (link) { + linkProps = { + component: Link, + to: link, + }; + } + + return ( + + + + + + {entry.label} +
+ } + /> +
+ {n(entry.value)} +
+ + ); + })} + + + ); +}; + +export default WidgetDistributionList; diff --git a/opencti-platform/opencti-front/src/components/dashboard/WidgetDonut.tsx b/opencti-platform/opencti-front/src/components/dashboard/WidgetDonut.tsx new file mode 100644 index 000000000000..4a4beee20b63 --- /dev/null +++ b/opencti-platform/opencti-front/src/components/dashboard/WidgetDonut.tsx @@ -0,0 +1,72 @@ +import Chart from '@components/common/charts/Chart'; +import React from 'react'; +import { useTheme } from '@mui/styles'; +import type { ApexOptions } from 'apexcharts'; +import { defaultValue } from '../../utils/Graph'; +import { donutChartOptions } from '../../utils/Charts'; +import { useFormatter } from '../i18n'; +import type { Theme } from '../Theme'; + +interface WidgetDonutProps { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + data: any[] + groupBy: string + withExport?: boolean + readonly?: boolean +} + +const WidgetDonut = ({ + data, + groupBy, + withExport = false, + readonly = false, +}: WidgetDonutProps) => { + const theme = useTheme(); + const { t_i18n } = useFormatter(); + + const chartData = data.map((n) => n.value); + // eslint-disable-next-line no-nested-ternary + const labels = data.map((n) => (groupBy.endsWith('_id') + ? defaultValue(n.entity) + : groupBy === 'entity_type' && t_i18n(`entity_${n.label}`) !== `entity_${n.label}` + ? t_i18n(`entity_${n.label}`) + : n.label)); + + let chartColors = []; + if (data.at(0)?.entity?.color) { + chartColors = data.map((n) => (theme.palette.mode === 'light' && n.entity.color === '#ffffff' + ? '#000000' + : n.entity.color)); + } + if (data.at(0)?.entity?.x_opencti_color) { + chartColors = data.map((n) => (theme.palette.mode === 'light' + && n.entity.x_opencti_color === '#ffffff' + ? '#000000' + : n.entity.x_opencti_color)); + } + if (data.at(0)?.entity?.template?.color) { + chartColors = data.map((n) => (theme.palette.mode === 'light' && n.entity.template.color === '#ffffff' + ? '#000000' + : n.entity.template.color)); + } + + return ( + + ); +}; + +export default WidgetDonut; diff --git a/opencti-platform/opencti-front/src/components/dashboard/WidgetHorizontalBars.tsx b/opencti-platform/opencti-front/src/components/dashboard/WidgetHorizontalBars.tsx new file mode 100644 index 000000000000..060d6049c0da --- /dev/null +++ b/opencti-platform/opencti-front/src/components/dashboard/WidgetHorizontalBars.tsx @@ -0,0 +1,64 @@ +import Chart from '@components/common/charts/Chart'; +import React from 'react'; +import { useTheme } from '@mui/styles'; +import { useNavigate } from 'react-router-dom-v5-compat'; +import { ApexOptions } from 'apexcharts'; +import { horizontalBarsChartOptions } from '../../utils/Charts'; +import { simpleNumberFormat } from '../../utils/Number'; +import type { Theme } from '../Theme'; + +interface WidgetHorizontalBarsProps { + series: ApexAxisChartSeries + distributed?: boolean + stacked?: boolean + total?: boolean + legend?: boolean + categories?: string[] + withExport?: boolean + readonly?: boolean + redirectionUtils?: { + id?: string + entity_type?: string + }[] +} + +const WidgetHorizontalBars = ({ + series, + distributed, + stacked, + total, + legend, + categories, + withExport, + readonly, + redirectionUtils, +}: WidgetHorizontalBarsProps) => { + const theme = useTheme(); + const navigate = useNavigate(); + + return ( + + ); +}; + +export default WidgetHorizontalBars; diff --git a/opencti-platform/opencti-front/src/components/dashboard/WidgetListCoreObjects.tsx b/opencti-platform/opencti-front/src/components/dashboard/WidgetListCoreObjects.tsx new file mode 100644 index 000000000000..d79fa4fc0537 --- /dev/null +++ b/opencti-platform/opencti-front/src/components/dashboard/WidgetListCoreObjects.tsx @@ -0,0 +1,118 @@ +import List from '@mui/material/List'; +import ListItem from '@mui/material/ListItem'; +import { Link } from 'react-router-dom'; +import ListItemIcon from '@mui/material/ListItemIcon'; +import ListItemText from '@mui/material/ListItemText'; +import * as R from 'ramda'; +import StixCoreObjectLabels from '@components/common/stix_core_objects/StixCoreObjectLabels'; +import React, { CSSProperties } from 'react'; +import { resolveLink } from '../../utils/Entity'; +import ItemIcon from '../ItemIcon'; +import { defaultValue } from '../../utils/Graph'; +import ItemStatus from '../ItemStatus'; +import ItemMarkings from '../ItemMarkings'; +import { useFormatter } from '../i18n'; + +const bodyItemStyle = (width: string): CSSProperties => ({ + height: 20, + fontSize: 13, + float: 'left', + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis', + paddingRight: 10, + width, +}); + +interface WidgetListCoreObjectsProps { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + data: any[] + dateAttribute: string +} + +const WidgetListCoreObjects = ({ + data, + dateAttribute, +}: WidgetListCoreObjectsProps) => { + const { fsd } = useFormatter(); + + return ( +
+ + {data.map((stixCoreObjectEdge) => { + const stixCoreObject = stixCoreObjectEdge.node; + const date = stixCoreObject[dateAttribute]; + + return ( + + + + + +
+ {defaultValue(stixCoreObject)} +
+
+ {fsd(date)} +
+
+ {R.pathOr( + '', + ['createdBy', 'name'], + stixCoreObject, + )} +
+
+ +
+
+ +
+
+ +
+ + } + /> +
+ ); + })} +
+
+ ); +}; + +export default WidgetListCoreObjects; diff --git a/opencti-platform/opencti-front/src/components/dashboard/WidgetListRelationships.tsx b/opencti-platform/opencti-front/src/components/dashboard/WidgetListRelationships.tsx new file mode 100644 index 000000000000..244b9a374045 --- /dev/null +++ b/opencti-platform/opencti-front/src/components/dashboard/WidgetListRelationships.tsx @@ -0,0 +1,186 @@ +import List from '@mui/material/List'; +import { Link } from 'react-router-dom'; +import ListItemIcon from '@mui/material/ListItemIcon'; +import ListItemText from '@mui/material/ListItemText'; +import * as R from 'ramda'; +import React, { CSSProperties } from 'react'; +import { useTheme } from '@mui/styles'; +import { ListItemButton } from '@mui/material'; +import ItemIcon from '../ItemIcon'; +import { defaultValue } from '../../utils/Graph'; +import ItemMarkings from '../ItemMarkings'; +import { computeLink } from '../../utils/Entity'; +import type { Theme } from '../Theme'; +import { useFormatter } from '../i18n'; + +const bodyItemStyle = (width: string): CSSProperties => ({ + height: 20, + fontSize: 13, + float: 'left', + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis', + paddingRight: 2, + width, +}); + +interface WidgetListRelationshipsProps { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + data: any[] + dateAttribute: string +} + +const WidgetListRelationships = ({ + data, + dateAttribute, +}: WidgetListRelationshipsProps) => { + const theme = useTheme(); + const { fsd, t_i18n } = useFormatter(); + + return ( +
+ + {data.map((stixRelationshipEdge) => { + const stixRelationship = stixRelationshipEdge.node; + const remoteNode = stixRelationship.from + ? stixRelationship.from + : stixRelationship.to; + let link = null; + if (remoteNode) { + link = computeLink(remoteNode); + } + let linkProps = {}; + if (link) { + linkProps = { + component: Link, + to: link, + }; + } + + return ( + + + + + +
+ + {/* eslint-disable-next-line no-nested-ternary */} + {stixRelationship.from + ? stixRelationship.from.relationship_type + ? t_i18n( + `relationship_${stixRelationship.from.entity_type}`, + ) + : t_i18n( + `entity_${stixRelationship.from.entity_type}`, + ) + : t_i18n('Restricted')} +
+
+ + {stixRelationship.from + ? defaultValue(stixRelationship.from) + : t_i18n('Restricted')} + +
+
+ + {t_i18n( + `relationship_${stixRelationship.relationship_type}`, + )} + +
+
+ + {/* eslint-disable-next-line no-nested-ternary */} + {stixRelationship.to + ? stixRelationship.to.relationship_type + ? t_i18n( + `relationship_${stixRelationship.to.entity_type}`, + ) + : t_i18n( + `entity_${stixRelationship.to.entity_type}`, + ) + : t_i18n('Restricted')} +
+
+ + {stixRelationship.to + ? defaultValue(stixRelationship.to) + : t_i18n('Restricted')} + +
+
+ {fsd(stixRelationship[dateAttribute])} +
+
+ {R.pathOr( + '', + ['createdBy', 'name'], + stixRelationship, + )} +
+
+ +
+
+ } + /> + + ); + })} + + + ); +}; + +export default WidgetListRelationships; diff --git a/opencti-platform/opencti-front/src/components/dashboard/WidgetMultiAreas.tsx b/opencti-platform/opencti-front/src/components/dashboard/WidgetMultiAreas.tsx new file mode 100644 index 000000000000..61f93424baa1 --- /dev/null +++ b/opencti-platform/opencti-front/src/components/dashboard/WidgetMultiAreas.tsx @@ -0,0 +1,59 @@ +import { useTheme } from '@mui/styles'; +import Chart from '@components/common/charts/Chart'; +import { ApexOptions } from 'apexcharts'; +import React from 'react'; +import type { Theme } from '../Theme'; +import { useFormatter } from '../i18n'; +import { areaChartOptions } from '../../utils/Charts'; +import { simpleNumberFormat } from '../../utils/Number'; + +interface WidgetMultiAreasProps { + series: ApexAxisChartSeries + interval?: string + isStacked?: boolean + hasLegend?: boolean + withExport?: boolean + readonly?: boolean +} + +const WidgetMultiAreas = ({ + series, + interval, + isStacked = false, + hasLegend = false, + withExport = false, + readonly = false, +}: WidgetMultiAreasProps) => { + const theme = useTheme(); + const { fsd, mtdy, yd } = useFormatter(); + + let formatter = fsd; + if (interval === 'month' || interval === 'quarter') { + formatter = mtdy; + } + if (interval === 'year') { + formatter = yd; + } + + return ( + + ); +}; + +export default WidgetMultiAreas; diff --git a/opencti-platform/opencti-front/src/components/dashboard/WidgetMultiHeatMap.tsx b/opencti-platform/opencti-front/src/components/dashboard/WidgetMultiHeatMap.tsx new file mode 100644 index 000000000000..bc39e22239a2 --- /dev/null +++ b/opencti-platform/opencti-front/src/components/dashboard/WidgetMultiHeatMap.tsx @@ -0,0 +1,100 @@ +import Chart from '@components/common/charts/Chart'; +import React from 'react'; +import { useTheme } from '@mui/styles'; +import { ApexOptions } from 'apexcharts'; +import { heatMapOptions } from '../../utils/Charts'; +import { useFormatter } from '../i18n'; +import type { Theme } from '../Theme'; + +const darkColors = [ + '#001e3c', + '#023362', + '#02407a', + '#045198', + '#0561b4', + '#0b75d9', + '#2986e7', + '#3a95f3', + '#4da3ff', + '#76bbff', + '#9eccff', +]; + +const lightColors = [ + '#f3f6f9', + '#76bbff', + '#4da3ff', + '#3a95f3', + '#2986e7', + '#0b75d9', + '#0561b4', + '#02407a', + '#023362', + '#001e3c', + '#021428', +]; + +interface WidgetMultiHeatMapProps { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + data: any[] + minValue: number + maxValue: number + isStacked?: boolean + withExport?: boolean + readonly?: boolean +} + +const WidgetMultiHeatMap = ({ + data, + minValue, + maxValue, + isStacked = false, + withExport = false, + readonly = false, +}: WidgetMultiHeatMapProps) => { + const theme = useTheme(); + const { fsd } = useFormatter(); + + const interval = Math.trunc((maxValue - minValue) / 9); + const colorRanges = Array(10) + .fill(0) + .map((_, i) => ({ + from: + minValue + (i + 1) * interval - interval === 0 + ? 1 + : minValue + (i + 1) * interval - interval, + to: minValue + (i + 1) * interval, + color: + theme.palette.mode === 'dark' + ? darkColors[i + 1] + : lightColors[i + 1], + })); + colorRanges.push({ + from: 0, + to: 0, + color: + theme.palette.mode === 'dark' ? darkColors[0] : lightColors[0], + }); + + return ( + + ); +}; + +export default WidgetMultiHeatMap; diff --git a/opencti-platform/opencti-front/src/components/dashboard/WidgetMultiLines.tsx b/opencti-platform/opencti-front/src/components/dashboard/WidgetMultiLines.tsx new file mode 100644 index 000000000000..3ef20909951d --- /dev/null +++ b/opencti-platform/opencti-front/src/components/dashboard/WidgetMultiLines.tsx @@ -0,0 +1,57 @@ +import { useTheme } from '@mui/styles'; +import Chart from '@components/common/charts/Chart'; +import { ApexOptions } from 'apexcharts'; +import React from 'react'; +import type { Theme } from '../Theme'; +import { useFormatter } from '../i18n'; +import { lineChartOptions } from '../../utils/Charts'; +import { simpleNumberFormat } from '../../utils/Number'; + +interface WidgetMultiLinesProps { + series: ApexAxisChartSeries + interval?: string + hasLegend?: boolean + withExport?: boolean + readonly?: boolean +} + +const WidgetMultiLines = ({ + series, + interval, + hasLegend = false, + withExport = false, + readonly = false, +}: WidgetMultiLinesProps) => { + const theme = useTheme(); + const { fsd, mtdy, yd } = useFormatter(); + + let formatter = fsd; + if (interval === 'month' || interval === 'quarter') { + formatter = mtdy; + } + if (interval === 'year') { + formatter = yd; + } + + return ( + + ); +}; + +export default WidgetMultiLines; diff --git a/opencti-platform/opencti-front/src/components/dashboard/WidgetRadar.tsx b/opencti-platform/opencti-front/src/components/dashboard/WidgetRadar.tsx new file mode 100644 index 000000000000..c2d6b8148bf0 --- /dev/null +++ b/opencti-platform/opencti-front/src/components/dashboard/WidgetRadar.tsx @@ -0,0 +1,54 @@ +import Chart from '@components/common/charts/Chart'; +import React from 'react'; +import { useTheme } from '@mui/styles'; +import { ApexOptions } from 'apexcharts'; +import { radarChartOptions } from '../../utils/Charts'; +import { defaultValue } from '../../utils/Graph'; +import { useFormatter } from '../i18n'; +import type { Theme } from '../Theme'; + +interface WidgetRadarProps { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + data: any[] + label: string + groupBy: string + withExport?: boolean + readonly?: boolean +} + +const WidgetRadar = ({ + data, + label, + groupBy, + withExport = false, + readonly = false, +}: WidgetRadarProps) => { + const theme = useTheme(); + const { t_i18n } = useFormatter(); + + const chartData = [{ + name: label || t_i18n('Number of relationships'), + data: data.map((n) => n.value), + }]; + + // eslint-disable-next-line no-nested-ternary,implicit-arrow-linebreak + const labels = data.map((n) => (groupBy.endsWith('_id') + ? defaultValue(n.entity) + : groupBy === 'entity_type' && t_i18n(`entity_${n.label}`) !== `entity_${n.label}` + ? t_i18n(`entity_${n.label}`) + : n.label)); + + return ( + + ); +}; + +export default WidgetRadar; diff --git a/opencti-platform/opencti-front/src/components/dashboard/WidgetTimeline.tsx b/opencti-platform/opencti-front/src/components/dashboard/WidgetTimeline.tsx new file mode 100644 index 000000000000..6f76d7a43d5f --- /dev/null +++ b/opencti-platform/opencti-front/src/components/dashboard/WidgetTimeline.tsx @@ -0,0 +1,88 @@ +import Timeline from '@mui/lab/Timeline'; +import TimelineItem from '@mui/lab/TimelineItem'; +import TimelineOppositeContent from '@mui/lab/TimelineOppositeContent'; +import TimelineSeparator from '@mui/lab/TimelineSeparator'; +import { Link } from 'react-router-dom'; +import TimelineDot from '@mui/lab/TimelineDot'; +import TimelineConnector from '@mui/lab/TimelineConnector'; +import TimelineContent from '@mui/lab/TimelineContent'; +import Paper from '@mui/material/Paper'; +import Typography from '@mui/material/Typography'; +import React from 'react'; +import { defaultValue } from '../../utils/Graph'; +import MarkdownDisplay from '../MarkdownDisplay'; +import ItemIcon from '../ItemIcon'; +import { itemColor } from '../../utils/Colors'; +import { useFormatter } from '../i18n'; + +interface WidgetTimelineProps { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + data: { value: any, link?: string }[] +} + +const WidgetTimeline = ({ data }: WidgetTimelineProps) => { + const { fldt } = useFormatter(); + + return ( +
+ + {data.map(({ value, link }) => { + return ( + + + {fldt(value.created)} + + + {link ? ( + + + + + + ) : ( + + + + )} + + + + + + {defaultValue(value)} + +
+ +
+
+
+
+ ); + })} +
+
+ ); +}; + +export default WidgetTimeline; diff --git a/opencti-platform/opencti-front/src/components/dashboard/WidgetTree.tsx b/opencti-platform/opencti-front/src/components/dashboard/WidgetTree.tsx new file mode 100644 index 000000000000..4672b6067834 --- /dev/null +++ b/opencti-platform/opencti-front/src/components/dashboard/WidgetTree.tsx @@ -0,0 +1,56 @@ +import Chart from '@components/common/charts/Chart'; +import React from 'react'; +import { useTheme } from '@mui/styles'; +import { ApexOptions } from 'apexcharts'; +import { useFormatter } from '../i18n'; +import { treeMapOptions } from '../../utils/Charts'; +import { defaultValue } from '../../utils/Graph'; + +interface WidgetTreeProps { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + data: any[] + groupBy: string + withExport?: boolean + readonly?: boolean + isDistributed?: boolean +} + +const WidgetTree = ({ + data, + groupBy, + withExport = false, + readonly = false, + isDistributed = false, +}: WidgetTreeProps) => { + const theme = useTheme(); + const { t_i18n } = useFormatter(); + + const chartData = data.map((n) => ({ + // eslint-disable-next-line no-nested-ternary + x: groupBy.endsWith('_id') + ? defaultValue(n.entity) + : groupBy === 'entity_type' && t_i18n(`entity_${n.label}`) !== `entity_${n.label}` + ? t_i18n(`entity_${n.label}`) + : n.label, + y: n.value, + })); + const series = [{ data: chartData }]; + + return ( + + ); +}; + +export default WidgetTree; diff --git a/opencti-platform/opencti-front/src/components/dashboard/WidgetVerticalBars.tsx b/opencti-platform/opencti-front/src/components/dashboard/WidgetVerticalBars.tsx new file mode 100644 index 000000000000..f9497d4b23d5 --- /dev/null +++ b/opencti-platform/opencti-front/src/components/dashboard/WidgetVerticalBars.tsx @@ -0,0 +1,60 @@ +import Chart from '@components/common/charts/Chart'; +import React from 'react'; +import { useTheme } from '@mui/styles'; +import { ApexOptions } from 'apexcharts'; +import { verticalBarsChartOptions } from '../../utils/Charts'; +import { simpleNumberFormat } from '../../utils/Number'; +import type { Theme } from '../Theme'; +import { useFormatter } from '../i18n'; + +interface WidgetVerticalBarsProps { + series: ApexAxisChartSeries + interval?: string + isStacked?: boolean + hasLegend?: boolean + withExport?: boolean + readonly?: boolean +} + +const WidgetVerticalBars = ({ + series, + interval, + isStacked = false, + hasLegend = false, + withExport = false, + readonly = false, +}: WidgetVerticalBarsProps) => { + const theme = useTheme(); + const { fsd, mtdy, yd } = useFormatter(); + + let formatter = fsd; + if (interval === 'month' || interval === 'quarter') { + formatter = mtdy; + } + if (interval === 'year') { + formatter = yd; + } + + return ( + + ); +}; + +export default WidgetVerticalBars; diff --git a/opencti-platform/opencti-front/src/private/components/common/audits/AuditsDistributionList.jsx b/opencti-platform/opencti-front/src/private/components/common/audits/AuditsDistributionList.jsx index 81451ad5e2d2..4ee8c0a11c2f 100644 --- a/opencti-platform/opencti-front/src/private/components/common/audits/AuditsDistributionList.jsx +++ b/opencti-platform/opencti-front/src/private/components/common/audits/AuditsDistributionList.jsx @@ -15,61 +15,15 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. import React from 'react'; import { graphql } from 'react-relay'; -import CircularProgress from '@mui/material/CircularProgress'; -import Paper from '@mui/material/Paper'; -import Typography from '@mui/material/Typography'; -import makeStyles from '@mui/styles/makeStyles'; -import List from '@mui/material/List'; -import ListItem from '@mui/material/ListItem'; -import { Link } from 'react-router-dom'; -import ListItemIcon from '@mui/material/ListItemIcon'; -import ListItemText from '@mui/material/ListItemText'; import { QueryRenderer } from '../../../../relay/environment'; import { useFormatter } from '../../../../components/i18n'; import { defaultValue } from '../../../../utils/Graph'; -import { resolveLink } from '../../../../utils/Entity'; -import ItemIcon from '../../../../components/ItemIcon'; import useGranted, { SETTINGS, SETTINGS_SETACCESSES } from '../../../../utils/hooks/useGranted'; import useEnterpriseEdition from '../../../../utils/hooks/useEnterpriseEdition'; - -const useStyles = makeStyles({ - container: { - width: '100%', - height: '100%', - overflow: 'auto', - paddingBottom: 10, - marginBottom: 10, - }, - paper: { - height: '100%', - margin: '10px 0 0 0', - padding: 0, - borderRadius: 4, - }, - item: { - height: 50, - minHeight: 50, - maxHeight: 50, - paddingRight: 0, - }, - itemText: { - whiteSpace: 'nowrap', - overflow: 'hidden', - textOverflow: 'ellipsis', - paddingRight: 10, - }, -}); - -const inlineStyles = { - itemNumber: { - float: 'right', - marginRight: 20, - fontSize: 20, - whiteSpace: 'nowrap', - overflow: 'hidden', - textOverflow: 'ellipsis', - }, -}; +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; +import WidgetDistributionList from '../../../../components/dashboard/WidgetDistributionList'; const auditsDistributionListDistributionQuery = graphql` query AuditsDistributionListDistributionQuery( @@ -257,8 +211,7 @@ const AuditsDistributionList = ({ dataSelection, parameters = {}, }) => { - const classes = useStyles(); - const { t_i18n, n } = useFormatter(); + const { t_i18n } = useFormatter(); const hasSetAccess = useGranted([SETTINGS_SETACCESSES]); const isGrantedToSettings = useGranted([SETTINGS]); const isEnterpriseEdition = useEnterpriseEdition(); @@ -329,100 +282,24 @@ const AuditsDistributionList = ({ ? o.entity.entity_type : o.label, })); - return ( -
- - {data.map((entry) => { - // eslint-disable-next-line no-nested-ternary - const link = entry.type === 'User' && !hasSetAccess - ? null - : entry.id - ? `${resolveLink(entry.type)}/${entry.id}` - : null; - return ( - - - - - - {entry.label} -
- } - /> -
- {n(entry.value)} -
- - ); - })} - - - ); + return ; } if (props) { - return ( -
- - {t_i18n('No entities of this type has been found.')} - -
- ); + return ; } - return ( -
- - - -
- ); + return ; }} /> ); }; return ( -
- - {parameters.title || t_i18n('Distribution of entities')} - - {variant === 'inLine' ? ( - renderContent() - ) : ( - - {renderContent()} - - )} -
+ + {renderContent()} + ); }; diff --git a/opencti-platform/opencti-front/src/private/components/common/audits/AuditsDonut.jsx b/opencti-platform/opencti-front/src/private/components/common/audits/AuditsDonut.jsx index 37e11472c9c3..f610b07e1a98 100644 --- a/opencti-platform/opencti-front/src/private/components/common/audits/AuditsDonut.jsx +++ b/opencti-platform/opencti-front/src/private/components/common/audits/AuditsDonut.jsx @@ -15,28 +15,15 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. import React from 'react'; import { graphql } from 'react-relay'; -import CircularProgress from '@mui/material/CircularProgress'; -import Paper from '@mui/material/Paper'; -import Typography from '@mui/material/Typography'; -import makeStyles from '@mui/styles/makeStyles'; -import { useTheme } from '@mui/styles'; import * as PropTypes from 'prop-types'; import { QueryRenderer } from '../../../../relay/environment'; import { useFormatter } from '../../../../components/i18n'; -import { donutChartOptions } from '../../../../utils/Charts'; -import { defaultValue } from '../../../../utils/Graph'; -import Chart from '../charts/Chart'; import useGranted, { SETTINGS } from '../../../../utils/hooks/useGranted'; import useEnterpriseEdition from '../../../../utils/hooks/useEnterpriseEdition'; - -const useStyles = makeStyles(() => ({ - paper: { - height: '100%', - margin: '10px 0 0 0', - padding: 0, - borderRadius: 4, - }, -})); +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; +import WidgetDonut from '../../../../components/dashboard/WidgetDonut'; const auditsDonutDistributionQuery = graphql` query AuditsDonutDistributionQuery( @@ -207,8 +194,6 @@ const AuditsDonut = ({ withExportPopover = false, isReadOnly = false, }) => { - const classes = useStyles(); - const theme = useTheme(); const { t_i18n } = useFormatter(); const isGrantedToSettings = useGranted([SETTINGS]); const isEnterpriseEdition = useEnterpriseEdition(); @@ -256,82 +241,31 @@ const AuditsDonut = ({ && props.auditsDistribution && props.auditsDistribution.length > 0 ) { - const data = props.auditsDistribution; - const chartData = data.map((n) => n.value); - // eslint-disable-next-line no-nested-ternary - const labels = data.map((n) => (selection.attribute.endsWith('.id') - || selection.attribute.endsWith('_id') - || selection.attribute.endsWith('_ids') - ? defaultValue(n.entity) - : selection.attribute === 'entity_type' - ? t_i18n(`entity_${n.label}`) - : n.label)); return ( - ); } if (props) { - return ( -
- - {t_i18n('No entities of this type has been found.')} - -
- ); + return ; } - return ( -
- - - -
- ); + return ; }} /> ); }; return ( -
- - {parameters.title || t_i18n('Distribution of history')} - - {variant === 'inLine' || variant === 'inEntity' ? ( - renderContent() - ) : ( - - {renderContent()} - - )} -
+ + {renderContent()} + ); }; diff --git a/opencti-platform/opencti-front/src/private/components/common/audits/AuditsList.jsx b/opencti-platform/opencti-front/src/private/components/common/audits/AuditsList.jsx index 42ba4e397e23..ca7170cc5fb8 100644 --- a/opencti-platform/opencti-front/src/private/components/common/audits/AuditsList.jsx +++ b/opencti-platform/opencti-front/src/private/components/common/audits/AuditsList.jsx @@ -15,9 +15,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. import React from 'react'; import { graphql } from 'react-relay'; -import CircularProgress from '@mui/material/CircularProgress'; -import Paper from '@mui/material/Paper'; -import Typography from '@mui/material/Typography'; import ListItem from '@mui/material/ListItem'; import { Link } from 'react-router-dom'; import ListItemIcon from '@mui/material/ListItemIcon'; @@ -34,6 +31,9 @@ import MarkdownDisplay from '../../../../components/MarkdownDisplay'; import { isNotEmptyField } from '../../../../utils/utils'; import useEnterpriseEdition from '../../../../utils/hooks/useEnterpriseEdition'; import { buildFiltersAndOptionsForWidgets } from '../../../../utils/filters/filtersUtils'; +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; const useStyles = makeStyles({ container: { @@ -255,59 +255,21 @@ const AuditsList = ({ ); } if (props) { - return ( -
- - {t_i18n('No entities of this type has been found.')} - -
- ); + return ; } - return ( -
- - - -
- ); + return ; }} /> ); }; return ( -
- - {parameters.title ?? t_i18n('Audits list')} - - {variant !== 'inLine' ? ( - - {renderContent()} - - ) : ( - renderContent() - )} -
+ + {renderContent()} + ); }; diff --git a/opencti-platform/opencti-front/src/private/components/common/audits/AuditsMultiAreaChart.jsx b/opencti-platform/opencti-front/src/private/components/common/audits/AuditsMultiAreaChart.jsx index 560101555c05..28987c7384d9 100644 --- a/opencti-platform/opencti-front/src/private/components/common/audits/AuditsMultiAreaChart.jsx +++ b/opencti-platform/opencti-front/src/private/components/common/audits/AuditsMultiAreaChart.jsx @@ -15,30 +15,16 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. import React from 'react'; import { graphql } from 'react-relay'; -import CircularProgress from '@mui/material/CircularProgress'; -import Paper from '@mui/material/Paper'; -import Typography from '@mui/material/Typography'; -import makeStyles from '@mui/styles/makeStyles'; -import { useTheme } from '@mui/styles'; -import Chart from '../charts/Chart'; import { QueryRenderer } from '../../../../relay/environment'; import { useFormatter } from '../../../../components/i18n'; import { monthsAgo, now } from '../../../../utils/Time'; -import { areaChartOptions } from '../../../../utils/Charts'; -import { simpleNumberFormat } from '../../../../utils/Number'; import useGranted, { SETTINGS } from '../../../../utils/hooks/useGranted'; import useEnterpriseEdition from '../../../../utils/hooks/useEnterpriseEdition'; import { removeEntityTypeAllFromFilterGroup } from '../../../../utils/filters/filtersUtils'; - -const useStyles = makeStyles(() => ({ - paper: { - minHeight: 280, - height: '100%', - margin: '4px 0 0 0', - padding: '0 0 10px 0', - borderRadius: 4, - }, -})); +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; +import WidgetMultiAreas from '../../../../components/dashboard/WidgetMultiAreas'; const auditsMultiAreaChartTimeSeriesQuery = graphql` query AuditsMultiAreaChartTimeSeriesQuery( @@ -73,9 +59,7 @@ const AuditsMultiAreaChart = ({ withExportPopover = false, isReadOnly = false, }) => { - const theme = useTheme(); - const classes = useStyles(); - const { t_i18n, fsd, mtdy, yd } = useFormatter(); + const { t_i18n } = useFormatter(); const isGrantedToSettings = useGranted([SETTINGS]); const isEnterpriseEdition = useEnterpriseEdition(); const renderContent = () => { @@ -108,13 +92,7 @@ const AuditsMultiAreaChart = ({ filters: removeEntityTypeAllFromFilterGroup(selection.filters), }; }); - let formatter = fsd; - if (parameters.interval === 'month' || parameters.interval === 'quarter') { - formatter = mtdy; - } - if (parameters.interval === 'year') { - formatter = yd; - } + return ( { if (props && props.auditsMultiTimeSeries) { return ( - ({ - name: selection.label ?? t_i18n('Number of history entries'), + name: selection.label || t_i18n('Number of history entries'), data: props.auditsMultiTimeSeries[i].data.map((entry) => ({ x: new Date(entry.date), y: entry.value, })), }))} - type="area" - width="100%" - height="100%" - withExportPopover={withExportPopover} - isReadOnly={isReadOnly} + interval={parameters.interval} + isStacked={parameters.stacked} + hasLegend={parameters.legend} + withExport={withExportPopover} + readonly={isReadOnly} /> ); } if (props) { - return ( -
- - {t_i18n('No entities of this type has been found.')} - -
- ); + return ; } - return ( -
- - - -
- ); + return ; }} /> ); }; return ( -
- - {parameters.title ?? t_i18n('Activity and history')} - - {variant !== 'inLine' ? ( - - {renderContent()} - - ) : ( - renderContent() - )} -
+ + {renderContent()} + ); }; diff --git a/opencti-platform/opencti-front/src/private/components/common/audits/AuditsMultiHeatMap.jsx b/opencti-platform/opencti-front/src/private/components/common/audits/AuditsMultiHeatMap.jsx index 2a139803a92c..2b9ca02c73ab 100644 --- a/opencti-platform/opencti-front/src/private/components/common/audits/AuditsMultiHeatMap.jsx +++ b/opencti-platform/opencti-front/src/private/components/common/audits/AuditsMultiHeatMap.jsx @@ -15,57 +15,16 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. import React from 'react'; import { graphql } from 'react-relay'; -import CircularProgress from '@mui/material/CircularProgress'; -import Paper from '@mui/material/Paper'; -import Typography from '@mui/material/Typography'; -import makeStyles from '@mui/styles/makeStyles'; -import { useTheme } from '@mui/styles'; -import Chart from '../charts/Chart'; import { QueryRenderer } from '../../../../relay/environment'; import { useFormatter } from '../../../../components/i18n'; import { monthsAgo, now } from '../../../../utils/Time'; -import { heatMapOptions } from '../../../../utils/Charts'; import useGranted, { SETTINGS } from '../../../../utils/hooks/useGranted'; import useEnterpriseEdition from '../../../../utils/hooks/useEnterpriseEdition'; import { removeEntityTypeAllFromFilterGroup } from '../../../../utils/filters/filtersUtils'; - -const useStyles = makeStyles(() => ({ - paper: { - minHeight: 280, - height: '100%', - margin: '4px 0 0 0', - padding: '0 0 10px 0', - borderRadius: 4, - }, -})); - -const darkColors = [ - '#001e3c', - '#023362', - '#02407a', - '#045198', - '#0561b4', - '#0b75d9', - '#2986e7', - '#3a95f3', - '#4da3ff', - '#76bbff', - '#9eccff', -]; - -const lightColors = [ - '#f3f6f9', - '#76bbff', - '#4da3ff', - '#3a95f3', - '#2986e7', - '#0b75d9', - '#0561b4', - '#02407a', - '#023362', - '#001e3c', - '#021428', -]; +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; +import WidgetMultiHeatMap from '../../../../components/dashboard/WidgetMultiHeatMap'; const auditsMultiHeatMapTimeSeriesQuery = graphql` query AuditsMultiHeatMapTimeSeriesQuery( @@ -100,9 +59,7 @@ const AuditsMultiHeatMap = ({ withExportPopover = false, isReadOnly = false, }) => { - const theme = useTheme(); - const classes = useStyles(); - const { t_i18n, fsd } = useFormatter(); + const { t_i18n } = useFormatter(); const isGrantedToSettings = useGranted([SETTINGS]); const isEnterpriseEdition = useEnterpriseEdition(); const renderContent = () => { @@ -149,7 +106,7 @@ const AuditsMultiHeatMap = ({ if (props && props.auditsMultiTimeSeries) { const chartData = dataSelection .map((selection, i) => ({ - name: selection.label ?? t_i18n('Number of history entries'), + name: selection.label || t_i18n('Number of history entries'), data: props.auditsMultiTimeSeries[i].data.map((entry) => ({ x: new Date(entry.date), y: entry.value, @@ -161,100 +118,34 @@ const AuditsMultiHeatMap = ({ .flat(); const maxValue = Math.max(...allValues); const minValue = Math.min(...allValues); - const interval = Math.trunc((maxValue - minValue) / 9); - const colorRanges = Array(10) - .fill(0) - .map((_, i) => ({ - from: - minValue + (i + 1) * interval - interval === 0 - ? 1 - : minValue + (i + 1) * interval - interval, - to: minValue + (i + 1) * interval, - color: - theme.palette.mode === 'dark' - ? darkColors[i + 1] - : lightColors[i + 1], - })); - colorRanges.push({ - from: 0, - to: 0, - color: - theme.palette.mode === 'dark' ? darkColors[0] : lightColors[0], - }); + return ( - ); } if (props) { - return ( -
- - {t_i18n('No entities of this type has been found.')} - -
- ); + return ; } - return ( -
- - - -
- ); + return ; }} /> ); }; return ( -
- - {parameters.title ?? t_i18n('Entities history')} - - {variant !== 'inLine' ? ( - - {renderContent()} - - ) : ( - renderContent() - )} -
+ + {renderContent()} + ); }; diff --git a/opencti-platform/opencti-front/src/private/components/common/audits/AuditsMultiLineChart.jsx b/opencti-platform/opencti-front/src/private/components/common/audits/AuditsMultiLineChart.jsx index 96d1e9d5b4fc..14f998283c0e 100644 --- a/opencti-platform/opencti-front/src/private/components/common/audits/AuditsMultiLineChart.jsx +++ b/opencti-platform/opencti-front/src/private/components/common/audits/AuditsMultiLineChart.jsx @@ -15,30 +15,16 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. import React from 'react'; import { graphql } from 'react-relay'; -import CircularProgress from '@mui/material/CircularProgress'; -import Paper from '@mui/material/Paper'; -import Typography from '@mui/material/Typography'; -import makeStyles from '@mui/styles/makeStyles'; -import { useTheme } from '@mui/styles'; -import Chart from '../charts/Chart'; import { QueryRenderer } from '../../../../relay/environment'; import { useFormatter } from '../../../../components/i18n'; import { monthsAgo, now } from '../../../../utils/Time'; -import { lineChartOptions } from '../../../../utils/Charts'; -import { simpleNumberFormat } from '../../../../utils/Number'; import useGranted, { SETTINGS } from '../../../../utils/hooks/useGranted'; import useEnterpriseEdition from '../../../../utils/hooks/useEnterpriseEdition'; import { removeEntityTypeAllFromFilterGroup } from '../../../../utils/filters/filtersUtils'; - -const useStyles = makeStyles(() => ({ - paper: { - minHeight: 280, - height: '100%', - margin: '4px 0 0 0', - padding: '0 0 10px 0', - borderRadius: 4, - }, -})); +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; +import WidgetMultiLines from '../../../../components/dashboard/WidgetMultiLines'; const auditsMultiLineChartTimeSeriesQuery = graphql` query AuditsMultiLineChartTimeSeriesQuery( @@ -73,9 +59,7 @@ const AuditsMultiLineChart = ({ withExportPopover = false, isReadOnly = false, }) => { - const theme = useTheme(); - const classes = useStyles(); - const { t_i18n, fsd, mtdy, yd } = useFormatter(); + const { t_i18n } = useFormatter(); const isGrantedToSettings = useGranted([SETTINGS]); const isEnterpriseEdition = useEnterpriseEdition(); const renderContent = () => { @@ -108,13 +92,7 @@ const AuditsMultiLineChart = ({ filters: removeEntityTypeAllFromFilterGroup(selection.filters), }; }); - let formatter = fsd; - if (parameters.interval === 'month' || parameters.interval === 'quarter') { - formatter = mtdy; - } - if (parameters.interval === 'year') { - formatter = yd; - } + return ( { if (props && props.auditsMultiTimeSeries) { return ( - ({ - name: selection.label ?? t_i18n('Number of history entries'), + name: selection.label || t_i18n('Number of history entries'), data: props.auditsMultiTimeSeries[i].data.map((entry) => ({ x: new Date(entry.date), y: entry.value, })), }))} - type="line" - width="100%" - height="100%" - withExportPopover={withExportPopover} - isReadOnly={isReadOnly} + interval={parameters.interval} + hasLegend={parameters.legend} + withExport={withExportPopover} + readonly={isReadOnly} /> ); } if (props) { - return ( -
- - {t_i18n('No entities of this type has been found.')} - -
- ); + return ; } - return ( -
- - - -
- ); + return ; }} /> ); }; return ( -
- - {parameters.title ?? t_i18n('Activity and history')} - - {variant !== 'inLine' ? ( - - {renderContent()} - - ) : ( - renderContent() - )} -
+ + {renderContent()} + ); }; diff --git a/opencti-platform/opencti-front/src/private/components/common/audits/AuditsMultiVerticalBars.jsx b/opencti-platform/opencti-front/src/private/components/common/audits/AuditsMultiVerticalBars.jsx index aadd86f0bd56..2724b66302ef 100644 --- a/opencti-platform/opencti-front/src/private/components/common/audits/AuditsMultiVerticalBars.jsx +++ b/opencti-platform/opencti-front/src/private/components/common/audits/AuditsMultiVerticalBars.jsx @@ -15,30 +15,16 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. import React from 'react'; import { graphql } from 'react-relay'; -import CircularProgress from '@mui/material/CircularProgress'; -import Paper from '@mui/material/Paper'; -import Typography from '@mui/material/Typography'; -import makeStyles from '@mui/styles/makeStyles'; -import { useTheme } from '@mui/styles'; -import Chart from '../charts/Chart'; import { QueryRenderer } from '../../../../relay/environment'; import { useFormatter } from '../../../../components/i18n'; import { monthsAgo, now } from '../../../../utils/Time'; -import { verticalBarsChartOptions } from '../../../../utils/Charts'; -import { simpleNumberFormat } from '../../../../utils/Number'; import useGranted, { SETTINGS } from '../../../../utils/hooks/useGranted'; import useEnterpriseEdition from '../../../../utils/hooks/useEnterpriseEdition'; import { removeEntityTypeAllFromFilterGroup } from '../../../../utils/filters/filtersUtils'; - -const useStyles = makeStyles(() => ({ - paper: { - minHeight: 280, - height: '100%', - margin: '4px 0 0 0', - padding: '0 0 10px 0', - borderRadius: 4, - }, -})); +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; +import WidgetVerticalBars from '../../../../components/dashboard/WidgetVerticalBars'; const auditsMultiVerticalBarsTimeSeriesQuery = graphql` query AuditsMultiVerticalBarsTimeSeriesQuery( @@ -73,9 +59,7 @@ const AuditsMultiVerticalBars = ({ withExportPopover = false, isReadOnly = false, }) => { - const theme = useTheme(); - const classes = useStyles(); - const { t_i18n, fsd, mtdy, yd } = useFormatter(); + const { t_i18n } = useFormatter(); const isGrantedToSettings = useGranted([SETTINGS]); const isEnterpriseEdition = useEnterpriseEdition(); const renderContent = () => { @@ -108,13 +92,7 @@ const AuditsMultiVerticalBars = ({ filters: removeEntityTypeAllFromFilterGroup(selection.filters), }; }); - let formatter = fsd; - if (parameters.interval === 'month' || parameters.interval === 'quarter') { - formatter = mtdy; - } - if (parameters.interval === 'year') { - formatter = yd; - } + return ( { if (props && props.auditsMultiTimeSeries) { return ( - ({ - name: selection.label ?? t_i18n('Number of history entries'), + name: selection.label || t_i18n('Number of history entries'), data: props.auditsMultiTimeSeries[i].data.map((entry) => ({ x: new Date(entry.date), y: entry.value, })), }))} - type="bar" - width="100%" - height="100%" - withExportPopover={withExportPopover} - isReadOnly={isReadOnly} + interval={parameters.interval} + isStacked={parameters.stacked} + hasLegend={parameters.legend} + withExport={withExportPopover} + readonly={isReadOnly} /> ); } if (props) { - return ( -
- - {t_i18n('No entities of this type has been found.')} - -
- ); + return ; } - return ( -
- - - -
- ); + return ; }} /> ); }; return ( -
- - {parameters.title ?? t_i18n('Activity and history')} - - {variant !== 'inLine' ? ( - - {renderContent()} - - ) : ( - renderContent() - )} -
+ + {renderContent()} + ); }; diff --git a/opencti-platform/opencti-front/src/private/components/common/audits/AuditsRadar.jsx b/opencti-platform/opencti-front/src/private/components/common/audits/AuditsRadar.jsx index c2e061cf3089..183a7a1f7cec 100644 --- a/opencti-platform/opencti-front/src/private/components/common/audits/AuditsRadar.jsx +++ b/opencti-platform/opencti-front/src/private/components/common/audits/AuditsRadar.jsx @@ -14,29 +14,15 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. */ import React from 'react'; -import * as R from 'ramda'; import { graphql } from 'react-relay'; -import CircularProgress from '@mui/material/CircularProgress'; -import Paper from '@mui/material/Paper'; -import Typography from '@mui/material/Typography'; -import makeStyles from '@mui/styles/makeStyles'; -import { useTheme } from '@mui/styles'; -import Chart from '../charts/Chart'; import { QueryRenderer } from '../../../../relay/environment'; import { useFormatter } from '../../../../components/i18n'; -import { radarChartOptions } from '../../../../utils/Charts'; import useGranted, { SETTINGS } from '../../../../utils/hooks/useGranted'; import useEnterpriseEdition from '../../../../utils/hooks/useEnterpriseEdition'; -import { defaultValue } from '../../../../utils/Graph'; - -const useStyles = makeStyles(() => ({ - paper: { - height: '100%', - margin: '10px 0 0 0', - padding: 0, - borderRadius: 4, - }, -})); +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; +import WidgetRadar from '../../../../components/dashboard/WidgetRadar'; const auditsRadarDistributionQuery = graphql` query AuditsRadarDistributionQuery( @@ -201,8 +187,6 @@ const AuditsRadar = ({ withExportPopover = false, isReadOnly = false, }) => { - const classes = useStyles(); - const theme = useTheme(); const { t_i18n } = useFormatter(); const isGrantedToSettings = useGranted([SETTINGS]); const isEnterpriseEdition = useEnterpriseEdition(); @@ -249,91 +233,32 @@ const AuditsRadar = ({ && props.auditsDistribution && props.auditsDistribution.length > 0 ) { - let data = props.auditsDistribution; - if ( - selection.attribute.endsWith('.id') - || selection.attribute.endsWith('_id') - || selection.attribute.endsWith('_ids') - ) { - data = R.map( - (n) => R.assoc('label', defaultValue(n.entity), n), - props.auditsDistribution, - ); - } - const valueData = data.map((n) => n.value); - const chartData = [ - { - name: selection.label || t_i18n('Number of history entries'), - data: valueData, - }, - ]; - const labels = data.map((n) => n.label); return ( - ); } if (props) { - return ( -
- - {t_i18n('No entities of this type has been found.')} - -
- ); + return ; } - return ( -
- - - -
- ); + return ; }} /> ); }; return ( -
- - {parameters.title || t_i18n('Distribution of entities')} - - {variant === 'inLine' ? ( - renderContent() - ) : ( - - {renderContent()} - - )} -
+ + {renderContent()} + ); }; diff --git a/opencti-platform/opencti-front/src/private/components/common/audits/AuditsTreeMap.jsx b/opencti-platform/opencti-front/src/private/components/common/audits/AuditsTreeMap.jsx index 0144015e0583..9f438873cd15 100644 --- a/opencti-platform/opencti-front/src/private/components/common/audits/AuditsTreeMap.jsx +++ b/opencti-platform/opencti-front/src/private/components/common/audits/AuditsTreeMap.jsx @@ -15,27 +15,14 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. import React from 'react'; import { graphql } from 'react-relay'; -import CircularProgress from '@mui/material/CircularProgress'; -import Paper from '@mui/material/Paper'; -import Typography from '@mui/material/Typography'; -import makeStyles from '@mui/styles/makeStyles'; -import { useTheme } from '@mui/styles'; -import Chart from '../charts/Chart'; import { QueryRenderer } from '../../../../relay/environment'; import { useFormatter } from '../../../../components/i18n'; -import { treeMapOptions } from '../../../../utils/Charts'; import useGranted, { SETTINGS } from '../../../../utils/hooks/useGranted'; import useEnterpriseEdition from '../../../../utils/hooks/useEnterpriseEdition'; -import { defaultValue } from '../../../../utils/Graph'; - -const useStyles = makeStyles(() => ({ - paper: { - height: '100%', - margin: '10px 0 0 0', - padding: 0, - borderRadius: 4, - }, -})); +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; +import WidgetTree from '../../../../components/dashboard/WidgetTree'; const auditsTreeMapDistributionQuery = graphql` query AuditsTreeMapDistributionQuery( @@ -199,8 +186,6 @@ const AuditsTreeMap = ({ withExportPopover = false, isReadOnly = false, }) => { - const classes = useStyles(); - const theme = useTheme(); const { t_i18n } = useFormatter(); const isGrantedToSettings = useGranted([SETTINGS]); const isEnterpriseEdition = useEnterpriseEdition(); @@ -248,88 +233,32 @@ const AuditsTreeMap = ({ && props.auditsDistribution.length > 0 ) { const data = props.auditsDistribution; - const chartData = data.map((n) => ({ - x: - // eslint-disable-next-line no-nested-ternary - selection.attribute.endsWith('_id') - || selection.attribute.endsWith('_ids') - ? defaultValue(n.entity) - : selection.attribute === 'entity_type' - ? t_i18n(`entity_${n.label}`) - : n.label, - y: n.value, - })); - const series = [{ data: chartData }]; return ( - ); } if (props) { - return ( -
- - {t_i18n('No entities of this type has been found.')} - -
- ); + return ; } - return ( -
- - - -
- ); + return ; }} /> ); }; return ( -
- - {parameters.title || t_i18n('Distribution of entities')} - - {variant === 'inLine' ? ( - renderContent() - ) : ( - - {renderContent()} - - )} -
+ + {renderContent()} + ); }; diff --git a/opencti-platform/opencti-front/src/private/components/common/location/LocationMiniMapTargets.jsx b/opencti-platform/opencti-front/src/private/components/common/location/LocationMiniMapTargets.jsx index d872ae0742f7..ebe2d9df9f29 100644 --- a/opencti-platform/opencti-front/src/private/components/common/location/LocationMiniMapTargets.jsx +++ b/opencti-platform/opencti-front/src/private/components/common/location/LocationMiniMapTargets.jsx @@ -10,6 +10,7 @@ import inject18n from '../../../../components/i18n'; import { UserContext } from '../../../../utils/hooks/useAuth'; import { fileUri } from '../../../../relay/environment'; import CityOrange from '../../../../static/images/leaflet/city_orange.png'; +import { usePublicSettings } from '../../../../public/PublicSettingsProvider'; const styles = () => ({ paper: { @@ -42,7 +43,10 @@ const pointerIcon = new L.Icon({ }); const LocationMiniMapTargets = (props) => { - const { settings } = useContext(UserContext); + const { settings: privateSettings } = useContext(UserContext); + const { settings: publicSettings } = usePublicSettings(); + const settings = privateSettings ?? publicSettings; + const countriesAliases = pipe( pluck('x_opencti_aliases'), flatten, diff --git a/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectsDistributionList.jsx b/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectsDistributionList.jsx index d410bf712b36..f45782f5159a 100644 --- a/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectsDistributionList.jsx +++ b/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectsDistributionList.jsx @@ -1,60 +1,13 @@ import React from 'react'; import { graphql } from 'react-relay'; -import CircularProgress from '@mui/material/CircularProgress'; -import Paper from '@mui/material/Paper'; -import Typography from '@mui/material/Typography'; -import makeStyles from '@mui/styles/makeStyles'; -import List from '@mui/material/List'; -import ListItem from '@mui/material/ListItem'; -import { Link } from 'react-router-dom'; -import ListItemIcon from '@mui/material/ListItemIcon'; -import ListItemText from '@mui/material/ListItemText'; -import { useTheme } from '@mui/styles'; import { QueryRenderer } from '../../../../relay/environment'; import { useFormatter } from '../../../../components/i18n'; -import { resolveLink } from '../../../../utils/Entity'; -import ItemIcon from '../../../../components/ItemIcon'; import useGranted, { SETTINGS_SETACCESSES } from '../../../../utils/hooks/useGranted'; import { buildFiltersAndOptionsForWidgets } from '../../../../utils/filters/filtersUtils'; - -const useStyles = makeStyles({ - container: { - width: '100%', - height: '100%', - overflow: 'auto', - paddingBottom: 10, - marginBottom: 10, - }, - paper: { - height: '100%', - margin: '10px 0 0 0', - padding: 0, - borderRadius: 4, - }, - item: { - height: 50, - minHeight: 50, - maxHeight: 50, - paddingRight: 0, - }, - itemText: { - whiteSpace: 'nowrap', - overflow: 'hidden', - textOverflow: 'ellipsis', - paddingRight: 10, - }, -}); - -const inlineStyles = { - itemNumber: { - float: 'right', - marginRight: 20, - fontSize: 20, - whiteSpace: 'nowrap', - overflow: 'hidden', - textOverflow: 'ellipsis', - }, -}; +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; +import WidgetDistributionList from '../../../../components/dashboard/WidgetDistributionList'; const stixCoreObjectsDistributionListDistributionQuery = graphql` query StixCoreObjectsDistributionListDistributionQuery( @@ -137,22 +90,13 @@ const StixCoreObjectsDistributionList = ({ dataSelection, parameters = {}, }) => { - const classes = useStyles(); - const theme = useTheme(); - const { t_i18n, n } = useFormatter(); + const { t_i18n } = useFormatter(); const hasSetAccess = useGranted([SETTINGS_SETACCESSES]); const renderContent = () => { const selection = dataSelection[0]; const dataSelectionTypes = ['Stix-Core-Object']; const { filters, dataSelectionElementId, dataSelectionToTypes } = buildFiltersAndOptionsForWidgets(selection.filters); - const computeLink = (entry) => { - const resolution = resolveLink(entry.type); - if (resolution) { - // Handle type with no link - return `${resolution}/${entry.id}`; - } - return null; - }; + return ( - - {data.map((entry) => { - // eslint-disable-next-line no-nested-ternary - const link = entry.type === 'User' && !hasSetAccess - ? null - : entry.id - ? computeLink(entry) - : null; - return ( - - - - - - {entry.label} - - } - /> -
- {n(entry.value)} -
-
- ); - })} -
- - ); + return ; } if (props) { - return ( -
- - {t_i18n('No entities of this type has been found.')} - -
- ); + return ; } - return ( -
- - - -
- ); + return ; }} /> ); }; return ( -
- - {parameters.title || t_i18n('Distribution of entities')} - - {variant === 'inLine' ? ( - renderContent() - ) : ( - - {renderContent()} - - )} -
+ + {renderContent()} + ); }; diff --git a/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectsDonut.jsx b/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectsDonut.jsx index aae1afd18078..06b03b5053db 100644 --- a/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectsDonut.jsx +++ b/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectsDonut.jsx @@ -1,25 +1,12 @@ import React from 'react'; import { graphql } from 'react-relay'; -import CircularProgress from '@mui/material/CircularProgress'; -import Paper from '@mui/material/Paper'; -import Typography from '@mui/material/Typography'; -import makeStyles from '@mui/styles/makeStyles'; -import { useTheme } from '@mui/styles'; import * as PropTypes from 'prop-types'; import { QueryRenderer } from '../../../../relay/environment'; import { useFormatter } from '../../../../components/i18n'; -import { donutChartOptions } from '../../../../utils/Charts'; -import { defaultValue } from '../../../../utils/Graph'; -import Chart from '../charts/Chart'; - -const useStyles = makeStyles(() => ({ - paper: { - height: '100%', - margin: '10px 0 0 0', - padding: 0, - borderRadius: 4, - }, -})); +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetDonut from '../../../../components/dashboard/WidgetDonut'; const stixCoreObjectsDonutDistributionQuery = graphql` query StixCoreObjectsDonutDistributionQuery( @@ -210,8 +197,6 @@ const StixCoreObjectsDonut = ({ withExportPopover = false, isReadOnly = false, }) => { - const classes = useStyles(); - const theme = useTheme(); const { t_i18n } = useFormatter(); const renderContent = () => { const selection = dataSelection[0]; @@ -239,103 +224,31 @@ const StixCoreObjectsDonut = ({ && props.stixCoreObjectsDistribution && props.stixCoreObjectsDistribution.length > 0 ) { - const data = props.stixCoreObjectsDistribution; - const chartData = data.map((n) => n.value); - // eslint-disable-next-line no-nested-ternary - const labels = data.map((n) => (selection.attribute.endsWith('_id') - ? defaultValue(n.entity) - : selection.attribute === 'entity_type' - ? t_i18n(`entity_${n.label}`) - : n.label)); - let chartColors = []; - if (data.at(0)?.entity?.color) { - chartColors = data.map((n) => (theme.palette.mode === 'light' && n.entity.color === '#ffffff' - ? '#000000' - : n.entity.color)); - } - if (data.at(0)?.entity?.x_opencti_color) { - chartColors = data.map((n) => (theme.palette.mode === 'light' - && n.entity.x_opencti_color === '#ffffff' - ? '#000000' - : n.entity.x_opencti_color)); - } - if (data.at(0)?.entity?.template?.color) { - chartColors = data.map((n) => (theme.palette.mode === 'light' && n.entity.template.color === '#ffffff' - ? '#000000' - : n.entity.template.color)); - } return ( - ); } if (props) { - return ( -
- - {t_i18n('No entities of this type has been found.')} - -
- ); + return ; } - return ( -
- - - -
- ); + return ; }} /> ); }; return ( -
- - {parameters.title || t_i18n('Distribution of entities')} - - {variant === 'inLine' || variant === 'inEntity' ? ( - renderContent() - ) : ( - - {renderContent()} - - )} -
+ + {renderContent()} + ); }; diff --git a/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectsHorizontalBars.jsx b/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectsHorizontalBars.jsx index 9102e5266a17..fc5db087e43d 100644 --- a/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectsHorizontalBars.jsx +++ b/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectsHorizontalBars.jsx @@ -1,28 +1,15 @@ import React from 'react'; import { graphql } from 'react-relay'; -import CircularProgress from '@mui/material/CircularProgress'; -import Paper from '@mui/material/Paper'; -import Typography from '@mui/material/Typography'; -import makeStyles from '@mui/styles/makeStyles'; import { useTheme } from '@mui/styles'; -import { useNavigate } from 'react-router-dom-v5-compat'; -import Chart from '../charts/Chart'; import { QueryRenderer } from '../../../../relay/environment'; import { useFormatter } from '../../../../components/i18n'; -import { horizontalBarsChartOptions } from '../../../../utils/Charts'; import { itemColor } from '../../../../utils/Colors'; -import { simpleNumberFormat } from '../../../../utils/Number'; import { defaultValue } from '../../../../utils/Graph'; import { buildFiltersAndOptionsForWidgets } from '../../../../utils/filters/filtersUtils'; - -const useStyles = makeStyles(() => ({ - paper: { - height: '100%', - margin: '10px 0 0 0', - padding: 0, - borderRadius: 4, - }, -})); +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; +import WidgetHorizontalBars from '../../../../components/dashboard/WidgetHorizontalBars'; const stixCoreObjectsHorizontalBarsDistributionQuery = graphql` query StixCoreObjectsHorizontalBarsDistributionQuery( @@ -222,10 +209,8 @@ const StixCoreObjectsHorizontalBars = ({ withExportPopover = false, isReadOnly = false, }) => { - const classes = useStyles(); const theme = useTheme(); const { t_i18n } = useFormatter(); - const navigate = useNavigate(); const renderContent = () => { const selection = dataSelection[0]; const dataSelectionTypes = ['Stix-Core-Object']; @@ -242,17 +227,17 @@ const StixCoreObjectsHorizontalBars = ({ startDate, endDate, dateAttribute: - selection.date_attribute && selection.date_attribute.length > 0 - ? selection.date_attribute - : 'created_at', + selection.date_attribute && selection.date_attribute.length > 0 + ? selection.date_attribute + : 'created_at', filters, limit: selection.number ?? 10, }} render={({ props }) => { if ( props - && props.stixCoreObjectsDistribution - && props.stixCoreObjectsDistribution.length > 0 + && props.stixCoreObjectsDistribution + && props.stixCoreObjectsDistribution.length > 0 ) { const data = props.stixCoreObjectsDistribution.map((n) => { let color = selection.attribute.endsWith('_id') @@ -276,13 +261,12 @@ const StixCoreObjectsHorizontalBars = ({ : n.entity.template.color; } return { - x: // eslint-disable-next-line no-nested-ternary - selection.attribute.endsWith('_id') - ? defaultValue(n.entity) - : selection.attribute === 'entity_type' - ? t_i18n(`entity_${n.label}`) - : n.label, + x: selection.attribute.endsWith('_id') + ? defaultValue(n.entity) + : selection.attribute === 'entity_type' + ? t_i18n(`entity_${n.label}`) + : n.label, y: n.value, fillColor: color, }; @@ -298,81 +282,33 @@ const StixCoreObjectsHorizontalBars = ({ id: n.entity.id, entity_type: n.entity.entity_type, })) - : null; + : undefined; return ( - ); } if (props) { - return ( -
- - {t_i18n('No entities of this type has been found.')} - -
- ); + return ; } - return ( -
- - - -
- ); + return ; }} /> ); }; return ( -
- - {parameters.title || t_i18n('Distribution of entities')} - - {variant === 'inLine' ? ( - renderContent() - ) : ( - - {renderContent()} - - )} -
+ + {renderContent()} + ); }; diff --git a/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectsList.jsx b/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectsList.jsx index 7854d484fb1b..f873508b3d01 100644 --- a/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectsList.jsx +++ b/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectsList.jsx @@ -1,53 +1,12 @@ import React from 'react'; -import * as R from 'ramda'; import { graphql } from 'react-relay'; -import CircularProgress from '@mui/material/CircularProgress'; -import Paper from '@mui/material/Paper'; -import Typography from '@mui/material/Typography'; -import ListItem from '@mui/material/ListItem'; -import { Link } from 'react-router-dom'; -import ListItemIcon from '@mui/material/ListItemIcon'; -import ListItemText from '@mui/material/ListItemText'; -import List from '@mui/material/List'; -import makeStyles from '@mui/styles/makeStyles'; -import StixCoreObjectLabels from './StixCoreObjectLabels'; -import ItemIcon from '../../../../components/ItemIcon'; import { useFormatter } from '../../../../components/i18n'; import { QueryRenderer } from '../../../../relay/environment'; -import { resolveLink } from '../../../../utils/Entity'; -import { defaultValue } from '../../../../utils/Graph'; -import ItemMarkings from '../../../../components/ItemMarkings'; -import ItemStatus from '../../../../components/ItemStatus'; import { buildFiltersAndOptionsForWidgets } from '../../../../utils/filters/filtersUtils'; - -const useStyles = makeStyles({ - container: { - width: '100%', - height: '100%', - overflow: 'auto', - paddingBottom: 10, - marginBottom: 10, - }, - paper: { - height: '100%', - margin: '10px 0 0 0', - padding: 0, - borderRadius: 4, - }, - item: { - paddingLeft: 10, - height: 50, - }, - bodyItem: { - height: 20, - fontSize: 13, - float: 'left', - whiteSpace: 'nowrap', - overflow: 'hidden', - textOverflow: 'ellipsis', - paddingRight: 10, - }, -}); +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; +import WidgetListCoreObjects from '../../../../components/dashboard/WidgetListCoreObjects'; const stixCoreObjectsListQuery = graphql` query StixCoreObjectsListQuery( @@ -251,8 +210,7 @@ const StixCoreObjectsList = ({ dataSelection, parameters = {}, }) => { - const classes = useStyles(); - const { t_i18n, fsd } = useFormatter(); + const { t_i18n } = useFormatter(); const renderContent = () => { const selection = dataSelection[0]; const dataSelectionTypes = ['Stix-Core-Object']; @@ -277,144 +235,24 @@ const StixCoreObjectsList = ({ && props.stixCoreObjects.edges.length > 0 ) { const data = props.stixCoreObjects.edges; - return ( -
- - {data.map((stixCoreObjectEdge) => { - const stixCoreObject = stixCoreObjectEdge.node; - return ( - - - - - -
- {defaultValue(stixCoreObject)} -
-
- {fsd(stixCoreObject[dateAttribute])} -
-
- {R.pathOr( - '', - ['createdBy', 'name'], - stixCoreObject, - )} -
-
- -
-
- -
-
- -
- - } - /> -
- ); - })} -
-
- ); + return ; } if (props) { - return ( -
- - {t_i18n('No entities of this type has been found.')} - -
- ); - } - return ( -
- - - -
- ); + return ; + } + return ; }} /> ); }; return ( -
- - {parameters.title || title || t_i18n('Entities list')} - - {variant !== 'inLine' ? ( - - {renderContent()} - - ) : ( - renderContent() - )} -
+ + {renderContent()} + ); }; diff --git a/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectsMultiAreaChart.jsx b/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectsMultiAreaChart.jsx index dcbfa2b24ee2..b2f8c053ce89 100644 --- a/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectsMultiAreaChart.jsx +++ b/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectsMultiAreaChart.jsx @@ -1,27 +1,13 @@ import React from 'react'; import { graphql } from 'react-relay'; -import CircularProgress from '@mui/material/CircularProgress'; -import Paper from '@mui/material/Paper'; -import Typography from '@mui/material/Typography'; -import makeStyles from '@mui/styles/makeStyles'; -import { useTheme } from '@mui/styles'; -import Chart from '../charts/Chart'; import { QueryRenderer } from '../../../../relay/environment'; import { useFormatter } from '../../../../components/i18n'; import { monthsAgo, now } from '../../../../utils/Time'; -import { areaChartOptions } from '../../../../utils/Charts'; -import { simpleNumberFormat } from '../../../../utils/Number'; import { buildFiltersAndOptionsForWidgets } from '../../../../utils/filters/filtersUtils'; - -const useStyles = makeStyles(() => ({ - paper: { - minHeight: 280, - height: '100%', - margin: '4px 0 0 0', - padding: '0 0 10px 0', - borderRadius: 4, - }, -})); +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; +import WidgetMultiAreas from '../../../../components/dashboard/WidgetMultiAreas'; const stixCoreObjectsMultiAreaChartTimeSeriesQuery = graphql` query StixCoreObjectsMultiAreaChartTimeSeriesQuery( @@ -54,9 +40,7 @@ const StixCoreObjectsMultiAreaChart = ({ withExportPopover = false, isReadOnly = false, }) => { - const theme = useTheme(); - const classes = useStyles(); - const { t_i18n, fsd, mtdy, yd } = useFormatter(); + const { t_i18n } = useFormatter(); const renderContent = () => { const timeSeriesParameters = dataSelection.map((selection) => { const dataSelectionTypes = ['Stix-Core-Object']; @@ -70,13 +54,7 @@ const StixCoreObjectsMultiAreaChart = ({ filters, }; }); - let formatter = fsd; - if (parameters.interval === 'month' || parameters.interval === 'quarter') { - formatter = mtdy; - } - if (parameters.interval === 'year') { - formatter = yd; - } + return ( { if (props && props.stixCoreObjectsMultiTimeSeries) { return ( - ({ - name: selection.label ?? t_i18n('Number of entities'), + name: selection.label || t_i18n('Number of entities'), data: props.stixCoreObjectsMultiTimeSeries[i].data.map( (entry) => ({ x: new Date(entry.date), @@ -112,68 +77,30 @@ const StixCoreObjectsMultiAreaChart = ({ }), ), }))} - type="area" - width="100%" - height="100%" - withExportPopover={withExportPopover} - isReadOnly={isReadOnly} + interval={parameters.interval} + isStacked={parameters.stacked} + hasLegend={parameters.legend} + withExport={withExportPopover} + readonly={isReadOnly} /> ); } if (props) { - return ( -
- - {t_i18n('No entities of this type has been found.')} - -
- ); + return ; } - return ( -
- - - -
- ); + return ; }} /> ); }; return ( -
- - {parameters.title ?? t_i18n('Entities history')} - - {variant !== 'inLine' ? ( - - {renderContent()} - - ) : ( - renderContent() - )} -
+ + {renderContent()} + ); }; diff --git a/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectsMultiHeatMap.jsx b/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectsMultiHeatMap.jsx index 3df25c2729a2..31e343843f6b 100644 --- a/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectsMultiHeatMap.jsx +++ b/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectsMultiHeatMap.jsx @@ -1,54 +1,13 @@ import React from 'react'; import { graphql } from 'react-relay'; -import CircularProgress from '@mui/material/CircularProgress'; -import Paper from '@mui/material/Paper'; -import Typography from '@mui/material/Typography'; -import makeStyles from '@mui/styles/makeStyles'; -import { useTheme } from '@mui/styles'; -import Chart from '../charts/Chart'; import { QueryRenderer } from '../../../../relay/environment'; import { useFormatter } from '../../../../components/i18n'; import { monthsAgo, now } from '../../../../utils/Time'; -import { heatMapOptions } from '../../../../utils/Charts'; import { removeEntityTypeAllFromFilterGroup } from '../../../../utils/filters/filtersUtils'; - -const useStyles = makeStyles(() => ({ - paper: { - minHeight: 280, - height: '100%', - margin: '4px 0 0 0', - padding: '0 0 10px 0', - borderRadius: 4, - }, -})); - -const darkColors = [ - '#001e3c', - '#023362', - '#02407a', - '#045198', - '#0561b4', - '#0b75d9', - '#2986e7', - '#3a95f3', - '#4da3ff', - '#76bbff', - '#9eccff', -]; - -const lightColors = [ - '#f3f6f9', - '#76bbff', - '#4da3ff', - '#3a95f3', - '#2986e7', - '#0b75d9', - '#0561b4', - '#02407a', - '#023362', - '#001e3c', - '#021428', -]; +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetMultiHeatMap from '../../../../components/dashboard/WidgetMultiHeatMap'; const stixCoreObjectsMultiHeatMapTimeSeriesQuery = graphql` query StixCoreObjectsMultiHeatMapTimeSeriesQuery( @@ -81,9 +40,7 @@ const StixCoreObjectsMultiHeatMap = ({ withExportPopover = false, isReadOnly = false, }) => { - const theme = useTheme(); - const classes = useStyles(); - const { t_i18n, fsd } = useFormatter(); + const { t_i18n } = useFormatter(); const renderContent = () => { const timeSeriesParameters = dataSelection.map((selection) => { return { @@ -108,7 +65,7 @@ const StixCoreObjectsMultiHeatMap = ({ if (props && props.stixCoreObjectsMultiTimeSeries) { const chartData = dataSelection .map((selection, i) => ({ - name: selection.label ?? t_i18n('Number of entities'), + name: selection.label || t_i18n('Number of entities'), data: props.stixCoreObjectsMultiTimeSeries[i].data.map( (entry) => ({ x: new Date(entry.date), @@ -122,100 +79,34 @@ const StixCoreObjectsMultiHeatMap = ({ .flat(); const maxValue = Math.max(...allValues); const minValue = Math.min(...allValues); - const interval = Math.trunc((maxValue - minValue) / 9); - const colorRanges = Array(10) - .fill(0) - .map((_, i) => ({ - from: - minValue + (i + 1) * interval - interval === 0 - ? 1 - : minValue + (i + 1) * interval - interval, - to: minValue + (i + 1) * interval, - color: - theme.palette.mode === 'dark' - ? darkColors[i + 1] - : lightColors[i + 1], - })); - colorRanges.push({ - from: 0, - to: 0, - color: - theme.palette.mode === 'dark' ? darkColors[0] : lightColors[0], - }); + return ( - ); } if (props) { - return ( -
- - {t_i18n('No entities of this type has been found.')} - -
- ); + return ; } - return ( -
- - - -
- ); + return ; }} /> ); }; return ( -
- - {parameters.title ?? t_i18n('Entities history')} - - {variant !== 'inLine' ? ( - - {renderContent()} - - ) : ( - renderContent() - )} -
+ + {renderContent()} + ); }; diff --git a/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectsMultiLineChart.jsx b/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectsMultiLineChart.jsx index 9c3dee966de7..9f2077e6b0d0 100644 --- a/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectsMultiLineChart.jsx +++ b/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectsMultiLineChart.jsx @@ -1,27 +1,13 @@ import React from 'react'; import { graphql } from 'react-relay'; -import CircularProgress from '@mui/material/CircularProgress'; -import Paper from '@mui/material/Paper'; -import Typography from '@mui/material/Typography'; -import makeStyles from '@mui/styles/makeStyles'; -import { useTheme } from '@mui/styles'; -import Chart from '../charts/Chart'; import { QueryRenderer } from '../../../../relay/environment'; import { useFormatter } from '../../../../components/i18n'; import { monthsAgo, now } from '../../../../utils/Time'; -import { lineChartOptions } from '../../../../utils/Charts'; -import { simpleNumberFormat } from '../../../../utils/Number'; import { buildFiltersAndOptionsForWidgets } from '../../../../utils/filters/filtersUtils'; - -const useStyles = makeStyles(() => ({ - paper: { - minHeight: 280, - height: '100%', - margin: '4px 0 0 0', - padding: '0 0 10px 0', - borderRadius: 4, - }, -})); +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; +import WidgetMultiLines from '../../../../components/dashboard/WidgetMultiLines'; const stixCoreObjectsMultiLineChartTimeSeriesQuery = graphql` query StixCoreObjectsMultiLineChartTimeSeriesQuery( @@ -54,9 +40,7 @@ const StixCoreObjectsMultiLineChart = ({ withExportPopover = false, isReadOnly = false, }) => { - const theme = useTheme(); - const classes = useStyles(); - const { t_i18n, fsd, mtdy, yd } = useFormatter(); + const { t_i18n } = useFormatter(); const renderContent = () => { const timeSeriesParameters = dataSelection.map((selection) => { const dataSelectionTypes = ['Stix-Core-Object']; @@ -70,13 +54,7 @@ const StixCoreObjectsMultiLineChart = ({ filters, }; }); - let formatter = fsd; - if (parameters.interval === 'month' || parameters.interval === 'quarter') { - formatter = mtdy; - } - if (parameters.interval === 'year') { - formatter = yd; - } + return ( { if (props && props.stixCoreObjectsMultiTimeSeries) { return ( - ({ - name: selection.label ?? t_i18n('Number of entities'), + name: selection.label || t_i18n('Number of entities'), data: props.stixCoreObjectsMultiTimeSeries[i].data.map( (entry) => ({ x: new Date(entry.date), @@ -112,68 +77,29 @@ const StixCoreObjectsMultiLineChart = ({ }), ), }))} - type="line" - width="100%" - height="100%" - withExportPopover={withExportPopover} - isReadOnly={isReadOnly} + interval={parameters.interval} + hasLegend={parameters.legend} + withExport={withExportPopover} + readonly={isReadOnly} /> ); } if (props) { - return ( -
- - {t_i18n('No entities of this type has been found.')} - -
- ); + return ; } - return ( -
- - - -
- ); + return ; }} /> ); }; return ( -
- - {parameters.title ?? t_i18n('Entities history')} - - {variant !== 'inLine' ? ( - - {renderContent()} - - ) : ( - renderContent() - )} -
+ + {renderContent()} + ); }; diff --git a/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectsMultiVerticalBars.jsx b/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectsMultiVerticalBars.jsx index daf328621879..13435e135c01 100644 --- a/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectsMultiVerticalBars.jsx +++ b/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectsMultiVerticalBars.jsx @@ -1,27 +1,13 @@ import React from 'react'; import { graphql } from 'react-relay'; -import CircularProgress from '@mui/material/CircularProgress'; -import Paper from '@mui/material/Paper'; -import Typography from '@mui/material/Typography'; -import makeStyles from '@mui/styles/makeStyles'; -import { useTheme } from '@mui/styles'; -import Chart from '../charts/Chart'; import { QueryRenderer } from '../../../../relay/environment'; import { useFormatter } from '../../../../components/i18n'; import { monthsAgo, now } from '../../../../utils/Time'; -import { verticalBarsChartOptions } from '../../../../utils/Charts'; -import { simpleNumberFormat } from '../../../../utils/Number'; import { buildFiltersAndOptionsForWidgets } from '../../../../utils/filters/filtersUtils'; - -const useStyles = makeStyles(() => ({ - paper: { - minHeight: 280, - height: '100%', - margin: '4px 0 0 0', - padding: '0 0 10px 0', - borderRadius: 4, - }, -})); +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; +import WidgetVerticalBars from '../../../../components/dashboard/WidgetVerticalBars'; const stixCoreObjectsMultiVerticalBarsTimeSeriesQuery = graphql` query StixCoreObjectsMultiVerticalBarsTimeSeriesQuery( @@ -54,9 +40,7 @@ const StixCoreObjectsMultiVerticalBars = ({ withExportPopover = false, isReadOnly = false, }) => { - const theme = useTheme(); - const classes = useStyles(); - const { t_i18n, fsd, mtdy, yd } = useFormatter(); + const { t_i18n } = useFormatter(); const renderContent = () => { const timeSeriesParameters = dataSelection.map((selection) => { const dataSelectionTypes = ['Stix-Core-Object']; @@ -70,13 +54,7 @@ const StixCoreObjectsMultiVerticalBars = ({ filters, }; }); - let formatter = fsd; - if (parameters.interval === 'month' || parameters.interval === 'quarter') { - formatter = mtdy; - } - if (parameters.interval === 'year') { - formatter = yd; - } + return ( { if (props && props.stixCoreObjectsMultiTimeSeries) { return ( - ({ - name: selection.label ?? t_i18n('Number of entities'), + name: selection.label || t_i18n('Number of entities'), data: props.stixCoreObjectsMultiTimeSeries[i].data.map( (entry) => ({ x: new Date(entry.date), @@ -113,68 +77,30 @@ const StixCoreObjectsMultiVerticalBars = ({ }), ), }))} - type="bar" - width="100%" - height="100%" - withExportPopover={withExportPopover} - isReadOnly={isReadOnly} + interval={parameters.interval} + isStacked={parameters.stacked} + hasLegend={parameters.legend} + withExport={withExportPopover} + readonly={isReadOnly} /> ); } if (props) { - return ( -
- - {t_i18n('No entities of this type has been found.')} - -
- ); + return ; } - return ( -
- - - -
- ); + return ; }} /> ); }; return ( -
- - {parameters.title ?? t_i18n('Entities history')} - - {variant !== 'inLine' ? ( - - {renderContent()} - - ) : ( - renderContent() - )} -
+ + {renderContent()} + ); }; diff --git a/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectsRadar.jsx b/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectsRadar.jsx index 63d07a123c0f..bcd9a5ee9a87 100644 --- a/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectsRadar.jsx +++ b/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectsRadar.jsx @@ -1,25 +1,12 @@ import React from 'react'; import { graphql } from 'react-relay'; -import CircularProgress from '@mui/material/CircularProgress'; -import Paper from '@mui/material/Paper'; -import Typography from '@mui/material/Typography'; -import makeStyles from '@mui/styles/makeStyles'; -import { useTheme } from '@mui/styles'; -import Chart from '../charts/Chart'; import { QueryRenderer } from '../../../../relay/environment'; import { useFormatter } from '../../../../components/i18n'; -import { radarChartOptions } from '../../../../utils/Charts'; -import { defaultValue } from '../../../../utils/Graph'; import { buildFiltersAndOptionsForWidgets } from '../../../../utils/filters/filtersUtils'; - -const useStyles = makeStyles(() => ({ - paper: { - height: '100%', - margin: '10px 0 0 0', - padding: 0, - borderRadius: 4, - }, -})); +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; +import WidgetRadar from '../../../../components/dashboard/WidgetRadar'; const stixCoreObjectsRadarDistributionQuery = graphql` query StixCoreObjectsRadarDistributionQuery( @@ -206,8 +193,6 @@ const StixCoreObjectsRadar = ({ withExportPopover = false, isReadOnly = false, }) => { - const classes = useStyles(); - const theme = useTheme(); const { t_i18n } = useFormatter(); const renderContent = () => { const selection = dataSelection[0]; @@ -237,89 +222,32 @@ const StixCoreObjectsRadar = ({ && props.stixCoreObjectsDistribution && props.stixCoreObjectsDistribution.length > 0 ) { - const data = props.stixCoreObjectsDistribution.map((n) => n.value); - const chartData = [ - { - name: selection.label || t_i18n('Number of relationships'), - data, - }, - ]; - const labels = props.stixCoreObjectsDistribution.map( - (n) => - // eslint-disable-next-line no-nested-ternary,implicit-arrow-linebreak - (selection.attribute.endsWith('_id') - ? defaultValue(n.entity) - : selection.attribute === 'entity_type' - ? t_i18n(`entity_${n.label}`) - : n.label), - 20, - ); return ( - ); } if (props) { - return ( -
- - {t_i18n('No entities of this type has been found.')} - -
- ); + return ; } - return ( -
- - - -
- ); + return ; }} /> ); }; return ( -
- - {parameters.title || t_i18n('Distribution of entities')} - - {variant === 'inLine' ? ( - renderContent() - ) : ( - - {renderContent()} - - )} -
+ + {renderContent()} + ); }; diff --git a/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectsTimeline.jsx b/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectsTimeline.jsx index c60799cdeaa8..f317def339a9 100644 --- a/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectsTimeline.jsx +++ b/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectsTimeline.jsx @@ -1,36 +1,13 @@ import React from 'react'; import { graphql } from 'react-relay'; -import CircularProgress from '@mui/material/CircularProgress'; -import Paper from '@mui/material/Paper'; -import Typography from '@mui/material/Typography'; -import { Link } from 'react-router-dom'; -import Timeline from '@mui/lab/Timeline'; -import TimelineItem from '@mui/lab/TimelineItem'; -import TimelineSeparator from '@mui/lab/TimelineSeparator'; -import TimelineConnector from '@mui/lab/TimelineConnector'; -import TimelineContent from '@mui/lab/TimelineContent'; -import TimelineOppositeContent from '@mui/lab/TimelineOppositeContent'; -import TimelineDot from '@mui/lab/TimelineDot'; -import makeStyles from '@mui/styles/makeStyles'; -import ItemIcon from '../../../../components/ItemIcon'; import { useFormatter } from '../../../../components/i18n'; import { QueryRenderer } from '../../../../relay/environment'; -import { resolveLink } from '../../../../utils/Entity'; -import { defaultValue } from '../../../../utils/Graph'; -import { itemColor } from '../../../../utils/Colors'; -import MarkdownDisplay from '../../../../components/MarkdownDisplay'; import { buildFiltersAndOptionsForWidgets } from '../../../../utils/filters/filtersUtils'; - -const useStyles = makeStyles({ - container: { - width: '100%', - height: '100%', - overflow: 'auto', - }, - paper: { - padding: 15, - }, -}); +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; +import WidgetTimeline from '../../../../components/dashboard/WidgetTimeline'; +import { resolveLink } from '../../../../utils/Entity'; const stixCoreObjectsTimelineQuery = graphql` query StixCoreObjectsTimelineQuery( @@ -223,8 +200,7 @@ const StixCoreObjectsTimeline = ({ dataSelection, parameters = {}, }) => { - const classes = useStyles(); - const { t_i18n, fldt } = useFormatter(); + const { t_i18n } = useFormatter(); const renderContent = () => { const selection = dataSelection[0]; const dataSelectionTypes = ['Stix-Core-Object']; @@ -249,109 +225,32 @@ const StixCoreObjectsTimeline = ({ && props.stixCoreObjects.edges.length > 0 ) { const stixCoreObjectsEdges = props.stixCoreObjects.edges; - return ( -
- - {stixCoreObjectsEdges.map((stixCoreObjectEdge) => { - const stixCoreObject = stixCoreObjectEdge.node; - const link = `${resolveLink(stixCoreObject.entity_type)}/${ - stixCoreObject.id - }`; - return ( - - - {fldt(stixCoreObject.created)} - - - - - - - - - - - - - {defaultValue(stixCoreObject)} - -
- -
-
-
-
- ); - })} -
-
- ); + const data = stixCoreObjectsEdges.map((stixCoreObjectEdge) => { + const stixCoreObject = stixCoreObjectEdge.node; + const link = `${resolveLink(stixCoreObject.entity_type)}/${stixCoreObject.id}`; + return { + value: stixCoreObject, + link, + }; + }); + return ; } if (props) { - return ( -
- - {t_i18n('No entities of this type has been found.')} - -
- ); - } - return ( -
- - - -
- ); + return ; + } + return ; }} /> ); }; return ( -
- - {parameters.title ?? t_i18n('Entities list')} - - {variant !== 'inLine' ? ( - - {renderContent()} - - ) : ( - renderContent() - )} -
+ + {renderContent()} + ); }; diff --git a/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectsTreeMap.jsx b/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectsTreeMap.jsx index 550a31ac37cd..d04b18dbe9b4 100644 --- a/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectsTreeMap.jsx +++ b/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectsTreeMap.jsx @@ -1,25 +1,12 @@ import React from 'react'; import { graphql } from 'react-relay'; -import CircularProgress from '@mui/material/CircularProgress'; -import Paper from '@mui/material/Paper'; -import Typography from '@mui/material/Typography'; -import makeStyles from '@mui/styles/makeStyles'; -import { useTheme } from '@mui/styles'; -import Chart from '../charts/Chart'; import { QueryRenderer } from '../../../../relay/environment'; import { useFormatter } from '../../../../components/i18n'; -import { treeMapOptions } from '../../../../utils/Charts'; -import { defaultValue } from '../../../../utils/Graph'; import { buildFiltersAndOptionsForWidgets } from '../../../../utils/filters/filtersUtils'; - -const useStyles = makeStyles(() => ({ - paper: { - height: '100%', - margin: '10px 0 0 0', - padding: 0, - borderRadius: 4, - }, -})); +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetTree from '../../../../components/dashboard/WidgetTree'; const stixCoreObjectsTreeMapDistributionQuery = graphql` query StixCoreObjectsTreeMapDistributionQuery( @@ -205,8 +192,6 @@ const StixCoreObjectsTreeMap = ({ withExportPopover = false, isReadOnly = false, }) => { - const classes = useStyles(); - const theme = useTheme(); const { t_i18n } = useFormatter(); const renderContent = () => { const selection = dataSelection[0]; @@ -237,83 +222,32 @@ const StixCoreObjectsTreeMap = ({ && props.stixCoreObjectsDistribution.length > 0 ) { const data = props.stixCoreObjectsDistribution; - const chartData = data.map((n) => ({ - // eslint-disable-next-line no-nested-ternary - x: selection.attribute.endsWith('_id') - ? defaultValue(n.entity) - : selection.attribute === 'entity_type' - ? t_i18n(`entity_${n.label}`) - : n.label, - y: n.value, - })); - const series = [{ data: chartData }]; return ( - ); } if (props) { - return ( -
- - {t_i18n('No entities of this type has been found.')} - -
- ); + return ; } - return ( -
- - - -
- ); + return ; }} /> ); }; return ( -
- - {parameters.title || t_i18n('Distribution of entities')} - - {variant === 'inLine' ? ( - renderContent() - ) : ( - - {renderContent()} - - )} -
+ + {renderContent()} + ); }; diff --git a/opencti-platform/opencti-front/src/private/components/common/stix_domain_objects/StixDomainObjectBookmarksList.jsx b/opencti-platform/opencti-front/src/private/components/common/stix_domain_objects/StixDomainObjectBookmarksList.jsx index e95f073bcd71..8037195a715b 100644 --- a/opencti-platform/opencti-front/src/private/components/common/stix_domain_objects/StixDomainObjectBookmarksList.jsx +++ b/opencti-platform/opencti-front/src/private/components/common/stix_domain_objects/StixDomainObjectBookmarksList.jsx @@ -1,54 +1,12 @@ import React from 'react'; import { graphql } from 'react-relay'; -import CircularProgress from '@mui/material/CircularProgress'; -import Paper from '@mui/material/Paper'; -import Typography from '@mui/material/Typography'; -import { Link } from 'react-router-dom'; -import Grid from '@mui/material/Grid'; -import makeStyles from '@mui/styles/makeStyles'; -import CardActionArea from '@mui/material/CardActionArea'; -import CardHeader from '@mui/material/CardHeader'; -import Avatar from '@mui/material/Avatar'; -import Card from '@mui/material/Card'; -import { useTheme } from '@mui/styles'; -import ItemIcon from '../../../../components/ItemIcon'; import { useFormatter } from '../../../../components/i18n'; import { QueryRenderer } from '../../../../relay/environment'; -import { resolveLink } from '../../../../utils/Entity'; import { removeEntityTypeAllFromFilterGroup } from '../../../../utils/filters/filtersUtils'; - -const useStyles = makeStyles((theme) => ({ - container: { - width: '100%', - height: '100%', - overflow: 'auto', - paddingBottom: 10, - marginBottom: 10, - }, - paper: { - height: '100%', - margin: '10px 0 0 0', - padding: 0, - borderRadius: 4, - }, - card: { - width: '100%', - height: 70, - borderRadius: 4, - }, - avatar: { - backgroundColor: theme.palette.primary.main, - }, - area: { - width: '100%', - height: '100%', - }, - header: { - height: 55, - paddingBottom: 0, - marginBottom: 0, - }, -})); +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetBookmarks from '../../../../components/dashboard/WidgetBookmarks'; const stixDomainObjectBookmarksListQuery = graphql` query StixDomainObjectBookmarksListQuery($types: [String], $first: Int, $filters: FilterGroup) { @@ -209,9 +167,7 @@ const StixDomainObjectBookmarksList = ({ dataSelection, parameters = {}, }) => { - const classes = useStyles(); - const theme = useTheme(); - const { t_i18n, fsd } = useFormatter(); + const { t_i18n } = useFormatter(); const renderContent = () => { const selection = dataSelection[0]; return ( @@ -224,101 +180,24 @@ const StixDomainObjectBookmarksList = ({ render={({ props }) => { if (props && props.bookmarks && props.bookmarks.edges.length > 0) { const data = props.bookmarks.edges; - return ( -
- - {data.map((bookmarkEdge) => { - const bookmark = bookmarkEdge.node; - const link = resolveLink(bookmark.entity_type); - return ( - - - - - - - } - title={bookmark.name} - subheader={`${t_i18n('Updated on')} ${fsd( - bookmark.modified, - )}`} - /> - - - - ); - })} - -
- ); + return ; } if (props) { - return ( -
- - {t_i18n('No entities of this type has been found.')} - -
- ); - } - return ( -
- - - -
- ); + return ; + } + return ; }} /> ); }; return ( -
- - {parameters.title ?? t_i18n('Entities list')} - - {variant !== 'inLine' ? ( - - {renderContent()} - - ) : ( - renderContent() - )} -
+ + {renderContent()} + ); }; diff --git a/opencti-platform/opencti-front/src/private/components/common/stix_relationships/StixRelationshipsDistributionList.jsx b/opencti-platform/opencti-front/src/private/components/common/stix_relationships/StixRelationshipsDistributionList.jsx index e7bdad9d273e..8bd34a11e185 100644 --- a/opencti-platform/opencti-front/src/private/components/common/stix_relationships/StixRelationshipsDistributionList.jsx +++ b/opencti-platform/opencti-front/src/private/components/common/stix_relationships/StixRelationshipsDistributionList.jsx @@ -1,58 +1,14 @@ import React from 'react'; import { graphql } from 'react-relay'; -import CircularProgress from '@mui/material/CircularProgress'; -import Paper from '@mui/material/Paper'; -import Typography from '@mui/material/Typography'; -import makeStyles from '@mui/styles/makeStyles'; -import List from '@mui/material/List'; -import ListItem from '@mui/material/ListItem'; -import { Link } from 'react-router-dom'; -import ListItemIcon from '@mui/material/ListItemIcon'; -import ListItemText from '@mui/material/ListItemText'; import { QueryRenderer } from '../../../../relay/environment'; import { useFormatter } from '../../../../components/i18n'; import { defaultValue } from '../../../../utils/Graph'; -import { resolveLink } from '../../../../utils/Entity'; -import ItemIcon from '../../../../components/ItemIcon'; import useGranted, { SETTINGS_SETACCESSES } from '../../../../utils/hooks/useGranted'; import { buildFiltersAndOptionsForWidgets } from '../../../../utils/filters/filtersUtils'; - -const useStyles = makeStyles((theme) => ({ - container: { - width: '100%', - height: '100%', - paddingBottom: 10, - marginBottom: 10, - }, - paper: { - height: '100%', - margin: '10px 0 0 0', - padding: 0, - borderRadius: 4, - }, - item: { - height: 50, - minHeight: 50, - maxHeight: 50, - paddingRight: 0, - }, - itemText: { - whiteSpace: 'nowrap', - overflow: 'hidden', - textOverflow: 'ellipsis', - paddingRight: 10, - }, - itemNumber: { - float: 'right', - marginRight: 20, - fontSize: 18, - fontWeight: 600, - whiteSpace: 'nowrap', - overflow: 'hidden', - textOverflow: 'ellipsis', - color: theme.palette.primary.main, - }, -})); +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetDistributionList from '../../../../components/dashboard/WidgetDistributionList'; const stixRelationshipsDistributionListDistributionQuery = graphql` query StixRelationshipsDistributionListDistributionQuery( @@ -168,6 +124,10 @@ const stixRelationshipsDistributionListDistributionQuery = graphql` name description } + ... on AdministrativeArea { + name + description + } ... on Malware { name description @@ -259,8 +219,7 @@ const StixRelationshipsDistributionList = ({ dataSelection, parameters = {}, }) => { - const classes = useStyles(); - const { t_i18n, n } = useFormatter(); + const { t_i18n } = useFormatter(); const hasSetAccess = useGranted([SETTINGS_SETACCESSES]); const renderContent = () => { let selection = {}; @@ -302,101 +261,29 @@ const StixRelationshipsDistributionList = ({ type: finalField.endsWith('_id') ? o.entity.entity_type : o.label, })); return ( -
- - {data.map((entry, key) => { - // eslint-disable-next-line no-nested-ternary - const link = entry.type === 'User' && !hasSetAccess - ? null - : entry.id - ? `${resolveLink(entry.type)}/${entry.id}` - : null; - return ( - - - - - - {entry.label} -
- } - /> -
- {n(entry.value)} -
- - ); - })} - - + ); } if (props) { - return ( -
- - {t_i18n('No entities of this type has been found.')} - -
- ); + return ; } - return ( -
- - - -
- ); + return ; }} /> ); }; return ( -
- - {parameters.title || title || t_i18n('Relationships distribution')} - - {variant !== 'inLine' ? ( - - {renderContent()} - - ) : ( - renderContent() - )} -
+ + {renderContent()} + ); }; diff --git a/opencti-platform/opencti-front/src/private/components/common/stix_relationships/StixRelationshipsDonut.jsx b/opencti-platform/opencti-front/src/private/components/common/stix_relationships/StixRelationshipsDonut.jsx index db12d6e5e4b4..565709bd9180 100644 --- a/opencti-platform/opencti-front/src/private/components/common/stix_relationships/StixRelationshipsDonut.jsx +++ b/opencti-platform/opencti-front/src/private/components/common/stix_relationships/StixRelationshipsDonut.jsx @@ -1,26 +1,12 @@ import React from 'react'; -import * as R from 'ramda'; import { graphql } from 'react-relay'; -import CircularProgress from '@mui/material/CircularProgress'; -import Paper from '@mui/material/Paper'; -import Typography from '@mui/material/Typography'; -import { useTheme } from '@mui/styles'; -import makeStyles from '@mui/styles/makeStyles'; -import Chart from '../charts/Chart'; import { QueryRenderer } from '../../../../relay/environment'; import { useFormatter } from '../../../../components/i18n'; -import { donutChartOptions } from '../../../../utils/Charts'; -import { defaultValue } from '../../../../utils/Graph'; import { buildFiltersAndOptionsForWidgets } from '../../../../utils/filters/filtersUtils'; - -const useStyles = makeStyles(() => ({ - paper: { - height: '100%', - margin: '10px 0 0 0', - padding: 0, - borderRadius: 4, - }, -})); +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; +import WidgetDonut from '../../../../components/dashboard/WidgetDonut'; const stixRelationshipsDonutsDistributionQuery = graphql` query StixRelationshipsDonutDistributionQuery( @@ -230,8 +216,6 @@ const StixRelationshipsDonut = ({ withExportPopover = false, isReadOnly = false, }) => { - const classes = useStyles(); - const theme = useTheme(); const { t_i18n } = useFormatter(); const renderContent = () => { let selection = {}; @@ -259,109 +243,36 @@ const StixRelationshipsDonut = ({ query={stixRelationshipsDonutsDistributionQuery} variables={variables} render={({ props }) => { - if (props && props.stixRelationshipsDistribution && props.stixRelationshipsDistribution.length > 0) { - let data = props.stixRelationshipsDistribution; - if (finalField.endsWith('_id')) { - data = R.map( - (n) => R.assoc( - 'label', - defaultValue(n.entity), - n, - ), - props.stixRelationshipsDistribution, - ); - } - let chartColors = []; - if (data.at(0)?.entity?.color) { - chartColors = data.map((n) => (theme.palette.mode === 'light' && n.entity.color === '#ffffff' - ? '#000000' - : n.entity.color)); - } - if (data.at(0)?.entity?.x_opencti_color) { - chartColors = data.map((n) => (theme.palette.mode === 'light' - && n.entity.x_opencti_color === '#ffffff' - ? '#000000' - : n.entity.x_opencti_color)); - } - if (data.at(0)?.entity?.template?.color) { - chartColors = data.map((n) => (theme.palette.mode === 'light' && n.entity.template.color === '#ffffff' - ? '#000000' - : n.entity.template.color)); - } - const chartData = data.map((n) => n.value); - const labels = data.map((n) => n.label); + if ( + props + && props.stixRelationshipsDistribution + && props.stixRelationshipsDistribution.length > 0 + ) { return ( - ); } if (props) { - return ( -
- - {t_i18n('No entities of this type has been found.')} - -
- ); + return ; } - return ( -
- - - -
- ); + return ; }} /> ); }; return ( -
- - {parameters.title || title || t_i18n('Relationships distribution')} - - {variant !== 'inLine' ? ( - - {renderContent()} - - ) : ( - renderContent() - )} -
+ + {renderContent()} + ); }; diff --git a/opencti-platform/opencti-front/src/private/components/common/stix_relationships/StixRelationshipsHorizontalBars.jsx b/opencti-platform/opencti-front/src/private/components/common/stix_relationships/StixRelationshipsHorizontalBars.jsx index 5586bbf7944f..4731d68a3a6d 100644 --- a/opencti-platform/opencti-front/src/private/components/common/stix_relationships/StixRelationshipsHorizontalBars.jsx +++ b/opencti-platform/opencti-front/src/private/components/common/stix_relationships/StixRelationshipsHorizontalBars.jsx @@ -1,28 +1,15 @@ import React from 'react'; import { graphql } from 'react-relay'; -import CircularProgress from '@mui/material/CircularProgress'; -import Paper from '@mui/material/Paper'; -import Typography from '@mui/material/Typography'; -import makeStyles from '@mui/styles/makeStyles'; import { useTheme } from '@mui/styles'; -import { useNavigate } from 'react-router-dom-v5-compat'; -import Chart from '../charts/Chart'; import { QueryRenderer } from '../../../../relay/environment'; import { useFormatter } from '../../../../components/i18n'; import { itemColor } from '../../../../utils/Colors'; -import { horizontalBarsChartOptions } from '../../../../utils/Charts'; -import { simpleNumberFormat } from '../../../../utils/Number'; import { defaultValue } from '../../../../utils/Graph'; import { buildFiltersAndOptionsForWidgets } from '../../../../utils/filters/filtersUtils'; - -const useStyles = makeStyles(() => ({ - paper: { - height: '100%', - margin: '10px 0 0 0', - padding: 0, - borderRadius: 4, - }, -})); +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; +import WidgetHorizontalBars from '../../../../components/dashboard/WidgetHorizontalBars'; const stixRelationshipsHorizontalBarsDistributionQuery = graphql` query StixRelationshipsHorizontalBarsDistributionQuery( @@ -234,10 +221,8 @@ const StixRelationshipsHorizontalBars = ({ withExportPopover = false, isReadOnly = false, }) => { - const classes = useStyles(); const theme = useTheme(); const { t_i18n } = useFormatter(); - const navigate = useNavigate(); const renderContent = () => { let selection = {}; let filtersAndOptions; @@ -308,83 +293,34 @@ const StixRelationshipsHorizontalBars = ({ id: n.label, entity_type: n.entity.entity_type, })) - : null; + : undefined; return ( - ); } if (props) { - return ( -
- - {t_i18n('No entities of this type has been found.')} - -
- ); + return ; } - return ( -
- - - -
- ); + return ; }} /> ); }; return ( -
- {!withoutTitle && ( - - {parameters.title || title || t_i18n('Relationships distribution')} - - )} - {variant !== 'inLine' ? ( - - {renderContent()} - - ) : ( - renderContent() - )} -
+ + {renderContent()} + ); }; diff --git a/opencti-platform/opencti-front/src/private/components/common/stix_relationships/StixRelationshipsList.jsx b/opencti-platform/opencti-front/src/private/components/common/stix_relationships/StixRelationshipsList.jsx index c844e866ed04..2fa439898045 100644 --- a/opencti-platform/opencti-front/src/private/components/common/stix_relationships/StixRelationshipsList.jsx +++ b/opencti-platform/opencti-front/src/private/components/common/stix_relationships/StixRelationshipsList.jsx @@ -1,57 +1,12 @@ import React from 'react'; -import * as R from 'ramda'; import { graphql } from 'react-relay'; -import CircularProgress from '@mui/material/CircularProgress'; -import Paper from '@mui/material/Paper'; -import Typography from '@mui/material/Typography'; -import ListItem from '@mui/material/ListItem'; -import { Link } from 'react-router-dom'; -import ListItemIcon from '@mui/material/ListItemIcon'; -import ListItemText from '@mui/material/ListItemText'; -import List from '@mui/material/List'; -import makeStyles from '@mui/styles/makeStyles'; -import ItemIcon from '../../../../components/ItemIcon'; import { useFormatter } from '../../../../components/i18n'; import { QueryRenderer } from '../../../../relay/environment'; -import { computeLink } from '../../../../utils/Entity'; -import { defaultValue } from '../../../../utils/Graph'; -import ItemMarkings from '../../../../components/ItemMarkings'; import { buildFiltersAndOptionsForWidgets } from '../../../../utils/filters/filtersUtils'; - -const useStyles = makeStyles((theme) => ({ - container: { - width: '100%', - height: '100%', - overflow: 'auto', - paddingBottom: 10, - marginBottom: 10, - }, - paper: { - height: '100%', - margin: '10px 0 0 0', - padding: 0, - borderRadius: 4, - }, - item: { - height: 50, - minHeight: 50, - maxHeight: 50, - paddingRight: 0, - }, - bodyItem: { - height: 20, - fontSize: 13, - float: 'left', - whiteSpace: 'nowrap', - overflow: 'hidden', - textOverflow: 'ellipsis', - paddingRight: 10, - }, - itemIcon: { - marginRight: 0, - color: theme.palette.primary.main, - }, -})); +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; +import WidgetListRelationships from '../../../../components/dashboard/WidgetListRelationships'; export const stixRelationshipsListSearchQuery = graphql` query StixRelationshipsListSearchQuery( @@ -3971,6 +3926,9 @@ const stixRelationshipsListQuery = graphql` ... on City { name } + ... on AdministrativeArea { + name + } ... on Country { name } @@ -4445,8 +4403,7 @@ const StixRelationshipsList = ({ dataSelection, parameters = {}, }) => { - const classes = useStyles(); - const { t_i18n, fsd } = useFormatter(); + const { t_i18n } = useFormatter(); const renderContent = () => { if (!dataSelection) { return 'No data selection'; @@ -4474,215 +4431,24 @@ const StixRelationshipsList = ({ && props.stixRelationships.edges.length > 0 ) { const data = props.stixRelationships.edges; - return ( -
- - {data.map((stixRelationshipEdge) => { - const stixRelationship = stixRelationshipEdge.node; - const remoteNode = stixRelationship.from - ? stixRelationship.from - : stixRelationship.to; - let link = null; - if (remoteNode) { - link = computeLink(remoteNode); - } - return ( - - - - - -
- - {/* eslint-disable-next-line no-nested-ternary */} - {stixRelationship.from - ? stixRelationship.from.relationship_type - ? t_i18n( - `relationship_${stixRelationship.from.entity_type}`, - ) - : t_i18n( - `entity_${stixRelationship.from.entity_type}`, - ) - : t_i18n('Restricted')} -
-
- - {stixRelationship.from - ? defaultValue(stixRelationship.from) - : t_i18n('Restricted')} - -
-
- - {t_i18n( - `relationship_${stixRelationship.relationship_type}`, - )} - -
-
- - {/* eslint-disable-next-line no-nested-ternary */} - {stixRelationship.to - ? stixRelationship.to.relationship_type - ? t_i18n( - `relationship_${stixRelationship.to.entity_type}`, - ) - : t_i18n( - `entity_${stixRelationship.to.entity_type}`, - ) - : t_i18n('Restricted')} -
-
- - {stixRelationship.to - ? defaultValue(stixRelationship.to) - : t_i18n('Restricted')} - -
-
- {fsd(stixRelationship[dateAttribute])} -
-
- {R.pathOr( - '', - ['createdBy', 'name'], - stixRelationship, - )} -
-
- -
-
- } - /> - - ); - })} - - - ); + return ; } if (props) { - return ( -
- - {t_i18n('No entities of this type has been found.')} - -
- ); + return ; } - return ( -
- - - -
- ); + return ; }} /> ); }; return ( -
- - {parameters.title ?? t_i18n('Relationships list')} - - {variant !== 'inLine' ? ( - - {renderContent()} - - ) : ( - renderContent() - )} -
+ + {renderContent()} + ); }; diff --git a/opencti-platform/opencti-front/src/private/components/common/stix_relationships/StixRelationshipsMap.jsx b/opencti-platform/opencti-front/src/private/components/common/stix_relationships/StixRelationshipsMap.jsx index fab830bc6e6e..6bad85e27f30 100644 --- a/opencti-platform/opencti-front/src/private/components/common/stix_relationships/StixRelationshipsMap.jsx +++ b/opencti-platform/opencti-front/src/private/components/common/stix_relationships/StixRelationshipsMap.jsx @@ -1,23 +1,14 @@ import React from 'react'; import * as R from 'ramda'; -import CircularProgress from '@mui/material/CircularProgress'; -import Typography from '@mui/material/Typography'; import { graphql } from 'react-relay'; -import makeStyles from '@mui/styles/makeStyles'; -import Paper from '@mui/material/Paper'; import { useFormatter } from '../../../../components/i18n'; import LocationMiniMapTargets from '../location/LocationMiniMapTargets'; import { QueryRenderer } from '../../../../relay/environment'; import { computeLevel } from '../../../../utils/Number'; import { buildFiltersAndOptionsForWidgets } from '../../../../utils/filters/filtersUtils'; - -const useStyles = makeStyles(() => ({ - paper: { - margin: '10px 0 0 0', - padding: 0, - borderRadius: 4, - }, -})); +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; export const stixRelationshipsMapStixRelationshipsDistributionQuery = graphql` query StixRelationshipsMapStixRelationshipsDistributionQuery( @@ -103,7 +94,6 @@ const StixRelationshipsMap = ({ dataSelection, parameters = {}, }) => { - const classes = useStyles(); const { t_i18n } = useFormatter(); const renderContent = () => { let selection = {}; @@ -168,59 +158,21 @@ const StixRelationshipsMap = ({ ); } if (props) { - return ( -
- - {t_i18n('No entities of this type has been found.')} - -
- ); + return ; } - return ( -
- - - -
- ); + return ; }} /> ); }; return ( -
- - {parameters.title || title || t_i18n('Relationships distribution')} - - {variant !== 'inLine' ? ( - - {renderContent()} - - ) : ( - renderContent() - )} -
+ + {renderContent()} + ); }; diff --git a/opencti-platform/opencti-front/src/private/components/common/stix_relationships/StixRelationshipsMultiAreaChart.jsx b/opencti-platform/opencti-front/src/private/components/common/stix_relationships/StixRelationshipsMultiAreaChart.jsx index 57b29883f7a9..b3137796e5aa 100644 --- a/opencti-platform/opencti-front/src/private/components/common/stix_relationships/StixRelationshipsMultiAreaChart.jsx +++ b/opencti-platform/opencti-front/src/private/components/common/stix_relationships/StixRelationshipsMultiAreaChart.jsx @@ -1,27 +1,13 @@ import React from 'react'; import { graphql } from 'react-relay'; -import CircularProgress from '@mui/material/CircularProgress'; -import Paper from '@mui/material/Paper'; -import Typography from '@mui/material/Typography'; -import makeStyles from '@mui/styles/makeStyles'; -import { useTheme } from '@mui/styles'; -import Chart from '../charts/Chart'; import { QueryRenderer } from '../../../../relay/environment'; import { useFormatter } from '../../../../components/i18n'; import { monthsAgo, now } from '../../../../utils/Time'; -import { areaChartOptions } from '../../../../utils/Charts'; -import { simpleNumberFormat } from '../../../../utils/Number'; import { buildFiltersAndOptionsForWidgets } from '../../../../utils/filters/filtersUtils'; - -const useStyles = makeStyles(() => ({ - paper: { - minHeight: 280, - height: '100%', - margin: '4px 0 0 0', - padding: '0 0 10px 0', - borderRadius: 4, - }, -})); +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; +import WidgetMultiAreas from '../../../../components/dashboard/WidgetMultiAreas'; const stixRelationshipsMultiAreaChartTimeSeriesQuery = graphql` query StixRelationshipsMultiAreaChartTimeSeriesQuery( @@ -57,9 +43,7 @@ const StixRelationshipsMultiAreaChart = ({ withExportPopover = false, isReadOnly = false, }) => { - const theme = useTheme(); - const classes = useStyles(); - const { t_i18n, fsd, mtdy, yd } = useFormatter(); + const { t_i18n } = useFormatter(); const renderContent = () => { const timeSeriesParameters = dataSelection.map((selection) => { const dataSelectionDateAttribute = selection.date_attribute && selection.date_attribute.length > 0 @@ -73,13 +57,7 @@ const StixRelationshipsMultiAreaChart = ({ dynamicTo: selection.dynamicTo, }; }); - let formatter = fsd; - if (parameters.interval === 'month' || parameters.interval === 'quarter') { - formatter = mtdy; - } - if (parameters.interval === 'year') { - formatter = yd; - } + return ( { if (props && props.stixRelationshipsMultiTimeSeries) { return ( - ({ - name: selection.label ?? t_i18n('Number of entities'), + name: selection.label || t_i18n('Number of entities'), data: props.stixRelationshipsMultiTimeSeries[i].data.map( (entry) => ({ x: new Date(entry.date), @@ -116,68 +81,30 @@ const StixRelationshipsMultiAreaChart = ({ }), ), }))} - type="area" - width="100%" - height="100%" - withExportPopover={withExportPopover} - isReadOnly={isReadOnly} + interval={parameters.interval} + isStacked={parameters.stacked} + hasLegend={parameters.legend} + withExport={withExportPopover} + readonly={isReadOnly} /> ); } if (props) { - return ( -
- - {t_i18n('No entities of this type has been found.')} - -
- ); + return ; } - return ( -
- - - -
- ); + return ; }} /> ); }; return ( -
- - {parameters.title || title || t_i18n('Entities history')} - - {variant !== 'inLine' ? ( - - {renderContent()} - - ) : ( - renderContent() - )} -
+ + {renderContent()} + ); }; diff --git a/opencti-platform/opencti-front/src/private/components/common/stix_relationships/StixRelationshipsMultiHeatMap.jsx b/opencti-platform/opencti-front/src/private/components/common/stix_relationships/StixRelationshipsMultiHeatMap.jsx index 1614ff24e082..5223a8dd06ae 100644 --- a/opencti-platform/opencti-front/src/private/components/common/stix_relationships/StixRelationshipsMultiHeatMap.jsx +++ b/opencti-platform/opencti-front/src/private/components/common/stix_relationships/StixRelationshipsMultiHeatMap.jsx @@ -1,26 +1,13 @@ import React from 'react'; import { graphql } from 'react-relay'; -import CircularProgress from '@mui/material/CircularProgress'; -import Paper from '@mui/material/Paper'; -import Typography from '@mui/material/Typography'; -import makeStyles from '@mui/styles/makeStyles'; -import { useTheme } from '@mui/styles'; -import Chart from '../charts/Chart'; import { QueryRenderer } from '../../../../relay/environment'; import { useFormatter } from '../../../../components/i18n'; import { monthsAgo, now } from '../../../../utils/Time'; -import { heatMapOptions } from '../../../../utils/Charts'; import { buildFiltersAndOptionsForWidgets } from '../../../../utils/filters/filtersUtils'; - -const useStyles = makeStyles(() => ({ - paper: { - minHeight: 280, - height: '100%', - margin: '4px 0 0 0', - padding: '0 0 10px 0', - borderRadius: 4, - }, -})); +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; +import WidgetMultiHeatMap from '../../../../components/dashboard/WidgetMultiHeatMap'; const stixRelationshipsMultiHeatMapTimeSeriesQuery = graphql` query StixRelationshipsMultiHeatMapTimeSeriesQuery( @@ -45,34 +32,6 @@ const stixRelationshipsMultiHeatMapTimeSeriesQuery = graphql` } `; -const darkColors = [ - '#001e3c', - '#023362', - '#02407a', - '#045198', - '#0561b4', - '#0b75d9', - '#2986e7', - '#3a95f3', - '#4da3ff', - '#76bbff', - '#9eccff', -]; - -const lightColors = [ - '#f3f6f9', - '#76bbff', - '#4da3ff', - '#3a95f3', - '#2986e7', - '#0b75d9', - '#0561b4', - '#02407a', - '#023362', - '#001e3c', - '#021428', -]; - const StixRelationshipsMultiHeatMap = ({ variant, height, @@ -83,9 +42,7 @@ const StixRelationshipsMultiHeatMap = ({ withExportPopover = false, isReadOnly = false, }) => { - const theme = useTheme(); - const classes = useStyles(); - const { t_i18n, fsd } = useFormatter(); + const { t_i18n } = useFormatter(); const renderContent = () => { const timeSeriesParameters = dataSelection.map((selection) => { const dataSelectionDateAttribute = selection.date_attribute && selection.date_attribute.length > 0 @@ -111,9 +68,9 @@ const StixRelationshipsMultiHeatMap = ({ }} render={({ props }) => { if (props && props.stixRelationshipsMultiTimeSeries) { - const chartdata = dataSelection + const chartData = dataSelection .map((selection, i) => ({ - name: selection.label ?? t_i18n('Number of relationships'), + name: selection.label || t_i18n('Number of relationships'), data: props.stixRelationshipsMultiTimeSeries[i].data.map( (entry) => ({ x: new Date(entry.date), @@ -127,100 +84,34 @@ const StixRelationshipsMultiHeatMap = ({ .flat(); const maxValue = Math.max(...allValues); const minValue = Math.min(...allValues); - const interval = Math.trunc((maxValue - minValue) / 9); - const colorRanges = Array(10) - .fill(0) - .map((_, i) => ({ - from: - minValue + (i + 1) * interval - interval === 0 - ? 1 - : minValue + (i + 1) * interval - interval, - to: minValue + (i + 1) * interval, - color: - theme.palette.mode === 'dark' - ? darkColors[i + 1] - : lightColors[i + 1], - })); - colorRanges.push({ - from: 0, - to: 0, - color: - theme.palette.mode === 'dark' ? darkColors[0] : lightColors[0], - }); + return ( - ); } if (props) { - return ( -
- - {t_i18n('No entities of this type has been found.')} - -
- ); + return ; } - return ( -
- - - -
- ); + return ; }} /> ); }; return ( -
- - {parameters.title ?? t_i18n('Entities history')} - - {variant !== 'inLine' ? ( - - {renderContent()} - - ) : ( - renderContent() - )} -
+ + {renderContent()} + ); }; diff --git a/opencti-platform/opencti-front/src/private/components/common/stix_relationships/StixRelationshipsMultiHorizontalBars.jsx b/opencti-platform/opencti-front/src/private/components/common/stix_relationships/StixRelationshipsMultiHorizontalBars.jsx index 1e52b147c98b..042a33d1eb95 100644 --- a/opencti-platform/opencti-front/src/private/components/common/stix_relationships/StixRelationshipsMultiHorizontalBars.jsx +++ b/opencti-platform/opencti-front/src/private/components/common/stix_relationships/StixRelationshipsMultiHorizontalBars.jsx @@ -1,28 +1,14 @@ import React from 'react'; import { graphql } from 'react-relay'; -import CircularProgress from '@mui/material/CircularProgress'; -import Paper from '@mui/material/Paper'; -import Typography from '@mui/material/Typography'; -import makeStyles from '@mui/styles/makeStyles'; -import { useTheme } from '@mui/styles'; import * as R from 'ramda'; -import { useNavigate } from 'react-router-dom-v5-compat'; -import Chart from '../charts/Chart'; import { QueryRenderer } from '../../../../relay/environment'; import { useFormatter } from '../../../../components/i18n'; -import { horizontalBarsChartOptions } from '../../../../utils/Charts'; -import { simpleNumberFormat } from '../../../../utils/Number'; import { defaultValue } from '../../../../utils/Graph'; import { buildFiltersAndOptionsForWidgets } from '../../../../utils/filters/filtersUtils'; - -const useStyles = makeStyles(() => ({ - paper: { - height: '100%', - margin: '10px 0 0 0', - padding: 0, - borderRadius: 4, - }, -})); +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; +import WidgetHorizontalBars from '../../../../components/dashboard/WidgetHorizontalBars'; const stixRelationshipsMultiHorizontalBarsWithRelationshipsDistributionQuery = graphql` query StixRelationshipsMultiHorizontalBarsWithRelationshipsDistributionQuery( @@ -754,10 +740,7 @@ const StixRelationshipsMultiHorizontalBars = ({ withExportPopover = false, isReadOnly = false, }) => { - const classes = useStyles(); - const theme = useTheme(); const { t_i18n } = useFormatter(); - const navigate = useNavigate(); const renderContent = () => { let selection = {}; let dataSelectionDateAttribute = null; @@ -937,83 +920,35 @@ const StixRelationshipsMultiHorizontalBars = ({ })) : null; return ( - ); } if (props) { - return ( -
- - {t_i18n('No entities of this type has been found.')} - -
- ); + return ; } - return ( -
- - - -
- ); + return ; }} /> ); }; return ( -
- - {parameters.title || title || t_i18n('Relationships distribution')} - - {variant !== 'inLine' ? ( - - {renderContent()} - - ) : ( - renderContent() - )} -
+ + {renderContent()} + ); }; diff --git a/opencti-platform/opencti-front/src/private/components/common/stix_relationships/StixRelationshipsMultiLineChart.jsx b/opencti-platform/opencti-front/src/private/components/common/stix_relationships/StixRelationshipsMultiLineChart.jsx index ff535d4e8e0d..0da732d2c55e 100644 --- a/opencti-platform/opencti-front/src/private/components/common/stix_relationships/StixRelationshipsMultiLineChart.jsx +++ b/opencti-platform/opencti-front/src/private/components/common/stix_relationships/StixRelationshipsMultiLineChart.jsx @@ -1,27 +1,13 @@ import React from 'react'; import { graphql } from 'react-relay'; -import CircularProgress from '@mui/material/CircularProgress'; -import Paper from '@mui/material/Paper'; -import Typography from '@mui/material/Typography'; -import makeStyles from '@mui/styles/makeStyles'; -import { useTheme } from '@mui/styles'; -import Chart from '../charts/Chart'; import { QueryRenderer } from '../../../../relay/environment'; import { useFormatter } from '../../../../components/i18n'; import { monthsAgo, now } from '../../../../utils/Time'; -import { lineChartOptions } from '../../../../utils/Charts'; -import { simpleNumberFormat } from '../../../../utils/Number'; import { buildFiltersAndOptionsForWidgets } from '../../../../utils/filters/filtersUtils'; - -const useStyles = makeStyles(() => ({ - paper: { - minHeight: 280, - height: '100%', - margin: '4px 0 0 0', - padding: '0 0 10px 0', - borderRadius: 4, - }, -})); +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; +import WidgetMultiLines from '../../../../components/dashboard/WidgetMultiLines'; const stixRelationshipsMultiLineChartTimeSeriesQuery = graphql` query StixRelationshipsMultiLineChartTimeSeriesQuery( @@ -56,9 +42,7 @@ const StixRelationshipsMultiLineChart = ({ withExportPopover = false, isReadOnly = false, }) => { - const theme = useTheme(); - const classes = useStyles(); - const { t_i18n, fsd, mtdy, yd } = useFormatter(); + const { t_i18n } = useFormatter(); const renderContent = () => { const timeSeriesParameters = dataSelection.map((selection) => { const dataSelectionDateAttribute = selection.date_attribute && selection.date_attribute.length > 0 @@ -72,13 +56,7 @@ const StixRelationshipsMultiLineChart = ({ dynamicTo: selection.dynamicTo, }; }); - let formatter = fsd; - if (parameters.interval === 'month' || parameters.interval === 'quarter') { - formatter = mtdy; - } - if (parameters.interval === 'year') { - formatter = yd; - } + return ( { if (props && props.stixRelationshipsMultiTimeSeries) { return ( - ({ - name: selection.label ?? t_i18n('Number of entities'), + name: selection.label || t_i18n('Number of entities'), data: props.stixRelationshipsMultiTimeSeries[i].data.map( (entry) => ({ x: new Date(entry.date), @@ -115,68 +80,29 @@ const StixRelationshipsMultiLineChart = ({ }), ), }))} - type="line" - width="100%" - height="100%" - withExportPopover={withExportPopover} - isReadOnly={isReadOnly} + interval={parameters.interval} + hasLegend={parameters.legend} + withExport={withExportPopover} + readonly={isReadOnly} /> ); } if (props) { - return ( -
- - {t_i18n('No entities of this type has been found.')} - -
- ); + return ; } - return ( -
- - - -
- ); + return ; }} /> ); }; return ( -
- - {parameters.title ?? t_i18n('Entities history')} - - {variant !== 'inLine' ? ( - - {renderContent()} - - ) : ( - renderContent() - )} -
+ + {renderContent()} + ); }; diff --git a/opencti-platform/opencti-front/src/private/components/common/stix_relationships/StixRelationshipsMultiVerticalBars.jsx b/opencti-platform/opencti-front/src/private/components/common/stix_relationships/StixRelationshipsMultiVerticalBars.jsx index 2eea141e4f7e..3e96758541ed 100644 --- a/opencti-platform/opencti-front/src/private/components/common/stix_relationships/StixRelationshipsMultiVerticalBars.jsx +++ b/opencti-platform/opencti-front/src/private/components/common/stix_relationships/StixRelationshipsMultiVerticalBars.jsx @@ -1,27 +1,13 @@ import React from 'react'; import { graphql } from 'react-relay'; -import CircularProgress from '@mui/material/CircularProgress'; -import Paper from '@mui/material/Paper'; -import Typography from '@mui/material/Typography'; -import makeStyles from '@mui/styles/makeStyles'; -import { useTheme } from '@mui/styles'; -import Chart from '../charts/Chart'; import { QueryRenderer } from '../../../../relay/environment'; import { useFormatter } from '../../../../components/i18n'; import { monthsAgo, now } from '../../../../utils/Time'; -import { verticalBarsChartOptions } from '../../../../utils/Charts'; -import { simpleNumberFormat } from '../../../../utils/Number'; import { buildFiltersAndOptionsForWidgets } from '../../../../utils/filters/filtersUtils'; - -const useStyles = makeStyles(() => ({ - paper: { - minHeight: 280, - height: '100%', - margin: '4px 0 0 0', - padding: '0 0 10px 0', - borderRadius: 4, - }, -})); +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; +import WidgetVerticalBars from '../../../../components/dashboard/WidgetVerticalBars'; const stixRelationshipsMultiVerticalBarsTimeSeriesQuery = graphql` query StixRelationshipsMultiVerticalBarsTimeSeriesQuery( @@ -56,9 +42,7 @@ const StixRelationshipsMultiVerticalBars = ({ withExportPopover = false, isReadOnly = false, }) => { - const theme = useTheme(); - const classes = useStyles(); - const { t_i18n, fsd, mtdy, yd } = useFormatter(); + const { t_i18n } = useFormatter(); const renderContent = () => { const timeSeriesParameters = dataSelection.map((selection) => { const dataSelectionDateAttribute = selection.date_attribute && selection.date_attribute.length > 0 @@ -72,13 +56,7 @@ const StixRelationshipsMultiVerticalBars = ({ dynamicTo: selection.dynamicTo, }; }); - let formatter = fsd; - if (parameters.interval === 'month' || parameters.interval === 'quarter') { - formatter = mtdy; - } - if (parameters.interval === 'year') { - formatter = yd; - } + return ( { if (props && props.stixRelationshipsMultiTimeSeries) { return ( - ({ - name: selection.label ?? t_i18n('Number of entities'), + name: selection.label || t_i18n('Number of entities'), data: props.stixRelationshipsMultiTimeSeries[i].data.map( (entry) => ({ x: new Date(entry.date), @@ -116,68 +80,30 @@ const StixRelationshipsMultiVerticalBars = ({ }), ), }))} - type="bar" - width="100%" - height="100%" - withExportPopover={withExportPopover} - isReadOnly={isReadOnly} + interval={parameters.interval} + isStacked={parameters.stacked} + hasLegend={parameters.legend} + withExport={withExportPopover} + readonly={isReadOnly} /> ); } if (props) { - return ( -
- - {t_i18n('No entities of this type has been found.')} - -
- ); + return ; } - return ( -
- - - -
- ); + return ; }} /> ); }; return ( -
- - {parameters.title ?? t_i18n('Entities history')} - - {variant !== 'inLine' ? ( - - {renderContent()} - - ) : ( - renderContent() - )} -
+ + {renderContent()} + ); }; diff --git a/opencti-platform/opencti-front/src/private/components/common/stix_relationships/StixRelationshipsRadar.jsx b/opencti-platform/opencti-front/src/private/components/common/stix_relationships/StixRelationshipsRadar.jsx index 52e01c3d5b38..a032d62e1af7 100644 --- a/opencti-platform/opencti-front/src/private/components/common/stix_relationships/StixRelationshipsRadar.jsx +++ b/opencti-platform/opencti-front/src/private/components/common/stix_relationships/StixRelationshipsRadar.jsx @@ -1,26 +1,12 @@ import React from 'react'; -import * as R from 'ramda'; import { graphql } from 'react-relay'; -import CircularProgress from '@mui/material/CircularProgress'; -import Paper from '@mui/material/Paper'; -import Typography from '@mui/material/Typography'; -import { useTheme } from '@mui/styles'; -import makeStyles from '@mui/styles/makeStyles'; -import Chart from '../charts/Chart'; import { QueryRenderer } from '../../../../relay/environment'; import { useFormatter } from '../../../../components/i18n'; -import { radarChartOptions } from '../../../../utils/Charts'; -import { defaultValue } from '../../../../utils/Graph'; import { buildFiltersAndOptionsForWidgets } from '../../../../utils/filters/filtersUtils'; - -const useStyles = makeStyles(() => ({ - paper: { - height: '100%', - margin: '10px 0 0 0', - padding: 0, - borderRadius: 4, - }, -})); +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; +import WidgetRadar from '../../../../components/dashboard/WidgetRadar'; const stixRelationshipsRadarsDistributionQuery = graphql` query StixRelationshipsRadarDistributionQuery( @@ -228,8 +214,6 @@ const StixRelationshipsRadar = ({ withExportPopover = false, isReadOnly = false, }) => { - const classes = useStyles(); - const theme = useTheme(); const { t_i18n } = useFormatter(); const renderContent = () => { let selection = {}; @@ -262,91 +246,32 @@ const StixRelationshipsRadar = ({ && props.stixRelationshipsDistribution && props.stixRelationshipsDistribution.length > 0 ) { - let data = props.stixRelationshipsDistribution; - if (finalField.endsWith('_id')) { - data = R.map( - (n) => R.assoc( - 'label', - defaultValue(n.entity), - n, - ), - props.stixRelationshipsDistribution, - ); - } - const valueData = data.map((n) => n.value); - const chartData = [ - { - name: selection.label || t_i18n('Number of relationships'), - data: valueData, - }, - ]; - const labels = data.map((n) => n.label); return ( - ); } if (props) { - return ( -
- - {t_i18n('No entities of this type has been found.')} - -
- ); + return ; } - return ( -
- - - -
- ); + return ; }} /> ); }; return ( -
- - {parameters.title || title || t_i18n('Relationships distribution')} - - {variant !== 'inLine' ? ( - - {renderContent()} - - ) : ( - renderContent() - )} -
+ + {renderContent()} + ); }; diff --git a/opencti-platform/opencti-front/src/private/components/common/stix_relationships/StixRelationshipsTimeline.jsx b/opencti-platform/opencti-front/src/private/components/common/stix_relationships/StixRelationshipsTimeline.jsx index 0efae8a81ad5..20e1faef2a70 100644 --- a/opencti-platform/opencti-front/src/private/components/common/stix_relationships/StixRelationshipsTimeline.jsx +++ b/opencti-platform/opencti-front/src/private/components/common/stix_relationships/StixRelationshipsTimeline.jsx @@ -1,36 +1,13 @@ import React from 'react'; import { graphql } from 'react-relay'; -import CircularProgress from '@mui/material/CircularProgress'; -import Paper from '@mui/material/Paper'; -import Typography from '@mui/material/Typography'; -import Timeline from '@mui/lab/Timeline'; -import TimelineItem from '@mui/lab/TimelineItem'; -import TimelineOppositeContent from '@mui/lab/TimelineOppositeContent'; -import TimelineSeparator from '@mui/lab/TimelineSeparator'; -import { Link } from 'react-router-dom'; -import TimelineDot from '@mui/lab/TimelineDot'; -import TimelineConnector from '@mui/lab/TimelineConnector'; -import TimelineContent from '@mui/lab/TimelineContent'; -import makeStyles from '@mui/styles/makeStyles'; -import { defaultValue } from '../../../../utils/Graph'; -import ItemIcon from '../../../../components/ItemIcon'; import { resolveLink } from '../../../../utils/Entity'; import { useFormatter } from '../../../../components/i18n'; import { QueryRenderer } from '../../../../relay/environment'; -import { itemColor } from '../../../../utils/Colors'; -import MarkdownDisplay from '../../../../components/MarkdownDisplay'; import { buildFiltersAndOptionsForWidgets } from '../../../../utils/filters/filtersUtils'; - -const useStyles = makeStyles({ - container: { - width: '100%', - height: '100%', - overflow: 'auto', - }, - paper: { - padding: 15, - }, -}); +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; +import WidgetTimeline from '../../../../components/dashboard/WidgetTimeline'; const stixRelationshipsTimelineStixRelationshipQuery = graphql` query StixRelationshipsTimelineStixRelationshipQuery( @@ -1064,8 +1041,7 @@ const StixRelationshipsTimeline = ({ dataSelection, parameters = {}, }) => { - const classes = useStyles(); - const { t_i18n, fldt } = useFormatter(); + const { t_i18n } = useFormatter(); const renderContent = () => { const selection = dataSelection[0]; const dateAttribute = selection.date_attribute && selection.date_attribute.length > 0 @@ -1091,119 +1067,48 @@ const StixRelationshipsTimeline = ({ && props.stixRelationships.edges.length > 0 ) { const stixRelationshipsEdges = props.stixRelationships.edges; - return ( -
- - {stixRelationshipsEdges.map((stixRelationshipEdge) => { - const stixRelationship = stixRelationshipEdge.node; - const remoteNode = stixRelationship.from - && stixRelationship.from.id === fromId - && selection.isTo !== false - ? stixRelationship.to - : stixRelationship.from; - const restricted = stixRelationship.from === null - || stixRelationship.to === null; - const link = restricted - ? null - : `${resolveLink(remoteNode.entity_type)}/${ - remoteNode.id - }/knowledge/relations/${stixRelationship.id}`; - return ( - - - {fldt(stixRelationship.created)} - - - - - - - - - - - - - {defaultValue(remoteNode)} - -
- -
-
-
-
- ); - })} -
-
- ); + const data = stixRelationshipsEdges.flatMap((stixRelationshipEdge) => { + const stixRelationship = stixRelationshipEdge.node; + const remoteNode = stixRelationship.from + && stixRelationship.from.id === fromId + && selection.isTo !== false + ? stixRelationship.to + : stixRelationship.from; + if (!remoteNode) return []; + + const restricted = stixRelationship.from === null + || stixRelationship.to === null; + const link = restricted + ? undefined + : `${resolveLink(remoteNode.entity_type)}/${ + remoteNode.id + }/knowledge/relations/${stixRelationship.id}`; + return { + value: { + ...remoteNode, + created: stixRelationship.created, + }, + link, + }; + }); + return ; } if (props) { - return ( -
- - {t_i18n('No entities of this type has been found.')} - -
- ); + return ; } - return ( -
- - - -
- ); + return ; }} /> ); }; return ( -
- - {parameters.title ?? t_i18n('Relationships timeline')} - - {variant !== 'inLine' ? ( - - {renderContent()} - - ) : ( - renderContent() - )} -
+ + {renderContent()} + ); }; diff --git a/opencti-platform/opencti-front/src/private/components/common/stix_relationships/StixRelationshipsTreeMap.jsx b/opencti-platform/opencti-front/src/private/components/common/stix_relationships/StixRelationshipsTreeMap.jsx index fafe28211477..66217a195eba 100644 --- a/opencti-platform/opencti-front/src/private/components/common/stix_relationships/StixRelationshipsTreeMap.jsx +++ b/opencti-platform/opencti-front/src/private/components/common/stix_relationships/StixRelationshipsTreeMap.jsx @@ -1,26 +1,12 @@ import React from 'react'; -import * as R from 'ramda'; import { graphql } from 'react-relay'; -import CircularProgress from '@mui/material/CircularProgress'; -import Paper from '@mui/material/Paper'; -import Typography from '@mui/material/Typography'; -import { useTheme } from '@mui/styles'; -import makeStyles from '@mui/styles/makeStyles'; -import Chart from '../charts/Chart'; import { QueryRenderer } from '../../../../relay/environment'; import { useFormatter } from '../../../../components/i18n'; -import { treeMapOptions } from '../../../../utils/Charts'; -import { defaultValue } from '../../../../utils/Graph'; import { buildFiltersAndOptionsForWidgets } from '../../../../utils/filters/filtersUtils'; - -const useStyles = makeStyles(() => ({ - paper: { - height: '100%', - margin: '10px 0 0 0', - padding: 0, - borderRadius: 4, - }, -})); +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; +import WidgetTree from '../../../../components/dashboard/WidgetTree'; const stixRelationshipsTreeMapsDistributionQuery = graphql` query StixRelationshipsTreeMapDistributionQuery( @@ -227,8 +213,6 @@ const StixRelationshipsTreeMap = ({ withExportPopover = false, isReadOnly = false, }) => { - const classes = useStyles(); - const theme = useTheme(); const { t_i18n } = useFormatter(); const renderContent = () => { let selection = {}; @@ -261,89 +245,33 @@ const StixRelationshipsTreeMap = ({ && props.stixRelationshipsDistribution && props.stixRelationshipsDistribution.length > 0 ) { - let data = props.stixRelationshipsDistribution; - if (finalField.endsWith('_id')) { - data = R.map( - (n) => R.assoc( - 'label', - `[${t_i18n(`entity_${n.entity.entity_type}`)}] ${defaultValue(n.entity)}`, - n, - ), - props.stixRelationshipsDistribution, - ); - } - const chartData = data.map((n) => ({ x: n.label, y: n.value })); - const series = [{ data: chartData }]; + const data = props.stixRelationshipsDistribution; return ( - ); } if (props) { - return ( -
- - {t_i18n('No entities of this type has been found.')} - -
- ); + return ; } - return ( -
- - - -
- ); + return ; }} /> ); }; return ( -
- - {parameters.title || title || t_i18n('Relationships distribution')} - - {variant !== 'inLine' ? ( - - {renderContent()} - - ) : ( - renderContent() - )} -
+ + {renderContent()} + ); }; diff --git a/opencti-platform/opencti-front/src/private/components/workspaces/WorkspaceShareButton.tsx b/opencti-platform/opencti-front/src/private/components/workspaces/WorkspaceShareButton.tsx index c68eca6a6c1b..5ceafd90be57 100644 --- a/opencti-platform/opencti-front/src/private/components/workspaces/WorkspaceShareButton.tsx +++ b/opencti-platform/opencti-front/src/private/components/workspaces/WorkspaceShareButton.tsx @@ -106,7 +106,7 @@ const WorkspaceShareButton = ({ workspaceId }: WorkspaceShareButtonProps) => { deletion.setDeleting(false); deletion.handleCloseDelete(); idToDelete.current = undefined; - fetchList({}, { fetchPolicy: 'network-only' }); + fetchWithFilters({ fetchPolicy: 'network-only' }); }, }); } diff --git a/opencti-platform/opencti-front/src/private/components/workspaces/dashboards/Dashboard.jsx b/opencti-platform/opencti-front/src/private/components/workspaces/dashboards/Dashboard.jsx index 8dbf53865469..52ad04b63584 100644 --- a/opencti-platform/opencti-front/src/private/components/workspaces/dashboards/Dashboard.jsx +++ b/opencti-platform/opencti-front/src/private/components/workspaces/dashboards/Dashboard.jsx @@ -693,23 +693,13 @@ const DashboardComponent = ({ workspace, noToolbar }) => { return 'Not implemented yet'; } }; - const renderRawVisualization = (widget, config) => { - const { relativeDate } = config; - const startDate = relativeDate - ? computerRelativeDate(relativeDate) - : config.startDate; - const endDate = relativeDate ? getDayStartDate() : config.endDate; + const renderRawVisualization = (widget) => { switch (widget.type) { case 'text': return ( ); default: @@ -790,7 +780,7 @@ const DashboardComponent = ({ workspace, noToolbar }) => { {widget.perspective === 'audits' && renderAuditsVisualization(removeIdFilterWidget, manifest.config)} {widget.perspective === null - && renderRawVisualization(removeIdFilterWidget, manifest.config)} + && renderRawVisualization(removeIdFilterWidget)} ; })} @@ -841,7 +831,7 @@ const DashboardComponent = ({ workspace, noToolbar }) => { {widget.perspective === 'audits' && renderAuditsVisualization(removeIdFilterWidget, manifest.config)} {widget.perspective === null - && renderRawVisualization(removeIdFilterWidget, manifest.config)} + && renderRawVisualization(removeIdFilterWidget)} ; })} diff --git a/opencti-platform/opencti-front/src/private/components/workspaces/dashboards/WidgetText.jsx b/opencti-platform/opencti-front/src/private/components/workspaces/dashboards/WidgetText.jsx index 567133ba3e32..13a9379c041f 100644 --- a/opencti-platform/opencti-front/src/private/components/workspaces/dashboards/WidgetText.jsx +++ b/opencti-platform/opencti-front/src/private/components/workspaces/dashboards/WidgetText.jsx @@ -1,19 +1,8 @@ import React from 'react'; -import Paper from '@mui/material/Paper'; -import makeStyles from '@mui/styles/makeStyles'; import MarkdownDisplay from '../../../../components/MarkdownDisplay'; +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; -const useStyles = makeStyles(() => ({ - paper: { - height: '100%', - margin: '10px 0 0 0', - padding: 0, - borderRadius: 4, - }, -})); - -const WidgetText = ({ variant, height, parameters = {} }) => { - const classes = useStyles(); +const WidgetText = ({ variant, height = undefined, parameters = {} }) => { const renderContent = () => { return ( { ); }; return ( -
- {variant !== 'inLine' ? ( - - {renderContent()} - - ) : ( - renderContent() - )} -
+ {renderContent()} + ); }; diff --git a/opencti-platform/opencti-front/src/public/LoginRoot.tsx b/opencti-platform/opencti-front/src/public/LoginRoot.tsx index 2f688f213b2d..946f5e1b7fc8 100644 --- a/opencti-platform/opencti-front/src/public/LoginRoot.tsx +++ b/opencti-platform/opencti-front/src/public/LoginRoot.tsx @@ -28,6 +28,7 @@ export const rootPublicQuery = graphql` } ...AppThemeProvider_settings ...AppIntlProvider_settings + ...PublicSettingsProvider_settings } } `; diff --git a/opencti-platform/opencti-front/src/public/PublicRoot.tsx b/opencti-platform/opencti-front/src/public/PublicRoot.tsx index 27f2dc69c8d5..5e0a6f89fad0 100644 --- a/opencti-platform/opencti-front/src/public/PublicRoot.tsx +++ b/opencti-platform/opencti-front/src/public/PublicRoot.tsx @@ -11,6 +11,7 @@ import { rootPublicQuery } from './LoginRoot'; import { LoginRootPublicQuery } from './__generated__/LoginRootPublicQuery.graphql'; import PublicDataSharing from './components/PublicDataSharing'; import PublicDashboard from './components/PublicDashboard'; +import PublicSettingsProvider from './PublicSettingsProvider'; const queryRef = loadQuery( environment, @@ -24,17 +25,19 @@ const PublicRoot = () => { queryRef, ); return ( - - - - - - - - - - - + + + + + + + + + + + + + ); }; diff --git a/opencti-platform/opencti-front/src/public/PublicSettingsProvider.tsx b/opencti-platform/opencti-front/src/public/PublicSettingsProvider.tsx new file mode 100644 index 000000000000..2533e8a46a93 --- /dev/null +++ b/opencti-platform/opencti-front/src/public/PublicSettingsProvider.tsx @@ -0,0 +1,38 @@ +import { createFragmentContainer, graphql } from 'react-relay'; +import React, { ReactNode, createContext, useContext } from 'react'; +import { PublicSettingsProvider_settings$data } from './__generated__/PublicSettingsProvider_settings.graphql'; + +interface PublicSettingsContextType { + settings?: PublicSettingsProvider_settings$data +} + +const PublicSettingsContext = createContext({}); + +interface PublicSettingsProviderProps { + children: ReactNode + settings: PublicSettingsProvider_settings$data +} + +const PublicSettingsProvider = createFragmentContainer( + ({ children, settings }: PublicSettingsProviderProps) => { + return ( + + {children} + + ); + }, + { + settings: graphql` + fragment PublicSettingsProvider_settings on Settings { + platform_map_tile_server_light + platform_map_tile_server_dark + } + `, + }, +); + +export const usePublicSettings = () => { + return useContext(PublicSettingsContext); +}; + +export default PublicSettingsProvider; diff --git a/opencti-platform/opencti-front/src/public/Root.js b/opencti-platform/opencti-front/src/public/Root.js deleted file mode 100644 index 2c7f80faa4a1..000000000000 --- a/opencti-platform/opencti-front/src/public/Root.js +++ /dev/null @@ -1,55 +0,0 @@ -import React from 'react'; -import { graphql } from 'react-relay'; -import { StyledEngineProvider } from '@mui/material/styles'; -import CssBaseline from '@mui/material/CssBaseline'; -import { QueryRenderer } from '../relay/environment'; -import { ConnectedThemeProvider } from '../components/AppThemeProvider'; -import { ConnectedIntlProvider } from '../components/AppIntlProvider'; -import Login from './components/Login'; - -const rootPublicQuery = graphql` - query RootPublicQuery { - settings { - enterprise_edition - platform_theme - platform_login_message - platform_banner_text - platform_banner_level - platform_theme_dark_logo_login - platform_theme_light_logo_login - platform_banner_text - platform_banner_level - platform_providers { - name - type - provider - } - ...AppThemeProvider_settings - ...AppIntlProvider_settings - } - } -`; - -const Root = ({ type }) => ( - { - if (props && props.settings) { - return ( - - - - - - - - - ); - } - return
; - }} - /> -); - -export default Root; diff --git a/opencti-platform/opencti-front/src/public/components/dashboard/PublicManifest.d.ts b/opencti-platform/opencti-front/src/public/components/dashboard/PublicManifest.d.ts index 9ff86ae9e0ae..e476d5b03b56 100644 --- a/opencti-platform/opencti-front/src/public/components/dashboard/PublicManifest.d.ts +++ b/opencti-platform/opencti-front/src/public/components/dashboard/PublicManifest.d.ts @@ -10,8 +10,22 @@ export interface PublicManifestWidget { static: boolean } parameters: { - title: string + title?: string + interval?: string + stacked?: boolean + legend?: boolean + distributed?: boolean } + dataSelection: { + label?: string + number?: number + attribute?: string + date_attribute?: string + centerLat?: number + centerLng?: number + zoom?: number + isTo?: boolean + }[] perspective: 'entities' | 'relationships' | 'audits' | null type: string } diff --git a/opencti-platform/opencti-front/src/public/components/dashboard/PublicWidgetContainerProps.d.ts b/opencti-platform/opencti-front/src/public/components/dashboard/PublicWidgetContainerProps.d.ts new file mode 100644 index 000000000000..b0946f0d339b --- /dev/null +++ b/opencti-platform/opencti-front/src/public/components/dashboard/PublicWidgetContainerProps.d.ts @@ -0,0 +1,9 @@ +import type { PublicManifestWidget } from './PublicManifest'; + +export interface PublicWidgetContainerProps { + startDate?: string | null | undefined + endDate?: string | null | undefined + uriKey: string + widget: PublicManifestWidget + title?: string +} diff --git a/opencti-platform/opencti-front/src/public/components/dashboard/stix_core_objects/PublicStixCoreObjectsDistributionList.tsx b/opencti-platform/opencti-front/src/public/components/dashboard/stix_core_objects/PublicStixCoreObjectsDistributionList.tsx new file mode 100644 index 000000000000..6074af33abcf --- /dev/null +++ b/opencti-platform/opencti-front/src/public/components/dashboard/stix_core_objects/PublicStixCoreObjectsDistributionList.tsx @@ -0,0 +1,137 @@ +import { graphql, PreloadedQuery, usePreloadedQuery } from 'react-relay'; +import React from 'react'; +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import type { PublicWidgetContainerProps } from '../PublicWidgetContainerProps'; +import { useFormatter } from '../../../../components/i18n'; +import useQueryLoading from '../../../../utils/hooks/useQueryLoading'; +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; +import WidgetDistributionList from '../../../../components/dashboard/WidgetDistributionList'; +import { PublicStixCoreObjectsDistributionListQuery } from './__generated__/PublicStixCoreObjectsDistributionListQuery.graphql'; + +const publicStixCoreObjectsDistributionListQuery = graphql` + query PublicStixCoreObjectsDistributionListQuery( + $startDate: DateTime + $endDate: DateTime + $uriKey: String! + $widgetId : String! + ) { + publicStixCoreObjectsDistribution( + startDate: $startDate + endDate: $endDate + uriKey: $uriKey + widgetId : $widgetId + ) { + label + value + entity { + ... on StixObject { + id + entity_type + representative { + main + } + } + ... on StixRelationship { + id + entity_type + representative { + main + } + } + ... on Creator { + id + entity_type + representative { + main + } + } + ... on Label { + value + color + } + ... on MarkingDefinition { + x_opencti_color + } + ... on Status { + template { + name + color + } + } + } + } + } +`; + +interface PublicStixCoreObjectsDistributionListComponentProps { + queryRef: PreloadedQuery +} + +const PublicStixCoreObjectsDistributionListComponent = ({ + queryRef, +}: PublicStixCoreObjectsDistributionListComponentProps) => { + const { t_i18n } = useFormatter(); + const { publicStixCoreObjectsDistribution } = usePreloadedQuery( + publicStixCoreObjectsDistributionListQuery, + queryRef, + ); + + if (publicStixCoreObjectsDistribution && publicStixCoreObjectsDistribution.length > 0) { + const data = publicStixCoreObjectsDistribution.flatMap((o) => { + if (!o) return []; + return { + label: + // eslint-disable-next-line no-nested-ternary + o.entity?.representative?.main + ? o.entity?.representative?.main + : t_i18n(`entity_${o.label}`) !== `entity_${o.label}` + ? t_i18n(`entity_${o.label}`) + : o.label, + value: o.value, + color: o.entity?.color ?? o.entity?.x_opencti_color, + id: o.entity?.id ?? null, + type: o.entity?.entity_type ?? o.label, + }; + }); + return ; + } + return ; +}; + +const PublicStixCoreObjectsDistributionList = ({ + uriKey, + widget, + startDate, + endDate, + title, +}: PublicWidgetContainerProps) => { + const { t_i18n } = useFormatter(); + const { id, parameters } = widget; + const queryRef = useQueryLoading( + publicStixCoreObjectsDistributionListQuery, + { + uriKey, + widgetId: id, + startDate, + endDate, + }, + ); + + return ( + + {queryRef ? ( + }> + + + ) : ( + + )} + + ); +}; + +export default PublicStixCoreObjectsDistributionList; diff --git a/opencti-platform/opencti-front/src/public/components/dashboard/stix_core_objects/PublicStixCoreObjectsDonut.tsx b/opencti-platform/opencti-front/src/public/components/dashboard/stix_core_objects/PublicStixCoreObjectsDonut.tsx new file mode 100644 index 000000000000..a9adbf3ce447 --- /dev/null +++ b/opencti-platform/opencti-front/src/public/components/dashboard/stix_core_objects/PublicStixCoreObjectsDonut.tsx @@ -0,0 +1,240 @@ +import { graphql, PreloadedQuery, usePreloadedQuery } from 'react-relay'; +import React from 'react'; +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import type { PublicWidgetContainerProps } from '../PublicWidgetContainerProps'; +import { useFormatter } from '../../../../components/i18n'; +import useQueryLoading from '../../../../utils/hooks/useQueryLoading'; +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; +import { PublicStixCoreObjectsDonutQuery } from './__generated__/PublicStixCoreObjectsDonutQuery.graphql'; +import WidgetDonut from '../../../../components/dashboard/WidgetDonut'; +import type { PublicManifestWidget } from '../PublicManifest'; + +const publicStixCoreObjectsDonutQuery = graphql` + query PublicStixCoreObjectsDonutQuery( + $startDate: DateTime + $endDate: DateTime + $uriKey: String! + $widgetId : String! + ) { + publicStixCoreObjectsDistribution( + startDate: $startDate + endDate: $endDate + uriKey: $uriKey + widgetId : $widgetId + ) { + label + value + entity { + ... on BasicObject { + entity_type + } + ... on BasicRelationship { + entity_type + } + ... on AttackPattern { + name + description + } + ... on Campaign { + name + description + } + ... on CourseOfAction { + name + description + } + ... on Individual { + name + description + } + ... on Organization { + name + description + } + ... on Sector { + name + description + } + ... on System { + name + description + } + ... on Indicator { + name + description + } + ... on Infrastructure { + name + description + } + ... on IntrusionSet { + name + description + } + ... on Position { + name + description + } + ... on City { + name + description + } + ... on AdministrativeArea { + name + description + } + ... on Country { + name + description + } + ... on Region { + name + description + } + ... on Malware { + name + description + } + ... on MalwareAnalysis { + result_name + } + ... on ThreatActor { + name + description + } + ... on Tool { + name + description + } + ... on Vulnerability { + name + description + } + ... on Incident { + name + description + } + ... on Event { + name + description + } + ... on Channel { + name + description + } + ... on Narrative { + name + description + } + ... on Language { + name + } + ... on DataComponent { + name + } + ... on DataSource { + name + } + ... on Case { + name + } + ... on StixCyberObservable { + observable_value + } + ... on MarkingDefinition { + definition_type + definition + x_opencti_color + } + ... on KillChainPhase { + kill_chain_name + phase_name + } + ... on Creator { + name + } + ... on Label { + value + color + } + ... on Status { + template { + name + color + } + } + } + } + } +`; + +interface PublicStixCoreObjectsDonutComponentProps { + dataSelection: PublicManifestWidget['dataSelection'] + queryRef: PreloadedQuery +} + +const PublicStixCoreObjectsDonutComponent = ({ + dataSelection, + queryRef, +}: PublicStixCoreObjectsDonutComponentProps) => { + const { publicStixCoreObjectsDistribution } = usePreloadedQuery( + publicStixCoreObjectsDonutQuery, + queryRef, + ); + + if ( + publicStixCoreObjectsDistribution + && publicStixCoreObjectsDistribution.length > 0 + ) { + return ( + + ); + } + return ; +}; + +const PublicStixCoreObjectsDonut = ({ + uriKey, + widget, + startDate, + endDate, + title, +}: PublicWidgetContainerProps) => { + const { t_i18n } = useFormatter(); + const { id, parameters, dataSelection } = widget; + const queryRef = useQueryLoading( + publicStixCoreObjectsDonutQuery, + { + uriKey, + widgetId: id, + startDate, + endDate, + }, + ); + + return ( + + {queryRef ? ( + }> + + + ) : ( + + )} + + ); +}; + +export default PublicStixCoreObjectsDonut; diff --git a/opencti-platform/opencti-front/src/public/components/dashboard/stix_core_objects/PublicStixCoreObjectsHorizontalBars.tsx b/opencti-platform/opencti-front/src/public/components/dashboard/stix_core_objects/PublicStixCoreObjectsHorizontalBars.tsx new file mode 100644 index 000000000000..bf05a5b6a007 --- /dev/null +++ b/opencti-platform/opencti-front/src/public/components/dashboard/stix_core_objects/PublicStixCoreObjectsHorizontalBars.tsx @@ -0,0 +1,310 @@ +import { graphql, PreloadedQuery, usePreloadedQuery } from 'react-relay'; +import React from 'react'; +import { useTheme } from '@mui/styles'; +import type { PublicManifestWidget } from '../PublicManifest'; +import { useFormatter } from '../../../../components/i18n'; +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import { itemColor } from '../../../../utils/Colors'; +import { defaultValue } from '../../../../utils/Graph'; +import WidgetHorizontalBars from '../../../../components/dashboard/WidgetHorizontalBars'; +import type { Theme } from '../../../../components/Theme'; +import type { PublicWidgetContainerProps } from '../PublicWidgetContainerProps'; +import useQueryLoading from '../../../../utils/hooks/useQueryLoading'; +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; +import { PublicStixCoreObjectsHorizontalBarsQuery } from './__generated__/PublicStixCoreObjectsHorizontalBarsQuery.graphql'; + +const publicStixCoreObjectsHorizontalBarsQuery = graphql` + query PublicStixCoreObjectsHorizontalBarsQuery( + $startDate: DateTime + $endDate: DateTime + $uriKey: String! + $widgetId : String! + ) { + publicStixCoreObjectsDistribution( + startDate: $startDate + endDate: $endDate + uriKey: $uriKey + widgetId : $widgetId + ) { + label + value + entity { + ... on BasicObject { + entity_type + id + } + ... on BasicRelationship { + entity_type + id + } + ... on AttackPattern { + name + description + } + ... on Campaign { + name + description + } + ... on CourseOfAction { + name + description + } + ... on Individual { + name + description + } + ... on Organization { + name + description + } + ... on Sector { + name + description + } + ... on System { + name + description + } + ... on Indicator { + name + description + } + ... on Infrastructure { + name + description + } + ... on IntrusionSet { + name + description + } + ... on Position { + name + description + } + ... on City { + name + description + } + ... on AdministrativeArea { + name + description + } + ... on Country { + name + description + } + ... on Region { + name + description + } + ... on Malware { + name + description + } + ... on MalwareAnalysis { + result_name + } + ... on ThreatActor { + name + description + } + ... on Tool { + name + description + } + ... on Vulnerability { + name + description + } + ... on Incident { + name + description + } + ... on Event { + name + description + } + ... on Channel { + name + description + } + ... on Narrative { + name + description + } + ... on Language { + name + } + ... on DataComponent { + name + } + ... on DataSource { + name + } + ... on Case { + name + } + ... on StixCyberObservable { + observable_value + } + ... on MarkingDefinition { + definition_type + definition + x_opencti_color + } + ... on Creator { + name + } + ... on Report { + name + } + ... on Grouping { + name + } + ... on Note { + attribute_abstract + content + } + ... on Opinion { + opinion + } + ... on Label { + value + color + } + ... on Status { + template { + name + color + } + } + } + } + } +`; + +interface PublicStixCoreObjectsHorizontalBarsComponentProps { + parameters: PublicManifestWidget['parameters'] + dataSelection: PublicManifestWidget['dataSelection'] + queryRef: PreloadedQuery +} + +const PublicStixCoreObjectsHorizontalBarsComponent = ({ + parameters, + dataSelection, + queryRef, +}: PublicStixCoreObjectsHorizontalBarsComponentProps) => { + const theme = useTheme(); + const { t_i18n } = useFormatter(); + const { publicStixCoreObjectsDistribution } = usePreloadedQuery( + publicStixCoreObjectsHorizontalBarsQuery, + queryRef, + ); + + if ( + publicStixCoreObjectsDistribution + && publicStixCoreObjectsDistribution.length > 0 + ) { + const selection = dataSelection[0]; + const data = publicStixCoreObjectsDistribution.map((n) => { + let color = selection.attribute?.endsWith('_id') + ? itemColor(n?.entity?.entity_type) + : itemColor(n?.label); + if (n?.entity?.color) { + color = theme.palette.mode === 'light' && n.entity.color === '#ffffff' + ? '#000000' + : n.entity.color; + } + if (n?.entity?.x_opencti_color) { + color = theme.palette.mode === 'light' + && n.entity.x_opencti_color === '#ffffff' + ? '#000000' + : n.entity.x_opencti_color; + } + if (n?.entity?.template?.color) { + color = theme.palette.mode === 'light' + && n.entity.template.color === '#ffffff' + ? '#000000' + : n.entity.template.color; + } + return { + // eslint-disable-next-line no-nested-ternary + x: selection.attribute?.endsWith('_id') + ? defaultValue(n?.entity) + : selection.attribute === 'entity_type' + ? t_i18n(`entity_${n?.label}`) + : n?.label, + y: n?.value, + fillColor: color, + }; + }); + const chartData = [ + { + name: selection.label || t_i18n('Number of relationships'), + data, + }, + ]; + const redirectionUtils = selection.attribute === 'name' + ? publicStixCoreObjectsDistribution.flatMap((n) => { + if (!n || !n.entity) return []; + return { + id: n.entity.id, + entity_type: n.entity.entity_type, + }; + }) + : undefined; + + return ( + + ); + } + return ; +}; + +const PublicStixCoreObjectsHorizontalBars = ({ + uriKey, + widget, + startDate, + endDate, + title, +}: PublicWidgetContainerProps) => { + const { t_i18n } = useFormatter(); + const { id, parameters, dataSelection } = widget; + const queryRef = useQueryLoading( + publicStixCoreObjectsHorizontalBarsQuery, + { + uriKey, + widgetId: id, + startDate, + endDate, + }, + ); + + return ( + + {queryRef ? ( + }> + + + ) : ( + + )} + + ); +}; + +export default PublicStixCoreObjectsHorizontalBars; diff --git a/opencti-platform/opencti-front/src/public/components/dashboard/stix_core_objects/PublicStixCoreObjectsList.tsx b/opencti-platform/opencti-front/src/public/components/dashboard/stix_core_objects/PublicStixCoreObjectsList.tsx new file mode 100644 index 000000000000..6e56295c5117 --- /dev/null +++ b/opencti-platform/opencti-front/src/public/components/dashboard/stix_core_objects/PublicStixCoreObjectsList.tsx @@ -0,0 +1,268 @@ +import { graphql, PreloadedQuery, usePreloadedQuery } from 'react-relay'; +import React from 'react'; +import WidgetListCoreObjects from '../../../../components/dashboard/WidgetListCoreObjects'; +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import type { PublicWidgetContainerProps } from '../PublicWidgetContainerProps'; +import { useFormatter } from '../../../../components/i18n'; +import useQueryLoading from '../../../../utils/hooks/useQueryLoading'; +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; +import { PublicStixCoreObjectsListQuery } from './__generated__/PublicStixCoreObjectsListQuery.graphql'; + +const publicStixCoreObjectsListQuery = graphql` + query PublicStixCoreObjectsListQuery( + $startDate: DateTime + $endDate: DateTime + $uriKey: String! + $widgetId : String! + ) { + publicStixCoreObjects( + startDate: $startDate + endDate: $endDate + uriKey: $uriKey + widgetId : $widgetId + ) { + edges { + node { + id + entity_type + created_at + ... on StixDomainObject { + created + modified + } + ... on AttackPattern { + name + description + } + ... on Campaign { + name + description + } + ... on Note { + attribute_abstract + } + ... on ObservedData { + first_observed + last_observed + } + ... on Opinion { + opinion + } + ... on Report { + name + description + published + } + ... on Grouping { + name + description + } + ... on CourseOfAction { + name + description + } + ... on Individual { + name + description + } + ... on Organization { + name + description + } + ... on Sector { + name + description + } + ... on System { + name + description + } + ... on Indicator { + name + description + } + ... on Infrastructure { + name + description + } + ... on IntrusionSet { + name + description + } + ... on Position { + name + description + } + ... on City { + name + description + } + ... on AdministrativeArea { + name + description + } + ... on Country { + name + description + } + ... on Region { + name + description + } + ... on Malware { + name + description + } + ... on MalwareAnalysis { + result_name + } + ... on ThreatActor { + name + description + } + ... on Tool { + name + description + } + ... on Vulnerability { + name + description + } + ... on Incident { + name + description + } + ... on Event { + name + description + } + ... on Channel { + name + description + } + ... on Narrative { + name + description + } + ... on Language { + name + } + ... on DataComponent { + name + } + ... on DataSource { + name + } + ... on Case { + name + } + ... on Task { + name + description + } + ... on StixCyberObservable { + observable_value + } + createdBy { + ... on Identity { + id + name + entity_type + } + } + objectLabel { + id + value + color + } + objectMarking { + id + definition_type + definition + x_opencti_order + x_opencti_color + } + ... on StixDomainObject { + status { + id + order + template { + name + color + } + } + workflowEnabled + } + } + } + } + } +`; + +interface PublicStixCoreObjectsListComponentProps { + queryRef: PreloadedQuery + dateAttribute: string +} + +const PublicStixCoreObjectsListComponent = ({ + queryRef, + dateAttribute, +}: PublicStixCoreObjectsListComponentProps) => { + const { publicStixCoreObjects } = usePreloadedQuery( + publicStixCoreObjectsListQuery, + queryRef, + ); + + if (publicStixCoreObjects?.edges && publicStixCoreObjects.edges.length > 0) { + return ( + + ); + } + return ; +}; + +const PublicStixCoreObjectsList = ({ + uriKey, + widget, + startDate, + endDate, + title, +}: PublicWidgetContainerProps) => { + const { t_i18n } = useFormatter(); + const { id, parameters, dataSelection } = widget; + const queryRef = useQueryLoading( + publicStixCoreObjectsListQuery, + { + uriKey, + widgetId: id, + startDate, + endDate, + }, + ); + + const dateAttribute = dataSelection[0].date_attribute ?? 'created_at'; + + return ( + + {queryRef ? ( + }> + + + ) : ( + + )} + + ); +}; + +export default PublicStixCoreObjectsList; diff --git a/opencti-platform/opencti-front/src/public/components/dashboard/stix_core_objects/PublicStixCoreObjectsMultiAreaChart.tsx b/opencti-platform/opencti-front/src/public/components/dashboard/stix_core_objects/PublicStixCoreObjectsMultiAreaChart.tsx new file mode 100644 index 000000000000..e6067f04eada --- /dev/null +++ b/opencti-platform/opencti-front/src/public/components/dashboard/stix_core_objects/PublicStixCoreObjectsMultiAreaChart.tsx @@ -0,0 +1,112 @@ +import { graphql, PreloadedQuery, usePreloadedQuery } from 'react-relay'; +import React from 'react'; +import type { PublicManifestWidget } from '../PublicManifest'; +import { useFormatter } from '../../../../components/i18n'; +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import WidgetMultiAreas from '../../../../components/dashboard/WidgetMultiAreas'; +import type { PublicWidgetContainerProps } from '../PublicWidgetContainerProps'; +import useQueryLoading from '../../../../utils/hooks/useQueryLoading'; +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; +import { PublicStixCoreObjectsMultiAreaChartQuery } from './__generated__/PublicStixCoreObjectsMultiAreaChartQuery.graphql'; +import { monthsAgo, now } from '../../../../utils/Time'; + +const publicStixCoreObjectsMultiAreaChartQuery = graphql` + query PublicStixCoreObjectsMultiAreaChartQuery( + $startDate: DateTime + $endDate: DateTime + $uriKey: String! + $widgetId : String! + ) { + publicStixCoreObjectsMultiTimeSeries( + startDate: $startDate + endDate: $endDate + uriKey: $uriKey + widgetId : $widgetId + ) { + data { + date + value + } + } + } +`; + +interface PublicStixCoreObjectsMultiAreaChartComponentProps { + parameters: PublicManifestWidget['parameters'] + dataSelection: PublicManifestWidget['dataSelection'] + queryRef: PreloadedQuery +} + +const PublicStixCoreObjectsMultiAreaChartComponent = ({ + parameters, + dataSelection, + queryRef, +}: PublicStixCoreObjectsMultiAreaChartComponentProps) => { + const { t_i18n } = useFormatter(); + const { publicStixCoreObjectsMultiTimeSeries } = usePreloadedQuery( + publicStixCoreObjectsMultiAreaChartQuery, + queryRef, + ); + + if (publicStixCoreObjectsMultiTimeSeries) { + return ( + ({ + name: dataSelection[i].label ?? t_i18n('Number of entities'), + data: (serie?.data ?? []).map((entry) => ({ + x: new Date(entry?.date), + y: entry?.value, + })), + }))} + interval={parameters.interval} + isStacked={parameters.stacked} + hasLegend={parameters.legend} + withExport={false} + readonly={true} + /> + ); + } + return ; +}; + +const PublicStixCoreObjectsMultiAreaChart = ({ + uriKey, + widget, + startDate = monthsAgo(12), + endDate = now(), + title, +}: PublicWidgetContainerProps) => { + const { t_i18n } = useFormatter(); + const { id, parameters, dataSelection } = widget; + const queryRef = useQueryLoading( + publicStixCoreObjectsMultiAreaChartQuery, + { + uriKey, + widgetId: id, + startDate, + endDate, + }, + ); + + return ( + + {queryRef ? ( + }> + + + ) : ( + + )} + + ); +}; + +export default PublicStixCoreObjectsMultiAreaChart; diff --git a/opencti-platform/opencti-front/src/public/components/dashboard/stix_core_objects/PublicStixCoreObjectsMultiHeatMap.tsx b/opencti-platform/opencti-front/src/public/components/dashboard/stix_core_objects/PublicStixCoreObjectsMultiHeatMap.tsx new file mode 100644 index 000000000000..a9e3949b1193 --- /dev/null +++ b/opencti-platform/opencti-front/src/public/components/dashboard/stix_core_objects/PublicStixCoreObjectsMultiHeatMap.tsx @@ -0,0 +1,118 @@ +import { graphql, PreloadedQuery, usePreloadedQuery } from 'react-relay'; +import React from 'react'; +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import type { PublicWidgetContainerProps } from '../PublicWidgetContainerProps'; +import { useFormatter } from '../../../../components/i18n'; +import useQueryLoading from '../../../../utils/hooks/useQueryLoading'; +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; +import { PublicStixCoreObjectsMultiHeatMapQuery } from './__generated__/PublicStixCoreObjectsMultiHeatMapQuery.graphql'; +import WidgetMultiHeatMap from '../../../../components/dashboard/WidgetMultiHeatMap'; +import type { PublicManifestWidget } from '../PublicManifest'; +import { monthsAgo, now } from '../../../../utils/Time'; + +const publicStixCoreObjectsMultiHeatMapQuery = graphql` + query PublicStixCoreObjectsMultiHeatMapQuery( + $startDate: DateTime + $endDate: DateTime + $uriKey: String! + $widgetId : String! + ) { + publicStixCoreObjectsMultiTimeSeries( + startDate: $startDate + endDate: $endDate + uriKey: $uriKey + widgetId : $widgetId + ) { + data { + date + value + } + } + } +`; + +interface PublicStixCoreObjectsMultiHeatMapComponentProps { + parameters: PublicManifestWidget['parameters'] + dataSelection: PublicManifestWidget['dataSelection'] + queryRef: PreloadedQuery +} + +const PublicStixCoreObjectsMultiHeatMapComponent = ({ + parameters, + dataSelection, + queryRef, +}: PublicStixCoreObjectsMultiHeatMapComponentProps) => { + const { t_i18n } = useFormatter(); + const { publicStixCoreObjectsMultiTimeSeries } = usePreloadedQuery( + publicStixCoreObjectsMultiHeatMapQuery, + queryRef, + ); + + if (publicStixCoreObjectsMultiTimeSeries) { + const allValues = publicStixCoreObjectsMultiTimeSeries + .map((serie) => (serie?.data ?? []).flatMap((o) => o?.value ?? [])) + .flat(); + const maxValue = Math.max(...allValues); + const minValue = Math.min(...allValues); + + return ( + ({ + name: dataSelection[i].label ?? t_i18n('Number of entities'), + data: (serie?.data ?? []).map((entry) => ({ + x: new Date(entry?.date), + y: entry?.value, + })), + })).sort((a, b) => b.name.localeCompare(a.name))} + minValue={minValue} + maxValue={maxValue} + isStacked={parameters.stacked} + withExport={false} + readonly={true} + /> + ); + } + return ; +}; + +const PublicStixCoreObjectsMultiHeatMap = ({ + uriKey, + widget, + startDate = monthsAgo(12), + endDate = now(), + title, +}: PublicWidgetContainerProps) => { + const { t_i18n } = useFormatter(); + const { id, parameters, dataSelection } = widget; + const queryRef = useQueryLoading( + publicStixCoreObjectsMultiHeatMapQuery, + { + uriKey, + widgetId: id, + startDate, + endDate, + }, + ); + + return ( + + {queryRef ? ( + }> + + + ) : ( + + )} + + ); +}; + +export default PublicStixCoreObjectsMultiHeatMap; diff --git a/opencti-platform/opencti-front/src/public/components/dashboard/stix_core_objects/PublicStixCoreObjectsMultiLineChart.tsx b/opencti-platform/opencti-front/src/public/components/dashboard/stix_core_objects/PublicStixCoreObjectsMultiLineChart.tsx new file mode 100644 index 000000000000..afa51af9d9bc --- /dev/null +++ b/opencti-platform/opencti-front/src/public/components/dashboard/stix_core_objects/PublicStixCoreObjectsMultiLineChart.tsx @@ -0,0 +1,111 @@ +import { graphql, PreloadedQuery, usePreloadedQuery } from 'react-relay'; +import React from 'react'; +import type { PublicManifestWidget } from '../PublicManifest'; +import { useFormatter } from '../../../../components/i18n'; +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import WidgetMultiLines from '../../../../components/dashboard/WidgetMultiLines'; +import type { PublicWidgetContainerProps } from '../PublicWidgetContainerProps'; +import useQueryLoading from '../../../../utils/hooks/useQueryLoading'; +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; +import { PublicStixCoreObjectsMultiLineChartQuery } from './__generated__/PublicStixCoreObjectsMultiLineChartQuery.graphql'; +import { monthsAgo, now } from '../../../../utils/Time'; + +const publicStixCoreObjectsMultiLineChartQuery = graphql` + query PublicStixCoreObjectsMultiLineChartQuery( + $startDate: DateTime + $endDate: DateTime + $uriKey: String! + $widgetId : String! + ) { + publicStixCoreObjectsMultiTimeSeries( + startDate: $startDate + endDate: $endDate + uriKey: $uriKey + widgetId : $widgetId + ) { + data { + date + value + } + } + } +`; + +interface PublicStixCoreObjectsMultiLineChartComponentProps { + parameters: PublicManifestWidget['parameters'] + dataSelection: PublicManifestWidget['dataSelection'] + queryRef: PreloadedQuery +} + +const PublicStixCoreObjectsMultiLineChartComponent = ({ + parameters, + dataSelection, + queryRef, +}: PublicStixCoreObjectsMultiLineChartComponentProps) => { + const { t_i18n } = useFormatter(); + const { publicStixCoreObjectsMultiTimeSeries } = usePreloadedQuery( + publicStixCoreObjectsMultiLineChartQuery, + queryRef, + ); + + if (publicStixCoreObjectsMultiTimeSeries) { + return ( + ({ + name: dataSelection[i].label ?? t_i18n('Number of entities'), + data: (serie?.data ?? []).map((entry) => ({ + x: new Date(entry?.date), + y: entry?.value, + })), + }))} + interval={parameters.interval} + hasLegend={parameters.legend} + withExport={false} + readonly={true} + /> + ); + } + return ; +}; + +const PublicStixCoreObjectsMultiLineChart = ({ + uriKey, + widget, + startDate = monthsAgo(12), + endDate = now(), + title, +}: PublicWidgetContainerProps) => { + const { t_i18n } = useFormatter(); + const { id, parameters, dataSelection } = widget; + const queryRef = useQueryLoading( + publicStixCoreObjectsMultiLineChartQuery, + { + uriKey, + widgetId: id, + startDate, + endDate, + }, + ); + + return ( + + {queryRef ? ( + }> + + + ) : ( + + )} + + ); +}; + +export default PublicStixCoreObjectsMultiLineChart; diff --git a/opencti-platform/opencti-front/src/public/components/dashboard/stix_core_objects/PublicStixCoreObjectsMultiVerticalBars.tsx b/opencti-platform/opencti-front/src/public/components/dashboard/stix_core_objects/PublicStixCoreObjectsMultiVerticalBars.tsx new file mode 100644 index 000000000000..d7613a58ce98 --- /dev/null +++ b/opencti-platform/opencti-front/src/public/components/dashboard/stix_core_objects/PublicStixCoreObjectsMultiVerticalBars.tsx @@ -0,0 +1,112 @@ +import { graphql, PreloadedQuery, usePreloadedQuery } from 'react-relay'; +import React from 'react'; +import type { PublicWidgetContainerProps } from '../PublicWidgetContainerProps'; +import { useFormatter } from '../../../../components/i18n'; +import useQueryLoading from '../../../../utils/hooks/useQueryLoading'; +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import WidgetVerticalBars from '../../../../components/dashboard/WidgetVerticalBars'; +import { PublicStixCoreObjectsMultiVerticalBarsQuery } from './__generated__/PublicStixCoreObjectsMultiVerticalBarsQuery.graphql'; +import type { PublicManifestWidget } from '../PublicManifest'; +import { monthsAgo, now } from '../../../../utils/Time'; + +const publicStixCoreObjectsMultiVerticalBarsQuery = graphql` + query PublicStixCoreObjectsMultiVerticalBarsQuery( + $startDate: DateTime + $endDate: DateTime + $uriKey: String! + $widgetId : String! + ) { + publicStixCoreObjectsMultiTimeSeries( + startDate: $startDate + endDate: $endDate + uriKey: $uriKey + widgetId : $widgetId + ) { + data { + date + value + } + } + } +`; + +interface PublicStixCoreObjectsMultiVerticalBarsComponentProps { + parameters: PublicManifestWidget['parameters'] + dataSelection: PublicManifestWidget['dataSelection'] + queryRef: PreloadedQuery +} + +const PublicStixCoreObjectsMultiVerticalBarsComponent = ({ + parameters, + dataSelection, + queryRef, +}: PublicStixCoreObjectsMultiVerticalBarsComponentProps) => { + const { t_i18n } = useFormatter(); + const { publicStixCoreObjectsMultiTimeSeries } = usePreloadedQuery( + publicStixCoreObjectsMultiVerticalBarsQuery, + queryRef, + ); + + if (publicStixCoreObjectsMultiTimeSeries) { + return ( + ({ + name: dataSelection[i].label ?? t_i18n('Number of entities'), + data: (serie?.data ?? []).map((entry) => ({ + x: new Date(entry?.date), + y: entry?.value, + })), + }))} + interval={parameters.interval} + isStacked={parameters.stacked} + hasLegend={parameters.legend} + withExport={false} + readonly={true} + /> + ); + } + return ; +}; + +const PublicStixCoreObjectsMultiVerticalBars = ({ + uriKey, + widget, + startDate = monthsAgo(12), + endDate = now(), + title, +}: PublicWidgetContainerProps) => { + const { t_i18n } = useFormatter(); + const { id, parameters, dataSelection } = widget; + const queryRef = useQueryLoading( + publicStixCoreObjectsMultiVerticalBarsQuery, + { + uriKey, + widgetId: id, + startDate, + endDate, + }, + ); + + return ( + + {queryRef ? ( + }> + + + ) : ( + + )} + + ); +}; + +export default PublicStixCoreObjectsMultiVerticalBars; diff --git a/opencti-platform/opencti-front/src/public/components/dashboard/PublicStixCoreObjectsNumber.tsx b/opencti-platform/opencti-front/src/public/components/dashboard/stix_core_objects/PublicStixCoreObjectsNumber.tsx similarity index 70% rename from opencti-platform/opencti-front/src/public/components/dashboard/PublicStixCoreObjectsNumber.tsx rename to opencti-platform/opencti-front/src/public/components/dashboard/stix_core_objects/PublicStixCoreObjectsNumber.tsx index a3af66d1af45..fee383217a3f 100644 --- a/opencti-platform/opencti-front/src/public/components/dashboard/PublicStixCoreObjectsNumber.tsx +++ b/opencti-platform/opencti-front/src/public/components/dashboard/stix_core_objects/PublicStixCoreObjectsNumber.tsx @@ -1,13 +1,13 @@ import { graphql, PreloadedQuery, usePreloadedQuery } from 'react-relay'; import React from 'react'; -import useQueryLoading from '../../../utils/hooks/useQueryLoading'; +import useQueryLoading from '../../../../utils/hooks/useQueryLoading'; import { PublicStixCoreObjectsNumberQuery } from './__generated__/PublicStixCoreObjectsNumberQuery.graphql'; -import WidgetContainer from '../../../components/dashboard/WidgetContainer'; -import { useFormatter } from '../../../components/i18n'; -import WidgetLoader from '../../../components/dashboard/WidgetLoader'; -import WidgetNumber from '../../../components/dashboard/WidgetNumber'; -import WidgetNoData from '../../../components/dashboard/WidgetNoData'; -import type { PublicManifestWidget } from './PublicManifest'; +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import { useFormatter } from '../../../../components/i18n'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; +import WidgetNumber from '../../../../components/dashboard/WidgetNumber'; +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import type { PublicWidgetContainerProps } from '../PublicWidgetContainerProps'; const publicStixCoreObjectsNumberQuery = graphql` query PublicStixCoreObjectsNumberQuery( @@ -47,19 +47,13 @@ const PublicStixCoreObjectsNumberComponent = ({ return ; }; -interface PublicStixCoreObjectsNumberProps { - startDate: string | null | undefined - endDate: string | null | undefined - uriKey: string - widget: PublicManifestWidget -} - const PublicStixCoreObjectsNumber = ({ uriKey, widget, startDate, endDate, -}: PublicStixCoreObjectsNumberProps) => { + title, +}: PublicWidgetContainerProps) => { const { t_i18n } = useFormatter(); const { id, parameters } = widget; const queryRef = useQueryLoading( @@ -74,7 +68,7 @@ const PublicStixCoreObjectsNumber = ({ return ( {queryRef ? ( diff --git a/opencti-platform/opencti-front/src/public/components/dashboard/stix_core_objects/PublicStixCoreObjectsRadar.tsx b/opencti-platform/opencti-front/src/public/components/dashboard/stix_core_objects/PublicStixCoreObjectsRadar.tsx new file mode 100644 index 000000000000..079a83614580 --- /dev/null +++ b/opencti-platform/opencti-front/src/public/components/dashboard/stix_core_objects/PublicStixCoreObjectsRadar.tsx @@ -0,0 +1,239 @@ +import { graphql, PreloadedQuery, usePreloadedQuery } from 'react-relay'; +import React from 'react'; +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import type { PublicWidgetContainerProps } from '../PublicWidgetContainerProps'; +import { useFormatter } from '../../../../components/i18n'; +import useQueryLoading from '../../../../utils/hooks/useQueryLoading'; +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; +import { PublicStixCoreObjectsRadarQuery } from './__generated__/PublicStixCoreObjectsRadarQuery.graphql'; +import WidgetRadar from '../../../../components/dashboard/WidgetRadar'; +import type { PublicManifestWidget } from '../PublicManifest'; + +const publicStixCoreObjectsRadarQuery = graphql` + query PublicStixCoreObjectsRadarQuery( + $startDate: DateTime + $endDate: DateTime + $uriKey: String! + $widgetId : String! + ) { + publicStixCoreObjectsDistribution( + startDate: $startDate + endDate: $endDate + uriKey: $uriKey + widgetId : $widgetId + ) { + label + value + entity { + ... on BasicObject { + entity_type + } + ... on BasicRelationship { + entity_type + } + ... on AttackPattern { + name + description + x_mitre_id + } + ... on Campaign { + name + description + } + ... on CourseOfAction { + name + description + } + ... on Individual { + name + description + } + ... on Organization { + name + description + } + ... on Sector { + name + description + } + ... on System { + name + description + } + ... on Indicator { + name + description + } + ... on Infrastructure { + name + description + } + ... on IntrusionSet { + name + description + } + ... on Position { + name + description + } + ... on City { + name + description + } + ... on AdministrativeArea { + name + description + } + ... on Country { + name + description + } + ... on Region { + name + description + } + ... on Malware { + name + description + } + ... on MalwareAnalysis { + result_name + } + ... on ThreatActor { + name + description + } + ... on Tool { + name + description + } + ... on Vulnerability { + name + description + } + ... on Incident { + name + description + } + ... on Event { + name + description + } + ... on Channel { + name + description + } + ... on Narrative { + name + description + } + ... on Language { + name + } + ... on DataComponent { + name + } + ... on DataSource { + name + } + ... on Case { + name + } + ... on StixCyberObservable { + observable_value + } + ... on MarkingDefinition { + definition_type + definition + } + ... on KillChainPhase { + kill_chain_name + phase_name + } + ... on Creator { + name + } + ... on Label { + value + } + ... on Status { + template { + name + } + } + } + } + } +`; + +interface PublicStixCoreObjectsRadarComponentProps { + dataSelection: PublicManifestWidget['dataSelection'] + queryRef: PreloadedQuery +} + +const PublicStixCoreObjectsRadarComponent = ({ + dataSelection, + queryRef, +}: PublicStixCoreObjectsRadarComponentProps) => { + const { publicStixCoreObjectsDistribution } = usePreloadedQuery( + publicStixCoreObjectsRadarQuery, + queryRef, + ); + + if ( + publicStixCoreObjectsDistribution + && publicStixCoreObjectsDistribution.length > 0 + ) { + return ( + + ); + } + return ; +}; + +const PublicStixCoreObjectsRadar = ({ + uriKey, + widget, + startDate, + endDate, + title, +}: PublicWidgetContainerProps) => { + const { t_i18n } = useFormatter(); + const { id, parameters, dataSelection } = widget; + const queryRef = useQueryLoading( + publicStixCoreObjectsRadarQuery, + { + uriKey, + widgetId: id, + startDate, + endDate, + }, + ); + + return ( + + {queryRef ? ( + }> + + + ) : ( + + )} + + ); +}; + +export default PublicStixCoreObjectsRadar; diff --git a/opencti-platform/opencti-front/src/public/components/dashboard/stix_core_objects/PublicStixCoreObjectsTimeline.tsx b/opencti-platform/opencti-front/src/public/components/dashboard/stix_core_objects/PublicStixCoreObjectsTimeline.tsx new file mode 100644 index 000000000000..da71b3ece45a --- /dev/null +++ b/opencti-platform/opencti-front/src/public/components/dashboard/stix_core_objects/PublicStixCoreObjectsTimeline.tsx @@ -0,0 +1,261 @@ +import { graphql, PreloadedQuery, usePreloadedQuery } from 'react-relay'; +import React from 'react'; +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import type { PublicWidgetContainerProps } from '../PublicWidgetContainerProps'; +import { useFormatter } from '../../../../components/i18n'; +import useQueryLoading from '../../../../utils/hooks/useQueryLoading'; +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; +import { PublicStixCoreObjectsTimelineQuery } from './__generated__/PublicStixCoreObjectsTimelineQuery.graphql'; +import { resolveLink } from '../../../../utils/Entity'; +import WidgetTimeline from '../../../../components/dashboard/WidgetTimeline'; + +const publicStixCoreObjectsTimelineQuery = graphql` + query PublicStixCoreObjectsTimelineQuery( + $startDate: DateTime + $endDate: DateTime + $uriKey: String! + $widgetId : String! + ) { + publicStixCoreObjects( + startDate: $startDate + endDate: $endDate + uriKey: $uriKey + widgetId : $widgetId + ) { + edges { + node { + id + entity_type + created_at + ... on StixDomainObject { + created + modified + } + ... on AttackPattern { + name + description + } + ... on Campaign { + name + description + } + ... on Note { + attribute_abstract + } + ... on Opinion { + opinion + } + ... on ObservedData { + first_observed + last_observed + } + ... on Opinion { + opinion + } + ... on Report { + name + description + published + } + ... on Grouping { + name + description + } + ... on CourseOfAction { + name + description + } + ... on Individual { + name + description + } + ... on Organization { + name + description + } + ... on Sector { + name + description + } + ... on System { + name + description + } + ... on Indicator { + name + description + } + ... on Infrastructure { + name + description + } + ... on IntrusionSet { + name + description + } + ... on Position { + name + description + } + ... on City { + name + description + } + ... on AdministrativeArea { + name + description + } + ... on Country { + name + description + } + ... on Region { + name + description + } + ... on Malware { + name + description + } + ... on MalwareAnalysis { + result_name + } + ... on ThreatActor { + name + description + } + ... on Tool { + name + description + } + ... on Vulnerability { + name + description + } + ... on Incident { + name + description + } + ... on Event { + name + description + } + ... on Channel { + name + description + } + ... on Narrative { + name + description + } + ... on Language { + name + } + ... on DataComponent { + name + } + ... on DataSource { + name + } + ... on Case { + name + } + ... on Note { + attribute_abstract + content + } + ... on Opinion { + opinion + } + ... on StixCyberObservable { + observable_value + } + createdBy { + ... on Identity { + id + name + entity_type + } + } + objectMarking { + id + definition_type + definition + x_opencti_order + x_opencti_color + } + } + } + } + } +`; + +interface PublicStixCoreObjectsTimelineComponentProps { + queryRef: PreloadedQuery +} + +const PublicStixCoreObjectsTimelineComponent = ({ + queryRef, +}: PublicStixCoreObjectsTimelineComponentProps) => { + const { publicStixCoreObjects } = usePreloadedQuery( + publicStixCoreObjectsTimelineQuery, + queryRef, + ); + + if ( + publicStixCoreObjects + && publicStixCoreObjects?.edges + && publicStixCoreObjects.edges.length > 0 + ) { + const stixCoreObjectsEdges = publicStixCoreObjects.edges; + const data = stixCoreObjectsEdges.flatMap((stixCoreObjectEdge) => { + const stixCoreObject = stixCoreObjectEdge?.node; + if (!stixCoreObject) return []; + const link = `${resolveLink(stixCoreObject.entity_type)}/${stixCoreObject.id}`; + return { + value: stixCoreObject, + link, + }; + }); + return ; + } + return ; +}; + +const PublicStixCoreObjectsTimeline = ({ + uriKey, + widget, + startDate, + endDate, + title, +}: PublicWidgetContainerProps) => { + const { t_i18n } = useFormatter(); + const { id, parameters } = widget; + const queryRef = useQueryLoading( + publicStixCoreObjectsTimelineQuery, + { + uriKey, + widgetId: id, + startDate, + endDate, + }, + ); + + return ( + + {queryRef ? ( + }> + + + ) : ( + + )} + + ); +}; + +export default PublicStixCoreObjectsTimeline; diff --git a/opencti-platform/opencti-front/src/public/components/dashboard/stix_core_objects/PublicStixCoreObjectsTreeMap.tsx b/opencti-platform/opencti-front/src/public/components/dashboard/stix_core_objects/PublicStixCoreObjectsTreeMap.tsx new file mode 100644 index 000000000000..a56775f7aeb7 --- /dev/null +++ b/opencti-platform/opencti-front/src/public/components/dashboard/stix_core_objects/PublicStixCoreObjectsTreeMap.tsx @@ -0,0 +1,241 @@ +import { graphql, PreloadedQuery, usePreloadedQuery } from 'react-relay'; +import React from 'react'; +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import type { PublicWidgetContainerProps } from '../PublicWidgetContainerProps'; +import { useFormatter } from '../../../../components/i18n'; +import useQueryLoading from '../../../../utils/hooks/useQueryLoading'; +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; +import { PublicStixCoreObjectsTreeMapQuery } from './__generated__/PublicStixCoreObjectsTreeMapQuery.graphql'; +import WidgetTree from '../../../../components/dashboard/WidgetTree'; +import type { PublicManifestWidget } from '../PublicManifest'; + +const publicStixCoreObjectsTreeMapQuery = graphql` + query PublicStixCoreObjectsTreeMapQuery( + $startDate: DateTime + $endDate: DateTime + $uriKey: String! + $widgetId : String! + ) { + publicStixCoreObjectsDistribution( + startDate: $startDate + endDate: $endDate + uriKey: $uriKey + widgetId : $widgetId + ) { + label + value + entity { + ... on BasicObject { + entity_type + } + ... on BasicRelationship { + entity_type + } + ... on AttackPattern { + name + description + } + ... on Campaign { + name + description + } + ... on CourseOfAction { + name + description + } + ... on Individual { + name + description + } + ... on Organization { + name + description + } + ... on Sector { + name + description + } + ... on System { + name + description + } + ... on Indicator { + name + description + } + ... on Infrastructure { + name + description + } + ... on IntrusionSet { + name + description + } + ... on Position { + name + description + } + ... on City { + name + description + } + ... on AdministrativeArea { + name + description + } + ... on Country { + name + description + } + ... on Region { + name + description + } + ... on Malware { + name + description + } + ... on MalwareAnalysis { + result_name + } + ... on ThreatActor { + name + description + } + ... on Tool { + name + description + } + ... on Vulnerability { + name + description + } + ... on Incident { + name + description + } + ... on Event { + name + description + } + ... on Channel { + name + description + } + ... on Narrative { + name + description + } + ... on Language { + name + } + ... on DataComponent { + name + } + ... on DataSource { + name + } + ... on Case { + name + } + ... on StixCyberObservable { + observable_value + } + ... on MarkingDefinition { + definition_type + definition + } + ... on KillChainPhase { + kill_chain_name + phase_name + } + ... on Creator { + name + } + ... on Label { + value + } + ... on Status { + template { + name + } + } + } + } + } +`; + +interface PublicStixCoreObjectsTreeMapComponentProps { + parameters: PublicManifestWidget['parameters'] + dataSelection: PublicManifestWidget['dataSelection'] + queryRef: PreloadedQuery +} + +const PublicStixCoreObjectsTreeMapComponent = ({ + parameters, + dataSelection, + queryRef, +}: PublicStixCoreObjectsTreeMapComponentProps) => { + const { publicStixCoreObjectsDistribution } = usePreloadedQuery( + publicStixCoreObjectsTreeMapQuery, + queryRef, + ); + + if ( + publicStixCoreObjectsDistribution + && publicStixCoreObjectsDistribution.length > 0 + ) { + return ( + + ); + } + return ; +}; + +const PublicStixCoreObjectsTreeMap = ({ + uriKey, + widget, + startDate, + endDate, + title, +}: PublicWidgetContainerProps) => { + const { t_i18n } = useFormatter(); + const { id, parameters, dataSelection } = widget; + const queryRef = useQueryLoading( + publicStixCoreObjectsTreeMapQuery, + { + uriKey, + widgetId: id, + startDate, + endDate, + }, + ); + + return ( + + {queryRef ? ( + }> + + + ) : ( + + )} + + ); +}; + +export default PublicStixCoreObjectsTreeMap; diff --git a/opencti-platform/opencti-front/src/public/components/dashboard/stix_domain_objects/PublicStixDomainObjectBookmarksList.tsx b/opencti-platform/opencti-front/src/public/components/dashboard/stix_domain_objects/PublicStixDomainObjectBookmarksList.tsx new file mode 100644 index 000000000000..a4c0741c0d4e --- /dev/null +++ b/opencti-platform/opencti-front/src/public/components/dashboard/stix_domain_objects/PublicStixDomainObjectBookmarksList.tsx @@ -0,0 +1,224 @@ +import { graphql, PreloadedQuery, usePreloadedQuery } from 'react-relay'; +import React from 'react'; +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import WidgetBookmarks from '../../../../components/dashboard/WidgetBookmarks'; +import type { PublicWidgetContainerProps } from '../PublicWidgetContainerProps'; +import { useFormatter } from '../../../../components/i18n'; +import useQueryLoading from '../../../../utils/hooks/useQueryLoading'; +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; +import { PublicStixDomainObjectBookmarksListQuery } from './__generated__/PublicStixDomainObjectBookmarksListQuery.graphql'; + +const publicStixDomainObjectBookmarksListQuery = graphql` + query PublicStixDomainObjectBookmarksListQuery( + $uriKey: String! + $widgetId : String! + ) { + publicBookmarks( + uriKey: $uriKey + widgetId : $widgetId + ) { + edges { + node { + id + entity_type + created_at + created + modified + ... on AttackPattern { + name + description + } + ... on Campaign { + name + description + } + ... on Note { + attribute_abstract + } + ... on ObservedData { + first_observed + last_observed + } + ... on Opinion { + opinion + } + ... on Report { + name + description + published + } + ... on Grouping { + name + description + } + ... on CourseOfAction { + name + description + } + ... on Individual { + name + description + } + ... on Organization { + name + description + } + ... on Sector { + name + description + } + ... on System { + name + description + } + ... on Indicator { + name + description + } + ... on Infrastructure { + name + description + } + ... on IntrusionSet { + name + description + } + ... on Position { + name + description + } + ... on City { + name + description + } + ... on AdministrativeArea { + name + description + } + ... on Country { + name + description + } + ... on Region { + name + description + } + ... on Malware { + name + description + } + ... on ThreatActor { + name + description + } + ... on Tool { + name + description + } + ... on Vulnerability { + name + description + } + ... on Incident { + name + description + } + ... on Event { + name + description + } + ... on Channel { + name + description + } + ... on Narrative { + name + description + } + ... on Language { + name + } + ... on DataComponent { + name + } + ... on DataSource { + name + } + ... on Case { + name + } + createdBy { + ... on Identity { + id + name + entity_type + } + } + objectMarking { + id + definition_type + definition + x_opencti_order + x_opencti_color + } + } + } + } + } +`; + +interface PublicStixDomainObjectBookmarksListComponentProps { + queryRef: PreloadedQuery +} + +const PublicStixDomainObjectBookmarksListComponent = ({ + queryRef, +}: PublicStixDomainObjectBookmarksListComponentProps) => { + const { publicBookmarks } = usePreloadedQuery( + publicStixDomainObjectBookmarksListQuery, + queryRef, + ); + + if ( + publicBookmarks + && publicBookmarks.edges + && publicBookmarks.edges.length > 0 + ) { + return ; + } + return ; +}; + +const PublicStixDomainObjectBookmarksList = ({ + uriKey, + widget, + title, +}: PublicWidgetContainerProps) => { + const { t_i18n } = useFormatter(); + const { id, parameters } = widget; + const queryRef = useQueryLoading( + publicStixDomainObjectBookmarksListQuery, + { + uriKey, + widgetId: id, + }, + ); + + return ( + + {queryRef ? ( + }> + + + ) : ( + + )} + + ); +}; + +export default PublicStixDomainObjectBookmarksList; diff --git a/opencti-platform/opencti-front/src/public/components/dashboard/stix_relationships/PublicStixRelationshipsDistributionList.tsx b/opencti-platform/opencti-front/src/public/components/dashboard/stix_relationships/PublicStixRelationshipsDistributionList.tsx new file mode 100644 index 000000000000..1ad6c301c21d --- /dev/null +++ b/opencti-platform/opencti-front/src/public/components/dashboard/stix_relationships/PublicStixRelationshipsDistributionList.tsx @@ -0,0 +1,247 @@ +import { graphql, PreloadedQuery, usePreloadedQuery } from 'react-relay'; +import React from 'react'; +import { useFormatter } from '../../../../components/i18n'; +import WidgetDistributionList from '../../../../components/dashboard/WidgetDistributionList'; +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import type { PublicWidgetContainerProps } from '../PublicWidgetContainerProps'; +import useQueryLoading from '../../../../utils/hooks/useQueryLoading'; +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; +import { PublicStixRelationshipsDistributionListQuery } from './__generated__/PublicStixRelationshipsDistributionListQuery.graphql'; +import type { PublicManifestWidget } from '../PublicManifest'; +import { defaultValue } from '../../../../utils/Graph'; + +const publicStixRelationshipsDistributionListQuery = graphql` + query PublicStixRelationshipsDistributionListQuery( + $startDate: DateTime + $endDate: DateTime + $uriKey: String! + $widgetId : String! + ) { + publicStixRelationshipsDistribution( + startDate: $startDate + endDate: $endDate + uriKey: $uriKey + widgetId : $widgetId + ) { + label + value + entity { + ... on BasicObject { + id + entity_type + } + ... on BasicRelationship { + id + entity_type + } + ... on AttackPattern { + name + description + } + ... on Campaign { + name + description + } + ... on CourseOfAction { + name + description + } + ... on Individual { + name + description + } + ... on Organization { + name + description + } + ... on Sector { + name + description + } + ... on System { + name + description + } + ... on Indicator { + name + description + } + ... on Infrastructure { + name + description + } + ... on IntrusionSet { + name + description + } + ... on Position { + name + description + } + ... on City { + name + description + } + ... on Country { + name + description + } + ... on Region { + name + description + } + ... on AdministrativeArea { + name + description + } + ... on Malware { + name + description + } + ... on ThreatActor { + name + description + } + ... on Tool { + name + description + } + ... on Vulnerability { + name + description + } + ... on Incident { + name + description + } + ... on Event { + name + description + } + ... on Channel { + name + description + } + ... on Narrative { + name + description + } + ... on Language { + name + } + ... on DataComponent { + name + } + ... on DataSource { + name + } + ... on Case { + name + } + ... on Report { + name + } + ... on StixCyberObservable { + observable_value + } + ... on MarkingDefinition { + definition_type + definition + } + ... on KillChainPhase { + kill_chain_name + phase_name + } + ... on Creator { + name + } + ... on Report { + name + } + ... on Grouping { + name + } + ... on Note { + attribute_abstract + content + } + ... on Opinion { + opinion + } + } + } + } +`; + +interface PublicStixRelationshipsDistributionListComponentProps { + dataSelection: PublicManifestWidget['dataSelection'] + queryRef: PreloadedQuery +} + +const PublicStixRelationshipsDistributionListComponent = ({ + dataSelection, + queryRef, +}: PublicStixRelationshipsDistributionListComponentProps) => { + const { publicStixRelationshipsDistribution } = usePreloadedQuery( + publicStixRelationshipsDistributionListQuery, + queryRef, + ); + + if (publicStixRelationshipsDistribution && publicStixRelationshipsDistribution.length > 0) { + const finalField = dataSelection[0].attribute || 'entity_type'; + const data = publicStixRelationshipsDistribution.flatMap((o) => { + if (!o) return []; + return { + label: finalField.endsWith('_id') + ? defaultValue(o.entity) + : o.label, + value: o.value, + id: o.entity?.id ?? null, + type: o.entity?.entity_type ?? o.label, + }; + }); + return ; + } + return ; +}; + +const PublicStixRelationshipsDistributionList = ({ + uriKey, + widget, + startDate, + endDate, + title, +}: PublicWidgetContainerProps) => { + const { t_i18n } = useFormatter(); + const { id, parameters, dataSelection } = widget; + const queryRef = useQueryLoading( + publicStixRelationshipsDistributionListQuery, + { + uriKey, + widgetId: id, + startDate, + endDate, + }, + ); + + return ( + + {queryRef ? ( + }> + + + ) : ( + + )} + + ); +}; + +export default PublicStixRelationshipsDistributionList; diff --git a/opencti-platform/opencti-front/src/public/components/dashboard/stix_relationships/PublicStixRelationshipsDonut.tsx b/opencti-platform/opencti-front/src/public/components/dashboard/stix_relationships/PublicStixRelationshipsDonut.tsx new file mode 100644 index 000000000000..02a4a9401cff --- /dev/null +++ b/opencti-platform/opencti-front/src/public/components/dashboard/stix_relationships/PublicStixRelationshipsDonut.tsx @@ -0,0 +1,242 @@ +import { graphql, PreloadedQuery, usePreloadedQuery } from 'react-relay'; +import React from 'react'; +import type { PublicManifestWidget } from '../PublicManifest'; +import WidgetDonut from '../../../../components/dashboard/WidgetDonut'; +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import type { PublicWidgetContainerProps } from '../PublicWidgetContainerProps'; +import { useFormatter } from '../../../../components/i18n'; +import useQueryLoading from '../../../../utils/hooks/useQueryLoading'; +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; +import { PublicStixRelationshipsDonutQuery } from './__generated__/PublicStixRelationshipsDonutQuery.graphql'; + +const publicStixRelationshipsDonutQuery = graphql` + query PublicStixRelationshipsDonutQuery( + $startDate: DateTime + $endDate: DateTime + $uriKey: String! + $widgetId : String! + ) { + publicStixRelationshipsDistribution( + startDate: $startDate + endDate: $endDate + uriKey: $uriKey + widgetId : $widgetId + ) { + label + value + entity { + ... on BasicObject { + entity_type + } + ... on BasicRelationship { + entity_type + } + ... on AttackPattern { + name + description + } + ... on Campaign { + name + description + } + ... on CourseOfAction { + name + description + } + ... on Individual { + name + description + } + ... on Organization { + name + description + } + ... on Sector { + name + description + } + ... on System { + name + description + } + ... on Indicator { + name + description + } + ... on Infrastructure { + name + description + } + ... on IntrusionSet { + name + description + } + ... on Position { + name + description + } + ... on City { + name + description + } + ... on Country { + name + description + } + ... on Region { + name + description + } + ... on Malware { + name + description + } + ... on ThreatActor { + name + description + } + ... on Tool { + name + description + } + ... on Vulnerability { + name + description + } + ... on Incident { + name + description + } + ... on Event { + name + description + } + ... on Channel { + name + description + } + ... on Narrative { + name + description + } + ... on Language { + name + } + ... on DataComponent { + name + description + } + ... on DataSource { + name + description + } + ... on Case { + name + description + } + ... on StixCyberObservable { + observable_value + } + ... on MarkingDefinition { + definition_type + definition + } + ... on KillChainPhase { + kill_chain_name + phase_name + } + ... on Creator { + name + } + ... on Report { + name + } + ... on Grouping { + name + } + ... on Note { + attribute_abstract + content + } + ... on Opinion { + opinion + } + ... on Label { + value + color + } + } + } + } +`; + +interface PublicStixRelationshipsDonutComponentProps { + dataSelection: PublicManifestWidget['dataSelection'] + queryRef: PreloadedQuery +} + +const PublicStixRelationshipsDonutComponent = ({ + dataSelection, + queryRef, +}: PublicStixRelationshipsDonutComponentProps) => { + const { publicStixRelationshipsDistribution } = usePreloadedQuery( + publicStixRelationshipsDonutQuery, + queryRef, + ); + + if ( + publicStixRelationshipsDistribution + && publicStixRelationshipsDistribution.length > 0 + ) { + return ( + + ); + } + return ; +}; + +const PublicStixRelationshipsDonut = ({ + uriKey, + widget, + startDate, + endDate, + title, +}: PublicWidgetContainerProps) => { + const { t_i18n } = useFormatter(); + const { id, parameters, dataSelection } = widget; + const queryRef = useQueryLoading( + publicStixRelationshipsDonutQuery, + { + uriKey, + widgetId: id, + startDate, + endDate, + }, + ); + + return ( + + {queryRef ? ( + }> + + + ) : ( + + )} + + ); +}; + +export default PublicStixRelationshipsDonut; diff --git a/opencti-platform/opencti-front/src/public/components/dashboard/stix_relationships/PublicStixRelationshipsHorizontalBars.tsx b/opencti-platform/opencti-front/src/public/components/dashboard/stix_relationships/PublicStixRelationshipsHorizontalBars.tsx new file mode 100644 index 000000000000..39fa5e1d5b99 --- /dev/null +++ b/opencti-platform/opencti-front/src/public/components/dashboard/stix_relationships/PublicStixRelationshipsHorizontalBars.tsx @@ -0,0 +1,294 @@ +import { graphql, PreloadedQuery, usePreloadedQuery } from 'react-relay'; +import { useTheme } from '@mui/styles'; +import React from 'react'; +import type { PublicManifestWidget } from '../PublicManifest'; +import type { Theme } from '../../../../components/Theme'; +import { useFormatter } from '../../../../components/i18n'; +import { itemColor } from '../../../../utils/Colors'; +import { defaultValue } from '../../../../utils/Graph'; +import WidgetHorizontalBars from '../../../../components/dashboard/WidgetHorizontalBars'; +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import type { PublicWidgetContainerProps } from '../PublicWidgetContainerProps'; +import useQueryLoading from '../../../../utils/hooks/useQueryLoading'; +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; +import { PublicStixRelationshipsHorizontalBarsQuery } from './__generated__/PublicStixRelationshipsHorizontalBarsQuery.graphql'; + +const publicStixRelationshipsHorizontalBarsQuery = graphql` + query PublicStixRelationshipsHorizontalBarsQuery( + $startDate: DateTime + $endDate: DateTime + $uriKey: String! + $widgetId : String! + ) { + publicStixRelationshipsDistribution( + startDate: $startDate + endDate: $endDate + uriKey: $uriKey + widgetId : $widgetId + ) { + label + value + entity { + ... on BasicObject { + entity_type + id + } + ... on BasicRelationship { + entity_type + id + } + ... on AttackPattern { + name + description + } + ... on Campaign { + name + description + } + ... on CourseOfAction { + name + description + } + ... on Individual { + name + description + } + ... on Organization { + name + description + } + ... on Sector { + name + description + } + ... on System { + name + description + } + ... on Indicator { + name + description + } + ... on Infrastructure { + name + description + } + ... on IntrusionSet { + name + description + } + ... on Position { + name + description + } + ... on City { + name + description + } + ... on Country { + name + description + } + ... on Region { + name + description + } + ... on Malware { + name + description + } + ... on ThreatActor { + name + description + } + ... on Tool { + name + description + } + ... on Vulnerability { + name + description + } + ... on Incident { + name + description + } + ... on Event { + name + description + } + ... on Channel { + name + description + } + ... on Narrative { + name + description + } + ... on Language { + name + } + ... on DataComponent { + name + } + ... on DataSource { + name + } + ... on Case { + name + } + ... on Report { + name + } + ... on StixCyberObservable { + observable_value + } + ... on MarkingDefinition { + definition_type + definition + x_opencti_color + } + ... on KillChainPhase { + kill_chain_name + phase_name + } + ... on Creator { + name + } + ... on Report { + name + } + ... on Grouping { + name + } + ... on Note { + attribute_abstract + content + } + ... on Opinion { + opinion + } + ... on Label { + value + color + } + } + } + } +`; + +interface PublicStixRelationshipsHorizontalBarsComponentProps { + parameters: PublicManifestWidget['parameters'] + dataSelection: PublicManifestWidget['dataSelection'] + queryRef: PreloadedQuery +} + +const PublicStixRelationshipsHorizontalBarsComponent = ({ + parameters, + dataSelection, + queryRef, +}: PublicStixRelationshipsHorizontalBarsComponentProps) => { + const theme = useTheme(); + const { t_i18n } = useFormatter(); + const { publicStixRelationshipsDistribution } = usePreloadedQuery( + publicStixRelationshipsHorizontalBarsQuery, + queryRef, + ); + + if ( + publicStixRelationshipsDistribution + && publicStixRelationshipsDistribution.length > 0 + ) { + const selection = dataSelection[0]; + const finalField = selection.attribute || 'entity_type'; + const data = publicStixRelationshipsDistribution.map((n) => { + let color = selection.attribute?.endsWith('_id') + ? itemColor(n?.entity?.entity_type) + : itemColor(n?.label); + if (n?.entity?.color) { + color = theme.palette.mode === 'light' && n.entity.color === '#ffffff' + ? '#000000' + : n.entity.color; + } + if (n?.entity?.x_opencti_color) { + color = theme.palette.mode === 'light' + && n.entity.x_opencti_color === '#ffffff' + ? '#000000' + : n.entity.x_opencti_color; + } + return { + x: finalField.endsWith('_id') + ? defaultValue(n?.entity) + : n?.label, + y: n?.value, + fillColor: color, + }; + }); + const chartData = [{ + name: selection.label || t_i18n('Number of relationships'), + data, + }]; + const redirectionUtils = finalField.endsWith('_id') + ? publicStixRelationshipsDistribution.flatMap((n) => { + if (!n || !n.entity) return []; + return { + id: n.entity.id, + entity_type: n.entity.entity_type, + }; + }) + : undefined; + + return ( + + ); + } + return ; +}; + +const PublicStixRelationshipsHorizontalBars = ({ + uriKey, + widget, + startDate, + endDate, + title, +}: PublicWidgetContainerProps) => { + const { t_i18n } = useFormatter(); + const { id, parameters, dataSelection } = widget; + const queryRef = useQueryLoading( + publicStixRelationshipsHorizontalBarsQuery, + { + uriKey, + widgetId: id, + startDate, + endDate, + }, + ); + + return ( + + {queryRef ? ( + }> + + + ) : ( + + )} + + ); +}; + +export default PublicStixRelationshipsHorizontalBars; diff --git a/opencti-platform/opencti-front/src/public/components/dashboard/stix_relationships/PublicStixRelationshipsList.tsx b/opencti-platform/opencti-front/src/public/components/dashboard/stix_relationships/PublicStixRelationshipsList.tsx new file mode 100644 index 000000000000..9ba55acb1c98 --- /dev/null +++ b/opencti-platform/opencti-front/src/public/components/dashboard/stix_relationships/PublicStixRelationshipsList.tsx @@ -0,0 +1,4414 @@ +import { graphql, PreloadedQuery, usePreloadedQuery } from 'react-relay'; +import React from 'react'; +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import type { PublicWidgetContainerProps } from '../PublicWidgetContainerProps'; +import { useFormatter } from '../../../../components/i18n'; +import useQueryLoading from '../../../../utils/hooks/useQueryLoading'; +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; +import { PublicStixRelationshipsListQuery } from './__generated__/PublicStixRelationshipsListQuery.graphql'; +import WidgetListRelationships from '../../../../components/dashboard/WidgetListRelationships'; + +const publicStixRelationshipsListQuery = graphql` + query PublicStixRelationshipsListQuery( + $startDate: DateTime + $endDate: DateTime + $uriKey: String! + $widgetId : String! + ) { + publicStixRelationships( + startDate: $startDate + endDate: $endDate + uriKey: $uriKey + widgetId : $widgetId + ) { + edges { + node { + id + entity_type + parent_types + relationship_type + confidence + ... on StixCoreRelationship { + start_time + stop_time + description + objectLabel { + id + value + color + } + } + ... on StixSightingRelationship { + first_seen + last_seen + } + fromRole + toRole + created_at + updated_at + is_inferred + createdBy { + ... on Identity { + name + } + } + objectMarking { + id + definition + x_opencti_order + x_opencti_color + } + x_opencti_inferences { + rule { + id + name + description + } + explanation { + ... on BasicObject { + id + entity_type + parent_types + } + ... on BasicRelationship { + id + entity_type + parent_types + } + ... on StixCoreObject { + created_at + } + ... on AttackPattern { + name + } + ... on Campaign { + name + } + ... on CourseOfAction { + name + } + ... on Individual { + name + } + ... on Organization { + name + } + ... on Sector { + name + } + ... on System { + name + } + ... on Indicator { + name + } + ... on Infrastructure { + name + } + ... on IntrusionSet { + name + } + ... on Position { + name + } + ... on City { + name + } + ... on AdministrativeArea { + name + } + ... on Country { + name + } + ... on Region { + name + } + ... on Malware { + name + } + ... on ThreatActor { + name + } + ... on Tool { + name + } + ... on Vulnerability { + name + } + ... on Incident { + name + } + ... on Event { + name + } + ... on Channel { + name + } + ... on Narrative { + name + } + ... on Language { + name + } + ... on DataComponent { + name + } + ... on DataSource { + name + } + ... on Case { + name + } + ... on Report { + name + } + ... on StixRelationship { + id + relationship_type + created_at + ... on StixCoreRelationship { + start_time + stop_time + description + } + ... on StixSightingRelationship { + first_seen + last_seen + } + created + from { + ... on BasicObject { + id + entity_type + parent_types + } + ... on BasicRelationship { + id + entity_type + parent_types + } + ... on StixCoreObject { + created_at + } + ... on StixRelationship { + relationship_type + created_at + ... on StixCoreRelationship { + start_time + stop_time + description + } + ... on StixSightingRelationship { + first_seen + last_seen + } + created + } + ... on AttackPattern { + name + } + ... on Campaign { + name + } + ... on CourseOfAction { + name + } + ... on Individual { + name + } + ... on Organization { + name + } + ... on Sector { + name + } + ... on System { + name + } + ... on Indicator { + name + } + ... on Infrastructure { + name + } + ... on IntrusionSet { + name + } + ... on Position { + name + } + ... on City { + name + } + ... on AdministrativeArea { + name + } + ... on Country { + name + } + ... on Region { + name + } + ... on Malware { + name + } + ... on ThreatActor { + name + } + ... on Tool { + name + } + ... on Vulnerability { + name + } + ... on Incident { + name + } + ... on Event { + name + } + ... on Channel { + name + } + ... on Narrative { + name + } + ... on Language { + name + } + ... on DataComponent { + name + } + ... on DataSource { + name + } + ... on Case { + name + } + ... on Report { + name + } + ... on ExternalReference { + source_name + url + external_id + } + ... on StixCyberObservable { + observable_value + } + ... on ObservedData { + name + objects(first: 1) { + edges { + node { + ... on StixCoreObject { + id + entity_type + parent_types + created_at + createdBy { + ... on Identity { + id + name + entity_type + } + } + objectMarking { + id + definition + x_opencti_order + x_opencti_color + } + } + ... on AttackPattern { + name + description + x_mitre_id + } + ... on Campaign { + name + description + first_seen + last_seen + } + ... on Note { + attribute_abstract + } + ... on ObservedData { + name + first_observed + last_observed + } + ... on Opinion { + opinion + } + ... on Report { + name + description + published + } + ... on Grouping { + name + description + } + ... on CourseOfAction { + name + description + } + ... on Individual { + name + description + } + ... on Organization { + name + description + } + ... on Sector { + name + description + } + ... on System { + name + description + } + ... on Indicator { + name + description + valid_from + } + ... on Infrastructure { + name + description + } + ... on IntrusionSet { + name + description + first_seen + last_seen + } + ... on Position { + name + description + } + ... on City { + name + description + } + ... on AdministrativeArea { + name + description + } + ... on Country { + name + description + } + ... on Region { + name + description + } + ... on Malware { + name + description + first_seen + last_seen + } + ... on ThreatActor { + name + description + first_seen + last_seen + } + ... on Tool { + name + description + } + ... on Vulnerability { + name + description + } + ... on Incident { + name + description + first_seen + last_seen + } + ... on Event { + name + } + ... on Channel { + name + } + ... on Narrative { + name + } + ... on Language { + name + } + ... on DataComponent { + name + } + ... on DataSource { + name + } + ... on Case { + name + } + ... on Report { + name + } + ... on ExternalReference { + source_name + url + external_id + } + ... on StixCyberObservable { + observable_value + x_opencti_description + } + } + } + } + } + ... on StixRelationship { + id + entity_type + relationship_type + ... on StixCoreRelationship { + start_time + stop_time + description + } + ... on StixSightingRelationship { + first_seen + last_seen + } + created + from { + ... on BasicObject { + id + entity_type + } + ... on BasicRelationship { + id + entity_type + } + ... on StixCoreObject { + created_at + } + ... on StixRelationship { + created_at + ... on StixCoreRelationship { + start_time + stop_time + description + } + ... on StixSightingRelationship { + first_seen + last_seen + } + created + } + ... on AttackPattern { + name + } + ... on Campaign { + name + } + ... on CourseOfAction { + name + } + ... on Individual { + name + } + ... on Organization { + name + } + ... on Sector { + name + } + ... on System { + name + } + ... on Indicator { + name + } + ... on Infrastructure { + name + } + ... on IntrusionSet { + name + } + ... on Position { + name + } + ... on City { + name + } + ... on AdministrativeArea { + name + } + ... on Country { + name + } + ... on Region { + name + } + ... on Malware { + name + } + ... on ThreatActor { + name + } + ... on Tool { + name + } + ... on Vulnerability { + name + } + ... on Incident { + name + } + ... on Event { + name + } + ... on Channel { + name + } + ... on Narrative { + name + } + ... on Language { + name + } + ... on DataComponent { + name + } + ... on DataSource { + name + } + ... on Case { + name + } + ... on Report { + name + } + ... on ExternalReference { + source_name + url + external_id + } + } + to { + ... on BasicObject { + id + entity_type + } + ... on BasicRelationship { + id + entity_type + } + ... on StixCoreObject { + created_at + } + ... on StixRelationship { + created_at + ... on StixCoreRelationship { + start_time + stop_time + description + } + ... on StixSightingRelationship { + first_seen + last_seen + } + created + } + ... on AttackPattern { + name + } + ... on Campaign { + name + } + ... on CourseOfAction { + name + } + ... on Individual { + name + } + ... on Organization { + name + } + ... on Sector { + name + } + ... on System { + name + } + ... on Indicator { + name + } + ... on Infrastructure { + name + } + ... on IntrusionSet { + name + } + ... on Position { + name + } + ... on City { + name + } + ... on Country { + name + } + ... on Region { + name + } + ... on Malware { + name + } + ... on ThreatActor { + name + } + ... on Tool { + name + } + ... on Vulnerability { + name + } + ... on Incident { + name + } + ... on Event { + name + } + ... on Channel { + name + } + ... on Narrative { + name + } + ... on Language { + name + } + ... on DataComponent { + name + } + ... on DataSource { + name + } + ... on Case { + name + } + ... on Report { + name + } + ... on ExternalReference { + source_name + url + external_id + } + ... on ObservedData { + name + objects(first: 1) { + edges { + node { + ... on StixCoreObject { + id + entity_type + parent_types + created_at + createdBy { + ... on Identity { + id + name + entity_type + } + } + objectMarking { + id + definition + x_opencti_order + x_opencti_color + } + } + ... on AttackPattern { + name + description + x_mitre_id + } + ... on Campaign { + name + description + first_seen + last_seen + } + ... on Note { + attribute_abstract + } + ... on ObservedData { + name + first_observed + last_observed + } + ... on Opinion { + opinion + } + ... on Report { + name + description + published + } + ... on Grouping { + name + description + } + ... on CourseOfAction { + name + description + } + ... on Individual { + name + description + } + ... on Organization { + name + description + } + ... on Sector { + name + description + } + ... on System { + name + description + } + ... on Indicator { + name + description + valid_from + } + ... on Infrastructure { + name + description + } + ... on IntrusionSet { + name + description + first_seen + last_seen + } + ... on Position { + name + description + } + ... on City { + name + description + } + ... on Country { + name + description + } + ... on Region { + name + description + } + ... on Malware { + name + description + first_seen + last_seen + } + ... on ThreatActor { + name + description + first_seen + last_seen + } + ... on Tool { + name + description + } + ... on Vulnerability { + name + description + } + ... on Incident { + name + description + first_seen + last_seen + } + ... on Event { + name + } + ... on Channel { + name + } + ... on Narrative { + name + } + ... on Language { + name + } + ... on DataComponent { + name + } + ... on DataSource { + name + } + ... on Case { + name + } + ... on ExternalReference { + source_name + url + external_id + } + ... on StixCyberObservable { + observable_value + x_opencti_description + } + } + } + } + } + } + } + } + to { + ... on BasicObject { + id + entity_type + parent_types + } + ... on BasicRelationship { + id + entity_type + parent_types + } + ... on StixCoreObject { + created_at + } + ... on StixRelationship { + created_at + relationship_type + ... on StixCoreRelationship { + start_time + stop_time + description + } + ... on StixSightingRelationship { + first_seen + last_seen + } + created + } + ... on AttackPattern { + name + } + ... on Campaign { + name + } + ... on CourseOfAction { + name + } + ... on Individual { + name + } + ... on Organization { + name + } + ... on Sector { + name + } + ... on System { + name + } + ... on Indicator { + name + } + ... on Infrastructure { + name + } + ... on IntrusionSet { + name + } + ... on Position { + name + } + ... on City { + name + } + ... on Country { + name + } + ... on Region { + name + } + ... on Malware { + name + } + ... on ThreatActor { + name + } + ... on Tool { + name + } + ... on Vulnerability { + name + } + ... on Incident { + name + } + ... on Event { + name + } + ... on Channel { + name + } + ... on Narrative { + name + } + ... on Language { + name + } + ... on DataComponent { + name + } + ... on DataSource { + name + } + ... on Case { + name + } + ... on StixCyberObservable { + observable_value + } + ... on ObservedData { + name + objects(first: 1) { + edges { + node { + ... on StixCoreObject { + id + entity_type + parent_types + created_at + createdBy { + ... on Identity { + id + name + entity_type + } + } + objectMarking { + id + definition + x_opencti_order + x_opencti_color + } + } + ... on AttackPattern { + name + description + x_mitre_id + } + ... on Campaign { + name + description + first_seen + last_seen + } + ... on Note { + attribute_abstract + } + ... on ObservedData { + name + first_observed + last_observed + } + ... on Opinion { + opinion + } + ... on Report { + name + description + published + } + ... on Grouping { + name + description + } + ... on CourseOfAction { + name + description + } + ... on Individual { + name + description + } + ... on Organization { + name + description + } + ... on Sector { + name + description + } + ... on System { + name + description + } + ... on Indicator { + name + description + valid_from + } + ... on Infrastructure { + name + description + } + ... on IntrusionSet { + name + description + first_seen + last_seen + } + ... on Position { + name + description + } + ... on City { + name + description + } + ... on Country { + name + description + } + ... on Region { + name + description + } + ... on Malware { + name + description + first_seen + last_seen + } + ... on ThreatActor { + name + description + first_seen + last_seen + } + ... on Tool { + name + description + } + ... on Vulnerability { + name + description + } + ... on Incident { + name + description + first_seen + last_seen + } + ... on Event { + name + } + ... on Channel { + name + } + ... on Narrative { + name + } + ... on Language { + name + } + ... on DataComponent { + name + } + ... on DataSource { + name + } + ... on Case { + name + } + ... on StixCyberObservable { + observable_value + x_opencti_description + } + } + } + } + } + ... on StixRelationship { + id + entity_type + relationship_type + ... on StixCoreRelationship { + start_time + stop_time + description + } + ... on StixSightingRelationship { + first_seen + last_seen + } + created + from { + ... on BasicObject { + id + entity_type + parent_types + } + ... on BasicRelationship { + id + entity_type + parent_types + } + ... on StixCoreObject { + created_at + } + ... on StixRelationship { + created_at + ... on StixCoreRelationship { + start_time + stop_time + description + } + ... on StixSightingRelationship { + first_seen + last_seen + } + created + } + ... on AttackPattern { + name + } + ... on Campaign { + name + } + ... on CourseOfAction { + name + } + ... on Individual { + name + } + ... on Organization { + name + } + ... on Sector { + name + } + ... on System { + name + } + ... on Indicator { + name + } + ... on Infrastructure { + name + } + ... on IntrusionSet { + name + } + ... on Position { + name + } + ... on City { + name + } + ... on Country { + name + } + ... on Region { + name + } + ... on Malware { + name + } + ... on ThreatActor { + name + } + ... on Tool { + name + } + ... on Vulnerability { + name + } + ... on Incident { + name + } + ... on Event { + name + } + ... on Channel { + name + } + ... on Narrative { + name + } + ... on Language { + name + } + ... on DataComponent { + name + } + ... on DataSource { + name + } + ... on Case { + name + } + ... on Report { + name + } + ... on ExternalReference { + source_name + url + external_id + } + ... on MarkingDefinition { + definition_type + definition + } + ... on StixCyberObservable { + observable_value + } + ... on ObservedData { + name + objects(first: 1) { + edges { + node { + ... on StixCoreObject { + id + entity_type + parent_types + created_at + createdBy { + ... on Identity { + id + name + entity_type + } + } + objectMarking { + id + definition + x_opencti_order + x_opencti_color + } + } + ... on AttackPattern { + name + description + x_mitre_id + } + ... on Campaign { + name + description + first_seen + last_seen + } + ... on Note { + attribute_abstract + } + ... on ObservedData { + name + first_observed + last_observed + } + ... on Opinion { + opinion + } + ... on Report { + name + description + published + } + ... on Grouping { + name + description + } + ... on CourseOfAction { + name + description + } + ... on Individual { + name + description + } + ... on Organization { + name + description + } + ... on Sector { + name + description + } + ... on System { + name + description + } + ... on Indicator { + name + description + valid_from + } + ... on Infrastructure { + name + description + } + ... on IntrusionSet { + name + description + first_seen + last_seen + } + ... on Position { + name + description + } + ... on City { + name + description + } + ... on Country { + name + description + } + ... on Region { + name + description + } + ... on Malware { + name + description + first_seen + last_seen + } + ... on ThreatActor { + name + description + first_seen + last_seen + } + ... on Tool { + name + description + } + ... on Vulnerability { + name + description + } + ... on Incident { + name + description + first_seen + last_seen + } + ... on Event { + name + } + ... on Channel { + name + } + ... on Narrative { + name + } + ... on Language { + name + } + ... on DataComponent { + name + } + ... on DataSource { + name + } + ... on Case { + name + } + ... on ExternalReference { + source_name + url + external_id + } + ... on MarkingDefinition { + definition_type + definition + } + ... on StixCyberObservable { + observable_value + x_opencti_description + } + } + } + } + } + } + to { + ... on BasicObject { + id + entity_type + parent_types + } + ... on BasicRelationship { + id + entity_type + parent_types + } + ... on StixCoreObject { + created_at + } + ... on StixRelationship { + created_at + ... on StixCoreRelationship { + start_time + stop_time + description + } + ... on StixSightingRelationship { + first_seen + last_seen + } + created + } + ... on AttackPattern { + name + } + ... on Campaign { + name + } + ... on CourseOfAction { + name + } + ... on Individual { + name + } + ... on Organization { + name + } + ... on Sector { + name + } + ... on System { + name + } + ... on Indicator { + name + } + ... on Infrastructure { + name + } + ... on IntrusionSet { + name + } + ... on Position { + name + } + ... on City { + name + } + ... on Country { + name + } + ... on Region { + name + } + ... on Malware { + name + } + ... on ThreatActor { + name + } + ... on Tool { + name + } + ... on Vulnerability { + name + } + ... on Incident { + name + } + ... on Event { + name + } + ... on Channel { + name + } + ... on Narrative { + name + } + ... on Language { + name + } + ... on DataComponent { + name + } + ... on DataSource { + name + } + ... on Case { + name + } + ... on StixCyberObservable { + observable_value + } + ... on ObservedData { + name + objects(first: 1) { + edges { + node { + ... on StixCoreObject { + id + entity_type + parent_types + created_at + createdBy { + ... on Identity { + id + name + entity_type + } + } + objectMarking { + id + definition + x_opencti_order + x_opencti_color + } + } + ... on AttackPattern { + name + description + x_mitre_id + } + ... on Campaign { + name + description + first_seen + last_seen + } + ... on Note { + attribute_abstract + } + ... on ObservedData { + name + first_observed + last_observed + } + ... on Opinion { + opinion + } + ... on Report { + name + description + published + } + ... on Grouping { + name + description + } + ... on CourseOfAction { + name + description + } + ... on Individual { + name + description + } + ... on Organization { + name + description + } + ... on Sector { + name + description + } + ... on System { + name + description + } + ... on Indicator { + name + description + valid_from + } + ... on Infrastructure { + name + description + } + ... on IntrusionSet { + name + description + first_seen + last_seen + } + ... on Position { + name + description + } + ... on City { + name + description + } + ... on Country { + name + description + } + ... on Region { + name + description + } + ... on Malware { + name + description + first_seen + last_seen + } + ... on ThreatActor { + name + description + first_seen + last_seen + } + ... on Tool { + name + description + } + ... on Vulnerability { + name + description + } + ... on Incident { + name + description + first_seen + last_seen + } + ... on Event { + name + } + ... on Channel { + name + } + ... on Narrative { + name + } + ... on Language { + name + } + ... on DataComponent { + name + } + ... on DataSource { + name + } + ... on Case { + name + } + ... on ExternalReference { + source_name + url + external_id + } + ... on MarkingDefinition { + definition_type + definition + } + ... on StixCyberObservable { + observable_value + x_opencti_description + } + } + } + } + } + } + } + } + } + ... on StixSightingRelationship { + id + created_at + from { + ... on BasicObject { + id + entity_type + parent_types + } + ... on BasicRelationship { + id + entity_type + parent_types + } + ... on StixCoreObject { + created_at + } + ... on StixRelationship { + relationship_type + created_at + ... on StixCoreRelationship { + start_time + stop_time + description + } + ... on StixSightingRelationship { + first_seen + last_seen + } + created + } + ... on AttackPattern { + name + } + ... on Campaign { + name + } + ... on CourseOfAction { + name + } + ... on Individual { + name + } + ... on Organization { + name + } + ... on Sector { + name + } + ... on System { + name + } + ... on Indicator { + name + } + ... on Infrastructure { + name + } + ... on IntrusionSet { + name + } + ... on Position { + name + } + ... on City { + name + } + ... on Country { + name + } + ... on Region { + name + } + ... on Malware { + name + } + ... on ThreatActor { + name + } + ... on Tool { + name + } + ... on Vulnerability { + name + } + ... on Incident { + name + } + ... on Event { + name + } + ... on Channel { + name + } + ... on Narrative { + name + } + ... on Language { + name + } + ... on DataComponent { + name + } + ... on DataSource { + name + } + ... on Case { + name + } + ... on StixCyberObservable { + observable_value + } + ... on ObservedData { + name + objects(first: 1) { + edges { + node { + ... on StixCoreObject { + id + entity_type + parent_types + created_at + createdBy { + ... on Identity { + id + name + entity_type + } + } + objectMarking { + id + definition + x_opencti_order + x_opencti_color + } + } + ... on AttackPattern { + name + description + x_mitre_id + } + ... on Campaign { + name + description + first_seen + last_seen + } + ... on Note { + attribute_abstract + } + ... on ObservedData { + name + first_observed + last_observed + } + ... on Opinion { + opinion + } + ... on Report { + name + description + published + } + ... on Grouping { + name + description + } + ... on CourseOfAction { + name + description + } + ... on Individual { + name + description + } + ... on Organization { + name + description + } + ... on Sector { + name + description + } + ... on System { + name + description + } + ... on Indicator { + name + description + valid_from + } + ... on Infrastructure { + name + description + } + ... on IntrusionSet { + name + description + first_seen + last_seen + } + ... on Position { + name + description + } + ... on City { + name + description + } + ... on Country { + name + description + } + ... on Region { + name + description + } + ... on Malware { + name + description + first_seen + last_seen + } + ... on ThreatActor { + name + description + first_seen + last_seen + } + ... on Tool { + name + description + } + ... on Vulnerability { + name + description + } + ... on Incident { + name + description + first_seen + last_seen + } + ... on Event { + name + } + ... on Channel { + name + } + ... on Narrative { + name + } + ... on Language { + name + } + ... on DataComponent { + name + } + ... on DataSource { + name + } + ... on Case { + name + } + ... on ExternalReference { + source_name + url + external_id + } + ... on MarkingDefinition { + definition_type + definition + } + ... on StixCyberObservable { + observable_value + x_opencti_description + } + } + } + } + } + ... on StixRelationship { + id + entity_type + relationship_type + ... on StixCoreRelationship { + start_time + stop_time + description + } + ... on StixSightingRelationship { + first_seen + last_seen + } + created + from { + ... on BasicObject { + id + entity_type + } + ... on BasicRelationship { + id + entity_type + } + ... on StixCoreObject { + created_at + } + ... on StixRelationship { + created_at + ... on StixCoreRelationship { + start_time + stop_time + description + } + ... on StixSightingRelationship { + first_seen + last_seen + } + created + } + ... on AttackPattern { + name + } + ... on Campaign { + name + } + ... on CourseOfAction { + name + } + ... on Individual { + name + } + ... on Organization { + name + } + ... on Sector { + name + } + ... on System { + name + } + ... on Indicator { + name + } + ... on Infrastructure { + name + } + ... on IntrusionSet { + name + } + ... on Position { + name + } + ... on City { + name + } + ... on Country { + name + } + ... on Region { + name + } + ... on Malware { + name + } + ... on ThreatActor { + name + } + ... on Tool { + name + } + ... on Vulnerability { + name + } + ... on Incident { + name + } + ... on Event { + name + } + ... on Channel { + name + } + ... on Narrative { + name + } + ... on Language { + name + } + ... on DataComponent { + name + } + ... on DataSource { + name + } + ... on Case { + name + } + } + to { + ... on BasicObject { + id + entity_type + } + ... on BasicRelationship { + id + entity_type + } + ... on StixCoreObject { + created_at + } + ... on StixRelationship { + created_at + ... on StixCoreRelationship { + start_time + stop_time + description + } + ... on StixSightingRelationship { + first_seen + last_seen + } + created + } + ... on AttackPattern { + name + } + ... on Campaign { + name + } + ... on CourseOfAction { + name + } + ... on Individual { + name + } + ... on Organization { + name + } + ... on Sector { + name + } + ... on System { + name + } + ... on Indicator { + name + } + ... on Infrastructure { + name + } + ... on IntrusionSet { + name + } + ... on Position { + name + } + ... on City { + name + } + ... on Country { + name + } + ... on Region { + name + } + ... on Malware { + name + } + ... on ThreatActor { + name + } + ... on Tool { + name + } + ... on Vulnerability { + name + } + ... on Incident { + name + } + ... on Event { + name + } + ... on Channel { + name + } + ... on Narrative { + name + } + ... on Language { + name + } + ... on DataComponent { + name + } + ... on DataSource { + name + } + ... on Case { + name + } + ... on ObservedData { + name + objects(first: 1) { + edges { + node { + ... on StixCoreObject { + id + entity_type + parent_types + created_at + createdBy { + ... on Identity { + id + name + entity_type + } + } + objectMarking { + id + definition + x_opencti_order + x_opencti_color + } + } + ... on AttackPattern { + name + description + x_mitre_id + } + ... on Campaign { + name + description + first_seen + last_seen + } + ... on Note { + attribute_abstract + } + ... on ObservedData { + name + first_observed + last_observed + } + ... on Opinion { + opinion + } + ... on Report { + name + description + published + } + ... on Grouping { + name + description + } + ... on CourseOfAction { + name + description + } + ... on Individual { + name + description + } + ... on Organization { + name + description + } + ... on Sector { + name + description + } + ... on System { + name + description + } + ... on Indicator { + name + description + valid_from + } + ... on Infrastructure { + name + description + } + ... on IntrusionSet { + name + description + first_seen + last_seen + } + ... on Position { + name + description + } + ... on City { + name + description + } + ... on Country { + name + description + } + ... on Region { + name + description + } + ... on Malware { + name + description + first_seen + last_seen + } + ... on ThreatActor { + name + description + first_seen + last_seen + } + ... on Tool { + name + description + } + ... on Vulnerability { + name + description + } + ... on Incident { + name + description + first_seen + last_seen + } + ... on Event { + name + } + ... on Channel { + name + } + ... on Narrative { + name + } + ... on Language { + name + } + ... on DataComponent { + name + } + ... on DataSource { + name + } + ... on Case { + name + } + ... on StixCyberObservable { + observable_value + x_opencti_description + } + } + } + } + } + } + } + } + to { + ... on BasicObject { + id + entity_type + parent_types + } + ... on BasicRelationship { + id + entity_type + parent_types + } + ... on StixCoreObject { + created_at + } + ... on StixRelationship { + created_at + relationship_type + ... on StixCoreRelationship { + start_time + stop_time + description + } + ... on StixSightingRelationship { + first_seen + last_seen + } + created + } + ... on AttackPattern { + name + } + ... on Campaign { + name + } + ... on CourseOfAction { + name + } + ... on Individual { + name + } + ... on Organization { + name + } + ... on Sector { + name + } + ... on System { + name + } + ... on Indicator { + name + } + ... on Infrastructure { + name + } + ... on IntrusionSet { + name + } + ... on Position { + name + } + ... on City { + name + } + ... on Country { + name + } + ... on Region { + name + } + ... on Malware { + name + } + ... on ThreatActor { + name + } + ... on Tool { + name + } + ... on Vulnerability { + name + } + ... on Incident { + name + } + ... on Event { + name + } + ... on Channel { + name + } + ... on Narrative { + name + } + ... on Language { + name + } + ... on DataComponent { + name + } + ... on DataSource { + name + } + ... on Case { + name + } + ... on StixCyberObservable { + observable_value + } + ... on ObservedData { + name + objects(first: 1) { + edges { + node { + ... on StixCoreObject { + id + entity_type + parent_types + created_at + createdBy { + ... on Identity { + id + name + entity_type + } + } + objectMarking { + id + definition + x_opencti_order + x_opencti_color + } + } + ... on AttackPattern { + name + description + x_mitre_id + } + ... on Campaign { + name + description + first_seen + last_seen + } + ... on Note { + attribute_abstract + } + ... on ObservedData { + name + first_observed + last_observed + } + ... on Opinion { + opinion + } + ... on Report { + name + description + published + } + ... on Grouping { + name + description + } + ... on CourseOfAction { + name + description + } + ... on Individual { + name + description + } + ... on Organization { + name + description + } + ... on Sector { + name + description + } + ... on System { + name + description + } + ... on Indicator { + name + description + valid_from + } + ... on Infrastructure { + name + description + } + ... on IntrusionSet { + name + description + first_seen + last_seen + } + ... on Position { + name + description + } + ... on City { + name + description + } + ... on Country { + name + description + } + ... on Region { + name + description + } + ... on Malware { + name + description + first_seen + last_seen + } + ... on ThreatActor { + name + description + first_seen + last_seen + } + ... on Tool { + name + description + } + ... on Vulnerability { + name + description + } + ... on Incident { + name + description + first_seen + last_seen + } + ... on Event { + name + } + ... on Channel { + name + } + ... on Narrative { + name + } + ... on Language { + name + } + ... on DataComponent { + name + } + ... on DataSource { + name + } + ... on Case { + name + } + ... on StixCyberObservable { + observable_value + x_opencti_description + } + } + } + } + } + ... on StixRelationship { + id + entity_type + relationship_type + ... on StixCoreRelationship { + start_time + stop_time + description + } + ... on StixSightingRelationship { + first_seen + last_seen + } + created + from { + ... on BasicObject { + id + entity_type + parent_types + } + ... on BasicRelationship { + id + entity_type + parent_types + } + ... on StixCoreObject { + created_at + } + ... on StixRelationship { + created_at + ... on StixCoreRelationship { + start_time + stop_time + description + } + ... on StixSightingRelationship { + first_seen + last_seen + } + created + } + ... on AttackPattern { + name + } + ... on Campaign { + name + } + ... on CourseOfAction { + name + } + ... on Individual { + name + } + ... on Organization { + name + } + ... on Sector { + name + } + ... on System { + name + } + ... on Indicator { + name + } + ... on Infrastructure { + name + } + ... on IntrusionSet { + name + } + ... on Position { + name + } + ... on City { + name + } + ... on Country { + name + } + ... on Region { + name + } + ... on Malware { + name + } + ... on ThreatActor { + name + } + ... on Tool { + name + } + ... on Vulnerability { + name + } + ... on Incident { + name + } + ... on Event { + name + } + ... on Channel { + name + } + ... on Narrative { + name + } + ... on Language { + name + } + ... on DataComponent { + name + } + ... on DataSource { + name + } + ... on Case { + name + } + ... on Report { + name + } + ... on ExternalReference { + source_name + url + external_id + } + ... on MarkingDefinition { + definition_type + definition + } + ... on StixCyberObservable { + observable_value + } + ... on ObservedData { + name + objects(first: 1) { + edges { + node { + ... on StixCoreObject { + id + entity_type + parent_types + created_at + createdBy { + ... on Identity { + id + name + entity_type + } + } + objectMarking { + id + definition + x_opencti_order + x_opencti_color + } + } + ... on AttackPattern { + name + description + x_mitre_id + } + ... on Campaign { + name + description + first_seen + last_seen + } + ... on Note { + attribute_abstract + } + ... on ObservedData { + name + first_observed + last_observed + } + ... on Opinion { + opinion + } + ... on Report { + name + description + published + } + ... on Grouping { + name + description + } + ... on CourseOfAction { + name + description + } + ... on Individual { + name + description + } + ... on Organization { + name + description + } + ... on Sector { + name + description + } + ... on System { + name + description + } + ... on Indicator { + name + description + valid_from + } + ... on Infrastructure { + name + description + } + ... on IntrusionSet { + name + description + first_seen + last_seen + } + ... on Position { + name + description + } + ... on City { + name + description + } + ... on Country { + name + description + } + ... on Region { + name + description + } + ... on Malware { + name + description + first_seen + last_seen + } + ... on ThreatActor { + name + description + first_seen + last_seen + } + ... on Tool { + name + description + } + ... on Vulnerability { + name + description + } + ... on Incident { + name + description + first_seen + last_seen + } + ... on Event { + name + } + ... on Channel { + name + } + ... on Narrative { + name + } + ... on Language { + name + } + ... on DataComponent { + name + } + ... on DataSource { + name + } + ... on Case { + name + } + ... on StixCyberObservable { + observable_value + x_opencti_description + } + } + } + } + } + } + to { + ... on BasicObject { + id + entity_type + parent_types + } + ... on BasicRelationship { + id + entity_type + parent_types + } + ... on StixCoreObject { + created_at + } + ... on StixRelationship { + created_at + ... on StixCoreRelationship { + start_time + stop_time + description + } + ... on StixSightingRelationship { + first_seen + last_seen + } + created + } + ... on AttackPattern { + name + } + ... on Campaign { + name + } + ... on CourseOfAction { + name + } + ... on Individual { + name + } + ... on Organization { + name + } + ... on Sector { + name + } + ... on System { + name + } + ... on Indicator { + name + } + ... on Infrastructure { + name + } + ... on IntrusionSet { + name + } + ... on Position { + name + } + ... on City { + name + } + ... on Country { + name + } + ... on Region { + name + } + ... on Malware { + name + } + ... on ThreatActor { + name + } + ... on Tool { + name + } + ... on Vulnerability { + name + } + ... on Incident { + name + } + ... on Event { + name + } + ... on Channel { + name + } + ... on Narrative { + name + } + ... on Language { + name + } + ... on DataComponent { + name + } + ... on DataSource { + name + } + ... on Case { + name + } + ... on Report { + name + } + ... on ExternalReference { + source_name + url + external_id + } + ... on MarkingDefinition { + definition_type + definition + } + ... on StixCyberObservable { + observable_value + } + ... on ObservedData { + name + objects(first: 1) { + edges { + node { + ... on StixCoreObject { + id + entity_type + parent_types + created_at + createdBy { + ... on Identity { + id + name + entity_type + } + } + objectMarking { + id + definition + x_opencti_order + x_opencti_color + } + } + ... on AttackPattern { + name + description + x_mitre_id + } + ... on Campaign { + name + description + first_seen + last_seen + } + ... on Note { + attribute_abstract + } + ... on ObservedData { + name + first_observed + last_observed + } + ... on Opinion { + opinion + } + ... on Report { + name + description + published + } + ... on Grouping { + name + description + } + ... on CourseOfAction { + name + description + } + ... on Individual { + name + description + } + ... on Organization { + name + description + } + ... on Sector { + name + description + } + ... on System { + name + description + } + ... on Indicator { + name + description + valid_from + } + ... on Infrastructure { + name + description + } + ... on IntrusionSet { + name + description + first_seen + last_seen + } + ... on Position { + name + description + } + ... on City { + name + description + } + ... on Country { + name + description + } + ... on Region { + name + description + } + ... on Malware { + name + description + first_seen + last_seen + } + ... on ThreatActor { + name + description + first_seen + last_seen + } + ... on Tool { + name + description + } + ... on Vulnerability { + name + description + } + ... on Incident { + name + description + first_seen + last_seen + } + ... on Event { + name + } + ... on Channel { + name + } + ... on Narrative { + name + } + ... on Language { + name + } + ... on DataComponent { + name + } + ... on DataSource { + name + } + ... on Case { + name + } + ... on StixCyberObservable { + observable_value + x_opencti_description + } + } + } + } + } + } + } + } + } + } + } + createdBy { + ... on Identity { + id + name + entity_type + } + } + objectMarking { + id + definition + x_opencti_order + x_opencti_color + } + from { + ... on BasicObject { + id + entity_type + parent_types + } + ... on BasicRelationship { + id + entity_type + parent_types + } + ... on StixCoreObject { + created_at + } + ... on StixRelationship { + created_at + ... on StixCoreRelationship { + start_time + stop_time + description + } + ... on StixSightingRelationship { + first_seen + last_seen + } + created + } + ... on AttackPattern { + name + } + ... on Campaign { + name + } + ... on CourseOfAction { + name + } + ... on Individual { + name + } + ... on Organization { + name + } + ... on Sector { + name + } + ... on System { + name + } + ... on Indicator { + name + } + ... on Infrastructure { + name + } + ... on IntrusionSet { + name + } + ... on Position { + name + } + ... on City { + name + } + ... on Country { + name + } + ... on Region { + name + } + ... on Malware { + name + } + ... on ThreatActor { + name + } + ... on Tool { + name + } + ... on Vulnerability { + name + } + ... on Incident { + name + } + ... on Event { + name + } + ... on Channel { + name + } + ... on Narrative { + name + } + ... on Language { + name + } + ... on DataComponent { + name + } + ... on DataSource { + name + } + ... on Case { + name + } + ... on Report { + name + } + ... on Note { + attribute_abstract + content + } + ... on ExternalReference { + source_name + url + external_id + } + ... on MarkingDefinition { + definition_type + definition + } + ... on StixCyberObservable { + observable_value + } + ... on ObservedData { + name + objects(first: 1) { + edges { + node { + ... on StixCoreObject { + id + entity_type + parent_types + created_at + createdBy { + ... on Identity { + id + name + entity_type + } + } + objectMarking { + id + definition + x_opencti_order + x_opencti_color + } + } + ... on AttackPattern { + name + description + x_mitre_id + } + ... on Campaign { + name + description + first_seen + last_seen + } + ... on Note { + attribute_abstract + } + ... on ObservedData { + name + first_observed + last_observed + } + ... on Opinion { + opinion + } + ... on Report { + name + description + published + } + ... on Grouping { + name + description + } + ... on CourseOfAction { + name + description + } + ... on Individual { + name + description + } + ... on Organization { + name + description + } + ... on Sector { + name + description + } + ... on System { + name + description + } + ... on Indicator { + name + description + valid_from + } + ... on Infrastructure { + name + description + } + ... on IntrusionSet { + name + description + first_seen + last_seen + } + ... on Position { + name + description + } + ... on City { + name + description + } + ... on Country { + name + description + } + ... on Region { + name + description + } + ... on Malware { + name + description + first_seen + last_seen + } + ... on ThreatActor { + name + description + first_seen + last_seen + } + ... on Tool { + name + description + } + ... on Vulnerability { + name + description + } + ... on Incident { + name + description + first_seen + last_seen + } + ... on Event { + name + } + ... on Channel { + name + } + ... on Narrative { + name + } + ... on Language { + name + } + ... on DataComponent { + name + } + ... on DataSource { + name + } + ... on Case { + name + } + ... on Report { + name + } + ... on ExternalReference { + source_name + url + external_id + } + ... on MarkingDefinition { + definition_type + definition + } + ... on StixCyberObservable { + observable_value + x_opencti_description + } + } + } + } + } + ... on StixRelationship { + id + entity_type + relationship_type + ... on StixCoreRelationship { + start_time + stop_time + description + } + ... on StixSightingRelationship { + first_seen + last_seen + } + created + from { + ... on BasicObject { + id + entity_type + } + ... on BasicRelationship { + id + entity_type + } + ... on StixCoreObject { + created_at + } + ... on StixRelationship { + created_at + ... on StixCoreRelationship { + start_time + stop_time + description + } + ... on StixSightingRelationship { + first_seen + last_seen + } + created + } + ... on AttackPattern { + name + } + ... on Campaign { + name + } + ... on CourseOfAction { + name + } + ... on Individual { + name + } + ... on Organization { + name + } + ... on Sector { + name + } + ... on System { + name + } + ... on Indicator { + name + } + ... on Infrastructure { + name + } + ... on IntrusionSet { + name + } + ... on Position { + name + } + ... on City { + name + } + ... on Country { + name + } + ... on Region { + name + } + ... on Malware { + name + } + ... on ThreatActor { + name + } + ... on Tool { + name + } + ... on Vulnerability { + name + } + ... on Incident { + name + } + ... on Event { + name + } + ... on Channel { + name + } + ... on Narrative { + name + } + ... on Language { + name + } + ... on DataComponent { + name + } + ... on DataSource { + name + } + ... on Case { + name + } + ... on Report { + name + } + ... on ExternalReference { + source_name + url + external_id + } + ... on MarkingDefinition { + definition_type + definition + } + } + to { + ... on BasicObject { + id + entity_type + } + ... on BasicRelationship { + id + entity_type + } + ... on StixCoreObject { + created_at + } + ... on StixRelationship { + created_at + ... on StixCoreRelationship { + start_time + stop_time + description + } + ... on StixSightingRelationship { + first_seen + last_seen + } + created + } + ... on AttackPattern { + name + } + ... on Campaign { + name + } + ... on CourseOfAction { + name + } + ... on Individual { + name + } + ... on Organization { + name + } + ... on Sector { + name + } + ... on System { + name + } + ... on Indicator { + name + } + ... on Infrastructure { + name + } + ... on IntrusionSet { + name + } + ... on Position { + name + } + ... on City { + name + } + ... on Country { + name + } + ... on Region { + name + } + ... on Malware { + name + } + ... on ThreatActor { + name + } + ... on Tool { + name + } + ... on Vulnerability { + name + } + ... on Incident { + name + } + ... on Event { + name + } + ... on Channel { + name + } + ... on Narrative { + name + } + ... on Language { + name + } + ... on DataComponent { + name + } + ... on DataSource { + name + } + ... on Case { + name + } + ... on Report { + name + } + ... on ExternalReference { + source_name + url + external_id + } + ... on MarkingDefinition { + definition_type + definition + } + } + } + } + to { + ... on BasicObject { + id + entity_type + parent_types + } + ... on BasicRelationship { + id + entity_type + parent_types + } + ... on StixCoreObject { + created_at + } + ... on StixRelationship { + created_at + ... on StixCoreRelationship { + start_time + stop_time + description + } + ... on StixSightingRelationship { + first_seen + last_seen + } + created + } + ... on AttackPattern { + name + } + ... on Campaign { + name + } + ... on CourseOfAction { + name + } + ... on Individual { + name + } + ... on Organization { + name + } + ... on Sector { + name + } + ... on System { + name + } + ... on Indicator { + name + } + ... on Infrastructure { + name + } + ... on IntrusionSet { + name + } + ... on Position { + name + } + ... on City { + name + } + ... on AdministrativeArea { + name + } + ... on Country { + name + } + ... on Region { + name + } + ... on Malware { + name + } + ... on ThreatActor { + name + } + ... on Tool { + name + } + ... on Vulnerability { + name + } + ... on Incident { + name + } + ... on Event { + name + } + ... on Channel { + name + } + ... on Narrative { + name + } + ... on Language { + name + } + ... on DataComponent { + name + } + ... on DataSource { + name + } + ... on Case { + name + } + ... on Report { + name + } + ... on Note { + attribute_abstract + content + } + ... on ExternalReference { + source_name + url + external_id + } + ... on MarkingDefinition { + definition_type + definition + } + ... on StixCyberObservable { + observable_value + } + ... on ObservedData { + name + objects(first: 1) { + edges { + node { + ... on StixCoreObject { + id + entity_type + parent_types + created_at + createdBy { + ... on Identity { + id + name + entity_type + } + } + objectMarking { + id + definition + x_opencti_order + x_opencti_color + } + } + ... on AttackPattern { + name + description + x_mitre_id + } + ... on Campaign { + name + description + first_seen + last_seen + } + ... on Note { + attribute_abstract + } + ... on ObservedData { + name + first_observed + last_observed + } + ... on Opinion { + opinion + } + ... on Report { + name + description + published + } + ... on Grouping { + name + description + } + ... on CourseOfAction { + name + description + } + ... on Individual { + name + description + } + ... on Organization { + name + description + } + ... on Sector { + name + description + } + ... on System { + name + description + } + ... on Indicator { + name + description + valid_from + } + ... on Infrastructure { + name + description + } + ... on IntrusionSet { + name + description + first_seen + last_seen + } + ... on Position { + name + description + } + ... on City { + name + description + } + ... on Country { + name + description + } + ... on Region { + name + description + } + ... on Malware { + name + description + first_seen + last_seen + } + ... on ThreatActor { + name + description + first_seen + last_seen + } + ... on Tool { + name + description + } + ... on Vulnerability { + name + description + } + ... on Incident { + name + description + first_seen + last_seen + } + ... on Event { + name + } + ... on Channel { + name + } + ... on Narrative { + name + } + ... on Language { + name + } + ... on DataComponent { + name + } + ... on DataSource { + name + } + ... on Case { + name + } + ... on Report { + name + } + ... on ExternalReference { + source_name + url + external_id + } + ... on MarkingDefinition { + definition_type + definition + } + ... on StixCyberObservable { + observable_value + x_opencti_description + } + } + } + } + } + ... on StixRelationship { + id + entity_type + relationship_type + ... on StixCoreRelationship { + start_time + stop_time + description + } + ... on StixSightingRelationship { + first_seen + last_seen + } + created + from { + ... on BasicObject { + id + entity_type + parent_types + } + ... on BasicRelationship { + id + entity_type + parent_types + } + ... on AttackPattern { + name + } + ... on Campaign { + name + } + ... on CourseOfAction { + name + } + ... on Individual { + name + } + ... on Organization { + name + } + ... on Sector { + name + } + ... on System { + name + } + ... on Indicator { + name + } + ... on Infrastructure { + name + } + ... on IntrusionSet { + name + } + ... on Position { + name + } + ... on City { + name + } + ... on Country { + name + } + ... on Region { + name + } + ... on Malware { + name + } + ... on ThreatActor { + name + } + ... on Tool { + name + } + ... on Vulnerability { + name + } + ... on Incident { + name + } + ... on Event { + name + } + ... on Channel { + name + } + ... on Narrative { + name + } + ... on Language { + name + } + ... on DataComponent { + name + } + ... on DataSource { + name + } + ... on Case { + name + } + ... on Report { + name + } + ... on ExternalReference { + source_name + url + external_id + } + ... on MarkingDefinition { + definition_type + definition + } + ... on StixCyberObservable { + observable_value + } + } + to { + ... on BasicObject { + id + entity_type + parent_types + } + ... on BasicRelationship { + id + entity_type + parent_types + } + ... on AttackPattern { + name + } + ... on Campaign { + name + } + ... on CourseOfAction { + name + } + ... on Individual { + name + } + ... on Organization { + name + } + ... on Sector { + name + } + ... on System { + name + } + ... on Indicator { + name + } + ... on Infrastructure { + name + } + ... on IntrusionSet { + name + } + ... on Position { + name + } + ... on City { + name + } + ... on Country { + name + } + ... on Region { + name + } + ... on Malware { + name + } + ... on ThreatActor { + name + } + ... on Tool { + name + } + ... on Vulnerability { + name + } + ... on Incident { + name + } + ... on Event { + name + } + ... on Channel { + name + } + ... on Narrative { + name + } + ... on Language { + name + } + ... on DataComponent { + name + } + ... on DataSource { + name + } + ... on Case { + name + } + ... on Report { + name + } + ... on ExternalReference { + source_name + url + external_id + } + ... on MarkingDefinition { + definition_type + definition + } + ... on StixCyberObservable { + observable_value + } + } + } + } + } + } + } + } +`; + +interface PublicStixRelationshipsListComponentProps { + queryRef: PreloadedQuery + dateAttribute: string +} + +const PublicStixRelationshipsListComponent = ({ + queryRef, + dateAttribute, +}: PublicStixRelationshipsListComponentProps) => { + const { publicStixRelationships } = usePreloadedQuery( + publicStixRelationshipsListQuery, + queryRef, + ); + + if (publicStixRelationships?.edges && publicStixRelationships.edges.length > 0) { + return ( + + ); + } + return ; +}; + +const PublicStixRelationshipsList = ({ + uriKey, + widget, + startDate, + endDate, + title, +}: PublicWidgetContainerProps) => { + const { t_i18n } = useFormatter(); + const { id, parameters, dataSelection } = widget; + const queryRef = useQueryLoading( + publicStixRelationshipsListQuery, + { + uriKey, + widgetId: id, + startDate, + endDate, + }, + ); + + const dateAttribute = dataSelection[0].date_attribute ?? 'created_at'; + + return ( + + {queryRef ? ( + }> + + + ) : ( + + )} + + ); +}; + +export default PublicStixRelationshipsList; diff --git a/opencti-platform/opencti-front/src/public/components/dashboard/stix_relationships/PublicStixRelationshipsMap.tsx b/opencti-platform/opencti-front/src/public/components/dashboard/stix_relationships/PublicStixRelationshipsMap.tsx new file mode 100644 index 000000000000..b98636ecad02 --- /dev/null +++ b/opencti-platform/opencti-front/src/public/components/dashboard/stix_relationships/PublicStixRelationshipsMap.tsx @@ -0,0 +1,139 @@ +import { graphql, PreloadedQuery, usePreloadedQuery } from 'react-relay'; +import React from 'react'; +import LocationMiniMapTargets from '@components/common/location/LocationMiniMapTargets'; +import type { PublicManifestWidget } from '../PublicManifest'; +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import { computeLevel } from '../../../../utils/Number'; +import type { PublicWidgetContainerProps } from '../PublicWidgetContainerProps'; +import { useFormatter } from '../../../../components/i18n'; +import useQueryLoading from '../../../../utils/hooks/useQueryLoading'; +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; +import { PublicStixRelationshipsMapQuery } from './__generated__/PublicStixRelationshipsMapQuery.graphql'; + +const publicStixRelationshipsMapQuery = graphql` + query PublicStixRelationshipsMapQuery( + $startDate: DateTime + $endDate: DateTime + $uriKey: String! + $widgetId : String! + ) { + publicStixRelationshipsDistribution( + startDate: $startDate + endDate: $endDate + uriKey: $uriKey + widgetId : $widgetId + ) { + label + value + entity { + ... on BasicObject { + entity_type + } + ... on BasicRelationship { + entity_type + } + ... on Country { + name + x_opencti_aliases + latitude + longitude + } + ... on City { + name + x_opencti_aliases + latitude + longitude + } + } + } + } +`; + +interface PublicStixRelationshipsMapComponentProps { + dataSelection: PublicManifestWidget['dataSelection'] + queryRef: PreloadedQuery +} + +const PublicStixRelationshipsMapComponent = ({ + dataSelection, + queryRef, +}: PublicStixRelationshipsMapComponentProps) => { + const { publicStixRelationshipsDistribution } = usePreloadedQuery( + publicStixRelationshipsMapQuery, + queryRef, + ); + + if ( + publicStixRelationshipsDistribution + && publicStixRelationshipsDistribution.length > 0 + ) { + const values = publicStixRelationshipsDistribution.flatMap((node) => { + if (node?.value === null || node?.value === undefined) return []; + return node.value; + }); + + const countries = publicStixRelationshipsDistribution.flatMap((node) => { + if (node?.entity?.entity_type !== 'Country') return []; + return { + ...node.entity, + level: computeLevel(node.value, values[values.length - 1], values[0] + 1), + }; + }); + + const cities = publicStixRelationshipsDistribution.flatMap((node) => { + if (node?.entity?.entity_type !== 'City') return []; + return node.entity; + }); + + return ( + + ); + } + return ; +}; + +const PublicStixRelationshipsMap = ({ + uriKey, + widget, + startDate, + endDate, + title, +}: PublicWidgetContainerProps) => { + const { t_i18n } = useFormatter(); + const { id, parameters, dataSelection } = widget; + const queryRef = useQueryLoading( + publicStixRelationshipsMapQuery, + { + uriKey, + widgetId: id, + startDate, + endDate, + }, + ); + + return ( + + {queryRef ? ( + }> + + + ) : ( + + )} + + ); +}; + +export default PublicStixRelationshipsMap; diff --git a/opencti-platform/opencti-front/src/public/components/dashboard/stix_relationships/PublicStixRelationshipsMultiAreaChart.tsx b/opencti-platform/opencti-front/src/public/components/dashboard/stix_relationships/PublicStixRelationshipsMultiAreaChart.tsx new file mode 100644 index 000000000000..1a7cd97b9ef4 --- /dev/null +++ b/opencti-platform/opencti-front/src/public/components/dashboard/stix_relationships/PublicStixRelationshipsMultiAreaChart.tsx @@ -0,0 +1,112 @@ +import { graphql, PreloadedQuery, usePreloadedQuery } from 'react-relay'; +import React from 'react'; +import type { PublicManifestWidget } from '../PublicManifest'; +import { useFormatter } from '../../../../components/i18n'; +import WidgetMultiAreas from '../../../../components/dashboard/WidgetMultiAreas'; +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import type { PublicWidgetContainerProps } from '../PublicWidgetContainerProps'; +import useQueryLoading from '../../../../utils/hooks/useQueryLoading'; +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; +import { PublicStixRelationshipsMultiAreaChartQuery } from './__generated__/PublicStixRelationshipsMultiAreaChartQuery.graphql'; +import { monthsAgo, now } from '../../../../utils/Time'; + +const publicStixRelationshipsMultiAreaChartQuery = graphql` + query PublicStixRelationshipsMultiAreaChartQuery( + $startDate: DateTime + $endDate: DateTime + $uriKey: String! + $widgetId : String! + ) { + publicStixRelationshipsMultiTimeSeries( + startDate: $startDate + endDate: $endDate + uriKey: $uriKey + widgetId : $widgetId + ) { + data { + date + value + } + } + } +`; + +interface PublicStixRelationshipsMultiAreaChartComponentProps { + parameters: PublicManifestWidget['parameters'] + dataSelection: PublicManifestWidget['dataSelection'] + queryRef: PreloadedQuery +} + +const PublicStixRelationshipsMultiAreaChartComponent = ({ + parameters, + dataSelection, + queryRef, +}: PublicStixRelationshipsMultiAreaChartComponentProps) => { + const { t_i18n } = useFormatter(); + const { publicStixRelationshipsMultiTimeSeries } = usePreloadedQuery( + publicStixRelationshipsMultiAreaChartQuery, + queryRef, + ); + + if (publicStixRelationshipsMultiTimeSeries) { + return ( + ({ + name: dataSelection[i].label || t_i18n('Number of entities'), + data: (serie?.data ?? []).map((entry) => ({ + x: new Date(entry?.date), + y: entry?.value, + })), + }))} + interval={parameters.interval} + isStacked={parameters.stacked} + hasLegend={parameters.legend} + withExport={false} + readonly={true} + /> + ); + } + return ; +}; + +const PublicStixRelationshipsMultiAreaChart = ({ + uriKey, + widget, + startDate = monthsAgo(12), + endDate = now(), + title, +}: PublicWidgetContainerProps) => { + const { t_i18n } = useFormatter(); + const { id, parameters, dataSelection } = widget; + const queryRef = useQueryLoading( + publicStixRelationshipsMultiAreaChartQuery, + { + uriKey, + widgetId: id, + startDate, + endDate, + }, + ); + + return ( + + {queryRef ? ( + }> + + + ) : ( + + )} + + ); +}; + +export default PublicStixRelationshipsMultiAreaChart; diff --git a/opencti-platform/opencti-front/src/public/components/dashboard/stix_relationships/PublicStixRelationshipsMultiHeatMap.tsx b/opencti-platform/opencti-front/src/public/components/dashboard/stix_relationships/PublicStixRelationshipsMultiHeatMap.tsx new file mode 100644 index 000000000000..04018b50a284 --- /dev/null +++ b/opencti-platform/opencti-front/src/public/components/dashboard/stix_relationships/PublicStixRelationshipsMultiHeatMap.tsx @@ -0,0 +1,118 @@ +import { graphql, PreloadedQuery, usePreloadedQuery } from 'react-relay'; +import React from 'react'; +import type { PublicManifestWidget } from '../PublicManifest'; +import { useFormatter } from '../../../../components/i18n'; +import WidgetMultiHeatMap from '../../../../components/dashboard/WidgetMultiHeatMap'; +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import type { PublicWidgetContainerProps } from '../PublicWidgetContainerProps'; +import useQueryLoading from '../../../../utils/hooks/useQueryLoading'; +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; +import { PublicStixRelationshipsMultiHeatMapQuery } from './__generated__/PublicStixRelationshipsMultiHeatMapQuery.graphql'; +import { monthsAgo, now } from '../../../../utils/Time'; + +const publicStixRelationshipsMultiHeatMapQuery = graphql` + query PublicStixRelationshipsMultiHeatMapQuery( + $startDate: DateTime + $endDate: DateTime + $uriKey: String! + $widgetId : String! + ) { + publicStixRelationshipsMultiTimeSeries( + startDate: $startDate + endDate: $endDate + uriKey: $uriKey + widgetId : $widgetId + ) { + data { + date + value + } + } + } +`; + +interface PublicStixRelationshipsMultiHeatMapComponentProps { + parameters: PublicManifestWidget['parameters'] + dataSelection: PublicManifestWidget['dataSelection'] + queryRef: PreloadedQuery +} + +const PublicStixRelationshipsMultiHeatMapComponent = ({ + parameters, + dataSelection, + queryRef, +}: PublicStixRelationshipsMultiHeatMapComponentProps) => { + const { t_i18n } = useFormatter(); + const { publicStixRelationshipsMultiTimeSeries } = usePreloadedQuery( + publicStixRelationshipsMultiHeatMapQuery, + queryRef, + ); + + if (publicStixRelationshipsMultiTimeSeries) { + const allValues = publicStixRelationshipsMultiTimeSeries + .map((serie) => (serie?.data ?? []).flatMap((o) => o?.value ?? [])) + .flat(); + const maxValue = Math.max(...allValues); + const minValue = Math.min(...allValues); + + return ( + ({ + name: dataSelection[i].label ?? t_i18n('Number of entities'), + data: (serie?.data ?? []).map((entry) => ({ + x: new Date(entry?.date), + y: entry?.value, + })), + })).sort((a, b) => b.name.localeCompare(a.name))} + minValue={minValue} + maxValue={maxValue} + isStacked={parameters.stacked} + withExport={false} + readonly={true} + /> + ); + } + return ; +}; + +const PublicStixRelationshipsMultiHeatMap = ({ + uriKey, + widget, + startDate = monthsAgo(12), + endDate = now(), + title, +}: PublicWidgetContainerProps) => { + const { t_i18n } = useFormatter(); + const { id, parameters, dataSelection } = widget; + const queryRef = useQueryLoading( + publicStixRelationshipsMultiHeatMapQuery, + { + uriKey, + widgetId: id, + startDate, + endDate, + }, + ); + + return ( + + {queryRef ? ( + }> + + + ) : ( + + )} + + ); +}; + +export default PublicStixRelationshipsMultiHeatMap; diff --git a/opencti-platform/opencti-front/src/public/components/dashboard/stix_relationships/PublicStixRelationshipsMultiHorizontalBars.tsx b/opencti-platform/opencti-front/src/public/components/dashboard/stix_relationships/PublicStixRelationshipsMultiHorizontalBars.tsx new file mode 100644 index 000000000000..7f188259539c --- /dev/null +++ b/opencti-platform/opencti-front/src/public/components/dashboard/stix_relationships/PublicStixRelationshipsMultiHorizontalBars.tsx @@ -0,0 +1,513 @@ +import { graphql, PreloadedQuery, usePreloadedQuery } from 'react-relay'; +import React from 'react'; +import * as R from 'ramda'; +import type { PublicManifestWidget } from '../PublicManifest'; +import { useFormatter } from '../../../../components/i18n'; +import { defaultValue } from '../../../../utils/Graph'; +import WidgetHorizontalBars from '../../../../components/dashboard/WidgetHorizontalBars'; +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import type { PublicWidgetContainerProps } from '../PublicWidgetContainerProps'; +import useQueryLoading from '../../../../utils/hooks/useQueryLoading'; +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; +import { PublicStixRelationshipsMultiHorizontalBarsQuery } from './__generated__/PublicStixRelationshipsMultiHorizontalBarsQuery.graphql'; + +const publicStixRelationshipsMultiHorizontalBarsQuery = graphql` + query PublicStixRelationshipsMultiHorizontalBarsQuery( + $startDate: DateTime + $endDate: DateTime + $uriKey: String! + $widgetId : String! + ) { + publicStixRelationshipsDistribution( + startDate: $startDate + endDate: $endDate + uriKey: $uriKey + widgetId : $widgetId + ) { + label + value + entity { + ... on BasicObject { + entity_type + id + } + ... on BasicRelationship { + entity_type + id + } + ... on AttackPattern { + name + description + } + ... on Campaign { + name + description + } + ... on CourseOfAction { + name + description + } + ... on Individual { + name + description + } + ... on Organization { + name + description + } + ... on Sector { + name + description + } + ... on System { + name + description + } + ... on Indicator { + name + description + } + ... on Infrastructure { + name + description + } + ... on IntrusionSet { + name + description + } + ... on Position { + name + description + } + ... on City { + name + description + } + ... on AdministrativeArea { + name + description + } + ... on Country { + name + description + } + ... on Region { + name + description + } + ... on Malware { + name + description + } + ... on ThreatActor { + name + description + } + ... on Tool { + name + description + } + ... on Vulnerability { + name + description + } + ... on Incident { + name + description + } + ... on Event { + name + description + } + ... on Channel { + name + description + } + ... on Narrative { + name + description + } + ... on Language { + name + } + ... on DataComponent { + name + } + ... on DataSource { + name + } + ... on Case { + name + } + ... on Report { + name + } + ... on StixCyberObservable { + observable_value + } + ... on MarkingDefinition { + definition_type + definition + x_opencti_color + } + ... on KillChainPhase { + kill_chain_name + phase_name + } + ... on Creator { + name + } + ... on Report { + name + } + ... on Grouping { + name + } + ... on Note { + attribute_abstract + content + } + ... on Opinion { + opinion + } + ... on Label { + value + color + } + } + breakdownDistribution { + value + label + entity { + ... on BasicObject { + entity_type + id + } + ... on BasicRelationship { + entity_type + id + } + ... on AttackPattern { + name + description + } + ... on Campaign { + name + description + } + ... on CourseOfAction { + name + description + } + ... on Individual { + name + description + } + ... on Organization { + name + description + } + ... on Sector { + name + description + } + ... on System { + name + description + } + ... on Indicator { + name + description + } + ... on Infrastructure { + name + description + } + ... on IntrusionSet { + name + description + } + ... on Position { + name + description + } + ... on City { + name + description + } + ... on Country { + name + description + } + ... on AdministrativeArea { + name + description + } + ... on Region { + name + description + } + ... on Malware { + name + description + } + ... on ThreatActor { + name + description + } + ... on Tool { + name + description + } + ... on Vulnerability { + name + description + } + ... on Incident { + name + description + } + ... on Event { + name + description + } + ... on Channel { + name + description + } + ... on Narrative { + name + description + } + ... on Language { + name + } + ... on DataComponent { + name + } + ... on DataSource { + name + } + ... on Case { + name + } + ... on Report { + name + } + ... on StixCyberObservable { + observable_value + } + ... on MarkingDefinition { + definition_type + definition + x_opencti_color + } + ... on KillChainPhase { + kill_chain_name + phase_name + } + ... on Creator { + name + } + ... on Report { + name + } + ... on Grouping { + name + } + ... on Note { + attribute_abstract + content + } + ... on Opinion { + opinion + } + ... on Label { + value + color + } + } + } + } + } +`; + +interface PublicStixRelationshipsMultiHorizontalBarsComponentProps { + parameters: PublicManifestWidget['parameters'] + dataSelection: PublicManifestWidget['dataSelection'] + queryRef: PreloadedQuery +} + +const PublicStixRelationshipsMultiHorizontalBarsComponent = ({ + parameters, + dataSelection, + queryRef, +}: PublicStixRelationshipsMultiHorizontalBarsComponentProps) => { + const { publicStixRelationshipsDistribution } = usePreloadedQuery( + publicStixRelationshipsMultiHorizontalBarsQuery, + queryRef, + ); + + if ( + publicStixRelationshipsDistribution + && publicStixRelationshipsDistribution.length > 0 + ) { + const selection = dataSelection[0]; + const finalField = selection.attribute || 'entity_type'; + const subSelection = dataSelection[1]; + const finalSubDistributionField = subSelection.attribute || 'entity_type'; + + const categories = publicStixRelationshipsDistribution.map((n) => defaultValue(n?.entity)); + const entitiesMapping: Record = {}; + for (const distrib of publicStixRelationshipsDistribution) { + for (const subDistrib of distrib?.breakdownDistribution ?? []) { + entitiesMapping[ + finalSubDistributionField === 'internal_id' + ? defaultValue(subDistrib?.entity) + : subDistrib?.label + ] = (entitiesMapping[ + finalSubDistributionField === 'internal_id' + ? defaultValue(subDistrib?.entity) + : subDistrib?.label + ] || 0) + (subDistrib?.value ?? 0); + } + } + const sortedEntityMapping = R.take( + subSelection.number ?? 15, + Object.entries(entitiesMapping).sort(([, a], [, b]) => b - a), + ); + const categoriesValues: Record = {}; + for (const distrib of publicStixRelationshipsDistribution) { + for (const sortedEntity of sortedEntityMapping) { + const entityData = R.head( + (distrib?.breakdownDistribution ?? []).filter( + (n) => (finalSubDistributionField === 'internal_id' + ? defaultValue(n?.entity) + : n?.label) === sortedEntity[0], + ), + ); + let value = 0; + if (entityData && entityData.value) { + value = entityData.value; + } + if (categoriesValues[defaultValue(distrib?.entity)]) { + categoriesValues[defaultValue(distrib?.entity)].push(value); + } else { + categoriesValues[defaultValue(distrib?.entity)] = [value]; + } + } + const sum = ( + categoriesValues[defaultValue(distrib?.entity)] || [] + ).reduce((partialSum, a) => partialSum + a, 0); + if (categoriesValues[defaultValue(distrib?.entity)]) { + categoriesValues[defaultValue(distrib?.entity)].push( + (distrib?.value ?? 0) - sum, + ); + } else { + categoriesValues[defaultValue(distrib?.entity)] = [ + (distrib?.value ?? 0) - sum, + ]; + } + } + sortedEntityMapping.push(['Others', 0]); + const chartData = sortedEntityMapping.map((n, k) => { + return { + name: n[0], + data: Object.entries(categoriesValues).map((o) => o[1][k]), + }; + }); + const subSectionIds: Record = {}; + if ( + finalField === 'internal_id' + && finalSubDistributionField === 'internal_id' + ) { + // find subbars orders for entity subbars redirection + for (const distrib of publicStixRelationshipsDistribution) { + for (const subDistrib of (distrib?.breakdownDistribution ?? [])) { + if (subDistrib?.label) { + subSectionIds[subDistrib.label] = (subSectionIds[subDistrib.label] || 0) + + (subDistrib.value ?? 0); + } + } + } + } + const subSectionIdsOrder = R.take( + subSelection.number ?? 15, + Object.entries(subSectionIds) + .sort(([, a], [, b]) => b - a) + .map((k) => k[0]), + ); + const redirectionUtils = finalField === 'internal_id' + ? publicStixRelationshipsDistribution.map((n) => ({ + id: n?.label, + entity_type: n?.entity?.entity_type, + series: subSectionIdsOrder.map((subSectionId) => { + const [entity] = (n?.breakdownDistribution ?? []).filter( + (e) => e?.label === subSectionId, + ); + return { + id: subSectionId, + entity_type: entity ? entity.entity?.entity_type : null, + }; + }), + })) + : undefined; + + return ( + + ); + } + return ; +}; + +const PublicStixRelationshipsMultiHorizontalBars = ({ + uriKey, + widget, + startDate, + endDate, + title, +}: PublicWidgetContainerProps) => { + const { t_i18n } = useFormatter(); + const { id, parameters, dataSelection } = widget; + const queryRef = useQueryLoading( + publicStixRelationshipsMultiHorizontalBarsQuery, + { + uriKey, + widgetId: id, + startDate, + endDate, + }, + ); + + return ( + + {queryRef ? ( + }> + + + ) : ( + + )} + + ); +}; + +export default PublicStixRelationshipsMultiHorizontalBars; diff --git a/opencti-platform/opencti-front/src/public/components/dashboard/stix_relationships/PublicStixRelationshipsMultiLineChart.tsx b/opencti-platform/opencti-front/src/public/components/dashboard/stix_relationships/PublicStixRelationshipsMultiLineChart.tsx new file mode 100644 index 000000000000..82f2bb73a617 --- /dev/null +++ b/opencti-platform/opencti-front/src/public/components/dashboard/stix_relationships/PublicStixRelationshipsMultiLineChart.tsx @@ -0,0 +1,111 @@ +import { graphql, PreloadedQuery, usePreloadedQuery } from 'react-relay'; +import React from 'react'; +import type { PublicManifestWidget } from '../PublicManifest'; +import { useFormatter } from '../../../../components/i18n'; +import WidgetMultiLines from '../../../../components/dashboard/WidgetMultiLines'; +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import type { PublicWidgetContainerProps } from '../PublicWidgetContainerProps'; +import useQueryLoading from '../../../../utils/hooks/useQueryLoading'; +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; +import { PublicStixRelationshipsMultiLineChartQuery } from './__generated__/PublicStixRelationshipsMultiLineChartQuery.graphql'; +import { monthsAgo, now } from '../../../../utils/Time'; + +const publicStixRelationshipsMultiLineChartQuery = graphql` + query PublicStixRelationshipsMultiLineChartQuery( + $startDate: DateTime + $endDate: DateTime + $uriKey: String! + $widgetId : String! + ) { + publicStixRelationshipsMultiTimeSeries( + startDate: $startDate + endDate: $endDate + uriKey: $uriKey + widgetId : $widgetId + ) { + data { + date + value + } + } + } +`; + +interface PublicStixRelationshipsMultiLineChartComponentProps { + parameters: PublicManifestWidget['parameters'] + dataSelection: PublicManifestWidget['dataSelection'] + queryRef: PreloadedQuery +} + +const PublicStixRelationshipsMultiLineChartComponent = ({ + parameters, + dataSelection, + queryRef, +}: PublicStixRelationshipsMultiLineChartComponentProps) => { + const { t_i18n } = useFormatter(); + const { publicStixRelationshipsMultiTimeSeries } = usePreloadedQuery( + publicStixRelationshipsMultiLineChartQuery, + queryRef, + ); + + if (publicStixRelationshipsMultiTimeSeries) { + return ( + ({ + name: dataSelection[i].label || t_i18n('Number of entities'), + data: (serie?.data ?? []).map((entry) => ({ + x: new Date(entry?.date), + y: entry?.value, + })), + }))} + interval={parameters.interval} + hasLegend={parameters.legend} + withExport={false} + readonly={true} + /> + ); + } + return ; +}; + +const PublicStixRelationshipsMultiLineChart = ({ + uriKey, + widget, + startDate = monthsAgo(12), + endDate = now(), + title, +}: PublicWidgetContainerProps) => { + const { t_i18n } = useFormatter(); + const { id, parameters, dataSelection } = widget; + const queryRef = useQueryLoading( + publicStixRelationshipsMultiLineChartQuery, + { + uriKey, + widgetId: id, + startDate, + endDate, + }, + ); + + return ( + + {queryRef ? ( + }> + + + ) : ( + + )} + + ); +}; + +export default PublicStixRelationshipsMultiLineChart; diff --git a/opencti-platform/opencti-front/src/public/components/dashboard/stix_relationships/PublicStixRelationshipsMultiVerticalBars.tsx b/opencti-platform/opencti-front/src/public/components/dashboard/stix_relationships/PublicStixRelationshipsMultiVerticalBars.tsx new file mode 100644 index 000000000000..5aebf1b8670c --- /dev/null +++ b/opencti-platform/opencti-front/src/public/components/dashboard/stix_relationships/PublicStixRelationshipsMultiVerticalBars.tsx @@ -0,0 +1,112 @@ +import { graphql, PreloadedQuery, usePreloadedQuery } from 'react-relay'; +import React from 'react'; +import type { PublicManifestWidget } from '../PublicManifest'; +import { useFormatter } from '../../../../components/i18n'; +import WidgetVerticalBars from '../../../../components/dashboard/WidgetVerticalBars'; +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import type { PublicWidgetContainerProps } from '../PublicWidgetContainerProps'; +import useQueryLoading from '../../../../utils/hooks/useQueryLoading'; +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; +import { PublicStixRelationshipsMultiVerticalBarsQuery } from './__generated__/PublicStixRelationshipsMultiVerticalBarsQuery.graphql'; +import { monthsAgo, now } from '../../../../utils/Time'; + +const publicStixRelationshipsMultiVerticalBarsQuery = graphql` + query PublicStixRelationshipsMultiVerticalBarsQuery( + $startDate: DateTime + $endDate: DateTime + $uriKey: String! + $widgetId : String! + ) { + publicStixRelationshipsMultiTimeSeries( + startDate: $startDate + endDate: $endDate + uriKey: $uriKey + widgetId : $widgetId + ) { + data { + date + value + } + } + } +`; + +interface PublicStixRelationshipsMultiVerticalBarsComponentProps { + parameters: PublicManifestWidget['parameters'] + dataSelection: PublicManifestWidget['dataSelection'] + queryRef: PreloadedQuery +} + +const PublicStixRelationshipsMultiVerticalBarsComponent = ({ + parameters, + dataSelection, + queryRef, +}: PublicStixRelationshipsMultiVerticalBarsComponentProps) => { + const { t_i18n } = useFormatter(); + const { publicStixRelationshipsMultiTimeSeries } = usePreloadedQuery( + publicStixRelationshipsMultiVerticalBarsQuery, + queryRef, + ); + + if (publicStixRelationshipsMultiTimeSeries) { + return ( + ({ + name: dataSelection[i].label ?? t_i18n('Number of entities'), + data: (serie?.data ?? []).map((entry) => ({ + x: new Date(entry?.date), + y: entry?.value, + })), + }))} + interval={parameters.interval} + isStacked={parameters.stacked} + hasLegend={parameters.legend} + withExport={false} + readonly={true} + /> + ); + } + return ; +}; + +const PublicStixRelationshipsMultiVerticalBars = ({ + uriKey, + widget, + startDate = monthsAgo(12), + endDate = now(), + title, +}: PublicWidgetContainerProps) => { + const { t_i18n } = useFormatter(); + const { id, parameters, dataSelection } = widget; + const queryRef = useQueryLoading( + publicStixRelationshipsMultiVerticalBarsQuery, + { + uriKey, + widgetId: id, + startDate, + endDate, + }, + ); + + return ( + + {queryRef ? ( + }> + + + ) : ( + + )} + + ); +}; + +export default PublicStixRelationshipsMultiVerticalBars; diff --git a/opencti-platform/opencti-front/src/public/components/dashboard/stix_relationships/PublicStixRelationshipsNumber.tsx b/opencti-platform/opencti-front/src/public/components/dashboard/stix_relationships/PublicStixRelationshipsNumber.tsx new file mode 100644 index 000000000000..eec79cf58f09 --- /dev/null +++ b/opencti-platform/opencti-front/src/public/components/dashboard/stix_relationships/PublicStixRelationshipsNumber.tsx @@ -0,0 +1,85 @@ +import { graphql, PreloadedQuery, usePreloadedQuery } from 'react-relay'; +import React from 'react'; +import WidgetNumber from '../../../../components/dashboard/WidgetNumber'; +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import type { PublicWidgetContainerProps } from '../PublicWidgetContainerProps'; +import { useFormatter } from '../../../../components/i18n'; +import useQueryLoading from '../../../../utils/hooks/useQueryLoading'; +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; +import { PublicStixRelationshipsNumberQuery } from './__generated__/PublicStixRelationshipsNumberQuery.graphql'; + +const publicStixRelationshipsNumberQuery = graphql` + query PublicStixRelationshipsNumberQuery( + $startDate: DateTime + $endDate: DateTime + $uriKey: String! + $widgetId : String! + ) { + publicStixRelationshipsNumber( + startDate: $startDate + endDate: $endDate + uriKey: $uriKey + widgetId : $widgetId + ) { + total + count + } + } +`; + +interface PublicStixCoreRelationshipsNumberComponentProps { + queryRef: PreloadedQuery +} + +const PublicStixCoreRelationshipsNumberComponent = ({ + queryRef, +}: PublicStixCoreRelationshipsNumberComponentProps) => { + const { publicStixRelationshipsNumber } = usePreloadedQuery( + publicStixRelationshipsNumberQuery, + queryRef, + ); + + if (publicStixRelationshipsNumber) { + const { total, count } = publicStixRelationshipsNumber; + return ; + } + return ; +}; + +const PublicStixCoreRelationshipsNumber = ({ + uriKey, + widget, + startDate, + endDate, + title, +}: PublicWidgetContainerProps) => { + const { t_i18n } = useFormatter(); + const { id, parameters } = widget; + const queryRef = useQueryLoading( + publicStixRelationshipsNumberQuery, + { + uriKey, + widgetId: id, + startDate, + endDate, + }, + ); + + return ( + + {queryRef ? ( + }> + + + ) : ( + + )} + + ); +}; + +export default PublicStixCoreRelationshipsNumber; diff --git a/opencti-platform/opencti-front/src/public/components/dashboard/stix_relationships/PublicStixRelationshipsRadar.tsx b/opencti-platform/opencti-front/src/public/components/dashboard/stix_relationships/PublicStixRelationshipsRadar.tsx new file mode 100644 index 000000000000..02fc0c3b879f --- /dev/null +++ b/opencti-platform/opencti-front/src/public/components/dashboard/stix_relationships/PublicStixRelationshipsRadar.tsx @@ -0,0 +1,241 @@ +import { graphql, PreloadedQuery, usePreloadedQuery } from 'react-relay'; +import React from 'react'; +import type { PublicManifestWidget } from '../PublicManifest'; +import WidgetRadar from '../../../../components/dashboard/WidgetRadar'; +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import type { PublicWidgetContainerProps } from '../PublicWidgetContainerProps'; +import { useFormatter } from '../../../../components/i18n'; +import useQueryLoading from '../../../../utils/hooks/useQueryLoading'; +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; +import { PublicStixRelationshipsRadarQuery } from './__generated__/PublicStixRelationshipsRadarQuery.graphql'; + +const publicStixRelationshipsRadarsQuery = graphql` + query PublicStixRelationshipsRadarQuery( + $startDate: DateTime + $endDate: DateTime + $uriKey: String! + $widgetId : String! + ) { + publicStixRelationshipsDistribution( + startDate: $startDate + endDate: $endDate + uriKey: $uriKey + widgetId : $widgetId + ) { + label + value + entity { + ... on BasicObject { + entity_type + } + ... on BasicRelationship { + entity_type + } + ... on AttackPattern { + name + description + x_mitre_id + } + ... on Campaign { + name + description + } + ... on CourseOfAction { + name + description + } + ... on Individual { + name + description + } + ... on Organization { + name + description + } + ... on Sector { + name + description + } + ... on System { + name + description + } + ... on Indicator { + name + description + } + ... on Infrastructure { + name + description + } + ... on IntrusionSet { + name + description + } + ... on Position { + name + description + } + ... on City { + name + description + } + ... on AdministrativeArea { + name + description + } + ... on Country { + name + description + } + ... on Region { + name + description + } + ... on Malware { + name + description + } + ... on ThreatActor { + name + description + } + ... on Tool { + name + description + } + ... on Vulnerability { + name + description + } + ... on Incident { + name + description + } + ... on Event { + name + description + } + ... on Channel { + name + description + } + ... on Narrative { + name + description + } + ... on Language { + name + } + ... on DataComponent { + name + } + ... on DataSource { + name + } + ... on Case { + name + } + ... on StixCyberObservable { + observable_value + } + ... on MarkingDefinition { + definition_type + definition + } + ... on KillChainPhase { + kill_chain_name + phase_name + } + ... on Creator { + name + } + ... on Report { + name + } + ... on Grouping { + name + } + ... on Note { + attribute_abstract + content + } + ... on Opinion { + opinion + } + } + } + } +`; + +interface PublicStixRelationshipsRadarComponentProps { + dataSelection: PublicManifestWidget['dataSelection'] + queryRef: PreloadedQuery +} + +const PublicStixRelationshipsRadarComponent = ({ + dataSelection, + queryRef, +}: PublicStixRelationshipsRadarComponentProps) => { + const { publicStixRelationshipsDistribution } = usePreloadedQuery( + publicStixRelationshipsRadarsQuery, + queryRef, + ); + + if ( + publicStixRelationshipsDistribution + && publicStixRelationshipsDistribution.length > 0 + ) { + return ( + + ); + } + return ; +}; + +const PublicStixRelationshipsRadar = ({ + uriKey, + widget, + startDate, + endDate, + title, +}: PublicWidgetContainerProps) => { + const { t_i18n } = useFormatter(); + const { id, parameters, dataSelection } = widget; + const queryRef = useQueryLoading( + publicStixRelationshipsRadarsQuery, + { + uriKey, + widgetId: id, + startDate, + endDate, + }, + ); + + return ( + + {queryRef ? ( + }> + + + ) : ( + + )} + + ); +}; + +export default PublicStixRelationshipsRadar; diff --git a/opencti-platform/opencti-front/src/public/components/dashboard/stix_relationships/PublicStixRelationshipsTimeline.tsx b/opencti-platform/opencti-front/src/public/components/dashboard/stix_relationships/PublicStixRelationshipsTimeline.tsx new file mode 100644 index 000000000000..effb416aaa52 --- /dev/null +++ b/opencti-platform/opencti-front/src/public/components/dashboard/stix_relationships/PublicStixRelationshipsTimeline.tsx @@ -0,0 +1,1109 @@ +import { graphql, PreloadedQuery, usePreloadedQuery } from 'react-relay'; +import React from 'react'; +import { resolveLink } from '../../../../utils/Entity'; +import WidgetTimeline from '../../../../components/dashboard/WidgetTimeline'; +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import type { PublicWidgetContainerProps } from '../PublicWidgetContainerProps'; +import { useFormatter } from '../../../../components/i18n'; +import useQueryLoading from '../../../../utils/hooks/useQueryLoading'; +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; +import { PublicStixRelationshipsTimelineQuery } from './__generated__/PublicStixRelationshipsTimelineQuery.graphql'; +import type { PublicManifestWidget } from '../PublicManifest'; + +const publicStixRelationshipsTimelineQuery = graphql` + query PublicStixRelationshipsTimelineQuery( + $startDate: DateTime + $endDate: DateTime + $uriKey: String! + $widgetId : String! + ) { + publicStixRelationships( + startDate: $startDate + endDate: $endDate + uriKey: $uriKey + widgetId : $widgetId + ) { + edges { + node { + id + entity_type + parent_types + relationship_type + confidence + is_inferred + created + x_opencti_inferences { + rule { + id + name + } + } + ... on StixCoreRelationship { + start_time + stop_time + description + killChainPhases { + id + phase_name + x_opencti_order + } + } + ... on StixSightingRelationship { + first_seen + last_seen + } + from { + ... on StixDomainObject { + id + entity_type + parent_types + created_at + updated_at + objectLabel { + id + value + color + } + } + ... on AttackPattern { + name + description + x_mitre_id + killChainPhases { + id + phase_name + x_opencti_order + } + objectMarking { + id + definition + x_opencti_order + x_opencti_color + } + objectLabel { + id + value + color + } + } + ... on Campaign { + name + description + } + ... on CourseOfAction { + name + description + } + ... on Individual { + name + description + } + ... on Organization { + name + description + } + ... on Sector { + name + description + } + ... on System { + name + description + } + ... on Indicator { + name + description + } + ... on Infrastructure { + name + description + } + ... on IntrusionSet { + name + description + } + ... on Position { + name + description + } + ... on City { + name + description + } + ... on AdministrativeArea { + name + description + } + ... on Country { + name + description + } + ... on Region { + name + description + } + ... on Malware { + name + description + } + ... on ThreatActor { + name + description + } + ... on Tool { + name + description + } + ... on Vulnerability { + name + description + } + ... on Incident { + name + description + } + ... on Event { + name + description + } + ... on Channel { + name + description + } + ... on Narrative { + name + description + } + ... on Language { + name + } + ... on DataComponent { + name + } + ... on DataSource { + name + } + ... on Case { + name + } + ... on Note { + attribute_abstract + content + } + ... on Opinion { + opinion + } + ... on StixCyberObservable { + id + entity_type + parent_types + observable_value + objectMarking { + id + definition + x_opencti_order + x_opencti_color + } + objectLabel { + id + value + color + } + } + ... on Indicator { + id + name + pattern_type + pattern_version + description + valid_from + valid_until + x_opencti_score + x_opencti_main_observable_type + created + objectMarking { + id + definition + x_opencti_order + x_opencti_color + } + objectLabel { + id + value + color + } + } + ... on StixRelationship { + id + entity_type + parent_types + created + created_at + from { + ... on StixDomainObject { + id + entity_type + parent_types + created_at + updated_at + objectLabel { + id + value + color + } + } + ... on AttackPattern { + name + description + x_mitre_id + killChainPhases { + id + phase_name + x_opencti_order + } + objectMarking { + id + definition + x_opencti_order + x_opencti_color + } + objectLabel { + id + value + color + } + } + ... on Campaign { + name + description + } + ... on CourseOfAction { + name + description + } + ... on Individual { + name + description + } + ... on Organization { + name + description + } + ... on Sector { + name + description + } + ... on System { + name + description + } + ... on Indicator { + name + description + } + ... on Infrastructure { + name + description + } + ... on IntrusionSet { + name + description + } + ... on Position { + name + description + } + ... on City { + name + description + } + ... on Country { + name + description + } + ... on Region { + name + description + } + ... on Malware { + name + description + } + ... on ThreatActor { + name + description + } + ... on Tool { + name + description + } + ... on Vulnerability { + name + description + } + ... on Incident { + name + description + } + ... on StixCyberObservable { + id + entity_type + parent_types + observable_value + objectMarking { + id + definition + x_opencti_order + x_opencti_color + } + objectLabel { + id + value + color + } + } + ... on Indicator { + id + name + pattern_type + pattern_version + description + valid_from + valid_until + x_opencti_score + x_opencti_main_observable_type + created + objectMarking { + id + definition + x_opencti_order + x_opencti_color + } + objectLabel { + id + value + color + } + } + ... on StixRelationship { + id + entity_type + parent_types + created + created_at + } + } + to { + ... on StixDomainObject { + id + entity_type + parent_types + created_at + updated_at + objectLabel { + id + value + color + } + } + ... on AttackPattern { + name + description + x_mitre_id + killChainPhases { + id + phase_name + x_opencti_order + } + objectMarking { + id + definition + x_opencti_order + x_opencti_color + } + objectLabel { + id + value + color + } + } + ... on Campaign { + name + description + } + ... on CourseOfAction { + name + description + } + ... on Individual { + name + description + } + ... on Organization { + name + description + } + ... on Sector { + name + description + } + ... on System { + name + description + } + ... on Indicator { + name + description + } + ... on Infrastructure { + name + description + } + ... on IntrusionSet { + name + description + } + ... on Position { + name + description + } + ... on City { + name + description + } + ... on Country { + name + description + } + ... on Region { + name + description + } + ... on Malware { + name + description + } + ... on ThreatActor { + name + description + } + ... on Tool { + name + description + } + ... on Vulnerability { + name + description + } + ... on Incident { + name + description + } + ... on StixCyberObservable { + id + entity_type + parent_types + observable_value + objectMarking { + id + definition + x_opencti_order + x_opencti_color + } + objectLabel { + id + value + color + } + } + ... on Indicator { + id + name + pattern_type + pattern_version + description + valid_from + valid_until + x_opencti_score + x_opencti_main_observable_type + created + objectMarking { + id + definition + x_opencti_order + x_opencti_color + } + objectLabel { + id + value + color + } + } + ... on StixRelationship { + id + entity_type + created + created_at + parent_types + } + } + } + } + to { + ... on StixDomainObject { + id + entity_type + parent_types + created_at + updated_at + objectLabel { + id + value + color + } + } + ... on AttackPattern { + name + description + x_mitre_id + killChainPhases { + id + phase_name + x_opencti_order + } + objectMarking { + id + definition + x_opencti_order + x_opencti_color + } + objectLabel { + id + value + color + } + } + ... on Campaign { + name + description + } + ... on CourseOfAction { + name + description + } + ... on Individual { + name + description + } + ... on Organization { + name + description + } + ... on Sector { + name + description + } + ... on System { + name + description + } + ... on Indicator { + name + description + } + ... on Infrastructure { + name + description + } + ... on IntrusionSet { + name + description + } + ... on Position { + name + description + } + ... on City { + name + description + } + ... on Country { + name + description + } + ... on Region { + name + description + } + ... on Malware { + name + description + } + ... on ThreatActor { + name + description + } + ... on Tool { + name + description + } + ... on Vulnerability { + name + description + } + ... on Incident { + name + description + } + ... on StixCyberObservable { + id + entity_type + parent_types + observable_value + objectMarking { + id + definition + x_opencti_order + x_opencti_color + } + objectLabel { + id + value + color + } + } + ... on Indicator { + id + name + pattern_type + pattern_version + description + valid_from + valid_until + x_opencti_score + x_opencti_main_observable_type + created + objectMarking { + id + definition + x_opencti_order + x_opencti_color + } + objectLabel { + id + value + color + } + } + ... on StixRelationship { + id + entity_type + created + created_at + parent_types + from { + ... on StixDomainObject { + id + entity_type + parent_types + created_at + updated_at + objectLabel { + id + value + color + } + } + ... on AttackPattern { + name + description + x_mitre_id + killChainPhases { + id + phase_name + x_opencti_order + } + objectMarking { + id + definition + x_opencti_order + x_opencti_color + } + objectLabel { + id + value + color + } + } + ... on Campaign { + name + description + } + ... on CourseOfAction { + name + description + } + ... on Individual { + name + description + } + ... on Organization { + name + description + } + ... on Sector { + name + description + } + ... on System { + name + description + } + ... on Indicator { + name + description + } + ... on Infrastructure { + name + description + } + ... on IntrusionSet { + name + description + } + ... on Position { + name + description + } + ... on City { + name + description + } + ... on Country { + name + description + } + ... on Region { + name + description + } + ... on Malware { + name + description + } + ... on ThreatActor { + name + description + } + ... on Tool { + name + description + } + ... on Vulnerability { + name + description + } + ... on Incident { + name + description + } + ... on StixCyberObservable { + id + entity_type + parent_types + observable_value + objectMarking { + id + definition + x_opencti_order + x_opencti_color + } + objectLabel { + id + value + color + } + } + ... on Indicator { + id + name + pattern_type + pattern_version + description + valid_from + valid_until + x_opencti_score + x_opencti_main_observable_type + created + objectMarking { + id + definition + x_opencti_order + x_opencti_color + } + objectLabel { + id + value + color + } + } + ... on StixRelationship { + id + entity_type + parent_types + created + created_at + } + } + to { + ... on StixDomainObject { + id + entity_type + parent_types + created_at + updated_at + objectLabel { + id + value + color + } + } + ... on AttackPattern { + name + description + x_mitre_id + killChainPhases { + id + phase_name + x_opencti_order + } + objectMarking { + id + definition + x_opencti_order + x_opencti_color + } + objectLabel { + id + value + color + } + } + ... on Campaign { + name + description + } + ... on CourseOfAction { + name + description + } + ... on Individual { + name + description + } + ... on Organization { + name + description + } + ... on Sector { + name + description + } + ... on System { + name + description + } + ... on Indicator { + name + description + } + ... on Infrastructure { + name + description + } + ... on IntrusionSet { + name + description + } + ... on Position { + name + description + } + ... on City { + name + description + } + ... on Country { + name + description + } + ... on Region { + name + description + } + ... on Malware { + name + description + } + ... on ThreatActor { + name + description + } + ... on Tool { + name + description + } + ... on Vulnerability { + name + description + } + ... on Incident { + name + description + } + ... on StixCyberObservable { + id + entity_type + parent_types + observable_value + objectMarking { + id + definition + x_opencti_order + x_opencti_color + } + objectLabel { + id + value + color + } + } + ... on Indicator { + id + name + pattern_type + pattern_version + description + valid_from + valid_until + x_opencti_score + x_opencti_main_observable_type + created + objectMarking { + id + definition_type + definition + x_opencti_order + x_opencti_color + } + objectLabel { + id + value + color + } + } + ... on StixRelationship { + id + entity_type + created + created_at + parent_types + } + } + } + } + } + } + } + } +`; + +interface PublicStixRelationshipsTimelineComponentProps { + dataSelection: PublicManifestWidget['dataSelection'] + queryRef: PreloadedQuery +} + +const PublicStixRelationshipsTimelineComponent = ({ + dataSelection, + queryRef, +}: PublicStixRelationshipsTimelineComponentProps) => { + const { publicStixRelationships } = usePreloadedQuery( + publicStixRelationshipsTimelineQuery, + queryRef, + ); + + if ( + publicStixRelationships + && publicStixRelationships?.edges + && publicStixRelationships.edges.length > 0 + ) { + const stixRelationshipsEdges = publicStixRelationships.edges; + const data = stixRelationshipsEdges.flatMap((stixRelationshipEdge) => { + const stixRelationship = stixRelationshipEdge?.node; + if (!stixRelationship) return []; + const remoteNode = stixRelationship.from + && dataSelection[0].isTo + ? stixRelationship.to + : stixRelationship.from; + if (!remoteNode) return []; + + const restricted = stixRelationship.from === null + || stixRelationship.to === null; + const link = restricted + ? undefined + : `${resolveLink(remoteNode.entity_type)}/${ + remoteNode.id + }/knowledge/relations/${stixRelationship.id}`; + return { + value: { + ...remoteNode, + created: stixRelationship.created, + }, + link, + }; + }); + return ; + } + return ; +}; + +const PublicStixRelationshipsTimeline = ({ + uriKey, + widget, + startDate, + endDate, + title, +}: PublicWidgetContainerProps) => { + const { t_i18n } = useFormatter(); + const { id, parameters, dataSelection } = widget; + const queryRef = useQueryLoading( + publicStixRelationshipsTimelineQuery, + { + uriKey, + widgetId: id, + startDate, + endDate, + }, + ); + + return ( + + {queryRef ? ( + }> + + + ) : ( + + )} + + ); +}; + +export default PublicStixRelationshipsTimeline; diff --git a/opencti-platform/opencti-front/src/public/components/dashboard/stix_relationships/PublicStixRelationshipsTreeMap.tsx b/opencti-platform/opencti-front/src/public/components/dashboard/stix_relationships/PublicStixRelationshipsTreeMap.tsx new file mode 100644 index 000000000000..95e653607e11 --- /dev/null +++ b/opencti-platform/opencti-front/src/public/components/dashboard/stix_relationships/PublicStixRelationshipsTreeMap.tsx @@ -0,0 +1,243 @@ +import { graphql, PreloadedQuery, usePreloadedQuery } from 'react-relay'; +import React from 'react'; +import type { PublicManifestWidget } from '../PublicManifest'; +import WidgetTree from '../../../../components/dashboard/WidgetTree'; +import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; +import type { PublicWidgetContainerProps } from '../PublicWidgetContainerProps'; +import { useFormatter } from '../../../../components/i18n'; +import useQueryLoading from '../../../../utils/hooks/useQueryLoading'; +import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; +import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; +import { PublicStixRelationshipsTreeMapQuery } from './__generated__/PublicStixRelationshipsTreeMapQuery.graphql'; + +const publicStixRelationshipsTreeMapsQuery = graphql` + query PublicStixRelationshipsTreeMapQuery( + $startDate: DateTime + $endDate: DateTime + $uriKey: String! + $widgetId : String! + ) { + publicStixRelationshipsDistribution( + startDate: $startDate + endDate: $endDate + uriKey: $uriKey + widgetId : $widgetId + ) { + label + value + entity { + ... on BasicObject { + entity_type + } + ... on BasicRelationship { + entity_type + } + ... on AttackPattern { + name + description + } + ... on Campaign { + name + description + } + ... on CourseOfAction { + name + description + } + ... on Individual { + name + description + } + ... on Organization { + name + description + } + ... on Sector { + name + description + } + ... on System { + name + description + } + ... on Indicator { + name + description + } + ... on Infrastructure { + name + description + } + ... on IntrusionSet { + name + description + } + ... on Position { + name + description + } + ... on City { + name + description + } + ... on AdministrativeArea { + name + description + } + ... on Country { + name + description + } + ... on Region { + name + description + } + ... on Malware { + name + description + } + ... on ThreatActor { + name + description + } + ... on Tool { + name + description + } + ... on Vulnerability { + name + description + } + ... on Incident { + name + description + } + ... on Event { + name + description + } + ... on Channel { + name + description + } + ... on Narrative { + name + description + } + ... on Language { + name + } + ... on DataComponent { + name + } + ... on DataSource { + name + } + ... on Case { + name + } + ... on StixCyberObservable { + observable_value + } + ... on MarkingDefinition { + definition_type + definition + } + ... on KillChainPhase { + kill_chain_name + phase_name + } + ... on Creator { + name + } + ... on Report { + name + } + ... on Grouping { + name + } + ... on Note { + attribute_abstract + content + } + ... on Opinion { + opinion + } + } + } + } +`; + +interface PublicStixRelationshipsTreeMapComponentProps { + parameters: PublicManifestWidget['parameters'] + dataSelection: PublicManifestWidget['dataSelection'] + queryRef: PreloadedQuery +} + +const PublicStixRelationshipsTreeMapComponent = ({ + parameters, + dataSelection, + queryRef, +}: PublicStixRelationshipsTreeMapComponentProps) => { + const { publicStixRelationshipsDistribution } = usePreloadedQuery( + publicStixRelationshipsTreeMapsQuery, + queryRef, + ); + + if ( + publicStixRelationshipsDistribution + && publicStixRelationshipsDistribution.length > 0 + ) { + return ( + + ); + } + return ; +}; + +const PublicStixRelationshipsTreeMap = ({ + uriKey, + widget, + startDate, + endDate, + title, +}: PublicWidgetContainerProps) => { + const { t_i18n } = useFormatter(); + const { id, parameters, dataSelection } = widget; + const queryRef = useQueryLoading( + publicStixRelationshipsTreeMapsQuery, + { + uriKey, + widgetId: id, + startDate, + endDate, + }, + ); + + return ( + + {queryRef ? ( + }> + + + ) : ( + + )} + + ); +}; + +export default PublicStixRelationshipsTreeMap; diff --git a/opencti-platform/opencti-front/src/public/components/dashboard/usePublicDashboardWidgets.tsx b/opencti-platform/opencti-front/src/public/components/dashboard/usePublicDashboardWidgets.tsx index f3b631f81a9c..8dc4a35dcdcb 100644 --- a/opencti-platform/opencti-front/src/public/components/dashboard/usePublicDashboardWidgets.tsx +++ b/opencti-platform/opencti-front/src/public/components/dashboard/usePublicDashboardWidgets.tsx @@ -1,7 +1,34 @@ import React from 'react'; +import WidgetText from '@components/workspaces/dashboards/WidgetText'; import type { PublicManifestConfig, PublicManifestWidget } from './PublicManifest'; import { computerRelativeDate, dayStartDate, formatDate } from '../../../utils/Time'; -import PublicStixCoreObjectsNumber from './PublicStixCoreObjectsNumber'; +import PublicStixCoreObjectsNumber from './stix_core_objects/PublicStixCoreObjectsNumber'; +import PublicStixCoreObjectsList from './stix_core_objects/PublicStixCoreObjectsList'; +import PublicStixCoreObjectsDistributionList from './stix_core_objects/PublicStixCoreObjectsDistributionList'; +import PublicStixCoreObjectsMultiVerticalBars from './stix_core_objects/PublicStixCoreObjectsMultiVerticalBars'; +import PublicStixCoreObjectsMultiLineChart from './stix_core_objects/PublicStixCoreObjectsMultiLineChart'; +import PublicStixCoreObjectsMultiAreaChart from './stix_core_objects/PublicStixCoreObjectsMultiAreaChart'; +import PublicStixCoreObjectsTimeline from './stix_core_objects/PublicStixCoreObjectsTimeline'; +import PublicStixCoreObjectsDonut from './stix_core_objects/PublicStixCoreObjectsDonut'; +import PublicStixCoreObjectsRadar from './stix_core_objects/PublicStixCoreObjectsRadar'; +import PublicStixCoreObjectsMultiHeatMap from './stix_core_objects/PublicStixCoreObjectsMultiHeatMap'; +import PublicStixCoreObjectsTreeMap from './stix_core_objects/PublicStixCoreObjectsTreeMap'; +import PublicStixCoreRelationshipsNumber from './stix_relationships/PublicStixRelationshipsNumber'; +import PublicStixRelationshipsList from './stix_relationships/PublicStixRelationshipsList'; +import PublicStixRelationshipsDistributionList from './stix_relationships/PublicStixRelationshipsDistributionList'; +import PublicStixRelationshipsMultiVerticalBars from './stix_relationships/PublicStixRelationshipsMultiVerticalBars'; +import PublicStixRelationshipsMultiLineChart from './stix_relationships/PublicStixRelationshipsMultiLineChart'; +import PublicStixRelationshipsMultiAreaChart from './stix_relationships/PublicStixRelationshipsMultiAreaChart'; +import PublicStixRelationshipsTimeline from './stix_relationships/PublicStixRelationshipsTimeline'; +import PublicStixRelationshipsDonut from './stix_relationships/PublicStixRelationshipsDonut'; +import PublicStixRelationshipsRadar from './stix_relationships/PublicStixRelationshipsRadar'; +import PublicStixRelationshipsMultiHeatMap from './stix_relationships/PublicStixRelationshipsMultiHeatMap'; +import PublicStixRelationshipsTreeMap from './stix_relationships/PublicStixRelationshipsTreeMap'; +import PublicStixRelationshipsMap from './stix_relationships/PublicStixRelationshipsMap'; +import PublicStixCoreObjectsHorizontalBars from './stix_core_objects/PublicStixCoreObjectsHorizontalBars'; +import PublicStixRelationshipsHorizontalBars from './stix_relationships/PublicStixRelationshipsHorizontalBars'; +import PublicStixDomainObjectBookmarksList from './stix_domain_objects/PublicStixDomainObjectBookmarksList'; +import PublicStixRelationshipsMultiHorizontalBars from './stix_relationships/PublicStixRelationshipsMultiHorizontalBars'; const usePublicDashboardWidgets = (uriKey: string, config?: PublicManifestConfig) => { const startDate = config?.relativeDate ? computerRelativeDate(config.relativeDate) : config?.startDate; @@ -10,7 +37,12 @@ const usePublicDashboardWidgets = (uriKey: string, config?: PublicManifestConfig const entityWidget = (widget: PublicManifestWidget) => { switch (widget.type) { case 'bookmark': - return 'Not implemented yet'; + return ( + + ); case 'number': return ( ); case 'list': - return 'Not implemented yet'; + return ( + + ); case 'distribution-list': - return 'Not implemented yet'; + return ( + + ); case 'vertical-bar': - return 'Not implemented yet'; + return ( + + ); case 'line': - return 'Not implemented yet'; + return ( + + ); case 'area': - return 'Not implemented yet'; + return ( + + ); case 'timeline': - return 'Not implemented yet'; + return ( + + ); case 'donut': - return 'Not implemented yet'; + return ( + + ); case 'horizontal-bar': - return 'Not implemented yet'; + if (widget.dataSelection.length > 1) { + // TODO implement multi horizontal bars with breakdowns + return 'Not implemented yet'; + } + return ( + + ); case 'radar': - return 'Not implemented yet'; + return ( + + ); case 'heatmap': - return 'Not implemented yet'; + return ( + + ); case 'tree': - return 'Not implemented yet'; + return ( + + ); default: return 'Not implemented yet'; } @@ -50,31 +163,132 @@ const usePublicDashboardWidgets = (uriKey: string, config?: PublicManifestConfig const relationshipWidget = (widget: PublicManifestWidget) => { switch (widget.type) { case 'number': - return 'Not implemented yet'; + return ( + + ); case 'list': - return 'Not implemented yet'; + return ( + + ); case 'distribution-list': - return 'Not implemented yet'; + return ( + + ); case 'vertical-bar': - return 'Not implemented yet'; + return ( + + ); case 'line': - return 'Not implemented yet'; + return ( + + ); case 'area': - return 'Not implemented yet'; + return ( + + ); case 'timeline': - return 'Not implemented yet'; + return ( + + ); case 'donut': - return 'Not implemented yet'; + return ( + + ); case 'horizontal-bar': - return 'Not implemented yet'; + if (widget.dataSelection.length > 1) { + return ( + + ); + } + return ( + + ); case 'radar': - return 'Not implemented yet'; + return ( + + ); case 'heatmap': - return 'Not implemented yet'; + return ( + + ); case 'tree': - return 'Not implemented yet'; + return ( + + ); case 'map': - return 'Not implemented yet'; + return ( + + ); default: return 'Not implemented yet'; } @@ -83,7 +297,12 @@ const usePublicDashboardWidgets = (uriKey: string, config?: PublicManifestConfig const rawWidget = (widget: PublicManifestWidget) => { switch (widget.type) { case 'text': - return 'Not implemented yet'; + return ( + + ); default: return 'Not implemented yet'; } diff --git a/opencti-platform/opencti-front/src/schema/relay.schema.graphql b/opencti-platform/opencti-front/src/schema/relay.schema.graphql index 6285a4a19638..e58892465768 100644 --- a/opencti-platform/opencti-front/src/schema/relay.schema.graphql +++ b/opencti-platform/opencti-front/src/schema/relay.schema.graphql @@ -7392,8 +7392,15 @@ type Query { publicDashboard(id: String!): PublicDashboard publicDashboards(first: Int, after: ID, orderBy: PublicDashboardsOrdering, orderMode: OrderingMode, filters: FilterGroup, search: String): PublicDashboardConnection publicDashboardByUriKey(uri_key: String!): PublicDashboard - publicStixCoreObjectsNumber(startDate: DateTime, endDate: DateTime, uriKey: String!, widgetId: String!): Number - publicStixCoreObjectsMultiTimeSeries(startDate: DateTime!, endDate: DateTime, uriKey: String!, widgetId: String!): [MultiTimeSeries] + publicStixCoreObjectsNumber(uriKey: String!, widgetId: String!, startDate: DateTime, endDate: DateTime): Number + publicStixRelationshipsNumber(uriKey: String!, widgetId: String!, startDate: DateTime, endDate: DateTime): Number + publicStixCoreObjectsMultiTimeSeries(uriKey: String!, widgetId: String!, startDate: DateTime, endDate: DateTime): [MultiTimeSeries] + publicStixRelationshipsMultiTimeSeries(uriKey: String!, widgetId: String!, startDate: DateTime, endDate: DateTime): [MultiTimeSeries] + publicStixCoreObjectsDistribution(uriKey: String!, widgetId: String!, startDate: DateTime, endDate: DateTime): [PublicDistribution] + publicStixRelationshipsDistribution(uriKey: String!, widgetId: String!, startDate: DateTime, endDate: DateTime): [PublicDistribution] + publicBookmarks(uriKey: String!, widgetId: String!): StixDomainObjectConnection + publicStixCoreObjects(uriKey: String!, widgetId: String!, startDate: DateTime, endDate: DateTime): StixCoreObjectConnection + publicStixRelationships(uriKey: String!, widgetId: String!, startDate: DateTime, endDate: DateTime): StixRelationshipConnection } type Subscription { @@ -11199,6 +11206,13 @@ type PublicDashboard implements InternalObject & BasicObject { authorized_members: [MemberAccess!] } +type PublicDistribution { + label: String! + entity: StixObjectOrStixRelationshipOrCreator + value: Int + breakdownDistribution: [Distribution] +} + enum PublicDashboardsOrdering { name created_at diff --git a/opencti-platform/opencti-front/src/utils/Charts.js b/opencti-platform/opencti-front/src/utils/Charts.js index 4c3a1f5c8bf7..cb7a55fab0b0 100644 --- a/opencti-platform/opencti-front/src/utils/Charts.js +++ b/opencti-platform/opencti-front/src/utils/Charts.js @@ -38,6 +38,15 @@ const toolbarOptions = { }, }; +/** + * @param {Theme} theme + * @param {boolean} isTimeSeries + * @param {function} xFormatter + * @param {function} yFormatter + * @param {number | 'dataPoints'} tickAmount + * @param {boolean} dataLabels + * @param {boolean} legend + */ export const lineChartOptions = ( theme, isTimeSeries = false, @@ -128,7 +137,7 @@ export const lineChartOptions = ( * @param {boolean} isTimeSeries * @param {function} xFormatter * @param {function} yFormatter - * @param {number} tickAmount + * @param {number | 'dataPoints'} tickAmount * @param {boolean} isStacked * @param {boolean} legend */ @@ -231,10 +240,20 @@ export const areaChartOptions = ( }, }); +/** + * @param {Theme} theme + * @param {function} xFormatter + * @param {function} yFormatter + * @param {boolean} distributed + * @param {boolean} isTimeSeries + * @param {boolean} isStacked + * @param {boolean} legend + * @param {number | 'dataPoints'} tickAmount + */ export const verticalBarsChartOptions = ( theme, - xFormatter = null, - yFormatter = null, + xFormatter, + yFormatter, distributed = false, isTimeSeries = false, isStacked = false, @@ -323,6 +342,19 @@ export const verticalBarsChartOptions = ( }, }); +/** + * @param {Theme} theme + * @param {boolean} adjustTicks + * @param {function} xFormatter + * @param {function} yFormatter + * @param {boolean} distributed + * @param {function} navigate + * @param {object[]} redirectionUtils + * @param {boolean} stacked + * @param {boolean} total + * @param {string[]} categories + * @param {boolean} legend + */ export const horizontalBarsChartOptions = ( theme, adjustTicks = false, @@ -495,6 +527,13 @@ export const horizontalBarsChartOptions = ( }, }); +/** + * @param {Theme} theme + * @param {string[]} labels + * @param {string[]} chartColors + * @param {boolean} legend + * @param {boolean} offset + */ export const radarChartOptions = ( theme, labels, @@ -657,6 +696,13 @@ export const polarAreaChartOptions = ( }, }); +/** + * @param {Theme} theme + * @param {string[]} labels + * @param {string} legendPosition + * @param {boolean} reversed + * @param {string[]} chartColors + */ export const donutChartOptions = ( theme, labels, @@ -747,6 +793,12 @@ export const donutChartOptions = ( }; }; +/** + * + * @param {Theme} theme + * @param {string} legendPosition + * @param {boolean} distributed + */ export const treeMapOptions = ( theme, legendPosition = 'bottom', @@ -813,6 +865,15 @@ export const treeMapOptions = ( }; }; +/** + * @param {Theme} theme + * @param {boolean} isTimeSeries + * @param {function} xFormatter + * @param {function} yFormatter + * @param {number | 'dataPoints'} tickAmount + * @param {boolean} isStacked + * @param {object[]} ranges + */ export const heatMapOptions = ( theme, isTimeSeries = false, diff --git a/opencti-platform/opencti-graphql/src/database/middleware-loader.ts b/opencti-platform/opencti-graphql/src/database/middleware-loader.ts index ee519857e22c..96a9d963789c 100644 --- a/opencti-platform/opencti-graphql/src/database/middleware-loader.ts +++ b/opencti-platform/opencti-graphql/src/database/middleware-loader.ts @@ -22,7 +22,8 @@ import type { BasicStoreRelation, StoreCommonConnection, StoreEntityConnection, - StoreProxyRelation + StoreProxyRelation, + StoreRelationConnection } from '../types/store'; import { FunctionalError, UnsupportedError } from '../config/errors'; import { type Filter, type FilterGroup, FilterMode, FilterOperator, type InputMaybe, OrderingMode } from '../generated/graphql'; @@ -277,6 +278,7 @@ export const buildRelationsFilter = (relationTypes: filters: computedFilters, }; }; + export const listRelations = async (context: AuthContext, user: AuthUser, type: string | Array, args: RelationOptions = {}): Promise> => { const { indices = READ_RELATIONSHIPS_INDICES } = args; @@ -284,6 +286,16 @@ export const listRelations = async (context: AuthC return elPaginate(context, user, indices, paginateArgs); }; +export const listRelationsPaginated = async (context: AuthContext, user: AuthUser, type: string | Array, + args: RelationOptions = {}): Promise> => { + const { indices = READ_RELATIONSHIPS_INDICES, connectionFormat } = args; + if (connectionFormat === false) { + throw UnsupportedError('List connection require connectionFormat option to true'); + } + const paginateArgs = buildRelationsFilter(type, args); + return elPaginate(context, user, indices, paginateArgs); +}; + export const listAllRelations = async (context: AuthContext, user: AuthUser, type: string | Array, args: RelationOptions = {}): Promise> => { const { indices = READ_RELATIONSHIPS_INDICES } = args; diff --git a/opencti-platform/opencti-graphql/src/domain/stixCoreObject.js b/opencti-platform/opencti-graphql/src/domain/stixCoreObject.js index 448e8f604603..ee52ba6c50c2 100644 --- a/opencti-platform/opencti-graphql/src/domain/stixCoreObject.js +++ b/opencti-platform/opencti-graphql/src/domain/stixCoreObject.js @@ -1,6 +1,6 @@ import * as R from 'ramda'; import { createEntity, createRelationRaw, deleteElementById, distributionEntities, storeLoadByIdWithRefs, timeSeriesEntities } from '../database/middleware'; -import { internalFindByIds, internalLoadById, listEntities, listEntitiesThroughRelationsPaginated, storeLoadById, storeLoadByIds } from '../database/middleware-loader'; +import { internalFindByIds, internalLoadById, listEntitiesPaginated, listEntitiesThroughRelationsPaginated, storeLoadById, storeLoadByIds } from '../database/middleware-loader'; import { findAll as relationFindAll } from './stixCoreRelationship'; import { delEditContext, lockResource, notify, setEditContext, storeUpdateEvent } from '../database/redis'; import { BUS_TOPICS } from '../config/conf'; @@ -71,7 +71,7 @@ export const findAll = async (context, user, args) => { context_data: contextData, }); } - return listEntities(context, user, types, args); + return listEntitiesPaginated(context, user, types, args); }; export const findById = async (context, user, stixCoreObjectId) => { diff --git a/opencti-platform/opencti-graphql/src/domain/stixRelationship.js b/opencti-platform/opencti-graphql/src/domain/stixRelationship.js index 4a1031910a12..dd6eca20f605 100644 --- a/opencti-platform/opencti-graphql/src/domain/stixRelationship.js +++ b/opencti-platform/opencti-graphql/src/domain/stixRelationship.js @@ -1,7 +1,7 @@ import * as R from 'ramda'; import { deleteElementById, distributionRelations, timeSeriesRelations } from '../database/middleware'; import { ABSTRACT_STIX_CORE_RELATIONSHIP, ABSTRACT_STIX_OBJECT, ABSTRACT_STIX_RELATIONSHIP } from '../schema/general'; -import { buildRelationsFilter, listEntities, listRelations, storeLoadById } from '../database/middleware-loader'; +import { buildRelationsFilter, listEntities, listRelationsPaginated, storeLoadById } from '../database/middleware-loader'; import { fillTimeSeries, isEmptyField, READ_INDEX_INFERRED_RELATIONSHIPS, READ_RELATIONSHIPS_INDICES } from '../database/utils'; import { elCount, MAX_RUNTIME_RESOLUTION_SIZE } from '../database/engine'; import { STIX_SPEC_VERSION, stixCoreRelationshipsMapping } from '../database/stix'; @@ -50,7 +50,7 @@ export const findAll = async (context, user, args) => { return { edges: [] }; } const type = isEmptyField(dynamicArgs.relationship_type) ? ABSTRACT_STIX_RELATIONSHIP : dynamicArgs.relationship_type; - return listRelations(context, user, type, R.dissoc('relationship_type', dynamicArgs)); + return listRelationsPaginated(context, user, type, R.dissoc('relationship_type', dynamicArgs)); }; export const findById = (context, user, stixRelationshipId) => { diff --git a/opencti-platform/opencti-graphql/src/generated/graphql.ts b/opencti-platform/opencti-graphql/src/generated/graphql.ts index 41fcb7700c6e..d87d6de8f79a 100644 --- a/opencti-platform/opencti-graphql/src/generated/graphql.ts +++ b/opencti-platform/opencti-graphql/src/generated/graphql.ts @@ -17403,6 +17403,14 @@ export enum PublicDashboardsOrdering { UpdatedAt = 'updated_at' } +export type PublicDistribution = { + __typename?: 'PublicDistribution'; + breakdownDistribution?: Maybe>>; + entity?: Maybe; + label: Scalars['String']['output']; + value?: Maybe; +}; + export type Query = { __typename?: 'Query'; about?: Maybe; @@ -17577,11 +17585,18 @@ export type Query = { playbooks?: Maybe; position?: Maybe; positions?: Maybe; + publicBookmarks?: Maybe; publicDashboard?: Maybe; publicDashboardByUriKey?: Maybe; publicDashboards?: Maybe; + publicStixCoreObjects?: Maybe; + publicStixCoreObjectsDistribution?: Maybe>>; publicStixCoreObjectsMultiTimeSeries?: Maybe>>; publicStixCoreObjectsNumber?: Maybe; + publicStixRelationships?: Maybe; + publicStixRelationshipsDistribution?: Maybe>>; + publicStixRelationshipsMultiTimeSeries?: Maybe>>; + publicStixRelationshipsNumber?: Maybe; rabbitMQMetrics?: Maybe; region?: Maybe; regions?: Maybe; @@ -18910,6 +18925,12 @@ export type QueryPositionsArgs = { }; +export type QueryPublicBookmarksArgs = { + uriKey: Scalars['String']['input']; + widgetId: Scalars['String']['input']; +}; + + export type QueryPublicDashboardArgs = { id: Scalars['String']['input']; }; @@ -18930,9 +18951,25 @@ export type QueryPublicDashboardsArgs = { }; +export type QueryPublicStixCoreObjectsArgs = { + endDate?: InputMaybe; + startDate?: InputMaybe; + uriKey: Scalars['String']['input']; + widgetId: Scalars['String']['input']; +}; + + +export type QueryPublicStixCoreObjectsDistributionArgs = { + endDate?: InputMaybe; + startDate?: InputMaybe; + uriKey: Scalars['String']['input']; + widgetId: Scalars['String']['input']; +}; + + export type QueryPublicStixCoreObjectsMultiTimeSeriesArgs = { endDate?: InputMaybe; - startDate: Scalars['DateTime']['input']; + startDate?: InputMaybe; uriKey: Scalars['String']['input']; widgetId: Scalars['String']['input']; }; @@ -18946,6 +18983,38 @@ export type QueryPublicStixCoreObjectsNumberArgs = { }; +export type QueryPublicStixRelationshipsArgs = { + endDate?: InputMaybe; + startDate?: InputMaybe; + uriKey: Scalars['String']['input']; + widgetId: Scalars['String']['input']; +}; + + +export type QueryPublicStixRelationshipsDistributionArgs = { + endDate?: InputMaybe; + startDate?: InputMaybe; + uriKey: Scalars['String']['input']; + widgetId: Scalars['String']['input']; +}; + + +export type QueryPublicStixRelationshipsMultiTimeSeriesArgs = { + endDate?: InputMaybe; + startDate?: InputMaybe; + uriKey: Scalars['String']['input']; + widgetId: Scalars['String']['input']; +}; + + +export type QueryPublicStixRelationshipsNumberArgs = { + endDate?: InputMaybe; + startDate?: InputMaybe; + uriKey: Scalars['String']['input']; + widgetId: Scalars['String']['input']; +}; + + export type QueryRabbitMqMetricsArgs = { prefix?: InputMaybe; }; @@ -28301,6 +28370,7 @@ export type ResolversTypes = ResolversObject<{ PublicDashboardConnection: ResolverTypeWrapper & { edges: Array }>; PublicDashboardEdge: ResolverTypeWrapper & { node: ResolversTypes['PublicDashboard'] }>; PublicDashboardsOrdering: PublicDashboardsOrdering; + PublicDistribution: ResolverTypeWrapper & { entity?: Maybe }>; Query: ResolverTypeWrapper<{}>; QueryTask: ResolverTypeWrapper; QueryTaskAddInput: QueryTaskAddInput; @@ -29010,6 +29080,7 @@ export type ResolversParentTypes = ResolversObject<{ PublicDashboardAddInput: PublicDashboardAddInput; PublicDashboardConnection: Omit & { edges: Array }; PublicDashboardEdge: Omit & { node: ResolversParentTypes['PublicDashboard'] }; + PublicDistribution: Omit & { entity?: Maybe }; Query: {}; QueryTask: QueryTask; QueryTaskAddInput: QueryTaskAddInput; @@ -34723,6 +34794,14 @@ export type PublicDashboardEdgeResolvers; }>; +export type PublicDistributionResolvers = ResolversObject<{ + breakdownDistribution?: Resolver>>, ParentType, ContextType>; + entity?: Resolver, ParentType, ContextType>; + label?: Resolver; + value?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}>; + export type QueryResolvers = ResolversObject<{ about?: Resolver, ParentType, ContextType>; administrativeArea?: Resolver, ParentType, ContextType, RequireFields>; @@ -34896,11 +34975,18 @@ export type QueryResolvers, ParentType, ContextType, Partial>; position?: Resolver, ParentType, ContextType, RequireFields>; positions?: Resolver, ParentType, ContextType, Partial>; + publicBookmarks?: Resolver, ParentType, ContextType, RequireFields>; publicDashboard?: Resolver, ParentType, ContextType, RequireFields>; publicDashboardByUriKey?: Resolver, ParentType, ContextType, RequireFields>; publicDashboards?: Resolver, ParentType, ContextType, Partial>; - publicStixCoreObjectsMultiTimeSeries?: Resolver>>, ParentType, ContextType, RequireFields>; + publicStixCoreObjects?: Resolver, ParentType, ContextType, RequireFields>; + publicStixCoreObjectsDistribution?: Resolver>>, ParentType, ContextType, RequireFields>; + publicStixCoreObjectsMultiTimeSeries?: Resolver>>, ParentType, ContextType, RequireFields>; publicStixCoreObjectsNumber?: Resolver, ParentType, ContextType, RequireFields>; + publicStixRelationships?: Resolver, ParentType, ContextType, RequireFields>; + publicStixRelationshipsDistribution?: Resolver>>, ParentType, ContextType, RequireFields>; + publicStixRelationshipsMultiTimeSeries?: Resolver>>, ParentType, ContextType, RequireFields>; + publicStixRelationshipsNumber?: Resolver, ParentType, ContextType, RequireFields>; rabbitMQMetrics?: Resolver, ParentType, ContextType, Partial>; region?: Resolver, ParentType, ContextType, RequireFields>; regions?: Resolver, ParentType, ContextType, Partial>; @@ -38019,6 +38105,7 @@ export type Resolvers = ResolversObject<{ PublicDashboard?: PublicDashboardResolvers; PublicDashboardConnection?: PublicDashboardConnectionResolvers; PublicDashboardEdge?: PublicDashboardEdgeResolvers; + PublicDistribution?: PublicDistributionResolvers; Query?: QueryResolvers; QueryTask?: QueryTaskResolvers; QueueArguments?: QueueArgumentsResolvers; diff --git a/opencti-platform/opencti-graphql/src/modules/publicDashboard/publicDashboard-domain.ts b/opencti-platform/opencti-graphql/src/modules/publicDashboard/publicDashboard-domain.ts index 09d644bdb0a1..b6e36793e7cb 100644 --- a/opencti-platform/opencti-graphql/src/modules/publicDashboard/publicDashboard-domain.ts +++ b/opencti-platform/opencti-graphql/src/modules/publicDashboard/publicDashboard-domain.ts @@ -1,19 +1,29 @@ import { v4 as uuidv4 } from 'uuid'; +import { Promise as BluePromise } from 'bluebird'; import type { AuthContext, AuthUser } from '../../types/user'; import { internalLoadById, listEntitiesPaginated, storeLoadById } from '../../database/middleware-loader'; -import { ENTITY_TYPE_PUBLIC_DASHBOARD, type BasicStoreEntityPublicDashboard, type PublicDashboardCached } from './publicDashboard-types'; +import { type BasicStoreEntityPublicDashboard, ENTITY_TYPE_PUBLIC_DASHBOARD, type PublicDashboardCached } from './publicDashboard-types'; import { createEntity, deleteElementById, loadEntity, updateAttribute } from '../../database/middleware'; import { type BasicStoreEntityWorkspace } from '../workspace/workspace-types'; import { fromBase64, isNotEmptyField, toBase64 } from '../../database/utils'; import { notify } from '../../database/redis'; import { BUS_TOPICS } from '../../config/conf'; -import type { - EditInput, - FilterGroup, - PublicDashboardAddInput, - QueryPublicDashboardsArgs, - QueryPublicStixCoreObjectsNumberArgs, - QueryPublicStixCoreObjectsMultiTimeSeriesArgs +import { + type EditInput, + type FilterGroup, + FilterMode, + FilterOperator, + type PublicDashboardAddInput, + type QueryPublicBookmarksArgs, + type QueryPublicDashboardsArgs, + type QueryPublicStixCoreObjectsArgs, + type QueryPublicStixCoreObjectsDistributionArgs, + type QueryPublicStixCoreObjectsMultiTimeSeriesArgs, + type QueryPublicStixCoreObjectsNumberArgs, + type QueryPublicStixRelationshipsArgs, + type QueryPublicStixRelationshipsDistributionArgs, + type QueryPublicStixRelationshipsMultiTimeSeriesArgs, + type QueryPublicStixRelationshipsNumberArgs } from '../../generated/graphql'; import { FunctionalError, UnsupportedError } from '../../config/errors'; import { SYSTEM_USER } from '../../utils/access'; @@ -21,10 +31,21 @@ import { publishUserAction } from '../../listener/UserActionListener'; import { initializeAuthorizedMembers } from '../workspace/workspace-domain'; import { ENTITY_TYPE_MARKING_DEFINITION } from '../../schema/stixMetaObject'; import { getEntitiesMapFromCache } from '../../database/cache'; -import type { NumberResult, StoreMarkingDefinition } from '../../types/store'; +import type { BasicStoreRelation, NumberResult, StoreMarkingDefinition, StoreRelationConnection } from '../../types/store'; import { getWidgetArguments } from './publicDashboard-utils'; -import { stixCoreObjectsMultiTimeSeries, stixCoreObjectsNumber } from '../../domain/stixCoreObject'; +import { + findAll as stixCoreObjects, + stixCoreObjectsDistribution, + stixCoreObjectsDistributionByEntity, + stixCoreObjectsMultiTimeSeries, + stixCoreObjectsNumber +} from '../../domain/stixCoreObject'; import { ABSTRACT_STIX_CORE_OBJECT } from '../../schema/general'; +import { findAll as stixRelationships, stixRelationshipsDistribution, stixRelationshipsMultiTimeSeries, stixRelationshipsNumber } from '../../domain/stixRelationship'; +import { bookmarks } from '../../domain/user'; +import { daysAgo } from '../../utils/format'; +import { isStixCoreObject } from '../../schema/stixCoreObject'; +import { ES_MAX_CONCURRENCY } from '../../database/engine'; export const findById = ( context: AuthContext, @@ -77,9 +98,12 @@ export const getAllowedMarkings = async ( user: AuthUser, publicDashboard: BasicStoreEntityPublicDashboard | PublicDashboardCached, ): Promise => { + const publicDashboardMarkingsIds = publicDashboard.allowed_markings_ids; + if (!publicDashboardMarkingsIds) { + return []; + } // get markings from cache const markingsMap = await getEntitiesMapFromCache(context, user, ENTITY_TYPE_MARKING_DEFINITION); - const publicDashboardMarkingsIds = publicDashboard.allowed_markings_ids; return publicDashboardMarkingsIds.flatMap((id: string) => markingsMap.get(id) || []); }; @@ -102,10 +126,21 @@ export const addPublicDashboard = async ( } const parsedManifest = JSON.parse(fromBase64(dashboard.manifest) ?? '{}'); - // Removing the "dataSelection" key if (parsedManifest && isNotEmptyField(parsedManifest.widgets)) { Object.keys(parsedManifest.widgets).forEach((widgetId) => { - delete parsedManifest.widgets[widgetId].dataSelection; + parsedManifest.widgets[widgetId].dataSelection = parsedManifest + .widgets[widgetId] + .dataSelection.map((selection: any) => { + return { + ...(selection.label && { label: selection.label }), + ...(selection.attribute && { attribute: selection.attribute }), + ...(selection.date_attribute && { date_attribute: selection.date_attribute }), + ...(selection.number && { number: selection.number }), + ...(selection.centerLat && { centerLat: selection.centerLat }), + ...(selection.centerLng && { centerLng: selection.centerLng }), + ...(selection.zoom && { zoom: selection.zoom }), + }; + }); }); } @@ -207,16 +242,27 @@ export const publicDashboardDelete = async (context: AuthContext, user: AuthUser return notify(BUS_TOPICS[ENTITY_TYPE_PUBLIC_DASHBOARD].DELETE_TOPIC, deleted, user).then(() => id); }; -// Widgets Public API +// region Widgets Public API -// Heatmap +// heatmap & vertical-bar & line & area export const publicStixCoreObjectsMultiTimeSeries = async (context: AuthContext, args: QueryPublicStixCoreObjectsMultiTimeSeriesArgs) => { - const { user, config, timeSeriesParameters } = await getWidgetArguments(context, args.uriKey, args.widgetId, true); + const { user, parameters, dataSelection } = await getWidgetArguments(context, args.uriKey, args.widgetId); + const timeSeriesParameters = dataSelection.map((selection) => { + const filters = { + filterGroups: [selection.filters], + filters: [], + mode: 'and' + }; + return { + field: selection.date_attribute, + filters, + }; + }); const standardArgs = { startDate: args.startDate, endDate: args.endDate, - interval: config.interval, + interval: parameters?.interval ?? 'day', timeSeriesParameters }; @@ -224,20 +270,50 @@ export const publicStixCoreObjectsMultiTimeSeries = async (context: AuthContext, return stixCoreObjectsMultiTimeSeries(context, user, standardArgs); }; -// Number +export const publicStixRelationshipsMultiTimeSeries = async ( + context: AuthContext, + args: QueryPublicStixRelationshipsMultiTimeSeriesArgs, +) => { + const { user, parameters, dataSelection } = await getWidgetArguments(context, args.uriKey, args.widgetId); + const timeSeriesParameters = dataSelection.map((selection) => { + const filters = { + filterGroups: [selection.filters], + filters: [], + mode: 'and' + }; + return { + field: selection.date_attribute, + filters, + }; + }); + + const standardArgs = { + operation: 'count', + startDate: args.startDate, + endDate: args.endDate, + interval: parameters?.interval ?? 'day', + timeSeriesParameters + }; + + // Use standard API + return stixRelationshipsMultiTimeSeries(context, user, standardArgs); +}; + +// number export const publicStixCoreObjectsNumber = async ( context: AuthContext, args: QueryPublicStixCoreObjectsNumberArgs ): Promise => { - const { user, config, filters, dateAttribute } = await getWidgetArguments(context, args.uriKey, args.widgetId); + const { user, dataSelection } = await getWidgetArguments(context, args.uriKey, args.widgetId); + + const selection = dataSelection[0]; + const { filters } = selection; const parameters = { - dateAttribute, + dateAttribute: selection.date_attribute, startDate: args.startDate, - endDate: args.endDate, + endDate: daysAgo(1), filters, - onlyInferred: config.onlyInferred, - search: config.search, types: [ ABSTRACT_STIX_CORE_OBJECT, ] @@ -246,3 +322,261 @@ export const publicStixCoreObjectsNumber = async ( // Use standard API return stixCoreObjectsNumber(context, user, parameters) as unknown as Promise; }; + +export const publicStixRelationshipsNumber = async ( + context: AuthContext, + args: QueryPublicStixRelationshipsNumberArgs +): Promise => { + const { user, dataSelection } = await getWidgetArguments(context, args.uriKey, args.widgetId); + + const selection = dataSelection[0]; + const { filters } = selection; + + const parameters = { + startDate: args.startDate, + endDate: daysAgo(1), + filters, + dateAttribute: selection.date_attribute, + dynamicFrom: selection.dynamicFrom, + dynamicTo: selection.dynamicTo, + }; + + // Use standard API + return stixRelationshipsNumber(context, user, parameters) as unknown as Promise; +}; + +// donut & horizontal-bar & distribution-list & radar & tree +export const publicStixCoreObjectsDistribution = async ( + context: AuthContext, + args: QueryPublicStixCoreObjectsDistributionArgs +) => { + const { user, dataSelection } = await getWidgetArguments(context, args.uriKey, args.widgetId); + + const mainSelection = dataSelection[0]; + const breakdownSelection = dataSelection[1]; + const { filters: mainFilters } = mainSelection; + + const parameters = { + startDate: args.startDate, + endDate: args.endDate, + filters: mainFilters, + toTypes: mainSelection.toTypes, + field: mainSelection.attribute, + dateAttribute: mainSelection.date_attribute || 'created_at', + operation: 'count', + limit: mainSelection.number ?? 10, + types: [ + ABSTRACT_STIX_CORE_OBJECT, + ], + }; + + // Use standard API + const mainDistribution = await stixCoreObjectsDistribution(context, user, parameters); + if (!breakdownSelection) { + // Stop here if there is no breakdown to make with a second selection. + return mainDistribution; + } + + return BluePromise.map( + mainDistribution, + async (distributionItem) => { + if (!isStixCoreObject(distributionItem.entity.entity_type)) { + return distributionItem; + } + + const breakdownFilters: FilterGroup = { + filterGroups: breakdownSelection.filters ? [breakdownSelection.filters] : [], + filters: [{ + key: ['fromId'], + values: [distributionItem.entity.id], + mode: FilterMode.And, + operator: FilterOperator.Eq, + }], + mode: FilterMode.And + }; + + const breakdownParameters = { + startDate: args.startDate, + endDate: args.endDate, + filters: breakdownFilters, + toTypes: breakdownSelection.toTypes, + field: breakdownSelection.attribute, + dateAttribute: breakdownSelection.date_attribute || 'created_at', + operation: 'count', + limit: breakdownSelection.number ?? 10, + types: [ + ABSTRACT_STIX_CORE_OBJECT, + ], + }; + + return { + ...distributionItem, + breakdownDistribution: await stixCoreObjectsDistribution(context, user, breakdownParameters), + }; + }, + { concurrency: ES_MAX_CONCURRENCY } + ); +}; + +export const publicStixRelationshipsDistribution = async ( + context: AuthContext, + args: QueryPublicStixRelationshipsDistributionArgs +) => { + const { user, dataSelection } = await getWidgetArguments(context, args.uriKey, args.widgetId); + context.user = user; + + const mainSelection = dataSelection[0]; + const breakdownSelection = dataSelection[1]; + const { filters: mainFilters } = mainSelection; + + const parameters = { + operation: 'count', + field: mainSelection.attribute || 'entity_type', // TODO check for StixRelationshipsDonut + startDate: args.startDate, + endDate: args.endDate, + filters: mainFilters, + dynamicFrom: mainSelection.dynamicFrom, + dynamicTo: mainSelection.dynamicTo, + dateAttribute: mainSelection.date_attribute, + isTo: mainSelection.isTo, + limit: mainSelection.number ?? 10, + }; + + // Use standard API + const mainDistribution = await stixRelationshipsDistribution(context, user, parameters); + if (!breakdownSelection) { + // Stop here if there is no breakdown to make with a second selection. + return mainDistribution; + } + + return BluePromise.map( + mainDistribution, + async (distributionItem) => { + if (!isStixCoreObject(distributionItem.entity.entity_type)) { + return distributionItem; + } + + const breakdownFilters: FilterGroup = { + filterGroups: breakdownSelection.filters ? [breakdownSelection.filters] : [], + filters: breakdownSelection.perspective === 'entities' ? [] : [{ + key: ['fromId'], + values: [distributionItem.entity.id], + mode: FilterMode.And, + operator: FilterOperator.Eq, + }], + mode: FilterMode.And + }; + + const breakdownParameters = { + operation: 'count', + field: breakdownSelection.attribute || 'entity_type', + startDate: args.startDate, + endDate: args.endDate, + filters: breakdownFilters, + dynamicFrom: breakdownSelection.dynamicFrom, + dynamicTo: breakdownSelection.dynamicTo, + dateAttribute: breakdownSelection.date_attribute, + limit: breakdownSelection.number ?? 10, + }; + + let breakdownDistribution: any; + if (breakdownSelection.perspective === 'entities') { + breakdownDistribution = await stixCoreObjectsDistributionByEntity( + context, + user, + { + ...breakdownParameters, + types: ['Stix-Core-Object'], + objectId: distributionItem.entity.id + } + ); + } else { + breakdownDistribution = await stixRelationshipsDistribution( + context, + user, + { + ...breakdownParameters, + isTo: breakdownSelection.isTo, + fromOrToId: distributionItem.entity.id, + } + ); + } + + return { + ...distributionItem, + breakdownDistribution, + }; + }, + { concurrency: ES_MAX_CONCURRENCY } + ); +}; + +// bookmarks +export const publicBookmarks = async ( + context: AuthContext, + args: QueryPublicBookmarksArgs +) => { + const { user, dataSelection } = await getWidgetArguments(context, args.uriKey, args.widgetId); + + const selection = dataSelection[0]; + const { filters } = selection; + + const parameters = { + filters + }; + + // Use standard API + return bookmarks(context, user, parameters); +}; + +// list & timeline +export const publicStixCoreObjects = async ( + context: AuthContext, + args: QueryPublicStixCoreObjectsArgs +) => { + const { user, dataSelection } = await getWidgetArguments(context, args.uriKey, args.widgetId); + context.user = user; // context.user is used in standard api + const selection = dataSelection[0]; + const { filters } = selection; + + const parameters = { + startDate: args.startDate, + endDate: args.endDate, + types: [ + ABSTRACT_STIX_CORE_OBJECT, + ], + filters, + orderBy: selection.date_attribute, + orderMode: 'desc', + first: selection.number ?? 10, + }; + + // Use standard API + return stixCoreObjects(context, user, parameters); +}; + +export const publicStixRelationships = async ( + context: AuthContext, + args: QueryPublicStixRelationshipsArgs +) => { + const { user, dataSelection } = await getWidgetArguments(context, args.uriKey, args.widgetId); + context.user = user; + + const selection = dataSelection[0]; + const { filters } = selection; + + const parameters = { + startDate: args.startDate, + endDate: args.endDate, + filters, + dynamicFrom: selection.dynamicFrom, + dynamicTo: selection.dynamicTo, + orderBy: selection.date_attribute, + orderMode: 'desc', + first: 50, + }; + + // Use standard API + return (await stixRelationships(context, user, parameters) as unknown as StoreRelationConnection); +}; +// endregion diff --git a/opencti-platform/opencti-graphql/src/modules/publicDashboard/publicDashboard-resolvers.ts b/opencti-platform/opencti-graphql/src/modules/publicDashboard/publicDashboard-resolvers.ts index 46dccd0b8afa..9e5b6a119a1e 100644 --- a/opencti-platform/opencti-graphql/src/modules/publicDashboard/publicDashboard-resolvers.ts +++ b/opencti-platform/opencti-graphql/src/modules/publicDashboard/publicDashboard-resolvers.ts @@ -8,7 +8,14 @@ import { getPublicDashboardByUriKey, getAllowedMarkings, publicStixCoreObjectsNumber, - publicStixCoreObjectsMultiTimeSeries + publicStixCoreObjectsMultiTimeSeries, + publicStixRelationshipsMultiTimeSeries, + publicStixRelationshipsNumber, + publicStixCoreObjectsDistribution, + publicStixRelationshipsDistribution, + publicBookmarks, + publicStixCoreObjects, + publicStixRelationships, } from './publicDashboard-domain'; import { getAuthorizedMembers } from '../../utils/authorizedMembers'; @@ -19,6 +26,13 @@ const publicDashboardResolvers: Resolvers = { publicDashboardByUriKey: (_, { uri_key }, context) => getPublicDashboardByUriKey(context, uri_key), publicStixCoreObjectsNumber: (_, args, context) => publicStixCoreObjectsNumber(context, args), publicStixCoreObjectsMultiTimeSeries: (_, args, context) => publicStixCoreObjectsMultiTimeSeries(context, args), + publicStixRelationshipsMultiTimeSeries: (_, args, context) => publicStixRelationshipsMultiTimeSeries(context, args), + publicStixRelationshipsNumber: (_, args, context) => publicStixRelationshipsNumber(context, args), + publicStixCoreObjectsDistribution: (_, args, context) => publicStixCoreObjectsDistribution(context, args), + publicStixRelationshipsDistribution: (_, args, context) => publicStixRelationshipsDistribution(context, args), + publicBookmarks: (_, args, context) => publicBookmarks(context, args), + publicStixCoreObjects: (_, args, context) => publicStixCoreObjects(context, args), + publicStixRelationships: (_, args, context) => publicStixRelationships(context, args), }, PublicDashboard: { authorized_members: (publicDashboard, _, context) => getAuthorizedMembers(context, context.user, publicDashboard), diff --git a/opencti-platform/opencti-graphql/src/modules/publicDashboard/publicDashboard-types.ts b/opencti-platform/opencti-graphql/src/modules/publicDashboard/publicDashboard-types.ts index 44fe93ba7ed5..0cdc3010b94e 100644 --- a/opencti-platform/opencti-graphql/src/modules/publicDashboard/publicDashboard-types.ts +++ b/opencti-platform/opencti-graphql/src/modules/publicDashboard/publicDashboard-types.ts @@ -2,6 +2,7 @@ import type { BasicStoreEntity, StoreEntity, StoreMarkingDefinition } from '../. import type { StixDomainObject, StixOpenctiExtensionSDO } from '../../types/stix-common'; import { STIX_EXT_OCTI } from '../../types/stix-extensions'; import type { AuthorizedMember } from '../../utils/access'; +import type { FilterGroup } from '../../generated/graphql'; export const ENTITY_TYPE_PUBLIC_DASHBOARD = 'PublicDashboard'; @@ -34,12 +35,56 @@ export interface StoreEntityPublicDashboard extends StoreEntity { // endregion // region cache type +export interface PublicDashboardCachedWidget { + id: string + perspective: 'entities' | 'relationships' | 'audits' | null + type: string, + layout: { + w: number + h: number, + x: number + y: number + i: string + moved: boolean + static: boolean + } + parameters: { + title?: string + interval?: string + stacked?: boolean + legend?: boolean + distributed?: boolean + } + dataSelection: { + filters?: FilterGroup + dynamicFrom?: FilterGroup + dynamicTo?: FilterGroup + label?: string + attribute?: string + date_attribute?: string + centerLat?: number + centerLng?: number + zoom?: number + isTo?: boolean + number?: boolean + toTypes?: string[] + perspective?: 'entities' | 'relationships' | 'audits' | null + }[] +} + export interface PublicDashboardCached { id: string; internal_id: string; uri_key: string; dashboard_id: string; - private_manifest: { widgets:any, config: any }; + private_manifest: { + widgets: Record, + config: { + startDate?: string + endDate?: string + relativeDate?: string + } + }; user_id: string; allowed_markings_ids: string[]; allowed_markings: Array; diff --git a/opencti-platform/opencti-graphql/src/modules/publicDashboard/publicDashboard-utils.ts b/opencti-platform/opencti-graphql/src/modules/publicDashboard/publicDashboard-utils.ts index 6969be2f815c..1f4e6c681ae2 100644 --- a/opencti-platform/opencti-graphql/src/modules/publicDashboard/publicDashboard-utils.ts +++ b/opencti-platform/opencti-graphql/src/modules/publicDashboard/publicDashboard-utils.ts @@ -1,6 +1,6 @@ import { getEntitiesMapFromCache, getEntitiesListFromCache } from '../../database/cache'; import { SYSTEM_USER } from '../../utils/access'; -import { ENTITY_TYPE_PUBLIC_DASHBOARD, type PublicDashboardCached } from './publicDashboard-types'; +import { ENTITY_TYPE_PUBLIC_DASHBOARD, type PublicDashboardCached, type PublicDashboardCachedWidget } from './publicDashboard-types'; import { ENTITY_TYPE_USER } from '../../schema/internalObject'; import type { AuthContext, AuthUser, UserCapability } from '../../types/user'; import { UnsupportedError } from '../../config/errors'; @@ -11,17 +11,14 @@ import { elLoadById } from '../../database/engine'; interface WidgetArguments { user: AuthUser, - dateAttribute?: string, - config: any, - filters?: any, - timeSeriesParameters?: any + dataSelection: PublicDashboardCachedWidget['dataSelection'], + parameters: PublicDashboardCachedWidget['parameters'], } export const getWidgetArguments = async ( context: AuthContext, uriKey: string, widgetId: string, - multiTime = false, ): Promise => { // Get publicDashboard from cache const publicDashboardsMapByUriKey = await getEntitiesMapFromCache(context, SYSTEM_USER, ENTITY_TYPE_PUBLIC_DASHBOARD); @@ -32,58 +29,33 @@ export const getWidgetArguments = async ( const { user_id, private_manifest, allowed_markings }: PublicDashboardCached = publicDashboard; - // Get user from cache + // Get user that creates the public dashboard from cache const platformUsersMap = await getEntitiesMapFromCache(context, SYSTEM_USER, ENTITY_TYPE_USER); - const plateformUser = platformUsersMap.get(user_id); - if (!plateformUser) { + const platformUser = platformUsersMap.get(user_id); + if (!platformUser) { throw UnsupportedError('User not found'); } - // replace User markings by publicDashboard allowed_markings + // To replace User markings by publicDashboard allowed_markings const allMarkings = await getEntitiesListFromCache(context, SYSTEM_USER, ENTITY_TYPE_MARKING_DEFINITION); - - // replace User capabilities by KNOWLEDGE capability + // To replace User capabilities by KNOWLEDGE capability const accessKnowledgeCapability: UserCapability = await elLoadById(context, SYSTEM_USER, 'capability--cbc68f4b-1d0c-51f6-a1b9-10344503b493') as unknown as UserCapability; + // Construct a fake user to be able to call private API const user = { - ...plateformUser, - origin: { user_id: plateformUser.id, referer: 'public-dashboard' }, + ...platformUser, + origin: { user_id: platformUser.id, referer: 'public-dashboard' }, allowed_marking: computeAvailableMarkings(allowed_markings, allMarkings), // TODO what if user is downgraded ?? capabilities: [accessKnowledgeCapability] }; // Get widget query configuration - const { widgets, config } = private_manifest; - - // if multiTimeSerie - if (multiTime) { - const { dataSelection } = widgets[widgetId]; - const timeSeriesParameters = dataSelection.map((selection: { filters: any; date_attribute: any; }) => { - const filters = { - filterGroups: [selection.filters], - filters: [], - mode: 'and' - }; - return { - field: selection.date_attribute, - filters, - }; - }); - - return { user, config, timeSeriesParameters }; - } - - const widgetConfig = widgets[widgetId].dataSelection[0]; - const filters = { - filterGroups: [widgetConfig.filters], - filters: [], - mode: 'and' - }; + const { widgets } = private_manifest; + const { dataSelection, parameters } = widgets[widgetId]; return { user, - config, - filters, - dateAttribute: widgetConfig.date_attribute + parameters, + dataSelection }; }; diff --git a/opencti-platform/opencti-graphql/src/modules/publicDashboard/publicDashboard.graphql b/opencti-platform/opencti-graphql/src/modules/publicDashboard/publicDashboard.graphql index 3f6d7961fe48..ab73497c024c 100644 --- a/opencti-platform/opencti-graphql/src/modules/publicDashboard/publicDashboard.graphql +++ b/opencti-platform/opencti-graphql/src/modules/publicDashboard/publicDashboard.graphql @@ -19,6 +19,13 @@ type PublicDashboard implements InternalObject & BasicObject { authorized_members: [MemberAccess!] @auth(for: [KNOWLEDGE, EXPLORE]) } +type PublicDistribution { + label: String! + entity: StixObjectOrStixRelationshipOrCreator + value: Int + breakdownDistribution: [Distribution] +} + # Ordering enum PublicDashboardsOrdering { name @@ -46,17 +53,57 @@ type Query { ): PublicDashboardConnection @auth(for: [EXPLORE]) publicDashboardByUriKey(uri_key: String!): PublicDashboard publicStixCoreObjectsNumber( + uriKey: String! + widgetId : String! startDate: DateTime endDate: DateTime + ): Number + publicStixRelationshipsNumber( uriKey: String! widgetId : String! + startDate: DateTime + endDate: DateTime ): Number publicStixCoreObjectsMultiTimeSeries( - startDate: DateTime! + uriKey: String! + widgetId : String! + startDate: DateTime endDate: DateTime + ): [MultiTimeSeries] + publicStixRelationshipsMultiTimeSeries( uriKey: String! widgetId : String! + startDate: DateTime + endDate: DateTime ): [MultiTimeSeries] + publicStixCoreObjectsDistribution( + uriKey: String! + widgetId : String! + startDate: DateTime + endDate: DateTime + ): [PublicDistribution] + publicStixRelationshipsDistribution( + uriKey: String! + widgetId : String! + startDate: DateTime + endDate: DateTime + ): [PublicDistribution] + publicBookmarks( + uriKey: String! + widgetId : String! + ): StixDomainObjectConnection + publicStixCoreObjects( + uriKey: String! + widgetId : String! + startDate: DateTime + endDate: DateTime + ): StixCoreObjectConnection + publicStixRelationships( + uriKey: String! + widgetId : String! + startDate: DateTime + endDate: DateTime + ): StixRelationshipConnection } type PublicDashboardEdge { diff --git a/opencti-platform/opencti-graphql/src/modules/publicDashboard/publicDashboard.ts b/opencti-platform/opencti-graphql/src/modules/publicDashboard/publicDashboard.ts index 77b3da4b8f84..41e3ee686f12 100644 --- a/opencti-platform/opencti-graphql/src/modules/publicDashboard/publicDashboard.ts +++ b/opencti-platform/opencti-graphql/src/modules/publicDashboard/publicDashboard.ts @@ -19,12 +19,13 @@ export const PUBLIC_DASHBOARD_DEFINITION: ModuleDefinition( + context, + SYSTEM_USER, + ENTITY_TYPE_PUBLIC_DASHBOARD + ); + const publicDashboardsToDelete = publicDashboards + .filter((dashboard) => dashboard.dashboard_id === workspaceId) + .map((dashboard) => dashboard.id); + if (publicDashboardsToDelete.length > 0) { + await elRawDeleteByQuery({ + index: READ_INDEX_INTERNAL_OBJECTS, + refresh: true, + body: { + query: { + bool: { + must: [ + { term: { 'entity_type.keyword': { value: 'PublicDashboard' } } }, + { terms: { 'internal_id.keyword': publicDashboardsToDelete } } + ] + } + } + } + }).catch((err) => { + throw DatabaseError( + '[DELETE] Error deleting public dashboard for workspace ', + { cause: err, workspace_id: workspaceId, } + ); + }); + } + // endregion + await publishUserAction({ user, event_type: 'mutation', diff --git a/opencti-platform/opencti-graphql/src/types/store.d.ts b/opencti-platform/opencti-graphql/src/types/store.d.ts index 2fefabe4edcf..a8374243f67c 100644 --- a/opencti-platform/opencti-graphql/src/types/store.d.ts +++ b/opencti-platform/opencti-graphql/src/types/store.d.ts @@ -211,6 +211,7 @@ interface StoreRawRelation extends StoreProxyRelation { // boolean revoked: boolean; x_opencti_negative: boolean; + is_inferred: boolean; // number confidence: number; attribute_count: number; @@ -243,6 +244,12 @@ interface BasicStoreEntityEdge { node: T; } +interface BasicStoreRelationshipEdge { + cursor: string + types?: string[] + node: T; +} + interface BasicStoreCommonEdge { cursor: string types?: string[] @@ -254,6 +261,11 @@ interface StoreEntityConnection { pageInfo: PageInfo; } +interface StoreRelationConnection { + edges: Array>; + pageInfo: PageInfo; +} + interface StoreCommonConnection { edges: Array>; pageInfo: PageInfo; diff --git a/opencti-platform/opencti-graphql/src/utils/format.js b/opencti-platform/opencti-graphql/src/utils/format.js index 8ab95986d610..10e259be4c7f 100644 --- a/opencti-platform/opencti-graphql/src/utils/format.js +++ b/opencti-platform/opencti-graphql/src/utils/format.js @@ -75,6 +75,16 @@ export const computeRangeIntersection = (a, b) => { export const minutesAgo = (minutes) => moment().utc().subtract(minutes, 'minutes'); export const hoursAgo = (hours) => moment().utc().subtract(hours, 'hours'); +/** + * @param {number} days Number of days + * @return {string} ISO Date string + */ +export const daysAgo = (days) => { + const currentDate = new Date(); + currentDate.setDate(currentDate.getDate() - days); + return currentDate.toISOString().split('T')[0]; +}; + const hashes = ['SHA-512', 'SHA-256', 'SHA-1', 'MD5']; export const hashValue = (stixCyberObservable) => { if (stixCyberObservable.hashes) { diff --git a/opencti-platform/opencti-graphql/tests/02-integration/02-resolvers/publicDashboard-data.ts b/opencti-platform/opencti-graphql/tests/02-integration/02-resolvers/publicDashboard-data.ts new file mode 100644 index 000000000000..385e32be3a65 --- /dev/null +++ b/opencti-platform/opencti-graphql/tests/02-integration/02-resolvers/publicDashboard-data.ts @@ -0,0 +1,446 @@ +export const PRIVATE_DASHBOARD_MANIFEST = { + widgets: { + 'ebb25410-7048-4de7-9288-704e962215f6': { + id: 'ebb25410-7048-4de7-9288-704e962215f6', + type: 'number', + perspective: 'entities', + dataSelection: [ + { + label: 'malwares', + attribute: 'entity_type', + date_attribute: 'created_at', + perspective: 'entities', + isTo: true, + filters: { + mode: 'and', + filters: [ + { + key: 'entity_type', + values: ['Malware'], + operator: 'eq', + mode: 'or' + }, + { + key: 'description', + values: ['widget tests'], + operator: 'search', + mode: 'or' + } + ], + filterGroups: [] + } + } + ], + parameters: { + title: 'malwares number' + }, + layout: { + w: 4, + h: 2, + x: 4, + y: 0, + i: 'ebb25410-7048-4de7-9288-704e962215f6', + moved: false, + static: false + } + }, + 'ecb25410-7048-4de7-9288-704e962215f6': { + id: 'ecb25410-7048-4de7-9288-704e962215f6', + type: 'number', + perspective: 'relationships', + dataSelection: [ + { + label: 'malwares', + attribute: 'entity_type', + date_attribute: 'created_at', + perspective: 'relationships', + isTo: true, + filters: { + mode: 'and', + filters: [ + { + key: 'toTypes', + values: ['Administrative-Area'], + operator: 'eq', + mode: 'or' + }, + { + key: 'relationship_type', + values: ['targets'], + operator: 'eq', + mode: 'or' + } + ], + filterGroups: [] + } + } + ], + parameters: { + title: 'malwares attacking areas' + }, + layout: { + w: 4, + h: 2, + x: 4, + y: 0, + i: 'ecb25410-7048-4de7-9288-704e962215f6', + moved: false, + static: false + } + }, + '0a471055-7426-4840-9501-33770b845f92': { + id: '0a471055-7426-4840-9501-33770b845f92', + type: 'line', + perspective: 'entities', + dataSelection: [ + { + label: 'areas', + attribute: 'entity_type', + date_attribute: 'created_at', + perspective: 'entities', + isTo: true, + filters: { + mode: 'and', + filters: [ + { + key: ['entity_type'], + values: ['Administrative-Area'], + operator: 'eq', + mode: 'or' + }, + { + key: 'description', + values: ['widget tests'], + operator: 'search', + mode: 'or' + } + ], + filterGroups: [] + }, + }, + { + label: 'malwares', + attribute: 'entity_type', + date_attribute: 'created_at', + perspective: 'entities', + isTo: true, + filters: { + mode: 'and', + filters: [ + { + key: ['entity_type'], + values: ['Malware'], + operator: 'eq', + mode: 'or' + }, + { + key: 'description', + values: ['widget tests'], + operator: 'search', + mode: 'or' + } + ], + filterGroups: [] + }, + } + ], + parameters: { + title: 'Evolution of malwares and areas' + }, + layout: { + w: 2, + h: 4, + x: 0, + y: 0, + i: '0a471055-7426-4840-9501-33770b845f92', + moved: false, + static: false + } + }, + '9e6afa7e-0db7-424c-8951-16b867245583': { + id: '9e6afa7e-0db7-424c-8951-16b867245583', + type: 'line', + perspective: 'relationships', + dataSelection: [ + { + label: '', + attribute: 'entity_type', + date_attribute: 'created_at', + perspective: 'relationships', + isTo: true, + filters: { + mode: 'and', + filters: [ + { + key: ['relationship_type'], + values: ['targets'], + operator: 'eq', + mode: 'or' + } + ], + filterGroups: [] + }, + dynamicFrom: { + mode: 'and', + filters: [{ + key: ['description'], + values: ['widget tests'], + operator: 'search', + mode: 'or' + }], + filterGroups: [] + } + } + ], + parameters: { + title: 'Evolution of attacks' + }, + layout: { + w: 3, + h: 4, + x: 2, + y: 0, + i: '9e6afa7e-0db7-424c-8951-16b867245583', + moved: false, + static: false + } + }, + '9865bec0-d8b1-4592-b14e-0e81e1645f59': { + id: '9865bec0-d8b1-4592-b14e-0e81e1645f59', + type: 'donut', + perspective: 'entities', + dataSelection: [ + { + label: 'Area', + attribute: 'entity_type', + date_attribute: 'created_at', + perspective: 'entities', + isTo: true, + filters: { + mode: 'and', + filters: [ + { + key: ['entity_type'], + values: ['Administrative-Area'], + operator: 'eq', + mode: 'or' + }, + { + key: 'description', + values: ['widget tests'], + operator: 'search', + mode: 'or' + } + ], + filterGroups: [] + } + } + ], + parameters: { + title: 'Donut entities' + }, + layout: { + w: 2, + h: 4, + x: 6, + y: 0, + i: '9865bec0-d8b1-4592-b14e-0e81e1645f59', + moved: false, + static: false + } + }, + '1865bec0-d8b1-4592-b14e-0e81e1645f59': { + id: '1865bec0-d8b1-4592-b14e-0e81e1645f59', + type: 'donut', + perspective: 'entities', + dataSelection: [ + { + label: '', + attribute: 'malware_types', + date_attribute: 'created_at', + perspective: 'entities', + isTo: true, + filters: { + mode: 'and', + filters: [ + { + key: ['entity_type'], + values: ['Malware'], + operator: 'eq', + mode: 'or' + }, + { + key: 'description', + values: ['widget tests'], + operator: 'search', + mode: 'or' + } + ], + filterGroups: [] + }, + } + ], + parameters: { + title: 'Malwares by type' + }, + layout: { + w: 2, + h: 4, + x: 6, + y: 0, + i: '1865bec0-d8b1-4592-b14e-0e81e1645f59', + moved: false, + static: false + } + }, + '2b3c637b-bf25-46ca-8b28-b891d349cc31': { + id: '2b3c637b-bf25-46ca-8b28-b891d349cc31', + type: 'donut', + perspective: 'relationships', + dataSelection: [ + { + label: '', + attribute: 'internal_id', + date_attribute: 'created_at', + perspective: 'relationships', + isTo: true, + filters: { + mode: 'and', + filters: [ + { + key: ['relationship_type'], + values: ['targets'], + operator: 'eq', + mode: 'or' + }, + { + key: ['toTypes'], + values: ['Administrative-Area'], + operator: 'eq', + mode: 'or' + } + ], + filterGroups: [] + }, + dynamicFrom: { + mode: 'and', + filters: [{ + key: ['description'], + values: ['widget tests'], + operator: 'search', + mode: 'or' + }], + filterGroups: [] + } + } + ], + parameters: { + title: 'Donut relationships' + }, + layout: { + w: 2, + h: 4, + x: 8, + y: 0, + i: '2b3c637b-bf25-46ca-8b28-b891d349cc31', + moved: false, + static: false + } + }, + 'bec879df-4da2-46c0-994a-e795c1b3a649': { + id: 'bec879df-4da2-46c0-994a-e795c1b3a649', + type: 'list', + perspective: 'entities', + dataSelection: [ + { + label: '', + attribute: 'entity_type', + date_attribute: 'created_at', + perspective: 'entities', + isTo: true, + filters: { + mode: 'and', + filters: [ + { + key: ['entity_type'], + values: ['Administrative-Area'], + operator: 'eq', + mode: 'or' + }, + { + key: 'description', + values: ['widget tests'], + operator: 'search', + mode: 'or' + } + ], + filterGroups: [] + }, + } + ], + parameters: { + title: 'List entities' + }, + layout: { + w: 4, + h: 2, + x: 8, + y: 4, + i: 'bec879df-4da2-46c0-994a-e795c1b3a649', + moved: false, + static: false + } + }, + '6dbb6564-3e4a-4a28-85b1-e2ac479e38e7': { + id: '6dbb6564-3e4a-4a28-85b1-e2ac479e38e7', + type: 'list', + perspective: 'relationships', + dataSelection: [ + { + label: '', + attribute: 'entity_type', + date_attribute: 'created_at', + perspective: 'relationships', + isTo: true, + filters: { + mode: 'and', + filters: [ + { + key: ['relationship_type'], + values: ['targets'], + operator: 'eq', + mode: 'or' + } + ], + filterGroups: [] + }, + dynamicFrom: { + mode: 'and', + filters: [{ + key: ['description'], + values: ['widget tests'], + operator: 'search', + mode: 'or' + }], + filterGroups: [] + } + } + ], + parameters: { + title: 'List relationships' + }, + layout: { + w: 4, + h: 2, + x: 8, + y: 6, + i: '6dbb6564-3e4a-4a28-85b1-e2ac479e38e7', + moved: false, + static: false + } + } + }, + config: { + + } +}; diff --git a/opencti-platform/opencti-graphql/tests/02-integration/02-resolvers/publicDashboard-test.js b/opencti-platform/opencti-graphql/tests/02-integration/02-resolvers/publicDashboard-test.js index 2549fa0651c8..f018094fb515 100644 --- a/opencti-platform/opencti-graphql/tests/02-integration/02-resolvers/publicDashboard-test.js +++ b/opencti-platform/opencti-graphql/tests/02-integration/02-resolvers/publicDashboard-test.js @@ -2,6 +2,7 @@ import { describe, expect, it, beforeAll, afterAll } from 'vitest'; import gql from 'graphql-tag'; import { editorQuery, participantQuery, queryAsAdmin } from '../../utils/testQuery'; import { toBase64 } from '../../../src/database/utils'; +import { PRIVATE_DASHBOARD_MANIFEST } from './publicDashboard-data'; const LIST_QUERY = gql` query publicDashboards( @@ -35,6 +36,17 @@ const READ_QUERY = gql` publicDashboard(id: $id) { id name + uri_key + } + } +`; + +const READ_URI_KEY_QUERY = gql` + query PublicDashboardByUriKey($uri_key: String!) { + publicDashboardByUriKey(uri_key: $uri_key) { + id + name + uri_key } } `; @@ -53,6 +65,7 @@ const CREATE_QUERY = gql` publicDashboardAdd(input: $input) { id name + uri_key } } `; @@ -134,67 +147,7 @@ describe('PublicDashboard resolver', () => { describe('Tests with manifest', () => { beforeAll(async () => { // Add manifest to Private dashboard as empty dashboard should not be published - const parsedManifest = { - widgets: { - 'ebb25410-7048-4de7-9288-704e962215f6': { - id: 'ebb25410-7048-4de7-9288-704e962215f6', - type: 'number', - perspective: 'entities', - dataSelection: [ - { - label: 'area', - attribute: 'entity_type', - date_attribute: 'created_at', - perspective: 'entities', - isTo: true, - filters: { - mode: 'and', - filters: [ - - ], - filterGroups: [ - - ] - }, - dynamicFrom: { - mode: 'and', - filters: [ - - ], - filterGroups: [ - - ] - }, - dynamicTo: { - mode: 'and', - filters: [ - - ], - filterGroups: [ - - ] - } - } - ], - parameters: { - title: 'area number' - }, - layout: { - w: 4, - h: 2, - x: 4, - y: 0, - i: 'ebb25410-7048-4de7-9288-704e962215f6', - moved: false, - static: false - } - } - }, - config: { - - } - }; - const manifest = toBase64(JSON.stringify(parsedManifest)); + const manifest = toBase64(JSON.stringify(PRIVATE_DASHBOARD_MANIFEST)); await queryAsAdmin({ query: UPDATE_PRIVATE_DASHBOARD_QUERY, variables: { @@ -206,6 +159,7 @@ describe('PublicDashboard resolver', () => { describe('PublicDashboard resolver standard behavior', () => { let publicDashboardInternalId; + let publicDashboardUriKey; it('User without EXPLORE_EXUPDATE_PUBLISH capability should not create private dashboards', async () => { // Create the publicDashboard @@ -227,7 +181,7 @@ describe('PublicDashboard resolver', () => { it('should publicDashboard created', async () => { // Create the publicDashboard - const PUBLICDASHBOARD_TO_CREATE = { + const PUBLIC_DASHBOARD_TO_CREATE = { input: { name: publicDashboardName, dashboard_id: privateDashboardInternalId, @@ -235,12 +189,12 @@ describe('PublicDashboard resolver', () => { }; const publicDashboard = await queryAsAdmin({ query: CREATE_QUERY, - variables: PUBLICDASHBOARD_TO_CREATE, + variables: PUBLIC_DASHBOARD_TO_CREATE, }); - expect(publicDashboard.data.publicDashboardAdd).not.toBeNull(); expect(publicDashboard.data.publicDashboardAdd.name).toEqual(publicDashboardName); publicDashboardInternalId = publicDashboard.data.publicDashboardAdd.id; + publicDashboardUriKey = publicDashboard.data.publicDashboardAdd.uri_key; }); it('should publicDashboard loaded by internal id', async () => { @@ -253,6 +207,17 @@ describe('PublicDashboard resolver', () => { expect(queryResult.data.publicDashboard.id).toEqual(publicDashboardInternalId); }); + it('should fetch publicDashboard by uri key', async () => { + const queryResult = await queryAsAdmin({ + query: READ_URI_KEY_QUERY, + variables: { uri_key: publicDashboardUriKey }, + }); + expect(queryResult).not.toBeNull(); + expect(queryResult.data.publicDashboardByUriKey).not.toBeNull(); + expect(queryResult.data.publicDashboardByUriKey.id).toEqual(publicDashboardInternalId); + expect(queryResult.data.publicDashboardByUriKey.uri_key).toEqual(publicDashboardUriKey); + }); + it('should list publicDashboards', async () => { const queryResult = await queryAsAdmin({ query: LIST_QUERY, @@ -300,6 +265,457 @@ describe('PublicDashboard resolver', () => { expect(queryResult.errors.at(0).message).toEqual('You are not allowed to do this.'); }); + describe('Tests widgets API', () => { + let vadorId; + let magnetoId; + let octopusId; + let franceId; + let belgiqueId; + + afterAll(async () => { + // region Delete areas. + const DELETE_AREA = gql` + mutation administrativeAreaDelete($id: ID!) { + administrativeAreaDelete(id: $id) + } + `; + await queryAsAdmin({ + query: DELETE_AREA, + variables: { id: franceId }, + }); + await queryAsAdmin({ + query: DELETE_AREA, + variables: { id: belgiqueId }, + }); + // endregion + + // region Delete malwares. + const DELETE_MALWARE = gql` + mutation malwareDelete($id: ID!) { + malwareEdit(id: $id) { + delete + } + } + `; + await queryAsAdmin({ + query: DELETE_MALWARE, + variables: { id: vadorId }, + }); + await queryAsAdmin({ + query: DELETE_MALWARE, + variables: { id: magnetoId }, + }); + await queryAsAdmin({ + query: DELETE_MALWARE, + variables: { id: octopusId }, + }); + // endregion + }); + + beforeAll(async () => { + // region Create some areas. + const CREATE_AREA = gql` + mutation AdministrativeAreaAdd($input: AdministrativeAreaAddInput!) { + administrativeAreaAdd(input: $input) { id } + } + `; + const france = await editorQuery({ + query: CREATE_AREA, + variables: { input: { name: 'france', description: 'widget tests' } }, + }); + franceId = france.data.administrativeAreaAdd.id; + const belgique = await editorQuery({ + query: CREATE_AREA, + variables: { input: { name: 'belgique', description: 'widget tests' } }, + }); + belgiqueId = belgique.data.administrativeAreaAdd.id; + // endregion + + // region Create some malwares. + const CREATE_MALWARES = gql` + mutation MalwareAdd($input: MalwareAddInput!) { + malwareAdd(input: $input) { id } + } + `; + const vador = await editorQuery({ + query: CREATE_MALWARES, + variables: { input: { name: 'vador', malware_types: ['ddos'], description: 'widget tests' } }, + }); + vadorId = vador.data.malwareAdd.id; + const magneto = await editorQuery({ + query: CREATE_MALWARES, + variables: { input: { name: 'magneto', malware_types: ['backdoor'], description: 'widget tests' } }, + }); + magnetoId = magneto.data.malwareAdd.id; + const octopus = await editorQuery({ + query: CREATE_MALWARES, + variables: { input: { name: 'octopus', malware_types: ['rootkit'], description: 'widget tests' } }, + }); + octopusId = octopus.data.malwareAdd.id; + // endregion + + // region Create targets relationships between areas and malwares + const ADD_TARGETS_REL = gql` + mutation StixCoreRelationshipAdd($input: StixCoreRelationshipAddInput!) { + stixCoreRelationshipAdd(input: $input) { id } + } + `; + await editorQuery({ + query: ADD_TARGETS_REL, + variables: { + input: { + relationship_type: 'targets', + fromId: vadorId, + toId: franceId + } + }, + }); + await editorQuery({ + query: ADD_TARGETS_REL, + variables: { + input: { + relationship_type: 'targets', + fromId: magnetoId, + toId: franceId + } + }, + }); + await editorQuery({ + query: ADD_TARGETS_REL, + variables: { + input: { + relationship_type: 'targets', + fromId: magnetoId, + toId: belgiqueId + } + }, + }); + await editorQuery({ + query: ADD_TARGETS_REL, + variables: { + input: { + relationship_type: 'targets', + fromId: octopusId, + toId: belgiqueId + } + }, + }); + // endregion + }); + + it('should return the data for API: SCO Number', async () => { + const API_SCO_NUMBER_QUERY = gql` + query PublicStixCoreObjectsNumber( + $startDate: DateTime + $endDate: DateTime + $uriKey: String! + $widgetId : String! + ) { + publicStixCoreObjectsNumber( + startDate: $startDate + endDate: $endDate + uriKey: $uriKey + widgetId : $widgetId + ) { + total + count + } + } + `; + const { data } = await queryAsAdmin({ + query: API_SCO_NUMBER_QUERY, + variables: { + uriKey: publicDashboardUriKey, + widgetId: 'ebb25410-7048-4de7-9288-704e962215f6' + }, + }); + const { publicStixCoreObjectsNumber } = data; + expect(publicStixCoreObjectsNumber.total).toEqual(3); + expect(publicStixCoreObjectsNumber.count).toEqual(0); + }); + + it('should return the data for API: SCR Number', async () => { + const API_SCR_NUMBER_QUERY = gql` + query PublicStixRelationshipsNumber( + $startDate: DateTime + $endDate: DateTime + $uriKey: String! + $widgetId : String! + ) { + publicStixRelationshipsNumber( + startDate: $startDate + endDate: $endDate + uriKey: $uriKey + widgetId : $widgetId + ) { + total + count + } + } + `; + const { data } = await queryAsAdmin({ + query: API_SCR_NUMBER_QUERY, + variables: { + uriKey: publicDashboardUriKey, + widgetId: 'ecb25410-7048-4de7-9288-704e962215f6' + }, + }); + const { publicStixRelationshipsNumber } = data; + expect(publicStixRelationshipsNumber.total).toEqual(4); + expect(publicStixRelationshipsNumber.count).toEqual(0); + }); + + it('should return the data for API: SCO Time series', async () => { + const API_SCO_LIST_QUERY = gql` + query PublicStixCoreObjectsMultiTimeSeries( + $startDate: DateTime + $endDate: DateTime + $uriKey: String! + $widgetId : String! + ) { + publicStixCoreObjectsMultiTimeSeries( + startDate: $startDate + endDate: $endDate + uriKey: $uriKey + widgetId : $widgetId + ) { + data { + date + value + } + } + } + `; + const { data } = await queryAsAdmin({ + query: API_SCO_LIST_QUERY, + variables: { + uriKey: publicDashboardUriKey, + widgetId: '0a471055-7426-4840-9501-33770b845f92' + }, + }); + const { publicStixCoreObjectsMultiTimeSeries } = data; + const areasData = publicStixCoreObjectsMultiTimeSeries[0].data; + const malwaressData = publicStixCoreObjectsMultiTimeSeries[1].data; + expect(areasData.length).toEqual(1); + expect(areasData[0].value).toEqual(2); + expect(malwaressData.length).toEqual(1); + expect(malwaressData[0].value).toEqual(3); + }); + + it('should return the data for API: SCR Time series', async () => { + const API_SCR_LIST_QUERY = gql` + query PublicStixRelationshipsMultiTimeSeries( + $startDate: DateTime + $endDate: DateTime + $uriKey: String! + $widgetId : String! + ) { + publicStixRelationshipsMultiTimeSeries( + startDate: $startDate + endDate: $endDate + uriKey: $uriKey + widgetId : $widgetId + ) { + data { + date + value + } + } + } + `; + const { data } = await queryAsAdmin({ + query: API_SCR_LIST_QUERY, + variables: { + uriKey: publicDashboardUriKey, + widgetId: '9e6afa7e-0db7-424c-8951-16b867245583' + }, + }); + const { publicStixRelationshipsMultiTimeSeries } = data; + const attacksData = publicStixRelationshipsMultiTimeSeries[0].data; + expect(attacksData.length).toEqual(1); + expect(attacksData[0].value).toEqual(5); + }); + + it('should return the data for API: SCO Distribution', async () => { + const API_SCO_DONUT_QUERY = gql` + query PublicStixCoreObjectsDistribution( + $startDate: DateTime + $endDate: DateTime + $uriKey: String! + $widgetId : String! + ) { + publicStixCoreObjectsDistribution( + startDate: $startDate + endDate: $endDate + uriKey: $uriKey + widgetId : $widgetId + ) { + label + entity { + __typename + } + value + } + } + `; + const { data } = await queryAsAdmin({ + query: API_SCO_DONUT_QUERY, + variables: { + uriKey: publicDashboardUriKey, + widgetId: '9865bec0-d8b1-4592-b14e-0e81e1645f59' + }, + }); + const { publicStixCoreObjectsDistribution } = data; + expect(publicStixCoreObjectsDistribution[0].label).toEqual('Administrative-Area'); + expect(publicStixCoreObjectsDistribution[0].value).toEqual(2); + + const { data: dataMalwares } = await queryAsAdmin({ + query: API_SCO_DONUT_QUERY, + variables: { + uriKey: publicDashboardUriKey, + widgetId: '1865bec0-d8b1-4592-b14e-0e81e1645f59' + }, + }); + const malwaresDistribution = dataMalwares.publicStixCoreObjectsDistribution; + expect(malwaresDistribution.length).toEqual(3); + const backdoor = malwaresDistribution.find((m) => m.label === 'Backdoor'); + const ddos = malwaresDistribution.find((m) => m.label === 'Ddos'); + const rootkit = malwaresDistribution.find((m) => m.label === 'Rootkit'); + expect(backdoor).toBeDefined(); + expect(ddos).toBeDefined(); + expect(rootkit).toBeDefined(); + expect(backdoor.value).toEqual(1); + expect(ddos.value).toEqual(1); + expect(rootkit.value).toEqual(1); + }); + + it('should return the data for API: SCR Distribution', async () => { + const API_SCR_DONUT_QUERY = gql` + query PublicStixRelationshipsDistribution( + $startDate: DateTime + $endDate: DateTime + $uriKey: String! + $widgetId : String! + ) { + publicStixRelationshipsDistribution( + startDate: $startDate + endDate: $endDate + uriKey: $uriKey + widgetId : $widgetId + ) { + label + entity { + __typename + ... on AdministrativeArea { + name + } + } + value + } + } + `; + const { data } = await queryAsAdmin({ + query: API_SCR_DONUT_QUERY, + variables: { + uriKey: publicDashboardUriKey, + widgetId: '2b3c637b-bf25-46ca-8b28-b891d349cc31' + }, + }); + const { publicStixRelationshipsDistribution } = data; + const france = publicStixRelationshipsDistribution.find((d) => d.entity.name === 'france'); + const belgique = publicStixRelationshipsDistribution.find((d) => d.entity.name === 'belgique'); + expect(france).toBeDefined(); + expect(belgique).toBeDefined(); + expect(france.value).toEqual(2); + expect(belgique.value).toEqual(2); + }); + + it('should return the data for API: SCO List', async () => { + const API_SCO_LIST_QUERY = gql` + query PublicStixCoreObjects( + $startDate: DateTime + $endDate: DateTime + $uriKey: String! + $widgetId : String! + ) { + publicStixCoreObjects( + startDate: $startDate + endDate: $endDate + uriKey: $uriKey + widgetId : $widgetId + ) { + edges { + node { + entity_type + ... on AdministrativeArea { + name + } + } + } + pageInfo { + globalCount + } + } + } + `; + const { data } = await queryAsAdmin({ + query: API_SCO_LIST_QUERY, + variables: { + uriKey: publicDashboardUriKey, + widgetId: 'bec879df-4da2-46c0-994a-e795c1b3a649' + }, + }); + const { publicStixCoreObjects } = data; + const entityTypes = new Set(publicStixCoreObjects.edges.map((e) => e.node.entity_type)); + expect(entityTypes.size).toEqual(1); + expect(entityTypes.has('Administrative-Area')).toEqual(true); + expect(publicStixCoreObjects.pageInfo.globalCount).toEqual(2); + const france = publicStixCoreObjects.edges.find((e) => e.node.name === 'france'); + const belgique = publicStixCoreObjects.edges.find((e) => e.node.name === 'belgique'); + expect(france).toBeDefined(); + expect(belgique).toBeDefined(); + }); + + it('should return the data for API: SCR List', async () => { + const API_SCR_LIST_QUERY = gql` + query PublicStixRelationships( + $startDate: DateTime + $endDate: DateTime + $uriKey: String! + $widgetId : String! + ) { + publicStixRelationships( + startDate: $startDate + endDate: $endDate + uriKey: $uriKey + widgetId : $widgetId + ) { + edges { + node { + relationship_type + } + } + pageInfo { + globalCount + } + } + } + `; + const { data } = await queryAsAdmin({ + query: API_SCR_LIST_QUERY, + variables: { + uriKey: publicDashboardUriKey, + widgetId: '6dbb6564-3e4a-4a28-85b1-e2ac479e38e7' + }, + }); + const { publicStixRelationships } = data; + expect(publicStixRelationships.edges[0].node.relationship_type).toEqual('targets'); + expect(publicStixRelationships.pageInfo.globalCount).toEqual(4); + }); + + // TODO add tests for other APIS + }); + it('should delete publicDashboard', async () => { // Delete the publicDashboard await queryAsAdmin({ diff --git a/opencti-platform/opencti-graphql/tests/03-streams/00-Raw/raw-test.js b/opencti-platform/opencti-graphql/tests/03-streams/00-Raw/raw-test.js index ccc477442aa7..2f059404bbe3 100644 --- a/opencti-platform/opencti-graphql/tests/03-streams/00-Raw/raw-test.js +++ b/opencti-platform/opencti-graphql/tests/03-streams/00-Raw/raw-test.js @@ -22,14 +22,14 @@ describe('Raw streams tests', () => { expect(createEventsByTypes['external-reference'].length).toBe(17); expect(createEventsByTypes.label.length).toBe(15); expect(createEventsByTypes.identity.length).toBe(30); - expect(createEventsByTypes.relationship.length).toBe(126); + expect(createEventsByTypes.relationship.length).toBe(130); expect(createEventsByTypes.indicator.length).toBe(33); expect(createEventsByTypes['attack-pattern'].length).toBe(7); expect(createEventsByTypes.report.length).toBe(22); expect(createEventsByTypes.tool.length).toBe(2); expect(createEventsByTypes.vocabulary.length).toBe(335); // 328 created at init + 2 created in tests + 5 vocabulary organizations types expect(createEventsByTypes.vulnerability.length).toBe(7); - expect(createEvents.length).toBe(723); + expect(createEvents.length).toBe(732); for (let createIndex = 0; createIndex < createEvents.length; createIndex += 1) { const { data: insideData, origin, type } = createEvents[createIndex]; expect(origin).toBeDefined(); @@ -78,7 +78,7 @@ describe('Raw streams tests', () => { } // 03 - CHECK DELETE EVENTS const deleteEvents = events.filter((e) => e.type === EVENT_TYPE_DELETE); - expect(deleteEvents.length).toBe(98); + expect(deleteEvents.length).toBe(103); // const deleteEventsByTypes = R.groupBy((e) => e.data.data.type, deleteEvents); for (let delIndex = 0; delIndex < deleteEvents.length; delIndex += 1) { const { data: insideData, origin, type } = deleteEvents[delIndex]; diff --git a/opencti-platform/opencti-graphql/tests/utils/testQuery.ts b/opencti-platform/opencti-graphql/tests/utils/testQuery.ts index 3993af0ac92d..d077b39ff1aa 100644 --- a/opencti-platform/opencti-graphql/tests/utils/testQuery.ts +++ b/opencti-platform/opencti-graphql/tests/utils/testQuery.ts @@ -22,7 +22,7 @@ export const SYNC_LIVE_START_REMOTE_URI = conf.get('app:sync_live_start_remote_u export const SYNC_DIRECT_START_REMOTE_URI = conf.get('app:sync_direct_start_remote_uri'); export const SYNC_RESTORE_START_REMOTE_URI = conf.get('app:sync_restore_start_remote_uri'); export const SYNC_TEST_REMOTE_URI = `http://api-tests:${PORT}`; -export const RAW_EVENTS_SIZE = 966; +export const RAW_EVENTS_SIZE = 980; export const SYNC_LIVE_EVENTS_SIZE = 598; export const PYTHON_PATH = './src/python/testing';