modify global cluster selector and view style
This commit is contained in:
parent
b8f1297a19
commit
5a7f978fea
2
main.go
2
main.go
|
@ -4,6 +4,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
_ "expvar"
|
_ "expvar"
|
||||||
"infini.sh/framework"
|
"infini.sh/framework"
|
||||||
|
"infini.sh/framework/core/elastic"
|
||||||
"infini.sh/framework/core/env"
|
"infini.sh/framework/core/env"
|
||||||
"infini.sh/framework/core/module"
|
"infini.sh/framework/core/module"
|
||||||
"infini.sh/framework/core/orm"
|
"infini.sh/framework/core/orm"
|
||||||
|
@ -70,6 +71,7 @@ func main() {
|
||||||
}, func() {
|
}, func() {
|
||||||
orm.RegisterSchemaWithIndexName(model.Dict{}, "dict")
|
orm.RegisterSchemaWithIndexName(model.Dict{}, "dict")
|
||||||
orm.RegisterSchemaWithIndexName(model.Reindex{}, "reindex")
|
orm.RegisterSchemaWithIndexName(model.Reindex{}, "reindex")
|
||||||
|
orm.RegisterSchemaWithIndexName(elastic.IndexPattern{}, "view")
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,11 @@ const config = {
|
||||||
title: '500',
|
title: '500',
|
||||||
desc: '抱歉,服务器出错了',
|
desc: '抱歉,服务器出错了',
|
||||||
},
|
},
|
||||||
|
empty: {
|
||||||
|
img: 'https://gw.alipayobjects.com/zos/rmsportal/KpnpchXsobRgLElEozzI.svg',
|
||||||
|
title: '找不到数据',
|
||||||
|
desc: '当前集群找到相关数据',
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default config;
|
export default config;
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
import {Icon} from 'antd';
|
||||||
|
import styles from './DropdownSelect.less';
|
||||||
|
import {HealthStatusCircle, ClusterHealthStatus} from '@/components/infini/health_status_circle'
|
||||||
|
|
||||||
|
export interface ClusterItem {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
version: string
|
||||||
|
endpoint: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ClusterStatus {
|
||||||
|
cluster_available: boolean
|
||||||
|
health_status: ClusterHealthStatus
|
||||||
|
nodes_count: number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface props {
|
||||||
|
clusterItem?: ClusterItem
|
||||||
|
clusterStatus?: ClusterStatus
|
||||||
|
onClick: React.MouseEventHandler<HTMLDivElement> | undefined
|
||||||
|
isSelected: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DropdownItem = ({
|
||||||
|
clusterItem,
|
||||||
|
clusterStatus,
|
||||||
|
onClick,
|
||||||
|
isSelected
|
||||||
|
}:props)=>{
|
||||||
|
return <div className={styles["dropdown-item"] +" " + (isSelected ? styles['selected']: '')} onClick={onClick}>
|
||||||
|
<div className={styles["wrapper"]}>
|
||||||
|
{clusterStatus?.cluster_available ? <HealthStatusCircle status={clusterStatus?.health_status} />: <Icon type='close-circle' style={{width:14, height:14, color:'red',borderRadius: 14, boxShadow: '0px 0px 5px #555'}} />}
|
||||||
|
<span className={styles["name"]} >{clusterItem?.name}</span>
|
||||||
|
<div className={styles["version"]}>{clusterItem?.version}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
}
|
|
@ -1,20 +1,24 @@
|
||||||
import { Button, Dropdown, List, Spin, message, Icon } from 'antd';
|
import { Button, Dropdown, List, Spin, message, Icon, Input } from 'antd';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import InfiniteScroll from 'react-infinite-scroller';
|
import InfiniteScroll from 'react-infinite-scroller';
|
||||||
import styles from './DropdownSelect.less';
|
import styles from './DropdownSelect.less';
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
|
import {DropdownItem} from './DropdownItem';
|
||||||
|
import {HealthStatusCircle} from '@/components/infini/health_status_circle'
|
||||||
|
|
||||||
class DropdownSelect extends React.Component{
|
class DropdownSelect extends React.Component{
|
||||||
state={
|
state={
|
||||||
value: this.props.defaultValue,
|
value: this.props.defaultValue,
|
||||||
loading: false,
|
loading: false,
|
||||||
hasMore: true,
|
hasMore: true,
|
||||||
|
overlayVisible: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
handleItemClick = (item)=>{
|
handleItemClick = (item)=>{
|
||||||
let preValue = this.props.value || this.state.value;
|
let preValue = this.props.value || this.state.value;
|
||||||
this.setState({
|
this.setState({
|
||||||
value: item,
|
value: item,
|
||||||
|
overlayVisible: false,
|
||||||
},()=>{
|
},()=>{
|
||||||
let onChange = this.props.onChange;
|
let onChange = this.props.onChange;
|
||||||
if(preValue != item && onChange && typeof onChange == 'function'){
|
if(preValue != item && onChange && typeof onChange == 'function'){
|
||||||
|
@ -35,75 +39,74 @@ class DropdownSelect extends React.Component{
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
fetchData = ()=>{
|
fetchData = (name)=>{
|
||||||
let me = this;
|
let me = this;
|
||||||
const {fetchData, size} = this.props;
|
const {fetchData, size} = this.props;
|
||||||
let data = this.props.data || [];
|
let data = this.props.data || [];
|
||||||
let from = data.length;
|
return fetchData(name || '', size);
|
||||||
return fetchData(from, size);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleInfiniteOnLoad = (page) => {
|
handleInfiniteOnLoad = (name) => {
|
||||||
let { data } = this.props;
|
let { data } = this.props;
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: true,
|
loading: true,
|
||||||
})
|
})
|
||||||
this.fetchData().then((newdata)=>{
|
this.fetchData(name).then((newdata)=>{
|
||||||
let newState = {
|
let newState = {
|
||||||
loading: false,
|
loading: false,
|
||||||
};
|
};
|
||||||
if(newdata.length < this.props.size){
|
if(newdata.length < this.props.size){
|
||||||
message.info("no more data");
|
//message.info("no more data");
|
||||||
newState.hasMore = false;
|
newState.hasMore = false;
|
||||||
}
|
}
|
||||||
this.setState(newState);
|
this.setState(newState);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleInputChange = (e) =>{
|
||||||
|
const name = e.target.value;
|
||||||
|
this.setState({
|
||||||
|
displayValue: name,
|
||||||
|
})
|
||||||
|
this.handleInfiniteOnLoad(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
render(){
|
render(){
|
||||||
let me = this;
|
let me = this;
|
||||||
const {labelField} = this.props;
|
const {labelField, clusterStatus} = this.props;
|
||||||
let value = this.props.value || this.state.value;
|
let value = this.props.value || this.state.value;
|
||||||
|
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}}>
|
||||||
|
<input className={styles['btn-ds']} style={{outline:'none'}} onChange={this.handleInputChange} placeholder="输入集群名称查找" value={this.state.displayValue||''} />
|
||||||
|
</div>
|
||||||
<InfiniteScroll
|
<InfiniteScroll
|
||||||
initialLoad={false}
|
initialLoad={false}
|
||||||
loadMore={this.handleInfiniteOnLoad}
|
loadMore={this.handleInfiniteOnLoad}
|
||||||
hasMore={!this.state.loading && this.state.hasMore}
|
hasMore={!this.state.loading && this.state.hasMore}
|
||||||
useWindow={false}
|
useWindow={false}
|
||||||
>
|
>
|
||||||
{/* <List
|
|
||||||
grid={{
|
|
||||||
gutter: 16,
|
|
||||||
sm: 4,
|
|
||||||
xs: 3
|
|
||||||
}}
|
|
||||||
dataSource={this.props.data}
|
|
||||||
renderItem={item => {
|
|
||||||
return (
|
|
||||||
<List.Item key={item[labelField]}>
|
|
||||||
<Button onClick={() => {
|
|
||||||
this.handleItemClick(item)
|
|
||||||
}}
|
|
||||||
className={_.isEqual(item, value) ? styles.btnitem + " " + styles.selected : styles.btnitem}>{item[labelField]}</Button>
|
|
||||||
</List.Item>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{this.state.loading && this.state.hasMore && (
|
|
||||||
<div className={styles.loadingContainer}>
|
|
||||||
<Spin />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</List> */}
|
|
||||||
<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.props.data || []).map((item)=>{
|
{(this.props.data || []).map((item)=>{
|
||||||
return <div className={styles.item}><Button key={item[labelField]} onClick={() => {
|
// return <div className={styles.item}>
|
||||||
|
// <Button key={item[labelField]}
|
||||||
|
// onClick={() => {
|
||||||
|
// this.handleItemClick(item)
|
||||||
|
// }}
|
||||||
|
// className={_.isEqual(item, value) ? styles.btnitem + " " + styles.selected : styles.btnitem}>{item[labelField]}</Button>
|
||||||
|
// </div>
|
||||||
|
const cstatus = clusterStatus ? clusterStatus[item.id] : null;
|
||||||
|
return <DropdownItem key={item.id}
|
||||||
|
isSelected={item.id===value.id}
|
||||||
|
clusterItem={item}
|
||||||
|
clusterStatus={cstatus}
|
||||||
|
onClick={() => {
|
||||||
this.handleItemClick(item)
|
this.handleItemClick(item)
|
||||||
}}
|
}}
|
||||||
className={_.isEqual(item, value) ? styles.btnitem + " " + styles.selected : styles.btnitem}>{item[labelField]}</Button></div>
|
/>
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</InfiniteScroll>
|
</InfiniteScroll>
|
||||||
|
@ -114,11 +117,23 @@ class DropdownSelect extends React.Component{
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>);
|
</div>);
|
||||||
|
const cstatus = clusterStatus ? clusterStatus[value?.id] : null;
|
||||||
return(
|
return(
|
||||||
this.props.visible ?
|
this.props.visible ?
|
||||||
(<Dropdown overlay={menu} placement="bottomLeft">
|
(<Dropdown overlay={menu} placement="bottomLeft" visible={this.state.overlayVisible}
|
||||||
<Button className={styles['btn-ds']}>{value[labelField]} <Icon style={{float: 'right', marginTop: 3}}
|
onVisibleChange={(flag)=>{
|
||||||
type="caret-down"/></Button>
|
this.setState({ overlayVisible: flag });
|
||||||
|
}}>
|
||||||
|
{/* <Button className={styles['btn-ds']}>{value[labelField]} <Icon style={{float: 'right', marginTop: 3}}
|
||||||
|
type="caret-down"/></Button> */}
|
||||||
|
<span style={{position:'relative'}}>
|
||||||
|
<i style={{position: 'absolute', left:15,zIndex:10, top: -28}}>
|
||||||
|
{cstatus?.cluster_available ? <HealthStatusCircle status={cstatus?.health_status} />: <Icon type='close-circle' style={{width:14, height:14, color:'red',borderRadius: 14, boxShadow: '0px 0px 5px #555'}} />}
|
||||||
|
</i>
|
||||||
|
<input className={styles['btn-ds']} style={{outline:'none', paddingLeft:22}} value={value[labelField]} readOnly={true} />
|
||||||
|
<Icon style={{position:'absolute', top:-6, right:-4}} type="caret-down"/>
|
||||||
|
</span>
|
||||||
|
|
||||||
</Dropdown>) : ""
|
</Dropdown>) : ""
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
margin-left: 15px;
|
margin-left: 15px;
|
||||||
position: relative;
|
position: relative;
|
||||||
bottom: 8px;
|
bottom: 8px;
|
||||||
|
height: 32px;
|
||||||
span{
|
span{
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
|
@ -16,7 +17,7 @@
|
||||||
|
|
||||||
.dropmenu{
|
.dropmenu{
|
||||||
box-shadow: 0 0 15px 0 rgba(0, 0, 0, .15);
|
box-shadow: 0 0 15px 0 rgba(0, 0, 0, .15);
|
||||||
padding: 20px;
|
padding: 0px;
|
||||||
padding-bottom: 4px;
|
padding-bottom: 4px;
|
||||||
width: 500px;
|
width: 500px;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
|
@ -57,3 +58,27 @@
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dropdown-item{
|
||||||
|
box-shadow: 0 0 1px 0 rgba(196, 191, 191, 0.15);
|
||||||
|
.wrapper{
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 10px;
|
||||||
|
padding-left: 15px;
|
||||||
|
.name{
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
.version{
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
:hover{
|
||||||
|
background-color: #939ea0;
|
||||||
|
color: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
&.selected {
|
||||||
|
background-color: #eee;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import { Icon } from 'antd';
|
import { Icon, Select } from 'antd';
|
||||||
import Link from 'umi/link';
|
import Link from 'umi/link';
|
||||||
import Debounce from 'lodash-decorators/debounce';
|
import Debounce from 'lodash-decorators/debounce';
|
||||||
import styles from './index.less';
|
import styles from './index.less';
|
||||||
|
@ -53,6 +53,7 @@ export default class GlobalHeader extends PureComponent {
|
||||||
onClick={this.toggle}
|
onClick={this.toggle}
|
||||||
/>
|
/>
|
||||||
<DropdownSelect defaultValue={selectedCluster}
|
<DropdownSelect defaultValue={selectedCluster}
|
||||||
|
clusterStatus={this.props.clusterStatus}
|
||||||
value={selectedCluster}
|
value={selectedCluster}
|
||||||
labelField="name"
|
labelField="name"
|
||||||
visible={clusterVisible}
|
visible={clusterVisible}
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
export type ClusterHealthStatus = 'green' | 'yellow' | 'red';
|
||||||
|
|
||||||
|
const statusColorMap: Record<string, string> = {
|
||||||
|
'green': '#39b362',
|
||||||
|
'yellow': 'yellow',
|
||||||
|
'red': 'red',
|
||||||
|
}
|
||||||
|
|
||||||
|
export function convertStatusToColor(status: ClusterHealthStatus){
|
||||||
|
return statusColorMap[status];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface props {
|
||||||
|
status: ClusterHealthStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
export const HealthStatusCircle = ({status}: props)=>{
|
||||||
|
const color = convertStatusToColor(status);
|
||||||
|
return <div style={{background: color, height:14, width:14, borderRadius: 14, boxShadow: '0px 0px 5px #999', display: 'inline-block'}}></div>
|
||||||
|
}
|
|
@ -626,10 +626,6 @@ export class IndexPatternsService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes an index pattern from .kibana index
|
|
||||||
* @param indexPatternId: Id of kibana Index Pattern to delete
|
|
||||||
*/
|
|
||||||
async delete(indexPatternId: string) {
|
async delete(indexPatternId: string) {
|
||||||
indexPatternCache.clear(indexPatternId);
|
indexPatternCache.clear(indexPatternId);
|
||||||
return this.savedObjectsClient.delete(savedObjectType, indexPatternId);
|
return this.savedObjectsClient.delete(savedObjectType, indexPatternId);
|
||||||
|
|
|
@ -23,8 +23,8 @@ import moment from 'moment';
|
||||||
import { areRefreshIntervalsDifferent, areTimeRangesDifferent } from './lib/diff_time_picker_vals';
|
import { areRefreshIntervalsDifferent, areTimeRangesDifferent } from './lib/diff_time_picker_vals';
|
||||||
import { getForceNow } from './lib/get_force_now';
|
import { getForceNow } from './lib/get_force_now';
|
||||||
import { TimefilterConfig, InputTimeRange, TimeRangeBounds } from './types';
|
import { TimefilterConfig, InputTimeRange, TimeRangeBounds } from './types';
|
||||||
import { getTime, RefreshInterval, TimeRange } from '../../../common';
|
import { RefreshInterval, TimeRange } from '../../../common';
|
||||||
import { calculateBounds } from '../../../common/query/timefilter/get_time';
|
import {getTime, calculateBounds } from '../../../common/query/timefilter/get_time';
|
||||||
|
|
||||||
import { TimeHistoryContract } from './time_history';
|
import { TimeHistoryContract } from './time_history';
|
||||||
import { IndexPattern } from '../../index_patterns';
|
import { IndexPattern } from '../../index_patterns';
|
||||||
|
|
|
@ -218,7 +218,7 @@ export class CreateIndexPatternWizard extends Component<
|
||||||
return <LoadingState />;
|
return <LoadingState />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const header = this.renderHeader();
|
// const header = this.renderHeader();
|
||||||
|
|
||||||
if (step === 1) {
|
if (step === 1) {
|
||||||
const { location } = this.props;
|
const { location } = this.props;
|
||||||
|
@ -226,8 +226,8 @@ export class CreateIndexPatternWizard extends Component<
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EuiPageContent>
|
<EuiPageContent>
|
||||||
{header}
|
{/* {header}
|
||||||
<EuiHorizontalRule />
|
<EuiHorizontalRule /> */}
|
||||||
<StepIndexPattern
|
<StepIndexPattern
|
||||||
allIndices={allIndices}
|
allIndices={allIndices}
|
||||||
initialQuery={indexPattern || initialQuery}
|
initialQuery={indexPattern || initialQuery}
|
||||||
|
@ -245,8 +245,8 @@ export class CreateIndexPatternWizard extends Component<
|
||||||
if (step === 2) {
|
if (step === 2) {
|
||||||
return (
|
return (
|
||||||
<EuiPageContent>
|
<EuiPageContent>
|
||||||
{header}
|
{/* {header}
|
||||||
<EuiHorizontalRule />
|
<EuiHorizontalRule /> */}
|
||||||
<StepTimeField
|
<StepTimeField
|
||||||
indexPattern={indexPattern}
|
indexPattern={indexPattern}
|
||||||
viewName={viewName}
|
viewName={viewName}
|
||||||
|
|
|
@ -18,76 +18,32 @@
|
||||||
*/
|
*/
|
||||||
import './empty_index_pattern_prompt.scss';
|
import './empty_index_pattern_prompt.scss';
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import { EuiPageContent, EuiSpacer, EuiText, EuiFlexItem, EuiFlexGroup } from '@elastic/eui';
|
|
||||||
import { EuiDescriptionListTitle } from '@elastic/eui';
|
|
||||||
import { EuiDescriptionListDescription, EuiDescriptionList } from '@elastic/eui';
|
|
||||||
import { EuiLink } from '@elastic/eui';
|
|
||||||
import { getListBreadcrumbs } from '../../breadcrumbs';
|
|
||||||
import { IndexPatternCreationOption } from '../../types';
|
import { IndexPatternCreationOption } from '../../types';
|
||||||
import { CreateButton } from '../../create_button';
|
|
||||||
import { Illustration } from './assets/index_pattern_illustration';
|
|
||||||
import { ManagementAppMountParams } from '../../../../../management/public';
|
|
||||||
import Link from 'umi/link';
|
import Link from 'umi/link';
|
||||||
import Exception from '@/components/Exception';
|
import Exception from '@/components/Exception';
|
||||||
|
import {Button} from 'antd';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
canSave: boolean;
|
|
||||||
creationOptions: IndexPatternCreationOption[];
|
creationOptions: IndexPatternCreationOption[];
|
||||||
docLinksIndexPatternIntro: string;
|
|
||||||
setBreadcrumbs: ManagementAppMountParams['setBreadcrumbs'];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EmptyIndexPatternPrompt = ({
|
export const EmptyIndexPatternPrompt = ({
|
||||||
canSave,
|
|
||||||
creationOptions,
|
creationOptions,
|
||||||
docLinksIndexPatternIntro,
|
|
||||||
setBreadcrumbs,
|
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
setBreadcrumbs(getListBreadcrumbs());
|
const actions = (<div>
|
||||||
|
<Button type="primary" onClick={()=>{
|
||||||
|
creationOptions[0].onClick();
|
||||||
|
}}>创建视图</Button>
|
||||||
|
</div>)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EuiPageContent
|
<Exception
|
||||||
data-test-subj="emptyIndexPatternPrompt"
|
type="404"
|
||||||
className="inpEmptyIndexPatternPrompt"
|
title="没有视图"
|
||||||
grow={false}
|
linkElement={Link}
|
||||||
horizontalPosition="center"
|
desc={'当前集群找不到任何数据视图'}
|
||||||
>
|
actions={actions}
|
||||||
<EuiFlexGroup gutterSize="xl" alignItems="center" direction="rowReverse" wrap>
|
/>
|
||||||
{/* <EuiFlexItem grow={1} className="inpEmptyIndexPatternPrompt__illustration">
|
|
||||||
<Illustration />
|
|
||||||
</EuiFlexItem> */}
|
|
||||||
<EuiFlexItem grow={2} className="inpEmptyIndexPatternPrompt__text">
|
|
||||||
<EuiText grow={false}>
|
|
||||||
<h2>
|
|
||||||
Elasticsearch 里面有数据。
|
|
||||||
<br />
|
|
||||||
去试试创建一个数据视图吧。
|
|
||||||
</h2>
|
|
||||||
<p>
|
|
||||||
极限搜索中心通过创建数据视图来标识您想要浏览的索引,一个数据视图可以对应一个索引或多个索引。比如数据视图 server_log 匹配索引
|
|
||||||
server_log, server_log* 匹配所有以 server_log 开头的索引。
|
|
||||||
</p>
|
|
||||||
{canSave && (
|
|
||||||
<CreateButton options={creationOptions}>
|
|
||||||
创建数据视图
|
|
||||||
</CreateButton>
|
|
||||||
)}
|
|
||||||
</EuiText>
|
|
||||||
</EuiFlexItem>
|
|
||||||
</EuiFlexGroup>
|
|
||||||
<EuiSpacer size="xxl" />
|
|
||||||
<EuiDescriptionList className="inpEmptyIndexPatternPrompt__footer" type="responsiveColumn">
|
|
||||||
<EuiDescriptionListTitle className="inpEmptyIndexPatternPrompt__title">
|
|
||||||
了解更多?
|
|
||||||
</EuiDescriptionListTitle>
|
|
||||||
<EuiDescriptionListDescription>
|
|
||||||
<EuiLink href={docLinksIndexPatternIntro} target="_blank" external>
|
|
||||||
阅读文档
|
|
||||||
</EuiLink>
|
|
||||||
</EuiDescriptionListDescription>
|
|
||||||
</EuiDescriptionList>
|
|
||||||
</EuiPageContent>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -17,165 +17,28 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import './empty_state.scss';
|
import Exception from '@/components/Exception';
|
||||||
import React from 'react';
|
import {Button} from 'antd';
|
||||||
import { DocLinksStart, ApplicationStart } from 'kibana/public';
|
import Link from 'umi/link';
|
||||||
import {
|
import {router} from 'umi';
|
||||||
EuiPageContentHeader,
|
|
||||||
EuiPageContentHeaderSection,
|
|
||||||
EuiTitle,
|
|
||||||
EuiPageContentBody,
|
|
||||||
EuiPageContent,
|
|
||||||
EuiIcon,
|
|
||||||
EuiSpacer,
|
|
||||||
EuiFlexItem,
|
|
||||||
EuiDescriptionList,
|
|
||||||
EuiFlexGrid,
|
|
||||||
EuiCard,
|
|
||||||
EuiLink,
|
|
||||||
EuiText,
|
|
||||||
} from '@elastic/eui';
|
|
||||||
import { useHistory } from 'react-router-dom';
|
|
||||||
// import { reactRouterNavigate } from '../../../../../../plugins/kibana_react/public';
|
|
||||||
import { MlCardState } from '../../../types';
|
|
||||||
|
|
||||||
export const EmptyState = ({
|
export const EmptyState = ({
|
||||||
onRefresh,
|
|
||||||
navigateToApp,
|
|
||||||
docLinks,
|
|
||||||
getMlCardState,
|
|
||||||
canSave,
|
|
||||||
}: {
|
}: {
|
||||||
onRefresh: () => void;
|
|
||||||
navigateToApp: ApplicationStart['navigateToApp'];
|
|
||||||
docLinks: DocLinksStart;
|
|
||||||
getMlCardState: () => MlCardState;
|
|
||||||
canSave: boolean;
|
|
||||||
}) => {
|
}) => {
|
||||||
const mlCard = (
|
|
||||||
<EuiFlexItem>
|
|
||||||
<EuiCard
|
|
||||||
onClick={() => navigateToApp('ml', { path: '#/filedatavisualizer' })}
|
|
||||||
className="inpEmptyState__card"
|
|
||||||
betaBadgeLabel={
|
|
||||||
getMlCardState() === MlCardState.ENABLED
|
|
||||||
? undefined
|
|
||||||
: 'Basic'
|
|
||||||
}
|
|
||||||
betaBadgeTooltipContent={ 'This feature requires a Basic license.'}
|
|
||||||
isDisabled={getMlCardState() === MlCardState.DISABLED}
|
|
||||||
icon={<EuiIcon size="xl" type="document" color="subdued" />}
|
|
||||||
title={
|
|
||||||
"Upload a file"
|
|
||||||
}
|
|
||||||
description={
|
|
||||||
"Import a CSV, NDJSON, or log file."
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</EuiFlexItem>
|
|
||||||
);
|
|
||||||
|
|
||||||
const createAnyway = (
|
const actions = (<div>
|
||||||
<EuiText color="subdued" textAlign="center" size="xs">
|
<Button type="primary" onClick={()=>{
|
||||||
Some indices may be hidden. Try to
|
router.push('/dev_tool');
|
||||||
<EuiLink
|
}}>创建索引</Button>
|
||||||
// {...reactRouterNavigate(useHistory(), 'create')}
|
</div>)
|
||||||
data-test-subj="createAnyway">
|
|
||||||
create an index pattern
|
|
||||||
</EuiLink> anyway.
|
|
||||||
</EuiText>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Exception
|
||||||
<EuiPageContent
|
type="404"
|
||||||
className="inpEmptyState"
|
title="没有数据"
|
||||||
grow={false}
|
linkElement={Link}
|
||||||
horizontalPosition="center"
|
desc={'当前集群找不到任何数据索引'}
|
||||||
data-test-subj="indexPatternEmptyState"
|
actions={actions}
|
||||||
>
|
|
||||||
<EuiPageContentHeader>
|
|
||||||
<EuiPageContentHeaderSection>
|
|
||||||
<EuiTitle>
|
|
||||||
<h2>
|
|
||||||
当前集群没有数据,使用数据视图之前要创建数据索引。
|
|
||||||
</h2>
|
|
||||||
</EuiTitle>
|
|
||||||
</EuiPageContentHeaderSection>
|
|
||||||
</EuiPageContentHeader>
|
|
||||||
<EuiSpacer size="m" />
|
|
||||||
<EuiPageContentBody>
|
|
||||||
{/* <EuiFlexGrid className="inpEmptyState__cardGrid" columns={3} responsive={true}>
|
|
||||||
<EuiFlexItem>
|
|
||||||
<EuiCard
|
|
||||||
className="inpEmptyState__card"
|
|
||||||
onClick={() => navigateToApp('home', { path: '#/tutorial_directory' })}
|
|
||||||
icon={<EuiIcon size="xl" type="database" color="subdued" />}
|
|
||||||
title={
|
|
||||||
"Add integration"
|
|
||||||
}
|
|
||||||
description={
|
|
||||||
"Add data from a variety of sources."
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</EuiFlexItem>
|
|
||||||
{getMlCardState() !== MlCardState.HIDDEN ? mlCard : <></>}
|
|
||||||
<EuiFlexItem>
|
|
||||||
<EuiCard
|
|
||||||
className="inpEmptyState__card"
|
|
||||||
onClick={() => navigateToApp('home', { path: '#/tutorial_directory/sampleData' })}
|
|
||||||
icon={<EuiIcon size="xl" type="heatmap" color="subdued" />}
|
|
||||||
title={
|
|
||||||
"Add sample data"
|
|
||||||
}
|
|
||||||
description={
|
|
||||||
"Load a data set and a Kibana dashboard."
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</EuiFlexItem>
|
|
||||||
</EuiFlexGrid> */}
|
|
||||||
<EuiSpacer size="xxl" />
|
|
||||||
<div className="inpEmptyState__footer">
|
|
||||||
<EuiFlexGrid columns={3}>
|
|
||||||
{/* <EuiFlexItem className="inpEmptyState__footerFlexItem">
|
|
||||||
<EuiDescriptionList
|
|
||||||
listItems={[
|
|
||||||
{
|
|
||||||
title: (
|
|
||||||
"了解更多?"
|
|
||||||
),
|
|
||||||
description: (
|
|
||||||
<EuiLink href={docLinks.links.addData} target="_blank" external>
|
|
||||||
Read documentation
|
|
||||||
</EuiLink>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</EuiFlexItem> */}
|
|
||||||
{/* <EuiFlexItem className="inpEmptyState__footerFlexItem">
|
|
||||||
<EuiDescriptionList
|
|
||||||
listItems={[
|
|
||||||
{
|
|
||||||
title: (
|
|
||||||
"确定已经有数据?"
|
|
||||||
),
|
|
||||||
description: (
|
|
||||||
<EuiLink onClick={onRefresh} data-test-subj="refreshIndicesButton">
|
|
||||||
点击查看是否有新数据{' '}
|
|
||||||
<EuiIcon type="refresh" size="s" />
|
|
||||||
</EuiLink>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</EuiFlexItem> */}
|
|
||||||
</EuiFlexGrid>
|
|
||||||
</div>
|
|
||||||
</EuiPageContentBody>
|
|
||||||
</EuiPageContent>
|
|
||||||
<EuiSpacer />
|
|
||||||
{/* {canSave && createAnyway} */}
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -32,17 +32,18 @@ import {
|
||||||
} from '@elastic/eui';
|
} from '@elastic/eui';
|
||||||
import { withRouter, RouteComponentProps } from 'react-router-dom';
|
import { withRouter, RouteComponentProps } from 'react-router-dom';
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { reactRouterNavigate, useKibana } from '../../../../kibana_react/public';
|
import { reactRouterNavigate } from '../../../../kibana_react/public';
|
||||||
import { IndexPatternManagmentContext } from '../../types';
|
|
||||||
import { CreateButton } from '../create_button';
|
|
||||||
import { IndexPatternTableItem, IndexPatternCreationOption } from '../types';
|
import { IndexPatternTableItem, IndexPatternCreationOption } from '../types';
|
||||||
import { getIndexPatterns } from '../utils';
|
import { getIndexPatterns } from '../utils';
|
||||||
import { getListBreadcrumbs } from '../breadcrumbs';
|
|
||||||
import { EmptyState } from './empty_state';
|
import { EmptyState } from './empty_state';
|
||||||
import { MatchedItem, ResolveIndexResponseItemAlias } from '../create_index_pattern_wizard/types';
|
import { MatchedItem, ResolveIndexResponseItemAlias } from '../create_index_pattern_wizard/types';
|
||||||
import { EmptyIndexPatternPrompt } from './empty_index_pattern_prompt';
|
import { EmptyIndexPatternPrompt } from './empty_index_pattern_prompt';
|
||||||
import { getIndices } from '../create_index_pattern_wizard/lib';
|
import { getIndices } from '../create_index_pattern_wizard/lib';
|
||||||
import { useGlobalContext } from '../../context';
|
import { useGlobalContext } from '../../context';
|
||||||
|
import {Button} from 'antd';
|
||||||
|
import PageHeaderWrapper from '@/components/PageHeaderWrapper';
|
||||||
|
import styles from '@/pages/System/Cluster/step.less';
|
||||||
|
import clusterBg from '@/assets/cluster_bg.png';
|
||||||
|
|
||||||
const pagination = {
|
const pagination = {
|
||||||
initialPageSize: 10,
|
initialPageSize: 10,
|
||||||
|
@ -65,10 +66,28 @@ const search = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const ariaRegion = 'Index patterns';
|
|
||||||
|
const ariaRegion = '数据视图';
|
||||||
|
|
||||||
const title = '数据视图';
|
const title = '数据视图';
|
||||||
|
|
||||||
|
const content = (
|
||||||
|
<div className={styles.pageHeaderContent}>
|
||||||
|
<p>
|
||||||
|
创建和管理数据视图可以帮助您更好地从 Elasticsearch 获取数据。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const extraContent = (
|
||||||
|
<div className={styles.extraImg}>
|
||||||
|
<img
|
||||||
|
alt="数据视图"
|
||||||
|
src={clusterBg}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
interface Props extends RouteComponentProps {
|
interface Props extends RouteComponentProps {
|
||||||
canSave: boolean;
|
canSave: boolean;
|
||||||
}
|
}
|
||||||
|
@ -79,9 +98,7 @@ export const IndexPatternTable = ({ canSave, history, selectedCluster }: Props)
|
||||||
savedObjects,
|
savedObjects,
|
||||||
uiSettings,
|
uiSettings,
|
||||||
indexPatternManagementStart,
|
indexPatternManagementStart,
|
||||||
chrome,
|
|
||||||
docLinks,
|
docLinks,
|
||||||
application,
|
|
||||||
http,
|
http,
|
||||||
getMlCardState,
|
getMlCardState,
|
||||||
data,
|
data,
|
||||||
|
@ -165,38 +182,17 @@ export const IndexPatternTable = ({ canSave, history, selectedCluster }: Props)
|
||||||
{
|
{
|
||||||
field: 'title',
|
field: 'title',
|
||||||
name: '匹配规则',
|
name: '匹配规则',
|
||||||
// render: (
|
|
||||||
// name: string,
|
|
||||||
// index: {
|
|
||||||
// id: string;
|
|
||||||
// tags?: Array<{
|
|
||||||
// key: string;
|
|
||||||
// name: string;
|
|
||||||
// }>;
|
|
||||||
// }
|
|
||||||
// ) => (
|
|
||||||
// <>
|
|
||||||
// <EuiButtonEmpty style={{fontSize:14}} size="xs" {...reactRouterNavigate(history, `patterns/${index.id}`)}>
|
|
||||||
// {name}
|
|
||||||
// </EuiButtonEmpty>
|
|
||||||
//  
|
|
||||||
// <EuiBadgeGroup gutterSize="s">
|
|
||||||
// {index.tags &&
|
|
||||||
// index.tags.map(({ key: tagKey, name: tagName }) => (
|
|
||||||
// <EuiBadge key={tagKey}>{tagName}</EuiBadge>
|
|
||||||
// ))}
|
|
||||||
// </EuiBadgeGroup>
|
|
||||||
// </>
|
|
||||||
// ),
|
|
||||||
dataType: 'string' as const,
|
dataType: 'string' as const,
|
||||||
sortable: ({ sort }: { sort: string }) => sort,
|
sortable: ({ sort }: { sort: string }) => sort,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const createButton = canSave ? (
|
const createButton = canSave ? (
|
||||||
<CreateButton options={creationOptions}>
|
<Button icon="plus" type="primary" onClick={()=>{
|
||||||
|
creationOptions[0].onClick();
|
||||||
|
}}>
|
||||||
创建{title}
|
创建{title}
|
||||||
</CreateButton>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
);
|
);
|
||||||
|
@ -210,43 +206,26 @@ export const IndexPatternTable = ({ canSave, history, selectedCluster }: Props)
|
||||||
if (!indexPatterns.length) {
|
if (!indexPatterns.length) {
|
||||||
if (!hasDataIndices && !remoteClustersExist) {
|
if (!hasDataIndices && !remoteClustersExist) {
|
||||||
return (
|
return (
|
||||||
<EmptyState
|
<EmptyState/>
|
||||||
onRefresh={loadSources}
|
|
||||||
docLinks={docLinks}
|
|
||||||
// navigateToApp={application.navigateToApp}
|
|
||||||
getMlCardState={getMlCardState}
|
|
||||||
canSave={canSave}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<EmptyIndexPatternPrompt
|
<EmptyIndexPatternPrompt
|
||||||
canSave={canSave}
|
|
||||||
creationOptions={creationOptions}
|
creationOptions={creationOptions}
|
||||||
docLinksIndexPatternIntro={docLinks.links.indexPatterns.introduction}
|
|
||||||
setBreadcrumbs={setBreadcrumbs}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const renderToolsRight = () => {
|
||||||
|
return [
|
||||||
|
createButton
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<PageHeaderWrapper title="数据视图" content={content} extraContent={extraContent}>
|
||||||
<EuiPageContent data-test-subj="indexPatternTable" role="region" aria-label={ariaRegion}>
|
<EuiPageContent data-test-subj="indexPatternTable" role="region" aria-label={ariaRegion}>
|
||||||
<EuiFlexGroup justifyContent="spaceBetween">
|
|
||||||
<EuiFlexItem grow={false}>
|
|
||||||
<EuiTitle>
|
|
||||||
<h2 style={{fontWeight:300}}>{title}</h2>
|
|
||||||
</EuiTitle>
|
|
||||||
<EuiSpacer size="s" />
|
|
||||||
<EuiText>
|
|
||||||
<p>
|
|
||||||
创建和管理{title}可以帮助您更好地从 Elasticsearch 获取数据。
|
|
||||||
</p>
|
|
||||||
</EuiText>
|
|
||||||
</EuiFlexItem>
|
|
||||||
<EuiFlexItem grow={false}>{createButton}</EuiFlexItem>
|
|
||||||
</EuiFlexGroup>
|
|
||||||
<EuiSpacer />
|
|
||||||
<EuiContext i18n={{
|
<EuiContext i18n={{
|
||||||
mapping: {
|
mapping: {
|
||||||
'euiTablePagination.rowsPerPage': '每页行数',
|
'euiTablePagination.rowsPerPage': '每页行数',
|
||||||
|
@ -261,10 +240,14 @@ export const IndexPatternTable = ({ canSave, history, selectedCluster }: Props)
|
||||||
columns={columns}
|
columns={columns}
|
||||||
pagination={pagination}
|
pagination={pagination}
|
||||||
sorting={sorting}
|
sorting={sorting}
|
||||||
search={search}
|
search={{
|
||||||
|
...search,
|
||||||
|
toolsRight: renderToolsRight(),
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</EuiContext>
|
</EuiContext>
|
||||||
</EuiPageContent>
|
</EuiPageContent>
|
||||||
|
</PageHeaderWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -27,10 +27,14 @@ class HeaderView extends PureComponent {
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
document.addEventListener('scroll', this.handScroll, { passive: true });
|
document.addEventListener('scroll', this.handScroll, { passive: true });
|
||||||
|
this.fetchClusterStatus()
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
document.removeEventListener('scroll', this.handScroll);
|
document.removeEventListener('scroll', this.handScroll);
|
||||||
|
if(this.fetchClusterStatusTimer){
|
||||||
|
clearTimeout(this.fetchClusterStatusTimer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getHeadWidth = () => {
|
getHeadWidth = () => {
|
||||||
|
@ -114,14 +118,13 @@ class HeaderView extends PureComponent {
|
||||||
this.ticking = false;
|
this.ticking = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
handleFetchClusterList = (from, size) => {
|
handleFetchClusterList = (name, size) => {
|
||||||
const { dispatch } = this.props;
|
const { dispatch } = this.props;
|
||||||
return dispatch({
|
return dispatch({
|
||||||
type: 'global/fetchClusterList',
|
type: 'global/fetchClusterList',
|
||||||
payload: {
|
payload: {
|
||||||
from,
|
|
||||||
size,
|
size,
|
||||||
enabled: true,
|
name
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -134,6 +137,20 @@ class HeaderView extends PureComponent {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fetchClusterStatus = async ()=>{
|
||||||
|
const {dispatch} = this.props;
|
||||||
|
const res = await dispatch({
|
||||||
|
type: 'global/fetchClusterStatus',
|
||||||
|
});
|
||||||
|
if(this.fetchClusterStatusTimer){
|
||||||
|
clearTimeout(this.fetchClusterStatusTimer);
|
||||||
|
}
|
||||||
|
if(!res){
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.fetchClusterStatusTimer = setTimeout(this.fetchClusterStatus, 10000);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { isMobile, handleMenuCollapse, setting } = this.props;
|
const { isMobile, handleMenuCollapse, setting } = this.props;
|
||||||
const { navTheme, layout, fixedHeader } = setting;
|
const { navTheme, layout, fixedHeader } = setting;
|
||||||
|
@ -183,4 +200,5 @@ export default connect(({ user, global, setting, loading }) => ({
|
||||||
clusterVisible: global.clusterVisible,
|
clusterVisible: global.clusterVisible,
|
||||||
clusterList: global.clusterList,
|
clusterList: global.clusterList,
|
||||||
selectedCluster: global.selectedCluster,
|
selectedCluster: global.selectedCluster,
|
||||||
|
clusterStatus: global.clusterStatus,
|
||||||
}))(HeaderView);
|
}))(HeaderView);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { queryNotices } from '@/services/api';
|
import { queryNotices } from '@/services/api';
|
||||||
import {message} from "antd";
|
import {message} from "antd";
|
||||||
import {searchClusterConfig} from "@/services/cluster";
|
import {searchClusterConfig, getClusterStatus} from "@/services/cluster";
|
||||||
import {formatESSearchResult, extractClusterIDFromURL} from '@/lib/elasticsearch/util';
|
import {formatESSearchResult, extractClusterIDFromURL} from '@/lib/elasticsearch/util';
|
||||||
import {Modal} from 'antd';
|
import {Modal} from 'antd';
|
||||||
import router from "umi/router";
|
import router from "umi/router";
|
||||||
|
@ -16,6 +16,10 @@ export default {
|
||||||
clusterList: [],
|
clusterList: [],
|
||||||
selectedCluster: {name:"Select cluster", id: ""},
|
selectedCluster: {name:"Select cluster", id: ""},
|
||||||
selectedClusterID: "",
|
selectedClusterID: "",
|
||||||
|
search:{
|
||||||
|
cluster: {
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
effects: {
|
effects: {
|
||||||
|
@ -48,7 +52,7 @@ export default {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
res = formatESSearchResult(res)
|
res = formatESSearchResult(res)
|
||||||
let clusterList = yield select(state => state.global.clusterList);
|
let {clusterList, search} = yield select(state => state.global);
|
||||||
let data = res.data.filter(item=>item.enabled).map((item)=>{
|
let data = res.data.filter(item=>item.enabled).map((item)=>{
|
||||||
return {
|
return {
|
||||||
name: item.name,
|
name: item.name,
|
||||||
|
@ -58,7 +62,7 @@ export default {
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
|
|
||||||
if(clusterList.length === 0){
|
if(clusterList.length === 0 && !payload.name){
|
||||||
if(data.length === 0 ){
|
if(data.length === 0 ){
|
||||||
Modal.info({
|
Modal.info({
|
||||||
title: '系统提示',
|
title: '系统提示',
|
||||||
|
@ -83,11 +87,20 @@ export default {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let newClusterList = [];
|
||||||
|
if(search.name != payload.name){
|
||||||
|
newClusterList = data;
|
||||||
|
}else{
|
||||||
|
newClusterList = clusterList.concat(data);
|
||||||
|
}
|
||||||
yield put({
|
yield put({
|
||||||
type: 'saveData',
|
type: 'saveData',
|
||||||
payload: {
|
payload: {
|
||||||
clusterList: clusterList.concat(data),
|
clusterList: newClusterList,
|
||||||
|
search: {
|
||||||
|
...search,
|
||||||
|
cluster: payload,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return data;
|
return data;
|
||||||
|
@ -124,7 +137,24 @@ export default {
|
||||||
history.replace(newPath)
|
history.replace(newPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
*fetchClusterStatus({payload}, {call, put}){
|
||||||
|
let res = yield call(getClusterStatus, payload);
|
||||||
|
if(!res){
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
if(res.error){
|
||||||
|
console.log(res.error)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
yield put({
|
||||||
|
type: 'saveData',
|
||||||
|
payload: {
|
||||||
|
clusterStatus: res
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return res;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
reducers: {
|
reducers: {
|
||||||
|
|
|
@ -9,10 +9,31 @@ import {
|
||||||
CreateIndexPatternWizardWithRouter,
|
CreateIndexPatternWizardWithRouter,
|
||||||
} from '../../components/kibana/index_pattern_management/public/components';
|
} from '../../components/kibana/index_pattern_management/public/components';
|
||||||
// import '@elastic/eui/dist/eui_theme_amsterdam_light.css';
|
// import '@elastic/eui/dist/eui_theme_amsterdam_light.css';
|
||||||
import {useGlobalContext} from '../../components/kibana/index_pattern_management/public/context'
|
import {useGlobalContext} from '../../components/kibana/index_pattern_management/public/context';
|
||||||
|
import PageHeaderWrapper from '@/components/PageHeaderWrapper';
|
||||||
|
import styles from '../System/Cluster/step.less';
|
||||||
|
import clusterBg from '@/assets/cluster_bg.png';
|
||||||
|
|
||||||
import { connect } from 'dva';
|
import { connect } from 'dva';
|
||||||
|
|
||||||
|
|
||||||
|
const createContent = (
|
||||||
|
<div className={styles.pageHeaderContent}>
|
||||||
|
<p>
|
||||||
|
一个数据视图可以匹配单个索引, 比如, server-log-1, 或者 多个 索引, server-log-*.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const createExtraContent = (
|
||||||
|
<div className={styles.extraImg}>
|
||||||
|
<img
|
||||||
|
alt="创建视图"
|
||||||
|
src={clusterBg}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
const IndexPatterns = (props)=> {
|
const IndexPatterns = (props)=> {
|
||||||
const history = useMemo(()=>{
|
const history = useMemo(()=>{
|
||||||
return new ScopedHistory(props.history, '/data/views');
|
return new ScopedHistory(props.history, '/data/views');
|
||||||
|
@ -35,11 +56,15 @@ const IndexPatterns = (props)=> {
|
||||||
initFetch();
|
initFetch();
|
||||||
}, [props.selectedCluster]);
|
}, [props.selectedCluster]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Router history={history}>
|
<Router history={history}>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route path={['/create']} >
|
<Route path={['/create']} >
|
||||||
|
<PageHeaderWrapper title="创建视图" content={createContent} extraContent={createExtraContent}>
|
||||||
<CreateIndexPatternWizardWithRouter key={createComponentKey} />
|
<CreateIndexPatternWizardWithRouter key={createComponentKey} />
|
||||||
|
</PageHeaderWrapper>
|
||||||
</Route>
|
</Route>
|
||||||
<Route path={['/patterns/:id/field/:fieldName', '/patterns/:id/create-field/']}>
|
<Route path={['/patterns/:id/field/:fieldName', '/patterns/:id/create-field/']}>
|
||||||
<CreateEditFieldContainer selectedCluster={props.selectedCluster}/>
|
<CreateEditFieldContainer selectedCluster={props.selectedCluster}/>
|
||||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||||
import {Button, Card, Col, Divider, Form, Input, Row, Table, Switch, Icon, Popconfirm, message} from "antd";
|
import {Button, Card, Col, Divider, Form, Input, Row, Table, Switch, Icon, Popconfirm, message} from "antd";
|
||||||
import Link from "umi/link";
|
import Link from "umi/link";
|
||||||
import {connect} from "dva";
|
import {connect} from "dva";
|
||||||
import {HealthStatusCircle} from './health_status';
|
import {HealthStatusCircle} from '@/components/infini/health_status_circle';
|
||||||
import PageHeaderWrapper from '@/components/PageHeaderWrapper';
|
import PageHeaderWrapper from '@/components/PageHeaderWrapper';
|
||||||
import styles from './step.less';
|
import styles from './step.less';
|
||||||
import clusterBg from '@/assets/cluster_bg.png';
|
import clusterBg from '@/assets/cluster_bg.png';
|
||||||
|
@ -25,8 +25,9 @@ const extraContent = (
|
||||||
);
|
);
|
||||||
|
|
||||||
@Form.create()
|
@Form.create()
|
||||||
@connect(({clusterConfig}) =>({
|
@connect(({clusterConfig, global}) =>({
|
||||||
clusterConfig
|
clusterConfig,
|
||||||
|
clusterStatus: global.clusterStatus,
|
||||||
}))
|
}))
|
||||||
class Index extends React.Component {
|
class Index extends React.Component {
|
||||||
columns = [{
|
columns = [{
|
||||||
|
@ -38,12 +39,17 @@ class Index extends React.Component {
|
||||||
dataIndex: 'id',
|
dataIndex: 'id',
|
||||||
key: 'health_status',
|
key: 'health_status',
|
||||||
render: (val)=>{
|
render: (val)=>{
|
||||||
const {clusterStatus} = this.props.clusterConfig;
|
const {clusterStatus} = this.props;
|
||||||
if(!clusterStatus || !clusterStatus[val]){
|
if(!clusterStatus || !clusterStatus[val]){
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
const isAvailable = clusterStatus[val].cluster_available;
|
||||||
|
if(!isAvailable){
|
||||||
|
return <Icon type="close-circle" style={{width:14, height:14, color:'red',borderRadius: 14, boxShadow: '0px 0px 5px #555'}}/>
|
||||||
|
}
|
||||||
const status = clusterStatus[val].health_status;
|
const status = clusterStatus[val].health_status;
|
||||||
return <HealthStatusCircle status={status}/>
|
return <HealthStatusCircle status={status}/>
|
||||||
|
|
||||||
}
|
}
|
||||||
},{
|
},{
|
||||||
title: '所属业务',
|
title: '所属业务',
|
||||||
|
@ -79,7 +85,7 @@ class Index extends React.Component {
|
||||||
dataIndex: 'id',
|
dataIndex: 'id',
|
||||||
key: 'mode_count',
|
key: 'mode_count',
|
||||||
render: (val)=>{
|
render: (val)=>{
|
||||||
const {clusterStatus} = this.props.clusterConfig;
|
const {clusterStatus} = this.props;
|
||||||
if(!clusterStatus || !clusterStatus[val]){
|
if(!clusterStatus || !clusterStatus[val]){
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -141,25 +147,6 @@ class Index extends React.Component {
|
||||||
}
|
}
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.fetchData({})
|
this.fetchData({})
|
||||||
this.fetchClusterStatus();
|
|
||||||
}
|
|
||||||
componentWillUnmount(){
|
|
||||||
if(this.fetchClusterStatusTimer){
|
|
||||||
clearTimeout(this.fetchClusterStatusTimer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fetchClusterStatus = async ()=>{
|
|
||||||
const {dispatch} = this.props;
|
|
||||||
const res = await dispatch({
|
|
||||||
type: 'clusterConfig/fetchClusterStatus',
|
|
||||||
});
|
|
||||||
if(this.fetchClusterStatusTimer){
|
|
||||||
clearTimeout(this.fetchClusterStatusTimer);
|
|
||||||
}
|
|
||||||
if(!res){
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.fetchClusterStatusTimer = setTimeout(this.fetchClusterStatus, 10000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSearchClick = ()=>{
|
handleSearchClick = ()=>{
|
||||||
|
@ -224,7 +211,7 @@ class Index extends React.Component {
|
||||||
return (
|
return (
|
||||||
<PageHeaderWrapper title="集群管理" content={content} extraContent={extraContent}>
|
<PageHeaderWrapper title="集群管理" content={content} extraContent={extraContent}>
|
||||||
<Card>
|
<Card>
|
||||||
<div style={{display:'flex', marginBottom:10, flex:"1 1 auto", justifyContent: 'space-between'}}>
|
<div style={{display:'flex', marginBottom:10, flex:"1 1 auto", justifyContent: 'space-between',alignItems:'center',}}>
|
||||||
<div>
|
<div>
|
||||||
<Form>
|
<Form>
|
||||||
<Row gutter={{md:24, sm:16}}>
|
<Row gutter={{md:24, sm:16}}>
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
export const HealthStatusCircle = ({status})=>{
|
|
||||||
return <div style={{background: status, height:14, width:14, borderRadius: 14, boxShadow: '0px 0px 5px #555'}}></div>
|
|
||||||
}
|
|
|
@ -1,5 +1,5 @@
|
||||||
import {createClusterConfig, searchClusterConfig, updateClusterConfig,deleteClusterConfig,
|
import {createClusterConfig, searchClusterConfig, updateClusterConfig,deleteClusterConfig,
|
||||||
getClusterStatus, tryConnect} from "@/services/cluster";
|
tryConnect} from "@/services/cluster";
|
||||||
import {message} from "antd";
|
import {message} from "antd";
|
||||||
import {formatESSearchResult} from '@/lib/elasticsearch/util';
|
import {formatESSearchResult} from '@/lib/elasticsearch/util';
|
||||||
|
|
||||||
|
@ -10,20 +10,6 @@ export default {
|
||||||
editValue: {},
|
editValue: {},
|
||||||
},
|
},
|
||||||
effects:{
|
effects:{
|
||||||
*fetchClusterStatus({payload}, {call, put}){
|
|
||||||
let res = yield call(getClusterStatus, payload);
|
|
||||||
if(res.error){
|
|
||||||
message.error(res.error)
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
yield put({
|
|
||||||
type: 'saveData',
|
|
||||||
payload: {
|
|
||||||
clusterStatus: res
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return res;
|
|
||||||
},
|
|
||||||
*fetchClusterList({payload}, {call, put, select}){
|
*fetchClusterList({payload}, {call, put, select}){
|
||||||
let res = yield call(searchClusterConfig, payload);
|
let res = yield call(searchClusterConfig, payload);
|
||||||
if(res.error){
|
if(res.error){
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import {Form, Input, Switch, Icon, InputNumber, Divider, Descriptions} from 'antd';
|
import {Form, Input, Switch, Icon, InputNumber, Divider, Descriptions} from 'antd';
|
||||||
import {HealthStatusCircle} from '../health_status';
|
import {HealthStatusCircle} from '@/components/infini/health_status_circle';
|
||||||
|
|
||||||
@Form.create()
|
@Form.create()
|
||||||
export class ExtraStep extends React.Component {
|
export class ExtraStep extends React.Component {
|
||||||
|
|
|
@ -42,7 +42,9 @@ export async function searchClusterConfig(params) {
|
||||||
let url = `/elasticsearch/_search`;
|
let url = `/elasticsearch/_search`;
|
||||||
let args = buildQueryArgs({
|
let args = buildQueryArgs({
|
||||||
name: params.name,
|
name: params.name,
|
||||||
enabled: params.enabled
|
enabled: params.enabled,
|
||||||
|
from: params.from,
|
||||||
|
size: params.size,
|
||||||
});
|
});
|
||||||
if(args.length > 0){
|
if(args.length > 0){
|
||||||
url += args;
|
url += args;
|
||||||
|
|
Loading…
Reference in New Issue