You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@seatunnel.apache.org by so...@apache.org on 2023/05/25 09:31:40 UTC

[seatunnel-web] branch add_canvas_job_define updated: [Feat][UI] Add datasource in this project.

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

songjian pushed a commit to branch add_canvas_job_define
in repository https://gitbox.apache.org/repos/asf/seatunnel-web.git


The following commit(s) were added to refs/heads/add_canvas_job_define by this push:
     new f6fa0109 [Feat][UI] Add datasource in this project.
f6fa0109 is described below

commit f6fa0109d3983a6e8d8d24f329574758a1e419b5
Author: songjianet <17...@qq.com>
AuthorDate: Thu May 25 17:31:29 2023 +0800

    [Feat][UI] Add datasource in this project.
---
 seatunnel-ui/src/App.tsx                           |   5 +-
 .../src/layouts/dashboard/header/menu/use-menu.ts  |   4 +
 seatunnel-ui/src/locales/en_US/datasource.ts       |  88 ++++++++++
 seatunnel-ui/src/locales/en_US/index.ts            |   4 +-
 seatunnel-ui/src/locales/en_US/menu.ts             |   3 +-
 seatunnel-ui/src/locales/zh_CN/datasource.ts       |  84 ++++++++++
 seatunnel-ui/src/locales/zh_CN/index.ts            |   4 +-
 seatunnel-ui/src/locales/zh_CN/menu.ts             |   3 +-
 .../src/router/{routes.ts => datasource.ts}        |  68 ++++----
 seatunnel-ui/src/router/routes.ts                  |  11 +-
 seatunnel-ui/src/service/datasource/index.ts       |  83 ++++++++++
 .../zh_CN/index.ts => service/datasource/types.ts} |  46 +++---
 .../{locales/zh_CN => store/datasource}/index.ts   |  42 ++---
 .../zh_CN/menu.ts => store/datasource/types.ts}    |  20 ++-
 seatunnel-ui/src/views/data-pipes/create/index.tsx |   2 +-
 seatunnel-ui/src/views/data-pipes/list/index.tsx   |   2 +-
 .../components/json-highlight.module.scss}         |  16 +-
 .../views/datasource/components/json-highlight.tsx |  73 ++++++++
 .../views/datasource/components/source-modal.tsx   |  90 ++++++++++
 .../components/source-model.module.scss}           |  25 ++-
 .../src/views/datasource/components/use-source.ts  |  85 ++++++++++
 seatunnel-ui/src/views/datasource/create/index.tsx | 184 +++++++++++++++++++++
 .../src/views/datasource/create/use-detail.ts      | 119 +++++++++++++
 .../src/views/datasource/create/use-form.ts        | 113 +++++++++++++
 .../menu.ts => views/datasource/index.module.scss} |  40 ++++-
 seatunnel-ui/src/views/datasource/list/index.tsx   | 173 +++++++++++++++++++
 .../src/views/datasource/list/use-columns.ts       | 111 +++++++++++++
 .../src/views/datasource/list/use-table.ts         |  71 ++++++++
 seatunnel-ui/src/views/user-manage/list/index.tsx  |   2 +-
 29 files changed, 1455 insertions(+), 116 deletions(-)

