Skip to content
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/components/NetworkTable/NetworkTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@ import {

type NetworkWrapperProps = Pick<
NodesProps,
'path' | 'scrollContainerRef' | 'database' | 'databaseFullPath'
'path' | 'scrollContainerRef' | 'database' | 'databaseFullPath' | 'nodeId'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it seems no need to tune this component?

>;

export function NetworkTable({
database,
databaseFullPath,
path,
scrollContainerRef,
nodeId,
}: NetworkWrapperProps) {
return (
<Nodes
Expand All @@ -32,6 +33,7 @@ export function NetworkTable({
requiredColumnsIds={NETWORK_REQUIRED_NODES_COLUMNS}
selectedColumnsKey={NETWORK_NODES_TABLE_SELECTED_COLUMNS_KEY}
groupByParams={NETWORK_NODES_GROUP_BY_PARAMS}
nodeId={nodeId}
/>
);
}
31 changes: 31 additions & 0 deletions src/components/nodesColumns/columns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
} from '../../utils/dataFormatters/dataFormatters';
import {getUsageSeverity} from '../../utils/generateEvaluator';
import type {Column} from '../../utils/tableUtils/types';
import {formatToMs, parseUsToMs} from '../../utils/timeParsers';
import {bytesToSpeed, isNumeric} from '../../utils/utils';
import {CellWithPopover} from '../CellWithPopover/CellWithPopover';
import {MemoryViewer} from '../MemoryViewer/MemoryViewer';
Expand Down Expand Up @@ -551,3 +552,33 @@ export function getClockSkewColumn<
width: 110,
};
}

// Peers columns

export function getPeerSkewColumn<T extends {ClockSkewUs?: string | number}>(): Column<T> {
return {
name: NODES_COLUMNS_IDS.ClockSkew,
header: NODES_COLUMNS_TITLES.ClockSkew,
align: DataTable.RIGHT,
width: 110,
resizeMinWidth: 90,
render: ({row}) =>
isNumeric(row.ClockSkewUs)
? formatToMs(parseUsToMs(row.ClockSkewUs))
: EMPTY_DATA_PLACEHOLDER,
};
}

export function getPeerPingColumn<T extends {PingTimeUs?: string | number}>(): Column<T> {
return {
name: NODES_COLUMNS_IDS.PingTime,
header: NODES_COLUMNS_TITLES.PingTime,
align: DataTable.RIGHT,
width: 110,
resizeMinWidth: 90,
render: ({row}) =>
isNumeric(row.PingTimeUs)
? formatToMs(parseUsToMs(row.PingTimeUs))
: EMPTY_DATA_PLACEHOLDER,
};
}
12 changes: 12 additions & 0 deletions src/containers/Node/Node.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
useCapabilitiesLoaded,
useConfigAvailable,
useDiskPagesAvailable,
useViewerPeersHandlerAvailable,
} from '../../store/reducers/capabilities/hooks';
import {setHeaderBreadcrumbs} from '../../store/reducers/header/header';
import {nodeApi} from '../../store/reducers/node/node';
Expand All @@ -27,6 +28,7 @@ import {useIsViewerUser} from '../../utils/hooks/useIsUserAllowedToMakeChanges';
import {checkIsStorageNode} from '../../utils/nodes';
import {useAppTitle} from '../App/AppTitleContext';
import {Configs} from '../Configs/Configs';
import {NodeNetwork} from '../Node/NodeNetwork/NodeNetwork';
import {PaginatedStorage} from '../Storage/PaginatedStorage';
import {Tablets} from '../Tablets/Tablets';

