From 40e87b8838ffaae52c6439c9f4c21c9e755b834f Mon Sep 17 00:00:00 2001 From: Daria Vorontsova Date: Fri, 31 Oct 2025 14:53:50 +0300 Subject: [PATCH 1/3] fix(storage): keep separate search for groups and nodes --- .../GroupedStorageGroupsComponent.tsx | 8 ++--- .../StorageGroupsComponent.tsx | 6 ++-- .../StorageGroupsControls.tsx | 8 ++--- .../GroupedStorageNodesComponent.tsx | 9 +++--- .../StorageNodesComponent.tsx | 6 ++-- .../StorageNodesControls.tsx | 8 ++--- src/containers/Storage/constants.ts | 4 +++ .../Storage/useStorageQueryParams.ts | 31 ++++++++++++++++--- 8 files changed, 53 insertions(+), 27 deletions(-) create mode 100644 src/containers/Storage/constants.ts diff --git a/src/containers/Storage/PaginatedStorageGroups/GroupedStorageGroupsComponent.tsx b/src/containers/Storage/PaginatedStorageGroups/GroupedStorageGroupsComponent.tsx index 4ccbd85076..2e0f22c0bf 100644 --- a/src/containers/Storage/PaginatedStorageGroups/GroupedStorageGroupsComponent.tsx +++ b/src/containers/Storage/PaginatedStorageGroups/GroupedStorageGroupsComponent.tsx @@ -96,7 +96,7 @@ export function GroupedStorageGroupsComponent({ viewContext, }: PaginatedStorageProps) { const [autoRefreshInterval] = useAutoRefreshInterval(); - const {searchValue, storageGroupsGroupByParam, visibleEntities, handleShowAllGroups} = + const {groupsSearchValue, storageGroupsGroupByParam, visibleEntities, handleShowAllGroups} = useStorageQueryParams(); const {columnsToShow, columnsToSelect, setColumns} = useStorageGroupsSelectedColumns({ @@ -111,7 +111,7 @@ export function GroupedStorageGroupsComponent({ nodeId, groupId, pDiskId, - filter: searchValue, + filter: groupsSearchValue, shouldUseGroupsHandler: true, group: storageGroupsGroupByParam, }, @@ -152,7 +152,7 @@ export function GroupedStorageGroupsComponent({ groupId={groupId} pDiskId={pDiskId} filterGroupBy={storageGroupsGroupByParam} - searchValue={searchValue} + searchValue={groupsSearchValue} visibleEntities={'all'} scrollContainerRef={scrollContainerRef} onIsExpandedChange={setIsGroupExpanded} @@ -190,7 +190,7 @@ export function GroupedStorageGroupsComponent({ initialState={initialState} tableWrapperProps={{ scrollContainerRef, - scrollDependencies: [searchValue, storageGroupsGroupByParam, tableGroups], + scrollDependencies: [groupsSearchValue, storageGroupsGroupByParam, tableGroups], loading: isLoading, className: b('groups-wrapper'), }} diff --git a/src/containers/Storage/PaginatedStorageGroups/StorageGroupsComponent.tsx b/src/containers/Storage/PaginatedStorageGroups/StorageGroupsComponent.tsx index 14c1f52656..dd131e5139 100644 --- a/src/containers/Storage/PaginatedStorageGroups/StorageGroupsComponent.tsx +++ b/src/containers/Storage/PaginatedStorageGroups/StorageGroupsComponent.tsx @@ -18,7 +18,7 @@ export function StorageGroupsComponent({ scrollContainerRef, initialEntitiesCount, }: PaginatedStorageProps) { - const {searchValue, visibleEntities, handleShowAllGroups} = useStorageQueryParams(); + const {groupsSearchValue, visibleEntities, handleShowAllGroups} = useStorageQueryParams(); const storageGroupsHandlerHasGrouping = useStorageGroupsHandlerHasGrouping(); @@ -49,7 +49,7 @@ export function StorageGroupsComponent({ nodeId={nodeId} groupId={groupId} pDiskId={pDiskId} - searchValue={searchValue} + searchValue={groupsSearchValue} visibleEntities={visibleEntities} onShowAll={handleShowAllGroups} scrollContainerRef={scrollContainerRef} @@ -60,7 +60,7 @@ export function StorageGroupsComponent({ } tableWrapperProps={{ scrollContainerRef, - scrollDependencies: [searchValue, visibleEntities], + scrollDependencies: [groupsSearchValue, visibleEntities], }} /> ); diff --git a/src/containers/Storage/PaginatedStorageGroups/StorageGroupsControls.tsx b/src/containers/Storage/PaginatedStorageGroups/StorageGroupsControls.tsx index ec9f18067e..0af22bcde9 100644 --- a/src/containers/Storage/PaginatedStorageGroups/StorageGroupsControls.tsx +++ b/src/containers/Storage/PaginatedStorageGroups/StorageGroupsControls.tsx @@ -32,11 +32,11 @@ export function StorageGroupsControls({ entitiesLoading, }: StorageControlsProps) { const { - searchValue, + groupsSearchValue, storageType, visibleEntities, storageGroupsGroupByParam, - handleTextFilterChange, + handleTextFilterGroupsChange, handleStorageTypeChange, handleVisibleEntitiesChange, handleStorageGroupsGroupByParamChange, @@ -60,8 +60,8 @@ export function StorageGroupsControls({ return ( diff --git a/src/containers/Storage/PaginatedStorageNodes/GroupedStorageNodesComponent.tsx b/src/containers/Storage/PaginatedStorageNodes/GroupedStorageNodesComponent.tsx index a27723fefa..b6cac0d5dc 100644 --- a/src/containers/Storage/PaginatedStorageNodes/GroupedStorageNodesComponent.tsx +++ b/src/containers/Storage/PaginatedStorageNodes/GroupedStorageNodesComponent.tsx @@ -98,7 +98,8 @@ export function GroupedStorageNodesComponent({ }: PaginatedStorageProps) { const [autoRefreshInterval] = useAutoRefreshInterval(); - const {searchValue, storageNodesGroupByParam, handleShowAllNodes} = useStorageQueryParams(); + const {nodesSearchValue, storageNodesGroupByParam, handleShowAllNodes} = + useStorageQueryParams(); const {handleDataFetched, columnsSettings} = useStorageColumnsSettings(); const {columnsToShow, columnsToSelect, setColumns} = useStorageNodesColumnsToSelect({ @@ -111,7 +112,7 @@ export function GroupedStorageNodesComponent({ { database, with: 'all', - filter: searchValue, + filter: nodesSearchValue, node_id: nodeId, group_id: groupId, group: storageNodesGroupByParam, @@ -151,7 +152,7 @@ export function GroupedStorageNodesComponent({ database={database} nodeId={nodeId} groupId={groupId} - searchValue={searchValue} + searchValue={nodesSearchValue} visibleEntities="all" filterGroupBy={storageNodesGroupByParam} scrollContainerRef={scrollContainerRef} @@ -191,7 +192,7 @@ export function GroupedStorageNodesComponent({ initialState={initialState} tableWrapperProps={{ scrollContainerRef, - scrollDependencies: [searchValue, storageNodesGroupByParam, tableGroups], + scrollDependencies: [nodesSearchValue, storageNodesGroupByParam, tableGroups], loading: isLoading, className: b('groups-wrapper'), }} diff --git a/src/containers/Storage/PaginatedStorageNodes/StorageNodesComponent.tsx b/src/containers/Storage/PaginatedStorageNodes/StorageNodesComponent.tsx index 747361cc93..6187d5eb5a 100644 --- a/src/containers/Storage/PaginatedStorageNodes/StorageNodesComponent.tsx +++ b/src/containers/Storage/PaginatedStorageNodes/StorageNodesComponent.tsx @@ -18,7 +18,7 @@ export function StorageNodesComponent({ scrollContainerRef, initialEntitiesCount, }: PaginatedStorageProps) { - const {searchValue, visibleEntities, nodesUptimeFilter, handleShowAllNodes} = + const {nodesSearchValue, visibleEntities, nodesUptimeFilter, handleShowAllNodes} = useStorageQueryParams(); const viewerNodesHandlerHasGrouping = useViewerNodesHandlerHasGrouping(); @@ -52,7 +52,7 @@ export function StorageNodesComponent({ database={database} nodeId={nodeId} groupId={groupId} - searchValue={searchValue} + searchValue={nodesSearchValue} visibleEntities={visibleEntities} nodesUptimeFilter={nodesUptimeFilter} onShowAll={handleShowAllNodes} @@ -65,7 +65,7 @@ export function StorageNodesComponent({ } tableWrapperProps={{ scrollContainerRef, - scrollDependencies: [searchValue, visibleEntities, nodesUptimeFilter], + scrollDependencies: [nodesSearchValue, visibleEntities, nodesUptimeFilter], }} /> ); diff --git a/src/containers/Storage/PaginatedStorageNodes/StorageNodesControls.tsx b/src/containers/Storage/PaginatedStorageNodes/StorageNodesControls.tsx index 44293d9657..7b56886199 100644 --- a/src/containers/Storage/PaginatedStorageNodes/StorageNodesControls.tsx +++ b/src/containers/Storage/PaginatedStorageNodes/StorageNodesControls.tsx @@ -31,12 +31,12 @@ export function StorageNodesControls({ entitiesLoading, }: StorageControlsProps) { const { - searchValue, + nodesSearchValue, storageType, visibleEntities, nodesUptimeFilter, storageNodesGroupByParam, - handleTextFilterChange, + handleTextFilterNodesChange, handleStorageTypeChange, handleVisibleEntitiesChange, handleUptimeFilterChange, @@ -50,8 +50,8 @@ export function StorageNodesControls({ return ( diff --git a/src/containers/Storage/constants.ts b/src/containers/Storage/constants.ts new file mode 100644 index 0000000000..f479af1fb6 --- /dev/null +++ b/src/containers/Storage/constants.ts @@ -0,0 +1,4 @@ +export const STORAGE_SEARCH_PARAM_BY_TYPE: Record = { + groups: 'groupsSearch', + nodes: 'nodesSearch', +}; diff --git a/src/containers/Storage/useStorageQueryParams.ts b/src/containers/Storage/useStorageQueryParams.ts index 3caf92987e..8424e7006d 100644 --- a/src/containers/Storage/useStorageQueryParams.ts +++ b/src/containers/Storage/useStorageQueryParams.ts @@ -1,3 +1,5 @@ +import {useEffect} from 'react'; + import {StringParam, useQueryParams} from 'use-query-params'; import type {StorageType, VisibleEntities} from '../../store/reducers/storage/types'; @@ -6,11 +8,14 @@ import {NodesUptimeFilterValues, nodesUptimeFilterValuesSchema} from '../../util import {storageGroupsGroupByParamSchema} from './PaginatedStorageGroupsTable/columns/constants'; import {storageNodesGroupByParamSchema} from './PaginatedStorageNodesTable/columns/constants'; +import {STORAGE_SEARCH_PARAM_BY_TYPE} from './constants'; export function useStorageQueryParams() { const [queryParams, setQueryParams] = useQueryParams({ type: StringParam, visible: StringParam, + groupsSearch: StringParam, + nodesSearch: StringParam, search: StringParam, uptimeFilter: StringParam, storageNodesGroupBy: StringParam, @@ -20,7 +25,8 @@ export function useStorageQueryParams() { const storageType = storageTypeSchema.parse(queryParams.type); const visibleEntities = visibleEntitiesSchema.parse(queryParams.visible); - const searchValue = queryParams.search ?? ''; + const groupsSearchValue = queryParams.groupsSearch ?? ''; + const nodesSearchValue = queryParams.nodesSearch ?? ''; const nodesUptimeFilter = nodesUptimeFilterValuesSchema.parse(queryParams.uptimeFilter); const storageGroupsGroupByParam = storageGroupsGroupByParamSchema.parse( @@ -30,8 +36,20 @@ export function useStorageQueryParams() { queryParams.storageNodesGroupBy, ); - const handleTextFilterChange = (value: string) => { - setQueryParams({search: value || undefined}, 'replaceIn'); + useEffect(() => { + if (queryParams.search) { + const patch: Record = {search: undefined}; + patch[STORAGE_SEARCH_PARAM_BY_TYPE[storageType]] = queryParams.search; + setQueryParams(patch, 'replaceIn'); + } + }, [queryParams.search, storageType]); + + const handleTextFilterGroupsChange = (value: string) => { + setQueryParams({groupsSearch: value || undefined}, 'replaceIn'); + }; + + const handleTextFilterNodesChange = (value: string) => { + setQueryParams({nodesSearch: value || undefined}, 'replaceIn'); }; const handleVisibleEntitiesChange = (value: VisibleEntities) => { @@ -49,6 +67,7 @@ export function useStorageQueryParams() { const handleStorageGroupsGroupByParamChange = (value: string) => { setQueryParams({storageGroupsGroupBy: value}, 'replaceIn'); }; + const handleStorageNodesGroupByParamChange = (value: string) => { setQueryParams({storageNodesGroupBy: value}, 'replaceIn'); }; @@ -65,12 +84,14 @@ export function useStorageQueryParams() { return { storageType, visibleEntities, - searchValue, + groupsSearchValue, + nodesSearchValue, nodesUptimeFilter, storageGroupsGroupByParam, storageNodesGroupByParam, - handleTextFilterChange, + handleTextFilterGroupsChange, + handleTextFilterNodesChange, handleVisibleEntitiesChange, handleStorageTypeChange, handleUptimeFilterChange, From a07576b2dbe820d112063e0ecaf8473b70e461b3 Mon Sep 17 00:00:00 2001 From: Darya <148378389+DaryaVorontsova@users.noreply.github.com> Date: Fri, 31 Oct 2025 17:38:59 +0300 Subject: [PATCH 2/3] fix: disable restart for followers and remove duplicate tooltip (#3036) --- .../ButtonWithConfirmDialog.tsx | 6 ++--- src/containers/Tablets/TabletsTable.tsx | 26 +++++++++++++------ src/containers/Tablets/i18n/en.json | 1 + 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/components/ButtonWithConfirmDialog/ButtonWithConfirmDialog.tsx b/src/components/ButtonWithConfirmDialog/ButtonWithConfirmDialog.tsx index e27540aabe..70d724a46a 100644 --- a/src/components/ButtonWithConfirmDialog/ButtonWithConfirmDialog.tsx +++ b/src/components/ButtonWithConfirmDialog/ButtonWithConfirmDialog.tsx @@ -15,7 +15,6 @@ interface ButtonWithConfirmDialogProps { retryButtonText?: string; buttonDisabled?: ButtonProps['disabled']; buttonView?: ButtonProps['view']; - buttonTitle?: ButtonProps['title']; buttonClassName?: ButtonProps['className']; withPopover?: boolean; popoverContent?: PopoverProps['content']; @@ -32,7 +31,6 @@ export function ButtonWithConfirmDialog({ retryButtonText, buttonDisabled = false, buttonView = 'action', - buttonTitle, buttonClassName, withPopover = false, popoverContent, @@ -71,13 +69,13 @@ export function ButtonWithConfirmDialog({ disabled={buttonDisabled} loading={!buttonDisabled && buttonLoading} className={buttonClassName} - title={buttonTitle} > {children} ); }; + // keep : if button is disabled, popover won't open without this wrapper const renderContent = () => { if (withPopover) { return ( @@ -86,7 +84,7 @@ export function ButtonWithConfirmDialog({ placement={popoverPlacement} disabled={popoverDisabled} > - {renderButton()} + {renderButton()} ); } diff --git a/src/containers/Tablets/TabletsTable.tsx b/src/containers/Tablets/TabletsTable.tsx index 4b15d14e2e..843acd2768 100644 --- a/src/containers/Tablets/TabletsTable.tsx +++ b/src/containers/Tablets/TabletsTable.tsx @@ -25,6 +25,10 @@ import {useIsUserAllowedToMakeChanges} from '../../utils/hooks/useIsUserAllowedT import i18n from './i18n'; +function isFollowerTablet(state: TTabletStateInfo) { + return state.Leader === false; +} + function getColumns({nodeId}: {nodeId?: string | number}) { const columns: DataTableColumn[] = [ { @@ -34,7 +38,7 @@ function getColumns({nodeId}: {nodeId?: string | number}) { return i18n('Type'); }, render: ({row}) => { - const isFollower = row.Leader === false; + const isFollower = isFollowerTablet(row); return ( {row.Type} {isFollower ? follower : ''} @@ -138,7 +142,8 @@ function getColumns({nodeId}: {nodeId?: string | number}) { } function TabletActions(tablet: TTabletStateInfo) { - const isDisabledRestart = tablet.State === ETabletState.Stopped; + const isFollower = isFollowerTablet(tablet); + const isDisabledRestart = tablet.State === ETabletState.Stopped || isFollower; const isUserAllowedToMakeChanges = useIsUserAllowedToMakeChanges(); const [killTablet] = tabletApi.useKillTabletMutation(); @@ -147,10 +152,19 @@ function TabletActions(tablet: TTabletStateInfo) { return null; } + let popoverContent: React.ReactNode; + + if (isFollower) { + popoverContent = i18n('controls.kill-impossible-follower'); + } else if (!isUserAllowedToMakeChanges) { + popoverContent = i18n('controls.kill-not-allowed'); + } else { + popoverContent = i18n('dialog.kill-header'); + } + return ( { @@ -158,11 +172,7 @@ function TabletActions(tablet: TTabletStateInfo) { }} buttonDisabled={isDisabledRestart || !isUserAllowedToMakeChanges} withPopover - popoverContent={ - isUserAllowedToMakeChanges - ? i18n('dialog.kill-header') - : i18n('controls.kill-not-allowed') - } + popoverContent={popoverContent} popoverPlacement={['right', 'bottom']} popoverDisabled={false} > diff --git a/src/containers/Tablets/i18n/en.json b/src/containers/Tablets/i18n/en.json index f8966f3dad..2994ef95b8 100644 --- a/src/containers/Tablets/i18n/en.json +++ b/src/containers/Tablets/i18n/en.json @@ -10,6 +10,7 @@ "dialog.kill-header": "Restart tablet", "dialog.kill-text": "The tablet will be restarted. Do you want to proceed?", "controls.kill-not-allowed": "You don't have enough rights to restart tablet", + "controls.kill-impossible-follower": "It's impossible to restart a follower", "controls.search-placeholder": "Tablet ID", "controls.entities-count-label": "Tablets" } From f8b08ca307d6a0f31dd5001f58e7ccb0ddee778c Mon Sep 17 00:00:00 2001 From: Daria Vorontsova Date: Fri, 31 Oct 2025 18:02:14 +0300 Subject: [PATCH 3/3] Fix bugs --- src/containers/Storage/useStorageQueryParams.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/containers/Storage/useStorageQueryParams.ts b/src/containers/Storage/useStorageQueryParams.ts index 8424e7006d..169e60a392 100644 --- a/src/containers/Storage/useStorageQueryParams.ts +++ b/src/containers/Storage/useStorageQueryParams.ts @@ -1,4 +1,4 @@ -import {useEffect} from 'react'; +import React from 'react'; import {StringParam, useQueryParams} from 'use-query-params'; @@ -36,7 +36,7 @@ export function useStorageQueryParams() { queryParams.storageNodesGroupBy, ); - useEffect(() => { + React.useEffect(() => { if (queryParams.search) { const patch: Record = {search: undefined}; patch[STORAGE_SEARCH_PARAM_BY_TYPE[storageType]] = queryParams.search;