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.
Files changed (41) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/components/Icon/Icon.tsx +6 -0
  3. package/dist/components/InfoViewer/InfoViewer.tsx +2 -2
  4. package/dist/components/InfoViewer/formatters/index.ts +0 -1
  5. package/dist/components/LabelWithPopover/LabelWithPopover.tsx +3 -7
  6. package/dist/components/LagPopoverContent/LagPopoverContent.scss +13 -0
  7. package/dist/components/LagPopoverContent/LagPopoverContent.tsx +19 -0
  8. package/dist/components/LagPopoverContent/index.ts +1 -0
  9. package/dist/components/Tooltips/NodeEndpointsTooltip/NodeEndpointsTooltip.scss +5 -0
  10. package/dist/components/Tooltips/NodeEndpointsTooltip/NodeEndpointsTooltip.tsx +31 -0
  11. package/dist/containers/Nodes/Nodes.tsx +17 -15
  12. package/dist/containers/Nodes/NodesTable.scss +11 -10
  13. package/dist/containers/Nodes/getNodesColumns.tsx +29 -24
  14. package/dist/containers/Storage/PDisk/PDisk.scss +2 -0
  15. package/dist/containers/Storage/PDiskPopup/PDiskPopup.tsx +6 -9
  16. package/dist/containers/Storage/Storage.js +12 -5
  17. package/dist/containers/Storage/StorageGroups/StorageGroups.tsx +3 -1
  18. package/dist/containers/Storage/StorageNodes/StorageNodes.scss +20 -7
  19. package/dist/containers/Storage/StorageNodes/StorageNodes.tsx +43 -7
  20. package/dist/containers/Storage/VDisk/VDisk.tsx +3 -2
  21. package/dist/containers/Storage/VDiskPopup/VDiskPopup.tsx +4 -2
  22. package/dist/containers/Tenant/Diagnostics/Consumers/Headers/Headers.scss +0 -8
  23. package/dist/containers/Tenant/Diagnostics/Consumers/Headers/Headers.tsx +3 -10
  24. package/dist/containers/Tenant/Diagnostics/Consumers/i18n/ru.json +1 -1
  25. package/dist/containers/Tenant/Diagnostics/Diagnostics.tsx +2 -1
  26. package/dist/containers/Tenant/Diagnostics/DiagnosticsPages.ts +4 -4
  27. package/dist/containers/Tenant/Diagnostics/Overview/TopicStats/TopicStats.tsx +51 -32
  28. package/dist/containers/Tenant/Diagnostics/Overview/TopicStats/i18n/en.json +2 -1
  29. package/dist/containers/Tenant/Diagnostics/Overview/TopicStats/i18n/ru.json +2 -1
  30. package/dist/containers/Tenant/Diagnostics/Partitions/Headers/Headers.scss +0 -8
  31. package/dist/containers/Tenant/Diagnostics/Partitions/Headers/Headers.tsx +7 -21
  32. package/dist/services/api.d.ts +4 -0
  33. package/dist/store/reducers/{nodesList.js → nodesList.ts} +19 -7
  34. package/dist/store/reducers/storage.js +3 -11
  35. package/dist/types/api/nodesList.ts +25 -0
  36. package/dist/types/store/nodesList.ts +24 -0
  37. package/dist/utils/index.js +9 -1
  38. package/dist/utils/nodes.ts +4 -0
  39. package/dist/utils/tooltip.js +2 -28
  40. package/package.json +1 -1
  41. 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: string;
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={data.label + infoIndex}>
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}
@@ -1,6 +1,5 @@
1
1
  export * from './common';
2
2
  export * from './schema';
3
- export * from './topicStats';
4
3
  export * from './pqGroup';
5
4
  export * from './cdcStream';
6
5
  export * from './table';
@@ -3,18 +3,14 @@ import type {ReactNode} from 'react';
3
3
  import {HelpPopover} from '@gravity-ui/uikit';
4
4
 
5
5
  interface LabelWithPopoverProps {
6
- headerText: string;
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
- {headerText}
13
+ {text}
18
14
  <HelpPopover content={popoverContent} />
19
15
  </div>
20
16
  );
