Browse Source

use vue-gettext

0xJacky 3 years ago
parent
commit
35d144c678
61 changed files with 1941 additions and 424 deletions
  1. 16 2
      README-zh_CN.md
  2. 19 4
      README.md
  3. 61 0
      frontend/Makefile
  4. 3 3
      frontend/babel.config.js
  5. 2 0
      frontend/package.json
  6. 4 4
      frontend/src/App.vue
  7. 1 1
      frontend/src/api/config.js
  8. 1 1
      frontend/src/api/domain.js
  9. 1 1
      frontend/src/api/install.js
  10. 0 1
      frontend/src/assets/css/dark.less
  11. 9 8
      frontend/src/components/Chart/LineChart.vue
  12. 1 1
      frontend/src/components/Logo/Logo.vue
  13. 10 8
      frontend/src/components/RichText/CodeBlockComponent.vue
  14. 6 6
      frontend/src/components/RichText/MenuBar.vue
  15. 2 2
      frontend/src/components/RichText/MenuItem.vue
  16. 1 1
      frontend/src/components/RichText/RichText.vue
  17. 1 1
      frontend/src/components/RichText/RichTextEditor.vue
  18. 1 1
      frontend/src/components/StdDataDisplay/StdCurd.vue
  19. 12 20
      frontend/src/components/StdDataDisplay/StdTable.vue
  20. 1 1
      frontend/src/components/StdDataEntry/StdRadioGroup.vue
  21. 3 3
      frontend/src/components/VueItextarea/VueItextarea.vue
  22. 2 0
      frontend/src/layouts/SideBar.vue
  23. 3 3
      frontend/src/lazy.js
  24. 2 0
      frontend/src/lib/http/index.js
  25. 3 2
      frontend/src/lib/store/index.js
  26. 21 0
      frontend/src/lib/store/settings.js
  27. 29 0
      frontend/src/lib/translate/gettext.js
  28. 5 0
      frontend/src/lib/translate/index.js
  29. 4 4
      frontend/src/lib/utils/index.js
  30. 123 0
      frontend/src/locale/en/LC_MESSAGES/app.po
  31. 123 0
      frontend/src/locale/en/LC_MESSAGES/app.po~
  32. BIN
      frontend/src/locale/zh_CN/LC_MESSAGES/app.mo
  33. 125 0
      frontend/src/locale/zh_CN/LC_MESSAGES/app.po
  34. 125 0
      frontend/src/locale/zh_CN/LC_MESSAGES/app.po~
  35. 122 0
      frontend/src/locale/zh_TW/LC_MESSAGES/app.po
  36. 122 0
      frontend/src/locale/zh_TW/LC_MESSAGES/app.po~
  37. 10 0
      frontend/src/main.js
  38. 20 19
      frontend/src/router/index.js
  39. 1 0
      frontend/src/translations.json
  40. 11 11
      frontend/src/views/config/Config.vue
  41. 9 9
      frontend/src/views/config/ConfigEdit.vue
  42. 28 24
      frontend/src/views/dashboard/DashBoard.vue
  43. 6 4
      frontend/src/views/domain/CertInfo.vue
  44. 7 7
      frontend/src/views/domain/DomainAdd.vue
  45. 32 32
      frontend/src/views/domain/DomainEdit.vue
  46. 20 20
      frontend/src/views/domain/DomainList.vue
  47. 32 32
      frontend/src/views/domain/columns.js
  48. 6 6
      frontend/src/views/domain/methods.js
  49. 3 2
      frontend/src/views/other/About.vue
  50. 1 1
      frontend/src/views/other/Install.vue
  51. 3 1
      frontend/src/views/other/Login.vue
  52. 13 15
      frontend/src/views/user/User.vue
  53. 5 1
      frontend/version.json
  54. 615 2
      frontend/yarn.lock
  55. 0 1
      server/api/analytic.go
  56. 1 1
      server/model/config-backup.go
  57. 16 16
      server/model/curd.go
  58. 24 24
      server/test/analytic_test.go
  59. 75 79
      server/test/lego_test.go
  60. 1 1
      server/test/nginx_test.go
  61. 38 38
      server/tool/config_list.go

+ 16 - 2
README-zh_CN.md

@@ -1,4 +1,5 @@
 # Nginx UI
+
 Yet another Nginx Web UI
 
 Version: 1.1.0
@@ -15,46 +16,59 @@ Version: 1.1.0
 ## 项目预览
 
 ### 登录
+
 ![](resources/screenshots/login.png)
 
 ### 仪表盘
+
 ![](resources/screenshots/dashboard.png)
 
 ### 用户列表
+
 ![](resources/screenshots/user-list.png)
 
 ### 域名列表
+
 ![](resources/screenshots/domain-list.png)
 
 ### 域名编辑
+
 ![](resources/screenshots/domain-edit.png)
 
 ### 配置列表
+
 ![](resources/screenshots/config-list.png)
 
 ### 配置编辑
+
 ![](resources/screenshots/config-edit.png)
 
 ## 使用前注意
 
-Nginx UI 遵循 Nginx 的标准,创建的网站配置文件位于 Nginx 配置目录(自动检测)下的 `sites-available` 目录,
-启用后的网站的配置文件将会创建一份软连接到 `sites-enabled` 目录中。因此,您可能需要调整配置文件的组织方式。
+Nginx UI 遵循 Nginx 的标准,创建的网站配置文件位于 Nginx 配置目录(自动检测)下的 `sites-available` 目录, 启用后的网站的配置文件将会创建一份软连接到 `sites-enabled`
+目录中。因此,您可能需要调整配置文件的组织方式。
 
 ## 安装
+
 1. 克隆项目
+
 ```
 git clone https://github.com/0xJacky/nginx-ui
 ```
+
 2. 编译后端
+
 ```
 cd server
 go build -o nginx-ui-server main.go
 ```
+
 3. 启动后端
     1. 前台启动 `./nginx-ui-server`
     2. 后台启动 `nohup ./nginx-ui-server &`
 
 4. 添加配置文件到 nginx
