You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@linkis.apache.org by pe...@apache.org on 2022/02/15 02:55:29 UTC

[incubator-linkis] 01/06: add linkis datasource web

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

peacewong pushed a commit to branch dev-1.1.0-datasource
in repository https://gitbox.apache.org/repos/asf/incubator-linkis.git

commit e42c146e3460b12d0e94e1cd0ca549e4b6ca23b2
Author: luxiaolong <li...@126.com>
AuthorDate: Mon Dec 6 19:40:04 2021 +0800

    add linkis datasource web
---
 web/src/apps/linkis/i18n/common/en.json            |  43 +-
 web/src/apps/linkis/i18n/common/zh.json            |  46 +-
 .../apps/linkis/module/datasource/dataSourceApi.js | 167 ++++++
 .../module/datasource/datasourceForm/index.scss    |  78 +++
 .../module/datasource/datasourceForm/index.vue     | 272 +++++++++
 .../module/datasource/datasourceType/index.scss    | 111 ++++
 .../module/datasource/datasourceType/index.vue     |  76 +++
 web/src/apps/linkis/module/datasource/index.js     |  21 +
 web/src/apps/linkis/module/datasource/index.scss   |  65 +++
 web/src/apps/linkis/module/datasource/index.vue    | 613 +++++++++++++++++++++
 web/src/apps/linkis/router.js                      |  16 +-
 web/src/apps/linkis/view/linkis/index.vue          |   1 +
 web/src/main.js                                    |   8 +-
 13 files changed, 1505 insertions(+), 12 deletions(-)

diff --git a/web/src/apps/linkis/i18n/common/en.json b/web/src/apps/linkis/i18n/common/en.json
index 89b5509..22a0ade 100644
--- a/web/src/apps/linkis/i18n/common/en.json
+++ b/web/src/apps/linkis/i18n/common/en.json
@@ -65,7 +65,7 @@
       "resourceManagement": {
         "resourceUsage": "Resource usage",
         "applicationList": "Application List"
-    },
+      },
       "time": {
         "second": "Second",
         "minute": "Minute",
@@ -120,7 +120,8 @@
             "dateReport": "Global Variables",
             "globalValiable": "Frequently Asked",
             "microserviceManage": "Microservice management",
-            "ECMManage": "ECM Management"
+            "ECMManage": "ECM Management",
+            "dataSourceManage": "DataSource Manage"
           }
         }
       },
@@ -235,7 +236,43 @@
       },
       "success": {
         "update": "Successfully updated global variables!"
+      },
+      "datasource": {
+        "pleaseInput": "Please input",
+        "datasourceSrc": "Datasource",
+        "connectTest": "Test Connection",
+        "sourceName": "Data source name",
+        "sourceDec": "Data source description",
+        "sourceType": "Data source type:",
+        "creator": "Creator:",
+        "create": "New data source",
+        "exports": "Demonstration export data source",
+        "imports": "Demonstration of importing data sources",
+        "Expired": "Expired",
+        "versionList": "Version List",
+        "dataSourceName": "Data Source Name",
+        "dataSourceType": "Data Source Type",
+        "dataSourceEnv": "Available Space",
+        "status": "Status",
+        "permissions": "Permissions",
+        "label": "label",
+        "Version": "Version",
+        "desc": "Description",
+        "action": "Action",
+        "createUser": "Create User",
+        "createTime": "Create Time",
+        "versionDec": "Version Description",
+        "watch": "View",
+        "rollback": "Rollback",
+        "publish": "Publish",
+        "initVersion": "Initial Version",
+        "updateVersion": "Version update",
+        "published": "Published",
+        "unpublish": "Unpublished",
+        "cannotPublish": "Cannot Publish",
+        "used": "Available",
+        "commentValue": "Roll back from version {text}"
       }
     }
   }
-}
+}
\ No newline at end of file
diff --git a/web/src/apps/linkis/i18n/common/zh.json b/web/src/apps/linkis/i18n/common/zh.json
index e6dbed1..e56b79c 100644
--- a/web/src/apps/linkis/i18n/common/zh.json
+++ b/web/src/apps/linkis/i18n/common/zh.json
@@ -6,7 +6,7 @@
             "emptyString": "空字符串",
             "addAppType": "新增应用类型",
             "editContents": "编辑目录",
-            "eurekeRegisterCenter":  "Eureke注册中心",
+            "eurekeRegisterCenter": "Eureke注册中心",
             "addParameterConfig": "新增参数配置",
             "editDescriptionEngineConfig": "编辑引擎配置",
             "name": "名称",
@@ -54,6 +54,9 @@
             "generalView": "切换普通视图",
             "manageView": "切换管理员视图",
             "back": "返回",
+            "prev": "上一步",
+            "complete": "完成",
+            "close": "关闭",
             "warning": {
                 "api": "接口请求中,请稍候!",
                 "data": "数据请求中,请稍候!",
@@ -120,7 +123,8 @@
                         "dateReport": "全局变量",
                         "globalValiable": "常见问题",
                         "ECMManage": "ECM管理",
-                        "microserviceManage": "微服务管理"
+                        "microserviceManage": "微服务管理",
+                        "dataSourceManage": "数据源管理"
                     }
                 }
             },
@@ -235,7 +239,43 @@
             },
             "success": {
                 "update": "全局变量更新成功!"
+            },
+            "datasource": {
+                "pleaseInput": "请输入",
+                "datasourceSrc": "数据源",
+                "connectTest": "测试连接",
+                "sourceName": "数据源名称",
+                "sourceDec": "数据源描述",
+                "sourceType": "数据源类型:",
+                "creator": "创建人:",
+                "create": "新增数据源",
+                "exports": "批量导出数据源",
+                "imports": "批量导入数据源",
+                "overdue": "过期",
+                "versionList": "版本列表",
+                "dataSourceName": "数据源名称",
+                "dataSourceType": "数据源类型",
+                "dataSourceEnv": "可用集群",
+                "status": "状态",
+                "permissions": "权限",
+                "label": "标签",
+                "version": "版本",
+                "desc": "描述",
+                "action": "操作",
+                "createUser": "创建人",
+                "createTime": "创建时间",
+                "versionDec": "版本描述",
+                "watch": "查看",
+                "rollback": "回滚",
+                "publish": "发布",
+                "initVersion": "初始化版本",
+                "updateVersion": "版本更新",
+                "published": "已发布",
+                "unpublish": "未发布",
+                "cannotPublish": "不可发布",
+                "used": "可用",
+                "commentValue": "从版本 {text} 回滚"
             }
         }
     }
