Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
35 changes: 33 additions & 2 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 @@ -453,7 +454,7 @@ export function getSendThroughputColumn<T extends {SendThroughput?: string}>():
header: NODES_COLUMNS_TITLES.SendThroughput,
render: ({row}) =>
isNumeric(row.SendThroughput)
? bytesToSpeed(row.SendThroughput)
? bytesToSpeed(row.SendThroughput, 1)
: EMPTY_DATA_PLACEHOLDER,
align: DataTable.RIGHT,
width: 110,
Expand All @@ -465,7 +466,7 @@ export function getReceiveThroughputColumn<T extends {ReceiveThroughput?: string
header: NODES_COLUMNS_TITLES.ReceiveThroughput,
render: ({row}) =>
isNumeric(row.ReceiveThroughput)
? bytesToSpeed(row.ReceiveThroughput)
? bytesToSpeed(row.ReceiveThroughput, 1)
: EMPTY_DATA_PLACEHOLDER,
align: DataTable.RIGHT,
width: 110,
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, 1))
: 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,
};
}
34 changes: 32 additions & 2 deletions src/containers/Node/Node.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import React from 'react';
import {Tab, TabList, TabProvider} from '@gravity-ui/uikit';
import {skipToken} from '@reduxjs/toolkit/query';
import {Helmet} from 'react-helmet-async';
import {useRouteMatch} from 'react-router-dom';
import {useHistory, useRouteMatch} from 'react-router-dom';
import {useQueryParams} from 'use-query-params';

import {EntityPageTitle} from '../../components/EntityPageTitle/EntityPageTitle';
Expand All @@ -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 All @@ -48,6 +50,7 @@ export function Node() {
const configsAvailable = isViewerUser && hasConfigs;

const dispatch = useTypedDispatch();
const history = useHistory();

const match = useRouteMatch<{id: string; activeTab: string}>(routes.node);

Expand All @@ -69,6 +72,7 @@ export function Node() {

const capabilitiesLoaded = useCapabilitiesLoaded();
const isDiskPagesAvailable = useDiskPagesAvailable();
const isPeersHandlerAvailable = useViewerPeersHandlerAvailable();

const pageLoading = isLoading || !capabilitiesLoaded;

Expand All @@ -90,13 +94,23 @@ export function Node() {
if (!threadsQuantity) {
skippedTabs.push('threads');
}
if (!isPeersHandlerAvailable) {
skippedTabs.push('network');
}
const actualNodeTabs = NODE_TABS.filter((el) => !skippedTabs.includes(el.id));

const actualActiveTab =
actualNodeTabs.find(({id}) => id === activeTabId) ?? actualNodeTabs[0];

return {activeTab: actualActiveTab, nodeTabs: actualNodeTabs};
}, [isStorageNode, isDiskPagesAvailable, activeTabId, threadsQuantity, configsAvailable]);
}, [
isStorageNode,
isDiskPagesAvailable,
isPeersHandlerAvailable,
activeTabId,
threadsQuantity,
configsAvailable,
]);

const database = tenantNameFromQuery?.toString();

Expand All @@ -116,6 +130,18 @@ export function Node() {
}
}, [dispatch, database, nodeId, isLoading, isStorageNode, databaseName]);

React.useEffect(() => {
if (!nodeId || !activeTab) {
return;
}

if (activeTab.id !== activeTabId) {
const path = getDefaultNodePath({id: nodeId, activeTab: activeTab.id}, {database});

history.replace(path);
}
}, [nodeId, database, activeTab.id, activeTabId, history, activeTab]);

return (
<div className={b(null)} ref={container}>
{<NodePageHelmet node={node} activeTabTitle={activeTab.title} />}
Expand Down Expand Up @@ -281,6 +307,10 @@ 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.

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,39 @@
import React from 'react';

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

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={i18n('search-placeholder')}
width={238}
/>
<EntitiesCount
current={entitiesCountCurrent}
total={entitiesCountTotal}
label={i18n('field_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={i18n('table_node-peers')}
renderErrorMessage={renderPaginatedTableErrorMessage}
renderEmptyDataMessage={renderEmptyDataMessage}
onDataFetched={onDataFetched}
/>
);
}
Loading
Loading