@cccsaurora/howler-ui 2.17.0-dev.525 → 2.17.0-dev.533
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/api/index.d.ts +0 -2
- package/api/index.js +2 -4
- package/api/search/index.d.ts +1 -2
- package/api/search/index.js +1 -2
- package/commons/components/leftnav/LeftNavDrawer.js +1 -1
- package/components/app/App.js +0 -14
- package/components/app/providers/FavouritesProvider.js +2 -2
- package/components/elements/PluginTypography.d.ts +1 -2
- package/components/elements/PluginTypography.js +2 -3
- package/components/elements/UserList.d.ts +2 -5
- package/components/elements/UserList.js +5 -14
- package/components/elements/addons/search/phrase/Phrase.js +1 -1
- package/components/elements/display/HowlerCard.js +1 -1
- package/components/elements/display/Modal.js +0 -1
- package/components/elements/display/icons/BundleButton.d.ts +6 -0
- package/components/elements/display/icons/BundleButton.js +32 -0
- package/components/elements/hit/HitBanner.js +48 -27
- package/components/elements/{ObjectDetails.d.ts → hit/HitDetails.d.ts} +1 -2
- package/components/elements/{ObjectDetails.js → hit/HitDetails.js} +17 -17
- package/components/elements/hit/outlines/DefaultOutline.js +1 -1
- package/components/elements/view/ViewTitle.js +1 -1
- package/components/hooks/useHitActions.d.ts +1 -1
- package/components/hooks/useHitActions.js +2 -2
- package/components/hooks/useHitSelection.js +35 -1
- package/components/hooks/useMyPreferences.js +1 -10
- package/components/hooks/useMySitemap.js +1 -4
- package/components/hooks/useMyTheme.js +2 -9
- package/components/routes/action/view/ActionSearch.js +1 -1
- package/components/routes/action/view/Integrations.js +9 -1
- package/components/routes/action/view/markdown/integrations.en.md.js +1 -0
- package/components/routes/action/view/markdown/integrations.fr.md.js +1 -0
- package/components/routes/advanced/QueryBuilder.js +1 -1
- package/components/routes/analytics/AnalyticDetails.js +2 -2
- package/components/routes/analytics/AnalyticSearch.js +1 -1
- package/components/routes/help/ApiDocumentation.js +1 -1
- package/components/routes/help/BundleDocumentation.d.ts +3 -0
- package/components/routes/help/BundleDocumentation.js +12 -0
- package/components/routes/help/HitDocumentation.js +3 -1
- package/components/routes/help/markdown/en/bundles.md.js +1 -0
- package/components/routes/help/markdown/fr/bundles.md.js +1 -0
- package/components/routes/hits/search/BundleParentMenu.d.ts +6 -0
- package/components/routes/hits/search/BundleParentMenu.js +32 -0
- package/components/routes/hits/search/HitContextMenu.js +27 -4
- package/components/routes/hits/search/HitContextMenu.test.js +140 -0
- package/components/routes/hits/search/InformationPane.d.ts +0 -1
- package/components/routes/hits/search/InformationPane.js +28 -6
- package/components/routes/hits/search/SearchPane.js +5 -3
- package/components/routes/hits/search/ViewLink.js +1 -1
- package/components/routes/hits/search/grid/EnhancedCell.js +1 -1
- package/components/routes/hits/view/HitViewer.js +4 -3
- package/components/routes/home/ViewCard.js +1 -1
- package/components/{elements/MarkdownEditor.js → routes/overviews/OverviewEditor.js} +3 -3
- package/components/routes/overviews/OverviewViewer.js +2 -2
- package/locales/en/translation.json +396 -423
- package/locales/fr/translation.json +421 -445
- package/models/entities/generated/{ThreatEnrichment.d.ts → Enrichment.d.ts} +1 -1
- package/models/entities/generated/Howler.d.ts +4 -0
- package/models/entities/generated/Rule.d.ts +10 -2
- package/models/entities/generated/Threat.d.ts +2 -2
- package/package.json +4 -16
- package/plugins/clue/components/ClueTypography.js +2 -2
- package/plugins/clue/utils.d.ts +1 -2
- package/utils/constants.d.ts +3 -3
- package/api/search/case.d.ts +0 -4
- package/api/search/case.js +0 -8
- package/api/v2/case/index.d.ts +0 -6
- package/api/v2/case/index.js +0 -18
- package/api/v2/index.d.ts +0 -4
- package/api/v2/index.js +0 -6
- package/api/v2/search/facet.d.ts +0 -3
- package/api/v2/search/facet.js +0 -12
- package/api/v2/search/index.d.ts +0 -6
- package/api/v2/search/index.js +0 -18
- package/components/elements/hit/elements/AnalyticLink.d.ts +0 -8
- package/components/elements/hit/elements/AnalyticLink.js +0 -22
- package/components/routes/cases/CaseCard.d.ts +0 -8
- package/components/routes/cases/CaseCard.js +0 -34
- package/components/routes/cases/CaseViewer.d.ts +0 -2
- package/components/routes/cases/CaseViewer.js +0 -24
- package/components/routes/cases/Cases.d.ts +0 -2
- package/components/routes/cases/Cases.js +0 -101
- package/components/routes/cases/constants.d.ts +0 -5
- package/components/routes/cases/constants.js +0 -5
- package/components/routes/cases/detail/AlertPanel.d.ts +0 -6
- package/components/routes/cases/detail/AlertPanel.js +0 -32
- package/components/routes/cases/detail/CaseDashboard.d.ts +0 -7
- package/components/routes/cases/detail/CaseDashboard.js +0 -49
- package/components/routes/cases/detail/CaseDetails.d.ts +0 -6
- package/components/routes/cases/detail/CaseDetails.js +0 -61
- package/components/routes/cases/detail/CaseOverview.d.ts +0 -7
- package/components/routes/cases/detail/CaseOverview.js +0 -43
- package/components/routes/cases/detail/CaseSidebar.d.ts +0 -6
- package/components/routes/cases/detail/CaseSidebar.js +0 -36
- package/components/routes/cases/detail/CaseTask.d.ts +0 -11
- package/components/routes/cases/detail/CaseTask.js +0 -57
- package/components/routes/cases/detail/ItemPage.d.ts +0 -6
- package/components/routes/cases/detail/ItemPage.js +0 -93
- package/components/routes/cases/detail/RelatedCasePanel.d.ts +0 -6
- package/components/routes/cases/detail/RelatedCasePanel.js +0 -31
- package/components/routes/cases/detail/TaskPanel.d.ts +0 -7
- package/components/routes/cases/detail/TaskPanel.js +0 -52
- package/components/routes/cases/detail/Untitled-1.md.js +0 -1
- package/components/routes/cases/detail/aggregates/CaseAggregate.d.ts +0 -12
- package/components/routes/cases/detail/aggregates/CaseAggregate.js +0 -19
- package/components/routes/cases/detail/aggregates/SourceAggregate.d.ts +0 -6
- package/components/routes/cases/detail/aggregates/SourceAggregate.js +0 -27
- package/components/routes/cases/detail/sidebar/CaseFolder.d.ts +0 -12
- package/components/routes/cases/detail/sidebar/CaseFolder.js +0 -179
- package/components/routes/cases/detail/sidebar/types.d.ts +0 -3
- package/components/routes/cases/hooks/useCase.d.ts +0 -13
- package/components/routes/cases/hooks/useCase.js +0 -38
- package/components/routes/cases/modals/ResolveModal.d.ts +0 -7
- package/components/routes/cases/modals/ResolveModal.js +0 -56
- package/components/routes/observables/ObservableViewer.d.ts +0 -7
- package/components/routes/observables/ObservableViewer.js +0 -27
- package/models/entities/generated/AttachmentsFile.d.ts +0 -12
- package/models/entities/generated/Case.d.ts +0 -28
- package/models/entities/generated/DestinationOriginal.d.ts +0 -19
- package/models/entities/generated/EmailAttachment.d.ts +0 -8
- package/models/entities/generated/EmailParent.d.ts +0 -19
- package/models/entities/generated/Enrichments.d.ts +0 -7
- package/models/entities/generated/EnrichmentsIndicator.d.ts +0 -21
- package/models/entities/generated/HttpResponse.d.ts +0 -11
- package/models/entities/generated/Item.d.ts +0 -9
- package/models/entities/generated/Observable.d.ts +0 -84
- package/models/entities/generated/ObservableCloud.d.ts +0 -20
- package/models/entities/generated/ObservableDestination.d.ts +0 -23
- package/models/entities/generated/ObservableEmail.d.ts +0 -30
- package/models/entities/generated/ObservableFile.d.ts +0 -36
- package/models/entities/generated/ObservableHowler.d.ts +0 -44
- package/models/entities/generated/ObservableHttp.d.ts +0 -11
- package/models/entities/generated/ObservableObserver.d.ts +0 -21
- package/models/entities/generated/ObservableOrganization.d.ts +0 -7
- package/models/entities/generated/ObservableProcess.d.ts +0 -34
- package/models/entities/generated/ObservableSource.d.ts +0 -23
- package/models/entities/generated/ObservableThreat.d.ts +0 -21
- package/models/entities/generated/ObservableTls.d.ts +0 -12
- package/models/entities/generated/ObserverIngress.d.ts +0 -9
- package/models/entities/generated/Task.d.ts +0 -10
- /package/components/{elements/MarkdownEditor.d.ts → routes/overviews/OverviewEditor.d.ts} +0 -0
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { Article, BookRounded, CheckCircle, ChevronRight, Folder as FolderIcon, Lightbulb, Link as LinkIcon, TableChart, Visibility } from '@mui/icons-material';
|
|
3
|
-
import { Skeleton, Stack, Typography, useTheme } from '@mui/material';
|
|
4
|
-
import api from '@cccsaurora/howler-ui/api';
|
|
5
|
-
import useMyApi from '@cccsaurora/howler-ui/components/hooks/useMyApi';
|
|
6
|
-
import { get, last, omit, set } from 'lodash-es';
|
|
7
|
-
import { useEffect, useMemo, useState } from 'react';
|
|
8
|
-
import { Link, useLocation } from 'react-router-dom';
|
|
9
|
-
import { ESCALATION_COLORS } from '@cccsaurora/howler-ui/utils/constants';
|
|
10
|
-
const buildTree = (items = []) => {
|
|
11
|
-
// Root tree node stores direct children in `leaves` and nested folders as object keys.
|
|
12
|
-
const tree = { leaves: [] };
|
|
13
|
-
items.forEach(item => {
|
|
14
|
-
// Ignore items that cannot be placed in the folder structure.
|
|
15
|
-
if (!item?.path) {
|
|
16
|
-
return;
|
|
17
|
-
}
|
|
18
|
-
// Split path into folder segments + item name, then remove the item name.
|
|
19
|
-
const parts = item.path.split('/');
|
|
20
|
-
parts.pop();
|
|
21
|
-
if (parts.length > 0) {
|
|
22
|
-
// Use dot notation so lodash `get/set` can address nested folder objects.
|
|
23
|
-
const key = parts.join('.');
|
|
24
|
-
const size = get(tree, key)?.leaves?.length || 0;
|
|
25
|
-
// Append this item to the folder's `leaves` array.
|
|
26
|
-
set(tree, `${key}.leaves.${size}`, item);
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
// Items without parent folders are top-level leaves.
|
|
30
|
-
tree.leaves.push(item);
|
|
31
|
-
});
|
|
32
|
-
return tree;
|
|
33
|
-
};
|
|
34
|
-
const CaseFolder = ({ case: _case, folder, name, step = -1, rootCaseId, pathPrefix = '' }) => {
|
|
35
|
-
const theme = useTheme();
|
|
36
|
-
const location = useLocation();
|
|
37
|
-
const { dispatchApi } = useMyApi();
|
|
38
|
-
const [open, setOpen] = useState(true);
|
|
39
|
-
const [openCases, setOpenCases] = useState({});
|
|
40
|
-
const [loadingCases, setLoadingCases] = useState({});
|
|
41
|
-
const [nestedCases, setNestedCases] = useState({});
|
|
42
|
-
const [hitMetadata, setHitMetadata] = useState({});
|
|
43
|
-
const tree = useMemo(() => folder || buildTree(_case?.items), [folder, _case?.items]);
|
|
44
|
-
const currentRootCaseId = rootCaseId || _case?.case_id;
|
|
45
|
-
// Metadata for hit-type items
|
|
46
|
-
useEffect(() => {
|
|
47
|
-
const ids = tree.leaves?.filter(leaf => leaf.type?.toLowerCase() === 'hit').map(leaf => leaf.id);
|
|
48
|
-
if (!ids || ids.length < 1) {
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
dispatchApi(api.search.hit.post({ query: `howler.id:(${ids.join(' OR ')})` }), { throwError: false }).then(result => {
|
|
52
|
-
if (result?.items?.length < 1) {
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
setHitMetadata(Object.fromEntries(result.items.map(hit => [hit.howler.id, hit.howler])));
|
|
56
|
-
});
|
|
57
|
-
}, [tree.leaves, dispatchApi]);
|
|
58
|
-
const getIconColor = (itemType, itemKey, leafId) => {
|
|
59
|
-
if (itemType === 'hit' && leafId) {
|
|
60
|
-
const meta = hitMetadata[leafId];
|
|
61
|
-
if (meta?.escalation && ESCALATION_COLORS[meta.escalation]) {
|
|
62
|
-
return ESCALATION_COLORS[meta.escalation];
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
if (itemType === 'case' && itemKey) {
|
|
66
|
-
const caseData = nestedCases[itemKey];
|
|
67
|
-
if (caseData?.escalation && ESCALATION_COLORS[caseData.escalation]) {
|
|
68
|
-
return ESCALATION_COLORS[caseData.escalation];
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
return 'default';
|
|
72
|
-
};
|
|
73
|
-
const getItemColor = (itemType, itemKey, leafId) => {
|
|
74
|
-
if (itemType === 'hit' && leafId) {
|
|
75
|
-
const meta = hitMetadata[leafId];
|
|
76
|
-
if (meta?.escalation && ESCALATION_COLORS[meta.escalation]) {
|
|
77
|
-
return `${ESCALATION_COLORS[meta.escalation]}.light`;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
if (itemType === 'case' && itemKey) {
|
|
81
|
-
const caseData = nestedCases[itemKey];
|
|
82
|
-
if (caseData?.escalation && ESCALATION_COLORS[caseData.escalation]) {
|
|
83
|
-
return `${ESCALATION_COLORS[caseData.escalation]}.light`;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
return 'text.secondary';
|
|
87
|
-
};
|
|
88
|
-
const toggleCase = (item, itemKey) => {
|
|
89
|
-
// Use the fully-qualified path key when available so nested case toggles are unique.
|
|
90
|
-
const resolvedItemKey = itemKey || item.path || item.id;
|
|
91
|
-
if (!resolvedItemKey) {
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
// Toggle expand/collapse state for this case node.
|
|
95
|
-
const shouldOpen = !openCases[resolvedItemKey];
|
|
96
|
-
setOpenCases(current => ({ ...current, [resolvedItemKey]: shouldOpen }));
|
|
97
|
-
// Only fetch when opening, with a valid case id, and when no fetch/data is already in-flight/cached.
|
|
98
|
-
if (!shouldOpen || !item.id || nestedCases[resolvedItemKey] || loadingCases[resolvedItemKey]) {
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
setLoadingCases(current => ({ ...current, [resolvedItemKey]: true }));
|
|
102
|
-
// Lazy-load the nested case content and cache it by the same unique key.
|
|
103
|
-
dispatchApi(api.v2.case.get(item.id), { throwError: false })
|
|
104
|
-
.then(caseResponse => {
|
|
105
|
-
if (!caseResponse) {
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
setNestedCases(current => ({ ...current, [resolvedItemKey]: caseResponse }));
|
|
109
|
-
})
|
|
110
|
-
.finally(() => {
|
|
111
|
-
setLoadingCases(current => ({ ...current, [resolvedItemKey]: false }));
|
|
112
|
-
});
|
|
113
|
-
};
|
|
114
|
-
return (_jsxs(Stack, { sx: { overflow: 'visible' }, children: [name && (_jsxs(Stack, { direction: "row", pl: step * 1.5, py: 0.25, sx: {
|
|
115
|
-
cursor: 'pointer',
|
|
116
|
-
transition: theme.transitions.create('background', { duration: 50 }),
|
|
117
|
-
background: 'transparent',
|
|
118
|
-
'&:hover': {
|
|
119
|
-
background: theme.palette.grey[800]
|
|
120
|
-
}
|
|
121
|
-
}, onClick: () => setOpen(_open => !_open), children: [_jsx(ChevronRight, { fontSize: "small", color: "disabled", sx: [
|
|
122
|
-
{ transition: theme.transitions.create('transform', { duration: 100 }), transform: 'rotate(0deg)' },
|
|
123
|
-
open && { transform: 'rotate(90deg)' }
|
|
124
|
-
] }), _jsx(FolderIcon, { fontSize: "small", color: "disabled" }), _jsx(Typography, { variant: "caption", color: "textSecondary", sx: { userSelect: 'none', pl: 0.5, textWrap: 'nowrap' }, children: name })] })), open && (_jsxs(_Fragment, { children: [Object.entries(omit(tree, 'leaves')).map(([path, subfolder]) => (_jsx(CaseFolder, { name: path, case: _case, folder: subfolder, step: step + 1, rootCaseId: currentRootCaseId, pathPrefix: pathPrefix }, `${_case?.case_id}-${path}`))), tree.leaves?.map(leaf => {
|
|
125
|
-
const itemType = leaf.type?.toLowerCase();
|
|
126
|
-
const isCase = itemType === 'case';
|
|
127
|
-
const fullRelativePath = [pathPrefix, leaf.path].filter(Boolean).join('/');
|
|
128
|
-
const itemKey = fullRelativePath || leaf.id;
|
|
129
|
-
const isCaseOpen = !!(itemKey && openCases[itemKey]);
|
|
130
|
-
const isCaseLoading = !!(itemKey && loadingCases[itemKey]);
|
|
131
|
-
const nestedCase = itemKey ? nestedCases[itemKey] : null;
|
|
132
|
-
const itemPath = fullRelativePath
|
|
133
|
-
? `/cases/${currentRootCaseId}/${fullRelativePath}`
|
|
134
|
-
: `/cases/${currentRootCaseId}`;
|
|
135
|
-
const getIconForType = () => {
|
|
136
|
-
const iconColor = getIconColor(itemType, itemKey, leaf.id);
|
|
137
|
-
switch (itemType) {
|
|
138
|
-
case 'case':
|
|
139
|
-
return _jsx(BookRounded, { fontSize: "small", color: iconColor });
|
|
140
|
-
case 'observable':
|
|
141
|
-
return _jsx(Visibility, { fontSize: "small", color: iconColor });
|
|
142
|
-
case 'hit':
|
|
143
|
-
return _jsx(CheckCircle, { fontSize: "small", color: iconColor });
|
|
144
|
-
case 'table':
|
|
145
|
-
return _jsx(TableChart, { fontSize: "small", color: iconColor });
|
|
146
|
-
case 'lead':
|
|
147
|
-
return _jsx(Lightbulb, { fontSize: "small", color: iconColor });
|
|
148
|
-
case 'reference':
|
|
149
|
-
return _jsx(LinkIcon, { fontSize: "small", color: iconColor });
|
|
150
|
-
default:
|
|
151
|
-
return _jsx(Article, { fontSize: "small", color: iconColor });
|
|
152
|
-
}
|
|
153
|
-
};
|
|
154
|
-
const leafColor = getItemColor(itemType, itemKey, leaf.id);
|
|
155
|
-
return (_jsxs(Stack, { children: [_jsxs(Stack, { direction: "row", pl: step * 1.5 + 1, py: 0.25, sx: [
|
|
156
|
-
{
|
|
157
|
-
cursor: 'pointer',
|
|
158
|
-
overflow: 'visible',
|
|
159
|
-
color: `${theme.palette.text.secondary} !important`,
|
|
160
|
-
textDecoration: 'none',
|
|
161
|
-
transition: theme.transitions.create('background', { duration: 100 }),
|
|
162
|
-
background: 'transparent',
|
|
163
|
-
'&:hover': {
|
|
164
|
-
background: theme.palette.grey[800]
|
|
165
|
-
}
|
|
166
|
-
},
|
|
167
|
-
decodeURIComponent(location.pathname) === itemPath && {
|
|
168
|
-
background: theme.palette.grey[800]
|
|
169
|
-
}
|
|
170
|
-
], onClick: () => isCase && toggleCase(leaf, itemKey), component: Link, to: itemPath, children: [_jsx(ChevronRight, { fontSize: "small", sx: [
|
|
171
|
-
!isCase && { opacity: 0 },
|
|
172
|
-
isCase && {
|
|
173
|
-
transition: theme.transitions.create('transform', { duration: 100 }),
|
|
174
|
-
transform: isCaseOpen ? 'rotate(90deg)' : 'rotate(0deg)'
|
|
175
|
-
}
|
|
176
|
-
] }), getIconForType(), _jsx(Typography, { variant: "caption", color: leafColor, sx: { userSelect: 'none', pl: 0.5, textWrap: 'nowrap' }, children: last(leaf.path?.split('/') || []) })] }), isCase && isCaseOpen && isCaseLoading && (_jsx(Stack, { pl: step * 1.5 + 4, py: 0.25, children: _jsx(Skeleton, { width: 140, height: 16 }) })), isCase && isCaseOpen && nestedCase && (_jsx(CaseFolder, { case: nestedCase, step: step + 1, rootCaseId: currentRootCaseId, pathPrefix: fullRelativePath }))] }, `${_case?.case_id}-${leaf.id}-${leaf.path}`));
|
|
177
|
-
})] }))] }));
|
|
178
|
-
};
|
|
179
|
-
export default CaseFolder;
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import type { Case } from '@cccsaurora/howler-ui/models/entities/generated/Case';
|
|
2
|
-
interface CaseArguments {
|
|
3
|
-
case?: Case;
|
|
4
|
-
caseId?: string;
|
|
5
|
-
}
|
|
6
|
-
interface CaseResult {
|
|
7
|
-
case: Case;
|
|
8
|
-
updateCase: (update: Partial<Case>) => Promise<void>;
|
|
9
|
-
loading: boolean;
|
|
10
|
-
missing: boolean;
|
|
11
|
-
}
|
|
12
|
-
declare const useCase: (args: CaseArguments) => CaseResult;
|
|
13
|
-
export default useCase;
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import api from '@cccsaurora/howler-ui/api';
|
|
2
|
-
import useMyApi from '@cccsaurora/howler-ui/components/hooks/useMyApi';
|
|
3
|
-
import { useCallback, useEffect, useState } from 'react';
|
|
4
|
-
const useCase = ({ caseId, case: providedCase }) => {
|
|
5
|
-
const { dispatchApi } = useMyApi();
|
|
6
|
-
const [loading, setLoading] = useState(false);
|
|
7
|
-
const [missing, setMissing] = useState(false);
|
|
8
|
-
const [_case, setCase] = useState(providedCase);
|
|
9
|
-
useEffect(() => {
|
|
10
|
-
if (providedCase) {
|
|
11
|
-
setCase(providedCase);
|
|
12
|
-
}
|
|
13
|
-
}, [providedCase]);
|
|
14
|
-
useEffect(() => {
|
|
15
|
-
if (caseId) {
|
|
16
|
-
setLoading(true);
|
|
17
|
-
dispatchApi(api.v2.case.get(caseId), { throwError: false })
|
|
18
|
-
.then(setCase)
|
|
19
|
-
.finally(() => setLoading(false));
|
|
20
|
-
}
|
|
21
|
-
}, [caseId, dispatchApi]);
|
|
22
|
-
const updateCase = useCallback(async (_updatedCase) => {
|
|
23
|
-
if (!_case?.case_id) {
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
try {
|
|
27
|
-
setCase(await dispatchApi(api.v2.case.put(_case.case_id, _updatedCase)));
|
|
28
|
-
}
|
|
29
|
-
catch (e) {
|
|
30
|
-
setMissing(true);
|
|
31
|
-
}
|
|
32
|
-
finally {
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
}, [_case?.case_id, dispatchApi]);
|
|
36
|
-
return { case: _case, updateCase, loading, missing };
|
|
37
|
-
};
|
|
38
|
-
export default useCase;
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { OpenInNew } from '@mui/icons-material';
|
|
3
|
-
import { Autocomplete, Box, Button, Card, Chip, Divider, IconButton, LinearProgress, Skeleton, Stack, TextField, Typography } from '@mui/material';
|
|
4
|
-
import api from '@cccsaurora/howler-ui/api';
|
|
5
|
-
import { ApiConfigContext } from '@cccsaurora/howler-ui/components/app/providers/ApiConfigProvider';
|
|
6
|
-
import { ModalContext } from '@cccsaurora/howler-ui/components/app/providers/ModalProvider';
|
|
7
|
-
import AnalyticLink from '@cccsaurora/howler-ui/components/elements/hit/elements/AnalyticLink';
|
|
8
|
-
import EscalationChip from '@cccsaurora/howler-ui/components/elements/hit/elements/EscalationChip';
|
|
9
|
-
import { HitLayout } from '@cccsaurora/howler-ui/components/elements/hit/HitLayout';
|
|
10
|
-
import useHitActions from '@cccsaurora/howler-ui/components/hooks/useHitActions';
|
|
11
|
-
import useMyApi from '@cccsaurora/howler-ui/components/hooks/useMyApi';
|
|
12
|
-
import { uniq } from 'lodash-es';
|
|
13
|
-
import { useContext, useEffect, useMemo, useState } from 'react';
|
|
14
|
-
import { useTranslation } from 'react-i18next';
|
|
15
|
-
import { Link } from 'react-router-dom';
|
|
16
|
-
import useCase from '../hooks/useCase';
|
|
17
|
-
const ResolveModal = ({ case: _case, onConfirm }) => {
|
|
18
|
-
const { t } = useTranslation();
|
|
19
|
-
const { dispatchApi } = useMyApi();
|
|
20
|
-
const { close } = useContext(ModalContext);
|
|
21
|
-
const { config } = useContext(ApiConfigContext);
|
|
22
|
-
const { updateCase } = useCase({ case: _case });
|
|
23
|
-
const [loading, setLoading] = useState(true);
|
|
24
|
-
const [rationale, setRationale] = useState('');
|
|
25
|
-
const [assessment, setAssessment] = useState(null);
|
|
26
|
-
const [hits, setHits] = useState([]);
|
|
27
|
-
const hitIds = useMemo(() => uniq((_case?.items ?? []).filter(item => item.type === 'hit').map(item => item.id)), [_case?.items]);
|
|
28
|
-
const { assess } = useHitActions(hits);
|
|
29
|
-
useEffect(() => {
|
|
30
|
-
(async () => {
|
|
31
|
-
try {
|
|
32
|
-
const result = await dispatchApi(api.search.hit.post({ query: `howler.id:(${hitIds.join(' OR ')}) AND -howler.status:resolved` }));
|
|
33
|
-
setHits(result.items);
|
|
34
|
-
}
|
|
35
|
-
finally {
|
|
36
|
-
setLoading(false);
|
|
37
|
-
}
|
|
38
|
-
})();
|
|
39
|
-
}, [dispatchApi, hitIds]);
|
|
40
|
-
const handleConfirm = async () => {
|
|
41
|
-
setLoading(true);
|
|
42
|
-
try {
|
|
43
|
-
await assess(assessment, true, rationale);
|
|
44
|
-
await updateCase({ status: 'resolved' });
|
|
45
|
-
onConfirm();
|
|
46
|
-
close();
|
|
47
|
-
}
|
|
48
|
-
finally {
|
|
49
|
-
setLoading(false);
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
|
-
return (_jsxs(Stack, { spacing: 2, p: 2, alignItems: "start", sx: { minWidth: 'min(1000px, 60vw)', maxHeight: '100%', height: '100%' }, children: [_jsx(Typography, { variant: "h4", children: t('modal.cases.resolve') }), _jsx(Typography, { children: t('modal.cases.resolve.description') }), _jsxs(Stack, { spacing: 1, overflow: "auto", width: "100%", flex: 1, children: [_jsxs(Stack, { direction: "row", spacing: 1, children: [_jsx(Box, { flex: 1, children: _jsx(TextField, { size: "small", fullWidth: true, placeholder: t('modal.rationale.label'), value: rationale, onChange: ev => setRationale(ev.target.value) }) }), _jsx(Box, { flex: 1, children: _jsx(Autocomplete, { size: "small", value: assessment, onChange: (_ev, _assessment) => setAssessment(_assessment), options: config.lookups['howler.assessment'], disablePortal: true, renderInput: params => (_jsx(TextField, { ...params, placeholder: t('hit.details.actions.assessment'), fullWidth: true })) }) })] }), _jsxs(Stack, { position: "relative", children: [_jsx(Divider, {}), _jsx(LinearProgress, { sx: { opacity: +loading } })] }), loading
|
|
53
|
-
? hitIds.map(id => _jsx(Skeleton, { variant: "rounded", height: "40px", width: "100%" }, id))
|
|
54
|
-
: hits.map(hit => (_jsx(Card, { sx: { p: 1, flexShrink: 0 }, children: _jsxs(Stack, { direction: "row", alignItems: "center", spacing: 1, width: "100%", children: [_jsx(AnalyticLink, { hit: hit, compressed: true, alignSelf: "center" }), _jsx(EscalationChip, { hit: hit, layout: HitLayout.DENSE }), _jsx(Chip, { sx: { width: 'fit-content', display: 'inline-flex' }, label: hit.howler.status, size: "small", color: "primary" }), _jsx("div", { style: { flex: 1 } }), _jsx(IconButton, { size: "small", component: Link, to: `/hits/${hit.howler.id}`, children: _jsx(OpenInNew, { fontSize: "small" }) })] }) }, hit.howler.id)))] }), _jsxs(Stack, { direction: "row", spacing: 1, alignSelf: "end", children: [_jsx(Button, { variant: "outlined", color: "error", onClick: close, children: t('cancel') }), _jsx(Button, { variant: "outlined", color: "success", disabled: loading || !assessment || !rationale, onClick: handleConfirm, children: t('confirm') })] })] }));
|
|
55
|
-
};
|
|
56
|
-
export default ResolveModal;
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { Box, Skeleton } from '@mui/material';
|
|
3
|
-
import api from '@cccsaurora/howler-ui/api';
|
|
4
|
-
import ObjectDetails from '@cccsaurora/howler-ui/components/elements/ObjectDetails';
|
|
5
|
-
import useMyApi from '@cccsaurora/howler-ui/components/hooks/useMyApi';
|
|
6
|
-
import { useEffect, useState } from 'react';
|
|
7
|
-
const ObservableViewer = ({ observable: provided, observableId }) => {
|
|
8
|
-
const { dispatchApi } = useMyApi();
|
|
9
|
-
const [observable, setObservable] = useState(null);
|
|
10
|
-
useEffect(() => {
|
|
11
|
-
if (provided) {
|
|
12
|
-
setObservable(provided);
|
|
13
|
-
}
|
|
14
|
-
}, [provided]);
|
|
15
|
-
useEffect(() => {
|
|
16
|
-
if (observableId) {
|
|
17
|
-
dispatchApi(api.v2.search.post('observable', { query: `howler.id:${observableId}`, rows: 1 }), {
|
|
18
|
-
throwError: false
|
|
19
|
-
}).then(res => setObservable(res.items[0]));
|
|
20
|
-
}
|
|
21
|
-
}, [dispatchApi, observableId]);
|
|
22
|
-
if (!observable) {
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
return _jsx(Box, { p: 1, children: observable ? _jsx(ObjectDetails, { obj: observable }) : _jsx(Skeleton, { height: 120 }) });
|
|
26
|
-
};
|
|
27
|
-
export default ObservableViewer;
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import type { FileHash } from './FileHash';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* NOTE: This is an auto-generated file. Don't edit this manually.
|
|
5
|
-
*/
|
|
6
|
-
export interface AttachmentsFile {
|
|
7
|
-
extension?: string;
|
|
8
|
-
hash?: FileHash;
|
|
9
|
-
mime_type?: string;
|
|
10
|
-
name?: string;
|
|
11
|
-
size?: number;
|
|
12
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import type { Enrichments } from './Enrichments';
|
|
2
|
-
import type { Item } from './Item';
|
|
3
|
-
import type { Rule } from './Rule';
|
|
4
|
-
import type { Task } from './Task';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* NOTE: This is an auto-generated file. Don't edit this manually.
|
|
8
|
-
*/
|
|
9
|
-
export interface Case {
|
|
10
|
-
case_id?: string;
|
|
11
|
-
created?: string;
|
|
12
|
-
end?: string;
|
|
13
|
-
enrichments?: Enrichments;
|
|
14
|
-
escalation?: string;
|
|
15
|
-
indicators?: string[];
|
|
16
|
-
items?: Item[];
|
|
17
|
-
overview?: string;
|
|
18
|
-
participants?: string[];
|
|
19
|
-
rules?: Rule[];
|
|
20
|
-
status?: string;
|
|
21
|
-
start?: string;
|
|
22
|
-
summary?: string;
|
|
23
|
-
targets?: string[];
|
|
24
|
-
tasks?: Task[];
|
|
25
|
-
threats?: string[];
|
|
26
|
-
title?: string;
|
|
27
|
-
updated?: string;
|
|
28
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import type { AutonomousSystems } from './AutonomousSystems';
|
|
2
|
-
import type { Geo } from './Geo';
|
|
3
|
-
import type { Nat } from './Nat';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* NOTE: This is an auto-generated file. Don't edit this manually.
|
|
7
|
-
*/
|
|
8
|
-
export interface DestinationOriginal {
|
|
9
|
-
address?: string;
|
|
10
|
-
autonomous_systems?: AutonomousSystems;
|
|
11
|
-
bytes?: number;
|
|
12
|
-
domain?: string;
|
|
13
|
-
geo?: Geo;
|
|
14
|
-
ip?: string;
|
|
15
|
-
mac?: string;
|
|
16
|
-
nat?: Nat;
|
|
17
|
-
packets?: number;
|
|
18
|
-
port?: number;
|
|
19
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import type { Bcc } from './Bcc';
|
|
2
|
-
import type { Cc } from './Cc';
|
|
3
|
-
import type { From } from './From';
|
|
4
|
-
import type { To } from './To';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* NOTE: This is an auto-generated file. Don't edit this manually.
|
|
8
|
-
*/
|
|
9
|
-
export interface EmailParent {
|
|
10
|
-
bcc?: Bcc;
|
|
11
|
-
cc?: Cc;
|
|
12
|
-
destination?: string;
|
|
13
|
-
from?: From;
|
|
14
|
-
message_id?: string;
|
|
15
|
-
origination_timestamp?: string;
|
|
16
|
-
source?: string;
|
|
17
|
-
subject?: string;
|
|
18
|
-
to?: To;
|
|
19
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import type { IndicatorEmail } from './IndicatorEmail';
|
|
2
|
-
import type { IndicatorFile } from './IndicatorFile';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* NOTE: This is an auto-generated file. Don't edit this manually.
|
|
6
|
-
*/
|
|
7
|
-
export interface EnrichmentsIndicator {
|
|
8
|
-
confidence?: string;
|
|
9
|
-
description?: string;
|
|
10
|
-
email?: IndicatorEmail;
|
|
11
|
-
file?: IndicatorFile;
|
|
12
|
-
first_seen?: string;
|
|
13
|
-
ip?: string;
|
|
14
|
-
last_seen?: string;
|
|
15
|
-
port?: number;
|
|
16
|
-
provider?: string;
|
|
17
|
-
reference?: string;
|
|
18
|
-
scanner_stats?: number;
|
|
19
|
-
sightings?: number;
|
|
20
|
-
type?: string;
|
|
21
|
-
}
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import type { Agent } from './Agent';
|
|
2
|
-
import type { Assemblyline } from './Assemblyline';
|
|
3
|
-
import type { Aws } from './Aws';
|
|
4
|
-
import type { Azure } from './Azure';
|
|
5
|
-
import type { Cbs } from './Cbs';
|
|
6
|
-
import type { Clue } from './Clue';
|
|
7
|
-
import type { Container } from './Container';
|
|
8
|
-
import type { Dns } from './Dns';
|
|
9
|
-
import type { Ecs } from './Ecs';
|
|
10
|
-
import type { Error } from './Error';
|
|
11
|
-
import type { Event } from './Event';
|
|
12
|
-
import type { Faas } from './Faas';
|
|
13
|
-
import type { Gcp } from './Gcp';
|
|
14
|
-
import type { Group } from './Group';
|
|
15
|
-
import type { Host } from './Host';
|
|
16
|
-
import type { Interface } from './Interface';
|
|
17
|
-
import type { Network } from './Network';
|
|
18
|
-
import type { ObservableCloud } from './ObservableCloud';
|
|
19
|
-
import type { ObservableDestination } from './ObservableDestination';
|
|
20
|
-
import type { ObservableEmail } from './ObservableEmail';
|
|
21
|
-
import type { ObservableFile } from './ObservableFile';
|
|
22
|
-
import type { ObservableHowler } from './ObservableHowler';
|
|
23
|
-
import type { ObservableHttp } from './ObservableHttp';
|
|
24
|
-
import type { ObservableObserver } from './ObservableObserver';
|
|
25
|
-
import type { ObservableOrganization } from './ObservableOrganization';
|
|
26
|
-
import type { ObservableProcess } from './ObservableProcess';
|
|
27
|
-
import type { ObservableSource } from './ObservableSource';
|
|
28
|
-
import type { ObservableThreat } from './ObservableThreat';
|
|
29
|
-
import type { ObservableTls } from './ObservableTls';
|
|
30
|
-
import type { Registry } from './Registry';
|
|
31
|
-
import type { Related } from './Related';
|
|
32
|
-
import type { Rule } from './Rule';
|
|
33
|
-
import type { Server } from './Server';
|
|
34
|
-
import type { Url } from './Url';
|
|
35
|
-
import type { User } from './User';
|
|
36
|
-
import type { UserAgent } from './UserAgent';
|
|
37
|
-
import type { Vulnerability } from './Vulnerability';
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* NOTE: This is an auto-generated file. Don't edit this manually.
|
|
41
|
-
*/
|
|
42
|
-
export interface Observable {
|
|
43
|
-
agent?: Agent;
|
|
44
|
-
assemblyline?: Assemblyline;
|
|
45
|
-
aws?: Aws;
|
|
46
|
-
azure?: Azure;
|
|
47
|
-
cbs?: Cbs;
|
|
48
|
-
cloud?: ObservableCloud;
|
|
49
|
-
clue?: Clue;
|
|
50
|
-
container?: Container;
|
|
51
|
-
destination?: ObservableDestination;
|
|
52
|
-
dns?: Dns;
|
|
53
|
-
ecs?: Ecs;
|
|
54
|
-
email?: ObservableEmail;
|
|
55
|
-
error?: Error;
|
|
56
|
-
event?: Event;
|
|
57
|
-
faas?: Faas;
|
|
58
|
-
file?: ObservableFile;
|
|
59
|
-
gcp?: Gcp;
|
|
60
|
-
group?: Group;
|
|
61
|
-
host?: Host;
|
|
62
|
-
howler: ObservableHowler;
|
|
63
|
-
http?: ObservableHttp;
|
|
64
|
-
interface?: Interface;
|
|
65
|
-
labels?: { [index: string]: string };
|
|
66
|
-
message?: string;
|
|
67
|
-
network?: Network;
|
|
68
|
-
observer?: ObservableObserver;
|
|
69
|
-
organization?: ObservableOrganization;
|
|
70
|
-
process?: ObservableProcess;
|
|
71
|
-
registry?: Registry;
|
|
72
|
-
related?: Related;
|
|
73
|
-
rule?: Rule;
|
|
74
|
-
server?: Server;
|
|
75
|
-
source?: ObservableSource;
|
|
76
|
-
tags?: string[];
|
|
77
|
-
threat?: ObservableThreat;
|
|
78
|
-
timestamp: string;
|
|
79
|
-
tls?: ObservableTls;
|
|
80
|
-
url?: Url;
|
|
81
|
-
user?: User;
|
|
82
|
-
user_agent?: UserAgent;
|
|
83
|
-
vulnerability?: Vulnerability;
|
|
84
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import type { CloudAccount } from './CloudAccount';
|
|
2
|
-
import type { Instance } from './Instance';
|
|
3
|
-
import type { Machine } from './Machine';
|
|
4
|
-
import type { Project } from './Project';
|
|
5
|
-
import type { Service } from './Service';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* NOTE: This is an auto-generated file. Don't edit this manually.
|
|
9
|
-
*/
|
|
10
|
-
export interface ObservableCloud {
|
|
11
|
-
account?: CloudAccount;
|
|
12
|
-
availability_zone?: string;
|
|
13
|
-
instance?: Instance;
|
|
14
|
-
machine?: Machine;
|
|
15
|
-
project?: Project;
|
|
16
|
-
provider?: string;
|
|
17
|
-
region?: string;
|
|
18
|
-
service?: Service;
|
|
19
|
-
tenant_id?: string;
|
|
20
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import type { AutonomousSystems } from './AutonomousSystems';
|
|
2
|
-
import type { DestinationOriginal } from './DestinationOriginal';
|
|
3
|
-
import type { Geo } from './Geo';
|
|
4
|
-
import type { Nat } from './Nat';
|
|
5
|
-
import type { User } from './User';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* NOTE: This is an auto-generated file. Don't edit this manually.
|
|
9
|
-
*/
|
|
10
|
-
export interface ObservableDestination {
|
|
11
|
-
address?: string;
|
|
12
|
-
autonomous_systems?: AutonomousSystems;
|
|
13
|
-
bytes?: number;
|
|
14
|
-
domain?: string;
|
|
15
|
-
geo?: Geo;
|
|
16
|
-
ip?: string;
|
|
17
|
-
mac?: string;
|
|
18
|
-
nat?: Nat;
|
|
19
|
-
original?: DestinationOriginal;
|
|
20
|
-
packets?: number;
|
|
21
|
-
port?: number;
|
|
22
|
-
user?: User;
|
|
23
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import type { Bcc } from './Bcc';
|
|
2
|
-
import type { Cc } from './Cc';
|
|
3
|
-
import type { EmailAttachment } from './EmailAttachment';
|
|
4
|
-
import type { EmailParent } from './EmailParent';
|
|
5
|
-
import type { From } from './From';
|
|
6
|
-
import type { ReplyTo } from './ReplyTo';
|
|
7
|
-
import type { Sender } from './Sender';
|
|
8
|
-
import type { To } from './To';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* NOTE: This is an auto-generated file. Don't edit this manually.
|
|
12
|
-
*/
|
|
13
|
-
export interface ObservableEmail {
|
|
14
|
-
attachments?: EmailAttachment[];
|
|
15
|
-
bcc?: Bcc;
|
|
16
|
-
cc?: Cc;
|
|
17
|
-
content_type?: string;
|
|
18
|
-
delivery_timestamp?: string;
|
|
19
|
-
direction?: string;
|
|
20
|
-
from?: From;
|
|
21
|
-
local_id?: string;
|
|
22
|
-
message_id?: string;
|
|
23
|
-
origination_timestamp?: string;
|
|
24
|
-
parent?: EmailParent;
|
|
25
|
-
reply_to?: ReplyTo;
|
|
26
|
-
sender?: Sender;
|
|
27
|
-
subject?: string;
|
|
28
|
-
to?: To;
|
|
29
|
-
x_mailer?: string;
|
|
30
|
-
}
|