import React from 'react'; import {ArrowRotateLeft, Wrench} from '@gravity-ui/icons'; import {Flex, Icon, Tab, TabList, TabProvider, Text} from '@gravity-ui/uikit'; import {skipToken} from '@reduxjs/toolkit/query'; import {Helmet} from 'react-helmet-async'; import {StringParam, useQueryParams} from 'use-query-params'; import {z} from 'zod'; import {StickyAutoRefreshControl} from '../../components/AutoRefreshControl/StickyAutoRefreshControl'; import {ButtonWithConfirmDialog} from '../../components/ButtonWithConfirmDialog/ButtonWithConfirmDialog'; import {CopyableValue} from '../../components/CopyableValue/CopyableValue'; import {DiskStatusLabel, DiskTypeLabel} from '../../components/DiskStatus/DiskStatus'; import {ResponseError} from '../../components/Errors/ResponseError'; import {InfoViewerSkeleton} from '../../components/InfoViewerSkeleton/InfoViewerSkeleton'; import {InternalLink} from '../../components/InternalLink/InternalLink'; import {LinkWithIcon} from '../../components/LinkWithIcon/LinkWithIcon'; import {PDiskInfo} from '../../components/PDiskInfo/PDiskInfo'; import { getPDiskDecommitLabel, getPDiskDriveLabel, getPDiskMaintenanceLabel, getPDiskStateLabel, } from '../../components/PDiskInfo/statuses'; import {getPDiskPagePath} from '../../routes'; import {api} from '../../store/reducers/api'; import {useDiskPagesAvailable} from '../../store/reducers/capabilities/hooks'; import {setHeaderBreadcrumbs} from '../../store/reducers/header/header'; import {pDiskApi} from '../../store/reducers/pdisk/pdisk'; import {valueIsDefined} from '../../utils'; import {cn} from '../../utils/cn'; import {EMPTY_DATA_PLACEHOLDER} from '../../utils/constants'; import {createPDiskDeveloperUILink, useHasDeveloperUi} from '../../utils/developerUI/developerUI'; import {getPDiskId} from '../../utils/disks/helpers'; import {useAutoRefreshInterval, useTypedDispatch} from '../../utils/hooks'; import {useIsUserAllowedToMakeChanges} from '../../utils/hooks/useIsUserAllowedToMakeChanges'; import {useAppTitle} from '../App/AppTitleContext'; import {PaginatedStorage} from '../Storage/PaginatedStorage'; import {PDiskSpaceDistribution} from './PDiskSpaceDistribution/PDiskSpaceDistribution'; import {PDiskStorageDetails} from './PDiskStorageDetails'; import {pDiskPageKeyset} from './i18n'; import './PDiskPage.scss'; const pdiskPageCn = cn('ydb-pdisk-page'); const PDISK_TABS_IDS = { spaceDistribution: 'spaceDistribution', storage: 'storage', } as const; const PDISK_PAGE_TABS = [ { id: PDISK_TABS_IDS.spaceDistribution, get title() { return pDiskPageKeyset('space-distribution'); }, }, { id: PDISK_TABS_IDS.storage, get title() { return pDiskPageKeyset('storage'); }, }, ]; const pDiskTabSchema = z.nativeEnum(PDISK_TABS_IDS).catch(PDISK_TABS_IDS.spaceDistribution); export function PDiskPage() { const dispatch = useTypedDispatch(); const hasDeveloperUi = useHasDeveloperUi(); const isUserAllowedToMakeChanges = useIsUserAllowedToMakeChanges(); const newDiskApiAvailable = useDiskPagesAvailable(); const containerRef = React.useRef(null); const [{nodeId, pDiskId, activeTab}] = useQueryParams({ activeTab: StringParam, nodeId: StringParam, pDiskId: StringParam, }); const pDiskParamsDefined = valueIsDefined(nodeId) && valueIsDefined(pDiskId); const pDiskTab = pDiskTabSchema.parse(activeTab); React.useEffect(() => { dispatch(setHeaderBreadcrumbs('pDisk', {nodeId, pDiskId})); }, [dispatch, nodeId, pDiskId]); const [autoRefreshInterval] = useAutoRefreshInterval(); const params = pDiskParamsDefined ? {nodeId, pDiskId} : skipToken; const pdiskDataQuery = pDiskApi.useGetPdiskInfoQuery(params, { pollingInterval: autoRefreshInterval, }); const pDiskLoading = pdiskDataQuery.isFetching && pdiskDataQuery.currentData === undefined; const pDiskData = pdiskDataQuery.currentData; const {NodeHost, DecommitStatus} = pDiskData || {}; const diskId = getPDiskId({nodeId, pDiskId}); const handleRestart = async (isRetry?: boolean) => { if (pDiskParamsDefined) { const response = await window.api.pdisk[ newDiskApiAvailable ? 'restartPDisk' : 'restartPDiskOld' ]({nodeId, pDiskId, force: isRetry}); if (response?.result === false) { const err = { statusText: response.error, retryPossible: response.forceRetryPossible, }; throw err; } } }; const handleAfterAction = () => { if (pDiskParamsDefined) { dispatch( api.util.invalidateTags([{type: 'PDiskData', id: getPDiskId({nodeId, pDiskId})}]), 'StorageData', ); } }; const {appTitle} = useAppTitle(); const renderHelmet = () => { const pDiskPagePart = pDiskId ? `${pDiskPageKeyset('pdisk')} ${pDiskId}` : pDiskPageKeyset('pdisk'); const nodePagePart = NodeHost ? NodeHost : pDiskPageKeyset('node'); return ( ); }; const renderPageTitle = () => { const statusLabels = [ {id: 'state', label: getPDiskStateLabel(pDiskData?.State)}, {id: 'drive', label: getPDiskDriveLabel(pDiskData?.DriveStatus)}, {id: 'decommit', label: getPDiskDecommitLabel(DecommitStatus)}, {id: 'maintenance', label: getPDiskMaintenanceLabel(pDiskData?.MaintenanceStatus)}, ]; return ( {pDiskPageKeyset('pdisk')}{' '} {diskId || EMPTY_DATA_PLACEHOLDER} {statusLabels.map(({id, label}) => label ? : null, )} ); }; const renderControls = () => { return (
{pDiskPageKeyset('restart-pdisk-button')} {hasDeveloperUi && pDiskParamsDefined && ( )}
); }; const renderInfo = () => { if (pDiskLoading) { return ; } return ( ); }; const renderTabs = () => { return (
{PDISK_PAGE_TABS.map(({id, title}) => { const path = pDiskParamsDefined ? getPDiskPagePath(pDiskId, nodeId, {activeTab: id}) : undefined; return ( {title} ); })}
); }; const renderTabsContent = () => { switch (pDiskTab) { case 'spaceDistribution': { return pDiskData ? (
) : null; } case 'storage': { return pDiskParamsDefined ? ( ) : null; } default: return null; } }; const renderError = () => { if (!pdiskDataQuery.error) { return null; } return ; }; return (
{renderHelmet()} {renderPageTitle()} {renderControls()} {renderError()} {renderInfo()} {renderTabs()} {renderTabsContent()}
); }