diff --git a/user-dashboard/src/.eslintrc b/user-dashboard/src/.eslintrc
index b26d137f6..4de5b3da0 100644
--- a/user-dashboard/src/.eslintrc
+++ b/user-dashboard/src/.eslintrc
@@ -2,7 +2,8 @@
"extends": "eslint-config-egg",
"rules": {
"array-bracket-spacing": [0],
- "no-extend-native": [0]
+ "no-extend-native": [0],
+ "no-bitwise": [0]
},
"parser": "babel-eslint"
}
diff --git a/user-dashboard/src/.ui-eslintrc b/user-dashboard/src/.ui-eslintrc
index bcd649f49..3879a4fdb 100644
--- a/user-dashboard/src/.ui-eslintrc
+++ b/user-dashboard/src/.ui-eslintrc
@@ -19,6 +19,7 @@
"react/jsx-no-bind": [0],
"react/prop-types": [0],
"react/prefer-stateless-function": [0],
+ "react/no-did-mount-set-state": [0],
"react/jsx-wrap-multilines": [
"error",
{
diff --git a/user-dashboard/src/app/assets/src/common/menu.js b/user-dashboard/src/app/assets/src/common/menu.js
index 2b4454013..355350a95 100644
--- a/user-dashboard/src/app/assets/src/common/menu.js
+++ b/user-dashboard/src/app/assets/src/common/menu.js
@@ -1,34 +1,52 @@
/*
SPDX-License-Identifier: Apache-2.0
*/
-import { isUrl } from '../utils/utils';
+import { isUrl } from "../utils/utils";
const menuData = [
{
- name: 'Chain',
- icon: 'link',
- path: 'chain',
+ name: "Chain",
+ icon: "link",
+ path: "chain",
},
{
- name: 'Apply Chain',
- path: 'apply-chain',
+ name: "Apply Chain",
+ path: "apply-chain",
hideInMenu: true,
hideInBreadcrumb: false,
},
{
- name: 'Smart Contract',
- path: 'smart-contract',
- icon: 'code-o',
- },
- {
- name: 'Create New Smart Contract',
- path: 'new-smart-contract',
- hideInMenu: true,
- hideInBreadcrumb: false,
+ name: "Smart Contract",
+ path: "smart-contract",
+ icon: "code-o",
+ children: [
+ {
+ name: "List",
+ path: "index",
+ },
+ {
+ name: "Info",
+ path: "info/:id",
+ hideInMenu: true,
+ hideInBreadcrumb: false,
+ },
+ {
+ name: "Create",
+ path: "new",
+ hideInMenu: true,
+ hideInBreadcrumb: false,
+ },
+ {
+ name: "New Code",
+ path: "new-code",
+ hideInMenu: true,
+ hideInBreadcrumb: false,
+ },
+ ],
},
];
-function formatter(data, parentPath = '/', parentAuthority) {
+function formatter(data, parentPath = "/", parentAuthority) {
return data.map(item => {
let { path } = item;
if (!isUrl(path)) {
@@ -40,7 +58,11 @@ function formatter(data, parentPath = '/', parentAuthority) {
authority: item.authority || parentAuthority,
};
if (item.children) {
- result.children = formatter(item.children, `${parentPath}${item.path}/`, item.authority);
+ result.children = formatter(
+ item.children,
+ `${parentPath}${item.path}/`,
+ item.authority
+ );
}
return result;
});
diff --git a/user-dashboard/src/app/assets/src/common/router.js b/user-dashboard/src/app/assets/src/common/router.js
index 3f13ee00e..afb0bb69b 100644
--- a/user-dashboard/src/app/assets/src/common/router.js
+++ b/user-dashboard/src/app/assets/src/common/router.js
@@ -1,24 +1,24 @@
/*
SPDX-License-Identifier: Apache-2.0
*/
-import { createElement } from 'react';
-import dynamic from 'dva/dynamic';
-import pathToRegexp from 'path-to-regexp';
-import { getMenuData } from './menu';
+import { createElement } from "react";
+import dynamic from "dva/dynamic";
+import pathToRegexp from "path-to-regexp";
+import { getMenuData } from "./menu";
let routerDataCache;
const modelNotExisted = (app, model) =>
// eslint-disable-next-line
!app._models.some(({ namespace }) => {
- return namespace === model.substring(model.lastIndexOf('/') + 1);
+ return namespace === model.substring(model.lastIndexOf("/") + 1);
});
// wrapper of dynamic
const dynamicWrapper = (app, models, component) => {
// () => require('module')
// transformed by babel-plugin-dynamic-import-node-sync
- if (component.toString().indexOf('.then(') < 0) {
+ if (component.toString().indexOf(".then(") < 0) {
models.forEach(model => {
if (modelNotExisted(app, model)) {
// eslint-disable-next-line
@@ -39,7 +39,9 @@ const dynamicWrapper = (app, models, component) => {
return dynamic({
app,
models: () =>
- models.filter(model => modelNotExisted(app, model)).map(m => import(`../models/${m}.js`)),
+ models
+ .filter(model => modelNotExisted(app, model))
+ .map(m => import(`../models/${m}.js`)),
// add routerData prop
component: () => {
if (!routerDataCache) {
@@ -72,35 +74,61 @@ function getFlatMenuData(menus) {
export const getRouterData = app => {
const routerConfig = {
- '/': {
- component: dynamicWrapper(app, ['user', 'login'], () => import('../layouts/BasicLayout')),
+ "/": {
+ component: dynamicWrapper(app, ["user", "login"], () =>
+ import("../layouts/BasicLayout")
+ ),
},
- '/chain': {
- component: dynamicWrapper(app, ['chain'], () => import('../routes/Chain')),
+ "/chain": {
+ component: dynamicWrapper(app, ["chain"], () => import("../routes/Chain")),
},
- '/apply-chain': {
- component: dynamicWrapper(app, ['chain'], () => import('../routes/Chain/Apply')),
+ "/apply-chain": {
+ component: dynamicWrapper(app, ["chain"], () =>
+ import("../routes/Chain/Apply")
+ ),
},
- '/exception/403': {
- component: dynamicWrapper(app, [], () => import('../routes/Exception/403')),
+ "/exception/403": {
+ component: dynamicWrapper(app, [], () =>
+ import("../routes/Exception/403")
+ ),
},
- '/exception/404': {
- component: dynamicWrapper(app, [], () => import('../routes/Exception/404')),
+ "/exception/404": {
+ component: dynamicWrapper(app, [], () =>
+ import("../routes/Exception/404")
+ ),
},
- '/exception/500': {
- component: dynamicWrapper(app, [], () => import('../routes/Exception/500')),
+ "/exception/500": {
+ component: dynamicWrapper(app, [], () =>
+ import("../routes/Exception/500")
+ ),
},
- '/user': {
- component: dynamicWrapper(app, [], () => import('../layouts/UserLayout')),
+ "/user": {
+ component: dynamicWrapper(app, [], () => import("../layouts/UserLayout")),
},
- '/user/login': {
- component: dynamicWrapper(app, ['login'], () => import('../routes/User/Login')),
+ "/user/login": {
+ component: dynamicWrapper(app, ["login"], () =>
+ import("../routes/User/Login")
+ ),
},
- '/smart-contract': {
- component: dynamicWrapper(app, ['smartContract'], () => import('../routes/SmartContract')),
+ "/smart-contract/index": {
+ component: dynamicWrapper(app, ["smartContract"], () =>
+ import("../routes/SmartContract")
+ ),
},
- '/new-smart-contract': {
- component: dynamicWrapper(app, ['smartContract'], () => import('../routes/SmartContract/New')),
+ "/smart-contract/info/:id": {
+ component: dynamicWrapper(app, ["smartContract", "chain"], () =>
+ import("../routes/SmartContract/Info")
+ ),
+ },
+ "/smart-contract/new": {
+ component: dynamicWrapper(app, ["smartContract"], () =>
+ import("../routes/SmartContract/New")
+ ),
+ },
+ "/smart-contract/new-code/:id": {
+ component: dynamicWrapper(app, ["smartContract"], () =>
+ import("../routes/SmartContract/New/code")
+ ),
},
};
// Get name from ./menu.js or just set it in the router data.
@@ -114,7 +142,9 @@ export const getRouterData = app => {
// Regular match item name
// eg. router /user/:id === /user/chen
const pathRegexp = pathToRegexp(path);
- const menuKey = Object.keys(menuData).find(key => pathRegexp.test(`${key}`));
+ const menuKey = Object.keys(menuData).find(key =>
+ pathRegexp.test(`${key}`)
+ );
let menuItem = {};
// If menuKey is not empty
if (menuKey) {
diff --git a/user-dashboard/src/app/assets/src/components/DescriptionList/Description.js b/user-dashboard/src/app/assets/src/components/DescriptionList/Description.js
new file mode 100644
index 000000000..3e41c0717
--- /dev/null
+++ b/user-dashboard/src/app/assets/src/components/DescriptionList/Description.js
@@ -0,0 +1,27 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import classNames from 'classnames';
+import { Col } from 'antd';
+import styles from './index.less';
+import responsive from './responsive';
+
+const Description = ({ term, column, className, children, ...restProps }) => {
+ const clsString = classNames(styles.description, className);
+ return (
+
+ {term && {term}
}
+ {children !== null && children !== undefined &&
+ {children}
}
+
+ );
+};
+
+Description.defaultProps = {
+ term: '',
+};
+
+Description.propTypes = {
+ term: PropTypes.node,
+};
+
+export default Description;
diff --git a/user-dashboard/src/app/assets/src/components/DescriptionList/DescriptionList.js b/user-dashboard/src/app/assets/src/components/DescriptionList/DescriptionList.js
new file mode 100644
index 000000000..382d7e85f
--- /dev/null
+++ b/user-dashboard/src/app/assets/src/components/DescriptionList/DescriptionList.js
@@ -0,0 +1,31 @@
+import React from 'react';
+import classNames from 'classnames';
+import { Row } from 'antd';
+import styles from './index.less';
+
+const DescriptionList = ({
+ className,
+ title,
+ col = 3,
+ layout = 'horizontal',
+ gutter = 32,
+ children,
+ size,
+ ...restProps
+}) => {
+ const clsString = classNames(styles.descriptionList, styles[layout], className, {
+ [styles.small]: size === 'small',
+ [styles.large]: size === 'large',
+ });
+ const column = col > 4 ? 4 : col;
+ return (
+
+ {title ?
{title}
: null}
+
+ {React.Children.map(children, child => child ? React.cloneElement(child, { column }) : child)}
+
+
+ );
+};
+
+export default DescriptionList;
diff --git a/user-dashboard/src/app/assets/src/components/DescriptionList/index.js b/user-dashboard/src/app/assets/src/components/DescriptionList/index.js
new file mode 100644
index 000000000..357f479fd
--- /dev/null
+++ b/user-dashboard/src/app/assets/src/components/DescriptionList/index.js
@@ -0,0 +1,5 @@
+import DescriptionList from './DescriptionList';
+import Description from './Description';
+
+DescriptionList.Description = Description;
+export default DescriptionList;
diff --git a/user-dashboard/src/app/assets/src/components/DescriptionList/index.less b/user-dashboard/src/app/assets/src/components/DescriptionList/index.less
new file mode 100644
index 000000000..bcb6fd1da
--- /dev/null
+++ b/user-dashboard/src/app/assets/src/components/DescriptionList/index.less
@@ -0,0 +1,77 @@
+@import '~antd/lib/style/themes/default.less';
+
+.descriptionList {
+ // offset the padding-bottom of last row
+ :global {
+ .ant-row {
+ margin-bottom: -16px;
+ overflow: hidden;
+ }
+ }
+
+ .title {
+ font-size: 14px;
+ color: @heading-color;
+ font-weight: 500;
+ margin-bottom: 16px;
+ }
+
+ .term {
+ // Line-height is 22px IE dom height will calculate error
+ line-height: 20px;
+ padding-bottom: 16px;
+ margin-right: 8px;
+ color: @heading-color;
+ white-space: nowrap;
+ display: table-cell;
+
+ &:after {
+ content: ':';
+ margin: 0 8px 0 2px;
+ position: relative;
+ top: -0.5px;
+ }
+ }
+
+ .detail {
+ line-height: 22px;
+ width: 100%;
+ padding-bottom: 16px;
+ color: @text-color;
+ display: table-cell;
+ }
+
+ &.small {
+ // offset the padding-bottom of last row
+ :global {
+ .ant-row {
+ margin-bottom: -8px;
+ }
+ }
+ .title {
+ margin-bottom: 12px;
+ color: @text-color;
+ }
+ .term,
+ .detail {
+ padding-bottom: 8px;
+ }
+ }
+
+ &.large {
+ .title {
+ font-size: 16px;
+ }
+ }
+
+ &.vertical {
+ .term {
+ padding-bottom: 8px;
+ display: block;
+ }
+
+ .detail {
+ display: block;
+ }
+ }
+}
diff --git a/user-dashboard/src/app/assets/src/components/DescriptionList/responsive.js b/user-dashboard/src/app/assets/src/components/DescriptionList/responsive.js
new file mode 100644
index 000000000..a5aa73f78
--- /dev/null
+++ b/user-dashboard/src/app/assets/src/components/DescriptionList/responsive.js
@@ -0,0 +1,6 @@
+export default {
+ 1: { xs: 24 },
+ 2: { xs: 24, sm: 12 },
+ 3: { xs: 24, sm: 12, md: 8 },
+ 4: { xs: 24, sm: 12, md: 6 },
+};
diff --git a/user-dashboard/src/app/assets/src/layouts/BasicLayout.js b/user-dashboard/src/app/assets/src/layouts/BasicLayout.js
index 84e8f10dc..f2926b2a7 100644
--- a/user-dashboard/src/app/assets/src/layouts/BasicLayout.js
+++ b/user-dashboard/src/app/assets/src/layouts/BasicLayout.js
@@ -1,23 +1,23 @@
/*
SPDX-License-Identifier: Apache-2.0
*/
-import React, { Fragment } from 'react';
-import PropTypes from 'prop-types';
-import { Layout, Icon } from 'antd';
-import DocumentTitle from 'react-document-title';
-import { connect } from 'dva';
-import { Route, Redirect, Switch } from 'dva/router';
-import { ContainerQuery } from 'react-container-query';
-import classNames from 'classnames';
-import { enquireScreen } from 'enquire-js';
-import GlobalHeader from '../components/GlobalHeader';
-import GlobalFooter from '../components/GlobalFooter';
-import SiderMenu from '../components/SiderMenu';
-import NotFound from '../routes/Exception/404';
-import { getRoutes } from '../utils/utils';
-import Authorized from '../utils/Authorized';
-import { getMenuData } from '../common/menu';
-import logo from '../assets/logo.svg';
+import React, { Fragment } from "react";
+import PropTypes from "prop-types";
+import { Layout, Icon } from "antd";
+import DocumentTitle from "react-document-title";
+import { connect } from "dva";
+import { Route, Redirect, Switch } from "dva/router";
+import { ContainerQuery } from "react-container-query";
+import classNames from "classnames";
+import { enquireScreen } from "enquire-js";
+import GlobalHeader from "../components/GlobalHeader";
+import GlobalFooter from "../components/GlobalFooter";
+import SiderMenu from "../components/SiderMenu";
+import NotFound from "../routes/Exception/404";
+import { getRoutes } from "../utils/utils";
+import Authorized from "../utils/Authorized";
+import { getMenuData } from "../common/menu";
+import logo from "../assets/logo.svg";
const { Content, Header, Footer } = Layout;
const { check } = Authorized;
@@ -53,22 +53,22 @@ const getBreadcrumbNameMap = (menuData, routerData) => {
};
const query = {
- 'screen-xs': {
+ "screen-xs": {
maxWidth: 575,
},
- 'screen-sm': {
+ "screen-sm": {
minWidth: 576,
maxWidth: 767,
},
- 'screen-md': {
+ "screen-md": {
minWidth: 768,
maxWidth: 991,
},
- 'screen-lg': {
+ "screen-lg": {
minWidth: 992,
maxWidth: 1199,
},
- 'screen-xl': {
+ "screen-xl": {
minWidth: 1200,
},
};
@@ -103,7 +103,7 @@ class BasicLayout extends React.PureComponent {
getPageTitle() {
const { routerData, location } = this.props;
const { pathname } = location;
- let title = 'Cello Operator Dashboard';
+ let title = "Cello Operator Dashboard";
if (routerData[pathname] && routerData[pathname].name) {
title = `${routerData[pathname].name} - Cello Operator Dashboard`;
}
@@ -113,16 +113,16 @@ class BasicLayout extends React.PureComponent {
// According to the url parameter to redirect
const urlParams = new URL(window.location.href);
- const redirect = urlParams.searchParams.get('redirect');
+ const redirect = urlParams.searchParams.get("redirect");
// Remove the parameters in the url
if (redirect) {
- urlParams.searchParams.delete('redirect');
- window.history.replaceState(null, 'redirect', urlParams.href);
+ urlParams.searchParams.delete("redirect");
+ window.history.replaceState(null, "redirect", urlParams.href);
} else {
const { routerData } = this.props;
// get the first authorized route path in routerData
const authorizedPath = Object.keys(routerData).find(
- item => check(routerData[item].authority, item) && item !== '/'
+ item => check(routerData[item].authority, item) && item !== "/"
);
return authorizedPath;
}
@@ -130,13 +130,13 @@ class BasicLayout extends React.PureComponent {
};
handleMenuCollapse = collapsed => {
this.props.dispatch({
- type: 'global/changeLayoutCollapsed',
+ type: "global/changeLayoutCollapsed",
payload: collapsed,
});
};
handleMenuClick = ({ key }) => {
- if (key === 'logout') {
- window.location.href = '/logout';
+ if (key === "logout") {
+ window.location.href = "/logout";
}
};
render() {
@@ -165,7 +165,7 @@ class BasicLayout extends React.PureComponent {
onMenuClick={this.handleMenuClick}
/>
-
+
{redirectData.map(item => (
@@ -209,6 +209,6 @@ class BasicLayout extends React.PureComponent {
export default connect(({ user, global, loading }) => ({
currentUser: user.currentUser,
collapsed: global.collapsed,
- fetchingNotices: loading.effects['global/fetchNotices'],
+ fetchingNotices: loading.effects["global/fetchNotices"],
notices: global.notices,
}))(BasicLayout);
diff --git a/user-dashboard/src/app/assets/src/layouts/BlankLayout.js b/user-dashboard/src/app/assets/src/layouts/BlankLayout.js
index d83a1b1e8..2a77a6346 100644
--- a/user-dashboard/src/app/assets/src/layouts/BlankLayout.js
+++ b/user-dashboard/src/app/assets/src/layouts/BlankLayout.js
@@ -1,6 +1,6 @@
/*
SPDX-License-Identifier: Apache-2.0
*/
-import React from 'react';
+import React from "react";
export default props => ;
diff --git a/user-dashboard/src/app/assets/src/layouts/PageHeaderLayout.js b/user-dashboard/src/app/assets/src/layouts/PageHeaderLayout.js
index 533f4b862..e37118c03 100644
--- a/user-dashboard/src/app/assets/src/layouts/PageHeaderLayout.js
+++ b/user-dashboard/src/app/assets/src/layouts/PageHeaderLayout.js
@@ -1,15 +1,18 @@
/*
SPDX-License-Identifier: Apache-2.0
*/
-import React from 'react';
-import { Link } from 'dva/router';
-import PageHeader from '../components/PageHeader';
-import styles from './PageHeaderLayout.less';
+import React from "react";
+import { Link } from "dva/router";
+import { Spin } from "antd";
+import PageHeader from "../components/PageHeader";
+import styles from "./PageHeaderLayout.less";
-export default ({ children, wrapperClassName, top, ...restProps }) => (
-
- {top}
-
- {children ?
{children}
: null}
-
+export default ({ children, loading, wrapperClassName, top, ...restProps }) => (
+
+
+ {top}
+
+ {children ?
{children}
: null}
+
+
);
diff --git a/user-dashboard/src/app/assets/src/layouts/PageHeaderLayout.less b/user-dashboard/src/app/assets/src/layouts/PageHeaderLayout.less
index 39a449657..a0c0a6efe 100644
--- a/user-dashboard/src/app/assets/src/layouts/PageHeaderLayout.less
+++ b/user-dashboard/src/app/assets/src/layouts/PageHeaderLayout.less
@@ -1,4 +1,4 @@
-@import '~antd/lib/style/themes/default.less';
+@import "~antd/lib/style/themes/default.less";
.content {
margin: 24px 24px 0;
diff --git a/user-dashboard/src/app/assets/src/layouts/UserLayout.js b/user-dashboard/src/app/assets/src/layouts/UserLayout.js
index da2bc9d97..5ab656b45 100644
--- a/user-dashboard/src/app/assets/src/layouts/UserLayout.js
+++ b/user-dashboard/src/app/assets/src/layouts/UserLayout.js
@@ -1,15 +1,15 @@
/*
SPDX-License-Identifier: Apache-2.0
*/
-import React, { Fragment } from 'react';
-import { Link, Redirect, Switch, Route } from 'dva/router';
-import DocumentTitle from 'react-document-title';
-import Particles from 'react-particles-js';
-import { Icon } from 'antd';
-import GlobalFooter from '../components/GlobalFooter';
-import styles from './UserLayout.less';
-import logo from '../assets/logo.svg';
-import { getRoutes } from '../utils/utils';
+import React, { Fragment } from "react";
+import { Link, Redirect, Switch, Route } from "dva/router";
+import DocumentTitle from "react-document-title";
+import Particles from "react-particles-js";
+import { Icon } from "antd";
+import GlobalFooter from "../components/GlobalFooter";
+import styles from "./UserLayout.less";
+import logo from "../assets/logo.svg";
+import { getRoutes } from "../utils/utils";
const copyright = (
@@ -21,7 +21,7 @@ class UserLayout extends React.PureComponent {
getPageTitle() {
const { routerData, location } = this.props;
const { pathname } = location;
- let title = 'Cello User Dashboard';
+ let title = "Cello User Dashboard";
if (routerData[pathname] && routerData[pathname].name) {
title = `${routerData[pathname].name} - Cello User Dashboard`;
}
@@ -30,105 +30,105 @@ class UserLayout extends React.PureComponent {
render() {
const { routerData, match } = this.props;
const particlesParams = {
- "particles": {
- "number": {
- "value": 10,
- "density": {
- "enable": true,
- "value_area": 800,
+ particles: {
+ number: {
+ value: 10,
+ density: {
+ enable: true,
+ value_area: 800,
},
},
- "color": {
- "value": "#40a9ff",
+ color: {
+ value: "#40a9ff",
},
- "shape": {
- "type": "polygon",
- "stroke": {
- "width": 0,
- "color": "#000000",
+ shape: {
+ type: "polygon",
+ stroke: {
+ width: 0,
+ color: "#000000",
},
- "polygon": {
- "nb_sides": 5,
+ polygon: {
+ nb_sides: 5,
},
},
- "opacity": {
- "value": 0.5,
- "random": false,
- "anim": {
- "enable": false,
- "speed": 1,
- "opacity_min": 0.1,
- "sync": false,
+ opacity: {
+ value: 0.5,
+ random: false,
+ anim: {
+ enable: false,
+ speed: 1,
+ opacity_min: 0.1,
+ sync: false,
},
},
- "size": {
- "value": 3,
- "random": true,
- "anim": {
- "enable": false,
- "speed": 40,
- "size_min": 0.1,
- "sync": false,
+ size: {
+ value: 3,
+ random: true,
+ anim: {
+ enable: false,
+ speed: 40,
+ size_min: 0.1,
+ sync: false,
},
},
- "line_linked": {
- "enable": true,
- "distance": 150,
- "color": "#40a9ff",
- "opacity": 0.4,
- "width": 1,
+ line_linked: {
+ enable: true,
+ distance: 150,
+ color: "#40a9ff",
+ opacity: 0.4,
+ width: 1,
},
- "move": {
- "enable": true,
- "speed": 6,
- "direction": "none",
- "random": false,
- "straight": false,
- "out_mode": "out",
- "bounce": false,
- "attract": {
- "enable": false,
- "rotateX": 600,
- "rotateY": 1200,
+ move: {
+ enable: true,
+ speed: 6,
+ direction: "none",
+ random: false,
+ straight: false,
+ out_mode: "out",
+ bounce: false,
+ attract: {
+ enable: false,
+ rotateX: 600,
+ rotateY: 1200,
},
},
},
- "interactivity": {
- "detect_on": "canvas",
- "events": {
- "onhover": {
- "enable": true,
- "mode": "repulse",
+ interactivity: {
+ detect_on: "canvas",
+ events: {
+ onhover: {
+ enable: true,
+ mode: "repulse",
},
- "onclick": {
- "enable": true,
- "mode": "push",
+ onclick: {
+ enable: true,
+ mode: "push",
},
- "resize": true,
+ resize: true,
},
- "modes": {
- "grab": {
- "distance": 400,
- "line_linked": {
- "opacity": 1,
+ modes: {
+ grab: {
+ distance: 400,
+ line_linked: {
+ opacity: 1,
},
},
- "bubble": {
- "distance": 400,
- "size": 40,
- "duration": 2,
- "opacity": 8,
- "speed": 3,
+ bubble: {
+ distance: 400,
+ size: 40,
+ duration: 2,
+ opacity: 8,
+ speed: 3,
},
- "repulse": {
- "distance": 200,
- "duration": 0.4,
+ repulse: {
+ distance: 200,
+ duration: 0.4,
},
- "push": {
- "particles_nb": 4,
+ push: {
+ particles_nb: 4,
},
- "remove": {
- "particles_nb": 2,
+ remove: {
+ particles_nb: 2,
},
},
},
diff --git a/user-dashboard/src/app/assets/src/layouts/UserLayout.less b/user-dashboard/src/app/assets/src/layouts/UserLayout.less
index 4965e7ea7..97537a3c7 100644
--- a/user-dashboard/src/app/assets/src/layouts/UserLayout.less
+++ b/user-dashboard/src/app/assets/src/layouts/UserLayout.less
@@ -1,4 +1,4 @@
-@import '~antd/lib/style/themes/default.less';
+@import "~antd/lib/style/themes/default.less";
.container {
display: flex;
@@ -40,7 +40,7 @@
.title {
font-size: 33px;
color: @heading-color;
- font-family: 'Myriad Pro', 'Helvetica Neue', Arial, Helvetica, sans-serif;
+ font-family: "Myriad Pro", "Helvetica Neue", Arial, Helvetica, sans-serif;
font-weight: 600;
position: relative;
top: 2px;
@@ -57,4 +57,4 @@
width: 100%;
position: fixed;
top: 0;
-}
\ No newline at end of file
+}
diff --git a/user-dashboard/src/app/assets/src/locales/en-US.js b/user-dashboard/src/app/assets/src/locales/en-US.js
index 45e1217c8..b3f111a37 100755
--- a/user-dashboard/src/app/assets/src/locales/en-US.js
+++ b/user-dashboard/src/app/assets/src/locales/en-US.js
@@ -1,15 +1,15 @@
/*
SPDX-License-Identifier: Apache-2.0
*/
-import antdEn from 'antd/lib/locale-provider/en_US';
-import appLocaleData from 'react-intl/locale-data/en';
-import enMessages from './en.json';
+import antdEn from "antd/lib/locale-provider/en_US";
+import appLocaleData from "react-intl/locale-data/en";
+import enMessages from "./en.json";
export default {
messages: {
...enMessages,
},
antd: antdEn,
- locale: 'en-US',
+ locale: "en-US",
data: appLocaleData,
};
diff --git a/user-dashboard/src/app/assets/src/locales/en.json b/user-dashboard/src/app/assets/src/locales/en.json
index cf5b92ec8..157844428 100755
--- a/user-dashboard/src/app/assets/src/locales/en.json
+++ b/user-dashboard/src/app/assets/src/locales/en.json
@@ -5,20 +5,29 @@
"Menu.Chain": "Chain",
"Menu.UserManagement": "User Management",
"Messages.RequestError": "Request Error",
- "Messages.HttpStatus.200": "The server successfully returned the requested data.",
+ "Messages.HttpStatus.200":
+ "The server successfully returned the requested data.",
"Messages.HttpStatus.201": "New or modified data is successful.",
- "Messages.HttpStatus.202": "A request has entered the background queue (asynchronous task).",
+ "Messages.HttpStatus.202":
+ "A request has entered the background queue (asynchronous task).",
"Messages.HttpStatus.204": "Delete data successfully.",
- "Messages.HttpStatus.400": "The request was issued with an error. The server did not create or modify data.",
- "Messages.HttpStatus.401": "User does not have permission (token, username, password is wrong).",
+ "Messages.HttpStatus.400":
+ "The request was issued with an error. The server did not create or modify data.",
+ "Messages.HttpStatus.401":
+ "User does not have permission (token, username, password is wrong).",
"Messages.HttpStatus.403": "User authorized, but access is forbidden.",
- "Messages.HttpStatus.404": "The request was issued for a non-existent record and the server did not perform the operation.",
+ "Messages.HttpStatus.404":
+ "The request was issued for a non-existent record and the server did not perform the operation.",
"Messages.HttpStatus.406": "The requested format is not available.",
- "Messages.HttpStatus.410": "The requested resource is permanently deleted and will no longer be available.",
- "Messages.HttpStatus.422": "A validation error occurred when creating an object.",
- "Messages.HttpStatus.500": "An error occurred on the server. Please check the server.",
+ "Messages.HttpStatus.410":
+ "The requested resource is permanently deleted and will no longer be available.",
+ "Messages.HttpStatus.422":
+ "A validation error occurred when creating an object.",
+ "Messages.HttpStatus.500":
+ "An error occurred on the server. Please check the server.",
"Messages.HttpStatus.502": "Bad gateway.",
- "Messages.HttpStatus.503": "The service is unavailable and the server is temporarily overloaded or maintained.",
+ "Messages.HttpStatus.503":
+ "The service is unavailable and the server is temporarily overloaded or maintained.",
"Messages.HttpStatus.504": "Gateway timeout.",
"Login.Button.Login": "Login",
"Login.Placeholder.Username": "Username",
diff --git a/user-dashboard/src/app/assets/src/locales/zh-CN.js b/user-dashboard/src/app/assets/src/locales/zh-CN.js
index 90e2ce096..9427d5ce9 100755
--- a/user-dashboard/src/app/assets/src/locales/zh-CN.js
+++ b/user-dashboard/src/app/assets/src/locales/zh-CN.js
@@ -1,15 +1,15 @@
/*
SPDX-License-Identifier: Apache-2.0
*/
-import appLocaleData from 'react-intl/locale-data/zh';
-import zhCN from 'antd/lib/locale-provider/zh_CN';
-import zhMessages from './zh.json';
+import appLocaleData from "react-intl/locale-data/zh";
+import zhCN from "antd/lib/locale-provider/zh_CN";
+import zhMessages from "./zh.json";
export default {
messages: {
...zhMessages,
},
antd: zhCN,
- locale: 'zh-Hans',
+ locale: "zh-Hans",
data: appLocaleData,
};
diff --git a/user-dashboard/src/app/assets/src/locales/zh.json b/user-dashboard/src/app/assets/src/locales/zh.json
index 30fd6628a..c3ea5184b 100755
--- a/user-dashboard/src/app/assets/src/locales/zh.json
+++ b/user-dashboard/src/app/assets/src/locales/zh.json
@@ -9,10 +9,12 @@
"Messages.HttpStatus.201": "新建或修改数据成功。",
"Messages.HttpStatus.202": "一个请求已经进入后台排队(异步任务)。",
"Messages.HttpStatus.204": "删除数据成功。",
- "Messages.HttpStatus.400": "发出的请求有错误,服务器没有进行新建或修改数据的操作。",
+ "Messages.HttpStatus.400":
+ "发出的请求有错误,服务器没有进行新建或修改数据的操作。",
"Messages.HttpStatus.401": "用户没有权限(令牌、用户名、密码错误)。",
"Messages.HttpStatus.403": "用户得到授权,但是访问是被禁止的。",
- "Messages.HttpStatus.404": "发出的请求针对的是不存在的记录,服务器没有进行操作。",
+ "Messages.HttpStatus.404":
+ "发出的请求针对的是不存在的记录,服务器没有进行操作。",
"Messages.HttpStatus.406": "请求的格式不可得。",
"Messages.HttpStatus.410": "请求的资源被永久删除,且不会再得到的。",
"Messages.HttpStatus.422": "当创建一个对象时,发生一个验证错误。",
diff --git a/user-dashboard/src/app/assets/src/models/chain.js b/user-dashboard/src/app/assets/src/models/chain.js
index 459180926..e3c27914b 100644
--- a/user-dashboard/src/app/assets/src/models/chain.js
+++ b/user-dashboard/src/app/assets/src/models/chain.js
@@ -1,12 +1,12 @@
/*
SPDX-License-Identifier: Apache-2.0
*/
-import { routerRedux } from 'dva/router';
-import { message } from 'antd';
-import { queryChains, release, apply } from '../services/chain';
+import { routerRedux } from "dva/router";
+import { message } from "antd";
+import { queryChains, release, apply } from "../services/chain";
export default {
- namespace: 'chain',
+ namespace: "chain",
state: {
chains: [],
@@ -16,26 +16,26 @@ export default {
*fetch(_, { call, put }) {
const response = yield call(queryChains);
yield put({
- type: 'setChains',
+ type: "setChains",
payload: response.data,
- })
+ });
},
*release({ payload }, { call, put }) {
const response = yield call(release, payload.id);
if (JSON.parse(response).success) {
- message.success('Release Chain successfully');
+ message.success("Release Chain successfully");
yield put({
- type: 'fetch',
- })
+ type: "fetch",
+ });
}
},
*apply({ payload }, { call, put }) {
- const response = yield call(apply, payload);
- if (response.success) {
- message.success('Apply Chain successfully');
+ const response = yield call(apply, payload);
+ if (response.success) {
+ message.success("Apply Chain successfully");
yield put(
routerRedux.push({
- pathname: '/chain',
+ pathname: "/chain",
})
);
}
diff --git a/user-dashboard/src/app/assets/src/models/error.js b/user-dashboard/src/app/assets/src/models/error.js
index 94162c1f9..da1d704a7 100644
--- a/user-dashboard/src/app/assets/src/models/error.js
+++ b/user-dashboard/src/app/assets/src/models/error.js
@@ -1,14 +1,14 @@
/*
SPDX-License-Identifier: Apache-2.0
*/
-import { routerRedux } from 'dva/router';
-import { query } from '../services/error';
+import { routerRedux } from "dva/router";
+import { query } from "../services/error";
export default {
- namespace: 'error',
+ namespace: "error",
state: {
- error: '',
+ error: "",
isloading: false,
},
@@ -18,7 +18,7 @@ export default {
// redirect on client when network broken
yield put(routerRedux.push(`/exception/${payload.code}`));
yield put({
- type: 'trigger',
+ type: "trigger",
payload: payload.code,
});
},
diff --git a/user-dashboard/src/app/assets/src/models/global.js b/user-dashboard/src/app/assets/src/models/global.js
index 63bfbc800..086f55170 100644
--- a/user-dashboard/src/app/assets/src/models/global.js
+++ b/user-dashboard/src/app/assets/src/models/global.js
@@ -2,7 +2,7 @@
SPDX-License-Identifier: Apache-2.0
*/
export default {
- namespace: 'global',
+ namespace: "global",
state: {
collapsed: false,
@@ -12,12 +12,12 @@ export default {
effects: {
*clearNotices({ payload }, { put, select }) {
yield put({
- type: 'saveClearedNotices',
+ type: "saveClearedNotices",
payload,
});
const count = yield select(state => state.global.notices.length);
yield put({
- type: 'user/changeNotifyCount',
+ type: "user/changeNotifyCount",
payload: count,
});
},
@@ -48,8 +48,8 @@ export default {
setup({ history }) {
// Subscribe history(url) change, trigger `load` action if pathname is `/`
return history.listen(({ pathname, search }) => {
- if (typeof window.ga !== 'undefined') {
- window.ga('send', 'pageview', pathname + search);
+ if (typeof window.ga !== "undefined") {
+ window.ga("send", "pageview", pathname + search);
}
});
},
diff --git a/user-dashboard/src/app/assets/src/models/index.js b/user-dashboard/src/app/assets/src/models/index.js
index 6eb7f2a29..c22dbe41a 100644
--- a/user-dashboard/src/app/assets/src/models/index.js
+++ b/user-dashboard/src/app/assets/src/models/index.js
@@ -3,8 +3,8 @@
*/
// Use require.context to require reducers automatically
// Ref: https://webpack.js.org/guides/dependency-management/#require-context
-const context = require.context('./', false, /\.js$/);
+const context = require.context("./", false, /\.js$/);
export default context
.keys()
- .filter(item => item !== './index.js')
+ .filter(item => item !== "./index.js")
.map(key => context(key));
diff --git a/user-dashboard/src/app/assets/src/models/login.js b/user-dashboard/src/app/assets/src/models/login.js
index d20424cdf..d60db1dee 100644
--- a/user-dashboard/src/app/assets/src/models/login.js
+++ b/user-dashboard/src/app/assets/src/models/login.js
@@ -1,11 +1,11 @@
/*
SPDX-License-Identifier: Apache-2.0
*/
-import { login } from '../services/user';
-import { setAuthority } from '../utils/authority';
+import { login } from "../services/user";
+import { setAuthority } from "../utils/authority";
export default {
- namespace: 'login',
+ namespace: "login",
state: {
status: undefined,
diff --git a/user-dashboard/src/app/assets/src/models/smartContract.js b/user-dashboard/src/app/assets/src/models/smartContract.js
index 777b54829..970b6b964 100644
--- a/user-dashboard/src/app/assets/src/models/smartContract.js
+++ b/user-dashboard/src/app/assets/src/models/smartContract.js
@@ -1,27 +1,37 @@
/*
SPDX-License-Identifier: Apache-2.0
*/
-import { querySmartContracts, deleteSmartContractCode, updateSmartContractCode, deleteSmartContract } from '../services/smart_contract';
+import {
+ querySmartContracts,
+ deleteSmartContractCode,
+ updateSmartContractCode,
+ deleteSmartContract,
+ querySmartContract,
+ deploySmartContract,
+} from "../services/smart_contract";
export default {
- namespace: 'smartContract',
+ namespace: "smartContract",
state: {
smartContracts: [],
+ currentSmartContract: {},
+ codes: [],
+ newOperations: [],
},
effects: {
*fetch(_, { call, put }) {
const response = yield call(querySmartContracts);
yield put({
- type: 'setSmartContracts',
+ type: "setSmartContracts",
payload: response.data,
- })
+ });
},
*deleteSmartContractCode({ payload }, { call }) {
yield call(deleteSmartContractCode, payload.id);
if (payload.callback) {
- yield call(payload.callback)
+ yield call(payload.callback);
}
},
*updateSmartContractCode({ payload }, { call }) {
@@ -33,6 +43,25 @@ export default {
});
}
},
+ *querySmartContract({ payload }, { call, put }) {
+ const response = yield call(querySmartContract, payload.id);
+ if (response.success) {
+ yield put({
+ type: "setCurrentSmartContract",
+ payload: {
+ currentSmartContract: response.info,
+ codes: response.codes,
+ newOperations: response.newOperations,
+ },
+ });
+ }
+ },
+ *deploySmartContract({ payload }, { call }) {
+ const response = yield call(deploySmartContract, payload);
+ if (payload.callback) {
+ yield call(payload.callback, response);
+ }
+ },
*deleteSmartContract({ payload }, { call }) {
yield call(deleteSmartContract, payload.id);
if (payload.callback) {
@@ -48,5 +77,14 @@ export default {
smartContracts: action.payload,
};
},
+ setCurrentSmartContract(state, action) {
+ const { currentSmartContract, codes, newOperations } = action.payload;
+ return {
+ ...state,
+ currentSmartContract,
+ codes,
+ newOperations,
+ };
+ },
},
};
diff --git a/user-dashboard/src/app/assets/src/models/user.js b/user-dashboard/src/app/assets/src/models/user.js
index 4e9bfa6f8..4d5332ffd 100644
--- a/user-dashboard/src/app/assets/src/models/user.js
+++ b/user-dashboard/src/app/assets/src/models/user.js
@@ -1,10 +1,10 @@
/*
SPDX-License-Identifier: Apache-2.0
*/
-import { query as queryUsers, queryCurrent } from '../services/user';
+import { query as queryUsers, queryCurrent } from "../services/user";
export default {
- namespace: 'user',
+ namespace: "user",
state: {
list: [],
@@ -15,14 +15,14 @@ export default {
*fetch(_, { call, put }) {
const response = yield call(queryUsers);
yield put({
- type: 'save',
+ type: "save",
payload: response,
});
},
*fetchCurrent(_, { call, put }) {
const response = yield call(queryCurrent);
yield put({
- type: 'saveCurrentUser',
+ type: "saveCurrentUser",
payload: response,
});
},
diff --git a/user-dashboard/src/app/assets/src/routes/SmartContract/Info/deploy.js b/user-dashboard/src/app/assets/src/routes/SmartContract/Info/deploy.js
new file mode 100644
index 000000000..e3eb2cc55
--- /dev/null
+++ b/user-dashboard/src/app/assets/src/routes/SmartContract/Info/deploy.js
@@ -0,0 +1,314 @@
+/*
+ SPDX-License-Identifier: Apache-2.0
+*/
+import React, { Component, Fragment } from 'react';
+import {
+ Card,
+ Popover,
+ Steps,
+ Form,
+ Select,
+ Button,
+ message,
+ Input,
+} from 'antd';
+import classNames from 'classnames';
+import styles from './index.less';
+
+const { Step } = Steps;
+const FormItem = Form.Item;
+const { Option } = Select;
+
+@Form.create()
+export default class Deploy extends Component {
+ constructor(props) {
+ super(props);
+ const current = props.current || 0;
+ const version = props.version || '';
+ const versionId = props.versionId || '';
+ this.state = {
+ stepDirection: 'horizontal',
+ current,
+ version,
+ versionId,
+ chainName: '',
+ chainId: '',
+ deployId: '',
+ installing: false,
+ instantiating: false,
+ }
+ }
+ changeVersion = value => {
+ const { codes } = this.props;
+ let codeVersion = this.state.version;
+ codes.forEach(code => {
+ if (code._id === value) {
+ codeVersion = code.version;
+ return false;
+ }
+ });
+ this.setState({
+ versionId: value,
+ version: codeVersion,
+ })
+ };
+ changeChain = value => {
+ const { chains } = this.props;
+ let { chainName } = this.state;
+ chains.forEach(chain => {
+ if (chain._id === value) {
+ chainName = chain.name;
+ return false;
+ }
+ });
+ this.setState({
+ chainId: value,
+ chainName,
+ })
+
+ };
+ installCallback = (response) => {
+ if (response.success) {
+ message.success('Install chain code successfully.');
+ const current = this.state.current + 1;
+ this.setState({
+ current,
+ deployId: response.deployId,
+ });
+ } else {
+ message.error('Install chain code failed.');
+ }
+ this.setState({
+ installing: false,
+ })
+ };
+ instantiateCallback = (response) => {
+ const { onDeployDone } = this.props;
+ if (response.success) {
+ message.success('Instantiate chain code successfully.');
+ onDeployDone();
+ } else {
+ message.error('Instantiate chain code failed.');
+ }
+ this.setState({
+ instantiating: false,
+ })
+ };
+ next = () => {
+ let { current } = this.state;
+ const { chainId, versionId, deployId } = this.state;
+ const { onDeploy } = this.props;
+ const { installCallback, instantiateCallback } = this;
+ switch (current) {
+ case 1:
+ this.setState({
+ installing: true,
+ }, () => {
+ onDeploy({
+ id: versionId,
+ chainId,
+ operation: 'install',
+ callback: installCallback,
+ });
+ });
+ break;
+ case 2:
+ this.props.form.validateFieldsAndScroll({ force: true }, (err, values) => {
+ if (!err) {
+ let { functionName, args } = values;
+ functionName = functionName === "" ? null : functionName;
+ args = args.split(',');
+ this.setState({
+ instantiating: true,
+ }, () => {
+ onDeploy({
+ id: versionId,
+ chainId,
+ operation: 'instantiate',
+ deployId,
+ callback: instantiateCallback,
+ functionName,
+ args,
+ })
+ });
+ }
+ });
+ break;
+ default:
+ current += 1;
+ break;
+ }
+ this.setState({ current });
+ };
+ prev = () => {
+ const current = this.state.current - 1;
+ this.setState({ current });
+ };
+
+ render() {
+ const { stepDirection, current, version, versionId, chainName, chainId, installing, instantiating } = this.state;
+ const { chains, codes } = this.props;
+ const { getFieldDecorator } = this.props.form;
+ const versionOptions = codes.map(code => );
+ const chainOptions = chains.map(chain => );
+ const versionDesc = (
+
+
+ {current > 0 && version}
+
+
+ );
+ const chainDesc = (
+
+
+ {current > 1 && chainName}
+
+
+ );
+ const formItemLayout = {
+ labelCol: {
+ xs: { span: 24 },
+ sm: { span: 4 },
+ },
+ wrapperCol: {
+ xs: { span: 24 },
+ sm: { span: 20 },
+ },
+ };
+
+ const steps = [
+ {
+ title: 'Version',
+ content: (
+
+
+
+ ),
+ description: versionDesc,
+ help: 'Select code version to deploy',
+ },
+ {
+ title: 'Install',
+ content: (
+
+
+
+ ),
+ description: chainDesc,
+ help: 'Select network to install',
+ },
+ {
+ title: 'Instantiate',
+ content: (
+
+
+ {getFieldDecorator('functionName', {
+ initialValue: '',
+ })()}
+
+
+ {getFieldDecorator('args', {
+ initialValue: '',
+ rules: [
+ {
+ required: true,
+ message: 'Must input arguments',
+ },
+ ],
+ })()}
+
+
+ ),
+ help: 'Input function & arguments to instantiate',
+ },
+ ];
+
+ const popoverContent = (
+
+ {steps[current].help}
+
+ );
+
+ const customDot = (dot, { status }) =>
+ status === 'process' ? (
+
+ {dot}
+
+ ) : (
+ dot
+ );
+
+ function getNextText() {
+ switch (current) {
+ case 0:
+ return 'Next';
+ case 1:
+ return 'Install';
+ case 2:
+ return 'Instantiate';
+ default:
+ return 'Next';
+ }
+ }
+ function getLoading() {
+ switch (current) {
+ case 1:
+ return installing;
+ case 2:
+ return instantiating;
+ default:
+ return false;
+ }
+ }
+ function checkDisabled() {
+ switch (current) {
+ case 1:
+ return chainId === '';
+ default:
+ return false;
+ }
+ }
+ return (
+
+
+ {steps.map(item => )}
+
+
+
+
+
+ {
+ current > 0
+ && (
+
+)}
+ {
+ (current < steps.length - 1 || current === steps.length - 1)
+ &&
+
+ }
+
+
+ );
+ }
+}
diff --git a/user-dashboard/src/app/assets/src/routes/SmartContract/Info/detail.js b/user-dashboard/src/app/assets/src/routes/SmartContract/Info/detail.js
new file mode 100644
index 000000000..599c0718a
--- /dev/null
+++ b/user-dashboard/src/app/assets/src/routes/SmartContract/Info/detail.js
@@ -0,0 +1,65 @@
+/*
+ SPDX-License-Identifier: Apache-2.0
+*/
+import React, { Component, Fragment } from 'react';
+import {
+ Card,
+ Button,
+ Table,
+ Divider,
+} from 'antd';
+import moment from 'moment';
+import styles from './index.less';
+
+
+export default class Detail extends Component {
+
+ render() {
+ const { codes, loadingInfo, onAddNewCode, onDeploy } = this.props;
+ const codeColumns = [
+ {
+ title: 'Version',
+ dataIndex: 'version',
+ key: 'version',
+ },
+ {
+ title: 'Create Time',
+ dataIndex: 'createTime',
+ key: 'createTime',
+ render: text => moment(text).format('YYYY-MM-DD HH:mm:ss'),
+ },
+ {
+ title: 'Operate',
+ render: ( text, record ) => (
+
+ onDeploy(record)}>Deploy
+
+ Delete
+
+ ),
+ },
+ ];
+ return (
+
+ );
+ }
+}
diff --git a/user-dashboard/src/app/assets/src/routes/SmartContract/Info/history.js b/user-dashboard/src/app/assets/src/routes/SmartContract/Info/history.js
new file mode 100644
index 000000000..4f42ebded
--- /dev/null
+++ b/user-dashboard/src/app/assets/src/routes/SmartContract/Info/history.js
@@ -0,0 +1,79 @@
+/*
+ SPDX-License-Identifier: Apache-2.0
+*/
+import React, { Component } from 'react';
+import moment from 'moment';
+import {
+ Card,
+ Badge,
+ Table,
+} from 'antd';
+import styles from './index.less';
+
+const operationTabList = [
+ {
+ key: 'newOperations',
+ tab: 'History of create new code',
+ },
+];
+
+const newOperationColumns = [
+ {
+ title: 'Code Version',
+ dataIndex: 'smartContractCode',
+ key: 'smartContractCode',
+ render: item => item.version,
+ },
+ {
+ title: 'Operate Time',
+ dataIndex: 'operateTime',
+ key: 'operateTime',
+ render: text => moment(text).format('YYYY-MM-DD HH:mm:ss'),
+ },
+ {
+ title: 'Status',
+ dataIndex: 'status',
+ key: 'status',
+ render: text =>
+ text === 'success' ? (
+
+ ) : (
+
+ ),
+ },
+];
+
+export default class History extends Component {
+ state = {
+ operationKey: 'newOperations',
+ };
+
+ onOperationTabChange = key => {
+ this.setState({ operationKey: key });
+ };
+
+ render() {
+ const { loading, newOperations } = this.props;
+ const contentList = {
+ newOperations: (
+
+ ),
+ };
+
+ return (
+
+ {contentList[this.state.operationKey]}
+
+ );
+ }
+}
diff --git a/user-dashboard/src/app/assets/src/routes/SmartContract/Info/index.js b/user-dashboard/src/app/assets/src/routes/SmartContract/Info/index.js
new file mode 100644
index 000000000..5ea85fea9
--- /dev/null
+++ b/user-dashboard/src/app/assets/src/routes/SmartContract/Info/index.js
@@ -0,0 +1,205 @@
+/*
+ SPDX-License-Identifier: Apache-2.0
+*/
+import React, { Component, Fragment } from 'react';
+import Debounce from 'lodash-decorators/debounce';
+import Bind from 'lodash-decorators/bind';
+import pathToRegexp from 'path-to-regexp';
+import { connect } from 'dva';
+import moment from 'moment';
+import { routerRedux } from 'dva/router';
+import {
+ Button,
+ Icon,
+ Tag,
+} from 'antd';
+import DescriptionList from 'components/DescriptionList';
+import PageHeaderLayout from '../../../layouts/PageHeaderLayout';
+import styles from './index.less';
+import History from './history';
+import Deploy from './deploy';
+import Detail from './detail'
+
+const { Description } = DescriptionList;
+
+const getWindowWidth = () => window.innerWidth || document.documentElement.clientWidth;
+
+const action = (
+
+
+
+);
+
+const tabList = [
+ {
+ key: 'detail',
+ tab: 'Detail',
+ },
+ {
+ key: 'history',
+ tab: 'History',
+ },
+ {
+ key: 'deploy',
+ tab: 'Deploy',
+ },
+];
+
+@connect(({ smartContract, chain, loading }) => ({
+ smartContract,
+ chain,
+ loadingInfo: loading.effects['smartContract/querySmartContract'],
+}))
+export default class AdvancedProfile extends Component {
+ state = {
+ operationKey: 'detail',
+ stepDirection: 'horizontal',
+ selectedVersion: '',
+ selectedVersionId: '',
+ deployStep: 0,
+ };
+
+ componentDidMount() {
+ const { location, dispatch } = this.props;
+ const info = pathToRegexp('/smart-contract/info/:id').exec(location.pathname);
+ if (info) {
+ const id = info[1];
+ dispatch({
+ type: 'smartContract/querySmartContract',
+ payload: {
+ id,
+ },
+ });
+ }
+ this.props.dispatch({
+ type: 'chain/fetch',
+ });
+ this.setStepDirection();
+ window.addEventListener('resize', this.setStepDirection);
+ }
+
+ componentWillUnmount() {
+ window.removeEventListener('resize', this.setStepDirection);
+ this.setStepDirection.cancel();
+ }
+
+ onOperationTabChange = key => {
+ this.setState({
+ operationKey: key,
+ deployStep: 0,
+ selectedVersion: '',
+ selectedVersionId: '',
+ });
+ };
+
+ onAddNewCode = () => {
+ const { smartContract, dispatch } = this.props;
+ const { currentSmartContract } = smartContract;
+ dispatch(routerRedux.push({
+ pathname: `/smart-contract/new-code/${currentSmartContract._id}`,
+ }));
+ };
+ onClickDeploy = (smartContractCode) => {
+ this.setState({
+ deployStep: 1,
+ selectedVersion: smartContractCode.version,
+ selectedVersionId: smartContractCode._id,
+ operationKey: 'deploy',
+ });
+ };
+ onDeploy = (payload) => {
+ this.props.dispatch({
+ type: 'smartContract/deploySmartContract',
+ payload,
+ })
+ };
+ onDeployDone = () => {
+ const { dispatch } = this.props;
+ const { smartContract } = this.props;
+ const { currentSmartContract } = smartContract;
+ dispatch({
+ type: 'smartContract/querySmartContract',
+ payload: {
+ id: currentSmartContract._id,
+ },
+ });
+ this.onOperationTabChange('detail');
+ };
+
+ @Bind()
+ @Debounce(200)
+ setStepDirection() {
+ const { stepDirection } = this.state;
+ const w = getWindowWidth();
+ if (stepDirection !== 'vertical' && w <= 576) {
+ this.setState({
+ stepDirection: 'vertical',
+ });
+ } else if (stepDirection !== 'horizontal' && w > 576) {
+ this.setState({
+ stepDirection: 'horizontal',
+ });
+ }
+ }
+ render() {
+ const { smartContract, chain: { chains }, loadingInfo } = this.props;
+ const { currentSmartContract, codes, newOperations } = smartContract;
+ const { deployStep, selectedVersion, selectedVersionId } = this.state;
+ const versions = codes.map(code => code.version);
+ const versionTags = versions.map(version => {version});
+
+ const detailProps = {
+ codes,
+ loadingInfo,
+ onAddNewCode: this.onAddNewCode,
+ onDeploy: this.onClickDeploy,
+ };
+ const deployProps = {
+ version: selectedVersion,
+ versionId: selectedVersionId,
+ current: deployStep,
+ chains,
+ codes,
+ onDeploy: this.onDeploy,
+ onDeployDone: this.onDeployDone,
+ currentSmartContract,
+ };
+
+ const contentList = {
+ detail: (
+
+ ),
+ history: (
+
+ ),
+ deploy: (
+
+ ),
+ };
+
+ const description = (
+
+ {currentSmartContract && moment(currentSmartContract.createTime).format('YYYY-MM-DD HH:mm:ss')}
+ {currentSmartContract && currentSmartContract.description}
+ {versionTags}
+
+ );
+
+ return (
+
+ }
+ loading={loadingInfo}
+ action={action}
+ content={description}
+ tabList={tabList}
+ tabActiveKey={this.state.operationKey}
+ onTabChange={this.onOperationTabChange}
+ >
+ {contentList[this.state.operationKey]}
+
+ );
+ }
+}
diff --git a/user-dashboard/src/app/assets/src/routes/SmartContract/Info/index.less b/user-dashboard/src/app/assets/src/routes/SmartContract/Info/index.less
new file mode 100644
index 000000000..160b02638
--- /dev/null
+++ b/user-dashboard/src/app/assets/src/routes/SmartContract/Info/index.less
@@ -0,0 +1,109 @@
+@import '~antd/lib/style/themes/default.less';
+@import "../../../utils/utils";
+
+.headerList {
+ margin-bottom: 4px;
+}
+
+.tabsCard {
+ :global {
+ .ant-card-head {
+ padding: 0 16px;
+ }
+ }
+}
+
+.noData {
+ color: @disabled-color;
+ text-align: center;
+ line-height: 64px;
+ font-size: 16px;
+ i {
+ font-size: 24px;
+ margin-right: 16px;
+ position: relative;
+ top: 3px;
+ }
+}
+
+.heading {
+ color: @heading-color;
+ font-size: 20px;
+}
+
+.stepDescription {
+ font-size: 14px;
+ position: relative;
+ left: 38px;
+ & > div {
+ margin-top: 8px;
+ margin-bottom: 4px;
+ }
+}
+
+.textSecondary {
+ color: @text-color-secondary;
+}
+
+@media screen and (max-width: @screen-sm) {
+ .stepDescription {
+ left: 8px;
+ }
+}
+
+.tableList {
+ .tableListOperator {
+ margin-bottom: 16px;
+ button {
+ margin-right: 8px;
+ }
+ }
+}
+
+.tableListForm {
+ :global {
+ .ant-form-item {
+ margin-bottom: 24px;
+ margin-right: 0;
+ display: flex;
+ > .ant-form-item-label {
+ width: auto;
+ line-height: 32px;
+ padding-right: 8px;
+ }
+ .ant-form-item-control {
+ line-height: 32px;
+ }
+ }
+ .ant-form-item-control-wrapper {
+ flex: 1;
+ }
+ }
+ .submitButtons {
+ white-space: nowrap;
+ margin-bottom: 24px;
+ }
+}
+
+@media screen and (max-width: @screen-lg) {
+ .tableListForm :global(.ant-form-item) {
+ margin-right: 24px;
+ }
+}
+
+@media screen and (max-width: @screen-md) {
+ .tableListForm :global(.ant-form-item) {
+ margin-right: 8px;
+ }
+}
+
+.step-content {
+ margin-top: 18px;
+}
+
+.step-button {
+ margin-top: 18px;
+ padding: 5px 20px;
+ align-content: center;
+ text-align: center;
+}
diff --git a/user-dashboard/src/app/assets/src/routes/SmartContract/New/code.js b/user-dashboard/src/app/assets/src/routes/SmartContract/New/code.js
new file mode 100644
index 000000000..38e1021e0
--- /dev/null
+++ b/user-dashboard/src/app/assets/src/routes/SmartContract/New/code.js
@@ -0,0 +1,213 @@
+/*
+ SPDX-License-Identifier: Apache-2.0
+*/
+import React, { PureComponent } from 'react';
+import { Card, Form, Input, Button, Upload, Icon, message } from 'antd';
+import { routerRedux } from 'dva/router';
+import { connect } from 'dva';
+import PropTypes from 'prop-types';
+import { injectIntl } from 'react-intl';
+import pathToRegexp from 'path-to-regexp';
+import PageHeaderLayout from '../../../layouts/PageHeaderLayout';
+import config from '../../../utils/config';
+
+const FormItem = Form.Item;
+
+@connect(({ smartContract }) => ({
+ smartContract,
+}))
+@Form.create()
+class NewSmartContractCode extends PureComponent {
+ static contextTypes = {
+ routes: PropTypes.array,
+ params: PropTypes.object,
+ location: PropTypes.object,
+ };
+ state = {
+ submitting: false,
+ smartContractId: '',
+ smartContractCodeId: '',
+ };
+ componentDidMount() {
+ const { location } = this.props;
+ const info = pathToRegexp('/smart-contract/new-code/:id').exec(location.pathname);
+ if (info) {
+ const id = info[1];
+ this.setState({
+ smartContractId: id,
+ });
+ }
+ }
+ onRemoveFile = () => {
+ const { smartContractCodeId } = this.state;
+ this.props.dispatch({
+ type: 'smartContract/deleteSmartContractCode',
+ payload: {
+ id: smartContractCodeId,
+ callback: this.deleteCallback,
+ },
+ })
+ };
+ onUploadFile = info => {
+ if (info.file.status === 'done') {
+ const { response } = info.file;
+ if (response.success) {
+ this.setState({
+ smartContractCodeId: response.id,
+ });
+ } else {
+ message.error("Upload smart contract file failed");
+ }
+ } else if (info.file.status === 'error') {
+ message.error('Upload smart contract file failed.')
+ }
+ };
+ submitCallback = ({ payload, success }) => {
+ const { smartContractId } = this.state;
+ this.setState({
+ submitting: false,
+ });
+ if (success) {
+ message.success(`Create new smart contract version ${payload.version} successfully.`);
+ this.props.dispatch(
+ routerRedux.push({
+ pathname: `/smart-contract/info/${smartContractId}`,
+ })
+ );
+ } else {
+ message.error(`Create new smart contract version ${payload.version} failed.`);
+ }
+ };
+ clickCancel = () => {
+ const { smartContractCodeId } = this.state;
+ if (smartContractCodeId !== '') {
+ this.props.dispatch({
+ type: 'smartContract/deleteSmartContractCode',
+ payload: {
+ id: smartContractCodeId,
+ },
+ });
+ }
+ this.props.dispatch(
+ routerRedux.push({
+ pathname: '/smart-contract',
+ })
+ );
+ };
+ handleSubmit = e => {
+ const { smartContractCodeId } = this.state;
+ e.preventDefault();
+ this.props.form.validateFieldsAndScroll({ force: true }, (err, values) => {
+ if (!err) {
+ this.props.dispatch({
+ type: 'smartContract/updateSmartContractCode',
+ payload: {
+ id: smartContractCodeId,
+ ...values,
+ callback: this.submitCallback,
+ },
+ })
+ }
+ });
+ };
+ normFile = () => {
+ return this.state.smartContractCodeId;
+ };
+ validateUpload = (rule, value, callback) => {
+ const { smartContractCodeId } = this.state;
+ if (smartContractCodeId === '') {
+ callback('Must upload smart contract zip file');
+ }
+ callback();
+ };
+ deleteCallback = () => {
+ this.setState({
+ smartContractCodeId: '',
+ });
+ };
+ render() {
+ const { getFieldDecorator } = this.props.form;
+ const { submitting, smartContractCodeId, smartContractId } = this.state;
+ 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 },
+ },
+ };
+ const uploadProps = {
+ name: "smart_contract",
+ accept: '.zip',
+ action: `${config.url.smartContract.upload}?_csrf=${window.csrf}&id=${smartContractId}`,
+ onChange: this.onUploadFile,
+ onRemove: this.onRemoveFile,
+ beforeUpload() {
+ return smartContractCodeId === '';
+ },
+ };
+
+ return (
+
+
+
+
+
+ );
+ }
+}
+
+export default injectIntl(NewSmartContractCode);
diff --git a/user-dashboard/src/app/assets/src/routes/SmartContract/index.js b/user-dashboard/src/app/assets/src/routes/SmartContract/index.js
index cc250d55a..8c8aa31a2 100644
--- a/user-dashboard/src/app/assets/src/routes/SmartContract/index.js
+++ b/user-dashboard/src/app/assets/src/routes/SmartContract/index.js
@@ -25,7 +25,7 @@ export default class SmartContract extends PureComponent {
newSmartContract = () => {
this.props.dispatch(
routerRedux.push({
- pathname: '/new-smart-contract',
+ pathname: '/smart-contract/new',
})
);
};
@@ -53,6 +53,11 @@ export default class SmartContract extends PureComponent {
},
});
};
+ smartContractInfo = (smartContract) => {
+ this.props.dispatch(routerRedux.push({
+ pathname: `/smart-contract/info/${smartContract._id}`,
+ }));
+ };
render() {
const { smartContract: { smartContracts }, loadingSmartContracts } = this.props;
const content = (
@@ -82,7 +87,7 @@ export default class SmartContract extends PureComponent {
renderItem={item =>
item ? (
- Info, this.deleteSmartContract(item)}>Delete]}>
+ this.smartContractInfo(item)}>Info, this.deleteSmartContract(item)}>Delete]}>
diff --git a/user-dashboard/src/app/assets/src/services/chain.js b/user-dashboard/src/app/assets/src/services/chain.js
index 834e9e2ca..a84a49df7 100644
--- a/user-dashboard/src/app/assets/src/services/chain.js
+++ b/user-dashboard/src/app/assets/src/services/chain.js
@@ -1,22 +1,22 @@
/*
SPDX-License-Identifier: Apache-2.0
*/
-import request from '../utils/request';
-import config from '../utils/config';
+import request from "../utils/request";
+import config from "../utils/config";
export async function queryChains() {
return request(config.url.chain.list);
}
export async function release(id) {
- return request(`${config.url.chain.release.format({id})}`, {
- method: 'DELETE',
+ return request(`${config.url.chain.release.format({ id })}`, {
+ method: "DELETE",
});
}
export async function apply(params) {
return request(config.url.chain.apply, {
- method: 'POST',
+ method: "POST",
body: params,
- })
+ });
}
diff --git a/user-dashboard/src/app/assets/src/services/error.js b/user-dashboard/src/app/assets/src/services/error.js
index 687141036..4fe9bfb3e 100644
--- a/user-dashboard/src/app/assets/src/services/error.js
+++ b/user-dashboard/src/app/assets/src/services/error.js
@@ -1,7 +1,7 @@
/*
SPDX-License-Identifier: Apache-2.0
*/
-import request from '../utils/request';
+import request from "../utils/request";
export async function query(code) {
return request(`/api/${code}`);
diff --git a/user-dashboard/src/app/assets/src/services/smart_contract.js b/user-dashboard/src/app/assets/src/services/smart_contract.js
index ad887fe30..620082d6c 100644
--- a/user-dashboard/src/app/assets/src/services/smart_contract.js
+++ b/user-dashboard/src/app/assets/src/services/smart_contract.js
@@ -1,8 +1,8 @@
/*
SPDX-License-Identifier: Apache-2.0
*/
-import request from '../utils/request';
-import config from '../utils/config';
+import request from "../utils/request";
+import config from "../utils/config";
export async function querySmartContracts() {
return request(config.url.smartContract.list);
@@ -10,19 +10,36 @@ export async function querySmartContracts() {
export async function deleteSmartContractCode(id) {
return request(config.url.smartContract.codeOperate.format({ id }), {
- method: 'DELETE',
+ method: "DELETE",
});
}
export async function updateSmartContractCode(payload) {
- return request(config.url.smartContract.codeOperate.format({ id: payload.id }), {
- method: 'PUT',
- body: payload,
- })
+ return request(
+ config.url.smartContract.codeOperate.format({ id: payload.id }),
+ {
+ method: "PUT",
+ body: payload,
+ }
+ );
}
export async function deleteSmartContract(id) {
return request(config.url.smartContract.operate.format({ id }), {
- method: 'DELETE',
- })
+ method: "DELETE",
+ });
+}
+
+export async function querySmartContract(id) {
+ return request(config.url.smartContract.operate.format({ id }));
+}
+
+export async function deploySmartContract(payload) {
+ return request(
+ config.url.smartContract.codeDeploy.format({ id: payload.id }),
+ {
+ method: "POST",
+ body: payload,
+ }
+ );
}
diff --git a/user-dashboard/src/app/assets/src/services/user.js b/user-dashboard/src/app/assets/src/services/user.js
index e6ca9d2cb..1f6c2b75e 100644
--- a/user-dashboard/src/app/assets/src/services/user.js
+++ b/user-dashboard/src/app/assets/src/services/user.js
@@ -1,19 +1,19 @@
/*
SPDX-License-Identifier: Apache-2.0
*/
-import request from '../utils/request';
+import request from "../utils/request";
export async function query() {
- return request('/api/users');
+ return request("/api/users");
}
export async function queryCurrent() {
- return request('/api/currentUser');
+ return request("/api/currentUser");
}
export async function login(params) {
- return request('/login', {
- method: 'POST',
+ return request("/login", {
+ method: "POST",
body: params,
});
}
diff --git a/user-dashboard/src/app/assets/src/utils/Authorized.js b/user-dashboard/src/app/assets/src/utils/Authorized.js
index d15311c22..f98ddf519 100644
--- a/user-dashboard/src/app/assets/src/utils/Authorized.js
+++ b/user-dashboard/src/app/assets/src/utils/Authorized.js
@@ -1,8 +1,8 @@
/*
SPDX-License-Identifier: Apache-2.0
*/
-import RenderAuthorized from '../components/Authorized';
-import { getAuthority } from './authority';
+import RenderAuthorized from "../components/Authorized";
+import { getAuthority } from "./authority";
let Authorized = RenderAuthorized(getAuthority()); // eslint-disable-line
diff --git a/user-dashboard/src/app/assets/src/utils/authority.js b/user-dashboard/src/app/assets/src/utils/authority.js
index 2a8e4e898..95cd94fad 100644
--- a/user-dashboard/src/app/assets/src/utils/authority.js
+++ b/user-dashboard/src/app/assets/src/utils/authority.js
@@ -3,9 +3,9 @@
*/
// use localStorage to store the authority info, which might be sent from server in actual project.
export function getAuthority() {
- return localStorage.getItem('cello-authority');
+ return localStorage.getItem("cello-authority");
}
export function setAuthority(authority) {
- return localStorage.setItem('cello-authority', authority);
+ return localStorage.setItem("cello-authority", authority);
}
diff --git a/user-dashboard/src/app/assets/src/utils/config.js b/user-dashboard/src/app/assets/src/utils/config.js
index ab11450ab..11e769b2a 100644
--- a/user-dashboard/src/app/assets/src/utils/config.js
+++ b/user-dashboard/src/app/assets/src/utils/config.js
@@ -1,7 +1,7 @@
/*
SPDX-License-Identifier: Apache-2.0
*/
-const format = require('string-format');
+const format = require("string-format");
const urlBase = window.webRoot;
format.extend(String.prototype);
@@ -19,6 +19,7 @@ export default {
upload: `${urlBase}upload-smart-contract`,
codeOperate: `${urlBase}api/smart-contract/code/{id}`,
operate: `${urlBase}api/smart-contract/{id}`,
+ codeDeploy: `${urlBase}api/smart-contract/deploy-code/{id}`,
},
},
};
diff --git a/user-dashboard/src/app/assets/src/utils/request.js b/user-dashboard/src/app/assets/src/utils/request.js
index f8d0b6837..1b587f1d1 100644
--- a/user-dashboard/src/app/assets/src/utils/request.js
+++ b/user-dashboard/src/app/assets/src/utils/request.js
@@ -1,14 +1,14 @@
/*
SPDX-License-Identifier: Apache-2.0
*/
-import fetch from 'dva/fetch';
-import { notification } from 'antd';
-import { routerRedux } from 'dva/router';
-import { IntlProvider, defineMessages } from 'react-intl';
-import store from '../index';
-import { getLocale } from '../utils/utils';
+import fetch from "dva/fetch";
+import { notification } from "antd";
+import { routerRedux } from "dva/router";
+import { IntlProvider, defineMessages } from "react-intl";
+import store from "../index";
+import { getLocale } from "../utils/utils";
-const Cookies = require('js-cookie');
+const Cookies = require("js-cookie");
const currentLocale = getLocale();
const intlProvider = new IntlProvider(
@@ -109,7 +109,9 @@ function checkStatus(response) {
}
const errortext = codeMessage[response.status] || response.statusText;
notification.error({
- message: `${intl.formatMessage(messages.requestError)} ${response.status}: ${response.url}`,
+ message: `${intl.formatMessage(messages.requestError)} ${
+ response.status
+ }: ${response.url}`,
description: errortext,
});
if ([400, 500].indexOf(response.status) < 0) {
@@ -131,33 +133,33 @@ function checkStatus(response) {
*/
export default function request(url, options) {
const defaultOptions = {
- credentials: 'include',
+ credentials: "include",
};
const newOptions = { ...defaultOptions, ...options };
- const csrftoken = Cookies.get('csrfToken');
- if (newOptions.method === 'POST' || newOptions.method === 'PUT') {
+ const csrftoken = Cookies.get("csrfToken");
+ if (newOptions.method === "POST" || newOptions.method === "PUT") {
if (!(newOptions.body instanceof FormData)) {
newOptions.headers = {
- Accept: 'application/json',
- 'Content-Type': 'application/json; charset=utf-8',
+ Accept: "application/json",
+ "Content-Type": "application/json; charset=utf-8",
...newOptions.headers,
};
newOptions.body = JSON.stringify(newOptions.body);
} else {
// newOptions.body is FormData
newOptions.headers = {
- Accept: 'application/json',
- 'Content-Type': 'multipart/form-data',
+ Accept: "application/json",
+ "Content-Type": "multipart/form-data",
...newOptions.headers,
};
}
newOptions.headers = {
- 'x-csrf-token': csrftoken,
+ "x-csrf-token": csrftoken,
...newOptions.headers,
};
- } else if (newOptions.method === 'DELETE') {
+ } else if (newOptions.method === "DELETE") {
newOptions.headers = {
- 'x-csrf-token': csrftoken,
+ "x-csrf-token": csrftoken,
...newOptions.headers,
};
}
@@ -165,7 +167,7 @@ export default function request(url, options) {
return fetch(url, newOptions)
.then(checkStatus)
.then(response => {
- if (newOptions.method === 'DELETE' || response.status === 204) {
+ if (newOptions.method === "DELETE" || response.status === 204) {
return response.text();
}
return response.json();
@@ -175,22 +177,22 @@ export default function request(url, options) {
const status = e.name;
if (status === 401) {
dispatch({
- type: 'login/logout',
+ type: "login/logout",
});
return {
status,
};
}
if (status === 403) {
- dispatch(routerRedux.push('/exception/403'));
+ dispatch(routerRedux.push("/exception/403"));
return;
}
if (status <= 504 && status >= 500) {
- dispatch(routerRedux.push('/exception/500'));
+ dispatch(routerRedux.push("/exception/500"));
return;
}
if (status >= 404 && status < 422) {
- dispatch(routerRedux.push('/exception/404'));
+ dispatch(routerRedux.push("/exception/404"));
}
});
}
diff --git a/user-dashboard/src/app/assets/src/utils/utils.js b/user-dashboard/src/app/assets/src/utils/utils.js
index 2d5405ed7..490c0ee24 100644
--- a/user-dashboard/src/app/assets/src/utils/utils.js
+++ b/user-dashboard/src/app/assets/src/utils/utils.js
@@ -1,9 +1,9 @@
/*
SPDX-License-Identifier: Apache-2.0
*/
-import moment from 'moment';
-import enLocale from '../locales/en-US';
-import zhLocale from '../locales/zh-CN';
+import moment from "moment";
+import enLocale from "../locales/en-US";
+import zhLocale from "../locales/zh-CN";
export function fixedZero(val) {
return val * 1 < 10 ? `0${val}` : val;
@@ -13,14 +13,14 @@ export function getTimeDistance(type) {
const now = new Date();
const oneDay = 1000 * 60 * 60 * 24;
- if (type === 'today') {
+ if (type === "today") {
now.setHours(0);
now.setMinutes(0);
now.setSeconds(0);
return [moment(now), moment(now.getTime() + (oneDay - 1000))];
}
- if (type === 'week') {
+ if (type === "week") {
let day = now.getDay();
now.setHours(0);
now.setMinutes(0);
@@ -37,31 +37,35 @@ export function getTimeDistance(type) {
return [moment(beginTime), moment(beginTime + (7 * oneDay - 1000))];
}
- if (type === 'month') {
+ if (type === "month") {
const year = now.getFullYear();
const month = now.getMonth();
- const nextDate = moment(now).add(1, 'months');
+ const nextDate = moment(now).add(1, "months");
const nextYear = nextDate.year();
const nextMonth = nextDate.month();
return [
moment(`${year}-${fixedZero(month + 1)}-01 00:00:00`),
- moment(moment(`${nextYear}-${fixedZero(nextMonth + 1)}-01 00:00:00`).valueOf() - 1000),
+ moment(
+ moment(
+ `${nextYear}-${fixedZero(nextMonth + 1)}-01 00:00:00`
+ ).valueOf() - 1000
+ ),
];
}
- if (type === 'year') {
+ if (type === "year") {
const year = now.getFullYear();
return [moment(`${year}-01-01 00:00:00`), moment(`${year}-12-31 23:59:59`)];
}
}
-export function getPlainNode(nodeList, parentPath = '') {
+export function getPlainNode(nodeList, parentPath = "") {
const arr = [];
nodeList.forEach(node => {
const item = node;
- item.path = `${parentPath}/${item.path || ''}`.replace(/\/+/g, '/');
+ item.path = `${parentPath}/${item.path || ""}`.replace(/\/+/g, "/");
item.exact = true;
if (item.children && !item.component) {
arr.push(...getPlainNode(item.children, item.path));
@@ -77,10 +81,10 @@ export function getPlainNode(nodeList, parentPath = '') {
function getRelation(str1, str2) {
if (str1 === str2) {
- console.warn('Two path are equal!'); // eslint-disable-line
+ console.warn("Two path are equal!"); // eslint-disable-line
}
- const arr1 = str1.split('/');
- const arr2 = str2.split('/');
+ const arr1 = str1.split("/");
+ const arr2 = str2.split("/");
if (arr2.every((item, index) => item === arr1[index])) {
return 1;
} else if (arr1.every((item, index) => item === arr2[index])) {
@@ -114,12 +118,14 @@ export function getRoutes(path, routerData) {
routePath => routePath.indexOf(path) === 0 && routePath !== path
);
// Replace path to '' eg. path='user' /user/name => name
- routes = routes.map(item => item.replace(path, ''));
+ routes = routes.map(item => item.replace(path, ""));
// Get the route to be rendered to remove the deep rendering
const renderArr = getRenderArr(routes);
// Conversion and stitching parameters
const renderRoutes = renderArr.map(item => {
- const exact = !routes.some(route => route !== item && getRelation(route, item) === 1);
+ const exact = !routes.some(
+ route => route !== item && getRelation(route, item) === 1
+ );
return {
exact,
...routerData[`${path}${item}`],
@@ -138,11 +144,15 @@ export function isUrl(path) {
}
export function getLocale() {
- return ((window.localStorage && localStorage.getItem('language')) || (navigator.language || navigator.browserLanguage).toLowerCase()) === 'en'
+ return ((window.localStorage && localStorage.getItem("language")) ||
+ (navigator.language || navigator.browserLanguage).toLowerCase()) === "en"
? enLocale
: zhLocale;
}
export function getLang() {
- return (window.localStorage && localStorage.getItem('language')) || (navigator.language || navigator.browserLanguage).toLowerCase();
+ return (
+ (window.localStorage && localStorage.getItem("language")) ||
+ (navigator.language || navigator.browserLanguage).toLowerCase()
+ );
}
diff --git a/user-dashboard/src/app/assets/src/utils/utils.less b/user-dashboard/src/app/assets/src/utils/utils.less
index 725792252..ca8a3bbc0 100644
--- a/user-dashboard/src/app/assets/src/utils/utils.less
+++ b/user-dashboard/src/app/assets/src/utils/utils.less
@@ -15,7 +15,7 @@
padding-right: 1em;
&:before {
background: @bg;
- content: '...';
+ content: "...";
padding: 0 1px;
position: absolute;
right: 14px;
@@ -23,7 +23,7 @@
}
&:after {
background: white;
- content: '';
+ content: "";
margin-top: 0.2em;
position: absolute;
right: 14px;
@@ -38,7 +38,7 @@
zoom: 1;
&:before,
&:after {
- content: ' ';
+ content: " ";
display: table;
}
&:after {
diff --git a/user-dashboard/src/app/controller/smart_contract.js b/user-dashboard/src/app/controller/smart_contract.js
index 64788b81c..5afd261b6 100644
--- a/user-dashboard/src/app/controller/smart_contract.js
+++ b/user-dashboard/src/app/controller/smart_contract.js
@@ -34,6 +34,27 @@ class SmartContractController extends Controller {
await ctx.service.smartContract.deleteSmartContract(id);
ctx.status = 204;
}
+ async querySmartContract() {
+ const { ctx } = this;
+ const id = ctx.params.id;
+ ctx.body = await ctx.service.smartContract.querySmartContract(id);
+ }
+ async deploySmartContractCode() {
+ const { ctx } = this;
+ const id = ctx.params.id;
+ const { chainId, operation } = ctx.request.body;
+ switch (operation) {
+ case 'instantiate':
+ ctx.service.smartContract.deploySmartContractCode(id, chainId, operation);
+ ctx.body = {
+ success: true,
+ };
+ break;
+ default:
+ ctx.body = await ctx.service.smartContract.deploySmartContractCode(id, chainId, operation);
+ break;
+ }
+ }
}
module.exports = SmartContractController;
diff --git a/user-dashboard/src/app/extend/context.js b/user-dashboard/src/app/extend/context.js
index 12d75ebab..720793194 100644
--- a/user-dashboard/src/app/extend/context.js
+++ b/user-dashboard/src/app/extend/context.js
@@ -22,6 +22,12 @@ module.exports = {
get joinChannel() {
return this.app.joinChannel;
},
+ get installSmartContract() {
+ return this.app.installSmartContract;
+ },
+ get instantiateSmartContract() {
+ return this.app.instantiateSmartContract;
+ },
get sleep() {
return this.app.sleep;
},
diff --git a/user-dashboard/src/app/lib/fabric/index.js b/user-dashboard/src/app/lib/fabric/index.js
index 45e027efd..91670bf1a 100644
--- a/user-dashboard/src/app/lib/fabric/index.js
+++ b/user-dashboard/src/app/lib/fabric/index.js
@@ -231,6 +231,171 @@ module.exports = app => {
return {
success: false,
};
+ }
+ async function installSmartContract(network, keyValueStorePath, peers, userId, smartContractCodeId, chainId, org) {
+ const ctx = app.createAnonymousContext();
+ const smartContractCode = await ctx.model.SmartContractCode.findOne({ _id: smartContractCodeId });
+ const chain = await ctx.model.Chain.findOne({ _id: chainId });
+ const chainCodeName = `${chain.chainId}-${smartContractCodeId}`;
+ const smartContractSourcePath = `github.com/${smartContractCodeId}`;
+ const chainRootPath = `/opt/data/${userId}/chains/${chainId}`;
+ process.env.GOPATH = chainRootPath;
+ fs.ensureDirSync(`${chainRootPath}/src/github.com`);
+ fs.copySync(smartContractCode.path, `${chainRootPath}/src/${smartContractSourcePath}`);
+
+ const helper = await fabricHelper(network, keyValueStorePath);
+ const client = await getClientForOrg('org1', helper.clients);
+
+ await getOrgAdmin('org1', helper);
+
+ const request = {
+ targets: await newPeers(network, peers, org, helper.clients),
+ chaincodePath: smartContractSourcePath,
+ chaincodeId: chainCodeName,
+ chaincodeVersion: smartContractCode.version,
+ };
+ const results = await client.installChaincode(request);
+ const proposalResponses = results[0];
+ // const proposal = results[1];
+ let all_good = true;
+ for (const i in proposalResponses) {
+ let one_good = false;
+ if (proposalResponses && proposalResponses[i].response &&
+ proposalResponses[i].response.status === 200) {
+ one_good = true;
+ ctx.logger.info('install proposal was good');
+ } else {
+ ctx.logger.error('install proposal was bad');
+ }
+ all_good = all_good & one_good;
+ }
+ if (all_good) {
+ ctx.logger.info(util.format(
+ 'Successfully sent install Proposal and received ProposalResponse: Status - %s',
+ proposalResponses[0].response.status));
+ ctx.logger.debug('\nSuccessfully Installed chaincode on organization ' + org +
+ '\n');
+ const deploy = await ctx.model.SmartContractDeploy.findOneAndUpdate({
+ smartContractCode,
+ smartContract: smartContractCode.smartContract,
+ name: chainCodeName,
+ chain: chainId,
+ }, {
+ status: 'installed',
+ }, { upsert: true, new: true });
+ return {
+ success: true,
+ deployId: deploy._id.toString(),
+ message: 'Successfully Installed chaincode on organization ' + org,
+ };
+ }
+ ctx.logger.error(
+ 'Failed to send install Proposal or receive valid response. Response null or status is not 200. exiting...'
+ );
+ return {
+ success: false,
+ message: 'Failed to send install Proposal or receive valid response. Response null or status is not 200. exiting...',
+ };
+
+ }
+ async function instantiateSmartContract(network, keyValueStorePath, channelName, deployId, functionName, args, org) {
+ const ctx = app.createAnonymousContext();
+ const deploy = await ctx.model.SmartContractDeploy.findOne({ _id: deployId }).populate('smartContractCode');
+ deploy.status = 'instantiating';
+ deploy.save();
+ const helper = await fabricHelper(network, keyValueStorePath);
+ const client = await getClientForOrg('org1', helper.clients);
+ const channel = await getChannelForOrg('org1', helper.channels);
+
+ await getOrgAdmin('org1', helper);
+ await channel.initialize();
+ const txId = client.newTransactionID();
+ // send proposal to endorser
+ const request = {
+ chaincodeId: deploy.name,
+ chaincodeVersion: deploy.smartContractCode.version,
+ args,
+ txId,
+ };
+
+ if (functionName) { request.fcn = functionName; }
+
+ const results = await channel.sendInstantiateProposal(request);
+
+ const proposalResponses = results[0];
+ const proposal = results[1];
+ let all_good = true;
+ for (const i in proposalResponses) {
+ let one_good = false;
+ if (proposalResponses && proposalResponses[i].response &&
+ proposalResponses[i].response.status === 200) {
+ one_good = true;
+ ctx.logger.debug('instantiate proposal was good');
+ } else {
+ ctx.logger.error('instantiate proposal was bad');
+ }
+ all_good = all_good & one_good;
+ }
+ if (all_good) {
+ deploy.status = 'instantiated';
+ deploy.deployTime = Date.now();
+ deploy.save();
+ ctx.logger.info(util.format(
+ 'Successfully sent Proposal and received ProposalResponse: Status - %s, message - "%s", metadata - "%s", endorsement signature: %s',
+ proposalResponses[0].response.status, proposalResponses[0].response.message,
+ proposalResponses[0].response.payload, proposalResponses[0].endorsement
+ .signature));
+ const promiseRequest = {
+ proposalResponses,
+ proposal,
+ };
+ // set the transaction listener and set a timeout of 30sec
+ // if the transaction did not get committed within the timeout period,
+ // fail the test
+ const deployId = await txId.getTransactionID();
+
+ const eh = await client.newEventHub();
+ const data = fs.readFileSync(path.join(__dirname, network[org].peers.peer1.tls_cacerts));
+ await eh.setPeerAddr(network[org].peers.peer1.events, {
+ pem: Buffer.from(data).toString(),
+ 'ssl-target-name-override': network[org].peers.peer1['server-hostname'],
+ });
+ await eh.connect();
+
+ const txPromise = new Promise((resolve, reject) => {
+ const handle = setTimeout(() => {
+ eh.disconnect();
+ reject();
+ }, 30000);
+
+ eh.registerTxEvent(deployId, (tx, code) => {
+ ctx.logger.info(
+ 'The chaincode instantiate transaction has been committed on peer ' +
+ eh._ep._endpoint.addr);
+ clearTimeout(handle);
+ eh.unregisterTxEvent(deployId);
+ eh.disconnect();
+
+ if (code !== 'VALID') {
+ ctx.logger.error('The chaincode instantiate transaction was invalid, code = ' + code);
+ reject();
+ } else {
+ ctx.logger.info('The chaincode instantiate transaction was valid.');
+ resolve();
+ }
+ });
+ });
+
+ const sendPromise = await channel.sendTransaction(promiseRequest);
+ const promiseResults = await Promise.all([sendPromise].concat([txPromise]));
+ return promiseResults[0];
+ }
+ deploy.status = 'error';
+ deploy.save();
+ ctx.logger.error(
+ 'Failed to send instantiate Proposal or receive valid response. Response null or status is not 200. exiting...'
+ );
+ return 'Failed to send instantiate Proposal or receive valid response. Response null or status is not 200. exiting...';
}
async function fabricHelper(network, keyValueStore) {
@@ -274,6 +439,8 @@ module.exports = app => {
app.getChannelForOrg = getChannelForOrg;
app.createChannel = createChannel;
app.joinChannel = joinChannel;
+ app.installSmartContract = installSmartContract;
+ app.instantiateSmartContract = instantiateSmartContract;
app.sleep = sleep;
hfc.setLogger(app.logger);
};
diff --git a/user-dashboard/src/app/model/smart-contract-deploy.js b/user-dashboard/src/app/model/smart-contract-deploy.js
index 58842af2e..0162fe4aa 100644
--- a/user-dashboard/src/app/model/smart-contract-deploy.js
+++ b/user-dashboard/src/app/model/smart-contract-deploy.js
@@ -9,6 +9,9 @@ module.exports = app => {
const SmartContractDeploySchema = new Schema({
user: { type: Schema.Types.ObjectId, ref: 'User' },
+ smartContract: { type: Schema.Types.ObjectId, ref: 'SmartContract' },
+ smartContractCode: { type: Schema.Types.ObjectId, ref: 'SmartContractCode' },
+ chain: { type: Schema.Types.ObjectId, ref: 'Chain' },
name: { type: String },
status: { type: String, default: 'idle', enum: [ 'idle', 'installed', 'instantiating', 'instantiated', 'error'] },
deployTime: { type: Date, default: Date.now },
diff --git a/user-dashboard/src/app/model/smart-contract-operate-history.js b/user-dashboard/src/app/model/smart-contract-operate-history.js
new file mode 100644
index 000000000..0432dd9cc
--- /dev/null
+++ b/user-dashboard/src/app/model/smart-contract-operate-history.js
@@ -0,0 +1,21 @@
+/*
+ SPDX-License-Identifier: Apache-2.0
+*/
+'use strict';
+
+module.exports = app => {
+ const mongoose = app.mongoose;
+ const Schema = mongoose.Schema;
+
+ const SmartContractOperateHistorySchema = new Schema({
+ user: { type: Schema.Types.ObjectId, ref: 'User' },
+ smartContract: { type: Schema.Types.ObjectId, ref: 'SmartContract' },
+ smartContractCode: { type: Schema.Types.ObjectId, ref: 'SmartContractCode' },
+ chain: { type: Schema.Types.ObjectId, ref: 'Chain' },
+ operate: { type: String, enum: ['new', 'deploy', 'delete-code'] },
+ status: { type: String, default: 'success', enum: ['success', 'failed'] },
+ operateTime: { type: Date, default: Date.now },
+ });
+
+ return mongoose.model('SmartContractOperateHistory', SmartContractOperateHistorySchema);
+};
diff --git a/user-dashboard/src/app/router/api.js b/user-dashboard/src/app/router/api.js
index eee2a81b7..050eaadf2 100644
--- a/user-dashboard/src/app/router/api.js
+++ b/user-dashboard/src/app/router/api.js
@@ -14,4 +14,6 @@ module.exports = app => {
app.router.delete('/api/smart-contract/code/:id', app.controller.smartContract.removeSmartContractCode);
app.router.put('/api/smart-contract/code/:id', app.controller.smartContract.updateSmartContractCode);
app.router.delete('/api/smart-contract/:id', app.controller.smartContract.deleteSmartContract);
+ app.router.get('/api/smart-contract/:id', app.controller.smartContract.querySmartContract);
+ app.router.post('/api/smart-contract/deploy-code/:id', app.controller.smartContract.deploySmartContractCode);
};
diff --git a/user-dashboard/src/app/service/smart_contract.js b/user-dashboard/src/app/service/smart_contract.js
index bed88e986..5d88b0d09 100644
--- a/user-dashboard/src/app/service/smart_contract.js
+++ b/user-dashboard/src/app/service/smart_contract.js
@@ -48,8 +48,9 @@ class SmartContractService extends Service {
}
async storeSmartContract(stream) {
const { ctx, config } = this;
+ const id = ctx.query.id;
const smartContractRootDir = `${config.dataDir}/${ctx.user.id}/smart_contract`;
- const smartContractId = new ObjectID();
+ const smartContractId = id || new ObjectID();
const targetFileName = `${smartContractId}${path.extname(stream.filename)}`;
const smartContractPath = `${smartContractRootDir}/${smartContractId}`;
const smartContractCodePath = `${smartContractRootDir}/${smartContractId}/tmp`;
@@ -67,11 +68,13 @@ class SmartContractService extends Service {
const zip = AdmZip(zipFile);
zip.extractAllTo(smartContractCodePath, true);
commonFs.unlinkSync(zipFile);
- await ctx.model.SmartContract.create({
- _id: smartContractId,
- path: smartContractPath,
- user: ctx.user.id,
- });
+ if (!id) {
+ await ctx.model.SmartContract.create({
+ _id: smartContractId,
+ path: smartContractPath,
+ user: ctx.user.id,
+ });
+ }
const smartContractCode = await ctx.model.SmartContractCode.create({
smartContract: smartContractId,
path: smartContractCodePath,
@@ -121,6 +124,12 @@ class SmartContractService extends Service {
smartContract.name = name;
smartContract.description = description;
await smartContract.save();
+ await ctx.model.SmartContractOperateHistory.create({
+ user: smartContract.user,
+ smartContract,
+ smartContractCode,
+ operate: 'new',
+ });
return {
success: true,
};
@@ -134,6 +143,47 @@ class SmartContractService extends Service {
}
await smartContract.remove();
}
+ async querySmartContract(id) {
+ const { ctx } = this;
+ const smartContract = await ctx.model.SmartContract.findOne({ _id: id }, '_id description name createTime');
+ if (!smartContract) {
+ return {
+ success: false,
+ };
+ }
+ const codes = await ctx.model.SmartContractCode.find({ smartContract }, '_id version createTime');
+ const newOperations = await ctx.model.SmartContractOperateHistory.find({ smartContract }, '_id operateTime status').populate('smartContractCode', 'version');
+ const deploys = await ctx.model.SmartContractDeploy.find({ smartContract }, '_id status deployTime');
+ return {
+ success: true,
+ info: smartContract,
+ codes,
+ newOperations,
+ deploys,
+ };
+ }
+ async deploySmartContractCode(id, chainId, operation) {
+ const { ctx, config } = this;
+
+ const { functionName, args, deployId } = ctx.request.body;
+ const chainRootDir = `${config.dataDir}/${ctx.user.id}/chains/${chainId}`;
+ const keyValueStorePath = `${chainRootDir}/client-kvs`;
+ const network = await ctx.service.chain.generateNetwork(chainId);
+ switch (operation) {
+ case 'install':
+ return await ctx.installSmartContract(network, keyValueStorePath, ['peer1', 'peer2'], ctx.user.id, id, chainId, 'org1');
+ case 'instantiate':
+ ctx.instantiateSmartContract(network, keyValueStorePath, config.default.channelName, deployId, functionName, args, 'org1');
+ return {
+ success: true,
+ };
+ default:
+ return {
+ success: false,
+ message: 'Please input deploy operation',
+ };
+ }
+ }
}
module.exports = SmartContractService;
diff --git a/user-dashboard/src/package.json b/user-dashboard/src/package.json
index 2931d8f62..19ce1d6ba 100644
--- a/user-dashboard/src/package.json
+++ b/user-dashboard/src/package.json
@@ -118,6 +118,7 @@
"autod": "autod",
"build": "if-env DEV=True && npm run build:dev || npm run build:prod",
"build:dev": "cross-env ESLINT=none COMPRESS=none roadhog build && cp -r app/assets/src/assets/* app/assets/public/",
+ "prettier": "prettier --write ./app/**/**/**/*.{js,jsx,less}",
"build:prod": "cross-env ESLINT=none roadhog build && cp -r app/assets/src/assets/* app/assets/public/"
},
"ci": {