ydb-embedded-ui 4.3.0 → 4.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/CHANGELOG.md +17 -0
- package/dist/containers/App/Content.js +2 -8
- package/dist/containers/AsideNavigation/AsideNavigation.tsx +1 -1
- package/dist/containers/Cluster/Cluster.scss +4 -0
- package/dist/containers/Cluster/Cluster.tsx +14 -8
- package/dist/{components → containers}/ClusterInfo/ClusterInfo.scss +39 -0
- package/dist/containers/ClusterInfo/ClusterInfo.tsx +207 -0
- package/dist/containers/ClusterInfo/utils.ts +13 -0
- package/dist/containers/Header/Header.tsx +9 -16
- package/dist/containers/Nodes/Nodes.tsx +4 -6
- package/dist/containers/Storage/DiskStateProgressBar/DiskStateProgressBar.tsx +2 -3
- package/dist/containers/Tenant/Diagnostics/Partitions/Headers/Headers.tsx +4 -4
- package/dist/containers/Tenant/Diagnostics/Partitions/Partitions.scss +4 -0
- package/dist/containers/Tenant/Diagnostics/Partitions/Partitions.tsx +2 -2
- package/dist/containers/Tenant/Diagnostics/Partitions/PartitionsControls/PartitionsControls.tsx +20 -26
- package/dist/containers/Tenant/Diagnostics/Partitions/i18n/en.json +1 -1
- package/dist/containers/Tenant/Diagnostics/Partitions/i18n/ru.json +1 -1
- package/dist/containers/UserSettings/Setting.tsx +82 -0
- package/dist/containers/UserSettings/UserSettings.tsx +61 -99
- package/dist/containers/Versions/GroupedNodesTree/GroupedNodesTree.scss +59 -0
- package/dist/containers/Versions/GroupedNodesTree/GroupedNodesTree.tsx +98 -0
- package/dist/containers/Versions/NodesTable/NodesTable.tsx +150 -0
- package/dist/containers/Versions/NodesTreeTitle/NodesTreeTitle.scss +55 -0
- package/dist/containers/Versions/NodesTreeTitle/NodesTreeTitle.tsx +62 -0
- package/dist/containers/Versions/Versions.scss +32 -0
- package/dist/containers/Versions/Versions.tsx +121 -0
- package/dist/containers/Versions/groupNodes.ts +124 -0
- package/dist/containers/Versions/types.ts +16 -0
- package/dist/routes.ts +0 -6
- package/dist/services/api.ts +3 -0
- package/dist/store/reducers/cluster/cluster.ts +4 -0
- package/dist/store/reducers/cluster/types.ts +3 -2
- package/dist/store/reducers/clusterNodes/clusterNodes.tsx +64 -0
- package/dist/store/reducers/clusterNodes/types.ts +22 -0
- package/dist/store/reducers/index.ts +2 -8
- package/dist/store/reducers/partitions/partitions.ts +2 -2
- package/dist/store/reducers/partitions/types.ts +1 -1
- package/dist/types/additionalProps.ts +5 -0
- package/dist/types/versions.ts +9 -0
- package/dist/utils/constants.ts +0 -11
- package/dist/utils/hooks/useSetting.ts +5 -3
- package/dist/utils/versions/getVersionsColors.ts +98 -0
- package/dist/utils/versions/index.ts +3 -0
- package/dist/utils/versions/parseNodesToVersionsValues.ts +28 -0
- package/dist/utils/versions/parseVersion.ts +23 -0
- package/package.json +1 -1
- package/dist/components/ClusterInfo/ClusterInfo.tsx +0 -239
- package/dist/components/FullGroupViewer/FullGroupViewer.js +0 -147
- package/dist/components/FullGroupViewer/FullGroupViewer.scss +0 -35
- package/dist/components/GroupTreeViewer/GroupTreeViewer.js +0 -87
- package/dist/components/GroupTreeViewer/GroupTreeViewer.scss +0 -16
- package/dist/components/GroupViewer/GroupViewer.js +0 -100
- package/dist/components/GroupViewer/GroupViewer.scss +0 -45
- package/dist/components/PDiskViewer/PDiskViewer.js +0 -79
- package/dist/components/PDiskViewer/PDiskViewer.scss +0 -46
- package/dist/components/TabletsViewer/TabletsViewer.js +0 -44
- package/dist/components/TabletsViewer/TabletsViewer.scss +0 -40
- package/dist/components/VerticalBars/VerticalBars.scss +0 -15
- package/dist/components/VerticalBars/VerticalBars.tsx +0 -38
- package/dist/components/VerticalBars/index.ts +0 -1
- package/dist/containers/Group/Group.js +0 -97
- package/dist/containers/Group/Group.scss +0 -6
- package/dist/containers/Header/Host/Host.js +0 -66
- package/dist/containers/Header/Host/Host.scss +0 -50
- package/dist/containers/Pdisk/Pdisk.js +0 -156
- package/dist/containers/Pdisk/Pdisk.scss +0 -42
- package/dist/containers/Pool/Pool.js +0 -170
- package/dist/containers/Pool/Pool.scss +0 -35
- package/dist/containers/Vdisk/Vdisk.js +0 -158
- package/dist/containers/Vdisk/Vdisk.scss +0 -42
- package/dist/containers/VdiskPdiskNode/VdiskPdiskNode.js +0 -526
- package/dist/containers/VdiskPdiskNode/VdiskPdiskNode.scss +0 -60
- package/dist/store/reducers/group.js +0 -49
- package/dist/store/reducers/pdisk.js +0 -51
- package/dist/store/reducers/pool.js +0 -42
- package/dist/store/reducers/vdisk.js +0 -49
@@ -0,0 +1,98 @@
|
|
1
|
+
import type {VersionToColorMap, VersionsMap} from '../../types/versions';
|
2
|
+
|
3
|
+
import {getMajorVersion, getMinorVersion} from './parseVersion';
|
4
|
+
|
5
|
+
export const hashCode = (s: string) => {
|
6
|
+
return s.split('').reduce((a, b) => {
|
7
|
+
const num = (a << 5) - a + b.charCodeAt(0); // eslint-disable-line
|
8
|
+
return num & num; // eslint-disable-line
|
9
|
+
}, 0);
|
10
|
+
};
|
11
|
+
|
12
|
+
// 11 distinct colors from https://mokole.com/palette.html
|
13
|
+
export const COLORS = [
|
14
|
+
'#008000', // green
|
15
|
+
'#4169e1', // royalblue
|
16
|
+
'#ffd700', // gold
|
17
|
+
'#ff8c00', // darkorange
|
18
|
+
'#808000', // olive
|
19
|
+
'#e9967a', // darksalmon
|
20
|
+
'#ff1493', // deeppink
|
21
|
+
'#00bfff', // deepskyblue
|
22
|
+
'#da70d6', // orchid
|
23
|
+
'#3cb371', // mediumseagreen
|
24
|
+
'#b22222', // firebrick
|
25
|
+
];
|
26
|
+
|
27
|
+
export const GRAY_COLOR = '#bfbfbf'; // --yc-color-base-neutral-hover
|
28
|
+
|
29
|
+
export const getVersionsMap = (versions: string[], initialMap: VersionsMap = new Map()) => {
|
30
|
+
versions.forEach((version) => {
|
31
|
+
const majorVersion = getMajorVersion(version);
|
32
|
+
const minorVersion = getMinorVersion(version);
|
33
|
+
if (!initialMap.has(majorVersion)) {
|
34
|
+
initialMap.set(majorVersion, new Set());
|
35
|
+
}
|
36
|
+
initialMap.get(majorVersion)?.add(minorVersion);
|
37
|
+
});
|
38
|
+
return initialMap;
|
39
|
+
};
|
40
|
+
|
41
|
+
export const getVersionToColorMap = (versionsMap: VersionsMap) => {
|
42
|
+
const clustersVersions = Array.from(versionsMap.keys()).map((version) => {
|
43
|
+
return {
|
44
|
+
version,
|
45
|
+
hash: hashCode(version),
|
46
|
+
};
|
47
|
+
});
|
48
|
+
|
49
|
+
const versionToColor: VersionToColorMap = new Map();
|
50
|
+
// not every version is colored, therefore iteration index can't be used consistently
|
51
|
+
// init with the colors length to put increment right after condition for better readability
|
52
|
+
let currentColorIndex = COLORS.length - 1;
|
53
|
+
|
54
|
+
clustersVersions
|
55
|
+
// ascending by version name, just for consistency
|
56
|
+
// sorting only impacts color choose for a version
|
57
|
+
.sort((a, b) => a.hash - b.hash)
|
58
|
+
.forEach((item) => {
|
59
|
+
if (/^(\w+-)?stable/.test(item.version)) {
|
60
|
+
currentColorIndex = (currentColorIndex + 1) % COLORS.length;
|
61
|
+
|
62
|
+
versionToColor.set(item.version, COLORS[currentColorIndex]);
|
63
|
+
|
64
|
+
const minors = Array.from(versionsMap.get(item.version) || [])
|
65
|
+
.filter((v) => v !== item.version)
|
66
|
+
.map((v) => {
|
67
|
+
return {
|
68
|
+
version: v,
|
69
|
+
hash: hashCode(v),
|
70
|
+
};
|
71
|
+
});
|
72
|
+
|
73
|
+
const minorQuantity = minors.length;
|
74
|
+
|
75
|
+
minors
|
76
|
+
// descending by version name: newer versions come first,
|
77
|
+
// so the newer version gets the brighter color
|
78
|
+
.sort((a, b) => b.hash - a.hash)
|
79
|
+
.forEach((minor, minorIndex) => {
|
80
|
+
const majorColor = COLORS[currentColorIndex];
|
81
|
+
const opacityPercent = Math.max(
|
82
|
+
100 - minorIndex * (100 / minorQuantity),
|
83
|
+
20,
|
84
|
+
);
|
85
|
+
const hexOpacity = Math.round((opacityPercent * 255) / 100).toString(16);
|
86
|
+
const versionColor = `${majorColor}${hexOpacity}`;
|
87
|
+
versionToColor.set(minor.version, versionColor);
|
88
|
+
});
|
89
|
+
} else {
|
90
|
+
versionToColor.set(item.version, GRAY_COLOR);
|
91
|
+
}
|
92
|
+
});
|
93
|
+
return versionToColor;
|
94
|
+
};
|
95
|
+
|
96
|
+
export const parseVersionsToVersionToColorMap = (versions: string[] = []) => {
|
97
|
+
return getVersionToColorMap(getVersionsMap(versions));
|
98
|
+
};
|
@@ -0,0 +1,28 @@
|
|
1
|
+
import type {TSystemStateInfo} from '../../types/api/nodes';
|
2
|
+
import type {VersionToColorMap, VersionValue} from '../../types/versions';
|
3
|
+
import {getMinorVersion} from './parseVersion';
|
4
|
+
|
5
|
+
export const parseNodesToVersionsValues = (
|
6
|
+
nodes: TSystemStateInfo[] = [],
|
7
|
+
versionsToColor?: VersionToColorMap,
|
8
|
+
): VersionValue[] => {
|
9
|
+
const versionsCount = nodes.reduce<Record<string, number>>((acc, node) => {
|
10
|
+
if (node.Version) {
|
11
|
+
if (acc[node.Version]) {
|
12
|
+
acc[node.Version] = acc[node.Version] + 1;
|
13
|
+
} else {
|
14
|
+
acc[node.Version] = 1;
|
15
|
+
}
|
16
|
+
}
|
17
|
+
return acc;
|
18
|
+
}, {});
|
19
|
+
|
20
|
+
return Object.keys(versionsCount).map((version) => {
|
21
|
+
return {
|
22
|
+
title: version,
|
23
|
+
version: version,
|
24
|
+
color: versionsToColor?.get(getMinorVersion(version)),
|
25
|
+
value: (versionsCount[version] / nodes.length) * 100,
|
26
|
+
};
|
27
|
+
});
|
28
|
+
};
|
@@ -0,0 +1,23 @@
|
|
1
|
+
export const getMinorVersion = (version: string) => {
|
2
|
+
const regexp = /\d{1,}-\d{1,}(-\d){0,}(-hotfix-\d{1,}(-\d{1,})?)?\.[0-9a-zA-Z]+$/;
|
3
|
+
|
4
|
+
let result = version;
|
5
|
+
|
6
|
+
if (regexp.test(version)) {
|
7
|
+
result = result.replace(/(-hotfix-\d{1,}(-\d{1,})?)?\.[0-9a-zA-Z]+$/, ''); // stable-19-2-18.bfa368f -> stable-19-2-18
|
8
|
+
}
|
9
|
+
|
10
|
+
const buildVersionRegexp = /\d{1,}-\d{1,}-\d{1,}-\d{1,}$/;
|
11
|
+
if (buildVersionRegexp.test(version)) {
|
12
|
+
result = result.replace(/-\d{1,}$/, ''); // stable-19-2-18-1 -> stable-19-2-18
|
13
|
+
}
|
14
|
+
|
15
|
+
return result;
|
16
|
+
};
|
17
|
+
|
18
|
+
export const getMajorVersion = (version: string) => {
|
19
|
+
const minorVersion = getMinorVersion(version);
|
20
|
+
const regexp = /\d{1,}-\d{1,}-\d{1,}/; // to check versions that have minor part
|
21
|
+
|
22
|
+
return regexp.test(minorVersion) ? minorVersion.replace(/-\d{1,}$/, '') : minorVersion;
|
23
|
+
};
|
package/package.json
CHANGED
@@ -1,239 +0,0 @@
|
|
1
|
-
import React, {ReactNode} from 'react';
|
2
|
-
import cn from 'bem-cn-lite';
|
3
|
-
import {connect} from 'react-redux';
|
4
|
-
import {Link, Loader} from '@gravity-ui/uikit';
|
5
|
-
|
6
|
-
//@ts-ignore
|
7
|
-
import EntityStatus from '../EntityStatus/EntityStatus';
|
8
|
-
//@ts-ignore
|
9
|
-
import ProgressViewer from '../ProgressViewer/ProgressViewer';
|
10
|
-
//@ts-ignore
|
11
|
-
import InfoViewer from '../InfoViewer/InfoViewer';
|
12
|
-
import {Tags} from '../Tags';
|
13
|
-
import {Tablet} from '../Tablet';
|
14
|
-
|
15
|
-
//@ts-ignore
|
16
|
-
import {hideTooltip, showTooltip} from '../../store/reducers/tooltip';
|
17
|
-
//@ts-ignore
|
18
|
-
import {getClusterInfo} from '../../store/reducers/cluster/cluster';
|
19
|
-
//@ts-ignore
|
20
|
-
import {clusterName, backend, customBackend} from '../../store';
|
21
|
-
|
22
|
-
//@ts-ignore
|
23
|
-
import {formatStorageValues} from '../../utils';
|
24
|
-
//@ts-ignore
|
25
|
-
import {TxAllocator} from '../../utils/constants';
|
26
|
-
import {AutoFetcher} from '../../utils/autofetcher';
|
27
|
-
import {Icon} from '../Icon';
|
28
|
-
import {setHeader} from '../../store/reducers/header';
|
29
|
-
import routes, {CLUSTER_PAGES, createHref} from '../../routes';
|
30
|
-
|
31
|
-
import type {TClusterInfo} from '../../types/api/cluster';
|
32
|
-
import type {TTabletStateInfo} from '../../types/api/tablet';
|
33
|
-
|
34
|
-
import './ClusterInfo.scss';
|
35
|
-
|
36
|
-
const b = cn('cluster-info');
|
37
|
-
|
38
|
-
export interface IClusterInfoItem {
|
39
|
-
label: string;
|
40
|
-
value: ReactNode;
|
41
|
-
}
|
42
|
-
|
43
|
-
interface ClusterInfoProps {
|
44
|
-
className?: string;
|
45
|
-
cluster?: TClusterInfo;
|
46
|
-
hideTooltip: VoidFunction;
|
47
|
-
showTooltip: (...args: Parameters<typeof showTooltip>) => void;
|
48
|
-
setHeader: any;
|
49
|
-
getClusterInfo: (clusterName: string) => void;
|
50
|
-
clusterTitle?: string;
|
51
|
-
additionalClusterInfo?: IClusterInfoItem[];
|
52
|
-
loading: boolean;
|
53
|
-
singleClusterMode: boolean;
|
54
|
-
wasLoaded: boolean;
|
55
|
-
error?: {statusText: string};
|
56
|
-
}
|
57
|
-
|
58
|
-
class ClusterInfo extends React.Component<ClusterInfoProps> {
|
59
|
-
static compareTablets(tablet1: TTabletStateInfo, tablet2: TTabletStateInfo) {
|
60
|
-
if (tablet1.Type === TxAllocator) {
|
61
|
-
return 1;
|
62
|
-
}
|
63
|
-
|
64
|
-
if (tablet2.Type === TxAllocator) {
|
65
|
-
return -1;
|
66
|
-
}
|
67
|
-
|
68
|
-
return 0;
|
69
|
-
}
|
70
|
-
|
71
|
-
private autofetcher: any;
|
72
|
-
|
73
|
-
componentDidMount() {
|
74
|
-
const {setHeader} = this.props;
|
75
|
-
setHeader([
|
76
|
-
{
|
77
|
-
text: CLUSTER_PAGES.cluster.title,
|
78
|
-
link: createHref(routes.cluster, {activeTab: CLUSTER_PAGES.cluster.id}),
|
79
|
-
},
|
80
|
-
]);
|
81
|
-
this.props.getClusterInfo(clusterName);
|
82
|
-
this.autofetcher = new AutoFetcher();
|
83
|
-
this.autofetcher.fetch(() => this.props.getClusterInfo(clusterName));
|
84
|
-
}
|
85
|
-
|
86
|
-
shouldComponentUpdate(nextProps: ClusterInfoProps) {
|
87
|
-
const {cluster = {}} = nextProps;
|
88
|
-
return !(Object.keys(cluster).length === 0);
|
89
|
-
}
|
90
|
-
|
91
|
-
componentWillUnmount() {
|
92
|
-
this.autofetcher.stop();
|
93
|
-
}
|
94
|
-
|
95
|
-
renderLoader() {
|
96
|
-
return (
|
97
|
-
<div className={b('loader')}>
|
98
|
-
<Loader size="l" />
|
99
|
-
</div>
|
100
|
-
);
|
101
|
-
}
|
102
|
-
|
103
|
-
render() {
|
104
|
-
const {className, error} = this.props;
|
105
|
-
let helper;
|
106
|
-
if (error) {
|
107
|
-
helper = error.statusText;
|
108
|
-
}
|
109
|
-
return <div className={b(null, className)}>{helper || this.renderContent()}</div>;
|
110
|
-
}
|
111
|
-
|
112
|
-
private getInfo() {
|
113
|
-
const {cluster = {}, additionalClusterInfo = [], singleClusterMode} = this.props;
|
114
|
-
const {StorageTotal, StorageUsed} = cluster;
|
115
|
-
|
116
|
-
let link = backend + '/internal';
|
117
|
-
|
118
|
-
if (singleClusterMode && !customBackend) {
|
119
|
-
link = `/internal`;
|
120
|
-
}
|
121
|
-
|
122
|
-
const info: IClusterInfoItem[] = [
|
123
|
-
{
|
124
|
-
label: 'Nodes',
|
125
|
-
value: (
|
126
|
-
<ProgressViewer
|
127
|
-
className={b('metric-field')}
|
128
|
-
value={cluster.NodesAlive}
|
129
|
-
capacity={cluster.NodesTotal}
|
130
|
-
/>
|
131
|
-
),
|
132
|
-
},
|
133
|
-
{
|
134
|
-
label: 'Load',
|
135
|
-
value: (
|
136
|
-
<ProgressViewer
|
137
|
-
className={b('metric-field')}
|
138
|
-
value={cluster.LoadAverage}
|
139
|
-
capacity={cluster.NumberOfCpus}
|
140
|
-
/>
|
141
|
-
),
|
142
|
-
},
|
143
|
-
{
|
144
|
-
label: 'Storage',
|
145
|
-
value: (
|
146
|
-
<ProgressViewer
|
147
|
-
className={b('metric-field')}
|
148
|
-
value={StorageUsed}
|
149
|
-
capacity={StorageTotal}
|
150
|
-
formatValues={formatStorageValues}
|
151
|
-
/>
|
152
|
-
),
|
153
|
-
},
|
154
|
-
{
|
155
|
-
label: 'Versions',
|
156
|
-
value: <div>{cluster.Versions?.join(', ')}</div>,
|
157
|
-
},
|
158
|
-
...additionalClusterInfo,
|
159
|
-
{
|
160
|
-
label: 'Internal viewer',
|
161
|
-
value: (
|
162
|
-
<Link href={link} target="_blank">
|
163
|
-
<Icon name="external" viewBox={'0 0 16 16'} width={16} height={16} />
|
164
|
-
</Link>
|
165
|
-
),
|
166
|
-
},
|
167
|
-
];
|
168
|
-
|
169
|
-
return info;
|
170
|
-
}
|
171
|
-
|
172
|
-
private renderContent = () => {
|
173
|
-
const {
|
174
|
-
cluster = {},
|
175
|
-
showTooltip,
|
176
|
-
hideTooltip,
|
177
|
-
clusterTitle,
|
178
|
-
loading,
|
179
|
-
wasLoaded,
|
180
|
-
} = this.props;
|
181
|
-
const {Name: clusterName = 'Unknown cluster'} = cluster;
|
182
|
-
|
183
|
-
const info = this.getInfo();
|
184
|
-
|
185
|
-
return loading && !wasLoaded ? (
|
186
|
-
this.renderLoader()
|
187
|
-
) : (
|
188
|
-
<React.Fragment>
|
189
|
-
<div className={b('common')}>
|
190
|
-
<div className={b('url')}>
|
191
|
-
<EntityStatus
|
192
|
-
size="m"
|
193
|
-
status={cluster.Overall}
|
194
|
-
name={clusterTitle ?? clusterName}
|
195
|
-
/>
|
196
|
-
</div>
|
197
|
-
|
198
|
-
{cluster.DataCenters && <Tags tags={cluster.DataCenters} />}
|
199
|
-
|
200
|
-
<div className={b('system-tablets')}>
|
201
|
-
{cluster.SystemTablets &&
|
202
|
-
cluster.SystemTablets.sort(ClusterInfo.compareTablets).map(
|
203
|
-
(tablet, tabletIndex) => (
|
204
|
-
<Tablet
|
205
|
-
onMouseEnter={showTooltip}
|
206
|
-
onMouseLeave={hideTooltip}
|
207
|
-
key={tabletIndex}
|
208
|
-
tablet={tablet}
|
209
|
-
/>
|
210
|
-
),
|
211
|
-
)}
|
212
|
-
</div>
|
213
|
-
</div>
|
214
|
-
<InfoViewer dots={true} info={info} />
|
215
|
-
</React.Fragment>
|
216
|
-
);
|
217
|
-
};
|
218
|
-
}
|
219
|
-
|
220
|
-
const mapStateToProps = (state: any) => {
|
221
|
-
const {data: cluster, loading, error, wasLoaded} = state.cluster;
|
222
|
-
|
223
|
-
return {
|
224
|
-
cluster,
|
225
|
-
loading,
|
226
|
-
wasLoaded,
|
227
|
-
error,
|
228
|
-
singleClusterMode: state.singleClusterMode,
|
229
|
-
};
|
230
|
-
};
|
231
|
-
|
232
|
-
const mapDispatchToProps = {
|
233
|
-
getClusterInfo,
|
234
|
-
hideTooltip,
|
235
|
-
showTooltip,
|
236
|
-
setHeader,
|
237
|
-
};
|
238
|
-
|
239
|
-
export default connect(mapStateToProps, mapDispatchToProps)(ClusterInfo);
|
@@ -1,147 +0,0 @@
|
|
1
|
-
import React from 'react';
|
2
|
-
import cn from 'bem-cn-lite';
|
3
|
-
import PropTypes from 'prop-types';
|
4
|
-
|
5
|
-
import DataTable from '@gravity-ui/react-data-table';
|
6
|
-
|
7
|
-
import InfoViewer from '../InfoViewer/InfoViewer';
|
8
|
-
import EntityStatus from '../EntityStatus/EntityStatus';
|
9
|
-
import ProgressViewer from '../ProgressViewer/ProgressViewer';
|
10
|
-
import Breadcrumbs from '../Breadcrumbs/Breadcrumbs';
|
11
|
-
|
12
|
-
import {stringifyVdiskId, formatStorageValues} from '../../utils';
|
13
|
-
import {DEFAULT_TABLE_SETTINGS, PDISK_CATEGORIES} from '../../utils/constants';
|
14
|
-
import routes, {createHref} from '../../routes';
|
15
|
-
|
16
|
-
import './FullGroupViewer.scss';
|
17
|
-
|
18
|
-
const b = cn('full-group-viewer');
|
19
|
-
|
20
|
-
const tableSettings = {
|
21
|
-
...DEFAULT_TABLE_SETTINGS,
|
22
|
-
stickyHead: DataTable.FIXED,
|
23
|
-
};
|
24
|
-
|
25
|
-
class FullGroupViewer extends React.Component {
|
26
|
-
static propTypes = {
|
27
|
-
className: PropTypes.string,
|
28
|
-
group: PropTypes.object.isRequired,
|
29
|
-
};
|
30
|
-
|
31
|
-
static defaultProps = {
|
32
|
-
className: '',
|
33
|
-
};
|
34
|
-
|
35
|
-
renderContent = () => {
|
36
|
-
const {group} = this.props;
|
37
|
-
const data = group.VDisks;
|
38
|
-
const columns = [
|
39
|
-
{
|
40
|
-
name: 'VDiskId',
|
41
|
-
header: 'VDisk',
|
42
|
-
render: ({value, row}) => (
|
43
|
-
<EntityStatus
|
44
|
-
status={row.Overall}
|
45
|
-
name={stringifyVdiskId(value)}
|
46
|
-
path={createHref(routes.vdisk, null, {
|
47
|
-
vdiskId: stringifyVdiskId(value),
|
48
|
-
})}
|
49
|
-
/>
|
50
|
-
),
|
51
|
-
},
|
52
|
-
{
|
53
|
-
name: 'pdisk',
|
54
|
-
header: 'PDisk',
|
55
|
-
// eslint-disable-next-line no-confusing-arrow
|
56
|
-
render: ({row}) =>
|
57
|
-
row.PDisk ? (
|
58
|
-
<EntityStatus
|
59
|
-
path={createHref(
|
60
|
-
routes.pdisk,
|
61
|
-
{id: row.PDisk.PDiskId},
|
62
|
-
{node_id: row.PDisk.NodeId},
|
63
|
-
)}
|
64
|
-
status={row.PDisk.Overall}
|
65
|
-
name={`${row.NodeId}-${row.PDisk.PDiskId}`}
|
66
|
-
/>
|
67
|
-
) : (
|
68
|
-
<div className="error">—</div>
|
69
|
-
),
|
70
|
-
},
|
71
|
-
{
|
72
|
-
name: 'space',
|
73
|
-
header: 'Space',
|
74
|
-
width: '150px',
|
75
|
-
// eslint-disable-next-line no-confusing-arrow
|
76
|
-
render: ({row}) =>
|
77
|
-
row.PDisk ? (
|
78
|
-
<ProgressViewer
|
79
|
-
capacity={row.PDisk.TotalSize}
|
80
|
-
value={row.PDisk.TotalSize - row.PDisk.AvailableSize}
|
81
|
-
formatValues={formatStorageValues}
|
82
|
-
/>
|
83
|
-
) : (
|
84
|
-
<div className="error">—</div>
|
85
|
-
),
|
86
|
-
},
|
87
|
-
{
|
88
|
-
name: 'category',
|
89
|
-
header: 'Category',
|
90
|
-
// eslint-disable-next-line no-confusing-arrow
|
91
|
-
render: ({row}) =>
|
92
|
-
row.PDisk ? (
|
93
|
-
PDISK_CATEGORIES[row.PDisk.Category]
|
94
|
-
) : (
|
95
|
-
<div className="error">—</div>
|
96
|
-
),
|
97
|
-
align: DataTable.RIGHT,
|
98
|
-
},
|
99
|
-
{
|
100
|
-
name: 'path',
|
101
|
-
header: 'Path',
|
102
|
-
// eslint-disable-next-line no-confusing-arrow
|
103
|
-
render: ({row}) => (row.PDisk ? row.PDisk.Path : <div className="error">—</div>),
|
104
|
-
},
|
105
|
-
{
|
106
|
-
name: 'guid',
|
107
|
-
header: 'Guid',
|
108
|
-
// eslint-disable-next-line no-confusing-arrow
|
109
|
-
render: ({row}) => (row.PDisk ? row.PDisk.Guid : <div className="error">—</div>),
|
110
|
-
align: DataTable.RIGHT,
|
111
|
-
},
|
112
|
-
];
|
113
|
-
|
114
|
-
const groupInfo = [
|
115
|
-
{label: 'Generation', value: group.GroupGeneration},
|
116
|
-
{label: 'Latency', value: <EntityStatus status={group.Latency} />},
|
117
|
-
{label: 'Erasure', value: group.ErasureSpecies},
|
118
|
-
];
|
119
|
-
|
120
|
-
const breadcrumbsItems = [{text: 'Database'}, {text: 'Storage Pool'}, {text: 'BS Group'}];
|
121
|
-
|
122
|
-
return (
|
123
|
-
<React.Fragment>
|
124
|
-
<Breadcrumbs items={breadcrumbsItems} />
|
125
|
-
<InfoViewer className={b('section')} info={groupInfo} title={'Info'} />
|
126
|
-
<DataTable
|
127
|
-
cls={b('disks')}
|
128
|
-
columns={columns}
|
129
|
-
data={data}
|
130
|
-
settings={tableSettings}
|
131
|
-
/>
|
132
|
-
</React.Fragment>
|
133
|
-
);
|
134
|
-
};
|
135
|
-
|
136
|
-
render() {
|
137
|
-
const {className, group} = this.props;
|
138
|
-
|
139
|
-
return (
|
140
|
-
<div className={`${b()} ${className}`}>
|
141
|
-
{group ? this.renderContent() : <div className="error">no group data</div>}
|
142
|
-
</div>
|
143
|
-
);
|
144
|
-
}
|
145
|
-
}
|
146
|
-
|
147
|
-
export default FullGroupViewer;
|
@@ -1,35 +0,0 @@
|
|
1
|
-
@import '../../styles/mixins';
|
2
|
-
|
3
|
-
.full-group-viewer {
|
4
|
-
overflow: auto;
|
5
|
-
@include container-fluid();
|
6
|
-
@include flex-container();
|
7
|
-
|
8
|
-
&__section {
|
9
|
-
display: inline-block;
|
10
|
-
|
11
|
-
min-width: 315px;
|
12
|
-
margin-right: 40px;
|
13
|
-
|
14
|
-
border-radius: 5px;
|
15
|
-
|
16
|
-
.info-viewer__label {
|
17
|
-
min-width: 75px;
|
18
|
-
}
|
19
|
-
}
|
20
|
-
|
21
|
-
.info-viewer__items {
|
22
|
-
grid-template-columns: max-content;
|
23
|
-
}
|
24
|
-
|
25
|
-
.data-table {
|
26
|
-
overflow: auto;
|
27
|
-
|
28
|
-
margin-top: 50px;
|
29
|
-
@include flex-container();
|
30
|
-
}
|
31
|
-
|
32
|
-
.data-table__table {
|
33
|
-
width: 100%;
|
34
|
-
}
|
35
|
-
}
|
@@ -1,87 +0,0 @@
|
|
1
|
-
import React from 'react';
|
2
|
-
import PropTypes from 'prop-types';
|
3
|
-
import cn from 'bem-cn-lite';
|
4
|
-
import {withRouter} from 'react-router';
|
5
|
-
|
6
|
-
import {TreeView} from 'ydb-ui-components';
|
7
|
-
|
8
|
-
import GroupViewer from '../GroupViewer/GroupViewer';
|
9
|
-
import PDiskViewer from '../PDiskViewer/PDiskViewer';
|
10
|
-
import EntityStatus from '../EntityStatus/EntityStatus';
|
11
|
-
import {stringifyVdiskId} from '../../utils';
|
12
|
-
import routes, {createHref} from '../../routes';
|
13
|
-
import {backend} from '../../store';
|
14
|
-
|
15
|
-
import './GroupTreeViewer.scss';
|
16
|
-
|
17
|
-
const b = cn('group-tree-viewer');
|
18
|
-
|
19
|
-
class GroupTreeViewer extends React.Component {
|
20
|
-
static propTypes = {
|
21
|
-
group: PropTypes.object.isRequired,
|
22
|
-
collapsed: PropTypes.bool,
|
23
|
-
onClick: PropTypes.func,
|
24
|
-
};
|
25
|
-
|
26
|
-
static ITEM_HEIGHT_COLLAPSED = 38;
|
27
|
-
static GROUP_ITEM_HEIGHT = 34;
|
28
|
-
|
29
|
-
static makeGetHeight = (groups, extendedGroups) => (index) => {
|
30
|
-
const {VDisks} = groups[index];
|
31
|
-
const countVDisks = VDisks?.length;
|
32
|
-
const parentHeight = GroupTreeViewer.ITEM_HEIGHT_COLLAPSED;
|
33
|
-
const childHeight = GroupTreeViewer.GROUP_ITEM_HEIGHT;
|
34
|
-
|
35
|
-
if (extendedGroups.has(index)) {
|
36
|
-
return parentHeight + countVDisks * childHeight;
|
37
|
-
}
|
38
|
-
|
39
|
-
return parentHeight;
|
40
|
-
};
|
41
|
-
|
42
|
-
render() {
|
43
|
-
const {group, collapsed, onClick} = this.props;
|
44
|
-
const label2 = (
|
45
|
-
<div>
|
46
|
-
<GroupViewer key={group.GroupID} group={group} />
|
47
|
-
</div>
|
48
|
-
);
|
49
|
-
if (group && Object.keys(group).length) {
|
50
|
-
return (
|
51
|
-
<div className={b()}>
|
52
|
-
<TreeView
|
53
|
-
key={group.GroupID}
|
54
|
-
name={label2}
|
55
|
-
collapsed={collapsed}
|
56
|
-
onClick={onClick}
|
57
|
-
>
|
58
|
-
{group.VDisks?.map((disk, diskIndex) => (
|
59
|
-
<div key={diskIndex} className={b('row')}>
|
60
|
-
<EntityStatus
|
61
|
-
className={b('disk')}
|
62
|
-
status={disk.Overall}
|
63
|
-
name={stringifyVdiskId(disk.VDiskId)}
|
64
|
-
label="VDiskID"
|
65
|
-
path={createHref(routes.vdisk, null, {
|
66
|
-
vdiskId: stringifyVdiskId(disk.VDiskId),
|
67
|
-
})}
|
68
|
-
/>
|
69
|
-
|
70
|
-
<PDiskViewer
|
71
|
-
className={b('disk')}
|
72
|
-
disk={disk.PDisk}
|
73
|
-
key={disk.Guid}
|
74
|
-
backend={backend}
|
75
|
-
/>
|
76
|
-
</div>
|
77
|
-
))}
|
78
|
-
</TreeView>
|
79
|
-
</div>
|
80
|
-
);
|
81
|
-
} else {
|
82
|
-
return <div className={b()}>No data</div>;
|
83
|
-
}
|
84
|
-
}
|
85
|
-
}
|
86
|
-
|
87
|
-
export default withRouter(GroupTreeViewer);
|