Skip to content

Commit 8e05166

Browse files
authored
feat: add storage group page (#1289)
1 parent 6acbc71 commit 8e05166

File tree

21 files changed

+587
-38
lines changed

21 files changed

+587
-38
lines changed

src/components/PDiskInfo/PDiskInfo.scss

-14
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,4 @@
11
.ydb-pdisk-info {
2-
&__wrapper {
3-
display: flex;
4-
flex-flow: row wrap;
5-
gap: 7px;
6-
}
7-
8-
&__col {
9-
display: flex;
10-
flex-direction: column;
11-
gap: 7px;
12-
13-
width: 500px;
14-
}
15-
162
&__links {
173
display: flex;
184
flex-flow: row wrap;

src/components/PDiskInfo/PDiskInfo.tsx

+8-6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import {Flex} from '@gravity-ui/uikit';
2+
13
import {getPDiskPagePath} from '../../routes';
24
import {selectIsUserAllowedToMakeChanges} from '../../store/reducers/authentication/authentication';
35
import {useDiskPagesAvailable} from '../../store/reducers/capabilities/hooks';
@@ -199,15 +201,15 @@ export function PDiskInfo<T extends PreparedPDisk>({
199201
});
200202

201203
return (
202-
<div className={b('wrapper', className)}>
203-
<div className={b('col')}>
204+
<Flex className={className} gap={2} direction="row" wrap>
205+
<Flex direction="column" gap={2} width={500}>
204206
<InfoViewer info={generalInfo} renderEmptyState={() => null} />
205207
<InfoViewer info={spaceInfo} renderEmptyState={() => null} />
206-
</div>
207-
<div className={b('col')}>
208+
</Flex>
209+
<Flex direction="column" gap={2} width={500}>
208210
<InfoViewer info={statusInfo} renderEmptyState={() => null} />
209211
<InfoViewer info={additionalInfo} renderEmptyState={() => null} />
210-
</div>
211-
</div>
212+
</Flex>
213+
</Flex>
212214
);
213215
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
import {Flex} from '@gravity-ui/uikit';
2+
3+
import type {PreparedStorageGroup} from '../../store/reducers/storage/types';
4+
import {valueIsDefined} from '../../utils';
5+
import {formatStorageValuesToGb} from '../../utils/dataFormatters/dataFormatters';
6+
import {formatToMs} from '../../utils/timeParsers';
7+
import {bytesToSpeed} from '../../utils/utils';
8+
import {EntityStatus} from '../EntityStatus/EntityStatus';
9+
import {InfoViewer} from '../InfoViewer';
10+
import type {InfoViewerProps} from '../InfoViewer/InfoViewer';
11+
import {ProgressViewer} from '../ProgressViewer/ProgressViewer';
12+
13+
import {storageGroupInfoKeyset} from './i18n';
14+
15+
interface StorageGroupInfoProps extends Omit<InfoViewerProps, 'info'> {
16+
data?: PreparedStorageGroup;
17+
className?: string;
18+
}
19+
20+
// eslint-disable-next-line complexity
21+
export function StorageGroupInfo({data, className, ...infoViewerProps}: StorageGroupInfoProps) {
22+
const {
23+
Encryption,
24+
Overall,
25+
DiskSpace,
26+
MediaType,
27+
ErasureSpecies,
28+
Used,
29+
Limit,
30+
Usage,
31+
Read,
32+
Write,
33+
GroupGeneration,
34+
Latency,
35+
AllocationUnits,
36+
State,
37+
MissingDisks,
38+
Available,
39+
LatencyPutTabletLog,
40+
LatencyPutUserData,
41+
LatencyGetFast,
42+
} = data || {};
43+
44+
const storageGroupInfoFirstColumn = [];
45+
46+
if (valueIsDefined(GroupGeneration)) {
47+
storageGroupInfoFirstColumn.push({
48+
label: storageGroupInfoKeyset('group-generation'),
49+
value: GroupGeneration,
50+
});
51+
}
52+
if (valueIsDefined(ErasureSpecies)) {
53+
storageGroupInfoFirstColumn.push({
54+
label: storageGroupInfoKeyset('erasure-species'),
55+
value: ErasureSpecies,
56+
});
57+
}
58+
if (valueIsDefined(MediaType)) {
59+
storageGroupInfoFirstColumn.push({
60+
label: storageGroupInfoKeyset('media-type'),
61+
value: MediaType,
62+
});
63+
}
64+
if (valueIsDefined(Encryption)) {
65+
storageGroupInfoFirstColumn.push({
66+
label: storageGroupInfoKeyset('encryption'),
67+
value: Encryption ? storageGroupInfoKeyset('yes') : storageGroupInfoKeyset('no'),
68+
});
69+
}
70+
if (valueIsDefined(Overall)) {
71+
storageGroupInfoFirstColumn.push({
72+
label: storageGroupInfoKeyset('overall'),
73+
value: <EntityStatus status={Overall} />,
74+
});
75+
}
76+
if (valueIsDefined(State)) {
77+
storageGroupInfoFirstColumn.push({label: storageGroupInfoKeyset('state'), value: State});
78+
}
79+
if (valueIsDefined(MissingDisks)) {
80+
storageGroupInfoFirstColumn.push({
81+
label: storageGroupInfoKeyset('missing-disks'),
82+
value: MissingDisks,
83+
});
84+
}
85+
86+
const storageGroupInfoSecondColumn = [];
87+
88+
if (valueIsDefined(Used) && valueIsDefined(Limit)) {
89+
storageGroupInfoSecondColumn.push({
90+
label: storageGroupInfoKeyset('used-space'),
91+
value: (
92+
<ProgressViewer
93+
value={Number(Used)}
94+
capacity={Number(Limit)}
95+
formatValues={formatStorageValuesToGb}
96+
colorizeProgress={true}
97+
/>
98+
),
99+
});
100+
}
101+
if (valueIsDefined(Available)) {
102+
storageGroupInfoSecondColumn.push({
103+
label: storageGroupInfoKeyset('available'),
104+
value: formatStorageValuesToGb(Number(Available)),
105+
});
106+
}
107+
if (valueIsDefined(Usage)) {
108+
storageGroupInfoSecondColumn.push({
109+
label: storageGroupInfoKeyset('usage'),
110+
value: `${Usage.toFixed(2)}%`,
111+
});
112+
}
113+
if (valueIsDefined(DiskSpace)) {
114+
storageGroupInfoSecondColumn.push({
115+
label: storageGroupInfoKeyset('disk-space'),
116+
value: <EntityStatus status={DiskSpace} />,
117+
});
118+
}
119+
if (valueIsDefined(Latency)) {
120+
storageGroupInfoSecondColumn.push({
121+
label: storageGroupInfoKeyset('latency'),
122+
value: <EntityStatus status={Latency} />,
123+
});
124+
}
125+
if (valueIsDefined(LatencyPutTabletLog)) {
126+
storageGroupInfoSecondColumn.push({
127+
label: storageGroupInfoKeyset('latency-put-tablet-log'),
128+
value: formatToMs(LatencyPutTabletLog),
129+
});
130+
}
131+
if (valueIsDefined(LatencyPutUserData)) {
132+
storageGroupInfoSecondColumn.push({
133+
label: storageGroupInfoKeyset('latency-put-user-data'),
134+
value: formatToMs(LatencyPutUserData),
135+
});
136+
}
137+
if (valueIsDefined(LatencyGetFast)) {
138+
storageGroupInfoSecondColumn.push({
139+
label: storageGroupInfoKeyset('latency-get-fast'),
140+
value: formatToMs(LatencyGetFast),
141+
});
142+
}
143+
if (valueIsDefined(AllocationUnits)) {
144+
storageGroupInfoSecondColumn.push({
145+
label: storageGroupInfoKeyset('allocation-units'),
146+
value: AllocationUnits,
147+
});
148+
}
149+
if (valueIsDefined(Read)) {
150+
storageGroupInfoSecondColumn.push({
151+
label: storageGroupInfoKeyset('read-throughput'),
152+
value: bytesToSpeed(Number(Read)),
153+
});
154+
}
155+
if (valueIsDefined(Write)) {
156+
storageGroupInfoSecondColumn.push({
157+
label: storageGroupInfoKeyset('write-throughput'),
158+
value: bytesToSpeed(Number(Write)),
159+
});
160+
}
161+
162+
return (
163+
<Flex className={className} gap={2} direction="row" wrap>
164+
<InfoViewer info={storageGroupInfoFirstColumn} {...infoViewerProps} />
165+
<InfoViewer info={storageGroupInfoSecondColumn} {...infoViewerProps} />
166+
</Flex>
167+
);
168+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"encryption": "Encryption",
3+
"overall": "Overall",
4+
"disk-space": "Disk Space",
5+
"media-type": "Media Type",
6+
"erasure-species": "Erasure Species",
7+
"used-space": "Used Space",
8+
"usage": "Usage",
9+
"read-throughput": "Read Throughput",
10+
"write-throughput": "Write Throughput",
11+
"yes": "Yes",
12+
"no": "No",
13+
"group-generation": "Group Generation",
14+
"latency": "Latency",
15+
"allocation-units": "Units",
16+
"state": "State",
17+
"missing-disks": "Missing Disks",
18+
"available": "Available Space",
19+
"latency-put-tablet-log": "Latency (Put Tablet Log)",
20+
"latency-put-user-data": "Latency (Put User Data)",
21+
"latency-get-fast": "Latency (Get Fast)"
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import {registerKeysets} from '../../../utils/i18n';
2+
3+
import en from './en.json';
4+
5+
const COMPONENT = 'storage-group-info';
6+
7+
export const storageGroupInfoKeyset = registerKeysets(COMPONENT, {en});

src/containers/App/Content.tsx

+10
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
PDiskPageSlot,
2929
RedirectSlot,
3030
RoutesSlot,
31+
StorageGroupSlot,
3132
TabletSlot,
3233
TenantSlot,
3334
VDiskPageSlot,
@@ -76,6 +77,15 @@ const routesSlots: RouteSlot[] = [
7677
component: lazyComponent(() => import('../VDiskPage/VDiskPage'), 'VDiskPage'),
7778
wrapper: DataWrapper,
7879
},
80+
{
81+
path: routes.storageGroup,
82+
slot: StorageGroupSlot,
83+
component: lazyComponent(
84+
() => import('../StorageGroupPage/StorageGroupPage'),
85+
'StorageGroupPage',
86+
),
87+
wrapper: DataWrapper,
88+
},
7989
{
8090
path: routes.tablet,
8191
slot: TabletSlot,

src/containers/App/appSlots.tsx

+6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type {Cluster} from '../Cluster/Cluster';
55
import type {Clusters} from '../Clusters/Clusters';
66
import type {Node} from '../Node/Node';
77
import type {PDiskPage} from '../PDiskPage/PDiskPage';
8+
import type {StorageGroupPage} from '../StorageGroupPage/StorageGroupPage';
89
import type {Tablet} from '../Tablet';
910
import type {Tenant} from '../Tenant/Tenant';
1011
import type {VDiskPage} from '../VDiskPage/VDiskPage';
@@ -39,6 +40,11 @@ export const VDiskPageSlot = createSlot<{
3940
| React.ReactNode
4041
| ((props: {component: typeof VDiskPage} & RouteComponentProps) => React.ReactNode);
4142
}>('vDisk');
43+
export const StorageGroupSlot = createSlot<{
44+
children:
45+
| React.ReactNode
46+
| ((props: {component: typeof StorageGroupPage} & RouteComponentProps) => React.ReactNode);
47+
}>('storageGroup');
4248
export const TabletSlot = createSlot<{
4349
children:
4450
| React.ReactNode

src/containers/Header/breadcrumbs.tsx

+23
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import type {
1313
NodeBreadcrumbsOptions,
1414
PDiskBreadcrumbsOptions,
1515
Page,
16+
StorageGroupBreadcrumbsOptions,
1617
TabletBreadcrumbsOptions,
1718
TenantBreadcrumbsOptions,
1819
VDiskBreadcrumbsOptions,
@@ -156,6 +157,27 @@ const getVDiskBreadcrumbs: GetBreadcrumbs<VDiskBreadcrumbsOptions> = (options, q
156157
return breadcrumbs;
157158
};
158159

160+
const getStorageGroupBreadcrumbs: GetBreadcrumbs<StorageGroupBreadcrumbsOptions> = (
161+
options,
162+
query = {},
163+
) => {
164+
const {groupId} = options;
165+
166+
const breadcrumbs = getClusterBreadcrumbs(options, query);
167+
168+
let text = headerKeyset('breadcrumbs.storageGroup');
169+
if (groupId) {
170+
text += ` ${groupId}`;
171+
}
172+
173+
const lastItem = {
174+
text,
175+
};
176+
breadcrumbs.push(lastItem);
177+
178+
return breadcrumbs;
179+
};
180+
159181
const getTabletBreadcrumbs: GetBreadcrumbs<TabletBreadcrumbsOptions> = (options, query = {}) => {
160182
const {tabletId, tabletType, nodeId, nodeRole, nodeActiveTab = TABLETS, tenantName} = options;
161183

@@ -178,6 +200,7 @@ const mapPageToGetter = {
178200
tablet: getTabletBreadcrumbs,
179201
tenant: getTenantBreadcrumbs,
180202
vDisk: getVDiskBreadcrumbs,
203+
storageGroup: getStorageGroupBreadcrumbs,
181204
} as const;
182205

183206
export const getBreadcrumbs = (

src/containers/Header/i18n/en.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@
44
"breadcrumbs.pDisk": "PDisk",
55
"breadcrumbs.vDisk": "VDisk",
66
"breadcrumbs.tablet": "Tablet",
7-
"breadcrumbs.tablets": "Tablets"
7+
"breadcrumbs.tablets": "Tablets",
8+
"breadcrumbs.storageGroup": "Storage Group"
89
}

src/containers/PDiskPage/PDiskPage.scss

+15-4
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,24 @@
33
.ydb-pdisk-page {
44
position: relative;
55

6-
display: flex;
76
overflow: auto;
8-
flex-direction: column;
9-
gap: 20px;
107

118
height: 100%;
12-
padding: 20px;
9+
10+
&__info-content {
11+
position: sticky;
12+
left: 0;
13+
14+
display: flex;
15+
flex-direction: column;
16+
gap: 20px;
17+
18+
padding: 20px;
19+
}
20+
21+
&__tabs-content {
22+
padding-left: 20px;
23+
}
1324

1425
&__meta,
1526
&__title,

src/containers/PDiskPage/PDiskPage.tsx

+10-8
Original file line numberDiff line numberDiff line change
@@ -252,14 +252,16 @@ export function PDiskPage() {
252252

253253
return (
254254
<div className={pdiskPageCn(null)}>
255-
{renderHelmet()}
256-
{renderPageMeta()}
257-
{renderPageTitle()}
258-
{renderControls()}
259-
{renderError()}
260-
{renderInfo()}
261-
{renderTabs()}
262-
{renderTabsContent()}
255+
<div className={pdiskPageCn('info-content')}>
256+
{renderHelmet()}
257+
{renderPageMeta()}
258+
{renderPageTitle()}
259+
{renderControls()}
260+
{renderError()}
261+
{renderInfo()}
262+
{renderTabs()}
263+
</div>
264+
<div className={pdiskPageCn('tabs-content')}>{renderTabsContent()}</div>
263265
</div>
264266
);
265267
}

0 commit comments

Comments
 (0)