@cccsaurora/howler-ui 2.13.0-dev.96 → 2.13.0
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/hit/index.d.ts +1 -1
- package/api/hit/index.js +6 -2
- package/api/search/index.d.ts +1 -0
- package/api/view/index.d.ts +1 -1
- package/api/view/index.js +2 -2
- package/commons/components/notification/elements/item/NotificationItemDate.js +2 -2
- package/commons/components/utils/hooks/useEnv.d.ts +1 -1
- package/components/app/App.js +1 -3
- package/components/app/drawers/ApiKeyDrawer.js +4 -4
- package/components/app/hooks/useMatchers.d.ts +9 -0
- package/components/app/hooks/useMatchers.js +82 -0
- package/components/app/hooks/useMatchers.test.d.ts +1 -0
- package/components/app/hooks/useMatchers.test.js +237 -0
- package/components/app/hooks/useTitle.js +5 -4
- package/components/app/providers/AnalyticProvider.d.ts +0 -4
- package/components/app/providers/AnalyticProvider.js +1 -44
- package/components/app/providers/ApiConfigProvider.js +2 -1
- package/components/app/providers/HitProvider.d.ts +2 -1
- package/components/app/providers/HitProvider.js +8 -2
- package/components/app/providers/HitSearchProvider.d.ts +2 -1
- package/components/app/providers/HitSearchProvider.js +2 -1
- package/components/app/providers/SocketProvider.js +1 -1
- package/components/app/providers/ViewProvider.d.ts +1 -1
- package/components/app/providers/ViewProvider.js +3 -3
- package/components/app/providers/ViewProvider.test.d.ts +1 -0
- package/components/app/providers/ViewProvider.test.js +150 -0
- package/components/elements/display/ActionButton.d.ts +8 -0
- package/components/elements/display/ActionButton.js +18 -0
- package/components/elements/display/handlebars/helpers.js +17 -2
- package/components/elements/display/json/JSONViewer.d.ts +2 -0
- package/components/elements/display/json/JSONViewer.js +6 -13
- package/components/elements/hit/HitActions.js +4 -6
- package/components/elements/hit/HitBanner.js +13 -5
- package/components/elements/hit/HitCard.js +0 -5
- package/components/elements/hit/HitComments.js +5 -4
- package/components/elements/hit/HitOutline.d.ts +2 -2
- package/components/elements/hit/HitOutline.js +11 -21
- package/components/elements/hit/HitOverview.js +7 -4
- package/components/elements/hit/HitSummary.d.ts +2 -1
- package/components/elements/hit/HitSummary.js +8 -7
- package/components/elements/hit/aggregate/HitGraph.d.ts +1 -1
- package/components/elements/hit/aggregate/HitGraph.js +7 -7
- package/components/elements/hit/elements/HitTimestamp.js +8 -8
- package/components/elements/hit/related/PivotLink.js +11 -5
- package/components/elements/hit/related/RelatedIcon.d.ts +8 -0
- package/components/elements/hit/related/RelatedIcon.js +32 -0
- package/components/elements/hit/related/RelatedLink.js +4 -25
- package/components/hooks/useMyChart.d.ts +1 -1
- package/components/hooks/useMyChart.js +1 -1
- package/components/routes/advanced/QueryBuilder.js +47 -11
- package/components/routes/advanced/QueryEditor.js +8 -13
- package/components/routes/analytics/AnalyticOverview.d.ts +1 -1
- package/components/routes/analytics/AnalyticOverview.js +1 -1
- package/components/routes/analytics/AnalyticOverviews.d.ts +1 -1
- package/components/routes/analytics/AnalyticOverviews.js +1 -1
- package/components/routes/analytics/AnalyticSearch.js +14 -2
- package/components/routes/analytics/AnalyticTemplates.d.ts +1 -1
- package/components/routes/analytics/AnalyticTemplates.js +10 -7
- package/components/routes/analytics/RuleView.d.ts +1 -1
- package/components/routes/analytics/RuleView.js +1 -1
- package/components/routes/analytics/TriageSettings.d.ts +1 -1
- package/components/routes/analytics/TriageSettings.js +1 -1
- package/components/routes/analytics/widgets/Assessment.d.ts +1 -1
- package/components/routes/analytics/widgets/Assessment.js +1 -1
- package/components/routes/analytics/widgets/Created.d.ts +1 -1
- package/components/routes/analytics/widgets/Created.js +1 -1
- package/components/routes/analytics/widgets/Detection.d.ts +1 -1
- package/components/routes/analytics/widgets/Detection.js +1 -1
- package/components/routes/analytics/widgets/Escalation.d.ts +1 -1
- package/components/routes/analytics/widgets/Escalation.js +1 -1
- package/components/routes/analytics/widgets/Stacked.d.ts +1 -1
- package/components/routes/analytics/widgets/Stacked.js +1 -1
- package/components/routes/analytics/widgets/Status.d.ts +1 -1
- package/components/routes/analytics/widgets/Status.js +1 -1
- package/components/routes/help/SearchDocumentation.js +2 -1
- package/components/routes/help/TemplateDocumentation.js +5 -5
- package/components/routes/hits/search/HitBrowser.js +2 -2
- package/components/routes/hits/search/HitContextMenu.js +6 -7
- package/components/routes/hits/search/HitQuery.js +2 -1
- package/components/routes/hits/search/InformationPane.js +75 -78
- package/components/routes/hits/search/SearchPane.js +3 -9
- package/components/routes/hits/search/grid/AddColumnModal.js +10 -5
- package/components/routes/hits/search/grid/HitGrid.js +6 -5
- package/components/routes/hits/search/shared/CustomSpan.js +6 -6
- package/components/routes/hits/view/HitViewer.js +18 -26
- package/components/routes/home/index.js +4 -4
- package/components/routes/overviews/OverviewViewer.js +33 -31
- package/components/routes/settings/SecuritySection.js +2 -2
- package/components/routes/templates/TemplateViewer.js +27 -36
- package/components/routes/templates/Templates.js +4 -11
- package/components/routes/views/ViewComposer.js +8 -1
- package/components/routes/views/Views.js +25 -9
- package/index.js +7 -0
- package/locales/en/help/search.json +17 -0
- package/locales/en/translation.json +12 -3
- package/locales/fr/help/search.json +17 -0
- package/locales/fr/translation.json +12 -4
- package/models/WithMetadata.d.ts +10 -0
- package/models/WithMetadata.js +1 -0
- package/models/entities/generated/ApiType.d.ts +7 -0
- package/package.json +112 -111
- package/plugins/borealis/components/BorealisTypography.js +4 -2
- package/setupTests.d.ts +1 -0
- package/setupTests.js +12 -0
- package/tests/MockLocalStorage.d.ts +5 -0
- package/tests/MockLocalStorage.js +44 -0
- package/tests/server-handlers.d.ts +5 -0
- package/tests/server-handlers.js +97 -0
- package/tests/server.d.ts +3 -0
- package/tests/server.js +5 -0
- package/utils/constants.js +2 -2
- package/utils/stringUtils.d.ts +1 -0
- package/utils/stringUtils.js +9 -0
- package/utils/utils.js +3 -3
- package/components/app/providers/DossierProvider.d.ts +0 -16
- package/components/app/providers/DossierProvider.js +0 -82
- package/components/app/providers/TemplateProvider.d.ts +0 -14
- package/components/app/providers/TemplateProvider.js +0 -103
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Close, ErrorOutline, List, TableChart, Terminal } from '@mui/icons-material';
|
|
2
|
+
import { Close, ErrorOutline, List, SavedSearch, TableChart, Terminal } from '@mui/icons-material';
|
|
3
3
|
import { Box, IconButton, LinearProgress, Stack, ToggleButton, ToggleButtonGroup, Tooltip, Typography, useMediaQuery, useTheme } from '@mui/material';
|
|
4
4
|
import { grey } from '@mui/material/colors';
|
|
5
5
|
import AppListEmpty from '@cccsaurora/howler-ui/commons/components/display/AppListEmpty';
|
|
@@ -7,7 +7,6 @@ import PageCenter from '@cccsaurora/howler-ui/commons/components/pages/PageCente
|
|
|
7
7
|
import { HitContext } from '@cccsaurora/howler-ui/components/app/providers/HitProvider';
|
|
8
8
|
import { HitSearchContext } from '@cccsaurora/howler-ui/components/app/providers/HitSearchProvider';
|
|
9
9
|
import { ParameterContext } from '@cccsaurora/howler-ui/components/app/providers/ParameterProvider';
|
|
10
|
-
import { TemplateContext } from '@cccsaurora/howler-ui/components/app/providers/TemplateProvider';
|
|
11
10
|
import { ViewContext } from '@cccsaurora/howler-ui/components/app/providers/ViewProvider';
|
|
12
11
|
import FlexOne from '@cccsaurora/howler-ui/components/elements/addons/layout/FlexOne';
|
|
13
12
|
import FlexPort from '@cccsaurora/howler-ui/components/elements/addons/layout/FlexPort';
|
|
@@ -46,7 +45,7 @@ const Item = memo(({ hit, onClick }) => {
|
|
|
46
45
|
}
|
|
47
46
|
}, []);
|
|
48
47
|
// Search result list item renderer.
|
|
49
|
-
return (_jsx(Box, { id: hit.howler.id,
|
|
48
|
+
return (_jsx(Box, { id: hit.howler.id, onAuxClick: e => checkMiddleClick(e, hit.howler.id), onClick: ev => onClick(ev, hit), sx: [
|
|
50
49
|
{
|
|
51
50
|
mb: 2,
|
|
52
51
|
cursor: 'pointer',
|
|
@@ -82,7 +81,6 @@ const SearchPane = () => {
|
|
|
82
81
|
const location = useLocation();
|
|
83
82
|
const navigate = useNavigate();
|
|
84
83
|
const routeParams = useParams();
|
|
85
|
-
const refresh = useContextSelector(TemplateContext, ctx => ctx.refresh);
|
|
86
84
|
const selected = useContextSelector(ParameterContext, ctx => ctx.selected);
|
|
87
85
|
const setSelected = useContextSelector(ParameterContext, ctx => ctx.setSelected);
|
|
88
86
|
const query = useContextSelector(ParameterContext, ctx => ctx.query);
|
|
@@ -109,10 +107,6 @@ const SearchPane = () => {
|
|
|
109
107
|
}
|
|
110
108
|
return selectedElement.id;
|
|
111
109
|
}, []);
|
|
112
|
-
// Load the index field for a hit in order to provide autocomplete suggestions.
|
|
113
|
-
useEffect(() => {
|
|
114
|
-
refresh();
|
|
115
|
-
}, [refresh]);
|
|
116
110
|
useEffect(() => {
|
|
117
111
|
if (location.pathname.startsWith('/bundles')) {
|
|
118
112
|
getHit(routeParams.id);
|
|
@@ -126,7 +120,7 @@ const SearchPane = () => {
|
|
|
126
120
|
], onClick: () => {
|
|
127
121
|
clearSelectedHits(bundleHit.howler.id);
|
|
128
122
|
setSelected(bundleHit.howler.id);
|
|
129
|
-
}, children: _jsx(HitBanner, { hit: bundleHit, layout: HitLayout.DENSE, useListener: true }) }) }) }) })), _jsxs(Stack, { direction: "row", spacing: 1, alignItems: "center", children: [_jsx(Typography, { sx: { color: 'text.secondary', fontSize: '0.9em', fontStyle: 'italic', mb: 0.5 }, variant: "body2", children: t('hit.search.prompt') }), error && (_jsx(Tooltip, { title: `${t('route.advanced.error')}: ${error}`, children: _jsx(ErrorOutline, { fontSize: "small", color: "error" }) })), _jsx(FlexOne, {}), bundleHit?.howler.bundles.length > 0 && _jsx(BundleParentMenu, { bundle: bundleHit }), bundleHit && (_jsx(Tooltip, { title: t('hit.bundle.close'), children: _jsx(IconButton, { size: "small", onClick: () => navigate('/search'), children: _jsx(Close, {}) }) })), _jsx(Tooltip, { title: t('route.actions.save'), children: _jsx(IconButton, { component: Link, disabled: !query, to: `/action/execute?query=${query}`, children: _jsx(Terminal, {}) }) }), _jsxs(ToggleButtonGroup, { exclusive: true, value: displayType, onChange: (__, value) => setDisplayType(value), size: "small", children: [_jsx(ToggleButton, { value: "list", children: _jsx(List, {}) }), _jsx(ToggleButton, { value: "grid", children: _jsx(TableChart, {}) })] })] })] }), _jsxs(VSBoxHeader, { ml: -3, mr: -3, px: 2, pb: 1, sx: { zIndex: 999 }, children: [_jsxs(Stack, { sx: { pt: 1 }, children: [_jsxs(Stack, { sx: { position: 'relative', flex: 1 }, children: [_jsx(HitQuery, { disabled: viewId && !selectedView, searching: searching, triggerSearch: triggerSearch }), searching && (_jsx(LinearProgress, { sx: theme => ({
|
|
123
|
+
}, children: _jsx(HitBanner, { hit: bundleHit, layout: HitLayout.DENSE, useListener: true }) }) }) }) })), _jsxs(Stack, { direction: "row", spacing: 1, alignItems: "center", children: [_jsx(Typography, { sx: { color: 'text.secondary', fontSize: '0.9em', fontStyle: 'italic', mb: 0.5 }, variant: "body2", children: t('hit.search.prompt') }), error && (_jsx(Tooltip, { title: `${t('route.advanced.error')}: ${error}`, children: _jsx(ErrorOutline, { fontSize: "small", color: "error" }) })), _jsx(FlexOne, {}), bundleHit?.howler.bundles.length > 0 && _jsx(BundleParentMenu, { bundle: bundleHit }), bundleHit && (_jsx(Tooltip, { title: t('hit.bundle.close'), children: _jsx(IconButton, { size: "small", onClick: () => navigate('/search'), children: _jsx(Close, {}) }) })), _jsx(Tooltip, { title: t('route.views.save'), children: _jsx(IconButton, { component: Link, disabled: !query, to: `/views/create?query=${query}`, children: _jsx(SavedSearch, {}) }) }), _jsx(Tooltip, { title: t('route.actions.save'), children: _jsx(IconButton, { component: Link, disabled: !query, to: `/action/execute?query=${query}`, children: _jsx(Terminal, {}) }) }), _jsxs(ToggleButtonGroup, { exclusive: true, value: displayType, onChange: (__, value) => setDisplayType(value), size: "small", children: [_jsx(ToggleButton, { value: "list", children: _jsx(List, {}) }), _jsx(ToggleButton, { value: "grid", children: _jsx(TableChart, {}) })] })] })] }), _jsxs(VSBoxHeader, { ml: -3, mr: -3, px: 2, pb: 1, sx: { zIndex: 999 }, children: [_jsxs(Stack, { sx: { pt: 1 }, children: [_jsxs(Stack, { sx: { position: 'relative', flex: 1 }, children: [_jsx(HitQuery, { disabled: viewId && !selectedView, searching: searching, triggerSearch: triggerSearch }), searching && (_jsx(LinearProgress, { sx: theme => ({
|
|
130
124
|
position: 'absolute',
|
|
131
125
|
left: 0,
|
|
132
126
|
right: 0,
|
|
@@ -1,21 +1,26 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Add, Check } from '@mui/icons-material';
|
|
3
3
|
import { Autocomplete, Chip, Divider, Grid, IconButton, Popover, Stack, TextField } from '@mui/material';
|
|
4
|
+
import useMatchers from '@cccsaurora/howler-ui/components/app/hooks/useMatchers';
|
|
4
5
|
import { FieldContext } from '@cccsaurora/howler-ui/components/app/providers/FieldProvider';
|
|
5
6
|
import { HitSearchContext } from '@cccsaurora/howler-ui/components/app/providers/HitSearchProvider';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { memo, useContext, useMemo, useState } from 'react';
|
|
7
|
+
import { has, sortBy, uniq } from 'lodash-es';
|
|
8
|
+
import { memo, useContext, useEffect, useMemo, useState } from 'react';
|
|
9
9
|
import { useTranslation } from 'react-i18next';
|
|
10
10
|
import { useContextSelector } from 'use-context-selector';
|
|
11
11
|
const AddColumnModal = ({ open, onClose, anchorEl, addColumn, columns }) => {
|
|
12
12
|
const { t } = useTranslation();
|
|
13
13
|
const { hitFields } = useContext(FieldContext);
|
|
14
14
|
const response = useContextSelector(HitSearchContext, ctx => ctx.response);
|
|
15
|
-
const getMatchingTemplate =
|
|
15
|
+
const { getMatchingTemplate } = useMatchers();
|
|
16
16
|
const [columnToAdd, setColumnToAdd] = useState(null);
|
|
17
17
|
const options = useMemo(() => hitFields.map(field => field.key), [hitFields]);
|
|
18
|
-
const suggestions
|
|
18
|
+
const [suggestions, setSuggestions] = useState([]);
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
(async () => {
|
|
21
|
+
setSuggestions(uniq((await Promise.all((response?.items ?? []).map(async (_hit) => (has(_hit, '__template') ? _hit.__template?.keys : (await getMatchingTemplate(_hit))?.keys) ?? []))).flat()));
|
|
22
|
+
})();
|
|
23
|
+
}, [getMatchingTemplate, response?.items]);
|
|
19
24
|
return (_jsx(Popover, { open: open, onClose: onClose, anchorEl: anchorEl, anchorOrigin: { vertical: 'bottom', horizontal: 'left' }, children: _jsxs(Stack, { spacing: 1, p: 1, width: "500px", children: [_jsxs(Stack, { direction: "row", spacing: 1, children: [_jsx(Autocomplete, { sx: { flex: 1 }, size: "small", options: options, value: columnToAdd, renderInput: params => _jsx(TextField, { fullWidth: true, placeholder: t('hit.fields'), ...params }), onChange: (_ev, value) => setColumnToAdd(value) }), _jsx(IconButton, { disabled: !columnToAdd, onClick: () => {
|
|
20
25
|
addColumn(columnToAdd);
|
|
21
26
|
setColumnToAdd(null);
|
|
@@ -3,7 +3,7 @@ import { DndContext, KeyboardSensor, PointerSensor, pointerWithin, useSensor, us
|
|
|
3
3
|
import { arrayMove, SortableContext, sortableKeyboardCoordinates } from '@dnd-kit/sortable';
|
|
4
4
|
import { Add, FormatIndentDecrease, FormatIndentIncrease, Info, List, Search, TableChart } from '@mui/icons-material';
|
|
5
5
|
import { IconButton, LinearProgress, Paper, Stack, Table, TableBody, TableCell, TableHead, TableRow, ToggleButton, ToggleButtonGroup, Typography, useTheme } from '@mui/material';
|
|
6
|
-
import
|
|
6
|
+
import useMatchers from '@cccsaurora/howler-ui/components/app/hooks/useMatchers';
|
|
7
7
|
import { HitContext } from '@cccsaurora/howler-ui/components/app/providers/HitProvider';
|
|
8
8
|
import { HitSearchContext } from '@cccsaurora/howler-ui/components/app/providers/HitSearchProvider';
|
|
9
9
|
import { ParameterContext } from '@cccsaurora/howler-ui/components/app/providers/ParameterProvider';
|
|
@@ -14,7 +14,7 @@ import DevelopmentBanner from '@cccsaurora/howler-ui/components/elements/display
|
|
|
14
14
|
import DevelopmentIcon from '@cccsaurora/howler-ui/components/elements/display/features/DevelopmentIcon';
|
|
15
15
|
import useHitSelection from '@cccsaurora/howler-ui/components/hooks/useHitSelection';
|
|
16
16
|
import { useMyLocalStorageItem } from '@cccsaurora/howler-ui/components/hooks/useMyLocalStorage';
|
|
17
|
-
import { useCallback,
|
|
17
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
18
18
|
import { useTranslation } from 'react-i18next';
|
|
19
19
|
import { useContextSelector } from 'use-context-selector';
|
|
20
20
|
import { StorageKey } from '@cccsaurora/howler-ui/utils/constants';
|
|
@@ -27,10 +27,10 @@ import ColumnHeader from './ColumnHeader';
|
|
|
27
27
|
import HitRow from './HitRow';
|
|
28
28
|
const HitGrid = () => {
|
|
29
29
|
const { t } = useTranslation();
|
|
30
|
-
const { getIdFromName } = useContext(AnalyticContext);
|
|
31
30
|
const theme = useTheme();
|
|
32
31
|
const sensors = useSensors(useSensor(PointerSensor), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }));
|
|
33
32
|
const { onClick } = useHitSelection();
|
|
33
|
+
const { getMatchingAnalytic } = useMatchers();
|
|
34
34
|
const search = useContextSelector(HitSearchContext, ctx => ctx.search);
|
|
35
35
|
const displayType = useContextSelector(HitSearchContext, ctx => ctx.displayType);
|
|
36
36
|
const setDisplayType = useContextSelector(HitSearchContext, ctx => ctx.setDisplayType);
|
|
@@ -65,10 +65,11 @@ const HitGrid = () => {
|
|
|
65
65
|
useEffect(() => {
|
|
66
66
|
response?.items.forEach(hit => {
|
|
67
67
|
if (!analyticIds[hit.howler.analytic]) {
|
|
68
|
-
|
|
68
|
+
getMatchingAnalytic(hit).then(_analytic => setAnalyticIds(_analyticIds => ({ ..._analyticIds, [hit.howler.analytic]: _analytic.analytic_id })));
|
|
69
69
|
}
|
|
70
70
|
});
|
|
71
|
-
|
|
71
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
72
|
+
}, [analyticIds, response]);
|
|
72
73
|
const onMouseMove = useCallback((event) => {
|
|
73
74
|
event.stopPropagation();
|
|
74
75
|
event.preventDefault();
|
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Stack } from '@mui/material';
|
|
3
3
|
import { LocalizationProvider } from '@mui/x-date-pickers';
|
|
4
|
-
import {
|
|
4
|
+
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
|
|
5
5
|
import { DateTimePicker } from '@mui/x-date-pickers/DateTimePicker';
|
|
6
6
|
import { ParameterContext } from '@cccsaurora/howler-ui/components/app/providers/ParameterProvider';
|
|
7
|
-
import
|
|
7
|
+
import dayjs from 'dayjs';
|
|
8
8
|
import { memo, useEffect } from 'react';
|
|
9
9
|
import { useTranslation } from 'react-i18next';
|
|
10
10
|
import { useContextSelector } from 'use-context-selector';
|
|
11
11
|
const CustomSpan = () => {
|
|
12
12
|
const { t } = useTranslation();
|
|
13
13
|
const span = useContextSelector(ParameterContext, ctx => ctx.span);
|
|
14
|
-
const startDate = useContextSelector(ParameterContext, ctx => (ctx.startDate ?
|
|
14
|
+
const startDate = useContextSelector(ParameterContext, ctx => (ctx.startDate ? dayjs(ctx.startDate) : null));
|
|
15
15
|
const setCustomSpan = useContextSelector(ParameterContext, ctx => ctx.setCustomSpan);
|
|
16
|
-
const endDate = useContextSelector(ParameterContext, ctx => (ctx.endDate ?
|
|
16
|
+
const endDate = useContextSelector(ParameterContext, ctx => (ctx.endDate ? dayjs(ctx.endDate) : null));
|
|
17
17
|
useEffect(() => {
|
|
18
18
|
if (span?.endsWith('custom') && (!startDate || !endDate)) {
|
|
19
|
-
setCustomSpan(
|
|
19
|
+
setCustomSpan(dayjs().subtract(3, 'days').toISOString(), dayjs().toISOString());
|
|
20
20
|
}
|
|
21
21
|
}, [endDate, setCustomSpan, span, startDate]);
|
|
22
|
-
return span?.endsWith('custom') ? (_jsx(LocalizationProvider, { dateAdapter:
|
|
22
|
+
return span?.endsWith('custom') ? (_jsx(LocalizationProvider, { dateAdapter: AdapterDayjs, children: _jsxs(Stack, { direction: "row", spacing: 1, useFlexGap: true, flexWrap: "wrap", children: [_jsx(DateTimePicker, { sx: { minWidth: '175px', flexGrow: 1, marginTop: 1 }, slotProps: { textField: { size: 'small' } }, label: t('date.select.start'), value: startDate ? dayjs(startDate) : dayjs().subtract(1, 'days'), maxDate: endDate, onChange: (newStartDate) => setCustomSpan(newStartDate.toISOString(), endDate.toISOString()), ampm: false, disableFuture: true }), _jsx(DateTimePicker, { sx: { minWidth: '175px', flexGrow: 1, marginTop: 1 }, slotProps: { textField: { size: 'small' } }, label: t('date.select.end'), value: endDate, minDate: startDate, onChange: (newEndDate) => setCustomSpan(startDate.toISOString(), newEndDate.toISOString()), ampm: false, disableFuture: true })] }) })) : null;
|
|
23
23
|
};
|
|
24
24
|
export default memo(CustomSpan);
|
|
@@ -3,11 +3,8 @@ import { Icon } from '@iconify/react/dist/iconify.js';
|
|
|
3
3
|
import { Code, Comment, DataObject, History, LinkSharp, QueryStats, ViewAgenda } from '@mui/icons-material';
|
|
4
4
|
import { Badge, Box, CardContent, Collapse, IconButton, Skeleton, Stack, Tab, Tabs, Tooltip, useMediaQuery, useTheme } from '@mui/material';
|
|
5
5
|
import PageCenter from '@cccsaurora/howler-ui/commons/components/pages/PageCenter';
|
|
6
|
-
import
|
|
7
|
-
import { DossierContext } from '@cccsaurora/howler-ui/components/app/providers/DossierProvider';
|
|
6
|
+
import useMatchers from '@cccsaurora/howler-ui/components/app/hooks/useMatchers';
|
|
8
7
|
import { HitContext } from '@cccsaurora/howler-ui/components/app/providers/HitProvider';
|
|
9
|
-
import { OverviewContext } from '@cccsaurora/howler-ui/components/app/providers/OverviewProvider';
|
|
10
|
-
import { TemplateContext } from '@cccsaurora/howler-ui/components/app/providers/TemplateProvider';
|
|
11
8
|
import FlexOne from '@cccsaurora/howler-ui/components/elements/addons/layout/FlexOne';
|
|
12
9
|
import HowlerCard from '@cccsaurora/howler-ui/components/elements/display/HowlerCard';
|
|
13
10
|
import BundleButton from '@cccsaurora/howler-ui/components/elements/display/icons/BundleButton';
|
|
@@ -29,7 +26,7 @@ import RelatedLink from '@cccsaurora/howler-ui/components/elements/hit/related/R
|
|
|
29
26
|
import { useMyLocalStorageItem } from '@cccsaurora/howler-ui/components/hooks/useMyLocalStorage';
|
|
30
27
|
import useMyUserList from '@cccsaurora/howler-ui/components/hooks/useMyUserList';
|
|
31
28
|
import uniqBy from 'lodash-es/uniqBy';
|
|
32
|
-
import { useCallback,
|
|
29
|
+
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
33
30
|
import { useTranslation } from 'react-i18next';
|
|
34
31
|
import { useNavigate, useParams } from 'react-router-dom';
|
|
35
32
|
import { useContextSelector } from 'use-context-selector';
|
|
@@ -49,10 +46,7 @@ const HitViewer = () => {
|
|
|
49
46
|
const theme = useTheme();
|
|
50
47
|
const isUnderLg = useMediaQuery(theme.breakpoints.down('lg'));
|
|
51
48
|
const [orientation, setOrientation] = useMyLocalStorageItem(StorageKey.VIEWER_ORIENTATION, Orientation.VERTICAL);
|
|
52
|
-
const
|
|
53
|
-
const { getAnalyticFromName } = useContext(AnalyticContext);
|
|
54
|
-
const { getMatchingOverview, refresh: refreshOverviews } = useContext(OverviewContext);
|
|
55
|
-
const getMatchingDossiers = useContextSelector(DossierContext, ctx => ctx.getMatchingDossiers);
|
|
49
|
+
const { getMatchingOverview, getMatchingDossiers, getMatchingAnalytic } = useMatchers();
|
|
56
50
|
const getHit = useContextSelector(HitContext, ctx => ctx.getHit);
|
|
57
51
|
const hit = useContextSelector(HitContext, ctx => ctx.hits[params.id]);
|
|
58
52
|
const [userIds, setUserIds] = useState(new Set());
|
|
@@ -60,22 +54,22 @@ const HitViewer = () => {
|
|
|
60
54
|
const [tab, setTab] = useState('details');
|
|
61
55
|
const [analytic, setAnalytic] = useState();
|
|
62
56
|
const [dossiers, setDossiers] = useState([]);
|
|
57
|
+
const [hasOverview, setHasOverview] = useState(false);
|
|
63
58
|
const fetchData = useCallback(async () => {
|
|
64
59
|
try {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
60
|
+
if (!hit) {
|
|
61
|
+
await getHit(params.id, true);
|
|
62
|
+
return;
|
|
68
63
|
}
|
|
69
|
-
setUserIds(getUserList(
|
|
70
|
-
setAnalytic(await
|
|
64
|
+
setUserIds(getUserList(hit));
|
|
65
|
+
setAnalytic(await getMatchingAnalytic(hit));
|
|
71
66
|
}
|
|
72
67
|
catch (err) {
|
|
73
68
|
if (err.cause?.api_status_code === 404) {
|
|
74
69
|
navigate('/404');
|
|
75
70
|
}
|
|
76
71
|
}
|
|
77
|
-
|
|
78
|
-
}, [getAnalyticFromName, getHit, params.id, navigate]);
|
|
72
|
+
}, [hit, getMatchingAnalytic, getHit, params.id, navigate]);
|
|
79
73
|
useEffect(() => {
|
|
80
74
|
if (isUnderLg) {
|
|
81
75
|
setOrientation(Orientation.HORIZONTAL);
|
|
@@ -83,22 +77,20 @@ const HitViewer = () => {
|
|
|
83
77
|
}, [isUnderLg, setOrientation]);
|
|
84
78
|
useEffect(() => {
|
|
85
79
|
fetchData();
|
|
86
|
-
}, [params.id, fetchData]);
|
|
80
|
+
}, [params.id, fetchData, hit]);
|
|
87
81
|
const onOrientationChange = useCallback(() => setOrientation(orientation === Orientation.VERTICAL ? Orientation.HORIZONTAL : Orientation.VERTICAL), [orientation, setOrientation]);
|
|
88
|
-
const matchingOverview = useMemo(() => getMatchingOverview(hit), [getMatchingOverview, hit]);
|
|
89
82
|
useEffect(() => {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}, [refreshOverviews, refreshTemplates]);
|
|
83
|
+
getMatchingOverview(hit).then(_overview => setHasOverview(!!_overview));
|
|
84
|
+
}, [getMatchingOverview, hit]);
|
|
93
85
|
useEffect(() => {
|
|
94
|
-
if (
|
|
86
|
+
if (hasOverview && tab === 'details') {
|
|
95
87
|
setTab('overview');
|
|
96
88
|
}
|
|
97
|
-
else if (!
|
|
89
|
+
else if (!hasOverview && tab === 'overview') {
|
|
98
90
|
setTab('details');
|
|
99
91
|
}
|
|
100
92
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
101
|
-
}, [
|
|
93
|
+
}, [hasOverview]);
|
|
102
94
|
const tabContent = useMemo(() => {
|
|
103
95
|
if (!tab || !hit) {
|
|
104
96
|
return;
|
|
@@ -122,7 +114,7 @@ const HitViewer = () => {
|
|
|
122
114
|
if (!hit) {
|
|
123
115
|
return;
|
|
124
116
|
}
|
|
125
|
-
getMatchingDossiers(hit
|
|
117
|
+
getMatchingDossiers(hit).then(setDossiers);
|
|
126
118
|
}, [getMatchingDossiers, hit]);
|
|
127
119
|
if (!hit) {
|
|
128
120
|
return (_jsx(PageCenter, { children: _jsx(Skeleton, { variant: "rounded", height: "520px" }) }));
|
|
@@ -150,7 +142,7 @@ const HitViewer = () => {
|
|
|
150
142
|
position: 'absolute',
|
|
151
143
|
top: theme.spacing(2),
|
|
152
144
|
right: theme.spacing(-6)
|
|
153
|
-
}, children: [_jsx(Tooltip, { title: t('page.hits.view.layout'), children: _jsx(IconButton, { onClick: onOrientationChange, children: _jsx(ViewAgenda, { sx: { transition: 'rotate 250ms', rotate: orientation === 'vertical' ? '90deg' : '0deg' } }) }) }), _jsx(SocketBadge, { size: "medium" }), analytic && (_jsx(Tooltip, { title: t('hit.panel.analytic.open'), children: _jsx(IconButton, { onClick: () => navigate(`/analytics/${analytic.analytic_id}`), children: _jsx(QueryStats, {}) }) })), hit?.howler.bundles?.length > 0 && _jsx(BundleButton, { ids: hit.howler.bundles })] }))] }), _jsx(HowlerCard, { sx: [orientation === 'horizontal' && { height: '0px' }], children: _jsx(CardContent, { sx: { padding: 1, position: 'relative' }, children: _jsx(HitActions, { hit: hit, orientation: "vertical" }) }) }), _jsx(Box, { sx: { gridColumn: '1 / span 2', mb: 1 }, children: _jsxs(Tabs, { value: tab === 'overview' && !
|
|
145
|
+
}, children: [_jsx(Tooltip, { title: t('page.hits.view.layout'), children: _jsx(IconButton, { onClick: onOrientationChange, children: _jsx(ViewAgenda, { sx: { transition: 'rotate 250ms', rotate: orientation === 'vertical' ? '90deg' : '0deg' } }) }) }), _jsx(SocketBadge, { size: "medium" }), analytic && (_jsx(Tooltip, { title: t('hit.panel.analytic.open'), children: _jsx(IconButton, { onClick: () => navigate(`/analytics/${analytic.analytic_id}`), children: _jsx(QueryStats, {}) }) })), hit?.howler.bundles?.length > 0 && _jsx(BundleButton, { ids: hit.howler.bundles })] }))] }), _jsx(HowlerCard, { sx: [orientation === 'horizontal' && { height: '0px' }], children: _jsx(CardContent, { sx: { padding: 1, position: 'relative' }, children: _jsx(HitActions, { hit: hit, orientation: "vertical" }) }) }), _jsx(Box, { sx: { gridColumn: '1 / span 2', mb: 1 }, children: _jsxs(Tabs, { value: tab === 'overview' && !hasOverview ? 'details' : tab, sx: { display: 'flex', flexDirection: 'row', pr: 2, alignItems: 'center' }, children: [hit?.howler?.is_bundle && (_jsx(Tab, { label: t('hit.viewer.aggregate'), value: "hit_aggregate", onClick: () => setTab('hit_aggregate') })), hasOverview && (_jsx(Tab, { label: t('hit.viewer.overview'), value: "overview", onClick: () => setTab('overview') })), _jsx(Tab, { label: t('hit.viewer.details'), value: "details", onClick: () => setTab('details') }), hit?.howler.dossier?.map((lead, index) => (_jsx(Tab
|
|
154
146
|
// eslint-disable-next-line react/no-array-index-key
|
|
155
147
|
, { label: _jsxs(Stack, { direction: "row", spacing: 0.5, children: [lead.icon && _jsx(Icon, { icon: lead.icon }), _jsx("span", { children: i18n.language === 'en' ? lead.label.en : lead.label.fr })] }), value: 'lead:' + index, onClick: () => setTab('lead:' + index) }, 'lead:' + index))), dossiers.flatMap((_dossier, dossierIndex) => _dossier.leads?.map((_lead, leadIndex) => (_jsx(Tab
|
|
156
148
|
// eslint-disable-next-line react/no-array-index-key
|
|
@@ -10,8 +10,8 @@ import PageCenter from '@cccsaurora/howler-ui/commons/components/pages/PageCente
|
|
|
10
10
|
import CustomButton from '@cccsaurora/howler-ui/components/elements/addons/buttons/CustomButton';
|
|
11
11
|
import { useMyLocalStorageItem } from '@cccsaurora/howler-ui/components/hooks/useMyLocalStorage';
|
|
12
12
|
import useMyUserFunctions from '@cccsaurora/howler-ui/components/hooks/useMyUserFunctions';
|
|
13
|
+
import dayjs from 'dayjs';
|
|
13
14
|
import isEqual from 'lodash-es/isEqual';
|
|
14
|
-
import moment from 'moment';
|
|
15
15
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
16
16
|
import { useTranslation } from 'react-i18next';
|
|
17
17
|
import { Link } from 'react-router-dom';
|
|
@@ -26,7 +26,7 @@ const Home = () => {
|
|
|
26
26
|
const { user, setUser } = useAppUser();
|
|
27
27
|
const { setDashboard } = useMyUserFunctions();
|
|
28
28
|
const sensors = useSensors(useSensor(PointerSensor), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }));
|
|
29
|
-
const [lastViewed, setLastViewed] = useMyLocalStorageItem(StorageKey.LAST_VIEW,
|
|
29
|
+
const [lastViewed, setLastViewed] = useMyLocalStorageItem(StorageKey.LAST_VIEW, dayjs().utc().format(LUCENE_DATE_FMT));
|
|
30
30
|
const [loading, setLoading] = useState(false);
|
|
31
31
|
const [isEditing, setIsEditing] = useState(false);
|
|
32
32
|
const [updatedHitTotal, setUpdatedHitTotal] = useState(0);
|
|
@@ -81,8 +81,8 @@ const Home = () => {
|
|
|
81
81
|
})
|
|
82
82
|
.then(result => setUpdatedHitTotal(result.total));
|
|
83
83
|
}, [updateQuery]);
|
|
84
|
-
return (_jsx(PageCenter, { maxWidth: "1800px", textAlign: "left", height: "100%", children: _jsxs(Stack, { direction: "column", spacing: 1, sx: { height: '100%' }, children: [_jsxs(Stack, { direction: "row", justifyContent: "end", spacing: 1, children: [isEditing && (_jsx(CustomButton, { variant: "outlined", size: "small", color: "error", startIcon: _jsx(Cancel, {}), onClick: discardChanges, children: t('cancel') })), _jsx(CustomButton, { variant: "outlined", size: "small", disabled: isEditing && isEqual(dashboard, user.dashboard), color: isEditing ? 'success' : 'primary', startIcon: isEditing ? loading ? _jsx(CircularProgress, { size: 20 }) : _jsx(Check, {}) : _jsx(Edit, {}), onClick: () => (!isEditing ? setIsEditing(true) : saveChanges()), children: t(isEditing ? 'save' : 'edit') })] }), updatedHitTotal > 0 && (_jsxs(Alert, { severity: "info", variant: "outlined", action: _jsxs(Stack, { spacing: 1, direction: "row", children: [_jsx(IconButton, { color: "info", component: Link, to: `/hits?query=${encodeURIComponent(updateQuery)}`, onClick: () => setLastViewed(
|
|
85
|
-
setLastViewed(
|
|
84
|
+
return (_jsx(PageCenter, { maxWidth: "1800px", textAlign: "left", height: "100%", children: _jsxs(Stack, { direction: "column", spacing: 1, sx: { height: '100%' }, children: [_jsxs(Stack, { direction: "row", justifyContent: "end", spacing: 1, children: [isEditing && (_jsx(CustomButton, { variant: "outlined", size: "small", color: "error", startIcon: _jsx(Cancel, {}), onClick: discardChanges, children: t('cancel') })), _jsx(CustomButton, { variant: "outlined", size: "small", disabled: isEditing && isEqual(dashboard, user.dashboard), color: isEditing ? 'success' : 'primary', startIcon: isEditing ? loading ? _jsx(CircularProgress, { size: 20 }) : _jsx(Check, {}) : _jsx(Edit, {}), onClick: () => (!isEditing ? setIsEditing(true) : saveChanges()), children: t(isEditing ? 'save' : 'edit') })] }), updatedHitTotal > 0 && (_jsxs(Alert, { severity: "info", variant: "outlined", action: _jsxs(Stack, { spacing: 1, direction: "row", children: [_jsx(IconButton, { color: "info", component: Link, to: `/hits?query=${encodeURIComponent(updateQuery)}`, onClick: () => setLastViewed(dayjs().utc().format(LUCENE_DATE_FMT)), children: _jsx(OpenInNew, {}) }), _jsx(IconButton, { color: "info", onClick: () => {
|
|
85
|
+
setLastViewed(dayjs().utc().format(LUCENE_DATE_FMT));
|
|
86
86
|
setUpdatedHitTotal(0);
|
|
87
87
|
}, children: _jsx(Close, {}) })] }), children: [_jsx(AlertTitle, { children: t('route.home.alert.updated.title') }), t('route.home.alert.updated.description', { count: updatedHitTotal })] })), _jsx(DndContext, { sensors: sensors, collisionDetection: closestCenter, onDragEnd: handleDragEnd, children: _jsx(SortableContext, { items: (dashboard ?? []).map(entry => getIdFromEntry(entry)), children: _jsxs(Grid, { container: true, spacing: 1, alignItems: "stretch", sx: [
|
|
88
88
|
theme => ({
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Autocomplete, Box, Button, CircularProgress, Divider, FormControl, LinearProgress, Stack, TextField, Tooltip, useTheme } from '@mui/material';
|
|
2
|
+
import { Autocomplete, Box, Button, CircularProgress, Divider, FormControl, LinearProgress, Stack, TextField, ThemeProvider, ToggleButton, ToggleButtonGroup, Tooltip, useTheme } from '@mui/material';
|
|
3
3
|
import api from '@cccsaurora/howler-ui/api';
|
|
4
4
|
import PageCenter from '@cccsaurora/howler-ui/commons/components/pages/PageCenter';
|
|
5
5
|
import { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
|
6
6
|
import { useTranslation } from 'react-i18next';
|
|
7
|
-
import { Check, Delete, SsidChart } from '@mui/icons-material';
|
|
7
|
+
import { Check, DarkMode, Delete, SsidChart, WbSunny } from '@mui/icons-material';
|
|
8
|
+
import { useApp } from '@cccsaurora/howler-ui/commons/components/app/hooks';
|
|
8
9
|
import AppInfoPanel from '@cccsaurora/howler-ui/commons/components/display/AppInfoPanel';
|
|
9
|
-
import
|
|
10
|
+
import useThemeBuilder from '@cccsaurora/howler-ui/commons/components/utils/hooks/useThemeBuilder';
|
|
10
11
|
import { OverviewContext } from '@cccsaurora/howler-ui/components/app/providers/OverviewProvider';
|
|
11
12
|
import HitOverview from '@cccsaurora/howler-ui/components/elements/hit/HitOverview';
|
|
12
13
|
import useMyApi from '@cccsaurora/howler-ui/components/hooks/useMyApi';
|
|
14
|
+
import useMyTheme from '@cccsaurora/howler-ui/components/hooks/useMyTheme';
|
|
13
15
|
import { useSearchParams } from 'react-router-dom';
|
|
14
16
|
import hitsData from '@cccsaurora/howler-ui/utils/hit.json';
|
|
15
17
|
import { sanitizeLuceneQuery } from '@cccsaurora/howler-ui/utils/stringUtils';
|
|
@@ -17,6 +19,8 @@ import OverviewEditor from './OverviewEditor';
|
|
|
17
19
|
import { useStartingTemplate } from './startingTemplate';
|
|
18
20
|
const OverviewViewer = () => {
|
|
19
21
|
const theme = useTheme();
|
|
22
|
+
const app = useApp();
|
|
23
|
+
const { lightTheme, darkTheme } = useThemeBuilder(useMyTheme());
|
|
20
24
|
const { t } = useTranslation();
|
|
21
25
|
const [params, setParams] = useSearchParams();
|
|
22
26
|
const { getOverviews } = useContext(OverviewContext);
|
|
@@ -24,6 +28,7 @@ const OverviewViewer = () => {
|
|
|
24
28
|
const [overviewList, setOverviewList] = useState([]);
|
|
25
29
|
const [selectedOverview, setSelectedOverview] = useState(null);
|
|
26
30
|
const [content, setContent] = useState('');
|
|
31
|
+
const [chosenTheme, setChosenTheme] = useState(app.theme);
|
|
27
32
|
const [analytics, setAnalytics] = useState([]);
|
|
28
33
|
const [detections, setDetections] = useState([]);
|
|
29
34
|
const [analytic, setAnalytic] = useState(params.get('analytic') ?? '');
|
|
@@ -32,7 +37,6 @@ const OverviewViewer = () => {
|
|
|
32
37
|
const [overviewLoading, setOverviewLoading] = useState(false);
|
|
33
38
|
const [exampleHit, setExampleHit] = useState(null);
|
|
34
39
|
const [x, setX] = useState(0);
|
|
35
|
-
const analyticContext = useContext(AnalyticContext);
|
|
36
40
|
const wrapper = useRef();
|
|
37
41
|
const startingTemplate = useStartingTemplate();
|
|
38
42
|
useEffect(() => {
|
|
@@ -58,19 +62,14 @@ const OverviewViewer = () => {
|
|
|
58
62
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
59
63
|
}, [analytic, dispatchApi]);
|
|
60
64
|
useEffect(() => {
|
|
61
|
-
if (analytic) {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
.then(foundAnalytic => {
|
|
66
|
-
setDetections(foundAnalytic.detections);
|
|
67
|
-
})
|
|
68
|
-
.catch(() => {
|
|
65
|
+
if (analytic && analytics) {
|
|
66
|
+
const _detections = analytics.find(_analytic => _analytic.name.toLowerCase() === analytic.toLowerCase())?.detections ?? [];
|
|
67
|
+
setDetections(_detections);
|
|
68
|
+
if (detection && !_detections.map(_detection => _detection.toLowerCase()).includes(detection.toLowerCase())) {
|
|
69
69
|
setDetection('ANY');
|
|
70
|
-
}
|
|
71
|
-
.finally(() => setLoading(false));
|
|
70
|
+
}
|
|
72
71
|
}
|
|
73
|
-
}, [analytic,
|
|
72
|
+
}, [analytic, analytics, detection]);
|
|
74
73
|
useEffect(() => {
|
|
75
74
|
(async () => {
|
|
76
75
|
const result = await dispatchApi(api.search.hit.post({
|
|
@@ -173,7 +172,8 @@ const OverviewViewer = () => {
|
|
|
173
172
|
}, [onMouseMove, onMouseUp]);
|
|
174
173
|
const analyticOrDetectionMissing = useMemo(() => !analytic || !detection, [analytic, detection]);
|
|
175
174
|
const noChange = useMemo(() => selectedOverview?.content === content, [content, selectedOverview?.content]);
|
|
176
|
-
|
|
175
|
+
const activeTheme = chosenTheme === 'light' ? lightTheme : darkTheme;
|
|
176
|
+
return (_jsxs(PageCenter, { maxWidth: "100%", width: "100%", textAlign: "left", height: "100%", children: [_jsx(LinearProgress, { sx: { mb: 1, opacity: +loading } }), _jsxs(Stack, { direction: "column", spacing: 2, divider: _jsx(Divider, { orientation: "horizontal", flexItem: true }), height: "100%", children: [_jsxs(Stack, { direction: "row", spacing: 2, mb: 2, alignItems: "stretch", children: [_jsx(FormControl, { sx: { maxWidth: { sm: '300px', lg: '450px' }, width: '100%' }, children: _jsx(Autocomplete, { id: "analytic", options: analytics.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase())), getOptionLabel: option => option.name, value: analytics.find(a => a.name === analytic) || null, onChange: (_event, newValue) => setAnalytic(newValue ? newValue.name : ''), renderInput: autocompleteAnalyticParams => (_jsx(TextField, { ...autocompleteAnalyticParams, label: t('route.overviews.analytic'), size: "small" })) }) }), !analytics.find(_analytic => _analytic.name === analytic)?.rule ? (_jsx(FormControl, { sx: { minWidth: { sm: '200px' } }, disabled: !analytic, children: _jsx(Autocomplete, { id: "detection", options: ['ANY', ...detections.sort()], getOptionLabel: option => option, value: detection ?? '', onChange: (_event, newValue) => setDetection(newValue), renderInput: autocompleteDetectionParams => (_jsx(TextField, { ...autocompleteDetectionParams, label: t('route.overviews.detection'), size: "small" })) }) })) : (_jsx(Tooltip, { title: t('route.overviews.rule.explanation'), children: _jsx(SsidChart, { color: "info", sx: { alignSelf: 'center' } }) })), selectedOverview && (_jsx(Button, { variant: "outlined", startIcon: _jsx(Delete, {}), onClick: onDelete, children: t('button.delete') })), _jsx(Button, { variant: "outlined", disabled: analyticOrDetectionMissing || noChange, startIcon: overviewLoading ? _jsx(CircularProgress, { size: 16 }) : _jsx(Check, {}), onClick: onSave, children: t(!analyticOrDetectionMissing && !noChange ? 'button.save' : 'button.saved') }), _jsx("div", { style: { flex: 1 } }), _jsxs(ToggleButtonGroup, { exclusive: true, value: chosenTheme, onChange: (_event, value) => setChosenTheme(value), sx: { maxHeight: '40px' }, children: [_jsx(Tooltip, { title: t('route.overviews.theme.light'), children: _jsx(ToggleButton, { value: "light", children: _jsx(WbSunny, {}) }) }), _jsx(Tooltip, { title: t('route.overviews.theme.dark'), children: _jsx(ToggleButton, { value: "dark", children: _jsx(DarkMode, {}) }) })] })] }), analyticOrDetectionMissing ? (_jsx(AppInfoPanel, { i18nKey: "route.overviews.select", sx: { width: '100%', alignSelf: 'start' } })) : (_jsxs(Stack, { ref: wrapper, direction: "row", spacing: 1, height: "100%", onKeyDown: e => {
|
|
177
177
|
if (e.ctrlKey && e.key === 's') {
|
|
178
178
|
if (!noChange) {
|
|
179
179
|
onSave();
|
|
@@ -199,20 +199,22 @@ const OverviewViewer = () => {
|
|
|
199
199
|
transform: `translateX(${x}px)`,
|
|
200
200
|
zIndex: 1000,
|
|
201
201
|
borderRadius: theme.shape.borderRadius
|
|
202
|
-
}, onMouseDown: onMouseDown }), _jsx(Box, { flex: 1, px: 2, sx: {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
202
|
+
}, onMouseDown: onMouseDown }), _jsx(ThemeProvider, { theme: activeTheme, children: _jsx(Box, { flex: 1, px: 2, sx: {
|
|
203
|
+
position: 'absolute',
|
|
204
|
+
top: 0,
|
|
205
|
+
left: `calc(50% + 7px + ${x}px)`,
|
|
206
|
+
bottom: 0,
|
|
207
|
+
right: 0,
|
|
208
|
+
display: 'flex',
|
|
209
|
+
alignItems: 'stretch',
|
|
210
|
+
justifyContent: 'stretch',
|
|
211
|
+
px: 1,
|
|
212
|
+
pt: 1,
|
|
213
|
+
mt: -1,
|
|
214
|
+
'& > *': { width: '100%' },
|
|
215
|
+
'& > div > :first-child': { mt: 0 },
|
|
216
|
+
backgroundColor: activeTheme.palette.background.default,
|
|
217
|
+
color: activeTheme.palette.text.primary
|
|
218
|
+
}, children: _jsx(HitOverview, { content: content || startingTemplate, hit: exampleHit }) }) })] }))] })] }));
|
|
217
219
|
};
|
|
218
220
|
export default OverviewViewer;
|
|
@@ -3,7 +3,7 @@ import { Add } from '@mui/icons-material';
|
|
|
3
3
|
import { Chip, Grid, IconButton, TableCell, TableRow } from '@mui/material';
|
|
4
4
|
import { ApiConfigContext } from '@cccsaurora/howler-ui/components/app/providers/ApiConfigProvider';
|
|
5
5
|
import useMyLocalStorage from '@cccsaurora/howler-ui/components/hooks/useMyLocalStorage';
|
|
6
|
-
import
|
|
6
|
+
import dayjs from 'dayjs';
|
|
7
7
|
import howlerPluginStore from '@cccsaurora/howler-ui/plugins/store';
|
|
8
8
|
import { useContext, useMemo } from 'react';
|
|
9
9
|
import { Trans, useTranslation } from 'react-i18next';
|
|
@@ -26,6 +26,6 @@ const SecuritySection = ({ user, editPassword, addApiKey, removeApiKey, editQuot
|
|
|
26
26
|
return (_jsxs(SettingsSection, { title: t('page.settings.security.title'), colSpan: 3, children: [!isOAuth && editPassword && (_jsx(EditRow, { titleKey: "page.settings.security.table.password", value: "\u25CF\u25CF\u25CF\u25CF\u25CF\u25CF\u25CF\u25CF\u25CF\u25CF\u25CF", onEdit: editPassword, type: "password" })), config?.configuration.auth.allow_apikeys && (_jsxs(TableRow, { sx: { cursor: 'pointer' }, children: [_jsx(TableCell, { style: { whiteSpace: 'nowrap' }, children: t('page.settings.security.table.apikeys') }), _jsx(TableCell, { width: "100%", children: _jsxs(Grid, { container: true, spacing: 1, children: [user?.apikeys?.map(apiKey => (_jsx(Grid, { item: true, children: _jsx(Chip, { label: apiKey[0].toLocaleLowerCase() +
|
|
27
27
|
(apiKey[1].length > 0
|
|
28
28
|
? ` (${apiKey[1].map(permission => t(APIKEY_LABELS[permission])).join(', ')})`
|
|
29
|
-
: ''), style: { backgroundColor:
|
|
29
|
+
: ''), style: { backgroundColor: dayjs.utc(apiKey[2]).isBefore(dayjs().utc()) ? 'orange' : 'default' }, onDelete: removeApiKey ? () => removeApiKey(apiKey) : null }) }, apiKey[0]))), user?.apikeys?.length < 1 && (_jsx(Grid, { item: true, children: _jsx(Trans, { i18nKey: "none" }) }))] }) }), _jsx(TableCell, { align: "right", children: addApiKey && (_jsx(IconButton, { onClick: addApiKey, children: _jsx(Add, { fontSize: "small" }) })) })] })), _jsx(EditRow, { titleKey: "page.settings.security.table.apiquota", descriptionKey: "page.settings.security.table.apiquota.description", value: user?.api_quota, validate: value => value && /^[0-9]*$/m.test(value.toString()), type: "number", onEdit: editQuota }), howlerPluginStore.plugins.map(plugin => pluginStore.executeFunction(`${plugin}.settings`, 'security'))] }));
|
|
30
30
|
};
|
|
31
31
|
export default SecuritySection;
|
|
@@ -5,22 +5,17 @@ import PageCenter from '@cccsaurora/howler-ui/commons/components/pages/PageCente
|
|
|
5
5
|
import TemplateEditor from '@cccsaurora/howler-ui/components/routes/templates/TemplateEditor';
|
|
6
6
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
7
7
|
import { useTranslation } from 'react-i18next';
|
|
8
|
-
import { Check, Delete,
|
|
8
|
+
import { Check, Delete, SsidChart } from '@mui/icons-material';
|
|
9
9
|
import AppInfoPanel from '@cccsaurora/howler-ui/commons/components/display/AppInfoPanel';
|
|
10
|
-
import {
|
|
11
|
-
import { HitLayout } from '@cccsaurora/howler-ui/components/elements/hit/HitLayout';
|
|
12
|
-
import HitOutline, { DEFAULT_FIELDS } from '@cccsaurora/howler-ui/components/elements/hit/HitOutline';
|
|
10
|
+
import { DEFAULT_FIELDS } from '@cccsaurora/howler-ui/components/elements/hit/HitOutline';
|
|
13
11
|
import useMyApi from '@cccsaurora/howler-ui/components/hooks/useMyApi';
|
|
14
12
|
import isEqual from 'lodash-es/isEqual';
|
|
15
13
|
import { useSearchParams } from 'react-router-dom';
|
|
16
|
-
import { useContextSelector } from 'use-context-selector';
|
|
17
14
|
import hitsData from '@cccsaurora/howler-ui/utils/hit.json';
|
|
18
15
|
import { sanitizeLuceneQuery } from '@cccsaurora/howler-ui/utils/stringUtils';
|
|
19
|
-
const CUSTOM_OUTLINES = ['cmt.aws.sigma.rules', 'assemblyline', '6tailphish'];
|
|
20
16
|
const TemplateViewer = () => {
|
|
21
17
|
const { t } = useTranslation();
|
|
22
18
|
const [params, setParams] = useSearchParams();
|
|
23
|
-
const getTemplates = useContextSelector(TemplateContext, ctx => ctx.getTemplates);
|
|
24
19
|
const { dispatchApi } = useMyApi();
|
|
25
20
|
const [templateList, setTemplateList] = useState([]);
|
|
26
21
|
const [selectedTemplate, setSelectedTemplate] = useState(null);
|
|
@@ -47,32 +42,33 @@ const TemplateViewer = () => {
|
|
|
47
42
|
}
|
|
48
43
|
setAnalytics(_analytics);
|
|
49
44
|
});
|
|
50
|
-
|
|
45
|
+
dispatchApi(api.template.get()).then(setTemplateList);
|
|
51
46
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
52
47
|
}, [analytic, dispatchApi]);
|
|
53
48
|
useEffect(() => {
|
|
54
|
-
if (analytic) {
|
|
55
|
-
|
|
56
|
-
dispatchApi(api.search.grouped.hit.post('howler.detection', {
|
|
57
|
-
limit: 0,
|
|
58
|
-
query: `howler.analytic:"${sanitizeLuceneQuery(analytic)}"`
|
|
59
|
-
}), {
|
|
60
|
-
logError: false,
|
|
61
|
-
showError: true,
|
|
62
|
-
throwError: true
|
|
63
|
-
})
|
|
64
|
-
.finally(() => setLoading(false))
|
|
65
|
-
.then(result => result.items.map(i => i.value))
|
|
66
|
-
.then(_detections => {
|
|
67
|
-
if (_detections.length < 1 || (type === 'global' && CUSTOM_OUTLINES.includes(analytic.toLowerCase()))) {
|
|
68
|
-
setDetection('ANY');
|
|
69
|
-
}
|
|
70
|
-
if (detection && !_detections.includes(detection)) {
|
|
71
|
-
setDetection('ANY');
|
|
72
|
-
}
|
|
73
|
-
setDetections(_detections);
|
|
74
|
-
});
|
|
49
|
+
if (!analytic) {
|
|
50
|
+
return;
|
|
75
51
|
}
|
|
52
|
+
setLoading(true);
|
|
53
|
+
dispatchApi(api.search.grouped.hit.post('howler.detection', {
|
|
54
|
+
limit: 0,
|
|
55
|
+
query: `howler.analytic:"${sanitizeLuceneQuery(analytic)}"`
|
|
56
|
+
}), {
|
|
57
|
+
logError: false,
|
|
58
|
+
showError: true,
|
|
59
|
+
throwError: true
|
|
60
|
+
})
|
|
61
|
+
.finally(() => setLoading(false))
|
|
62
|
+
.then(result => result.items.map(i => i.value))
|
|
63
|
+
.then(_detections => {
|
|
64
|
+
if (_detections.length < 1) {
|
|
65
|
+
setDetection('ANY');
|
|
66
|
+
}
|
|
67
|
+
if (detection && !_detections.includes(detection)) {
|
|
68
|
+
setDetection('ANY');
|
|
69
|
+
}
|
|
70
|
+
setDetections(_detections);
|
|
71
|
+
});
|
|
76
72
|
}, [analytic, detection, dispatchApi, params, setParams, type]);
|
|
77
73
|
useEffect(() => {
|
|
78
74
|
if (analytic && detection) {
|
|
@@ -149,17 +145,12 @@ const TemplateViewer = () => {
|
|
|
149
145
|
}
|
|
150
146
|
}
|
|
151
147
|
}, [analytic, detection, dispatchApi, displayFields, selectedTemplate, templateList, type]);
|
|
152
|
-
const isCustomOutline = useMemo(() => CUSTOM_OUTLINES.includes(analytic.toLowerCase()), [analytic]);
|
|
153
148
|
const analyticOrDetectionMissing = useMemo(() => !analytic || !detection, [analytic, detection]);
|
|
154
149
|
const noFieldChange = useMemo(() => displayFields.length < 1 || isEqual(selectedTemplate?.keys ?? DEFAULT_FIELDS, displayFields), [displayFields, selectedTemplate?.keys]);
|
|
155
|
-
return (_jsxs(PageCenter, { maxWidth: "1500px", textAlign: "left", height: "100%", children: [_jsx(LinearProgress, { sx: { mb: 1, opacity: +loading } }), _jsxs(Stack, { direction: "column", spacing: 2, divider: _jsx(Divider, { orientation: "horizontal", flexItem: true }), height: "100%", children: [_jsxs(Stack, { direction: "row", spacing: 2, mb: 2, alignItems: "stretch", children: [_jsx(FormControl, { sx: { flex: 1, maxWidth: '450px' }, children: _jsx(Autocomplete, { id: "analytic", options: analytics.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase())), getOptionLabel: option => option.name, value: analytics.find(a => a.name === analytic) || null, onChange: (__, newValue) => setAnalytic(newValue ? newValue.name : ''), renderInput: autocompleteAnalyticParams => (_jsx(TextField, { ...autocompleteAnalyticParams, label: t('route.templates.analytic'), size: "small" })) }) }), !(detections?.length < 2 && detections[0]?.toLowerCase() === 'rule') ? (_jsx(FormControl, { sx: { flex: 1, maxWidth: '300px' }, disabled: !analytic
|
|
150
|
+
return (_jsxs(PageCenter, { maxWidth: "1500px", textAlign: "left", height: "100%", children: [_jsx(LinearProgress, { sx: { mb: 1, opacity: +loading } }), _jsxs(Stack, { direction: "column", spacing: 2, divider: _jsx(Divider, { orientation: "horizontal", flexItem: true }), height: "100%", children: [_jsxs(Stack, { direction: "row", spacing: 2, mb: 2, alignItems: "stretch", children: [_jsx(FormControl, { sx: { flex: 1, maxWidth: '450px' }, children: _jsx(Autocomplete, { id: "analytic", options: analytics.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase())), getOptionLabel: option => option.name, value: analytics.find(a => a.name === analytic) || null, onChange: (__, newValue) => setAnalytic(newValue ? newValue.name : ''), renderInput: autocompleteAnalyticParams => (_jsx(TextField, { ...autocompleteAnalyticParams, label: t('route.templates.analytic'), size: "small" })) }) }), !(detections?.length < 2 && detections[0]?.toLowerCase() === 'rule') ? (_jsx(FormControl, { sx: { flex: 1, maxWidth: '300px' }, disabled: !analytic, children: _jsx(Autocomplete, { id: "detection", options: ['ANY', ...detections.sort()], getOptionLabel: option => option, value: detection ?? '', onChange: (__, newValue) => setDetection(newValue), renderInput: autocompleteDetectionParams => (_jsx(TextField, { ...autocompleteDetectionParams, label: t('route.templates.detection'), size: "small" })) }) })) : (_jsx(Tooltip, { title: t('route.templates.rule.explanation'), children: _jsx(SsidChart, { color: "info", sx: { alignSelf: 'center' } }) })), _jsxs(ToggleButtonGroup, { sx: { display: 'grid', gridTemplateColumns: '1fr 1fr' }, size: "small", exclusive: true, value: type, disabled: analyticOrDetectionMissing, onChange: (__, _type) => {
|
|
156
151
|
if (_type) {
|
|
157
152
|
setType(_type);
|
|
158
153
|
}
|
|
159
|
-
}, children: [_jsx(ToggleButton, { sx: { flex: 1 }, value: "personal", "aria-label": "personal", children: t('route.templates.personal') }), _jsx(ToggleButton, { sx: { flex: 1 }, value: "global", "aria-label": "global", children: t('route.templates.global') })] }), selectedTemplate && (_jsx(Button, { variant: "outlined", startIcon: _jsx(Delete, {}), onClick: onDelete, children: t('button.delete') })), _jsx(Button, { variant: "outlined", disabled: analyticOrDetectionMissing ||
|
|
160
|
-
? 'button.readonly'
|
|
161
|
-
: !analyticOrDetectionMissing && !noFieldChange
|
|
162
|
-
? 'button.save'
|
|
163
|
-
: 'button.saved') })] }), isCustomOutline && type === 'global' ? (_jsx(HitOutline, { hit: exampleHit, layout: HitLayout.COMFY, type: "global" })) : analyticOrDetectionMissing ? (_jsx(AppInfoPanel, { i18nKey: "route.templates.select", sx: { width: '100%', alignSelf: 'start' } })) : (_jsx(TemplateEditor, { hit: exampleHit, fields: displayFields, setFields: setDisplayFields, onAdd: field => setDisplayFields([...displayFields, field]), onRemove: field => setDisplayFields(displayFields.filter(f => f !== field)) }))] })] }));
|
|
154
|
+
}, children: [_jsx(ToggleButton, { sx: { flex: 1 }, value: "personal", "aria-label": "personal", children: t('route.templates.personal') }), _jsx(ToggleButton, { sx: { flex: 1 }, value: "global", "aria-label": "global", children: t('route.templates.global') })] }), selectedTemplate && (_jsx(Button, { variant: "outlined", startIcon: _jsx(Delete, {}), onClick: onDelete, children: t('button.delete') })), _jsx(Button, { variant: "outlined", disabled: analyticOrDetectionMissing || noFieldChange, startIcon: templateLoading ? _jsx(CircularProgress, { size: 16 }) : _jsx(Check, {}), onClick: onSave, children: t(!analyticOrDetectionMissing && !noFieldChange ? 'button.save' : 'button.saved') })] }), analyticOrDetectionMissing ? (_jsx(AppInfoPanel, { i18nKey: "route.templates.select", sx: { width: '100%', alignSelf: 'start' } })) : (_jsx(TemplateEditor, { hit: exampleHit, fields: displayFields, setFields: setDisplayFields, onAdd: field => setDisplayFields([...displayFields, field]), onRemove: field => setDisplayFields(displayFields.filter(f => f !== field)) }))] })] }));
|
|
164
155
|
};
|
|
165
156
|
export default TemplateViewer;
|