diff --git a/web/config/config.js b/web/config/config.js index 726d0f15..08b140ea 100644 --- a/web/config/config.js +++ b/web/config/config.js @@ -69,7 +69,7 @@ export default { // }, proxy: { '/_search-center/': { - target: 'http://localhost:2900', + target: 'http://localhost:9000', changeOrigin: true, // pathRewrite: { '^/server': '' }, }, diff --git a/web/config/router.config.js b/web/config/router.config.js index fc6c109d..568571c3 100644 --- a/web/config/router.config.js +++ b/web/config/router.config.js @@ -304,7 +304,7 @@ export default [ }, { path: '/system/cluster/edit', - name: 'edit-cluster', + name: 'editCluster', component: './System/Cluster/Form', hideInMenu: true }, diff --git a/web/mock/system/cluster.js b/web/mock/system/cluster.js new file mode 100644 index 00000000..c6677b8b --- /dev/null +++ b/web/mock/system/cluster.js @@ -0,0 +1,75 @@ +export default { + // 'post /_search-center/system/cluster/_search': function(req, res){ + // res.send({ + // "took": 0, + // "timed_out": false, + // "hits": { + // "total": { + // "relation": "eq", + // "value": 1 + // }, + // "max_score": 1, + // "hits": [ + // { + // "_index": ".infini-search-center_cluster", + // "_type": "_doc", + // "_id": "c0oc4kkgq9s8qss2uk50", + // "_source": { + // "basic_auth": { + // "password": "123", + // "username": "medcl" + // }, + // "created": "2021-02-20T16:03:30.867084+08:00", + // "description": "xx业务集群1", + // "enabled": false, + // "endpoint": "http://localhost:9200", + // "name": "cluster1", + // "updated": "2021-02-20T16:03:30.867084+08:00" + // } + // } + // ] + // } + // }) + // }, + // 'post /_search-center/system/cluster': function(req, res){ + // res.send({ + // "_id": "c0obhd4gq9s7akom0o60", + // "_source": { + // "name": "cluster1", + // "endpoint": "http://localhost:9200", + // "basic_auth": { + // "username": "medcl", + // "password": "123" + // }, + // "description": "xx业务集群1", + // "enabled": false, + // "created": "2021-02-20T15:12:50.984062+08:00", + // "updated": "2021-02-20T15:12:50.984062+08:00" + // }, + // "result": "created" + // }); + // }, + // 'put /_search-center/system/cluster/:id': function(req, res){ + // res.send({ + // "_id": "c0obhd4gq9s7akom0o60", + // "_source": { + // "basic_auth": { + // "password": "456", + // "username": "medcl" + // }, + // "description": "xx业务集群2", + // "endpoint": "http://localhost:9201", + // "name": "cluster2", + // "updated": "2021-02-20T15:25:12.159789+08:00" + // }, + // "result": "updated" + // }); + // }, + // + // 'delete /_search-center/system/cluster/:id': function(req, res){ + // res.send({ + // "_id": "c0obk7cgq9s7hi05aou0", + // "result": "deleted" + // }); + // } +} \ No newline at end of file diff --git a/web/src/components/GlobalHeader/DropdownSelect.js b/web/src/components/GlobalHeader/DropdownSelect.js index 94e36ff7..df5b23a6 100644 --- a/web/src/components/GlobalHeader/DropdownSelect.js +++ b/web/src/components/GlobalHeader/DropdownSelect.js @@ -6,7 +6,6 @@ import styles from './DropdownSelect.less'; class DropdownSelect extends React.Component{ state={ value: this.props.defaultValue, - data: this.props.data || [], loading: false, hasMore: true, } @@ -24,26 +23,23 @@ class DropdownSelect extends React.Component{ } componentDidMount(){ - let data = []; - for(let i = 1; i<=28; i++){ - data.push('cluster'+i) - } - this.setState({ - data: data, + let me = this; + this.fetchData().then((data)=>{ + let hasMore = true; + if(data.length < this.props.size){ + hasMore = false; + } + me.setState({ + hasMore + }) }) } fetchData = ()=>{ let me = this; - return new Promise(resolve => { - setTimeout(() => { - let start = me.state.data.length; - let data =[] - for(let i = start + 1; i { @@ -51,28 +47,24 @@ class DropdownSelect extends React.Component{ this.setState({ loading: true, }) - if (data.length > 50) { - message.warning('No more data'); - this.setState({ - hasMore: false, - loading: false, - }); - return; - } this.fetchData().then((newdata)=>{ - data = data.concat(newdata); - this.setState({ - data, + let newState = { loading: false, - }); + }; + if(newdata.length < this.props.size){ + message.info("no more data"); + newState.hasMore = false; + } + this.setState(newState); }); } render(){ let me = this; - const menu = (
-
+ const {labelField} = this.props; + const menu = (
+
( - - + + )} > @@ -106,9 +98,11 @@ class DropdownSelect extends React.Component{ )}
); return( - - - + this.props.visible ? + ( + + ) : "" ) } diff --git a/web/src/components/GlobalHeader/DropdownSelect.less b/web/src/components/GlobalHeader/DropdownSelect.less index 7a2d6805..b9b49117 100644 --- a/web/src/components/GlobalHeader/DropdownSelect.less +++ b/web/src/components/GlobalHeader/DropdownSelect.less @@ -32,7 +32,7 @@ .infiniteContainer { overflow: auto; overflow-x: hidden; - height: 300px; + max-height: 300px; } .loadingContainer { position: absolute; diff --git a/web/src/components/GlobalHeader/index.js b/web/src/components/GlobalHeader/index.js index 666f7172..bf0b0cd5 100644 --- a/web/src/components/GlobalHeader/index.js +++ b/web/src/components/GlobalHeader/index.js @@ -24,7 +24,7 @@ export default class GlobalHeader extends PureComponent { this.triggerResizeEvent(); }; render() { - const { collapsed, isMobile, logo } = this.props; + const { collapsed, isMobile, logo, clusterVisible, clusterList } = this.props; return (
{isMobile && ( @@ -37,9 +37,34 @@ export default class GlobalHeader extends PureComponent { type={collapsed ? 'menu-unfold' : 'menu-fold'} onClick={this.toggle} /> - {}} - data={['cluster1', 'cluster2','cluster3', 'cluster4','cluster5', 'cluster6']}/> + { + this.props.handleSaveGlobalState({ + selectedCluster: item + }) + }} + size={56} + fetchData={ + this.props.onFetchClusterList + // (from, size)=>{ + // return new Promise(resolve => { + // setTimeout(() => { + // let start = from; + // let data =[] + // for(let i = start + 1; i 56){ + // break; + // } + // data.push('cluster'+i) + // } + // resolve(data) + // }, 2000) + // }); + // } + } + data={clusterList}/>
); diff --git a/web/src/layouts/Header.js b/web/src/layouts/Header.js index 385a8853..e6c2507a 100644 --- a/web/src/layouts/Header.js +++ b/web/src/layouts/Header.js @@ -114,6 +114,25 @@ class HeaderView extends PureComponent { this.ticking = false; }; + handleFetchClusterList = (from, size) => { + const { dispatch } = this.props; + return dispatch({ + type: 'global/fetchClusterList', + payload: { + from, + size, + } + }); + }; + + handleSaveGlobalState = (newState) => { + const { dispatch } = this.props; + return dispatch({ + type: 'global/saveData', + payload: newState + }); + } + render() { const { isMobile, handleMenuCollapse, setting } = this.props; const { navTheme, layout, fixedHeader } = setting; @@ -139,6 +158,8 @@ class HeaderView extends PureComponent { onNoticeClear={this.handleNoticeClear} onMenuClick={this.handleMenuClick} onNoticeVisibleChange={this.handleNoticeVisibleChange} + onFetchClusterList={this.handleFetchClusterList} + handleSaveGlobalState={this.handleSaveGlobalState} {...this.props} /> )} @@ -158,4 +179,6 @@ export default connect(({ user, global, setting, loading }) => ({ fetchingNotices: loading.effects['global/fetchNotices'], notices: global.notices, setting, + clusterVisible: global.clusterVisible, + clusterList: global.clusterList, }))(HeaderView); diff --git a/web/src/locales/en-US.js b/web/src/locales/en-US.js index 19d28205..19840c3a 100644 --- a/web/src/locales/en-US.js +++ b/web/src/locales/en-US.js @@ -120,6 +120,7 @@ export default { 'menu.system': 'SYSTEM', 'menu.system.cluster': 'CLUSTER', + 'menu.system.editCluster': 'EDIT CLUSTER', 'menu.system.settings': 'SETTINGS', 'menu.system.settings.global': 'GLOBAL', 'menu.system.settings.gateway': 'GATEWAY', diff --git a/web/src/locales/zh-CN.js b/web/src/locales/zh-CN.js index e4afc234..c9985e19 100644 --- a/web/src/locales/zh-CN.js +++ b/web/src/locales/zh-CN.js @@ -127,6 +127,7 @@ export default { 'menu.system': '系统管理', 'menu.system.cluster': '集群管理', + 'menu.system.editCluster': '集群编辑', 'menu.system.settings': '系统设置', 'menu.system.settings.global': '全局设置', 'menu.system.settings.gateway': '网关设置', diff --git a/web/src/models/global.js b/web/src/models/global.js index 62f5c6a2..bd2397e0 100644 --- a/web/src/models/global.js +++ b/web/src/models/global.js @@ -1,4 +1,8 @@ import { queryNotices } from '@/services/api'; +import {message} from "antd"; +import {searchClusterConfig} from "@/services/clusterConfig"; +import {formatESSearchResult} from '@/lib/elasticsearch/util'; + export default { namespace: 'global', @@ -6,6 +10,9 @@ export default { state: { collapsed: false, notices: [], + clusterVisible: true, + clusterList: [], + selectedCluster: '', }, effects: { @@ -31,6 +38,29 @@ export default { payload: count, }); }, + *fetchClusterList({payload}, {call, put, select}){ + let res = yield call(searchClusterConfig, payload); + if(res.error){ + message.error(res.error) + return false; + } + res = formatESSearchResult(res) + let clusterList = yield select(state => state.global.clusterList); + let data = res.data.map((item)=>{ + return { + name: item.name, + id: item.id, + }; + }) + + yield put({ + type: 'saveData', + payload: { + clusterList: clusterList.concat(data) + } + }) + return data; + }, }, reducers: { @@ -52,12 +82,43 @@ export default { notices: state.notices.filter(item => item.type !== payload), }; }, + saveData(state, {payload}){ + return { + ...state, + ...payload, + } + }, + removeCluster(state, {payload}){ + return { + ...state, + clusterList: state.clusterList.filter(item => item.id !== payload.id) + } + }, + addCluster(state, {payload}){ + state.clusterList.push(payload) + return state; + }, + updateCluster(state, {payload}){ + let idx = state.clusterList.findIndex(item => item.id === payload.id); + idx > -1 && (state.clusterList[idx].name = payload.name); + return state; + } }, subscriptions: { - setup({ history }) { + setup({ history, dispatch }) { // Subscribe history(url) change, trigger `load` action if pathname is `/` return history.listen(({ pathname, search }) => { + let clusterVisible = true; + if(pathname.startsWith("/system")){ + clusterVisible = false; + } + dispatch({ + type: 'saveData', + payload: { + clusterVisible, + } + }) if (typeof window.ga !== 'undefined') { window.ga('send', 'pageview', pathname + search); } diff --git a/web/src/pages/Cluster/Overview.js b/web/src/pages/Cluster/Overview.js index 696692d3..083ef77a 100644 --- a/web/src/pages/Cluster/Overview.js +++ b/web/src/pages/Cluster/Overview.js @@ -1,8 +1,26 @@ import React, {Fragment} from 'react'; -import {Card, Divider, Popconfirm, Table} from "antd"; +import {Card, Divider, Popconfirm, Row, Col, Table, Descriptions} from "antd"; import {Link} from "umi" import moment from "moment"; +import styles from "./Overview.less"; +import {connect} from "dva"; +let HealthCircle = (props)=>{ + return ( +
+ ) +} + +@connect(({global}) => ({ + selectedCluster: global.selectedCluster +})) class Overview extends React.Component { state = { data: [{id:"JFpIbacZQamv9hkgQEDZ2Q", name:"single-es", endpoint:"http://localhost:9200", health: "green", version: "7.10.0", uptime:"320883955"}] @@ -39,13 +57,43 @@ class Overview extends React.Component { } ]; render() { - return ( - + return ( + + + + + Healthy + 7.10.0 + 3 天 + Basic + + + + + + + + 83.21% +

775.1 GB / 931.5 GB

+
+ + 27.60% +

565.3 GB / 2.0 GB

+
+
+
+ + + + + 20,812,087 + 1.1 GB + 28 + 26 + + + + , ) } } diff --git a/web/src/pages/Cluster/Overview.less b/web/src/pages/Cluster/Overview.less new file mode 100644 index 00000000..85036184 --- /dev/null +++ b/web/src/pages/Cluster/Overview.less @@ -0,0 +1,19 @@ +.overview{ + :global(.ant-descriptions-row){ + border-bottom: none; + } + :global(.ant-descriptions-item-label){ + border-right: none; + background-color: #fff; + font-weight: bold; + } + :global(.ant-descriptions-view){ + border: none; + } + :global(.ant-descriptions-item-content){ + color: #333; + } + .light{ + color: #666; + } +} \ No newline at end of file diff --git a/web/src/pages/System/Cluster/Form.js b/web/src/pages/System/Cluster/Form.js index 4a6a7f3f..73403107 100644 --- a/web/src/pages/System/Cluster/Form.js +++ b/web/src/pages/System/Cluster/Form.js @@ -1,5 +1,5 @@ import React from 'react'; -import {Card, Form, Icon, Input, InputNumber, Button, Switch} from 'antd'; +import {Card, Form, Icon, Input, InputNumber, Button, Switch, message} from 'antd'; import router from 'umi/router'; import styles from './Form.less'; @@ -42,15 +42,23 @@ class ClusterForm extends React.Component{ } //console.log(values); let newVals = { - ...values + name: values.name, + endpoint: values.endpoint, + basic_auth: { + username: values.username, + password: values.password, + }, + description: values.description, + enabled: values.enabled, + order: values.order, } - delete(newVals['confirm']); if(clusterConfig.editMode === 'NEW') { dispatch({ type: 'clusterConfig/addCluster', payload: newVals, }).then(function (rel){ if(rel){ + message.success("添加成功") router.push('/system/cluster'); } }); @@ -61,6 +69,7 @@ class ClusterForm extends React.Component{ payload: newVals, }).then(function (rel){ if(rel){ + message.success("修改成功") router.push('/system/cluster'); } }); @@ -124,34 +133,21 @@ class ClusterForm extends React.Component{ {getFieldDecorator('username', { - initialValue: editValue.username, + initialValue: editValue.basic_auth.username, rules: [ ], })()} {getFieldDecorator('password', { - initialValue: editValue.password, + initialValue: editValue.basic_auth.password, rules: [ - { - validator: this.validateToNextPassword, - }, ], })()} - - {getFieldDecorator('confirm', { - initialValue: editValue.password, - rules: [ - { - validator: this.compareToFirstPassword, - }, - ], - })()} - {getFieldDecorator('order', { - initialValue: editValue.order, + initialValue: editValue.order || 0, })()} diff --git a/web/src/pages/System/Cluster/Index.js b/web/src/pages/System/Cluster/Index.js index f8ed0105..d14c3d99 100644 --- a/web/src/pages/System/Cluster/Index.js +++ b/web/src/pages/System/Cluster/Index.js @@ -1,5 +1,5 @@ import React from 'react'; -import {Button, Card, Col, Divider, Form, Input, Row, Table, Switch, Icon, Popconfirm} from "antd"; +import {Button, Card, Col, Divider, Form, Input, Row, Table, Switch, Icon, Popconfirm, message} from "antd"; import Link from "umi/link"; import {connect} from "dva"; @@ -18,12 +18,15 @@ class Index extends React.Component { key: 'endpoint', },{ title: '用户名', - dataIndex: 'username', + dataIndex: 'basic_auth.username', key: 'username', },{ title: '密码', - dataIndex: 'password', + dataIndex: 'basic_auth.password', key: 'password', + render: (val) =>{ + return "******"; + } },{ title: '排序权重', dataIndex: 'order', @@ -60,7 +63,9 @@ class Index extends React.Component { }) } componentDidMount() { - this.fetchData({}) + if(typeof this.props.clusterConfig.data === 'undefined') { + this.fetchData({}) + } } handleSearchClick = ()=>{ @@ -77,6 +82,10 @@ class Index extends React.Component { payload: { id: record.id } + }).then((result)=>{ + if(result){ + message.success("删除成功"); + } }); } @@ -92,7 +101,7 @@ class Index extends React.Component { handleNewClick = () => { this.saveData({ editMode: 'NEW', - editValue: {}, + editValue: {basic_auth: {}}, }) } handleEditClick = (record)=>{ diff --git a/web/src/pages/System/Cluster/models/cluster.js b/web/src/pages/System/Cluster/models/cluster.js index e9b8b798..15090435 100644 --- a/web/src/pages/System/Cluster/models/cluster.js +++ b/web/src/pages/System/Cluster/models/cluster.js @@ -27,6 +27,28 @@ export default { message.error(res.error) return false; } + let {data, total} = yield select(state => state.clusterConfig); + data.unshift({ + ...res._source, + id: res._id, + }); + yield put({ + type: 'saveData', + payload: { + data, + total: { + ...total, + value: total.value + 1 + }, + } + }) + yield put({ + type: 'global/addCluster', + payload: { + id: res._id, + name: res._source.name, + } + }) return res; }, *updateCluster({payload}, {call, put, select}) { @@ -35,6 +57,27 @@ export default { message.error(res.error) return false; } + let {data} = yield select(state => state.clusterConfig); + let idx = data.findIndex((item)=>{ + return item.id === res._id; + }); + data[idx] = { + ...data[idx], + ...res._source + }; + yield put({ + type: 'saveData', + payload: { + data + } + }) + yield put({ + type: 'global/updateCluster', + payload: { + id: res._id, + name: res._source.name, + } + }) return res; }, *deleteCluster({payload}, {call, put, select}) { @@ -51,7 +94,16 @@ export default { type: 'saveData', payload: { data, - total: total -1, + total: { + ...total, + value: total.value + 1 + } + } + }) + yield put({ + type: 'global/removeCluster', + payload: { + id: payload.id } }) return res; diff --git a/web/src/services/clusterConfig.js b/web/src/services/clusterConfig.js index 21637846..2be3d123 100644 --- a/web/src/services/clusterConfig.js +++ b/web/src/services/clusterConfig.js @@ -9,7 +9,9 @@ export async function createClusterConfig(params) { } export async function updateClusterConfig(params) { - return request(`${pathPrefix}/system/cluster/${params.id}`, { + let id = params.id; + delete(params['id']); + return request(`${pathPrefix}/system/cluster/${id}`, { method: 'PUT', body: params, }); @@ -23,7 +25,7 @@ export async function deleteClusterConfig(params) { } export async function searchClusterConfig(params) { - let url = `${pathPrefix}/system/cluster`; + let url = `${pathPrefix}/system/cluster/_search`; let args = buildQueryArgs({ name: params.name, enabled: params.enabled