Skip to content

Commit b88c30e

Browse files
authored
fix: add network section (#2635)
1 parent 30c6427 commit b88c30e

File tree

14 files changed

+385
-18
lines changed

14 files changed

+385
-18
lines changed

src/containers/Tenant/Diagnostics/TenantOverview/MetricsTabs/MetricsTabs.tsx

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {useSetting, useTypedSelector} from '../../../../../utils/hooks';
1515
import {calculateMetricAggregates} from '../../../../../utils/metrics';
1616
import {
1717
formatCoresLegend,
18+
formatSpeedLegend,
1819
formatStorageLegend,
1920
} from '../../../../../utils/metrics/formatMetricLegend';
2021
import {TenantTabsGroups, getTenantPath} from '../../../TenantPages';
@@ -57,6 +58,10 @@ export function MetricsTabs({
5758
...queryParams,
5859
[TenantTabsGroups.metricsTab]: TENANT_METRICS_TABS_IDS.memory,
5960
}),
61+
[TENANT_METRICS_TABS_IDS.network]: getTenantPath({
62+
...queryParams,
63+
[TenantTabsGroups.metricsTab]: TENANT_METRICS_TABS_IDS.network,
64+
}),
6065
};
6166

6267
// Use only pools that directly indicate resources available to perform user queries
@@ -131,19 +136,22 @@ export function MetricsTabs({
131136
</Link>
132137
</div>
133138
{showNetworkUtilization && networkStats && networkMetrics && (
134-
<div className={b('link-container')}>
135-
<div className={b('link')}>
139+
<div
140+
className={b('link-container', {
141+
active: metricsTab === TENANT_METRICS_TABS_IDS.network,
142+
})}
143+
>
144+
<Link to={tabLinks.network} className={b('link')}>
136145
<TabCard
137146
label={i18n('cards.network-label')}
138-
sublabel={i18n('context_network-evaluation')}
147+
sublabel={i18n('context_network-usage')}
139148
value={networkMetrics.totalUsed}
140149
limit={networkMetrics.totalLimit}
141-
legendFormatter={formatStorageLegend}
142-
active={false}
143-
clickable={false}
150+
legendFormatter={formatSpeedLegend}
151+
active={metricsTab === TENANT_METRICS_TABS_IDS.network}
144152
helpText={i18n('context_network-description')}
145153
/>
146-
</div>
154+
</Link>
147155
</div>
148156
)}
149157
</Flex>

src/containers/Tenant/Diagnostics/TenantOverview/TabCard/TabCard.scss

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,10 @@
44
&__card-container {
55
padding: var(--g-spacing-4);
66

7+
cursor: pointer;
8+
79
border: 0;
810
border-radius: var(--g-border-radius-s);
9-
}
10-
11-
&__card-container_clickable {
12-
cursor: pointer;
1311

1412
// Smooth hover transition
1513
transition: background-color 0.15s ease;

src/containers/Tenant/Diagnostics/TenantOverview/TabCard/TabCard.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ interface TabCardProps {
1515
limit: number;
1616
active?: boolean;
1717
helpText?: string;
18-
clickable?: boolean;
1918
legendFormatter: (params: {value: number; capacity: number}) => string;
2019
}
2120

@@ -26,7 +25,6 @@ export function TabCard({
2625
limit,
2726
active,
2827
helpText,
29-
clickable = true,
3028
legendFormatter,
3129
}: TabCardProps) {
3230
const {status, percents, legend, fill} = getDiagramValues({
@@ -38,7 +36,7 @@ export function TabCard({
3836
return (
3937
<div className={b({active})}>
4038
<Card
41-
className={b('card-container', {active, clickable})}
39+
className={b('card-container', {active})}
4240
type="container"
4341
view={active ? 'outlined' : 'raised'}
4442
>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
.tenant-network {
2+
&__tabs-container {
3+
margin-top: var(--g-spacing-3);
4+
}
5+
6+
&__tab-content {
7+
margin-top: var(--g-spacing-3);
8+
}
9+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import {Flex, Tab, TabList, TabProvider} from '@gravity-ui/uikit';
2+
3+
import {TENANT_NETWORK_TABS_IDS} from '../../../../../store/reducers/tenant/constants';
4+
import type {AdditionalNodesProps} from '../../../../../types/additionalProps';
5+
import {cn} from '../../../../../utils/cn';
6+
import i18n from '../i18n';
7+
8+
import {TopNodesByPing} from './TopNodesByPing';
9+
import {TopNodesBySkew} from './TopNodesBySkew';
10+
import {useTenantNetworkQueryParams} from './useTenantNetworkQueryParams';
11+
12+
import './TenantNetwork.scss';
13+
14+
const b = cn('tenant-network');
15+
16+
const networkTabs = [
17+
{id: TENANT_NETWORK_TABS_IDS.ping, title: i18n('title_nodes-by-ping')},
18+
{id: TENANT_NETWORK_TABS_IDS.skew, title: i18n('title_nodes-by-skew')},
19+
];
20+
21+
interface TenantNetworkProps {
22+
tenantName: string;
23+
additionalNodesProps?: AdditionalNodesProps;
24+
}
25+
26+
export function TenantNetwork({tenantName, additionalNodesProps}: TenantNetworkProps) {
27+
const {networkTab, handleNetworkTabChange} = useTenantNetworkQueryParams();
28+
29+
const renderTabContent = () => {
30+
switch (networkTab) {
31+
case TENANT_NETWORK_TABS_IDS.ping: {
32+
return (
33+
<TopNodesByPing
34+
tenantName={tenantName}
35+
additionalNodesProps={additionalNodesProps}
36+
/>
37+
);
38+
}
39+
case TENANT_NETWORK_TABS_IDS.skew: {
40+
return (
41+
<TopNodesBySkew
42+
tenantName={tenantName}
43+
additionalNodesProps={additionalNodesProps}
44+
/>
45+
);
46+
}
47+
default: {
48+
return null;
49+
}
50+
}
51+
};
52+
53+
return (
54+
<Flex direction="column" gap={4} className={b()}>
55+
<Flex direction="column" gap={3} className={b('tabs-container')}>
56+
<TabProvider value={networkTab}>
57+
<TabList size="m">
58+
{networkTabs.map(({id, title}) => {
59+
return (
60+
<Tab key={id} value={id} onClick={() => handleNetworkTabChange(id)}>
61+
{title}
62+
</Tab>
63+
);
64+
})}
65+
</TabList>
66+
</TabProvider>
67+
68+
<Flex direction="column" className={b('tab-content')}>
69+
{renderTabContent()}
70+
</Flex>
71+
</Flex>
72+
</Flex>
73+
);
74+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import {ResizeableDataTable} from '../../../../../components/ResizeableDataTable/ResizeableDataTable';
2+
import {NODES_COLUMNS_WIDTH_LS_KEY} from '../../../../../components/nodesColumns/constants';
3+
import {nodesApi} from '../../../../../store/reducers/nodes/nodes';
4+
import {TENANT_DIAGNOSTICS_TABS_IDS} from '../../../../../store/reducers/tenant/constants';
5+
import type {AdditionalNodesProps} from '../../../../../types/additionalProps';
6+
import {TENANT_OVERVIEW_TABLES_LIMIT} from '../../../../../utils/constants';
7+
import {useAutoRefreshInterval, useSearchQuery} from '../../../../../utils/hooks';
8+
import {TenantTabsGroups, getTenantPath} from '../../../TenantPages';
9+
import {TenantOverviewTableLayout} from '../TenantOverviewTableLayout';
10+
import {getSectionTitle} from '../getSectionTitle';
11+
import i18n from '../i18n';
12+
13+
import {getTopNodesByPingColumns} from './columns';
14+
15+
const TENANT_OVERVIEW_TABLES_SETTINGS = {
16+
stripedRows: false,
17+
sortable: false,
18+
};
19+
20+
interface TopNodesByPingProps {
21+
tenantName: string;
22+
additionalNodesProps?: AdditionalNodesProps;
23+
}
24+
25+
export function TopNodesByPing({tenantName, additionalNodesProps}: TopNodesByPingProps) {
26+
const query = useSearchQuery();
27+
const [autoRefreshInterval] = useAutoRefreshInterval();
28+
const [columns, fieldsRequired] = getTopNodesByPingColumns({
29+
getNodeRef: additionalNodesProps?.getNodeRef,
30+
database: tenantName,
31+
});
32+
33+
const {currentData, isFetching, error} = nodesApi.useGetNodesQuery(
34+
{
35+
tenant: tenantName,
36+
type: 'any',
37+
sort: '-PingTime',
38+
limit: TENANT_OVERVIEW_TABLES_LIMIT,
39+
tablets: false,
40+
fieldsRequired: fieldsRequired as any,
41+
},
42+
{pollingInterval: autoRefreshInterval},
43+
);
44+
45+
const loading = isFetching && currentData === undefined;
46+
const topNodes = currentData?.Nodes || [];
47+
48+
const title = getSectionTitle({
49+
entity: i18n('nodes'),
50+
postfix: i18n('by-ping'),
51+
link: getTenantPath({
52+
...query,
53+
[TenantTabsGroups.diagnosticsTab]: TENANT_DIAGNOSTICS_TABS_IDS.nodes,
54+
}),
55+
});
56+
57+
return (
58+
<TenantOverviewTableLayout
59+
title={title}
60+
loading={loading}
61+
error={error}
62+
withData={Boolean(currentData)}
63+
>
64+
<ResizeableDataTable
65+
columnsWidthLSKey={NODES_COLUMNS_WIDTH_LS_KEY}
66+
data={topNodes}
67+
columns={columns}
68+
emptyDataMessage={i18n('top-nodes.empty-data')}
69+
settings={TENANT_OVERVIEW_TABLES_SETTINGS}
70+
/>
71+
</TenantOverviewTableLayout>
72+
);
73+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import {ResizeableDataTable} from '../../../../../components/ResizeableDataTable/ResizeableDataTable';
2+
import {NODES_COLUMNS_WIDTH_LS_KEY} from '../../../../../components/nodesColumns/constants';
3+
import {nodesApi} from '../../../../../store/reducers/nodes/nodes';
4+
import {TENANT_DIAGNOSTICS_TABS_IDS} from '../../../../../store/reducers/tenant/constants';
5+
import type {AdditionalNodesProps} from '../../../../../types/additionalProps';
6+
import {TENANT_OVERVIEW_TABLES_LIMIT} from '../../../../../utils/constants';
7+
import {useAutoRefreshInterval, useSearchQuery} from '../../../../../utils/hooks';
8+
import {TenantTabsGroups, getTenantPath} from '../../../TenantPages';
9+
import {TenantOverviewTableLayout} from '../TenantOverviewTableLayout';
10+
import {getSectionTitle} from '../getSectionTitle';
11+
import i18n from '../i18n';
12+
13+
import {getTopNodesBySkewColumns} from './columns';
14+
15+
const TENANT_OVERVIEW_TABLES_SETTINGS = {
16+
stripedRows: false,
17+
sortable: false,
18+
};
19+
20+
interface TopNodesBySkewProps {
21+
tenantName: string;
22+
additionalNodesProps?: AdditionalNodesProps;
23+
}
24+
25+
export function TopNodesBySkew({tenantName, additionalNodesProps}: TopNodesBySkewProps) {
26+
const query = useSearchQuery();
27+
const [autoRefreshInterval] = useAutoRefreshInterval();
28+
const [columns, fieldsRequired] = getTopNodesBySkewColumns({
29+
getNodeRef: additionalNodesProps?.getNodeRef,
30+
database: tenantName,
31+
});
32+
33+
const {currentData, isFetching, error} = nodesApi.useGetNodesQuery(
34+
{
35+
tenant: tenantName,
36+
type: 'any',
37+
sort: '-ClockSkew',
38+
limit: TENANT_OVERVIEW_TABLES_LIMIT,
39+
tablets: false,
40+
fieldsRequired: fieldsRequired as any,
41+
},
42+
{pollingInterval: autoRefreshInterval},
43+
);
44+
45+
const loading = isFetching && currentData === undefined;
46+
const topNodes = currentData?.Nodes || [];
47+
48+
const title = getSectionTitle({
49+
entity: i18n('nodes'),
50+
postfix: i18n('by-skew'),
51+
link: getTenantPath({
52+
...query,
53+
[TenantTabsGroups.diagnosticsTab]: TENANT_DIAGNOSTICS_TABS_IDS.nodes,
54+
}),
55+
});
56+
57+
return (
58+
<TenantOverviewTableLayout
59+
title={title}
60+
loading={loading}
61+
error={error}
62+
withData={Boolean(currentData)}
63+
>
64+
<ResizeableDataTable
65+
columnsWidthLSKey={NODES_COLUMNS_WIDTH_LS_KEY}
66+
data={topNodes}
67+
columns={columns}
68+
emptyDataMessage={i18n('top-nodes.empty-data')}
69+
settings={TENANT_OVERVIEW_TABLES_SETTINGS}
70+
/>
71+
</TenantOverviewTableLayout>
72+
);
73+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import {
2+
getClockSkewColumn,
3+
getNetworkHostColumn,
4+
getNodeIdColumn,
5+
getPingTimeColumn,
6+
getUptimeColumn,
7+
} from '../../../../../components/nodesColumns/columns';
8+
import {
9+
NODES_COLUMNS_TO_DATA_FIELDS,
10+
isSortableNodesColumn,
11+
} from '../../../../../components/nodesColumns/constants';
12+
import type {GetNodesColumnsParams} from '../../../../../components/nodesColumns/types';
13+
import type {NodesPreparedEntity} from '../../../../../store/reducers/nodes/types';
14+
import {getRequiredDataFields} from '../../../../../utils/tableUtils/getRequiredDataFields';
15+
import type {Column} from '../../../../../utils/tableUtils/types';
16+
17+
export function getTopNodesByPingColumns(params: GetNodesColumnsParams) {
18+
const columns: Column<NodesPreparedEntity>[] = [
19+
getNodeIdColumn(),
20+
getNetworkHostColumn(params),
21+
getUptimeColumn(),
22+
getPingTimeColumn(),
23+
];
24+
25+
const columnsIds = columns.map((column) => column.name);
26+
const fieldsRequired = getRequiredDataFields(columnsIds, NODES_COLUMNS_TO_DATA_FIELDS);
27+
28+
return [
29+
columns.map((column) => ({
30+
...column,
31+
sortable: isSortableNodesColumn(column.name),
32+
})),
33+
fieldsRequired,
34+
] as const;
35+
}
36+
37+
export function getTopNodesBySkewColumns(params: GetNodesColumnsParams) {
38+
const columns: Column<NodesPreparedEntity>[] = [
39+
getNodeIdColumn(),
40+
getNetworkHostColumn(params),
41+
getUptimeColumn(),
42+
getClockSkewColumn(),
43+
];
44+
45+
const columnsIds = columns.map((column) => column.name);
46+
const fieldsRequired = getRequiredDataFields(columnsIds, NODES_COLUMNS_TO_DATA_FIELDS);
47+
48+
return [
49+
columns.map((column) => ({
50+
...column,
51+
sortable: isSortableNodesColumn(column.name),
52+
})),
53+
fieldsRequired,
54+
] as const;
55+
}

0 commit comments

Comments
 (0)