diff --git a/src/resources/cluster_api.py b/src/resources/cluster_api.py
index 98a4ea99a..03d5d0ed8 100644
--- a/src/resources/cluster_api.py
+++ b/src/resources/cluster_api.py
@@ -214,24 +214,28 @@ def cluster_create():
"""
logger.info("/cluster action=" + r.method)
request_debug(r, logger)
- if not r.form["name"] or not r.form["host_id"] or \
- not r.form["network_type"]:
+ if r.content_type.startswith("application/json"):
+ body = dict(r.get_json(force=True, silent=True))
+ else:
+ body = r.form
+ if not body["name"] or not body["host_id"] or \
+ not body["network_type"]:
error_msg = "cluster post without enough data"
logger.warning(error_msg)
- return make_fail_resp(error=error_msg, data=r.form)
+ return make_fail_resp(error=error_msg, data=body)
name, host_id, network_type, size = \
- r.form['name'], r.form['host_id'],\
- r.form['network_type'], int(r.form['size'])
+ body['name'], body['host_id'],\
+ body['network_type'], int(body['size'])
if network_type == NETWORK_TYPE_FABRIC_PRE_V1: # TODO: deprecated soon
config = FabricPreNetworkConfig(
- consensus_plugin=r.form['consensus_plugin'],
- consensus_mode=r.form['consensus_mode'],
+ consensus_plugin=body['consensus_plugin'],
+ consensus_mode=body['consensus_mode'],
size=size)
elif network_type == NETWORK_TYPE_FABRIC_V1:
config = FabricV1NetworkConfig(
- consensus_plugin=r.form['consensus_plugin'],
+ consensus_plugin=body['consensus_plugin'],
size=size)
else:
error_msg = "Unknown network_type={}".format(network_type)
@@ -308,7 +312,8 @@ def cluster_list():
elif r.method == 'POST':
f.update(request_json_body(r))
logger.info(f)
- result = cluster_handler.list(filter_data=f)
+ col_name = f.get("state", "active")
+ result = cluster_handler.list(filter_data=f, col_name=col_name)
logger.error(result)
return make_ok_resp(data=result)
diff --git a/src/themes/react/static/dashboard/src/common/router.js b/src/themes/react/static/dashboard/src/common/router.js
index 0a9f7cf17..a029369e2 100644
--- a/src/themes/react/static/dashboard/src/common/router.js
+++ b/src/themes/react/static/dashboard/src/common/router.js
@@ -85,7 +85,12 @@ export const getRouterData = app => {
component: dynamicWrapper(app, ['host'], () => import('../routes/Host/CreateHost')),
},
'/chain': {
- component: dynamicWrapper(app, [], () => import('../routes/Chain')),
+ component: dynamicWrapper(app, ['chain'], () => import('../routes/Chain')),
+ },
+ '/create-chain': {
+ component: dynamicWrapper(app, ['host', 'chain'], () =>
+ import('../routes/Chain/CreateChain')
+ ),
},
'/user-management': {
component: dynamicWrapper(app, [], () => import('../routes/UserManagement')),
diff --git a/src/themes/react/static/dashboard/src/locales/en.json b/src/themes/react/static/dashboard/src/locales/en.json
index f46168266..fee31ba64 100755
--- a/src/themes/react/static/dashboard/src/locales/en.json
+++ b/src/themes/react/static/dashboard/src/locales/en.json
@@ -60,5 +60,28 @@
"Host.Create.Validate.Label.Schedulable": "Schedulable",
"Host.Create.Validate.Label.Filled": "Auto Filled",
"Host.Create.Button.Submit": "Submit",
- "Host.Create.Button.Cancel": "Cancel"
+ "Host.Create.Button.Cancel": "Cancel",
+ "Chain.Messages.Operate.Success.Restart": "Restart chain {name} successfully.",
+ "Chain.Messages.Operate.Success.Start": "Start chain {name} successfully.",
+ "Chain.Messages.Operate.Success.Stop": "Stop chain {name} successfully.",
+ "Chain.Messages.Operate.Success.Release": "Release chain successfully",
+ "Chain.Messages.Confirm.DeleteChain": "Do you want to delete chain {name}?",
+ "Chain.Title": "Chains",
+ "Chain.Button.Restart": "Restart",
+ "Chain.Button.Start": "Start",
+ "Chain.Button.Stop": "Stop",
+ "Chain.Button.Release": "Release",
+ "Chain.Button.More": "More",
+ "Chain.Button.Delete": "Delete",
+ "Chain.Button.Add": "Add",
+ "Chain.Radio.Option.Active": "Active",
+ "Chain.Radio.Option.Released": "Released",
+ "Chain.Label.NetworkType": "Network Type",
+ "Chain.Label.ConsensusPlugin": "Consensus Plugin",
+ "Chain.Label.Owner": "Owner",
+ "Chain.Label.CreateTime": "Create Time",
+ "Chain.Create.Title": "Create New Chain",
+ "Chain.Create.Validate.Required.Host": "Must select a host",
+ "Chain.Create.Label.Host": "Host",
+ "Chain.Create.Label.ChainSize": "Chain Size"
}
diff --git a/src/themes/react/static/dashboard/src/locales/zh.json b/src/themes/react/static/dashboard/src/locales/zh.json
index 313f74a67..ff64e10db 100755
--- a/src/themes/react/static/dashboard/src/locales/zh.json
+++ b/src/themes/react/static/dashboard/src/locales/zh.json
@@ -60,5 +60,28 @@
"Host.Create.Validate.Label.Schedulable": "可调度",
"Host.Create.Validate.Label.Filled": "自动填充",
"Host.Create.Button.Submit": "提交",
- "Host.Create.Button.Cancel": "取消"
+ "Host.Create.Button.Cancel": "取消",
+ "Chain.Messages.Operate.Success.Restart": "重启链 {name} 成功。",
+ "Chain.Messages.Operate.Success.Start": "启动链 {name} 成功。",
+ "Chain.Messages.Operate.Success.Stop": "停止链 {name} 成功。",
+ "Chain.Messages.Operate.Success.Release": "释放链成功",
+ "Chain.Messages.Confirm.DeleteChain": "是否确认删除链 {name}?",
+ "Chain.Title": "链",
+ "Chain.Button.Restart": "重启",
+ "Chain.Button.Start": "启动",
+ "Chain.Button.Stop": "停止",
+ "Chain.Button.Release": "释放",
+ "Chain.Button.More": "更多",
+ "Chain.Button.Delete": "删除",
+ "Chain.Button.Add": "添加",
+ "Chain.Radio.Option.Active": "激活",
+ "Chain.Radio.Option.Released": "释放",
+ "Chain.Label.NetworkType": "网络类型",
+ "Chain.Label.ConsensusPlugin": "共识",
+ "Chain.Label.Owner": "使用者",
+ "Chain.Label.CreateTime": "创建时间",
+ "Chain.Create.Title": "创建新的链",
+ "Chain.Create.Validate.Required.Host": "必须选择一个主机",
+ "Chain.Create.Label.Host": "主机",
+ "Chain.Create.Label.ChainSize": "链大小"
}
diff --git a/src/themes/react/static/dashboard/src/models/chain.js b/src/themes/react/static/dashboard/src/models/chain.js
new file mode 100644
index 000000000..54fb520b9
--- /dev/null
+++ b/src/themes/react/static/dashboard/src/models/chain.js
@@ -0,0 +1,96 @@
+/*
+ SPDX-License-Identifier: Apache-2.0
+*/
+import { routerRedux } from 'dva/router';
+import { IntlProvider, defineMessages } from 'react-intl';
+import { message } from 'antd';
+import { queryChains, operateChain, deleteChain, createChain } from '../services/chain';
+import { getLocale } from '../utils/utils';
+
+const currentLocale = getLocale();
+const intlProvider = new IntlProvider(
+ { locale: currentLocale.locale, messages: currentLocale.messages },
+ {}
+);
+const { intl } = intlProvider.getChildContext();
+
+const messages = defineMessages({
+ operate: {
+ success: {
+ restart: {
+ id: 'Chain.Messages.Operate.Success.Restart',
+ defaultMessage: '重启链 {name} 成功。',
+ },
+ start: {
+ id: 'Chain.Messages.Operate.Success.Start',
+ defaultMessage: '启动链 {name} 成功。',
+ },
+ stop: {
+ id: 'Chain.Messages.Operate.Success.Stop',
+ defaultMessage: '停止链 {name} 成功。',
+ },
+ release: {
+ id: 'Chain.Messages.Operate.Success.Release',
+ defaultMessage: '释放链成功。',
+ },
+ },
+ },
+});
+
+export default {
+ namespace: 'chain',
+
+ state: {
+ chains: [],
+ },
+
+ effects: {
+ *fetchChains({ payload }, { call, put }) {
+ const response = yield call(queryChains, payload);
+ yield put({
+ type: 'setChains',
+ payload: response.data,
+ });
+ },
+ *operateChain({ payload }, { call, put }) {
+ const response = yield call(operateChain, payload);
+ yield put({
+ type: 'fetchChains',
+ });
+ if (response.code === 200) {
+ const values = { name: payload.name };
+ message.success(intl.formatMessage(messages.operate.success[payload.action], values));
+ }
+ },
+ *deleteChain({ payload }, { call, put }) {
+ const response = yield call(deleteChain, payload);
+ if (response.code === 200) {
+ message.success(`Delete Chain ${payload.name} successfully`);
+ }
+ yield put({
+ type: 'fetchChains',
+ });
+ },
+ *createChain({ payload }, { call, put }) {
+ const response = yield call(createChain, payload);
+ if (response.code === 201) {
+ message.success('Create Chain successfully');
+ yield put(
+ routerRedux.push({
+ pathname: '/chain',
+ })
+ );
+ }
+ yield call(payload.callback);
+ },
+ },
+
+ reducers: {
+ setChains(state, action) {
+ return {
+ ...state,
+ chains: action.payload,
+ };
+ },
+ },
+};
diff --git a/src/themes/react/static/dashboard/src/routes/Chain/CreateChain/index.js b/src/themes/react/static/dashboard/src/routes/Chain/CreateChain/index.js
new file mode 100644
index 000000000..2639047de
--- /dev/null
+++ b/src/themes/react/static/dashboard/src/routes/Chain/CreateChain/index.js
@@ -0,0 +1,242 @@
+/*
+ SPDX-License-Identifier: Apache-2.0
+*/
+import React, { PureComponent } from 'react';
+import { Card, Form, Input, Button, Select } from 'antd';
+import { routerRedux } from 'dva/router';
+import { connect } from 'dva';
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+import PageHeaderLayout from '../../../layouts/PageHeaderLayout';
+import styles from './index.less';
+
+const FormItem = Form.Item;
+const { Option } = Select;
+
+const messages = defineMessages({
+ updateTitle: {
+ id: 'Host.Create.UpdateTitle',
+ defaultMessage: 'Update Host',
+ },
+ title: {
+ id: 'Chain.Create.Title',
+ defaultMessage: 'Create New Chain',
+ },
+ subTitle: {
+ id: 'Host.Create.SubTitle',
+ defaultMessage: 'Here you can create multiple type host, for creating fabric cluster.',
+ },
+ label: {
+ name: {
+ id: 'Host.Create.Validate.Label.Name',
+ defaultMessage: 'Name',
+ },
+ host: {
+ id: 'Chain.Create.Label.Host',
+ defaultMessage: 'Host',
+ },
+ networkType: {
+ id: 'Chain.Label.NetworkType',
+ defaultMessage: 'Network Type',
+ },
+ consensusPlugin: {
+ id: 'Chain.Label.ConsensusPlugin',
+ defaultMessage: 'Consensus Plugin',
+ },
+ chainSize: {
+ id: 'Chain.Create.Label.ChainSize',
+ defaultMessage: 'Chain Size',
+ },
+ },
+ button: {
+ submit: {
+ id: 'Host.Create.Button.Submit',
+ defaultMessage: 'Submit',
+ },
+ cancel: {
+ id: 'Host.Create.Button.Cancel',
+ defaultMessage: 'Cancel',
+ },
+ },
+ validate: {
+ error: {
+ workerApi: {
+ id: 'Host.Create.Validate.Error.WorkerApi',
+ defaultMessage: 'Please input validate worker api.',
+ },
+ },
+ required: {
+ name: {
+ id: 'Host.Create.Validate.Required.Name',
+ defaultMessage: 'Please input name.',
+ },
+ host: {
+ id: 'Chain.Create.Validate.Required.Host',
+ defaultMessage: 'Must select a host',
+ },
+ },
+ },
+});
+
+@connect(({ host, loading }) => ({
+ host,
+ loadingHosts: loading.effects['host/fetchHosts'],
+}))
+@Form.create()
+class CreateChain extends PureComponent {
+ state = {
+ submitting: false,
+ };
+ componentDidMount() {
+ this.props.dispatch({
+ type: 'host/fetchHosts',
+ });
+ }
+ submitCallback = () => {
+ this.setState({
+ submitting: false,
+ });
+ };
+ clickCancel = () => {
+ this.props.dispatch(
+ routerRedux.push({
+ pathname: '/chain',
+ })
+ );
+ };
+ handleSubmit = e => {
+ e.preventDefault();
+ this.props.form.validateFieldsAndScroll((err, values) => {
+ if (!err) {
+ this.setState({
+ submitting: true,
+ });
+ this.props.dispatch({
+ type: 'chain/createChain',
+ payload: {
+ ...values,
+ callback: this.submitCallback,
+ },
+ });
+ }
+ });
+ };
+ render() {
+ const { getFieldDecorator } = this.props.form;
+ const { intl, host } = this.props;
+ const { submitting } = this.state;
+ const { hosts } = host;
+ const availableHosts = hosts.filter(hostItem => hostItem.capacity > hostItem.clusters.length);
+ const hostOptions = availableHosts.map(hostItem => (
+
+ ));
+ const networkTypes = ['fabric-1.0'];
+ const networkTypeOptions = networkTypes.map(networkType => (
+
+ ));
+ const chainSizes = [4];
+ const chainSizeOptions = chainSizes.map(chainSize => (
+
+ ));
+ const consensusPlugins = ['solo', 'kafka'];
+ const consensusPluginOptions = consensusPlugins.map(consensusPlugin => (
+
+ ));
+
+ const formItemLayout = {
+ labelCol: {
+ xs: { span: 24 },
+ sm: { span: 7 },
+ },
+ wrapperCol: {
+ xs: { span: 24 },
+ sm: { span: 12 },
+ md: { span: 10 },
+ },
+ };
+
+ const submitFormLayout = {
+ wrapperCol: {
+ xs: { span: 24, offset: 0 },
+ sm: { span: 10, offset: 7 },
+ },
+ };
+ return (
+
{user_id === '' ? 'Empty' : user_id}
+{create_ts}
+
+
+