ydb-embedded-ui 1.10.3 → 1.12.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 +37 -0
- package/dist/components/IndexInfoViewer/IndexInfoViewer.tsx +2 -2
- package/dist/components/InfoViewer/InfoViewer.scss +32 -7
- package/dist/components/InfoViewer/InfoViewer.tsx +43 -0
- package/dist/components/InfoViewer/index.ts +1 -0
- package/dist/components/InfoViewer/utils.ts +6 -4
- 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 +31 -3
- package/dist/containers/Storage/StorageGroups/StorageGroups.tsx +72 -33
- 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/Storage/utils/index.ts +49 -0
- package/dist/services/api.d.ts +9 -0
- package/dist/setupTests.js +8 -0
- package/dist/types/api/schema.ts +6 -14
- package/dist/types/api/storage.ts +164 -0
- package/dist/types/index.ts +1 -0
- package/dist/types/store/storage.ts +11 -0
- 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,42 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [1.12.0](https://github.com/ydb-platform/ydb-embedded-ui/compare/v1.11.1...v1.12.0) (2022-08-26)
|
4
|
+
|
5
|
+
|
6
|
+
### Features
|
7
|
+
|
8
|
+
* **Storage:** show usage column ([73aed5f](https://github.com/ydb-platform/ydb-embedded-ui/commit/73aed5f9ed60b6d2bd77fd315ae514ee7443c489))
|
9
|
+
* **Storage:** vividly show degraded disks count ([7315a9c](https://github.com/ydb-platform/ydb-embedded-ui/commit/7315a9cfd98002a7fab85d721712aa82c6dbb552))
|
10
|
+
|
11
|
+
## [1.11.1](https://github.com/ydb-platform/ydb-embedded-ui/compare/v1.11.0...v1.11.1) (2022-08-26)
|
12
|
+
|
13
|
+
|
14
|
+
### Bug Fixes
|
15
|
+
|
16
|
+
* number type instead of string for uint32 ([e60799e](https://github.com/ydb-platform/ydb-embedded-ui/commit/e60799edec4ef831e8c0d51f4384cde83520541d))
|
17
|
+
* **Storage:** expect arbitrary donors data ([09f8e08](https://github.com/ydb-platform/ydb-embedded-ui/commit/09f8e085c94faacd9da502643355e932346502ac))
|
18
|
+
* vdisk data contains pdisk data, not id ([bd1ea7f](https://github.com/ydb-platform/ydb-embedded-ui/commit/bd1ea7f59e0461256bb12f146b50470d21ac1ace))
|
19
|
+
|
20
|
+
## [1.11.0](https://github.com/ydb-platform/ydb-embedded-ui/compare/v1.10.3...v1.11.0) (2022-08-23)
|
21
|
+
|
22
|
+
|
23
|
+
### Features
|
24
|
+
|
25
|
+
* **Stack:** new component for stacked elements ([c42ba37](https://github.com/ydb-platform/ydb-embedded-ui/commit/c42ba37fafdd9dedc4be9d625d7e756a83c01fe3))
|
26
|
+
* **Storage:** display donor disks ([b808fe9](https://github.com/ydb-platform/ydb-embedded-ui/commit/b808fe951987c615f797af56017f8045a1ed852f))
|
27
|
+
* **VDisk:** display label for donors ([bba5ae8](https://github.com/ydb-platform/ydb-embedded-ui/commit/bba5ae8e44347a5b1d9cb72424f5a963a6848e59))
|
28
|
+
|
29
|
+
|
30
|
+
### Bug Fixes
|
31
|
+
|
32
|
+
* **InfoViewer:** add size_s ([fc06451](https://github.com/ydb-platform/ydb-embedded-ui/commit/fc0645118f64a79f660d734c2ff43c42c738fd40))
|
33
|
+
* **PDisk:** new popup design ([9c0355d](https://github.com/ydb-platform/ydb-embedded-ui/commit/9c0355d4d9ccf69d43a5287b0e78d7c7993c4a18))
|
34
|
+
* **PDisk:** restrict component interface ([328efa9](https://github.com/ydb-platform/ydb-embedded-ui/commit/328efa90d214eca1bceeeb5bd9099aab36a3ddb0))
|
35
|
+
* **Storage:** shrink tooltip active area on Pool Name ([30a2b92](https://github.com/ydb-platform/ydb-embedded-ui/commit/30a2b92ff598d9caeabe17a4b8de214943945a91))
|
36
|
+
* **VDisk:** add a missing prop type ([39b6cf3](https://github.com/ydb-platform/ydb-embedded-ui/commit/39b6cf38811cab6c4374c77d3eb63c11fa7b83d5))
|
37
|
+
* **VDisk:** don't paint donors blue ([6b148b9](https://github.com/ydb-platform/ydb-embedded-ui/commit/6b148b914663a74e528a01a35f575f87ed6e9f09))
|
38
|
+
* **VDisk:** new popup design ([107b139](https://github.com/ydb-platform/ydb-embedded-ui/commit/107b13900b08631ea42034a6a2f7961c49c86556))
|
39
|
+
|
3
40
|
## [1.10.3](https://github.com/ydb-platform/ydb-embedded-ui/compare/v1.10.2...v1.10.3) (2022-08-23)
|
4
41
|
|
5
42
|
|
@@ -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',
|
@@ -34,7 +34,7 @@ export const IndexInfoViewer = ({data}: IndexInfoViewerProps) => {
|
|
34
34
|
}
|
35
35
|
|
36
36
|
const TableIndex = data.PathDescription?.TableIndex;
|
37
|
-
const info: Array<
|
37
|
+
const info: Array<InfoViewerItem> = [];
|
38
38
|
|
39
39
|
let key: keyof TIndexDescription;
|
40
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
|
}
|
@@ -51,4 +56,24 @@
|
|
51
56
|
|
52
57
|
white-space: nowrap;
|
53
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
|
+
}
|
54
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
7
|
type ValueFormatters<T> = {
|
6
|
-
[label in keyof T]?: (value: T[label]) =>
|
8
|
+
[label in keyof T]?: (value: T[label]) => ReactNode;
|
7
9
|
}
|
8
10
|
|
9
11
|
function formatLabel<Shape>(label: keyof Shape, map: LabelMap<Shape>) {
|
@@ -14,18 +16,18 @@ function formatValue<Shape, Key extends keyof Shape>(
|
|
14
16
|
label: Key,
|
15
17
|
value: Shape[Key],
|
16
18
|
formatters: ValueFormatters<Shape>,
|
17
|
-
defaultFormatter?: (value: Shape[Key]) =>
|
19
|
+
defaultFormatter?: (value: Shape[Key]) => ReactNode,
|
18
20
|
) {
|
19
21
|
const formatter = formatters[label] || defaultFormatter;
|
20
22
|
const formattedValue = formatter ? formatter(value) : value;
|
21
23
|
|
22
|
-
return
|
24
|
+
return formattedValue;
|
23
25
|
}
|
24
26
|
|
25
27
|
interface CreateInfoFormatterOptions<Shape> {
|
26
28
|
values?: ValueFormatters<Shape>,
|
27
29
|
labels?: LabelMap<Shape>,
|
28
|
-
defaultValueFormatter?: (value: Shape[keyof Shape]) =>
|
30
|
+
defaultValueFormatter?: (value: Shape[keyof Shape]) => ReactNode,
|
29
31
|
}
|
30
32
|
|
31
33
|
export function createInfoFormatter<Shape extends Record<string, any>>({
|
@@ -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,25 +1,53 @@
|
|
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
|
}
|
45
|
+
&__usage-label {
|
46
|
+
&_overload {
|
47
|
+
color: var(--yc-color-text-light-primary);
|
48
|
+
background-color: var(--yc-color-base-danger-heavy);
|
49
|
+
}
|
50
|
+
}
|
23
51
|
&__group-id {
|
24
52
|
font-weight: 500;
|
25
53
|
}
|