@@ -0,0 +1,13 @@
1
+ .ydb-lag-popover-content {
2
+ &__text {
3
+ margin-bottom: 10px;
4
+ }
5
+
6
+ &_type_read {
7
+ max-width: 280px;
8
+ }
9
+
10
+ &_type_write {
11
+ max-width: 220px;
12
+ }
13
+ }
@@ -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,5 @@
1
+ .ydb-node-endpoints-tooltip {
2
+ .info-viewer__value {
3
+ min-width: 70px;
4
+ }
5
+ }
@@ -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
- tenantPath?: string;
50
+ path?: string;
51
+ type?: EPathType;
52
52
  className?: string;
53
- additionalNodesInfo?: IAdditionalNodesInfo;
53
+ additionalNodesInfo?: AdditionalNodesInfo;
54
54
  }
55
55
 
56
- export const Nodes = ({tenantPath, className, additionalNodesInfo = {}}: NodesProps) => {
56
+ export const Nodes = ({path, type, className, additionalNodesInfo = {}}: NodesProps) => {
57
57
  const dispatch = useDispatch();
58
58
 
59
- const isClusterNodes = !tenantPath;
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, tenantPath]);
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
- if (tenantPath && !JSON.parse(useNodesEndpoint)) {
81
- dispatch(getComputeNodes(tenantPath));
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: tenantPath}));
85
+ dispatch(getNodes({tenant: path}));
84
86
  }
