VDisks
{renderVDisks()}
diff --git a/src/containers/Node/NodeStructure/Vdisk.tsx b/src/containers/Node/NodeStructure/Vdisk.tsx
index ca6626696..3c8c1ddc3 100644
--- a/src/containers/Node/NodeStructure/Vdisk.tsx
+++ b/src/containers/Node/NodeStructure/Vdisk.tsx
@@ -8,12 +8,11 @@ import {
stringifyVdiskId,
} from '../../../utils/dataFormatters/dataFormatters';
import {bytesToGB, bytesToSpeed} from '../../../utils/utils';
+import {valueIsDefined} from '../../../utils';
import {EntityStatus} from '../../../components/EntityStatus/EntityStatus';
import InfoViewer from '../../../components/InfoViewer/InfoViewer';
import {ProgressViewer} from '../../../components/ProgressViewer/ProgressViewer';
-import {valueIsDefined} from './NodeStructure';
-
const b = cn('kv-node-structure');
export function Vdisk({
diff --git a/src/containers/PDisk/PDisk.scss b/src/containers/PDisk/PDisk.scss
new file mode 100644
index 000000000..89525b07e
--- /dev/null
+++ b/src/containers/PDisk/PDisk.scss
@@ -0,0 +1,41 @@
+@import '../../styles//mixins.scss';
+
+.ydb-pdisk-page {
+ position: relative;
+
+ display: flex;
+ overflow: auto;
+ flex-direction: column;
+
+ height: 100%;
+ padding-top: 20px;
+ padding-left: 20px;
+
+ gap: 20px;
+
+ &__meta,
+ &__title,
+ &__info,
+ &__groups-title {
+ position: sticky;
+ left: 0;
+ }
+
+ &__title {
+ display: flex;
+ flex-flow: row nowrap;
+ align-items: baseline;
+
+ @include header-2-typography();
+
+ &__prefix {
+ margin-right: 6px;
+
+ color: var(--g-color-text-secondary);
+ }
+ }
+
+ &__groups-title {
+ @include header-1-typography();
+ }
+}
diff --git a/src/containers/PDisk/PDisk.tsx b/src/containers/PDisk/PDisk.tsx
new file mode 100644
index 000000000..5b8ac7cf0
--- /dev/null
+++ b/src/containers/PDisk/PDisk.tsx
@@ -0,0 +1,133 @@
+import {useCallback, useEffect} from 'react';
+import {StringParam, useQueryParams} from 'use-query-params';
+import {Helmet} from 'react-helmet-async';
+
+import {
+ getPDiskData,
+ getPDiskStorage,
+ setPDiskDataWasNotLoaded,
+} from '../../store/reducers/pdisk/pdisk';
+import {setHeaderBreadcrumbs} from '../../store/reducers/header/header';
+import {getNodesList, selectNodesMap} from '../../store/reducers/nodesList';
+
+import {useAutofetcher, useTypedDispatch, useTypedSelector} from '../../utils/hooks';
+import {getSeverityColor} from '../../utils/disks/helpers';
+
+import {PageMeta} from '../../components/PageMeta/PageMeta';
+import {StatusIcon} from '../../components/StatusIcon/StatusIcon';
+import {PDiskInfo} from '../../components/PDiskInfo/PDiskInfo';
+import {InfoViewerSkeleton} from '../../components/InfoViewerSkeleton/InfoViewerSkeleton';
+
+import {PDiskGroups} from './PDiskGroups';
+import {pdiskPageCn} from './shared';
+import {pDiskPageKeyset} from './i18n';
+
+import './PDisk.scss';
+
+export function PDisk() {
+ const dispatch = useTypedDispatch();
+
+ const nodesMap = useTypedSelector(selectNodesMap);
+ const {pDiskData, groupsData, pDiskLoading, pDiskWasLoaded, groupsLoading, groupsWasLoaded} =
+ useTypedSelector((state) => state.pDisk);
+ const {NodeHost, NodeId, NodeType, NodeDC, Severity} = pDiskData;
+
+ const [{nodeId, pDiskId}] = useQueryParams({
+ nodeId: StringParam,
+ pDiskId: StringParam,
+ });
+
+ useEffect(() => {
+ dispatch(setHeaderBreadcrumbs('pDisk', {nodeId, pDiskId}));
+ }, [dispatch, nodeId, pDiskId]);
+
+ useEffect(() => {
+ dispatch(getNodesList());
+ }, [dispatch]);
+
+ const fetchData = useCallback(
+ (isBackground: boolean) => {
+ if (!isBackground) {
+ dispatch(setPDiskDataWasNotLoaded());
+ }
+ if (nodeId && pDiskId) {
+ dispatch(getPDiskData({nodeId, pDiskId}));
+ dispatch(getPDiskStorage({nodeId, pDiskId}));
+ }
+ },
+ [dispatch, nodeId, pDiskId],
+ );
+
+ useAutofetcher(fetchData, [fetchData], true);
+
+ const renderHelmet = () => {
+ const pDiskPagePart = pDiskId
+ ? `${pDiskPageKeyset('pdisk')} ${pDiskId}`
+ : pDiskPageKeyset('pdisk');
+
+ const nodePagePart = NodeHost ? NodeHost : pDiskPageKeyset('node');
+
+ return (
+
+ );
+ };
+
+ const renderPageMeta = () => {
+ const hostItem = NodeHost ? `${pDiskPageKeyset('fqdn')}: ${NodeHost}` : undefined;
+ const nodeIdItem = NodeId ? `${pDiskPageKeyset('node')}: ${NodeId}` : undefined;
+
+ return (
+
+ );
+ };
+
+ const renderPageTitle = () => {
+ return (
+
+ {pDiskPageKeyset('pdisk')}
+
+ {pDiskId}
+
+ );
+ };
+
+ const renderInfo = () => {
+ if (pDiskLoading && !pDiskWasLoaded) {
+ return
;
+ }
+ return (
+
+ );
+ };
+
+ const renderGroupsTable = () => {
+ return (
+
+ );
+ };
+
+ return (
+
+ {renderHelmet()}
+ {renderPageMeta()}
+ {renderPageTitle()}
+ {renderInfo()}
+ {renderGroupsTable()}
+
+ );
+}
diff --git a/src/containers/PDisk/PDiskGroups.tsx b/src/containers/PDisk/PDiskGroups.tsx
new file mode 100644
index 000000000..80bfd4a9b
--- /dev/null
+++ b/src/containers/PDisk/PDiskGroups.tsx
@@ -0,0 +1,49 @@
+import {useMemo} from 'react';
+
+import DataTable from '@gravity-ui/react-data-table';
+
+import type {PreparedStorageGroup} from '../../store/reducers/storage/types';
+import type {NodesMap} from '../../types/store/nodesList';
+
+import {TableSkeleton} from '../../components/TableSkeleton/TableSkeleton';
+
+import {DEFAULT_TABLE_SETTINGS} from '../../utils/constants';
+
+import {getPDiskStorageColumns} from '../Storage/StorageGroups/getStorageGroupsColumns';
+
+import {pdiskPageCn} from './shared';
+import {pDiskPageKeyset} from './i18n';
+
+interface PDiskGroupsProps {
+ data: PreparedStorageGroup[];
+ nodesMap?: NodesMap;
+ loading?: boolean;
+}
+
+export function PDiskGroups({data, nodesMap, loading}: PDiskGroupsProps) {
+ const pDiskStorageColumns = useMemo(() => {
+ return getPDiskStorageColumns(nodesMap);
+ }, [nodesMap]);
+
+ const renderContent = () => {
+ if (loading) {
+ return
;
+ }
+
+ return (
+
+ );
+ };
+
+ return (
+ <>
+
{pDiskPageKeyset('groups')}
+
{renderContent()}
+ >
+ );
+}
diff --git a/src/containers/PDisk/i18n/en.json b/src/containers/PDisk/i18n/en.json
new file mode 100644
index 000000000..197ed9608
--- /dev/null
+++ b/src/containers/PDisk/i18n/en.json
@@ -0,0 +1,6 @@
+{
+ "fqdn": "FQDN",
+ "pdisk": "PDisk",
+ "groups": "Groups",
+ "node": "Node"
+}
diff --git a/src/containers/PDisk/i18n/index.ts b/src/containers/PDisk/i18n/index.ts
new file mode 100644
index 000000000..86065fddf
--- /dev/null
+++ b/src/containers/PDisk/i18n/index.ts
@@ -0,0 +1,7 @@
+import {registerKeysets} from '../../../utils/i18n';
+
+import en from './en.json';
+
+const COMPONENT = 'ydb-pDisk-page';
+
+export const pDiskPageKeyset = registerKeysets(COMPONENT, {en});
diff --git a/src/containers/PDisk/shared.ts b/src/containers/PDisk/shared.ts
new file mode 100644
index 000000000..27792b5d2
--- /dev/null
+++ b/src/containers/PDisk/shared.ts
@@ -0,0 +1,3 @@
+import {cn} from '../../utils/cn';
+
+export const pdiskPageCn = cn('ydb-pdisk-page');
diff --git a/src/containers/Storage/DiskStateProgressBar/DiskStateProgressBar.tsx b/src/containers/Storage/DiskStateProgressBar/DiskStateProgressBar.tsx
index 84409540a..a259c72cc 100644
--- a/src/containers/Storage/DiskStateProgressBar/DiskStateProgressBar.tsx
+++ b/src/containers/Storage/DiskStateProgressBar/DiskStateProgressBar.tsx
@@ -1,22 +1,14 @@
import React from 'react';
import cn from 'bem-cn-lite';
-import {DISK_COLOR_STATE_TO_NUMERIC_SEVERITY} from '../../../utils/disks/constants';
import {INVERTED_DISKS_KEY} from '../../../utils/constants';
import {useSetting} from '../../../utils/hooks';
+import {getSeverityColor} from '../../../utils/disks/helpers';
import './DiskStateProgressBar.scss';
const b = cn('storage-disk-progress-bar');
-type Color = keyof typeof DISK_COLOR_STATE_TO_NUMERIC_SEVERITY;
-
-type SeverityToColor = Record
;
-
-const severityToColor = Object.entries(
- DISK_COLOR_STATE_TO_NUMERIC_SEVERITY,
-).reduce((acc, [color, severity]) => ({...acc, [severity]: color as Color}), {});
-
interface DiskStateProgressBarProps {
diskAllocatedPercent?: number;
severity?: number;
@@ -56,7 +48,7 @@ export function DiskStateProgressBar({
const mods: Record = {inverted, compact};
- const color = severity !== undefined && severityToColor[severity];
+ const color = severity !== undefined && getSeverityColor(severity);
if (color) {
mods[color.toLocaleLowerCase()] = true;
}
diff --git a/src/containers/Storage/StorageGroups/getStorageGroupsColumns.tsx b/src/containers/Storage/StorageGroups/getStorageGroupsColumns.tsx
index 904dce3c4..680473ce2 100644
--- a/src/containers/Storage/StorageGroups/getStorageGroupsColumns.tsx
+++ b/src/containers/Storage/StorageGroups/getStorageGroupsColumns.tsx
@@ -247,6 +247,19 @@ export const getStorageTopGroupsColumns = (): StorageGroupsColumn[] => {
return [groupIdColumn, kindColumn, erasureColumn, usageColumn, usedColumn, limitColumn];
};
+export const getPDiskStorageColumns = (nodes?: NodesMap): StorageGroupsColumn[] => {
+ return [
+ poolNameColumn,
+ kindColumn,
+ erasureColumn,
+ degradedColumn,
+ groupIdColumn,
+ usageColumn,
+ usedColumn,
+ getVdiscksColumn(nodes),
+ ];
+};
+
const getStorageGroupsColumns = (nodes?: NodesMap): StorageGroupsColumn[] => {
return [
poolNameColumn,
diff --git a/src/containers/Tenant/Info/ExternalTable/ExternalTable.tsx b/src/containers/Tenant/Info/ExternalTable/ExternalTable.tsx
index 836accd8d..cc9ff47fd 100644
--- a/src/containers/Tenant/Info/ExternalTable/ExternalTable.tsx
+++ b/src/containers/Tenant/Info/ExternalTable/ExternalTable.tsx
@@ -6,7 +6,7 @@ import {useTypedSelector} from '../../../../utils/hooks';
import {createExternalUILink, parseQuery} from '../../../../routes';
import {formatCommonItem} from '../../../../components/InfoViewer/formatters';
import {InfoViewer, InfoViewerItem} from '../../../../components/InfoViewer';
-import {ExternalLinkWithIcon} from '../../../../components/ExternalLinkWithIcon/ExternalLinkWithIcon';
+import {LinkWithIcon} from '../../../../components/LinkWithIcon/LinkWithIcon';
import {EntityStatus} from '../../../../components/EntityStatus/EntityStatus';
import {ResponseError} from '../../../../components/Errors/ResponseError';
@@ -33,7 +33,7 @@ const prepareExternalTableSummary = (
label: i18n('external-objects.data-source'),
value: DataSourcePath && (
-
+
),
},
diff --git a/src/routes.ts b/src/routes.ts
index e8222c6ef..73c71f907 100644
--- a/src/routes.ts
+++ b/src/routes.ts
@@ -9,6 +9,7 @@ export const CLUSTERS = 'clusters';
export const CLUSTER = 'cluster';
export const TENANT = 'tenant';
export const NODE = 'node';
+export const PDISK = 'pDisk';
export const TABLET = 'tablet';
const routes = {
@@ -16,11 +17,14 @@ const routes = {
cluster: `/${CLUSTER}/:activeTab?`,
tenant: `/${TENANT}`,
node: `/${NODE}/:id/:activeTab?`,
+ pDisk: `/${PDISK}`,
tablet: `/${TABLET}/:id`,
tabletsFilters: `/tabletsFilters`,
auth: `/auth`,
} as const;
+export default routes;
+
export const parseQuery = (location: Location) => {
return qs.parse(location.search, {
ignoreQueryPrefix: true,
@@ -81,4 +85,10 @@ export function getLocationObjectFromHref(href: string) {
return {pathname, search, hash};
}
-export default routes;
+export function getPDiskPagePath(
+ pDiskId: string | number,
+ nodeId: string | number,
+ query: Query = {},
+) {
+ return createHref(routes.pDisk, undefined, {...query, nodeId, pDiskId});
+}
diff --git a/src/services/api.ts b/src/services/api.ts
index b3e7a877d..f40f69c7a 100644
--- a/src/services/api.ts
+++ b/src/services/api.ts
@@ -69,7 +69,7 @@ export class YdbEmbeddedAPI extends AxiosWrapper {
{concurrentId: concurrentId || `getClusterNodes`},
);
}
- getNodeInfo(id?: string) {
+ getNodeInfo(id?: string | number) {
return this.get(this.getPath('/viewer/json/sysinfo?enums=true'), {
node_id: id,
});
diff --git a/src/store/reducers/header/types.ts b/src/store/reducers/header/types.ts
index de75357bc..f8332e7fc 100644
--- a/src/store/reducers/header/types.ts
+++ b/src/store/reducers/header/types.ts
@@ -3,7 +3,7 @@ import type {EType} from '../../../types/api/tablet';
import {setHeaderBreadcrumbs} from './header';
-export type Page = 'cluster' | 'tenant' | 'node' | 'tablets' | 'tablet' | undefined;
+export type Page = 'cluster' | 'tenant' | 'node' | 'pDisk' | 'tablets' | 'tablet' | undefined;
export interface ClusterBreadcrumbsOptions {
clusterName?: string;
@@ -18,6 +18,10 @@ export interface NodeBreadcrumbsOptions extends TenantBreadcrumbsOptions {
nodeId?: string | number;
}
+export interface PDiskBreadcrumbsOptions extends Omit {
+ pDiskId?: string | number;
+}
+
export interface TabletsBreadcrumbsOptions extends TenantBreadcrumbsOptions {
nodeIds?: string[] | number[];
}
diff --git a/src/store/reducers/index.ts b/src/store/reducers/index.ts
index b2f11fbcd..3700f6eef 100644
--- a/src/store/reducers/index.ts
+++ b/src/store/reducers/index.ts
@@ -21,6 +21,7 @@ import tenants from './tenants/tenants';
import tablet from './tablet';
import topic from './topic';
import partitions from './partitions/partitions';
+import pDisk from './pdisk/pdisk';
import executeQuery from './executeQuery';
import explainQuery from './explainQuery';
import tabletsFilters from './tabletsFilters';
@@ -67,6 +68,7 @@ export const rootReducer = {
tablet,
topic,
partitions,
+ pDisk,
executeQuery,
explainQuery,
tabletsFilters,
diff --git a/src/store/reducers/nodes/types.ts b/src/store/reducers/nodes/types.ts
index 3975089f8..1c6c57184 100644
--- a/src/store/reducers/nodes/types.ts
+++ b/src/store/reducers/nodes/types.ts
@@ -80,6 +80,7 @@ export interface NodesGeneralRequestParams extends NodesSortParams {
}
export interface NodesApiRequestParams extends NodesGeneralRequestParams {
+ node_id?: number | string; // get only specific node
path?: string;
tenant?: string;
type?: NodeType;
diff --git a/src/store/reducers/pdisk/pdisk.ts b/src/store/reducers/pdisk/pdisk.ts
new file mode 100644
index 000000000..64d57fe92
--- /dev/null
+++ b/src/store/reducers/pdisk/pdisk.ts
@@ -0,0 +1,116 @@
+import type {Reducer} from '@reduxjs/toolkit';
+
+import {EVersion} from '../../../types/api/storage';
+import {createRequestActionTypes, createApiRequest} from '../../utils';
+import type {PDiskAction, PDiskState} from './types';
+import {preparePDiksDataResponse, preparePDiskStorageResponse} from './utils';
+
+export const FETCH_PDISK = createRequestActionTypes('pdisk', 'FETCH_PDISK');
+export const FETCH_PDISK_GROUPS = createRequestActionTypes('pdisk', 'FETCH_PDISK_GROUPS');
+const SET_PDISK_DATA_WAS_NOT_LOADED = 'pdisk/SET_PDISK_DATA_WAS_NOT_LOADED';
+
+const initialState = {
+ pDiskLoading: false,
+ pDiskWasLoaded: false,
+ pDiskData: {},
+ groupsLoading: false,
+ groupsWasLoaded: false,
+ groupsData: [],
+};
+
+const pdisk: Reducer = (state = initialState, action) => {
+ switch (action.type) {
+ case FETCH_PDISK.REQUEST: {
+ return {
+ ...state,
+ pDiskLoading: true,
+ };
+ }
+ case FETCH_PDISK.SUCCESS: {
+ return {
+ ...state,
+ pDiskData: action.data,
+ pDiskLoading: false,
+ pDiskWasLoaded: true,
+ pDiskError: undefined,
+ };
+ }
+ case FETCH_PDISK.FAILURE: {
+ return {
+ ...state,
+ pDiskError: action.error,
+ pDiskLoading: false,
+ };
+ }
+ case FETCH_PDISK_GROUPS.REQUEST: {
+ return {
+ ...state,
+ groupsLoading: true,
+ };
+ }
+ case FETCH_PDISK_GROUPS.SUCCESS: {
+ return {
+ ...state,
+ groupsData: action.data,
+ groupsLoading: false,
+ groupsWasLoaded: true,
+ groupsError: undefined,
+ };
+ }
+ case FETCH_PDISK_GROUPS.FAILURE: {
+ return {
+ ...state,
+ groupsError: action.error,
+ groupsLoading: false,
+ };
+ }
+ case SET_PDISK_DATA_WAS_NOT_LOADED: {
+ return {
+ ...state,
+ pDiskWasLoaded: false,
+ groupsWasLoaded: false,
+ };
+ }
+ default:
+ return state;
+ }
+};
+
+export const setPDiskDataWasNotLoaded = () => {
+ return {
+ type: SET_PDISK_DATA_WAS_NOT_LOADED,
+ } as const;
+};
+
+export const getPDiskData = ({
+ nodeId,
+ pDiskId,
+}: {
+ nodeId: number | string;
+ pDiskId: number | string;
+}) => {
+ return createApiRequest({
+ request: Promise.all([
+ window.api.getPdiskInfo(nodeId, pDiskId),
+ window.api.getNodeInfo(nodeId),
+ ]),
+ actions: FETCH_PDISK,
+ dataHandler: preparePDiksDataResponse,
+ });
+};
+
+export const getPDiskStorage = ({
+ nodeId,
+ pDiskId,
+}: {
+ nodeId: number | string;
+ pDiskId: number | string;
+}) => {
+ return createApiRequest({
+ request: window.api.getStorageInfo({nodeId, version: EVersion.v1}),
+ actions: FETCH_PDISK_GROUPS,
+ dataHandler: (data) => preparePDiskStorageResponse(data, pDiskId, nodeId),
+ });
+};
+
+export default pdisk;
diff --git a/src/store/reducers/pdisk/types.ts b/src/store/reducers/pdisk/types.ts
new file mode 100644
index 000000000..b3a728fab
--- /dev/null
+++ b/src/store/reducers/pdisk/types.ts
@@ -0,0 +1,29 @@
+import type {IResponseError} from '../../../types/api/error';
+import type {PreparedPDisk} from '../../../utils/disks/types';
+import type {ApiRequestAction} from '../../utils';
+import type {PreparedStorageGroup} from '../storage/types';
+import type {FETCH_PDISK, FETCH_PDISK_GROUPS, setPDiskDataWasNotLoaded} from './pdisk';
+
+interface PDiskData extends PreparedPDisk {
+ NodeId?: number;
+ NodeHost?: string;
+ NodeType?: string;
+ NodeDC?: string;
+}
+
+export interface PDiskState {
+ pDiskLoading: boolean;
+ pDiskWasLoaded: boolean;
+ pDiskData: PDiskData;
+ pDiskError?: IResponseError;
+
+ groupsLoading: boolean;
+ groupsWasLoaded: boolean;
+ groupsData: PreparedStorageGroup[];
+ groupsError?: IResponseError;
+}
+
+export type PDiskAction =
+ | ApiRequestAction
+ | ApiRequestAction
+ | ReturnType;
diff --git a/src/store/reducers/pdisk/utils.ts b/src/store/reducers/pdisk/utils.ts
new file mode 100644
index 000000000..30d7ea895
--- /dev/null
+++ b/src/store/reducers/pdisk/utils.ts
@@ -0,0 +1,52 @@
+import type {TEvPDiskStateResponse} from '../../../types/api/pdisk';
+import type {TStorageInfo} from '../../../types/api/storage';
+import type {TEvSystemStateResponse} from '../../../types/api/systemState';
+import {preparePDiskData} from '../../../utils/disks/prepareDisks';
+import {prepareNodeSystemState} from '../../../utils/nodes';
+import type {PreparedStorageGroup} from '../storage/types';
+import {prepareStorageGroupData} from '../storage/utils';
+
+export function preparePDiksDataResponse([pdiskResponse, nodeResponse]: [
+ TEvPDiskStateResponse,
+ TEvSystemStateResponse,
+]) {
+ const rawPDisk = pdiskResponse.PDiskStateInfo?.[0];
+ const preparedPDisk = preparePDiskData(rawPDisk);
+
+ const rawNode = nodeResponse.SystemStateInfo?.[0];
+ const preparedNode = prepareNodeSystemState(rawNode);
+
+ return {
+ ...preparedPDisk,
+ NodeId: preparedPDisk.NodeId ?? preparedNode.NodeId,
+ NodeHost: preparedNode.Host,
+ NodeType: preparedNode.Roles?.[0],
+ NodeDC: preparedNode.DC,
+ };
+}
+
+export function preparePDiskStorageResponse(
+ data: TStorageInfo,
+ pDiskId: number | string,
+ nodeId: number | string,
+) {
+ const preparedGroups: PreparedStorageGroup[] = [];
+
+ data.StoragePools?.forEach((pool) =>
+ pool.Groups?.forEach((group) => {
+ const groupHasPDiskVDisks = group.VDisks?.some((vdisk) => {
+ // If VDisk has PDisk inside, PDiskId and NodeId fields could be only inside PDisk and vice versa
+ const groupPDiskId = vdisk.PDiskId ?? vdisk.PDisk?.PDiskId;
+ const groupNodeId = vdisk.NodeId ?? vdisk.PDisk?.NodeId;
+
+ return groupPDiskId === Number(pDiskId) && groupNodeId === Number(nodeId);
+ });
+
+ if (groupHasPDiskVDisks) {
+ preparedGroups.push(prepareStorageGroupData(group, pool.Name));
+ }
+ }),
+ );
+
+ return preparedGroups;
+}
diff --git a/src/store/reducers/storage/types.ts b/src/store/reducers/storage/types.ts
index 0b105f1b9..486fca395 100644
--- a/src/store/reducers/storage/types.ts
+++ b/src/store/reducers/storage/types.ts
@@ -73,7 +73,7 @@ export interface StorageSortAndFilterParams extends StorageSortParams {
export interface StorageApiRequestParams extends StorageSortAndFilterParams {
tenant?: string;
- nodeId?: string;
+ nodeId?: string | number;
visibleEntities?: VisibleEntities;
version?: EVersion;
diff --git a/src/store/reducers/storage/utils.ts b/src/store/reducers/storage/utils.ts
index af3079bc3..bfe20d4db 100644
--- a/src/store/reducers/storage/utils.ts
+++ b/src/store/reducers/storage/utils.ts
@@ -43,7 +43,7 @@ const prepareVDisk = (vDisk: TVDiskStateInfo, poolName: string | undefined) => {
};
};
-const prepareStorageGroupData = (
+export const prepareStorageGroupData = (
group: TStorageGroupInfo,
poolName?: string,
): PreparedStorageGroup => {
@@ -114,7 +114,7 @@ const prepareStorageGroupData = (
};
};
-const prepareStorageGroupDataV2 = (group: TStorageGroupInfoV2): PreparedStorageGroup => {
+export const prepareStorageGroupDataV2 = (group: TStorageGroupInfoV2): PreparedStorageGroup => {
const {
VDisks = [],
PoolName,
diff --git a/src/utils/disks/constants.ts b/src/utils/disks/constants.ts
index 7c4ce9faf..00d06eb62 100644
--- a/src/utils/disks/constants.ts
+++ b/src/utils/disks/constants.ts
@@ -12,7 +12,15 @@ export const DISK_COLOR_STATE_TO_NUMERIC_SEVERITY: Record = {
Red: 5,
};
+type SeverityToColor = Record;
+
+export const DISK_NUMERIC_SEVERITY_TO_STATE_COLOR = Object.entries(
+ DISK_COLOR_STATE_TO_NUMERIC_SEVERITY,
+).reduce((acc, [color, severity]) => ({...acc, [severity]: color as EFlag}), {});
+
export const NOT_AVAILABLE_SEVERITY = DISK_COLOR_STATE_TO_NUMERIC_SEVERITY.Grey;
+export const NOT_AVAILABLE_SEVERITY_COLOR =
+ DISK_NUMERIC_SEVERITY_TO_STATE_COLOR[NOT_AVAILABLE_SEVERITY];
export const VDISK_STATE_SEVERITY: Record = {
[EVDiskState.Initial]: DISK_COLOR_STATE_TO_NUMERIC_SEVERITY.Yellow,
diff --git a/src/utils/disks/helpers.ts b/src/utils/disks/helpers.ts
index a05e41d52..5441a6b92 100644
--- a/src/utils/disks/helpers.ts
+++ b/src/utils/disks/helpers.ts
@@ -1,5 +1,14 @@
import type {TVDiskStateInfo, TVSlotId} from '../../types/api/vdisk';
+import {DISK_NUMERIC_SEVERITY_TO_STATE_COLOR, NOT_AVAILABLE_SEVERITY_COLOR} from './constants';
export function isFullVDiskData(disk: TVDiskStateInfo | TVSlotId): disk is TVDiskStateInfo {
return 'VDiskId' in disk;
}
+
+export function getSeverityColor(severity: number | undefined) {
+ if (severity === undefined) {
+ return NOT_AVAILABLE_SEVERITY_COLOR;
+ }
+
+ return DISK_NUMERIC_SEVERITY_TO_STATE_COLOR[severity] || NOT_AVAILABLE_SEVERITY_COLOR;
+}
diff --git a/src/utils/index.ts b/src/utils/index.ts
index ffa1fa018..4d231399f 100644
--- a/src/utils/index.ts
+++ b/src/utils/index.ts
@@ -1,3 +1,7 @@
export const getArray = (arrayLength: number) => {
return [...Array(arrayLength).keys()];
};
+
+export function valueIsDefined(value: T | null | undefined): value is T {
+ return value !== null && value !== undefined;
+}