diff --git a/src/containers/Cluster/Cluster.tsx b/src/containers/Cluster/Cluster.tsx index d9f559c4c..9d75e810d 100644 --- a/src/containers/Cluster/Cluster.tsx +++ b/src/containers/Cluster/Cluster.tsx @@ -26,6 +26,7 @@ import {useTypedDispatch, useTypedSelector} from '../../utils/hooks'; import {parseVersionsToVersionToColorMap} from '../../utils/versions'; import {NodesWrapper} from '../Nodes/NodesWrapper'; import {StorageWrapper} from '../Storage/StorageWrapper'; +import {TabletsTable} from '../Tablets/TabletsTable'; import {Tenants} from '../Tenants/Tenants'; import {Versions} from '../Versions/Versions'; @@ -153,6 +154,19 @@ export function Cluster({ additionalClusterProps={additionalClusterProps} /> + +
+
+ +
+ ; @@ -32,8 +33,12 @@ const versions = { id: clusterTabsIds.versions, title: 'Versions', }; +const tablets = { + id: clusterTabsIds.tablets, + title: 'Tablets', +}; -export const clusterTabs = [overview, tenants, nodes, storage, versions]; +export const clusterTabs = [overview, tenants, nodes, storage, tablets, versions]; export function isClusterTab(tab: any): tab is ClusterTab { return Object.values(clusterTabsIds).includes(tab); diff --git a/src/containers/Tablets/Tablets.tsx b/src/containers/Tablets/Tablets.tsx index 7c5ba2aca..bdbeaf4f4 100644 --- a/src/containers/Tablets/Tablets.tsx +++ b/src/containers/Tablets/Tablets.tsx @@ -1,174 +1,16 @@ -import {ArrowsRotateRight} from '@gravity-ui/icons'; -import type {Column as DataTableColumn} from '@gravity-ui/react-data-table'; -import {Icon, Text} from '@gravity-ui/uikit'; import {skipToken} from '@reduxjs/toolkit/query'; -import {ButtonWithConfirmDialog} from '../../components/ButtonWithConfirmDialog/ButtonWithConfirmDialog'; -import {DeveloperUILinkButton} from '../../components/DeveloperUILinkButton/DeveloperUILinkButton'; -import {EntityStatus} from '../../components/EntityStatus/EntityStatus'; import {ResponseError} from '../../components/Errors/ResponseError'; -import {InternalLink} from '../../components/InternalLink'; -import {ResizeableDataTable} from '../../components/ResizeableDataTable/ResizeableDataTable'; import {TableSkeleton} from '../../components/TableSkeleton/TableSkeleton'; -import {TabletState} from '../../components/TabletState/TabletState'; -import {getTabletPagePath} from '../../routes'; -import {selectIsUserAllowedToMakeChanges} from '../../store/reducers/authentication/authentication'; -import {tabletApi} from '../../store/reducers/tablet'; import {selectTabletsWithFqdn, tabletsApi} from '../../store/reducers/tablets'; -import {ETabletState} from '../../types/api/tablet'; -import type {TTabletStateInfo} from '../../types/api/tablet'; import type {TabletsApiRequestParams} from '../../types/store/tablets'; import {cn} from '../../utils/cn'; -import {DEFAULT_TABLE_SETTINGS, EMPTY_DATA_PLACEHOLDER} from '../../utils/constants'; -import {calcUptime} from '../../utils/dataFormatters/dataFormatters'; -import {createTabletDeveloperUIHref} from '../../utils/developerUI/developerUI'; import {useAutoRefreshInterval, useTypedSelector} from '../../utils/hooks'; -import {getDefaultNodePath} from '../Node/NodePages'; -import i18n from './i18n'; +import {TabletsTable} from './TabletsTable'; const b = cn('tablets'); -function getColumns({database}: {database?: string}) { - const columns: DataTableColumn[] = [ - { - name: 'Type', - get header() { - return i18n('Type'); - }, - render: ({row}) => { - const isFollower = row.Leader === false; - return ( - - {row.Type} {isFollower ? follower : ''} - - ); - }, - }, - { - name: 'TabletId', - width: 220, - get header() { - return i18n('Tablet'); - }, - render: ({row}) => { - if (!row.TabletId) { - return EMPTY_DATA_PLACEHOLDER; - } - - const tabletPath = getTabletPagePath(row.TabletId, { - nodeId: row.NodeId, - type: row.Type, - tenantName: database, - }); - - return ( - - } - /> - ); - }, - }, - { - name: 'State', - get header() { - return i18n('State'); - }, - render: ({row}) => { - return ; - }, - }, - { - name: 'NodeId', - get header() { - return i18n('Node ID'); - }, - render: ({row}) => { - const nodePath = - row.NodeId === undefined ? undefined : getDefaultNodePath(row.NodeId); - return {row.NodeId}; - }, - align: 'right', - }, - { - name: 'fqdn', - get header() { - return i18n('Node FQDN'); - }, - render: ({row}) => { - if (!row.fqdn) { - return ; - } - return ; - }, - }, - { - name: 'Generation', - get header() { - return i18n('Generation'); - }, - align: 'right', - }, - { - name: 'Uptime', - get header() { - return i18n('Uptime'); - }, - render: ({row}) => { - return calcUptime(row.ChangeTime); - }, - sortAccessor: (row) => -Number(row.ChangeTime), - align: 'right', - }, - { - name: 'Actions', - sortable: false, - resizeable: false, - header: '', - render: ({row}) => { - return ; - }, - }, - ]; - return columns; -} - -function TabletActions(tablet: TTabletStateInfo) { - const isDisabledRestart = tablet.State === ETabletState.Stopped; - const isUserAllowedToMakeChanges = useTypedSelector(selectIsUserAllowedToMakeChanges); - const [killTablet] = tabletApi.useKillTabletMutation(); - - const id = tablet.TabletId; - if (!id) { - return null; - } - - return ( - { - return killTablet({id}).unwrap(); - }} - buttonDisabled={isDisabledRestart || !isUserAllowedToMakeChanges} - withPopover - popoverContent={i18n('controls.kill-not-allowed')} - popoverDisabled={isUserAllowedToMakeChanges} - > - - - ); -} - interface TabletsProps { path?: string; database?: string; @@ -203,14 +45,7 @@ export function Tablets({nodeId, path, database, className}: TabletsProps) { return (
{error ? : null} - {currentData ? ( - - ) : null} + {currentData ? : null}
); } diff --git a/src/containers/Tablets/TabletsTable.tsx b/src/containers/Tablets/TabletsTable.tsx new file mode 100644 index 000000000..6129e1452 --- /dev/null +++ b/src/containers/Tablets/TabletsTable.tsx @@ -0,0 +1,183 @@ +import {ArrowsRotateRight} from '@gravity-ui/icons'; +import type {Column as DataTableColumn} from '@gravity-ui/react-data-table'; +import {Icon, Text} from '@gravity-ui/uikit'; + +import {ButtonWithConfirmDialog} from '../../components/ButtonWithConfirmDialog/ButtonWithConfirmDialog'; +import {DeveloperUILinkButton} from '../../components/DeveloperUILinkButton/DeveloperUILinkButton'; +import {EntityStatus} from '../../components/EntityStatus/EntityStatus'; +import {InternalLink} from '../../components/InternalLink'; +import {ResizeableDataTable} from '../../components/ResizeableDataTable/ResizeableDataTable'; +import {TabletState} from '../../components/TabletState/TabletState'; +import {getTabletPagePath} from '../../routes'; +import {selectIsUserAllowedToMakeChanges} from '../../store/reducers/authentication/authentication'; +import {tabletApi} from '../../store/reducers/tablet'; +import {ETabletState} from '../../types/api/tablet'; +import type {TTabletStateInfo} from '../../types/api/tablet'; +import {DEFAULT_TABLE_SETTINGS, EMPTY_DATA_PLACEHOLDER} from '../../utils/constants'; +import {calcUptime} from '../../utils/dataFormatters/dataFormatters'; +import {createTabletDeveloperUIHref} from '../../utils/developerUI/developerUI'; +import {useTypedSelector} from '../../utils/hooks'; +import {getDefaultNodePath} from '../Node/NodePages'; + +import i18n from './i18n'; + +function getColumns({database}: {database?: string}) { + const columns: DataTableColumn[] = [ + { + name: 'Type', + width: 150, + get header() { + return i18n('Type'); + }, + render: ({row}) => { + const isFollower = row.Leader === false; + return ( + + {row.Type} {isFollower ? follower : ''} + + ); + }, + }, + { + name: 'TabletId', + width: 220, + get header() { + return i18n('Tablet'); + }, + render: ({row}) => { + if (!row.TabletId) { + return EMPTY_DATA_PLACEHOLDER; + } + + const tabletPath = getTabletPagePath(row.TabletId, { + nodeId: row.NodeId, + type: row.Type, + tenantName: database, + }); + + return ( + + } + /> + ); + }, + }, + { + name: 'State', + get header() { + return i18n('State'); + }, + render: ({row}) => { + return ; + }, + }, + { + name: 'NodeId', + get header() { + return i18n('Node ID'); + }, + render: ({row}) => { + const nodePath = + row.NodeId === undefined ? undefined : getDefaultNodePath(row.NodeId); + return {row.NodeId}; + }, + align: 'right', + }, + { + name: 'fqdn', + get header() { + return i18n('Node FQDN'); + }, + render: ({row}) => { + if (!row.fqdn) { + return ; + } + return ; + }, + }, + { + name: 'Generation', + get header() { + return i18n('Generation'); + }, + align: 'right', + }, + { + name: 'Uptime', + get header() { + return i18n('Uptime'); + }, + render: ({row}) => { + return calcUptime(row.ChangeTime); + }, + sortAccessor: (row) => -Number(row.ChangeTime), + align: 'right', + }, + { + name: 'Actions', + sortable: false, + resizeable: false, + header: '', + render: ({row}) => { + return ; + }, + }, + ]; + return columns; +} + +function TabletActions(tablet: TTabletStateInfo) { + const isDisabledRestart = tablet.State === ETabletState.Stopped; + const isUserAllowedToMakeChanges = useTypedSelector(selectIsUserAllowedToMakeChanges); + const [killTablet] = tabletApi.useKillTabletMutation(); + + const id = tablet.TabletId; + if (!id) { + return null; + } + + return ( + { + return killTablet({id}).unwrap(); + }} + buttonDisabled={isDisabledRestart || !isUserAllowedToMakeChanges} + withPopover + popoverContent={i18n('controls.kill-not-allowed')} + popoverDisabled={isUserAllowedToMakeChanges} + > + + + ); +} + +interface TabletsTableProps { + database?: string; + tablets: (TTabletStateInfo & { + fqdn?: string; + })[]; + className?: string; +} + +export function TabletsTable({database, tablets, className}: TabletsTableProps) { + return ( + + ); +}