custom tabbar of console tab
This commit is contained in:
parent
c7af0059f1
commit
b8e6c8a579
|
@ -0,0 +1 @@
|
||||||
|
<svg width="2500" height="2500" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMinYMin meet"><path d="M255.96 134.393c0-21.521-13.373-40.117-33.223-47.43a75.239 75.239 0 0 0 1.253-13.791c0-39.909-32.386-72.295-72.295-72.295-23.193 0-44.923 11.074-58.505 30.088-6.686-5.224-14.835-7.94-23.402-7.94-21.104 0-38.446 17.133-38.446 38.446 0 4.597.836 9.194 2.298 13.373C13.582 81.739 0 100.962 0 122.274c0 21.522 13.373 40.327 33.431 47.64-.835 4.388-1.253 8.985-1.253 13.79 0 39.7 32.386 72.087 72.086 72.087 23.402 0 44.924-11.283 58.505-30.088 6.686 5.223 15.044 8.149 23.611 8.149 21.104 0 38.446-17.134 38.446-38.446 0-4.597-.836-9.194-2.298-13.373 19.64-7.104 33.431-26.327 33.431-47.64z" fill="#FFF"/><path d="M100.085 110.364l57.043 26.119 57.669-50.565a64.312 64.312 0 0 0 1.253-12.746c0-35.52-28.834-64.355-64.355-64.355-21.313 0-41.162 10.447-53.072 27.998l-9.612 49.73 11.074 23.82z" fill="#F4BD19"/><path d="M40.953 170.75c-.835 4.179-1.253 8.567-1.253 12.955 0 35.52 29.043 64.564 64.564 64.564 21.522 0 41.372-10.656 53.49-28.208l9.403-49.729-12.746-24.238-57.251-26.118-56.207 50.774z" fill="#3CBEB1"/><path d="M40.536 71.918l39.073 9.194 8.775-44.506c-5.432-4.179-11.91-6.268-18.805-6.268-16.925 0-30.924 13.79-30.924 30.924 0 3.552.627 7.313 1.88 10.656z" fill="#E9478C"/><path d="M37.192 81.32c-17.551 5.642-29.67 22.567-29.67 40.954 0 17.97 11.074 34.059 27.79 40.327l54.953-49.73-10.03-21.52-43.043-10.03z" fill="#2C458F"/><path d="M167.784 219.852c5.432 4.18 11.91 6.478 18.596 6.478 16.925 0 30.924-13.79 30.924-30.924 0-3.761-.627-7.314-1.88-10.657l-39.073-9.193-8.567 44.296z" fill="#95C63D"/><path d="M175.724 165.317l43.043 10.03c17.551-5.85 29.67-22.566 29.67-40.954 0-17.97-11.074-33.849-27.79-40.326l-56.415 49.311 11.492 21.94z" fill="#176655"/></svg>
|
After Width: | Height: | Size: 1.8 KiB |
|
@ -7,11 +7,16 @@ import {DropdownItem} from './DropdownItem';
|
||||||
import {HealthStatusCircle} from '@/components/infini/health_status_circle'
|
import {HealthStatusCircle} from '@/components/infini/health_status_circle'
|
||||||
|
|
||||||
class DropdownSelect extends React.Component{
|
class DropdownSelect extends React.Component{
|
||||||
state={
|
constructor(props){
|
||||||
value: this.props.defaultValue,
|
super(props)
|
||||||
loading: false,
|
this.state={
|
||||||
hasMore: true,
|
value: props.defaultValue,
|
||||||
overlayVisible: false,
|
loading: false,
|
||||||
|
hasMore: props.data.length > props.size,
|
||||||
|
overlayVisible: false,
|
||||||
|
data: (props.data || []).slice(0, props.size),
|
||||||
|
dataSource: [...props.data],
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleItemClick = (item)=>{
|
handleItemClick = (item)=>{
|
||||||
|
@ -28,47 +33,35 @@ class DropdownSelect extends React.Component{
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount(){
|
componentDidMount(){
|
||||||
let me = this;
|
|
||||||
this.fetchData().then((data)=>{
|
|
||||||
let hasMore = true;
|
|
||||||
if(data.length < this.props.size){
|
|
||||||
hasMore = false;
|
|
||||||
}
|
|
||||||
me.setState({
|
|
||||||
hasMore
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
fetchData = (name)=>{
|
|
||||||
let me = this;
|
|
||||||
const {fetchData, size} = this.props;
|
|
||||||
let data = this.props.data || [];
|
|
||||||
return fetchData(name || '', size);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleInfiniteOnLoad = (name) => {
|
handleInfiniteOnLoad = (current) => {
|
||||||
let { data } = this.props;
|
let {size } = this.props;
|
||||||
|
let targetLength = current * size;
|
||||||
|
let {hasMore, dataSource} = this.state;
|
||||||
|
if(dataSource.length < targetLength){
|
||||||
|
targetLength = dataSource.length;
|
||||||
|
hasMore = false
|
||||||
|
}
|
||||||
|
const newData = this.state.dataSource.slice(0, targetLength);
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: true,
|
data: newData,
|
||||||
|
hasMore: hasMore,
|
||||||
})
|
})
|
||||||
this.fetchData(name).then((newdata)=>{
|
|
||||||
let newState = {
|
|
||||||
loading: false,
|
|
||||||
};
|
|
||||||
if(newdata.length < this.props.size){
|
|
||||||
//message.info("no more data");
|
|
||||||
newState.hasMore = false;
|
|
||||||
}
|
|
||||||
this.setState(newState);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleInputChange = (e) =>{
|
handleInputChange = (e) =>{
|
||||||
const name = e.target.value;
|
const name = e.target.value;
|
||||||
|
const newData = this.props.data.filter(item=>{
|
||||||
|
return item.name.includes(name);
|
||||||
|
});
|
||||||
this.setState({
|
this.setState({
|
||||||
displayValue: name,
|
displayValue: name,
|
||||||
|
dataSource: newData,
|
||||||
|
data: newData,
|
||||||
|
hasMore: newData.length > this.props.size,
|
||||||
})
|
})
|
||||||
this.handleInfiniteOnLoad(name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -79,7 +72,7 @@ class DropdownSelect extends React.Component{
|
||||||
let displayVaue = value[labelField];
|
let displayVaue = value[labelField];
|
||||||
const menu = (<div className={styles.dropmenu} style={{width: this.props.width}}>
|
const menu = (<div className={styles.dropmenu} style={{width: this.props.width}}>
|
||||||
<div className={styles.infiniteContainer} style={{height: this.props.height}}>
|
<div className={styles.infiniteContainer} style={{height: this.props.height}}>
|
||||||
<div className="filter" style={{paddingTop: 10, paddingBottom:0}}>
|
<div className={styles.filter} style={{paddingTop: 10, paddingBottom:0}}>
|
||||||
<input className={styles['btn-ds']} style={{outline:'none'}} onChange={this.handleInputChange} placeholder="输入集群名称查找" value={this.state.displayValue||''} />
|
<input className={styles['btn-ds']} style={{outline:'none'}} onChange={this.handleInputChange} placeholder="输入集群名称查找" value={this.state.displayValue||''} />
|
||||||
</div>
|
</div>
|
||||||
<InfiniteScroll
|
<InfiniteScroll
|
||||||
|
@ -89,8 +82,8 @@ class DropdownSelect extends React.Component{
|
||||||
useWindow={false}
|
useWindow={false}
|
||||||
>
|
>
|
||||||
<div className={styles.dslist}>
|
<div className={styles.dslist}>
|
||||||
{(!this.props.data || !this.props.data.length)&& <div style={{display:'flex', justifyContent:'center', alignItems: 'center', height:50}}>匹配不到集群(匹配规则为前缀匹配)</div>}
|
{(!this.state.data || !this.state.data.length)&& <div style={{display:'flex', justifyContent:'center', alignItems: 'center', height:50}}>匹配不到集群(匹配规则为前缀匹配)</div>}
|
||||||
{(this.props.data || []).map((item)=>{
|
{(this.state.data || []).map((item)=>{
|
||||||
// return <div className={styles.item}>
|
// return <div className={styles.item}>
|
||||||
// <Button key={item[labelField]}
|
// <Button key={item[labelField]}
|
||||||
// onClick={() => {
|
// onClick={() => {
|
||||||
|
|
|
@ -40,6 +40,14 @@
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
max-height: 300px;
|
max-height: 300px;
|
||||||
|
position: relative;
|
||||||
|
.filter{
|
||||||
|
position: sticky;
|
||||||
|
background-color: #fff;
|
||||||
|
top:0;
|
||||||
|
left:0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.loadingContainer {
|
.loadingContainer {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
|
@ -17,7 +17,7 @@ export default class GlobalHeader extends PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
this.props.onFetchClusterList('', 200)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
@ -40,6 +40,9 @@ export default class GlobalHeader extends PureComponent {
|
||||||
};
|
};
|
||||||
render() {
|
render() {
|
||||||
const { collapsed, isMobile, logo, clusterVisible, clusterList, selectedCluster } = this.props;
|
const { collapsed, isMobile, logo, clusterVisible, clusterList, selectedCluster } = this.props;
|
||||||
|
if(clusterList.length == 0){
|
||||||
|
return null
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div className={styles.header}>
|
<div className={styles.header}>
|
||||||
{isMobile && (
|
{isMobile && (
|
||||||
|
@ -85,24 +88,6 @@ export default class GlobalHeader extends PureComponent {
|
||||||
|
|
||||||
}}
|
}}
|
||||||
size={56}
|
size={56}
|
||||||
fetchData={
|
|
||||||
this.props.onFetchClusterList
|
|
||||||
// (from, size)=>{
|
|
||||||
// return new Promise(resolve => {
|
|
||||||
// setTimeout(() => {
|
|
||||||
// let start = from;
|
|
||||||
// let data =[]
|
|
||||||
// for(let i = start + 1; i<start+size+1; i++){
|
|
||||||
// if(start+size > 56){
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// data.push('cluster'+i)
|
|
||||||
// }
|
|
||||||
// resolve(data)
|
|
||||||
// }, 2000)
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
data={clusterList}/>
|
data={clusterList}/>
|
||||||
<RightContent {...this.props} />
|
<RightContent {...this.props} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -0,0 +1,127 @@
|
||||||
|
import Tabs from './index';
|
||||||
|
import { DndProvider, DragSource, DropTarget } from 'react-dnd';
|
||||||
|
import { HTML5Backend } from 'react-dnd-html5-backend';
|
||||||
|
|
||||||
|
const { TabPane } = Tabs;
|
||||||
|
|
||||||
|
// Drag & Drop node
|
||||||
|
class TabNode extends React.Component {
|
||||||
|
render() {
|
||||||
|
const { connectDragSource, connectDropTarget, children } = this.props;
|
||||||
|
|
||||||
|
return connectDragSource(connectDropTarget(children));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const cardTarget = {
|
||||||
|
drop(props, monitor) {
|
||||||
|
const dragKey = monitor.getItem().index;
|
||||||
|
const hoverKey = props.index;
|
||||||
|
|
||||||
|
if (dragKey === hoverKey) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
props.moveTabNode(dragKey, hoverKey);
|
||||||
|
monitor.getItem().index = hoverKey;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const cardSource = {
|
||||||
|
beginDrag(props) {
|
||||||
|
return {
|
||||||
|
id: props.id,
|
||||||
|
index: props.index,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const WrapTabNode = DropTarget('DND_NODE', cardTarget, connect => ({
|
||||||
|
connectDropTarget: connect.dropTarget(),
|
||||||
|
}))(
|
||||||
|
DragSource('DND_NODE', cardSource, (connect, monitor) => ({
|
||||||
|
connectDragSource: connect.dragSource(),
|
||||||
|
isDragging: monitor.isDragging(),
|
||||||
|
}))(TabNode),
|
||||||
|
);
|
||||||
|
|
||||||
|
export class DraggableTabs extends React.Component {
|
||||||
|
constructor(props){
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
order: props.initialOrder || [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
moveTabNode = (dragKey, hoverKey) => {
|
||||||
|
const newOrder = this.state.order.slice();
|
||||||
|
const { children } = this.props;
|
||||||
|
|
||||||
|
React.Children.forEach(children, c => {
|
||||||
|
if (newOrder.indexOf(c.key) === -1) {
|
||||||
|
newOrder.push(c.key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const dragIndex = newOrder.indexOf(dragKey);
|
||||||
|
const hoverIndex = newOrder.indexOf(hoverKey);
|
||||||
|
|
||||||
|
newOrder.splice(dragIndex, 1);
|
||||||
|
newOrder.splice(hoverIndex, 0, dragKey);
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
order: newOrder,
|
||||||
|
});
|
||||||
|
if(typeof this.props.onTabNodeMoved == 'function'){
|
||||||
|
this.props.onTabNodeMoved(newOrder)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
renderTabBar = (props, DefaultTabBar) => (
|
||||||
|
<DefaultTabBar {...props}>
|
||||||
|
{node => (
|
||||||
|
<WrapTabNode key={node.key} index={node.key} moveTabNode={this.moveTabNode}>
|
||||||
|
{node}
|
||||||
|
</WrapTabNode>
|
||||||
|
)}
|
||||||
|
</DefaultTabBar>
|
||||||
|
);
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { order } = this.state;
|
||||||
|
const { children } = this.props;
|
||||||
|
|
||||||
|
const tabs = [];
|
||||||
|
React.Children.forEach(children, c => {
|
||||||
|
tabs.push(c);
|
||||||
|
});
|
||||||
|
|
||||||
|
const orderTabs = tabs.slice().sort((a, b) => {
|
||||||
|
const orderA = order.indexOf(a.key);
|
||||||
|
const orderB = order.indexOf(b.key);
|
||||||
|
|
||||||
|
if (orderA !== -1 && orderB !== -1) {
|
||||||
|
return orderA - orderB;
|
||||||
|
}
|
||||||
|
if (orderA !== -1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (orderB !== -1) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ia = tabs.indexOf(a);
|
||||||
|
const ib = tabs.indexOf(b);
|
||||||
|
|
||||||
|
return ia - ib;
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DndProvider backend={HTML5Backend}>
|
||||||
|
<Tabs renderTabBar={this.renderTabBar} {...this.props}>
|
||||||
|
{orderTabs}
|
||||||
|
</Tabs>
|
||||||
|
</DndProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import ScrollableInkTabBar from 'rc-tabs/lib/ScrollableInkTabBar';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { TabsProps } from './index';
|
||||||
|
import {Icon} from 'antd';
|
||||||
|
|
||||||
|
export default class TabBar extends React.Component<TabsProps> {
|
||||||
|
static defaultProps = {
|
||||||
|
animated: true,
|
||||||
|
type: 'line',
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
tabBarStyle,
|
||||||
|
animated,
|
||||||
|
renderTabBar,
|
||||||
|
tabBarExtraContent,
|
||||||
|
tabPosition,
|
||||||
|
prefixCls,
|
||||||
|
className,
|
||||||
|
size,
|
||||||
|
type,
|
||||||
|
} = this.props;
|
||||||
|
const inkBarAnimated = typeof animated === 'object' ? animated.inkBar : animated;
|
||||||
|
|
||||||
|
const isVertical = tabPosition === 'left' || tabPosition === 'right';
|
||||||
|
const prevIconType = isVertical ? 'up' : 'left';
|
||||||
|
const nextIconType = isVertical ? 'down' : 'right';
|
||||||
|
const prevIcon = (
|
||||||
|
<span className={`${prefixCls}-tab-prev-icon`}>
|
||||||
|
<Icon type={prevIconType} className={`${prefixCls}-tab-prev-icon-target`} />
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
const nextIcon = (
|
||||||
|
<span className={`${prefixCls}-tab-next-icon`}>
|
||||||
|
<Icon type={nextIconType} className={`${prefixCls}-tab-next-icon-target`} />
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Additional className for style usage
|
||||||
|
const cls: string = classNames(
|
||||||
|
`${prefixCls}-${tabPosition}-bar`,
|
||||||
|
{
|
||||||
|
[`${prefixCls}-${size}-bar`]: !!size,
|
||||||
|
[`${prefixCls}-card-bar`]: type && type.indexOf('card') >= 0,
|
||||||
|
},
|
||||||
|
className,
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderProps = {
|
||||||
|
...this.props,
|
||||||
|
children: null,
|
||||||
|
inkBarAnimated,
|
||||||
|
extraContent: tabBarExtraContent,
|
||||||
|
style: tabBarStyle,
|
||||||
|
prevIcon,
|
||||||
|
nextIcon,
|
||||||
|
className: cls,
|
||||||
|
};
|
||||||
|
|
||||||
|
let RenderTabBar: React.ReactElement<any>;
|
||||||
|
|
||||||
|
if (renderTabBar) {
|
||||||
|
RenderTabBar = renderTabBar(renderProps, ScrollableInkTabBar);
|
||||||
|
} else {
|
||||||
|
RenderTabBar = <ScrollableInkTabBar {...renderProps} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return React.cloneElement(RenderTabBar);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,193 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import * as ReactDOM from 'react-dom';
|
||||||
|
import RcTabs, { TabPane } from 'rc-tabs';
|
||||||
|
import TabContent from 'rc-tabs/lib/TabContent';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import omit from 'omit.js';
|
||||||
|
import TabBar from './TabBar';
|
||||||
|
import {Icon} from 'antd';
|
||||||
|
import { ConfigConsumer, ConfigConsumerProps } from 'antd/lib/config-provider';
|
||||||
|
import './style/flex-bar.scss';
|
||||||
|
|
||||||
|
export type TabsType = 'line' | 'card' | 'editable-card';
|
||||||
|
export type TabsPosition = 'top' | 'right' | 'bottom' | 'left';
|
||||||
|
|
||||||
|
export interface TabsProps {
|
||||||
|
activeKey?: string;
|
||||||
|
defaultActiveKey?: string;
|
||||||
|
hideAdd?: boolean;
|
||||||
|
onChange?: (activeKey: string) => void;
|
||||||
|
onTabClick?: Function;
|
||||||
|
onPrevClick?: React.MouseEventHandler<HTMLElement>;
|
||||||
|
onNextClick?: React.MouseEventHandler<HTMLElement>;
|
||||||
|
tabBarExtraContent?: React.ReactNode | {left?: React.ReactNode, right?: React.ReactNode, append?: React.ReactNode} | null;
|
||||||
|
tabBarStyle?: React.CSSProperties;
|
||||||
|
type?: TabsType;
|
||||||
|
tabPosition?: TabsPosition;
|
||||||
|
onEdit?: (targetKey: string | React.MouseEvent<HTMLElement>, action: 'add' | 'remove') => void;
|
||||||
|
size?: 'large' | 'default' | 'small';
|
||||||
|
style?: React.CSSProperties;
|
||||||
|
prefixCls?: string;
|
||||||
|
className?: string;
|
||||||
|
animated?: boolean | { inkBar: boolean; tabPane: boolean };
|
||||||
|
tabBarGutter?: number;
|
||||||
|
renderTabBar?: (
|
||||||
|
props: TabsProps,
|
||||||
|
DefaultTabBar: React.ComponentClass<any>,
|
||||||
|
) => React.ReactElement<any>;
|
||||||
|
destroyInactiveTabPane?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tabs
|
||||||
|
export interface TabPaneProps {
|
||||||
|
/** 选项卡头显示文字 */
|
||||||
|
tab?: React.ReactNode | string;
|
||||||
|
style?: React.CSSProperties;
|
||||||
|
closable?: boolean;
|
||||||
|
className?: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
forceRender?: boolean;
|
||||||
|
key?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Tabs extends React.Component<TabsProps, any> {
|
||||||
|
static TabPane = TabPane as React.ClassicComponentClass<TabPaneProps>;
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
hideAdd: false,
|
||||||
|
tabPosition: 'top' as TabsPosition,
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
// const NO_FLEX = ' no-flex';
|
||||||
|
// const tabNode = ReactDOM.findDOMNode(this) as Element;
|
||||||
|
// if (tabNode && !isFlexSupported && tabNode.className.indexOf(NO_FLEX) === -1) {
|
||||||
|
// tabNode.className += NO_FLEX;
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
removeTab = (targetKey: string, e: React.MouseEvent<HTMLElement>) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (!targetKey) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { onEdit } = this.props;
|
||||||
|
if (onEdit) {
|
||||||
|
onEdit(targetKey, 'remove');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
handleChange = (activeKey: string) => {
|
||||||
|
const { onChange } = this.props;
|
||||||
|
if (onChange) {
|
||||||
|
onChange(activeKey);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
createNewTab = (targetKey: React.MouseEvent<HTMLElement>) => {
|
||||||
|
const { onEdit } = this.props;
|
||||||
|
if (onEdit) {
|
||||||
|
onEdit(targetKey, 'add');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
renderTabs = ({ getPrefixCls }: ConfigConsumerProps) => {
|
||||||
|
const {
|
||||||
|
prefixCls: customizePrefixCls,
|
||||||
|
className = '',
|
||||||
|
size,
|
||||||
|
type = 'line',
|
||||||
|
tabPosition,
|
||||||
|
children,
|
||||||
|
animated = true,
|
||||||
|
hideAdd,
|
||||||
|
} = this.props;
|
||||||
|
let { tabBarExtraContent } = this.props;
|
||||||
|
|
||||||
|
let tabPaneAnimated = typeof animated === 'object' ? animated.tabPane : animated;
|
||||||
|
|
||||||
|
// card tabs should not have animation
|
||||||
|
if (type !== 'line') {
|
||||||
|
tabPaneAnimated = 'animated' in this.props ? tabPaneAnimated : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const prefixCls = getPrefixCls('tabs', customizePrefixCls);
|
||||||
|
const cls = classNames(className, {
|
||||||
|
[`${prefixCls}-vertical`]: tabPosition === 'left' || tabPosition === 'right',
|
||||||
|
[`${prefixCls}-${size}`]: !!size,
|
||||||
|
[`${prefixCls}-card`]: type.indexOf('card') >= 0,
|
||||||
|
[`${prefixCls}-${type}`]: true,
|
||||||
|
[`${prefixCls}-no-animation`]: !tabPaneAnimated,
|
||||||
|
});
|
||||||
|
// only card type tabs can be added and closed
|
||||||
|
let childrenWithClose: React.ReactElement<any>[] = [];
|
||||||
|
if (type === 'editable-card') {
|
||||||
|
childrenWithClose = [];
|
||||||
|
React.Children.forEach(children as React.ReactNode, (child, index) => {
|
||||||
|
if (!React.isValidElement(child)) return child;
|
||||||
|
let { closable } = child.props;
|
||||||
|
closable = typeof closable === 'undefined' ? true : closable;
|
||||||
|
const closeIcon = closable ? (
|
||||||
|
<Icon
|
||||||
|
type="close"
|
||||||
|
className={`${prefixCls}-close-x`}
|
||||||
|
onClick={e => this.removeTab(child.key as string, e)}
|
||||||
|
/>
|
||||||
|
) : null;
|
||||||
|
childrenWithClose.push(
|
||||||
|
React.cloneElement(child, {
|
||||||
|
tab: (
|
||||||
|
<div className={closable ? undefined : `${prefixCls}-tab-unclosable`}>
|
||||||
|
{child.props.tab}
|
||||||
|
{closeIcon}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
key: child.key || index,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
// Add new tab handler
|
||||||
|
// if (!hideAdd) {
|
||||||
|
// tabBarExtraContent = (
|
||||||
|
// <span>
|
||||||
|
// <Icon type="plus" className={`${prefixCls}-new-tab`} onClick={this.createNewTab} />
|
||||||
|
// {tabBarExtraContent}
|
||||||
|
// </span>
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
const newTabBarExtraContent: React.ReactNode = tabBarExtraContent ? (
|
||||||
|
<><div className={`${prefixCls}-extra-content ant-tabs-extra-append`}>{tabBarExtraContent?.append}</div><div className={`${prefixCls}-extra-content ant-tabs-extra-left`} style={{float:'left', margin:'0 10px'}}>{tabBarExtraContent.left}</div><div style={{float:'right', marginRight:10}} className={`${prefixCls}-extra-content ant-tabs-extra-right`}>{tabBarExtraContent.right}</div></>
|
||||||
|
) : null;
|
||||||
|
|
||||||
|
const { ...tabBarProps } = this.props;
|
||||||
|
const contentCls: string = classNames(
|
||||||
|
`${prefixCls}-${tabPosition}-content`,
|
||||||
|
type.indexOf('card') >= 0 && `${prefixCls}-card-content`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RcTabs
|
||||||
|
{...this.props}
|
||||||
|
prefixCls={prefixCls}
|
||||||
|
className={cls}
|
||||||
|
tabBarPosition={tabPosition}
|
||||||
|
renderTabBar={() => (
|
||||||
|
<TabBar {...omit(tabBarProps, ['className'])} tabBarExtraContent={newTabBarExtraContent} />
|
||||||
|
)}
|
||||||
|
renderTabContent={() => (
|
||||||
|
<TabContent className={contentCls} animated={tabPaneAnimated} animatedWithMargin />
|
||||||
|
)}
|
||||||
|
onChange={this.handleChange}
|
||||||
|
>
|
||||||
|
{childrenWithClose.length > 0 ? childrenWithClose : children}
|
||||||
|
</RcTabs>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return <div className="flex-tabbar"><ConfigConsumer>{this.renderTabs}</ConfigConsumer></div>;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
.ant-tabs-top-bar{
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.ant-tabs-extra-left{
|
||||||
|
order: 1;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
.ant-tabs-nav-container{
|
||||||
|
order: 2;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
|
||||||
|
}
|
||||||
|
.flex-tabbar .ant-tabs-top-bar .ant-tabs-nav-container{
|
||||||
|
max-width: calc(100% - 140px);
|
||||||
|
}
|
||||||
|
.ant-tabs-extra-append{
|
||||||
|
order: 3;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
.ant-tabs-extra-right{
|
||||||
|
order: 4;
|
||||||
|
margin-left: auto;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
|
@ -32,4 +32,12 @@
|
||||||
background-color: #cce1f0;
|
background-color: #cce1f0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.ant-tabs-right-content{
|
||||||
|
padding-right: 0px;
|
||||||
|
}
|
||||||
|
.ant-tabs-right-bar .ant-tabs-tab{
|
||||||
|
padding: 8px 10px;
|
||||||
|
writing-mode: vertical-lr;
|
||||||
|
height: max-content;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,9 +12,10 @@ import {EditorContextProvider} from '../contexts/editor_context/editor_context';
|
||||||
import { ServicesContextProvider } from '../contexts/services_context';
|
import { ServicesContextProvider } from '../contexts/services_context';
|
||||||
import { createHistory, History, createStorage, createSettings } from '../services';
|
import { createHistory, History, createStorage, createSettings } from '../services';
|
||||||
import { create } from '../storage/local_storage_object_client';
|
import { create } from '../storage/local_storage_object_client';
|
||||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
import { EuiFlexGroup, EuiFlexItem,EuiCodeBlock } from '@elastic/eui';
|
||||||
import {RequestStatusBar} from './request_status_bar';
|
import {RequestStatusBar} from './request_status_bar';
|
||||||
import useEventListener from '@/lib/hooks/use_event_listener';
|
import useEventListener from '@/lib/hooks/use_event_listener';
|
||||||
|
import {Tabs} from 'antd';
|
||||||
|
|
||||||
interface props {
|
interface props {
|
||||||
selectedCluster: any;
|
selectedCluster: any;
|
||||||
|
@ -78,7 +79,28 @@ const ConsoleWrapper = ({
|
||||||
</div>
|
</div>
|
||||||
</Panel>
|
</Panel>
|
||||||
<Panel style={{ height: '100%', position: 'relative', minWidth: PANEL_MIN_WIDTH, paddingBottom:30 }} initialWidth={INITIAL_PANEL_WIDTH}>
|
<Panel style={{ height: '100%', position: 'relative', minWidth: PANEL_MIN_WIDTH, paddingBottom:30 }} initialWidth={INITIAL_PANEL_WIDTH}>
|
||||||
<ConsoleOutput clusterID={selectedCluster.id} />
|
<Tabs tabPosition='right' style={{height:'100%', width:'100%'}} size="small">
|
||||||
|
<Tabs.TabPane tab="Result" key="result">
|
||||||
|
<div style={{height:height-30}}>
|
||||||
|
<ConsoleOutput clusterID={selectedCluster.id} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</Tabs.TabPane>
|
||||||
|
<Tabs.TabPane tab="Headers" key="headers">
|
||||||
|
<Tabs>
|
||||||
|
<Tabs.TabPane tab="Request" key="1">
|
||||||
|
<EuiCodeBlock language="text" isCopyable paddingSize="s">
|
||||||
|
{lastDatum?.request.header}
|
||||||
|
</EuiCodeBlock>
|
||||||
|
</Tabs.TabPane>
|
||||||
|
<Tabs.TabPane tab="Response" key="2">
|
||||||
|
<EuiCodeBlock language="text" isCopyable paddingSize="s">
|
||||||
|
{lastDatum?.response.header}
|
||||||
|
</EuiCodeBlock>
|
||||||
|
</Tabs.TabPane>
|
||||||
|
</Tabs>
|
||||||
|
</Tabs.TabPane>
|
||||||
|
</Tabs>
|
||||||
<div style={{background:'#fff', position:'absolute', right:0, bottom:0, width: '100%', height:30, zIndex:1001, borderTop: '1px solid #eee'}}>
|
<div style={{background:'#fff', position:'absolute', right:0, bottom:0, width: '100%', height:30, zIndex:1001, borderTop: '1px solid #eee'}}>
|
||||||
<RequestStatusBar
|
<RequestStatusBar
|
||||||
requestInProgress={requestInProgress}
|
requestInProgress={requestInProgress}
|
||||||
|
|
|
@ -89,7 +89,6 @@ export const RequestStatusBar = ({
|
||||||
<EuiBadge color="default">{selectedCluster.version}</EuiBadge>
|
<EuiBadge color="default">{selectedCluster.version}</EuiBadge>
|
||||||
</div>
|
</div>
|
||||||
</div>);
|
</div>);
|
||||||
const [headerInfoVisible, setHeaderInfoVisible] = React.useState(false)
|
|
||||||
|
|
||||||
if (requestInProgress) {
|
if (requestInProgress) {
|
||||||
content = (
|
content = (
|
||||||
|
@ -140,12 +139,6 @@ const [headerInfoVisible, setHeaderInfoVisible] = React.useState(false)
|
||||||
</EuiText>
|
</EuiText>
|
||||||
</EuiToolTip>
|
</EuiToolTip>
|
||||||
</div>
|
</div>
|
||||||
<div className="info-item">
|
|
||||||
{/* <Button type="link" onClick={()=>{setHeaderInfoVisible(true)}}> */}
|
|
||||||
{/* <FormattedMessage id="console.request.headers"/> */}
|
|
||||||
Headers
|
|
||||||
{/* </Button> */}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -154,32 +147,7 @@ const [headerInfoVisible, setHeaderInfoVisible] = React.useState(false)
|
||||||
return (
|
return (
|
||||||
<div className="request-status-bar">
|
<div className="request-status-bar">
|
||||||
{left? <div className="bar-item">{clusterContent}</div>:
|
{left? <div className="bar-item">{clusterContent}</div>:
|
||||||
[<div className="bar-item">{content}</div>,
|
[<div className="bar-item">{content}</div>,]
|
||||||
<Drawer title="Request header info"
|
|
||||||
style={{zIndex:1004}}
|
|
||||||
width={520}
|
|
||||||
mask={false}
|
|
||||||
// getContainer={container.current}
|
|
||||||
destroyOnClose={true}
|
|
||||||
visible={headerInfoVisible}
|
|
||||||
onClose={()=>{setHeaderInfoVisible(false)}}
|
|
||||||
>
|
|
||||||
<Tabs>
|
|
||||||
<Tabs.TabPane tab="Request" key="1">
|
|
||||||
<div>
|
|
||||||
<EuiCodeBlock language="text" isCopyable paddingSize="s">
|
|
||||||
{requestResult?.requestHeader}
|
|
||||||
</EuiCodeBlock>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</Tabs.TabPane>
|
|
||||||
<Tabs.TabPane tab="Response" key="2">
|
|
||||||
<EuiCodeBlock language="text" isCopyable paddingSize="s">
|
|
||||||
{requestResult?.responseHeader}
|
|
||||||
</EuiCodeBlock>
|
|
||||||
</Tabs.TabPane>
|
|
||||||
</Tabs>
|
|
||||||
</Drawer>]
|
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import Console from '../../components/kibana/console/components/Console';
|
import Console from '../../components/kibana/console/components/Console';
|
||||||
import {connect} from 'dva';
|
import {connect} from 'dva';
|
||||||
import {Button, Icon, Menu, Dropdown, Tabs} from 'antd';
|
import {Button, Icon, Menu, Dropdown, } from 'antd';
|
||||||
// import Tabs from '@/components/infini/tabs';
|
import Tabs from '@/components/infini/tabs';
|
||||||
|
import {DraggableTabs} from '@/components/infini/tabs/DraggableTabs';
|
||||||
import {useState, useReducer, useCallback, useEffect, useMemo, useRef, useLayoutEffect} from 'react';
|
import {useState, useReducer, useCallback, useEffect, useMemo, useRef, useLayoutEffect} from 'react';
|
||||||
import {useLocalStorage} from '@/lib/hooks/storage';
|
import {useLocalStorage} from '@/lib/hooks/storage';
|
||||||
import {setClusterID} from '../../components/kibana/console/modules/mappings/mappings';
|
import {setClusterID} from '../../components/kibana/console/modules/mappings/mappings';
|
||||||
|
@ -9,6 +10,7 @@ import {TabTitle} from './console_tab_title';
|
||||||
import '@/assets/utility.scss';
|
import '@/assets/utility.scss';
|
||||||
import { Resizable } from "re-resizable";
|
import { Resizable } from "re-resizable";
|
||||||
import {ResizeBar} from '@/components/infini/resize_bar';
|
import {ResizeBar} from '@/components/infini/resize_bar';
|
||||||
|
import NewTabMenu from './NewTabMenu';
|
||||||
|
|
||||||
const { TabPane } = Tabs;
|
const { TabPane } = Tabs;
|
||||||
|
|
||||||
|
@ -94,6 +96,11 @@ const consoleTabReducer = (state: any, action: any) => {
|
||||||
panes,
|
panes,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
case 'saveOrder':
|
||||||
|
newState = ({
|
||||||
|
...state,
|
||||||
|
order: action.payload.order,
|
||||||
|
});
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
// setLocalState(newState);
|
// setLocalState(newState);
|
||||||
|
@ -123,8 +130,8 @@ export const ConsoleUI = ({selectedCluster,
|
||||||
return cm;
|
return cm;
|
||||||
}
|
}
|
||||||
(clusterList || []).map((cluster: any)=>{
|
(clusterList || []).map((cluster: any)=>{
|
||||||
cluster.status = clusterStatus[cluster.id].health?.status;
|
cluster.status = clusterStatus[cluster.id]?.health?.status;
|
||||||
if(!clusterStatus[cluster.id].available){
|
if(!clusterStatus[cluster.id]?.available){
|
||||||
cluster.status = 'unavailable';
|
cluster.status = 'unavailable';
|
||||||
}
|
}
|
||||||
cm[cluster.id] = cluster;
|
cm[cluster.id] = cluster;
|
||||||
|
@ -184,7 +191,7 @@ export const ConsoleUI = ({selectedCluster,
|
||||||
};
|
};
|
||||||
|
|
||||||
const newTabClick = useCallback((param: any)=>{
|
const newTabClick = useCallback((param: any)=>{
|
||||||
const cluster = clusterList.find(item=>item.id == param.key);
|
const cluster = clusterList.find(item=>item.id == param.id);
|
||||||
if(!cluster){
|
if(!cluster){
|
||||||
console.log('cluster not found')
|
console.log('cluster not found')
|
||||||
return;
|
return;
|
||||||
|
@ -198,11 +205,16 @@ export const ConsoleUI = ({selectedCluster,
|
||||||
},[clusterList])
|
},[clusterList])
|
||||||
|
|
||||||
const menu = (
|
const menu = (
|
||||||
<Menu onClick={newTabClick}>
|
// <Menu onClick={newTabClick}>
|
||||||
{(clusterList||[]).map((cluster:any)=>{
|
// {(clusterList||[]).map((cluster:any)=>{
|
||||||
return <Menu.Item key={cluster.id}>{cluster.name}</Menu.Item>
|
// return <Menu.Item key={cluster.id}>{cluster.name}</Menu.Item>
|
||||||
})}
|
// })}
|
||||||
</Menu>
|
// </Menu>
|
||||||
|
<NewTabMenu data={clusterList}
|
||||||
|
onItemClick={newTabClick}
|
||||||
|
clusterStatus={clusterStatus}
|
||||||
|
size={10}
|
||||||
|
width="300px"/>
|
||||||
);
|
);
|
||||||
|
|
||||||
const rootRef = useRef(null);
|
const rootRef = useRef(null);
|
||||||
|
@ -220,26 +232,35 @@ export const ConsoleUI = ({selectedCluster,
|
||||||
setIsFullscreen(!isFullscreen)
|
setIsFullscreen(!isFullscreen)
|
||||||
}
|
}
|
||||||
|
|
||||||
const tabBarExtra =(
|
const tabBarExtra ={
|
||||||
|
left: <div>
|
||||||
|
{minimize? <Button size="small" onClick={onMinimizeClick} style={{marginLeft:5}} title="Close">
|
||||||
|
{/* <Icon type="minus"/> */}
|
||||||
|
<Icon type="poweroff"/>
|
||||||
|
</Button>:null}
|
||||||
|
</div>,
|
||||||
|
right:(
|
||||||
<div>
|
<div>
|
||||||
<Dropdown overlay={menu}>
|
|
||||||
<Button size="small" style={{marginRight:5}}>
|
|
||||||
<Icon type="plus"/>
|
|
||||||
</Button>
|
|
||||||
</Dropdown>
|
|
||||||
{isFullscreen?
|
{isFullscreen?
|
||||||
<Button size="small" onClick={fullscreenClick}>
|
<Button size="small" onClick={fullscreenClick}>
|
||||||
<Icon type="fullscreen-exit"/>
|
<Icon type="fullscreen-exit"/>
|
||||||
</Button>:
|
</Button>:
|
||||||
<Button size="small" onClick={fullscreenClick}>
|
<Button size="small" onClick={fullscreenClick} title="Fullscreen">
|
||||||
<Icon type="fullscreen"/>
|
<Icon type="fullscreen"/>
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
{minimize? <Button size="small" onClick={onMinimizeClick} style={{marginLeft:5}}>
|
|
||||||
<Icon type="minus"/>
|
|
||||||
</Button>:null}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
),
|
||||||
|
append:(
|
||||||
|
<div>
|
||||||
|
<Dropdown overlay={menu} placement="bottomLeft">
|
||||||
|
<Button size="small" style={{marginRight:5}}>
|
||||||
|
<Icon type="plus"/>
|
||||||
|
</Button>
|
||||||
|
</Dropdown>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
setClusterID(tabState.activeKey?.split(':')[0]);
|
setClusterID(tabState.activeKey?.split(':')[0]);
|
||||||
const panes = tabState.panes.filter((pane: any)=>{
|
const panes = tabState.panes.filter((pane: any)=>{
|
||||||
|
@ -268,6 +289,14 @@ export const ConsoleUI = ({selectedCluster,
|
||||||
const enableWindowScroll = ()=>{
|
const enableWindowScroll = ()=>{
|
||||||
document.body.style.overflow = '';
|
document.body.style.overflow = '';
|
||||||
}
|
}
|
||||||
|
const onTabNodeMoved=(newOrder:string[])=>{
|
||||||
|
dispatch({
|
||||||
|
type:'saveOrder',
|
||||||
|
payload: {
|
||||||
|
order: newOrder,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -275,7 +304,7 @@ export const ConsoleUI = ({selectedCluster,
|
||||||
defaultSize={{
|
defaultSize={{
|
||||||
height: editorHeight||'50vh'
|
height: editorHeight||'50vh'
|
||||||
}}
|
}}
|
||||||
minHeight={200}
|
minHeight={70}
|
||||||
maxHeight="100vh"
|
maxHeight="100vh"
|
||||||
handleComponent={{ top: <ResizeBar/> }}
|
handleComponent={{ top: <ResizeBar/> }}
|
||||||
onResize={onResize}
|
onResize={onResize}
|
||||||
|
@ -294,13 +323,15 @@ export const ConsoleUI = ({selectedCluster,
|
||||||
onMouseOut={enableWindowScroll}
|
onMouseOut={enableWindowScroll}
|
||||||
id="console"
|
id="console"
|
||||||
ref={rootRef} >
|
ref={rootRef} >
|
||||||
<Tabs
|
<DraggableTabs
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
activeKey={tabState.activeKey}
|
activeKey={tabState.activeKey}
|
||||||
type="editable-card"
|
type="editable-card"
|
||||||
onEdit={onEdit}
|
onEdit={onEdit}
|
||||||
hideAdd
|
hideAdd
|
||||||
|
initialOrder={tabState.order}
|
||||||
tabBarExtraContent={tabBarExtra}
|
tabBarExtraContent={tabBarExtra}
|
||||||
|
onTabNodeMoved={onTabNodeMoved}
|
||||||
>
|
>
|
||||||
{panes.map(pane => (
|
{panes.map(pane => (
|
||||||
<TabPane tab={<TabTitle title={pane.title} onTitleChange={(title)=>{saveTitle(pane.key, title)}}/>} key={pane.key} closable={pane.closable}>
|
<TabPane tab={<TabTitle title={pane.title} onTitleChange={(title)=>{saveTitle(pane.key, title)}}/>} key={pane.key} closable={pane.closable}>
|
||||||
|
@ -308,7 +339,7 @@ export const ConsoleUI = ({selectedCluster,
|
||||||
{/* {pane.content} */}
|
{/* {pane.content} */}
|
||||||
</TabPane>
|
</TabPane>
|
||||||
))}
|
))}
|
||||||
</Tabs>
|
</DraggableTabs>
|
||||||
</div>
|
</div>
|
||||||
</Resizable>
|
</Resizable>
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
import { Button, Dropdown, List, Spin, message, Icon, Input } from 'antd';
|
||||||
|
import * as React from 'react';
|
||||||
|
import InfiniteScroll from 'react-infinite-scroller';
|
||||||
|
import styles from '@/components/GlobalHeader/DropdownSelect.less';
|
||||||
|
import _ from "lodash";
|
||||||
|
import {DropdownItem} from '@/components/GlobalHeader/DropdownItem';
|
||||||
|
import {HealthStatusCircle} from '@/components/infini/health_status_circle'
|
||||||
|
|
||||||
|
class NewTabMenu extends React.Component{
|
||||||
|
handleItemClick = (item)=>{
|
||||||
|
const onItemClick = this.props.onItemClick;
|
||||||
|
if(onItemClick && typeof onItemClick == 'function'){
|
||||||
|
onItemClick(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
constructor(props){
|
||||||
|
super(props);
|
||||||
|
this.state={
|
||||||
|
loading: false,
|
||||||
|
hasMore: (props.data || []).length > props.size,
|
||||||
|
data: (props.data || []).slice(0, props.size || 10),
|
||||||
|
initialLoad: true,
|
||||||
|
dataSource: [...props.data],
|
||||||
|
dataSourceKey: 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount(){
|
||||||
|
}
|
||||||
|
handleInfiniteOnLoad = (current) => {
|
||||||
|
let {size } = this.props;
|
||||||
|
let targetLength = current * size;
|
||||||
|
let {hasMore, dataSource} = this.state;
|
||||||
|
if(dataSource.length < targetLength){
|
||||||
|
targetLength = dataSource.length;
|
||||||
|
hasMore = false
|
||||||
|
}
|
||||||
|
const newData = this.state.dataSource.slice(0, targetLength);
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
data: newData,
|
||||||
|
hasMore: hasMore,
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
handleInputChange = (e) =>{
|
||||||
|
const name = e.target.value;
|
||||||
|
const newData = this.props.data.filter(item=>{
|
||||||
|
return item.name.includes(name);
|
||||||
|
});
|
||||||
|
this.setState({
|
||||||
|
displayValue: name,
|
||||||
|
dataSource: newData,
|
||||||
|
data: newData,
|
||||||
|
hasMore: newData.length > this.props.size,
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
render(){
|
||||||
|
const {clusterStatus} = this.props;
|
||||||
|
return (<div className={styles.dropmenu} style={{width: this.props.width}}>
|
||||||
|
<div className={styles.infiniteContainer} style={{height: this.props.height}}>
|
||||||
|
<div className={styles.filter} style={{paddingTop: 10, paddingBottom:0}}>
|
||||||
|
<input className={styles['btn-ds']} style={{outline:'none'}} onChange={this.handleInputChange} placeholder="输入集群名称查找" value={this.state.displayValue||''} />
|
||||||
|
</div>
|
||||||
|
<InfiniteScroll
|
||||||
|
initialLoad={this.state.initialLoad}
|
||||||
|
loadMore={this.handleInfiniteOnLoad}
|
||||||
|
hasMore={!this.state.loading && this.state.hasMore}
|
||||||
|
useWindow={false}
|
||||||
|
>
|
||||||
|
<div className={styles.dslist}>
|
||||||
|
{(!this.state.data || !this.state.data.length)&& <div style={{display:'flex', justifyContent:'center', alignItems: 'center', height:50}}>匹配不到集群(匹配规则为前缀匹配)</div>}
|
||||||
|
{(this.state.data || []).map((item)=>{
|
||||||
|
const cstatus = clusterStatus ? clusterStatus[item.id] : null;
|
||||||
|
return <DropdownItem key={item.id}
|
||||||
|
clusterItem={item}
|
||||||
|
clusterStatus={cstatus}
|
||||||
|
onClick={() => {
|
||||||
|
this.handleItemClick(item)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</InfiniteScroll>
|
||||||
|
</div>
|
||||||
|
{!this.state.loading && this.state.hasMore && (
|
||||||
|
<div style={{textAlign:'center', marginTop: 10, color:'#ccc'}}>
|
||||||
|
pull load more
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NewTabMenu;
|
|
@ -1,5 +1,11 @@
|
||||||
import {useState, useRef, useEffect} from 'react';
|
import {useState, useRef, useEffect} from 'react';
|
||||||
import './console_tab_title.scss';
|
import './console_tab_title.scss';
|
||||||
|
import ElasticSvg from '@/assets/elasticsearch.svg';
|
||||||
|
import {Icon} from 'antd';
|
||||||
|
|
||||||
|
const ElasticIcon = () => (
|
||||||
|
<img height="14px" width="14px" src={ElasticSvg} />
|
||||||
|
);
|
||||||
|
|
||||||
interface TabTitleProps {
|
interface TabTitleProps {
|
||||||
title: string,
|
title: string,
|
||||||
|
@ -28,7 +34,8 @@ export const TabTitle = ({title, onTitleChange}: TabTitleProps)=>{
|
||||||
onBlur={()=>{
|
onBlur={()=>{
|
||||||
setEditable(false)
|
setEditable(false)
|
||||||
}}
|
}}
|
||||||
onChange={onValueChange}/>:value}
|
onChange={onValueChange}/>:
|
||||||
|
<div style={{display:'flex', alignItems:'center'}}><Icon component={ElasticIcon} />{value}</div>}
|
||||||
</div>)
|
</div>)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue