@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.
Files changed (42) hide show
  1. package/components/elements/PluginChip.d.ts +2 -0
  2. package/components/elements/PluginChip.js +2 -1
  3. package/components/elements/PluginTypography.d.ts +4 -3
  4. package/components/elements/PluginTypography.js +4 -3
  5. package/components/elements/hit/HitBanner.js +2 -2
  6. package/components/elements/hit/HitDetails.js +9 -9
  7. package/components/elements/hit/outlines/DefaultOutline.js +1 -1
  8. package/components/routes/hits/search/grid/EnhancedCell.d.ts +2 -0
  9. package/components/routes/hits/search/grid/EnhancedCell.js +2 -2
  10. package/components/routes/hits/search/grid/HitRow.js +1 -1
  11. package/index.js +5 -0
  12. package/models/entities/generated/Analytic.d.ts +2 -2
  13. package/models/entities/generated/ApiType.d.ts +2 -1
  14. package/models/entities/generated/Clue.d.ts +8 -0
  15. package/models/entities/generated/Hit.d.ts +2 -20
  16. package/models/entities/generated/Labels.d.ts +1 -0
  17. package/models/entities/generated/Type.d.ts +7 -0
  18. package/package.json +23 -14
  19. package/plugins/HowlerPlugin.js +1 -0
  20. package/plugins/clue/Provider.d.ts +3 -0
  21. package/plugins/clue/Provider.js +13 -0
  22. package/plugins/clue/components/ClueChip.d.ts +3 -0
  23. package/plugins/clue/components/ClueChip.js +29 -0
  24. package/plugins/clue/components/ClueLeadForm.d.ts +4 -0
  25. package/plugins/clue/components/ClueLeadForm.js +24 -0
  26. package/plugins/clue/components/CluePivot.d.ts +3 -0
  27. package/plugins/clue/components/CluePivot.js +145 -0
  28. package/plugins/clue/components/CluePivotForm.d.ts +21 -0
  29. package/plugins/clue/components/CluePivotForm.js +270 -0
  30. package/plugins/clue/components/ClueTypography.d.ts +3 -0
  31. package/plugins/clue/components/ClueTypography.js +53 -0
  32. package/plugins/clue/helpers.d.ts +3 -0
  33. package/plugins/clue/helpers.js +196 -0
  34. package/plugins/clue/index.d.ts +21 -0
  35. package/plugins/clue/index.js +66 -0
  36. package/plugins/clue/locales/clue.en.json +8 -0
  37. package/plugins/clue/locales/clue.fr.json +8 -0
  38. package/plugins/clue/setup.d.ts +2 -0
  39. package/plugins/clue/setup.js +46 -0
  40. package/plugins/clue/utils.d.ts +2 -0
  41. package/plugins/clue/utils.js +19 -0
  42. 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 { type FC } from 'react';
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 PluginTypography: FC<PluginTypographyProps>;
9
- export default PluginTypography;
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);
@@ -1,5 +1,7 @@
1
1
  import { type SxProps } from '@mui/material';
2
+ import type { Hit } from '@cccsaurora/howler-ui/models/entities/generated/Hit';
2
3
  declare const _default: import("react").NamedExoticComponent<{
4
+ hit: Hit;
3
5
  value: string;
4
6
  sx?: SxProps;
5
7
  className: string;
@@ -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
  };
@@ -0,0 +1,8 @@
1
+ import type { Type } from './Type';
2
+
3
+ /**
4
+ * NOTE: This is an auto-generated file. Don't edit this manually.
5
+ */
6
+ export interface Clue {
7
+ types?: Type[];
8
+ }
@@ -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
- cluster_size?: number;
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[];
@@ -9,5 +9,6 @@ export interface Labels {
9
9
  mitigation?: string[];
10
10
  operation?: string[];
11
11
  threat?: string[];
12
+ tuning?: string[];
12
13
  victim?: string[];
13
14
  }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * NOTE: This is an auto-generated file. Don't edit this manually.
3
+ */
4
+ export interface Type {
5
+ field?: string;
6
+ type?: string;
7
+ }
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.6",
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
- "@microlink/react-json-view": "^1.26.2",
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.12.0",
31
+ "axios": "^1.13.4",
28
32
  "axios-retry": "^3.9.1",
29
- "chart.js": "^4.5.0",
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.13",
33
- "dompurify": "^3.2.6",
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.10.0",
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.0",
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.2.4",
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.1",
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.23.2",
65
+ "scheduler": "^0.27.0",
61
66
  "unified": "^11.0.5",
62
- "unist-util-visit": "^5.0.0",
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.16.5",
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
  }
@@ -133,6 +133,7 @@ class HowlerPlugin {
133
133
  if (isRoot) {
134
134
  if (breadcrumbs != null) {
135
135
  breadcrumbs = null;
136
+ // eslint-disable-next-line no-console
136
137
  console.warn(`Sitemap '${path}' with isRoot should not contain breadcrumbs and have been removed`);
137
138
  }
138
139
  }
@@ -0,0 +1,3 @@
1
+ import { type PropsWithChildren } from 'react';
2
+ declare const Provider: React.FC<PropsWithChildren<{}>>;
3
+ export default Provider;
@@ -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,3 @@
1
+ import type { PluginChipProps } from '@cccsaurora/howler-ui/components/elements/PluginChip';
2
+ declare const _default: import("react").NamedExoticComponent<PluginChipProps>;
3
+ export default _default;
@@ -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,4 @@
1
+ import type { LeadFormProps } from '@cccsaurora/howler-ui/components/routes/dossiers/LeadEditor';
2
+ import { type FC } from 'react';
3
+ declare const ClueLeadForm: FC<LeadFormProps>;
4
+ export default ClueLeadForm;
@@ -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;
@@ -0,0 +1,3 @@
1
+ import type { PivotLinkProps } from '@cccsaurora/howler-ui/components/elements/hit/related/PivotLink';
2
+ declare const _default: import("react").NamedExoticComponent<PivotLinkProps>;
3
+ export default _default;