ydb-embedded-ui 3.4.3 → 3.4.5
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/CHANGELOG.md +19 -0
- package/dist/components/Icon/Icon.tsx +6 -0
- package/dist/components/InfoViewer/InfoViewer.tsx +2 -2
- package/dist/components/InfoViewer/formatters/index.ts +0 -1
- package/dist/components/LabelWithPopover/LabelWithPopover.tsx +3 -7
- package/dist/components/LagPopoverContent/LagPopoverContent.scss +13 -0
- package/dist/components/LagPopoverContent/LagPopoverContent.tsx +19 -0
- package/dist/components/LagPopoverContent/index.ts +1 -0
- package/dist/components/Tooltips/NodeEndpointsTooltip/NodeEndpointsTooltip.scss +5 -0
- package/dist/components/Tooltips/NodeEndpointsTooltip/NodeEndpointsTooltip.tsx +31 -0
- package/dist/containers/Nodes/Nodes.tsx +17 -15
- package/dist/containers/Nodes/NodesTable.scss +11 -10
- package/dist/containers/Nodes/getNodesColumns.tsx +29 -24
- package/dist/containers/Storage/PDisk/PDisk.scss +2 -0
- package/dist/containers/Storage/PDiskPopup/PDiskPopup.tsx +6 -9
- package/dist/containers/Storage/Storage.js +12 -5
- package/dist/containers/Storage/StorageGroups/StorageGroups.tsx +3 -1
- package/dist/containers/Storage/StorageNodes/StorageNodes.scss +20 -7
- package/dist/containers/Storage/StorageNodes/StorageNodes.tsx +43 -7
- package/dist/containers/Storage/VDisk/VDisk.tsx +3 -2
- package/dist/containers/Storage/VDiskPopup/VDiskPopup.tsx +4 -2
- package/dist/containers/Tenant/Diagnostics/Consumers/Headers/Headers.scss +0 -8
- package/dist/containers/Tenant/Diagnostics/Consumers/Headers/Headers.tsx +3 -10
- package/dist/containers/Tenant/Diagnostics/Consumers/i18n/ru.json +1 -1
- package/dist/containers/Tenant/Diagnostics/Diagnostics.tsx +2 -1
- package/dist/containers/Tenant/Diagnostics/DiagnosticsPages.ts +4 -4
- package/dist/containers/Tenant/Diagnostics/Overview/TopicStats/TopicStats.tsx +51 -32
- package/dist/containers/Tenant/Diagnostics/Overview/TopicStats/i18n/en.json +2 -1
- package/dist/containers/Tenant/Diagnostics/Overview/TopicStats/i18n/ru.json +2 -1
- package/dist/containers/Tenant/Diagnostics/Partitions/Headers/Headers.scss +0 -8
- package/dist/containers/Tenant/Diagnostics/Partitions/Headers/Headers.tsx +7 -21
- package/dist/services/api.d.ts +4 -0
- package/dist/store/reducers/{nodesList.js → nodesList.ts} +19 -7
- package/dist/store/reducers/storage.js +3 -11
- package/dist/types/api/nodesList.ts +25 -0
- package/dist/types/store/nodesList.ts +24 -0
- package/dist/utils/index.js +9 -1
- package/dist/utils/nodes.ts +4 -0
- package/dist/utils/tooltip.js +2 -28
- package/package.json +1 -1
- package/dist/components/InfoViewer/formatters/topicStats.tsx +0 -29
package/CHANGELOG.md
CHANGED
@@ -1,5 +1,24 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [3.4.5](https://github.com/ydb-platform/ydb-embedded-ui/compare/v3.4.4...v3.4.5) (2023-03-30)
|
4
|
+
|
5
|
+
|
6
|
+
### Bug Fixes
|
7
|
+
|
8
|
+
* **Consumers:** fix typo ([aaa9dbd](https://github.com/ydb-platform/ydb-embedded-ui/commit/aaa9dbda1f28702917793a61bae2813f6ef018bb))
|
9
|
+
* **PDisk:** add display block to content ([130dab2](https://github.com/ydb-platform/ydb-embedded-ui/commit/130dab20ffdc9da77225c94a6e6064f0308a1c2a))
|
10
|
+
* **Storage:** get nodes hosts from /nodelist ([cc82dd9](https://github.com/ydb-platform/ydb-embedded-ui/commit/cc82dd93808133b0d1dcd21b31ee3744df4f7383))
|
11
|
+
* **StorageNodes:** make fqdn similar to nodes page ([344298a](https://github.com/ydb-platform/ydb-embedded-ui/commit/344298a9a29380f1068b002fa304cdcc221ce0d4))
|
12
|
+
* **TopicInfo:** do not display /s when speed is undefined ([2d41832](https://github.com/ydb-platform/ydb-embedded-ui/commit/2d4183247ec33acdfa45be72a93f0dbd93b716e0))
|
13
|
+
* **TopicStats:** use prepared stats, update fields ([a614a8c](https://github.com/ydb-platform/ydb-embedded-ui/commit/a614a8caa2744b844d97f23f25e5385387367d6b))
|
14
|
+
|
15
|
+
## [3.4.4](https://github.com/ydb-platform/ydb-embedded-ui/compare/v3.4.3...v3.4.4) (2023-03-22)
|
16
|
+
|
17
|
+
|
18
|
+
### Bug Fixes
|
19
|
+
|
20
|
+
* **Diagnostics:** display nodes tab for not db entities ([a542dbc](https://github.com/ydb-platform/ydb-embedded-ui/commit/a542dbc23d01138a5c1a4126cfc1836a1543b68c))
|
21
|
+
|
3
22
|
## [3.4.3](https://github.com/ydb-platform/ydb-embedded-ui/compare/v3.4.2...v3.4.3) (2023-03-17)
|
4
23
|
|
5
24
|
|
@@ -31,3 +31,9 @@ export const Icon = ({
|
|
31
31
|
/>
|
32
32
|
);
|
33
33
|
};
|
34
|
+
|
35
|
+
// When used with uikit components Icon is considered as text element and corresponding styles are applied
|
36
|
+
// IconWrapper overrides displayName to 'Icon', so it will be considered as an icon with right styles
|
37
|
+
export const IconWrapper = (props: IconProps) => <Icon {...props} />;
|
38
|
+
|
39
|
+
IconWrapper.displayName = 'Icon';
|
@@ -4,7 +4,7 @@ import cn from 'bem-cn-lite';
|
|
4
4
|
import './InfoViewer.scss';
|
5
5
|
|
6
6
|
export interface InfoViewerItem {
|
7
|
-
label:
|
7
|
+
label: ReactNode;
|
8
8
|
value: ReactNode;
|
9
9
|
}
|
10
10
|
|
@@ -39,7 +39,7 @@ const InfoViewer = ({
|
|
39
39
|
{info && info.length > 0 ? (
|
40
40
|
<div className={b('items')}>
|
41
41
|
{info.map((data, infoIndex) => (
|
42
|
-
<div className={b('row')} key={
|
42
|
+
<div className={b('row')} key={infoIndex}>
|
43
43
|
<div className={b('label')}>
|
44
44
|
<div className={b('label-text', {multiline: multilineLabels})}>
|
45
45
|
{data.label}
|
@@ -3,18 +3,14 @@ import type {ReactNode} from 'react';
|
|
3
3
|
import {HelpPopover} from '@gravity-ui/uikit';
|
4
4
|
|
5
5
|
interface LabelWithPopoverProps {
|
6
|
-
|
6
|
+
text: string;
|
7
7
|
popoverContent: ReactNode;
|
8
8
|
className?: string;
|
9
9
|
}
|
10
10
|
|
11
|
-
export const LabelWithPopover = ({
|
12
|
-
headerText,
|
13
|
-
popoverContent,
|
14
|
-
className,
|
15
|
-
}: LabelWithPopoverProps) => (
|
11
|
+
export const LabelWithPopover = ({text, popoverContent, className}: LabelWithPopoverProps) => (
|
16
12
|
<div className={className}>
|
17
|
-
{
|
13
|
+
{text}
|
18
14
|
<HelpPopover content={popoverContent} />
|
19
15
|
</div>
|
20
16
|
);
|
@@ -0,0 +1,19 @@
|
|
1
|
+
import block from 'bem-cn-lite';
|
2
|
+
|
3
|
+
import {ReadLagImage, WriteLagImage} from '../LagImages';
|
4
|
+
|
5
|
+
import './LagPopoverContent.scss';
|
6
|
+
|
7
|
+
const b = block('ydb-lag-popover-content');
|
8
|
+
|
9
|
+
interface LagPopoverContentProps {
|
10
|
+
text: string;
|
11
|
+
type: 'read' | 'write';
|
12
|
+
}
|
13
|
+
|
14
|
+
export const LagPopoverContent = ({text, type}: LagPopoverContentProps) => (
|
15
|
+
<div className={b({type})}>
|
16
|
+
<div className={b('text')}>{text}</div>
|
17
|
+
<div>{type === 'read' ? <ReadLagImage /> : <WriteLagImage />}</div>
|
18
|
+
</div>
|
19
|
+
);
|
@@ -0,0 +1 @@
|
|
1
|
+
export * from './LagPopoverContent';
|
@@ -0,0 +1,31 @@
|
|
1
|
+
import block from 'bem-cn-lite';
|
2
|
+
|
3
|
+
import type {TSystemStateInfo} from '../../../types/api/nodes';
|
4
|
+
|
5
|
+
import {InfoViewer, InfoViewerItem} from '../../InfoViewer';
|
6
|
+
|
7
|
+
import './NodeEndpointsTooltip.scss';
|
8
|
+
|
9
|
+
const b = block('ydb-node-endpoints-tooltip');
|
10
|
+
|
11
|
+
interface NodeEdpointsTooltipProps {
|
12
|
+
data?: TSystemStateInfo;
|
13
|
+
}
|
14
|
+
|
15
|
+
export const NodeEndpointsTooltip = ({data}: NodeEdpointsTooltipProps) => {
|
16
|
+
const info: InfoViewerItem[] = [];
|
17
|
+
|
18
|
+
if (data?.Rack) {
|
19
|
+
info.push({label: 'Rack', value: data.Rack});
|
20
|
+
}
|
21
|
+
|
22
|
+
if (data?.Endpoints && data.Endpoints.length) {
|
23
|
+
data.Endpoints.forEach(({Name, Address}) => {
|
24
|
+
if (Name && Address) {
|
25
|
+
info.push({label: Name, value: Address});
|
26
|
+
}
|
27
|
+
});
|
28
|
+
}
|
29
|
+
|
30
|
+
return <InfoViewer className={b(null)} info={info} dots={false} size={'s'} />;
|
31
|
+
};
|
@@ -4,10 +4,11 @@ import {useDispatch} from 'react-redux';
|
|
4
4
|
|
5
5
|
import DataTable from '@gravity-ui/react-data-table';
|
6
6
|
|
7
|
+
import type {EPathType} from '../../types/api/schema';
|
8
|
+
|
7
9
|
import {AccessDenied} from '../../components/Errors/403';
|
8
10
|
import {Illustration} from '../../components/Illustration';
|
9
11
|
import {Loader} from '../../components/Loader';
|
10
|
-
|
11
12
|
import {Search} from '../../components/Search';
|
12
13
|
import {ProblemFilter} from '../../components/ProblemFilter';
|
13
14
|
import {UptimeFilter} from '../../components/UptimeFIlter';
|
@@ -21,7 +22,7 @@ import {
|
|
21
22
|
USE_NODES_ENDPOINT_IN_DIAGNOSTICS_KEY,
|
22
23
|
} from '../../utils/constants';
|
23
24
|
import {useAutofetcher, useTypedSelector} from '../../utils/hooks';
|
24
|
-
import {isUnavailableNode, NodesUptimeFilterValues} from '../../utils/nodes';
|
25
|
+
import {AdditionalNodesInfo, isUnavailableNode, NodesUptimeFilterValues} from '../../utils/nodes';
|
25
26
|
|
26
27
|
import {setHeader} from '../../store/reducers/header';
|
27
28
|
import {
|
@@ -35,6 +36,8 @@ import {
|
|
35
36
|
import {changeFilter, getSettingValue} from '../../store/reducers/settings';
|
36
37
|
import {hideTooltip, showTooltip} from '../../store/reducers/tooltip';
|
37
38
|
|
39
|
+
import {isDatabaseEntityType} from '../Tenant/utils/schema';
|
40
|
+
|
38
41
|
import {getNodesColumns} from './getNodesColumns';
|
39
42
|
|
40
43
|
import './Nodes.scss';
|
@@ -43,27 +46,24 @@ import i18n from './i18n';
|
|
43
46
|
|
44
47
|
const b = cn('ydb-nodes');
|
45
48
|
|
46
|
-
interface IAdditionalNodesInfo extends Record<string, unknown> {
|
47
|
-
getNodeRef?: Function;
|
48
|
-
}
|
49
|
-
|
50
49
|
interface NodesProps {
|
51
|
-
|
50
|
+
path?: string;
|
51
|
+
type?: EPathType;
|
52
52
|
className?: string;
|
53
|
-
additionalNodesInfo?:
|
53
|
+
additionalNodesInfo?: AdditionalNodesInfo;
|
54
54
|
}
|
55
55
|
|
56
|
-
export const Nodes = ({
|
56
|
+
export const Nodes = ({path, type, className, additionalNodesInfo = {}}: NodesProps) => {
|
57
57
|
const dispatch = useDispatch();
|
58
58
|
|
59
|
-
const isClusterNodes = !
|
59
|
+
const isClusterNodes = !path;
|
60
60
|
|
61
61
|
// Since Nodes component is used in several places,
|
62
62
|
// we need to reset filters, searchValue and loading state
|
63
63
|
// in nodes reducer when path changes
|
64
64
|
useEffect(() => {
|
65
65
|
dispatch(resetNodesState());
|
66
|
-
}, [dispatch,
|
66
|
+
}, [dispatch, path]);
|
67
67
|
|
68
68
|
const {wasLoaded, loading, error, nodesUptimeFilter, searchValue, totalNodes} =
|
69
69
|
useTypedSelector((state) => state.nodes);
|
@@ -77,12 +77,14 @@ export const Nodes = ({tenantPath, className, additionalNodesInfo = {}}: NodesPr
|
|
77
77
|
);
|
78
78
|
|
79
79
|
const fetchNodes = useCallback(() => {
|
80
|
-
|
81
|
-
|
80
|
+
// For not DB entities we always use /compute endpoint instead of /nodes
|
81
|
+
// since /nodes can return data only for tenants
|
82
|
+
if (path && (!JSON.parse(useNodesEndpoint) || !isDatabaseEntityType(type))) {
|
83
|
+
dispatch(getComputeNodes(path));
|
82
84
|
} else {
|
83
|
-
dispatch(getNodes({tenant:
|
85
|
+
dispatch(getNodes({tenant: path}));
|
84
86
|
}
|
85
|
-
}, [dispatch,
|
87
|
+
}, [dispatch, path, type, useNodesEndpoint]);
|
86
88
|
|
87
89
|
useAutofetcher(fetchNodes, [fetchNodes], isClusterNodes ? true : autorefresh);
|
88
90
|
|
@@ -1,22 +1,23 @@
|
|
1
1
|
.ydb-nodes-table {
|
2
|
-
&__host-
|
2
|
+
&__host-field-wrapper {
|
3
3
|
display: flex;
|
4
4
|
}
|
5
5
|
|
6
|
-
&
|
7
|
-
display:
|
8
|
-
align-items: center;
|
9
|
-
|
10
|
-
margin-left: 4px;
|
6
|
+
&__host-wrapper {
|
7
|
+
display: flex;
|
11
8
|
|
12
|
-
|
13
|
-
margin: 0 4px;
|
14
|
-
}
|
9
|
+
max-width: 330px;
|
15
10
|
}
|
16
11
|
|
17
|
-
&__host
|
12
|
+
&__host {
|
18
13
|
overflow: hidden;
|
19
14
|
}
|
15
|
+
|
16
|
+
&__external-button {
|
17
|
+
display: none;
|
18
|
+
|
19
|
+
margin-left: 4px;
|
20
|
+
}
|
20
21
|
}
|
21
22
|
|
22
23
|
.data-table__row:hover {
|
@@ -1,12 +1,13 @@
|
|
1
1
|
import cn from 'bem-cn-lite';
|
2
2
|
import DataTable, {Column} from '@gravity-ui/react-data-table';
|
3
|
-
import {Button, Popover} from '@gravity-ui/uikit';
|
3
|
+
import {Button, Popover, PopoverBehavior} from '@gravity-ui/uikit';
|
4
4
|
|
5
|
-
import {
|
5
|
+
import {IconWrapper} from '../../components/Icon';
|
6
6
|
import EntityStatus from '../../components/EntityStatus/EntityStatus';
|
7
7
|
import PoolsGraph from '../../components/PoolsGraph/PoolsGraph';
|
8
8
|
import ProgressViewer from '../../components/ProgressViewer/ProgressViewer';
|
9
9
|
import {TabletsStatistic} from '../../components/TabletsStatistic';
|
10
|
+
import {NodeEndpointsTooltip} from '../../components/Tooltips/NodeEndpointsTooltip/NodeEndpointsTooltip';
|
10
11
|
|
11
12
|
import {formatBytesToGigabyte} from '../../utils/index';
|
12
13
|
import {INodesPreparedEntity} from '../../types/store/nodes';
|
@@ -46,28 +47,32 @@ export function getNodesColumns({
|
|
46
47
|
return <span>—</span>;
|
47
48
|
}
|
48
49
|
return (
|
49
|
-
<div className={b('host-
|
50
|
-
<
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
50
|
+
<div className={b('host-field-wrapper')}>
|
51
|
+
<Popover
|
52
|
+
content={<NodeEndpointsTooltip data={row} />}
|
53
|
+
placement={['top', 'bottom']}
|
54
|
+
behavior={PopoverBehavior.Immediate}
|
55
|
+
>
|
56
|
+
<div className={b('host-wrapper')}>
|
57
|
+
<EntityStatus
|
58
|
+
name={row.Host}
|
59
|
+
status={row.SystemState}
|
60
|
+
path={getDefaultNodePath(row.NodeId)}
|
61
|
+
hasClipboardButton
|
62
|
+
className={b('host')}
|
63
|
+
/>
|
64
|
+
{nodeRef && (
|
65
|
+
<Button
|
66
|
+
size="s"
|
67
|
+
href={nodeRef}
|
68
|
+
className={b('external-button')}
|
69
|
+
target="_blank"
|
70
|
+
>
|
71
|
+
<IconWrapper name="external" />
|
72
|
+
</Button>
|
73
|
+
)}
|
74
|
+
</div>
|
75
|
+
</Popover>
|
71
76
|
</div>
|
72
77
|
);
|
73
78
|
},
|
@@ -3,6 +3,8 @@ import cn from 'bem-cn-lite';
|
|
3
3
|
|
4
4
|
import {Popup, PopupProps} from '@gravity-ui/uikit';
|
5
5
|
|
6
|
+
import type {NodesMap} from '../../../types/store/nodesList';
|
7
|
+
|
6
8
|
import {InfoViewer, InfoViewerItem} from '../../../components/InfoViewer';
|
7
9
|
|
8
10
|
import {EFlag} from '../../../types/api/enums';
|
@@ -17,12 +19,7 @@ const b = cn('pdisk-storage-popup');
|
|
17
19
|
|
18
20
|
const errorColors = [EFlag.Orange, EFlag.Red, EFlag.Yellow];
|
19
21
|
|
20
|
-
export
|
21
|
-
// NodeId => Host
|
22
|
-
[nodeId: number]: string;
|
23
|
-
}
|
24
|
-
|
25
|
-
export const preparePDiskData = (data: TPDiskStateInfo, nodes?: NodesHosts) => {
|
22
|
+
export const preparePDiskData = (data: TPDiskStateInfo, nodes?: NodesMap) => {
|
26
23
|
const {AvailableSize, TotalSize, State, PDiskId, NodeId, Path, Realtime, Device} = data;
|
27
24
|
|
28
25
|
const pdiskData: InfoViewerItem[] = [
|
@@ -35,8 +32,8 @@ export const preparePDiskData = (data: TPDiskStateInfo, nodes?: NodesHosts) => {
|
|
35
32
|
pdiskData.push({label: 'Node Id', value: NodeId});
|
36
33
|
}
|
37
34
|
|
38
|
-
if (nodes && NodeId && nodes
|
39
|
-
pdiskData.push({label: 'Host', value: nodes
|
35
|
+
if (nodes && NodeId && nodes.get(NodeId)) {
|
36
|
+
pdiskData.push({label: 'Host', value: nodes.get(NodeId)});
|
40
37
|
}
|
41
38
|
|
42
39
|
if (Path) {
|
@@ -61,7 +58,7 @@ export const preparePDiskData = (data: TPDiskStateInfo, nodes?: NodesHosts) => {
|
|
61
58
|
|
62
59
|
interface PDiskPopupProps extends PopupProps {
|
63
60
|
data: TPDiskStateInfo;
|
64
|
-
nodes?:
|
61
|
+
nodes?: NodesMap;
|
65
62
|
}
|
66
63
|
|
67
64
|
export const PDiskPopup = ({data, nodes, ...props}: PDiskPopupProps) => {
|
@@ -22,7 +22,6 @@ import {
|
|
22
22
|
setVisibleEntities,
|
23
23
|
setStorageFilter,
|
24
24
|
setUsageFilter,
|
25
|
-
getNodesObject,
|
26
25
|
StorageTypes,
|
27
26
|
setStorageType,
|
28
27
|
setNodesUptimeFilter,
|
@@ -32,7 +31,7 @@ import {
|
|
32
31
|
getStorageNodesCount,
|
33
32
|
getUsageFilterOptions,
|
34
33
|
} from '../../store/reducers/storage';
|
35
|
-
import {getNodesList} from '../../store/reducers/nodesList';
|
34
|
+
import {getNodesList, selectNodesMap} from '../../store/reducers/nodesList';
|
36
35
|
import StorageGroups from './StorageGroups/StorageGroups';
|
37
36
|
import StorageNodes from './StorageNodes/StorageNodes';
|
38
37
|
import {DEFAULT_TABLE_SETTINGS} from '../../utils/constants';
|
@@ -74,6 +73,7 @@ class Storage extends React.Component {
|
|
74
73
|
nodesUptimeFilter: PropTypes.string,
|
75
74
|
setNodesUptimeFilter: PropTypes.func,
|
76
75
|
setDataWasNotLoaded: PropTypes.func,
|
76
|
+
additionalNodesInfo: PropTypes.object,
|
77
77
|
};
|
78
78
|
|
79
79
|
componentDidMount() {
|
@@ -181,8 +181,14 @@ class Storage extends React.Component {
|
|
181
181
|
}
|
182
182
|
|
183
183
|
renderDataTable() {
|
184
|
-
const {
|
185
|
-
|
184
|
+
const {
|
185
|
+
flatListStorageEntities,
|
186
|
+
visibleEntities,
|
187
|
+
nodesUptimeFilter,
|
188
|
+
nodes,
|
189
|
+
storageType,
|
190
|
+
additionalNodesInfo,
|
191
|
+
} = this.props;
|
186
192
|
|
187
193
|
return (
|
188
194
|
<div className={b('table-wrapper')}>
|
@@ -202,6 +208,7 @@ class Storage extends React.Component {
|
|
202
208
|
data={flatListStorageEntities}
|
203
209
|
tableSettings={tableSettings}
|
204
210
|
onShowAll={this.onShowAllNodes}
|
211
|
+
additionalNodesInfo={additionalNodesInfo}
|
205
212
|
/>
|
206
213
|
)}
|
207
214
|
</div>
|
@@ -345,7 +352,7 @@ function mapStateToProps(state) {
|
|
345
352
|
flatListStorageEntities: getFilteredEntities(state),
|
346
353
|
groupsCount: getStoragePoolsGroupsCount(state),
|
347
354
|
autorefresh: state.schema.autorefresh,
|
348
|
-
nodes:
|
355
|
+
nodes: selectNodesMap(state),
|
349
356
|
nodesCount: getStorageNodesCount(state),
|
350
357
|
loading,
|
351
358
|
wasLoaded,
|
@@ -3,6 +3,8 @@ import cn from 'bem-cn-lite';
|
|
3
3
|
import DataTable, {Column, Settings, SortOrder} from '@gravity-ui/react-data-table';
|
4
4
|
import {Icon, Label, Popover, PopoverBehavior} from '@gravity-ui/uikit';
|
5
5
|
|
6
|
+
import type {NodesMap} from '../../../types/store/nodesList';
|
7
|
+
|
6
8
|
import shieldIcon from '../../../assets/icons/shield.svg';
|
7
9
|
|
8
10
|
import {Stack} from '../../../components/Stack/Stack';
|
@@ -45,7 +47,7 @@ type TableColumnsIdsValues = typeof TableColumnsIds[TableColumnsIdsKeys];
|
|
45
47
|
|
46
48
|
interface StorageGroupsProps {
|
47
49
|
data: any;
|
48
|
-
nodes:
|
50
|
+
nodes: NodesMap;
|
49
51
|
tableSettings: Settings;
|
50
52
|
visibleEntities: keyof typeof VisibleEntities;
|
51
53
|
onShowAll?: VoidFunction;
|
@@ -20,18 +20,19 @@
|
|
20
20
|
margin-right: 0px;
|
21
21
|
}
|
22
22
|
}
|
23
|
-
&__fqdn-wrapper {
|
23
|
+
&__fqdn-field-wrapper {
|
24
24
|
width: 330px;
|
25
25
|
}
|
26
|
-
&__fqdn {
|
27
|
-
display:
|
28
|
-
|
26
|
+
&__fqdn-wrapper {
|
27
|
+
display: flex;
|
28
|
+
align-items: center;
|
29
29
|
|
30
30
|
max-width: 330px;
|
31
|
-
|
32
|
-
vertical-align: top;
|
33
|
-
text-overflow: ellipsis;
|
34
31
|
}
|
32
|
+
&__fqdn {
|
33
|
+
overflow: hidden;
|
34
|
+
}
|
35
|
+
|
35
36
|
&__group-id {
|
36
37
|
font-weight: 500;
|
37
38
|
}
|
@@ -39,4 +40,16 @@
|
|
39
40
|
&__node_unavailable {
|
40
41
|
opacity: 0.6;
|
41
42
|
}
|
43
|
+
|
44
|
+
&__external-button {
|
45
|
+
display: none;
|
46
|
+
|
47
|
+
margin-left: 4px;
|
48
|
+
}
|
49
|
+
}
|
50
|
+
|
51
|
+
.data-table__row:hover {
|
52
|
+
.global-storage-nodes__external-button {
|
53
|
+
display: inline-flex;
|
54
|
+
}
|
42
55
|
}
|
@@ -2,10 +2,20 @@ import _ from 'lodash';
|
|
2
2
|
import cn from 'bem-cn-lite';
|
3
3
|
|
4
4
|
import DataTable, {Column, Settings, SortOrder} from '@gravity-ui/react-data-table';
|
5
|
-
import {Popover, PopoverBehavior} from '@gravity-ui/uikit';
|
5
|
+
import {Button, Popover, PopoverBehavior} from '@gravity-ui/uikit';
|
6
|
+
|
7
|
+
import {NodeEndpointsTooltip} from '../../../components/Tooltips/NodeEndpointsTooltip/NodeEndpointsTooltip';
|
8
|
+
import EntityStatus from '../../../components/EntityStatus/EntityStatus';
|
9
|
+
import {IconWrapper} from '../../../components/Icon';
|
6
10
|
|
7
11
|
import {VisibleEntities} from '../../../store/reducers/storage';
|
8
|
-
import {
|
12
|
+
import {
|
13
|
+
AdditionalNodesInfo,
|
14
|
+
isUnavailableNode,
|
15
|
+
NodesUptimeFilterValues,
|
16
|
+
} from '../../../utils/nodes';
|
17
|
+
|
18
|
+
import {getDefaultNodePath} from '../../Node/NodePages';
|
9
19
|
|
10
20
|
import {EmptyFilter} from '../EmptyFilter/EmptyFilter';
|
11
21
|
import {PDisk} from '../PDisk';
|
@@ -33,6 +43,7 @@ interface StorageNodesProps {
|
|
33
43
|
visibleEntities: keyof typeof VisibleEntities;
|
34
44
|
nodesUptimeFilter: keyof typeof NodesUptimeFilterValues;
|
35
45
|
onShowAll?: VoidFunction;
|
46
|
+
additionalNodesInfo?: AdditionalNodesInfo;
|
36
47
|
}
|
37
48
|
|
38
49
|
const tableColumnsNames: Record<TableColumnsIdsValues, string> = {
|
@@ -73,7 +84,10 @@ function StorageNodes({
|
|
73
84
|
visibleEntities,
|
74
85
|
onShowAll,
|
75
86
|
nodesUptimeFilter,
|
87
|
+
additionalNodesInfo,
|
76
88
|
}: StorageNodesProps) {
|
89
|
+
const getNodeRef = additionalNodesInfo?.getNodeRef;
|
90
|
+
|
77
91
|
const allColumns: Column<any>[] = [
|
78
92
|
{
|
79
93
|
name: TableColumnsIds.NodeId,
|
@@ -85,15 +99,37 @@ function StorageNodes({
|
|
85
99
|
name: TableColumnsIds.FQDN,
|
86
100
|
header: tableColumnsNames[TableColumnsIds.FQDN],
|
87
101
|
width: 350,
|
88
|
-
render: ({
|
102
|
+
render: ({row}) => {
|
103
|
+
const nodeRef = getNodeRef ? getNodeRef(row) + 'internal' : undefined;
|
104
|
+
if (!row.Host) {
|
105
|
+
return <span>—</span>;
|
106
|
+
}
|
89
107
|
return (
|
90
|
-
<div className={b('fqdn-wrapper')}>
|
108
|
+
<div className={b('fqdn-field-wrapper')}>
|
91
109
|
<Popover
|
92
|
-
content={
|
93
|
-
placement={['
|
110
|
+
content={<NodeEndpointsTooltip data={row} />}
|
111
|
+
placement={['top', 'bottom']}
|
94
112
|
behavior={PopoverBehavior.Immediate}
|
95
113
|
>
|
96
|
-
<
|
114
|
+
<div className={b('fqdn-wrapper')}>
|
115
|
+
<EntityStatus
|
116
|
+
name={row.Host}
|
117
|
+
showStatus={false}
|
118
|
+
path={getDefaultNodePath(row.NodeId)}
|
119
|
+
hasClipboardButton
|
120
|
+
className={b('fqdn')}
|
121
|
+
/>
|
122
|
+
{nodeRef && (
|
123
|
+
<Button
|
124
|
+
size="s"
|
125
|
+
href={nodeRef}
|
126
|
+
className={b('external-button')}
|
127
|
+
target="_blank"
|
128
|
+
>
|
129
|
+
<IconWrapper name="external" />
|
130
|
+
</Button>
|
131
|
+
)}
|
132
|
+
</div>
|
97
133
|
</Popover>
|
98
134
|
</div>
|
99
135
|
);
|
@@ -1,6 +1,8 @@
|
|
1
1
|
import React, {useEffect, useState, useRef, useMemo} from 'react';
|
2
2
|
import cn from 'bem-cn-lite';
|
3
3
|
|
4
|
+
import type {NodesMap} from '../../../types/store/nodesList';
|
5
|
+
|
4
6
|
import {InternalLink} from '../../../components/InternalLink';
|
5
7
|
|
6
8
|
import routes, {createHref} from '../../../routes';
|
@@ -12,7 +14,6 @@ import {isFullVDiskData} from '../../../utils/storage';
|
|
12
14
|
import {STRUCTURE} from '../../Node/NodePages';
|
13
15
|
|
14
16
|
import {DiskStateProgressBar, EDiskStateSeverity} from '../DiskStateProgressBar';
|
15
|
-
import type {NodesHosts} from '../PDiskPopup';
|
16
17
|
import {VDiskPopup} from '../VDiskPopup';
|
17
18
|
|
18
19
|
import type {IUnavailableDonor} from '../utils/types';
|
@@ -50,7 +51,7 @@ const getColorSeverity = (color?: EFlag) => {
|
|
50
51
|
interface VDiskProps {
|
51
52
|
data?: TVDiskStateInfo | IUnavailableDonor;
|
52
53
|
poolName?: string;
|
53
|
-
nodes?:
|
54
|
+
nodes?: NodesMap;
|
54
55
|
compact?: boolean;
|
55
56
|
}
|
56
57
|
|
@@ -3,6 +3,8 @@ import cn from 'bem-cn-lite';
|
|
3
3
|
|
4
4
|
import {Label, Popup, PopupProps} from '@gravity-ui/uikit';
|
5
5
|
|
6
|
+
import type {NodesMap} from '../../../types/store/nodesList';
|
7
|
+
|
6
8
|
import {InfoViewer, InfoViewerItem} from '../../../components/InfoViewer';
|
7
9
|
|
8
10
|
import {EFlag} from '../../../types/api/enums';
|
@@ -13,7 +15,7 @@ import {isFullVDiskData} from '../../../utils/storage';
|
|
13
15
|
|
14
16
|
import type {IUnavailableDonor} from '../utils/types';
|
15
17
|
|
16
|
-
import {
|
18
|
+
import {preparePDiskData} from '../PDiskPopup';
|
17
19
|
|
18
20
|
import './VDiskPopup.scss';
|
19
21
|
|
@@ -128,7 +130,7 @@ const prepareVDiskData = (data: TVDiskStateInfo, poolName?: string) => {
|
|
128
130
|
interface VDiskPopupProps extends PopupProps {
|
129
131
|
data: TVDiskStateInfo | IUnavailableDonor;
|
130
132
|
poolName?: string;
|
131
|
-
nodes?:
|
133
|
+
nodes?: NodesMap;
|
132
134
|
}
|
133
135
|
|
134
136
|
export const VDiskPopup = ({data, poolName, nodes, ...props}: VDiskPopupProps) => {
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import block from 'bem-cn-lite';
|
2
2
|
|
3
|
-
import {ReadLagImage} from '../../../../../components/LagImages';
|
4
3
|
import {LabelWithPopover} from '../../../../../components/LabelWithPopover';
|
4
|
+
import {LagPopoverContent} from '../../../../../components/LagPopoverContent';
|
5
5
|
|
6
6
|
import {CONSUMERS_COLUMNS_IDS, CONSUMERS_COLUMNS_TITILES} from '../utils/constants';
|
7
7
|
|
@@ -14,14 +14,7 @@ const b = block('ydb-diagnostics-consumers-columns-header');
|
|
14
14
|
export const ReadLagsHeader = () => (
|
15
15
|
<LabelWithPopover
|
16
16
|
className={b('lags')}
|
17
|
-
|
18
|
-
popoverContent={
|
19
|
-
<div className={b('lags-popover-content')}>
|
20
|
-
<div>{i18n('lagsPopover.readLags')}</div>
|
21
|
-
<div>
|
22
|
-
<ReadLagImage />
|
23
|
-
</div>
|
24
|
-
</div>
|
25
|
-
}
|
17
|
+
text={CONSUMERS_COLUMNS_TITILES[CONSUMERS_COLUMNS_IDS.READ_LAGS]}
|
18
|
+
popoverContent={<LagPopoverContent text={i18n('lagsPopover.readLags')} type="read" />}
|
26
19
|
/>
|
27
20
|
);
|
@@ -1,7 +1,7 @@
|
|
1
1
|
{
|
2
2
|
"noConsumersMessage.topic": "У этого топика нет читателей",
|
3
3
|
"noConsumersMessage.stream": "У этого стрима нет читателей",
|
4
|
-
"lagsPopover.readLags": "Статистика лагов чтения,
|
4
|
+
"lagsPopover.readLags": "Статистика лагов чтения, максимальное значение среди всех партиций читателя (формат времени дд чч:мм:сс)",
|
5
5
|
"table.emptyDataMessage": "По заданному поиску нет читателей",
|
6
6
|
"controls.search": "Consumer"
|
7
7
|
}
|
@@ -131,7 +131,8 @@ function Diagnostics(props: DiagnosticsProps) {
|
|
131
131
|
case GeneralPagesIds.nodes: {
|
132
132
|
return (
|
133
133
|
<Nodes
|
134
|
-
|
134
|
+
path={currentSchemaPath}
|
135
|
+
type={type}
|
135
136
|
additionalNodesInfo={props.additionalNodesInfo}
|
136
137
|
/>
|
137
138
|
);
|
@@ -89,12 +89,12 @@ export const DATABASE_PAGES = [
|
|
89
89
|
describe,
|
90
90
|
];
|
91
91
|
|
92
|
-
export const TABLE_PAGES = [overview, topShards, graph, tablets, hotKeys, describe];
|
92
|
+
export const TABLE_PAGES = [overview, topShards, nodes, graph, tablets, hotKeys, describe];
|
93
93
|
|
94
|
-
export const DIR_PAGES = [overview, topShards, describe];
|
94
|
+
export const DIR_PAGES = [overview, topShards, nodes, describe];
|
95
95
|
|
96
|
-
export const CDC_STREAM_PAGES = [overview, consumers, partitions, describe];
|
97
|
-
export const TOPIC_PAGES = [overview, consumers, partitions, describe];
|
96
|
+
export const CDC_STREAM_PAGES = [overview, consumers, partitions, nodes, describe];
|
97
|
+
export const TOPIC_PAGES = [overview, consumers, partitions, nodes, describe];
|
98
98
|
|
99
99
|
// verbose mapping to guarantee correct tabs for new path types
|
100
100
|
// TS will error when a new type is added but not mapped here
|
@@ -1,16 +1,19 @@
|
|
1
1
|
import cn from 'bem-cn-lite';
|
2
|
-
import {isEmpty} from 'lodash/fp';
|
3
2
|
|
4
|
-
import type {
|
3
|
+
import type {IPreparedTopicStats} from '../../../../../types/store/topic';
|
5
4
|
|
6
5
|
import {Loader} from '../../../../../components/Loader';
|
7
|
-
import {InfoViewerItem,
|
8
|
-
|
9
|
-
import {
|
6
|
+
import {InfoViewerItem, InfoViewer} from '../../../../../components/InfoViewer';
|
7
|
+
import {SpeedMultiMeter} from '../../../../../components/SpeedMultiMeter';
|
8
|
+
import {LabelWithPopover} from '../../../../../components/LabelWithPopover';
|
9
|
+
import {LagPopoverContent} from '../../../../../components/LagPopoverContent';
|
10
|
+
import {ResponseError} from '../../../../../components/Errors/ResponseError';
|
10
11
|
|
11
12
|
import {useTypedSelector} from '../../../../../utils/hooks';
|
12
|
-
import {
|
13
|
-
import {formatBps} from '../../../../../utils';
|
13
|
+
import {formatDurationToShortTimeFormat} from '../../../../../utils/timeParsers';
|
14
|
+
import {formatBps, formatBytes} from '../../../../../utils';
|
15
|
+
|
16
|
+
import {selectPreparedTopicStats} from '../../../../../store/reducers/topic';
|
14
17
|
|
15
18
|
import i18n from './i18n';
|
16
19
|
|
@@ -18,35 +21,61 @@ import './TopicStats.scss';
|
|
18
21
|
|
19
22
|
const b = cn('ydb-overview-topic-stats');
|
20
23
|
|
21
|
-
const prepareTopicInfo = (data:
|
24
|
+
const prepareTopicInfo = (data: IPreparedTopicStats): Array<InfoViewerItem> => {
|
22
25
|
return [
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
+
{label: 'Store size', value: formatBytes(data.storeSize)},
|
27
|
+
{
|
28
|
+
label: (
|
29
|
+
<LabelWithPopover
|
30
|
+
text={'Write idle time'}
|
31
|
+
popoverContent={
|
32
|
+
<LagPopoverContent text={i18n('writeIdleTimePopover')} type="write" />
|
33
|
+
}
|
34
|
+
/>
|
35
|
+
),
|
36
|
+
value: formatDurationToShortTimeFormat(data.partitionsIdleTime),
|
37
|
+
},
|
38
|
+
{
|
39
|
+
label: (
|
40
|
+
<LabelWithPopover
|
41
|
+
text={'Write lag'}
|
42
|
+
popoverContent={
|
43
|
+
<LagPopoverContent text={i18n('writeLagPopover')} type="write" />
|
44
|
+
}
|
45
|
+
/>
|
46
|
+
),
|
47
|
+
value: formatDurationToShortTimeFormat(data.partitionsWriteLag),
|
48
|
+
},
|
49
|
+
{
|
50
|
+
label: 'Average write speed',
|
51
|
+
value: <SpeedMultiMeter data={data.writeSpeed} withValue={false} />,
|
52
|
+
},
|
26
53
|
];
|
27
54
|
};
|
28
55
|
|
29
|
-
const prepareBytesWrittenInfo = (data:
|
30
|
-
const
|
56
|
+
const prepareBytesWrittenInfo = (data: IPreparedTopicStats): Array<InfoViewerItem> => {
|
57
|
+
const writeSpeed = data.writeSpeed;
|
31
58
|
|
32
59
|
return [
|
33
60
|
{
|
34
61
|
label: 'per minute',
|
35
|
-
value: formatBps(
|
62
|
+
value: formatBps(writeSpeed.perMinute),
|
36
63
|
},
|
37
64
|
{
|
38
65
|
label: 'per hour',
|
39
|
-
value: formatBps(
|
66
|
+
value: formatBps(writeSpeed.perHour),
|
40
67
|
},
|
41
68
|
{
|
42
69
|
label: 'per day',
|
43
|
-
value: formatBps(
|
70
|
+
value: formatBps(writeSpeed.perDay),
|
44
71
|
},
|
45
72
|
];
|
46
73
|
};
|
47
74
|
|
48
75
|
export const TopicStats = () => {
|
49
|
-
const {
|
76
|
+
const {error, loading, wasLoaded} = useTypedSelector((state) => state.topic);
|
77
|
+
|
78
|
+
const data = useTypedSelector(selectPreparedTopicStats);
|
50
79
|
|
51
80
|
if (loading && !wasLoaded) {
|
52
81
|
return (
|
@@ -56,24 +85,14 @@ export const TopicStats = () => {
|
|
56
85
|
);
|
57
86
|
}
|
58
87
|
|
59
|
-
//
|
60
|
-
//
|
61
|
-
//
|
62
|
-
|
63
|
-
// 3. HTML page of Internal Viewer with an error
|
64
|
-
// 4. Data with no topic stats
|
65
|
-
// 5. Topic Stats as an empty object
|
66
|
-
if (
|
67
|
-
error ||
|
68
|
-
!data ||
|
69
|
-
typeof data !== 'object' ||
|
70
|
-
!data.topic_stats ||
|
71
|
-
isEmpty(data.topic_stats)
|
72
|
-
) {
|
88
|
+
// If there is at least some empty data object
|
89
|
+
// we initialize its fields with zero values
|
90
|
+
// so no data at all is considered to be error as well
|
91
|
+
if (error || !data) {
|
73
92
|
return (
|
74
93
|
<div className={b()}>
|
75
94
|
<div className={b('title')}>Stats</div>
|
76
|
-
<
|
95
|
+
<ResponseError error={error} />
|
77
96
|
</div>
|
78
97
|
);
|
79
98
|
}
|
@@ -1,3 +1,4 @@
|
|
1
1
|
{
|
2
|
-
"
|
2
|
+
"writeLagPopover": "Write lag, maximum among all topic partitions",
|
3
|
+
"writeIdleTimePopover": "Write idle time, maximum among all topic partitions"
|
3
4
|
}
|
@@ -1,3 +1,4 @@
|
|
1
1
|
{
|
2
|
-
"
|
2
|
+
"writeLagPopover": "Лаг записи, максимальное значение среди всех партиций топика",
|
3
|
+
"writeIdleTimePopover": "Время без записи, максимальное значение среди всех партиций топика"
|
3
4
|
}
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import block from 'bem-cn-lite';
|
2
2
|
|
3
3
|
import {LabelWithPopover} from '../../../../../components/LabelWithPopover';
|
4
|
-
import {
|
4
|
+
import {LagPopoverContent} from '../../../../../components/LagPopoverContent';
|
5
5
|
|
6
6
|
import {PARTITIONS_COLUMNS_IDS, PARTITIONS_COLUMNS_TITILES} from '../utils/constants';
|
7
7
|
|
@@ -28,37 +28,23 @@ export const ReadSessionHeader = () => (
|
|
28
28
|
export const WriteLagsHeader = () => (
|
29
29
|
<LabelWithPopover
|
30
30
|
className={b('lags')}
|
31
|
-
|
32
|
-
popoverContent={
|
33
|
-
<div className={b('lags-popover-content')}>
|
34
|
-
<div>{i18n('lagsPopover.writeLags')}</div>
|
35
|
-
<div>
|
36
|
-
<WriteLagImage />
|
37
|
-
</div>
|
38
|
-
</div>
|
39
|
-
}
|
31
|
+
text={PARTITIONS_COLUMNS_TITILES[PARTITIONS_COLUMNS_IDS.READ_LAGS]}
|
32
|
+
popoverContent={<LagPopoverContent text={i18n('lagsPopover.readLags')} type="read" />}
|
40
33
|
/>
|
41
34
|
);
|
42
35
|
|
43
36
|
export const ReadLagsHeader = () => (
|
44
37
|
<LabelWithPopover
|
45
38
|
className={b('lags')}
|
46
|
-
|
47
|
-
popoverContent={
|
48
|
-
<div className={b('lags-popover-content')}>
|
49
|
-
<div>{i18n('lagsPopover.readLags')}</div>
|
50
|
-
<div>
|
51
|
-
<ReadLagImage />
|
52
|
-
</div>
|
53
|
-
</div>
|
54
|
-
}
|
39
|
+
text={PARTITIONS_COLUMNS_TITILES[PARTITIONS_COLUMNS_IDS.WRITE_LAGS]}
|
40
|
+
popoverContent={<LagPopoverContent text={i18n('lagsPopover.writeLags')} type="write" />}
|
55
41
|
/>
|
56
42
|
);
|
57
43
|
|
58
44
|
export const UnreadMessagesHeader = () => (
|
59
45
|
<LabelWithPopover
|
60
46
|
className={b('messages')}
|
61
|
-
|
47
|
+
text={PARTITIONS_COLUMNS_TITILES[PARTITIONS_COLUMNS_IDS.UNREAD_MESSAGES]}
|
62
48
|
popoverContent={
|
63
49
|
<div className={b('messages-popover-content')}>{i18n('headers.unread')}</div>
|
64
50
|
}
|
@@ -68,7 +54,7 @@ export const UnreadMessagesHeader = () => (
|
|
68
54
|
export const UncommitedMessagesHeader = () => (
|
69
55
|
<LabelWithPopover
|
70
56
|
className={b('messages')}
|
71
|
-
|
57
|
+
text={PARTITIONS_COLUMNS_TITILES[PARTITIONS_COLUMNS_IDS.UNCOMMITED_MESSAGES]}
|
72
58
|
popoverContent={
|
73
59
|
<div className={b('messages-popover-content')}>{i18n('headers.uncommited')}</div>
|
74
60
|
}
|
package/dist/services/api.d.ts
CHANGED
@@ -77,6 +77,10 @@ interface Window {
|
|
77
77
|
consumer?: string;
|
78
78
|
}) => Promise<import('../types/api/consumer').DescribeConsumerResult>;
|
79
79
|
getHostInfo: () => Promise<import('../types/api/systemState').TEvSystemStateResponse>;
|
80
|
+
getNodeInfo: (
|
81
|
+
id?: string,
|
82
|
+
) => Promise<import('../types/api/systemState').TEvSystemStateResponse>;
|
83
|
+
getNodesList: () => Promise<import('../types/api/nodesList').TEvNodesInfo>;
|
80
84
|
[method: string]: Function;
|
81
85
|
};
|
82
86
|
}
|
@@ -1,11 +1,19 @@
|
|
1
|
+
import type {Reducer} from 'redux';
|
2
|
+
|
3
|
+
import type {
|
4
|
+
NodesListState,
|
5
|
+
NodesListAction,
|
6
|
+
NodesListRootStateSlice,
|
7
|
+
NodesMap,
|
8
|
+
} from '../../types/store/nodesList';
|
1
9
|
import '../../services/api';
|
2
10
|
import {createRequestActionTypes, createApiRequest} from '../utils';
|
3
11
|
|
4
|
-
const FETCH_NODES_LIST = createRequestActionTypes('
|
12
|
+
export const FETCH_NODES_LIST = createRequestActionTypes('nodesList', 'FETCH_NODES_LIST');
|
5
13
|
|
6
14
|
const initialState = {loading: true, wasLoaded: false, data: []};
|
7
15
|
|
8
|
-
const nodesList =
|
16
|
+
const nodesList: Reducer<NodesListState, NodesListAction> = (state = initialState, action) => {
|
9
17
|
switch (action.type) {
|
10
18
|
case FETCH_NODES_LIST.REQUEST: {
|
11
19
|
return {
|
@@ -36,13 +44,17 @@ const nodesList = function (state = initialState, action) {
|
|
36
44
|
|
37
45
|
export function getNodesList() {
|
38
46
|
return createApiRequest({
|
39
|
-
request: window.api.
|
47
|
+
request: window.api.getNodesList(),
|
40
48
|
actions: FETCH_NODES_LIST,
|
41
|
-
dataHandler: (data) => {
|
42
|
-
const {SystemStateInfo: nodes = []} = data;
|
43
|
-
return nodes;
|
44
|
-
},
|
45
49
|
});
|
46
50
|
}
|
47
51
|
|
52
|
+
export const selectNodesMap = (state: NodesListRootStateSlice) =>
|
53
|
+
state.nodesList.data?.reduce<NodesMap>((nodesMap, node) => {
|
54
|
+
if (node.Id && node.Host) {
|
55
|
+
nodesMap.set(node.Id, node.Host);
|
56
|
+
}
|
57
|
+
return nodesMap;
|
58
|
+
}, new Map());
|
59
|
+
|
48
60
|
export default nodesList;
|
@@ -204,15 +204,6 @@ export const getUsageFilter = (state) => state.storage.usageFilter;
|
|
204
204
|
export const getVisibleEntities = (state) => state.storage.visible;
|
205
205
|
export const getNodesUptimeFilter = (state) => state.storage.nodesUptimeFilter;
|
206
206
|
export const getStorageType = (state) => state.storage.type;
|
207
|
-
export const getNodesObject = (state) =>
|
208
|
-
_.reduce(
|
209
|
-
state.nodesList?.data,
|
210
|
-
(acc, el) => {
|
211
|
-
acc[el.NodeId] = el.Host;
|
212
|
-
return acc;
|
213
|
-
},
|
214
|
-
{},
|
215
|
-
);
|
216
207
|
|
217
208
|
const FLAGS_POINTS = {
|
218
209
|
Green: 1,
|
@@ -314,9 +305,10 @@ export const getFlatListStorageNodes = createSelector([getStorageNodes], (storag
|
|
314
305
|
return {
|
315
306
|
NodeId: node.NodeId,
|
316
307
|
SystemState: systemState.SystemState,
|
317
|
-
FQDN: systemState.Host,
|
318
308
|
DataCenter: systemState.DataCenter,
|
319
309
|
Rack: systemState.Rack,
|
310
|
+
Host: systemState.Host,
|
311
|
+
Endpoints: systemState.Endpoints,
|
320
312
|
uptime: calcUptime(systemState.StartTime),
|
321
313
|
StartTime: systemState.StartTime,
|
322
314
|
PDisks: node.PDisks,
|
@@ -378,7 +370,7 @@ const filterByText = (entities, type, text) => {
|
|
378
370
|
|
379
371
|
return (
|
380
372
|
entity.NodeId.toString().includes(cleanedFilter) ||
|
381
|
-
entity.
|
373
|
+
entity.Host.toLowerCase().includes(cleanedFilter)
|
382
374
|
);
|
383
375
|
});
|
384
376
|
};
|
@@ -0,0 +1,25 @@
|
|
1
|
+
/**
|
2
|
+
* endpoint: /viewer/json/nodesList
|
3
|
+
*
|
4
|
+
* source: https://github.com/ydb-platform/ydb/blob/main/library/cpp/actors/core/interconnect.h
|
5
|
+
*/
|
6
|
+
export type TEvNodesInfo = TNodeInfo[];
|
7
|
+
|
8
|
+
export interface TNodeInfo {
|
9
|
+
Id?: number;
|
10
|
+
Host?: string;
|
11
|
+
ResolveHost?: string;
|
12
|
+
Address?: string;
|
13
|
+
Port?: number;
|
14
|
+
PhysicalLocation?: TNodeLocation;
|
15
|
+
}
|
16
|
+
|
17
|
+
interface TNodeLocation {
|
18
|
+
DataCenter?: number;
|
19
|
+
Room?: number;
|
20
|
+
Rack?: number;
|
21
|
+
Body?: number;
|
22
|
+
DataCenterId?: string;
|
23
|
+
/** String with DC, Module, Rack and Unit ids */
|
24
|
+
Location?: string;
|
25
|
+
}
|
@@ -0,0 +1,24 @@
|
|
1
|
+
import {FETCH_NODES_LIST} from '../../store/reducers/nodesList';
|
2
|
+
|
3
|
+
import type {ApiRequestAction} from '../../store/utils';
|
4
|
+
import type {IResponseError} from '../api/error';
|
5
|
+
import type {TEvNodesInfo} from '../api/nodesList';
|
6
|
+
|
7
|
+
export interface NodesListState {
|
8
|
+
loading: boolean;
|
9
|
+
wasLoaded: boolean;
|
10
|
+
data?: TEvNodesInfo;
|
11
|
+
error?: IResponseError;
|
12
|
+
}
|
13
|
+
|
14
|
+
export type NodesListAction = ApiRequestAction<
|
15
|
+
typeof FETCH_NODES_LIST,
|
16
|
+
TEvNodesInfo,
|
17
|
+
IResponseError
|
18
|
+
>;
|
19
|
+
|
20
|
+
export type NodesMap = Map<number, string>;
|
21
|
+
|
22
|
+
export interface NodesListRootStateSlice {
|
23
|
+
nodesList: NodesListState;
|
24
|
+
}
|
package/dist/utils/index.js
CHANGED
@@ -20,7 +20,15 @@ export const formatBytes = (bytes) => {
|
|
20
20
|
return numeral(bytes).format('0 b');
|
21
21
|
};
|
22
22
|
|
23
|
-
export const formatBps = (bytes) =>
|
23
|
+
export const formatBps = (bytes) => {
|
24
|
+
const formattedBytes = formatBytes(bytes);
|
25
|
+
|
26
|
+
if (!formattedBytes) {
|
27
|
+
return '';
|
28
|
+
}
|
29
|
+
|
30
|
+
return formattedBytes + '/s';
|
31
|
+
};
|
24
32
|
|
25
33
|
export const formatBytesToGigabyte = (bytes) => {
|
26
34
|
return `${Math.floor(bytes / GIGABYTE)} GB`;
|
package/dist/utils/nodes.ts
CHANGED
@@ -14,3 +14,7 @@ export const NodesUptimeFilterTitles = {
|
|
14
14
|
|
15
15
|
export const isUnavailableNode = (node: INodesPreparedEntity | TSystemStateInfo) =>
|
16
16
|
!node.SystemState || node.SystemState === EFlag.Grey;
|
17
|
+
|
18
|
+
export interface AdditionalNodesInfo extends Record<string, unknown> {
|
19
|
+
getNodeRef?: Function;
|
20
|
+
}
|
package/dist/utils/tooltip.js
CHANGED
@@ -2,6 +2,8 @@ import cn from 'bem-cn-lite';
|
|
2
2
|
import {calcUptime} from '.';
|
3
3
|
import JSONTree from 'react-json-inspector';
|
4
4
|
|
5
|
+
import {NodeEndpointsTooltip} from '../components/Tooltips/NodeEndpointsTooltip/NodeEndpointsTooltip';
|
6
|
+
|
5
7
|
const poolB = cn('pool-tooltip');
|
6
8
|
|
7
9
|
const PoolTooltip = (props) => {
|
@@ -109,34 +111,6 @@ const NodeTooltip = (props) => {
|
|
109
111
|
);
|
110
112
|
};
|
111
113
|
|
112
|
-
const NodeEndpointsTooltip = (props) => {
|
113
|
-
const {data} = props;
|
114
|
-
return (
|
115
|
-
data && (
|
116
|
-
<div className={nodeB()}>
|
117
|
-
<table>
|
118
|
-
<tbody>
|
119
|
-
{data.Rack && (
|
120
|
-
<tr>
|
121
|
-
<td className={nodeB('label')}>Rack</td>
|
122
|
-
<td className={nodeB('value')}>{data.Rack}</td>
|
123
|
-
</tr>
|
124
|
-
)}
|
125
|
-
{data.Endpoints &&
|
126
|
-
data.Endpoints.length &&
|
127
|
-
data.Endpoints.map(({Name, Address}) => (
|
128
|
-
<tr key={Name}>
|
129
|
-
<td className={nodeB('label')}>{Name}</td>
|
130
|
-
<td className={nodeB('value')}>{Address}</td>
|
131
|
-
</tr>
|
132
|
-
))}
|
133
|
-
</tbody>
|
134
|
-
</table>
|
135
|
-
</div>
|
136
|
-
)
|
137
|
-
);
|
138
|
-
};
|
139
|
-
|
140
114
|
const tabletsOverallB = cn('tabletsOverall-tooltip');
|
141
115
|
|
142
116
|
const TabletsOverallTooltip = (props) => {
|
package/package.json
CHANGED
@@ -1,29 +0,0 @@
|
|
1
|
-
import type {TopicStats} from '../../../types/api/topic';
|
2
|
-
import {formatBytes} from '../../../utils';
|
3
|
-
import {
|
4
|
-
parseLag,
|
5
|
-
parseTimestampToIdleTime,
|
6
|
-
formatDurationToShortTimeFormat,
|
7
|
-
} from '../../../utils/timeParsers';
|
8
|
-
import {convertBytesObjectToSpeed} from '../../../utils/bytesParsers';
|
9
|
-
|
10
|
-
import {SpeedMultiMeter} from '../../SpeedMultiMeter';
|
11
|
-
|
12
|
-
import {createInfoFormatter} from '../utils';
|
13
|
-
|
14
|
-
export const formatTopicStats = createInfoFormatter<TopicStats>({
|
15
|
-
values: {
|
16
|
-
store_size_bytes: formatBytes,
|
17
|
-
min_last_write_time: (value) =>
|
18
|
-
formatDurationToShortTimeFormat(parseTimestampToIdleTime(value)),
|
19
|
-
max_write_time_lag: (value) => formatDurationToShortTimeFormat(parseLag(value)),
|
20
|
-
bytes_written: (value) =>
|
21
|
-
value && <SpeedMultiMeter data={convertBytesObjectToSpeed(value)} withValue={false} />,
|
22
|
-
},
|
23
|
-
labels: {
|
24
|
-
store_size_bytes: 'Store size',
|
25
|
-
min_last_write_time: 'Partitions max time since last write',
|
26
|
-
max_write_time_lag: 'Partitions max write time lag',
|
27
|
-
bytes_written: 'Average write speed',
|
28
|
-
},
|
29
|
-
});
|