Skip to content

Commit ee06b4e

Browse files
authored
feat(Cluster): rework cluster page (#1473)
1 parent 3dda3fe commit ee06b4e

33 files changed

+883
-316
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
.ydb-diagnostic-card {
22
flex-shrink: 0;
33

4-
width: 206px;
54
padding: 16px;
65
padding-bottom: 28px;
76

@@ -13,10 +12,24 @@
1312
border-color: var(--g-color-base-info-medium);
1413
background-color: var(--g-color-base-selection);
1514
}
15+
&_interactive {
16+
&:hover {
17+
cursor: pointer;
1618

17-
&:hover {
18-
cursor: pointer;
19+
box-shadow: 0px 1px 5px var(--g-color-sfx-shadow);
20+
}
21+
}
1922

20-
box-shadow: 0px 1px 5px var(--g-color-sfx-shadow);
23+
&_size_m {
24+
width: 206px;
25+
min-width: 206px;
26+
}
27+
&_size_l {
28+
width: 289px;
29+
min-width: 289px;
30+
}
31+
&_size_s {
32+
width: 134px;
33+
min-width: 134px;
2134
}
2235
}

src/components/DiagnosticCard/DiagnosticCard.tsx

+11-3
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,20 @@ import './DiagnosticCard.scss';
44

55
const b = cn('ydb-diagnostic-card');
66

7-
interface DiagnosticCardProps {
7+
export interface DiagnosticCardProps {
88
children?: React.ReactNode;
99
className?: string;
1010
active?: boolean;
11+
size?: 'm' | 'l' | 's';
12+
interactive?: boolean;
1113
}
1214

13-
export function DiagnosticCard({children, className, active}: DiagnosticCardProps) {
14-
return <div className={b({active}, className)}>{children}</div>;
15+
export function DiagnosticCard({
16+
children,
17+
className,
18+
active,
19+
size = 'm',
20+
interactive = true,
21+
}: DiagnosticCardProps) {
22+
return <div className={b({active, size, interactive}, className)}>{children}</div>;
1523
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
.ydb-doughnut-metrics {
2+
--doughnut-border: 11px;
3+
--doughnut-color: var(--ydb-color-status-green);
4+
&__doughnut {
5+
position: relative;
6+
7+
width: 172px;
8+
aspect-ratio: 1;
9+
10+
border-radius: 50%;
11+
background-color: var(--doughnut-color);
12+
&::before {
13+
display: block;
14+
15+
height: calc(100% - calc(var(--doughnut-border) * 2));
16+
17+
content: '';
18+
19+
border-radius: 50%;
20+
background-color: var(--g-color-base-background);
21+
22+
transform: translate(var(--doughnut-border), var(--doughnut-border));
23+
aspect-ratio: 1;
24+
}
25+
}
26+
&__doughnut_status_warning {
27+
--doughnut-color: var(--ydb-color-status-yellow);
28+
}
29+
&__doughnut_status_danger {
30+
--doughnut-color: var(--ydb-color-status-red);
31+
}
32+
&__text-wrapper {
33+
--wrapper-indent: calc(var(--doughnut-border) + 5px);
34+
35+
position: absolute;
36+
top: var(--wrapper-indent);
37+
right: var(--wrapper-indent);
38+
39+
display: flex;
40+
flex-direction: column;
41+
justify-content: center;
42+
align-items: center;
43+
44+
width: calc(100% - calc(var(--wrapper-indent) * 2));
45+
46+
text-align: center;
47+
aspect-ratio: 1;
48+
}
49+
&__value {
50+
position: absolute;
51+
bottom: 20px;
52+
}
53+
&__legend {
54+
height: 50%;
55+
56+
white-space: pre-wrap;
57+
}
58+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import React from 'react';
2+
3+
import type {TextProps} from '@gravity-ui/uikit';
4+
import {Text} from '@gravity-ui/uikit';
5+
6+
import {cn} from '../../utils/cn';
7+
import type {ProgressStatus} from '../../utils/progress';
8+
9+
import './DoughnutMetrics.scss';
10+
11+
const b = cn('ydb-doughnut-metrics');
12+
13+
interface LegendProps {
14+
children?: React.ReactNode;
15+
variant?: TextProps['variant'];
16+
}
17+
18+
function Legend({children, variant = 'subheader-3'}: LegendProps) {
19+
return (
20+
<Text variant={variant} color="secondary" className={b('legend')}>
21+
{children}
22+
</Text>
23+
);
24+
}
25+
function Value({children, variant = 'subheader-2'}: LegendProps) {
26+
return (
27+
<Text variant={variant} color="secondary" className={b('value')}>
28+
{children}
29+
</Text>
30+
);
31+
}
32+
33+
interface DoughnutProps {
34+
status: ProgressStatus;
35+
fillWidth: number;
36+
children?: React.ReactNode;
37+
className?: string;
38+
}
39+
40+
export function DoughnutMetrics({status, fillWidth, children, className}: DoughnutProps) {
41+
let gradientFill = 'var(--g-color-line-generic-solid)';
42+
let filledDegrees = fillWidth * 3.6 - 90;
43+
44+
if (fillWidth > 50) {
45+
gradientFill = 'var(--doughnut-color)';
46+
filledDegrees = fillWidth * 3.6 + 90;
47+
}
48+
const gradientDegrees = filledDegrees;
49+
return (
50+
<div className={b(null, className)}>
51+
<div
52+
style={{
53+
backgroundImage: `linear-gradient(${gradientDegrees}deg, transparent 50%, ${gradientFill} 50%), linear-gradient(-90deg, var(--g-color-line-generic-solid) 50%, transparent 50%)`,
54+
}}
55+
className={b('doughnut', {status})}
56+
>
57+
<div className={b('text-wrapper')}>{children}</div>
58+
</div>
59+
</div>
60+
);
61+
}
62+
63+
DoughnutMetrics.Legend = Legend;
64+
DoughnutMetrics.Value = Value;

src/components/ProgressViewer/ProgressViewer.scss

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
justify-content: center;
1212
align-items: center;
1313

14-
min-width: 120px;
14+
min-width: 150px;
1515
height: 23px;
1616
padding: 0 4px;
1717

src/components/ProgressViewer/ProgressViewer.tsx

+10-12
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {useTheme} from '@gravity-ui/uikit';
22

33
import {cn} from '../../utils/cn';
44
import {formatNumber, roundToPrecision} from '../../utils/dataFormatters/dataFormatters';
5+
import {calculateProgressStatus} from '../../utils/progress';
56
import {isNumeric} from '../../utils/utils';
67

78
import './ProgressViewer.scss';
@@ -10,8 +11,6 @@ const b = cn('progress-viewer');
1011

1112
type ProgressViewerSize = 'xs' | 's' | 'ns' | 'm' | 'n' | 'l' | 'head';
1213

13-
type ProgressViewerStatus = 'good' | 'warning' | 'danger';
14-
1514
type FormatProgressViewerValues = (
1615
value?: number,
1716
capacity?: number,
@@ -79,16 +78,15 @@ export function ProgressViewer({
7978
[valueText, capacityText] = formatValues(Number(value), Number(capacity));
8079
}
8180

82-
let status: ProgressViewerStatus = inverseColorize ? 'danger' : 'good';
83-
if (colorizeProgress) {
84-
if (fillWidth > warningThreshold && fillWidth <= dangerThreshold) {
85-
status = 'warning';
86-
} else if (fillWidth > dangerThreshold) {
87-
status = inverseColorize ? 'good' : 'danger';
88-
}
89-
if (!isNumeric(capacity)) {
90-
fillWidth = 100;
91-
}
81+
const status = calculateProgressStatus({
82+
fillWidth,
83+
warningThreshold,
84+
dangerThreshold,
85+
colorizeProgress,
86+
inverseColorize,
87+
});
88+
if (colorizeProgress && !isNumeric(capacity)) {
89+
fillWidth = 100;
9290
}
9391

9492
const lineStyle = {

src/components/Tag/Tag.scss

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
.tag {
2-
margin-right: 5px;
32
padding: 2px 5px;
43

54
font-size: 12px;
6-
text-transform: uppercase;
5+
white-space: nowrap;
76

87
color: var(--g-color-text-primary);
98
border-radius: 3px;

src/components/Tags/Tags.tsx

+7-8
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,23 @@
11
import React from 'react';
22

3-
import {cn} from '../../utils/cn';
3+
import type {FlexProps} from '@gravity-ui/uikit';
4+
import {Flex} from '@gravity-ui/uikit';
5+
46
import type {TagType} from '../Tag';
57
import {Tag} from '../Tag';
68

7-
import './Tags.scss';
8-
9-
const b = cn('tags');
10-
119
interface TagsProps {
1210
tags: React.ReactNode[];
1311
tagsType?: TagType;
1412
className?: string;
13+
gap?: FlexProps['gap'];
1514
}
1615

17-
export const Tags = ({tags, tagsType, className = ''}: TagsProps) => {
16+
export const Tags = ({tags, tagsType, className = '', gap = 1}: TagsProps) => {
1817
return (
19-
<div className={b(null, className)}>
18+
<Flex className={className} gap={gap} wrap="wrap" alignItems="center">
2019
{tags &&
2120
tags.map((tag, tagIndex) => <Tag text={tag} key={tagIndex} type={tagsType}></Tag>)}
22-
</div>
21+
</Flex>
2322
);
2423
};

src/containers/Cluster/Cluster.scss

+30-7
Original file line numberDiff line numberDiff line change
@@ -27,27 +27,50 @@
2727
height: var(--g-text-header-1-line-height);
2828
}
2929

30-
&__tabs {
31-
position: sticky;
32-
left: 0;
30+
&__tabs-sticky-wrapper {
31+
z-index: 3;
3332

33+
margin-top: 20px;
34+
margin-right: -20px;
35+
padding-right: 20px;
36+
@include sticky-top();
37+
}
38+
&__tabs {
3439
display: flex;
35-
justify-content: space-between;
36-
align-items: center;
3740
@include tabs-wrapper-styles();
3841
}
3942

4043
&__sticky-wrapper {
4144
position: sticky;
4245
z-index: 4;
43-
top: 56px;
46+
top: 66px;
4447
left: 0;
4548
}
4649

4750
&__auto-refresh-control {
4851
float: right;
4952

50-
margin-top: -40px;
53+
margin-top: -46px;
54+
55+
background-color: var(--g-color-base-background);
56+
}
57+
.ydb-table-with-controls-layout__controls-wrapper {
58+
top: 40px;
59+
}
60+
61+
&__tablets {
62+
.data-table__sticky_moving {
63+
// Place table head right after controls
64+
top: 60px !important;
65+
}
66+
}
67+
68+
&__fake-block {
69+
position: sticky;
70+
z-index: 3;
71+
top: 40px;
72+
73+
height: 20px;
5174

5275
background-color: var(--g-color-base-background);
5376
}

src/containers/Cluster/Cluster.tsx

+6-5
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {TabletsTable} from '../Tablets/TabletsTable';
3131
import {Tenants} from '../Tenants/Tenants';
3232
import {Versions} from '../Versions/Versions';
3333

34+
import {ClusterDashboard} from './ClusterDashboard/ClusterDashboard';
3435
import {ClusterInfo} from './ClusterInfo/ClusterInfo';
3536
import type {ClusterTab} from './utils';
3637
import {clusterTabs, clusterTabsIds, getClusterPath, isClusterTab} from './utils';
@@ -119,7 +120,11 @@ export function Cluster({
119120
{activeTab ? <title>{activeTab.title}</title> : null}
120121
</Helmet>
121122
<div className={b('header')}>{getClusterTitle()}</div>
122-
<div className={b('tabs')}>
123+
<div className={b('sticky-wrapper')}>
124+
<AutoRefreshControl className={b('auto-refresh-control')} />
125+
</div>
126+
<ClusterDashboard cluster={cluster} groupStats={groupsStats} loading={infoLoading} />
127+
<div className={b('tabs-sticky-wrapper')}>
123128
<Tabs
124129
size="l"
125130
allowNotSelected={true}
@@ -141,9 +146,6 @@ export function Cluster({
141146
}}
142147
/>
143148
</div>
144-
<div className={b('sticky-wrapper')}>
145-
<AutoRefreshControl className={b('auto-refresh-control')} />
146-
</div>
147149
<Switch>
148150
<Route
149151
path={
@@ -152,7 +154,6 @@ export function Cluster({
152154
>
153155
<ClusterInfo
154156
cluster={cluster}
155-
groupsStats={groupsStats}
156157
versionToColor={versionToColor}
157158
loading={infoLoading}
158159
error={clusterError}

0 commit comments

Comments
 (0)