diff --git a/src/components/DiskStateProgressBar/DiskStateProgressBar.scss b/src/components/DiskStateProgressBar/DiskStateProgressBar.scss index e8ceb76438..801f7c818b 100644 --- a/src/components/DiskStateProgressBar/DiskStateProgressBar.scss +++ b/src/components/DiskStateProgressBar/DiskStateProgressBar.scss @@ -4,10 +4,12 @@ $block: &; $border-width: 1px; - $outer-border-radius: 4px; + $outer-border-radius: var(--g-border-radius-s); $inner-border-radius: $outer-border-radius - $border-width; + $outer-compact-border-radius: var(--g-border-radius-xs); + $inner-compact-border-radius: $outer-compact-border-radius - $border-width; - --progress-bar-full-height: var(--g-text-body-3-line-height); + --progress-bar-full-height: var(--g-text-subheader-2-line-height); --progress-bar-compact-height: 12px; --stripe-width: 4px; @@ -16,6 +18,8 @@ position: relative; z-index: 0; + overflow: hidden; + min-width: 50px; height: var(--progress-bar-full-height); @@ -25,13 +29,16 @@ border: $border-width solid var(--entity-state-border-color); border-radius: $outer-border-radius; background-color: var(--entity-state-background-color); - @include mixins.entity-state-colors(); + + transition: opacity 300ms ease-in-out; + + @include mixins.entity-state-colors($block); &_compact { - min-width: 0; + min-width: 8px; height: var(--progress-bar-compact-height); - border-radius: 2px; + border-radius: $outer-compact-border-radius; } &_faded { @@ -42,6 +49,10 @@ opacity: 0.5; } + &_darkened { + opacity: 0.8; + } + &_empty { color: var(--g-color-text-hint); border-style: dashed; @@ -78,7 +89,7 @@ } &_compact { - border-radius: 1px; + border-radius: $inner-compact-border-radius; } &_inverted { @@ -93,20 +104,21 @@ position: relative; z-index: 2; - margin-right: var(--g-spacing-1); + margin-right: var(--g-spacing-half); - font-size: var(--g-text-body-1-font-size); + font-family: var(--g-text-caption-font-family); + font-size: var(--g-text-caption-1-font-size); // bar height minus borders line-height: calc(var(--progress-bar-full-height) - #{$border-width * 2}); - color: inherit; + color: var(--entity-state-font-color); } &__icon { position: relative; z-index: 2; - margin-left: var(--g-spacing-1); + margin-left: calc(var(--g-spacing-1) - $border-width); color: var(--entity-state-border-color); } diff --git a/src/components/DiskStateProgressBar/DiskStateProgressBar.tsx b/src/components/DiskStateProgressBar/DiskStateProgressBar.tsx index ac93c6ab82..ca351cc581 100644 --- a/src/components/DiskStateProgressBar/DiskStateProgressBar.tsx +++ b/src/components/DiskStateProgressBar/DiskStateProgressBar.tsx @@ -7,6 +7,7 @@ import {cn} from '../../utils/cn'; import {DONOR_COLOR} from '../../utils/disks/constants'; import {getSeverityColor, getVDiskStatusIcon} from '../../utils/disks/helpers'; import {useSetting} from '../../utils/hooks'; +import {isNumeric} from '../../utils/utils'; import './DiskStateProgressBar.scss'; @@ -24,6 +25,9 @@ interface DiskStateProgressBarProps { className?: string; isDonor?: boolean; withIcon?: boolean; + highlighted?: boolean; + darkened?: boolean; + noDataPlaceholder?: React.ReactNode; } export function DiskStateProgressBar({ @@ -38,6 +42,9 @@ export function DiskStateProgressBar({ className, isDonor, withIcon, + highlighted, + darkened, + noDataPlaceholder, }: DiskStateProgressBarProps) { const [inverted] = useSetting(SETTING_KEYS.INVERTED_DISKS); @@ -48,6 +55,8 @@ export function DiskStateProgressBar({ empty, inactive, striped, + highlighted, + darkened, }; if (isDonor) { @@ -59,22 +68,24 @@ export function DiskStateProgressBar({ } } + const hasAllocatedPercent = isNumeric(diskAllocatedPercent) && diskAllocatedPercent >= 0; + const renderAllocatedPercent = () => { if (compact) { return
; } + if (!hasAllocatedPercent) { + return null; + } + // diskAllocatedPercent could be more than 100 let fillWidth = Math.min(diskAllocatedPercent, 100); if (inverted) { fillWidth = Math.max(100 - diskAllocatedPercent, 0); } - if (diskAllocatedPercent >= 0) { - return
; - } - - return null; + return
; }; const renderContent = () => { @@ -82,10 +93,14 @@ export function DiskStateProgressBar({ return content; } - if (!compact && diskAllocatedPercent >= 0) { + if (!compact && hasAllocatedPercent) { return
{`${Math.floor(diskAllocatedPercent)}%`}
; } + if (!compact && !hasAllocatedPercent && noDataPlaceholder) { + return
{noDataPlaceholder}
; + } + return null; }; @@ -111,7 +126,7 @@ export function DiskStateProgressBar({ aria-label="Disk allocated space" aria-valuemin={0} aria-valuemax={100} - aria-valuenow={diskAllocatedPercent} + aria-valuenow={hasAllocatedPercent ? diskAllocatedPercent : undefined} > {iconElement} {renderAllocatedPercent()} diff --git a/src/components/HoverPopup/HoverPopup.tsx b/src/components/HoverPopup/HoverPopup.tsx index ac902e9a52..23bd56be59 100644 --- a/src/components/HoverPopup/HoverPopup.tsx +++ b/src/components/HoverPopup/HoverPopup.tsx @@ -34,44 +34,47 @@ export const HoverPopup = ({ delayOpen = DEBOUNCE_TIMEOUT, }: HoverPopupProps) => { const [isPopupVisible, setIsPopupVisible] = React.useState(false); - const anchor = React.useRef(null); + const [isPopupContentHovered, setIsPopupContentHovered] = React.useState(false); + const [isFocused, setIsFocused] = React.useState(false); + + const anchor = React.useRef(null); const debouncedHandleShowPopup = React.useMemo( () => debounce(() => { setIsPopupVisible(true); - onShowPopup?.(); }, delayOpen), - [onShowPopup, delayOpen], + [delayOpen], ); const hidePopup = React.useCallback(() => { setIsPopupVisible(false); - onHidePopup?.(); - }, [onHidePopup]); + }, []); const debouncedHandleHidePopup = React.useMemo( () => debounce(hidePopup, delayClose), [hidePopup, delayClose], ); - const onMouseEnter = debouncedHandleShowPopup; + const onMouseEnter = () => { + debouncedHandleHidePopup.cancel(); + debouncedHandleShowPopup(); + }; const onMouseLeave = () => { debouncedHandleShowPopup.cancel(); debouncedHandleHidePopup(); }; - const [isPopupContentHovered, setIsPopupContentHovered] = React.useState(false); - const [isFocused, setIsFocused] = React.useState(false); - const onPopupMouseEnter = React.useCallback(() => { setIsPopupContentHovered(true); - }, []); + debouncedHandleHidePopup.cancel(); + }, [debouncedHandleHidePopup]); const onPopupMouseLeave = React.useCallback(() => { setIsPopupContentHovered(false); - }, []); + debouncedHandleHidePopup(); + }, [debouncedHandleHidePopup]); const onPopupContextMenu = React.useCallback(() => { setIsFocused(true); @@ -87,16 +90,39 @@ export const HoverPopup = ({ hidePopup(); }, [hidePopup]); - const open = isPopupVisible || showPopup || isPopupContentHovered || isFocused; + const internalOpen = isPopupVisible || isPopupContentHovered || isFocused; + const open = internalOpen || showPopup; + + const prevInternalOpenRef = React.useRef(internalOpen); + + React.useEffect(() => { + const prev = prevInternalOpenRef.current; + + if (prev === internalOpen) { + return; + } + + if (internalOpen) { + onShowPopup?.(); + } else { + onHidePopup?.(); + } + + prevInternalOpenRef.current = internalOpen; + }, [internalOpen, onShowPopup, onHidePopup]); + + // Do not render Popup until it is available + // to avoid a brief initial render at (0, 0) before positioning is applied. + const anchorElement = anchorRef?.current || anchor.current; return ( {children} - {open ? ( + {open && anchorElement ? ( { if (reason === 'escape-key') { onPopupEscapeKeyDown(); diff --git a/src/components/VDisk/VDisk.tsx b/src/components/VDisk/VDisk.tsx index 76eb71ce2e..ddcd7ac8c7 100644 --- a/src/components/VDisk/VDisk.tsx +++ b/src/components/VDisk/VDisk.tsx @@ -22,6 +22,8 @@ export interface VDiskProps { delayOpen?: number; delayClose?: number; withIcon?: boolean; + highlighted?: boolean; + darkened?: boolean; } export const VDisk = ({ @@ -35,13 +37,15 @@ export const VDisk = ({ delayClose, delayOpen, withIcon, + highlighted, + darkened, }: VDiskProps) => { const getVDiskLink = useVDiskPagePath(); const vDiskPath = getVDiskLink({nodeId: data.NodeId, vDiskId: data.StringifiedId}); const severity = data.Severity; const isReplicatingColor = severity === DISK_COLOR_STATE_TO_NUMERIC_SEVERITY.Blue; - const isHealthyDonor = data.DonorMode && isReplicatingColor; + const isDonor = data.DonorMode; return (
diff --git a/src/components/VDisk/VDiskWithDonorsStack.tsx b/src/components/VDisk/VDiskWithDonorsStack.tsx index e1411310d8..5715f20165 100644 --- a/src/components/VDisk/VDiskWithDonorsStack.tsx +++ b/src/components/VDisk/VDiskWithDonorsStack.tsx @@ -1,3 +1,5 @@ +import React from 'react'; + import {stringifyVdiskId} from '../../utils/dataFormatters/dataFormatters'; import {isFullVDiskData} from '../../utils/disks/helpers'; import type {PreparedVDisk} from '../../utils/disks/types'; @@ -10,6 +12,9 @@ interface VDiskWithDonorsStackProps extends VDiskProps { data?: PreparedVDisk; className?: string; stackClassName?: string; + highlightedVDisk?: string; + setHighlightedVDisk?: (id?: string) => void; + progressBarClassName?: string; } export function VDiskWithDonorsStack({ @@ -17,30 +22,54 @@ export function VDiskWithDonorsStack({ className, stackClassName, withIcon, + highlightedVDisk, + setHighlightedVDisk, ...restProps }: VDiskWithDonorsStackProps) { const {Donors: donors, ...restData} = data || {}; + const stackId = data?.StringifiedId; + const isHighlighted = Boolean(stackId && highlightedVDisk === stackId); + const isDarkened = Boolean(highlightedVDisk && highlightedVDisk !== stackId); + + const handleShowPopup = React.useCallback(() => { + if (stackId) { + setHighlightedVDisk?.(stackId); + } + }, [stackId, setHighlightedVDisk]); + + const handleHidePopup = React.useCallback(() => { + setHighlightedVDisk?.(undefined); + }, [setHighlightedVDisk]); + + const commonVDiskProps: Partial = { + withIcon, + showPopup: isHighlighted, + highlighted: isHighlighted, + darkened: isDarkened, + onShowPopup: handleShowPopup, + onHidePopup: handleHidePopup, + ...restProps, + }; + const content = donors && donors.length > 0 ? ( - + {donors.map((donor) => { const isFullData = isFullVDiskData(donor); - // donor and acceptor are always in the same group return ( ); })} ) : ( - + ); return
{content}
; diff --git a/src/components/VDiskPopup/VDiskPopup.tsx b/src/components/VDiskPopup/VDiskPopup.tsx index 315c5df0b5..b2e5a05de4 100644 --- a/src/components/VDiskPopup/VDiskPopup.tsx +++ b/src/components/VDiskPopup/VDiskPopup.tsx @@ -14,7 +14,7 @@ import {createVDiskDeveloperUILink} from '../../utils/developerUI/developerUI'; import {getStateSeverity} from '../../utils/disks/calculateVDiskSeverity'; import { DISK_COLOR_STATE_TO_NUMERIC_SEVERITY, - NOT_AVAILABLE_SEVERITY, + ERROR_SEVERITY, NUMERIC_SEVERITY_TO_LABEL_VIEW, VDISK_LABEL_CONFIG, } from '../../utils/disks/constants'; @@ -134,6 +134,7 @@ const prepareVDiskData = ( ReplicationSecondsRemaining, UnsyncedVDisks, AllocatedSize, + AvailableSize, ReadThroughput, WriteThroughput, StoragePoolName, @@ -257,6 +258,13 @@ const prepareVDiskData = ( }); } + if (Number(AvailableSize)) { + vdiskData.push({ + name: vDiskPopupKeyset('label_available'), + content: bytesToGB(AvailableSize), + }); + } + if (Number(ReadThroughput)) { vdiskData.push({ name: vDiskPopupKeyset('label_read'), @@ -345,24 +353,18 @@ const prepareHeaderLabels = (data: PreparedVDisk): YDBDefinitionListHeaderLabel[ theme: donorConfig.theme, icon: donorConfig.icon, }); - } - - if (isReplicatingColor) { - if (!DonorMode) { - const replicaConfig = VDISK_LABEL_CONFIG.replica; + } else if (isReplicatingColor) { + const replicaConfig = VDISK_LABEL_CONFIG.replica; - labels.push({ - id: 'replication', - value: vDiskPopupKeyset('label_replication'), - theme: replicaConfig.theme, - icon: replicaConfig.icon, - }); - } - - return labels; + labels.push({ + id: 'replication', + value: vDiskPopupKeyset('label_replication'), + theme: replicaConfig.theme, + icon: replicaConfig.icon, + }); } - const severity = VDiskState ? getStateSeverity(VDiskState) : NOT_AVAILABLE_SEVERITY; + const severity = VDiskState ? getStateSeverity(VDiskState) : ERROR_SEVERITY; const {theme: stateTheme, icon: stateIcon} = NUMERIC_SEVERITY_TO_LABEL_VIEW[severity]; diff --git a/src/components/VDiskPopup/i18n/en.json b/src/components/VDiskPopup/i18n/en.json index 1d4a165832..bfb6780ea8 100644 --- a/src/components/VDiskPopup/i18n/en.json +++ b/src/components/VDiskPopup/i18n/en.json @@ -17,6 +17,7 @@ "label_remaining": "Remaining", "label_unsync-vdisks": "UnsyncVDisks", "label_allocated": "Allocated", + "label_available": "Available", "label_read": "Read", "label_write": "Write", "label_replication": "Replication", diff --git a/src/components/YDBDefinitionList/YDBDefinitionList.scss b/src/components/YDBDefinitionList/YDBDefinitionList.scss index b54f57ba88..067b9e868e 100644 --- a/src/components/YDBDefinitionList/YDBDefinitionList.scss +++ b/src/components/YDBDefinitionList/YDBDefinitionList.scss @@ -12,10 +12,6 @@ color: var(--g-color-text-secondary); } - &__properties-list { - max-width: calc(100% - 40px); - } - &__footer { margin-top: var(--g-spacing-3); } diff --git a/src/containers/Storage/Disks/Disks.scss b/src/containers/Storage/Disks/Disks.scss index 4bb544e9df..28bded1eb2 100644 --- a/src/containers/Storage/Disks/Disks.scss +++ b/src/containers/Storage/Disks/Disks.scss @@ -2,7 +2,7 @@ display: flex; flex-direction: row; align-items: center; - gap: 20px; + gap: var(--g-spacing-4); width: max-content; @@ -17,19 +17,21 @@ &__vdisk-item { flex-basis: 8px; flex-shrink: 0; + + min-width: 20px; } &__vdisk-progress-bar { - --progress-bar-compact-height: 18px; + --progress-bar-compact-height: 20px; - border-radius: 4px; + border-radius: var(--g-border-radius-m); } &__pdisk-item { - min-width: 80px; - margin-right: 4px; + min-width: 55px; + margin-right: var(--g-spacing-1); &_with-dc-margin { - margin-right: 12px; + margin-right: var(--g-spacing-3); } &:last-child { @@ -39,6 +41,6 @@ &__pdisk-progress-bar { --progress-bar-full-height: 20px; - text-align: left; + border-radius: var(--g-border-radius-m); } } diff --git a/src/containers/Storage/Disks/Disks.tsx b/src/containers/Storage/Disks/Disks.tsx index 66c2243c07..138bc929c6 100644 --- a/src/containers/Storage/Disks/Disks.tsx +++ b/src/containers/Storage/Disks/Disks.tsx @@ -1,5 +1,3 @@ -import React from 'react'; - import {Flex, useLayoutContext} from '@gravity-ui/uikit'; import {VDisk} from '../../../components/VDisk/VDisk'; @@ -16,18 +14,25 @@ import './Disks.scss'; const b = cn('ydb-storage-disks'); -const VDISKS_CONTAINER_WIDTH = 300; +const VDISKS_CONTAINER_WIDTH = 316; interface DisksProps { vDisks?: PreparedVDisk[]; viewContext?: StorageViewContext; erasure?: Erasure; withIcon?: boolean; + highlightedVDisk?: string; + setHighlightedVDisk?: (id?: string) => void; } -export function Disks({vDisks = [], viewContext, erasure, withIcon}: DisksProps) { - const [highlightedVDisk, setHighlightedVDisk] = React.useState(); - +export function Disks({ + vDisks = [], + viewContext, + erasure, + withIcon, + highlightedVDisk, + setHighlightedVDisk, +}: DisksProps) { const vDisksWithDCMargins = useVDisksWithDCMargins(vDisks, erasure); const { @@ -76,8 +81,8 @@ export function Disks({vDisks = [], viewContext, erasure, withIcon}: DisksProps) interface DisksItemProps { vDisk: PreparedVDisk; inactive?: boolean; - highlightedVDisk: string | undefined; - setHighlightedVDisk: (id: string | undefined) => void; + highlightedVDisk?: string; + setHighlightedVDisk?: (id?: string) => void; unavailableVDiskWidth?: number; withDCMargin?: boolean; withIcon?: boolean; @@ -100,6 +105,9 @@ function VDiskItem({ const minWidth = isNumeric(vDiskToShow.AllocatedSize) ? undefined : unavailableVDiskWidth; const flexGrow = Number(vDiskToShow.AllocatedSize) || 1; + const isHighlighted = highlightedVDisk === vDiskId; + const darkened = Boolean(highlightedVDisk && highlightedVDisk !== vDiskId); + return (
setHighlightedVDisk(vDiskId)} - onHidePopup={() => setHighlightedVDisk(undefined)} + onShowPopup={() => setHighlightedVDisk?.(vDiskId)} + onHidePopup={() => setHighlightedVDisk?.(undefined)} progressBarClassName={b('vdisk-progress-bar')} + highlighted={isHighlighted} + darkened={darkened} />
); @@ -127,6 +137,9 @@ function PDiskItem({ }: DisksItemProps) { const vDiskId = vDisk.StringifiedId; + const isHighlighted = highlightedVDisk === vDiskId; + const darkened = Boolean(highlightedVDisk && highlightedVDisk !== vDiskId); + if (!vDisk.PDisk) { return null; } @@ -136,12 +149,14 @@ function PDiskItem({ className={b('pdisk-item', {['with-dc-margin']: withDCMargin})} progressBarClassName={b('pdisk-progress-bar')} data={vDisk.PDisk} - showPopup={highlightedVDisk === vDiskId} + showPopup={isHighlighted} delayOpen={DISKS_POPUP_DEBOUNCE_TIMEOUT} delayClose={DISKS_POPUP_DEBOUNCE_TIMEOUT} - onShowPopup={() => setHighlightedVDisk(vDiskId)} - onHidePopup={() => setHighlightedVDisk(undefined)} + onShowPopup={() => setHighlightedVDisk?.(vDiskId)} + onHidePopup={() => setHighlightedVDisk?.(undefined)} withIcon={withIcon} + highlighted={isHighlighted} + darkened={darkened} /> ); } diff --git a/src/containers/Storage/PDisk/PDisk.scss b/src/containers/Storage/PDisk/PDisk.scss index 5e442fd5d2..06d8f7ae79 100644 --- a/src/containers/Storage/PDisk/PDisk.scss +++ b/src/containers/Storage/PDisk/PDisk.scss @@ -1,7 +1,7 @@ @import '../../../styles/mixins.scss'; .pdisk-storage { - --pdisk-vdisk-width: 3px; + --pdisk-vdisk-width: 8px; --pdisk-gap-width: 2px; position: relative; diff --git a/src/containers/Storage/PDisk/PDisk.tsx b/src/containers/Storage/PDisk/PDisk.tsx index b79e72095a..ca74d8ea72 100644 --- a/src/containers/Storage/PDisk/PDisk.tsx +++ b/src/containers/Storage/PDisk/PDisk.tsx @@ -10,6 +10,7 @@ import {VDisk} from '../../../components/VDisk/VDisk'; import {getPDiskPagePath} from '../../../routes'; import {cn} from '../../../utils/cn'; import type {PreparedPDisk, PreparedVDisk} from '../../../utils/disks/types'; +import i18n from '../i18n'; import {DISKS_POPUP_DEBOUNCE_TIMEOUT} from '../shared'; import type {StorageViewContext} from '../types'; import {isVdiskActive} from '../utils'; @@ -31,6 +32,10 @@ interface PDiskProps { delayOpen?: number; delayClose?: number; withIcon?: boolean; + highlighted?: boolean; + darkened?: boolean; + highlightedDisk?: string; + setHighlightedDisk?: (id?: string) => void; } export const PDisk = ({ @@ -46,6 +51,10 @@ export const PDisk = ({ delayOpen = DISKS_POPUP_DEBOUNCE_TIMEOUT, delayClose = DISKS_POPUP_DEBOUNCE_TIMEOUT, withIcon, + highlighted, + darkened, + highlightedDisk, + setHighlightedDisk, }: PDiskProps) => { const {NodeId, PDiskId} = data; const pDiskIdsDefined = !isNil(NodeId) && !isNil(PDiskId); @@ -58,26 +67,37 @@ export const PDisk = ({ return (
- {vDisks.map((vdisk) => ( -
- -
- ))} + {vDisks.map((vdisk) => { + const vDiskId = vdisk.StringifiedId; + const vDiskHighlighted = highlightedDisk === vDiskId; + const vDiskDarkened = Boolean(highlightedDisk && highlightedDisk !== vDiskId); + + return ( +
+ setHighlightedDisk?.(vDiskId)} + onHidePopup={() => setHighlightedDisk?.(undefined)} + highlighted={vDiskHighlighted} + darkened={vDiskDarkened} + /> +
+ ); + })}
); }; @@ -107,6 +127,9 @@ export const PDisk = ({ diskAllocatedPercent={data.AllocatedPercent} severity={data.Severity} className={progressBarClassName} + highlighted={highlighted} + darkened={darkened} + noDataPlaceholder={i18n('no-data')} /> diff --git a/src/containers/Storage/PaginatedStorageGroupsTable/columns/StorageGroupsColumns.scss b/src/containers/Storage/PaginatedStorageGroupsTable/columns/StorageGroupsColumns.scss index b4c4a9a1a6..05b0f1d687 100644 --- a/src/containers/Storage/PaginatedStorageGroupsTable/columns/StorageGroupsColumns.scss +++ b/src/containers/Storage/PaginatedStorageGroupsTable/columns/StorageGroupsColumns.scss @@ -20,3 +20,6 @@ font-weight: 500; } } + +@include mixins.hover-dim-column-class('ydb-storage-groups-columns__disks-column'); +@include mixins.hover-dim-column-class('ydb-storage-groups-columns__vdisks-column'); diff --git a/src/containers/Storage/PaginatedStorageGroupsTable/columns/columns.tsx b/src/containers/Storage/PaginatedStorageGroupsTable/columns/columns.tsx index 1a4c932c58..5378860d9a 100644 --- a/src/containers/Storage/PaginatedStorageGroupsTable/columns/columns.tsx +++ b/src/containers/Storage/PaginatedStorageGroupsTable/columns/columns.tsx @@ -16,7 +16,7 @@ import {formatToMs} from '../../../../utils/timeParsers'; import {bytesToGB, bytesToSpeed} from '../../../../utils/utils'; import {Disks} from '../../Disks/Disks'; import {VDisks} from '../../VDisks/VDisks'; -import {getDegradedSeverity} from '../../utils'; +import {getDegradedSeverity, isTopLevelStorageContext} from '../../utils'; import i18n from '../i18n'; import { @@ -123,10 +123,10 @@ const usageColumn: StorageGroupsColumn = { width: 85, resizeMinWidth: 75, render: ({row}) => { - return !isNil(row.Usage) ? ( - - ) : ( + return isNil(row.Usage) ? ( EMPTY_DATA_PLACEHOLDER + ) : ( + ); }, align: DataTable.LEFT, @@ -137,13 +137,13 @@ const diskSpaceUsageColumn: StorageGroupsColumn = { width: 115, resizeMinWidth: 75, render: ({row}) => { - return !isNil(row.DiskSpaceUsage) ? ( + return isNil(row.DiskSpaceUsage) ? ( + EMPTY_DATA_PLACEHOLDER + ) : ( - ) : ( - EMPTY_DATA_PLACEHOLDER ); }, align: DataTable.LEFT, @@ -215,9 +215,9 @@ const latencyColumn: StorageGroupsColumn = { header: STORAGE_GROUPS_COLUMNS_TITLES.Latency, width: 100, render: ({row}) => { - return !isNil(row.LatencyPutTabletLogMs) - ? formatToMs(row.LatencyPutTabletLogMs) - : EMPTY_DATA_PLACEHOLDER; + return isNil(row.LatencyPutTabletLogMs) + ? EMPTY_DATA_PLACEHOLDER + : formatToMs(row.LatencyPutTabletLogMs); }, align: DataTable.RIGHT, }; @@ -227,50 +227,64 @@ const allocationUnitsColumn: StorageGroupsColumn = { header: STORAGE_GROUPS_COLUMNS_TITLES.AllocationUnits, width: 150, render: ({row}) => { - return !isNil(row.AllocationUnits) - ? formatNumber(row.AllocationUnits) - : EMPTY_DATA_PLACEHOLDER; + return isNil(row.AllocationUnits) + ? EMPTY_DATA_PLACEHOLDER + : formatNumber(row.AllocationUnits); }, align: DataTable.RIGHT, }; -const getVDisksColumn = (data?: GetStorageColumnsData): StorageGroupsColumn => ({ - name: STORAGE_GROUPS_COLUMNS_IDS.VDisks, - header: STORAGE_GROUPS_COLUMNS_TITLES.VDisks, - className: b('vdisks-column'), - render: ({row}) => ( - - ), - align: DataTable.CENTER, - width: 780, // usually 8-9 vdisks, this width corresponds to 8 vdisks, column is expanded if more - resizeable: false, - sortable: false, -}); +const getVDisksColumn = (data?: GetStorageColumnsData): StorageGroupsColumn => { + const highlightEnabled = isTopLevelStorageContext(data?.viewContext); + const highlightedVDisk = highlightEnabled ? data?.highlightedVDisksVDisk : undefined; + const setHighlightedVDisk = highlightEnabled ? data?.setHighlightedVDisksVDisk : undefined; -const getDisksColumn = (data?: GetStorageColumnsData): StorageGroupsColumn => ({ - name: STORAGE_GROUPS_COLUMNS_IDS.VDisksPDisks, - header: STORAGE_GROUPS_COLUMNS_TITLES.VDisksPDisks, - className: b('disks-column'), - render: ({row}) => { - return ( + return { + name: STORAGE_GROUPS_COLUMNS_IDS.VDisks, + header: STORAGE_GROUPS_COLUMNS_TITLES.VDisks, + className: b('vdisks-column', {highlighted: highlightEnabled}), + render: ({row}) => ( + + ), + align: DataTable.CENTER, + width: 475, // usually 8-9 vdisks, this width corresponds to 8 vdisks, column is expanded if more + resizeable: false, + sortable: false, + }; +}; + +const getDisksColumn = (data?: GetStorageColumnsData): StorageGroupsColumn => { + const highlightEnabled = isTopLevelStorageContext(data?.viewContext); + const highlightedVDisk = highlightEnabled ? data?.highlightedVDisk : undefined; + const setHighlightedVDisk = highlightEnabled ? data?.setHighlightedVDisk : undefined; + + return { + name: STORAGE_GROUPS_COLUMNS_IDS.VDisksPDisks, + header: STORAGE_GROUPS_COLUMNS_TITLES.VDisksPDisks, + className: b('disks-column', {highlighted: highlightEnabled}), + render: ({row}) => ( - ); - }, - align: DataTable.CENTER, - width: 900, - resizeable: false, - sortable: false, -}); + ), + align: DataTable.CENTER, + width: 800, + resizeable: false, + sortable: false, + }; +}; export const getStorageTopGroupsColumns: StorageColumnsGetter = () => { return [groupIdColumn, typeColumn, erasureColumn, usageColumn, usedColumn, limitColumn]; diff --git a/src/containers/Storage/PaginatedStorageGroupsTable/columns/hooks.ts b/src/containers/Storage/PaginatedStorageGroupsTable/columns/hooks.ts index eaffdf2711..c1f72f350a 100644 --- a/src/containers/Storage/PaginatedStorageGroupsTable/columns/hooks.ts +++ b/src/containers/Storage/PaginatedStorageGroupsTable/columns/hooks.ts @@ -28,8 +28,20 @@ export function useStorageGroupsSelectedColumns({ const isViewerUser = useIsViewerUser(); const bridgeModeEnabled = useBridgeModeEnabled(); + const [highlightedVDisk, setHighlightedVDisk] = React.useState(); + + const [highlightedVDisksVDisk, setHighlightedVDisksVDisk] = React.useState< + string | undefined + >(); + const columns = React.useMemo(() => { - const allColumns = getStorageGroupsColumns({viewContext}); + const allColumns = getStorageGroupsColumns({ + viewContext, + highlightedVDisk, + setHighlightedVDisk, + highlightedVDisksVDisk, + setHighlightedVDisksVDisk, + }); const filteredByBridge = bridgeModeEnabled ? allColumns : allColumns.filter((c) => c.name !== STORAGE_GROUPS_COLUMNS_IDS.PileName); @@ -44,7 +56,14 @@ export function useStorageGroupsSelectedColumns({ return filteredColumns; } return filteredColumns.filter((column) => !isViewerGroupsColumn(column.name)); - }, [isUserAllowedToMakeChanges, viewContext, isViewerUser, bridgeModeEnabled]); + }, [ + isUserAllowedToMakeChanges, + viewContext, + isViewerUser, + bridgeModeEnabled, + highlightedVDisk, + highlightedVDisksVDisk, + ]); const requiredColumns = React.useMemo(() => { if (visibleEntities === VISIBLE_ENTITIES.missing) { diff --git a/src/containers/Storage/PaginatedStorageGroupsTable/columns/types.ts b/src/containers/Storage/PaginatedStorageGroupsTable/columns/types.ts index a7d30a47ad..3c8225d213 100644 --- a/src/containers/Storage/PaginatedStorageGroupsTable/columns/types.ts +++ b/src/containers/Storage/PaginatedStorageGroupsTable/columns/types.ts @@ -6,6 +6,12 @@ export type StorageGroupsColumn = Column; export interface GetStorageColumnsData { viewContext?: StorageViewContext; + + highlightedVDisk?: string; + setHighlightedVDisk?: (id: string | undefined) => void; + + highlightedVDisksVDisk?: string; + setHighlightedVDisksVDisk?: (id: string | undefined) => void; } export interface GetStorageGroupsColumnsParams { diff --git a/src/containers/Storage/PaginatedStorageNodesTable/columns/StorageNodesColumns.scss b/src/containers/Storage/PaginatedStorageNodesTable/columns/StorageNodesColumns.scss index 16e429a71b..7c7424f64f 100644 --- a/src/containers/Storage/PaginatedStorageNodesTable/columns/StorageNodesColumns.scss +++ b/src/containers/Storage/PaginatedStorageNodesTable/columns/StorageNodesColumns.scss @@ -1,4 +1,4 @@ -@import '../../../../styles/mixins.scss'; +@use '../../../../styles/mixins.scss'; .ydb-storage-nodes-columns { &__pdisks-column { @@ -17,3 +17,5 @@ flex-shrink: 0; } } + +@include mixins.hover-dim-column-class('ydb-storage-nodes-columns__pdisks-column'); diff --git a/src/containers/Storage/PaginatedStorageNodesTable/columns/columns.tsx b/src/containers/Storage/PaginatedStorageNodesTable/columns/columns.tsx index ad06d62944..c5c0715c60 100644 --- a/src/containers/Storage/PaginatedStorageNodesTable/columns/columns.tsx +++ b/src/containers/Storage/PaginatedStorageNodesTable/columns/columns.tsx @@ -25,6 +25,7 @@ import { import type {NodesColumn} from '../../../../components/nodesColumns/types'; import {cn} from '../../../../utils/cn'; import {PDisk} from '../../PDisk/PDisk'; +import {isTopLevelStorageContext} from '../../utils'; import type {GetStorageNodesColumnsParams} from './types'; @@ -35,11 +36,17 @@ const b = cn('ydb-storage-nodes-columns'); export const getPDisksColumn = ({ viewContext, columnsSettings, + highlightedDisk, + setHighlightedDisk, }: GetStorageNodesColumnsParams): NodesColumn => { + const highlightEnabled = isTopLevelStorageContext(viewContext); + const coloredDisk = highlightEnabled ? highlightedDisk : undefined; + const setColoredDisk = highlightEnabled ? setHighlightedDisk : undefined; + return { name: NODES_COLUMNS_IDS.PDisks, header: NODES_COLUMNS_TITLES.PDisks, - className: b('pdisks-column'), + className: b('pdisks-column', {highlighted: highlightEnabled}), width: columnsSettings?.pDiskContainerWidth, render: ({row}) => { return ( @@ -49,6 +56,11 @@ export const getPDisksColumn = ({ (vdisk) => vdisk.PDiskId === pDisk.PDiskId, ); + const id = `${row.NodeId}-${pDisk.PDiskId}`; + + const highlighted = coloredDisk === id; + const darkened = Boolean(coloredDisk && coloredDisk !== id); + return (
setColoredDisk?.(id)} + onHidePopup={() => setColoredDisk?.(undefined)} + highlighted={highlighted} + darkened={darkened} + highlightedDisk={coloredDisk} + setHighlightedDisk={setColoredDisk} />
); @@ -73,6 +92,8 @@ export const getStorageNodesColumns = ({ database, viewContext, columnsSettings, + highlightedDisk, + setHighlightedDisk, }: GetStorageNodesColumnsParams): NodesColumn[] => { const columns: NodesColumn[] = [ getNodeIdColumn(), @@ -89,7 +110,7 @@ export const getStorageNodesColumns = ({ getDiskSpaceUsageColumn(), getVersionColumn(), getMissingDisksColumn(), - getPDisksColumn({viewContext, columnsSettings}), + getPDisksColumn({viewContext, columnsSettings, highlightedDisk, setHighlightedDisk}), getTabletsColumn({database}), ]; diff --git a/src/containers/Storage/PaginatedStorageNodesTable/columns/hooks.ts b/src/containers/Storage/PaginatedStorageNodesTable/columns/hooks.ts index cba6c1b783..928fef72ea 100644 --- a/src/containers/Storage/PaginatedStorageNodesTable/columns/hooks.ts +++ b/src/containers/Storage/PaginatedStorageNodesTable/columns/hooks.ts @@ -24,14 +24,18 @@ export function useStorageNodesSelectedColumns({ }: GetStorageNodesColumnsParams) { const bridgeModeEnabled = useBridgeModeEnabled(); + const [highlightedDisk, setHighlightedDisk] = React.useState(); + const columns = React.useMemo(() => { const all = getStorageNodesColumns({ database, viewContext, columnsSettings, + highlightedDisk, + setHighlightedDisk, }); return bridgeModeEnabled ? all : all.filter((c) => c.name !== NODES_COLUMNS_IDS.PileName); - }, [database, viewContext, columnsSettings, bridgeModeEnabled]); + }, [database, viewContext, columnsSettings, bridgeModeEnabled, highlightedDisk]); const requiredColumns = React.useMemo(() => { if (visibleEntities === VISIBLE_ENTITIES.missing) { diff --git a/src/containers/Storage/PaginatedStorageNodesTable/columns/types.ts b/src/containers/Storage/PaginatedStorageNodesTable/columns/types.ts index 4f559144a6..54efbc8add 100644 --- a/src/containers/Storage/PaginatedStorageNodesTable/columns/types.ts +++ b/src/containers/Storage/PaginatedStorageNodesTable/columns/types.ts @@ -11,4 +11,7 @@ export interface GetStorageNodesColumnsParams { database?: string; viewContext?: StorageViewContext; columnsSettings?: StorageNodesColumnsSettings; + + highlightedDisk?: string; + setHighlightedDisk?: (id: string | undefined) => void; } diff --git a/src/containers/Storage/VDisks/VDisks.scss b/src/containers/Storage/VDisks/VDisks.scss index a8927d4afe..064d5b18d0 100644 --- a/src/containers/Storage/VDisks/VDisks.scss +++ b/src/containers/Storage/VDisks/VDisks.scss @@ -4,11 +4,11 @@ } &__item { - width: 90px; - margin-right: 6px; + width: 55px; + margin-right: var(--g-spacing-1); &_with-dc-margin { - margin-right: 12px; + margin-right: var(--g-spacing-3); } &:last-child { @@ -21,4 +21,8 @@ } } } + + &__vdisks-progress-bar { + border-radius: var(--g-border-radius-m); + } } diff --git a/src/containers/Storage/VDisks/VDisks.tsx b/src/containers/Storage/VDisks/VDisks.tsx index b268cbd598..296c7d61df 100644 --- a/src/containers/Storage/VDisks/VDisks.tsx +++ b/src/containers/Storage/VDisks/VDisks.tsx @@ -15,9 +15,18 @@ interface VDisksProps { viewContext?: StorageViewContext; erasure?: Erasure; withIcon?: boolean; + highlightedVDisk?: string; + setHighlightedVDisk?: (id: string | undefined) => void; } -export function VDisks({vDisks, viewContext, erasure, withIcon}: VDisksProps) { +export function VDisks({ + vDisks, + viewContext, + erasure, + withIcon, + highlightedVDisk, + setHighlightedVDisk, +}: VDisksProps) { const vDisksWithDCMargins = useVDisksWithDCMargins(vDisks, erasure); return ( @@ -33,6 +42,9 @@ export function VDisks({vDisks, viewContext, erasure, withIcon}: VDisksProps) { className={b('item', { 'with-dc-margin': vDisksWithDCMargins.includes(index), })} + highlightedVDisk={highlightedVDisk} + setHighlightedVDisk={setHighlightedVDisk} + progressBarClassName={b('vdisks-progress-bar')} /> ))}
diff --git a/src/containers/Storage/i18n/en.json b/src/containers/Storage/i18n/en.json index 3ebb61d745..36cb8592aa 100644 --- a/src/containers/Storage/i18n/en.json +++ b/src/containers/Storage/i18n/en.json @@ -7,5 +7,7 @@ "controls_group-by-placeholder": "Group by:", "no-nodes": "No such nodes", - "no-groups": "No such groups" + "no-groups": "No such groups", + + "no-data": "no data" } diff --git a/src/containers/Storage/utils/index.ts b/src/containers/Storage/utils/index.ts index 8d7b7fea78..e511a5ac67 100644 --- a/src/containers/Storage/utils/index.ts +++ b/src/containers/Storage/utils/index.ts @@ -1,9 +1,10 @@ import React from 'react'; +import {isNil} from 'lodash'; + import {selectNodesMap} from '../../../store/reducers/nodesList'; import type {PreparedStorageGroup} from '../../../store/reducers/storage/types'; import type {Erasure} from '../../../types/api/storage'; -import {valueIsDefined} from '../../../utils'; import type {PreparedVDisk} from '../../../utils/disks/types'; import {generateEvaluator} from '../../../utils/generateEvaluator'; import {useTypedSelector} from '../../../utils/hooks'; @@ -30,25 +31,35 @@ export const getDegradedSeverity = (group: PreparedStorageGroup) => { export function isVdiskActive(vDisk: PreparedVDisk, viewContext?: StorageViewContext) { let isActive = true; - if (valueIsDefined(vDisk.VDiskId?.GroupID) && viewContext?.groupId) { + if (!isNil(vDisk.VDiskId?.GroupID) && viewContext?.groupId) { isActive &&= String(vDisk.VDiskId.GroupID) === viewContext.groupId; } - if (valueIsDefined(vDisk.NodeId) && viewContext?.nodeId) { + if (!isNil(vDisk.NodeId) && viewContext?.nodeId) { isActive &&= String(vDisk.NodeId) === viewContext.nodeId; } - if (valueIsDefined(vDisk.PDiskId) && viewContext?.pDiskId) { + if (!isNil(vDisk.PDiskId) && viewContext?.pDiskId) { isActive &&= String(vDisk.PDiskId) === viewContext.pDiskId; } - if (valueIsDefined(vDisk.VDiskSlotId) && viewContext?.vDiskSlotId) { + if (!isNil(vDisk.VDiskSlotId) && viewContext?.vDiskSlotId) { isActive &&= String(vDisk.VDiskSlotId) === viewContext.vDiskSlotId; } return isActive; } +export function isTopLevelStorageContext(context?: StorageViewContext): boolean { + // highlight the disk only where we are not committed to a specific node / p-disk / v-disk slot / group + return ( + isNil(context?.nodeId) && + isNil(context?.pDiskId) && + isNil(context?.vDiskSlotId) && + isNil(context?.groupId) + ); +} + const DEFAULT_ENTITIES_COUNT = 10; // NodePage - 1 node @@ -58,11 +69,7 @@ const DEFAULT_ENTITIES_COUNT = 10; export function getStorageNodesInitialEntitiesCount( context?: StorageViewContext, ): number | undefined { - if ( - valueIsDefined(context?.nodeId) || - valueIsDefined(context?.pDiskId) || - valueIsDefined(context?.vDiskSlotId) - ) { + if (!isNil(context?.nodeId) || !isNil(context?.pDiskId) || !isNil(context?.vDiskSlotId)) { return 1; } @@ -76,10 +83,10 @@ export function getStorageNodesInitialEntitiesCount( export function getStorageGroupsInitialEntitiesCount( context?: StorageViewContext, ): number | undefined { - if (valueIsDefined(context?.groupId)) { + if (!isNil(context?.groupId)) { return 1; } - if (valueIsDefined(context?.vDiskSlotId)) { + if (!isNil(context?.vDiskSlotId)) { return 1; } diff --git a/src/containers/Storage/utils/useStorageColumnsSettings.ts b/src/containers/Storage/utils/useStorageColumnsSettings.ts index d166fee074..b3049f2544 100644 --- a/src/containers/Storage/utils/useStorageColumnsSettings.ts +++ b/src/containers/Storage/utils/useStorageColumnsSettings.ts @@ -5,7 +5,7 @@ import type {StorageNodesPaginatedTableData} from '../types'; const PDISK_VDISK_WIDTH = 3; const PDISK_GAP_WIDTH = 2; -const PDISK_MIN_WIDTH = 120; +const PDISK_MIN_WIDTH = 165; const PDISK_MARGIN = 10; const MAX_SLOTS_DEFAULT = 1; const PAGNATED_TABLE_CELL_HORIZONTAL_PADDING = 10; diff --git a/src/store/reducers/storage/__tests__/prepareGroupsDisks.test.ts b/src/store/reducers/storage/__tests__/prepareGroupsDisks.test.ts index 1d79ffac43..f58138c460 100644 --- a/src/store/reducers/storage/__tests__/prepareGroupsDisks.test.ts +++ b/src/store/reducers/storage/__tests__/prepareGroupsDisks.test.ts @@ -128,7 +128,7 @@ describe('prepareGroupsVDisk', () => { StringifiedId: '2181038134-22-0-0-0', NodeId: 224, - Severity: 0, + Severity: 5, DiskSpace: 'Green', Status: 'READY', diff --git a/src/styles/mixins.scss b/src/styles/mixins.scss index 2d1f18a825..322fc62cd9 100644 --- a/src/styles/mixins.scss +++ b/src/styles/mixins.scss @@ -240,51 +240,92 @@ } } -@mixin entity-state-colors { +@mixin entity-state-colors($block: null) { --entity-state-border-color: var(--g-color-base-misc-heavy); --entity-state-background-color: var(--g-color-base-misc-light); --entity-state-fill-color: var(--g-color-base-misc-medium); --entity-state-font-color: var(--g-color-text-primary); &_green { - --entity-state-font-color: var(--g-color-text-positive); - --entity-state-border-color: var(--g-color-base-positive-heavy); + --entity-state-border-color: var(--g-color-base-positive-medium); --entity-state-background-color: var(--g-color-base-positive-light); --entity-state-fill-color: var(--g-color-base-positive-medium); + + &#{$block}_highlighted { + --entity-state-border-color: var(--g-color-base-positive-medium-hover); + --entity-state-background-color: var(--g-color-base-positive-light-hover); + --entity-state-fill-color: var(--g-color-base-positive-heavy); + } } &_blue { - --entity-state-font-color: var(--g-color-text-info); --entity-state-border-color: var(--g-color-base-info-heavy); - --entity-state-shadow-color: var(--g-color-base-info-light); + --entity-state-shadow-color: var(--g-color-base-info-medium); --entity-state-background-color: var(--g-color-base-info-light); --entity-state-fill-color: var(--g-color-base-info-medium); + + &#{$block}_highlighted { + --entity-state-border-color: var(--g-color-base-info-heavy-hover); + --entity-state-shadow-color: var(--g-color-base-info-heavy); + --entity-state-background-color: var(--g-color-base-info-light-hover); + --entity-state-fill-color: var(--g-color-base-info-medium-hover); + } } &_yellow { - --entity-state-font-color: var(--g-color-text-warning); - --entity-state-border-color: var(--g-color-base-warning-heavy); - --entity-state-background-color: var(--g-color-base-yellow-light); - --entity-state-fill-color: var(--g-color-base-yellow-medium); + --entity-state-border-color: var(--g-color-base-warning-medium); + --entity-state-background-color: var(--g-color-base-warning-light); + --entity-state-fill-color: var(--g-color-base-warning-medium); + + &#{$block}_highlighted { + --entity-state-border-color: var(--g-color-base-warning-medium-hover); + --entity-state-background-color: var(--g-color-base-warning-light-hover); + --entity-state-fill-color: var(--g-color-base-warning-medium-hover); + } } &_red { - --entity-state-font-color: var(--g-color-text-danger); --entity-state-border-color: var(--g-color-base-danger-heavy); --entity-state-background-color: var(--g-color-base-danger-light); --entity-state-fill-color: var(--g-color-base-danger-medium); + + &#{$block}_highlighted { + --entity-state-border-color: var(--g-color-base-danger-heavy-hover); + --entity-state-background-color: var(--g-color-base-danger-light); + --entity-state-fill-color: var(--g-color-base-danger-medium-hover); + } } &_darkgrey { - --entity-state-font-color: var(--g-color-text-secondary); --entity-state-border-color: var(--g-color-base-neutral-heavy); --entity-state-shadow-color: var(--g-color-base-neutral-light); --entity-state-fill-color: var(--g-color-base-neutral-light); --entity-state-background-color: transparent; + + &#{$block}_highlighted { + --entity-state-border-color: var(--g-color-base-neutral-heavy-hover); + --entity-state-shadow-color: var(--g-color-base-neutral-light-hover); + --entity-state-fill-color: var(--g-color-base-neutral-light-hover); + --entity-state-background-color: transparent; + } } - &__grey { + &_grey { --entity-state-font-color: var(--g-color-text-secondary); - --entity-state-border-color: var(--g-color-line-generic-hover); + --entity-state-background-color: var(--g-color-base-neutral-light-hover); + --entity-state-fill-color: transparent; + --entity-state-border-color: transparent; + + &#{$block}_highlighted { + --entity-state-background-color: var(--g-color-base-neutral-medium); + } + } +} + +@mixin hover-dim-column-class($column-class) { + .ydb-paginated-table__table:has(.#{$column-class}:hover) + .#{$column-class}_highlighted + .storage-disk-progress-bar:not(.storage-disk-progress-bar_highlighted) { + opacity: 0.8; } } diff --git a/src/utils/disks/__test__/calculateVDiskSeverity.test.ts b/src/utils/disks/__test__/calculateVDiskSeverity.test.ts index c1a392e9e7..e5577dc7d9 100644 --- a/src/utils/disks/__test__/calculateVDiskSeverity.test.ts +++ b/src/utils/disks/__test__/calculateVDiskSeverity.test.ts @@ -67,15 +67,15 @@ describe('VDisk state', () => { VDiskState: EVDiskState.OK, DiskSpace: EFlag.Green, FrontQueues: EFlag.Green }); - // unavailable disks display with the grey color - expect(severity1).toEqual(DISK_COLOR_STATE_TO_NUMERIC_SEVERITY.Grey); - expect(severity2).not.toEqual(DISK_COLOR_STATE_TO_NUMERIC_SEVERITY.Grey); - expect(severity3).toEqual(DISK_COLOR_STATE_TO_NUMERIC_SEVERITY.Grey); - expect(severity4).toEqual(DISK_COLOR_STATE_TO_NUMERIC_SEVERITY.Grey); - expect(severity5).not.toEqual(DISK_COLOR_STATE_TO_NUMERIC_SEVERITY.Grey); - expect(severity6).not.toEqual(DISK_COLOR_STATE_TO_NUMERIC_SEVERITY.Grey); - expect(severity7).toEqual(DISK_COLOR_STATE_TO_NUMERIC_SEVERITY.Grey); - expect(severity8).not.toEqual(DISK_COLOR_STATE_TO_NUMERIC_SEVERITY.Grey); + // unavailable vDisks display with the red color (with error) + expect(severity1).toEqual(DISK_COLOR_STATE_TO_NUMERIC_SEVERITY.Red); + expect(severity2).not.toEqual(DISK_COLOR_STATE_TO_NUMERIC_SEVERITY.Red); + expect(severity3).toEqual(DISK_COLOR_STATE_TO_NUMERIC_SEVERITY.Red); + expect(severity4).toEqual(DISK_COLOR_STATE_TO_NUMERIC_SEVERITY.Red); + expect(severity5).not.toEqual(DISK_COLOR_STATE_TO_NUMERIC_SEVERITY.Red); + expect(severity6).not.toEqual(DISK_COLOR_STATE_TO_NUMERIC_SEVERITY.Red); + expect(severity7).toEqual(DISK_COLOR_STATE_TO_NUMERIC_SEVERITY.Red); + expect(severity8).not.toEqual(DISK_COLOR_STATE_TO_NUMERIC_SEVERITY.Red); }); test('Should display as unavailable when no VDiskState is provided even if DiskSpace or FrontQueues flags are not green', () => { @@ -84,8 +84,8 @@ describe('VDisk state', () => { FrontQueues: EFlag.Yellow, }); - // unavailable disks display with the grey color - expect(severity1).toEqual(DISK_COLOR_STATE_TO_NUMERIC_SEVERITY.Grey); + // unavailable vDisks display with the red color + expect(severity1).toEqual(DISK_COLOR_STATE_TO_NUMERIC_SEVERITY.Red); }); test('Should display replicating VDisks in OK state with a distinct color', () => { diff --git a/src/utils/disks/calculateVDiskSeverity.ts b/src/utils/disks/calculateVDiskSeverity.ts index efc07a7b5a..9cfda2e9f2 100644 --- a/src/utils/disks/calculateVDiskSeverity.ts +++ b/src/utils/disks/calculateVDiskSeverity.ts @@ -3,6 +3,7 @@ import type {EVDiskState} from '../../types/api/vdisk'; import { DISK_COLOR_STATE_TO_NUMERIC_SEVERITY, + ERROR_SEVERITY, NOT_AVAILABLE_SEVERITY, VDISK_STATE_SEVERITY, } from './constants'; @@ -18,9 +19,9 @@ export function calculateVDiskSeverity< >(vDisk: T) { const {DiskSpace, VDiskState, FrontQueues, Replicated} = vDisk; - // if the disk is not available, this determines its status severity regardless of other features + // if the VDisk is not available, we consider that disk has an error severity if (!VDiskState) { - return NOT_AVAILABLE_SEVERITY; + return ERROR_SEVERITY; } const DiskSpaceSeverity = Math.min( @@ -44,10 +45,13 @@ export function calculateVDiskSeverity< } export function getStateSeverity(vDiskState?: EVDiskState) { + // if the VDiskState if undefined, we consider that this VDisk has an error if (!vDiskState) { - return NOT_AVAILABLE_SEVERITY; + return ERROR_SEVERITY; } + // If some strange value arrives that isn't in the map, + // we consider it "not available" and color it gray return VDISK_STATE_SEVERITY[vDiskState] ?? NOT_AVAILABLE_SEVERITY; } diff --git a/src/utils/disks/constants.ts b/src/utils/disks/constants.ts index a8d367cc50..a8e17c98d2 100644 --- a/src/utils/disks/constants.ts +++ b/src/utils/disks/constants.ts @@ -34,6 +34,8 @@ 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 ERROR_SEVERITY = DISK_COLOR_STATE_TO_NUMERIC_SEVERITY.Red; + export const VDISK_STATE_SEVERITY: Record = { [EVDiskState.OK]: DISK_COLOR_STATE_TO_NUMERIC_SEVERITY.Green, diff --git a/src/utils/disks/helpers.ts b/src/utils/disks/helpers.ts index d38e4c7f57..f1e4e0ef09 100644 --- a/src/utils/disks/helpers.ts +++ b/src/utils/disks/helpers.ts @@ -24,7 +24,7 @@ export function isFullVDiskData( const getSpaceFlag = generateEvaluator([EFlag.Green, EFlag.Yellow, EFlag.Red]); export const getSpaceSeverity = (allocatedPercent?: number) => { - return !isNil(allocatedPercent) ? getColorSeverity(getSpaceFlag(allocatedPercent)) : 0; + return isNil(allocatedPercent) ? 0 : getColorSeverity(getSpaceFlag(allocatedPercent)); }; export function getSeverityColor(severity: number | undefined) { @@ -66,10 +66,9 @@ export function getVDiskStatusIcon(severity?: number, isDonor?: boolean): IconDa } const isError = severity === DISK_COLOR_STATE_TO_NUMERIC_SEVERITY.Red; - const isReplicating = severity === DISK_COLOR_STATE_TO_NUMERIC_SEVERITY.Blue; // Display icon only for error and donor - if (isReplicating && isDonor) { + if (isDonor) { return DONOR_ICON; }