@cccsaurora/howler-ui 2.17.1-dev.627 → 2.17.2-dev.649
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/app/App.js +2 -1
- package/components/app/providers/AppBarProvider.d.ts +14 -0
- package/components/app/providers/AppBarProvider.js +22 -0
- package/components/elements/hit/HitOutline.d.ts +1 -0
- package/components/elements/hit/HitOutline.js +4 -2
- package/components/hooks/useMyPreferences.js +8 -5
- package/components/routes/dossiers/DossierEditor.js +5 -1
- package/components/routes/hits/search/InformationPane.js +1 -1
- package/components/routes/hits/search/LayoutSettings.js +9 -3
- package/components/routes/hits/search/grid/AddColumnModal.d.ts +0 -3
- package/components/routes/hits/search/grid/AddColumnModal.js +5 -4
- package/components/routes/hits/search/grid/HitGrid.js +13 -16
- package/components/routes/hits/view/HitViewer.js +1 -1
- package/components/routes/home/HomeSettings.d.ts +13 -0
- package/components/routes/home/HomeSettings.js +23 -0
- package/components/routes/home/ViewCard.js +3 -2
- package/components/routes/home/ViewRefresh.js +2 -2
- package/components/routes/home/index.js +15 -20
- package/locales/en/translation.json +2 -0
- package/locales/fr/translation.json +3 -1
- package/package.json +1 -1
- package/utils/constants.d.ts +2 -0
- package/utils/constants.js +2 -0
package/components/app/App.js
CHANGED
|
@@ -66,6 +66,7 @@ import useMySearch from '../hooks/useMySearch';
|
|
|
66
66
|
import AppContainer from './AppContainer';
|
|
67
67
|
import AnalyticProvider from './providers/AnalyticProvider';
|
|
68
68
|
import ApiConfigProvider, { ApiConfigContext } from './providers/ApiConfigProvider';
|
|
69
|
+
import AppBarProvider from './providers/AppBarProvider';
|
|
69
70
|
import AvatarProvider from './providers/AvatarProvider';
|
|
70
71
|
import CustomPluginProvider from './providers/CustomPluginProvider';
|
|
71
72
|
import FavouriteProvider from './providers/FavouritesProvider';
|
|
@@ -150,7 +151,7 @@ const MyAppProvider = ({ children }) => {
|
|
|
150
151
|
return (_jsx(ErrorBoundary, { children: _jsx(AppProvider, { preferences: myPreferences, theme: myTheme, sitemap: mySitemap, user: myUser, search: mySearch, children: _jsx(CustomPluginProvider, { children: _jsx(ErrorBoundary, { children: _jsx(ErrorBoundary, { children: _jsx(ViewProvider, { children: _jsx(AvatarProvider, { children: _jsx(ModalProvider, { children: _jsx(FieldProvider, { children: _jsx(LocalStorageProvider, { children: _jsx(SocketProvider, { children: _jsx(HitProvider, { children: _jsx(OverviewProvider, { children: _jsx(AnalyticProvider, { children: _jsx(FavouriteProvider, { children: _jsx(UserListProvider, { children: children }) }) }) }) }) }) }) }) }) }) }) }) }) }) }) }));
|
|
151
152
|
};
|
|
152
153
|
const AppProviderWrapper = () => {
|
|
153
|
-
return (_jsx(I18nextProvider, { i18n: i18n, defaultNS: "translation", children: _jsx(ApiConfigProvider, { children: _jsx(PluginProvider, { pluginStore: howlerPluginStore.pluginStore, children: _jsxs(MyAppProvider, { children: [_jsx(MyApp, {}), _jsx(Modal, {})] }) }) }) }));
|
|
154
|
+
return (_jsx(I18nextProvider, { i18n: i18n, defaultNS: "translation", children: _jsx(ApiConfigProvider, { children: _jsx(PluginProvider, { pluginStore: howlerPluginStore.pluginStore, children: _jsx(AppBarProvider, { children: _jsxs(MyAppProvider, { children: [_jsx(MyApp, {}), _jsx(Modal, {})] }) }) }) }) }));
|
|
154
155
|
};
|
|
155
156
|
const router = createBrowserRouter([
|
|
156
157
|
{
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { FC, PropsWithChildren, ReactNode } from 'react';
|
|
2
|
+
interface AppBarItem {
|
|
3
|
+
id: string;
|
|
4
|
+
component: ReactNode;
|
|
5
|
+
}
|
|
6
|
+
export interface AppBarContextType {
|
|
7
|
+
leftItems: AppBarItem[];
|
|
8
|
+
rightItems: AppBarItem[];
|
|
9
|
+
addToAppBar: (alignment: 'left' | 'right', id: string, component: ReactNode) => void;
|
|
10
|
+
removeFromAppBar: (id: string) => void;
|
|
11
|
+
}
|
|
12
|
+
export declare const AppBarContext: import("react").Context<AppBarContextType>;
|
|
13
|
+
declare const AppBarProvider: FC<PropsWithChildren>;
|
|
14
|
+
export default AppBarProvider;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useCallback, useState } from 'react';
|
|
3
|
+
export const AppBarContext = createContext(null);
|
|
4
|
+
const AppBarProvider = ({ children }) => {
|
|
5
|
+
const [leftItems, setLeftItems] = useState([]);
|
|
6
|
+
const [rightItems, setRightItems] = useState([]);
|
|
7
|
+
const addToAppBar = useCallback((alignment, id, component) => {
|
|
8
|
+
const setter = alignment === 'left' ? setLeftItems : setRightItems;
|
|
9
|
+
setter(prev => {
|
|
10
|
+
if (prev.some(item => item.id === id)) {
|
|
11
|
+
return prev;
|
|
12
|
+
}
|
|
13
|
+
return [...prev, { id, component }];
|
|
14
|
+
});
|
|
15
|
+
}, []);
|
|
16
|
+
const removeFromAppBar = useCallback((id) => {
|
|
17
|
+
setLeftItems(prev => prev.filter(item => item.id !== id));
|
|
18
|
+
setRightItems(prev => prev.filter(item => item.id !== id));
|
|
19
|
+
}, []);
|
|
20
|
+
return (_jsx(AppBarContext.Provider, { value: { leftItems, rightItems, addToAppBar, removeFromAppBar }, children: children }));
|
|
21
|
+
};
|
|
22
|
+
export default AppBarProvider;
|
|
@@ -9,7 +9,7 @@ import { StorageKey } from '@cccsaurora/howler-ui/utils/constants';
|
|
|
9
9
|
import { HitLayout } from './HitLayout';
|
|
10
10
|
import DefaultOutline from './outlines/DefaultOutline';
|
|
11
11
|
export const DEFAULT_FIELDS = ['event.created', 'howler.id', 'howler.hash'];
|
|
12
|
-
const HitOutline = ({ hit, layout }) => {
|
|
12
|
+
const HitOutline = ({ hit, layout, forceAllFields = false }) => {
|
|
13
13
|
const { t } = useTranslation();
|
|
14
14
|
const { getMatchingTemplate } = useMatchers();
|
|
15
15
|
const [templateFieldCount] = useMyLocalStorageItem(StorageKey.TEMPLATE_FIELD_COUNT, null);
|
|
@@ -23,7 +23,9 @@ const HitOutline = ({ hit, layout }) => {
|
|
|
23
23
|
hit,
|
|
24
24
|
layout,
|
|
25
25
|
template,
|
|
26
|
-
fields: !isNil(templateFieldCount)
|
|
26
|
+
fields: !isNil(templateFieldCount) && !forceAllFields
|
|
27
|
+
? [...template.keys].slice(0, templateFieldCount)
|
|
28
|
+
: template.keys,
|
|
27
29
|
readonly: template.type === 'readonly'
|
|
28
30
|
});
|
|
29
31
|
}
|
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Api, Article, Book, Code, Dashboard, Description, ExitToApp, FormatListBulleted, Help, HelpCenter, Key, ManageSearch, QueryStats, SavedSearch, Search, Settings, SettingsSuggest, Shield, Storage, SupervisorAccount, Terminal, Topic } from '@mui/icons-material';
|
|
3
|
+
import { Stack } from '@mui/material';
|
|
3
4
|
import { AppBrand } from '@cccsaurora/howler-ui/branding/AppBrand';
|
|
5
|
+
import { AppBarContext } from '@cccsaurora/howler-ui/components/app/providers/AppBarProvider';
|
|
4
6
|
import Classification from '@cccsaurora/howler-ui/components/elements/display/Classification';
|
|
5
7
|
import DocumentationButton from '@cccsaurora/howler-ui/components/elements/display/DocumentationButton';
|
|
6
8
|
import howlerPluginStore from '@cccsaurora/howler-ui/plugins/store';
|
|
7
|
-
import { useMemo } from 'react';
|
|
9
|
+
import { Fragment, useContext, useMemo } from 'react';
|
|
8
10
|
import AppMenuBuilder from '@cccsaurora/howler-ui/utils/menuUtils';
|
|
9
11
|
// This is your App Name that will be displayed in the left drawer and the top navbar
|
|
10
12
|
const APP_NAME = 'howler';
|
|
11
13
|
const useMyPreferences = () => {
|
|
14
|
+
const { leftItems, rightItems } = useContext(AppBarContext);
|
|
12
15
|
// The following menu items will show up in the Left Navigation Drawer
|
|
13
16
|
const MENU_ITEMS = useMemo(() => {
|
|
14
17
|
let defaultMenu = [
|
|
@@ -269,12 +272,12 @@ const useMyPreferences = () => {
|
|
|
269
272
|
adminMenuI18nKey: 'adminmenu',
|
|
270
273
|
quickSearchParam: 'query',
|
|
271
274
|
quickSearchURI: '/hits',
|
|
272
|
-
leftAfterBreadcrumbs: _jsx(DocumentationButton, {}),
|
|
273
|
-
rightBeforeSearch: _jsx(Classification, {})
|
|
275
|
+
leftAfterBreadcrumbs: (_jsxs(Stack, { direction: "row", spacing: 1, alignItems: "center", children: [_jsx(DocumentationButton, {}), leftItems.map(item => (_jsx(Fragment, { children: item.component }, item.id)))] })),
|
|
276
|
+
rightBeforeSearch: (_jsxs(Stack, { direction: "row", spacing: 1, alignItems: "center", pr: 1, children: [rightItems.map(item => (_jsx(Fragment, { children: item.component }, item.id))), _jsx(Classification, {})] }))
|
|
274
277
|
},
|
|
275
278
|
leftnav: {
|
|
276
279
|
elements: MENU_ITEMS
|
|
277
280
|
}
|
|
278
|
-
}), [USER_MENU_ITEMS, ADMIN_MENU_ITEMS, MENU_ITEMS]);
|
|
281
|
+
}), [USER_MENU_ITEMS, ADMIN_MENU_ITEMS, MENU_ITEMS, leftItems, rightItems]);
|
|
279
282
|
};
|
|
280
283
|
export default useMyPreferences;
|
|
@@ -6,6 +6,7 @@ import api from '@cccsaurora/howler-ui/api';
|
|
|
6
6
|
import PageCenter from '@cccsaurora/howler-ui/commons/components/pages/PageCenter';
|
|
7
7
|
import { ParameterContext } from '@cccsaurora/howler-ui/components/app/providers/ParameterProvider';
|
|
8
8
|
import useMyApi from '@cccsaurora/howler-ui/components/hooks/useMyApi';
|
|
9
|
+
import useMySnackbar from '@cccsaurora/howler-ui/components/hooks/useMySnackbar';
|
|
9
10
|
import { isEqual, omit, uniqBy } from 'lodash-es';
|
|
10
11
|
import { memo, useCallback, useEffect, useMemo, useState } from 'react';
|
|
11
12
|
import { useTranslation } from 'react-i18next';
|
|
@@ -19,6 +20,7 @@ const DossierEditor = () => {
|
|
|
19
20
|
const { t, i18n } = useTranslation();
|
|
20
21
|
const params = useParams();
|
|
21
22
|
const { dispatchApi } = useMyApi();
|
|
23
|
+
const { showSuccessMessage } = useMySnackbar();
|
|
22
24
|
const navigate = useNavigate();
|
|
23
25
|
const [searchParams, setSearchParams] = useSearchParams();
|
|
24
26
|
const setQuery = useContextSelector(ParameterContext, ctx => ctx.setQuery);
|
|
@@ -127,16 +129,18 @@ const DossierEditor = () => {
|
|
|
127
129
|
try {
|
|
128
130
|
if (!params.id) {
|
|
129
131
|
const result = await dispatchApi(api.dossier.post(dossier));
|
|
132
|
+
showSuccessMessage(t('route.dossiers.manager.create.success'));
|
|
130
133
|
navigate(`/dossiers/${result.dossier_id}/edit`);
|
|
131
134
|
}
|
|
132
135
|
else {
|
|
133
136
|
setDossier(await dispatchApi(api.dossier.put(dossier.dossier_id, omit(dossier, ['dossier_id', 'id']))));
|
|
137
|
+
showSuccessMessage(t('route.dossiers.manager.edit.success'));
|
|
134
138
|
}
|
|
135
139
|
}
|
|
136
140
|
finally {
|
|
137
141
|
setLoading(false);
|
|
138
142
|
}
|
|
139
|
-
}, [dispatchApi, dossier, navigate, params.id]);
|
|
143
|
+
}, [dispatchApi, dossier, navigate, params.id, showSuccessMessage, t]);
|
|
140
144
|
useEffect(() => {
|
|
141
145
|
if (!params.id) {
|
|
142
146
|
return;
|
|
@@ -163,7 +163,7 @@ const InformationPane = ({ onClose }) => {
|
|
|
163
163
|
const hasError = useMemo(() => !validateRegex(filter), [filter]);
|
|
164
164
|
return (_jsxs(VSBox, { top: 10, sx: { height: '100%', flex: 1 }, children: [_jsxs(Stack, { direction: "column", flex: 1, sx: { overflowY: 'auto', flexGrow: 1 }, position: "relative", spacing: 1, ml: 2, children: [_jsxs(Stack, { direction: "row", alignItems: "center", spacing: 0.5, flexShrink: 0, pr: 2, sx: [hit?.howler?.is_bundle && { position: 'absolute', top: 1, right: 0, zIndex: 1100 }], children: [_jsx(FlexOne, {}), onClose && !location.pathname.startsWith('/bundles') && (_jsx(TuiIconButton, { size: "small", onClick: onClose, tooltip: t('hit.panel.details.exit'), children: _jsx(Clear, {}) })), _jsx(SocketBadge, { size: "small" }), analytic && (_jsx(TuiIconButton, { size: "small", tooltip: t('hit.panel.analytic.open'), disabled: !analytic || loading, route: `/analytics/${analytic.analytic_id}`, children: _jsx(QueryStats, {}) })), hit?.howler.bundles?.length > 0 && _jsx(BundleButton, { ids: hit.howler.bundles, disabled: loading }), !!hit && !hit.howler.is_bundle && (_jsx(TuiIconButton, { tooltip: t('hit.panel.open'), href: `/hits/${selected}`, disabled: !hit || loading, size: "small", target: "_blank", children: _jsx(OpenInNew, {}) }))] }), _jsx(Box, { pr: 2, children: header }), !!hit &&
|
|
165
165
|
!hit.howler.is_bundle &&
|
|
166
|
-
(!loading ? (_jsxs(_Fragment, { children: [_jsx(HitOutline, { hit: hit, layout: HitLayout.DENSE }), _jsx(HitLabels, { hit: hit })] })) : (_jsx(Skeleton, { height: 124 }))), _jsx(HitLinks, { hit: hit, analytic: analytic, dossiers: dossiers }), _jsxs(VSBoxHeader, { ml: -1, mr: -1, pb: 1, sx: { top: '0px' }, children: [_jsxs(Tabs, { value: tab === 'overview' && !hasOverview ? 'details' : tab, sx: {
|
|
166
|
+
(!loading ? (_jsxs(_Fragment, { children: [_jsx(HitOutline, { hit: hit, layout: HitLayout.DENSE, forceAllFields: true }), _jsx(HitLabels, { hit: hit })] })) : (_jsx(Skeleton, { height: 124 }))), _jsx(HitLinks, { hit: hit, analytic: analytic, dossiers: dossiers }), _jsxs(VSBoxHeader, { ml: -1, mr: -1, pb: 1, sx: { top: '0px' }, children: [_jsxs(Tabs, { value: tab === 'overview' && !hasOverview ? 'details' : tab, sx: {
|
|
167
167
|
display: 'flex',
|
|
168
168
|
flexDirection: 'row',
|
|
169
169
|
pr: 2,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { ArrowDropDown, List, Settings, TableChart, ViewComfy, ViewCompact, ViewModule } from '@mui/icons-material';
|
|
3
|
-
import { FormLabel, Stack, ToggleButton, ToggleButtonGroup } from '@mui/material';
|
|
2
|
+
import { ArrowDropDown, InfoOutlined, List, Settings, TableChart, ViewComfy, ViewCompact, ViewModule } from '@mui/icons-material';
|
|
3
|
+
import { Checkbox, Divider, FormLabel, Stack, TextField, ToggleButton, ToggleButtonGroup, Tooltip } from '@mui/material';
|
|
4
4
|
import { HitSearchContext } from '@cccsaurora/howler-ui/components/app/providers/HitSearchProvider';
|
|
5
5
|
import ChipPopper from '@cccsaurora/howler-ui/components/elements/display/ChipPopper';
|
|
6
6
|
import { HitLayout } from '@cccsaurora/howler-ui/components/elements/hit/HitLayout';
|
|
@@ -13,6 +13,12 @@ const LayoutSettings = () => {
|
|
|
13
13
|
const displayType = useContextSelector(HitSearchContext, ctx => ctx.displayType);
|
|
14
14
|
const setDisplayType = useContextSelector(HitSearchContext, ctx => ctx.setDisplayType);
|
|
15
15
|
const [hitLayout, setHitLayout] = useMyLocalStorageItem(StorageKey.HIT_LAYOUT, false);
|
|
16
|
-
|
|
16
|
+
const [templateFieldCount, setTemplateFieldCount] = useMyLocalStorageItem(StorageKey.TEMPLATE_FIELD_COUNT, null);
|
|
17
|
+
return (_jsx(ChipPopper, { icon: _jsx(Tooltip, { title: t('search.layout.settings'), children: _jsx(Settings, {}) }), deleteIcon: _jsx(ArrowDropDown, {}), toggleOnDelete: true, disablePortal: false, slotProps: { chip: { size: 'medium', 'aria-label': t('search.layout.settings') } }, placement: "bottom-end", children: _jsxs(Stack, { spacing: 1, alignItems: "start", children: [_jsxs(Stack, { direction: "row", spacing: 0.5, alignItems: "center", alignSelf: "stretch", children: [_jsx(FormLabel, { id: "display_type", children: t('page.settings.local.hits.display_type') }), _jsx("div", { style: { flex: 1 } }), _jsx(Tooltip, { title: t('page.settings.local.hits.display_type.description'), children: _jsx(InfoOutlined, { fontSize: "inherit" }) })] }), _jsxs(ToggleButtonGroup, { exclusive: true, value: displayType, onChange: (__, value) => setDisplayType(value), size: "small", "aria-labelledby": "display_type", children: [_jsx(ToggleButton, { value: "list", children: _jsxs(Stack, { direction: "row", spacing: 0.5, children: [_jsx(List, {}), _jsx("span", { children: t('page.settings.local.hits.display_type.list') })] }) }), _jsx(ToggleButton, { value: "grid", children: _jsxs(Stack, { direction: "row", spacing: 0.5, children: [_jsx(TableChart, {}), _jsx("span", { children: t('page.settings.local.hits.display_type.grid') })] }) })] }), _jsx(Divider, { flexItem: true }), _jsxs(Stack, { direction: "row", spacing: 0.5, alignItems: "center", alignSelf: "stretch", children: [_jsx(FormLabel, { id: "layout", children: t('page.settings.local.hits.layout') }), _jsx("div", { style: { flex: 1 } }), _jsx(Tooltip, { title: t('page.settings.local.hits.layout.description'), children: _jsx(InfoOutlined, { fontSize: "inherit" }) })] }), _jsxs(ToggleButtonGroup, { exclusive: true, size: "small", value: hitLayout, onChange: (_, value) => setHitLayout(value), "aria-labelledby": "layout", children: [_jsx(ToggleButton, { value: HitLayout.DENSE, children: _jsxs(Stack, { direction: "row", spacing: 0.5, children: [_jsx(ViewCompact, {}), _jsx("span", { children: t('page.settings.local.hits.layout.dense') })] }) }), _jsx(ToggleButton, { value: HitLayout.NORMAL, children: _jsxs(Stack, { direction: "row", spacing: 0.5, children: [_jsx(ViewModule, {}), _jsx("span", { children: t('page.settings.local.hits.layout.normal') })] }) }), _jsx(ToggleButton, { value: HitLayout.COMFY, children: _jsxs(Stack, { direction: "row", spacing: 0.5, children: [_jsx(ViewComfy, {}), _jsx("span", { children: t('page.settings.local.hits.layout.comfy') })] }) })] }), _jsx(Divider, { flexItem: true }), _jsxs(Stack, { direction: "row", spacing: 0.5, alignItems: "center", alignSelf: "stretch", children: [_jsx(FormLabel, { id: "field_count", children: t('page.settings.local.hits.field_count') }), _jsx("div", { style: { flex: 1 } }), _jsx(Tooltip, { title: t('page.settings.local.hits.field_count.description'), children: _jsx(InfoOutlined, { fontSize: "inherit" }) })] }), _jsxs(Stack, { direction: "row", spacing: 0.5, alignSelf: "stretch", children: [_jsx(Checkbox, { checked: templateFieldCount !== null, onChange: (_, checked) => setTemplateFieldCount(checked ? 3 : null), size: "small" }), _jsx(TextField, { type: "number", size: "small", disabled: templateFieldCount === null, value: templateFieldCount ?? 3, fullWidth: true, onChange: e => {
|
|
18
|
+
const val = parseInt(e.target.value);
|
|
19
|
+
if (!isNaN(val)) {
|
|
20
|
+
setTemplateFieldCount(Math.min(15, Math.max(0, val)));
|
|
21
|
+
}
|
|
22
|
+
}, inputProps: { min: 0, max: 15, 'aria-labelledby': 'field_count' } })] })] }) }));
|
|
17
23
|
};
|
|
18
24
|
export default LayoutSettings;
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Add, Check } from '@mui/icons-material';
|
|
3
|
-
import { Autocomplete, Chip, Divider, Grid, IconButton,
|
|
2
|
+
import { Add, Check, Settings, TableChart } from '@mui/icons-material';
|
|
3
|
+
import { Autocomplete, Chip, Divider, Grid, IconButton, Stack, TextField } from '@mui/material';
|
|
4
4
|
import useMatchers from '@cccsaurora/howler-ui/components/app/hooks/useMatchers';
|
|
5
5
|
import { FieldContext } from '@cccsaurora/howler-ui/components/app/providers/FieldProvider';
|
|
6
6
|
import { HitSearchContext } from '@cccsaurora/howler-ui/components/app/providers/HitSearchProvider';
|
|
7
|
+
import ChipPopper from '@cccsaurora/howler-ui/components/elements/display/ChipPopper';
|
|
7
8
|
import { has, sortBy, uniq } from 'lodash-es';
|
|
8
9
|
import { memo, useContext, useEffect, useMemo, useState } from 'react';
|
|
9
10
|
import { useTranslation } from 'react-i18next';
|
|
10
11
|
import { useContextSelector } from 'use-context-selector';
|
|
11
|
-
const AddColumnModal = ({
|
|
12
|
+
const AddColumnModal = ({ addColumn, columns }) => {
|
|
12
13
|
const { t } = useTranslation();
|
|
13
14
|
const { hitFields } = useContext(FieldContext);
|
|
14
15
|
const response = useContextSelector(HitSearchContext, ctx => ctx.response);
|
|
@@ -21,7 +22,7 @@ const AddColumnModal = ({ open, onClose, anchorEl, addColumn, columns }) => {
|
|
|
21
22
|
setSuggestions(uniq((await Promise.all((response?.items ?? []).map(async (_hit) => (has(_hit, '__template') ? _hit.__template?.keys : (await getMatchingTemplate(_hit))?.keys) ?? []))).flat()));
|
|
22
23
|
})();
|
|
23
24
|
}, [getMatchingTemplate, response?.items]);
|
|
24
|
-
return (_jsx(
|
|
25
|
+
return (_jsx(ChipPopper, { icon: _jsx(TableChart, {}), deleteIcon: _jsx(Settings, {}), toggleOnDelete: true, slotProps: { chip: { size: 'small' } }, 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: () => {
|
|
25
26
|
addColumn(columnToAdd);
|
|
26
27
|
setColumnToAdd(null);
|
|
27
28
|
}, children: _jsx(Add, {}) })] }), _jsx(Divider, { orientation: "horizontal" }), _jsx(Grid, { container: true, spacing: 1, children: sortBy(suggestions.map(key => ({ key, used: columns.includes(key) })), 'used').map(({ key, used }) => {
|
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { DndContext, KeyboardSensor, PointerSensor, pointerWithin, useSensor, useSensors } from '@dnd-kit/core';
|
|
3
3
|
import { arrayMove, SortableContext, sortableKeyboardCoordinates } from '@dnd-kit/sortable';
|
|
4
|
-
import {
|
|
4
|
+
import { 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
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';
|
|
10
|
-
import FlexOne from '@cccsaurora/howler-ui/components/elements/addons/layout/FlexOne';
|
|
11
10
|
import SearchTotal from '@cccsaurora/howler-ui/components/elements/addons/search/SearchTotal';
|
|
12
11
|
import DevelopmentBanner from '@cccsaurora/howler-ui/components/elements/display/features/DevelopmentBanner';
|
|
13
|
-
import DevelopmentIcon from '@cccsaurora/howler-ui/components/elements/display/features/DevelopmentIcon';
|
|
14
12
|
import useHitSelection from '@cccsaurora/howler-ui/components/hooks/useHitSelection';
|
|
15
13
|
import { useMyLocalStorageItem } from '@cccsaurora/howler-ui/components/hooks/useMyLocalStorage';
|
|
14
|
+
import { uniq } from 'lodash-es';
|
|
16
15
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
17
16
|
import { useTranslation } from 'react-i18next';
|
|
18
17
|
import { useContextSelector } from 'use-context-selector';
|
|
@@ -38,16 +37,14 @@ const HitGrid = () => {
|
|
|
38
37
|
const query = useContextSelector(ParameterContext, ctx => ctx.query);
|
|
39
38
|
const selected = useContextSelector(ParameterContext, ctx => ctx.selected);
|
|
40
39
|
const [collapseMainColumn, setCollapseMainColumn] = useMyLocalStorageItem(StorageKey.GRID_COLLAPSE_COLUMN, false);
|
|
41
|
-
const [
|
|
42
|
-
const columnModalRef = useRef();
|
|
43
|
-
const [columns, setColumns] = useState([
|
|
40
|
+
const [columns, setColumns] = useMyLocalStorageItem(StorageKey.GRID_COLUMNS, [
|
|
44
41
|
'howler.outline.threat',
|
|
45
42
|
'howler.outline.target',
|
|
46
43
|
'howler.outline.indicators',
|
|
47
44
|
'howler.outline.summary'
|
|
48
45
|
]);
|
|
49
|
-
const [columnWidths, setColumnWidths] =
|
|
50
|
-
const [
|
|
46
|
+
const [columnWidths, setColumnWidths] = useMyLocalStorageItem(StorageKey.GRID_COLUMN_WIDTHS, {});
|
|
47
|
+
const [analyticIds, setAnalyticIds] = useState({});
|
|
51
48
|
const resizingCol = useRef();
|
|
52
49
|
const showSelectBar = useMemo(() => {
|
|
53
50
|
if (selectedHits.length > 1) {
|
|
@@ -78,10 +75,10 @@ const HitGrid = () => {
|
|
|
78
75
|
}, []);
|
|
79
76
|
const onMouseUp = useCallback(() => {
|
|
80
77
|
const [col, element] = resizingCol.current;
|
|
81
|
-
setColumnWidths(
|
|
82
|
-
...
|
|
78
|
+
setColumnWidths({
|
|
79
|
+
...columnWidths,
|
|
83
80
|
[col]: element.style.width
|
|
84
|
-
})
|
|
81
|
+
});
|
|
85
82
|
element.style.width = null;
|
|
86
83
|
element.style.maxWidth = null;
|
|
87
84
|
document.querySelectorAll(`.col-${col.replaceAll('.', '-')}`).forEach(el => {
|
|
@@ -90,7 +87,7 @@ const HitGrid = () => {
|
|
|
90
87
|
});
|
|
91
88
|
window.removeEventListener('mousemove', onMouseMove);
|
|
92
89
|
window.removeEventListener('mouseup', onMouseUp);
|
|
93
|
-
}, [onMouseMove]);
|
|
90
|
+
}, [columnWidths, onMouseMove, setColumnWidths]);
|
|
94
91
|
const onMouseDown = useCallback((col, event) => {
|
|
95
92
|
event.stopPropagation();
|
|
96
93
|
event.preventDefault();
|
|
@@ -106,12 +103,12 @@ const HitGrid = () => {
|
|
|
106
103
|
}, [query, search]);
|
|
107
104
|
const handleDragEnd = useCallback((event) => {
|
|
108
105
|
const { active, over } = event;
|
|
109
|
-
if (active.id !== over.id) {
|
|
106
|
+
if (over && active.id !== over.id) {
|
|
110
107
|
const oldIndex = (columns ?? []).findIndex(entry => entry === active.id);
|
|
111
108
|
const newIndex = (columns ?? []).findIndex(entry => entry === over.id);
|
|
112
109
|
setColumns(arrayMove(columns, oldIndex, newIndex));
|
|
113
110
|
}
|
|
114
|
-
}, [columns]);
|
|
111
|
+
}, [columns, setColumns]);
|
|
115
112
|
const getSelectedId = useCallback((event) => {
|
|
116
113
|
const target = event.target;
|
|
117
114
|
const selectedElement = target.closest('[id]');
|
|
@@ -120,14 +117,14 @@ const HitGrid = () => {
|
|
|
120
117
|
}
|
|
121
118
|
return selectedElement.id;
|
|
122
119
|
}, []);
|
|
123
|
-
return (_jsxs(Stack, { spacing: 1, p: 2, width: "100%", sx: { overflow: 'hidden', height: `calc(100vh - ${theme.spacing(showSelectBar ? 13 : 8)})` }, children: [_jsx(DevelopmentBanner, {}), _jsxs(Stack, { direction: "row", justifyContent: "space-between",
|
|
120
|
+
return (_jsxs(Stack, { spacing: 1, p: 2, width: "100%", sx: { overflow: 'hidden', height: `calc(100vh - ${theme.spacing(showSelectBar ? 13 : 8)})` }, children: [_jsx(DevelopmentBanner, {}), _jsxs(Stack, { direction: "row", justifyContent: "space-between", children: [_jsx(Typography, { sx: { color: 'text.secondary', fontSize: '0.9em', fontStyle: 'italic', mb: 0.5, textAlign: 'left' }, variant: "body2", children: t('hit.search.prompt') }), response && (_jsx(SearchTotal, { sx: { color: 'text.secondary', fontSize: '0.9em', fontStyle: 'italic', mb: 0.5 }, variant: "body2", offset: response.offset, pageLength: response.rows, total: response.total }))] }), _jsxs(Stack, { direction: "row", spacing: 1, children: [_jsxs(Stack, { position: "relative", flex: 1, children: [_jsx(HitQuery, { searching: searching, triggerSearch: search, compact: true }), searching && (_jsx(LinearProgress, { sx: {
|
|
124
121
|
position: 'absolute',
|
|
125
122
|
left: 0,
|
|
126
123
|
right: 0,
|
|
127
124
|
bottom: 0,
|
|
128
125
|
borderBottomLeftRadius: theme.shape.borderRadius,
|
|
129
126
|
borderBottomRightRadius: theme.shape.borderRadius
|
|
130
|
-
} }))] }), _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(Stack, { direction: "row", spacing: 1, width: "100%",
|
|
127
|
+
} }))] }), _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(Stack, { direction: "row", spacing: 1, width: "100%", alignItems: "center", children: [_jsx(QuerySettings, { boxSx: { flex: 1 } }), _jsx(AddColumnModal, { columns: columns, addColumn: key => setColumns(uniq([...columns, key])) })] }), _jsxs(Stack, { component: Paper, spacing: 1, width: "100%", height: "100%", sx: { overflow: 'auto', flex: 1 }, onScroll: onScroll, children: [_jsxs(Table, { sx: { '& td,th': { px: 1, py: 0.25, whiteSpace: 'nowrap' } }, children: [_jsx(TableHead, { children: _jsxs(TableRow, { children: [_jsx(TableCell, { sx: {
|
|
131
128
|
borderRight: 'thin solid',
|
|
132
129
|
borderRightColor: 'divider'
|
|
133
130
|
}, children: _jsx(IconButton, { onClick: () => setCollapseMainColumn(!collapseMainColumn), children: collapseMainColumn ? (_jsx(FormatIndentIncrease, { fontSize: "small" })) : (_jsx(FormatIndentDecrease, { fontSize: "small" })) }) }), _jsx(DndContext, { sensors: sensors, collisionDetection: pointerWithin, onDragEnd: handleDragEnd, children: _jsx(SortableContext, { items: columns, children: columns.map(col => (_jsx(ColumnHeader, { col: col, width: columnWidths[col], onMouseDown: onMouseDown, setColumns: setColumns }, col))) }) }), _jsx(TableCell, { sx: { width: '100%' } })] }) }), _jsxs(HitContextMenu, { Component: TableBody, getSelectedId: getSelectedId, children: [response?.items.map(hit => (_jsx(HitRow, { hit: hit, analyticIds: analyticIds, columns: columns, columnWidths: columnWidths, collapseMainColumn: collapseMainColumn, onClick: onClick }, hit.howler.id))), _jsx(TableRow, { children: _jsx(TableCell, { colSpan: columns.length + 2, children: _jsx(Stack, { alignItems: "center", justifyContent: "center", py: 0.5, px: 1, children: _jsx(IconButton, { onClick: () => search(query, true), children: _jsx(Search, {}) }) }) }) })] })] }), (response?.total ?? 0) < 1 && (_jsx(Stack, { direction: "row", spacing: 1, alignItems: "center", p: 1, justifyContent: "center", flex: 1, children: _jsxs(Typography, { variant: "h3", color: "text.secondary", display: "flex", flexDirection: "row", alignItems: "center", children: [_jsx(Info, { fontSize: "inherit", sx: { color: 'text.secondary', mr: 1 } }), _jsx("span", { children: t('app.list.empty') })] }) }))] })] }));
|
|
@@ -128,7 +128,7 @@ const HitViewer = () => {
|
|
|
128
128
|
display: 'flex',
|
|
129
129
|
'& > .MuiPaper-root': { flex: 1 },
|
|
130
130
|
mr: orientation === 'vertical' ? 0 : -2
|
|
131
|
-
}, children: [_jsx(HowlerCard, { tabIndex: 0, sx: { position: 'relative' }, children: _jsxs(CardContent, { children: [_jsx(HitBanner, { hit: hit, layout: HitLayout.COMFY, useListener: true }), _jsx(HitOutline, { hit: hit, layout: HitLayout.COMFY }), _jsx(HitLabels, { hit: hit }), _jsx(HitLinks, { hit: hit, analytic: analytic, dossiers: dossiers })] }) }), !isUnderLg && (_jsxs(Stack, { spacing: 1, sx: {
|
|
131
|
+
}, children: [_jsx(HowlerCard, { tabIndex: 0, sx: { position: 'relative' }, children: _jsxs(CardContent, { children: [_jsx(HitBanner, { hit: hit, layout: HitLayout.COMFY, useListener: true }), _jsx(HitOutline, { hit: hit, layout: HitLayout.COMFY, forceAllFields: true }), _jsx(HitLabels, { hit: hit }), _jsx(HitLinks, { hit: hit, analytic: analytic, dossiers: dossiers })] }) }), !isUnderLg && (_jsxs(Stack, { spacing: 1, sx: {
|
|
132
132
|
position: 'absolute',
|
|
133
133
|
top: theme.spacing(2),
|
|
134
134
|
right: theme.spacing(-6)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type FC } from 'react';
|
|
2
|
+
interface HomeSettingsProps {
|
|
3
|
+
/** Whether the dashboard is currently in edit mode. Disables the "Edit" menu item. */
|
|
4
|
+
isEditing: boolean;
|
|
5
|
+
/** Current auto-refresh interval in seconds. */
|
|
6
|
+
refreshRate: number;
|
|
7
|
+
/** Called when the user selects a new refresh rate. */
|
|
8
|
+
onRefreshRateChange: (rate: number) => void;
|
|
9
|
+
/** Called when the user clicks the "Edit" menu item. */
|
|
10
|
+
onEdit: () => void;
|
|
11
|
+
}
|
|
12
|
+
declare const HomeSettings: FC<HomeSettingsProps>;
|
|
13
|
+
export default HomeSettings;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { Edit, Settings } from '@mui/icons-material';
|
|
3
|
+
import { FormControl, FormLabel, IconButton, ListItemIcon, Menu, MenuItem, Slider, Tooltip } from '@mui/material';
|
|
4
|
+
import { useState } from 'react';
|
|
5
|
+
import { useTranslation } from 'react-i18next';
|
|
6
|
+
const REFRESH_RATES = [15, 30, 60, 300];
|
|
7
|
+
const HomeSettings = ({ isEditing, refreshRate, onRefreshRateChange, onEdit }) => {
|
|
8
|
+
const { t } = useTranslation();
|
|
9
|
+
const [anchorEl, setAnchorEl] = useState(null);
|
|
10
|
+
return (_jsxs(_Fragment, { children: [_jsx(Tooltip, { title: t('page.dashboard.settings.edit'), children: _jsx(IconButton, { onClick: e => setAnchorEl(e.currentTarget), size: "small", children: _jsx(Settings, { color: "primary" }) }) }), _jsxs(Menu, { id: "settings-menu", anchorEl: anchorEl, open: !!anchorEl, onClose: () => setAnchorEl(null), children: [_jsxs(MenuItem, { disabled: isEditing, onClick: () => {
|
|
11
|
+
setAnchorEl(null);
|
|
12
|
+
onEdit();
|
|
13
|
+
}, children: [_jsx(ListItemIcon, { children: _jsx(Edit, {}) }), t('page.dashboard.settings.edit')] }), _jsx(MenuItem, { disableRipple: true, disableTouchRipple: true, sx: { '&:hover': { bgcolor: 'transparent' }, cursor: 'default' }, children: _jsxs(FormControl, { sx: { px: 2, py: 1, minWidth: 250, pointerEvents: 'auto' }, children: [_jsx(FormLabel, { id: "refresh-rate-label", sx: { mb: 2 }, children: t('page.dashboard.settings.refreshRate') }), _jsx(Slider, { "aria-labelledby": "refresh-rate-label", value: REFRESH_RATES.indexOf(refreshRate), onChange: (_, value) => onRefreshRateChange(REFRESH_RATES[value]), step: 1, marks: [
|
|
14
|
+
{ value: 0, label: '15s' },
|
|
15
|
+
{ value: 1, label: '30s' },
|
|
16
|
+
{ value: 2, label: '1m' },
|
|
17
|
+
{ value: 3, label: '5m' }
|
|
18
|
+
], min: 0, max: 3, valueLabelDisplay: "auto", valueLabelFormat: value => {
|
|
19
|
+
const rates = ['15s', '30s', '1m', '5m'];
|
|
20
|
+
return rates[value] || '';
|
|
21
|
+
} })] }) })] })] }));
|
|
22
|
+
};
|
|
23
|
+
export default HomeSettings;
|
|
@@ -11,8 +11,9 @@ import useMyApi from '@cccsaurora/howler-ui/components/hooks/useMyApi';
|
|
|
11
11
|
import HitContextMenu from '@cccsaurora/howler-ui/components/routes/hits/search/HitContextMenu';
|
|
12
12
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
13
13
|
import { useTranslation } from 'react-i18next';
|
|
14
|
-
import { useNavigate } from 'react-router-dom';
|
|
14
|
+
import { Link, useNavigate } from 'react-router-dom';
|
|
15
15
|
import { useContextSelector } from 'use-context-selector';
|
|
16
|
+
import { buildViewUrl } from '@cccsaurora/howler-ui/utils/viewUtils';
|
|
16
17
|
// Custom hook to select hits by IDs with proper memoization
|
|
17
18
|
const useSelectHitsByIds = (hitIds) => {
|
|
18
19
|
const hitIdsRef = useRef(hitIds);
|
|
@@ -150,6 +151,6 @@ const ViewCard = ({ viewId, limit, refreshTick, onRefreshComplete }) => {
|
|
|
150
151
|
}
|
|
151
152
|
return selectedElement.id;
|
|
152
153
|
}, []);
|
|
153
|
-
return (_jsx(Card, { variant: "outlined", sx: { height: '100%' }, children: _jsxs(Stack, { spacing: 1, sx: { p: 1, minHeight: 100 }, children: [_jsxs(Stack, { direction: "row", spacing: 1, alignItems: "center", children: [_jsx(Typography, { variant: "h6", children: t(view?.title) || _jsx(Skeleton, { variant: "text", height: "2em", width: "100px" }) }), _jsx(IconButton, { size: "small", onClick: () => onClick(view.query), children: _jsx(OpenInNew, { fontSize: "small" }) })] }), loading ? (_jsxs(_Fragment, { children: [_jsx(Skeleton, { height: 150, width: "100%", variant: "rounded" }), _jsx(Skeleton, { height: 160, width: "100%", variant: "rounded" }), _jsx(Skeleton, { height: 140, width: "100%", variant: "rounded" })] })) : hits.length > 0 ? (_jsx(HitContextMenu, { getSelectedId: getSelectedId, children: hits.map(h => (_jsx(Card, { id: h.howler.id, variant: "outlined", sx: { cursor: 'pointer' }, onClick: () => navigate((h.howler.is_bundle ? '/bundles/' : '/hits/') + h.howler.id), children: _jsx(CardContent, { children: _jsx(HitBanner, { layout: HitLayout.DENSE, hit: h }) }) }, h.howler.id))) })) : (_jsx(AppListEmpty, {}))] }) }));
|
|
154
|
+
return (_jsx(Card, { variant: "outlined", sx: { height: '100%' }, children: _jsxs(Stack, { spacing: 1, sx: { p: 1, minHeight: 100 }, children: [_jsxs(Stack, { direction: "row", spacing: 1, alignItems: "center", children: [_jsx(Typography, { variant: "h6", children: t(view?.title) || _jsx(Skeleton, { variant: "text", height: "2em", width: "100px" }) }), _jsx(IconButton, { size: "small", component: Link, disabled: !view, to: view ? buildViewUrl(view) : '', onClick: () => onClick(view.query), children: _jsx(OpenInNew, { fontSize: "small" }) })] }), loading ? (_jsxs(_Fragment, { children: [_jsx(Skeleton, { height: 150, width: "100%", variant: "rounded" }), _jsx(Skeleton, { height: 160, width: "100%", variant: "rounded" }), _jsx(Skeleton, { height: 140, width: "100%", variant: "rounded" })] })) : hits.length > 0 ? (_jsx(HitContextMenu, { getSelectedId: getSelectedId, children: hits.map(h => (_jsx(Card, { id: h.howler.id, variant: "outlined", sx: { cursor: 'pointer' }, onClick: () => navigate((h.howler.is_bundle ? '/bundles/' : '/hits/') + h.howler.id), children: _jsx(CardContent, { children: _jsx(HitBanner, { layout: HitLayout.DENSE, hit: h }) }) }, h.howler.id))) })) : (_jsx(AppListEmpty, {}))] }) }));
|
|
154
155
|
};
|
|
155
156
|
export default ViewCard;
|
|
@@ -52,7 +52,7 @@ const ViewRefresh = forwardRef(({ refreshRate, viewCardCount, onRefresh }, ref)
|
|
|
52
52
|
clearTimeout(timerRef.current);
|
|
53
53
|
};
|
|
54
54
|
}, [progress, isRefreshing, refreshRate, triggerRefresh]);
|
|
55
|
-
return (_jsxs(Box, { sx: { position: 'relative', display: 'inline-flex' }, children: [isRefreshing ? (_jsx(CircularProgress, { variant: "indeterminate" })) : (_jsx(CircularProgress, { variant: "determinate", value: progress })), _jsx(Box, { sx: {
|
|
55
|
+
return (_jsxs(Box, { sx: { position: 'relative', display: 'inline-flex' }, children: [isRefreshing ? (_jsx(CircularProgress, { variant: "indeterminate", size: 32 })) : (_jsx(CircularProgress, { variant: "determinate", value: progress, size: 32 })), _jsx(Box, { sx: {
|
|
56
56
|
top: 0,
|
|
57
57
|
left: 0,
|
|
58
58
|
bottom: 0,
|
|
@@ -61,7 +61,7 @@ const ViewRefresh = forwardRef(({ refreshRate, viewCardCount, onRefresh }, ref)
|
|
|
61
61
|
display: 'flex',
|
|
62
62
|
alignItems: 'center',
|
|
63
63
|
justifyContent: 'center'
|
|
64
|
-
}, children: _jsx(Tooltip, { title: t('refresh'), children: _jsx(IconButton, { onClick: triggerRefresh, disabled: isRefreshing, color: "primary", children: _jsx(Refresh, {}) }) }) })] }));
|
|
64
|
+
}, children: _jsx(Tooltip, { title: t('refresh'), children: _jsx(IconButton, { onClick: triggerRefresh, disabled: isRefreshing, color: "primary", size: "small", children: _jsx(Refresh, {}) }) }) })] }));
|
|
65
65
|
});
|
|
66
66
|
ViewRefresh.displayName = 'ViewRefresh';
|
|
67
67
|
export default ViewRefresh;
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { DndContext, KeyboardSensor, PointerSensor, closestCenter, useSensor, useSensors } from '@dnd-kit/core';
|
|
3
3
|
import { SortableContext, arrayMove, sortableKeyboardCoordinates } from '@dnd-kit/sortable';
|
|
4
|
-
import { Cancel, Check, Close,
|
|
5
|
-
import { Alert, AlertTitle, CircularProgress,
|
|
4
|
+
import { Cancel, Check, Close, OpenInNew } from '@mui/icons-material';
|
|
5
|
+
import { Alert, AlertTitle, CircularProgress, Grid, IconButton, Stack, Typography } from '@mui/material';
|
|
6
6
|
import api from '@cccsaurora/howler-ui/api';
|
|
7
7
|
import { AppBrand } from '@cccsaurora/howler-ui/branding/AppBrand';
|
|
8
8
|
import { useAppUser } from '@cccsaurora/howler-ui/commons/components/app/hooks';
|
|
9
9
|
import PageCenter from '@cccsaurora/howler-ui/commons/components/pages/PageCenter';
|
|
10
|
+
import { AppBarContext } from '@cccsaurora/howler-ui/components/app/providers/AppBarProvider';
|
|
10
11
|
import CustomButton from '@cccsaurora/howler-ui/components/elements/addons/buttons/CustomButton';
|
|
11
12
|
import { useMyLocalStorageItem } from '@cccsaurora/howler-ui/components/hooks/useMyLocalStorage';
|
|
12
13
|
import useMyUserFunctions from '@cccsaurora/howler-ui/components/hooks/useMyUserFunctions';
|
|
13
14
|
import dayjs from 'dayjs';
|
|
14
15
|
import isEqual from 'lodash-es/isEqual';
|
|
15
|
-
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
16
|
+
import { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
|
16
17
|
import { useTranslation } from 'react-i18next';
|
|
17
18
|
import { Link } from 'react-router-dom';
|
|
18
19
|
import { StorageKey } from '@cccsaurora/howler-ui/utils/constants';
|
|
@@ -20,13 +21,14 @@ import ErrorBoundary from '../ErrorBoundary';
|
|
|
20
21
|
import AddNewCard from './AddNewCard';
|
|
21
22
|
import AnalyticCard, {} from './AnalyticCard';
|
|
22
23
|
import EntryWrapper from './EntryWrapper';
|
|
24
|
+
import HomeSettings from './HomeSettings';
|
|
23
25
|
import ViewCard, {} from './ViewCard';
|
|
24
26
|
import ViewRefresh, {} from './ViewRefresh';
|
|
25
27
|
const LUCENE_DATE_FMT = 'YYYY-MM-DD[T]HH:mm:ss';
|
|
26
|
-
const REFRESH_RATES = [15, 30, 60, 300];
|
|
27
28
|
const Home = () => {
|
|
28
29
|
const { t } = useTranslation();
|
|
29
30
|
const { user, setUser } = useAppUser();
|
|
31
|
+
const { addToAppBar, removeFromAppBar } = useContext(AppBarContext);
|
|
30
32
|
const { setDashboard, setRefreshRate: setRefreshRateBackend } = useMyUserFunctions();
|
|
31
33
|
const sensors = useSensors(useSensor(PointerSensor), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }));
|
|
32
34
|
const [lastViewed, setLastViewed] = useMyLocalStorageItem(StorageKey.LAST_VIEW, dayjs().utc().format(LUCENE_DATE_FMT));
|
|
@@ -34,7 +36,6 @@ const Home = () => {
|
|
|
34
36
|
const [isEditing, setIsEditing] = useState(false);
|
|
35
37
|
const [updatedHitTotal, setUpdatedHitTotal] = useState(0);
|
|
36
38
|
const [dashboard, setStateDashboard] = useState(user.dashboard ?? []);
|
|
37
|
-
const [openSettings, setOpenSettings] = useState(null);
|
|
38
39
|
const [refreshRate, setRefreshRate] = useState(user.refresh_rate ?? 15);
|
|
39
40
|
const [refreshTick, setRefreshTick] = useState(null);
|
|
40
41
|
const viewRefreshRef = useRef(null);
|
|
@@ -75,9 +76,6 @@ const Home = () => {
|
|
|
75
76
|
const handleRefresh = useCallback(() => {
|
|
76
77
|
setRefreshTick(Symbol());
|
|
77
78
|
}, []);
|
|
78
|
-
const handleOpenSettings = (event) => {
|
|
79
|
-
setOpenSettings(event.currentTarget);
|
|
80
|
-
};
|
|
81
79
|
const saveChanges = useCallback(async () => {
|
|
82
80
|
setLoading(true);
|
|
83
81
|
try {
|
|
@@ -119,18 +117,15 @@ const Home = () => {
|
|
|
119
117
|
};
|
|
120
118
|
}, []);
|
|
121
119
|
const viewCardCount = useMemo(() => (dashboard ?? []).filter(e => e.type === 'view').length, [dashboard]);
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const rates = ['15s', '30s', '1m', '5m'];
|
|
132
|
-
return rates[value] || '';
|
|
133
|
-
} })] }) })] })] }), 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: () => {
|
|
120
|
+
useEffect(() => {
|
|
121
|
+
addToAppBar('left', 'view_refresh', _jsx(ViewRefresh, { ref: viewRefreshRef, refreshRate: refreshRate, viewCardCount: viewCardCount, onRefresh: handleRefresh }));
|
|
122
|
+
addToAppBar('left', 'home_settings', _jsx(HomeSettings, { isEditing: isEditing, refreshRate: refreshRate, onRefreshRateChange: handleRefreshRateChange, onEdit: () => setIsEditing(true) }));
|
|
123
|
+
return () => {
|
|
124
|
+
removeFromAppBar('view_refresh');
|
|
125
|
+
removeFromAppBar('home_settings');
|
|
126
|
+
};
|
|
127
|
+
}, [addToAppBar, handleRefresh, handleRefreshRateChange, isEditing, refreshRate, removeFromAppBar, viewCardCount]);
|
|
128
|
+
return (_jsx(PageCenter, { maxWidth: "100%", textAlign: "left", height: "100%", children: _jsx(ErrorBoundary, { 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') })), isEditing && (_jsx(CustomButton, { variant: "outlined", size: "small", disabled: isEqual(dashboard, user.dashboard), color: 'success', startIcon: loading ? _jsx(CircularProgress, { size: 20 }) : _jsx(Check, {}), onClick: saveChanges, children: t('save') }))] }), 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: () => {
|
|
134
129
|
setLastViewed(dayjs().utc().format(LUCENE_DATE_FMT));
|
|
135
130
|
setUpdatedHitTotal(0);
|
|
136
131
|
}, 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: [
|
|
@@ -534,8 +534,10 @@
|
|
|
534
534
|
"route.dossiers.create": "New Dossier",
|
|
535
535
|
"route.dossiers.default": "Default",
|
|
536
536
|
"route.dossiers.edit": "Edit Dossier",
|
|
537
|
+
"route.dossiers.manager.create.success": "Dossier Created.",
|
|
537
538
|
"route.dossiers.manager.delete": "Delete Dossier",
|
|
538
539
|
"route.dossiers.manager.delete.success": "Dossier Removed.",
|
|
540
|
+
"route.dossiers.manager.edit.success": "Dossier Updated.",
|
|
539
541
|
"route.dossiers.manager.field.query": "Query",
|
|
540
542
|
"route.dossiers.manager.field.title": "Title",
|
|
541
543
|
"route.dossiers.manager.field.type": "Type",
|
|
@@ -539,8 +539,10 @@
|
|
|
539
539
|
"route.dossiers.create": "Nouveau dossier",
|
|
540
540
|
"route.dossiers.default": "Défaut",
|
|
541
541
|
"route.dossiers.edit": "Modifier le dossier",
|
|
542
|
+
"route.dossiers.manager.create.success": "Dossier crée.",
|
|
542
543
|
"route.dossiers.manager.delete": "Supprimer un dossier",
|
|
543
544
|
"route.dossiers.manager.delete.success": "Dossier supprimé.",
|
|
545
|
+
"route.dossiers.manager.edit.success": "Dossier mise à jour.",
|
|
544
546
|
"route.dossiers.manager.field.query": "Requête",
|
|
545
547
|
"route.dossiers.manager.field.title": "Titre",
|
|
546
548
|
"route.dossiers.manager.field.type": "Type",
|
|
@@ -635,7 +637,7 @@
|
|
|
635
637
|
"route.home.add.visualization.status.description": "Quel est l'état des hits créés au cours des trois derniers mois ?",
|
|
636
638
|
"route.home.add.visualization.unavailable": "Aucune option de visualisation disponible.",
|
|
637
639
|
"route.home.alert.updated.description": "Des mises à jour ont été apportées aux alertes auxquelles vous participez. Depuis votre dernière consultation, {{count}} alertes ont été mises à jour",
|
|
638
|
-
"route.home.alert.updated.title": "
|
|
640
|
+
"route.home.alert.updated.title": "Mises à jour d'alert",
|
|
639
641
|
"route.home.description": "Bienvenue sur Howler. Vous pouvez ajouter des éléments à votre tableau de bord en utilisant les boutons ci-dessus.",
|
|
640
642
|
"route.home.title": "Howler",
|
|
641
643
|
"route.integrations": "Intégrations",
|
package/package.json
CHANGED
package/utils/constants.d.ts
CHANGED
|
@@ -55,6 +55,8 @@ export declare enum StorageKey {
|
|
|
55
55
|
SEARCH_PANE_WIDTH = "search_pane_width",
|
|
56
56
|
TEMPLATE_FIELD_COUNT = "template_field_count",
|
|
57
57
|
GRID_COLLAPSE_COLUMN = "grid_collapse_column",
|
|
58
|
+
GRID_COLUMNS = "grid_columns",
|
|
59
|
+
GRID_COLUMN_WIDTHS = "grid_column_widths",
|
|
58
60
|
QUERY_HISTORY = "query_history",
|
|
59
61
|
LOGIN_NONCE = "login_nonce",
|
|
60
62
|
DISPLAY_TYPE = "display_type"
|
package/utils/constants.js
CHANGED
|
@@ -60,6 +60,8 @@ export var StorageKey;
|
|
|
60
60
|
StorageKey["SEARCH_PANE_WIDTH"] = "search_pane_width";
|
|
61
61
|
StorageKey["TEMPLATE_FIELD_COUNT"] = "template_field_count";
|
|
62
62
|
StorageKey["GRID_COLLAPSE_COLUMN"] = "grid_collapse_column";
|
|
63
|
+
StorageKey["GRID_COLUMNS"] = "grid_columns";
|
|
64
|
+
StorageKey["GRID_COLUMN_WIDTHS"] = "grid_column_widths";
|
|
63
65
|
StorageKey["QUERY_HISTORY"] = "query_history";
|
|
64
66
|
StorageKey["LOGIN_NONCE"] = "login_nonce";
|
|
65
67
|
StorageKey["DISPLAY_TYPE"] = "display_type";
|