1- import React from 'react ' ;
2-
3- import { Select } from '@gravity-ui/uikit' ;
1+ import { ArrowsRotateRight } from '@gravity-ui/icons ' ;
2+ import type { Column as DataTableColumn } from '@gravity-ui/react-data-table' ;
3+ import { Icon , Label , Text } from '@gravity-ui/uikit' ;
44import { skipToken } from '@reduxjs/toolkit/query' ;
5- import ReactList from 'react-list' ;
65
6+ import { ButtonWithConfirmDialog } from '../../components/ButtonWithConfirmDialog/ButtonWithConfirmDialog' ;
7+ import { EntityStatus } from '../../components/EntityStatus/EntityStatus' ;
78import { ResponseError } from '../../components/Errors/ResponseError' ;
8- import { Loader } from '../../components/Loader' ;
9- import { Tablet } from '../../components/Tablet' ;
10- import TabletsOverall from '../../components/TabletsOverall/TabletsOverall' ;
11- import { setStateFilter , setTypeFilter , tabletsApi } from '../../store/reducers/tablets' ;
12- import type { ETabletState , EType } from '../../types/api/tablet' ;
9+ import { InternalLink } from '../../components/InternalLink' ;
10+ import { ResizeableDataTable } from '../../components/ResizeableDataTable/ResizeableDataTable' ;
11+ import { TableSkeleton } from '../../components/TableSkeleton/TableSkeleton' ;
12+ import routes , { createHref } from '../../routes' ;
13+ import { selectTabletsWithFqdn , tabletsApi } from '../../store/reducers/tablets' ;
14+ import { ETabletState } from '../../types/api/tablet' ;
15+ import type { TTabletStateInfo } from '../../types/api/tablet' ;
1316import type { TabletsApiRequestParams } from '../../types/store/tablets' ;
1417import { cn } from '../../utils/cn' ;
18+ import { DEFAULT_TABLE_SETTINGS } from '../../utils/constants' ;
19+ import { calcUptime } from '../../utils/dataFormatters/dataFormatters' ;
1520import { useTypedDispatch , useTypedSelector } from '../../utils/hooks' ;
21+ import { mapTabletStateToLabelTheme } from '../../utils/tablet' ;
22+ import { getDefaultNodePath } from '../Node/NodePages' ;
1623
1724import i18n from './i18n' ;
1825
19- import './Tablets.scss' ;
20-
2126const b = cn ( 'tablets' ) ;
2227
28+ const columns : DataTableColumn < TTabletStateInfo & { fqdn ?: string } > [ ] = [
29+ {
30+ name : 'Type' ,
31+ get header ( ) {
32+ return i18n ( 'Type' ) ;
33+ } ,
34+ render : ( { row} ) => {
35+ return (
36+ < span >
37+ { row . Type } { row . Leader ? < Text color = "secondary" > leader</ Text > : '' }
38+ </ span >
39+ ) ;
40+ } ,
41+ } ,
42+ {
43+ name : 'TabletId' ,
44+ get header ( ) {
45+ return i18n ( 'Tablet' ) ;
46+ } ,
47+ render : ( { row} ) => {
48+ const tabletPath =
49+ row . TabletId &&
50+ createHref ( routes . tablet , { id : row . TabletId } , { nodeId : row . NodeId , type : row . Type } ) ;
51+
52+ return < InternalLink to = { tabletPath } > { row . TabletId } </ InternalLink > ;
53+ } ,
54+ } ,
55+ {
56+ name : 'State' ,
57+ get header ( ) {
58+ return i18n ( 'State' ) ;
59+ } ,
60+ render : ( { row} ) => {
61+ return < Label theme = { mapTabletStateToLabelTheme ( row . State ) } > { row . State } </ Label > ;
62+ } ,
63+ } ,
64+ {
65+ name : 'NodeId' ,
66+ get header ( ) {
67+ return i18n ( 'Node ID' ) ;
68+ } ,
69+ render : ( { row} ) => {
70+ const nodePath = row . NodeId === undefined ? undefined : getDefaultNodePath ( row . NodeId ) ;
71+ return < InternalLink to = { nodePath } > { row . NodeId } </ InternalLink > ;
72+ } ,
73+ align : 'right' ,
74+ } ,
75+ {
76+ name : 'FQDN' ,
77+ get header ( ) {
78+ return i18n ( 'Node FQDN' ) ;
79+ } ,
80+ render : ( { row} ) => {
81+ if ( ! row . fqdn ) {
82+ return < span > —</ span > ;
83+ }
84+ return < EntityStatus name = { row . fqdn } showStatus = { false } hasClipboardButton /> ;
85+ } ,
86+ } ,
87+ {
88+ name : 'Generation' ,
89+ get header ( ) {
90+ return i18n ( 'Generation' ) ;
91+ } ,
92+ align : 'right' ,
93+ } ,
94+ {
95+ name : 'Uptime' ,
96+ get header ( ) {
97+ return i18n ( 'Uptime' ) ;
98+ } ,
99+ render : ( { row} ) => {
100+ return calcUptime ( row . ChangeTime ) ;
101+ } ,
102+ sortAccessor : ( row ) => - Number ( row . ChangeTime ) ,
103+ align : 'right' ,
104+ } ,
105+ {
106+ name : 'Actions' ,
107+ sortable : false ,
108+ resizeable : false ,
109+ header : '' ,
110+ render : ( { row} ) => {
111+ return < TabletActions { ...row } /> ;
112+ } ,
113+ } ,
114+ ] ;
115+
116+ function TabletActions ( tablet : TTabletStateInfo ) {
117+ const isDisabledRestart = tablet . State === ETabletState . Stopped ;
118+ const dispatch = useTypedDispatch ( ) ;
119+ return (
120+ < ButtonWithConfirmDialog
121+ buttonView = "outlined"
122+ dialogContent = { i18n ( 'dialog.kill' ) }
123+ onConfirmAction = { ( ) => {
124+ return window . api . killTablet ( tablet . TabletId ) ;
125+ } }
126+ onConfirmActionSuccess = { ( ) => {
127+ dispatch ( tabletsApi . util . invalidateTags ( [ 'All' ] ) ) ;
128+ } }
129+ buttonDisabled = { isDisabledRestart }
130+ >
131+ < Icon data = { ArrowsRotateRight } />
132+ </ ButtonWithConfirmDialog >
133+ ) ;
134+ }
135+
23136interface TabletsProps {
24137 path ?: string ;
25138 nodeId ?: string | number ;
26139 className ?: string ;
27140}
28141
29- export const Tablets = ( { path, nodeId, className} : TabletsProps ) => {
30- const dispatch = useTypedDispatch ( ) ;
31-
32- const { stateFilter, typeFilter} = useTypedSelector ( ( state ) => state . tablets ) ;
142+ export function Tablets ( { nodeId, path, className} : TabletsProps ) {
33143 const { autorefresh} = useTypedSelector ( ( state ) => state . schema ) ;
34144
35145 let params : TabletsApiRequestParams | typeof skipToken = skipToken ;
36- if ( nodeId ) {
37- params = { nodes : [ String ( nodeId ) ] } ;
146+ const node = nodeId === undefined ? undefined : String ( nodeId ) ;
147+ if ( node !== undefined ) {
148+ params = { nodes : [ String ( node ) ] } ;
38149 } else if ( path ) {
39150 params = { path} ;
40151 }
@@ -43,94 +154,23 @@ export const Tablets = ({path, nodeId, className}: TabletsProps) => {
43154 } ) ;
44155
45156 const loading = isFetching && currentData === undefined ;
46- const tablets = React . useMemo ( ( ) => currentData ?. TabletStateInfo || [ ] , [ currentData ] ) ;
47-
48- const tabletsToRender = React . useMemo ( ( ) => {
49- let filteredTablets = tablets ;
50-
51- if ( typeFilter . length > 0 ) {
52- filteredTablets = filteredTablets . filter ( ( tablet ) =>
53- typeFilter . some ( ( filter ) => tablet . Type === filter ) ,
54- ) ;
55- }
56- if ( stateFilter . length > 0 ) {
57- filteredTablets = filteredTablets . filter ( ( tablet ) =>
58- stateFilter . some ( ( filter ) => tablet . State === filter ) ,
59- ) ;
60- }
61- return filteredTablets ;
62- } , [ tablets , stateFilter , typeFilter ] ) ;
63-
64- const handleStateFilterChange = ( value : string [ ] ) => {
65- dispatch ( setStateFilter ( value as ETabletState [ ] ) ) ;
66- } ;
67-
68- const handleTypeFilterChange = ( value : string [ ] ) => {
69- dispatch ( setTypeFilter ( value as EType [ ] ) ) ;
70- } ;
71-
72- const renderTablet = ( tabletIndex : number ) => {
73- return < Tablet tablet = { tabletsToRender [ tabletIndex ] } key = { tabletIndex } /> ;
74- } ;
75-
76- const renderContent = ( ) => {
77- const states = Array . from ( new Set ( tablets . map ( ( tablet ) => tablet . State ) ) )
78- . filter ( ( state ) : state is ETabletState => state !== undefined )
79- . map ( ( item ) => ( {
80- value : item ,
81- content : item ,
82- } ) ) ;
83- const types = Array . from ( new Set ( tablets . map ( ( tablet ) => tablet . Type ) ) )
84- . filter ( ( type ) : type is EType => type !== undefined )
85- . map ( ( item ) => ( {
86- value : item ,
87- content : item ,
88- } ) ) ;
89-
90- return (
91- < div className = { b ( null , className ) } >
92- < div className = { b ( 'header' ) } >
93- < Select
94- className = { b ( 'filter-control' ) }
95- multiple
96- placeholder = { i18n ( 'controls.allItems' ) }
97- label = { `${ i18n ( 'controls.state' ) } :` }
98- options = { states }
99- value = { stateFilter }
100- onUpdate = { handleStateFilterChange }
101- />
102- < Select
103- className = { b ( 'filter-control' ) }
104- multiple
105- placeholder = { i18n ( 'controls.allItems' ) }
106- label = { `${ i18n ( 'controls.type' ) } :` }
107- options = { types }
108- value = { typeFilter }
109- onUpdate = { handleTypeFilterChange }
110- />
111- < TabletsOverall tablets = { tablets } />
112- </ div >
113-
114- < div className = { b ( 'items' ) } >
115- < ReactList
116- itemRenderer = { renderTablet }
117- length = { tabletsToRender . length }
118- type = "uniform"
119- />
120- </ div >
121- </ div >
122- ) ;
123- } ;
157+ const tablets = useTypedSelector ( ( state ) => selectTabletsWithFqdn ( state , node , path ) ) ;
124158
125159 if ( loading ) {
126- return < Loader /> ;
127- } else if ( error ) {
160+ return < TableSkeleton /> ;
161+ }
162+ if ( error ) {
128163 return < ResponseError error = { error } /> ;
129- } else {
130- return tablets . length > 0 ? (
131- renderContent ( )
132- ) : (
133- < div className = "error" > { i18n ( 'noTabletsData' ) } </ div >
134- ) ;
135164 }
136- } ;
165+
166+ return (
167+ < div className = { b ( null , className ) } >
168+ < ResizeableDataTable
169+ columns = { columns }
170+ data = { tablets }
171+ settings = { DEFAULT_TABLE_SETTINGS }
172+ emptyDataMessage = { i18n ( 'noTabletsData' ) }
173+ />
174+ </ div >
175+ ) ;
176+ }
0 commit comments