85
- }, [dispatch, tenantPath, useNodesEndpoint]);
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-name-wrapper {
2
+ &__host-field-wrapper {
3
3
  display: flex;
4
4
  }
5
5
 
6
- &__external-button {
7
- display: none;
8
- align-items: center;
9
-
10
- margin-left: 4px;
6
+ &__host-wrapper {
7
+ display: flex;
11
8
 
12
- .yc-button__text {
13
- margin: 0 4px;
14
- }
9
+ max-width: 330px;
15
10
  }
16
11
 
17
- &__host-name {
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 {Icon} from '../../components/Icon';
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-name-wrapper')}>
50
- <EntityStatus
51
- name={row.Host}
52
- onNameMouseEnter={(e: MouseEvent) =>
53
- showTooltip(e.target, row, 'nodeEndpoints')
54
- }
55
- onNameMouseLeave={hideTooltip}
56
- status={row.SystemState}
57
- path={getDefaultNodePath(row.NodeId)}
58
- hasClipboardButton
59
- className={b('host-name')}
60
- />
61
- {nodeRef && (
62
- <Button
63
- size="s"
64
- href={nodeRef}
65
- className={b('external-button')}
66
- target="_blank"
67
- >
68
- <Icon name="external" />
69
- </Button>
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
  },
@@ -6,6 +6,8 @@
6
6
  &__content {
7
7
  position: relative;
8
8
 
9
+ display: block;
10
+
9
11
  border-radius: 4px; // to match interactive area with disk shape
10
12
  }
11
13
 
@@ -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 type NodesHosts = {
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[NodeId]) {
39
- pdiskData.push({label: 'Host', value: nodes[NodeId]});
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?: NodesHosts;
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 {flatListStorageEntities, visibleEntities, nodesUptimeFilter, nodes, storageType} =
185
- this.props;
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: getNodesObject(state),
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: any;
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: inline-block;
28
- overflow: hidden;
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 {isUnavailableNode, NodesUptimeFilterValues} from '../../../utils/nodes';
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: ({value}) => {
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={value as string}
93
- placement={['right']}
110
+ content={<NodeEndpointsTooltip data={row} />}
111
+ placement={['top', 'bottom']}
94
112
  behavior={PopoverBehavior.Immediate}
95
113
  >
96
- <span className={b('fqdn')}>{value as string}</span>
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?: NodesHosts;
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 {NodesHosts, preparePDiskData} from '../PDiskPopup';
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?: NodesHosts;
133
+ nodes?: NodesMap;
132
134
  }
133
135
 
134
136
  export const VDiskPopup = ({data, poolName, nodes, ...props}: VDiskPopupProps) => {
@@ -2,12 +2,4 @@
2
2
  &__lags {
3
3
  white-space: nowrap;
4
4
  }
5
-
6
- &__lags-popover-content {
7
- max-width: 300px;
8
-
9
- div:nth-child(1) {
10
- margin-bottom: 10px;
11
- }
12
- }
13
5
  }
@@ -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
- headerText={CONSUMERS_COLUMNS_TITILES[CONSUMERS_COLUMNS_IDS.READ_LAGS]}
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
- tenantPath={tenantNameString}
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 {DescribeTopicResult} from '../../../../../types/api/topic';
3
+ import type {IPreparedTopicStats} from '../../../../../types/store/topic';
5
4
 
6
5
  import {Loader} from '../../../../../components/Loader';
7
- import {InfoViewerItem, formatObject, InfoViewer} from '../../../../../components/InfoViewer';
8
-
9
- import {formatTopicStats} from '../../../../../components/InfoViewer/formatters';
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 {convertBytesObjectToSpeed} from '../../../../../utils/bytesParsers';
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: DescribeTopicResult): Array<InfoViewerItem> => {
24
+ const prepareTopicInfo = (data: IPreparedTopicStats): Array<InfoViewerItem> => {
22
25
  return [
23
- ...formatObject(formatTopicStats, {
24
- ...data.topic_stats,
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: DescribeTopicResult): Array<InfoViewerItem> => {
30
- const preparedBytes = convertBytesObjectToSpeed(data?.topic_stats?.bytes_written);
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(preparedBytes.perMinute),
62
+ value: formatBps(writeSpeed.perMinute),
36
63
  },
37
64
  {
38
65
  label: 'per hour',
39
- value: formatBps(preparedBytes.perHour),
66
+ value: formatBps(writeSpeed.perHour),
40
67
  },
41
68
  {
42
69
  label: 'per day',
43
- value: formatBps(preparedBytes.perDay),
70
+ value: formatBps(writeSpeed.perDay),
44
71
  },
45
72
  ];
46
73
  };
47
74
 
48
75
  export const TopicStats = () => {
49
- const {data, error, loading, wasLoaded} = useTypedSelector((state) => state.topic);
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
- // There are several backed versions with different behaviour
60
- // Possible returns on older versions:
61
- // 1. Error when trying to access endpoint
62
- // 2. No data
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
- <div className="error">{i18n('notSupportedVersion')}</div>
95
+ <ResponseError error={error} />
77
96
  </div>
78
97
  );
79
98
  }
@@ -1,3 +1,4 @@
1
1
  {
2
- "notSupportedVersion": "Topic stats are not supported in the current cluster version. Update cluster version to see topic stats"
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
- "notSupportedVersion": "Статистика топиков не поддерживается в текущей версии. Обновите кластер до более новой версии, чтобы увидеть статистику топиков"
2
+ "writeLagPopover": "Лаг записи, максимальное значение среди всех партиций топика",
3
+ "writeIdleTimePopover": "Время без записи, максимальное значение среди всех партиций топика"
3
4
  }
@@ -19,14 +19,6 @@
19
19
  white-space: normal;
20
20
  }
21
21
 
22
- &__lags-popover-content {
23
- max-width: 300px;
24
-
25
- div:nth-child(1) {
26
- margin-bottom: 10px;
27
- }
28
- }
29
-
30
22
  &__messages-popover-content {
31
23
  max-width: 200px;
32
24
  }
@@ -1,7 +1,7 @@
1
1
  import block from 'bem-cn-lite';
2
2
 
3
3
  import {LabelWithPopover} from '../../../../../components/LabelWithPopover';
4
- import {WriteLagImage, ReadLagImage} from '../../../../../components/LagImages';
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
- headerText={PARTITIONS_COLUMNS_TITILES[PARTITIONS_COLUMNS_IDS.READ_LAGS]}
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
- headerText={PARTITIONS_COLUMNS_TITILES[PARTITIONS_COLUMNS_IDS.WRITE_LAGS]}
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
- headerText={PARTITIONS_COLUMNS_TITILES[PARTITIONS_COLUMNS_IDS.UNREAD_MESSAGES]}
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
- headerText={PARTITIONS_COLUMNS_TITILES[PARTITIONS_COLUMNS_IDS.UNCOMMITED_MESSAGES]}
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
  }
@@ -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('tenants', 'FETCH_NODES_LIST');
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 = function (state = initialState, action) {
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.getNodeInfo(),
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.FQDN.toLowerCase().includes(cleanedFilter)
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
+ }
@@ -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) => formatBytes(bytes) + '/s';
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`;
@@ -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
+ }
@@ -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,6 +1,6 @@
1
1
  {
2
2
  "name": "ydb-embedded-ui",
3
- "version": "3.4.3",
3
+ "version": "3.4.5",
4
4
  "files": [
5
5
  "dist"
6
6
  ],
@@ -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
- });