Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import type {BytesSizes} from '../../../../utils/bytesParsers';
import {getBytesSizeUnit, sizes} from '../../../../utils/bytesParsers';
import {EMPTY_DATA_PLACEHOLDER, UNBREAKABLE_GAP} from '../../../../utils/constants';
import {EMPTY_DATA_PLACEHOLDER} from '../../../../utils/constants';
import {formatNumber, formatPercent} from '../../../../utils/dataFormatters/dataFormatters';
import {
formatMetricBytes as formatMetricBytesShared,
getConsistentMetricBytesSize as getConsistentMetricBytesSizeShared,
} from '../../../../utils/storageMetrics';

export const STORAGE_USAGE_INITIAL_ROWS_COUNT = 3;

Expand All @@ -10,54 +12,8 @@ const MIN_SHARE_PERCENT_WITH_FRACTION = 1;
const PERCENT_MULTIPLIER = 100;
const MAX_PROGRESS_PERCENT = 100;

function getMetricBytesDecimalPlaces(size: BytesSizes, convertedValue: number) {
if (size === 'b' || size === 'kb' || size === 'mb') {
return convertedValue % 1 === 0 ? 0 : 1;
}
if (size === 'gb') {
if (convertedValue < 1) {
return 2;
}
return convertedValue % 1 === 0 ? 0 : 1;
}
// TB and PB: <10 → 2 decimal places, 10+ → 1 decimal place
if (convertedValue < 10) {
return 2;
}
return 1;
}

export function getConsistentMetricBytesSize(values: Array<string | number | undefined>) {
const maxValue = values.reduce<number>((currentMaxValue, value) => {
const numericValue = Number(value);

if (!Number.isFinite(numericValue) || numericValue < 0) {
return currentMaxValue;
}

return Math.max(currentMaxValue, numericValue);
}, 0);

return getBytesSizeUnit(maxValue);
}

export function formatMetricBytes(value?: string | number, size?: BytesSizes) {
const numericValue = Number(value);

if (!Number.isFinite(numericValue)) {
return EMPTY_DATA_PLACEHOLDER;
}

const resolvedSize = size ?? getBytesSizeUnit(numericValue);
const convertedValue = numericValue / sizes[resolvedSize].value;
const decimalPlaces = getMetricBytesDecimalPlaces(resolvedSize, convertedValue);
const rounded = Number(convertedValue.toFixed(decimalPlaces));
const formatted = formatNumber(rounded);

return formatted
? `${formatted}${UNBREAKABLE_GAP}${sizes[resolvedSize].label}`
: EMPTY_DATA_PLACEHOLDER;
}
export const getConsistentMetricBytesSize = getConsistentMetricBytesSizeShared;
export const formatMetricBytes = formatMetricBytesShared;

export function formatOverhead(
diskUsage: number | undefined,
Expand Down
4 changes: 4 additions & 0 deletions src/containers/VDiskPage/VDiskPage.scss
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@
gap: var(--g-spacing-2);
}

&__storage-details {
margin-bottom: var(--g-spacing-5);
}

&__tablets-content {
margin-top: var(--g-spacing-4);
}
Expand Down
56 changes: 34 additions & 22 deletions src/containers/VDiskPage/VDiskPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {PageMetaWithAutorefresh} from '../../components/PageMeta/PageMeta';
import {VDiskInfo} from '../../components/VDiskInfo/VDiskInfo';
import {useVDiskPagePath} from '../../routes';
import {api} from '../../store/reducers/api';
import {useNewStorageViewEnabled} from '../../store/reducers/capabilities/hooks';
import {setHeaderBreadcrumbs} from '../../store/reducers/header/header';
import {vDiskApi} from '../../store/reducers/vdisk/vdisk';
import type {TVDiskID} from '../../types/api/vdisk';
Expand All @@ -29,6 +30,7 @@ import {useAutoRefreshInterval, useTypedDispatch} from '../../utils/hooks';
import {useAppTitle} from '../App/AppTitleContext';
import {PaginatedStorage} from '../Storage/PaginatedStorage';

import {VDiskStorageDetails} from './VDiskStorageDetails';
import {VDiskTablets} from './VDiskTablets';
import {vDiskPageKeyset} from './i18n';

