You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iotdb.apache.org by qi...@apache.org on 2022/08/03 12:31:26 UTC

[iotdb-web-workbench] 31/34: feat(add the feature of metric)

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

qiaojialin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iotdb-web-workbench.git

commit dd8d83de0aaad2290c57d07124751f6a33acc148
Author: 470058871@qq.com <47...@qq.com>
AuthorDate: Wed Jun 1 14:43:48 2022 +0800

    feat(add the feature of metric)
---
 frontend/package-lock.json                         |  16 +-
 frontend/public/index.html                         |   3 +
 frontend/src/App.vue                               |  43 +-
 frontend/src/components/FormTable.vue              |   2 +-
 frontend/src/components/StandTable.vue             |  24 +-
 frontend/src/components/TreeSelect.vue             |  18 +
 .../useElementResize.js.js}                        |   0
 .../variables.scss => hooks/useLanguageWatch.js}   |  53 +-
 frontend/src/i18n/cn.js                            |  95 +++-
 frontend/src/i18n/de.js                            |  95 +++-
 frontend/src/i18n/en.js                            |  93 ++-
 frontend/src/main.js                               |   8 +
 frontend/src/plugins/element_plus.js               |  23 +-
 frontend/src/plugins/event_bus.js                  |  18 +
 frontend/src/plugins/index.js                      |  18 +
 frontend/src/router/index.js                       |  17 +
 frontend/src/store/index.js                        |  18 +
 frontend/src/styles/element.scss                   |  62 +-
 frontend/src/styles/variables.scss                 |   5 +-
 frontend/src/util/axios.js                         |   2 +-
 frontend/src/util/constant.js                      |  18 +
 frontend/src/util/export.js                        |  18 +
 frontend/src/util/setOperation.js                  |  18 +
 frontend/src/views/About/index.vue                 |  15 +-
 frontend/src/views/Control/api/index.js            |  81 +++
 .../src/views/Control/components/indicator.vue     |  77 +++
 .../views/Control/components/indicatorChart.vue    | 115 ++++
 .../src/views/Control/components/indicatorList.vue | 370 ++++++++++++
 .../views/Control/components/indicatorPanel.vue    | 414 ++++++++++++++
 frontend/src/views/Control/components/query.vue    | 536 ++++++++++++++++++
 frontend/src/views/Control/hooks/useInitChart.js   | 625 +++++++++++++++++++++
 frontend/src/views/Control/index.vue               | 420 ++++++++++++++
 .../views/DataBaseM/components/dataListTree.vue    | 154 +++--
 frontend/src/views/DataBaseM/index.vue             |   6 +-
 frontend/src/views/Device/index.vue                |  24 +-
 frontend/src/views/DeviceMessage/index.vue         |  73 ++-
 frontend/src/views/Login/index.vue                 |  19 +-
 frontend/src/views/Root/index.vue                  |  34 +-
 frontend/src/views/Source/components/dataModal.vue |  37 +-
 .../src/views/Source/components/dataModalAll.vue   | 180 +++---
 .../src/views/Source/components/permitDialog.vue   |  22 +-
 .../views/Source/components/role/AuthManage.vue    |  18 +
 .../views/Source/components/role/DataManage.vue    |  18 +
 .../Source/components/role/DialogGrantUser.vue     |  18 +
 .../src/views/Source/components/role/Index.vue     |  18 +
 .../views/Source/components/role/PowerManage.vue   |  18 +
 .../src/views/Source/components/role/RoleInfo.vue  |  23 +-
 .../src/views/Source/components/role/RoleList.vue  |  22 +-
 frontend/src/views/Source/index.vue                |  73 ++-
 frontend/src/views/SqlSerch/index.vue              | 148 +++--
 frontend/src/views/storage/index.vue               |   9 +-
 frontend/src/views/storage/newStorage.vue          |   2 +-
 frontend/vue.config.js                             |  30 +-
 53 files changed, 3932 insertions(+), 334 deletions(-)

diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 2d17ab8..9c43d69 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -2233,7 +2233,7 @@
         },
         "ansi-styles": {
           "version": "4.3.0",
-          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+          "resolved": "http://nexus.cisdigital.cn/repository/npm/ansi-styles/-/ansi-styles-4.3.0.tgz",
           "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
           "dev": true,
           "optional": true,
@@ -2243,7 +2243,7 @@
         },
         "chalk": {
           "version": "4.1.2",
-          "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+          "resolved": "http://nexus.cisdigital.cn/repository/npm/chalk/-/chalk-4.1.2.tgz",
           "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
           "dev": true,
           "optional": true,
@@ -2254,7 +2254,7 @@
         },
         "color-convert": {
           "version": "2.0.1",
-          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+          "resolved": "http://nexus.cisdigital.cn/repository/npm/color-convert/-/color-convert-2.0.1.tgz",
           "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
           "dev": true,
           "optional": true,
@@ -2264,21 +2264,21 @@
         },
         "color-name": {
           "version": "1.1.4",
-          "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+          "resolved": "http://nexus.cisdigital.cn/repository/npm/color-name/-/color-name-1.1.4.tgz",
           "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
           "dev": true,
           "optional": true
         },
         "has-flag": {
           "version": "4.0.0",
-          "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+          "resolved": "http://nexus.cisdigital.cn/repository/npm/has-flag/-/has-flag-4.0.0.tgz",
           "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
           "dev": true,
           "optional": true
         },
         "loader-utils": {
           "version": "2.0.2",
-          "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz",
+          "resolved": "http://nexus.cisdigital.cn/repository/npm/loader-utils/-/loader-utils-2.0.2.tgz",
           "integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==",
           "dev": true,
           "optional": true,
@@ -2299,7 +2299,7 @@
         },
         "supports-color": {
           "version": "7.2.0",
-          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+          "resolved": "http://nexus.cisdigital.cn/repository/npm/supports-color/-/supports-color-7.2.0.tgz",
           "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
           "dev": true,
           "optional": true,
@@ -2309,7 +2309,7 @@
         },
         "vue-loader-v16": {
           "version": "npm:vue-loader@16.8.3",
-          "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.8.3.tgz",
+          "resolved": "http://nexus.cisdigital.cn/repository/npm/vue-loader/-/vue-loader-16.8.3.tgz",
           "integrity": "sha512-7vKN45IxsKxe5GcVCbc2qFU5aWzyiLrYJyUuMz4BQLKctCj/fmCa0w6fGiiQ2cLFetNcek1ppGJQDCup0c1hpA==",
           "dev": true,
           "optional": true,
diff --git a/frontend/public/index.html b/frontend/public/index.html
index 81ad929..22403d5 100644
--- a/frontend/public/index.html
+++ b/frontend/public/index.html
@@ -4,6 +4,9 @@
     <meta charset="utf-8" />
     <meta http-equiv="X-UA-Compatible" content="IE=edge" />
     <meta name="viewport" content="width=device-width,initial-scale=1.0" />
+    <meta http-equiv="pragram" content="no-cache">
+    <meta http-equiv="cache-control" content="no-cache, no-store, must-revalidate">
+    <meta http-equiv="expires" content="0">
     <link rel="icon" href="<%= BASE_URL %>iotdb.ico" />
     <title><%= htmlWebpackPlugin.options.title %></title>
     <script type="text/javascript" src="<%= BASE_URL %>iconfont.js"></script>
diff --git a/frontend/src/App.vue b/frontend/src/App.vue
index 98d7f19..aa67e8b 100644
--- a/frontend/src/App.vue
+++ b/frontend/src/App.vue
@@ -18,9 +18,50 @@
 -->
 
 <template>
-  <router-view />
+  <el-config-provider :locale="language">
+    <router-view />
+  </el-config-provider>
 </template>
 
+<script>
+import { ref, watch, nextTick } from 'vue';
+import { ElConfigProvider } from 'element-plus';
+import { useI18n } from 'vue-i18n';
+import enLocale from 'element-plus/lib/locale/lang/en';
+import deLocale from 'element-plus/lib/locale/lang/de';
+import zhLocale from 'element-plus/lib/locale/lang/zh-cn';
+
+export default {
+  name: 'App',
+  components: {
+    ElConfigProvider,
+  },
+  setup() {
+    const map = {
+      [enLocale.name]: enLocale,
+      [deLocale.name]: deLocale,
+      [zhLocale.name]: zhLocale,
+    };
+    let { locale } = useI18n();
+    let language = ref(map[locale.value]);
+    function useLanguageWatch(refValue, callback) {
+      let { locale } = useI18n();
+      watch(locale, () => {
+        refValue.value = map[locale.value];
+        nextTick(() => {
+          refValue.value = callback();
+        });
+      });
+    }
+    useLanguageWatch(language, () => {
+      return map[locale.value];
+    });
+    return {
+      language,
+    };
+  },
+};
+</script>
 <style lang="scss">
 #app {
   -webkit-font-smoothing: antialiased;
diff --git a/frontend/src/components/FormTable.vue b/frontend/src/components/FormTable.vue
index 18f2525..8ea8711 100644
--- a/frontend/src/components/FormTable.vue
+++ b/frontend/src/components/FormTable.vue
@@ -23,7 +23,7 @@ IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or imp
         :suffix-icon="item.suffixIcon"
         :prefix-icon="item.prefixIcon"
       >
-        <template #prepend v-if="item.inputHeader">{{ formData[item.inputHeaderText] }}</template>
+        <template #prepend v-if="item.inputHeader">{{ typeof item.inputHeaderText === 'function' ? item.inputHeaderText(formData, item) : formData[item.inputHeaderText] }}</template>
       </el-input>
       <el-select
         v-if="item.type === 'SELECT'"
diff --git a/frontend/src/components/StandTable.vue b/frontend/src/components/StandTable.vue
index d3b77ac..ce4d1b8 100644
--- a/frontend/src/components/StandTable.vue
+++ b/frontend/src/components/StandTable.vue
@@ -30,7 +30,10 @@ IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or imp
             <svg v-if="iconArr.icon[item.icon]" :class="['icon', { 'icon-time': item.icon === 'TIME' }]" @click="sqlClick" aria-hidden="true">
               <use :xlink:href="`#icon-${iconArr.icon[item.icon]}`"></use>
             </svg>
-            <span :style="{ 'margin-left': iconArr.icon[item.icon] ? '5px' : '' }">{{ $t(item.label) }}</span>
+            <span :style="{ 'margin-left': iconArr.icon[item.icon] ? '5px' : '' }" :class="[{ closable: !!item.closable }]" :title="$t(item.label)"
+              >{{ $t(item.label) }}
+              <span class="el-icon-close" v-on:click="item.closable(item)"></span>
+            </span>
             <i :class="item.icon" style="margin-left: 4px" @click="iconEvent(item.iconNum)"></i>
           </template>
           <template #default="scope">
@@ -40,7 +43,7 @@ IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or imp
               :size="item.size"
               :class="{ borderRed: (scope.row.namecopy || !scope.row[item.prop]) && scope.row.border && item.border }"
               :placeholder="$t('device.inputTip') + $t(item.label)"
-              @blur="item.event(scope, scope.row, scope.row[item.prop], $event)"
+              @blur="item.event(scope, scope.row, scope.row[item.prop], $event, item)"
             >
             </el-input>
             <el-input
@@ -382,6 +385,23 @@ export default {
   line-height: 0px;
   min-height: 0 !important;
 }
+.el-icon-close {
+  display: none;
+  position: absolute;
+  top: 5px;
+  cursor: pointer;
+}
+.closable {
+  &:hover > .el-icon-close {
+    display: inline-block;
+    position: absolute;
+    top: 5px;
+    cursor: pointer;
+  }
+}
+:deep(.el-table) th > .cell {
+  white-space: nowrap;
+}
 </style>
 <style lang="scss">
 .borderRed {
diff --git a/frontend/src/components/TreeSelect.vue b/frontend/src/components/TreeSelect.vue
index 0f1df81..7141802 100644
--- a/frontend/src/components/TreeSelect.vue
+++ b/frontend/src/components/TreeSelect.vue
@@ -1,3 +1,21 @@
+<!--
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+-->
 <template>
   <div class="tree-select-wraper">
     <el-select v-model="mineStatus" :placeholder="placeholder" multiple collapse-tags @change="changeSelect" style="width: 100%">
diff --git a/frontend/src/views/DataBaseM/hooks/useElementResize.js b/frontend/src/hooks/useElementResize.js.js
similarity index 100%
rename from frontend/src/views/DataBaseM/hooks/useElementResize.js
rename to frontend/src/hooks/useElementResize.js.js
diff --git a/frontend/src/styles/variables.scss b/frontend/src/hooks/useLanguageWatch.js
similarity index 72%
copy from frontend/src/styles/variables.scss
copy to frontend/src/hooks/useLanguageWatch.js
index 230d8c9..4b8e8f5 100644
--- a/frontend/src/styles/variables.scss
+++ b/frontend/src/hooks/useLanguageWatch.js
@@ -1,22 +1,31 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-$theme-color: #16c493ff;
-$border-color: #EEF0F5;
-$danger-color: #FB5151;
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { watch, nextTick } from 'vue';
+import { useI18n } from 'vue-i18n';
+function useLanguageWatch(refValue, callback) {
+  let { locale } = useI18n();
+  watch(locale, () => {
+    refValue.value = [];
+    nextTick(() => {
+      refValue.value = callback();
+    });
+  });
+}
+
+export default useLanguageWatch;
diff --git a/frontend/src/i18n/cn.js b/frontend/src/i18n/cn.js
index 6148354..03d2620 100644
--- a/frontend/src/i18n/cn.js
+++ b/frontend/src/i18n/cn.js
@@ -25,9 +25,9 @@ const cn = {
     about: {
       'line-2': '关于我们',
       'line-3': 'IoTDB的可视化管理工具',
-      'line-4': 'IoTDB Admin是IoTDB的可视化管理工具,可对IoTDB的数据进行增删改查、权限控制等,简化IoTDB的使用及学习成本。',
+      'line-4': 'IoTDB WorkBench是IoTDB的可视化管理工具,可对IoTDB的数据进行增删改查、权限控制等,简化IoTDB的使用及学习成本。',
       'line-5': `在我们心中IoTDB是最棒的时序数据库之一,我们将一直不遗余力地推动国产时序数据库IoTDB的应用和发展,为本土开源能力的提高、开源生态的发展,贡献自己的力量,欢迎大家加入IoTDB
-      Admin的开发及维护,期待你的加入:`,
+      WorkBench的开发及维护,期待你的加入:`,
       'line-6-text': '微信扫一扫',
       'line-7': '版本号: V0.12',
       'back-btn': '返回工作页面',
@@ -45,6 +45,18 @@ const cn = {
       tip: '提示',
       deleteSuccess: '删除成功',
       add: '新增',
+      placeHolder: '请输入',
+      selectPlaceholder: '请选择',
+      survival: '存活',
+      death: '死机',
+      port: '端口',
+      refresh: '刷新',
+      startTime: '开始时间',
+      endTime: '结束时间',
+      all: '全部',
+      success: '成功',
+      fail: '失败',
+      query: '查询',
     },
     databasem: {
       newStoreGroup: '新建存储组',
@@ -65,6 +77,7 @@ const cn = {
       nodatasource: '目前还没有数据连接,请',
       newQueryWindow: '查询',
       feedback: '问题反馈',
+      monitorManagement: '监控管理',
     },
     loginPage: {
       account: '账号',
@@ -74,13 +87,13 @@ const cn = {
       forgetPassWord: '忘记密码',
       signIn: '登录',
       forgetPassword: '忘记密码',
-      forgetPasswordTip: '请联系系统管理员',
+      forgetPasswordTip: '请联系系统管理员 WeChat:loveher147',
       accountEmptyTip: '账号不能为空',
       accountContentTip: '用户名必须由字母、数字、下划线组成,不能以数字和下划线开始',
       accountLengthTip: '用户名必须大于等于3个字符,小于等于32字符',
       passwordEmptyTip: '密码不能为空',
       passwordLenghtTip: '密码必须大于等于6位,请检查密码位数',
-      welcomeLogin: '欢迎登录IoTDB数据库管理系统',
+      welcomeLogin: '欢迎登录 IoTDB WorkBench',
       loginErrorTip: '用户名或密码不正确,请重新输入',
     },
 
@@ -415,6 +428,80 @@ const cn = {
       deleteArry: '批量删除',
       importTip: '导入成功',
     },
+    controlPage: {
+      dataList: '数据列表',
+      address: '主机或 IP 地址',
+      storage: '存储组数量',
+      entity: '实体数量',
+      physics: '物理量数量',
+      total: '数据总量',
+      monitor: '监控指标',
+      search: '查询统计',
+      allMode: '全部模式',
+      devMode: '开发者模式',
+      opeMode: '运维者模式',
+      chartBtn: '图表面板',
+      listBtn: '列表面板',
+      jvm: 'JVM指标',
+      cpu: 'CPU指标',
+      memory: '内存指标',
+      store: '存储指标',
+      write: '写入指标',
+      isearch: '查询指标',
+      iName: '指标名称',
+      iType: '指标类型',
+      zxjg: '最新结果发生时间',
+      zxzb: '最新指标结果',
+      sql: 'SQL语句',
+      runTime: '运行时间',
+      exeTime: '执行时间(s)',
+      slowSearch: '慢查询',
+      lastTime: '最近一次运行时间',
+      runCount: '运行次数',
+      querySentence: '查询语句',
+      exeResult: '执行结果',
+      downloadLog: '日志下载',
+      totalUseTime: '总耗时 (ms)',
+      garmmarUseTime: '语法耗时 (ms)',
+      ybyUseTime: '预编译耗时 (ms)',
+      yhbyUseTime: '优化编译耗时 (ms)',
+      exeUseTime: '执行耗时 (ms)',
+
+      JVMThread: 'JVM 指标-线程',
+      JVMRecycle: 'JVM 指标-垃圾回收',
+      JVMMemory: 'JVM 指标-内存',
+      JVMClasses: 'JVM 指标-classes',
+
+      selectMetrics: '请选择指标类型',
+
+      GCEchart: 'GC发生次数及耗时(以分钟计算最近15分钟)',
+      JVMClassEchart: 'JVM卸载/加载的class数量',
+      YGCEchart: 'YGC发生原因及耗时',
+      FGCEchart: 'FGC发生原因及耗时',
+      JAVATypeEchart: 'java各类型线程数',
+      JAVATimeEchart: 'java各时间段线程数',
+      MemoryEchart: '内存使用大小',
+      BufferEchart: '缓冲区使用大小',
+      CPUEchart: 'CPU time占比',
+      IOEchart: '磁盘 IO 吞吐',
+      FileCountEchart: '文件数量统计',
+      FileSizeEchart: '文件大小统计',
+      WriteEchart: '写入成功失败统计及耗时',
+      SearchEchart: '查询成功失败统计',
+      ApiEchart: '接口耗时',
+      ApiQPSEchart: '接口 QPS',
+
+      fgcCount: 'fgc次数',
+      ygcCount: 'ygc次数',
+      fgcTime: 'fgc耗时',
+      ygcTime: 'ygc耗时',
+
+      Ptotal: '共',
+      Pentries: '条',
+      EachPage: '每页',
+      slow: '慢',
+      nodata: '无数据',
+    },
   },
 };
 
diff --git a/frontend/src/i18n/de.js b/frontend/src/i18n/de.js
index f8cde1d..6634c75 100644
--- a/frontend/src/i18n/de.js
+++ b/frontend/src/i18n/de.js
@@ -25,7 +25,7 @@ const de = {
     about: {
       'line-2': 'Über uns',
       'line-3': 'Visual management tool für IoTDB',
-      'line-4': `IoTDB Admin ist eine browserbasiertes Verwaltungsoberfläche für IoTDB, dass alle nötigen Operationen wie das Hinzufügen, Löschen, Verändern oder Abfragen von Werten ermöglicht. Ausserdem wird die Zugriffskontrolle unterstützt. Es vereinfacht die Benutzung von IoTDB deutlich und ist einfach zu verwenden.`,
+      'line-4': `IoTDB WorkBench ist eine browserbasiertes Verwaltungsoberfläche für IoTDB, dass alle nötigen Operationen wie das Hinzufügen, Löschen, Verändern oder Abfragen von Werten ermöglicht. Ausserdem wird die Zugriffskontrolle unterstützt. Es vereinfacht die Benutzung von IoTDB deutlich und ist einfach zu verwenden.`,
       'line-5': `IoTDB ist unserer Meinung nach eine der besten Zeitreihendatenbanken. Wir werden stets versuchen die Entwicklung und Anwendung dieser Zeitreihendatenbank zu unterstützen. Wir heissen jeden herzlich Willkommen mitzumachen. Ihr könnt uns kontaktieren unter:`,
       'line-6-text': 'Wechat scannen',
       'line-7': 'Version: 0.12',
@@ -34,7 +34,7 @@ const de = {
     common: {
       submit: 'Absenden',
       cancel: 'Abbrechen',
-      detail: 'Details',
+      detail: 'Weitere Informationen',
       delete: 'Löschen',
       edit: 'Bearbeiten',
       operation: 'Oeration',
@@ -44,6 +44,18 @@ const de = {
       tip: 'Tips',
       deleteSuccess: 'Löschen erfolgreich',
       add: 'neu hinzugefügt',
+      placeHolder: 'Bitte geben Sie ein',
+      selectPlaceholder: 'Wählen Sie',
+      survival: 'Überleben',
+      death: 'Tot',
+      port: 'Hafen',
+      refresh: 'Auffrischen',
+      startTime: 'Startzeit',
+      endTime: 'Endzeit',
+      all: 'Alle',
+      success: 'Erfolgreich',
+      fail: 'Fehlgeschlagen',
+      query: 'Abfragen',
     },
     databasem: {
       newStoreGroup: 'Neue Speichergruppe ("storage group")',
@@ -64,6 +76,7 @@ const de = {
       newQueryWindow: 'Query',
       nodatasource: 'Aktuell existiert keine Datenquelle',
       feedback: 'Problem Feedback',
+      monitorManagement: 'Überwachungsverwaltung',
     },
     loginPage: {
       account: 'Account',
@@ -73,13 +86,13 @@ const de = {
       forgetPassWord: 'Passwort vergessen',
       signIn: 'Anmelden',
       forgetPassword: 'Passwort vergessen',
-      forgetPasswordTip: 'Bitte kontaktieren Sie Ihren Administrator!',
+      forgetPasswordTip: 'Bitte kontaktieren Sie Ihren Administrator WeChat:loveher147',
       accountEmptyTip: 'Benutzername darf nicht leer sein',
       accountContentTip: 'Der Benutzername muss aus mindestens 3 Buchstaben, Zahlen oder Unterstrichen bestehen und muss mit einem Buchstaben beginnen',
       accountLengthTip: 'Der Benutzername muss zwischen 3 und 32 Zeichen lang sein',
       passwordEmptyTip: 'Passwort darf nicht leer sein',
       passwordLenghtTip: 'Das Passwort muss mindestens 6 Zeichen lang sein',
-      welcomeLogin: 'Willkommen bei der IoTDB Admin Oberfläche',
+      welcomeLogin: 'Willkommen bei IoTDB WorkBench',
       loginErrorTip: 'Benutzername oder Passwort falsch',
     },
 
@@ -405,6 +418,80 @@ const de = {
       deleteArry: 'Stapel löschen',
       importTip: 'Import erfolgreich',
     },
+    controlPage: {
+      dataList: 'Datenliste',
+      address: 'Host Oder IP Adresse',
+      storage: 'Anzahl Der Lagergruppen',
+      entity: 'Anzahl Der Unternehmen',
+      physics: 'Physik',
+      total: 'Gesamtmenge',
+      monitor: 'Überwachungsindikatoren',
+      search: 'Abfragestatistik',
+      allMode: 'Alle Modi',
+      devMode: 'Entwicklermodus',
+      opeMode: 'Modus Betreiber',
+      chartBtn: 'Diagramm Bedienfeld',
+      listBtn: 'Gruppe Liste',
+      jvm: 'JVM Zeiger',
+      cpu: 'CPU Zeiger',
+      memory: 'Speicherindikator',
+      store: 'Massenspeicherindikatoren',
+      write: 'Schreib Zeiger',
+      isearch: 'Abfragekriterien',
+
+      iName: 'Zeigerbezeichnung',
+      iType: 'Zeigertypen',
+      zxjg: 'Zuletzt aufgetretene Ergebnisse',
+      zxzb: 'Aktuelle Kennzahlergebnisse',
+      sql: 'SQL Anweisungen',
+      runTime: 'Laufzeit',
+      exeTime: 'Ausführungszeit(s)',
+      slowSearch: 'Langsames Abfragen',
+      lastTime: 'Letzte Ausführung',
+      runCount: 'Anzahl der Ausführungen',
+      querySentence: 'Abfrageanweisungen',
+      exeResult: 'Ausführungsergebnisse',
+      downloadLog: 'Protokolle Herunterladen',
+      totalUseTime: 'Gesamte verstrichene Zeit (ms)',
+      garmmarUseTime: 'Syntax zeitaufwändig (ms)',
+      ybyUseTime: 'Kompilierungszeit (ms)',
+      yhbyUseTime: 'Kompilierungszeit optimieren (ms)',
+      exeUseTime: 'Ausführungszeit (ms)',
+
+      JVMThread: 'JVM Zeiger–Threads',
+      JVMRecycle: 'JVM Zeiger–Rückgewinnung',
+      JVMMemory: 'JVM Zeiger–Speicher',
+      JVMClasses: 'JVM Zeiger-Classes',
+
+      selectMetrics: 'Wählen Sie Einen Zeigertyp Aus',
+
+      GCEchart: 'GC Vorkommen und Zeitaufwand (in Minuten, fast 15 Minuten)',
+      JVMClassEchart: 'Anzahl der class, die JVM entladen/ geladen',
+      YGCEchart: 'Gründe und Zeitaufwand für YGC Ausfälle',
+      FGCEchart: 'Gründe und Zeitaufwand für FGC Ausfälle',
+      JAVATypeEchart: 'Anzahl der java Threads pro Typ',
+      JAVATimeEchart: 'Threads pro Zeitraum in java',
+      MemoryEchart: 'Speichernutzung',
+      BufferEchart: 'Puffernutzungsgröße',
+      CPUEchart: 'CPU-Zeit in Prozent',
+      IOEchart: 'Festplatten-E/ A-Durchsatz',
+      FileCountEchart: 'Anzahl der Dateien, Statistik',
+      FileSizeEchart: 'Dateigröße Statistiken',
+      WriteEchart: 'Statistiken und Zeitaufwand für erfolgreiche Schreibvorgänge',
+      SearchEchart: 'Statistiken zu erfolgreichen Abfragen',
+      ApiEchart: 'Schnittstelle zeitaufwändig',
+      ApiQPSEchart: 'Schnittstelle QPS',
+
+      fgcCount: 'fgc Anzahl',
+      ygcCount: 'ygc Anzahl',
+      fgcTime: 'fgc Zeit',
+      ygcTime: 'ygc Zeit',
+      Ptotal: 'gesamt',
+      Pentries: 'Einträge',
+      EachPage: 'Jede Seite',
+      slow: 'Langsam',
+      nodata: 'Keine Daten',
+    },
   },
 };
 
diff --git a/frontend/src/i18n/en.js b/frontend/src/i18n/en.js
index e860d25..87113a6 100644
--- a/frontend/src/i18n/en.js
+++ b/frontend/src/i18n/en.js
@@ -25,7 +25,7 @@ const en = {
     about: {
       'line-2': 'About Us',
       'line-3': 'Visual Management Tool Of IoTDB',
-      'line-4': `IoTDB Admin Is A GUI Interface Of IoTDB, Providing All The Adding, Deleting, Altering And Querying Operations. Besides That, Accessing Control Is Also Built. It Extremely Simplifies The Use Of IoTDB And Has Very Little Learning Cost. `,
+      'line-4': `IoTDB WorkBench Is A GUI Interface Of IoTDB, Providing All The Adding, Deleting, Altering And Querying Operations. Besides That, Accessing Control Is Also Built. It Extremely Simplifies The Use Of IoTDB And Has Very Little Learning Cost. `,
       'line-5': `IoTDB Is One Of The Best Time Series Database In Our Opinion. We Will Always Try Our Best To Advance The Development And Application Of Time Series Database, Making Contribution To Rise Of Native Open Source Ability And Ecosystem Development.Welcome Everyone Of You To Join Us, Waiting For You! Contact Us:`,
       'line-6-text': 'Scan Wechat',
       'line-7': 'Version: 0.12',
@@ -44,6 +44,18 @@ const en = {
       tip: 'Tips',
       deleteSuccess: 'Delete Success',
       add: 'Add',
+      placeHolder: 'Please Input',
+      selectPlaceholder: 'Please Select',
+      survival: 'Survival',
+      death: 'Die',
+      port: 'Prot',
+      refresh: 'Refresh',
+      startTime: 'Start Time',
+      endTime: 'End Time',
+      all: 'All',
+      success: 'Success',
+      fail: 'Fail',
+      query: 'Query',
     },
     databasem: {
       newStoreGroup: 'New Storage Group',
@@ -64,6 +76,7 @@ const en = {
       newQueryWindow: 'Query',
       nodatasource: 'It Is Has No Data Source At Present, Please',
       feedback: 'Problem Feedback',
+      monitorManagement: 'Monitor Management',
     },
     loginPage: {
       account: 'Account',
@@ -73,13 +86,13 @@ const en = {
       forgetPassWord: 'Forget Password',
       signIn: 'Sign In',
       forgetPassword: 'Forget Password',
-      forgetPasswordTip: 'Please Contact System Administrator',
+      forgetPasswordTip: 'Please Contact System Administrator WeChat:loveher147',
       accountEmptyTip: 'Account Can Not Be Empty',
       accountContentTip: 'The User Name Must Be Made Up Of Letters, Numbers, Underscores, And Cannot Start With Numbers And Underscores',
       accountLengthTip: 'The User Name Must Be Greater Than Or Equal To 3 Characters And Less Than Or Equal To 32 Characters',
       passwordEmptyTip: 'Password Can Not Be Empty',
       passwordLenghtTip: 'Password Must Be Greater Than Or Equal To 6 Digits. Please Check The Number Of Digits',
-      welcomeLogin: 'Welcome To IoTDB Admin',
+      welcomeLogin: 'Welcome To IoTDB WorkBench',
       loginErrorTip: 'Incorrect User Name Or Password, Please Re-enter',
     },
 
@@ -412,6 +425,80 @@ const en = {
       deleteArry: 'Batch Delete',
       importTip: 'Import Succeeded',
     },
+    controlPage: {
+      dataList: 'DataList',
+      address: 'Host Or IP Address',
+      storage: 'Number Of Storage Groups',
+      entity: 'Number Of Entities',
+      physics: 'Physics',
+      total: 'Total Quantity',
+      monitor: 'Monitoring Indicators',
+      search: 'Query Statistics',
+      allMode: 'All Modes',
+      devMode: 'Developer Mode',
+      opeMode: 'Operator Mode',
+      chartBtn: 'Chart Panel',
+      listBtn: 'List Panel',
+      jvm: 'JVM Metric',
+      cpu: 'CPU Metric',
+      memory: 'Memory Metric',
+      store: 'Store Metric',
+      write: 'Write Metric',
+      isearch: 'Search Metric',
+
+      iName: 'Metric Name',
+      iType: 'Metric Type',
+      zxjg: 'Latest Result Occurrence Time',
+      zxzb: 'Latest Metric results',
+      sql: 'SQL Statement',
+      runTime: 'Run Time',
+      exeTime: 'Execution Time(s)',
+      slowSearch: 'Slow Search',
+      lastTime: 'The Last Running Time',
+      runCount: 'Running Times',
+      querySentence: 'Query Statement',
+      exeResult: 'Execution Result',
+      downloadLog: 'Download Log',
+      totalUseTime: 'Total Time Consumption (ms)',
+      garmmarUseTime: 'Grammar Time Consuming (ms)',
+      ybyUseTime: 'Pre Compilation Time Consuming (ms)',
+      yhbyUseTime: 'Optimizing Compilation Time (ms)',
+      exeUseTime: 'Execution Time Consuming (ms)',
+
+      JVMThread: 'JVM Metrics-threads',
+      JVMRecycle: 'JVM Metrics-Recycling',
+      JVMMemory: 'JVM Metrics-Memory',
+      JVMClasses: 'JVM Metrics-Classes',
+
+      selectMetrics: 'Please Select Indicators',
+
+      GCEchart: 'Number Of GC Occurrences And Elapsed Time (Calculated In The Last 15 Minutes)',
+      JVMClassEchart: 'Number Of Classes Unloaded/Loaded By The JVM',
+      YGCEchart: 'Causes And Time Consuming Of YGC',
+      FGCEchart: 'Causes And Time Consuming Of FGC',
+      JAVATypeEchart: 'Number Of Java Threads Of Each Type',
+      JAVATimeEchart: 'Number Of Java Threads In Each Time Period',
+      MemoryEchart: 'Memory Usage Size',
+      BufferEchart: 'Cache Usage Size',
+      CPUEchart: 'CPU Time Proportion',
+      IOEchart: 'Disk I/O Throughput',
+      FileCountEchart: 'File Quantity Statistics',
+      FileSizeEchart: 'File Size Statistics',
+      WriteEchart: 'Write Success And Failure Statistics And Time Consumption',
+      SearchEchart: 'Query Success And Failure Statistics',
+      ApiEchart: 'Interface Time Consuming',
+      ApiQPSEchart: 'Interface QPS',
+
+      fgcCount: 'fgc Times',
+      ygcCount: 'ygc Times',
+      fgcTime: 'fgc Time Consuming',
+      ygcTime: 'ygc Time Consuming',
+      Ptotal: 'Total',
+      Pentries: 'Entries',
+      EachPage: 'EachPage',
+      slow: 'Slow',
+      nodata: 'No Data',
+    },
   },
 };
 
diff --git a/frontend/src/main.js b/frontend/src/main.js
index 3b786c0..ff0fe2a 100644
--- a/frontend/src/main.js
+++ b/frontend/src/main.js
@@ -28,6 +28,14 @@ import i18nFile from '@/i18n/index.js';
 import '@/styles/reset.scss';
 import '@/styles/element.scss';
 
+const VUE_APP_VERSION = require('../package.json').version;
+const vers = window.localStorage.getItem('appVersion');
+if (VUE_APP_VERSION != vers) {
+  localStorage.clear();
+  window.localStorage.setItem('appVersion', VUE_APP_VERSION);
+  location.reload();
+}
+
 i18n(i18nFile.global.t);
 const app = createApp(App);
 directive(app);
diff --git a/frontend/src/plugins/element_plus.js b/frontend/src/plugins/element_plus.js
index d2aee8a..6787d5e 100644
--- a/frontend/src/plugins/element_plus.js
+++ b/frontend/src/plugins/element_plus.js
@@ -1,3 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
 import 'element-plus/packages/theme-chalk/src/base.scss';
 import {
   ElLoading,
@@ -79,13 +97,15 @@ import {
   // ElMessageBox,
   // ElMessage,
   ElPopconfirm,
-  // ElNotification
+  // ElNotification,
+  ElConfigProvider,
 } from 'element-plus';
 
 export default {
   install: (Vue) => {
     Vue.use(ElPopconfirm);
     Vue.use(ElPagination);
+    Vue.use(ElLoading);
     // Vue.use(ElDrawer);
     Vue.use(ElDialog);
     // Vue.use(ElAutocomplete);
@@ -160,6 +180,7 @@ export default {
     // Vue.use(ElBacktop);
     // Vue.use(ElPageHeader);
     // Vue.use(ElCascaderPanel);
+    Vue.use(ElConfigProvider);
     // Vue.prototype.$message = ElMessage;
     // Vue.prototype.$alert = ElMessageBox.alert;
     // Vue.prototype.$confirm = ElMessageBox.confirm;
diff --git a/frontend/src/plugins/event_bus.js b/frontend/src/plugins/event_bus.js
index fe6fa93..b9f7666 100644
--- a/frontend/src/plugins/event_bus.js
+++ b/frontend/src/plugins/event_bus.js
@@ -1,3 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
 import mitt from 'mitt';
 const emitter = mitt();
 
diff --git a/frontend/src/plugins/index.js b/frontend/src/plugins/index.js
index ca386bb..d3b7ea3 100644
--- a/frontend/src/plugins/index.js
+++ b/frontend/src/plugins/index.js
@@ -1,3 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
 import element_plus from './element_plus';
 import event_bus from './event_bus';
 
diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js
index e62712c..0c8ce56 100644
--- a/frontend/src/router/index.js
+++ b/frontend/src/router/index.js
@@ -98,6 +98,23 @@ const routes = [
           },
         ],
       },
+      {
+        path: '/control',
+        name: 'Control',
+        component: () => import(/* webpackChunkName: "control" */ '../views/Control'),
+        children: [
+          {
+            path: 'indicator/:panel/:id/:mode',
+            name: 'Indicator',
+            component: () => import(/* webpackChunkName: "Indicator" */ '../views/Control/components/indicator.vue'),
+          },
+          {
+            path: 'query/:id',
+            name: 'Query',
+            component: () => import(/* webpackChunkName: "Query" */ '../views/Control/components/query.vue'),
+          },
+        ],
+      },
     ],
   },
   {
diff --git a/frontend/src/store/index.js b/frontend/src/store/index.js
index 44c3088..d016290 100644
--- a/frontend/src/store/index.js
+++ b/frontend/src/store/index.js
@@ -1,3 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
 import { createStore } from 'vuex';
 import moduleA from './moduleA';
 import axios from '@/util/axios.js';
diff --git a/frontend/src/styles/element.scss b/frontend/src/styles/element.scss
index 21e67e1..fc556db 100644
--- a/frontend/src/styles/element.scss
+++ b/frontend/src/styles/element.scss
@@ -20,6 +20,11 @@
 .el-button--primary {
   background-color: $theme-color !important;
   border-color: $theme-color !important;
+  &:hover.el-button,&:focus.el-button {
+    background:$theme-color !important;
+    border-color:$theme-color !important;
+    color: #fff;
+  }
   span {
     color: #fff;
   }
@@ -39,11 +44,17 @@
 }
 
 .el-button--default {
-  &:hover {
+  &:hover,
+  &:focus {
     color: #15c294 !important;
     background-color: transparent !important;
     border-color: #15c294 !important;
   }
+  //   &:focus {
+  //     color: none !important;
+  //     background-color: transparent !important;
+  //     border-color: #15c294 !important;
+  //   }
 }
 .button-delete:hover {
   background: #fff !important;
@@ -243,3 +254,52 @@
     }
   }
 }
+
+.el-select .el-input__inner:focus,
+.el-select .el-input.is-focus .el-input__inner {
+  border-color: $theme-bj-color !important;
+}
+.el-select-dropdown__item {
+  font-size: 12px;
+}
+.el-select-dropdown__item.selected {
+  color: $theme-bj-color;
+}
+// iotdb-table-style
+.iotdb-table {
+  width: 100%;
+  th {
+    font-weight: 500;
+    color: #808ba3 !important;
+    background-color: #f9fafc;
+  }
+  td {
+    font-weight: 400;
+    color: #333;
+  }
+  &.el-table--border th,
+  &.el-table--border td {
+    border-right: none;
+  }
+  &.el-table--border {
+    border-radius: 4px;
+  }
+  .el-table__row:last-child td {
+    border-bottom: none;
+  }
+  .el-table__body {
+    tr.hover-row > td {
+      background-color: #edf8f5 !important;
+    }
+  }
+  .el-table__fixed-right {
+    border-left: 1px solid #e7eaf2;
+    border-right: 1px solid #e7eaf2;
+  }
+}
+input.el-input__inner:hover {
+  border: 1px solid $theme-bj-color !important;
+}
+.el-loading-spinner .path {
+    stroke: $theme-color;
+}
diff --git a/frontend/src/styles/variables.scss b/frontend/src/styles/variables.scss
index 230d8c9..071bcae 100644
--- a/frontend/src/styles/variables.scss
+++ b/frontend/src/styles/variables.scss
@@ -18,5 +18,6 @@
  */
 
 $theme-color: #16c493ff;
-$border-color: #EEF0F5;
-$danger-color: #FB5151;
+$border-color: #eef0f5;
+$danger-color: #fb5151;
+$theme-bj-color: #15c294;
diff --git a/frontend/src/util/axios.js b/frontend/src/util/axios.js
index 369d688..193f878 100644
--- a/frontend/src/util/axios.js
+++ b/frontend/src/util/axios.js
@@ -22,7 +22,7 @@ import { ElMessage } from 'element-plus';
 import router from '../router';
 
 const instance = axios.create({});
-const headerUrls = ['/api/login', '/api/downloadFile/template'];
+const headerUrls = ['/api/login', '/api/downloadFile/template', '/api/downloadQueryLogFile'];
 const exportUrl = '/exportData';
 const downUrl = '/downloadFile';
 instance.defaults.withCredentials = true;
diff --git a/frontend/src/util/constant.js b/frontend/src/util/constant.js
index 8b396e3..75c390b 100644
--- a/frontend/src/util/constant.js
+++ b/frontend/src/util/constant.js
@@ -1,3 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
 export const DataGranularityMap = {
   dataLink: 'dataLink', // 数据连接
   group: 'groupPath', // 存储组
diff --git a/frontend/src/util/export.js b/frontend/src/util/export.js
index add3d56..d0b86c5 100644
--- a/frontend/src/util/export.js
+++ b/frontend/src/util/export.js
@@ -1,3 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
 export function handleExport(data, name) {
   const blob = new Blob([data]);
   const downloadElement = document.createElement('a');
diff --git a/frontend/src/util/setOperation.js b/frontend/src/util/setOperation.js
index 9b4e804..1a3fb48 100644
--- a/frontend/src/util/setOperation.js
+++ b/frontend/src/util/setOperation.js
@@ -1,3 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
 function array_remove_repeat(a) {
   // 去重
   var r = [];
diff --git a/frontend/src/views/About/index.vue b/frontend/src/views/About/index.vue
index 5f1f3d5..89204e6 100644
--- a/frontend/src/views/About/index.vue
+++ b/frontend/src/views/About/index.vue
@@ -26,12 +26,17 @@
       </div>
       <div class="lang-btn">
         <el-dropdown @command="handleLangCommand">
-          <span class="el-dropdown-link"> {{ [$t('rootPage.chinalang'), $t('rootPage.englishlang'), $t('rootPage.deutsch')][langIndex] }}<i class="el-icon-arrow-down el-icon--right"></i> </span>
+          <svg preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24" width="1.2em" height="1.2em" data-v-dd9c9540="">
+            <path
+              fill="currentColor"
+              d="m18.5 10l4.4 11h-2.155l-1.201-3h-4.09l-1.199 3h-2.154L16.5 10h2zM10 2v2h6v2h-1.968a18.222 18.222 0 0 1-3.62 6.301a14.864 14.864 0 0 0 2.336 1.707l-.751 1.878A17.015 17.015 0 0 1 9 13.725a16.676 16.676 0 0 1-6.201 3.548l-.536-1.929a14.7 14.7 0 0 0 5.327-3.042A18.078 18.078 0 0 1 4.767 8h2.24A16.032 16.032 0 0 0 9 10.877a16.165 16.165 0 0 0 2.91-4.876L2 6V4h6V2h2zm7.5 10.885L16.253 16h2.492L17.5 12.885z"
+            ></path>
+          </svg>
           <template #dropdown>
             <el-dropdown-menu>
-              <el-dropdown-item :disabled="langIndex === 0" command="0">{{ $t('rootPage.chinalang') }}</el-dropdown-item>
-              <el-dropdown-item :disabled="langIndex === 1" command="1">{{ $t('rootPage.englishlang') }}</el-dropdown-item>
-              <el-dropdown-item :disabled="langIndex === 2" command="2">{{ $t('rootPage.deutsch') }}</el-dropdown-item>
+              <el-dropdown-item :disabled="langIndex === 0" command="0">中文</el-dropdown-item>
+              <el-dropdown-item :disabled="langIndex === 1" command="1">English</el-dropdown-item>
+              <el-dropdown-item :disabled="langIndex === 2" command="2">Deutsch</el-dropdown-item>
             </el-dropdown-menu>
           </template>
         </el-dropdown>
@@ -107,7 +112,7 @@ export default {
     right: 0;
     .lang-btn {
       position: absolute;
-      right: 20px;
+      right: 40px;
       top: 50%;
       transform: translate(0, -50%);
     }
diff --git a/frontend/src/views/Control/api/index.js b/frontend/src/views/Control/api/index.js
new file mode 100644
index 0000000..4736ed3
--- /dev/null
+++ b/frontend/src/views/Control/api/index.js
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import axios from '@/util/axios.js';
+
+//Get Monitor List
+export function getMonitorList() {
+  return axios.get(`/servers/metrics/connection`);
+}
+
+//Get Single Monitor info
+export function getMonitorInfo(serverId) {
+  return axios.get(`/servers/${serverId}/metrics/datacount`);
+}
+
+//Get Metrics tableData
+export function getMetricsData(serverId, metricsType) {
+  return axios.get(`/servers/${serverId}/metrics/list/${metricsType}`);
+}
+//Get Search Metrics tableData
+export function getSearchMetricsData(serverId, mode) {
+  return axios.get(`/servers/${serverId}/metrics/list/query/${mode}`);
+}
+
+// Query Statistics API
+
+//Get  Classify List
+export function getClassifyList(serverId) {
+  return axios.get(`/servers/${serverId}/metrics/QueryClassification`);
+}
+
+//Get Current Classify Data
+export function getClassifyData(serverId, classifyId, query) {
+  return axios.get(`/servers/${serverId}/metrics/${classifyId}/selectcount`, {
+    params: query,
+  });
+}
+
+// Chart API
+
+//Get Chart data
+export function getChartData(serverId, metricsId) {
+  return axios.get(`/servers/${serverId}/metrics/diagram/?metricId=${metricsId}`);
+}
+
+// Get log
+export function getDownloadQueryLogFile(params) {
+  return axios.get(`/downloadQueryLogFile`, {
+    params,
+    responseType: 'blob',
+  });
+}
+
+//Get the measurement  list under the entity
+// export function getList(deviceData, data) {
+//     return axios.get(`/servers/${deviceData.connectionid}/storageGroups/${deviceData.storagegroupid}/devices/${deviceData.name}/info`, { params: data });
+// }
+// //Delete measurement
+// export function deleteData(deviceData, timeseriesName) {
+//     return axios.delete(`/servers/${deviceData.connectionid}/storageGroups/${deviceData.storagegroupid}/devices/${deviceData.name}/timeseries/${timeseriesName}`);
+// }
+// //Add / edit entity information
+// export function deviceAddEdite(serverId, groupName, data) {
+//     return axios.post(`/servers/${serverId}/storageGroups/${groupName}/devices`, data);
+// }
diff --git a/frontend/src/views/Control/components/indicator.vue b/frontend/src/views/Control/components/indicator.vue
new file mode 100644
index 0000000..9146dcd
--- /dev/null
+++ b/frontend/src/views/Control/components/indicator.vue
@@ -0,0 +1,77 @@
+<!--
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+-->
+
+<template>
+  <div class="main-center">
+    <div class="main-center-container">
+      <indicator-list v-if="panelMode === 'list'" :currentData="currentData" />
+
+      <indicator-panel v-else :currentData="currentData" />
+    </div>
+  </div>
+</template>
+
+<script>
+import { computed, ref, watch } from 'vue';
+import IndicatorPanel from './indicatorPanel';
+import IndicatorList from './indicatorList';
+import { useRoute } from 'vue-router';
+export default {
+  name: 'Indicator',
+  props: {
+    data: {
+      type: Object,
+    },
+  },
+  components: {
+    IndicatorPanel,
+    IndicatorList,
+  },
+  setup(props) {
+    let route = useRoute();
+    let panelMode = ref('list');
+    let currentData = computed(() => props.data);
+    watch(
+      () => route.params.panel,
+      (newValue) => {
+        panelMode.value = newValue;
+      },
+      {
+        immediate: true,
+      }
+    );
+    return {
+      panelMode,
+      currentData,
+    };
+  },
+};
+</script>
+
+<style scoped lang="scss">
+.main-center {
+  min-height: calc(100% - 243px);
+  padding: 20px;
+  background: #f9fbfc;
+  &-container {
+    background: #fff;
+    border-radius: 4px;
+  }
+}
+</style>
diff --git a/frontend/src/views/Control/components/indicatorChart.vue b/frontend/src/views/Control/components/indicatorChart.vue
new file mode 100644
index 0000000..4a34a71
--- /dev/null
+++ b/frontend/src/views/Control/components/indicatorChart.vue
@@ -0,0 +1,115 @@
+<!--
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+-->
+<template>
+  <div class="chart-box">
+    <div class="chart-title">{{ title }}</div>
+    <div class="chart-con">
+      <div class="chart" ref="chartDom"></div>
+    </div>
+  </div>
+</template>
+
+<script>
+import * as echarts from 'echarts';
+import { onMounted, ref, watch } from 'vue';
+export default {
+  name: 'IndicatorChart',
+  props: {
+    data: {
+      type: Object,
+    },
+  },
+  setup(props) {
+    let chartDom = ref();
+    let Chart;
+    onMounted(() => {
+      let option;
+      console.log(chartDom.value);
+      Chart = echarts.init(chartDom.value);
+      option = {
+        tooltip: {
+          trigger: 'axis',
+          textStyle: {
+            align: 'left',
+          },
+        },
+        grid: {
+          top: '20px',
+          left: '20px',
+          right: '20px',
+          bottom: '44px',
+          containLabel: true,
+        },
+        xAxis: {
+          type: 'category',
+          axisTick: { show: false },
+          axisLine: { show: true, lineStyle: { color: ' #eee', width: 1 } },
+          axisLabel: { show: true, textStyle: { color: '#8E97AA' } },
+        },
+        yAxis: {
+          type: 'value',
+          axisLine: { show: true, lineStyle: { color: ' #eee', width: 1 } },
+          axisLabel: { show: true, textStyle: { color: '#8E97AA' } },
+          splitLine: { show: false },
+        },
+      };
+      Object.assign(option, props.data.options);
+      option && Chart.setOption(option);
+    });
+    watch(
+      () => props.data,
+      (newValue) => {
+        if (newValue) {
+          Chart.setOption(newValue.options);
+        }
+      },
+      {
+        deep: true,
+      }
+    );
+
+    return {
+      title: props.data.title,
+      chartDom,
+    };
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.chart-box {
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+}
+.chart-con {
+  flex: 1;
+}
+.chart {
+  height: 100%;
+}
+.chart-title {
+  padding: 20px 0 0 20px;
+  font-size: 14px;
+  font-weight: 500;
+  color: #333;
+  line-height: 22px;
+  text-align: left;
+}
+</style>
diff --git a/frontend/src/views/Control/components/indicatorList.vue b/frontend/src/views/Control/components/indicatorList.vue
new file mode 100644
index 0000000..a002ed1
--- /dev/null
+++ b/frontend/src/views/Control/components/indicatorList.vue
@@ -0,0 +1,370 @@
+<!--
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+-->
+<template>
+  <div>
+    <div class="main-center-container-head">
+      <div class="select-box">
+        <el-select v-model="mode" class="m-2" placeholder="Select">
+          <el-option v-for="item in modeOptions" :key="item.value" :label="item.label" :value="item.value" />
+        </el-select>
+      </div>
+      <div v-if="showBtnGroup">
+        <el-button type="primary" @click="handleChangePanel">{{ $t('controlPage.chartBtn') }}</el-button>
+        <el-button @click="handleRefres">{{ $t('common.refresh') }}</el-button>
+      </div>
+    </div>
+    <div class="main-center-container-center">
+      <div class="main-center-container-center-left">
+        <el-tabs v-model="activeName" tab-position="left" style="height: 200px" class="demo-tabs">
+          <el-tab-pane v-for="(item, index) in tabPanelOptions" :key="index" v-bind="item"></el-tab-pane>
+        </el-tabs>
+      </div>
+      <div class="main-center-container-center-right">
+        <div class="search-way" v-if="activeName === 'search'">
+          <span :class="{ active: searchWay === '0' }" @click="handleSearchWay('0')">Top Sql</span>
+          <span :class="{ active: searchWay === '1' }" @click="handleSearchWay('1')">{{ $t('controlPage.slowSearch') }}</span>
+        </div>
+        <el-table class="iotdb-table" border style="width: 100%" :empty-text="$t('controlPage.nodata')" :data="tableData">
+          <template v-for="(item, index) in tableColumn">
+            <el-table-column v-if="!item.fixed" :key="'col' + index" v-bind="item"></el-table-column>
+
+            <el-table-column v-else :key="'colfixed' + index" fixed="right" :label="$t('common.operation')" width="140">
+              <template #default="scoped">
+                <el-button v-if="scoped.row.detailAvailable !== 0" type="text" size="small" @click="handleDeatil(scoped)">{{ $t('common.detail') }}</el-button>
+              </template>
+            </el-table-column>
+          </template>
+        </el-table>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { computed, onMounted, ref, watch } from 'vue';
+import { useRoute, useRouter } from 'vue-router';
+import { useI18n } from 'vue-i18n';
+import { getMetricsData, getSearchMetricsData } from '../api';
+
+export default {
+  name: 'IndicatorList',
+  props: {
+    currentData: {
+      type: Object,
+    },
+  },
+  setup(props) {
+    let { t } = useI18n();
+    let router = useRouter();
+    let route = useRoute();
+    let mode = ref(route.query.mode || '0');
+    let activeName = ref(route.params.mode || 'JVM');
+    let searchWay = ref('0');
+    let tableData = ref();
+
+    let modeOptions = computed(() => [
+      {
+        label: t('controlPage.allMode'),
+        value: '0',
+      },
+      {
+        label: t('controlPage.devMode'),
+        value: '1',
+      },
+      {
+        label: t('controlPage.opeMode'),
+        value: '2',
+      },
+    ]);
+    let tableColumn = computed(() => {
+      const temp = [
+        {
+          prop: 'name',
+          label: t('controlPage.iName'),
+          minWidth: '160px',
+        },
+        {
+          prop: 'latestScratchTime',
+          label: t('controlPage.zxjg'),
+          minWidth: '160px',
+        },
+        {
+          prop: 'latestResult',
+          label: t('controlPage.zxzb'),
+          minWidth: '160px',
+        },
+        { fixed: true },
+      ];
+      if (activeName.value === 'JVM') {
+        let formatCol = [...temp];
+        formatCol.splice(1, 0, {
+          prop: 'metricType',
+          label: t('controlPage.iType'),
+          minWidth: '120px',
+        });
+        return formatCol;
+      } else if (activeName.value === 'search') {
+        return [
+          {
+            prop: 'sqlstatement',
+            minWidth: '372',
+            label: t('controlPage.sql'),
+          },
+          {
+            prop: 'runningTime',
+            minWidth: '160',
+            label: t('controlPage.runTime'),
+          },
+          {
+            prop: 'executionTime',
+            sortable: true,
+            minWidth: '140',
+            label: t('controlPage.exeTime'),
+          },
+        ];
+      } else if (activeName.value === 'memory') {
+        let formatCol = [...temp];
+        formatCol.pop();
+        return formatCol;
+      }
+      return temp;
+    });
+    let targetType = computed(() => [
+      {
+        label: t('controlPage.jvm'),
+        name: 'JVM',
+        type: '1',
+      },
+      {
+        label: t('controlPage.cpu'),
+        name: 'CPU',
+        type: '2',
+      },
+      {
+        label: t('controlPage.memory'),
+        name: 'memory',
+        type: '2',
+      },
+      {
+        label: t('controlPage.store'),
+        name: 'store',
+        type: '2',
+      },
+      {
+        label: t('controlPage.write'),
+        name: 'write',
+        type: '0',
+      },
+      {
+        label: t('controlPage.isearch'),
+        name: 'search',
+        type: '0',
+      },
+    ]);
+    let tabPanelOptions = computed(() => {
+      let filterData = targetType.value.filter(
+        (item) => {
+          if (mode.value === '0' || item.type === mode.value || item.type === '0') {
+            return true;
+          }
+        },
+        { immediate: true }
+      );
+      checkedTarget(filterData, activeName.value);
+      return filterData;
+    });
+    let showBtnGroup = computed(() => props.currentData?.status);
+    watch(
+      activeName,
+      (newVlaue) => {
+        initTableData(props.currentData, newVlaue);
+        router.push({
+          path: `/control/indicator/${route.params?.panel || 'list'}/${props.currentData.serverId}/${newVlaue}`,
+          query: { ...route.query },
+        });
+      },
+      {
+        immediate: true,
+      }
+    );
+    watch(
+      () => route.params.mode,
+      (newValue) => {
+        newValue && (activeName.value = newValue);
+      },
+      {
+        immediate: true,
+      }
+    );
+    watch(
+      () => route.query.mode,
+      (newValue) => {
+        newValue && (mode.value = newValue);
+      }
+    );
+    watch(
+      () => props.currentData,
+      (newValue) => {
+        initTableData(newValue, activeName.value);
+      }
+    );
+    onMounted(() => {});
+    function handleRefres() {
+      initTableData(props.currentData, activeName.value);
+    }
+    function handleSearchWay(way) {
+      if (searchWay.value !== way) {
+        searchWay.value = way;
+        initTableData(props.currentData, activeName.value);
+      }
+    }
+    function checkedTarget(data, type) {
+      for (let i = 0, len = data.length; i < len; i++) {
+        if (data[i].name === type) {
+          return false;
+        }
+      }
+      data[0]?.name && (activeName.value = data[0].name);
+    }
+    function handleDeatil({ row }) {
+      let modeMap = {
+        JVM: {
+          1: 0,
+          2: 1,
+          3: 2,
+          4: 3,
+        },
+      };
+      let type = activeName.value === 'JVM' ? modeMap[activeName.value][row.detailAvailable] : undefined;
+      router.push({ path: `/control/indicator/chart/${route.params.id}/${route.params.mode}`, query: { mode: mode.value, type } });
+    }
+    function initTableData(data, mode) {
+      const modeMap = {
+        JVM: 0,
+        CPU: 1,
+        memory: 2,
+        store: 3,
+        write: 4,
+      };
+      if (!data?.status) {
+        tableData.value = [];
+        return;
+      }
+      // 数据列表信息
+      if (mode !== 'search') {
+        getMetricsData(data.serverId, modeMap[mode]).then((res) => {
+          tableData.value = res.data.listInfo;
+        });
+      } else {
+        getSearchMetricsData(data.serverId, searchWay.value).then((res) => {
+          tableData.value = res.data.queryMetricsVOs;
+        });
+      }
+    }
+    function handleChangePanel() {
+      router.push({ path: `/control/indicator/chart/${route.params.id}/${route.params.mode}`, query: { mode: mode.value } });
+    }
+    return {
+      mode,
+      activeName,
+      modeOptions,
+      tabPanelOptions,
+      tableData,
+      tableColumn,
+      searchWay,
+      showBtnGroup,
+
+      handleDeatil,
+      handleSearchWay,
+      handleChangePanel,
+      handleRefres,
+    };
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.main-center-container {
+  background: #fff;
+  border-radius: 4px;
+  &-head {
+    display: flex;
+    justify-content: space-between;
+    padding: 12px 20px;
+    border: 1px solid #eaecf0;
+  }
+  &-center {
+    padding: 20px 20px 20px 0;
+    display: flex;
+    border: 1px solid #eaecf0;
+    border-top: none;
+    &-left {
+      margin-right: 15px;
+      &:deep .el-tabs__active-bar {
+        min-width: 0 !important;
+      }
+      &:deep .el-tabs__item {
+        height: 24px;
+        line-height: 24px;
+        color: #8e97aa;
+        font-size: 12px !important;
+        &.is-active {
+          color: $theme-bj-color !important;
+        }
+      }
+      &:deep .el-tabs__nav-scroll,
+      &:deep .el-tabs__nav-wrap {
+        height: auto;
+      }
+    }
+    &-right {
+      width: 100px;
+      flex: 1;
+      .search-way {
+        display: flex;
+        font-size: 12px;
+        margin-bottom: 20px;
+        span {
+          padding: 8px 21px;
+          color: #8e97aa;
+          border: 1px solid #eaecf0;
+          cursor: pointer;
+          &.active {
+            background: #edf8f5;
+            color: #15c294;
+          }
+        }
+        span:first-child {
+          border-radius: 4px 0 0 4px;
+          border-right: none;
+        }
+        span:last-child {
+          border-radius: 0 4px 4px 0;
+        }
+      }
+    }
+  }
+  .select-box {
+    width: 120px;
+    &:deep .el-input__inner {
+      font-size: 12px;
+    }
+  }
+}
+</style>
diff --git a/frontend/src/views/Control/components/indicatorPanel.vue b/frontend/src/views/Control/components/indicatorPanel.vue
new file mode 100644
index 0000000..3d78d6b
--- /dev/null
+++ b/frontend/src/views/Control/components/indicatorPanel.vue
@@ -0,0 +1,414 @@
+<!--
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+-->
+<template>
+  <div class="indicator-list">
+    <div class="list-head">
+      <div class="select-box">
+        <el-select v-model="modeValue" class="selece-box-mode" placeholder="请选择">
+          <el-option v-for="item in modeOptions" :key="item.value" :label="item.label" :value="item.value" />
+        </el-select>
+        <el-select v-model="typeValue" class="selece-box-type" :placeholder="$t('controlPage.selectMetrics')">
+          <el-option v-for="item in typeOptions" :key="item.value" :label="item.label" :value="item.value" />
+        </el-select>
+      </div>
+      <div>
+        <el-button type="primary" @click="handleChangePanel">{{ $t('controlPage.listBtn') }}</el-button>
+        <el-button @click="handleRefresh">{{ $t('common.refresh') }}</el-button>
+      </div>
+    </div>
+    <div class="list-center">
+      <div v-for="(item, index) in chartList" v-show="isShowChart(item.name)" :key="'chart' + index" :class="['chart-item', 'chart-' + item.size]" :tag="item.name">
+        <IndicatorChart v-if="ChartObject[item.name]" :data="ChartObject[item.name]" />
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { computed, nextTick, onMounted, onUnmounted, reactive, ref, watch } from 'vue';
+import { useRouter, useRoute } from 'vue-router';
+import IndicatorChart from './indicatorChart';
+import useInitChart from '../hooks/useInitChart';
+import { useI18n } from 'vue-i18n';
+export default {
+  name: 'IndicatorPanel',
+  props: {
+    currentData: {
+      type: Object,
+    },
+  },
+  components: {
+    IndicatorChart,
+  },
+  setup(props) {
+    const typeMap = {
+      CPU: '4',
+      memory: '5',
+      store: '6',
+      write: '7',
+      search: '8',
+    };
+    const chartList = [
+      {
+        name: 'GCEchart',
+        size: '50',
+      },
+
+      {
+        name: 'JVMClassEchart',
+        size: '50',
+      },
+      {
+        name: 'YGCEchart',
+        size: '100',
+      },
+      {
+        name: 'FGCEchart',
+        size: '100',
+      },
+      {
+        name: 'JAVATypeEchart',
+        size: '50',
+      },
+      {
+        name: 'JAVATimeEchart',
+        size: '50',
+      },
+      {
+        name: 'MemoryEchart',
+        size: '50',
+      },
+      {
+        name: 'BufferEchart',
+        size: '50',
+      },
+      {
+        name: 'CPUEchart',
+        size: '50',
+      },
+      {
+        name: 'IOEchart',
+        size: '50',
+      },
+      {
+        name: 'FileCountEchart',
+        size: '50',
+      },
+      {
+        name: 'FileSizeEchart',
+        size: '50',
+      },
+      {
+        name: 'WriteEchart',
+        size: '50',
+      },
+      {
+        name: 'SearchEchart',
+        size: '50',
+      },
+      {
+        name: 'ApiEchart',
+        size: '50',
+      },
+      {
+        name: 'ApiQPSEchart',
+        size: '50',
+      },
+    ];
+    let DOMQueue = [];
+    let { t, locale } = useI18n();
+    let router = useRouter();
+    let route = useRoute();
+    let modeValue = ref(route.query?.mode || '0');
+    const typeDefault = route.params.mode === 'JVM' ? route.query.type : typeMap[route.params.mode];
+    let typeValue = ref(typeDefault);
+    let ChartObject = reactive({
+      GCEchart: null,
+      JVMClassEchart: null,
+      YGCEchart: null,
+      FGCEchart: null,
+      JAVATypeEchart: null,
+      JAVATimeEchart: null,
+      MemoryEchart: null,
+      BufferEchart: null,
+      CPUEchart: null,
+      IOEchart: null,
+      FileCountEchart: null,
+      FileSizeEchart: null,
+      WriteEchart: null,
+      SearchEchart: null,
+      ApiEchart: null,
+      ApiQPSEchart: null,
+    });
+
+    let modeOptions = computed(() => [
+      {
+        label: t('controlPage.allMode'),
+        value: '0',
+      },
+      {
+        label: t('controlPage.devMode'),
+        value: '1',
+      },
+      {
+        label: t('controlPage.opeMode'),
+        value: '2',
+      },
+    ]);
+    let typeOptions = computed(() => {
+      const JVM = [
+        {
+          label: t('controlPage.JVMThread'),
+          value: '0',
+        },
+        {
+          label: t('controlPage.JVMRecycle'),
+          value: '1',
+        },
+        {
+          label: t('controlPage.JVMMemory'),
+          value: '2',
+        },
+        {
+          label: t('controlPage.JVMClasses'),
+          value: '3',
+        },
+      ];
+      const CPU = {
+        label: t('controlPage.cpu'),
+        value: '4',
+      };
+      const memory = {
+        label: t('controlPage.memory'),
+        value: '5',
+      };
+      const store = {
+        label: t('controlPage.store'),
+        value: '6',
+      };
+      const write = {
+        label: t('controlPage.write'),
+        value: '7',
+      };
+      const search = {
+        label: t('controlPage.isearch'),
+        value: '8',
+      };
+      if (modeValue.value === '0') {
+        return [...JVM, CPU, memory, store, write, search];
+      } else if (modeValue.value === '1') {
+        return [...JVM, write, search];
+      } else {
+        return [CPU, memory, store, write, search];
+      }
+    });
+    watch(modeValue, () => {
+      typeValue.value = '';
+      nextTick(() => {
+        lazyChart();
+      });
+    });
+    watch(typeValue, () => {
+      nextTick(() => {
+        lazyChart();
+      });
+    });
+    watch(
+      () => props.currentData,
+      () => {
+        handleRefresh();
+      }
+    );
+    watch(locale, async () => {
+      handleRefresh('0');
+    });
+    onMounted(() => {
+      DOMQueue = [...document.querySelectorAll('.chart-item')];
+      let main = document.querySelectorAll('main')[0];
+      main?.addEventListener('scroll', lazyChart);
+      main && lazyChart();
+    });
+    onUnmounted(() => {
+      document.querySelectorAll('main')[0]?.removeEventListener('scroll', lazyChart);
+    });
+
+    function isShowChart(name) {
+      // JVM-线程
+      let JVMThreadMap = {
+        JAVATypeEchart: true,
+        JAVATimeEchart: true,
+      };
+      // JVM-垃圾回收
+      let JVMRecycleMap = {
+        YGCEchart: true,
+        FGCEchart: true,
+        GCEchart: true,
+      };
+      // JVM-内存
+      let JVMMemoryMap = {
+        BufferEchart: true,
+      };
+      // JVM-classes
+      let JVMclassesMap = {
+        JVMClassEchart: true,
+      };
+      //   CPU
+      let CPUMap = {
+        CPUEchart: true,
+      };
+      //   内存
+      let MemoryMap = {
+        MemoryEchart: true,
+      };
+      //   存储
+      let StoreMap = {
+        IOEchart: true,
+        FileCountEchart: true,
+        FileSizeEchart: true,
+      };
+      //   写入
+      let WriteMap = {
+        WriteEchart: true,
+      };
+      //   查询
+      let SearchMap = {
+        SearchEchart: true,
+        ApiEchart: true,
+        ApiQPSEchart: true,
+      };
+      let devChartMap = {
+        ...JVMThreadMap,
+        ...JVMRecycleMap,
+        ...JVMMemoryMap,
+        ...JVMclassesMap,
+      };
+      let operatorMap = {
+        ...CPUMap,
+        ...MemoryMap,
+        ...StoreMap,
+        ...WriteMap,
+        ...SearchMap,
+      };
+      let type2Map = {
+        0: JVMThreadMap,
+        1: JVMRecycleMap,
+        2: JVMMemoryMap,
+        3: JVMclassesMap,
+        4: CPUMap,
+        5: MemoryMap,
+        6: StoreMap,
+        7: WriteMap,
+        8: SearchMap,
+      };
+      if (!typeValue.value) {
+        if (modeValue.value === '0') {
+          return true;
+        } else if (modeValue.value === '1') {
+          return devChartMap[name];
+        } else if (modeValue.value === '2') {
+          return operatorMap[name];
+        }
+      } else {
+        return type2Map[typeValue.value][name];
+      }
+    }
+    function handleChangePanel() {
+      router.push({ path: `/control/indicator/list/${route.params.id}/${route.params.mode}`, query: { mode: modeValue.value } });
+    }
+    function lazyChart(refreshData) {
+      DOMQueue.filter((item) => {
+        let tag = item.getAttribute('tag');
+        if (window.getComputedStyle(item).display !== 'none' && !ChartObject[tag]) {
+          return true;
+        }
+      }).map((item) => {
+        let rect = item.getBoundingClientRect();
+        if (rect.top + 100 <= (window.innerHeight || document.documentElement.clientHeight) && rect.top > -300) {
+          initMatchChart(item.getAttribute('tag'), refreshData);
+        }
+      });
+    }
+    async function initMatchChart(name, refreshData) {
+      let result = await useInitChart(props.currentData.serverId, name, refreshData);
+      ChartObject[name] = result;
+    }
+    function handleRefresh(refreshData) {
+      let arr = [...document.querySelectorAll('.chart-item')];
+      arr.map((item) => {
+        if (window.getComputedStyle(item).display !== 'none') {
+          let chartName = item.getAttribute('tag');
+          ChartObject[chartName] = null;
+        }
+      });
+      lazyChart(refreshData);
+    }
+    return {
+      modeValue,
+      chartList,
+      typeValue,
+      modeOptions,
+      typeOptions,
+      ChartObject,
+
+      handleChangePanel,
+      handleRefresh,
+      isShowChart,
+    };
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.indicator-list {
+  .list-head {
+    display: flex;
+    justify-content: space-between;
+    padding: 12px 20px;
+    border: 1px solid #eaecf0;
+  }
+  .list-center {
+    display: grid;
+    border-left: 1px solid #eaecf0;
+    grid-template-columns: repeat(2, 50%);
+  }
+  .chart-item {
+    box-sizing: border-box;
+    height: 304px;
+    border-right: 1px solid #eaecf0;
+    border-bottom: 1px solid #eaecf0;
+  }
+  .chart-50 {
+    width: 100%;
+  }
+  .chart-100 {
+    grid-column: 1/3;
+    width: 100%;
+  }
+}
+.selece-box-mode {
+  width: 120px;
+}
+.selece-box-type {
+  margin-left: 20px;
+  width: 200px;
+}
+:deep .el-select .el-input__inner {
+  font-size: 12px;
+}
+</style>
diff --git a/frontend/src/views/Control/components/query.vue b/frontend/src/views/Control/components/query.vue
new file mode 100644
index 0000000..07115fe
--- /dev/null
+++ b/frontend/src/views/Control/components/query.vue
@@ -0,0 +1,536 @@
+<!--
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+-->
+
+<template>
+  <div class="query-main">
+    <div class="query-main-container">
+      <div class="container-left">
+        <el-tabs v-model="activeType" tab-position="left" style="height: 200px" class="demo-tabs">
+          <el-tab-pane v-for="(item, index) in tabPanelOptions" :key="index" :label="item.name" :name="item.id + ''">
+            <template #label>
+              <div class="tab-label" :key="index">{{ item.name }}</div>
+            </template>
+          </el-tab-pane>
+        </el-tabs>
+      </div>
+      <div v-if="tabPanelOptions?.length" class="container-right">
+        <div class="container-right-tip">
+          <div>
+            {{ $t('controlPage.lastTime') }} <span>{{ formatInfo(latestTime) }}</span>
+          </div>
+          <div>
+            {{ $t('controlPage.runCount') }}<span>{{ formatInfo(runTotal) }}</span>
+          </div>
+        </div>
+        <div class="container-right-operate">
+          <div class="operate-item">
+            <span class="input-label">{{ $t('controlPage.querySentence') }}:</span>
+            <div class="input-box">
+              <el-input v-model="searchValue" class="w-50 m-2" :placeholder="$t('common.placeHolder')" />
+            </div>
+          </div>
+          <div class="operate-item">
+            <span class="input-label">{{ $t('controlPage.runTime') }}:</span>
+            <div class="date-box">
+              <el-config-provider :locale="langLocale">
+                <!-- <el-color-picker :model-value="''" style="vertical-align: middle" /> -->
+                <el-date-picker v-model="runTime" size="mini" type="datetimerange" :start-placeholder="$t('common.startTime')" :end-placeholder="$t('common.endTime')" />
+              </el-config-provider>
+            </div>
+          </div>
+          <div class="operate-item">
+            <span class="input-label">{{ $t('controlPage.exeResult') }}:</span>
+            <div class="select-box">
+              <el-select v-model="runResult" class="m-2" :placeholder="$t('common.selectPlaceholder')">
+                <el-option v-for="item in resultOptions" :key="item.value" :label="item.label" :value="item.value" />
+              </el-select>
+            </div>
+          </div>
+          <el-button class="operate-btn" @click="handleSearch">{{ $t('common.query') }}</el-button>
+        </div>
+        <div class="container-right-table">
+          <el-table class="iotdb-table" border style="width: 100%" :empty-text="$t('controlPage.nodata')" :data="tableData">
+            <template v-for="(item, index) in tableColumn">
+              <el-table-column v-if="item.isSlowQuery" :key="'col' + index" label="" align="right" :width="item.maxWidth">
+                <template #default="scope">
+                  <div class="slow-query" v-if="scope.row.isSlowQuery">{{ $t('controlPage.slow') }}</div>
+                </template>
+              </el-table-column>
+              <el-table-column v-else-if="item.type === 'result'" :key="'col' + index" :label="$t('controlPage.exeResult')">
+                <template #default="scope">
+                  <div :class="+scope.row.executionResult === 1 ? 'success' : 'error'">{{ +scope.row.executionResult === 1 ? $t('common.success') : $t('common.fail') }}{{ scope.row.result }}</div>
+                </template>
+              </el-table-column>
+              <el-table-column v-else-if="item.fixed === 'right'" :key="'colright' + index" v-bind="item">
+                <template #default="scoped">
+                  <el-button type="text" size="small" @click="handleDownload(scoped)">{{ $t('controlPage.downloadLog') }}</el-button>
+                </template>
+              </el-table-column>
+              <el-table-column v-else :key="'col' + index" show-overflow-tooltip v-bind="item"></el-table-column>
+            </template>
+          </el-table>
+          <!-- :page-sizes="[10, 25,50, 100]" -->
+          <div class="table-pagination">
+            <el-pagination layout="slot" v-model:currentPage="currentPage" v-model:page-size="pageSize" :total="totalCount">
+              <span class="pagination-title">{{ $t('controlPage.Ptotal') }} {{ totalCount }} {{ $t('controlPage.Pentries') }}</span>
+            </el-pagination>
+            <el-pagination layout="slot" v-model:currentPage="currentPage" v-model:page-size="pageSize" :total="totalCount">
+              {{ $t('controlPage.EachPage')
+              }}<el-select class="pageSelectContainer" v-model="pageSize" @change="handlePageSize" placeholder="请选择">
+                <el-option v-for="item in optionsPage" :key="item.value" :label="item.label" :value="item.value"> </el-option> </el-select
+              >{{ $t('controlPage.Pentries') }}
+            </el-pagination>
+            <el-pagination
+              v-model:currentPage="currentPage"
+              v-model:page-size="pageSize"
+              layout="prev, pager, next"
+              :total="totalCount"
+              @size-change="handlePageSize"
+              @current-change="handleCurrentPage"
+            >
+            </el-pagination>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+// @ is an alias to /src
+import { get } from 'lodash';
+import { ElMessage } from 'element-plus';
+import { handleExport } from '@/util/export';
+import { computed, nextTick, onMounted, reactive, ref, toRefs, watch } from 'vue';
+import { useI18n } from 'vue-i18n';
+import zhCn from 'element-plus/lib/locale/lang/zh-cn';
+import en from 'element-plus/lib/locale/lang/en';
+import de from 'element-plus/lib/locale/lang/de';
+// import { ElTabs } from 'element-plus';
+// import { useRouter } from 'vue-router';
+import { getClassifyList, getClassifyData, getDownloadQueryLogFile } from '../api';
+
+export default {
+  name: 'Query',
+  props: {
+    data: {
+      type: Object,
+    },
+  },
+  setup(props) {
+    let { t, locale } = useI18n();
+    let tabPanelOptions = ref();
+    let tableColumnType = ref(true);
+    let activeType = ref();
+    let runTime = ref('');
+    let tableData = ref();
+    let runResult = ref('0');
+    let searchValue = ref('');
+    let pageReactive = reactive({
+      latestTime: '',
+      runTotal: '',
+    });
+    let tablePage = reactive({
+      totalCount: 0,
+      currentPage: 1,
+      pageSize: 10,
+    });
+    let resultOptions = computed(() => [
+      {
+        label: t('common.all'),
+        value: '0',
+      },
+      {
+        label: t('common.success'),
+        value: '1',
+      },
+      {
+        label: t('common.fail'),
+        value: '2',
+      },
+    ]);
+    let langLocale = computed(() => {
+      let lang = locale.value;
+      let langMap = {
+        'zh-cn': zhCn,
+        en,
+        de,
+      };
+      return langMap[lang];
+    });
+    let tableColumn = computed(() => {
+      let flagWidthMap = {
+        'zh-cn': 40,
+        en: 50,
+        de: 80,
+      };
+      let ctrlWidthMap = {
+        'zh-cn': 100,
+        en: 120,
+        de: 170,
+      };
+      let temp = [
+        {
+          label: '',
+          maxWidth: flagWidthMap[locale.value] || 80,
+          isSlowQuery: true,
+        },
+        {
+          label: t('controlPage.runTime'),
+          prop: 'runningTime',
+          width: '170px',
+        },
+        {
+          label: t('controlPage.querySentence'),
+          prop: 'statement',
+          minWidth: '180px',
+        },
+        {
+          label: t('controlPage.totalUseTime'),
+          prop: 'totalTime',
+          minWidth: '100px',
+        },
+      ];
+      if (tableColumnType.value === 0) {
+        temp = [
+          ...temp,
+          {
+            label: t('controlPage.garmmarUseTime'),
+            prop: 'analysisTime',
+            minWidth: '120px',
+          },
+          {
+            label: t('controlPage.ybyUseTime'),
+            prop: 'precompiledTime',
+            minWidth: '120px',
+          },
+          {
+            label: t('controlPage.yhbyUseTime'),
+            prop: 'optimizedTime',
+            minWidth: '120px',
+          },
+          {
+            label: t('controlPage.exeUseTime'),
+            prop: 'executionTime',
+            minWidth: '150px',
+          },
+        ];
+      }
+      temp.push({
+        label: t('controlPage.exeResult'),
+        prop: 'executionResult',
+        minWidth: '80px',
+        type: 'result',
+      });
+      temp.push({
+        fixed: 'right',
+        label: t('common.operation'),
+        width: ctrlWidthMap[locale.value],
+      });
+      return temp;
+    });
+    let optionsPage = computed(() => [
+      {
+        label: '10',
+        value: 10,
+      },
+      {
+        label: '20',
+        value: 20,
+      },
+      {
+        label: '30',
+        value: 30,
+      },
+    ]);
+    watch(activeType, (newValue) => {
+      if (newValue) {
+        QueryDataInit();
+        tabPanelOptions.value.every((item) => {
+          if (item.id + '' === newValue + '') {
+            tableColumnType.value = item.flag;
+            return false;
+          }
+          return true;
+        });
+        getCurrentQueryData(props.data.serverId, newValue);
+      }
+    });
+    watch(
+      () => props.data,
+      (newValue) => {
+        if (newValue.status) {
+          getClassifyList(newValue.serverId).then((res) => {
+            tabPanelOptions.value = res.data.classificationList;
+            activeType.value = '';
+            nextTick(() => {
+              activeType.value = tabPanelOptions.value[0]?.id + '';
+            });
+          });
+        } else {
+          tabPanelOptions.value = [];
+          tableData.value = [];
+          QueryDataInit();
+        }
+      },
+      { immediate: true }
+    );
+
+    onMounted(() => {});
+    function QueryDataInit() {
+      pageReactive.latestTime = '';
+      pageReactive.runTotal = '';
+      tablePage.currentPage = 1;
+      tablePage.pageSize = 10;
+      tablePage.totalCount = 0;
+      runResult.value = '0';
+      searchValue.value = '';
+    }
+    function handlePageSize() {
+      tablePage.currentPage = 1;
+      getCurrentQueryData();
+    }
+    function handleCurrentPage() {
+      getCurrentQueryData();
+    }
+    function getCurrentQueryData() {
+      if (!props.data.status) {
+        tableData.value = [];
+        return;
+      }
+      let query = {
+        pageSize: tablePage.pageSize,
+        pageNum: tablePage.currentPage,
+        filterString: searchValue.value || undefined,
+        startTime: (runTime.value && runTime.value[0]?.getTime()) || undefined,
+        endTime: (runTime.value && runTime.value[1]?.getTime()) || undefined,
+        executionResult: runResult.value,
+      };
+
+      getClassifyData(props.data.serverId, activeType.value, query).then((res) => {
+        let { data } = res;
+        pageReactive.latestTime = data.latestRunningTime;
+        pageReactive.runTotal = data.totalCount;
+        tableData.value = data.filteredQueryDataStrVOSList;
+        tablePage.totalCount = data.totalCount;
+      });
+    }
+    function handleSearch() {
+      getCurrentQueryData();
+    }
+    async function handleDownload({ row }) {
+      const res = await getDownloadQueryLogFile({
+        SQLStatement: row.statement,
+        timeStamp: new Date(row.runningTime).valueOf(),
+      });
+      let filename = get(res, 'headers.content-disposition');
+      filename = filename ? decodeURI(filename.replace(/.*(?=filename=)filename=/, '')) : '';
+      if (get(res, 'data.type') === 'application/json') {
+        return ElMessage({
+          type: 'error',
+          message: `${t('common.fail')}`,
+        });
+      }
+      handleExport(res.data, filename);
+      ElMessage({
+        type: 'success',
+        message: `${t('device.exportSucceeded')}`,
+      });
+    }
+    function formatInfo(val) {
+      return val || '-';
+    }
+    return {
+      searchValue,
+      activeType,
+      tabPanelOptions,
+      runTime,
+      runResult,
+      tableData,
+      optionsPage,
+      ...toRefs(tablePage),
+      ...toRefs(pageReactive),
+      //   ...toRefs(tablePage),
+      langLocale,
+
+      resultOptions,
+      tableColumn,
+
+      handleSearch,
+      //   handlePaginateChange,
+      handleDownload,
+      handleCurrentPage,
+      handlePageSize,
+      formatInfo,
+    };
+  },
+  components: {
+    // ElTabs,
+  },
+};
+</script>
+
+<style scoped lang="scss">
+.query-main {
+  min-height: calc(100% - 243px);
+  padding: 20px;
+  background: #f9fbfc;
+  display: flex;
+  &-container {
+    width: 100%;
+    display: flex;
+    background: #fff;
+    border-radius: 4px;
+    border: 1px solid #eaecf0;
+    padding: 20px 20px 20px 0;
+  }
+  .container-left {
+    &:deep .el-tabs__active-bar {
+      min-width: 0 !important;
+    }
+    &:deep .el-tabs__item {
+      height: 24px;
+      line-height: 24px;
+      font-weight: 400;
+      color: #8e97aa;
+      font-size: 12px !important;
+      &.is-active {
+        font-weight: 400;
+        color: $theme-bj-color !important;
+      }
+    }
+    &:deep .el-tabs__nav-scroll,
+    &:deep .el-tabs__nav-wrap {
+      height: auto;
+    }
+  }
+  .container-right {
+    padding-left: 10px;
+    width: 500px;
+    flex: 1;
+    &-tip {
+      display: flex;
+      align-items: center;
+      height: 48px;
+      padding: 0 20px;
+      background: #fafafa;
+      font-weight: 500;
+      font-size: 12px;
+      color: #808ba3;
+      div {
+        margin-left: 50px;
+        display: flex;
+        align-items: center;
+      }
+      div:first-child {
+        margin-left: 0;
+      }
+      span {
+        padding-left: 12px;
+        font-size: 16px;
+        font-weight: 400;
+        color: #333;
+        line-height: 24px;
+      }
+    }
+    &-operate {
+      font-size: 12px;
+      display: flex;
+      flex-wrap: wrap;
+      align-items: center;
+      .operate-item {
+        margin-top: 20px;
+        display: flex;
+        align-items: center;
+      }
+      .operate-btn {
+        margin-left: 20px;
+        margin-top: 20px;
+      }
+      .input-box {
+        width: 160px;
+      }
+      .select-box {
+        width: 120px;
+      }
+      .input-label {
+        padding: 0 10px;
+      }
+      &:deep .el-date-editor--datetimerange.el-input__inner {
+        width: 320px;
+      }
+    }
+    &-table {
+      .slow-query {
+        display: inline-block;
+        background: #f33f2b;
+        color: #fff;
+        height: 18px;
+        line-height: 18px;
+        padding: 0 2px;
+      }
+
+      margin-top: 20px;
+      .success {
+        font-weight: 400;
+        color: #5776ed;
+      }
+      .error {
+        font-weight: 400;
+        color: #fd5c5c;
+      }
+      .table-pagination {
+        padding: 12px 0;
+        text-align: right;
+        display: flex;
+        justify-content: flex-end;
+        .pagination-title {
+          font-weight: 400;
+          font-size: 14px;
+          color: rgb(96, 98, 102);
+        }
+        &:deep button:hover {
+          color: #15c294;
+        }
+        &:deep li {
+          outline: none;
+          &:hover {
+            color: #15c294;
+          }
+          &.active {
+            color: #15c294;
+          }
+        }
+      }
+    }
+  }
+  &:deep .el-input .el-input__inner {
+    font-size: 12px;
+  }
+  &:deep .el-pagination {
+    font-size: 14px;
+    font-weight: 400;
+    color: rgba(34, 34, 34, 0.75);
+    line-height: 14px;
+  }
+  &:deep .el-pagination .el-select .el-input {
+    width: 60px;
+  }
+}
+</style>
diff --git a/frontend/src/views/Control/hooks/useInitChart.js b/frontend/src/views/Control/hooks/useInitChart.js
new file mode 100644
index 0000000..630e82e
--- /dev/null
+++ b/frontend/src/views/Control/hooks/useInitChart.js
@@ -0,0 +1,625 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { getChartData } from '../api';
+import { useI18n } from 'vue-i18n';
+import { get } from 'lodash';
+const ChartMap = {
+  GCEchart: 0,
+  JVMClassEchart: 1,
+  YGCEchart: 2,
+  FGCEchart: 3,
+  JAVATypeEchart: 4,
+  JAVATimeEchart: 5,
+  MemoryEchart: 6,
+  BufferEchart: 7,
+  CPUEchart: 8,
+  IOEchart: 9,
+  FileCountEchart: 10,
+  FileSizeEchart: 11,
+  WriteEchart: 12,
+  SearchEchart: 13,
+  ApiEchart: 14,
+  ApiQPSEchart: 15,
+};
+let ChartCacheData = {};
+let chartsTitle = null;
+let t = null;
+async function initMatchChart(serverId, type, refreshData = true) {
+  !t && (t = useI18n().t);
+  chartsTitle = {
+    GCEchart: t('controlPage.GCEchart'),
+    JVMClassEchart: t('controlPage.JVMClassEchart'),
+    YGCEchart: t('controlPage.YGCEchart'),
+    FGCEchart: t('controlPage.FGCEchart'),
+    JAVATypeEchart: t('controlPage.JAVATypeEchart'),
+    JAVATimeEchart: t('controlPage.JAVATimeEchart'),
+    MemoryEchart: t('controlPage.MemoryEchart'),
+    BufferEchart: t('controlPage.BufferEchart'),
+    CPUEchart: t('controlPage.CPUEchart'),
+    IOEchart: t('controlPage.IOEchart'),
+    FileCountEchart: t('controlPage.FileCountEchart'),
+    FileSizeEchart: t('controlPage.FileSizeEchart'),
+    WriteEchart: t('controlPage.WriteEchart'),
+    SearchEchart: t('controlPage.SearchEchart'),
+    ApiEchart: t('controlPage.ApiEchart'),
+    ApiQPSEchart: t('controlPage.ApiQPSEchart'),
+  };
+  let res;
+  // Cache
+  if (refreshData === '0') {
+    res = ChartCacheData[type];
+  } else {
+    let result = await getChartData(serverId, ChartMap[type]);
+    ChartCacheData[type] = result.data.chartData;
+    res = result.data.chartData;
+  }
+  if (type === 'GCEchart') {
+    let temp = {
+      title: chartsTitle[type],
+      options: {
+        color: ['#379E7D', '#5776ED', '#F69823', '#FD6031'],
+        legend: {
+          type: 'scroll',
+          data: res?.metricnameList,
+          bottom: '12px',
+        },
+        xAxis: {
+          type: 'category',
+          axisTick: { show: false },
+          axisLine: { show: true, lineStyle: { color: ' #eee', width: 1 } },
+          axisLabel: { show: true, textStyle: { color: '#8E97AA' } },
+          data: res ? res.timeList : [],
+        },
+        yAxis: {
+          type: 'category',
+        },
+        series: res?.metricnameList.map((item, index) => {
+          return {
+            name: item,
+            data: res?.dataList && get(res, `dataList.${get(res, `metricnameList.${index}`)}`),
+            type: 'bar',
+            barGap: '0',
+          };
+        }),
+      },
+    };
+    return temp;
+  } else if (type === 'JVMClassEchart') {
+    let temp = {
+      title: chartsTitle[type],
+      options: {
+        color: ['#5776ED', '#FD5C5C'],
+        legend: {
+          data: res?.metricnameList,
+          bottom: '12px',
+        },
+        xAxis: {
+          type: 'category',
+          axisTick: { show: false },
+          axisLine: { show: true, lineStyle: { color: ' #eee', width: 1 } },
+          axisLabel: { show: true, textStyle: { color: '#8E97AA' } },
+          data: res ? res.timeList : [],
+        },
+        yAxis: {
+          axisLabel: {
+            //这种做法就是在y轴的数据的值旁边拼接单位,貌似也挺方便的
+            formatter: `{value}${get(res, 'unitList.0')}`,
+          },
+        },
+        series: res?.metricnameList.map((item, index) => {
+          return {
+            name: item,
+            data: res?.dataList && get(res, `dataList.${get(res, `metricnameList.${index}`)}`),
+            type: 'line',
+          };
+        }),
+      },
+    };
+    return temp;
+  } else if (type === 'YGCEchart') {
+    let temp = {
+      title: chartsTitle[type],
+      options: {
+        color: ['#F69823'],
+        tooltip: {},
+        xAxis: {
+          type: 'value',
+          splitLine: { show: false },
+          axisTick: { show: false },
+          axisLine: { show: true, lineStyle: { color: ' #eee', width: 1 } },
+          axisLabel: { show: true, textStyle: { color: '#8E97AA' }, formatter: `{value}${get(res, 'unitList.0')}` },
+        },
+        yAxis: {
+          inverse: true,
+          type: 'category',
+          axisTick: { show: false },
+          data: res ? res.metricnameList : [],
+          axisLine: { show: true, lineStyle: { color: ' #eee', width: 1 } },
+          axisLabel: { show: true, textStyle: { color: '#8E97AA' } },
+        },
+        grid: {
+          top: '20px',
+          left: '20px',
+          right: '20px',
+          bottom: '16px',
+          containLabel: true,
+        },
+        series: [
+          {
+            type: 'bar',
+            data: res?.metricnameList.map((item, index) => {
+              return res?.dataList && get(res, `dataList.${get(res, `metricnameList.${index}`)}`).map((item) => parseFloat(item))[0];
+            }),
+          },
+        ],
+      },
+    };
+    return temp;
+  } else if (type === 'FGCEchart') {
+    let temp = {
+      title: chartsTitle[type],
+      options: {
+        color: ['#5855DA'],
+        tooltip: {},
+        xAxis: {
+          type: 'value',
+          splitLine: { show: false },
+          axisTick: { show: false },
+          axisLine: { show: true, lineStyle: { color: ' #eee', width: 1 } },
+          axisLabel: { show: true, textStyle: { color: '#8E97AA' }, formatter: `{value}${get(res, 'unitList.0')}` },
+        },
+        yAxis: {
+          inverse: true,
+          type: 'category',
+          data: res ? res.metricnameList : [],
+          axisTick: { show: false },
+          axisLine: { show: true, lineStyle: { color: ' #eee', width: 1 } },
+          axisLabel: { show: true, textStyle: { color: '#8E97AA' } },
+        },
+        grid: {
+          top: '20px',
+          left: '20px',
+          right: '20px',
+          bottom: '16px',
+          containLabel: true,
+        },
+        series: [
+          {
+            type: 'bar',
+            data: res?.metricnameList.map((item, index) => {
+              return res?.dataList && get(res, `dataList.${get(res, `metricnameList.${index}`)}`).map((item) => parseFloat(item))[0];
+            }),
+          },
+        ],
+      },
+    };
+    return temp;
+  } else if (type === 'JAVATypeEchart') {
+    let temp = {
+      title: chartsTitle[type],
+      options: {
+        color: ['#F69823', '#FD5C5C', '#5776ED'],
+        legend: {
+          data: res?.metricnameList,
+          bottom: '12px',
+        },
+        xAxis: {
+          type: 'category',
+          axisTick: { show: false },
+          axisLine: { show: true, lineStyle: { color: ' #eee', width: 1 } },
+          axisLabel: { show: true, textStyle: { color: '#8E97AA' } },
+          data: res?.timeList,
+        },
+        yAxis: {
+          axisLabel: {
+            //这种做法就是在y轴的数据的值旁边拼接单位,貌似也挺方便的
+            formatter: `{value}${get(res, 'unitList.0')}`,
+          },
+        },
+        series: res?.metricnameList.map((item, index) => {
+          return {
+            name: item,
+            data: res?.dataList && get(res, `dataList.${get(res, `metricnameList.${index}`)}`),
+            type: 'line',
+          };
+        }),
+      },
+    };
+    return temp;
+  } else if (type === 'JAVATimeEchart') {
+    let temp = {
+      title: chartsTitle[type],
+      options: {
+        color: ['#379E7D', '#66A5FF', '#5776ED', '#F69823', '#FD5C5C', '#50D6BB'],
+        legend: {
+          type: 'scroll',
+          data: res?.metricnameList,
+          bottom: '12px',
+        },
+        xAxis: {
+          type: 'category',
+          axisTick: { show: false },
+          axisLine: { show: true, lineStyle: { color: ' #eee', width: 1 } },
+          axisLabel: { show: true, textStyle: { color: '#8E97AA' } },
+          data: res?.timeList,
+        },
+        yAxis: {
+          axisLabel: {
+            //这种做法就是在y轴的数据的值旁边拼接单位,貌似也挺方便的
+            formatter: `{value}${get(res, 'unitList.0')}`,
+          },
+        },
+        series: res?.metricnameList.map((item, index) => {
+          return {
+            name: item,
+            data: res?.dataList && get(res, `dataList.${get(res, `metricnameList.${index}`)}`),
+            type: 'line',
+          };
+        }),
+      },
+    };
+    return temp;
+  } else if (type === 'MemoryEchart') {
+    let temp = {
+      title: chartsTitle[type],
+      options: {
+        color: ['#379E7D', '#5776ED'],
+        legend: {
+          data: res?.metricnameList,
+          bottom: '12px',
+        },
+        xAxis: {
+          type: 'category',
+          axisTick: { show: false },
+          axisLine: { show: true, lineStyle: { color: ' #eee', width: 1 } },
+          axisLabel: { show: true, textStyle: { color: '#8E97AA' } },
+          data: res?.timeList,
+        },
+        yAxis: {
+          axisLabel: {
+            //这种做法就是在y轴的数据的值旁边拼接单位,貌似也挺方便的
+            formatter: `{value}${get(res, 'unitList.0')}`,
+          },
+        },
+        series: res?.metricnameList.map((item, index) => {
+          return {
+            name: item,
+            data: res?.dataList && get(res, `dataList.${get(res, `metricnameList.${index}`)}`),
+            type: 'line',
+          };
+        }),
+      },
+    };
+    return temp;
+  } else if (type === 'BufferEchart') {
+    let temp = {
+      title: chartsTitle[type],
+      options: {
+        color: ['#379E7D', '#5776ED'],
+        legend: {
+          data: res?.metricnameList,
+          bottom: '12px',
+        },
+        xAxis: {
+          type: 'category',
+          axisTick: { show: false },
+          axisLine: { show: true, lineStyle: { color: ' #eee', width: 1 } },
+          axisLabel: { show: true, textStyle: { color: '#8E97AA' } },
+          data: res?.timeList,
+        },
+        yAxis: {
+          axisLabel: {
+            //这种做法就是在y轴的数据的值旁边拼接单位,貌似也挺方便的
+            formatter: `{value}${get(res, 'unitList.0')}`,
+          },
+        },
+        series: res?.metricnameList.map((item, index) => {
+          return {
+            name: item,
+            data: res?.dataList && get(res, `dataList.${get(res, `metricnameList.${index}`)}`),
+            type: 'line',
+          };
+        }),
+      },
+    };
+    return temp;
+  } else if (type === 'CPUEchart') {
+    let data = [];
+    res?.metricnameList.map((item) => {
+      data.push({ value: parseFloat(get(res, `dataList.${item}`)), name: item });
+    });
+    let temp = {
+      title: chartsTitle[type],
+      options: {
+        color: ['#379E7D', '#5776ED', '#66A5FF', '#F69823', '#FD5C5C'],
+        legend: {
+          type: 'scroll',
+          bottom: '12px',
+        },
+        tooltip: {
+          trigger: 'item',
+          textStyle: {
+            align: 'left',
+          },
+          formatter: function (val) {
+            console.log(val.name, val.value);
+            return `${val.marker}${val.name}&nbsp;&nbsp;&nbsp;&nbsp;${val.value}%`;
+          },
+        },
+        xAxis: null,
+        yAxis: null,
+        series: [
+          {
+            name: chartsTitle[type],
+            type: 'pie',
+            radius: ['45%', '70%'],
+            avoidLabelOverlap: false,
+            center: ['50%', '120px'],
+            label: {
+              show: false,
+              position: 'center',
+            },
+            emphasis: {
+              label: {
+                show: false,
+                fontSize: '40',
+                fontWeight: 'bold',
+              },
+            },
+            labelLine: {
+              show: false,
+            },
+            data,
+          },
+        ],
+      },
+    };
+    return temp;
+  } else if (type === 'IOEchart') {
+    let temp = {
+      title: chartsTitle[type],
+      options: {
+        color: ['#379E7D'],
+        legend: null,
+        xAxis: {
+          type: 'category',
+          axisTick: { show: false },
+          axisLine: { show: true, lineStyle: { color: ' #eee', width: 1 } },
+          axisLabel: { show: true, textStyle: { color: '#8E97AA' } },
+          data: res?.timeList,
+        },
+        yAxis: {
+          axisLabel: {
+            //这种做法就是在y轴的数据的值旁边拼接单位,貌似也挺方便的
+            formatter: `{value}${get(res, 'unitList.0')}`,
+          },
+        },
+        series: res?.metricnameList.map((item, index) => {
+          return {
+            name: item,
+            data: res?.dataList && get(res, `dataList.${get(res, `metricnameList.${index}`)}`),
+            type: 'line',
+          };
+        }),
+      },
+    };
+    return temp;
+  } else if (type === 'FileCountEchart') {
+    let temp = {
+      title: chartsTitle[type],
+      options: {
+        color: ['#5776ED', '#66A5FF', '#F69823', '#379E7D'],
+        legend: {
+          type: 'scroll',
+          data: res?.metricnameList,
+          bottom: '12px',
+        },
+        xAxis: {
+          type: 'category',
+          axisTick: { show: false },
+          axisLine: { show: true, lineStyle: { color: ' #eee', width: 1 } },
+          axisLabel: { show: true, textStyle: { color: '#8E97AA' } },
+          data: res?.timeList,
+        },
+        yAxis: {
+          axisLabel: {
+            //这种做法就是在y轴的数据的值旁边拼接单位,貌似也挺方便的
+            formatter: `{value}${get(res, 'unitList.0')}`,
+          },
+        },
+        series: res?.metricnameList.map((item, index) => {
+          return {
+            name: item,
+            data: res?.dataList && get(res, `dataList.${get(res, `metricnameList.${index}`)}`),
+            type: 'line',
+          };
+        }),
+      },
+    };
+    return temp;
+  } else if (type === 'FileSizeEchart') {
+    let temp = {
+      title: chartsTitle[type],
+      options: {
+        color: ['#5776ED', '#66A5FF', '#F69823', '#379E7D'],
+        legend: {
+          type: 'scroll',
+          data: res?.metricnameList,
+          bottom: '12px',
+        },
+        xAxis: {
+          type: 'category',
+          axisTick: { show: false },
+          axisLine: { show: true, lineStyle: { color: ' #eee', width: 1 } },
+          axisLabel: { show: true, textStyle: { color: '#8E97AA' } },
+          data: res?.timeList,
+        },
+        yAxis: {
+          axisLabel: {
+            //这种做法就是在y轴的数据的值旁边拼接单位,貌似也挺方便的
+            formatter: `{value}${get(res, 'unitList.0')}`,
+          },
+        },
+        series: res?.metricnameList.map((item, index) => {
+          return {
+            name: item,
+            data: res?.dataList && get(res, `dataList.${get(res, `metricnameList.${index}`)}`),
+            type: 'line',
+          };
+        }),
+      },
+    };
+    return temp;
+  } else if (type === 'WriteEchart') {
+    let temp = {
+      title: chartsTitle[type],
+      options: {
+        color: ['#379E7D', '#FD6031', '#5776ED'],
+        legend: {
+          data: res?.metricnameList,
+          bottom: '12px',
+        },
+        xAxis: {
+          type: 'category',
+          axisTick: { show: false },
+          axisLine: { show: true, lineStyle: { color: ' #eee', width: 1 } },
+          axisLabel: { show: true, textStyle: { color: '#8E97AA' } },
+          data: res?.timeList,
+        },
+        yAxis: {
+          axisLabel: {
+            //这种做法就是在y轴的数据的值旁边拼接单位,貌似也挺方便的
+            formatter: `{value}${get(res, 'unitList.0')}`,
+          },
+        },
+        series: res?.metricnameList.map((item, index) => {
+          return {
+            name: item,
+            data: res?.dataList && get(res, `dataList.${get(res, `metricnameList.${index}`)}`),
+            type: 'line',
+          };
+        }),
+      },
+    };
+
+    return temp;
+  } else if (type === 'SearchEchart') {
+    let temp = {
+      title: chartsTitle[type],
+      options: {
+        color: ['#379E7D', '#FD6031', '#5776ED'],
+        legend: {
+          data: res?.metricnameList,
+          bottom: '12px',
+        },
+        xAxis: {
+          type: 'category',
+          axisTick: { show: false },
+          axisLine: { show: true, lineStyle: { color: ' #eee', width: 1 } },
+          axisLabel: { show: true, textStyle: { color: '#8E97AA' } },
+          data: res?.timeList,
+        },
+        yAxis: {
+          axisLabel: {
+            //这种做法就是在y轴的数据的值旁边拼接单位,貌似也挺方便的
+            formatter: `{value}${get(res, 'unitList.0')}`,
+          },
+        },
+        series: res?.metricnameList.map((item, index) => {
+          return {
+            name: item,
+            data: res?.dataList && get(res, `dataList.${get(res, `metricnameList.${index}`)}`),
+            type: 'bar',
+            barGap: '0',
+          };
+        }),
+      },
+    };
+    return temp;
+  } else if (type === 'ApiEchart') {
+    let temp = {
+      title: chartsTitle[type],
+      options: {
+        color: ['#379E7D', '#66A5FF', '#5776ED', '#F69823', '#FD5C5C', '#50D6BB'],
+        legend: {
+          type: 'scroll',
+          data: res?.metricnameList,
+          bottom: '12px',
+          left: '20px',
+        },
+        xAxis: {
+          type: 'category',
+          axisTick: { show: false },
+          axisLine: { show: true, lineStyle: { color: ' #eee', width: 1 } },
+          axisLabel: { show: true, textStyle: { color: '#8E97AA' } },
+          data: res?.timeList,
+        },
+        yAxis: {
+          axisLabel: {
+            //这种做法就是在y轴的数据的值旁边拼接单位,貌似也挺方便的
+            formatter: `{value}${get(res, 'unitList.0')}`,
+          },
+        },
+        series: res?.metricnameList.map((item, index) => {
+          return {
+            name: item,
+            data: res?.dataList && get(res, `dataList.${get(res, `metricnameList.${index}`)}`),
+            type: 'line',
+          };
+        }),
+      },
+    };
+    return temp;
+  } else if (type === 'ApiQPSEchart') {
+    let temp = {
+      title: chartsTitle[type],
+      options: {
+        color: ['#379E7D', '#66A5FF', '#5776ED', '#F69823', '#FD5C5C', '#50D6BB'],
+        legend: {
+          type: 'scroll',
+          data: res?.metricnameList,
+          bottom: '12px',
+          left: '20px',
+        },
+        xAxis: {
+          type: 'category',
+          axisTick: { show: false },
+          axisLine: { show: true, lineStyle: { color: ' #eee', width: 1 } },
+          axisLabel: { show: true, textStyle: { color: '#8E97AA' } },
+          data: res?.timeList,
+        },
+        yAxis: {
+          axisLabel: {
+            //这种做法就是在y轴的数据的值旁边拼接单位,貌似也挺方便的
+            formatter: `{value}${get(res, 'unitList.0')}`,
+          },
+        },
+        series: res?.metricnameList.map((item, index) => {
+          return {
+            name: item,
+            data: res?.dataList && get(res, `dataList.${get(res, `metricnameList.${index}`)}`),
+            type: 'line',
+          };
+        }),
+      },
+    };
+    return temp;
+  }
+}
+export default initMatchChart;
diff --git a/frontend/src/views/Control/index.vue b/frontend/src/views/Control/index.vue
new file mode 100644
index 0000000..e78f36b
--- /dev/null
+++ b/frontend/src/views/Control/index.vue
@@ -0,0 +1,420 @@
+<!--
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+-->
+
+<template>
+  <div class="monitor">
+    <el-container>
+      <el-aside :width="dividerWidth + 'px'">
+        <div class="aside-title">
+          {{ $t('controlPage.dataList') }}<span> ({{ filterDataList.length || 0 }}) </span>
+        </div>
+        <el-input v-if="dataList.length" v-model="search" class="w-50 m-2" :placeholder="$t('common.placeHolder')" suffix-icon="el-icon-search" />
+        <div class="aside-main">
+          <div :class="{ 'data-item': true, 'active-item': checkedId === item.id }" v-for="(item, index) in filterDataList" :key="index" @click="handleSwitch(item)">
+            <svg class="aside-icon" aria-hidden="true">
+              <use xlink:href="#icon-shujulianjie1"></use></svg
+            >{{ item.name }}
+          </div>
+          <div v-if="dataList.length === 0" class="datalist-empty">
+            <span>{{ $t('controlPage.nodata') }}</span>
+          </div>
+        </div>
+      </el-aside>
+      <div class="divider" ref="dividerRef"></div>
+      <el-main v-if="monitorInfo">
+        <div class="main-top">
+          <div class="main-head">
+            <div class="main-title">
+              {{ currentData.name }}
+              <span v-if="monitorInfo.status" class="survive">{{ $t('common.survival') }}</span>
+              <span v-else class="die">{{ $t('common.death') }}</span>
+            </div>
+            <div class="main-host format">
+              <div>
+                <svg class="main-icon" aria-hidden="true">
+                  <use xlink:href="#icon-se-icon-ip"></use>
+                </svg>
+                {{ $t('controlPage.address') }}:<span>{{ formatInfo(monitorInfo?.url) }}</span>
+              </div>
+              <div>
+                <svg class="main-icon" aria-hidden="true">
+                  <use xlink:href="#icon-duankou"></use>
+                </svg>
+                {{ $t('common.port') }}: <span>{{ formatInfo(monitorInfo?.port) }}</span>
+              </div>
+            </div>
+            <div class="main-info format">
+              <div>
+                <svg class="main-icon" aria-hidden="true">
+                  <use xlink:href="#icon-cunchuzu1"></use></svg
+                >{{ $t('controlPage.storage') }}: <span>{{ formatInfo(monitorInfo?.storageGroupCount) }}</span>
+              </div>
+              <div>
+                <svg class="main-icon" aria-hidden="true">
+                  <use xlink:href="#icon-shiti"></use></svg
+                >{{ $t('controlPage.entity') }}: <span>{{ formatInfo(monitorInfo?.monitorCount) }}</span>
+              </div>
+              <div>
+                <svg class="main-icon" aria-hidden="true">
+                  <use xlink:href="#icon-wuliliang"></use></svg
+                >{{ $t('controlPage.physics') }}: <span>{{ formatInfo(monitorInfo?.deviceCount) }}</span>
+              </div>
+              <div>
+                <svg class="main-icon" aria-hidden="true">
+                  <use xlink:href="#icon-shujuzongliang"></use></svg
+                >{{ $t('controlPage.total') }}: <span>{{ formatInfo(monitorInfo?.dataCount) }}</span>
+              </div>
+            </div>
+          </div>
+          <div class="main-top-tabs">
+            <el-tabs v-model="activeTab" @tab-click="handleChangeTab">
+              <template v-for="(item, index) in tabPaneOptions" :key="index">
+                <el-tab-pane v-bind="item" :disabled="item.name === 'Query' ? disabled : false"> </el-tab-pane>
+              </template>
+            </el-tabs>
+          </div>
+        </div>
+        <router-view v-slot="{ Component }">
+          <keep-alive>
+            <component :is="Component" :data="monitorInfo" />
+          </keep-alive>
+        </router-view>
+      </el-main>
+    </el-container>
+  </div>
+</template>
+
+<script>
+// @ is an alias to /src
+import { computed, onMounted, ref, watch } from 'vue';
+import { ElTabs, ElContainer, ElAside, ElMain } from 'element-plus';
+import { useRoute, useRouter } from 'vue-router';
+import useElementResize from '@/hooks/useElementResize.js';
+import useLanguageWatch from '@/hooks/useLanguageWatch';
+import { useI18n } from 'vue-i18n';
+import { getMonitorList, getMonitorInfo } from './api';
+
+export default {
+  name: 'ComtrolIndex',
+  components: {
+    ElTabs,
+    ElContainer,
+    ElAside,
+    ElMain,
+  },
+  setup() {
+    let { t } = useI18n();
+    const router = useRouter();
+    const route = useRoute();
+    let routerParams = {};
+    let dividerRef = ref();
+    let dividerWidth = ref(240);
+    let search = ref();
+    let activeTab = ref('Indicator');
+    let currentData = ref(null);
+    let dataList = ref([]);
+    let monitorInfo = ref();
+    let show = ref(false);
+    let disabled = ref(false);
+    let tabPaneOptions = ref([
+      { name: 'Indicator', label: t('controlPage.monitor') },
+      { name: 'Query', label: t('controlPage.search') },
+    ]);
+    //数据列表选中id
+    let checkedId = computed(() => currentData.value && currentData.value.id);
+    let filterDataList = computed(() => {
+      let reg = new RegExp(search.value);
+      return dataList.value.filter((item) => {
+        if (reg.test(item.name)) {
+          return true;
+        }
+      });
+    });
+    watch(currentData, async (newValue) => {
+      if (newValue) {
+        let res = await getMonitorInfo(newValue.id);
+        monitorInfo.value = res.data;
+        if (monitorInfo.value?.status) {
+          disabled.value = false;
+          handleChangeTab({ paneName: activeTab.value });
+        } else {
+          handleChangeTab({ paneName: 'Indicator' });
+          disabled.value = true;
+        }
+      }
+    });
+
+    onMounted(() => {
+      initFun();
+      useElementResize(dividerRef, dividerWidth);
+      useLanguageWatch(tabPaneOptions, () => [
+        { name: 'Indicator', label: t('controlPage.monitor') },
+        { name: 'Query', label: t('controlPage.search') },
+      ]);
+    });
+
+    function urlSkip(routerName, id, params) {
+      let panelMode = monitorInfo.value?.status ? params?.panel || 'list' : 'list';
+      if (routerName == 'Indicator') {
+        router.push({ path: `/control/indicator/${panelMode}/${id}/${params.mode}`, query: { ...route.query } });
+      } else if (routerName === 'Query') {
+        router.push({ path: `/control/query/${id}` });
+      }
+    }
+    async function initFun() {
+      // init tabs
+      activeTab.value = routerNameLimit(route.name);
+      await getData();
+      //   routerParamId to data
+      filterCurrentData(route.params.id);
+    }
+    async function getData() {
+      let res = await getMonitorList();
+      dataList.value = res.data || [];
+    }
+    function routerNameLimit(name) {
+      for (let i = 0, len = tabPaneOptions.value.length; i < len; i++) {
+        if (tabPaneOptions.value[i].name === name) {
+          return name;
+        }
+      }
+      return tabPaneOptions.value[0].name;
+    }
+    function filterCurrentData(id) {
+      let len = dataList.value.length;
+      for (let i = 0; i < len; i++) {
+        let item = dataList.value[i];
+        if (item.id === +id) {
+          currentData.value = { ...item };
+          return;
+        }
+      }
+      currentData.value = dataList.value[0];
+    }
+    const handleChangeTab = (tabs) => {
+      routerParams[route.name] = route.params;
+      activeTab.value = tabs.paneName;
+      let id = currentData.value?.id;
+      let params = { ...routerParams['Indicator'] };
+      params.mode = routerParams['Indicator']?.mode || 'JVM';
+      urlSkip(tabs.paneName, id, params);
+    };
+    function handleSwitch(data) {
+      currentData.value = data;
+    }
+    function formatInfo(val) {
+      return val || '-';
+    }
+    return {
+      dividerRef,
+      dividerWidth,
+      activeTab,
+      search,
+      dataList,
+      currentData,
+      monitorInfo,
+      filterDataList,
+      show,
+      disabled,
+
+      checkedId,
+      tabPaneOptions,
+
+      handleChangeTab,
+      handleSwitch,
+      formatInfo,
+    };
+  },
+};
+</script>
+
+<style scoped lang="scss">
+.monitor {
+  height: 100%;
+  .datalist-empty {
+    height: 100%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    font-size: 14px;
+    color: #909399;
+  }
+  .el-container {
+    height: 100%;
+  }
+  .el-aside {
+    text-align: left;
+    padding: 0 20px;
+  }
+  .divider {
+    z-index: 10;
+    width: 1px;
+    height: 100%;
+    background-color: #e0e0e0;
+    &:hover {
+      cursor: w-resize;
+      background-color: $theme-color;
+      width: 2px;
+    }
+  }
+  .aside-title {
+    height: 20px;
+    font-size: 14px;
+    font-weight: 500;
+    color: #333;
+    line-height: 20px;
+    margin: 20px 0;
+    span {
+      font-weight: 400;
+      color: #808a9f;
+    }
+  }
+  .aside-main {
+    margin: 20px 0 10px 0;
+    height: calc(100% - 118px);
+    font-size: 12px;
+    font-weight: 400;
+    overflow-y: auto;
+
+    .data-item {
+      margin-top: 10px;
+      height: 30px;
+      line-height: 30px;
+      border-radius: 4px;
+      display: flex;
+      align-items: center;
+      cursor: pointer;
+      &:hover {
+        background: #edf8f5;
+        color: $theme-color;
+      }
+      &:first-child {
+        margin-top: 0;
+      }
+    }
+    .active-item {
+      background: #edf8f5;
+      color: $theme-color;
+    }
+    .aside-icon {
+      width: 14px;
+      height: 14px;
+      margin: 0 10px;
+    }
+  }
+  .main-top {
+    padding: 20px 20px 0 20px;
+    &:deep .el-tabs {
+      .el-tabs__nav-wrap::after {
+        display: none;
+      }
+      .el-tabs__item.is-disabled {
+        cursor: not-allowed;
+        &:hover {
+          color: #8e97aaff !important;
+        }
+      }
+      .el-tabs__header {
+        margin: 0;
+      }
+      .el-tabs__item {
+        &.is-active {
+          color: #15c294 !important;
+        }
+      }
+    }
+    .main-top-tabs {
+      padding-left: 20px;
+    }
+  }
+  .main-head {
+    margin-bottom: 10px;
+    padding: 20px;
+    border-radius: 4px;
+    border: 1px solid #eaecf0;
+    text-align: left;
+    .main-title {
+      display: flex;
+      align-items: center;
+      height: 24px;
+      line-height: 24px;
+      color: #333;
+      font-size: 16px;
+      font-weight: 500;
+      span {
+        margin-left: 26px;
+        font-size: 12px;
+        padding: 0 12px;
+        height: 20px;
+        line-height: 20px;
+        border-radius: 2px;
+        &.survive {
+          color: #00a950;
+          background: rgba(0, 169, 80, 0.1);
+        }
+        &.die {
+          color: #f33f2b;
+          background: rgba(243, 63, 43, 0.1);
+        }
+      }
+    }
+    .format {
+      display: flex;
+      margin-top: 12px;
+      height: 20px;
+      line-height: 20px;
+      font-size: 12px;
+      color: #828ca1;
+      font-weight: 400;
+      > div {
+        display: flex;
+        margin-left: 38px;
+        align-items: center;
+        span {
+          color: #333;
+        }
+        &:first-child {
+          margin-left: 0;
+        }
+      }
+    }
+
+    .main-icon {
+      margin-right: 8px;
+      width: 14px;
+      height: 14px;
+    }
+  }
+  .main-tab {
+    display: inline-block;
+    padding: 12px 20px;
+  }
+
+  &:deep .el-main {
+    padding: 0;
+  }
+  &:deep td,
+  &:deep th {
+    height: 40px;
+  }
+  &:deep .el-input .el-input__inner {
+    font-size: 12px;
+  }
+}
+</style>
diff --git a/frontend/src/views/DataBaseM/components/dataListTree.vue b/frontend/src/views/DataBaseM/components/dataListTree.vue
index 8fba1b9..86ae528 100644
--- a/frontend/src/views/DataBaseM/components/dataListTree.vue
+++ b/frontend/src/views/DataBaseM/components/dataListTree.vue
@@ -74,7 +74,7 @@
 
 <script>
 import { ElTree, ElButton } from 'element-plus';
-import { reactive, ref, computed } from 'vue';
+import { reactive, ref, computed, nextTick } from 'vue';
 import { useStore } from 'vuex';
 import IconTypes from './iconTypes.vue';
 import axios from '@/util/axios.js';
@@ -110,7 +110,23 @@ export default {
     };
 
     const nodeClick = (data, node) => {
-      props.handleNodeClick(data, node);
+      if (data.type === 'pre' || data.type === 'next') {
+        const resolve = async (array) => {
+          const childNodes = node.parent.childNodes.slice();
+          props.handleNodeClick(node.parent.data, node.parent);
+          array.forEach((element) => {
+            nextTick(() => treeRef.value.append(element, node.parent));
+          });
+          await Promise.all(
+            childNodes.map((element) => {
+              return nextTick(() => treeRef.value.remove(element));
+            })
+          );
+        };
+        getData(node.parent, resolve, node);
+      } else {
+        props.handleNodeClick(data, node);
+      }
     };
 
     /**
@@ -170,7 +186,7 @@ export default {
       treeExpandKey.value = arr;
     };
 
-    const recurseDeviceTree = (data, node) => {
+    const recurseDeviceTree = (data, node, res) => {
       let newDevice = {
         id: node.data.id + ':newdevice',
         name: computed(() => t(`databasem.newDevice`)),
@@ -180,6 +196,35 @@ export default {
         connectionid: node.data.connectionid,
         storagegroupid: node.data.storagegroupid,
       };
+      let isPage = res.data.total > res.data.pageSize;
+      let prev = {
+        id: node.data.id + ':pre',
+        name: computed(() => t('sourcePage.prePage')),
+        type: 'pre',
+        leaf: true,
+        parent: node.data,
+        rawid: node.data.name,
+        connectionid: node.data.connectionid,
+        storagegroupid: node.data.storagegroupid,
+        serverId: res.serverId,
+        pageNum: res.data.pageNum,
+        pageSize: res.data.pageSize,
+        total: res.data.total,
+      };
+      let next = {
+        id: node.data.id + ':next',
+        name: computed(() => t('sourcePage.nextPage')),
+        type: 'next',
+        leaf: true,
+        parent: node.data,
+        rawid: node.data.name,
+        connectionid: node.data.connectionid,
+        storagegroupid: node.data.storagegroupid,
+        serverId: res.serverId,
+        pageNum: res.data.pageNum,
+        pageSize: res.data.pageSize,
+        total: res.data.total,
+      };
       let childs = data.map((e) => {
         let child = {
           parent: node.data,
@@ -192,16 +237,62 @@ export default {
           storagegroupid: node.data.storagegroupid,
           connectionid: node.data.connectionid,
           deviceid: e.name,
+          serverId: res.serverId,
         };
-        // if (e.children) {
-        let innerChilds = recurseDeviceTree(e.children || [], { data: child });
-        child.zones = innerChilds;
-        // }
+        if (e.children) {
+          let innerChilds = recurseDeviceTree(e.children || [], { data: child }, res);
+          child.zones = innerChilds;
+        }
         return child;
       });
+      isPage && childs.unshift(prev);
       childs.unshift(newDevice);
+      isPage && childs.push(next);
       return childs;
     };
+    const getData = (node, resolve, pagination) => {
+      let groupName = node.data.rawid;
+      let serverId = node.data.parent.rawid;
+      if (node.data.type !== 'storageGroup') {
+        serverId = node.data.serverId;
+      }
+      const params = { pageNum: (pagination && pagination.data.pageNum) || 1, pageSize: (pagination && pagination.data.pageSize) || 10 };
+      if (pagination && pagination.data.type === 'pre') {
+        params.pageNum = params.pageNum - 1 < 1 ? 1 : params.pageNum - 1;
+      }
+      if (pagination && pagination.data.type === 'next') {
+        const max = Math.ceil((pagination.data.total || 1) / params.pageSize);
+        params.pageNum = params.pageNum + 1 > max ? max : params.pageNum + 1;
+      }
+      axios
+        .get(`/servers/${serverId}/storageGroups/${groupName}/devices/tree`, {
+          params,
+        })
+        .then((res) => {
+          if (res?.code === '0') {
+            if (!res.data) {
+              let newDevice = {
+                id: node.data.id + ':newdevice',
+                name: computed(() => t(`databasem.newDevice`)),
+                type: 'newdevice',
+                leaf: true,
+                parent: node.data,
+                connectionid: node.data.connectionid,
+                storagegroupid: node.data.storagegroupid,
+              };
+              resolve([newDevice]);
+              return;
+            }
+            let childs = recurseDeviceTree(res.data.children || [], node, { serverId, data: res.data });
+            resolve(childs);
+          } else {
+            resolve([]);
+          }
+        })
+        .catch(() => {
+          resolve([]);
+        });
+    };
 
     const loadNode = (node, resolve) => {
       if (node?.data?.zones) {
@@ -281,53 +372,8 @@ export default {
             resolve([]);
           });
       }
-      if (node.level === 2 && node.data.type === 'storageGroup') {
-        let groupName = node.data.rawid;
-        let serverId = node.data.parent.rawid;
-        axios
-          .get(`/servers/${serverId}/storageGroups/${groupName}/devices/tree`, {})
-          .then((res) => {
-            if (res?.code === '0') {
-              if (!res.data) {
-                let newDevice = {
-                  id: node.data.id + ':newdevice',
-                  name: computed(() => t(`databasem.newDevice`)),
-                  type: 'newdevice',
-                  leaf: true,
-                  parent: node.data,
-                  connectionid: node.data.connectionid,
-                  storagegroupid: node.data.storagegroupid,
-                };
-                resolve([newDevice]);
-                return;
-              }
-              if (res.data.name === null) {
-                let childs = recurseDeviceTree(res.data.children || [], node);
-                resolve(childs);
-              } else {
-                let rootDevice = {
-                  // parent: node.data,
-                  name: res.data.name,
-                  // id: node.data.id + res.data.name + 'device',
-                  // type: 'device',
-                  // leaf: false,
-                  // rawid: res.data.name,
-                  // storagegroupid: node.data.storagegroupid,
-                  // connectionid: node.data.connectionid,
-                  // deviceid: res.data.name,
-                  children: res.data.children,
-                };
-                let childs = recurseDeviceTree([rootDevice] || [], node);
-                node.zones = childs;
-                resolve(childs);
-              }
-            } else {
-              resolve([]);
-            }
-          })
-          .catch(() => {
-            resolve([]);
-          });
+      if ((node.level === 2 && node.data.type === 'storageGroup') || (node.data && node.data.type === 'device')) {
+        getData(node, resolve);
       }
       if (node.level === 2 && node.data.type === 'querylist') {
         let serverId = node.data.parent.rawid;
diff --git a/frontend/src/views/DataBaseM/index.vue b/frontend/src/views/DataBaseM/index.vue
index cf9e9d5..c549225 100644
--- a/frontend/src/views/DataBaseM/index.vue
+++ b/frontend/src/views/DataBaseM/index.vue
@@ -74,12 +74,12 @@
 <script>
 // @ is an alias to /src
 import { onMounted, ref, watch } from 'vue';
-import useElementResize from './hooks/useElementResize.js';
+import { useStore } from 'vuex';
+import { useRouter, useRoute } from 'vue-router';
+import useElementResize from '@/hooks/useElementResize.js';
 import DataListTree from './components/dataListTree.vue';
 import IconTypes from './components/iconTypes.vue';
 import { ElContainer, ElAside, ElMain, ElTabs, ElTabPane } from 'element-plus';
-import { useStore } from 'vuex';
-import { useRouter, useRoute } from 'vue-router';
 
 export default {
   name: 'Root',
diff --git a/frontend/src/views/Device/index.vue b/frontend/src/views/Device/index.vue
index 9e3cef8..73ba311 100644
--- a/frontend/src/views/Device/index.vue
+++ b/frontend/src/views/Device/index.vue
@@ -22,9 +22,7 @@
     <form-table :form="form"></form-table>
     <div class="addbox">
       {{ $t('device.physical') }}
-      <el-button type="primary" class="addbutton" size="small" @click="addItem">
-        {{ $t('device.addphysical') }}
-      </el-button>
+      <el-button type="primary" class="addbutton" size="small" @click="addItem"> {{ $t('device.addphysical') }} </el-button>
     </div>
     <div class="tableBox">
       <stand-table
@@ -118,6 +116,9 @@ export default {
           type: 'INPUT',
           size: 'small',
           canEdit: true,
+          border: true,
+          required: true,
+          event: checkValue,
         },
         {
           label: 'device.datatype',
@@ -213,7 +214,7 @@ export default {
           required: true,
           disabled: false,
           inputHeader: true,
-          inputHeaderText: 'groupName',
+          inputHeaderText: (data) => `${data.groupName}.`,
           message: 'device.inputdevice',
         },
         {
@@ -231,6 +232,13 @@ export default {
     function changeBorder(scope) {
       tableData.list[scope.$index].seBorder = false;
     }
+    function checkValue(scope, object, value, event, item) {
+      if (value == null || value === '') {
+        ElMessage.error(`${t('common.placeHolder')}${t(item.label)}`);
+      } else {
+        tableData.list[scope.$index].border = false;
+      }
+    }
     function checkVal(scope, obj, val) {
       console.log(obj);
       if (!/^\w+$/.test(val)) {
@@ -331,11 +339,14 @@ export default {
     function sumbitData() {
       let checkfalg = true;
       tableData.list.forEach((item) => {
-        if (item.timeseries === null || item.dataType === null || item.border || item.seBorder) {
+        if (item.timeseries === null || item.dataType === null || item.border || item.seBorder || item.alias == null || item.alias === '') {
           if (checkfalg) {
             if (item.timeseries === null) {
               item.border = true;
               ElMessage.error(`${t('device.pynamel')}`);
+            } else if (item.alias == null || item.alias === '') {
+              item.border = true;
+              ElMessage.error(`${t('common.placeHolder')}${t('device.alias')}`);
             } else if (item.dataType === null) {
               item.seBorder = true;
               ElMessage.error(`"${item.timeseries}"${t('device.selectdatatype')}`);
@@ -351,6 +362,9 @@ export default {
       if (checkfalg) {
         let copyForm = _cloneDeep(form);
         let { deviceName, groupName } = copyForm.formData;
+        if (/\./.test(deviceName)) {
+          return ElMessage.error(`"${t('device.devicename')}"${t('device.must')}`);
+        }
         let copyTableData = _cloneDeep(tableData);
         copyForm.formData.deviceName = deviceName ? groupName + '.' + deviceName : groupName;
 
diff --git a/frontend/src/views/DeviceMessage/index.vue b/frontend/src/views/DeviceMessage/index.vue
index c6a219f..0c2d34e 100644
--- a/frontend/src/views/DeviceMessage/index.vue
+++ b/frontend/src/views/DeviceMessage/index.vue
@@ -27,13 +27,13 @@
             <el-button @click="editDevce">
               <svg class="icon edit" aria-hidden="true">
                 <use xlink:href="#icon-se-icon-f-edit"></use></svg
-              >&nbsp;{{ $t('common.edit') }}</el-button
-            >
+              >&nbsp;{{ $t('common.edit') }}
+            </el-button>
             <el-button @click="deleteData">
               <svg class="icon delete" aria-hidden="true">
                 <use xlink:href="#icon-se-icon-delete"></use></svg
-              >&nbsp;{{ $t('common.delete') }}</el-button
-            >
+              >&nbsp;{{ $t('common.delete') }}
+            </el-button>
           </div>
         </div>
         <div class="messageBox">
@@ -52,7 +52,7 @@
             </svg>
           </span>
           <span style="margin-left: 5px">{{ $t('device.description') }}:</span>
-          <span>{{ deviceObj.deviceData.description }}</span>
+          <span class="ellipsis" :title="deviceObj.deviceData.description">{{ deviceObj.deviceData.description }}</span>
           <span class="spanmargin">
             <svg class="icon" aria-hidden="true">
               <use xlink:href="#icon-user"></use>
@@ -78,6 +78,7 @@
             <form-table :form="form" @serchFormData="serchFormData"></form-table>
           </div>
           <stand-table
+            v-loading="loading"
             :column="column"
             :tableData="tableData"
             :getList="getListData"
@@ -238,8 +239,8 @@
 import { ElMessageBox, ElMessage, ElButton, ElTabs, ElTabPane, ElDropdown, ElDropdownMenu, ElDropdownItem, ElDialog, ElForm, ElFormItem, ElProgress } from 'element-plus';
 import StandTable from '@/components/StandTable';
 import FormTable from '@/components/FormTable';
-import { reactive, ref, onActivated } from 'vue';
-import { getList, getDeviceDate, getTimeseiresList, deleteDevice, getDataDeviceList, randomImport, editData, deleteDeviceData, exportDataCSV, downloadFile, importData } from './api';
+import { reactive, ref, onActivated, computed } from 'vue';
+import { getList, getDeviceDate, deleteDevice, getDataDeviceList, randomImport, editData, deleteDeviceData, exportDataCSV, downloadFile, importData } from './api';
 import Echarts from '@/components/Echarts';
 import { useI18n } from 'vue-i18n';
 import { useRoute, useRouter } from 'vue-router';
@@ -357,7 +358,7 @@ export default {
           width: '150px',
           itemID: 'measurementList',
           placeholder: 'device.serchPy',
-          options: timeseriesOptions,
+          options: computed(() => [{ label: t('device.all'), value: '' }, ...timeseriesOptions.value]),
           multiple: true,
         },
       ],
@@ -808,19 +809,25 @@ export default {
     }
     //Get physical quantity list
     async function getListData() {
-      await getList(routeData.obj, { ...pagination, ...form.formData }).then((res) => {
-        tableData.list = res.data.measurementVOList;
-        totalCount.value = res.data.totalCount;
-      });
+      try {
+        loading.value = true;
+        await getList(routeData.obj, { ...pagination, ...form.formData }).then((res) => {
+          tableData.list = res.data.measurementVOList;
+          totalCount.value = res.data.totalCount;
+          getTimeseriesOption(res.data.measurementVOList.map((item) => item.timeseries));
+        });
+      } catch (error) {
+        //   console.error(error)
+      } finally {
+        loading.value = false;
+      }
     }
-    async function getTimeseriesOption() {
-      let { connectionid, storagegroupid, deviceid } = routeData.obj;
-      await getTimeseiresList(connectionid, storagegroupid, deviceid).then((res) => {
-        if (res.code === '0') {
-          timeseriesOptions.value = res.data.map((d) => ({ label: d, value: d }));
-          timeseriesOptions.value.unshift({ label: t('device.all'), value: '' });
-        }
-      });
+    async function getTimeseriesOption(array) {
+      let before = timeseriesOptions.value.map((item) => item.value);
+      before.unshift(...array);
+      timeseriesOptions.value = [...new Set(before)].map((d) => ({ label: d, value: d })).slice(0, 50);
+      form1.formData.measurementList[0] = '';
+      getPview();
     }
     //Get physical quantity data preview list
     function getPview() {
@@ -843,9 +850,23 @@ export default {
       if (form1.formData.measurementList[0] === '') {
         data = timeseriesOptions.value.filter((d) => d.value !== '').map((d) => d.value);
       }
+      if (!Array.isArray(data) || data.length === 0) return;
       getDataDeviceList(routeData.obj, pagination1, { startTime: sTime, endTime: eTime, measurementList: data }).then((res) => {
         res.data.metaDataList.forEach((item, index) => {
-          column1.list.push({ label: item, prop: `t${index}`, value: '——', icon: index ? res.data.typeList[index] : 'TIME' });
+          column1.list.push({
+            label: item,
+            prop: `t${index}`,
+            value: '——',
+            icon: index ? res.data.typeList[index] : 'TIME',
+            closable:
+              index > 0 && (form1.formData.measurementList.length === 0 || form1.formData.measurementList[0] !== item)
+                ? (data) => {
+                    const index = timeseriesOptions.value.findIndex((item) => item.value === data.label);
+                    timeseriesOptions.value.splice(index, 1);
+                    getPview();
+                  }
+                : void 0,
+          });
         });
         res.data.valueList.forEach((item) => {
           let obj = {};
@@ -900,7 +921,6 @@ export default {
       }
       setTimeout(async () => {
         getdData();
-        await getTimeseriesOption();
         await getListData();
         await getPview();
       }, 500);
@@ -1127,6 +1147,15 @@ $cursor: pointer;
   color: #fff;
   margin-top: 3px;
 }
+
+.ellipsis {
+  display: inline-block;
+  width: 80px;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  vertical-align: top;
+  text-align: left;
+}
 </style>
 <style lang="scss">
 .tab_class {
diff --git a/frontend/src/views/Login/index.vue b/frontend/src/views/Login/index.vue
index 2b973c6..3b18b4f 100644
--- a/frontend/src/views/Login/index.vue
+++ b/frontend/src/views/Login/index.vue
@@ -23,12 +23,18 @@
       <div class="header-logo"></div>
       <div class="lang-btn">
         <el-dropdown @command="handleLangCommand">
-          <span class="el-dropdown-link"> {{ [$t('rootPage.chinalang'), $t('rootPage.englishlang'), $t('rootPage.deutsch')][langIndex] }}<i class="el-icon-arrow-down el-icon--right"></i> </span>
+          <svg preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24" width="1.2em" height="1.2em" data-v-dd9c9540="">
+            <path
+              fill="currentColor"
+              d="m18.5 10l4.4 11h-2.155l-1.201-3h-4.09l-1.199 3h-2.154L16.5 10h2zM10 2v2h6v2h-1.968a18.222 18.222 0 0 1-3.62 6.301a14.864 14.864 0 0 0 2.336 1.707l-.751 1.878A17.015 17.015 0 0 1 9 13.725a16.676 16.676 0 0 1-6.201 3.548l-.536-1.929a14.7 14.7 0 0 0 5.327-3.042A18.078 18.078 0 0 1 4.767 8h2.24A16.032 16.032 0 0 0 9 10.877a16.165 16.165 0 0 0 2.91-4.876L2 6V4h6V2h2zm7.5 10.885L16.253 16h2.492L17.5 12.885z"
+            ></path>
+          </svg>
+
           <template #dropdown>
             <el-dropdown-menu>
-              <el-dropdown-item :disabled="langIndex === 0" command="0">{{ $t('rootPage.chinalang') }}</el-dropdown-item>
-              <el-dropdown-item :disabled="langIndex === 1" command="1">{{ $t('rootPage.englishlang') }}</el-dropdown-item>
-              <el-dropdown-item :disabled="langIndex === 2" command="2">{{ $t('rootPage.deutsch') }}</el-dropdown-item>
+              <el-dropdown-item :disabled="langIndex === 0" command="0">中文</el-dropdown-item>
+              <el-dropdown-item :disabled="langIndex === 1" command="1">English</el-dropdown-item>
+              <el-dropdown-item :disabled="langIndex === 2" command="2">Deutsch</el-dropdown-item>
             </el-dropdown-menu>
           </template>
         </el-dropdown>
@@ -58,7 +64,7 @@
       </div>
     </div>
 
-    <el-dialog append-to-body :title="$t(`loginPage.forgetPassword`)" v-model="dialogVisible" width="30%">
+    <el-dialog append-to-body :title="$t(`loginPage.forgetPassword`)" v-model="dialogVisible" width="32%">
       <div class="forget-tip">
         {{ $t(`loginPage.forgetPasswordTip`) }}
       </div>
@@ -188,9 +194,10 @@ export default {
     position: relative;
     .lang-btn {
       position: absolute;
-      right: 20px;
+      right: 40px;
       top: 50%;
       transform: translate(0, -50%);
+      cursor: pointer;
     }
     .header-logo {
       background-image: url(~@/assets/logo.png);
diff --git a/frontend/src/views/Root/index.vue b/frontend/src/views/Root/index.vue
index 3b57237..ba1d3eb 100644
--- a/frontend/src/views/Root/index.vue
+++ b/frontend/src/views/Root/index.vue
@@ -23,16 +23,22 @@
       <el-header>
         <el-menu :default-active="menuIndex" mode="horizontal" @select="handleMenuSelect">
           <el-menu-item index="1">{{ $t('rootPage.databaseManagement') }}</el-menu-item>
+          <el-menu-item index="2">{{ $t('rootPage.monitorManagement') }}</el-menu-item>
         </el-menu>
         <div class="logo-img"></div>
         <div class="lang-btn">
           <el-dropdown @command="handleLangCommand">
-            <span class="el-dropdown-link"> {{ [$t('rootPage.chinalang'), $t('rootPage.englishlang'), $t('rootPage.deutsch')][langIndex] }}<i class="el-icon-arrow-down el-icon--right"></i> </span>
+            <svg preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24" width="1.2em" height="1.2em" data-v-dd9c9540="">
+              <path
+                fill="currentColor"
+                d="m18.5 10l4.4 11h-2.155l-1.201-3h-4.09l-1.199 3h-2.154L16.5 10h2zM10 2v2h6v2h-1.968a18.222 18.222 0 0 1-3.62 6.301a14.864 14.864 0 0 0 2.336 1.707l-.751 1.878A17.015 17.015 0 0 1 9 13.725a16.676 16.676 0 0 1-6.201 3.548l-.536-1.929a14.7 14.7 0 0 0 5.327-3.042A18.078 18.078 0 0 1 4.767 8h2.24A16.032 16.032 0 0 0 9 10.877a16.165 16.165 0 0 0 2.91-4.876L2 6V4h6V2h2zm7.5 10.885L16.253 16h2.492L17.5 12.885z"
+              ></path>
+            </svg>
             <template #dropdown>
               <el-dropdown-menu>
-                <el-dropdown-item :disabled="langIndex === 0" command="0">{{ $t('rootPage.chinalang') }}</el-dropdown-item>
-                <el-dropdown-item :disabled="langIndex === 1" command="1">{{ $t('rootPage.englishlang') }}</el-dropdown-item>
-                <el-dropdown-item :disabled="langIndex === 2" command="2">{{ $t('rootPage.deutsch') }}</el-dropdown-item>
+                <el-dropdown-item :disabled="langIndex === 0" command="0">中文</el-dropdown-item>
+                <el-dropdown-item :disabled="langIndex === 1" command="1">English</el-dropdown-item>
+                <el-dropdown-item :disabled="langIndex === 2" command="2">Deutsch</el-dropdown-item>
               </el-dropdown-menu>
             </template>
           </el-dropdown>
@@ -65,19 +71,30 @@
 // @ is an alias to /src
 import { onMounted, ref } from 'vue';
 import { useStore } from 'vuex';
-import { useRouter } from 'vue-router';
+import { useRouter, useRoute } from 'vue-router';
 import useLangSwitch from './hooks/useLangSwitch.js';
 import { ElContainer, ElHeader, ElMenu, ElMenuItem, ElDropdown, ElDropdownMenu, ElDropdownItem } from 'element-plus';
 
 export default {
   name: 'Root',
   setup() {
+    const TopMenuMap = {
+      DataBaseM: '1',
+      Control: '2',
+    };
     const router = useRouter();
+    const route = useRoute();
     const store = useStore();
-    const menuIndex = ref('1');
+    const menuIndex = ref(TopMenuMap[route.matched[1]?.name] || '1');
     const { langIndex, handleLangCommand } = useLangSwitch();
     const handleMenuSelect = (key) => {
       menuIndex.value = key;
+      if (key == 1) {
+        store.commit('setFirstPageLoad', true);
+        router.push({ name: 'DataBaseM' });
+      } else if (key == 2) {
+        router.push({ name: 'Control' });
+      }
     };
 
     const handleLoginCommand = (val) => {
@@ -101,7 +118,6 @@ export default {
       store.commit('setFirstPageLoad', true);
       store.dispatch('fetchIsLogin');
     });
-
     // watch(
     //   () => {
     //     return store.state.userInfo;
@@ -179,14 +195,16 @@ export default {
   .lang-btn {
     position: absolute;
     right: 100px;
-    top: 50%;
+    top: 52%;
     transform: translate(0, -50%);
+    cursor: pointer;
   }
   .user-btn {
     position: absolute;
     right: 20px;
     top: 50%;
     transform: translate(0, -50%);
+    cursor: pointer;
   }
 }
 </style>
diff --git a/frontend/src/views/Source/components/dataModal.vue b/frontend/src/views/Source/components/dataModal.vue
index ba811c4..42c3c4d 100644
--- a/frontend/src/views/Source/components/dataModal.vue
+++ b/frontend/src/views/Source/components/dataModal.vue
@@ -1,3 +1,21 @@
+<!--
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+-->
 <template>
   <div id="main" class="main-contain"></div>
 </template>
@@ -19,23 +37,25 @@ export default {
   name: 'DataModal',
   //   props: ['func'],
   //   setup(props) {
-  setup() {
+  setup(props, { emit }) {
     const { t } = useI18n();
     const x = ref(0);
     const router = useRouter();
     const datas = ref({});
+    let showNum = ref(0);
 
     const getModalTreeData = (func) => {
       axios.get(`/servers/${router.currentRoute.value.params.serverid}/dataModel`, {}).then((res) => {
         if (res && res.code == 0) {
           datas.value = res.data || {};
+          showNum.value = res.data.showNum;
+          emit('show-num', showNum);
           func && func();
         }
       });
     };
-    const initCharts = () => {
-      MyCharts = echarts.init(document.getElementById('main'));
 
+    const setOption = () => {
       let option = {
         tooltip: {
           trigger: 'item',
@@ -68,6 +88,10 @@ export default {
             top: '8%',
             bottom: '8%',
             roam: true,
+            scaleLimit: {
+              min: 0.5,
+              max: 3,
+            },
             symbol: 'emptyCircle',
             symbolSize: 0,
             orient: 'vertical',
@@ -150,9 +174,16 @@ export default {
         ],
       };
 
+      // show echart
       MyCharts.setOption(option);
     };
 
+    const initCharts = () => {
+      MyCharts = echarts.init(document.getElementById('main'));
+
+      setOption();
+    };
+
     onMounted(() => {
       // getModalTreeData(() => {
       //   initCharts();
diff --git a/frontend/src/views/Source/components/dataModalAll.vue b/frontend/src/views/Source/components/dataModalAll.vue
index aaef6bd..170e9d0 100644
--- a/frontend/src/views/Source/components/dataModalAll.vue
+++ b/frontend/src/views/Source/components/dataModalAll.vue
@@ -1,10 +1,28 @@
+<!--
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+-->
 <template>
   <div id="mains" class="mains-contain"></div>
 </template>
 
 <script>
 // @ is an alias to /src
-import { onMounted, ref, onActivated, onDeactivated } from 'vue';
+import { onMounted, ref, onActivated, onDeactivated, reactive } from 'vue';
 import * as echarts from 'echarts';
 // import { ElButton } from 'element-plus';
 import { useI18n } from 'vue-i18n';
@@ -25,83 +43,88 @@ export default {
     const x = ref(0);
     const router = useRouter();
     // const route = useRoute();
-
+    const pagination = reactive({
+      pageSize: 10,
+      pageNum: 1,
+    });
     const datas = ref({});
 
     const getModalTreeData = (func) => {
-      axios.get(`/servers/${router.currentRoute.value.params.serverid}/dataModel`, {}).then((res) => {
-        if (res && res.code == 0) {
-          // dealData(res.data, (res.data && res.data.children) || [], 0);
-          dealData([res.data] || {}, 0);
-          datas.value = res.data || {};
-          func && func();
-        }
-      });
+      axios
+        .get(`/servers/${router.currentRoute.value.params.serverid}/dataModel/detail`, {
+          params: pagination,
+        })
+        .then((res) => {
+          if (res && res.code == 0) {
+            res.data.pageSize = pagination.pageSize;
+            res.data.pageNum = pagination.pageNum;
+            dealData([res.data] || {}, 0);
+            res.data.collapsed = false;
+            datas.value = res.data || {};
+            func && func();
+          }
+        });
     };
     /**
      * data current level data
      * index  level num
      */
-    const dealData = (data, index) => {
+    const dealData = (data) => {
       for (let i = 0; i < data.length; i++) {
-        if (index < initialTreeDepth.value) {
-          data[i].collapsed = false;
-        } else {
-          data[i].collapsed = true;
-        }
+        data[i].collapsed = true;
+        const isNext = data[i].total > data[i].pageNum * data[i].pageSize;
+        const isPrev = data[i].pageNum !== 1;
         if (data[i].children && data[i].children.length) {
-          if (data[i].children.length > 10) {
-            data[i].pageNum = 1;
-            data[i].totalPage = Math.ceil(data[i].children.length / 10);
-            data[i].childrensTemp = JSON.parse(JSON.stringify(data[i].children));
-            data[i].children = data[i].children.splice((data[i].pageNum - 1) * 10, 10);
-
-            data[i].children.push({ name: t('sourcePage.nextPage'), parentName: data[i].path, type: 'next', pageNum: 1, totalPage: Math.ceil(data[i].childrensTemp.length / 10) });
-            data[i].children.unshift({ name: t('sourcePage.prePage'), parentName: data[i].path, type: 'pre', pageNum: 1, totalPage: Math.ceil(data[i].childrensTemp.length / 10) });
-          } else {
-            data[i].pageNum = 1;
-            data[i].totalPage = 1;
-          }
-
-          index += 1;
-          dealData(data[i].children, index);
+          isNext && data[i].children.push({ name: t('sourcePage.nextPage'), path: data[i].path, type: 'next', pageNum: data[i].pageNum, pageSize: data[i].pageSize });
+          isPrev && data[i].children.unshift({ name: t('sourcePage.prePage'), path: data[i].path, type: 'pre', pageNum: data[i].pageNum, pageSize: data[i].pageSize });
         }
       }
     };
-
+    let loading = false;
     const clickFunction = (params) => {
+      if (loading) return;
+      loading = true;
       let data = params.data || {};
-      if (data.type) {
-        //do next if has 'type' key;
-        if (data.type == 'pre') {
-          if (data.pageNum == 1) {
-            return;
-          } else {
-            circulateData(data, 'pre');
-          }
-        } else if (data.type == 'next') {
-          if (data.pageNum == data.totalPage) {
-            return;
-          } else {
-            circulateData(data, 'next');
-          }
-        }
+      if (data.type === 'next') {
+        data.pageNum += 1;
+      } else if (data.type === 'pre' && data.pageNum >= 1) {
+        data.pageNum -= 1;
       } else {
-        if (params.data.children && params.data.children.length) {
-          circulateDataSelf(data);
-        }
+        data.pageNum = 1;
+        data.pageSize = 10;
       }
+      axios
+        .get(`/servers/${router.currentRoute.value.params.serverid}/dataModel/detail`, {
+          params: { pageNum: data.pageNum, pageSize: data.pageSize, path: data.path },
+        })
+        .then((res) => {
+          if (res && res.code == 0) {
+            dealData([res.data] || [], 0);
+            params.data.children = res.data.children || [];
+            if (params.data.children && params.data.children.length) {
+              circulateDataSelf(data, res.data || {}); //第二个参数为动态请求回来的数据
+            }
+          }
+        })
+        .finally(() => {
+          loading = false;
+        });
     };
-    const circulateDataSelf = (data) => {
+    const circulateDataSelf = (data, levelData) => {
       let name = data.path;
       // let dataAll = datas.value;
       let index = 1;
-      deepSearchSelf(datas.value, name, index);
+      deepSearchSelf(datas.value, name, index, levelData);
     };
-    const deepSearchSelf = (data, name, index) => {
+    const deepSearchSelf = (data, name, index, levelData) => {
       if (data.path == name) {
         //do it
+        data.collapsed = levelData.collapsed;
         data.collapsed = !data.collapsed;
+        data.children = levelData.children || [];
+        data.childrensTemp = levelData.childrensTemp || [];
+        data.pageNum = levelData.pageNum;
+        data.totalPage = levelData.totalPage;
         initialTreeDepth.value = index;
         MyChart.clear();
         setOption();
@@ -110,55 +133,12 @@ export default {
         index++;
         if (data.children && data.children.length) {
           for (let i = 0; i < data.children.length; i++) {
-            deepSearchSelf(data.children[i], name, index);
+            deepSearchSelf(data.children[i], name, index, levelData);
           }
         }
       }
     };
-    const circulateData = (data, type) => {
-      let name = data.parentName;
-      let dataAll = datas.value;
-      let index = 1;
-      deepSearch(dataAll, name, type, index);
-    };
     const initialTreeDepth = ref(1);
-    const deepSearch = (data, name, type, index) => {
-      if (data.path == name) {
-        //do it
-        // data.collapsed = false;
-        let temp = JSON.parse(JSON.stringify(data.childrensTemp));
-        initialTreeDepth.value = index;
-        if (type == 'next') {
-          data.pageNum += 1;
-          temp = temp.splice((data.pageNum - 1) * 10, 10);
-          data.children[0].pageNum += 1;
-          data.children[data.children.length - 1].pageNum += 1;
-        } else {
-          data.pageNum -= 1;
-          temp = temp.splice((data.pageNum - 1) * 10, 10);
-          data.children[0].pageNum -= 1;
-          data.children[data.children.length - 1].pageNum -= 1;
-        }
-        temp.unshift(data.children[0]);
-        temp.push(data.children[data.children.length - 1]);
-        data.children = temp;
-        for (let i = 0; i < temp.length; i++) {
-          if (temp[i].children && temp[i].children.length) {
-            dealData(temp[i], temp[i].children || []);
-          }
-        }
-        MyChart.clear();
-        setOption();
-        return;
-      } else {
-        index++;
-        if (data.children && data.children.length) {
-          for (let i = 0; i < data.children.length; i++) {
-            deepSearch(data.children[i], name, type, index);
-          }
-        }
-      }
-    };
     const initCharts = () => {
       //  init charts
       MyChart = echarts.init(document.getElementById('mains'));
@@ -196,11 +176,15 @@ export default {
             type: 'tree',
             edgeShape: 'polyline',
             data: [datas.value],
-            left: '2%',
+            left: '4%',
             // right: '2%',
             top: '8%',
             bottom: '8%',
             roam: true,
+            scaleLimit: {
+              min: 0.5,
+              max: 3,
+            },
             symbol: 'emptyCircle',
             symbolSize: 0,
             // orient: 'vertical',
diff --git a/frontend/src/views/Source/components/permitDialog.vue b/frontend/src/views/Source/components/permitDialog.vue
index e9b55cf..f37b4e5 100644
--- a/frontend/src/views/Source/components/permitDialog.vue
+++ b/frontend/src/views/Source/components/permitDialog.vue
@@ -21,12 +21,12 @@
   <div id="mains" class="mains-contain">
     <el-dialog v-model="visible" :title="dialogType === 'add' ? $t('sourcePage.addPermission') : $t('sourcePage.editPermission')" width="520px">
       <el-form ref="formRef" :model="form" :rules="rules" label-position="top" class="permit-form">
-        <el-form-item prop="type" :label="$t('sourcePage.path')">
+        <el-form-item prop="type" :label="language.path">
           <el-radio-group v-model="form.type" @change="changeRadio">
             <el-radio v-for="item in pathList" :disabled="dialogType === 'edit'" :key="item.value" :label="item.value">{{ item.label }}</el-radio>
           </el-radio-group>
         </el-form-item>
-        <el-form-item prop="path" :label="$t('sourcePage.range')">
+        <el-form-item prop="path" :label="language.range">
           <!-- data connection -->
           <template v-if="form.type === 0"> -- </template>
           <!-- Storage group -->
@@ -53,7 +53,7 @@
             </el-select>
           </template>
         </el-form-item>
-        <el-form-item prop="privileges" :label="$t('sourcePage.selectPermissions')">
+        <el-form-item prop="privileges" :label="language.selectPermissions">
           <el-checkbox-group v-model="form.privileges">
             <el-checkbox v-for="item in dataPrivileges[form.type]" :key="item.id" :label="item.id" :value="item.id">{{ item.label }}</el-checkbox>
           </el-checkbox-group>
@@ -61,8 +61,8 @@
       </el-form>
       <template #footer>
         <span class="dialog-footer">
-          <el-button @click="handleCancel">{{ $t('common.cancel') }}</el-button>
-          <el-button type="primary" @click="handleSubmit">{{ $t('common.submit') }}</el-button>
+          <el-button @click="handleCancel">{{ language.cancel }}</el-button>
+          <el-button type="primary" @click="handleSubmit">{{ language.submit }}</el-button>
         </span>
       </template>
     </el-dialog>
@@ -105,7 +105,15 @@ export default {
       path: [], //tree
       privileges: [],
     });
-
+    let language = computed(() => {
+      return {
+        cancel: t('common.cancel'),
+        submit: t('common.submit'),
+        path: t('sourcePage.path'),
+        range: t('sourcePage.range'),
+        selectPermissions: t('sourcePage.selectPermissions'),
+      };
+    });
     let storage = ref([]);
     let device = reactive({
       storage: '',
@@ -508,6 +516,8 @@ export default {
       form,
       granularityValue,
       rules,
+      language,
+
       ...toRefs(options),
       open,
       origin,
diff --git a/frontend/src/views/Source/components/role/AuthManage.vue b/frontend/src/views/Source/components/role/AuthManage.vue
index c069bdd..c4cd097 100644
--- a/frontend/src/views/Source/components/role/AuthManage.vue
+++ b/frontend/src/views/Source/components/role/AuthManage.vue
@@ -1,3 +1,21 @@
+<!--
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+-->
 <!-- Permission management permission -->
 <template>
   <div class="permitpermission-content">
diff --git a/frontend/src/views/Source/components/role/DataManage.vue b/frontend/src/views/Source/components/role/DataManage.vue
index 4cc52d0..027318c 100644
--- a/frontend/src/views/Source/components/role/DataManage.vue
+++ b/frontend/src/views/Source/components/role/DataManage.vue
@@ -1,3 +1,21 @@
+<!--
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+-->
 <!-- Role data management permission -->
 <template>
   <div class="data-manage">
diff --git a/frontend/src/views/Source/components/role/DialogGrantUser.vue b/frontend/src/views/Source/components/role/DialogGrantUser.vue
index 133c8bf..1885425 100644
--- a/frontend/src/views/Source/components/role/DialogGrantUser.vue
+++ b/frontend/src/views/Source/components/role/DialogGrantUser.vue
@@ -1,3 +1,21 @@
+<!--
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+-->
 <!-- Authorized user Popup -->
 <template>
   <el-dialog :title="$t('sourcePage.grantUser')" width="520px" :append-to-body="true" v-model="visible" custom-class="grant-dialog">
diff --git a/frontend/src/views/Source/components/role/Index.vue b/frontend/src/views/Source/components/role/Index.vue
index 463a028..a9e5102 100644
--- a/frontend/src/views/Source/components/role/Index.vue
+++ b/frontend/src/views/Source/components/role/Index.vue
@@ -1,3 +1,21 @@
+<!--
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+-->
 <!-- 用户角色 -->
 <template>
   <div class="user-role-wrapper">
diff --git a/frontend/src/views/Source/components/role/PowerManage.vue b/frontend/src/views/Source/components/role/PowerManage.vue
index 8e4046c..e8741b5 100644
--- a/frontend/src/views/Source/components/role/PowerManage.vue
+++ b/frontend/src/views/Source/components/role/PowerManage.vue
@@ -1,3 +1,21 @@
+<!--
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+-->
 <!-- Right part of role management -->
 <template>
   <div id="role-tabs">
diff --git a/frontend/src/views/Source/components/role/RoleInfo.vue b/frontend/src/views/Source/components/role/RoleInfo.vue
index 558bae2..24e3d5e 100644
--- a/frontend/src/views/Source/components/role/RoleInfo.vue
+++ b/frontend/src/views/Source/components/role/RoleInfo.vue
@@ -1,3 +1,21 @@
+<!--
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+-->
 <!-- User role - basic information -->
 <template>
   <el-form ref="roleForm" label-position="top" :model="form" :rules="rules" label-width="120px">
@@ -74,9 +92,12 @@ export default {
           form.value = { ...val };
           form.value.users = val.userList;
           oldForm.value = { ...form.value };
-          roleForm.value.clearValidate();
+          roleForm.value?.clearValidate();
           stateType.value = props.roleInfo.type;
         }
+      },
+      {
+        immediate: true,
       }
     );
     const rules = ref({
diff --git a/frontend/src/views/Source/components/role/RoleList.vue b/frontend/src/views/Source/components/role/RoleList.vue
index 062eaab..35ce4f6 100644
--- a/frontend/src/views/Source/components/role/RoleList.vue
+++ b/frontend/src/views/Source/components/role/RoleList.vue
@@ -1,3 +1,21 @@
+<!--
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+-->
 <!-- role list -->
 <template>
   <div class="list-wrapper">
@@ -9,7 +27,7 @@
         </svg>
       </div>
     </div>
-    <ul v-if="roleList.length" class="role-list">
+    <ul v-if="roleList?.length" class="role-list">
       <li v-for="item in roleList" :key="item" :class="[activeRole === item ? 'active-item' : '']" class="role-list-item" @click="clickRole(item)">
         <div :class="[activeRole === item ? 'circle active-circle' : 'circle']">
           <div class="small-circle"></div>
@@ -64,7 +82,7 @@ export default {
       isAdding.value = false;
       let result = await api.getRoles(serverId);
       roleList.value = result.data;
-      if (!roleList.value.length) {
+      if (!roleList.value?.length) {
         emit('changeCurrRole', { id: '', name: 'NEW' });
         activeRole.value = '';
       } else {
diff --git a/frontend/src/views/Source/index.vue b/frontend/src/views/Source/index.vue
index 0076c66..e16e5bc 100644
--- a/frontend/src/views/Source/index.vue
+++ b/frontend/src/views/Source/index.vue
@@ -40,7 +40,7 @@
       <p class="more last">
         <span
           ><span class="more-title">{{ $t('sourcePage.storageNum') + ':' }}</span
-          >{{ countInfo.groupCount }}</span
+          >{{ countInfo.storageGroupCount }}</span
         >
         <span
           ><span class="more-title">{{ $t('sourcePage.entityNum') + ':' }}</span
@@ -48,7 +48,7 @@
         >
         <span
           ><span class="more-title">{{ $t('sourcePage.physicalNum') + ':' }}</span
-          >{{ countInfo.measurementCount }}</span
+          >{{ countInfo.monitorCount }}</span
         >
         <span
           ><span class="more-title">{{ $t('sourcePage.dataNum') + ':' }}</span
@@ -79,7 +79,8 @@
         <el-tab-pane :label="$t('sourcePage.dataModel')" name="d">
           <div class="tab-content">
             <el-button class="title" @click="goToAllModal()">{{ $t('sourcePage.showMore') }}</el-button>
-            <DataModal></DataModal>
+            <div class="tip">第一层最多展示多{{ showNum }}个模型,若需查看所有模型,点击查看更多</div>
+            <DataModal @show-num="onShowNum"></DataModal>
           </div>
         </el-tab-pane>
         <el-tab-pane :label="$t('sourcePage.accountPermitLabel')" name="a">
@@ -392,6 +393,17 @@
                 </template>
               </el-table-column>
             </el-table>
+            <div class="pagination">
+              <el-pagination
+                @current-change="currentChange"
+                v-model:currentPage="pagination.pageNum"
+                v-model:page-size="pagination.pageSize"
+                layout="total, prev, pager, next"
+                :total="groupTotal"
+                :hide-on-single-page="true"
+              >
+              </el-pagination>
+            </div>
           </div>
         </el-tab-pane>
       </el-tabs>
@@ -421,6 +433,7 @@ import {
   ElPopover,
   ElPopper,
   ElTooltip,
+  ElPagination,
 } from 'element-plus';
 // import { Close } from '@element-plus/icons';
 import NewSource from './components/newSource.vue';
@@ -462,6 +475,12 @@ export default {
     let canAuth = ref(false);
 
     let canAuthRole = ref(false);
+    let showNum = ref(0);
+
+    function onShowNum(value) {
+      showNum.value = value;
+    }
+
     const router = useRouter();
     let pathList = ref([
       { label: t('sourcePage.selectAlias'), value: 0 },
@@ -764,6 +783,19 @@ export default {
       }
     };
 
+    const pagination = reactive({
+      pageSize: 10,
+      pageNum: 1,
+    });
+    function currentChange() {
+      getGroupList();
+    }
+
+    let paginationAll = {
+      pagination,
+      currentChange,
+    };
+
     const savepermitAuth = () => {
       if (store.state.dataBaseM.privilegeListAll.indexOf('GRANT_USER_PRIVILEGE') < 0) {
         ElMessage.error(t(`sourcePage.noAuthTip`));
@@ -784,10 +816,10 @@ export default {
       axios.post(`/servers/${serverId.value}/users/${baseInfoForm.userName}/authorityPrivilege`, { ...reqObj }).then((rs) => {
         if (rs && rs.code == 0) {
           ElMessage.success(t('sourcePage.successEditPermit'));
-          store.dispatch('fetchAllPrivileges', {
-            serverId: serverId.value,
-            userName: baseInfoForm.userName,
-          });
+          //   store.dispatch('fetchAllPrivileges', {
+          //     serverId: serverId.value,
+          //     userName: baseInfoForm.userName,
+          //   });
         }
       });
     };
@@ -1213,10 +1245,10 @@ export default {
     };
 
     const getGroupList = () => {
-      axios.get(`/servers/${serverId.value}/storageGroups/info`, {}).then((res) => {
+      axios.get(`/servers/${serverId.value}/storageGroups/info`, { params: pagination }).then((res) => {
         if (res && res.code == 0) {
-          tableData.value = res.data;
-          groupTotal.value = (res.data && res.data.length) || 0;
+          tableData.value = res.data?.groupInfoList;
+          groupTotal.value = (res.data && res.data.groupCount) || 0;
           let temp = [];
           for (let i = 0; i < res.data.length; i++) {
             temp.push(res.data[i].groupName);
@@ -1590,6 +1622,9 @@ export default {
       permitPermissionListTemp,
       savepermitAuth,
       permitDialogRef,
+      showNum,
+      onShowNum,
+      ...paginationAll,
     };
   },
   components: {
@@ -1616,6 +1651,7 @@ export default {
     ElPopover,
     ElPopper,
     UserRole,
+    ElPagination,
     /* eslint-disable */
   },
 };
@@ -2001,7 +2037,7 @@ export default {
   .group-table {
     width: 100%;
     padding: 10px;
-    height: 100% !important;
+    height: calc(100% - 34px) !important;
     max-height: initial !important;
     &:deep(.el-table__body-wrapper) {
       height: calc(100% - 32px) !important;
@@ -2010,5 +2046,20 @@ export default {
       padding-left: 0 !important;
     }
   }
+  .pagination {
+    display: flex;
+    justify-content: flex-end;
+    margin-top: 10px;
+
+    // padding: 10px 0px;
+    .el-pagination {
+      padding: 4px 5px 0 5px;
+    }
+  }
+  .tip {
+    float: right;
+    font-size: 12px;
+    color: $danger-color;
+  }
 }
 </style>
diff --git a/frontend/src/views/SqlSerch/index.vue b/frontend/src/views/SqlSerch/index.vue
index dd2a1a0..d205fc5 100644
--- a/frontend/src/views/SqlSerch/index.vue
+++ b/frontend/src/views/SqlSerch/index.vue
@@ -66,13 +66,16 @@
                 <div class="table_top_border"></div>
                 <div class="tab_table" v-if="item && display">
                   <stand-table
+                    :key="key"
                     ref="standTable"
                     :column="item"
-                    :tableData="tableData.list[index]"
+                    :tableData="tableDataPagination[index]"
                     :lineHeight="5"
                     :celineHeight="5"
                     :maxHeight="divwerHeight - 78"
                     :pagination="pagination"
+                    :total="total[index]"
+                    :getList="getList(index)"
                     backColor="#E7EAF2"
                   >
                   </stand-table>
@@ -162,6 +165,7 @@ export default {
     let codemirror = ref(null);
     let tabelNum = ref(0);
     const standTable = ref(null);
+    let key = ref('1');
     let activeName = ref(0);
     let activeNameRight = ref('first');
     let runFlag = ref(true);
@@ -191,6 +195,29 @@ export default {
     const treeList = reactive({
       list: [],
     });
+    const total = computed(() => tableData.list.map((item) => item?.list?.length));
+    const pagination = reactive({
+      pageSize: 10,
+      pageNum: 1,
+    });
+    const pageNums = reactive([]);
+    function getList(index) {
+      return (value) => {
+        pageNums[index] = value;
+      };
+    }
+    const tableDataPagination = computed(() =>
+      tableData.list.map((item, index) => {
+        nextTick(() => {
+          key.value = Math.random() + Date.now() + '';
+        });
+        return {
+          ...item,
+          list: item?.list?.slice(((pageNums[index] || 1) - 1) * pagination.pageSize, (pageNums[index] || 1) * pagination.pageSize),
+        };
+      })
+    );
+
     function getFunction(val) {
       codemirror.value.onCmCodeChange(val);
     }
@@ -211,62 +238,66 @@ export default {
         divwerHeight.value = 400;
         timeNumber.value = Number(new Date());
         useElementResize(dividerRef, divwerHeight);
-        querySql(routeData.obj.connectionid, { sqls: codeArr, timestamp: timeNumber.value }).then((res) => {
-          activeName.value = 't0';
-          column.list = [];
-          tableData.list = [];
-          let lengthArry = [];
-          time.list = [];
-          line.list = [];
-          tabelNum.value = res.data.length;
-          res.data.forEach((item) => {
-            let length = [];
-            time.list.push(item.queryTime);
-            line.list.push(item.line);
-            if (item.metaDataList) {
-              column.list.push({
-                list: item.metaDataList.map((eleitem, index) => {
-                  return {
-                    label: eleitem,
-                    prop: `t${index}`,
-                    width: 'auto',
-                    fixed: index === 0 ? 'left' : index === item.metaDataList.length - 1 ? 'right' : false,
-                  };
-                }),
-              });
-            } else {
-              column.list.push(null);
-            }
-            if (item.valueList) {
-              tableData.list.push({
-                list: item.valueList.map((eleitem) => {
-                  const obj = {};
-                  for (let i = 0; i < eleitem.length; i++) {
-                    if (eleitem[i].length > length[i] || !length[i]) {
-                      length[i] = eleitem[i].length;
+        querySql(routeData.obj.connectionid, { sqls: codeArr, timestamp: timeNumber.value })
+          .then((res) => {
+            activeName.value = 't0';
+            column.list = [];
+            tableData.list = [];
+            let lengthArry = [];
+            time.list = [];
+            line.list = [];
+            tabelNum.value = res.data.length;
+            res.data.forEach((item) => {
+              let length = [];
+              time.list.push(item.queryTime);
+              line.list.push(item.line);
+              if (item.metaDataList) {
+                column.list.push({
+                  list: item.metaDataList.map((eleitem, index) => {
+                    return {
+                      label: eleitem,
+                      prop: `t${index}`,
+                      width: 'auto',
+                      fixed: index === 0 ? 'left' : index === item.metaDataList.length - 1 ? 'right' : false,
+                    };
+                  }),
+                });
+              } else {
+                column.list.push(null);
+              }
+              if (item.valueList) {
+                tableData.list.push({
+                  list: item.valueList.map((eleitem) => {
+                    const obj = {};
+                    for (let i = 0; i < eleitem.length; i++) {
+                      if (eleitem[i].length > length[i] || !length[i]) {
+                        length[i] = eleitem[i].length;
+                      }
+                      obj[`t${i}`] = eleitem[i];
                     }
-                    obj[`t${i}`] = eleitem[i];
-                  }
-                  return obj;
-                }),
-              });
-            } else {
-              tableData.list.push(null);
-            }
-            lengthArry.push(length);
+                    return obj;
+                  }),
+                });
+              } else {
+                tableData.list.push(null);
+              }
+              lengthArry.push(length);
+            });
+            // lengthArry.forEach((element, i) => {
+            //   element.forEach((item, index) => {
+            //     if (index === element.length - 1) {
+            //       return false;
+            //     }
+            //     column.list[i].list[index].width = column.list[i].list[index].label.length < item ? item * 12 : column.list[i].list[index].label.length * 12;
+            //   });
+            // });
+            display.value = true;
+            runFlag.value = true;
+            console.log(line.list);
+          })
+          .finally(() => {
+            runFlag.value = true;
           });
-          // lengthArry.forEach((element, i) => {
-          //   element.forEach((item, index) => {
-          //     if (index === element.length - 1) {
-          //       return false;
-          //     }
-          //     column.list[i].list[index].width = column.list[i].list[index].label.length < item ? item * 12 : column.list[i].list[index].label.length * 12;
-          //   });
-          // });
-          display.value = true;
-          runFlag.value = true;
-          console.log(line.list);
-        });
         setTimeout(() => {
           runFlag.value = true;
         }, 5000);
@@ -294,6 +325,7 @@ export default {
             type: 'success',
             message: t('device.savesuccess'),
           });
+          sqlName.value = null;
           centerDialogVisible.value = false;
           props.func.updateTree();
           let locationId = '';
@@ -416,6 +448,12 @@ export default {
       querySqlRun,
       deleteQuery,
       routeData,
+      total,
+      pagination,
+      getList,
+      pageNums,
+      tableDataPagination,
+      key,
     };
   },
   components: {
diff --git a/frontend/src/views/storage/index.vue b/frontend/src/views/storage/index.vue
index 6490324..ec9bbac 100644
--- a/frontend/src/views/storage/index.vue
+++ b/frontend/src/views/storage/index.vue
@@ -60,7 +60,7 @@
           <el-input v-model="searchVal" class="search-btn" suffix-icon="el-icon-search" :placeholder="$t('device.devicename')" @blur="search()" @keyup.enter="search()"></el-input>
         </div>
         <div class="device-list">
-          <el-table :data="tableData" style="width: 100%">
+          <el-table :data="tableData" style="width: 100%" v-loading="loading">
             <el-table-column show-overflow-tooltip prop="deviceName" :label="$t('device.devicename')" width="180" sortable>
               <template #default="scope">
                 <a class="to-entity" @click="goToEntity(scope)">{{ scope.row.deviceName }}</a>
@@ -102,10 +102,10 @@ export default {
   props: ['data', 'func'],
   setup(props) {
     const { t, locale } = useI18n();
-
     const router = useRouter();
     let baseInfo = ref({});
     let searchVal = ref(null);
+    const loading = ref(false);
     let tableData = ref([]);
     let currentPage = ref(1);
     const pageSize = ref(10);
@@ -184,6 +184,7 @@ export default {
      * groupname:storage name
      */
     const getDeviceList = () => {
+      loading.value = true;
       axios
         .get(`/servers/${router.currentRoute.value.params.serverid}/storageGroups/${router.currentRoute.value.params.groupname}/devices/info`, {
           params: {
@@ -200,6 +201,9 @@ export default {
             tableData.value = [];
             total.value = 0;
           }
+        })
+        .finally(() => {
+          loading.value = false;
         });
     };
     /**
@@ -270,6 +274,7 @@ export default {
       t,
       baseInfo,
       searchVal,
+      loading,
       tableData,
       currentPage,
       pageSize,
diff --git a/frontend/src/views/storage/newStorage.vue b/frontend/src/views/storage/newStorage.vue
index e79c105..88c320d 100644
--- a/frontend/src/views/storage/newStorage.vue
+++ b/frontend/src/views/storage/newStorage.vue
@@ -78,7 +78,7 @@ export default {
           trigger: 'blur',
         },
         {
-          pattern: /^[^.][a-zA-Z0-9_\u4e00-\u9fa5.]+$/,
+          pattern: /^(["'a-zA-Z0-9_\u4e00-\u9fa5]*)$/,
           message: () => {
             return t(`sourcePage.newUserErrorTip`);
           },
diff --git a/frontend/vue.config.js b/frontend/vue.config.js
index 1624e93..a2636dc 100644
--- a/frontend/vue.config.js
+++ b/frontend/vue.config.js
@@ -22,6 +22,8 @@ function resolve(dir) {
   return path.join(__dirname, dir);
 }
 
+const Version = new Date().getTime();
+
 module.exports = {
   chainWebpack: (config) => {
     config.resolve.alias.set('@', resolve('./src')).set('components', resolve('./src/components'));
@@ -30,15 +32,39 @@ module.exports = {
     loaderOptions: {
       sass: {
         prependData: `@use "@/styles/variables.scss" as *;`,
+        sassOptions: {
+          outputStyle: 'expanded',
+        },
       },
     },
+    extract: {
+      // 打包后css文件名称添加时间戳
+      filename: `css/[name].${Version}.css`,
+      chunkFilename: `css/chunk.[id].${Version}.css`,
+    },
+  },
+  configureWebpack: {
+    // 重点
+    // 输出重构 打包编译后的js文件名称,添加时间戳.
+    output: {
+      filename: `js/[name].${Version}.js`,
+      chunkFilename: `js/chunk.[id].${Version}.js`,
+    },
   },
+  productionSourceMap: false,
   devServer: {
     proxy: {
       '/.*': {
-        target: 'http://127.0.0.1:9090',
-        changeOrigin: true,
+        target: 'http://192.168.1.84:9090',
+        progress: false,
       },
+      // '/user': {
+      //     target: 'http://119.84.128.59:8079/api',
+      //     changeOrigin: true,
+      //     pathRewrite: {
+      //         '^/user': ''
+      //     }
+      // },
     },
   },
 };