ydb-embedded-ui 3.4.4 → 3.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. package/CHANGELOG.md +31 -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/InfoViewer/formatters/table.ts +6 -0
  6. package/dist/components/LabelWithPopover/LabelWithPopover.tsx +3 -7
  7. package/dist/components/LagPopoverContent/LagPopoverContent.scss +13 -0
  8. package/dist/components/LagPopoverContent/LagPopoverContent.tsx +19 -0
  9. package/dist/components/LagPopoverContent/index.ts +1 -0
  10. package/dist/components/Tooltips/NodeEndpointsTooltip/NodeEndpointsTooltip.scss +5 -0
  11. package/dist/components/Tooltips/NodeEndpointsTooltip/NodeEndpointsTooltip.tsx +31 -0
  12. package/dist/components/TruncatedQuery/TruncatedQuery.js +1 -1
  13. package/dist/components/TruncatedQuery/TruncatedQuery.scss +7 -3
  14. package/dist/containers/Node/{NodePages.js → NodePages.ts} +1 -1
  15. package/dist/containers/Nodes/Nodes.tsx +2 -6
  16. package/dist/containers/Nodes/NodesTable.scss +11 -10
  17. package/dist/containers/Nodes/getNodesColumns.tsx +29 -24
  18. package/dist/containers/Storage/PDisk/PDisk.scss +2 -0
  19. package/dist/containers/Storage/PDiskPopup/PDiskPopup.tsx +6 -9
  20. package/dist/containers/Storage/Storage.js +12 -5
  21. package/dist/containers/Storage/StorageGroups/StorageGroups.tsx +3 -1
  22. package/dist/containers/Storage/StorageNodes/StorageNodes.scss +20 -7
  23. package/dist/containers/Storage/StorageNodes/StorageNodes.tsx +43 -7
  24. package/dist/containers/Storage/VDisk/VDisk.tsx +3 -2
  25. package/dist/containers/Storage/VDiskPopup/VDiskPopup.tsx +4 -2
  26. package/dist/containers/Tablet/TabletControls/TabletControls.tsx +2 -2
  27. package/dist/containers/Tenant/Diagnostics/Consumers/Headers/Headers.scss +0 -8
  28. package/dist/containers/Tenant/Diagnostics/Consumers/Headers/Headers.tsx +3 -10
  29. package/dist/containers/Tenant/Diagnostics/Consumers/i18n/ru.json +1 -1
  30. package/dist/containers/Tenant/Diagnostics/Overview/Overview.tsx +11 -43
  31. package/dist/containers/Tenant/Diagnostics/Overview/TableInfo/TableInfo.tsx +19 -17
  32. package/dist/containers/Tenant/Diagnostics/Overview/TableInfo/prepareTableInfo.ts +192 -37
  33. package/dist/containers/Tenant/Diagnostics/Overview/TopicStats/TopicStats.tsx +51 -32
  34. package/dist/containers/Tenant/Diagnostics/Overview/TopicStats/i18n/en.json +2 -1
  35. package/dist/containers/Tenant/Diagnostics/Overview/TopicStats/i18n/ru.json +2 -1
  36. package/dist/containers/Tenant/Diagnostics/Partitions/Headers/Headers.scss +0 -8
  37. package/dist/containers/Tenant/Diagnostics/Partitions/Headers/Headers.tsx +7 -21
  38. package/dist/containers/Tenant/Diagnostics/TopQueries/TopQueries.scss +20 -14
  39. package/dist/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx +49 -12
  40. package/dist/containers/Tenant/Diagnostics/TopShards/TopShards.tsx +37 -18
  41. package/dist/containers/Tenant/QueryEditor/QueryEditor.js +1 -0
  42. package/dist/routes.ts +1 -1
  43. package/dist/services/api.d.ts +4 -0
  44. package/dist/services/api.js +3 -3
  45. package/dist/store/reducers/{executeQuery.js → executeQuery.ts} +51 -21
  46. package/dist/store/reducers/executeTopQueries.ts +5 -1
  47. package/dist/store/reducers/{nodesList.js → nodesList.ts} +19 -7
  48. package/dist/store/reducers/{olapStats.js → olapStats.ts} +8 -18
  49. package/dist/store/reducers/settings.js +1 -1
  50. package/dist/store/reducers/storage.js +8 -18
  51. package/dist/types/api/nodesList.ts +25 -0
  52. package/dist/types/api/query.ts +4 -1
  53. package/dist/types/api/schema.ts +523 -3
  54. package/dist/types/common.ts +1 -0
  55. package/dist/types/store/executeQuery.ts +42 -0
  56. package/dist/types/store/nodesList.ts +24 -0
  57. package/dist/types/store/olapStats.ts +14 -0
  58. package/dist/utils/index.js +9 -1
  59. package/dist/utils/nodes.ts +4 -0
  60. package/dist/utils/query.test.ts +42 -29
  61. package/dist/utils/query.ts +34 -22
  62. package/dist/utils/timeParsers/formatDuration.ts +30 -12
  63. package/dist/utils/timeParsers/i18n/en.json +4 -0
  64. package/dist/utils/timeParsers/i18n/ru.json +4 -0
  65. package/dist/utils/tooltip.js +2 -28
  66. package/package.json +1 -1
  67. package/dist/components/InfoViewer/formatters/topicStats.tsx +0 -29
