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"
|
||||
_ "expvar"
|
||||
"infini.sh/framework"
|
||||
"infini.sh/framework/core/elastic"
|
||||
"infini.sh/framework/core/env"
|
||||
"infini.sh/framework/core/module"
|
||||
"infini.sh/framework/core/orm"
|
||||
|
@ -70,6 +71,7 @@ func main() {
|
|||
}, func() {
|
||||
orm.RegisterSchemaWithIndexName(model.Dict{}, "dict")
|
||||
orm.RegisterSchemaWithIndexName(model.Reindex{}, "reindex")
|
||||
orm.RegisterSchemaWithIndexName(elastic.IndexPattern{}, "view")
|
||||
})
|
||||
|
||||
}
|
||||
|
|
|
@ -14,6 +14,11 @@ const config = {
|
|||
title: '500',
|
||||
desc: '抱歉,服务器出错了',
|
||||
},
|
||||
empty: {
|
||||
img: 'https://gw.alipayobjects.com/zos/rmsportal/KpnpchXsobRgLElEozzI.svg',
|
||||
title: '找不到数据',
|
||||
desc: '当前集群找到相关数据',
|
||||
}
|
||||
};
|
||||
|
||||
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 InfiniteScroll from 'react-infinite-scroller';
|
||||
import styles from './DropdownSelect.less';
|
||||
import _ from "lodash";
|
||||
import {DropdownItem} from './DropdownItem';
|
||||
import {HealthStatusCircle} from '@/components/infini/health_status_circle'
|
||||
|
||||
class DropdownSelect extends React.Component{
|
||||
state={
|
||||
value: this.props.defaultValue,
|
||||
loading: false,
|
||||
hasMore: true,
|
||||
overlayVisible: false,
|
||||
}
|
||||
|
||||
handleItemClick = (item)=>{
|
||||
let preValue = this.props.value || this.state.value;
|
||||
this.setState({
|
||||
value: item,
|
||||
overlayVisible: false,
|
||||
},()=>{
|
||||
let onChange = this.props.onChange;
|
||||
if(preValue != item && onChange && typeof onChange == 'function'){
|
||||
|
@ -35,75 +39,74 @@ class DropdownSelect extends React.Component{
|
|||
})
|
||||
})
|
||||
}
|
||||
fetchData = ()=>{
|
||||
fetchData = (name)=>{
|
||||
let me = this;
|
||||
const {fetchData, size} = this.props;
|
||||
let data = this.props.data || [];
|
||||
let from = data.length;
|
||||
return fetchData(from, size);
|
||||
return fetchData(name || '', size);
|
||||
}
|
||||
|
||||
handleInfiniteOnLoad = (page) => {
|
||||
handleInfiniteOnLoad = (name) => {
|
||||
let { data } = this.props;
|
||||
this.setState({
|
||||
loading: true,
|
||||
})
|
||||
this.fetchData().then((newdata)=>{
|
||||
this.fetchData(name).then((newdata)=>{
|
||||
let newState = {
|
||||
loading: false,
|
||||
};
|
||||
if(newdata.length < this.props.size){
|
||||
message.info("no more data");
|
||||
//message.info("no more data");
|
||||
newState.hasMore = false;
|
||||
}
|
||||
this.setState(newState);
|
||||
});
|
||||
}
|
||||
|
||||
handleInputChange = (e) =>{
|
||||
const name = e.target.value;
|
||||
this.setState({
|
||||
displayValue: name,
|
||||
})
|
||||
this.handleInfiniteOnLoad(name);
|
||||
}
|
||||
|
||||
|
||||
render(){
|
||||
let me = this;
|
||||
const {labelField} = this.props;
|
||||
const {labelField, clusterStatus} = this.props;
|
||||
let value = this.props.value || this.state.value;
|
||||
|
||||
let displayVaue = value[labelField];
|
||||
const menu = (<div className={styles.dropmenu} style={{width: this.props.width}}>
|
||||
<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
|
||||
initialLoad={false}
|
||||
loadMore={this.handleInfiniteOnLoad}
|
||||
hasMore={!this.state.loading && this.state.hasMore}
|
||||
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}>
|
||||
{(!this.props.data || !this.props.data.length)&& <div style={{display:'flex', justifyContent:'center', alignItems: 'center', height:50}}>匹配不到集群(匹配规则为前缀匹配)</div>}
|
||||
{(this.props.data || []).map((item)=>{
|
||||
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>
|
||||
// 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)
|
||||
}}
|
||||
/>
|
||||
})}
|
||||
</div>
|
||||
</InfiniteScroll>
|
||||
|
@ -114,11 +117,23 @@ class DropdownSelect extends React.Component{
|
|||
</div>
|
||||
)}
|
||||
</div>);
|
||||
const cstatus = clusterStatus ? clusterStatus[value?.id] : null;
|
||||
return(
|
||||
this.props.visible ?
|
||||
(<Dropdown overlay={menu} placement="bottomLeft">
|
||||
<Button className={styles['btn-ds']}>{value[labelField]} <Icon style={{float: 'right', marginTop: 3}}
|
||||
type="caret-down"/></Button>
|
||||
(<Dropdown overlay={menu} placement="bottomLeft" visible={this.state.overlayVisible}
|
||||
onVisibleChange={(flag)=>{
|
||||
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>) : ""
|
||||
)
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
margin-left: 15px;
|
||||
position: relative;
|
||||
bottom: 8px;
|
||||
height: 32px;
|
||||
span{
|
||||
color: #333;
|
||||
}
|
||||
|
@ -16,7 +17,7 @@
|
|||
|
||||
.dropmenu{
|
||||
box-shadow: 0 0 15px 0 rgba(0, 0, 0, .15);
|
||||
padding: 20px;
|
||||
padding: 0px;
|
||||
padding-bottom: 4px;
|
||||
width: 500px;
|
||||
background: #fff;
|
||||
|
@ -56,4 +57,28 @@
|
|||
.dslist{
|
||||
display: inline-block;
|
||||
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 { Icon } from 'antd';
|
||||
import { Icon, Select } from 'antd';
|
||||
import Link from 'umi/link';
|
||||
import Debounce from 'lodash-decorators/debounce';
|
||||
import styles from './index.less';
|
||||
|
@ -13,7 +13,7 @@ const path=require('path');
|
|||
export default class GlobalHeader extends PureComponent {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
super(props);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
@ -53,6 +53,7 @@ export default class GlobalHeader extends PureComponent {
|
|||
onClick={this.toggle}
|
||||
/>
|
||||
<DropdownSelect defaultValue={selectedCluster}
|
||||
clusterStatus={this.props.clusterStatus}
|
||||
value={selectedCluster}
|
||||
labelField="name"
|
||||
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) {
|
||||
indexPatternCache.clear(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 { getForceNow } from './lib/get_force_now';
|
||||
import { TimefilterConfig, InputTimeRange, TimeRangeBounds } from './types';
|
||||
import { getTime, RefreshInterval, TimeRange } from '../../../common';
|
||||
import { calculateBounds } from '../../../common/query/timefilter/get_time';
|
||||
import { RefreshInterval, TimeRange } from '../../../common';
|
||||
import {getTime, calculateBounds } from '../../../common/query/timefilter/get_time';
|
||||
|
||||
import { TimeHistoryContract } from './time_history';
|
||||
import { IndexPattern } from '../../index_patterns';
|
||||
|
|
|
@ -218,7 +218,7 @@ export class CreateIndexPatternWizard extends Component<
|
|||
return <LoadingState />;
|
||||
}
|
||||
|
||||
const header = this.renderHeader();
|
||||
// const header = this.renderHeader();
|
||||
|
||||
if (step === 1) {
|
||||
const { location } = this.props;
|
||||
|
@ -226,8 +226,8 @@ export class CreateIndexPatternWizard extends Component<
|
|||
|
||||
return (
|
||||
<EuiPageContent>
|
||||
{header}
|
||||
<EuiHorizontalRule />
|
||||
{/* {header}
|
||||
<EuiHorizontalRule /> */}
|
||||
<StepIndexPattern
|
||||
allIndices={allIndices}
|
||||
initialQuery={indexPattern || initialQuery}
|
||||
|
@ -245,8 +245,8 @@ export class CreateIndexPatternWizard extends Component<
|
|||
if (step === 2) {
|
||||
return (
|
||||
<EuiPageContent>
|
||||
{header}
|
||||
<EuiHorizontalRule />
|
||||
{/* {header}
|
||||
<EuiHorizontalRule /> */}
|
||||
<StepTimeField
|
||||
indexPattern={indexPattern}
|
||||
viewName={viewName}
|
||||
|
|
|
@ -18,76 +18,32 @@
|
|||
*/
|
||||
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 { CreateButton } from '../../create_button';
|
||||
import { Illustration } from './assets/index_pattern_illustration';
|
||||
import { ManagementAppMountParams } from '../../../../../management/public';
|
||||
|
||||
import Link from 'umi/link';
|
||||
import Exception from '@/components/Exception';
|
||||
import {Button} from 'antd';
|
||||
|
||||
interface Props {
|
||||
canSave: boolean;
|
||||
creationOptions: IndexPatternCreationOption[];
|
||||
docLinksIndexPatternIntro: string;
|
||||
setBreadcrumbs: ManagementAppMountParams['setBreadcrumbs'];
|
||||
}
|
||||
|
||||
export const EmptyIndexPatternPrompt = ({
|
||||
canSave,
|
||||
creationOptions,
|
||||
docLinksIndexPatternIntro,
|
||||
setBreadcrumbs,
|
||||
}: Props) => {
|
||||
setBreadcrumbs(getListBreadcrumbs());
|
||||
const actions = (<div>
|
||||
<Button type="primary" onClick={()=>{
|
||||
creationOptions[0].onClick();
|
||||
}}>创建视图</Button>
|
||||
</div>)
|
||||
|
||||
return (
|
||||
<EuiPageContent
|
||||
data-test-subj="emptyIndexPatternPrompt"
|
||||
className="inpEmptyIndexPatternPrompt"
|
||||
grow={false}
|
||||
horizontalPosition="center"
|
||||
>
|
||||
<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>
|
||||
<Exception
|
||||
type="404"
|
||||
title="没有视图"
|
||||
linkElement={Link}
|
||||
desc={'当前集群找不到任何数据视图'}
|
||||
actions={actions}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -17,165 +17,28 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import './empty_state.scss';
|
||||
import React from 'react';
|
||||
import { DocLinksStart, ApplicationStart } from 'kibana/public';
|
||||
import {
|
||||
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';
|
||||
import Exception from '@/components/Exception';
|
||||
import {Button} from 'antd';
|
||||
import Link from 'umi/link';
|
||||
import {router} from 'umi';
|
||||
|
||||
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 = (
|
||||
<EuiText color="subdued" textAlign="center" size="xs">
|
||||
Some indices may be hidden. Try to
|
||||
<EuiLink
|
||||
// {...reactRouterNavigate(useHistory(), 'create')}
|
||||
data-test-subj="createAnyway">
|
||||
create an index pattern
|
||||
</EuiLink> anyway.
|
||||
</EuiText>
|
||||
);
|
||||
const actions = (<div>
|
||||
<Button type="primary" onClick={()=>{
|
||||
router.push('/dev_tool');
|
||||
}}>创建索引</Button>
|
||||
</div>)
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiPageContent
|
||||
className="inpEmptyState"
|
||||
grow={false}
|
||||
horizontalPosition="center"
|
||||
data-test-subj="indexPatternEmptyState"
|
||||
>
|
||||
<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} */}
|
||||
</>
|
||||
<Exception
|
||||
type="404"
|
||||
title="没有数据"
|
||||
linkElement={Link}
|
||||
desc={'当前集群找不到任何数据索引'}
|
||||
actions={actions}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -32,17 +32,18 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import { withRouter, RouteComponentProps } from 'react-router-dom';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { reactRouterNavigate, useKibana } from '../../../../kibana_react/public';
|
||||
import { IndexPatternManagmentContext } from '../../types';
|
||||
import { CreateButton } from '../create_button';
|
||||
import { reactRouterNavigate } from '../../../../kibana_react/public';
|
||||
import { IndexPatternTableItem, IndexPatternCreationOption } from '../types';
|
||||
import { getIndexPatterns } from '../utils';
|
||||
import { getListBreadcrumbs } from '../breadcrumbs';
|
||||
import { EmptyState } from './empty_state';
|
||||
import { MatchedItem, ResolveIndexResponseItemAlias } from '../create_index_pattern_wizard/types';
|
||||
import { EmptyIndexPatternPrompt } from './empty_index_pattern_prompt';
|
||||
import { getIndices } from '../create_index_pattern_wizard/lib';
|
||||
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 = {
|
||||
initialPageSize: 10,
|
||||
|
@ -65,10 +66,28 @@ const search = {
|
|||
},
|
||||
};
|
||||
|
||||
const ariaRegion = 'Index patterns';
|
||||
|
||||
const ariaRegion = '数据视图';
|
||||
|
||||
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 {
|
||||
canSave: boolean;
|
||||
}
|
||||
|
@ -79,9 +98,7 @@ export const IndexPatternTable = ({ canSave, history, selectedCluster }: Props)
|
|||
savedObjects,
|
||||
uiSettings,
|
||||
indexPatternManagementStart,
|
||||
chrome,
|
||||
docLinks,
|
||||
application,
|
||||
http,
|
||||
getMlCardState,
|
||||
data,
|
||||
|
@ -165,38 +182,17 @@ export const IndexPatternTable = ({ canSave, history, selectedCluster }: Props)
|
|||
{
|
||||
field: 'title',
|
||||
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,
|
||||
sortable: ({ sort }: { sort: string }) => sort,
|
||||
},
|
||||
];
|
||||
|
||||
const createButton = canSave ? (
|
||||
<CreateButton options={creationOptions}>
|
||||
<Button icon="plus" type="primary" onClick={()=>{
|
||||
creationOptions[0].onClick();
|
||||
}}>
|
||||
创建{title}
|
||||
</CreateButton>
|
||||
</Button>
|
||||
) : (
|
||||
<></>
|
||||
);
|
||||
|
@ -210,43 +206,26 @@ export const IndexPatternTable = ({ canSave, history, selectedCluster }: Props)
|
|||
if (!indexPatterns.length) {
|
||||
if (!hasDataIndices && !remoteClustersExist) {
|
||||
return (
|
||||
<EmptyState
|
||||
onRefresh={loadSources}
|
||||
docLinks={docLinks}
|
||||
// navigateToApp={application.navigateToApp}
|
||||
getMlCardState={getMlCardState}
|
||||
canSave={canSave}
|
||||
/>
|
||||
<EmptyState/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<EmptyIndexPatternPrompt
|
||||
canSave={canSave}
|
||||
creationOptions={creationOptions}
|
||||
docLinksIndexPatternIntro={docLinks.links.indexPatterns.introduction}
|
||||
setBreadcrumbs={setBreadcrumbs}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const renderToolsRight = () => {
|
||||
return [
|
||||
createButton
|
||||
];
|
||||
};
|
||||
|
||||
return (
|
||||
<PageHeaderWrapper title="数据视图" content={content} extraContent={extraContent}>
|
||||
<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={{
|
||||
mapping: {
|
||||
'euiTablePagination.rowsPerPage': '每页行数',
|
||||
|
@ -261,10 +240,14 @@ export const IndexPatternTable = ({ canSave, history, selectedCluster }: Props)
|
|||
columns={columns}
|
||||
pagination={pagination}
|
||||
sorting={sorting}
|
||||
search={search}
|
||||
search={{
|
||||
...search,
|
||||
toolsRight: renderToolsRight(),
|
||||
}}
|
||||
/>
|
||||
</EuiContext>
|
||||
</EuiPageContent>
|
||||
</PageHeaderWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -27,10 +27,14 @@ class HeaderView extends PureComponent {
|
|||
|
||||
componentDidMount() {
|
||||
document.addEventListener('scroll', this.handScroll, { passive: true });
|
||||
this.fetchClusterStatus()
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener('scroll', this.handScroll);
|
||||
if(this.fetchClusterStatusTimer){
|
||||
clearTimeout(this.fetchClusterStatusTimer);
|
||||
}
|
||||
}
|
||||
|
||||
getHeadWidth = () => {
|
||||
|
@ -114,14 +118,13 @@ class HeaderView extends PureComponent {
|
|||
this.ticking = false;
|
||||
};
|
||||
|
||||
handleFetchClusterList = (from, size) => {
|
||||
handleFetchClusterList = (name, size) => {
|
||||
const { dispatch } = this.props;
|
||||
return dispatch({
|
||||
type: 'global/fetchClusterList',
|
||||
payload: {
|
||||
from,
|
||||
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() {
|
||||
const { isMobile, handleMenuCollapse, setting } = this.props;
|
||||
const { navTheme, layout, fixedHeader } = setting;
|
||||
|
@ -183,4 +200,5 @@ export default connect(({ user, global, setting, loading }) => ({
|
|||
clusterVisible: global.clusterVisible,
|
||||
clusterList: global.clusterList,
|
||||
selectedCluster: global.selectedCluster,
|
||||
clusterStatus: global.clusterStatus,
|
||||
}))(HeaderView);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { queryNotices } from '@/services/api';
|
||||
import {message} from "antd";
|
||||
import {searchClusterConfig} from "@/services/cluster";
|
||||
import {searchClusterConfig, getClusterStatus} from "@/services/cluster";
|
||||
import {formatESSearchResult, extractClusterIDFromURL} from '@/lib/elasticsearch/util';
|
||||
import {Modal} from 'antd';
|
||||
import router from "umi/router";
|
||||
|
@ -16,6 +16,10 @@ export default {
|
|||
clusterList: [],
|
||||
selectedCluster: {name:"Select cluster", id: ""},
|
||||
selectedClusterID: "",
|
||||
search:{
|
||||
cluster: {
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
effects: {
|
||||
|
@ -48,7 +52,7 @@ export default {
|
|||
return false;
|
||||
}
|
||||
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)=>{
|
||||
return {
|
||||
name: item.name,
|
||||
|
@ -58,7 +62,7 @@ export default {
|
|||
};
|
||||
})
|
||||
|
||||
if(clusterList.length === 0){
|
||||
if(clusterList.length === 0 && !payload.name){
|
||||
if(data.length === 0 ){
|
||||
Modal.info({
|
||||
title: '系统提示',
|
||||
|
@ -83,11 +87,20 @@ export default {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
let newClusterList = [];
|
||||
if(search.name != payload.name){
|
||||
newClusterList = data;
|
||||
}else{
|
||||
newClusterList = clusterList.concat(data);
|
||||
}
|
||||
yield put({
|
||||
type: 'saveData',
|
||||
payload: {
|
||||
clusterList: clusterList.concat(data),
|
||||
clusterList: newClusterList,
|
||||
search: {
|
||||
...search,
|
||||
cluster: payload,
|
||||
}
|
||||
}
|
||||
})
|
||||
return data;
|
||||
|
@ -124,7 +137,24 @@ export default {
|
|||
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: {
|
||||
|
|
|
@ -9,10 +9,31 @@ import {
|
|||
CreateIndexPatternWizardWithRouter,
|
||||
} from '../../components/kibana/index_pattern_management/public/components';
|
||||
// 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';
|
||||
|
||||
|
||||
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 history = useMemo(()=>{
|
||||
return new ScopedHistory(props.history, '/data/views');
|
||||
|
@ -35,23 +56,27 @@ const IndexPatterns = (props)=> {
|
|||
initFetch();
|
||||
}, [props.selectedCluster]);
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<Router history={history}>
|
||||
<Switch>
|
||||
<Route path={['/create']} >
|
||||
<CreateIndexPatternWizardWithRouter key={createComponentKey} />
|
||||
</Route>
|
||||
<Route path={['/patterns/:id/field/:fieldName', '/patterns/:id/create-field/']}>
|
||||
<CreateEditFieldContainer selectedCluster={props.selectedCluster}/>
|
||||
</Route>
|
||||
<Route path={['/patterns/:id']} >
|
||||
<EditIndexPatternContainer selectedCluster={props.selectedCluster}/>
|
||||
</Route>
|
||||
<Route path={['/']} >
|
||||
<IndexPatternTableWithRouter canSave={true} selectedCluster={props.selectedCluster}/>
|
||||
</Route>
|
||||
</Switch>
|
||||
</Router>
|
||||
<Router history={history}>
|
||||
<Switch>
|
||||
<Route path={['/create']} >
|
||||
<PageHeaderWrapper title="创建视图" content={createContent} extraContent={createExtraContent}>
|
||||
<CreateIndexPatternWizardWithRouter key={createComponentKey} />
|
||||
</PageHeaderWrapper>
|
||||
</Route>
|
||||
<Route path={['/patterns/:id/field/:fieldName', '/patterns/:id/create-field/']}>
|
||||
<CreateEditFieldContainer selectedCluster={props.selectedCluster}/>
|
||||
</Route>
|
||||
<Route path={['/patterns/:id']} >
|
||||
<EditIndexPatternContainer selectedCluster={props.selectedCluster}/>
|
||||
</Route>
|
||||
<Route path={['/']} >
|
||||
<IndexPatternTableWithRouter canSave={true} selectedCluster={props.selectedCluster}/>
|
||||
</Route>
|
||||
</Switch>
|
||||
</Router>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||
import {Button, Card, Col, Divider, Form, Input, Row, Table, Switch, Icon, Popconfirm, message} from "antd";
|
||||
import Link from "umi/link";
|
||||
import {connect} from "dva";
|
||||
import {HealthStatusCircle} from './health_status';
|
||||
import {HealthStatusCircle} from '@/components/infini/health_status_circle';
|
||||
import PageHeaderWrapper from '@/components/PageHeaderWrapper';
|
||||
import styles from './step.less';
|
||||
import clusterBg from '@/assets/cluster_bg.png';
|
||||
|
@ -25,8 +25,9 @@ const extraContent = (
|
|||
);
|
||||
|
||||
@Form.create()
|
||||
@connect(({clusterConfig}) =>({
|
||||
clusterConfig
|
||||
@connect(({clusterConfig, global}) =>({
|
||||
clusterConfig,
|
||||
clusterStatus: global.clusterStatus,
|
||||
}))
|
||||
class Index extends React.Component {
|
||||
columns = [{
|
||||
|
@ -38,12 +39,17 @@ class Index extends React.Component {
|
|||
dataIndex: 'id',
|
||||
key: 'health_status',
|
||||
render: (val)=>{
|
||||
const {clusterStatus} = this.props.clusterConfig;
|
||||
const {clusterStatus} = this.props;
|
||||
if(!clusterStatus || !clusterStatus[val]){
|
||||
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;
|
||||
return <HealthStatusCircle status={status}/>
|
||||
|
||||
}
|
||||
},{
|
||||
title: '所属业务',
|
||||
|
@ -79,7 +85,7 @@ class Index extends React.Component {
|
|||
dataIndex: 'id',
|
||||
key: 'mode_count',
|
||||
render: (val)=>{
|
||||
const {clusterStatus} = this.props.clusterConfig;
|
||||
const {clusterStatus} = this.props;
|
||||
if(!clusterStatus || !clusterStatus[val]){
|
||||
return
|
||||
}
|
||||
|
@ -141,25 +147,6 @@ class Index extends React.Component {
|
|||
}
|
||||
componentDidMount() {
|
||||
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 = ()=>{
|
||||
|
@ -224,7 +211,7 @@ class Index extends React.Component {
|
|||
return (
|
||||
<PageHeaderWrapper title="集群管理" content={content} extraContent={extraContent}>
|
||||
<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>
|
||||
<Form>
|
||||
<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,
|
||||
getClusterStatus, tryConnect} from "@/services/cluster";
|
||||
tryConnect} from "@/services/cluster";
|
||||
import {message} from "antd";
|
||||
import {formatESSearchResult} from '@/lib/elasticsearch/util';
|
||||
|
||||
|
@ -10,20 +10,6 @@ export default {
|
|||
editValue: {},
|
||||
},
|
||||
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}){
|
||||
let res = yield call(searchClusterConfig, payload);
|
||||
if(res.error){
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
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()
|
||||
export class ExtraStep extends React.Component {
|
||||
|
|
|
@ -42,7 +42,9 @@ export async function searchClusterConfig(params) {
|
|||
let url = `/elasticsearch/_search`;
|
||||
let args = buildQueryArgs({
|
||||
name: params.name,
|
||||
enabled: params.enabled
|
||||
enabled: params.enabled,
|
||||
from: params.from,
|
||||
size: params.size,
|
||||
});
|
||||
if(args.length > 0){
|
||||
url += args;
|
||||
|
|
Loading…
Reference in New Issue