ydb-embedded-ui 20.3.0 → 20.4.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/dist/components/Drawer/Drawer.d.ts +2 -1
- package/dist/components/Drawer/Drawer.js +4 -4
- package/dist/components/Drawer/Drawer.js.map +1 -1
- package/dist/components/EntityStatus/EntityStatus.d.ts +4 -1
- package/dist/components/EntityStatus/EntityStatus.js +2 -2
- package/dist/components/EntityStatus/EntityStatus.js.map +1 -1
- package/dist/components/PDiskInfo/i18n/index.d.ts +1 -1
- package/dist/components/TenantNameWrapper/utils.d.ts +1 -1
- package/dist/components/TenantNameWrapper/utils.js.map +1 -1
- package/dist/containers/Cluster/Cluster.js +1 -1
- package/dist/containers/Cluster/Cluster.js.map +1 -1
- package/dist/containers/Cluster/ClusterDrawerHealthcheck.d.ts +8 -0
- package/dist/containers/Cluster/ClusterDrawerHealthcheck.js +71 -9
- package/dist/containers/Cluster/ClusterDrawerHealthcheck.js.map +1 -1
- package/dist/containers/Cluster/ClusterInfo/ClusterInfo.d.ts +2 -1
- package/dist/containers/Cluster/ClusterInfo/ClusterInfo.js +2 -2
- package/dist/containers/Cluster/ClusterInfo/ClusterInfo.js.map +1 -1
- package/dist/containers/Cluster/ClusterOverview/ClusterOverview.d.ts +1 -0
- package/dist/containers/Cluster/ClusterOverview/ClusterOverview.js.map +1 -1
- package/dist/containers/Cluster/ClusterOverview/components/BridgeInfoTable.d.ts +2 -0
- package/dist/containers/Cluster/ClusterOverview/components/BridgeInfoTable.js +54 -103
- package/dist/containers/Cluster/ClusterOverview/components/BridgeInfoTable.js.map +1 -1
- package/dist/containers/Cluster/ClusterOverview/components/BridgeInfoTable.scss +24 -89
- package/dist/containers/Cluster/ClusterOverview/components/bridgePileHealthcheck.d.ts +13 -0
- package/dist/containers/Cluster/ClusterOverview/components/bridgePileHealthcheck.js +94 -0
- package/dist/containers/Cluster/ClusterOverview/components/bridgePileHealthcheck.js.map +1 -0
- package/dist/containers/Cluster/i18n/en.json +8 -5
- package/dist/containers/Cluster/i18n/index.d.ts +1 -1
- package/dist/containers/Clusters/constants.d.ts +1 -1
- package/dist/containers/Header/DatabaseRightControls.d.ts +4 -2
- package/dist/containers/Header/DatabaseRightControls.js +2 -2
- package/dist/containers/Header/DatabaseRightControls.js.map +1 -1
- package/dist/containers/Header/Header.js +2 -2
- package/dist/containers/Header/Header.js.map +1 -1
- package/dist/containers/Header/HeaderActionsMenu.d.ts +4 -2
- package/dist/containers/Header/HeaderActionsMenu.js +19 -2
- package/dist/containers/Header/HeaderActionsMenu.js.map +1 -1
- package/dist/containers/Header/HeaderRightControls.d.ts +4 -2
- package/dist/containers/Header/HeaderRightControls.js +2 -2
- package/dist/containers/Header/HeaderRightControls.js.map +1 -1
- package/dist/containers/Header/hooks/useClusterData.d.ts +1 -0
- package/dist/containers/Header/hooks/useHeaderData.d.ts +1 -0
- package/dist/containers/Header/hooks/useHeaderData.js +1 -0
- package/dist/containers/Header/hooks/useHeaderData.js.map +1 -1
- package/dist/containers/Header/hooks/useSharedDatabasePath.d.ts +10 -0
- package/dist/containers/Header/hooks/useSharedDatabasePath.js +102 -0
- package/dist/containers/Header/hooks/useSharedDatabasePath.js.map +1 -0
- package/dist/containers/Header/i18n/en.json +1 -0
- package/dist/containers/Header/i18n/index.d.ts +1 -1
- package/dist/containers/Operations/constants.d.ts +1 -1
- package/dist/containers/Storage/PaginatedStorageGroupsTable/columns/i18n/index.d.ts +1 -1
- package/dist/containers/Tablets/Tablets.js +3 -3
- package/dist/containers/Tablets/Tablets.js.map +1 -1
- package/dist/containers/Tablets/TabletsTable.js +17 -27
- package/dist/containers/Tablets/TabletsTable.js.map +1 -1
- package/dist/containers/Tablets/i18n/en.json +3 -0
- package/dist/containers/Tablets/i18n/index.d.ts +1 -1
- package/dist/containers/Tablets/useTabletQueryParams.d.ts +8 -0
- package/dist/containers/Tablets/useTabletQueryParams.js +40 -0
- package/dist/containers/Tablets/useTabletQueryParams.js.map +1 -0
- package/dist/containers/Tablets/utils.d.ts +11 -0
- package/dist/containers/Tablets/utils.js +23 -0
- package/dist/containers/Tablets/utils.js.map +1 -0
- package/dist/containers/Tenant/Healthcheck/Healthcheck.d.ts +6 -1
- package/dist/containers/Tenant/Healthcheck/Healthcheck.js +5 -5
- package/dist/containers/Tenant/Healthcheck/Healthcheck.js.map +1 -1
- package/dist/containers/Tenant/Healthcheck/components/HealthcheckDrawer.d.ts +2 -1
- package/dist/containers/Tenant/Healthcheck/components/HealthcheckDrawer.js +2 -2
- package/dist/containers/Tenant/Healthcheck/components/HealthcheckDrawer.js.map +1 -1
- package/dist/containers/Tenant/Healthcheck/components/HealthcheckIssue.d.ts +5 -1
- package/dist/containers/Tenant/Healthcheck/components/HealthcheckIssue.js +5 -3
- package/dist/containers/Tenant/Healthcheck/components/HealthcheckIssue.js.map +1 -1
- package/dist/containers/Tenant/Healthcheck/components/HealthcheckIssueDetails/ComputeLocation.d.ts +1 -1
- package/dist/containers/Tenant/Healthcheck/components/HealthcheckIssueDetails/ComputeLocation.js +11 -2
- package/dist/containers/Tenant/Healthcheck/components/HealthcheckIssueDetails/ComputeLocation.js.map +1 -1
- package/dist/containers/Tenant/Healthcheck/components/HealthcheckIssueDetails/NodeInfo.js +2 -0
- package/dist/containers/Tenant/Healthcheck/components/HealthcheckIssueDetails/NodeInfo.js.map +1 -1
- package/dist/containers/Tenant/Healthcheck/components/HealthcheckIssueDetails/StorageLocation.js +4 -1
- package/dist/containers/Tenant/Healthcheck/components/HealthcheckIssueDetails/StorageLocation.js.map +1 -1
- package/dist/containers/Tenant/Healthcheck/components/HealthcheckIssues.d.ts +5 -1
- package/dist/containers/Tenant/Healthcheck/components/HealthcheckIssues.js +2 -2
- package/dist/containers/Tenant/Healthcheck/components/HealthcheckIssues.js.map +1 -1
- package/dist/containers/Tenant/Healthcheck/i18n/en.json +2 -1
- package/dist/containers/Tenant/Healthcheck/i18n/index.d.ts +1 -1
- package/dist/containers/Tenant/Query/SavedQueries/SavedQueries.scss +2 -0
- package/dist/containers/VDiskPage/i18n/index.d.ts +1 -1
- package/dist/routes.d.ts +4 -1
- package/dist/routes.js +3 -2
- package/dist/routes.js.map +1 -1
- package/dist/services/api/viewer.d.ts +3 -1
- package/dist/services/api/viewer.js +3 -2
- package/dist/services/api/viewer.js.map +1 -1
- package/dist/store/reducers/cluster/cluster.d.ts +2 -1
- package/dist/store/reducers/cluster/cluster.js +1 -0
- package/dist/store/reducers/cluster/cluster.js.map +1 -1
- package/dist/store/reducers/healthcheckInfo/types.d.ts +1 -0
- package/dist/store/reducers/healthcheckInfo/utils.d.ts +1 -0
- package/dist/store/reducers/healthcheckInfo/utils.js +43 -9
- package/dist/store/reducers/healthcheckInfo/utils.js.map +1 -1
- package/dist/store/reducers/tenants/tenants.d.ts +10 -1
- package/dist/store/reducers/tenants/tenants.js +33 -0
- package/dist/store/reducers/tenants/tenants.js.map +1 -1
- package/dist/store/reducers/tenants/types.d.ts +4 -0
- package/dist/types/api/healthcheck.d.ts +7 -0
- package/package.json +1 -1
- package/src/components/Drawer/Drawer.tsx +6 -0
- package/src/components/EntityStatus/EntityStatus.tsx +9 -1
- package/src/components/TenantNameWrapper/utils.ts +1 -1
- package/src/containers/Cluster/Cluster.tsx +1 -0
- package/src/containers/Cluster/ClusterDrawerHealthcheck.tsx +112 -13
- package/src/containers/Cluster/ClusterInfo/ClusterInfo.tsx +7 -1
- package/src/containers/Cluster/ClusterOverview/ClusterOverview.tsx +1 -0
- package/src/containers/Cluster/ClusterOverview/components/BridgeInfoTable.scss +24 -89
- package/src/containers/Cluster/ClusterOverview/components/BridgeInfoTable.tsx +156 -200
- package/src/containers/Cluster/ClusterOverview/components/bridgePileHealthcheck.ts +142 -0
- package/src/containers/Cluster/i18n/en.json +8 -5
- package/src/containers/Header/DatabaseRightControls.tsx +7 -1
- package/src/containers/Header/Header.tsx +3 -0
- package/src/containers/Header/HeaderActionsMenu.tsx +24 -2
- package/src/containers/Header/HeaderRightControls.tsx +7 -1
- package/src/containers/Header/hooks/useHeaderData.ts +1 -0
- package/src/containers/Header/hooks/useSharedDatabasePath.ts +195 -0
- package/src/containers/Header/i18n/en.json +1 -0
- package/src/containers/Tablets/Tablets.tsx +3 -3
- package/src/containers/Tablets/TabletsTable.tsx +37 -31
- package/src/containers/Tablets/i18n/en.json +3 -0
- package/src/containers/Tablets/useTabletQueryParams.ts +54 -0
- package/src/containers/Tablets/utils.ts +43 -0
- package/src/containers/Tenant/Healthcheck/Healthcheck.tsx +37 -4
- package/src/containers/Tenant/Healthcheck/components/HealthcheckDrawer.tsx +3 -0
- package/src/containers/Tenant/Healthcheck/components/HealthcheckIssue.tsx +17 -4
- package/src/containers/Tenant/Healthcheck/components/HealthcheckIssueDetails/ComputeLocation.tsx +13 -3
- package/src/containers/Tenant/Healthcheck/components/HealthcheckIssueDetails/NodeInfo.tsx +1 -0
- package/src/containers/Tenant/Healthcheck/components/HealthcheckIssueDetails/StorageLocation.tsx +3 -1
- package/src/containers/Tenant/Healthcheck/components/HealthcheckIssues.tsx +15 -2
- package/src/containers/Tenant/Healthcheck/i18n/en.json +2 -1
- package/src/containers/Tenant/Query/SavedQueries/SavedQueries.scss +2 -0
- package/src/routes.ts +8 -2
- package/src/services/api/viewer.ts +10 -2
- package/src/store/reducers/cluster/cluster.ts +2 -1
- package/src/store/reducers/healthcheckInfo/types.ts +1 -0
- package/src/store/reducers/healthcheckInfo/utils.ts +50 -10
- package/src/store/reducers/tenants/tenants.ts +55 -1
- package/src/store/reducers/tenants/types.ts +5 -0
- package/src/types/api/healthcheck.ts +8 -0
|
@@ -1,30 +1,39 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import {
|
|
4
|
+
Archive,
|
|
5
|
+
ArrowChevronRight,
|
|
6
|
+
Ban,
|
|
7
|
+
ChevronRight,
|
|
8
|
+
CirclePause,
|
|
9
|
+
LinkSlash,
|
|
10
|
+
MapPin,
|
|
11
|
+
} from '@gravity-ui/icons';
|
|
12
|
+
import {Flex, Icon, Label, Text} from '@gravity-ui/uikit';
|
|
5
13
|
import type {LabelProps} from '@gravity-ui/uikit';
|
|
6
14
|
|
|
15
|
+
import {EntityStatus} from '../../../../components/EntityStatus/EntityStatus';
|
|
7
16
|
import {LabelWithHelpMark} from '../../../../components/LabelWithHelpMark/LabelWithHelpMark';
|
|
17
|
+
import {Skeleton} from '../../../../components/Skeleton/Skeleton';
|
|
18
|
+
import {
|
|
19
|
+
healthcheckApi,
|
|
20
|
+
selectLeavesIssues,
|
|
21
|
+
} from '../../../../store/reducers/healthcheckInfo/healthcheckInfo';
|
|
8
22
|
import type {TBridgePile} from '../../../../types/api/cluster';
|
|
9
|
-
import {
|
|
23
|
+
import {BridgePileState} from '../../../../types/api/cluster';
|
|
10
24
|
import {cn} from '../../../../utils/cn';
|
|
11
25
|
import {EMPTY_DATA_PLACEHOLDER} from '../../../../utils/constants';
|
|
12
26
|
import {formatNumber} from '../../../../utils/dataFormatters/dataFormatters';
|
|
27
|
+
import {useAutoRefreshInterval, useTypedSelector} from '../../../../utils/hooks';
|
|
28
|
+
import {useClusterHealthcheckQueryParams} from '../../ClusterDrawerHealthcheck';
|
|
13
29
|
import i18n from '../../i18n';
|
|
14
30
|
|
|
31
|
+
import type {BridgePileHealthcheckTarget} from './bridgePileHealthcheck';
|
|
32
|
+
import {getBridgePileHealthcheck} from './bridgePileHealthcheck';
|
|
33
|
+
|
|
15
34
|
import './BridgeInfoTable.scss';
|
|
16
35
|
|
|
17
36
|
const b = cn('ydb-bridge-info-table');
|
|
18
|
-
const progressB = cn('ydb-bridge-info-table-progress');
|
|
19
|
-
const legendB = cn('ydb-bridge-info-table-legend');
|
|
20
|
-
|
|
21
|
-
type GroupStatusTheme = 'positive' | 'warning' | 'danger' | 'dark-complementary' | 'secondary';
|
|
22
|
-
|
|
23
|
-
interface PreparedGroupStatus {
|
|
24
|
-
label: string;
|
|
25
|
-
theme: GroupStatusTheme;
|
|
26
|
-
value: number;
|
|
27
|
-
}
|
|
28
37
|
|
|
29
38
|
interface BridgePileStateInfo {
|
|
30
39
|
label?: string;
|
|
@@ -39,25 +48,25 @@ function getBridgePileStateInfo(state?: BridgePileState): BridgePileStateInfo {
|
|
|
39
48
|
return {
|
|
40
49
|
label: i18n('value_bridge-state-primary'),
|
|
41
50
|
theme: 'normal',
|
|
51
|
+
help: i18n('context_bridge-pile-state-primary'),
|
|
42
52
|
};
|
|
43
53
|
case BridgePileState.PROMOTED:
|
|
44
54
|
return {
|
|
45
55
|
label: i18n('value_bridge-state-promoted'),
|
|
46
|
-
theme: '
|
|
56
|
+
theme: 'info',
|
|
47
57
|
icon: <Icon data={ArrowChevronRight} size={12} />,
|
|
48
58
|
help: i18n('context_bridge-pile-state-promoted'),
|
|
49
59
|
};
|
|
50
60
|
case BridgePileState.SYNCHRONIZED:
|
|
51
61
|
return {
|
|
52
62
|
label: i18n('value_bridge-state-synchronised'),
|
|
53
|
-
theme: '
|
|
54
|
-
icon: <Icon data={CircleCheck} size={12} />,
|
|
63
|
+
theme: 'normal',
|
|
55
64
|
help: i18n('context_bridge-pile-state-synchronised'),
|
|
56
65
|
};
|
|
57
66
|
case BridgePileState.NOT_SYNCHRONIZED:
|
|
58
67
|
return {
|
|
59
68
|
label: i18n('value_bridge-state-not-synchronised'),
|
|
60
|
-
theme: '
|
|
69
|
+
theme: 'warning',
|
|
61
70
|
icon: <Icon data={Ban} size={12} />,
|
|
62
71
|
help: i18n('context_bridge-pile-state-not-synchronised'),
|
|
63
72
|
};
|
|
@@ -71,7 +80,7 @@ function getBridgePileStateInfo(state?: BridgePileState): BridgePileStateInfo {
|
|
|
71
80
|
case BridgePileState.DISCONNECTED:
|
|
72
81
|
return {
|
|
73
82
|
label: i18n('value_bridge-state-disconnected'),
|
|
74
|
-
theme: '
|
|
83
|
+
theme: 'danger',
|
|
75
84
|
icon: <Icon data={LinkSlash} size={12} />,
|
|
76
85
|
help: i18n('context_bridge-pile-state-disconnected'),
|
|
77
86
|
};
|
|
@@ -88,182 +97,43 @@ function getBridgePileStateInfo(state?: BridgePileState): BridgePileStateInfo {
|
|
|
88
97
|
}
|
|
89
98
|
}
|
|
90
99
|
|
|
91
|
-
interface BridgePileGroupStatusInfo {
|
|
92
|
-
label: string;
|
|
93
|
-
theme: GroupStatusTheme;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function getNodesLabel(nodes?: number) {
|
|
97
|
-
if (nodes === undefined) {
|
|
98
|
-
return EMPTY_DATA_PLACEHOLDER;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
return i18n('value_nodes', {
|
|
102
|
-
count: nodes,
|
|
103
|
-
formattedCount: formatNumber(nodes),
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function getBridgePileGroupStatusInfo(status?: BridgePileGroupStatus): BridgePileGroupStatusInfo {
|
|
108
|
-
switch (status) {
|
|
109
|
-
case BridgePileGroupStatus.UNKNOWN:
|
|
110
|
-
return {
|
|
111
|
-
label: i18n('value_bridge-group-status-unknown'),
|
|
112
|
-
theme: 'secondary',
|
|
113
|
-
};
|
|
114
|
-
case BridgePileGroupStatus.FULL:
|
|
115
|
-
return {
|
|
116
|
-
label: i18n('value_bridge-group-status-full'),
|
|
117
|
-
theme: 'positive',
|
|
118
|
-
};
|
|
119
|
-
case BridgePileGroupStatus.PARTIAL:
|
|
120
|
-
return {
|
|
121
|
-
label: i18n('value_bridge-group-status-partial'),
|
|
122
|
-
theme: 'warning',
|
|
123
|
-
};
|
|
124
|
-
case BridgePileGroupStatus.DEGRADED:
|
|
125
|
-
return {
|
|
126
|
-
label: i18n('value_bridge-group-status-degraded'),
|
|
127
|
-
theme: 'danger',
|
|
128
|
-
};
|
|
129
|
-
case BridgePileGroupStatus.DISINTEGRATED:
|
|
130
|
-
return {
|
|
131
|
-
label: i18n('value_bridge-group-status-disintegrated'),
|
|
132
|
-
theme: 'dark-complementary',
|
|
133
|
-
};
|
|
134
|
-
default:
|
|
135
|
-
return {
|
|
136
|
-
label: EMPTY_DATA_PLACEHOLDER,
|
|
137
|
-
theme: 'secondary',
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function getBridgePileGroupStatus(status: string): BridgePileGroupStatus | undefined {
|
|
143
|
-
if (Object.values(BridgePileGroupStatus).includes(status as BridgePileGroupStatus)) {
|
|
144
|
-
return status as BridgePileGroupStatus;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
return undefined;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
function prepareGroupStatuses(groupStatuses?: TBridgePile['GroupStatuses']): PreparedGroupStatus[] {
|
|
151
|
-
return Object.entries(groupStatuses ?? {})
|
|
152
|
-
.map(([status, value]) => {
|
|
153
|
-
const groupStatus = getBridgePileGroupStatus(status);
|
|
154
|
-
const {label, theme} = groupStatus
|
|
155
|
-
? getBridgePileGroupStatusInfo(groupStatus)
|
|
156
|
-
: {label: status, theme: 'secondary' as const};
|
|
157
|
-
const numericValue = Number(value);
|
|
158
|
-
|
|
159
|
-
if (!Number.isFinite(numericValue) || numericValue <= 0) {
|
|
160
|
-
return undefined;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
return {
|
|
164
|
-
label,
|
|
165
|
-
theme,
|
|
166
|
-
value: numericValue,
|
|
167
|
-
};
|
|
168
|
-
})
|
|
169
|
-
.filter((status): status is PreparedGroupStatus => status !== undefined);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
interface SegmentedProgressBarProps {
|
|
173
|
-
statuses: PreparedGroupStatus[];
|
|
174
|
-
describedBy?: string;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function SegmentedProgressBar({statuses, describedBy}: SegmentedProgressBarProps) {
|
|
178
|
-
const total = statuses.reduce((sum, {value}) => sum + value, 0);
|
|
179
|
-
|
|
180
|
-
if (!Number.isFinite(total) || total <= 0) {
|
|
181
|
-
return (
|
|
182
|
-
<div
|
|
183
|
-
className={progressB({empty: true})}
|
|
184
|
-
role="img"
|
|
185
|
-
aria-label={i18n('context_group-statuses-empty')}
|
|
186
|
-
/>
|
|
187
|
-
);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
return (
|
|
191
|
-
<div
|
|
192
|
-
className={progressB()}
|
|
193
|
-
role="img"
|
|
194
|
-
aria-label={i18n('context_group-statuses')}
|
|
195
|
-
aria-describedby={describedBy}
|
|
196
|
-
>
|
|
197
|
-
{statuses.map(({label, theme, value}) => (
|
|
198
|
-
<div
|
|
199
|
-
key={`${label}-${value}`}
|
|
200
|
-
className={progressB('item', {theme})}
|
|
201
|
-
aria-hidden="true"
|
|
202
|
-
style={{
|
|
203
|
-
width: `${(value / total) * 100}%`,
|
|
204
|
-
}}
|
|
205
|
-
/>
|
|
206
|
-
))}
|
|
207
|
-
</div>
|
|
208
|
-
);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
interface GroupStatusesLegendProps {
|
|
212
|
-
statuses: PreparedGroupStatus[];
|
|
213
|
-
id?: string;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
function GroupStatusesLegend({statuses, id}: GroupStatusesLegendProps) {
|
|
217
|
-
if (!statuses.length) {
|
|
218
|
-
return (
|
|
219
|
-
<Text variant="caption-2" color="secondary">
|
|
220
|
-
{EMPTY_DATA_PLACEHOLDER}
|
|
221
|
-
</Text>
|
|
222
|
-
);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
return (
|
|
226
|
-
<ul id={id} className={legendB()} aria-label={i18n('context_group-statuses')}>
|
|
227
|
-
{statuses.map(({label, theme, value}) => (
|
|
228
|
-
<li key={`${label}-${value}`} className={legendB('item')}>
|
|
229
|
-
<span aria-hidden="true" className={legendB('dot', {theme})} />
|
|
230
|
-
<Text variant="caption-2" className={legendB('label')}>
|
|
231
|
-
{label}
|
|
232
|
-
</Text>
|
|
233
|
-
<Text variant="caption-2" color="secondary">
|
|
234
|
-
{formatNumber(value)}
|
|
235
|
-
</Text>
|
|
236
|
-
</li>
|
|
237
|
-
))}
|
|
238
|
-
</ul>
|
|
239
|
-
);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
100
|
interface BridgeInfoTableProps {
|
|
243
101
|
piles: TBridgePile[];
|
|
102
|
+
database?: string;
|
|
103
|
+
clusterName?: string;
|
|
244
104
|
}
|
|
245
105
|
|
|
246
106
|
interface BridgePileCardProps {
|
|
247
107
|
pile: TBridgePile;
|
|
108
|
+
current: boolean;
|
|
109
|
+
healthcheckAvailable: boolean;
|
|
110
|
+
healthcheckSupportsPiles: boolean;
|
|
111
|
+
healthcheckLoading: boolean;
|
|
112
|
+
leavesIssues: ReturnType<typeof selectLeavesIssues>;
|
|
113
|
+
onHealthcheckClick: (target: BridgePileHealthcheckTarget) => void;
|
|
248
114
|
}
|
|
249
115
|
|
|
250
|
-
const BridgePileCard = React.memo(function BridgePileCard({
|
|
251
|
-
|
|
252
|
-
|
|
116
|
+
const BridgePileCard = React.memo(function BridgePileCard({
|
|
117
|
+
pile,
|
|
118
|
+
current,
|
|
119
|
+
healthcheckAvailable,
|
|
120
|
+
healthcheckSupportsPiles,
|
|
121
|
+
healthcheckLoading,
|
|
122
|
+
leavesIssues,
|
|
123
|
+
onHealthcheckClick,
|
|
124
|
+
}: BridgePileCardProps) {
|
|
253
125
|
const stateStatus = React.useMemo(() => {
|
|
254
126
|
const {label, theme, icon, help} = getBridgePileStateInfo(pile.State);
|
|
255
127
|
if (!label) {
|
|
256
128
|
return null;
|
|
257
129
|
}
|
|
258
130
|
|
|
259
|
-
const showStateHelp = Boolean(help);
|
|
260
|
-
|
|
261
131
|
return (
|
|
262
132
|
<LabelWithHelpMark
|
|
263
133
|
theme={theme}
|
|
264
134
|
size="xs"
|
|
265
135
|
icon={icon}
|
|
266
|
-
className={b('state-label'
|
|
136
|
+
className={b('state-label')}
|
|
267
137
|
contentGap={2}
|
|
268
138
|
help={help}
|
|
269
139
|
helpMarkProps={{
|
|
@@ -279,45 +149,131 @@ const BridgePileCard = React.memo(function BridgePileCard({pile}: BridgePileCard
|
|
|
279
149
|
);
|
|
280
150
|
}, [pile.State]);
|
|
281
151
|
|
|
282
|
-
const
|
|
283
|
-
() =>
|
|
284
|
-
|
|
152
|
+
const pileHealthcheck = React.useMemo(
|
|
153
|
+
() =>
|
|
154
|
+
getBridgePileHealthcheck(
|
|
155
|
+
pile.Name,
|
|
156
|
+
leavesIssues,
|
|
157
|
+
healthcheckAvailable,
|
|
158
|
+
healthcheckSupportsPiles,
|
|
159
|
+
),
|
|
160
|
+
[healthcheckAvailable, healthcheckSupportsPiles, leavesIssues, pile.Name],
|
|
285
161
|
);
|
|
286
|
-
|
|
162
|
+
|
|
163
|
+
const pileName = pile.Name?.trim() || EMPTY_DATA_PLACEHOLDER;
|
|
164
|
+
const nodes = pile.Nodes === undefined ? EMPTY_DATA_PLACEHOLDER : formatNumber(pile.Nodes);
|
|
165
|
+
const handleHealthcheckClick = () => {
|
|
166
|
+
if (pileHealthcheck.target) {
|
|
167
|
+
onHealthcheckClick(pileHealthcheck.target);
|
|
168
|
+
}
|
|
169
|
+
};
|
|
287
170
|
|
|
288
171
|
return (
|
|
289
172
|
<Flex direction="column" gap={2} className={b('pile')}>
|
|
290
|
-
<Flex justifyContent="space-between" alignItems="flex-start" gap={
|
|
291
|
-
<
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
<
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
173
|
+
<Flex justifyContent="space-between" alignItems="flex-start" gap={2} width="100%">
|
|
174
|
+
<Text variant="subheader-1" ellipsis className={b('pile-name')}>
|
|
175
|
+
{pileName}
|
|
176
|
+
</Text>
|
|
177
|
+
{current ? (
|
|
178
|
+
<Label
|
|
179
|
+
theme="clear"
|
|
180
|
+
size="xs"
|
|
181
|
+
icon={<Icon data={MapPin} size={12} />}
|
|
182
|
+
className={b('current-pile')}
|
|
183
|
+
>
|
|
184
|
+
{i18n('label_you-are-here')}
|
|
185
|
+
</Label>
|
|
186
|
+
) : null}
|
|
187
|
+
</Flex>
|
|
188
|
+
<Flex gap={2} alignItems="center" wrap>
|
|
299
189
|
{stateStatus}
|
|
190
|
+
{healthcheckLoading ? (
|
|
191
|
+
<Skeleton className={b('healthcheck-skeleton')} />
|
|
192
|
+
) : (
|
|
193
|
+
<EntityStatus.Label
|
|
194
|
+
status={pileHealthcheck.status}
|
|
195
|
+
size="xs"
|
|
196
|
+
className={b('healthcheck-status')}
|
|
197
|
+
onClick={pileHealthcheck.target ? handleHealthcheckClick : undefined}
|
|
198
|
+
endContent={
|
|
199
|
+
pileHealthcheck.target ? (
|
|
200
|
+
<Icon data={ChevronRight} size={12} />
|
|
201
|
+
) : undefined
|
|
202
|
+
}
|
|
203
|
+
qa="bridge-pile-healthcheck"
|
|
204
|
+
>
|
|
205
|
+
{pileHealthcheck.target ? (
|
|
206
|
+
<span className={b('healthcheck-accessible-prefix')}>
|
|
207
|
+
{i18n('label_bridge-pile-health-status-prefix', {pileName})}
|
|
208
|
+
</span>
|
|
209
|
+
) : null}
|
|
210
|
+
</EntityStatus.Label>
|
|
211
|
+
)}
|
|
300
212
|
</Flex>
|
|
301
|
-
<Flex
|
|
302
|
-
<
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
<GroupStatusesLegend statuses={groupStatuses} id={groupStatusesDescriptionId} />
|
|
213
|
+
<Flex gap={1} alignItems="center" className={b('nodes')}>
|
|
214
|
+
<Icon data={Archive} size={14} />
|
|
215
|
+
<Text variant="body-1">
|
|
216
|
+
{i18n('field_nodes')}: {nodes}
|
|
217
|
+
</Text>
|
|
307
218
|
</Flex>
|
|
308
219
|
</Flex>
|
|
309
220
|
);
|
|
310
221
|
});
|
|
311
222
|
|
|
312
|
-
export const BridgeInfoTable = React.memo(function BridgeInfoTable({
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
223
|
+
export const BridgeInfoTable = React.memo(function BridgeInfoTable({
|
|
224
|
+
piles,
|
|
225
|
+
database,
|
|
226
|
+
clusterName,
|
|
227
|
+
}: BridgeInfoTableProps) {
|
|
228
|
+
const [autoRefreshInterval] = useAutoRefreshInterval();
|
|
229
|
+
const {handleOpenHealthcheckIssue} = useClusterHealthcheckQueryParams();
|
|
230
|
+
const {
|
|
231
|
+
currentData: healthcheckData,
|
|
232
|
+
isFetching: healthcheckFetching,
|
|
233
|
+
error: healthcheckError,
|
|
234
|
+
} = healthcheckApi.useGetHealthcheckInfoQuery(
|
|
235
|
+
{database: database ?? '', clusterName},
|
|
236
|
+
{
|
|
237
|
+
pollingInterval: autoRefreshInterval,
|
|
238
|
+
skip: !database,
|
|
239
|
+
},
|
|
316
240
|
);
|
|
241
|
+
const leavesIssues = useTypedSelector((state) =>
|
|
242
|
+
selectLeavesIssues(state, database ?? '', clusterName),
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
const healthcheckAvailable = healthcheckData !== undefined && healthcheckError === undefined;
|
|
246
|
+
const healthcheckLoading = healthcheckData === undefined && healthcheckFetching;
|
|
247
|
+
const currentPileName = healthcheckData?.location?.pile?.name;
|
|
248
|
+
const healthcheckSupportsPiles = healthcheckData?.location?.pile !== undefined;
|
|
249
|
+
|
|
250
|
+
const sortedPiles = React.useMemo(() => {
|
|
251
|
+
return piles
|
|
252
|
+
.map((pile, index) => ({pile, index}))
|
|
253
|
+
.sort((left, right) => {
|
|
254
|
+
const leftPrimary = left.pile.State === BridgePileState.PRIMARY;
|
|
255
|
+
const rightPrimary = right.pile.State === BridgePileState.PRIMARY;
|
|
256
|
+
if (leftPrimary !== rightPrimary) {
|
|
257
|
+
return leftPrimary ? -1 : 1;
|
|
258
|
+
}
|
|
259
|
+
return left.index - right.index;
|
|
260
|
+
});
|
|
261
|
+
}, [piles]);
|
|
317
262
|
|
|
318
263
|
return (
|
|
319
|
-
<Flex
|
|
320
|
-
{
|
|
264
|
+
<Flex wrap className={b()}>
|
|
265
|
+
{sortedPiles.map(({pile, index}) => (
|
|
266
|
+
<BridgePileCard
|
|
267
|
+
key={pile.PileId ?? pile.Name ?? index}
|
|
268
|
+
pile={pile}
|
|
269
|
+
current={Boolean(currentPileName?.trim() && pile.Name === currentPileName)}
|
|
270
|
+
healthcheckAvailable={healthcheckAvailable}
|
|
271
|
+
healthcheckSupportsPiles={healthcheckSupportsPiles}
|
|
272
|
+
healthcheckLoading={healthcheckLoading}
|
|
273
|
+
leavesIssues={leavesIssues}
|
|
274
|
+
onHealthcheckClick={handleOpenHealthcheckIssue}
|
|
275
|
+
/>
|
|
276
|
+
))}
|
|
321
277
|
</Flex>
|
|
322
278
|
);
|
|
323
279
|
});
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import type {IssuesTree} from '../../../../store/reducers/healthcheckInfo/types';
|
|
2
|
+
import {
|
|
3
|
+
getHealthcheckIssuePileNames,
|
|
4
|
+
hcStatusToColorFlag,
|
|
5
|
+
} from '../../../../store/reducers/healthcheckInfo/utils';
|
|
6
|
+
import {EFlag} from '../../../../types/api/enums';
|
|
7
|
+
import {StatusFlag} from '../../../../types/api/healthcheck';
|
|
8
|
+
|
|
9
|
+
export interface BridgePileHealthcheckTarget {
|
|
10
|
+
issueId: string;
|
|
11
|
+
leafIssueId: string;
|
|
12
|
+
view: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface BridgePileHealthcheck {
|
|
16
|
+
status: EFlag;
|
|
17
|
+
target?: BridgePileHealthcheckTarget;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface Candidate {
|
|
21
|
+
issue: IssuesTree;
|
|
22
|
+
leaf: IssuesTree;
|
|
23
|
+
level: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const STATUS_PRIORITY: Record<StatusFlag, number> = {
|
|
27
|
+
[StatusFlag.UNSPECIFIED]: 0,
|
|
28
|
+
[StatusFlag.GREY]: 0,
|
|
29
|
+
[StatusFlag.GREEN]: 1,
|
|
30
|
+
[StatusFlag.BLUE]: 2,
|
|
31
|
+
[StatusFlag.YELLOW]: 3,
|
|
32
|
+
[StatusFlag.ORANGE]: 4,
|
|
33
|
+
[StatusFlag.RED]: 5,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
function normalizeStatus(status: unknown): StatusFlag {
|
|
37
|
+
return typeof status === 'string' && Object.hasOwn(STATUS_PRIORITY, status)
|
|
38
|
+
? (status as StatusFlag)
|
|
39
|
+
: StatusFlag.UNSPECIFIED;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function getBranch(leaf: IssuesTree): IssuesTree[] {
|
|
43
|
+
const branch: IssuesTree[] = [];
|
|
44
|
+
let current: IssuesTree | undefined = leaf;
|
|
45
|
+
|
|
46
|
+
while (current) {
|
|
47
|
+
branch.push(current);
|
|
48
|
+
current = current.parent;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return branch.reverse();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function getBranchCandidate(leaf: IssuesTree, pileName: string): Candidate | undefined {
|
|
55
|
+
const branch = getBranch(leaf);
|
|
56
|
+
|
|
57
|
+
for (const [branchIndex, issue] of branch.entries()) {
|
|
58
|
+
if (!getHealthcheckIssuePileNames(issue).includes(pileName)) {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Backend guarantees increasing levels; add validation here if that can change.
|
|
63
|
+
const level = issue.level ?? branchIndex;
|
|
64
|
+
return {issue, leaf, level};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function compareSourceOrder(left?: number[], right?: number[]): number {
|
|
71
|
+
if (!left) {
|
|
72
|
+
return right ? 1 : 0;
|
|
73
|
+
}
|
|
74
|
+
if (!right) {
|
|
75
|
+
return -1;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const commonLength = Math.min(left.length, right.length);
|
|
79
|
+
for (let index = 0; index < commonLength; index += 1) {
|
|
80
|
+
if (left[index] !== right[index]) {
|
|
81
|
+
return left[index] - right[index];
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return left.length - right.length;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function isWorseCandidate(candidate: Candidate, current: Candidate): boolean {
|
|
89
|
+
const candidatePriority = STATUS_PRIORITY[normalizeStatus(candidate.issue.status)];
|
|
90
|
+
const currentPriority = STATUS_PRIORITY[normalizeStatus(current.issue.status)];
|
|
91
|
+
|
|
92
|
+
if (candidatePriority !== currentPriority) {
|
|
93
|
+
return candidatePriority > currentPriority;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (candidate.level !== current.level) {
|
|
97
|
+
return candidate.level < current.level;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const issueOrder = compareSourceOrder(
|
|
101
|
+
candidate.issue.sourceOrderPath,
|
|
102
|
+
current.issue.sourceOrderPath,
|
|
103
|
+
);
|
|
104
|
+
if (issueOrder !== 0) {
|
|
105
|
+
return issueOrder < 0;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return compareSourceOrder(candidate.leaf.sourceOrderPath, current.leaf.sourceOrderPath) < 0;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function getBridgePileHealthcheck(
|
|
112
|
+
pileName: string | undefined,
|
|
113
|
+
leavesIssues: IssuesTree[],
|
|
114
|
+
healthcheckAvailable: boolean,
|
|
115
|
+
healthcheckSupportsPiles = healthcheckAvailable,
|
|
116
|
+
): BridgePileHealthcheck {
|
|
117
|
+
if (!healthcheckAvailable || !pileName?.trim()) {
|
|
118
|
+
return {status: EFlag.Grey};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
let selectedCandidate: Candidate | undefined;
|
|
122
|
+
for (const leaf of leavesIssues) {
|
|
123
|
+
const candidate = getBranchCandidate(leaf, pileName);
|
|
124
|
+
if (candidate && (!selectedCandidate || isWorseCandidate(candidate, selectedCandidate))) {
|
|
125
|
+
selectedCandidate = candidate;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (!selectedCandidate) {
|
|
130
|
+
return {status: healthcheckSupportsPiles ? EFlag.Green : EFlag.Grey};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const issueStatus = normalizeStatus(selectedCandidate.issue.status);
|
|
134
|
+
return {
|
|
135
|
+
status: hcStatusToColorFlag[issueStatus],
|
|
136
|
+
target: {
|
|
137
|
+
issueId: selectedCandidate.issue.id,
|
|
138
|
+
leafIssueId: selectedCandidate.leaf.id,
|
|
139
|
+
view: selectedCandidate.leaf.categoryForUI,
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
}
|
|
@@ -24,16 +24,19 @@
|
|
|
24
24
|
"value_bridge-state-suspended": "Suspended",
|
|
25
25
|
"value_bridge-state-disconnected": "Disconnected",
|
|
26
26
|
"value_bridge-state-unspecified": "Unspecified",
|
|
27
|
+
"label_you-are-here": "You are here",
|
|
28
|
+
"label_bridge-pile-health-status-prefix": "Health status for pile {{pileName}}:",
|
|
27
29
|
"value_bridge-group-status-unknown": "Unknown",
|
|
28
30
|
"value_bridge-group-status-full": "Full",
|
|
29
31
|
"value_bridge-group-status-partial": "Partial",
|
|
30
32
|
"value_bridge-group-status-degraded": "Degraded",
|
|
31
33
|
"value_bridge-group-status-disintegrated": "Disintegrated",
|
|
32
|
-
"context_bridge-pile-state-promoted": "Pile that is being promoted from
|
|
33
|
-
"context_bridge-pile-state-
|
|
34
|
-
"context_bridge-pile-state-
|
|
35
|
-
"context_bridge-pile-state-
|
|
36
|
-
"context_bridge-pile-state-
|
|
34
|
+
"context_bridge-pile-state-promoted": "Pile that is being promoted from Synchronized to\u00a0Primary",
|
|
35
|
+
"context_bridge-pile-state-primary": "Primary pile that currently participates in operation execution.",
|
|
36
|
+
"context_bridge-pile-state-synchronised": "Secondary pile where all storage groups are fully synchronized with Primary",
|
|
37
|
+
"context_bridge-pile-state-not-synchronised": "Storage groups are not yet fully synchronized with Primary",
|
|
38
|
+
"context_bridge-pile-state-suspended": "Pile is being transitioned to\u00a0Disconnected state",
|
|
39
|
+
"context_bridge-pile-state-disconnected": "Pile is disconnected from Primary and does not participate in operation execution",
|
|
37
40
|
"context_group-statuses": "Storage group statuses",
|
|
38
41
|
"context_group-statuses-empty": "No storage group status data",
|
|
39
42
|
"context_cpu": "CPU load",
|
|
@@ -5,7 +5,7 @@ import {ActionTooltip, Button, Icon} from '@gravity-ui/uikit';
|
|
|
5
5
|
|
|
6
6
|
import {getConnectToDBDialog} from '../../components/ConnectToDB/ConnectToDBDialog';
|
|
7
7
|
import type {PreparedTenant} from '../../store/reducers/tenants/types';
|
|
8
|
-
import type {ClusterLinkWithTitle} from '../../types/additionalProps';
|
|
8
|
+
import type {AdditionalTenantsProps, ClusterLinkWithTitle} from '../../types/additionalProps';
|
|
9
9
|
import {CLUSTER_LINK_CONTEXT} from '../../utils/clusterLinks/clusterLinkConstants';
|
|
10
10
|
|
|
11
11
|
import {DeveloperUIControl} from './GlobalRightControls';
|
|
@@ -19,6 +19,8 @@ interface DatabaseRightControlsProps {
|
|
|
19
19
|
databaseData?: PreparedTenant;
|
|
20
20
|
isDatabaseDataLoading: boolean;
|
|
21
21
|
isV2NavigationEnabled: boolean;
|
|
22
|
+
isViewerUser?: boolean;
|
|
23
|
+
prepareTenantBackend?: AdditionalTenantsProps['prepareTenantBackend'];
|
|
22
24
|
showDeveloperUI: boolean;
|
|
23
25
|
databaseLinks: ClusterLinkWithTitle[];
|
|
24
26
|
}
|
|
@@ -29,6 +31,8 @@ export function DatabaseRightControls({
|
|
|
29
31
|
databaseData,
|
|
30
32
|
isDatabaseDataLoading,
|
|
31
33
|
isV2NavigationEnabled,
|
|
34
|
+
isViewerUser,
|
|
35
|
+
prepareTenantBackend,
|
|
32
36
|
showDeveloperUI,
|
|
33
37
|
databaseLinks,
|
|
34
38
|
}: DatabaseRightControlsProps) {
|
|
@@ -62,6 +66,8 @@ export function DatabaseRightControls({
|
|
|
62
66
|
databaseData={databaseData}
|
|
63
67
|
isDatabaseDataLoading={isDatabaseDataLoading}
|
|
64
68
|
isV2NavigationEnabled={isV2NavigationEnabled}
|
|
69
|
+
isViewerUser={isViewerUser}
|
|
70
|
+
prepareTenantBackend={prepareTenantBackend}
|
|
65
71
|
databaseLinks={actionDatabaseLinks}
|
|
66
72
|
/>
|
|
67
73
|
</React.Fragment>
|
|
@@ -38,6 +38,7 @@ export function Header() {
|
|
|
38
38
|
isDatabaseDataLoading,
|
|
39
39
|
clusterLinks,
|
|
40
40
|
databaseLinks,
|
|
41
|
+
prepareTenantBackend,
|
|
41
42
|
} = useHeaderData({
|
|
42
43
|
metaCapabilitiesLoaded,
|
|
43
44
|
database,
|
|
@@ -89,6 +90,8 @@ export function Header() {
|
|
|
89
90
|
isDatabaseDataLoading={isDatabaseDataLoading}
|
|
90
91
|
isAddClusterAvailable={isAddClusterAvailable}
|
|
91
92
|
isV2NavigationEnabled={isV2NavigationEnabled}
|
|
93
|
+
isViewerUser={isViewerUser}
|
|
94
|
+
prepareTenantBackend={prepareTenantBackend}
|
|
92
95
|
clusterLinks={clusterLinks}
|
|
93
96
|
databaseLinks={databaseLinks}
|
|
94
97
|
handleEditCluster={handleEditCluster}
|