ydb-embedded-ui 3.2.2 → 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.
- package/CHANGELOG.md +27 -0
- package/dist/components/InfoViewer/InfoViewer.scss +10 -0
- package/dist/components/InfoViewer/InfoViewer.tsx +12 -2
- package/dist/components/InfoViewer/formatters/cdcStream.ts +10 -0
- package/dist/components/InfoViewer/formatters/index.ts +3 -0
- package/dist/components/InfoViewer/formatters/pqGroup.ts +51 -0
- package/dist/components/InfoViewer/formatters/schema.ts +1 -29
- package/dist/components/InfoViewer/formatters/topicStats.tsx +50 -0
- package/dist/components/InfoViewer/schemaInfo/index.ts +0 -2
- package/dist/components/InfoViewer/utils.ts +15 -0
- package/dist/components/InternalLink/InternalLink.tsx +17 -0
- package/dist/components/InternalLink/index.ts +1 -0
- package/dist/components/Tablet/Tablet.js +1 -1
- package/dist/components/TabletsStatistic/TabletsStatistic.tsx +1 -1
- package/dist/components/VerticalBars/VerticalBars.scss +15 -0
- package/dist/components/VerticalBars/VerticalBars.tsx +38 -0
- package/dist/components/VerticalBars/index.ts +1 -0
- package/dist/containers/App/App.js +0 -11
- package/dist/containers/App/App.scss +0 -1
- package/dist/containers/Cluster/Cluster.tsx +2 -2
- package/dist/containers/Nodes/Nodes.scss +5 -1
- package/dist/containers/Nodes/Nodes.tsx +196 -0
- package/dist/containers/{App → Nodes}/NodesTable.scss +2 -2
- package/dist/{utils/getNodesColumns.js → containers/Nodes/getNodesColumns.tsx} +60 -35
- package/dist/containers/Nodes/i18n/en.json +3 -0
- package/dist/containers/Nodes/i18n/index.ts +11 -0
- package/dist/containers/Nodes/i18n/ru.json +3 -0
- package/dist/containers/Nodes/index.ts +1 -0
- package/dist/containers/Storage/DiskStateProgressBar/DiskStateProgressBar.scss +14 -20
- package/dist/containers/Storage/DiskStateProgressBar/DiskStateProgressBar.tsx +32 -16
- package/dist/containers/Storage/DiskStateProgressBar/index.ts +1 -0
- package/dist/containers/Storage/{Pdisk/Pdisk.scss → PDisk/PDisk.scss} +15 -4
- package/dist/containers/Storage/PDisk/PDisk.tsx +145 -0
- package/dist/containers/Storage/PDisk/__tests__/colors.tsx +37 -0
- package/dist/containers/Storage/PDisk/index.ts +1 -0
- package/dist/containers/Storage/PDiskPopup/PDiskPopup.scss +3 -0
- package/dist/containers/Storage/PDiskPopup/PDiskPopup.tsx +82 -0
- package/dist/containers/Storage/PDiskPopup/index.ts +1 -0
- package/dist/containers/Storage/Storage.js +1 -1
- package/dist/containers/Storage/StorageGroups/StorageGroups.tsx +10 -10
- package/dist/containers/Storage/StorageNodes/StorageNodes.scss +1 -0
- package/dist/containers/Storage/StorageNodes/StorageNodes.tsx +7 -4
- package/dist/containers/Storage/VDisk/VDisk.scss +7 -0
- package/dist/containers/Storage/VDisk/VDisk.tsx +148 -0
- package/dist/containers/Storage/VDisk/__tests__/colors.tsx +209 -0
- package/dist/containers/Storage/VDisk/index.ts +1 -0
- package/dist/containers/Storage/VDiskPopup/VDiskPopup.scss +14 -0
- package/dist/containers/Storage/VDiskPopup/VDiskPopup.tsx +134 -0
- package/dist/containers/Storage/VDiskPopup/index.ts +1 -0
- package/dist/containers/Storage/utils/constants.ts +2 -9
- package/dist/containers/TabletsFilters/TabletsFilters.js +10 -6
- package/dist/containers/Tenant/Diagnostics/Consumers/Consumers.tsx +2 -1
- package/dist/containers/Tenant/Diagnostics/Diagnostics.scss +2 -2
- package/dist/containers/Tenant/Diagnostics/Diagnostics.tsx +3 -4
- package/dist/containers/Tenant/Diagnostics/OverloadedShards/OverloadedShards.tsx +1 -1
- package/dist/containers/Tenant/Diagnostics/Overview/ChangefeedInfo/ChangefeedInfo.tsx +69 -0
- package/dist/containers/Tenant/Diagnostics/Overview/ChangefeedInfo/index.ts +1 -0
- package/dist/containers/Tenant/Diagnostics/Overview/Overview.tsx +18 -16
- package/dist/containers/Tenant/Diagnostics/Overview/TopicInfo/TopicInfo.tsx +37 -0
- package/dist/containers/Tenant/Diagnostics/Overview/TopicInfo/index.ts +1 -0
- package/dist/containers/Tenant/Diagnostics/Overview/TopicStats/TopicStats.scss +30 -0
- package/dist/containers/Tenant/Diagnostics/Overview/TopicStats/TopicStats.tsx +94 -0
- package/dist/containers/Tenant/Diagnostics/Overview/TopicStats/i18n/en.json +3 -0
- package/dist/containers/Tenant/Diagnostics/Overview/TopicStats/i18n/index.ts +11 -0
- package/dist/containers/Tenant/Diagnostics/Overview/TopicStats/i18n/ru.json +3 -0
- package/dist/containers/Tenant/Diagnostics/Overview/TopicStats/index.ts +1 -0
- package/dist/containers/Tenant/Diagnostics/Overview/utils/index.ts +1 -0
- package/dist/containers/Tenant/Diagnostics/Overview/utils/prepareTopicSchemaInfo.ts +42 -0
- package/dist/containers/Tenant/utils/schema.ts +19 -0
- package/dist/containers/Tenants/Tenants.js +2 -1
- package/dist/containers/UserSettings/UserSettings.tsx +18 -10
- package/dist/services/api.d.ts +8 -1
- package/dist/services/api.js +27 -8
- package/dist/store/reducers/index.ts +3 -1
- package/dist/store/reducers/nodes.ts +148 -14
- package/dist/store/reducers/{clusterNodes.js → nodesList.js} +0 -41
- package/dist/store/reducers/settings.js +10 -4
- package/dist/store/reducers/storage.js +24 -13
- package/dist/store/reducers/tenant.js +5 -4
- package/dist/store/reducers/tooltip.ts +1 -1
- package/dist/store/reducers/topic.ts +52 -0
- package/dist/styles/mixins.scss +19 -11
- package/dist/types/api/common.ts +5 -0
- package/dist/types/api/compute.ts +1 -1
- package/dist/types/api/consumer.ts +12 -10
- package/dist/types/api/nodes.ts +2 -0
- package/dist/types/api/pdisk.ts +1 -0
- package/dist/types/api/schema.ts +3 -3
- package/dist/types/api/topic.ts +5 -4
- package/dist/types/api/vdisk.ts +2 -1
- package/dist/types/store/nodes.ts +56 -6
- package/dist/types/store/tooltip.ts +1 -1
- package/dist/types/store/topic.ts +21 -0
- package/dist/utils/constants.ts +5 -1
- package/dist/utils/i18n/i18n.ts +10 -2
- package/dist/utils/index.js +1 -1
- package/dist/utils/timeParsers/__test__/formatDuration.test.ts +50 -0
- package/dist/utils/timeParsers/__test__/protobuf.test.ts +74 -0
- package/dist/utils/timeParsers/formatDuration.ts +46 -0
- package/dist/utils/timeParsers/i18n/en.json +7 -0
- package/dist/utils/timeParsers/i18n/index.ts +11 -0
- package/dist/utils/timeParsers/i18n/ru.json +7 -0
- package/dist/utils/timeParsers/index.ts +2 -0
- package/dist/utils/timeParsers/protobuf.ts +36 -0
- package/package.json +1 -1
- package/dist/components/InfoViewer/schemaInfo/CDCStreamInfo.tsx +0 -48
- package/dist/components/InfoViewer/schemaInfo/PersQueueGroupInfo.tsx +0 -30
- package/dist/components/InternalLink/InternalLink.js +0 -23
- package/dist/containers/Nodes/Nodes.js +0 -213
- package/dist/containers/NodesViewer/NodesViewer.js +0 -163
- package/dist/containers/NodesViewer/NodesViewer.scss +0 -66
- package/dist/containers/Storage/Pdisk/Pdisk.tsx +0 -153
- package/dist/containers/Storage/Pdisk/__tests__/colors.tsx +0 -41
- package/dist/containers/Storage/Vdisk/Vdisk.js +0 -275
- package/dist/containers/Storage/Vdisk/Vdisk.scss +0 -22
- package/dist/containers/Storage/Vdisk/__tests__/colors.tsx +0 -163
- package/dist/containers/Tenant/Diagnostics/Compute/Compute.js +0 -139
- package/dist/containers/Tenant/Diagnostics/Compute/Compute.scss +0 -14
|
@@ -1,20 +1,37 @@
|
|
|
1
1
|
import cn from 'bem-cn-lite';
|
|
2
|
-
import DataTable from '@yandex-cloud/react-data-table';
|
|
2
|
+
import DataTable, {Column} from '@yandex-cloud/react-data-table';
|
|
3
3
|
import {Button, Popover} from '@gravity-ui/uikit';
|
|
4
4
|
|
|
5
|
-
import Icon from '
|
|
6
|
-
import EntityStatus from '
|
|
7
|
-
import PoolsGraph from '
|
|
8
|
-
import ProgressViewer from '
|
|
9
|
-
import {TabletsStatistic} from '
|
|
5
|
+
import Icon from '../../components/Icon/Icon';
|
|
6
|
+
import EntityStatus from '../../components/EntityStatus/EntityStatus';
|
|
7
|
+
import PoolsGraph from '../../components/PoolsGraph/PoolsGraph';
|
|
8
|
+
import ProgressViewer from '../../components/ProgressViewer/ProgressViewer';
|
|
9
|
+
import {TabletsStatistic} from '../../components/TabletsStatistic';
|
|
10
10
|
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
11
|
+
import {formatBytes} from '../../utils/index';
|
|
12
|
+
import {INodesPreparedEntity} from '../../types/store/nodes';
|
|
13
|
+
import {showTooltip as externalShowTooltip} from '../../store/reducers/tooltip';
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
import {getDefaultNodePath} from '../Node/NodePages';
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
import './NodesTable.scss';
|
|
18
|
+
|
|
19
|
+
const b = cn('ydb-nodes-table');
|
|
20
|
+
|
|
21
|
+
interface GetNodesColumnsProps {
|
|
22
|
+
showTooltip: (...args: Parameters<typeof externalShowTooltip>) => void;
|
|
23
|
+
hideTooltip: VoidFunction;
|
|
24
|
+
tabletsPath?: string;
|
|
25
|
+
getNodeRef?: Function;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function getNodesColumns({
|
|
29
|
+
showTooltip,
|
|
30
|
+
hideTooltip,
|
|
31
|
+
tabletsPath,
|
|
32
|
+
getNodeRef,
|
|
33
|
+
}: GetNodesColumnsProps) {
|
|
34
|
+
const columns: Column<INodesPreparedEntity>[] = [
|
|
18
35
|
{
|
|
19
36
|
name: 'NodeId',
|
|
20
37
|
header: '#',
|
|
@@ -23,19 +40,20 @@ export function getNodesColumns({showTooltip, hideTooltip, tabletsPath, getNodeR
|
|
|
23
40
|
},
|
|
24
41
|
{
|
|
25
42
|
name: 'Host',
|
|
26
|
-
render: ({row
|
|
43
|
+
render: ({row}) => {
|
|
27
44
|
const nodeRef = getNodeRef ? getNodeRef(row) + 'internal' : undefined;
|
|
28
|
-
|
|
29
|
-
if (typeof value === 'undefined') {
|
|
45
|
+
if (typeof row.Host === 'undefined') {
|
|
30
46
|
return <span>—</span>;
|
|
31
47
|
}
|
|
32
48
|
return (
|
|
33
49
|
<div className={b('host-name-wrapper')}>
|
|
34
50
|
<EntityStatus
|
|
35
51
|
name={row.Host}
|
|
36
|
-
onNameMouseEnter={(e) =>
|
|
52
|
+
onNameMouseEnter={(e: MouseEvent) =>
|
|
53
|
+
showTooltip(e.target, row, 'nodeEndpoints')
|
|
54
|
+
}
|
|
37
55
|
onNameMouseLeave={hideTooltip}
|
|
38
|
-
status={row.
|
|
56
|
+
status={row.SystemState}
|
|
39
57
|
path={getDefaultNodePath(row.NodeId)}
|
|
40
58
|
hasClipboardButton
|
|
41
59
|
className={b('host-name')}
|
|
@@ -60,28 +78,28 @@ export function getNodesColumns({showTooltip, hideTooltip, tabletsPath, getNodeR
|
|
|
60
78
|
name: 'DataCenter',
|
|
61
79
|
header: 'DC',
|
|
62
80
|
align: DataTable.LEFT,
|
|
63
|
-
render: ({
|
|
81
|
+
render: ({row}) => (row.DataCenter ? row.DataCenter : '—'),
|
|
64
82
|
width: '60px',
|
|
65
83
|
},
|
|
66
84
|
{
|
|
67
85
|
name: 'Rack',
|
|
68
86
|
header: 'Rack',
|
|
69
87
|
align: DataTable.LEFT,
|
|
70
|
-
render: ({
|
|
88
|
+
render: ({row}) => (row.Rack ? row.Rack : '—'),
|
|
71
89
|
width: '80px',
|
|
72
90
|
},
|
|
73
91
|
{
|
|
74
92
|
name: 'Version',
|
|
75
93
|
width: '200px',
|
|
76
94
|
align: DataTable.LEFT,
|
|
77
|
-
render: ({
|
|
78
|
-
return <Popover content={
|
|
95
|
+
render: ({row}) => {
|
|
96
|
+
return <Popover content={row.Version}>{row.Version}</Popover>;
|
|
79
97
|
},
|
|
80
98
|
},
|
|
81
99
|
{
|
|
82
|
-
name: '
|
|
100
|
+
name: 'Uptime',
|
|
83
101
|
header: 'Uptime',
|
|
84
|
-
sortAccessor: ({StartTime}) => -StartTime,
|
|
102
|
+
sortAccessor: ({StartTime}) => StartTime && -StartTime,
|
|
85
103
|
align: DataTable.LEFT,
|
|
86
104
|
width: '110px',
|
|
87
105
|
},
|
|
@@ -90,12 +108,9 @@ export function getNodesColumns({showTooltip, hideTooltip, tabletsPath, getNodeR
|
|
|
90
108
|
header: 'Memory',
|
|
91
109
|
sortAccessor: ({MemoryUsed = 0}) => Number(MemoryUsed),
|
|
92
110
|
defaultOrder: DataTable.DESCENDING,
|
|
93
|
-
render: ({
|
|
94
|
-
if (
|
|
95
|
-
return formatBytes(
|
|
96
|
-
}
|
|
97
|
-
if (row.Metrics) {
|
|
98
|
-
return formatBytes(row.Metrics.Memory);
|
|
111
|
+
render: ({row}) => {
|
|
112
|
+
if (row.MemoryUsed) {
|
|
113
|
+
return formatBytes(row.MemoryUsed);
|
|
99
114
|
} else {
|
|
100
115
|
return '—';
|
|
101
116
|
}
|
|
@@ -107,14 +122,20 @@ export function getNodesColumns({showTooltip, hideTooltip, tabletsPath, getNodeR
|
|
|
107
122
|
name: 'PoolStats',
|
|
108
123
|
header: 'CPU',
|
|
109
124
|
sortAccessor: ({PoolStats = []}) =>
|
|
110
|
-
PoolStats.reduce((acc, item) =>
|
|
125
|
+
PoolStats.reduce((acc, item) => {
|
|
126
|
+
if (item.Usage) {
|
|
127
|
+
return acc + item.Usage;
|
|
128
|
+
} else {
|
|
129
|
+
return acc;
|
|
130
|
+
}
|
|
131
|
+
}, 0),
|
|
111
132
|
defaultOrder: DataTable.DESCENDING,
|
|
112
|
-
render: ({
|
|
113
|
-
|
|
133
|
+
render: ({row}) =>
|
|
134
|
+
row.PoolStats ? (
|
|
114
135
|
<PoolsGraph
|
|
115
136
|
onMouseEnter={showTooltip}
|
|
116
137
|
onMouseLeave={hideTooltip}
|
|
117
|
-
pools={
|
|
138
|
+
pools={row.PoolStats}
|
|
118
139
|
/>
|
|
119
140
|
) : (
|
|
120
141
|
'—'
|
|
@@ -128,9 +149,13 @@ export function getNodesColumns({showTooltip, hideTooltip, tabletsPath, getNodeR
|
|
|
128
149
|
sortAccessor: ({LoadAverage = []}) =>
|
|
129
150
|
LoadAverage.slice(0, 1).reduce((acc, item) => acc + item, 0),
|
|
130
151
|
defaultOrder: DataTable.DESCENDING,
|
|
131
|
-
render: ({
|
|
132
|
-
|
|
133
|
-
<ProgressViewer
|
|
152
|
+
render: ({row}) =>
|
|
153
|
+
row.LoadAverage && row.LoadAverage.length > 0 ? (
|
|
154
|
+
<ProgressViewer
|
|
155
|
+
value={row.LoadAverage[0]}
|
|
156
|
+
percents={true}
|
|
157
|
+
colorizeProgress={true}
|
|
158
|
+
/>
|
|
134
159
|
) : (
|
|
135
160
|
'—'
|
|
136
161
|
),
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import {i18n, Lang} from '../../../utils/i18n';
|
|
2
|
+
|
|
3
|
+
import en from './en.json';
|
|
4
|
+
import ru from './ru.json';
|
|
5
|
+
|
|
6
|
+
const COMPONENT = 'ydb-nodes';
|
|
7
|
+
|
|
8
|
+
i18n.registerKeyset(Lang.En, COMPONENT, en);
|
|
9
|
+
i18n.registerKeyset(Lang.Ru, COMPONENT, ru);
|
|
10
|
+
|
|
11
|
+
export default i18n.keyset(COMPONENT);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Nodes';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
.storage-disk-progress-bar {
|
|
2
2
|
$block: &;
|
|
3
3
|
|
|
4
|
-
$border-width:
|
|
4
|
+
$border-width: 1px;
|
|
5
5
|
$outer-border-radius: 4px;
|
|
6
6
|
$inner-border-radius: $outer-border-radius - $border-width;
|
|
7
7
|
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
display: block;
|
|
11
11
|
|
|
12
12
|
min-width: 50px;
|
|
13
|
-
height: var(--yc-text-body-
|
|
13
|
+
height: var(--yc-text-body-3-line-height);
|
|
14
14
|
|
|
15
15
|
text-align: center;
|
|
16
16
|
|
|
@@ -19,6 +19,17 @@
|
|
|
19
19
|
border-radius: $outer-border-radius;
|
|
20
20
|
background-color: var(--yc-color-infographics-misc-light);
|
|
21
21
|
|
|
22
|
+
&_compact {
|
|
23
|
+
min-width: 0;
|
|
24
|
+
height: 12px;
|
|
25
|
+
|
|
26
|
+
border-radius: 2px;
|
|
27
|
+
|
|
28
|
+
#{$block}__filled {
|
|
29
|
+
border-radius: 1px;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
22
33
|
#{$block}__filled {
|
|
23
34
|
background-color: var(--yc-color-infographics-misc-medium);
|
|
24
35
|
}
|
|
@@ -89,25 +100,8 @@
|
|
|
89
100
|
|
|
90
101
|
font-size: var(--yc-text-body-1-font-size);
|
|
91
102
|
// bar height minus borders
|
|
92
|
-
line-height: calc(var(--yc-text-body-
|
|
103
|
+
line-height: calc(var(--yc-text-body-3-line-height) - #{$border-width * 2});
|
|
93
104
|
|
|
94
105
|
color: inherit;
|
|
95
106
|
}
|
|
96
|
-
|
|
97
|
-
&__link {
|
|
98
|
-
display: flex;
|
|
99
|
-
justify-content: center;
|
|
100
|
-
|
|
101
|
-
// extend active area to include borders
|
|
102
|
-
height: var(--yc-text-body-2-line-height);
|
|
103
|
-
margin: -$border-width;
|
|
104
|
-
padding: $border-width;
|
|
105
|
-
|
|
106
|
-
color: inherit;
|
|
107
|
-
border-radius: $outer-border-radius;
|
|
108
|
-
|
|
109
|
-
&:hover {
|
|
110
|
-
color: inherit;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
107
|
}
|
|
@@ -9,33 +9,49 @@ import './DiskStateProgressBar.scss';
|
|
|
9
9
|
|
|
10
10
|
const b = cn('storage-disk-progress-bar');
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
12
|
+
// numeric enum to allow ordinal comparison
|
|
13
|
+
export enum EDiskStateSeverity {
|
|
14
|
+
Grey = 0,
|
|
15
|
+
Green = 1,
|
|
16
|
+
Blue = 2,
|
|
17
|
+
Yellow = 3,
|
|
18
|
+
Orange = 4,
|
|
19
|
+
Red = 5,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const severityToColor = Object.entries(EDiskStateSeverity).reduce(
|
|
23
|
+
(acc, [color, severity]) => ({...acc, [severity]: color}),
|
|
24
|
+
{} as Record<EDiskStateSeverity, keyof typeof EDiskStateSeverity>,
|
|
25
|
+
);
|
|
20
26
|
|
|
21
27
|
interface DiskStateProgressBarProps {
|
|
22
28
|
diskAllocatedPercent?: number;
|
|
23
|
-
severity?:
|
|
29
|
+
severity?: EDiskStateSeverity;
|
|
30
|
+
compact?: boolean;
|
|
24
31
|
}
|
|
25
32
|
|
|
26
|
-
function DiskStateProgressBar({
|
|
33
|
+
export function DiskStateProgressBar({
|
|
27
34
|
diskAllocatedPercent = -1,
|
|
28
35
|
severity,
|
|
36
|
+
compact,
|
|
29
37
|
}: DiskStateProgressBarProps) {
|
|
30
38
|
const inverted = useSelector((state) => JSON.parse(getSettingValue(state, INVERTED_DISKS_KEY)));
|
|
31
39
|
|
|
32
40
|
const renderAllocatedPercent = () => {
|
|
41
|
+
if (compact) {
|
|
42
|
+
return <div className={b('filled')} style={{width: '100%'}} />;
|
|
43
|
+
}
|
|
44
|
+
|
|
33
45
|
return (
|
|
34
46
|
diskAllocatedPercent >= 0 && (
|
|
35
47
|
<React.Fragment>
|
|
36
48
|
<div
|
|
37
49
|
className={b('filled')}
|
|
38
|
-
style={{
|
|
50
|
+
style={{
|
|
51
|
+
width: `${
|
|
52
|
+
inverted ? 100 - diskAllocatedPercent : diskAllocatedPercent
|
|
53
|
+
}%`,
|
|
54
|
+
}}
|
|
39
55
|
/>
|
|
40
56
|
<div className={b('filled-title')}>
|
|
41
57
|
{`${Math.round(diskAllocatedPercent)}%`}
|
|
@@ -45,9 +61,11 @@ function DiskStateProgressBar({
|
|
|
45
61
|
);
|
|
46
62
|
};
|
|
47
63
|
|
|
48
|
-
const mods: Record<string, boolean | undefined> = {inverted};
|
|
49
|
-
|
|
50
|
-
|
|
64
|
+
const mods: Record<string, boolean | undefined> = {inverted, compact};
|
|
65
|
+
|
|
66
|
+
const color = severity !== undefined && severityToColor[severity];
|
|
67
|
+
if (color) {
|
|
68
|
+
mods[color.toLocaleLowerCase()] = true;
|
|
51
69
|
}
|
|
52
70
|
|
|
53
71
|
return (
|
|
@@ -63,5 +81,3 @@ function DiskStateProgressBar({
|
|
|
63
81
|
</div>
|
|
64
82
|
);
|
|
65
83
|
}
|
|
66
|
-
|
|
67
|
-
export default DiskStateProgressBar;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './DiskStateProgressBar';
|
|
@@ -3,14 +3,24 @@
|
|
|
3
3
|
|
|
4
4
|
width: 120px;
|
|
5
5
|
|
|
6
|
-
border-radius: 4px; // to match interactive area with disk shape
|
|
7
|
-
|
|
8
6
|
&__content {
|
|
7
|
+
position: relative;
|
|
8
|
+
|
|
9
9
|
border-radius: 4px; // to match interactive area with disk shape
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
&
|
|
13
|
-
|
|
12
|
+
&__vdisks {
|
|
13
|
+
display: flex;
|
|
14
|
+
// this breaks disks relative sizes, but disks rarely exceed one line
|
|
15
|
+
flex-wrap: wrap;
|
|
16
|
+
gap: 2px;
|
|
17
|
+
|
|
18
|
+
margin-bottom: 4px;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
&__vdisks-item {
|
|
22
|
+
flex-basis: 5px;
|
|
23
|
+
flex-shrink: 0;
|
|
14
24
|
}
|
|
15
25
|
|
|
16
26
|
&__media-type {
|
|
@@ -19,6 +29,7 @@
|
|
|
19
29
|
right: 4px;
|
|
20
30
|
|
|
21
31
|
font-size: var(--yc-text-body-1-font-size);
|
|
32
|
+
line-height: var(--yc-text-body-3-line-height);
|
|
22
33
|
|
|
23
34
|
color: var(--yc-color-text-secondary);
|
|
24
35
|
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import React, {useEffect, useState, useRef, useMemo} from 'react';
|
|
2
|
+
import cn from 'bem-cn-lite';
|
|
3
|
+
|
|
4
|
+
import {InternalLink} from '../../../components/InternalLink';
|
|
5
|
+
|
|
6
|
+
import routes, {createHref} from '../../../routes';
|
|
7
|
+
import {getVDisksForPDisk} from '../../../store/reducers/storage';
|
|
8
|
+
import {TPDiskStateInfo, TPDiskState} from '../../../types/api/pdisk';
|
|
9
|
+
import {TVDiskStateInfo} from '../../../types/api/vdisk';
|
|
10
|
+
import {stringifyVdiskId} from '../../../utils';
|
|
11
|
+
import {useTypedSelector} from '../../../utils/hooks';
|
|
12
|
+
import {getPDiskType} from '../../../utils/pdisk';
|
|
13
|
+
|
|
14
|
+
import {STRUCTURE} from '../../Node/NodePages';
|
|
15
|
+
|
|
16
|
+
import {DiskStateProgressBar, EDiskStateSeverity} from '../DiskStateProgressBar';
|
|
17
|
+
import {PDiskPopup} from '../PDiskPopup';
|
|
18
|
+
import {VDisk} from '../VDisk';
|
|
19
|
+
|
|
20
|
+
import {NOT_AVAILABLE_SEVERITY} from '../utils';
|
|
21
|
+
|
|
22
|
+
import './PDisk.scss';
|
|
23
|
+
|
|
24
|
+
const b = cn('pdisk-storage');
|
|
25
|
+
|
|
26
|
+
const stateSeverity = {
|
|
27
|
+
[TPDiskState.Initial]: EDiskStateSeverity.Grey,
|
|
28
|
+
[TPDiskState.Normal]: EDiskStateSeverity.Green,
|
|
29
|
+
[TPDiskState.InitialFormatRead]: EDiskStateSeverity.Yellow,
|
|
30
|
+
[TPDiskState.InitialSysLogRead]: EDiskStateSeverity.Yellow,
|
|
31
|
+
[TPDiskState.InitialCommonLogRead]: EDiskStateSeverity.Yellow,
|
|
32
|
+
[TPDiskState.InitialFormatReadError]: EDiskStateSeverity.Red,
|
|
33
|
+
[TPDiskState.InitialSysLogReadError]: EDiskStateSeverity.Red,
|
|
34
|
+
[TPDiskState.InitialSysLogParseError]: EDiskStateSeverity.Red,
|
|
35
|
+
[TPDiskState.InitialCommonLogReadError]: EDiskStateSeverity.Red,
|
|
36
|
+
[TPDiskState.InitialCommonLogParseError]: EDiskStateSeverity.Red,
|
|
37
|
+
[TPDiskState.CommonLoggerInitError]: EDiskStateSeverity.Red,
|
|
38
|
+
[TPDiskState.OpenFileError]: EDiskStateSeverity.Red,
|
|
39
|
+
[TPDiskState.ChunkQuotaError]: EDiskStateSeverity.Red,
|
|
40
|
+
[TPDiskState.DeviceIoError]: EDiskStateSeverity.Red,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
interface PDiskProps {
|
|
44
|
+
nodeId: number;
|
|
45
|
+
data?: TPDiskStateInfo;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const isSeverityKey = (key?: TPDiskState): key is keyof typeof stateSeverity =>
|
|
49
|
+
key !== undefined && key in stateSeverity;
|
|
50
|
+
|
|
51
|
+
const getStateSeverity = (pDiskState?: TPDiskState) => {
|
|
52
|
+
return isSeverityKey(pDiskState) ? stateSeverity[pDiskState] : NOT_AVAILABLE_SEVERITY;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const PDisk = ({nodeId, data: rawData = {}}: PDiskProps) => {
|
|
56
|
+
// NodeId in data is required for the popup
|
|
57
|
+
const data = useMemo(() => ({...rawData, NodeId: nodeId}), [rawData, nodeId]);
|
|
58
|
+
|
|
59
|
+
const vdisks: TVDiskStateInfo[] | undefined = useTypedSelector((state) =>
|
|
60
|
+
// @ts-expect-error selector is correct, but js infers broken type
|
|
61
|
+
// unignore after rewriting reducer in ts
|
|
62
|
+
getVDisksForPDisk(state, nodeId, data.PDiskId),
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const [severity, setSeverity] = useState(getStateSeverity(data.State));
|
|
66
|
+
const [isPopupVisible, setIsPopupVisible] = useState(false);
|
|
67
|
+
|
|
68
|
+
const anchor = useRef(null);
|
|
69
|
+
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
const newSeverity = getStateSeverity(data.State);
|
|
72
|
+
if (severity !== newSeverity) {
|
|
73
|
+
setSeverity(newSeverity);
|
|
74
|
+
}
|
|
75
|
+
}, [data.State]);
|
|
76
|
+
|
|
77
|
+
const showPopup = () => {
|
|
78
|
+
setIsPopupVisible(true);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const hidePopup = () => {
|
|
82
|
+
setIsPopupVisible(false);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const pdiskAllocatedPercent = useMemo(() => {
|
|
86
|
+
const {AvailableSize, TotalSize} = data;
|
|
87
|
+
|
|
88
|
+
if (!AvailableSize || !TotalSize) {
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return !isNaN(Number(AvailableSize)) && !isNaN(Number(TotalSize))
|
|
93
|
+
? Math.round(((Number(TotalSize) - Number(AvailableSize)) * 100) / Number(TotalSize))
|
|
94
|
+
: undefined;
|
|
95
|
+
}, [data]);
|
|
96
|
+
|
|
97
|
+
const renderVDisks = () => {
|
|
98
|
+
if (!vdisks?.length) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<div className={b('vdisks')}>
|
|
104
|
+
{vdisks.map((vdisk) => (
|
|
105
|
+
<div
|
|
106
|
+
key={stringifyVdiskId(vdisk.VDiskId)}
|
|
107
|
+
className={b('vdisks-item')}
|
|
108
|
+
style={{
|
|
109
|
+
// 1 is small enough for empty disks to be of the minimum width
|
|
110
|
+
// but if all of them are empty, `flex-grow: 1` would size them evenly
|
|
111
|
+
flexGrow: (Number(vdisk.AllocatedSize) || 1),
|
|
112
|
+
}}
|
|
113
|
+
>
|
|
114
|
+
<VDisk data={vdisk} compact />
|
|
115
|
+
</div>
|
|
116
|
+
))}
|
|
117
|
+
</div>
|
|
118
|
+
);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
return (
|
|
122
|
+
<React.Fragment>
|
|
123
|
+
<PDiskPopup data={data} anchorRef={anchor} open={isPopupVisible} />
|
|
124
|
+
<div className={b()} ref={anchor}>
|
|
125
|
+
{renderVDisks()}
|
|
126
|
+
<InternalLink
|
|
127
|
+
to={createHref(
|
|
128
|
+
routes.node,
|
|
129
|
+
{id: nodeId, activeTab: STRUCTURE},
|
|
130
|
+
{pdiskId: data.PDiskId || ''},
|
|
131
|
+
)}
|
|
132
|
+
className={b('content')}
|
|
133
|
+
onMouseEnter={showPopup}
|
|
134
|
+
onMouseLeave={hidePopup}
|
|
135
|
+
>
|
|
136
|
+
<DiskStateProgressBar
|
|
137
|
+
diskAllocatedPercent={pdiskAllocatedPercent}
|
|
138
|
+
severity={severity}
|
|
139
|
+
/>
|
|
140
|
+
<div className={b('media-type')}>{getPDiskType(data)}</div>
|
|
141
|
+
</InternalLink>
|
|
142
|
+
</div>
|
|
143
|
+
</React.Fragment>
|
|
144
|
+
);
|
|
145
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import {MemoryRouter} from 'react-router-dom';
|
|
2
|
+
|
|
3
|
+
import {renderWithStore} from '../../../../utils/tests/providers';
|
|
4
|
+
|
|
5
|
+
import {TPDiskState} from '../../../../types/api/pdisk';
|
|
6
|
+
|
|
7
|
+
import {PDisk} from '../PDisk';
|
|
8
|
+
|
|
9
|
+
describe('PDisk state', () => {
|
|
10
|
+
it('Should determine severity based on State', () => {
|
|
11
|
+
const {getAllByRole} = renderWithStore(
|
|
12
|
+
<MemoryRouter>
|
|
13
|
+
<PDisk nodeId={1} data={{State: TPDiskState.Normal}} />
|
|
14
|
+
<PDisk nodeId={2} data={{State: TPDiskState.ChunkQuotaError}} />
|
|
15
|
+
</MemoryRouter>,
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
const [normalDisk, erroredDisk] = getAllByRole('meter');
|
|
19
|
+
|
|
20
|
+
expect(normalDisk.className).not.toBe(erroredDisk.className);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('Should display as unavailabe when no State is provided', () => {
|
|
24
|
+
const {getAllByRole} = renderWithStore(
|
|
25
|
+
<MemoryRouter>
|
|
26
|
+
<PDisk nodeId={1} />
|
|
27
|
+
<PDisk nodeId={2} data={{State: TPDiskState.ChunkQuotaError}} />
|
|
28
|
+
</MemoryRouter>,
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
const [disk1, disk2] = getAllByRole('meter');
|
|
32
|
+
|
|
33
|
+
// unavailable disks display with the grey color
|
|
34
|
+
expect(disk1.className).toMatch(/_grey\b/i);
|
|
35
|
+
expect(disk2.className).not.toMatch(/_grey\b/i);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './PDisk';
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import {useMemo} from 'react';
|
|
2
|
+
import cn from 'bem-cn-lite';
|
|
3
|
+
|
|
4
|
+
import {Popup, PopupProps} from '@gravity-ui/uikit';
|
|
5
|
+
|
|
6
|
+
import {InfoViewer, InfoViewerItem} from '../../../components/InfoViewer';
|
|
7
|
+
|
|
8
|
+
import {EFlag} from '../../../types/api/enums';
|
|
9
|
+
import {TPDiskStateInfo} from '../../../types/api/pdisk';
|
|
10
|
+
import {getPDiskId} from '../../../utils';
|
|
11
|
+
import {getPDiskType} from '../../../utils/pdisk';
|
|
12
|
+
import {bytesToGB} from '../../../utils/utils';
|
|
13
|
+
|
|
14
|
+
import './PDiskPopup.scss';
|
|
15
|
+
|
|
16
|
+
const b = cn('pdisk-storage-popup');
|
|
17
|
+
|
|
18
|
+
const errorColors = [EFlag.Orange, EFlag.Red, EFlag.Yellow];
|
|
19
|
+
|
|
20
|
+
export type NodesHosts = {
|
|
21
|
+
// NodeId => Host
|
|
22
|
+
[nodeId: number]: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const preparePDiskData = (data: TPDiskStateInfo, nodes?: NodesHosts) => {
|
|
26
|
+
const {AvailableSize, TotalSize, State, PDiskId, NodeId, Path, Realtime, Device} = data;
|
|
27
|
+
|
|
28
|
+
const pdiskData: InfoViewerItem[] = [
|
|
29
|
+
{label: 'PDisk', value: getPDiskId({NodeId, PDiskId})},
|
|
30
|
+
{label: 'State', value: State || 'not available'},
|
|
31
|
+
{label: 'Type', value: getPDiskType(data) || 'unknown'},
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
if (NodeId) {
|
|
35
|
+
pdiskData.push({label: 'Node Id', value: NodeId});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (nodes && NodeId && nodes[NodeId]) {
|
|
39
|
+
pdiskData.push({label: 'Host', value: nodes[NodeId]});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (Path) {
|
|
43
|
+
pdiskData.push({label: 'Path', value: Path});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
pdiskData.push({
|
|
47
|
+
label: 'Available',
|
|
48
|
+
value: `${bytesToGB(AvailableSize)} of ${bytesToGB(TotalSize)}`,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
if (Realtime && errorColors.includes(Realtime)) {
|
|
52
|
+
pdiskData.push({label: 'Realtime', value: Realtime});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (Device && errorColors.includes(Device)) {
|
|
56
|
+
pdiskData.push({label: 'Device', value: Device});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return pdiskData;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
interface PDiskPopupProps extends PopupProps {
|
|
63
|
+
data: TPDiskStateInfo;
|
|
64
|
+
nodes?: NodesHosts;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export const PDiskPopup = ({data, nodes, ...props}: PDiskPopupProps) => {
|
|
68
|
+
const info = useMemo(() => preparePDiskData(data, nodes), [data, nodes]);
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<Popup
|
|
72
|
+
className={b()}
|
|
73
|
+
placement={['top', 'bottom']}
|
|
74
|
+
// bigger offset for easier switching to neighbour nodes
|
|
75
|
+
// matches the default offset for popup with arrow out of a sense of beauty
|
|
76
|
+
offset={[0, 12]}
|
|
77
|
+
{...props}
|
|
78
|
+
>
|
|
79
|
+
<InfoViewer title="PDisk" info={info} size="s" />
|
|
80
|
+
</Popup>
|
|
81
|
+
);
|
|
82
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './PDiskPopup';
|
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
getStorageNodesCount,
|
|
33
33
|
getUsageFilterOptions,
|
|
34
34
|
} from '../../store/reducers/storage';
|
|
35
|
-
import {getNodesList} from '../../store/reducers/
|
|
35
|
+
import {getNodesList} from '../../store/reducers/nodesList';
|
|
36
36
|
import StorageGroups from './StorageGroups/StorageGroups';
|
|
37
37
|
import StorageNodes from './StorageNodes/StorageNodes';
|
|
38
38
|
import {DEFAULT_TABLE_SETTINGS} from '../../utils/constants';
|