├── release_note.txt
├── .eslintignore
├── tests
└── unit
│ ├── .eslintrc.js
│ └── utils
│ ├── param2Obj.spec.js
│ ├── formatTime.spec.js
│ ├── parseTime.spec.js
│ └── validate.spec.js
├── public
├── favicon.ico
└── index.html
├── src
├── assets
│ ├── app.png
│ ├── chain.png
│ ├── issue.png
│ ├── QRCode.jpg
│ ├── router.png
│ ├── 404_images
│ │ ├── 404.png
│ │ └── 404_cloud.png
│ ├── icon-zy.svg
│ ├── check-fail.svg
│ ├── check-pass.svg
│ ├── icon-bc.svg
│ ├── icon-ly.svg
│ ├── icon-sw.svg
│ ├── nav-logo.svg
│ ├── favicon.svg
│ └── wecross.svg
├── styles
│ ├── intro.scss
│ ├── variables.scss
│ ├── element-variables.scss
│ ├── transition.scss
│ ├── mixin.scss
│ ├── element-ui.scss
│ ├── index.scss
│ └── sidebar.scss
├── layout
│ ├── components
│ │ ├── index.js
│ │ ├── Sidebar
│ │ │ ├── FixiOSBug.js
│ │ │ ├── Link.vue
│ │ │ ├── Item.vue
│ │ │ ├── index.vue
│ │ │ ├── Logo.vue
│ │ │ └── SidebarItem.vue
│ │ └── AppMain.vue
│ └── mixin
│ │ └── ResizeHandler.js
├── App.vue
├── utils
│ ├── get-page-title.js
│ ├── rsa.js
│ ├── errorcode.js
│ ├── clipboard.js
│ ├── auth.js
│ ├── authcode.js
│ ├── transaction.js
│ ├── validate.js
│ ├── chainAccountIntro.js
│ ├── resource.js
│ ├── index.js
│ ├── request.js
│ ├── messageBox.js
│ └── pem.js
├── .travis.yml
├── icons
│ ├── index.js
│ ├── svgo.yml
│ └── svg
│ │ ├── fullscreen.svg
│ │ ├── user.svg
│ │ ├── table.svg
│ │ ├── search.svg
│ │ ├── password.svg
│ │ ├── eye.svg
│ │ ├── question.svg
│ │ ├── qrcode.svg
│ │ ├── user-avatar.svg
│ │ ├── eye-open.svg
│ │ ├── exit-fullscreen.svg
│ │ └── chicken.svg
├── store
│ ├── getters.js
│ ├── index.js
│ └── modules
│ │ ├── app.js
│ │ ├── permission.js
│ │ └── transaction.js
├── views
│ ├── resource
│ │ ├── resourceSteps
│ │ │ ├── resourceManagerSteps.js
│ │ │ └── resourceDeploySteps.js
│ │ └── resourceManager.vue
│ ├── transaction
│ │ ├── transactionSteps
│ │ │ ├── transactionManagerSteps.js
│ │ │ └── xaTransactionStep.js
│ │ └── transactionManager.vue
│ ├── router
│ │ ├── routerGuide.vue
│ │ └── routerManager.vue
│ └── document
│ │ └── index.vue
├── components
│ ├── Clipboard
│ │ └── index.vue
│ ├── Hamburger
│ │ └── index.vue
│ ├── SvgIcon
│ │ └── index.vue
│ ├── Breadcrumb
│ │ └── index.vue
│ ├── ResourceShower
│ │ └── index.vue
│ ├── ChainExplorer
│ │ └── index.vue
│ └── HeaderSearch
│ │ └── index.vue
├── main.js
├── api
│ ├── status.js
│ ├── conn.js
│ ├── user.js
│ ├── resource.js
│ ├── ua.js
│ ├── common.js
│ └── transaction.js
└── permission.js
├── .env.production
├── babel.config.js
├── plop-templates
├── utils.js
├── store
│ ├── index.hbs
│ └── prompt.js
├── view
│ ├── index.hbs
│ └── prompt.js
└── component
│ ├── index.hbs
│ └── prompt.js
├── .env.development
├── docs
└── images
│ └── menu_logo_wecross.png
├── .travis.yml
├── .gitignore
├── plopfile.js
├── mock
├── index.js
├── utils.js
├── status.js
├── resource.js
├── mock-server.js
├── conn.js
├── ua.js
└── user.js
├── .github
└── workflows
│ └── ci_check.yml
├── jest.config.js
├── CONTRIBUTING.md
├── Changelog.md
├── README.md
├── package.json
└── vue.config.js
/release_note.txt:
--------------------------------------------------------------------------------
1 | v1.4.0
2 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | build/*.js
2 | src/assets
3 | public
4 | dist
5 |
--------------------------------------------------------------------------------
/tests/unit/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | jest: true
4 | }
5 | }
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WeBankBlockchain/WeCross-WebApp/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/src/assets/app.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WeBankBlockchain/WeCross-WebApp/HEAD/src/assets/app.png
--------------------------------------------------------------------------------
/.env.production:
--------------------------------------------------------------------------------
1 | # just a flag
2 | ENV = 'production'
3 |
4 | # base api
5 | VUE_APP_BASE_API = '/'
6 |
7 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/cli-plugin-babel/preset'
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/src/assets/chain.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WeBankBlockchain/WeCross-WebApp/HEAD/src/assets/chain.png
--------------------------------------------------------------------------------
/src/assets/issue.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WeBankBlockchain/WeCross-WebApp/HEAD/src/assets/issue.png
--------------------------------------------------------------------------------
/src/assets/QRCode.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WeBankBlockchain/WeCross-WebApp/HEAD/src/assets/QRCode.jpg
--------------------------------------------------------------------------------
/src/assets/router.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WeBankBlockchain/WeCross-WebApp/HEAD/src/assets/router.png
--------------------------------------------------------------------------------
/plop-templates/utils.js:
--------------------------------------------------------------------------------
1 | exports.notEmpty = name => v =>
2 | !v || v.trim() === '' ? `${name} is required` : true
3 |
--------------------------------------------------------------------------------
/src/assets/404_images/404.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WeBankBlockchain/WeCross-WebApp/HEAD/src/assets/404_images/404.png
--------------------------------------------------------------------------------
/.env.development:
--------------------------------------------------------------------------------
1 | # just a flag
2 | ENV = 'development'
3 |
4 | # base api
5 | VUE_APP_BASE_API = ''
6 | VUE_APP_MOCK_API = '/dev-api'
--------------------------------------------------------------------------------
/docs/images/menu_logo_wecross.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WeBankBlockchain/WeCross-WebApp/HEAD/docs/images/menu_logo_wecross.png
--------------------------------------------------------------------------------
/src/assets/404_images/404_cloud.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WeBankBlockchain/WeCross-WebApp/HEAD/src/assets/404_images/404_cloud.png
--------------------------------------------------------------------------------
/src/styles/intro.scss:
--------------------------------------------------------------------------------
1 | .customTooltip {
2 | width: 300px;
3 | }
4 |
5 | .customTooltip .text-blue {
6 | color: #0366d6;
7 | }
8 |
9 |
--------------------------------------------------------------------------------
/src/layout/components/index.js:
--------------------------------------------------------------------------------
1 | export { default as Navbar } from './Navbar'
2 | export { default as Sidebar } from './Sidebar'
3 | export { default as AppMain } from './AppMain'
4 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
--------------------------------------------------------------------------------
/src/utils/get-page-title.js:
--------------------------------------------------------------------------------
1 | const title = 'WeCross Web App'
2 |
3 | export default function getPageTitle(pageTitle) {
4 | if (pageTitle) {
5 | return `${pageTitle} - ${title}`
6 | }
7 | return `${title}`
8 | }
9 |
--------------------------------------------------------------------------------
/src/utils/rsa.js:
--------------------------------------------------------------------------------
1 | import JSEncrypt from 'jsencrypt'
2 |
3 | /**
4 | * rsa encrypt
5 | */
6 | export function rsa_encode(input, pub) {
7 | const encryptor = new JSEncrypt(null)
8 | encryptor.setPublicKey(pub)
9 | return encryptor.encrypt(input)
10 | }
11 |
--------------------------------------------------------------------------------
/src/.travis.yml:
--------------------------------------------------------------------------------
1 | # safelist
2 | branches:
3 | only:
4 | - /.*/
5 |
6 | matrix:
7 | fast_finish: true
8 | include:
9 |
10 | - language: node_js
11 | node_js: 10
12 | script: npm run tests:ci
13 | os: linux
14 | dist: xenial
15 |
--------------------------------------------------------------------------------
/plop-templates/store/index.hbs:
--------------------------------------------------------------------------------
1 | {{#if state}}
2 | const state = {}
3 | {{/if}}
4 |
5 | {{#if mutations}}
6 | const mutations = {}
7 | {{/if}}
8 |
9 | {{#if actions}}
10 | const actions = {}
11 | {{/if}}
12 |
13 | export default {
14 | namespaced: true,
15 | {{options}}
16 | }
17 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | # safelist
2 | branches:
3 | only:
4 | - /.*/
5 |
6 | matrix:
7 | fast_finish: true
8 | include:
9 |
10 | - language: node_js
11 | node_js: 10
12 | script: npm install && npm run build:prod && npm run test:ci
13 | os: linux
14 | dist: xenial
15 |
--------------------------------------------------------------------------------
/src/icons/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import SvgIcon from '@/components/SvgIcon'// svg component
3 |
4 | // register globally
5 | Vue.component('svg-icon', SvgIcon)
6 |
7 | const req = require.context('./svg', false, /\.svg$/)
8 | const requireAll = requireContext => requireContext.keys().map(requireContext)
9 | requireAll(req)
10 |
--------------------------------------------------------------------------------
/src/icons/svgo.yml:
--------------------------------------------------------------------------------
1 | # replace default config
2 |
3 | # multipass: true
4 | # full: true
5 |
6 | plugins:
7 |
8 | # - name
9 | #
10 | # or:
11 | # - name: false
12 | # - name: true
13 | #
14 | # or:
15 | # - name:
16 | # param1: 1
17 | # param2: 2
18 |
19 | - removeAttrs:
20 | attrs:
21 | - 'fill'
22 | - 'fill-rule'
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 |
6 | # local env files
7 | .env.local
8 | .env.*.local
9 | package-lock.json
10 |
11 | # Log files
12 | npm-debug.log*
13 | yarn-debug.log*
14 | yarn-error.log*
15 | pnpm-debug.log*
16 |
17 | # Editor directories and files
18 | .idea
19 | .vscode
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/plopfile.js:
--------------------------------------------------------------------------------
1 | const viewGenerator = require('./plop-templates/view/prompt')
2 | const componentGenerator = require('./plop-templates/component/prompt')
3 | const storeGenerator = require('./plop-templates/store/prompt.js')
4 |
5 | module.exports = function(plop) {
6 | plop.setGenerator('view', viewGenerator)
7 | plop.setGenerator('component', componentGenerator)
8 | plop.setGenerator('store', storeGenerator)
9 | }
10 |
--------------------------------------------------------------------------------
/mock/index.js:
--------------------------------------------------------------------------------
1 | const user = require('./user')
2 | const resource = require('./resource')
3 | const conn = require('./conn')
4 | const ua = require('./ua')
5 | const transaction = require('./transaction')
6 | const status = require('./status')
7 |
8 | const mocks = [
9 | ...user,
10 | ...resource,
11 | ...conn,
12 | ...ua,
13 | ...transaction,
14 | ...status
15 | ]
16 |
17 | module.exports = {
18 | mocks
19 | }
20 |
21 |
--------------------------------------------------------------------------------
/src/icons/svg/fullscreen.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/plop-templates/view/index.hbs:
--------------------------------------------------------------------------------
1 | {{#if template}}
2 |
3 |
4 |
5 | {{/if}}
6 |
7 | {{#if script}}
8 |
20 | {{/if}}
21 |
22 | {{#if style}}
23 |
26 | {{/if}}
27 |
--------------------------------------------------------------------------------
/src/icons/svg/user.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/plop-templates/component/index.hbs:
--------------------------------------------------------------------------------
1 | {{#if template}}
2 |
3 |
4 |
5 | {{/if}}
6 |
7 | {{#if script}}
8 |
20 | {{/if}}
21 |
22 | {{#if style}}
23 |
26 | {{/if}}
27 |
--------------------------------------------------------------------------------
/tests/unit/utils/param2Obj.spec.js:
--------------------------------------------------------------------------------
1 | import { param2Obj } from '@/utils/index.js'
2 | describe('Utils:param2Obj', () => {
3 | const url = 'https://github.com/PanJiaChen/vue-element-admin?name=bill&age=29&sex=1&field=dGVzdA==&key=%E6%B5%8B%E8%AF%95'
4 |
5 | it('param2Obj test', () => {
6 | expect(param2Obj(url)).toEqual({
7 | name: 'bill',
8 | age: '29',
9 | sex: '1',
10 | field: window.btoa('test'),
11 | key: '测试'
12 | })
13 | })
14 | })
15 |
--------------------------------------------------------------------------------
/src/store/getters.js:
--------------------------------------------------------------------------------
1 | const getters = {
2 | sidebar: state => state.app.sidebar,
3 | device: state => state.app.device,
4 | token: state => state.user.token,
5 | avatar: state => state.user.avatar,
6 | name: state => state.user.name,
7 | roles: state => state.user.roles,
8 | transactionID: state => state.transaction.transactionID,
9 | XAPaths: state => state.transaction.paths,
10 | permissionRoutes: state => state.permission.routes
11 | }
12 | export default getters
13 |
--------------------------------------------------------------------------------
/src/assets/icon-zy.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/.github/workflows/ci_check.yml:
--------------------------------------------------------------------------------
1 | name: CI Check
2 |
3 | on: [pull_request]
4 |
5 | jobs:
6 | check:
7 | runs-on: ubuntu-latest
8 | steps:
9 | - uses: actions/checkout@v2
10 | - uses: actions/setup-node@v2-beta
11 | with:
12 | node-version: '10'
13 | - name: npm install
14 | run: npm install
15 | - name: Build production
16 | run: npm install && npm run build:prod
17 | - name: Test
18 | run: npm run test:ci
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Vuex from 'vuex'
3 | import getters from './getters'
4 | import app from './modules/app'
5 | import user from './modules/user'
6 | import transaction from './modules/transaction'
7 | import permission from '@/store/modules/permission'
8 |
9 | Vue.use(Vuex)
10 |
11 | const store = new Vuex.Store({
12 | modules: {
13 | app,
14 | user,
15 | transaction,
16 | permission
17 | },
18 | getters
19 | })
20 |
21 | export default store
22 |
--------------------------------------------------------------------------------
/src/views/resource/resourceSteps/resourceManagerSteps.js:
--------------------------------------------------------------------------------
1 | export const resourceManagerSteps = [
2 | {
3 | element: '#ChainExplorer',
4 | title: '1. 导航选择',
5 | intro: '第一步:从zone-chain导航中选择对应的链',
6 | position: 'right'
7 | },
8 | {
9 | element: '#ResourceExplorer',
10 | title: '2. 资源列表展示',
11 | intro: '第二步:选择好对应的链之后,就会在资源列表中展示这条链中所有资源',
12 | position: 'left'
13 | },
14 | {
15 | element: '#resourceDeploy',
16 | title: '资源部署',
17 | intro: '若需要部署资源,请点击"部署资源"按钮',
18 | position: 'left'
19 | }
20 | ]
21 |
--------------------------------------------------------------------------------
/src/views/transaction/transactionSteps/transactionManagerSteps.js:
--------------------------------------------------------------------------------
1 | export const transactionManagerSteps = [
2 | {
3 | element: '#ChainExplorer',
4 | title: '1. 导航选择',
5 | intro: '第一步:从zone-chain导航中选择对应的链',
6 | position: 'right'
7 | },
8 | {
9 | element: '#TransactionListExplorer',
10 | title: '2. 交易列表展示',
11 | intro: '第二步:选择好对应的链之后,就会在资源列表中展示这条链中所有交易',
12 | position: 'left'
13 | },
14 | {
15 | element: '#sendTransaction',
16 | title: '发起交易',
17 | intro: '若需要发起跨链交易,请点击"发交易"按钮',
18 | position: 'left'
19 | }
20 | ]
21 |
--------------------------------------------------------------------------------
/src/utils/errorcode.js:
--------------------------------------------------------------------------------
1 | export const ErrorCode = {
2 | Success: 0,
3 | InvalidParameters: 40001,
4 | UAAccountExist: 40002,
5 | UAAccountNotExist: 40003,
6 | ChainAccountExist: 40004,
7 | ChainAccountNotExist: 40005,
8 | ConfigurationItemError: 40006,
9 | AccountOrPasswordIncorrect: 40007,
10 | ImageAuthTokenExpired: 40008,
11 | ImageAuthTokenNotExist: 40009,
12 | ImageAuthTokenNotMatch: 40010,
13 | CreateUAFailed: 40011,
14 | UserHasLogout: 40012,
15 | ChainAccountTypeNotFound: 40013,
16 | FlushDataException: 40014,
17 | UndefinedError: 40099
18 | }
19 |
20 |
--------------------------------------------------------------------------------
/src/components/Clipboard/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
27 |
--------------------------------------------------------------------------------
/src/icons/svg/table.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/search.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/layout/components/Sidebar/FixiOSBug.js:
--------------------------------------------------------------------------------
1 | export default {
2 | computed: {
3 | device() {
4 | return this.$store.state.app.device
5 | }
6 | },
7 | mounted() {
8 | this.fixBugIniOS()
9 | },
10 | methods: {
11 | fixBugIniOS() {
12 | const $subMenu = this.$refs.subMenu
13 | if ($subMenu) {
14 | const handleMouseleave = $subMenu.handleMouseleave
15 | $subMenu.handleMouseleave = (e) => {
16 | if (this.device === 'mobile') {
17 | return
18 | }
19 | handleMouseleave(e)
20 | }
21 | }
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/mock/utils.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @param {string} url
3 | * @returns {Object}
4 | */
5 | function param2Obj(url) {
6 | const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ')
7 | if (!search) {
8 | return {}
9 | }
10 | const obj = {}
11 | const searchArr = search.split('&')
12 | searchArr.forEach(v => {
13 | const index = v.indexOf('=')
14 | if (index !== -1) {
15 | const name = v.substring(0, index)
16 | const val = v.substring(index + 1, v.length)
17 | obj[name] = val
18 | }
19 | })
20 | return obj
21 | }
22 |
23 | module.exports = {
24 | param2Obj
25 | }
26 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 |
3 | import 'normalize.css/normalize.css' // A modern alternative to CSS resets
4 |
5 | import ElementUI from 'element-ui'
6 | import 'element-ui/lib/theme-chalk/index.css'
7 |
8 | import '@/styles/index.scss' // global css
9 |
10 | import App from './App'
11 | import store from './store'
12 | import router from './router'
13 |
14 | import '@/icons' // icon
15 | import '@/permission' // permission control
16 |
17 | Vue.use(ElementUI)
18 |
19 | Vue.config.productionTip = false
20 |
21 | new Vue({
22 | el: '#app',
23 | router,
24 | store,
25 | render: h => h(App)
26 | })
27 |
--------------------------------------------------------------------------------
/src/icons/svg/password.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | <%= htmlWebpackPlugin.options.title %>
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/utils/clipboard.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Clipboard from 'clipboard'
3 |
4 | function clipboardSuccess() {
5 | Vue.prototype.$message({
6 | message: '复制成功',
7 | type: 'success',
8 | duration: 1500
9 | })
10 | }
11 |
12 | function clipboardError() {
13 | Vue.prototype.$message({
14 | message: '复制失败',
15 | type: 'error'
16 | })
17 | }
18 |
19 | export default function handleClipboard(text, event) {
20 | const clipboard = new Clipboard(event.target, {
21 | text: () => text
22 | })
23 | clipboard.on('success', () => {
24 | clipboardSuccess()
25 | clipboard.destroy()
26 | })
27 | clipboard.on('error', () => {
28 | clipboardError()
29 | clipboard.destroy()
30 | })
31 | clipboard.onClick(event)
32 | }
33 |
--------------------------------------------------------------------------------
/src/assets/check-fail.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | moduleFileExtensions: ['js', 'jsx', 'json', 'vue'],
3 | transform: {
4 | '^.+\\.vue$': 'vue-jest',
5 | '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$':
6 | 'jest-transform-stub',
7 | '^.+\\.jsx?$': 'babel-jest'
8 | },
9 | moduleNameMapper: {
10 | '^@/(.*)$': '/src/$1'
11 | },
12 | snapshotSerializers: ['jest-serializer-vue'],
13 | testMatch: [
14 | '**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)'
15 | ],
16 | collectCoverageFrom: ['src/unit/**/*.{js,vue}', '!src/unit/auth.js', '!src/unit/request.js', 'src/components/**/*.{js,vue}'],
17 | coverageDirectory: '/tests/unit/coverage',
18 | // 'collectCoverage': true,
19 | 'coverageReporters': [
20 | 'lcov',
21 | 'text-summary'
22 | ],
23 | testURL: 'http://localhost/'
24 | }
25 |
--------------------------------------------------------------------------------
/src/api/status.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 |
3 | /**
4 | * get the status of system
5 | * @return {Promise} an axios promise object of response
6 | */
7 | export function systemStatus() {
8 | return request({
9 | url: '/sys/systemStatus',
10 | method: 'get'
11 | })
12 | }
13 |
14 | /**
15 | * list supported stub types in wecross
16 | * @return {Promise} an axios promise object of response
17 | */
18 | export function supportedStubs(params) {
19 | return request({
20 | url: '/sys/supportedStubs',
21 | method: 'get',
22 | params
23 | })
24 | }
25 |
26 | /**
27 | * get wecross router status
28 | * @return {Promise} an axios promise object of response
29 | */
30 | export function routerStatus() {
31 | return request({
32 | url: '/sys/routerStatus',
33 | method: 'get'
34 | })
35 | }
36 |
--------------------------------------------------------------------------------
/src/layout/components/Sidebar/Link.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
44 |
--------------------------------------------------------------------------------
/src/views/router/routerGuide.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
31 |
32 |
35 |
--------------------------------------------------------------------------------
/src/icons/svg/eye.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/styles/variables.scss:
--------------------------------------------------------------------------------
1 | // base color
2 | $blue:#324157;
3 | $light-blue:#3A71A8;
4 | $red:#C03639;
5 | $pink: #E65D6E;
6 | $green: #30B08F;
7 | $tiffany: #4AB7BD;
8 | $yellow:#FEC171;
9 | $panGreen: #30B08F;
10 |
11 | // sidebar
12 | $menuText:#bfcbd9;
13 | $menuActiveText:#409EFF;
14 | $subMenuActiveText:#f4f4f5; // https://github.com/ElemeFE/element/issues/12951
15 |
16 | $menuBg:#304156;
17 | $menuHover:#263445;
18 |
19 | $subMenuBg:#1f2d3d;
20 | $subMenuHover:#001528;
21 |
22 | $sideBarWidth: 190px;
23 |
24 | // the :export directive is the magic sauce for webpack
25 | // https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
26 | :export {
27 | menuText: $menuText;
28 | menuActiveText: $menuActiveText;
29 | subMenuActiveText: $subMenuActiveText;
30 | menuBg: $menuBg;
31 | menuHover: $menuHover;
32 | subMenuBg: $subMenuBg;
33 | subMenuHover: $subMenuHover;
34 | sideBarWidth: $sideBarWidth;
35 | }
36 |
--------------------------------------------------------------------------------
/src/styles/element-variables.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * I think element-ui's default theme color is too light for long-term use.
3 | * So I modified the default color and you can modify it to your liking.
4 | **/
5 |
6 | /* theme color */
7 | $--color-primary: #1890ff;
8 | $--color-success: #13ce66;
9 | $--color-warning: #ffba00;
10 | $--color-danger: #ff4949;
11 | // $--color-info: #1E1E1E;
12 |
13 | $--button-font-weight: 400;
14 |
15 | // $--color-text-regular: #1f2d3d;
16 |
17 | $--border-color-light: #dfe4ed;
18 | $--border-color-lighter: #e6ebf5;
19 |
20 | $--table-border: 1px solid #dfe6ec;
21 |
22 | /* icon font path, required */
23 | $--font-path: "~element-ui/lib/theme-chalk/fonts";
24 |
25 | @import "~element-ui/packages/theme-chalk/src/index";
26 |
27 | // the :export directive is the magic sauce for webpack
28 | // https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
29 | :export {
30 | theme: $--color-primary;
31 | }
32 |
--------------------------------------------------------------------------------
/src/styles/transition.scss:
--------------------------------------------------------------------------------
1 | // global transition css
2 |
3 | /* fade */
4 | .fade-enter-active,
5 | .fade-leave-active {
6 | transition: opacity 0.2s;
7 | }
8 |
9 | .fade-enter,
10 | .fade-leave-active {
11 | opacity: 0;
12 | }
13 |
14 | /* fade-transform */
15 | .fade-transform-leave-active,
16 | .fade-transform-enter-active {
17 | transition: all .2s;
18 | }
19 |
20 | .fade-transform-enter {
21 | opacity: 0;
22 | transform: translateX(-30px);
23 | }
24 |
25 | .fade-transform-leave-to {
26 | opacity: 0;
27 | transform: translateX(30px);
28 | }
29 |
30 | /* breadcrumb transition */
31 | .breadcrumb-enter-active,
32 | .breadcrumb-leave-active {
33 | transition: all .2s;
34 | }
35 |
36 | .breadcrumb-enter,
37 | .breadcrumb-leave-active {
38 | opacity: 0;
39 | transform: translateX(20px);
40 | }
41 |
42 | .breadcrumb-move {
43 | transition: all .2s;
44 | }
45 |
46 | .breadcrumb-leave-active {
47 | position: absolute;
48 | }
49 |
--------------------------------------------------------------------------------
/src/assets/check-pass.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
20 |
--------------------------------------------------------------------------------
/src/icons/svg/question.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icon-bc.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/src/layout/components/Sidebar/Item.vue:
--------------------------------------------------------------------------------
1 |
34 |
35 |
47 |
--------------------------------------------------------------------------------
/src/assets/icon-ly.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # 贡献代码
2 |
3 | 非常感谢能有心为WeCross贡献代码!
4 |
5 | ## 分支策略
6 |
7 | 项目采用[git-flow](https://jeffkreeftmeijer.com/git-flow/)的分支策略。
8 |
9 | * master:最新的稳定分支
10 | * dev:待发布的稳定分支
11 | * feature-xxxx:一个正在开发xxxx特性分支
12 | * bugfix-xxxx:一个正在修bug xxxx的分支
13 |
14 | ## 贡献方法
15 |
16 | ### Issue
17 |
18 | 可直接去[issues page](https://github.com/WeBankBlockchain/WeCross-WebApp/issues)提issue。
19 |
20 | ### 修复bug
21 |
22 | 1. Fork本仓库到个人仓库
23 | 2. 从个人仓库的master分支拉出一个bugfix-xxxx分支
24 | 3. 在bugfix-xxxx上修复bug
25 | 4. 测试修复的bug
26 | 5. PR(Pull Request)到本仓库的dev分支
27 | 6. 等待社区review这个PR
28 | 7. PR合入,bug修复完成!
29 |
30 | ### 开发新特性
31 |
32 | 1. Fork本仓库到个人仓库
33 | 2. 从个人仓库的dev分支拉出一个feature-xxxx分支
34 | 3. 在feature-xxxx上进行特性开发
35 | 4. 不定期的从本仓库的dev分支pull最新的改动到feature-xxxx分支
36 | 5. 测试新特性
37 | 6. PR(Pull Request)到本参考的dev分支
38 | 7. 等待社区review这个PR
39 | 8. PR合入,特性开发完成!
40 |
41 | ## 代码格式化
42 |
43 | 代码格式化gradle插件[google-java-format-gradle-plugin](https://github.com/sherter/google-java-format-gradle-plugin).
44 |
45 | 执行任务 `googleJavaFormat`格式化java文件。
46 | ```
47 | ./gradlew goJF
48 | ```
49 | 执行任务 `verifyGoogleJavaFormat`验证java文件是否格式化完成
50 | ```
51 | ./gradlew verGJF
52 | ```
53 |
--------------------------------------------------------------------------------
/tests/unit/utils/formatTime.spec.js:
--------------------------------------------------------------------------------
1 | import { formatTime } from '@/utils/index.js'
2 | describe('Utils:formatTime', () => {
3 | const d = new Date('2018-07-13 17:54:01') // "2018-07-13 17:54:01"
4 | const retrofit = 5 * 1000
5 |
6 | it('ten digits timestamp', () => {
7 | expect(formatTime((d / 1000).toFixed(0))).toBe('7月13日17时54分')
8 | })
9 | it('test now', () => {
10 | expect(formatTime(+new Date() - 1)).toBe('刚刚')
11 | })
12 | it('less two minute', () => {
13 | expect(formatTime(+new Date() - 60 * 2 * 1000 + retrofit)).toBe('2分钟前')
14 | })
15 | it('less two hour', () => {
16 | expect(formatTime(+new Date() - 60 * 60 * 2 * 1000 + retrofit)).toBe('2小时前')
17 | })
18 | it('less one day', () => {
19 | expect(formatTime(+new Date() - 60 * 60 * 24 * 1 * 1000)).toBe('1天前')
20 | })
21 | it('more than one day', () => {
22 | expect(formatTime(d)).toBe('7月13日17时54分')
23 | })
24 | it('format', () => {
25 | expect(formatTime(d, '{y}-{m}-{d} {h}:{i}')).toBe('2018-07-13 17:54')
26 | expect(formatTime(d, '{y}-{m}-{d}')).toBe('2018-07-13')
27 | expect(formatTime(d, '{y}/{m}/{d} {h}-{i}')).toBe('2018/07/13 17-54')
28 | })
29 | })
30 |
--------------------------------------------------------------------------------
/Changelog.md:
--------------------------------------------------------------------------------
1 | ### v1.4.0
2 |
3 | (2024-03-01)
4 |
5 | **新增**
6 |
7 | - 适配 `systemStatus` 的GET接口,获取系统信息在首页展示 https://github.com/WeBankBlockchain/WeCross-WebApp/pull/180
8 | - 事务列表页面新增按链查询的功能,优化事务查询效率 https://github.com/WeBankBlockchain/WeCross-WebApp/pull/182
9 |
10 | ### v1.3.1
11 |
12 | (2023-07-31)
13 |
14 | **新增**
15 |
16 | * 新增对FISCO BCOS 3.x版本WASM合约的部署调用
17 |
18 | ### v1.3.0
19 |
20 | (2023-03-15)
21 |
22 | **新增**
23 |
24 | * 新增对FISCO BCOS 3.x版本的支持
25 | * 新增Email登陆注册检查的功能
26 |
27 | ### v1.2.1
28 |
29 | (2021-12-15)
30 |
31 | (无改动,配合其他组件同步更新版本)
32 |
33 | ### v1.2.0
34 |
35 | (2021-08-20)
36 |
37 | **新增**
38 |
39 | * 资源访问控制功能,管理员可通过网页管理台给用户授权可访问的资源
40 |
41 | ### v1.1.1
42 |
43 | (2021-04-02)
44 |
45 | **更改**
46 |
47 | * 增加内容复制按钮
48 | * 用户操作优化
49 | * 展示效果优化
50 |
51 | ### v1.1.0
52 |
53 | (2020-02-02)
54 |
55 | **新增**
56 |
57 | * 添加修改密码功能
58 | * 添加页面帮助指引
59 |
60 | **更新**
61 |
62 | * 体验优化
63 | * 问题修复
64 |
65 | ### v1.0.0
66 |
67 | (2020-12-17)
68 |
69 | **功能**
70 |
71 | * WeCross管理台:提供可视化的跨链管理服务
72 |
73 | **新增**
74 |
75 | * 注册与登录页:注册跨链账户,登录管理平台
76 | * 平台首页:展示跨链网络信息以及系统配置信息
77 | * 账户管理页:跨链账户生命周期管理
78 | * 路由管理页:互联的跨链路由管理
79 | * 资源管理页:跨链资源展示以及资源调用
80 | * 交易管理页:跨链交易列表以及交易详情展示
81 | * 事务管理页:事务列表展示以及事务操作
82 |
--------------------------------------------------------------------------------
/src/utils/auth.js:
--------------------------------------------------------------------------------
1 |
2 | const TokenKey = 'wecross-token'
3 | const UsernameKey = 'wecross-user'
4 | const PubKey = 'wecross-pub'
5 |
6 | export function getToken() {
7 | return localStorage.getItem(TokenKey)
8 | }
9 |
10 | export function setToken(token) {
11 | return localStorage.setItem(TokenKey, token)
12 | }
13 |
14 | export function removeToken() {
15 | return localStorage.removeItem(TokenKey)
16 | }
17 |
18 | export function getUsername() {
19 | return localStorage.getItem(UsernameKey)
20 | }
21 |
22 | export function setUsername(username) {
23 | return localStorage.setItem(UsernameKey, username)
24 | }
25 |
26 | export function removeUsername() {
27 | return localStorage.removeItem(UsernameKey)
28 | }
29 |
30 | export function isUserFirstTimeUse(username) {
31 | // not first time
32 | if (localStorage.getItem(username)) {
33 | return false
34 | } else {
35 | localStorage.setItem(username, '1')
36 | return true
37 | }
38 | }
39 |
40 | export function getPubKey(pub) {
41 | return localStorage.getItem(PubKey)
42 | }
43 |
44 | export function setPubKey(pub) {
45 | return localStorage.setItem(PubKey, pub)
46 | }
47 |
48 | export function removePubKey() {
49 | return localStorage.removeItem(PubKey)
50 | }
51 |
--------------------------------------------------------------------------------
/src/icons/svg/qrcode.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/user-avatar.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/eye-open.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/unit/utils/parseTime.spec.js:
--------------------------------------------------------------------------------
1 | import { parseTime } from '@/utils/index.js'
2 |
3 | describe('Utils:parseTime', () => {
4 | const d = new Date('2018-07-13 17:54:01') // "2018-07-13 17:54:01"
5 | it('timestamp', () => {
6 | expect(parseTime(d)).toBe('2018-07-13 17:54:01')
7 | })
8 |
9 | it('timestamp string', () => {
10 | expect(parseTime((d + ''))).toBe('2018-07-13 17:54:01')
11 | })
12 |
13 | it('ten digits timestamp', () => {
14 | expect(parseTime((d / 1000).toFixed(0))).toBe('2018-07-13 17:54:01')
15 | })
16 | it('new Date', () => {
17 | expect(parseTime(new Date(d))).toBe('2018-07-13 17:54:01')
18 | })
19 | it('format', () => {
20 | expect(parseTime(d, '{y}-{m}-{d} {h}:{i}')).toBe('2018-07-13 17:54')
21 | expect(parseTime(d, '{y}-{m}-{d}')).toBe('2018-07-13')
22 | expect(parseTime(d, '{y}/{m}/{d} {h}-{i}')).toBe('2018/07/13 17-54')
23 | })
24 | it('get the day of the week', () => {
25 | expect(parseTime(d, '{a}')).toBe('五') // 星期五
26 | })
27 | it('get the day of the week', () => {
28 | expect(parseTime(+d + 1000 * 60 * 60 * 24 * 2, '{a}')).toBe('日') // 星期日
29 | })
30 | it('empty argument', () => {
31 | expect(parseTime()).toBeNull()
32 | })
33 |
34 | it('null', () => {
35 | expect(parseTime(null)).toBeNull()
36 | })
37 | })
38 |
--------------------------------------------------------------------------------
/src/views/transaction/transactionSteps/xaTransactionStep.js:
--------------------------------------------------------------------------------
1 | export const xaTransactionManagerSteps = [
2 | {
3 | element: '#ChainExplorer',
4 | title: '1. 导航选择',
5 | intro: '第一步:从zone-chain导航中选择对应的链',
6 | position: 'right'
7 | },
8 | {
9 | element: '#xaTransactionListExplorer',
10 | title: '2. 事务列表展示',
11 | intro: '第二步:选择好对应的链之后,就会在资源列表中展示这条链中所有事务',
12 | position: 'left'
13 | },
14 | {
15 | element: '#sendxaTransaction',
16 | title: '发起交易',
17 | intro: '若需要发起事务,请点击"发起事务"按钮',
18 | position: 'left'
19 | }
20 | ]
21 |
22 | export const startXASteps = [
23 | {
24 | element: '#XAID',
25 | title: '开启事务',
26 | intro: '第一步:生成事务ID!
只支持输入16进制',
27 | position: 'bottom'
28 | },
29 | {
30 | element: '#XAPath',
31 | title: '开启事务',
32 | intro: '第二步:选择事务资源!
从左边待选资源列表勾选资源,点击添加按钮到已选资源列表',
33 | position: 'top'
34 | },
35 | {
36 | element: '#btnGroup',
37 | title: '开启事务',
38 | intro: '第三步:点击开启事务!',
39 | position: 'top'
40 | }
41 | ]
42 |
43 | export const execXASteps = [
44 | {
45 | element: '#xaForm',
46 | title: '执行事务',
47 | intro: '执行事务交易!
填写资源、方法和参数,执行调用',
48 | position: 'top'
49 | },
50 | {
51 | element: '#xaList',
52 | title: '执行事务',
53 | intro: '查看事务步骤!
查看事务详细步骤',
54 | position: 'top'
55 | }
56 | ]
57 |
--------------------------------------------------------------------------------
/src/layout/components/AppMain.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
37 |
38 |
50 |
51 |
59 |
--------------------------------------------------------------------------------
/src/components/Hamburger/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
16 |
32 |
33 |
45 |
--------------------------------------------------------------------------------
/src/assets/icon-sw.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/plop-templates/view/prompt.js:
--------------------------------------------------------------------------------
1 | const { notEmpty } = require('../utils.js')
2 |
3 | module.exports = {
4 | description: 'generate a view',
5 | prompts: [{
6 | type: 'input',
7 | name: 'name',
8 | message: 'view name please',
9 | validate: notEmpty('name')
10 | },
11 | {
12 | type: 'checkbox',
13 | name: 'blocks',
14 | message: 'Blocks:',
15 | choices: [{
16 | name: '',
17 | value: 'template',
18 | checked: true
19 | },
20 | {
21 | name: '
46 |
47 |
62 |
--------------------------------------------------------------------------------
/mock/status.js:
--------------------------------------------------------------------------------
1 | module.exports = [{
2 | url: '/sys/systemStatus',
3 | type: 'get',
4 | response: param => {
5 | return {
6 | 'version': '1',
7 | 'errorCode': 0,
8 | 'message': 'Success',
9 | 'data': {
10 | 'osName': 'Linux',
11 | 'osArch': 'amd64',
12 | 'osVersion': '4.4.0-17763-Microsoft',
13 | 'javaVMVersion': '11.0.9.1+1-Ubuntu-0ubuntu1.20.04',
14 | 'javaVMVendor': 'Ubuntu',
15 | 'javaVMName': 'OpenJDK 64-Bit Server VM',
16 | 'providerName': 'SUN',
17 | 'providerVersion': '11',
18 | 'providerInfo': 'SUN (DSA key/parameter generation; DSA signing; SHA-1, MD5 digests; SecureRandom; X.509 certificates; PKCS12, JKS & DKS keystores; PKIX CertPathValidator; PKIX CertPathBuilder; LDAP, Collection CertStores, JavaPolicy Policy; JavaLoginConfig Configuration)',
19 | 'namedGroups': 'secp256k1',
20 | 'disabledNamedGroups': null
21 | }
22 | }
23 | }
24 | },
25 | {
26 | url: '/sys/routerStatus',
27 | type: 'get',
28 | response: param => {
29 | return {
30 | 'version': '1',
31 | 'errorCode': 0,
32 | 'message': 'Success',
33 | 'data': {
34 | 'version': '1.0.0',
35 | 'supportedStubs': 'BCOS2.0,GM_BCOS2.0',
36 | 'rpcNetInfo': '127.0.0.1:8250',
37 | 'p2pNetInfo': '0.0.0.0:25500',
38 | 'adminAccount': 'monan',
39 | 'enableAccessControl': true
40 | }
41 | }
42 | }
43 | }
44 | ]
45 |
--------------------------------------------------------------------------------
/src/layout/components/Sidebar/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
52 |
--------------------------------------------------------------------------------
/src/store/modules/app.js:
--------------------------------------------------------------------------------
1 | import Cookies from 'js-cookie'
2 |
3 | const state = {
4 | sidebar: {
5 | opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true,
6 | withoutAnimation: false
7 | },
8 | device: 'desktop'
9 | }
10 |
11 | const mutations = {
12 | TOGGLE_SIDEBAR: state => {
13 | state.sidebar.opened = !state.sidebar.opened
14 | state.sidebar.withoutAnimation = false
15 | if (state.sidebar.opened) {
16 | Cookies.set('sidebarStatus', 1)
17 | } else {
18 | Cookies.set('sidebarStatus', 0)
19 | }
20 | },
21 | CLOSE_SIDEBAR: (state, withoutAnimation) => {
22 | Cookies.set('sidebarStatus', 0)
23 | state.sidebar.opened = false
24 | state.sidebar.withoutAnimation = withoutAnimation
25 | },
26 | OPEN_SIDEBAR: (state, withoutAnimation) => {
27 | Cookies.set('sidebarStatus', 1)
28 | state.sidebar.opened = true
29 | state.sidebar.withoutAnimation = withoutAnimation
30 | },
31 | TOGGLE_DEVICE: (state, device) => {
32 | state.device = device
33 | }
34 | }
35 |
36 | const actions = {
37 | toggleSideBar({ commit }) {
38 | commit('TOGGLE_SIDEBAR')
39 | },
40 | closeSideBar({ commit }, { withoutAnimation }) {
41 | commit('CLOSE_SIDEBAR', withoutAnimation)
42 | },
43 | openSideBar({ commit }, { withoutAnimation }) {
44 | commit('OPEN_SIDEBAR', withoutAnimation)
45 | },
46 | toggleDevice({ commit }, device) {
47 | commit('TOGGLE_DEVICE', device)
48 | }
49 | }
50 |
51 | export default {
52 | namespaced: true,
53 | state,
54 | mutations,
55 | actions
56 | }
57 |
--------------------------------------------------------------------------------
/mock/resource.js:
--------------------------------------------------------------------------------
1 | const Mock = require('mockjs')
2 |
3 | module.exports = [{
4 | url: '/sys/listResources',
5 | type: 'post',
6 | response: config => {
7 | return {
8 | ...Mock.mock({
9 | version: 0,
10 | errorCode: 0,
11 | message: 'success',
12 | data: {
13 | total: 1000,
14 | 'resourceDetails|10': [{
15 | id: '@id',
16 | path: config.query.path ? config.query.path + '.@word(3,5)' : '@pick([\'payment\',\'load\',\'resource\']).@pick([\'bcos\',\'bcos_gm\',\'fabric\']).@word(3,5)',
17 | checksum: 'checksum',
18 | 'stubType|1': ['BCOS2.0', 'GM_BCOS2.0', 'Fabric1.4', 'BCOS3_ECDSA_EVM', 'BCOS3_GM_EVM'],
19 | properties: '@sentence(3,3)',
20 | distance: '@integer(0, 3)'
21 | }]
22 | }
23 | })
24 | }
25 | }
26 | },
27 | {
28 | url: '/customCommand',
29 | type: 'post',
30 | response: config => {
31 | return {
32 | ...Mock.mock({
33 | 'version': 1,
34 | 'errorCode': 0,
35 | 'message': 'Success: ' + config.body.command,
36 | 'data': 'result'
37 | })
38 | }
39 | }
40 | },
41 | {
42 | url: '/detail',
43 | type: 'post',
44 | response: config => {
45 | return {
46 | ...Mock.mock({
47 | 'version': 1,
48 | 'errorCode': 0,
49 | 'message': 'Success: ' + config.body.command,
50 | 'data': {
51 | 'stubType': 'BCOS2.0'
52 | }
53 | })
54 | }
55 | }
56 | }
57 | ]
58 |
--------------------------------------------------------------------------------
/src/utils/authcode.js:
--------------------------------------------------------------------------------
1 | import { authCode } from '@/api/user'
2 | import { authPub } from '@/api/user'
3 | import { setPubKey } from '@/utils/auth'
4 | import { Message } from 'element-ui'
5 |
6 | /**
7 | * query server encrypt publicKey
8 | */
9 | export function queryPub(callback) {
10 | authPub().then((resp) => {
11 | if (!resp) {
12 | Message({
13 | type: 'error',
14 | message: 'response is null, check server status.'
15 | })
16 | if (
17 | typeof resp.errorCode !== 'undefined' &&
18 | resp.errorCode !== null &&
19 | resp.errorCode !== 0
20 | ) {
21 | Message({
22 | type: 'error',
23 | message: JSON.stringify(resp)
24 | })
25 | }
26 | } else {
27 | const pub = resp.data.pub
28 | setPubKey(pub)
29 | if (typeof callback !== 'undefined' && callback !== null) {
30 | callback()
31 | }
32 | }
33 | })
34 | }
35 |
36 | /**
37 | * query auth code
38 | */
39 | export function queryAuthCode(callback) {
40 | authCode().then((resp) => {
41 | if (!resp) {
42 | Message({
43 | type: 'error',
44 | message: 'response is null, check server status.'
45 | })
46 | if (
47 | typeof resp.errorCode !== 'undefined' &&
48 | resp.errorCode !== null &&
49 | resp.errorCode !== 0
50 | ) {
51 | Message({
52 | type: 'error',
53 | message: JSON.stringify(resp)
54 | })
55 | }
56 | } else {
57 | callback(resp.data.authCode)
58 | }
59 | })
60 | }
61 |
--------------------------------------------------------------------------------
/plop-templates/store/prompt.js:
--------------------------------------------------------------------------------
1 | const { notEmpty } = require('../utils.js')
2 |
3 | module.exports = {
4 | description: 'generate store',
5 | prompts: [{
6 | type: 'input',
7 | name: 'name',
8 | message: 'store name please',
9 | validate: notEmpty('name')
10 | },
11 | {
12 | type: 'checkbox',
13 | name: 'blocks',
14 | message: 'Blocks:',
15 | choices: [{
16 | name: 'state',
17 | value: 'state',
18 | checked: true
19 | },
20 | {
21 | name: 'mutations',
22 | value: 'mutations',
23 | checked: true
24 | },
25 | {
26 | name: 'actions',
27 | value: 'actions',
28 | checked: true
29 | }
30 | ],
31 | validate(value) {
32 | if (!value.includes('state') || !value.includes('mutations')) {
33 | return 'store require at least state and mutations'
34 | }
35 | return true
36 | }
37 | }
38 | ],
39 | actions(data) {
40 | const name = '{{name}}'
41 | const { blocks } = data
42 | const options = ['state', 'mutations']
43 | const joinFlag = `,
44 | `
45 | if (blocks.length === 3) {
46 | options.push('actions')
47 | }
48 |
49 | const actions = [{
50 | type: 'add',
51 | path: `src/store/modules/${name}.js`,
52 | templateFile: 'plop-templates/store/index.hbs',
53 | data: {
54 | options: options.join(joinFlag),
55 | state: blocks.includes('state'),
56 | mutations: blocks.includes('mutations'),
57 | actions: blocks.includes('actions')
58 | }
59 | }]
60 | return actions
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/styles/mixin.scss:
--------------------------------------------------------------------------------
1 | @mixin clearfix {
2 | &:after {
3 | content: "";
4 | display: table;
5 | clear: both;
6 | }
7 | }
8 |
9 | @mixin scrollBar {
10 | &::-webkit-scrollbar-track-piece {
11 | background: #d3dce6;
12 | }
13 |
14 | &::-webkit-scrollbar {
15 | width: 6px;
16 | }
17 |
18 | &::-webkit-scrollbar-thumb {
19 | background: #99a9bf;
20 | border-radius: 20px;
21 | }
22 | }
23 |
24 | @mixin relative {
25 | position: relative;
26 | width: 100%;
27 | height: 100%;
28 | }
29 |
30 | @mixin pct($pct) {
31 | width: #{$pct};
32 | position: relative;
33 | margin: 0 auto;
34 | }
35 |
36 | @mixin triangle($width, $height, $color, $direction) {
37 | $width: $width/2;
38 | $color-border-style: $height solid $color;
39 | $transparent-border-style: $width solid transparent;
40 | height: 0;
41 | width: 0;
42 |
43 | @if $direction==up {
44 | border-bottom: $color-border-style;
45 | border-left: $transparent-border-style;
46 | border-right: $transparent-border-style;
47 | }
48 |
49 | @else if $direction==right {
50 | border-left: $color-border-style;
51 | border-top: $transparent-border-style;
52 | border-bottom: $transparent-border-style;
53 | }
54 |
55 | @else if $direction==down {
56 | border-top: $color-border-style;
57 | border-left: $transparent-border-style;
58 | border-right: $transparent-border-style;
59 | }
60 |
61 | @else if $direction==left {
62 | border-right: $color-border-style;
63 | border-top: $transparent-border-style;
64 | border-bottom: $transparent-border-style;
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/layout/mixin/ResizeHandler.js:
--------------------------------------------------------------------------------
1 | import store from '@/store'
2 |
3 | const { body } = document
4 | const WIDTH = 992 // refer to Bootstrap's responsive design
5 |
6 | export default {
7 | watch: {
8 | $route() {
9 | if (this.device === 'mobile' && this.sidebar.opened) {
10 | store.dispatch('app/closeSideBar', { withoutAnimation: false }).then(_ => {})
11 | }
12 | }
13 | },
14 | beforeMount() {
15 | window.addEventListener('resize', this.$_resizeHandler)
16 | },
17 | beforeDestroy() {
18 | window.removeEventListener('resize', this.$_resizeHandler)
19 | },
20 | mounted() {
21 | const isMobile = this.$_isMobile()
22 | if (isMobile) {
23 | store.dispatch('app/toggleDevice', 'mobile').then(_ => {})
24 | store.dispatch('app/closeSideBar', { withoutAnimation: true }).then(_ => {})
25 | }
26 | },
27 | methods: {
28 | // use $_ for mixins properties
29 | // https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential
30 | $_isMobile() {
31 | const rect = body.getBoundingClientRect()
32 | return rect.width - 1 < WIDTH
33 | },
34 | $_resizeHandler() {
35 | if (!document.hidden) {
36 | const isMobile = this.$_isMobile()
37 | store.dispatch('app/toggleDevice', isMobile ? 'mobile' : 'desktop').then(_ => {})
38 |
39 | if (isMobile) {
40 | store.dispatch('app/closeSideBar', { withoutAnimation: true }).then(_ => {})
41 | } else {
42 | store.dispatch('app/openSideBar', { withoutAnimation: false }).then(_ => {})
43 | }
44 | }
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/layout/components/Sidebar/Logo.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
23 |
24 |
69 |
--------------------------------------------------------------------------------
/src/utils/transaction.js:
--------------------------------------------------------------------------------
1 | const xaTransactionKey = 'xaTX'
2 |
3 | /**
4 | * @typedef {Object} xaTransaction
5 | * @property {string} transactionID
6 | * @property {Array} paths
7 | */
8 |
9 | /**
10 | * get XA transaction in sessionStorage
11 | * @return {xaTransaction|null}
12 | */
13 | export function getXATX() {
14 | return JSON.parse(sessionStorage.getItem(xaTransactionKey))
15 | }
16 |
17 | export function setXATX(xaTX) {
18 | return sessionStorage.setItem(xaTransactionKey, xaTX)
19 | }
20 |
21 | export function removeXATX() {
22 | return sessionStorage.removeItem(xaTransactionKey)
23 | }
24 |
25 | export function buildXAResponseError(response) {
26 | if (typeof response.data !== 'undefined' && response.data !== null &&
27 | typeof response.data.xaResponse.chainErrorMessages !== 'undefined' &&
28 | response.data.xaResponse.chainErrorMessages !== []) {
29 | let str = ''
30 | for (const chainErrorMessage of response.data.xaResponse.chainErrorMessages) {
31 | str += chainErrorMessage.path + ': ' + chainErrorMessage.message + '\n\n'
32 | }
33 | return str
34 | } else {
35 | return response.message
36 | }
37 | }
38 |
39 | export function buildXAError(response) {
40 | if (typeof response.data !== 'undefined' && response.data !== null &&
41 | typeof response.data.chainErrorMessages !== 'undefined' &&
42 | response.data.chainErrorMessages !== []) {
43 | let str = ''
44 | for (const chainErrorMessage of response.data.chainErrorMessages) {
45 | str += chainErrorMessage.path + ': ' + chainErrorMessage.message + '\n\n'
46 | }
47 | return str
48 | } else {
49 | return response.message
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/tests/unit/utils/validate.spec.js:
--------------------------------------------------------------------------------
1 | import { validUsername, validURL, validLowerCase, validUpperCase, validAlphabets, isString } from '@/utils/validate.js'
2 | import { path2Url } from '@/utils'
3 | describe('Utils:validate', () => {
4 | it('validUsername', () => {
5 | expect(validUsername('admin1')).toBe(true)
6 | expect(validUsername('org-user1')).toBe(true)
7 | expect(validUsername('xx')).toBe(false)
8 | })
9 | it('validURL', () => {
10 | expect(validURL('https://github.com/WeBankBlockchain/WeCross-WebApp.git')).toBe(true)
11 | expect(validURL('http://github.com/WeBankBlockchain/WeCross-WebApp.git')).toBe(true)
12 | expect(validURL('github.com/WeBankBlockchain/WeCross-WebApp.git')).toBe(false)
13 | })
14 | it('validLowerCase', () => {
15 | expect(validLowerCase('abc')).toBe(true)
16 | expect(validLowerCase('Abc')).toBe(false)
17 | expect(validLowerCase('123abc')).toBe(false)
18 | })
19 | it('validUpperCase', () => {
20 | expect(validUpperCase('ABC')).toBe(true)
21 | expect(validUpperCase('Abc')).toBe(false)
22 | expect(validUpperCase('123ABC')).toBe(false)
23 | })
24 | it('validAlphabets', () => {
25 | expect(validAlphabets('ABC')).toBe(true)
26 | expect(validAlphabets('Abc')).toBe(true)
27 | expect(validAlphabets('123aBC')).toBe(false)
28 | })
29 | it('isString', () => {
30 | expect(isString('')).toBe(true)
31 | const obj = {
32 | str: 'str'
33 | }
34 | expect(isString(obj.str)).toBe(true)
35 | expect(isString(obj)).toBe(false)
36 | expect(isString(123)).toBe(false)
37 | })
38 | it('path2URL', () => {
39 | expect(path2Url('test.test.test')).toBe('/test/test/test')
40 | })
41 | })
42 |
--------------------------------------------------------------------------------
/src/icons/svg/exit-fullscreen.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # WeCross-WebApp
4 |
5 | [](https://github.com/WeBankBlockchain/WeCross-WebApp/releases/latest) [](https://www.apache.org/licenses/LICENSE-2.0) [](https://vuejs.org/index.html)
6 |
7 | WeCross WebApp是[WeCross](https://github.com/WeBankBlockchain/WeCross)可视化跨链管理平台。
8 |
9 | ## 关键特性
10 |
11 | - 跨链路由、跨链账户、跨链资源、跨链交易以及跨链事务的可视化管理
12 | - 跨链网络信息以及跨链路由系统信息的获取和展示
13 |
14 | ## 部署与使用
15 |
16 | WeCross跨链管理平台的部署和使用可参考[使用文档](https://wecross.readthedocs.io/zh_CN/latest/docs/manual/webApp.html)。
17 |
18 | ## 源码编译
19 |
20 | **环境要求**:
21 |
22 | - [node.js](https://nodejs.org/en/) 8.10及以上
23 |
24 | **编译命令**:
25 |
26 | ```shell
27 | # 初始化
28 | npm install
29 |
30 | # 编译及热重载
31 | npm run dev
32 |
33 | # 编译并最小化生产
34 | npm run build:prod
35 |
36 | # 使用lint修复代码
37 | npm run lint
38 | ```
39 |
40 | ## 项目贡献
41 |
42 | 欢迎参与WeCross社区的维护和建设:
43 |
44 | - 提交代码(Pull requests),可参考[代码贡献流程](CONTRIBUTING.md)以及[wiki指南](https://github.com/WeBankBlockchain/WeCross/wiki/%E8%B4%A1%E7%8C%AE%E4%BB%A3%E7%A0%81)
45 | - [提问和提交BUG](https://github.com/WeBankBlockchain/WeCross/issues/new)
46 |
47 | 您将成为贡献者,感谢各位贡献者的付出:
48 |
49 |
50 |
51 | ## 开源社区
52 |
53 | 参与meetup:[活动日历](https://github.com/WeBankBlockchain/WeCross/wiki#%E6%B4%BB%E5%8A%A8%E6%97%A5%E5%8E%86)
54 |
55 | 学习知识、讨论方案、开发新特性:[联系微信小助手,加入跨链兴趣小组(CC-SIG)](https://wecross.readthedocs.io/zh_CN/latest/docs/community/cc-sig.html#id3)
56 |
57 | ## License
58 |
59 | WeCross WebApp的开源协议为Apache License 2.0,详情参考[LICENSE](./LICENSE)。
60 |
--------------------------------------------------------------------------------
/src/store/modules/permission.js:
--------------------------------------------------------------------------------
1 | import { asyncRoutes, constantRoutes } from '@/router'
2 |
3 | /**
4 | * Use meta.role to determine if the current user has permission
5 | * @param roles
6 | * @param route
7 | */
8 | function hasPermission(roles, route) {
9 | if (route.meta && route.meta.roles) {
10 | return roles.some(role => route.meta.roles.includes(role))
11 | } else {
12 | return true
13 | }
14 | }
15 |
16 | /**
17 | * Filter asynchronous routing tables by recursion
18 | * @param routes asyncRoutes
19 | * @param roles
20 | */
21 | export function filterAsyncRoutes(routes, roles) {
22 | const res = []
23 |
24 | routes.forEach(route => {
25 | const tmp = { ...route }
26 | if (hasPermission(roles, tmp)) {
27 | if (tmp.children) {
28 | tmp.children = filterAsyncRoutes(tmp.children, roles)
29 | }
30 | res.push(tmp)
31 | }
32 | })
33 |
34 | return res
35 | }
36 |
37 | const state = {
38 | routes: [],
39 | addRoutes: []
40 | }
41 |
42 | const mutations = {
43 | SET_ROUTES: (state, routes) => {
44 | state.addRoutes = routes
45 | state.routes = constantRoutes.concat(routes)
46 | },
47 | RESET_ROUTES: (state) => {
48 | state.addRoutes = []
49 | state.routes = []
50 | }
51 | }
52 |
53 | const actions = {
54 | generateRoutes({ commit }, roles) {
55 | return new Promise(resolve => {
56 | let accessedRoutes
57 | if (roles.includes('admin')) {
58 | accessedRoutes = asyncRoutes || []
59 | } else {
60 | accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
61 | }
62 | commit('SET_ROUTES', accessedRoutes)
63 | resolve(accessedRoutes)
64 | })
65 | },
66 | resetRoutes({ commit }) {
67 | return new Promise(resolve => {
68 | commit('RESET_ROUTES')
69 | resolve()
70 | })
71 | }
72 | }
73 |
74 | export default {
75 | namespaced: true,
76 | state,
77 | mutations,
78 | actions
79 | }
80 |
--------------------------------------------------------------------------------
/src/styles/element-ui.scss:
--------------------------------------------------------------------------------
1 | // cover some element-ui styles
2 |
3 | .el-breadcrumb__inner,
4 | .el-breadcrumb__inner a {
5 | font-weight: 400 !important;
6 | }
7 |
8 | .el-upload {
9 | input[type="file"] {
10 | display: none !important;
11 | }
12 | }
13 |
14 | .el-upload__input {
15 | display: none;
16 | }
17 |
18 | .cell {
19 | .el-tag {
20 | margin-right: 0px;
21 | }
22 | }
23 |
24 | .small-padding {
25 | .cell {
26 | padding-left: 5px;
27 | padding-right: 5px;
28 | }
29 | }
30 |
31 | .fixed-width {
32 | .el-button--mini {
33 | padding: 7px 10px;
34 | min-width: 60px;
35 | }
36 | }
37 |
38 | .status-col {
39 | .cell {
40 | padding: 0 10px;
41 | text-align: center;
42 |
43 | .el-tag {
44 | margin-right: 0px;
45 | }
46 | }
47 | }
48 |
49 | // to fixed https://github.com/ElemeFE/element/issues/2461
50 | .el-dialog {
51 | transform: none;
52 | left: 0;
53 | position: relative;
54 | margin: 0 auto;
55 | }
56 |
57 | // refine element ui upload
58 | .upload-container {
59 | .el-upload {
60 | width: 100%;
61 |
62 | .el-upload-dragger {
63 | width: 100%;
64 | height: 200px;
65 | }
66 | }
67 | }
68 |
69 | // dropdown
70 | .el-dropdown-menu {
71 | a {
72 | display: block
73 | }
74 | }
75 |
76 | // fix date-picker ui bug in filter-item
77 | .el-range-editor.el-input__inner {
78 | display: inline-flex !important;
79 | }
80 |
81 | // to fix el-date-picker css style
82 | .el-range-separator {
83 | box-sizing: content-box;
84 | }
85 |
86 | .el-form-item__label {
87 | padding: 0 10px 0 0;
88 | }
89 |
90 | .el-dropdown-menu__item {
91 | line-height: 30px;
92 | }
93 |
94 | .el-form--label-top .el-form-item__label {
95 | float: none;
96 | display: inline;
97 | text-align: left;
98 | padding: 0 0 10px;
99 | }
100 |
101 | .el-table__body-wrapper {
102 | overflow-y: auto;
103 | }
104 |
--------------------------------------------------------------------------------
/src/components/Breadcrumb/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ item.meta.title }}
6 | {{ item.meta.title }}
7 |
8 |
9 |
10 |
11 |
12 |
64 |
65 |
78 |
--------------------------------------------------------------------------------
/src/api/conn.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 |
3 | /**
4 | * list peer routers
5 | * @param {Object|null} params - params to request: {offset,size}
6 | * @param {number} params.offset - the query page offset
7 | * @param {number} params.size - the page size of query
8 | * @return {Promise} an axios promise object of response
9 | */
10 | export function listPeers(params) {
11 | return request({
12 | url: '/conn/listPeers',
13 | method: 'get',
14 | params: params
15 | })
16 | }
17 |
18 | /**
19 | * add peer router
20 | * @param {Object} data - body data to request: {version, data:{address}}
21 | * @return {Promise} an axios promise object of response
22 | */
23 | export function addPeer(data) {
24 | return request({
25 | url: '/conn/addPeer',
26 | method: 'post',
27 | data: data
28 | })
29 | }
30 |
31 | /**
32 | * remove peer router
33 | * @param {Object} data - body data to request: {version, data:{address}}
34 | * @return {Promise} an axios promise object of response
35 | */
36 | export function removePeer(data) {
37 | return request({
38 | url: '/conn/removePeer',
39 | method: 'post',
40 | data: data
41 | })
42 | }
43 |
44 | /**
45 | * get a list of chains in network
46 | * @param {Object} params - params to request: {zone,offset,size}, if offset & size is null then return all list
47 | * @param {string} params.zone - the zone which chains on
48 | * @param {number|null} params.offset - the query page offset
49 | * @param {number|null} params.size - the page size of query
50 | * @return {Promise} an axios promise object of response
51 | */
52 | export function listChains(params) {
53 | return request({
54 | url: '/conn/listChains',
55 | method: 'get',
56 | params: params
57 | })
58 | }
59 |
60 | /**
61 | * get a list of zones in network
62 | * @param {Object|null} params - params to request: {offset,size}, if params is null then return all list
63 | * @param {number} params.offset - the query page offset
64 | * @param {number} params.size - the page size of query
65 | * @return {Promise} an axios promise object of response
66 | */
67 | export function listZones(params) {
68 | return request({
69 | url: '/conn/listZones',
70 | method: 'get',
71 | params: params
72 | })
73 | }
74 |
--------------------------------------------------------------------------------
/src/api/user.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 |
3 | /**
4 | * login
5 | * @param {string} data - encoded login params
6 | * @return {Promise} an axios promise object of response
7 | */
8 | export function login(data) {
9 | return request({
10 | url: '/auth/login',
11 | method: 'post',
12 | data: {
13 | version: '1',
14 | data: data
15 | }
16 | })
17 | }
18 |
19 | /**
20 | * logout
21 | * @return {Promise} an axios promise object of response
22 | */
23 | export function logout() {
24 | return request({
25 | url: '/auth/logout',
26 | method: 'post',
27 | data: {}
28 | })
29 | }
30 |
31 | /**
32 | * register
33 | * @param {string} data - encoded register params
34 | * @return {Promise} an axios promise object of response
35 | */
36 | export function register(data) {
37 | return request({
38 | url: '/auth/register',
39 | method: 'post',
40 | data: {
41 | version: '1',
42 | data: data
43 | }
44 | })
45 | }
46 |
47 | /**
48 | * changePassword
49 | * @param {string} data - encoded changePassword params
50 | * @return {Promise} an axios promise object of response
51 | */
52 | export function changePassword(data) {
53 | return request({
54 | url: '/auth/changePassword',
55 | method: 'post',
56 | data: {
57 | version: '1',
58 | data: data
59 | }
60 | })
61 | }
62 |
63 | /**
64 | * get a auth code for login/register
65 | * @return {Promise} an axios promise object of response
66 | */
67 | export function authCode() {
68 | return request({
69 | url: '/auth/authCode',
70 | method: 'get'
71 | })
72 | }
73 |
74 | /**
75 | * get a pubKey for login/register
76 | * @return {Promise} an axios promise object of response
77 | */
78 | export function authPub() {
79 | return request({
80 | url: '/auth/pub',
81 | method: 'get'
82 | })
83 | }
84 |
85 | export function isNeedMailAuth() {
86 | return request({
87 | url: '/auth/need-mail-auth',
88 | method: 'get'
89 | })
90 | }
91 |
92 | export function sendMailCode(username, email) {
93 | return request({
94 | url: '/auth/mail-code',
95 | method: 'post',
96 | data: {
97 | data: {
98 | username: username,
99 | email: email
100 | }
101 | }
102 | })
103 | }
104 |
--------------------------------------------------------------------------------
/src/assets/nav-logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/components/ResourceShower/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 | {{ scope.row.path }}
10 |
11 |
12 |
13 | {{ JSON.stringify(scope.row.properties) }}
14 |
15 |
16 |
17 |
26 |
27 |
28 |
29 |
91 |
92 |
94 |
--------------------------------------------------------------------------------
/src/views/document/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |

4 |
Welcome to WeCross
5 |
6 |
7 |
8 | WeCross是由微众银行自主研发并完全开源的区块链跨链协作平台,致力于促进跨行业、机构和地域的跨区块链信任传递和商业合作。
9 | WeCross不局限于满足同构区块链平行扩展后的可信数据交换需求,还进一步探索解决异构区块链之间因底层架构、
10 | 数据结构、接口协议、安全机制等多维异构性导致无法互联互通问题的有效方案。
11 |
12 |
13 |
14 |
17 |
WeCross GitHub
18 |
24 |
29 |
30 |
31 |
32 |
44 |
45 |
82 |
--------------------------------------------------------------------------------
/src/permission.js:
--------------------------------------------------------------------------------
1 | import router from './router'
2 | import store from './store'
3 | import { Message } from 'element-ui'
4 | import NProgress from 'nprogress' // progress bar
5 | import 'nprogress/nprogress.css' // progress bar style
6 | import { getToken } from '@/utils/auth' // get token from cookie
7 | import getPageTitle from '@/utils/get-page-title'
8 |
9 | NProgress.configure({ showSpinner: false }) // NProgress Configuration
10 |
11 | const whiteList = ['/login', '/register'] // no redirect whitelist
12 |
13 | router.beforeEach(async(to, from, next) => {
14 | // start progress bar
15 | NProgress.start()
16 |
17 | // set page title
18 | document.title = getPageTitle(to.meta.title)
19 |
20 | // determine whether the user has logged in
21 | const hasToken = getToken()
22 |
23 | if (hasToken) {
24 | if (to.path === '/login') {
25 | if (from.path === '/register') {
26 | await store.dispatch('user/resetToken')
27 | next({ path: '/login' })
28 | NProgress.done()
29 | } else {
30 | // if is logged in, redirect to the home page
31 | next({ path: '/' })
32 | NProgress.done()
33 | }
34 | } else {
35 | const hasRoles = store.getters.roles && store.getters.roles.length > 0
36 | if (hasRoles) {
37 | next()
38 | NProgress.done()
39 | } else {
40 | try {
41 | if (store.getters.permissionRoutes === null || store.getters.permissionRoutes.length === 0) {
42 | const roles = await store.dispatch('user/getRole')
43 | const accessRoutes = await store.dispatch('permission/generateRoutes', roles)
44 | router.addRoutes(accessRoutes)
45 | next({ ...to, replace: true })
46 | NProgress.done()
47 | } else {
48 | next()
49 | NProgress.done()
50 | }
51 | } catch (error) {
52 | // remove token and go to login page to re-login
53 | await store.dispatch('user/resetToken')
54 | Message.error(error || 'Has Error')
55 | next(`/login?redirect=${to.path}`)
56 | NProgress.done()
57 | }
58 | }
59 | }
60 | } else {
61 | /* has no token*/
62 | if (whiteList.indexOf(to.path) !== -1) {
63 | // in the free login whitelist, go directly
64 | next()
65 | } else {
66 | // other pages that do not have permission to access are redirected to the login page.
67 | next(`/login?redirect=${to.path}`)
68 | NProgress.done()
69 | }
70 | }
71 | })
72 |
73 | router.afterEach(() => {
74 | // finish progress bar
75 | NProgress.done()
76 | })
77 |
--------------------------------------------------------------------------------
/mock/mock-server.js:
--------------------------------------------------------------------------------
1 | const chokidar = require('chokidar')
2 | const bodyParser = require('body-parser')
3 | const chalk = require('chalk')
4 | const path = require('path')
5 | const Mock = require('mockjs')
6 |
7 | const mockDir = path.join(process.cwd(), 'mock')
8 |
9 | function registerRoutes(app) {
10 | let mockLastIndex
11 | const { mocks } = require('./index.js')
12 | const mocksForServer = mocks.map(route => {
13 | return responseFake(route.url, route.type, route.response)
14 | })
15 | for (const mock of mocksForServer) {
16 | app[mock.type](mock.url, mock.response)
17 | mockLastIndex = app._router.stack.length
18 | }
19 | const mockRoutesLength = Object.keys(mocksForServer).length
20 | return {
21 | mockRoutesLength: mockRoutesLength,
22 | mockStartIndex: mockLastIndex - mockRoutesLength
23 | }
24 | }
25 |
26 | function unregisterRoutes() {
27 | Object.keys(require.cache).forEach(i => {
28 | if (i.includes(mockDir)) {
29 | delete require.cache[require.resolve(i)]
30 | }
31 | })
32 | }
33 |
34 | // for mock server
35 | const responseFake = (url, type, respond) => {
36 | return {
37 | url: new RegExp(`${process.env.VUE_APP_BASE_API}${url}`),
38 | type: type || 'get',
39 | response(req, res) {
40 | console.log('request invoke:' + req.path + ' query:' + JSON.stringify(req.query))
41 | res.json(Mock.mock(respond instanceof Function ? respond(req, res) : respond))
42 | }
43 | }
44 | }
45 |
46 | module.exports = app => {
47 | // parse app.body
48 | // https://expressjs.com/en/4x/api.html#req.body
49 | app.use(bodyParser.json())
50 | app.use(bodyParser.urlencoded({
51 | extended: true
52 | }))
53 |
54 | const mockRoutes = registerRoutes(app)
55 | var mockRoutesLength = mockRoutes.mockRoutesLength
56 | var mockStartIndex = mockRoutes.mockStartIndex
57 |
58 | // watch files, hot reload mock server
59 | chokidar.watch(mockDir, {
60 | ignored: /mock-server/,
61 | ignoreInitial: true
62 | }).on('all', (event, path) => {
63 | if (event === 'change' || event === 'add') {
64 | try {
65 | // remove mock routes stack
66 | app._router.stack.splice(mockStartIndex, mockRoutesLength)
67 |
68 | // clear routes cache
69 | unregisterRoutes()
70 |
71 | const mockRoutes = registerRoutes(app)
72 | mockRoutesLength = mockRoutes.mockRoutesLength
73 | mockStartIndex = mockRoutes.mockStartIndex
74 |
75 | console.log(chalk.magentaBright(`\n > Mock Server hot reload success! changed ${path}`))
76 | } catch (error) {
77 | console.log(chalk.redBright(error))
78 | }
79 | }
80 | })
81 | }
82 |
--------------------------------------------------------------------------------
/src/utils/validate.js:
--------------------------------------------------------------------------------
1 | import { sha256 } from 'js-sha256'
2 |
3 | /**
4 | * @param {string} path
5 | * @returns {Boolean}
6 | */
7 | export function isExternal(path) {
8 | return /^(https?:|mailto:|tel:)/.test(path)
9 | }
10 |
11 | /**
12 | * @param {string} str
13 | * @returns {Boolean}
14 | */
15 | export function validUsername(str) {
16 | return /^[a-zA-Z0-9_-]{3,18}$/.test(str)
17 | }
18 |
19 | /**
20 | * @param {string} str
21 | * @returns {Boolean}
22 | */
23 | export function validPassword(str) {
24 | // return /^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{6,18}$/.test(str)
25 | return /^(?=.*([a-zA-Z].*))(?=.*[0-9].*)[a-zA-Z0-9-*/+.~!@#$%^&*()_-]{6,18}$/.test(str)
26 | }
27 |
28 | /**
29 | *
30 | * @param {*} str
31 | */
32 | export function confusePassword(str) {
33 | var constantSalt = '1234567890~!#$%^&*()_+'
34 | var passwdWithSalt = constantSalt + str
35 | return sha256(passwdWithSalt)
36 | }
37 |
38 | /**
39 | * @param {string} url
40 | * @returns {Boolean}
41 | */
42 | export function validURL(url) {
43 | const reg = /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/
44 | return reg.test(url)
45 | }
46 |
47 | /**
48 | * @param {string} str
49 | * @returns {Boolean}
50 | */
51 | export function validLowerCase(str) {
52 | const reg = /^[a-z]+$/
53 | return reg.test(str)
54 | }
55 |
56 | /**
57 | * @param {string} str
58 | * @returns {Boolean}
59 | */
60 | export function validUpperCase(str) {
61 | const reg = /^[A-Z]+$/
62 | return reg.test(str)
63 | }
64 |
65 | /**
66 | * @param {string} str
67 | * @returns {Boolean}
68 | */
69 | export function validAlphabets(str) {
70 | const reg = /^[A-Za-z]+$/
71 | return reg.test(str)
72 | }
73 |
74 | /**
75 | * @param {string} str
76 | * @returns {Boolean}
77 | */
78 | export function isString(str) {
79 | return typeof str === 'string' || str instanceof String
80 | }
81 |
82 | /**
83 | * @param {Array} arg
84 | * @returns {Boolean}
85 | */
86 | export function isArray(arg) {
87 | if (typeof Array.isArray === 'undefined') {
88 | return Object.prototype.toString.call(arg) === '[object Array]'
89 | }
90 | return Array.isArray(arg)
91 | }
92 |
93 | /**
94 | * @param {string} path
95 | * @return {Boolean}
96 | */
97 | export function isValidPath(path) {
98 | const reg = /^[A-Za-z]+\.[A-Za-z]+\.[A-Za-z]+$/
99 | return reg.test(path)
100 | }
101 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "wecross-webapp",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "vue-cli-service serve",
7 | "build:prod": "vue-cli-service build",
8 | "build:stage": "vue-cli-service build --mode staging",
9 | "lint": "eslint --ext .js,.vue src",
10 | "new": "plop",
11 | "test:unit": "jest --clearCache && vue-cli-service test:unit",
12 | "test:ci": "npm run lint && npm run test:unit"
13 | },
14 | "dependencies": {
15 | "axios": "^0.21.3",
16 | "body-parser": "^1.19.0",
17 | "chalk": "^4.1.0",
18 | "chokidar": "^3.5.1",
19 | "clipboard": "^2.0.6",
20 | "connect": "^3.7.0",
21 | "core-js": "3.7.0",
22 | "echarts": "^4.9.0",
23 | "element-ui": "2.13.2",
24 | "elliptic": "^6.5.4",
25 | "fuse.js": "^6.4.6",
26 | "intro.js": "^3.2.1",
27 | "js-cookie": "2.2.0",
28 | "js-sha256": "^0.9.0",
29 | "js-sha3": "0.8.0",
30 | "jsencrypt": "^3.0.0-rc.1",
31 | "jsonlint": "1.6.3",
32 | "jszip": "^3.8.0",
33 | "normalize.css": "7.0.0",
34 | "nprogress": "0.2.0",
35 | "path-to-regexp": "2.4.0",
36 | "script-loader": "0.7.2",
37 | "sm-crypto": "0.2.1",
38 | "sortablejs": "1.8.4",
39 | "svg-sprite-loader": "^5.0.0",
40 | "uuid": "^8.3.1",
41 | "vue": "^2.6.11",
42 | "vue-count-to": "1.0.13",
43 | "vue-json-pretty": "^1.7.1",
44 | "vue-router": "3.0.2",
45 | "vue-splitpane": "1.0.4",
46 | "vue-vis-network": "1.0.2",
47 | "vuex": "3.1.0"
48 | },
49 | "devDependencies": {
50 | "@vue/cli-plugin-babel": "~4.5.0",
51 | "@vue/cli-plugin-eslint": "~4.5.0",
52 | "@vue/cli-plugin-unit-jest": "4.4.4",
53 | "@vue/cli-service": "~4.5.0",
54 | "@vue/test-utils": "1.0.0-beta.29",
55 | "babel-eslint": "^10.1.0",
56 | "babel-jest": "^26.6.3",
57 | "babel-plugin-dynamic-import-node": "2.3.3",
58 | "eslint": "^6.7.2",
59 | "eslint-plugin-vue": "^6.2.2",
60 | "eslint-plugin-vue-libs": "^4.0.0",
61 | "html-webpack-plugin": "3.2.0",
62 | "husky": "1.3.1",
63 | "lint-staged": "8.1.5",
64 | "mockjs": "1.0.1-beta3",
65 | "plop": "2.3.0",
66 | "runjs": "4.3.2",
67 | "sass": "1.26.2",
68 | "sass-loader": "8.0.2",
69 | "script-ext-html-webpack-plugin": "2.1.3",
70 | "serve-static": "1.13.2",
71 | "vue-template-compiler": "^2.6.11"
72 | },
73 | "engines": {
74 | "node": ">=8.10",
75 | "npm": ">= 6.4.1"
76 | },
77 | "husky": {
78 | "hooks": {
79 | "pre-commit": "lint-staged"
80 | }
81 | },
82 | "lint-staged": {
83 | "src/**/*.{js,vue}": [
84 | "eslint --fix",
85 | "git add"
86 | ]
87 | },
88 | "browserslist": [
89 | "> 1%",
90 | "last 2 versions",
91 | "not dead"
92 | ]
93 | }
94 |
--------------------------------------------------------------------------------
/src/utils/chainAccountIntro.js:
--------------------------------------------------------------------------------
1 | import introJS from 'intro.js'
2 | import 'intro.js/introjs.css'
3 | import 'intro.js/themes/introjs-modern.css'
4 | import { listAccount } from '@/api/ua'
5 | import { Message } from 'element-ui'
6 |
7 | /**
8 | * check if chain account fit the chainTypes
9 | * @param {Array|String} chainTypes - the type of chains to query
10 | * @param {Function} callBack - what to do on exiting intro
11 | * @return {IntroJs|null}
12 | */
13 | export function isChainAccountFit(chainTypes, callBack) {
14 | if (!chainTypes) {
15 | Message.error('Wrong chain types, please check.')
16 | return null
17 | }
18 | listAccount().then(res => {
19 | const chainAccountTypes = new Set()
20 | for (const chainAccount of res.data.chainAccounts) {
21 | chainAccountTypes.add(chainAccount.type)
22 | }
23 | if (chainTypes instanceof Array) {
24 | for (const chainType of chainTypes) {
25 | if (chainAccountTypes.size < 1 || !chainAccountTypes.has(chainType)) {
26 | return introJS().setOptions({
27 | prevLabel: '上一步',
28 | nextLabel: '下一步',
29 | doneLabel: '结束',
30 | disableInteraction: true,
31 | tooltipClass: 'customTooltip',
32 | steps: [
33 | {
34 | title: '警告⚠️',
35 | intro: '请检查是否正确配置链账户未配置链账户将会影响基本使用'
36 | },
37 | {
38 | element: '#Account',
39 | title: '账户管理',
40 | intro: '请在这里配置链账户信息
账户功能详情介绍请参考:账号服务',
41 | position: 'right'
42 | }
43 | ]
44 | }).start().onexit(callBack)
45 | }
46 | }
47 | callBack()
48 | } else if (typeof chainTypes === 'string') {
49 | if (!chainAccountTypes.has(chainTypes)) {
50 | console.log(chainAccountTypes, chainTypes)
51 | return introJS().setOptions({
52 | prevLabel: '上一步',
53 | nextLabel: '下一步',
54 | doneLabel: '结束',
55 | disableInteraction: true,
56 | tooltipClass: 'customTooltip',
57 | steps: [
58 | {
59 | title: '警告⚠️',
60 | intro: '请检查是否正确配置链账户未配置链账户将会影响基本使用'
61 | },
62 | {
63 | element: '#Account',
64 | title: '账户管理',
65 | intro: '请在这里配置链账户信息
账户功能详情介绍请参考:账号服务',
66 | position: 'right'
67 | }
68 | ]
69 | }).start().onexit(callBack)
70 | } else {
71 | callBack()
72 | }
73 | }
74 | })
75 | }
76 |
--------------------------------------------------------------------------------
/src/layout/components/Sidebar/SidebarItem.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
22 |
23 |
24 |
25 |
26 |
93 |
--------------------------------------------------------------------------------
/src/api/resource.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 | import { path2Url } from '@/utils'
3 |
4 | /**
5 | * get a list of resources
6 | * @param {Object|null} params - params to request
7 | * @param {string|null} params.path - the path which resources on
8 | * @param {number} params.offset - the query page offset
9 | * @param {number} params.size - the page size of query
10 | * @param {Object} data - Body data to request: {version, data:{ignoreRemote}}
11 | * @return {Promise} an axios promise object of response
12 | */
13 | export function getResourceList(params, data) {
14 | return request({
15 | url: '/sys/listResources',
16 | method: 'post',
17 | params: params,
18 | data: data
19 | })
20 | }
21 |
22 | /**
23 | * get a detail info of a resource
24 | * @param {String} path - request path
25 | * @return {Promise} an axios promise object of response
26 | */
27 | export function detail(path) {
28 | return request({
29 | url: 'resource' + path2Url(path) + '/detail',
30 | method: 'post',
31 | data: { version: 1, data: null }
32 | })
33 | }
34 |
35 | /**
36 | * deploy a BCOS contract
37 | * @param {CustomRequest|*} data - body data to request
38 | * @return {Promise} an axios promise object of response
39 | */
40 | export function bcosDeploy(data) {
41 | return request({
42 | url: 'resource' + path2Url(data.path) + '/customCommand',
43 | method: 'post',
44 | data: data
45 | })
46 | }
47 |
48 | /**
49 | * register a BCOS contract
50 | * @param {CustomRequest|*} data - body data to request
51 | * @return {Promise} an axios promise object of response
52 | */
53 | export function bcosRegister(data) {
54 | return request({
55 | url: 'resource' + path2Url(data.path) + '/customCommand',
56 | method: 'post',
57 | data: data
58 | })
59 | }
60 |
61 | /**
62 | * install a Fabric chaincode
63 | * @param {CustomRequest|*} data - body data to request
64 | * @return {Promise} an axios promise object of response
65 | */
66 | export function fabricInstall(data) {
67 | return request({
68 | url: 'resource' + path2Url(data.path) + '/customCommand',
69 | method: 'post',
70 | data: data
71 | })
72 | }
73 |
74 | /**
75 | * instantiate a Fabric chaincode
76 | * @param {CustomRequest|*} data - body data to request
77 | * @return {Promise} an axios promise object of response
78 | */
79 | export function fabricInstantiate(data) {
80 | return request({
81 | url: 'resource' + path2Url(data.path) + '/customCommand',
82 | method: 'post',
83 | data: data
84 | })
85 | }
86 |
87 | /**
88 | * upgrade a Fabric chaincode
89 | * @param {CustomRequest|*} data - body data to request
90 | * @return {Promise} an axios promise object of response
91 | */
92 | export function fabricUpgrade(data) {
93 | return request({
94 | url: 'resource' + path2Url(data.path) + '/customCommand',
95 | method: 'post',
96 | data: data
97 | })
98 | }
99 |
--------------------------------------------------------------------------------
/src/api/ua.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 |
3 | /**
4 | * list all chain account of UA
5 | * @return {Promise} an axios promise object of response
6 | */
7 | export function listAccount() {
8 | return request({
9 | url: '/auth/listAccount',
10 | method: 'post',
11 | data: {}
12 | })
13 | }
14 |
15 | /**
16 | * set default chain account by keyID
17 | * @param {Object} data
18 | * @param {string} data.version
19 | * @param {Object} data.data - main body data of request
20 | * @param {string} data.data.type - the chain type
21 | * @param {number} data.data.keyID
22 | * @return {Promise} an axios promise object of response
23 | */
24 | export function setDefaultAccount(data) {
25 | return request({
26 | url: '/auth/setDefaultAccount',
27 | method: 'post',
28 | data: data
29 | })
30 | }
31 |
32 | /**
33 | * add a chain account to UA
34 | * @param {Object} data
35 | * @param {string} data.version
36 | * @param {Object} data.data - main body data of request
37 | * @param {string} data.data.type - the chain type
38 | * @param {string} data.data.pubKey
39 | * @param {string} data.data.secKey
40 | * @param {string} data.data.ext
41 | * @param {boolean} data.data.isDefault
42 | * @return {Promise} an axios promise object of response
43 | */
44 | export function addChainAccount(data) {
45 | return request({
46 | url: '/auth/addChainAccount',
47 | method: 'post',
48 | data: data
49 | })
50 | }
51 |
52 | /**
53 | * remove a chain account by keyID
54 | * @param {Object} data
55 | * @param {string} data.version
56 | * @param {Object} data.data - main body data of request
57 | * @param {string} data.data.type - the chain type
58 | * @param {number} data.data.keyID
59 | * @return {Promise} an axios promise object of response
60 | */
61 | export function removeChainAccount(data) {
62 | return request({
63 | url: '/auth/removeChainAccount',
64 | method: 'post',
65 | data: data
66 | })
67 | }
68 |
69 | /**
70 | * get all access chain of username
71 | * @param {Object|null} params param include username, if null get all user
72 | * @param {String} params.username username
73 | * @return {Promise} an axios promise object of response
74 | */
75 | export function accessControlListGet(params) {
76 | return request({
77 | url: '/auth/admin/accessControlList',
78 | method: 'get',
79 | params: params
80 | })
81 | }
82 |
83 | /**
84 | * post access chain of username
85 | * @param {Object} params param include username, if null get all user
86 | * @param {String} params.username username
87 | * @param {Object} data
88 | * @param {String} data.version
89 | * @param {Object} data.data
90 | * @param {Array[String]} data.data.allowChainPaths
91 | * @return {Promise} an axios promise object of response
92 | */
93 | export function accessControlListPost(params, data) {
94 | return request({
95 | url: '/auth/admin/accessControlList',
96 | method: 'post',
97 | params: params,
98 | data: data
99 | })
100 | }
101 |
--------------------------------------------------------------------------------
/src/api/common.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @typedef {Object} ChainErrorMessage
3 | * @property {string} chain
4 | * @property {string} message
5 | *
6 | */
7 | /**
8 | * @typedef {Object} ResourceDetail
9 | * @property {string} path - the path of resource
10 | * @property {number} distance - the distance of this resource
11 | * @property {Object} properties
12 | * @property {string} checksum
13 | */
14 | /**
15 | * @typedef {Object} CallRequest
16 | * @property {string} version - the version of request
17 | * @property {string|*} path
18 | * @property {Object} data - the main body data of request
19 | * @property {string|*} data.method - call method
20 | * @property {Array|*} args - call args
21 | * @property {Object|*} options - sendTransaction options
22 | */
23 | /**
24 | * @typedef {Object} CustomRequest
25 | * @property {string} version - the version of request
26 | * @property {string} path
27 | * @property {Object} data - the main body data of request
28 | * @property {string} data.command - the command of request
29 | * @property {Array|null} data.args - the args of command
30 | */
31 | /**
32 | * @typedef {Object} xaRequest
33 | * @property {string} version - the version of request
34 | * @property {Object} data - the main body data of request
35 | * @property {string|null} data.xaTransactionID - the xa transaction ID
36 | * @property {Array|null} data.paths - the paths of xa transaction
37 | */
38 | /**
39 | * @typedef {Object} Response
40 | * @property {string} version - the version of response
41 | * @property {number} errorCode - the system layer error code, 0 means success
42 | * @property {string} message
43 | * @property {Object|string} data - the main body data of response
44 | */
45 | /**
46 | * @typedef {Object} resourceResponse
47 | * @property {string} version - the version of response
48 | * @property {number} errorCode - the system layer error code, 0 means success
49 | * @property {string} message
50 | * @property {Object|string} data - the main body data of response
51 | * @property {number} data.total - the total number of 'listResources'
52 | * @property {Array} data.resourceDetails - list of resources in 'listResources'
53 | */
54 | /**
55 | * @typedef {Object} xaResponse
56 | * @property {string} version - the version of response
57 | * @property {number} errorCode - the system layer error code, 0 means success
58 | * @property {string} message
59 | * @property {Object} data - the main body data of response
60 | * @property {number} data.status - the status of this XA transaction
61 | * @property {Array|null} chainErrorMessages
62 | */
63 | /**
64 | * @typedef {Object} xaListResponse
65 | * @property {string} version - the version of response
66 | * @property {number} errorCode - the system layer error code, 0 means success
67 | * @property {string} message
68 | * @property {Object} data - the main body data of response
69 | * @property {Object} data.xaResponse
70 | * @property {number} data.xaResponse.status
71 | * @property {Array|null} chainErrorMessages
72 | * @property {Object} xaTransaction
73 | */
74 |
--------------------------------------------------------------------------------
/src/utils/resource.js:
--------------------------------------------------------------------------------
1 | export function clearForm(formData) {
2 | formData.className = null
3 | formData.version = null
4 | formData.address = null
5 | formData.org = null
6 | formData.lang = null
7 | formData.policy = 'default'
8 | formData.args = null
9 | formData.chosenSolidity = null
10 | formData.sourceContent = null
11 | formData.compressedContent = null
12 | }
13 |
14 | export function buildBCOSDeployRequest(formData) {
15 | return {
16 | version: 1,
17 | path: formData.fullPath || formData.prependPath + formData.appendPath,
18 | data: {
19 | command: formData.method,
20 | args: [formData.appendPath || formData.fullPath.split('.')[2], formData.sourceContent, formData.className, formData.version]
21 | }
22 | }
23 | }
24 |
25 | export function buildBCOSDeployWasmRequest(formData) {
26 | return {
27 | version: 1,
28 | path: formData.fullPath || formData.prependPath + formData.appendPath,
29 | data: {
30 | command: formData.method,
31 | args: [formData.appendPath || formData.fullPath.split('.')[2], formData.abiContent, formData.sourceContent]
32 | }
33 | }
34 | }
35 |
36 | export function buildBCOSRegisterRequest(formData) {
37 | return {
38 | version: 1,
39 | path: formData.fullPath || formData.prependPath + formData.appendPath,
40 | data: {
41 | command: formData.method,
42 | args: [formData.appendPath || formData.fullPath.split('.')[2], formData.chosenSolidity.split('.')[1], formData.sourceContent, '0x' + formData.address, formData.className, formData.version]
43 | }
44 | }
45 | }
46 |
47 | export function buildFabricInstallRequest(formData) {
48 | return {
49 | version: 1,
50 | path: formData.fullPath || formData.prependPath + formData.appendPath,
51 | data: {
52 | command: formData.method,
53 | args: [
54 | formData.appendPath || formData.fullPath.split('.')[2],
55 | formData.version,
56 | formData.org.trim(),
57 | formData.lang,
58 | formData.compressedContent
59 | ]
60 | }
61 | }
62 | }
63 |
64 | export function buildFabricInstantiateRequest(formData) {
65 | return {
66 | version: 1,
67 | path: formData.fullPath || formData.prependPath + formData.appendPath,
68 | data: {
69 | command: formData.method,
70 | args: [
71 | formData.appendPath || formData.fullPath.split('.')[2],
72 | formData.version,
73 | formData.org,
74 | formData.lang,
75 | formData.policy === 'default' ? '' : formData.policy,
76 | formData.args.trim()
77 | ]
78 | }
79 | }
80 | }
81 |
82 | export function buildFabricUpgradeRequest(formData) {
83 | return {
84 | version: 1,
85 | path: formData.fullPath || formData.prependPath + formData.appendPath,
86 | data: {
87 | command: formData.method,
88 | args: [
89 | formData.appendPath || formData.fullPath.split('.')[2],
90 | formData.version,
91 | formData.org,
92 | formData.lang,
93 | formData.policy === 'default' ? '' : formData.policy,
94 | formData.args
95 | ]
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/mock/conn.js:
--------------------------------------------------------------------------------
1 | const Mock = require('mockjs')
2 | module.exports = [{
3 | url: '/conn/listPeers',
4 | type: 'get',
5 | response: _ => {
6 | return {
7 | ...Mock.mock({
8 | version: '1',
9 | errorCode: 0,
10 | message: 'success',
11 | data: {
12 | size: 1000,
13 | 'data|10': [{
14 | nodeID: '@id',
15 | address: '@integer(1,255).@integer(1,255).@integer(1,255).@integer(1,255):@integer(1, 65535)',
16 | seq: 1,
17 | chainInfos: [{
18 | name: '@id',
19 | stubType: '@pick([\'Fabirc1.4\', \'BCOS2.0\', \'GM_BCOS2.0\'])'
20 | }]
21 | }]
22 | }
23 | }) }
24 | }
25 | },
26 | {
27 | url: '/conn/addPeer',
28 | type: 'post',
29 | response: config => {
30 | /*
31 | peerData.items.push({
32 | nodeID: '@id',
33 | address: config.body.data.address,
34 | seq: 1,
35 | chainInfos: [{
36 | name: 'bcos',
37 | stubType: 'BCOS2.0'
38 | }]
39 | })
40 | */
41 |
42 | return {
43 | 'version': '1',
44 | 'errorCode': 0,
45 | 'message': 'success',
46 | 'peerData': {
47 | 'errorCode': 0,
48 | 'message': 'success'
49 | }
50 | }
51 | }
52 | }, {
53 | url: '/conn/removePeer',
54 | type: 'post',
55 | response: _ => {
56 | // for (var i in peerData.items) {
57 | // if (peerData.items[i].address === config.body.data.address) {
58 | // peerData.items.splice(i, 1)
59 | // break
60 | // }
61 | // }
62 | return {
63 | 'version': '1',
64 | 'errorCode': 0,
65 | 'message': 'success',
66 | 'peerData': {
67 | 'errorCode': 0,
68 | 'message': 'success'
69 | }
70 | }
71 | }
72 | }, {
73 | url: '/conn/listChains',
74 | type: 'get',
75 | response: param => {
76 | return {
77 | 'version': '1',
78 | 'errorCode': 0,
79 | 'message': 'success',
80 | data: {
81 | size: 1000,
82 | 'data|10': [{
83 | zone: param.query.zone,
84 | 'chain|1': ['bcos@integer(1,10000)', 'bcos_gm@integer(1,10000)', 'fabric@integer(1,10000)'],
85 | 'type|1': ['BCOS2.0', 'GM_BCOS2.0', 'Fabric1.4'],
86 | blockNumber: '@integer(1,1000000)',
87 | isLocal: '@pick(true,false)',
88 | 'properties': {
89 | 'BCOS_PROPERTY_CHAIN_ID': '1',
90 | 'WeCrossProxyABI': 'xxxxxxxxx',
91 | 'BCOS_PROPERTY_GROUP_ID': '1',
92 | 'WeCrossProxy': '0x8f9a2f54ca70f6a3f50b1ed27bdccad363b126f0',
93 | 'BCOS_PROPERTY_STUB_TYPE': 'BCOS2.0',
94 | 'WeCrossHub': '0x894b85761beec3aa08b00b9012c4ccd45c43ed84'
95 | }
96 | }]
97 | }
98 | }
99 | }
100 | }, {
101 | url: '/conn/listZones',
102 | type: 'get',
103 | response: _ => {
104 | return {
105 | 'version': '1',
106 | 'errorCode': 0,
107 | 'message': 'success',
108 | data: {
109 | size: 1,
110 | 'data|10': ["@pick([\'payment\',\'load\',\'resource\'])"]
111 | }
112 | }
113 | }
114 | }
115 | ]
116 |
--------------------------------------------------------------------------------
/src/api/transaction.js:
--------------------------------------------------------------------------------
1 | import request from '@/utils/request'
2 | import { path2Url } from '@/utils'
3 |
4 | /**
5 | * start a XA transaction
6 | * @param {xaRequest} data - body data to request
7 | * @return {Promise} an axios promise object of response
8 | */
9 | export function startXATransaction(data) {
10 | return request({
11 | url: '/xa/startXATransaction',
12 | method: 'post',
13 | data: data
14 | })
15 | }
16 |
17 | /**
18 | * commit a XA transaction
19 | * @param {xaRequest} data - body data to request
20 | * @return {Promise} an axios promise object of response
21 | */
22 | export function commitXATransaction(data) {
23 | return request({
24 | url: '/xa/commitXATransaction',
25 | method: 'post',
26 | data: data
27 | })
28 | }
29 |
30 | /**
31 | * rollback a XA transaction
32 | * @param {xaRequest} data - body data to request
33 | * @return {Promise} an axios promise object of response
34 | */
35 | export function rollbackXATransaction(data) {
36 | return request({
37 | url: '/xa/rollbackXATransaction',
38 | method: 'post',
39 | data: data
40 | })
41 | }
42 |
43 | /**
44 | * get a XA transaction's info
45 | * @param {xaRequest|*} data - body data to request
46 | * @return {Promise} an axios promise object of response
47 | */
48 | export function getXATransaction(data) {
49 | return request({
50 | url: '/xa/getXATransaction',
51 | method: 'post',
52 | data: data
53 | })
54 | }
55 |
56 | /**
57 | * get a XA transaction's info
58 | * @param {Object} data - body data to request
59 | * @param {string} data.version
60 | * @param {number} data.data.size
61 | * @param {Map|*} data.data.offsets - path => number
62 | * @return {Promise} an axios promise object of response
63 | */
64 | export function listXATransactions(data) {
65 | return request({
66 | url: '/xa/listXATransactions',
67 | method: 'post',
68 | data: data
69 | })
70 | }
71 |
72 | /**
73 | * call a contract status
74 | * @param {CallRequest} data
75 | * @return {Promise} an axios promise object of response
76 | */
77 | export function call(data) {
78 | return request({
79 | url: 'resource' + path2Url(data.path) + '/call',
80 | method: 'post',
81 | data: data
82 | })
83 | }
84 |
85 | /**
86 | * send a contract transaction
87 | * @param {CallRequest} data
88 | * @return {Promise} an axios promise object of response
89 | */
90 | export function sendTransaction(data) {
91 | return request({
92 | url: 'resource' + path2Url(data.path) + '/sendTransaction',
93 | method: 'post',
94 | data: data
95 | })
96 | }
97 |
98 | /**
99 | * list transactions of path
100 | * @param {Object} params
101 | * @param {string} params.path
102 | * @param {number} params.blockNumber
103 | * @param {number} params.offset
104 | * @param {number} params.size
105 | * @return {Promise} an axios promise object of response
106 | */
107 | export function listTransactions(params) {
108 | return request({
109 | url: '/trans/listTransactions',
110 | method: 'get',
111 | params: params
112 | })
113 | }
114 |
115 | /**
116 | * get a exact transaction's info by hash
117 | * @param {Object} params
118 | * @param {string} params.path
119 | * @param {string} params.txHash
120 | * @return {Promise} an axios promise object of response
121 | */
122 | export function getTransaction(params) {
123 | return request({
124 | url: '/trans/getTransaction',
125 | method: 'get',
126 | params: params
127 | })
128 | }
129 |
--------------------------------------------------------------------------------
/src/store/modules/transaction.js:
--------------------------------------------------------------------------------
1 | import { commitXATransaction, startXATransaction, rollbackXATransaction } from '@/api/transaction'
2 | import { getXATX, removeXATX, setXATX, buildXAError } from '@/utils/transaction'
3 | import { handleErrorMsgBox } from '@/utils/messageBox'
4 |
5 | const getDefaultState = () => {
6 | return {
7 | transactionID: getXATX() ? getXATX().transactionID : null,
8 | paths: getXATX() ? getXATX().paths : []
9 | }
10 | }
11 | const state = getDefaultState()
12 |
13 | const mutations = {
14 | RESET_STATE: (state) => {
15 | removeXATX()
16 | Object.assign(state, getDefaultState())
17 | },
18 | SET_TRANSACTION: (state, transaction) => {
19 | state.transactionID = transaction.transactionID
20 | state.paths = transaction.paths
21 | }
22 | }
23 |
24 | const actions = {
25 | startTransaction({ commit }, transaction) {
26 | return new Promise((resolve, reject) => {
27 | startXATransaction(transaction).then(response => {
28 | if (response.errorCode !== 0 || response.data.status !== 0) {
29 | const errMessage = buildXAError(response)
30 | handleErrorMsgBox('开启事务失败,错误:', '错误', errMessage, null).then(_ => {})
31 | reject()
32 | } else {
33 | commit('SET_TRANSACTION', { transactionID: transaction.data.xaTransactionID, paths: transaction.data.paths })
34 | setXATX(JSON.stringify({ transactionID: transaction.data.xaTransactionID, paths: transaction.data.paths }))
35 | resolve()
36 | }
37 | }).catch(error => {
38 | handleErrorMsgBox('开启事务失败,错误:', '错误', error.message, null).then(_ => {})
39 | reject(error)
40 | })
41 | })
42 | },
43 | commitTransaction({ commit }, transaction) {
44 | return new Promise((resolve, reject) => {
45 | commitXATransaction(transaction).then(response => {
46 | if (response.errorCode !== 0 || response.data.status !== 0) {
47 | const errMessage = buildXAError(response)
48 | handleErrorMsgBox('提交事务失败,错误:', '错误', errMessage, null)
49 | .then(_ => {
50 | if (/(committed|rolledback)/.test(errMessage)) {
51 | commit('RESET_STATE')
52 | removeXATX()
53 | }
54 | })
55 | reject(errMessage)
56 | } else {
57 | commit('RESET_STATE')
58 | removeXATX()
59 | resolve()
60 | }
61 | }).catch(error => {
62 | handleErrorMsgBox('提交事务失败,错误:', '错误', error.message, null)
63 | .then(_ => {})
64 | reject(error)
65 | })
66 | })
67 | },
68 | rollbackTransaction({ commit }, transaction) {
69 | return new Promise((resolve, reject) => {
70 | rollbackXATransaction(transaction).then(response => {
71 | if (response.errorCode !== 0 || response.data.status !== 0) {
72 | const errMessage = buildXAError(response)
73 | handleErrorMsgBox('回滚事务失败,错误:', '错误', errMessage, null)
74 | .then(_ => {
75 | if (/(committed|rolledback)$/.test(errMessage)) {
76 | commit('RESET_STATE')
77 | removeXATX()
78 | }
79 | })
80 | reject(errMessage)
81 | } else {
82 | commit('RESET_STATE')
83 | removeXATX()
84 | resolve()
85 | }
86 | }).catch(error => {
87 | handleErrorMsgBox('回滚事务失败,错误:', '错误', error.message, null)
88 | .then(_ => {})
89 | reject(error)
90 | })
91 | })
92 | }
93 | }
94 |
95 | export default {
96 | namespaced: true,
97 | state,
98 | mutations,
99 | actions
100 | }
101 |
--------------------------------------------------------------------------------
/src/icons/svg/chicken.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/styles/index.scss:
--------------------------------------------------------------------------------
1 | @import './variables.scss';
2 | @import './mixin.scss';
3 | @import './transition.scss';
4 | @import './element-ui.scss';
5 | @import './sidebar.scss';
6 |
7 | body {
8 | height: 100%;
9 | -moz-osx-font-smoothing: grayscale;
10 | -webkit-font-smoothing: antialiased;
11 | text-rendering: optimizeLegibility;
12 | font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
13 | }
14 |
15 | label {
16 | font-weight: 700;
17 | }
18 |
19 | html {
20 | height: 100%;
21 | box-sizing: border-box;
22 | }
23 |
24 | #app {
25 | height: 100%;
26 | }
27 |
28 | *,
29 | *:before,
30 | *:after {
31 | box-sizing: inherit;
32 | }
33 |
34 | .no-padding {
35 | padding: 0px !important;
36 | }
37 |
38 | .padding-content {
39 | padding: 4px 0;
40 | }
41 |
42 | a:focus,
43 | a:active {
44 | outline: none;
45 | }
46 |
47 | a,
48 | a:focus,
49 | a:hover {
50 | cursor: pointer;
51 | color: inherit;
52 | text-decoration: none;
53 | }
54 |
55 | div:focus {
56 | outline: none;
57 | }
58 |
59 | .fr {
60 | float: right;
61 | }
62 |
63 | .fl {
64 | float: left;
65 | }
66 |
67 | .pr-5 {
68 | padding-right: 5px;
69 | }
70 |
71 | .pl-5 {
72 | padding-left: 5px;
73 | }
74 |
75 | .block {
76 | display: block;
77 | }
78 |
79 | .pointer {
80 | cursor: pointer;
81 | }
82 |
83 | .inlineBlock {
84 | display: block;
85 | }
86 |
87 | .clearfix {
88 | &:after {
89 | visibility: hidden;
90 | display: block;
91 | font-size: 0;
92 | content: " ";
93 | clear: both;
94 | height: 0;
95 | }
96 | }
97 |
98 | aside {
99 | background: #eef1f6;
100 | padding: 8px 24px;
101 | margin-bottom: 20px;
102 | border-radius: 2px;
103 | display: block;
104 | line-height: 32px;
105 | font-size: 16px;
106 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
107 | color: #2c3e50;
108 | -webkit-font-smoothing: antialiased;
109 | -moz-osx-font-smoothing: grayscale;
110 |
111 | a {
112 | color: #337ab7;
113 | cursor: pointer;
114 |
115 | &:hover {
116 | color: rgb(32, 160, 255);
117 | }
118 | }
119 | }
120 |
121 | //main-container全局样式
122 | .app-container {
123 | padding: 20px;
124 | }
125 |
126 | .components-container {
127 | margin: 30px 50px;
128 | position: relative;
129 | }
130 |
131 | .pagination-container {
132 | margin-top: 30px;
133 | }
134 |
135 | .text-center {
136 | text-align: center
137 | }
138 |
139 | .sub-navbar {
140 | height: 50px;
141 | line-height: 50px;
142 | position: relative;
143 | width: 100%;
144 | text-align: right;
145 | padding-right: 20px;
146 | transition: 600ms ease position;
147 | background: linear-gradient(90deg, rgba(32, 182, 249, 1) 0%, rgba(32, 182, 249, 1) 0%, rgba(33, 120, 241, 1) 100%, rgba(33, 120, 241, 1) 100%);
148 |
149 | .subtitle {
150 | font-size: 20px;
151 | color: #fff;
152 | }
153 |
154 | &.draft {
155 | background: #d0d0d0;
156 | }
157 |
158 | &.deleted {
159 | background: #d0d0d0;
160 | }
161 | }
162 |
163 | .link-type,
164 | .link-type:focus {
165 | color: #337ab7;
166 | cursor: pointer;
167 |
168 | &:hover {
169 | color: rgb(32, 160, 255);
170 | }
171 | }
172 |
173 | .filter-container {
174 | padding-bottom: 10px;
175 |
176 | .filter-item {
177 | display: inline-block;
178 | vertical-align: middle;
179 | margin-bottom: 10px;
180 | }
181 | }
182 |
183 | //refine vue-multiselect plugin
184 | .multiselect {
185 | line-height: 16px;
186 | }
187 |
188 | .el-tooltip__popper {
189 | max-width: 300px;
190 | line-height: 180%;
191 | }
192 |
193 | .multiselect--active {
194 | z-index: 1000 !important;
195 | }
196 |
--------------------------------------------------------------------------------
/src/utils/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Parse the time to string
3 | * @param {(Object|string|number)} time
4 | * @param {string|null} cFormat
5 | * @returns {string | null}
6 | */
7 | export function parseTime(time, cFormat) {
8 | if (arguments.length === 0 || !time) {
9 | return null
10 | }
11 | const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'
12 | let date
13 | if (typeof time === 'object') {
14 | date = time
15 | } else {
16 | if ((typeof time === 'string')) {
17 | if ((/^[0-9]+$/.test(time))) {
18 | // support "1548221490638"
19 | time = parseInt(time)
20 | } else {
21 | // support safari
22 | // https://stackoverflow.com/questions/4310953/invalid-date-in-safari
23 | time = time.replace(new RegExp(/-/gm), '/')
24 | }
25 | }
26 |
27 | if ((typeof time === 'number') && (time.toString().length === 10)) {
28 | time = time * 1000
29 | }
30 | date = new Date(time)
31 | }
32 | const formatObj = {
33 | y: date.getFullYear(),
34 | m: date.getMonth() + 1,
35 | d: date.getDate(),
36 | h: date.getHours(),
37 | i: date.getMinutes(),
38 | s: date.getSeconds(),
39 | a: date.getDay()
40 | }
41 | const time_str = format.replace(/{([ymdhisa])+}/g, (result, key) => {
42 | const value = formatObj[key]
43 | // Note: getDay() returns 0 on Sunday
44 | if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value ] }
45 | return value.toString().padStart(2, '0')
46 | })
47 | return time_str
48 | }
49 |
50 | /**
51 | * @param {number} time
52 | * @param {string} option
53 | * @returns {string}
54 | */
55 | export function formatTime(time, option) {
56 | if (('' + time).length === 10) {
57 | time = parseInt(time) * 1000
58 | } else {
59 | time = +time
60 | }
61 | const d = new Date(time)
62 | const now = Date.now()
63 |
64 | const diff = (now - d) / 1000
65 |
66 | if (diff < 30) {
67 | return '刚刚'
68 | } else if (diff < 3600) {
69 | // less 1 hour
70 | return Math.ceil(diff / 60) + '分钟前'
71 | } else if (diff < 3600 * 24) {
72 | return Math.ceil(diff / 3600) + '小时前'
73 | } else if (diff < 3600 * 24 * 2) {
74 | return '1天前'
75 | }
76 | if (option) {
77 | return parseTime(time, option)
78 | } else {
79 | return (
80 | d.getMonth() +
81 | 1 +
82 | '月' +
83 | d.getDate() +
84 | '日' +
85 | d.getHours() +
86 | '时' +
87 | d.getMinutes() +
88 | '分'
89 | )
90 | }
91 | }
92 |
93 | /**
94 | * @param {string} url
95 | * @returns {Object}
96 | */
97 | export function param2Obj(url) {
98 | const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ')
99 | if (!search) {
100 | return {}
101 | }
102 | const obj = {}
103 | const searchArr = search.split('&')
104 | searchArr.forEach(v => {
105 | const index = v.indexOf('=')
106 | if (index !== -1) {
107 | const name = v.substring(0, index)
108 | obj[name] = v.substring(index + 1, v.length)
109 | }
110 | })
111 | return obj
112 | }
113 |
114 | export function uniqueFilter(element, index, self) {
115 | return self.indexOf(element) === index
116 | }
117 |
118 | export function uniqueObjectArray(objectArray) {
119 | return [...new Set(objectArray.map(e => JSON.stringify(e)))].map(e => JSON.parse(e))
120 | }
121 |
122 | export function buildRequest(path, method, data) {
123 | return {
124 | version: 1,
125 | path: path,
126 | method: method,
127 | data: data
128 | }
129 | }
130 |
131 | export function path2Url(path) {
132 | const part = path.split('.')
133 | return '/' + part.join('/')
134 | }
135 |
136 | export function limitString(str, limitNum = 40) {
137 | if (typeof str === 'string') {
138 | if (str.length > limitNum) {
139 | return str.substring(0, limitNum) + '...'
140 | } else {
141 | return str
142 | }
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/src/views/resource/resourceManager.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | 导航
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
资源列表
23 |
24 |
25 |
26 | {if(typeof currentChain !== 'undefined'){$refs['ResourceExplorer'].refresh()}}" />
27 |
28 |
29 | 部署资源
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
103 |
104 |
107 |
--------------------------------------------------------------------------------
/src/assets/favicon.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/components/ChainExplorer/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 |
138 |
139 |
141 |
--------------------------------------------------------------------------------
/src/views/transaction/transactionManager.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | 导航
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
交易列表
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | 发交易
31 |
32 |
33 |
34 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
118 |
119 |
120 |
--------------------------------------------------------------------------------
/src/assets/wecross.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
52 |
--------------------------------------------------------------------------------
/src/views/resource/resourceSteps/resourceDeploySteps.js:
--------------------------------------------------------------------------------
1 |
2 | export function stepRoute(type, method) {
3 | if (type === null) {
4 | return defaultSteps
5 | }
6 | if (type === 'BCOS2.0' || type === 'GM_BCOS2.0') {
7 | return BCOSSteps
8 | } else if (type === 'Fabric1.4') {
9 | if (method === null) {
10 | return [{
11 | element: '#method',
12 | title: '选择操作',
13 | intro: '请先选择操作类型',
14 | position: 'bottom'
15 | }
16 | ]
17 | }
18 | if (method === 'install') {
19 | return FabricInstallSteps
20 | } else {
21 | return FabricInstantSteps
22 | }
23 | }
24 | }
25 |
26 | const BCOSSteps = [
27 | {
28 | element: '#method',
29 | title: '1. 选择操作',
30 | intro: '选择操作类型',
31 | position: 'top'
32 | }, {
33 | element: '#Path',
34 | title: '2. 资源路径',
35 | intro: '填写资源路径',
36 | position: 'top'
37 | }, {
38 | element: '#zipContract',
39 | title: '3. 选取合约上传',
40 | intro: '选取需要部署的合约文件,打包成ZIP格式上传
打包zip文件时,合约入口必须放在最外层合约的所有依赖文件必须按照依赖相对路径一起打包',
41 | position: 'right'
42 | }, {
43 | element: '#chosenSolidity',
44 | title: '4. 选取合约入口文件',
45 | intro: '页面会读取您所上传的zip文件的最外层,选择入口文件进行部署',
46 | position: 'left'
47 | }, {
48 | element: '#className',
49 | title: '5. 填写合约类名',
50 | intro: '合约类名是您选取的"合约入口文件"中的合约类名',
51 | position: 'top'
52 | }, {
53 | element: '#bcosVersion',
54 | title: '6. 填写合约版本号',
55 | intro: '若在链中已有待部署合约的旧版本,填写较大版本可对旧合约进行升级',
56 | position: 'top'
57 | }, {
58 | element: '#onSubmit',
59 | title: '执行部署操作',
60 | intro: '检查表单,执行操作',
61 | position: 'top'
62 | }
63 | ]
64 |
65 | const FabricInstallSteps = [
66 | {
67 | element: '#method',
68 | title: '1. 选择操作',
69 | intro: '选择操作类型',
70 | position: 'top'
71 | }, {
72 | element: '#Path',
73 | title: '2. 资源路径',
74 | intro: '填写资源路径',
75 | position: 'top'
76 | }, {
77 | element: '#org',
78 | title: '3. 所属机构名',
79 | intro: '安装链码的endorser所属的机构名当前账户必须有对应机构的证书密钥才可以正确安装chaincode',
80 | position: 'top'
81 | }, {
82 | element: '#compressedContent',
83 | title: '4. 选择合约文件',
84 | intro: '选取需要部署的chaincode合约文件,打包成GZIP格式上传
打包时必须将链码放在"src/chaincode/"的目录下在能正确安装文件路径例如:src/chaincode/sacc.go',
85 | position: 'top'
86 | }, {
87 | element: '#fabricVersion',
88 | title: '5. 填写合约版本号',
89 | intro: '若在链中已有待部署合约的旧版本,填写较大版本可对旧合约进行升级',
90 | position: 'top'
91 | }, {
92 | element: '#lang',
93 | title: '6. 选择合约语言版本',
94 | intro: 'Golang/Java',
95 | position: 'top'
96 | }, {
97 | element: '#onSubmit',
98 | title: '执行部署操作',
99 | intro: '检查表单,执行操作因为Fabric的原因,安装的chaincode必须实例化才能显示在跨链资源列表',
100 | position: 'top'
101 | }
102 | ]
103 |
104 | const FabricInstantSteps = [
105 | {
106 | element: '#method',
107 | title: '1. 选择操作',
108 | intro: '选择操作类型',
109 | position: 'top'
110 | }, {
111 | element: '#Path',
112 | title: '2. 资源路径',
113 | intro: '填写资源路径',
114 | position: 'top'
115 | }, {
116 | element: '#orgs',
117 | title: '3. 机构列表',
118 | intro: '链码被安装的的机构列表将chaincode在列表中的机构进行实例化必须以JSON数组的形式填写,例如:["Org1","Org2"]',
119 | position: 'top'
120 | }, {
121 | element: '#fabricVersion',
122 | title: '4. 填写合约版本号',
123 | intro: '选择对应版本的chaincode进行实例化',
124 | position: 'top'
125 | }, {
126 | element: '#lang',
127 | title: '5. 选择合约语言版本',
128 | intro: 'Golang/Java',
129 | position: 'top'
130 | }, {
131 | element: '#policy',
132 | title: '6. 选择背书文件',
133 | intro: '选择实例化时的背书
只能上传policy的yaml格式文件, 不上传默认为defaultdefault为所有机构以OR连接',
134 | position: 'top'
135 | }, {
136 | element: '#args',
137 | title: '7. 填写实例化参数',
138 | intro: '填写实例化时的参数必须以JSON数组的形式填写,例如:["a","10"],若参数为空也必须填写 []',
139 | position: 'top'
140 | }, {
141 | element: '#onSubmit',
142 | title: '执行部署操作',
143 | intro: '检查表单,执行操作',
144 | position: 'top'
145 | }
146 | ]
147 |
148 | const defaultSteps = [
149 | {
150 | element: '#stubType',
151 | title: '1. 选择链类型',
152 | intro: '选择链的类型',
153 | position: 'bottom'
154 | }, {
155 | element: '#method',
156 | title: '2. 选择操作',
157 | intro: '选择操作类型',
158 | position: 'top'
159 | }
160 | ]
161 |
--------------------------------------------------------------------------------
/vue.config.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const path = require('path')
3 |
4 | function resolve(dir) {
5 | return path.join(__dirname, dir)
6 | }
7 |
8 | const name = 'WeCross Web App' // page title
9 |
10 | // If your port is set to 80,
11 | // use administrator privileges to execute the command line.
12 | // For example, Mac: sudo npm run
13 | // You can change the port by the following methods:
14 | // port = 9528 npm run dev OR npm run dev --port = 9528
15 | const port = process.env.port || process.env.npm_config_port || 9528 // dev port
16 |
17 | // All configuration item explanations can be find in https://cli.vuejs.org/config/
18 | module.exports = {
19 | /**
20 | * You will need to set publicPath if you plan to deploy your site under a sub path,
21 | * for example GitHub Pages. If you plan to deploy your site to https://foo.github.io/bar/,
22 | * then publicPath should be set to "/bar/".
23 | * In most cases please use '/' !!!
24 | * Detail: https://cli.vuejs.org/config/#publicpath
25 | */
26 | publicPath: './',
27 | outputDir: 'dist',
28 | assetsDir: 'static',
29 | lintOnSave: process.env.NODE_ENV === 'development',
30 | productionSourceMap: false,
31 | devServer: {
32 | port: port,
33 | host: '0.0.0.0',
34 | open: true,
35 | overlay: {
36 | warnings: false,
37 | errors: true
38 | },
39 | proxy: 'http://121.37.203.43:8250/'
40 | // before: require('./mock/mock-server.js')
41 | },
42 | configureWebpack: {
43 | // provide the app's title in webpack's name field, so that
44 | // it can be accessed in index.html to inject the correct title.
45 | name: name,
46 | resolve: {
47 | alias: {
48 | '@': resolve('src')
49 | }
50 | }
51 | },
52 | chainWebpack(config) {
53 | // it can improve the speed of the first screen, it is recommended to turn on preload
54 | config.plugin('preload').tap(() => [
55 | {
56 | rel: 'preload',
57 | // to ignore runtime.js
58 | // https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/cli-service/lib/config/app.js#L171
59 | fileBlacklist: [/\.map$/, /hot-update\.js$/, /runtime\..*\.js$/],
60 | include: 'initial'
61 | }
62 | ])
63 |
64 | // when there are many pages, it will cause too many meaningless requests
65 | config.plugins.delete('prefetch')
66 |
67 | // set svg-sprite-loader
68 | config.module
69 | .rule('svg')
70 | .exclude.add(resolve('src/icons'))
71 | .end()
72 | config.module
73 | .rule('icons')
74 | .test(/\.svg$/)
75 | .include.add(resolve('src/icons'))
76 | .end()
77 | .use('svg-sprite-loader')
78 | .loader('svg-sprite-loader')
79 | .options({
80 | symbolId: 'icon-[name]'
81 | })
82 | .end()
83 |
84 | config
85 | .when(process.env.NODE_ENV !== 'development',
86 | subConfig => {
87 | subConfig
88 | .plugin('ScriptExtHtmlWebpackPlugin')
89 | .after('html')
90 | .use('script-ext-html-webpack-plugin', [{
91 | // `runtime` must same as runtimeChunk name. default is `runtime`
92 | inline: /runtime\..*\.js$/
93 | }])
94 | .end()
95 | subConfig
96 | .optimization.splitChunks({
97 | chunks: 'all',
98 | cacheGroups: {
99 | libs: {
100 | name: 'chunk-libs',
101 | test: /[\\/]node_modules[\\/]/,
102 | priority: 10,
103 | chunks: 'initial' // only package third parties that are initially dependent
104 | },
105 | elementUI: {
106 | name: 'chunk-elementUI', // split elementUI into a single package
107 | priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app
108 | test: /[\\/]node_modules[\\/]_?element-ui(.*)/ // in order to adapt to cnpm
109 | },
110 | commons: {
111 | name: 'chunk-commons',
112 | test: resolve('src/components'), // can customize your rules
113 | minChunks: 3, // minimum common number
114 | priority: 5,
115 | reuseExistingChunk: true
116 | }
117 | }
118 | })
119 | // https:// webpack.js.org/configuration/optimization/#optimizationruntimechunk
120 | subConfig.optimization.runtimeChunk('single')
121 | }
122 | )
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/src/utils/request.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 | import { MessageBox, Message } from 'element-ui'
3 | import store from '@/store'
4 | import { getToken } from '@/utils/auth'
5 |
6 | // create an axios instance
7 | const request = axios.create({
8 | baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
9 | // withCredentials: true, // send cookies when cross-domain requests
10 | timeout: 30000 // request timeout
11 | })
12 |
13 | // request interceptor
14 | request.interceptors.request.use(
15 | config => {
16 | // do something before request is sent
17 |
18 | if (store.getters.token) {
19 | if (store.getters.token !== getToken()) {
20 | return Promise.reject(new Error('needRefresh'))
21 | }
22 | config.headers['Authorization'] = getToken()
23 | config.headers['Accept'] = 'application/json'
24 | config.headers['content-type'] = 'application/json;charset=UTF-8'
25 | }
26 | return config
27 | },
28 | error => {
29 | // do something with request error
30 | console.log('err: ', error) // for debug
31 | return Promise.reject(error)
32 | }
33 | )
34 |
35 | // response interceptor
36 | request.interceptors.response.use(
37 | response => {
38 | if (!response) {
39 | Message({
40 | message: 'HTTP返回为空!',
41 | type: 'error',
42 | duration: 5 * 1000
43 | })
44 | return Promise.reject(new Error('HTTP返回为空'))
45 | }
46 | if (!response.status) {
47 | Message({
48 | message: 'HTTP响应状态码为空!',
49 | type: 'error',
50 | duration: 5 * 1000
51 | })
52 | return Promise.reject(new Error('HTTP响应状态码为空!'))
53 | }
54 | const res = response.data
55 | const status = response.status
56 | if (status !== 200) {
57 | Message({
58 | message: res.message || 'Error: status is' + status,
59 | type: 'error',
60 | duration: 5 * 1000
61 | })
62 |
63 | // 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired;
64 | if (res.errorCode === 10502 || res.errorCode === 50012 || res.errorCode === 50014) {
65 | // to re-login
66 | MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', {
67 | confirmButtonText: 'Re-Login',
68 | cancelButtonText: 'Cancel',
69 | type: 'warning'
70 | }).then(() => {
71 | store.dispatch('user/resetToken').then(() => {
72 | location.reload()
73 | })
74 | })
75 | }
76 | return Promise.reject(new Error(res.message || 'Error'))
77 | } else {
78 | return res
79 | }
80 | },
81 | error => {
82 | // another tab login, should fresh status
83 | if (axios.isCancel(error)) {
84 | throw new Error('canceled')
85 | } else if (error.message === 'needRefresh') {
86 | throw new Error('当前页面状态已变化,请刷新页面再重试!')
87 | }
88 | // timeout
89 | if (error.message.includes('timeout')) {
90 | Message({
91 | message: '请求超时,请检查后台服务:' + error.message,
92 | type: 'error',
93 | duration: 5 * 1000
94 | })
95 | return Promise.reject(new Error(error.message || '请求超时,请检查后台服务'))
96 | }
97 | // no response status
98 | if (typeof error.response !== 'undefined' && typeof error.response.status !== 'undefined') {
99 | const status = error.response.status
100 | switch (status) {
101 | case 401 :
102 | MessageBox.confirm('您的登录态已超时,请重新登录', '超时提醒', {
103 | confirmButtonText: '重登录',
104 | cancelButtonText: '取消',
105 | type: 'warning'
106 | }).then(() => {
107 | store.dispatch('user/resetToken').then(() => {
108 | location.reload()
109 | })
110 | })
111 | break
112 | case 400:
113 | Message.error({
114 | message: '参数异常'
115 | })
116 | break
117 | case 404:
118 | Message.error({
119 | message: '请求URL错误:' + error.message,
120 | center: true,
121 | duration: 5 * 1000
122 | })
123 | break
124 | case 500:
125 | Message.error({
126 | message: '服务器异常'
127 | })
128 | break
129 | default:
130 | Message({
131 | message: error.message,
132 | type: 'error',
133 | duration: 5 * 1000
134 | })
135 | }
136 | console.log('err: ' + error) // for debug
137 | return Promise.reject(error)
138 | }
139 | }
140 | )
141 |
142 | export default request
143 |
--------------------------------------------------------------------------------
/src/components/HeaderSearch/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
20 |
21 |
22 |
135 |
136 |
174 |
--------------------------------------------------------------------------------
/mock/ua.js:
--------------------------------------------------------------------------------
1 | const Mock = require('mockjs')
2 |
3 | module.exports = [
4 | {
5 | url: '/auth/listAccount',
6 | type: 'post',
7 | response: _ => {
8 | return {
9 | ...Mock.mock({
10 | version: 0,
11 | errorCode: 0,
12 | message: 'success',
13 | data: {
14 | 'username': 'org1-admin',
15 | 'uaID': '3059301306072a8648ce3d020106082a811ccf5501822d034200047cfc7f4488a171e4a80051cdf93e2febc3066181b17bccd81264b79e346affc1f684738aa565485a459bbc00f03bd1df3df7dac985e6a740a3ed5533d5a60874',
16 | 'pubKey': '3059301306072a8648ce3d020106082a811ccf5501822d034200047cfc7f4488a171e4a80051cdf93e2febc3066181b17bccd81264b79e346affc1f684738aa565485a459bbc00f03bd1df3df7dac985e6a740a3ed5533d5a60874',
17 | 'admin': true,
18 | 'version': 1,
19 | 'chainAccounts': [
20 | {
21 | 'keyID': 0,
22 | 'identity': '0x6c51a6cef228f784636c690d8b13f956e177cc76',
23 | 'type': 'BCOS2.0',
24 | 'pubKey': '-----BEGIN PUBLIC KEY-----\nmock 1111\n-----END PUBLIC KEY-----\n',
25 | 'secKey': '-----BEGIN PRIVATE KEY-----\nmock xxxx\n-----END PRIVATE KEY-----\n',
26 | 'ext': '0x6c51a6cef228f784636c690d8b13f956e177cc76',
27 | 'isDefault': true
28 | },
29 | {
30 | 'keyID': 3,
31 | 'identity': '-----BEGIN CERTIFICATE-----\nmock 2222\n-----END CERTIFICATE-----\n',
32 | 'type': 'Fabric1.4',
33 | 'pubKey': '-----BEGIN CERTIFICATE-----\nmock 2222\n-----END CERTIFICATE-----\n',
34 | 'secKey': '-----BEGIN PRIVATE KEY-----\nmock xxxx\n-----END PRIVATE KEY-----\n',
35 | 'ext': 'Org1MSP',
36 | 'isDefault': false
37 | },
38 | {
39 | 'keyID': 2,
40 | 'identity': '-----BEGIN CERTIFICATE-----\nmock 3333\n-----END CERTIFICATE-----\n',
41 | 'type': 'Fabric1.4',
42 | 'pubKey': '-----BEGIN CERTIFICATE-----\nmock 3333\n-----END CERTIFICATE-----\n',
43 | 'secKey': '-----BEGIN PRIVATE KEY-----\nmock xxxx\n-----END PRIVATE KEY-----\n',
44 | 'ext': 'Org2MSP',
45 | 'isDefault': false
46 | },
47 | {
48 | 'keyID': 1,
49 | 'identity': '-----BEGIN CERTIFICATE-----\nmock 4444\n-----END CERTIFICATE-----\n',
50 | 'type': 'Fabric1.4',
51 | 'pubKey': '-----BEGIN CERTIFICATE-----\nmock 4444\n-----END CERTIFICATE-----\n',
52 | 'secKey': '-----BEGIN PRIVATE KEY-----\nmock xxxx\n-----END PRIVATE KEY-----\n',
53 | 'ext': 'Org1MSP',
54 | 'isDefault': true
55 | }
56 | ]
57 | }
58 | })
59 | }
60 | }
61 | },
62 | {
63 | url: '/auth/setDefaultAccount',
64 | type: 'post',
65 | response: _ => {
66 | return {
67 | ...Mock.mock({
68 | version: 0,
69 | errorCode: 0,
70 | message: 'success',
71 | data: {
72 | errorCode: 0,
73 | message: 'success'
74 | }
75 | })
76 | }
77 | }
78 | },
79 | {
80 | url: '/auth/addChainAccount',
81 | type: 'post',
82 | response: _ => {
83 | return {
84 | ...Mock.mock({
85 | version: 0,
86 | errorCode: 0,
87 | message: 'success',
88 | data: {
89 | errorCode: 0,
90 | message: 'success'
91 | }
92 | })
93 | }
94 | }
95 | },
96 | {
97 | url: '/auth/removeChainAccount',
98 | type: 'post',
99 | response: _ => {
100 | return {
101 | ...Mock.mock({
102 | version: 0,
103 | errorCode: 0,
104 | message: 'success',
105 | data: {
106 | errorCode: 0,
107 | message: 'success'
108 | }
109 | })
110 | }
111 | }
112 | },
113 | {
114 | url: '/auth/admin/accessControlList',
115 | type: 'get',
116 | response: _ => {
117 | return {
118 | ...Mock.mock({
119 | version: 0,
120 | errorCode: 0,
121 | message: 'success',
122 | 'data|20': [
123 | {
124 | 'username': '@id',
125 | 'allowChainPaths|20': [
126 | 'payment.@id'
127 | ],
128 | 'updateTimestamp': 1624517049308
129 | }
130 | ]
131 | })
132 | }
133 | }
134 | },
135 | {
136 | url: '/auth/admin/accessControlList',
137 | type: 'post',
138 | response: _ => {
139 | return {
140 | ...Mock.mock({
141 | version: 1,
142 | errorCode: 0,
143 | message: 'success',
144 | data: {
145 | errorCode: 0,
146 | message: 'success'
147 | }
148 | })
149 | }
150 | }
151 | }
152 | ]
153 |
--------------------------------------------------------------------------------
/src/styles/sidebar.scss:
--------------------------------------------------------------------------------
1 | #app {
2 |
3 | .main-container {
4 | min-height: 100%;
5 | transition: margin-left .28s;
6 | margin-left: $sideBarWidth;
7 | position: relative;
8 | }
9 |
10 | .sidebar-container {
11 | transition: width 0.28s;
12 | width: $sideBarWidth !important;
13 | background-color: $menuBg;
14 | height: 100%;
15 | position: fixed;
16 | font-size: 0px;
17 | top: 0;
18 | bottom: 0;
19 | left: 0;
20 | z-index: 1001;
21 | overflow: hidden;
22 |
23 | // reset element-ui css
24 | .horizontal-collapse-transition {
25 | transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out;
26 | }
27 |
28 | .scrollbar-wrapper {
29 | overflow-x: hidden !important;
30 | }
31 |
32 | .el-scrollbar__bar.is-vertical {
33 | right: 0px;
34 | }
35 |
36 | .el-scrollbar {
37 | height: 100%;
38 | }
39 |
40 | &.has-logo {
41 | .el-scrollbar {
42 | height: calc(100% - 50px);
43 | }
44 | }
45 |
46 | .is-horizontal {
47 | display: none;
48 | }
49 |
50 | a {
51 | display: inline-block;
52 | width: 100%;
53 | overflow: hidden;
54 | }
55 |
56 | .svg-icon {
57 | margin-right: 16px;
58 | }
59 |
60 | .sub-el-icon {
61 | margin-right: 12px;
62 | margin-left: -2px;
63 | }
64 |
65 | .el-menu {
66 | border: none;
67 | height: 100%;
68 | width: 100% !important;
69 | }
70 |
71 | // menu hover
72 | .submenu-title-noDropdown,
73 | .el-submenu__title {
74 | &:hover {
75 | background-color: $menuHover !important;
76 | }
77 | }
78 |
79 | .is-active>.el-submenu__title {
80 | color: $subMenuActiveText !important;
81 | }
82 |
83 | & .nest-menu .el-submenu>.el-submenu__title,
84 | & .el-submenu .el-menu-item {
85 | min-width: $sideBarWidth !important;
86 | background-color: $subMenuBg !important;
87 |
88 | &:hover {
89 | background-color: $subMenuHover !important;
90 | }
91 | }
92 | }
93 |
94 | .hideSidebar {
95 | .sidebar-container {
96 | width: 54px !important;
97 | }
98 |
99 | .main-container {
100 | margin-left: 54px;
101 | }
102 |
103 | .submenu-title-noDropdown {
104 | padding: 0 !important;
105 | position: relative;
106 |
107 | .el-tooltip {
108 | padding: 0 !important;
109 |
110 | .svg-icon {
111 | margin-left: 20px;
112 | }
113 |
114 | .sub-el-icon {
115 | margin-left: 19px;
116 | }
117 | }
118 | }
119 |
120 | .el-submenu {
121 | overflow: hidden;
122 |
123 | &>.el-submenu__title {
124 | padding: 0 !important;
125 |
126 | .svg-icon {
127 | margin-left: 20px;
128 | }
129 |
130 | .sub-el-icon {
131 | margin-left: 19px;
132 | }
133 |
134 | .el-submenu__icon-arrow {
135 | display: none;
136 | }
137 | }
138 | }
139 |
140 | .el-menu--collapse {
141 | .el-submenu {
142 | &>.el-submenu__title {
143 | &>span {
144 | height: 0;
145 | width: 0;
146 | overflow: hidden;
147 | visibility: hidden;
148 | display: inline-block;
149 | }
150 | }
151 | }
152 | }
153 | }
154 |
155 | .el-menu--collapse .el-menu .el-submenu {
156 | min-width: $sideBarWidth !important;
157 | }
158 |
159 | // mobile responsive
160 | .mobile {
161 | .main-container {
162 | margin-left: 0px;
163 | }
164 |
165 | .sidebar-container {
166 | transition: transform .28s;
167 | width: $sideBarWidth !important;
168 | }
169 |
170 | &.hideSidebar {
171 | .sidebar-container {
172 | pointer-events: none;
173 | transition-duration: 0.3s;
174 | transform: translate3d(-$sideBarWidth, 0, 0);
175 | }
176 | }
177 | }
178 |
179 | .withoutAnimation {
180 |
181 | .main-container,
182 | .sidebar-container {
183 | transition: none;
184 | }
185 | }
186 | }
187 |
188 | // when menu collapsed
189 | .el-menu--vertical {
190 | &>.el-menu {
191 | .svg-icon {
192 | margin-right: 16px;
193 | }
194 | .sub-el-icon {
195 | margin-right: 12px;
196 | margin-left: -2px;
197 | }
198 | }
199 |
200 | .nest-menu .el-submenu>.el-submenu__title,
201 | .el-menu-item {
202 | &:hover {
203 | // you can use $subMenuHover
204 | background-color: $menuHover !important;
205 | }
206 | }
207 |
208 | // the scroll bar appears when the subMenu is too long
209 | >.el-menu--popup {
210 | max-height: 100vh;
211 | overflow-y: auto;
212 |
213 | &::-webkit-scrollbar-track-piece {
214 | background: #d3dce6;
215 | }
216 |
217 | &::-webkit-scrollbar {
218 | width: 6px;
219 | }
220 |
221 | &::-webkit-scrollbar-thumb {
222 | background: #99a9bf;
223 | border-radius: 20px;
224 | }
225 | }
226 | }
227 |
--------------------------------------------------------------------------------
/src/utils/messageBox.js:
--------------------------------------------------------------------------------
1 | import { MessageBox } from 'element-ui'
2 | import Vue from 'vue'
3 |
4 | /**
5 | * handle a long error message output
6 | * @param {string} message - set to msgBox's main body
7 | * @param {string} title - set to msgBox's title
8 | * @param {string} errorMessage - set to textarea, a long output
9 | * @param {Object|null} options - other msgBox options
10 | * @return {Promise}
11 | */
12 | export function handleErrorMsgBox(message, title, errorMessage, options) {
13 | const vm = new Vue()
14 | const h = vm.$createElement
15 | return MessageBox({
16 | message: h('div', { style: {
17 | display: 'block',
18 | fontSize: '16px',
19 | fontFamily: 'Helvetica Neue',
20 | width: '100%',
21 | height: '100%'
22 | }},
23 | [
24 | h('i', {
25 | style: {
26 | color: '#f56c6c',
27 | marginRight: '6px'
28 | },
29 | attrs: {
30 | class: 'el-icon-error'
31 | }
32 | }, ''),
33 | h('span', null, message),
34 | h('div', {
35 | style: { display: 'block' },
36 | attrs: {
37 | }
38 | }, [
39 | h('textarea', {
40 | attrs: {
41 | readonly: true
42 | },
43 | style: {
44 | color: '#606266',
45 | margin: '10px 0',
46 | padding: '8px 10px',
47 | height: 'auto',
48 | minHeight: '100px',
49 | maxHeight: '150px',
50 | overflow: 'auto',
51 | width: '100%',
52 | resize: 'none',
53 | fontSize: '15px',
54 | border: '0px'
55 | }
56 | }, errorMessage)
57 | ])
58 | ]),
59 | title: title,
60 | ...options
61 | })
62 | }
63 |
64 | /**
65 | * handle a long success message output
66 | * @param {string} message - set to msgBox's main body
67 | * @param {string} title - set to msgBox's title
68 | * @param {string} successMessage - set to textarea, a long output
69 | * @param {Object|null} options - other msgBox options
70 | * @return {Promise}
71 | */
72 | export function handleSuccessMsgBox(message, title, successMessage, options) {
73 | const vm = new Vue()
74 | const h = vm.$createElement
75 | return MessageBox({
76 | message: h('div', { style: {
77 | display: 'block',
78 | fontSize: '16px',
79 | fontFamily: 'Helvetica Neue',
80 | width: '100%'
81 | }},
82 | [
83 | h('i', {
84 | style: {
85 | color: '#67C23A',
86 | marginRight: '6px'
87 | },
88 | attrs: {
89 | class: 'el-icon-success'
90 | }
91 | }, ''),
92 | h('span', null, message),
93 | h('div', {
94 | style: { display: 'block' },
95 | attrs: {
96 | }
97 | }, [
98 | h('textarea', {
99 | attrs: {
100 | readonly: true
101 | },
102 | style: {
103 | color: '#606266',
104 | margin: '10px 0',
105 | padding: '8px 10px',
106 | height: 'auto',
107 | minHeight: '80px',
108 | maxHeight: '150px',
109 | overflow: 'auto',
110 | width: '100%',
111 | resize: 'none',
112 | fontSize: '15px',
113 | border: '0px'
114 | }
115 | }, successMessage)
116 | ])
117 | ]),
118 | title: title,
119 | ...options
120 | })
121 | }
122 |
123 | /**
124 | * handle a long warning message output
125 | * @param {string} message - set to msgBox's main body
126 | * @param {string} title - set to msgBox's title
127 | * @param {string} warningMessage - set to textarea, a long output
128 | * @param {Object|null} options - other msgBox options
129 | * @return {Promise}
130 | */
131 | export function handleWarningMsgBox(message, title, warningMessage, options) {
132 | const vm = new Vue()
133 | const h = vm.$createElement
134 | return MessageBox({
135 | message: h('div', { style: {
136 | display: 'block',
137 | fontSize: '16px',
138 | fontFamily: 'Helvetica Neue',
139 | width: '100%',
140 | height: '100%'
141 | }},
142 | [
143 | h('i', {
144 | style: {
145 | color: '#e6a23c',
146 | marginRight: '6px'
147 | },
148 | attrs: {
149 | class: 'el-icon-warning'
150 | }
151 | }, ''),
152 | h('span', null, message),
153 | h('div', {
154 | style: { display: 'block' },
155 | attrs: {
156 | }
157 | }, [
158 | h('textarea', {
159 | attrs: {
160 | readonly: true
161 | },
162 | style: {
163 | color: '#606266',
164 | margin: '10px 0',
165 | padding: '8px 10px',
166 | height: 'auto',
167 | minHeight: '100px',
168 | maxHeight: '150px',
169 | overflow: 'auto',
170 | width: '100%',
171 | resize: 'none',
172 | fontSize: '15px',
173 | border: '0px'
174 | }
175 | }, warningMessage)
176 | ])
177 | ]),
178 | title: title,
179 | ...options
180 | })
181 | }
182 |
--------------------------------------------------------------------------------
/src/views/router/routerManager.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | 刷新
8 | 添加跨链路由
9 |
10 |
11 |
12 |
13 |
14 |
15 | {{ getAlias(item.row.nodeID) !== null ? getAlias(item.row.nodeID) : '未设置' }}
16 |
17 |
18 |
19 |
20 |
21 | {{ item.row.nodeID==='Local'?'':item.row.nodeID }}
22 | {{ item.row.nodeID }}
23 |
24 |
25 |
26 |
27 | {{ item.row.address }}
28 |
29 |
30 |
31 |
32 |
33 | {{ chainItem.stubType }}
34 |
35 |
36 |
37 |
38 |
39 |
40 | 设置别名
41 |
42 |
43 |
44 |
45 |
46 |
47 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
154 |
155 |
158 |
--------------------------------------------------------------------------------
/mock/user.js:
--------------------------------------------------------------------------------
1 | module.exports = [
2 | // user login
3 | {
4 | url: '/auth/login',
5 | type: 'post',
6 | response: _ => {
7 | const token = 'access-token'
8 |
9 | // mock error
10 | if (!token) {
11 | return {
12 | code: 60204,
13 | message: 'Account and password are incorrect.'
14 | }
15 | }
16 |
17 | return {
18 | 'version': '1.0',
19 | 'errorCode': 0,
20 | 'message': 'success',
21 | 'data': {
22 | 'errorCode': 0,
23 | 'message': 'success',
24 | 'credential': 'Bearer eyJpYXRtaWxsIjoxNjI0NDM3MDgwODk2LCJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJvcmcxLWFkbWluIiwibmJmIjoxNjI0NDM3MDgwLCJpc3MiOiJvcmcxIiwiZXhwIjoxNjI0NDM3NjgwLCJpYXQiOjE2MjQ0MzcwODB9.0yIjh54NvVgeoOX2DqbfTEm_ukdJeF7f44lii0Unkzc',
25 | 'universalAccount': {
26 | 'username': 'org1-admin',
27 | 'uaID': '3059301306072a8648ce3d020106082a811ccf5501822d03420004c52c0d26c2fc5368127472d9f7cad96d5792c3c6781f8b9cedaffd14c45deb6eabec7b7598f0ae194262bd1b8b5b4fbf75ade010b1ad688fa3ba7112a00c5d72',
28 | 'pubKey': '3059301306072a8648ce3d020106082a811ccf5501822d03420004c52c0d26c2fc5368127472d9f7cad96d5792c3c6781f8b9cedaffd14c45deb6eabec7b7598f0ae194262bd1b8b5b4fbf75ade010b1ad688fa3ba7112a00c5d72',
29 | 'isAdmin': true
30 | }
31 | }
32 | }
33 | }
34 | },
35 | // user logout
36 | {
37 | url: '/auth/logout',
38 | type: 'post',
39 | response: _ => {
40 | return {
41 | 'version': '1',
42 | 'errorCode': 0,
43 | 'message': 'xxx',
44 | 'data': {
45 | }
46 | }
47 | }
48 | },
49 |
50 | // user register
51 | {
52 | url: '/auth/register',
53 | type: 'post',
54 | response: _ => {
55 | return {
56 | 'version': '1',
57 | 'errorCode': 0,
58 | 'message': 'xxx',
59 | 'data': {
60 | 'errorCode': 0,
61 | 'message': 'success',
62 | 'universalAccount': {
63 | 'username': 'xxx',
64 | 'pubKey': 'xxx',
65 | 'uaID': 'xxx'
66 | }
67 | }
68 | }
69 | }
70 | },
71 |
72 | // user changePassword
73 | {
74 | url: '/auth/changePassword',
75 | type: 'post',
76 | response: _ => {
77 | return {
78 | 'version': '1',
79 | 'errorCode': 0,
80 | 'message': 'xxx',
81 | 'data': {
82 | 'errorCode': 0,
83 | 'message': 'success'
84 | }
85 | }
86 | }
87 | },
88 |
89 | // user authCode
90 | {
91 | url: '/auth/authCode',
92 | type: 'get',
93 | response: _ => {
94 | return {
95 | version: '1.0',
96 | errorCode: 0,
97 | message: 'success',
98 | data: {
99 | errorCode: 0,
100 | message: 'success',
101 | authCode: {
102 | randomToken: 'ad4b480b9585eaee7368a8260e28a198119bb88073f6f3b1aa03ede49ef1214e',
103 | imageBase64: 'iVBORw0KGgoAAAANSUhEUgAAAJsAAAA8CAIAAAD+Gl+NAAADQUlEQVR42u3cMW7kMAwFUB1im9S5xQLpUi9yhhxiq82Vttw+N0q53WSAAQzDGtGfFClSGgpqknFsj58lk7Sd8vX/kn2lXi6N9u/zL/FjNqN2Pc6yvq2hIJyJOkyxf20M0XQNPkxvPUVXa0Ugl6gpmi2AaIZIa4pmGrOgqEWEnc1fNFuKZkvRbCmaLUVTdJ4mvllRt/ffP/c9Rc/b25/XQwf/8OXXE9gF+dgB8m5P0dCoxLic1zVF2ZzBUT2vo1zUbZIMghrTdRrR7WqHc+KiMs4UlYvuQ9Mxorj6o4se2EDUQ7LRGQSJUVuurA19PP+49YlFa7YNDxGtc0dHUdaStWKrLyJKSNOiNerhN1qoBBjrruKpqKJricaJiFrko0h8JBMFObVQI4oqopqWGkBUlmi/q5so8ik+6yoWBdUTGBpsMlFk/LmI0oV7Lip9A+AUTHfi9Rmj3NwUR6W995Z2xd56E2OiXDdR2WKnl1LEW/bYm3gG3lwXj3W1RAU1ffWcFcxzrn1YuOuTj4oX47pa1CJ66oIWES/0JlNYURy1dZdGsXjUCoy1khlQ0aeuq4tKXFbrBexQb5sQV3rFrnRk5yYqiHjrL9bSou/VyKbT1iklTlL36wFFkSg9kCiozgpoZdUG4gIJPpoEjsj6/COW6Xoj2MUVrAhqJaa6ySg4RokhSAxWVg7tL8qt8Q6oCI4UPe3c8N7hjre6qEXhXlbj1c1e6MgoxKwrdtVF5T6iPaB2j4iCj48HerqTeLahp6SgVV5QfJYMryIJXtt1fktCxnZ6vOxKRZ3Pj9U2BGTcKuD4FvBFdALp7kBMUSpkCMtJQ8r2vKxk2Rqpjq6tHaN3uAe1LGnZOuUHu7YsWVeK6PnoSM7WQRlGy9ouvUv4Ny1BVASHmPUn9SasUfHN4V8fuR4Xd06xUP+YtkMFOcWnMhEilCCcrEPcKbE/oy1QTy/eKkF4aw0lDieopWhgkd7Qc4DW5qYZo50fRaAlkkvFs4cY9yUg58Xjf8Fuk3BndUJrPeJDN5moaYBKh5GK/13H7rh1ifbstCCJvAyp1vZHKwMmkhQdLeo4QOWi1pyd2aoX6oOKLow68aw7XtS9yD7LGP0G/T53V67Tc1kAAAAASUVORK5CYII='
104 | }
105 | }
106 | }
107 | }
108 | },
109 | // user pub
110 | {
111 | url: '/auth/pub',
112 | type: 'get',
113 | response: _ => {
114 | return {
115 | version: '1.0',
116 | errorCode: 0,
117 | message: 'success',
118 | data: {
119 | errorCode: 0,
120 | message: 'success',
121 | pub: 'MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAiJ0u2c/Xze3Jv+beltOfoaavZtatVmldwctq3ItZZ5w60whzKiNdrsFcQZqj2PszLbwiYsC4fEFKQIcaTiHCXDSQ1Km25ay8/c+NKprl/ruevGB1pXDnQNZhQqoaghfzijTX2bl6DJqCnuSV46sCKgKfyKm3PNPcsMUxYWC1283an3cviUvdSnZyXHN++T2Otw77EVm55CNuDpX5MkOOIPTMSAxzweC9n9dJf5sGbmzqK2Yx8Jp/9vUA+jqR8ljqKJ7Q6bLVW3/xuqAN542U8a3dvPY3RVAkLVxgnl7UIfQl5PcBIxd4W3NZM6da/ZGmp76MAq/hxpceoU7DmQntkP9mX9ExtzcUTNFTm+LERwR530YRx4P7QB3EAAujEklZrlhXVwNa3phXnZRJWm4nuJ3qQB0I2VIw9q247aSLWEkoXQWu9CyRWzt7hmxgspwCYwsMdkvs0t8zv5L1LK0i8qXLHQCrasHkoJQ16+aztSDFmrAhJKtC4JN+ACnR1kMXAz/r2o3Y+pCO/2eBSDllsYSwCMRcgFwGvmutSD5dLes+zFZusxTRZ6vVnnnob+fOZ0NAdEDG9QY4UZoUxMjqSqM2db9jQ67QlcuMuEsc7uQ7T5mWlNORBnEVCz/UIjvFKnw7XnvGWcT/hKTPKYbgkqOJ/KQ05DoF/W3VHU+inPMCAwEAAQ=='
122 | }
123 | }
124 | }
125 | },
126 | {
127 | url: '/auth/need-mail-auth',
128 | type: 'get',
129 | response: _ => {
130 | return {
131 | version: '1.0',
132 | errorCode: 0,
133 | message: 'success',
134 | data: true
135 | }
136 | }
137 | },
138 | {
139 | url: '/auth/mail-code',
140 | type: 'post',
141 | response: _ => {
142 | return {
143 | version: '1.0',
144 | errorCode: 0,
145 | message: 'success'
146 | }
147 | }
148 | }
149 | ]
150 |
--------------------------------------------------------------------------------
/src/utils/pem.js:
--------------------------------------------------------------------------------
1 | import { ec as EC } from 'elliptic'
2 | import { keccak256 } from 'js-sha3'
3 | import { sm2 as SM2 } from 'sm-crypto'
4 | import { sm3Hex } from '@/utils/sm3.js'
5 |
6 | const CERT_PATTERN =
7 | '-----BEGIN\\s+.*CERTIFICATE[^-]*-+(?:\\s|\\r|\\n)+' + // Header
8 | '([A-Za-z0-9+/=\\r\\n]+)' + // Base64 text
9 | '-----END\\s+.*CERTIFICATE[^-]*-+' // Footer
10 |
11 | const SEC_KEY_PATTERN =
12 | '-----BEGIN\\s+.*PRIVATE\\s+KEY[^-]*-+(?:\\s|\\r|\\n)+' + // Header
13 | '([A-Za-z0-9+/=\\r\\n]+)' + // Base64 text
14 | '-----END\\s+.*PRIVATE\\s+KEY[^-]*-+' // Footer
15 | const PUB_KEY_PATTERN =
16 | '-----BEGIN\\s+.*PUBLIC\\s+KEY[^-]*-+(?:\\s|\\r|\\n)+' + // Header
17 | '([A-Za-z0-9+/=\\r\\n]+)' + // Base64 text
18 | '-----END\\s+.*PUBLIC\\s+KEY[^-]*-+' // Footer
19 |
20 | export const pem = {
21 | isCertFormat(content) {
22 | return content.search(CERT_PATTERN) === 0
23 | },
24 |
25 | isSecKeyFormat(content) {
26 | return content.search(SEC_KEY_PATTERN) === 0
27 | },
28 |
29 | isPubKeyFormat(content) {
30 | return content.search(PUB_KEY_PATTERN) === 0
31 | }
32 | }
33 |
34 | // ECDSA
35 |
36 | const ecdsaSecPemPrefix = '308184020100301006072a8648ce3d020106052b8104000a046d306b0201010420'
37 | const ecdsaPubPemPrefix = '3056301006072a8648ce3d020106052b8104000a034200'
38 |
39 | const ecdsaSecPemFingerprint = '020100301006072a8648ce3d020106052b8104000a'
40 |
41 | function getPubKeyHexFromECDSASecPem(secKeyContent) {
42 | var base64Content = secKeyContent.substr(0, secKeyContent.lastIndexOf('-----END')).replace('\n', '').replace('-----BEGIN PRIVATE KEY-----', '')
43 |
44 | var buffer = Buffer.from(base64Content, 'base64')
45 | var hexString = buffer.toString('hex')
46 | var pubKeyHex = hexString.substr(hexString.length - 130, 130)
47 | return pubKeyHex
48 | }
49 |
50 | function buildECDSAPubKeyPem(pubKeyHex) {
51 | var asn1HexString = ecdsaPubPemPrefix + pubKeyHex
52 | var base64String = Buffer.from(asn1HexString, 'hex').toString('base64')
53 |
54 | return '-----BEGIN PUBLIC KEY-----\n' + base64String + '\n-----END PUBLIC KEY-----\n'
55 | }
56 |
57 | function ecdsaPub2Addr(pubKeyHex) {
58 | var pubKeyHexWithoutPrefix = pubKeyHex.substr(2, 128) // No prefix 04
59 | var address = '0x' + keccak256(Uint8Array.from(Buffer.from(pubKeyHexWithoutPrefix, 'hex'))).substr(24, 40)
60 | return address
61 | }
62 |
63 | export const ecdsa = {
64 | generateSecPem() {
65 | const secp256k1 = new EC('secp256k1')
66 | var keyPair = secp256k1.genKeyPair()
67 |
68 | var pubKey = keyPair.getPublic('hex')
69 | var secKey = keyPair.getPrivate('hex')
70 |
71 | var asn1HexString = ecdsaSecPemPrefix + secKey + 'a144034200' + pubKey
72 | var base64String = Buffer.from(asn1HexString, 'hex').toString('base64')
73 |
74 | return '-----BEGIN PRIVATE KEY-----\n' + base64String + '\n-----END PRIVATE KEY-----\n'
75 | },
76 | isSecPem(secPem) {
77 | if (!pem.isSecKeyFormat(secPem)) {
78 | return false
79 | }
80 |
81 | var base64Content = secPem.replace('\n', '').replace('-----BEGIN PRIVATE KEY-----', '')
82 |
83 | var buffer = Buffer.from(base64Content, 'base64')
84 | var hexString = buffer.toString('hex')
85 |
86 | return hexString.includes(ecdsaSecPemFingerprint)
87 | },
88 | build(secPem) {
89 | var pubKeyHex = getPubKeyHexFromECDSASecPem(secPem)
90 |
91 | return {
92 | secPem: secPem,
93 | pubPem: buildECDSAPubKeyPem(pubKeyHex),
94 | address: ecdsaPub2Addr(pubKeyHex)
95 | }
96 | }
97 | }
98 |
99 | // SM2
100 |
101 | const sm2SecPemPrefix = '308187020100301306072a8648ce3d020106082a811ccf5501822d046d306b0201010420'
102 | const sm2PubPemPrefix = '3059301306072a8648ce3d020106082a811ccf5501822d034200'
103 |
104 | const sm2SecPemFingerprint = '020100301306072a8648ce3d020106082a811ccf5501822d04'
105 |
106 | function getPubKeyHexFromSM2SecPem(secKeyContent) {
107 | var base64Content = secKeyContent.substr(0, secKeyContent.lastIndexOf('-----END')).replace('\n', '').replace('-----BEGIN PRIVATE KEY-----', '')
108 |
109 | var buffer = Buffer.from(base64Content, 'base64')
110 | var hexString = buffer.toString('hex')
111 | var pubKeyHex = hexString.substr(hexString.length - 130, 130)
112 | return pubKeyHex
113 | }
114 |
115 | function buildSM2PubKeyPem(pubKeyHex) {
116 | var asn1HexString = sm2PubPemPrefix + pubKeyHex
117 | var base64String = Buffer.from(asn1HexString, 'hex').toString('base64')
118 |
119 | return '-----BEGIN PUBLIC KEY-----\n' + base64String + '\n-----END PUBLIC KEY-----\n'
120 | }
121 |
122 | function sm2Pub2Addr(pubKeyHex) {
123 | var pubKeyHexWithoutPrefix = pubKeyHex.substr(2, 128)
124 |
125 | var address = '0x' + sm3Hex(pubKeyHexWithoutPrefix).substr(24, 40)
126 | return address
127 | }
128 |
129 | export const sm2 = {
130 | generateSecPem() {
131 | const keyPair = SM2.generateKeyPairHex()
132 |
133 | var asn1HexString = sm2SecPemPrefix + keyPair.privateKey + 'a144034200' + keyPair.publicKey
134 | var base64String = Buffer.from(asn1HexString, 'hex').toString('base64')
135 |
136 | return '-----BEGIN PRIVATE KEY-----\n' + base64String + '\n-----END PRIVATE KEY-----\n'
137 | },
138 | isSecPem(secPem) {
139 | if (!pem.isSecKeyFormat(secPem)) {
140 | return false
141 | }
142 |
143 | var base64Content = secPem.replace('\n', '').replace('-----BEGIN PRIVATE KEY-----', '')
144 |
145 | var buffer = Buffer.from(base64Content, 'base64')
146 | var hexString = buffer.toString('hex')
147 |
148 | return hexString.includes(sm2SecPemFingerprint)
149 | },
150 | build(secPem) {
151 | var pubKeyHex = getPubKeyHexFromSM2SecPem(secPem)
152 |
153 | return {
154 | secPem: secPem,
155 | pubPem: buildSM2PubKeyPem(pubKeyHex),
156 | address: sm2Pub2Addr(pubKeyHex)
157 | }
158 | }
159 |
160 | }
161 |
--------------------------------------------------------------------------------