ydb-embedded-ui 1.10.1 → 1.11.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 +41 -0
- package/dist/components/IndexInfoViewer/IndexInfoViewer.tsx +12 -9
- package/dist/components/InfoViewer/InfoViewer.scss +33 -9
- package/dist/components/InfoViewer/InfoViewer.tsx +43 -0
- package/dist/components/InfoViewer/index.ts +1 -0
- package/dist/components/InfoViewer/utils.ts +21 -11
- package/dist/components/Stack/Stack.scss +55 -0
- package/dist/components/Stack/Stack.tsx +35 -0
- package/dist/containers/Storage/DiskStateProgressBar/DiskStateProgressBar.scss +2 -0
- package/dist/containers/Storage/DiskStateProgressBar/DiskStateProgressBar.tsx +5 -0
- package/dist/containers/Storage/Pdisk/Pdisk.scss +2 -19
- package/dist/containers/Storage/Pdisk/Pdisk.tsx +30 -33
- package/dist/containers/Storage/Pdisk/__tests__/colors.tsx +40 -0
- package/dist/containers/Storage/StorageGroups/StorageGroups.scss +25 -3
- package/dist/containers/Storage/StorageGroups/StorageGroups.tsx +31 -7
- package/dist/containers/Storage/Vdisk/Vdisk.js +63 -64
- package/dist/containers/Storage/Vdisk/Vdisk.scss +9 -28
- package/dist/containers/Storage/Vdisk/__tests__/colors.tsx +163 -0
- package/dist/containers/Tenant/Diagnostics/DetailedOverview/DetailedOverview.tsx +15 -14
- package/dist/containers/Tenant/QueryEditor/QueryEditor.js +12 -2
- package/dist/containers/Tenant/Schema/SchemaInfoViewer/SchemaInfoViewer.js +164 -42
- package/dist/containers/Tenant/Schema/SchemaInfoViewer/SchemaInfoViewer.scss +18 -0
- package/dist/services/api.js +0 -1
- package/dist/setupTests.js +8 -0
- package/dist/store/reducers/executeQuery.js +3 -2
- package/dist/store/reducers/settings.js +20 -13
- package/dist/types/api/schema.ts +117 -4
- package/dist/types/api/storage.ts +121 -0
- package/dist/types/index.ts +1 -0
- package/dist/utils/constants.js +4 -0
- package/dist/utils/index.js +28 -4
- package/dist/utils/pdisk.ts +2 -2
- package/package.json +28 -5
- package/dist/components/InfoViewer/InfoViewer.js +0 -47
- package/dist/index.test.js +0 -5
package/CHANGELOG.md
CHANGED
@@ -1,5 +1,46 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [1.11.0](https://github.com/ydb-platform/ydb-embedded-ui/compare/v1.10.3...v1.11.0) (2022-08-23)
|
4
|
+
|
5
|
+
|
6
|
+
### Features
|
7
|
+
|
8
|
+
* **Stack:** new component for stacked elements ([c42ba37](https://github.com/ydb-platform/ydb-embedded-ui/commit/c42ba37fafdd9dedc4be9d625d7e756a83c01fe3))
|
9
|
+
* **Storage:** display donor disks ([b808fe9](https://github.com/ydb-platform/ydb-embedded-ui/commit/b808fe951987c615f797af56017f8045a1ed852f))
|
10
|
+
* **VDisk:** display label for donors ([bba5ae8](https://github.com/ydb-platform/ydb-embedded-ui/commit/bba5ae8e44347a5b1d9cb72424f5a963a6848e59))
|
11
|
+
|
12
|
+
|
13
|
+
### Bug Fixes
|
14
|
+
|
15
|
+
* **InfoViewer:** add size_s ([fc06451](https://github.com/ydb-platform/ydb-embedded-ui/commit/fc0645118f64a79f660d734c2ff43c42c738fd40))
|
16
|
+
* **PDisk:** new popup design ([9c0355d](https://github.com/ydb-platform/ydb-embedded-ui/commit/9c0355d4d9ccf69d43a5287b0e78d7c7993c4a18))
|
17
|
+
* **PDisk:** restrict component interface ([328efa9](https://github.com/ydb-platform/ydb-embedded-ui/commit/328efa90d214eca1bceeeb5bd9099aab36a3ddb0))
|
18
|
+
* **Storage:** shrink tooltip active area on Pool Name ([30a2b92](https://github.com/ydb-platform/ydb-embedded-ui/commit/30a2b92ff598d9caeabe17a4b8de214943945a91))
|
19
|
+
* **VDisk:** add a missing prop type ([39b6cf3](https://github.com/ydb-platform/ydb-embedded-ui/commit/39b6cf38811cab6c4374c77d3eb63c11fa7b83d5))
|
20
|
+
* **VDisk:** don't paint donors blue ([6b148b9](https://github.com/ydb-platform/ydb-embedded-ui/commit/6b148b914663a74e528a01a35f575f87ed6e9f09))
|
21
|
+
* **VDisk:** new popup design ([107b139](https://github.com/ydb-platform/ydb-embedded-ui/commit/107b13900b08631ea42034a6a2f7961c49c86556))
|
22
|
+
|
23
|
+
## [1.10.3](https://github.com/ydb-platform/ydb-embedded-ui/compare/v1.10.2...v1.10.3) (2022-08-23)
|
24
|
+
|
25
|
+
|
26
|
+
### Bug Fixes
|
27
|
+
|
28
|
+
* **Overview:** format undefined values to empty string, not 0 ([1a37c27](https://github.com/ydb-platform/ydb-embedded-ui/commit/1a37c278328ad8eb4397d9507566829f01a9c872))
|
29
|
+
|
30
|
+
## [1.10.2](https://github.com/ydb-platform/ydb-embedded-ui/compare/v1.10.1...v1.10.2) (2022-08-17)
|
31
|
+
|
32
|
+
|
33
|
+
### Bug Fixes
|
34
|
+
|
35
|
+
* convert bytes on decimal scale ([db9b0a7](https://github.com/ydb-platform/ydb-embedded-ui/commit/db9b0a71fc5334f5a40992cc6abc0688782ad5d2))
|
36
|
+
* display HDD instead of ROT as pdisk type ([bd9e5ba](https://github.com/ydb-platform/ydb-embedded-ui/commit/bd9e5ba4e594cb3a1f6a964f619f9824e083ae7c))
|
37
|
+
* **InfoViewer:** accept default value formatter ([e03d8cc](https://github.com/ydb-platform/ydb-embedded-ui/commit/e03d8cc5de76e4ac00b05586ae6f6522a9708fb0))
|
38
|
+
* **InfoViewer:** allow longer labels ([89060a3](https://github.com/ydb-platform/ydb-embedded-ui/commit/89060a381858b5beaa3c3cf3402c13c917705676))
|
39
|
+
* **Overview:** display table r/o replicas ([6dbe0b4](https://github.com/ydb-platform/ydb-embedded-ui/commit/6dbe0b45fc5e3867f9d6141d270c15508a693e35))
|
40
|
+
* **Overview:** format & group table info in overview ([1a35cfc](https://github.com/ydb-platform/ydb-embedded-ui/commit/1a35cfcd2075454c4a1f1fc4961a4b3106b6d225))
|
41
|
+
* **QueryEditor:** save chosen run action ([b0fb436](https://github.com/ydb-platform/ydb-embedded-ui/commit/b0fb43651e0c6d1dc5d6a25f92716703402b556d))
|
42
|
+
* use current i18n lang for numeral formatting ([5d58fcf](https://github.com/ydb-platform/ydb-embedded-ui/commit/5d58fcffde21924f3cbe6c28946c7a9f755a8490))
|
43
|
+
|
3
44
|
## [1.10.1](https://github.com/ydb-platform/ydb-embedded-ui/compare/v1.10.0...v1.10.1) (2022-08-10)
|
4
45
|
|
5
46
|
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import type {TEvDescribeSchemeResult, TIndexDescription} from '../../types/api/schema';
|
2
|
-
import {InfoViewer, createInfoFormatter} from '../InfoViewer';
|
2
|
+
import {InfoViewer, createInfoFormatter, InfoViewerItem} from '../InfoViewer';
|
3
3
|
|
4
4
|
const DISPLAYED_FIELDS: Set<keyof TIndexDescription> = new Set([
|
5
5
|
'Type',
|
@@ -10,13 +10,16 @@ const DISPLAYED_FIELDS: Set<keyof TIndexDescription> = new Set([
|
|
10
10
|
]);
|
11
11
|
|
12
12
|
const formatItem = createInfoFormatter<TIndexDescription>({
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
13
|
+
values: {
|
14
|
+
Type: (value) => value?.substring(10), // trims EIndexType prefix
|
15
|
+
State: (value) => value?.substring(11), // trims EIndexState prefix
|
16
|
+
KeyColumnNames: (value) => value?.join(', '),
|
17
|
+
DataColumnNames: (value) => value?.join(', '),
|
18
|
+
},
|
19
|
+
labels: {
|
20
|
+
KeyColumnNames: 'Columns',
|
21
|
+
DataColumnNames: 'Includes',
|
22
|
+
},
|
20
23
|
});
|
21
24
|
|
22
25
|
interface IndexInfoViewerProps {
|
@@ -31,7 +34,7 @@ export const IndexInfoViewer = ({data}: IndexInfoViewerProps) => {
|
|
31
34
|
}
|
32
35
|
|
33
36
|
const TableIndex = data.PathDescription?.TableIndex;
|
34
|
-
const info: Array<
|
37
|
+
const info: Array<InfoViewerItem> = [];
|
35
38
|
|
36
39
|
let key: keyof TIndexDescription;
|
37
40
|
for (key in TableIndex) {
|
@@ -1,18 +1,23 @@
|
|
1
1
|
.info-viewer {
|
2
|
-
font-size: var(--yc-text-body-2-font-size);
|
3
|
-
line-height: var(--yc-text-body-2-line-height);
|
2
|
+
--ydb-info-viewer-font-size: var(--yc-text-body-2-font-size);
|
3
|
+
--ydb-info-viewer-line-height: var(--yc-text-body-2-line-height);
|
4
|
+
--ydb-info-viewer-title-font-weight: 600;
|
5
|
+
--ydb-info-viewer-title-margin: 15px 0 10px;
|
6
|
+
--ydb-info-viewer-items-gap: 7px;
|
7
|
+
|
8
|
+
font-size: var(--ydb-info-viewer-font-size);
|
9
|
+
line-height: var(--ydb-info-viewer-line-height);
|
10
|
+
|
4
11
|
&__title {
|
5
|
-
margin:
|
12
|
+
margin: var(--ydb-info-viewer-title-margin);
|
6
13
|
|
7
|
-
font-
|
8
|
-
font-weight: 600;
|
9
|
-
line-height: var(--yc-text-body-2-line-height);
|
14
|
+
font-weight: var(--ydb-info-viewer-title-font-weight);
|
10
15
|
}
|
11
16
|
|
12
17
|
&__items {
|
13
18
|
display: flex;
|
14
19
|
flex-direction: column;
|
15
|
-
gap:
|
20
|
+
gap: var(--ydb-info-viewer-items-gap);
|
16
21
|
|
17
22
|
max-width: 100%;
|
18
23
|
}
|
@@ -27,11 +32,10 @@
|
|
27
32
|
|
28
33
|
&__label {
|
29
34
|
display: flex;
|
30
|
-
flex:
|
35
|
+
flex: 0 1 auto;
|
31
36
|
align-items: baseline;
|
32
37
|
|
33
38
|
min-width: 200px;
|
34
|
-
max-width: 200px;
|
35
39
|
|
36
40
|
white-space: nowrap;
|
37
41
|
|
@@ -52,4 +56,24 @@
|
|
52
56
|
|
53
57
|
white-space: nowrap;
|
54
58
|
}
|
59
|
+
|
60
|
+
&_size {
|
61
|
+
&_s {
|
62
|
+
--ydb-info-viewer-font-size: var(--yc-text-body-1-font-size);
|
63
|
+
--ydb-info-viewer-line-height: var(--yc-text-body-1-line-height);
|
64
|
+
--ydb-info-viewer-title-font-weight: 500;
|
65
|
+
--ydb-info-viewer-title-margin: 0 0 4px;
|
66
|
+
--ydb-info-viewer-items-gap: 4px;
|
67
|
+
|
68
|
+
.info-viewer {
|
69
|
+
&__row {
|
70
|
+
height: auto;
|
71
|
+
}
|
72
|
+
|
73
|
+
&__label {
|
74
|
+
min-width: 85px;
|
75
|
+
}
|
76
|
+
}
|
77
|
+
}
|
78
|
+
}
|
55
79
|
}
|
@@ -0,0 +1,43 @@
|
|
1
|
+
import type {ReactNode} from 'react';
|
2
|
+
import cn from 'bem-cn-lite';
|
3
|
+
|
4
|
+
import './InfoViewer.scss';
|
5
|
+
|
6
|
+
export interface InfoViewerItem {
|
7
|
+
label: string;
|
8
|
+
value: ReactNode;
|
9
|
+
}
|
10
|
+
|
11
|
+
interface InfoViewerProps {
|
12
|
+
title?: string;
|
13
|
+
info?: InfoViewerItem[];
|
14
|
+
dots?: boolean;
|
15
|
+
size?: 's';
|
16
|
+
className?: string;
|
17
|
+
}
|
18
|
+
|
19
|
+
const b = cn('info-viewer');
|
20
|
+
|
21
|
+
const InfoViewer = ({title, info, dots = true, size, className}: InfoViewerProps) => (
|
22
|
+
<div className={b({size}, className)}>
|
23
|
+
{title && <div className={b('title')}>{title}</div>}
|
24
|
+
{info && info.length > 0 ? (
|
25
|
+
<div className={b('items')}>
|
26
|
+
{info.map((data, infoIndex) => (
|
27
|
+
<div className={b('row')} key={data.label + infoIndex}>
|
28
|
+
<div className={b('label')}>
|
29
|
+
{data.label}
|
30
|
+
{dots && <div className={b('dots')} />}
|
31
|
+
</div>
|
32
|
+
|
33
|
+
<div className={b('value')}>{data.value}</div>
|
34
|
+
</div>
|
35
|
+
))}
|
36
|
+
</div>
|
37
|
+
) : (
|
38
|
+
<>no {title} data</>
|
39
|
+
)}
|
40
|
+
</div>
|
41
|
+
);
|
42
|
+
|
43
|
+
export default InfoViewer;
|
@@ -1,9 +1,11 @@
|
|
1
|
+
import type {ReactNode} from "react";
|
2
|
+
|
1
3
|
type LabelMap<T> = {
|
2
4
|
[label in keyof T]?: string;
|
3
5
|
}
|
4
6
|
|
5
|
-
type
|
6
|
-
[label in keyof T]?: (value: T[label]) =>
|
7
|
+
type ValueFormatters<T> = {
|
8
|
+
[label in keyof T]?: (value: T[label]) => ReactNode;
|
7
9
|
}
|
8
10
|
|
9
11
|
function formatLabel<Shape>(label: keyof Shape, map: LabelMap<Shape>) {
|
@@ -13,20 +15,28 @@ function formatLabel<Shape>(label: keyof Shape, map: LabelMap<Shape>) {
|
|
13
15
|
function formatValue<Shape, Key extends keyof Shape>(
|
14
16
|
label: Key,
|
15
17
|
value: Shape[Key],
|
16
|
-
|
18
|
+
formatters: ValueFormatters<Shape>,
|
19
|
+
defaultFormatter?: (value: Shape[Key]) => ReactNode,
|
17
20
|
) {
|
18
|
-
const
|
19
|
-
const
|
21
|
+
const formatter = formatters[label] || defaultFormatter;
|
22
|
+
const formattedValue = formatter ? formatter(value) : value;
|
20
23
|
|
21
|
-
return
|
24
|
+
return formattedValue;
|
22
25
|
}
|
23
26
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
)
|
27
|
+
interface CreateInfoFormatterOptions<Shape> {
|
28
|
+
values?: ValueFormatters<Shape>,
|
29
|
+
labels?: LabelMap<Shape>,
|
30
|
+
defaultValueFormatter?: (value: Shape[keyof Shape]) => ReactNode,
|
31
|
+
}
|
32
|
+
|
33
|
+
export function createInfoFormatter<Shape extends Record<string, any>>({
|
34
|
+
values: valueFormatters,
|
35
|
+
labels: labelMap,
|
36
|
+
defaultValueFormatter,
|
37
|
+
}: CreateInfoFormatterOptions<Shape>) {
|
28
38
|
return <Key extends keyof Shape>(label: Key, value: Shape[Key]) => ({
|
29
39
|
label: formatLabel(label, labelMap || {}),
|
30
|
-
value: formatValue(label, value,
|
40
|
+
value: formatValue(label, value, valueFormatters || {}, defaultValueFormatter),
|
31
41
|
});
|
32
42
|
}
|
@@ -0,0 +1,55 @@
|
|
1
|
+
.stack {
|
2
|
+
--ydb-stack-base-z-index: 100;
|
3
|
+
--ydb-stack-offset-x: 4px;
|
4
|
+
--ydb-stack-offset-y: 4px;
|
5
|
+
--ydb-stack-offset-x-hover: 4px;
|
6
|
+
--ydb-stack-offset-y-hover: 8px;
|
7
|
+
|
8
|
+
position: relative;
|
9
|
+
|
10
|
+
&__layer {
|
11
|
+
transition: transform 0.1s ease-out;
|
12
|
+
|
13
|
+
&:first-child {
|
14
|
+
position: relative;
|
15
|
+
z-index: var(--ydb-stack-base-z-index);
|
16
|
+
}
|
17
|
+
|
18
|
+
& + & {
|
19
|
+
position: absolute;
|
20
|
+
z-index: calc(var(--ydb-stack-base-z-index) - var(--ydb-stack-level));
|
21
|
+
top: 0;
|
22
|
+
left: 0;
|
23
|
+
|
24
|
+
width: 100%;
|
25
|
+
height: 100%;
|
26
|
+
|
27
|
+
transform: translate(
|
28
|
+
calc(var(--ydb-stack-level) * var(--ydb-stack-offset-x)),
|
29
|
+
calc(var(--ydb-stack-level) * var(--ydb-stack-offset-y))
|
30
|
+
);
|
31
|
+
}
|
32
|
+
}
|
33
|
+
|
34
|
+
&:hover {
|
35
|
+
.stack__layer:first-child {
|
36
|
+
transform: translate(
|
37
|
+
calc(-1 * var(--ydb-stack-offset-x-hover)),
|
38
|
+
calc(-1 * var(--ydb-stack-offset-y-hover))
|
39
|
+
);
|
40
|
+
}
|
41
|
+
|
42
|
+
.stack__layer + .stack__layer {
|
43
|
+
transform: translate(
|
44
|
+
calc(
|
45
|
+
var(--ydb-stack-level) * (var(--ydb-stack-offset-x-hover) * 2) -
|
46
|
+
var(--ydb-stack-offset-x-hover)
|
47
|
+
),
|
48
|
+
calc(
|
49
|
+
var(--ydb-stack-level) * (var(--ydb-stack-offset-y-hover) * 2) -
|
50
|
+
var(--ydb-stack-offset-y-hover)
|
51
|
+
)
|
52
|
+
);
|
53
|
+
}
|
54
|
+
}
|
55
|
+
}
|
@@ -0,0 +1,35 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import cn from 'bem-cn-lite';
|
3
|
+
|
4
|
+
import './Stack.scss';
|
5
|
+
|
6
|
+
interface StackProps {
|
7
|
+
className?: string;
|
8
|
+
}
|
9
|
+
|
10
|
+
const LAYER_CSS_VAR = '--ydb-stack-level';
|
11
|
+
|
12
|
+
const b = cn('stack');
|
13
|
+
|
14
|
+
export const Stack: React.FC<StackProps> = ({children, className}) => (
|
15
|
+
<div className={b(null, className)}>
|
16
|
+
{
|
17
|
+
React.Children.map(children, (child, index) => {
|
18
|
+
if (!React.isValidElement(child)) {
|
19
|
+
return null;
|
20
|
+
}
|
21
|
+
|
22
|
+
return (
|
23
|
+
<div
|
24
|
+
className={b('layer')}
|
25
|
+
style={{
|
26
|
+
[LAYER_CSS_VAR]: index,
|
27
|
+
} as React.CSSProperties}
|
28
|
+
>
|
29
|
+
{child}
|
30
|
+
</div>
|
31
|
+
);
|
32
|
+
})
|
33
|
+
}
|
34
|
+
</div>
|
35
|
+
);
|
@@ -46,6 +46,11 @@ function DiskStateProgressBar({
|
|
46
46
|
? b({[diskProgressColors[severity].toLowerCase()]: true})
|
47
47
|
: undefined
|
48
48
|
}
|
49
|
+
role="meter"
|
50
|
+
aria-label="Disk allocated space"
|
51
|
+
aria-valuemin={0}
|
52
|
+
aria-valuemax={100}
|
53
|
+
aria-valuenow={diskAllocatedPercent}
|
49
54
|
>
|
50
55
|
{href ? (
|
51
56
|
<InternalLink to={href} className={b('link')}>
|
@@ -11,25 +11,8 @@
|
|
11
11
|
&:last-child {
|
12
12
|
margin-right: 0px;
|
13
13
|
}
|
14
|
-
&__popup-wrapper {
|
15
|
-
padding: 5px 10px;
|
16
|
-
}
|
17
|
-
&__popup-content {
|
18
|
-
display: grid;
|
19
|
-
justify-items: stretch;
|
20
|
-
column-gap: 5px;
|
21
|
-
}
|
22
|
-
&__popup-section-name {
|
23
|
-
grid-column: 1 / 3;
|
24
|
-
|
25
|
-
margin: 5px 0;
|
26
14
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
border-bottom: 1px solid var(--yc-color-line-generic);
|
31
|
-
}
|
32
|
-
&__property {
|
33
|
-
text-align: right;
|
15
|
+
&__popup-wrapper {
|
16
|
+
padding: 12px;
|
34
17
|
}
|
35
18
|
}
|
@@ -1,7 +1,8 @@
|
|
1
1
|
import React, {useEffect, useState, useRef, useMemo} from 'react';
|
2
2
|
import cn from 'bem-cn-lite';
|
3
|
-
import _ from 'lodash';
|
4
3
|
import {Popup} from '@yandex-cloud/uikit';
|
4
|
+
|
5
|
+
import type {RequiredField} from '../../../types';
|
5
6
|
//@ts-ignore
|
6
7
|
import {bytesToGB} from '../../../utils/utils';
|
7
8
|
//@ts-ignore
|
@@ -10,6 +11,7 @@ import routes, {createHref} from '../../../routes';
|
|
10
11
|
import {getPDiskId} from '../../../utils';
|
11
12
|
import {getPDiskType} from '../../../utils/pdisk';
|
12
13
|
import {TPDiskStateInfo, TPDiskState} from '../../../types/api/storage';
|
14
|
+
import {InfoViewer} from '../../../components/InfoViewer';
|
13
15
|
import DiskStateProgressBar, {
|
14
16
|
diskProgressColors,
|
15
17
|
} from '../DiskStateProgressBar/DiskStateProgressBar';
|
@@ -38,7 +40,7 @@ const stateSeverity = {
|
|
38
40
|
[TPDiskState.DeviceIoError]: 5,
|
39
41
|
};
|
40
42
|
|
41
|
-
type PDiskProps = TPDiskStateInfo
|
43
|
+
type PDiskProps = RequiredField<TPDiskStateInfo, 'NodeId'>;
|
42
44
|
|
43
45
|
const isSeverityKey = (key?: TPDiskState): key is keyof typeof stateSeverity =>
|
44
46
|
key !== undefined && key in stateSeverity;
|
@@ -76,51 +78,46 @@ function Pdisk(props: PDiskProps) {
|
|
76
78
|
diskProgressColors[colorSeverity.Yellow as keyof typeof diskProgressColors],
|
77
79
|
];
|
78
80
|
|
79
|
-
const pdiskData: {
|
80
|
-
{
|
81
|
+
const pdiskData: {label: string; value: string | number}[] = [
|
82
|
+
{label: 'PDisk', value: getPDiskId({NodeId, PDiskId})},
|
81
83
|
];
|
82
84
|
|
83
|
-
pdiskData.push({
|
84
|
-
pdiskData.push({
|
85
|
-
NodeId && pdiskData.push({
|
85
|
+
pdiskData.push({label: 'State', value: State || 'not available'});
|
86
|
+
pdiskData.push({label: 'Type', value: getPDiskType(props) || 'unknown'});
|
87
|
+
NodeId && pdiskData.push({label: 'Node Id', value: NodeId});
|
86
88
|
|
87
|
-
Path && pdiskData.push({
|
89
|
+
Path && pdiskData.push({label: 'Path', value: Path});
|
88
90
|
pdiskData.push({
|
89
|
-
|
91
|
+
label: 'Available',
|
90
92
|
value: `${bytesToGB(AvailableSize)} of ${bytesToGB(TotalSize)}`,
|
91
93
|
});
|
92
94
|
Realtime &&
|
93
95
|
errorColors.includes(Realtime) &&
|
94
|
-
pdiskData.push({
|
96
|
+
pdiskData.push({label: 'Realtime', value: Realtime});
|
95
97
|
Device &&
|
96
98
|
errorColors.includes(Device) &&
|
97
|
-
pdiskData.push({
|
99
|
+
pdiskData.push({label: 'Device', value: Device});
|
98
100
|
return pdiskData;
|
99
101
|
};
|
100
102
|
/* eslint-enable */
|
101
103
|
|
102
|
-
const renderPopup = () =>
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
))}
|
120
|
-
</div>
|
121
|
-
</Popup>
|
122
|
-
);
|
123
|
-
};
|
104
|
+
const renderPopup = () => (
|
105
|
+
<Popup
|
106
|
+
className={b('popup-wrapper')}
|
107
|
+
anchorRef={anchor}
|
108
|
+
open={isPopupVisible}
|
109
|
+
placement={['top', 'bottom']}
|
110
|
+
// bigger offset for easier switching to neighbour nodes
|
111
|
+
// matches the default offset for popup with arrow out of a sense of beauty
|
112
|
+
offset={[0, 12]}
|
113
|
+
>
|
114
|
+
<InfoViewer
|
115
|
+
title="PDisk"
|
116
|
+
info={preparePdiskData()}
|
117
|
+
size="s"
|
118
|
+
/>
|
119
|
+
</Popup>
|
120
|
+
);
|
124
121
|
|
125
122
|
const pdiskAllocatedPercent = useMemo(() => {
|
126
123
|
const {AvailableSize, TotalSize} = props;
|
@@ -0,0 +1,40 @@
|
|
1
|
+
import {render} from '@testing-library/react'
|
2
|
+
import {MemoryRouter} from 'react-router-dom';
|
3
|
+
|
4
|
+
import {TPDiskState} from '../../../../types/api/storage'
|
5
|
+
|
6
|
+
import PDisk from '../Pdisk'
|
7
|
+
|
8
|
+
describe('PDisk state', () => {
|
9
|
+
it('Should determine severity based on State', () => {
|
10
|
+
const {getAllByRole} = render(
|
11
|
+
<MemoryRouter>
|
12
|
+
<PDisk
|
13
|
+
NodeId={1}
|
14
|
+
State={TPDiskState.Normal}
|
15
|
+
/>
|
16
|
+
<PDisk
|
17
|
+
NodeId={2}
|
18
|
+
State={TPDiskState.ChunkQuotaError}
|
19
|
+
/>
|
20
|
+
</MemoryRouter>
|
21
|
+
);
|
22
|
+
|
23
|
+
const [normalDisk, erroredDisk] = getAllByRole('meter');
|
24
|
+
|
25
|
+
expect(normalDisk.className).not.toBe(erroredDisk.className);
|
26
|
+
});
|
27
|
+
|
28
|
+
it('Should display as unavailabe when no State is provided', () => {
|
29
|
+
const {getByRole} = render(
|
30
|
+
<MemoryRouter>
|
31
|
+
<PDisk NodeId={1} />
|
32
|
+
</MemoryRouter>
|
33
|
+
);
|
34
|
+
|
35
|
+
const disk = getByRole('meter');
|
36
|
+
|
37
|
+
// unavailable disks display with the highest severity
|
38
|
+
expect(disk.className).toMatch(/_red\b/i);
|
39
|
+
});
|
40
|
+
});
|
@@ -1,23 +1,45 @@
|
|
1
1
|
.global-storage-groups {
|
2
|
+
&__vdisks-column {
|
3
|
+
overflow: visible; // to enable stacked disks overflow the row
|
4
|
+
}
|
5
|
+
|
2
6
|
&__vdisks-wrapper {
|
3
7
|
display: flex;
|
4
|
-
overflow-x: auto;
|
5
|
-
overflow-y: hidden;
|
6
8
|
justify-content: center;
|
7
9
|
|
8
10
|
min-width: 500px;
|
9
11
|
}
|
12
|
+
&__vdisks-item {
|
13
|
+
flex-grow: 1;
|
14
|
+
|
15
|
+
max-width: 200px;
|
16
|
+
margin-right: 10px;
|
17
|
+
|
18
|
+
&:last-child {
|
19
|
+
margin-right: 0px;
|
20
|
+
}
|
21
|
+
|
22
|
+
.stack__layer {
|
23
|
+
background: var(--yc-color-base-background);
|
24
|
+
|
25
|
+
.data-table__row:hover & {
|
26
|
+
background: var(--yc-color-base-float-hover);
|
27
|
+
}
|
28
|
+
}
|
29
|
+
}
|
10
30
|
&__pool-name-wrapper {
|
11
31
|
display: flex;
|
12
32
|
align-items: flex-end;
|
33
|
+
|
34
|
+
width: 230px;
|
13
35
|
}
|
14
36
|
&__pool-name {
|
15
37
|
display: inline-block;
|
16
38
|
overflow: hidden;
|
17
39
|
|
18
|
-
width: 230px;
|
19
40
|
max-width: 230px;
|
20
41
|
|
42
|
+
vertical-align: top;
|
21
43
|
text-overflow: ellipsis;
|
22
44
|
}
|
23
45
|
&__group-id {
|
@@ -4,8 +4,11 @@ import DataTable, {Column, Settings, SortOrder} from '@yandex-cloud/react-data-t
|
|
4
4
|
import {Popover, PopoverBehavior} from '@yandex-cloud/uikit';
|
5
5
|
|
6
6
|
import Vdisk from '../Vdisk/Vdisk';
|
7
|
+
import {Stack} from '../../../components/Stack/Stack';
|
7
8
|
//@ts-ignore
|
8
9
|
import EntityStatus from '../../../components/EntityStatus/EntityStatus';
|
10
|
+
|
11
|
+
import {TVDiskStateInfo} from '../../../types/api/storage';
|
9
12
|
//@ts-ignore
|
10
13
|
import {VisibleEntities} from '../../../store/reducers/storage';
|
11
14
|
//@ts-ignore
|
@@ -190,16 +193,37 @@ function StorageGroups({data, tableSettings, visibleEntities, nodes}: StorageGro
|
|
190
193
|
},
|
191
194
|
{
|
192
195
|
name: TableColumnsIds.VDisks,
|
196
|
+
className: b('vdisks-column'),
|
193
197
|
header: tableColumnsNames[TableColumnsIds.VDisks],
|
194
198
|
render: ({value, row}) => (
|
195
199
|
<div className={b('vdisks-wrapper')}>
|
196
|
-
{_.map(value as
|
197
|
-
|
198
|
-
key={stringifyVdiskId(el.VDiskId)}
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
200
|
+
{_.map(value as TVDiskStateInfo[], (el) => (
|
201
|
+
Array.isArray(el.Donors) && el.Donors.length > 0 ? (
|
202
|
+
<Stack className={b('vdisks-item')} key={stringifyVdiskId(el.VDiskId)}>
|
203
|
+
<Vdisk
|
204
|
+
{...el}
|
205
|
+
PoolName={row[TableColumnsIds.PoolName]}
|
206
|
+
nodes={nodes}
|
207
|
+
/>
|
208
|
+
{el.Donors.map((donor) => (
|
209
|
+
<Vdisk
|
210
|
+
{...donor}
|
211
|
+
// donor and acceptor are always in the same group
|
212
|
+
PoolName={row[TableColumnsIds.PoolName]}
|
213
|
+
nodes={nodes}
|
214
|
+
key={stringifyVdiskId(donor.VDiskId)}
|
215
|
+
/>
|
216
|
+
))}
|
217
|
+
</Stack>
|
218
|
+
) : (
|
219
|
+
<div className={b('vdisks-item')} key={stringifyVdiskId(el.VDiskId)}>
|
220
|
+
<Vdisk
|
221
|
+
{...el}
|
222
|
+
PoolName={row[TableColumnsIds.PoolName]}
|
223
|
+
nodes={nodes}
|
224
|
+
/>
|
225
|
+
</div>
|
226
|
+
)
|
203
227
|
))}
|
204
228
|
</div>
|
205
229
|
),
|