Expand Down Expand Up @@ -214,6 +216,8 @@ function NodePageContent({
tabs,
parentContainer,
}: NodePageContentProps) {
const isPeersHandlerAvailable = useViewerPeersHandlerAvailable();

const renderTabs = () => {
return (
<div className={b('tabs')}>
Expand Down Expand Up @@ -281,6 +285,14 @@ function NodePageContent({
return <Configs database={database} scrollContainerRef={parentContainer} />;
}

case 'network': {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if not isPeersHandlerAvailable, there shouldn't be such a tab at all. Also if network is in url, we should redirect user to default tab and change url as well.

if (!isPeersHandlerAvailable) {
return i18n('alert_no-peers');
}

return <NodeNetwork nodeId={nodeId} scrollContainerRef={parentContainer} />;
}

default:
return false;
}
Expand Down
84 changes: 84 additions & 0 deletions src/containers/Node/NodeNetwork/NodeNetwork.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import React from 'react';

import {PaginatedTableWithLayout} from '../../../components/PaginatedTable/PaginatedTableWithLayout';
import {TableColumnSetup} from '../../../components/TableColumnSetup/TableColumnSetup';
import {useBridgeModeEnabled} from '../../../store/reducers/capabilities/hooks';
import {useDatabaseFromQuery} from '../../../utils/hooks/useDatabaseFromQuery';
import {useSelectedColumns} from '../../../utils/hooks/useSelectedColumns';
import {useNodesPageQueryParams} from '../../Nodes/useNodesPageQueryParams';

import {NodeNetworkControlsWithTableState} from './NodeNetworkControls/NodeNetworkControlsWithTableState';
import {NodeNetworkTable} from './NodeNetworkTable';
import {getNodeNetworkColumns} from './columns';
import {
NODE_NETWORK_COLUMNS_IDS,
NODE_NETWORK_COLUMNS_TITLES,
NODE_NETWORK_DEFAULT_COLUMNS,
NODE_NETWORK_REQUIRED_COLUMNS,
NODE_NETWORK_TABLE_SELECTED_COLUMNS_KEY,
} from './constants';

interface NodeNetworkProps {
nodeId: string;
scrollContainerRef: React.RefObject<HTMLDivElement>;
}

export function NodeNetwork({nodeId, scrollContainerRef}: NodeNetworkProps) {
const database = useDatabaseFromQuery();
const isBridgeModeEnabled = useBridgeModeEnabled();

const {searchValue, handleSearchQueryChange} = useNodesPageQueryParams(
undefined, // We don't need use groupByParams yet
false, // withPeerRoleFilter = false for this tab
);

const allColumns = React.useMemo(() => {
const columns = getNodeNetworkColumns({database});

if (!isBridgeModeEnabled) {
return columns.filter((column) => column.name !== NODE_NETWORK_COLUMNS_IDS.PileName);
}

return columns;
}, [database, isBridgeModeEnabled]);

const {columnsToShow, columnsToSelect, setColumns} = useSelectedColumns(
allColumns,
NODE_NETWORK_TABLE_SELECTED_COLUMNS_KEY,
NODE_NETWORK_COLUMNS_TITLES,
NODE_NETWORK_DEFAULT_COLUMNS,
NODE_NETWORK_REQUIRED_COLUMNS,
);

return (
<PaginatedTableWithLayout
controls={
<NodeNetworkControlsWithTableState
searchValue={searchValue}
onSearchChange={handleSearchQueryChange}
/>
}
extraControls={
<TableColumnSetup
popupWidth={200}
items={columnsToSelect}
showStatus
onUpdate={setColumns}
/>
}
table={
<NodeNetworkTable
nodeId={nodeId}
searchValue={searchValue}
columns={columnsToShow}
scrollContainerRef={scrollContainerRef}
/>
}
tableWrapperProps={{
scrollContainerRef,
scrollDependencies: [searchValue],
}}
fullHeight
/>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React from 'react';

import {EntitiesCount} from '../../../../components/EntitiesCount';
import {Search} from '../../../../components/Search';

interface NodeNetworkControlsProps {
searchValue: string;
onSearchChange: (value: string) => void;

entitiesCountCurrent: number;
entitiesCountTotal: number;
entitiesLoading: boolean;
}

export function NodeNetworkControls({
searchValue,
onSearchChange,
entitiesCountCurrent,
entitiesCountTotal,
entitiesLoading,
}: NodeNetworkControlsProps) {
return (
<React.Fragment>
<Search
value={searchValue}
onChange={onSearchChange}
placeholder="Search peers"
width={238}
/>
<EntitiesCount
current={entitiesCountCurrent}
total={entitiesCountTotal}
label="Peers"
loading={entitiesLoading}
/>
</React.Fragment>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {usePaginatedTableState} from '../../../../components/PaginatedTable/PaginatedTableContext';

import {NodeNetworkControls} from './NodeNetworkControls';

interface NodeNetworkControlsWithTableStateProps {
searchValue: string;
onSearchChange: (value: string) => void;
}

export function NodeNetworkControlsWithTableState({
searchValue,
onSearchChange,
}: NodeNetworkControlsWithTableStateProps) {
const {tableState} = usePaginatedTableState();
const {foundEntities, totalEntities, isInitialLoad} = tableState;

return (
<NodeNetworkControls
searchValue={searchValue}
onSearchChange={onSearchChange}
entitiesCountCurrent={foundEntities}
entitiesCountTotal={totalEntities}
entitiesLoading={isInitialLoad}
/>
);
}
51 changes: 51 additions & 0 deletions src/containers/Node/NodeNetwork/NodeNetworkTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React from 'react';

import {ResizeablePaginatedTable} from '../../../components/PaginatedTable';
import type {PaginatedTableData} from '../../../components/PaginatedTable';
import {renderPaginatedTableErrorMessage} from '../../../utils/renderPaginatedTableErrorMessage';
import type {Column} from '../../../utils/tableUtils/types';

import {NODE_NETWORK_COLUMNS_WIDTH_LS_KEY} from './constants';
import {getNodePeers} from './helpers/getNodePeers';
import type {NodePeerRow} from './helpers/nodeNetworkMapper';
import i18n from './i18n';

interface NodeNetworkTableProps {
nodeId: string;
searchValue: string;
columns: Column<NodePeerRow>[];
scrollContainerRef: React.RefObject<HTMLElement>;
onDataFetched?: (data: PaginatedTableData<NodePeerRow>) => void;
}

export function NodeNetworkTable({
nodeId,
searchValue,
columns,
scrollContainerRef,
onDataFetched,
}: NodeNetworkTableProps) {
const filters = React.useMemo(
() => ({
nodeId,
searchValue: searchValue || undefined,
}),
[nodeId, searchValue],
);

const renderEmptyDataMessage = React.useCallback(() => i18n('alert_no-network-data'), []);

return (
<ResizeablePaginatedTable
columnsWidthLSKey={NODE_NETWORK_COLUMNS_WIDTH_LS_KEY}
scrollContainerRef={scrollContainerRef}
columns={columns}
fetchData={getNodePeers}
filters={filters}
tableName="node-peers"
renderErrorMessage={renderPaginatedTableErrorMessage}
renderEmptyDataMessage={renderEmptyDataMessage}
onDataFetched={onDataFetched}
/>
);
}
85 changes: 85 additions & 0 deletions src/containers/Node/NodeNetwork/columns.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import DataTable from '@gravity-ui/react-data-table';

import {
getHostColumn,
getNodeIdColumn,
getNodeNameColumn,
getPeerPingColumn,
getPeerSkewColumn,
getPileNameColumn,
getReceiveThroughputColumn,
getSendThroughputColumn,
} from '../../../components/nodesColumns/columns';
import type {GetNodesColumnsParams} from '../../../components/nodesColumns/types';
import {EMPTY_DATA_PLACEHOLDER} from '../../../lib';
import {formatDateTime} from '../../../utils/dataFormatters/dataFormatters';
import type {Column} from '../../../utils/tableUtils/types';
import {bytesToMB, isNumeric} from '../../../utils/utils';

import {
NODE_NETWORK_COLUMNS_IDS,
NODE_NETWORK_COLUMNS_TITLES,
isSortableNodeNetworkColumn,
} from './constants';
import type {NodePeerRow} from './helpers/nodeNetworkMapper';

function getPeerConnectTimeColumn<T extends {ConnectTime?: string}>(): Column<T> {
return {
name: NODE_NETWORK_COLUMNS_IDS.ConnectTime,
header: NODE_NETWORK_COLUMNS_TITLES.ConnectTime,
align: DataTable.LEFT,
width: 150,
resizeMinWidth: 120,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please set it up so that the default width is enough to display without ellipsis.

render: ({row}) =>
row.ConnectTime ? formatDateTime(row.ConnectTime) : EMPTY_DATA_PLACEHOLDER,
};
}

function getPeerSentBytesColumn<T extends {BytesSend?: string | number}>(): Column<T> {
return {
name: NODE_NETWORK_COLUMNS_IDS.BytesSend,
header: NODE_NETWORK_COLUMNS_TITLES.BytesSend,
align: DataTable.RIGHT,
width: 140,
resizeMinWidth: 120,
render: ({row}) =>
isNumeric(row.BytesSend) ? bytesToMB(row.BytesSend) : EMPTY_DATA_PLACEHOLDER,
};
}

function getPeerReceivedBytesColumn<T extends {BytesReceived?: string | number}>(): Column<T> {
return {
name: NODE_NETWORK_COLUMNS_IDS.BytesReceived,
header: NODE_NETWORK_COLUMNS_TITLES.BytesReceived,
align: DataTable.RIGHT,
width: 160,
resizeMinWidth: 130,
render: ({row}) =>
isNumeric(row.BytesReceived) ? bytesToMB(row.BytesReceived) : EMPTY_DATA_PLACEHOLDER,
};
}

export function getNodeNetworkColumns(params: GetNodesColumnsParams = {}): Column<NodePeerRow>[] {
const hostColumn = getHostColumn(params) as unknown as Column<NodePeerRow>;

const cols: Column<NodePeerRow>[] = [
getNodeIdColumn(),
getNodeNameColumn(),
getPileNameColumn(),
hostColumn,
getPeerConnectTimeColumn(),
getPeerSkewColumn(),
getPeerPingColumn(),
getSendThroughputColumn(),
getPeerSentBytesColumn(),
getReceiveThroughputColumn(),
getPeerReceivedBytesColumn(),
];

return cols.map((column) => {
return {
...column,
sortable: isSortableNodeNetworkColumn(column.name),
};
});
}
Loading
Loading