package/CHANGELOG.md CHANGED
@@ -1,5 +1,36 @@
1
1
  # Changelog
2
2
 
3
+ ## [3.5.0](https://github.com/ydb-platform/ydb-embedded-ui/compare/v3.4.5...v3.5.0) (2023-04-18)
4
+
5
+
6
+ ### Features
7
+
8
+ * **TableInfo:** extend Table and ColumnTable info ([89e54aa](https://github.com/ydb-platform/ydb-embedded-ui/commit/89e54aa97d7bcbabfd5100daeb1dc0c03608e86e))
9
+ * **TopQueries:** add columns ([b49b98d](https://github.com/ydb-platform/ydb-embedded-ui/commit/b49b98db2da08c355b23f4a33bf05247530543db))
10
+
11
+
12
+ ### Bug Fixes
13
+
14
+ * **settings:** use system theme by default ([726c9cb](https://github.com/ydb-platform/ydb-embedded-ui/commit/726c9cb14d7f87cc9248340d1ebebfc8bf0d0384))
15
+ * **Storage:** fix incorrect usage on zero available space ([2704cd7](https://github.com/ydb-platform/ydb-embedded-ui/commit/2704cd7c696d337cc8e3af68941cf444f8dfae81))
16
+ * **TableInfo:** add default format for FollowerGroup fields ([961334a](https://github.com/ydb-platform/ydb-embedded-ui/commit/961334aabe89672994f0f3440e20602e180b3394))
17
+ * **Tablet:** fix dialog type enum ([c477042](https://github.com/ydb-platform/ydb-embedded-ui/commit/c477042cacc2e777cae4bd6981381a8042c603ed))
18
+ * **TopQueries:** enable go back to TopQueries from Query tab ([bbdfe72](https://github.com/ydb-platform/ydb-embedded-ui/commit/bbdfe726c9081f01422dca787b83399ea44b3956))
19
+ * **TopShards:** fix table crash on undefined values ([604e99a](https://github.com/ydb-platform/ydb-embedded-ui/commit/604e99a9427021c61ceb8ea366e316e629032b84))
20
+ * **TruncatedQuery:** wrap message ([f41b7ff](https://github.com/ydb-platform/ydb-embedded-ui/commit/f41b7ff33ac0145446ca89aab031036247f3ddf8))
21
+
22
+ ## [3.4.5](https://github.com/ydb-platform/ydb-embedded-ui/compare/v3.4.4...v3.4.5) (2023-03-30)
23
+
24
+
25
+ ### Bug Fixes
26
+
27
+ * **Consumers:** fix typo ([aaa9dbd](https://github.com/ydb-platform/ydb-embedded-ui/commit/aaa9dbda1f28702917793a61bae2813f6ef018bb))
28
+ * **PDisk:** add display block to content ([130dab2](https://github.com/ydb-platform/ydb-embedded-ui/commit/130dab20ffdc9da77225c94a6e6064f0308a1c2a))
29
+ * **Storage:** get nodes hosts from /nodelist ([cc82dd9](https://github.com/ydb-platform/ydb-embedded-ui/commit/cc82dd93808133b0d1dcd21b31ee3744df4f7383))
30
+ * **StorageNodes:** make fqdn similar to nodes page ([344298a](https://github.com/ydb-platform/ydb-embedded-ui/commit/344298a9a29380f1068b002fa304cdcc221ce0d4))
31
+ * **TopicInfo:** do not display /s when speed is undefined ([2d41832](https://github.com/ydb-platform/ydb-embedded-ui/commit/2d4183247ec33acdfa45be72a93f0dbd93b716e0))
32
+ * **TopicStats:** use prepared stats, update fields ([a614a8c](https://github.com/ydb-platform/ydb-embedded-ui/commit/a614a8caa2744b844d97f23f25e5385387367d6b))
33
+
3
34
  ## [3.4.4](https://github.com/ydb-platform/ydb-embedded-ui/compare/v3.4.3...v3.4.4) (2023-03-22)
4
35
 
5
36
 
@@ -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';
@@ -20,6 +20,12 @@ export const formatFollowerGroupItem = createInfoFormatter<TFollowerGroup>({
20
20
  values: {
21
21
  FollowerCount: formatNumber,
22
22
  },
23
+ labels: {
24
+ // Make it shorter to fit label width
25
+ FollowerCountPerDataCenter: 'FollowerCountPerDC',
26
+ },
27
+ // Most of the FollowerGroup fields are arrays or boolean
28
+ defaultValueFormatter: (value) => value && String(value),
23
29
  });
24
30
 
25
31
  export const formatPartitionConfigItem = createInfoFormatter<TPartitionConfig>({
@@ -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
+ };
@@ -16,7 +16,7 @@ function TruncatedQuery({value, maxQueryHeight}) {
16
16
  return (
17
17
  <React.Fragment>
18
18
  <span className={b()}>{content}</span>
19
- <span className={b({color: 'secondary'})}>{message}</span>
19
+ <span className={b('message', {color: 'secondary'})}>{message}</span>
20
20
  </React.Fragment>
21
21
  );
22
22
  }
@@ -5,9 +5,13 @@
5
5
  white-space: pre;
6
6
  word-break: break-word;
7
7
 
8
- &_color {
9
- &_secondary {
10
- color: var(--yc-color-text-secondary);
8
+ &__message {
9
+ white-space: pre-wrap;
10
+
11
+ &_color {
12
+ &_secondary {
13
+ color: var(--yc-color-text-secondary);
14
+ }
11
15
  }
12
16
  }
13
17
  }
@@ -21,7 +21,7 @@ export const NODE_PAGES = [
21
21
  },
22
22
  ];
23
23
 
24
- export function getDefaultNodePath(nodeId) {
24
+ export function getDefaultNodePath(nodeId: string | number) {
25
25
  return createHref(routes.node, {
26
26
  id: nodeId,
27
27
  activeTab: OVERVIEW,
@@ -22,7 +22,7 @@ import {
22
22
  USE_NODES_ENDPOINT_IN_DIAGNOSTICS_KEY,
23
23
  } from '../../utils/constants';
24
24
  import {useAutofetcher, useTypedSelector} from '../../utils/hooks';
25
- import {isUnavailableNode, NodesUptimeFilterValues} from '../../utils/nodes';
25
+ import {AdditionalNodesInfo, isUnavailableNode, NodesUptimeFilterValues} from '../../utils/nodes';
26
26
 
27
27
  import {setHeader} from '../../store/reducers/header';
28
28
  import {
@@ -46,15 +46,11 @@ import i18n from './i18n';
46
46
 
47
47
  const b = cn('ydb-nodes');
48
48
 
49
- interface IAdditionalNodesInfo extends Record<string, unknown> {
50
- getNodeRef?: Function;
51
- }
52
-
53
49
  interface NodesProps {
54
50
  path?: string;
55
51
  type?: EPathType;
56
52
  className?: string;
57
- additionalNodesInfo?: IAdditionalNodesInfo;
53
+ additionalNodesInfo?: AdditionalNodesInfo;
58
54
  }
59
55
 
60
56
  export const Nodes = ({path, type, className, additionalNodesInfo = {}}: NodesProps) => {
@@ -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