diff --git a/package-lock.json b/package-lock.json index d17bab5232..4f471a6d18 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ "@gravity-ui/icons": "^2.9.1", "@gravity-ui/navigation": "^2.7.0", "@gravity-ui/paranoid": "^1.4.1", - "@gravity-ui/react-data-table": "^2.0.1", + "@gravity-ui/react-data-table": "^2.1.1", "@gravity-ui/uikit": "^6.10.2", "@gravity-ui/websql-autocomplete": "^8.1.0", "@reduxjs/toolkit": "^2.2.3", @@ -3901,9 +3901,9 @@ } }, "node_modules/@gravity-ui/react-data-table": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@gravity-ui/react-data-table/-/react-data-table-2.0.1.tgz", - "integrity": "sha512-b926wU0jHEJyLS28VnYLD88IICYqUI6ys1YEnM/+vIUcyJ/9fUP56xQFCrlLZnxbqww6TIXEQ9RC3GJ5qJwdaA==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@gravity-ui/react-data-table/-/react-data-table-2.1.1.tgz", + "integrity": "sha512-7h5Idn1hRrdjVr46xDfJ57I5Zu9n4biV8ytuYCKBoj2LpuH4t93VHLGy+E1CTx87gjoDLPCVI6FEGAI/iyQJ3Q==", "dependencies": { "@bem-react/classname": ">=1.6.0", "react-list": "^0.8.17", diff --git a/package.json b/package.json index d93b8444b5..f8e29aedcd 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "@gravity-ui/icons": "^2.9.1", "@gravity-ui/navigation": "^2.7.0", "@gravity-ui/paranoid": "^1.4.1", - "@gravity-ui/react-data-table": "^2.0.1", + "@gravity-ui/react-data-table": "^2.1.1", "@gravity-ui/uikit": "^6.10.2", "@gravity-ui/websql-autocomplete": "^8.1.0", "@reduxjs/toolkit": "^2.2.3", diff --git a/src/components/DateRange/DateRange.scss b/src/components/DateRange/DateRange.scss index 8c036523e2..0efe8594e1 100644 --- a/src/components/DateRange/DateRange.scss +++ b/src/components/DateRange/DateRange.scss @@ -1,11 +1,18 @@ .date-range { &__input { min-width: 190px; + height: 28px; padding: 5px 8px; color: var(--g-color-text-primary); border: 1px solid var(--g-color-line-generic); border-radius: var(--g-border-radius-m); + outline: none; background: transparent; } + + &__input:focus, + &__input:focus-visible { + border: 1px solid var(--g-color-line-generic-hover); + } } diff --git a/src/components/NodeHostWrapper/NodeHostWrapper.scss b/src/components/NodeHostWrapper/NodeHostWrapper.scss index 82455f3a55..7b01ea1b18 100644 --- a/src/components/NodeHostWrapper/NodeHostWrapper.scss +++ b/src/components/NodeHostWrapper/NodeHostWrapper.scss @@ -1,10 +1,4 @@ .ydb-node-host-wrapper { - &__host-wrapper { - display: flex; - - width: 330px; - } - &__host { overflow: hidden; } diff --git a/src/components/NodeHostWrapper/NodeHostWrapper.tsx b/src/components/NodeHostWrapper/NodeHostWrapper.tsx index d731c2720d..6ee1442da0 100644 --- a/src/components/NodeHostWrapper/NodeHostWrapper.tsx +++ b/src/components/NodeHostWrapper/NodeHostWrapper.tsx @@ -39,25 +39,18 @@ export const NodeHostWrapper = ({node, getNodeRef}: NodeHostWrapperProps) => { placement={['top', 'bottom']} behavior={PopoverBehavior.Immediate} > -
- - {nodeRef && ( - - )} -
+ + {nodeRef && ( + + )} ); }; diff --git a/src/components/QueryResultTable/QueryResultTable.scss b/src/components/QueryResultTable/QueryResultTable.scss index 8412de2958..41f9d85369 100644 --- a/src/components/QueryResultTable/QueryResultTable.scss +++ b/src/components/QueryResultTable/QueryResultTable.scss @@ -2,6 +2,7 @@ .ydb-query-result-table { &__cell { + width: 100%; @include cell-container; } diff --git a/src/components/QueryResultTable/QueryResultTable.tsx b/src/components/QueryResultTable/QueryResultTable.tsx index 133ec737b2..e92acc98d4 100644 --- a/src/components/QueryResultTable/QueryResultTable.tsx +++ b/src/components/QueryResultTable/QueryResultTable.tsx @@ -1,13 +1,15 @@ import React from 'react'; import DataTable from '@gravity-ui/react-data-table'; -import type {Column, DataTableProps, Settings} from '@gravity-ui/react-data-table'; +import type {Column, Settings} from '@gravity-ui/react-data-table'; import type {ColumnType, KeyValueRow} from '../../types/api/query'; import {cn} from '../../utils/cn'; import {DEFAULT_TABLE_SETTINGS} from '../../utils/constants'; import {getColumnType, prepareQueryResponse} from '../../utils/query'; import {isNumeric} from '../../utils/utils'; +import type {ResizeableDataTableProps} from '../ResizeableDataTable/ResizeableDataTable'; +import {ResizeableDataTable} from '../ResizeableDataTable/ResizeableDataTable'; import {Cell} from './Cell'; import i18n from './i18n'; @@ -70,7 +72,7 @@ const prepareGenericColumns = (data: KeyValueRow[]) => { const getRowIndex = (_: unknown, index: number) => index; interface QueryResultTableProps - extends Omit, 'data' | 'columns' | 'theme'> { + extends Omit, 'data' | 'columns'> { data?: KeyValueRow[]; columns?: ColumnType[]; } @@ -101,8 +103,7 @@ export const QueryResultTable = (props: QueryResultTableProps) => { } return ( - extends Omit, 'theme' | 'onResize'> { + columnsWidthLSKey?: string; + wrapperClassName?: string; +} + +export function ResizeableDataTable({ + columnsWidthLSKey, + columns, + settings, + wrapperClassName, + ...props +}: ResizeableDataTableProps) { + const [tableColumnsWidth, setTableColumnsWidth] = useTableResize(columnsWidthLSKey); + + const updatedColumns = updateColumnsWidth(columns, tableColumnsWidth); + + const newSettings: Settings = { + ...settings, + defaultResizeable: true, + }; + + return ( +
+ +
+ ); +} diff --git a/src/components/VirtualTable/ResizeHandler.tsx b/src/components/VirtualTable/ResizeHandler.tsx new file mode 100644 index 0000000000..3682bd3153 --- /dev/null +++ b/src/components/VirtualTable/ResizeHandler.tsx @@ -0,0 +1,106 @@ +import React from 'react'; + +import {b} from './shared'; +import {calculateColumnWidth, rafThrottle} from './utils'; + +interface ResizeHandlerProps { + maxWidth?: number; + minWidth?: number; + getCurrentColumnWidth: () => number | undefined; + onResize?: (width: number) => void; +} + +export function ResizeHandler({ + minWidth, + maxWidth, + getCurrentColumnWidth, + onResize, +}: ResizeHandlerProps) { + const elementRef = React.useRef(null); + + const [resizing, setResizing] = React.useState(false); + + React.useEffect(() => { + const element = elementRef.current; + + if (!element) { + return undefined; + } + + let mouseXPosition: number | undefined; + let initialColumnWidth: number | undefined; + let currentColumnWidth: number | undefined; + + const onMouseMove = rafThrottle((e: MouseEvent) => { + restrictMouseEvent(e); + + if (typeof mouseXPosition !== 'number' || typeof initialColumnWidth !== 'number') { + return; + } + + const xDiff = e.clientX - mouseXPosition; + + const newWidth = calculateColumnWidth(initialColumnWidth + xDiff, minWidth, maxWidth); + + if (newWidth === currentColumnWidth) { + return; + } + + currentColumnWidth = newWidth; + + onResize?.(currentColumnWidth); + }); + + const onMouseUp = (e: MouseEvent) => { + restrictMouseEvent(e); + + if (currentColumnWidth !== undefined) { + onResize?.(currentColumnWidth); + } + + setResizing(false); + mouseXPosition = undefined; + + document.removeEventListener('mousemove', onMouseMove); + document.removeEventListener('mouseup', onMouseUp); + }; + + const onMouseDown = (e: MouseEvent) => { + initialColumnWidth = getCurrentColumnWidth(); + + restrictMouseEvent(e); + + mouseXPosition = e.clientX; + + setResizing(true); + + document.addEventListener('mousemove', onMouseMove); + document.addEventListener('mouseup', onMouseUp); + }; + + element.addEventListener('mousedown', onMouseDown); + + return () => { + element.removeEventListener('mousedown', onMouseDown); + document.removeEventListener('mousemove', onMouseMove); + document.removeEventListener('mouseup', onMouseUp); + }; + }, [onResize, minWidth, maxWidth, getCurrentColumnWidth]); + + return ( + restrictMouseEvent(e)} + /> + ); +} + +// Prevent sort trigger and text selection on resize +function restrictMouseEvent< + T extends {preventDefault: VoidFunction; stopPropagation: VoidFunction}, +>(e: T) { + e.preventDefault(); + e.stopPropagation(); +} diff --git a/src/components/VirtualTable/ResizeableVirtualTable.tsx b/src/components/VirtualTable/ResizeableVirtualTable.tsx new file mode 100644 index 0000000000..b44520962c --- /dev/null +++ b/src/components/VirtualTable/ResizeableVirtualTable.tsx @@ -0,0 +1,31 @@ +import type {ColumnWidthByName} from '@gravity-ui/react-data-table'; + +import {useTableResize} from '../../utils/hooks/useTableResize'; + +import type {VirtualTableProps} from './VirtualTable'; +import {VirtualTable} from './VirtualTable'; +import type {Column} from './types'; + +function updateColumnsWidth(columns: Column[], columnsWidthSetup: ColumnWidthByName) { + return columns.map((column) => { + return {...column, width: columnsWidthSetup[column.name] ?? column.width}; + }); +} + +interface ResizeableVirtualTableProps extends Omit, 'onColumnsResize'> { + columnsWidthLSKey: string; +} + +export function ResizeableVirtualTable({ + columnsWidthLSKey, + columns, + ...props +}: ResizeableVirtualTableProps) { + const [tableColumnsWidth, setTableColumnsWidth] = useTableResize(columnsWidthLSKey); + + const updatedColumns = updateColumnsWidth(columns, tableColumnsWidth); + + return ( + + ); +} diff --git a/src/components/VirtualTable/TableHead.tsx b/src/components/VirtualTable/TableHead.tsx index e477206fc6..0c46db94cd 100644 --- a/src/components/VirtualTable/TableHead.tsx +++ b/src/components/VirtualTable/TableHead.tsx @@ -1,10 +1,6 @@ import React from 'react'; -import type { - HandleTableColumnsResize, - TableColumnsWidthSetup, -} from '../../utils/hooks/useTableResize'; - +import {ResizeHandler} from './ResizeHandler'; import { ASCENDING, DEFAULT_RESIZEABLE, @@ -13,9 +9,7 @@ import { DESCENDING, } from './constants'; import {b} from './shared'; -import type {Column, OnSort, SortOrderType, SortParams} from './types'; - -const COLUMN_NAME_HTML_ATTRIBUTE = 'data-columnname'; +import type {Column, HandleTableColumnsResize, OnSort, SortOrderType, SortParams} from './types'; // Icon similar to original DataTable icons to keep the same tables across diferent pages and tabs const SortIcon = ({order}: {order?: SortOrderType}) => { @@ -58,6 +52,7 @@ interface TableHeadCellProps { rowHeight: number; onCellMount?: (element: Element) => void; onCellUnMount?: (element: Element) => void; + onColumnsResize?: HandleTableColumnsResize; } export const TableHeadCell = ({ @@ -69,6 +64,7 @@ export const TableHeadCell = ({ rowHeight, onCellMount, onCellUnMount, + onColumnsResize, }: TableHeadCellProps) => { const cellWrapperRef = React.useRef(null); @@ -84,20 +80,28 @@ export const TableHeadCell = ({ }; }, [onCellMount, onCellUnMount]); + const getCurrentColumnWidth = React.useCallback(() => { + return cellWrapperRef.current?.getBoundingClientRect().width; + }, []); + + const handleResize = React.useCallback( + (newWidth: number) => { + onColumnsResize?.(column.name, newWidth); + }, + [onColumnsResize, column.name], + ); + const content = column.header ?? column.name; return (
({ defaultSortOrder={defaultSortOrder} />
+ {resizeable ? ( + + ) : null}
); @@ -140,38 +152,6 @@ export const TableHead = ({ }: TableHeadProps) => { const [sortParams, setSortParams] = React.useState({}); - const isTableResizeable = Boolean(onColumnsResize); - - const resizeObserver: ResizeObserver | undefined = React.useMemo(() => { - if (!isTableResizeable) { - return undefined; - } - - return new ResizeObserver((entries) => { - const columnsWidth: TableColumnsWidthSetup = {}; - entries.forEach((entry) => { - // @ts-expect-error ignore custrom property usage - const id = entry.target.attributes[COLUMN_NAME_HTML_ATTRIBUTE]?.value; - columnsWidth[id] = entry.contentRect.width; - }); - - onColumnsResize?.(columnsWidth); - }); - }, [onColumnsResize, isTableResizeable]); - - const handleCellMount = React.useCallback( - (element: Element) => { - resizeObserver?.observe(element); - }, - [resizeObserver], - ); - const handleCellUnMount = React.useCallback( - (element: Element) => { - resizeObserver?.unobserve(element); - }, - [resizeObserver], - ); - const handleSort = (columnId: string) => { let newSortParams: SortParams = {}; @@ -231,8 +211,7 @@ export const TableHead = ({ defaultSortOrder={defaultSortOrder} onSort={handleSort} rowHeight={rowHeight} - onCellMount={handleCellMount} - onCellUnMount={handleCellUnMount} + onColumnsResize={onColumnsResize} /> ); })} diff --git a/src/components/VirtualTable/VirtualTable.scss b/src/components/VirtualTable/VirtualTable.scss index 77441fc6c5..008952b8d2 100644 --- a/src/components/VirtualTable/VirtualTable.scss +++ b/src/components/VirtualTable/VirtualTable.scss @@ -60,14 +60,12 @@ } &__head-cell-wrapper { + position: relative; + display: flex; overflow-x: hidden; border-bottom: $cell-border; - - &_resizeable { - resize: horizontal; - } } &__head-cell { @@ -145,4 +143,26 @@ } } } + + &__resize-handler { + position: absolute; + top: 0; + right: 0; + + visibility: hidden; + + width: 6px; + height: 100%; + + cursor: col-resize; + + background-color: var(--g-color-base-generic); + + &_resizing { + visibility: visible; + } + } + &__head-cell-wrapper:hover > &__resize-handler { + visibility: visible; + } } diff --git a/src/components/VirtualTable/VirtualTable.tsx b/src/components/VirtualTable/VirtualTable.tsx index 48beca0705..5b2398a006 100644 --- a/src/components/VirtualTable/VirtualTable.tsx +++ b/src/components/VirtualTable/VirtualTable.tsx @@ -2,7 +2,6 @@ import React from 'react'; import type {IResponseError} from '../../types/api/error'; import {getArray} from '../../utils'; -import type {HandleTableColumnsResize} from '../../utils/hooks/useTableResize'; import {ResponseError} from '../Errors/ResponseError'; import {TableWithControlsLayout} from '../TableWithControlsLayout/TableWithControlsLayout'; @@ -25,6 +24,7 @@ import type { Column, FetchData, GetRowClassName, + HandleTableColumnsResize, OnEntry, OnLeave, OnSort, @@ -37,7 +37,7 @@ import {useIntersectionObserver} from './useIntersectionObserver'; import './VirtualTable.scss'; -interface VirtualTableProps { +export interface VirtualTableProps { limit: number; fetchData: FetchData; columns: Column[]; diff --git a/src/components/VirtualTable/types.ts b/src/components/VirtualTable/types.ts index 5f6d5a82ae..04cd2a3e77 100644 --- a/src/components/VirtualTable/types.ts +++ b/src/components/VirtualTable/types.ts @@ -21,6 +21,8 @@ export type SortOrderType = typeof ASCENDING | typeof DESCENDING; export type SortParams = {columnId?: string; sortOrder?: SortOrderType}; export type OnSort = (params: SortParams) => void; +export type HandleTableColumnsResize = (columnId: string, width: number) => void; + export interface Column { name: string; header?: React.ReactNode; @@ -29,6 +31,8 @@ export interface Column { resizeable?: boolean; render: (props: {row: T; index: number}) => React.ReactNode; width: number; + resizeMaxWidth?: number; + resizeMinWidth?: number; align: AlignType; } diff --git a/src/components/VirtualTable/utils.ts b/src/components/VirtualTable/utils.ts new file mode 100644 index 0000000000..e546d8e3ee --- /dev/null +++ b/src/components/VirtualTable/utils.ts @@ -0,0 +1,25 @@ +// invoke passed function at most once per animation frame +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function rafThrottle any>(fn: Fn) { + let rafId: number | null = null; + let latestArgs: Parameters; + + return function throttled(...args: Parameters) { + // call throttled function with latest args + latestArgs = args; + + if (typeof rafId === 'number') { + return; + } + + rafId = requestAnimationFrame(() => { + fn(...latestArgs); + rafId = null; + }); + }; +} + +// 40px minWidth so sort icon won't overlap wrapped column title +export function calculateColumnWidth(newWidth: number, minWidth = 40, maxWidth = Infinity) { + return Math.max(minWidth, Math.min(newWidth, maxWidth)); +} diff --git a/src/containers/ClusterModeGuard/ClusterModeGuard.tsx b/src/containers/ClusterModeGuard/ClusterModeGuard.tsx index bbb4876303..3aa600cdb4 100644 --- a/src/containers/ClusterModeGuard/ClusterModeGuard.tsx +++ b/src/containers/ClusterModeGuard/ClusterModeGuard.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import {useTypedSelector} from '../../lib'; +import {useTypedSelector} from '../../utils/hooks'; export interface ClusterModeGuardProps { children: React.ReactNode; diff --git a/src/containers/Clusters/Clusters.scss b/src/containers/Clusters/Clusters.scss index 4fdef07530..4d8977e0ec 100644 --- a/src/containers/Clusters/Clusters.scss +++ b/src/containers/Clusters/Clusters.scss @@ -151,8 +151,13 @@ &__table-content { overflow: auto; + height: 100%; + } + + &__table { @include mixins.freeze-nth-column(1); } + &__balancer-cell { display: flex; flex-direction: row; diff --git a/src/containers/Clusters/Clusters.tsx b/src/containers/Clusters/Clusters.tsx index 6adfc8f860..b24d13e176 100644 --- a/src/containers/Clusters/Clusters.tsx +++ b/src/containers/Clusters/Clusters.tsx @@ -6,6 +6,7 @@ import {Helmet} from 'react-helmet-async'; import {ResponseError} from '../../components/Errors/ResponseError'; import {Loader} from '../../components/Loader'; +import {ResizeableDataTable} from '../../components/ResizeableDataTable/ResizeableDataTable'; import {Search} from '../../components/Search'; import {changeClustersFilters, clustersApi} from '../../store/reducers/clusters/clusters'; import { @@ -21,7 +22,7 @@ import {useTypedDispatch, useTypedSelector} from '../../utils/hooks'; import {getMinorVersion} from '../../utils/versions'; import {ClustersStatistics} from './ClustersStatistics'; -import {CLUSTERS_COLUMNS} from './columns'; +import {CLUSTERS_COLUMNS, CLUSTERS_COLUMNS_WIDTH_LS_KEY} from './columns'; import { CLUSTER_STATUSES, COLUMNS_NAMES, @@ -174,8 +175,9 @@ export function Clusters() { {query.isError ? : null}
- —; export const CLUSTERS_COLUMNS: Column[] = [ @@ -144,7 +146,7 @@ export const CLUSTERS_COLUMNS: Column[] = [ { name: COLUMNS_NAMES.NODES, header: COLUMNS_TITLES[COLUMNS_NAMES.NODES], - width: 150, + resizeMinWidth: 140, defaultOrder: DataTable.DESCENDING, sortAccessor: ({cluster = {}}) => { const {NodesTotal = 0} = cluster; @@ -164,7 +166,7 @@ export const CLUSTERS_COLUMNS: Column[] = [ { name: COLUMNS_NAMES.LOAD, header: COLUMNS_TITLES[COLUMNS_NAMES.LOAD], - width: 150, + resizeMinWidth: 140, defaultOrder: DataTable.DESCENDING, sortAccessor: ({cluster}) => { return cluster?.NumberOfCpus; @@ -182,7 +184,7 @@ export const CLUSTERS_COLUMNS: Column[] = [ { name: COLUMNS_NAMES.STORAGE, header: COLUMNS_TITLES[COLUMNS_NAMES.STORAGE], - width: 150, + resizeMinWidth: 140, defaultOrder: DataTable.DESCENDING, sortAccessor: ({cluster}) => { return Number(cluster?.StorageTotal); diff --git a/src/containers/Nodes/Nodes.scss b/src/containers/Nodes/Nodes.scss index 74aacb99fd..de91414dda 100644 --- a/src/containers/Nodes/Nodes.scss +++ b/src/containers/Nodes/Nodes.scss @@ -12,11 +12,6 @@ margin-bottom: 15px; } - &__table { - @include table-styles; - @include table-sticky-styles; - } - &__node_unavailable { opacity: 0.6; } diff --git a/src/containers/Nodes/Nodes.tsx b/src/containers/Nodes/Nodes.tsx index 47e17894bd..f6953ae474 100644 --- a/src/containers/Nodes/Nodes.tsx +++ b/src/containers/Nodes/Nodes.tsx @@ -1,6 +1,5 @@ import React from 'react'; -import DataTable from '@gravity-ui/react-data-table'; import {ASCENDING} from '@gravity-ui/react-data-table/build/esm/lib/constants'; import {skipToken} from '@reduxjs/toolkit/query'; @@ -9,6 +8,7 @@ import {AccessDenied} from '../../components/Errors/403'; import {ResponseError} from '../../components/Errors/ResponseError'; import {Illustration} from '../../components/Illustration'; import {ProblemFilter} from '../../components/ProblemFilter'; +import {ResizeableDataTable} from '../../components/ResizeableDataTable/ResizeableDataTable'; import {Search} from '../../components/Search'; import {TableWithControlsLayout} from '../../components/TableWithControlsLayout/TableWithControlsLayout'; import {UptimeFilter} from '../../components/UptimeFIlter'; @@ -37,7 +37,7 @@ import { isUnavailableNode, } from '../../utils/nodes'; -import {getNodesColumns} from './getNodesColumns'; +import {NODES_COLUMNS_WIDTH_LS_KEY, getNodesColumns} from './getNodesColumns'; import i18n from './i18n'; import './Nodes.scss'; @@ -149,8 +149,8 @@ export const Nodes = ({path, additionalNodesProps = {}}: NodesProps) => { } return ( - { return ( {renderControls()} - + {renderTable()} diff --git a/src/containers/Nodes/VirtualNodes.tsx b/src/containers/Nodes/VirtualNodes.tsx index eec5eece28..7facbde17e 100644 --- a/src/containers/Nodes/VirtualNodes.tsx +++ b/src/containers/Nodes/VirtualNodes.tsx @@ -7,19 +7,18 @@ import {Illustration} from '../../components/Illustration'; import {ProblemFilter} from '../../components/ProblemFilter'; import {Search} from '../../components/Search'; import {UptimeFilter} from '../../components/UptimeFIlter'; -import {VirtualTable} from '../../components/VirtualTable'; import type { FetchData, GetRowClassName, RenderControls, RenderErrorMessage, } from '../../components/VirtualTable'; +import {ResizeableVirtualTable} from '../../components/VirtualTable/ResizeableVirtualTable'; import type {NodesPreparedEntity} from '../../store/reducers/nodes/types'; import {ProblemFilterValues} from '../../store/reducers/settings/settings'; import type {ProblemFilterValue} from '../../store/reducers/settings/types'; import type {AdditionalNodesProps} from '../../types/additionalProps'; import {cn} from '../../utils/cn'; -import {updateColumnsWidth, useTableResize} from '../../utils/hooks/useTableResize'; import { NodesUptimeFilterValues, getProblemParamValue, @@ -30,7 +29,7 @@ import { import type {NodesSortValue} from '../../utils/nodes'; import {getNodes} from './getNodes'; -import {getNodesColumns} from './getNodesColumns'; +import {NODES_COLUMNS_WIDTH_LS_KEY, getNodesColumns} from './getNodesColumns'; import i18n from './i18n'; import './Nodes.scss'; @@ -52,8 +51,6 @@ export const VirtualNodes = ({path, parentContainer, additionalNodesProps}: Node NodesUptimeFilterValues.All, ); - const [tableColumnsWidthSetup, setTableColumnsWidth] = useTableResize('nodesTableColumnsWidth'); - const filters = React.useMemo(() => { return [path, searchValue, problemFilter, uptimeFilter]; }, [path, searchValue, problemFilter, uptimeFilter]); @@ -122,12 +119,13 @@ export const VirtualNodes = ({path, parentContainer, additionalNodesProps}: Node getNodeRef: additionalNodesProps?.getNodeRef, }); - const columns = updateColumnsWidth(rawColumns, tableColumnsWidthSetup).map((column) => { + const columns = rawColumns.map((column) => { return {...column, sortable: isSortableNodesProperty(column.name)}; }); return ( - ); }; diff --git a/src/containers/Nodes/getNodesColumns.tsx b/src/containers/Nodes/getNodesColumns.tsx index 995eaa5636..53bb702175 100644 --- a/src/containers/Nodes/getNodesColumns.tsx +++ b/src/containers/Nodes/getNodesColumns.tsx @@ -17,6 +17,8 @@ import { formatStorageValuesToGb, } from '../../utils/dataFormatters/dataFormatters'; +export const NODES_COLUMNS_WIDTH_LS_KEY = 'nodesTableColumnsWidth'; + const NODES_COLUMNS_IDS = { NodeId: 'NodeId', Host: 'Host', @@ -127,6 +129,7 @@ const cpuColumn: NodesColumn = { render: ({row}) => (row.PoolStats ? : '—'), align: DataTable.LEFT, width: 80, + resizeMinWidth: 60, sortable: false, }; @@ -149,12 +152,14 @@ const loadAverageColumn: NodesColumn = { ), align: DataTable.LEFT, width: 140, + resizeMinWidth: 140, sortable: false, }; const getTabletsColumn = (tabletsPath?: string): NodesColumn => ({ name: NODES_COLUMNS_IDS.Tablets, - width: 430, + width: 500, + resizeMinWidth: 500, render: ({row}) => { return row.Tablets ? ( & DataTableColumn; @@ -71,6 +73,7 @@ const typeColumn: StorageGroupsColumn = { name: GROUPS_COLUMNS_IDS.MediaType, header: 'Type', width: 100, + resizeMinWidth: 100, align: DataTable.LEFT, render: ({row}) => ( @@ -119,6 +122,7 @@ const usageColumn: StorageGroupsColumn = { name: GROUPS_COLUMNS_IDS.Usage, header: 'Usage', width: 100, + resizeMinWidth: 70, render: ({row}) => { // without a limit the usage can be evaluated as 0, // but the absence of a value is more clear @@ -209,7 +213,7 @@ const writeColumn: StorageGroupsColumn = { align: DataTable.RIGHT, }; -const getVdiscksColumn = (nodes?: NodesMap): StorageGroupsColumn => ({ +const getVDisksColumn = (nodes?: NodesMap): StorageGroupsColumn => ({ name: GROUPS_COLUMNS_IDS.VDisks, className: b('vdisks-column'), header: 'VDisks', @@ -229,6 +233,7 @@ const getVdiscksColumn = (nodes?: NodesMap): StorageGroupsColumn => ({ ), align: DataTable.CENTER, width: 900, + resizeable: false, }); export const getStorageTopGroupsColumns = (): StorageGroupsColumn[] => { @@ -244,7 +249,7 @@ export const getPDiskStorageColumns = (nodes?: NodesMap): StorageGroupsColumn[] groupIdColumn, usageColumn, usedColumn, - getVdiscksColumn(nodes), + getVDisksColumn(nodes), ]; }; @@ -261,7 +266,7 @@ const getStorageGroupsColumns = (nodes?: NodesMap): StorageGroupsColumn[] => { usedSpaceFlagColumn, readColumn, writeColumn, - getVdiscksColumn(nodes), + getVDisksColumn(nodes), ]; }; diff --git a/src/containers/Storage/StorageNodes/StorageNodes.tsx b/src/containers/Storage/StorageNodes/StorageNodes.tsx index dac1e81567..0062915c41 100644 --- a/src/containers/Storage/StorageNodes/StorageNodes.tsx +++ b/src/containers/Storage/StorageNodes/StorageNodes.tsx @@ -1,6 +1,6 @@ import type {Settings, SortOrder} from '@gravity-ui/react-data-table'; -import DataTable from '@gravity-ui/react-data-table'; +import {ResizeableDataTable} from '../../../components/ResizeableDataTable/ResizeableDataTable'; import {VISIBLE_ENTITIES} from '../../../store/reducers/storage/constants'; import type {PreparedStorageNode, VisibleEntities} from '../../../store/reducers/storage/types'; import type {AdditionalNodesProps} from '../../../types/additionalProps'; @@ -8,7 +8,10 @@ import type {HandleSort} from '../../../utils/hooks/useTableSort'; import {NodesUptimeFilterValues} from '../../../utils/nodes'; import {StorageNodesEmptyDataMessage} from './StorageNodesEmptyDataMessage'; -import {getPreparedStorageNodesColumns} from './getStorageNodesColumns'; +import { + STORAGE_NODES_COLUMNS_WIDTH_LS_KEY, + getPreparedStorageNodesColumns, +} from './getStorageNodesColumns'; import i18n from './i18n'; import {getRowUnavailableClassName} from './shared'; @@ -52,9 +55,9 @@ export function StorageNodes({ } return ( - [] = [ { @@ -43,15 +45,12 @@ const columns: Column[] = [ { name: 'Node FQDN', sortable: false, + width: 300, render: ({row}) => { if (!row.fqdn) { return ; } - return ( -
- -
- ); + return ; }, }, ]; @@ -66,8 +65,8 @@ interface TabletTableProps { export const TabletTable = ({history}: TabletTableProps) => { return ( - { } return ( - {
- [] = [ { name: CONSUMERS_COLUMNS_IDS.CONSUMER, @@ -53,6 +55,7 @@ export const columns: Column[] = [ name: CONSUMERS_COLUMNS_IDS.READ_SPEED, header: CONSUMERS_COLUMNS_TITILES[CONSUMERS_COLUMNS_IDS.READ_SPEED], align: DataTable.RIGHT, + resizeMinWidth: 140, sortAccessor: (row) => row.readSpeed.perMinute, render: ({row}) => , }, diff --git a/src/containers/Tenant/Diagnostics/HotKeys/HotKeys.scss b/src/containers/Tenant/Diagnostics/HotKeys/HotKeys.scss index 1aac9acd9a..05a10cfa8d 100644 --- a/src/containers/Tenant/Diagnostics/HotKeys/HotKeys.scss +++ b/src/containers/Tenant/Diagnostics/HotKeys/HotKeys.scss @@ -1,11 +1,7 @@ @import '../../../../styles/mixins.scss'; .ydb-hot-keys { - &__table-content { - overflow: auto; - - width: 100%; - height: 100%; + &__table { @include freeze-nth-column(1); } diff --git a/src/containers/Tenant/Diagnostics/HotKeys/HotKeys.tsx b/src/containers/Tenant/Diagnostics/HotKeys/HotKeys.tsx index f9aaf57f77..dbabd542d6 100644 --- a/src/containers/Tenant/Diagnostics/HotKeys/HotKeys.tsx +++ b/src/containers/Tenant/Diagnostics/HotKeys/HotKeys.tsx @@ -5,6 +5,7 @@ import type {Column} from '@gravity-ui/react-data-table'; import {ResponseError} from '../../../../components/Errors/ResponseError'; import {Icon} from '../../../../components/Icon'; +import {ResizeableDataTable} from '../../../../components/ResizeableDataTable/ResizeableDataTable'; import { setHotKeysData, setHotKeysDataWasNotLoaded, @@ -129,17 +130,15 @@ export function HotKeys({path}: HotKeysProps) { } return ( -
- -
+ ); } diff --git a/src/containers/Tenant/Diagnostics/Partitions/Partitions.scss b/src/containers/Tenant/Diagnostics/Partitions/Partitions.scss index 5a08ba102a..713a7f98f6 100644 --- a/src/containers/Tenant/Diagnostics/Partitions/Partitions.scss +++ b/src/containers/Tenant/Diagnostics/Partitions/Partitions.scss @@ -39,7 +39,9 @@ overflow: auto; height: 100%; + } + &__table { @include freeze-nth-column(1); } } diff --git a/src/containers/Tenant/Diagnostics/Partitions/Partitions.tsx b/src/containers/Tenant/Diagnostics/Partitions/Partitions.tsx index 34a8bd659b..07ace11a11 100644 --- a/src/containers/Tenant/Diagnostics/Partitions/Partitions.tsx +++ b/src/containers/Tenant/Diagnostics/Partitions/Partitions.tsx @@ -1,9 +1,9 @@ import React from 'react'; -import DataTable from '@gravity-ui/react-data-table'; import {skipToken} from '@reduxjs/toolkit/query'; import {ResponseError} from '../../../../components/Errors/ResponseError'; +import {ResizeableDataTable} from '../../../../components/ResizeableDataTable/ResizeableDataTable'; import {TableSkeleton} from '../../../../components/TableSkeleton/TableSkeleton'; import {nodesListApi, selectNodesMap} from '../../../../store/reducers/nodesList'; import {partitionsApi, setSelectedConsumer} from '../../../../store/reducers/partitions/partitions'; @@ -13,6 +13,7 @@ import {DEFAULT_TABLE_SETTINGS, PARTITIONS_HIDDEN_COLUMNS_KEY} from '../../../.. import {useSetting, useTypedDispatch, useTypedSelector} from '../../../../utils/hooks'; import {PartitionsControls} from './PartitionsControls/PartitionsControls'; +import {PARTITIONS_COLUMNS_WIDTH_LS_KEY} from './columns'; import i18n from './i18n'; import {addHostToPartitions} from './utils'; import type {PreparedPartitionDataWithHosts} from './utils/types'; @@ -106,7 +107,23 @@ export const Partitions = ({path}: PartitionsProps) => { const error = nodesError || topicError || partitionsError; - const getContent = () => { + const renderControls = () => { + return ( + + ); + }; + + const renderContent = () => { if (loading) { return ; } @@ -115,8 +132,9 @@ export const Partitions = ({path}: PartitionsProps) => { } return ( - { return (
- +
{renderControls()}
-
{getContent()}
+
{renderContent()}
); diff --git a/src/containers/Tenant/Diagnostics/Partitions/PartitionsControls/PartitionsControls.tsx b/src/containers/Tenant/Diagnostics/Partitions/PartitionsControls/PartitionsControls.tsx index 3348ebb9fc..25e70bd5cf 100644 --- a/src/containers/Tenant/Diagnostics/Partitions/PartitionsControls/PartitionsControls.tsx +++ b/src/containers/Tenant/Diagnostics/Partitions/PartitionsControls/PartitionsControls.tsx @@ -146,7 +146,7 @@ export const PartitionsControls = ({ }; return ( -
+