ydb-embedded-ui 3.2.3 → 3.3.0

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 (116) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/components/InfoViewer/InfoViewer.scss +10 -0
  3. package/dist/components/InfoViewer/InfoViewer.tsx +12 -2
  4. package/dist/components/InfoViewer/formatters/cdcStream.ts +10 -0
  5. package/dist/components/InfoViewer/formatters/index.ts +3 -0
  6. package/dist/components/InfoViewer/formatters/pqGroup.ts +51 -0
  7. package/dist/components/InfoViewer/formatters/schema.ts +1 -29
  8. package/dist/components/InfoViewer/formatters/topicStats.tsx +50 -0
  9. package/dist/components/InfoViewer/schemaInfo/index.ts +0 -2
  10. package/dist/components/InfoViewer/utils.ts +15 -0
  11. package/dist/components/InternalLink/InternalLink.tsx +17 -0
  12. package/dist/components/InternalLink/index.ts +1 -0
  13. package/dist/components/Tablet/Tablet.js +1 -1
  14. package/dist/components/TabletsStatistic/TabletsStatistic.tsx +1 -1
  15. package/dist/components/VerticalBars/VerticalBars.scss +15 -0
  16. package/dist/components/VerticalBars/VerticalBars.tsx +38 -0
  17. package/dist/components/VerticalBars/index.ts +1 -0
  18. package/dist/containers/App/App.js +0 -11
  19. package/dist/containers/App/App.scss +0 -1
  20. package/dist/containers/Cluster/Cluster.tsx +2 -2
  21. package/dist/containers/Nodes/Nodes.scss +5 -1
  22. package/dist/containers/Nodes/Nodes.tsx +196 -0
  23. package/dist/containers/{App → Nodes}/NodesTable.scss +2 -2
  24. package/dist/{utils/getNodesColumns.js → containers/Nodes/getNodesColumns.tsx} +60 -35
  25. package/dist/containers/Nodes/i18n/en.json +3 -0
  26. package/dist/containers/Nodes/i18n/index.ts +11 -0
  27. package/dist/containers/Nodes/i18n/ru.json +3 -0
  28. package/dist/containers/Nodes/index.ts +1 -0
  29. package/dist/containers/Storage/DiskStateProgressBar/DiskStateProgressBar.scss +14 -20
  30. package/dist/containers/Storage/DiskStateProgressBar/DiskStateProgressBar.tsx +32 -16
  31. package/dist/containers/Storage/DiskStateProgressBar/index.ts +1 -0
  32. package/dist/containers/Storage/{Pdisk/Pdisk.scss → PDisk/PDisk.scss} +15 -4
  33. package/dist/containers/Storage/PDisk/PDisk.tsx +145 -0
  34. package/dist/containers/Storage/PDisk/__tests__/colors.tsx +37 -0
  35. package/dist/containers/Storage/PDisk/index.ts +1 -0
  36. package/dist/containers/Storage/PDiskPopup/PDiskPopup.scss +3 -0
  37. package/dist/containers/Storage/PDiskPopup/PDiskPopup.tsx +82 -0
  38. package/dist/containers/Storage/PDiskPopup/index.ts +1 -0
  39. package/dist/containers/Storage/Storage.js +1 -1
  40. package/dist/containers/Storage/StorageGroups/StorageGroups.tsx +10 -10
  41. package/dist/containers/Storage/StorageNodes/StorageNodes.scss +1 -0
  42. package/dist/containers/Storage/StorageNodes/StorageNodes.tsx +7 -4
  43. package/dist/containers/Storage/VDisk/VDisk.scss +7 -0
  44. package/dist/containers/Storage/VDisk/VDisk.tsx +148 -0
  45. package/dist/containers/Storage/VDisk/__tests__/colors.tsx +209 -0
  46. package/dist/containers/Storage/VDisk/index.ts +1 -0
  47. package/dist/containers/Storage/VDiskPopup/VDiskPopup.scss +14 -0
  48. package/dist/containers/Storage/VDiskPopup/VDiskPopup.tsx +134 -0
  49. package/dist/containers/Storage/VDiskPopup/index.ts +1 -0
  50. package/dist/containers/Storage/utils/constants.ts +2 -9
  51. package/dist/containers/TabletsFilters/TabletsFilters.js +10 -6
  52. package/dist/containers/Tenant/Diagnostics/Diagnostics.scss +2 -2
  53. package/dist/containers/Tenant/Diagnostics/Diagnostics.tsx +3 -4
  54. package/dist/containers/Tenant/Diagnostics/OverloadedShards/OverloadedShards.tsx +1 -1
  55. package/dist/containers/Tenant/Diagnostics/Overview/ChangefeedInfo/ChangefeedInfo.tsx +69 -0
  56. package/dist/containers/Tenant/Diagnostics/Overview/ChangefeedInfo/index.ts +1 -0
  57. package/dist/containers/Tenant/Diagnostics/Overview/Overview.tsx +18 -16
  58. package/dist/containers/Tenant/Diagnostics/Overview/TopicInfo/TopicInfo.tsx +37 -0
  59. package/dist/containers/Tenant/Diagnostics/Overview/TopicInfo/index.ts +1 -0
  60. package/dist/containers/Tenant/Diagnostics/Overview/TopicStats/TopicStats.scss +30 -0
  61. package/dist/containers/Tenant/Diagnostics/Overview/TopicStats/TopicStats.tsx +94 -0
  62. package/dist/containers/Tenant/Diagnostics/Overview/TopicStats/i18n/en.json +3 -0
  63. package/dist/containers/Tenant/Diagnostics/Overview/TopicStats/i18n/index.ts +11 -0
  64. package/dist/containers/Tenant/Diagnostics/Overview/TopicStats/i18n/ru.json +3 -0
  65. package/dist/containers/Tenant/Diagnostics/Overview/TopicStats/index.ts +1 -0
  66. package/dist/containers/Tenant/Diagnostics/Overview/utils/index.ts +1 -0
  67. package/dist/containers/Tenant/Diagnostics/Overview/utils/prepareTopicSchemaInfo.ts +42 -0
  68. package/dist/containers/Tenant/utils/schema.ts +19 -0
  69. package/dist/containers/UserSettings/UserSettings.tsx +18 -10
  70. package/dist/services/api.d.ts +8 -1
  71. package/dist/services/api.js +27 -8
  72. package/dist/store/reducers/index.ts +3 -1
  73. package/dist/store/reducers/nodes.ts +148 -14
  74. package/dist/store/reducers/{clusterNodes.js → nodesList.js} +0 -41
  75. package/dist/store/reducers/settings.js +10 -4
  76. package/dist/store/reducers/storage.js +24 -13
  77. package/dist/store/reducers/tenant.js +5 -4
  78. package/dist/store/reducers/tooltip.ts +1 -1
  79. package/dist/store/reducers/topic.ts +52 -0
  80. package/dist/styles/mixins.scss +19 -11
  81. package/dist/types/api/common.ts +5 -0
  82. package/dist/types/api/compute.ts +1 -1
  83. package/dist/types/api/consumer.ts +12 -10
  84. package/dist/types/api/nodes.ts +2 -0
  85. package/dist/types/api/pdisk.ts +1 -0
  86. package/dist/types/api/schema.ts +3 -3
  87. package/dist/types/api/topic.ts +5 -4
  88. package/dist/types/api/vdisk.ts +2 -1
  89. package/dist/types/store/nodes.ts +56 -6
  90. package/dist/types/store/tooltip.ts +1 -1
  91. package/dist/types/store/topic.ts +21 -0
  92. package/dist/utils/constants.ts +5 -1
  93. package/dist/utils/i18n/i18n.ts +10 -2
  94. package/dist/utils/index.js +1 -1
  95. package/dist/utils/timeParsers/__test__/formatDuration.test.ts +50 -0
  96. package/dist/utils/timeParsers/__test__/protobuf.test.ts +74 -0
  97. package/dist/utils/timeParsers/formatDuration.ts +46 -0
  98. package/dist/utils/timeParsers/i18n/en.json +7 -0
  99. package/dist/utils/timeParsers/i18n/index.ts +11 -0
  100. package/dist/utils/timeParsers/i18n/ru.json +7 -0
  101. package/dist/utils/timeParsers/index.ts +2 -0
  102. package/dist/utils/timeParsers/protobuf.ts +36 -0
  103. package/package.json +1 -1
  104. package/dist/components/InfoViewer/schemaInfo/CDCStreamInfo.tsx +0 -48
  105. package/dist/components/InfoViewer/schemaInfo/PersQueueGroupInfo.tsx +0 -30
  106. package/dist/components/InternalLink/InternalLink.js +0 -23
  107. package/dist/containers/Nodes/Nodes.js +0 -214
  108. package/dist/containers/NodesViewer/NodesViewer.js +0 -163
  109. package/dist/containers/NodesViewer/NodesViewer.scss +0 -66
  110. package/dist/containers/Storage/Pdisk/Pdisk.tsx +0 -153
  111. package/dist/containers/Storage/Pdisk/__tests__/colors.tsx +0 -41
  112. package/dist/containers/Storage/Vdisk/Vdisk.js +0 -275
  113. package/dist/containers/Storage/Vdisk/Vdisk.scss +0 -22
  114. package/dist/containers/Storage/Vdisk/__tests__/colors.tsx +0 -163
  115. package/dist/containers/Tenant/Diagnostics/Compute/Compute.js +0 -139
  116. package/dist/containers/Tenant/Diagnostics/Compute/Compute.scss +0 -14
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## [3.3.0](https://github.com/ydb-platform/ydb-embedded-ui/compare/v3.2.3...v3.3.0) (2023-01-30)
4
+
5
+
6
+ ### Features
7
+
8
+ * **Nodes:** use /viewer/json/nodes endpoint ([226cc70](https://github.com/ydb-platform/ydb-embedded-ui/commit/226cc70dcb89262890856b4d0cb03eac0675256d))
9
+ * **Overview:** display topic stats for topics and streams ([08e9fe0](https://github.com/ydb-platform/ydb-embedded-ui/commit/08e9fe0ee379715229474322a03ec668e26bdb9b))
10
+ * **Storage:** display vdisks over pdisks ([bb5d1fa](https://github.com/ydb-platform/ydb-embedded-ui/commit/bb5d1fa5ae2953ca30b13df45340b7a1a63056cb))
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * add duration formatter ([e325d98](https://github.com/ydb-platform/ydb-embedded-ui/commit/e325d98845d29dea208debdfcb88d330c1d6daee))
16
+ * add protobuf time formatters ([c74cd9d](https://github.com/ydb-platform/ydb-embedded-ui/commit/c74cd9d0949674414ba2c9754e3dcc5c2be622a5))
17
+ * add verticalBars component ([053ffa8](https://github.com/ydb-platform/ydb-embedded-ui/commit/053ffa8fd460f89f4296a96fcf46a9267ac4cae3))
18
+ * **PDisk:** grey color for unknown state ([54f7e15](https://github.com/ydb-platform/ydb-embedded-ui/commit/54f7e159aaddd932ccecddfb10265ee596fed1e2))
19
+ * **Storage:** request only static nodes ([e91e136](https://github.com/ydb-platform/ydb-embedded-ui/commit/e91e136d7c72bea694c7a282c83d577cc60e5386))
20
+ * **Topic:** add reducer for describe_topic ([7c61dc9](https://github.com/ydb-platform/ydb-embedded-ui/commit/7c61dc906452df2e1a77a2ff602916f6ea785df5))
21
+ * update PDisks and VDisks tests ([3bf660e](https://github.com/ydb-platform/ydb-embedded-ui/commit/3bf660e41d92e1a32444872c5fb9d47209bef8b5))
22
+
3
23
  ## [3.2.3](https://github.com/ydb-platform/ydb-embedded-ui/compare/v3.2.2...v3.2.3) (2023-01-16)
4
24
 
5
25
 
@@ -42,6 +42,16 @@
42
42
  color: var(--yc-color-text-secondary);
43
43
  }
44
44
 
45
+ &__label-text {
46
+ &_multiline {
47
+ overflow: visible;
48
+
49
+ max-width: 180px;
50
+
51
+ white-space: normal;
52
+ }
53
+ }
54
+
45
55
  &__dots {
46
56
  display: flex;
47
57
  flex: 1 1 auto;
@@ -14,11 +14,19 @@ interface InfoViewerProps {
14
14
  dots?: boolean;
15
15
  size?: 's';
16
16
  className?: string;
17
+ multilineLabels?: boolean;
17
18
  }
18
19
 
19
20
  const b = cn('info-viewer');
20
21
 
21
- const InfoViewer = ({title, info, dots = true, size, className}: InfoViewerProps) => (
22
+ const InfoViewer = ({
23
+ title,
24
+ info,
25
+ dots = true,
26
+ size,
27
+ className,
28
+ multilineLabels,
29
+ }: InfoViewerProps) => (
22
30
  <div className={b({size}, className)}>
23
31
  {title && <div className={b('title')}>{title}</div>}
24
32
  {info && info.length > 0 ? (
@@ -26,7 +34,9 @@ const InfoViewer = ({title, info, dots = true, size, className}: InfoViewerProps
26
34
  {info.map((data, infoIndex) => (
27
35
  <div className={b('row')} key={data.label + infoIndex}>
28
36
  <div className={b('label')}>
29
- {data.label}
37
+ <div className={b('label-text', {multiline: multilineLabels})}>
38
+ {data.label}
39
+ </div>
30
40
  {dots && <div className={b('dots')} />}
31
41
  </div>
32
42
 
@@ -0,0 +1,10 @@
1
+ import {TCdcStreamDescription} from '../../../types/api/schema';
2
+
3
+ import {createInfoFormatter} from '../utils';
4
+
5
+ export const formatCdcStreamItem = createInfoFormatter<TCdcStreamDescription>({
6
+ values: {
7
+ Mode: (value) => value?.substring('ECdcStreamMode'.length),
8
+ Format: (value) => value?.substring('ECdcStreamFormat'.length),
9
+ },
10
+ });
@@ -1,2 +1,5 @@
1
1
  export * from './common';
2
2
  export * from './schema';
3
+ export * from './topicStats';
4
+ export * from './pqGroup';
5
+ export * from './cdcStream';
@@ -0,0 +1,51 @@
1
+ import {
2
+ EMeteringMode,
3
+ TPersQueueGroupDescription,
4
+ TPQPartitionConfig,
5
+ TPQTabletConfig,
6
+ } from '../../../types/api/schema';
7
+ import {formatBps, formatBytes, formatNumber} from '../../../utils';
8
+ import {HOUR_IN_SECONDS} from '../../../utils/constants';
9
+
10
+ import {createInfoFormatter} from '../utils';
11
+
12
+ const EMeteringModeToNames: Record<EMeteringMode, string> = {
13
+ [EMeteringMode.METERING_MODE_REQUEST_UNITS]: 'request-units',
14
+ [EMeteringMode.METERING_MODE_RESERVED_CAPACITY]: 'reserved-capacity',
15
+ };
16
+
17
+ export const formatPQGroupItem = createInfoFormatter<TPersQueueGroupDescription>({
18
+ values: {
19
+ Partitions: (value) => formatNumber(value?.length || 0),
20
+ PQTabletConfig: (value) => {
21
+ const hours =
22
+ Math.round((value.PartitionConfig.LifetimeSeconds / HOUR_IN_SECONDS) * 100) / 100;
23
+ return `${formatNumber(hours)} hours`;
24
+ },
25
+ },
26
+ labels: {
27
+ Partitions: 'Partitions count',
28
+ PQTabletConfig: 'Retention',
29
+ },
30
+ });
31
+
32
+ export const formatPQTabletConfig = createInfoFormatter<TPQTabletConfig>({
33
+ values: {
34
+ Codecs: (value) => value && Object.values(value.Codecs || {}).join(', '),
35
+ MeteringMode: (value) => value && EMeteringModeToNames[value],
36
+ },
37
+ labels: {
38
+ MeteringMode: 'Metering mode',
39
+ },
40
+ });
41
+
42
+ export const formatPQPartitionConfig = createInfoFormatter<TPQPartitionConfig>({
43
+ values: {
44
+ StorageLimitBytes: formatBytes,
45
+ WriteSpeedInBytesPerSecond: formatBps,
46
+ },
47
+ labels: {
48
+ StorageLimitBytes: 'Retention storage',
49
+ WriteSpeedInBytesPerSecond: 'Partitions write speed',
50
+ },
51
+ });
@@ -1,10 +1,4 @@
1
- import type {
2
- TCdcStreamDescription,
3
- TIndexDescription,
4
- TPersQueueGroupDescription,
5
- } from '../../../types/api/schema';
6
- import {formatNumber} from '../../../utils';
7
- import {HOUR_IN_SECONDS} from '../../../utils/constants';
1
+ import {TIndexDescription} from '../../../types/api/schema';
8
2
 
9
3
  import {createInfoFormatter} from '../utils';
10
4
 
@@ -20,25 +14,3 @@ export const formatTableIndexItem = createInfoFormatter<TIndexDescription>({
20
14
  DataColumnNames: 'Includes',
21
15
  },
22
16
  });
23
-
24
- export const formatCdcStreamItem = createInfoFormatter<TCdcStreamDescription>({
25
- values: {
26
- Mode: (value) => value?.substring('ECdcStreamMode'.length),
27
- Format: (value) => value?.substring('ECdcStreamFormat'.length),
28
- },
29
- });
30
-
31
- export const formatPQGroupItem = createInfoFormatter<TPersQueueGroupDescription>({
32
- values: {
33
- Partitions: (value) => formatNumber(value?.length || 0),
34
- PQTabletConfig: (value) => {
35
- const hours =
36
- Math.round((value.PartitionConfig.LifetimeSeconds / HOUR_IN_SECONDS) * 100) / 100;
37
- return `${formatNumber(hours)} hours`;
38
- },
39
- },
40
- labels: {
41
- Partitions: 'Partitions count',
42
- PQTabletConfig: 'Retention',
43
- },
44
- });
@@ -0,0 +1,50 @@
1
+ import type {MultipleWindowsStat} from '../../../types/api/consumer';
2
+ import type {TopicStats} from '../../../types/api/topic';
3
+ import {formatBytes} from '../../../utils';
4
+ import {DAY_IN_SECONDS, HOUR_IN_SECONDS, MINUTE_IN_SECONDS} from '../../../utils/constants';
5
+
6
+ import {
7
+ parseProtobufTimestampToMs,
8
+ parseProtobufDurationToMs,
9
+ formatDurationToShortTimeFormat,
10
+ } from '../../../utils/timeParsers';
11
+
12
+ import {VerticalBars} from '../../VerticalBars/VerticalBars';
13
+
14
+ import {createInfoFormatter} from '../utils';
15
+
16
+ export const prepareBytesWritten = (data?: MultipleWindowsStat) => {
17
+ return {
18
+ per_minute:
19
+ data && data.per_minute ? Math.floor(Number(data.per_minute) / MINUTE_IN_SECONDS) : 0,
20
+ per_hour: data && data.per_hour ? Math.floor(Number(data.per_hour) / HOUR_IN_SECONDS) : 0,
21
+ per_day: data && data.per_day ? Math.floor(Number(data.per_day) / DAY_IN_SECONDS) : 0,
22
+ };
23
+ };
24
+
25
+ export const formatTopicStats = createInfoFormatter<TopicStats>({
26
+ values: {
27
+ store_size_bytes: formatBytes,
28
+ min_last_write_time: (value) => {
29
+ if (!value) {
30
+ return formatDurationToShortTimeFormat(0);
31
+ }
32
+
33
+ const durationMs = Date.now() - parseProtobufTimestampToMs(value);
34
+
35
+ // Duration could be negative because of the difference between server and local time
36
+ // Usually it below 100ms, so it could be omitted
37
+ return formatDurationToShortTimeFormat(durationMs < 0 ? 0 : durationMs);
38
+ },
39
+ max_write_time_lag: (value) =>
40
+ formatDurationToShortTimeFormat(value ? parseProtobufDurationToMs(value) : 0),
41
+ bytes_written: (value) =>
42
+ value && <VerticalBars values={Object.values(prepareBytesWritten(value))} />,
43
+ },
44
+ labels: {
45
+ store_size_bytes: 'Store size',
46
+ min_last_write_time: 'Partitions max time since last write',
47
+ max_write_time_lag: 'Partitions max write time lag',
48
+ bytes_written: 'Average write speed',
49
+ },
50
+ });
@@ -1,3 +1 @@
1
- export * from './CDCStreamInfo';
2
1
  export * from './TableIndexInfo';
3
- export * from './PersQueueGroupInfo';
@@ -1,5 +1,7 @@
1
1
  import type {ReactNode} from 'react';
2
2
 
3
+ import {InfoViewerItem} from './InfoViewer';
4
+
3
5
  type LabelMap<T> = {
4
6
  [label in keyof T]?: string;
5
7
  };
@@ -40,3 +42,16 @@ export function createInfoFormatter<Shape extends Record<string, any>>({
40
42
  value: formatValue(label, value, valueFormatters || {}, defaultValueFormatter),
41
43
  });
42
44
  }
45
+
46
+ export const formatObject = <Shape extends Record<string, any>>(
47
+ formatter: <Key extends keyof Shape>(value: Key, label: Shape[Key]) => InfoViewerItem,
48
+ obj?: Partial<Shape>,
49
+ ): InfoViewerItem[] => {
50
+ if (!obj) {
51
+ return [];
52
+ }
53
+
54
+ return Object.entries(obj)
55
+ .map(([label, value]) => formatter(label, value))
56
+ .filter(({value}) => Boolean(value));
57
+ };
@@ -0,0 +1,17 @@
1
+ import {Link, LinkProps} from 'react-router-dom';
2
+ import cn from 'bem-cn-lite';
3
+
4
+ const bLink = cn('yc-link');
5
+
6
+ interface InternalLinkProps extends Omit<LinkProps, 'to'> {
7
+ to?: LinkProps['to'];
8
+ }
9
+
10
+ export const InternalLink = ({className, to, onClick, ...props}: InternalLinkProps) =>
11
+ to ? (
12
+ <Link to={to} onClick={onClick} className={bLink({view: 'normal'}, className)} {...props} />
13
+ ) : (
14
+ <span className={className} onClick={onClick}>
15
+ {props.children}
16
+ </span>
17
+ );
@@ -0,0 +1 @@
1
+ export * from './InternalLink';
@@ -2,7 +2,7 @@ import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import cn from 'bem-cn-lite';
4
4
 
5
- import InternalLink from '../InternalLink/InternalLink';
5
+ import {InternalLink} from '../InternalLink';
6
6
 
7
7
  import {getTabletLabel} from '../../utils/constants';
8
8
  import routes, {createHref} from '../../routes';
@@ -29,7 +29,7 @@ const prepareTablets = (tablets: ITablets) => {
29
29
 
30
30
  interface TabletsStatisticProps {
31
31
  tablets: ITablets;
32
- path: string;
32
+ path: string | undefined;
33
33
  nodeIds: string[] | number[];
34
34
  }
35
35
 
@@ -0,0 +1,15 @@
1
+ .ydb-bars {
2
+ display: flex;
3
+ flex-direction: row;
4
+ align-items: flex-end;
5
+
6
+ height: 20px;
7
+
8
+ &__value {
9
+ width: 6px;
10
+ min-height: 3px;
11
+ margin-right: 2px;
12
+
13
+ background-color: var(--yc-color-infographics-info-heavy);
14
+ }
15
+ }
@@ -0,0 +1,38 @@
1
+ import cn from 'bem-cn-lite';
2
+ import {useMemo} from 'react';
3
+
4
+ import './VerticalBars.scss';
5
+
6
+ const b = cn('ydb-bars');
7
+
8
+ const calculateValuesInPercents = (values: number[]) => {
9
+ const max = Math.max(...values);
10
+
11
+ if (!max) {
12
+ return values;
13
+ }
14
+
15
+ const res = [];
16
+
17
+ for (const value of values) {
18
+ res.push((value / max) * 100);
19
+ }
20
+
21
+ return res;
22
+ };
23
+
24
+ interface VerticalBarsProps {
25
+ values: number[];
26
+ }
27
+
28
+ export const VerticalBars = ({values}: VerticalBarsProps) => {
29
+ const preparedValues = useMemo(() => calculateValuesInPercents(values), [values]);
30
+
31
+ const getBars = () => {
32
+ return preparedValues.map((value, index) => {
33
+ return <div key={index} style={{height: `${value}%`}} className={b('value')} />;
34
+ });
35
+ };
36
+
37
+ return <div className={b()}>{getBars()}</div>;
38
+ };
@@ -0,0 +1 @@
1
+ export * from './VerticalBars';
@@ -2,10 +2,6 @@ import React from 'react';
2
2
  import {connect} from 'react-redux';
3
3
  import PropTypes from 'prop-types';
4
4
 
5
- import {configure as configureUiKit} from '@gravity-ui/uikit';
6
- import {configure as configureYdbUiComponents} from 'ydb-ui-components';
7
- import {i18n, Lang} from '../../utils/i18n';
8
-
9
5
  import ContentWrapper, {Content} from './Content';
10
6
  import AsideNavigation from '../AsideNavigation/AsideNavigation';
11
7
 
@@ -24,13 +20,6 @@ class App extends React.Component {
24
20
  children: PropTypes.node,
25
21
  };
26
22
 
27
- constructor(props) {
28
- super(props);
29
- i18n.setLang(Lang.En);
30
- configureYdbUiComponents({lang: Lang.En});
31
- configureUiKit({lang: Lang.En});
32
- }
33
-
34
23
  componentDidMount() {
35
24
  const {isAuthenticated, getUser} = this.props;
36
25
  if (isAuthenticated) {
@@ -1,6 +1,5 @@
1
1
  @import url('https://fonts.googleapis.com/css2?family=Rubik&display=swap');
2
2
  @import '../../styles/mixins.scss';
3
- @import './NodesTable';
4
3
 
5
4
  * {
6
5
  // FIXME: this is an overkill, potentially could break external components, needs refactoring
@@ -2,7 +2,7 @@ import cn from 'bem-cn-lite';
2
2
  //@ts-ignore
3
3
  import Tenants from '../Tenants/Tenants';
4
4
  //@ts-ignore
5
- import Nodes from '../Nodes/Nodes';
5
+ import {Nodes} from '../Nodes/Nodes';
6
6
  //@ts-ignore
7
7
  import Storage from '../Storage/Storage';
8
8
  import routes, {CLUSTER_PAGES} from '../../routes';
@@ -28,7 +28,7 @@ function Cluster(props: ClusterProps) {
28
28
  return <Tenants {...props} />;
29
29
  }
30
30
  case CLUSTER_PAGES.nodes.id: {
31
- return <Nodes {...props} />;
31
+ return <Nodes additionalNodesInfo={props.additionalNodesInfo} />;
32
32
  }
33
33
  case CLUSTER_PAGES.storage.id: {
34
34
  //@ts-ignore
@@ -1,7 +1,11 @@
1
1
  @import '../../styles/mixins.scss';
2
2
 
3
- .cluster-nodes {
3
+ .ydb-nodes {
4
4
  overflow: auto;
5
+ flex-grow: 1;
6
+
7
+ height: 100%;
8
+
5
9
  @include flex-container();
6
10
 
7
11
  &__search {
@@ -0,0 +1,196 @@
1
+ import {useCallback, useEffect} from 'react';
2
+ import cn from 'bem-cn-lite';
3
+ import {useDispatch} from 'react-redux';
4
+
5
+ import DataTable from '@yandex-cloud/react-data-table';
6
+
7
+ import {AccessDenied} from '../../components/Errors/403';
8
+ import {Illustration} from '../../components/Illustration';
9
+ import {Loader} from '../../components/Loader';
10
+
11
+ import {Search} from '../../components/Search';
12
+ import {ProblemFilter} from '../../components/ProblemFilter';
13
+ import {UptimeFilter} from '../../components/UptimeFIlter';
14
+ import {EntitiesCount} from '../../components/EntitiesCount';
15
+
16
+ import routes, {CLUSTER_PAGES, createHref} from '../../routes';
17
+
18
+ import {
19
+ ALL,
20
+ DEFAULT_TABLE_SETTINGS,
21
+ USE_NODES_ENDPOINT_IN_DIAGNOSTICS_KEY,
22
+ } from '../../utils/constants';
23
+ import {useAutofetcher, useTypedSelector} from '../../utils/hooks';
24
+ import {NodesUptimeFilterValues} from '../../utils/nodes';
25
+
26
+ import {setHeader} from '../../store/reducers/header';
27
+ import {
28
+ getNodes,
29
+ getFilteredPreparedNodesList,
30
+ setNodesUptimeFilter,
31
+ setSearchValue,
32
+ resetNodesState,
33
+ getComputeNodes,
34
+ } from '../../store/reducers/nodes';
35
+ import {changeFilter, getSettingValue} from '../../store/reducers/settings';
36
+ import {hideTooltip, showTooltip} from '../../store/reducers/tooltip';
37
+
38
+ import {getNodesColumns} from './getNodesColumns';
39
+
40
+ import './Nodes.scss';
41
+
42
+ import i18n from './i18n';
43
+
44
+ const b = cn('ydb-nodes');
45
+
46
+ interface IAdditionalNodesInfo extends Record<string, unknown> {
47
+ getNodeRef?: Function;
48
+ }
49
+
50
+ interface NodesProps {
51
+ tenantPath?: string;
52
+ className?: string;
53
+ additionalNodesInfo?: IAdditionalNodesInfo;
54
+ }
55
+
56
+ export const Nodes = ({tenantPath, className, additionalNodesInfo = {}}: NodesProps) => {
57
+ const dispatch = useDispatch();
58
+
59
+ const isClusterNodes = !tenantPath;
60
+
61
+ // Since Nodes component is used in several places,
62
+ // we need to reset filters, searchValue and loading state
63
+ // in nodes reducer when path changes
64
+ useEffect(() => {
65
+ dispatch(resetNodesState());
66
+ }, [dispatch, tenantPath]);
67
+
68
+ const {wasLoaded, loading, error, nodesUptimeFilter, searchValue, totalNodes} =
69
+ useTypedSelector((state) => state.nodes);
70
+ const problemFilter = useTypedSelector((state) => state.settings.problemFilter);
71
+ const {autorefresh} = useTypedSelector((state) => state.schema);
72
+
73
+ const nodes = useTypedSelector(getFilteredPreparedNodesList);
74
+
75
+ const useNodesEndpoint = useTypedSelector((state) =>
76
+ getSettingValue(state, USE_NODES_ENDPOINT_IN_DIAGNOSTICS_KEY),
77
+ );
78
+
79
+ const fetchNodes = useCallback(() => {
80
+ if (tenantPath && !JSON.parse(useNodesEndpoint)) {
81
+ dispatch(getComputeNodes(tenantPath));
82
+ } else {
83
+ dispatch(getNodes({tenant: tenantPath}));
84
+ }
85
+ }, [dispatch, tenantPath, useNodesEndpoint]);
86
+
87
+ useAutofetcher(fetchNodes, [fetchNodes], isClusterNodes ? true : autorefresh);
88
+
89
+ useEffect(() => {
90
+ if (isClusterNodes) {
91
+ dispatch(
92
+ setHeader([
93
+ {
94
+ text: CLUSTER_PAGES.nodes.title,
95
+ link: createHref(routes.cluster, {activeTab: CLUSTER_PAGES.nodes.id}),
96
+ },
97
+ ]),
98
+ );
99
+ }
100
+ }, [dispatch, isClusterNodes]);
101
+
102
+ const handleSearchQueryChange = (value: string) => {
103
+ dispatch(setSearchValue(value));
104
+ };
105
+
106
+ const handleProblemFilterChange = (value: string) => {
107
+ dispatch(changeFilter(value));
108
+ };
109
+
110
+ const handleUptimeFilterChange = (value: string) => {
111
+ dispatch(setNodesUptimeFilter(value as NodesUptimeFilterValues));
112
+ };
113
+
114
+ const renderControls = () => {
115
+ return (
116
+ <div className={b('controls')}>
117
+ <Search
118
+ onChange={handleSearchQueryChange}
119
+ placeholder="Host name"
120
+ className={b('search')}
121
+ value={searchValue}
122
+ />
123
+ <ProblemFilter value={problemFilter} onChange={handleProblemFilterChange} />
124
+ <UptimeFilter value={nodesUptimeFilter} onChange={handleUptimeFilterChange} />
125
+ <EntitiesCount
126
+ total={totalNodes}
127
+ current={nodes?.length || 0}
128
+ label={'Nodes'}
129
+ loading={loading && !wasLoaded}
130
+ />
131
+ </div>
132
+ );
133
+ };
134
+
135
+ const onShowTooltip = (...args: Parameters<typeof showTooltip>) => {
136
+ dispatch(showTooltip(...args));
137
+ };
138
+
139
+ const onHideTooltip = () => {
140
+ dispatch(hideTooltip());
141
+ };
142
+
143
+ const renderTable = () => {
144
+ const columns = getNodesColumns({
145
+ showTooltip: onShowTooltip,
146
+ hideTooltip: onHideTooltip,
147
+ getNodeRef: additionalNodesInfo.getNodeRef,
148
+ });
149
+
150
+ if (nodes && nodes.length === 0) {
151
+ if (problemFilter !== ALL || nodesUptimeFilter !== NodesUptimeFilterValues.All) {
152
+ return <Illustration name="thumbsUp" width="200" />;
153
+ }
154
+ }
155
+
156
+ return (
157
+ <div className={b('table-wrapper')}>
158
+ <div className={b('table-content')}>
159
+ <DataTable
160
+ theme="yandex-cloud"
161
+ data={nodes || []}
162
+ columns={columns}
163
+ settings={DEFAULT_TABLE_SETTINGS}
164
+ initialSortOrder={{
165
+ columnId: 'NodeId',
166
+ order: DataTable.ASCENDING,
167
+ }}
168
+ emptyDataMessage={i18n('empty.default')}
169
+ />
170
+ </div>
171
+ </div>
172
+ );
173
+ };
174
+
175
+ const renderContent = () => {
176
+ return (
177
+ <div className={b(null, className)}>
178
+ {renderControls()}
179
+ {renderTable()}
180
+ </div>
181
+ );
182
+ };
183
+
184
+ if (loading && !wasLoaded) {
185
+ return <Loader size={isClusterNodes ? 'l' : 'm'} />;
186
+ }
187
+
188
+ if (error) {
189
+ if (error.status === 403) {
190
+ return <AccessDenied />;
191
+ }
192
+ return <div>{error.statusText}</div>;
193
+ }
194
+
195
+ return renderContent();
196
+ };
@@ -1,4 +1,4 @@
1
- .kv-nodes {
1
+ .ydb-nodes-table {
2
2
  &__host-name-wrapper {
3
3
  display: flex;
4
4
  }
@@ -20,7 +20,7 @@
20
20
  }
21
21
 
22
22
  .data-table__row:hover {
23
- .kv-nodes__external-button {
23
+ .ydb-nodes-table__external-button {
24
24
  display: inline-flex;
25
25
  }
26
26
  }