Skip to content

Commit 61808bf

Browse files
feat(Network): add table view
1 parent 2632b90 commit 61808bf

File tree

21 files changed

+605
-55
lines changed

21 files changed

+605
-55
lines changed

src/components/NodeHostWrapper/NodeHostWrapper.tsx

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import {PopoverBehavior} from '@gravity-ui/uikit';
22

33
import {getDefaultNodePath} from '../../containers/Node/NodePages';
44
import type {NodeAddress} from '../../types/additionalProps';
5-
import type {TSystemStateInfo} from '../../types/api/nodes';
5+
import type {TNodeInfo, TSystemStateInfo} from '../../types/api/nodes';
66
import {
77
createDeveloperUIInternalPageHref,
88
createDeveloperUILinkWithNodeId,
@@ -13,22 +13,33 @@ import {EntityStatus} from '../EntityStatus/EntityStatus';
1313
import {NodeEndpointsTooltipContent} from '../TooltipsContent';
1414

1515
export type NodeHostData = NodeAddress &
16+
Pick<TNodeInfo, 'ConnectStatus'> &
1617
Pick<TSystemStateInfo, 'SystemState'> & {
1718
NodeId: string | number;
1819
TenantName?: string;
1920
};
2021

22+
export type StatusForIcon = 'SystemState' | 'ConnectStatus';
23+
2124
interface NodeHostWrapperProps {
2225
node: NodeHostData;
2326
getNodeRef?: (node?: NodeAddress) => string | null;
2427
database?: string;
28+
statusForIcon?: StatusForIcon;
2529
}
2630

27-
export const NodeHostWrapper = ({node, getNodeRef, database}: NodeHostWrapperProps) => {
31+
export const NodeHostWrapper = ({
32+
node,
33+
getNodeRef,
34+
database,
35+
statusForIcon,
36+
}: NodeHostWrapperProps) => {
2837
if (!node.Host) {
2938
return <span></span>;
3039
}
3140

41+
const status = statusForIcon === 'ConnectStatus' ? node.ConnectStatus : node.SystemState;
42+
3243
const isNodeAvailable = !isUnavailableNode(node);
3344

3445
let developerUIInternalHref: string | undefined;
@@ -56,12 +67,7 @@ export const NodeHostWrapper = ({node, getNodeRef, database}: NodeHostWrapperPro
5667
behavior={PopoverBehavior.Immediate}
5768
delayClosing={200}
5869
>
59-
<EntityStatus
60-
name={node.Host}
61-
status={node.SystemState}
62-
path={nodePath}
63-
hasClipboardButton
64-
/>
70+
<EntityStatus name={node.Host} status={status} path={nodePath} hasClipboardButton />
6571
</CellWithPopover>
6672
);
6773
};

src/components/nodesColumns/columns.tsx

Lines changed: 176 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,18 @@ import {valueIsDefined} from '../../utils';
88
import {cn} from '../../utils/cn';
99
import {EMPTY_DATA_PLACEHOLDER} from '../../utils/constants';
1010
import {
11+
formatPercent,
1112
formatStorageValues,
1213
formatStorageValuesToGb,
1314
} from '../../utils/dataFormatters/dataFormatters';
1415
import {getSpaceUsageSeverity} from '../../utils/storage';
1516
import type {Column} from '../../utils/tableUtils/types';
16-
import {isNumeric} from '../../utils/utils';
17+
import {formatToMs, parseUsToMs} from '../../utils/timeParsers';
18+
import {bytesToSpeed, isNumeric} from '../../utils/utils';
1719
import {CellWithPopover} from '../CellWithPopover/CellWithPopover';
1820
import {MemoryViewer} from '../MemoryViewer/MemoryViewer';
1921
import {NodeHostWrapper} from '../NodeHostWrapper/NodeHostWrapper';
20-
import type {NodeHostData} from '../NodeHostWrapper/NodeHostWrapper';
22+
import type {NodeHostData, StatusForIcon} from '../NodeHostWrapper/NodeHostWrapper';
2123
import {PoolsGraph} from '../PoolsGraph/PoolsGraph';
2224
import {ProgressViewer} from '../ProgressViewer/ProgressViewer';
2325
import {TabletsStatistic} from '../TabletsStatistic';
@@ -41,15 +43,22 @@ export function getNodeIdColumn<T extends {NodeId?: string | number}>(): Column<
4143
align: DataTable.RIGHT,
4244
};
4345
}
44-
export function getHostColumn<T extends NodeHostData>({
45-
getNodeRef,
46-
database,
47-
}: GetNodesColumnsParams): Column<T> {
46+
export function getHostColumn<T extends NodeHostData>(
47+
{getNodeRef, database}: GetNodesColumnsParams,
48+
{statusForIcon}: {statusForIcon?: StatusForIcon} = {},
49+
): Column<T> {
4850
return {
4951
name: NODES_COLUMNS_IDS.Host,
5052
header: NODES_COLUMNS_TITLES.Host,
5153
render: ({row}) => {
52-
return <NodeHostWrapper node={row} getNodeRef={getNodeRef} database={database} />;
54+
return (
55+
<NodeHostWrapper
56+
node={row}
57+
getNodeRef={getNodeRef}
58+
database={database}
59+
statusForIcon={statusForIcon}
60+
/>
61+
);
5362
},
5463
width: 350,
5564
align: DataTable.LEFT,
@@ -363,3 +372,163 @@ export function getMissingDisksColumn<T extends {Missing?: number}>(): Column<T>
363372
defaultOrder: DataTable.DESCENDING,
364373
};
365374
}
375+
376+
// Network diagnostics columns
377+
export function getConnectionsColumn<T extends {Connections?: number}>(): Column<T> {
378+
return {
379+
name: NODES_COLUMNS_IDS.Connections,
380+
header: NODES_COLUMNS_TITLES.Connections,
381+
render: ({row}) => (isNumeric(row.Connections) ? row.Connections : EMPTY_DATA_PLACEHOLDER),
382+
align: DataTable.RIGHT,
383+
width: 130,
384+
};
385+
}
386+
export function getNetworkUtilizationColumn<
387+
T extends {
388+
NetworkUtilization?: number;
389+
NetworkUtilizationMin?: number;
390+
NetworkUtilizationMax?: number;
391+
},
392+
>(): Column<T> {
393+
return {
394+
name: NODES_COLUMNS_IDS.NetworkUtilization,
395+
header: NODES_COLUMNS_TITLES.NetworkUtilization,
396+
render: ({row}) => {
397+
const {NetworkUtilization, NetworkUtilizationMin = 0, NetworkUtilizationMax = 0} = row;
398+
399+
if (!isNumeric(NetworkUtilization)) {
400+
return EMPTY_DATA_PLACEHOLDER;
401+
}
402+
403+
return (
404+
<CellWithPopover
405+
placement={['top', 'auto']}
406+
fullWidth
407+
content={
408+
<DefinitionList responsive>
409+
<DefinitionList.Item key={'NetworkUtilization'} name={i18n('sum')}>
410+
{formatPercent(NetworkUtilization)}
411+
</DefinitionList.Item>
412+
<DefinitionList.Item key={'NetworkUtilizationMin'} name={i18n('min')}>
413+
{formatPercent(NetworkUtilizationMin)}
414+
</DefinitionList.Item>
415+
<DefinitionList.Item key={'NetworkUtilizationMax'} name={i18n('max')}>
416+
{formatPercent(NetworkUtilizationMax)}
417+
</DefinitionList.Item>
418+
</DefinitionList>
419+
}
420+
>
421+
{formatPercent(NetworkUtilization)}
422+
</CellWithPopover>
423+
);
424+
},
425+
align: DataTable.RIGHT,
426+
width: 180,
427+
};
428+
}
429+
export function getSendThroughputColumn<T extends {SendThroughput?: string}>(): Column<T> {
430+
return {
431+
name: NODES_COLUMNS_IDS.SendThroughput,
432+
header: NODES_COLUMNS_TITLES.SendThroughput,
433+
render: ({row}) =>
434+
isNumeric(row.SendThroughput)
435+
? bytesToSpeed(row.SendThroughput)
436+
: EMPTY_DATA_PLACEHOLDER,
437+
align: DataTable.RIGHT,
438+
width: 160,
439+
};
440+
}
441+
export function getReceiveThroughputColumn<T extends {ReceiveThroughput?: string}>(): Column<T> {
442+
return {
443+
name: NODES_COLUMNS_IDS.ReceiveThroughput,
444+
header: NODES_COLUMNS_TITLES.ReceiveThroughput,
445+
render: ({row}) =>
446+
isNumeric(row.ReceiveThroughput)
447+
? bytesToSpeed(row.ReceiveThroughput)
448+
: EMPTY_DATA_PLACEHOLDER,
449+
align: DataTable.RIGHT,
450+
width: 180,
451+
};
452+
}
453+
export function getPingTimeColumn<
454+
T extends {
455+
PingTimeUs?: string;
456+
PingTimeMinUs?: string;
457+
PingTimeMaxUs?: string;
458+
},
459+
>(): Column<T> {
460+
return {
461+
name: NODES_COLUMNS_IDS.PingTime,
462+
header: NODES_COLUMNS_TITLES.PingTime,
463+
render: ({row}) => {
464+
const {PingTimeUs, PingTimeMinUs = 0, PingTimeMaxUs = 0} = row;
465+
466+
if (!isNumeric(PingTimeUs)) {
467+
return EMPTY_DATA_PLACEHOLDER;
468+
}
469+
470+
return (
471+
<CellWithPopover
472+
placement={['top', 'auto']}
473+
fullWidth
474+
content={
475+
<DefinitionList responsive>
476+
<DefinitionList.Item key={'PingTimeUs'} name={i18n('avg')}>
477+
{formatToMs(parseUsToMs(PingTimeUs))}
478+
</DefinitionList.Item>
479+
<DefinitionList.Item key={'PingTimeMinUs'} name={i18n('min')}>
480+
{formatToMs(parseUsToMs(PingTimeMinUs))}
481+
</DefinitionList.Item>
482+
<DefinitionList.Item key={'PingTimeMaxUs'} name={i18n('max')}>
483+
{formatToMs(parseUsToMs(PingTimeMaxUs))}
484+
</DefinitionList.Item>
485+
</DefinitionList>
486+
}
487+
>
488+
{formatToMs(parseUsToMs(PingTimeUs))}
489+
</CellWithPopover>
490+
);
491+
},
492+
align: DataTable.RIGHT,
493+
width: 110,
494+
};
495+
}
496+
export function getClockSkewColumn<
497+
T extends {ClockSkewUs?: string; ClockSkewMinUs?: string; ClockSkewMaxUs?: string},
498+
>(): Column<T> {
499+
return {
500+
name: NODES_COLUMNS_IDS.ClockSkew,
501+
header: NODES_COLUMNS_TITLES.ClockSkew,
502+
render: ({row}) => {
503+
const {ClockSkewUs, ClockSkewMinUs = 0, ClockSkewMaxUs = 0} = row;
504+
505+
if (!isNumeric(ClockSkewUs)) {
506+
return EMPTY_DATA_PLACEHOLDER;
507+
}
508+
509+
return (
510+
<CellWithPopover
511+
placement={['top', 'auto']}
512+
fullWidth
513+
content={
514+
<DefinitionList responsive>
515+
<DefinitionList.Item key={'ClockSkewUs'} name={i18n('avg')}>
516+
{formatToMs(parseUsToMs(ClockSkewUs))}
517+
</DefinitionList.Item>
518+
<DefinitionList.Item key={'ClockSkewMinUs'} name={i18n('min')}>
519+
{formatToMs(parseUsToMs(ClockSkewMinUs))}
520+
</DefinitionList.Item>
521+
<DefinitionList.Item key={'ClockSkewMaxUs'} name={i18n('max')}>
522+
{formatToMs(parseUsToMs(ClockSkewMaxUs))}
523+
</DefinitionList.Item>
524+
</DefinitionList>
525+
}
526+
>
527+
{formatToMs(parseUsToMs(ClockSkewUs))}
528+
</CellWithPopover>
529+
);
530+
},
531+
align: DataTable.RIGHT,
532+
width: 120,
533+
};
534+
}

src/components/nodesColumns/constants.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ export const NODES_COLUMNS_IDS = {
2222
Load: 'Load',
2323
DiskSpaceUsage: 'DiskSpaceUsage',
2424
TotalSessions: 'TotalSessions',
25+
Connections: 'Connections',
26+
NetworkUtilization: 'NetworkUtilization',
27+
SendThroughput: 'SendThroughput',
28+
ReceiveThroughput: 'ReceiveThroughput',
29+
PingTime: 'PingTime',
30+
ClockSkew: 'ClockSkew',
2531
Missing: 'Missing',
2632
Tablets: 'Tablets',
2733
PDisks: 'PDisks',
@@ -80,6 +86,24 @@ export const NODES_COLUMNS_TITLES = {
8086
get TotalSessions() {
8187
return i18n('sessions');
8288
},
89+
get Connections() {
90+
return i18n('connections');
91+
},
92+
get NetworkUtilization() {
93+
return i18n('network-utilization');
94+
},
95+
get SendThroughput() {
96+
return i18n('send-throughput');
97+
},
98+
get ReceiveThroughput() {
99+
return i18n('receive-throughput');
100+
},
101+
get PingTime() {
102+
return i18n('ping-time');
103+
},
104+
get ClockSkew() {
105+
return i18n('clock-skew');
106+
},
83107
get Missing() {
84108
return i18n('missing');
85109
},
@@ -162,6 +186,12 @@ export const NODES_COLUMNS_TO_DATA_FIELDS: Record<NodesColumnId, NodesRequiredFi
162186
Load: ['LoadAverage'],
163187
DiskSpaceUsage: ['DiskSpaceUsage'],
164188
TotalSessions: ['SystemState'],
189+
Connections: ['Connections'],
190+
NetworkUtilization: ['NetworkUtilization'],
191+
SendThroughput: ['SendThroughput'],
192+
ReceiveThroughput: ['ReceiveThroughput'],
193+
PingTime: ['PingTime'],
194+
ClockSkew: ['ClockSkew'],
165195
Missing: ['Missing'],
166196
Tablets: ['Tablets', 'Database'],
167197
PDisks: ['PDisks'],
@@ -184,6 +214,12 @@ const NODES_COLUMNS_TO_SORT_FIELDS: Record<NodesColumnId, NodesSortValue | undef
184214
Load: 'LoadAverage',
185215
DiskSpaceUsage: 'DiskSpaceUsage',
186216
TotalSessions: undefined,
217+
Connections: 'Connections',
218+
NetworkUtilization: 'NetworkUtilization',
219+
SendThroughput: 'SendThroughput',
220+
ReceiveThroughput: 'ReceiveThroughput',
221+
PingTime: 'PingTime',
222+
ClockSkew: 'ClockSkew',
187223
Missing: 'Missing',
188224
Tablets: undefined,
189225
PDisks: undefined,

src/components/nodesColumns/i18n/en.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,14 @@
2525
"system-state": "System State",
2626
"connect-status": "Connect Status",
2727
"network-utilization": "Network Utilization",
28+
"connections": "Connections",
29+
"send-throughput": "Send Throughput",
30+
"receive-throughput": "Receive Throughput",
2831
"clock-skew": "Clock Skew",
29-
"ping-time": "Ping Time"
32+
"ping-time": "Ping Time",
33+
34+
"max": "Max",
35+
"min": "Min",
36+
"avg": "Avg",
37+
"sum": "Sum"
3038
}

src/containers/Nodes/NodesControls/NodesControls.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,15 @@ import {Search} from '../../../components/Search';
99
import {UptimeFilter} from '../../../components/UptimeFIlter';
1010
import {useViewerNodesHandlerHasGroupingBySystemState} from '../../../store/reducers/capabilities/hooks';
1111
import {useProblemFilter} from '../../../store/reducers/settings/hooks';
12+
import type {NodesGroupByField} from '../../../types/api/nodes';
1213
import {getNodesGroupByOptions} from '../columns/constants';
1314
import i18n from '../i18n';
1415
import {b} from '../shared';
1516
import {useNodesPageQueryParams} from '../useNodesPageQueryParams';
1617

1718
interface NodesControlsProps {
1819
withGroupBySelect?: boolean;
20+
groupByParams?: NodesGroupByField[];
1921

2022
columnsToSelect: TableColumnSetupItem[];
2123
handleSelectedColumnsUpdate: (updated: TableColumnSetupItem[]) => void;
@@ -27,6 +29,7 @@ interface NodesControlsProps {
2729

2830
export function NodesControls({
2931
withGroupBySelect,
32+
groupByParams = [],
3033

3134
columnsToSelect,
3235
handleSelectedColumnsUpdate,
@@ -43,11 +46,11 @@ export function NodesControls({
4346
handleSearchQueryChange,
4447
handleUptimeFilterChange,
4548
handleGroupByParamChange,
46-
} = useNodesPageQueryParams();
49+
} = useNodesPageQueryParams(groupByParams);
4750
const {problemFilter, handleProblemFilterChange} = useProblemFilter();
4851

4952
const systemStateGroupingAvailable = useViewerNodesHandlerHasGroupingBySystemState();
50-
const groupByoptions = getNodesGroupByOptions(systemStateGroupingAvailable);
53+
const groupByoptions = getNodesGroupByOptions(groupByParams, systemStateGroupingAvailable);
5154

5255
const handleGroupBySelectUpdate = (value: string[]) => {
5356
handleGroupByParamChange(value[0]);

0 commit comments

Comments
 (0)