ydb-embedded-ui 3.2.0 → 3.2.1
Sign up to get free protection for your applications and to get access to all the features.
- package/CHANGELOG.md +11 -0
- package/dist/components/EntitiesCount/EntitiesCount.tsx +34 -0
- package/dist/components/EntitiesCount/i18n/en.json +3 -0
- package/dist/components/{AsideNavigation/Settings → EntitiesCount}/i18n/index.ts +2 -2
- package/dist/components/EntitiesCount/i18n/ru.json +3 -0
- package/dist/components/EntitiesCount/index.ts +1 -0
- package/dist/components/Fullscreen/Fullscreen.scss +7 -5
- package/dist/components/TabletsOverall/TabletsOverall.tsx +4 -4
- package/dist/components/TabletsStatistic/TabletsStatistic.tsx +56 -0
- package/dist/components/TabletsStatistic/index.ts +1 -0
- package/dist/containers/App/App.scss +4 -12
- package/dist/containers/AsideNavigation/AsideNavigation.scss +0 -18
- package/dist/containers/AsideNavigation/AsideNavigation.tsx +95 -33
- package/dist/containers/Heatmap/Heatmap.scss +0 -7
- package/dist/containers/Heatmap/Heatmap.tsx +203 -0
- package/dist/containers/Heatmap/HeatmapCanvas/HeatmapCanvas.js +2 -1
- package/dist/containers/Heatmap/index.ts +1 -0
- package/dist/containers/Node/Node.tsx +1 -1
- package/dist/containers/Storage/Storage.js +12 -19
- package/dist/containers/Tablets/Tablets.scss +0 -5
- package/dist/containers/Tablets/Tablets.tsx +172 -0
- package/dist/containers/Tablets/i18n/en.json +6 -0
- package/dist/{components/AsideNavigation → containers/Tablets}/i18n/index.ts +1 -1
- package/dist/containers/Tablets/i18n/ru.json +6 -0
- package/dist/containers/Tablets/index.ts +1 -0
- package/dist/containers/TabletsFilters/TabletsFilters.js +4 -8
- package/dist/containers/TabletsFilters/TabletsFilters.scss +6 -2
- package/dist/containers/Tenant/Diagnostics/Diagnostics.tsx +4 -8
- package/dist/containers/Tenant/Diagnostics/TenantOverview/TenantOverview.js +7 -7
- package/dist/containers/Tenants/Tenants.js +1 -1
- package/dist/containers/UserSettings/UserSettings.tsx +4 -3
- package/dist/routes.ts +1 -1
- package/dist/store/reducers/{heatmap.js → heatmap.ts} +33 -18
- package/dist/store/reducers/settings.js +12 -2
- package/dist/types/api/compute.ts +1 -1
- package/dist/types/api/schema.ts +16 -3
- package/dist/types/store/heatmap.ts +51 -0
- package/dist/utils/constants.ts +1 -37
- package/dist/utils/getNodesColumns.js +7 -2
- package/dist/utils/tablet.ts +53 -0
- package/package.json +2 -1
- package/dist/components/AsideNavigation/AsideHeader.scss +0 -147
- package/dist/components/AsideNavigation/AsideHeader.tsx +0 -389
- package/dist/components/AsideNavigation/AsideHeaderFooterItem/AsideHeaderFooterItem.scss +0 -82
- package/dist/components/AsideNavigation/AsideHeaderFooterItem/AsideHeaderFooterItem.tsx +0 -138
- package/dist/components/AsideNavigation/AsideHeaderFooterSlot/AsideHeaderFooterSlot.tsx +0 -33
- package/dist/components/AsideNavigation/AsideHeaderFooterSlot/SlotsContext.tsx +0 -49
- package/dist/components/AsideNavigation/AsideHeaderTooltip/AsideHeaderTooltip.scss +0 -16
- package/dist/components/AsideNavigation/AsideHeaderTooltip/AsideHeaderTooltip.tsx +0 -37
- package/dist/components/AsideNavigation/CompositeBar/CompositeBar.scss +0 -108
- package/dist/components/AsideNavigation/CompositeBar/CompositeBar.tsx +0 -282
- package/dist/components/AsideNavigation/Content/Content.tsx +0 -35
- package/dist/components/AsideNavigation/Drawer/Drawer.scss +0 -76
- package/dist/components/AsideNavigation/Drawer/Drawer.tsx +0 -134
- package/dist/components/AsideNavigation/Drawer/index.ts +0 -1
- package/dist/components/AsideNavigation/Logo/Logo.scss +0 -43
- package/dist/components/AsideNavigation/Logo/Logo.tsx +0 -82
- package/dist/components/AsideNavigation/Settings/README.md +0 -92
- package/dist/components/AsideNavigation/Settings/Settings.scss +0 -128
- package/dist/components/AsideNavigation/Settings/Settings.tsx +0 -270
- package/dist/components/AsideNavigation/Settings/SettingsMenu/SettingsMenu.scss +0 -78
- package/dist/components/AsideNavigation/Settings/SettingsMenu/SettingsMenu.tsx +0 -141
- package/dist/components/AsideNavigation/Settings/SettingsSearch/SettingsSearch.tsx +0 -57
- package/dist/components/AsideNavigation/Settings/collect-settings.ts +0 -156
- package/dist/components/AsideNavigation/Settings/filter-settings.ts +0 -38
- package/dist/components/AsideNavigation/Settings/helpers.ts +0 -39
- package/dist/components/AsideNavigation/Settings/i18n/en.json +0 -5
- package/dist/components/AsideNavigation/Settings/i18n/ru.json +0 -5
- package/dist/components/AsideNavigation/Settings/index.ts +0 -1
- package/dist/components/AsideNavigation/constants.ts +0 -28
- package/dist/components/AsideNavigation/helpers.ts +0 -34
- package/dist/components/AsideNavigation/i18n/en.json +0 -4
- package/dist/components/AsideNavigation/i18n/ru.json +0 -4
- package/dist/components/AsideNavigation/icons.ts +0 -32
- package/dist/components/AsideNavigation/types.ts +0 -23
- package/dist/components/TabletsStatistic/TabletsStatistic.js +0 -58
- package/dist/containers/Heatmap/Heatmap.js +0 -244
- package/dist/containers/Tablets/Tablets.js +0 -228
package/CHANGELOG.md
CHANGED
@@ -1,5 +1,16 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [3.2.1](https://github.com/ydb-platform/ydb-embedded-ui/compare/v3.2.0...v3.2.1) (2023-01-12)
|
4
|
+
|
5
|
+
|
6
|
+
### Bug Fixes
|
7
|
+
|
8
|
+
* align standard errors to the left ([cce100c](https://github.com/ydb-platform/ydb-embedded-ui/commit/cce100c5df83243df1fb0bc59d84d0d9b33719e6))
|
9
|
+
* **TabletsFilters:** properly display long data in select options ([ea37d9f](https://github.com/ydb-platform/ydb-embedded-ui/commit/ea37d9fc08245ccdd38a6120dd620f59a528879c))
|
10
|
+
* **TabletsFilters:** replace constants ([ea948ca](https://github.com/ydb-platform/ydb-embedded-ui/commit/ea948ca86276b5521979105b2ab99546da389e80))
|
11
|
+
* **TabletsStatistic:** process different tablets state types ([78798de](https://github.com/ydb-platform/ydb-embedded-ui/commit/78798de984bf4f6133515bb1c440e4fe0d15b07e))
|
12
|
+
* **Tenant:** always display Pools heading ([94baeff](https://github.com/ydb-platform/ydb-embedded-ui/commit/94baeff82f9c2c1aecda7c11c3b090125ba9e4b6))
|
13
|
+
|
3
14
|
## [3.2.0](https://github.com/ydb-platform/ydb-embedded-ui/compare/v3.1.0...v3.2.0) (2023-01-09)
|
4
15
|
|
5
16
|
|
@@ -0,0 +1,34 @@
|
|
1
|
+
import {Label} from '@gravity-ui/uikit';
|
2
|
+
import i18n from './i18n';
|
3
|
+
|
4
|
+
interface EntitiesCountProps {
|
5
|
+
current: number | string;
|
6
|
+
total?: number | string;
|
7
|
+
label?: string;
|
8
|
+
loading?: boolean;
|
9
|
+
className?: string;
|
10
|
+
}
|
11
|
+
|
12
|
+
export const EntitiesCount = ({total, current, label, loading, className}: EntitiesCountProps) => {
|
13
|
+
let content = '';
|
14
|
+
|
15
|
+
if (label) {
|
16
|
+
content += `${label}: `;
|
17
|
+
}
|
18
|
+
|
19
|
+
if (loading) {
|
20
|
+
content += '...';
|
21
|
+
} else {
|
22
|
+
content += `${current}`;
|
23
|
+
|
24
|
+
if (total && Number(total) !== Number(current)) {
|
25
|
+
content += ` ${i18n('of')} ${total}`;
|
26
|
+
}
|
27
|
+
}
|
28
|
+
|
29
|
+
return (
|
30
|
+
<Label theme="info" size="m" className={className}>
|
31
|
+
{content}
|
32
|
+
</Label>
|
33
|
+
);
|
34
|
+
};
|
@@ -1,9 +1,9 @@
|
|
1
|
-
import {i18n, Lang} from '
|
1
|
+
import {i18n, Lang} from '../../../utils/i18n';
|
2
2
|
|
3
3
|
import en from './en.json';
|
4
4
|
import ru from './ru.json';
|
5
5
|
|
6
|
-
const COMPONENT = '
|
6
|
+
const COMPONENT = 'ydb-entities-count';
|
7
7
|
|
8
8
|
i18n.registerKeyset(Lang.En, COMPONENT, en);
|
9
9
|
i18n.registerKeyset(Lang.Ru, COMPONENT, ru);
|
@@ -0,0 +1 @@
|
|
1
|
+
export * from './EntitiesCount';
|
@@ -1,16 +1,17 @@
|
|
1
1
|
.kv-fullscreen {
|
2
|
-
|
2
|
+
// should expand to fill the content area, keeping aside navigation visible
|
3
|
+
// counts on .ycn-aside-header__content to have position: relative, it is set in App.scss
|
4
|
+
position: absolute;
|
3
5
|
z-index: 10;
|
4
6
|
top: 0;
|
5
|
-
|
7
|
+
right: 0;
|
8
|
+
bottom: 0;
|
9
|
+
left: 0;
|
6
10
|
|
7
11
|
display: flex;
|
8
12
|
overflow: hidden;
|
9
13
|
flex-grow: 1;
|
10
14
|
|
11
|
-
width: calc(100% - var(--nv-aside-header-size));
|
12
|
-
height: 100%;
|
13
|
-
|
14
15
|
background-color: var(--yc-color-base-background);
|
15
16
|
|
16
17
|
&__close-button {
|
@@ -18,6 +19,7 @@
|
|
18
19
|
z-index: 11;
|
19
20
|
top: 8px;
|
20
21
|
right: 20px;
|
22
|
+
|
21
23
|
.yc-button__text {
|
22
24
|
display: flex;
|
23
25
|
align-items: center;
|
@@ -25,7 +25,7 @@ const b = cn('kv-tablets-overall');
|
|
25
25
|
type Color = keyof typeof colors;
|
26
26
|
|
27
27
|
interface TabletsOverallProps {
|
28
|
-
tablets: {Overall
|
28
|
+
tablets: {Overall?: string}[];
|
29
29
|
}
|
30
30
|
|
31
31
|
function TabletsOverall({tablets}: TabletsOverallProps) {
|
@@ -48,10 +48,10 @@ function TabletsOverall({tablets}: TabletsOverallProps) {
|
|
48
48
|
|
49
49
|
// determine how many tablets of what color are in "tablets"
|
50
50
|
const statesForOverallProgress: Record<string, number> = tablets.reduce((acc, tablet) => {
|
51
|
-
const color = tablet.Overall
|
52
|
-
if (!acc[color]) {
|
51
|
+
const color = tablet.Overall?.toLowerCase();
|
52
|
+
if (color && !acc[color]) {
|
53
53
|
acc[color] = 1;
|
54
|
-
} else {
|
54
|
+
} else if (color) {
|
55
55
|
acc[color]++;
|
56
56
|
}
|
57
57
|
|
@@ -0,0 +1,56 @@
|
|
1
|
+
import cn from 'bem-cn-lite';
|
2
|
+
import {Link} from 'react-router-dom';
|
3
|
+
|
4
|
+
import {getTabletLabel} from '../../utils/constants';
|
5
|
+
import {mapTabletStateToColorState} from '../../utils/tablet';
|
6
|
+
import routes, {createHref} from '../../routes';
|
7
|
+
|
8
|
+
import type {TTabletStateInfo as TFullTabletStateInfo} from '../../types/api/tablet';
|
9
|
+
import type {TTabletStateInfo as TComputeTabletStateInfo} from '../../types/api/compute';
|
10
|
+
|
11
|
+
import './TabletsStatistic.scss';
|
12
|
+
|
13
|
+
const b = cn('tablets-statistic');
|
14
|
+
|
15
|
+
type ITablets = TFullTabletStateInfo[] | TComputeTabletStateInfo[];
|
16
|
+
|
17
|
+
const prepareTablets = (tablets: ITablets) => {
|
18
|
+
const res = tablets.map((tablet) => {
|
19
|
+
return {
|
20
|
+
label: getTabletLabel(tablet.Type),
|
21
|
+
type: tablet.Type,
|
22
|
+
count: tablet.Count,
|
23
|
+
state: mapTabletStateToColorState(tablet.State),
|
24
|
+
};
|
25
|
+
});
|
26
|
+
|
27
|
+
return res.sort((x, y) => String(x.label).localeCompare(String(y.label)));
|
28
|
+
};
|
29
|
+
|
30
|
+
interface TabletsStatisticProps {
|
31
|
+
tablets: ITablets;
|
32
|
+
path: string;
|
33
|
+
nodeIds: string[] | number[];
|
34
|
+
}
|
35
|
+
|
36
|
+
export const TabletsStatistic = ({tablets = [], path, nodeIds}: TabletsStatisticProps) => {
|
37
|
+
const renderTabletInfo = (item: ReturnType<typeof prepareTablets>[number], index: number) => {
|
38
|
+
return (
|
39
|
+
<Link
|
40
|
+
to={createHref(routes.tabletsFilters, undefined, {
|
41
|
+
nodeIds,
|
42
|
+
state: item.state,
|
43
|
+
type: item.type,
|
44
|
+
path,
|
45
|
+
})}
|
46
|
+
key={index}
|
47
|
+
className={b('tablet', {state: item.state?.toLowerCase()})}
|
48
|
+
>
|
49
|
+
{item.label}: {item.count}
|
50
|
+
</Link>
|
51
|
+
);
|
52
|
+
};
|
53
|
+
const preparedTablets = prepareTablets(tablets);
|
54
|
+
|
55
|
+
return <div className={b()}>{preparedTablets.map(renderTabletInfo)}</div>;
|
56
|
+
};
|
@@ -0,0 +1 @@
|
|
1
|
+
export * from './TabletsStatistic';
|
@@ -47,11 +47,13 @@ body,
|
|
47
47
|
color: var(--yc-color-text-secondary);
|
48
48
|
}
|
49
49
|
|
50
|
-
.
|
50
|
+
.ycn-aside-header__pane-container {
|
51
51
|
height: 100%;
|
52
52
|
}
|
53
53
|
|
54
|
-
.
|
54
|
+
.ycn-aside-header__content {
|
55
|
+
position: relative;
|
56
|
+
|
55
57
|
display: flex;
|
56
58
|
overflow: auto;
|
57
59
|
flex-direction: column;
|
@@ -116,17 +118,7 @@ body,
|
|
116
118
|
}
|
117
119
|
|
118
120
|
.error {
|
119
|
-
display: flex;
|
120
|
-
justify-content: center;
|
121
|
-
align-items: center;
|
122
|
-
|
123
|
-
margin: 2px;
|
124
|
-
padding: 2px;
|
125
|
-
|
126
|
-
text-align: center;
|
127
|
-
|
128
121
|
color: var(--yc-color-text-danger);
|
129
|
-
border-color: var(--yc-color-text-danger);
|
130
122
|
}
|
131
123
|
|
132
124
|
.data-table__row:hover .entity-status__clipboard-button {
|
@@ -23,21 +23,3 @@
|
|
23
23
|
padding: 10px;
|
24
24
|
}
|
25
25
|
}
|
26
|
-
|
27
|
-
.nv-aside-header-footer-item__popup {
|
28
|
-
.nv-user-menu-content__organizations {
|
29
|
-
margin-top: 19px;
|
30
|
-
margin-left: 0;
|
31
|
-
.yc-menu__item {
|
32
|
-
cursor: auto;
|
33
|
-
}
|
34
|
-
}
|
35
|
-
|
36
|
-
.yc-menu__item {
|
37
|
-
//stylelint-disable-next-line declaration-no-important
|
38
|
-
padding-right: 0 !important;
|
39
|
-
&:hover {
|
40
|
-
background-color: unset;
|
41
|
-
}
|
42
|
-
}
|
43
|
-
}
|
@@ -1,16 +1,12 @@
|
|
1
|
-
import React, {useState} from 'react';
|
1
|
+
import React, {useEffect, useState} from 'react';
|
2
|
+
import {connect} from 'react-redux';
|
3
|
+
import {useLocation} from 'react-router';
|
2
4
|
import {useHistory} from 'react-router-dom';
|
3
5
|
import cn from 'bem-cn-lite';
|
4
|
-
import {useLocation} from 'react-router';
|
5
|
-
import {connect} from 'react-redux';
|
6
6
|
|
7
|
-
import {
|
8
|
-
AsideHeader,
|
9
|
-
AsideHeaderFooterItem,
|
10
|
-
AsideHeaderMenuItem,
|
11
|
-
SlotName,
|
12
|
-
} from '../../components/AsideNavigation/AsideHeader';
|
13
7
|
import {Icon, Button} from '@gravity-ui/uikit';
|
8
|
+
import {AsideHeader, MenuItem as AsideHeaderMenuItem, FooterItem} from '@gravity-ui/navigation';
|
9
|
+
|
14
10
|
import signOutIcon from '../../assets/icons/signOut.svg';
|
15
11
|
import signInIcon from '../../assets/icons/signIn.svg';
|
16
12
|
import databaseIcon from '../../assets/icons/server.svg';
|
@@ -20,12 +16,17 @@ import ydbLogoIcon from '../../assets/icons/ydb.svg';
|
|
20
16
|
import databasesIcon from '../../assets/icons/databases.svg';
|
21
17
|
import userSecret from '../../assets/icons/user-secret.svg';
|
22
18
|
import userChecked from '../../assets/icons/user-check.svg';
|
23
|
-
|
19
|
+
import settingsIcon from '../../assets/icons/settings.svg';
|
20
|
+
import supportIcon from '../../assets/icons/support.svg';
|
21
|
+
|
24
22
|
import UserSettings from '../UserSettings/UserSettings';
|
23
|
+
|
25
24
|
import routes, {createHref, CLUSTER_PAGES} from '../../routes';
|
26
25
|
|
27
|
-
//@ts-ignore
|
28
26
|
import {logout, setIsNotAuthenticated} from '../../store/reducers/authentication';
|
27
|
+
import {getSettingValue, setSettingValue} from '../../store/reducers/settings';
|
28
|
+
|
29
|
+
import {ASIDE_HEADER_COMPACT_KEY} from '../../utils/constants';
|
29
30
|
|
30
31
|
import './AsideNavigation.scss';
|
31
32
|
|
@@ -77,17 +78,21 @@ interface YdbUserDropdownProps {
|
|
77
78
|
|
78
79
|
function YdbUserDropdown({isCompact, popupAnchor, ydbUser}: YdbUserDropdownProps) {
|
79
80
|
const [isUserDropdownVisible, setIsUserDropdownVisible] = useState(false);
|
80
|
-
const
|
81
|
+
const iconData = ydbUser?.login ? userChecked : userSecret;
|
81
82
|
return (
|
82
|
-
<
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
83
|
+
<FooterItem
|
84
|
+
compact={isCompact}
|
85
|
+
item={{
|
86
|
+
id: 'user-popup',
|
87
|
+
title: ydbUser?.login ?? 'Account',
|
88
|
+
current: isUserDropdownVisible,
|
89
|
+
icon: iconData,
|
90
|
+
iconSize: 22,
|
91
|
+
onItemClick: () => setIsUserDropdownVisible(true),
|
92
|
+
}}
|
93
|
+
enableTooltip={!isUserDropdownVisible}
|
88
94
|
popupAnchor={popupAnchor}
|
89
95
|
popupVisible={isUserDropdownVisible}
|
90
|
-
onClick={() => setIsUserDropdownVisible(true)}
|
91
96
|
onClosePopup={() => setIsUserDropdownVisible(false)}
|
92
97
|
renderPopupContent={() => (
|
93
98
|
<div className={b('ydb-user-wrapper')}>
|
@@ -105,8 +110,10 @@ function YdbUserDropdown({isCompact, popupAnchor, ydbUser}: YdbUserDropdownProps
|
|
105
110
|
interface AsideNavigationProps {
|
106
111
|
children: React.ReactNode;
|
107
112
|
ydbUser: string;
|
113
|
+
compact: boolean;
|
108
114
|
logout: VoidFunction;
|
109
115
|
setIsNotAuthenticated: VoidFunction;
|
116
|
+
setSettingValue: (name: string, value: string) => void;
|
110
117
|
}
|
111
118
|
|
112
119
|
const items: MenuItem[] = [
|
@@ -150,10 +157,30 @@ const items: MenuItem[] = [
|
|
150
157
|
},
|
151
158
|
];
|
152
159
|
|
160
|
+
enum Panel {
|
161
|
+
UserSettings = 'UserSettings',
|
162
|
+
}
|
163
|
+
|
153
164
|
function AsideNavigation(props: AsideNavigationProps) {
|
154
165
|
const location = useLocation();
|
155
166
|
const history = useHistory();
|
156
167
|
|
168
|
+
const [visiblePanel, setVisiblePanel] = useState<Panel>();
|
169
|
+
|
170
|
+
const setIsCompact = (compact: boolean) => {
|
171
|
+
props.setSettingValue(ASIDE_HEADER_COMPACT_KEY, JSON.stringify(compact));
|
172
|
+
};
|
173
|
+
|
174
|
+
// navigation managed its compact state internally before, and its approach is not compatible with settings
|
175
|
+
// to migrate, save the incoming value again; save only `false` because `true` is the default value
|
176
|
+
// assume it is safe to remove this code block if it is at least a few months old
|
177
|
+
// there a two of these, search for a similar comment
|
178
|
+
useEffect(() => {
|
179
|
+
if (props.compact === false) {
|
180
|
+
setIsCompact(props.compact);
|
181
|
+
}
|
182
|
+
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
183
|
+
|
157
184
|
const menuItems: AsideHeaderMenuItem[] = React.useMemo(() => {
|
158
185
|
const {pathname} = location;
|
159
186
|
const isClusterPage = pathname === '/cluster';
|
@@ -184,28 +211,51 @@ function AsideNavigation(props: AsideNavigationProps) {
|
|
184
211
|
return (
|
185
212
|
<React.Fragment>
|
186
213
|
<AsideHeader
|
187
|
-
|
188
|
-
|
189
|
-
|
214
|
+
logo={{
|
215
|
+
text: 'YDB',
|
216
|
+
icon: ydbLogoIcon,
|
217
|
+
onClick: () => history.push('/'),
|
218
|
+
}}
|
190
219
|
menuItems={menuItems}
|
191
|
-
|
192
|
-
|
220
|
+
compact={props.compact}
|
221
|
+
onChangeCompact={setIsCompact}
|
193
222
|
className={b()}
|
194
223
|
renderContent={() => props.children}
|
195
|
-
renderFooter={({
|
224
|
+
renderFooter={({compact, asideRef}) => (
|
196
225
|
<React.Fragment>
|
197
|
-
<
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
226
|
+
<FooterItem
|
227
|
+
compact={compact}
|
228
|
+
item={{
|
229
|
+
id: 'documentation',
|
230
|
+
title: 'Documentation',
|
231
|
+
icon: supportIcon,
|
232
|
+
iconSize: 24,
|
233
|
+
onItemClick: () => {
|
234
|
+
window.open('https://ydb.tech/docs', '_blank', 'noreferrer');
|
235
|
+
},
|
236
|
+
}}
|
237
|
+
/>
|
238
|
+
|
239
|
+
<FooterItem
|
240
|
+
item={{
|
241
|
+
id: 'user-settings',
|
242
|
+
title: 'Settings',
|
243
|
+
icon: settingsIcon,
|
244
|
+
iconSize: 24,
|
245
|
+
current: visiblePanel === Panel.UserSettings,
|
246
|
+
onItemClick: () => {
|
247
|
+
setVisiblePanel(
|
248
|
+
visiblePanel === Panel.UserSettings
|
249
|
+
? undefined
|
250
|
+
: Panel.UserSettings,
|
251
|
+
);
|
252
|
+
},
|
204
253
|
}}
|
254
|
+
compact={compact}
|
205
255
|
/>
|
206
256
|
|
207
257
|
<YdbUserDropdown
|
208
|
-
isCompact={
|
258
|
+
isCompact={compact}
|
209
259
|
popupAnchor={asideRef}
|
210
260
|
ydbUser={{
|
211
261
|
login: props.ydbUser,
|
@@ -215,6 +265,16 @@ function AsideNavigation(props: AsideNavigationProps) {
|
|
215
265
|
/>
|
216
266
|
</React.Fragment>
|
217
267
|
)}
|
268
|
+
panelItems={[
|
269
|
+
{
|
270
|
+
id: 'user-settings',
|
271
|
+
visible: visiblePanel === Panel.UserSettings,
|
272
|
+
content: <UserSettings />,
|
273
|
+
},
|
274
|
+
]}
|
275
|
+
onClosePanel={() => {
|
276
|
+
setVisiblePanel(undefined);
|
277
|
+
}}
|
218
278
|
/>
|
219
279
|
</React.Fragment>
|
220
280
|
);
|
@@ -225,12 +285,14 @@ const mapStateToProps = (state: any) => {
|
|
225
285
|
|
226
286
|
return {
|
227
287
|
ydbUser,
|
288
|
+
compact: JSON.parse(getSettingValue(state, ASIDE_HEADER_COMPACT_KEY)),
|
228
289
|
};
|
229
290
|
};
|
230
291
|
|
231
292
|
const mapDispatchToProps = {
|
232
293
|
logout,
|
233
294
|
setIsNotAuthenticated,
|
295
|
+
setSettingValue,
|
234
296
|
};
|
235
297
|
|
236
298
|
export default connect(mapStateToProps, mapDispatchToProps)(AsideNavigation);
|
@@ -0,0 +1,203 @@
|
|
1
|
+
import React, {useCallback, useEffect, useState} from 'react';
|
2
|
+
import {useDispatch} from 'react-redux';
|
3
|
+
import cn from 'bem-cn-lite';
|
4
|
+
|
5
|
+
import {Checkbox, Select} from '@gravity-ui/uikit';
|
6
|
+
|
7
|
+
import {getTabletsInfo, setHeatmapOptions} from '../../store/reducers/heatmap';
|
8
|
+
import {showTooltip, hideTooltip} from '../../store/reducers/tooltip';
|
9
|
+
import {formatNumber} from '../../utils';
|
10
|
+
import {prepareQueryError} from '../../utils/query';
|
11
|
+
import {useAutofetcher, useTypedSelector} from '../../utils/hooks';
|
12
|
+
import {Loader} from '../../components/Loader';
|
13
|
+
import type {IHeatmapMetricValue} from '../../types/store/heatmap';
|
14
|
+
|
15
|
+
import {COLORS_RANGE_SIZE, getColorRange, getColorIndex, getCurrentMetricLimits} from './util';
|
16
|
+
import {HeatmapCanvas} from './HeatmapCanvas/HeatmapCanvas';
|
17
|
+
import {Histogram} from './Histogram/Histogram';
|
18
|
+
|
19
|
+
import './Heatmap.scss';
|
20
|
+
|
21
|
+
const b = cn('heatmap');
|
22
|
+
const COLORS_RANGE = getColorRange(COLORS_RANGE_SIZE);
|
23
|
+
|
24
|
+
interface HeatmapProps {
|
25
|
+
path: string;
|
26
|
+
}
|
27
|
+
|
28
|
+
export const Heatmap = ({path}: HeatmapProps) => {
|
29
|
+
const dispatch = useDispatch();
|
30
|
+
|
31
|
+
const itemsContainer = React.createRef<HTMLDivElement>();
|
32
|
+
|
33
|
+
const {autorefresh} = useTypedSelector((state) => state.schema);
|
34
|
+
const {
|
35
|
+
loading,
|
36
|
+
wasLoaded,
|
37
|
+
error,
|
38
|
+
sort,
|
39
|
+
heatmap,
|
40
|
+
metrics,
|
41
|
+
currentMetric,
|
42
|
+
data: tablets = [],
|
43
|
+
} = useTypedSelector((state) => state.heatmap);
|
44
|
+
|
45
|
+
const [selectedMetric, setSelectedMetric] = useState(['']);
|
46
|
+
|
47
|
+
useEffect(() => {
|
48
|
+
if (!currentMetric && metrics && metrics.length) {
|
49
|
+
dispatch(
|
50
|
+
setHeatmapOptions({
|
51
|
+
currentMetric: metrics[0].value,
|
52
|
+
}),
|
53
|
+
);
|
54
|
+
}
|
55
|
+
if (currentMetric) {
|
56
|
+
setSelectedMetric([currentMetric]);
|
57
|
+
}
|
58
|
+
}, [currentMetric, metrics, dispatch]);
|
59
|
+
|
60
|
+
const fetchData = useCallback(
|
61
|
+
(isBackground: boolean) => {
|
62
|
+
if (!isBackground) {
|
63
|
+
dispatch(setHeatmapOptions({wasLoaded: false}));
|
64
|
+
}
|
65
|
+
dispatch(getTabletsInfo({path}));
|
66
|
+
},
|
67
|
+
[path, dispatch],
|
68
|
+
);
|
69
|
+
|
70
|
+
useAutofetcher(fetchData, [fetchData], autorefresh);
|
71
|
+
|
72
|
+
const onShowTooltip = (...args: Parameters<typeof showTooltip>) => {
|
73
|
+
dispatch(showTooltip(...args));
|
74
|
+
};
|
75
|
+
|
76
|
+
const onHideTooltip = () => {
|
77
|
+
dispatch(hideTooltip);
|
78
|
+
};
|
79
|
+
|
80
|
+
const handleMetricChange = (value: string[]) => {
|
81
|
+
dispatch(
|
82
|
+
setHeatmapOptions({
|
83
|
+
currentMetric: value[0] as IHeatmapMetricValue,
|
84
|
+
}),
|
85
|
+
);
|
86
|
+
};
|
87
|
+
const handleCheckboxChange = () => {
|
88
|
+
dispatch(
|
89
|
+
setHeatmapOptions({
|
90
|
+
sort: !sort,
|
91
|
+
}),
|
92
|
+
);
|
93
|
+
};
|
94
|
+
|
95
|
+
const handleHeatmapChange = () => {
|
96
|
+
dispatch(
|
97
|
+
setHeatmapOptions({
|
98
|
+
heatmap: !heatmap,
|
99
|
+
}),
|
100
|
+
);
|
101
|
+
};
|
102
|
+
|
103
|
+
const renderHistogram = () => {
|
104
|
+
return (
|
105
|
+
<Histogram
|
106
|
+
tablets={tablets}
|
107
|
+
currentMetric={currentMetric}
|
108
|
+
showTooltip={onShowTooltip}
|
109
|
+
hideTooltip={onHideTooltip}
|
110
|
+
/>
|
111
|
+
);
|
112
|
+
};
|
113
|
+
|
114
|
+
const renderHeatmapCanvas = () => {
|
115
|
+
const {min, max} = getCurrentMetricLimits(currentMetric, tablets);
|
116
|
+
|
117
|
+
const preparedTablets = tablets.map((tablet) => {
|
118
|
+
const value = currentMetric && Number(tablet.metrics?.[currentMetric]);
|
119
|
+
const colorIndex = getColorIndex(value, min, max);
|
120
|
+
const color = COLORS_RANGE[colorIndex];
|
121
|
+
|
122
|
+
return {
|
123
|
+
...tablet,
|
124
|
+
color,
|
125
|
+
value,
|
126
|
+
formattedValue: formatNumber(value),
|
127
|
+
currentMetric,
|
128
|
+
};
|
129
|
+
});
|
130
|
+
const sortedTablets = sort
|
131
|
+
? preparedTablets.sort((x, y) => Number(y.value) - Number(x.value))
|
132
|
+
: preparedTablets;
|
133
|
+
|
134
|
+
return (
|
135
|
+
<div ref={itemsContainer} className={b('items')}>
|
136
|
+
<HeatmapCanvas
|
137
|
+
tablets={sortedTablets}
|
138
|
+
parentRef={itemsContainer}
|
139
|
+
showTooltip={onShowTooltip}
|
140
|
+
hideTooltip={onHideTooltip}
|
141
|
+
currentMetric={currentMetric}
|
142
|
+
/>
|
143
|
+
</div>
|
144
|
+
);
|
145
|
+
};
|
146
|
+
|
147
|
+
const renderContent = () => {
|
148
|
+
const {min, max} = getCurrentMetricLimits(currentMetric, tablets);
|
149
|
+
|
150
|
+
return (
|
151
|
+
<div className={b()}>
|
152
|
+
<div className={b('filters')}>
|
153
|
+
<Select
|
154
|
+
className={b('heatmap-select')}
|
155
|
+
value={selectedMetric}
|
156
|
+
options={metrics}
|
157
|
+
onUpdate={handleMetricChange}
|
158
|
+
width={200}
|
159
|
+
/>
|
160
|
+
<div className={b('sort-checkbox')}>
|
161
|
+
<Checkbox onUpdate={handleCheckboxChange} checked={sort}>
|
162
|
+
Sort
|
163
|
+
</Checkbox>
|
164
|
+
</div>
|
165
|
+
<div className={b('histogram-checkbox')}>
|
166
|
+
<Checkbox onUpdate={handleHeatmapChange} checked={heatmap}>
|
167
|
+
Heatmap
|
168
|
+
</Checkbox>
|
169
|
+
</div>
|
170
|
+
<div className={b('limits')}>
|
171
|
+
<div className={b('limits-block')}>
|
172
|
+
<div className={b('limits-title')}>min:</div>
|
173
|
+
<div className={b('limits-value')}>
|
174
|
+
{Number.isInteger(min) ? formatNumber(min) : '—'}
|
175
|
+
</div>
|
176
|
+
</div>
|
177
|
+
<div className={b('limits-block')}>
|
178
|
+
<div className={b('limits-title')}>max:</div>
|
179
|
+
<div className={b('limits-value')}>
|
180
|
+
{Number.isInteger(max) ? formatNumber(max) : '—'}
|
181
|
+
</div>
|
182
|
+
</div>
|
183
|
+
<div className={b('limits-block')}>
|
184
|
+
<div className={b('limits-title')}>count:</div>
|
185
|
+
<div className={b('limits-value')}>{formatNumber(tablets.length)}</div>
|
186
|
+
</div>
|
187
|
+
</div>
|
188
|
+
</div>
|
189
|
+
{heatmap ? renderHeatmapCanvas() : renderHistogram()}
|
190
|
+
</div>
|
191
|
+
);
|
192
|
+
};
|
193
|
+
|
194
|
+
if (loading && !wasLoaded) {
|
195
|
+
return <Loader />;
|
196
|
+
}
|
197
|
+
|
198
|
+
if (error) {
|
199
|
+
return <div>{prepareQueryError(error)}</div>;
|
200
|
+
}
|
201
|
+
|
202
|
+
return renderContent();
|
203
|
+
};
|