You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by ju...@apache.org on 2020/06/17 03:11:37 UTC

[incubator-apisix-dashboard] branch master updated: Feat master (#263)

This is an automated email from the ASF dual-hosted git repository.

juzhiyuan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-apisix-dashboard.git


The following commit(s) were added to refs/heads/master by this push:
     new d71816d  Feat master (#263)
d71816d is described below

commit d71816d8a884d99193798dea706d7785a3f1179e
Author: 琚致远 <ju...@apache.org>
AuthorDate: Wed Jun 17 11:09:57 2020 +0800

    Feat master (#263)
    
    * remove outdated codes
    
    * feat: merged next
    
    * feat: update action
    
    * feat: update README
    
    * Delete .DS_Store
---
 CHANGELOG.md => .asf.yaml                          |    35 +-
 .browserslistrc                                    |     2 -
 .dockerignore                                      |     1 +
 .editorconfig                                      |    31 +-
 .env.development                                   |    11 -
 .env.production                                    |     5 -
 .eslintignore                                      |     7 +-
 .eslintrc.js                                       |    43 +-
 .github/workflows/api_ut.yml                       |    18 +
 .gitignore                                         |    52 +-
 .prettierignore                                    |    21 +
 .prettierrc.js                                     |     5 +
 .stylelintrc.js                                    |     5 +
 DISCLAIMER                                         |     5 -
 Dockerfile                                         |    19 +
 LICENSE                                            |   206 -
 NOTICE                                             |     5 -
 README-dashboard.md                                |    47 +
 README.md                                          |   112 +-
 api/Dockerfile                                     |    49 +
 src/App.vue => api/README.md                       |    15 +-
 api/build.sh                                       |    30 +
 api/conf.json                                      |    19 +
 api/conf/conf.go                                   |   104 +
 api/conf/conf.json                                 |    19 +
 src/api/schema/consumers.ts => api/conf/mysql.go   |    52 +-
 api/docker-compose.yml                             |     9 +
 api/errno/error.go                                 |    98 +
 postcss.config.js => api/filter/cors.go            |    19 +-
 api/filter/logging.go                              |    84 +
 api/filter/recover.go                              |   120 +
 .../schema/plugins.ts => api/filter/request_id.go  |    30 +-
 api/go.mod                                         |    15 +
 api/go.sum                                         |    89 +
 api/log/log.go                                     |   147 +
 api/main.go                                        |    65 +
 src/api/users.ts => api/route/healthz.go           |    35 +-
 api/route/plugin.go                                |    58 +
 api/route/route.go                                 |   242 +
 api/route/ssl.go                                   |   153 +
 api/script/db/schema.sql                           |    31 +
 src/store/index.ts => api/service/base.go          |    43 +-
 api/service/plugin.go                              |    61 +
 api/service/route.go                               |   560 +
 api/service/route_test.go                          |   139 +
 api/service/ssl.go                                 |   300 +
 babel.config.js => api/utils/copy.go               |    27 +-
 api/utils/http.go                                  |   113 +
 compose/README.md                                  |    56 +
 compose/apisix_conf/config.yaml                    |   137 +
 compose/docker-compose.yml                         |   124 +
 compose/grafana_conf/config/grafana.ini            |   756 +
 .../dashboards/apisix_http_prometheus.json         |   956 +
 .../grafana_conf/provisioning/dashboards/all.yaml  |    11 +
 .../grafana_conf/provisioning/datasources/all.yaml |     9 +
 compose/manager_conf/build.sh                      |    20 +
 compose/pics/grafana_1.png                         |   Bin 0 -> 57816 bytes
 compose/pics/grafana_2.png                         |   Bin 0 -> 127932 bytes
 compose/pics/grafana_3.png                         |   Bin 0 -> 103384 bytes
 compose/pics/grafana_4.png                         |   Bin 0 -> 82558 bytes
 compose/pics/grafana_5.png                         |   Bin 0 -> 105366 bytes
 compose/pics/grafana_6.png                         |   Bin 0 -> 179132 bytes
 compose/pics/login.png                             |   Bin 0 -> 84958 bytes
 compose/prometheus_conf/prometheus.yml             |    23 +
 config/config.ts                                   |    48 +
 config/defaultSettings.ts                          |    20 +
 config/proxy.ts                                    |    30 +
 config/routes.ts                                   |    76 +
 docker/nginx.conf                                  |    21 +
 jest.config.js                                     |     9 +
 jsconfig.json                                      |    10 +
 licenses/LICENSE.ALv2                              |   201 -
 licenses/LICENSE.vue-typescript-admin-template     |    21 -
 mock/notices.ts                                    |   105 +
 mock/route.ts                                      |     5 +
 mock/user.ts                                       |   154 +
 netlify.toml                                       |    13 +
 package.json                                       |   171 +-
 public/favicon.ico                                 |   Bin 9662 -> 0 bytes
 public/favicon.png                                 |   Bin 0 -> 85376 bytes
 public/home_bg.png                                 |   Bin 0 -> 203330 bytes
 public/icons/icon-128x128.png                      |   Bin 0 -> 1329 bytes
 public/icons/icon-192x192.png                      |   Bin 0 -> 1856 bytes
 public/icons/icon-512x512.png                      |   Bin 0 -> 5082 bytes
 public/img/icons/favicon-16x16.png                 |   Bin 9662 -> 0 bytes
 public/img/icons/favicon-32x32.png                 |   Bin 9662 -> 0 bytes
 public/index.html                                  |    43 -
 public/pro_icon.svg                                |     1 +
 public/robots.txt                                  |     2 -
 src/access.ts                                      |     6 +
 src/api/schema/routes.ts                           |    51 -
 src/api/schema/schema.ts                           |    24 -
 src/api/schema/services.ts                         |    51 -
 src/api/schema/ssl.ts                              |    52 -
 src/api/schema/upstream.ts                         |    50 -
 src/api/types.d.ts                                 |    78 -
 src/app.tsx                                        |    92 +
 src/assets/logo.svg                                |    21 +
 src/components/Breadcrumb/index.vue                |   125 -
 src/components/Footer/index.tsx                    |    17 +
 src/components/Hamburger/index.vue                 |    75 -
 src/components/HeaderDropdown/index.less           |    16 +
 src/components/HeaderDropdown/index.tsx            |    19 +
 src/components/HeaderSearch/index.vue              |   244 -
 src/components/LangSelect/index.vue                |    77 -
 src/components/NoticeIcon/NoticeList.less          |   103 +
 src/components/NoticeIcon/NoticeList.tsx           |   113 +
 src/components/NoticeIcon/index.less               |    31 +
 src/components/NoticeIcon/index.tsx                |   124 +
 src/components/PageLoading/index.tsx               |     5 +
 src/components/Pagination/index.vue                |   104 -
 src/components/PluginDialog/index.vue              |   523 -
 src/components/PluginForm/PluginForm.tsx           |   192 +
 src/components/PluginForm/README.md                |     9 +
 src/components/PluginForm/data.ts                  |   100 +
 src/components/PluginForm/index.ts                 |     3 +
 src/components/PluginForm/locales/en-US.ts         |   152 +
 src/components/PluginForm/locales/zh-CN.ts         |   152 +
 src/components/PluginForm/service.ts               |     5 +
 src/components/PluginForm/transformer.ts           |    90 +
 src/components/PluginForm/typing.d.ts              |    64 +
 src/components/PluginModal/index.tsx               |    32 +
 src/components/RightContent/AvatarDropdown.tsx     |    94 +
 src/components/RightContent/index.less             |    82 +
 src/components/RightContent/index.tsx              |    51 +
 src/components/Screenfull/index.vue                |    79 -
 src/components/VarArgs/index.vue                   |   128 -
 src/e2e/__mocks__/antd-pro-merge-less.js           |     1 +
 src/e2e/baseLayout.e2e.js                          |    57 +
 src/global.less                                    |    69 +
 src/global.tsx                                     |    83 +
 src/lang/en.ts                                     |    94 -
 src/lang/index.ts                                  |    67 -
 src/lang/zh.ts                                     |   102 -
 src/layout/components/AppMain.vue                  |    83 -
 src/layout/components/Navbar/index.vue             |   195 -
 src/layout/components/Sidebar/SidebarItem.vue      |   209 -
 src/layout/components/Sidebar/SidebarItemLink.vue  |    56 -
 src/layout/components/Sidebar/SidebarLogo.vue      |   123 -
 src/layout/components/Sidebar/index.vue            |   156 -
 src/layout/components/TagsView/ScrollPane.vue      |   113 -
 src/layout/components/TagsView/index.vue           |   359 -
 src/layout/components/index.ts                     |    28 -
 src/layout/index.vue                               |   192 -
 src/layout/mixin/resize.ts                         |    79 -
 src/locales/en-US.ts                               |    23 +
 src/locales/en-US/component.ts                     |    39 +
 src/locales/en-US/globalHeader.ts                  |    17 +
 src/locales/en-US/menu.ts                          |    60 +
 src/locales/en-US/pwa.ts                           |     6 +
 src/locales/en-US/setting.ts                       |     4 +
 src/locales/en-US/settingDrawer.ts                 |    31 +
 src/locales/zh-CN.ts                               |    23 +
 src/locales/zh-CN/component.ts                     |    39 +
 src/locales/zh-CN/globalHeader.ts                  |    17 +
 src/locales/zh-CN/menu.ts                          |    61 +
 src/locales/zh-CN/pwa.ts                           |     6 +
 src/locales/zh-CN/setting.ts                       |    12 +
 src/locales/zh-CN/settingDrawer.ts                 |    31 +
 src/main.ts                                        |    57 -
 src/manifest.json                                  |    22 +
 src/pages/404.tsx                                  |    18 +
 src/pages/Metrics/Metrics.tsx                      |    54 +
 src/pages/Routes/Create.less                       |   112 +
 src/pages/Routes/Create.tsx                        |   223 +
 src/pages/Routes/List.tsx                          |    82 +
 .../Routes/components/ActionBar/ActionBar.tsx      |    46 +
 src/pages/Routes/components/ActionBar/index.ts     |     1 +
 .../Routes/components/CreateStep3/CreateStep3.tsx  |    96 +
 .../Routes/components/CreateStep3/PluginCard.tsx   |    26 +
 .../Routes/components/CreateStep3/PluginDrawer.tsx |    73 +
 src/pages/Routes/components/CreateStep3/index.ts   |     1 +
 .../Routes/components/CreateStep4/CreateStep4.tsx  |    35 +
 src/pages/Routes/components/CreateStep4/index.ts   |     1 +
 src/pages/Routes/components/PanelSection/index.tsx |    16 +
 .../Routes/components/ResultView/ResultView.tsx    |    24 +
 src/pages/Routes/components/ResultView/index.ts    |     1 +
 .../Routes/components/Step1/MatchingRulesView.tsx  |   214 +
 src/pages/Routes/components/Step1/MetaView.tsx     |    27 +
 .../Routes/components/Step1/RequestConfigView.tsx  |   201 +
 src/pages/Routes/components/Step1/index.tsx        |    41 +
 .../components/Step2/HttpHeaderRewriteView.tsx     |   166 +
 .../Routes/components/Step2/RequestRewriteView.tsx |   174 +
 src/pages/Routes/components/Step2/index.tsx        |    20 +
 src/pages/Routes/constants.ts                      |    61 +
 src/pages/Routes/service.ts                        |    24 +
 src/pages/Routes/transform.ts                      |   176 +
 src/pages/Routes/typing.d.ts                       |   125 +
 src/pages/Setting/Setting.tsx                      |   102 +
 src/pages/Setting/index.ts                         |     2 +
 src/pages/Setting/service.ts                       |     7 +
 src/pages/Setting/style.less                       |   109 +
 src/pages/Setting/typingd.d.ts                     |     9 +
 src/pages/document.ejs                             |   193 +
 src/pages/ssl/Create.less                          |   101 +
 src/pages/ssl/Create.tsx                           |    65 +
 src/pages/ssl/List.tsx                             |   110 +
 src/pages/ssl/components/CertificateForm/index.tsx |    75 +
 .../ssl/components/CertificateUploader/index.tsx   |    97 +
 src/pages/ssl/components/Step1/index.tsx           |   104 +
 src/pages/ssl/components/Step2/index.tsx           |    32 +
 src/pages/ssl/components/Step3/index.tsx           |    36 +
 src/pages/ssl/service.ts                           |    78 +
 src/pages/ssl/typing.d.ts                          |    18 +
 src/permission.ts                                  |   102 -
 src/router/index.ts                                |    82 -
 src/router/modules/schema/consumers.ts             |    56 -
 src/router/modules/schema/routes.ts                |    56 -
 src/router/modules/schema/services.ts              |    56 -
 src/router/modules/schema/ssl.ts                   |    56 -
 src/router/modules/schema/upstream.ts              |    56 -
 src/service-worker.js                              |    70 +
 src/services/API.d.ts                              |    24 +
 src/services/login.ts                              |    20 +
 src/services/user.ts                               |    23 +
 src/settings.ts                                    |    48 -
 src/shims.d.ts                                     |    36 -
 src/store/modules/app.ts                           |   116 -
 src/store/modules/permission.ts                    |    80 -
 src/store/modules/settings.ts                      |    62 -
 src/store/modules/tags-view.ts                     |   165 -
 src/store/modules/user.ts                          |   169 -
 src/styles/_mixins.scss                            |    31 -
 src/styles/_svgicon.scss                           |    56 -
 src/styles/_transition.scss                        |    73 -
 src/styles/_variables.scss                         |    56 -
 src/styles/_variables.scss.d.ts                    |    33 -
 src/styles/element-variables.scss                  |    47 -
 src/styles/element-variables.scss.d.ts             |    31 -
 src/styles/index.scss                              |   183 -
 src/transforms/global.ts                           |    26 +
 src/typings.d.ts                                   |    40 +
 src/utils/cookies.ts                               |    44 -
 src/utils/index.ts                                 |   112 -
 src/utils/permission.ts                            |    39 -
 src/utils/request.ts                               |    59 -
 src/utils/scroll-to.ts                             |    74 -
 src/utils/validate.ts                              |    39 -
 src/views/login/index.vue                          |   228 -
 src/views/schema/consumers/edit.vue                |   266 -
 src/views/schema/consumers/list.vue                |   222 -
 src/views/schema/routes/edit.vue                   |   549 -
 src/views/schema/routes/list.vue                   |   252 -
 src/views/schema/service/edit.vue                  |   300 -
 src/views/schema/service/list.vue                  |   215 -
 src/views/schema/ssl/edit.vue                      |   191 -
 src/views/schema/ssl/list.vue                      |   206 -
 src/views/schema/upstream/edit.vue                 |   434 -
 src/views/schema/upstream/list.vue                 |   289 -
 tests/PuppeteerEnvironment.js                      |    41 +
 tests/beforeTest.js                                |    39 +
 tests/getBrowser.js                                |    45 +
 tests/run-tests.js                                 |    52 +
 tsconfig.json                                      |    49 +-
 vue.config.js                                      |    84 -
 yarn.lock                                          | 22870 +++++++++++++------
 256 files changed, 27441 insertions(+), 16413 deletions(-)

diff --git a/CHANGELOG.md b/.asf.yaml
similarity index 62%
rename from CHANGELOG.md
rename to .asf.yaml
index f2eb346..fe73d20 100644
--- a/CHANGELOG.md
+++ b/.asf.yaml
@@ -1,5 +1,3 @@
-
-<!--
 #
 # Licensed to the Apache Software Foundation (ASF) under one or more
 # contributor license agreements.  See the NOTICE file distributed with
@@ -16,21 +14,24 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
--->
-
-# Table of Contents
-
-- [1.0.0](#100)
-
-## 1.0.0
 
-This release is mainly to build some basic panels and resolve License issue.
+github:
+    description: Dashboard for Apache APISIX
+    homepage: https://apisix.apache.org/
+    labels:
+      - dashboard
+      - api
+      - api-management
+      - apisix
+      - devops
+      - docker
 
-### Core
-- Dashboard initial. [#1](https://github.com/apache/incubator-apisix-dashboard/pull/1)
-- Resolve licence issues.
-- Remove unused files from the Dashboard boilerplate.
-- Support panel to list, create and modify Route, Consumer, Service, SSL and Upstream.
-- Support custom configuration for Plugin dialog.
+    enabled_merge_buttons:
+      squash:  true
+      merge:   false
+      rebase:  false
 
-[Back to TOC](#table-of-contents)
+    notifications:
+      commits:      notifications@apisix.apache.org
+      issues:       notifications@apisix.apache.org
+      pullrequests: notifications@apisix.apache.org
diff --git a/.browserslistrc b/.browserslistrc
deleted file mode 100644
index 3a455dd..0000000
--- a/.browserslistrc
+++ /dev/null
@@ -1,2 +0,0 @@
-> 1%
-last 2 versions
\ No newline at end of file
diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..3c3629e
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1 @@
+node_modules
diff --git a/.editorconfig b/.editorconfig
index 9b38463..7e3649a 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -1,35 +1,16 @@
 # http://editorconfig.org
-
-# top-most EditorConfig file
 root = true
 
-# Unix-style newlines with a newline ending every file
 [*]
-charset = utf-8
-end_of_line = lf
-insert_final_newline = true
-trim_trailing_whitespace = true
-
-# Indentation override for js(x), ts(x) and vue files
-[*.{js,jsx,ts,tsx,vue}]
-indent_size = 2
 indent_style = space
-
-# Indentation override for css related files
-[*.{css,styl,scss,less,sass}]
 indent_size = 2
-indent_style = space
-
-# Indentation override for html files
-[*.html]
-indent_size = 2
-indent_style = space
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
 
-# Trailing space override for markdown file
 [*.md]
 trim_trailing_whitespace = false
 
-# Indentation override for config files
-[*.{json,yml}]
-indent_size = 2
-indent_style = space
\ No newline at end of file
+[Makefile]
+indent_style = tab
diff --git a/.env.development b/.env.development
deleted file mode 100644
index 1e3d951..0000000
--- a/.env.development
+++ /dev/null
@@ -1,11 +0,0 @@
-# Base api
-VUE_APP_BASE_API = ''
-
-# vue-cli uses the VUE_CLI_BABEL_TRANSPILE_MODULES environment variable,
-# to control whether the babel-plugin-dynamic-import-node plugin is enabled.
-# It only does one thing by converting all import() to require().
-# This configuration can significantly increase the speed of hot updates,
-# when you have a large number of pages.
-# Detail:  https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/babel-preset-app/index.js
-
-VUE_CLI_BABEL_TRANSPILE_MODULES = true
\ No newline at end of file
diff --git a/.env.production b/.env.production
deleted file mode 100644
index edd19d0..0000000
--- a/.env.production
+++ /dev/null
@@ -1,5 +0,0 @@
-
-# Base api
-# Remeber to change this to your production server address
-# Here I used my mock server for this project
-VUE_APP_BASE_API = '/apisix/admin/'
\ No newline at end of file
diff --git a/.eslintignore b/.eslintignore
index 4bcf40f..16116a2 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -1,3 +1,4 @@
-
-dist/*.js
-tests/unit/coverage
\ No newline at end of file
+/lambda/
+/scripts
+/config
+.history
\ No newline at end of file
diff --git a/.eslintrc.js b/.eslintrc.js
index 4f0b93f..b882c20 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -1,37 +1,8 @@
 module.exports = {
-    root: true,
-    env: {
-      browser: true,
-      node: true,
-      es6: true
-    },
-    parserOptions: {
-      parser: '@typescript-eslint/parser',
-      sourceType: 'module'
-    },
-    plugins: [
-      'vue'
-    ],
-    rules: {
-      'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
-      'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
-      'space-before-function-paren': [2, 'never'],
-      'vue/array-bracket-spacing': 'error',
-      'vue/arrow-spacing': 'error',
-      'vue/block-spacing': 'error',
-      'vue/brace-style': 'error',
-      'vue/comma-dangle': 'error',
-      'vue/component-name-in-template-casing': 'error',
-      'vue/eqeqeq': 'error',
-      'vue/key-spacing': 'error',
-      'vue/match-component-file-name': 'error',
-      'vue/object-curly-spacing': 'error',
-      "camelcase": 'off'
-    },
-    'extends': [
-      'eslint:recommended',
-      'plugin:vue/recommended',
-      '@vue/standard',
-      '@vue/typescript'
-    ]
-  }
\ No newline at end of file
+  extends: [require.resolve('@umijs/fabric/dist/eslint')],
+  globals: {
+    ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION: true,
+    page: true,
+    REACT_APP_ENV: true,
+  },
+};
diff --git a/.github/workflows/api_ut.yml b/.github/workflows/api_ut.yml
new file mode 100644
index 0000000..2b3c4bb
--- /dev/null
+++ b/.github/workflows/api_ut.yml
@@ -0,0 +1,18 @@
+name: API unit test
+
+on:
+  push:
+    branches: 
+      - master
+      - manager
+  pull_request:
+    branches:
+      - master
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v2
+    - name: use docker-compose in api
+      run: cd ./api && docker-compose up
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 2b33ebe..9caa192 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,21 +1,43 @@
-.DS_Store
-node_modules
-/dist
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
 
-# local env files
-.env.local
-.env.*.local
+# dependencies
+**/node_modules
+# roadhog-api-doc ignore
+/src/utils/request-temp.js
+_roadhog-api-doc
 
-# Log files
+# production
+/dist
+/.vscode
+
+# misc
+.DS_Store
 npm-debug.log*
-yarn-debug.log*
-yarn-error.log*
+yarn-error.log
 
-# Editor directories and files
+/coverage
 .idea
+package-lock.json
+*bak
 .vscode
-*.suo
-*.ntvs*
-*.njsproj
-*.sln
-*.sw*
\ No newline at end of file
+
+# visual studio code
+.history
+*.log
+functions/*
+.temp/**
+
+# umi
+.umi
+.umi-production
+
+# screenshot
+screenshot
+.firebase
+.eslintcache
+
+build
+
+/compose/**/*.log
+/compose/**/nginx.pid
+/compose/etcd_data
\ No newline at end of file
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 0000000..87715a7
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,21 @@
+**/*.svg
+package.json
+.umi
+.umi-production
+/dist
+.dockerignore
+.DS_Store
+.eslintignore
+*.png
+*.toml
+docker
+.editorconfig
+Dockerfile*
+.gitignore
+.prettierignore
+LICENSE
+.eslintcache
+*.lock
+yarn-error.log
+.history
+CNAME
diff --git a/.prettierrc.js b/.prettierrc.js
new file mode 100644
index 0000000..7b597d7
--- /dev/null
+++ b/.prettierrc.js
@@ -0,0 +1,5 @@
+const fabric = require('@umijs/fabric');
+
+module.exports = {
+  ...fabric.prettier,
+};
diff --git a/.stylelintrc.js b/.stylelintrc.js
new file mode 100644
index 0000000..c203078
--- /dev/null
+++ b/.stylelintrc.js
@@ -0,0 +1,5 @@
+const fabric = require('@umijs/fabric');
+
+module.exports = {
+  ...fabric.stylelint,
+};
diff --git a/DISCLAIMER b/DISCLAIMER
deleted file mode 100644
index 70fc20c..0000000
--- a/DISCLAIMER
+++ /dev/null
@@ -1,5 +0,0 @@
-Apache APISIX Dashboard (incubating) is an effort undergoing incubation at The Apache Software Foundation (ASF), sponsored by the Apache Incubator PMC.
-Incubation is required of all newly accepted projects until a further review indicates that the infrastructure,
-communications, and decision making process have stabilized in a manner consistent with other successful ASF projects.
-While incubation status is not necessarily a reflection of the completeness or stability of the code,
-it does indicate that the project has yet to be fully endorsed by the ASF.
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..72ac8d3
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,19 @@
+# phase-build
+FROM node:12-alpine as builder
+
+WORKDIR /usr/src/app/
+USER root
+
+COPY package.json /usr/src/app/
+RUN yarn
+
+COPY . /usr/src/app/
+RUN yarn build && rm -rf /usr/src/app/node_modules
+
+# phase-run
+FROM nginx:1.16-alpine
+
+COPY ./docker/nginx.conf /etc/nginx/conf.d/default.conf
+COPY --from=builder /usr/src/app/dist /usr/share/nginx/html/dashboard
+
+EXPOSE 80
diff --git a/LICENSE b/LICENSE
deleted file mode 100644
index 6da9712..0000000
--- a/LICENSE
+++ /dev/null
@@ -1,206 +0,0 @@
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "[]"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright [yyyy] [name of copyright owner]
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-
-=======================================================================
-
-The Apache APISIX project bundles vue-typescript-admin-template under the MIT license.
-For details, see licenses/LICENSE.vue-typescript-admin-template.
diff --git a/NOTICE b/NOTICE
deleted file mode 100644
index f0a4483..0000000
--- a/NOTICE
+++ /dev/null
@@ -1,5 +0,0 @@
-Apache APISIX Dashboard (incubating)
-Copyright 2020 The Apache Software Foundation
-
-This product includes software developed at
-The Apache Software Foundation (http://www.apache.org/).
diff --git a/README-dashboard.md b/README-dashboard.md
new file mode 100644
index 0000000..74f8748
--- /dev/null
+++ b/README-dashboard.md
@@ -0,0 +1,47 @@
+# READMD for Dashboard
+
+This project is initialized with [Ant Design Pro](https://pro.ant.design). Follow is the quick guide for how to use.
+
+## Environment Prepare
+
+1. Make sure you have `Node.js` installed on your machine.
+2. Install [yarn](https://yarnpkg.com/).
+3. Install `node_modules`:
+
+```bash
+$ yarn
+```
+
+### Start project
+
+```bash
+yarn start:no-mock
+```
+
+### Build project
+
+```bash
+yarn build
+```
+
+### Check code style
+
+```bash
+yarn lint
+```
+
+You can also use script to auto fix some lint error:
+
+```bash
+yarn lint:fix
+```
+
+### Test code
+
+```bash
+yarn test
+```
+
+## More
+
+You can view full document on our [official website](https://pro.ant.design). And welcome any feedback in our [github](https://github.com/ant-design/ant-design-pro).
diff --git a/README.md b/README.md
index 438999c..8b13b91 100644
--- a/README.md
+++ b/README.md
@@ -1,110 +1,12 @@
-<!--
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements.  See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License.  You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
--->
+# Apache APISIX Dashboard
 
-# apisix_dashboard
+Dashboard for [Apache APISIX](https://github.com/apache/incubator-apisix-dashboard)
 
-## Overview
+## Deploy with Docker
 
-Dashboard for APISIX & based on ElementUI.
+Please refer to [Deploy with Docker README](./compose/README.md)
 
-## Documentation
+## More
 
-[Docs](https://armour.github.io/vue-typescript-admin-docs)
-
-[Vue Config Docs](https://cli.vuejs.org/zh/config/#publicpath)
-
-## Project Structure
-
-```bash
-├── public                     # public static assets
-│   │── img/                   # static image files
-│   │── favicon.ico            # favicon
-│   │── index.html             # index.html template
-│   └── robots.txt             # robots file
-├── src                        # main source code
-│   ├── api/                   # api service
-│   ├── components/            # global components
-│   ├── lang/                  # i18n language
-│   ├── layout/                # global layout
-│   ├── router/                # router
-│   ├── store/                 # store
-│   ├── styles/                # global css
-│   ├── utils/                 # global utils
-│   ├── views/                 # views
-│   ├── App.vue                # main app component
-│   ├── main.ts                # app entry file
-│   ├── permission.ts          # permission authentication
-│   ├── settings.ts            # setting file
-│   └── shims.d.ts             # type definition shims
-├── licenses                   # license files for ALv2 and boilerplate
-├── .browserslistrc            # browserslist config file (to support Autoprefixer)
-├── .editorconfig              # editor code format consistency config
-├── .env.xxx                   # env variable configuration
-├── .eslintrc.js               # eslint config
-├── .eslintignore              # eslint ignore config
-├── .gitignore                 # git ignore config
-├── babel.config.js            # babel config
-├── LICENSE                    # license file
-├── NOTICE                     # notice file
-├── package.json               # package.json
-├── postcss.config.js          # postcss config
-├── README.md                  # some information about APISIX
-├── tsconfig.json              # typescript config
-├── vue.config.js              # vue-cli config
-└── yarn.lock                  # keep exact versions of each dependency
-```
-
-## Project setup
-> Make sure Node.js 8.12.0 or higher, and Yarn are installed on your machine: https://yarnpkg.com/en/docs/install
-
-### Install dependencies
-
-```bash
-yarn install
-```
-
-### Compiles and hot-reloads for development
-
-```bash
-yarn run serve
-```
-
-### Compiles and minifies for production
-
-```bash
-yarn run build:prod
-```
-
-### Lints and fixes files
-
-```bash
-yarn run lint
-```
-
-### Customize Vue configuration
-
-See [Configuration Reference](https://cli.vuejs.org/config/).
-
-## Browsers support
-
-Modern browsers and Internet Explorer 10+.
-
-| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="IE / Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE / Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/mas [...]
-| --------- | --------- | --------- | --------- |
-| IE10, IE11, Edge| last 2 versions| last 2 versions| last 2 versions
+1. More infomation about the frontend Dashboard, please refer to [README for Dashboard](./README-dashboard.md)
+2. If you need the dashboard built with Vue.js, please refer to [master-vue](https://github.com/apache/incubator-apisix-dashboard/tree/master-vue).
diff --git a/api/Dockerfile b/api/Dockerfile
new file mode 100644
index 0000000..85092a1
--- /dev/null
+++ b/api/Dockerfile
@@ -0,0 +1,49 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+FROM golang:1.13.8 AS build-env
+
+WORKDIR /go/src/github.com/apisix/manager-api
+COPY . .
+RUN mkdir /root/manager-api \
+    && go env -w GOPROXY=https://goproxy.io,direct \
+    && export GOPROXY=https://goproxy.io \
+    && go build -o /root/manager-api/manager-api \
+    && mv /go/src/github.com/apisix/manager-api/build.sh /root/manager-api/ \
+    && mv /go/src/github.com/apisix/manager-api/conf.json /root/manager-api/ \
+    && rm -rf /go/src/github.com/apisix/manager-api \
+    && rm -rf /etc/localtime \
+    && ln -s  /usr/share/zoneinfo/Hongkong /etc/localtime \
+    && dpkg-reconfigure -f noninteractive tzdata
+
+FROM alpine:3.11
+
+RUN mkdir /root/manager-api \
+   && apk update  \
+   && apk add ca-certificates \
+   && update-ca-certificates \
+   && apk add --no-cache libc6-compat \
+   && echo "hosts: files dns" > /etc/nsswitch.conf \
+   && rm -rf /var/cache/apk/*
+
+
+WORKDIR /root/manager-api
+COPY --from=build-env /root/manager-api/* /root/manager-api/
+COPY --from=build-env /usr/share/zoneinfo/Hongkong /etc/localtime
+EXPOSE 8080
+RUN chmod +x ./build.sh
+CMD ["/root/manager-api/build.sh"]
diff --git a/src/App.vue b/api/README.md
similarity index 78%
rename from src/App.vue
rename to api/README.md
index adf2931..24befa6 100644
--- a/src/App.vue
+++ b/api/README.md
@@ -17,17 +17,6 @@
 #
 -->
 
-<template>
-  <div id="app">
-    <router-view />
-  </div>
-</template>
+# manager-api
 
-<script lang="ts">
-import { Component, Vue } from 'vue-property-decorator'
-
-@Component({
-  name: 'App'
-})
-export default class extends Vue {}
-</script>
+This is a back-end project that the dashboard depends on, implemented through golang.
diff --git a/api/build.sh b/api/build.sh
new file mode 100644
index 0000000..086fe73
--- /dev/null
+++ b/api/build.sh
@@ -0,0 +1,30 @@
+#!/bin/sh
+#	
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with	
+# this work for additional information regarding copyright ownership.	
+# The ASF licenses this file to You under the Apache License, Version 2.0	
+# (the "License"); you may not use this file except in compliance with	
+# the License.  You may obtain a copy of the License at	
+#	
+#     http://www.apache.org/licenses/LICENSE-2.0	
+#	
+# Unless required by applicable law or agreed to in writing, software	
+# distributed under the License is distributed on an "AS IS" BASIS,	
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.	
+# See the License for the specific language governing permissions and	
+# limitations under the License.	
+#
+
+pwd=`pwd`
+
+sed -i -e "s%#mysqlAddress#%`echo $MYSQL_SERVER_ADDRESS`%g" ${pwd}/conf.json
+sed -i -e "s%#mysqlUser#%`echo $MYSQL_USER`%g" ${pwd}/conf.json
+sed -i -e "s%#mysqlPWD#%`echo $MYSQL_PASSWORD`%g" ${pwd}/conf.json
+sed -i -e "s%#syslogAddress#%`echo $SYSLOG_HOST`%g" ${pwd}/conf.json
+sed -i -e "s%#apisixBaseUrl#%`echo $APISIX_BASE_URL`%g" ${pwd}/conf.json
+sed -i -e "s%#apisixApiKey#%`echo $APISIX_API_KEY`%g" ${pwd}/conf.json
+
+cd /root/manager-api
+exec ./manager-api
+
diff --git a/api/conf.json b/api/conf.json
new file mode 100644
index 0000000..31cbcce
--- /dev/null
+++ b/api/conf.json
@@ -0,0 +1,19 @@
+{
+  "conf": {
+    "mysql":{
+      "address": "#mysqlAddress#",
+      "user": "#mysqlUser#",
+      "password": "#mysqlPWD#",
+      "maxConns": 50,
+      "maxIdleConns": 25,
+      "maxLifeTime": 10
+    },
+    "syslog": {
+      "host": "#syslogAddress#"
+    },
+    "apisix": {
+      "base_url": "#apisixBaseUrl#",
+      "api_key": "#apisixApiKey#"
+    }
+  }
+}
diff --git a/api/conf/conf.go b/api/conf/conf.go
new file mode 100644
index 0000000..5819789
--- /dev/null
+++ b/api/conf/conf.go
@@ -0,0 +1,104 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package conf
+
+import (
+	"fmt"
+	"github.com/tidwall/gjson"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"runtime"
+)
+
+const ServerPort = 8080
+const PROD = "prod"
+const BETA = "beta"
+const DEV = "dev"
+const LOCAL = "local"
+const confPath = "/root/manager-api/conf.json"
+const RequestId = "requestId"
+
+var (
+	ENV      string
+	basePath string
+	ApiKey   = "edd1c9f034335f136f87ad84b625c8f1"
+	BaseUrl  = "http://127.0.0.1:9080/apisix/admin"
+)
+
+func init() {
+	setEnvironment()
+	initMysql()
+	initApisix()
+}
+
+func setEnvironment() {
+	if env := os.Getenv("ENV"); env == "" {
+		ENV = LOCAL
+	} else {
+		ENV = env
+	}
+	_, basePath, _, _ = runtime.Caller(1)
+}
+
+func configurationPath() string {
+	if ENV == LOCAL {
+		return filepath.Join(filepath.Dir(basePath), "conf.json")
+	} else {
+		return confPath
+	}
+}
+
+type mysqlConfig struct {
+	Address  string
+	User     string
+	Password string
+
+	MaxConns     int
+	MaxIdleConns int
+	MaxLifeTime  int
+}
+
+var MysqlConfig mysqlConfig
+
+func initMysql() {
+	filePath := configurationPath()
+	if configurationContent, err := ioutil.ReadFile(filePath); err != nil {
+		panic(fmt.Sprintf("fail to read configuration: %s", filePath))
+	} else {
+		configuration := gjson.ParseBytes(configurationContent)
+		mysqlConf := configuration.Get("conf.mysql")
+		MysqlConfig.Address = mysqlConf.Get("address").String()
+		MysqlConfig.User = mysqlConf.Get("user").String()
+		MysqlConfig.Password = mysqlConf.Get("password").String()
+		MysqlConfig.MaxConns = int(mysqlConf.Get("maxConns").Int())
+		MysqlConfig.MaxIdleConns = int(mysqlConf.Get("maxIdleConns").Int())
+		MysqlConfig.MaxLifeTime = int(mysqlConf.Get("maxLifeTime").Int())
+	}
+}
+
+func initApisix() {
+	filePath := configurationPath()
+	if configurationContent, err := ioutil.ReadFile(filePath); err != nil {
+		panic(fmt.Sprintf("fail to read configuration: %s", filePath))
+	} else {
+		configuration := gjson.ParseBytes(configurationContent)
+		apisixConf := configuration.Get("conf.apisix")
+		BaseUrl = apisixConf.Get("base_url").String()
+		ApiKey = apisixConf.Get("api_key").String()
+	}
+}
diff --git a/api/conf/conf.json b/api/conf/conf.json
new file mode 100644
index 0000000..95fe86e
--- /dev/null
+++ b/api/conf/conf.json
@@ -0,0 +1,19 @@
+{
+  "conf":{
+    "mysql":{
+      "address": "127.0.0.1:3306",
+      "user": "root",
+      "password": "123456",
+      "maxConns": 50,
+      "maxIdleConns": 25,
+      "maxLifeTime": 10
+    },
+    "syslog":{
+      "host": "localhost"
+    },
+    "apisix":{
+      "base_url": "http://127.0.0.1:9080/apisix/admin",
+      "api_key": "edd1c9f034335f136f87ad84b625c8f1"
+    }
+  }
+}
\ No newline at end of file
diff --git a/src/api/schema/consumers.ts b/api/conf/mysql.go
similarity index 52%
rename from src/api/schema/consumers.ts
rename to api/conf/mysql.go
index 06b72d8..62e0f9e 100644
--- a/src/api/schema/consumers.ts
+++ b/api/conf/mysql.go
@@ -14,37 +14,33 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package conf
 
-import request from '@/utils/request'
+import (
+	"fmt"
+	"github.com/jinzhu/gorm"
+	_ "github.com/jinzhu/gorm/dialects/mysql"
+	"time"
+)
 
-import { IConsumerData } from '../types'
+var db *gorm.DB
 
-export const defaultConsumerData: IConsumerData = {
-  username: '',
-  plugins: {}
+func DB() *gorm.DB {
+	return db
 }
 
-export const updateOrCreateConsumer = (data: IConsumerData) =>
-  request({
-    url: '/consumers',
-    method: 'PUT',
-    data
-  })
+// InitializeMysql creates mysql's *sqlDB instance
+func InitializeMysql() {
+	dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=True&loc=Local", MysqlConfig.User,
+		MysqlConfig.Password, MysqlConfig.Address, "manager")
+	if tmp, err := gorm.Open("mysql", dsn); err != nil {
+		panic(fmt.Sprintf("fail to connect to DB: %s for %s", err.Error(), dsn))
+	} else {
+		db = tmp
+		db.LogMode(true)
+		db.DB().SetMaxOpenConns(MysqlConfig.MaxConns)
+		db.DB().SetMaxIdleConns(MysqlConfig.MaxIdleConns)
+		db.DB().SetConnMaxLifetime(time.Duration(MysqlConfig.MaxLifeTime) * time.Minute)
+	}
 
-export const getList = () =>
-  request({
-    url: '/consumers',
-    method: 'GET'
-  })
-
-export const get = (username: string) =>
-  request({
-    url: `/consumers/${username}`,
-    method: 'GET'
-  })
-
-export const removeConsumer = (username: string) =>
-  request({
-    url: `/consumers/${username}`,
-    method: 'DELETE'
-  })
+}
diff --git a/api/docker-compose.yml b/api/docker-compose.yml
new file mode 100644
index 0000000..79760d7
--- /dev/null
+++ b/api/docker-compose.yml
@@ -0,0 +1,9 @@
+version: "3"
+
+services:
+  manager:
+    image: golang:1.13.8
+    volumes:
+      - .:/go/src/github.com/apisix/manager-api
+    working_dir: /go/src/github.com/apisix/manager-api
+    command: go test -v github.com/apisix/manager-api/service
diff --git a/api/errno/error.go b/api/errno/error.go
new file mode 100644
index 0000000..20b7e24
--- /dev/null
+++ b/api/errno/error.go
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package errno
+
+import (
+	"encoding/json"
+	"fmt"
+)
+
+type Message struct {
+	Code string
+	Msg  string
+}
+
+var (
+	//AA 01 api-manager-api
+	SystemSuccess   = Message{"010000", "success"}
+	SystemError     = Message{"010001", "system error"}
+	BadRequestError = Message{Code: "010002", Msg: "Request format error"}
+	NotFoundError   = Message{Code: "010003", Msg: "No resources found"}
+
+	//BB 01 config module
+	ConfEnvError      = Message{"010101", "Environment variable not found: %s"}
+	ConfFilePathError = Message{"010102", "Error loading configuration file: %s"}
+
+	// BB 02 route module
+	RouteRequestError      = Message{"010201", "Route request parameters are abnormal: %s"}
+	ApisixRouteCreateError = Message{"010202", "Failed to create APISIX route: %s"}
+	DBRouteCreateError     = Message{"010203", "Route storage failure: %s"}
+	ApisixRouteUpdateError = Message{"010204", "Update APISIX routing failed: %s"}
+	ApisixRouteDeleteError = Message{"010205", "Failed to remove APISIX route: %s"}
+	DBRouteUpdateError     = Message{"010206", "Route update failed: %s"}
+	DBRouteDeleteError     = Message{"010207", "Route remove failed: %s"}
+
+	// 03 plugin module
+	ApisixPluginListError   = Message{"010301", "List APISIX plugins  failed: %s"}
+	ApisixPluginSchemaError = Message{"010301", "Find APISIX plugin schema failed: %s"}
+)
+
+type ManagerError struct {
+	TraceId string
+	Code    string
+	Msg     string
+	Data    interface{}
+}
+
+// toString
+func (e *ManagerError) Error() string {
+	return e.Msg
+}
+
+func FromMessage(m Message, args ...interface{}) *ManagerError {
+	return &ManagerError{TraceId: "", Code: m.Code, Msg: fmt.Sprintf(m.Msg, args...)}
+}
+
+func (e *ManagerError) Response() map[string]interface{} {
+	return map[string]interface{}{
+		"code": e.Code,
+		"msg":  e.Msg,
+	}
+}
+
+func (e *ManagerError) ItemResponse(data interface{}) map[string]interface{} {
+	return map[string]interface{}{
+		"code": e.Code,
+		"msg":  e.Msg,
+		"data": data,
+	}
+}
+
+func (e *ManagerError) ListResponse(count, list interface{}) map[string]interface{} {
+	return map[string]interface{}{
+		"code":  e.Code,
+		"msg":   e.Msg,
+		"count": count,
+		"list":  list,
+	}
+}
+
+func Success() []byte {
+	w := FromMessage(SystemSuccess).Response()
+	result, _ := json.Marshal(w)
+	return result
+}
diff --git a/postcss.config.js b/api/filter/cors.go
similarity index 64%
rename from postcss.config.js
rename to api/filter/cors.go
index a5e9c1d..f2c7ac1 100644
--- a/postcss.config.js
+++ b/api/filter/cors.go
@@ -14,9 +14,20 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package filter
 
-module.exports = {
-  plugins: {
-    autoprefixer: {}
-  }
+import "github.com/gin-gonic/gin"
+
+func CORS() gin.HandlerFunc {
+	return func(c *gin.Context) {
+		c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
+		c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
+		c.Writer.Header().Set("Access-Control-Allow-Headers", "*")
+		c.Writer.Header().Set("Access-Control-Allow-Methods", "*")
+		if c.Request.Method == "OPTIONS" {
+			c.AbortWithStatus(204)
+			return
+		}
+		c.Next()
+	}
 }
diff --git a/api/filter/logging.go b/api/filter/logging.go
new file mode 100644
index 0000000..22f5528
--- /dev/null
+++ b/api/filter/logging.go
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package filter
+
+import (
+	"bytes"
+	"github.com/apisix/manager-api/errno"
+	"github.com/gin-gonic/gin"
+	"github.com/sirupsen/logrus"
+	"time"
+)
+
+func RequestLogHandler() gin.HandlerFunc {
+	return func(c *gin.Context) {
+		start, host, remoteIP, path, method := time.Now(), c.Request.Host, c.ClientIP(), c.Request.URL.Path, c.Request.Method
+		var val interface{}
+		if method == "GET" {
+			val = c.Request.URL.Query()
+		} else {
+			val, _ = c.GetRawData()
+		}
+		c.Set("requestBody", val)
+		uuid, _ := c.Get("X-Request-Id")
+
+		param, _ := c.Get("requestBody")
+		switch param.(type) {
+		case []byte:
+			param = string(param.([]byte))
+		default:
+		}
+
+		blw := &bodyLogWriter{body: bytes.NewBufferString(""), ResponseWriter: c.Writer}
+		c.Writer = blw
+		c.Next()
+		latency := time.Now().Sub(start) / 1000000
+		statusCode := c.Writer.Status()
+		respBody := blw.body.String()
+		if uuid == "" {
+			uuid = c.Writer.Header().Get("X-Request-Id")
+		}
+		var errs []string
+		for _, err := range c.Errors {
+			if e, ok := err.Err.(*errno.ManagerError); ok {
+				errs = append(errs, e.Error())
+			}
+		}
+		logger.WithFields(logrus.Fields{
+			"requestId":  uuid,
+			"latency":    latency,
+			"remoteIp":   remoteIP,
+			"method":     method,
+			"path":       path,
+			"statusCode": statusCode,
+			"host":       host,
+			"params":     param,
+			"respBody":   respBody,
+			"errMsg":     errs,
+		}).Info("")
+	}
+}
+
+type bodyLogWriter struct {
+	gin.ResponseWriter
+	body *bytes.Buffer
+}
+
+func (w bodyLogWriter) Write(b []byte) (int, error) {
+	w.body.Write(b)
+	return w.ResponseWriter.Write(b)
+}
diff --git a/api/filter/recover.go b/api/filter/recover.go
new file mode 100644
index 0000000..433331f
--- /dev/null
+++ b/api/filter/recover.go
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package filter
+
+import (
+	"bytes"
+	"fmt"
+	"github.com/apisix/manager-api/log"
+	"github.com/gin-gonic/gin"
+	"github.com/sirupsen/logrus"
+	"io/ioutil"
+	"net/http"
+	"runtime"
+	"time"
+)
+
+var (
+	logger    = log.GetLogger()
+	dunno     = []byte("???")
+	centerDot = []byte("·")
+	dot       = []byte(".")
+	slash     = []byte("/")
+)
+
+func RecoverHandler() gin.HandlerFunc {
+	return func(c *gin.Context) {
+		defer func() {
+			if err := recover(); err != nil {
+				uuid := c.Writer.Header().Get("X-Request-Id")
+				logger.WithFields(logrus.Fields{
+					"uuid": uuid,
+				})
+				stack := stack(3)
+				logger.Errorf("[Recovery] %s panic recovered:\n\n%s\n%s", timeFormat(time.Now()), err, stack)
+				c.AbortWithStatus(http.StatusInternalServerError)
+			}
+		}()
+		c.Next()
+	}
+}
+
+func WrapGo(f func(...interface{}), args ...interface{}) {
+	defer func() {
+		if err := recover(); err != nil {
+			stack := stack(3)
+			logger.Errorf("[Recovery] %s panic recovered:\n\n%s\n%s", timeFormat(time.Now()), err, stack)
+		}
+	}()
+	f(args...)
+}
+
+func stack(skip int) []byte {
+	buf := new(bytes.Buffer) // the returned data
+	// loaded file.
+	var lines [][]byte
+	var lastFile string
+	for i := skip; ; i++ {
+		pc, file, line, ok := runtime.Caller(i)
+		if !ok {
+			break
+		}
+		// Print this much at least.  If we can't find the source, it won't show.
+		fmt.Fprintf(buf, "%s:%d (0x%x)\n", file, line, pc)
+		if file != lastFile {
+			data, err := ioutil.ReadFile(file)
+			if err != nil {
+				continue
+			}
+			lines = bytes.Split(data, []byte{'\n'})
+			lastFile = file
+		}
+		fmt.Fprintf(buf, "\t%s: %s\n", function(pc), source(lines, line))
+	}
+	return buf.Bytes()
+}
+
+// source returns a space-trimmed slice of the n'th line.
+func source(lines [][]byte, n int) []byte {
+	n-- // in stack trace, lines are 1-indexed but our array is 0-indexed
+	if n < 0 || n >= len(lines) {
+		return dunno
+	}
+	return bytes.TrimSpace(lines[n])
+}
+
+// function returns, if possible, the name of the function containing the PC.
+func function(pc uintptr) []byte {
+	fn := runtime.FuncForPC(pc)
+	if fn == nil {
+		return dunno
+	}
+	name := []byte(fn.Name())
+	if lastslash := bytes.LastIndex(name, slash); lastslash >= 0 {
+		name = name[lastslash+1:]
+	}
+	if period := bytes.Index(name, dot); period >= 0 {
+		name = name[period+1:]
+	}
+	name = bytes.Replace(name, centerDot, dot, -1)
+	return name
+}
+
+func timeFormat(t time.Time) string {
+	var timeString = t.Format("2006/01/02 - 15:04:05")
+	return timeString
+}
diff --git a/src/api/schema/plugins.ts b/api/filter/request_id.go
similarity index 60%
rename from src/api/schema/plugins.ts
rename to api/filter/request_id.go
index bc71e96..2992869 100644
--- a/src/api/schema/plugins.ts
+++ b/api/filter/request_id.go
@@ -14,11 +14,29 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package filter
 
-import request from '@/utils/request'
+import (
+	"github.com/gin-gonic/gin"
+	"github.com/satori/go.uuid"
+)
 
-export const getPluginList = () =>
-  request({
-    url: '/plugins/list',
-    method: 'get'
-  })
+func RequestId() gin.HandlerFunc {
+	return func(c *gin.Context) {
+		// Check for incoming header, use it if exists
+		requestId := c.Request.Header.Get("X-Request-Id")
+
+		// Create request id with UUID4
+		if requestId == "" {
+			u4 := uuid.NewV4()
+			requestId = u4.String()
+		}
+
+		// Expose it for use in the application
+		c.Set("X-Request-Id", requestId)
+
+		// Set X-Request-Id header
+		c.Writer.Header().Set("X-Request-Id", requestId)
+		c.Next()
+	}
+}
diff --git a/api/go.mod b/api/go.mod
new file mode 100644
index 0000000..45372ec
--- /dev/null
+++ b/api/go.mod
@@ -0,0 +1,15 @@
+module github.com/apisix/manager-api
+
+go 1.13
+
+require (
+	github.com/gin-contrib/pprof v1.3.0
+	github.com/gin-gonic/gin v1.6.3
+	github.com/go-sql-driver/mysql v1.5.0
+	github.com/jinzhu/gorm v1.9.12
+	github.com/satori/go.uuid v1.2.0
+	github.com/sirupsen/logrus v1.6.0
+	github.com/stretchr/testify v1.4.0
+	github.com/tidwall/gjson v1.6.0
+	gopkg.in/resty.v1 v1.12.0
+)
diff --git a/api/go.sum b/api/go.sum
new file mode 100644
index 0000000..9e3e3e3
--- /dev/null
+++ b/api/go.sum
@@ -0,0 +1,89 @@
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
+github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
+github.com/gin-contrib/pprof v1.3.0 h1:G9eK6HnbkSqDZBYbzG4wrjCsA4e+cvYAHUZw6W+W9K0=
+github.com/gin-contrib/pprof v1.3.0/go.mod h1:waMjT1H9b179t3CxuG1cV3DHpga6ybizwfBaM5OXaB0=
+github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
+github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
+github.com/gin-gonic/gin v1.6.2/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
+github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
+github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
+github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
+github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
+github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
+github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
+github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
+github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
+github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
+github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
+github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
+github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
+github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
+github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/jinzhu/gorm v1.9.12 h1:Drgk1clyWT9t9ERbzHza6Mj/8FY/CqMyVzOiHviMo6Q=
+github.com/jinzhu/gorm v1.9.12/go.mod h1:vhTjlKSJUTWNtcbQtrMBFCxy7eXTzeCAzfL5fBZT/Qs=
+github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
+github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
+github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
+github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
+github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
+github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
+github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
+github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
+github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
+github.com/mattn/go-sqlite3 v2.0.1+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
+github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
+github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
+github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
+github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/tidwall/gjson v1.6.0 h1:9VEQWz6LLMUsUl6PueE49ir4Ka6CzLymOAZDxpFsTDc=
+github.com/tidwall/gjson v1.6.0/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls=
+github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc=
+github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E=
+github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
+github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
+github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
+github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
+github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
+github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
+golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/resty.v1 v1.12.0 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI=
+gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
diff --git a/api/log/log.go b/api/log/log.go
new file mode 100644
index 0000000..c1645e8
--- /dev/null
+++ b/api/log/log.go
@@ -0,0 +1,147 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package log
+
+import (
+	"bufio"
+	"fmt"
+	"github.com/apisix/manager-api/conf"
+	"github.com/sirupsen/logrus"
+	"os"
+	"runtime"
+	"strings"
+)
+
+var logEntry *logrus.Entry
+
+func GetLogger() *logrus.Entry {
+	if logEntry == nil {
+		var log = logrus.New()
+		setNull(log)
+		log.SetLevel(logrus.DebugLevel)
+		if conf.ENV != conf.LOCAL {
+			log.SetLevel(logrus.ErrorLevel)
+		}
+		log.SetFormatter(&logrus.JSONFormatter{})
+		logEntry = log.WithFields(logrus.Fields{
+			"app": "manager-api",
+		})
+		if hook, err := createHook(); err == nil {
+			log.AddHook(hook)
+		}
+	}
+	return logEntry
+}
+
+func setNull(log *logrus.Logger) {
+	src, err := os.OpenFile(os.DevNull, os.O_APPEND|os.O_WRONLY, os.ModeAppend)
+	if err != nil {
+		fmt.Println("err", err)
+	}
+	writer := bufio.NewWriter(src)
+	log.SetOutput(writer)
+}
+
+type Hook struct {
+	Formatter func(file, function string, line int) string
+}
+
+func createHook() (*Hook, error) {
+	return &Hook{
+		func(file, function string, line int) string {
+			return fmt.Sprintf("%s:%d", file, line)
+		},
+	}, nil
+}
+
+func (hook *Hook) Fire(entry *logrus.Entry) error {
+	str := hook.Formatter(findCaller(5))
+	en := entry.WithField("line", str)
+	en.Level = entry.Level
+	en.Message = entry.Message
+	en.Time = entry.Time
+	line, err := en.String()
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "Unable to read entry, %v", err)
+		return err
+	}
+	switch en.Level {
+	case logrus.PanicLevel:
+		fmt.Print(line)
+		return nil
+	case logrus.FatalLevel:
+		fmt.Print(line)
+		return nil
+	case logrus.ErrorLevel:
+		fmt.Print(line)
+		return nil
+	case logrus.WarnLevel:
+		fmt.Print(line)
+		return nil
+	case logrus.InfoLevel:
+		fmt.Print(line)
+		return nil
+	case logrus.DebugLevel:
+		fmt.Print(line)
+		return nil
+	default:
+		return nil
+	}
+}
+
+func (hook *Hook) Levels() []logrus.Level {
+	return logrus.AllLevels
+}
+
+func findCaller(skip int) (string, string, int) {
+	var (
+		pc       uintptr
+		file     string
+		function string
+		line     int
+	)
+	for i := 0; i < 10; i++ {
+		pc, file, line = getCaller(skip + i)
+		if !strings.HasPrefix(file, "logrus") {
+			break
+		}
+	}
+	if pc != 0 {
+		frames := runtime.CallersFrames([]uintptr{pc})
+		frame, _ := frames.Next()
+		function = frame.Function
+	}
+	return file, function, line
+}
+
+func getCaller(skip int) (uintptr, string, int) {
+	pc, file, line, ok := runtime.Caller(skip)
+	if !ok {
+		return 0, "", 0
+	}
+	n := 0
+	for i := len(file) - 1; i > 0; i-- {
+		if file[i] == '/' {
+			n += 1
+			if n >= 2 {
+				file = file[i+1:]
+				break
+			}
+		}
+	}
+	return pc, file, line
+}
diff --git a/api/main.go b/api/main.go
new file mode 100644
index 0000000..3d756dd
--- /dev/null
+++ b/api/main.go
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package main
+
+import (
+	"fmt"
+	"github.com/apisix/manager-api/conf"
+	"github.com/apisix/manager-api/filter"
+	"github.com/apisix/manager-api/log"
+	"github.com/apisix/manager-api/route"
+	"github.com/gin-contrib/pprof"
+	"github.com/gin-gonic/gin"
+	"net/http"
+	"time"
+)
+
+var logger = log.GetLogger()
+
+func setUpRouter() *gin.Engine {
+	if conf.ENV != conf.LOCAL && conf.ENV != conf.BETA {
+		gin.SetMode(gin.DebugMode)
+	} else {
+		gin.SetMode(gin.ReleaseMode)
+	}
+	r := gin.New()
+
+	r.Use(filter.CORS(), filter.RequestId(), filter.RequestLogHandler(), filter.RecoverHandler())
+	route.AppendHealthCheck(r)
+	route.AppendRoute(r)
+	route.AppendSsl(r)
+	route.AppendPlugin(r)
+
+	pprof.Register(r)
+
+	return r
+}
+
+func main() {
+	// init
+	conf.InitializeMysql()
+	// routes
+	r := setUpRouter()
+	addr := fmt.Sprintf(":%d", conf.ServerPort)
+	s := &http.Server{
+		Addr:         addr,
+		Handler:      r,
+		ReadTimeout:  time.Duration(1000) * time.Millisecond,
+		WriteTimeout: time.Duration(5000) * time.Millisecond,
+	}
+	s.ListenAndServe()
+}
diff --git a/src/api/users.ts b/api/route/healthz.go
similarity index 69%
rename from src/api/users.ts
rename to api/route/healthz.go
index 591b787..97dcd04 100644
--- a/src/api/users.ts
+++ b/api/route/healthz.go
@@ -14,25 +14,24 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package route
 
-import request from '@/utils/request'
+import (
+	"github.com/apisix/manager-api/log"
+	"github.com/gin-gonic/gin"
+	"net/http"
+)
 
-export const getUserInfo = (data: any) =>
-  request({
-    url: '/users/info',
-    method: 'post',
-    data
-  })
+var logger = log.GetLogger()
 
-export const login = (data: any) =>
-  request({
-    url: '/users/login',
-    method: 'post',
-    data
-  })
+func healthzHandler() gin.HandlerFunc {
+	return func(c *gin.Context) {
+		c.Copy()
+		c.String(http.StatusOK, "pong")
+	}
+}
 
-export const logout = () =>
-  request({
-    url: '/users/logout',
-    method: 'post'
-  })
+func AppendHealthCheck(r *gin.Engine) *gin.Engine {
+	r.GET("/ping", healthzHandler())
+	return r
+}
diff --git a/api/route/plugin.go b/api/route/plugin.go
new file mode 100644
index 0000000..0032b13
--- /dev/null
+++ b/api/route/plugin.go
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package route
+
+import (
+	"encoding/json"
+	"github.com/apisix/manager-api/errno"
+	"github.com/apisix/manager-api/service"
+	"github.com/gin-gonic/gin"
+	"net/http"
+)
+
+func AppendPlugin(r *gin.Engine) *gin.Engine {
+	r.GET("/apisix/admin/plugins", listPlugin)
+	r.GET("/apisix/admin/schema/plugins/:name", findSchema)
+	return r
+}
+
+func findSchema(c *gin.Context) {
+	name := c.Param("name")
+	request := &service.ApisixPluginRequest{Name: name}
+	if result, err := request.Schema(); err != nil {
+		e := errno.FromMessage(errno.ApisixPluginSchemaError, err.Error())
+		logger.Error(e.Msg)
+		c.AbortWithStatusJSON(http.StatusBadRequest, e.Response())
+		return
+	} else {
+		resp, _ := json.Marshal(result)
+		c.Data(http.StatusOK, service.ContentType, resp)
+	}
+}
+
+func listPlugin(c *gin.Context) {
+	request := &service.ApisixPluginRequest{}
+	if result, err := request.List(); err != nil {
+		e := errno.FromMessage(errno.ApisixPluginListError, err.Error())
+		logger.Error(e.Msg)
+		c.AbortWithStatusJSON(http.StatusBadRequest, e.Response())
+		return
+	} else {
+		resp, _ := json.Marshal(result)
+		c.Data(http.StatusOK, service.ContentType, resp)
+	}
+}
diff --git a/api/route/route.go b/api/route/route.go
new file mode 100644
index 0000000..efc95d2
--- /dev/null
+++ b/api/route/route.go
@@ -0,0 +1,242 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package route
+
+import (
+	"encoding/json"
+	"github.com/apisix/manager-api/conf"
+	"github.com/apisix/manager-api/errno"
+	"github.com/apisix/manager-api/service"
+	"github.com/gin-gonic/gin"
+	"github.com/satori/go.uuid"
+	"net/http"
+	"strconv"
+)
+
+func AppendRoute(r *gin.Engine) *gin.Engine {
+	r.POST("/apisix/admin/routes", createRoute)
+	r.GET("/apisix/admin/routes/:rid", findRoute)
+	r.GET("/apisix/admin/routes", listRoute)
+	r.PUT("/apisix/admin/routes/:rid", updateRoute)
+	r.DELETE("/apisix/admin/routes/:rid", deleteRoute)
+	return r
+}
+
+func listRoute(c *gin.Context) {
+	db := conf.DB()
+	size, _ := strconv.Atoi(c.Query("size"))
+	page, _ := strconv.Atoi(c.Query("page"))
+	if size == 0 {
+		size = 10
+	}
+	isSearch := true
+	if name, exist := c.GetQuery("name"); exist {
+		db = db.Where("name like ? ", "%"+name+"%")
+		isSearch = false
+	}
+	if description, exist := c.GetQuery("description"); exist {
+		db = db.Where("description like ? ", "%"+description+"%")
+		isSearch = false
+	}
+	if host, exist := c.GetQuery("host"); exist {
+		db = db.Where("hosts like ? ", "%"+host+"%")
+		isSearch = false
+	}
+	if uri, exist := c.GetQuery("uri"); exist {
+		db = db.Where("uris like ? ", "%"+uri+"%")
+		isSearch = false
+	}
+	if ip, exist := c.GetQuery("ip"); exist {
+		db = db.Where("upstream_nodes like ? ", "%"+ip+"%")
+		isSearch = false
+	}
+	// search
+	if isSearch {
+		if search, exist := c.GetQuery("search"); exist {
+			db = db.Where("name like ? ", "%"+search+"%").
+				Or("description like ? ", "%"+search+"%").
+				Or("hosts like ? ", "%"+search+"%").
+				Or("uris like ? ", "%"+search+"%").
+				Or("upstream_nodes like ? ", "%"+search+"%")
+		}
+	}
+	// todo params check
+	// mysql
+	routeList := []service.Route{}
+	var count int
+	if err := db.Order("priority, update_time desc").Table("routes").Offset((page - 1) * size).Limit(size).Find(&routeList).Count(&count).Error; err != nil {
+		e := errno.FromMessage(errno.RouteRequestError, err.Error())
+		logger.Error(e.Msg)
+		c.AbortWithStatusJSON(http.StatusBadRequest, e.Response())
+		return
+	} else {
+		responseList := make([]service.RouteResponse, 0)
+		for _, r := range routeList {
+			response := &service.RouteResponse{}
+			response.Parse(&r)
+			responseList = append(responseList, *response)
+		}
+		result := &service.ListResponse{Count: count, Data: responseList}
+		resp, _ := json.Marshal(result)
+		c.Data(http.StatusOK, service.ContentType, resp)
+	}
+}
+
+func deleteRoute(c *gin.Context) {
+	rid := c.Param("rid")
+	// todo  params check
+	// delete from apisix
+	request := &service.ApisixRouteRequest{}
+	if _, err := request.Delete(rid); err != nil {
+		e := errno.FromMessage(errno.ApisixRouteDeleteError, err.Error())
+		logger.Error(e.Msg)
+		c.AbortWithStatusJSON(http.StatusInternalServerError, e.Response())
+		return
+	} else {
+		// delete from mysql
+		rd := &service.Route{}
+		rd.ID = uuid.FromStringOrNil(rid)
+		if err := conf.DB().Delete(rd).Error; err != nil {
+			e := errno.FromMessage(errno.DBRouteDeleteError, err.Error())
+			logger.Error(e.Msg)
+			c.AbortWithStatusJSON(http.StatusInternalServerError, e.Response())
+			return
+		}
+	}
+	c.Data(http.StatusOK, service.ContentType, errno.Success())
+}
+func updateRoute(c *gin.Context) {
+	rid := c.Param("rid")
+	// todo  params check
+	param, exist := c.Get("requestBody")
+	if !exist || len(param.([]byte)) < 1 {
+		e := errno.FromMessage(errno.RouteRequestError, "route create with no post data")
+		logger.Error(e.Msg)
+		c.AbortWithStatusJSON(http.StatusBadRequest, e.Response())
+		return
+	}
+	routeRequest := &service.RouteRequest{}
+	if err := routeRequest.Parse(param); err != nil {
+		e := errno.FromMessage(errno.RouteRequestError, err.Error())
+		logger.Error(e.Msg)
+		c.AbortWithStatusJSON(http.StatusBadRequest, e.Response())
+		return
+	}
+	logger.Info(routeRequest.Plugins)
+
+	arr := service.ToApisixRequest(routeRequest)
+	logger.Info(arr)
+	if resp, err := arr.Update(rid); err != nil {
+		e := errno.FromMessage(errno.ApisixRouteUpdateError, err.Error())
+		logger.Error(e.Msg)
+		c.AbortWithStatusJSON(http.StatusInternalServerError, e.Response())
+		return
+	} else {
+		// update mysql
+		if rd, err := service.ToRoute(routeRequest, arr, uuid.FromStringOrNil(rid), resp); err != nil {
+			c.AbortWithStatusJSON(http.StatusInternalServerError, err.Response())
+			return
+		} else {
+			if err := conf.DB().Model(&service.Route{}).Update(rd).Error; err != nil {
+				e := errno.FromMessage(errno.DBRouteUpdateError, err.Error())
+				logger.Error(e.Msg)
+				c.AbortWithStatusJSON(http.StatusInternalServerError, e.Response())
+				return
+			}
+			logger.Info(rd)
+		}
+	}
+	c.Data(http.StatusOK, service.ContentType, errno.Success())
+}
+
+func findRoute(c *gin.Context) {
+	rid := c.Param("rid")
+	// todo  params check
+	// find from apisix
+	request := &service.ApisixRouteRequest{}
+	if response, err := request.FindById(rid); err != nil {
+		e := errno.FromMessage(errno.RouteRequestError, err.Error()+" route ID: "+rid)
+		logger.Error(e.Msg)
+		c.AbortWithStatusJSON(http.StatusBadRequest, e.Response())
+		return
+	} else {
+		// transfer response to dashboard struct
+		if result, err := response.Parse(); err != nil {
+			e := errno.FromMessage(errno.RouteRequestError, err.Error()+" route ID: "+rid)
+			logger.Error(e.Msg)
+			c.AbortWithStatusJSON(http.StatusBadRequest, e.Response())
+			return
+		} else {
+			// need to find name from mysql temporary
+			route := &service.Route{}
+			if err := conf.DB().Table("routes").Where("id=?", rid).First(&route).Error; err != nil {
+				e := errno.FromMessage(errno.RouteRequestError, err.Error()+" route ID: "+rid)
+				logger.Error(e.Msg)
+				c.AbortWithStatusJSON(http.StatusBadRequest, e.Response())
+				return
+			}
+			result.Name = route.Name
+			resp, _ := json.Marshal(result)
+			c.Data(http.StatusOK, service.ContentType, resp)
+		}
+	}
+}
+
+func createRoute(c *gin.Context) {
+	u4 := uuid.NewV4()
+	rid := u4.String()
+	// todo params check
+	param, exist := c.Get("requestBody")
+	if !exist || len(param.([]byte)) < 1 {
+		e := errno.FromMessage(errno.RouteRequestError, "route create with no post data")
+		logger.Error(e.Msg)
+		c.AbortWithStatusJSON(http.StatusBadRequest, e.Response())
+		return
+	}
+	routeRequest := &service.RouteRequest{}
+	if err := routeRequest.Parse(param); err != nil {
+		e := errno.FromMessage(errno.RouteRequestError, err.Error())
+		logger.Error(e.Msg)
+		c.AbortWithStatusJSON(http.StatusBadRequest, e.Response())
+		return
+	}
+	logger.Info(routeRequest.Plugins)
+
+	arr := service.ToApisixRequest(routeRequest)
+	logger.Info(arr)
+	if resp, err := arr.Create(rid); err != nil {
+		e := errno.FromMessage(errno.ApisixRouteCreateError, err.Error())
+		logger.Error(e.Msg)
+		c.AbortWithStatusJSON(http.StatusInternalServerError, e.Response())
+		return
+	} else {
+		// update mysql
+		if rd, err := service.ToRoute(routeRequest, arr, u4, resp); err != nil {
+			c.AbortWithStatusJSON(http.StatusInternalServerError, err.Response())
+			return
+		} else {
+			logger.Info(rd)
+			if err := conf.DB().Create(rd).Error; err != nil {
+				e := errno.FromMessage(errno.DBRouteCreateError, err.Error())
+				logger.Error(e.Msg)
+				c.AbortWithStatusJSON(http.StatusInternalServerError, e.Response())
+				return
+			}
+		}
+	}
+	c.Data(http.StatusOK, service.ContentType, errno.Success())
+}
diff --git a/api/route/ssl.go b/api/route/ssl.go
new file mode 100644
index 0000000..2a9d6f7
--- /dev/null
+++ b/api/route/ssl.go
@@ -0,0 +1,153 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package route
+
+import (
+	"net/http"
+	"strconv"
+
+	"github.com/gin-gonic/gin"
+	"github.com/satori/go.uuid"
+
+	"github.com/apisix/manager-api/errno"
+	"github.com/apisix/manager-api/service"
+)
+
+const contentType = "application/json"
+
+func AppendSsl(r *gin.Engine) *gin.Engine {
+	r.POST("/apisix/admin/check_ssl_cert", sslCheck)
+	r.GET("/apisix/admin/ssls", sslList)
+	r.POST("/apisix/admin/ssls", sslCreate)
+	r.GET("/apisix/admin/ssls/:id", sslItem)
+	r.PUT("/apisix/admin/ssls/:id", sslUpdate)
+	r.DELETE("/apisix/admin/ssls/:id", sslDelete)
+	return r
+}
+
+func sslList(c *gin.Context) {
+	size, _ := strconv.Atoi(c.DefaultQuery("size", "10"))
+	page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
+	// todo params check
+	resp, err := service.SslList(page, size)
+
+	if err != nil {
+		e := errno.FromMessage(errno.RouteRequestError, err.Error())
+		logger.Error(e.Msg)
+		c.AbortWithStatusJSON(http.StatusBadRequest, e.Response())
+		return
+	}
+
+	c.Data(http.StatusOK, service.ContentType, resp)
+}
+
+func sslItem(c *gin.Context) {
+	id := c.Param("id")
+
+	// todo params check
+	resp, err := service.SslItem(id)
+
+	if err != nil {
+		e := errno.FromMessage(errno.RouteRequestError, err.Error())
+		logger.Error(e.Msg)
+		c.AbortWithStatusJSON(http.StatusBadRequest, e.Response())
+		return
+	}
+
+	c.Data(http.StatusOK, service.ContentType, resp)
+}
+
+func sslCheck(c *gin.Context) {
+	// todo params check
+	param, exist := c.Get("requestBody")
+
+	if !exist || len(param.([]byte)) < 1 {
+		e := errno.FromMessage(errno.RouteRequestError, "route create with no post data")
+		logger.Error(e.Msg)
+		c.AbortWithStatusJSON(http.StatusBadRequest, e.Response())
+		return
+	}
+
+	resp, err := service.SslCheck(param)
+	if err != nil {
+		e := errno.FromMessage(errno.RouteRequestError, err.Error())
+		logger.Error(e.Msg)
+		c.AbortWithStatusJSON(http.StatusBadRequest, e.Response())
+		return
+	}
+
+	c.Data(http.StatusOK, contentType, resp)
+}
+
+func sslCreate(c *gin.Context) {
+	// todo params check
+	param, exist := c.Get("requestBody")
+
+	u4 := uuid.NewV4()
+
+	if !exist || len(param.([]byte)) < 1 {
+		e := errno.FromMessage(errno.RouteRequestError, "route create with no post data")
+		logger.Error(e.Msg)
+		c.AbortWithStatusJSON(http.StatusBadRequest, e.Response())
+		return
+	}
+
+	if err := service.SslCreate(param, u4.String()); err != nil {
+		e := errno.FromMessage(errno.ApisixRouteCreateError, err.Error())
+		logger.Error(e.Msg)
+		c.AbortWithStatusJSON(http.StatusInternalServerError, e.Response())
+		return
+	}
+
+	c.Data(http.StatusOK, contentType, errno.Success())
+}
+
+func sslUpdate(c *gin.Context) {
+	// todo params check
+	param, exist := c.Get("requestBody")
+
+	id := c.Param("id")
+
+	if !exist || len(param.([]byte)) < 1 {
+		e := errno.FromMessage(errno.RouteRequestError, "route create with no post data")
+		logger.Error(e.Msg)
+		c.AbortWithStatusJSON(http.StatusBadRequest, e.Response())
+		return
+	}
+
+	if err := service.SslUpdate(param, id); err != nil {
+		e := errno.FromMessage(errno.ApisixRouteCreateError, err.Error())
+		logger.Error(e.Msg)
+		c.AbortWithStatusJSON(http.StatusInternalServerError, e.Response())
+		return
+	}
+
+	c.Data(http.StatusOK, contentType, errno.Success())
+}
+
+func sslDelete(c *gin.Context) {
+	id := c.Param("id")
+	// todo params check
+	if err := service.SslDelete(id); err != nil {
+		e := errno.FromMessage(errno.RouteRequestError, err.Error())
+		logger.Error(e.Msg)
+		c.AbortWithStatusJSON(http.StatusBadRequest, e.Response())
+		return
+	}
+
+	c.Data(http.StatusOK, service.ContentType, errno.Success())
+}
diff --git a/api/script/db/schema.sql b/api/script/db/schema.sql
new file mode 100644
index 0000000..69f088c
--- /dev/null
+++ b/api/script/db/schema.sql
@@ -0,0 +1,31 @@
+-- this is a db script for init
+CREATE DATABASE `manager`;
+use `manager`;
+CREATE TABLE `routes` (
+  `id` varchar(64) NOT NULL unique,
+  `name` varchar(200) NOT NULL unique, -- not support yet
+  `description` varchar(200) DEFAULT NULL,
+  `hosts` text,
+  `uris` text,
+  `upstream_nodes` text,
+  `upstream_id` varchar(32) , -- fk
+  `priority` int NOT NULL DEFAULT 0,
+  `state` int NOT NULL DEFAULT 1, -- 1-normal 0-disable
+  `content` text,
+  `content_admin_api` text,
+  `create_time` bigint(20),
+  `update_time` bigint(20),
+
+  PRIMARY KEY (`id`)
+) DEFAULT CHARSET=utf8;
+CREATE TABLE `ssls` (
+  `id` char(36) NOT NULL DEFAULT '',
+  `public_key` text NOT NULL,
+  `snis` text NOT NULL,
+  `validity_start` bigint(20) unsigned NOT NULL,
+  `validity_end` bigint(20) unsigned NOT NULL,
+  `status` tinyint(1) unsigned NOT NULL DEFAULT '1',
+  `create_time` bigint(20) unsigned NOT NULL,
+  `update_time` bigint(20) unsigned NOT NULL,
+  PRIMARY KEY (`id`)
+) DEFAULT CHARSET=utf8;
\ No newline at end of file
diff --git a/src/store/index.ts b/api/service/base.go
similarity index 50%
rename from src/store/index.ts
rename to api/service/base.go
index aa389a9..6ee57b5 100644
--- a/src/store/index.ts
+++ b/api/service/base.go
@@ -14,24 +14,35 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package service
 
-import Vue from 'vue'
-import Vuex from 'vuex'
-import { IAppState } from './modules/app'
-import { IUserState } from './modules/user'
-import { ITagsViewState } from './modules/tags-view'
-import { IPermissionState } from './modules/permission'
-import { ISettingsState } from './modules/settings'
+import (
+	"github.com/jinzhu/gorm"
+	"github.com/satori/go.uuid"
+	"time"
+)
 
-Vue.use(Vuex)
+// Base contains common columns for all tables.
+type Base struct {
+	ID         uuid.UUID `json:"id",sql:"type:uuid;primary_key;"`
+	CreateTime int64     `json:"create_time"`
+	UpdateTime int64     `json:"update_time"`
+}
 
-export interface IRootState {
-  app: IAppState
-  user: IUserState
-  tagsView: ITagsViewState
-  permission: IPermissionState
-  settings: ISettingsState
+// BeforeCreate will set a UUID rather than numeric ID.
+func (base *Base) BeforeCreate(scope *gorm.Scope) error {
+	timestamp := time.Now().Unix()
+	err := scope.SetColumn("UpdateTime", timestamp)
+	err = scope.SetColumn("CreateTime", timestamp)
+	if len(base.ID) == 0 {
+		uuid := uuid.NewV4()
+		err = scope.SetColumn("ID", uuid)
+		return err
+	}
+	return err
 }
 
-// Declare empty store first, dynamically register all modules later.
-export default new Vuex.Store<IRootState>({})
+func (base *Base) BeforeSave(scope *gorm.Scope) error {
+	err := scope.SetColumn("UpdateTime", time.Now().Unix())
+	return err
+}
diff --git a/api/service/plugin.go b/api/service/plugin.go
new file mode 100644
index 0000000..92cd6c3
--- /dev/null
+++ b/api/service/plugin.go
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package service
+
+import (
+	"encoding/json"
+	"fmt"
+	"github.com/apisix/manager-api/conf"
+	"github.com/apisix/manager-api/utils"
+)
+
+type ApisixPluginRequest struct {
+	Name string `json:"name"`
+}
+
+func (apr *ApisixPluginRequest) Schema() (map[string]interface{}, error) {
+	url := fmt.Sprintf("%s/schema/plugins/%s", conf.BaseUrl, apr.Name)
+	if resp, err := utils.Get(url); err != nil {
+		logger.Error(err.Error())
+		return nil, err
+	} else {
+		arresp := make(map[string]interface{})
+		if err := json.Unmarshal(resp, &arresp); err != nil {
+			logger.Error(err.Error())
+			return nil, err
+		} else {
+			return arresp, nil
+		}
+	}
+}
+
+func (apr *ApisixPluginRequest) List() ([]string, error) {
+
+	url := fmt.Sprintf("%s/plugins/list", conf.BaseUrl)
+	if resp, err := utils.Get(url); err != nil {
+		logger.Error(err.Error())
+		return nil, err
+	} else {
+		var arresp []string
+		if err := json.Unmarshal(resp, &arresp); err != nil {
+			logger.Error(err.Error())
+			return nil, err
+		} else {
+			return arresp, nil
+		}
+	}
+}
diff --git a/api/service/route.go b/api/service/route.go
new file mode 100644
index 0000000..dae7278
--- /dev/null
+++ b/api/service/route.go
@@ -0,0 +1,560 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package service
+
+import (
+	"encoding/json"
+	"fmt"
+	"github.com/apisix/manager-api/conf"
+	"github.com/apisix/manager-api/errno"
+	"github.com/apisix/manager-api/log"
+	"github.com/apisix/manager-api/utils"
+	"github.com/satori/go.uuid"
+	"time"
+)
+
+const (
+	ContentType      = "application/json"
+	HTTP             = "http"
+	HTTPS            = "https"
+	SCHEME           = "scheme"
+	WEBSOCKET        = "websocket"
+	REDIRECT         = "redirect"
+	PROXY_REWRIETE   = "proxy-rewrite"
+	UPATHTYPE_STATIC = "static"
+	UPATHTYPE_REGX   = "regx"
+	UPATHTYPE_KEEP   = "keep"
+)
+
+var logger = log.GetLogger()
+
+func (r *RouteRequest) Parse(body interface{}) error {
+	if err := json.Unmarshal(body.([]byte), r); err != nil {
+		r = nil
+		return err
+	} else {
+		if r.Uris == nil || len(r.Uris) < 1 {
+			r.Uris = []string{"/*"}
+		}
+	}
+	return nil
+}
+
+func (arr *ApisixRouteRequest) Parse(r *RouteRequest) {
+	arr.Desc = r.Desc
+	arr.Priority = r.Priority
+	arr.Methods = r.Methods
+	arr.Uris = r.Uris
+	arr.Hosts = r.Hosts
+	arr.Vars = r.Vars
+	arr.Upstream = r.Upstream
+	arr.Plugins = r.Plugins
+}
+
+func (rd *Route) Parse(r *RouteRequest, arr *ApisixRouteRequest) error {
+	//rd.Name = arr.Name
+	rd.Description = arr.Desc
+	// todo transfer
+	rd.Hosts = ""
+	rd.Uris = ""
+	rd.UpstreamNodes = ""
+	rd.UpstreamId = ""
+	if content, err := json.Marshal(r); err != nil {
+		return err
+	} else {
+		rd.Content = string(content)
+	}
+	timestamp := time.Now().Unix()
+	rd.CreateTime = timestamp
+	return nil
+}
+
+func (arr *ApisixRouteRequest) FindById(rid string) (*ApisixRouteResponse, error) {
+	url := fmt.Sprintf("%s/routes/%s", conf.BaseUrl, rid)
+	if resp, err := utils.Get(url); err != nil {
+		logger.Error(err.Error())
+		return nil, err
+	} else {
+		var arresp ApisixRouteResponse
+		if err := json.Unmarshal(resp, &arresp); err != nil {
+			logger.Error(err.Error())
+			return nil, err
+		} else {
+			return &arresp, nil
+		}
+	}
+}
+
+func (arr *ApisixRouteRequest) Update(rid string) (*ApisixRouteResponse, error) {
+	url := fmt.Sprintf("%s/routes/%s", conf.BaseUrl, rid)
+	if b, err := json.Marshal(arr); err != nil {
+		return nil, err
+	} else {
+		if resp, err := utils.Patch(url, b); err != nil {
+			logger.Error(err.Error())
+			return nil, err
+		} else {
+			var arresp ApisixRouteResponse
+			if err := json.Unmarshal(resp, &arresp); err != nil {
+				logger.Error(err.Error())
+				return nil, err
+			} else {
+				return &arresp, nil
+			}
+		}
+	}
+}
+
+func (arr *ApisixRouteRequest) Create(rid string) (*ApisixRouteResponse, error) {
+	url := fmt.Sprintf("%s/routes/%s", conf.BaseUrl, rid)
+	if b, err := json.Marshal(arr); err != nil {
+		return nil, err
+	} else {
+		fmt.Println(string(b))
+		if resp, err := utils.Put(url, b); err != nil {
+			logger.Error(err.Error())
+			return nil, err
+		} else {
+			var arresp ApisixRouteResponse
+			if err := json.Unmarshal(resp, &arresp); err != nil {
+				logger.Error(err.Error())
+				return nil, err
+			} else {
+				return &arresp, nil
+			}
+		}
+	}
+}
+
+func (arr *ApisixRouteRequest) Delete(rid string) (*ApisixRouteResponse, error) {
+	url := fmt.Sprintf("%s/routes/%s", conf.BaseUrl, rid)
+	if resp, err := utils.Delete(url); err != nil {
+		logger.Error(err.Error())
+		return nil, err
+	} else {
+		var arresp ApisixRouteResponse
+		if err := json.Unmarshal(resp, &arresp); err != nil {
+			logger.Error(err.Error())
+			return nil, err
+		} else {
+			return &arresp, nil
+		}
+	}
+}
+
+type RouteRequest struct {
+	ID               string                 `json:"id,omitempty"`
+	Name             string                 `json:"name"`
+	Desc             string                 `json:"desc,omitempty"`
+	Priority         int64                  `json:"priority,omitempty"`
+	Methods          []string               `json:"methods,omitempty"`
+	Uris             []string               `json:"uris"`
+	Hosts            []string               `json:"hosts,omitempty"`
+	Protocols        []string               `json:"protocols,omitempty"`
+	Redirect         *Redirect              `json:"redirect,omitempty"`
+	Vars             [][]string             `json:"vars,omitempty"`
+	Upstream         *Upstream              `json:"upstream,omitempty"`
+	UpstreamProtocol string                 `json:"upstream_protocol,omitempty"`
+	UpstreamPath     *UpstreamPath          `json:"upstream_path,omitempty"`
+	UpstreamHeader   map[string]string      `json:"upstream_header,omitempty"`
+	Plugins          map[string]interface{} `json:"plugins"`
+}
+
+func (r *ApisixRouteResponse) Parse() (*RouteRequest, error) {
+	o := r.Node.Value
+
+	//Protocols from vars and upstream
+	protocols := make([]string, 0)
+	if o.Upstream != nil && o.Upstream.EnableWebsocket {
+		protocols = append(protocols, WEBSOCKET)
+	}
+	flag := true
+	for _, t := range o.Vars {
+		if t[0] == SCHEME {
+			flag = false
+			protocols = append(protocols, t[2])
+		}
+	}
+	if flag {
+		protocols = append(protocols, HTTP)
+		protocols = append(protocols, HTTPS)
+	}
+	//Redirect from plugins
+	redirect := &Redirect{}
+	upstreamProtocol := UPATHTYPE_KEEP
+	upstreamHeader := make(map[string]string)
+	upstreamPath := &UpstreamPath{}
+	for k, v := range o.Plugins {
+		if k == REDIRECT {
+			if bytes, err := json.Marshal(v); err != nil {
+				return nil, err
+			} else {
+				if err := json.Unmarshal(bytes, redirect); err != nil {
+					return nil, err
+				}
+			}
+
+		}
+		if k == PROXY_REWRIETE {
+			pr := &ProxyRewrite{}
+			if bytes, err := json.Marshal(v); err != nil {
+				return nil, err
+			} else {
+				if err := json.Unmarshal(bytes, pr); err != nil {
+					return nil, err
+				} else {
+					if pr.Scheme != "" {
+						upstreamProtocol = pr.Scheme
+					}
+					upstreamHeader = pr.Headers
+					if pr.RegexUri == nil || len(pr.RegexUri) < 2 {
+						upstreamPath.UPathType = UPATHTYPE_STATIC
+						upstreamPath.To = pr.Uri
+					} else {
+						upstreamPath.UPathType = UPATHTYPE_REGX
+						upstreamPath.From = pr.RegexUri[0]
+						upstreamPath.To = pr.RegexUri[1]
+					}
+				}
+			}
+		}
+	}
+	//Vars
+	requestVars := make([][]string, 0)
+	for _, t := range o.Vars {
+		if t[0] != SCHEME {
+			requestVars = append(requestVars, t)
+		}
+	}
+	//Plugins
+	requestPlugins := utils.CopyMap(o.Plugins)
+	delete(requestPlugins, REDIRECT)
+
+	// check if upstream is not exist
+	if o.Upstream == nil {
+		upstreamProtocol = ""
+		upstreamHeader = nil
+		upstreamPath = nil
+	}
+	if upstreamPath.UPathType == "" {
+		upstreamPath = nil
+	}
+	result := &RouteRequest{
+		ID:               o.Id,
+		Desc:             o.Desc,
+		Priority:         o.Priority,
+		Methods:          o.Methods,
+		Uris:             o.Uris,
+		Hosts:            o.Hosts,
+		Redirect:         redirect,
+		Upstream:         o.Upstream,
+		UpstreamProtocol: upstreamProtocol,
+		UpstreamPath:     upstreamPath,
+		UpstreamHeader:   upstreamHeader,
+		Protocols:        protocols,
+		Vars:             requestVars,
+		Plugins:          requestPlugins,
+	}
+	return result, nil
+}
+
+type Redirect struct {
+	HttpToHttps bool   `json:"http_to_https,omitempty"`
+	Code        int64  `json:"code,omitempty"`
+	Uri         string `json:"uri,omitempty"`
+}
+
+type ProxyRewrite struct {
+	Uri      string            `json:"uri"`
+	RegexUri []string          `json:"regex_uri"`
+	Scheme   string            `json:"scheme"`
+	Host     string            `json:"host"`
+	Headers  map[string]string `json:"headers"`
+}
+
+func (r ProxyRewrite) MarshalJSON() ([]byte, error) {
+	m := make(map[string]interface{})
+	if r.RegexUri != nil {
+		m["regex_uri"] = r.RegexUri
+	}
+	if r.Uri != "" {
+		m["uri"] = r.Uri
+	}
+	if r.Scheme != UPATHTYPE_KEEP && r.Scheme != "" {
+		m["scheme"] = r.Scheme
+	}
+	if r.Host != "" {
+		m["host"] = r.Host
+	}
+	if r.Headers != nil && len(r.Headers) > 0 {
+		m["headers"] = r.Headers
+	}
+	if result, err := json.Marshal(m); err != nil {
+		return nil, err
+	} else {
+		return result, nil
+	}
+}
+
+func (r Redirect) MarshalJSON() ([]byte, error) {
+	m := make(map[string]interface{})
+	if r.HttpToHttps {
+		m["http_to_https"] = true
+	} else {
+		m["code"] = r.Code
+		m["uri"] = r.Uri
+	}
+	if result, err := json.Marshal(m); err != nil {
+		return nil, err
+	} else {
+		return result, nil
+	}
+}
+
+type Upstream struct {
+	UType           string           `json:"type"`
+	Nodes           map[string]int64 `json:"nodes"`
+	Timeout         UpstreamTimeout  `json:"timeout"`
+	EnableWebsocket bool             `json:"enable_websocket"`
+}
+
+type UpstreamTimeout struct {
+	Connect int64 `json:"connect"`
+	Send    int64 `json:"send"`
+	Read    int64 `json:"read"`
+}
+
+type UpstreamPath struct {
+	UPathType string `json:"type"`
+	From      string `json:"from"`
+	To        string `json:"to"`
+}
+
+type ApisixRouteRequest struct {
+	Desc     string                 `json:"desc,omitempty"`
+	Priority int64                  `json:"priority"`
+	Methods  []string               `json:"methods,omitempty"`
+	Uris     []string               `json:"uris,omitempty"`
+	Hosts    []string               `json:"hosts,omitempty"`
+	Vars     [][]string             `json:"vars,omitempty"`
+	Upstream *Upstream              `json:"upstream,omitempty"`
+	Plugins  map[string]interface{} `json:"plugins,omitempty"`
+	//Name     string                 `json:"name"`
+}
+
+// ApisixRouteResponse is response from apisix admin api
+type ApisixRouteResponse struct {
+	Action string `json:"action"`
+	Node   *Node  `json:"node"`
+}
+
+type Node struct {
+	Value         Value  `json:"value"`
+	ModifiedIndex uint64 `json:"modifiedIndex"`
+}
+
+type Value struct {
+	Id         string                 `json:"id"`
+	Name       string                 `json:"name"`
+	Desc       string                 `json:"desc,omitempty"`
+	Priority   int64                  `json:"priority"`
+	Methods    []string               `json:"methods"`
+	Uris       []string               `json:"uris"`
+	Hosts      []string               `json:"hosts"`
+	Vars       [][]string             `json:"vars"`
+	Upstream   *Upstream              `json:"upstream,omitempty"`
+	UpstreamId string                 `json:"upstream_id,omitempty"`
+	Plugins    map[string]interface{} `json:"plugins"`
+}
+
+type Route struct {
+	Base
+	Name            string `json:"name"`
+	Description     string `json:"description,omitempty"`
+	Hosts           string `json:"hosts"`
+	Uris            string `json:"uris"`
+	UpstreamNodes   string `json:"upstream_nodes"`
+	UpstreamId      string `json:"upstream_id"`
+	Priority        int64  `json:"priority"`
+	Content         string `json:"content"`
+	ContentAdminApi string `json:"content_admin_api"`
+}
+
+type RouteResponse struct {
+	Base
+	Name        string    `json:"name"`
+	Description string    `json:"description,omitempty"`
+	Hosts       []string  `json:"hosts,omitempty"`
+	Uris        []string  `json:"uris,omitempty"`
+	Upstream    *Upstream `json:"upstream,omitempty"`
+	UpstreamId  string    `json:"upstream_id,omitempty"`
+	Priority    int64     `json:"priority"`
+}
+
+type ListResponse struct {
+	Count int         `json:"count"`
+	Data  interface{} `json:"data"`
+}
+
+func (rr *RouteResponse) Parse(r *Route) {
+	rr.Base = r.Base
+	rr.Name = r.Name
+	rr.Description = r.Description
+	rr.UpstreamId = r.UpstreamId
+	rr.Priority = r.Priority
+	// hosts
+	if len(r.Hosts) > 0 {
+		var hosts []string
+		if err := json.Unmarshal([]byte(r.Hosts), &hosts); err == nil {
+			rr.Hosts = hosts
+		} else {
+			logger.Error(err.Error())
+		}
+	}
+
+	// uris
+	if len(r.Uris) > 0 {
+		var uris []string
+		if err := json.Unmarshal([]byte(r.Uris), &uris); err == nil {
+			rr.Uris = uris
+		}
+	}
+
+	// uris
+	var resp ApisixRouteResponse
+	if err := json.Unmarshal([]byte(r.ContentAdminApi), &resp); err == nil {
+		rr.Upstream = resp.Node.Value.Upstream
+	}
+}
+
+// RouteRequest -> ApisixRouteRequest
+func ToApisixRequest(routeRequest *RouteRequest) *ApisixRouteRequest {
+	// redirect -> plugins
+	plugins := utils.CopyMap(routeRequest.Plugins)
+	redirect := routeRequest.Redirect
+	if redirect != nil {
+		plugins["redirect"] = redirect
+	}
+
+	logger.Info(routeRequest.Plugins)
+
+	// scheme https and not http -> vars ['scheme', '==', 'https']
+	pMap := utils.Set2Map(routeRequest.Protocols)
+
+	arr := &ApisixRouteRequest{}
+	arr.Parse(routeRequest)
+
+	// protocols[websokect] -> upstream
+	if pMap[WEBSOCKET] == 1 && arr.Upstream != nil {
+		arr.Upstream.EnableWebsocket = true
+	}
+	vars := utils.CopyStrings(routeRequest.Vars)
+	if pMap[HTTP] != 1 || pMap[HTTPS] != 1 {
+		if pMap[HTTP] == 1 {
+			vars = append(vars, []string{SCHEME, "==", HTTP})
+		}
+		if pMap[HTTPS] == 1 {
+			vars = append(vars, []string{SCHEME, "==", HTTPS})
+		}
+	}
+	if len(vars) > 0 {
+		arr.Vars = vars
+	} else {
+		arr.Vars = nil
+	}
+
+	// upstream protocol
+	if arr.Upstream != nil {
+		pr := &ProxyRewrite{}
+		pr.Scheme = routeRequest.UpstreamProtocol
+		// upstream path
+		proxyPath := routeRequest.UpstreamPath
+		if proxyPath != nil {
+			if proxyPath.UPathType == UPATHTYPE_STATIC {
+				pr.Uri = proxyPath.To
+				pr.RegexUri = nil
+			} else {
+				pr.RegexUri = []string{proxyPath.From, proxyPath.To}
+			}
+		}
+		// upstream headers
+		pr.Headers = routeRequest.UpstreamHeader
+		if proxyPath != nil {
+			plugins[PROXY_REWRIETE] = pr
+		}
+	}
+
+	if plugins != nil && len(plugins) > 0 {
+		arr.Plugins = plugins
+	} else {
+		arr.Plugins = nil
+	}
+	return arr
+}
+
+func ToRoute(routeRequest *RouteRequest,
+	arr *ApisixRouteRequest,
+	u4 uuid.UUID,
+	resp *ApisixRouteResponse) (*Route, *errno.ManagerError) {
+	rd := &Route{}
+	if err := rd.Parse(routeRequest, arr); err != nil {
+		e := errno.FromMessage(errno.DBRouteCreateError, err.Error())
+		return nil, e
+	}
+	if rd.Name == "" {
+		rd.Name = routeRequest.Name
+	}
+	rd.ID = u4
+	// content_admin_api
+	if respStr, err := json.Marshal(resp); err != nil {
+		e := errno.FromMessage(errno.DBRouteCreateError, err.Error())
+		return nil, e
+	} else {
+		rd.ContentAdminApi = string(respStr)
+	}
+	// hosts
+	hosts := resp.Node.Value.Hosts
+	if hb, err := json.Marshal(hosts); err != nil {
+		e := errno.FromMessage(errno.DBRouteCreateError, err.Error())
+		logger.Warn(e.Msg)
+	} else {
+		rd.Hosts = string(hb)
+	}
+	// uris
+	uris := resp.Node.Value.Uris
+	if ub, err := json.Marshal(uris); err != nil {
+		e := errno.FromMessage(errno.DBRouteCreateError, err.Error())
+		logger.Warn(e.Msg)
+	} else {
+		rd.Uris = string(ub)
+	}
+	// upstreamNodes
+	if resp.Node.Value.Upstream != nil {
+		nodes := resp.Node.Value.Upstream.Nodes
+		ips := make([]string, 0)
+		for k, _ := range nodes {
+			ips = append(ips, k)
+		}
+		if nb, err := json.Marshal(ips); err != nil {
+			e := errno.FromMessage(errno.DBRouteCreateError, err.Error())
+			logger.Warn(e.Msg)
+		} else {
+			rd.UpstreamNodes = string(nb)
+		}
+	}
+	return rd, nil
+}
diff --git a/api/service/route_test.go b/api/service/route_test.go
new file mode 100644
index 0000000..82652ce
--- /dev/null
+++ b/api/service/route_test.go
@@ -0,0 +1,139 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package service
+
+import (
+	"encoding/json"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestRedirectMarshal(t *testing.T) {
+	a := assert.New(t)
+	r := &Redirect{
+		HttpToHttps: true,
+		Code:        302,
+		Uri:         "/hello",
+	}
+	if result, err := json.Marshal(r); err != nil {
+		t.Error(err.Error())
+	} else {
+		h := make(map[string]interface{})
+		json.Unmarshal(result, &h)
+		a.Equal(1, len(h))
+		a.Equal(nil, h["code"])
+		a.Equal(true, h["http_to_https"])
+	}
+}
+
+func TestToApisixRequest_RediretPlugins(t *testing.T) {
+	rr := &RouteRequest{
+		ID:        "u guess a uuid",
+		Name:      "a special name",
+		Desc:      "any description",
+		Priority:  0,
+		Methods:   []string{"GET"},
+		Uris:      []string{},
+		Hosts:     []string{"www.baidu.com"},
+		Protocols: []string{"http", "https", "websocket"},
+		Redirect:  &Redirect{HttpToHttps: true, Code: 200, Uri: "/hello"},
+		Vars:      [][]string{},
+	}
+	ar := ToApisixRequest(rr)
+	a := assert.New(t)
+	a.Equal(1, len(ar.Plugins))
+	a.NotEqual(nil, ar.Plugins["redirect"])
+}
+
+func TestToApisixRequest_Vars(t *testing.T) {
+	rr := &RouteRequest{
+		ID:        "u guess a uuid",
+		Name:      "a special name",
+		Desc:      "any description",
+		Priority:  0,
+		Methods:   []string{"GET"},
+		Uris:      []string{},
+		Hosts:     []string{"www.baidu.com"},
+		Protocols: []string{"http", "https", "websocket"},
+		Redirect:  &Redirect{HttpToHttps: true, Code: 200, Uri: "/hello"},
+		Vars:      [][]string{},
+	}
+	ar := ToApisixRequest(rr)
+	a := assert.New(t)
+	b, err := json.Marshal(ar)
+	a.Equal(nil, err)
+
+	m := make(map[string]interface{})
+	err = json.Unmarshal(b, &m)
+	a.Equal(nil, err)
+	t.Log(m["vars"])
+	a.Equal(nil, m["vars"])
+}
+
+func TestToApisixRequest_Upstream(t *testing.T) {
+	nodes := make(map[string]int64)
+	nodes["127.0.0.1:8080"] = 100
+	upstream := &Upstream{
+		UType:   "roundrobin",
+		Nodes:   nodes,
+		Timeout: UpstreamTimeout{15, 15, 15},
+	}
+	rr := &RouteRequest{
+		ID:        "u guess a uuid",
+		Name:      "a special name",
+		Desc:      "any description",
+		Priority:  0,
+		Methods:   []string{"GET"},
+		Uris:      []string{},
+		Hosts:     []string{"www.baidu.com"},
+		Protocols: []string{"http", "https", "websocket"},
+		Redirect:  &Redirect{HttpToHttps: true, Code: 200, Uri: "/hello"},
+		Vars:      [][]string{},
+		Upstream:  upstream,
+	}
+	ar := ToApisixRequest(rr)
+	a := assert.New(t)
+	a.Equal("roundrobin", ar.Upstream.UType)
+	a.Equal(true, ar.Upstream.EnableWebsocket)
+}
+
+func TestToApisixRequest_UpstreamUnable(t *testing.T) {
+	nodes := make(map[string]int64)
+	nodes["127.0.0.1:8080"] = 100
+	upstream := &Upstream{
+		UType:   "roundrobin",
+		Nodes:   nodes,
+		Timeout: UpstreamTimeout{15, 15, 15},
+	}
+	rr := &RouteRequest{
+		ID:        "u guess a uuid",
+		Name:      "a special name",
+		Desc:      "any description",
+		Priority:  0,
+		Methods:   []string{"GET"},
+		Uris:      []string{},
+		Hosts:     []string{"www.baidu.com"},
+		Protocols: []string{"http", "https"},
+		Redirect:  &Redirect{HttpToHttps: true, Code: 200, Uri: "/hello"},
+		Vars:      [][]string{},
+		Upstream:  upstream,
+	}
+	ar := ToApisixRequest(rr)
+	a := assert.New(t)
+	a.Equal("roundrobin", ar.Upstream.UType)
+	a.Equal(false, ar.Upstream.EnableWebsocket)
+}
diff --git a/api/service/ssl.go b/api/service/ssl.go
new file mode 100644
index 0000000..839e3b8
--- /dev/null
+++ b/api/service/ssl.go
@@ -0,0 +1,300 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package service
+
+import (
+	"crypto/tls"
+	"crypto/x509"
+	"encoding/json"
+	"encoding/pem"
+	"errors"
+	"fmt"
+
+	"github.com/satori/go.uuid"
+
+	"github.com/apisix/manager-api/conf"
+	"github.com/apisix/manager-api/errno"
+	"github.com/apisix/manager-api/utils"
+)
+
+type Ssl struct {
+	Base
+	ValidityStart uint64 `json:"validity_start"`
+	ValidityEnd   uint64 `json:"validity_end"`
+	Snis          string `json:"snis"`
+	Status        uint64 `json:"status"`
+	PublicKey     string `json:"public_key,omitempty"`
+}
+
+type SslDto struct {
+	Base
+	ValidityStart uint64   `json:"validity_start"`
+	ValidityEnd   uint64   `json:"validity_end"`
+	Snis          []string `json:"snis"`
+	Status        uint64   `json:"status"`
+	PublicKey     string   `json:"public_key,omitempty"`
+}
+
+type SslRequest struct {
+	ID         string   `json:"id,omitempty"`
+	PublicKey  string   `json:"cert"`
+	PrivateKey string   `json:"key"`
+	Snis       []string `json:"snis"`
+}
+
+// ApisixSslResponse is response from apisix admin api
+type ApisixSslResponse struct {
+	Action string   `json:"action"`
+	Node   *SslNode `json:"node"`
+}
+
+type SslNode struct {
+	Value         SslRequest `json:"value"`
+	ModifiedIndex uint64     `json:"modifiedIndex"`
+}
+
+func (req *SslRequest) Parse(body interface{}) {
+	if err := json.Unmarshal(body.([]byte), req); err != nil {
+		req = nil
+		logger.Error(errno.FromMessage(errno.RouteRequestError, err.Error()).Msg)
+	}
+}
+
+func (sslDto *SslDto) Parse(ssl *Ssl) error {
+	sslDto.ID = ssl.ID
+	sslDto.ValidityStart = ssl.ValidityStart
+	sslDto.ValidityEnd = ssl.ValidityEnd
+
+	var snis []string
+	_ = json.Unmarshal([]byte(ssl.Snis), &snis)
+	sslDto.Snis = snis
+
+	sslDto.Status = ssl.Status
+	sslDto.PublicKey = ssl.PublicKey
+	sslDto.CreateTime = ssl.CreateTime
+	sslDto.UpdateTime = ssl.UpdateTime
+
+	return nil
+}
+
+func SslList(page, size int) ([]byte, error) {
+	var count int
+	sslList := []Ssl{}
+	if err := conf.DB().Table("ssls").Offset((page - 1) * size).Limit(size).Find(&sslList).Count(&count).Error; err != nil {
+		return nil, err
+	}
+
+	sslDtoList := []SslDto{}
+
+	for _, ssl := range sslList {
+		sslDto := SslDto{}
+		sslDto.Parse(&ssl)
+
+		sslDtoList = append(sslDtoList, sslDto)
+	}
+
+	data := errno.FromMessage(errno.SystemSuccess).ListResponse(count, sslDtoList)
+
+	return json.Marshal(data)
+}
+
+func SslItem(id string) ([]byte, error) {
+	ssl := &Ssl{}
+	if err := conf.DB().Table("ssls").Where("id = ?", id).First(ssl).Error; err != nil {
+		return nil, err
+	}
+
+	sslDto := &SslDto{}
+	sslDto.Parse(ssl)
+
+	data := errno.FromMessage(errno.SystemSuccess).ItemResponse(sslDto)
+
+	return json.Marshal(data)
+}
+
+func SslCheck(param interface{}) ([]byte, error) {
+	sslReq := &SslRequest{}
+	sslReq.Parse(param)
+
+	ssl, err := ParseCert(sslReq.PublicKey, sslReq.PrivateKey)
+
+	if err != nil {
+		return nil, err
+	}
+
+	ssl.PublicKey = ""
+
+	sslDto := &SslDto{}
+	sslDto.Parse(ssl)
+
+	data := errno.FromMessage(errno.SystemSuccess).ItemResponse(sslDto)
+
+	return json.Marshal(data)
+}
+
+func SslCreate(param interface{}, id string) error {
+	sslReq := &SslRequest{}
+	sslReq.Parse(param)
+
+	ssl, err := ParseCert(sslReq.PublicKey, sslReq.PrivateKey)
+
+	if err != nil {
+		return err
+	}
+
+	// first admin api
+	var snis []string
+	_ = json.Unmarshal([]byte(ssl.Snis), &snis)
+	sslReq.Snis = snis
+
+	if _, err := sslReq.PutToApisix(id); err != nil {
+		return err
+	}
+	// then mysql
+	ssl.ID = uuid.FromStringOrNil(id)
+	if err := conf.DB().Create(ssl).Error; err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func SslUpdate(param interface{}, id string) error {
+	sslReq := &SslRequest{}
+	sslReq.Parse(param)
+
+	ssl, err := ParseCert(sslReq.PublicKey, sslReq.PrivateKey)
+
+	if err != nil {
+		return err
+	}
+
+	// first admin api
+	var snis []string
+	_ = json.Unmarshal([]byte(ssl.Snis), &snis)
+	sslReq.Snis = snis
+
+	if _, err := sslReq.PutToApisix(id); err != nil {
+		return err
+	}
+
+	// then mysql
+	ssl.ID = uuid.FromStringOrNil(id)
+	data := Ssl{PublicKey: ssl.PublicKey, Snis: ssl.Snis, ValidityStart: ssl.ValidityStart, ValidityEnd: ssl.ValidityEnd}
+	if err := conf.DB().Model(&ssl).Updates(data).Error; err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func SslDelete(id string) error {
+	// delete from apisix
+	request := &SslRequest{}
+	request.ID = id
+	if _, err := request.DeleteFromApisix(); err != nil {
+		return err
+	}
+	// delete from mysql
+	ssl := &Ssl{}
+	ssl.ID = uuid.FromStringOrNil(id)
+	if err := conf.DB().Delete(ssl).Error; err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (req *SslRequest) PutToApisix(rid string) (*ApisixSslResponse, error) {
+	url := fmt.Sprintf("%s/ssl/%s", conf.BaseUrl, rid)
+	if data, err := json.Marshal(req); err != nil {
+		return nil, err
+	} else {
+		if resp, err := utils.Put(url, data); err != nil {
+			logger.Error(url)
+			logger.Error(string(data))
+			logger.Error(err.Error())
+			return nil, err
+		} else {
+			var arresp ApisixSslResponse
+			if err := json.Unmarshal(resp, &arresp); err != nil {
+				logger.Error(err.Error())
+				return nil, err
+			} else {
+				return &arresp, nil
+			}
+		}
+	}
+}
+
+func (req *SslRequest) DeleteFromApisix() (*ApisixSslResponse, error) {
+	id := req.ID
+	url := fmt.Sprintf("%s/ssl/%s", conf.BaseUrl, id)
+
+	if resp, err := utils.Delete(url); err != nil {
+		logger.Error(err.Error())
+		return nil, err
+	} else {
+		var arresp ApisixSslResponse
+		if err := json.Unmarshal(resp, &arresp); err != nil {
+			logger.Error(err.Error())
+			return nil, err
+		} else {
+			return &arresp, nil
+		}
+	}
+}
+
+func ParseCert(crt, key string) (*Ssl, error) {
+	// print private key
+	certDERBlock, _ := pem.Decode([]byte(crt))
+	if certDERBlock == nil {
+		return nil, errors.New("Certificate resolution failed")
+	}
+	// match
+	_, err := tls.X509KeyPair([]byte(crt), []byte(key))
+	if err != nil {
+		return nil, err
+	}
+
+	x509Cert, err := x509.ParseCertificate(certDERBlock.Bytes)
+
+	if err != nil {
+		return nil, errors.New("Certificate resolution failed")
+	} else {
+		ssl := Ssl{}
+
+		//domain
+		snis := []byte{}
+		if x509Cert.DNSNames == nil || len(x509Cert.DNSNames) < 1 {
+			tmp := []string{}
+			if x509Cert.Subject.CommonName != "" {
+				tmp = []string{x509Cert.Subject.CommonName}
+			}
+			snis, _ = json.Marshal(tmp)
+		} else {
+			snis, _ = json.Marshal(x509Cert.DNSNames)
+		}
+		ssl.Snis = string(snis)
+
+		ssl.ValidityStart = uint64(x509Cert.NotBefore.Unix())
+		ssl.ValidityEnd = uint64(x509Cert.NotAfter.Unix())
+		ssl.PublicKey = crt
+
+		return &ssl, nil
+	}
+}
diff --git a/babel.config.js b/api/utils/copy.go
similarity index 62%
rename from babel.config.js
rename to api/utils/copy.go
index e7708a0..30f228a 100644
--- a/babel.config.js
+++ b/api/utils/copy.go
@@ -14,9 +14,28 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package utils
 
-module.exports = {
-  presets: [
-    '@vue/app'
-  ]
+func CopyMap(origin map[string]interface{}) map[string]interface{} {
+	result := make(map[string]interface{})
+	for k, v := range origin {
+		result[k] = v
+	}
+	return result
+}
+
+func CopyStrings(origin [][]string) [][]string {
+	result := make([][]string, 0)
+	for _, s := range origin {
+		result = append(result, s)
+	}
+	return result
+}
+
+func Set2Map(origin []string) map[string]int {
+	result := make(map[string]int)
+	for _, s := range origin {
+		result[s] = 1
+	}
+	return result
 }
diff --git a/api/utils/http.go b/api/utils/http.go
new file mode 100644
index 0000000..119cf0b
--- /dev/null
+++ b/api/utils/http.go
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package utils
+
+import (
+	"fmt"
+	"github.com/apisix/manager-api/conf"
+	"github.com/apisix/manager-api/log"
+	"gopkg.in/resty.v1"
+	"net/http"
+	"time"
+)
+
+const timeout = 3000
+
+var logger = log.GetLogger()
+
+func Get(url string) ([]byte, error) {
+	r := resty.New().
+		SetTimeout(time.Duration(timeout)*time.Millisecond).
+		R().
+		SetHeader("content-type", "application/json").
+		SetHeader("X-API-KEY", conf.ApiKey)
+	resp, err := r.Get(url)
+	if err != nil {
+		return nil, err
+	}
+	if resp.StatusCode() != http.StatusOK {
+		return nil, fmt.Errorf("status: %d, body: %s", resp.StatusCode(), resp.Body())
+	}
+	return resp.Body(), nil
+}
+
+func Post(url string, bytes []byte) ([]byte, error) {
+	r := resty.New().
+		SetTimeout(time.Duration(timeout)*time.Millisecond).
+		R().
+		SetHeader("content-type", "application/json").
+		SetHeader("X-API-KEY", conf.ApiKey)
+	r.SetBody(bytes)
+	resp, err := r.Post(url)
+	if err != nil {
+		return nil, err
+	}
+	if resp.StatusCode() != http.StatusOK && resp.StatusCode() != http.StatusCreated {
+		return nil, fmt.Errorf("status: %d, body: %s", resp.StatusCode(), resp.Body())
+	}
+	return resp.Body(), nil
+}
+
+func Put(url string, bytes []byte) ([]byte, error) {
+	r := resty.New().
+		SetTimeout(time.Duration(timeout)*time.Millisecond).
+		R().
+		SetHeader("content-type", "application/json").
+		SetHeader("X-API-KEY", conf.ApiKey)
+	r.SetBody(bytes)
+	resp, err := r.Put(url)
+	if err != nil {
+		return nil, err
+	}
+	if resp.StatusCode() != http.StatusOK && resp.StatusCode() != http.StatusCreated {
+		return nil, fmt.Errorf("status: %d, body: %s", resp.StatusCode(), resp.Body())
+	}
+	return resp.Body(), nil
+}
+
+func Patch(url string, bytes []byte) ([]byte, error) {
+	r := resty.New().
+		SetTimeout(time.Duration(timeout)*time.Millisecond).
+		R().
+		SetHeader("content-type", "application/json").
+		SetHeader("X-API-KEY", conf.ApiKey)
+	r.SetBody(bytes)
+	resp, err := r.Patch(url)
+	if err != nil {
+		return nil, err
+	}
+	if resp.StatusCode() != http.StatusOK {
+		return nil, fmt.Errorf("status: %d, body: %s", resp.StatusCode(), resp.Body())
+	}
+	return resp.Body(), nil
+}
+
+func Delete(url string) ([]byte, error) {
+	r := resty.New().
+		SetTimeout(time.Duration(timeout)*time.Millisecond).
+		R().
+		SetHeader("content-type", "application/json").
+		SetHeader("X-API-KEY", conf.ApiKey)
+	resp, err := r.Delete(url)
+	if err != nil {
+		return nil, err
+	}
+	if resp.StatusCode() != http.StatusOK {
+		return nil, fmt.Errorf("status: %d, body: %s", resp.StatusCode(), resp.Body())
+	}
+	return resp.Body(), nil
+}
diff --git a/compose/README.md b/compose/README.md
new file mode 100644
index 0000000..7d7c7ce
--- /dev/null
+++ b/compose/README.md
@@ -0,0 +1,56 @@
+## Deploy
+
+```sh
+$ cd incubator-apisix-dashboard/compose
+
+$ chmod +x ./manager_conf/build.sh
+
+$ docker-compose -p dashboard up -d
+```
+
+## Usage
+
+### 1. login dashboard
+
+Visit `http://127.0.0.1/dashboard/` in the browser, 
+Enter `http://127.0.0.1:8080/apisix/admin` into the first input box, this is the backend management service address
+
+![login](pics/login.png)
+
+now, click `save`.
+
+### 2. If you want to display the grafana metric dashboard, please fill in the grafana shared link as follows
+
+1.get grafana shared link
+
+Visit `http://127.0.0.1:3000/?search=open&orgId=1`
+
+![login](pics/grafana_1.png)
+
+click `Apache APISIX` dashboard, and you can see the page as follow
+
+![login](pics/grafana_2.png)
+
+click the button `shard dashboard` on the right of `Apache APISIX`
+
+![login](pics/grafana_3.png)
+
+copy the link, and then return to dashboard on the step 1
+
+![login](pics/grafana_4.png)
+
+click metric on the left, and then the config button
+
+Paste shared link
+
+![login](pics/grafana_5.png)
+
+save, and you can see the metrics
+ 
+![login](pics/grafana_6.png)
+ 
+
+
+
+
+
diff --git a/compose/apisix_conf/config.yaml b/compose/apisix_conf/config.yaml
new file mode 100644
index 0000000..bbdc65b
--- /dev/null
+++ b/compose/apisix_conf/config.yaml
@@ -0,0 +1,137 @@
+apisix:
+  node_listen: 9080              # APISIX listening port
+  enable_heartbeat: true
+  enable_admin: true
+  enable_admin_cors: true         # Admin API support CORS response headers.
+  enable_debug: false
+  enable_dev_mode: false          # Sets nginx worker_processes to 1 if set to true
+  enable_reuseport: true          # Enable nginx SO_REUSEPORT switch if set to true.
+  enable_ipv6: true
+  config_center: etcd             # etcd: use etcd to store the config value
+                                  # yaml: fetch the config value from local yaml file `/your_path/conf/apisix.yaml`
+
+  #proxy_protocol:                 # Proxy Protocol configuration
+  #  listen_http_port: 9181        # The port with proxy protocol for http, it differs from node_listen and port_admin.
+                                   # This port can only receive http request with proxy protocol, but node_listen & port_admin
+                                   # can only receive http request. If you enable proxy protocol, you must use this port to
+                                   # receive http request with proxy protocol
+  #  listen_https_port: 9182       # The port with proxy protocol for https
+  #  enable_tcp_pp: true           # Enable the proxy protocol for tcp proxy, it works for stream_proxy.tcp option
+  #  enable_tcp_pp_to_upstream: true # Enables the proxy protocol to the upstream server
+
+  proxy_cache:                     # Proxy Caching configuration
+    cache_ttl: 10s                 # The default caching time if the upstream does not specify the cache time
+    zones:                         # The parameters of a cache
+    - name: disk_cache_one         # The name of the cache, administrator can be specify
+                                   # which cache to use by name in the admin api
+      memory_size: 50m             # The size of shared memory, it's used to store the cache index
+      disk_size: 1G                # The size of disk, it's used to store the cache data
+      disk_path: "/tmp/disk_cache_one" # The path to store the cache data
+      cache_levels: "1:2"           # The hierarchy levels of a cache
+  #  - name: disk_cache_two
+  #    memory_size: 50m
+  #    disk_size: 1G
+  #    disk_path: "/tmp/disk_cache_two"
+  #    cache_levels: "1:2"
+
+#  allow_admin:                  # http://nginx.org/en/docs/http/ngx_http_access_module.html#allow
+#    - 127.0.0.0/24              # If we don't set any IP list, then any IP access is allowed by default.
+#    - 172.17.0.0/24
+  #   - "::/64"
+  # port_admin: 9180              # use a separate port
+
+  # Default token when use API to call for Admin API.
+  # *NOTE*: Highly recommended to modify this value to protect APISIX's Admin API.
+  # Disabling this configuration item means that the Admin API does not
+  # require any authentication.
+  admin_key:
+    -
+      name: "admin"
+      key: edd1c9f034335f136f87ad84b625c8f1
+      role: admin                 # admin: manage all configuration data
+                                  # viewer: only can view configuration data
+    -
+      name: "viewer"
+      key: 4054f7cf07e344346cd3f287985e76a2
+      role: viewer
+  router:
+    http: 'radixtree_uri'         # radixtree_uri: match route by uri(base on radixtree)
+                                  # radixtree_host_uri: match route by host + uri(base on radixtree)
+    ssl: 'radixtree_sni'          # radixtree_sni: match route by SNI(base on radixtree)
+  # stream_proxy:                 # TCP/UDP proxy
+  #   tcp:                        # TCP proxy port list
+  #     - 9100
+  #     - 9101
+  #   udp:                        # UDP proxy port list
+  #     - 9200
+  #     - 9211
+  dns_resolver:                   # default DNS resolver, with disable IPv6 and enable local DNS
+    - 127.0.0.11
+    - 114.114.114.114
+    - 223.5.5.5
+    - 1.1.1.1
+    - 8.8.8.8
+  dns_resolver_valid: 30          # valid time for dns result 30 seconds
+  resolver_timeout: 5             # resolver timeout
+  ssl:
+    enable: true
+    enable_http2: true
+    listen_port: 9443
+    ssl_protocols: "TLSv1 TLSv1.1 TLSv1.2 TLSv1.3"
+    ssl_ciphers: "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES2 [...]
+
+nginx_config:                     # config for render the template to genarate nginx.conf
+  error_log: "logs/error.log"
+  error_log_level: "warn"         # warn,error
+  worker_rlimit_nofile: 20480     # the number of files a worker process can open, should be larger than worker_connections
+  event:
+    worker_connections: 10620
+  http:
+    access_log: "logs/access.log"
+    keepalive_timeout: 60s         # timeout during which a keep-alive client connection will stay open on the server side.
+    client_header_timeout: 60s     # timeout for reading client request header, then 408 (Request Time-out) error is returned to the client
+    client_body_timeout: 60s       # timeout for reading client request body, then 408 (Request Time-out) error is returned to the client
+    send_timeout: 10s              # timeout for transmitting a response to the client.then the connection is closed
+    underscores_in_headers: "on"   # default enables the use of underscores in client request header fields
+    real_ip_header: "X-Real-IP"    # http://nginx.org/en/docs/http/ngx_http_realip_module.html#real_ip_header
+    real_ip_from:                  # http://nginx.org/en/docs/http/ngx_http_realip_module.html#set_real_ip_from
+      - 127.0.0.1
+      - 'unix:'
+    #lua_shared_dicts:              # add custom shared cache to nginx.conf
+    #  ipc_shared_dict: 100m        # custom shared cache, format: `cache-key: cache-size`
+
+etcd:
+  host:                           # it's possible to define multiple etcd hosts addresses of the same etcd cluster.
+    - "http://192.17.5.10:2379"     # multiple etcd address
+  prefix: "/apisix"               # apisix configurations prefix
+  timeout: 3                      # 3 seconds
+
+plugins:                          # plugin list
+  - example-plugin
+  - limit-req
+  - limit-count
+  - limit-conn
+  - key-auth
+  - basic-auth
+  - prometheus
+  - node-status
+  - jwt-auth
+  - zipkin
+  - ip-restriction
+  - grpc-transcode
+  - serverless-pre-function
+  - serverless-post-function
+  - openid-connect
+  - proxy-rewrite
+  - redirect
+  - response-rewrite
+  - fault-injection
+  - udp-logger
+  - wolf-rbac
+  - proxy-cache
+  - tcp-logger
+  - proxy-mirror
+  - kafka-logger
+  - cors
+stream_plugins:
+  - mqtt-proxy
diff --git a/compose/docker-compose.yml b/compose/docker-compose.yml
new file mode 100644
index 0000000..859e390
--- /dev/null
+++ b/compose/docker-compose.yml
@@ -0,0 +1,124 @@
+version: "3"
+
+services:
+  apisix:
+    image: apache/apisix:latest
+    restart: always
+    volumes:
+      - ./apisix_log:/usr/local/apisix/logs
+      - ./apisix_conf/config.yaml:/usr/local/apisix/conf/config.yaml:ro
+    depends_on:
+      - etcd
+    ##network_mode: host
+    ports:
+      - "9080:9080/tcp"
+      - "9443:9443/tcp"
+    networks:
+      apisix-dashboard:
+        ipv4_address: 192.17.5.11
+
+  etcd:
+    image: gcr.io/etcd-development/etcd:v3.3.12
+    command: /usr/local/bin/etcd --advertise-client-urls http://0.0.0.0:2379 --listen-client-urls http://0.0.0.0:2379
+    restart: always
+    volumes:
+      - ./etcd_data:/etcd_data
+    environment:
+      ETCD_DATA_DIR: /etcd_data
+    ports:
+      - "2379:2379/tcp"
+    networks:
+      apisix-dashboard:
+        ipv4_address: 192.17.5.10
+
+  web1:
+    image: ruby:2-alpine
+    command: sh -c "mkdir -p /tmp/www && echo 'web1' > /tmp/www/web1.txt && ruby -run -ehttpd /tmp/www -p8000"
+    restart: always
+    ports:
+      - "9081:8000/tcp"
+    networks:
+      apisix-dashboard:
+        ipv4_address: 192.17.5.12
+
+  web2:
+    image: ruby:2-alpine
+    command: sh -c "mkdir -p /tmp/www && echo 'web2' > /tmp/www/web2.txt && ruby -run -ehttpd /tmp/www -p8000"
+    restart: always
+    ports:
+      - "9082:8000/tcp"
+    networks:
+      apisix-dashboard:
+        ipv4_address: 192.17.5.13
+
+  mysql:
+    image: mysql:latest
+    restart: always
+    ports:
+      - "3309:3306/tcp"
+    environment:
+      - MYSQL_ROOT_PASSWORD=123456
+    volumes:
+      - /tmp/datadir:/var/lib/mysql
+      - /tmp/conf.d:/etc/mysql/conf.d
+      - ../api/script/db:/docker-entrypoint-initdb.d
+    networks:
+      apisix-dashboard:
+        ipv4_address: 192.17.5.14
+  
+  manager:
+    build:
+      context: ./../api
+      dockerfile: Dockerfile
+    restart: always
+    ports:
+      - "8080:8080/tcp"
+    environment:
+      - ENV=prod
+    volumes:
+      - ./manager_conf/build.sh:/root/manager-api/build.sh
+    networks:
+      apisix-dashboard:
+        ipv4_address: 192.17.5.15
+
+  prometheus:
+    image: prom/prometheus
+    hostname: prometheus
+    restart: always
+    volumes:
+      - ./prometheus_conf/prometheus.yml:/etc/prometheus/prometheus.yml
+    ports:
+      - "9090:9090"
+    networks:
+      apisix-dashboard:
+        ipv4_address: 192.17.5.16
+
+  grafana:
+    image: grafana/grafana
+    container_name: grafana
+    hostname: grafana
+    restart: always
+    ports:
+      - "3000:3000"
+    volumes:
+      - "./grafana_conf/provisioning:/etc/grafana/provisioning"
+      - "./grafana_conf/dashboards:/var/lib/grafana/dashboards"
+      - "./grafana_conf/config/grafana.ini:/etc/grafana/grafana.ini"
+    networks:
+      apisix-dashboard:
+        ipv4_address: 192.17.5.17
+
+  dashboard:
+    build:
+      context: ./..
+      dockerfile: Dockerfile
+    restart: always
+    ports:
+      - "80:80/tcp"
+
+networks:
+  apisix-dashboard:
+    driver: bridge
+    ipam:
+      config:
+      - subnet: 192.17.0.0/16
\ No newline at end of file
diff --git a/compose/grafana_conf/config/grafana.ini b/compose/grafana_conf/config/grafana.ini
new file mode 100644
index 0000000..cb6a737
--- /dev/null
+++ b/compose/grafana_conf/config/grafana.ini
@@ -0,0 +1,756 @@
+##################### Grafana Configuration Example #####################
+#
+# Everything has defaults so you only need to uncomment things you want to
+# change
+
+# possible values : production, development
+;app_mode = production
+
+# instance name, defaults to HOSTNAME environment variable value or hostname if HOSTNAME var is empty
+;instance_name = ${HOSTNAME}
+
+#################################### Paths ####################################
+[paths]
+# Path to where grafana can store temp files, sessions, and the sqlite3 db (if that is used)
+;data = /var/lib/grafana
+
+# Temporary files in `data` directory older than given duration will be removed
+;temp_data_lifetime = 24h
+
+# Directory where grafana can store logs
+;logs = /var/log/grafana
+
+# Directory where grafana will automatically scan and look for plugins
+;plugins = /var/lib/grafana/plugins
+
+# folder that contains provisioning config files that grafana will apply on startup and while running.
+;provisioning = conf/provisioning
+
+#################################### Server ####################################
+[server]
+# Protocol (http, https, h2, socket)
+;protocol = http
+
+# The ip address to bind to, empty will bind to all interfaces
+;http_addr =
+
+# The http port  to use
+;http_port = 3000
+
+# The public facing domain name used to access grafana from a browser
+;domain = localhost
+
+# Redirect to correct domain if host header does not match domain
+# Prevents DNS rebinding attacks
+;enforce_domain = false
+
+# The full public facing url you use in browser, used for redirects and emails
+# If you use reverse proxy and sub path specify full url (with sub path)
+;root_url = %(protocol)s://%(domain)s:%(http_port)s/
+
+# Serve Grafana from subpath specified in `root_url` setting. By default it is set to `false` for compatibility reasons.
+;serve_from_sub_path = false
+
+# Log web requests
+;router_logging = false
+
+# the path relative working path
+;static_root_path = public
+
+# enable gzip
+;enable_gzip = false
+
+# https certs & key file
+;cert_file =
+;cert_key =
+
+# Unix socket path
+;socket =
+
+#################################### Database ####################################
+[database]
+# You can configure the database connection by specifying type, host, name, user and password
+# as separate properties or as on string using the url properties.
+
+# Either "mysql", "postgres" or "sqlite3", it's your choice
+;type = sqlite3
+;host = 127.0.0.1:3306
+;name = grafana
+;user = root
+# If the password contains # or ; you have to wrap it with triple quotes. Ex """#password;"""
+;password =
+
+# Use either URL or the previous fields to configure the database
+# Example: mysql://user:secret@host:port/database
+;url =
+
+# For "postgres" only, either "disable", "require" or "verify-full"
+;ssl_mode = disable
+
+;ca_cert_path =
+;client_key_path =
+;client_cert_path =
+;server_cert_name =
+
+# For "sqlite3" only, path relative to data_path setting
+;path = grafana.db
+
+# Max idle conn setting default is 2
+;max_idle_conn = 2
+
+# Max conn setting default is 0 (mean not set)
+;max_open_conn =
+
+# Connection Max Lifetime default is 14400 (means 14400 seconds or 4 hours)
+;conn_max_lifetime = 14400
+
+# Set to true to log the sql calls and execution times.
+;log_queries =
+
+# For "sqlite3" only. cache mode setting used for connecting to the database. (private, shared)
+;cache_mode = private
+
+#################################### Cache server #############################
+[remote_cache]
+# Either "redis", "memcached" or "database" default is "database"
+;type = database
+
+# cache connectionstring options
+# database: will use Grafana primary database.
+# redis: config like redis server e.g. `addr=127.0.0.1:6379,pool_size=100,db=0,ssl=false`. Only addr is required. ssl may be 'true', 'false', or 'insecure'.
+# memcache: 127.0.0.1:11211
+;connstr =
+
+#################################### Data proxy ###########################
+[dataproxy]
+
+# This enables data proxy logging, default is false
+;logging = false
+
+# How long the data proxy should wait before timing out default is 30 (seconds)
+;timeout = 30
+
+# If enabled and user is not anonymous, data proxy will add X-Grafana-User header with username into the request, default is false.
+;send_user_header = false
+
+#################################### Analytics ####################################
+[analytics]
+# Server reporting, sends usage counters to stats.grafana.org every 24 hours.
+# No ip addresses are being tracked, only simple counters to track
+# running instances, dashboard and error counts. It is very helpful to us.
+# Change this option to false to disable reporting.
+;reporting_enabled = true
+
+# Set to false to disable all checks to https://grafana.net
+# for new vesions (grafana itself and plugins), check is used
+# in some UI views to notify that grafana or plugin update exists
+# This option does not cause any auto updates, nor send any information
+# only a GET request to http://grafana.com to get latest versions
+;check_for_updates = true
+
+# Google Analytics universal tracking code, only enabled if you specify an id here
+;google_analytics_ua_id =
+
+# Google Tag Manager ID, only enabled if you specify an id here
+;google_tag_manager_id =
+
+#################################### Security ####################################
+[security]
+# disable creation of admin user on first start of grafana
+;disable_initial_admin_creation = false
+
+# default admin user, created on startup
+;admin_user = admin
+
+# default admin password, can be changed before first start of grafana,  or in profile settings
+;admin_password = admin
+
+# used for signing
+;secret_key = SW2YcwTIb9zpOOhoPsMm
+
+# disable gravatar profile images
+;disable_gravatar = false
+
+# data source proxy whitelist (ip_or_domain:port separated by spaces)
+;data_source_proxy_whitelist =
+
+# disable protection against brute force login attempts
+;disable_brute_force_login_protection = false
+
+# set to true if you host Grafana behind HTTPS. default is false.
+;cookie_secure = false
+
+# set cookie SameSite attribute. defaults to `lax`. can be set to "lax", "strict", "none" and "disabled"
+;cookie_samesite = none
+
+# set to true if you want to allow browsers to render Grafana in a <frame>, <iframe>, <embed> or <object>. default is false.
+allow_embedding = true
+
+# Set to true if you want to enable http strict transport security (HSTS) response header.
+# This is only sent when HTTPS is enabled in this configuration.
+# HSTS tells browsers that the site should only be accessed using HTTPS.
+# The default version will change to true in the next minor release, 6.3.
+;strict_transport_security = false
+
+# Sets how long a browser should cache HSTS. Only applied if strict_transport_security is enabled.
+;strict_transport_security_max_age_seconds = 86400
+
+# Set to true if to enable HSTS preloading option. Only applied if strict_transport_security is enabled.
+;strict_transport_security_preload = false
+
+# Set to true if to enable the HSTS includeSubDomains option. Only applied if strict_transport_security is enabled.
+;strict_transport_security_subdomains = false
+
+# Set to true to enable the X-Content-Type-Options response header.
+# The X-Content-Type-Options response HTTP header is a marker used by the server to indicate that the MIME types advertised
+# in the Content-Type headers should not be changed and be followed. The default will change to true in the next minor release, 6.3.
+;x_content_type_options = false
+
+# Set to true to enable the X-XSS-Protection header, which tells browsers to stop pages from loading
+# when they detect reflected cross-site scripting (XSS) attacks. The default will change to true in the next minor release, 6.3.
+;x_xss_protection = false
+
+#################################### Snapshots ###########################
+[snapshots]
+# snapshot sharing options
+;external_enabled = true
+;external_snapshot_url = https://snapshots-origin.raintank.io
+;external_snapshot_name = Publish to snapshot.raintank.io
+
+# Set to true to enable this Grafana instance act as an external snapshot server and allow unauthenticated requests for
+# creating and deleting snapshots.
+;public_mode = false
+
+# remove expired snapshot
+;snapshot_remove_expired = true
+
+#################################### Dashboards History ##################
+[dashboards]
+# Number dashboard versions to keep (per dashboard). Default: 20, Minimum: 1
+;versions_to_keep = 20
+
+# Minimum dashboard refresh interval. When set, this will restrict users to set the refresh interval of a dashboard lower than given interval. Per default this is 5 seconds.
+# The interval string is a possibly signed sequence of decimal numbers, followed by a unit suffix (ms, s, m, h, d), e.g. 30s or 1m.
+;min_refresh_interval = 5s
+
+#################################### Users ###############################
+[users]
+# disable user signup / registration
+;allow_sign_up = true
+
+# Allow non admin users to create organizations
+;allow_org_create = true
+
+# Set to true to automatically assign new users to the default organization (id 1)
+;auto_assign_org = true
+
+# Set this value to automatically add new users to the provided organization (if auto_assign_org above is set to true)
+;auto_assign_org_id = 1
+
+# Default role new users will be automatically assigned (if disabled above is set to true)
+;auto_assign_org_role = Viewer
+
+# Require email validation before sign up completes
+;verify_email_enabled = false
+
+# Background text for the user field on the login page
+;login_hint = email or username
+;password_hint = password
+
+# Default UI theme ("dark" or "light")
+;default_theme = dark
+
+# External user management, these options affect the organization users view
+;external_manage_link_url =
+;external_manage_link_name =
+;external_manage_info =
+
+# Viewers can edit/inspect dashboard settings in the browser. But not save the dashboard.
+;viewers_can_edit = false
+
+# Editors can administrate dashboard, folders and teams they create
+;editors_can_admin = false
+
+[auth]
+# Login cookie name
+;login_cookie_name = grafana_session
+
+# The lifetime (days) an authenticated user can be inactive before being required to login at next visit. Default is 7 days,
+;login_maximum_inactive_lifetime_days = 7
+
+# The maximum lifetime (days) an authenticated user can be logged in since login time before being required to login. Default is 30 days.
+;login_maximum_lifetime_days = 30
+
+# How often should auth tokens be rotated for authenticated users when being active. The default is each 10 minutes.
+;token_rotation_interval_minutes = 10
+
+# Set to true to disable (hide) the login form, useful if you use OAuth, defaults to false
+;disable_login_form = false
+
+# Set to true to disable the signout link in the side menu. useful if you use auth.proxy, defaults to false
+;disable_signout_menu = false
+
+# URL to redirect the user to after sign out
+;signout_redirect_url =
+
+# Set to true to attempt login with OAuth automatically, skipping the login screen.
+# This setting is ignored if multiple OAuth providers are configured.
+;oauth_auto_login = false
+
+# OAuth state max age cookie duration. Defaults to 60 seconds.
+;oauth_state_cookie_max_age = 60
+
+# limit of api_key seconds to live before expiration
+;api_key_max_seconds_to_live = -1
+
+#################################### Anonymous Auth ######################
+[auth.anonymous]
+# enable anonymous access
+enabled = true
+
+# specify organization name that should be used for unauthenticated users
+;org_name = Main Org.
+
+# specify role for unauthenticated users
+;org_role = Viewer
+
+#################################### Github Auth ##########################
+[auth.github]
+;enabled = false
+;allow_sign_up = true
+;client_id = some_id
+;client_secret = some_secret
+;scopes = user:email,read:org
+;auth_url = https://github.com/login/oauth/authorize
+;token_url = https://github.com/login/oauth/access_token
+;api_url = https://api.github.com/user
+;allowed_domains =
+;team_ids =
+;allowed_organizations =
+
+#################################### GitLab Auth #########################
+[auth.gitlab]
+;enabled = false
+;allow_sign_up = true
+;client_id = some_id
+;client_secret = some_secret
+;scopes = api
+;auth_url = https://gitlab.com/oauth/authorize
+;token_url = https://gitlab.com/oauth/token
+;api_url = https://gitlab.com/api/v4
+;allowed_domains =
+;allowed_groups =
+
+#################################### Google Auth ##########################
+[auth.google]
+;enabled = false
+;allow_sign_up = true
+;client_id = some_client_id
+;client_secret = some_client_secret
+;scopes = https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email
+;auth_url = https://accounts.google.com/o/oauth2/auth
+;token_url = https://accounts.google.com/o/oauth2/token
+;api_url = https://www.googleapis.com/oauth2/v1/userinfo
+;allowed_domains =
+;hosted_domain =
+
+#################################### Grafana.com Auth ####################
+[auth.grafana_com]
+;enabled = false
+;allow_sign_up = true
+;client_id = some_id
+;client_secret = some_secret
+;scopes = user:email
+;allowed_organizations =
+
+#################################### Azure AD OAuth #######################
+[auth.azuread]
+;name = Azure AD
+;enabled = false
+;allow_sign_up = true
+;client_id = some_client_id
+;client_secret = some_client_secret
+;scopes = openid email profile
+;auth_url = https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/authorize
+;token_url = https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token
+;allowed_domains =
+;allowed_groups =
+
+#################################### Okta OAuth #######################
+[auth.okta]
+;name = Okta
+;enabled = false
+;allow_sign_up = true
+;client_id = some_id
+;client_secret = some_secret
+;scopes = openid profile email groups
+;auth_url = https://<tenant-id>.okta.com/oauth2/v1/authorize
+;token_url = https://<tenant-id>.okta.com/oauth2/v1/token
+;api_url = https://<tenant-id>.okta.com/oauth2/v1/userinfo
+;allowed_domains =
+;allowed_groups =
+;role_attribute_path =
+
+#################################### Generic OAuth ##########################
+[auth.generic_oauth]
+;enabled = false
+;name = OAuth
+;allow_sign_up = true
+;client_id = some_id
+;client_secret = some_secret
+;scopes = user:email,read:org
+;email_attribute_name = email:primary
+;email_attribute_path =
+;auth_url = https://foo.bar/login/oauth/authorize
+;token_url = https://foo.bar/login/oauth/access_token
+;api_url = https://foo.bar/user
+;allowed_domains =
+;team_ids =
+;allowed_organizations =
+;role_attribute_path =
+;tls_skip_verify_insecure = false
+;tls_client_cert =
+;tls_client_key =
+;tls_client_ca =
+
+#################################### Basic Auth ##########################
+[auth.basic]
+;enabled = true
+
+#################################### Auth Proxy ##########################
+[auth.proxy]
+;enabled = false
+;header_name = X-WEBAUTH-USER
+;header_property = username
+;auto_sign_up = true
+;sync_ttl = 60
+;whitelist = 192.168.1.1, 192.168.2.1
+;headers = Email:X-User-Email, Name:X-User-Name
+# Read the auth proxy docs for details on what the setting below enables
+;enable_login_token = false
+
+#################################### Auth LDAP ##########################
+[auth.ldap]
+;enabled = false
+;config_file = /etc/grafana/ldap.toml
+;allow_sign_up = true
+
+# LDAP backround sync (Enterprise only)
+# At 1 am every day
+;sync_cron = "0 0 1 * * *"
+;active_sync_enabled = true
+
+#################################### SMTP / Emailing ##########################
+[smtp]
+;enabled = false
+;host = localhost:25
+;user =
+# If the password contains # or ; you have to wrap it with triple quotes. Ex """#password;"""
+;password =
+;cert_file =
+;key_file =
+;skip_verify = false
+;from_address = admin@grafana.localhost
+;from_name = Grafana
+# EHLO identity in SMTP dialog (defaults to instance_name)
+;ehlo_identity = dashboard.example.com
+
+[emails]
+;welcome_email_on_sign_up = false
+;templates_pattern = emails/*.html
+
+#################################### Logging ##########################
+[log]
+# Either "console", "file", "syslog". Default is console and  file
+# Use space to separate multiple modes, e.g. "console file"
+;mode = console file
+
+# Either "debug", "info", "warn", "error", "critical", default is "info"
+;level = info
+
+# optional settings to set different levels for specific loggers. Ex filters = sqlstore:debug
+;filters =
+
+# For "console" mode only
+[log.console]
+;level =
+
+# log line format, valid options are text, console and json
+;format = console
+
+# For "file" mode only
+[log.file]
+;level =
+
+# log line format, valid options are text, console and json
+;format = text
+
+# This enables automated log rotate(switch of following options), default is true
+;log_rotate = true
+
+# Max line number of single file, default is 1000000
+;max_lines = 1000000
+
+# Max size shift of single file, default is 28 means 1 << 28, 256MB
+;max_size_shift = 28
+
+# Segment log daily, default is true
+;daily_rotate = true
+
+# Expired days of log file(delete after max days), default is 7
+;max_days = 7
+
+[log.syslog]
+;level =
+
+# log line format, valid options are text, console and json
+;format = text
+
+# Syslog network type and address. This can be udp, tcp, or unix. If left blank, the default unix endpoints will be used.
+;network =
+;address =
+
+# Syslog facility. user, daemon and local0 through local7 are valid.
+;facility =
+
+# Syslog tag. By default, the process' argv[0] is used.
+;tag =
+
+#################################### Usage Quotas ########################
+[quota]
+; enabled = false
+
+#### set quotas to -1 to make unlimited. ####
+# limit number of users per Org.
+; org_user = 10
+
+# limit number of dashboards per Org.
+; org_dashboard = 100
+
+# limit number of data_sources per Org.
+; org_data_source = 10
+
+# limit number of api_keys per Org.
+; org_api_key = 10
+
+# limit number of orgs a user can create.
+; user_org = 10
+
+# Global limit of users.
+; global_user = -1
+
+# global limit of orgs.
+; global_org = -1
+
+# global limit of dashboards
+; global_dashboard = -1
+
+# global limit of api_keys
+; global_api_key = -1
+
+# global limit on number of logged in users.
+; global_session = -1
+
+#################################### Alerting ############################
+[alerting]
+# Disable alerting engine & UI features
+;enabled = true
+# Makes it possible to turn off alert rule execution but alerting UI is visible
+;execute_alerts = true
+
+# Default setting for new alert rules. Defaults to categorize error and timeouts as alerting. (alerting, keep_state)
+;error_or_timeout = alerting
+
+# Default setting for how Grafana handles nodata or null values in alerting. (alerting, no_data, keep_state, ok)
+;nodata_or_nullvalues = no_data
+
+# Alert notifications can include images, but rendering many images at the same time can overload the server
+# This limit will protect the server from render overloading and make sure notifications are sent out quickly
+;concurrent_render_limit = 5
+
+
+# Default setting for alert calculation timeout. Default value is 30
+;evaluation_timeout_seconds = 30
+
+# Default setting for alert notification timeout. Default value is 30
+;notification_timeout_seconds = 30
+
+# Default setting for max attempts to sending alert notifications. Default value is 3
+;max_attempts = 3
+
+# Makes it possible to enforce a minimal interval between evaluations, to reduce load on the backend
+;min_interval_seconds = 1
+
+#################################### Explore #############################
+[explore]
+# Enable the Explore section
+;enabled = true
+
+#################################### Internal Grafana Metrics ##########################
+# Metrics available at HTTP API Url /metrics
+[metrics]
+# Disable / Enable internal metrics
+;enabled           = true
+# Graphite Publish interval
+;interval_seconds  = 10
+# Disable total stats (stat_totals_*) metrics to be generated
+;disable_total_stats = false
+
+#If both are set, basic auth will be required for the metrics endpoint.
+; basic_auth_username =
+; basic_auth_password =
+
+# Send internal metrics to Graphite
+[metrics.graphite]
+# Enable by setting the address setting (ex localhost:2003)
+;address =
+;prefix = prod.grafana.%(instance_name)s.
+
+#################################### Grafana.com integration  ##########################
+# Url used to import dashboards directly from Grafana.com
+[grafana_com]
+;url = https://grafana.com
+
+#################################### Distributed tracing ############
+[tracing.jaeger]
+# Enable by setting the address sending traces to jaeger (ex localhost:6831)
+;address = localhost:6831
+# Tag that will always be included in when creating new spans. ex (tag1:value1,tag2:value2)
+;always_included_tag = tag1:value1
+# Type specifies the type of the sampler: const, probabilistic, rateLimiting, or remote
+;sampler_type = const
+# jaeger samplerconfig param
+# for "const" sampler, 0 or 1 for always false/true respectively
+# for "probabilistic" sampler, a probability between 0 and 1
+# for "rateLimiting" sampler, the number of spans per second
+# for "remote" sampler, param is the same as for "probabilistic"
+# and indicates the initial sampling rate before the actual one
+# is received from the mothership
+;sampler_param = 1
+# Whether or not to use Zipkin propagation (x-b3- HTTP headers).
+;zipkin_propagation = false
+# Setting this to true disables shared RPC spans.
+# Not disabling is the most common setting when using Zipkin elsewhere in your infrastructure.
+;disable_shared_zipkin_spans = false
+
+#################################### External image storage ##########################
+[external_image_storage]
+# Used for uploading images to public servers so they can be included in slack/email messages.
+# you can choose between (s3, webdav, gcs, azure_blob, local)
+;provider =
+
+[external_image_storage.s3]
+;endpoint =
+;path_style_access =
+;bucket =
+;region =
+;path =
+;access_key =
+;secret_key =
+
+[external_image_storage.webdav]
+;url =
+;public_url =
+;username =
+;password =
+
+[external_image_storage.gcs]
+;key_file =
+;bucket =
+;path =
+
+[external_image_storage.azure_blob]
+;account_name =
+;account_key =
+;container_name =
+
+[external_image_storage.local]
+# does not require any configuration
+
+[rendering]
+# Options to configure a remote HTTP image rendering service, e.g. using https://github.com/grafana/grafana-image-renderer.
+# URL to a remote HTTP image renderer service, e.g. http://localhost:8081/render, will enable Grafana to render panels and dashboards to PNG-images using HTTP requests to an external service.
+;server_url =
+# If the remote HTTP image renderer service runs on a different server than the Grafana server you may have to configure this to a URL where Grafana is reachable, e.g. http://grafana.domain/.
+;callback_url =
+# Concurrent render request limit affects when the /render HTTP endpoint is used. Rendering many images at the same time can overload the server,
+# which this setting can help protect against by only allowing a certain amount of concurrent requests.
+;concurrent_render_request_limit = 30
+
+[panels]
+# If set to true Grafana will allow script tags in text panels. Not recommended as it enable XSS vulnerabilities.
+;disable_sanitize_html = false
+
+[plugins]
+;enable_alpha = false
+;app_tls_skip_verify_insecure = false
+# Enter a comma-separated list of plugin identifiers to identify plugins that are allowed to be loaded even if they lack a valid signature.
+;allow_loading_unsigned_plugins =
+
+#################################### Grafana Image Renderer Plugin ##########################
+[plugin.grafana-image-renderer]
+# Instruct headless browser instance to use a default timezone when not provided by Grafana, e.g. when rendering panel image of alert.
+# See ICU’s metaZones.txt (https://cs.chromium.org/chromium/src/third_party/icu/source/data/misc/metaZones.txt) for a list of supported
+# timezone IDs. Fallbacks to TZ environment variable if not set.
+;rendering_timezone =
+
+# Instruct headless browser instance to use a default language when not provided by Grafana, e.g. when rendering panel image of alert.
+# Please refer to the HTTP header Accept-Language to understand how to format this value, e.g. 'fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5'.
+;rendering_language =
+
+# Instruct headless browser instance to use a default device scale factor when not provided by Grafana, e.g. when rendering panel image of alert.
+# Default is 1. Using a higher value will produce more detailed images (higher DPI), but will require more disk space to store an image.
+;rendering_viewport_device_scale_factor =
+
+# Instruct headless browser instance whether to ignore HTTPS errors during navigation. Per default HTTPS errors are not ignored. Due to
+# the security risk it's not recommended to ignore HTTPS errors.
+;rendering_ignore_https_errors =
+
+# Instruct headless browser instance whether to capture and log verbose information when rendering an image. Default is false and will
+# only capture and log error messages. When enabled, debug messages are captured and logged as well.
+# For the verbose information to be included in the Grafana server log you have to adjust the rendering log level to debug, configure
+# [log].filter = rendering:debug.
+;rendering_verbose_logging =
+
+# Instruct headless browser instance whether to output its debug and error messages into running process of remote rendering service.
+# Default is false. This can be useful to enable (true) when troubleshooting.
+;rendering_dumpio =
+
+# Additional arguments to pass to the headless browser instance. Default is --no-sandbox. The list of Chromium flags can be found
+# here (https://peter.sh/experiments/chromium-command-line-switches/). Multiple arguments is separated with comma-character.
+;rendering_args =
+
+# You can configure the plugin to use a different browser binary instead of the pre-packaged version of Chromium.
+# Please note that this is not recommended, since you may encounter problems if the installed version of Chrome/Chromium is not
+# compatible with the plugin.
+;rendering_chrome_bin =
+
+# Instruct how headless browser instances are created. Default is 'default' and will create a new browser instance on each request.
+# Mode 'clustered' will make sure that only a maximum of browsers/incognito pages can execute concurrently.
+# Mode 'reusable' will have one browser instance and will create a new incognito page on each request.
+;rendering_mode =
+
+# When rendering_mode = clustered you can instruct how many browsers or incognito pages can execute concurrently. Default is 'browser'
+# and will cluster using browser instances.
+# Mode 'context' will cluster using incognito pages.
+;rendering_clustering_mode =
+# When rendering_mode = clustered you can define maximum number of browser instances/incognito pages that can execute concurrently..
+;rendering_clustering_max_concurrency =
+
+# Limit the maxiumum viewport width, height and device scale factor that can be requested.
+;rendering_viewport_max_width =
+;rendering_viewport_max_height =
+;rendering_viewport_max_device_scale_factor =
+
+# Change the listening host and port of the gRPC server. Default host is 127.0.0.1 and default port is 0 and will automatically assign
+# a port not in use.
+;grpc_host =
+;grpc_port =
+
+[enterprise]
+# Path to a valid Grafana Enterprise license.jwt file
+;license_path =
+
+[feature_toggles]
+# enable features, separated by spaces
+;enable =
diff --git a/compose/grafana_conf/dashboards/apisix_http_prometheus.json b/compose/grafana_conf/dashboards/apisix_http_prometheus.json
new file mode 100644
index 0000000..8dd9f8b
--- /dev/null
+++ b/compose/grafana_conf/dashboards/apisix_http_prometheus.json
@@ -0,0 +1,956 @@
+{
+  "annotations": {
+    "list": [
+      {
+        "builtIn": 1,
+        "datasource": "apisix",
+        "enable": true,
+        "hide": true,
+        "iconColor": "rgba(0, 211, 255, 1)",
+        "limit": 100,
+        "name": "Annotations & Alerts",
+        "showIn": 0,
+        "type": "dashboard"
+      }
+    ]
+  },
+  "description": "MicroService API Gateway Apache APISIX",
+  "editable": true,
+  "gnetId": 11719,
+  "graphTooltip": 0,
+  "id": 10,
+  "iteration": 1591947413854,
+  "links": [],
+  "panels": [
+    {
+      "collapsed": false,
+      "datasource": "apisix",
+      "gridPos": {
+        "h": 1,
+        "w": 24,
+        "x": 0,
+        "y": 0
+      },
+      "id": 10,
+      "panels": [],
+      "title": "Nginx",
+      "type": "row"
+    },
+    {
+      "cacheTimeout": null,
+      "colorBackground": false,
+      "colorValue": false,
+      "colors": [
+        "#299c46",
+        "rgba(237, 129, 40, 0.89)",
+        "#d44a3a"
+      ],
+      "datasource": "apisix",
+      "description": "",
+      "fieldConfig": {
+        "defaults": {
+          "custom": {}
+        },
+        "overrides": []
+      },
+      "format": "none",
+      "gauge": {
+        "maxValue": 100,
+        "minValue": 0,
+        "show": false,
+        "thresholdLabels": false,
+        "thresholdMarkers": true
+      },
+      "gridPos": {
+        "h": 5,
+        "w": 5,
+        "x": 0,
+        "y": 1
+      },
+      "id": 8,
+      "interval": null,
+      "links": [],
+      "mappingType": 1,
+      "mappingTypes": [
+        {
+          "name": "value to text",
+          "value": 1
+        },
+        {
+          "name": "range to text",
+          "value": 2
+        }
+      ],
+      "maxDataPoints": 100,
+      "nullPointMode": "connected",
+      "nullText": null,
+      "postfix": "",
+      "postfixFontSize": "50%",
+      "prefix": "",
+      "prefixFontSize": "50%",
+      "rangeMaps": [
+        {
+          "from": "null",
+          "text": "N/A",
+          "to": "null"
+        }
+      ],
+      "sparkline": {
+        "fillColor": "rgba(31, 118, 189, 0.18)",
+        "full": true,
+        "lineColor": "rgb(31, 120, 193)",
+        "show": true,
+        "ymax": null,
+        "ymin": null
+      },
+      "tableColumn": "Total",
+      "targets": [
+        {
+          "expr": "sum(apisix_nginx_http_current_connections{state=\"total\", instance=~\"$instance\"})",
+          "intervalFactor": 2,
+          "legendFormat": "Total",
+          "refId": "A"
+        }
+      ],
+      "thresholds": "",
+      "timeFrom": null,
+      "timeShift": null,
+      "title": "Total Connections",
+      "type": "singlestat",
+      "valueFontSize": "80%",
+      "valueMaps": [
+        {
+          "op": "=",
+          "text": "N/A",
+          "value": "null"
+        }
+      ],
+      "valueName": "current"
+    },
+    {
+      "cacheTimeout": null,
+      "colorBackground": false,
+      "colorValue": false,
+      "colors": [
+        "#299c46",
+        "rgba(237, 129, 40, 0.89)",
+        "#d44a3a"
+      ],
+      "datasource": "apisix",
+      "description": "",
+      "fieldConfig": {
+        "defaults": {
+          "custom": {}
+        },
+        "overrides": []
+      },
+      "format": "none",
+      "gauge": {
+        "maxValue": 100,
+        "minValue": 0,
+        "show": false,
+        "thresholdLabels": false,
+        "thresholdMarkers": true
+      },
+      "gridPos": {
+        "h": 5,
+        "w": 5,
+        "x": 5,
+        "y": 1
+      },
+      "id": 16,
+      "interval": null,
+      "links": [],
+      "mappingType": 1,
+      "mappingTypes": [
+        {
+          "name": "value to text",
+          "value": 1
+        },
+        {
+          "name": "range to text",
+          "value": 2
+        }
+      ],
+      "maxDataPoints": 100,
+      "nullPointMode": "connected",
+      "nullText": null,
+      "postfix": "",
+      "postfixFontSize": "50%",
+      "prefix": "",
+      "prefixFontSize": "50%",
+      "rangeMaps": [
+        {
+          "from": "null",
+          "text": "N/A",
+          "to": "null"
+        }
+      ],
+      "sparkline": {
+        "fillColor": "rgba(31, 118, 189, 0.18)",
+        "full": true,
+        "lineColor": "rgb(31, 120, 193)",
+        "show": true,
+        "ymax": null,
+        "ymin": null
+      },
+      "tableColumn": "Accepted",
+      "targets": [
+        {
+          "expr": "sum(apisix_nginx_http_current_connections{state=\"accepted\", instance=~\"$instance\"})",
+          "intervalFactor": 2,
+          "legendFormat": "Accepted",
+          "refId": "A"
+        }
+      ],
+      "thresholds": "",
+      "timeFrom": null,
+      "timeShift": null,
+      "title": "Accepted Connections",
+      "type": "singlestat",
+      "valueFontSize": "80%",
+      "valueMaps": [
+        {
+          "op": "=",
+          "text": "N/A",
+          "value": "null"
+        }
+      ],
+      "valueName": "current"
+    },
+    {
+      "cacheTimeout": null,
+      "colorBackground": false,
+      "colorValue": false,
+      "colors": [
+        "#299c46",
+        "rgba(237, 129, 40, 0.89)",
+        "#d44a3a"
+      ],
+      "datasource": "apisix",
+      "description": "",
+      "fieldConfig": {
+        "defaults": {
+          "custom": {}
+        },
+        "overrides": []
+      },
+      "format": "none",
+      "gauge": {
+        "maxValue": 100,
+        "minValue": 0,
+        "show": false,
+        "thresholdLabels": false,
+        "thresholdMarkers": true
+      },
+      "gridPos": {
+        "h": 5,
+        "w": 5,
+        "x": 10,
+        "y": 1
+      },
+      "id": 11,
+      "interval": null,
+      "links": [],
+      "mappingType": 1,
+      "mappingTypes": [
+        {
+          "name": "value to text",
+          "value": 1
+        },
+        {
+          "name": "range to text",
+          "value": 2
+        }
+      ],
+      "maxDataPoints": 100,
+      "nullPointMode": "connected",
+      "nullText": null,
+      "postfix": "",
+      "postfixFontSize": "50%",
+      "prefix": "",
+      "prefixFontSize": "50%",
+      "rangeMaps": [
+        {
+          "from": "null",
+          "text": "N/A",
+          "to": "null"
+        }
+      ],
+      "sparkline": {
+        "fillColor": "rgba(31, 118, 189, 0.18)",
+        "full": true,
+        "lineColor": "rgb(31, 120, 193)",
+        "show": true,
+        "ymax": null,
+        "ymin": null
+      },
+      "tableColumn": "Total",
+      "targets": [
+        {
+          "expr": "sum(apisix_nginx_http_current_connections{state=\"handled\", instance=~\"$instance\"})",
+          "intervalFactor": 2,
+          "legendFormat": "Total",
+          "refId": "A"
+        }
+      ],
+      "thresholds": "",
+      "timeFrom": null,
+      "timeShift": null,
+      "title": "Handled Connections",
+      "type": "singlestat",
+      "valueFontSize": "80%",
+      "valueMaps": [
+        {
+          "op": "=",
+          "text": "N/A",
+          "value": "null"
+        }
+      ],
+      "valueName": "current"
+    },
+    {
+      "aliasColors": {},
+      "bars": false,
+      "dashLength": 10,
+      "dashes": false,
+      "datasource": "apisix",
+      "fieldConfig": {
+        "defaults": {
+          "custom": {}
+        },
+        "overrides": []
+      },
+      "fill": 1,
+      "fillGradient": 0,
+      "gridPos": {
+        "h": 6,
+        "w": 15,
+        "x": 0,
+        "y": 6
+      },
+      "hiddenSeries": false,
+      "id": 17,
+      "legend": {
+        "alignAsTable": false,
+        "avg": false,
+        "current": false,
+        "max": false,
+        "min": false,
+        "rightSide": false,
+        "show": true,
+        "total": false,
+        "values": false
+      },
+      "lines": true,
+      "linewidth": 1,
+      "nullPointMode": "null",
+      "options": {
+        "dataLinks": []
+      },
+      "percentage": false,
+      "pointradius": 2,
+      "points": false,
+      "renderer": "flot",
+      "seriesOverrides": [],
+      "spaceLength": 10,
+      "stack": false,
+      "steppedLine": false,
+      "targets": [
+        {
+          "expr": "sum(apisix_nginx_http_current_connections{state=~\"active|reading|writing|waiting\", instance=~\"$instance\"}) by (state)",
+          "intervalFactor": 1,
+          "legendFormat": "{{state}}",
+          "refId": "A"
+        }
+      ],
+      "thresholds": [],
+      "timeFrom": null,
+      "timeRegions": [],
+      "timeShift": null,
+      "title": "Nginx connection state",
+      "tooltip": {
+        "shared": true,
+        "sort": 0,
+        "value_type": "individual"
+      },
+      "type": "graph",
+      "xaxis": {
+        "buckets": null,
+        "mode": "time",
+        "name": null,
+        "show": true,
+        "values": []
+      },
+      "yaxes": [
+        {
+          "format": "short",
+          "label": null,
+          "logBase": 1,
+          "max": null,
+          "min": null,
+          "show": true
+        },
+        {
+          "format": "short",
+          "label": null,
+          "logBase": 1,
+          "max": null,
+          "min": null,
+          "show": true
+        }
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
+    },
+    {
+      "collapsed": false,
+      "datasource": "apisix",
+      "gridPos": {
+        "h": 1,
+        "w": 24,
+        "x": 0,
+        "y": 12
+      },
+      "id": 13,
+      "panels": [],
+      "title": "Bandwidth",
+      "type": "row"
+    },
+    {
+      "aliasColors": {},
+      "bars": false,
+      "dashLength": 10,
+      "dashes": false,
+      "datasource": "apisix",
+      "fieldConfig": {
+        "defaults": {
+          "custom": {}
+        },
+        "overrides": []
+      },
+      "fill": 1,
+      "fillGradient": 0,
+      "gridPos": {
+        "h": 6,
+        "w": 15,
+        "x": 0,
+        "y": 13
+      },
+      "hiddenSeries": false,
+      "id": 6,
+      "legend": {
+        "alignAsTable": true,
+        "avg": true,
+        "current": true,
+        "max": true,
+        "min": true,
+        "rightSide": true,
+        "show": true,
+        "total": false,
+        "values": true
+      },
+      "lines": true,
+      "linewidth": 1,
+      "nullPointMode": "null",
+      "options": {
+        "dataLinks": []
+      },
+      "percentage": false,
+      "pointradius": 2,
+      "points": false,
+      "renderer": "flot",
+      "seriesOverrides": [],
+      "spaceLength": 10,
+      "stack": false,
+      "steppedLine": false,
+      "targets": [
+        {
+          "expr": "sum(rate(apisix_bandwidth{instance=~\"$instance\"}[30s])) by (type)",
+          "legendFormat": "{{type}}",
+          "refId": "A"
+        }
+      ],
+      "thresholds": [],
+      "timeFrom": null,
+      "timeRegions": [],
+      "timeShift": null,
+      "title": "Total Bandwidth",
+      "tooltip": {
+        "shared": true,
+        "sort": 0,
+        "value_type": "individual"
+      },
+      "type": "graph",
+      "xaxis": {
+        "buckets": null,
+        "mode": "time",
+        "name": null,
+        "show": true,
+        "values": []
+      },
+      "yaxes": [
+        {
+          "format": "short",
+          "label": null,
+          "logBase": 1,
+          "max": null,
+          "min": null,
+          "show": true
+        },
+        {
+          "format": "short",
+          "label": null,
+          "logBase": 1,
+          "max": null,
+          "min": null,
+          "show": true
+        }
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
+    },
+    {
+      "aliasColors": {},
+      "bars": false,
+      "dashLength": 10,
+      "dashes": false,
+      "datasource": "apisix",
+      "fieldConfig": {
+        "defaults": {
+          "custom": {}
+        },
+        "overrides": []
+      },
+      "fill": 1,
+      "fillGradient": 0,
+      "gridPos": {
+        "h": 6,
+        "w": 7,
+        "x": 0,
+        "y": 19
+      },
+      "hiddenSeries": false,
+      "id": 19,
+      "legend": {
+        "avg": false,
+        "current": false,
+        "max": false,
+        "min": false,
+        "show": true,
+        "total": false,
+        "values": false
+      },
+      "lines": true,
+      "linewidth": 1,
+      "nullPointMode": "null",
+      "options": {
+        "dataLinks": []
+      },
+      "percentage": false,
+      "pointradius": 2,
+      "points": false,
+      "renderer": "flot",
+      "seriesOverrides": [],
+      "spaceLength": 10,
+      "stack": false,
+      "steppedLine": false,
+      "targets": [
+        {
+          "expr": "sum(rate(apisix_bandwidth{type=\"egress\", service =~\"$service\",route=~\"$route\",instance=~\"$instance\"}[1m])) by (service)",
+          "legendFormat": "service:{{service}}",
+          "refId": "A"
+        },
+        {
+          "expr": "sum(rate(apisix_bandwidth{type=\"egress\", service =~\"$service\",route=~\"$route\",instance=~\"$instance\"}[1m])) by (route)",
+          "legendFormat": "route:{{route}}",
+          "refId": "B"
+        }
+      ],
+      "thresholds": [],
+      "timeFrom": null,
+      "timeRegions": [],
+      "timeShift": null,
+      "title": "Egress per service/route",
+      "tooltip": {
+        "shared": true,
+        "sort": 0,
+        "value_type": "individual"
+      },
+      "type": "graph",
+      "xaxis": {
+        "buckets": null,
+        "mode": "time",
+        "name": null,
+        "show": true,
+        "values": []
+      },
+      "yaxes": [
+        {
+          "format": "short",
+          "label": null,
+          "logBase": 1,
+          "max": null,
+          "min": null,
+          "show": true
+        },
+        {
+          "format": "short",
+          "label": null,
+          "logBase": 1,
+          "max": null,
+          "min": null,
+          "show": true
+        }
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
+    },
+    {
+      "aliasColors": {},
+      "bars": false,
+      "dashLength": 10,
+      "dashes": false,
+      "datasource": "apisix",
+      "fieldConfig": {
+        "defaults": {
+          "custom": {}
+        },
+        "overrides": []
+      },
+      "fill": 1,
+      "fillGradient": 0,
+      "gridPos": {
+        "h": 6,
+        "w": 8,
+        "x": 7,
+        "y": 19
+      },
+      "hiddenSeries": false,
+      "id": 21,
+      "legend": {
+        "avg": false,
+        "current": false,
+        "max": false,
+        "min": false,
+        "show": true,
+        "total": false,
+        "values": false
+      },
+      "lines": true,
+      "linewidth": 1,
+      "nullPointMode": "null",
+      "options": {
+        "dataLinks": []
+      },
+      "percentage": false,
+      "pointradius": 2,
+      "points": false,
+      "renderer": "flot",
+      "seriesOverrides": [],
+      "spaceLength": 10,
+      "stack": false,
+      "steppedLine": false,
+      "targets": [
+        {
+          "expr": "sum(irate(apisix_bandwidth{type=\"ingress\", service =~\"$service\",route=~\"$route\",instance=~\"$instance\"}[1m])) by (service)",
+          "legendFormat": "service:{{service}}",
+          "refId": "A"
+        },
+        {
+          "expr": "sum(irate(apisix_bandwidth{type=\"ingress\", service =~\"$service\",route=~\"$route\",instance=~\"$instance\"}[1m])) by (route)",
+          "legendFormat": "route:{{route}}",
+          "refId": "B"
+        }
+      ],
+      "thresholds": [],
+      "timeFrom": null,
+      "timeRegions": [],
+      "timeShift": null,
+      "title": "Ingress per service/route",
+      "tooltip": {
+        "shared": true,
+        "sort": 0,
+        "value_type": "individual"
+      },
+      "type": "graph",
+      "xaxis": {
+        "buckets": null,
+        "mode": "time",
+        "name": null,
+        "show": true,
+        "values": []
+      },
+      "yaxes": [
+        {
+          "format": "short",
+          "label": null,
+          "logBase": 1,
+          "max": null,
+          "min": null,
+          "show": true
+        },
+        {
+          "format": "short",
+          "label": null,
+          "logBase": 1,
+          "max": null,
+          "min": null,
+          "show": true
+        }
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
+    },
+    {
+      "collapsed": false,
+      "datasource": "apisix",
+      "gridPos": {
+        "h": 1,
+        "w": 24,
+        "x": 0,
+        "y": 25
+      },
+      "id": 15,
+      "panels": [],
+      "title": "HTTP Status",
+      "type": "row"
+    },
+    {
+      "aliasColors": {},
+      "bars": false,
+      "cacheTimeout": null,
+      "dashLength": 10,
+      "dashes": false,
+      "datasource": "apisix",
+      "fieldConfig": {
+        "defaults": {
+          "custom": {}
+        },
+        "overrides": []
+      },
+      "fill": 3,
+      "fillGradient": 0,
+      "gridPos": {
+        "h": 6,
+        "w": 15,
+        "x": 0,
+        "y": 26
+      },
+      "hiddenSeries": false,
+      "id": 2,
+      "interval": "",
+      "legend": {
+        "avg": false,
+        "current": false,
+        "max": false,
+        "min": false,
+        "show": true,
+        "total": false,
+        "values": false
+      },
+      "lines": true,
+      "linewidth": 1,
+      "links": [],
+      "nullPointMode": "null",
+      "options": {
+        "dataLinks": []
+      },
+      "percentage": false,
+      "pluginVersion": "6.5.2",
+      "pointradius": 2,
+      "points": false,
+      "renderer": "flot",
+      "seriesOverrides": [
+        {
+          "alias": "state",
+          "lines": true
+        }
+      ],
+      "spaceLength": 10,
+      "stack": false,
+      "steppedLine": false,
+      "targets": [
+        {
+          "expr": "sum(rate(apisix_http_status{code=~\"2..\",service=~\"$service\",route=~\"$route\",instance=~\"$instance\"}[30s])) by (status_2xx)",
+          "instant": false,
+          "intervalFactor": 1,
+          "legendFormat": "{{status_2xx}}",
+          "refId": "A"
+        },
+        {
+          "expr": "sum(rate(apisix_http_status{code=~\"3..\",service=~\"$service\",route=~\"$route\",instance=~\"$instance\"}[30s])) by (status_3xx)",
+          "legendFormat": "{{status_3xx}}",
+          "refId": "D"
+        },
+        {
+          "expr": "sum(rate(apisix_http_status{code=~\"4..\",service=~\"$service\",route=~\"$route\",instance=~\"$instance\"}[30s])) by (status_4xx)",
+          "intervalFactor": 1,
+          "legendFormat": "{{status_4xx}}",
+          "refId": "B"
+        },
+        {
+          "expr": "sum(rate(apisix_http_status{code=~\"5..\",service=~\"$service\",route=~\"$route\",instance=~\"$instance\"}[30s])) by (status_5xx)",
+          "legendFormat": "{{status_5xx}}",
+          "refId": "C"
+        }
+      ],
+      "thresholds": [],
+      "timeFrom": null,
+      "timeRegions": [],
+      "timeShift": null,
+      "title": "Service HTTP Code",
+      "tooltip": {
+        "shared": true,
+        "sort": 0,
+        "value_type": "individual"
+      },
+      "type": "graph",
+      "xaxis": {
+        "buckets": null,
+        "mode": "time",
+        "name": null,
+        "show": true,
+        "values": []
+      },
+      "yaxes": [
+        {
+          "format": "short",
+          "label": null,
+          "logBase": 1,
+          "max": null,
+          "min": null,
+          "show": true
+        },
+        {
+          "format": "short",
+          "label": null,
+          "logBase": 1,
+          "max": null,
+          "min": null,
+          "show": true
+        }
+      ],
+      "yaxis": {
+        "align": false,
+        "alignLevel": null
+      }
+    }
+  ],
+  "refresh": "5s",
+  "schemaVersion": 25,
+  "style": "dark",
+  "tags": [],
+  "templating": {
+    "list": [
+      {
+        "allValue": ".*",
+        "current": {
+          "selected": false,
+          "text": "All",
+          "value": "$__all"
+        },
+        "datasource": "apisix",
+        "definition": "label_values(apisix_http_status,service)",
+        "hide": 0,
+        "includeAll": true,
+        "label": null,
+        "multi": true,
+        "name": "service",
+        "options": [],
+        "query": "label_values(apisix_http_status,service)",
+        "refresh": 1,
+        "regex": "",
+        "skipUrlSync": false,
+        "sort": 1,
+        "tagValuesQuery": "",
+        "tags": [],
+        "tagsQuery": "",
+        "type": "query",
+        "useTags": false
+      },
+      {
+        "allValue": ".*",
+        "current": {
+          "selected": false,
+          "text": "All",
+          "value": "$__all"
+        },
+        "datasource": "apisix",
+        "definition": "label_values(apisix_http_status,route)",
+        "hide": 0,
+        "includeAll": true,
+        "label": null,
+        "multi": true,
+        "name": "route",
+        "options": [],
+        "query": "label_values(apisix_http_status,route)",
+        "refresh": 1,
+        "regex": "",
+        "skipUrlSync": false,
+        "sort": 1,
+        "tagValuesQuery": "",
+        "tags": [],
+        "tagsQuery": "",
+        "type": "query",
+        "useTags": false
+      },
+      {
+        "allValue": ".*",
+        "current": {
+          "selected": false,
+          "text": "All",
+          "value": "$__all"
+        },
+        "datasource": "apisix",
+        "definition": "label_values(apisix_http_status,instance)",
+        "hide": 0,
+        "includeAll": true,
+        "label": null,
+        "multi": true,
+        "name": "instance",
+        "options": [],
+        "query": "label_values(apisix_http_status,instance)",
+        "refresh": 2,
+        "regex": ".*",
+        "skipUrlSync": false,
+        "sort": 1,
+        "tagValuesQuery": "",
+        "tags": [],
+        "tagsQuery": "",
+        "type": "query",
+        "useTags": false
+      }
+    ]
+  },
+  "time": {
+    "from": "now-30m",
+    "to": "now"
+  },
+  "timepicker": {
+    "refresh_intervals": [
+      "5s",
+      "10s",
+      "30s",
+      "1m",
+      "5m",
+      "15m",
+      "30m",
+      "1h",
+      "2h",
+      "1d"
+    ]
+  },
+  "timezone": "",
+  "title": "Apache APISIX",
+  "uid": "bLlNuRLWz",
+  "version": 1
+}
\ No newline at end of file
diff --git a/compose/grafana_conf/provisioning/dashboards/all.yaml b/compose/grafana_conf/provisioning/dashboards/all.yaml
new file mode 100644
index 0000000..c58cbc6
--- /dev/null
+++ b/compose/grafana_conf/provisioning/dashboards/all.yaml
@@ -0,0 +1,11 @@
+apiVersion: 1
+
+providers:
+- name: 'default'
+  orgId: 1
+  folder: ''
+  type: file
+  disableDeletion: false
+  editable: false
+  options:
+    path: /var/lib/grafana/dashboards
\ No newline at end of file
diff --git a/compose/grafana_conf/provisioning/datasources/all.yaml b/compose/grafana_conf/provisioning/datasources/all.yaml
new file mode 100644
index 0000000..4245eac
--- /dev/null
+++ b/compose/grafana_conf/provisioning/datasources/all.yaml
@@ -0,0 +1,9 @@
+datasources:
+ - access: 'proxy'
+   editable: true
+   is_default: true
+   name: 'apisix'
+   org_id: 1
+   type: 'prometheus'
+   url: 'http://prometheus:9090'
+   version: 1
\ No newline at end of file
diff --git a/compose/manager_conf/build.sh b/compose/manager_conf/build.sh
new file mode 100755
index 0000000..efedb1c
--- /dev/null
+++ b/compose/manager_conf/build.sh
@@ -0,0 +1,20 @@
+#!/bin/sh
+
+pwd=`pwd`
+
+export MYSQL_SERVER_ADDRESS="192.17.5.14:3306"
+export MYSQL_USER=root
+export MYSQL_PASSWORD=123456
+export SYSLOG_HOST=127.0.0.1
+export APISIX_BASE_URL="http://192.17.5.11:9080/apisix/admin"
+export APISIX_API_KEY="edd1c9f034335f136f87ad84b625c8f1"
+
+sed -i -e "s%#mysqlAddress#%`echo $MYSQL_SERVER_ADDRESS`%g" ${pwd}/conf.json
+sed -i -e "s%#mysqlUser#%`echo $MYSQL_USER`%g" ${pwd}/conf.json
+sed -i -e "s%#mysqlPWD#%`echo $MYSQL_PASSWORD`%g" ${pwd}/conf.json
+sed -i -e "s%#syslogAddress#%`echo $SYSLOG_HOST`%g" ${pwd}/conf.json
+sed -i -e "s%#apisixBaseUrl#%`echo $APISIX_BASE_URL`%g" ${pwd}/conf.json
+sed -i -e "s%#apisixApiKey#%`echo $APISIX_API_KEY`%g" ${pwd}/conf.json
+
+cd /root/manager-api
+exec ./manager-api
diff --git a/compose/pics/grafana_1.png b/compose/pics/grafana_1.png
new file mode 100644
index 0000000..631276e
Binary files /dev/null and b/compose/pics/grafana_1.png differ
diff --git a/compose/pics/grafana_2.png b/compose/pics/grafana_2.png
new file mode 100644
index 0000000..03711eb
Binary files /dev/null and b/compose/pics/grafana_2.png differ
diff --git a/compose/pics/grafana_3.png b/compose/pics/grafana_3.png
new file mode 100644
index 0000000..5b2e834
Binary files /dev/null and b/compose/pics/grafana_3.png differ
diff --git a/compose/pics/grafana_4.png b/compose/pics/grafana_4.png
new file mode 100644
index 0000000..237e4dc
Binary files /dev/null and b/compose/pics/grafana_4.png differ
diff --git a/compose/pics/grafana_5.png b/compose/pics/grafana_5.png
new file mode 100644
index 0000000..dd9654d
Binary files /dev/null and b/compose/pics/grafana_5.png differ
diff --git a/compose/pics/grafana_6.png b/compose/pics/grafana_6.png
new file mode 100644
index 0000000..8931a0d
Binary files /dev/null and b/compose/pics/grafana_6.png differ
diff --git a/compose/pics/login.png b/compose/pics/login.png
new file mode 100644
index 0000000..c42e375
Binary files /dev/null and b/compose/pics/login.png differ
diff --git a/compose/prometheus_conf/prometheus.yml b/compose/prometheus_conf/prometheus.yml
new file mode 100644
index 0000000..0804c81
--- /dev/null
+++ b/compose/prometheus_conf/prometheus.yml
@@ -0,0 +1,23 @@
+global:
+  scrape_interval: 15s # By default, scrape targets every 15 seconds.
+
+  # Attach these labels to any time series or alerts when communicating with
+  # external systems (federation, remote storage, Alertmanager).
+  external_labels:
+    monitor: "codelab-monitor"
+
+# A scrape configuration containing exactly one endpoint to scrape:
+# Here it's Prometheus itself.
+scrape_configs:
+  # The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
+  - job_name: "prometheus"
+
+    # Override the global default and scrape targets from this job every 5 seconds.
+    scrape_interval: 5s
+
+    static_configs:
+      - targets: ["localhost:9090"]
+  - job_name: "apisix"
+    metrics_path: "/apisix/prometheus/metrics"
+    static_configs:
+      - targets: ["192.17.5.11:9080"]
diff --git a/config/config.ts b/config/config.ts
new file mode 100644
index 0000000..b332ab4
--- /dev/null
+++ b/config/config.ts
@@ -0,0 +1,48 @@
+import { defineConfig } from 'umi';
+
+import defaultSettings from './defaultSettings';
+import proxy from './proxy';
+import routes from './routes';
+
+const { REACT_APP_ENV } = process.env;
+
+export default defineConfig({
+  hash: true,
+  antd: {},
+  dva: {
+    hmr: true,
+  },
+  locale: {
+    default: 'zh-CN',
+    antd: true,
+    baseNavigator: true,
+  },
+  dynamicImport: {
+    loading: '@/components/PageLoading/index',
+  },
+  targets: {
+    ie: 11,
+  },
+  routes,
+  layout: {
+    name: 'APISIX Dashboard',
+    locale: true,
+    logo: '/favicon.png',
+  },
+  base: '/dashboard/',
+  publicPath: '/',
+  define: {
+    REACT_APP_ENV: REACT_APP_ENV || false,
+  },
+  // Theme for antd: https://ant.design/docs/react/customize-theme-cn
+  theme: {
+    'primary-color': defaultSettings.primaryColor,
+  },
+  // @ts-ignore
+  title: false,
+  ignoreMomentLocale: true,
+  proxy: proxy[REACT_APP_ENV || 'dev'],
+  manifest: {
+    basePath: '/',
+  },
+});
diff --git a/config/defaultSettings.ts b/config/defaultSettings.ts
new file mode 100644
index 0000000..0245a76
--- /dev/null
+++ b/config/defaultSettings.ts
@@ -0,0 +1,20 @@
+import { Settings as LayoutSettings } from '@ant-design/pro-layout';
+
+export default {
+  navTheme: 'light',
+  primaryColor: '#1890ff',
+  layout: 'mix',
+  contentWidth: 'Fluid',
+  fixedHeader: false,
+  autoHideHeader: false,
+  fixSiderbar: false,
+  colorWeak: false,
+  menu: {
+    locale: true,
+  },
+  title: 'APISIX Dashboard',
+  pwa: false,
+  iconfontUrl: '',
+} as LayoutSettings & {
+  pwa: boolean;
+};
diff --git a/config/proxy.ts b/config/proxy.ts
new file mode 100644
index 0000000..e189b0f
--- /dev/null
+++ b/config/proxy.ts
@@ -0,0 +1,30 @@
+/**
+ * 在生产环境 代理是无法生效的,所以这里没有生产环境的配置
+ * The agent cannot take effect in the production environment
+ * so there is no configuration of the production environment
+ * For details, please see
+ * https://pro.ant.design/docs/deploy
+ */
+export default {
+  dev: {
+    '/api/': {
+      target: 'https://apisix.iresty.com/apisix/admin/',
+      changeOrigin: true,
+      pathRewrite: { '^/api': '' },
+    },
+  },
+  test: {
+    '/api/': {
+      target: 'https://preview.pro.ant.design',
+      changeOrigin: true,
+      pathRewrite: { '^': '' },
+    },
+  },
+  pre: {
+    '/api/': {
+      target: 'your pre url',
+      changeOrigin: true,
+      pathRewrite: { '^': '' },
+    },
+  },
+};
diff --git a/config/routes.ts b/config/routes.ts
new file mode 100644
index 0000000..75bee66
--- /dev/null
+++ b/config/routes.ts
@@ -0,0 +1,76 @@
+const routes = [
+  {
+    path: '/',
+    redirect: '/ssl',
+  },
+  {
+    name: 'metrics',
+    path: '/metrics',
+    component: './Metrics/Metrics',
+    icon: 'AreaChartOutlined',
+  },
+  {
+    name: 'setting',
+    path: '/setting',
+    component: './Setting',
+    layout: false,
+    hideInMenu: true,
+  },
+  {
+    name: 'ssl',
+    path: '/ssl',
+    icon: 'BarsOutlined',
+    routes: [
+      {
+        path: '/ssl',
+        redirect: '/ssl/list',
+      },
+      {
+        path: '/ssl/list',
+        name: 'list',
+        component: './ssl/List',
+        hideInMenu: true,
+      },
+      {
+        name: 'create',
+        path: '/ssl/create',
+        component: './ssl/Create',
+        hideInMenu: true,
+      },
+    ],
+  },
+  {
+    name: 'routes',
+    path: '/routes',
+    icon: 'BarsOutlined',
+    routes: [
+      {
+        path: '/routes',
+        redirect: '/routes/list',
+      },
+      {
+        path: '/routes/list',
+        name: 'list',
+        icon: 'BarsOutlined',
+        component: './Routes/List',
+      },
+      {
+        path: '/routes/create',
+        name: 'create',
+        component: './Routes/Create',
+        hideInMenu: true,
+      },
+      {
+        path: '/routes/:rid/edit',
+        name: 'edit',
+        component: './Routes/Create',
+        hideInMenu: true,
+      },
+    ],
+  },
+  {
+    component: './404',
+  },
+];
+
+export default routes;
diff --git a/docker/nginx.conf b/docker/nginx.conf
new file mode 100644
index 0000000..40c19cb
--- /dev/null
+++ b/docker/nginx.conf
@@ -0,0 +1,21 @@
+server {
+    listen 80;
+    # gzip config
+    gzip on;
+    gzip_min_length 1k;
+    gzip_comp_level 9;
+    gzip_types text/plain text/css text/javascript application/json application/javascript application/x-javascript application/xml;
+    gzip_vary on;
+    gzip_disable "MSIE [1-6]\.";
+
+    root /usr/share/nginx/html;
+    include /etc/nginx/mime.types;
+
+    location / {
+        rewrite ^/  /dashboard$uri redirect;
+    }
+
+    location /dashboard {
+        try_files $uri $uri/ /index.html;
+    }
+}
diff --git a/jest.config.js b/jest.config.js
new file mode 100644
index 0000000..4c4eeaf
--- /dev/null
+++ b/jest.config.js
@@ -0,0 +1,9 @@
+module.exports = {
+  testURL: 'http://localhost:8000',
+  testEnvironment: './tests/PuppeteerEnvironment',
+  verbose: false,
+  globals: {
+    ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION: false,
+    localStorage: null,
+  },
+};
diff --git a/jsconfig.json b/jsconfig.json
new file mode 100644
index 0000000..f87334d
--- /dev/null
+++ b/jsconfig.json
@@ -0,0 +1,10 @@
+{
+  "compilerOptions": {
+    "emitDecoratorMetadata": true,
+    "experimentalDecorators": true,
+    "baseUrl": ".",
+    "paths": {
+      "@/*": ["./src/*"]
+    }
+  }
+}
diff --git a/licenses/LICENSE.ALv2 b/licenses/LICENSE.ALv2
deleted file mode 100644
index 261eeb9..0000000
--- a/licenses/LICENSE.ALv2
+++ /dev/null
@@ -1,201 +0,0 @@
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "[]"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright [yyyy] [name of copyright owner]
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
diff --git a/licenses/LICENSE.vue-typescript-admin-template b/licenses/LICENSE.vue-typescript-admin-template
deleted file mode 100644
index 7c3565d..0000000
--- a/licenses/LICENSE.vue-typescript-admin-template
+++ /dev/null
@@ -1,21 +0,0 @@
-MIT License
-
-Copyright (c) 2018 Chong Guo
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
diff --git a/mock/notices.ts b/mock/notices.ts
new file mode 100644
index 0000000..b9e3bf2
--- /dev/null
+++ b/mock/notices.ts
@@ -0,0 +1,105 @@
+import { Request, Response } from 'express';
+
+const getNotices = (req: Request, res: Response) => {
+  res.json([
+    {
+      id: '000000001',
+      avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png',
+      title: '你收到了 14 份新周报',
+      datetime: '2017-08-09',
+      type: 'notification',
+    },
+    {
+      id: '000000002',
+      avatar: 'https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png',
+      title: '你推荐的 曲妮妮 已通过第三轮面试',
+      datetime: '2017-08-08',
+      type: 'notification',
+    },
+    {
+      id: '000000003',
+      avatar: 'https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png',
+      title: '这种模板可以区分多种通知类型',
+      datetime: '2017-08-07',
+      read: true,
+      type: 'notification',
+    },
+    {
+      id: '000000004',
+      avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png',
+      title: '左侧图标用于区分不同的类型',
+      datetime: '2017-08-07',
+      type: 'notification',
+    },
+    {
+      id: '000000005',
+      avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png',
+      title: '内容不要超过两行字,超出时自动截断',
+      datetime: '2017-08-07',
+      type: 'notification',
+    },
+    {
+      id: '000000006',
+      avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
+      title: '曲丽丽 评论了你',
+      description: '描述信息描述信息描述信息',
+      datetime: '2017-08-07',
+      type: 'message',
+      clickClose: true,
+    },
+    {
+      id: '000000007',
+      avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
+      title: '朱偏右 回复了你',
+      description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像',
+      datetime: '2017-08-07',
+      type: 'message',
+      clickClose: true,
+    },
+    {
+      id: '000000008',
+      avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
+      title: '标题',
+      description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像',
+      datetime: '2017-08-07',
+      type: 'message',
+      clickClose: true,
+    },
+    {
+      id: '000000009',
+      title: '任务名称',
+      description: '任务需要在 2017-01-12 20:00 前启动',
+      extra: '未开始',
+      status: 'todo',
+      type: 'event',
+    },
+    {
+      id: '000000010',
+      title: '第三方紧急代码变更',
+      description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务',
+      extra: '马上到期',
+      status: 'urgent',
+      type: 'event',
+    },
+    {
+      id: '000000011',
+      title: '信息安全考试',
+      description: '指派竹尔于 2017-01-09 前完成更新并发布',
+      extra: '已耗时 8 天',
+      status: 'doing',
+      type: 'event',
+    },
+    {
+      id: '000000012',
+      title: 'ABCD 版本发布',
+      description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务',
+      extra: '进行中',
+      status: 'processing',
+      type: 'event',
+    },
+  ]);
+};
+
+export default {
+  'GET /api/notices': getNotices,
+};
diff --git a/mock/route.ts b/mock/route.ts
new file mode 100644
index 0000000..418d10f
--- /dev/null
+++ b/mock/route.ts
@@ -0,0 +1,5 @@
+export default {
+  '/api/auth_routes': {
+    '/form/advanced-form': { authority: ['admin', 'user'] },
+  },
+};
diff --git a/mock/user.ts b/mock/user.ts
new file mode 100644
index 0000000..24fa3f7
--- /dev/null
+++ b/mock/user.ts
@@ -0,0 +1,154 @@
+import { Request, Response } from 'express';
+
+function getFakeCaptcha(req: Request, res: Response) {
+  return res.json('captcha-xxx');
+}
+// 代码中会兼容本地 service mock 以及部署站点的静态数据
+export default {
+  // 支持值为 Object 和 Array
+  'GET /api/currentUser': {
+    name: 'Serati Ma',
+    avatar: 'https://gw.alipayobjects.com/zos/antfincdn/XAosXuNZyF/BiazfanxmamNRoxxVxka.png',
+    userid: '00000001',
+    email: 'antdesign@alipay.com',
+    signature: '海纳百川,有容乃大',
+    title: '交互专家',
+    group: '蚂蚁金服-某某某事业群-某某平台部-某某技术部-UED',
+    tags: [
+      {
+        key: '0',
+        label: '很有想法的',
+      },
+      {
+        key: '1',
+        label: '专注设计',
+      },
+      {
+        key: '2',
+        label: '辣~',
+      },
+      {
+        key: '3',
+        label: '大长腿',
+      },
+      {
+        key: '4',
+        label: '川妹子',
+      },
+      {
+        key: '5',
+        label: '海纳百川',
+      },
+    ],
+    notifyCount: 12,
+    unreadCount: 11,
+    country: 'China',
+    geographic: {
+      province: {
+        label: '浙江省',
+        key: '330000',
+      },
+      city: {
+        label: '杭州市',
+        key: '330100',
+      },
+    },
+    address: '西湖区工专路 77 号',
+    phone: '0752-268888888',
+  },
+  // GET POST 可省略
+  'GET /api/users': [
+    {
+      key: '1',
+      name: 'John Brown',
+      age: 32,
+      address: 'New York No. 1 Lake Park',
+    },
+    {
+      key: '2',
+      name: 'Jim Green',
+      age: 42,
+      address: 'London No. 1 Lake Park',
+    },
+    {
+      key: '3',
+      name: 'Joe Black',
+      age: 32,
+      address: 'Sidney No. 1 Lake Park',
+    },
+  ],
+  'POST /api/login/account': (req: Request, res: Response) => {
+    const { password, userName, type } = req.body;
+    if (password === 'ant.design' && userName === 'admin') {
+      res.send({
+        status: 'ok',
+        type,
+        currentAuthority: 'admin',
+      });
+      return;
+    }
+    if (password === 'ant.design' && userName === 'user') {
+      res.send({
+        status: 'ok',
+        type,
+        currentAuthority: 'user',
+      });
+      return;
+    }
+    if (type === 'mobile') {
+      res.send({
+        status: 'ok',
+        type,
+        currentAuthority: 'admin',
+      });
+      return;
+    }
+
+    res.send({
+      status: 'error',
+      type,
+      currentAuthority: 'guest',
+    });
+  },
+  'POST /api/register': (req: Request, res: Response) => {
+    res.send({ status: 'ok', currentAuthority: 'user' });
+  },
+  'GET /api/500': (req: Request, res: Response) => {
+    res.status(500).send({
+      timestamp: 1513932555104,
+      status: 500,
+      error: 'error',
+      message: 'error',
+      path: '/base/category/list',
+    });
+  },
+  'GET /api/404': (req: Request, res: Response) => {
+    res.status(404).send({
+      timestamp: 1513932643431,
+      status: 404,
+      error: 'Not Found',
+      message: 'No message available',
+      path: '/base/category/list/2121212',
+    });
+  },
+  'GET /api/403': (req: Request, res: Response) => {
+    res.status(403).send({
+      timestamp: 1513932555104,
+      status: 403,
+      error: 'Unauthorized',
+      message: 'Unauthorized',
+      path: '/base/category/list',
+    });
+  },
+  'GET /api/401': (req: Request, res: Response) => {
+    res.status(401).send({
+      timestamp: 1513932555104,
+      status: 401,
+      error: 'Unauthorized',
+      message: 'Unauthorized',
+      path: '/base/category/list',
+    });
+  },
+
+  'GET  /api/login/captcha': getFakeCaptcha,
+};
diff --git a/netlify.toml b/netlify.toml
new file mode 100644
index 0000000..89a3866
--- /dev/null
+++ b/netlify.toml
@@ -0,0 +1,13 @@
+[build]
+  publish = "dist/"
+
+[[redirects]]
+  from = "/api/*"
+  to = "https://apisix.iresty.com/apisix/admin/:splat"
+  status = 200
+  force = true
+
+[[redirects]]
+  from = "/*"
+  to = "/index.html"
+  status = 200 
\ No newline at end of file
diff --git a/package.json b/package.json
index b769c33..2dd0b40 100644
--- a/package.json
+++ b/package.json
@@ -1,82 +1,109 @@
 {
-  "name": "incubator-apisix-dashboard",
-  "version": "0.1.0",
+  "name": "apisix-dashboard",
+  "version": "1.0.2",
   "private": true,
+  "description": "Dashboard for APISIX",
   "scripts": {
-    "serve": "concurrently \"vue-cli-service serve\"",
-    "lint": "vue-cli-service lint",
-    "build:prod": "vue-cli-service build"
+    "analyze": "cross-env ANALYZE=1 umi build",
+    "build": "umi build",
+    "dev": "npm run start:dev",
+    "fetch:blocks": "pro fetch-blocks --branch antd@4 && npm run prettier",
+    "i18n-remove": "pro i18n-remove --locale=zh-CN --write",
+    "postinstall": "umi g tmp",
+    "lint": "umi g tmp && npm run lint:js && npm run lint:style && npm run lint:prettier",
+    "lint-staged": "lint-staged",
+    "lint-staged:js": "eslint --ext .js,.jsx,.ts,.tsx ",
+    "lint:fix": "eslint --fix --cache --ext .js,.jsx,.ts,.tsx --format=pretty ./src && npm run lint:style",
+    "lint:js": "eslint --cache --ext .js,.jsx,.ts,.tsx --format=pretty ./src",
+    "lint:prettier": "prettier --check \"**/*\" --end-of-line auto",
+    "lint:style": "stylelint --fix \"src/**/*.less\" --syntax less",
+    "prettier": "prettier -c --write \"**/*\"",
+    "site": "npm run fetch:blocks && npm run build",
+    "start": "umi dev",
+    "start:dev": "cross-env REACT_APP_ENV=dev MOCK=none umi dev",
+    "start:no-mock": "cross-env MOCK=none umi dev",
+    "start:no-ui": "cross-env UMI_UI=none umi dev",
+    "start:pre": "cross-env REACT_APP_ENV=pre umi dev",
+    "start:test": "cross-env REACT_APP_ENV=test MOCK=none umi dev",
+    "pretest": "node ./tests/beforeTest",
+    "test": "umi test",
+    "test:all": "node ./tests/run-tests.js",
+    "test:component": "umi test ./src/components",
+    "tsc": "tsc"
   },
-  "dependencies": {
-    "axios": "^0.19.0",
-    "echarts": "^4.2.1",
-    "element-ui": "^2.9.2",
-    "fuse.js": "^3.4.5",
-    "js-cookie": "^2.2.0",
-    "normalize.css": "^8.0.1",
-    "nprogress": "^0.2.0",
-    "path-to-regexp": "^3.0.0",
-    "screenfull": "^4.2.0",
-    "uuid": "^3.3.2",
-    "vue": "^2.6.10",
-    "vue-class-component": "^7.1.0",
-    "vue-i18n": "^8.11.2",
-    "vue-property-decorator": "^8.2.1",
-    "vue-router": "^3.0.6",
-    "vue-svgicon": "^3.2.6",
-    "vuex": "^3.1.1",
-    "vuex-class": "^0.3.2",
-    "vuex-module-decorators": "^0.9.9"
+  "husky": {
+    "hooks": {
+      "pre-commit": "npm run lint-staged"
+    }
   },
-  "devDependencies": {
-    "@types/compression": "^0.0.36",
-    "@types/echarts": "^4.1.9",
-    "@types/js-cookie": "^2.2.2",
-    "@types/nprogress": "^0.2.0",
-    "@types/webpack-env": "^1.13.9",
-    "@vue/cli-plugin-babel": "^3.8.0",
-    "@vue/cli-plugin-eslint": "^3.8.0",
-    "@vue/cli-plugin-typescript": "^3.8.1",
-    "@vue/cli-service": "^3.8.4",
-    "@vue/eslint-config-standard": "^4.0.0",
-    "@vue/eslint-config-typescript": "^4.0.0",
-    "babel-core": "^7.0.0-bridge.0",
-    "babel-eslint": "^10.0.2",
-    "concurrently": "^4.1.0",
-    "eslint": "^5.16.0",
-    "eslint-plugin-vue": "^5.2.2",
-    "fibers": "^4.0.1",
-    "lint-staged": "^8.2.1",
-    "sass": "^1.21.0",
-    "sass-loader": "^7.1.0",
-    "style-resources-loader": "^1.2.1",
-    "typescript": "3.5.2",
-    "vue-cli-plugin-element": "^1.0.1",
-    "vue-cli-plugin-style-resources-loader": "^0.1.3",
-    "vue-template-compiler": "^2.6.10",
-    "webpack": "^4.35.0"
+  "lint-staged": {
+    "**/*.less": "stylelint --syntax less",
+    "**/*.{js,jsx,ts,tsx}": "npm run lint-staged:js",
+    "**/*.{js,jsx,tsx,ts,less,md,json}": [
+      "prettier --write"
+    ]
   },
-  "bugs": {
-    "url": "https://github.com/apache/incubator-apisix-dashboard/issues"
+  "dependencies": {
+    "@ant-design/icons": "^4.2.1",
+    "@ant-design/pro-layout": "6.0.0-2",
+    "@ant-design/pro-table": "^2.3.3",
+    "antd": "^4.3.3",
+    "classnames": "^2.2.6",
+    "lodash": "^4.17.15",
+    "moment": "^2.25.3",
+    "nzh": "^1.0.3",
+    "omit.js": "^1.0.2",
+    "path-to-regexp": "2.4.0",
+    "qs": "^6.9.0",
+    "react": "^16.8.6",
+    "react-dom": "^16.8.6",
+    "umi": "^3.1.0",
+    "umi-request": "^1.3.3",
+    "use-merge-value": "^1.0.1",
+    "uuid": "^7.0.2"
   },
-  "gitHooks": {
-    "pre-commit": "lint-staged"
+  "devDependencies": {
+    "@ant-design/pro-cli": "^2.0.2",
+    "@types/classnames": "^2.2.7",
+    "@types/express": "^4.17.0",
+    "@types/history": "^4.7.2",
+    "@types/jest": "^25.1.0",
+    "@types/lodash": "^4.14.144",
+    "@types/node-forge": "^0.9.3",
+    "@types/qs": "^6.5.3",
+    "@types/react": "^16.9.17",
+    "@types/react-dom": "^16.8.4",
+    "@types/react-helmet": "^5.0.13",
+    "@types/uuid": "^7.0.0",
+    "@umijs/fabric": "^2.0.5",
+    "@umijs/plugin-blocks": "^2.0.5",
+    "@umijs/plugin-esbuild": "^1.0.0-beta.2",
+    "@umijs/preset-ant-design-pro": "^1.2.0",
+    "@umijs/preset-react": "^1.4.24",
+    "@umijs/preset-ui": "^2.1.11",
+    "carlo": "^0.9.46",
+    "cross-env": "^7.0.0",
+    "cross-port-killer": "^1.1.1",
+    "detect-installer": "^1.0.1",
+    "eslint": "^6.8.0",
+    "express": "^4.17.1",
+    "husky": "^4.0.7",
+    "lint-staged": "^10.0.0",
+    "mockjs": "^1.0.1-beta3",
+    "prettier": "^2.0.1",
+    "pro-download": "1.0.1",
+    "puppeteer-core": "^2.1.1",
+    "react-helmet-async": "^1.0.6",
+    "stylelint": "^13.0.0"
   },
-  "keywords": [
-    "vue",
-    "typescript",
-    "admin",
-    "template",
-    "element-ui"
-  ],
-  "lint-staged": {
-    "*.{js,vue}": [
-      "vue-cli-service lint",
-      "git add"
-    ]
+  "engines": {
+    "node": ">=10.0.0"
   },
-  "repository": {
-    "type": "git",
-    "url": "git+https://github.com/apache/incubator-apisix-dashboard.git"
-  }
+  "checkFiles": [
+    "src/**/*.js*",
+    "src/**/*.ts*",
+    "src/**/*.less",
+    "config/**/*.js*",
+    "scripts/**/*.js"
+  ]
 }
diff --git a/public/favicon.ico b/public/favicon.ico
deleted file mode 100644
index 05cb493..0000000
Binary files a/public/favicon.ico and /dev/null differ
diff --git a/public/favicon.png b/public/favicon.png
new file mode 100644
index 0000000..381ab08
Binary files /dev/null and b/public/favicon.png differ
diff --git a/public/home_bg.png b/public/home_bg.png
new file mode 100644
index 0000000..7c92a4b
Binary files /dev/null and b/public/home_bg.png differ
diff --git a/public/icons/icon-128x128.png b/public/icons/icon-128x128.png
new file mode 100644
index 0000000..48d0e23
Binary files /dev/null and b/public/icons/icon-128x128.png differ
diff --git a/public/icons/icon-192x192.png b/public/icons/icon-192x192.png
new file mode 100644
index 0000000..938e9b5
Binary files /dev/null and b/public/icons/icon-192x192.png differ
diff --git a/public/icons/icon-512x512.png b/public/icons/icon-512x512.png
new file mode 100644
index 0000000..21fc108
Binary files /dev/null and b/public/icons/icon-512x512.png differ
diff --git a/public/img/icons/favicon-16x16.png b/public/img/icons/favicon-16x16.png
deleted file mode 100644
index 05cb493..0000000
Binary files a/public/img/icons/favicon-16x16.png and /dev/null differ
diff --git a/public/img/icons/favicon-32x32.png b/public/img/icons/favicon-32x32.png
deleted file mode 100644
index 05cb493..0000000
Binary files a/public/img/icons/favicon-32x32.png and /dev/null differ
diff --git a/public/index.html b/public/index.html
deleted file mode 100644
index c3907ad..0000000
--- a/public/index.html
+++ /dev/null
@@ -1,43 +0,0 @@
-<!--
-#
-# MIT License
-#
-# Copyright (c) 2018 Chong Guo
-# 
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-# 
-# The above copyright notice and this permission notice shall be included in all
-# copies or substantial portions of the Software.
-# 
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-# SOFTWARE.
-#
--->
-
-<!DOCTYPE html>
-<html lang="en">
-  <head>
-    <meta charset="utf-8">
-    <meta http-equiv="X-UA-Compatible" content="IE=edge">
-    <meta name="viewport" content="width=device-width,initial-scale=1.0">
-    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
-    <title><%= webpackConfig.name %></title>
-  </head>
-  <body>
-    <noscript>
-      <strong>We're sorry but <%= webpackConfig.name %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
-    </noscript>
-    <div id="app"></div>
-    <!-- built files will be auto injected -->
-  </body>
-</html>
diff --git a/public/pro_icon.svg b/public/pro_icon.svg
new file mode 100644
index 0000000..2c24ec7
--- /dev/null
+++ b/public/pro_icon.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="图层_1" width="512" height="512" x="0" y="0" enable-background="new 0 0 512 512" version="1.1" viewBox="0 0 512 512" xml:space="preserve"><path fill-rule="evenodd" d="M259.119,233.588c0-3.644,0.041-7.289-0.008-10.932	c-0.111-8.558-4.697-13.308-13.231-13.486c-6.658-0.139-13.326,0.12-19.98-0.096c-3.292-0.107-4.247,0.995-4.24,4.266	c0.094,44.794,0.101,89.589-0.008,134.383c-0.009,3.492,1.346,4.154,4.407,4.11 [...]
\ No newline at end of file
diff --git a/public/robots.txt b/public/robots.txt
deleted file mode 100644
index eb05362..0000000
--- a/public/robots.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-User-agent: *
-Disallow:
diff --git a/src/access.ts b/src/access.ts
new file mode 100644
index 0000000..207bc4b
--- /dev/null
+++ b/src/access.ts
@@ -0,0 +1,6 @@
+export default function (initialState: { currentUser?: API.CurrentUser | undefined }) {
+  const { currentUser } = initialState || {};
+  return {
+    canAdmin: currentUser && currentUser.access === 'admin',
+  };
+}
diff --git a/src/api/schema/routes.ts b/src/api/schema/routes.ts
deleted file mode 100644
index a871a96..0000000
--- a/src/api/schema/routes.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import request from '@/utils/request'
-import { IRouteData } from '../types'
-
-export const getList = () =>
-  request({
-    url: '/routes',
-    method: 'GET'
-  })
-
-export const updateRouter = (id: string, data: any) =>
-  request({
-    url: `/routes/${id}`,
-    method: 'PUT',
-    data
-  })
-
-export const getRouter = (id: string) =>
-  request({
-    url: `/routes/${id}`,
-    method: 'GET'
-  })
-
-export const removeRouter = (id: string) =>
-  request({
-    url: `/routes/${id}`,
-    method: 'DELETE'
-  })
-
-export const createRouter = (data: any) =>
-  request({
-    url: '/routes',
-    method: 'POST',
-    data
-  })
diff --git a/src/api/schema/schema.ts b/src/api/schema/schema.ts
deleted file mode 100644
index e58a27b..0000000
--- a/src/api/schema/schema.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import request from '@/utils/request'
-
-export const getPluginSchema = (name: string) =>
-  request({
-    url: `/schema/plugins/${name}`,
-    method: 'get'
-  })
diff --git a/src/api/schema/services.ts b/src/api/schema/services.ts
deleted file mode 100644
index d6bb1bb..0000000
--- a/src/api/schema/services.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import request from '@/utils/request'
-import { IServiceData } from '../types'
-
-export const getServiceList = () =>
-  request({
-    url: '/services',
-    method: 'get'
-  })
-
-export const updateService = (id: string, data: any) =>
-  request({
-    url: `/services/${id}`,
-    method: 'PUT',
-    data
-  })
-
-export const getService = (id: string) =>
-  request({
-    url: `/services/${id}`,
-    method: 'GET'
-  })
-
-export const removeService = (id: string) =>
-  request({
-    url: `/services/${id}`,
-    method: 'DELETE'
-  })
-
-export const createService = (data: any) =>
-  request({
-    url: '/services',
-    method: 'POST',
-    data
-  })
diff --git a/src/api/schema/ssl.ts b/src/api/schema/ssl.ts
deleted file mode 100644
index b945c14..0000000
--- a/src/api/schema/ssl.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import request from '@/utils/request'
-
-import { ISSLData } from '../types'
-
-export const getSSLList = () =>
-  request({
-    url: '/ssl',
-    method: 'GET'
-  })
-
-export const updateSSL = (id: string, data: any) =>
-  request({
-    url: `/ssl/${id}`,
-    method: 'PUT',
-    data
-  })
-
-export const getSSL = (id: string) =>
-  request({
-    url: `/ssl/${id}`,
-    method: 'GET'
-  })
-
-export const removeSSL = (id: string) =>
-  request({
-    url: `/ssl/${id}`,
-    method: 'DELETE'
-  })
-
-export const createSSL = (data: any) =>
-  request({
-    url: '/ssl',
-    method: 'POST',
-    data
-  })
diff --git a/src/api/schema/upstream.ts b/src/api/schema/upstream.ts
deleted file mode 100644
index 04c6f03..0000000
--- a/src/api/schema/upstream.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import request from '@/utils/request'
-
-export const updateStream = (id: string, data: any) =>
-  request({
-    url: `/upstreams/${id}`,
-    method: 'PUT',
-    data
-  })
-
-export const getUpstream = (id: string) =>
-  request({
-    url: `/upstreams/${id}`,
-    method: 'GET'
-  })
-
-export const removeUpstream = (id: string) =>
-  request({
-    url: `/upstreams/${id}`,
-    method: 'DELETE'
-  })
-
-export const createUpstream = (data: any) =>
-  request({
-    url: `/upstreams`,
-    method: 'POST',
-    data
-  })
-
-export const getUpstreamList = () =>
-  request({
-    url: `/upstreams`,
-    method: 'GET'
-  })
diff --git a/src/api/types.d.ts b/src/api/types.d.ts
deleted file mode 100644
index c80ec23..0000000
--- a/src/api/types.d.ts
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-export type TypeID = number | string
-export type TypePlugin = object
-
-export interface IConsumerData {
-  username: string
-  plugins?: TypePlugin
-}
-
-export interface IRouteData {
-  methods: []
-  plugins: TypePlugin
-  upstream: IUpstreamData
-  uri: string
-  host: string
-  remote_addr: string
-  service_id: TypeID
-  upstream_id: TypeID
-  id: TypeID
-}
-
-export interface IServiceData {
-  id: TypeID
-  plugins: TypePlugin
-  upstream: IUpstreamData
-  upstream_id: TypeID
-}
-
-export interface ISSLData {
-  cert: string
-  key: string
-  sni: string
-}
-
-export enum EnumUpstreamType {
-  chash,
-  roundrobin
-}
-
-export enum EnumUpstreamKey {
-  remote_addr
-}
-export interface IUpstreamData {
-  nodes: object
-  type: EnumUpstreamType
-  key?: EnumUpstreamKey
-  id?: TypeID
-}
-
-export enum EnumAction {
-  get,
-  set,
-  delete,
-  create
-}
-
-export interface IDataWrapper<T> {
-  node: {
-    value: T
-  },
-  action: EnumAction
-}
diff --git a/src/app.tsx b/src/app.tsx
new file mode 100644
index 0000000..1a38f9c
--- /dev/null
+++ b/src/app.tsx
@@ -0,0 +1,92 @@
+import React from 'react';
+import { notification } from 'antd';
+import { RequestConfig, history } from 'umi';
+import { BasicLayoutProps, Settings as LayoutSettings } from '@ant-design/pro-layout';
+
+import { getSetting } from '@/pages/Setting';
+import RightContent from '@/components/RightContent';
+import Footer from '@/components/Footer';
+import { queryCurrent } from '@/services/user';
+import defaultSettings from '../config/defaultSettings';
+
+export async function getInitialState(): Promise<{
+  currentUser?: API.CurrentUser;
+  settings?: LayoutSettings;
+}> {
+  // 如果是设置页面,不执行
+  if (history.location.pathname !== '/setting') {
+    try {
+      const currentUser = await queryCurrent();
+      return {
+        currentUser,
+        settings: defaultSettings,
+      };
+    } catch (error) {
+      history.push('/setting');
+    }
+  }
+  return {
+    settings: defaultSettings,
+  };
+}
+
+export const layout = ({
+  initialState,
+}: {
+  initialState: { settings?: LayoutSettings };
+}): BasicLayoutProps => {
+  return {
+    rightContentRender: () => <RightContent />,
+    disableContentMargin: false,
+    footerRender: () => <Footer />,
+    menuHeaderRender: undefined,
+    ...initialState?.settings,
+  };
+};
+
+const codeMessage = {
+  200: '服务器成功返回请求的数据。',
+  201: '新建或修改数据成功。',
+  202: '一个请求已经进入后台排队(异步任务)。',
+  204: '删除数据成功。',
+  400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。',
+  401: '用户没有权限(令牌、用户名、密码错误)。',
+  403: '用户得到授权,但是访问是被禁止的。',
+  404: '发出的请求针对的是不存在的记录,服务器没有进行操作。',
+  406: '请求的格式不可得。',
+  410: '请求的资源被永久删除,且不会再得到的。',
+  422: '当创建一个对象时,发生一个验证错误。',
+  500: '服务器发生错误,请检查服务器。',
+  502: '网关错误。',
+  503: '服务不可用,服务器暂时过载或维护。',
+  504: '网关超时。',
+};
+
+/**
+ * 异常处理程序
+ */
+const errorHandler = (error: { response: Response; data: any }): Promise<Response> => {
+  const { response } = error;
+  if (response && response.status) {
+    const errorText =
+      error.data.msg || error.data.message || error.data.error_msg || codeMessage[response.status];
+
+    notification.error({
+      message: `请求错误,错误码: ${error.data.errorCode || response.status}`,
+      description: errorText,
+    });
+  } else if (!response) {
+    notification.error({
+      description: '您的网络发生异常,无法连接服务器',
+      message: '网络异常',
+    });
+  }
+  return Promise.reject(response);
+};
+
+const { baseURL } = getSetting();
+export const request: RequestConfig = {
+  prefix: baseURL,
+  errorHandler,
+  credentials: 'same-origin',
+};
diff --git a/src/assets/logo.svg b/src/assets/logo.svg
new file mode 100644
index 0000000..ef5ec53
--- /dev/null
+++ b/src/assets/logo.svg
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg viewBox="0 0 500 500" xmlns="http://www.w3.org/2000/svg">
+  <defs>
+    <linearGradient id="id0" gradientUnits="userSpaceOnUse" x1="25119.8" y1="11052.5" x2="21725.7" y2="21551.7" gradientTransform="matrix(0.028095, 0, 0, 0.028095, -492.867096, -144.769821)">
+      <stop offset="0" style="stop-color:#A92F33"/>
+      <stop offset="1" style="stop-color:#E62129"/>
+    </linearGradient>
+    <linearGradient id="id1" gradientUnits="userSpaceOnUse" x1="27026.6" y1="8021.8" x2="30514.6" y2="16218.5" gradientTransform="matrix(0.028095, 0, 0, 0.028095, -492.867096, -144.769821)">
+      <stop offset="0" style="stop-color:#A92F33"/>
+      <stop offset="1" style="stop-color:#E8443F"/>
+    </linearGradient>
+    <linearGradient id="id2" gradientUnits="userSpaceOnUse" x1="23046.1" y1="14340.2" x2="26713.9" y2="9900.07" gradientTransform="matrix(0.028095, 0, 0, 0.028095, -492.867096, -144.769821)">
+      <stop offset="0" style="stop-color:#E62129"/>
+      <stop offset="1" style="stop-color:#E8443F"/>
+    </linearGradient>
+  </defs>
+  <path class="fil1" d="M 156.005 337.641 L 247.284 205.232 L 218.515 160.195 L 134.429 259.986 C 134.429 259.986 134.429 259.986 134.429 259.986 C 134.429 259.986 134.429 259.986 134.429 259.986 C 106.503 293.139 102.007 301.399 78.689 340.281 L 156.033 337.641 Z" style="fill: url(#id0);"/>
+  <path class="fil2" d="M 382.616 340.281 L 419.616 340.141 L 253.325 51.188 L 253.325 51.188 L 333.619 340.281 L 382.644 340.281 Z M 253.297 51.188 L 211.239 120.694 L 219.695 106.732 L 253.297 51.216 Z" style="fill: url(#id1);"/>
+  <polygon class="fil3" points="218.487 160.195 333.59 340.281 253.297 51.188 253.297 51.188 253.297 51.188 219.724 106.703 78.661 340.281" style="fill: url(#id2);"/>
+  <path d="M 116.292 266.849 L 116.292 278.269 L 111.252 278.269 L 111.252 277.099 C 111.119 277.392 110.855 277.659 110.462 277.899 C 110.062 278.146 109.702 278.269 109.382 278.269 L 95.732 278.269 C 95.365 278.269 94.995 278.176 94.622 277.989 C 94.249 277.796 93.945 277.526 93.712 277.179 C 93.479 276.839 93.359 276.452 93.352 276.019 C 93.345 275.192 93.335 274.146 93.322 272.879 C 93.309 271.612 93.302 270.946 93.302 270.879 C 93.302 269.932 93.555 269.189 94.062 268.649 C 94.562 2 [...]
+</svg>
\ No newline at end of file
diff --git a/src/components/Breadcrumb/index.vue b/src/components/Breadcrumb/index.vue
deleted file mode 100644
index 54c04ce..0000000
--- a/src/components/Breadcrumb/index.vue
+++ /dev/null
@@ -1,125 +0,0 @@
-<!--
-#
-# MIT License
-#
-# Copyright (c) 2018 Chong Guo
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in all
-# copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-# SOFTWARE.
-#
--->
-
-<template>
-  <el-breadcrumb
-    class="app-breadcrumb"
-    separator="/"
-  >
-    <transition-group name="breadcrumb">
-      <el-breadcrumb-item
-        v-for="(item, index) in breadcrumbs"
-        :key="item.path"
-      >
-        <span
-          v-if="item.redirect === 'noredirect' || index === breadcrumbs.length-1"
-          class="no-redirect"
-        >{{ $t('route.' + item.meta.title) }}</span>
-        <a
-          v-else
-          @click.prevent="handleLink(item)"
-        >{{ $t('route.' + item.meta.title) }}</a>
-      </el-breadcrumb-item>
-    </transition-group>
-  </el-breadcrumb>
-</template>
-
-<script lang="ts">
-import pathToRegexp from 'path-to-regexp'
-import { Component, Vue, Watch } from 'vue-property-decorator'
-import { RouteRecord, Route } from 'vue-router'
-
-@Component({
-  name: 'Breadcrumb'
-})
-export default class extends Vue {
-  private breadcrumbs: RouteRecord[] = []
-
-  @Watch('$route')
-  private onRouteChange(route: Route) {
-    // if you go to the redirect page, do not update the breadcrumbs
-    if (route.path.startsWith('/redirect/')) {
-      return
-    }
-    this.getBreadcrumb()
-  }
-
-  created() {
-    this.getBreadcrumb()
-  }
-
-  private getBreadcrumb() {
-    let matched = this.$route.matched.filter((item) => item.meta && item.meta.title)
-    const first = matched[0]
-    this.breadcrumbs = matched.filter((item) => {
-      return item.meta && item.meta.title && item.meta.breadcrumb !== false
-    })
-  }
-
-  private isDashboard(route: RouteRecord) {
-    const name = route && route.name
-    if (!name) {
-      return false
-    }
-    return name.trim().toLocaleLowerCase() === 'Dashboard'.toLocaleLowerCase()
-  }
-
-  private pathCompile(path: string) {
-    // To solve this problem https://github.com/PanJiaChen/vue-element-admin/issues/561
-    const { params } = this.$route
-    const toPath = pathToRegexp.compile(path)
-    return toPath(params)
-  }
-
-  private handleLink(item: any) {
-    const { redirect, path } = item
-    if (redirect) {
-      this.$router.push(redirect)
-      return
-    }
-    this.$router.push(this.pathCompile(path))
-  }
-}
-</script>
-
-<style lang="scss" scoped>
-.el-breadcrumb__inner,
-.el-breadcrumb__inner a {
-  font-weight: 400 !important;
-}
-
-.app-breadcrumb.el-breadcrumb {
-  display: inline-block;
-  font-size: 14px;
-  line-height: 50px;
-  margin-left: 8px;
-
-  .no-redirect {
-    color: #97a8be;
-    cursor: text;
-  }
-}
-</style>
diff --git a/src/components/Footer/index.tsx b/src/components/Footer/index.tsx
new file mode 100644
index 0000000..0f6648b
--- /dev/null
+++ b/src/components/Footer/index.tsx
@@ -0,0 +1,17 @@
+import React from 'react';
+import { GithubOutlined } from '@ant-design/icons';
+import { DefaultFooter } from '@ant-design/pro-layout';
+
+export default () => (
+  <DefaultFooter
+    copyright="2020 Apache APISIX"
+    links={[
+      {
+        key: 'GitHub',
+        title: <GithubOutlined />,
+        href: 'https://github.com/apache/incubator-apisix',
+        blankTarget: true,
+      },
+    ]}
+  />
+);
diff --git a/src/components/Hamburger/index.vue b/src/components/Hamburger/index.vue
deleted file mode 100644
index e21e513..0000000
--- a/src/components/Hamburger/index.vue
+++ /dev/null
@@ -1,75 +0,0 @@
-<!--
-#
-# MIT License
-#
-# Copyright (c) 2018 Chong Guo
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in all
-# copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-# SOFTWARE.
-#
--->
-
-<template>
-  <div
-    :class="[{'is-active': isActive}]"
-    @click="toggleClick"
-  >
-    <svg
-      viewBox="0 0 100 80"
-      width="20"
-      height="20"
-    >
-      <rect
-        width="100"
-        height="20"
-      />
-      <rect
-        y="30"
-        width="100"
-        height="20"
-      />
-      <rect
-        y="60"
-        width="100"
-        height="20"
-      />
-    </svg>
-  </div>
-</template>
-
-<script lang="ts">
-import { Component, Prop, Vue } from 'vue-property-decorator'
-
-@Component({
-  name: 'Hamburger'
-})
-export default class extends Vue {
-  @Prop({ default: false }) private isActive!: boolean
-
-  private toggleClick() {
-    this.$emit('toggleClick')
-  }
-}
-</script>
-
-<style lang="scss" scoped>
-.svg-icon {
-  vertical-align: middle;
-}
-
-</style>
diff --git a/src/components/HeaderDropdown/index.less b/src/components/HeaderDropdown/index.less
new file mode 100644
index 0000000..004b53e
--- /dev/null
+++ b/src/components/HeaderDropdown/index.less
@@ -0,0 +1,16 @@
+@import '~antd/es/style/themes/default.less';
+
+.container > * {
+  background-color: @popover-bg;
+  border-radius: 4px;
+  box-shadow: @shadow-1-down;
+}
+
+@media screen and (max-width: @screen-xs) {
+  .container {
+    width: 100% !important;
+  }
+  .container > * {
+    border-radius: 0 !important;
+  }
+}
diff --git a/src/components/HeaderDropdown/index.tsx b/src/components/HeaderDropdown/index.tsx
new file mode 100644
index 0000000..cc60727
--- /dev/null
+++ b/src/components/HeaderDropdown/index.tsx
@@ -0,0 +1,19 @@
+import { DropDownProps } from 'antd/es/dropdown';
+import { Dropdown } from 'antd';
+import React from 'react';
+import classNames from 'classnames';
+import styles from './index.less';
+
+declare type OverlayFunc = () => React.ReactNode;
+
+export interface HeaderDropdownProps extends Omit<DropDownProps, 'overlay'> {
+  overlayClassName?: string;
+  overlay: React.ReactNode | OverlayFunc | any;
+  placement?: 'bottomLeft' | 'bottomRight' | 'topLeft' | 'topCenter' | 'topRight' | 'bottomCenter';
+}
+
+const HeaderDropdown: React.FC<HeaderDropdownProps> = ({ overlayClassName: cls, ...restProps }) => (
+  <Dropdown overlayClassName={classNames(styles.container, cls)} {...restProps} />
+);
+
+export default HeaderDropdown;
diff --git a/src/components/HeaderSearch/index.vue b/src/components/HeaderSearch/index.vue
deleted file mode 100644
index c1a2428..0000000
--- a/src/components/HeaderSearch/index.vue
+++ /dev/null
@@ -1,244 +0,0 @@
-<!--
-#
-# MIT License
-#
-# Copyright (c) 2018 Chong Guo
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in all
-# copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-# SOFTWARE.
-#
--->
-
-<template>
-  <div
-    id="header-search"
-    :class="{'show': show}"
-    class="header-search"
-  >
-    <svg-icon
-      class="search-icon"
-      name="search"
-      @click.stop="click"
-    />
-    <el-select
-      ref="headerSearchSelect"
-      v-model="search"
-      :remote-method="querySearch"
-      filterable
-      default-first-option
-      remote
-      placeholder="Search"
-      class="header-search-select"
-      @change="change"
-    >
-      <el-option
-        v-for="item in options"
-        :key="item.path"
-        :value="item"
-        :label="item.meta.title.join(' > ')"
-      />
-    </el-select>
-  </div>
-</template>
-
-<script lang="ts">
-import path from 'path'
-import Fuse from 'fuse.js' // A lightweight fuzzy-search module
-import { Component, Vue, Watch } from 'vue-property-decorator'
-import { RouteConfig } from 'vue-router'
-import { AppModule } from '@/store/modules/app'
-import { PermissionModule } from '@/store/modules/permission'
-import i18n from '@/lang' // Internationalization
-
-@Component({
-  name: 'HeaderSearch'
-})
-export default class extends Vue {
-  private search = ''
-  private show = false
-  private options: RouteConfig[] = []
-  private searchPool: RouteConfig[] = []
-  private fuse?: Fuse<RouteConfig, Fuse.FuseOptions<RouteConfig>>
-
-  get routes() {
-    return PermissionModule.routes
-  }
-
-  get lang() {
-    return AppModule.language
-  }
-
-  @Watch('lang')
-  private onLangChange() {
-    this.searchPool = this.generateRoutes(this.routes)
-  }
-
-  @Watch('routes')
-  private onRoutesChange() {
-    this.searchPool = this.generateRoutes(this.routes)
-  }
-
-  @Watch('searchPool')
-  private onSearchPoolChange(value: RouteConfig[]) {
-    this.initFuse(value)
-  }
-
-  @Watch('show')
-  private onShowChange(value: boolean) {
-    if (value) {
-      document.body.addEventListener('click', this.close)
-    } else {
-      document.body.removeEventListener('click', this.close)
-    }
-  }
-
-  mounted() {
-    this.searchPool = this.generateRoutes(this.routes)
-  }
-
-  private click() {
-    this.show = !this.show
-    if (this.show) {
-      this.$refs.headerSearchSelect && (this.$refs.headerSearchSelect as HTMLElement).focus()
-    }
-  }
-
-  private close() {
-    this.$refs.headerSearchSelect && (this.$refs.headerSearchSelect as HTMLElement).blur()
-    this.options = []
-    this.show = false
-  }
-
-  private change(route: RouteConfig) {
-    this.$router.push(route.path)
-    this.search = ''
-    this.options = []
-    this.$nextTick(() => {
-      this.show = false
-    })
-  }
-
-  private initFuse(list: RouteConfig[]) {
-    this.fuse = new Fuse(list, {
-      shouldSort: true,
-      threshold: 0.4,
-      location: 0,
-      distance: 100,
-      maxPatternLength: 32,
-      minMatchCharLength: 1,
-      keys: [{
-        name: 'title',
-        weight: 0.7
-      }, {
-        name: 'path',
-        weight: 0.3
-      }]
-    })
-  }
-
-  // Filter out the routes that can be displayed in the sidebar
-  // And generate the internationalized title
-  private generateRoutes(routes: RouteConfig[], basePath = '/', prefixTitle: string[] = []) {
-    let res: RouteConfig[] = []
-
-    for (const router of routes) {
-      // skip hidden router
-      if (router.meta && router.meta.hidden) {
-        continue
-      }
-
-      const data: RouteConfig = {
-        path: path.resolve(basePath, router.path),
-        meta: {
-          title: [...prefixTitle]
-        }
-      }
-
-      if (router.meta && router.meta.title) {
-        // generate internationalized title
-        const i18ntitle = i18n.t(`route.${router.meta.title}`).toString()
-        data.meta.title = [...data.meta.title, i18ntitle]
-        if (router.redirect !== 'noRedirect') {
-          // only push the routes with title
-          // special case: need to exclude parent router without redirect
-          res.push(data)
-        }
-      }
-
-      // recursive child routes
-      if (router.children) {
-        const tempRoutes = this.generateRoutes(router.children, data.path, data.meta.title)
-        if (tempRoutes.length >= 1) {
-          res = [...res, ...tempRoutes]
-        }
-      }
-    }
-    return res
-  }
-
-  private querySearch(query: string) {
-    if (query !== '') {
-      if (this.fuse) {
-        this.options = this.fuse.search(query) as any
-      }
-    } else {
-      this.options = []
-    }
-  }
-}
-</script>
-
-<style lang="scss" scoped>
-.header-search {
-  font-size: 0 !important;
-
-  .search-icon {
-    cursor: pointer;
-    font-size: 18px;
-    vertical-align: middle;
-  }
-
-  .header-search-select {
-    font-size: 18px;
-    transition: width 0.2s;
-    width: 0;
-    overflow: hidden;
-    background: transparent;
-    border-radius: 0;
-    display: inline-block;
-    vertical-align: middle;
-
-    .el-input__inner {
-      border-radius: 0;
-      border: 0;
-      padding-left: 0;
-      padding-right: 0;
-      box-shadow: none !important;
-      border-bottom: 1px solid #d9d9d9;
-      vertical-align: middle;
-    }
-  }
-
-  &.show {
-    .header-search-select {
-      width: 210px;
-      margin-left: 10px;
-    }
-  }
-}
-</style>
diff --git a/src/components/LangSelect/index.vue b/src/components/LangSelect/index.vue
deleted file mode 100644
index 937813a..0000000
--- a/src/components/LangSelect/index.vue
+++ /dev/null
@@ -1,77 +0,0 @@
-<!--
-#
-# MIT License
-#
-# Copyright (c) 2018 Chong Guo
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in all
-# copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-# SOFTWARE.
-#
--->
-
-<template>
-  <el-dropdown
-    trigger="click"
-    class="international"
-    @command="handleSetLanguage"
-  >
-    <div>
-      <svg-icon
-        name="language"
-        class="international-icon"
-      />
-    </div>
-    <el-dropdown-menu slot="dropdown">
-      <el-dropdown-item
-        :disabled="language==='zh'"
-        command="zh"
-      >
-        中文
-      </el-dropdown-item>
-      <el-dropdown-item
-        :disabled="language==='en'"
-        command="en"
-      >
-        English
-      </el-dropdown-item>
-    </el-dropdown-menu>
-  </el-dropdown>
-</template>
-
-<script lang="ts">
-import { Component, Vue, Watch } from 'vue-property-decorator'
-import { AppModule } from '@/store/modules/app'
-
-@Component({
-  name: 'Login'
-})
-export default class extends Vue {
-  get language() {
-    return AppModule.language
-  }
-
-  private handleSetLanguage(lang: string) {
-    this.$i18n.locale = lang
-    AppModule.SetLanguage(lang)
-    this.$message({
-      message: 'Switch Language Success',
-      type: 'success'
-    })
-  }
-}
-</script>
diff --git a/src/components/NoticeIcon/NoticeList.less b/src/components/NoticeIcon/NoticeList.less
new file mode 100755
index 0000000..1aba610
--- /dev/null
+++ b/src/components/NoticeIcon/NoticeList.less
@@ -0,0 +1,103 @@
+@import '~antd/es/style/themes/default.less';
+
+.list {
+  max-height: 400px;
+  overflow: auto;
+  &::-webkit-scrollbar {
+    display: none;
+  }
+  .item {
+    padding-right: 24px;
+    padding-left: 24px;
+    overflow: hidden;
+    cursor: pointer;
+    transition: all 0.3s;
+
+    .meta {
+      width: 100%;
+    }
+
+    .avatar {
+      margin-top: 4px;
+      background: #fff;
+    }
+    .iconElement {
+      font-size: 32px;
+    }
+
+    &.read {
+      opacity: 0.4;
+    }
+    &:last-child {
+      border-bottom: 0;
+    }
+    &:hover {
+      background: @primary-1;
+    }
+    .title {
+      margin-bottom: 8px;
+      font-weight: normal;
+    }
+    .description {
+      font-size: 12px;
+      line-height: @line-height-base;
+    }
+    .datetime {
+      margin-top: 4px;
+      font-size: 12px;
+      line-height: @line-height-base;
+    }
+    .extra {
+      float: right;
+      margin-top: -1.5px;
+      margin-right: 0;
+      color: @text-color-secondary;
+      font-weight: normal;
+    }
+  }
+  .loadMore {
+    padding: 8px 0;
+    color: @primary-6;
+    text-align: center;
+    cursor: pointer;
+    &.loadedAll {
+      color: rgba(0, 0, 0, 0.25);
+      cursor: unset;
+    }
+  }
+}
+
+.notFound {
+  padding: 73px 0 88px;
+  color: @text-color-secondary;
+  text-align: center;
+  img {
+    display: inline-block;
+    height: 76px;
+    margin-bottom: 16px;
+  }
+}
+
+.bottomBar {
+  height: 46px;
+  color: @text-color;
+  line-height: 46px;
+  text-align: center;
+  border-top: 1px solid @border-color-split;
+  border-radius: 0 0 @border-radius-base @border-radius-base;
+  transition: all 0.3s;
+  div {
+    display: inline-block;
+    width: 50%;
+    cursor: pointer;
+    transition: all 0.3s;
+    user-select: none;
+
+    &:only-child {
+      width: 100%;
+    }
+    &:not(:only-child):last-child {
+      border-left: 1px solid @border-color-split;
+    }
+  }
+}
diff --git a/src/components/NoticeIcon/NoticeList.tsx b/src/components/NoticeIcon/NoticeList.tsx
new file mode 100644
index 0000000..8d00be2
--- /dev/null
+++ b/src/components/NoticeIcon/NoticeList.tsx
@@ -0,0 +1,113 @@
+import { Avatar, List } from 'antd';
+
+import React from 'react';
+import classNames from 'classnames';
+import styles from './NoticeList.less';
+
+export interface NoticeIconTabProps {
+  count?: number;
+  name?: string;
+  showClear?: boolean;
+  showViewMore?: boolean;
+  style?: React.CSSProperties;
+  title: string;
+  tabKey: string;
+  data?: API.NoticeIconData[];
+  onClick?: (item: API.NoticeIconData) => void;
+  onClear?: () => void;
+  emptyText?: string;
+  clearText?: string;
+  viewMoreText?: string;
+  list: API.NoticeIconData[];
+  onViewMore?: (e: any) => void;
+}
+const NoticeList: React.SFC<NoticeIconTabProps> = ({
+  data = [],
+  onClick,
+  onClear,
+  title,
+  onViewMore,
+  emptyText,
+  showClear = true,
+  clearText,
+  viewMoreText,
+  showViewMore = false,
+}) => {
+  if (data.length === 0) {
+    return (
+      <div className={styles.notFound}>
+        <img
+          src="https://gw.alipayobjects.com/zos/rmsportal/sAuJeJzSKbUmHfBQRzmZ.svg"
+          alt="not found"
+        />
+        <div>{emptyText}</div>
+      </div>
+    );
+  }
+  return (
+    <div>
+      <List<NoticeIconData>
+        className={styles.list}
+        dataSource={data}
+        renderItem={(item, i) => {
+          const itemCls = classNames(styles.item, {
+            [styles.read]: item.read,
+          });
+          // eslint-disable-next-line no-nested-ternary
+          const leftIcon = item.avatar ? (
+            typeof item.avatar === 'string' ? (
+              <Avatar className={styles.avatar} src={item.avatar} />
+            ) : (
+              <span className={styles.iconElement}>{item.avatar}</span>
+            )
+          ) : null;
+
+          return (
+            <List.Item
+              className={itemCls}
+              key={item.key || i}
+              onClick={() => onClick && onClick(item)}
+            >
+              <List.Item.Meta
+                className={styles.meta}
+                avatar={leftIcon}
+                title={
+                  <div className={styles.title}>
+                    {item.title}
+                    <div className={styles.extra}>{item.extra}</div>
+                  </div>
+                }
+                description={
+                  <div>
+                    <div className={styles.description}>{item.description}</div>
+                    <div className={styles.datetime}>{item.datetime}</div>
+                  </div>
+                }
+              />
+            </List.Item>
+          );
+        }}
+      />
+      <div className={styles.bottomBar}>
+        {showClear ? (
+          <div onClick={onClear}>
+            {clearText} {title}
+          </div>
+        ) : null}
+        {showViewMore ? (
+          <div
+            onClick={(e) => {
+              if (onViewMore) {
+                onViewMore(e);
+              }
+            }}
+          >
+            {viewMoreText}
+          </div>
+        ) : null}
+      </div>
+    </div>
+  );
+};
+
+export default NoticeList;
diff --git a/src/components/NoticeIcon/index.less b/src/components/NoticeIcon/index.less
new file mode 100644
index 0000000..650ccd2
--- /dev/null
+++ b/src/components/NoticeIcon/index.less
@@ -0,0 +1,31 @@
+@import '~antd/es/style/themes/default.less';
+
+.popover {
+  position: relative;
+  width: 336px;
+}
+
+.noticeButton {
+  display: inline-block;
+  cursor: pointer;
+  transition: all 0.3s;
+}
+.icon {
+  padding: 4px;
+  vertical-align: middle;
+}
+
+.badge {
+  font-size: 16px;
+}
+
+.tabs {
+  :global {
+    .ant-tabs-nav-scroll {
+      text-align: center;
+    }
+    .ant-tabs-bar {
+      margin-bottom: 0;
+    }
+  }
+}
diff --git a/src/components/NoticeIcon/index.tsx b/src/components/NoticeIcon/index.tsx
new file mode 100644
index 0000000..0df4061
--- /dev/null
+++ b/src/components/NoticeIcon/index.tsx
@@ -0,0 +1,124 @@
+import { BellOutlined } from '@ant-design/icons';
+import { Badge, Spin, Tabs } from 'antd';
+import useMergeValue from 'use-merge-value';
+import React from 'react';
+import classNames from 'classnames';
+import NoticeList, { NoticeIconTabProps } from './NoticeList';
+
+import HeaderDropdown from '../HeaderDropdown';
+import styles from './index.less';
+
+const { TabPane } = Tabs;
+
+export interface NoticeIconProps {
+  count?: number;
+  bell?: React.ReactNode;
+  className?: string;
+  loading?: boolean;
+  onClear?: (tabName: string, tabKey: string) => void;
+  onItemClick?: (item: API.NoticeIconData, tabProps: NoticeIconTabProps) => void;
+  onViewMore?: (tabProps: NoticeIconTabProps, e: MouseEvent) => void;
+  onTabChange?: (tabTile: string) => void;
+  style?: React.CSSProperties;
+  onPopupVisibleChange?: (visible: boolean) => void;
+  popupVisible?: boolean;
+  clearText?: string;
+  viewMoreText?: string;
+  clearClose?: boolean;
+  emptyImage?: string;
+  children: React.ReactElement<NoticeIconTabProps>[];
+}
+
+const NoticeIcon: React.FC<NoticeIconProps> & {
+  Tab: typeof NoticeList;
+} = (props) => {
+  const getNotificationBox = (): React.ReactNode => {
+    const {
+      children,
+      loading,
+      onClear,
+      onTabChange,
+      onItemClick,
+      onViewMore,
+      clearText,
+      viewMoreText,
+    } = props;
+    if (!children) {
+      return null;
+    }
+    const panes: React.ReactNode[] = [];
+    React.Children.forEach(children, (child: React.ReactElement<NoticeIconTabProps>): void => {
+      if (!child) {
+        return;
+      }
+      const { list, title, count, tabKey, showClear, showViewMore } = child.props;
+      const len = list && list.length ? list.length : 0;
+      const msgCount = count || count === 0 ? count : len;
+      const tabTitle: string = msgCount > 0 ? `${title} (${msgCount})` : title;
+      panes.push(
+        <TabPane tab={tabTitle} key={tabKey}>
+          <NoticeList
+            clearText={clearText}
+            viewMoreText={viewMoreText}
+            data={list}
+            onClear={(): void => onClear && onClear(title, tabKey)}
+            onClick={(item): void => onItemClick && onItemClick(item, child.props)}
+            onViewMore={(event): void => onViewMore && onViewMore(child.props, event)}
+            showClear={showClear}
+            showViewMore={showViewMore}
+            title={title}
+            {...child.props}
+          />
+        </TabPane>,
+      );
+    });
+    return (
+      <Spin spinning={loading} delay={300}>
+        <Tabs className={styles.tabs} onChange={onTabChange}>
+          {panes}
+        </Tabs>
+      </Spin>
+    );
+  };
+
+  const { className, count, bell } = props;
+
+  const [visible, setVisible] = useMergeValue<boolean>(false, {
+    value: props.popupVisible,
+    onChange: props.onPopupVisibleChange,
+  });
+  const noticeButtonClass = classNames(className, styles.noticeButton);
+  const notificationBox = getNotificationBox();
+  const NoticeBellIcon = bell || <BellOutlined className={styles.icon} />;
+  const trigger = (
+    <span className={classNames(noticeButtonClass, { opened: visible })}>
+      <Badge count={count} style={{ boxShadow: 'none' }} className={styles.badge}>
+        {NoticeBellIcon}
+      </Badge>
+    </span>
+  );
+  if (!notificationBox) {
+    return trigger;
+  }
+
+  return (
+    <HeaderDropdown
+      placement="bottomRight"
+      overlay={notificationBox}
+      overlayClassName={styles.popover}
+      trigger={['click']}
+      visible={visible}
+      onVisibleChange={setVisible}
+    >
+      {trigger}
+    </HeaderDropdown>
+  );
+};
+
+NoticeIcon.defaultProps = {
+  emptyImage: 'https://gw.alipayobjects.com/zos/rmsportal/wAhyIChODzsoKIOBHcBk.svg',
+};
+
+NoticeIcon.Tab = NoticeList;
+
+export default NoticeIcon;
diff --git a/src/components/PageLoading/index.tsx b/src/components/PageLoading/index.tsx
new file mode 100644
index 0000000..096c58f
--- /dev/null
+++ b/src/components/PageLoading/index.tsx
@@ -0,0 +1,5 @@
+import { PageLoading } from '@ant-design/pro-layout';
+
+// loading components from code split
+// https://umijs.org/plugin/umi-plugin-react.html#dynamicimport
+export default PageLoading;
diff --git a/src/components/Pagination/index.vue b/src/components/Pagination/index.vue
deleted file mode 100644
index eb31aa1..0000000
--- a/src/components/Pagination/index.vue
+++ /dev/null
@@ -1,104 +0,0 @@
-<!--
-#
-# MIT License
-#
-# Copyright (c) 2018 Chong Guo
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in all
-# copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-# SOFTWARE.
-#
--->
-
-<template>
-  <div
-    :class="{'hidden': hidden}"
-    class="pagination-container"
-  >
-    <el-pagination
-      :background="background"
-      :current-page.sync="currentPage"
-      :page-size.sync="pageSize"
-      :layout="layout"
-      :page-sizes="pageSizes"
-      :total="total"
-      v-bind="$attrs"
-      @size-change="handleSizeChange"
-      @current-change="handleCurrentChange"
-    />
-  </div>
-</template>
-
-<script lang="ts">
-import { Component, Prop, Vue } from 'vue-property-decorator'
-import { scrollTo } from '@/utils/scroll-to'
-
-@Component({
-  name: 'Pagination'
-})
-export default class extends Vue {
-  @Prop({ required: true }) private total!: number
-  @Prop({ default: 1 }) private page!: number
-  @Prop({ default: 20 }) private limit!: number
-  @Prop({ default: () => [10, 20, 30, 50] }) private pageSizes!: number[]
-  @Prop({ default: 'total, sizes, prev, pager, next, jumper' }) private layout!: string
-  @Prop({ default: true }) private background!: boolean
-  @Prop({ default: true }) private autoScroll!: boolean
-  @Prop({ default: false }) private hidden!: boolean
-
-  get currentPage() {
-    return this.page
-  }
-
-  set currentPage(value) {
-    this.$emit('update:page', value)
-  }
-
-  get pageSize() {
-    return this.limit
-  }
-
-  set pageSize(value) {
-    this.$emit('update:limit', value)
-  }
-
-  handleSizeChange(value: number) {
-    this.$emit('pagination', { page: this.currentPage, limit: value })
-    if (this.autoScroll) {
-      scrollTo(0, 800)
-    }
-  }
-
-  handleCurrentChange(value: number) {
-    this.$emit('pagination', { page: value, limit: this.pageSize })
-    if (this.autoScroll) {
-      scrollTo(0, 800)
-    }
-  }
-}
-</script>
-
-<style lang="scss" scoped>
-.pagination-container {
-  background: #fff;
-  padding: 32px 16px;
-}
-
-.pagination-container.hidden {
-  display: none;
-}
-</style>
diff --git a/src/components/PluginDialog/index.vue b/src/components/PluginDialog/index.vue
deleted file mode 100644
index d31d4b9..0000000
--- a/src/components/PluginDialog/index.vue
+++ /dev/null
@@ -1,523 +0,0 @@
-<!--
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements.  See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License.  You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
--->
-
-<template>
-  <div class="plugin-dialog">
-    <el-dialog
-      :title="'Plugin ' + name + ' Edit'"
-      :visible.sync="showDialog"
-    >
-      <el-form
-        v-if="schema.oneOf"
-        ref="form"
-        :model="data"
-        :rules="rules"
-        :show-message="false"
-        class="oneof-plugin-wrapper"
-      >
-        <el-form-item
-          label="Option"
-          :rules="{
-            required: true, trigger: 'blur'
-          }"
-        >
-          <el-radio-group
-            v-model="data['radioKey']"
-            @change="handleOneOfChange"
-          >
-            <el-radio
-              v-for="(value, key) in schema.properties"
-              :key="key"
-              :label="key"
-            >
-              {{ key }}
-            </el-radio>
-          </el-radio-group>
-        </el-form-item>
-
-        <el-form-item
-          v-for="(value, index) in data.values"
-          :key="index"
-          :label="'Value' + (index + 1)"
-          :rules="{
-            required: true, trigger: 'blur'
-          }"
-        >
-          <el-input v-model="data['values'][index]" />
-          <el-button
-            v-if="data.values.length !== 1"
-            class="remove-value-btn"
-            type="danger"
-            @click.prevent="removeOneOfPropValue(index)"
-          >
-            Remove
-          </el-button>
-        </el-form-item>
-
-        <el-form-item>
-          <el-button
-            :disabled="oneOfPropHasEmptyValue"
-            @click="addValueInput"
-          >
-            {{ $t('button.addValue') }}
-          </el-button>
-        </el-form-item>
-      </el-form>
-
-      <el-form
-        v-if="!schema.oneOf"
-        ref="form"
-        :model="data"
-        :rules="rules"
-        :show-message="false"
-      >
-        <el-form-item
-          v-for="(index, key) in schema.properties"
-          :key="key"
-          :label="key"
-          label-width="160px"
-          :prop="key"
-        >
-          <!-- 分情况讨论 -->
-          <!-- number property -->
-          <el-input-number
-            v-if="schema.properties[key].type === 'integer' || schema.properties[key].type === 'number'"
-            v-model="data[key]"
-            :min="schema.properties[key].minimum || -99999999999"
-            :max="schema.properties[key].maximum || 99999999999"
-            label="描述文字"
-            @change="onPropertyChange(key, $event)"
-          />
-
-          <!-- enum property -->
-          <el-select
-            v-if="schema.properties[key].hasOwnProperty('enum')"
-            v-model="data[key]"
-            :clearable="true"
-            :placeholder="`Select a ${key}`"
-            @change="onPropertyChange(key, $event)"
-          >
-            <el-option
-              v-for="value in schema.properties[key]['enum']"
-              :key="value"
-              :label="value"
-              :value="value"
-            />
-          </el-select>
-
-          <!-- string property -->
-          <el-input
-            v-if="schema.properties[key].type === 'string' && !schema.properties[key].hasOwnProperty('enum')"
-            v-model="data[key]"
-            :placeholder="key"
-            @input="onPropertyChange(key, $event)"
-          />
-
-          <!-- boolean property -->
-          <el-switch
-            v-if="schema.properties[key].type === 'boolean' && !schema.properties[key].hasOwnProperty('enum')"
-            v-model="data[key]"
-            active-color="#13ce66"
-            inactive-color="#ff4949"
-          />
-
-          <!-- array property -->
-          <div
-            v-if="schema.properties[key].type === 'array'"
-            class="array-input-container"
-          >
-            <el-input
-              v-for="(arrayIndex) in arrayPropertiesLength[key]"
-              :key="arrayIndex"
-              v-model="data[key][arrayIndex]"
-              :placeholder="`${key} [${arrayIndex}]`"
-              @input="isDataChanged = true"
-            />
-
-            <el-button
-              @click="addArrayItem(key)"
-            >
-              {{ $t('button.addValue') }}
-            </el-button>
-          </div>
-
-          <!-- object property -->
-          <div
-            v-if="schema.properties[key].type === 'object'"
-            class="object-input-container"
-          >
-            <div
-              v-for="(objectItem, objectIndex) in objectPropertiesArray[key]"
-              :key="objectIndex"
-              class="object-input-item"
-            >
-              <el-input
-                v-model="objectPropertiesArray[key][objectIndex].key"
-                :placeholder="`${key} [key ${objectIndex}]`"
-                @input="onObjectPropertyChange(key, $event, true)"
-              />
-              <el-input
-                v-model="objectPropertiesArray[key][objectIndex].value"
-                :placeholder="`${key} [value ${objectIndex}]`"
-                @input="onObjectPropertyChange(key, $event, false)"
-              />
-              <el-button
-                @click="deleteObjectItem(key, objectIndex)"
-              >
-                {{ $t('button.delete') }}
-              </el-button>
-            </div>
-            <el-button
-              @click="addObjectItem(key)"
-            >
-              {{ $t('button.addValue') }}
-            </el-button>
-          </div>
-        </el-form-item>
-      </el-form>
-      <span
-        slot="footer"
-        class="dialog-footer"
-      >
-        <el-button
-          @click="onCancel"
-        >
-          {{ $t('button.cancel') }}
-        </el-button>
-        <el-button
-          type="primary"
-          :disabled="!isDataChanged && oneOfPropHasEmptyValue"
-          @click="onSave"
-        >
-          {{ $t('button.confirm') }}
-        </el-button>
-      </span>
-    </el-dialog>
-  </div>
-</template>
-
-<script lang="ts">
-import { Component, Prop, Vue, Watch } from 'vue-property-decorator'
-
-import { getPluginSchema } from '../../api/schema/schema'
-
-const uuidv1 = require('uuid/v1')
-
-@Component({
-  name: 'PluginDialog'
-})
-export default class extends Vue {
-  @Prop({ default: false }) private show!: boolean
-  @Prop({ default: '' }) private name!: string
-  @Prop({ default: null }) private pluginData!: any
-
-  private schema: any = {
-    properties: {}
-  }
-  private rules: any = {}
-  private data: any = {}
-  private isDataChanged: boolean = false
-  private showDialog: boolean = false
-  private arrayPropertiesLength = {}
-  private objectPropertiesArray = {}
-
-  @Watch('show')
-  private onShowChange(value: boolean) {
-    this.resetPlugin()
-    if (value) {
-      this.getschema(this.name)
-    }
-    this.showDialog = value
-  }
-
-  private resetPlugin() {
-    this.schema = {
-      properties: {}
-    }
-    this.rules = {}
-    this.data = {}
-    this.isDataChanged = false
-  }
-
-  private async getschema(name: string) {
-    const schema = await getPluginSchema(name) as any
-
-    if (!schema.properties) {
-      this.isDataChanged = true
-      return
-    }
-
-    this.schema = Object.assign({}, {
-      ...schema,
-      name: this.name
-    })
-
-    const rules = Object.assign({}, schema.properties)
-
-    for (let pluginName in rules) {
-      const plugin = Object.assign({}, rules[pluginName])
-
-      rules[pluginName] = {
-        trigger: 'blur'
-      }
-
-      if (schema.required) {
-        rules[pluginName].required = schema.required.includes(pluginName)
-      }
-
-      if (plugin.hasOwnProperty('type')) {
-        rules[pluginName]['type'] = plugin['type']
-      }
-
-      if (plugin.hasOwnProperty('minimum')) {
-        rules[pluginName]['min'] = plugin['minimum']
-      }
-
-      if (plugin.hasOwnProperty('maximum')) {
-        rules[pluginName]['max'] = plugin['maximum']
-      }
-
-      if (plugin.hasOwnProperty('enum')) {
-        rules[pluginName]['type'] = 'enum'
-        rules[pluginName]['enum'] = plugin['enum']
-      }
-    }
-
-    this.rules = rules
-
-    // Generate initial data and merge current data
-    let schemaKeys = {}
-    for (let key in schema.properties) {
-      if (schema.properties[key].default) {
-        schemaKeys[key] = schema.properties[key].default
-        continue
-      }
-
-      switch (schema.properties[key].type) {
-        case 'array':
-          schemaKeys[key] = []
-          this.arrayPropertiesLength[key] = [...new Array(this.pluginData[key] ? this.pluginData[key].length : schema.properties[key].minItems).keys()]
-          break
-        case 'object':
-          schemaKeys[key] = {}
-          this.objectPropertiesArray[key] = []
-          if (this.pluginData[key]) {
-            Object.keys(this.pluginData[key]).map(item => {
-              this.objectPropertiesArray[key].push({
-                key: item,
-                value: this.pluginData[key][item]
-              })
-            })
-          }
-          break
-        case 'boolean':
-          schemaKeys[key] = false
-          break
-        default:
-          schemaKeys[key] = ''
-      }
-    }
-
-    if (this.pluginData) {
-      this.data = Object.assign(schemaKeys, this.pluginData)
-    } else {
-      this.data = schemaKeys
-    }
-
-    if (this.name === 'key-auth' && !this.pluginData) {
-      this.data = {
-        key: uuidv1()
-      }
-
-      this.isDataChanged = true
-    }
-
-    // Edit plugin data
-    if (this.name === 'ip-restriction' && this.pluginData) {
-      const key = Object.keys(this.pluginData)[0]
-      this.$nextTick(() => {
-        this.data = {
-          radioKey: key,
-          values: this.pluginData[key]
-        }
-      })
-    }
-
-    // Create new plugin data
-    if (this.schema.oneOf) {
-      this.data = {
-        radioKey: Object.keys(this.schema.properties)[0],
-        values: ['']
-      }
-    }
-  }
-
-  private onCancel() {
-    this.$emit('hidePlugin')
-  }
-
-  private onSave() {
-    (this.$refs.form as any).validate((valid: boolean) => {
-      // 标记该插件数据是否通过校验
-      if (valid) {
-        // Reorganize an array of object properties into objects
-        this.data = Object.assign({}, this.data, this.reorganizeObjectProperty())
-        this.data = this.processOneOfProp(this.data)
-        this.$emit('save', this.name, this.data)
-        this.$message.warning(`${this.$t('message.clickSaveButton')}`)
-      } else {
-        return false
-      }
-    })
-  }
-
-  /**
-   * Add item to array property
-   * @param key
-   */
-  private addArrayItem(key: any) {
-    if (this.arrayPropertiesLength[key].length < this.schema.properties[key].maxItems) {
-      this.arrayPropertiesLength[key].push(this.arrayPropertiesLength[key].length)
-      this.$forceUpdate()
-    } else {
-      this.$message.warning(`${this.$t('message.cannotAddMoreItems')}`)
-    }
-  }
-
-  /**
-   * Add item to object property
-   * @param key
-   */
-  private addObjectItem(key: any) {
-    this.objectPropertiesArray[key].push({
-      key: '',
-      value: ''
-    })
-    this.isDataChanged = true
-    this.$forceUpdate()
-  }
-
-  /**
-   * Delete item form object property
-   * @param key
-   * @param index
-   */
-  private deleteObjectItem(key: any, index: number) {
-    this.objectPropertiesArray[key].splice(index, 1)
-    this.isDataChanged = true
-    this.$forceUpdate()
-  }
-
-  /**
-   * Reorganize an array of object properties into objects
-   */
-  private reorganizeObjectProperty() {
-    let data = {}
-    for (let i in this.objectPropertiesArray) {
-      let objectItem = {}
-      this.objectPropertiesArray[i].map((item: any) => {
-        objectItem[item.key] = item.value
-      })
-      data[i] = objectItem
-    }
-    return data
-  }
-
-  /**
-   * Force rerender on object property content changed
-   */
-  private onObjectPropertyChange() {
-    this.isDataChanged = true
-    this.$forceUpdate()
-  }
-
-  private onPropertyChange(key: any, value: any) {
-    this.data[key] = value
-    this.isDataChanged = true
-  }
-
-  private handleOneOfChange(e: any) {
-    this.data.values = ['']
-  }
-
-  get oneOfPropHasEmptyValue() {
-    return this.data.values ? this.data.values.find((value: string) => value === '') : true
-  }
-
-  private addValueInput() {
-    this.data.values = this.data.values.concat([''])
-  }
-
-  private removeOneOfPropValue(index: number) {
-    this.data.values = this.data.values.filter((item: any, _index: number) => index !== _index)
-  }
-
-  private processOneOfProp(data: any) {
-    if (!this.schema.oneOf) {
-      // remove empty field
-      for (let key in data) {
-        if (data[key] === '') {
-          delete data[key]
-        }
-        if (typeof data[key] === 'object' && Object.keys(data[key]).length === 0) {
-          delete data[key]
-        }
-      }
-      return data
-    }
-
-    return {
-      [this.data.radioKey]: this.data.values
-    }
-  }
-}
-</script>
-
-<style lang="scss">
-.plugin-dialog {
-  .el-form {
-    .el-form-item {
-      .el-form-item__content {
-        .el-input {
-          width: 200px !important;
-        }
-      }
-    }
-  }
-
-  .oneof-plugin-wrapper {
-    .remove-value-btn {
-      margin-left: 10px;
-    }
-  }
-
-  .array-input-container > * {
-    display: flex;
-    margin-top: 5px;
-  }
-
-  .object-input-container > * {
-    display: flex;
-    margin-top: 5px;
-    :not(:first-child){
-      margin-left: 5px;
-    }
-  }
-}
-</style>
diff --git a/src/components/PluginForm/PluginForm.tsx b/src/components/PluginForm/PluginForm.tsx
new file mode 100644
index 0000000..5b69bec
--- /dev/null
+++ b/src/components/PluginForm/PluginForm.tsx
@@ -0,0 +1,192 @@
+import React, { useState, useEffect } from 'react';
+import { Form, Input, Switch, Select, InputNumber, Button } from 'antd';
+import { PlusOutlined, MinusCircleOutlined } from '@ant-design/icons';
+import { useIntl } from 'umi';
+
+import { fetchPluginSchema } from './service';
+import { transformPropertyToRules } from './transformer';
+
+const formLayout = {
+  labelCol: { span: 10 },
+  wrapperCol: { span: 14 },
+};
+
+interface RenderComponentProps {
+  placeholder?: string;
+  disabled?: boolean;
+}
+
+const renderComponentByProperty = (
+  propertyValue: PluginForm.PluginProperty,
+  restProps?: RenderComponentProps,
+) => {
+  const { type, minimum, maximum } = propertyValue;
+
+  if (type === 'string') {
+    if (propertyValue.enum) {
+      return (
+        <Select disabled={restProps?.disabled}>
+          {propertyValue.enum.map((enumValue) => (
+            <Select.Option value={enumValue} key={enumValue}>
+              {enumValue}
+            </Select.Option>
+          ))}
+        </Select>
+      );
+    }
+    return <Input {...restProps} />;
+  }
+
+  if (type === 'boolean') {
+    return <Switch {...restProps} />;
+  }
+
+  if (type === 'number' || type === 'integer') {
+    return (
+      <InputNumber
+        min={minimum ?? Number.MIN_SAFE_INTEGER}
+        max={maximum ?? Number.MAX_SAFE_INTEGER}
+        {...restProps}
+      />
+    );
+  }
+
+  return <Input {...restProps} />;
+};
+
+interface ArrayComponentProps {
+  disabled?: boolean;
+  schema: PluginForm.PluginSchema;
+  propertyName: string;
+  propertyValue: PluginForm.PluginProperty;
+}
+
+const ArrayComponent: React.FC<ArrayComponentProps> = ({
+  propertyName,
+  propertyValue,
+  schema,
+  disabled,
+  children,
+}) => {
+  const { formatMessage } = useIntl();
+  return (
+    <Form.List key={propertyName} name={propertyName}>
+      {(fields, { add, remove }) => (
+        <>
+          {fields.map((field, index) => (
+            <Form.Item
+              key={field.key}
+              rules={transformPropertyToRules(schema!, propertyName, propertyValue)}
+              label={`${propertyName}-${index + 1}`}
+            >
+              {children}
+              {fields.length > 1 ? (
+                <MinusCircleOutlined onClick={() => remove(field.name)} />
+              ) : (
+                <React.Fragment />
+              )}
+            </Form.Item>
+          ))}
+          {/* BUG: There should also care about minItems */}
+          {fields.length < (propertyValue.maxItems ?? Number.MAX_SAFE_INTEGER) ? (
+            <Form.Item label={propertyName}>
+              <Button type="dashed" onClick={add} disabled={disabled}>
+                <PlusOutlined /> {formatMessage({ id: 'component.global.add' })}
+              </Button>
+            </Form.Item>
+          ) : null}
+        </>
+      )}
+    </Form.List>
+  );
+};
+
+const PluginForm: React.FC<PluginForm.Props> = ({
+  name,
+  form,
+  disabled,
+  initialData = {},
+  onFinish,
+}) => {
+  const { formatMessage } = useIntl();
+  const [schema, setSchema] = useState<PluginForm.PluginSchema>();
+
+  useEffect(() => {
+    if (name) {
+      setSchema(undefined);
+      fetchPluginSchema(name).then((data) => {
+        setSchema(data);
+
+        const propertyDefaultData = {};
+        Object.entries(data.properties || {}).forEach(([propertyName, propertyValue]) => {
+          if (propertyValue.hasOwnProperty('default')) {
+            propertyDefaultData[propertyName] = propertyValue.default;
+          }
+        });
+        form.setFieldsValue(propertyDefaultData);
+
+        requestAnimationFrame(() => {
+          form.setFieldsValue(initialData);
+        });
+      });
+    }
+  }, [name]);
+
+  return (
+    <Form {...formLayout} form={form} onFinish={onFinish} labelAlign="left">
+      {Object.entries(schema?.properties || {}).map(([propertyName, propertyValue]) => {
+        // eslint-disable-next-line arrow-body-style
+        if (propertyValue.type === 'array') {
+          return (
+            <ArrayComponent
+              key={propertyName}
+              disabled={disabled}
+              schema={schema!}
+              propertyName={propertyName}
+              propertyValue={propertyValue}
+            >
+              {renderComponentByProperty({ type: 'string' })}
+            </ArrayComponent>
+          );
+        }
+
+        if (propertyValue.type === 'object') {
+          return (
+            <ArrayComponent
+              key={propertyName}
+              disabled={disabled}
+              schema={schema!}
+              propertyName={propertyName}
+              propertyValue={propertyValue}
+            >
+              {/* TODO: there should not be fixed value, and it should receive custom key */}
+              {renderComponentByProperty({ type: 'string' }, { placeholder: 'Header' })}
+              {renderComponentByProperty({ type: 'string' }, { placeholder: 'Value' })}
+            </ArrayComponent>
+          );
+        }
+
+        return (
+          <Form.Item
+            label={formatMessage({
+              id: `PluginForm.plugin.${name}.property.${propertyName}`,
+              defaultMessage: propertyName,
+            })}
+            extra={formatMessage({
+              id: `PluginForm.plugin.${name}.property.${propertyName}.extra`,
+              defaultMessage: '',
+            })}
+            name={propertyName}
+            key={propertyName}
+            rules={transformPropertyToRules(schema!, propertyName, propertyValue)}
+            valuePropName={propertyValue.type === 'boolean' ? 'checked' : 'value'}
+          >
+            {renderComponentByProperty(propertyValue, { disabled })}
+          </Form.Item>
+        );
+      })}
+    </Form>
+  );
+};
+
+export default PluginForm;
diff --git a/src/components/PluginForm/README.md b/src/components/PluginForm/README.md
new file mode 100644
index 0000000..68d4686
--- /dev/null
+++ b/src/components/PluginForm/README.md
@@ -0,0 +1,9 @@
+# PluginForm
+
+> The PluginForm component aims to build UI according to the plugin schema.
+
+## Usage
+
+1. Modify `list` variable in `data.ts` file.
+2. Modify filds under `/locales` folder to have a good translation.
+3. Import files under `/locales` folder to `/src/locales` to be localization.
diff --git a/src/components/PluginForm/data.ts b/src/components/PluginForm/data.ts
new file mode 100644
index 0000000..d8dd6e3
--- /dev/null
+++ b/src/components/PluginForm/data.ts
@@ -0,0 +1,100 @@
+export const PLUGIN_MAPPER_SOURCE: { [name: string]: PluginForm.PluginMapperItem } = {
+  'limit-req': {
+    category: 'Limit',
+  },
+  'limit-count': {
+    category: 'Limit',
+  },
+  'limit-conn': {
+    category: 'Limit',
+  },
+  'key-auth': {
+    category: 'Security',
+    hidden: true,
+  },
+  'basic-auth': {
+    category: 'Security',
+    hidden: true,
+  },
+  prometheus: {
+    category: 'Metric',
+  },
+  'node-status': {
+    category: 'Other',
+  },
+  'jwt-auth': {
+    category: 'Security',
+    hidden: true,
+  },
+  zipkin: {
+    category: 'Metric',
+  },
+  'ip-restriction': {
+    category: 'Security',
+  },
+  'grpc-transcode': {
+    category: 'Other',
+    hidden: true,
+  },
+  'serverless-pre-function': {
+    category: 'Other',
+  },
+  'serverless-post-function': {
+    category: 'Other',
+  },
+  'openid-connect': {
+    category: 'Security',
+  },
+  'proxy-rewrite': {
+    category: 'Other',
+    hidden: true,
+  },
+  redirect: {
+    category: 'Other',
+    hidden: true,
+  },
+  'response-rewrite': {
+    category: 'Other',
+  },
+  'fault-injection': {
+    category: 'Security',
+  },
+  'udp-logger': {
+    category: 'Log',
+  },
+  'wolf-rbac': {
+    category: 'Other',
+    hidden: true,
+  },
+  'proxy-cache': {
+    category: 'Other',
+  },
+  'tcp-logger': {
+    category: 'Log',
+  },
+  'proxy-mirror': {
+    category: 'Other',
+  },
+  'kafka-logger': {
+    category: 'Log',
+  },
+  cors: {
+    category: 'Security',
+  },
+  heartbeat: {
+    category: 'Other',
+    hidden: true,
+  },
+  'batch-requests': {
+    category: 'Other',
+  },
+  'http-logger': {
+    category: 'Log',
+  },
+  'mqtt-proxy': {
+    category: 'Other',
+  },
+  oauth: {
+    category: 'Security',
+  },
+};
diff --git a/src/components/PluginForm/index.ts b/src/components/PluginForm/index.ts
new file mode 100644
index 0000000..8770cd6
--- /dev/null
+++ b/src/components/PluginForm/index.ts
@@ -0,0 +1,3 @@
+export { default } from './PluginForm';
+export { default as PluginFormZhCN } from './locales/zh-CN';
+export { default as PluginFormEnUS } from './locales/en-US';
diff --git a/src/components/PluginForm/locales/en-US.ts b/src/components/PluginForm/locales/en-US.ts
new file mode 100644
index 0000000..4f19e6d
--- /dev/null
+++ b/src/components/PluginForm/locales/en-US.ts
@@ -0,0 +1,152 @@
+export default {
+  'PluginForm.plugin.limit-conn.desc': '限制并发连接数',
+  'PluginForm.plugin.limit-conn.property.conn': 'conn',
+  'PluginForm.plugin.limit-conn.property.conn.extra': '最大并发连接数',
+  'PluginForm.plugin.limit-conn.property.burst': 'burst',
+  'PluginForm.plugin.limit-conn.property.burst.extra': '并发连接数超过 conn,但是低于 conn + burst 时,请求将被延迟处理',
+  'PluginForm.plugin.limit-conn.property.default_conn_delay': '延迟时间',
+  'PluginForm.plugin.limit-conn.property.default_conn_delay.extra': '被延迟处理的请求,需要等待多少秒',
+  'PluginForm.plugin.limit-conn.property.key': 'key',
+  'PluginForm.plugin.limit-conn.property.key.extra': '用来做限制的依据',
+  'PluginForm.plugin.limit-conn.property.rejected_code': '拒绝状态码',
+  'PluginForm.plugin.limit-conn.property.rejected_code.extra': '当并发连接数超过 conn + burst 的限制时,返回给终端的 HTTP 状态码',
+
+  'PluginForm.plugin.limit-count.desc': '在指定的时间范围内,限制总的请求次数',
+  'PluginForm.plugin.limit-count.property.count': '总请求次数',
+  'PluginForm.plugin.limit-count.property.count.extra': '指定时间窗口内的请求数量阈值',
+  'PluginForm.plugin.limit-count.property.time_window': '时间窗口',
+  'PluginForm.plugin.limit-count.property.time_window.extra':
+    '时间窗口的大小(以秒为单位),超过这个时间,总请求次数就会重置',
+  'PluginForm.plugin.limit-count.property.key': 'key',
+  'PluginForm.plugin.limit-count.property.key.extra': '用来做请求计数的依据',
+  'PluginForm.plugin.limit-count.property.rejected_code': '拒绝状态码',
+  'PluginForm.plugin.limit-count.property.rejected_code.extra':
+    '当请求超过阈值时,返回给终端的 HTTP 状态码',
+  'PluginForm.plugin.limit-count.property.policy': '策略',
+  'PluginForm.plugin.limit-count.property.redis_host': '地址',
+  'PluginForm.plugin.limit-count.property.redis_host.extra': '用于集群限流的 Redis 节点地址',
+  'PluginForm.plugin.limit-count.property.redis_port': '端口',
+  'PluginForm.plugin.limit-count.property.redis_password': '密码',
+  'PluginForm.plugin.limit-count.property.redis_timeout': '超时时间(毫秒)',
+
+  'PluginForm.plugin.limit-req.desc': '限制请求速度的插件,基于漏桶算法',
+  'PluginForm.plugin.limit-req.property.rate': 'rate',
+  'PluginForm.plugin.limit-req.property.rate.extra': '每秒请求速率',
+  'PluginForm.plugin.limit-req.property.burst': 'burst',
+  'PluginForm.plugin.limit-req.property.burst.extra':
+    '每秒请求速率超过 rate,但是低于 rate + burst 时,请求将被延迟处理',
+  'PluginForm.plugin.limit-req.property.key': 'key',
+  'PluginForm.plugin.limit-req.property.key.extra': '用来做请求计数的依据',
+  'PluginForm.plugin.limit-req.property.rejected_code': '拒绝状态码',
+  'PluginForm.plugin.limit-req.property.rejected_code.extra':
+    '速率超过 rate + burst 的限制时,返回给终端的 HTTP 状态码',
+
+  'PluginForm.plugin.cors.desc': 'CORS 插件可以为服务端启用 CORS 的返回头',
+  'PluginForm.plugin.cors.property.allow_origins': '允许跨域访问的 Origin',
+  'PluginForm.plugin.cors.property.allow_origins.extra':
+    '比如 https://somehost.com:8081',
+  'PluginForm.plugin.cors.property.allow_methods': '允许跨域访问的 Method',
+
+  'PluginForm.plugin.fault-injection.desc': '故障注入插件,用来模拟各种后端故障和高延迟',
+  'PluginForm.plugin.fault-injection.property.http_status': 'HTTP 状态码',
+  'PluginForm.plugin.fault-injection.property.body': '响应体',
+  'PluginForm.plugin.fault-injection.property.duration': '延迟时间(秒)',
+
+  'PluginForm.plugin.http-logger.desc': 'http-logger 可以将日志数据请求推送到 HTTP/HTTPS 服务器',
+  'PluginForm.plugin.http-logger.property.uri': '日志服务器地址',
+  'PluginForm.plugin.http-logger.property.uri.extra': '比如 127.0.0.1:80/postendpoint?param=1',
+
+  'PluginForm.plugin.ip-restriction.desc':
+    'ip-restriction 可以把一批 IP 地址列入白名单或黑名单(二选一),时间复杂度是O(1),并支持用 CIDR 来表示 IP 范围',
+  'PluginForm.plugin.ip-restriction.property.whitelist': '白名单',
+  'PluginForm.plugin.ip-restriction.property.blacklist': '黑名单',
+
+  'PluginForm.plugin.kafka-logger.desc': '把接口请求日志以 JSON 的形式推送给外部 Kafka 集群',
+  'PluginForm.plugin.kafka-logger.property.broker_list': 'broker',
+  'PluginForm.plugin.kafka-logger.property.kafka_topic': 'topic',
+
+  'PluginForm.plugin.prometheus.desc': '提供符合 prometheus 数据格式的 metrics 数据',
+
+  'PluginForm.plugin.proxy-cache.desc': '代理缓存插件,缓存后端服务的响应数据',
+  'PluginForm.plugin.proxy-cache.property.cache_zone': '缓存区域名',
+  'PluginForm.plugin.proxy-cache.property.cache_zone.extra': ' 本地目录为 /tmp/${区域名},修改默认区域名必须同时修改 config.yaml',
+  'PluginForm.plugin.proxy-cache.property.cache_key': '缓存 key',
+  'PluginForm.plugin.proxy-cache.property.cache_key.extra': '可以使用 Nginx 变量,例如:$host, $uri',
+  'PluginForm.plugin.proxy-cache.property.cache_bypass': '跳过缓存检索',
+  'PluginForm.plugin.proxy-cache.property.cache_bypass.extra': '这里可以使用 Nginx 变量,当此参数的值不为空或非0时将会跳过缓存的检索',
+  'PluginForm.plugin.proxy-cache.property.cache_method': '缓存 Method',
+  'PluginForm.plugin.proxy-cache.property.cache_http_status': '缓存响应状态码',
+  'PluginForm.plugin.proxy-cache.property.hide_cache_headers': '隐藏缓存头',
+  'PluginForm.plugin.proxy-cache.property.hide_cache_headers.extra': '是否将 Expires 和 Cache-Control 响应头返回给客户端',
+  'PluginForm.plugin.proxy-cache.property.no_cache': '不缓存的数据',
+  'PluginForm.plugin.proxy-cache.property.no_cache.extra': '这里可以使用 Nginx 变量, 当此参数的值不为空或非0时将不会缓存数据',
+
+  'PluginForm.plugin.proxy-mirror.desc': 'proxy mirror 代理镜像插件,提供了镜像客户端请求的能力',
+  'PluginForm.plugin.proxy-mirror.property.host': '镜像服务地址',
+  'PluginForm.plugin.proxy-mirror.property.host.extra': '例如:http://127.0.0.1:9797。地址中需要包含 http 或 https,不能包含 URI 部分',
+
+  'PluginForm.plugin.response-rewrite.desc': '该插件支持修改上游服务返回的 body 和 header 信息',
+  'PluginForm.plugin.response-rewrite.property.status_code': '状态码',
+  'PluginForm.plugin.response-rewrite.property.body': '响应体',
+  'PluginForm.plugin.response-rewrite.property.body_base64': '响应体是否需要 base64 解码',
+  'PluginForm.plugin.response-rewrite.property.headers': 'HTTP 头',
+
+  'PluginForm.plugin.syslog.desc': '对接 syslog 日志服务器',
+  'PluginForm.plugin.syslog.property.host': '日志服务器地址',
+  'PluginForm.plugin.syslog.property.port': '日志服务器端口',
+  'PluginForm.plugin.syslog.property.timeout': '超时时间',
+  'PluginForm.plugin.syslog.property.tls': '开启 SSL',
+  'PluginForm.plugin.syslog.property.flush_limit': '缓存区大小',
+  'PluginForm.plugin.syslog.property.sock_type': '协议类型',
+  'PluginForm.plugin.syslog.property.max_retry_times': '重试次数',
+  'PluginForm.plugin.syslog.property.retry_interval': '重试间隔时间(毫秒)',
+  'PluginForm.plugin.syslog.property.pool_size': '连接池大小',
+
+  'PluginForm.plugin.tcp-logger.desc': '对接 TCP 日志服务器',
+  'PluginForm.plugin.tcp-logger.property.host': '日志服务器地址',
+  'PluginForm.plugin.tcp-logger.property.port': '日志服务器地址',
+  'PluginForm.plugin.tcp-logger.property.timeout': '超时时间',
+  'PluginForm.plugin.tcp-logger.property.tls': '开启 SSL',
+  'PluginForm.plugin.tcp-logger.property.tls_options': 'TLS 选型',
+
+  'PluginForm.plugin.udp-logger.desc': '对接 UDP 日志服务器',
+  'PluginForm.plugin.udp-logger.property.host': '日志服务器地址',
+  'PluginForm.plugin.udp-logger.property.port': '日志服务器地址',
+  'PluginForm.plugin.udp-logger.property.timeout': '超时时间',
+
+  'PluginForm.plugin.zipkin.desc': '对接 zipkin',
+  'PluginForm.plugin.zipkin.property.endpoint': 'endpoint',
+  'PluginForm.plugin.zipkin.property.endpoint.extra': '例如 http://127.0.0.1:9411/api/v2/spans',
+  'PluginForm.plugin.zipkin.property.sample_ratio': '采样率',
+  'PluginForm.plugin.zipkin.property.service_name': '服务名',
+  'PluginForm.plugin.zipkin.property.server_addr': '网关实例 IP',
+  'PluginForm.plugin.zipkin.property.server_addr.extra': '默认值是 Nginx 内置变量 server_addr',
+
+  'PluginForm.plugin.skywalking.desc': '对接 Apache Skywalking',
+  'PluginForm.plugin.skywalking.property.endpoint': 'endpoint',
+  'PluginForm.plugin.skywalking.property.endpoint.extra': '例如 http://127.0.0.1:12800',
+  'PluginForm.plugin.skywalking.property.sample_ratio': '采样率',
+  'PluginForm.plugin.skywalking.property.service_name': '服务名',
+
+  'PluginForm.plugin.serverless-pre-function.desc': '在指定阶段最开始的位置,运行指定的 Lua 函数',
+  'PluginForm.plugin.serverless-pre-function.property.phase': '运行阶段',
+  'PluginForm.plugin.serverless-pre-function.property.functions': '运行的函数集',
+
+  'PluginForm.plugin.serverless-post-function.desc': '在指定阶段最后的位置,运行指定的 Lua 函数',
+  'PluginForm.plugin.serverless-post-function.property.phase': '运行阶段',
+  'PluginForm.plugin.serverless-post-function.property.functions': '运行的函数集',
+
+
+  'PluginForm.plugin.basic-auth.desc': 'basic auth 插件',
+  'PluginForm.plugin.jwt-auth.desc': 'JWT 认证插件',
+  'PluginForm.plugin.key-auth.desc': 'key auth 插件',
+  'PluginForm.plugin.wolf-rbac.desc': '对接 wolf RBAC 服务',
+  'PluginForm.plugin.openid-connect.desc': 'Open ID Connect(OIDC) 插件提供对接外部认证服务的能力',
+
+  'PluginForm.plugin.redirect.desc': '重定向插件',
+  'PluginForm.plugin.proxy-rewrite.desc': 'proxy rewrite 代理改写插件,可以改写客户端请求',
+  'PluginForm.plugin.mqtt-proxy.desc': 'mqtt-proxy 插件可以帮助你根据 MQTT 的 client_id 实现动态负载均衡',
+  'PluginForm.plugin.grpc-transcoding.desc': 'gRPC 转换插件,实现 HTTP(s) -> APISIX -> gRPC server 的转换',
+  'PluginForm.plugin.batch-requests.desc':
+    'batch-requests 插件可以一次接受多个请求并以 http pipeline 的方式在网关发起多个 http 请求,合并结果后再返回客户端,这在客户端需要访问多个接口时可以显著地提升请求性能',
+};
diff --git a/src/components/PluginForm/locales/zh-CN.ts b/src/components/PluginForm/locales/zh-CN.ts
new file mode 100644
index 0000000..4f19e6d
--- /dev/null
+++ b/src/components/PluginForm/locales/zh-CN.ts
@@ -0,0 +1,152 @@
+export default {
+  'PluginForm.plugin.limit-conn.desc': '限制并发连接数',
+  'PluginForm.plugin.limit-conn.property.conn': 'conn',
+  'PluginForm.plugin.limit-conn.property.conn.extra': '最大并发连接数',
+  'PluginForm.plugin.limit-conn.property.burst': 'burst',
+  'PluginForm.plugin.limit-conn.property.burst.extra': '并发连接数超过 conn,但是低于 conn + burst 时,请求将被延迟处理',
+  'PluginForm.plugin.limit-conn.property.default_conn_delay': '延迟时间',
+  'PluginForm.plugin.limit-conn.property.default_conn_delay.extra': '被延迟处理的请求,需要等待多少秒',
+  'PluginForm.plugin.limit-conn.property.key': 'key',
+  'PluginForm.plugin.limit-conn.property.key.extra': '用来做限制的依据',
+  'PluginForm.plugin.limit-conn.property.rejected_code': '拒绝状态码',
+  'PluginForm.plugin.limit-conn.property.rejected_code.extra': '当并发连接数超过 conn + burst 的限制时,返回给终端的 HTTP 状态码',
+
+  'PluginForm.plugin.limit-count.desc': '在指定的时间范围内,限制总的请求次数',
+  'PluginForm.plugin.limit-count.property.count': '总请求次数',
+  'PluginForm.plugin.limit-count.property.count.extra': '指定时间窗口内的请求数量阈值',
+  'PluginForm.plugin.limit-count.property.time_window': '时间窗口',
+  'PluginForm.plugin.limit-count.property.time_window.extra':
+    '时间窗口的大小(以秒为单位),超过这个时间,总请求次数就会重置',
+  'PluginForm.plugin.limit-count.property.key': 'key',
+  'PluginForm.plugin.limit-count.property.key.extra': '用来做请求计数的依据',
+  'PluginForm.plugin.limit-count.property.rejected_code': '拒绝状态码',
+  'PluginForm.plugin.limit-count.property.rejected_code.extra':
+    '当请求超过阈值时,返回给终端的 HTTP 状态码',
+  'PluginForm.plugin.limit-count.property.policy': '策略',
+  'PluginForm.plugin.limit-count.property.redis_host': '地址',
+  'PluginForm.plugin.limit-count.property.redis_host.extra': '用于集群限流的 Redis 节点地址',
+  'PluginForm.plugin.limit-count.property.redis_port': '端口',
+  'PluginForm.plugin.limit-count.property.redis_password': '密码',
+  'PluginForm.plugin.limit-count.property.redis_timeout': '超时时间(毫秒)',
+
+  'PluginForm.plugin.limit-req.desc': '限制请求速度的插件,基于漏桶算法',
+  'PluginForm.plugin.limit-req.property.rate': 'rate',
+  'PluginForm.plugin.limit-req.property.rate.extra': '每秒请求速率',
+  'PluginForm.plugin.limit-req.property.burst': 'burst',
+  'PluginForm.plugin.limit-req.property.burst.extra':
+    '每秒请求速率超过 rate,但是低于 rate + burst 时,请求将被延迟处理',
+  'PluginForm.plugin.limit-req.property.key': 'key',
+  'PluginForm.plugin.limit-req.property.key.extra': '用来做请求计数的依据',
+  'PluginForm.plugin.limit-req.property.rejected_code': '拒绝状态码',
+  'PluginForm.plugin.limit-req.property.rejected_code.extra':
+    '速率超过 rate + burst 的限制时,返回给终端的 HTTP 状态码',
+
+  'PluginForm.plugin.cors.desc': 'CORS 插件可以为服务端启用 CORS 的返回头',
+  'PluginForm.plugin.cors.property.allow_origins': '允许跨域访问的 Origin',
+  'PluginForm.plugin.cors.property.allow_origins.extra':
+    '比如 https://somehost.com:8081',
+  'PluginForm.plugin.cors.property.allow_methods': '允许跨域访问的 Method',
+
+  'PluginForm.plugin.fault-injection.desc': '故障注入插件,用来模拟各种后端故障和高延迟',
+  'PluginForm.plugin.fault-injection.property.http_status': 'HTTP 状态码',
+  'PluginForm.plugin.fault-injection.property.body': '响应体',
+  'PluginForm.plugin.fault-injection.property.duration': '延迟时间(秒)',
+
+  'PluginForm.plugin.http-logger.desc': 'http-logger 可以将日志数据请求推送到 HTTP/HTTPS 服务器',
+  'PluginForm.plugin.http-logger.property.uri': '日志服务器地址',
+  'PluginForm.plugin.http-logger.property.uri.extra': '比如 127.0.0.1:80/postendpoint?param=1',
+
+  'PluginForm.plugin.ip-restriction.desc':
+    'ip-restriction 可以把一批 IP 地址列入白名单或黑名单(二选一),时间复杂度是O(1),并支持用 CIDR 来表示 IP 范围',
+  'PluginForm.plugin.ip-restriction.property.whitelist': '白名单',
+  'PluginForm.plugin.ip-restriction.property.blacklist': '黑名单',
+
+  'PluginForm.plugin.kafka-logger.desc': '把接口请求日志以 JSON 的形式推送给外部 Kafka 集群',
+  'PluginForm.plugin.kafka-logger.property.broker_list': 'broker',
+  'PluginForm.plugin.kafka-logger.property.kafka_topic': 'topic',
+
+  'PluginForm.plugin.prometheus.desc': '提供符合 prometheus 数据格式的 metrics 数据',
+
+  'PluginForm.plugin.proxy-cache.desc': '代理缓存插件,缓存后端服务的响应数据',
+  'PluginForm.plugin.proxy-cache.property.cache_zone': '缓存区域名',
+  'PluginForm.plugin.proxy-cache.property.cache_zone.extra': ' 本地目录为 /tmp/${区域名},修改默认区域名必须同时修改 config.yaml',
+  'PluginForm.plugin.proxy-cache.property.cache_key': '缓存 key',
+  'PluginForm.plugin.proxy-cache.property.cache_key.extra': '可以使用 Nginx 变量,例如:$host, $uri',
+  'PluginForm.plugin.proxy-cache.property.cache_bypass': '跳过缓存检索',
+  'PluginForm.plugin.proxy-cache.property.cache_bypass.extra': '这里可以使用 Nginx 变量,当此参数的值不为空或非0时将会跳过缓存的检索',
+  'PluginForm.plugin.proxy-cache.property.cache_method': '缓存 Method',
+  'PluginForm.plugin.proxy-cache.property.cache_http_status': '缓存响应状态码',
+  'PluginForm.plugin.proxy-cache.property.hide_cache_headers': '隐藏缓存头',
+  'PluginForm.plugin.proxy-cache.property.hide_cache_headers.extra': '是否将 Expires 和 Cache-Control 响应头返回给客户端',
+  'PluginForm.plugin.proxy-cache.property.no_cache': '不缓存的数据',
+  'PluginForm.plugin.proxy-cache.property.no_cache.extra': '这里可以使用 Nginx 变量, 当此参数的值不为空或非0时将不会缓存数据',
+
+  'PluginForm.plugin.proxy-mirror.desc': 'proxy mirror 代理镜像插件,提供了镜像客户端请求的能力',
+  'PluginForm.plugin.proxy-mirror.property.host': '镜像服务地址',
+  'PluginForm.plugin.proxy-mirror.property.host.extra': '例如:http://127.0.0.1:9797。地址中需要包含 http 或 https,不能包含 URI 部分',
+
+  'PluginForm.plugin.response-rewrite.desc': '该插件支持修改上游服务返回的 body 和 header 信息',
+  'PluginForm.plugin.response-rewrite.property.status_code': '状态码',
+  'PluginForm.plugin.response-rewrite.property.body': '响应体',
+  'PluginForm.plugin.response-rewrite.property.body_base64': '响应体是否需要 base64 解码',
+  'PluginForm.plugin.response-rewrite.property.headers': 'HTTP 头',
+
+  'PluginForm.plugin.syslog.desc': '对接 syslog 日志服务器',
... 38436 lines suppressed ...