Skip to content

Commit 7af71c4

Browse files
feat(Cluster): add Network tab (#2424)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 0acabd6 commit 7af71c4

File tree

10 files changed

+159
-79
lines changed

10 files changed

+159
-79
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import {Nodes} from '../../containers/Nodes/Nodes';
2+
import type {NodesProps} from '../../containers/Nodes/Nodes';
3+
4+
import {getNetworkTableNodesColumns} from './columns';
5+
import {
6+
NETWORK_DEFAULT_NODES_COLUMNS,
7+
NETWORK_NODES_GROUP_BY_PARAMS,
8+
NETWORK_NODES_TABLE_SELECTED_COLUMNS_KEY,
9+
NETWORK_REQUIRED_NODES_COLUMNS,
10+
} from './constants';
11+
12+
type NetworkWrapperProps = Pick<
13+
NodesProps,
14+
'path' | 'scrollContainerRef' | 'additionalNodesProps' | 'database'
15+
>;
16+
17+
export function NetworkTable({
18+
database,
19+
path,
20+
scrollContainerRef,
21+
additionalNodesProps,
22+
}: NetworkWrapperProps) {
23+
return (
24+
<Nodes
25+
path={path}
26+
database={database}
27+
scrollContainerRef={scrollContainerRef}
28+
withPeerRoleFilter={Boolean(database)}
29+
additionalNodesProps={additionalNodesProps}
30+
columns={getNetworkTableNodesColumns({
31+
database: database,
32+
getNodeRef: additionalNodesProps?.getNodeRef,
33+
})}
34+
defaultColumnsIds={NETWORK_DEFAULT_NODES_COLUMNS}
35+
requiredColumnsIds={NETWORK_REQUIRED_NODES_COLUMNS}
36+
selectedColumnsKey={NETWORK_NODES_TABLE_SELECTED_COLUMNS_KEY}
37+
groupByParams={NETWORK_NODES_GROUP_BY_PARAMS}
38+
/>
39+
);
40+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import type {NodesPreparedEntity} from '../../store/reducers/nodes/types';
2+
import type {Column} from '../../utils/tableUtils/types';
3+
import {
4+
getClockSkewColumn,
5+
getConnectionsColumn,
6+
getCpuColumn,
7+
getDataCenterColumn,
8+
getHostColumn,
9+
getNetworkUtilizationColumn,
10+
getNodeIdColumn,
11+
getPingTimeColumn,
12+
getPoolsColumn,
13+
getRackColumn,
14+
getReceiveThroughputColumn,
15+
getSendThroughputColumn,
16+
getUptimeColumn,
17+
} from '../nodesColumns/columns';
18+
import {isSortableNodesColumn} from '../nodesColumns/constants';
19+
import type {GetNodesColumnsParams} from '../nodesColumns/types';
20+
21+
export function getNetworkTableNodesColumns(params: GetNodesColumnsParams) {
22+
const columns: Column<NodesPreparedEntity>[] = [
23+
getNodeIdColumn(),
24+
getHostColumn(params, {statusForIcon: 'ConnectStatus'}),
25+
getDataCenterColumn(),
26+
getRackColumn(),
27+
getUptimeColumn(),
28+
getCpuColumn(),
29+
getPoolsColumn(),
30+
getConnectionsColumn(),
31+
getNetworkUtilizationColumn(),
32+
getSendThroughputColumn(),
33+
getReceiveThroughputColumn(),
34+
getPingTimeColumn(),
35+
getClockSkewColumn(),
36+
];
37+
38+
return columns.map((column) => {
39+
return {...column, sortable: isSortableNodesColumn(column.name)};
40+
});
41+
}

src/containers/Tenant/Diagnostics/Network/NetworkTable/constants.ts renamed to src/components/NetworkTable/constants.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import type {NodesColumnId} from '../../../../../components/nodesColumns/constants';
2-
import type {NodesGroupByField} from '../../../../../types/api/nodes';
1+
import type {NodesGroupByField} from '../../types/api/nodes';
2+
import type {NodesColumnId} from '../nodesColumns/constants';
33

44
export const NETWORK_NODES_TABLE_SELECTED_COLUMNS_KEY = 'networkNodesTableSelectedColumns';
55

src/components/NetworkTable/hooks.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import {
2+
useNodesHandlerHasWorkingClusterNetworkStats,
3+
useViewerNodesHandlerHasNetworkStats,
4+
} from '../../store/reducers/capabilities/hooks';
5+
import {ENABLE_NETWORK_TABLE_KEY} from '../../utils/constants';
6+
import {useSetting} from '../../utils/hooks';
7+
8+
export function useShouldShowDatabaseNetworkTable() {
9+
const viewerNodesHasNetworkStats = useViewerNodesHandlerHasNetworkStats();
10+
const [networkTableEnabled] = useSetting(ENABLE_NETWORK_TABLE_KEY);
11+
12+
return Boolean(viewerNodesHasNetworkStats && networkTableEnabled);
13+
}
14+
15+
export function useShouldShowClusterNetworkTable() {
16+
const nodesHasWorkingClusterNetworkStats = useNodesHandlerHasWorkingClusterNetworkStats();
17+
const [networkTableEnabled] = useSetting(ENABLE_NETWORK_TABLE_KEY);
18+
19+
return Boolean(nodesHasWorkingClusterNetworkStats && networkTableEnabled);
20+
}

src/containers/Cluster/Cluster.tsx

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,12 @@ import {AutoRefreshControl} from '../../components/AutoRefreshControl/AutoRefres
99
import {EntityStatus} from '../../components/EntityStatusNew/EntityStatus';
1010
import {EFlagToDescription} from '../../components/EntityStatusNew/utils';
1111
import {InternalLink} from '../../components/InternalLink';
12+
import {NetworkTable} from '../../components/NetworkTable/NetworkTable';
13+
import {useShouldShowClusterNetworkTable} from '../../components/NetworkTable/hooks';
1214
import routes, {getLocationObjectFromHref} from '../../routes';
1315
import {useClusterDashboardAvailable} from '../../store/reducers/capabilities/hooks';
1416
import {
17+
INITIAL_DEFAULT_CLUSTER_TAB,
1518
clusterApi,
1619
selectClusterTabletsWithFqdn,
1720
selectClusterTitle,
@@ -55,6 +58,8 @@ export function Cluster({
5558
const container = React.useRef<HTMLDivElement>(null);
5659
const isClusterDashboardAvailable = useClusterDashboardAvailable();
5760

61+
const shouldShowNetworkTable = useShouldShowClusterNetworkTable();
62+
5863
const [autoRefreshInterval] = useAutoRefreshInterval();
5964

6065
const dispatch = useTypedDispatch();
@@ -92,6 +97,14 @@ export function Cluster({
9297
dispatch(setHeaderBreadcrumbs('cluster', {}));
9398
}, [dispatch]);
9499

100+
const actualClusterTabs = React.useMemo(() => {
101+
if (shouldShowNetworkTable) {
102+
return clusterTabs;
103+
} else {
104+
return clusterTabs.filter((tab) => tab.id !== clusterTabsIds.network);
105+
}
106+
}, [shouldShowNetworkTable]);
107+
95108
const getClusterTitle = () => {
96109
if (infoLoading) {
97110
return <Skeleton className={b('title-skeleton')} />;
@@ -110,8 +123,8 @@ export function Cluster({
110123
};
111124

112125
const activeTab = React.useMemo(
113-
() => clusterTabs.find(({id}) => id === activeTabId),
114-
[activeTabId],
126+
() => actualClusterTabs.find(({id}) => id === activeTabId),
127+
[activeTabId, actualClusterTabs],
115128
);
116129

117130
return (
@@ -142,7 +155,7 @@ export function Cluster({
142155
size="l"
143156
allowNotSelected={true}
144157
activeTab={activeTabId}
145-
items={clusterTabs}
158+
items={actualClusterTabs}
146159
wrapTo={({id}, node) => {
147160
const path = getClusterPath(id as ClusterTab, {clusterName, backend});
148161
return (
@@ -202,6 +215,19 @@ export function Cluster({
202215
>
203216
<PaginatedStorage scrollContainerRef={container} />
204217
</Route>
218+
{shouldShowNetworkTable && (
219+
<Route
220+
path={
221+
getLocationObjectFromHref(getClusterPath(clusterTabsIds.network))
222+
.pathname
223+
}
224+
>
225+
<NetworkTable
226+
scrollContainerRef={container}
227+
additionalNodesProps={additionalNodesProps}
228+
/>
229+
</Route>
230+
)}
205231
<Route
206232
path={
207233
getLocationObjectFromHref(getClusterPath(clusterTabsIds.versions))
@@ -226,11 +252,16 @@ function useClusterTab() {
226252

227253
const defaultTab = useTypedSelector((state) => state.cluster.defaultClusterTab);
228254

255+
const shouldShowNetworkTable = useShouldShowClusterNetworkTable();
256+
229257
const match = useRouteMatch<{activeTab: string}>(routes.cluster);
230258

231259
const {activeTab: activeTabFromParams} = match?.params || {};
232260
let activeTab: ClusterTab;
233-
if (isClusterTab(activeTabFromParams)) {
261+
262+
if (!shouldShowNetworkTable && activeTabFromParams === clusterTabsIds.network) {
263+
activeTab = INITIAL_DEFAULT_CLUSTER_TAB;
264+
} else if (isClusterTab(activeTabFromParams)) {
234265
activeTab = activeTabFromParams;
235266
} else {
236267
activeTab = defaultTab;

src/containers/Cluster/utils.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export const clusterTabsIds = {
77
tenants: 'tenants',
88
nodes: 'nodes',
99
storage: 'storage',
10+
network: 'network',
1011
versions: 'versions',
1112
tablets: 'tablets',
1213
} as const;
@@ -25,6 +26,10 @@ const storage = {
2526
id: clusterTabsIds.storage,
2627
title: 'Storage',
2728
};
29+
const network = {
30+
id: clusterTabsIds.network,
31+
title: 'Network',
32+
};
2833
const versions = {
2934
id: clusterTabsIds.versions,
3035
title: 'Versions',
@@ -34,7 +39,7 @@ const tablets = {
3439
title: 'Tablets',
3540
};
3641

37-
export const clusterTabs = [tenants, nodes, storage, tablets, versions];
42+
export const clusterTabs = [tenants, nodes, storage, network, tablets, versions];
3843

3944
export function isClusterTab(tab: any): tab is ClusterTab {
4045
return Object.values(clusterTabsIds).includes(tab);

src/containers/Tenant/Diagnostics/Network/NetworkTable/columns.tsx

Lines changed: 0 additions & 43 deletions
This file was deleted.

src/containers/Tenant/Diagnostics/Network/NetworkWrapper.tsx

Lines changed: 5 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,10 @@
11
import {LoaderWrapper} from '../../../../components/LoaderWrapper/LoaderWrapper';
2-
import {
3-
useCapabilitiesLoaded,
4-
useViewerNodesHandlerHasNetworkStats,
5-
} from '../../../../store/reducers/capabilities/hooks';
6-
import {ENABLE_NETWORK_TABLE_KEY} from '../../../../utils/constants';
7-
import {useSetting} from '../../../../utils/hooks';
2+
import {NetworkTable} from '../../../../components/NetworkTable/NetworkTable';
3+
import {useShouldShowDatabaseNetworkTable} from '../../../../components/NetworkTable/hooks';
4+
import {useCapabilitiesLoaded} from '../../../../store/reducers/capabilities/hooks';
85
import type {NodesProps} from '../../../Nodes/Nodes';
9-
import {Nodes} from '../../../Nodes/Nodes';
106

117
import {Network} from './Network';
12-
import {getNetworkTableNodesColumns} from './NetworkTable/columns';
13-
import {
14-
NETWORK_DEFAULT_NODES_COLUMNS,
15-
NETWORK_NODES_GROUP_BY_PARAMS,
16-
NETWORK_NODES_TABLE_SELECTED_COLUMNS_KEY,
17-
NETWORK_REQUIRED_NODES_COLUMNS,
18-
} from './NetworkTable/constants';
198

209
interface NetworkWrapperProps
2110
extends Pick<NodesProps, 'path' | 'scrollContainerRef' | 'additionalNodesProps'> {
@@ -29,28 +18,16 @@ export function NetworkWrapper({
2918
additionalNodesProps,
3019
}: NetworkWrapperProps) {
3120
const capabilitiesLoaded = useCapabilitiesLoaded();
32-
const viewerNodesHasNetworkStats = useViewerNodesHandlerHasNetworkStats();
33-
const [networkTableEnabled] = useSetting(ENABLE_NETWORK_TABLE_KEY);
34-
35-
const shouldUseNetworkNodesTable = viewerNodesHasNetworkStats && networkTableEnabled;
21+
const shouldUseNetworkNodesTable = useShouldShowDatabaseNetworkTable();
3622

3723
const renderContent = () => {
3824
if (shouldUseNetworkNodesTable) {
3925
return (
40-
<Nodes
26+
<NetworkTable
4127
path={path}
4228
database={database}
4329
scrollContainerRef={scrollContainerRef}
44-
withPeerRoleFilter
4530
additionalNodesProps={additionalNodesProps}
46-
columns={getNetworkTableNodesColumns({
47-
database: database,
48-
getNodeRef: additionalNodesProps?.getNodeRef,
49-
})}
50-
defaultColumnsIds={NETWORK_DEFAULT_NODES_COLUMNS}
51-
requiredColumnsIds={NETWORK_REQUIRED_NODES_COLUMNS}
52-
selectedColumnsKey={NETWORK_NODES_TABLE_SELECTED_COLUMNS_KEY}
53-
groupByParams={NETWORK_NODES_GROUP_BY_PARAMS}
5431
/>
5532
);
5633
}

src/store/reducers/capabilities/hooks.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,13 @@ export const useViewerNodesHandlerHasNetworkStats = () => {
6767
return useGetFeatureVersion('/viewer/nodes') > 13;
6868
};
6969

70+
// Before this version handler has very big response size if nodes quantity is more than 100
71+
// Response size could be up to 20-30MB, it loads very long and freezes UI
72+
// It is not very common for databases, but an often case for clusters
73+
export const useNodesHandlerHasWorkingClusterNetworkStats = () => {
74+
return useGetFeatureVersion('/viewer/nodes') >= 16;
75+
};
76+
7077
export const useFeatureFlagsAvailable = () => {
7178
return useGetFeatureVersion('/viewer/feature_flags') > 1;
7279
};

src/store/reducers/cluster/cluster.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,15 @@ import {
2323
parseGroupsStatsQueryResponse,
2424
} from './utils';
2525

26+
export const INITIAL_DEFAULT_CLUSTER_TAB = clusterTabsIds.tenants;
27+
2628
const defaultClusterTabLS = localStorage.getItem(DEFAULT_CLUSTER_TAB_KEY);
2729

2830
let defaultClusterTab: ClusterTab;
2931
if (isClusterTab(defaultClusterTabLS)) {
3032
defaultClusterTab = defaultClusterTabLS;
3133
} else {
32-
defaultClusterTab = clusterTabsIds.tenants;
34+
defaultClusterTab = INITIAL_DEFAULT_CLUSTER_TAB;
3335
}
3436

3537
const initialState: ClusterState = {

0 commit comments

Comments
 (0)