-}
+}
\ No newline at end of file
diff --git a/web/src/apps/linkis/module/datasource/dataSourceApi.js b/web/src/apps/linkis/module/datasource/dataSourceApi.js
new file mode 100644
index 0000000..d718501
--- /dev/null
+++ b/web/src/apps/linkis/module/datasource/dataSourceApi.js
@@ -0,0 +1,167 @@
+/*
+ * 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 api from '@/common/service/api';
+import { serialize } from 'object-to-formdata';
+
+/**
+ * 获取数据源列表
+ */
+const getDataSourceList = (params)=>{
+  return api.fetch('data_source/info', params, 'get')
+}
+
+/**
+ * 获取数据源类型列表
+ */
+const getDataSourceTypeList = ()=>{
+  return api.fetch('/data_source/type/all', {}, 'get')
+}
+
+/**
+ * 获取环境列表
+ */
+const getEnvList = ()=>{
+  return api.fetch('data_source/env', {}, 'get')
+}
+
+/**
+ * 
+ * @returns 获取datasource key定义
+ */
+const getKeyDefine = (id)=>{
+  return api.fetch(`/data_source/key_define/type/${id}`, {}, 'get')
+}
+
+/**
+ * 创建数据源
+ * @param {*} realFormData 
+ * @returns 
+ */
+const createDataSource = (realFormData)=>{
+  return api.fetch('data_source/info/json', realFormData)
+}
+
+/**
+ * 创建数据源 formdata
+ * @param {*} realFormData 
+ * @returns 
+ */
+const createDataSourceForm = (realFormData)=>{
+  return api.fetch('data_source/info/form', realFormData, {methed: 'post', 'Content-Type': 'text/plain'})
+}
+
+/**
+ * 更新数据源
+ * @param {*} datasourceId 
+ * @param {*} data 
+ * @returns 
+ */
+const updateDataSource = (data, datasourceId)=>{
+  return api.fetch(`data_source/info/${datasourceId}/json`, data, 'put')
+}
+
+/**
+ * 
+ * @param {数据源id} datasourceId 
+ * @param {连接参数} data 
+ * @returns 
+ */
+const saveConnectParams = (datasourceId, data, comment)=>{
+  return api.fetch(`/data_source/parameter/${datasourceId}/json`, {connectParams: data, comment})//{connectParams: data, comment}
+}
+
+/**
+ * 创建数据源 formdata
+ * @param {*} realFormData 
+ * @returns 
+ */
+const saveConnectParamsForm = (datasourceId, data, comment)=>{
+  const formData = serialize({connectParams: data, comment});
+  return api.fetch(`/data_source/parameter/${datasourceId}/form`, formData, {methed: 'post', 'Content-Type': 'text/plain'})
+}
+  
+
+/**
+ * 
+ * @param {数据源id} datasourceId 
+ * @returns 数据源详情
+ */
+const getDataSourceByIdAndVersion = (datasourceId, version)=>{
+  return api.fetch(`/data_source/info/${datasourceId}/${version}`, {}, 'get')
+}
+/**
+ * 获取版本列表
+ * @param {数据源id}} datasourceId 
+ * @returns 
+ */
+const getVersionListByDatasourceId = (datasourceId)=>{
+  return api.fetch(`/data_source/${datasourceId}/versions`, {}, 'get')
+}
+  
+
+
+
+/**
+ * 设置过期=软删除  
+ * @param {数据源id} datasourceId 
+ * @returns 
+ */
+const expire = (datasourceId)=>{
+  return api.fetch(`/data_source/info/${datasourceId}/expire`, {}, 'put')
+}
+
+/**
+ * 发布数据源
+ * @param {*} datasourceId 
+ * @param {*} versionId 
+ * @returns 
+ */
+const publish = (datasourceId, versionId)=>{
+  return api.fetch(`data_source/publish/${datasourceId}/${versionId}`, {}, 'post')
+}
+
+/**
+ * 连接数据源
+ * @param {连接信息} data 
+ * @returns 
+ */
+const connect = (data)=> {
+  return api.fetch(`data_source/op/connect/json`, data);
+}
+
+
+//过期done
+//创建数据源,创建版本done
+//
+
+export  {
+  getDataSourceList,
+  getDataSourceTypeList,
+  getEnvList,
+  getKeyDefine,
+  createDataSource,
+  saveConnectParams,
+  getDataSourceByIdAndVersion,
+  updateDataSource,
+  expire,
+  publish,
+  connect,
+  createDataSourceForm,
+  getVersionListByDatasourceId,
+  saveConnectParamsForm
+}
\ No newline at end of file
diff --git a/web/src/apps/linkis/module/datasource/datasourceForm/index.scss b/web/src/apps/linkis/module/datasource/datasourceForm/index.scss
new file mode 100644
index 0000000..2a1033b
--- /dev/null
+++ b/web/src/apps/linkis/module/datasource/datasourceForm/index.scss
@@ -0,0 +1,78 @@
+/*
+ * 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 '@/common/style/variables.scss';
+
+.search-bar {
+  .search-item {
+    display: flex;
+    justify-content: flex-start;
+    align-items: center;
+    font-size: $font-size-base;
+
+    .lable {
+      min-width: 90px;
+      // flex-basis: 130px;
+      text-align: right;
+    }
+  }
+
+  .ivu-col {
+    display: flex;
+    justify-content: center;
+  }
+
+}
+
+.table-content {
+  margin-top: 25px;
+}
+
+.datasource-type-wrap {
+  .project-header {
+    height: 32px;
+
+    .header-title {
+      font-size: $font-size-large;
+      font-weight: bold;
+      padding-left: 12px;
+      border-left: 3px solid $primary-color;
+      color: $text-title-color;
+    }
+
+    .header-tool {
+      float: right;
+      margin-right: 6%;
+      color: $primary-color;
+      cursor: pointer;
+
+      .sort-icon {
+        margin-right: 20px;
+
+        .icon {
+          margin-left: 5px;
+          color: $primary-color;
+        }
+      }
+
+      .search-input {
+        width: 200px;
+        margin-right: 30px;
+      }
+    }
+  }
+
+}
diff --git a/web/src/apps/linkis/module/datasource/datasourceForm/index.vue b/web/src/apps/linkis/module/datasource/datasourceForm/index.vue
new file mode 100644
index 0000000..6960314
--- /dev/null
+++ b/web/src/apps/linkis/module/datasource/datasourceForm/index.vue
@@ -0,0 +1,272 @@
+<!--
+  ~ 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="table-warp">
+    <form-create :rule="rule" v-model="fApi" :option="options" :value.sync="formData"/>
+  </div>
+</template>
+<script>
+import _, { merge, mergeWith} from 'lodash';
+import api from '@/common/service/api';
+import {getKeyDefine, getDataSourceByIdAndVersion} from '../dataSourceApi';
+// import {mysql, hive} from '../datasource';
+
+const type = {
+  TEXT: {type: 'input'},
+  NUMBER: {type: 'input', props: {type: 'number'}},
+  PASSWORD: {type: 'input', props: {type: 'password'}},
+  EMAIL: {type: 'input', props: {type: 'email'}},
+  DATE: {type: 'input', props: {type: 'date'}},
+  TEL: {type: 'input', props: {type: 'tel'}},
+  TEXTAREA: {type: 'input', props: {type: 'textarea'}},
+  URL: {type: 'input', props: {type: 'url'}},
+  FILE: (self, source)=>{
+    return {type: 'upload', props: {uploadType: 'file', action: '',allowRemove: true, "maxLength": 1,
+      beforeUpload: (file)=>{
+        self.file = file;
+        source.props.value = file.name;
+        return false;
+      }}}
+  },
+  SELECT: {type: 'select', props: {placement: "bottom"}}
+}
+const typesMap = {
+  valueType: (data, source, self)=>{
+    if(type[data.valueType]){
+      if(typeof type[data.valueType] === 'function'){
+        return type[data.valueType](self, source);
+      }
+      return type[data.valueType]
+    }else{
+      return {type: data.valueType}
+    }
+  },
+  name: 'title',
+
+  defaultValue: 'value',
+  // dataSource: 'options',
+  dataSource: (data, source, self)=>{
+    const fApi = self.fApi;
+    if(/^https?:/.test(data.dataSource)){
+      api.fetch(data.dataSource, {}, 'get').then(result=>{
+        delete source.options;
+        source.options = result.env_list.map(item=>{
+          return {label: item.envName, value: ''+item.id}
+        });
+        // console.log('self.rule',self.rule)
+        fApi.refreshOptions();
+      })
+      return {options: []}
+    }else {
+      try {
+        return {options: JSON.parse(data.dataSource)}
+      } catch (error) {
+        return {options: []}
+      }
+    }
+  },
+  key: 'field',
+  description: (data)=>{
+    return {props: {placeholder: data.description}}
+  },
+  require: (data)=>{
+    if(data.require) //&& !data.valueRegex
+      return {validate: [{ required: true, message: `请输入${data.name}`, trigger: 'blur' }]}
+    else return null
+  },
+  valueRegex: (data)=>{
+    if(data.valueRegex){
+      return {validate: [{pattern: new RegExp(data.valueRegex), message: '不符合规则', trigger: 'blur'}]}
+    }
+    else return null;
+  },
+  // valueRegex: (data)=>{
+  //   if(data.valueRegex)
+  //     return {validate: { pattern: new RegExp(data.valueRegex), message: '不符合规则', trigger: 'blur' }}
+  //   else return null
+  // },
+
+  refId: 'refId',
+  refValue: 'refValue',
+  id: 'id',
+}
+export default {
+  props: {
+    data: Object
+  },
+  data () {
+    return {
+      sourceConnectData: {},
+      fApi: {},
+      loading: false,
+      formData: {file: 'adn'},
+      options: {
+        submitBtn: false,
+      },
+      rule: [
+        {
+          type: "input",
+          title: this.$t('message.linkis.datasource.sourceName'),
+          field: "dataSourceName",
+          value: "",
+          props: {
+            "placeholder": this.$t('message.linkis.datasource.sourceName'),
+          },
+          validate: [{
+            required: true,
+            message: `${this.$t('message.linkis.datasource.pleaseInput')}${this.$t('message.linkis.datasource.sourceName')}`,
+            trigger: 'blur'
+          },
+          ],
+        },
+        {
+          type: "input",
+          title: this.$t('message.linkis.datasource.sourceDec'),
+          field: "dataSourceDesc",
+          value: "",
+          props: {
+            "placeholder": this.$t('message.linkis.datasource.sourceDec'),
+          }
+        },
+        {
+          type: "input",
+          title: this.$t('message.linkis.datasource.label'),
+          field: "labels",
+          value: "",
+          props: {
+            "placeholder": this.$t('message.linkis.datasource.label'),
+          }
+        }
+      ],
+
+    }
+  },
+  created(){
+    this.loading = true;
+
+    this.getDataSource(this.data);
+
+    getKeyDefine(this.data.dataSourceTypeId).then((data)=>{
+      this.loading = false;
+      this.transformData(data.key_define);
+    })
+  },
+  watch: {
+    data: {
+      handler (newV) {
+
+        this.getDataSource(newV);
+      },
+      deep: true
+    }
+  },
+  methods: {
+    getDataSource(newV){
+      if(this.data.id){
+        getDataSourceByIdAndVersion(newV.id, newV.versionId||0).then(result=>{
+
+          const mConnect = result.info.connectParams;
+          this.sourceConnectData = mConnect;
+          delete result.info.connectParams;
+          this.dataSrc = { ...result.info,  ...mConnect};
+          this.formData = { ...result.info,  ...mConnect};
+        })
+      }else{
+        const connectParams = newV.connectParams;
+        delete newV.connectParams;
+        this.formData = {...newV, ...connectParams};
+        this.dataSrc = {...newV, ...connectParams};
+      }
+    },
+    transformData(keyDefinitions){
+      const tempData = [];
+
+      keyDefinitions.forEach((obj)=>{
+        let item = {};
+        Object.keys(obj).forEach((keyName) =>{
+
+          switch (typeof typesMap[keyName]) {
+            case 'object':
+              item = merge({}, item, typesMap[keyName])
+              break;
+
+            case 'function':
+
+              item = mergeWith(item, typesMap[keyName](obj, item, this), function(objValue, srcValue){
+                if(_.isArray(objValue)) {
+                  return objValue.concat(srcValue);
+                }
+              });
+              break;
+
+            case 'string':
+              item[typesMap[keyName]] = obj[keyName];
+              break;
+          }
+        });
+        tempData.push(item);
+      });
+
+      const insertParent = (id, child)=>{
+
+        let parent = tempData.find(item=> id==item.id);
+        if(parent && child){
+
+          if(!parent.control || parent.control.length===0) {
+            parent.control = [  //不存在新建
+              {
+                value: child.refValue,
+                rule: [{...child} ]
+              }
+            ]
+          }else {
+            let index = parent.control.findIndex(item=>item.value+'' === child.refValue+'');
+            if(index > -1){
+              parent.control[index].rule.push({...child})
+            }else {
+              parent.control.push(
+                {
+                  value: child.refValue,
+                  rule: [{...child} ]
+                }
+              )
+            }
+
+          }
+
+        }
+
+      }
+
+      for(var i =0; i<tempData.length; i++){
+        let item = tempData[i];
+        if(item.refId && item.refId > 0){
+          let children = tempData.splice(i, 1);
+          i--;
+          insertParent(item.refId, children[0]);
+        }
+      }
+      this.rule =  this.rule.concat(tempData);
+      return tempData;
+
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped src="./index.scss"></style>
diff --git a/web/src/apps/linkis/module/datasource/datasourceType/index.scss b/web/src/apps/linkis/module/datasource/datasourceType/index.scss
new file mode 100644
index 0000000..0b66b25
--- /dev/null
+++ b/web/src/apps/linkis/module/datasource/datasourceType/index.scss
@@ -0,0 +1,111 @@
+/*
+ * 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 '@/common/style/variables.scss';
+
+.datasource-type-wrap {
+  position: relative;
+  height: 400px;
+  overflow-y: auto;
+
+  .header-tool {
+    position: absolute;
+    top: 0;
+    right: 0;
+    margin-right: 6%;
+    color: $primary-color;
+    cursor: pointer;
+
+    .sort-icon {
+      margin-right: 20px;
+
+      .icon {
+        margin-left: 5px;
+        color: $primary-color;
+      }
+    }
+
+    .search-input {
+      width: 200px;
+      margin-right: 30px;
+    }
+  }
+
+  .classifier {
+    // padding-top: 20px;
+
+    .project-header {
+      height: 32px;
+
+      .header-title {
+        font-size: $font-size-large;
+        font-weight: bold;
+        padding-left: 12px;
+        border-left: 3px solid $primary-color;
+        color: $text-title-color;
+      }
+    }
+
+    .resource-item {
+
+      background-color: #fff;
+      box-shadow: 0 0 6px 0 rgba(0 ,0 ,0,0.2);
+      margin-right: 12px;
+      margin-bottom: 16px;
+      display: inline-block;
+      text-align: center;
+      width: 140px;
+      height: 96px;
+      cursor: pointer;
+      position: relative;
+      vertical-align: top;
+      margin-left: 20px;
+
+      &:hover {
+        box-shadow: 0 0 6px 0 rgba(0,154,176,0.6);
+      }
+
+      .resource-img {
+        width: 140px;
+        height: 87px;
+        display: block;
+        box-sizing: content-box;
+        margin-left: auto;
+        margin-right: auto;
+        margin-top: 10px;
+      }
+
+      .resource-name {
+        text-align: center;
+        display: block;
+        position: absolute;
+        bottom: 0;
+        width: 100%;
+        font-size: 12px;
+        height: 30px;
+        padding: 4px;
+        display: flex;
+        -webkit-align-items: center;
+        -ms-flex-align: center;
+        align-items: center;
+        -webkit-justify-content: center;
+        -ms-flex-pack: center;
+        justify-content: center;
+      }
+    }
+  }
+
+}
diff --git a/web/src/apps/linkis/module/datasource/datasourceType/index.vue b/web/src/apps/linkis/module/datasource/datasourceType/index.vue
new file mode 100644
index 0000000..4371b8e
--- /dev/null
+++ b/web/src/apps/linkis/module/datasource/datasourceType/index.vue
@@ -0,0 +1,76 @@
+<!--
+  ~ 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="datasource-type-wrap">
+
+    <div class="classifier" v-for="(item, name) in sourceType" :key="name">
+      <h3 class="project-header">
+        <span class="header-title" >{{name}}</span>
+      </h3>
+
+      <div class="resource-item" v-for="sourceItem in item" :key="sourceItem.name"
+        @click="onSelected(sourceItem)">
+        <img class="resource-img" :src="sourceItem.icon"/>
+        <span class="resource-name">{{sourceItem.name}}</span>
+      </div>
+    </div>
+
+    <Spin size="large" fix v-if="loading"></Spin>
+    
+  </div>
+</template>
+
+<script>
+export default {
+  props: {
+    onSelected: Function,
+    data: Array
+  },
+  methods: {
+    transformTypeData(data){
+      const tempObj = {};
+      data.forEach(item=>{
+        if(!tempObj[item.classifier]){
+          tempObj[item.classifier] = [];
+        }
+        tempObj[item.classifier].push(item)
+      })
+      this.sourceType = tempObj;
+    }
+  },
+  computed: {
+    sourceType: function(){
+      const tempObj = {};
+      this.data.forEach(item=>{
+        if(!tempObj[item.classifier]){
+          tempObj[item.classifier] = [];
+        }
+        tempObj[item.classifier].push(item)
+      })
+      return tempObj;
+    }
+  },
+  data() {
+    return {
+      loading: false
+    }  
+  },
+}
+</script>
+
+<style lang="scss" scoped src="./index.scss"></style>
\ No newline at end of file
diff --git a/web/src/apps/linkis/module/datasource/index.js b/web/src/apps/linkis/module/datasource/index.js
new file mode 100644
index 0000000..8218e73
--- /dev/null
+++ b/web/src/apps/linkis/module/datasource/index.js
@@ -0,0 +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 default {
+  name: 'datasourceManager',
+  component: () => import('./index.vue'),
+};
\ No newline at end of file
diff --git a/web/src/apps/linkis/module/datasource/index.scss b/web/src/apps/linkis/module/datasource/index.scss
new file mode 100644
index 0000000..7ea4fd6
--- /dev/null
+++ b/web/src/apps/linkis/module/datasource/index.scss
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+@import '@/common/style/variables.scss';
+
+.search-bar {
+  .search-item {
+    display: flex;
+    justify-content: flex-start;
+    align-items: center;
+    font-size: $font-size-base;
+
+    .lable {
+      min-width: 90px;
+      // flex-basis: 130px;
+      text-align: right;
+    }
+  }
+
+  .ivu-col {
+    display: flex;
+    justify-content: center;
+  }
+
+}
+
+.table-content {
+  margin-top: 25px;
+}
+
+.datasource-type-wrap {
+  .project-header {
+    height: 32px;
+
+    .header-title {
+      font-size: $font-size-large;
+      font-weight: bold;
+      padding-left: 12px;
+      border-left: 3px solid $primary-color;
+      color: $text-title-color;
+    }
+
+  }
+
+}
+
+.modal {
+  .footer {
+    display: flex;
+    justify-content: space-between;
+  }
+}
diff --git a/web/src/apps/linkis/module/datasource/index.vue b/web/src/apps/linkis/module/datasource/index.vue
new file mode 100644
index 0000000..839ff08
--- /dev/null
+++ b/web/src/apps/linkis/module/datasource/index.vue
@@ -0,0 +1,613 @@
+<!--
+  ~ 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>
+    <Modal
+      width="800"
+      class="modal"
+      v-model="showVersionList"
+      :loading="loadingForm"
+      :title="`${actionType}${$t('message.linkis.datasource.versionList')}`">
+      <Spin size="large" fix v-if="loadingVersionList"></Spin>
+      <Table
+        border
+        size="small"
+        align="center"
+        :columns="versionTableColumnNum"
+        :data="currentVersionList"
+        max-height="300"
+        class="table-content">
+        <template
+          slot-scope="{row, index}"
+          slot="action"
+        >
+          <ButtonGroup size="small" :key="row.versionId">
+            
+            <Button v-if="row.status==0" size="small" type="text" @click="onPublish(row)"> {{$t('message.linkis.datasource.publish')}} </Button>
+                      
+            <Button size="small" type="text" @click="watch(row, index)">{{$t('message.linkis.datasource.watch')}}</Button>
+            <Button v-if="row.status==2" size="small" type="text" @click="onRollback(row)"> {{$t('message.linkis.datasource.rollback')}} </Button>
+
+            <Button type="primary" @click="onConnectTestFromVersion(row)">{{$t('message.linkis.datasource.connectTest')}}</Button>
+          </ButtonGroup>
+        </template>
+      </Table>
+      <div slot="footer">
+        <div v-if="currentStep===0">
+          <Button @click="showVersionList = false">{{$t('message.linkis.cancel')}}</Button>
+        </div>
+      </div>
+    </Modal>
+    <Modal
+      width="800"
+      class="modal"
+      v-model="showDataSource"
+      :title="`${actionType}${$t('message.linkis.datasource.datasourceSrc')}`">
+      <Spin size="large" fix v-if="loadingForm"></Spin>
+      <DataSourceType :data="type_list" v-show="currentStep===0" :onSelected="onSelect"/>
+      <DatasourceForm :data="currentSourceData" ref="datasourceForm" v-if="showDataSource&&currentStep===1"/>
+
+      <div slot="footer">
+        <div v-if="currentStep===0">
+          <Button @click="showDataSource = false">{{$t('message.linkis.cancel')}}</Button>
+        </div>
+
+        <div v-else  class="footer">
+          <div><Button type="primary" @click="onConnectFormTest">{{$t('message.linkis.datasource.connectTest')}}</Button></div>
+          <div>
+            <Button v-if="actionType==$t('message.linkis.create')" @click="stepChange(-1)">{{$t('message.linkis.prev')}}</Button>
+            <Button v-if="actionType==$t('message.linkis.datasource.watch')" type="primary" @click="showDataSource = false">{{$t('message.linkis.close')}}</Button>
+            <Button v-else type="primary" @click="onSubmit">{{$t('message.linkis.complete')}}</Button>
+          </div>
+        </div>
+      </div>
+    </Modal>
+    <Row class="search-bar" type="flex" justify="space-around">
+      <Col span="6">
+        <Input clearable v-model="searchName" suffix="ios-search" class="input" :placeholder="$t('message.linkis.datasource.sourceName')" @on-enter="searchList(true)"></Input>
+      </Col>
+      <Col span="6" class="search-item">
+        <span class="lable">{{$t('message.linkis.datasource.sourceType')}}</span>
+        <Select v-model="dataSourceTypeId" class="input">
+          <Option v-for="item in type_list" :value="item.id" :key="item.id">{{item.name}}</Option>
+          <Option value="null">{{$t('message.linkis.statusType.all')}}</Option>
+        </Select>
+      </Col>
+      <Col span="3">
+        <Button type="primary" class="button" @click="searchList(true)">{{$t('message.linkis.search')}}</Button>
+      </Col>
+      <Col span="3">
+        <Button type="primary" @click="createDatasource">{{$t('message.linkis.datasource.create')}}</Button>
+      </Col>
+      <Col span="3">
+        <Button type="primary" disabled>{{$t('message.linkis.datasource.exports')}}</Button>
+      </Col>
+      <Col span="3">
+        <Button type="primary" disabled>{{$t('message.linkis.datasource.imports')}}</Button>
+      </Col>
+    </Row>
+    <Table
+      border
+      size="small"
+      align="center"
+      :columns="tableColumnNum"
+      :data="pageDatalist"
+      max-height="420"
+      :loading="tableLoading"
+      class="table-content">
+      <template
+        slot-scope="{row, index}"
+        slot="version"
+      >
+        <Button size="small" type="primary" :disabled="row.expire" @click="openVersionList(row, index)">{{`${row.versionId||'-'}`}}</Button>
+      </template>
+      <template
+        slot-scope="{row, index}"
+        slot="action"
+      >
+        <ButtonGroup size="small">
+          <Button :disabled="row.expire" size="small" type="primary" @click="modify(row, index)">{{$t('message.linkis.edit')}}</Button>
+
+          <Button :disabled="row.expire" size="small" type="primary" @click="overdue(row)"> {{$t('message.linkis.datasource.overdue')}} </Button>
+          
+          <Button type="primary" @click="onConnectTest(row)"> {{$t('message.linkis.datasource.connectTest')}} </Button>
+        </ButtonGroup>
+      </template>
+    </Table>
+    <div style="margin: 10px;overflow: hidden">
+      <div style="float: right;">
+        <Page :page-size="page.pageSize" :total="page.totalSize" :current="page.pageNow" @on-change="changePage"></Page>
+      </div>
+    </div>
+  </div>
+</template>
+<script>
+import DatasourceForm from './datasourceForm/index';
+import DataSourceType from './datasourceType/index';
+import {  //createDataSourceForm
+  getDataSourceList,getDataSourceTypeList,getEnvList,createDataSource, saveConnectParams, saveConnectParamsForm,
+  expire, updateDataSource, publish, connect, getDataSourceByIdAndVersion, getVersionListByDatasourceId
+} from './dataSourceApi';
+// import _ from 'lodash';
+const FORM_KEYS = ['dataSourceName', 'dataSourceDesc', 'labels']; //'dataSourceEnvId'
+export default {
+  components: {
+    DatasourceForm,
+    DataSourceType
+  },
+  data() {
+    return {
+      currentStep: 0,
+      currentSourceData: null,
+      dataSourceTypeId: null,
+      isCreate: true,
+      showDataSource: false,
+      searchName: '',
+      searchCreator: '',
+      actionType: '',
+      loadingForm: false,
+      tableLoading: false,
+      loadingVersionList: false,
+      page: {
+        totalSize: 0,
+        pageSize: 10,
+        pageNow: 1
+      },
+      tableColumnNum: [
+        {
+          title: this.$t('message.linkis.datasource.dataSourceName'),
+          key: 'dataSourceName',
+          minWidth: 120,
+          tooltip: true,
+          align: 'center'
+        },
+        {
+          title: this.$t('message.linkis.datasource.dataSourceType'),
+          key: 'dataSourceTypeId',
+          minWidth: 100,
+          render: (h, params)=>{
+            let result = {};
+            if(this.type_list){
+              result = this.type_list.find(item => params.row.dataSourceTypeId == item.id) || {};
+            }
+            return h('span', result.name);
+          },
+          align: 'center'
+        },
+        // {
+        //   title: this.$t('message.linkis.datasource.dataSourceEnv'),
+        //   key: 'dataSourceEnvId',
+        //   tooltip: true,
+        //   minWidth: 100,
+        //   render: (h, params)=>{
+        //     let result = {};
+        //     if(this.env_list){
+        //       result = this.env_list.find(item => params.row.dataSourceEnvId == item.id) || {envName: '无'};
+        //     }
+        //     return h('span', result.envName);
+        //   },
+        //   align: 'center'
+        // },
+        {
+          title: this.$t('message.linkis.datasource.status'),
+          key: 'status',
+          tooltip: true,
+          minWidth: 60,
+          align: 'center',
+          render: (h, params)=>{
+            let result = {};
+            if(this.env_list){
+             
+              result = this.tableStatusMap.find(item => params.row.expire == item.status) || {};
+            }
+            var color = 'green';
+            if(result.status){
+              color = 'red';
+            }
+            return h('p',{style: {color: color,},}, result.name);
+          }
+        },
+        {
+          title: this.$t('message.linkis.datasource.label'),
+          key: 'labels',
+          tooltip: true,
+          minWidth: 80,
+          align: 'center',
+        },
+        {
+          title: this.$t('message.linkis.datasource.version'),
+          key: 'version',
+          slot: "version",
+          minWidth: 60,
+          // render: (h, params)=>{
+          //   return h('span', params.row.versionId || '-');
+          // },
+          align: 'center',
+        },
+        {
+          title: this.$t('message.linkis.datasource.desc'),
+          key: 'dataSourceDesc',
+          minWidth: 120,
+          
+          tooltip: true,
+          align: 'center'
+        },
+        {
+          title: this.$t('message.linkis.datasource.createUser'),
+          key: 'createUser',
+          minWidth: 80,
+          tooltip: true,
+          align: 'center'
+        },
+        {
+          title: this.$t('message.linkis.datasource.action'),
+          minWidth: 170,
+          slot: "action",
+          align: 'center'
+        },
+      ],
+      pageDatalist: [],
+      type_list: [],
+      versionStatusMap: [{status: 1, name: this.$t('message.linkis.datasource.published')}, {status: 0, name: this.$t('message.linkis.datasource.unpublish')}, {status: 2, name: this.$t('message.linkis.datasource.cannotPublish')} ],
+      tableStatusMap: [{status: false, name: this.$t('message.linkis.datasource.used')}, {status: true, name: this.$t('message.linkis.datasource.overdue')} ],
+      currentVersionList: [],
+      showVersionList: false,
+      versionTableColumnNum: [
+        {
+          title: this.$t('message.linkis.datasource.version'),
+          key: 'versionId',
+          tooltip: true,
+          minWidth: 60,
+          align: 'center'
+        },
+        {
+          title: this.$t('message.linkis.datasource.status'),
+          key: 'status',
+          tooltip: true,
+          minWidth: 60,
+          align: 'center',
+          render: (h, params)=>{
+            let result = {};
+            result = this.versionStatusMap.find(item => params.row.status == item.status) || {};
+            return h('span', result.name);
+          },
+        },
+        {
+          title: this.$t('message.linkis.datasource.versionDec'),
+          key: 'comment',
+          tooltip: true,
+          minWidth: 60,
+          align: 'center'
+        },
+        // {
+        //   title: this.$t('message.linkis.datasource.createTime'),
+        //   key: 'createTime',
+        //   tooltip: true,
+        //   minWidth: 60,
+        //   align: 'center'
+        // },
+        {
+          title: this.$t('message.linkis.datasource.action'),
+          key: 'action',
+          tooltip: true,
+          minWidth: 120,
+          align: 'center',
+          slot: 'action'
+        }
+      ]
+    }
+  },
+  created(){
+    
+    getDataSourceTypeList().then((data)=>{
+      this.type_list = data.type_list;
+
+      getEnvList().then((data)=>{
+        this.env_list = data.query_list;
+
+        this.searchList();
+      })
+    })
+
+    
+
+    
+  },
+  methods: {
+    overdue(data){
+      expire(data.id).then(()=>{
+        this.searchList();
+      })
+    },
+    searchList(isSearch){
+      this.loadingTable = true;
+      if(isSearch) {
+        this.page.pageNow = 1;
+      }
+      const data = {
+        typeId: this.dataSourceTypeId,
+        pageSize: this.page.pageSize, 
+        currentPage: this.page.pageNow,
+        name: this.searchName
+      };
+     
+      if(data.typeId == 'null'){
+        delete data.typeId;
+      }
+      
+      getDataSourceList(
+        {
+          ...data
+        }).then(result=>{
+        this.tableLoading = false;
+        this.pageDatalist = result.query_list;
+        this.page.totalSize = result.totalPage;
+      });
+    },
+    getVersionListBySourceId(){
+
+      getVersionListByDatasourceId(this.currentSourceData.id).then(result=>{
+        this.currentVersionList = result.versions;
+        this.currentVersionList.sort((a, b)=>{
+          if(a.versionId > b.versionId){
+            return -1;
+          }
+          if(a.versionId < b.versionId){
+            return 1;
+          }
+          return 0
+        })
+        let lastPulishIndex = Infinity;
+        for (let index = 0; index < this.currentVersionList.length; index++) {
+          const element = this.currentVersionList[index];
+          if(lastPulishIndex < index){  //不能发布
+            element.status = 2;
+          }else{
+            element.status = 0;
+          }
+        
+          if(this.currentSourceData.publishedVersionId == element.versionId){
+            lastPulishIndex = index;
+            element.status = 1;
+          }
+        
+        }
+      })
+
+
+      
+      
+    },
+    openVersionList(row){
+      this.currentSourceData = row;
+      this.getVersionListBySourceId();
+      this.showVersionList = true;
+    },
+
+    changePage(value){
+      this.page.pageNow = value;
+      this.searchList();
+    },
+    watch(data) {
+      this.actionType = this.$t('message.linkis.datasource.watch');
+      this.showDataSource = true;
+      this.currentSourceData.versionId = data.versionId;
+      this.currentStep = 1;
+    },
+    modify(data) {
+      this.actionType = this.$t('message.linkis.edit');
+      this.showDataSource = true;
+      this.currentSourceData = data;
+      this.currentStep = 1;
+    },
+    createDatasource(){
+      this.currentStep = 0;
+      this.currentSourceData = null;
+      this.actionType = this.$t('message.linkis.create');
+      this.showDataSource = true;
+    },
+    onSelect(item){
+      this.currentSourceData = {dataSourceTypeId: item.id};
+      this.currentStep = 1;
+    },
+    stepChange(step){
+
+      this.currentStep += step;
+      if(this.currentStep<0){
+        this.currentStep = 0;
+      }else if(this.currentStep>1){
+        this.currentStep = 1;
+      }
+      
+    },
+    tranceFormData(data){
+      const formData = new FormData();
+      Object.keys(data).forEach((key) => {
+        if(typeof data[key] === 'object'){
+          formData.append(key, JSON.stringify(data[key]));
+        }else {
+          formData.append(key, data[key]);
+        }
+        
+      });
+      return formData;
+    },
+    isEqual(obj1, obj2) {
+      const isObject = (obj)=> {
+        return typeof obj === 'object' && obj !== null
+      }
+      if (!isObject(obj1) || !isObject(obj2)) {
+        return obj1 === obj2
+      }
+      if (obj1 === obj2) {
+        return true
+      }
+      let obj1Keys = Object.keys(obj1)
+      let obj2Keys = Object.keys(obj2)
+      if (obj1Keys.length !== obj2Keys.length) {
+        return false
+      }
+      for (let key in obj1) {
+        const res = this.isEqual(obj1[key], obj2[key])
+        if (!res) {
+          return false
+        }
+      }
+      // 否则全相等
+      return true
+    },
+    onSubmit(){
+      this.$refs.datasourceForm.fApi.submit((formData)=>{
+        
+        const realFormData = {};
+        FORM_KEYS.forEach(key=>{
+          realFormData[key] = formData[key];
+          delete formData[key];
+        })
+        realFormData.connectParams = formData;
+        realFormData.createSystem = "Linkis";
+        realFormData.dataSourceTypeId = this.currentSourceData.dataSourceTypeId;
+
+        
+        
+        
+
+        let postDataSource = createDataSource;
+        let commentMsg = this.$t('message.linkis.datasource.initVersion');
+        if(!this.currentSourceData.id){ //新增数据源
+          postDataSource = createDataSource;
+        }else{
+          postDataSource = updateDataSource;
+          commentMsg = this.$t('message.linkis.datasource.updateVersion');
+        }
+        this.loadingForm = true;
+        postDataSource(realFormData, this.currentSourceData.id).then(data=>{
+          this.loadingForm = false;
+          // if(连接信息有变化)
+          
+          const sourceId = data.id || data.insert_id || data.update_id;
+
+          console.log()
+          if(!this.currentSourceData.id || !this.isEqual(this.preProcessData(this.$refs.datasourceForm.sourceConnectData), formData)){
+            if(this.$refs.datasourceForm.file){
+              formData.file = this.$refs.datasourceForm.file;
+              saveConnectParamsForm(sourceId, formData, commentMsg).then(()=>{
+                this.$Message.success('Success');
+                this.searchList();
+              });
+              this.showDataSource = false;
+            }else {
+              saveConnectParams(sourceId, formData, commentMsg).then(()=>{
+                this.$Message.success('Success');
+                this.searchList();
+              });
+              this.showDataSource = false;
+            }
+          }else {
+            this.$Message.success('Success');
+            this.searchList();
+            this.showDataSource = false;
+          }
+          
+        }).catch(()=>{
+          this.loadingForm = false;
+        })
+      })
+    },
+    preProcessData(data){
+      Object.keys(data).forEach(item=>{
+        let obj = data[item]
+        if(typeof obj === 'undefined' || obj === null || obj === '') {
+          delete data[item];
+        }
+      })
+      return data;
+    },
+    onConnectTestFromVersion(data){
+      this.loadingVersionList = true;
+      getDataSourceByIdAndVersion(data.datasourceId, data.versionId).then(data=>{
+        connect(data.info).then(()=>{
+          this.loadingVersionList = false;
+          this.$Message.success('Connect Success');
+  
+        }).catch(()=>{
+          this.loadingVersionList = false;
+        })
+      })
+    },
+    onConnectTest(data){
+      this.currentSourceData = data;
+      this.tableLoading = true;
+      getDataSourceByIdAndVersion(this.currentSourceData.id, this.currentSourceData.versionId).then(data=>{
+        connect(data.info).then(()=>{
+          this.tableLoading = false;
+          this.$Message.success('Connect Success');
+  
+        }).catch(()=>{
+          this.tableLoading = false;
+        })
+      }).catch(()=>{
+        this.tableLoading = false;
+      })
+      
+    },
+    onConnectFormTest(){
+      this.$refs.datasourceForm.fApi.submit(()=>{
+        this.loadingForm = true;
+        this.$refs.datasourceForm.fApi.submit((formData)=>{
+          const realFormData = {};
+          FORM_KEYS.forEach(key=>{
+            realFormData[key] = formData[key];
+            delete formData[key];
+          })
+
+          realFormData.connectParams = formData;
+          realFormData.createSystem = "Linkis";
+          realFormData.dataSourceTypeId = this.currentSourceData.dataSourceTypeId;
+          realFormData.dataSourceEnvId = parseInt(realFormData.dataSourceEnvId);
+          connect(realFormData).then(()=>{
+            this.loadingForm = false;
+            this.$Message.success('Connect Success');
+          }).catch(()=>{this.loadingForm = false;})
+        })
+      })
+      
+    },
+    onPublish(data){
+      this.loadingVersionList = true;
+      publish(data.datasourceId, data.versionId).then(()=>{
+        this.showVersionList = false;
+        this.loadingVersionList = false;
+        this.$Message.success('Publish Success');
+        this.searchList();
+      })
+    },
+    onRollback(data){
+      this.showVersionList = false;
+      const sourceId = data.id || data.datasourceId || data.update_id;
+  
+      saveConnectParams(sourceId, data.connectParams, this.$t('message.linkis.datasource.commentValue', {text: data.versionId})).then(()=>{
+        this.$Message.success('onRollback Success');
+        this.searchList();
+      });
+    }
+    
+  }
+}
+</script>
+<style lang="scss" src="./index.scss" scoped></style>
\ No newline at end of file
diff --git a/web/src/apps/linkis/router.js b/web/src/apps/linkis/router.js
index bc662f9..1523561 100644
--- a/web/src/apps/linkis/router.js
+++ b/web/src/apps/linkis/router.js
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
- 
+
 export const subAppRoutes = {
   path: '',
   name: 'layout',
@@ -75,7 +75,7 @@ export default [
         title: 'setting',
         publicPage: true,
       },
-    },{
+    }, {
       name: 'ECM',
       path: 'ECM',
       component: () =>
@@ -84,7 +84,7 @@ export default [
         title: 'ECM',
         publicPage: true,
       },
-    },{
+    }, {
       name: 'EngineConnList',
       path: 'EngineConnList',
       component: () =>
@@ -121,6 +121,16 @@ export default [
         title: 'microServiceManagement',
         publicPage: true,
       },
+    },
+    {
+      name: 'datasource',
+      path: 'datasource',
+      component: () =>
+        import('./module/datasource/index.vue'),
+      meta: {
+        title: 'datasourceManagement',
+        publicPage: true,
+      },
     }
     ],
   },
diff --git a/web/src/apps/linkis/view/linkis/index.vue b/web/src/apps/linkis/view/linkis/index.vue
index 0510647..69bba89 100644
--- a/web/src/apps/linkis/view/linkis/index.vue
+++ b/web/src/apps/linkis/view/linkis/index.vue
@@ -85,6 +85,7 @@ export default {
           { key: '1-6', name: this.$t('message.linkis.sideNavList.function.children.ECMManage'), path: '/console/ECM' },
           { key: '1-7', name: this.$t('message.linkis.sideNavList.function.children.microserviceManage'), path: '/console/microService' },
           { key: '1-5', name: this.$t('message.linkis.sideNavList.function.children.globalValiable'), path: '/console/FAQ' },
+          { key: '1-8', name: this.$t('message.linkis.sideNavList.function.children.dataSourceManage'), path: '/console/dataSource' },
         ],
       },
       breadcrumbSecondName: this.$t('message.linkis.sideNavList.function.children.globalHistory')
diff --git a/web/src/main.js b/web/src/main.js
index 698a85c..3f297f0 100644
--- a/web/src/main.js
+++ b/web/src/main.js
@@ -14,11 +14,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
- 
+
 
 import Vue from 'vue'
 import iView from 'iview'
 import VueRouter from 'vue-router'
+import formCreate from '@form-create/iview'
 import { apps } from './dynamic-apps'
 import component from './components'
 import App from './dss/view/app.vue'
@@ -35,12 +36,12 @@ import './dss/module/index.js'
 
 // moduleMixin
 if (apps.requireComponent) {
-  apps.requireComponent.forEach(item=>{
+  apps.requireComponent.forEach(item => {
     mixinDispatch(item)
   })
 }
 if (apps.requireComponentVue) {
-  apps.requireComponentVue.forEach(item=>{
+  apps.requireComponentVue.forEach(item => {
     mixinDispatch(undefined, item)
   })
 }
@@ -50,6 +51,7 @@ Vue.use(component)
 Vue.use(iView, {
   i18n: (key, value) => i18n.t(key, value)
 })
+Vue.use(formCreate)
 
 Vue.config.productionTip = false
 Vue.prototype.$Message.config({

---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@linkis.apache.org
For additional commands, e-mail: commits-help@linkis.apache.org