+
 ```
 server {
     listen	80;

+ 19 - 4
README.md

@@ -1,4 +1,5 @@
 # Nginx UI
+
 Yet another Nginx Web UI
 
 Version: 1.1.0
@@ -17,46 +18,60 @@ Version: 1.1.0
 ## Screenshots
 
 ### Login
+
 ![](resources/screenshots/login.png)
 
 ### Dashboard
+
 ![](resources/screenshots/dashboard.png)
 
 ### Users Management
+
 ![](resources/screenshots/user-list.png)
 
 ### Domains Management
+
 ![](resources/screenshots/domain-list.png)
 
 ### Domain Editor
+
 ![](resources/screenshots/domain-edit.png)
 
 ### Configurations Management
+
 ![](resources/screenshots/config-list.png)
 
 ### Configuration Editor
+
 ![](resources/screenshots/config-edit.png)
 
 ## Note Before Use
 
-The Nginx UI follows the Nginx standard of creating site configuration files in the `sites-available` directory under the Nginx configuration directory (auto-detected).
-The configuration files for an enabled site will create a soft link to the `sites-enabled` directory. Therefore, you may need to adjust the way the configuration files are organised.
+The Nginx UI follows the Nginx standard of creating site configuration files in the `sites-available` directory under
+the Nginx configuration directory (auto-detected). The configuration files for an enabled site will create a soft link
+to the `sites-enabled` directory. Therefore, you may need to adjust the way the configuration files are organised.
 
 ## Install
+
 1. Clone
+
 ```
 git clone https://github.com/0xJacky/nginx-ui
 ```
+
 2. Compiling the backend
+
 ```
 cd server
 go build -o nginx-ui-server main.go
 ```
+
 3. Start up the backend
-    1.  `./nginx-ui-server` for direct run.
-    2.  `nohup ./nginx-ui-server &` for run as service.
+    1. `./nginx-ui-server` for direct run.
+    2. `nohup ./nginx-ui-server &` for run as service.
 
 4. Adding a configuration file to nginx
+
 ```
 server {
     listen	80;

+ 61 - 0
frontend/Makefile

@@ -0,0 +1,61 @@
+# On OSX the PATH variable isn't exported unless "SHELL" is also set, see: http://stackoverflow.com/a/25506676
+SHELL = /bin/bash
+NODE_BINDIR = ./node_modules/.bin
+export PATH := $(NODE_BINDIR):$(PATH)
+LOGNAME ?= $(shell logname)
+
+# adding the name of the user's login name to the template file, so that
+# on a multi-user system several users can run this without interference
+TEMPLATE_POT ?= /tmp/template-$(LOGNAME).pot
+
+# Where to find input files (it can be multiple paths).
+INPUT_FILES = ./src
+
+# Where to write the files generated by this makefile.
+OUTPUT_DIR = ./src
+
+# Available locales for the app.
+LOCALES = zh_CN zh_TW en
+
+# Name of the generated .po files for each available locale.
+LOCALE_FILES ?= $(patsubst %,$(OUTPUT_DIR)/locale/%/LC_MESSAGES/app.po,$(LOCALES))
+
+GETTEXT_SOURCES ?= $(shell find $(INPUT_FILES) -name '*.jade' -o -name '*.html' -o -name '*.js' -o -name '*.vue' 2> /dev/null)
+
+# Makefile Targets
+.PHONY: clean makemessages translations all
+
+all:
+	@echo choose a target from: clean makemessages translations
+
+clean:
+	rm -f $(TEMPLATE_POT) $(OUTPUT_DIR)/translations.json
+
+makemessages: $(TEMPLATE_POT)
+
+translations: ./$(OUTPUT_DIR)/translations.json
+# Create a main .pot template, then generate .po files for each available language.
+# Thanx to Systematic: https://github.com/Polyconseil/systematic/blob/866d5a/mk/main.mk#L167-L183
+$(TEMPLATE_POT): $(GETTEXT_SOURCES)
+# `dir` is a Makefile built-in expansion function which extracts the directory-part of `$@`.
+# `$@` is a Makefile automatic variable: the file name of the target of the rule.
+# => `mkdir -p /tmp/`
+	mkdir -p $(dir $@)
+# Extract gettext strings from templates files and create a POT dictionary template.
+	gettext-extract --quiet --attribute v-translate --output $@ $(GETTEXT_SOURCES)
+# Generate .po files for each available language.
+	@for lang in $(LOCALES); do \
+		export PO_FILE=$(OUTPUT_DIR)/locale/$$lang/LC_MESSAGES/app.po; \
+		mkdir -p $$(dirname $$PO_FILE); \
+		if [ -f $$PO_FILE ]; then  \
+			echo "msgmerge --update $$PO_FILE $@"; \
+			msgmerge --lang=$$lang --update $$PO_FILE $@ || break ;\
+		else \
+			msginit --no-translator --locale=$$lang --input=$@ --output-file=$$PO_FILE || break ; \
+			msgattrib --no-wrap --no-obsolete -o $$PO_FILE $$PO_FILE || break; \
+		fi; \
+	done;
+
+$(OUTPUT_DIR)/translations.json: $(LOCALE_FILES)
+	mkdir -p $(OUTPUT_DIR)
+	gettext-compile --output $@ $(LOCALE_FILES)

+ 3 - 3
frontend/babel.config.js

@@ -1,10 +1,10 @@
 module.exports = {
     presets: [
-        "@vue/cli-plugin-babel/preset"
+        '@vue/cli-plugin-babel/preset'
     ],
-    "plugins": [
+    'plugins': [
         '@babel/plugin-proposal-optional-chaining',
         '@babel/plugin-proposal-nullish-coalescing-operator',
-        ["import", {"libraryName": "ant-design-vue", "libraryDirectory": "es", "style": true}, "syntax-dynamic-import"]
+        ['import', {'libraryName': 'ant-design-vue', 'libraryDirectory': 'es', 'style': true}, 'syntax-dynamic-import']
     ],
 }

+ 2 - 0
frontend/package.json

@@ -16,6 +16,7 @@
         "vue": "^2.6.11",
         "vue-chartjs": "^3.5.1",
         "vue-codemirror": "^4.0.6",
+        "vue-gettext": "^2.1.12",
         "vue-itextarea": "^1.0.9",
         "vue-router": "^3.5.1",
         "vuex": "^3.6.2",
@@ -39,6 +40,7 @@
         "@vue/cli-service": "~4.5.0",
         "babel-eslint": "^10.1.0",
         "babel-plugin-import": "^1.13.3",
+        "easygettext": "^2.17.0",
         "eslint": "^6.7.2",
         "eslint-plugin-vue": "^6.2.2",
         "less": "^3.11.1",

+ 4 - 4
frontend/src/App.vue

@@ -1,12 +1,12 @@
 <template>
-  <div id="app">
-    <router-view/>
-  </div>
+    <div id="app">
+        <router-view/>
+    </div>
 </template>
 
 <style lang="less">
 #app {
-  height: 100%;
+    height: 100%;
 }
 
 </style>

+ 1 - 1
frontend/src/api/config.js

@@ -1,4 +1,4 @@
-import http from "@/lib/http"
+import http from '@/lib/http'
 
 const base_url = '/config'
 

+ 1 - 1
frontend/src/api/domain.js

@@ -1,4 +1,4 @@
-import http from "@/lib/http"
+import http from '@/lib/http'
 
 const base_url = '/domain'
 

+ 1 - 1
frontend/src/api/install.js

@@ -1,4 +1,4 @@
-import http from "@/lib/http";
+import http from '@/lib/http'
 
 const install = {
     get_lock() {

+ 0 - 1
frontend/src/assets/css/dark.less

@@ -37,7 +37,6 @@
     @popover-bg: @black_bg;
 
     @background-color-light: fade(@white, 4%); // background of header and selected item
-
     // Descriptions
     // ---
     @descriptions-bg: @background-color-light;

+ 9 - 8
frontend/src/components/Chart/LineChart.vue

@@ -1,24 +1,25 @@
 <script>
-import { Line, mixins } from 'vue-chartjs'
-const { reactiveProp } = mixins
+import {Line, mixins} from 'vue-chartjs'
+
+const {reactiveProp} = mixins
 
 export default {
-    name: "LineChart",
+    name: 'LineChart',
     extends: Line,
     mixins: [reactiveProp],
     props: ['options'],
     data() {
-      return {
-          updating: false
-      }
+        return {
+            updating: false
+        }
     },
-    mounted () {
+    mounted() {
         this.renderChart(this.chartData, this.options)
     },
     watch: {
         chartData: {
             deep: true,
-            handler () {
+            handler() {
                 if (!this.updating && this.$data && this.$data._chart) {
                     // Update the chart
                     this.updating = true

+ 1 - 1
frontend/src/components/Logo/Logo.vue

@@ -1,6 +1,6 @@
 <template>
     <div class="logo">
-        <img :src="logo" alt="logo" />
+        <img :src="logo" alt="logo"/>
         <p class="text">Nginx UI</p>
         <div class="clear"></div>
     </div>

+ 10 - 8
frontend/src/components/RichText/CodeBlockComponent.vue

@@ -11,12 +11,13 @@
                 {{ language }}
             </option>
         </select>
-        <pre><node-view-content as="code" /></pre>
+        <pre><node-view-content as="code"/></pre>
     </node-view-wrapper>
 </template>
 
 <script>
-import { NodeViewWrapper, NodeViewContent, nodeViewProps } from '@tiptap/vue-2'
+import {NodeViewContent, nodeViewProps, NodeViewWrapper} from '@tiptap/vue-2'
+
 export default {
     components: {
         NodeViewWrapper,
@@ -37,7 +38,7 @@ export default {
                 return (this.node.attrs.language ? this.node.attrs.language.split('')[0] : null)
             },
             set(language) {
-                this.updateAttributes({ language })
+                this.updateAttributes({language})
             },
         },
     },
@@ -48,10 +49,11 @@ export default {
 .code-block {
     position: relative;
 
-    select {
-        position: absolute;
-        top: 0.5rem;
-        right: 0.5rem;
-    }
+select {
+    position: absolute;
+    top: 0.5rem;
+    right: 0.5rem;
+}
+
 }
 </style>

+ 6 - 6
frontend/src/components/RichText/MenuBar.vue

@@ -1,8 +1,8 @@
 <template>
     <div>
         <template v-for="(item, index) in items">
-            <div class="divider" v-if="item.type === 'divider'" :key="index" />
-            <menu-item v-else :key="index" v-bind="item" />
+            <div class="divider" v-if="item.type === 'divider'" :key="index"/>
+            <menu-item v-else :key="index" v-bind="item"/>
         </template>
     </div>
 </template>
@@ -61,14 +61,14 @@ export default {
                 {
                     icon: 'h-1',
                     title: '一级标题',
-                    action: () => this.editor.chain().focus().toggleHeading({ level: 1 }).run(),
-                    isActive: () => this.editor.isActive('heading', { level: 1 }),
+                    action: () => this.editor.chain().focus().toggleHeading({level: 1}).run(),
+                    isActive: () => this.editor.isActive('heading', {level: 1}),
                 },
                 {
                     icon: 'h-2',
                     title: '二级标题',
-                    action: () => this.editor.chain().focus().toggleHeading({ level: 2 }).run(),
-                    isActive: () => this.editor.isActive('heading', { level: 2 }),
+                    action: () => this.editor.chain().focus().toggleHeading({level: 2}).run(),
+                    isActive: () => this.editor.isActive('heading', {level: 2}),
                 },
                 {
                     icon: 'paragraph',

+ 2 - 2
frontend/src/components/RichText/MenuItem.vue

@@ -1,7 +1,7 @@
 <template>
     <a-tooltip>
         <template slot="title">
-           {{ title }}
+            {{ title }}
         </template>
         <button
             class="menu-item"
@@ -10,7 +10,7 @@
             :title="title"
         >
             <svg class="remix">
-                <use :xlink:href="require('remixicon/fonts/remixicon.symbol.svg') + `#ri-${icon}`" />
+                <use :xlink:href="require('remixicon/fonts/remixicon.symbol.svg') + `#ri-${icon}`"/>
             </svg>
         </button>
     </a-tooltip>

+ 1 - 1
frontend/src/components/RichText/RichText.vue

@@ -4,7 +4,7 @@
 
 <script>
 export default {
-    name: "RichText",
+    name: 'RichText',
     props: ['html']
 }
 </script>

+ 1 - 1
frontend/src/components/RichText/RichTextEditor.vue

@@ -116,7 +116,7 @@ export default {
         }
     }
     border: 1px solid @gray;
-    line-height: 1.5!important;
+    line-height: 1.5 !important;
 
     &__header {
         display: flex;

+ 1 - 1
frontend/src/components/StdDataDisplay/StdCurd.vue

@@ -66,7 +66,7 @@
 <script>
 import StdTable from './StdTable'
 import StdDataEntry from '@/components/StdDataEntry/StdDataEntry'
-import FooterToolBar from "@/components/FooterToolbar/FooterToolBar"
+import FooterToolBar from '@/components/FooterToolbar/FooterToolBar'
 
 export default {
     name: 'StdCurd',

+ 12 - 20
frontend/src/components/StdDataDisplay/StdTable.vue

@@ -46,8 +46,8 @@
                     {{ c.mask ? c.mask[text] : text }}
                 </div>
                 <span v-else-if="c.datetime"
-                      :key="c.dataIndex">{{ text ? moment(text).format("yyyy-MM-DD HH:mm:ss") : '无' }}</span>
-                <span v-else-if="c.date" :key="c.dataIndex">{{ text ? moment(text).format("yyyy-MM-DD") : '无' }}</span>
+                      :key="c.dataIndex">{{ text ? moment(text).format('yyyy-MM-DD HH:mm:ss') : '无' }}</span>
+                <span v-else-if="c.date" :key="c.dataIndex">{{ text ? moment(text).format('yyyy-MM-DD') : '无' }}</span>
                 <div v-else-if="c.click" :key="c.dataIndex">
                     <a href="javascript:;"
                        @click="handleClick(
@@ -64,7 +64,7 @@
                     <template v-if="edit_text">{{ edit_text }}</template>
                     <template v-else>编辑</template>
                 </a>
-                <slot name="actions" :record="record" />
+                <slot name="actions" :record="record"/>
                 <template v-if="deletable">
                     <a-divider type="vertical"/>
                     <a-popconfirm
@@ -91,8 +91,8 @@
 
 <script>
 import StdPagination from './StdPagination'
-import moment from "moment"
-import StdDataEntry from "@/components/StdDataEntry/StdDataEntry"
+import moment from 'moment'
+import StdDataEntry from '@/components/StdDataEntry/StdDataEntry'
 
 export default {
     name: 'StdTable',
@@ -194,7 +194,7 @@ export default {
                 this.loading = false
             }).catch(e => {
                 console.log(e)
-                this.$message.error('系统错误')
+                this.$message.error(e?.message ?? '系统错误')
             })
         },
         stdChange(pagination, filters, sorter) {
@@ -212,11 +212,7 @@ export default {
                 this.$message.success('删除 ID: ' + id + ' 成功')
             }).catch(e => {
                 console.log(e)
-                if (e.message) {
-                    this.$message.error('错误 ' + e.message)
-                } else {
-                    this.$message.error('系统错误')
-                }
+                this.$message.error(e?.message ?? '系统错误')
             })
         },
         restore(id) {
@@ -225,11 +221,7 @@ export default {
                 this.$message.success('反删除 ID: ' + id + ' 成功')
             }).catch(e => {
                 console.log(e)
-                if (e.message) {
-                    this.$message.error('错误' + e.message)
-                } else {
-                    this.$message.error('系统错误')
-                }
+                this.$message.error(e?.message ?? '系统错误')
             })
         },
         get_searchColumns() {
@@ -240,9 +232,9 @@ export default {
                         && column.edit.type !== 'transfer') {
                         const tmp = Object.assign({}, column)
                         tmp.edit = Object.assign({}, column.edit)
-                        if (typeof column.search === "string") {
+                        if (typeof column.search === 'string') {
                             tmp.edit.type = column.search
-                        } else if (typeof column.search === "object") {
+                        } else if (typeof column.search === 'object') {
                             tmp.edit = column.search
                         }
                         searchColumns.push(tmp)
@@ -251,7 +243,7 @@ export default {
                     if (!column.edit) {
                         const tmp = Object.assign({}, column)
                         tmp.edit = Object.assign({}, column.edit)
-                        if (typeof column.search === "object") {
+                        if (typeof column.search === 'object') {
                             tmp.edit = column.search
                         }
                         searchColumns.push(tmp)
@@ -317,7 +309,7 @@ export default {
 <style lang="less">
 .ant-table-scroll {
     .ant-table-body {
-        overflow-x: auto!important;
+        overflow-x: auto !important;
     }
 }
 </style>

+ 1 - 1
frontend/src/components/StdDataEntry/StdRadioGroup.vue

@@ -11,7 +11,7 @@
 export default {
     name: 'StdRadioGroup',
     props: {
-        options: [Object,Array],
+        options: [Object, Array],
         value: {
             type: [String, Number]
         },

+ 3 - 3
frontend/src/components/VueItextarea/VueItextarea.vue

@@ -3,11 +3,11 @@
 </template>
 <style lang="less">
 .cm-s-monokai {
-    height: auto!important;
+    height: auto !important;
 }
 </style>
 <script>
-import { codemirror } from 'vue-codemirror'
+import {codemirror} from 'vue-codemirror'
 import 'codemirror/lib/codemirror.css'
 import 'codemirror/theme/monokai.css'
 
@@ -46,7 +46,7 @@ export default {
                 defaultTextHeight: 1000,
                 // more CodeMirror options...
             }
-        };
+        }
     },
 }
 </script>

+ 2 - 0
frontend/src/layouts/SideBar.vue

@@ -82,10 +82,12 @@ export default {
 .sidebar {
     position: fixed;
     width: 200px;
+
     .ant-menu-inline {
         height: calc(100vh - 120px);
         overflow-y: auto;
         overflow-x: hidden;
+
         .ant-menu-item {
             width: unset;
         }

+ 3 - 3
frontend/src/lazy.js

@@ -38,16 +38,16 @@ import {
     Select,
     Skeleton,
     Slider,
+    Space,
     Spin,
     Statistic,
     Steps,
+    Switch,
     Table,
     Tabs,
     Tooltip,
     Transfer,
-    Upload,
-    Switch,
-    Space
+    Upload
 } from 'ant-design-vue'
 
 Vue.use(ConfigProvider)

+ 2 - 0
frontend/src/lib/http/index.js

@@ -1,5 +1,6 @@
 import axios from 'axios'
 import store from '../store'
+import {router} from '@/router'
 
 /* 创建 axios 实例 */
 let http = axios.create({
@@ -40,6 +41,7 @@ http.interceptors.response.use(
             case 403:
                 // 无权访问时,直接登出
                 await store.dispatch('logout')
+                router.push('/login').catch()
                 break
         }
         return Promise.reject(error.response.data)

+ 3 - 2
frontend/src/lib/store/index.js

@@ -2,6 +2,7 @@ import Vue from 'vue'
 import Vuex from 'vuex'
 import VuexPersistence from 'vuex-persist'
 import {user} from './user'
+import {settings} from './settings'
 
 Vue.use(Vuex)
 
@@ -9,13 +10,13 @@ const debug = process.env.NODE_ENV !== 'production'
 
 const vuexLocal = new VuexPersistence({
     storage: window.localStorage,
-    modules: ['user']
+    modules: ['user', 'settings']
 })
 
 export default new Vuex.Store({
     // 将各组件分别模块化存入 Store
     modules: {
-        user
+        user, settings
     },
     plugins: [vuexLocal.plugin],
     strict: debug

+ 21 - 0
frontend/src/lib/store/settings.js

@@ -0,0 +1,21 @@
+export const settings = {
+    namespace: true,
+    state: {
+        language: ''
+    },
+    mutations: {
+        set_language(state, payload) {
+            state.language = payload
+        },
+    },
+    actions: {
+        set_language({commit}, data) {
+            commit('set_language', data)
+        },
+    },
+    getters: {
+        current_language(state) {
+            return state.language
+        }
+    }
+}

+ 29 - 0
frontend/src/lib/translate/gettext.js

@@ -0,0 +1,29 @@
+import {translate} from 'vue-gettext'
+import store from '@/lib/store'
+import {availableLanguages} from '@/lib/translate/index'
+import translations from '@/translations.json'
+
+let lang = window.navigator.language
+if (!lang.includes('zh')) {
+    lang = lang.split('-')[0]
+} else {
+    lang = lang.replace('-', '_')
+}
+store.getters.current_language ||
+store.commit('set_language', availableLanguages[lang] ? lang : 'en')
+
+const config = {
+    language: store.getters.current_language,
+    getTextPluginSilent: true,
+    getTextPluginMuteLanguages: [],
+    silent: true,
+}
+
+// easygettext aliases
+const {
+    gettext: $gettext,
+} = translate
+
+translate.initTranslations(translations, config)
+
+export default $gettext

+ 5 - 0
frontend/src/lib/translate/index.js

@@ -0,0 +1,5 @@
+export const availableLanguages = {
+    zh_CN: '简',
+    zh_TW: '繁',
+    en: 'En'
+}

+ 4 - 4
frontend/src/lib/utils/index.js

@@ -32,11 +32,11 @@ export default {
         Vue.prototype.scrollPosition = scrollPosition
 
         Vue.prototype.getWebSocketRoot = () => {
-            const protocol = location.protocol === "https:" ? "wss://" : "ws://"
-            if (process.env.NODE_ENV === 'development' && process.env["VUE_APP_API_WSS_ROOT"]) {
-                return process.env["VUE_APP_API_WSS_ROOT"]
+            const protocol = location.protocol === 'https:' ? 'wss://' : 'ws://'
+            if (process.env.NODE_ENV === 'development' && process.env['VUE_APP_API_WSS_ROOT']) {
+                return process.env['VUE_APP_API_WSS_ROOT']
             }
-            return protocol + location.host + process.env["VUE_APP_API_WSS_ROOT"]
+            return protocol + location.host + process.env['VUE_APP_API_WSS_ROOT']
         }
     }
 }

+ 123 - 0
frontend/src/locale/en/LC_MESSAGES/app.po

@@ -0,0 +1,123 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"Last-Translator: Automatically generated\n"
+"Language-Team: none\n"
+"Language: en\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: easygettext\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: src/router/index.js:98
+msgid "404 Not Found"
+msgstr ""
+
+#: src/router/index.js:76
+msgid "About"
+msgstr ""
+
+#: src/router/index.js:47
+msgid "Add Sites"
+msgstr ""
+
+#: src/views/dashboard/DashBoard.vue:94
+msgid "Cached:"
+msgstr ""
+
+#: src/router/index.js:60
+msgid "Config"
+msgstr ""
+
+#: src/router/index.js:19
+msgid "Dashboard"
+msgstr ""
+
+#: src/router/index.js:124
+msgid "Detected version update, this page will automatically refresh."
+msgstr ""
+
+#: src/views/dashboard/DashBoard.vue:95
+msgid "Free:"
+msgstr ""
+
+#: src/router/index.js:12
+msgid "Home"
+msgstr ""
+
+#: src/router/index.js:86
+msgid "Install"
+msgstr ""
+
+#: src/views/dashboard/DashBoard.vue:13
+msgid "Load averages"
+msgstr ""
+
+#: src/router/index.js:92
+msgid "Login"
+msgstr ""
+
+#: src/views/dashboard/DashBoard.vue:23
+msgid "Memory"
+msgstr ""
+
+#: src/router/index.js:68
+msgid "Modify Config"
+msgstr ""
+
+#: src/router/index.js:51
+msgid "Modify Sites"
+msgstr ""
+
+#: src/router/index.js:104
+msgid "Not Found"
+msgstr ""
+
+#: src/router/index.js:128
+msgid "OK"
+msgstr ""
+
+#: src/views/dashboard/DashBoard.vue:96
+msgid "Physical memory:"
+msgstr ""
+
+#: src/views/dashboard/DashBoard.vue:38
+msgid "Server status"
+msgstr ""
+
+#: src/router/index.js:35
+msgid "Sites"
+msgstr ""
+
+#: src/router/index.js:43
+msgid "Sites List"
+msgstr ""
+
+#: src/views/dashboard/DashBoard.vue:32
+msgid "Storage"
+msgstr ""
+
+#: src/router/index.js:123
+msgid "System message"
+msgstr ""
+
+#: src/views/dashboard/DashBoard.vue:124
+msgid "Total: "
+msgstr ""
+
+#: src/views/dashboard/DashBoard.vue:12
+msgid "Uptime"
+msgstr ""
+
+#: src/views/dashboard/DashBoard.vue:94
+msgid "Used:"
+msgstr ""
+
+#: src/views/dashboard/DashBoard.vue:123
+msgid "Used: "
+msgstr ""
+
+#: src/router/index.js:27
+msgid "Users"
+msgstr ""

+ 123 - 0
frontend/src/locale/en/LC_MESSAGES/app.po~

@@ -0,0 +1,123 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"Last-Translator: Automatically generated\n"
+"Language-Team: none\n"
+"Language: en\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: easygettext\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: src/router/index.js:98
+msgid "404 Not Found"
+msgstr ""
+
+#: src/router/index.js:76
+msgid "About"
+msgstr ""
+
+#: src/router/index.js:47
+msgid "Add Sites"
+msgstr ""
+
+#: src/views/dashboard/DashBoard.vue:94
+msgid "Cached: "
+msgstr ""
+
+#: src/router/index.js:60
+msgid "Config"
+msgstr ""
+
+#: src/router/index.js:19
+msgid "Dashboard"
+msgstr ""
+
+#: src/router/index.js:124
+msgid "Detected version update, this page will automatically refresh."
+msgstr ""
+
+#: src/views/dashboard/DashBoard.vue:95
+msgid "Free:"
+msgstr ""
+
+#: src/router/index.js:12
+msgid "Home"
+msgstr ""
+
+#: src/router/index.js:86
+msgid "Install"
+msgstr ""
+
+#: src/views/dashboard/DashBoard.vue:13
+msgid "Load averages"
+msgstr ""
+
+#: src/router/index.js:92
+msgid "Login"
+msgstr ""
+
+#: src/views/dashboard/DashBoard.vue:23
+msgid "Memory"
+msgstr ""
+
+#: src/router/index.js:68
+msgid "Modify Config"
+msgstr ""
+
+#: src/router/index.js:51
+msgid "Modify Sites"
+msgstr ""
+
+#: src/router/index.js:104
+msgid "Not Found"
+msgstr ""
+
+#: src/router/index.js:128
+msgid "OK"
+msgstr ""
+
+#: src/views/dashboard/DashBoard.vue:96
+msgid "Physical memory:"
+msgstr ""
+
+#: src/views/dashboard/DashBoard.vue:38
+msgid "Server status"
+msgstr ""
+
+#: src/router/index.js:35
+msgid "Sites"
+msgstr ""
+
+#: src/router/index.js:43
+msgid "Sites List"
+msgstr ""
+
+#: src/views/dashboard/DashBoard.vue:32
+msgid "Storage"
+msgstr ""
+
+#: src/router/index.js:123
+msgid "System message"
+msgstr ""
+
+#: src/views/dashboard/DashBoard.vue:124
+msgid "Total: "
+msgstr ""
+
+#: src/views/dashboard/DashBoard.vue:12
+msgid "Uptime"
+msgstr ""
+
+#: src/views/dashboard/DashBoard.vue:94
+msgid "Used:"
+msgstr ""
+
+#: src/views/dashboard/DashBoard.vue:123
+msgid "Used: "
+msgstr ""
+
+#: src/router/index.js:27
+msgid "Users"
+msgstr ""

BIN
frontend/src/locale/zh_CN/LC_MESSAGES/app.mo


+ 125 - 0
frontend/src/locale/zh_CN/LC_MESSAGES/app.po

@@ -0,0 +1,125 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Last-Translator: Automatically generated\n"
+"Language-Team: none\n"
+"Language: zh_CN\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: easygettext\n"
+"X-Generator: Poedit 3.0.1\n"
+
+#: src/router/index.js:98
+msgid "404 Not Found"
+msgstr "404 找不到页面"
+
+#: src/router/index.js:76
+msgid "About"
+msgstr "关于"
+
+#: src/router/index.js:47
+msgid "Add Sites"
+msgstr "添加站点"
+
+#: src/views/dashboard/DashBoard.vue:94
+msgid "Cached:"
+msgstr "缓存:"
+
+#: src/router/index.js:60
+msgid "Config"
+msgstr "配置"
+
+#: src/router/index.js:19
+msgid "Dashboard"
+msgstr "仪表盘"
+
+#: src/router/index.js:124
+msgid "Detected version update, this page will automatically refresh."
+msgstr "检测到版本更新,页面将会自动刷新。"
+
+#: src/views/dashboard/DashBoard.vue:95
+msgid "Free:"
+msgstr "空闲:"
+
+#: src/router/index.js:12
+msgid "Home"
+msgstr "首页"
+
+#: src/router/index.js:86
+msgid "Install"
+msgstr "安装"
+
+#: src/views/dashboard/DashBoard.vue:13
+msgid "Load averages"
+msgstr "系统负载"
+
+#: src/router/index.js:92
+msgid "Login"
+msgstr "登录"
+
+#: src/views/dashboard/DashBoard.vue:23
+msgid "Memory"
+msgstr "内存"
+
+#: src/router/index.js:68
+msgid "Modify Config"
+msgstr "配置修改"
+
+#: src/router/index.js:51
+msgid "Modify Sites"
+msgstr "站点修改"
+
+#: src/router/index.js:104
+msgid "Not Found"
+msgstr "找不到页面"
+
+#: src/router/index.js:128
+msgid "OK"
+msgstr "好的"
+
+#: src/views/dashboard/DashBoard.vue:96
+msgid "Physical memory:"
+msgstr "物理内存:"
+
+#: src/views/dashboard/DashBoard.vue:38
+msgid "Server status"
+msgstr "服务器状态"
+
+#: src/router/index.js:35
+msgid "Sites"
+msgstr "站点"
+
+#: src/router/index.js:43
+msgid "Sites List"
+msgstr "站点列表"
+
+#: src/views/dashboard/DashBoard.vue:32
+msgid "Storage"
+msgstr "存储"
+
+#: src/router/index.js:123
+msgid "System message"
+msgstr "系统消息"
+
+#: src/views/dashboard/DashBoard.vue:124
+msgid "Total: "
+msgstr "总共: "
+
+#: src/views/dashboard/DashBoard.vue:12
+msgid "Uptime"
+msgstr "运行时间"
+
+#: src/views/dashboard/DashBoard.vue:94
+msgid "Used:"
+msgstr "已使用:"
+
+#: src/views/dashboard/DashBoard.vue:123
+msgid "Used: "
+msgstr "已使用: "
+
+#: src/router/index.js:27
+msgid "Users"
+msgstr "用户"

+ 125 - 0
frontend/src/locale/zh_CN/LC_MESSAGES/app.po~

@@ -0,0 +1,125 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Last-Translator: Automatically generated\n"
+"Language-Team: none\n"
+"Language: zh_CN\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: easygettext\n"
+"X-Generator: Poedit 3.0.1\n"
+
+#: src/router/index.js:98
+msgid "404 Not Found"
+msgstr "404 找不到页面"
+
+#: src/router/index.js:76
+msgid "About"
+msgstr "关于"
+
+#: src/router/index.js:47
+msgid "Add Sites"
+msgstr "添加站点"
+
+#: src/views/dashboard/DashBoard.vue:94
+msgid "Cached: "
+msgstr "缓存: "
+
+#: src/router/index.js:60
+msgid "Config"
+msgstr "配置"
+
+#: src/router/index.js:19
+msgid "Dashboard"
+msgstr "仪表盘"
+
+#: src/router/index.js:124
+msgid "Detected version update, this page will automatically refresh."
+msgstr "检测到版本更新,页面将会自动刷新。"
+
+#: src/views/dashboard/DashBoard.vue:95
+msgid "Free:"
+msgstr "空闲:"
+
+#: src/router/index.js:12
+msgid "Home"
+msgstr "首页"
+
+#: src/router/index.js:86
+msgid "Install"
+msgstr "安装"
+
+#: src/views/dashboard/DashBoard.vue:13
+msgid "Load averages"
+msgstr "系统负载"
+
+#: src/router/index.js:92
+msgid "Login"
+msgstr "登录"
+
+#: src/views/dashboard/DashBoard.vue:23
+msgid "Memory"
+msgstr "内存"
+
+#: src/router/index.js:68
+msgid "Modify Config"
+msgstr "配置修改"
+
+#: src/router/index.js:51
+msgid "Modify Sites"
+msgstr "站点修改"
+
+#: src/router/index.js:104
+msgid "Not Found"
+msgstr "找不到页面"
+
+#: src/router/index.js:128
+msgid "OK"
+msgstr "好的"
+
+#: src/views/dashboard/DashBoard.vue:96
+msgid "Physical memory:"
+msgstr "物理内存:"
+
+#: src/views/dashboard/DashBoard.vue:38
+msgid "Server status"
+msgstr "服务器状态"
+
+#: src/router/index.js:35
+msgid "Sites"
+msgstr "站点"
+
+#: src/router/index.js:43
+msgid "Sites List"
+msgstr "站点列表"
+
+#: src/views/dashboard/DashBoard.vue:32
+msgid "Storage"
+msgstr "存储"
+
+#: src/router/index.js:123
+msgid "System message"
+msgstr "系统消息"
+
+#: src/views/dashboard/DashBoard.vue:124
+msgid "Total: "
+msgstr "总共: "
+
+#: src/views/dashboard/DashBoard.vue:12
+msgid "Uptime"
+msgstr "运行时间"
+
+#: src/views/dashboard/DashBoard.vue:94
+msgid "Used:"
+msgstr "已使用:"
+
+#: src/views/dashboard/DashBoard.vue:123
+msgid "Used: "
+msgstr "已使用: "
+
+#: src/router/index.js:27
+msgid "Users"
+msgstr "用户"

+ 122 - 0
frontend/src/locale/zh_TW/LC_MESSAGES/app.po

@@ -0,0 +1,122 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"Last-Translator: Automatically generated\n"
+"Language-Team: none\n"
+"Language: zh_TW\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: easygettext\n"
+
+#: src/router/index.js:98
+msgid "404 Not Found"
+msgstr ""
+
+#: src/router/index.js:76
+msgid "About"
+msgstr ""
+
+#: src/router/index.js:47
+msgid "Add Sites"
+msgstr ""
+
+#: src/views/dashboard/DashBoard.vue:94
+msgid "Cached:"
+msgstr ""
+
+#: src/router/index.js:60
+msgid "Config"
+msgstr ""
+
+#: src/router/index.js:19
+msgid "Dashboard"
+msgstr ""
+
+#: src/router/index.js:124
+msgid "Detected version update, this page will automatically refresh."
+msgstr ""
+
+#: src/views/dashboard/DashBoard.vue:95
+msgid "Free:"
+msgstr ""
+
+#: src/router/index.js:12
+msgid "Home"
+msgstr ""
+
+#: src/router/index.js:86
+msgid "Install"
+msgstr ""
+
+#: src/views/dashboard/DashBoard.vue:13
+msgid "Load averages"
+msgstr ""
+
+#: src/router/index.js:92
+msgid "Login"
+msgstr ""
+
+#: src/views/dashboard/DashBoard.vue:23
+msgid "Memory"
+msgstr ""
+
+#: src/router/index.js:68
+msgid "Modify Config"
+msgstr ""
+
+#: src/router/index.js:51
+msgid "Modify Sites"
+msgstr ""
+
+#: src/router/index.js:104
+msgid "Not Found"
+msgstr ""
+
+#: src/router/index.js:128
+msgid "OK"
+msgstr ""
+
+#: src/views/dashboard/DashBoard.vue:96
+msgid "Physical memory:"
+msgstr ""
+
+#: src/views/dashboard/DashBoard.vue:38
+msgid "Server status"
+msgstr ""
+
+#: src/router/index.js:35
+msgid "Sites"
+msgstr ""
+
+#: src/router/index.js:43
+msgid "Sites List"
+msgstr ""
+
+#: src/views/dashboard/DashBoard.vue:32
+msgid "Storage"
+msgstr ""
+
+#: src/router/index.js:123
+msgid "System message"
+msgstr ""
+
+#: src/views/dashboard/DashBoard.vue:124
+msgid "Total: "
+msgstr ""
+
+#: src/views/dashboard/DashBoard.vue:12
+msgid "Uptime"
+msgstr ""
+
+#: src/views/dashboard/DashBoard.vue:94
+msgid "Used:"
+msgstr ""
+
+#: src/views/dashboard/DashBoard.vue:123
+msgid "Used: "
+msgstr ""
+
+#: src/router/index.js:27
+msgid "Users"
+msgstr ""

+ 122 - 0
frontend/src/locale/zh_TW/LC_MESSAGES/app.po~

@@ -0,0 +1,122 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"Last-Translator: Automatically generated\n"
+"Language-Team: none\n"
+"Language: zh_TW\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: easygettext\n"
+
+#: src/router/index.js:98
+msgid "404 Not Found"
+msgstr ""
+
+#: src/router/index.js:76
+msgid "About"
+msgstr ""
+
+#: src/router/index.js:47
+msgid "Add Sites"
+msgstr ""
+
+#: src/views/dashboard/DashBoard.vue:94
+msgid "Cached: "
+msgstr ""
+
+#: src/router/index.js:60
+msgid "Config"
+msgstr ""
+
+#: src/router/index.js:19
+msgid "Dashboard"
+msgstr ""
+
+#: src/router/index.js:124
+msgid "Detected version update, this page will automatically refresh."
+msgstr ""
+
+#: src/views/dashboard/DashBoard.vue:95
+msgid "Free:"
+msgstr ""
+
+#: src/router/index.js:12
+msgid "Home"
+msgstr ""
+
+#: src/router/index.js:86
+msgid "Install"
+msgstr ""
+
+#: src/views/dashboard/DashBoard.vue:13
+msgid "Load averages"
+msgstr ""
+
+#: src/router/index.js:92
+msgid "Login"
+msgstr ""
+
+#: src/views/dashboard/DashBoard.vue:23
+msgid "Memory"
+msgstr ""
+
+#: src/router/index.js:68
+msgid "Modify Config"
+msgstr ""
+
+#: src/router/index.js:51
+msgid "Modify Sites"
+msgstr ""
+
+#: src/router/index.js:104
+msgid "Not Found"
+msgstr ""
+
+#: src/router/index.js:128
+msgid "OK"
+msgstr ""
+
+#: src/views/dashboard/DashBoard.vue:96
+msgid "Physical memory:"
+msgstr ""
+
+#: src/views/dashboard/DashBoard.vue:38
+msgid "Server status"
+msgstr ""
+
+#: src/router/index.js:35
+msgid "Sites"
+msgstr ""
+
+#: src/router/index.js:43
+msgid "Sites List"
+msgstr ""
+
+#: src/views/dashboard/DashBoard.vue:32
+msgid "Storage"
+msgstr ""
+
+#: src/router/index.js:123
+msgid "System message"
+msgstr ""
+
+#: src/views/dashboard/DashBoard.vue:124
+msgid "Total: "
+msgstr ""
+
+#: src/views/dashboard/DashBoard.vue:12
+msgid "Uptime"
+msgstr ""
+
+#: src/views/dashboard/DashBoard.vue:94
+msgid "Used:"
+msgstr ""
+
+#: src/views/dashboard/DashBoard.vue:123
+msgid "Used: "
+msgstr ""
+
+#: src/router/index.js:27
+msgid "Users"
+msgstr ""

+ 10 - 0
frontend/src/main.js

@@ -9,6 +9,9 @@ import NProgress from 'nprogress'
 import 'nprogress/nprogress.css'
 import utils from '@/lib/utils'
 import api from '@/api'
+import GetTextPlugin from 'vue-gettext'
+import {availableLanguages} from '@/lib/translate'
+import translations from '@/translations.json'
 
 Vue.use(utils)
 
@@ -17,6 +20,13 @@ Vue.config.productionTip = false
 Vue.prototype.$routeConfig = routes
 Vue.prototype.$api = api
 
+Vue.use(GetTextPlugin, {
+    availableLanguages,
+    defaultLanguage: store.getters.current_language,
+    translations: translations,
+    silent: true
+})
+
 NProgress.configure({
     easing: 'ease',
     speed: 500,

+ 20 - 19
frontend/src/router/index.js

@@ -2,20 +2,21 @@ import Vue from 'vue'
 import VueRouter from 'vue-router'
 import axios from 'axios'
 import store from '@/lib/store'
+import $gettext from '@/lib/translate/gettext'
 
 Vue.use(VueRouter)
 
 export const routes = [
     {
         path: '/',
-        name: '首页',
+        name: $gettext('Home'),
         component: () => import('@/layouts/BaseLayout'),
         redirect: '/dashboard',
         children: [
             {
                 path: 'dashboard',
-                component: () => import('@/views/doashboard/DashBoard'),
-                name: '仪表盘',
+                component: () => import('@/views/dashboard/DashBoard'),
+                name:  $gettext('Dashboard'),
                 meta: {
                     //hiddenHeaderContent: true,
                     icon: 'home'
@@ -23,7 +24,7 @@ export const routes = [
             },
             {
                 path: 'user',
-                name: '用户管理',
+                name: $gettext('Users'),
                 component: () => import('@/views/user/User.vue'),
                 meta: {
                     icon: 'user'
@@ -31,7 +32,7 @@ export const routes = [
             },
             {
                 path: 'domain',
-                name: '网站管理',
+                name: $gettext('Sites'),
                 component: () => import('@/layouts/BaseRouterView'),
                 meta: {
                     icon: 'cloud'
@@ -39,24 +40,24 @@ export const routes = [
                 redirect: '/domain/list',
                 children: [{
                     path: 'list',
-                    name: '网站列表',
+                    name: $gettext('Sites List'),
                     component: () => import('@/views/domain/DomainList.vue'),
                 }, {
                     path: 'add',
-                    name: '添加站点',
+                    name: $gettext('Add Sites'),
                     component: () => import('@/views/domain/DomainAdd.vue'),
                 }, {
                     path: ':name',
-                    name: '编辑站点',
+                    name: $gettext('Modify Sites'),
                     component: () => import('@/views/domain/DomainEdit.vue'),
                     meta: {
                         hiddenInSidebar: true
                     }
-                }, ]
+                },]
             },
             {
                 path: 'config',
-                name: '配置管理',
+                name: $gettext('Config'),
                 component: () => import('@/views/config/Config.vue'),
                 meta: {
                     icon: 'file'
@@ -64,7 +65,7 @@ export const routes = [
             },
             {
                 path: 'config/:name',
-                name: '配置编辑',
+                name: $gettext('Modify Config'),
                 component: () => import('@/views/config/ConfigEdit.vue'),
                 meta: {
                     hiddenInSidebar: true
@@ -72,7 +73,7 @@ export const routes = [
             },
             {
                 path: 'about',
-                name: '关于',
+                name: $gettext('About'),
                 component: () => import('@/views/other/About.vue'),
                 meta: {
                     icon: 'info-circle'
@@ -82,25 +83,25 @@ export const routes = [
     },
     {
         path: '/install',
-        name: '安装',
+        name: $gettext('Install'),
         component: () => import('@/views/other/Install'),
         meta: {noAuth: true}
     },
     {
         path: '/login',
-        name: '登录',
+        name: $gettext('Login'),
         component: () => import('@/views/other/Login'),
         meta: {noAuth: true}
     },
     {
         path: '/404',
-        name: '404 Not Found',
+        name: $gettext('404 Not Found'),
         component: () => import('@/views/other/Error'),
         meta: {noAuth: true, status_code: 404, error: 'Not Found'}
     },
     {
         path: '*',
-        name: '未找到页面',
+        name: $gettext('Not Found'),
         redirect: '/404',
         meta: {noAuth: true}
     }
@@ -119,12 +120,12 @@ router.beforeEach((to, from, next) => {
             if (!(process.env.VUE_APP_VERSION === r.data.version
                 && Number(process.env.VUE_APP_BUILD_ID) === r.data.build_id)) {
                 Vue.prototype.$info({
-                    title: '系统信息',
-                    content: '检测到版本更新,将会自动刷新本页',
+                    title: $gettext('System message'),
+                    content: $gettext('Detected version update, this page will automatically refresh.'),
                     onOk() {
                         location.reload()
                     },
-                    okText: '好的'
+                    okText: $gettext('OK')
                 })
             }
         })

+ 1 - 0
frontend/src/translations.json

@@ -0,0 +1 @@
+{"en":{},"zh_CN":{"404 Not Found":"404 找不到页面","About":"关于","Add Sites":"添加站点","Cached:":"缓存:","Config":"配置","Dashboard":"仪表盘","Detected version update, this page will automatically refresh.":"检测到版本更新,页面将会自动刷新。","Free:":"空闲:","Home":"首页","Install":"安装","Load averages":"系统负载","Login":"登录","Memory":"内存","Modify Config":"配置修改","Modify Sites":"站点修改","Not Found":"找不到页面","OK":"好的","Physical memory:":"物理内存:","Server status":"服务器状态","Sites":"站点","Sites List":"站点列表","Storage":"存储","System message":"系统消息","Total: ":"总共: ","Uptime":"运行时间","Used:":"已使用:","Used: ":"已使用: ","Users":"用户"},"zh_TW":{}}

+ 11 - 11
frontend/src/views/config/Config.vue

@@ -17,29 +17,29 @@
 </template>
 
 <script>
-import StdTable from "@/components/StdDataDisplay/StdTable"
+import StdTable from '@/components/StdDataDisplay/StdTable'
 
 const columns = [{
-    title: "名称",
-    dataIndex: "name",
-    scopedSlots: {customRender: "名称"},
+    title: '名称',
+    dataIndex: 'name',
+    scopedSlots: {customRender: '名称'},
     sorter: true,
     pithy: true
 }, {
-    title: "修改时间",
-    dataIndex: "modify",
+    title: '修改时间',
+    dataIndex: 'modify',
     datetime: true,
-    scopedSlots: {customRender: "modify"},
+    scopedSlots: {customRender: 'modify'},
     sorter: true,
     pithy: true
 }, {
-    title: "操作",
-    dataIndex: "action",
-    scopedSlots: {customRender: "action"}
+    title: '操作',
+    dataIndex: 'action',
+    scopedSlots: {customRender: 'action'}
 }]
 
 export default {
-    name: "Config",
+    name: 'Config',
     components: {StdTable},
     data() {
         return {

+ 9 - 9
frontend/src/views/config/ConfigEdit.vue

@@ -11,16 +11,16 @@
 </template>
 
 <script>
-import FooterToolBar from "@/components/FooterToolbar/FooterToolBar"
-import VueItextarea from "@/components/VueItextarea/VueItextarea"
+import FooterToolBar from '@/components/FooterToolbar/FooterToolBar'
+import VueItextarea from '@/components/VueItextarea/VueItextarea'
 
 export default {
-    name: "DomainEdit",
+    name: 'DomainEdit',
     components: {FooterToolBar, VueItextarea},
     data() {
         return {
             name: this.$route.params.name,
-            configText: ""
+            configText: ''
         }
     },
     watch: {
@@ -35,7 +35,7 @@ export default {
         }
     },
     created() {
-       this.init()
+        this.init()
     },
     methods: {
         init() {
@@ -44,19 +44,19 @@ export default {
                     this.configText = r.config
                 }).catch(r => {
                     console.log(r)
-                    this.$message.error("服务器错误")
+                    this.$message.error('服务器错误')
                 })
             } else {
-                this.configText = ""
+                this.configText = ''
             }
         },
         save() {
             this.$api.config.save(this.name ? this.name : this.config.name, {content: this.configText}).then(r => {
                 this.configText = r.config
-                this.$message.success("保存成功")
+                this.$message.success('保存成功')
             }).catch(r => {
                 console.log(r)
-                this.$message.error("保存错误")
+                this.$message.error('保存错误')
             })
         }
     }

+ 28 - 24
frontend/src/views/doashboard/DashBoard.vue → frontend/src/views/dashboard/DashBoard.vue

@@ -2,7 +2,7 @@
     <div>
         <a-row class="row-two">
             <a-col :lg="24" :sm="24">
-                <a-card style="min-height: 400px" title="服务器状态">
+                <a-card style="min-height: 400px" :title="$gettext('Server status')">
                     <a-row>
                         <a-col :lg="12" :sm="24" class="chart">
                             <a-statistic :value="cpu" style="margin: 0 50px 10px 0" title="CPU">
@@ -10,27 +10,30 @@
                                     <span>%</span>
                                 </template>
                             </a-statistic>
-                            <p>运行时间 {{ uptime }}</p>
-                            <p>系统负载 1min:{{ loadavg.load1 }}  5min:{{ loadavg.load5 }}
+                            <p><translate>Uptime</translate> {{ uptime }}</p>
+                            <p><translate>Load averages</translate> 1min:{{ loadavg.load1 }} 5min:{{ loadavg.load5 }}
                                 15min:{{ loadavg.load15 }}</p>
                             <line-chart :chart-data="cpu_analytic" :options="cpu_analytic.options" :height="150"/>
                         </a-col>
                         <a-col :lg="6" :sm="8" :xs="12" class="chart_dashboard">
                             <div>
                                 <a-tooltip
-                                    :title="'已使用: '+ memory_used + ' 缓存: ' + memory_cached + '空闲:' + memory_free +
-                                     '  物理内存: ' + memory_total">
-                                    <a-progress :percent="memory_pressure" strokeColor="rgb(135, 208, 104)" type="dashboard" />
-                                    <p class="description">实际内存占用</p>
+                                    :title="$gettext('Used:') + memory_used + $gettext('Cached:') +
+                                     memory_cached + $gettext('Free:') + memory_free +
+                                     $gettext('Physical memory:') + memory_total">
+                                    <a-progress :percent="memory_pressure" strokeColor="rgb(135, 208, 104)"
+                                                type="dashboard"/>
+                                    <p class="description" v-translate>Memory</p>
                                 </a-tooltip>
                             </div>
                         </a-col>
                         <a-col :lg="6" :sm="8" :xs="12" class="chart_dashboard">
                             <div>
                                 <a-tooltip
-                                    :title="'已使用: '+ disk_used + ' / 总共: ' + disk_total">
-                                    <a-progress :percent="disk_percentage" type="dashboard" />
-                                    <p class="description">存储空间</p>
+                                    :title="$gettext('Used: ')+ disk_used +
+                                     ' / '+ $gettext('Total: ') + disk_total">
+                                    <a-progress :percent="disk_percentage" type="dashboard"/>
+                                    <p class="description" v-translate>Storage</p>
                                 </a-tooltip>
                             </div>
                         </a-col>
@@ -42,11 +45,11 @@
 </template>
 
 <script>
-import LineChart from "@/components/Chart/LineChart"
+import LineChart from '@/components/Chart/LineChart'
 import ReconnectingWebSocket from 'reconnecting-websocket'
 
 export default {
-    name: "DashBoard",
+    name: 'DashBoard',
     components: {
         LineChart
     },
@@ -56,10 +59,10 @@ export default {
             loading: true,
             stat: {},
             memory_pressure: 0,
-            memory_used: "",
-            memory_cached: "",
-            memory_free: "",
-            memory_total: "",
+            memory_used: '',
+            memory_cached: '',
+            memory_free: '',
+            memory_total: '',
             cpu_analytic: {
                 datasets: [{
                     label: 'cpu user',
@@ -76,7 +79,7 @@ export default {
                 }],
                 options: {
                     responsive: true,
-                    maintainAspectRatio:false,
+                    maintainAspectRatio: false,
                     responsiveAnimationDuration: 0, // 调整大小后的动画持续时间
                     elements: {
                         line: {
@@ -94,7 +97,7 @@ export default {
                         }],
                         xAxes: [
                             {
-                                type: "time",
+                                type: 'time',
                                 time: {
                                     unit: 'minute',
                                 }
@@ -105,14 +108,14 @@ export default {
             },
             cpu: 0,
             disk_percentage: 0,
-            disk_total: "",
-            disk_used: "",
-            uptime: "",
+            disk_total: '',
+            disk_used: '',
+            uptime: '',
             loadavg: {}
         }
     },
     created() {
-        this.websocket = new ReconnectingWebSocket(this.getWebSocketRoot() + "/analytic?token="
+        this.websocket = new ReconnectingWebSocket(this.getWebSocketRoot() + '/analytic?token='
             + btoa(this.$store.state.user.token))
         this.websocket.onmessage = this.wsOnMessage
         this.websocket.onopen = this.wsOpen
@@ -127,7 +130,7 @@ export default {
     },
     methods: {
         wsOpen() {
-            this.websocket.send("ping")
+            this.websocket.send('ping')
         },
         wsOnMessage(m) {
             const r = JSON.parse(m.data)
@@ -156,7 +159,7 @@ export default {
             uptime -= uptime_days * 86400
             let uptime_hours = Math.floor(uptime / 3600)
             uptime -= uptime_hours * 3600
-            this.uptime = uptime_days + 'd ' + uptime_hours + 'h ' +  Math.floor(uptime/60) + 'm'
+            this.uptime = uptime_days + 'd ' + uptime_hours + 'h ' + Math.floor(uptime / 60) + 'm'
             this.loadavg = r.loadavg
         }
     }
@@ -172,6 +175,7 @@ export default {
 
     .chart_dashboard {
         padding: 60px;
+
         .description {
             width: 120px;
             text-align: center

+ 6 - 4
frontend/src/views/domain/CertInfo.vue

@@ -6,19 +6,21 @@
         <p>过期时间:{{ moment(cert.not_after).format('YYYY-MM-DD HH:mm:ss') }}</p>
         <p>在此之前无效:{{ moment(cert.not_before).format('YYYY-MM-DD HH:mm:ss') }}</p>
         <template v-if="new Date().toISOString() < cert.not_before || new Date().toISOString() > cert.not_after">
-            <a-icon :style="{ color: 'red' }" type="close-circle" /> 此证书已过期
+            <a-icon :style="{ color: 'red' }" type="close-circle"/>
+            此证书已过期
         </template>
         <template v-else>
-            <a-icon :style="{ color: 'green' }" type="check-circle" /> 证书处在有效期内
+            <a-icon :style="{ color: 'green' }" type="check-circle"/>
+            证书处在有效期内
         </template>
     </div>
 </template>
 
 <script>
-import moment from "moment"
+import moment from 'moment'
 
 export default {
-    name: "CertInfo",
+    name: 'CertInfo',
     data() {
         return {
             ok: false,

+ 7 - 7
frontend/src/views/domain/DomainAdd.vue

@@ -14,13 +14,13 @@
 </template>
 
 <script>
-import FooterToolBar from "@/components/FooterToolbar/FooterToolBar"
-import StdDataEntry from "@/components/StdDataEntry/StdDataEntry"
-import {columns} from "@/views/domain/columns"
-import {unparse} from "@/views/domain/methods"
+import FooterToolBar from '@/components/FooterToolbar/FooterToolBar'
+import StdDataEntry from '@/components/StdDataEntry/StdDataEntry'
+import {columns} from '@/views/domain/columns'
+import {unparse} from '@/views/domain/methods'
 
 export default {
-    name: "DomainAdd",
+    name: 'DomainAdd',
     components: {StdDataEntry, FooterToolBar},
     data() {
         return {
@@ -36,10 +36,10 @@ export default {
             this.$api.domain.get_template('http-conf').then(r => {
                 let text = unparse(r.template, this.config)
                 this.$api.domain.save(this.config.name, {content: text, enabled: true}).then(() => {
-                    this.$message.success("保存成功")
+                    this.$message.success('保存成功')
 
                     this.$api.domain.enable(this.config.name).then(() => {
-                        this.$message.success("启用成功")
+                        this.$message.success('启用成功')
 
                         this.$router.push('/domain/' + this.config.name)
 

+ 32 - 32
frontend/src/views/domain/DomainEdit.vue

@@ -33,15 +33,15 @@
 
 
 <script>
-import StdDataEntry from "@/components/StdDataEntry/StdDataEntry"
-import FooterToolBar from "@/components/FooterToolbar/FooterToolBar"
-import VueItextarea from "@/components/VueItextarea/VueItextarea"
-import {columns, columnsSSL} from "@/views/domain/columns"
-import {unparse} from "@/views/domain/methods"
-import CertInfo from "@/views/domain/CertInfo"
+import StdDataEntry from '@/components/StdDataEntry/StdDataEntry'
+import FooterToolBar from '@/components/FooterToolbar/FooterToolBar'
+import VueItextarea from '@/components/VueItextarea/VueItextarea'
+import {columns, columnsSSL} from '@/views/domain/columns'
+import {unparse} from '@/views/domain/methods'
+import CertInfo from '@/views/domain/CertInfo'
 
 export default {
-    name: "DomainEdit",
+    name: 'DomainEdit',
     components: {CertInfo, FooterToolBar, StdDataEntry, VueItextarea},
     data() {
         return {
@@ -49,15 +49,15 @@ export default {
             config: {
                 http_listen_port: 80,
                 https_listen_port: null,
-                server_name: "",
-                index: "",
-                root: "",
-                ssl_certificate: "",
-                ssl_certificate_key: "",
+                server_name: '',
+                index: '',
+                root: '',
+                ssl_certificate: '',
+                ssl_certificate_key: '',
                 support_ssl: false,
                 auto_cert: false
             },
-            configText: "",
+            configText: '',
             ws: null,
             ok: false
         }
@@ -102,17 +102,17 @@ export default {
                     })
                 }).catch(r => {
                     console.log(r)
-                    this.$message.error("服务器错误")
+                    this.$message.error('服务器错误')
                 })
             } else {
                 this.config = {
                     http_listen_port: 80,
                     https_listen_port: null,
-                    server_name: "",
-                    index: "",
-                    root: "",
-                    ssl_certificate: "",
-                    ssl_certificate_key: "",
+                    server_name: '',
+                    index: '',
+                    root: '',
+                    ssl_certificate: '',
+                    ssl_certificate_key: '',
                     support_ssl: false,
                     auto_cert: false,
                 }
@@ -176,43 +176,43 @@ export default {
         save() {
             this.$api.domain.save(this.name, {content: this.configText}).then(r => {
                 this.parse(r)
-                this.$message.success("保存成功")
+                this.$message.success('保存成功')
                 if (this.name) {
-                    if (this.$refs["cert-info"]) this.$refs["cert-info"].get()
+                    if (this.$refs['cert-info']) this.$refs['cert-info'].get()
                 }
             }).catch(r => {
                 console.log(r)
-                this.$message.error("保存错误" + r.message !== undefined ? " " + r.message : null, 10)
+                this.$message.error('保存错误' + r.message !== undefined ? ' ' + r.message : null, 10)
             })
         },
         issue_cert() {
-            this.$message.info("请注意,当前配置中 server_name 必须为需要申请证书的域名,否则无法申请", 15)
-            this.$message.info("正在申请,请稍后", 15)
-            this.ws = new WebSocket(this.getWebSocketRoot() + "/cert/issue/" + this.config.server_name
-                + "?token=" + btoa(this.$store.state.user.token))
+            this.$message.info('请注意,当前配置中 server_name 必须为需要申请证书的域名,否则无法申请', 15)
+            this.$message.info('正在申请,请稍后', 15)
+            this.ws = new WebSocket(this.getWebSocketRoot() + '/cert/issue/' + this.config.server_name
+                + '?token=' + btoa(this.$store.state.user.token))
 
             this.ws.onopen = () => {
-                this.ws.send("go")
+                this.ws.send('go')
             }
 
             this.ws.onmessage = m => {
                 const r = JSON.parse(m.data)
                 switch (r.status) {
-                    case "success":
+                    case 'success':
                         this.$message.success(r.message, 10)
                         break
-                    case "info":
+                    case 'info':
                         this.$message.info(r.message, 10)
                         break
-                    case "error":
+                    case 'error':
                         this.$message.error(r.message, 10)
                         break
                 }
 
-                if (r.status === "success" && r.ssl_certificate !== undefined && r.ssl_certificate_key !== undefined) {
+                if (r.status === 'success' && r.ssl_certificate !== undefined && r.ssl_certificate_key !== undefined) {
                     this.config.ssl_certificate = r.ssl_certificate
                     this.config.ssl_certificate_key = r.ssl_certificate_key
-                    if (this.$refs["cert-info"]) this.$refs["cert-info"].get()
+                    if (this.$refs['cert-info']) this.$refs['cert-info'].get()
                 }
             }
         },

+ 20 - 20
frontend/src/views/domain/DomainList.vue

@@ -21,40 +21,40 @@
 </template>
 
 <script>
-import StdTable from "@/components/StdDataDisplay/StdTable"
+import StdTable from '@/components/StdDataDisplay/StdTable'
 
 const columns = [{
-    title: "配置名称",
-    dataIndex: "name",
-    scopedSlots: {customRender: "名称"},
+    title: '配置名称',
+    dataIndex: 'name',
+    scopedSlots: {customRender: '名称'},
     sorter: true,
     pithy: true
 }, {
-    title: "状态",
-    dataIndex: "enabled",
+    title: '状态',
+    dataIndex: 'enabled',
     badge: true,
-    scopedSlots: {customRender: "enabled"},
+    scopedSlots: {customRender: 'enabled'},
     mask: {
-        true: "启用",
-        false: "未启用"
+        true: '启用',
+        false: '未启用'
     },
     sorter: true,
     pithy: true
 }, {
-    title: "修改时间",
-    dataIndex: "modify",
+    title: '修改时间',
+    dataIndex: 'modify',
     datetime: true,
-    scopedSlots: {customRender: "modify"},
+    scopedSlots: {customRender: 'modify'},
     sorter: true,
     pithy: true
 }, {
-    title: "操作",
-    dataIndex: "action",
-    scopedSlots: {customRender: "action"}
+    title: '操作',
+    dataIndex: 'action',
+    scopedSlots: {customRender: 'action'}
 }]
 
 export default {
-    name: "Domain",
+    name: 'Domain',
     components: {StdTable},
     data() {
         return {
@@ -65,20 +65,20 @@ export default {
     methods: {
         enable(name) {
             this.$api.domain.enable(name).then(() => {
-                this.$message.success("启用成功")
+                this.$message.success('启用成功')
                 this.$refs.table.get_list()
             }).catch(r => {
                 console.log(r)
-                this.$message.error("启用失败 " + (r.message??''), 10)
+                this.$message.error('启用失败 ' + (r.message ?? ''), 10)
             })
         },
         disable(name) {
             this.$api.domain.disable(name).then(() => {
-                this.$message.success("禁用成功")
+                this.$message.success('禁用成功')
                 this.$refs.table.get_list()
             }).catch(r => {
                 console.log(r)
-                this.$message.error("禁用失败")
+                this.$message.error('禁用失败')
             })
         }
     }

+ 32 - 32
frontend/src/views/domain/columns.js

@@ -1,71 +1,71 @@
 const columns = [{
-    title: "配置文件名称",
-    dataIndex: "name",
+    title: '配置文件名称',
+    dataIndex: 'name',
     edit: {
-        type: "input"
+        type: 'input'
     }
 }, {
-    title: "网站域名 (server_name)",
-    dataIndex: "server_name",
+    title: '网站域名 (server_name)',
+    dataIndex: 'server_name',
     edit: {
-        type: "input"
+        type: 'input'
     }
 }, {
-    title: "网站根目录 (root)",
-    dataIndex: "root",
+    title: '网站根目录 (root)',
+    dataIndex: 'root',
     edit: {
-        type: "input"
+        type: 'input'
     }
 }, {
-    title: "网站首页 (index)",
-    dataIndex: "index",
+    title: '网站首页 (index)',
+    dataIndex: 'index',
     edit: {
-        type: "input"
+        type: 'input'
     }
 }, {
-    title: "http 监听端口",
-    dataIndex: "http_listen_port",
+    title: 'http 监听端口',
+    dataIndex: 'http_listen_port',
     edit: {
-        type: "number",
+        type: 'number',
         min: 80
     }
 }, {
-    title: "支持 SSL",
-    dataIndex: "support_ssl",
+    title: '支持 SSL',
+    dataIndex: 'support_ssl',
     edit: {
-        type: "switch",
-        event: "change_support_ssl"
+        type: 'switch',
+        event: 'change_support_ssl'
     }
 }]
 
 const columnsSSL = [{
-    title: "自动续签",
-    dataIndex: "auto_cert",
+    title: '自动续签',
+    dataIndex: 'auto_cert',
     edit: {
-        type: "switch",
-        event: "change_auto_cert"
+        type: 'switch',
+        event: 'change_auto_cert'
     },
     description: '启用自动续签后,系统将会每小时检测一次该域名证书的信息,' +
         '如果距离上次签发已超过1个月,则将执行自动续签。' +
         '<br/>启用前先点击下方「自动申请 Let\'s Encrypt 证书」即可获得证书路径。'
 }, {
-    title: "https 监听端口",
-    dataIndex: "https_listen_port",
+    title: 'https 监听端口',
+    dataIndex: 'https_listen_port',
     edit: {
-        type: "number",
+        type: 'number',
         min: 443
     }
 }, {
-    title: "SSL 证书路径 (ssl_certificate)",
-    dataIndex: "ssl_certificate",
+    title: 'SSL 证书路径 (ssl_certificate)',
+    dataIndex: 'ssl_certificate',
     edit: {
-        type: "input"
+        type: 'input'
     }
 }, {
-    title: "SSL 证书私钥路径 (ssl_certificate_key)",
-    dataIndex: "ssl_certificate_key",
+    title: 'SSL 证书私钥路径 (ssl_certificate_key)',
+    dataIndex: 'ssl_certificate_key',
     edit: {
-        type: "input"
+        type: 'input'
     }
 }]
 

+ 6 - 6
frontend/src/views/domain/methods.js

@@ -8,19 +8,19 @@ const unparse = (text, config) => {
         ssl_certificate: /ssl_certificate[\s](.*);/i,
         ssl_certificate_key: /ssl_certificate_key[\s](.*);/i
     }
-    text = text.replace(/listen[\s](.*);/i, "listen\t"
+    text = text.replace(/listen[\s](.*);/i, 'listen\t'
         + config['http_listen_port'] + ';')
-    text = text.replace(/listen[\s](.*) ssl/i, "listen\t"
+    text = text.replace(/listen[\s](.*) ssl/i, 'listen\t'
         + config['https_listen_port'] + ' ssl')
 
-    text = text.replace(/listen(.*):(.*);/i, "listen\t[::]:"
+    text = text.replace(/listen(.*):(.*);/i, 'listen\t[::]:'
         + config['http_listen_port'] + ';')
-    text = text.replace(/listen(.*):(.*) ssl/i, "listen\t[::]:"
+    text = text.replace(/listen(.*):(.*) ssl/i, 'listen\t[::]:'
         + config['https_listen_port'] + ' ssl')
 
     for (let k in reg) {
-        text = text.replace(new RegExp(reg[k]), k + "\t" +
-            (config[k] !== undefined ? config[k] : " ") + ";")
+        text = text.replace(new RegExp(reg[k]), k + '\t' +
+            (config[k] !== undefined ? config[k] : ' ') + ';')
     }
 
     return text

+ 3 - 2
frontend/src/views/other/About.vue

@@ -1,7 +1,7 @@
 <template>
     <a-card style="text-align: center">
         <div class="logo">
-            <img :src="logo"  alt="logo" />
+            <img :src="logo" alt="logo"/>
         </div>
         <h2>Nginx UI</h2>
         <p>Yet another WebUI for Nginx</p>
@@ -37,7 +37,7 @@ export default {
         async changeUserPower(power) {
             await this.$store.dispatch('update_mock_user', {power: power})
             await this.$api.user.info()
-            await this.$message.success("修改成功")
+            await this.$message.success('修改成功')
         }
     }
 }
@@ -50,6 +50,7 @@ export default {
         max-width: 120px
     }
 }
+
 .egg {
     padding: 10px 0;
 }

+ 1 - 1
frontend/src/views/other/Install.vue

@@ -109,7 +109,7 @@ export default {
             })
         },
     },
-};
+}
 </script>
 <style lang="less">
 .project-title {

+ 3 - 1
frontend/src/views/other/Login.vue

@@ -90,7 +90,7 @@ export default {
             })
         },
     },
-};
+}
 </script>
 <style lang="less">
 .container {
@@ -98,9 +98,11 @@ export default {
     align-items: center;
     justify-content: center;
     height: 100%;
+
     .login-form {
         max-width: 400px;
         width: 80%;
+
         .project-title {
             margin: 50px;
 

+ 13 - 15
frontend/src/views/user/User.vue

@@ -4,19 +4,19 @@
 
 <script>
 
-import StdCurd from "@/components/StdDataDisplay/StdCurd"
+import StdCurd from '@/components/StdDataDisplay/StdCurd'
 
 const columns = [{
-    title: "用户名",
-    dataIndex: "name",
+    title: '用户名',
+    dataIndex: 'name',
     sorter: true,
     pithy: true,
     edit: {
         type: 'input'
     }
 }, {
-    title: "密码",
-    dataIndex: "password",
+    title: '密码',
+    dataIndex: 'password',
     sorter: true,
     pithy: true,
     edit: {
@@ -25,24 +25,24 @@ const columns = [{
     },
     display: false
 }, {
-    title: "创建时间",
-    dataIndex: "created_at",
+    title: '创建时间',
+    dataIndex: 'created_at',
     datetime: true,
     sorter: true,
     pithy: true
 }, {
-    title: "修改时间",
-    dataIndex: "updated_at",
+    title: '修改时间',
+    dataIndex: 'updated_at',
     datetime: true,
     sorter: true,
     pithy: true
 }, {
-    title: "操作",
-    dataIndex: "action"
+    title: '操作',
+    dataIndex: 'action'
 }]
 
 export default {
-    name: "User",
+    name: 'User',
     components: {StdCurd},
     data() {
         return {
@@ -50,9 +50,7 @@ export default {
             columns
         }
     },
-    methods: {
-
-    }
+    methods: {}
 }
 </script>
 

+ 5 - 1
frontend/version.json

@@ -1 +1,5 @@
-{"version":"1.1.0","build_id":8,"total_build":25}
+{
+    "version": "1.1.0",
+    "build_id": 8,
+    "total_build": 25
+}

+ 615 - 2
frontend/yarn.lock

@@ -2,6 +2,13 @@
 # yarn lockfile v1
 
 
+"@ampproject/remapping@^2.1.0":
+  version "2.1.2"
+  resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.1.2.tgz#4edca94973ded9630d20101cd8559cedb8d8bd34"
+  integrity sha512-hoyByceqwKirw7w3Z7gnIIZC3Wx3J484Y3L/cMpXFbr7d9ZQj2mODrirNzcJa+SM3UlpWXYvKV4RlRpFXlWgXg==
+  dependencies:
+    "@jridgewell/trace-mapping" "^0.3.0"
+
 "@ant-design/colors@^3.1.0":
   version "3.2.2"
   resolved "https://registry.yarnpkg.com/@ant-design/colors/-/colors-3.2.2.tgz#5ad43d619e911f3488ebac303d606e66a8423903"
@@ -68,6 +75,27 @@
     semver "^6.3.0"
     source-map "^0.5.0"
 
+"@babel/core@^7.11.6":
+  version "7.17.5"
+  resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.17.5.tgz#6cd2e836058c28f06a4ca8ee7ed955bbf37c8225"
+  integrity sha512-/BBMw4EvjmyquN5O+t5eh0+YqB3XXJkYD2cjKpYtWOfFy4lQ4UozNSmxAcWT8r2XtZs0ewG+zrfsqeR15i1ajA==
+  dependencies:
+    "@ampproject/remapping" "^2.1.0"
+    "@babel/code-frame" "^7.16.7"
+    "@babel/generator" "^7.17.3"
+    "@babel/helper-compilation-targets" "^7.16.7"
+    "@babel/helper-module-transforms" "^7.16.7"
+    "@babel/helpers" "^7.17.2"
+    "@babel/parser" "^7.17.3"
+    "@babel/template" "^7.16.7"
+    "@babel/traverse" "^7.17.3"
+    "@babel/types" "^7.17.0"
+    convert-source-map "^1.7.0"
+    debug "^4.1.0"
+    gensync "^1.0.0-beta.2"
+    json5 "^2.1.2"
+    semver "^6.3.0"
+
 "@babel/core@^7.14.3":
   version "7.16.12"
   resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.16.12.tgz#5edc53c1b71e54881315923ae2aedea2522bb784"
@@ -107,6 +135,15 @@
     jsesc "^2.5.1"
     source-map "^0.5.0"
 
+"@babel/generator@^7.17.3":
+  version "7.17.3"
+  resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.17.3.tgz#a2c30b0c4f89858cb87050c3ffdfd36bdf443200"
+  integrity sha512-+R6Dctil/MgUsZsZAkYgK+ADNSZzJRRy0TvY65T71z/CR854xHQ1EweBYXdfT+HNeN7w0cSJJEzgxZMv40pxsg==
+  dependencies:
+    "@babel/types" "^7.17.0"
+    jsesc "^2.5.1"
+    source-map "^0.5.0"
+
 "@babel/helper-annotate-as-pure@^7.12.13":
   version "7.12.13"
   resolved "https://registry.npm.taobao.org/@babel/helper-annotate-as-pure/download/@babel/helper-annotate-as-pure-7.12.13.tgz?cache=0&sync_timestamp=1612314684390&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fhelper-annotate-as-pure%2Fdownload%2F%40babel%2Fhelper-annotate-as-pure-7.12.13.tgz#0f58e86dfc4bb3b1fcd7db806570e177d439b6ab"
@@ -412,6 +449,15 @@
     "@babel/traverse" "^7.16.7"
     "@babel/types" "^7.16.7"
 
+"@babel/helpers@^7.17.2":
+  version "7.17.2"
+  resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.17.2.tgz#23f0a0746c8e287773ccd27c14be428891f63417"
+  integrity sha512-0Qu7RLR1dILozr/6M0xgj+DFPmi6Bnulgm9M8BVa9ZCWxDqlSnqt3cf8IDPB5m45sVXUZ0kuQAgUrdSFFH79fQ==
+  dependencies:
+    "@babel/template" "^7.16.7"
+    "@babel/traverse" "^7.17.0"
+    "@babel/types" "^7.17.0"
+
 "@babel/highlight@^7.12.13":
   version "7.13.10"
   resolved "https://registry.npm.taobao.org/@babel/highlight/download/@babel/highlight-7.13.10.tgz#a8b2a66148f5b27d666b15d81774347a731d52d1"
@@ -440,6 +486,11 @@
   resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.16.12.tgz#9474794f9a650cf5e2f892444227f98e28cdf8b6"
   integrity sha512-VfaV15po8RiZssrkPweyvbGVSe4x2y+aciFCgn0n0/SJMR22cwofRV1mtnJQYcSB1wUTaA/X1LnA3es66MCO5A==
 
+"@babel/parser@^7.16.4", "@babel/parser@^7.17.3", "@babel/parser@^7.6.0", "@babel/parser@^7.9.6":
+  version "7.17.3"
+  resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.17.3.tgz#b07702b982990bf6fdc1da5049a23fece4c5c3d0"
+  integrity sha512-7yJPvPV+ESz2IUTPbOL+YkIGyCqOyNIzdguKQuJGnH7bg1WTIifuM21YqokFt/THWh1AkCRn9IgoykTRCBVpzA==
+
 "@babel/plugin-proposal-async-generator-functions@^7.13.8":
   version "7.13.8"
   resolved "https://registry.npm.taobao.org/@babel/plugin-proposal-async-generator-functions/download/@babel/plugin-proposal-async-generator-functions-7.13.8.tgz?cache=0&sync_timestamp=1614382839074&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fplugin-proposal-async-generator-functions%2Fdownload%2F%40babel%2Fplugin-proposal-async-generator-functions-7.13.8.tgz#87aacb574b3bc4b5603f6fe41458d72a5a2ec4b1"
@@ -1128,6 +1179,22 @@
     debug "^4.1.0"
     globals "^11.1.0"
 
+"@babel/traverse@^7.17.0", "@babel/traverse@^7.17.3":
+  version "7.17.3"
+  resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.17.3.tgz#0ae0f15b27d9a92ba1f2263358ea7c4e7db47b57"
+  integrity sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw==
+  dependencies:
+    "@babel/code-frame" "^7.16.7"
+    "@babel/generator" "^7.17.3"
+    "@babel/helper-environment-visitor" "^7.16.7"
+    "@babel/helper-function-name" "^7.16.7"
+    "@babel/helper-hoist-variables" "^7.16.7"
+    "@babel/helper-split-export-declaration" "^7.16.7"
+    "@babel/parser" "^7.17.3"
+    "@babel/types" "^7.17.0"
+    debug "^4.1.0"
+    globals "^11.1.0"
+
 "@babel/types@^7.0.0", "@babel/types@^7.12.1", "@babel/types@^7.12.13", "@babel/types@^7.13.0", "@babel/types@^7.4.4", "@babel/types@^7.7.0":
   version "7.13.0"
   resolved "https://registry.npm.taobao.org/@babel/types/download/@babel/types-7.13.0.tgz?cache=0&sync_timestamp=1614034208093&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Ftypes%2Fdownload%2F%40babel%2Ftypes-7.13.0.tgz#74424d2816f0171b4100f0ab34e9a374efdf7f80"
@@ -1145,6 +1212,14 @@
     "@babel/helper-validator-identifier" "^7.16.7"
     to-fast-properties "^2.0.0"
 
+"@babel/types@^7.17.0", "@babel/types@^7.6.1", "@babel/types@^7.9.6":
+  version "7.17.0"
+  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.17.0.tgz#a826e368bccb6b3d84acd76acad5c0d87342390b"
+  integrity sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==
+  dependencies:
+    "@babel/helper-validator-identifier" "^7.16.7"
+    to-fast-properties "^2.0.0"
+
 "@hapi/address@2.x.x":
   version "2.1.4"
   resolved "https://registry.npm.taobao.org/@hapi/address/download/@hapi/address-2.1.4.tgz?cache=0&sync_timestamp=1603524710662&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40hapi%2Faddress%2Fdownload%2F%40hapi%2Faddress-2.1.4.tgz#5d67ed43f3fd41a69d4b9ff7b56e7c0d1d0a81e5"
@@ -1186,6 +1261,24 @@
     cssnano-preset-default "^4.0.0"
     postcss "^7.0.0"
 
+"@jridgewell/resolve-uri@^3.0.3":
+  version "3.0.5"
+  resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz#68eb521368db76d040a6315cdb24bf2483037b9c"
+  integrity sha512-VPeQ7+wH0itvQxnG+lIzWgkysKIr3L9sslimFW55rHMdGu/qCQ5z5h9zq4gI8uBtqkpHhsF4Z/OwExufUCThew==
+
+"@jridgewell/sourcemap-codec@^1.4.10":
+  version "1.4.11"
+  resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz#771a1d8d744eeb71b6adb35808e1a6c7b9b8c8ec"
+  integrity sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg==
+
+"@jridgewell/trace-mapping@^0.3.0":
+  version "0.3.4"
+  resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.4.tgz#f6a0832dffd5b8a6aaa633b7d9f8e8e94c83a0c3"
+  integrity sha512-vFv9ttIedivx0ux3QSjhgtCVjPZd5l46ZOMDSCwnH1yUO2e964gO8LZGyv2QkqcgR6TnBU1v+1IFqmeoG+0UJQ==
+  dependencies:
+    "@jridgewell/resolve-uri" "^3.0.3"
+    "@jridgewell/sourcemap-codec" "^1.4.10"
+
 "@mrmlnc/readdir-enhanced@^2.2.1":
   version "2.2.1"
   resolved "https://registry.npm.taobao.org/@mrmlnc/readdir-enhanced/download/@mrmlnc/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde"
@@ -1968,6 +2061,48 @@
     semver "^6.1.0"
     strip-ansi "^6.0.0"
 
+"@vue/compiler-core@3.2.31":
+  version "3.2.31"
+  resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.2.31.tgz#d38f06c2cf845742403b523ab4596a3fda152e89"
+  integrity sha512-aKno00qoA4o+V/kR6i/pE+aP+esng5siNAVQ422TkBNM6qA4veXiZbSe8OTXHXquEi/f6Akc+nLfB4JGfe4/WQ==
+  dependencies:
+    "@babel/parser" "^7.16.4"
+    "@vue/shared" "3.2.31"
+    estree-walker "^2.0.2"
+    source-map "^0.6.1"
+
+"@vue/compiler-dom@3.2.31":
+  version "3.2.31"
+  resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.31.tgz#b1b7dfad55c96c8cc2b919cd7eb5fd7e4ddbf00e"
+  integrity sha512-60zIlFfzIDf3u91cqfqy9KhCKIJgPeqxgveH2L+87RcGU/alT6BRrk5JtUso0OibH3O7NXuNOQ0cDc9beT0wrg==
+  dependencies:
+    "@vue/compiler-core" "3.2.31"
+    "@vue/shared" "3.2.31"
+
+"@vue/compiler-sfc@^3.0.0":
+  version "3.2.31"
+  resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.31.tgz#d02b29c3fe34d599a52c5ae1c6937b4d69f11c2f"
+  integrity sha512-748adc9msSPGzXgibHiO6T7RWgfnDcVQD+VVwYgSsyyY8Ans64tALHZANrKtOzvkwznV/F4H7OAod/jIlp/dkQ==
+  dependencies:
+    "@babel/parser" "^7.16.4"
+    "@vue/compiler-core" "3.2.31"
+    "@vue/compiler-dom" "3.2.31"
+    "@vue/compiler-ssr" "3.2.31"
+    "@vue/reactivity-transform" "3.2.31"
+    "@vue/shared" "3.2.31"
+    estree-walker "^2.0.2"
+    magic-string "^0.25.7"
+    postcss "^8.1.10"
+    source-map "^0.6.1"
+
+"@vue/compiler-ssr@3.2.31":
+  version "3.2.31"
+  resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.31.tgz#4fa00f486c9c4580b40a4177871ebbd650ecb99c"
+  integrity sha512-mjN0rqig+A8TVDnsGPYJM5dpbjlXeHUm2oZHZwGyMYiGT/F4fhJf/cXy8QpjnLQK4Y9Et4GWzHn9PS8AHUnSkw==
+  dependencies:
+    "@vue/compiler-dom" "3.2.31"
+    "@vue/shared" "3.2.31"
+
 "@vue/component-compiler-utils@^3.1.0", "@vue/component-compiler-utils@^3.1.2":
   version "3.2.0"
   resolved "https://registry.npm.taobao.org/@vue/component-compiler-utils/download/@vue/component-compiler-utils-3.2.0.tgz?cache=0&sync_timestamp=1595427628913&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40vue%2Fcomponent-compiler-utils%2Fdownload%2F%40vue%2Fcomponent-compiler-utils-3.2.0.tgz#8f85182ceed28e9b3c75313de669f83166d11e5d"
@@ -1989,6 +2124,22 @@
   resolved "https://registry.npm.taobao.org/@vue/preload-webpack-plugin/download/@vue/preload-webpack-plugin-1.1.2.tgz?cache=0&sync_timestamp=1613214843074&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40vue%2Fpreload-webpack-plugin%2Fdownload%2F%40vue%2Fpreload-webpack-plugin-1.1.2.tgz#ceb924b4ecb3b9c43871c7a429a02f8423e621ab"
   integrity sha1-zrkktOyzucQ4ccekKaAvhCPmIas=
 
+"@vue/reactivity-transform@3.2.31":
+  version "3.2.31"
+  resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.2.31.tgz#0f5b25c24e70edab2b613d5305c465b50fc00911"
+  integrity sha512-uS4l4z/W7wXdI+Va5pgVxBJ345wyGFKvpPYtdSgvfJfX/x2Ymm6ophQlXXB6acqGHtXuBqNyyO3zVp9b1r0MOA==
+  dependencies:
+    "@babel/parser" "^7.16.4"
+    "@vue/compiler-core" "3.2.31"
+    "@vue/shared" "3.2.31"
+    estree-walker "^2.0.2"
+    magic-string "^0.25.7"
+
+"@vue/shared@3.2.31":
+  version "3.2.31"
+  resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.31.tgz#c90de7126d833dcd3a4c7534d534be2fb41faa4e"
+  integrity sha512-ymN2pj6zEjiKJZbrf98UM2pfDd6F2H7ksKw7NDt/ZZ1fh5Ei39X5tABugtT03ZRlWd9imccoK0hE8hpjpU7irQ==
+
 "@vue/web-component-wrapper@^1.2.0":
   version "1.3.0"
   resolved "https://registry.npm.taobao.org/@vue/web-component-wrapper/download/@vue/web-component-wrapper-1.3.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40vue%2Fweb-component-wrapper%2Fdownload%2F%40vue%2Fweb-component-wrapper-1.3.0.tgz#b6b40a7625429d2bd7c2281ddba601ed05dc7f1a"
@@ -2162,16 +2313,56 @@ accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7:
     mime-types "~2.1.24"
     negotiator "0.6.2"
 
+acorn-class-fields@^0.3.7:
+  version "0.3.7"
+  resolved "https://registry.yarnpkg.com/acorn-class-fields/-/acorn-class-fields-0.3.7.tgz#a35122f3cc6ad2bb33b1857e79215677fcfdd720"
+  integrity sha512-jdUWSFce0fuADUljmExz4TWpPkxmRW/ZCPRqeeUzbGf0vFUcpQYbyq52l75qGd0oSwwtAepeL6hgb/naRgvcKQ==
+  dependencies:
+    acorn-private-class-elements "^0.2.7"
+
 acorn-jsx@^5.2.0:
   version "5.3.1"
   resolved "https://registry.npm.taobao.org/acorn-jsx/download/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b"
   integrity sha1-/IZh4Rt6wVOcR9v+oucrOvNNJns=
 
+acorn-private-class-elements@^0.2.7:
+  version "0.2.7"
+  resolved "https://registry.yarnpkg.com/acorn-private-class-elements/-/acorn-private-class-elements-0.2.7.tgz#b14902c705bcff267adede1c9f61c1a317ef95d2"
+  integrity sha512-+GZH2wOKNZOBI4OOPmzpo4cs6mW297sn6fgIk1dUI08jGjhAaEwvC39mN2gJAg2lmAQJ1rBkFqKWonL3Zz6PVA==
+
+acorn-private-methods@^0.3.3:
+  version "0.3.3"
+  resolved "https://registry.yarnpkg.com/acorn-private-methods/-/acorn-private-methods-0.3.3.tgz#724414ce5b2fec733089d73a5cbba8f7beff75b1"
+  integrity sha512-46oeEol3YFvLSah5m9hGMlNpxDBCEkdceJgf01AjqKYTK9r6HexKs2rgSbLK81pYjZZMonhftuUReGMlbbv05w==
+  dependencies:
+    acorn-private-class-elements "^0.2.7"
+
+acorn-stage3@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/acorn-stage3/-/acorn-stage3-4.0.0.tgz#e8b98ae2a9991be0ba1745b5b626211086b435a8"
+  integrity sha512-BR+LaADtA6GTB5prkNqWmlmCLYmkyW0whvSxdHhbupTaro2qBJ95fJDEiRLPUmiACGHPaYyeH9xmNJWdGfXRQw==
+  dependencies:
+    acorn-class-fields "^0.3.7"
+    acorn-private-methods "^0.3.3"
+    acorn-static-class-features "^0.2.4"
+
+acorn-static-class-features@^0.2.4:
+  version "0.2.4"
+  resolved "https://registry.yarnpkg.com/acorn-static-class-features/-/acorn-static-class-features-0.2.4.tgz#a0f5261dd483f25196716854f2d7652a1deb39ee"
+  integrity sha512-5X4mpYq5J3pdndLmIB0+WtFd/mKWnNYpuTlTzj32wUu/PMmEGOiayQ5UrqgwdBNiaZBtDDh5kddpP7Yg2QaQYA==
+  dependencies:
+    acorn-private-class-elements "^0.2.7"
+
 acorn-walk@^7.1.1:
   version "7.2.0"
   resolved "https://registry.npm.taobao.org/acorn-walk/download/acorn-walk-7.2.0.tgz?cache=0&sync_timestamp=1611560713023&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Facorn-walk%2Fdownload%2Facorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc"
   integrity sha1-DeiJpgEgOQmw++B7iTjcIdLpZ7w=
 
+acorn-walk@^8.0.0:
+  version "8.2.0"
+  resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1"
+  integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==
+
 acorn@^6.4.1:
   version "6.4.2"
   resolved "https://registry.npm.taobao.org/acorn/download/acorn-6.4.2.tgz#35866fd710528e92de10cf06016498e47e39e1e6"
@@ -2422,6 +2613,11 @@ arrify@^1.0.1:
   resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
   integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=
 
+asap@~2.0.3:
+  version "2.0.6"
+  resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
+  integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=
+
 asn1.js@^5.2.0:
   version "5.4.1"
   resolved "https://registry.npm.taobao.org/asn1.js/download/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07"
@@ -2439,6 +2635,11 @@ asn1@~0.2.3:
   dependencies:
     safer-buffer "~2.1.0"
 
+assert-never@^1.2.1:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/assert-never/-/assert-never-1.2.1.tgz#11f0e363bf146205fb08193b5c7b90f4d1cf44fe"
+  integrity sha512-TaTivMB6pYI1kXwrFlEhLeGfOqoDNdTxjCdwRfFFkEA30Eu+k48W34nlok2EYWJfFFzqaEmichdNM7th6M5HNw==
+
 assert-plus@1.0.0, assert-plus@^1.0.0:
   version "1.0.0"
   resolved "https://registry.npm.taobao.org/assert-plus/download/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
@@ -2603,6 +2804,13 @@ babel-runtime@6.x, babel-runtime@^6.23.0, babel-runtime@^6.26.0:
     core-js "^2.4.0"
     regenerator-runtime "^0.11.0"
 
+babel-walk@3.0.0-canary-5:
+  version "3.0.0-canary-5"
+  resolved "https://registry.yarnpkg.com/babel-walk/-/babel-walk-3.0.0-canary-5.tgz#f66ecd7298357aee44955f235a6ef54219104b11"
+  integrity sha512-GAwkz0AihzY5bkwIY5QDR+LvsRQgB/B+1foMPvi0FZPMl5fjD7ICiznUiBdLYMH1QYe6vqu4gWYytZOccLouFw==
+  dependencies:
+    "@babel/types" "^7.9.6"
+
 balanced-match@^1.0.0:
   version "1.0.0"
   resolved "https://registry.npm.taobao.org/balanced-match/download/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
@@ -2876,6 +3084,11 @@ builtin-status-codes@^3.0.0:
   resolved "https://registry.npm.taobao.org/builtin-status-codes/download/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8"
   integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=
 
+buntis@0.2.1:
+  version "0.2.1"
+  resolved "https://registry.yarnpkg.com/buntis/-/buntis-0.2.1.tgz#a043aabc7d64f2243bfaa53e34e999c2dd790e82"
+  integrity sha512-5wszfQlsqJmZrfxpPkO5yQcEoBAmfUYlXxXU/IM6PhPZ8DMnMMJQ9rvAHfe5WZmnB6E1IoJYylFfTaf1e2FJbQ==
+
 bytes@3.0.0:
   version "3.0.0"
   resolved "https://registry.npm.taobao.org/bytes/download/bytes-3.0.0.tgz?cache=0&sync_timestamp=1589682741197&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fbytes%2Fdownload%2Fbytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
@@ -3075,6 +3288,13 @@ chalk@^4.0.0, chalk@^4.1.0:
     ansi-styles "^4.1.0"
     supports-color "^7.1.0"
 
+character-parser@^2.2.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/character-parser/-/character-parser-2.2.0.tgz#c7ce28f36d4bcd9744e5ffc2c5fcde1c73261fc0"
+  integrity sha1-x84o821LzZdE5f/CxfzeHHMmH8A=
+  dependencies:
+    is-regex "^1.0.3"
+
 chardet@^0.7.0:
   version "0.7.0"
   resolved "https://registry.npm.taobao.org/chardet/download/chardet-0.7.0.tgz?cache=0&sync_timestamp=1601032454247&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fchardet%2Fdownload%2Fchardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
@@ -3108,6 +3328,30 @@ check-types@^8.0.3:
   resolved "https://registry.npm.taobao.org/check-types/download/check-types-8.0.3.tgz#3356cca19c889544f2d7a95ed49ce508a0ecf552"
   integrity sha1-M1bMoZyIlUTy16le1JzlCKDs9VI=
 
+cheerio-select@^1.5.0:
+  version "1.5.0"
+  resolved "https://registry.yarnpkg.com/cheerio-select/-/cheerio-select-1.5.0.tgz#faf3daeb31b17c5e1a9dabcee288aaf8aafa5823"
+  integrity sha512-qocaHPv5ypefh6YNxvnbABM07KMxExbtbfuJoIie3iZXX1ERwYmJcIiRrr9H05ucQP1k28dav8rpdDgjQd8drg==
+  dependencies:
+    css-select "^4.1.3"
+    css-what "^5.0.1"
+    domelementtype "^2.2.0"
+    domhandler "^4.2.0"
+    domutils "^2.7.0"
+
+cheerio@^1.0.0-rc.3:
+  version "1.0.0-rc.10"
+  resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.10.tgz#2ba3dcdfcc26e7956fc1f440e61d51c643379f3e"
+  integrity sha512-g0J0q/O6mW8z5zxQ3A8E8J1hUgp4SMOvEoW/x84OwyHKe/Zccz83PVT4y5Crcr530FV6NgmKI1qvGTKVl9XXVw==
+  dependencies:
+    cheerio-select "^1.5.0"
+    dom-serializer "^1.3.2"
+    domhandler "^4.2.0"
+    htmlparser2 "^6.1.0"
+    parse5 "^6.0.1"
+    parse5-htmlparser2-tree-adapter "^6.0.1"
+    tslib "^2.2.0"
+
 chokidar@^2.1.8:
   version "2.1.8"
   resolved "https://registry.npm.taobao.org/chokidar/download/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917"
@@ -3454,6 +3698,14 @@ consolidate@^0.15.1:
   dependencies:
     bluebird "^3.1.1"
 
+constantinople@^4.0.1:
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/constantinople/-/constantinople-4.0.1.tgz#0def113fa0e4dc8de83331a5cf79c8b325213151"
+  integrity sha512-vCrqcSIq4//Gx74TXXCGnHpulY1dskqLTFGDmhrGxzeXL8lF8kvXv6mpNWlJj1uD4DW23D4ljAqbY4RRaaUZIw==
+  dependencies:
+    "@babel/parser" "^7.6.0"
+    "@babel/types" "^7.6.1"
+
 constants-browserify@^1.0.0:
   version "1.0.0"
   resolved "https://registry.npm.taobao.org/constants-browserify/download/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75"
@@ -3687,6 +3939,17 @@ css-select@^2.0.0, css-select@^2.0.2:
     domutils "^1.7.0"
     nth-check "^1.0.2"
 
+css-select@^4.1.3:
+  version "4.2.1"
+  resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.2.1.tgz#9e665d6ae4c7f9d65dbe69d0316e3221fb274cdd"
+  integrity sha512-/aUslKhzkTNCQUB2qTX84lVmfia9NyjP3WpDGtj/WxhwBzWBYUV3DgUpurHTme8UTPcPlAD1DJ+b0nN/t50zDQ==
+  dependencies:
+    boolbase "^1.0.0"
+    css-what "^5.1.0"
+    domhandler "^4.3.0"
+    domutils "^2.8.0"
+    nth-check "^2.0.1"
+
 css-tree@1.0.0-alpha.37:
   version "1.0.0-alpha.37"
   resolved "https://registry.npm.taobao.org/css-tree/download/css-tree-1.0.0-alpha.37.tgz#98bebd62c4c1d9f960ec340cf9f7522e30709a22"
@@ -3708,6 +3971,11 @@ css-what@^3.2.1:
   resolved "https://registry.npm.taobao.org/css-what/download/css-what-3.4.2.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcss-what%2Fdownload%2Fcss-what-3.4.2.tgz#ea7026fcb01777edbde52124e21f327e7ae950e4"
   integrity sha1-6nAm/LAXd+295SEk4h8yfnrpUOQ=
 
+css-what@^5.0.1, css-what@^5.1.0:
+  version "5.1.0"
+  resolved "https://registry.yarnpkg.com/css-what/-/css-what-5.1.0.tgz#3f7b707aadf633baf62c2ceb8579b545bb40f7fe"
+  integrity sha512-arSMRWIIFY0hV8pIxZMEfmMI47Wj3R/aWpZDDxWYCPEiOMv6tfOrnpDtgxBYPEQD4V0Y/958+1TdC3iWTFcUPw==
+
 cssesc@^3.0.0:
   version "3.0.0"
   resolved "https://registry.npm.taobao.org/cssesc/download/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
@@ -4016,6 +4284,11 @@ doctrine@^3.0.0:
   dependencies:
     esutils "^2.0.2"
 
+doctypes@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/doctypes/-/doctypes-1.1.0.tgz#ea80b106a87538774e8a3a4a5afe293de489e0a9"
+  integrity sha1-6oCxBqh1OHdOijpKWv4pPeSJ4Kk=
+
 dom-align@^1.10.4:
   version "1.12.0"
   resolved "https://registry.yarnpkg.com/dom-align/-/dom-align-1.12.0.tgz#56fb7156df0b91099830364d2d48f88963f5a29c"
@@ -4053,6 +4326,15 @@ dom-serializer@0:
     domelementtype "^2.0.1"
     entities "^2.0.0"
 
+dom-serializer@^1.0.1, dom-serializer@^1.3.2:
+  version "1.3.2"
+  resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.3.2.tgz#6206437d32ceefaec7161803230c7a20bc1b4d91"
+  integrity sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==
+  dependencies:
+    domelementtype "^2.0.1"
+    domhandler "^4.2.0"
+    entities "^2.0.0"
+
 domain-browser@^1.1.1:
   version "1.2.0"
   resolved "https://registry.npm.taobao.org/domain-browser/download/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda"
@@ -4068,6 +4350,11 @@ domelementtype@^2.0.1:
   resolved "https://registry.npm.taobao.org/domelementtype/download/domelementtype-2.1.0.tgz#a851c080a6d1c3d94344aed151d99f669edf585e"
   integrity sha1-qFHAgKbRw9lDRK7RUdmfZp7fWF4=
 
+domelementtype@^2.2.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.2.0.tgz#9a0b6c2782ed6a1c7323d42267183df9bd8b1d57"
+  integrity sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==
+
 domhandler@^2.3.0:
   version "2.4.2"
   resolved "https://registry.npm.taobao.org/domhandler/download/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803"
@@ -4075,6 +4362,13 @@ domhandler@^2.3.0:
   dependencies:
     domelementtype "1"
 
+domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.0:
+  version "4.3.0"
+  resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.0.tgz#16c658c626cf966967e306f966b431f77d4a5626"
+  integrity sha512-fC0aXNQXqKSFTr2wDNZDhsEYjCiYsDWl3D01kwt25hm1YIPyDGHvvi3rw+PLqHAl/m71MaiF7d5zvBr0p5UB2g==
+  dependencies:
+    domelementtype "^2.2.0"
+
 domutils@^1.5.1, domutils@^1.7.0:
   version "1.7.0"
   resolved "https://registry.npm.taobao.org/domutils/download/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a"
@@ -4083,6 +4377,15 @@ domutils@^1.5.1, domutils@^1.7.0:
     dom-serializer "0"
     domelementtype "1"
 
+domutils@^2.5.2, domutils@^2.7.0, domutils@^2.8.0:
+  version "2.8.0"
+  resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135"
+  integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==
+  dependencies:
+    dom-serializer "^1.0.1"
+    domelementtype "^2.2.0"
+    domhandler "^4.2.0"
+
 dot-prop@^5.2.0:
   version "5.3.0"
   resolved "https://registry.npm.taobao.org/dot-prop/download/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88"
@@ -4120,6 +4423,25 @@ easy-stack@^1.0.1:
   resolved "https://registry.npm.taobao.org/easy-stack/download/easy-stack-1.0.1.tgz#8afe4264626988cabb11f3c704ccd0c835411066"
   integrity sha1-iv5CZGJpiMq7EfPHBMzQyDVBEGY=
 
+easygettext@^2.17.0:
+  version "2.17.0"
+  resolved "https://registry.yarnpkg.com/easygettext/-/easygettext-2.17.0.tgz#af55ec030b9aabf514aadf0e32383a6f45cc43af"
+  integrity sha512-QmMWIu6l83SW2QuEFd4GIDrTey0qOn0haTOMt4NdPKPHEBihAqdo9HHYRDHiPg/msZSKaye7qDOLAcqHlmfo+g==
+  dependencies:
+    "@babel/core" "^7.11.6"
+    acorn "^7.4.0"
+    acorn-stage3 "^4.0.0"
+    acorn-walk "^8.0.0"
+    buntis "0.2.1"
+    cheerio "^1.0.0-rc.3"
+    estree-walker "^2.0.1"
+    flow-remove-types "^2.135.0"
+    minimist "^1.2.5"
+    pofile "^1.1.0"
+  optionalDependencies:
+    "@vue/compiler-sfc" "^3.0.0"
+    pug "^3.0.2"
+
 ecc-jsbn@~0.1.1:
   version "0.1.2"
   resolved "https://registry.npm.taobao.org/ecc-jsbn/download/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9"
@@ -4418,6 +4740,11 @@ estraverse@^5.1.0, estraverse@^5.2.0:
   resolved "https://registry.npm.taobao.org/estraverse/download/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880"
   integrity sha1-MH30JUfmzHMk088DwVXVzbjFOIA=
 
+estree-walker@^2.0.1, estree-walker@^2.0.2:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac"
+  integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==
+
 esutils@^2.0.2:
   version "2.0.3"
   resolved "https://registry.npm.taobao.org/esutils/download/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
@@ -4779,6 +5106,20 @@ flatted@^3.0.5:
   resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.1.1.tgz#c4b489e80096d9df1dfc97c79871aea7c617c469"
   integrity sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==
 
+flow-parser@^0.172.0:
+  version "0.172.0"
+  resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.172.0.tgz#9f5ee62ebf6bad689d5de0b6b98445d8cf030a2f"
+  integrity sha512-WWqgvuJgD9Y1n2su9D73m0g5kQ4XVl8Dwk6DeW5V6bjt4XMtVLzSHg35s3iiZOvShY+7w7l8FzlK81PGXRcIYQ==
+
+flow-remove-types@^2.135.0:
+  version "2.172.0"
+  resolved "https://registry.yarnpkg.com/flow-remove-types/-/flow-remove-types-2.172.0.tgz#b8897ef1b121aa100a75572902461ee0b2a09466"
+  integrity sha512-EfHtwB48kgwQnU1ucNWVmEh+z2ubek9NWLkNb+9djaxRYijHl/YhuGut590ObHlz1wPaimUZcNyGi3kjBfSWZw==
+  dependencies:
+    flow-parser "^0.172.0"
+    pirates "^3.0.2"
+    vlq "^0.2.1"
+
 flush-write-stream@^1.0.0:
   version "1.1.1"
   resolved "https://registry.npm.taobao.org/flush-write-stream/download/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8"
@@ -5141,6 +5482,13 @@ has-symbols@^1.0.0, has-symbols@^1.0.1, has-symbols@^1.0.2:
   resolved "https://registry.npm.taobao.org/has-symbols/download/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423"
   integrity sha1-Fl0wcMADCXUqEjakeTMeOsVvFCM=
 
+has-tostringtag@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25"
+  integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==
+  dependencies:
+    has-symbols "^1.0.2"
+
 has-unicode@^2.0.0:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
@@ -5335,6 +5683,16 @@ htmlparser2@^3.10.1:
     inherits "^2.0.1"
     readable-stream "^3.1.1"
 
+htmlparser2@^6.1.0:
+  version "6.1.0"
+  resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.1.0.tgz#c4d762b6c3371a05dbe65e94ae43a9f845fb8fb7"
+  integrity sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==
+  dependencies:
+    domelementtype "^2.0.1"
+    domhandler "^4.0.0"
+    domutils "^2.5.2"
+    entities "^2.0.0"
+
 http-deceiver@^1.2.7:
   version "1.2.7"
   resolved "https://registry.npm.taobao.org/http-deceiver/download/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87"
@@ -5703,6 +6061,13 @@ is-core-module@^2.5.0:
   dependencies:
     has "^1.0.3"
 
+is-core-module@^2.8.1:
+  version "2.8.1"
+  resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.1.tgz#f59fdfca701d5879d0a6b100a40aa1560ce27211"
+  integrity sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==
+  dependencies:
+    has "^1.0.3"
+
 is-data-descriptor@^0.1.4:
   version "0.1.4"
   resolved "https://registry.npm.taobao.org/is-data-descriptor/download/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56"
@@ -5750,6 +6115,14 @@ is-docker@^2.0.0:
   resolved "https://registry.npm.taobao.org/is-docker/download/is-docker-2.1.1.tgz#4125a88e44e450d384e09047ede71adc2d144156"
   integrity sha1-QSWojkTkUNOE4JBH7eca3C0UQVY=
 
+is-expression@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/is-expression/-/is-expression-4.0.0.tgz#c33155962abf21d0afd2552514d67d2ec16fd2ab"
+  integrity sha512-zMIXX63sxzG3XrkHkrAPvm/OVZVSCPNkwMHU8oTX7/U3AL78I0QXCEICXUM13BIa8TYGZ68PiTKfQz3yaTNr4A==
+  dependencies:
+    acorn "^7.1.1"
+    object-assign "^4.1.1"
+
 is-extendable@^0.1.0, is-extendable@^0.1.1:
   version "0.1.1"
   resolved "https://registry.npm.taobao.org/is-extendable/download/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89"
@@ -5861,6 +6234,19 @@ is-plain-object@^2.0.3, is-plain-object@^2.0.4:
   dependencies:
     isobject "^3.0.1"
 
+is-promise@^2.0.0:
+  version "2.2.2"
+  resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1"
+  integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==
+
+is-regex@^1.0.3:
+  version "1.1.4"
+  resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958"
+  integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==
+  dependencies:
+    call-bind "^1.0.2"
+    has-tostringtag "^1.0.0"
+
 is-regex@^1.0.4, is-regex@^1.1.2:
   version "1.1.2"
   resolved "https://registry.npm.taobao.org/is-regex/download/is-regex-1.1.2.tgz?cache=0&sync_timestamp=1612217695164&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fis-regex%2Fdownload%2Fis-regex-1.1.2.tgz#81c8ebde4db142f2cf1c53fc86d6a45788266251"
@@ -5992,6 +6378,11 @@ js-queue@2.0.2:
   dependencies:
     easy-stack "^1.0.1"
 
+js-stringify@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/js-stringify/-/js-stringify-1.0.2.tgz#1736fddfd9724f28a3682adc6230ae7e4e9679db"
+  integrity sha1-Fzb939lyTyijaCrcYjCufk6Weds=
+
 "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
@@ -6098,6 +6489,14 @@ jsprim@^1.2.2:
     json-schema "0.2.3"
     verror "1.10.0"
 
+jstransformer@1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/jstransformer/-/jstransformer-1.0.0.tgz#ed8bf0921e2f3f1ed4d5c1a44f68709ed24722c3"
+  integrity sha1-7Yvwkh4vPx7U1cGkT2hwntJHIsM=
+  dependencies:
+    is-promise "^2.0.0"
+    promise "^7.0.1"
+
 killable@^1.0.1:
   version "1.0.1"
   resolved "https://registry.npm.taobao.org/killable/download/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892"
@@ -6340,6 +6739,13 @@ lru-cache@^6.0.0:
   dependencies:
     yallist "^4.0.0"
 
+magic-string@^0.25.7:
+  version "0.25.7"
+  resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051"
+  integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==
+  dependencies:
+    sourcemap-codec "^1.4.4"
+
 make-dir@^2.0.0, make-dir@^2.1.0:
   version "2.1.0"
   resolved "https://registry.npm.taobao.org/make-dir/download/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5"
@@ -6726,6 +7132,11 @@ nan@^2.13.2:
   resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee"
   integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==
 
+nanoid@^3.2.0:
+  version "3.3.1"
+  resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.1.tgz#6347a18cac88af88f58af0b3594b723d5e99bb35"
+  integrity sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==
+
 nanomatch@^1.2.9:
   version "1.2.13"
   resolved "https://registry.npm.taobao.org/nanomatch/download/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119"
@@ -6846,6 +7257,11 @@ node-libs-browser@^2.2.1:
     util "^0.11.0"
     vm-browserify "^1.0.1"
 
+node-modules-regexp@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40"
+  integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=
+
 node-releases@^1.1.75:
   version "1.1.75"
   resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.75.tgz#6dd8c876b9897a1b8e5a02de26afa79bb54ebbfe"
@@ -6977,6 +7393,13 @@ nth-check@^1.0.2:
   dependencies:
     boolbase "~1.0.0"
 
+nth-check@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.0.1.tgz#2efe162f5c3da06a28959fbd3db75dbeea9f0fc2"
+  integrity sha512-it1vE95zF6dTT9lBsYbxvqh0Soy4SPowchj0UBGj/V6cTPnXXtQOPUbhZ6CmGzAD/rW22LQK6E96pcdJXk4A4w==
+  dependencies:
+    boolbase "^1.0.0"
+
 num2fraction@^1.2.2:
   version "1.2.2"
   resolved "https://registry.npm.taobao.org/num2fraction/download/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede"
@@ -7294,7 +7717,7 @@ parse-json@^5.0.0:
     json-parse-even-better-errors "^2.3.0"
     lines-and-columns "^1.1.6"
 
-parse5-htmlparser2-tree-adapter@^6.0.0:
+parse5-htmlparser2-tree-adapter@^6.0.0, parse5-htmlparser2-tree-adapter@^6.0.1:
   version "6.0.1"
   resolved "https://registry.npm.taobao.org/parse5-htmlparser2-tree-adapter/download/parse5-htmlparser2-tree-adapter-6.0.1.tgz#2cdf9ad823321140370d4dbf5d3e92c7c8ddc6e6"
   integrity sha1-LN+a2CMyEUA3DU2/XT6Sx8jdxuY=
@@ -7368,7 +7791,7 @@ path-key@^3.0.0, path-key@^3.1.0:
   resolved "https://registry.npm.taobao.org/path-key/download/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
   integrity sha1-WB9q3mWMu6ZaDTOA3ndTKVBU83U=
 
-path-parse@^1.0.6:
+path-parse@^1.0.6, path-parse@^1.0.7:
   version "1.0.7"
   resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
   integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
@@ -7438,6 +7861,13 @@ pinkie@^2.0.0:
   resolved "https://registry.npm.taobao.org/pinkie/download/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
   integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA=
 
+pirates@^3.0.2:
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/pirates/-/pirates-3.0.2.tgz#7e6f85413fd9161ab4e12b539b06010d85954bb9"
+  integrity sha512-c5CgUJq6H2k6MJz72Ak1F5sN9n9wlSlJyEnwvpm9/y3WB4E3pHBDT2c6PEiS1vyJvq2bUxUAIu0EGf8Cx4Ic7Q==
+  dependencies:
+    node-modules-regexp "^1.0.0"
+
 pkg-dir@^1.0.0:
   version "1.0.0"
   resolved "https://registry.npm.taobao.org/pkg-dir/download/pkg-dir-1.0.0.tgz?cache=0&sync_timestamp=1602859045787&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fpkg-dir%2Fdownload%2Fpkg-dir-1.0.0.tgz#7a4b508a8d5bb2d629d447056ff4e9c9314cf3d4"
@@ -7466,6 +7896,11 @@ pnp-webpack-plugin@^1.6.4:
   dependencies:
     ts-pnp "^1.1.6"
 
+pofile@^1.1.0:
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/pofile/-/pofile-1.1.3.tgz#e2c0d4052b9829f171b888bfb35c87791dbea297"
+  integrity sha512-sk96pUvpNwDV6PLrnhr68Uu1S5NohsxqLKz0GuracgrDo40BdF/r1RhHnjakUk6Q4Z0OKIybOQ7GevLKGN1iYw==
+
 portfinder@^1.0.26:
   version "1.0.28"
   resolved "https://registry.npm.taobao.org/portfinder/download/portfinder-1.0.28.tgz?cache=0&sync_timestamp=1596018176291&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fportfinder%2Fdownload%2Fportfinder-1.0.28.tgz#67c4622852bd5374dd1dd900f779f53462fac778"
@@ -7816,6 +8251,15 @@ postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.27, postcss@^7.0.3
     source-map "^0.6.1"
     supports-color "^6.1.0"
 
+postcss@^8.1.10:
+  version "8.4.6"
+  resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.6.tgz#c5ff3c3c457a23864f32cb45ac9b741498a09ae1"
+  integrity sha512-OovjwIzs9Te46vlEx7+uXB0PLijpwjXGKXjVGGPIGubGpq7uh5Xgf6D6FiJ/SzJMBosHDp6a2hiXOS97iBXcaA==
+  dependencies:
+    nanoid "^3.2.0"
+    picocolors "^1.0.0"
+    source-map-js "^1.0.2"
+
 prelude-ls@~1.1.2:
   version "1.1.2"
   resolved "https://registry.npm.taobao.org/prelude-ls/download/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
@@ -7859,6 +8303,13 @@ promise-inflight@^1.0.1:
   resolved "https://registry.npm.taobao.org/promise-inflight/download/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3"
   integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM=
 
+promise@^7.0.1:
+  version "7.3.1"
+  resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf"
+  integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==
+  dependencies:
+    asap "~2.0.3"
+
 prosemirror-commands@^1.1.10:
   version "1.1.10"
   resolved "https://registry.yarnpkg.com/prosemirror-commands/-/prosemirror-commands-1.1.10.tgz#406a6589966e6cd80809cea2d801fb998639b37d"
@@ -7986,6 +8437,109 @@ public-encrypt@^4.0.0:
     randombytes "^2.0.1"
     safe-buffer "^5.1.2"
 
+pug-attrs@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/pug-attrs/-/pug-attrs-3.0.0.tgz#b10451e0348165e31fad1cc23ebddd9dc7347c41"
+  integrity sha512-azINV9dUtzPMFQktvTXciNAfAuVh/L/JCl0vtPCwvOA21uZrC08K/UnmrL+SXGEVc1FwzjW62+xw5S/uaLj6cA==
+  dependencies:
+    constantinople "^4.0.1"
+    js-stringify "^1.0.2"
+    pug-runtime "^3.0.0"
+
+pug-code-gen@^3.0.2:
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/pug-code-gen/-/pug-code-gen-3.0.2.tgz#ad190f4943133bf186b60b80de483100e132e2ce"
+  integrity sha512-nJMhW16MbiGRiyR4miDTQMRWDgKplnHyeLvioEJYbk1RsPI3FuA3saEP8uwnTb2nTJEKBU90NFVWJBk4OU5qyg==
+  dependencies:
+    constantinople "^4.0.1"
+    doctypes "^1.1.0"
+    js-stringify "^1.0.2"
+    pug-attrs "^3.0.0"
+    pug-error "^2.0.0"
+    pug-runtime "^3.0.0"
+    void-elements "^3.1.0"
+    with "^7.0.0"
+
+pug-error@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/pug-error/-/pug-error-2.0.0.tgz#5c62173cb09c34de2a2ce04f17b8adfec74d8ca5"
+  integrity sha512-sjiUsi9M4RAGHktC1drQfCr5C5eriu24Lfbt4s+7SykztEOwVZtbFk1RRq0tzLxcMxMYTBR+zMQaG07J/btayQ==
+
+pug-filters@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/pug-filters/-/pug-filters-4.0.0.tgz#d3e49af5ba8472e9b7a66d980e707ce9d2cc9b5e"
+  integrity sha512-yeNFtq5Yxmfz0f9z2rMXGw/8/4i1cCFecw/Q7+D0V2DdtII5UvqE12VaZ2AY7ri6o5RNXiweGH79OCq+2RQU4A==
+  dependencies:
+    constantinople "^4.0.1"
+    jstransformer "1.0.0"
+    pug-error "^2.0.0"
+    pug-walk "^2.0.0"
+    resolve "^1.15.1"
+
+pug-lexer@^5.0.1:
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/pug-lexer/-/pug-lexer-5.0.1.tgz#ae44628c5bef9b190b665683b288ca9024b8b0d5"
+  integrity sha512-0I6C62+keXlZPZkOJeVam9aBLVP2EnbeDw3An+k0/QlqdwH6rv8284nko14Na7c0TtqtogfWXcRoFE4O4Ff20w==
+  dependencies:
+    character-parser "^2.2.0"
+    is-expression "^4.0.0"
+    pug-error "^2.0.0"
+
+pug-linker@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/pug-linker/-/pug-linker-4.0.0.tgz#12cbc0594fc5a3e06b9fc59e6f93c146962a7708"
+  integrity sha512-gjD1yzp0yxbQqnzBAdlhbgoJL5qIFJw78juN1NpTLt/mfPJ5VgC4BvkoD3G23qKzJtIIXBbcCt6FioLSFLOHdw==
+  dependencies:
+    pug-error "^2.0.0"
+    pug-walk "^2.0.0"
+
+pug-load@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/pug-load/-/pug-load-3.0.0.tgz#9fd9cda52202b08adb11d25681fb9f34bd41b662"
+  integrity sha512-OCjTEnhLWZBvS4zni/WUMjH2YSUosnsmjGBB1An7CsKQarYSWQ0GCVyd4eQPMFJqZ8w9xgs01QdiZXKVjk92EQ==
+  dependencies:
+    object-assign "^4.1.1"
+    pug-walk "^2.0.0"
+
+pug-parser@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/pug-parser/-/pug-parser-6.0.0.tgz#a8fdc035863a95b2c1dc5ebf4ecf80b4e76a1260"
+  integrity sha512-ukiYM/9cH6Cml+AOl5kETtM9NR3WulyVP2y4HOU45DyMim1IeP/OOiyEWRr6qk5I5klpsBnbuHpwKmTx6WURnw==
+  dependencies:
+    pug-error "^2.0.0"
+    token-stream "1.0.0"
+
+pug-runtime@^3.0.0, pug-runtime@^3.0.1:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/pug-runtime/-/pug-runtime-3.0.1.tgz#f636976204723f35a8c5f6fad6acda2a191b83d7"
+  integrity sha512-L50zbvrQ35TkpHwv0G6aLSuueDRwc/97XdY8kL3tOT0FmhgG7UypU3VztfV/LATAvmUfYi4wNxSajhSAeNN+Kg==
+
+pug-strip-comments@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/pug-strip-comments/-/pug-strip-comments-2.0.0.tgz#f94b07fd6b495523330f490a7f554b4ff876303e"
+  integrity sha512-zo8DsDpH7eTkPHCXFeAk1xZXJbyoTfdPlNR0bK7rpOMuhBYb0f5qUVCO1xlsitYd3w5FQTK7zpNVKb3rZoUrrQ==
+  dependencies:
+    pug-error "^2.0.0"
+
+pug-walk@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/pug-walk/-/pug-walk-2.0.0.tgz#417aabc29232bb4499b5b5069a2b2d2a24d5f5fe"
+  integrity sha512-yYELe9Q5q9IQhuvqsZNwA5hfPkMJ8u92bQLIMcsMxf/VADjNtEYptU+inlufAFYcWdHlwNfZOEnOOQrZrcyJCQ==
+
+pug@^3.0.2:
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/pug/-/pug-3.0.2.tgz#f35c7107343454e43bc27ae0ff76c731b78ea535"
+  integrity sha512-bp0I/hiK1D1vChHh6EfDxtndHji55XP/ZJKwsRqrz6lRia6ZC2OZbdAymlxdVFwd1L70ebrVJw4/eZ79skrIaw==
+  dependencies:
+    pug-code-gen "^3.0.2"
+    pug-filters "^4.0.0"
+    pug-lexer "^5.0.1"
+    pug-linker "^4.0.0"
+    pug-load "^3.0.0"
+    pug-parser "^6.0.0"
+    pug-runtime "^3.0.1"
+    pug-strip-comments "^2.0.0"
+
 pump@^2.0.0:
   version "2.0.1"
   resolved "https://registry.npm.taobao.org/pump/download/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909"
@@ -8362,6 +8916,15 @@ resolve@^1.10.0, resolve@^1.12.0, resolve@^1.14.2:
     is-core-module "^2.2.0"
     path-parse "^1.0.6"
 
+resolve@^1.15.1:
+  version "1.22.0"
+  resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198"
+  integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==
+  dependencies:
+    is-core-module "^2.8.1"
+    path-parse "^1.0.7"
+    supports-preserve-symlinks-flag "^1.0.0"
+
 restore-cursor@^2.0.0:
   version "2.0.0"
   resolved "https://registry.npm.taobao.org/restore-cursor/download/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf"
@@ -8788,6 +9351,11 @@ source-list-map@^2.0.0:
   resolved "https://registry.npm.taobao.org/source-list-map/download/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34"
   integrity sha1-OZO9hzv8SEecyp6jpUeDXHwVSzQ=
 
+source-map-js@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
+  integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
+
 source-map-resolve@^0.5.0:
   version "0.5.3"
   resolved "https://registry.npm.taobao.org/source-map-resolve/download/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a"
@@ -8834,6 +9402,11 @@ source-map@^0.7.3:
   resolved "https://registry.npm.taobao.org/source-map/download/source-map-0.7.3.tgz?cache=0&sync_timestamp=1589682764497&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsource-map%2Fdownload%2Fsource-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
   integrity sha1-UwL4FpAxc1ImVECS5kmB91F1A4M=
 
+sourcemap-codec@^1.4.4:
+  version "1.4.8"
+  resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
+  integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
+
 spdx-correct@^3.0.0:
   version "3.1.1"
   resolved "https://registry.npm.taobao.org/spdx-correct/download/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9"
@@ -9152,6 +9725,11 @@ supports-color@^7.0.0, supports-color@^7.1.0:
   dependencies:
     has-flag "^4.0.0"
 
+supports-preserve-symlinks-flag@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
+  integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
+
 svg-tags@^1.0.0:
   version "1.0.0"
   resolved "https://registry.npm.taobao.org/svg-tags/download/svg-tags-1.0.0.tgz#58f71cee3bd519b59d4b2a843b6c7de64ac04764"
@@ -9366,6 +9944,11 @@ toidentifier@1.0.0:
   resolved "https://registry.npm.taobao.org/toidentifier/download/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
   integrity sha1-fhvjRw8ed5SLxD2Uo8j013UrpVM=
 
+token-stream@1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/token-stream/-/token-stream-1.0.0.tgz#cc200eab2613f4166d27ff9afc7ca56d49df6eb4"
+  integrity sha1-zCAOqyYT9BZtJ/+a/HylbUnfbrQ=
+
 toposort@^1.0.0:
   version "1.0.7"
   resolved "https://registry.npm.taobao.org/toposort/download/toposort-1.0.7.tgz#2e68442d9f64ec720b8cc89e6443ac6caa950029"
@@ -9406,6 +9989,11 @@ tslib@^1.10.0, tslib@^1.9.0:
   resolved "https://registry.npm.taobao.org/tslib/download/tslib-1.14.1.tgz?cache=0&sync_timestamp=1609887539329&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ftslib%2Fdownload%2Ftslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
   integrity sha1-zy04vcNKE0vK8QkcQfZhni9nLQA=
 
+tslib@^2.2.0:
+  version "2.3.1"
+  resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01"
+  integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==
+
 tty-browserify@0.0.0:
   version "0.0.0"
   resolved "https://registry.npm.taobao.org/tty-browserify/download/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6"
@@ -9697,11 +10285,21 @@ verror@1.10.0:
     core-util-is "1.0.2"
     extsprintf "^1.2.0"
 
+vlq@^0.2.1:
+  version "0.2.3"
+  resolved "https://registry.yarnpkg.com/vlq/-/vlq-0.2.3.tgz#8f3e4328cf63b1540c0d67e1b2778386f8975b26"
+  integrity sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow==
+
 vm-browserify@^1.0.1:
   version "1.1.2"
   resolved "https://registry.npm.taobao.org/vm-browserify/download/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
   integrity sha1-eGQcSIuObKkadfUR56OzKobl3aA=
 
+void-elements@^3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09"
+  integrity sha1-YU9/v42AHwu18GYfWy9XhXUOTwk=
+
 vue-chartjs@^3.5.1:
   version "3.5.1"
   resolved "https://registry.yarnpkg.com/vue-chartjs/-/vue-chartjs-3.5.1.tgz#d25e845708f7744ae51bed9d23a975f5f8fc6529"
@@ -9739,6 +10337,11 @@ vue-eslint-parser@^7.0.0:
     esquery "^1.4.0"
     lodash "^4.17.15"
 
+vue-gettext@^2.1.12:
+  version "2.1.12"
+  resolved "https://registry.yarnpkg.com/vue-gettext/-/vue-gettext-2.1.12.tgz#444d3220149b17fa4c7caeded3f12d439b698f33"
+  integrity sha512-7Kw36xtKvARp8ZafQGPK9WR6EM+dhFUikR5f0+etSkiHuvUM3yf1HsRDLYoLLdJ0AMaXxKwgekumzvCk6KX8rA==
+
 vue-hot-reload-api@^2.3.0:
   version "2.3.4"
   resolved "https://registry.npm.taobao.org/vue-hot-reload-api/download/vue-hot-reload-api-2.3.4.tgz?cache=0&sync_timestamp=1589682714858&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fvue-hot-reload-api%2Fdownload%2Fvue-hot-reload-api-2.3.4.tgz#532955cc1eb208a3d990b3a9f9a70574657e08f2"
@@ -10060,6 +10663,16 @@ wide-align@^1.1.0:
   dependencies:
     string-width "^1.0.2 || 2"
 
+with@^7.0.0:
+  version "7.0.2"
+  resolved "https://registry.yarnpkg.com/with/-/with-7.0.2.tgz#ccee3ad542d25538a7a7a80aad212b9828495bac"
+  integrity sha512-RNGKj82nUPg3g5ygxkQl0R937xLyho1J24ItRCBTr/m1YnZkzJy1hUiHUJrc/VlsDQzsCnInEGSg3bci0Lmd4w==
+  dependencies:
+    "@babel/parser" "^7.9.6"
+    "@babel/types" "^7.9.6"
+    assert-never "^1.2.1"
+    babel-walk "3.0.0-canary-5"
+
 word-wrap@~1.2.3:
   version "1.2.3"
   resolved "https://registry.npm.taobao.org/word-wrap/download/word-wrap-1.2.3.tgz?cache=0&sync_timestamp=1589683603678&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fword-wrap%2Fdownload%2Fword-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"

+ 0 - 1
server/api/analytic.go

@@ -63,7 +63,6 @@ func Analytic(c *gin.Context) {
 			cpuUserUsage := (cpuTimesAfter[0].User - cpuTimesBefore[0].User) / (float64(1000*threadNum) / 1000)
 			cpuSystemUsage := (cpuTimesAfter[0].System - cpuTimesBefore[0].System) / (float64(1000*threadNum) / 1000)
 
-
 			response["cpu_user"], _ = strconv.ParseFloat(fmt.Sprintf("%.2f",
 				cpuUserUsage*100), 64)
 

+ 1 - 1
server/model/config-backup.go

@@ -29,7 +29,7 @@ func GetBackupList(path string) (configs []ConfigBackupListItem) {
 	return
 }
 
-func GetBackup(id int) (config ConfigBackup)  {
+func GetBackup(id int) (config ConfigBackup) {
 	db.First(&config, id)
 
 	return

+ 16 - 16
server/model/curd.go

@@ -1,38 +1,38 @@
 package model
 
 type Curd struct {
-    Model interface{}
+	Model interface{}
 }
 
 func NewCurd(Model interface{}) *Curd {
-    return &Curd{Model: Model}
+	return &Curd{Model: Model}
 }
 
 func (c *Curd) GetList(dest interface{}) (err error) {
-    err = db.Model(c.Model).Scan(dest).Error
-    return
+	err = db.Model(c.Model).Scan(dest).Error
+	return
 }
 
 func (c *Curd) First(dest interface{}, conds ...interface{}) (err error) {
-    err = db.Model(c.Model).First(dest, conds).Error
-    return
+	err = db.Model(c.Model).First(dest, conds).Error
+	return
 }
 
 func (c *Curd) Add(value interface{}) (err error) {
-    err = db.Model(c.Model).Create(value).Error
-    if err != nil {
-        return err
-    }
-    err = db.Find(value).Error
-    return
+	err = db.Model(c.Model).Create(value).Error
+	if err != nil {
+		return err
+	}
+	err = db.Find(value).Error
+	return
 }
 
 func (c *Curd) Edit(orig interface{}, new interface{}) (err error) {
-    err = db.Model(orig).Updates(new).Error
-    return
+	err = db.Model(orig).Updates(new).Error
+	return
 }
 
 func (c *Curd) Delete(value interface{}, conds ...interface{}) (err error) {
-    err = db.Model(c.Model).Delete(value, conds).Error
-    return
+	err = db.Model(c.Model).Delete(value, conds).Error
+	return
 }

+ 24 - 24
server/test/analytic_test.go

@@ -1,36 +1,36 @@
 package test
 
 import (
-    "fmt"
-    "github.com/shirou/gopsutil/v3/cpu"
-    "github.com/shirou/gopsutil/v3/disk"
-    "github.com/shirou/gopsutil/v3/load"
-    "github.com/shirou/gopsutil/v3/mem"
-    "runtime"
-    "testing"
-    "time"
+	"fmt"
+	"github.com/shirou/gopsutil/v3/cpu"
+	"github.com/shirou/gopsutil/v3/disk"
+	"github.com/shirou/gopsutil/v3/load"
+	"github.com/shirou/gopsutil/v3/mem"
+	"runtime"
+	"testing"
+	"time"
 )
 
 func TestGoPsutil(t *testing.T) {
-    fmt.Println("os:", runtime.GOOS)
-    fmt.Println("threads:", runtime.GOMAXPROCS(0))
+	fmt.Println("os:", runtime.GOOS)
+	fmt.Println("threads:", runtime.GOMAXPROCS(0))
 
-    v, _ := mem.VirtualMemory()
+	v, _ := mem.VirtualMemory()
 
-    loadAvg, _ := load.Avg()
+	loadAvg, _ := load.Avg()
 
-    fmt.Println("loadavg", loadAvg.String())
+	fmt.Println("loadavg", loadAvg.String())
 
-    fmt.Printf("Total: %v, Free:%v, UsedPercent:%f%%\n", v.Total, v.Free, v.UsedPercent)
-    cpuTimesBefore, _ := cpu.Times(false)
-    time.Sleep(1000*time.Millisecond)
-    cpuTimesAfter, _ := cpu.Times(false)
-    threadNum := runtime.GOMAXPROCS(0)
-    fmt.Println(cpuTimesBefore[0].String(), "\n", cpuTimesAfter[0].String())
-    cpuUserUsage := (cpuTimesAfter[0].User - cpuTimesBefore[0].User) / (float64(1000*threadNum) / 1000)
-    cpuSystemUsage := (cpuTimesAfter[0].System - cpuTimesBefore[0].System) / (float64(1000*threadNum) / 1000)
-    fmt.Printf("%.2f, %.2f\n", cpuUserUsage*100, cpuSystemUsage*100)
+	fmt.Printf("Total: %v, Free:%v, UsedPercent:%f%%\n", v.Total, v.Free, v.UsedPercent)
+	cpuTimesBefore, _ := cpu.Times(false)
+	time.Sleep(1000 * time.Millisecond)
+	cpuTimesAfter, _ := cpu.Times(false)
+	threadNum := runtime.GOMAXPROCS(0)
+	fmt.Println(cpuTimesBefore[0].String(), "\n", cpuTimesAfter[0].String())
+	cpuUserUsage := (cpuTimesAfter[0].User - cpuTimesBefore[0].User) / (float64(1000*threadNum) / 1000)
+	cpuSystemUsage := (cpuTimesAfter[0].System - cpuTimesBefore[0].System) / (float64(1000*threadNum) / 1000)
+	fmt.Printf("%.2f, %.2f\n", cpuUserUsage*100, cpuSystemUsage*100)
 
-    diskUsage, _ := disk.Usage(".")
-    fmt.Println(diskUsage.String())
+	diskUsage, _ := disk.Usage(".")
+	fmt.Println(diskUsage.String())
 }

+ 75 - 79
server/test/lego_test.go

@@ -1,98 +1,94 @@
 package test
 
 import (
-    "crypto"
-    "crypto/ecdsa"
-    "crypto/elliptic"
-    "crypto/rand"
-    "fmt"
-    "io/ioutil"
-    "log"
-    "testing"
-
-    "github.com/go-acme/lego/v4/certcrypto"
-    "github.com/go-acme/lego/v4/certificate"
-    "github.com/go-acme/lego/v4/challenge/http01"
-    "github.com/go-acme/lego/v4/lego"
-    "github.com/go-acme/lego/v4/registration"
+	"crypto"
+	"crypto/ecdsa"
+	"crypto/elliptic"
+	"crypto/rand"
+	"fmt"
+	"io/ioutil"
+	"log"
+	"testing"
+
+	"github.com/go-acme/lego/v4/certcrypto"
+	"github.com/go-acme/lego/v4/certificate"
+	"github.com/go-acme/lego/v4/challenge/http01"
+	"github.com/go-acme/lego/v4/lego"
+	"github.com/go-acme/lego/v4/registration"
 )
 
 // You'll need a user or account type that implements acme.User
 type MyUser struct {
-    Email        string
-    Registration *registration.Resource
-    key          crypto.PrivateKey
+	Email        string
+	Registration *registration.Resource
+	key          crypto.PrivateKey
 }
 
 func (u *MyUser) GetEmail() string {
-    return u.Email
+	return u.Email
 }
 func (u MyUser) GetRegistration() *registration.Resource {
-    return u.Registration
+	return u.Registration
 }
 func (u *MyUser) GetPrivateKey() crypto.PrivateKey {
-    return u.key
+	return u.key
 }
 
-
 func TestLego(t *testing.T) {
-    // Create a user. New accounts need an email and private key to start.
-    privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
-    if err != nil {
-        log.Fatal(err)
-    }
-
-    myUser := MyUser{
-        Email: "me@jackyu.cn",
-        key:   privateKey,
-    }
-
-    config := lego.NewConfig(&myUser)
-
-    // This CA URL is configured for a local dev instance of Boulder running in Docker in a VM.
-    //config.CADirURL = "https://acme-staging-v02.api.letsencrypt.org/directory"
-    config.Certificate.KeyType = certcrypto.RSA2048
-
-    // A client facilitates communication with the CA server.
-    client, err := lego.NewClient(config)
-    if err != nil {
-        log.Fatal(err)
-    }
-
-
-    err = client.Challenge.SetHTTP01Provider(http01.NewProviderServer("", "9180"))
-    if err != nil {
-        log.Fatal(err)
-    }
-
-
-    // New users will need to register
-    reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
-    if err != nil {
-        log.Fatal(err)
-    }
-    myUser.Registration = reg
-
-    request := certificate.ObtainRequest{
-        Domains: []string{"shanghai2.ojbk.me"},
-        Bundle:  true,
-    }
-    certificates, err := client.Certificate.Obtain(request)
-    if err != nil {
-        log.Fatal(err)
-    }
-
-
-    // Each certificate comes back with the cert bytes, the bytes of the client's
-    // private key, and a certificate URL. SAVE THESE TO DISK.
-    fmt.Printf("%#v\n", certificates)
-    err = ioutil.WriteFile("fullchain.cer", certificates.Certificate, 0644)
-    if err != nil {
-        log.Fatal(err)
-    }
-    err = ioutil.WriteFile("private.key", certificates.PrivateKey, 0644)
-    if err != nil {
-        log.Fatal(err)
-    }
+	// Create a user. New accounts need an email and private key to start.
+	privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	myUser := MyUser{
+		Email: "me@jackyu.cn",
+		key:   privateKey,
+	}
+
+	config := lego.NewConfig(&myUser)
+
+	// This CA URL is configured for a local dev instance of Boulder running in Docker in a VM.
+	//config.CADirURL = "https://acme-staging-v02.api.letsencrypt.org/directory"
+	config.Certificate.KeyType = certcrypto.RSA2048
+
+	// A client facilitates communication with the CA server.
+	client, err := lego.NewClient(config)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	err = client.Challenge.SetHTTP01Provider(http01.NewProviderServer("", "9180"))
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	// New users will need to register
+	reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
+	if err != nil {
+		log.Fatal(err)
+	}
+	myUser.Registration = reg
+
+	request := certificate.ObtainRequest{
+		Domains: []string{"shanghai2.ojbk.me"},
+		Bundle:  true,
+	}
+	certificates, err := client.Certificate.Obtain(request)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	// Each certificate comes back with the cert bytes, the bytes of the client's
+	// private key, and a certificate URL. SAVE THESE TO DISK.
+	fmt.Printf("%#v\n", certificates)
+	err = ioutil.WriteFile("fullchain.cer", certificates.Certificate, 0644)
+	if err != nil {
+		log.Fatal(err)
+	}
+	err = ioutil.WriteFile("private.key", certificates.PrivateKey, 0644)
+	if err != nil {
+		log.Fatal(err)
+	}
 
 }

+ 1 - 1
server/test/nginx_test.go

@@ -8,7 +8,7 @@ import (
 	"testing"
 )
 
-func TestGetNginx(t *testing.T)  {
+func TestGetNginx(t *testing.T) {
 	out, err := exec.Command("nginx", "-V").CombinedOutput()
 	if err != nil {
 		log.Fatal(err)

+ 38 - 38
server/tool/config_list.go

@@ -1,62 +1,62 @@
 package tool
 
 import (
-    "github.com/gin-gonic/gin"
-    "sort"
-    "time"
+	"github.com/gin-gonic/gin"
+	"sort"
+	"time"
 )
 
 type MapsSort struct {
-    Key     string
-    Type    string
-    Order   string
-    MapList []gin.H
+	Key     string
+	Type    string
+	Order   string
+	MapList []gin.H
 }
 
 func boolToInt(b bool) int {
-    if b {
-        return 1
-    }
-    return 0
+	if b {
+		return 1
+	}
+	return 0
 }
 
 func (m MapsSort) Len() int {
-    return len(m.MapList)
+	return len(m.MapList)
 }
 
 func (m MapsSort) Less(i, j int) bool {
-    flag := false
-
-    if m.Type == "int" {
-        flag = m.MapList[i][m.Key].(int) > m.MapList[j][m.Key].(int)
-    } else if m.Type == "bool" {
-        flag = boolToInt(m.MapList[i][m.Key].(bool)) > boolToInt(m.MapList[j][m.Key].(bool))
-    } else if m.Type == "bool" {
-        flag = m.MapList[i][m.Key].(string) > m.MapList[j][m.Key].(string)
-    } else if m.Type == "time" {
-        flag = m.MapList[i][m.Key].(time.Time).After(m.MapList[j][m.Key].(time.Time))
-    }
-
-    if m.Order == "asc" {
-        flag = !flag
-    }
-
-    return flag
+	flag := false
+
+	if m.Type == "int" {
+		flag = m.MapList[i][m.Key].(int) > m.MapList[j][m.Key].(int)
+	} else if m.Type == "bool" {
+		flag = boolToInt(m.MapList[i][m.Key].(bool)) > boolToInt(m.MapList[j][m.Key].(bool))
+	} else if m.Type == "bool" {
+		flag = m.MapList[i][m.Key].(string) > m.MapList[j][m.Key].(string)
+	} else if m.Type == "time" {
+		flag = m.MapList[i][m.Key].(time.Time).After(m.MapList[j][m.Key].(time.Time))
+	}
+
+	if m.Order == "asc" {
+		flag = !flag
+	}
+
+	return flag
 }
 
 func (m MapsSort) Swap(i, j int) {
-    m.MapList[i], m.MapList[j] = m.MapList[j], m.MapList[i]
+	m.MapList[i], m.MapList[j] = m.MapList[j], m.MapList[i]
 }
 
 func Sort(key string, order string, Type string, maps []gin.H) []gin.H {
-    mapsSort := MapsSort{
-        Key: key,
-        MapList: maps,
-        Type: Type,
-        Order: order,
-    }
+	mapsSort := MapsSort{
+		Key:     key,
+		MapList: maps,
+		Type:    Type,
+		Order:   order,
+	}
 
-    sort.Sort(mapsSort)
+	sort.Sort(mapsSort)
 
-    return mapsSort.MapList
+	return mapsSort.MapList
 }