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

nicholasjiang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-paimon-webui.git


The following commit(s) were added to refs/heads/main by this push:
     new ee45ba6  [Feature] Introduce CDC module (#93)
ee45ba6 is described below

commit ee45ba6f94ff7f09e502121828d6dc2fe4bbdc7a
Author: labbomb <739955...@qq.com>
AuthorDate: Tue Nov 7 13:08:55 2023 +0800

    [Feature] Introduce CDC module (#93)
---
 .../src/components/dynamic-form/types.ts           |   2 +-
 paimon-web-ui-new/src/components/modal/index.tsx   |   6 +-
 .../src/form-lib/{source => cdc}/use-cdc-list.ts   |   3 +-
 paimon-web-ui-new/src/form-lib/cdc/use-mysql.ts    | 186 +++++++++++++++++++++
 paimon-web-ui-new/src/form-lib/cdc/use-paimon.ts   | 155 +++++++++++++++++
 paimon-web-ui-new/src/form-lib/index.ts            |   8 +-
 paimon-web-ui-new/src/locales/en/modules/cdc.ts    |  15 ++
 paimon-web-ui-new/src/locales/zh/modules/cdc.ts    |  15 ++
 .../src/{form-lib => store/cdc}/index.ts           |  24 ++-
 .../dag/{dag-canvas.tsx => context-menu.tsx}       |  40 +++--
 .../src/views/cdc/components/dag/custom-node.tsx   |   4 +-
 .../src/views/cdc/components/dag/dag-canvas.tsx    |  77 ++++++++-
 .../src/views/cdc/components/dag/dag-slider.tsx    |  10 +-
 .../src/views/cdc/components/dag/drawer.tsx        | 176 +++++++++++++++++++
 .../src/views/cdc/components/dag/index.module.scss |  13 +-
 .../src/views/cdc/components/dag/index.tsx         |  39 ++++-
 .../views/cdc/components/dag/use-canvas-init.ts    |  50 ++++--
 .../src/views/cdc/components/list/index.tsx        | 169 ++++++++++++++++++-
 paimon-web-ui-new/src/views/cdc/index.tsx          |  31 +++-
 19 files changed, 957 insertions(+), 66 deletions(-)

diff --git a/paimon-web-ui-new/src/components/dynamic-form/types.ts 
b/paimon-web-ui-new/src/components/dynamic-form/types.ts
index 6ee0c6a..56cd0e9 100644
--- a/paimon-web-ui-new/src/components/dynamic-form/types.ts
+++ b/paimon-web-ui-new/src/components/dynamic-form/types.ts
@@ -40,7 +40,7 @@ interface IJsonItemParams {
   value?: any
   props?: any
   options?: IOption[] | Ref<IOption[]>
-  span?: number
+  span?: number | Ref<number>
   children?: IJsonItem[]
   validate?: IFormItemRule
   slots?: object
diff --git a/paimon-web-ui-new/src/components/modal/index.tsx 
b/paimon-web-ui-new/src/components/modal/index.tsx
index 211c05f..e65d17f 100644
--- a/paimon-web-ui-new/src/components/modal/index.tsx
+++ b/paimon-web-ui-new/src/components/modal/index.tsx
@@ -52,7 +52,7 @@ export default defineComponent({
   setup(props, { expose, emit }) {
     const { t } = useLocaleHooks()
     const formRef = ref()
-    expose({formRef})
+    expose({ formRef })
 
     const { elementsRef, rulesRef, model } = useTask({
       data: props.row,
@@ -60,7 +60,7 @@ export default defineComponent({
     })
 
     const handleConfirm = () => {
-      emit('confirm')
+      emit('confirm', model)
     }
 
     const handleCancel = () => {
@@ -94,7 +94,7 @@ export default defineComponent({
           {{
             default: () => (
               <Form
-                ref={this.formRef}
+                ref='formRef'
                 meta={{
                   model: this.model,
                   rules: this.rulesRef,
diff --git a/paimon-web-ui-new/src/form-lib/source/use-cdc-list.ts 
b/paimon-web-ui-new/src/form-lib/cdc/use-cdc-list.ts
similarity index 97%
rename from paimon-web-ui-new/src/form-lib/source/use-cdc-list.ts
rename to paimon-web-ui-new/src/form-lib/cdc/use-cdc-list.ts
index 6129a2c..59abe7d 100644
--- a/paimon-web-ui-new/src/form-lib/source/use-cdc-list.ts
+++ b/paimon-web-ui-new/src/form-lib/cdc/use-cdc-list.ts
@@ -69,7 +69,8 @@ export function useCDCList(item:any) {
                                type: 'radio',
                                field: 'synchronizationType',
                                name: t('cdc.synchronization_type'),
-                               options: synchronizationTypeOptions
+                               options: synchronizationTypeOptions,
+                               value: 0,
                        },
                ] as IJsonItem[], model
        }
diff --git a/paimon-web-ui-new/src/form-lib/cdc/use-mysql.ts 
b/paimon-web-ui-new/src/form-lib/cdc/use-mysql.ts
new file mode 100644
index 0000000..d47eab5
--- /dev/null
+++ b/paimon-web-ui-new/src/form-lib/cdc/use-mysql.ts
@@ -0,0 +1,186 @@
+/* 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 type { IJsonItem } from "@/components/dynamic-form/types"
+
+export function useMYSQL(item:any) {
+  const { t } = useLocaleHooks()
+  
+  const tabType = item.data.tabType
+  console.log('item', item.data)
+       const data = item.data
+
+       const model = reactive({
+               host: data.host || '',
+    port: data.port || '',
+    username: data.username || '',
+    password: data.password || '',
+    other_configs: data.other_configs || '',
+    database: data.database || '',
+    table_name: data.table_name || '',
+    type_mapping: data.type_mapping || '',
+    metadata_column: data.metadata_column || '',
+    computed_column: data.computed_column || '',
+       })
+
+  const TypeMappingOptions = [] as any
+
+       return {
+               json: [
+                       {
+                               type: 'input',
+                               field: 'host',
+                               name: t('cdc.host_name_and_ip_address'),
+                               props: {
+                                       placeholder: ''
+                               },
+        span: computed(() => tabType.value === 'connection_information' ? 24 : 
0),
+                               validate: {
+                                       trigger: ['input', 'blur'],
+                                       required: true,
+                                       message: 'error',
+                                       validator: (validator: any, value: 
string) => {
+                                               if (!value) {
+                                                       return new 
Error('error')
+                                               }
+                                       }
+                               }
+                       },
+                       {
+                               type: 'input',
+                               field: 'port',
+                               name: t('cdc.port'),
+                               props: {
+                                       placeholder: ''
+                               },
+        span: computed(() => tabType.value === 'connection_information' ? 24 : 
0),
+                       },
+                       {
+                               type: 'input',
+                               field: 'username',
+                               name: t('cdc.username'),
+                               props: {
+                                       placeholder: ''
+                               },
+        span: computed(() => tabType.value === 'connection_information' ? 24 : 
0),
+                               validate: {
+                                       trigger: ['input', 'blur'],
+                                       required: true,
+                                       message: 'error',
+                                       validator: (validator: any, value: 
string) => {
+                                               if (!value) {
+                                                       return new 
Error('error')
+                                               }
+                                       }
+                               }
+                       },
+      {
+                               type: 'input',
+                               field: 'password',
+                               name: t('cdc.password'),
+                               props: {
+                                       placeholder: ''
+                               },
+        span: computed(() => tabType.value === 'connection_information' ? 24 : 
0),
+                               validate: {
+                                       trigger: ['input', 'blur'],
+                                       required: true,
+                                       message: 'error',
+                                       validator: (validator: any, value: 
string) => {
+                                               if (!value) {
+                                                       return new 
Error('error')
+                                               }
+                                       }
+                               }
+                       },
+      {
+                               type: 'input',
+                               field: 'other_configs',
+                               name: t('cdc.other_configs'),
+        span: computed(() => tabType.value === 'connection_information' ? 24 : 
0),
+                               props: {
+                                       placeholder: '',
+          type: 'textarea',
+                               }
+                       },
+      {
+                               type: 'input',
+                               field: 'database',
+                               name: t('cdc.database'),
+                               props: {
+                                       placeholder: ''
+                               },
+        span: computed(() => tabType.value === 'synchronization_configuration' 
? 24 : 0),
+                               validate: {
+                                       trigger: ['input', 'blur'],
+                                       required: true,
+                                       message: 'error',
+                                       validator: (validator: any, value: 
string) => {
+                                               if (!value) {
+                                                       return new 
Error('error')
+                                               }
+                                       }
+                               }
+                       },
+      {
+                               type: 'input',
+                               field: 'table_name',
+                               name: t('cdc.table_name'),
+                               props: {
+                                       placeholder: ''
+                               },
+        span: computed(() => tabType.value === 'synchronization_configuration' 
? 24 : 0),
+                               validate: {
+                                       trigger: ['input', 'blur'],
+                                       required: true,
+                                       message: 'error',
+                                       validator: (validator: any, value: 
string) => {
+                                               if (!value) {
+                                                       return new 
Error('error')
+                                               }
+                                       }
+                               }
+                       },
+      {
+        type: 'select',
+        field: 'type_mapping',
+        name: t('cdc.type_mapping'),
+        options: TypeMappingOptions,
+        span: computed(() => tabType.value === 'synchronization_configuration' 
? 24 : 0),
+      },
+      {
+                               type: 'input',
+                               field: 'metadata_column',
+                               name: t('cdc.metadata_column'),
+        span: computed(() => tabType.value === 'synchronization_configuration' 
? 24 : 0),
+                               props: {
+                                       placeholder: ''
+                               }
+                       },
+      {
+                               type: 'input',
+                               field: 'computed_column',
+                               name: t('cdc.computed_column'),
+        span: computed(() => tabType.value === 'synchronization_configuration' 
? 24 : 0),
+                               props: {
+                                       placeholder: '',
+          type: 'textarea',
+                               }
+                       },
+               ] as IJsonItem[], model
+       }
+}
diff --git a/paimon-web-ui-new/src/form-lib/cdc/use-paimon.ts 
b/paimon-web-ui-new/src/form-lib/cdc/use-paimon.ts
new file mode 100644
index 0000000..10e0daf
--- /dev/null
+++ b/paimon-web-ui-new/src/form-lib/cdc/use-paimon.ts
@@ -0,0 +1,155 @@
+/* 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 type { IJsonItem } from "@/components/dynamic-form/types"
+
+export function usePaimon(item:any) {
+  const { t } = useLocaleHooks()
+  
+  const tabType = item.data.tabType
+  console.log('item', item.data)
+
+       const model = reactive({
+               warehouse: '',
+    metastore: '',
+    url: '',
+    other_configs: '',
+    database: '',
+    table_name: '',
+    primary_key: '',
+    partition_column: '',
+               other_configs2: '',
+       })
+
+  const TypeMappingOptions = [] as any
+
+       return {
+               json: [
+                       {
+                               type: 'input',
+                               field: 'warehouse',
+                               name: 'Warehouse',
+                               props: {
+                                       placeholder: ''
+                               },
+        span: computed(() => tabType.value === 'catalog_configuration' ? 24 : 
0),
+                               validate: {
+                                       trigger: ['input', 'blur'],
+                                       required: true,
+                                       message: 'error',
+                                       validator: (validator: any, value: 
string) => {
+                                               if (!value) {
+                                                       return new 
Error('error')
+                                               }
+                                       }
+                               }
+                       },
+                       {
+        type: 'select',
+        field: 'metastore',
+        name: 'Metastore',
+        options: TypeMappingOptions,
+        span: computed(() => tabType.value === 'catalog_configuration' ? 24 : 
0),
+      },
+                       {
+                               type: 'input',
+                               field: 'url',
+                               name: 'Url',
+        span: computed(() => tabType.value === 'catalog_configuration' ? 24 : 
0),
+                               props: {
+                                       placeholder: ''
+                               }
+                       },
+      {
+                               type: 'input',
+                               field: 'other_configs',
+                               name: t('cdc.other_configs'),
+        span: computed(() => tabType.value === 'catalog_configuration' ? 24 : 
0),
+                               props: {
+                                       placeholder: '',
+          type: 'textarea',
+                               }
+                       },
+      {
+                               type: 'input',
+                               field: 'database',
+                               name: t('cdc.database'),
+                               props: {
+                                       placeholder: ''
+                               },
+        span: computed(() => tabType.value === 'synchronization_configuration' 
? 24 : 0),
+                               validate: {
+                                       trigger: ['input', 'blur'],
+                                       required: true,
+                                       message: 'error',
+                                       validator: (validator: any, value: 
string) => {
+                                               if (!value) {
+                                                       return new 
Error('error')
+                                               }
+                                       }
+                               }
+                       },
+      {
+                               type: 'input',
+                               field: 'table_name',
+                               name: t('cdc.table_name'),
+                               props: {
+                                       placeholder: ''
+                               },
+        span: computed(() => tabType.value === 'synchronization_configuration' 
? 24 : 0),
+                               validate: {
+                                       trigger: ['input', 'blur'],
+                                       required: true,
+                                       message: 'error',
+                                       validator: (validator: any, value: 
string) => {
+                                               if (!value) {
+                                                       return new 
Error('error')
+                                               }
+                                       }
+                               }
+                       },
+      {
+                               type: 'input',
+                               field: 'primary_key',
+                               name: t('cdc.primary_key'),
+        span: computed(() => tabType.value === 'synchronization_configuration' 
? 24 : 0),
+                               props: {
+                                       placeholder: ''
+                               }
+                       },
+                       {
+                               type: 'input',
+                               field: 'partition_column',
+                               name: t('cdc.partition_column'),
+        span: computed(() => tabType.value === 'synchronization_configuration' 
? 24 : 0),
+                               props: {
+                                       placeholder: ''
+                               }
+                       },
+      {
+                               type: 'input',
+                               field: 'other_configs2',
+                               name: t('cdc.other_configs'),
+        span: computed(() => tabType.value === 'synchronization_configuration' 
? 24 : 0),
+                               props: {
+                                       placeholder: '',
+          type: 'textarea',
+                               }
+                       },
+               ] as IJsonItem[], model
+       }
+}
diff --git a/paimon-web-ui-new/src/form-lib/index.ts 
b/paimon-web-ui-new/src/form-lib/index.ts
index 8d366d9..e505e13 100644
--- a/paimon-web-ui-new/src/form-lib/index.ts
+++ b/paimon-web-ui-new/src/form-lib/index.ts
@@ -15,8 +15,12 @@ KIND, either express or implied.  See the License for the
 specific language governing permissions and limitations
 under the License. */
 
-import { useCDCList } from "./source/use-cdc-list";
+import { useCDCList } from "./cdc/use-cdc-list";
+import { useMYSQL } from './cdc/use-mysql'
+import { usePaimon } from "./cdc/use-paimon";
 
 export default {
-  CDCLIST: useCDCList
+  CDCLIST: useCDCList,
+  MYSQL: useMYSQL,
+  PAIMON: usePaimon
 }
diff --git a/paimon-web-ui-new/src/locales/en/modules/cdc.ts 
b/paimon-web-ui-new/src/locales/en/modules/cdc.ts
index 5d51dcc..2ae4a73 100644
--- a/paimon-web-ui-new/src/locales/en/modules/cdc.ts
+++ b/paimon-web-ui-new/src/locales/en/modules/cdc.ts
@@ -34,4 +34,19 @@ export default {
   single_table_synchronization: 'Single Table Synchronization',
   whole_database_synchronization: 'Whole Database Synchronization',
   save: 'Save',
+  host_name_and_ip_address: 'Host Name/IP Address',
+  port: 'Port',
+  username: 'Username',
+  password: 'Password',
+  database: 'Database',
+  table_name: 'Table Name',
+  other_configs: 'Other Configs',
+  type_mapping: 'Type Mapping',
+  metadata_column: 'Metadata Column',
+  computed_column: 'Computed Column',
+  connection_information: 'Connection Information',
+  catalog_configuration: 'Catalog Configuration',
+  synchronization_configuration: 'Synchronization Configuration',
+  primary_key: 'Primary Key',
+  partition_column: 'Partition Column',
 }
diff --git a/paimon-web-ui-new/src/locales/zh/modules/cdc.ts 
b/paimon-web-ui-new/src/locales/zh/modules/cdc.ts
index bf7e738..c95dd5d 100644
--- a/paimon-web-ui-new/src/locales/zh/modules/cdc.ts
+++ b/paimon-web-ui-new/src/locales/zh/modules/cdc.ts
@@ -34,4 +34,19 @@ export default {
   single_table_synchronization: '单表同步',
   whole_database_synchronization: '整库同步',
   save: '保存',
+  host_name_and_ip_address: '主机名/IP地址',
+  port: '端口',
+  username: '用户名',
+  password: '密码',
+  database: '数据库',
+  table_name: '表名',
+  other_configs: '其他配置',
+  type_mapping: '类型映射',
+  metadata_column: '元数据列',
+  computed_column: '计算列',
+  connection_information: '连接信息',
+  catalog_configuration: 'Catalog配置',
+  synchronization_configuration: '同步配置',
+  primary_key: '主键',
+  partition_column: '分区列',
 }
diff --git a/paimon-web-ui-new/src/form-lib/index.ts 
b/paimon-web-ui-new/src/store/cdc/index.ts
similarity index 70%
copy from paimon-web-ui-new/src/form-lib/index.ts
copy to paimon-web-ui-new/src/store/cdc/index.ts
index 8d366d9..42b5c57 100644
--- a/paimon-web-ui-new/src/form-lib/index.ts
+++ b/paimon-web-ui-new/src/store/cdc/index.ts
@@ -15,8 +15,24 @@ KIND, either express or implied.  See the License for the
 specific language governing permissions and limitations
 under the License. */
 
-import { useCDCList } from "./source/use-cdc-list";
-
-export default {
-  CDCLIST: useCDCList
+export interface CDCState {
+  model: object
 }
+
+export const useCDCStore = defineStore({
+  id: 'cdc',
+  state: (): CDCState => ({
+    model: {}
+  }),
+  persist: true,
+  getters: {
+    getModel(): any {
+      return this.model
+    }
+  },
+  actions: {
+    setModel(model: object): void {
+      this.model = model
+    }
+  }
+})
diff --git a/paimon-web-ui-new/src/views/cdc/components/dag/dag-canvas.tsx 
b/paimon-web-ui-new/src/views/cdc/components/dag/context-menu.tsx
similarity index 63%
copy from paimon-web-ui-new/src/views/cdc/components/dag/dag-canvas.tsx
copy to paimon-web-ui-new/src/views/cdc/components/dag/context-menu.tsx
index ede6799..0f1df6f 100644
--- a/paimon-web-ui-new/src/views/cdc/components/dag/dag-canvas.tsx
+++ b/paimon-web-ui-new/src/views/cdc/components/dag/context-menu.tsx
@@ -15,34 +15,40 @@ KIND, either express or implied.  See the License for the
 specific language governing permissions and limitations
 under the License. */
 
-import { useCanvasInit } from './use-canvas-init'
+import { NButton } from 'naive-ui'
 import styles from './index.module.scss'
-import DagSlider from './dag-slider'
 
 export default defineComponent({
-  name: 'DagCanvasPage',
-  setup() {
+  name: 'ContextMenuTool',
+  emits: ['delete'],
+  props: {
+    x: {
+      type: Number,
+      default: 0
+    },
+    y: {
+      type: Number,
+      default: 0
+    }
+  },
+  setup(props, { emit }) {
     const { t } = useLocaleHooks()
-
-    const { graph, dnd } = useCanvasInit()
+    const handleDelete = () => {
+      emit('delete')
+    }
 
     return {
       t,
-      graph,
-      dnd
+      handleDelete
     }
   },
   render() {
     return (
-      <div class={styles.dag}>
-        <div
-          class={styles['dag-container']}
-          id="dag-container"
-        />
-        <DagSlider
-          graph={this.graph}
-          dnd={this.dnd}
-        />
+      <div
+        class={styles['context-menu']}
+        style={{ left: `${this.x}px`, top: `${this.y}px` }}
+      >
+        <NButton type='primary' style={'width: 100%'} 
onClick={this.handleDelete}>{this.t('cdc.delete')}</NButton>
       </div>
     )
   }
diff --git a/paimon-web-ui-new/src/views/cdc/components/dag/custom-node.tsx 
b/paimon-web-ui-new/src/views/cdc/components/dag/custom-node.tsx
index d90682c..118d84c 100644
--- a/paimon-web-ui-new/src/views/cdc/components/dag/custom-node.tsx
+++ b/paimon-web-ui-new/src/views/cdc/components/dag/custom-node.tsx
@@ -15,6 +15,7 @@ KIND, either express or implied.  See the License for the
 specific language governing permissions and limitations
 under the License. */
 
+import { NButton } from 'naive-ui'
 import styles from './index.module.scss'
 
 export default defineComponent({
@@ -23,6 +24,7 @@ export default defineComponent({
     const getNode = inject('getNode') as any
     const node = getNode()
     const data = node.data
+    
     const onMainMouseEnter = () => {
       const ports = node.getPorts() || []
       ports.forEach((port: { id: any }) => {
@@ -56,7 +58,7 @@ export default defineComponent({
         onMouseenter={this.onMainMouseEnter}
         onMouseleave={this.onMainMouseLeave}
       >
-        {this.data.name}
+        <NButton text>{this.data.name}</NButton>
       </div>
     )
   }
diff --git a/paimon-web-ui-new/src/views/cdc/components/dag/dag-canvas.tsx 
b/paimon-web-ui-new/src/views/cdc/components/dag/dag-canvas.tsx
index ede6799..b9ef7c3 100644
--- a/paimon-web-ui-new/src/views/cdc/components/dag/dag-canvas.tsx
+++ b/paimon-web-ui-new/src/views/cdc/components/dag/dag-canvas.tsx
@@ -18,18 +18,73 @@ under the License. */
 import { useCanvasInit } from './use-canvas-init'
 import styles from './index.module.scss'
 import DagSlider from './dag-slider'
+import Drawer from './drawer'
+import ContextMenuTool from './context-menu'
 
 export default defineComponent({
   name: 'DagCanvasPage',
-  setup() {
+  setup(props, { expose }) {
     const { t } = useLocaleHooks()
 
     const { graph, dnd } = useCanvasInit()
 
+    const nodeVariables = reactive({
+      x: 0,
+      y: 0,
+      row: {} as any,
+      cell: {} as any,
+      showDrawer: false,
+      showContextMenu: false
+    })
+
+    onMounted(() => {
+      if (graph.value) {
+        graph.value.on('node:dblclick', ({ node }) => {
+          nodeVariables.showDrawer = true
+          nodeVariables.row = node.data
+        })
+        graph.value.on('node:contextmenu', ({ e, node }) => {
+          nodeVariables.showContextMenu = true
+          nodeVariables.row = node.data
+          nodeVariables.x = e.clientX - 20
+          nodeVariables.y = e.clientY - 178
+        })
+        graph.value.on('blank:click', () => {
+          nodeVariables.showContextMenu = false
+        })
+      }
+    })
+
+    const handleNodeConfirm = (model: any) => {
+      if (graph.value) {
+        nodeVariables.cell = graph.value.getCellById(nodeVariables.row.name)
+        if (nodeVariables.cell) {
+          nodeVariables.cell.data = {
+            ...nodeVariables.cell.data,
+            ...model
+          }
+        }
+      }
+      nodeVariables.showDrawer = false
+    }
+
+    const handleDelete = () => {
+      nodeVariables.showContextMenu = false
+      graph.value?.removeNode(nodeVariables.row.name)
+    }
+
+    expose({
+      graph,
+      dnd
+    })
+
     return {
       t,
       graph,
-      dnd
+      dnd,
+      handleNodeConfirm,
+      handleDelete,
+      ...toRefs(nodeVariables)
     }
   },
   render() {
@@ -43,6 +98,24 @@ export default defineComponent({
           graph={this.graph}
           dnd={this.dnd}
         />
+        {
+          this.showDrawer &&
+          <Drawer
+            showDrawer={this.showDrawer}
+            formType={this.row.value || 'MYSQL'}
+            onConfirm={this.handleNodeConfirm}
+            onCancel={() => this.showDrawer = false}
+            row={this.row}
+          />
+        }
+        {
+          this.showContextMenu &&
+          <ContextMenuTool
+            onDelete={this.handleDelete}
+            x={this.x}
+            y={this.y}
+          />
+        }
       </div>
     )
   }
diff --git a/paimon-web-ui-new/src/views/cdc/components/dag/dag-slider.tsx 
b/paimon-web-ui-new/src/views/cdc/components/dag/dag-slider.tsx
index 982a474..9961d31 100644
--- a/paimon-web-ui-new/src/views/cdc/components/dag/dag-slider.tsx
+++ b/paimon-web-ui-new/src/views/cdc/components/dag/dag-slider.tsx
@@ -36,29 +36,29 @@ export default defineComponent({
       sourceList: [
         {
           name: 'MySQL',
-          value: 'mysql',
+          value: 'MYSQL',
           type: 'INPUT'
         },
         {
           name: 'Kafka',
-          value: 'kafka',
+          value: 'KAFKA',
           type: 'INPUT'
         },
         {
           name: 'MongoDB',
-          value: 'mongodb',
+          value: 'MONGODB',
           type: 'INPUT'
         },
         {
           name: 'PostgreSQL',
-          value: 'postgresql',
+          value: 'POSTGRESQL',
           type: 'INPUT'
         }
       ],
       sinkList: [
         {
           name: 'Paimon',
-          value: 'paimon',
+          value: 'PAIMON',
           type: 'OUTPUT'
         }
       ]
diff --git a/paimon-web-ui-new/src/views/cdc/components/dag/drawer.tsx 
b/paimon-web-ui-new/src/views/cdc/components/dag/drawer.tsx
new file mode 100644
index 0000000..f116261
--- /dev/null
+++ b/paimon-web-ui-new/src/views/cdc/components/dag/drawer.tsx
@@ -0,0 +1,176 @@
+/* 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 Form from "@/components/dynamic-form"
+import { useTask } from "@/components/modal/use-task"
+
+const props = {
+  title: {
+    type: String as PropType<string>,
+    default: ''
+  },
+  row: {
+    type: Object as PropType<any>,
+    default: () => {}
+  },
+  showDrawer: {
+    type: Boolean as PropType<boolean>,
+    default: false
+  },
+  autoFocus: {
+    type: Boolean as PropType<boolean>,
+    default: false
+  },
+  closeable: {
+    type: Boolean as PropType<boolean>,
+    default: true
+  },
+  formType: {
+    type: String as PropType<string>,
+    default: ''
+  },
+}
+
+export default defineComponent({
+  name: 'DrawerPage',
+  props,
+  emits: ['confirm', 'cancel'],
+  setup(props, { expose, emit }) {
+    const { t } = useLocaleHooks()
+    const formRef = ref()
+    expose({formRef})
+
+    const chooseTab = ref('connection_information')
+
+    const { elementsRef, rulesRef, model } = useTask({
+      data: {
+        ...props.row,
+        tabType: chooseTab
+      },
+      formType: props.formType
+    })
+
+    const handleConfirm = () => {
+      emit('confirm', model)
+    }
+
+    const handleCancel = () => {
+      emit('cancel')
+    }
+
+
+    watch(
+      () => props.row.type,
+      (val) => {
+        if (val === 'INPUT') {
+          chooseTab.value = 'connection_information'
+        } else if (val === 'OUTPUT') {
+          chooseTab.value = 'catalog_configuration'
+        }
+      },
+      {
+        immediate: true
+      }
+    )
+    
+    return {
+      t,
+      handleConfirm,
+      handleCancel,
+      formRef,
+      elementsRef,
+      rulesRef,
+      model,
+      chooseTab
+    }
+  },
+  render () {
+    return (
+      <n-drawer
+        v-model:show={this.showDrawer}
+        mask-closable={false}
+        auto-focus={this.autoFocus}
+        default-width="502"
+        resizable
+      >
+        <n-drawer-content>
+          {{
+            default: () => (
+              <n-tabs type="line" v-model:value={this.chooseTab}>
+                {
+                  this.row.type === 'INPUT' &&
+                  <n-tab-pane name="connection_information" 
tab={this.t('cdc.connection_information')}>
+                    <Form
+                      ref={this.formRef}
+                      meta={{
+                        model: this.model,
+                        rules: this.rulesRef,
+                        elements: this.elementsRef,
+                      }}
+                      gridProps={{
+                        xGap: 10
+                      }}
+                    />
+                  </n-tab-pane>
+                }
+                {
+                  this.row.type === 'OUTPUT' &&
+                  <n-tab-pane name="catalog_configuration" 
tab={this.t('cdc.catalog_configuration')}>
+                    <Form
+                      ref={this.formRef}
+                      meta={{
+                        model: this.model,
+                        rules: this.rulesRef,
+                        elements: this.elementsRef,
+                      }}
+                      gridProps={{
+                        xGap: 10
+                      }}
+                    />
+                  </n-tab-pane>
+                }
+                <n-tab-pane name="synchronization_configuration" 
tab={this.t('cdc.synchronization_configuration')}>
+                  <Form
+                    ref={this.formRef}
+                    meta={{
+                      model: this.model,
+                      rules: this.rulesRef,
+                      elements: this.elementsRef,
+                    }}
+                    gridProps={{
+                      xGap: 10
+                    }}
+                  />
+                </n-tab-pane>
+              </n-tabs>
+            ),
+            footer: () => (
+              <n-space justify='end'>
+                <n-button onClick={this.handleCancel}>
+                  {this.t('layout.cancel')}
+                </n-button>
+                <n-button type='primary' onClick={this.handleConfirm}>
+                  {this.t('layout.confirm')}
+                </n-button>
+              </n-space>
+            )
+          }}
+        </n-drawer-content>
+      </n-drawer>
+    )
+  }
+})
diff --git a/paimon-web-ui-new/src/views/cdc/components/dag/index.module.scss 
b/paimon-web-ui-new/src/views/cdc/components/dag/index.module.scss
index d1dd006..77658b5 100644
--- a/paimon-web-ui-new/src/views/cdc/components/dag/index.module.scss
+++ b/paimon-web-ui-new/src/views/cdc/components/dag/index.module.scss
@@ -15,10 +15,14 @@ KIND, either express or implied.  See the License for the
 specific language governing permissions and limitations
 under the License. */
 
-.title {
+.title-bar {
   width: 100%;
   display: flex;
   justify-content: space-between;
+
+  .title {
+    cursor: pointer;
+  }
 }
 
 .dag {
@@ -66,3 +70,10 @@ under the License. */
   border-radius: 4px;
   box-shadow: 0 2px 5px 1px rgba(0, 0, 0, 0.06);
 }
+
+
+.context-menu {
+  position: absolute;
+  width: 100px;
+  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.12);
+}
diff --git a/paimon-web-ui-new/src/views/cdc/components/dag/index.tsx 
b/paimon-web-ui-new/src/views/cdc/components/dag/index.tsx
index 8ea45a0..073ec6b 100644
--- a/paimon-web-ui-new/src/views/cdc/components/dag/index.tsx
+++ b/paimon-web-ui-new/src/views/cdc/components/dag/index.tsx
@@ -18,14 +18,44 @@ under the License. */
 import { Leaf, Save } from "@vicons/ionicons5"
 import styles from './index.module.scss';
 import DagCanvas from "./dag-canvas";
+import { useCDCStore } from "@/store/cdc";
+import type { Router } from "vue-router";
 
 export default defineComponent({
   name: 'DagPage',
   setup() {
     const { t } = useLocaleHooks()
+    const CDCStore = useCDCStore()
+    const title = ref('')
+    onMounted(() => {
+      title.value = CDCStore.getModel.name
+    })
+
+    const dagRef = ref() as any
+    const handleSave = () => {
+      // console.log('dagRef', dagRef.value.graph.toJSON())
+      router.push({ path: '/cdc_ingestion' })
+    }
+
+    const router: Router = useRouter()
+    const handleJump = () => {
+      router.push({ path: '/cdc_ingestion' })
+    }
+
+    onMounted(() => {
+      if (dagRef.value && dagRef.value.graph) {
+        dagRef.value.graph.fromJSON({
+          cells: CDCStore.getModel.cells
+        })
+      }
+    })
     
     return {
       t,
+      title,
+      handleSave,
+      dagRef,
+      handleJump
     }
   },
   render() {
@@ -33,10 +63,12 @@ export default defineComponent({
       <n-card>
         <n-space vertical size={24}>
           <n-card>
-            <div class={styles.title}>
+            <div class={styles['title-bar']}>
               <n-space align="center">
                 <n-icon component={Leaf} color="#2F7BEA" size="18" />
-                <span>{this.t('cdc.synchronization_job_definition')}</span>
+                <span class={styles.title} 
onClick={this.handleJump}>{this.t('cdc.synchronization_job_definition')} {
+                  this.title ? ` - ${this.title}` : ''
+                }</span>
               </n-space>
               <div class={styles.operation}>
                 <n-space>
@@ -44,6 +76,7 @@ export default defineComponent({
                     v-slots={{
                       trigger: () => (
                         <n-button
+                          onClick={this.handleSave}
                           v-slots={{
                             icon: () => <n-icon component={Save}></n-icon>
                           }}
@@ -57,7 +90,7 @@ export default defineComponent({
               </div>
             </div>
           </n-card>
-          <DagCanvas></DagCanvas>
+          <DagCanvas ref='dagRef'></DagCanvas>
         </n-space>
       </n-card>
     )
diff --git a/paimon-web-ui-new/src/views/cdc/components/dag/use-canvas-init.ts 
b/paimon-web-ui-new/src/views/cdc/components/dag/use-canvas-init.ts
index 59791dc..36526eb 100644
--- a/paimon-web-ui-new/src/views/cdc/components/dag/use-canvas-init.ts
+++ b/paimon-web-ui-new/src/views/cdc/components/dag/use-canvas-init.ts
@@ -21,29 +21,29 @@ import { register } from '@antv/x6-vue-shape'
 import CustomNode from './custom-node'
 import { EDGE, PORT } from './node-config'
 
-register({
-  shape: 'custom-node',
-  width: 150,
-  height: 40,
-  component: CustomNode,
-  ports: {
-    ...PORT
-  }
-})
-
 export function useCanvasInit() {
   const graph = ref<Graph>()
   const dnd = ref<Dnd>()
 
   const graphInit = () => {
+    register({
+      shape: 'custom-node',
+      width: 150,
+      height: 40,
+      component: CustomNode,
+      ports: {
+        ...PORT
+      }
+    })
+
     return new Graph({
       container: document.getElementById('dag-container') || undefined,
       // background: {
       //   color: '#F2F7FA',
       // },
       autoResize: true,
-      panning: true,
-      mousewheel: true,
+      panning: false,
+      mousewheel: false,
       grid: {
         visible: true,
         type: 'dot',
@@ -76,6 +76,32 @@ export function useCanvasInit() {
         anchor: {
           name: 'left',
         },
+        validateConnection(data: any) {
+          const { sourceCell, targetCell, sourceView, targetView } = data
+          // Prevent loop edges
+          if (sourceView === targetView) {
+            return false
+          }
+          if (
+            sourceCell &&
+            targetCell &&
+            sourceCell.isNode() &&
+            targetCell.isNode()
+          ) {
+            const sourceData = sourceCell.getData()
+            // Prevent edges from being created from the output port
+            if (sourceData.type === 'OUTPUT') return false
+            // Prevent edges from being created from the input port to the 
input port
+            if (targetCell.getData().type === 'INPUT') return false
+            // Prevent multiple edges from being created between the same 
start node and end node
+            const edges = graph.value?.getConnectedEdges(targetCell)
+            if (edges!.length > 0) {
+              return false
+            }
+            
+          }
+          return true
+        }
       }
     })
   }
diff --git a/paimon-web-ui-new/src/views/cdc/components/list/index.tsx 
b/paimon-web-ui-new/src/views/cdc/components/list/index.tsx
index 0d78f32..78deb86 100644
--- a/paimon-web-ui-new/src/views/cdc/components/list/index.tsx
+++ b/paimon-web-ui-new/src/views/cdc/components/list/index.tsx
@@ -17,6 +17,7 @@ under the License. */
 
 import styles from './index.module.scss';
 import TableAction from '@/components/table-action';
+import { useCDCStore } from '@/store/cdc';
 import type { Router } from 'vue-router';
 
 export default defineComponent({
@@ -64,20 +65,178 @@ export default defineComponent({
             h(TableAction, {
               row,
               onHandleEdit: (row) => {
-                tableVariables.row = row
+                const CDCStore = useCDCStore()
+                CDCStore.setModel(row)
                 router.push({ path: '/cdc_ingestion/dag' })
               },
             })
         }
       ],
       data: [
-        { name: 1, type: 'Single table synchronization', create_user: 'admin' 
},
-        { name: 2, type: "Whole database synchronization", create_user: 
'admin' },
+        {
+          name: 1,
+          type: 'Single table synchronization',
+          create_user: 'admin',
+          cells: [
+            {
+                "position": {
+                    "x": 300,
+                    "y": 40
+                },
+                "size": {
+                    "width": 150,
+                    "height": 40
+                },
+                "view": "vue-shape-view",
+                "shape": "custom-node",
+                "ports": {
+                    "groups": {
+                        "in": {
+                            "position": "left",
+                            "attrs": {
+                                "circle": {
+                                    "r": 4,
+                                    "magnet": true,
+                                    "stroke": "transparent",
+                                    "strokeWidth": 1,
+                                    "fill": "transparent"
+                                }
+                            }
+                        },
+                        "out": {
+                            "position": {
+                                "name": "right",
+                                "args": {
+                                    "dx": 5
+                                }
+                            },
+                            "attrs": {
+                                "circle": {
+                                    "r": 4,
+                                    "magnet": true,
+                                    "stroke": "transparent",
+                                    "strokeWidth": 1,
+                                    "fill": "transparent"
+                                }
+                            }
+                        }
+                    },
+                    "items": [
+                        {
+                            "id": "MySQL-out",
+                            "group": "out",
+                            "attrs": {
+                                "circle": {
+                                    "fill": "transparent",
+                                    "stroke": "transparent"
+                                }
+                            }
+                        }
+                    ]
+                },
+                "id": "MySQL",
+                "data": {
+                    "name": "MySQL",
+                    "value": "MYSQL",
+                    "type": "INPUT",
+                    "host": "1",
+                    "port": "2",
+                    "username": "3",
+                    "password": "4",
+                    "other_configs": "5",
+                    "database": "",
+                    "table_name": "",
+                    "type_mapping": "",
+                    "metadata_column": "",
+                    "computed_column": ""
+                },
+                "zIndex": 1
+            },
+            {
+                "position": {
+                    "x": 640,
+                    "y": 40
+                },
+                "size": {
+                    "width": 150,
+                    "height": 40
+                },
+                "view": "vue-shape-view",
+                "shape": "custom-node",
+                "ports": {
+                    "groups": {
+                        "in": {
+                            "position": "left",
+                            "attrs": {
+                                "circle": {
+                                    "r": 4,
+                                    "magnet": true,
+                                    "stroke": "transparent",
+                                    "strokeWidth": 1,
+                                    "fill": "transparent"
+                                }
+                            }
+                        },
+                        "out": {
+                            "position": {
+                                "name": "right",
+                                "args": {
+                                    "dx": 5
+                                }
+                            },
+                            "attrs": {
+                                "circle": {
+                                    "r": 4,
+                                    "magnet": true,
+                                    "stroke": "transparent",
+                                    "strokeWidth": 1,
+                                    "fill": "transparent"
+                                }
+                            }
+                        }
+                    },
+                    "items": [
+                        {
+                            "id": "Paimon-in",
+                            "group": "in",
+                            "attrs": {
+                                "circle": {
+                                    "fill": "transparent",
+                                    "stroke": "transparent"
+                                }
+                            }
+                        }
+                    ]
+                },
+                "id": "Paimon",
+                "data": {
+                    "name": "Paimon",
+                    "value": "PAIMON",
+                    "type": "OUTPUT"
+                },
+                "zIndex": 2
+            },
+            {
+                "shape": "dag-edge",
+                "connector": {
+                    "name": "smooth"
+                },
+                "id": "c3bec4f4-eea9-44ed-b98e-63605d619d50",
+                "zIndex": 3,
+                "source": {
+                    "cell": "MySQL",
+                    "port": "MySQL-out"
+                },
+                "target": {
+                    "cell": "Paimon"
+                }
+            }
+          ]
+        },
       ],
       pagination: {
         pageSize: 10
-      },
-      row: {}
+      }
     })
     
     return {
diff --git a/paimon-web-ui-new/src/views/cdc/index.tsx 
b/paimon-web-ui-new/src/views/cdc/index.tsx
index 548414e..bd21ba1 100644
--- a/paimon-web-ui-new/src/views/cdc/index.tsx
+++ b/paimon-web-ui-new/src/views/cdc/index.tsx
@@ -19,6 +19,8 @@ import Modal from '@/components/modal';
 import List from './components/list';
 import styles from './index.module.scss';
 import { Leaf } from '@vicons/ionicons5';
+import { useCDCStore } from '@/store/cdc';
+import type { Router } from 'vue-router';
 
 export default defineComponent({
   name: 'CDCPage',
@@ -31,15 +33,22 @@ export default defineComponent({
       showModalRef.value = true
     }
 
-    const handleConfirm = () => {
+    const CDCStore = useCDCStore()
+    const router: Router = useRouter()
+    const CDCModalRef = ref()
+    const handleConfirm = async(model: any) => {
+      CDCStore.setModel(model)
+      await CDCModalRef.value.formRef.validate()
       showModalRef.value = false
+      router.push({ path: '/cdc_ingestion/dag' })
     }
 
     return {
       t,
       showModalRef,
       handleOpenModal,
-      handleConfirm
+      handleConfirm,
+      CDCModalRef
     }
   },
   render() {
@@ -62,13 +71,17 @@ export default defineComponent({
               </div>
             </n-card>
             <List></List>
-            <Modal
-              showModal={this.showModalRef}
-              title={this.t('cdc.create_synchronization_job')}
-              formType="CDCLIST"
-              onCancel={() => this.showModalRef = false}
-              onConfirm={this.handleConfirm}
-            />
+            {
+              this.showModalRef &&
+              <Modal
+                ref='CDCModalRef'
+                showModal={this.showModalRef}
+                title={this.t('cdc.create_synchronization_job')}
+                formType="CDCLIST"
+                onCancel={() => this.showModalRef = false}
+                onConfirm={this.handleConfirm}
+              />
+            }
           </n-space>
         </n-card>
       </div>


Reply via email to