diff --git a/seatunnel-ui/src/App.tsx b/seatunnel-ui/src/App.tsx
index 0a006934..415f1ceb 100644
--- a/seatunnel-ui/src/App.tsx
+++ b/seatunnel-ui/src/App.tsx
@@ -19,6 +19,7 @@ import { defineComponent, computed, watch, ref } from 'vue'
 import {
   NConfigProvider,
   NMessageProvider,
+  NDialogProvider,
   darkTheme,
   dateZhCN,
   dateEnUS,
@@ -79,7 +80,9 @@ const App = defineComponent({
         locale={this.settingStore.getLocales === 'zh_CN' ? zhCN : enUS}
       >
         <NMessageProvider>
-          <router-view />
+          <NDialogProvider>
+            <router-view />
+          </NDialogProvider>
         </NMessageProvider>
       </NConfigProvider>
     )
diff --git a/seatunnel-ui/src/layouts/dashboard/header/menu/use-menu.ts b/seatunnel-ui/src/layouts/dashboard/header/menu/use-menu.ts
index aa599ad6..ebdc7994 100644
--- a/seatunnel-ui/src/layouts/dashboard/header/menu/use-menu.ts
+++ b/seatunnel-ui/src/layouts/dashboard/header/menu/use-menu.ts
@@ -35,6 +35,10 @@ export function useMenu() {
       label: () => h(NEllipsis, null, { default: () => t('menu.tasks') }),
       key: 'tasks'
     },
+    {
+      label: () => h(NEllipsis, null, { default: () => t('menu.datasource') }),
+      key: 'datasource'
+    },
     {
       label: () => h(NEllipsis, null, { default: () => t('menu.user_manage') }),
       key: 'user-manage'
diff --git a/seatunnel-ui/src/locales/en_US/datasource.ts b/seatunnel-ui/src/locales/en_US/datasource.ts
new file mode 100644
index 00000000..6edf737c
--- /dev/null
+++ b/seatunnel-ui/src/locales/en_US/datasource.ts
@@ -0,0 +1,88 @@
+/*
+ * 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 {
+  datasource: 'DataSource',
+  create_datasource: 'Create DataSource',
+  choose_datasource_type: 'Choose DataSource Type',
+  search: 'Search',
+  search_input_tips: 'Please input the keywords',
+  select: 'Select',
+  datasource_name: 'Datasource Name',
+  datasource_name_tips: 'Please enter datasource name',
+  datasource_user_name: 'Owner',
+  datasource_type: 'Datasource Type',
+  datasource_parameter: 'Datasource Parameter',
+  description: 'Description',
+  description_tips: 'Please enter description',
+  create_time: 'Create Time',
+  update_time: 'Update Time',
+  operation: 'Operation',
+  click_to_view: 'Click to view',
+  delete: 'Delete',
+  confirm: 'Confirm',
+  cancel: 'Cancel',
+  create: 'Create',
+  edit: 'Edit',
+  edit_datasource: 'Edit DataSource',
+  success: 'Success',
+  test_connect: 'Test Connect',
+  test_connect_success: 'Test Connect Success',
+  ip: 'IP',
+  ip_tips: 'Please enter IP',
+  port: 'Port',
+  port_tips: 'Please enter port',
+  database_name: 'Database Name',
+  database_name_tips: 'Please enter database name',
+  oracle_connect_type: 'ServiceName or SID',
+  oracle_connect_type_tips: 'Please select serviceName or SID',
+  oracle_service_name: 'ServiceName',
+  oracle_sid: 'SID',
+  jdbc_connect_parameters: 'jdbc connect parameters',
+  principal_tips: 'Please enter Principal',
+  authentication_type: 'Authentication Type',
+  dm_server_authentication: 'DM Server Authentication',
+  ldap_authentication: 'LDAP Authentication',
+  operating_system_authentication: 'Operating System Authentication',
+  kerberos_authentication: 'Kerberos Authentication',
+  krb5_conf_tips:
+    'Please enter the kerberos authentication parameter java.security.krb5.conf',
+  keytab_username_tips:
+    'Please enter the kerberos authentication parameter login.user.keytab.username',
+  keytab_path_tips:
+    'Please enter the kerberos authentication parameter login.user.keytab.path',
+  format_tips: 'Please enter format',
+  connection_parameter: 'connection parameter',
+  user_name: 'User Name',
+  user_name_tips: 'Please enter your username',
+  user_name_tips1: 'please enter “/”+username,for example:/username',
+  user_name_tips2: 'please enter “//”+username,for example://username',
+  user_name_tips3: 'please enter “///”+username,for example:///username',
+  user_password: 'Password',
+  user_password_tips: 'Please enter your password',
+  jdbc_format_tips: 'jdbc connection parameters is not a correct JSON format',
+  all: 'All',
+  warning: 'Warning',
+  close_confirm_tips:
+    'This operation will lose the source currently being created',
+  database: 'Database',
+  file: 'File',
+  no_structured: 'NoSQLs',
+  storage: 'Storage',
+  data_analysis: 'Data Analysis',
+  remote_connection: 'Remote Connection'
+}
diff --git a/seatunnel-ui/src/locales/en_US/index.ts b/seatunnel-ui/src/locales/en_US/index.ts
index fefb82cb..6d57ce9d 100644
--- a/seatunnel-ui/src/locales/en_US/index.ts
+++ b/seatunnel-ui/src/locales/en_US/index.ts
@@ -24,6 +24,7 @@ import log from '@/locales/en_US/log'
 import jobs from '@/locales/en_US/jobs'
 import tasks from '@/locales/en_US/tasks'
 import setting from '@/locales/en_US/setting'
+import datasource from '@/locales/en_US/datasource'
 
 export default {
   login,
@@ -34,5 +35,6 @@ export default {
   log,
   jobs,
   tasks,
-  setting
+  setting,
+  datasource
 }
diff --git a/seatunnel-ui/src/locales/en_US/menu.ts b/seatunnel-ui/src/locales/en_US/menu.ts
index c5566870..800541f5 100644
--- a/seatunnel-ui/src/locales/en_US/menu.ts
+++ b/seatunnel-ui/src/locales/en_US/menu.ts
@@ -22,5 +22,6 @@ export default {
   help: 'Help',
   setting: 'Setting',
   logout: 'Logout',
-  tasks: 'Tasks'
+  tasks: 'Tasks',
+  datasource: 'Datasource'
 }
diff --git a/seatunnel-ui/src/locales/zh_CN/datasource.ts b/seatunnel-ui/src/locales/zh_CN/datasource.ts
new file mode 100644
index 00000000..6aaf46bd
--- /dev/null
+++ b/seatunnel-ui/src/locales/zh_CN/datasource.ts
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export default {
+  datasource: '数据源',
+  create_datasource: '创建源',
+  choose_datasource_type: '选择源类型',
+  search:'搜索',
+  search_input_tips: '请输入关键字',
+  select: '更改',
+  datasource_name: '源名称',
+  datasource_name_tips: '请输入数据源名称',
+  datasource_user_name: '所属用户',
+  datasource_type: '源类型',
+  datasource_parameter: '参数',
+  description: '描述',
+  description_tips: '请输入描述',
+  create_time: '创建时间',
+  update_time: '更新时间',
+  operation: '操作',
+  click_to_view: '点击查看',
+  delete: '删除',
+  confirm: '确定',
+  cancel: '取消',
+  create: '创建',
+  edit: '编辑',
+  edit_datasource: '编辑源',
+  success: '成功',
+  test_connect: '测试连接',
+  test_connect_success: '测试连接成功',
+  ip: 'IP主机名',
+  ip_tips: '请输入IP主机名',
+  port: '端口',
+  port_tips: '请输入端口',
+  database_name: '数据库名',
+  database_name_tips: '请输入数据库名',
+  oracle_connect_type: '服务名或SID',
+  oracle_connect_type_tips: '请选择服务名或SID',
+  oracle_service_name: '服务名',
+  oracle_sid: 'SID',
+  jdbc_connect_parameters: 'jdbc连接参数',
+  principal_tips: '请输入Principal',
+  authentication_type: '验证方式',
+  dm_server_authentication: '达梦服务器验证',
+  ldap_authentication: 'LDAP验证',
+  operating_system_authentication: '操作系统验证',
+  kerberos_authentication: 'Kerberos验证',
+  krb5_conf_tips: '请输入kerberos认证参数 java.security.krb5.conf',
+  keytab_username_tips: '请输入kerberos认证参数 login.user.keytab.username',
+  keytab_path_tips: '请输入kerberos认证参数 login.user.keytab.path',
+  format_tips: '请输入格式为',
+  connection_parameter: '连接参数',
+  user_name: '用户名',
+  user_name_tips: '请输入用户名',
+  user_name_tips1: '请输入“/”+用户名,例:/username',
+  user_name_tips2: '请输入“//”+用户名,例://username',
+  user_name_tips3: '请输入“//”+用户名,例:///username',
+  user_password: '密码',
+  user_password_tips: '请输入密码',
+  jdbc_format_tips: 'jdbc连接参数不是一个正确的JSON格式',
+  all: '所有',
+  warning: '警告',
+  close_confirm_tips: '此操作会丢失当前正在创建的源',
+  database: '传统数据库',
+  file: '文件',
+  no_structured: '非结构化',
+  storage: '存储',
+  data_analysis: '数据分析',
+  remote_connection: '远程连接'
+}
diff --git a/seatunnel-ui/src/locales/zh_CN/index.ts b/seatunnel-ui/src/locales/zh_CN/index.ts
index 04351711..c3e11f4a 100644
--- a/seatunnel-ui/src/locales/zh_CN/index.ts
+++ b/seatunnel-ui/src/locales/zh_CN/index.ts
@@ -24,6 +24,7 @@ import log from '@/locales/zh_CN/log'
 import jobs from '@/locales/zh_CN/jobs'
 import tasks from '@/locales/zh_CN/tasks'
 import setting from '@/locales/zh_CN/setting'
+import datasource from '@/locales/zh_CN/datasource'
 
 export default {
   login,
@@ -34,5 +35,6 @@ export default {
   log,
   jobs,
   tasks,
-  setting
+  setting,
+  datasource
 }
diff --git a/seatunnel-ui/src/locales/zh_CN/menu.ts b/seatunnel-ui/src/locales/zh_CN/menu.ts
index e689b51e..5270249f 100644
--- a/seatunnel-ui/src/locales/zh_CN/menu.ts
+++ b/seatunnel-ui/src/locales/zh_CN/menu.ts
@@ -22,5 +22,6 @@ export default {
   help: '帮助',
   setting: '设置',
   logout: '登出',
-  tasks: '任务'
+  tasks: '任务',
+  datasource: '数据源'
 }
diff --git a/seatunnel-ui/src/router/routes.ts b/seatunnel-ui/src/router/datasource.ts
similarity index 54%
copy from seatunnel-ui/src/router/routes.ts
copy to seatunnel-ui/src/router/datasource.ts
index 19fdd67e..9a8de1cc 100644
--- a/seatunnel-ui/src/router/routes.ts
+++ b/seatunnel-ui/src/router/datasource.ts
@@ -16,45 +16,43 @@
  */
 
 import utils from '@/utils'
-import dataPipes from '@/router/data-pipes'
-import jobs from '@/router/jobs'
-import tasks from '@/router/tasks'
-import userManage from '@/router/user-manage'
-import type { RouteRecordRaw } from 'vue-router'
 import type { Component } from 'vue'
 
 const modules = import.meta.glob('/src/views/**/**.tsx')
 const components: { [key: string]: Component } = utils.mapping(modules)
 
-const basePage: RouteRecordRaw[] = [{
-    path: '/',
-    redirect: { name: 'login' }
+export default {
+  path: '/datasource',
+  name: 'datasource',
+  meta: {
+    title: 'datasource'
   },
-  dataPipes, jobs, tasks, userManage]
-
-const loginPage: RouteRecordRaw[] = [
-  {
-    path: '/login',
-    name: 'login',
-    component: components['login']
-  },
-  {
-    path: '/setting',
-    redirect: { name: 'setting' },
-    component: () => import('@/layouts/dashboard'),
-    children: [
-      {
-        path: '/setting',
-        name: 'setting',
-        component: components['setting'],
-        meta: {
-          title: 'setting'
-        }
+  redirect: { name: 'datasource-list' },
+  component: () => import('@/layouts/dashboard'),
+  children: [
+    {
+      path: '/datasource/list',
+      name: 'datasource-list',
+      component: components['datasource-list'],
+      meta: {
+        title: 'datasource-list'
       }
-    ]
-  }
-]
-
-const routes: RouteRecordRaw[] = [...basePage, ...loginPage]
-
-export default routes
+    },
+    {
+      path: '/datasource/create',
+      name: 'datasource-create',
+      component: components['datasource-create'],
+      meta: {
+        title: 'datasource-create'
+      }
+    },
+    {
+      path: '/datasource/:id',
+      name: 'datasource-edit',
+      component: components['datasource-create'],
+      meta: {
+        title: 'datasource-edit'
+      }
+    }
+  ]
+}
diff --git a/seatunnel-ui/src/router/routes.ts b/seatunnel-ui/src/router/routes.ts
index 19fdd67e..15d9eeff 100644
--- a/seatunnel-ui/src/router/routes.ts
+++ b/seatunnel-ui/src/router/routes.ts
@@ -20,17 +20,24 @@ import dataPipes from '@/router/data-pipes'
 import jobs from '@/router/jobs'
 import tasks from '@/router/tasks'
 import userManage from '@/router/user-manage'
+import datasource from '@/router/datasource'
 import type { RouteRecordRaw } from 'vue-router'
 import type { Component } from 'vue'
 
 const modules = import.meta.glob('/src/views/**/**.tsx')
 const components: { [key: string]: Component } = utils.mapping(modules)
 
-const basePage: RouteRecordRaw[] = [{
+const basePage: RouteRecordRaw[] = [
+  {
     path: '/',
     redirect: { name: 'login' }
   },
-  dataPipes, jobs, tasks, userManage]
+  dataPipes,
+  jobs,
+  tasks,
+  userManage,
+  datasource
+]
 
 const loginPage: RouteRecordRaw[] = [
   {
diff --git a/seatunnel-ui/src/service/datasource/index.ts b/seatunnel-ui/src/service/datasource/index.ts
new file mode 100644
index 00000000..0982eba0
--- /dev/null
+++ b/seatunnel-ui/src/service/datasource/index.ts
@@ -0,0 +1,83 @@
+/*
+ * 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 '@/service/service'
+
+export function datasourceList(params: any): any {
+  return axios({
+    url: '/datasource/list',
+    method: 'get',
+    params
+  })
+}
+
+export function datasourceDelete(id: string): any {
+  return axios({
+    url: '/datasource/' + id,
+    method: 'delete'
+  })
+}
+
+export function datasourceTypeList(params: {
+  showVirtualDataSource: boolean
+  source?: 'WS' | 'WT'
+}): any {
+  return axios({
+    url: '/datasource/support-datasources',
+    method: 'get',
+    params
+  })
+}
+
+export function datasourceDetail(id: string): any {
+  return axios({
+    url: '/datasource/' + id,
+    method: 'get'
+  })
+}
+
+export function datasourceAdd(data: any): any {
+  return axios({
+    url: '/datasource/create',
+    method: 'post',
+    data
+  })
+}
+
+export function datasourceUpdate(data: any, id: string): any {
+  return axios({
+    url: '/datasource/' + id,
+    method: 'put',
+    data
+  })
+}
+
+export function checkConnect(data: any): any {
+  return axios({
+    url: '/datasource/check/connect',
+    method: 'post',
+    data
+  })
+}
+
+export function dynamicFormItems(pluginName: string): any {
+  return axios({
+    url: '/datasource/dynamic-form',
+    method: 'get',
+    params: { pluginName }
+  })
+}
diff --git a/seatunnel-ui/src/locales/zh_CN/index.ts b/seatunnel-ui/src/service/datasource/types.ts
similarity index 60%
copy from seatunnel-ui/src/locales/zh_CN/index.ts
copy to seatunnel-ui/src/service/datasource/types.ts
index 04351711..a3af0f25 100644
--- a/seatunnel-ui/src/locales/zh_CN/index.ts
+++ b/seatunnel-ui/src/service/datasource/types.ts
@@ -15,24 +15,32 @@
  * limitations under the License.
  */
 
-import login from '@/locales/zh_CN/login'
-import menu from '@/locales/zh_CN/menu'
-import modal from '@/locales/zh_CN/modal'
-import user_manage from '@/locales/zh_CN/user-manage'
-import data_pipes from '@/locales/zh_CN/data-pipes'
-import log from '@/locales/zh_CN/log'
-import jobs from '@/locales/zh_CN/jobs'
-import tasks from '@/locales/zh_CN/tasks'
-import setting from '@/locales/zh_CN/setting'
+interface DatasourceConfig {}
 
-export default {
-  login,
-  menu,
-  modal,
-  user_manage,
-  data_pipes,
-  log,
-  jobs,
-  tasks,
-  setting
+interface DatasourceList {
+  createUserName: string
+  createTime: string
+  updateUserName: string
+  updateTime: string
+  id: string
+  datasourceName: string
+  pluginName: string
+  pluginVersion: string
+  description: string
+  datasourceConfig: DatasourceConfig
+  createUserId: number
+  updateUserId: number
+}
+
+interface DatasourceTypeList {
+  name: string
+  icon: string
+  version: string
+  type: number
+  supportVirtualTables: boolean
+}
+
+export {
+  DatasourceList,
+  DatasourceTypeList
 }
diff --git a/seatunnel-ui/src/locales/zh_CN/index.ts b/seatunnel-ui/src/store/datasource/index.ts
similarity index 59%
copy from seatunnel-ui/src/locales/zh_CN/index.ts
copy to seatunnel-ui/src/store/datasource/index.ts
index 04351711..70bd5a27 100644
--- a/seatunnel-ui/src/locales/zh_CN/index.ts
+++ b/seatunnel-ui/src/store/datasource/index.ts
@@ -15,24 +15,26 @@
  * limitations under the License.
  */
 
-import login from '@/locales/zh_CN/login'
-import menu from '@/locales/zh_CN/menu'
-import modal from '@/locales/zh_CN/modal'
-import user_manage from '@/locales/zh_CN/user-manage'
-import data_pipes from '@/locales/zh_CN/data-pipes'
-import log from '@/locales/zh_CN/log'
-import jobs from '@/locales/zh_CN/jobs'
-import tasks from '@/locales/zh_CN/tasks'
-import setting from '@/locales/zh_CN/setting'
+import { defineStore } from 'pinia'
+import { FormStructuresStore, StructureItem } from './types'
+export type { StructureItem }
 
-export default {
-  login,
-  menu,
-  modal,
-  user_manage,
-  data_pipes,
-  log,
-  jobs,
-  tasks,
-  setting
-}
+export const useFormStructuresStore = defineStore({
+  id: 'form-structures',
+  state: (): FormStructuresStore => ({
+    items: new Map()
+  }),
+  persist: {
+    storage: sessionStorage
+  },
+  getters: {
+    getItem(state) {
+      return (key: string): StructureItem[] | undefined => state.items.get(key)
+    }
+  },
+  actions: {
+    setItem(key: string, item: StructureItem[]): void {
+      this.items.set(key, item)
+    }
+  }
+})
diff --git a/seatunnel-ui/src/locales/zh_CN/menu.ts b/seatunnel-ui/src/store/datasource/types.ts
similarity index 74%
copy from seatunnel-ui/src/locales/zh_CN/menu.ts
copy to seatunnel-ui/src/store/datasource/types.ts
index e689b51e..5709e275 100644
--- a/seatunnel-ui/src/locales/zh_CN/menu.ts
+++ b/seatunnel-ui/src/store/datasource/types.ts
@@ -15,12 +15,16 @@
  * limitations under the License.
  */
 
-export default {
-  data_pipes: '数据管道',
-  jobs: '工作',
-  user_manage: '用户管理',
-  help: '帮助',
-  setting: '设置',
-  logout: '登出',
-  tasks: '任务'
+interface SourceType {}
+
+interface DatasourceTypeStore {
+  types: SourceType[]
 }
+
+type StructureItem = { [key: string]: any }
+
+interface FormStructuresStore {
+  items: Map<string, StructureItem[]>
+}
+
+export { DatasourceTypeStore, SourceType, FormStructuresStore, StructureItem }
diff --git a/seatunnel-ui/src/views/data-pipes/create/index.tsx b/seatunnel-ui/src/views/data-pipes/create/index.tsx
index 2699f08a..98ce5b12 100644
--- a/seatunnel-ui/src/views/data-pipes/create/index.tsx
+++ b/seatunnel-ui/src/views/data-pipes/create/index.tsx
@@ -82,7 +82,7 @@ const DataPipesCreate = defineComponent({
                 <NButton secondary onClick={this.handleClickDataPipes}>
                   {this.t('data_pipes.cancel')}
                 </NButton>
-                <NButton secondary type='primary' onClick={this.handleAdd}>
+                <NButton secondary type='success' onClick={this.handleAdd}>
                   {this.t('data_pipes.save')}
                 </NButton>
               </NSpace>
diff --git a/seatunnel-ui/src/views/data-pipes/list/index.tsx b/seatunnel-ui/src/views/data-pipes/list/index.tsx
index 53775995..81e68fa7 100644
--- a/seatunnel-ui/src/views/data-pipes/list/index.tsx
+++ b/seatunnel-ui/src/views/data-pipes/list/index.tsx
@@ -83,7 +83,7 @@ const DataPipesList = defineComponent({
         <NCard title={this.t('data_pipes.data_pipes')}>
           {{
             'header-extra': () => (
-              <NButton onClick={this.handleCreate} type='primary'>
+              <NButton onClick={this.handleCreate} type='success'>
                 {this.t('data_pipes.create')}
               </NButton>
             )
diff --git a/seatunnel-ui/src/locales/en_US/menu.ts b/seatunnel-ui/src/views/datasource/components/json-highlight.module.scss
similarity index 82%
copy from seatunnel-ui/src/locales/en_US/menu.ts
copy to seatunnel-ui/src/views/datasource/components/json-highlight.module.scss
index c5566870..a6f09f0f 100644
--- a/seatunnel-ui/src/locales/en_US/menu.ts
+++ b/seatunnel-ui/src/views/datasource/components/json-highlight.module.scss
@@ -15,12 +15,12 @@
  * limitations under the License.
  */
 
-export default {
-  data_pipes: 'Data Pipes',
-  jobs: 'Jobs',
-  user_manage: 'User Manage',
-  help: 'Help',
-  setting: 'Setting',
-  logout: 'Logout',
-  tasks: 'Tasks'
+.json-highlight {
+  display: block;
+  line-height: 1.5;
+  font-size: 12px;
+  padding: 5px;
+}
+.line {
+  padding-left: 8px;
 }
diff --git a/seatunnel-ui/src/views/datasource/components/json-highlight.tsx b/seatunnel-ui/src/views/datasource/components/json-highlight.tsx
new file mode 100644
index 00000000..873c4b84
--- /dev/null
+++ b/seatunnel-ui/src/views/datasource/components/json-highlight.tsx
@@ -0,0 +1,73 @@
+/*
+ * 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 { defineComponent, PropType } from 'vue'
+import { NText } from 'naive-ui'
+import { isBoolean, isNumber, isPlainObject } from 'lodash'
+import styles from './json-highlight.module.scss'
+
+const props = {
+  params: {
+    type: String as PropType<string>,
+    default: ''
+  }
+}
+
+const JsonHighlight = defineComponent({
+  name: 'JsonHighlight',
+  props,
+  render(props: { params: string }) {
+    return (
+      <pre class={styles['json-highlight']}>
+        {syntaxHighlight(props.params)}
+      </pre>
+    )
+  }
+})
+
+const syntaxHighlight = (json: string) => {
+  if (!isPlainObject(JSON.parse(json))) return ''
+  const lines = [<NText v-html={'{'}></NText>]
+  const entries = Object.entries(JSON.parse(json))
+  for (let i = 0, len = entries.length; i < len; i++) {
+    const [key, value] = entries[i]
+    let type = ''
+    if (isBoolean(value) || value === null) {
+      type = 'info'
+    } else if (isNumber(value)) {
+      type = 'warning'
+    } else {
+      type = 'success'
+    }
+    lines.push(
+      <NText tag='div' class={styles['line']}>
+        <NText type='error'>"{key}": </NText>
+        {isPlainObject(value) ? (
+          syntaxHighlight(JSON.stringify(value))
+        ) : (
+          <NText type={type}>
+            "{value}"{i !== len - 1 ? ',' : ''}
+          </NText>
+        )}
+      </NText>
+    )
+  }
+  lines.push(<NText v-html='}'></NText>)
+  return lines
+}
+
+export default JsonHighlight
diff --git a/seatunnel-ui/src/views/datasource/components/source-modal.tsx b/seatunnel-ui/src/views/datasource/components/source-modal.tsx
new file mode 100644
index 00000000..c0244524
--- /dev/null
+++ b/seatunnel-ui/src/views/datasource/components/source-modal.tsx
@@ -0,0 +1,90 @@
+/*
+ * 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 { defineComponent, PropType } from 'vue'
+import {
+  NSpace,
+  NModal,
+  NCard,
+  NButton,
+  NTabs,
+  NTabPane,
+  NEmpty
+} from 'naive-ui'
+import { useI18n } from 'vue-i18n'
+import { useSource } from './use-source'
+import styles from './source-model.module.scss'
+import type { SelectOption } from 'naive-ui'
+
+const props = {
+  show: {
+    type: Boolean as PropType<boolean>,
+    default: false
+  },
+  id: {
+    type: Number as PropType<number>
+  }
+}
+
+const SourceModal = defineComponent({
+  name: 'SourceModal',
+  props,
+  emits: ['change', 'cancel'],
+  setup(props, ctx) {
+    const { t } = useI18n()
+    const { state } = useSource(false)
+    const handleTypeSelect = (type: string) => {
+      ctx.emit('change', type)
+    }
+    const onCancel = () => {
+      ctx.emit('cancel')
+    }
+
+    return () => (
+      <NModal show={props.show} onMaskClick={onCancel} onEsc={onCancel}>
+        <NCard
+          class={styles.content}
+          title={t('datasource.choose_datasource_type')}
+        >
+          <NTabs>
+            {state.types.map((item) => (
+              <NTabPane name={item.key} tab={item.label} key={item.key}>
+                <div class={styles['types']}>
+                  {item?.children.map((slip: SelectOption) => (
+                    <div
+                      class={styles.itemBox}
+                      onClick={() => handleTypeSelect(slip.label as string)}
+                    >
+                      <div class='font-bold'>{slip.label}</div>
+                      <div class='text-xs mt-1.5'>{item.label}</div>
+                    </div>
+                  ))}
+                </div>
+                {item.children.length === 0 && <NEmpty />}
+              </NTabPane>
+            ))}
+          </NTabs>
+          <NSpace justify='end'>
+            <NButton onClick={onCancel}>{t('datasource.cancel')}</NButton>
+          </NSpace>
+        </NCard>
+      </NModal>
+    )
+  }
+})
+
+export default SourceModal
diff --git a/seatunnel-ui/src/locales/zh_CN/menu.ts b/seatunnel-ui/src/views/datasource/components/source-model.module.scss
similarity index 74%
copy from seatunnel-ui/src/locales/zh_CN/menu.ts
copy to seatunnel-ui/src/views/datasource/components/source-model.module.scss
index e689b51e..037d8fb9 100644
--- a/seatunnel-ui/src/locales/zh_CN/menu.ts
+++ b/seatunnel-ui/src/views/datasource/components/source-model.module.scss
@@ -15,12 +15,21 @@
  * limitations under the License.
  */
 
-export default {
-  data_pipes: '数据管道',
-  jobs: '工作',
-  user_manage: '用户管理',
-  help: '帮助',
-  setting: '设置',
-  logout: '登出',
-  tasks: '任务'
+.content {
+  width: 800px;
+
+  .types {
+    display: flex;
+    flex-wrap: wrap;
+  }
+
+  .itemBox {
+    margin: 10px;
+    border-radius: 10px;
+    width: 110px;
+    padding: 30px 10px;
+    text-align: center;
+    cursor: pointer;
+    box-shadow: 0 0 6px 1px rgba(0, 0, 0, 0.1);
+  }
 }
diff --git a/seatunnel-ui/src/views/datasource/components/use-source.ts b/seatunnel-ui/src/views/datasource/components/use-source.ts
new file mode 100644
index 00000000..8f85064b
--- /dev/null
+++ b/seatunnel-ui/src/views/datasource/components/use-source.ts
@@ -0,0 +1,85 @@
+/*
+ * 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 { onMounted, reactive, watch } from 'vue'
+import { datasourceTypeList } from '@/service/datasource'
+import { useI18n } from 'vue-i18n'
+import type { SelectOption } from 'naive-ui'
+import type { ResponseBasic } from '@/service/types'
+import type { DatasourceTypeList } from '@/service/datasource/types'
+
+type Key = '1' | '2' | '3' | '4' | '5'
+type IType = {
+  type: string
+  label: string
+  key: string
+  children: SelectOption[]
+}
+
+export const useSource = (showVirtualDataSource = false) => {
+  const i18n = useI18n()
+  const TYPE_MAP = {
+    1: 'database',
+    2: 'file',
+    3: 'no_structured',
+    4: 'storage',
+    5: 'remote_connection'
+  }
+  const state = reactive({
+    types: [] as IType[]
+  })
+
+  const querySource = () => {
+    datasourceTypeList({
+      showVirtualDataSource,
+      source: 'WT'
+    }).then((res: ResponseBasic<Array<DatasourceTypeList> | Array<any>>) => {
+      const locales = {
+        zh_CN: {} as { [key: string]: string },
+        en_US: {} as { [key: string]: string }
+      }
+
+      state.types = Object.entries(res.data).map(([key, value]) => {
+        return {
+          type: 'group',
+          label: i18n.t(`datasource.${TYPE_MAP[key as Key]}`),
+          key: TYPE_MAP[key as Key],
+          children: (value as any).map((item: any) => {
+            locales.zh_CN[item.name] = item.chineseName
+            locales.en_US[item.name] = item.name
+            return {
+              label: item.name,
+              value: item.name
+            }
+          })
+        }
+      })
+
+      i18n.mergeLocaleMessage('zh_CN', locales.zh_CN)
+      i18n.mergeLocaleMessage('en_US', locales.en_US)
+    })
+  }
+
+  onMounted(() => {
+    querySource()
+  })
+
+  watch(useI18n().locale, () => {
+    querySource()
+  })
+
+  return { state }
+}
diff --git a/seatunnel-ui/src/views/datasource/create/index.tsx b/seatunnel-ui/src/views/datasource/create/index.tsx
new file mode 100644
index 00000000..5f932126
--- /dev/null
+++ b/seatunnel-ui/src/views/datasource/create/index.tsx
@@ -0,0 +1,184 @@
+/*
+ * 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 { defineComponent, ref } from 'vue'
+import {
+  NSpace,
+  NBreadcrumb,
+  NBreadcrumbItem,
+  NForm,
+  useDialog,
+  NInput,
+  NButton,
+  NFormItemGi,
+  NGrid,
+  NDivider,
+  NCard
+} from 'naive-ui'
+import { DynamicFormItem } from '@/components/dynamic-form/dynamic-form-item'
+import { useRoute, useRouter } from 'vue-router'
+import { useI18n } from 'vue-i18n'
+import { useDetail } from './use-detail'
+import { useForm } from './use-form'
+import styles from '../index.module.scss'
+import SourceModal from '../components/source-modal'
+
+const DatasourceCreate = defineComponent({
+  setup() {
+    const { t } = useI18n()
+    const route = useRoute()
+    const router = useRouter()
+    const dialog = useDialog()
+    const showSourceModal = ref(false)
+    const detailFormRef = ref(null)
+
+    const { state, changeType, getFieldsValue, setFieldsValue, getFormItems } =
+      useForm(route.query.type as string)
+
+    const { status, testConnect, createOrUpdate } = useDetail(
+      getFieldsValue,
+      setFieldsValue,
+      getFormItems,
+      detailFormRef,
+      route.params.id as string
+    )
+
+    const onClose = () => {
+      dialog.warning({
+        title: t('datasource.warning'),
+        content: t('datasource.close_confirm_tips'),
+        onPositiveClick: () => {
+          router.push({
+            name: 'datasource-list'
+          })
+        },
+        positiveText: t('datasource.confirm'),
+        negativeText: t('datasource.cancel')
+      })
+    }
+
+    return () => (
+      <NSpace vertical>
+        <NCard>
+          {{
+            header: () => <NBreadcrumb>
+              <NBreadcrumbItem onClick={onClose}>
+                {t('datasource.datasource')}
+              </NBreadcrumbItem>
+              <NBreadcrumbItem>
+                {t(
+                  route.params.id
+                    ? 'datasource.edit_datasource'
+                    : 'datasource.create_datasource'
+                )}
+              </NBreadcrumbItem>
+            </NBreadcrumb>,
+            'header-extra': () => <NSpace>
+              <NButton secondary type='primary' onClick={testConnect} loading={status.testing}>
+                {t('datasource.test_connect')}
+              </NButton>
+              <NButton secondary onClick={onClose}>
+                {t('datasource.cancel')}
+              </NButton>
+              <NButton type='success' onClick={createOrUpdate} loading={status.saving}>
+                {t('datasource.confirm')}
+              </NButton>
+            </NSpace>
+          }}
+        </NCard>
+        <NCard>
+          <NForm
+            rules={state.rules}
+            ref={detailFormRef}
+            class={styles['detail-content']}
+          >
+            <NGrid xGap={10}>
+              <NFormItemGi
+                label={t('datasource.datasource_type')}
+                path='type'
+                show-require-mark
+                span={24}
+              >
+                <NSpace
+                  class={[
+                    styles.typeBox,
+                    !!route.params.id && styles.disabledBox
+                  ]}
+                >
+                  <div>{state.detailForm.pluginName}</div>
+                  {!route.params.id && (
+                    <NButton
+                      text
+                      type='primary'
+                      onClick={() => void (showSourceModal.value = true)}
+                    >
+                      {t('datasource.select')}
+                    </NButton>
+                  )}
+                </NSpace>
+              </NFormItemGi>
+              <NFormItemGi
+                label={t('datasource.datasource_name')}
+                path='name'
+                show-require-mark
+                span={12}
+              >
+                <NInput
+                  class='input-data-source-name'
+                  v-model={[state.detailForm.datasourceName, 'value']}
+                  maxlength={60}
+                  placeholder={t('datasource.datasource_name_tips')}
+                />
+              </NFormItemGi>
+              <NFormItemGi
+                label={t('datasource.description')}
+                path='note'
+                span={12}
+              >
+                <NInput
+                  class='input-data-source-description'
+                  v-model={[state.detailForm.description, 'value']}
+                  type='textarea'
+                  placeholder={t('datasource.description_tips')}
+                  rows={1}
+                />
+              </NFormItemGi>
+            </NGrid>
+            <NDivider style={{ marginTop: '0px' }} />
+            {state.formStructure.length > 0 && (
+              <DynamicFormItem
+                model={state.detailForm}
+                formStructure={state.formStructure}
+                name={state.formName}
+                locales={state.locales}
+              />
+            )}
+          </NForm>
+        </NCard>
+        <SourceModal
+          show={showSourceModal.value}
+          onChange={(type) => {
+            changeType(type)
+            showSourceModal.value = false
+          }}
+          onCancel={() => void (showSourceModal.value = false)}
+        />
+      </NSpace>
+    )
+  }
+})
+
+export default DatasourceCreate
diff --git a/seatunnel-ui/src/views/datasource/create/use-detail.ts b/seatunnel-ui/src/views/datasource/create/use-detail.ts
new file mode 100644
index 00000000..f18c1e0d
--- /dev/null
+++ b/seatunnel-ui/src/views/datasource/create/use-detail.ts
@@ -0,0 +1,119 @@
+/*
+ * 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 { onMounted, reactive, Ref } from 'vue'
+import {
+  datasourceDetail,
+  datasourceAdd,
+  datasourceUpdate,
+  checkConnect
+} from '@/service/datasource'
+import { useI18n } from 'vue-i18n'
+import { omit } from 'lodash'
+import { useRouter } from 'vue-router'
+
+export function useDetail(
+  getFieldsValue: Function,
+  setFieldsValue: Function,
+  getFormItems: Function,
+  detailFormRef: Ref,
+  id: string
+) {
+  const { t } = useI18n()
+  const router = useRouter()
+  const status = reactive({
+    saving: false,
+    testing: false,
+    loading: false
+  })
+
+  const formatParams = () => {
+    const values = getFieldsValue()
+    return {
+      datasourceName: values.datasourceName,
+      pluginName: values.pluginName,
+      description: values.description,
+      datasourceConfig: JSON.stringify(
+        omit(values, ['pluginName', 'datasourceName', 'description'])
+      )
+    }
+  }
+
+  const queryById = async () => {
+    try {
+      const result = await datasourceDetail(id)
+      await getFormItems(result.data.pluginName)
+      setFieldsValue({
+        datasourceName: result.data.datasourceName,
+        pluginName: result.data.pluginName,
+        description: result.data.description,
+        ...result.data.datasourceConfig
+      })
+    } finally {}
+  }
+
+  const testConnect = async () => {
+    await detailFormRef.value.validate()
+    if (status.testing) return
+    status.testing = true
+    const values = getFieldsValue()
+    try {
+      const result = await checkConnect({
+        pluginName: values.pluginName,
+        datasourceConfig: omit(values, ['pluginName', 'datasourceName', 'description'])
+      })
+      window.$message.success(
+        result.msg ? result.msg : `${t('datasource.test_connect_success')}`
+      )
+
+      status.testing = false
+    } catch (err) {
+      status.testing = false
+    }
+  }
+
+  const createOrUpdate = async () => {
+    await detailFormRef.value.validate()
+
+    if (status.saving) return false
+    status.saving = true
+
+    try {
+      id
+        ? await datasourceUpdate(formatParams(), id)
+        : await datasourceAdd(formatParams())
+
+      status.saving = false
+      router.push({
+        name: 'datasource-list',
+        query: {}
+      })
+      return true
+    } catch (err) {
+      status.saving = false
+      return false
+    }
+  }
+
+  onMounted(() => {
+    if (id) {
+      queryById()
+    }
+  })
+
+  return { status, testConnect, createOrUpdate }
+}
diff --git a/seatunnel-ui/src/views/datasource/create/use-form.ts b/seatunnel-ui/src/views/datasource/create/use-form.ts
new file mode 100644
index 00000000..5597800e
--- /dev/null
+++ b/seatunnel-ui/src/views/datasource/create/use-form.ts
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { onMounted, reactive } from 'vue'
+import { useI18n } from 'vue-i18n'
+import { useRouter } from 'vue-router'
+import {
+  useFormStructuresStore,
+  StructureItem
+} from '@/store/datasource'
+import { dynamicFormItems } from '@/service/datasource'
+import { useFormField } from '@/components/dynamic-form/use-form-field'
+import { useFormRequest } from '@/components/dynamic-form/use-form-request'
+import { useFormValidate } from '@/components/dynamic-form/use-form-validate'
+import { useFormStructure } from '@/components/dynamic-form/use-form-structure'
+import type { FormRules } from 'naive-ui'
+import type { ResponseBasic } from '@/service/types'
+
+export function useForm(type: string) {
+  const { t } = useI18n()
+  const router = useRouter()
+  const formStructuresStore = useFormStructuresStore()
+
+  const initialValues = {
+    pluginName: type,
+    datasourceName: '',
+    description: ''
+  }
+
+  const state = reactive({
+    detailForm: { ...initialValues },
+    formName: '',
+    formStructure: [] as StructureItem[],
+    locales: {},
+    rules: {
+      name: {
+        trigger: ['input'],
+        validator() {
+          if (!state.detailForm.datasourceName) {
+            return new Error(t('datasource.datasource_name_tips'))
+          }
+        }
+      }
+    } as FormRules
+  })
+
+  const getFormItems = async (value: string) => {
+    if (formStructuresStore.getItem(value)) {
+      state.formStructure = formStructuresStore.getItem(value) as StructureItem[]
+      return
+    }
+
+    const result: ResponseBasic<string> = await dynamicFormItems(value)
+
+    try {
+      const res = JSON.parse(result.data)
+      res.forms = res.forms.map((form: any) => ({ ...form, span: 12 }))
+      Object.assign(state.detailForm, useFormField(res.forms))
+      Object.assign(
+        state.rules,
+        useFormValidate(res.forms, state.detailForm, t)
+      )
+      state.locales = res.locales
+      state.formStructure = useFormStructure(
+        res.apis ? useFormRequest(res.apis, res.forms) : res.forms
+      ) as any
+    } finally {}
+  }
+
+  const changeType = (value: string) => {
+    router.replace({ name: 'datasource-create', query: { type: value } })
+    getFormItems(value)
+  }
+
+  const resetFieldsValue = () => {
+    state.detailForm = { ...initialValues }
+  }
+
+  const setFieldsValue = (values: any) => {
+    Object.assign(state.detailForm, values)
+  }
+
+  const getFieldsValue = () => state.detailForm
+
+  onMounted(() => {
+    if (type) {
+      getFormItems(type)
+    }
+  })
+
+  return {
+    state,
+    changeType,
+    resetFieldsValue,
+    getFieldsValue,
+    setFieldsValue,
+    getFormItems
+  }
+}
diff --git a/seatunnel-ui/src/locales/zh_CN/menu.ts b/seatunnel-ui/src/views/datasource/index.module.scss
similarity index 67%
copy from seatunnel-ui/src/locales/zh_CN/menu.ts
copy to seatunnel-ui/src/views/datasource/index.module.scss
index e689b51e..002c30f0 100644
--- a/seatunnel-ui/src/locales/zh_CN/menu.ts
+++ b/seatunnel-ui/src/views/datasource/index.module.scss
@@ -15,12 +15,36 @@
  * limitations under the License.
  */
 
-export default {
-  data_pipes: '数据管道',
-  jobs: '工作',
-  user_manage: '用户管理',
-  help: '帮助',
-  setting: '设置',
-  logout: '登出',
-  tasks: '任务'
+.conditions {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.pagination {
+  margin-top: 20px;
+  justify-content: center;
+}
+
+.mt-8 {
+  margin-top: 8px;
+}
+
+.typeBox {
+  display: flex;
+  padding: 5px 14px;
+
+  .text-color {
+    color: #1890ff;
+    cursor: pointer;
+  }
+}
+
+.disabledBox {
+  pointer-events: none;
+  opacity: 0.5;
+}
+
+.detail-content {
+  padding-top: 20px;
 }
diff --git a/seatunnel-ui/src/views/datasource/list/index.tsx b/seatunnel-ui/src/views/datasource/list/index.tsx
new file mode 100644
index 00000000..70232dd3
--- /dev/null
+++ b/seatunnel-ui/src/views/datasource/list/index.tsx
@@ -0,0 +1,173 @@
+/*
+ * 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 { defineComponent, onMounted, ref, toRefs, watch } from 'vue'
+import {
+  NButton,
+  NInput,
+  NIcon,
+  NDataTable,
+  NPagination,
+  NSpace,
+  NCard
+} from 'naive-ui'
+import { SearchOutlined, ReloadOutlined } from '@vicons/antd'
+import { useRouter, useRoute } from 'vue-router'
+import { useI18n } from 'vue-i18n'
+import { useColumns } from './use-columns'
+import { useTable } from './use-table'
+import styles from '../index.module.scss'
+import SourceModal from '../components/source-modal'
+import type { Ref } from 'vue'
+import type { TableColumns } from 'naive-ui/es/data-table/src/interface'
+
+const DatasourceList = defineComponent({
+  setup: function() {
+    const { t } = useI18n()
+    const showSourceModal = ref(false)
+    const columns: Ref<TableColumns> = ref([])
+    const router = useRouter()
+    const route = useRoute()
+    const { data, changePage, changePageSize, deleteRecord, updateList } =
+      useTable()
+
+    const handleSearch = () => {
+      updateList()
+    }
+
+    const { getColumns } = useColumns((id: string, type: 'edit' | 'delete') => {
+      if (type === 'edit') {
+        router.push({ name: 'datasource-edit', params: { id } })
+      } else {
+        deleteRecord(id)
+      }
+    })
+
+    const onCreate = () => {
+      showSourceModal.value = true
+    }
+
+    const closeSourceModal = () => {
+      showSourceModal.value = false
+    }
+
+    const handleSelectSourceType = (value: string) => {
+      router.push({ name: 'datasource-create', query: { type: value } })
+      closeSourceModal()
+    }
+
+    const initSearch = () => {
+      const { searchVal } = route.query
+      if (searchVal) {
+        data.searchVal = searchVal as string
+      }
+    }
+
+    onMounted(() => {
+      initSearch()
+      if (!route.query.tab || route.query.tab === 'datasource') {
+        changePage(1)
+        columns.value = getColumns()
+      }
+    })
+
+    watch(useI18n().locale, () => {
+      columns.value = getColumns()
+    })
+
+    return {
+      t,
+      showSourceModal,
+      columns,
+      ...toRefs(data),
+      changePage,
+      changePageSize,
+      onCreate,
+      handleSearch,
+      handleSelectSourceType,
+      closeSourceModal
+    }
+  },
+  render() {
+    const {
+      t,
+      showSourceModal,
+      columns,
+      list,
+      page,
+      pageSize,
+      itemCount,
+      changePage,
+      changePageSize,
+      onCreate,
+      handleSelectSourceType,
+      closeSourceModal
+    } = this
+
+    return (
+      <NSpace vertical>
+        <NCard title={t('datasource.datasource')}>
+          {{
+            'header-extra': () => (
+              <NSpace>
+                <NInput
+                  v-model={[this.searchVal, 'value']}
+                  placeholder={t('datasource.search_input_tips')}
+                  style={{ width: '200px' }}
+                />
+                <NButton onClick={this.handleSearch} type='primary'>
+                  {this.t('datasource.search')}
+                </NButton>
+                <NButton
+                  onClick={onCreate}
+                  type='success'
+                >
+                  {t('datasource.create')}
+                </NButton>
+              </NSpace>
+            )
+          }}
+        </NCard>
+        <NCard title='' class={styles['mt-8']}>
+          <NDataTable
+            row-class-name='data-source-items'
+            columns={columns}
+            data={list}
+            striped
+          />
+          <NPagination
+            page={page}
+            page-size={pageSize}
+            item-count={itemCount}
+            show-quick-jumper
+            show-size-picker
+            page-sizes={[10, 30, 50]}
+            class={styles['pagination']}
+            on-update:page={changePage}
+            on-update:page-size={changePageSize}
+          />
+        </NCard>
+        <SourceModal
+          show={showSourceModal}
+          onChange={handleSelectSourceType}
+          onCancel={closeSourceModal}
+        />
+      </NSpace>
+    )
+  }
+})
+export default DatasourceList
diff --git a/seatunnel-ui/src/views/datasource/list/use-columns.ts b/seatunnel-ui/src/views/datasource/list/use-columns.ts
new file mode 100644
index 00000000..a46df1c4
--- /dev/null
+++ b/seatunnel-ui/src/views/datasource/list/use-columns.ts
@@ -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 { h } from 'vue'
+import { useI18n } from 'vue-i18n'
+import { NPopover, NButton, NSpace } from 'naive-ui'
+import JsonHighlight from '../components/json-highlight'
+
+export function useColumns(onCallback: Function) {
+  const { t } = useI18n()
+  const getColumns = () => {
+    return [
+      {
+        title: t('datasource.datasource_name'),
+        key: 'datasourceName'
+      },
+      {
+        title: t('datasource.datasource_user_name'),
+        key: 'createUserName'
+      },
+      {
+        title: t('datasource.datasource_type'),
+        key: 'pluginName',
+        width: 180
+      },
+      {
+        title: t('datasource.datasource_parameter'),
+        key: 'parameter',
+        width: 180,
+        render: (row: any) => {
+          return row.datasourceConfig
+            ? h(
+                NPopover,
+                { trigger: 'click' },
+                {
+                  trigger: () =>
+                    h(NButton, { text: true }, {
+                      default: () => t('datasource.click_to_view')
+                    }),
+                  default: () =>
+                    h(JsonHighlight, {
+                      params: JSON.stringify(
+                        row.datasourceConfig
+                      ) as string
+                    })
+                }
+              )
+            : '--'
+        }
+      },
+      {
+        title: t('datasource.description'),
+        key: 'description',
+        render: (row: any) => row.description || '-'
+      },
+      {
+        title: t('datasource.create_time'),
+        key: 'createTime',
+      },
+      {
+        title: t('datasource.update_time'),
+        key: 'updateTime',
+      },
+      {
+        title: t('data_pipes.operation'),
+        key: 'operation',
+        render: (row: any) =>
+          h(NSpace, null, {
+            default: () => [
+              h(
+                NButton,
+                {
+                  text: true,
+                  onClick: () => void onCallback(row.id, 'edit')
+                },
+                {
+                  default: () => t('datasource.edit')
+                }
+              ),
+              h(
+                NButton,
+                {
+                  text: true,
+                  onClick: () => void onCallback(row.id, 'delete')
+                },
+                { default: () => t('datasource.delete') }
+              )
+            ]
+          })
+      }
+    ]
+  }
+
+  return {
+    getColumns
+  }
+}
diff --git a/seatunnel-ui/src/views/datasource/list/use-table.ts b/seatunnel-ui/src/views/datasource/list/use-table.ts
new file mode 100644
index 00000000..ba609eb2
--- /dev/null
+++ b/seatunnel-ui/src/views/datasource/list/use-table.ts
@@ -0,0 +1,71 @@
+/*
+ * 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 { reactive } from 'vue'
+import {
+  datasourceList,
+  datasourceDelete
+} from '@/service/datasource'
+import type { DatasourceList } from '@/service/datasource/types'
+import type { ResponseTable } from '@/service/types'
+
+export function useTable() {
+  const data = reactive({
+    page: 1,
+    pageSize: 10,
+    itemCount: 0,
+    searchVal: '',
+    list: []
+  })
+
+  const getList = () => {
+    datasourceList({
+      pageNo: data.page,
+      pageSize: data.pageSize,
+      searchVal: data.searchVal,
+      pluginName: ''
+    }).then((res: ResponseTable<Array<DatasourceList> | []>) => {
+      data.list = res.data.data as any
+      data.itemCount = res.data.totalCount
+    })
+  }
+
+  const updateList = () => {
+    if (data.list.length === 1 && data.page > 1) {
+      --data.page
+    }
+    getList()
+  }
+
+  const deleteRecord = async (id: string) => {
+    await datasourceDelete(id)
+    updateList()
+  }
+
+  const changePage = (page: number) => {
+    data.page = page
+    getList()
+  }
+
+  const changePageSize = (pageSize: number) => {
+    data.page = 1
+    data.pageSize = pageSize
+    getList()
+  }
+
+  return { data, getList, changePage, changePageSize, deleteRecord, updateList }
+}
diff --git a/seatunnel-ui/src/views/user-manage/list/index.tsx b/seatunnel-ui/src/views/user-manage/list/index.tsx
index 11a7d9fa..11a9cbe6 100644
--- a/seatunnel-ui/src/views/user-manage/list/index.tsx
+++ b/seatunnel-ui/src/views/user-manage/list/index.tsx
@@ -82,7 +82,7 @@ const UserManageList = defineComponent({
         <NCard title={this.t('user_manage.user_manage')}>
           {{
             'header-extra': () => (
-              <NButton onClick={this.handleFormModal} type='primary'>
+              <NButton onClick={this.handleFormModal} type='success'>
                 {this.t('user_manage.create')}
               </NButton>
             )