@cccsaurora/howler-ui 2.16.5 → 2.17.0-dev.470
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/elements/PluginChip.d.ts +2 -0
- package/components/elements/PluginChip.js +2 -1
- package/components/elements/PluginTypography.d.ts +4 -3
- package/components/elements/PluginTypography.js +4 -3
- package/components/elements/hit/HitBanner.js +2 -2
- package/components/elements/hit/HitDetails.js +9 -9
- package/components/elements/hit/outlines/DefaultOutline.js +1 -1
- package/components/routes/hits/search/grid/EnhancedCell.d.ts +2 -0
- package/components/routes/hits/search/grid/EnhancedCell.js +2 -2
- package/components/routes/hits/search/grid/HitRow.js +1 -1
- package/index.js +5 -0
- package/models/entities/generated/Analytic.d.ts +2 -2
- package/models/entities/generated/ApiType.d.ts +2 -1
- package/models/entities/generated/Clue.d.ts +8 -0
- package/models/entities/generated/Hit.d.ts +2 -20
- package/models/entities/generated/Labels.d.ts +1 -0
- package/models/entities/generated/Type.d.ts +7 -0
- package/package.json +23 -14
- package/plugins/HowlerPlugin.js +1 -0
- package/plugins/clue/Provider.d.ts +3 -0
- package/plugins/clue/Provider.js +13 -0
- package/plugins/clue/components/ClueChip.d.ts +3 -0
- package/plugins/clue/components/ClueChip.js +29 -0
- package/plugins/clue/components/ClueLeadForm.d.ts +4 -0
- package/plugins/clue/components/ClueLeadForm.js +24 -0
- package/plugins/clue/components/CluePivot.d.ts +3 -0
- package/plugins/clue/components/CluePivot.js +145 -0
- package/plugins/clue/components/CluePivotForm.d.ts +21 -0
- package/plugins/clue/components/CluePivotForm.js +270 -0
- package/plugins/clue/components/ClueTypography.d.ts +3 -0
- package/plugins/clue/components/ClueTypography.js +53 -0
- package/plugins/clue/helpers.d.ts +3 -0
- package/plugins/clue/helpers.js +196 -0
- package/plugins/clue/index.d.ts +21 -0
- package/plugins/clue/index.js +66 -0
- package/plugins/clue/locales/clue.en.json +8 -0
- package/plugins/clue/locales/clue.fr.json +8 -0
- package/plugins/clue/setup.d.ts +2 -0
- package/plugins/clue/setup.js +46 -0
- package/plugins/clue/utils.d.ts +2 -0
- package/plugins/clue/utils.js +19 -0
- package/plugins/store.js +3 -0
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { type ChipProps } from '@mui/material';
|
|
2
|
+
import type { Hit } from '@cccsaurora/howler-ui/models/entities/generated/Hit';
|
|
2
3
|
import { type FC } from 'react';
|
|
3
4
|
export type PluginChipProps = ChipProps & {
|
|
4
5
|
value: string;
|
|
5
6
|
context: string;
|
|
6
7
|
field?: string;
|
|
8
|
+
hit?: Hit;
|
|
7
9
|
};
|
|
8
10
|
declare const PluginChip: FC<PluginChipProps>;
|
|
9
11
|
export default PluginChip;
|
|
@@ -3,7 +3,7 @@ import { Chip } from '@mui/material';
|
|
|
3
3
|
import howlerPluginStore from '@cccsaurora/howler-ui/plugins/store';
|
|
4
4
|
import {} from 'react';
|
|
5
5
|
import { usePluginStore } from 'react-pluggable';
|
|
6
|
-
const PluginChip = ({ children, value, context, field, ...props }) => {
|
|
6
|
+
const PluginChip = ({ children, value, context, field, hit, ...props }) => {
|
|
7
7
|
const pluginStore = usePluginStore();
|
|
8
8
|
for (const plugin of howlerPluginStore.plugins) {
|
|
9
9
|
const component = pluginStore.executeFunction(`${plugin}.chip`, {
|
|
@@ -11,6 +11,7 @@ const PluginChip = ({ children, value, context, field, ...props }) => {
|
|
|
11
11
|
value,
|
|
12
12
|
context,
|
|
13
13
|
field,
|
|
14
|
+
hit,
|
|
14
15
|
...props
|
|
15
16
|
});
|
|
16
17
|
if (component) {
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { type TypographyProps } from '@mui/material';
|
|
2
|
-
import {
|
|
2
|
+
import type { Hit } from '@cccsaurora/howler-ui/models/entities/generated/Hit';
|
|
3
3
|
export type PluginTypographyProps = TypographyProps & {
|
|
4
4
|
value: string;
|
|
5
5
|
context: string;
|
|
6
6
|
field?: string;
|
|
7
|
+
hit?: Hit;
|
|
7
8
|
};
|
|
8
|
-
declare const
|
|
9
|
-
export default
|
|
9
|
+
declare const _default: import("react").NamedExoticComponent<PluginTypographyProps>;
|
|
10
|
+
export default _default;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { Typography } from '@mui/material';
|
|
3
3
|
import howlerPluginStore from '@cccsaurora/howler-ui/plugins/store';
|
|
4
|
-
import {} from 'react';
|
|
4
|
+
import { memo } from 'react';
|
|
5
5
|
import { usePluginStore } from 'react-pluggable';
|
|
6
|
-
const PluginTypography = ({ children, value, context, field, ...props }) => {
|
|
6
|
+
const PluginTypography = ({ children, value, context, field, hit, ...props }) => {
|
|
7
7
|
const pluginStore = usePluginStore();
|
|
8
8
|
for (const plugin of howlerPluginStore.plugins) {
|
|
9
9
|
const component = pluginStore.executeFunction(`${plugin}.typography`, {
|
|
@@ -11,6 +11,7 @@ const PluginTypography = ({ children, value, context, field, ...props }) => {
|
|
|
11
11
|
value,
|
|
12
12
|
context,
|
|
13
13
|
field,
|
|
14
|
+
hit,
|
|
14
15
|
...props
|
|
15
16
|
});
|
|
16
17
|
if (component) {
|
|
@@ -19,4 +20,4 @@ const PluginTypography = ({ children, value, context, field, ...props }) => {
|
|
|
19
20
|
}
|
|
20
21
|
return _jsx(Typography, { ...props, children: children ?? value });
|
|
21
22
|
};
|
|
22
|
-
export default PluginTypography;
|
|
23
|
+
export default memo(PluginTypography);
|
|
@@ -88,9 +88,9 @@ const HitBanner = ({ hit, layout = HitLayout.NORMAL, showAssigned = true }) => {
|
|
|
88
88
|
const _children = (_jsxs(Stack, { direction: "row", spacing: 1, flex: 1, children: [_jsxs(Typography, { variant: textVariant, noWrap: compressed, textOverflow: compressed ? 'ellipsis' : 'wrap', ...typographyProps, sx: [
|
|
89
89
|
{ display: 'flex', flexDirection: 'row' },
|
|
90
90
|
...(Array.isArray(typographyProps?.sx) ? typographyProps?.sx : [typographyProps?.sx])
|
|
91
|
-
], children: [t(i18nKey), ":"] }), (Array.isArray(value) ? value : [value]).map(val => (_jsx(PluginTypography, { component: "span", context: "banner", variant: textVariant, noWrap: compressed, textOverflow: compressed ? 'ellipsis' : 'wrap', ...typographyProps, value: val, field: field }, val)))] }));
|
|
91
|
+
], children: [t(i18nKey), ":"] }), (Array.isArray(value) ? value : [value]).map(val => (_jsx(PluginTypography, { component: "span", context: "banner", variant: textVariant, noWrap: compressed, textOverflow: compressed ? 'ellipsis' : 'wrap', ...typographyProps, value: val, field: field, hit: hit }, val)))] }));
|
|
92
92
|
return compressed ? (_jsx(Tooltip, { title: Array.isArray(value) ? (_jsx("div", { children: value.map(_indicator => (_jsx("p", { style: { margin: 0, padding: 0 }, children: _indicator }, _indicator))) })) : (value), children: _children })) : (_children);
|
|
93
|
-
}, [compressed, t, textVariant]);
|
|
93
|
+
}, [compressed, hit, t, textVariant]);
|
|
94
94
|
return (_jsxs(Box, { display: "grid", gridTemplateColumns: "minmax(0, auto) minmax(0, 1fr) minmax(0, auto)", alignItems: "stretch", sx: { width: '100%', ml: 0, overflow: 'hidden' }, children: [leftBox, _jsxs(Stack, { sx: {
|
|
95
95
|
height: '100%',
|
|
96
96
|
padding: theme.spacing(1),
|
|
@@ -10,7 +10,7 @@ import { memo, useEffect, useMemo, useState } from 'react';
|
|
|
10
10
|
import { useTranslation } from 'react-i18next';
|
|
11
11
|
import Throttler from '@cccsaurora/howler-ui/utils/Throttler';
|
|
12
12
|
import PluginTypography from '../PluginTypography';
|
|
13
|
-
const ListRenderer = memo(({ objKey: key, entries, maxKeyLength }) => {
|
|
13
|
+
const ListRenderer = memo(({ hit, objKey: key, entries, maxKeyLength }) => {
|
|
14
14
|
const theme = useTheme();
|
|
15
15
|
const { t } = useTranslation();
|
|
16
16
|
const allPrimitives = useMemo(() => entries.every(entry => !isObject(entry)), [entries]);
|
|
@@ -36,15 +36,15 @@ const ListRenderer = memo(({ objKey: key, entries, maxKeyLength }) => {
|
|
|
36
36
|
marginBottom: allPrimitives ? 0 : theme.spacing(1)
|
|
37
37
|
}, children: allPrimitives ? key.padStart(maxKeyLength ?? key.length) : key }) }), _jsxs(Grid, { container: true, spacing: allPrimitives ? 1 : 4, ml: allPrimitives ? -1 : -4, overflow: "hidden", maxWidth: "100%", children: [uniqueEntries.map((entry, index) => {
|
|
38
38
|
if (Array.isArray(entry)) {
|
|
39
|
-
return (_jsx(Grid, { item: true, xs: "auto", maxWidth: "100%", children: _jsx(ListRenderer, { objKey: `${key}.${index}`, entries: entry }) }, index));
|
|
39
|
+
return (_jsx(Grid, { item: true, xs: "auto", maxWidth: "100%", children: _jsx(ListRenderer, { hit: hit, objKey: `${key}.${index}`, entries: entry }) }, index));
|
|
40
40
|
}
|
|
41
41
|
if (isPlainObject(entry)) {
|
|
42
42
|
return (_jsx(Grid, { item: true, xs: 'auto', maxWidth: "100%", minWidth: "350px", children: _jsx(ObjectRenderer, { parentKey: `${key}.${index}`, indent: true, data: entry }) }, index));
|
|
43
43
|
}
|
|
44
|
-
return (_jsxs(Grid, { item: true, maxWidth: "100%", className: `${key}_${index}`.replace(/\./g, '_'), component: "code", display: "flex", flexDirection: "row", children: [_jsx(PluginTypography, { context: "details", component: "code", style: { maxWidth: '100%', font: 'inherit' }, value: entry, field: key.replace(/\.[0-9]+/g, ''), children: entry }), allPrimitives && index < uniqueEntries.length - 1 && _jsx("span", { children: "," })] }, entry));
|
|
44
|
+
return (_jsxs(Grid, { item: true, maxWidth: "100%", className: `${key}_${index}`.replace(/\./g, '_'), component: "code", display: "flex", flexDirection: "row", children: [_jsx(PluginTypography, { context: "details", component: "code", style: { maxWidth: '100%', font: 'inherit' }, value: entry, field: key.replace(/\.[0-9]+/g, ''), hit: hit, children: entry }), allPrimitives && index < uniqueEntries.length - 1 && _jsx("span", { children: "," })] }, entry));
|
|
45
45
|
}), omittedDuplicates && (_jsx(Grid, { item: true, display: "flex", alignItems: "center", children: _jsx(Tooltip, { title: t('duplicates.omitted'), children: _jsx(InfoOutlined, { sx: { fontSize: '20px', ml: 1 }, color: "disabled" }) }) }))] })] }));
|
|
46
46
|
});
|
|
47
|
-
const ObjectRenderer = memo(({ data, parentKey, indent = false }) => {
|
|
47
|
+
const ObjectRenderer = memo(({ hit, data, parentKey, indent = false }) => {
|
|
48
48
|
const theme = useTheme();
|
|
49
49
|
const entries = useMemo(() => {
|
|
50
50
|
const unsorted = Object.entries(flatten(data, { safe: true })).map(([key, val]) => [key, val]);
|
|
@@ -59,7 +59,7 @@ const ObjectRenderer = memo(({ data, parentKey, indent = false }) => {
|
|
|
59
59
|
.filter(([__, val]) => !isNull(val) && !isUndefined(val) && !isEmpty(val))
|
|
60
60
|
.map(([key, val]) => {
|
|
61
61
|
if (Array.isArray(val)) {
|
|
62
|
-
return _jsx(ListRenderer, { maxKeyLength: longestKey, objKey: key, entries: val }, key);
|
|
62
|
+
return _jsx(ListRenderer, { hit: hit, maxKeyLength: longestKey, objKey: key, entries: val }, key);
|
|
63
63
|
}
|
|
64
64
|
return (_jsxs("code", { className: (parentKey ? `${parentKey}.${key}` : key).replace(/\./g, '_'), style: {
|
|
65
65
|
display: 'grid',
|
|
@@ -75,10 +75,10 @@ const ObjectRenderer = memo(({ data, parentKey, indent = false }) => {
|
|
|
75
75
|
paddingRight: theme.spacing(1),
|
|
76
76
|
height: '100%',
|
|
77
77
|
wordWrap: 'break-word'
|
|
78
|
-
}, children: _jsx("code", { style: { maxWidth: '100%' }, children: key }) }), _jsx(Box, { display: "flex", alignItems: "start", children: _jsx(PluginTypography, { context: "details", component: "code", style: { maxWidth: '100%', font: 'inherit' }, value: val, field: (parentKey ? parentKey.concat('.', key) : key).replace(/\.[0-9]+/g, ''), children: val }) })] }, key));
|
|
78
|
+
}, children: _jsx("code", { style: { maxWidth: '100%' }, children: key }) }), _jsx(Box, { display: "flex", alignItems: "start", children: _jsx(PluginTypography, { context: "details", component: "code", style: { maxWidth: '100%', font: 'inherit' }, value: val, field: (parentKey ? parentKey.concat('.', key) : key).replace(/\.[0-9]+/g, ''), hit: hit, children: val }) })] }, key));
|
|
79
79
|
}) })] }));
|
|
80
80
|
});
|
|
81
|
-
const Collapsible = memo(({ title, data, query }) => {
|
|
81
|
+
const Collapsible = memo(({ hit, title, data, query }) => {
|
|
82
82
|
const throttler = useMemo(() => new Throttler(400), []);
|
|
83
83
|
const [scores, setScores] = useState([]);
|
|
84
84
|
const [results, setResults] = useState({});
|
|
@@ -109,7 +109,7 @@ const Collapsible = memo(({ title, data, query }) => {
|
|
|
109
109
|
if (isEmpty(results)) {
|
|
110
110
|
return null;
|
|
111
111
|
}
|
|
112
|
-
return (_jsxs(Accordion, { defaultExpanded: true, children: [_jsx(AccordionSummary, { expandIcon: _jsx(ArrowDropDown, {}), children: _jsx(Typography, { children: title }) }), _jsx(AccordionDetails, { children: _jsx(Stack, { spacing: 1, justifyContent: "stretch", sx: styles, children: _jsx(ObjectRenderer, { showParentKey: true, data: results }) }) })] }));
|
|
112
|
+
return (_jsxs(Accordion, { defaultExpanded: true, children: [_jsx(AccordionSummary, { expandIcon: _jsx(ArrowDropDown, {}), children: _jsx(Typography, { children: title }) }), _jsx(AccordionDetails, { children: _jsx(Stack, { spacing: 1, justifyContent: "stretch", sx: styles, children: _jsx(ObjectRenderer, { hit: hit, showParentKey: true, data: results }) }) })] }));
|
|
113
113
|
});
|
|
114
114
|
const HitDetails = ({ hit }) => {
|
|
115
115
|
const { t } = useTranslation();
|
|
@@ -119,7 +119,7 @@ const HitDetails = ({ hit }) => {
|
|
|
119
119
|
['howler', 'labels'].every(prefix => !key.startsWith(prefix)) &&
|
|
120
120
|
!isEmpty(value)), ([key]) => key.split('.')[0]), [hit]);
|
|
121
121
|
return (_jsxs(Stack, { spacing: 1, children: [_jsx(TextField, { value: query, onChange: event => setQuery(event.target.value), label: t('overview.search') }), Object.entries(groups).map(([section, entries]) => {
|
|
122
|
-
return (_jsx(Collapsible, { query: query, title: section
|
|
122
|
+
return (_jsx(Collapsible, { hit: hit, query: query, title: section
|
|
123
123
|
.split('_')
|
|
124
124
|
.map(word => capitalize(word))
|
|
125
125
|
.join(' '), data: Object.fromEntries(entries) }, section));
|
|
@@ -41,7 +41,7 @@ const DefaultOutline = ({ hit, fields, template, layout = HitLayout.NORMAL, read
|
|
|
41
41
|
if (!displayedData) {
|
|
42
42
|
return null;
|
|
43
43
|
}
|
|
44
|
-
return (_jsxs(React.Fragment, { children: [_jsx(Tooltip, { title: (config.indexes.hit[field]?.description ?? t('none')).split('\n')[0], children: _jsxs(Typography, { variant: layout !== HitLayout.COMFY ? 'caption' : 'body1', fontWeight: "bold", children: [field, ":"] }) }), _jsx(PluginTypography, { context: "outline", variant: layout !== HitLayout.COMFY ? 'caption' : 'body1', whiteSpace: "normal", sx: { width: '100%', wordBreak: 'break-all' }, value: displayedData, field: field, children: displayedData })] }, field));
|
|
44
|
+
return (_jsxs(React.Fragment, { children: [_jsx(Tooltip, { title: (config.indexes.hit[field]?.description ?? t('none')).split('\n')[0], children: _jsxs(Typography, { variant: layout !== HitLayout.COMFY ? 'caption' : 'body1', fontWeight: "bold", children: [field, ":"] }) }), _jsx(PluginTypography, { context: "outline", variant: layout !== HitLayout.COMFY ? 'caption' : 'body1', whiteSpace: "normal", sx: { width: '100%', wordBreak: 'break-all' }, value: displayedData, field: field, hit: hit, children: displayedData })] }, field));
|
|
45
45
|
})] }));
|
|
46
46
|
};
|
|
47
47
|
export default memo(DefaultOutline);
|
|
@@ -4,7 +4,7 @@ import { Stack, TableCell } from '@mui/material';
|
|
|
4
4
|
import PluginTypography from '@cccsaurora/howler-ui/components/elements/PluginTypography';
|
|
5
5
|
import { memo } from 'react';
|
|
6
6
|
import { useTranslation } from 'react-i18next';
|
|
7
|
-
const EnhancedCell = ({ value: rawValue, sx = {}, className, field }) => {
|
|
7
|
+
const EnhancedCell = ({ hit, value: rawValue, sx = {}, className, field }) => {
|
|
8
8
|
const { t } = useTranslation();
|
|
9
9
|
if (!rawValue) {
|
|
10
10
|
return _jsx(TableCell, { style: { borderBottom: 'none' }, children: t('none') });
|
|
@@ -13,6 +13,6 @@ const EnhancedCell = ({ value: rawValue, sx = {}, className, field }) => {
|
|
|
13
13
|
return (_jsx(TableCell, { sx: { borderBottom: 'none', borderRight: 'thin solid', borderRightColor: 'divider', fontSize: '0.8rem' }, children: _jsx(Stack, { direction: "row", className: className, spacing: 0.5, sx: [
|
|
14
14
|
{ display: 'flex', justifyContent: 'start', width: '100%', overflow: 'hidden' },
|
|
15
15
|
...(Array.isArray(sx) ? sx : [sx])
|
|
16
|
-
], children: values.map((value, index) => (_jsx(PluginTypography, { context: "table", sx: { fontSize: 'inherit', textOverflow: 'ellipsis' }, value: value, field: field, children: value }, value + index))) }) }));
|
|
16
|
+
], children: values.map((value, index) => (_jsx(PluginTypography, { context: "table", sx: { fontSize: 'inherit', textOverflow: 'ellipsis' }, value: value, field: field, hit: hit, children: value }, value + index))) }) }));
|
|
17
17
|
};
|
|
18
18
|
export default memo(EnhancedCell);
|
|
@@ -45,6 +45,6 @@ const HitRow = ({ hit, analyticIds, columns, columnWidths, collapseMainColumn, o
|
|
|
45
45
|
e.preventDefault();
|
|
46
46
|
e.stopPropagation();
|
|
47
47
|
setExpandRow(_expanded => !_expanded);
|
|
48
|
-
}, children: _jsx(KeyboardArrowUp, {}) }), _jsx(Collapse, { in: !collapseMainColumn, orientation: "horizontal", unmountOnExit: true, children: _jsxs(Stack, { direction: "row", spacing: 1, flexWrap: "nowrap", children: [_jsx(EscalationChip, { hit: hit, layout: HitLayout.DENSE, hideLabel: true }), _jsxs(Typography, { sx: { textWrap: 'nowrap', whiteSpace: 'nowrap', fontSize: 'inherit' }, children: [analyticIds[hit.howler.analytic] ? (_jsx(Link, { to: `/analytics/${analyticIds[hit.howler.analytic]}`, onClick: e => e.stopPropagation(), children: hit.howler.analytic })) : (hit.howler.analytic), hit.howler.detection && ': ', hit.howler.detection] }), hit.howler.assignment !== 'unassigned' && _jsx(Assigned, { hit: hit, layout: HitLayout.DENSE, hideLabel: true })] }) })] }) }), columns.map(col => (_jsx(EnhancedCell, { className: `col-${col.replaceAll('.', '-')}`, value: get(hit, col) ?? t('none'), sx: columnWidths[col] ? { width: columnWidths[col] } : { width: '220px', maxWidth: '300px' }, field: col }, col)))] }, hit.howler.id), _jsx(TableRow, { onClick: ev => onClick(ev, hit), children: _jsx(TableCell, { colSpan: columns.length + 2, style: { paddingBottom: 0, paddingTop: 0 }, children: _jsx(Collapse, { in: expandRow, unmountOnExit: true, children: _jsx(Box, { width: "100%", maxWidth: "1200px", margin: 1, children: _jsx(HitCard, { id: hit.howler.id, layout: HitLayout.NORMAL }) }) }) }) })] }));
|
|
48
|
+
}, children: _jsx(KeyboardArrowUp, {}) }), _jsx(Collapse, { in: !collapseMainColumn, orientation: "horizontal", unmountOnExit: true, children: _jsxs(Stack, { direction: "row", spacing: 1, flexWrap: "nowrap", children: [_jsx(EscalationChip, { hit: hit, layout: HitLayout.DENSE, hideLabel: true }), _jsxs(Typography, { sx: { textWrap: 'nowrap', whiteSpace: 'nowrap', fontSize: 'inherit' }, children: [analyticIds[hit.howler.analytic] ? (_jsx(Link, { to: `/analytics/${analyticIds[hit.howler.analytic]}`, onClick: e => e.stopPropagation(), children: hit.howler.analytic })) : (hit.howler.analytic), hit.howler.detection && ': ', hit.howler.detection] }), hit.howler.assignment !== 'unassigned' && _jsx(Assigned, { hit: hit, layout: HitLayout.DENSE, hideLabel: true })] }) })] }) }), columns.map(col => (_jsx(EnhancedCell, { hit: hit, className: `col-${col.replaceAll('.', '-')}`, value: get(hit, col) ?? t('none'), sx: columnWidths[col] ? { width: columnWidths[col] } : { width: '220px', maxWidth: '300px' }, field: col }, col)))] }, hit.howler.id), _jsx(TableRow, { onClick: ev => onClick(ev, hit), children: _jsx(TableCell, { colSpan: columns.length + 2, style: { paddingBottom: 0, paddingTop: 0 }, children: _jsx(Collapse, { in: expandRow, unmountOnExit: true, children: _jsx(Box, { width: "100%", maxWidth: "1200px", margin: 1, children: _jsx(HitCard, { id: hit.howler.id, layout: HitLayout.NORMAL }) }) }) }) })] }));
|
|
49
49
|
};
|
|
50
50
|
export default memo(HitRow);
|
package/index.js
CHANGED
|
@@ -3,8 +3,13 @@ import '@fontsource/roboto';
|
|
|
3
3
|
import App from '@cccsaurora/howler-ui/components/app/App';
|
|
4
4
|
import '@cccsaurora/howler-ui/i18n';
|
|
5
5
|
import 'index.css';
|
|
6
|
+
import howlerPluginStore from '@cccsaurora/howler-ui/plugins/store';
|
|
6
7
|
// import howlerPluginStore from '@cccsaurora/howler-ui/plugins/store';
|
|
7
8
|
import * as ReactDOM from 'react-dom/client';
|
|
8
9
|
// This is where you can inject UI plugins to modify Howler's interface.
|
|
9
10
|
// howlerPluginStore.install(new ExamplePlugin());
|
|
11
|
+
if (import.meta.env.VITE_ENABLE_CLUE === 'true') {
|
|
12
|
+
const cluePlugin = await import('plugins/clue');
|
|
13
|
+
howlerPluginStore.install(new cluePlugin.default());
|
|
14
|
+
}
|
|
10
15
|
ReactDOM.createRoot(document.getElementById('root')).render(_jsx(App, {}));
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import type { Notebook } from './Notebook';
|
|
2
1
|
import type { Comment } from './Comment';
|
|
2
|
+
import type { Notebook } from './Notebook';
|
|
3
3
|
import type { TriageSettings } from './TriageSettings';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* NOTE: This is an auto-generated file. Don't edit this manually.
|
|
7
7
|
*/
|
|
8
8
|
export interface Analytic {
|
|
9
|
-
notebooks?: Notebook[];
|
|
10
9
|
analytic_id?: string;
|
|
11
10
|
comment?: Comment[];
|
|
12
11
|
contributors?: string[];
|
|
13
12
|
description?: string;
|
|
14
13
|
detections?: string[];
|
|
15
14
|
name?: string;
|
|
15
|
+
notebooks?: Notebook[];
|
|
16
16
|
owner?: string;
|
|
17
17
|
rule?: string;
|
|
18
18
|
rule_crontab?: string;
|
|
@@ -48,8 +48,8 @@ export interface APILookups {
|
|
|
48
48
|
'mitigated'
|
|
49
49
|
];
|
|
50
50
|
transitions: { [index: string]: string[] };
|
|
51
|
-
tactics: { [index: string]: { key: string; name: string; url: string } };
|
|
52
51
|
techniques: { [index: string]: { key: string; name: string; url: string } };
|
|
52
|
+
tactics: { [index: string]: { key: string; name: string; url: string } };
|
|
53
53
|
icons: string[];
|
|
54
54
|
roles: ['admin', 'automation_advanced', 'automation_basic', 'user'];
|
|
55
55
|
}
|
|
@@ -81,6 +81,7 @@ export interface APIConfiguration {
|
|
|
81
81
|
};
|
|
82
82
|
mapping: APIMappings;
|
|
83
83
|
features: {
|
|
84
|
+
clue: boolean;
|
|
84
85
|
notebook: boolean;
|
|
85
86
|
[feature: string]: boolean;
|
|
86
87
|
};
|
|
@@ -4,6 +4,7 @@ import type { Aws } from './Aws';
|
|
|
4
4
|
import type { Azure } from './Azure';
|
|
5
5
|
import type { Cbs } from './Cbs';
|
|
6
6
|
import type { Cloud } from './Cloud';
|
|
7
|
+
import type { Clue } from './Clue';
|
|
7
8
|
import type { Container } from './Container';
|
|
8
9
|
import type { Destination } from './Destination';
|
|
9
10
|
import type { Dns } from './Dns';
|
|
@@ -41,30 +42,16 @@ import type { Vulnerability } from './Vulnerability';
|
|
|
41
42
|
export interface Hit {
|
|
42
43
|
agent?: Agent;
|
|
43
44
|
assemblyline?: Assemblyline;
|
|
44
|
-
autoruns_category?: string;
|
|
45
|
-
autoruns_display_name?: string;
|
|
46
|
-
autoruns_enabled?: number;
|
|
47
|
-
autoruns_flags?: string;
|
|
48
|
-
autoruns_last_runtime?: string;
|
|
49
|
-
autoruns_last_task_result?: string;
|
|
50
|
-
autoruns_location?: string;
|
|
51
|
-
autoruns_mod_time?: string;
|
|
52
|
-
autoruns_name?: string;
|
|
53
|
-
autoruns_scheduled_time?: string;
|
|
54
45
|
aws?: Aws;
|
|
55
46
|
azure?: Azure;
|
|
56
47
|
cbs?: Cbs;
|
|
57
|
-
client?: string;
|
|
58
|
-
client_id?: string;
|
|
59
48
|
cloud?: Cloud;
|
|
60
|
-
|
|
49
|
+
clue?: Clue;
|
|
61
50
|
container?: Container;
|
|
62
51
|
destination?: Destination;
|
|
63
52
|
dns?: Dns;
|
|
64
|
-
dns_servers?: string;
|
|
65
53
|
ecs?: Ecs;
|
|
66
54
|
email?: Email;
|
|
67
|
-
eml_paths?: string;
|
|
68
55
|
error?: Error;
|
|
69
56
|
event?: Event;
|
|
70
57
|
faas?: Faas;
|
|
@@ -74,8 +61,6 @@ export interface Hit {
|
|
|
74
61
|
host?: Host;
|
|
75
62
|
howler: Howler;
|
|
76
63
|
http?: Http;
|
|
77
|
-
incident_urls?: string;
|
|
78
|
-
indicator_summaries?: string;
|
|
79
64
|
interface?: Interface;
|
|
80
65
|
labels?: { [index: string]: string };
|
|
81
66
|
message?: string;
|
|
@@ -85,10 +70,7 @@ export interface Hit {
|
|
|
85
70
|
process?: Process;
|
|
86
71
|
registry?: Registry;
|
|
87
72
|
related?: Related;
|
|
88
|
-
retained_by?: string;
|
|
89
|
-
retention_url?: string;
|
|
90
73
|
rule?: Rule;
|
|
91
|
-
senders?: string;
|
|
92
74
|
server?: Server;
|
|
93
75
|
source?: Source;
|
|
94
76
|
tags?: string[];
|
package/package.json
CHANGED
|
@@ -12,54 +12,59 @@
|
|
|
12
12
|
]
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
+
"@cccsaurora/clue-ui": "1.1.0-dev.91",
|
|
15
16
|
"@dnd-kit/core": "^6.3.1",
|
|
16
17
|
"@dnd-kit/modifiers": "^7.0.0",
|
|
17
18
|
"@dnd-kit/sortable": "^8.0.0",
|
|
18
19
|
"@dnd-kit/utilities": "^3.2.2",
|
|
19
|
-
"@fontsource/roboto": "^5.2.
|
|
20
|
+
"@fontsource/roboto": "^5.2.9",
|
|
20
21
|
"@iconify/icons-logos": "^1.2.36",
|
|
21
22
|
"@iconify/icons-simple-icons": "^1.2.74",
|
|
22
23
|
"@iconify/react": "^4.1.1",
|
|
23
|
-
"@
|
|
24
|
+
"@jsonforms/core": "^3.7.0",
|
|
25
|
+
"@jsonforms/material-renderers": "^3.7.0",
|
|
26
|
+
"@jsonforms/react": "^3.7.0",
|
|
27
|
+
"@microlink/react-json-view": "^1.27.1",
|
|
24
28
|
"@monaco-editor/react": "^4.7.0",
|
|
25
29
|
"ajv": "^8.17.1",
|
|
26
30
|
"ajv-i18n": "^4.2.0",
|
|
27
|
-
"axios": "^1.
|
|
31
|
+
"axios": "^1.13.4",
|
|
28
32
|
"axios-retry": "^3.9.1",
|
|
29
|
-
"chart.js": "^4.5.
|
|
33
|
+
"chart.js": "^4.5.1",
|
|
30
34
|
"chartjs-adapter-dayjs-4": "^1.0.4",
|
|
31
35
|
"chartjs-plugin-zoom": "^2.2.0",
|
|
32
|
-
"dayjs": "^1.11.
|
|
33
|
-
"dompurify": "^3.
|
|
36
|
+
"dayjs": "^1.11.19",
|
|
37
|
+
"dompurify": "^3.3.1",
|
|
34
38
|
"flat": "^6.0.1",
|
|
35
39
|
"fuse.js": "^7.1.0",
|
|
36
40
|
"handlebars": "^4.7.8",
|
|
37
41
|
"handlebars-async-helpers": "^1.0.6",
|
|
38
42
|
"i18next": "^23.16.8",
|
|
39
43
|
"i18next-browser-languagedetector": "^7.2.2",
|
|
44
|
+
"json-schema": "^0.4.0",
|
|
40
45
|
"lodash-es": "^4.17.23",
|
|
41
46
|
"md5": "^2.3.0",
|
|
42
|
-
"mermaid": "^11.
|
|
47
|
+
"mermaid": "^11.12.2",
|
|
43
48
|
"monaco-editor": "^0.49.0",
|
|
44
49
|
"notistack": "^3.0.2",
|
|
45
50
|
"react": "^18.3.1",
|
|
46
|
-
"react-chartjs-2": "^5.3.
|
|
51
|
+
"react-chartjs-2": "^5.3.1",
|
|
47
52
|
"react-device-detect": "^2.2.3",
|
|
48
53
|
"react-dom": "^18.3.1",
|
|
49
54
|
"react-i18next": "^14.1.3",
|
|
50
|
-
"react-ipynb-renderer": "^2.
|
|
55
|
+
"react-ipynb-renderer": "^2.3.0",
|
|
51
56
|
"react-markdown": "^10.1.0",
|
|
52
57
|
"react-pluggable": "^0.4.3",
|
|
53
58
|
"react-resize-detector": "^9.1.1",
|
|
54
59
|
"react-router": "6.30.1",
|
|
55
60
|
"react-router-dom": "6.30.1",
|
|
56
|
-
"react-syntax-highlighter": "^15.6.
|
|
61
|
+
"react-syntax-highlighter": "^15.6.6",
|
|
57
62
|
"rehype-raw": "^7.0.0",
|
|
58
63
|
"remark": "^15.0.1",
|
|
59
64
|
"remark-gfm": "^4.0.1",
|
|
60
|
-
"scheduler": "^0.
|
|
65
|
+
"scheduler": "^0.27.0",
|
|
61
66
|
"unified": "^11.0.5",
|
|
62
|
-
"unist-util-visit": "^5.
|
|
67
|
+
"unist-util-visit": "^5.1.0",
|
|
63
68
|
"url-join": "^5.0.0",
|
|
64
69
|
"use-context-selector": "^2.0.0",
|
|
65
70
|
"uuid": "^9.0.1",
|
|
@@ -96,7 +101,7 @@
|
|
|
96
101
|
"internal-slot": "1.0.7"
|
|
97
102
|
},
|
|
98
103
|
"type": "module",
|
|
99
|
-
"version": "2.
|
|
104
|
+
"version": "2.17.0-dev.470",
|
|
100
105
|
"exports": {
|
|
101
106
|
"./i18n": "./i18n.js",
|
|
102
107
|
"./index.css": "./index.css",
|
|
@@ -251,6 +256,10 @@
|
|
|
251
256
|
"./commons/components/app/providers/*": "./commons/components/app/providers/*.js",
|
|
252
257
|
"./commons/components/display/hooks/*": "./commons/components/display/hooks/*.js",
|
|
253
258
|
"./commons/components/notification/elements/*": "./commons/components/notification/elements/*.js",
|
|
254
|
-
"./commons/components/notification/elements/item/*": "./commons/components/notification/elements/item/*.js"
|
|
259
|
+
"./commons/components/notification/elements/item/*": "./commons/components/notification/elements/item/*.js",
|
|
260
|
+
"./plugins/clue/*": "./plugins/clue/*.js",
|
|
261
|
+
"./plugins/clue": "./plugins/clue/index.js",
|
|
262
|
+
"./plugins/clue/locales/*": "./plugins/clue/locales/*.js",
|
|
263
|
+
"./plugins/clue/components/*": "./plugins/clue/components/*.js"
|
|
255
264
|
}
|
|
256
265
|
}
|
package/plugins/HowlerPlugin.js
CHANGED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { ClueProvider } from '@cccsaurora/clue-ui/hooks/ClueProvider';
|
|
3
|
+
import { ApiConfigContext } from '@cccsaurora/howler-ui/components/app/providers/ApiConfigProvider';
|
|
4
|
+
import { useContext } from 'react';
|
|
5
|
+
import { useTranslation } from 'react-i18next';
|
|
6
|
+
import { StorageKey } from '@cccsaurora/howler-ui/utils/constants';
|
|
7
|
+
import { getStored } from '@cccsaurora/howler-ui/utils/localStorage';
|
|
8
|
+
const Provider = ({ children }) => {
|
|
9
|
+
const { config } = useContext(ApiConfigContext);
|
|
10
|
+
const features = config.configuration?.features ?? {};
|
|
11
|
+
return (_jsx(ClueProvider, { baseURL: location.origin + '/api/v1/clue', getToken: () => getStored(StorageKey.APP_TOKEN), enabled: features.clue, publicIconify: location.origin.includes('localhost'), customIconify: location.origin.replace(/howler(-stg)?/, 'icons'), defaultTimeout: 5, i18next: useTranslation('clue'), chunkSize: 50, children: children }));
|
|
12
|
+
};
|
|
13
|
+
export default Provider;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import EnrichedChip from '@cccsaurora/clue-ui/components/EnrichedChip';
|
|
3
|
+
import { Chip } from '@mui/material';
|
|
4
|
+
import { memo } from 'react';
|
|
5
|
+
import { useType } from '../utils';
|
|
6
|
+
const ClueChip = ({ children, value, context, field, hit, ...props }) => {
|
|
7
|
+
const type = useType(hit, field, value);
|
|
8
|
+
if (!type) {
|
|
9
|
+
return _jsx(Chip, { ...props, children: children });
|
|
10
|
+
}
|
|
11
|
+
let enrichedProps = {
|
|
12
|
+
...props,
|
|
13
|
+
value
|
|
14
|
+
};
|
|
15
|
+
if (context === 'summary') {
|
|
16
|
+
enrichedProps = {
|
|
17
|
+
...enrichedProps,
|
|
18
|
+
sx: [
|
|
19
|
+
...(Array.isArray(enrichedProps.sx) ? enrichedProps.sx : [enrichedProps.sx]),
|
|
20
|
+
[{ height: '24px', '& .iconify': { fontSize: '1em' } }]
|
|
21
|
+
]
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
delete enrichedProps.label;
|
|
26
|
+
}
|
|
27
|
+
return _jsx(EnrichedChip, { ...enrichedProps, type: type, label: props.label });
|
|
28
|
+
};
|
|
29
|
+
export default memo(ClueChip);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useClueFetcherSelector } from '@cccsaurora/clue-ui/hooks/selectors';
|
|
3
|
+
import { Autocomplete, Divider, ListItemText, TextField, Typography } from '@mui/material';
|
|
4
|
+
import { ApiConfigContext } from '@cccsaurora/howler-ui/components/app/providers/ApiConfigProvider';
|
|
5
|
+
import uniq from 'lodash-es/uniq';
|
|
6
|
+
import { useContext, useState } from 'react';
|
|
7
|
+
import { useTranslation } from 'react-i18next';
|
|
8
|
+
const ClueLeadForm = ({ lead, metadata, update, updateMetadata }) => {
|
|
9
|
+
const { t } = useTranslation();
|
|
10
|
+
const { config } = useContext(ApiConfigContext);
|
|
11
|
+
const fetchers = useClueFetcherSelector(ctx => ctx.fetchers);
|
|
12
|
+
const [showCustom, setShowCustom] = useState(false);
|
|
13
|
+
return (_jsxs(_Fragment, { children: [_jsx(Divider, { orientation: "horizontal" }), _jsx(Autocomplete, { disabled: !lead, options: Object.keys(fetchers), renderInput: params => _jsx(TextField, { ...params, size: "small", label: t('route.dossiers.manager.clue') }), value: Object.keys(fetchers).includes(lead?.content) ? lead.content : null, onChange: (_ev, content) => update({ content, metadata: '{}' }), renderOption: ({ key, ...props }, option) => (_jsx(ListItemText, { ...props, sx: { flexDirection: 'column', alignItems: 'start !important' }, primary: _jsx("code", { children: option }), secondary: fetchers[option].description }, key)) }), _jsx(Autocomplete, { options: fetchers[lead?.content]?.supported_types ??
|
|
14
|
+
uniq(Object.values(fetchers).flatMap(fetcher => fetcher.supported_types)), renderInput: params => _jsx(TextField, { ...params, size: "small", label: t('route.dossiers.manager.clue.type') }), value: metadata?.type ?? null, onChange: (_ev, type) => updateMetadata({ type }) }), _jsx(Autocomplete, { options: ['custom', ...Object.keys(config.indexes.hit)], disabled: !metadata?.type, renderInput: params => _jsx(TextField, { ...params, size: "small", label: t('route.dossiers.manager.clue.value') }), getOptionLabel: opt => t(opt), value: metadata?.value ?? null, onChange: (_ev, value) => {
|
|
15
|
+
if (value === 'custom') {
|
|
16
|
+
setShowCustom(true);
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
setShowCustom(false);
|
|
20
|
+
updateMetadata({ value });
|
|
21
|
+
}
|
|
22
|
+
} }), showCustom && (_jsxs(_Fragment, { children: [_jsx(TextField, { size: "small", label: t('route.dossiers.manager.clue.value.custom'), value: metadata?.value ?? '', disabled: !metadata?.type, fullWidth: true, onChange: ev => updateMetadata({ value: ev.target.value }) }), _jsx(Typography, { variant: "caption", color: "text.secondary", sx: { mt: '0 !important' }, children: t('route.dossiers.manager.clue.value.description') })] }))] }));
|
|
23
|
+
};
|
|
24
|
+
export default ClueLeadForm;
|