Expand Down Expand Up @@ -73,6 +75,7 @@ export function VDiskPage() {
const database = databaseParam ?? undefined;

const vDiskTab = vDiskTabSchema.parse(activeTab);
const newStorageViewEnabled = useNewStorageViewEnabled();

const [autoRefreshInterval] = useAutoRefreshInterval();

Expand Down Expand Up @@ -218,6 +221,14 @@ export function VDiskPage() {
return <VDiskInfo data={vDiskData} className={vDiskPageCn('info')} wrap />;
};

const renderStorageDetails = () => {
if (!newStorageViewEnabled) {
return null;
}

return <VDiskStorageDetails data={vDiskData} className={vDiskPageCn('storage-details')} />;
};

const renderTabs = () => {
return (
<div className={vDiskPageCn('tabs')}>
Expand Down Expand Up @@ -246,28 +257,6 @@ export function VDiskPage() {
);
};

const renderTabsContent = () => {
switch (vDiskTab) {
case 'storage': {
return renderStorageInfo();
}
case 'tablets': {
return (
<VDiskTablets
scrollContainerRef={containerRef}
nodeId={nodeId ?? undefined}
pDiskId={PDiskId}
vDiskSlotId={vDiskSlotId ?? undefined}
vDiskId={StringifiedId}
className={vDiskPageCn('tablets-content')}
/>
);
}
default:
return null;
}
};

const renderStorageInfo = () => {
if (!isNil(GroupID)) {
return (
Expand All @@ -290,6 +279,28 @@ export function VDiskPage() {
return null;
};

const renderTabsContent = () => {
switch (vDiskTab) {
case 'storage': {
return renderStorageInfo();
}
case 'tablets': {
return (
<VDiskTablets
scrollContainerRef={containerRef}
nodeId={nodeId ?? undefined}
pDiskId={PDiskId}
vDiskSlotId={vDiskSlotId ?? undefined}
vDiskId={StringifiedId}
className={vDiskPageCn('tablets-content')}
/>
);
}
default:
return null;
}
};

const renderContent = () => {
if (loading) {
return <InfoViewerSkeleton rows={9} />;
Expand All @@ -299,6 +310,7 @@ export function VDiskPage() {
<React.Fragment>
{error ? <ResponseError error={error} /> : null}
{renderInfo()}
{renderStorageDetails()}
{renderTabs()}
{renderTabsContent()}
</React.Fragment>
Expand Down
129 changes: 129 additions & 0 deletions src/containers/VDiskPage/VDiskStorageDetails.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
.ydb-vdisk-storage-details {
&__title {
margin-bottom: var(--g-spacing-4);
}

&__cards {
display: flex;
flex-wrap: wrap;
align-items: flex-start;
gap: var(--g-spacing-1);

width: fit-content;
max-width: 100%;
}

&__card {
min-width: 0;
padding: var(--g-spacing-4) var(--g-spacing-5);

border-radius: var(--g-border-radius-m) var(--g-border-radius-xs) var(--g-border-radius-xs)
var(--g-border-radius-m);
background-color: var(--g-color-base-generic);

&_metrics {
display: flex;
flex: 0 0 auto;
align-items: stretch;
gap: var(--g-spacing-5);

width: max-content;
max-width: 100%;
min-height: 74px;
}

&_details {
display: flex;
flex: 0 1 auto;
flex-wrap: wrap;
justify-content: flex-start;
align-items: center;
gap: var(--g-spacing-4);

width: fit-content;
max-width: 100%;
min-height: 74px;

border-radius: var(--g-border-radius-xs) var(--g-border-radius-m)
var(--g-border-radius-m) var(--g-border-radius-xs);
}
}

&__details {
display: flex;
flex: 0 1 auto;
flex-wrap: wrap;
align-items: stretch;
gap: var(--g-spacing-4);

min-width: 0;
max-width: 100%;
}

&__metric,
&__detail {
display: flex;
flex-direction: column;
justify-content: center;
align-items: flex-start;
gap: var(--g-spacing-1);

min-width: 0;
padding-right: var(--g-spacing-4);
}

&__detail {
flex: 0 1 auto;

min-width: 100px;
max-width: 200px;
}

&__value,
&__label {
display: block;

white-space: nowrap;
}

&__value-row &__value {
overflow: hidden;

text-overflow: ellipsis;
}

&__value-row {
flex-wrap: nowrap;

width: 100%;
min-width: 0;
height: 20px;

&_copyable {
width: auto;
max-width: 100%;
}
}

&__value-popover {
flex: 1 1 auto;

min-width: 0;

&_copyable {
flex: 0 1 auto;

max-width: 100%;
}
}

&__value-popover-content {
width: 100%;
min-width: 0;

&_copyable {
width: auto;
max-width: 100%;
}
}
}
Loading
Loading