Skip to content

Commit e90e2d5

Browse files
authored
Merge branch 'main' into astandrik.2470
2 parents dae2d6d + 937c561 commit e90e2d5

File tree

30 files changed

+1035
-214
lines changed

30 files changed

+1035
-214
lines changed

config-overrides.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,9 @@ module.exports = {
5858
// By default jest does not transform anything in node_modules
5959
// So this override excludes node_modules except @gravity-ui
6060
// see https://github.com/timarney/react-app-rewired/issues/241
61-
config.transformIgnorePatterns = ['node_modules/(?!(@gravity-ui|@mjackson)/)'];
61+
config.transformIgnorePatterns = [
62+
'node_modules/(?!(@gravity-ui|@mjackson|@standard-schema)/)',
63+
];
6264

6365
// Add .github directory to roots
6466
config.roots = ['<rootDir>/src', '<rootDir>/.github'];

package-lock.json

Lines changed: 19 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
"@gravity-ui/websql-autocomplete": "^13.7.0",
3030
"@hookform/resolvers": "^3.10.0",
3131
"@mjackson/multipart-parser": "^0.8.2",
32-
"@reduxjs/toolkit": "^2.5.0",
32+
"@reduxjs/toolkit": "^2.8.2",
3333
"@tanstack/react-table": "^8.20.6",
3434
"@ydb-platform/monaco-ghost": "^0.6.1",
3535
"axios": "^1.8.4",
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+
}
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

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/components/TableSkeleton/TableSkeleton.scss

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,19 @@
3636

3737
&__col-5 {
3838
width: 20%;
39+
margin-right: 5%;
40+
}
41+
42+
&__col-6,
43+
&__col-7,
44+
&__col-8,
45+
&__col-9 {
46+
width: 8%;
47+
margin-right: 3%;
48+
}
49+
50+
&__col-10 {
51+
width: 8%;
3952
}
4053

4154
&__col-full {

src/components/TableSkeleton/TableSkeleton.tsx

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,36 @@ interface TableSkeletonProps {
1111
className?: string;
1212
rows?: number;
1313
delay?: number;
14+
columns?: number;
15+
showHeader?: boolean;
1416
}
1517

16-
export const TableSkeleton = ({rows = 2, delay = 600, className}: TableSkeletonProps) => {
18+
export const TableSkeleton = ({
19+
rows = 2,
20+
delay = 600,
21+
className,
22+
columns = 5,
23+
showHeader = true,
24+
}: TableSkeletonProps) => {
1725
const [show] = useDelayed(delay);
1826

19-
return (
20-
<div className={b('wrapper', {hidden: !show}, className)}>
27+
const renderHeaderRow = () => {
28+
if (!showHeader) {
29+
return null;
30+
}
31+
32+
return (
2133
<div className={b('row')}>
22-
<Skeleton className={b('col-1')} />
23-
<Skeleton className={b('col-2')} />
24-
<Skeleton className={b('col-3')} />
25-
<Skeleton className={b('col-4')} />
26-
<Skeleton className={b('col-5')} />
34+
{[...new Array(columns)].map((_, index) => (
35+
<Skeleton key={`header-col-${index}`} className={b(`col-${index + 1}`)} />
36+
))}
2737
</div>
38+
);
39+
};
40+
41+
return (
42+
<div className={b('wrapper', {hidden: !show}, className)}>
43+
{renderHeaderRow()}
2844
{[...new Array(rows)].map((_, index) => (
2945
<div className={b('row')} key={`skeleton-row-${index}`}>
3046
<Skeleton className={b('col-full')} />

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;

0 commit comments

Comments
 (0)