diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/types/datanode.types.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/types/datanode.types.tsx index ba8336b678c..e9cb1682081 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/types/datanode.types.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/types/datanode.types.tsx @@ -16,7 +16,9 @@ * limitations under the License. */ -export type DatanodeStatus = 'HEALTHY' | 'STALE' | 'DEAD' | 'DECOMMISSIONING' | 'DECOMMISSIONED'; +export const DatanodeStatusList = ['HEALTHY', 'STALE', 'DEAD', 'DECOMMISSIONING', 'DECOMMISSIONED'] as const; +type DatanodeStatusTuple = typeof DatanodeStatusList; +export type DatanodeStatus = DatanodeStatusTuple[number]; // 'HEALTHY' | 'STALE' | 'DEAD' | 'DECOMMISSIONING' | 'DECOMMISSIONED'; export interface IStorageReport { capacity: number; diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/utils/columnSearch.less b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/utils/columnSearch.less new file mode 100644 index 00000000000..4c7013a1304 --- /dev/null +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/utils/columnSearch.less @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.column-search-container { + padding: 8px; + + .input-block { + width: 188px; + margin-bottom: 8px; + display: block; + } + + .search-button { + width: 90px; + margin-right: 8px; + } + + .reset-button { + width: 90px; + } +} diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/utils/columnSearch.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/utils/columnSearch.tsx new file mode 100644 index 00000000000..319bfd29e01 --- /dev/null +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/utils/columnSearch.tsx @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import {Input, Button, Icon} from 'antd'; +import './columnSearch.less'; + +class ColumnSearch extends React.PureComponent { + searchInput: Input | null = null; + + getColumnSearchProps = (dataIndex: string) => ({ + filterDropdown: ({ + setSelectedKeys, + selectedKeys, + confirm, + clearFilters + }: { + setSelectedKeys: (keys: string[]) => void; + selectedKeys: string[]; + confirm: () => void; + clearFilters: () => void; + }) => ( +
+ { + this.searchInput = node; + }} + className='input-block' + placeholder={`Search ${dataIndex}`} + value={selectedKeys[0]} + onChange={e => + setSelectedKeys(e.target.value ? [e.target.value] : [])} + onPressEnter={() => this.handleSearch(confirm)} + /> + + +
+ ), + filterIcon: (filtered: boolean) => ( + + ), + onFilter: (value: string, record: any) => + record[dataIndex].toString().toLowerCase().includes(value.toLowerCase()), + onFilterDropdownVisibleChange: (visible: boolean) => { + if (visible) { + setTimeout(() => { + if (this.searchInput) { + this.searchInput.select(); + } + }); + } + } + }); + + handleSearch = (confirm: () => void) => { + confirm(); + }; + + handleReset = (clearFilters: () => void) => { + clearFilters(); + }; +} + +export {ColumnSearch}; diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/datanodes/datanodes.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/datanodes/datanodes.tsx index fada1f43c35..ceb7b51ac05 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/datanodes/datanodes.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/datanodes/datanodes.tsx @@ -23,13 +23,14 @@ import {PaginationConfig} from 'antd/lib/pagination'; import moment from 'moment'; import {ReplicationIcon} from 'utils/themeIcons'; import StorageBar from 'components/storageBar/storageBar'; -import {DatanodeStatus, IStorageReport} from 'types/datanode.types'; +import {DatanodeStatus, DatanodeStatusList, IStorageReport} from 'types/datanode.types'; import './datanodes.less'; import {AutoReloadHelper} from 'utils/autoReloadHelper'; import AutoReloadPanel from 'components/autoReloadPanel/autoReloadPanel'; import {MultiSelect, IOption} from 'components/multiSelect/multiSelect'; import {ActionMeta, ValueType} from 'react-select'; import {showDataFetchError} from 'utils/common'; +import {ColumnSearch} from 'utils/columnSearch'; interface IDatanodeResponse { hostname: string; @@ -98,6 +99,9 @@ const COLUMNS = [ dataIndex: 'state', key: 'state', isVisible: true, + filterMultiple: true, + filters: DatanodeStatusList.map(status => ({text: status, value: status})), + onFilter: (value: DatanodeStatus, record: IDatanode) => record.state === value, render: (text: DatanodeStatus) => renderDatanodeStatus(text), sorter: (a: IDatanode, b: IDatanode) => a.state.localeCompare(b.state) }, @@ -106,6 +110,7 @@ const COLUMNS = [ dataIndex: 'uuid', key: 'uuid', isVisible: true, + isSearchable: true, sorter: (a: IDatanode, b: IDatanode) => a.uuid.localeCompare(b.uuid), defaultSortOrder: 'ascend' as const }, @@ -114,6 +119,7 @@ const COLUMNS = [ dataIndex: 'hostname', key: 'hostname', isVisible: true, + isSearchable: true, sorter: (a: IDatanode, b: IDatanode) => a.hostname.localeCompare(b.hostname), defaultSortOrder: 'ascend' as const }, @@ -173,6 +179,7 @@ const COLUMNS = [ dataIndex: 'leaderCount', key: 'leaderCount', isVisible: true, + isSearchable: true, sorter: (a: IDatanode, b: IDatanode) => a.leaderCount - b.leaderCount }, { @@ -180,6 +187,7 @@ const COLUMNS = [ dataIndex: 'containers', key: 'containers', isVisible: true, + isSearchable: true, sorter: (a: IDatanode, b: IDatanode) => a.containers - b.containers }, { @@ -187,6 +195,7 @@ const COLUMNS = [ dataIndex: 'version', key: 'version', isVisible: false, + isSearchable: true, sorter: (a: IDatanode, b: IDatanode) => a.version.localeCompare(b.version), defaultSortOrder: 'ascend' as const }, @@ -330,9 +339,21 @@ export class Datanodes extends React.Component, IDatanode
- selectedColumns.some(e => e.value === column.key) - )} + columns={COLUMNS.reduce((filtered, column) => { + if (selectedColumns.some(e => e.value === column.key)) { + if (column.isSearchable) { + const newColumn = { + ...column, + ...new ColumnSearch(column).getColumnSearchProps(column.dataIndex) + }; + filtered.push(newColumn); + } else { + filtered.push(column); + } + } + + return filtered; + }, [])} loading={loading} pagination={paginationConfig} rowKey='hostname' diff --git a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/pipelines/pipelines.tsx b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/pipelines/pipelines.tsx index b898818cd23..342a8bd35f3 100644 --- a/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/pipelines/pipelines.tsx +++ b/hadoop-ozone/recon/src/main/resources/webapps/recon/ozone-recon-web/src/views/pipelines/pipelines.tsx @@ -28,9 +28,12 @@ import {AutoReloadHelper} from 'utils/autoReloadHelper'; import AutoReloadPanel from 'components/autoReloadPanel/autoReloadPanel'; import {showDataFetchError} from 'utils/common'; import {IAxiosResponse} from 'types/axios.types'; +import {ColumnSearch} from 'utils/columnSearch'; const {TabPane} = Tabs; -export type PipelineStatus = 'active' | 'inactive'; +const PipelineStatusList = ['OPEN', 'CLOSING', 'QUASI_CLOSED', 'CLOSED', 'UNHEALTHY', 'INVALID', 'DELETED'] as const; +type PipelineStatusTuple = typeof PipelineStatusList; +export type PipelineStatus = PipelineStatusTuple[number]; // 'OPEN' | 'CLOSING' | 'QUASI_CLOSED' | 'CLOSED' | 'UNHEALTHY' | 'INVALID' | 'DELETED'; interface IPipelineResponse { pipelineId: string; @@ -62,6 +65,7 @@ const COLUMNS = [ title: 'Pipeline ID', dataIndex: 'pipelineId', key: 'pipelineId', + isSearchable: true, sorter: (a: IPipelineResponse, b: IPipelineResponse) => a.pipelineId.localeCompare(b.pipelineId) }, { @@ -89,24 +93,30 @@ const COLUMNS = [ title: 'Status', dataIndex: 'status', key: 'status', + filterMultiple: true, + filters: PipelineStatusList.map(status => ({text: status, value: status})), + onFilter: (value: PipelineStatus, record: IPipelineResponse) => record.status === value, sorter: (a: IPipelineResponse, b: IPipelineResponse) => a.status.localeCompare(b.status) }, { title: 'Containers', dataIndex: 'containers', key: 'containers', + isSearchable: true, sorter: (a: IPipelineResponse, b: IPipelineResponse) => a.containers - b.containers }, { title: 'Datanodes', dataIndex: 'datanodes', key: 'datanodes', + isSearchable: true, render: (datanodes: string[]) =>
{datanodes.map(datanode =>
{datanode}
)}
}, { title: 'Leader', dataIndex: 'leaderNode', key: 'leaderNode', + isSearchable: true, sorter: (a: IPipelineResponse, b: IPipelineResponse) => a.leaderNode.localeCompare(b.leaderNode) }, { @@ -128,6 +138,7 @@ const COLUMNS = [ title: 'No. of Elections', dataIndex: 'leaderElections', key: 'leaderElections', + isSearchable: true, sorter: (a: IPipelineResponse, b: IPipelineResponse) => a.leaderElections - b.leaderElections } ]; @@ -205,7 +216,22 @@ export class Pipelines extends React.Component, IPipeline
-
+
((filtered, column) => { + if (column.isSearchable) { + const newColumn = { + ...column, + ...new ColumnSearch(column).getColumnSearchProps(column.dataIndex) + }; + filtered.push(newColumn); + } else { + filtered.push(column); + } + + return filtered; + }, [])} + loading={activeLoading} pagination={paginationConfig} rowKey='pipelineId'/>