diff --git a/src/components/Illustration/Illustration.tsx b/src/components/Illustration/Illustration.tsx
index d4b70e8acd..407bbe7ec0 100644
--- a/src/components/Illustration/Illustration.tsx
+++ b/src/components/Illustration/Illustration.tsx
@@ -42,5 +42,5 @@ export const Illustration = ({name, className, ...props}: IllustrationProps) =>
}
}, [srcGetter]);
- return
;
+ return src ?
: null;
};
diff --git a/src/containers/Cluster/Cluster.tsx b/src/containers/Cluster/Cluster.tsx
index c2d2523446..6c8ac5a22a 100644
--- a/src/containers/Cluster/Cluster.tsx
+++ b/src/containers/Cluster/Cluster.tsx
@@ -8,8 +8,8 @@ import {Redirect, Route, Switch, useLocation, useRouteMatch} from 'react-router'
import {EntityStatus} from '../../components/EntityStatus/EntityStatus';
import {InternalLink} from '../../components/InternalLink';
import routes, {getLocationObjectFromHref} from '../../routes';
-import {getClusterInfo, updateDefaultClusterTab} from '../../store/reducers/cluster/cluster';
-import {getClusterNodes} from '../../store/reducers/clusterNodes/clusterNodes';
+import {clusterApi, updateDefaultClusterTab} from '../../store/reducers/cluster/cluster';
+import {clusterNodesApi} from '../../store/reducers/clusterNodes/clusterNodes';
import {setHeaderBreadcrumbs} from '../../store/reducers/header/header';
import type {
AdditionalClusterProps,
@@ -18,8 +18,8 @@ import type {
AdditionalVersionsProps,
} from '../../types/additionalProps';
import {cn} from '../../utils/cn';
-import {CLUSTER_DEFAULT_TITLE} from '../../utils/constants';
-import {useAutofetcher, useTypedDispatch, useTypedSelector} from '../../utils/hooks';
+import {CLUSTER_DEFAULT_TITLE, DEFAULT_POLLING_INTERVAL} from '../../utils/constants';
+import {useTypedDispatch, useTypedSelector} from '../../utils/hooks';
import {parseNodesToVersionsValues, parseVersionsToVersionToColorMap} from '../../utils/versions';
import {NodesWrapper} from '../Nodes/NodesWrapper';
import {StorageWrapper} from '../Storage/StorageWrapper';
@@ -57,34 +57,24 @@ function Cluster({
const queryParams = qs.parse(location.search, {
ignoreQueryPrefix: true,
});
- const {clusterName} = queryParams;
+ const {clusterName, backend} = queryParams;
const {
- data: cluster = {},
- loading: clusterLoading,
- wasLoaded: clusterWasLoaded,
- error: clusterError,
- groupsStats,
- } = useTypedSelector((state) => state.cluster);
- const {
- nodes,
- loading: nodesLoading,
- wasLoaded: nodesWasLoaded,
- } = useTypedSelector((state) => state.clusterNodes);
+ data: {clusterData: cluster = {}, groupsStats} = {},
+ isLoading: isClusterLoading,
+ error,
+ } = clusterApi.useGetClusterInfoQuery(clusterName ? String(clusterName) : undefined, {
+ pollingInterval: DEFAULT_POLLING_INTERVAL,
+ });
- const {Name} = cluster;
+ const clusterError = error && typeof error === 'object' ? error : undefined;
- const infoLoading = (clusterLoading && !clusterWasLoaded) || (nodesLoading && !nodesWasLoaded);
+ const {data: nodes = [], isLoading: isNodesLoading} =
+ clusterNodesApi.useGetClusterNodesQuery(undefined);
- React.useEffect(() => {
- dispatch(getClusterNodes());
- }, [dispatch]);
+ const infoLoading = isClusterLoading || isNodesLoading;
- useAutofetcher(
- () => dispatch(getClusterInfo(clusterName ? String(clusterName) : undefined)),
- [dispatch, clusterName],
- true,
- );
+ const {Name} = cluster;
React.useEffect(() => {
dispatch(setHeaderBreadcrumbs('cluster', {}));
@@ -138,7 +128,7 @@ function Cluster({
activeTab={activeTabId}
items={clusterTabs}
wrapTo={({id}, node) => {
- const path = getClusterPath(id as ClusterTab, queryParams);
+ const path = getClusterPath(id as ClusterTab, {clusterName, backend});
return (
state.singleClusterMode);
const {page, pageBreadcrumbsOptions} = useTypedSelector((state) => state.header);
- const {data} = useTypedSelector((state) => state.cluster);
const queryParams = parseQuery(location);
const clusterNameFromQuery = queryParams.clusterName?.toString();
+ const {currentData: {clusterData: data} = {}} =
+ clusterApi.useGetClusterInfoQuery(clusterNameFromQuery);
const clusterNameFinal = data?.Name || clusterNameFromQuery;
- React.useEffect(() => {
- dispatch(getClusterInfo(clusterNameFromQuery));
- }, [dispatch, clusterNameFromQuery]);
-
const breadcrumbItems = React.useMemo(() => {
const rawBreadcrumbs: RawBreadcrumbItem[] = [];
let options = pageBreadcrumbsOptions;
diff --git a/src/containers/Nodes/Nodes.tsx b/src/containers/Nodes/Nodes.tsx
index addba471ca..59ca59daa9 100644
--- a/src/containers/Nodes/Nodes.tsx
+++ b/src/containers/Nodes/Nodes.tsx
@@ -2,6 +2,7 @@ import React from 'react';
import DataTable from '@gravity-ui/react-data-table';
import {ASCENDING} from '@gravity-ui/react-data-table/build/esm/lib/constants';
+import {skipToken} from '@reduxjs/toolkit/query';
import {EntitiesCount} from '../../components/EntitiesCount';
import {AccessDenied} from '../../components/Errors/403';
@@ -12,28 +13,24 @@ import {Search} from '../../components/Search';
import {TableWithControlsLayout} from '../../components/TableWithControlsLayout/TableWithControlsLayout';
import {UptimeFilter} from '../../components/UptimeFIlter';
import {
- getComputeNodes,
- getNodes,
- resetNodesState,
- setDataWasNotLoaded,
- setNodesUptimeFilter,
+ nodesApi,
+ setInitialState,
setSearchValue,
setSort,
+ setUptimeFilter,
} from '../../store/reducers/nodes/nodes';
-import {selectFilteredNodes} from '../../store/reducers/nodes/selectors';
+import {filterNodes} from '../../store/reducers/nodes/selectors';
import type {NodesSortParams} from '../../store/reducers/nodes/types';
import {ProblemFilterValues, changeFilter} from '../../store/reducers/settings/settings';
import type {ProblemFilterValue} from '../../store/reducers/settings/types';
import type {AdditionalNodesProps} from '../../types/additionalProps';
import {cn} from '../../utils/cn';
-import {DEFAULT_TABLE_SETTINGS, USE_NODES_ENDPOINT_IN_DIAGNOSTICS_KEY} from '../../utils/constants';
import {
- useAutofetcher,
- useSetting,
- useTableSort,
- useTypedDispatch,
- useTypedSelector,
-} from '../../utils/hooks';
+ DEFAULT_POLLING_INTERVAL,
+ DEFAULT_TABLE_SETTINGS,
+ USE_NODES_ENDPOINT_IN_DIAGNOSTICS_KEY,
+} from '../../utils/constants';
+import {useSetting, useTableSort, useTypedDispatch, useTypedSelector} from '../../utils/hooks';
import {
NodesUptimeFilterValues,
isSortableNodesProperty,
@@ -57,47 +54,28 @@ export const Nodes = ({path, additionalNodesProps = {}}: NodesProps) => {
const isClusterNodes = !path;
- // Since Nodes component is used in several places,
- // we need to reset filters, searchValue and loading state
- // in nodes reducer when path changes
- React.useEffect(() => {
- dispatch(resetNodesState());
- }, [dispatch, path]);
-
const {
- wasLoaded,
- loading,
- error,
- nodesUptimeFilter,
+ uptimeFilter,
searchValue,
sortOrder = ASCENDING,
sortValue = 'NodeId',
- totalNodes,
} = useTypedSelector((state) => state.nodes);
const problemFilter = useTypedSelector((state) => state.settings.problemFilter);
const {autorefresh} = useTypedSelector((state) => state.schema);
- const nodes = useTypedSelector(selectFilteredNodes);
-
const [useNodesEndpoint] = useSetting(USE_NODES_ENDPOINT_IN_DIAGNOSTICS_KEY);
- const fetchNodes = React.useCallback(
- (isBackground: boolean) => {
- if (!isBackground) {
- dispatch(setDataWasNotLoaded());
- }
+ const useAutoRefresh = isClusterNodes ? true : autorefresh;
+ // If there is no path, it's cluster Nodes tab
+ const useGetComputeNodes = path && !useNodesEndpoint;
+ const nodesQuery = nodesApi.useGetNodesQuery(useGetComputeNodes ? skipToken : {path}, {
+ pollingInterval: useAutoRefresh ? DEFAULT_POLLING_INTERVAL : 0,
+ });
+ const computeQuery = nodesApi.useGetComputeNodesQuery(useGetComputeNodes ? {path} : skipToken, {
+ pollingInterval: useAutoRefresh ? DEFAULT_POLLING_INTERVAL : 0,
+ });
- // If there is no path, it's cluster Nodes tab
- if (path && !useNodesEndpoint) {
- dispatch(getComputeNodes({path}));
- } else {
- dispatch(getNodes({path}));
- }
- },
- [dispatch, path, useNodesEndpoint],
- );
-
- useAutofetcher(fetchNodes, [fetchNodes], isClusterNodes ? true : autorefresh);
+ const {currentData: data, isLoading, error} = useGetComputeNodes ? computeQuery : nodesQuery;
const [sort, handleSort] = useTableSort({sortValue, sortOrder}, (sortParams) =>
dispatch(setSort(sortParams as NodesSortParams)),
@@ -112,9 +90,25 @@ export const Nodes = ({path, additionalNodesProps = {}}: NodesProps) => {
};
const handleUptimeFilterChange = (value: NodesUptimeFilterValues) => {
- dispatch(setNodesUptimeFilter(value));
+ dispatch(setUptimeFilter(value));
};
+ // Since Nodes component is used in several places,
+ // we need to reset filters, searchValue
+ // in nodes reducer when path changes
+ React.useEffect(() => {
+ return () => {
+ // Clean data on component unmount
+ dispatch(setInitialState());
+ };
+ }, [dispatch, path]);
+
+ const nodes = React.useMemo(() => {
+ return filterNodes(data?.Nodes, {searchValue, uptimeFilter, problemFilter});
+ }, [data, searchValue, uptimeFilter, problemFilter]);
+
+ const totalNodes = data?.TotalNodes || 0;
+
const renderControls = () => {
return (
@@ -125,12 +119,12 @@ export const Nodes = ({path, additionalNodesProps = {}}: NodesProps) => {
value={searchValue}
/>
-
+
);
@@ -148,7 +142,7 @@ export const Nodes = ({path, additionalNodesProps = {}}: NodesProps) => {
if (nodes && nodes.length === 0) {
if (
problemFilter !== ProblemFilterValues.ALL ||
- nodesUptimeFilter !== NodesUptimeFilterValues.All
+ uptimeFilter !== NodesUptimeFilterValues.All
) {
return ;
}
@@ -169,7 +163,7 @@ export const Nodes = ({path, additionalNodesProps = {}}: NodesProps) => {
};
if (error) {
- if (error.status === 403) {
+ if ((error as any).status === 403) {
return ;
}
return ;
@@ -178,7 +172,7 @@ export const Nodes = ({path, additionalNodesProps = {}}: NodesProps) => {
return (
{renderControls()}
-
+
{renderTable()}
diff --git a/src/containers/Storage/Storage.tsx b/src/containers/Storage/Storage.tsx
index 21a47ca218..a470def884 100644
--- a/src/containers/Storage/Storage.tsx
+++ b/src/containers/Storage/Storage.tsx
@@ -7,25 +7,22 @@ import type {NodesSortParams} from '../../store/reducers/nodes/types';
import {selectNodesMap} from '../../store/reducers/nodesList';
import {STORAGE_TYPES, VISIBLE_ENTITIES} from '../../store/reducers/storage/constants';
import {
- selectEntitiesCount,
- selectFilteredGroups,
- selectFilteredNodes,
+ filterGroups,
+ filterNodes,
+ getUsageFilterOptions,
selectGroupsSortParams,
selectNodesSortParams,
- selectUsageFilterOptions,
} from '../../store/reducers/storage/selectors';
import {
- getStorageGroupsInfo,
- getStorageNodesInfo,
- setDataWasNotLoaded,
setGroupsSortParams,
setInitialState,
setNodesSortParams,
- setNodesUptimeFilter,
setStorageTextFilter,
setStorageType,
+ setUptimeFilter,
setUsageFilter,
setVisibleEntities,
+ storageApi,
} from '../../store/reducers/storage/storage';
import type {
StorageSortParams,
@@ -33,9 +30,8 @@ import type {
VisibleEntities,
} from '../../store/reducers/storage/types';
import type {AdditionalNodesProps} from '../../types/additionalProps';
-import {DEFAULT_TABLE_SETTINGS} from '../../utils/constants';
+import {DEFAULT_POLLING_INTERVAL, DEFAULT_TABLE_SETTINGS} from '../../utils/constants';
import {
- useAutofetcher,
useNodesRequestParams,
useStorageRequestParams,
useTableSort,
@@ -62,20 +58,14 @@ export const Storage = ({additionalNodesProps, tenant, nodeId}: StorageProps) =>
const {autorefresh} = useTypedSelector((state) => state.schema);
const {
- loading,
- wasLoaded,
- error,
type,
visible: visibleEntities,
filter,
usageFilter,
- nodesUptimeFilter,
+ uptimeFilter,
} = useTypedSelector((state) => state.storage);
- const storageNodes = useTypedSelector(selectFilteredNodes);
- const storageGroups = useTypedSelector(selectFilteredGroups);
- const entitiesCount = useTypedSelector(selectEntitiesCount);
+
const nodesMap = useTypedSelector(selectNodesMap);
- const usageFilterOptions = useTypedSelector(selectUsageFilterOptions);
const nodesSortParams = useTypedSelector(selectNodesSortParams);
const groupsSortParams = useTypedSelector(selectGroupsSortParams);
@@ -83,16 +73,9 @@ export const Storage = ({additionalNodesProps, tenant, nodeId}: StorageProps) =>
const isNodePage = nodeId !== undefined;
const storageType = isNodePage ? STORAGE_TYPES.groups : type;
- React.useEffect(() => {
- return () => {
- // Clean data on component unmount
- dispatch(setInitialState());
- };
- }, [dispatch]);
-
const nodesRequestParams = useNodesRequestParams({
filter,
- nodesUptimeFilter,
+ nodesUptimeFilter: uptimeFilter,
...nodesSortParams,
});
const storageRequestParams = useStorageRequestParams({
@@ -100,42 +83,56 @@ export const Storage = ({additionalNodesProps, tenant, nodeId}: StorageProps) =>
...groupsSortParams,
});
- const [nodesSort, handleNodesSort] = useTableSort(nodesSortParams, (params) =>
- dispatch(setNodesSortParams(params as NodesSortParams)),
+ const autoRefreshEnabled = tenant ? autorefresh : true;
+
+ const nodesQuery = storageApi.useGetStorageNodesInfoQuery(
+ {tenant, visibleEntities, ...nodesRequestParams},
+ {
+ skip: storageType !== STORAGE_TYPES.nodes,
+ pollingInterval: autoRefreshEnabled ? DEFAULT_POLLING_INTERVAL : 0,
+ },
);
- const [groupsSort, handleGroupsSort] = useTableSort(groupsSortParams, (params) =>
- dispatch(setGroupsSortParams(params as StorageSortParams)),
+ const groupsQuery = storageApi.useGetStorageGroupsInfoQuery(
+ {tenant, visibleEntities, nodeId, ...storageRequestParams},
+ {
+ skip: storageType !== STORAGE_TYPES.groups,
+ pollingInterval: autoRefreshEnabled ? DEFAULT_POLLING_INTERVAL : 0,
+ },
);
- const fetchData = React.useCallback(
- (isBackground: boolean) => {
- if (!isBackground) {
- dispatch(setDataWasNotLoaded());
- }
+ const {currentData, isFetching, error} =
+ storageType === STORAGE_TYPES.nodes ? nodesQuery : groupsQuery;
- const nodesParams = nodesRequestParams || {};
- const storageParams = storageRequestParams || {};
+ const {currentData: {nodes = []} = {}} = nodesQuery;
+ const {currentData: {groups = []} = {}} = groupsQuery;
+ const {nodes: _, groups: __, ...entitiesCount} = currentData ?? {found: 0, total: 0};
- if (storageType === STORAGE_TYPES.nodes) {
- dispatch(getStorageNodesInfo({tenant, visibleEntities, ...nodesParams}));
- } else {
- dispatch(getStorageGroupsInfo({tenant, visibleEntities, nodeId, ...storageParams}));
- }
- },
- [
- dispatch,
- tenant,
- nodeId,
- visibleEntities,
- storageType,
- storageRequestParams,
- nodesRequestParams,
- ],
+ const isLoading = currentData === undefined && isFetching;
+
+ const storageNodes = React.useMemo(
+ () => filterNodes(nodes, filter, uptimeFilter),
+ [filter, nodes, uptimeFilter],
+ );
+ const storageGroups = React.useMemo(
+ () => filterGroups(groups, filter, usageFilter),
+ [filter, groups, usageFilter],
);
- const autorefreshEnabled = tenant ? autorefresh : true;
+ const usageFilterOptions = React.useMemo(() => getUsageFilterOptions(groups), [groups]);
- useAutofetcher(fetchData, [fetchData], autorefreshEnabled);
+ React.useEffect(() => {
+ return () => {
+ // Clean data on component unmount
+ dispatch(setInitialState());
+ };
+ }, [dispatch]);
+
+ const [nodesSort, handleNodesSort] = useTableSort(nodesSortParams, (params) =>
+ dispatch(setNodesSortParams(params as NodesSortParams)),
+ );
+ const [groupsSort, handleGroupsSort] = useTableSort(groupsSortParams, (params) =>
+ dispatch(setGroupsSortParams(params as StorageSortParams)),
+ );
const handleUsageFilterChange = (value: string[]) => {
dispatch(setUsageFilter(value));
@@ -154,7 +151,7 @@ export const Storage = ({additionalNodesProps, tenant, nodeId}: StorageProps) =>
};
const handleUptimeFilterChange = (value: NodesUptimeFilterValues) => {
- dispatch(setNodesUptimeFilter(value));
+ dispatch(setUptimeFilter(value));
};
const handleShowAllNodes = () => {
@@ -167,6 +164,7 @@ export const Storage = ({additionalNodesProps, tenant, nodeId}: StorageProps) =>
{storageType === STORAGE_TYPES.groups && (
)}
{storageType === STORAGE_TYPES.nodes && (
handleStorageTypeChange={handleStorageTypeChange}
visibleEntities={visibleEntities}
handleVisibleEntitiesChange={handleGroupVisibilityChange}
- nodesUptimeFilter={nodesUptimeFilter}
+ nodesUptimeFilter={uptimeFilter}
handleNodesUptimeFilterChange={handleUptimeFilterChange}
groupsUsageFilter={usageFilter}
groupsUsageFilterOptions={usageFilterOptions}
@@ -213,13 +212,13 @@ export const Storage = ({additionalNodesProps, tenant, nodeId}: StorageProps) =>
: storageNodes.length
}
entitiesCountTotal={entitiesCount.total}
- entitiesLoading={loading && !wasLoaded}
+ entitiesLoading={isLoading}
/>
);
};
if (error) {
- if (error.status === 403) {
+ if ((error as any).status === 403) {
return ;
}
@@ -229,7 +228,7 @@ export const Storage = ({additionalNodesProps, tenant, nodeId}: StorageProps) =>
return (
{renderControls()}
-
+
{renderDataTable()}
diff --git a/src/containers/Tenant/Diagnostics/Diagnostics.tsx b/src/containers/Tenant/Diagnostics/Diagnostics.tsx
index d2ba24f0ef..d36da5a768 100644
--- a/src/containers/Tenant/Diagnostics/Diagnostics.tsx
+++ b/src/containers/Tenant/Diagnostics/Diagnostics.tsx
@@ -8,6 +8,7 @@ import {Link} from 'react-router-dom';
import {Loader} from '../../../components/Loader';
import routes, {createHref} from '../../../routes';
+import {api} from '../../../store/reducers/api';
import {disableAutorefresh, enableAutorefresh} from '../../../store/reducers/schema/schema';
import {TENANT_DIAGNOSTICS_TABS_IDS} from '../../../store/reducers/tenant/constants';
import {setDiagnosticsTab} from '../../../store/reducers/tenant/tenant';
@@ -89,6 +90,7 @@ function Diagnostics(props: DiagnosticsProps) {
const onAutorefreshToggle = (value: boolean) => {
if (value) {
+ dispatch(api.util.invalidateTags(['All']));
dispatch(enableAutorefresh());
} else {
dispatch(disableAutorefresh());
diff --git a/src/containers/Versions/GroupedNodesTree/GroupedNodesTree.tsx b/src/containers/Versions/GroupedNodesTree/GroupedNodesTree.tsx
index 1454fdead5..c499210805 100644
--- a/src/containers/Versions/GroupedNodesTree/GroupedNodesTree.tsx
+++ b/src/containers/Versions/GroupedNodesTree/GroupedNodesTree.tsx
@@ -2,7 +2,7 @@ import React from 'react';
import {TreeView} from 'ydb-ui-components';
-import type {PreparedClusterNode} from '../../../store/reducers/clusterNodes/types';
+import type {PreparedClusterNode} from '../../../store/reducers/clusterNodes/clusterNodes';
import type {VersionValue} from '../../../types/versions';
import {cn} from '../../../utils/cn';
import {NodesTable} from '../NodesTable/NodesTable';
diff --git a/src/containers/Versions/NodesTable/NodesTable.tsx b/src/containers/Versions/NodesTable/NodesTable.tsx
index 21a60d2ffb..e0c8f38129 100644
--- a/src/containers/Versions/NodesTable/NodesTable.tsx
+++ b/src/containers/Versions/NodesTable/NodesTable.tsx
@@ -4,7 +4,7 @@ import DataTable from '@gravity-ui/react-data-table';
import {EntityStatus} from '../../../components/EntityStatus/EntityStatus';
import {PoolsGraph} from '../../../components/PoolsGraph/PoolsGraph';
import {ProgressViewer} from '../../../components/ProgressViewer/ProgressViewer';
-import type {PreparedClusterNode} from '../../../store/reducers/clusterNodes/types';
+import type {PreparedClusterNode} from '../../../store/reducers/clusterNodes/clusterNodes';
import {DEFAULT_TABLE_SETTINGS} from '../../../utils/constants';
import {formatBytes} from '../../../utils/dataFormatters/dataFormatters';
import {isUnavailableNode} from '../../../utils/nodes';
diff --git a/src/containers/Versions/NodesTreeTitle/NodesTreeTitle.tsx b/src/containers/Versions/NodesTreeTitle/NodesTreeTitle.tsx
index 4b930f24da..5f58f7c034 100644
--- a/src/containers/Versions/NodesTreeTitle/NodesTreeTitle.tsx
+++ b/src/containers/Versions/NodesTreeTitle/NodesTreeTitle.tsx
@@ -1,7 +1,7 @@
import {Progress} from '@gravity-ui/uikit';
import {ClipboardButton} from '../../../components/ClipboardButton';
-import type {PreparedClusterNode} from '../../../store/reducers/clusterNodes/types';
+import type {PreparedClusterNode} from '../../../store/reducers/clusterNodes/clusterNodes';
import type {VersionValue} from '../../../types/versions';
import {cn} from '../../../utils/cn';
import type {GroupedNodesItem} from '../types';
diff --git a/src/containers/Versions/Versions.tsx b/src/containers/Versions/Versions.tsx
index 292044dd72..3a53ce62b3 100644
--- a/src/containers/Versions/Versions.tsx
+++ b/src/containers/Versions/Versions.tsx
@@ -3,10 +3,10 @@ import React from 'react';
import {Checkbox, RadioButton} from '@gravity-ui/uikit';
import {Loader} from '../../components/Loader';
-import {getClusterNodes} from '../../store/reducers/clusterNodes/clusterNodes';
+import {clusterNodesApi} from '../../store/reducers/clusterNodes/clusterNodes';
import type {VersionToColorMap} from '../../types/versions';
import {cn} from '../../utils/cn';
-import {useAutofetcher, useTypedDispatch, useTypedSelector} from '../../utils/hooks';
+import {DEFAULT_POLLING_INTERVAL} from '../../utils/constants';
import {GroupedNodesTree} from './GroupedNodesTree/GroupedNodesTree';
import {getGroupedStorageNodes, getGroupedTenantNodes, getOtherNodes} from './groupNodes';
@@ -21,11 +21,10 @@ interface VersionsProps {
}
export const Versions = ({versionToColor}: VersionsProps) => {
- const dispatch = useTypedDispatch();
-
- const {nodes = [], loading, wasLoaded} = useTypedSelector((state) => state.clusterNodes);
-
- useAutofetcher(() => dispatch(getClusterNodes()), [dispatch], true);
+ const {data: nodes = [], isLoading: isNodesLoading} = clusterNodesApi.useGetClusterNodesQuery(
+ undefined,
+ {pollingInterval: DEFAULT_POLLING_INTERVAL},
+ );
const [groupByValue, setGroupByValue] = React.useState(GroupByValue.VERSION);
const [expanded, setExpanded] = React.useState(false);
@@ -64,7 +63,7 @@ export const Versions = ({versionToColor}: VersionsProps) => {
);
};
- if (loading && !wasLoaded) {
+ if (isNodesLoading) {
return ;
}
diff --git a/src/containers/Versions/groupNodes.ts b/src/containers/Versions/groupNodes.ts
index c4df31825c..f49902d3ed 100644
--- a/src/containers/Versions/groupNodes.ts
+++ b/src/containers/Versions/groupNodes.ts
@@ -1,6 +1,6 @@
import groupBy from 'lodash/groupBy';
-import type {PreparedClusterNode} from '../../store/reducers/clusterNodes/types';
+import type {PreparedClusterNode} from '../../store/reducers/clusterNodes/clusterNodes';
import type {VersionToColorMap} from '../../types/versions';
import {getMinorVersion, parseNodesToVersionsValues} from '../../utils/versions';
diff --git a/src/containers/Versions/types.ts b/src/containers/Versions/types.ts
index 0e766cb0fb..324d6c81f1 100644
--- a/src/containers/Versions/types.ts
+++ b/src/containers/Versions/types.ts
@@ -1,4 +1,4 @@
-import type {PreparedClusterNode} from '../../store/reducers/clusterNodes/types';
+import type {PreparedClusterNode} from '../../store/reducers/clusterNodes/clusterNodes';
import type {VersionValue} from '../../types/versions';
export interface GroupedNodesItem {
diff --git a/src/services/api.ts b/src/services/api.ts
index 9a5e6b412d..4c1930ca21 100644
--- a/src/services/api.ts
+++ b/src/services/api.ts
@@ -47,20 +47,21 @@ import {settingsManager} from './settings';
type AxiosOptions = {
concurrentId?: string;
+ signal?: AbortSignal;
};
export class YdbEmbeddedAPI extends AxiosWrapper {
getPath(path: string) {
return `${BACKEND ?? ''}${path}`;
}
- getClusterInfo(clusterName?: string, {concurrentId}: AxiosOptions = {}) {
+ getClusterInfo(clusterName?: string, {concurrentId, signal}: AxiosOptions = {}) {
return this.get(
this.getPath('/viewer/json/cluster'),
{
name: clusterName,
tablets: true,
},
- {concurrentId: concurrentId || `getClusterInfo`},
+ {concurrentId: concurrentId || `getClusterInfo`, requestConfig: {signal}},
);
}
getClusterNodes({concurrentId}: AxiosOptions = {}) {
@@ -102,14 +103,14 @@ export class YdbEmbeddedAPI extends AxiosWrapper {
sortValue,
...params
}: NodesApiRequestParams,
- {concurrentId}: AxiosOptions = {},
+ {concurrentId, signal}: AxiosOptions = {},
) {
const sort = prepareSortValue(sortValue, sortOrder);
return this.get(
this.getPath('/viewer/json/nodes?enums=true'),
{with: visibleEntities, type, tablets, sort, ...params},
- {concurrentId},
+ {concurrentId, requestConfig: {signal}},
);
}
/** @deprecated use getNodes instead */
@@ -521,13 +522,13 @@ export class YdbEmbeddedAPI extends AxiosWrapper {
}
export class YdbWebVersionAPI extends YdbEmbeddedAPI {
- getClusterInfo(clusterName: string) {
+ getClusterInfo(clusterName: string, {signal}: AxiosOptions = {}) {
return this.get(
`${META_BACKEND || ''}/meta/cluster`,
{
name: clusterName,
},
- {concurrentId: `getCluster${clusterName}`},
+ {concurrentId: `getCluster${clusterName}`, requestConfig: {signal}},
).then(parseMetaCluster);
}
diff --git a/src/store/configureStore.ts b/src/store/configureStore.ts
index 1efc00cec0..7185ebd7f3 100644
--- a/src/store/configureStore.ts
+++ b/src/store/configureStore.ts
@@ -29,7 +29,7 @@ function _configureStore<
getDefaultMiddleware({
immutableCheck: {ignoredPaths: ['tooltip.currentHoveredRef']},
serializableCheck: {
- ignoredPaths: ['tooltip.currentHoveredRef'],
+ ignoredPaths: ['tooltip.currentHoveredRef', 'api'],
ignoredActions: [UPDATE_REF],
},
}).concat(locationMiddleware, ...middleware),
diff --git a/src/store/reducers/api.ts b/src/store/reducers/api.ts
index f335375e8f..d29da43430 100644
--- a/src/store/reducers/api.ts
+++ b/src/store/reducers/api.ts
@@ -8,6 +8,8 @@ export const api = createApi({
* which is why no endpoints are shown below.
*/
endpoints: () => ({}),
+ refetchOnMountOrArgChange: true,
+ tagTypes: ['All'],
});
export const _NEVER = Symbol();
diff --git a/src/store/reducers/cluster/cluster.ts b/src/store/reducers/cluster/cluster.ts
index 93a9a0a0fc..058c906177 100644
--- a/src/store/reducers/cluster/cluster.ts
+++ b/src/store/reducers/cluster/cluster.ts
@@ -1,131 +1,94 @@
-import type {Dispatch, Reducer} from '@reduxjs/toolkit';
+import {createSlice} from '@reduxjs/toolkit';
+import type {Dispatch, PayloadAction} from '@reduxjs/toolkit';
import type {ClusterTab} from '../../../containers/Cluster/utils';
import {clusterTabsIds, isClusterTab} from '../../../containers/Cluster/utils';
+import type {TClusterInfo} from '../../../types/api/cluster';
import {DEFAULT_CLUSTER_TAB_KEY} from '../../../utils/constants';
-import {createApiRequest, createRequestActionTypes} from '../../utils';
+import {api} from '../api';
-import type {ClusterAction, ClusterState} from './types';
+import type {ClusterGroupsStats, ClusterState} from './types';
import {createSelectClusterGroupsQuery, parseGroupsStatsQueryResponse} from './utils';
-const SET_DEFAULT_CLUSTER_TAB = 'cluster/SET_DEFAULT_CLUSTER_TAB';
-
-export const FETCH_CLUSTER = createRequestActionTypes('cluster', 'FETCH_CLUSTER');
-
const defaultClusterTabLS = localStorage.getItem(DEFAULT_CLUSTER_TAB_KEY);
-let defaultClusterTab;
+let defaultClusterTab: ClusterTab;
if (isClusterTab(defaultClusterTabLS)) {
defaultClusterTab = defaultClusterTabLS;
} else {
defaultClusterTab = clusterTabsIds.overview;
}
-const initialState = {loading: true, wasLoaded: false, defaultClusterTab};
-
-const cluster: Reducer = (state = initialState, action) => {
- switch (action.type) {
- case FETCH_CLUSTER.REQUEST: {
- return {
- ...state,
- loading: true,
- };
- }
- case FETCH_CLUSTER.SUCCESS: {
- const {clusterData, groupsStats} = action.data;
-
- return {
- ...state,
- data: clusterData,
- groupsStats,
- loading: false,
- wasLoaded: true,
- error: undefined,
- };
- }
- case FETCH_CLUSTER.FAILURE: {
- if (action.error?.isCancelled) {
- return state;
- }
-
- return {
- ...state,
- error: action.error,
- loading: false,
- };
- }
- case SET_DEFAULT_CLUSTER_TAB: {
- return {
- ...state,
- defaultClusterTab: action.data,
- };
- }
- default:
- return state;
- }
+const initialState: ClusterState = {
+ defaultClusterTab,
};
-
-export function setDefaultClusterTab(tab: ClusterTab) {
- return {
- type: SET_DEFAULT_CLUSTER_TAB,
- data: tab,
- } as const;
-}
+const clusterSlice = createSlice({
+ name: 'cluster',
+ initialState,
+ reducers: {
+ setDefaultClusterTab(state, action: PayloadAction) {
+ state.defaultClusterTab = action.payload;
+ },
+ },
+});
export function updateDefaultClusterTab(tab: string) {
return (dispatch: Dispatch) => {
if (isClusterTab(tab)) {
localStorage.setItem(DEFAULT_CLUSTER_TAB_KEY, tab);
- dispatch(setDefaultClusterTab(tab));
+ dispatch(clusterSlice.actions.setDefaultClusterTab(tab));
}
};
}
-export function getClusterInfo(clusterName?: string) {
- async function requestClusterData() {
- // Error here is handled by createApiRequest
- const clusterData = await window.api.getClusterInfo(clusterName);
-
- try {
- const clusterRoot = clusterData.Domain;
-
- // Without domain we cannot get stats from system tables
- if (!clusterRoot) {
- return {
- clusterData,
- };
- }
-
- const query = createSelectClusterGroupsQuery(clusterRoot);
-
- // Normally query request should be fulfilled within 300-400ms even on very big clusters
- // Table with stats is supposed to be very small (less than 10 rows)
- // So we batch this request with cluster request to prevent possible layout shifts, if data is missing
- const groupsStatsResponse = await window.api.sendQuery({
- schema: 'modern',
- query: query,
- database: clusterRoot,
- action: 'execute-scan',
- });
-
- return {
- clusterData,
- groupsStats: parseGroupsStatsQueryResponse(groupsStatsResponse),
- };
- } catch {
- // Doesn't return groups stats on error
- // It could happen if user doesn't have access rights
- // Or there are no system tables in cluster root
- return {
- clusterData,
- };
- }
- }
-
- return createApiRequest({
- request: requestClusterData(),
- actions: FETCH_CLUSTER,
- });
-}
-
-export default cluster;
+export default clusterSlice.reducer;
+
+export const clusterApi = api.injectEndpoints({
+ endpoints: (builder) => ({
+ getClusterInfo: builder.query({
+ queryFn: async (
+ clusterName = '',
+ {signal},
+ ): Promise<
+ | {data: {clusterData: TClusterInfo; groupsStats?: ClusterGroupsStats}}
+ | {error: unknown}
+ > => {
+ try {
+ const clusterData = await window.api.getClusterInfo(clusterName, {signal});
+ const clusterRoot = clusterData.Domain;
+
+ // Without domain we cannot get stats from system tables
+ if (!clusterRoot) {
+ return {data: {clusterData}};
+ }
+
+ try {
+ const query = createSelectClusterGroupsQuery(clusterRoot);
+
+ // Normally query request should be fulfilled within 300-400ms even on very big clusters
+ // Table with stats is supposed to be very small (less than 10 rows)
+ // So we batch this request with cluster request to prevent possible layout shifts, if data is missing
+ const groupsStatsResponse = await window.api.sendQuery({
+ schema: 'modern',
+ query: query,
+ database: clusterRoot,
+ action: 'execute-scan',
+ });
+
+ return {
+ data: {
+ clusterData,
+ groupsStats: parseGroupsStatsQueryResponse(groupsStatsResponse),
+ },
+ };
+ } catch {
+ return {data: {clusterData}};
+ }
+ } catch (error) {
+ return {error};
+ }
+ },
+ providesTags: ['All'],
+ }),
+ }),
+});
diff --git a/src/store/reducers/cluster/types.ts b/src/store/reducers/cluster/types.ts
index e21a944b96..ca4030f53e 100644
--- a/src/store/reducers/cluster/types.ts
+++ b/src/store/reducers/cluster/types.ts
@@ -1,9 +1,5 @@
import type {ClusterTab} from '../../../containers/Cluster/utils';
import type {TClusterInfo} from '../../../types/api/cluster';
-import type {IResponseError} from '../../../types/api/error';
-import type {ApiRequestAction} from '../../utils';
-
-import type {FETCH_CLUSTER, setDefaultClusterTab} from './cluster';
export interface DiskErasureGroupsStats {
diskType: string;
@@ -21,11 +17,6 @@ export type DiskGroupsStats = Record;
export type ClusterGroupsStats = Record;
export interface ClusterState {
- loading: boolean;
- wasLoaded: boolean;
- data?: TClusterInfo;
- error?: IResponseError;
- groupsStats?: ClusterGroupsStats;
defaultClusterTab: ClusterTab;
}
@@ -33,7 +24,3 @@ export interface HandledClusterResponse {
clusterData: TClusterInfo;
groupsStats: ClusterGroupsStats;
}
-
-export type ClusterAction =
- | ApiRequestAction
- | ReturnType;
diff --git a/src/store/reducers/clusterNodes/clusterNodes.tsx b/src/store/reducers/clusterNodes/clusterNodes.tsx
index f8b61c8798..f11b960cfd 100644
--- a/src/store/reducers/clusterNodes/clusterNodes.tsx
+++ b/src/store/reducers/clusterNodes/clusterNodes.tsx
@@ -1,66 +1,30 @@
-import type {Reducer} from '@reduxjs/toolkit';
-
+import type {TSystemStateInfo} from '../../../types/api/nodes';
import {calcUptime} from '../../../utils/dataFormatters/dataFormatters';
-import {createApiRequest, createRequestActionTypes} from '../../utils';
-
-import type {ClusterNodesAction, ClusterNodesState, PreparedClusterNode} from './types';
-
-export const FETCH_CLUSTER_NODES = createRequestActionTypes('cluster', 'FETCH_CLUSTER_NODES');
-
-const initialState = {loading: false, wasLoaded: false};
-
-const clusterNodes: Reducer = (
- state = initialState,
- action,
-) => {
- switch (action.type) {
- case FETCH_CLUSTER_NODES.REQUEST: {
- return {
- ...state,
- loading: true,
- };
- }
- case FETCH_CLUSTER_NODES.SUCCESS: {
- const {data = []} = action;
-
- return {
- ...state,
- nodes: data,
- loading: false,
- wasLoaded: true,
- error: undefined,
- };
- }
- case FETCH_CLUSTER_NODES.FAILURE: {
- if (action.error?.isCancelled) {
- return state;
- }
-
- return {
- ...state,
- error: action.error,
- loading: false,
- };
- }
- default:
- return state;
- }
-};
+import {api} from '../api';
-export function getClusterNodes() {
- return createApiRequest({
- request: window.api.getClusterNodes(),
- actions: FETCH_CLUSTER_NODES,
- dataHandler: (data): PreparedClusterNode[] => {
- const {SystemStateInfo: nodes = []} = data;
- return nodes.map((node) => {
- return {
- ...node,
- uptime: calcUptime(node.StartTime),
- };
- });
- },
- });
+export interface PreparedClusterNode extends TSystemStateInfo {
+ uptime: string;
}
-export default clusterNodes;
+export const clusterNodesApi = api.injectEndpoints({
+ endpoints: (builder) => ({
+ getClusterNodes: builder.query({
+ queryFn: async () => {
+ try {
+ const result = await window.api.getClusterNodes();
+ const {SystemStateInfo: nodes = []} = result;
+ const data: PreparedClusterNode[] = nodes.map((node) => {
+ return {
+ ...node,
+ uptime: calcUptime(node.StartTime),
+ };
+ });
+ return {data};
+ } catch (error) {
+ return {error};
+ }
+ },
+ providesTags: ['All'],
+ }),
+ }),
+});
diff --git a/src/store/reducers/clusterNodes/types.ts b/src/store/reducers/clusterNodes/types.ts
deleted file mode 100644
index 92bfc1dc8f..0000000000
--- a/src/store/reducers/clusterNodes/types.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import type {IResponseError} from '../../../types/api/error';
-import type {TSystemStateInfo} from '../../../types/api/nodes';
-import type {ApiRequestAction} from '../../utils';
-
-import type {FETCH_CLUSTER_NODES} from './clusterNodes';
-
-export interface PreparedClusterNode extends TSystemStateInfo {
- uptime: string;
-}
-
-export interface ClusterNodesState {
- loading: boolean;
- wasLoaded: boolean;
- nodes?: PreparedClusterNode[];
- error?: IResponseError;
-}
-
-export type ClusterNodesAction = ApiRequestAction<
- typeof FETCH_CLUSTER_NODES,
- PreparedClusterNode[],
- IResponseError
->;
diff --git a/src/store/reducers/clusters/clusters.ts b/src/store/reducers/clusters/clusters.ts
index 109c7e67c0..9444ac279a 100644
--- a/src/store/reducers/clusters/clusters.ts
+++ b/src/store/reducers/clusters/clusters.ts
@@ -38,6 +38,7 @@ export const clustersApi = api.injectEndpoints({
return {error};
}
},
+ providesTags: ['All'],
}),
}),
});
diff --git a/src/store/reducers/index.ts b/src/store/reducers/index.ts
index cccd7677a7..926c0c5053 100644
--- a/src/store/reducers/index.ts
+++ b/src/store/reducers/index.ts
@@ -3,7 +3,6 @@ import {combineReducers} from '@reduxjs/toolkit';
import {api} from './api';
import authentication from './authentication/authentication';
import cluster from './cluster/cluster';
-import clusterNodes from './clusterNodes/clusterNodes';
import clusters from './clusters/clusters';
import describe from './describe';
import executeQuery from './executeQuery';
@@ -55,7 +54,6 @@ export const rootReducer = {
topNodesByCpu,
topNodesByMemory,
cluster,
- clusterNodes,
tenant,
storage,
topStorageGroups,
diff --git a/src/store/reducers/nodes/nodes.ts b/src/store/reducers/nodes/nodes.ts
index 2289fd4e41..c6083a8671 100644
--- a/src/store/reducers/nodes/nodes.ts
+++ b/src/store/reducers/nodes/nodes.ts
@@ -1,148 +1,83 @@
-import type {Reducer} from '@reduxjs/toolkit';
+import {createSlice} from '@reduxjs/toolkit';
+import type {PayloadAction} from '@reduxjs/toolkit';
import {EVersion} from '../../../types/api/compute';
import {NodesUptimeFilterValues} from '../../../utils/nodes';
-import {createApiRequest, createRequestActionTypes} from '../../utils';
+import {api} from '../api';
import type {
ComputeApiRequestParams,
- NodesAction,
NodesApiRequestParams,
NodesSortParams,
NodesState,
} from './types';
import {prepareComputeNodesData, prepareNodesData} from './utils';
-export const FETCH_NODES = createRequestActionTypes('nodes', 'FETCH_NODES');
-
-const RESET_NODES_STATE = 'nodes/RESET_NODES_STATE';
-const SET_NODES_UPTIME_FILTER = 'nodes/SET_NODES_UPTIME_FILTER';
-const SET_DATA_WAS_NOT_LOADED = 'nodes/SET_DATA_WAS_NOT_LOADED';
-const SET_SEARCH_VALUE = 'nodes/SET_SEARCH_VALUE';
-const SET_SORT = 'nodes/SET_SORT';
-
-const initialState = {
- loading: false,
- wasLoaded: false,
- nodesUptimeFilter: NodesUptimeFilterValues.All,
+const initialState: NodesState = {
+ uptimeFilter: NodesUptimeFilterValues.All,
searchValue: '',
};
-const nodes: Reducer = (state = initialState, action) => {
- switch (action.type) {
- case FETCH_NODES.REQUEST: {
- return {
- ...state,
- loading: true,
- };
- }
- case FETCH_NODES.SUCCESS: {
- return {
- ...state,
- data: action.data?.Nodes,
- totalNodes: action.data?.TotalNodes,
- loading: false,
- wasLoaded: true,
- error: undefined,
- };
- }
- case FETCH_NODES.FAILURE: {
- if (action.error?.isCancelled) {
- return state;
- }
-
- return {
- ...state,
- error: action.error,
- loading: false,
- };
- }
- case RESET_NODES_STATE: {
- return {
- ...state,
- loading: initialState.loading,
- wasLoaded: initialState.wasLoaded,
- nodesUptimeFilter: initialState.nodesUptimeFilter,
- searchValue: initialState.searchValue,
- };
- }
- case SET_NODES_UPTIME_FILTER: {
- return {
- ...state,
- nodesUptimeFilter: action.data,
- };
- }
- case SET_SEARCH_VALUE: {
- return {
- ...state,
- searchValue: action.data,
- };
- }
- case SET_SORT: {
- return {
- ...state,
- sortValue: action.data.sortValue,
- sortOrder: action.data.sortOrder,
- };
- }
- case SET_DATA_WAS_NOT_LOADED: {
- return {
- ...state,
- wasLoaded: false,
- };
- }
- default:
- return state;
- }
-};
-const concurrentId = 'getNodes';
+const slice = createSlice({
+ name: 'nodes',
+ initialState,
+ reducers: {
+ setUptimeFilter: (state, action: PayloadAction) => {
+ state.uptimeFilter = action.payload;
+ },
+ setSearchValue: (state, action: PayloadAction) => {
+ state.searchValue = action.payload;
+ },
+ setSort: (state, action: PayloadAction) => {
+ state.sortValue = action.payload.sortValue;
+ state.sortOrder = action.payload.sortOrder;
+ },
+ setInitialState: () => {
+ return initialState;
+ },
+ },
+});
-export function getNodes({type = 'any', storage = false, ...params}: NodesApiRequestParams) {
- return createApiRequest({
- request: window.api.getNodes({type, storage, ...params}, {concurrentId}),
- actions: FETCH_NODES,
- dataHandler: prepareNodesData,
- });
-}
-
-export function getComputeNodes({version = EVersion.v2, ...params}: ComputeApiRequestParams) {
- return createApiRequest({
- request: window.api.getCompute({version, ...params}, {concurrentId}),
- actions: FETCH_NODES,
- dataHandler: prepareComputeNodesData,
- });
-}
-
-export const resetNodesState = () => {
- return {
- type: RESET_NODES_STATE,
- } as const;
-};
+export default slice.reducer;
-export const setNodesUptimeFilter = (value: NodesUptimeFilterValues) =>
- ({
- type: SET_NODES_UPTIME_FILTER,
- data: value,
- }) as const;
-
-export const setDataWasNotLoaded = () => {
- return {
- type: SET_DATA_WAS_NOT_LOADED,
- } as const;
-};
-
-export const setSearchValue = (value: string) => {
- return {
- type: SET_SEARCH_VALUE,
- data: value,
- } as const;
-};
-
-export const setSort = (sortParams: NodesSortParams) => {
- return {
- type: SET_SORT,
- data: sortParams,
- } as const;
-};
+export const {setUptimeFilter, setSearchValue, setSort, setInitialState} = slice.actions;
-export default nodes;
+export const nodesApi = api.injectEndpoints({
+ endpoints: (builder) => ({
+ getNodes: builder.query({
+ queryFn: async (params: NodesApiRequestParams, {signal}) => {
+ try {
+ const data = await window.api.getNodes(
+ {
+ type: 'any',
+ storage: false,
+ ...params,
+ },
+ {signal},
+ );
+ return {data: prepareNodesData(data)};
+ } catch (error) {
+ return {error};
+ }
+ },
+ providesTags: ['All'],
+ }),
+ getComputeNodes: builder.query({
+ queryFn: async (params: ComputeApiRequestParams, {signal}) => {
+ try {
+ const data = await window.api.getCompute(
+ {
+ version: EVersion.v2,
+ ...params,
+ },
+ {signal},
+ );
+ return {data: prepareComputeNodesData(data)};
+ } catch (error) {
+ return {error};
+ }
+ },
+ providesTags: ['All'],
+ }),
+ }),
+});
diff --git a/src/store/reducers/nodes/selectors.ts b/src/store/reducers/nodes/selectors.ts
index 35408b86e7..2dabacdfa9 100644
--- a/src/store/reducers/nodes/selectors.ts
+++ b/src/store/reducers/nodes/selectors.ts
@@ -1,6 +1,3 @@
-import type {Selector} from '@reduxjs/toolkit';
-import {createSelector} from '@reduxjs/toolkit';
-
import {EFlag} from '../../../types/api/enums';
import {HOUR_IN_SECONDS} from '../../../utils/constants';
import {calcUptimeInSeconds} from '../../../utils/dataFormatters/dataFormatters';
@@ -9,7 +6,7 @@ import {NodesUptimeFilterValues} from '../../../utils/nodes';
import {ProblemFilterValues} from '../settings/settings';
import type {ProblemFilterValue} from '../settings/types';
-import type {NodesPreparedEntity, NodesStateSlice} from './types';
+import type {NodesPreparedEntity} from './types';
// ==== Filters ====
@@ -49,27 +46,21 @@ const filterNodesBySearchValue = (nodesList: NodesPreparedEntity[] = [], searchV
});
};
-// ==== Simple selectors ====
-
-const selectNodesUptimeFilter = (state: NodesStateSlice) => state.nodes.nodesUptimeFilter;
-const selectSearchValue = (state: NodesStateSlice) => state.nodes.searchValue;
-const selectNodesList = (state: NodesStateSlice) => state.nodes.data;
-
-// ==== Complex selectors ====
-
-export const selectFilteredNodes: Selector =
- createSelector(
- [
- selectNodesList,
- selectNodesUptimeFilter,
- selectSearchValue,
- (state) => state.settings.problemFilter,
- ],
- (nodesList, uptimeFilter, searchValue, problemFilter) => {
- let result = filterNodesByUptime(nodesList, uptimeFilter);
- result = filterNodesByProblemsStatus(result, problemFilter);
- result = filterNodesBySearchValue(result, searchValue);
+export function filterNodes(
+ nodesList: NodesPreparedEntity[] = [],
+ {
+ uptimeFilter,
+ searchValue,
+ problemFilter,
+ }: {
+ uptimeFilter: NodesUptimeFilterValues;
+ searchValue: string;
+ problemFilter: ProblemFilterValue;
+ },
+) {
+ let result = filterNodesByUptime(nodesList, uptimeFilter);
+ result = filterNodesByProblemsStatus(result, problemFilter);
+ result = filterNodesBySearchValue(result, searchValue);
- return result;
- },
- );
+ return result;
+}
diff --git a/src/store/reducers/nodes/types.ts b/src/store/reducers/nodes/types.ts
index dc76a939d3..b40ae0f571 100644
--- a/src/store/reducers/nodes/types.ts
+++ b/src/store/reducers/nodes/types.ts
@@ -5,22 +5,11 @@ import type {
TTabletStateInfo as TComputeTabletStateInfo,
} from '../../../types/api/compute';
import type {EFlag} from '../../../types/api/enums';
-import type {IResponseError} from '../../../types/api/error';
import type {TEndpoint, TPoolStats} from '../../../types/api/nodes';
import type {TTabletStateInfo as TFullTabletStateInfo} from '../../../types/api/tablet';
import type {NodesSortValue, NodesUptimeFilterValues} from '../../../utils/nodes';
-import type {ApiRequestAction} from '../../utils';
import type {VisibleEntities} from '../storage/types';
-import type {
- FETCH_NODES,
- resetNodesState,
- setDataWasNotLoaded,
- setNodesUptimeFilter,
- setSearchValue,
- setSort,
-} from './nodes';
-
// Since nodes from different endpoints can have different types,
// This type describes fields, that are expected by tables with nodes
export interface NodesPreparedEntity {
@@ -52,15 +41,10 @@ export interface NodesPreparedEntity {
}
export interface NodesState {
- loading: boolean;
- wasLoaded: boolean;
- nodesUptimeFilter: NodesUptimeFilterValues;
+ uptimeFilter: NodesUptimeFilterValues;
searchValue: string;
sortValue?: NodesSortValue;
sortOrder?: OrderType;
- data?: NodesPreparedEntity[];
- totalNodes?: number;
- error?: IResponseError;
}
export type NodeType = 'static' | 'dynamic' | 'any';
@@ -100,22 +84,6 @@ export interface NodesHandledResponse {
FoundNodes?: number;
}
-type NodesApiRequestAction = ApiRequestAction<
- typeof FETCH_NODES,
- NodesHandledResponse,
- IResponseError
->;
-
-export type NodesAction =
- | NodesApiRequestAction
- | (
- | ReturnType
- | ReturnType
- | ReturnType
- | ReturnType
- | ReturnType
- );
-
export interface NodesStateSlice {
nodes: NodesState;
}
diff --git a/src/store/reducers/nodesList.ts b/src/store/reducers/nodesList.ts
index 4e71928c3b..e721aa630b 100644
--- a/src/store/reducers/nodesList.ts
+++ b/src/store/reducers/nodesList.ts
@@ -1,3 +1,4 @@
+import {createSelector} from '@reduxjs/toolkit';
import type {Reducer} from '@reduxjs/toolkit';
import type {
@@ -48,7 +49,9 @@ export function getNodesList() {
});
}
-export const selectNodesMap = (state: NodesListRootStateSlice) =>
- prepareNodesMap(state.nodesList.data);
+export const selectNodesMap = createSelector(
+ (state: NodesListRootStateSlice) => state.nodesList.data,
+ (nodes) => prepareNodesMap(nodes),
+);
export default nodesList;
diff --git a/src/store/reducers/storage/selectors.ts b/src/store/reducers/storage/selectors.ts
index 12faeb5fb0..7e8bed06fa 100644
--- a/src/store/reducers/storage/selectors.ts
+++ b/src/store/reducers/storage/selectors.ts
@@ -1,10 +1,8 @@
import type {OrderType} from '@gravity-ui/react-data-table';
import {ASCENDING, DESCENDING} from '@gravity-ui/react-data-table/build/esm/lib/constants';
-import type {Selector} from '@reduxjs/toolkit';
-import {createSelector} from '@reduxjs/toolkit';
import {NODES_SORT_VALUES} from '../../../utils/nodes';
-import type {NodesSortValue} from '../../../utils/nodes';
+import type {NodesSortValue, NodesUptimeFilterValues} from '../../../utils/nodes';
import {STORAGE_SORT_VALUES, getUsage} from '../../../utils/storage';
import type {StorageSortValue} from '../../../utils/storage';
import {filterNodesByUptime} from '../nodes/selectors';
@@ -61,21 +59,35 @@ const filterGroupsByUsage = (entities: PreparedStorageGroup[], usage?: string[])
});
};
-// ==== Simple selectors ====
-
-export const selectEntitiesCount = (state: StorageStateSlice) => ({
- total: state.storage.total,
- found: state.storage.found,
-});
-
-export const selectStorageGroups = (state: StorageStateSlice) => state.storage.groups;
-export const selectStorageNodes = (state: StorageStateSlice) => state.storage.nodes;
+export function filterNodes(
+ storageNodes: PreparedStorageNode[],
+ textFilter: string,
+ uptimeFilter: NodesUptimeFilterValues,
+) {
+ let result = storageNodes || [];
+ result = filterNodesByText(result, textFilter);
+ result = filterNodesByUptime(result, uptimeFilter);
+
+ return result;
+}
+
+export function filterGroups(
+ storageGroups: PreparedStorageGroup[],
+ textFilter: string,
+ usageFilter: string[],
+) {
+ let result = storageGroups || [];
+ result = filterGroupsByText(result, textFilter);
+ result = filterGroupsByUsage(result, usageFilter);
+
+ return result;
+}
+// ==== Simple selectors ====
export const selectStorageFilter = (state: StorageStateSlice) => state.storage.filter;
export const selectUsageFilter = (state: StorageStateSlice) => state.storage.usageFilter;
export const selectVisibleEntities = (state: StorageStateSlice) => state.storage.visible;
-export const selectNodesUptimeFilter = (state: StorageStateSlice) =>
- state.storage.nodesUptimeFilter;
+export const selectNodesUptimeFilter = (state: StorageStateSlice) => state.storage.uptimeFilter;
export const selectStorageType = (state: StorageStateSlice) => state.storage.type;
// ==== Sort params selectors ====
@@ -112,50 +124,21 @@ export const selectGroupsSortParams = (state: StorageStateSlice) => {
};
// ==== Complex selectors ====
-export const selectUsageFilterOptions: Selector = createSelector(
- selectStorageGroups,
- (groups) => {
- const items: Record = {};
-
- groups?.forEach((group) => {
- // Get groups usage with step 5
- const usage = getUsage(group, 5);
-
- if (!Object.prototype.hasOwnProperty.call(items, usage)) {
- items[usage] = 0;
- }
-
- items[usage] += 1;
- });
-
- return Object.entries(items)
- .map(([threshold, count]) => ({threshold: Number(threshold), count}))
- .sort((a, b) => b.threshold - a.threshold);
- },
-);
-
-// ==== Complex selectors with filters ====
-
-export const selectFilteredNodes: Selector =
- createSelector(
- [selectStorageNodes, selectStorageFilter, selectNodesUptimeFilter],
- (storageNodes, textFilter, uptimeFilter) => {
- let result = storageNodes || [];
- result = filterNodesByText(result, textFilter);
- result = filterNodesByUptime(result, uptimeFilter);
-
- return result;
- },
- );
-
-export const selectFilteredGroups: Selector =
- createSelector(
- [selectStorageGroups, selectStorageFilter, selectUsageFilter],
- (storageGroups, textFilter, usageFilter) => {
- let result = storageGroups || [];
- result = filterGroupsByText(result, textFilter);
- result = filterGroupsByUsage(result, usageFilter);
-
- return result;
- },
- );
+export function getUsageFilterOptions(groups: PreparedStorageGroup[]): UsageFilter[] {
+ const items: Record = {};
+
+ groups?.forEach((group) => {
+ // Get groups usage with step 5
+ const usage = getUsage(group, 5);
+
+ if (!Object.prototype.hasOwnProperty.call(items, usage)) {
+ items[usage] = 0;
+ }
+
+ items[usage] += 1;
+ });
+
+ return Object.entries(items)
+ .map(([threshold, count]) => ({threshold: Number(threshold), count}))
+ .sort((a, b) => b.threshold - a.threshold);
+}
diff --git a/src/store/reducers/storage/storage.ts b/src/store/reducers/storage/storage.ts
index 2c9b077fad..c2eb6bbb33 100644
--- a/src/store/reducers/storage/storage.ts
+++ b/src/store/reducers/storage/storage.ts
@@ -1,13 +1,13 @@
-import type {Reducer} from '@reduxjs/toolkit';
+import {createSlice} from '@reduxjs/toolkit';
+import type {PayloadAction} from '@reduxjs/toolkit';
import {EVersion} from '../../../types/api/storage';
import {NodesUptimeFilterValues} from '../../../utils/nodes';
-import {createApiRequest, createRequestActionTypes} from '../../utils';
+import {api} from '../api';
import type {NodesApiRequestParams, NodesSortParams} from '../nodes/types';
import {STORAGE_TYPES, VISIBLE_ENTITIES} from './constants';
import type {
- StorageAction,
StorageApiRequestParams,
StorageSortParams,
StorageState,
@@ -16,221 +16,89 @@ import type {
} from './types';
import {prepareStorageGroupsResponse, prepareStorageNodesResponse} from './utils';
-export const FETCH_STORAGE = createRequestActionTypes('storage', 'FETCH_STORAGE');
-
-const SET_INITIAL = 'storage/SET_INITIAL';
-const SET_FILTER = 'storage/SET_FILTER';
-const SET_USAGE_FILTER = 'storage/SET_USAGE_FILTER';
-const SET_VISIBLE_GROUPS = 'storage/SET_VISIBLE_GROUPS';
-const SET_STORAGE_TYPE = 'storage/SET_STORAGE_TYPE';
-const SET_NODES_UPTIME_FILTER = 'storage/SET_NODES_UPTIME_FILTER';
-const SET_DATA_WAS_NOT_LOADED = 'storage/SET_DATA_WAS_NOT_LOADED';
-const SET_NODES_SORT_PARAMS = 'storage/SET_NODES_SORT_PARAMS';
-const SET_GROUPS_SORT_PARAMS = 'storage/SET_GROUPS_SORT_PARAMS';
-
-const initialState = {
- loading: true,
- wasLoaded: false,
+const initialState: StorageState = {
filter: '',
usageFilter: [],
visible: VISIBLE_ENTITIES.all,
- nodesUptimeFilter: NodesUptimeFilterValues.All,
+ uptimeFilter: NodesUptimeFilterValues.All,
type: STORAGE_TYPES.groups,
};
-const storage: Reducer = (state = initialState, action) => {
- switch (action.type) {
- case FETCH_STORAGE.REQUEST: {
- return {
- ...state,
- loading: true,
- };
- }
- case FETCH_STORAGE.SUCCESS: {
- return {
- ...state,
- nodes: action.data.nodes,
- groups: action.data.groups,
- total: action.data.total,
- found: action.data.found,
- loading: false,
- wasLoaded: true,
- error: undefined,
- };
- }
- case FETCH_STORAGE.FAILURE: {
- if (action.error?.isCancelled) {
- return state;
- }
-
- return {
- ...state,
- error: action.error,
- loading: false,
- wasLoaded: true,
- };
- }
- case SET_INITIAL: {
- return {
- ...initialState,
- };
- }
- case SET_FILTER: {
- return {
- ...state,
- filter: action.data,
- };
- }
- case SET_USAGE_FILTER: {
- return {
- ...state,
- usageFilter: action.data,
- };
- }
- case SET_VISIBLE_GROUPS: {
- return {
- ...state,
- visible: action.data,
- usageFilter: [],
- wasLoaded: false,
- error: undefined,
- };
- }
-
- case SET_NODES_UPTIME_FILTER: {
- return {
- ...state,
- nodesUptimeFilter: action.data,
- };
- }
- case SET_STORAGE_TYPE: {
- return {
- ...state,
- type: action.data,
- filter: '',
- usageFilter: [],
- wasLoaded: false,
- error: undefined,
- };
- }
- case SET_DATA_WAS_NOT_LOADED: {
- return {
- ...state,
- wasLoaded: false,
- };
- }
- case SET_NODES_SORT_PARAMS: {
- return {
- ...state,
- nodesSortValue: action.data.sortValue,
- nodesSortOrder: action.data.sortOrder,
- };
- }
- case SET_GROUPS_SORT_PARAMS: {
- return {
- ...state,
- groupsSortValue: action.data.sortValue,
- groupsSortOrder: action.data.sortOrder,
- };
- }
- default:
- return state;
- }
-};
-
-const concurrentId = 'getStorageInfo';
-
-export const getStorageNodesInfo = ({
- tenant,
- visibleEntities,
- ...params
-}: Omit) => {
- return createApiRequest({
- request: window.api.getNodes(
- {tenant, visibleEntities, storage: true, type: 'static', ...params},
- {concurrentId},
- ),
- actions: FETCH_STORAGE,
- dataHandler: prepareStorageNodesResponse,
- });
-};
-
-export const getStorageGroupsInfo = ({
- tenant,
- visibleEntities,
- nodeId,
- version = EVersion.v1,
- ...params
-}: StorageApiRequestParams) => {
- return createApiRequest({
- request: window.api.getStorageInfo(
- {tenant, visibleEntities, nodeId, version, ...params},
- {concurrentId},
- ),
- actions: FETCH_STORAGE,
- dataHandler: prepareStorageGroupsResponse,
- });
-};
-
-export function setInitialState() {
- return {
- type: SET_INITIAL,
- } as const;
-}
-
-export function setStorageType(value: StorageType) {
- return {
- type: SET_STORAGE_TYPE,
- data: value,
- } as const;
-}
-
-export function setStorageTextFilter(value: string) {
- return {
- type: SET_FILTER,
- data: value,
- } as const;
-}
-
-export function setUsageFilter(value: string[]) {
- return {
- type: SET_USAGE_FILTER,
- data: value,
- } as const;
-}
-
-export function setVisibleEntities(value: VisibleEntities) {
- return {
- type: SET_VISIBLE_GROUPS,
- data: value,
- } as const;
-}
-
-export function setNodesUptimeFilter(value: NodesUptimeFilterValues) {
- return {
- type: SET_NODES_UPTIME_FILTER,
- data: value,
- } as const;
-}
-
-export const setDataWasNotLoaded = () => {
- return {
- type: SET_DATA_WAS_NOT_LOADED,
- } as const;
-};
-
-export const setNodesSortParams = (sortParams: NodesSortParams) => {
- return {
- type: SET_NODES_SORT_PARAMS,
- data: sortParams,
- } as const;
-};
-
-export const setGroupsSortParams = (sortParams: StorageSortParams) => {
- return {
- type: SET_GROUPS_SORT_PARAMS,
- data: sortParams,
- } as const;
-};
-
-export default storage;
+const slice = createSlice({
+ name: 'storage',
+ initialState,
+ reducers: {
+ setUptimeFilter: (state, action: PayloadAction) => {
+ state.uptimeFilter = action.payload;
+ },
+ setStorageType: (state, action: PayloadAction) => {
+ state.type = action.payload;
+ },
+ setStorageTextFilter: (state, action: PayloadAction) => {
+ state.filter = action.payload;
+ },
+ setUsageFilter: (state, action: PayloadAction) => {
+ state.usageFilter = action.payload;
+ },
+ setVisibleEntities: (state, action: PayloadAction) => {
+ state.visible = action.payload;
+ },
+ setNodesSortParams: (state, action: PayloadAction) => {
+ state.nodesSortValue = action.payload.sortValue;
+ state.nodesSortOrder = action.payload.sortOrder;
+ },
+ setGroupsSortParams: (state, action: PayloadAction) => {
+ state.groupsSortValue = action.payload.sortValue;
+ state.groupsSortOrder = action.payload.sortOrder;
+ },
+ setInitialState: () => {
+ return initialState;
+ },
+ },
+});
+
+export default slice.reducer;
+
+export const {
+ setInitialState,
+ setStorageTextFilter,
+ setUsageFilter,
+ setVisibleEntities,
+ setStorageType,
+ setUptimeFilter,
+ setNodesSortParams,
+ setGroupsSortParams,
+} = slice.actions;
+
+export const storageApi = api.injectEndpoints({
+ endpoints: (builder) => ({
+ getStorageNodesInfo: builder.query({
+ queryFn: async (params: Omit, {signal}) => {
+ try {
+ const result = await window.api.getNodes(
+ {storage: true, type: 'static', ...params},
+ {signal},
+ );
+ return {data: prepareStorageNodesResponse(result)};
+ } catch (error) {
+ return {error};
+ }
+ },
+ providesTags: ['All'],
+ }),
+ getStorageGroupsInfo: builder.query({
+ queryFn: async (params: StorageApiRequestParams, {signal}) => {
+ try {
+ const result = await window.api.getStorageInfo(
+ {version: EVersion.v1, ...params},
+ {signal},
+ );
+ return {data: prepareStorageGroupsResponse(result)};
+ } catch (error) {
+ return {error};
+ }
+ },
+ providesTags: ['All'],
+ }),
+ }),
+});
diff --git a/src/store/reducers/storage/types.ts b/src/store/reducers/storage/types.ts
index 9b8b071095..f1a58132d1 100644
--- a/src/store/reducers/storage/types.ts
+++ b/src/store/reducers/storage/types.ts
@@ -1,27 +1,13 @@
import type {OrderType} from '@gravity-ui/react-data-table';
-import type {IResponseError} from '../../../types/api/error';
import type {TSystemStateInfo} from '../../../types/api/nodes';
import type {EVersion, TStorageGroupInfo} from '../../../types/api/storage';
import type {ValueOf} from '../../../types/common';
import type {PreparedPDisk, PreparedVDisk} from '../../../utils/disks/types';
import type {NodesSortValue, NodesUptimeFilterValues} from '../../../utils/nodes';
import type {StorageSortValue} from '../../../utils/storage';
-import type {ApiRequestAction} from '../../utils';
import type {STORAGE_TYPES, VISIBLE_ENTITIES} from './constants';
-import type {
- FETCH_STORAGE,
- setDataWasNotLoaded,
- setGroupsSortParams,
- setInitialState,
- setNodesSortParams,
- setNodesUptimeFilter,
- setStorageTextFilter,
- setStorageType,
- setUsageFilter,
- setVisibleEntities,
-} from './storage';
export type VisibleEntities = ValueOf;
export type StorageType = ValueOf;
@@ -82,22 +68,15 @@ export interface StorageApiRequestParams extends StorageSortAndFilterParams {
}
export interface StorageState {
- loading: boolean;
- wasLoaded: boolean;
filter: string;
usageFilter: string[];
visible: VisibleEntities;
- nodesUptimeFilter: NodesUptimeFilterValues;
+ uptimeFilter: NodesUptimeFilterValues;
groupsSortValue?: StorageSortValue;
groupsSortOrder?: OrderType;
nodesSortValue?: NodesSortValue;
nodesSortOrder?: OrderType;
type: StorageType;
- nodes?: PreparedStorageNode[];
- groups?: PreparedStorageGroup[];
- found?: number;
- total?: number;
- error?: IResponseError;
}
export interface PreparedStorageResponse {
@@ -107,24 +86,6 @@ export interface PreparedStorageResponse {
total: number | undefined;
}
-type GetStorageInfoApiRequestAction = ApiRequestAction<
- typeof FETCH_STORAGE,
- PreparedStorageResponse,
- IResponseError
->;
-
-export type StorageAction =
- | GetStorageInfoApiRequestAction
- | ReturnType
- | ReturnType
- | ReturnType
- | ReturnType
- | ReturnType
- | ReturnType
- | ReturnType
- | ReturnType
- | ReturnType;
-
export interface StorageStateSlice {
storage: StorageState;
}
diff --git a/src/store/state-url-mapping.ts b/src/store/state-url-mapping.ts
index 6a4cd18e6f..7ff5564002 100644
--- a/src/store/state-url-mapping.ts
+++ b/src/store/state-url-mapping.ts
@@ -89,16 +89,24 @@ const paramSetup: ParamSetup = {
storageType: {
stateKey: 'storage.type',
},
- storageVisibleType: {
+ visible: {
stateKey: 'storage.visible',
},
- storageNodesUptime: {
- stateKey: 'storage.nodesUptimeFilter',
+ uptimeFilter: {
+ stateKey: 'storage.uptimeFilter',
},
- storageFilter: {
+ search: {
stateKey: 'storage.filter',
},
},
+ '/cluster/nodes': {
+ uptimeFilter: {
+ stateKey: 'nodes.uptimeFilter',
+ },
+ search: {
+ stateKey: 'nodes.searchValue',
+ },
+ },
};
function mergeLocationToState(state: S, location: Pick): S {