From 9e9f1549f56aacc64a61650fd759c69d204f6d0e Mon Sep 17 00:00:00 2001 From: chaoling <126888254+zhuchaoling@users.noreply.github.com> Date: Fri, 30 Jun 2023 17:58:12 +0800 Subject: [PATCH 1/3] [Feat] Adding the sync task (#63) * add dark light theme * migrate sync-task file * add deps * feat: update datasource * feat: update virtual-tables * feat: update data-pipes * feat: update user-manage * [feat] Adding the synchronization task module. --- seatunnel-ui/package.json | 4 + seatunnel-ui/src/App.tsx | 9 +- seatunnel-ui/src/assets/styles/default.scss | 33 + .../src/common/column-width-config.ts | 130 ++ seatunnel-ui/src/common/common.ts | 419 ++++++ seatunnel-ui/src/common/types.ts | 50 + .../components/button-link/index.module.scss | 26 + .../src/components/button-link/index.tsx | 57 + seatunnel-ui/src/components/card/index.tsx | 67 + .../checkbox-tree/index.module.scss | 24 + .../src/components/checkbox-tree/index.tsx | 208 +++ .../components/log-modal/index.module.scss | 22 + .../src/components/log-modal/index.tsx | 216 +++ .../src/components/resource-auth/index.tsx | 163 +++ seatunnel-ui/src/directives/index.ts | 30 + seatunnel-ui/src/directives/permission.ts | 46 + seatunnel-ui/src/hooks/index.ts | 22 + seatunnel-ui/src/hooks/use-client-config.ts | 57 + .../src/hooks/use-permission-length.ts | 35 + seatunnel-ui/src/hooks/use-source-type.ts | 83 ++ seatunnel-ui/src/hooks/use-table-link.ts | 94 ++ seatunnel-ui/src/hooks/use-table-operation.ts | 219 +++ seatunnel-ui/src/hooks/use-text-copy.ts | 36 + .../src/layouts/dashboard/header/index.tsx | 2 +- .../layouts/dashboard/header/menu/index.tsx | 15 +- .../layouts/dashboard/header/menu/use-menu.ts | 4 - seatunnel-ui/src/layouts/dashboard/index.tsx | 51 +- .../dashboard/sidebar/index.module.scss | 67 + .../src/layouts/dashboard/sidebar/index.tsx | 137 ++ seatunnel-ui/src/locales/en_US/hook.ts | 20 + seatunnel-ui/src/locales/en_US/index.ts | 8 +- seatunnel-ui/src/locales/en_US/menu.ts | 4 +- seatunnel-ui/src/locales/en_US/project.ts | 1176 +++++++++++++++++ seatunnel-ui/src/locales/en_US/theme.ts | 22 + seatunnel-ui/src/locales/zh_CN/hook.ts | 20 + seatunnel-ui/src/locales/zh_CN/index.ts | 8 +- seatunnel-ui/src/locales/zh_CN/menu.ts | 4 +- seatunnel-ui/src/locales/zh_CN/project.ts | 1143 ++++++++++++++++ seatunnel-ui/src/locales/zh_CN/theme.ts | 22 + seatunnel-ui/src/main.ts | 1 + seatunnel-ui/src/router/data-pipes.ts | 12 +- seatunnel-ui/src/router/datasource.ts | 9 +- seatunnel-ui/src/router/jobs.ts | 3 +- seatunnel-ui/src/router/sync-task.ts | 80 ++ seatunnel-ui/src/router/tasks.ts | 46 +- seatunnel-ui/src/router/user-manage.ts | 3 +- seatunnel-ui/src/router/virtual-tables.ts | 21 +- seatunnel-ui/src/service/data-source/index.ts | 173 +++ .../{datasource => data-source}/types.ts | 55 +- seatunnel-ui/src/service/log/index.ts | 46 + seatunnel-ui/src/service/log/types.ts | 40 + seatunnel-ui/src/service/resources/index.ts | 385 ++++++ seatunnel-ui/src/service/resources/types.ts | 216 +++ seatunnel-ui/src/service/service.ts | 15 +- .../src/service/sync-task-definition/index.ts | 317 +++++ .../index.ts | 57 +- .../src/service/task-instances/index.ts | 87 ++ .../src/service/task-instances/types.ts | 127 ++ .../src/service/virtual-table/index.ts | 84 ++ .../src/service/virtual-table/types.ts | 58 + .../src/store/datasource/form-structures.ts | 40 + seatunnel-ui/src/store/project/index.ts | 76 ++ seatunnel-ui/src/store/project/task-node.ts | 155 +++ seatunnel-ui/src/store/project/task-type.ts | 178 +++ seatunnel-ui/src/store/project/types.ts | 117 ++ .../store/synchronization-definition/index.ts | 46 + .../store/synchronization-definition/types.ts | 22 + seatunnel-ui/src/store/theme/index.ts | 31 +- seatunnel-ui/src/store/theme/types.ts | 9 +- seatunnel-ui/src/themes/index.ts | 5 +- seatunnel-ui/src/themes/modules/dark-blue.ts | 47 + seatunnel-ui/src/themes/modules/dark.ts | 30 +- seatunnel-ui/src/themes/modules/light.ts | 77 +- seatunnel-ui/src/themes/type.ts | 18 + seatunnel-ui/src/utils/clipboard.ts | 31 + seatunnel-ui/src/utils/index.ts | 4 +- seatunnel-ui/src/utils/timePickeroption.ts | 56 + .../src/views/data-pipes/detail/index.tsx | 9 +- .../src/views/data-pipes/edit/index.tsx | 9 +- .../src/views/data-pipes/list/use-table.ts | 2 +- .../views/datasource/components/use-source.ts | 8 +- .../src/views/datasource/create/use-detail.ts | 12 +- .../src/views/datasource/create/use-form.ts | 6 +- .../src/views/datasource/list/types.ts | 20 + .../src/views/datasource/list/use-columns.ts | 16 +- .../src/views/datasource/list/use-source.ts | 91 ++ .../src/views/datasource/list/use-table.ts | 14 +- seatunnel-ui/src/views/jobs/list/use-table.ts | 2 +- seatunnel-ui/src/views/login/use-form.ts | 2 +- .../projects/components/column-selector.tsx | 143 ++ .../src/views/projects/components/index.scss | 34 + .../views/projects/components/jumpProject.ts | 0 .../projects/components/projectSelector.tsx | 96 ++ .../src/views/projects/utils/changeProject.ts | 26 + .../src/views/projects/utils/dealColumns.ts | 123 ++ .../views/projects/utils/getProjectCodes.ts | 24 + .../src/views/projects/utils/storeColums.ts | 24 + seatunnel-ui/src/views/setting/index.tsx | 10 +- .../setting/theme/index.module.scss} | 4 + .../src/views/setting/theme/index.tsx | 64 + .../src/views/setting/theme/use-theme.ts | 50 + .../task/components/node/detail-modal.tsx | 228 ++++ .../src/views/task/components/node/detail.tsx | 123 ++ .../task/components/node/fields/index.ts | 83 ++ .../components/node/fields/use-child-node.ts | 137 ++ .../node/fields/use-chunjun-deploy-mode.ts | 49 + .../components/node/fields/use-chunjun.ts | 92 ++ .../components/node/fields/use-conditions.ts | 127 ++ .../node/fields/use-custom-params.ts | 202 +++ .../components/node/fields/use-datasource.ts | 117 ++ .../task/components/node/fields/use-datax.ts | 289 ++++ .../components/node/fields/use-delay-time.ts | 33 + .../node/fields/use-dependent-timeout.ts | 101 ++ .../components/node/fields/use-dependent.ts | 543 ++++++++ .../components/node/fields/use-deploy-mode.ts | 62 + .../components/node/fields/use-description.ts | 33 + .../task/components/node/fields/use-dinky.ts | 66 + .../node/fields/use-driver-cores.ts | 42 + .../node/fields/use-driver-memory.ts | 47 + .../task/components/node/fields/use-dvc.ts | 175 +++ .../node/fields/use-dynamic-datasource.ts | 132 ++ .../task/components/node/fields/use-emr.ts | 48 + .../node/fields/use-environment-name.ts | 100 ++ .../node/fields/use-executor-cores.ts | 42 + .../node/fields/use-executor-memory.ts | 47 + .../node/fields/use-executor-number.ts | 42 + .../task/components/node/fields/use-failed.ts | 43 + .../task/components/node/fields/use-flink.ts | 229 ++++ .../components/node/fields/use-hive-cli.ts | 54 + .../task/components/node/fields/use-http.ts | 238 ++++ .../node/fields/use-informatica-item.ts | 90 ++ .../task/components/node/fields/use-jar.ts | 159 +++ .../components/node/fields/use-jupyter.ts | 127 ++ .../components/node/fields/use-main-jar.ts | 77 ++ .../node/fields/use-mlflow-models.ts | 80 ++ .../node/fields/use-mlflow-projects.ts | 322 +++++ .../task/components/node/fields/use-mlflow.ts | 69 + .../task/components/node/fields/use-mr.ts | 107 ++ .../task/components/node/fields/use-name.ts | 38 + .../components/node/fields/use-next-loop.ts | 140 ++ .../components/node/fields/use-openmldb.ts | 77 ++ .../components/node/fields/use-pre-tasks.ts | 48 + .../node/fields/use-process-name.ts | 92 ++ .../components/node/fields/use-pytorch.ts | 139 ++ .../node/fields/use-relation-custom-params.ts | 99 ++ .../node/fields/use-remote-connection.ts | 100 ++ .../components/node/fields/use-resources.ts | 111 ++ .../task/components/node/fields/use-rules.ts | 266 ++++ .../components/node/fields/use-run-flag.ts | 39 + .../components/node/fields/use-sagemaker.ts | 28 + .../components/node/fields/use-sea-tunnel.ts | 90 ++ .../task/components/node/fields/use-shell.ts | 45 + .../task/components/node/fields/use-spark.ts | 157 +++ .../components/node/fields/use-sql-utils.ts | 148 +++ .../task/components/node/fields/use-sql.ts | 81 ++ .../node/fields/use-sqoop-source-type.ts | 291 ++++ .../node/fields/use-sqoop-target-type.ts | 403 ++++++ .../task/components/node/fields/use-sqoop.ts | 148 +++ .../components/node/fields/use-surveil.ts | 371 ++++++ .../task/components/node/fields/use-switch.ts | 127 ++ .../node/fields/use-target-task-name.ts | 37 + .../components/node/fields/use-task-group.ts | 84 ++ .../node/fields/use-task-priority.ts | 92 ++ .../components/node/fields/use-task-type.ts | 50 + .../node/fields/use-timeout-alarm.ts | 88 ++ .../task/components/node/fields/use-udfs.ts | 59 + .../node/fields/use-whale-sea-tunnel.ts | 83 ++ .../node/fields/use-worker-group.ts | 63 + .../components/node/fields/use-zeppelin.ts | 85 ++ .../views/task/components/node/format-data.ts | 770 +++++++++++ .../task/components/node/index.module.scss | 62 + .../views/task/components/node/tasks/index.ts | 86 ++ .../task/components/node/tasks/use-card.ts | 89 ++ .../task/components/node/tasks/use-chunjun.ts | 86 ++ .../components/node/tasks/use-conditions.ts | 77 ++ .../components/node/tasks/use-data-quality.ts | 130 ++ .../task/components/node/tasks/use-datax.ts | 86 ++ .../components/node/tasks/use-dependent.ts | 82 ++ .../task/components/node/tasks/use-dinky.ts | 81 ++ .../task/components/node/tasks/use-dvc.ts | 82 ++ .../task/components/node/tasks/use-emr.ts | 82 ++ .../task/components/node/tasks/use-flink.ts | 88 ++ .../components/node/tasks/use-hive-cli.ts | 80 ++ .../task/components/node/tasks/use-http.ts | 87 ++ .../components/node/tasks/use-informatica.ts | 85 ++ .../task/components/node/tasks/use-jar.ts | 81 ++ .../task/components/node/tasks/use-jupyter.ts | 83 ++ .../task/components/node/tasks/use-mlflow.ts | 89 ++ .../task/components/node/tasks/use-mr.ts | 81 ++ .../components/node/tasks/use-openmldb.ts | 85 ++ .../task/components/node/tasks/use-pigeon.ts | 80 ++ .../components/node/tasks/use-procedure.ts | 89 ++ .../task/components/node/tasks/use-python.ts | 83 ++ .../task/components/node/tasks/use-pytorch.ts | 86 ++ .../components/node/tasks/use-sagemaker.ts | 81 ++ .../components/node/tasks/use-sea-tunnel.ts | 108 ++ .../task/components/node/tasks/use-shell.ts | 85 ++ .../task/components/node/tasks/use-spark.ts | 90 ++ .../task/components/node/tasks/use-sql.ts | 90 ++ .../task/components/node/tasks/use-sqoop.ts | 102 ++ .../components/node/tasks/use-sub-process.ts | 87 ++ .../task/components/node/tasks/use-surveil.ts | 89 ++ .../task/components/node/tasks/use-switch.ts | 79 ++ .../node/tasks/use-whale-sea-tunnel.ts | 87 ++ .../components/node/tasks/use-zeppelin.ts | 81 ++ .../src/views/task/components/node/types.ts | 490 +++++++ .../task/components/node/use-task-config.ts | 177 +++ .../views/task/components/node/use-task.ts | 89 ++ .../coronation/components/import-modal.tsx | 268 ++++ .../task/coronation/components/use-import.ts | 193 +++ .../views/task/coronation/index.module.scss | 31 + .../src/views/task/coronation/index.tsx | 262 ++++ .../src/views/task/coronation/use-table.ts | 274 ++++ .../task/definition/components/move-modal.tsx | 109 ++ .../task/definition/components/use-move.ts | 105 ++ .../task/definition/components/use-version.ts | 212 +++ .../definition/components/version-modal.tsx | 107 ++ .../definition/components/version.module.scss | 22 + .../views/task/definition/index.module.scss | 44 + .../src/views/task/definition/index.tsx | 236 ++++ .../src/views/task/definition/types.ts | 27 + .../src/views/task/definition/use-table.ts | 317 +++++ .../src/views/task/definition/use-task.ts | 108 ++ .../task/instance/index.module.scss} | 14 +- .../src/views/task/instance/index.tsx | 492 +++++++ seatunnel-ui/src/views/task/instance/types.ts | 37 + .../src/views/task/instance/use-table.ts | 725 ++++++++++ .../src/views/task/isolation/detail-modal.tsx | 171 +++ .../views/task/isolation/index.module.scss | 31 + .../src/views/task/isolation/index.tsx | 208 +++ .../src/views/task/isolation/types.ts | 33 + .../src/views/task/isolation/use-columns.ts | 117 ++ .../src/views/task/isolation/use-detail.ts | 112 ++ .../src/views/task/isolation/use-table.ts | 160 +++ .../dag/canvas/dag-data.ts | 62 + .../dag/canvas/dag-setting.ts | 49 + .../dag/canvas/dag-shape.ts | 202 +++ .../dag/canvas/index.module.scss | 59 + .../dag/canvas/index.tsx | 176 +++ .../dag/canvas/node.tsx | 76 ++ .../dag/canvas/use-dag-graph.ts | 79 ++ .../dag/canvas/use-dag-node.ts | 33 + .../dag/canvas/use-dag-resize.ts | 33 + .../synchronization-definition/dag/config.ts | 35 + .../dag/configuration-form.tsx | 306 +++++ .../dag/images/copy.png | Bin 0 -> 330 bytes .../dag/images/field-mapper.png | Bin 0 -> 3306 bytes .../dag/images/filter-event-type.png | Bin 0 -> 1148 bytes .../dag/images/replace.png | Bin 0 -> 3087 bytes .../dag/images/sink.png | Bin 0 -> 3393 bytes .../dag/images/source.png | Bin 0 -> 4025 bytes .../dag/images/spilt.png | Bin 0 -> 337 bytes .../dag/images/sql.png | Bin 0 -> 5193 bytes .../dag/index.module.scss | 21 + .../synchronization-definition/dag/index.tsx | 108 ++ .../dag/node-mode-model.module.scss | 50 + .../dag/node-model.tsx | 142 ++ .../dag/node-setting.tsx | 135 ++ .../dag/sidebar/index.module.scss | 40 + .../dag/sidebar/index.tsx | 128 ++ .../dag/sidebar/use-sidebar.ts | 38 + .../dag/split-modal.tsx | 76 ++ .../dag/toolbar/index.tsx | 240 ++++ .../dag/toolbar/layout-modal.tsx | 120 ++ .../dag/toolbar/task-setting-modal.tsx | 113 ++ .../dag/toolbar/use-task-setting-modal.ts | 123 ++ .../synchronization-definition/dag/types.ts | 61 + .../dag/use-configuration-form.ts | 394 ++++++ .../dag/use-dag-detail.ts | 166 +++ .../dag/use-model-columns.ts | 567 ++++++++ .../dag/use-model.ts | 339 +++++ .../dag/use-node-setting.ts | 341 +++++ .../task/synchronization-definition/index.tsx | 199 +++ .../synchronization-definition/task-modal.tsx | 220 +++ .../test/definitionNodesAndEdgesData.ts | 44 + .../synchronization-definition/test/index.ts | 1 + .../synchronization-definition/use-table.ts | 167 +++ .../use-task-modal.ts | 103 ++ .../detail/dag/dag-setting.ts | 20 + .../detail/dag/index.module.scss | 36 + .../detail/dag/node.tsx | 18 + .../detail/dag/use-dag-add-shape.ts | 157 +++ .../detail/dag/use-dag-edge.ts | 27 + .../detail/dag/use-dag-graph.ts | 48 + .../detail/dag/use-dag-layout.ts | 115 ++ .../dag/use-dag-node-change-position.ts | 93 ++ .../detail/dag/use-dag-node.ts | 33 + .../detail/dag/use-dag-resize.ts | 33 + .../synchronization-instance/detail/index.tsx | 93 ++ .../detail/running-instance.tsx | 64 + .../detail/task-definition.module.scss | 52 + .../detail/task-definition.tsx | 141 ++ .../detail/use-running-instance.ts | 95 ++ .../detail/use-task-definition.ts | 99 ++ .../task/synchronization-instance/index.tsx | 56 + .../synchronization-instance/sync-task.tsx | 419 ++++++ .../synchronization-instance/use-sync-task.ts | 403 ++++++ .../src/views/tasks/list/use-table.ts | 2 +- .../src/views/user-manage/list/use-table.ts | 2 +- .../src/views/virtual-tables/detail.tsx | 16 +- .../src/views/virtual-tables/list/index.tsx | 10 +- .../views/virtual-tables/list/use-table.ts | 30 +- .../views/virtual-tables/step-one-form.tsx | 14 +- .../views/virtual-tables/step-two-form.tsx | 76 +- .../src/views/virtual-tables/types.ts | 14 +- .../src/views/virtual-tables/use-detail.ts | 93 +- 306 files changed, 32503 insertions(+), 315 deletions(-) create mode 100644 seatunnel-ui/src/assets/styles/default.scss create mode 100644 seatunnel-ui/src/common/column-width-config.ts create mode 100644 seatunnel-ui/src/common/common.ts create mode 100644 seatunnel-ui/src/common/types.ts create mode 100644 seatunnel-ui/src/components/button-link/index.module.scss create mode 100644 seatunnel-ui/src/components/button-link/index.tsx create mode 100644 seatunnel-ui/src/components/card/index.tsx create mode 100644 seatunnel-ui/src/components/checkbox-tree/index.module.scss create mode 100644 seatunnel-ui/src/components/checkbox-tree/index.tsx create mode 100644 seatunnel-ui/src/components/log-modal/index.module.scss create mode 100644 seatunnel-ui/src/components/log-modal/index.tsx create mode 100644 seatunnel-ui/src/components/resource-auth/index.tsx create mode 100644 seatunnel-ui/src/directives/index.ts create mode 100644 seatunnel-ui/src/directives/permission.ts create mode 100644 seatunnel-ui/src/hooks/index.ts create mode 100644 seatunnel-ui/src/hooks/use-client-config.ts create mode 100644 seatunnel-ui/src/hooks/use-permission-length.ts create mode 100644 seatunnel-ui/src/hooks/use-source-type.ts create mode 100644 seatunnel-ui/src/hooks/use-table-link.ts create mode 100644 seatunnel-ui/src/hooks/use-table-operation.ts create mode 100644 seatunnel-ui/src/hooks/use-text-copy.ts create mode 100644 seatunnel-ui/src/layouts/dashboard/sidebar/index.module.scss create mode 100644 seatunnel-ui/src/layouts/dashboard/sidebar/index.tsx create mode 100644 seatunnel-ui/src/locales/en_US/hook.ts create mode 100644 seatunnel-ui/src/locales/en_US/project.ts create mode 100644 seatunnel-ui/src/locales/en_US/theme.ts create mode 100644 seatunnel-ui/src/locales/zh_CN/hook.ts create mode 100644 seatunnel-ui/src/locales/zh_CN/project.ts create mode 100644 seatunnel-ui/src/locales/zh_CN/theme.ts create mode 100644 seatunnel-ui/src/router/sync-task.ts create mode 100644 seatunnel-ui/src/service/data-source/index.ts rename seatunnel-ui/src/service/{datasource => data-source}/types.ts (62%) create mode 100644 seatunnel-ui/src/service/log/index.ts create mode 100644 seatunnel-ui/src/service/log/types.ts create mode 100644 seatunnel-ui/src/service/resources/index.ts create mode 100644 seatunnel-ui/src/service/resources/types.ts create mode 100644 seatunnel-ui/src/service/sync-task-definition/index.ts rename seatunnel-ui/src/service/{datasource => sync-task-instance}/index.ts (54%) create mode 100644 seatunnel-ui/src/service/task-instances/index.ts create mode 100644 seatunnel-ui/src/service/task-instances/types.ts create mode 100644 seatunnel-ui/src/service/virtual-table/index.ts create mode 100644 seatunnel-ui/src/service/virtual-table/types.ts create mode 100644 seatunnel-ui/src/store/datasource/form-structures.ts create mode 100644 seatunnel-ui/src/store/project/index.ts create mode 100644 seatunnel-ui/src/store/project/task-node.ts create mode 100644 seatunnel-ui/src/store/project/task-type.ts create mode 100644 seatunnel-ui/src/store/project/types.ts create mode 100644 seatunnel-ui/src/store/synchronization-definition/index.ts create mode 100644 seatunnel-ui/src/store/synchronization-definition/types.ts create mode 100644 seatunnel-ui/src/themes/modules/dark-blue.ts create mode 100644 seatunnel-ui/src/themes/type.ts create mode 100644 seatunnel-ui/src/utils/clipboard.ts create mode 100644 seatunnel-ui/src/utils/timePickeroption.ts create mode 100644 seatunnel-ui/src/views/datasource/list/types.ts create mode 100644 seatunnel-ui/src/views/datasource/list/use-source.ts create mode 100644 seatunnel-ui/src/views/projects/components/column-selector.tsx create mode 100644 seatunnel-ui/src/views/projects/components/index.scss create mode 100644 seatunnel-ui/src/views/projects/components/jumpProject.ts create mode 100644 seatunnel-ui/src/views/projects/components/projectSelector.tsx create mode 100644 seatunnel-ui/src/views/projects/utils/changeProject.ts create mode 100644 seatunnel-ui/src/views/projects/utils/dealColumns.ts create mode 100644 seatunnel-ui/src/views/projects/utils/getProjectCodes.ts create mode 100644 seatunnel-ui/src/views/projects/utils/storeColums.ts rename seatunnel-ui/src/{service/virtual-tables/types.ts => views/setting/theme/index.module.scss} (96%) create mode 100644 seatunnel-ui/src/views/setting/theme/index.tsx create mode 100644 seatunnel-ui/src/views/setting/theme/use-theme.ts create mode 100644 seatunnel-ui/src/views/task/components/node/detail-modal.tsx create mode 100644 seatunnel-ui/src/views/task/components/node/detail.tsx create mode 100644 seatunnel-ui/src/views/task/components/node/fields/index.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-child-node.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-chunjun-deploy-mode.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-chunjun.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-conditions.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-custom-params.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-datasource.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-datax.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-delay-time.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-dependent-timeout.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-dependent.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-deploy-mode.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-description.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-dinky.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-driver-cores.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-driver-memory.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-dvc.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-dynamic-datasource.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-emr.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-environment-name.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-executor-cores.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-executor-memory.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-executor-number.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-failed.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-flink.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-hive-cli.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-http.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-informatica-item.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-jar.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-jupyter.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-main-jar.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-mlflow-models.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-mlflow-projects.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-mlflow.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-mr.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-name.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-next-loop.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-openmldb.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-pre-tasks.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-process-name.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-pytorch.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-relation-custom-params.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-remote-connection.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-resources.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-rules.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-run-flag.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-sagemaker.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-sea-tunnel.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-shell.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-spark.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-sql-utils.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-sql.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-sqoop-source-type.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-sqoop-target-type.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-sqoop.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-surveil.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-switch.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-target-task-name.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-task-group.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-task-priority.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-task-type.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-timeout-alarm.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-udfs.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-whale-sea-tunnel.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-worker-group.ts create mode 100644 seatunnel-ui/src/views/task/components/node/fields/use-zeppelin.ts create mode 100644 seatunnel-ui/src/views/task/components/node/format-data.ts create mode 100644 seatunnel-ui/src/views/task/components/node/index.module.scss create mode 100644 seatunnel-ui/src/views/task/components/node/tasks/index.ts create mode 100644 seatunnel-ui/src/views/task/components/node/tasks/use-card.ts create mode 100644 seatunnel-ui/src/views/task/components/node/tasks/use-chunjun.ts create mode 100644 seatunnel-ui/src/views/task/components/node/tasks/use-conditions.ts create mode 100644 seatunnel-ui/src/views/task/components/node/tasks/use-data-quality.ts create mode 100644 seatunnel-ui/src/views/task/components/node/tasks/use-datax.ts create mode 100644 seatunnel-ui/src/views/task/components/node/tasks/use-dependent.ts create mode 100644 seatunnel-ui/src/views/task/components/node/tasks/use-dinky.ts create mode 100644 seatunnel-ui/src/views/task/components/node/tasks/use-dvc.ts create mode 100644 seatunnel-ui/src/views/task/components/node/tasks/use-emr.ts create mode 100644 seatunnel-ui/src/views/task/components/node/tasks/use-flink.ts create mode 100644 seatunnel-ui/src/views/task/components/node/tasks/use-hive-cli.ts create mode 100644 seatunnel-ui/src/views/task/components/node/tasks/use-http.ts create mode 100644 seatunnel-ui/src/views/task/components/node/tasks/use-informatica.ts create mode 100644 seatunnel-ui/src/views/task/components/node/tasks/use-jar.ts create mode 100644 seatunnel-ui/src/views/task/components/node/tasks/use-jupyter.ts create mode 100644 seatunnel-ui/src/views/task/components/node/tasks/use-mlflow.ts create mode 100644 seatunnel-ui/src/views/task/components/node/tasks/use-mr.ts create mode 100644 seatunnel-ui/src/views/task/components/node/tasks/use-openmldb.ts create mode 100644 seatunnel-ui/src/views/task/components/node/tasks/use-pigeon.ts create mode 100644 seatunnel-ui/src/views/task/components/node/tasks/use-procedure.ts create mode 100644 seatunnel-ui/src/views/task/components/node/tasks/use-python.ts create mode 100644 seatunnel-ui/src/views/task/components/node/tasks/use-pytorch.ts create mode 100644 seatunnel-ui/src/views/task/components/node/tasks/use-sagemaker.ts create mode 100644 seatunnel-ui/src/views/task/components/node/tasks/use-sea-tunnel.ts create mode 100644 seatunnel-ui/src/views/task/components/node/tasks/use-shell.ts create mode 100644 seatunnel-ui/src/views/task/components/node/tasks/use-spark.ts create mode 100644 seatunnel-ui/src/views/task/components/node/tasks/use-sql.ts create mode 100644 seatunnel-ui/src/views/task/components/node/tasks/use-sqoop.ts create mode 100644 seatunnel-ui/src/views/task/components/node/tasks/use-sub-process.ts create mode 100644 seatunnel-ui/src/views/task/components/node/tasks/use-surveil.ts create mode 100644 seatunnel-ui/src/views/task/components/node/tasks/use-switch.ts create mode 100644 seatunnel-ui/src/views/task/components/node/tasks/use-whale-sea-tunnel.ts create mode 100644 seatunnel-ui/src/views/task/components/node/tasks/use-zeppelin.ts create mode 100644 seatunnel-ui/src/views/task/components/node/types.ts create mode 100644 seatunnel-ui/src/views/task/components/node/use-task-config.ts create mode 100644 seatunnel-ui/src/views/task/components/node/use-task.ts create mode 100644 seatunnel-ui/src/views/task/coronation/components/import-modal.tsx create mode 100644 seatunnel-ui/src/views/task/coronation/components/use-import.ts create mode 100644 seatunnel-ui/src/views/task/coronation/index.module.scss create mode 100644 seatunnel-ui/src/views/task/coronation/index.tsx create mode 100644 seatunnel-ui/src/views/task/coronation/use-table.ts create mode 100644 seatunnel-ui/src/views/task/definition/components/move-modal.tsx create mode 100644 seatunnel-ui/src/views/task/definition/components/use-move.ts create mode 100644 seatunnel-ui/src/views/task/definition/components/use-version.ts create mode 100644 seatunnel-ui/src/views/task/definition/components/version-modal.tsx create mode 100644 seatunnel-ui/src/views/task/definition/components/version.module.scss create mode 100644 seatunnel-ui/src/views/task/definition/index.module.scss create mode 100644 seatunnel-ui/src/views/task/definition/index.tsx create mode 100644 seatunnel-ui/src/views/task/definition/types.ts create mode 100644 seatunnel-ui/src/views/task/definition/use-table.ts create mode 100644 seatunnel-ui/src/views/task/definition/use-task.ts rename seatunnel-ui/src/{service/virtual-tables/index.ts => views/task/instance/index.module.scss} (81%) create mode 100644 seatunnel-ui/src/views/task/instance/index.tsx create mode 100644 seatunnel-ui/src/views/task/instance/types.ts create mode 100644 seatunnel-ui/src/views/task/instance/use-table.ts create mode 100644 seatunnel-ui/src/views/task/isolation/detail-modal.tsx create mode 100644 seatunnel-ui/src/views/task/isolation/index.module.scss create mode 100644 seatunnel-ui/src/views/task/isolation/index.tsx create mode 100644 seatunnel-ui/src/views/task/isolation/types.ts create mode 100644 seatunnel-ui/src/views/task/isolation/use-columns.ts create mode 100644 seatunnel-ui/src/views/task/isolation/use-detail.ts create mode 100644 seatunnel-ui/src/views/task/isolation/use-table.ts create mode 100644 seatunnel-ui/src/views/task/synchronization-definition/dag/canvas/dag-data.ts create mode 100644 seatunnel-ui/src/views/task/synchronization-definition/dag/canvas/dag-setting.ts create mode 100644 seatunnel-ui/src/views/task/synchronization-definition/dag/canvas/dag-shape.ts create mode 100644 seatunnel-ui/src/views/task/synchronization-definition/dag/canvas/index.module.scss create mode 100644 seatunnel-ui/src/views/task/synchronization-definition/dag/canvas/index.tsx create mode 100644 seatunnel-ui/src/views/task/synchronization-definition/dag/canvas/node.tsx create mode 100644 seatunnel-ui/src/views/task/synchronization-definition/dag/canvas/use-dag-graph.ts create mode 100644 seatunnel-ui/src/views/task/synchronization-definition/dag/canvas/use-dag-node.ts create mode 100644 seatunnel-ui/src/views/task/synchronization-definition/dag/canvas/use-dag-resize.ts create mode 100644 seatunnel-ui/src/views/task/synchronization-definition/dag/config.ts create mode 100644 seatunnel-ui/src/views/task/synchronization-definition/dag/configuration-form.tsx create mode 100644 seatunnel-ui/src/views/task/synchronization-definition/dag/images/copy.png create mode 100644 seatunnel-ui/src/views/task/synchronization-definition/dag/images/field-mapper.png create mode 100644 seatunnel-ui/src/views/task/synchronization-definition/dag/images/filter-event-type.png create mode 100644 seatunnel-ui/src/views/task/synchronization-definition/dag/images/replace.png create mode 100644 seatunnel-ui/src/views/task/synchronization-definition/dag/images/sink.png create mode 100644 seatunnel-ui/src/views/task/synchronization-definition/dag/images/source.png create mode 100644 seatunnel-ui/src/views/task/synchronization-definition/dag/images/spilt.png create mode 100644 seatunnel-ui/src/views/task/synchronization-definition/dag/images/sql.png create mode 100644 seatunnel-ui/src/views/task/synchronization-definition/dag/index.module.scss create mode 100644 seatunnel-ui/src/views/task/synchronization-definition/dag/index.tsx create mode 100644 seatunnel-ui/src/views/task/synchronization-definition/dag/node-mode-model.module.scss create mode 100644 seatunnel-ui/src/views/task/synchronization-definition/dag/node-model.tsx create mode 100644 seatunnel-ui/src/views/task/synchronization-definition/dag/node-setting.tsx create mode 100644 seatunnel-ui/src/views/task/synchronization-definition/dag/sidebar/index.module.scss create mode 100644 seatunnel-ui/src/views/task/synchronization-definition/dag/sidebar/index.tsx create mode 100644 seatunnel-ui/src/views/task/synchronization-definition/dag/sidebar/use-sidebar.ts create mode 100644 seatunnel-ui/src/views/task/synchronization-definition/dag/split-modal.tsx create mode 100644 seatunnel-ui/src/views/task/synchronization-definition/dag/toolbar/index.tsx create mode 100644 seatunnel-ui/src/views/task/synchronization-definition/dag/toolbar/layout-modal.tsx create mode 100644 seatunnel-ui/src/views/task/synchronization-definition/dag/toolbar/task-setting-modal.tsx create mode 100644 seatunnel-ui/src/views/task/synchronization-definition/dag/toolbar/use-task-setting-modal.ts create mode 100644 seatunnel-ui/src/views/task/synchronization-definition/dag/types.ts create mode 100644 seatunnel-ui/src/views/task/synchronization-definition/dag/use-configuration-form.ts create mode 100644 seatunnel-ui/src/views/task/synchronization-definition/dag/use-dag-detail.ts create mode 100644 seatunnel-ui/src/views/task/synchronization-definition/dag/use-model-columns.ts create mode 100644 seatunnel-ui/src/views/task/synchronization-definition/dag/use-model.ts create mode 100644 seatunnel-ui/src/views/task/synchronization-definition/dag/use-node-setting.ts create mode 100644 seatunnel-ui/src/views/task/synchronization-definition/index.tsx create mode 100644 seatunnel-ui/src/views/task/synchronization-definition/task-modal.tsx create mode 100644 seatunnel-ui/src/views/task/synchronization-definition/test/definitionNodesAndEdgesData.ts create mode 100644 seatunnel-ui/src/views/task/synchronization-definition/test/index.ts create mode 100644 seatunnel-ui/src/views/task/synchronization-definition/use-table.ts create mode 100644 seatunnel-ui/src/views/task/synchronization-definition/use-task-modal.ts create mode 100644 seatunnel-ui/src/views/task/synchronization-instance/detail/dag/dag-setting.ts create mode 100644 seatunnel-ui/src/views/task/synchronization-instance/detail/dag/index.module.scss create mode 100644 seatunnel-ui/src/views/task/synchronization-instance/detail/dag/node.tsx create mode 100644 seatunnel-ui/src/views/task/synchronization-instance/detail/dag/use-dag-add-shape.ts create mode 100644 seatunnel-ui/src/views/task/synchronization-instance/detail/dag/use-dag-edge.ts create mode 100644 seatunnel-ui/src/views/task/synchronization-instance/detail/dag/use-dag-graph.ts create mode 100644 seatunnel-ui/src/views/task/synchronization-instance/detail/dag/use-dag-layout.ts create mode 100644 seatunnel-ui/src/views/task/synchronization-instance/detail/dag/use-dag-node-change-position.ts create mode 100644 seatunnel-ui/src/views/task/synchronization-instance/detail/dag/use-dag-node.ts create mode 100644 seatunnel-ui/src/views/task/synchronization-instance/detail/dag/use-dag-resize.ts create mode 100644 seatunnel-ui/src/views/task/synchronization-instance/detail/index.tsx create mode 100644 seatunnel-ui/src/views/task/synchronization-instance/detail/running-instance.tsx create mode 100644 seatunnel-ui/src/views/task/synchronization-instance/detail/task-definition.module.scss create mode 100644 seatunnel-ui/src/views/task/synchronization-instance/detail/task-definition.tsx create mode 100644 seatunnel-ui/src/views/task/synchronization-instance/detail/use-running-instance.ts create mode 100644 seatunnel-ui/src/views/task/synchronization-instance/detail/use-task-definition.ts create mode 100644 seatunnel-ui/src/views/task/synchronization-instance/index.tsx create mode 100644 seatunnel-ui/src/views/task/synchronization-instance/sync-task.tsx create mode 100644 seatunnel-ui/src/views/task/synchronization-instance/use-sync-task.ts diff --git a/seatunnel-ui/package.json b/seatunnel-ui/package.json index 5535b7330..c0ae6853e 100644 --- a/seatunnel-ui/package.json +++ b/seatunnel-ui/package.json @@ -9,6 +9,9 @@ "prettier": "prettier --write \"src/**/*.{vue,ts,tsx}\"" }, "dependencies": { + "@antv/layout": "0.1.31", + "@antv/x6": "1.30.1", + "@antv/x6-vue-shape": "1.5.3", "@vueuse/core": "^9.13.0", "autoprefixer": "^10.4.13", "axios": "^1.3.4", @@ -22,6 +25,7 @@ "pinia": "^2.0.32", "pinia-plugin-persistedstate": "^3.1.0", "postcss": "^8.4.21", + "screenfull": "6.0.1", "tailwindcss": "^3.2.7", "vfonts": "^0.0.3", "vue": "^3.2.47", diff --git a/seatunnel-ui/src/App.tsx b/seatunnel-ui/src/App.tsx index 415f1ceb7..cd9ecf549 100644 --- a/seatunnel-ui/src/App.tsx +++ b/seatunnel-ui/src/App.tsx @@ -39,11 +39,9 @@ const App = defineComponent({ const themeStore = useThemeStore() const settingStore = useSettingStore() const currentTheme = computed(() => - themeStore.darkTheme ? darkTheme : undefined - ) - const themeOverrides: Ref = ref( - themeList[currentTheme.value ? 'dark' : 'light'] + themeStore.getDarkTheme ? darkTheme : undefined ) + const themeOverrides = computed(() => themeList[currentTheme.value ? 'dark' : 'light']) const setBorderRadius = (v: number) => { (themeOverrides.value.common as Partial).borderRadius = v + 'px' @@ -66,7 +64,8 @@ const App = defineComponent({ return { settingStore, currentTheme, - themeOverrides + themeOverrides, + themeList } }, render() { diff --git a/seatunnel-ui/src/assets/styles/default.scss b/seatunnel-ui/src/assets/styles/default.scss new file mode 100644 index 000000000..374169edc --- /dev/null +++ b/seatunnel-ui/src/assets/styles/default.scss @@ -0,0 +1,33 @@ +/* + * 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. + */ + + +.x6-graph-scroller { + overflow: hidden !important; + &:hover { + overflow: scroll !important; + } +} +.x6-graph-scroller::-webkit-scrollbar { + width: 5px; + height: 5px; +} +.x6-graph-scroller::-webkit-scrollbar-thumb { + border-radius: 5px; + transition: all 0.2s; + background-color: rgba(0, 0, 0, 0.25); +} diff --git a/seatunnel-ui/src/common/column-width-config.ts b/seatunnel-ui/src/common/column-width-config.ts new file mode 100644 index 000000000..d980945c1 --- /dev/null +++ b/seatunnel-ui/src/common/column-width-config.ts @@ -0,0 +1,130 @@ +/* + * 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 { isNumber, sumBy } from 'lodash' +import type { + TableColumns, + CommonColumnInfo +} from 'naive-ui/es/data-table/src/interface' + +export const COLUMN_WIDTH_CONFIG = { + selection: { + width: 50 + }, + index: { + width: 50 + }, + name: { + width: 180, + ellipsis: { + tooltip: true + } + }, + link_name: { + width: 400 + }, + state: { + width: 120 + }, + type: { + width: 130 + }, + version: { + width: 80 + }, + time: { + width: 180 + }, + timeZone: { + width: 220 + }, + homeColumn: { + width: 80 + }, + home_project_name: { + width: 450 + }, + projectName: { + minWidth: 100 + }, + operation: (number: number): CommonColumnInfo => ({ + fixed: 'right', + width: Math.max(30 * number + 12 * (number - 1) + 24, 100) + }), + processDefinitionName: { + width: 120, + ellipsis: { + tooltip: true + } + }, + userName: { + width: 120, + ellipsis: { + tooltip: true + } + }, + ruleType: { + width: 120 + }, + note: { + width: 180, + ellipsis: { + tooltip: true + } + }, + dryRun: { + width: 140 + }, + times: { + width: 120 + }, + duration: { + width: 120 + }, + yesOrNo: { + width: 100, + ellipsis: { + tooltip: true + } + }, + size: { + width: 100 + }, + tag: { + width: 160 + }, + step: { + width: 160 + }, + copy: { + width: 50 + }, + url: { + width: 180 + }, + description: { + width: 180, + ellipsis: { + tooltip: true + } + } +} + +export const calculateTableWidth = (columns: TableColumns) => + sumBy(columns, (column) => (isNumber(column.width) ? column.width : 0)) + +export const DefaultTableWidth = 1800 diff --git a/seatunnel-ui/src/common/common.ts b/seatunnel-ui/src/common/common.ts new file mode 100644 index 000000000..c26ff6b57 --- /dev/null +++ b/seatunnel-ui/src/common/common.ts @@ -0,0 +1,419 @@ +/* + * 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 { + SettingFilled, + SettingOutlined, + CloseCircleOutlined, + PauseCircleOutlined, + CheckCircleOutlined, + EditOutlined, + MinusCircleOutlined, + CheckCircleFilled, + Loading3QuartersOutlined, + PauseCircleFilled, + ClockCircleOutlined, + StopFilled, + StopOutlined, + GlobalOutlined, + IssuesCloseOutlined, + SendOutlined, + PlayCircleOutlined, + DeleteRowOutlined +} from '@vicons/antd' +import { format, parseISO } from 'date-fns' +import _ from 'lodash' +import { ITaskStateConfig } from './types' + +/** + * Intelligent display kb m + */ +export const bytesToSize = (bytes: number) => { + if (bytes === 0) return '0 B' + const k = 1024 // or 1024 + const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] + const i = Math.floor(Math.log(bytes) / Math.log(k)) + + return parseFloat((bytes / Math.pow(k, i)).toPrecision(3)) + ' ' + sizes[i] +} + +export const fileTypeArr = [ + 'txt', + 'log', + 'sh', + 'bat', + 'conf', + 'cfg', + 'py', + 'java', + 'sql', + 'xml', + 'hql', + 'properties', + 'json', + 'yml', + 'yaml', + 'ini', + 'js' +] + +/** + * Operation type + * @desc tooltip + * @code identifier + */ +export const runningType = (t: any) => [ + { + desc: `${t('project.workflow.start_process')}`, + code: 'START_PROCESS' + }, + { + desc: `${t('project.workflow.start_from_state_clean_tasks')}`, + code: 'START_FROM_STATE_CLEAN_TASKS' + }, + { + desc: `${t('project.workflow.execute_from_the_current_node')}`, + code: 'START_CURRENT_TASK_PROCESS' + }, + { + desc: `${t('project.workflow.recover_tolerance_fault_process')}`, + code: 'RECOVER_TOLERANCE_FAULT_PROCESS' + }, + { + desc: `${t('project.workflow.resume_the_suspension_process')}`, + code: 'RECOVER_SUSPENDED_PROCESS' + }, + { + desc: `${t('project.workflow.execute_from_the_failed_nodes')}`, + code: 'START_FAILURE_TASK_PROCESS' + }, + { + desc: `${t('project.workflow.complement_data')}`, + code: 'COMPLEMENT_DATA' + }, + { + desc: `${t('project.workflow.scheduling_execution')}`, + code: 'SCHEDULER' + }, + { + desc: `${t('project.workflow.rerun')}`, + code: 'REPEAT_RUNNING' + }, + { + desc: `${t('project.workflow.pause')}`, + code: 'PAUSE' + }, + { + desc: `${t('project.workflow.stop')}`, + code: 'STOP' + }, + { + desc: `${t('project.workflow.recovery_waiting_thread')}`, + code: 'RECOVER_WAITING_THREAD' + }, + { + desc: `${t('project.workflow.recover_serial_wait')}`, + code: 'RECOVER_SERIAL_WAIT' + }, + { + desc: `${t('project.workflow.dependent_chain_rerun')}`, + code: 'DEPENDENT_CHAIN_RERUN' + }, + { + desc: `${t('project.workflow.dependent_chain_recovery')}`, + code: 'DEPENDENT_CHAIN_RECOVERY' + } +] + +/** + * State code table + */ +export const stateType = (t: any) => [ + { + value: '', + label: `${t('project.workflow.all_status')}` + }, + ...Object.entries(tasksState(t)).map(([key, item]) => ({ + value: key, + label: item.desc + })) +] + +/** + * Task status + * @id id + * @desc tooltip + * @color color + * @icon icon + * @isSpin is loading (Need to execute the code block to write if judgment) + */ +export const tasksState = (t: any): ITaskStateConfig => ({ + SUBMITTED_SUCCESS: { + id: 0, + desc: `${t('project.workflow.submit_success')}`, + color: '#A9A9A9', + icon: IssuesCloseOutlined, + isSpin: false, + classNames: 'submitted_success' + }, + RUNNING_EXECUTION: { + id: 1, + desc: `${t('project.workflow.executing')}`, + color: '#0097e0', + icon: SettingFilled, + isSpin: true, + classNames: 'running_execution' + }, + READY_PAUSE: { + id: 2, + desc: `${t('project.workflow.ready_to_pause')}`, + color: '#07b1a3', + icon: SettingOutlined, + isSpin: false, + classNames: 'ready_pause' + }, + PAUSE: { + id: 3, + desc: `${t('project.workflow.pause')}`, + color: '#057c72', + icon: PauseCircleOutlined, + isSpin: false, + classNames: 'pause' + }, + READY_STOP: { + id: 4, + desc: `${t('project.workflow.ready_to_stop')}`, + color: '#FE0402', + icon: StopFilled, + isSpin: false, + classNames: 'ready_stop' + }, + STOP: { + id: 5, + desc: `${t('project.workflow.stop')}`, + color: '#e90101', + icon: StopOutlined, + isSpin: false, + classNames: 'stop' + }, + FAILURE: { + id: 6, + desc: `${t('project.workflow.failed')}`, + color: '#000000', + icon: CloseCircleOutlined, + isSpin: false, + classNames: 'failed' + }, + SUCCESS: { + id: 7, + desc: `${t('project.workflow.success')}`, + color: '#95DF96', + icon: CheckCircleOutlined, + isSpin: false, + classNames: 'success' + }, + NEED_FAULT_TOLERANCE: { + id: 8, + desc: `${t('project.workflow.need_fault_tolerance')}`, + color: '#FF8C00', + icon: EditOutlined, + isSpin: false, + classNames: 'need_fault_tolerance' + }, + KILL: { + id: 9, + desc: `${t('project.workflow.kill')}`, + color: '#a70202', + icon: MinusCircleOutlined, + isSpin: false, + classNames: 'kill' + }, + WAITING_THREAD: { + id: 10, + desc: `${t('project.workflow.waiting_for_thread')}`, + color: '#912eed', + icon: ClockCircleOutlined, + isSpin: false, + classNames: 'waiting_thread' + }, + WAITING_DEPEND: { + id: 11, + desc: `${t('project.workflow.waiting_for_dependence')}`, + color: '#5101be', + icon: GlobalOutlined, + isSpin: false, + classNames: 'waiting_depend' + }, + DELAY_EXECUTION: { + id: 12, + desc: `${t('project.workflow.delay_execution')}`, + color: '#5102ce', + icon: PauseCircleFilled, + isSpin: false, + classNames: 'delay_execution' + }, + FORCED_SUCCESS: { + id: 13, + desc: `${t('project.workflow.forced_success')}`, + color: '#5102ce', + icon: CheckCircleFilled, + isSpin: false, + classNames: 'forced_success' + }, + SERIAL_WAIT: { + id: 14, + desc: `${t('project.workflow.serial_wait')}`, + color: '#5102ce', + icon: Loading3QuartersOutlined, + isSpin: true, + classNames: 'serial_wait' + }, + DISPATCH: { + id: 15, + desc: `${t('project.workflow.dispatch')}`, + color: '#5101be', + icon: SendOutlined, + isSpin: false, + classNames: 'dispatch' + }, + PAUSE_BY_ISOLATION: { + id: 16, + desc: `${t('project.overview.pause_by_isolation')}`, + color: '#057c72', + icon: PlayCircleOutlined, + isSpin: false, + classNames: 'pause_by_isolation' + }, + KILL_BY_ISOLATION: { + id: 17, + desc: `${t('project.overview.kill_by_isolation')}`, + color: '#a70202', + icon: DeleteRowOutlined, + isSpin: false, + classNames: 'kill_by_isolation' + }, + PAUSE_BY_CORONATION: { + id: 18, + desc: `${t('project.overview.pause_by_coronation')}`, + color: '#07b1a3', + icon: PlayCircleOutlined, + isSpin: false, + classNames: 'pause_by_coronation' + }, + FORBIDDEN_BY_CORONATION: { + id: 19, + desc: `${t('project.overview.forbidden_by_coronation')}`, + color: '#5102ce', + icon: CheckCircleOutlined, + isSpin: false, + classNames: 'forbidden_by_coronation' + } +}) + +/** + * A simple uuid generator, support prefix and template pattern. + * + * @example + * + * uuid('v-') // -> v-xxx + * uuid('v-ani-%{s}-translate') // -> v-ani-xxx + */ +export function uuid(prefix: string) { + const id = Math.floor(Math.random() * 10000).toString(36) + return prefix + ? ~prefix.indexOf('%{s}') + ? prefix.replace(/%\{s\}/g, id) + : prefix + id + : id +} + +export const warningTypeList = [ + { + id: 'NONE', + code: 'project.workflow.none_send' + }, + { + id: 'SUCCESS', + code: 'project.workflow.success_send' + }, + { + id: 'FAILURE', + code: 'project.workflow.failure_send' + }, + { + id: 'ALL', + code: 'project.workflow.all_send' + } +] + +export const parseTime = (dateTime: string | number) => { + if (_.isString(dateTime) === true) { + return parseISO(dateTime as string) + } else { + return new Date(dateTime) + } +} + +export const renderTableTime = (dateTime: string | null | undefined) => { + if (dateTime) { + return format(parseTime(dateTime), 'yyyy-MM-dd HH:mm:ss') + } else { + return '-' + } +} + +// cp Spring2304requirements +export const taskGroupQueueState = (t: any) => [ + { + label: `${t('resource.task_group_queue.state.all')}`, + value: '' + }, + { + label: `${t('resource.task_group_queue.state.WAIT_QUEUE')}`, + value: '-1' + }, + { + label: `${t('resource.task_group_queue.state.ACQUIRE_SUCCESS')}`, + value: '1' + }, + { + label: `${t('resource.task_group_queue.state.RELEASE')}`, + value: '2' + } +] +export const BaselineStatus = (t: any): { [key: string]: string } => ({ + SAFE: t('security.baseline.save'), + DANGEROUS: t('security.baseline.dangerous'), + BREAK_BASELINE: t('security.baseline.break_line') +}) + +export function dateType(t: any) { + return [ + { + label: t('project.workflow.start_time'), + value: 'START_END' + }, + { + label: t('project.workflow.scheduling_time'), + value: 'SCHEDULE' + }, + { + label: t('project.workflow.card_value'), + value: 'DATA_CARD' + } + ] +} diff --git a/seatunnel-ui/src/common/types.ts b/seatunnel-ui/src/common/types.ts new file mode 100644 index 000000000..fa4da7a6d --- /dev/null +++ b/seatunnel-ui/src/common/types.ts @@ -0,0 +1,50 @@ +/* + * 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 { Component } from 'vue' + +export type ITaskState = + | 'SUBMITTED_SUCCESS' + | 'RUNNING_EXECUTION' + | 'READY_PAUSE' + | 'PAUSE' + | 'READY_STOP' + | 'STOP' + | 'FAILURE' + | 'SUCCESS' + | 'NEED_FAULT_TOLERANCE' + | 'KILL' + | 'WAITING_THREAD' + | 'WAITING_DEPEND' + | 'DELAY_EXECUTION' + | 'FORCED_SUCCESS' + | 'SERIAL_WAIT' + | 'DISPATCH' + | 'PAUSE_BY_ISOLATION' + | 'KILL_BY_ISOLATION' + | 'PAUSE_BY_CORONATION' + | 'FORBIDDEN_BY_CORONATION' + +export type ITaskStateConfig = { + [key in ITaskState]: { + id: number + desc: string + color: string + icon: Component + isSpin: boolean + classNames?: string + } +} diff --git a/seatunnel-ui/src/components/button-link/index.module.scss b/seatunnel-ui/src/components/button-link/index.module.scss new file mode 100644 index 000000000..ebb87a5f1 --- /dev/null +++ b/seatunnel-ui/src/components/button-link/index.module.scss @@ -0,0 +1,26 @@ +/* + * 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. + */ +.button-link { + cursor: pointer; + color: var(--n-color-target); + line-height: normal; + + &:hover { + text-decoration: none; + box-shadow: 0 1px; + } +} diff --git a/seatunnel-ui/src/components/button-link/index.tsx b/seatunnel-ui/src/components/button-link/index.tsx new file mode 100644 index 000000000..2ac80badd --- /dev/null +++ b/seatunnel-ui/src/components/button-link/index.tsx @@ -0,0 +1,57 @@ +/* + * 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, renderSlot } from 'vue' +import { NButton } from 'naive-ui' +import styles from './index.module.scss' + +const props = { + disabled: { + type: Boolean, + default: false + }, + iconPlacement: { + type: String as PropType<'left' | 'right'>, + default: 'left' + } +} + +const ButtonLink = defineComponent({ + name: 'button-link', + props, + emits: ['click'], + setup(props, { slots, emit }) { + const onClick = (ev: MouseEvent) => { + emit('click', ev) + } + return () => ( + + {{ + default: () => renderSlot(slots, 'default'), + icon: () => renderSlot(slots, 'icon') + }} + + ) + } +}) + +export default ButtonLink diff --git a/seatunnel-ui/src/components/card/index.tsx b/seatunnel-ui/src/components/card/index.tsx new file mode 100644 index 000000000..8418ffc96 --- /dev/null +++ b/seatunnel-ui/src/components/card/index.tsx @@ -0,0 +1,67 @@ +/* + * 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 { CSSProperties, defineComponent, PropType } from 'vue' +import { NCard } from 'naive-ui' + +const headerStyle = { + borderBottom: '1px solid var(--n-border-color)' +} + +const contentStyle = { + padding: '8px 10px' +} + +const headerExtraStyle = {} + +const props = { + title: { + type: String as PropType + }, + headerStyle: { + type: String as PropType + }, + headerExtraStyle: { + type: String as PropType + }, + contentStyle: { + type: String as PropType + } +} + +const Card = defineComponent({ + name: 'Card', + props, + render() { + const { title, $slots } = this + return ( + + {$slots} + + ) + } +}) + +export default Card diff --git a/seatunnel-ui/src/components/checkbox-tree/index.module.scss b/seatunnel-ui/src/components/checkbox-tree/index.module.scss new file mode 100644 index 000000000..b4341403c --- /dev/null +++ b/seatunnel-ui/src/components/checkbox-tree/index.module.scss @@ -0,0 +1,24 @@ +/* + * 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. + */ + +.tree { + :global { + .n-tree-node-switcher { + display: none; + } + } +} \ No newline at end of file diff --git a/seatunnel-ui/src/components/checkbox-tree/index.tsx b/seatunnel-ui/src/components/checkbox-tree/index.tsx new file mode 100644 index 000000000..563ff78ef --- /dev/null +++ b/seatunnel-ui/src/components/checkbox-tree/index.tsx @@ -0,0 +1,208 @@ +/* + * 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 { + NCard, + NSpace, + NTree, + NButton, + NInput, + NSwitch, + NDivider, + NIcon +} from 'naive-ui' +import { computed, defineComponent, PropType, ref } from 'vue' +import { TreeOption } from 'naive-ui' +import { useI18n } from 'vue-i18n' +import styles from './index.module.scss' +import { useUserStore } from '@/store/user' +import { SearchOutlined } from '@vicons/antd' + +const props = { + data: { + type: Object as PropType, + default: [] + }, + defaultChecked: { + type: Object as PropType, + default: [] + }, + globalResource: { + type: Boolean as PropType, + default: false + }, + showGlobalResource: { + type: Boolean as PropType, + default: true + }, + isResource: { + type: Boolean as PropType, + default: false + }, + row: { + type: Object as any, + default: {} + } +} + +const CheckboxTree = defineComponent({ + name: 'checkbox-tree', + props, + emits: ['cancel', 'confirm'], + setup(props, ctx) { + const { t } = useI18n() + const userStore = useUserStore() + + const pattern = ref('') + const checkedValue = ref(props.defaultChecked) + const globalResource = ref(props.globalResource) + const rowData = ref(props.row) + + const isAllSelected = computed( + () => + checkedValue.value.length === props.data.length && + checkedValue.value.length + ) + + const onCancel = () => { + ctx.emit('cancel') + } + + const onConfirm = () => { + ctx.emit( + 'confirm', + props.showGlobalResource ? globalResource.value : false, + globalResource.value ? [] : checkedValue.value + ) + } + + const handleAllSelect = () => { + checkedValue.value = props.data.map((option) => option.key as number) + } + + const handleCancelSelect = () => (checkedValue.value = []) + + return () => { + return ( + + {{ + default: () => ( + + {props.isResource ? ( +
+ {globalResource.value ? ( + + {t('resource.auth.public_resource')} + + ) : ( + + {rowData.value.projectName} + + )} +
+ ) : ( +
+ {props.showGlobalResource && ( + + {t('resource.auth.public_resource')} + + + )} +
+ )} + {!globalResource.value && ( + + {props.showGlobalResource && ( + + )} + + {isAllSelected.value + ? t('resource.auth.cancel_select') + : t('resource.auth.select_all')} + + + {{ + suffix: () => ( + + + + ) + }} + + + + )} +
+ ), + footer: () => ( + + + {t('resource.auth.cancel')} + + + {t('resource.auth.confirm')} + + + ) + }} +
+ ) + } + } +}) + +export default CheckboxTree diff --git a/seatunnel-ui/src/components/log-modal/index.module.scss b/seatunnel-ui/src/components/log-modal/index.module.scss new file mode 100644 index 000000000..62dced953 --- /dev/null +++ b/seatunnel-ui/src/components/log-modal/index.module.scss @@ -0,0 +1,22 @@ +/* + * 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. + */ + +.btns { + position: absolute; + right: 10px; + bottom: 120px; +} diff --git a/seatunnel-ui/src/components/log-modal/index.tsx b/seatunnel-ui/src/components/log-modal/index.tsx new file mode 100644 index 000000000..85b28e603 --- /dev/null +++ b/seatunnel-ui/src/components/log-modal/index.tsx @@ -0,0 +1,216 @@ +/* + * 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, + h, + ref, + reactive, + toRefs, + onMounted, + onUnmounted, + watchEffect, + nextTick +} from 'vue' +import { NIcon, NLog, NSpace, NButton } from 'naive-ui' +import Modal from '../modal' +import { + DownloadOutlined, + FullscreenExitOutlined, + FullscreenOutlined, + SyncOutlined, + VerticalAlignTopOutlined, + VerticalAlignBottomOutlined +} from '@vicons/antd' +import screenfull from 'screenfull' +import styles from './index.module.scss' +import i18n from '@/locales' + +const props = { + showModalRef: { + type: Boolean as PropType, + default: false + }, + logRef: { + type: String as PropType, + default: '' + }, + logLoadingRef: { + type: Boolean as PropType, + default: false + }, + row: { + type: Object as PropType, + default: {} + }, + showDownloadLog: { + type: Boolean as PropType, + default: false + } +} + +export default defineComponent({ + name: 'log-modal', + props, + emits: ['confirmModal', 'refreshLogs', 'downloadLogs'], + setup(props, ctx) { + const { t } = i18n.global + const logInstRef = ref() + const variables = reactive({ + isFullscreen: false + }) + + const change = () => { + variables.isFullscreen = screenfull.isFullscreen + } + + const renderIcon = (icon: any) => { + return () => h(NIcon, null, { default: () => h(icon) }) + } + + const confirmModal = () => { + variables.isFullscreen = false + ctx.emit('confirmModal', props.showModalRef) + } + + const refreshLogs = () => { + ctx.emit('refreshLogs', props.row) + } + + const handleFullScreen = () => { + screenfull.toggle(document.querySelectorAll('.logModalRef')[0]) + } + + const downloadLogs = () => { + ctx.emit('downloadLogs', props.row) + } + + const setLogPosition = (position: 'top' | 'bottom') => { + logInstRef.value?.scrollTo({ position, slient: false }) + } + + onMounted(() => { + screenfull.on('change', change) + watchEffect(() => { + if (props.logRef) { + nextTick(() => { + logInstRef.value?.scrollTo({ position: 'bottom', slient: true }) + }) + } + }) + }) + + onUnmounted(() => { + screenfull.on('change', change) + }) + + const headerLinks:any = ref([ + { + text: t('project.workflow.download_log'), + show: props.showDownloadLog, + action: downloadLogs, + icon: renderIcon(DownloadOutlined) + }, + { + text: t('project.task.refresh'), + show: true, + action: refreshLogs, + icon: renderIcon(SyncOutlined) + }, + { + text: variables.isFullscreen + ? t('project.task.cancel_full_screen') + : t('project.task.enter_full_screen'), + show: true, + action: handleFullScreen, + icon: variables.isFullscreen + ? renderIcon(FullscreenExitOutlined) + : renderIcon(FullscreenOutlined) + } + ]) + + return { + t, + renderIcon, + confirmModal, + refreshLogs, + downloadLogs, + handleFullScreen, + ...toRefs(variables), + logInstRef, + setLogPosition, + headerLinks + } + }, + render() { + const { + t, + renderIcon, + refreshLogs, + downloadLogs, + isFullscreen, + handleFullScreen, + showDownloadLog, + setLogPosition + } = this + return ( + + + + void setLogPosition('top')} + > + + + + + void setLogPosition('bottom')} + > + + + + + + + ) + } +}) diff --git a/seatunnel-ui/src/components/resource-auth/index.tsx b/seatunnel-ui/src/components/resource-auth/index.tsx new file mode 100644 index 000000000..a00b63fb7 --- /dev/null +++ b/seatunnel-ui/src/components/resource-auth/index.tsx @@ -0,0 +1,163 @@ +/* + * 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 { NButton, NTooltip, NIcon, NPopover } from 'naive-ui' +import { useI18n } from 'vue-i18n' +import { defineComponent, PropType, h, withDirectives, ref } from 'vue' +import { permission } from '@/directives/permission' +import { UserOutlined } from '@vicons/antd' +import { TreeOption } from 'naive-ui' +import { + authResourceProject, + getResourceProject +} from '@/service/resources' +import { accessTypeKey } from '@/service/resources/types' +import CheckboxTree from '../checkbox-tree' +import { useRouter } from 'vue-router' + +const props = { + row: { + type: Object as PropType, + required: true + }, + accessType: { + type: String as PropType, + default: '' + }, + disabled: { + type: Boolean as PropType, + default: false + }, + isResource: { + type: Boolean as PropType, + default: false + } +} + +const ResourceAuth = defineComponent({ + name: 'resource-auth', + props, + setup(props) { + const { t } = useI18n() + const router = useRouter() + const showPopover = ref(false) + const authOptions = ref([]) + const handleAuth = () => { + getResourceProject({ + accessType: props.accessType, + accessCode: props.row.id + }).then((res: any) => { + if (props.isResource) { + authOptions.value = res + .filter((option: any) => { + return option.owner === false + }) + .map((item: any) => { + return { + label: item.name, + key: item.code, + selected: item.selected + } + }) + } else { + authOptions.value = res.map((option: any) => ({ + label: option.name, + key: option.code, + selected: option.selected + })) + } + + showPopover.value = true + }) + } + + const handleAuthConfirm = ( + globalResource: boolean, + checkedKeys: number[] + ) => { + if (props.isResource && props.row.globalResource) { + showPopover.value = false + return + } + authResourceProject({ + globalResource, + accessType: props.accessType, + accessCode: props.row.id, + isShare: true, + projectCodes: checkedKeys + }).then(() => { + showPopover.value = false + window.$message.success(t('resource.auth.success')) + router.go(0) + }) + } + + return () => ( + + {{ + trigger: () => + h( + NPopover, + { + show: showPopover.value, + placement: 'bottom-end', + trigger: 'manual' + }, + { + trigger: () => + withDirectives( + h( + NButton, + { + disabled: props.disabled, + tag: 'div', + circle: true, + size: 'small', + type: 'warning', + onClick: handleAuth + }, + { + default: () => + h(NIcon, null, { default: () => h(UserOutlined) }) + } + ), + [[permission, 'security:resources:auth']] + ), + default: () => + h(CheckboxTree, { + isResource: props.isResource, + data: authOptions.value, + row: props.row, + defaultChecked: authOptions.value + .filter((option) => option.selected) + .map((option) => option.key as number), + globalResource: props.row.globalResource || false, + showGlobalResource: props.accessType !== 'TASK_GROUP', + onCancel: () => (showPopover.value = false), + onConfirm: handleAuthConfirm + }) + } + ), + + default: () => t('resource.auth.authorize') + }} + + ) + } +}) + +export default ResourceAuth diff --git a/seatunnel-ui/src/directives/index.ts b/seatunnel-ui/src/directives/index.ts new file mode 100644 index 000000000..6781e0cf2 --- /dev/null +++ b/seatunnel-ui/src/directives/index.ts @@ -0,0 +1,30 @@ +/* + * 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 { App, Directive } from 'vue' +import { permission } from './permission' + +const directives = { + permission +} as { [key: string]: Directive } + +export default { + install(app: App) { + Object.keys(directives).forEach((key) => { + app.directive(key, directives[key]) + }) + } +} diff --git a/seatunnel-ui/src/directives/permission.ts b/seatunnel-ui/src/directives/permission.ts new file mode 100644 index 000000000..ea3ed7506 --- /dev/null +++ b/seatunnel-ui/src/directives/permission.ts @@ -0,0 +1,46 @@ +/* + * 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 { IPermissionModule, usePermissionStore } from '@/store/user' +import type { Directive } from 'vue' + +export const permission: Directive = { + mounted(el, binding) { + // const code = binding.value + // if (!code) return + // let module = 'common' as IPermissionModule + + // const codeItems = code.split(':') + // if (codeItems.length > 2 && codeItems[0] === 'project') { + // module = 'project' + // } + + // const functionPermissions = usePermissionStore().getPermissions( + // module, + // 'function' + // ) + + // const hasPermission = functionPermissions.has(code) + + // if (hasPermission) return + + // if (el.parentElement?.children.length === 1) { + // el.parentElement.style = 'display: none' + // } + + // el.parentElement.removeChild(el) + } +} diff --git a/seatunnel-ui/src/hooks/index.ts b/seatunnel-ui/src/hooks/index.ts new file mode 100644 index 000000000..107ac97f7 --- /dev/null +++ b/seatunnel-ui/src/hooks/index.ts @@ -0,0 +1,22 @@ +/* + * 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 { usePermissionLength } from './use-permission-length' +export { useTableOperation } from './use-table-operation' +export { useTextCopy } from './use-text-copy' +export { useTableLink } from './use-table-link' +// export * from './use-client-config' diff --git a/seatunnel-ui/src/hooks/use-client-config.ts b/seatunnel-ui/src/hooks/use-client-config.ts new file mode 100644 index 000000000..87c9f0655 --- /dev/null +++ b/seatunnel-ui/src/hooks/use-client-config.ts @@ -0,0 +1,57 @@ +/* + * 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 { +// getUauthorizedClientConfig, +// getAuthorizedClientConfig +// } from '@/service/client' +// import { useTaskTypeStore } from '@/store/project/task-type' +// import { useClientStore } from '@/store/client' + +// export const useClientConfig = () => { +// const taskTypeStore = useTaskTypeStore() +// const clientStore = useClientStore() + +// const getUnauthorizedClientInfo = async () => { +// if (clientStore.getLoaded) { +// document.title = clientStore.getTitle +// return +// } +// const result = await getUauthorizedClientConfig() + +// clientStore.init({ +// casModel: result['cas.anon.enabled'] === true ? 'cas' : 'common', +// casLoginUrl: result['cas.anon.login.address'], +// casLogoutUrl: result['cas.anon.logout.address'], +// title: result['title.anon.context'] +// }) + +// if (result['title.anon.context']) +// document.title = result['title.anon.context'] +// } + +// const getAuthorizedClientInfo = async () => { +// const result = await getAuthorizedClientConfig() +// if (result.task) { +// taskTypeStore.setTaskTypes(result.task) +// } +// } + +// return { +// getAuthorizedClientInfo, +// getUnauthorizedClientInfo +// } +// } diff --git a/seatunnel-ui/src/hooks/use-permission-length.ts b/seatunnel-ui/src/hooks/use-permission-length.ts new file mode 100644 index 000000000..ec5db7d9d --- /dev/null +++ b/seatunnel-ui/src/hooks/use-permission-length.ts @@ -0,0 +1,35 @@ +/* + * 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 { usePermissionStore, IPermissionModule } from '@/store/user' + +// export const usePermissionLength = ( +// permissions: string[], +// module?: IPermissionModule +// ) => { +// const functionPermissions = usePermissionStore().getPermissions( +// module, +// 'function' +// ) + +// let permissionLength = 0 +// permissions.forEach((permissionKey) => { +// if (permissionKey && functionPermissions.has(permissionKey)) { +// permissionLength++ +// } +// }) +// return permissionLength +// } diff --git a/seatunnel-ui/src/hooks/use-source-type.ts b/seatunnel-ui/src/hooks/use-source-type.ts new file mode 100644 index 000000000..1a1460ca8 --- /dev/null +++ b/seatunnel-ui/src/hooks/use-source-type.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 { onMounted, reactive } from 'vue' +import { getDatasourceType } from '@/service/modules/data-source' +import { useI18n } from 'vue-i18n' +import type { SelectOption } from 'naive-ui' + +type Key = '1' | '2' | '3' | '4' | '5' +type IType = { + type: string + label: string + key: string + children: SelectOption[] +} + +export const useSourceType = (showVirtualDataSource = false) => { + const i18n = useI18n() + const TYPE_MAP = { + 1: 'database', + 2: 'file', + 3: 'no_structured', + 4: 'storage', + 5: 'remote_connection' + } + const state = reactive({ + loading: false, + types: [] as IType[] + }) + + const querySource = async () => { + if (state.loading) return + state.loading = true + try { + const res = (await getDatasourceType({ showVirtualDataSource })) as { + [key: string]: { code: number; name: string; chineseName: string }[] + } + const locales = { + zh_CN: {} as { [key: string]: string }, + en_US: {} as { [key: string]: string } + } + const databaseKey = '1' + state.types = [ + { + type: 'group', + label: i18n.t(`datasource.${TYPE_MAP[databaseKey]}`), + key: TYPE_MAP[databaseKey], + children: res[databaseKey].map((item) => { + 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) + } finally { + state.loading = false + } + } + + onMounted(() => { + querySource() + }) + + return { state } +} diff --git a/seatunnel-ui/src/hooks/use-table-link.ts b/seatunnel-ui/src/hooks/use-table-link.ts new file mode 100644 index 000000000..280d3fc79 --- /dev/null +++ b/seatunnel-ui/src/hooks/use-table-link.ts @@ -0,0 +1,94 @@ +/* + * 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 { NEllipsis } from 'naive-ui' +import ButtonLink from '@/components/button-link' +// import { IPermissionModule, usePermissionStore } from '@/store/user' + +interface ITableLinkParams { + title: string + key: string + width?: number + isCopy?: boolean + sorter?: string + minWidth?: string | number + ellipsis?: any + textColor?: (rowData: any) => string + showEmpty?: boolean + button: { + permission?: string + getHref?: (rowData: any) => string | null + showText?: (rowData: any) => boolean + onClick?: (rowData: any) => void + disabled?: (rowData: any) => boolean + } +} + +export const useTableLink = ( + params: ITableLinkParams, + // module?: IPermissionModule +) => { + // const functionPermissions = usePermissionStore().getPermissions( + // module || 'common', + // 'function' + // ) + // const routerPermissions = usePermissionStore().getPermissions( + // module || 'common', + // 'router' + // ) + + const getButtonVnode = (rowData: any) => { + const maxWidth = params.width ? params.width - 24 : params.width + const textColor = params.textColor ? params.textColor(rowData) || '' : '' + return h( + ButtonLink, + { + href: params.button.getHref ? params.button.getHref(rowData) : null, + onClick: () => + params.button.onClick && params.button.onClick(rowData), + disabled: params.button.disabled && params.button.disabled(rowData) + }, + { + default: () => + h( + NEllipsis, + { style: `max-width: ${maxWidth}px; color: ${textColor}` }, + () => { + if (!params.showEmpty) { + return rowData[params.key] + } else { + return rowData[params.key] || '-' + } + } + ) + } + ) + + } + + return { + title: params.title, + key: params.key, + titleColSpan: params.isCopy ? 2 : 1, + width: params.width || '', + minWidth: params.minWidth || '', + sorter: params.sorter, + ellipsis: params.ellipsis || {}, + render: (rowData: any) => getButtonVnode(rowData) + } +} diff --git a/seatunnel-ui/src/hooks/use-table-operation.ts b/seatunnel-ui/src/hooks/use-table-operation.ts new file mode 100644 index 000000000..6c2b6f7f5 --- /dev/null +++ b/seatunnel-ui/src/hooks/use-table-operation.ts @@ -0,0 +1,219 @@ +/* + * 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, withDirectives, VNode } from 'vue' +import { + NSpace, + NTooltip, + NButton, + NIcon, + NPopconfirm, + ButtonProps, + NSwitch +} from 'naive-ui' +import { DeleteOutlined } from '@vicons/antd' +import { permission } from '@/directives/permission' +// import { usePermissionLength } from './use-permission-length' +import { COLUMN_WIDTH_CONFIG } from '@/common/column-width-config' +// import { IPermissionModule } from '@/store/user' +// import { accessTypeKey } from '@/service/resources/types' + +export const useTableOperation = ( + params: { + title: string + key: string + preRender?: (rowData: any, buttonVnodes: VNode[], index: number) => any + width?: number + noPermission?: boolean + itemNum?: number + buttons: { + isDelete?: boolean + isAuth?: boolean + isSwitch?: boolean + isCustom?: boolean + isHidden?: (rowData: any) => boolean + negativeText?: string + positiveText?: string + popTips?: string + text: string | ((rowData: any) => string) + permission?: string + icon?: VNode | ((rowData: any) => VNode) + auth?: any + // accessType?: accessTypeKey + disabled?: boolean | ((rowData: any) => boolean) + class?: string + value?: string | number | boolean | undefined + checkedValue?: string | boolean | number + uncheckedValue?: string | boolean | number + onPositiveClick?: (rowData: any, index: number) => void + onClick?: (rowData: any) => void + onUpdateValue?: (value: any, rowData: any) => void + customFunc?: (rowData: any) => VNode + }[] + }, + // module?: IPermissionModule +) => { + const buttonPermissions = [] as string[] + params.buttons.forEach((button) => { + button.permission && buttonPermissions.push(button.permission) + }) + let permissionLength + if (params.noPermission) { + permissionLength = params.buttons.length + } else { + // permissionLength = usePermissionLength( + // buttonPermissions, + // module || 'common' + // ) + } + + const wrapDirective = (vNode: VNode, permissionKey?: string) => { + return permissionKey + ? withDirectives(vNode, [[permission, permissionKey]]) + : vNode + } + const getButtonVnodes = (rowData: any, index: number) => { + // const showPopover = ref(false) + return params.buttons + .filter((button) => !(button.isHidden && button.isHidden(rowData))) + .map((button) => { + const mergedDisabled = + typeof button.disabled === 'function' + ? button.disabled(rowData) || rowData.isEdit === false + : !!button.disabled || rowData.isEdit === false + + const buttonIcon = + typeof button.icon === 'function' ? button.icon(rowData) : button.icon + + const buttonText = + typeof button.text === 'function' ? button.text(rowData) : button.text + + const commonProps = { + disabled: mergedDisabled, + tag: 'div', + circle: true, + size: 'small', + class: button.class + } as ButtonProps + if (button.isDelete) { + return h(NTooltip, null, { + trigger: () => + h( + NPopconfirm, + { + onPositiveClick: () => + button.onPositiveClick + ? void button.onPositiveClick(rowData, index) + : () => {}, + negativeText: button.negativeText, + positiveText: button.positiveText + }, + { + trigger: () => + wrapDirective( + h( + NButton, + { + ...commonProps, + type: 'error' + }, + { + default: () => + h(NIcon, null, { + default: () => button.icon || h(DeleteOutlined) + }) + } + ), + params.noPermission ? '' : button.permission + ), + default: () => button.popTips + } + ), + default: () => buttonText + }) + } + if (button.isAuth) { + return h(button.auth, { + ...commonProps, + row: rowData, + // accessType: button.accessType + }) + } + if (button.isSwitch) { + return h(NTooltip, null, { + trigger: () => + wrapDirective( + h(NSwitch, { + value: rowData.status, + checkedValue: button.checkedValue, + uncheckedValue: button.uncheckedValue, + onUpdateValue: (value) => + button.onUpdateValue + ? void button.onUpdateValue(value, rowData) + : () => {} + }), + params.noPermission ? '' : button.permission + ), + default: () => buttonText + }) + } + if (button.isCustom && button.customFunc) { + const { customFunc } = button + return wrapDirective(customFunc(rowData), button.permission) + } + return h(NTooltip, null, { + trigger: () => + wrapDirective( + h( + NButton, + { + ...commonProps, + type: 'info', + onClick: () => + button.onClick ? void button.onClick(rowData) : () => {} + }, + { + default: () => h(NIcon, null, { default: () => buttonIcon }) + } + ), + params.noPermission ? '' : button.permission + ), + default: () => buttonText + }) + }) + } + + return { + title: params.title, + key: params.key, + ...COLUMN_WIDTH_CONFIG['operation'](params.itemNum as any), + // ...COLUMN_WIDTH_CONFIG['operation'](params.itemNum || permissionLength), + render: (rowData: any, index: number) => { + const buttonVnodes = getButtonVnodes(rowData, index) + const result = + params.preRender && params.preRender(rowData, buttonVnodes, index) + return result === void 0 + ? h(NSpace, null, { + default: () => buttonVnodes + }) + : result + } + } + // : { + // width: 0 + // } +} diff --git a/seatunnel-ui/src/hooks/use-text-copy.ts b/seatunnel-ui/src/hooks/use-text-copy.ts new file mode 100644 index 000000000..f4176abd2 --- /dev/null +++ b/seatunnel-ui/src/hooks/use-text-copy.ts @@ -0,0 +1,36 @@ +/* + * 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 utils from '@/utils' +import { useMessage } from 'naive-ui' +import { useI18n } from 'vue-i18n' + +/** + * Text copy with success message + */ +export function useTextCopy() { + const { t } = useI18n() + const message = useMessage() + const copy = (text: string, msg: string = t('hook.copy_success')) => { + if (utils.clipboard(text)) { + message.success(msg) + } + } + return { + copy + } +} diff --git a/seatunnel-ui/src/layouts/dashboard/header/index.tsx b/seatunnel-ui/src/layouts/dashboard/header/index.tsx index 2d3265867..f6ac368cd 100644 --- a/seatunnel-ui/src/layouts/dashboard/header/index.tsx +++ b/seatunnel-ui/src/layouts/dashboard/header/index.tsx @@ -25,7 +25,7 @@ const Header = defineComponent({ setup() {}, render() { return ( - + diff --git a/seatunnel-ui/src/layouts/dashboard/header/menu/index.tsx b/seatunnel-ui/src/layouts/dashboard/header/menu/index.tsx index 5b30da873..8186e97cc 100644 --- a/seatunnel-ui/src/layouts/dashboard/header/menu/index.tsx +++ b/seatunnel-ui/src/layouts/dashboard/header/menu/index.tsx @@ -15,7 +15,7 @@ * limitations under the License. */ -import { defineComponent, toRefs } from 'vue' +import { defineComponent, toRefs, ref, watch } from 'vue' import { NMenu, NSpace } from 'naive-ui' import { useRouter, useRoute } from 'vue-router' import { useMenu } from './use-menu' @@ -30,17 +30,26 @@ const Menu = defineComponent({ router.push({ path: `/${key}` }) } + const menuKey = ref(route.meta.activeMenu as string) + + watch( + () => route.path, + () => { + menuKey.value = route.meta.activeMenu as string + } + ) + return { ...toRefs(state), handleMenuClick, - route + menuKey } }, render() { return ( h(NEllipsis, null, { default: () => t('menu.data_pipes') }), key: 'data-pipes' }, - { - label: () => h(NEllipsis, null, { default: () => t('menu.jobs') }), - key: 'jobs' - }, { label: () => h(NEllipsis, null, { default: () => t('menu.tasks') }), key: 'tasks' diff --git a/seatunnel-ui/src/layouts/dashboard/index.tsx b/seatunnel-ui/src/layouts/dashboard/index.tsx index 16050a59a..ffa80ea3c 100644 --- a/seatunnel-ui/src/layouts/dashboard/index.tsx +++ b/seatunnel-ui/src/layouts/dashboard/index.tsx @@ -15,13 +15,41 @@ * limitations under the License. */ -import { defineComponent } from 'vue' -import { NLayout, NLayoutHeader, NLayoutContent, useMessage } from 'naive-ui' +import { defineComponent, watch, watchEffect, ref, Ref, onMounted } from 'vue' +import { useRoute, useRouter, RouteLocationMatched } from 'vue-router' +import { + NLayout, + NLayoutHeader, + NLayoutContent, + useMessage, + NSpace +} from 'naive-ui' import Header from './header' +import Sidebar from './sidebar' const Dashboard = defineComponent({ setup() { window.$message = useMessage() + const route = useRoute() + let showSide = ref(false) + + const menuKey = ref(route.meta.activeMenu as string) + + watch( + () => route, + () => { + showSide.value = route?.meta?.showSide as boolean + menuKey.value = route.meta.activeSide as string + }, + { + immediate: true, + deep: true + } + ) + return { + showSide, + menuKey + } }, render() { return ( @@ -30,8 +58,23 @@ const Dashboard = defineComponent({
- - + + { this.showSide && } + + + + + diff --git a/seatunnel-ui/src/layouts/dashboard/sidebar/index.module.scss b/seatunnel-ui/src/layouts/dashboard/sidebar/index.module.scss new file mode 100644 index 000000000..f11738df6 --- /dev/null +++ b/seatunnel-ui/src/layouts/dashboard/sidebar/index.module.scss @@ -0,0 +1,67 @@ +.btn-box { + width: 168px; + height: 60px; + display: flex; + justify-content: space-around; + align-items: center; + margin: 0 14px; + border-radius: 8px; + margin-top: 10px; + .projectinfo { + width: 120px; + // background-color: aquamarine; + display: flex; + flex-direction: column; + justify-content: center; + } + .projectname { + margin-left: 15px; + display: flex; + margin-top: 5px; + align-items: center; + font-size: 16px; + } + .name-space { + margin-left: 5px; + } + .workflows { + display: flex; + align-items: center; + margin-left: 30px; + color: #6c6c6c; + } +} + +.dark { + color: #eee; +} + +.dark-blue { + color: #eee !important; +} + +.dark-blue-active { + background-color: #ffffff10; +} + +.dark-active { + background-color: #2c2c2f; +} + +.light-active { + background-color: #eeeeee; +} + +.light-none { + background-color: transparent; +} + +.collapsed-icon { + width: 100%; + height: 100%; + display: flex; + margin-top: 20px; + justify-content: center; + align-items: center; + color: #d6d6d6; +} diff --git a/seatunnel-ui/src/layouts/dashboard/sidebar/index.tsx b/seatunnel-ui/src/layouts/dashboard/sidebar/index.tsx new file mode 100644 index 000000000..c5ef0e108 --- /dev/null +++ b/seatunnel-ui/src/layouts/dashboard/sidebar/index.tsx @@ -0,0 +1,137 @@ +/* + * 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, PropType, onMounted, watch, h } from 'vue' +import { NLayoutSider, NMenu, NIcon, NDropdown, NEllipsis } from 'naive-ui' +import { useThemeStore } from '@/store/theme' +// import { DashOutlined } from '@vicons/antd' +// import { useMenuClick } from './use-menuClick' +import styles from './index.module.scss' +import { PartitionOutlined, ProjectOutlined, RightOutlined } from '@vicons/antd' +import { useRoute, useRouter, RouterLink } from 'vue-router' +import { MenuOption } from 'naive-ui' +import { useI18n } from 'vue-i18n' + +const toOverview = [ + 'workflow-definition-detail', + 'workflow-instance-detail', + 'workflow-instance-gantt', + 'synchronization-definition-dag' +] +const Sidebar = defineComponent({ + name: 'Sidebar', + props: { + sideMenuOptions: { + type: Array as PropType, + default: [] + }, + sideKey: { + type: String as PropType, + default: '' + } + }, + setup() { + const router = useRouter() + const collapsedRef = ref(false) + const defaultExpandedKeys = [ + 'workflow', + 'task', + 'udf-manage', + 'service-manage', + 'statistical-manage', + 'task-group-manage', + 'file-manage', + 'baseline' + ] + const route = useRoute() + const { t } = useI18n() + // Determine if it is a project overview + + const showDrop = ref(false) + const themeStore = useThemeStore() + const menuStyle = ref(themeStore.getTheme as 'dark' | 'dark-blue' | 'light') + + const sideMenuOptions = ref([ + { + label: () => + h( + RouterLink, + { + to: { + path: '/task/synchronization-definition' + }, + exact: false + }, + { default: () => t('menu.sync_task_definition') } + ), + key: 'synchronization-definition' + }, + { + label: () => + h( + RouterLink, + { + to: { + path: '/task/synchronization-instance' + }, + exact: false + }, + { default: () => t('menu.sync_task_instance') } + ), + key: 'synchronization-instance' + } + ]) + + onMounted(() => { + console.log(route, 'route') + }) + + + return { + collapsedRef, + defaultExpandedKeys, + menuStyle, + themeStore, + showDrop, + sideMenuOptions, + route + } + }, + render() { + return ( + (this.collapsedRef = true)} + onExpand={() => (this.collapsedRef = false)} + width={196} + > + + + ) + } +}) + +export default Sidebar diff --git a/seatunnel-ui/src/locales/en_US/hook.ts b/seatunnel-ui/src/locales/en_US/hook.ts new file mode 100644 index 000000000..f565791a5 --- /dev/null +++ b/seatunnel-ui/src/locales/en_US/hook.ts @@ -0,0 +1,20 @@ +/* + * 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 { + copy_success: 'Copy success' +} diff --git a/seatunnel-ui/src/locales/en_US/index.ts b/seatunnel-ui/src/locales/en_US/index.ts index 6a25d3dc0..b48d1948c 100644 --- a/seatunnel-ui/src/locales/en_US/index.ts +++ b/seatunnel-ui/src/locales/en_US/index.ts @@ -26,6 +26,9 @@ import tasks from '@/locales/en_US/tasks' import setting from '@/locales/en_US/setting' import datasource from '@/locales/en_US/datasource' import virtual_tables from '@/locales/en_US/virtual-tables' +import theme from '@/locales/en_US/theme' +import project from '@/locales/en_US/project' +import hook from '@/locales/en_US/hook' export default { login, @@ -38,5 +41,8 @@ export default { tasks, setting, datasource, - virtual_tables + virtual_tables, + theme, + project, + hook } diff --git a/seatunnel-ui/src/locales/en_US/menu.ts b/seatunnel-ui/src/locales/en_US/menu.ts index f3bd95878..7461dfa3d 100644 --- a/seatunnel-ui/src/locales/en_US/menu.ts +++ b/seatunnel-ui/src/locales/en_US/menu.ts @@ -24,5 +24,7 @@ export default { logout: 'Logout', tasks: 'Tasks', datasource: 'Datasource', - virtual_tables: 'Virtual Tables' + virtual_tables: 'Virtual Tables', + sync_task_definition: 'Syncing Task Definition', + sync_task_instance: 'Syncing Task Instance', } diff --git a/seatunnel-ui/src/locales/en_US/project.ts b/seatunnel-ui/src/locales/en_US/project.ts new file mode 100644 index 000000000..08858471c --- /dev/null +++ b/seatunnel-ui/src/locales/en_US/project.ts @@ -0,0 +1,1176 @@ +/* + * 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 { + list: { + create_project: 'Create Project', + edit_project: 'Edit Project', + project_list: 'Project List', + project_tips: 'Please enter your project', + description_tips: 'Please enter your description', + username_tips: 'Please enter your username', + project_name: 'Project Name', + project_description: 'Project Description', + owned_users: 'Owned Users', + workflow_define_count: 'Workflow Define Count', + process_instance_running_count: 'Process Instance Running Count', + description: 'Description', + create_time: 'Create Time', + update_time: 'Update Time', + operation: 'Operation', + edit: 'Edit', + delete: 'Delete', + confirm: 'Confirm', + cancel: 'Cancel', + delete_confirm: 'Delete?', + member_manage: 'Member Manage', + data_columns: 'Data columns' + }, + overview: { + task_state_statistics: 'Task State Statistics', + process_state_statistics: 'Process State Statistics', + process_definition_statistics: 'Process Definition Statistics', + number: 'Number', + state: 'State', + submitted_success: 'SUBMITTED_SUCCESS', + running_execution: 'RUNNING_EXECUTION', + ready_pause: 'READY_PAUSE', + pause: 'PAUSE', + ready_stop: 'READY_STOP', + stop: 'STOP', + failure: 'FAILURE', + success: 'SUCCESS', + need_fault_tolerance: 'NEED_FAULT_TOLERANCE', + kill: 'KILL', + waiting_thread: 'WAITING_THREAD', + waiting_depend: 'WAITING_DEPEND', + delay_execution: 'DELAY_EXECUTION', + forced_success: 'FORCED_SUCCESS', + serial_wait: 'SERIAL_WAIT', + dispatch: 'DISPATCH', + ready_block: 'READY_BLOCK', + block: 'BLOCK', + pause_by_isolation: 'PAUSE_BY_ISOLATION', + kill_by_isolation: 'KILL_BY_ISOLATION', + pause_by_coronation: 'PAUSE_BY_CORONATION', + forbidden_by_coronation: 'FORBIDDEN_BY_CORONATION' + }, + workflow: { + pause_recovery: 'Pause Recovery', + pause_recovery_confirm: 'Pause Recovery?', + suspend: 'Suspend', + suspend_confirm: 'Suspend?', + stop_recovery: 'Stop Recovery', + stop_recovery_confirm: 'Stop Recovery?', + copy_judge_tips: + 'In the current workflow, a node with brand name $value is referenced.', + workflow_relation: 'Workflow Relation', + create_workflow: 'Create Workflow', + select_project: 'Choose a project', + choose_project: 'Designated project', + import_workflow: 'Import Workflow', + export_workflow: 'Export Workflow', + workflow_name: 'Workflow Name', + workflow_instance_name: 'Workflow Instance Name', + current_selection: 'Current Selection', + online: 'Online', + offline: 'Offline', + refresh: 'Refresh', + show_hide_label: 'Show / Hide Label', + schedule_start_time: 'Schedule Start Time', + schedule_end_time: 'Schedule End Time', + crontab_expression: 'Crontab', + workflow_publish_status: 'Workflow Publish Status', + workflow_definition: 'Workflow Definition', + workflow_instance: 'Workflow Instance', + status: 'Status', + create_time: 'Create Time', + update_time: 'Update Time', + description: 'Description', + create_user: 'Create User', + modify_user: 'Modify User', + operation: 'Operation', + edit: 'Edit', + start: 'Start', + timing: 'Timing', + up_line: 'Online', + down_line: 'Offline', + copy_workflow: 'Copy Workflow', + delete: 'Delete', + tree_view: 'Tree View', + tree_limit: 'Limit Size', + export: 'Export', + batch_copy: 'Copy', + version_info: 'Version Info', + version: 'Version', + undo: 'Undo', + redo: 'Redo', + file_upload: 'File Upload', + upload_file: 'Upload File', + import_type: 'Import Type', + export_type: 'Export Type', + error_info: 'The template format is wrong, please confirm and upload again', + upload: 'Upload', + file_name: 'File Name', + success: 'Success', + parameter_configuration: 'Parameter Configuration', + set_parameters_before_timing: 'Set parameters before timing', + failure_strategy: 'Failure Strategy', + node_execution: 'Node Execution', + backward_execution: 'Backward execution', + forward_execution: 'Forward execution', + current_node_execution: 'Execute only the current node', + notification_strategy: 'Notification Strategy', + workflow_priority: 'Workflow Priority', + worker_group: 'Worker Group', + environment_name: 'Environment Name', + alarm_group: 'Alarm Group', + calendar: 'Calendar', + cut_day_time: 'Cut Day Time', + card: 'Card', + card_value: 'Card Value', + complement_data: 'Complement Data', + startup_parameter: 'Startup Parameter', + whether_dry_run: 'Whether Dry-Run', + continue: 'Continue', + end: 'End', + none_send: 'None', + success_send: 'Success', + failure_send: 'Failure', + all_send: 'All', + whether_complement_data: 'Whether it is a complement process?', + data_date: 'Data date', + select_date: 'Select Date', + enter_date: 'Enter Date', + data_date_tips: + 'The format is yyyy-MM-dd HH:mm:ss with multiple comma splits', + data_date_limit: 'Enter more than 100 dates', + mode_of_execution: 'Mode of execution', + serial_execution: 'Serial execution', + parallel_execution: 'Parallel execution', + parallelism: 'Parallelism', + custom_parallelism: 'Custom Parallelism', + please_enter_parallelism: 'Please enter Parallelism', + please_choose: 'Please Choose', + start_time: 'Start Time', + end_time: 'End Time', + delete_confirm: 'Delete?', + rerun_confirm: 'Rerun?', + stop_confirm: 'Stop?', + online_confirm: 'Online?', + offline_confirm: 'Offline?', + enter_name_tips: 'Please enter name', + uploaded_file_tips: 'The uploaded file cannot be empty', + switch_version: 'Switch To This Version', + confirm_switch_version: 'Confirm Switch To This Version?', + current_version: 'Current Version', + run_type: 'Run Type', + scheduling_time: 'Scheduling Time', + duration: 'Duration', + run_times: 'Run Times', + fault_tolerant_sign: 'Fault-tolerant Sign', + dry_run_flag: 'Dry-run Flag', + executor: 'Executor', + host: 'Host', + start_process: 'Start Process', + start_from_state_clean_tasks: 'Start from state clean tasks', + execute_from_the_current_node: 'Execute from the current node', + recover_tolerance_fault_process: 'Recover tolerance fault process', + resume_the_suspension_process: 'Resume the suspension process', + execute_from_the_failed_nodes: 'Execute from the failed nodes', + scheduling_execution: 'Scheduling execution', + rerun: 'Rerun', + stop: 'Stop', + pause: 'Pause', + recovery_waiting_thread: 'Recovery waiting thread', + recover_serial_wait: 'Recover serial wait', + recovery_suspend: 'Recovery Suspend', + dependent_chain_rerun: 'Dependent Chain Rerun', + dependent_chain_recovery: 'Dependent Chain Recovery', + failed_to_retry: 'Failed to retry', + gantt: 'Gantt', + name: 'Name', + all_status: 'AllStatus', + submit_success: 'Submitted successfully', + running: 'Running', + ready_to_pause: 'Ready to pause', + ready_to_stop: 'Ready to stop', + failed: 'Failed', + need_fault_tolerance: 'Need fault tolerance', + kill: 'Kill', + waiting_for_thread: 'Waiting for thread', + waiting_for_dependence: 'Waiting for dependence', + waiting_for_dependency_to_complete: 'Waiting for dependency to complete', + delay_execution: 'Delay execution', + forced_success: 'Forced success', + serial_wait: 'Serial wait', + dispatch: 'Dispatch', + executing: 'Executing', + startup_type: 'Startup Type', + complement_range: 'Complement Range', + parameters_variables: 'Parameters variables', + global_parameters: 'Global parameters', + local_parameters: 'Local parameters', + type: 'Type', + retry_count: 'Retry Count', + submit_time: 'Submit Time', + refresh_status_succeeded: 'Refresh status succeeded', + view_log: 'View log', + update_log_success: 'Update log success', + no_more_log: 'No more logs', + no_log: 'No log', + loading_log: 'Loading Log...', + close: 'Close', + download_log: 'Download Log', + refresh_log: 'Refresh Log', + enter_full_screen: 'Enter full screen', + cancel_full_screen: 'Cancel full screen', + task_state: 'Task status', + mode_of_dependent: 'Mode of dependent', + open: 'Open', + project_name_required: 'Project name is required', + related_items: 'Related items', + project_name: 'Project Name', + project_tips: 'Please select project name', + workflow_relation_no_data_result_title: + 'Can not find any relations of workflows.', + workflow_relation_no_data_result_desc: + 'There is not any workflows. Please create a workflow, and then visit this page again.', + timing_tips: 'The current workflow lacks timing', + label: 'Label', + estimated_end_time: 'Estimated End Time', + download_template: 'Download Template', + export_tips: + 'Currently, Shell, SQL, Procedure, Dependent, Trigger and Data Quality tasks can be exported', + timing_name: 'Timing Name', + next_run: 'Next Running Time', + coronation: 'Coronation', + cancle_coronation: 'Cancel the coronation', + coronation_confirm: 'Confirm coronation?', + cancle_coronation_confirm: 'Sure to cancel the coronation?', + isolation: 'Isolation', + cancle_isolation__confirm: 'Sure to cancel the isolation?', + cancle_isolation: 'Cancel the isolation', + isolation_confirm: 'Confirm isolation?', + debug_tips: 'Save workflow before debug', + impact_anaysis: 'Impact analysis', + natural: 'Calendar day', + scheduler_calendar: 'Scheduling calendars', + card_calendar: 'Card calendars', + custom_calendar: 'Custom calendars', + count: 'count', + choose_timing: 'Please select timing', + instance: 'Workflow Instance', + submitter: 'Submitter', + today: 'Today', + last_day: 'Yesterday', + last_three_day: 'The last 3 days', + last_week: 'The last 7 days', + last_month: 'The last 30 days', + local_import: 'Local Import', + workflow_update_list: 'Workflow Update List', + update: 'Update', + create: 'Create', + last_update_user: 'Last Update User', + last_update_time: 'Last Update Time', + dependent_chain_title: + 'The following workflow are about to start dependency chain cleanup and rerun', + dependent_chain_condition_title: + 'The following workflow do not meet the execution conditions' + }, + task: { + cancel_full_screen: 'Cancel full screen', + enter_full_screen: 'Enter full screen', + current_task_settings: 'Current task settings', + online: 'Online', + offline: 'Offline', + task_name: 'Task Name', + task_type: 'Task Type', + create_task: 'Create Task', + workflow_instance: 'Workflow Instance', + workflow_name: 'Workflow Name', + workflow_name_tips: 'Please select workflow name', + workflow_state: 'Workflow State', + version: 'Version', + current_version: 'Current Version', + switch_version: 'Switch To This Version', + confirm_switch_version: 'Confirm Switch To This Version?', + description: 'Description', + move: 'Move', + upstream_tasks: 'Upstream Tasks', + executor: 'Executor', + node_type: 'Node Type', + state: 'State', + submit_time: 'Submit Time', + start_time: 'Start Time', + create_time: 'Create Time', + update_time: 'Update Time', + end_time: 'End Time', + duration: 'Duration', + retry_count: 'Retry Count', + dry_run_flag: 'Dry Run Flag', + host: 'Host', + operation: 'Operation', + edit: 'Edit', + delete: 'Delete', + delete_confirm: 'Delete?', + clean_state: 'Clear and run again', + forced_success: 'Forced Success', + view_log: 'View Log', + download_log: 'Download Log', + refresh: 'Refresh', + run_state: 'Run State', + workflow_instance_name: 'Workflow Instance Name', + cancel_coronation: 'Cancel Coronation', + import_coronation_task: 'Import Coronation Task', + download_template: 'Download Template', + cancel_confirm: 'Cancel?', + task_coronation: 'Task Coronation', + upload_file: 'Upload File', + local_import: 'Local Import', + task_coronation_list: 'Task Coronation List', + associate_upstream: 'Associate Upstream', + next_step: 'Next Step', + prev_step: 'Prev Step', + confirm: 'Confirm', + error_info: 'The template format is wrong, please confirm and upload again', + more: 'More', + success: 'Success', + flag: 'Flag', + newest: 'Newest', + history: 'History', + confirm_operation: 'Confirm', + detail: 'Detail', + return: 'Return', + cancel: 'Cancel', + dependent_chain_title: + 'The following tasks are about to start dependency chain cleanup and rerun', + dependent_chain_condition_title: + 'The following tasks do not meet the execution conditions', + dependent_chain_condition_tip1: + 'An executing workflow instance is not executable', + dependent_chain_condition_tip2: + 'The task cannot be executed if it does not meet the execution conditions (the predecessor task of the task is suspended or failed)', + dependent_chain_condition_tip3: + 'Non-executable without operation permission', + batch_confirm: 'Batch Confirm' + }, + dag: { + create: 'Create Workflow', + start: 'Start', + search: 'Search', + download_png: 'Download PNG', + import_task: 'Import task', + download_template: 'Download task template', + fullscreen_open: 'Open Fullscreen', + fullscreen_close: 'Close Fullscreen', + save: 'Save', + close: 'Close', + format: 'Format', + refresh_dag_status: 'Refresh DAG status', + layout_type: 'Layout Type', + grid_layout: 'Grid', + dagre_layout: 'Dagre', + rows: 'Rows', + cols: 'Cols', + copy_success: 'Copy Success', + workflow_name: 'Workflow Name', + description: 'Description', + tenant: 'Tenant', + timeout_alert: 'Timeout Alert', + process_execute_type: 'Process execute type', + parallel: 'parallel', + serial_wait: 'Serial wait', + // serial_discard: 'Serial discard', + serial_priority: 'Serial priority', + recover_serial_wait: 'Recover serial wait', + global_variables: 'Global Variables', + basic_info: 'Basic Information', + minute: 'Minute', + key: 'Key', + value: 'Value', + success: 'Success', + delete_cell: 'Delete selected edges and nodes', + online_directly: 'Whether to go online the process definition', + update_directly: 'Whether to update the process definition', + dag_name_empty: 'Workflow name cannot be empty', + project_empty: 'Please select project', + positive_integer: 'Please enter a positive integer greater than 0', + prop_empty: 'prop is empty', + prop_repeat: 'prop is repeat', + node_not_created: 'Failed to save node not created', + copy_name: 'Copy Name', + view_variables: 'View Variables', + startup_parameter: 'Startup Parameter', + online: 'Online', + label: 'Label', + label_length_tips: 'The maximum number of labels is 3', + unsave_tips: + 'Please save the workflow first, and then execute the run operation.', + cancel: 'Cancel', + force_close: 'Force Close', + close_tip: 'Close Tip', + close_content_tip: + 'The current workflow has been modified and has not been saved. If it is forced to close, the edited content will be lost.', + more: 'More', + run: 'Run', + stop: 'Stop', + import: 'Import', + code_import: 'File import', + resource: 'Resource', + debug_tips: 'Getting the log, please wait...', + setting: 'Setting', + form_tips: 'Error in required information of node', + save_and_debug: 'Save Workflow and debug', + debug: 'Debug', + import_alert: + 'When the script content is updated, the referenced file content is also updated.', + select_file: 'Select a file' + }, + node: { + return_back: 'Return', + current_node_settings: 'Current node settings', + instructions: 'Instructions', + view_history: 'View history', + view_log: 'View log', + enter_this_child_node: 'Enter this child node', + name: 'Node Name', + task_name: 'Task Name', + task_name_tips: 'Please select a task (required)', + name_tips: 'Please enter name (required)', + task_type: 'Task Type', + task_type_tips: 'Please select a task type (required)', + workflow_name: 'Workflow Name', + workflow_name_tips: 'Please select a workflow (required)', + child_node: 'Child Node', + child_node_tips: 'Please select a child node (required)', + run_flag: 'Run flag', + normal: 'Normal', + prohibition_execution: 'Prohibition execution', + description: 'Description', + description_tips: 'Please enter description', + task_priority: 'Task priority', + worker_group: 'Worker group', + worker_group_tips: + 'The Worker group no longer exists, please select the correct Worker group!', + environment_name: 'Environment Name', + task_group_name: 'Task group name', + task_group_queue_priority: 'Priority', + number_of_failed_retries: 'Number of failed retries', + times: 'Times', + failed_retry_interval: 'Failed retry interval', + minute: 'Minute', + second: 'Second', + delay_execution_time: 'Delay execution time', + state: 'State', + branch_flow: 'Branch flow', + cancel: 'Cancel', + loading: 'Loading...', + confirm: 'Confirm', + success: 'Success', + failed: 'Failed', + backfill_tips: + 'The newly created sub-Process has not yet been executed and cannot enter the sub-Process', + task_instance_tips: + 'The task has not been executed and cannot enter the sub-Process', + branch_tips: + 'Cannot select the same node for successful branch flow and failed branch flow', + timeout_alarm: 'Timeout alarm', + timeout_strategy: 'Timeout strategy', + timeout_strategy_tips: 'Timeout strategy must be selected', + timeout_failure: 'Timeout failure', + timeout_period: 'Timeout period', + timeout_period_tips: 'Timeout must be a positive integer', + script: 'Script', + script_tips: 'Please enter script(required)', + resources: 'Resources', + resources_tips: 'Please select resources', + non_resources_tips: 'Please delete all non-existent resources', + useless_resources_tips: 'Unauthorized or deleted resources', + custom_parameters: 'Custom Parameters', + copy_success: 'Copy success', + copy_failed: 'The browser does not support automatic copying', + prop_tips: 'prop(required)', + prop_repeat: 'prop is repeat', + value_tips: 'value(optional)', + value_required_tips: 'value(required)', + pre_tasks: 'Pre tasks', + program_type: 'Program Type', + main_class: 'Main Class', + main_class_tips: 'Please enter main class', + main_package: 'Main Package', + main_package_tips: 'Please enter main package', + deploy_mode: 'Deploy Mode', + app_name: 'App Name', + app_name_tips: 'Please enter app name(optional)', + driver_cores: 'Driver Cores', + driver_cores_tips: 'Please enter Driver cores', + driver_memory: 'Driver Memory', + driver_memory_tips: 'Please enter Driver memory', + executor_number: 'Executor Number', + executor_number_tips: 'Please enter Executor number', + executor_memory: 'Executor Memory', + executor_memory_tips: 'Please enter Executor memory', + executor_cores: 'Executor Cores', + executor_cores_tips: 'Please enter Executor cores', + main_arguments: 'Main Arguments', + main_arguments_tips: 'Please enter main arguments', + option_parameters: 'Option Parameters', + option_parameters_tips: 'Please enter option parameters', + positive_integer_tips: 'should be a positive integer', + flink_version: 'Flink Version', + job_manager_memory: 'JobManager Memory', + job_manager_memory_tips: 'Please enter JobManager memory', + task_manager_memory: 'TaskManager Memory', + task_manager_memory_tips: 'Please enter TaskManager memory', + slot_number: 'Slot Number', + slot_number_tips: 'Please enter Slot number', + parallelism: 'Parallelism', + custom_parallelism: 'Configure parallelism', + parallelism_tips: 'Please enter Parallelism', + parallelism_number_tips: 'Parallelism number should be positive integer', + parallelism_complement_tips: + 'If there are a large number of tasks requiring complement, you can use the custom parallelism to ' + + 'set the complement task thread to a reasonable value to avoid too large impact on the server.', + task_manager_number: 'TaskManager Number', + task_manager_number_tips: 'Please enter TaskManager number', + http_url: 'Http Url', + http_url_tips: 'Please Enter Http Url', + http_url_validator: 'The request address must contain HTTP or HTTPS', + http_method: 'Http Method', + http_parameters: 'Http Parameters', + http_check_condition: 'Http Check Condition', + http_condition: 'Http Condition', + http_condition_tips: 'Please Enter Http Condition', + timeout_settings: 'Timeout Settings', + connect_timeout: 'Connect Timeout', + ms: 'ms', + socket_timeout: 'Socket Timeout', + status_code_default: 'Default response code 200', + status_code_custom: 'Custom response code', + body_contains: 'Content includes', + body_not_contains: 'Content does not contain', + body_check_custom: 'Content check', + http_parameters_position: 'Http Parameters Position', + target_task_name: 'Target Task Name', + target_task_name_tips: 'Please enter the Pigeon task name(required)', + datasource_type: 'Datasource types', + datasource_instances: 'Datasource instances', + datasource_require_tips: 'Datasource is required', + sql_type: 'SQL Type', + sql_type_query: 'Query', + sql_type_non_query: 'Non Query', + sql_statement: 'SQL Statement', + pre_sql_statement: 'Pre SQL Statement', + post_sql_statement: 'Post SQL Statement', + sql_input_placeholder: 'Please enter non-query sql.', + sql_empty_tips: 'The sql can not be empty.', + procedure_method: 'SQL Statement', + procedure_method_tips: 'Please enter the procedure script', + procedure_method_snippet: + '--Please enter the procedure script \n\n--call procedure:call [(,, ...)]\n\n--call function:?= call [(,, ...)]', + start: 'Start', + edit: 'Edit', + copy: 'Copy', + delete: 'Delete', + custom_job: 'Custom Job', + custom_script: 'Custom Script', + sqoop_job_name: 'Job Name', + sqoop_job_name_tips: 'Please enter Job Name(required)', + direct: 'Direct', + hadoop_custom_params: 'Hadoop Params', + sqoop_advanced_parameters: 'Sqoop Advanced Parameters', + data_source: 'Data Source', + type: 'Type', + datasource: 'Datasource', + datasource_tips: 'Please select the datasource', + model_type: 'ModelType', + form: 'Form', + table: 'Table', + table_tips: 'Please enter Mysql Table(required)', + column_type: 'ColumnType', + all_columns: 'All Columns', + some_columns: 'Some Columns', + column: 'Column', + column_tips: 'Please enter Columns (Comma separated)', + database: 'Database', + database_tips: 'Please enter Hive Database(required)', + zeppelin_note_id: 'zeppelinNoteId', + zeppelin_note_id_tips: 'Please enter the note id of your zeppelin note', + zeppelin_paragraph_id: 'zeppelinParagraphId', + zeppelin_paragraph_id_tips: + 'Please enter the paragraph id of your zeppelin paragraph', + zeppelin_parameters: 'parameters', + zeppelin_parameters_tips: + 'Please enter the parameters for zeppelin dynamic form', + zeppelin_rest_endpoint: 'zeppelinRestEndpoint', + zeppelin_rest_endpoint_tips: + 'Please enter the rest endpoint of your Zeppelin server', + zeppelin_production_note_directory: + 'Directory for cloned zeppelin note in production mode', + zeppelin_production_note_directory_tips: + 'Please enter the production note directory to enable production mode', + hive_table_tips: 'Please enter Hive Table(required)', + hive_partition_keys: 'Hive partition Keys', + hive_partition_keys_tips: 'Please enter Hive Partition Keys', + hive_partition_values: 'Hive partition Values', + hive_partition_values_tips: 'Please enter Hive Partition Values', + jupyter_conda_env_name: 'condaEnvName', + jupyter_conda_env_name_tips: + 'Please enter the conda environment name of papermill', + jupyter_input_note_path: 'inputNotePath', + jupyter_input_note_path_tips: 'Please enter the input jupyter note path', + jupyter_output_note_path: 'outputNotePath', + jupyter_output_note_path_tips: 'Please enter the output jupyter note path', + jupyter_parameters: 'parameters', + jupyter_parameters_tips: + 'Please enter the parameters for jupyter parameterization', + jupyter_kernel: 'kernel', + jupyter_kernel_tips: 'Please enter the jupyter kernel name', + jupyter_engine: 'engine', + jupyter_engine_tips: 'Please enter the engine name', + jupyter_execution_timeout: 'executionTimeout', + jupyter_execution_timeout_tips: + 'Please enter the execution timeout for each jupyter note cell', + jupyter_start_timeout: 'startTimeout', + jupyter_start_timeout_tips: + 'Please enter the start timeout for jupyter kernel', + jupyter_others: 'others', + jupyter_others_tips: + 'Please enter the other options you need for papermill', + mlflow_algorithm: 'Algorithm', + mlflow_algorithm_tips: 'svm', + mlflow_params: 'Parameters', + mlflow_params_tips: ' ', + mlflow_searchParams: 'Parameter Search Space', + mlflow_searchParams_tips: ' ', + mlflow_isSearchParams: 'Search Parameters', + mlflow_dataPath: 'Data Path', + mlflow_dataPath_tips: + ' The absolute path of the file or folder. Ends with .csv for file or contain train.csv and test.csv for folder', + mlflow_dataPath_error_tips: ' data data can not be empty ', + mlflow_experimentName: 'Experiment Name', + mlflow_experimentName_tips: 'experiment_001', + mlflow_registerModel: 'Register Model', + mlflow_modelName: 'Model Name', + mlflow_modelName_tips: 'model_001', + mlflow_mlflowTrackingUri: 'MLflow Tracking Server URI', + mlflow_mlflowTrackingUri_tips: 'http://127.0.0.1:5000', + mlflow_mlflowTrackingUri_error_tips: + 'MLflow Tracking Server URI can not be empty', + mlflow_jobType: 'Job Type', + mlflow_automlTool: 'AutoML Tool', + mlflow_taskType: 'MLflow Task Type', + mlflow_deployType: 'Deploy Mode', + mlflow_deployModelKey: 'Model-URI', + mlflow_deployPort: 'Port', + mlflowProjectRepository: 'Repository', + mlflowProjectRepository_tips: 'git repository or path on worker', + mlflowProjectVersion: 'Project Version', + mlflowProjectVersion_tips: 'git version', + mlflow_cpuLimit: 'Max Cpu Limit', + mlflow_memoryLimit: 'Max Memory Limit', + openmldb_zk_address: 'zookeeper address', + openmldb_zk_address_tips: 'Please enter the zookeeper address', + openmldb_zk_path: 'zookeeper path', + openmldb_zk_path_tips: 'Please enter the zookeeper path', + openmldb_execute_mode: 'Execute Mode', + openmldb_execute_mode_tips: 'Please select the execute mode', + openmldb_execute_mode_offline: 'offline', + openmldb_execute_mode_online: 'online', + dvc_task_type: 'DVC Task Type', + dvc_repository: 'DVC Repository', + dvc_repository_tips: 'please input the url of dvc repository', + dvc_version: 'Version', + dvc_version_tips: 'data version, will be mark as git tag', + dvc_data_location: 'Data Path in DVC Repository', + dvc_message: 'Version Message', + dvc_load_save_data_path: 'Data Path In Worker', + dvc_store_url: 'Store Url', + dvc_empty_tips: 'This parameter cannot be empty', + dinky_address: 'Dinky address', + dinky_address_tips: 'Please enter the url of your dinky', + dinky_task_id: 'Dinky task id', + dinky_task_id_tips: 'Please enter the task id of your dinky', + dinky_online: 'Online task', + pytorch_script: 'Python Script', + pytorch_script_params: 'Script Input Parameters', + pytorch_other_params: 'Show More Configurations', + pytorch_python_path: 'Project Path', + pytorch_is_create_environment: 'Create An Environment Or Not', + pytorch_python_command: 'Python Command Path', + pytorch_python_command_tips: 'If empty,will be set $PYTHON_HOME', + pytorch_python_env_tool: 'Python Environment Manager Tool', + pytorch_requirements: 'Requirement File', + pytorch_conda_python_version: 'Python Version', + pytorch_conda_python_version_tips: + 'Please enter the version number, such as 3.6, 3.7, 3.x', + export_dir: 'Export Dir', + export_dir_tips: 'Please enter Export Dir(required)', + sql_statement_tips: 'SQL Statement(required)', + map_column_hive: 'Map Column Hive', + map_column_java: 'Map Column Java', + data_target: 'Data Target', + create_hive_table: 'CreateHiveTable', + drop_delimiter: 'DropDelimiter', + over_write_src: 'OverWriteSrc', + hive_target_dir: 'Hive Target Dir', + hive_target_dir_tips: 'Please enter hive target dir', + replace_delimiter: 'ReplaceDelimiter', + replace_delimiter_tips: 'Please enter Replace Delimiter', + target_dir: 'Target Dir', + target_dir_tips: 'Please enter Target Dir(required)', + delete_target_dir: 'DeleteTargetDir', + compression_codec: 'CompressionCodec', + file_type: 'FileType', + fields_terminated: 'FieldsTerminated', + fields_terminated_tips: 'Please enter Fields Terminated', + lines_terminated: 'LinesTerminated', + lines_terminated_tips: 'Please enter Lines Terminated', + is_update: 'IsUpdate', + update_key: 'UpdateKey', + update_key_tips: 'Please enter Update Key', + update_mode: 'UpdateMode', + only_update: 'OnlyUpdate', + allow_insert: 'AllowInsert', + concurrency: 'Concurrency', + concurrency_tips: 'Please enter Concurrency', + sea_tunnel_master: 'Master', + sea_tunnel_master_url: 'Master URL', + sea_tunnel_queue: 'Queue', + sea_tunnel_master_url_tips: + 'Please enter the master url, e.g., 127.0.0.1:7077', + switch_condition: 'Condition', + switch_branch_flow: 'Branch Flow', + switch_branch_flow_tips: 'Please select branch flow', + and: 'and', + or: 'or', + datax_custom_template: 'Custom Template', + datax_json_template: 'JSON', + datax_target_datasource_type: 'Target Datasource Types', + datax_target_database: 'Target Database', + datax_target_table: 'Target Table', + datax_target_table_tips: 'Please enter the name of the target table', + datax_target_database_pre_sql: 'Pre SQL Statement', + datax_target_database_post_sql: 'Post SQL Statement', + datax_non_query_sql_tips: 'Please enter the non-query sql statement', + datax_job_speed_byte: 'Speed(Byte count)', + datax_job_speed_byte_info: '(0 means unlimited)', + datax_job_speed_record: 'Speed(Record count)', + datax_job_speed_record_info: '(0 means unlimited)', + datax_job_runtime_memory: 'Runtime Memory Limits', + datax_job_runtime_memory_xms: 'Low Limit Value', + datax_job_runtime_memory_xmx: 'High Limit Value', + datax_job_runtime_memory_unit: 'G', + chunjun_json_template: 'JSON script', + current_hour: 'CurrentHour', + last_1_hour: 'Last1Hour', + last_2_hour: 'Last2Hours', + last_3_hour: 'Last3Hours', + last_24_hour: 'Last24Hours', + today: 'today', + last_1_days: 'Last1Days', + last_2_days: 'Last2Days', + last_3_days: 'Last3Days', + last_7_days: 'Last7Days', + this_week: 'ThisWeek', + last_week: 'LastWeek', + last_monday: 'LastMonday', + last_tuesday: 'LastTuesday', + last_wednesday: 'LastWednesday', + last_thursday: 'LastThursday', + last_friday: 'LastFriday', + last_saturday: 'LastSaturday', + last_sunday: 'LastSunday', + this_month: 'ThisMonth', + this_month_begin: 'ThisMonthBegin', + last_month: 'LastMonth', + last_month_begin: 'LastMonthBegin', + last_month_end: 'LastMonthEnd', + month: 'month', + week: 'week', + day: 'day', + hour: 'hour', + add_dependency: 'Add dependency', + waiting_dependent_start: 'Waiting Dependent start', + check_interval: 'Check interval', + waiting_dependent_complete: 'Waiting Dependent complete', + project_name: 'Project Name', + project_name_tips: 'Please select a project(required)', + process_name: 'Workflow Name', + process_name_tips: 'Please select a workflow(required)', + cycle_time: 'Cycle Time', + cycle_time_tips: 'Please select a cycle time(required)', + date_tips: 'Please select a date(required)', + rule_name: 'Rule Name', + null_check: 'NullCheck', + custom_sql: 'CustomSql', + multi_table_accuracy: 'MulTableAccuracy', + multi_table_value_comparison: 'MulTableCompare', + field_length_check: 'FieldLengthCheck', + uniqueness_check: 'UniquenessCheck', + regexp_check: 'RegexpCheck', + timeliness_check: 'TimelinessCheck', + enumeration_check: 'EnumerationCheck', + table_count_check: 'TableCountCheck', + src_connector_type: 'SrcConnType', + src_datasource_id: 'SrcSource', + src_table: 'SrcTable', + src_filter: 'SrcFilter', + src_field: 'SrcField', + statistics_name: 'ActualValName', + check_type: 'CheckType', + operator: 'Operator', + threshold: 'Threshold', + failure_strategy: 'FailureStrategy', + target_connector_type: 'TargetConnType', + target_datasource_id: 'TargetSourceId', + target_table: 'TargetTable', + target_filter: 'TargetFilter', + mapping_columns: 'OnClause', + statistics_execute_sql: 'ActualValExecSql', + comparison_name: 'ExceptedValName', + comparison_execute_sql: 'ExceptedValExecSql', + comparison_type: 'ExceptedValType', + writer_connector_type: 'WriterConnType', + writer_datasource_id: 'WriterSourceId', + target_field: 'TargetField', + field_length: 'FieldLength', + logic_operator: 'LogicOperator', + regexp_pattern: 'RegexpPattern', + deadline: 'Deadline', + datetime_format: 'DatetimeFormat', + enum_list: 'EnumList', + begin_time: 'BeginTime', + fix_value: 'FixValue', + fix_value_tips: 'Please set the FixValue', + required: 'required', + emr_flow_define_json: 'jobFlowDefineJson', + emr_flow_define_json_tips: 'Please enter the definition of the job flow.', + emr_steps_define_json: 'stepsDefineJson', + emr_steps_define_json_tips: 'Please enter the definition of the emr step.', + send_email: 'Send Email', + log_display: 'Log display', + rows_of_result: 'rows of result', + title: 'Title', + title_tips: 'Please enter the title of email', + alarm_group: 'Alarm group', + alarm_group_tips: 'Alarm group required', + integer_tips: 'Please enter a positive integer', + sql_parameter: 'SQL Parameter', + format_tips: 'Please enter format', + udf_function: 'UDF Function', + unlimited: 'unlimited', + please_select_source_connector_type: 'Please select source connector type', + please_select_source_datasource_id: 'Please select source datasource id', + please_enter_source_table_name: 'Please select source table name', + please_enter_filter_expression: 'Please enter filter expression', + please_enter_column_only_single_column_is_supported: + 'Please select column, only single column is supported', + please_enter_threshold_number_is_needed: + 'Please enter threshold number is needed', + please_enter_comparison_title: 'please select comparison title', + please_enter_statistics_execute_sql: 'Please enter statistics execute sql', + please_enter_statistics_name_the_alias_in_statistics_execute_sql: + 'Please enter statistics name the alias in statistics execute sql', + please_select_target_connector_type: 'Please select target connector type', + please_select_target_datasource: 'Please select target datasource', + please_enter_target_table: 'Please enter target table', + please_enter_target_filter_expression: + 'Please enter target filter expression', + please_enter_comparison_name_the_alias_in_comparison_execute_sql: + 'Please enter comparison name the alias in comparison execute sql', + please_enter_comparison_execute_sql: 'Please enter comparison execute sql', + please_enter_length_limit: 'Please enter length limit', + scan_type: 'Scan Type', + scan_interval: 'Scan Interval', + file_path: 'File Path', + file_path_tips: 'File Path(required)', + topic_name: 'Topic Name', + topic_name_tips: 'Topic Name(required)', + offset_time: 'Offset Time', + offset_time_required_tips: + // eslint-disable-next-line quotes + "Offset Time(required) and the format is ${'{'}system.biz.curdate{'}'}, $[yyyyMMdd] or yyyyMMdd", + offset_time_incorrect_tips: + // eslint-disable-next-line quotes + "Please enter the correct offset time format(${'{'}system.biz.curdate{'}'},$[yyyyMMdd],yyyyMMdd).", + bootstrap_servers: 'Bootstrap Servers', + bootstrap_servers_tips: 'Bootstrap Servers(required)', + group_id: 'GroupId', + group_id_tips: 'GroupId(required)', + key_deserializer: 'Key Deserializer', + key_deserializer_tips: 'Key Deserializer(required)', + value_deserializer: 'Value Deserializer', + value_deserializer_tips: 'Value Deserializer(required)', + next_loop_custom_rule: 'Custom Next Loop Rule', + script_type: 'Script Type', + next_loop_date_tip: + 'The script return value is in date format: yyyy-MM-dd HH:mm:ss', + next_loop_timezone_tip: + 'Note that the return date is consistent with the scheduled time zone.', + next_loop_card: 'Card', + next_loop_card_tips: 'Card(required)', + dependency_strategy: 'Dependency Strategy', + failure: 'Failure', + continue: 'Continue', + wait: 'Wait', + failure_continue_tips: + 'When a dependency fails, the current node continues execution.', + failure_wait_tips: + 'When a dependency fails, the current node waits for the dependency to succeed and then continues execution.', + hive_cli_task_execution_type: 'Hive Cli task type', + hive_sql_script: 'Hive SQL script', + hive_cli_options: 'Hive Cli options', + hive_cli_options_tips: 'Please input your HIVE CLI options, like --verbose', + natural: 'Natural Day', + business: 'Business Date', + card: 'Card', + rename: 'Rename', + name_node: 'Name', + custom_config: 'Custom Config', + engine: 'engine', + engine_tips: 'Please select engine', + run_mode: 'Run Mode', + cart_null_tips: 'The workflow has not set the card value', + offline_task: 'Offline Task', + stream_task: 'Stream Task', + whale_seatunnel_task_tips: 'Please select task', + execute_type: 'Execute Type', + java_option_tips: 'Please input Java Option', + dependent_path: 'Dependent Package Path', + dependent_path_tips: 'Please input dependent package path', + local_file: 'Local File', + git_file: 'Git File', + break_continue: 'Break continue', + remote_connection: 'Remote Connection', + connection_type: 'Connection Type', + source_name: 'Source Name', + ssh: 'SSH', + connection_type_tips: 'Connection type is required', + source_name_tips: 'Source name is required', + folder_name: 'Folder Name', + mapping_name: 'Informatica Workflow Name', + target_name: 'Target Name', + folder_name_tips: 'Please choose folder name', + mapping_name_tips: 'Please choose informatica workflow name', + source_type_tips: 'Please choose source type', + target_type_tips: 'Please choose target type', + source_name_tips_infa: 'Please enter source name', + target_name_tips: 'Please enter target name' + }, + isolation: { + upload_isolation_tasks: 'Upload Isolation Tasks', + download_template: 'Download Template', + task_name: 'Task Name', + workflow_instance_name: 'Workflow Instance Name', + operation_status: 'Operation Status', + create_time: 'Create Time', + online_isolation: 'Online Isolation', + cancel_isolation: 'Cancel Isolation', + task_isolation: 'Task Isolation', + local_upload: 'Local Upload', + isolation_tasks: 'Isolation tasks', + show_more: 'Show More', + previous: 'Previous', + next: 'Next', + upload_tips: 'Only supports excel, and only supports one file at a time.', + sure: 'Sure', + cancel: 'Cancel', + operation: 'Operation', + online: 'Online', + offline: 'Offline', + total_items: 'Total {total} items', + file_tips: 'Please select an excel file.' + }, + synchronization_definition: { + node_prev_check_tips: 'The current node is not connected to the previous node', + create_synchronization_task: 'Create Synchronization Task', + edit_synchronization_task: 'Edit Synchronization Task', + synchronization_task_name: 'Synchronization Task Name', + task_describe: 'Task Describe', + create_user: 'Create User', + create_time: 'Create Time', + update_user: 'Update User', + update_time: 'Update Time', + operation: 'Operation', + edit: 'Edit', + start: 'start', + delete: 'Delete', + delete_confirm: 'Delete?', + task_name_tips: 'Please entry task name', + task_describe_tips: 'Please entry task describe', + task_name_validate: 'Task name cannot be empty', + copy: 'Copy', + open_full_screen: 'Open Full Screen', + close_full_screen: 'Close Full Screen', + format: 'Format', + save: 'Save', + close: 'Close', + layout_type: 'Layout Type', + rows: 'Rows', + cols: 'Cols', + dagre: 'Dagre', + grid: 'Grid', + format_canvas: 'Format Canvas', + setting: 'Setting', + task_name: 'Task Name', + description: 'Description', + engine: 'Engine', + task_name_placeholder: 'Please entry task name', + description_placeholder: 'Please entry description', + node_name_validate: 'Node name is not empty', + node_name: 'Node Name', + node_name_placeholder: 'Please entry node name', + transforms: 'Transforms', + source_and_sink: 'Source / Sink', + confirm: 'Confirm', + cancel: 'Cancel', + configuration: 'Configuration', + model: 'Model', + business_model: 'Business Model', + whole_library_sync: 'Whole Library Synchronization', + data_integration: 'Data Integration', + source_name: 'Source Name', + source_name_validate: 'Source name is not empty', + scene_mode: 'Scene Mode', + multi_table_sync: 'Multi Table Synchronization', + sub_library_and_sub_table: 'Sub-library And Sub-table', + single_table_sync: 'Single Table Synchronization', + database: 'Database', + table_name: 'Table Name', + field_name: 'Field Name', + field_type: 'Field Type', + non_empty: 'Non Empty', + field_comment: 'Field Comment', + input_table_structure: 'Input Table Structure', + output_table_structure: 'Output Table Structure', + exclude_kind: 'Exclude Kind', + include_kind: 'Include Kind', + exclude_kind_validate: 'Exclude kind is not empty', + include_kind_validate: 'Include kind is not empty', + database_validate: 'Database is not empty', + table_name_validate: 'Table name is not empty', + primary_key: 'Primary Key', + default_value: 'Default Value', + scene_mode_validate: 'Scene mode is not empty', + kind: 'Kind', + delete_empty_tips: 'Please select a node', + database_exception_message: + 'database exception,has been cleared in the form', + table_exception_message: 'table exception,has been cleared in the form', + start_node_tips: 'The starting node be source node', + end_node_tips: 'The ending node be sink node', + save_node_tips: 'Please save the nodes on the canvas first', + table_sync: 'Table to be synchronized', + selected_table: 'Selected table', + original_field: 'Original Field', + name_tips: 'Please enter a field name', + move_to_top: 'Move to top', + move_to_bottom: 'Move to bottom', + move_up: 'Move up', + move_down: 'Move down', + yes: 'Yes', + no: 'No', + split: 'MultiFieldSplit', + split_field: 'Split Field', + separator: 'Separator', + separator_tips: 'Please enter a separator', + segmented_fields: 'Segmented fields', + segmented_fields_placeholder: 'If two fields are separated, you can fill in field1, field2', + copy_field: 'Copy Field', + check_model: 'Please check the model information', + sql_content_label: 'sql', + sql_content_label_placeholder: 'please input the SQL statement', + query_validate: 'please input the SQL statement', + + }, + synchronization_instance: { + pipeline_id: 'Pipeline Id', + source: 'Source', + sink: 'Sink', + run_often: 'Run Often', + fail: 'Fail', + running: 'Running', + pause: 'Pause', + success: 'Success', + state: 'State', + start_time: 'Start Time', + end_time: 'End Time', + operation: 'Operation', + real_time_sync: 'Real Time Sync', + offline_sync: 'Offline Sync', + sync_task_definition: 'Sync Task Definition', + data_pipeline_running_instance: 'Data Pipeline Running Instance', + task_name: 'Task Name', + workflow_instance: 'Workflow Instance', + execute_user: 'Execute User', + host: 'Host', + amount_of_data_read: 'Amount Of Data Read (Row)', + read_rate: 'Read Rate (Row / Second)', + processing_rate: 'Processing Rate (Row / Second)', + amount_of_data_written: 'Amount Of Data Written (Row)', + delay_of_data: 'Delay Of Data (Second)', + node_type: 'Node Type', + submit_time: 'Submit Time', + run_time: 'Run Time', + number_of_retries: 'Number Of Retries', + rerun_mark: 'Rerun Mark', + clean_state: 'Clear and run again', + forced_success: 'Forced Success', + view_log: 'View Log', + download_log: 'Download Log', + description: 'Description', + engine: 'Engine', + write: 'Write', + read: 'Read', + line: 'lines' + }, + menu: { + fav: 'Favorites', + universal: 'Universal', + cloud: 'Cloud', + logic: 'Logic', + di: 'Data Integration', + dq: 'Data Quality', + ml: 'Machine Learning', + other: 'Other' + }, + member: { + alias: 'Alias', + role_name: 'Role Name', + member_manage: 'Member Manage', + new_member: 'New Member', + remove_member: 'Remove Member', + confirm: 'Confirm', + cancel: 'Cancel', + edit: 'Edit', + operation: 'Operation' + }, + synchronizing_task_instance: 'Synchronizing task instances', + task_instance: 'Task instance', + column: 'Column', + all_column: 'All', + select_workflow_instance: 'Select the workflow instance', + select_workflow_definition: 'Select the workflow definition', + select_task_instance: 'Select the task instance', + can_not_many: 'Cross-project operations are not yet supported', + all_project: 'All projects', + next_step: 'Next step', + pre_step: 'Last step', + project_name: 'Project Name' +} diff --git a/seatunnel-ui/src/locales/en_US/theme.ts b/seatunnel-ui/src/locales/en_US/theme.ts new file mode 100644 index 000000000..4d2768b3f --- /dev/null +++ b/seatunnel-ui/src/locales/en_US/theme.ts @@ -0,0 +1,22 @@ +/* + * 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 { + light: 'Light', + dark: 'Dark', + dark_blue: 'Dark Blue' +} diff --git a/seatunnel-ui/src/locales/zh_CN/hook.ts b/seatunnel-ui/src/locales/zh_CN/hook.ts new file mode 100644 index 000000000..53acfd6c2 --- /dev/null +++ b/seatunnel-ui/src/locales/zh_CN/hook.ts @@ -0,0 +1,20 @@ +/* + * 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 { + copy_success: '复制成功' +} diff --git a/seatunnel-ui/src/locales/zh_CN/index.ts b/seatunnel-ui/src/locales/zh_CN/index.ts index 8ecf70c4d..a7d45434e 100644 --- a/seatunnel-ui/src/locales/zh_CN/index.ts +++ b/seatunnel-ui/src/locales/zh_CN/index.ts @@ -26,6 +26,9 @@ import tasks from '@/locales/zh_CN/tasks' import setting from '@/locales/zh_CN/setting' import datasource from '@/locales/zh_CN/datasource' import virtual_tables from '@/locales/zh_CN/virtual-tables' +import theme from '@/locales/zh_CN/theme' +import project from '@/locales/zh_CN/project' +import hook from '@/locales/zh_CN/hook' export default { login, @@ -38,5 +41,8 @@ export default { tasks, setting, datasource, - virtual_tables + virtual_tables, + theme, + project, + hook } diff --git a/seatunnel-ui/src/locales/zh_CN/menu.ts b/seatunnel-ui/src/locales/zh_CN/menu.ts index 1ab59deb4..2d3acd835 100644 --- a/seatunnel-ui/src/locales/zh_CN/menu.ts +++ b/seatunnel-ui/src/locales/zh_CN/menu.ts @@ -24,5 +24,7 @@ export default { logout: '登出', tasks: '任务', datasource: '数据源', - virtual_tables: '虚拟表' + virtual_tables: '虚拟表', + sync_task_definition: '同步任务定义', + sync_task_instance: '同步任务实例', } diff --git a/seatunnel-ui/src/locales/zh_CN/project.ts b/seatunnel-ui/src/locales/zh_CN/project.ts new file mode 100644 index 000000000..55e2ce1e6 --- /dev/null +++ b/seatunnel-ui/src/locales/zh_CN/project.ts @@ -0,0 +1,1143 @@ +/* + * 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 { + list: { + create_project: '创建项目', + edit_project: '编辑项目', + project_list: '项目列表', + project_tips: '请输入项目名称', + description_tips: '请输入项目描述', + username_tips: '请输入所属用户', + project_name: '项目名称', + project_description: '项目描述', + owned_users: '所属用户', + workflow_define_count: '工作流定义数', + process_instance_running_count: '正在运行的流程数', + description: '描述', + create_time: '创建时间', + update_time: '更新时间', + operation: '操作', + edit: '编辑', + delete: '删除', + confirm: '确定', + cancel: '取消', + delete_confirm: '确定删除吗?', + member_manage: '成员管理', + data_columns: '列' + }, + overview: { + task_state_statistics: '任务状态统计', + process_state_statistics: '流程状态统计', + process_definition_statistics: '流程定义统计', + number: '数量', + state: '状态', + submitted_success: '提交成功', + running_execution: '正在运行', + ready_pause: '准备暂停', + pause: '暂停', + ready_stop: '准备停止', + stop: '停止', + failure: '失败', + success: '成功', + need_fault_tolerance: '需要容错', + kill: 'KILL', + waiting_thread: '等待线程', + waiting_depend: '等待依赖完成', + delay_execution: '延时执行', + forced_success: '强制成功', + serial_wait: '串行等待', + dispatch: '派发', + ready_block: '准备阻断', + block: '阻断', + pause_by_isolation: '隔离暂停', + kill_by_isolation: '隔离KILL', + pause_by_coronation: '加冕暂停', + forbidden_by_coronation: '加冕禁止执行' + }, + workflow: { + copy_judge_tips: '当前工作流中,引用到牌名称为$value的节点。', + workflow_relation: '工作流关系', + create_workflow: '创建工作流', + select_project: '选择项目', + choose_project: '指定项目', + import_workflow: '导入工作流', + export_workflow: '导出工作流', + workflow_name: '工作流名称', + workflow_instance_name: '工作流实例名称', + current_selection: '当前选择', + online: '已上线', + offline: '已下线', + refresh: '刷新', + show_hide_label: '显示 / 隐藏标签', + schedule_start_time: '定时开始时间', + schedule_end_time: '定时结束时间', + crontab_expression: 'Crontab', + workflow_publish_status: '工作流上线状态', + workflow_definition: '工作流定义', + workflow_instance: '工作流实例', + status: '状态', + create_time: '创建时间', + update_time: '更新时间', + description: '描述', + create_user: '创建用户', + modify_user: '修改用户', + operation: '操作', + edit: '编辑', + confirm: '确定', + cancel: '取消', + start: '运行', + timing: '定时', + up_line: '上线', + down_line: '下线', + copy_workflow: '复制工作流', + delete: '删除', + tree_view: '工作流树形图', + tree_limit: '限制大小', + export: '导出', + batch_copy: '复制', + version_info: '版本信息', + version: '版本', + undo: '撤销', + redo: '恢复', + file_upload: '文件上传', + upload_file: '上传文件', + import_type: '导入类型', + export_type: '导出类型', + error_info: '模版格式有误,请确认后再次上传', + upload: '上传', + file_name: '文件名称', + success: '成功', + parameter_configuration: '参数配置', + set_parameters_before_timing: '定时前请先设置参数', + failure_strategy: '失败策略', + node_execution: '节点执行', + backward_execution: '向后执行', + forward_execution: '向前执行', + current_node_execution: '仅执行当前节点', + notification_strategy: '通知策略', + workflow_priority: '流程优先级', + worker_group: 'Worker分组', + environment_name: '环境名称', + alarm_group: '告警组', + calendar: '日历', + cut_day_time: '日切时间', + card: '牌', + card_value: '牌值', + complement_data: '补数', + startup_parameter: '启动参数', + whether_dry_run: '是否空跑', + continue: '继续', + end: '结束', + none_send: '都不发', + success_send: '成功发', + failure_send: '失败发', + all_send: '成功或失败都发', + whether_complement_data: '是否是补数', + data_date: '数据日期', + select_date: '日期选择', + enter_date: '手动输入', + data_date_tips: '格式为yyyy-MM-dd HH:mm:ss,多个逗号分割', + data_date_limit: '输入日期不满足<=100条', + mode_of_execution: '执行方式', + serial_execution: '串行执行', + parallel_execution: '并行执行', + parallelism: '并行度', + custom_parallelism: '自定义并行度', + please_enter_parallelism: '请输入并行度', + please_choose: '请选择', + start_time: '开始时间', + end_time: '结束时间', + delete_confirm: '确定删除吗?', + pause_recovery: '暂停恢复', + pause_recovery_confirm: '确定暂停恢复吗?', + suspend: '暂停', + suspend_confirm: '确定暂停吗?', + stop_recovery: '停止恢复', + stop_recovery_confirm: '确定停止恢复吗?', + rerun_confirm: '确定重跑吗?', + stop_confirm: '确定停止吗?', + online_confirm: '确定上线吗?', + offline_confirm: '确定下线吗?', + enter_name_tips: '请输入名称', + uploaded_file_tips: '上传文件不能为空', + switch_version: '切换到该版本', + confirm_switch_version: '确定切换到该版本吗?', + current_version: '当前版本', + run_type: '运行类型', + scheduling_time: '调度时间', + duration: '运行时长', + run_times: '运行次数', + fault_tolerant_sign: '容错标识', + dry_run_flag: '空跑标识', + executor: '执行用户', + host: '主机', + start_process: '启动工作流', + start_from_state_clean_tasks: '从状态清除任务开始执行', + execute_from_the_current_node: '从当前节点开始执行', + recover_tolerance_fault_process: '恢复被容错的工作流', + resume_the_suspension_process: '恢复运行流程', + execute_from_the_failed_nodes: '从失败节点开始执行', + scheduling_execution: '调度执行', + rerun: '重跑', + stop: '停止', + pause: '暂停', + recovery_waiting_thread: '恢复等待线程', + recover_serial_wait: '串行恢复', + recovery_suspend: '恢复运行', + dependent_chain_rerun: '依赖链重跑', + dependent_chain_recovery: '依赖链恢复', + failed_to_retry: '重跑失败任务', + gantt: '甘特图', + name: '名称', + all_status: '全部状态', + submit_success: '提交成功', + running: '正在运行', + ready_to_pause: '准备暂停', + ready_to_stop: '准备停止', + failed: '失败', + need_fault_tolerance: '需要容错', + kill: 'Kill', + waiting_for_thread: '等待线程', + waiting_for_dependence: '等待依赖', + waiting_for_dependency_to_complete: '等待依赖完成', + delay_execution: '延时执行', + forced_success: '强制成功', + serial_wait: '串行等待', + dispatch: '派发', + executing: '正在执行', + startup_type: '启动类型', + complement_range: '补数范围', + parameters_variables: '参数变量', + global_parameters: '全局参数', + local_parameters: '局部参数', + type: '类型', + retry_count: '重试次数', + submit_time: '提交时间', + refresh_status_succeeded: '刷新状态成功', + view_log: '查看日志', + update_log_success: '更新日志成功', + no_more_log: '暂无更多日志', + no_log: '暂无日志', + loading_log: '正在努力请求日志中...', + close: '关闭', + download_log: '下载日志', + refresh_log: '刷新日志', + enter_full_screen: '进入全屏', + cancel_full_screen: '取消全屏', + task_state: '任务状态', + mode_of_dependent: '依赖模式', + open: '打开', + project_name_required: '项目名称必填', + related_items: '关联项目', + project_name: '项目名称', + project_tips: '请选择项目', + workflow_relation_no_data_result_title: '工作流关系不存在', + workflow_relation_no_data_result_desc: + '目前没有任何工作流,请先创建工作流,再访问该页面', + timing_tips: '当前工作流缺少定时', + label: '标签', + estimated_end_time: '预计结束时间点', + download_template: '下载模板', + export_tips: + '目前支持导出shell、sql、Procedure、Dependent、Trigger、Data Quality任务类型', + timing_name: '定时名称', + next_run: '下次运行时间', + coronation: '加冕', + cancle_coronation: '取消加冕', + coronation_confirm: '确定加冕?', + cancle_coronation_confirm: '确定取消加冕?', + isolation: '隔离', + cancle_isolation__confirm: '确定取消隔离?', + cancle_isolation: '取消隔离', + isolation_confirm: '确定隔离?', + debug_tips: '调试前请先保存工作流', + impact_anaysis: '影响分析', + natural: '自然日', + scheduler_calendar: '调度日历', + card_calendar: '牌日历', + custom_calendar: '自定义日历', + count: '次', + choose_timing: '请选择定时', + instance: '工作流实例', + submitter: '提交人', + today: '今天', + last_day: '昨天', + last_three_day: '过去3天', + last_week: '过去7天', + last_month: '过去30天', + local_import: '本地导入', + workflow_update_list: '工作流更新列表', + update: '更新', + create: '新建', + last_update_user: '上次更新用户', + last_update_time: '上次修改时间', + dependent_chain_title: '以下工作流即将开始依赖链清除并重跑', + dependent_chain_condition_title: '以下工作流不满足执行条件' + }, + task: { + cancel_full_screen: '取消全屏', + enter_full_screen: '全屏', + current_task_settings: '当前任务设置', + online: '已上线', + offline: '已下线', + task_name: '任务名称', + task_type: '任务类型', + create_task: '创建任务', + workflow_instance: '工作流实例', + workflow_name: '工作流名称', + workflow_name_tips: '请选择工作流名称', + workflow_state: '工作流状态', + version: '版本', + current_version: '当前版本', + switch_version: '切换到该版本', + confirm_switch_version: '确定切换到该版本吗?', + description: '描述', + move: '移动', + upstream_tasks: '上游任务', + executor: '执行用户', + node_type: '节点类型', + state: '状态', + submit_time: '提交时间', + start_time: '开始时间', + create_time: '创建时间', + update_time: '更新时间', + end_time: '结束时间', + duration: '运行时间', + retry_count: '重试次数', + dry_run_flag: '空跑标识', + host: '主机', + operation: '操作', + edit: '编辑', + delete: '删除', + delete_confirm: '确定删除吗?', + clean_state: '清除并重跑', + forced_success: '强制成功', + view_log: '查看日志', + download_log: '下载日志', + refresh: '刷新', + run_state: '运行状态', + workflow_instance_name: '工作流实例名称', + cancel_coronation: '取消加冕', + import_coronation_task: '导入加冕任务', + download_template: '下载模板', + cancel_confirm: '确定取消吗?', + task_coronation: '任务加冕', + upload_file: '上传文件', + local_import: '本地导入', + task_coronation_list: '加冕任务列表', + associate_upstream: '关联上游', + next_step: '下一步', + prev_step: '上一步', + confirm: '确定', + error_info: '模版格式有误,请确认后再次上传', + more: '显示更多', + success: '成功', + only_current: '仅当前', + current_downstream: '当前及下游', + cascaded_dependency_chain: '级联依赖链', + flag: '标记', + newest: '最新', + history: '历史', + confirm_operation: '确认操作', + detail: '详情', + return: '返回', + cancel: '取消', + dependent_chain_title: '以下任务即将开始依赖链清除并重跑', + dependent_chain_condition_title: '以下任务不满足执行条件', + dependent_chain_condition_tip1: '正在执行中的工作流实例不可执行', + dependent_chain_condition_tip2: + '任务不满足执行条件的不可执行(任务的前置任务为暂停或失败)', + dependent_chain_condition_tip3: '无操作权限的不可执行', + batch_confirm: '批量确认操作' + }, + dag: { + create: '创建工作流', + start: '运行', + search: '搜索', + download_png: '下载工作流图片', + import_task: '导入任务', + download_template: '下载任务模版', + fullscreen_open: '全屏', + fullscreen_close: '退出全屏', + save: '保存', + close: '关闭', + format: '格式化', + refresh_dag_status: '刷新DAG状态', + layout_type: '布局类型', + grid_layout: '网格布局', + dagre_layout: '层次布局', + rows: '行数', + cols: '列数', + copy_success: '复制成功', + workflow_name: '工作流名称', + description: '描述', + tenant: '租户', + timeout_alert: '超时告警', + process_execute_type: '执行策略', + parallel: '并行', + serial_wait: '串行等待', + // serial_discard: '串行抛弃', + serial_priority: '串行优先', + recover_serial_wait: '串行恢复', + global_variables: '全局变量', + basic_info: '基本信息', + minute: '分', + key: '键', + value: '值', + success: '成功', + delete_cell: '删除选中的线或节点', + online_directly: '是否上线流程定义', + update_directly: '是否更新流程定义', + dag_name_empty: '工作流名称不能为空', + project_empty: '请选择项目', + positive_integer: '请输入大于 0 的正整数', + prop_empty: '自定义参数prop不能为空', + prop_repeat: 'prop中有重复', + node_not_created: '未创建节点保存失败', + copy_name: '复制名称', + view_variables: '查看变量', + startup_parameter: '启动参数', + online: '已上线', + label: '标签', + label_length_tips: '标签个数最多为3个', + unsave_tips: '请先保存工作流,然后执行运行操作。', + cancel: '取消', + force_close: '强制关闭', + close_tip: '关闭提醒', + close_content_tip: + '当前工作流已被修改,尚未保存,若强制关闭将丢失已编辑的内容。', + more: '更多', + run: '运行', + stop: '停止', + import: '导入', + code_import: '文件引入', + resource: '资源', + debug_tips: '正在获取日志,请等待...', + setting: '设置', + form_tips: '节点必填信息错误', + save_and_debug: '保存工作流并调试', + debug: '调试', + import_alert: '当脚本内容更新时,会同时更新被引用的文件内容。', + select_file: '选择文件' + }, + node: { + return_back: '返回上一节点', + current_node_settings: '当前节点设置', + instructions: '使用说明', + view_history: '查看历史', + view_log: '查看日志', + enter_this_child_node: '进入该子节点', + name: '节点名称', + task_name: '任务名称', + task_name_tips: '任务名称(必填)', + name_tips: '请输入名称(必填)', + task_type: '任务类型', + task_type_tips: '请选择任务类型(必选)', + workflow_name: '工作流名称', + workflow_name_tips: '请选择工作流(必选)', + child_node: '子节点', + child_node_tips: '请选择子节点(必选)', + run_flag: '运行标志', + normal: '正常', + prohibition_execution: '禁止执行', + description: '描述', + description_tips: '请输入描述', + task_priority: '任务优先级', + worker_group: 'Worker分组', + worker_group_tips: '该Worker分组已经不存在,请选择正确的Worker分组!', + environment_name: '环境名称', + task_group_name: '任务组名称', + task_group_queue_priority: '组内优先级', + number_of_failed_retries: '失败重试次数', + times: '次', + failed_retry_interval: '失败重试间隔', + minute: '分', + second: '秒', + delay_execution_time: '延时执行时间', + state: '状态', + branch_flow: '分支流转', + cancel: '取消', + loading: '正在努力加载中...', + confirm: '确定', + success: '成功', + failed: '失败', + backfill_tips: '新创建子工作流还未执行,不能进入子工作流', + task_instance_tips: '该任务还未执行,不能进入子工作流', + branch_tips: '成功分支流转和失败分支流转不能选择同一个节点', + timeout_alarm: '超时告警', + timeout_strategy: '超时策略', + timeout_strategy_tips: '超时策略必须选一个', + timeout_failure: '超时失败', + timeout_period: '超时时长', + timeout_period_tips: '超时时长必须为正整数', + script: '脚本', + script_tips: '请输入脚本(必填)', + resources: '资源', + resources_tips: '请选择资源', + no_resources_tips: '请删除所有未授权或已删除资源', + useless_resources_tips: '未授权或已删除资源', + custom_parameters: '自定义参数', + copy_failed: '该浏览器不支持自动复制', + prop_tips: 'prop(必填)', + prop_repeat: 'prop中有重复', + value_tips: 'value(选填)', + value_required_tips: 'value(必填)', + pre_tasks: '前置任务', + program_type: '程序类型', + main_class: '主函数的Class', + main_class_tips: '请填写主函数的Class', + main_package: '主程序包', + main_package_tips: '请选择主程序包', + deploy_mode: '部署方式', + app_name: '任务名称', + app_name_tips: '请输入任务名称(选填)', + driver_cores: 'Driver核心数', + driver_cores_tips: '请输入Driver核心数', + driver_memory: 'Driver内存数', + driver_memory_tips: '请输入Driver内存数', + executor_number: 'Executor数量', + executor_number_tips: '请输入Executor数量', + executor_memory: 'Executor内存数', + executor_memory_tips: '请输入Executor内存数', + executor_cores: 'Executor核心数', + executor_cores_tips: '请输入Executor核心数', + main_arguments: '主程序参数', + main_arguments_tips: '请输入主程序参数', + option_parameters: '选项参数', + option_parameters_tips: '请输入选项参数', + positive_integer_tips: '应为正整数', + flink_version: 'Flink版本', + job_manager_memory: 'JobManager内存数', + job_manager_memory_tips: '请输入JobManager内存数', + task_manager_memory: 'TaskManager内存数', + task_manager_memory_tips: '请输入TaskManager内存数', + slot_number: 'Slot数量', + slot_number_tips: '请输入Slot数量', + parallelism: '并行度', + custom_parallelism: '自定义并行度', + parallelism_tips: '请输入并行度', + parallelism_number_tips: '并行度必须为正整数', + parallelism_complement_tips: + '如果存在大量任务需要补数时,可以利用自定义并行度将补数的任务线程设置成合理的数值,避免对服务器造成过大的影响', + task_manager_number: 'TaskManager数量', + task_manager_number_tips: '请输入TaskManager数量', + http_url: '请求地址', + http_url_tips: '请填写请求地址(必填)', + http_url_validator: '请求地址需包含http或者https', + http_method: '请求类型', + http_parameters: '请求参数', + http_check_condition: '校验条件', + http_condition: '校验内容', + http_condition_tips: '请填写校验内容', + timeout_settings: '超时设置', + connect_timeout: '连接超时', + ms: '毫秒', + socket_timeout: 'Socket超时', + status_code_default: '默认响应码200', + status_code_custom: '自定义响应码', + body_contains: '内容包含', + body_not_contains: '内容不包含', + body_check_custom: '自定义内容校验', + http_parameters_position: '参数位置', + target_task_name: '目标任务名', + target_task_name_tips: '请输入Pigeon任务名(必填)', + datasource_type: '数据源类型', + datasource_instances: '数据源实例', + datasource_require_tips: '数据源不能为空', + sql_type: 'SQL类型', + sql_type_query: '查询', + sql_type_non_query: '非查询', + sql_statement: 'SQL语句', + pre_sql_statement: '前置SQL语句', + post_sql_statement: '后置SQL语句', + sql_input_placeholder: '请输入非查询SQL语句', + sql_empty_tips: '语句不能为空', + procedure_method: 'SQL语句', + procedure_method_tips: '请输入存储脚本', + procedure_method_snippet: + '--请输入存储脚本 \n\n--调用存储过程: call [(,, ...)] \n\n--调用存储函数:?= call [(,, ...)]', + start: '运行', + edit: '编辑', + copy: '复制节点', + delete: '删除', + custom_job: '自定义任务', + custom_script: '自定义脚本', + sqoop_job_name: '任务名称', + sqoop_job_name_tips: '请输入任务名称(必填)', + direct: '流向', + hadoop_custom_params: 'Hadoop参数', + sqoop_advanced_parameters: 'Sqoop参数', + data_source: '数据来源', + type: '类型', + datasource: '数据源', + datasource_tips: '请选择数据源', + model_type: '模式', + form: '表单', + table: '表名', + table_tips: '请输入Mysql表名(必填)', + column_type: '列类型', + all_columns: '全表导入', + some_columns: '选择列', + column: '列', + column_tips: '请输入列名,用 , 隔开', + database: '数据库', + database_tips: '请输入Hive数据库(必填)', + zeppelin_note_id: 'zeppelinNoteId', + zeppelin_note_id_tips: '请输入zeppelin note id', + zeppelin_paragraph_id: 'zeppelinParagraphId', + zeppelin_production_note_directory: '生产模式下存放克隆note的目录', + zeppelin_production_note_directory_tips: + '请输入生产环境note目录以启用生产模式', + zeppelin_paragraph_id_tips: '请输入zeppelin paragraph id', + zeppelin_parameters: 'parameters', + zeppelin_parameters_tips: '请输入zeppelin dynamic form参数', + zeppelin_rest_endpoint: 'zeppelinRestEndpoint', + zeppelin_rest_endpoint_tips: '请输入zeppelin server的rest endpoint', + hive_table_tips: '请输入Hive表名(必填)', + hive_partition_keys: 'Hive 分区键', + hive_partition_keys_tips: '请输入分区键', + hive_partition_values: 'Hive 分区值', + hive_partition_values_tips: '请输入分区值', + export_dir: '数据源路径', + export_dir_tips: '请输入数据源路径(必填)', + sql_statement_tips: 'SQL语句(必填)', + map_column_hive: 'Hive类型映射', + map_column_java: 'Java类型映射', + data_target: '数据目的', + create_hive_table: '是否创建新表', + drop_delimiter: '是否删除分隔符', + over_write_src: '是否覆盖数据源', + hive_target_dir: 'Hive目标路径', + hive_target_dir_tips: '请输入Hive临时目录', + replace_delimiter: '替换分隔符', + replace_delimiter_tips: '请输入替换分隔符', + target_dir: '目标路径', + target_dir_tips: '请输入目标路径(必填)', + delete_target_dir: '是否删除目录', + compression_codec: '压缩类型', + file_type: '保存格式', + fields_terminated: '列分隔符', + fields_terminated_tips: '请输入列分隔符', + lines_terminated: '行分隔符', + lines_terminated_tips: '请输入行分隔符', + is_update: '是否更新', + update_key: '更新列', + update_key_tips: '请输入更新列', + update_mode: '更新类型', + only_update: '只更新', + allow_insert: '无更新便插入', + concurrency: '并发度', + concurrency_tips: '请输入并发度', + sea_tunnel_master: 'Master', + sea_tunnel_master_url: 'Master URL', + sea_tunnel_queue: '队列', + sea_tunnel_master_url_tips: '请直接填写地址,例如:127.0.0.1:7077', + switch_condition: '条件', + switch_branch_flow: '分支流转', + switch_branch_flow_tips: '请选择流转分支', + and: '且', + or: '或', + datax_custom_template: '自定义模板', + datax_json_template: 'JSON', + datax_target_datasource_type: '目标源类型', + datax_target_database: '目标源实例', + datax_target_table: '目标表', + datax_target_table_tips: '请输入目标表名', + datax_target_database_pre_sql: '目标库前置SQL', + datax_target_database_post_sql: '目标库后置SQL', + datax_non_query_sql_tips: '请输入非查询SQL语句', + datax_job_speed_byte: '限流(字节数)', + datax_job_speed_byte_info: '(KB,0代表不限制)', + datax_job_speed_record: '限流(记录数)', + datax_job_speed_record_info: '(0代表不限制)', + datax_job_runtime_memory: '运行内存', + datax_job_runtime_memory_xms: '最小内存', + datax_job_runtime_memory_xmx: '最大内存', + datax_job_runtime_memory_unit: 'G', + chunjun_json_template: 'JSON 脚本', + current_hour: '当前小时', + last_1_hour: '前1小时', + last_2_hour: '前2小时', + last_3_hour: '前3小时', + last_24_hour: '前24小时', + today: '今天', + last_1_days: '昨天', + last_2_days: '前两天', + last_3_days: '前三天', + last_7_days: '前七天', + this_week: '本周', + last_week: '上周', + last_monday: '上周一', + last_tuesday: '上周二', + last_wednesday: '上周三', + last_thursday: '上周四', + last_friday: '上周五', + last_saturday: '上周六', + last_sunday: '上周日', + this_month: '本月', + this_month_begin: '本月初', + last_month: '上月', + last_month_begin: '上月初', + last_month_end: '上月末', + month: '月', + week: '周', + day: '日', + hour: '时', + add_dependency: '添加依赖', + waiting_dependent_start: '等待依赖启动', + check_interval: '检查间隔', + waiting_dependent_complete: '等待依赖完成', + project_name: '项目名称', + project_name_tips: '项目名称(必填)', + process_name: '工作流名称', + process_name_tips: '工作流名称(必填)', + cycle_time: '时间周期', + cycle_time_tips: '时间周期(必填)', + date_tips: '日期(必填)', + rule_name: '规则名称', + null_check: '空值检测', + custom_sql: '自定义SQL', + multi_table_accuracy: '多表准确性', + multi_table_value_comparison: '两表值比对', + field_length_check: '字段长度校验', + uniqueness_check: '唯一性校验', + regexp_check: '正则表达式', + timeliness_check: '及时性校验', + enumeration_check: '枚举值校验', + table_count_check: '表行数校验', + src_connector_type: '源数据类型', + src_datasource_id: '源数据源', + src_table: '源数据表', + src_filter: '源表过滤条件', + src_field: '源表检测列', + statistics_name: '实际值名', + check_type: '校验方式', + operator: '校验操作符', + threshold: '阈值', + failure_strategy: '失败策略', + target_connector_type: '目标数据类型', + target_datasource_id: '目标数据源', + target_table: '目标数据表', + target_filter: '目标表过滤条件', + mapping_columns: 'ON语句', + statistics_execute_sql: '实际值计算SQL', + comparison_name: '期望值名', + comparison_execute_sql: '期望值计算SQL', + comparison_type: '期望值类型', + writer_connector_type: '输出数据类型', + writer_datasource_id: '输出数据源', + target_field: '目标表检测列', + field_length: '字段长度限制', + logic_operator: '逻辑操作符', + regexp_pattern: '正则表达式', + deadline: '截止时间', + datetime_format: '时间格式', + enum_list: '枚举值列表', + begin_time: '起始时间', + fix_value: '固定值', + fix_value_tips: '请输入固定值', + required: '必填', + emr_flow_define_json: 'jobFlowDefineJson', + emr_flow_define_json_tips: '请输入工作流定义', + emr_steps_define_json: 'stepsDefineJson', + emr_steps_define_json_tips: '请输入EMR步骤定义', + send_email: '发送邮件', + log_display: '日志显示', + rows_of_result: '行查询结果', + title: '主题', + title_tips: '请输入邮件主题', + alarm_group: '告警组', + alarm_group_tips: '告警组必填', + integer_tips: '请输入一个正整数', + sql_parameter: 'sql参数', + format_tips: '请输入格式为', + udf_function: 'UDF函数', + unlimited: '不限制', + please_select_source_connector_type: '请选择源数据类型', + please_select_source_datasource_id: '请选择源数据源', + please_enter_source_table_name: '请选择源数据表', + please_enter_filter_expression: '请输入源表过滤条件', + please_enter_column_only_single_column_is_supported: '请选择源表检测列', + please_enter_threshold_number_is_needed: '请输入阈值', + please_enter_comparison_title: '请选择期望值类型', + please_enter_statistics_execute_sql: '请输入实际值计算SQL', + please_enter_statistics_name_the_alias_in_statistics_execute_sql: + '请输入实际值名', + please_select_target_connector_type: '请选择目标数据类型', + please_select_target_datasource: '请选择目标数据源', + please_enter_target_table: '请选择目标数据表', + please_enter_target_filter_expression: '请输入目标表过滤条件', + please_enter_comparison_name_the_alias_in_comparison_execute_sql: + '请输入期望值名', + please_enter_comparison_execute_sql: '请输入期望值计算SQL', + please_enter_length_limit: '请输入长度限制', + scan_type: '扫描类型', + scan_interval: '扫描间隔', + file_path: '文件地址', + file_path_tips: '文件地址(必填)', + topic_name: 'Topic名称', + topic_name_tips: 'Topic名称(必填)', + offset_time: 'Offset Time', + offset_time_required_tips: + // eslint-disable-next-line quotes + "Offset Time(必填),格式为${'{'}system.biz.curdate{'}'}、$[yyyyMMdd]或yyyyMMdd", + offset_time_incorrect_tips: + // eslint-disable-next-line quotes + "请输入正确的Offset Time格式(${'{'}system.biz.curdate{'}'}、$[yyyyMMdd]或yyyyMMdd)", + bootstrap_servers: 'Bootstrap Servers', + bootstrap_servers_tips: 'Bootstrap Servers(必填)', + group_id: 'GroupId', + group_id_tips: 'GroupId(必填)', + key_deserializer: 'Key Deserializer', + key_deserializer_tips: 'Key Deserializer(必填)', + value_deserializer: 'Value Deserializer', + value_deserializer_tips: 'Value Deserializer(必填)', + next_loop_custom_rule: '自定义牌规则', + script_type: '脚本类型', + next_loop_date_tip: '脚本返回值为日期格式: yyyy-MM-dd HH:mm:ss', + next_loop_timezone_tip: '注意:返回日期与调度时区保持一致', + next_loop_card: '牌(必填)', + next_loop_card_tips: '请选择牌', + dependency_strategy: '依赖策略', + failure: '失败', + continue: '继续', + wait: '等待', + failure_continue_tips: '依赖任务失败时,当前节点继续执行。', + failure_wait_tips: '依赖任务失败时,当前节点等待依赖成功,继续执行。', + hive_cli_task_execution_type: 'Hive Cli 任务类型', + hive_sql_script: 'Hive SQL 脚本', + hive_cli_options: 'Hive Cli 选项', + hive_cli_options_tips: '请输入您的HIVE CLI选项,如--verbose等', + jupyter_conda_env_name: 'condaEnvName', + jupyter_conda_env_name_tips: '请输入papermill所在的conda环境名', + jupyter_input_note_path: 'inputNotePath', + jupyter_input_note_path_tips: '请输入jupyter note的输入路径', + jupyter_output_note_path: 'outputNotePath', + jupyter_output_note_path_tips: '请输入jupyter note的输出路径', + jupyter_parameters: 'parameters', + jupyter_parameters_tips: '请输入jupyter parameterization参数', + jupyter_kernel: 'kernel', + jupyter_kernel_tips: '请输入jupyter kernel名', + jupyter_engine: 'engine', + jupyter_engine_tips: '请输入引擎名称', + jupyter_execution_timeout: 'executionTimeout', + jupyter_execution_timeout_tips: '请输入jupyter note cell的执行最长时间', + jupyter_start_timeout: 'startTimeout', + jupyter_start_timeout_tips: '请输入jupyter kernel的启动最长时间', + jupyter_others: 'others', + jupyter_others_tips: '请输入papermill的其他参数', + mlflow_algorithm: '算法', + mlflow_algorithm_tips: 'svm', + mlflow_params: '参数', + mlflow_params_tips: ' ', + mlflow_searchParams: '参数搜索空间', + mlflow_searchParams_tips: ' ', + mlflow_isSearchParams: '是否搜索参数', + mlflow_dataPath: '数据路径', + mlflow_dataPath_tips: + ' 文件/文件夹的绝对路径, 若文件需以.csv结尾, 文件夹需包含train.csv和test.csv ', + mlflow_dataPath_error_tips: ' 数据路径不能为空 ', + mlflow_experimentName: '实验名称', + mlflow_experimentName_tips: 'experiment_001', + mlflow_registerModel: '注册模型', + mlflow_modelName: '注册的模型名称', + mlflow_modelName_tips: 'model_001', + mlflow_mlflowTrackingUri: 'MLflow Tracking Server URI', + mlflow_mlflowTrackingUri_tips: 'http://127.0.0.1:5000', + mlflow_mlflowTrackingUri_error_tips: ' MLflow Tracking Server URI 不能为空', + mlflow_jobType: '任务类型', + mlflow_automlTool: 'AutoML工具', + mlflow_taskType: 'MLflow 任务类型', + mlflow_deployType: '部署类型', + mlflow_deployModelKey: '部署的模型URI', + mlflow_deployPort: '监听端口', + mlflowProjectRepository: '运行仓库', + mlflowProjectRepository_tips: '可以为git仓库或worker上的路径', + mlflowProjectVersion: '项目版本', + mlflowProjectVersion_tips: '项目git版本', + mlflow_cpuLimit: '最大cpu限制', + mlflow_memoryLimit: '最大内存限制', + openmldb_zk_address: 'zookeeper地址', + openmldb_zk_address_tips: '请输入zookeeper地址', + openmldb_zk_path: 'zookeeper路径', + openmldb_zk_path_tips: '请输入zookeeper路径', + openmldb_execute_mode: '执行模式', + openmldb_execute_mode_tips: '请选择执行模式', + openmldb_execute_mode_offline: '离线', + openmldb_execute_mode_online: '在线', + dvc_task_type: 'DVC任务类型', + dvc_repository: 'DVC仓库', + dvc_repository_tips: '请输入DVC仓库地址', + dvc_version: '数据版本', + dvc_version_tips: '数据版本标识,会以git tag的形式标记', + dvc_data_location: 'DVC仓库中的数据路径', + dvc_message: '提交信息', + dvc_load_save_data_path: 'Worker中数据路径', + dvc_store_url: '数据存储地址', + dvc_empty_tips: '该参数不能为空', + dinky_address: 'dinky 地址', + dinky_address_tips: '请输入 Dinky 地址', + dinky_task_id: 'dinky 作业ID', + dinky_task_id_tips: '请输入作业 ID', + dinky_online: '是否上线作业', + pytorch_script: 'python脚本', + pytorch_script_params: '脚本启动参数', + pytorch_other_params: '展开更多配置', + pytorch_python_path: 'python项目地址', + pytorch_is_create_environment: '是否创建新环境', + pytorch_python_command: 'python命令路径', + pytorch_python_command_tips: '若为空,则使用$PYTHON_HOME', + pytorch_python_env_tool: 'python环境管理工具', + pytorch_requirements: '依赖文件', + pytorch_conda_python_version: 'python版本', + pytorch_conda_python_version_tips: '请输入版本号,如 3.6, 3.7, 3.x等', + natural: '自然日', + business: '业务日期', + card: '牌', + rename: '重命名', + name_node: '命名', + custom_config: '自定义配置', + engine: '引擎', + engine_tips: '请选择引擎', + run_mode: '运行模式', + cart_null_tips: '工作流未设置牌值', + execute_type: '执行类型', + java_option_tips: '请输入Java Option', + dependent_path: '依赖包路径', + dependent_path_tips: '请输入依赖包路径', + offline_task: '离线任务', + stream_task: '实时任务', + local_file: '本地文件', + git_file: 'Git文件', + whale_seatunnel_task_tips: '请选择任务', + break_continue: '断点续传', + remote_connection: '远程连接', + connection_type: '连接类型', + source_name: '源名称', + ssh: 'SSH', + connection_type_tips: '连接类型不能为空', + source_name_tips: '源名称不能为空', + folder_name: '文件夹名称', + mapping_name: 'informatica工作流名称', + target_name: '目标名称', + folder_name_tips: '请选择文件夹名称(必填)', + mapping_name_tips: '请选择informatica工作流名称(必填)', + source_type_tips: '请选择源名称(必填)', + target_type_tips: '请选择目标名称(必填)', + source_name_tips_infa: '请输入源名称(必填)', + target_name_tips: '请输入目标名称(必填)' + }, + isolation: { + upload_isolation_tasks: '导入隔离任务', + download_template: '下载模板', + task_name: '任务名称', + workflow_instance_name: '工作流实例名称', + operation_status: '运行状态', + create_time: '创建时间', + online_isolation: '上线隔离', + cancel_isolation: '取消隔离', + task_isolation: '任务隔离', + local_upload: '本地导入', + isolation_tasks: '隔离任务列表', + show_more: '显示更多', + previous: '上一步', + next: '下一步', + upload_tips: '仅支持上传Excel文件类型,每次只能上传一个Excel文件', + sure: '确定', + cancel: '取消', + operation: '操作', + online: '上线', + offline: '下线', + total_items: '共{total}条', + file_tips: '请选择一个excel文件' + }, + synchronization_definition: { + node_prev_check_tips: '当前节点尚未连接前置节点', + create_synchronization_task: '创建同步任务', + edit_synchronization_task: '编辑同步任务', + synchronization_task_name: '同步任务名称', + task_describe: '任务描述', + create_user: '创建用户', + create_time: '创建时间', + update_user: '更新用户', + update_time: '更新时间', + operation: '操作', + edit: '编辑', + start: '运行', + delete: '删除', + delete_confirm: '确定删除吗?', + task_name_tips: '请输入任务名称', + task_describe_tips: '请输入任务描述', + task_name_validate: '任务名称不能为空', + copy: '复制', + open_full_screen: '打开全屏', + close_full_screen: '关闭全屏', + format: '格式化', + save: '保存', + close: '关闭', + layout_type: '布局类型', + rows: '行数', + cols: '列数', + dagre: '层次布局', + grid: '网格布局', + format_canvas: '格式化画布', + setting: '设置', + task_name: '任务名称', + description: '描述', + engine: '引擎', + task_name_placeholder: '请输入任务名称', + description_placeholder: '请输入描述', + node_name_validate: '节点名称不为空', + node_name: '节点名称', + node_name_placeholder: '请输入节点名称', + transforms: '处理与转换', + source_and_sink: '来源和目标', + confirm: '确定', + cancel: '取消', + configuration: '配置', + model: '模型', + business_model: '业务模型', + whole_library_sync: '整库同步', + data_integration: '数据集成', + source_name: '源名称', + source_name_validate: '源名称不能为空', + scene_mode: '场景模式', + multi_table_sync: '多表同步', + sub_library_and_sub_table: '分库分表', + single_table_sync: '单表同步', + database: '数据库', + table_name: '表名', + field_name: '字段名称', + field_type: '字段类型', + non_empty: '非空', + field_comment: '字段注释', + input_table_structure: '输入表结构', + output_table_structure: '输出表结构', + exclude_kind: '排除种类', + include_kind: '包括种类', + exclude_kind_validate: '排除种类不能为空', + include_kind_validate: '包括种类不能为空', + database_validate: '数据库不能为空', + table_name_validate: '表名不能为空', + primary_key: '主键', + default_value: '默认值', + scene_mode_validate: '场景模式不能为空', + kind: '类型', + delete_empty_tips: '请选择一个节点', + database_exception_message: '数据库异常,已在表单中被清除', + table_exception_message: '表异常,已在表单中被清除', + start_node_tips: '起始节点需要是Source节点', + end_node_tips: '结束节点需要是Sink节点', + save_node_tips: '请先保存画布中的节点', + table_sync: '待同步表', + selected_table: '已选中表', + original_field: '原字段', + name_tips: '请输入一个字段名', + move_to_top: '移动到顶部', + move_to_bottom: '移动到底部', + move_up: '向上一级', + move_down: '向下一级', + yes: '是', + no: '否', + split: '分割', + split_field: '字段拆分', + separator: '分割符', + separator_tips: '请输入自定义的分割符', + segmented_fields: '拆分后的字段', + segmented_fields_placeholder: '如分割出来两个字段,可以填写 field1, field2', + copy_field: '复制字段', + check_model: '请检查模型信息', + sql_content_label: 'sql', + sql_content_label_placeholder: '请输入sql语句', + query_validate: '请输入sql语句' + }, + synchronization_instance: { + pipeline_id: 'Pipeline ID', + source: 'Source', + sink: 'Sink', + run_often: '运行时长', + fail: '失败', + running: '运行中', + pause: '暂停', + success: '成功', + state: '状态', + start_time: '开始时间', + end_time: '结束时间', + operation: '操作', + real_time_sync: '实时同步', + offline_sync: '离线同步', + sync_task_definition: '同步任务定义', + data_pipeline_running_instance: '数据管道运行实例', + task_name: '任务名称', + workflow_instance: '工作流实例', + execute_user: '执行用户', + host: '主机', + amount_of_data_read: '已读取数据量(行)', + read_rate: '读取速率(行/秒)', + processing_rate: '处理速率(行/秒)', + amount_of_data_written: '已写入数据量(行)', + delay_of_data: '数据延迟(秒)', + node_type: '节点类型', + submit_time: '提交时间', + run_time: '运行时间', + number_of_retries: '重试次数', + rerun_mark: '重跑标识', + clean_state: '清除并重跑', + forced_success: '强制成功', + view_log: '查看日志', + download_log: '下载日志', + description: '描述', + engine: '引擎', + write: '写', + read: '读', + line: '行' + }, + menu: { + fav: '收藏组件', + universal: '通用组件', + cloud: '云', + logic: '逻辑节点', + di: '数据集成', + dq: '数据质量', + ml: '机器学习', + other: '其他' + }, + member: { + alias: '用户名称', + role_name: '角色名称', + member_manage: '成员管理', + new_member: '新增成员', + remove_member: '移除成员', + confirm: '确定', + cancel: '取消', + edit: '编辑', + operation: '操作' + }, + synchronizing_task_instance: '同步任务实例', + task_instance: '任务实例', + column: ' 列 ', + all_column: '全部', + select_workflow_instance: '请选择工作流实例', + select_workflow_definition: '请选择工作流定义', + select_task_instance: '请选择任务实例', + can_not_many: '暂不支持跨项目操作', + all_project: '全部项目', + next_step: '下一步', + pre_step: '上一步', + project_name: '项目名称' +} diff --git a/seatunnel-ui/src/locales/zh_CN/theme.ts b/seatunnel-ui/src/locales/zh_CN/theme.ts new file mode 100644 index 000000000..0cf3a8678 --- /dev/null +++ b/seatunnel-ui/src/locales/zh_CN/theme.ts @@ -0,0 +1,22 @@ +/* + * 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 { + light: '浅色', + dark: '深色', + dark_blue: '深蓝色' +} diff --git a/seatunnel-ui/src/main.ts b/seatunnel-ui/src/main.ts index c648237c4..0189a5982 100644 --- a/seatunnel-ui/src/main.ts +++ b/seatunnel-ui/src/main.ts @@ -23,6 +23,7 @@ import i18n from '@/locales' import router from './router' import utils from '@/utils' import './index.css' +import './assets/styles/default.scss' const meta = document.createElement('meta') meta.name = 'naive-ui-style' diff --git a/seatunnel-ui/src/router/data-pipes.ts b/seatunnel-ui/src/router/data-pipes.ts index 9a06ede61..4065c7a01 100644 --- a/seatunnel-ui/src/router/data-pipes.ts +++ b/seatunnel-ui/src/router/data-pipes.ts @@ -35,7 +35,8 @@ export default { name: 'data-pipes-list', component: components['data-pipes-list'], meta: { - title: 'data-pipes-list' + title: 'data-pipes-list', + activeMenu: 'data-pipes' } }, { @@ -43,7 +44,8 @@ export default { name: 'data-pipes-detail', component: components['data-pipes-detail'], meta: { - title: 'data-pipes-detail' + title: 'data-pipes-detail', + activeMenu: 'data-pipes' } }, { @@ -51,7 +53,8 @@ export default { name: 'data-pipes-edit', component: components['data-pipes-edit'], meta: { - title: 'data-pipes-edit' + title: 'data-pipes-edit', + activeMenu: 'data-pipes' } }, { @@ -59,7 +62,8 @@ export default { name: 'data-pipes-create', component: components['data-pipes-create'], meta: { - title: 'data-pipes-create' + title: 'data-pipes-create', + activeMenu: 'data-pipes' } } ] diff --git a/seatunnel-ui/src/router/datasource.ts b/seatunnel-ui/src/router/datasource.ts index 9a8de1cc9..ae609ccf8 100644 --- a/seatunnel-ui/src/router/datasource.ts +++ b/seatunnel-ui/src/router/datasource.ts @@ -35,7 +35,8 @@ export default { name: 'datasource-list', component: components['datasource-list'], meta: { - title: 'datasource-list' + title: 'datasource-list', + activeMenu: 'datasource' } }, { @@ -43,7 +44,8 @@ export default { name: 'datasource-create', component: components['datasource-create'], meta: { - title: 'datasource-create' + title: 'datasource-create', + activeMenu: 'datasource' } }, { @@ -51,7 +53,8 @@ export default { name: 'datasource-edit', component: components['datasource-create'], meta: { - title: 'datasource-edit' + title: 'datasource-edit', + activeMenu: 'datasource' } } ] diff --git a/seatunnel-ui/src/router/jobs.ts b/seatunnel-ui/src/router/jobs.ts index 24c21cb33..1f210ff35 100644 --- a/seatunnel-ui/src/router/jobs.ts +++ b/seatunnel-ui/src/router/jobs.ts @@ -35,7 +35,8 @@ export default { name: 'jobs-list', component: components['jobs-list'], meta: { - title: 'jobs-list' + title: 'jobs-list', + activeMenu: 'job' } } ] diff --git a/seatunnel-ui/src/router/sync-task.ts b/seatunnel-ui/src/router/sync-task.ts new file mode 100644 index 000000000..910e18773 --- /dev/null +++ b/seatunnel-ui/src/router/sync-task.ts @@ -0,0 +1,80 @@ +/* + * 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 utils from '@/utils' +import type { Component } from 'vue' + +const modules = import.meta.glob('/src/views/**/**.tsx') +const components: { [key: string]: Component } = utils.mapping(modules) + +export default { + path: '/tasks', + name: 'tasks', + meta: { + title: 'tasks' + }, + redirect: { name: 'tasks-list' }, + component: () => import('@/layouts/dashboard'), + children: [ + { + path: '/task/synchronization-definition', + name: 'synchronization-definition', + component: components['projects-task-synchronization-definition'], + meta: { + title: '同步任务定义', + activeMenu: 'projects', + showSide: true + // auth: 'project:seatunnel-task:view' + } + }, + { + path: '/task/synchronization-definition/:jobDefinitionCode', + name: 'synchronization-definition-dag', + component: components['projects-task-synchronization-definition-dag'], + meta: { + title: '同步任务定义画布', + activeMenu: 'projects', + activeSide: '/task/synchronization-definition', + showSide: true, + auth: ['project:seatunnel-task:create', 'project:seatunnel-task:update'] + } + }, + { + path: '/task/synchronization-instance', + name: 'synchronization-instance', + component: components['projects-task-synchronization-instance'], + meta: { + title: '同步任务实例', + activeMenu: 'projects', + showSide: true + // auth: 'project:seatunnel-task-instance:view' + } + }, + { + path: '/task/synchronization-instance/:taskCode', + name: 'synchronization-instance-detail', + component: components['projects-task-synchronization-instance-detail'], + meta: { + title: '同步任务实例详情', + activeMenu: 'projects', + activeSide: '/task/synchronization-instance', + showSide: true, + auth: 'project:seatunnel-task-instance:details' + } + } + ] +} diff --git a/seatunnel-ui/src/router/tasks.ts b/seatunnel-ui/src/router/tasks.ts index 8e1dd713f..08b27fa10 100644 --- a/seatunnel-ui/src/router/tasks.ts +++ b/seatunnel-ui/src/router/tasks.ts @@ -27,15 +27,51 @@ export default { meta: { title: 'tasks' }, - redirect: { name: 'tasks-list' }, + redirect: { name: 'synchronization-definition' }, component: () => import('@/layouts/dashboard'), children: [ { - path: '/tasks/list', - name: 'tasks-list', - component: components['tasks-list'], + path: '/task/synchronization-definition', + name: 'synchronization-definition', + component: components['task-synchronization-definition'], meta: { - title: 'tasks-list' + title: '同步任务定义', + activeMenu: 'tasks', + activeSide: 'synchronization-definition', + showSide: true + } + }, + { + path: '/task/synchronization-definition/:jobDefinitionCode', + name: 'synchronization-definition-dag', + component: components['task-synchronization-definition-dag'], + meta: { + title: '同步任务定义画布', + activeMenu: 'tasks', + activeSide: 'synchronization-definition', + showSide: true, + } + }, + { + path: '/task/synchronization-instance', + name: 'synchronization-instance', + component: components['task-synchronization-instance'], + meta: { + title: '同步任务实例', + activeMenu: 'tasks', + activeSide: 'synchronization-instance', + showSide: true + } + }, + { + path: '/task/synchronization-instance/:taskCode', + name: 'synchronization-instance-detail', + component: components['task-synchronization-instance-detail'], + meta: { + title: '同步任务实例详情', + activeMenu: 'tasks', + activeSide: 'synchronization-instance', + showSide: true, } } ] diff --git a/seatunnel-ui/src/router/user-manage.ts b/seatunnel-ui/src/router/user-manage.ts index be0dcdc54..e1803faac 100644 --- a/seatunnel-ui/src/router/user-manage.ts +++ b/seatunnel-ui/src/router/user-manage.ts @@ -35,7 +35,8 @@ export default { name: 'user-manage-list', component: components['user-manage-list'], meta: { - title: 'user-manage-list' + title: 'user-manage-list', + activeMenu: 'user-manage', } } ] diff --git a/seatunnel-ui/src/router/virtual-tables.ts b/seatunnel-ui/src/router/virtual-tables.ts index 9224d2064..a097f2c0b 100644 --- a/seatunnel-ui/src/router/virtual-tables.ts +++ b/seatunnel-ui/src/router/virtual-tables.ts @@ -35,7 +35,26 @@ export default { name: 'virtual-tables-list', component: components['virtual-tables-list'], meta: { - title: 'virtual-tables-list' + title: 'virtual-tables-list', + activeMenu: 'virtual-tables', + } + }, + { + path: '/virtual-tables/creation', + name: 'virtual-tables-create', + component: components['virtual-tables-detail'], + meta: { + title: '虚拟表创建', + activeMenu: 'virtual-tables', + } + }, + { + path: '/virtual-tables/:id', + name: 'virtual-tables-editor', + component: components['virtual-tables-detail'], + meta: { + title: '虚拟表编辑', + activeMenu: 'virtual-tables', } } ] diff --git a/seatunnel-ui/src/service/data-source/index.ts b/seatunnel-ui/src/service/data-source/index.ts new file mode 100644 index 000000000..58228e469 --- /dev/null +++ b/seatunnel-ui/src/service/data-source/index.ts @@ -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 { axios } from '@/service/service' +import { + DatasourceListParameters, + DataSourceDetail, + DatasourceTestConnectParameters +} from './types' + +const DATASOURCE_BASE_URL = '/datasource' + +export function createDatasource(data: DataSourceDetail): any { + return axios({ + url: DATASOURCE_BASE_URL + '/create', + method: 'post', + data + }) +} + +export function updateDatasource(data: DataSourceDetail, id: string): any { + return axios({ + url: DATASOURCE_BASE_URL + '/' + id, + method: 'put', + data + }) +} + +// export function checkConnect(data: any): any { +// return axios({ +// url: DATASOURCE_BASE_URL + '/check/connect', +// method: 'post', +// data, +// timeout: 0 +// }) +// } + +export function deleteDatasource(id: string): any { + return axios({ + url: DATASOURCE_BASE_URL + '/' + id, + method: 'delete' + }) +} + +export function getDatasourceDetail(id: string): any { + return axios({ + url: DATASOURCE_BASE_URL + '/' + id, + method: 'get' + }) +} + +export function getDatasourceList(params: DatasourceListParameters): any { + return axios({ + url: DATASOURCE_BASE_URL + '/list', + method: 'get', + params + }) +} + +export function getDatasourceType(params: { + showVirtualDataSource: boolean + source?: 'WS' | 'WT' +}): any { + return axios({ + url: DATASOURCE_BASE_URL + '/support-datasources', + method: 'get', + params + }) +} + +export function getDynamicFormItems(pluginName: string): any { + return axios({ + url: DATASOURCE_BASE_URL + '/dynamic-form', + method: 'get', + params: { pluginName } + }) +} + +export function getDatasourceTablesById(datasourceId: string, database: string): any { + return axios({ + url: `/data-quality/tables/${datasourceId}`, + method: 'get', + params: { + database + } + }) +} + +export function getDatasourceTableColumnsById( + datasourceId: string, + database: string, + tableName: string +): any { + return axios({ + url: '/ws/data-quality/schema', + method: 'get', + params: { + datasourceId, + database, + tableName + } + }) +} + +export function datasourceDetail(id: string): any { + return axios({ + url: `${DATASOURCE_BASE_URL}/` + id, + method: 'get' + }) +} + +export function datasourceAdd(data: any): any { + return axios({ + // url: '/datasource/create', + url: `${DATASOURCE_BASE_URL}/create`, + method: 'post', + data + }) +} + +export function datasourceUpdate(data: any, id: string): any { + return axios({ + url: `${DATASOURCE_BASE_URL}/` + id, + method: 'put', + data + }) +} + +export function checkConnect(data: any): any { + return axios({ + // url: '/datasource/check/connect', + url: `${DATASOURCE_BASE_URL}/check/connect`, + method: 'post', + data + }) +} + +export function dynamicFormItems(pluginName: string): any { + return axios({ + url: `${DATASOURCE_BASE_URL}/dynamic-form`, + method: 'get', + params: { pluginName } + }) +} + +export function datasourceList(params: any): any { + return axios({ + url: `${DATASOURCE_BASE_URL}/list`, + method: 'get', + params + }) +} + +export function datasourceDelete(id: string): any { + return axios({ + url: `${DATASOURCE_BASE_URL}/` + id, + method: 'delete' + }) +} diff --git a/seatunnel-ui/src/service/datasource/types.ts b/seatunnel-ui/src/service/data-source/types.ts similarity index 62% rename from seatunnel-ui/src/service/datasource/types.ts rename to seatunnel-ui/src/service/data-source/types.ts index a3af0f259..e3e304558 100644 --- a/seatunnel-ui/src/service/datasource/types.ts +++ b/seatunnel-ui/src/service/data-source/types.ts @@ -15,9 +15,47 @@ * limitations under the License. */ -interface DatasourceConfig {} +export type DatasourceConfig = { [key: string]: any } -interface DatasourceList { +export interface DataSourceDetail { + datasourceName: string + pluginName: string + description?: string + datasourceConfig?: string +} + +export interface DatasourceListParameters { + searchVal: string + pageNo: number + pageSize: number + pluginName?: string +} + +export interface DatasourceTestConnectParameters { + pluginName: string + datasourceConfig?: string +} + + +export interface DatasourceTypeList { + name: string + icon: string + version: string + type: number + supportVirtualTables: boolean +} +export interface DatasourceRecord { + id: string + datasourceName: string + pluginName: string + pluginVersion: string + description: string + createUserName: string + createTime: number + updateTime: number +} + +export interface DatasourceList { createUserName: string createTime: string updateUserName: string @@ -31,16 +69,3 @@ interface DatasourceList { 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/service/log/index.ts b/seatunnel-ui/src/service/log/index.ts new file mode 100644 index 000000000..46846171e --- /dev/null +++ b/seatunnel-ui/src/service/log/index.ts @@ -0,0 +1,46 @@ +/* + * 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' +import { AxiosRequestConfig } from 'axios' +import { IdReq, LogReq, TaskLogReq } from './types' + +export function queryLog(params: LogReq): any { + return axios({ + url: '/log/detail', + method: 'get', + params + }) +} + +export function downloadTaskLog(params: IdReq): any { + return axios({ + url: '/log/download-log', + method: 'get', + params + }) +} + +export function queryTaskLog(params: TaskLogReq): any { + return axios({ + url: '/ws/studio/taskLog', + method: 'get', + params, + retry: 3, + retryDelay: 1000 + } as AxiosRequestConfig) +} diff --git a/seatunnel-ui/src/service/log/types.ts b/seatunnel-ui/src/service/log/types.ts new file mode 100644 index 000000000..70adf06b5 --- /dev/null +++ b/seatunnel-ui/src/service/log/types.ts @@ -0,0 +1,40 @@ +/* + * 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. + */ + +interface IdReq { + taskInstanceId: number +} + +interface LogReq extends IdReq { + limit: number + skipLineNum: number +} + +interface LogRes { + log: string + currentLogLineNumber: number + hasNext: boolean +} + +interface TaskLogReq { + taskCode: number + commandId: number + skipLineNum: number + limit: number +} + +export { IdReq, LogReq, LogRes, TaskLogReq } diff --git a/seatunnel-ui/src/service/resources/index.ts b/seatunnel-ui/src/service/resources/index.ts new file mode 100644 index 000000000..e228faeeb --- /dev/null +++ b/seatunnel-ui/src/service/resources/index.ts @@ -0,0 +1,385 @@ +/* + * 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' +import utils from '@/utils' +import { ProjectCodeReq } from '../users/types' +import { + ResourceTypeReq, + ResourceTypesReq, + NameReq, + FileNameReq, + FullNameReq, + IdReq, + ContentReq, + DescriptionReq, + CreateReq, + UserIdReq, + OnlineCreateReq, + ProgramTypeReq, + ListReq, + ViewResourceReq, + ResourceIdReq, + UdfFuncReq, + FileType, + GitReq, + ResourceProjectReq, + AuthResourceReq, + ResourceListReq +} from './types' + +export function queryResourceListPaging( + params: ListReq & IdReq & ResourceTypeReq & ProjectCodeReq +): any { + return axios({ + url: '/resources', + method: 'get', + params + }) +} + +export function queryResourceById( + params: ResourceTypeReq & FullNameReq & IdReq, + id: number +): any { + return axios({ + url: `/resources/${id}`, + method: 'get', + params + }) +} + +export function queryCurrentResourceById(id: number): any { + return axios({ + url: `/resources/${id}/query`, + method: 'get' + }) +} + +export function createResource( + data: CreateReq & FileNameReq & NameReq & ResourceTypeReq +): any { + return axios({ + url: '/resources', + method: 'post', + data, + timeout: 0 + }) +} + +export function resourceBatchUpload(data: { + type: FileType + files: File[] + pid: string + currentDir: string +}): any { + return axios({ + url: '/resources/batch-upload', + method: 'post', + data, + timeout: 0 + }) +} + +export function resourceFolderUpload(data: { + type: FileType + files: File[] + pid: string + currentDir: string +}): any { + return axios({ + url: '/resources/upload-folder', + method: 'post', + data, + timeout: 0 + }) +} + +export function authorizedFile(params: UserIdReq): any { + return axios({ + url: '/resources/authed-file', + method: 'get', + params + }) +} + +export function authorizeResourceTree(params: UserIdReq): any { + return axios({ + url: '/resources/authed-resource-tree', + method: 'get', + params + }) +} + +export function authUDFFunc(params: UserIdReq): any { + return axios({ + url: '/resources/authed-udf-func', + method: 'get', + params + }) +} + +export function createDirectory( + data: CreateReq & NameReq & ResourceTypeReq +): any { + return axios({ + url: '/resources/directory', + method: 'post', + data + }) +} + +export function queryResourceList(params: ResourceTypeReq): any { + return axios({ + url: '/resources/list', + method: 'get', + params + }) +} + +export function onlineCreateResource( + data: OnlineCreateReq & FileNameReq & ResourceTypeReq +): any { + return axios({ + url: '/resources/online-create', + method: 'post', + data + }) +} + +export function queryResourceByProgramType( + params: ResourceTypeReq & ProgramTypeReq +): any { + return axios({ + url: '/resources/query-by-type', + method: 'get', + params + }) +} + +export function queryUdfFuncListPaging(params: ListReq & ProjectCodeReq): any { + return axios({ + url: '/resources/udf-func', + method: 'get', + params + }) +} + +export function queryUdfFuncList(params: { type: 'HIVE' | 'SPARK' }): any { + return axios({ + url: '/resources/udf-func/list', + method: 'get', + params + }) +} + +export function verifyUdfFuncName(params: NameReq): any { + return axios({ + url: '/resources/udf-func/verify-name', + method: 'get', + params + }) +} + +export function deleteUdfFunc(id: number): any { + return axios({ + url: `/resources/udf-func/${id}`, + method: 'delete' + }) +} + +export function unAuthUDFFunc(params: UserIdReq): any { + return axios({ + url: '/resources/unauth-udf-func', + method: 'get', + params + }) +} + +export function verifyResourceName(params: FullNameReq & ResourceTypeReq): any { + return axios({ + url: '/resources/verify-name', + method: 'get', + params + }) +} + +export function queryResource( + params: FullNameReq & ResourceTypeReq, + id: IdReq +): any { + return axios({ + url: `/resources/verify-name/${id}`, + method: 'get', + params + }) +} + +export function updateResource( + data: NameReq & ResourceTypeReq & IdReq & DescriptionReq, + id: number +): any { + return axios({ + url: `/resources/${id}`, + method: 'put', + data + }) +} + +export function deleteResource(id: number): any { + return axios({ + url: `/resources/${id}`, + method: 'delete' + }) +} + +export function downloadResource(id: number): void { + utils.downloadFile(`resources/${id}/download`) +} + +export function viewUIUdfFunction(id: IdReq): any { + return axios({ + url: `/resources/${id}/udf-func`, + method: 'get' + }) +} + +export function updateResourceContent(data: ContentReq, id: number): any { + return axios({ + url: `/resources/${id}/update-content`, + method: 'put', + data + }) +} + +export function viewResource(params: ViewResourceReq, id: number): any { + return axios({ + url: `/resources/${id}/view`, + method: 'get', + params + }) +} + +export function createUdfFunc( + data: UdfFuncReq, + resourceId: ResourceIdReq +): any { + return axios({ + url: `/resources/${resourceId}/udf-func`, + method: 'post', + data + }) +} + +export function updateUdfFunc( + data: UdfFuncReq, + resourceId: ResourceIdReq, + id: number +): any { + return axios({ + url: `/resources/${resourceId}/udf-func/${id}`, + method: 'put', + data + }) +} + +export function queryGitListPaging(params: ListReq & ProjectCodeReq): any { + return axios({ + url: '/git/file', + method: 'get', + params + }) +} + +export function getResourceProject(params: ResourceProjectReq): any { + return axios({ + url: '/ws/resources/project', + method: 'get', + params + }) +} + +export function createGitFile(data: GitReq): any { + return axios({ + url: '/git/file/create', + method: 'post', + data, + headers: { + 'Content-Type': 'application/json;charset=UTF-8' + }, + transformRequest: (data) => JSON.stringify(data) + }) +} + +export function authResourceProject(data: AuthResourceReq): any { + return axios({ + url: '/ws/resources/auth', + method: 'post', + data + }) +} + +export function updateGitFile(data: GitReq & ProjectCodeReq): any { + return axios({ + url: '/git/file/update', + method: 'post', + data, + headers: { + 'Content-Type': 'application/json;charset=UTF-8' + }, + transformRequest: (data) => JSON.stringify(data) + }) +} + +export function queryFileAndGitList(params: ResourceTypesReq): any { + return axios({ + url: '/git/file/select', + method: 'get', + params + }) +} + +export function getResourceList(params: ResourceListReq): any { + return axios({ + url: '/ws/resources/list', + method: 'get', + params + }) +} + +export function queryCurrentGitFileById(id: number): any { + return axios({ + url: `/git/file/${id}/query`, + method: 'get' + }) +} + +export function deleteGitFile(data: IdReq): any { + return axios({ + url: '/git/file/delete', + method: 'post', + data + }) +} + +export function refreshGitFile(data: IdReq): any { + return axios({ + url: '/git/file/refresh', + method: 'post', + data + }) +} diff --git a/seatunnel-ui/src/service/resources/types.ts b/seatunnel-ui/src/service/resources/types.ts new file mode 100644 index 000000000..6c270fbf3 --- /dev/null +++ b/seatunnel-ui/src/service/resources/types.ts @@ -0,0 +1,216 @@ +/* + * 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. + */ + +interface FileReq { + file: any +} + +interface ResourceTypeReq { + type: 'FILE' | 'UDF' | 'GIT' + programType?: string +} + +interface ResourceTypesReq { + types: string +} + +interface UdfTypeReq { + type: 'HIVE' | 'SPARK' +} + +interface NameReq { + name: string +} + +interface FileNameReq { + fileName: string +} + +interface FullNameReq { + fullName: string +} + +interface IdReq { + id: number +} + +interface ContentReq { + content: string +} + +interface DescriptionReq { + description?: string +} + +interface CreateReq extends ResourceTypeReq, DescriptionReq { + currentDir: string + pid: number +} + +interface UserIdReq { + userId: number +} + +interface OnlineCreateReq extends CreateReq, ContentReq { + suffix: string +} + +interface ProgramTypeReq { + programType: 'JAVA' | 'SCALA' | 'PYTHON' | 'SQL' +} + +interface ListReq { + pageNo: number + pageSize: number + searchVal?: string | null +} + +interface ViewResourceReq { + limit: number + skipLineNum: number +} + +interface ResourceIdReq { + resourceId: number +} + +interface UdfFuncReq extends UdfTypeReq, DescriptionReq, ResourceIdReq { + className: string + funcName: string + argTypes?: string + database?: string +} + +interface ResourceFile { + id: number + pid: number + alias: string + userId: number + userName: string + type: string + directory: boolean + fileName: string + fullName: string + description: string + size: number + updateTime: string + path: string + isEdit: boolean + globalResource: boolean + projectCode: number + projectName: string +} + +interface ResourceListRes { + currentPage: number + pageSize: number + start: number + totalPage: number + totalList: ResourceFile[] +} + +interface ResourceViewRes { + alias: string + content: string +} + +interface GitReq { + name?: string + gitUrl?: string + token?: string + description?: string + pid?: number + id?: number + gitProxy?: any +} + +interface GitFile { + id: number + pid: number + name: string + path: string + type: string +} + +type accessTypeKey = + | 'DATASOURCE' + | 'WORKER_GROUP' + | 'ALERT_GROUP' + | 'ALERT_INSTANCE' + | 'ENVIRONMENT' + | 'TIMING' + | 'CALENDAR' + | 'DATA_CARD' + | 'TENANT' + | 'TASK_GROUP' + | 'QUEUE' + | 'FILE' + | 'UDF_FILE' + | 'UDF_FUNC' + | 'GIT_FILE' + | 'GIT_RESOURCE' + +interface ResourceProjectReq { + accessType: accessTypeKey + accessCode: number +} + +interface AuthResourceReq { + globalResource: boolean + accessType: accessTypeKey + accessCode: number + projectCodes: number[] + isShare: boolean +} + +interface ResourceListReq { + accessType: accessTypeKey + projectCode?: number + resourceType?: string +} + +export type FileType = 'FILE' | 'UDF' + +export { + FileReq, + ResourceTypeReq, + ResourceTypesReq, + UdfTypeReq, + NameReq, + FileNameReq, + FullNameReq, + IdReq, + ContentReq, + DescriptionReq, + CreateReq, + UserIdReq, + OnlineCreateReq, + ProgramTypeReq, + ListReq, + ViewResourceReq, + ResourceIdReq, + UdfFuncReq, + ResourceListRes, + ResourceViewRes, + ResourceFile, + GitReq, + GitFile, + ResourceProjectReq, + AuthResourceReq, + ResourceListReq, + accessTypeKey +} diff --git a/seatunnel-ui/src/service/service.ts b/seatunnel-ui/src/service/service.ts index 8f3de6b9e..2ca1580b1 100644 --- a/seatunnel-ui/src/service/service.ts +++ b/seatunnel-ui/src/service/service.ts @@ -67,9 +67,20 @@ service.interceptors.request.use((config: InternalAxiosRequestConfig) => { }, err) service.interceptors.response.use((res: AxiosResponse) => { - if (res.data.success) return res.data + if (res.data.code === undefined) { + return res.data + } + + if (res.data.success) return res.data.data - handleError(res) + switch (res.data.code) { + case 0: + return res.data.data + + default: + handleError(res) + throw new Error() + } }, err) export { service as axios } diff --git a/seatunnel-ui/src/service/sync-task-definition/index.ts b/seatunnel-ui/src/service/sync-task-definition/index.ts new file mode 100644 index 000000000..17301af8c --- /dev/null +++ b/seatunnel-ui/src/service/sync-task-definition/index.ts @@ -0,0 +1,317 @@ +/* + * 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 querySyncTaskDefinitionPaging(params: any): any { + return axios({ + url: '/job/definition', + method: 'get', + params + }) +} + +export function createSyncTaskDefinition(data: any): any { + return axios({ + url: '/job/definition', + method: 'post', + data, + headers: { + 'Content-Type': 'application/json;charset=UTF-8' + }, + transformRequest: (params) => JSON.stringify(params) + }) +} + +export function deleteSyncTaskDefinition(params: any): any { + return axios({ + url: '/job/definition', + method: 'delete', + params + }) +} + +export function getDefinitionNodesAndEdges(jobCode: string): any { + return axios({ + url: `/job/${jobCode}`, + method: 'get' + }) +} + +export function getDefinitionDetail(jobCode: string): any { + return axios({ + url: `/job/definition/${jobCode}`, + method: 'get' + }) +} + +export function getDefinitionConfig(jobCode: string): any { + return axios({ + url: `/job/config/${jobCode}`, + method: 'get' + }) +} + +export function updateSyncTaskDefinition(jobCode: string, data: any): any { + return axios({ + url: `/job/config/${jobCode}`, + method: 'put', + data, + headers: { + 'Content-Type': 'application/json;charset=UTF-8' + }, + transformRequest: (params) => JSON.stringify(params) + }) +} + +export function connectorSourcesTypeList(status = 'DOWNLOADED'): any { + return axios({ + url: '/connector/sources', + method: 'get', + params: { + status + } + }) +} + +export function connectorSinksTypeList(status = 'DOWNLOADED'): any { + return axios({ + url: '/connector/sinks', + method: 'get', + params: { + status + } + }) +} + +export function taskDefinitionForm(): any { + return axios({ + url: '/job/env', + method: 'get' + }) +} + +export function saveTaskDefinitionDag(jobCode: string, data: any): any { + return axios({ + url: `/job/dag/${jobCode}`, + method: 'post', + data, + headers: { + 'Content-Type': 'application/json;charset=UTF-8' + }, + transformRequest: (params) => JSON.stringify(params) + }) +} + +export function deleteTaskDefinitionDag( + jobCode: string, + taskCode: string +): any { + return axios({ + url: `/job/task/${jobCode}?pluginId=${taskCode}`, + method: 'delete' + }) +} + +export function saveTaskDefinitionItem(jobCode: string, data: any): any { + return axios({ + url: `/job/task/${jobCode}`, + method: 'post', + headers: { + 'Content-Type': 'application/json;charset=UTF-8' + }, + transformRequest: () => JSON.stringify(data) + }) +} + +export function queryTaskDetail(jobCode: string, taskCode: string): any { + return axios({ + url: `/job/task/${jobCode}?pluginId=${taskCode}`, + method: 'get' + }) +} + +// source类型,获取源名称 +export function listSourceName( + jobId: string, + sceneMode: string, + status: 'DOWNLOADED' | 'NOT_DOWNLOAD' | 'ALL' = 'DOWNLOADED' +): any { + return axios({ + url: '/datasource/sources', + method: 'get', + params: { + jobId, + sceneMode, + status + } + }) +} +// sink类型,获取源名称 +export function findSink( + jobId: string, + status: 'DOWNLOADED' | 'NOT_DOWNLOAD' | 'ALL' = 'DOWNLOADED' +): any { + return axios({ + url: '/datasource/sinks', + method: 'get', + params: { + jobId, + status + } + }) +} + +export function getFormStructureByDatasourceInstance(params: { + connectorType: string + connectorName?: string + dataSourceInstanceId?: string + jobCode: number +}): any { + return axios({ + url: '/datasource/form', + method: 'get', + params + }) +} + +export function connectorTransformsTypeList(jobId: string): any { + return axios({ + url: '/datasource/transforms', + method: 'get', + params: { + jobId + } + }) +} + +export function listEngine(): any { + return axios({ + url: '/engine/list', + method: 'get' + }) +} + +export function listEngineType(): any { + return axios({ + url: '/engine/type', + method: 'get' + }) +} + +export function getDatabaseByDatasource(datasourceName: string): any { + return axios({ + url: '/datasource/databases', + method: 'get', + params: { + datasourceName + } + }) +} + +export function getTableByDatabase( + datasourceName: string, + databaseName: string, + filterName?: string, + size?: number, +): any { + size = size || 100 + filterName = filterName || '' + return axios({ + url: '/datasource/tables', + method: 'get', + params: { + datasourceName, + databaseName, + filterName, + size + } + }) +} + +export function getInputTableSchema( + datasourceId: string, + databaseName: string, + tableName: string +): any { + return axios({ + url: '/datasource/schema', + method: 'get', + params: { + datasourceId, + databaseName, + tableName + } + }) +} + +export function getOutputTableSchema(pluginName: string, data: any): any { + return axios({ + url: `/job/table/schema?pluginName=${pluginName}`, + method: 'post', + headers: { + 'Content-Type': 'application/json;charset=UTF-8' + }, + transformRequest: () => JSON.stringify(data) + }) +} + +export function getColumnProjection(pluginName: string): any { + return axios({ + url: '/job/table/column-projection', + method: 'get', + params: { + pluginName + } + }) +} + +export function checkDatabaseAndTable( + datasourceId: string, + data: { databases: Array; tables: Array } +): any { + return axios({ + url: `/job/table/check?datasourceId=${datasourceId}`, + method: 'post', + headers: { + 'Content-Type': 'application/json;charset=UTF-8' + }, + transformRequest: () => JSON.stringify(data) + }) +} + +export function modelInfo(datasourceId: string, data: any): any { + return axios({ + url: `/datasource/schemas?datasourceId=${datasourceId}`, + method: 'post', + headers: { + 'Content-Type': 'application/json;charset=UTF-8' + }, + transformRequest: () => JSON.stringify(data) + }) +} + +export function sqlModelInfo(taskId: string, pluginId: string, data: any): any { + return axios({ + url: `/schema/derivation/sql?jobVersionId=${taskId}&inputPluginId=${pluginId}`, + method: 'post', + headers: { + 'Content-Type': 'application/json;charset=UTF-8' + }, + transformRequest: () => JSON.stringify(data) + }) +} diff --git a/seatunnel-ui/src/service/datasource/index.ts b/seatunnel-ui/src/service/sync-task-instance/index.ts similarity index 54% rename from seatunnel-ui/src/service/datasource/index.ts rename to seatunnel-ui/src/service/sync-task-instance/index.ts index 0982eba00..0c00ab431 100644 --- a/seatunnel-ui/src/service/datasource/index.ts +++ b/seatunnel-ui/src/service/sync-task-instance/index.ts @@ -17,67 +17,54 @@ import { axios } from '@/service/service' -export function datasourceList(params: any): any { +export function queryRunningInstancePaging(params: any): any { return axios({ - url: '/datasource/list', + url: '/job/metrics/detail', method: 'get', + timeout: 60000, params }) } -export function datasourceDelete(id: string): any { +export function querySyncTaskInstanceDetail(params: any): any { return axios({ - url: '/datasource/' + id, - method: 'delete' + url: '/job/metrics/summary', + method: 'get', + timeout: 60000, + params }) } -export function datasourceTypeList(params: { - showVirtualDataSource: boolean - source?: 'WS' | 'WT' -}): any { +export function querySyncTaskInstanceDag(params: any): any { return axios({ - url: '/datasource/support-datasources', + url: '/job/metrics/dag', method: 'get', + timeout: 60000, params }) } -export function datasourceDetail(id: string): any { +export function querySyncTaskInstancePaging(params: any): any { return axios({ - url: '/datasource/' + id, - method: 'get' + url: '/ws/seaTunnel/syncTaskInstancePaging', + method: 'get', + params, + timeout: 60000 }) } -export function datasourceAdd(data: any): any { +export function cleanStateByIds(taskInstanceIds: Array) { return axios({ - url: '/datasource/create', + url: 'ws/seaTunnel/batch-clean-task-instance-state', method: 'post', - data - }) -} - -export function datasourceUpdate(data: any, id: string): any { - return axios({ - url: '/datasource/' + id, - method: 'put', - data + data: { taskInstanceIds } }) } -export function checkConnect(data: any): any { +export function forcedSuccessByIds(taskInstanceIds: Array) { return axios({ - url: '/datasource/check/connect', + url: 'ws/seaTunnel/batch-force-task-success', method: 'post', - data - }) -} - -export function dynamicFormItems(pluginName: string): any { - return axios({ - url: '/datasource/dynamic-form', - method: 'get', - params: { pluginName } + data: { taskInstanceIds } }) } diff --git a/seatunnel-ui/src/service/task-instances/index.ts b/seatunnel-ui/src/service/task-instances/index.ts new file mode 100644 index 000000000..6673a1ed1 --- /dev/null +++ b/seatunnel-ui/src/service/task-instances/index.ts @@ -0,0 +1,87 @@ +/* + * 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' +import utils from '@/utils' +import { ProjectCodeReq, IdReq, TaskListReq } from './types' + +export function queryTaskListPaging(params: TaskListReq): any { + return axios({ + url: '/projects/task-instances', + method: 'get', + params + }) +} + +export function forceSuccess(taskId: IdReq, projectCode: ProjectCodeReq): any { + return axios({ + url: `/projects//task-instances/${taskId.id}/force-success`, + method: 'post' + }) +} + +export function downloadLog(id: number): void { + utils.downloadFile('log/download-log', { taskInstanceId: id }) +} + +export function cleanState( + projectCode: number, + taskInstanceIds: number[], + cleanDownstream?: boolean +): any { + return axios({ + url: '/projects/task-instances/clean-task-instance-state', + method: 'post', + data: { taskInstanceIds, cleanDownstream }, + headers: { + 'Content-Type': 'application/json;charset=UTF-8' + }, + transformRequest: (params) => JSON.stringify(params) + }) +} + +export function checkDependentChain( + projectCode: number, + taskInstanceIds: number[] +): any { + return axios({ + url: `/ws/projects/${projectCode}/task-instances/dependent-chain-rerun-precheck`, + method: 'post', + data: { taskInstanceIds }, + headers: { + 'Content-Type': 'application/json;charset=UTF-8' + }, + timeout: 60000, + transformRequest: (params) => JSON.stringify(params) + }) +} + +export function dependentChainRerun( + projectCode: number, + taskInstanceIds: number[] +): any { + return axios({ + url: `/ws/projects/${projectCode}/task-instances/dependent-chain-rerun`, + method: 'post', + data: { taskInstanceIds }, + headers: { + 'Content-Type': 'application/json;charset=UTF-8' + }, + timeout: 60000, + transformRequest: (params) => JSON.stringify(params) + }) +} diff --git a/seatunnel-ui/src/service/task-instances/types.ts b/seatunnel-ui/src/service/task-instances/types.ts new file mode 100644 index 000000000..0328e0019 --- /dev/null +++ b/seatunnel-ui/src/service/task-instances/types.ts @@ -0,0 +1,127 @@ +/* + * 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. + */ + +interface ProjectCodeReq { + projectCode: number +} + +interface IdReq { + id: number +} + +interface TaskListReq { + pageNo: number + pageSize: number + endDate?: string + executorName?: string + host?: string + processInstanceId?: number + processInstanceName?: string + searchVal?: string + startDate?: string + flag?: string + stateType?: string + taskName?: string + projectCodes: Array +} + +interface Dependency { + localParams?: any + varPool?: any + dependTaskList?: any + relation?: any + resourceFilesList: any[] + varPoolMap?: any + localParametersMap?: any +} + +interface SwitchDependency extends Dependency { + nextNode?: any + resultConditionLocation: number + dependTaskList?: any +} + +interface TotalList { + taskComplete: boolean + firstRun: boolean + environmentCode: number + processInstance?: any + pid: number + appLink: string + taskCode: any + switchTask: boolean + host: string + id: number + state: string + workerGroup: string + conditionsTask: boolean + processInstancePriority?: any + processInstanceId: number + dependency: Dependency + alertFlag: string + dependentResult?: any + executePath: string + switchDependency: SwitchDependency + maxRetryTimes: number + executorName: string + subProcess: boolean + submitTime: string + taskGroupId: number + name: string + taskDefinitionVersion: number + processInstanceName: string + taskGroupPriority: number + taskDefine?: any + dryRun: number + flag: string + taskParams: string + duration: string + processDefine?: any + taskType: string + taskInstancePriority: string + logPath: string + startTime: string + environmentConfig?: any + executorId: number + firstSubmitTime: string + resources?: any + retryTimes: number + varPool: string + dependTask: boolean + delayTime: number + retryInterval: number + endTime: string +} + +interface TaskInstancesRes { + totalList: TotalList[] + total: number + totalPage: number + pageSize: number + currentPage: number + start: number +} + +export { + ProjectCodeReq, + IdReq, + TaskListReq, + Dependency, + SwitchDependency, + TotalList, + TaskInstancesRes +} diff --git a/seatunnel-ui/src/service/virtual-table/index.ts b/seatunnel-ui/src/service/virtual-table/index.ts new file mode 100644 index 000000000..9167080d6 --- /dev/null +++ b/seatunnel-ui/src/service/virtual-table/index.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. + */ + +import { axios } from '@/service/service' +import { + VirtualTableDetail, + VirtualTableListParameters, + DynamicConfigParameters +} from './types' + +const VIRTUAL_TABLE_BASE_URL = '/virtual_table' + +export function createVirtualTable(data: VirtualTableDetail): any { + return axios({ + url: VIRTUAL_TABLE_BASE_URL + '/create', + method: 'post', + headers: { + 'Content-Type': 'application/json;charset=UTF-8' + }, + transformRequest: () => JSON.stringify(data) + }) +} + +export function updateVirtualTable(data: VirtualTableDetail, id: string): any { + return axios({ + url: VIRTUAL_TABLE_BASE_URL + '/' + id, + method: 'put', + headers: { + 'Content-Type': 'application/json;charset=UTF-8' + }, + transformRequest: () => JSON.stringify(data) + }) +} + +export function deleteVirtualTable(id: string): any { + return axios({ + url: VIRTUAL_TABLE_BASE_URL + '/' + id, + method: 'delete' + }) +} + +export function getVirtualTableDetail(id: string): any { + return axios({ + url: VIRTUAL_TABLE_BASE_URL + '/' + id, + method: 'get' + }) +} + +export function getVirtualTableList(params: VirtualTableListParameters): any { + return axios({ + url: VIRTUAL_TABLE_BASE_URL + '/list', + method: 'get', + params + }) +} + +export function getDynamicConfig(params: DynamicConfigParameters): any { + return axios({ + url: VIRTUAL_TABLE_BASE_URL + '/dynamic_config', + method: 'get', + params + }) +} + +export function getFieldType(): any { + return axios({ + url: '/engine/type', + method: 'get' + }) +} diff --git a/seatunnel-ui/src/service/virtual-table/types.ts b/seatunnel-ui/src/service/virtual-table/types.ts new file mode 100644 index 000000000..a1cc7dbcb --- /dev/null +++ b/seatunnel-ui/src/service/virtual-table/types.ts @@ -0,0 +1,58 @@ +/* + * 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 interface VirtualTableListParameters { + pageNo: number + pageSize: number + pluginName: string | null + datasourceName: string | null +} + +export type IDetailTableRecord = { + fieldName?: string + fieldType: string + nullable: number | boolean + primaryKey: number | boolean + fieldComment?: string + isEdit?: boolean + key?: number + defaultValue?: string +} + +export interface VirtualTableDetail { + datasourceId: string + datasourceName: string + pluginName: string + tableName: string + tableFields: IDetailTableRecord[] + databaseProperties: { [key: string]: string } + databaseName?: string +} + +export interface VirtualTableRecord { + tableId: string + datasourceName: string + databaseName: string + tableName: string + description: string + key: number + createTime: string +} + +export interface DynamicConfigParameters { + pluginName: string + datasourceName: string +} diff --git a/seatunnel-ui/src/store/datasource/form-structures.ts b/seatunnel-ui/src/store/datasource/form-structures.ts new file mode 100644 index 000000000..70bd5a276 --- /dev/null +++ b/seatunnel-ui/src/store/datasource/form-structures.ts @@ -0,0 +1,40 @@ +/* + * 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 { defineStore } from 'pinia' +import { FormStructuresStore, StructureItem } from './types' +export type { StructureItem } + +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/store/project/index.ts b/seatunnel-ui/src/store/project/index.ts new file mode 100644 index 000000000..f3a9ba88a --- /dev/null +++ b/seatunnel-ui/src/store/project/index.ts @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './task-node' +export * from './task-type' + +import { defineStore } from 'pinia' +import type { ProjectStore } from './types' + +export const useProjectStore = defineStore({ + id: 'project', + state: (): ProjectStore => ({ + currentProject: null, + projectList: [], + golbalProject: [], + globalFlag: false, + changeProject: false + }), + persist: true, + getters: { + getCurrentProject(): any { + if (this.globalFlag) { + return this.golbalProject + } else { + return [this.currentProject] + } + }, + getProjects(): any { + return this.projectList + }, + getGolbalProject(): Array { + return this.golbalProject + }, + getGlobalFlag(): boolean { + return this.globalFlag + }, + getChangeProject(): boolean { + return this.changeProject + } + }, + actions: { + setCurrentProject(projectCode: number | string): void { + this.currentProject = projectCode + }, + setProjectList(projectList: Array): void { + this.golbalProject = [] + projectList.forEach((item: any) => { + this.golbalProject.push(item.code) + }) + this.projectList = projectList + }, + setGolbalCode(codeList: Array) { + this.golbalProject = codeList + }, + setGlobalFlag(flag: boolean) { + this.globalFlag = flag + }, + setChangeProject(flag: boolean) { + this.changeProject = flag + } + } +}) diff --git a/seatunnel-ui/src/store/project/task-node.ts b/seatunnel-ui/src/store/project/task-node.ts new file mode 100644 index 000000000..f9c465eed --- /dev/null +++ b/seatunnel-ui/src/store/project/task-node.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 { defineStore } from 'pinia' +import { uniqBy } from 'lodash' +import type { + TaskNodeState, + EditWorkflowDefinition, + IOption, + IResource, + ProgramType, + IMainJar, + DependentResultType, + BDependentResultType +} from './types' + +export const useTaskNodeStore = defineStore({ + id: 'project-task', + state: (): TaskNodeState => ({ + preTaskOptions: [], + postTaskOptions: [], + preTasks: [], + resources: [], + mainJars: {}, + name: '', + dependentResult: {} + }), + persist: true, + getters: { + getPreTaskOptions(): IOption[] { + return this.preTaskOptions + }, + getPostTaskOptions(): IOption[] { + return this.postTaskOptions + }, + getPreTasks(): number[] { + return this.preTasks + }, + getResources(): IResource[] { + return this.resources + }, + getMainJar(state) { + return (type: ProgramType): IMainJar[] | undefined => state.mainJars[type] + }, + getName(): string { + return this.name + }, + getDependentResult(): DependentResultType { + return this.dependentResult + } + }, + actions: { + updateDefinition(definition?: EditWorkflowDefinition, code?: number) { + if (!definition) return + const { processTaskRelationList = [], taskDefinitionList = [] } = + definition + const preTaskOptions: { value: number; label: string; type: string }[] = + [] + const tasks: { [field: number]: { taskType: string; name: string } } = {} + taskDefinitionList.forEach( + (task: { code: number; taskType: string; name: string }) => { + tasks[task.code] = { + name: task.name, + taskType: task.taskType + } + if (task.code === code) return + if ( + task.taskType === 'CONDITIONS' && + processTaskRelationList.filter( + (relation: { preTaskCode: number }) => + relation.preTaskCode === task.code + ).length >= 2 + ) { + return + } + preTaskOptions.push({ + value: task.code, + label: task.name, + type: task.taskType + }) + } + ) + + this.preTaskOptions = uniqBy(preTaskOptions, 'value') + if (!code) return + const preTasks: number[] = [] + const postTaskOptions: { value: number; label: string }[] = [] + processTaskRelationList.forEach( + (relation: { preTaskCode: number; postTaskCode: number }) => { + const task = tasks[relation.preTaskCode] + const postTask = tasks[relation.postTaskCode] + if (relation.preTaskCode === code) { + postTaskOptions.push({ + value: relation.postTaskCode, + label: postTask.name + }) + } + if (relation.postTaskCode === code && relation.preTaskCode !== 0) { + preTasks.push(relation.preTaskCode) + if ( + !this.preTaskOptions.find( + (item) => item.value === relation.preTaskCode + ) + ) { + this.preTaskOptions.push({ + value: relation.preTaskCode, + label: task?.name, + type: task?.taskType + }) + } + } + } + ) + this.preTasks = preTasks + this.postTaskOptions = postTaskOptions + }, + updateResource(resources: IResource[]) { + this.resources = resources + }, + updateMainJar(type: ProgramType, mainJar: IMainJar[]) { + this.mainJars[type] = mainJar + }, + updateName(name: string) { + this.name = name + }, + updateDependentResult(dependentResult: BDependentResultType) { + const result = {} as DependentResultType + for (const [key, value] of Object.entries(dependentResult)) { + result[key] = value === 'FAILED' ? 'FAILURE' : value + } + this.dependentResult = result + }, + init() { + this.preTaskOptions = [] + this.postTaskOptions = [] + this.preTasks = [] + this.resources = [] + this.mainJars = {} + this.name = '' + } + } +}) diff --git a/seatunnel-ui/src/store/project/task-type.ts b/seatunnel-ui/src/store/project/task-type.ts new file mode 100644 index 000000000..05fc6c597 --- /dev/null +++ b/seatunnel-ui/src/store/project/task-type.ts @@ -0,0 +1,178 @@ +/* + * 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 { defineStore } from 'pinia' +import type { TaskTypeState, ITaskType, TaskType, ITaskTypeItem } from './types' + +export const TASK_TYPES_MAP = { + SHELL: { + alias: 'SHELL', + helperLinkDisable: true + }, + INFORMATICA: { + alias: 'INFORMATICA', + helperLinkDisable: true + }, + SUB_PROCESS: { + alias: 'SUB_PROCESS', + helperLinkDisable: true + }, + PROCEDURE: { + alias: 'PROCEDURE', + helperLinkDisable: true + }, + SQL: { + alias: 'SQL', + helperLinkDisable: true + }, + SPARK: { + alias: 'SPARK', + helperLinkDisable: true + }, + FLINK: { + alias: 'FLINK', + helperLinkDisable: true + }, + MR: { + alias: 'MapReduce', + helperLinkDisable: true + }, + PYTHON: { + alias: 'PYTHON', + helperLinkDisable: true + }, + DEPENDENT: { + alias: 'DEPENDENT', + helperLinkDisable: true + }, + HTTP: { + alias: 'HTTP', + helperLinkDisable: true + }, + DATAX: { + alias: 'DataX', + helperLinkDisable: true + }, + PIGEON: { + alias: 'PIGEON', + helperLinkDisable: true + }, + SQOOP: { + alias: 'SQOOP', + helperLinkDisable: true + }, + CONDITIONS: { + alias: 'CONDITIONS', + taskDefinitionDisable: true, + helperLinkDisable: true + }, + DATA_QUALITY: { + alias: 'DATA_QUALITY', + helperLinkDisable: true + }, + SWITCH: { + alias: 'SWITCH', + taskDefinitionDisable: true, + helperLinkDisable: true + }, + SEATUNNEL: { + alias: 'SeaTunnel', + helperLinkDisable: true + }, + WHALE_SEATUNNEL: { + alias: 'Whale_SeaTunnel' + }, + EMR: { + alias: 'AmazonEMR', + helperLinkDisable: true + }, + SURVEIL: { + alias: 'TRIGGER', + helperLinkDisable: true + }, + NEXT_LOOP: { + alias: 'NEXT_LOOP', + helperLinkDisable: true + }, + HIVECLI: { + alias: 'HIVECLI', + helperLinkDisable: true + }, + DINKY: { + alias: 'DINKY', + helperLinkDisable: true + }, + CHUNJUN: { + alias: 'CHUNJUN', + helperLinkDisable: true + }, + ZEPPELIN: { + alias: 'ZEPPELIN', + helperLinkDisable: true + }, + JUPYTER: { + alias: 'JUPYTER', + helperLinkDisable: true + }, + MLFLOW: { + alias: 'MLFLOW', + helperLinkDisable: true + }, + OPENMLDB: { + alias: 'OPENMLDB', + helperLinkDisable: true + }, + DVC: { + alias: 'DVC', + helperLinkDisable: true + }, + SAGEMAKER: { + alias: 'SageMaker', + helperLinkDisable: true + }, + PYTORCH: { + alias: 'PYTORCH', + helperLinkDisable: true + }, + JAR: { + alias: 'JAR', + helperLinkDisable: true + } +} as { [key in TaskType]: ITaskType } + +export const useTaskTypeStore = defineStore({ + id: 'project-task-type', + state: (): TaskTypeState => ({ + types: [] + }), + persist: true, + getters: { + getTaskType(): ITaskTypeItem[] { + return this.types + } + }, + actions: { + setTaskTypes(types: TaskType[]): void { + try { + this.types = types + .filter((type) => !!TASK_TYPES_MAP[type]) + .map((type) => ({ ...TASK_TYPES_MAP[type], type })) + } catch (err) { + this.types = [] + } + } + } +}) diff --git a/seatunnel-ui/src/store/project/types.ts b/seatunnel-ui/src/store/project/types.ts new file mode 100644 index 000000000..d5f417d94 --- /dev/null +++ b/seatunnel-ui/src/store/project/types.ts @@ -0,0 +1,117 @@ +/* + * 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 { EditWorkflowDefinition } from '@/views/projects/workflow/components/dag/types' +import type { IOption } from '@/components/form/types' + +type TaskType = + | 'SHELL' + | 'SUB_PROCESS' + | 'PROCEDURE' + | 'SQL' + | 'SPARK' + | 'FLINK' + | 'MR' + | 'PYTHON' + | 'DEPENDENT' + | 'HTTP' + | 'DATAX' + | 'PIGEON' + | 'SQOOP' + | 'CONDITIONS' + | 'DATA_QUALITY' + | 'SWITCH' + | 'SEATUNNEL' + | 'EMR' + | 'SURVEIL' + | 'NEXT_LOOP' + | 'HIVECLI' + | 'DINKY' + | 'CHUNJUN' + | 'ZEPPELIN' + | 'JUPYTER' + | 'MLFLOW' + | 'OPENMLDB' + | 'DVC' + | 'WHALE_SEATUNNEL' + | 'SAGEMAKER' + | 'PYTORCH' + | 'JAR' + | 'INFORMATICA' +type ProgramType = 'JAVA' | 'SCALA' | 'PYTHON' | 'SQL' +type DependentResultType = { + [key: string]: 'SUCCESS' | 'WAITING_THREAD' | 'FAILURE' +} +type BDependentResultType = { + [key: string]: 'SUCCESS' | 'WAITING_THREAD' | 'FAILED' +} + +interface ProjectStore { + currentProject: number | null | string + projectList: Array<{ label: string; value: number }> + golbalProject: Array + globalFlag: boolean + changeProject: boolean +} + +interface IResource { + id: number + name: string + children?: IResource[] +} +interface IMainJar { + id: number + fullName: string + children: IMainJar[] +} +interface TaskNodeState { + postTaskOptions: IOption[] + preTaskOptions: IOption[] + preTasks: number[] + resources: IResource[] + mainJars: { [key in ProgramType]?: IMainJar[] } + name: string + dependentResult: DependentResultType +} + +interface ITaskType { + alias: string + helperLinkDisable?: boolean + taskDefinitionDisable?: boolean +} +interface ITaskTypeItem extends ITaskType { + type: TaskType +} +interface TaskTypeState { + types: ITaskTypeItem[] +} + +export { + TaskNodeState, + EditWorkflowDefinition, + IOption, + IResource, + ProgramType, + DependentResultType, + BDependentResultType, + IMainJar, + TaskType, + ITaskType, + ITaskTypeItem, + ProjectStore, + TaskTypeState +} diff --git a/seatunnel-ui/src/store/synchronization-definition/index.ts b/seatunnel-ui/src/store/synchronization-definition/index.ts new file mode 100644 index 000000000..daba6dfb6 --- /dev/null +++ b/seatunnel-ui/src/store/synchronization-definition/index.ts @@ -0,0 +1,46 @@ +/* + * 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 { defineStore } from 'pinia' +import type { SynchronizationDefinitionState, DagInfo } from './types' + +export const useSynchronizationDefinitionStore = defineStore({ + id: 'synchronization-definitionState', + state: (): SynchronizationDefinitionState => ({ + columnSelectable: {}, + dagInfo: {} + }), + persist: { + storage: sessionStorage + }, + getters: { + getColumnSelectable(state) { + return (type: string) => state.columnSelectable[type] + }, + getDagInfo(): DagInfo { + return this.dagInfo + } + }, + actions: { + setColumnSelectable(type: string, value: boolean): void { + this.columnSelectable[type] = value + }, + setDagInfo(dagInfo: DagInfo) { + this.dagInfo = { ...this.dagInfo, ...dagInfo } + } + } +}) diff --git a/seatunnel-ui/src/store/synchronization-definition/types.ts b/seatunnel-ui/src/store/synchronization-definition/types.ts new file mode 100644 index 000000000..edbdb4d2c --- /dev/null +++ b/seatunnel-ui/src/store/synchronization-definition/types.ts @@ -0,0 +1,22 @@ +/* + * 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 type DagInfo = { [key: string]: any } + +export interface SynchronizationDefinitionState { + columnSelectable: { [key: string]: boolean } + dagInfo: DagInfo +} diff --git a/seatunnel-ui/src/store/theme/index.ts b/seatunnel-ui/src/store/theme/index.ts index 91df4f42f..ac6860558 100644 --- a/seatunnel-ui/src/store/theme/index.ts +++ b/seatunnel-ui/src/store/theme/index.ts @@ -16,22 +16,43 @@ */ import { defineStore } from 'pinia' -import { ThemeState } from './types' +import themeList from '@/themes' +import type { ThemeState, ITheme } from './types' export const useThemeStore = defineStore({ id: 'theme', state: (): ThemeState => ({ - darkTheme: false + darkTheme: false, + theme: 'light', + isNavLogoBlack: false, + navTextColor: '' }), persist: true, getters: { - getTheme(): boolean { + getDarkTheme(): boolean { return this.darkTheme + }, + getTheme(): ITheme { + return this.theme + }, + getIsNavLogoBlack(): boolean { + return this.isNavLogoBlack + }, + getNavTextColor(): string { + return this.navTextColor } }, actions: { - setDarkTheme(): void { - this.darkTheme = !this.darkTheme + setTheme(theme: ITheme): void { + this.theme = theme + this.darkTheme = theme === 'dark' + this.isNavLogoBlack = theme === 'light' + const themeConfig = themeList[theme] + //@ts-ignore + this.navTextColor = themeConfig?.Menu?.itemTextColor || '' + }, + init(theme?: ITheme): void { + this.setTheme(theme || this.theme) } } }) diff --git a/seatunnel-ui/src/store/theme/types.ts b/seatunnel-ui/src/store/theme/types.ts index fa7e56dd7..36e55a140 100644 --- a/seatunnel-ui/src/store/theme/types.ts +++ b/seatunnel-ui/src/store/theme/types.ts @@ -15,8 +15,11 @@ * limitations under the License. */ -interface ThemeState { +export type ITheme = 'light' | 'dark' | 'dark-blue' + +export interface ThemeState { darkTheme: boolean + theme: ITheme + isNavLogoBlack: boolean + navTextColor: '' } - -export { ThemeState } diff --git a/seatunnel-ui/src/themes/index.ts b/seatunnel-ui/src/themes/index.ts index a5f70cbf1..f0dd5d998 100644 --- a/seatunnel-ui/src/themes/index.ts +++ b/seatunnel-ui/src/themes/index.ts @@ -14,13 +14,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - import light from './modules/light' import dark from './modules/dark' +import darkBlue from './modules/dark-blue' const themeList = { light, - dark + dark, + 'dark-blue': darkBlue } export default themeList diff --git a/seatunnel-ui/src/themes/modules/dark-blue.ts b/seatunnel-ui/src/themes/modules/dark-blue.ts new file mode 100644 index 000000000..33130b327 --- /dev/null +++ b/seatunnel-ui/src/themes/modules/dark-blue.ts @@ -0,0 +1,47 @@ +/* + * 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 { GlobalThemeOverrides } from '../type' + +const darkBlue = { + common: { + bodyColor: '#f8f8fc', + + /**************** Brand color */ + primaryColor: '#1890ff', + primaryColorHover: '#40a9ff', + primaryColorPressed: '#096dd9', + primaryColorSuppl: '#1890ff', + + /**************** Function of color */ + infoColor: '#1890ff', + successColor: '#52c41a', + warningColor: '#faad14', + errorColor: '#ff4d4f' + }, + Layout: { + headerColor: '#29366c', + siderColor: '#29366c' + }, + Menu: { + itemTextColorHorizontal: '#dbdbdb', + itemTextColor: '#dbdbdb', + itemIconColor: '#dbdbdb', + itemIconColorCollapsed: '#dbdbdb' + } +} as GlobalThemeOverrides + +export default darkBlue diff --git a/seatunnel-ui/src/themes/modules/dark.ts b/seatunnel-ui/src/themes/modules/dark.ts index 5473e6f23..df8889965 100644 --- a/seatunnel-ui/src/themes/modules/dark.ts +++ b/seatunnel-ui/src/themes/modules/dark.ts @@ -14,7 +14,35 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import type { GlobalThemeOverrides } from '../type' -const dark = {} +const dark = { + common: { + bodyColor: '#141414', + // baseColor: '#f8f8fc', + + /**************** Brand color */ + primaryColor: '#177ddc', + primaryColorHover: '#1765ad', + primaryColorPressed: '#3c9ae8', + primaryColorSuppl: '#177ddc', + + /**************** Function of color */ + infoColor: '#177ddc', + // successColor: '#49aa19', + // warningColor: '#d89614', + // errorColor: '#a61d24' + }, + // Layout: { + // headerColor: '#141414' + // }, + // DataTable: { + // thTextColor: '#fff', + // tdColorHover: '#f2f2fa' + // }, + // Space: { + // gapLarge: '28px 32px' + // } +} as GlobalThemeOverrides export default dark diff --git a/seatunnel-ui/src/themes/modules/light.ts b/seatunnel-ui/src/themes/modules/light.ts index aa0f52d2a..9e9634043 100644 --- a/seatunnel-ui/src/themes/modules/light.ts +++ b/seatunnel-ui/src/themes/modules/light.ts @@ -19,50 +19,53 @@ import type { GlobalThemeOverrides } from 'naive-ui' const light: GlobalThemeOverrides = { common: { - primaryColor: '#614bdd', - primaryColorHover: '#7d68de', - primaryColorSuppl: '#7d68de', - primaryColorPressed: '#513ac2', + bodyColor: '#f7f8fa', - infoColor: '#614bdd', - infoColorHover: '#7d68de', - infoColorSuppl: '#7d68de', - infoColorPressed: '#513ac2', + primaryColor: '#1890ff', + primaryColorHover: '#40a9ff', + primaryColorPressed: '#096dd9', + primaryColorSuppl: '#1890ff', - errorColor: '#db2777', - errorColorHover: '#d64687', - errorColorSuppl: '#d64687', - errorColorPressed: '#c60165', + /**************** Function of color */ + infoColor: '#1890ff', + successColor: '#52c41a', + warningColor: '#faad14', + errorColor: '#ff4d4f' - successColor: '#04beca', - successColorHover: '#69c8d5', - successColorSuppl: '#69c8d5', - successColorPressed: '#04a6ae', + // errorColor: '#db2777', + // errorColorHover: '#d64687', + // errorColorSuppl: '#d64687', + // errorColorPressed: '#c60165', - warningColor: '#eab308', - warningColorHover: '#e5cb41', - warningColorSuppl: '#e5cb41', - warningColorPressed: '#b38706', + // successColor: '#04beca', + // successColorHover: '#69c8d5', + // successColorSuppl: '#69c8d5', + // successColorPressed: '#04a6ae', - textColorBase: '#151666', - textColor1: '#242660', - textColor2: '#313377', - textColor3: '#9096b8', + // warningColor: '#eab308', + // warningColorHover: '#e5cb41', + // warningColorSuppl: '#e5cb41', + // warningColorPressed: '#b38706', - bodyColor: '#f7f8fa', - borderRadius: '15px', - tableHeaderColor: '#614bdd' - }, - Layout: { - headerColor: '#fff' - }, - DataTable: { - thTextColor: '#fff', - tdColorHover: '#f2f2fa' + // textColorBase: '#151666', + // textColor1: '#242660', + // textColor2: '#313377', + // textColor3: '#9096b8', + + + // borderRadius: '15px', + // tableHeaderColor: '#614bdd' }, - Space: { - gapLarge: '28px 32px' - } + // Layout: { + // headerColor: '#fff' + // }, + // DataTable: { + // thTextColor: '#fff', + // tdColorHover: '#f2f2fa' + // }, + // Space: { + // gapLarge: '28px 32px' + // } } export default light diff --git a/seatunnel-ui/src/themes/type.ts b/seatunnel-ui/src/themes/type.ts new file mode 100644 index 000000000..0ada98925 --- /dev/null +++ b/seatunnel-ui/src/themes/type.ts @@ -0,0 +1,18 @@ +/* + * 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 type { GlobalThemeOverrides } from 'naive-ui' diff --git a/seatunnel-ui/src/utils/clipboard.ts b/seatunnel-ui/src/utils/clipboard.ts new file mode 100644 index 000000000..22a9f6462 --- /dev/null +++ b/seatunnel-ui/src/utils/clipboard.ts @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const clipboard = (text: string): boolean => { + const inp = document.createElement('input') + document.body.appendChild(inp) + inp.value = text + inp.select() + let result = false + try { + result = document.execCommand('copy') + } catch (err) {} + inp.remove() + return result +} + +export default clipboard diff --git a/seatunnel-ui/src/utils/index.ts b/seatunnel-ui/src/utils/index.ts index d806dfb47..1704a1fd7 100644 --- a/seatunnel-ui/src/utils/index.ts +++ b/seatunnel-ui/src/utils/index.ts @@ -18,11 +18,13 @@ import mapping from './mapping' import trim from './trim' import log from './log' +import clipboard from './clipboard' const utils = { mapping, trim, - log + log, + clipboard } export default utils diff --git a/seatunnel-ui/src/utils/timePickeroption.ts b/seatunnel-ui/src/utils/timePickeroption.ts new file mode 100644 index 000000000..a78c9418a --- /dev/null +++ b/seatunnel-ui/src/utils/timePickeroption.ts @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export const getNowDate = (): any => { + return [ + new Date(new Date().toLocaleDateString()).getTime(), + new Date().getTime() + ] +} + +function getTimeRange(time: any) { + const cur = new Date(new Date().toLocaleDateString()).getTime() + return [cur - time * 24 * 60 * 60 * 1000, cur] as const +} + +export const getDataRange = (item: any) => { + let data + if (item == 0) { + data = getNowDate() + } else { + data = getTimeRange(item) + } + return data +} + +export function getRangeShortCuts(t: any) { + // const { t } = useI18n() + const rangeShortCuts = {} as any + const rangeMap = { + today: 0, + last_day: 1, + last_three_day: 3, + last_week: 7, + last_month: 30 + } as any + Object.keys(rangeMap).forEach((item: string) => { + rangeShortCuts[`${t(`project.workflow.${item}`)}` as string] = getDataRange( + rangeMap[item] + ) + }) + return rangeShortCuts +} diff --git a/seatunnel-ui/src/views/data-pipes/detail/index.tsx b/seatunnel-ui/src/views/data-pipes/detail/index.tsx index aa34cfdff..64f747f3c 100644 --- a/seatunnel-ui/src/views/data-pipes/detail/index.tsx +++ b/seatunnel-ui/src/views/data-pipes/detail/index.tsx @@ -22,7 +22,6 @@ import { useRoute, useRouter } from 'vue-router' import { scriptDetail } from '@/service/script' import MonacoEditor from '@/components/monaco-editor' import type { Router, RouteLocationNormalizedLoaded } from 'vue-router' -import type { ResponseBasic } from '@/service/types' import type { ScriptDetail } from '@/service/script/types' const DataPipesDetail = defineComponent({ @@ -42,10 +41,10 @@ const DataPipesDetail = defineComponent({ onMounted(() => { scriptDetail(Number(route.params.dataPipeId)).then( - (res: ResponseBasic) => { - variables.name = res.data.name - variables.type = res.data.type - variables.content = res.data.content + (res: ScriptDetail) => { + variables.name = res.name + variables.type = res.type + variables.content = res.content } ) }) diff --git a/seatunnel-ui/src/views/data-pipes/edit/index.tsx b/seatunnel-ui/src/views/data-pipes/edit/index.tsx index 7a5564f78..ddc3282ce 100644 --- a/seatunnel-ui/src/views/data-pipes/edit/index.tsx +++ b/seatunnel-ui/src/views/data-pipes/edit/index.tsx @@ -32,7 +32,6 @@ import { BulbOutlined } from '@vicons/antd' import { scriptDetail, scriptUpdate } from '@/service/script' import MonacoEditor from '@/components/monaco-editor' import type { Router, RouteLocationNormalizedLoaded } from 'vue-router' -import type { ResponseBasic } from '@/service/types' import type { ScriptDetail } from '@/service/script/types' const DataPipesEdit = defineComponent({ @@ -60,10 +59,10 @@ const DataPipesEdit = defineComponent({ onMounted(() => { scriptDetail(Number(route.params.dataPipeId)).then( - (res: ResponseBasic) => { - variables.name = res.data.name - variables.type = res.data.type - variables.content = res.data.content + (res: ScriptDetail) => { + variables.name = res.name + variables.type = res.type + variables.content = res.content } ) }) diff --git a/seatunnel-ui/src/views/data-pipes/list/use-table.ts b/seatunnel-ui/src/views/data-pipes/list/use-table.ts index 529c76825..69ab9e79b 100644 --- a/seatunnel-ui/src/views/data-pipes/list/use-table.ts +++ b/seatunnel-ui/src/views/data-pipes/list/use-table.ts @@ -198,7 +198,7 @@ export function useTable() { if (state.loading) return state.loading = true scriptList(params).then((res: ResponseTable | []>) => { - state.tableData = res.data.data as any + state.tableData = res.data as any state.totalPage = res.data.totalPage state.loading = false }) diff --git a/seatunnel-ui/src/views/datasource/components/use-source.ts b/seatunnel-ui/src/views/datasource/components/use-source.ts index 8f85064bd..a775cde6c 100644 --- a/seatunnel-ui/src/views/datasource/components/use-source.ts +++ b/seatunnel-ui/src/views/datasource/components/use-source.ts @@ -15,11 +15,11 @@ * limitations under the License. */ import { onMounted, reactive, watch } from 'vue' -import { datasourceTypeList } from '@/service/datasource' +import { getDatasourceType } from '@/service/data-source' import { useI18n } from 'vue-i18n' import type { SelectOption } from 'naive-ui' import type { ResponseBasic } from '@/service/types' -import type { DatasourceTypeList } from '@/service/datasource/types' +import type { DatasourceTypeList } from '@/service/data-source/types' type Key = '1' | '2' | '3' | '4' | '5' type IType = { @@ -43,7 +43,7 @@ export const useSource = (showVirtualDataSource = false) => { }) const querySource = () => { - datasourceTypeList({ + getDatasourceType({ showVirtualDataSource, source: 'WT' }).then((res: ResponseBasic | Array>) => { @@ -52,7 +52,7 @@ export const useSource = (showVirtualDataSource = false) => { en_US: {} as { [key: string]: string } } - state.types = Object.entries(res.data).map(([key, value]) => { + state.types = Object.entries(res).map(([key, value]) => { return { type: 'group', label: i18n.t(`datasource.${TYPE_MAP[key as Key]}`), diff --git a/seatunnel-ui/src/views/datasource/create/use-detail.ts b/seatunnel-ui/src/views/datasource/create/use-detail.ts index f18c1e0d2..f2c4a775a 100644 --- a/seatunnel-ui/src/views/datasource/create/use-detail.ts +++ b/seatunnel-ui/src/views/datasource/create/use-detail.ts @@ -21,7 +21,7 @@ import { datasourceAdd, datasourceUpdate, checkConnect -} from '@/service/datasource' +} from '@/service/data-source' import { useI18n } from 'vue-i18n' import { omit } from 'lodash' import { useRouter } from 'vue-router' @@ -56,12 +56,12 @@ export function useDetail( const queryById = async () => { try { const result = await datasourceDetail(id) - await getFormItems(result.data.pluginName) + await getFormItems(result.pluginName) setFieldsValue({ - datasourceName: result.data.datasourceName, - pluginName: result.data.pluginName, - description: result.data.description, - ...result.data.datasourceConfig + datasourceName: result.datasourceName, + pluginName: result.pluginName, + description: result.description, + ...result.datasourceConfig }) } finally {} } diff --git a/seatunnel-ui/src/views/datasource/create/use-form.ts b/seatunnel-ui/src/views/datasource/create/use-form.ts index 5597800e8..23e41f327 100644 --- a/seatunnel-ui/src/views/datasource/create/use-form.ts +++ b/seatunnel-ui/src/views/datasource/create/use-form.ts @@ -22,7 +22,7 @@ import { useFormStructuresStore, StructureItem } from '@/store/datasource' -import { dynamicFormItems } from '@/service/datasource' +import { dynamicFormItems } from '@/service/data-source' 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' @@ -64,10 +64,10 @@ export function useForm(type: string) { return } - const result: ResponseBasic = await dynamicFormItems(value) + const result: any = await dynamicFormItems(value) try { - const res = JSON.parse(result.data) + const res = JSON.parse(result) res.forms = res.forms.map((form: any) => ({ ...form, span: 12 })) Object.assign(state.detailForm, useFormField(res.forms)) Object.assign( diff --git a/seatunnel-ui/src/views/datasource/list/types.ts b/seatunnel-ui/src/views/datasource/list/types.ts new file mode 100644 index 000000000..dcfba7ab5 --- /dev/null +++ b/seatunnel-ui/src/views/datasource/list/types.ts @@ -0,0 +1,20 @@ +/* + * 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 type { TableColumns } from 'naive-ui/es/data-table/src/interface' +export type { SelectOption } from 'naive-ui' +export type { DataSourceDetail } from '@/service/data-source/types' diff --git a/seatunnel-ui/src/views/datasource/list/use-columns.ts b/seatunnel-ui/src/views/datasource/list/use-columns.ts index 15bae6367..c67d0adec 100644 --- a/seatunnel-ui/src/views/datasource/list/use-columns.ts +++ b/seatunnel-ui/src/views/datasource/list/use-columns.ts @@ -21,6 +21,10 @@ import { NPopover, NButton, NSpace } from 'naive-ui' import JsonHighlight from '../components/json-highlight' import { getTableColumn } from '@/common/table' +import { useTableOperation } from '@/hooks' +import { EditOutlined } from '@vicons/antd' +import ResourceAuth from '@/components/resource-auth' + export function useColumns(onCallback: Function) { const { t } = useI18n() const getColumns = () => { @@ -85,11 +89,11 @@ export function useColumns(onCallback: Function) { default: () => [ h( NButton, - { + { text: true, onClick: () => void onCallback(row.id, 'edit') - }, - { + }, + { default: () => t('datasource.edit') } ), @@ -98,11 +102,11 @@ export function useColumns(onCallback: Function) { { text: true, onClick: () => void onCallback(row.id, 'delete') - }, + }, { default: () => t('datasource.delete') } ) - ] - }) + ] + }) } ] } diff --git a/seatunnel-ui/src/views/datasource/list/use-source.ts b/seatunnel-ui/src/views/datasource/list/use-source.ts new file mode 100644 index 000000000..c0e79cc43 --- /dev/null +++ b/seatunnel-ui/src/views/datasource/list/use-source.ts @@ -0,0 +1,91 @@ +/* + * 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 { getDatasourceType } from '@/service/data-source' +import { useI18n } from 'vue-i18n' +import type { SelectOption } from './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({ + loading: false, + types: [] as IType[] + }) + + const querySource = async () => { + if (state.loading) return + state.loading = true + try { + const res = (await getDatasourceType({ + showVirtualDataSource, + source: 'WT' + })) as { + Key: { code: number; name: string; chineseName: string }[] + } + + console.log(res, 'ers') + const locales = { + zh_CN: {} as { [key: string]: string }, + en_US: {} as { [key: string]: string } + } + state.types = Object.entries(res).map(([key, value]) => { + return { + type: 'group', + label: i18n.t(`datasource.${TYPE_MAP[key as Key]}`), + key: TYPE_MAP[key as Key], + children: value.map((item) => { + 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) + } finally { + state.loading = false + } + } + + onMounted(() => { + querySource() + }) + + watch(useI18n().locale, () => { + querySource() + }) + + return { state } +} diff --git a/seatunnel-ui/src/views/datasource/list/use-table.ts b/seatunnel-ui/src/views/datasource/list/use-table.ts index ba609eb23..914a4a5fc 100644 --- a/seatunnel-ui/src/views/datasource/list/use-table.ts +++ b/seatunnel-ui/src/views/datasource/list/use-table.ts @@ -19,8 +19,8 @@ import { reactive } from 'vue' import { datasourceList, datasourceDelete -} from '@/service/datasource' -import type { DatasourceList } from '@/service/datasource/types' +} from '@/service/data-source' +import type { DatasourceList } from '@/service/data-source/types' import type { ResponseTable } from '@/service/types' export function useTable() { @@ -32,15 +32,15 @@ export function useTable() { list: [] }) - const getList = () => { + const getList = (pluginName = '') => { datasourceList({ pageNo: data.page, pageSize: data.pageSize, searchVal: data.searchVal, - pluginName: '' - }).then((res: ResponseTable | []>) => { - data.list = res.data.data as any - data.itemCount = res.data.totalCount + pluginName + }).then((res: any) => { + data.list = res.data + data.itemCount = res.totalCount }) } diff --git a/seatunnel-ui/src/views/jobs/list/use-table.ts b/seatunnel-ui/src/views/jobs/list/use-table.ts index e7ac20707..488abbfee 100644 --- a/seatunnel-ui/src/views/jobs/list/use-table.ts +++ b/seatunnel-ui/src/views/jobs/list/use-table.ts @@ -89,7 +89,7 @@ export function useTable() { state.loading = true taskJobList({ ...params }).then( (res: ResponseTable | []>) => { - state.tableData = res.data.data as any + state.tableData = res.data as any state.totalPage = res.data.totalPage state.loading = false } diff --git a/seatunnel-ui/src/views/login/use-form.ts b/seatunnel-ui/src/views/login/use-form.ts index 65b4a9ba6..b432883c5 100644 --- a/seatunnel-ui/src/views/login/use-form.ts +++ b/seatunnel-ui/src/views/login/use-form.ts @@ -56,7 +56,7 @@ export function useForm() { const handleLogin = () => { userLogin({ ...state.loginForm }).then((res: any) => { - userStore.setUserInfo(res.data) + userStore.setUserInfo(res) router.push({ path: '/data-pipes' }) }) } diff --git a/seatunnel-ui/src/views/projects/components/column-selector.tsx b/seatunnel-ui/src/views/projects/components/column-selector.tsx new file mode 100644 index 000000000..597227fa2 --- /dev/null +++ b/seatunnel-ui/src/views/projects/components/column-selector.tsx @@ -0,0 +1,143 @@ +/* + * 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 { NButton, NCheckboxGroup, NDropdown } from 'naive-ui' +import { defineComponent, PropType, ref, watch } from 'vue' +import { + filterColumns, + getColumnsBytable, + getCheckboxList +} from '../utils/dealColumns' +import { getColumns, setColumns } from '../utils/storeColums' +import './index.scss' +import { useI18n } from 'vue-i18n' + +const props = { + tableKey: { + default: '', + type: String + }, + tableColumns: { + default: [], + type: Array as PropType> + } +} +export default defineComponent({ + name: 'columnSelector', + props, + emits: ['changeOptions'], + setup(props, ctx) { + const selector = ref([]) as any + const ff = ref(null) + const { t } = useI18n() + const showDropdown = ref(false) + const columns = ref<(string | number)[] | null>(null) + const localColumn = getColumns(props.tableKey) + const storeColumn = ref<(string | number)[] | null>(null) + watch(props, () => { + selector.value = getColumnsBytable(props.tableColumns, t) + if (localColumn && localColumn.length > 0) { + columns.value = getCheckboxList(selector.value, localColumn, false) + } else { + columns.value = getCheckboxList(selector.value, [], true) + } + storeColumn.value = [...columns.value] + const resColumn = filterColumns(columns.value, props.tableColumns) + ctx.emit('changeOptions', resColumn) + }) + + const handleClick = (show: boolean) => { + if (!show) { + const resColumn = filterColumns(columns.value, props.tableColumns) + ctx.emit('changeOptions', resColumn) + setColumns(props.tableKey, resColumn) + } + } + + const handleUpdateValue = (value: Array) => { + const length = getCheckboxList(selector.value, [], true).length - 1 + if (value.indexOf('ALL') == -1 && value.length == length) { + if (storeColumn.value && storeColumn.value.indexOf('ALL') > -1) { + columns.value = [] + storeColumn.value = [] + } else { + columns.value = getCheckboxList(selector.value, [], true) + storeColumn.value = getCheckboxList(selector.value, [], true) + } + } else if (value.indexOf('ALL') == -1 && value.length < length) { + columns.value = value.filter((item) => { + return item !== 'ALL' + }) + } else if (value.indexOf('ALL') > -1 && value.length == length + 1) { + columns.value = [] + storeColumn.value = [] + } else if (value.indexOf('ALL') > -1 && value.length == 1) { + columns.value = getCheckboxList(selector.value, [], true) + storeColumn.value = getCheckboxList(selector.value, [], true) + } else if (value.indexOf('ALL') > -1 && value.length == length) { + if (storeColumn.value && storeColumn.value.indexOf('ALL') > -1) { + columns.value = value.filter((item) => { + return item !== 'ALL' + }) + storeColumn.value = [ + ...value.filter((item) => { + return item !== 'ALL' + }) + ] + } + } else if (value.indexOf('ALL') > -1 && value.length < length) { + columns.value = value.filter((item) => { + return item !== 'ALL' + }) + storeColumn.value = [ + ...value.filter((item) => { + return item !== 'ALL' + }) + ] + if (storeColumn.value && storeColumn.value.indexOf('ALL') == -1) { + columns.value = getCheckboxList(selector.value, [], true) + storeColumn.value = getCheckboxList(selector.value, [], true) + } + } + } + + return { + selector, + ff, + showDropdown, + handleClick, + columns, + handleUpdateValue, + t + } + }, + render() { + const { t } = this + return ( +
+ + + {t('project.column')} + + +
+ ) + } +}) diff --git a/seatunnel-ui/src/views/projects/components/index.scss b/seatunnel-ui/src/views/projects/components/index.scss new file mode 100644 index 000000000..48ef20835 --- /dev/null +++ b/seatunnel-ui/src/views/projects/components/index.scss @@ -0,0 +1,34 @@ +/* + * 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. + */ + +.checkbox { + margin: 5px 5px 5px 15px; + width: 200px; +} +.project-style { + background-color: #eeeeee; + color: #000; +} + +.column-style{ + width: 70px; +} + +.btnlist-style { + width: 70px; +} + diff --git a/seatunnel-ui/src/views/projects/components/jumpProject.ts b/seatunnel-ui/src/views/projects/components/jumpProject.ts new file mode 100644 index 000000000..e69de29bb diff --git a/seatunnel-ui/src/views/projects/components/projectSelector.tsx b/seatunnel-ui/src/views/projects/components/projectSelector.tsx new file mode 100644 index 000000000..84331cf01 --- /dev/null +++ b/seatunnel-ui/src/views/projects/components/projectSelector.tsx @@ -0,0 +1,96 @@ +/* + * 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 { useProjectStore } from '@/store/project' +import { NSelect } from 'naive-ui' +import { defineComponent, ref, VNode, h } from 'vue' +import { NTooltip, SelectOption } from 'naive-ui' +import { useI18n } from 'vue-i18n' +import './index.scss' +const props = { + key: { + type: String, + default: '' + }, + size: { + default: 'medium' + }, + initCode: { + default: null + } +} + +export default defineComponent({ + name: 'projectSelector', + props, + emits: ['getprojectList'], + setup(props, ctx) { + const { t } = useI18n() + const projectItem = Number(props.initCode) || null + const projectOption = ref([] as any) + const projectStore = useProjectStore() + projectOption.value.push(...projectStore.getProjects) + const rendedrOption = ({ + node, + option + }: { + node: VNode + option: SelectOption + }) => { + return h( + NTooltip, + { + width: 100, + placement: 'bottom-end', + showArrow: false + }, + { + trigger: () => node, + default: () => option.label + } + ) + } + const handleSelectproject = (option: any) => { + ctx.emit('getprojectList', option) + } + return { + handleSelectproject, + projectOption, + projectItem, + rendedrOption, + t + } + }, + render() { + return ( +
+ {this.projectOption && ( + + )} +
+ ) + } +}) diff --git a/seatunnel-ui/src/views/projects/utils/changeProject.ts b/seatunnel-ui/src/views/projects/utils/changeProject.ts new file mode 100644 index 000000000..bd34eb48e --- /dev/null +++ b/seatunnel-ui/src/views/projects/utils/changeProject.ts @@ -0,0 +1,26 @@ +/* + * 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 { useProjectStore } from '@/store/project' + +const projectStore = useProjectStore() + +export function changeProject(code: string | number) { + projectStore.setGlobalFlag(false) + projectStore.setCurrentProject(code) + projectStore.setChangeProject(true) +} diff --git a/seatunnel-ui/src/views/projects/utils/dealColumns.ts b/seatunnel-ui/src/views/projects/utils/dealColumns.ts new file mode 100644 index 000000000..b1773a0cb --- /dev/null +++ b/seatunnel-ui/src/views/projects/utils/dealColumns.ts @@ -0,0 +1,123 @@ +/* + * 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 { NCheckbox } from 'naive-ui' + +interface columnType { + key: string + title: string + type?: string + [key: string]: any +} +interface optionType { + label: string + value: string + disabled?: () => boolean + [key: string]: any +} +const copyKey = ['copy'] + +function isCopyKyeOrSelection( + item: columnType, + selectArray: Array +): boolean { + return ( + (copyKey.indexOf(item.key) > -1 && selectArray.indexOf('name') > -1) || + item.type == 'selection' || + item.key == 'operation' + ) +} + +export function filterColumns( + selectArray: Array | null, + oriArray: Array +): Array { + if (!selectArray || selectArray.length == 0) { + return [] + } + + const resArray: Array = [] as Array + oriArray.forEach((item: any) => { + if ( + isCopyKyeOrSelection(item, selectArray) || + selectArray.indexOf(item.key) > -1 + ) { + resArray.push(item) + } + }) + return resArray +} + +export function getColumnsBytable( + tableColumns: Array, + t: any +): Array { + const resArray: Array = [ + { + label: t('project.all_column'), + value: 'ALL', + key: 'ALL', + type: 'render', + render: () => { + return h(NCheckbox, { + value: 'ALL', + label: t('project.all_column'), + class: 'checkbox' + }) + } + } + ] + tableColumns.forEach((item: any) => { + if (!isCopyKyeOrSelection(item, ['name'])) { + resArray.push({ + ...item, + label: item.title, + value: item.key, + type: 'render', + render: () => { + return h(NCheckbox, { + value: item.key, + label: item.title, + class: 'checkbox' + }) + } + }) + } + }) + return resArray +} + +export function getCheckboxList( + selectorColumns: Array, + localColumn: Array, + ALL_CONF = false +): Array { + const checkboxList: Array = [] + const localCheckboxList: Array = [] + localColumn.forEach((el: any) => { + if (!isCopyKyeOrSelection(el, ['name'])) localCheckboxList.push(el.key) + }) + if (ALL_CONF || selectorColumns.length == localCheckboxList.length + 1) { + selectorColumns.forEach((item: any) => { + checkboxList.push(item.key) + }) + } else { + checkboxList.push(...localCheckboxList) + } + return checkboxList +} diff --git a/seatunnel-ui/src/views/projects/utils/getProjectCodes.ts b/seatunnel-ui/src/views/projects/utils/getProjectCodes.ts new file mode 100644 index 000000000..9cff89eb5 --- /dev/null +++ b/seatunnel-ui/src/views/projects/utils/getProjectCodes.ts @@ -0,0 +1,24 @@ +/* + * 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 { useProjectStore } from '@/store/project' + +export const getProjectCodes = () => { + const projectStore = useProjectStore() + const codes = projectStore.getCurrentProject + return (codes as Array) || [] +} diff --git a/seatunnel-ui/src/views/projects/utils/storeColums.ts b/seatunnel-ui/src/views/projects/utils/storeColums.ts new file mode 100644 index 000000000..de563a05b --- /dev/null +++ b/seatunnel-ui/src/views/projects/utils/storeColums.ts @@ -0,0 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export function setColumns(key: string, value: any) { + localStorage.setItem(`col_${key}`, JSON.stringify(value)) +} + +export function getColumns(key: string): Array { + return JSON.parse(localStorage.getItem(`col_${key}`) as string) as Array +} diff --git a/seatunnel-ui/src/views/setting/index.tsx b/seatunnel-ui/src/views/setting/index.tsx index 04f86c8fe..9887923c5 100644 --- a/seatunnel-ui/src/views/setting/index.tsx +++ b/seatunnel-ui/src/views/setting/index.tsx @@ -19,6 +19,7 @@ import { defineComponent } from 'vue' import { useI18n } from 'vue-i18n' import { NSpace, NCard, NSwitch, NList, NListItem, NSelect } from 'naive-ui' import { useSettingStore } from '@/store/setting' +import Theme from './theme' const Setting = defineComponent({ name: 'Setting', @@ -105,16 +106,17 @@ const Setting = defineComponent({ {t('setting.model')}
- + {/* + /> */}
- + {/* {t('setting.hue')}
@@ -126,7 +128,7 @@ const Setting = defineComponent({ />
-
+
*/} {t('setting.fillet')} diff --git a/seatunnel-ui/src/service/virtual-tables/types.ts b/seatunnel-ui/src/views/setting/theme/index.module.scss similarity index 96% rename from seatunnel-ui/src/service/virtual-tables/types.ts rename to seatunnel-ui/src/views/setting/theme/index.module.scss index 3e7c6c26f..2cf83faba 100644 --- a/seatunnel-ui/src/service/virtual-tables/types.ts +++ b/seatunnel-ui/src/views/setting/theme/index.module.scss @@ -14,3 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + +.icon { + margin: 0 12px; +} diff --git a/seatunnel-ui/src/views/setting/theme/index.tsx b/seatunnel-ui/src/views/setting/theme/index.tsx new file mode 100644 index 000000000..17717eb85 --- /dev/null +++ b/seatunnel-ui/src/views/setting/theme/index.tsx @@ -0,0 +1,64 @@ +/* + * 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, computed, ref, watch } from 'vue' +import { NDropdown, NIcon, NButton, NSelect } from 'naive-ui' +import { DownOutlined } from '@vicons/antd' +import { useTheme } from './use-theme' +import styles from './index.module.scss' +import { useI18n } from 'vue-i18n' +import { useThemeStore } from '@/store/theme' +import { find } from 'lodash' +import type { ITheme } from '@/store/theme/types' + +const Theme = defineComponent({ + name: 'Theme', + setup() { + const themeStore = useThemeStore() + const { t } = useI18n() + const onSelectTheme = (theme: ITheme) => { + themeStore.setTheme(theme) + } + + console.log(t('theme.light'), 'theme.light') + const themeOpts = computed(() =>[ + // { label: t('theme.dark_blue'), key: 'dark-blue' }, + { label: t('theme.light'), key: 'light', value: 'light' }, + { label: t('theme.dark'), key: 'dark', value: 'dark' } + ]) + + let themeLabel = computed(() => themeStore.theme) + return { + themeOpts, + themeLabel, + onSelectTheme + } + }, + render() { + return ( + { + this.onSelectTheme(v) + }} + /> + ) + } +}) + +export default Theme diff --git a/seatunnel-ui/src/views/setting/theme/use-theme.ts b/seatunnel-ui/src/views/setting/theme/use-theme.ts new file mode 100644 index 000000000..cf0214ade --- /dev/null +++ b/seatunnel-ui/src/views/setting/theme/use-theme.ts @@ -0,0 +1,50 @@ +/* + * 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 { computed, ref, watch } from 'vue' +import { useI18n } from 'vue-i18n' +import { useThemeStore } from '@/store/theme' +import { find } from 'lodash' +import type { ITheme } from '@/store/theme/types' + +export function useTheme() { + const { t } = useI18n() + const themeStore = useThemeStore() + + const getThemes = () => [ + // { label: t('theme.dark_blue'), key: 'dark-blue' }, + { label: t('theme.light'), key: 'light', value: 'light' }, + { label: t('theme.dark'), key: 'dark', value: 'dark' } + ] + + let themeLabel = computed(() => themeStore.theme) + const themes = ref(getThemes()) + // const currentThemeLabel = ref(getThemeLabel()) + + const onSelectTheme = (theme: ITheme) => { + console.log(111) + themeStore.setTheme(theme) + // currentThemeLabel.value = getThemeLabel() + } + + watch(useI18n().locale, () => { + themes.value = getThemes() + // currentThemeLabel.value = getThemeLabel() + }) + + return { themes, themeLabel, onSelectTheme } +} diff --git a/seatunnel-ui/src/views/task/components/node/detail-modal.tsx b/seatunnel-ui/src/views/task/components/node/detail-modal.tsx new file mode 100644 index 000000000..e8be45d24 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/detail-modal.tsx @@ -0,0 +1,228 @@ +/* + * 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, ref, watch, nextTick, h, Ref } from 'vue' +import { useI18n } from 'vue-i18n' +import Modal from '@/components/modal' +import Detail from './detail' +import { formatModel } from './format-data' +import { + HistoryOutlined, + ProfileOutlined, + QuestionCircleTwotone, + BranchesOutlined +} from '@vicons/antd' +import { NIcon } from 'naive-ui' +import { Router, useRoute, useRouter } from 'vue-router' +import { querySubProcessInstanceByTaskCode } from '@/service/modules/process-instances' +import { useTaskNodeStore, TASK_TYPES_MAP } from '@/store/project' +import type { + ITaskData, + ITaskType, + EditWorkflowDefinition, + IWorkflowTaskInstance, + WorkflowInstance +} from './types' + +const props = { + show: { + type: Boolean as PropType, + default: false + }, + data: { + type: Object as PropType, + default: { code: 0, taskType: 'SHELL', name: '' } + }, + projectCode: { + type: Number as PropType, + required: true, + default: 0 + }, + readonly: { + type: Boolean as PropType, + default: false + }, + from: { + type: Number as PropType, + default: 0 + }, + definition: { + type: Object as PropType> + }, + processInstance: { + type: Object as PropType + }, + taskInstance: { + type: Object as PropType + }, + saving: { + type: Boolean, + default: false + } +} + +const NodeDetailModal = defineComponent({ + name: 'NodeDetailModal', + props, + emits: ['cancel', 'submit', 'viewLog'], + setup(props, { emit }) { + const { t, locale } = useI18n() + const router: Router = useRouter() + const route = useRoute() + const taskStore = useTaskNodeStore() + const renderIcon = (icon: any) => { + return () => h(NIcon, null, { default: () => h(icon) }) + } + const detailRef = ref() + + const onConfirm = async () => { + await detailRef.value.value.validate() + emit('submit', { data: detailRef.value.value.getValues() }) + } + const onCancel = () => { + emit('cancel') + } + + const headerLinks = ref([] as any) + + const handleViewLog = () => { + if (props.taskInstance) { + emit('viewLog', props.taskInstance.id, props.taskInstance.taskType) + } + } + + const initHeaderLinks = (processInstance: any, taskType?: ITaskType) => { + headerLinks.value = [ + { + text: t('project.node.instructions'), + show: !!(taskType && !TASK_TYPES_MAP[taskType]?.helperLinkDisable), + action: () => { + let linkedTaskType = taskType?.toLowerCase().replace('_', '-') + if (taskType === 'PROCEDURE') linkedTaskType = 'stored-procedure' + const helpUrl = + 'https://dolphinscheduler.apache.org/' + + locale.value.toLowerCase().replace('_', '-') + + '/docs/latest/user_doc/guide/task/' + + linkedTaskType + + '.html' + window.open(helpUrl) + }, + icon: renderIcon(QuestionCircleTwotone) + }, + { + text: t('project.node.view_history'), + show: !!props.taskInstance, + action: () => { + router.push({ + name: 'task-instance', + params: { + processInstanceId: processInstance.id + }, + query: { + searchVal: props.data.name + } + }) + }, + icon: renderIcon(HistoryOutlined) + }, + { + text: t('project.node.view_log'), + show: !!props.taskInstance, + action: () => { + handleViewLog() + }, + icon: renderIcon(ProfileOutlined) + }, + { + text: t('project.node.enter_this_child_node'), + show: props.data.taskType === 'SUB_PROCESS', + disabled: + !props.data.id || + (router.currentRoute.value.name === 'workflow-instance-detail' && + !props.taskInstance), + action: () => { + if (router.currentRoute.value.name === 'workflow-instance-detail') { + querySubProcessInstanceByTaskCode( + { taskId: props.taskInstance?.id }, + { projectCode: props.projectCode } + ).then((res: any) => { + router.push({ + name: 'workflow-instance-detail', + params: { id: res.subProcessInstanceId }, + query: { code: props.data.taskParams?.processDefinitionCode } + }) + }) + } else { + router.push({ + name: 'workflow-definition-detail', + params: { code: props.data.taskParams?.processDefinitionCode }, + query: {...route.query} + }) + } + }, + icon: renderIcon(BranchesOutlined) + } + ] + } + + const onTaskTypeChange = (taskType: ITaskType) => { + // eslint-disable-next-line vue/no-mutating-props + props.data.taskType = taskType + initHeaderLinks(props.processInstance, props.data.taskType) + } + + watch( + () => [props.show, props.data], + async () => { + if (!props.show) return + initHeaderLinks(props.processInstance, props.data.taskType) + taskStore.init() + await nextTick() + detailRef.value.value.setValues(formatModel(props.data)) + } + ) + + return () => ( + + + + ) + } +}) + +export default NodeDetailModal diff --git a/seatunnel-ui/src/views/task/components/node/detail.tsx b/seatunnel-ui/src/views/task/components/node/detail.tsx new file mode 100644 index 000000000..1ddb2ac6c --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/detail.tsx @@ -0,0 +1,123 @@ +/* + * 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, + watch, + Ref, + unref, + PropType, + onMounted +} from 'vue' +import Form from '@/components/form' +import { useTask } from './use-task' +import { useTaskNodeStore } from '@/store/project/task-node' +import { formatModel } from './format-data' +import type { ITaskData, EditWorkflowDefinition } from './types' + +const props = { + projectCode: { + type: Number, + default: 0 + }, + data: { + type: Object as PropType, + default: { code: 0, taskType: 'SHELL', name: '' } + }, + readonly: { + type: Boolean as PropType, + default: false + }, + from: { + type: Number as PropType, + default: 0 + }, + definitionRef: { + type: Object as PropType> + }, + setting: { + type: Object as PropType + } +} + +const NodeDetail = defineComponent({ + name: 'NodeDetail', + emits: ['taskTypeChange', 'valuesChange'], + props, + setup(props, { expose, emit }) { + const taskStore = useTaskNodeStore() + + const formRef = ref() + + const handleUpdateValue = (value: any, field: string) => { + emit('valuesChange', value, field) + } + const { elementsRef, rulesRef, model } = useTask({ + data: props.data, + projectCode: props.projectCode, + from: props.from, + readonly: props.readonly, + definitionRef: props.definitionRef, + setting: props.setting, + updateValue: handleUpdateValue + }) + watch( + () => model.taskType, + async (taskType) => { + taskStore.updateName(model.name || '') + emit('taskTypeChange', taskType) + } + ) + + expose(formRef) + + watch( + () => props.data, + () => { + if (props.data) { + formRef.value.setValues( + props.from === 0 ? props.data : formatModel(props.data) + ) + } + } + ) + + onMounted(() => { + formRef.value.setValues( + props.from === 0 ? props.data : formatModel(props.data) + ) + }) + + return () => ( +
+ ) + } +}) + +export default NodeDetail diff --git a/seatunnel-ui/src/views/task/components/node/fields/index.ts b/seatunnel-ui/src/views/task/components/node/fields/index.ts new file mode 100644 index 000000000..c18097ef5 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/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. + */ + +export { useName } from './use-name' +export { useRunFlag } from './use-run-flag' +export { useDescription } from './use-description' +export { useTaskPriority } from './use-task-priority' +export { useWorkerGroup } from './use-worker-group' +export { useEnvironmentName } from './use-environment-name' +export { useTaskGroup } from './use-task-group' +export { useFailed } from './use-failed' +export { useDelayTime } from './use-delay-time' +export { useTimeoutAlarm } from './use-timeout-alarm' +export { usePreTasks } from './use-pre-tasks' +export { useTaskType } from './use-task-type' +export { useProcessName } from './use-process-name' +export { useChildNode } from './use-child-node' +export { useTargetTaskName } from './use-target-task-name' +export { useDatasource } from './use-datasource' +export { useSqlUtils } from './use-sql-utils' +export { useCustomParams } from './use-custom-params' +export { useSourceType } from './use-sqoop-source-type' +export { useTargetType } from './use-sqoop-target-type' +export { useRelationCustomParams } from './use-relation-custom-params' +export { useDependentTimeout } from './use-dependent-timeout' +export { useRules } from './use-rules' +export { useDeployMode } from './use-deploy-mode' +export { useDriverCores } from './use-driver-cores' +export { useDriverMemory } from './use-driver-memory' +export { useExecutorNumber } from './use-executor-number' +export { useExecutorMemory } from './use-executor-memory' +export { useExecutorCores } from './use-executor-cores' +export { useMainJar } from './use-main-jar' +export { useResources } from './use-resources' +export { useDynamicDatasource } from './use-dynamic-datasource' +export { useRemoteConnection } from './use-remote-connection' + +export { useShell } from './use-shell' +export { useSpark } from './use-spark' +export { useMr } from './use-mr' +export { useFlink } from './use-flink' +export { useHttp } from './use-http' +export { useSql } from './use-sql' +export { useSqoop } from './use-sqoop' +export { useSeaTunnel } from './use-sea-tunnel' +export { useWhaleTunnel } from './use-whale-sea-tunnel' +export { useSwitch } from './use-switch' +export { useDataX } from './use-datax' +export { useConditions } from './use-conditions' +export { useDependent } from './use-dependent' +export { useEmr } from './use-emr' +export { useSurveil } from './use-surveil' +export { useNextLoop } from './use-next-loop' +export { useHiveCli } from './use-hive-cli' +export { useDinky } from './use-dinky' +export { useChunjun } from './use-chunjun' +export { useChunjunDeployMode } from './use-chunjun-deploy-mode' +export { useSagemaker } from './use-sagemaker' +export { useZeppelin } from './use-zeppelin' +export { useJupyter } from './use-jupyter' +export { useMlflow } from './use-mlflow' +export { useMlflowProjects } from './use-mlflow-projects' +export { useMlflowModels } from './use-mlflow-models' +export { useOpenmldb } from './use-openmldb' +export { useDvc } from './use-dvc' +export { usePytorch } from './use-pytorch' +export { useJar } from './use-jar' + +export { useInformatica } from './use-informatica-item' diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-child-node.ts b/seatunnel-ui/src/views/task/components/node/fields/use-child-node.ts new file mode 100644 index 000000000..f30844f9a --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-child-node.ts @@ -0,0 +1,137 @@ +/* + * 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 { ref, onMounted, h } from 'vue' +import { NIcon, NButton } from 'naive-ui' +import { BranchesOutlined } from '@vicons/antd' +import { useI18n } from 'vue-i18n' +import { useRoute, useRouter } from 'vue-router' +import { + querySimpleList, + queryProcessDefinitionByCode +} from '@/service/modules/process-definition' +import { querySubProcessInstanceByTaskCode } from '@/service/modules/process-instances' +import styles from '../index.module.scss' +import type { IJsonItem } from '../types' + +export function useChildNode({ + model, + projectCode, + from, + processName, + code, + isCreate +}: { + model: { [field: string]: any } + projectCode: number + from?: number + processName?: number + code?: number + isCreate: boolean +}): IJsonItem { + const { t } = useI18n() + const router = useRouter() + const route = useRoute() + + const options = ref([] as { label: string; value: string }[]) + const loading = ref(false) + + const getProcessList = async () => { + if (loading.value) return + loading.value = true + const res = await querySimpleList(projectCode) + options.value = res + .filter((option: { name: string; code: number }) => option.code !== code) + .map((option: { name: string; code: number }) => ({ + label: option.name, + value: option.code + })) + loading.value = false + } + const getProcessListByCode = async (processCode: number) => { + if (!processCode) return + const res = await queryProcessDefinitionByCode(processCode) + model.definition = res + } + + const handleSubProcessClick = () => { + if (router.currentRoute.value.name === 'workflow-definition-detail') { + router.push({ + name: 'workflow-definition-detail', + params: { code: model.processDefinitionCode }, + query: { project: route.query.project, global: route.query.global } + }) + return + } + if (model.instanceId) { + querySubProcessInstanceByTaskCode( + { taskId: model.instanceId }, + { projectCode: projectCode } + ).then((res: any) => { + router.push({ + name: 'workflow-instance-detail', + params: { id: res.subProcessInstanceId }, + query: { code: model.processDefinitionCode } + }) + }) + } + } + + onMounted(() => { + if (from === 1 && processName) { + getProcessListByCode(processName) + } + getProcessList() + }) + + return { + type: 'select', + field: 'processDefinitionCode', + span: 24, + name: h('div', null, [ + t('project.node.child_node'), + !isCreate + ? h( + NButton, + { + onClick: handleSubProcessClick, + quaternary: true, + circle: true, + type: 'info', + size: 'tiny', + attrType: 'button' + }, + { icon: () => h(NIcon, { size: 16 }, () => h(BranchesOutlined)) } + ) + : null + ]), + props: { + loading: loading + }, + options: options, + class: 'select-child-node', + validate: { + trigger: ['input', 'blur'], + required: true, + validator(unuse: any, value: number) { + if (!value) { + return Error(t('project.node.child_node_tips')) + } + } + } + } +} diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-chunjun-deploy-mode.ts b/seatunnel-ui/src/views/task/components/node/fields/use-chunjun-deploy-mode.ts new file mode 100644 index 000000000..9cd5083a9 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-chunjun-deploy-mode.ts @@ -0,0 +1,49 @@ +/* + * 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 { useI18n } from 'vue-i18n' +import type { IJsonItem } from '../types' + +export function useChunjunDeployMode(span = 24): IJsonItem { + const { t } = useI18n() + + return { + type: 'radio', + field: 'deployMode', + name: t('project.node.deploy_mode'), + options: DEPLOY_MODES, + span + } +} + +export const DEPLOY_MODES = [ + { + label: 'local', + value: 'local' + }, + { + label: 'standlone', + value: 'standlone' + }, + { + label: 'yarn-session', + value: 'yarn-session' + }, + { + label: 'yarn-per-job', + value: 'yarn-per-job' + } +] diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-chunjun.ts b/seatunnel-ui/src/views/task/components/node/fields/use-chunjun.ts new file mode 100644 index 000000000..7ed59acba --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-chunjun.ts @@ -0,0 +1,92 @@ +/* + * 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 { ref, onMounted } from 'vue' +import { useI18n } from 'vue-i18n' +import type { IJsonItem } from '../types' +import { useChunjunDeployMode } from './' + +export function useChunjun(model: { [field: string]: any }): IJsonItem[] { + const { t } = useI18n() + + const jsonEditorSpan = ref(0) + const customParameterSpan = ref(0) + + const initConstants = () => { + jsonEditorSpan.value = 24 + customParameterSpan.value = 24 + } + + onMounted(() => { + initConstants() + }) + + return [ + { + type: 'custom-parameters', + field: 'localParams', + name: t('project.node.custom_parameters'), + span: customParameterSpan, + children: [ + { + type: 'input', + field: 'prop', + span: 10, + props: { + placeholder: t('project.node.prop_tips'), + maxLength: 256 + }, + validate: { + trigger: ['input', 'blur'], + required: true, + validator(validate: any, value: string) { + if (!value) { + return new Error(t('project.node.prop_tips')) + } + + const sameItems = model.localParams.filter( + (item: { prop: string }) => item.prop === value + ) + + if (sameItems.length > 1) { + return new Error(t('project.node.prop_repeat')) + } + } + } + }, + { + type: 'input', + field: 'value', + span: 10, + props: { + placeholder: t('project.node.value_tips'), + maxLength: 256 + } + } + ] + }, + useChunjunDeployMode(24), + { + type: 'input', + field: 'others', + name: t('project.node.option_parameters'), + props: { + type: 'textarea', + placeholder: t('project.node.option_parameters_tips') + } + } + ] +} diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-conditions.ts b/seatunnel-ui/src/views/task/components/node/fields/use-conditions.ts new file mode 100644 index 000000000..356e6e0dd --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-conditions.ts @@ -0,0 +1,127 @@ +/* + * 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 { useI18n } from 'vue-i18n' +import { useTaskNodeStore } from '@/store/project/task-node' +import { useRelationCustomParams, useTimeoutAlarm } from '.' +import type { IJsonItem } from '../types' + +export function useConditions(model: { [field: string]: any }): IJsonItem[] { + const { t } = useI18n() + const taskStore = useTaskNodeStore() + + const preTaskOptions = taskStore.preTaskOptions.filter((option) => + taskStore.preTasks.includes(Number(option.value)) + ) + const stateOptions = [ + { label: t('project.node.success'), value: 'success' }, + { label: t('project.node.failed'), value: 'failed' } + ] + + return [ + { + type: 'select', + field: 'successNode', + name: t('project.node.state'), + span: 12, + props: { + disabled: true + }, + options: stateOptions + }, + { + type: 'select', + field: 'successBranch', + name: t('project.node.branch_flow'), + span: 12, + props: { + clearable: true + }, + validate: { + trigger: ['input', 'blur'], + validator: (unuse, value) => { + if (value && value === model.failedBranch) { + return new Error(t('project.node.branch_tips')) + } + } + }, + options: taskStore.getPostTaskOptions + }, + { + type: 'select', + field: 'failedNode', + name: t('project.node.state'), + span: 12, + props: { + disabled: true + }, + options: stateOptions + }, + { + type: 'select', + field: 'failedBranch', + name: t('project.node.branch_flow'), + span: 12, + props: { + clearable: true + }, + validate: { + trigger: ['input', 'blur'], + validator: (unuse, value) => { + if (value && value === model.successBranch) { + return new Error(t('project.node.branch_tips')) + } + } + }, + options: taskStore.getPostTaskOptions + }, + ...useTimeoutAlarm(model), + ...useRelationCustomParams({ + model, + children: { + type: 'custom-parameters', + field: 'dependItemList', + span: 18, + children: [ + { + type: 'select', + field: 'depTaskCode', + span: 10, + options: preTaskOptions + }, + { + type: 'select', + field: 'status', + span: 10, + options: [ + { + value: 'SUCCESS', + label: t('project.node.success') + }, + { + value: 'FAILURE', + label: t('project.node.failed') + } + ] + } + ] + }, + childrenField: 'dependItemList', + name: 'custom_parameters' + }) + ] +} diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-custom-params.ts b/seatunnel-ui/src/views/task/components/node/fields/use-custom-params.ts new file mode 100644 index 000000000..6adac53b3 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-custom-params.ts @@ -0,0 +1,202 @@ +/* + * 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 { Ref } from 'vue' +import { useI18n } from 'vue-i18n' +import type { IJsonItem } from '../types' + +export function useCustomParams({ + model, + field, + isSimple, + name = 'custom_parameters', + span = 24 +}: { + model: { [field: string]: any } + field: string + isSimple: boolean + name?: string + span?: Ref | number +}): IJsonItem[] { + const { t } = useI18n() + + if (isSimple) { + return [ + { + type: 'custom-parameters', + field: field, + name: t(`project.node.${name}`), + class: 'btn-custom-parameters', + span, + children: [ + { + type: 'input', + field: 'prop', + span: 10, + class: 'input-param-key', + props: { + placeholder: t('project.node.prop_tips'), + maxLength: 256 + }, + validate: { + trigger: ['input', 'blur'], + required: true, + validator(validate: any, value: string) { + if (!value) { + return new Error(t('project.node.prop_tips')) + } + + const sameItems = model[field].filter( + (item: { prop: string }) => item.prop === value + ) + + if (sameItems.length > 1) { + return new Error(t('project.node.prop_repeat')) + } + } + } + }, + { + type: 'input', + field: 'value', + span: 10, + class: 'input-param-value', + props: { + placeholder: t('project.node.value_tips'), + maxLength: 256 + } + } + ] + } + ] + } else { + return [ + { + type: 'custom-parameters', + field: field, + name: t(`project.node.${name}`), + class: 'btn-custom-parameters', + span, + children: [ + { + type: 'input', + field: 'prop', + span: 6, + class: 'input-param-key', + props: { + placeholder: t('project.node.prop_tips'), + maxLength: 256 + }, + validate: { + trigger: ['input', 'blur'], + required: true, + validator(validate: any, value: string) { + if (!value) { + return new Error(t('project.node.prop_tips')) + } + + const sameItems = model[field].filter( + (item: { prop: string }) => item.prop === value + ) + + if (sameItems.length > 1) { + return new Error(t('project.node.prop_repeat')) + } + } + } + }, + { + type: 'select', + field: 'direct', + span: 4, + options: DIRECT_LIST, + value: 'IN' + }, + { + type: 'select', + field: 'type', + span: 6, + options: TYPE_LIST, + value: 'VARCHAR' + }, + { + type: 'input', + field: 'value', + span: 6, + class: 'input-param-value', + props: { + placeholder: t('project.node.value_tips'), + maxLength: 256 + } + } + ] + } + ] + } +} +export const TYPE_LIST = [ + { + value: 'VARCHAR', + label: 'VARCHAR' + }, + { + value: 'INTEGER', + label: 'INTEGER' + }, + { + value: 'LONG', + label: 'LONG' + }, + { + value: 'FLOAT', + label: 'FLOAT' + }, + { + value: 'DOUBLE', + label: 'DOUBLE' + }, + { + value: 'DATE', + label: 'DATE' + }, + { + value: 'TIME', + label: 'TIME' + }, + { + value: 'TIMESTAMP', + label: 'TIMESTAMP' + }, + { + value: 'BOOLEAN', + label: 'BOOLEAN' + }, + { + value: 'LIST', + label: 'LIST' + } +] + +export const DIRECT_LIST = [ + { + value: 'IN', + label: 'IN' + }, + { + value: 'OUT', + label: 'OUT' + } +] diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-datasource.ts b/seatunnel-ui/src/views/task/components/node/fields/use-datasource.ts new file mode 100644 index 000000000..8cbecd61e --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-datasource.ts @@ -0,0 +1,117 @@ +/* + * 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 { ref, onMounted, nextTick, Ref, toRef } from 'vue' +import { useI18n } from 'vue-i18n' +import type { IJsonItem } from '../types' +import { useSourceType } from '@/hooks/use-source-type' +import { find } from 'lodash' +import { getResourceList } from '@/service/modules/resources' +import { useRoute } from 'vue-router' + +export function useDatasource({ + typeName, + sourceName, + model, + typeField = 'type', + sourceField = 'datasource', + span = 12 +}: { + typeName?: string + sourceName?: string + model: { [field: string]: any } + typeField?: string + sourceField?: string + span?: Ref | number +}): IJsonItem[] { + const { t } = useI18n() + + const datasourceOptions = ref([] as { label: string; value: number }[]) + + const { state } = useSourceType() + const route = useRoute() + const projectCode = Number(route.params.projectCode) + + const options = toRef(state, 'types') + + const refreshOptions = async () => { + const data = await getResourceList({ + projectCode, + accessType: 'DATASOURCE', + resourceType: model[typeField] + }) + datasourceOptions.value = data.map((item: any) => ({ + label: item.datasourceName, + value: item.id + })) + if (!data.length && model[sourceField]) model[sourceField] = null + if (data.length && model[sourceField]) { + const item = find(data, { id: model[sourceField] }) + if (!item) { + model[sourceField] = null + } + } + } + + const onChange = () => { + refreshOptions() + } + + onMounted(async () => { + await nextTick() + model[typeField] && refreshOptions() + }) + + return [ + { + type: 'select', + field: typeField, + span, + name: t(typeName || 'project.node.datasource_type'), + props: { + 'on-update:value': onChange, + loading: state.loading + }, + options, + validate: { + trigger: ['input', 'blur'], + required: true, + validator(unuse: any, value) { + if (!value && value !== 0) { + return Error(t('project.node.datasource_type')) + } + } + } + }, + { + type: 'select', + field: sourceField, + span, + name: t(sourceName || 'project.node.datasource_instances'), + options: datasourceOptions, + validate: { + trigger: ['input', 'blur'], + required: true, + validator(unuse: any, value) { + if (!value && value !== 0) { + return Error(t('project.node.datasource_instances')) + } + } + } + } + ] +} diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-datax.ts b/seatunnel-ui/src/views/task/components/node/fields/use-datax.ts new file mode 100644 index 000000000..e6c17c420 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-datax.ts @@ -0,0 +1,289 @@ +/* + * 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 { ref, onMounted, watch } from 'vue' +import { useI18n } from 'vue-i18n' +import { useDatasource } from '.' +import type { IJsonItem } from '../types' +import { useCustomParams } from '.' + +export function useDataX( + model: { [field: string]: any }, + updateValue?: (value: any, field: string) => void +): IJsonItem[] { + const { t } = useI18n() + + const jobSpeedByteOptions: any[] = [ + { + label: `0(${t('project.node.unlimited')})`, + value: 0 + }, + { + label: '1KB', + value: 1024 + }, + { + label: '10KB', + value: 10240 + }, + { + label: '50KB', + value: 51200 + }, + { + label: '100KB', + value: 102400 + }, + { + label: '512KB', + value: 524288 + } + ] + const jobSpeedRecordOptions: any[] = [ + { + label: `0(${t('project.node.unlimited')})`, + value: 0 + }, + { + label: '500', + value: 500 + }, + { + label: '1000', + value: 1000 + }, + { + label: '1500', + value: 1500 + }, + { + label: '2000', + value: 2000 + }, + { + label: '2500', + value: 2500 + }, + { + label: '3000', + value: 3000 + } + ] + const memoryLimitOptions = [ + { + label: '1G', + value: 1 + }, + { + label: '2G', + value: 2 + }, + { + label: '3G', + value: 3 + }, + { + label: '4G', + value: 4 + } + ] + + const sqlEditorSpan = ref(24) + const jsonEditorSpan = ref(0) + const datasourceSpan = ref(12) + const destinationDatasourceSpan = ref(8) + const otherStatementSpan = ref(22) + const jobSpeedSpan = ref(12) + const customParameterSpan = ref(0) + + const initConstants = () => { + if (model.customConfig) { + sqlEditorSpan.value = 0 + jsonEditorSpan.value = 24 + datasourceSpan.value = 0 + destinationDatasourceSpan.value = 0 + otherStatementSpan.value = 0 + jobSpeedSpan.value = 0 + customParameterSpan.value = 24 + } else { + sqlEditorSpan.value = 24 + jsonEditorSpan.value = 0 + datasourceSpan.value = 12 + destinationDatasourceSpan.value = 8 + otherStatementSpan.value = 22 + jobSpeedSpan.value = 12 + customParameterSpan.value = 0 + } + } + + onMounted(() => { + initConstants() + }) + + watch( + () => model.customConfig, + () => { + initConstants() + const handlers = model.customConfig + ? [{ key: 'script', name: t('project.node.datax_json_template') }] + : [{ key: 'script', name: t('project.node.sql_statement') }] + if (updateValue) { + updateValue( + { + handlers, + language: model.customConfig ? 'json' : 'sql', + script: '' + }, + 'batch' + ) + } + } + ) + + return [ + { + type: 'switch', + field: 'customConfig', + name: t('project.node.datax_custom_template') + }, + ...useDatasource({ + model, + typeField: 'dsType', + sourceField: 'dataSource', + span: datasourceSpan + }), + ...useDatasource({ + model, + typeField: 'dtType', + sourceField: 'dataTarget', + span: destinationDatasourceSpan, + typeName: 'project.node.datax_target_datasource_type', + sourceName: 'project.node.datax_target_database' + }), + { + type: 'input', + field: 'targetTable', + name: t('project.node.datax_target_table'), + span: destinationDatasourceSpan, + props: { + placeholder: t('project.node.datax_target_table_tips') + }, + validate: { + trigger: ['input', 'blur'], + required: true, + message: t('project.node.datax_target_table_tips') + } + }, + { + type: 'multi-input', + field: 'preStatements', + name: t('project.node.datax_target_database_pre_sql'), + span: otherStatementSpan, + props: { + placeholder: t('project.node.datax_non_query_sql_tips'), + type: 'textarea', + autosize: { minRows: 1 } + } + }, + { + type: 'multi-input', + field: 'postStatements', + name: t('project.node.datax_target_database_post_sql'), + span: otherStatementSpan, + props: { + placeholder: t('project.node.datax_non_query_sql_tips'), + type: 'textarea', + autosize: { minRows: 1 } + } + }, + { + type: 'select', + field: 'jobSpeedByte', + name: t('project.node.datax_job_speed_byte'), + span: jobSpeedSpan, + options: jobSpeedByteOptions, + value: 0 + }, + { + type: 'select', + field: 'jobSpeedRecord', + name: t('project.node.datax_job_speed_record'), + span: jobSpeedSpan, + options: jobSpeedRecordOptions, + value: 1000 + }, + { + type: 'custom-parameters', + field: 'localParams', + name: t('project.node.custom_parameters'), + span: customParameterSpan, + children: [ + { + type: 'input', + field: 'prop', + span: 10, + props: { + placeholder: t('project.node.prop_tips'), + maxLength: 256 + }, + validate: { + trigger: ['input', 'blur'], + required: true, + validator(validate: any, value: string) { + if (!value) { + return new Error(t('project.node.prop_tips')) + } + + const sameItems = model.localParams.filter( + (item: { prop: string }) => item.prop === value + ) + + if (sameItems.length > 1) { + return new Error(t('project.node.prop_repeat')) + } + } + } + }, + { + type: 'input', + field: 'value', + span: 10, + props: { + placeholder: t('project.node.value_tips'), + maxLength: 256 + } + } + ] + }, + { + type: 'select', + field: 'xms', + name: t('project.node.datax_job_runtime_memory_xms'), + span: 12, + options: memoryLimitOptions, + value: 1 + }, + { + type: 'select', + field: 'xmx', + name: t('project.node.datax_job_runtime_memory_xmx'), + span: 12, + options: memoryLimitOptions, + value: 1 + }, + ...useCustomParams({ model, field: 'localParams', isSimple: true }) + ] +} diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-delay-time.ts b/seatunnel-ui/src/views/task/components/node/fields/use-delay-time.ts new file mode 100644 index 000000000..7e747d6e5 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-delay-time.ts @@ -0,0 +1,33 @@ +/* + * 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 { useI18n } from 'vue-i18n' +import type { IJsonItem } from '../types' + +export function useDelayTime(model: { [field: string]: any }): IJsonItem { + const { t } = useI18n() + return { + type: 'input-number', + field: 'delayTime', + name: t('project.node.delay_execution_time'), + span: 12, + slots: { + suffix: () => t('project.node.minute') + }, + value: model.delayTime || 0 + } +} diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-dependent-timeout.ts b/seatunnel-ui/src/views/task/components/node/fields/use-dependent-timeout.ts new file mode 100644 index 000000000..09df39100 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-dependent-timeout.ts @@ -0,0 +1,101 @@ +/* + * 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 { computed, watch } from 'vue' +import { useI18n } from 'vue-i18n' +import type { IJsonItem } from '../types' + +export function useDependentTimeout(model: { + [field: string]: any +}): IJsonItem[] { + const { t } = useI18n() + const timeCompleteSpan = computed(() => (model.timeoutShowFlag ? 24 : 0)) + const timeCompleteEnableSpan = computed(() => (model.timeoutFlag ? 12 : 0)) + + const strategyOptions = [ + { + label: t('project.node.timeout_alarm'), + value: 'WARN' + }, + { + label: t('project.node.timeout_failure'), + value: 'FAILED' + } + ] + watch( + () => model.timeoutFlag, + (timeoutFlag) => { + model.timeoutNotifyStrategy = timeoutFlag ? ['WARN'] : [] + model.timeout = timeoutFlag ? 30 : null + } + ) + + return [ + { + type: 'switch', + field: 'timeoutShowFlag', + name: t('project.node.timeout_alarm') + }, + { + type: 'switch', + field: 'timeoutFlag', + name: t('project.node.waiting_dependent_complete'), + props: { + 'on-update:value': (value: boolean) => { + model.timeoutNotifyStrategy = value ? ['WARN'] : null + model.timeout = value ? 30 : null + } + }, + span: timeCompleteSpan + }, + { + type: 'input-number', + field: 'timeout', + name: t('project.node.timeout_period'), + span: timeCompleteEnableSpan, + props: { + max: Math.pow(9, 10) - 1 + }, + slots: { + suffix: () => t('project.node.minute') + }, + validate: { + trigger: ['input'], + validator(validate: any, value: number) { + if (model.timeoutFlag && !/^[1-9]\d*$/.test(String(value))) { + return new Error(t('project.node.timeout_period_tips')) + } + } + } + }, + { + type: 'checkbox', + field: 'timeoutNotifyStrategy', + name: t('project.node.timeout_strategy'), + options: strategyOptions, + span: timeCompleteEnableSpan, + validate: { + trigger: ['input'], + validator(validate: any, value: []) { + if (model.waitCompleteTimeoutEnable && !value.length) { + return new Error(t('project.node.timeout_strategy_tips')) + } + } + } + } + ] +} diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-dependent.ts b/seatunnel-ui/src/views/task/components/node/fields/use-dependent.ts new file mode 100644 index 000000000..c06ac0a4c --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-dependent.ts @@ -0,0 +1,543 @@ +/* + * 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 { ref, onMounted, watch, h, VNode, computed } from 'vue' +import { useI18n } from 'vue-i18n' +import { Router, useRouter } from 'vue-router' +import { useRelationCustomParams, useDependentTimeout } from '.' +import { useTaskNodeStore } from '@/store/project/task-node' +import { NIcon, NPopover } from 'naive-ui' +import { QuestionCircleOutlined } from '@vicons/antd' +import { queryProjectCreatedAndAuthorizedByUser } from '@/service/modules/projects' +import { + queryAllByProjectCode, + getTasksByDefinitionCode, queryProcessDefinitionListByProjectCode +} from '@/service/modules/process-definition' +import { tasksState } from '@/common/common' +import styles from '../index.module.scss' +import type { + IJsonItem, + IDependpendItem, + IDependTask, + ITaskState, + IDateType +} from '../types' +import { getResourceList } from '@/service/modules/resources' +import { useRoute } from 'vue-router' + +export function useDependent( + model: { [field: string]: any }, + setting: any +): IJsonItem[] { + const { t } = useI18n() + + const router: Router = useRouter() + const nodeStore = useTaskNodeStore() + + const cardRef = ref(t('project.node.cart_null_tips')) + const route = useRoute() + const projectCode = Number(route.params.projectCode) + + const dependentResult = nodeStore.getDependentResult + const TasksStateConfig = tasksState(t) + + const projectList = ref([] as { label: string; value: number }[]) + const processCache = {} as { + [key: number]: { label: string; value: number }[] + } + const taskCache = {} as { + [key: number]: { label: string; value: number }[] + } + + const CYCLE_TYPE = [ + { + value: 'NATURAL', + label: t('project.node.natural') + }, + { + value: 'BUSINESS', + label: t('project.node.business') + } + ] + + const CYCLE_LIST = [ + { + value: 'month', + label: t('project.node.month') + }, + { + value: 'week', + label: t('project.node.week') + }, + { + value: 'day', + label: t('project.node.day') + }, + { + value: 'hour', + label: t('project.node.hour') + } + ] + const DATE_LSIT = { + hour: [ + { + value: 'currentHour', + label: t('project.node.current_hour') + }, + { + value: 'last1Hour', + label: t('project.node.last_1_hour') + }, + { + value: 'last2Hours', + label: t('project.node.last_2_hour') + }, + { + value: 'last3Hours', + label: t('project.node.last_3_hour') + }, + { + value: 'last24Hours', + label: t('project.node.last_24_hour') + } + ], + day: [ + { + value: 'today', + label: t('project.node.today') + }, + { + value: 'last1Days', + label: t('project.node.last_1_days') + }, + { + value: 'last2Days', + label: t('project.node.last_2_days') + }, + { + value: 'last3Days', + label: t('project.node.last_3_days') + }, + { + value: 'last7Days', + label: t('project.node.last_7_days') + } + ], + week: [ + { + value: 'thisWeek', + label: t('project.node.this_week') + }, + { + value: 'lastWeek', + label: t('project.node.last_week') + }, + { + value: 'lastMonday', + label: t('project.node.last_monday') + }, + { + value: 'lastTuesday', + label: t('project.node.last_tuesday') + }, + { + value: 'lastWednesday', + label: t('project.node.last_wednesday') + }, + { + value: 'lastThursday', + label: t('project.node.last_thursday') + }, + { + value: 'lastFriday', + label: t('project.node.last_friday') + }, + { + value: 'lastSaturday', + label: t('project.node.last_saturday') + }, + { + value: 'lastSunday', + label: t('project.node.last_sunday') + } + ], + month: [ + { + value: 'thisMonth', + label: t('project.node.this_month') + }, + { + value: 'thisMonthBegin', + label: t('project.node.this_month_begin') + }, + { + value: 'lastMonth', + label: t('project.node.last_month') + }, + { + value: 'lastMonthBegin', + label: t('project.node.last_month_begin') + }, + { + value: 'lastMonthEnd', + label: t('project.node.last_month_end') + } + ] + } as { [key in IDateType]: { value: string; label: string }[] } + + const getProjectList = async () => { + const result = await queryProjectCreatedAndAuthorizedByUser() + projectList.value = result.map((item: { code: number; name: string }) => ({ + value: item.code, + label: item.name + })) + return projectList + } + const getProcessList = async (code: number) => { + if (processCache[code]) { + return processCache[code] + } + const result = await queryProcessDefinitionListByProjectCode(code) + const processList = result.map( + (item: { code: number; name: string }) => ({ + value: item.code, + label: item.name + }) + ) + processCache[code] = processList + + return processList + } + + const getTaskList = async (code: number, processCode: number) => { + if (taskCache[processCode]) { + return taskCache[processCode] + } + const result = await getTasksByDefinitionCode(code, processCode) + const taskList = result.map((item: { code: number; name: string }) => ({ + value: item.code, + label: item.name + })) + taskList.unshift({ + value: 0, + label: 'ALL' + }) + taskCache[processCode] = taskList + return taskList + } + + const renderState = (item: { + definitionCode: number + depTaskCode: number + cycle: string + dateValue: string + }) => { + if (!item || router.currentRoute.value.name !== 'workflow-instance-detail') + return null + const key = `${item.definitionCode}-${item.depTaskCode}-${item.cycle}-${item.dateValue}` + const state: ITaskState = dependentResult[key] || 'WAITING_THREAD' + return h(NIcon, { size: 24, color: TasksStateConfig[state].color }, () => + h(TasksStateConfig[state].icon) + ) + } + + const renderStrategyLabelExtra = (value: 0 | 1): VNode => { + return h( + NPopover, + { trigger: 'hover' }, + { + trigger: () => + h(NIcon, { size: 20, class: styles['question-icon'] }, () => + h(QuestionCircleOutlined) + ), + default: () => + value === 0 + ? t('project.node.failure_continue_tips') + : t('project.node.failure_wait_tips') + } + ) + } + + const initDependTaskList = (dependTaskList: IDependTask[]) => { + dependTaskList.forEach((item: IDependTask) => { + if (!item.dependItemList?.length) return + item.dependItemList?.forEach(async (dependItem: IDependpendItem) => { + if (dependItem.projectCode) { + dependItem.definitionCodeOptions = await getProcessList( + dependItem.projectCode + ) + } + if (dependItem.projectCode && dependItem.definitionCode) { + dependItem.depTaskCodeOptions = await getTaskList( + dependItem.projectCode, + dependItem.definitionCode + ) + } + if (dependItem.cycle) { + dependItem.dateOptions = DATE_LSIT[dependItem.cycle] + } + }) + }) + } + + const initProjectCard = async () => { + if (!setting || !setting.cardCode) { + cardRef.value = t('project.node.cart_null_tips') + return + } + + const cards = + (await getResourceList({ + projectCode, + accessType: 'DATA_CARD' + })) || [] + const card = cards.filter( + (item: { cardCode: number }) => item.cardCode === setting.cardCode + ) + cardRef.value = card.length > 0 ? card[0].cardValue : ' ' + } + + onMounted(() => { + getProjectList() + initDependTaskList(model.dependTaskList) + initProjectCard() + }) + + watch( + () => setting.cardCode, + () => initProjectCard() + ) + + watch( + () => model.dependTaskList, + (value) => initDependTaskList(value) + ) + + return [ + ...useDependentTimeout(model), + ...useRelationCustomParams({ + model, + children: (i = 0) => ({ + type: 'custom-parameters', + field: 'dependItemList', + span: 18, + children: [ + (j = 0) => ({ + type: 'select', + field: 'projectCode', + name: t('project.node.project_name'), + span: 24, + props: { + filterable: true, + onUpdateValue: async (projectCode: number) => { + const item = model.dependTaskList[i].dependItemList[j] + item.definitionCodeOptions = await getProcessList(projectCode) + item.depTaskCode = null + item.definitionCode = null + } + }, + options: projectList, + path: `dependTaskList.${i}.dependItemList.${j}.projectCode`, + rule: { + required: true, + trigger: ['input', 'blur'], + validator(validate: any, value: string) { + if (!value) { + return Error(t('project.node.project_name_tips')) + } + } + } + }), + (j = 0) => ({ + type: 'select', + field: 'definitionCode', + span: 24, + name: t('project.node.process_name'), + props: { + filterable: true, + onUpdateValue: async (processCode: number) => { + const item = model.dependTaskList[i].dependItemList[j] + item.depTaskCodeOptions = await getTaskList( + item.projectCode, + processCode + ) + item.depTaskCode = 0 + } + }, + options: + model.dependTaskList[i]?.dependItemList[j] + ?.definitionCodeOptions || [], + path: `dependTaskList.${i}.dependItemList.${j}.definitionCode`, + rule: { + required: true, + trigger: ['input', 'blur'], + validator(validate: any, value: string) { + if (!value) { + return Error(t('project.node.process_name_tips')) + } + } + } + }), + (j = 0) => ({ + type: 'select', + field: 'depTaskCode', + span: 24, + name: t('project.node.task_name'), + props: { + filterable: true + }, + options: + model.dependTaskList[i]?.dependItemList[j]?.depTaskCodeOptions || + [], + path: `dependTaskList.${i}.dependItemList.${j}.depTaskCode`, + rule: { + required: true, + trigger: ['input', 'blur'], + validator(validate: any, value: number) { + if (!value && value !== 0) { + return Error(t('project.node.task_name_tips')) + } + } + } + }), + (j = 0) => ({ + type: 'radio', + field: 'timeType', + span: 24, + value: 'NATURAL', + props: { + onUpdateValue: (value: 'NATURAL' | 'BUSINESS') => { + if (value === 'BUSINESS') { + model.dependTaskList[i].dependItemList[j].cycle = 'day' + model.dependTaskList[i].dependItemList[j].dateValue = null + model.dependTaskList[i].dependItemList[j].dateOptions = + DATE_LSIT['day'] + } else { + model.dependTaskList[i].dependItemList[j].cycle = null + model.dependTaskList[i].dependItemList[j].dateValue = null + model.dependTaskList[i].dependItemList[j].dateOptions = [] + } + } + }, + options: CYCLE_TYPE, + path: `dependTaskList.${i}.dependItemList.${j}.timeType`, + rule: { + required: true, + trigger: ['input', 'blur'], + validator(validate: any, value: string) { + if (!value) { + return Error(t('project.node.cycle_time_tips')) + } + } + } + }), + (j = 0) => ({ + type: 'input', + field: 'cardValue', + props: { + disabled: true, + defaultValue: cardRef + }, + span: computed(() => + model.dependTaskList[i].dependItemList[j].timeType === 'BUSINESS' + ? 24 + : 0 + ), + name: t('project.node.card'), + value: cardRef + }), + (j = 0) => ({ + type: 'select', + field: 'cycle', + span: 10, + name: t('project.node.cycle_time'), + props: { + disabled: computed( + () => + model.dependTaskList[i] && + model.dependTaskList[i].dependItemList[j] && + model.dependTaskList[i].dependItemList[j].timeType === + 'BUSINESS' + ).value, + onUpdateValue: (value: IDateType) => { + model.dependTaskList[i].dependItemList[j].dateOptions = + DATE_LSIT[value] + model.dependTaskList[i].dependItemList[j].dateValue = null + } + }, + options: CYCLE_LIST, + path: `dependTaskList.${i}.dependItemList.${j}.cycle`, + rule: { + required: false, + trigger: ['input', 'blur'], + validator(validate: any, value: string) { + if (!value) { + return Error(t('project.node.cycle_time_tips')) + } + } + } + }), + (j = 0) => ({ + type: 'select', + field: 'dateValue', + span: 10, + name: ' ', + options: + model.dependTaskList[i]?.dependItemList[j]?.dateOptions || [], + path: `dependTaskList.${i}.dependItemList.${j}.dateValue`, + rule: { + trigger: ['input', 'blur'], + validator(validate: any, value: string) { + if (!value) { + return Error(t('project.node.date_tips')) + } + } + } + }), + (j = 0) => ({ + type: 'custom', + field: 'state', + span: 2, + name: ' ', + widget: renderState(model.dependTaskList[i]?.dependItemList[j]) + }) + ] + }), + childrenField: 'dependItemList', + name: 'add_dependency' + }), + { + type: 'radio', + field: 'dependStrategy', + name: t('project.node.dependency_strategy'), + props: {}, + options: [ + { + label: + t('project.node.failure') + ' -> ' + t('project.node.continue'), + value: 'failure-continue', + extra: renderStrategyLabelExtra(0) + }, + { + label: t('project.node.failure') + ' -> ' + t('project.node.wait'), + value: 'failure-waiting', + extra: renderStrategyLabelExtra(1) + } + ] + } + ] +} diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-deploy-mode.ts b/seatunnel-ui/src/views/task/components/node/fields/use-deploy-mode.ts new file mode 100644 index 000000000..2ce3ad534 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-deploy-mode.ts @@ -0,0 +1,62 @@ +/* + * 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 {ref, watchEffect} from 'vue' +import {useI18n} from 'vue-i18n' +import type {IJsonItem, IOption} from '../types' + +export function useDeployMode(span = 24, showClient = ref(true), showCluster = ref(true)): IJsonItem { + const {t} = useI18n() + + const deployModeOptions = ref(DEPLOY_MODES as IOption[]) + + watchEffect( + () => { + deployModeOptions.value = DEPLOY_MODES.filter((option) => { + switch (option.value) { + case 'cluster': + return showCluster.value + case 'client': + return showClient.value + default: + return true + } + }) + } + ) + return { + type: 'radio', + field: 'deployMode', + name: t('project.node.deploy_mode'), + options: deployModeOptions, + span + } +} + +export const DEPLOY_MODES = [ + { + label: 'cluster', + value: 'cluster' + }, + { + label: 'client', + value: 'client' + }, + { + label: 'local', + value: 'local' + } +] diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-description.ts b/seatunnel-ui/src/views/task/components/node/fields/use-description.ts new file mode 100644 index 000000000..e317554c4 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-description.ts @@ -0,0 +1,33 @@ +/* + * 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 { useI18n } from 'vue-i18n' +import type { IJsonItem } from '../types' + +export function useDescription(): IJsonItem { + const { t } = useI18n() + return { + type: 'input', + field: 'description', + name: t('project.node.description'), + props: { + placeholder: t('project.node.description_tips'), + rows: 2, + type: 'textarea' + } + } +} diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-dinky.ts b/seatunnel-ui/src/views/task/components/node/fields/use-dinky.ts new file mode 100644 index 000000000..d043b2209 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-dinky.ts @@ -0,0 +1,66 @@ +/* + * 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 { useI18n } from 'vue-i18n' +import { useCustomParams } from '.' +import type { IJsonItem } from '../types' + +export function useDinky(model: { [field: string]: any }): IJsonItem[] { + const { t } = useI18n() + + return [ + { + type: 'input', + field: 'address', + name: t('project.node.dinky_address'), + props: { + placeholder: t('project.node.dinky_address_tips') + }, + validate: { + trigger: ['input', 'blur'], + required: true, + validator(_validate: any, value: string) { + if (!value) { + return new Error(t('project.node.dinky_address_tips')) + } + } + } + }, + { + type: 'input', + field: 'taskId', + name: t('project.node.dinky_task_id'), + props: { + placeholder: t('project.node.dinky_task_id_tips') + }, + validate: { + trigger: ['input', 'blur'], + required: true, + validator(_validate: any, value: string) { + if (!value) { + return new Error(t('project.node.dinky_task_id_tips')) + } + } + } + }, + { + type: 'switch', + field: 'online', + name: t('project.node.dinky_online') + }, + ...useCustomParams({ model, field: 'localParams', isSimple: false }) + ] +} diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-driver-cores.ts b/seatunnel-ui/src/views/task/components/node/fields/use-driver-cores.ts new file mode 100644 index 000000000..82b06f027 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-driver-cores.ts @@ -0,0 +1,42 @@ +/* + * 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 { useI18n } from 'vue-i18n' +import type { IJsonItem } from '../types' + +export function useDriverCores(): IJsonItem { + const { t } = useI18n() + + return { + type: 'input-number', + field: 'driverCores', + name: t('project.node.driver_cores'), + span: 12, + props: { + placeholder: t('project.node.driver_cores_tips'), + min: 1 + }, + validate: { + trigger: ['input', 'blur'], + required: true, + validator(validate: any, value: string) { + if (!value) { + return new Error(t('project.node.driver_cores_tips')) + } + } + } + } +} diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-driver-memory.ts b/seatunnel-ui/src/views/task/components/node/fields/use-driver-memory.ts new file mode 100644 index 000000000..6ba53b137 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-driver-memory.ts @@ -0,0 +1,47 @@ +/* + * 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 { useI18n } from 'vue-i18n' +import type { IJsonItem } from '../types' + +export function useDriverMemory(): IJsonItem { + const { t } = useI18n() + + return { + type: 'input', + field: 'driverMemory', + name: t('project.node.driver_memory'), + span: 12, + props: { + placeholder: t('project.node.driver_memory_tips') + }, + validate: { + trigger: ['input', 'blur'], + required: true, + validator(validate: any, value: string) { + if (!value) { + return new Error(t('project.node.driver_memory_tips')) + } + if (!Number.isInteger(parseInt(value))) { + return new Error( + t('project.node.driver_memory') + + t('project.node.positive_integer_tips') + ) + } + } + } + } +} diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-dvc.ts b/seatunnel-ui/src/views/task/components/node/fields/use-dvc.ts new file mode 100644 index 000000000..9352e9dcd --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-dvc.ts @@ -0,0 +1,175 @@ +/* + * 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 { useI18n } from 'vue-i18n' +import type { IJsonItem } from '../types' +import { watch, ref } from 'vue' +import {useResources} from "@/views/projects/task/components/node/fields/use-resources"; +import {useCustomParams} from "@/views/projects/task/components/node/fields/use-custom-params"; + +export const DVC_TASK_TYPE = [ + { + label: 'Upload', + value: 'Upload' + }, + { + label: 'Download', + value: 'Download' + }, + { + label: 'Init DVC', + value: 'Init DVC' + } +] + +export function useDvc(model: { [field: string]: any }): IJsonItem[] { + const { t } = useI18n() + + const dvcLoadSaveDataPathSpan = ref(0) + const dvcDataLocationSpan = ref(0) + const dvcVersionSpan = ref(0) + const dvcMessageSpan = ref(0) + const dvcStoreUrlSpan = ref(0) + + const setFlag = () => { + model.isUpload = model.dvcTaskType === 'Upload' + model.isDownload = model.dvcTaskType === 'Download' + model.isInit = model.dvcTaskType === 'Init DVC' + } + + const resetData = () => { + dvcLoadSaveDataPathSpan.value = model.isUpload || model.isDownload ? 24 : 0 + dvcDataLocationSpan.value = model.isUpload || model.isDownload ? 24 : 0 + dvcVersionSpan.value = model.isUpload || model.isDownload ? 24 : 0 + dvcMessageSpan.value = model.isUpload ? 24 : 0 + dvcStoreUrlSpan.value = model.isInit ? 24 : 0 + } + + watch( + () => [model.dvcTaskType], + () => { + setFlag() + resetData() + } + ) + setFlag() + resetData() + + return [ + { + type: 'select', + field: 'dvcTaskType', + name: t('project.node.dvc_task_type'), + span: 12, + options: DVC_TASK_TYPE + }, + { + type: 'input', + field: 'dvcRepository', + name: t('project.node.dvc_repository'), + span: 24, + props: { + placeholder: t('project.node.dvc_repository_tips') + }, + validate: { + trigger: ['input', 'blur'], + required: true, + message: t('project.node.dvc_empty_tips') + } + }, + { + type: 'input', + field: 'dvcDataLocation', + name: t('project.node.dvc_data_location'), + props: { placeholder: 'dvcDataLocation' }, + span: dvcDataLocationSpan, + validate: { + trigger: ['input', 'blur'], + required: true, + validator(validate: any, value: string) { + if ((model.isUpload || model.isDownload) && !value) { + return new Error(t('project.node.dvc_empty_tips')) + } + } + } + }, + { + type: 'input', + field: 'dvcLoadSaveDataPath', + name: t('project.node.dvc_load_save_data_path'), + span: dvcLoadSaveDataPathSpan, + validate: { + trigger: ['input', 'blur'], + required: true, + validator(validate: any, value: string) { + if ((model.isUpload || model.isDownload) && !value) { + return new Error(t('project.node.dvc_empty_tips')) + } + } + } + }, + { + type: 'input', + field: 'dvcVersion', + name: t('project.node.dvc_version'), + span: dvcVersionSpan, + props: { + placeholder: t('project.node.dvc_version_tips') + }, + validate: { + trigger: ['input', 'blur'], + required: true, + validator(validate: any, value: string) { + if ((model.isUpload || model.isDownload) && !value) { + return new Error(t('project.node.dvc_empty_tips')) + } + } + } + }, + { + type: 'input', + field: 'dvcMessage', + name: t('project.node.dvc_message'), + span: dvcMessageSpan, + validate: { + trigger: ['input', 'blur'], + required: true, + validator(validate: any, value: string) { + if (model.isUpload && !value) { + return new Error(t('project.node.dvc_empty_tips')) + } + } + } + }, + { + type: 'input', + field: 'dvcStoreUrl', + name: t('project.node.dvc_store_url'), + span: dvcStoreUrlSpan, + validate: { + trigger: ['input', 'blur'], + required: true, + validator(validate: any, value: string) { + if (!model.isInit && value) { + return new Error(t('project.node.dvc_empty_tips')) + } + } + } + }, + useResources(), + ...useCustomParams({ model, field: 'localParams', isSimple: true }) + ] +} diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-dynamic-datasource.ts b/seatunnel-ui/src/views/task/components/node/fields/use-dynamic-datasource.ts new file mode 100644 index 000000000..8050ffa3c --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-dynamic-datasource.ts @@ -0,0 +1,132 @@ +/* + * 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 { ref, onMounted, nextTick, Ref, toRef } from 'vue' +import { useI18n } from 'vue-i18n' +import { useRoute } from 'vue-router' +import { find } from 'lodash' +import type { IJsonItem } from '../types' +import { useSourceType } from '@/hooks/use-source-type' +import { getResourceList } from '@/service/modules/resources' + +export function useDynamicDatasource({ + typeName, + sourceName, + model, + typeField = 'type', + sourceField = 'datasource', + span = 10 +}: { + typeName?: string + sourceName?: string + model: { [field: string]: any } + supportedDatasourceType?: string[] + typeField?: string + sourceField?: string + span?: Ref | number +}): IJsonItem[] { + const { t } = useI18n() + + const route = useRoute() + const projectCode = Number(route.params.projectCode) + + const { state } = useSourceType() + const options = toRef(state, 'types') + + const datasourceOptions = ref([[]] as Array< + { label: string; value: number }[] + >) + + const refreshOptions = async (dataSourceType: string, index = 0) => { + const data = await getResourceList({ + projectCode, + accessType: 'DATASOURCE', + resourceType: dataSourceType + }) + datasourceOptions.value[index] = data.map((item: any) => ({ + label: item.datasourceName, + value: item.id + })) + if (!data.length && model.dataSourceList[index]) { + model.dataSourceList[index][sourceField] = null + } + + if (data.length && model.dataSourceList[index]) { + const item = find(data, { id: model.dataSourceList[index][sourceField] }) + if (!item) { + model.dataSourceList[index][sourceField] = null + } + } + } + + const onChange = (dataSourceType: string, index: number) => { + refreshOptions(dataSourceType, index) + } + + onMounted(async () => { + await nextTick() + model.dataSourceList.map( + (item: { type: string; datasource: number }, index: number) => { + refreshOptions(item.type, index) + } + ) + }) + return [ + { + type: 'custom-parameters', + field: 'dataSourceList', + name: t('project.node.datasource'), + children: [ + (i) => { + return { + type: 'select', + field: typeField, + span, + props: { + 'on-update:value': (dataSourceType: string) => + onChange(dataSourceType, i as number), + placeholder: t(typeName || 'project.node.datasource_type') + }, + options + } + }, + (i) => { + return { + type: 'select', + field: sourceField, + span, + options: datasourceOptions.value[i as number], + props: { + placeholder: t(sourceName || 'project.node.datasource_instances') + }, + validate: { + trigger: ['input', 'blur'], + required: true, + validator() { + for (const item of model.dataSourceList) { + if (!item.type || !item.datasource) { + return Error(t('project.node.datasource_require_tips')) + } + } + } + } + } + } + ] + } + ] +} diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-emr.ts b/seatunnel-ui/src/views/task/components/node/fields/use-emr.ts new file mode 100644 index 000000000..fbc28f9ec --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-emr.ts @@ -0,0 +1,48 @@ +/* + * 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 { useCustomParams } from '.' +import type { IJsonItem } from '../types' +import {useI18n} from "vue-i18n"; + +export function useEmr(model: { [field: string]: any }): IJsonItem[] { + const { t } = useI18n() + + return [ + { + type: 'select', + field: 'programType', + span: 24, + name: t('project.node.program_type'), + options: PROGRAM_TYPES, + validate: { + required: true + } + }, + ...useCustomParams({ model, field: 'localParams', isSimple: false }) + ] +} + +export const PROGRAM_TYPES = [ + { + label: 'RUN_JOB_FLOW', + value: 'RUN_JOB_FLOW' + }, + { + label: 'ADD_JOB_FLOW_STEPS', + value: 'ADD_JOB_FLOW_STEPS' + } +] diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-environment-name.ts b/seatunnel-ui/src/views/task/components/node/fields/use-environment-name.ts new file mode 100644 index 000000000..ccd07fca1 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-environment-name.ts @@ -0,0 +1,100 @@ +/* + * 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 { ref, onMounted, watch } from 'vue' +import { useI18n } from 'vue-i18n' +import type { IEnvironmentNameOption, IJsonItem } from '../types' +import { getResourceList } from '@/service/modules/resources' +import { useRoute } from 'vue-router' + +export function useEnvironmentName( + model: { [field: string]: any }, + isCreate: boolean +): IJsonItem { + const { t } = useI18n() + + let environmentList = [] as IEnvironmentNameOption[] + const options = ref([] as IEnvironmentNameOption[]) + const loading = ref(false) + let mounted = false + + const route = useRoute() + const projectCode = Number(route.params.projectCode) + + const getEnvironmentList = async () => { + if (loading.value) return + loading.value = true + const res = await getResourceList({ + projectCode, + accessType: 'ENVIRONMENT' + }) + environmentList = res.map( + (item: { code: string; name: string; workerGroups: string[] }) => ({ + label: item.name, + value: item.code, + workerGroups: item.workerGroups + }) + ) + options.value = environmentList.filter((option: IEnvironmentNameOption) => + filterByWorkerGroup(option) + ) + loading.value = false + + if (options.value.length === 0) { + model.environmentCode = null + } else { + isCreate && + !model.environmentCode && + (model.environmentCode = options.value[0].value) + } + } + + const filterByWorkerGroup = (option: IEnvironmentNameOption) => { + if (!model.workerGroup) return false + if (!option?.workerGroups?.length) return false + return option.workerGroups.indexOf(model.workerGroup) !== -1 + } + + watch( + () => model.workerGroup, + () => { + if (!model.workerGroup || !mounted) return + options.value = environmentList.filter((option: IEnvironmentNameOption) => + filterByWorkerGroup(option) + ) + model.environmentCode = + options.value.length === 0 ? null : options.value[0].value + } + ) + + onMounted(async () => { + await getEnvironmentList() + mounted = true + }) + + return { + type: 'select', + field: 'environmentCode', + span: 12, + name: t('project.node.environment_name'), + props: { + loading: loading.value, + clearable: true + }, + options: options + } +} diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-executor-cores.ts b/seatunnel-ui/src/views/task/components/node/fields/use-executor-cores.ts new file mode 100644 index 000000000..87540197c --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-executor-cores.ts @@ -0,0 +1,42 @@ +/* + * 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 { useI18n } from 'vue-i18n' +import type { IJsonItem } from '../types' + +export function useExecutorCores(): IJsonItem { + const { t } = useI18n() + + return { + type: 'input-number', + field: 'executorCores', + name: t('project.node.executor_cores'), + span: 12, + props: { + placeholder: t('project.node.executor_cores_tips'), + min: 1 + }, + validate: { + trigger: ['input', 'blur'], + required: true, + validator(validate: any, value: string) { + if (!value) { + return new Error(t('project.node.executor_cores_tips')) + } + } + } + } +} diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-executor-memory.ts b/seatunnel-ui/src/views/task/components/node/fields/use-executor-memory.ts new file mode 100644 index 000000000..b135d2275 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-executor-memory.ts @@ -0,0 +1,47 @@ +/* + * 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 { useI18n } from 'vue-i18n' +import type { IJsonItem } from '../types' + +export function useExecutorMemory(): IJsonItem { + const { t } = useI18n() + + return { + type: 'input', + field: 'executorMemory', + name: t('project.node.executor_memory'), + span: 12, + props: { + placeholder: t('project.node.executor_memory_tips') + }, + validate: { + trigger: ['input', 'blur'], + required: true, + validator(validate: any, value: string) { + if (!value) { + return new Error(t('project.node.executor_memory_tips')) + } + if (!Number.isInteger(parseInt(value))) { + return new Error( + t('project.node.executor_memory_tips') + + t('project.node.positive_integer_tips') + ) + } + } + } + } +} diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-executor-number.ts b/seatunnel-ui/src/views/task/components/node/fields/use-executor-number.ts new file mode 100644 index 000000000..34ba711d6 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-executor-number.ts @@ -0,0 +1,42 @@ +/* + * 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 { useI18n } from 'vue-i18n' +import type { IJsonItem } from '../types' + +export function useExecutorNumber(): IJsonItem { + const { t } = useI18n() + + return { + type: 'input-number', + field: 'numExecutors', + name: t('project.node.executor_number'), + span: 12, + props: { + placeholder: t('project.node.executor_number_tips'), + min: 1 + }, + validate: { + trigger: ['input', 'blur'], + required: true, + validator(validate: any, value: string) { + if (!value) { + return new Error(t('project.node.executor_number_tips')) + } + } + } + } +} diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-failed.ts b/seatunnel-ui/src/views/task/components/node/fields/use-failed.ts new file mode 100644 index 000000000..33b0adc91 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-failed.ts @@ -0,0 +1,43 @@ +/* + * 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 { useI18n } from 'vue-i18n' +import type { IJsonItem } from '../types' + +export function useFailed(): IJsonItem[] { + const { t } = useI18n() + return [ + { + type: 'input-number', + field: 'failRetryTimes', + name: t('project.node.number_of_failed_retries'), + span: 12, + slots: { + suffix: () => t('project.node.times') + } + }, + { + type: 'input-number', + field: 'failRetryInterval', + name: t('project.node.failed_retry_interval'), + span: 12, + slots: { + suffix: () => t('project.node.minute') + } + } + ] +} diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-flink.ts b/seatunnel-ui/src/views/task/components/node/fields/use-flink.ts new file mode 100644 index 000000000..9b2170ec9 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-flink.ts @@ -0,0 +1,229 @@ +/* + * 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 { computed, ref } from 'vue' +import { useI18n } from 'vue-i18n' +import { useCustomParams, useDeployMode, useMainJar, useResources } from '.' +import type { IJsonItem } from '../types' + +export function useFlink(model: { [field: string]: any }): IJsonItem[] { + const { t } = useI18n() + const mainClassSpan = computed(() => + model.programType === 'PYTHON' ? 0 : 24 + ) + + const taskManagerNumberSpan = computed(() => + model.flinkVersion === '<1.10' && model.deployMode === 'cluster' ? 12 : 0 + ) + + const deployModeSpan = computed(() => + model.deployMode === 'cluster' ? 12 : 0 + ) + + const appNameSpan = computed(() => (model.deployMode === 'cluster' ? 24 : 0)) + + return [ + { + type: 'select', + field: 'programType', + span: 12, + name: t('project.node.program_type'), + options: PROGRAM_TYPES, + props: { + 'on-update:value': () => { + model.mainJar = null + model.mainClass = '' + } + } + }, + { + type: 'input', + field: 'mainClass', + span: mainClassSpan, + name: t('project.node.main_class'), + props: { + placeholder: t('project.node.main_class_tips') + }, + validate: { + trigger: ['input', 'blur'], + required: model.programType !== 'PYTHON', + validator(validate: any, value: string) { + if (model.programType !== 'PYTHON' && !value) { + return new Error(t('project.node.main_class_tips')) + } + } + } + }, + useMainJar(model), + useDeployMode(24, ref(false)), + { + type: 'select', + field: 'flinkVersion', + name: t('project.node.flink_version'), + options: FLINK_VERSIONS, + value: model.flinkVersion, + span: deployModeSpan + }, + { + type: 'input', + field: 'appName', + name: t('project.node.app_name'), + props: { + placeholder: t('project.node.app_name_tips') + }, + span: appNameSpan + }, + { + type: 'input', + field: 'jobManagerMemory', + name: t('project.node.job_manager_memory'), + span: deployModeSpan, + props: { + placeholder: t('project.node.job_manager_memory_tips'), + min: 1 + }, + validate: { + trigger: ['input', 'blur'], + validator(validate: any, value: string) { + if (!value) { + return + } + if (!Number.isInteger(parseInt(value))) { + return new Error( + t('project.node.job_manager_memory_tips') + + t('project.node.positive_integer_tips') + ) + } + } + } + }, + { + type: 'input', + field: 'taskManagerMemory', + name: t('project.node.task_manager_memory'), + span: deployModeSpan, + props: { + placeholder: t('project.node.task_manager_memory_tips') + }, + validate: { + trigger: ['input', 'blur'], + validator(validate: any, value: string) { + if (!value) { + return + } + if (!Number.isInteger(parseInt(value))) { + return new Error( + t('project.node.task_manager_memory') + + t('project.node.positive_integer_tips') + ) + } + } + }, + value: model.taskManagerMemory + }, + { + type: 'input-number', + field: 'slot', + name: t('project.node.slot_number'), + span: deployModeSpan, + props: { + placeholder: t('project.node.slot_number_tips'), + min: 1 + }, + value: model.slot + }, + { + type: 'input-number', + field: 'taskManager', + name: t('project.node.task_manager_number'), + span: taskManagerNumberSpan, + props: { + placeholder: t('project.node.task_manager_number_tips') + }, + value: model.taskManager + }, + { + type: 'input-number', + field: 'parallelism', + name: t('project.node.parallelism'), + span: 12, + props: { + placeholder: t('project.node.parallelism_tips'), + min: 1 + }, + validate: { + trigger: ['input', 'blur'], + required: true, + validator(validate: any, value: string) { + if (!value) { + return new Error(t('project.node.parallelism_tips')) + } + } + }, + value: model.parallelism + }, + { + type: 'input', + field: 'mainArgs', + name: t('project.node.main_arguments'), + props: { + type: 'textarea', + placeholder: t('project.node.main_arguments_tips') + } + }, + { + type: 'input', + field: 'others', + name: t('project.node.option_parameters'), + props: { + type: 'textarea', + placeholder: t('project.node.option_parameters_tips') + } + }, + useResources(), + ...useCustomParams({ + model, + field: 'localParams', + isSimple: true + }) + ] +} + +const PROGRAM_TYPES = [ + { + label: 'JAVA', + value: 'JAVA' + }, + { + label: 'SCALA', + value: 'SCALA' + }, + { + label: 'PYTHON', + value: 'PYTHON' + } +] + +const FLINK_VERSIONS = [ + { + label: '<1.10', + value: '<1.10' + }, + { + label: '>=1.10', + value: '>=1.10' + } +] diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-hive-cli.ts b/seatunnel-ui/src/views/task/components/node/fields/use-hive-cli.ts new file mode 100644 index 000000000..ba8f5ce4e --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-hive-cli.ts @@ -0,0 +1,54 @@ +/* + * 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 { useI18n } from 'vue-i18n' +import { useCustomParams, useResources } from '.' +import type { IJsonItem } from '../types' + +export function useHiveCli(model: { [field: string]: any }): IJsonItem[] { + const { t } = useI18n() + + return [ + { + type: 'select', + field: 'hiveCliTaskExecutionType', + span: 12, + name: t('project.node.hive_cli_task_execution_type'), + options: HIVE_CLI_TASK_EXECUTION_TYPES + }, + { + type: 'input', + field: 'hiveCliOptions', + name: t('project.node.hive_cli_options'), + props: { + placeholder: t('project.node.hive_cli_options_tips') + } + }, + useResources(), + ...useCustomParams({ model, field: 'localParams', isSimple: false }) + ] +} + +export const HIVE_CLI_TASK_EXECUTION_TYPES = [ + { + label: 'FROM_SCRIPT', + value: 'SCRIPT' + }, + { + label: 'FROM_FILE', + value: 'FILE' + } +] diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-http.ts b/seatunnel-ui/src/views/task/components/node/fields/use-http.ts new file mode 100644 index 000000000..0cd5e02ba --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-http.ts @@ -0,0 +1,238 @@ +/* + * 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 { useI18n } from 'vue-i18n' +import { useCustomParams } from '.' +import type { IJsonItem } from '../types' + +export function useHttp(model: { [field: string]: any }): IJsonItem[] { + const { t } = useI18n() + + const HTTP_CHECK_CONDITIONS = [ + { + label: t('project.node.status_code_default'), + value: 'STATUS_CODE_DEFAULT' + }, + { + label: t('project.node.status_code_custom'), + value: 'STATUS_CODE_CUSTOM' + }, + { + label: t('project.node.body_contains'), + value: 'BODY_CONTAINS' + }, + { + label: t('project.node.body_not_contains'), + value: 'BODY_NOT_CONTAINS' + }, + { + label: t('project.node.body_check_custom'), + value: 'BODY_CHECK_CUSTOM' + } + ] + + return [ + { + type: 'input', + field: 'url', + name: t('project.node.http_url'), + props: { + placeholder: t('project.node.http_url_tips') + }, + validate: { + trigger: ['input', 'blur'], + required: true, + validator(validate: any, value: string) { + if (!value) { + return new Error(t('project.node.http_url_tips')) + } + if (value.search(new RegExp(/http[s]{0,1}:\/\/\S*/, 'i'))) { + return new Error(t('project.node.http_url_validator')) + } + } + } + }, + { + type: 'select', + field: 'httpMethod', + span: 12, + name: t('project.node.http_method'), + options: HTTP_METHODS + }, + { + type: 'custom-parameters', + field: 'httpParams', + name: t('project.node.http_parameters'), + children: [ + { + type: 'input', + field: 'prop', + span: 6, + props: { + placeholder: t('project.node.prop_tips'), + maxLength: 256 + }, + validate: { + trigger: ['input', 'blur'], + required: true, + validator(validate: any, value: string) { + if (!value) { + return new Error(t('project.node.prop_tips')) + } + + const sameItems = model.httpParams.filter( + (item: { prop: string }) => item.prop === value + ) + + if (sameItems.length > 1) { + return new Error(t('project.node.prop_repeat')) + } + } + } + }, + { + type: 'select', + field: 'httpParametersType', + span: 6, + options: POSITIONS, + value: 'PARAMETER' + }, + { + type: 'input', + field: 'value', + span: 6, + props: { + placeholder: t('project.node.value_tips'), + maxLength: 256 + }, + validate: { + trigger: ['input', 'blur'], + required: true, + validator(validate: any, value: string) { + if (!value) { + return new Error(t('project.node.value_required_tips')) + } + } + } + } + ] + }, + { + type: 'select', + field: 'httpCheckCondition', + name: t('project.node.http_check_condition'), + options: HTTP_CHECK_CONDITIONS + }, + { + type: 'input', + field: 'condition', + name: t('project.node.http_condition'), + props: { + type: 'textarea', + placeholder: t('project.node.http_condition_tips') + } + }, + { + type: 'input-number', + field: 'connectTimeout', + name: t('project.node.connect_timeout'), + span: 12, + props: { + max: Math.pow(7, 10) - 1 + }, + slots: { + suffix: () => t('project.node.ms') + }, + validate: { + trigger: ['input', 'blur'], + validator(validate: any, value: string) { + if (!Number.isInteger(parseInt(value))) { + return new Error( + t('project.node.connect_timeout') + + t('project.node.positive_integer_tips') + ) + } + } + } + }, + { + type: 'input-number', + field: 'socketTimeout', + name: t('project.node.socket_timeout'), + span: 12, + props: { + max: Math.pow(7, 10) - 1 + }, + slots: { + suffix: () => t('project.node.ms') + }, + validate: { + trigger: ['input', 'blur'], + validator(validate: any, value: string) { + if (!Number.isInteger(parseInt(value))) { + return new Error( + t('project.node.socket_timeout') + + t('project.node.positive_integer_tips') + ) + } + } + } + }, + ...useCustomParams({ + model, + field: 'localParams', + isSimple: true + }) + ] +} + +const HTTP_METHODS = [ + { + value: 'GET', + label: 'GET' + }, + { + value: 'POST', + label: 'POST' + }, + { + value: 'HEAD', + label: 'HEAD' + }, + { + value: 'PUT', + label: 'PUT' + }, + { + value: 'DELETE', + label: 'DELETE' + } +] + +const POSITIONS = [ + { + value: 'PARAMETER', + label: 'Parameter' + }, + { + value: 'BODY', + label: 'Body' + }, + { + value: 'HEADERS', + label: 'Headers' + } +] diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-informatica-item.ts b/seatunnel-ui/src/views/task/components/node/fields/use-informatica-item.ts new file mode 100644 index 000000000..e43e6bf62 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-informatica-item.ts @@ -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 { getFolderList, getMappingList } from '@/service/modules/informatica' +import { onMounted, ref } from 'vue' +import { useI18n } from 'vue-i18n' +import type { IJsonItem } from '../types' + +//use-informatica + +export function useInformatica(): IJsonItem[] { + const { t } = useI18n() + const folderLoading = ref(false) + const folderOptions = ref([] as any) + const mappingLoading = ref(false) + const mappingOptions = ref([] as any) + + const handleFolderList = async () => { + if (folderLoading.value) return + folderLoading.value = true + const res = await getFolderList() + folderOptions.value = res.map((item: any) => ({ + label: item, + value: item + })) + folderLoading.value = false + } + + const handleMappingList = async (value: string) => { + if (mappingLoading.value) return + mappingLoading.value = true + const res = await getMappingList(value) + mappingOptions.value = res.map((item: any) => ({ + label: item, + value: item + })) + mappingLoading.value = false + } + + onMounted(() => { + handleFolderList() + }) + + return [ + { + type: 'select', + field: 'informaticaFolderName', + name: t('project.node.folder_name'), + props: { + loading: folderLoading, + 'on-update:value': (value: any) => { + handleMappingList(value) + } + }, + options: folderOptions, + validate: { + trigger: ['input', 'blur'], + required: true, + message: t('project.node.folder_name_tips') + } + }, + { + type: 'select', + field: 'informaticaWorkflowName', + name: t('project.node.mapping_name'), + props: { + loading: mappingLoading + }, + options: mappingOptions, + validate: { + trigger: ['input', 'blur'], + required: true, + message: t('project.node.mapping_name_tips') + } + } + ] +} diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-jar.ts b/seatunnel-ui/src/views/task/components/node/fields/use-jar.ts new file mode 100644 index 000000000..6325abed9 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-jar.ts @@ -0,0 +1,159 @@ +/* + * 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 utils from '@/utils' +import { computed, onMounted, ref } from 'vue' +import { useI18n } from 'vue-i18n' +import { useCustomParams, useResources } from '.' +import { useTaskNodeStore } from '@/store/project/task-node' +import { queryResourceByProgramType } from '@/service/modules/resources' +import type { IJsonItem, IMainJar } from '../types' + +export function useJar(model: { [field: string]: any }): IJsonItem[] { + const { t } = useI18n() + const mainClassSpan = computed(() => (model.programType === 'CLASS' ? 24 : 0)) + const mainJarSpan = computed(() => (model.programType === 'CLASS' ? 0 : 24)) + + const dependentPathSpan = computed(() => + model.programType === 'CLASS' ? 24 : 0 + ) + const mainJarOptions = ref([] as IMainJar[]) + const taskStore = useTaskNodeStore() + + const getMainJars = async () => { + const programType = 'JAVA' + const res = await queryResourceByProgramType({ + type: 'FILE', + programType + }) + utils.removeUselessChildren(res) + mainJarOptions.value = res || [] + taskStore.updateMainJar(programType, res) + } + + onMounted(() => { + getMainJars() + }) + + return [ + { + type: 'radio', + field: 'programType', + span: 24, + name: t('project.node.execute_type'), + options: programTypeOptions + }, + { + type: 'input', + field: 'options', + span: 24, + name: 'Java Option', + props: { + placeholder: t('project.node.java_option_tips') + } + }, + { + type: 'tree-select', + field: 'cpResIds', + span: dependentPathSpan, + name: t('project.node.dependent_path'), + props: { + multiple: true, + checkable: true, + cascade: true, + showPath: true, + checkStrategy: 'child', + placeholder: t('project.node.dependent_path_tips'), + keyField: 'id', + labelField: 'fullName' + }, + validate: { + trigger: ['input', 'blur'], + required: true, + validator(validate: any, value: string) { + if (model.programType === 'CLASS' && !value) { + return new Error(t('project.node.dependent_path_tips')) + } + } + }, + options: mainJarOptions + }, + { + type: 'input', + field: 'mainClass', + span: mainClassSpan, + name: t('project.node.main_class'), + props: { + placeholder: t('project.node.main_class_tips') + }, + validate: { + trigger: ['input', 'blur'], + required: true, + validator(validate: any, value: string) { + if (model.programType === 'CLASS' && !value) { + return new Error(t('project.node.main_class_tips')) + } + } + } + }, + { + type: 'tree-select', + field: 'mainJar', + span: mainJarSpan, + name: t('project.node.main_package'), + props: { + cascade: true, + showPath: true, + checkStrategy: 'child', + placeholder: t('project.node.main_package_tips'), + keyField: 'id', + labelField: 'fullName' + }, + validate: { + trigger: ['input', 'blur'], + required: true, + validator(validate: any, value: string) { + if (!value) { + return new Error(t('project.node.main_package_tips')) + } + } + }, + options: mainJarOptions + }, + { + type: 'input', + field: 'mainArgs', + name: t('project.node.main_arguments'), + props: { + type: 'textarea', + placeholder: t('project.node.main_arguments_tips') + } + }, + useResources(), + ...useCustomParams({ model, field: 'localParams', isSimple: true }) + ] +} + +const programTypeOptions = [ + { + label: '执行类', + value: 'CLASS' + }, + { + label: '执行Jar', + value: 'JAR' + } +] diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-jupyter.ts b/seatunnel-ui/src/views/task/components/node/fields/use-jupyter.ts new file mode 100644 index 000000000..582b308d6 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-jupyter.ts @@ -0,0 +1,127 @@ +/* + * 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 { useI18n } from 'vue-i18n' +import { useCustomParams, useResources } from '.' +import type { IJsonItem } from '../types' + +export function useJupyter(model: { [field: string]: any }): IJsonItem[] { + const { t } = useI18n() + + return [ + { + type: 'input', + field: 'condaEnvName', + name: t('project.node.jupyter_conda_env_name'), + props: { + placeholder: t('project.node.jupyter_conda_env_name_tips') + }, + validate: { + trigger: ['input', 'blur'], + required: true, + validator(validate: any, value: string) { + if (!value) { + return new Error(t('project.node.jupyter_conda_env_name_tips')) + } + } + } + }, + { + type: 'input', + field: 'inputNotePath', + name: t('project.node.jupyter_input_note_path'), + props: { + placeholder: t('project.node.jupyter_input_note_path_tips') + }, + validate: { + trigger: ['input', 'blur'], + required: true, + validator(validate: any, value: string) { + if (!value) { + return new Error(t('project.node.jupyter_input_note_path_tips')) + } + } + } + }, + { + type: 'input', + field: 'outputNotePath', + name: t('project.node.jupyter_output_note_path'), + props: { + placeholder: t('project.node.jupyter_output_note_path_tips') + }, + validate: { + trigger: ['input', 'blur'], + required: true, + validator(validate: any, value: string) { + if (!value) { + return new Error(t('project.node.jupyter_output_note_path_tips')) + } + } + } + }, + { + type: 'input', + field: 'parameters', + name: t('project.node.jupyter_parameters'), + props: { + placeholder: t('project.node.jupyter_parameters_tips') + } + }, + { + type: 'input', + field: 'kernel', + name: t('project.node.jupyter_kernel'), + props: { + placeholder: t('project.node.jupyter_kernel_tips') + } + }, + { + type: 'input', + field: 'engine', + name: t('project.node.jupyter_engine'), + props: { + placeholder: t('project.node.jupyter_engine_tips') + } + }, + { + type: 'input', + field: 'executionTimeout', + name: t('project.node.jupyter_execution_timeout'), + props: { + placeholder: t('project.node.jupyter_execution_timeout_tips') + } + }, + { + type: 'input', + field: 'startTimeout', + name: t('project.node.jupyter_start_timeout'), + props: { + placeholder: t('project.node.jupyter_start_timeout_tips') + } + }, + { + type: 'input', + field: 'others', + name: t('project.node.jupyter_others'), + props: { + placeholder: t('project.node.jupyter_others_tips') + } + }, + useResources(), + ...useCustomParams({ model, field: 'localParams', isSimple: false }) + ] +} diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-main-jar.ts b/seatunnel-ui/src/views/task/components/node/fields/use-main-jar.ts new file mode 100644 index 000000000..9f8dd6543 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-main-jar.ts @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { computed, ref, onMounted, watch } from 'vue' +import { useI18n } from 'vue-i18n' +import { queryResourceByProgramType } from '@/service/modules/resources' +import { useTaskNodeStore } from '@/store/project/task-node' +import utils from '@/utils' +import type { IJsonItem, ProgramType, IMainJar } from '../types' + +export function useMainJar(model: { [field: string]: any }): IJsonItem { + const { t } = useI18n() + const mainJarOptions = ref([] as IMainJar[]) + const taskStore = useTaskNodeStore() + + const mainJarSpan = computed(() => + model.programType === 'SQL' ? 0 : 24 + ) + const getMainJars = async (programType: ProgramType) => { + const res = await queryResourceByProgramType({ + type: 'FILE', + programType + }) + utils.removeUselessChildren(res) + mainJarOptions.value = res || [] + taskStore.updateMainJar(programType, res) + } + + onMounted(() => { + getMainJars(model.programType) + }) + + watch( + () => model.programType, + (value) => { + getMainJars(value) + } + ) + + return { + type: 'tree-select', + field: 'mainJar', + name: t('project.node.main_package'), + span: mainJarSpan, + props: { + cascade: true, + showPath: true, + checkStrategy: 'child', + placeholder: t('project.node.main_package_tips'), + keyField: 'id', + labelField: 'fullName' + }, + validate: { + trigger: ['input', 'blur'], + required: model.programType !== 'SQL', + validator(validate: any, value: string) { + if (!value) { + return new Error(t('project.node.main_package_tips')) + } + } + }, + options: mainJarOptions + } +} diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-mlflow-models.ts b/seatunnel-ui/src/views/task/components/node/fields/use-mlflow-models.ts new file mode 100644 index 000000000..eef3837fd --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-mlflow-models.ts @@ -0,0 +1,80 @@ +/* + * 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 { useI18n } from 'vue-i18n' +import type { IJsonItem } from '../types' +import { watch, ref } from 'vue' + +export function useMlflowModels(model: { [field: string]: any }): IJsonItem[] { + const { t } = useI18n() + const deployTypeSpan = ref(0) + const deployModelKeySpan = ref(0) + const deployPortSpan = ref(0) + + const setFlag = () => { + model.isModels = model.mlflowTaskType === 'MLflow Models' ? true : false + } + + const resetSpan = () => { + deployTypeSpan.value = model.isModels ? 12 : 0 + deployModelKeySpan.value = model.isModels ? 24 : 0 + deployPortSpan.value = model.isModels ? 12 : 0 + } + + watch( + () => [model.mlflowTaskType], + () => { + setFlag() + resetSpan() + } + ) + + setFlag() + resetSpan() + + return [ + { + type: 'select', + field: 'deployType', + name: t('project.node.mlflow_deployType'), + span: deployTypeSpan, + options: DEPLOY_TYPE + }, + { + type: 'input', + field: 'deployModelKey', + name: t('project.node.mlflow_deployModelKey'), + span: deployModelKeySpan + }, + { + type: 'input', + field: 'deployPort', + name: t('project.node.mlflow_deployPort'), + span: deployPortSpan + } + ] +} + +const DEPLOY_TYPE = [ + { + label: 'MLFLOW', + value: 'MLFLOW' + }, + { + label: 'DOCKER', + value: 'DOCKER' + } +] diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-mlflow-projects.ts b/seatunnel-ui/src/views/task/components/node/fields/use-mlflow-projects.ts new file mode 100644 index 000000000..c312ee435 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-mlflow-projects.ts @@ -0,0 +1,322 @@ +/* + * 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 { useI18n } from 'vue-i18n' +import type { IJsonItem } from '../types' +import { watch, ref } from 'vue' + +export function useMlflowProjects(model: { + [field: string]: any +}): IJsonItem[] { + const { t } = useI18n() + + const experimentNameSpan = ref(0) + const registerModelSpan = ref(0) + const modelNameSpan = ref(0) + const mlflowJobTypeSpan = ref(0) + const dataPathSpan = ref(0) + const paramsSpan = ref(0) + + const setFlag = () => { + model.isProjects = model.mlflowTaskType === 'MLflow Projects' ? true : false + } + + const resetSpan = () => { + experimentNameSpan.value = model.isProjects ? 12 : 0 + mlflowJobTypeSpan.value = model.isProjects ? 12 : 0 + paramsSpan.value = model.isProjects ? 24 : 0 + registerModelSpan.value = + model.isProjects && model.mlflowJobType != 'CustomProject' ? 6 : 0 + dataPathSpan.value = + model.isProjects && model.mlflowJobType != 'CustomProject' ? 24 : 0 + } + + watch( + () => [model.mlflowTaskType, model.mlflowJobType], + () => { + setFlag() + resetSpan() + } + ) + + watch( + () => [model.registerModel], + () => { + modelNameSpan.value = model.isProjects && model.registerModel ? 6 : 0 + } + ) + + setFlag() + resetSpan() + + return [ + { + type: 'select', + field: 'mlflowJobType', + name: t('project.node.mlflow_jobType'), + span: mlflowJobTypeSpan, + options: MLFLOW_JOB_TYPE + }, + { + type: 'input', + field: 'experimentName', + name: t('project.node.mlflow_experimentName'), + span: experimentNameSpan, + props: { + placeholder: t('project.node.mlflow_experimentName_tips') + }, + validate: { + trigger: ['input', 'blur'], + required: false + } + }, + { + type: 'switch', + field: 'registerModel', + name: t('project.node.mlflow_registerModel'), + span: registerModelSpan + }, + { + type: 'input', + field: 'modelName', + name: t('project.node.mlflow_modelName'), + span: modelNameSpan, + props: { + placeholder: t('project.node.mlflow_modelName_tips') + }, + validate: { + trigger: ['input', 'blur'], + required: false + } + }, + { + type: 'input', + field: 'dataPath', + name: t('project.node.mlflow_dataPath'), + span: dataPathSpan, + props: { + placeholder: t('project.node.mlflow_dataPath_tips') + } + }, + { + type: 'input', + field: 'params', + name: t('project.node.mlflow_params'), + span: paramsSpan, + props: { + placeholder: t('project.node.mlflow_params_tips') + }, + validate: { + trigger: ['input', 'blur'], + required: false + } + }, + ...useBasicAlgorithm(model), + ...useAutoML(model), + ...useCustomProject(model) + ] +} + +export function useBasicAlgorithm(model: { + [field: string]: any +}): IJsonItem[] { + const { t } = useI18n() + + const algorithmSpan = ref(0) + const searchParamsSpan = ref(0) + + const setFlag = () => { + model.isBasicAlgorithm = + model.mlflowJobType === 'BasicAlgorithm' && + model.mlflowTaskType === 'MLflow Projects' + ? true + : false + } + + const resetSpan = () => { + algorithmSpan.value = model.isBasicAlgorithm ? 24 : 0 + searchParamsSpan.value = model.isBasicAlgorithm ? 24 : 0 + } + + watch( + () => [model.mlflowTaskType, model.mlflowJobType], + () => { + setFlag() + resetSpan() + } + ) + setFlag() + resetSpan() + + return [ + { + type: 'select', + field: 'algorithm', + name: t('project.node.mlflow_algorithm'), + span: algorithmSpan, + options: ALGORITHM + }, + { + type: 'input', + field: 'searchParams', + name: t('project.node.mlflow_searchParams'), + props: { + placeholder: t('project.node.mlflow_searchParams_tips') + }, + span: searchParamsSpan, + validate: { + trigger: ['input', 'blur'], + required: false + } + } + ] +} + +export function useAutoML(model: { [field: string]: any }): IJsonItem[] { + const { t } = useI18n() + + const automlToolSpan = ref(0) + + const setFlag = () => { + model.isAutoML = + model.mlflowJobType === 'AutoML' && + model.mlflowTaskType === 'MLflow Projects' + ? true + : false + } + + const resetSpan = () => { + automlToolSpan.value = model.isAutoML ? 12 : 0 + } + + watch( + () => [model.mlflowTaskType, model.mlflowJobType], + () => { + setFlag() + resetSpan() + } + ) + + setFlag() + resetSpan() + + return [ + { + type: 'select', + field: 'automlTool', + name: t('project.node.mlflow_automlTool'), + span: automlToolSpan, + options: AutoMLTOOL + } + ] +} + +export function useCustomProject(model: { [field: string]: any }): IJsonItem[] { + const { t } = useI18n() + + const mlflowProjectRepositorySpan = ref(0) + const mlflowProjectVersionSpan = ref(0) + const customParamsSpan = ref(0) + + const setFlag = () => { + model.isCustomProject = + model.mlflowJobType === 'CustomProject' && + model.mlflowTaskType === 'MLflow Projects' + ? true + : false + } + + const resetSpan = () => { + mlflowProjectRepositorySpan.value = model.isCustomProject ? 24 : 0 + mlflowProjectVersionSpan.value = model.isCustomProject ? 12 : 0 + customParamsSpan.value = model.isCustomProject ? 24 : 0 + } + + watch( + () => [model.mlflowTaskType, model.mlflowJobType], + () => { + setFlag() + resetSpan() + } + ) + + setFlag() + resetSpan() + + return [ + { + type: 'input', + field: 'mlflowProjectRepository', + name: t('project.node.mlflowProjectRepository'), + span: mlflowProjectRepositorySpan, + props: { + placeholder: t('project.node.mlflowProjectRepository_tips') + } + }, + { + type: 'input', + field: 'mlflowProjectVersion', + name: t('project.node.mlflowProjectVersion'), + span: mlflowProjectVersionSpan, + props: { + placeholder: t('project.node.mlflowProjectVersion_tips') + } + } + ] +} + +export const MLFLOW_JOB_TYPE = [ + { + label: 'Custom Project', + value: 'CustomProject' + }, + { + label: 'AutoML', + value: 'AutoML' + }, + { + label: 'BasicAlgorithm', + value: 'BasicAlgorithm' + } +] +export const ALGORITHM = [ + { + label: 'svm', + value: 'svm' + }, + { + label: 'lr', + value: 'lr' + }, + { + label: 'lightgbm', + value: 'lightgbm' + }, + { + label: 'xgboost', + value: 'xgboost' + } +] +export const AutoMLTOOL = [ + { + label: 'flaml', + value: 'flaml' + }, + { + label: 'autosklearn', + value: 'autosklearn' + } +] diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-mlflow.ts b/seatunnel-ui/src/views/task/components/node/fields/use-mlflow.ts new file mode 100644 index 000000000..efed24661 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-mlflow.ts @@ -0,0 +1,69 @@ +/* + * 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 { useI18n } from 'vue-i18n' +import type { IJsonItem } from '../types' +import { useMlflowProjects, useMlflowModels } from '.' +import { useCustomParams, useResources } from '.' + +export const MLFLOW_TASK_TYPE = [ + { + label: 'MLflow Models', + value: 'MLflow Models' + }, + { + label: 'MLflow Projects', + value: 'MLflow Projects' + } +] + +export function useMlflow(model: { [field: string]: any }): IJsonItem[] { + const { t } = useI18n() + + return [ + { + type: 'input', + field: 'mlflowTrackingUri', + name: t('project.node.mlflow_mlflowTrackingUri'), + span: 12, + props: { + placeholder: t('project.node.mlflow_mlflowTrackingUri_tips') + }, + validate: { + trigger: ['input', 'blur'], + required: false, + validator(validate: any, value: string) { + if (!value) { + return new Error( + t('project.node.mlflow_mlflowTrackingUri_error_tips') + ) + } + } + } + }, + { + type: 'select', + field: 'mlflowTaskType', + name: t('project.node.mlflow_taskType'), + span: 12, + options: MLFLOW_TASK_TYPE + }, + ...useMlflowProjects(model), + ...useMlflowModels(model), + useResources(), + ...useCustomParams({ model, field: 'localParams', isSimple: true }) + ] +} diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-mr.ts b/seatunnel-ui/src/views/task/components/node/fields/use-mr.ts new file mode 100644 index 000000000..76b89b342 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-mr.ts @@ -0,0 +1,107 @@ +/* + * 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 { computed } from 'vue' +import { useI18n } from 'vue-i18n' +import { useCustomParams, useMainJar, useResources } from '.' +import type { IJsonItem } from '../types' + +export function useMr(model: { [field: string]: any }): IJsonItem[] { + const { t } = useI18n() + + const mainClassSpan = computed(() => + (model.programType === 'PYTHON' || model.programType === 'SQL') ? 0 : 24 + ) + + return [ + { + type: 'select', + field: 'programType', + span: 12, + name: t('project.node.program_type'), + options: PROGRAM_TYPES, + props: { + 'on-update:value': () => { + model.mainJar = null + model.mainClass = '' + } + }, + value: model.programType + }, + { + type: 'input', + field: 'mainClass', + span: mainClassSpan, + name: t('project.node.main_class'), + props: { + placeholder: t('project.node.main_class_tips') + }, + validate: { + trigger: ['input', 'blur'], + required: model.programType !== 'PYTHON', + validator(validate: any, value: string) { + if (model.programType !== 'PYTHON' && !value) { + return new Error(t('project.node.main_class_tips')) + } + } + } + }, + useMainJar(model), + { + type: 'input', + field: 'appName', + name: t('project.node.app_name'), + props: { + placeholder: t('project.node.app_name_tips') + } + }, + { + type: 'input', + field: 'mainArgs', + name: t('project.node.main_arguments'), + props: { + type: 'textarea', + placeholder: t('project.node.main_arguments_tips') + } + }, + { + type: 'input', + field: 'others', + name: t('project.node.option_parameters'), + props: { + type: 'textarea', + placeholder: t('project.node.option_parameters_tips') + } + }, + useResources(), + ...useCustomParams({ model, field: 'localParams', isSimple: true }) + ] +} + +export const PROGRAM_TYPES = [ + { + label: 'JAVA', + value: 'JAVA' + }, + { + label: 'SCALA', + value: 'SCALA' + }, + { + label: 'PYTHON', + value: 'PYTHON' + } +] diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-name.ts b/seatunnel-ui/src/views/task/components/node/fields/use-name.ts new file mode 100644 index 000000000..8133e6a81 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-name.ts @@ -0,0 +1,38 @@ +/* + * 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 { useI18n } from 'vue-i18n' +import type { IJsonItem } from '../types' + +export function useName(from?: number): IJsonItem { + const { t } = useI18n() + return { + type: 'input', + field: 'name', + class: 'input-node-name', + name: from === 1 ? t('project.node.task_name') : t('project.node.name'), + props: { + placeholder: t('project.node.name_tips'), + maxLength: 100 + }, + validate: { + trigger: ['input', 'blur'], + required: true, + message: t('project.node.name_tips') + } + } +} diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-next-loop.ts b/seatunnel-ui/src/views/task/components/node/fields/use-next-loop.ts new file mode 100644 index 000000000..22bf91dff --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-next-loop.ts @@ -0,0 +1,140 @@ +/* + * 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 { computed, onMounted, ref, h, VNode, watch } from 'vue' +import { useI18n } from 'vue-i18n' +import type { IJsonItem } from '../types' +import { getAllNotLockCards } from '@/service/modules/card' +import { NIcon, NPopover } from 'naive-ui' +import { QuestionCircleOutlined } from '@vicons/antd' +import styles from '../index.module.scss' +import {LocationQueryValue} from "vue-router"; + +export function useNextLoop( + model: { [p: string]: any }, + projectCode?: number, + processDefinitionCode?: number, + updateValue?: (value: any, field: string) => void +): IJsonItem[] { + const { t } = useI18n() + const loading = ref(false) + const cardOptions = ref([]) + const customSpan = computed(() => (model.customConfig ? 24 : 0)) + + const getAllNotLockCardList = async () => { + if (loading.value) return + loading.value = true + const params = { + projectCode, + processDefinitionCode: Number.isNaN(processDefinitionCode)? 0 : processDefinitionCode, + } + const res = await getAllNotLockCards(params) + cardOptions.value = res.map( + (item: { cardCode: string; cardName: string; cardValue: string }) => ({ + label: `${item.cardName} ${item.cardValue}`, + value: item.cardCode + }) + ) + loading.value = false + } + + const renderStrategyLabelExtra = (): VNode => { + return h( + NPopover, + { trigger: 'hover' }, + { + trigger: () => + h(NIcon, { size: 20, class: styles['question-icon'] }, () => + h(QuestionCircleOutlined) + ), + default: () => [ + t('project.node.next_loop_date_tip'), + h('br'), + t('project.node.next_loop_timezone_tip') + ] + } + ) + } + + onMounted(() => { + getAllNotLockCardList() + }) + + const updateScriptAndLanguage = () => { + const handlers = model.customConfig + ? [{ key: 'script', name: t('project.node.script') }] + : [] + if (updateValue) { + updateValue( + { + ...model, + handlers, + language: model.programType === 'SHELL' ? 'shell' : 'python' + }, + 'batch' + ) + } + } + + watch(() => model.customConfig, updateScriptAndLanguage) + watch(() => model.programType, updateScriptAndLanguage) + + const programTypeOptions = [ + { + label: 'SHELL', + value: 'SHELL' + }, + { + label: 'PYTHON', + value: 'PYTHON', + extra: renderStrategyLabelExtra() + } + ] + return [ + { + type: 'select', + field: 'cardCode', + span: 12, + name: t('project.node.next_loop_card'), + props: { + loading: loading + }, + options: cardOptions, + validate: { + trigger: ['input', 'blur'], + required: true, + validator(unuse: any, value: number) { + if (!value) { + return Error(t('project.node.next_loop_card_tips')) + } + } + } + }, + { + type: 'switch', + field: 'customConfig', + name: t('project.node.next_loop_custom_rule') + }, + { + type: 'radio', + field: 'programType', + span: customSpan, + name: t('project.node.script_type'), + options: programTypeOptions + } + ] +} diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-openmldb.ts b/seatunnel-ui/src/views/task/components/node/fields/use-openmldb.ts new file mode 100644 index 000000000..0a34b554c --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-openmldb.ts @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { useI18n } from 'vue-i18n' +import { useCustomParams, useResources } from '.' +import type { IJsonItem } from '../types' + +export function useOpenmldb(model: { [field: string]: any }): IJsonItem[] { + const { t } = useI18n() + const options = [ + { + label: t('project.node.openmldb_execute_mode_offline'), + value: 'offline' + }, + { + label: t('project.node.openmldb_execute_mode_online'), + value: 'online' + } + ] + return [ + { + type: 'input', + field: 'zk', + name: t('project.node.openmldb_zk_address'), + props: { + placeholder: t('project.node.openmldb_zk_address_tips') + }, + validate: { + trigger: ['input', 'blur'], + required: true, + validator(validate: any, value: string) { + if (!value) { + return new Error(t('project.node.openmldb_zk_address_tips')) + } + } + } + }, + { + type: 'input', + field: 'zkPath', + name: t('project.node.openmldb_zk_path'), + props: { + placeholder: t('project.node.openmldb_zk_path_tips') + }, + validate: { + trigger: ['input', 'blur'], + required: true, + validator(validate: any, value: string) { + if (!value) { + return new Error(t('project.node.openmldb_zk_path_tips')) + } + } + } + }, + { + type: 'radio', + field: 'executeMode', + name: t('project.node.openmldb_execute_mode'), + options: options + }, + useResources(), + ...useCustomParams({ model, field: 'localParams', isSimple: false }) + ] +} diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-pre-tasks.ts b/seatunnel-ui/src/views/task/components/node/fields/use-pre-tasks.ts new file mode 100644 index 000000000..1e83adcc0 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-pre-tasks.ts @@ -0,0 +1,48 @@ +/* + * 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 { useI18n } from 'vue-i18n' +import { useTaskNodeStore } from '@/store/project/task-node' +import type { IJsonItem } from '../types' +import type { SelectOption } from 'naive-ui' + +export function usePreTasks(model: { [field: string]: any }): IJsonItem { + const { t } = useI18n() + const taskStore = useTaskNodeStore() + + return { + type: 'select', + field: 'preTasks', + span: 24, + class: 'pre-tasks-model', + name: t('project.node.pre_tasks'), + props: { + multiple: true, + filterable: true, + onUpdateValue: (val: [], options: SelectOption[]) => { + options.some((option) => { + if (option.type === 'CONDITIONS') { + model.preTasks = [option.value] + return true + } + return false + }) + } + }, + options: taskStore.getPreTaskOptions + } +} diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-process-name.ts b/seatunnel-ui/src/views/task/components/node/fields/use-process-name.ts new file mode 100644 index 000000000..16d776f45 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-process-name.ts @@ -0,0 +1,92 @@ +/* + * 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 { ref, onMounted } from 'vue' +import { useI18n } from 'vue-i18n' +import { + querySimpleList, + queryProcessDefinitionByCode +} from '@/service/modules/process-definition' +import type { IJsonItem } from '../types' + +export function useProcessName({ + model, + projectCode, + isCreate, + from, + processName +}: { + model: { [field: string]: any } + projectCode: number + isCreate: boolean + from?: number + processName?: number +}): IJsonItem { + const { t } = useI18n() + + const options = ref([] as { label: string; value: string }[]) + const loading = ref(false) + + const getProcessList = async () => { + if (loading.value) return + loading.value = true + const res = await querySimpleList(projectCode) + options.value = res.map((option: { name: string; code: number }) => ({ + label: option.name, + value: option.code + })) + loading.value = false + } + const getProcessListByCode = async (processCode: number) => { + if (!processCode) return + const res = await queryProcessDefinitionByCode(processCode) + model.definition = res + } + + const onChange = (code: number) => { + getProcessListByCode(code) + } + + onMounted(() => { + if (from === 1 && processName) { + getProcessListByCode(processName) + } + getProcessList() + }) + + return { + type: 'select', + field: 'processName', + span: 24, + name: t('project.node.workflow_name'), + props: { + loading: loading, + disabled: !isCreate, + 'on-update:value': onChange + }, + validate: { + trigger: ['input', 'blur'], + required: true, + validator(validate: any, value: string) { + if (!value) { + return new Error(t('project.node.workflow_name_tips')) + } + } + }, + options: options + } +} diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-pytorch.ts b/seatunnel-ui/src/views/task/components/node/fields/use-pytorch.ts new file mode 100644 index 000000000..661e1a620 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-pytorch.ts @@ -0,0 +1,139 @@ +/* + * 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 { useI18n } from 'vue-i18n' +import type { IJsonItem } from '../types' +import { watch, ref } from 'vue' +import { useCustomParams, useResources } from '.' + +export function usePytorch(model: { [field: string]: any }): IJsonItem[] { + const { t } = useI18n() + + const isCreateEnvironmentSpan = ref(0) + const pythonPathSpan = ref(0) + const pythonEnvToolSpan = ref(0) + const pythonCommandSpan = ref(0) + const requirementsSpan = ref(0) + const condaPythonVersionSpan = ref(0) + + const setFlag = () => { + model.showCreateEnvironment = model.isCreateEnvironment && model.showOtherParams + model.showCreateConda = + model.showCreateEnvironment && model.pythonEnvTool === 'conda' + ? true + : false + model.showCreateEnv = + model.showCreateEnvironment && model.pythonEnvTool === 'virtualenv' + ? true + : false + } + + const resetSpan = () => { + isCreateEnvironmentSpan.value = model.showOtherParams ? 12 : 0 + pythonPathSpan.value = model.showOtherParams ? 24 : 0 + pythonEnvToolSpan.value = model.showCreateEnvironment ? 12 : 0 + pythonCommandSpan.value = + ~model.showCreateEnvironment & model.showOtherParams ? 12 : 0 + requirementsSpan.value = model.showCreateEnvironment ? 24 : 0 + condaPythonVersionSpan.value = model.showCreateConda ? 24 : 0 + } + + watch( + () => [model.isCreateEnvironment, model.pythonEnvTool, model.showOtherParams], + () => { + setFlag() + resetSpan() + } + ) + + return [ + { + type: 'input', + field: 'script', + name: t('project.node.pytorch_script'), + span: 24 + }, + { + type: 'input', + field: 'scriptParams', + name: t('project.node.pytorch_script_params'), + span: 24 + }, + { + type: 'switch', + field: 'showOtherParams', + name: t('project.node.pytorch_other_params'), + span: 24 + }, + { + type: 'input', + field: 'pythonPath', + name: t('project.node.pytorch_python_path'), + span: pythonPathSpan + }, + { + type: 'switch', + field: 'isCreateEnvironment', + name: t('project.node.pytorch_is_create_environment'), + span: isCreateEnvironmentSpan + }, + { + type: 'input', + field: 'pythonCommand', + name: t('project.node.pytorch_python_command'), + span: pythonCommandSpan, + props: { + placeholder: t('project.node.pytorch_python_command_tips') + } + }, + { + type: 'select', + field: 'pythonEnvTool', + name: t('project.node.pytorch_python_env_tool'), + span: pythonEnvToolSpan, + options: PYTHON_ENV_TOOL + }, + { + type: 'input', + field: 'requirements', + name: t('project.node.pytorch_requirements'), + span: requirementsSpan + }, + { + type: 'input', + field: 'condaPythonVersion', + name: t('project.node.pytorch_conda_python_version'), + span: condaPythonVersionSpan, + props: { + placeholder: t('project.node.pytorch_conda_python_version_tips') + } + }, + useResources(), + ...useCustomParams({ model, field: 'localParams', isSimple: false }) + ] +} + +export const PYTHON_ENV_TOOL = [ + { + label: 'conda', + value: 'conda' + }, + { + label: 'virtualenv', + value: 'virtualenv' + } +] diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-relation-custom-params.ts b/seatunnel-ui/src/views/task/components/node/fields/use-relation-custom-params.ts new file mode 100644 index 000000000..c194f83a6 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-relation-custom-params.ts @@ -0,0 +1,99 @@ +/* + * 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 { computed, watchEffect } from 'vue' +import { useI18n } from 'vue-i18n' +import styles from '../index.module.scss' +import type { IJsonItem } from '../types' + +export function useRelationCustomParams({ + model, + children, + childrenField, + name +}: { + model: { + [field: string]: any + } + children: IJsonItem + childrenField: string + name: string +}): IJsonItem[] { + const { t } = useI18n() + const firstLevelRelationSpan = computed(() => + model.dependTaskList.length ? 3 : 0 + ) + + watchEffect(() => { + model.dependTaskList.forEach( + (item: { [childrenField: string]: [] }, i: number) => { + if (item[childrenField].length === 0) { + model.dependTaskList.splice(i, 1) + } + } + ) + }) + return [ + { + type: 'custom', + name: t(`project.node.${name}`), + field: 'relationLabel', + span: 24, + class: styles['relaction-label'] + }, + { + type: 'switch', + field: 'relation', + props: { + round: false, + 'checked-value': 'AND', + 'unchecked-value': 'OR', + size: 'small' + }, + slots: { + checked: () => t('project.node.and'), + unchecked: () => t('project.node.or') + }, + span: firstLevelRelationSpan, + class: styles['relaction-switch'] + }, + { + type: 'custom-parameters', + field: 'dependTaskList', + span: 20, + children: [ + { + type: 'switch', + field: 'relation', + props: { + round: false, + 'checked-value': 'AND', + 'unchecked-value': 'OR', + size: 'small' + }, + slots: { + checked: () => t('project.node.and'), + unchecked: () => t('project.node.or') + }, + span: 4, + value: 'AND', + class: styles['relaction-switch'] + }, + children + ] + } + ] +} diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-remote-connection.ts b/seatunnel-ui/src/views/task/components/node/fields/use-remote-connection.ts new file mode 100644 index 000000000..a96523704 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-remote-connection.ts @@ -0,0 +1,100 @@ +/* + * 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 { ref, onMounted, computed } from 'vue' +import { useI18n } from 'vue-i18n' +import { getResourceList } from '@/service/modules/resources' +import { useRoute } from 'vue-router' +import type { IJsonItem } from '../types' + +export function useRemoteConnection( + model: { + [field: string]: any + }, + display = ref(true) +): IJsonItem[] { + const { t } = useI18n() + const route = useRoute() + const projectCode = Number(route.params.projectCode) + + const remoteSpan = computed(() => (display.value ? 24 : 0)) + const remoteConnectionSpan = computed(() => + model.open && display.value ? 10 : 0 + ) + const sourceSpan = computed(() => (model.open && display.value ? 14 : 0)) + + const remoteConnectionOptions = [ + { label: t('project.node.ssh'), value: 'SSH' } + ] + + const sourceOptions = ref([]) + + const getSourceOptions = async () => { + const data = await getResourceList({ + projectCode, + accessType: 'DATASOURCE', + resourceType: 'SSH' + }) + sourceOptions.value = data.map((item: any) => ({ + label: item.datasourceName, + value: item.id + })) + } + + onMounted(() => getSourceOptions()) + + return [ + { + type: 'switch', + field: 'open', + span: remoteSpan, + name: t('project.node.remote_connection') + }, + { + type: 'select', + field: 'datasourceType', + name: t('project.node.connection_type'), + span: remoteConnectionSpan, + options: remoteConnectionOptions, + validate: { + trigger: ['input', 'blur'], + required: true, + validator(validate: any, value: string) { + if (model['open'] && !value) { + return new Error(t('project.node.connection_type_tips')) + } + } + } + }, + { + type: 'select', + field: 'datasourceId', + name: t('project.node.source_name'), + span: sourceSpan, + options: sourceOptions, + validate: { + trigger: ['input', 'blur'], + required: true, + validator(validate: any, value: string) { + if (model['open'] && !value) { + return new Error(t('project.node.source_name_tips')) + } + } + } + } + ] +} diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-resources.ts b/seatunnel-ui/src/views/task/components/node/fields/use-resources.ts new file mode 100644 index 000000000..8c9481d68 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-resources.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 { ref, onMounted, Ref } from 'vue' +import { useI18n } from 'vue-i18n' +import utils from '@/utils' +import { getResourceList } from '@/service/modules/resources' +import { useRoute } from 'vue-router' +import type { IJsonItem, IResource } from '../types' + +export function useResources( + span: number | Ref = 24, + required = false, + limit = -1 +): IJsonItem { + const { t } = useI18n() + const route = useRoute() + const projectCode = Number(route.params.projectCode) + const resourcesOptions = ref([] as IResource[]) + const resourcesLoading = ref(false) + + const getResources = async () => { + if (resourcesLoading.value) return + resourcesLoading.value = true + // const res = await queryFileAndGitList({ types: 'FILE,GIT' }) + const file = await getResourceList({ + projectCode, + accessType: 'FILE', + resourceType: 'FILE' + }) + + const git = await getResourceList({ + projectCode, + accessType: 'GIT_RESOURCE', + resourceType: 'GIT' + }) + + const options = [ + { + id: -1, + name: t('project.node.local_file'), + dirctory: true, + disabled: true, + children: file + }, + { + id: -2, + name: t('project.node.git_file'), + dirctory: true, + disabled: true, + children: git + } + ] + utils.removeUselessChildren(options) + resourcesOptions.value = options || [] + resourcesLoading.value = false + } + + onMounted(() => { + getResources() + }) + + return { + type: 'tree-select', + field: 'resourceList', + name: t('project.node.resources'), + span, + options: resourcesOptions, + props: { + filterable: true, + multiple: true, + checkable: true, + cascade: true, + showPath: true, + checkStrategy: 'parent', + placeholder: t('project.node.resources_tips'), + keyField: 'id', + labelField: 'name', + loading: resourcesLoading + }, + validate: { + trigger: ['input', 'blur'], + required, + validator(validate: any, value: IResource[]) { + if (required) { + if (!value) { + return new Error(t('project.node.resources_tips')) + } + + if (limit > 0 && value.length > limit) { + return new Error(t('project.node.resources_limit_tips') + limit) + } + } + } + } + } +} diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-rules.ts b/seatunnel-ui/src/views/task/components/node/fields/use-rules.ts new file mode 100644 index 000000000..3eb445d88 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-rules.ts @@ -0,0 +1,266 @@ +/* + * 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 { ref, onMounted, computed } from 'vue' +import { useI18n } from 'vue-i18n' +import { + queryRuleList, + getRuleFormCreateJson, + getDatasourceOptionsById +} from '@/service/data-quality' +import { + getDatasourceTablesById, + getDatasourceTableColumnsById +} from '@/service/data-source' +import type { IJsonItem, IResponseJsonItem, IJsonItemParams } from '../types' + +export function useRules( + model: { [field: string]: any }, + updateRules: (items: IJsonItem[], len: number) => void +): IJsonItem[] { + const { t } = useI18n() + const rules = ref([]) + const ruleLoading = ref(false) + const srcDatasourceOptions = ref([] as { label: string; value: number }[]) + const srcTableOptions = ref([] as { label: string; value: number }[]) + const srcTableColumnOptions = ref([] as { label: string; value: number }[]) + const targetDatasourceOptions = ref([] as { label: string; value: number }[]) + const targetTableOptions = ref([] as { label: string; value: string }[]) + const targetTableColumnOptions = ref([] as { label: string; value: number }[]) + const writerDatasourceOptions = ref([] as { label: string; value: number }[]) + + const fixValueSpan = computed(() => (model.comparison_type === '1' ? 24 : 0)) + + let preItemLen = 0 + + const getRuleList = async () => { + if (ruleLoading.value) return + ruleLoading.value = true + const result = await queryRuleList() + rules.value = result.map((item: { id: number; name: string }) => { + let name = '' + if (item.name) { + name = item.name.replace('$t(', '').replace(')', '') + } + return { + value: item.id, + label: name ? t(`project.node.${name}`) : '' + } + }) + ruleLoading.value = false + } + + const getRuleById = async (ruleId: number, reset = false) => { + if (ruleLoading.value) return + ruleLoading.value = true + const result = await getRuleFormCreateJson(ruleId) + const items = JSON.parse(result).map((item: IResponseJsonItem) => + formatResponseJson(item, reset) + ) + updateRules(items, preItemLen) + preItemLen = items.length + ruleLoading.value = false + } + + const formatResponseJson = ( + responseItem: IResponseJsonItem, + reset = false + ): IJsonItemParams => { + const item: IJsonItemParams = { + field: responseItem.field, + options: responseItem.options, + validate: responseItem.validate, + props: responseItem.props, + value: responseItem.value + } + const name = responseItem.name?.replace('$t(', '').replace(')', '') + item.name = name ? t(`project.node.${name}`) : '' + + if (responseItem.type !== 'group') { + item.type = responseItem.type + } else { + item.type = 'custom-parameters' + item.children = item.props.rules.map((child: IJsonItemParams) => { + child.span = Math.floor(22 / item.props.rules.length) + return child + }) + model[item.field] = model[item.field] || [] + delete item.props.rules + } + if (responseItem.emit) { + responseItem.emit.forEach((emit) => { + if (emit === 'change') { + item.props.onUpdateValue = (value: string | number) => { + onFieldChange(value, item.field, true) + } + } + }) + } + if (responseItem.props.placeholder) { + item.props.placeholder = t( + 'project.node.' + + responseItem.props.placeholder + .split(' ') + .join('_') + .split(',') + .join('') + .toLowerCase() + ) + } + if (item.field === 'src_datasource_id') { + item.options = srcDatasourceOptions + } + if (item.field === 'target_datasource_id') { + item.options = targetDatasourceOptions + } + if (item.field === 'writer_datasource_id') { + item.options = writerDatasourceOptions + } + if (item.field === 'src_table') { + item.options = srcTableOptions + item.props.filterable = true + } + if (item.field === 'target_table') { + item.options = targetTableOptions + item.props.filterable = true + } + if (item.field === 'src_field') { + item.options = srcTableColumnOptions + } + if (item.field === 'target_field') { + item.options = targetTableColumnOptions + } + + if (model[item.field] !== void 0 && !reset) { + onFieldChange(model[item.field], item.field, false) + item.value = model[item.field] + } + + return item + } + + const onFieldChange = async ( + value: string | number, + field: string, + reset: boolean + ) => { + if (field === 'src_connector_type' && typeof value === 'string') { + const result = await getDatasourceOptionsById(value) + srcDatasourceOptions.value = result || [] + if (reset) { + srcTableOptions.value = [] + model.src_datasource_id = null + model.src_table = null + model.src_field = null + } + return + } + if (field === 'target_connector_type' && typeof value === 'string') { + const result = await getDatasourceOptionsById(value) + targetDatasourceOptions.value = result || [] + if (reset) { + targetTableOptions.value = [] + model.target_datasource_id = null + model.target_table = null + model.target_field = null + } + return + } + if (field === 'writer_connector_type' && typeof value === 'string') { + const result = await getDatasourceOptionsById(value) + writerDatasourceOptions.value = result || [] + if (reset) { + model.writer_datasource_id = null + } + return + } + if (field === 'src_datasource_id' && typeof value === 'string') { + const result = await getDatasourceTablesById(value) + srcTableOptions.value = result || [] + if (reset) { + model.src_table = null + model.src_field = null + } + } + if (field === 'target_datasource_id' && typeof value === 'string') { + const result = await getDatasourceTablesById(value) + targetTableOptions.value = result || [] + if (reset) { + model.target_table = null + model.target_field = null + } + } + if (field === 'src_table' && typeof value === 'string') { + const result = await getDatasourceTableColumnsById( + model.src_datasource_id, + value + ) + srcTableColumnOptions.value = result || [] + if (reset) { + model.src_field = null + } + } + if (field === 'target_table' && typeof value === 'string') { + const result = await getDatasourceTableColumnsById( + model.target_datasource_id, + value + ) + targetTableColumnOptions.value = result || [] + if (reset) { + model.target_field = null + } + } + } + + onMounted(async () => { + await getRuleList() + await getRuleById(model.ruleId) + }) + + return [ + { + type: 'select', + field: 'ruleId', + name: t('project.node.rule_name'), + props: { + loading: ruleLoading, + onUpdateValue: (ruleId: number) => { + getRuleById(ruleId, true) + } + }, + options: rules + }, + { + type: 'input-number', + field: 'comparison_name', + name: t('project.node.fix_value'), + props: { + placeholder: t('project.node.fix_value'), + min: 0 + }, + validate: { + trigger: ['input', 'blur'], + required: true, + validator(validate: any, value: number) { + if (model.comparison_type === '1' && !value && value !== 0) { + return new Error(t('project.node.fix_value_tips')) + } + } + }, + span: fixValueSpan + } + ] +} diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-run-flag.ts b/seatunnel-ui/src/views/task/components/node/fields/use-run-flag.ts new file mode 100644 index 000000000..e32040079 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-run-flag.ts @@ -0,0 +1,39 @@ +/* + * 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 { useI18n } from 'vue-i18n' +import type { IJsonItem } from '../types' + +export function useRunFlag(): IJsonItem { + const { t } = useI18n() + const options = [ + { + label: t('project.node.normal'), + value: 'YES' + }, + { + label: t('project.node.prohibition_execution'), + value: 'NO' + } + ] + return { + type: 'radio', + field: 'flag', + name: t('project.node.run_flag'), + options: options + } +} diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-sagemaker.ts b/seatunnel-ui/src/views/task/components/node/fields/use-sagemaker.ts new file mode 100644 index 000000000..17029b1d7 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-sagemaker.ts @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import type { IJsonItem } from '../types' +import { useCustomParams, useResources } from '.' + +export function useSagemaker(model: { [field: string]: any }): IJsonItem[] { + + const nodes: IJsonItem[] = [] + nodes.push(useResources()) + nodes.push( + ...useCustomParams({ model, field: 'localParams', isSimple: false }) + ) + return nodes +} diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-sea-tunnel.ts b/seatunnel-ui/src/views/task/components/node/fields/use-sea-tunnel.ts new file mode 100644 index 000000000..7dc15436f --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-sea-tunnel.ts @@ -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 { computed, watch } from 'vue' +import { useI18n } from 'vue-i18n' +import { useResources, useCustomParams } from '.' +import type { IJsonItem } from '../types' + +export function useSeaTunnel( + model: { [field: string]: any }, + updateValue?: (value: any, field: string) => void +): IJsonItem[] { + const { t } = useI18n() + + const resourceEditorSpan = computed(() => (model.useCustom ? 0 : 24)) + + watch( + () => model.useCustom, + () => { + const handlers = model.useCustom + ? [{ key: 'script', name: t('project.node.script') }] + : [] + if (updateValue) { + updateValue( + { + ...model, + handlers, + language: 'script' + }, + 'batch' + ) + } + } + ) + + return [ + { + type: 'select', + field: 'engine', + span: 12, + name: t('project.node.engine'), + options: ENGINE, + validate: { + trigger: ['input', 'blur'], + required: true, + message: t('project.node.engine_tips') + } + }, + { + type: 'input', + field: 'others', + name: t('project.node.option_parameters'), + span: 24, + props: { + type: 'textarea', + placeholder: t('project.node.option_parameters_tips') + } + }, + + // SeaTunnel config parameter + { + type: 'switch', + field: 'useCustom', + name: t('project.node.custom_config') + }, + + useResources(resourceEditorSpan, true, 1), + ...useCustomParams({ model, field: 'localParams', isSimple: true }) + ] +} + +export const ENGINE = [ + { + label: 'SEATUNNEL', + value: 'SEATUNNEL' + } +] diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-shell.ts b/seatunnel-ui/src/views/task/components/node/fields/use-shell.ts new file mode 100644 index 000000000..27cf1c392 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-shell.ts @@ -0,0 +1,45 @@ +/* + * 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 { useI18n } from 'vue-i18n' +import { useCustomParams, useDynamicDatasource, useResources } from '.' +import type { IJsonItem } from '../types' + +export function useShell( + model: { [field: string]: any }, + from: number +): IJsonItem[] { + const { t } = useI18n() + const nodes: IJsonItem[] = [] + if (from === 1) { + nodes.push({ + type: 'editor', + field: 'rawScript', + name: t('project.node.script'), + validate: { + trigger: ['input', 'trigger'], + required: true, + message: t('project.node.script_tips') + } + }) + } + nodes.push(...useDynamicDatasource({ model })) + nodes.push(useResources()) + nodes.push( + ...useCustomParams({ model, field: 'localParams', isSimple: false }) + ) + return nodes +} diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-spark.ts b/seatunnel-ui/src/views/task/components/node/fields/use-spark.ts new file mode 100644 index 000000000..6d58919ec --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-spark.ts @@ -0,0 +1,157 @@ +/* + * 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 { computed, ref, watch } from 'vue' +import { useI18n } from 'vue-i18n' +import { + useCustomParams, + useDeployMode, + useDriverCores, + useDriverMemory, + useExecutorNumber, + useExecutorMemory, + useExecutorCores, + useMainJar, + useResources +} from '.' +import type { IJsonItem } from '../types' + +export function useSpark( + model: { [field: string]: any }, + updateValue?: (value: any, field: string) => void +): IJsonItem[] { + const { t } = useI18n() + const mainClassSpan = computed(() => + model.programType === 'PYTHON' || model.programType === 'SQL' ? 0 : 24 + ) + + const mainArgsSpan = computed(() => (model.programType === 'SQL' ? 0 : 24)) + + const showCluster = computed(() => model.programType !== 'SQL') + + watch( + () => model.programType, + () => { + const handlers = + model.programType === 'SQL' + ? [{ key: 'script', name: t('project.node.sql_statement') }] + : [] + if (updateValue) { + updateValue( + { + ...model, + handlers, + language: 'sql' + }, + 'batch' + ) + } + } + ) + + return [ + { + type: 'select', + field: 'programType', + span: 12, + name: t('project.node.program_type'), + options: PROGRAM_TYPES, + props: { + 'on-update:value': () => { + model.mainJar = null + model.mainClass = '' + } + } + }, + { + type: 'input', + field: 'mainClass', + span: mainClassSpan, + name: t('project.node.main_class'), + props: { + placeholder: t('project.node.main_class_tips') + }, + validate: { + trigger: ['input', 'blur'], + required: model.programType !== 'PYTHON' && model.programType !== 'SQL', + validator(validate: any, value: string) { + if ( + model.programType !== 'PYTHON' && + !value && + model.programType !== 'SQL' + ) { + return new Error(t('project.node.main_class_tips')) + } + } + } + }, + useMainJar(model), + useDeployMode(24, ref(true), showCluster), + { + type: 'input', + field: 'appName', + name: t('project.node.app_name'), + props: { + placeholder: t('project.node.app_name_tips') + } + }, + useDriverCores(), + useDriverMemory(), + useExecutorNumber(), + useExecutorMemory(), + useExecutorCores(), + { + type: 'input', + field: 'mainArgs', + span: mainArgsSpan, + name: t('project.node.main_arguments'), + props: { + type: 'textarea', + placeholder: t('project.node.main_arguments_tips') + } + }, + { + type: 'input', + field: 'others', + name: t('project.node.option_parameters'), + props: { + type: 'textarea', + placeholder: t('project.node.option_parameters_tips') + } + }, + useResources(), + ...useCustomParams({ model, field: 'localParams', isSimple: false }) + ] +} + +export const PROGRAM_TYPES = [ + { + label: 'JAVA', + value: 'JAVA' + }, + { + label: 'SCALA', + value: 'SCALA' + }, + { + label: 'PYTHON', + value: 'PYTHON' + }, + { + label: 'SQL', + value: 'SQL' + } +] diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-sql-utils.ts b/seatunnel-ui/src/views/task/components/node/fields/use-sql-utils.ts new file mode 100644 index 000000000..d5d687637 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-sql-utils.ts @@ -0,0 +1,148 @@ +/* + * 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 { ref, onMounted, computed, h } from 'vue' +import { useI18n } from 'vue-i18n' +import styles from '../index.module.scss' +import type { IJsonItem } from '../types' +import { getResourceList } from '@/service/modules/resources' +import { useRoute } from 'vue-router' + +export function useSqlUtils(model: { [field: string]: any }): IJsonItem[] { + const { t } = useI18n() + const emailSpan = computed(() => (model.sendEmail ? 24 : 0)) + const groups = ref([]) + const groupsLoading = ref(false) + + const route = useRoute() + const projectCode = Number(route.params.projectCode) + + const getGroups = async () => { + if (groupsLoading.value) return + groupsLoading.value = true + const res = await getResourceList({ + accessType: 'ALERT_GROUP', + projectCode + }) + groups.value = res.map((item: { id: number; groupName: string }) => ({ + label: item.groupName, + value: item.id + })) + groupsLoading.value = false + } + + onMounted(() => { + getGroups() + }) + + return [ + { + type: 'select', + field: 'displayRows', + span: 6, + name: t('project.node.log_display'), + options: DISPLAY_ROWS, + props: { + filterable: true, + tag: true + }, + validate: { + trigger: ['input', 'blur'], + validator(unuse, value) { + if (!/^\+?[1-9][0-9]*$/.test(value)) { + return new Error(t('project.node.integer_tips')) + } + } + } + }, + { + type: 'custom', + field: 'displayRowsTips', + span: 6, + widget: h( + 'div', + { class: styles['display-rows-tips'] }, + t('project.node.rows_of_result') + ) + }, + { + type: 'switch', + field: 'sendEmail', + span: 6, + name: t('project.node.send_email') + }, + { + type: 'input', + field: 'title', + name: t('project.node.title'), + props: { + placeholder: t('project.node.title_tips') + }, + span: emailSpan, + validate: { + trigger: ['input', 'blur'], + required: true, + validator(unuse, value) { + if (model.sendEmail && !value) + return new Error(t('project.node.title_tips')) + } + } + }, + { + type: 'select', + field: 'groupId', + name: t('project.node.alarm_group'), + options: groups, + span: emailSpan, + props: { + loading: groupsLoading, + placeholder: t('project.node.alarm_group_tips') + }, + validate: { + trigger: ['input', 'blur'], + required: true, + validator(unuse, value) { + if (model.sendEmail && !value) + return new Error(t('project.node.alarm_group_tips')) + } + } + } + ] +} + +const DISPLAY_ROWS = [ + { + label: '1', + value: 1 + }, + { + label: '10', + value: 10 + }, + { + label: '25', + value: 25 + }, + { + label: '50', + value: 50 + }, + { + label: '100', + value: 100 + } +] diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-sql.ts b/seatunnel-ui/src/views/task/components/node/fields/use-sql.ts new file mode 100644 index 000000000..396a66aae --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-sql.ts @@ -0,0 +1,81 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { computed } from 'vue' +import { useI18n } from 'vue-i18n' +import { useCustomParams } from '.' +import { useUdfs } from './use-udfs' +import type { IJsonItem } from '../types' + +export function useSql( + model: { [field: string]: any }, + from: number +): IJsonItem[] { + const { t } = useI18n() + const hiveSpan = computed(() => (model.type === 'HIVE' ? 24 : 0)) + const nodes: IJsonItem[] = [ + { + type: 'input', + field: 'connParams', + name: t('project.node.sql_parameter'), + props: { + placeholder: + t('project.node.format_tips') + ' key1=value1;key2=value2...' + }, + span: hiveSpan + } + ] + + if (from === 1) { + nodes.push({ + type: 'editor', + field: 'sql', + name: t('project.node.sql_statement'), + validate: { + trigger: ['input', 'trigger'], + required: true, + message: t('project.node.sql_empty_tips') + } + }) + } + nodes.push( + useUdfs(model), + ...useCustomParams({ model, field: 'localParams', isSimple: false }), + { + type: 'multi-input', + field: 'preStatements', + name: t('project.node.pre_sql_statement'), + span: 22, + props: { + placeholder: t('project.node.sql_input_placeholder'), + type: 'textarea', + autosize: { minRows: 1 } + } + }, + { + type: 'multi-input', + field: 'postStatements', + name: t('project.node.post_sql_statement'), + span: 22, + props: { + placeholder: t('project.node.sql_input_placeholder'), + type: 'textarea', + autosize: { minRows: 1 } + } + } + ) + return nodes +} diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-sqoop-source-type.ts b/seatunnel-ui/src/views/task/components/node/fields/use-sqoop-source-type.ts new file mode 100644 index 000000000..108d6ada0 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-sqoop-source-type.ts @@ -0,0 +1,291 @@ +/* + * 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 { ref, h, watch, Ref } from 'vue' +import { useI18n } from 'vue-i18n' +import { useCustomParams, useDatasource } from '.' +import styles from '../index.module.scss' +import type { IJsonItem, IOption, ModelType } from '../types' + +export function useSourceType( + model: { [field: string]: any }, + unCustomSpan: Ref +): IJsonItem[] { + const { t } = useI18n() + const mysqlSpan = ref(24) + const tableSpan = ref(0) + const editorSpan = ref(24) + const columnSpan = ref(0) + const hiveSpan = ref(0) + const hdfsSpan = ref(0) + const datasourceSpan = ref(12) + const resetSpan = () => { + mysqlSpan.value = + unCustomSpan.value && model.sourceType === 'MYSQL' ? 24 : 0 + tableSpan.value = mysqlSpan.value && model.srcQueryType === '0' ? 24 : 0 + editorSpan.value = mysqlSpan.value && model.srcQueryType === '1' ? 24 : 0 + columnSpan.value = tableSpan.value && model.srcColumnType === '1' ? 24 : 0 + hiveSpan.value = unCustomSpan.value && model.sourceType === 'HIVE' ? 24 : 0 + hdfsSpan.value = unCustomSpan.value && model.sourceType === 'HDFS' ? 24 : 0 + datasourceSpan.value = + unCustomSpan.value && model.sourceType === 'MYSQL' ? 12 : 0 + } + const sourceTypes = ref([ + { + label: 'MYSQL', + value: 'MYSQL' + } + ] as IOption[]) + + const getSourceTypesByModelType = (modelType: ModelType): IOption[] => { + switch (modelType) { + case 'import': + return [ + { + label: 'MYSQL', + value: 'MYSQL' + } + ] + case 'export': + return [ + { + label: 'HDFS', + value: 'HDFS' + }, + { + label: 'HIVE', + value: 'HIVE' + } + ] + default: + return [ + { + label: 'MYSQL', + value: 'MYSQL' + }, + { + label: 'HDFS', + value: 'HDFS' + }, + { + label: 'HIVE', + value: 'HIVE' + } + ] + } + } + + watch( + () => model.modelType, + (modelType: ModelType) => { + sourceTypes.value = getSourceTypesByModelType(modelType) + if (!model.sourceType) { + model.sourceType = sourceTypes.value[0].value + } + } + ) + watch( + () => [ + unCustomSpan.value, + model.sourceType, + model.srcQueryType, + model.srcColumnType + ], + () => { + resetSpan() + } + ) + + return [ + { + type: 'custom', + field: 'custom-title-source', + span: unCustomSpan, + widget: h( + 'div', + { class: styles['field-title'] }, + t('project.node.data_source') + ) + }, + { + type: 'select', + field: 'sourceType', + name: t('project.node.type'), + span: unCustomSpan, + options: sourceTypes + }, + ...useDatasource({ + model, + span: datasourceSpan, + typeField: 'sourceMysqlType', + sourceField: 'sourceMysqlDatasource' + }), + { + type: 'radio', + field: 'srcQueryType', + name: t('project.node.model_type'), + span: mysqlSpan, + options: [ + { + label: t('project.node.form'), + value: '0' + }, + { + label: 'SQL', + value: '1' + } + ], + props: { + 'on-update:value': (value: '0' | '1') => { + model.targetType = value === '0' ? 'HIVE' : 'HDFS' + } + } + }, + { + type: 'input', + field: 'srcTable', + name: t('project.node.table'), + span: tableSpan, + props: { + placeholder: t('project.node.table_tips') + }, + validate: { + trigger: ['input', 'blur'], + required: true, + validator(validate, value) { + if (tableSpan.value && !value) { + return new Error(t('project.node.table_tips')) + } + } + } + }, + { + type: 'radio', + field: 'srcColumnType', + name: t('project.node.column_type'), + span: tableSpan, + options: [ + { label: t('project.node.all_columns'), value: '0' }, + { label: t('project.node.some_columns'), value: '1' } + ] + }, + { + type: 'input', + field: 'srcColumns', + name: t('project.node.column'), + span: columnSpan, + props: { + placeholder: t('project.node.column_tips') + }, + validate: { + trigger: ['input', 'blur'], + required: true, + validator(validate, value) { + if (!!columnSpan.value && !value) { + return new Error(t('project.node.column_tips')) + } + } + } + }, + { + type: 'input', + field: 'sourceHiveDatabase', + name: t('project.node.database'), + span: hiveSpan, + props: { + placeholder: t('project.node.database_tips') + }, + validate: { + trigger: ['blur', 'input'], + required: true, + validator(validate, value) { + if (hiveSpan.value && !value) { + return new Error(t('project.node.database_tips')) + } + } + } + }, + { + type: 'input', + field: 'sourceHiveTable', + name: t('project.node.table'), + span: hiveSpan, + props: { + placeholder: t('project.node.hive_table_tips') + }, + validate: { + trigger: ['blur', 'input'], + required: true, + validator(validate, value) { + if (hiveSpan.value && !value) { + return new Error(t('project.node.hive_table_tips')) + } + } + } + }, + { + type: 'input', + field: 'sourceHivePartitionKey', + name: t('project.node.hive_partition_keys'), + span: hiveSpan, + props: { + placeholder: t('project.node.hive_partition_keys_tips') + } + }, + { + type: 'input', + field: 'sourceHivePartitionValue', + name: t('project.node.hive_partition_values'), + span: hiveSpan, + props: { + placeholder: t('project.node.hive_partition_values_tips') + } + }, + { + type: 'input', + field: 'sourceHdfsExportDir', + name: t('project.node.export_dir'), + span: hdfsSpan, + props: { + placeholder: t('project.node.export_dir_tips') + }, + validate: { + trigger: ['blur', 'input'], + required: true, + validator(validate, value) { + if (hdfsSpan.value && !value) { + return new Error(t('project.node.export_dir_tips')) + } + } + } + }, + ...useCustomParams({ + model, + field: 'mapColumnHive', + name: 'map_column_hive', + isSimple: true, + span: mysqlSpan + }), + ...useCustomParams({ + model, + field: 'mapColumnJava', + name: 'map_column_java', + isSimple: true, + span: mysqlSpan + }) + ] +} diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-sqoop-target-type.ts b/seatunnel-ui/src/views/task/components/node/fields/use-sqoop-target-type.ts new file mode 100644 index 000000000..f25752e92 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-sqoop-target-type.ts @@ -0,0 +1,403 @@ +/* + * 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 { ref, h, watch, Ref } from 'vue' +import { useI18n } from 'vue-i18n' +import styles from '../index.module.scss' +import type { IJsonItem, IOption, SourceType } from '../types' +import { useDatasource } from './use-datasource' + +export function useTargetType( + model: { [field: string]: any }, + unCustomSpan: Ref +): IJsonItem[] { + const { t } = useI18n() + const hiveSpan = ref(0) + const hdfsSpan = ref(24) + const mysqlSpan = ref(0) + const dataSourceSpan = ref(0) + const updateSpan = ref(0) + + const resetSpan = () => { + hiveSpan.value = unCustomSpan.value && model.targetType === 'HIVE' ? 24 : 0 + hdfsSpan.value = unCustomSpan.value && model.targetType === 'HDFS' ? 24 : 0 + mysqlSpan.value = + unCustomSpan.value && model.targetType === 'MYSQL' ? 24 : 0 + dataSourceSpan.value = + unCustomSpan.value && model.targetType === 'MYSQL' ? 12 : 0 + updateSpan.value = mysqlSpan.value && model.targetMysqlIsUpdate ? 24 : 0 + } + + const targetTypes = ref([ + { + label: 'HIVE', + value: 'HIVE' + }, + { + label: 'HDFS', + value: 'HDFS' + } + ] as IOption[]) + + const getTargetTypesBySourceType = ( + sourceType: SourceType, + srcQueryType: string + ): IOption[] => { + switch (sourceType) { + case 'MYSQL': + if (srcQueryType === '1') { + return [ + { + label: 'HDFS', + value: 'HDFS' + } + ] + } + return [ + { + label: 'HIVE', + value: 'HIVE' + }, + { + label: 'HDFS', + value: 'HDFS' + } + ] + case 'HDFS': + case 'HIVE': + return [ + { + label: 'MYSQL', + value: 'MYSQL' + } + ] + default: + return [ + { + label: 'HIVE', + value: 'HIVE' + }, + { + label: 'HDFS', + value: 'HDFS' + } + ] + } + } + + watch( + () => [model.sourceType, model.srcQueryType], + ([sourceType, srcQueryType]) => { + targetTypes.value = getTargetTypesBySourceType(sourceType, srcQueryType) + if (!model.targetType) { + model.targetType = targetTypes.value[0].value + } + } + ) + + watch( + () => [unCustomSpan.value, model.targetType, model.targetMysqlIsUpdate], + () => { + resetSpan() + } + ) + + return [ + { + type: 'custom', + field: 'custom-title-target', + span: unCustomSpan, + widget: h( + 'div', + { class: styles['field-title'] }, + t('project.node.data_target') + ) + }, + { + type: 'select', + field: 'targetType', + name: t('project.node.type'), + span: unCustomSpan, + options: targetTypes + }, + { + type: 'input', + field: 'targetHiveDatabase', + name: t('project.node.database'), + span: hiveSpan, + props: { + placeholder: t('project.node.database_tips') + }, + validate: { + trigger: ['blur', 'input'], + required: true, + validator(validate, value) { + if (hiveSpan.value && !value) { + return new Error(t('project.node.database_tips')) + } + } + } + }, + { + type: 'input', + field: 'targetHiveTable', + name: t('project.node.table'), + span: hiveSpan, + props: { + placeholder: t('project.node.table') + }, + validate: { + trigger: ['blur', 'input'], + required: true, + validator(rule, value) { + if (hiveSpan.value && !value) { + return new Error(t('project.node.hive_table_tips')) + } + } + } + }, + { + type: 'switch', + field: 'targetHiveCreateTable', + span: hiveSpan, + name: t('project.node.create_hive_table') + }, + { + type: 'switch', + field: 'targetHiveDropDelimiter', + span: hiveSpan, + name: t('project.node.drop_delimiter') + }, + { + type: 'switch', + field: 'targetHiveOverWrite', + span: hiveSpan, + name: t('project.node.over_write_src') + }, + { + type: 'input', + field: 'targetHiveTargetDir', + name: t('project.node.hive_target_dir'), + span: hiveSpan, + props: { + placeholder: t('project.node.hive_target_dir_tips') + } + }, + { + type: 'input', + field: 'targetHiveReplaceDelimiter', + name: t('project.node.replace_delimiter'), + span: hiveSpan, + props: { + placeholder: t('project.node.replace_delimiter_tips') + } + }, + { + type: 'input', + field: 'targetHivePartitionKey', + name: t('project.node.hive_partition_keys'), + span: hiveSpan, + props: { + placeholder: t('project.node.hive_partition_keys_tips') + } + }, + { + type: 'input', + field: 'targetHivePartitionValue', + name: t('project.node.hive_partition_values'), + span: hiveSpan, + props: { + placeholder: t('project.node.hive_partition_values_tips') + } + }, + { + type: 'input', + field: 'targetHdfsTargetPath', + name: t('project.node.target_dir'), + span: hdfsSpan, + props: { + placeholder: t('project.node.target_dir_tips') + }, + validate: { + trigger: ['blur', 'input'], + required: true, + validator(rule, value) { + if (hdfsSpan.value && !value) { + return new Error(t('project.node.target_dir_tips')) + } + } + } + }, + { + type: 'switch', + field: 'targetHdfsDeleteTargetDir', + name: t('project.node.delete_target_dir'), + span: hdfsSpan + }, + { + type: 'radio', + field: 'targetHdfsCompressionCodec', + name: t('project.node.compression_codec'), + span: hdfsSpan, + options: COMPRESSIONCODECS + }, + { + type: 'radio', + field: 'targetHdfsFileType', + name: t('project.node.file_type'), + span: hdfsSpan, + options: FILETYPES + }, + { + type: 'input', + field: 'targetHdfsFieldsTerminated', + name: t('project.node.fields_terminated'), + span: hdfsSpan, + props: { + placeholder: t('project.node.fields_terminated_tips') + } + }, + { + type: 'input', + field: 'targetHdfsLinesTerminated', + name: t('project.node.lines_terminated'), + span: hdfsSpan, + props: { + placeholder: t('project.node.lines_terminated_tips') + } + }, + ...useDatasource({ + model, + span: dataSourceSpan, + typeField: 'targetMysqlType', + sourceField: 'targetMysqlDatasource' + }), + { + type: 'input', + field: 'targetMysqlTable', + name: t('project.node.table'), + span: mysqlSpan, + props: { + placeholder: t('project.node.hive_table_tips') + }, + validate: { + trigger: ['blur', 'input'], + required: true, + validator(validate, value) { + if (mysqlSpan.value && !value) { + return new Error(t('project.node.table_tips')) + } + } + } + }, + { + type: 'input', + field: 'targetMysqlColumns', + name: t('project.node.column'), + span: mysqlSpan, + props: { + placeholder: t('project.node.column_tips') + } + }, + { + type: 'input', + field: 'targetMysqlFieldsTerminated', + name: t('project.node.fields_terminated'), + span: mysqlSpan, + props: { + placeholder: t('project.node.fields_terminated_tips') + } + }, + { + type: 'input', + field: 'targetMysqlLinesTerminated', + name: t('project.node.lines_terminated'), + span: mysqlSpan, + props: { + placeholder: t('project.node.lines_terminated_tips') + } + }, + { + type: 'switch', + field: 'targetMysqlIsUpdate', + span: mysqlSpan, + name: t('project.node.is_update') + }, + { + type: 'input', + field: 'targetMysqlTargetUpdateKey', + name: t('project.node.update_key'), + span: updateSpan, + props: { + placeholder: t('project.node.update_key_tips') + } + }, + { + type: 'radio', + field: 'targetMysqlUpdateMode', + name: t('project.node.update_mode'), + span: updateSpan, + options: [ + { + label: t('project.node.only_update'), + value: 'updateonly' + }, + { + label: t('project.node.allow_insert'), + value: 'allowinsert' + } + ] + } + ] +} + +const COMPRESSIONCODECS = [ + { + label: 'snappy', + value: 'snappy' + }, + { + label: 'lzo', + value: 'lzo' + }, + { + label: 'gzip', + value: 'gzip' + }, + { + label: 'no', + value: '' + } +] +const FILETYPES = [ + { + label: 'avro', + value: '--as-avrodatafile' + }, + { + label: 'sequence', + value: '--as-sequencefile' + }, + { + label: 'text', + value: '--as-textfile' + }, + { + label: 'parquet', + value: '--as-parquetfile' + } +] diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-sqoop.ts b/seatunnel-ui/src/views/task/components/node/fields/use-sqoop.ts new file mode 100644 index 000000000..d373b5f1c --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-sqoop.ts @@ -0,0 +1,148 @@ +/* + * 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 { computed, watch } from 'vue' +import { useI18n } from 'vue-i18n' +import { useCustomParams, useSourceType, useTargetType } from '.' +import type { IJsonItem, ModelType } from '../types' + +export function useSqoop( + model: { [field: string]: any }, + updateValue?: (value: any, field: string) => void +): IJsonItem[] { + const { t } = useI18n() + const unCustomSpan = computed(() => (model.isCustomTask ? 0 : 24)) + + watch( + () => model.isCustomTask, + () => { + const sqlHandlers = + model.srcQueryType === '1' + ? [{ key: 'script', name: t('project.node.sql_statement') }] + : [] + const handlers = model.isCustomTask + ? [{ key: 'script', name: t('project.node.script') }] + : sqlHandlers + if (updateValue) { + updateValue( + { + ...model, + handlers, + language: model.isCustomTask ? 'script' : 'sql', + script: '' + }, + 'batch' + ) + } + } + ) + + watch( + () => model.srcQueryType, + () => { + const handlers = + model.srcQueryType === '1' + ? [{ key: 'script', name: 'project.node.sql_statement' }] + : [] + if (updateValue) { + updateValue( + { + ...model, + handlers, + language: 'sql', + script: '' + }, + 'batch' + ) + } + } + ) + + return [ + { + type: 'switch', + field: 'isCustomTask', + name: t('project.node.custom_job') + }, + { + type: 'input', + field: 'jobName', + name: t('project.node.sqoop_job_name'), + span: unCustomSpan, + props: { + placeholder: t('project.node.sqoop_job_name_tips') + }, + validate: { + trigger: ['input', 'blur'], + required: true, + validator(validate, value) { + if (!model.isCustomTask && !value) { + return new Error(t('project.node.sqoop_job_name_tips')) + } + } + } + }, + { + type: 'select', + field: 'modelType', + name: t('project.node.direct'), + span: unCustomSpan, + options: MODEL_TYPES + }, + ...useCustomParams({ + model, + field: 'hadoopCustomParams', + name: 'hadoop_custom_params', + isSimple: true, + span: unCustomSpan + }), + ...useCustomParams({ + model, + field: 'sqoopAdvancedParams', + name: 'sqoop_advanced_parameters', + isSimple: true, + span: unCustomSpan + }), + ...useSourceType(model, unCustomSpan), + ...useTargetType(model, unCustomSpan), + { + type: 'input-number', + field: 'concurrency', + name: t('project.node.concurrency'), + span: unCustomSpan, + props: { + placeholder: t('project.node.concurrency_tips') + } + }, + ...useCustomParams({ + model, + field: 'localParams', + name: 'custom_parameters', + isSimple: true + }) + ] +} + +const MODEL_TYPES = [ + { + label: 'import', + value: 'import' + }, + { + label: 'export', + value: 'export' + } +] as { label: ModelType; value: ModelType }[] diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-surveil.ts b/seatunnel-ui/src/views/task/components/node/fields/use-surveil.ts new file mode 100644 index 000000000..3d54b0089 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-surveil.ts @@ -0,0 +1,371 @@ +/* + * 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 { computed, h, watch } from 'vue' +import { useI18n } from 'vue-i18n' +import { useCustomParams, useRemoteConnection } from '.' +import { useDatasource } from './use-datasource' +import { NIcon } from 'naive-ui' +import { QuestionCircleOutlined } from '@vicons/antd' +import styles from '../index.module.scss' +import type { IJsonItem } from '../types' + +export function useSurveil( + model: { [field: string]: any }, + updateValue?: (value: any, field: string) => void +): IJsonItem[] { + const { t, locale } = useI18n() + const filePathSpan = computed(() => (model.scanType === 1 ? 24 : 0)) + const dataSourceSpan = computed(() => (model.scanType === 2 ? 12 : 0)) + const kafkaSpan = computed(() => (model.scanType === 3 ? 24 : 0)) + const hdfsPathSpan = computed(() => (model.scanType === 4 ? 24 : 0)) + const remoteDisplay = computed(() => model['scanType'] === 1) + + watch( + () => model.scanType, + () => { + const handlers = + model.scanType === 2 + ? [{ key: 'script', name: 'project.node.sql_statement' }] + : [] + + if (updateValue) { + updateValue( + { + script: '', + ...model, + handlers, + language: 'sql' + }, + 'batch' + ) + } + } + ) + + return [ + { + type: 'select', + field: 'scanType', + name: t('project.node.scan_type'), + options: SCAN_TYPES, + span: 12 + }, + { + type: 'input-number', + field: 'interval', + name: t('project.node.scan_interval'), + span: 12, + slots: { + suffix: () => t('project.node.second') + }, + validate: { + trigger: ['input', 'trigger'], + validator(unuse, value) { + if (value < 1 || !Number.isInteger(value)) { + return Error(t('project.node.positive_integer_tips')) + } + } + } + }, + { + type: 'input', + field: 'filePath', + name: t('project.node.file_path'), + span: filePathSpan, + props: { + placeholder: t('project.node.file_path_tips') + }, + validate: { + trigger: ['input', 'trigger'], + required: true, + validator(unuse, value) { + if (filePathSpan.value && !value) { + return Error(t('project.node.file_path_tips')) + } + } + } + }, + ...useRemoteConnection(model, remoteDisplay), + { + type: 'input', + field: 'filePath', + name: t('project.node.file_path'), + span: hdfsPathSpan, + props: { + placeholder: t('project.node.file_path_tips') + }, + validate: { + trigger: ['input', 'trigger'], + required: true, + validator(unuse, value) { + if (hdfsPathSpan.value && !value) { + return Error(t('project.node.file_path_tips')) + } + } + } + }, + ...useDatasource({ + model, + span: dataSourceSpan + }), + { + type: 'input', + field: 'topic', + name: t('project.node.topic_name'), + props: { + placeholder: t('project.node.topic_name_tips') + }, + validate: { + trigger: ['input', 'trigger'], + required: true, + validator(unuse, value) { + if (kafkaSpan.value && !value) { + return Error(t('project.node.topic_name_tips')) + } + } + }, + span: kafkaSpan + }, + { + type: 'input', + field: 'offsetTime', + name: h('div', null, [ + t('project.node.offset_time'), + h( + NIcon, + { + size: 20, + class: styles['question-icon'], + onClick: () => { + window.open( + `https://dolphinscheduler.apache.org/${ + locale.value === 'en_US' ? 'en-us' : 'zh-cn' + }/docs/latest/user_doc/guide/parameter/built-in.html` + ) + } + }, + () => h(QuestionCircleOutlined) + ) + ]), + props: { + placeholder: t('project.node.offset_time_required_tips') + }, + validate: { + trigger: ['input', 'trigger'], + required: true, + validator(unuse, value) { + if (kafkaSpan.value && !value) { + return Error(t('project.node.offset_time_required_tips')) + } + if ( + !/^\${.+?}$/.test(value) && + !/^\$\[.+?\]$/.test(value) && + !/^((19|20)[0-9]{2})((0[1-9])|(1[0-2]))((0[1-9])|((1|2)[0-9])|(3[0-1]))$/.test( + value + ) + ) { + return Error(t('project.node.offset_time_incorrect_tips')) + } + } + }, + span: kafkaSpan + }, + { + type: 'input', + field: 'bootstrapServers', + name: t('project.node.bootstrap_servers'), + props: { + placeholder: t('project.node.bootstrap_servers_tips') + }, + validate: { + trigger: ['input', 'trigger'], + required: true, + validator(unuse, value) { + if (kafkaSpan.value && !value) { + return Error(t('project.node.bootstrap_servers_tips')) + } + } + }, + span: kafkaSpan + }, + { + type: 'input', + field: 'groupId', + name: t('project.node.group_id'), + props: { + placeholder: t('project.node.group_id_tips') + }, + validate: { + trigger: ['input', 'trigger'], + required: true, + validator(unuse, value) { + if (kafkaSpan.value && !value) { + return Error(t('project.node.group_id_tips')) + } + } + }, + span: kafkaSpan + }, + { + type: 'input', + field: 'keyDeserializer', + name: t('project.node.key_deserializer'), + props: { + placeholder: t('project.node.key_deserializer_tips') + }, + validate: { + trigger: ['input', 'trigger'], + required: true, + validator(unuse, value) { + if (kafkaSpan.value && !value) { + return Error(t('project.node.key_deserializer_tips')) + } + } + }, + span: kafkaSpan + }, + { + type: 'input', + field: 'valueDeserializer', + name: t('project.node.value_deserializer'), + props: { + placeholder: t('project.node.value_deserializer_tips') + }, + validate: { + trigger: ['input', 'trigger'], + required: true, + validator(unuse, value) { + if (kafkaSpan.value && !value) { + return Error(t('project.node.value_deserializer_tips')) + } + } + }, + span: kafkaSpan + }, + ...useCustomParams({ model, field: 'localParams', isSimple: false }) + ] +} + +export const SCAN_TYPES = [ + { + value: 1, + label: 'FILE' + }, + { + value: 2, + label: 'SQL' + }, + { + value: 3, + label: 'KAFKA' + }, + { + value: 4, + label: 'HDFS' + } +] + +export const STATUS_TYPES = [ + { + value: 0, + label: 'SUBMITTED_SUCCESS' + }, + { + value: 1, + label: 'RUNNING_EXECUTION' + }, + { + value: 2, + label: 'READY_PAUSE' + }, + { + value: 3, + label: 'PAUSE' + }, + { + value: 4, + label: 'READY_STOP' + }, + { + value: 5, + label: 'STOP' + }, + { + value: 6, + label: 'FAILURE' + }, + { + value: 7, + label: 'SUCCESS' + }, + { + value: 8, + label: 'NEED_FAULT_TOLERANC' + }, + { + value: 9, + label: 'KILL' + }, + { + value: 10, + label: 'WAITING_THREAD' + }, + { + value: 11, + label: 'WAITING_DEPEND' + }, + { + value: 12, + label: 'DELAY_EXECUTION' + }, + { + value: 13, + label: 'FORCED_SUCCESS' + }, + { + value: 14, + label: 'SERIAL_WAIT' + }, + { + value: 15, + label: 'READY_BLOCK' + }, + { + value: 16, + label: 'BLOCK' + }, + { + value: 17, + label: 'DISPATCH' + }, + { + value: 18, + label: 'PAUSE_BY_ISOLATION' + }, + { + value: 19, + label: 'KILL_BY_ISOLATION' + }, + { + value: 20, + label: 'PAUSE_BY_CORONATION' + }, + { + value: 21, + label: 'FORBIDDEN_BY_CORONATION' + } +] diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-switch.ts b/seatunnel-ui/src/views/task/components/node/fields/use-switch.ts new file mode 100644 index 000000000..287000de7 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-switch.ts @@ -0,0 +1,127 @@ +/* + * 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 { ref, watch, onMounted, nextTick } from 'vue' +import { useI18n } from 'vue-i18n' +import { useTaskNodeStore } from '@/store/project/task-node' +import { queryProcessDefinitionByCode } from '@/service/modules/process-definition' +import { findIndex } from 'lodash' +import type { IJsonItem } from '../types' + +export function useSwitch( + model: { [field: string]: any }, + projectCode: number +): IJsonItem[] { + const { t } = useI18n() + const taskStore = useTaskNodeStore() + const branchFlowOptions = ref(taskStore.postTaskOptions as any) + + const loading = ref(false) + + const getOtherTaskDefinitionList = async () => { + if (loading.value) return + loading.value = true + branchFlowOptions.value = [] + const res = await queryProcessDefinitionByCode(model.processName) + res?.taskDefinitionList.forEach((item: any) => { + if (item.code != model.code) { + branchFlowOptions.value.push({ label: item.name, value: item.code }) + } + }) + loading.value = false + + clearUselessNode(branchFlowOptions.value) + } + + const clearUselessNode = (options: { value: number }[]) => { + if (!options || !options.length) { + model.nextNode = null + model.dependTaskList?.forEach((task: { nextNode: number | null }) => { + task.nextNode = null + }) + return + } + if ( + findIndex( + branchFlowOptions.value, + (option: { value: number }) => option.value == model.nextNode + ) === -1 + ) { + model.nextNode = null + } + model.dependTaskList?.forEach((task: { nextNode: number | null }) => { + if ( + findIndex( + branchFlowOptions.value, + (option: { value: number }) => option.value == task.nextNode + ) === -1 + ) { + task.nextNode = null + } + }) + } + + watch( + () => [model.processName, model.nextCode], + () => { + if (model.processName) { + getOtherTaskDefinitionList() + } + } + ) + + onMounted(async () => { + await nextTick() + clearUselessNode(branchFlowOptions.value) + }) + + return [ + { + type: 'custom-parameters', + field: 'dependTaskList', + name: t('project.node.switch_condition'), + children: [ + { + type: 'input', + field: 'condition', + span: 24, + props: { + loading: loading, + type: 'textarea', + autosize: { minRows: 2 } + } + }, + () => ({ + type: 'select', + field: 'nextNode', + span: 22, + name: t('project.node.switch_branch_flow'), + options: branchFlowOptions + }) + ] + }, + { + type: 'select', + field: 'nextNode', + span: 24, + name: t('project.node.switch_branch_flow'), + props: { + loading: loading + }, + options: branchFlowOptions + } + ] +} diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-target-task-name.ts b/seatunnel-ui/src/views/task/components/node/fields/use-target-task-name.ts new file mode 100644 index 000000000..36e1adb5c --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-target-task-name.ts @@ -0,0 +1,37 @@ +/* + * 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 { useI18n } from 'vue-i18n' +import type { IJsonItem } from '../types' + +export function useTargetTaskName(): IJsonItem { + const { t } = useI18n() + return { + type: 'input', + field: 'targetJobName', + name: t('project.node.target_task_name'), + props: { + placeholder: t('project.node.target_task_name_tips'), + maxLength: 100 + }, + validate: { + trigger: ['input', 'blur'], + required: true, + message: t('project.node.target_task_name_tips') + } + } +} diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-task-group.ts b/seatunnel-ui/src/views/task/components/node/fields/use-task-group.ts new file mode 100644 index 000000000..1c2592c21 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-task-group.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. + */ + +import { ref, watch, computed, onMounted } from 'vue' +import { useI18n } from 'vue-i18n' +import type { IJsonItem } from '../types' +import { getResourceList } from '@/service/modules/resources' + +export function useTaskGroup( + model: { [field: string]: any }, + projectCode: number +): IJsonItem[] { + const { t } = useI18n() + + const options = ref([]) + const loading = ref(false) + const priorityDisabled = computed(() => !model.taskGroupId) + + const getTaskGroupList = async () => { + if (loading.value) return + loading.value = true + const res = await getResourceList({ + accessType: 'TASK_GROUP', + projectCode + }) + options.value = res.map((item: { id: string; name: string }) => ({ + label: item.name, + value: item.id + })) + loading.value = false + } + + onMounted(() => { + getTaskGroupList() + }) + + watch( + () => model.taskGroupId, + (taskGroupId) => { + if (!taskGroupId) { + model.taskGroupId = null + model.taskGroupPriority = null + } + } + ) + + return [ + { + type: 'select', + field: 'taskGroupId', + span: 12, + name: t('project.node.task_group_name'), + props: { + loading: loading.value, + clearable: true + }, + options + }, + { + type: 'input-number', + field: 'taskGroupPriority', + name: t('project.node.task_group_queue_priority'), + props: { + max: Math.pow(10, 60) - 1, + disabled: priorityDisabled + }, + span: 12 + } + ] +} diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-task-priority.ts b/seatunnel-ui/src/views/task/components/node/fields/use-task-priority.ts new file mode 100644 index 000000000..64db58d9d --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-task-priority.ts @@ -0,0 +1,92 @@ +/* + * 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, markRaw, VNode, VNodeChild } from 'vue' +import { NIcon } from 'naive-ui' +import { useI18n } from 'vue-i18n' +import { ArrowUpOutlined, ArrowDownOutlined } from '@vicons/antd' +import type { ITaskPriorityOption, IJsonItem } from '../types' + +export function useTaskPriority(): IJsonItem { + const { t } = useI18n() + const options = markRaw([ + { + label: 'HIGHEST', + value: 'HIGHEST', + icon: ArrowUpOutlined, + color: '#ff0000' + }, + { + label: 'HIGH', + value: 'HIGH', + icon: ArrowUpOutlined, + color: '#ff0000' + }, + { + label: 'MEDIUM', + value: 'MEDIUM', + icon: ArrowUpOutlined, + color: '#EA7D24' + }, + { + label: 'LOW', + value: 'LOW', + icon: ArrowDownOutlined, + color: '#2A8734' + }, + { + label: 'LOWEST', + value: 'LOWEST', + icon: ArrowDownOutlined, + color: '#2A8734' + } + ]) + const renderOption = ({ + node, + option + }: { + node: VNode + option: ITaskPriorityOption + }): VNodeChild => + h(node, null, { + default: () => [ + h( + NIcon, + { + color: option.color + }, + { + default: () => h(option.icon) + } + ), + option.label as string + ] + }) + return { + type: 'select', + field: 'taskPriority', + name: t('project.node.task_priority'), + options, + validate: { + required: true + }, + props: { + renderOption + }, + value: 'MEDIUM' + } +} diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-task-type.ts b/seatunnel-ui/src/views/task/components/node/fields/use-task-type.ts new file mode 100644 index 000000000..4cbbde22b --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-task-type.ts @@ -0,0 +1,50 @@ +/* + * 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 { useI18n } from 'vue-i18n' +import { useTaskTypeStore } from '@/store/project' +import type { IJsonItem, ITaskTypeItem } from '../types' + +export function useTaskType( + model: { [field: string]: any }, + readonly?: boolean +): IJsonItem { + const { t } = useI18n() + const taskTypeStore = useTaskTypeStore() + + const options = taskTypeStore.getTaskType.map((option: ITaskTypeItem) => ({ + label: option.alias, + value: option.type, + disabled: !!option.taskDefinitionDisable + })) + return { + type: 'select', + field: 'taskType', + span: 24, + name: t('project.node.task_type'), + props: { + disabled: readonly || ['CONDITIONS', 'SWITCH'].includes(model.taskType) + }, + options: options, + validate: { + trigger: ['input', 'blur'], + required: true, + message: t('project.node.task_type_tips') + }, + value: model.taskType ? model.taskType : 'SHELL' + } +} diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-timeout-alarm.ts b/seatunnel-ui/src/views/task/components/node/fields/use-timeout-alarm.ts new file mode 100644 index 000000000..53b51bc4d --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-timeout-alarm.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. + */ + +import { computed } from 'vue' +import { useI18n } from 'vue-i18n' +import type { IJsonItem } from '../types' + +export function useTimeoutAlarm(model: { [field: string]: any }): IJsonItem[] { + const { t } = useI18n() + const span = computed(() => (model.timeoutFlag ? 12 : 0)) + + const strategyOptions = [ + { + label: t('project.node.timeout_alarm'), + value: 'WARN' + }, + { + label: t('project.node.timeout_failure'), + value: 'FAILED' + } + ] + + return [ + { + type: 'switch', + field: 'timeoutFlag', + name: t('project.node.timeout_alarm'), + props: { + 'on-update:value': (value: boolean) => { + if (value) { + if (!model.timeoutNotifyStrategy.length) + model.timeoutNotifyStrategy = ['WARN'] + if (!model.timeout) model.timeout = 30 + } + } + } + }, + { + type: 'checkbox', + field: 'timeoutNotifyStrategy', + name: t('project.node.timeout_strategy'), + options: strategyOptions, + span: span, + validate: { + trigger: ['input'], + validator(validate: any, value: []) { + if (model.timeoutFlag && !value.length) { + return new Error(t('project.node.timeout_strategy_tips')) + } + } + } + }, + { + type: 'input-number', + field: 'timeout', + name: t('project.node.timeout_period'), + span, + props: { + max: Math.pow(10, 10) - 1 + }, + slots: { + suffix: () => t('project.node.minute') + }, + validate: { + trigger: ['input'], + validator(validate: any, value: number) { + if (model.timeoutFlag && !/^[1-9]\d*$/.test(String(value))) { + return new Error(t('project.node.timeout_period_tips')) + } + } + } + } + ] +} diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-udfs.ts b/seatunnel-ui/src/views/task/components/node/fields/use-udfs.ts new file mode 100644 index 000000000..6ac971271 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-udfs.ts @@ -0,0 +1,59 @@ +/* + * 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 { ref, watch, computed } from 'vue' +import { useI18n } from 'vue-i18n' +import { queryUdfFuncList } from '@/service/modules/resources' +import type { IJsonItem } from '../types' + +export function useUdfs(model: { [field: string]: any }): IJsonItem { + const { t } = useI18n() + const options = ref([]) + const loading = ref(false) + const span = computed(() => (['HIVE', 'SPARK'].includes(model.type) ? 24 : 0)) + + const getUdfs = async () => { + if (loading.value) return + loading.value = true + const res = await queryUdfFuncList({ type: model.type }) + options.value = res.map((udf: { id: number; funcName: string }) => ({ + value: String(udf.id), + label: udf.funcName + })) + loading.value = false + } + + watch( + () => model.type, + (value) => { + if (['HIVE', 'SPARK'].includes(value)) { + getUdfs() + } + } + ) + + return { + type: 'select', + field: 'udfs', + options: options, + name: t('project.node.udf_function'), + props: { + multiple: true, + loading + }, + span + } +} diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-whale-sea-tunnel.ts b/seatunnel-ui/src/views/task/components/node/fields/use-whale-sea-tunnel.ts new file mode 100644 index 000000000..238f772aa --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-whale-sea-tunnel.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 { ref, onMounted } from 'vue' +import { useI18n } from 'vue-i18n' +import { useCustomParams } from '.' +import type { IJsonItem } from '../types' +import { querySeaTunnelList } from '@/service/modules/seatunnel' + +export function useWhaleTunnel( + model: { [field: string]: any }, + projectCode: number +): IJsonItem[] { + const { t } = useI18n() + const loading = ref(false) + const options = ref( + [] as { + label: string + value: string + }[] + ) + + const getWhaleTunnelList = async () => { + if (loading.value) return + loading.value = true + const res = await querySeaTunnelList({ + projectCode + }) + options.value = [] + res.map((item: any) => { + options.value.push({ + label: `${item.syncTaskType}-${item.taskName}`, + value: item.taskId + }) + }) + + loading.value = false + } + + onMounted(() => getWhaleTunnelList()) + + return [ + { + type: 'select', + field: 'taskId', + name: t('project.node.task_name'), + span: 22, + props: { + loading: loading, + filterable: true + }, + options, + validate: { + trigger: ['input', 'blur'], + required: true, + validator(unuse: any, value: number) { + if (!value) { + return Error(t('project.node.whale_seatunnel_task_tips')) + } + } + } + }, + { + type: 'switch', + field: 'breakContinue', + name: t('project.node.break_continue') + }, + ...useCustomParams({ model, field: 'localParams', isSimple: false }) + ] +} diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-worker-group.ts b/seatunnel-ui/src/views/task/components/node/fields/use-worker-group.ts new file mode 100644 index 000000000..8c1e884f3 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-worker-group.ts @@ -0,0 +1,63 @@ +/* + * 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 { ref, onMounted } from 'vue' +import { useI18n } from 'vue-i18n' +import type { IJsonItem } from '../types' +import { getResourceList } from '@/service/modules/resources' +import { useRoute } from 'vue-router' + +export function useWorkerGroup(): IJsonItem { + const { t } = useI18n() + + const options = ref([] as { label: string; value: string }[]) + const loading = ref(false) + + const route = useRoute() + const projectCode = Number(route.params.projectCode) + + const getWorkerGroups = async () => { + if (loading.value) return + loading.value = true + const res = await getResourceList({ + accessType: 'WORKER_GROUP', + projectCode + }) + options.value = res.map((item: string) => ({ label: item, value: item })) + loading.value = false + } + + onMounted(() => { + getWorkerGroups() + }) + return { + type: 'select', + field: 'workerGroup', + span: 12, + name: t('project.node.worker_group'), + props: { + loading: loading.value + }, + options: options, + validate: { + trigger: ['input', 'blur'], + required: true, + message: t('project.node.worker_group_tips') + }, + value: 'default' + } +} diff --git a/seatunnel-ui/src/views/task/components/node/fields/use-zeppelin.ts b/seatunnel-ui/src/views/task/components/node/fields/use-zeppelin.ts new file mode 100644 index 000000000..540b2a093 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/fields/use-zeppelin.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 { useI18n } from 'vue-i18n' +import { useCustomParams } from '.' +import type { IJsonItem } from '../types' + +export function useZeppelin(model: { [field: string]: any }): IJsonItem[] { + const { t } = useI18n() + + return [ + { + type: 'input', + field: 'noteId', + name: t('project.node.zeppelin_note_id'), + props: { + placeholder: t('project.node.zeppelin_note_id_tips') + }, + validate: { + trigger: ['input', 'blur'], + required: true, + validator(validate: any, value: string) { + if (!value) { + return new Error(t('project.node.zeppelin_note_id_tips')) + } + } + } + }, + { + type: 'input', + field: 'paragraphId', + name: t('project.node.zeppelin_paragraph_id'), + props: { + placeholder: t('project.node.zeppelin_paragraph_id_tips') + } + }, + { + type: 'input', + field: 'restEndpoint', + name: t('project.node.zeppelin_rest_endpoint'), + props: { + placeholder: t('project.node.zeppelin_rest_endpoint_tips') + }, + validate: { + trigger: ['input', 'blur'], + required: true, + validator(validate: any, value: string) { + if (!value) { + return new Error(t('project.node.zeppelin_rest_endpoint_tips')) + } + } + } + }, + { + type: 'input', + field: 'productionNoteDirectory', + name: t('project.node.zeppelin_production_note_directory'), + props: { + placeholder: t('project.node.zeppelin_production_note_directory_tips') + } + }, + { + type: 'input', + field: 'parameters', + name: t('project.node.zeppelin_parameters'), + props: { + placeholder: t('project.node.zeppelin_parameters_tips') + } + }, + ...useCustomParams({ model, field: 'localParams', isSimple: false }) + ] +} diff --git a/seatunnel-ui/src/views/task/components/node/format-data.ts b/seatunnel-ui/src/views/task/components/node/format-data.ts new file mode 100644 index 000000000..bd5ebf9ff --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/format-data.ts @@ -0,0 +1,770 @@ +/* + * 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 { omit, cloneDeep } from 'lodash' +import type { + INodeData, + ITaskData, + ITaskParams, + ISqoopTargetParams, + ISqoopSourceParams, + ILocalParam, + IDependTask +} from './types' +import { fieldMap } from './use-task-config' + +export function formatParams(data: INodeData): { + processDefinitionCode: string + upstreamCodes: string + taskDefinitionJsonObj: object +} { + const taskParams: ITaskParams = {} + if ( + data.taskType && + ['SHELL', 'PYTHON', 'SEATUNNEL', 'NEXT_LOOP', 'SPARK'].includes( + data.taskType + ) + ) { + taskParams.rawScript = data.script + taskParams.fieldStr = fieldMap.rawScript + } + + if (data.taskType === 'SHELL' || data.taskType === 'PYTHON') { + taskParams.dataSourceList = data.dataSourceList?.filter( + (item) => item.type && item.datasource + ) + } + + if (data.taskType === 'SUB_PROCESS') { + taskParams.processDefinitionCode = data.processDefinitionCode + } + if ( + data.taskType === 'SPARK' || + data.taskType === 'MR' || + data.taskType === 'FLINK' + ) { + taskParams.programType = data.programType + taskParams.mainClass = data.mainClass + if (data.mainJar) { + taskParams.mainJar = { id: data.mainJar } + } + taskParams.deployMode = data.deployMode + taskParams.appName = data.appName + taskParams.mainArgs = data.mainArgs + taskParams.others = data.others + } + + if (data.taskType === 'SPARK') { + taskParams.driverCores = data.driverCores + taskParams.driverMemory = data.driverMemory + taskParams.numExecutors = data.numExecutors + taskParams.executorMemory = data.executorMemory + taskParams.executorCores = data.executorCores + } + + if (data.taskType === 'FLINK') { + taskParams.flinkVersion = data.flinkVersion + taskParams.jobManagerMemory = data.jobManagerMemory + taskParams.taskManagerMemory = data.taskManagerMemory + taskParams.slot = data.slot + taskParams.taskManager = data.taskManager + taskParams.parallelism = data.parallelism + } + if (data.taskType === 'HTTP') { + taskParams.httpMethod = data.httpMethod + taskParams.httpCheckCondition = data.httpCheckCondition + taskParams.httpParams = data.httpParams + taskParams.url = data.url + taskParams.condition = data.condition + taskParams.connectTimeout = data.connectTimeout + taskParams.socketTimeout = data.socketTimeout + } + + if (data.taskType === 'SQOOP') { + taskParams.jobType = data.isCustomTask ? 'CUSTOM' : 'TEMPLATE' + taskParams.localParams = data.localParams + if (data.isCustomTask) { + // customShell + taskParams.customShell = data.script + taskParams.fieldStr = fieldMap.SQOOP + } else { + taskParams.jobName = data.jobName + taskParams.hadoopCustomParams = data.hadoopCustomParams + taskParams.sqoopAdvancedParams = data.sqoopAdvancedParams + taskParams.concurrency = data.concurrency + taskParams.modelType = data.modelType + taskParams.sourceType = data.sourceType + taskParams.targetType = data.targetType + let targetParams: ISqoopTargetParams = {} + let sourceParams: ISqoopSourceParams = {} + switch (data.targetType) { + case 'HIVE': + targetParams = { + hiveDatabase: data.targetHiveDatabase, + hiveTable: data.targetHiveTable, + createHiveTable: data.targetHiveCreateTable, + dropDelimiter: data.targetHiveDropDelimiter, + hiveOverWrite: data.targetHiveOverWrite, + hiveTargetDir: data.targetHiveTargetDir, + replaceDelimiter: data.targetHiveReplaceDelimiter, + hivePartitionKey: data.targetHivePartitionKey, + hivePartitionValue: data.targetHivePartitionValue + } + break + case 'HDFS': + targetParams = { + targetPath: data.targetHdfsTargetPath, + deleteTargetDir: data.targetHdfsDeleteTargetDir, + compressionCodec: data.targetHdfsCompressionCodec, + fileType: data.targetHdfsFileType, + fieldsTerminated: data.targetHdfsFieldsTerminated, + linesTerminated: data.targetHdfsLinesTerminated + } + break + case 'MYSQL': + targetParams = { + targetType: data.targetMysqlType, + targetDatasource: data.targetMysqlDatasource, + targetTable: data.targetMysqlTable, + targetColumns: data.targetMysqlColumns, + fieldsTerminated: data.targetMysqlFieldsTerminated, + linesTerminated: data.targetMysqlLinesTerminated, + isUpdate: data.targetMysqlIsUpdate, + targetUpdateKey: data.targetMysqlTargetUpdateKey, + targetUpdateMode: data.targetMysqlUpdateMode + } + break + default: + break + } + switch (data.sourceType) { + case 'MYSQL': + sourceParams = { + srcTable: data.srcQueryType === '1' ? '' : data.srcTable, + srcColumnType: data.srcQueryType === '1' ? '0' : data.srcColumnType, + srcColumns: + data.srcQueryType === '1' || data.srcColumnType === '0' + ? '' + : data.srcColumns, + srcQuerySql: data.srcQueryType === '0' ? '' : data.script, + srcQueryType: data.srcQueryType, + srcType: data.sourceMysqlType, + srcDatasource: data.sourceMysqlDatasource, + mapColumnHive: data.mapColumnHive, + mapColumnJava: data.mapColumnJava + } + break + case 'HDFS': + sourceParams = { + exportDir: data.sourceHdfsExportDir + } + break + case 'HIVE': + sourceParams = { + hiveDatabase: data.sourceHiveDatabase, + hiveTable: data.sourceHiveTable, + hivePartitionKey: data.sourceHivePartitionKey, + hivePartitionValue: data.sourceHivePartitionValue + } + break + default: + break + } + taskParams.targetParams = JSON.stringify(targetParams) + taskParams.sourceParams = JSON.stringify(sourceParams) + } + } + + if (data.taskType === 'SQL') { + taskParams.type = data.type + taskParams.datasource = data.datasource + taskParams.sql = data.script + taskParams.fieldStr = fieldMap.SQL + taskParams.sqlType = data.sqlType + taskParams.preStatements = data.preStatements + taskParams.postStatements = data.postStatements + taskParams.sendEmail = data.sendEmail + taskParams.displayRows = data.displayRows + if (data.sqlType === '0' && data.sendEmail) { + taskParams.title = data.title + taskParams.groupId = data.groupId + } + if (data.type === 'HIVE') { + if (data.udfs) taskParams.udfs = data.udfs.join(',') + taskParams.connParams = data.connParams + } + } + + if (data.taskType === 'PROCEDURE') { + taskParams.type = data.type + taskParams.datasource = data.datasource + taskParams.method = data.script + taskParams.fieldStr = fieldMap.PROCEDURE + } + + if (data.taskType === 'SEATUNNEL') { + taskParams.engine = data.engine + taskParams.useCustom = data.useCustom + taskParams.others = data.others + } + + if (data.taskType === 'WHALE_SEATUNNEL') { + taskParams.taskId = data.taskId + taskParams.breakContinue = data.breakContinue + } + + if (data.taskType === 'SWITCH') { + taskParams.switchResult = {} + taskParams.switchResult.dependTaskList = data.dependTaskList + taskParams.switchResult.nextNode = data.nextNode + } + + if (data.taskType === 'CONDITIONS') { + taskParams.dependence = { + relation: data.relation, + dependTaskList: data.dependTaskList + } + taskParams.conditionResult = {} + if (data.successBranch) { + taskParams.conditionResult.successNode = [data.successBranch] + } + if (data.failedBranch) { + taskParams.conditionResult.failedNode = [data.failedBranch] + } + } + + if (data.taskType === 'DATAX') { + taskParams.customConfig = data.customConfig ? 1 : 0 + if (taskParams.customConfig === 0) { + taskParams.dsType = data.dsType + taskParams.dataSource = data.dataSource + taskParams.dtType = data.dtType + taskParams.dataTarget = data.dataTarget + taskParams.sql = data.script + taskParams.fieldStr = fieldMap.DATAX_IF + taskParams.targetTable = data.targetTable + taskParams.jobSpeedByte = data.jobSpeedByte + taskParams.jobSpeedRecord = data.jobSpeedRecord + taskParams.preStatements = data.preStatements + taskParams.postStatements = data.postStatements + } else { + taskParams.json = data.script + taskParams.fieldStr = fieldMap.DATAX_ELSE + data?.localParams?.map((param: ILocalParam) => { + param.direct = 'IN' + param.type = 'VARCHAR' + }) + } + taskParams.xms = data.xms + taskParams.xmx = data.xmx + } + if (data.taskType === 'DEPENDENT') { + const dependTaskList = cloneDeep(data.dependTaskList)?.map( + (taskItem: IDependTask) => { + if (taskItem.dependItemList?.length) { + taskItem.dependItemList.forEach((dependItem) => { + delete dependItem.definitionCodeOptions + delete dependItem.depTaskCodeOptions + delete dependItem.dateOptions + delete dependItem.cardValue + }) + } + return taskItem + } + ) + taskParams.dependence = { + relation: data.relation, + dependTaskList: dependTaskList + } + taskParams.otherParams = { + dependStrategy: data.dependStrategy + } + } + if (data.taskType === 'DATA_QUALITY') { + taskParams.ruleId = data.ruleId + taskParams.ruleInputParameter = { + check_type: data.check_type, + comparison_execute_sql: data.comparison_execute_sql, + comparison_type: data.comparison_type, + comparison_name: data.comparison_name, + failure_strategy: data.failure_strategy, + operator: data.operator, + src_connector_type: data.src_connector_type, + src_datasource_id: data.src_datasource_id, + field_length: data.field_length, + begin_time: data.begin_time, + deadline: data.deadline, + datetime_format: data.datetime_format, + enum_list: data.enum_list, + regexp_pattern: data.regexp_pattern, + target_filter: data.target_filter, + src_filter: data.src_filter, + src_field: data.src_field, + src_table: data.src_table, + statistics_execute_sql: data.statistics_execute_sql, + statistics_name: data.statistics_name, + target_connector_type: data.target_connector_type, + target_datasource_id: data.target_datasource_id, + target_table: data.target_table, + threshold: data.threshold, + logic_operator: data.logic_operator, + mapping_columns: JSON.stringify(data.mapping_columns) + } + taskParams.sparkParameters = { + deployMode: data.deployMode, + driverCores: data.driverCores, + driverMemory: data.driverMemory, + executorCores: data.executorCores, + executorMemory: data.executorMemory, + numExecutors: data.numExecutors, + others: data.others + } + } + + if (data.taskType === 'EMR') { + taskParams.type = data.type + taskParams.programType = data.programType + if (data.programType === 'RUN_JOB_FLOW') { + taskParams.jobFlowDefineJson = data.script + taskParams.fieldStr = fieldMap.RUN_JOB_FLOW_IF + } else { + taskParams.stepsDefineJson = data.script + taskParams.fieldStr = fieldMap.RUN_JOB_FLOW_ELSE + } + } + if (data.taskType === 'ZEPPELIN') { + taskParams.noteId = data.noteId + taskParams.paragraphId = data.paragraphId + taskParams.restEndpoint = data.restEndpoint + taskParams.productionNoteDirectory = data.productionNoteDirectory + taskParams.parameters = data.parameters + } + if (data.taskType === 'JUPYTER') { + taskParams.condaEnvName = data.condaEnvName + taskParams.inputNotePath = data.inputNotePath + taskParams.outputNotePath = data.outputNotePath + taskParams.parameters = data.parameters + taskParams.kernel = data.kernel + taskParams.engine = data.engine + taskParams.executionTimeout = data.executionTimeout + taskParams.startTimeout = data.startTimeout + taskParams.others = data.others + } + if (data.taskType === 'MLFLOW') { + taskParams.algorithm = data.algorithm + taskParams.params = data.params + taskParams.searchParams = data.searchParams + taskParams.dataPath = data.dataPath + taskParams.experimentName = data.experimentName + taskParams.modelName = data.modelName + taskParams.mlflowTrackingUri = data.mlflowTrackingUri + taskParams.mlflowJobType = data.mlflowJobType + taskParams.automlTool = data.automlTool + taskParams.registerModel = data.registerModel + taskParams.mlflowTaskType = data.mlflowTaskType + taskParams.deployType = data.deployType + taskParams.deployPort = data.deployPort + taskParams.deployModelKey = data.deployModelKey + taskParams.mlflowProjectRepository = data.mlflowProjectRepository + taskParams.mlflowProjectVersion = data.mlflowProjectVersion + } + if (data.taskType === 'DVC') { + taskParams.dvcTaskType = data.dvcTaskType + taskParams.dvcRepository = data.dvcRepository + taskParams.dvcVersion = data.dvcVersion + taskParams.dvcDataLocation = data.dvcDataLocation + taskParams.dvcMessage = data.dvcMessage + taskParams.dvcLoadSaveDataPath = data.dvcLoadSaveDataPath + taskParams.dvcStoreUrl = data.dvcStoreUrl + } + if (data.taskType === 'OPENMLDB') { + taskParams.zk = data.zk + taskParams.zkPath = data.zkPath + taskParams.executeMode = data.executeMode + taskParams.sql = data.script + taskParams.fieldStr = fieldMap.OPENMLDB + } + if (data.taskType === 'SAGEMAKER') { + taskParams.sagemakerRequestJson = data.script + taskParams.fieldStr = fieldMap.SAGEMAKER + } + if (data.taskType === 'PYTORCH') { + taskParams.script = data.script + taskParams.fieldStr = fieldMap.PYTORCH + taskParams.scriptParams = data.scriptParams + taskParams.pythonPath = data.pythonPath + taskParams.isCreateEnvironment = data.isCreateEnvironment + taskParams.pythonCommand = data.pythonCommand + taskParams.pythonEnvTool = data.pythonEnvTool + taskParams.requirements = data.requirements + taskParams.condaPythonVersion = data.condaPythonVersion + taskParams.showOtherParams = data.showOtherParams + } + if (data.taskType === 'PIGEON') { + taskParams.targetJobName = data.targetJobName + } + if (data.taskType === 'SURVEIL') { + taskParams.interval = data.interval + taskParams.scanType = data.scanType + if (taskParams.scanType === 1) { + taskParams.filePath = data.filePath + } + if (taskParams.scanType === 4) { + taskParams.filePath = data.filePath + } + if (taskParams.scanType === 2) { + taskParams.type = data.type + taskParams.datasource = data.datasource + taskParams.sql = data.script + taskParams.fieldStr = fieldMap.SURVEIL_TYPE2 + taskParams.sqlType = '2' + } + if (taskParams.scanType === 3) { + taskParams.topic = data.topic + taskParams.bootstrapServers = data.bootstrapServers + taskParams.groupId = data.groupId + taskParams.keyDeserializer = data.keyDeserializer + taskParams.valueDeserializer = data.valueDeserializer + taskParams.offsetTime = data.offsetTime + } + } + if (data.taskType === 'NEXT_LOOP') { + taskParams.customConfig = data.customConfig ? 1 : 0 + taskParams.programType = data.programType + taskParams.cardCode = data.cardCode + } + if (data.taskType === 'INFORMATICA') { + taskParams.informaticaFolderName = data.informaticaFolderName + taskParams.informaticaWorkflowName = data.informaticaWorkflowName + } + if (data.taskType === 'HIVECLI') { + taskParams.hiveCliTaskExecutionType = data.hiveCliTaskExecutionType + taskParams.hiveSqlScript = data.script + taskParams.fieldStr = fieldMap.HIVECLI + taskParams.hiveCliOptions = data.hiveCliOptions + } + if (data.taskType === 'DINKY') { + taskParams.address = data.address + taskParams.taskId = data.taskId + taskParams.online = data.online + } + + if (data.taskType === 'CHUNJUN') { + taskParams.customConfig = data.customConfig ? 1 : 0 + taskParams.json = data.script + taskParams.fieldStr = fieldMap.CHUNJUN + taskParams.deployMode = data.deployMode + taskParams.others = data.others + } + + if (data.taskType === 'JAR') { + taskParams.programType = data.programType + taskParams.options = data.options + taskParams.cpResIds = data.cpResIds + taskParams.mainClass = data.mainClass + if (data.mainJar) { + taskParams.mainJar = { id: data.mainJar } + } + taskParams.mainArgs = data.mainArgs + } + + let timeoutNotifyStrategy = '' + if (data.timeoutNotifyStrategy) { + if (data.timeoutNotifyStrategy.length === 1) { + timeoutNotifyStrategy = data.timeoutNotifyStrategy[0] + } + if (data.timeoutNotifyStrategy.length === 2) { + timeoutNotifyStrategy = 'WARNFAILED' + } + } + + if (data.open) { + taskParams.remoteConnection = { + open: data.open, + datasourceType: data.datasourceType, + datasourceId: data.datasourceId + } + } + + const params = { + processDefinitionCode: data.processName ? String(data.processName) : '', + upstreamCodes: data?.preTasks?.join(','), + taskDefinitionJsonObj: { + code: data.code, + delayTime: data.delayTime ? String(data.delayTime) : '0', + description: data.description, + environmentCode: data.environmentCode || -1, + failRetryInterval: data.failRetryInterval + ? String(data.failRetryInterval) + : '0', + failRetryTimes: data.failRetryTimes ? String(data.failRetryTimes) : '0', + flag: data.flag, + name: data.name, + taskGroupId: data.taskGroupId, + taskGroupPriority: data.taskGroupPriority, + resourceId: data?.resourceId || null, + taskParams: { + localParams: data.localParams + ?.filter((item: any) => item.prop) + .map((item: any) => { + item.value = item.value || '' + return item + }), + rawScript: data.rawScript, + resourceList: data.resourceList?.length + ? data.resourceList.map((id: number) => ({ id })) + : [], + ...taskParams, + ideContentField: { + resourceId: data?.resourceId || null, + field: taskParams.fieldStr || null + } + }, + taskPriority: data.taskPriority, + taskType: data.taskType, + timeout: data.timeoutFlag ? data.timeout : 0, + timeoutFlag: data.timeoutFlag ? 'OPEN' : 'CLOSE', + timeoutNotifyStrategy: data.timeoutFlag ? timeoutNotifyStrategy : '', + workerGroup: data.workerGroup + } + } as { + processDefinitionCode: string + upstreamCodes: string + taskDefinitionJsonObj: { + timeout: number + timeoutNotifyStrategy: string + taskProcessType?: string + } + } + if (!data.timeoutFlag) { + params.taskDefinitionJsonObj.timeout = 0 + params.taskDefinitionJsonObj.timeoutNotifyStrategy = '' + } + + return params +} + +export function formatModel(data: ITaskData) { + const params = { + ...omit(data, [ + 'environmentCode', + 'timeoutFlag', + 'timeoutNotifyStrategy', + 'taskParams' + ]), + ...omit(data.taskParams, ['resourceList', 'mainJar', 'localParams']), + environmentCode: data.environmentCode === -1 ? null : data.environmentCode, + timeoutFlag: data.timeoutFlag === 'OPEN', + timeoutNotifyStrategy: data.timeoutNotifyStrategy + ? [data.timeoutNotifyStrategy] + : [], + localParams: data.taskParams?.localParams || [] + } as INodeData + + if (data.timeoutNotifyStrategy === 'WARNFAILED') { + params.timeoutNotifyStrategy = ['WARN', 'FAILED'] + } + if (data.taskParams?.resourceList) { + params.resourceList = data.taskParams.resourceList.map( + (item: { id: number }) => item.id + ) + } + if (data.taskParams?.mainJar) { + params.mainJar = data.taskParams?.mainJar.id + } + + if (data.taskParams?.method) { + params.method = data.taskParams?.method + } + + if (data.taskParams?.targetParams) { + const targetParams: ISqoopTargetParams = JSON.parse( + data.taskParams.targetParams + ) + params.targetHiveDatabase = targetParams.hiveDatabase + params.targetHiveTable = targetParams.hiveTable + params.targetHiveCreateTable = targetParams.createHiveTable + params.targetHiveDropDelimiter = targetParams.dropDelimiter + params.targetHiveOverWrite = + targetParams.hiveOverWrite === void 0 ? true : targetParams.hiveOverWrite + params.targetHiveTargetDir = targetParams.hiveTargetDir + params.targetHiveReplaceDelimiter = targetParams.replaceDelimiter + params.targetHivePartitionKey = targetParams.hivePartitionKey + params.targetHivePartitionValue = targetParams.hivePartitionValue + params.targetHdfsTargetPath = targetParams.targetPath + params.targetHdfsDeleteTargetDir = + targetParams.deleteTargetDir === void 0 + ? true + : targetParams.deleteTargetDir + params.targetHdfsCompressionCodec = + targetParams.compressionCodec === void 0 + ? 'snappy' + : targetParams.compressionCodec + params.targetHdfsFileType = + targetParams.fileType === void 0 + ? '--as-avrodatafile' + : targetParams.fileType + params.targetHdfsFieldsTerminated = targetParams.fieldsTerminated + params.targetHdfsLinesTerminated = targetParams.linesTerminated + params.targetMysqlType = targetParams.targetType + params.targetMysqlDatasource = targetParams.targetDatasource + params.targetMysqlTable = targetParams.targetTable + params.targetMysqlColumns = targetParams.targetColumns + params.targetMysqlFieldsTerminated = targetParams.fieldsTerminated + params.targetMysqlLinesTerminated = targetParams.linesTerminated + params.targetMysqlIsUpdate = targetParams.isUpdate + params.targetMysqlTargetUpdateKey = targetParams.targetUpdateKey + params.targetMysqlUpdateMode = + targetParams.targetUpdateMode === void 0 + ? 'allowinsert' + : targetParams.targetUpdateMode + } + if (data.taskParams?.sourceParams) { + const sourceParams: ISqoopSourceParams = JSON.parse( + data.taskParams.sourceParams + ) + params.srcTable = sourceParams.srcTable + params.srcColumnType = sourceParams.srcColumnType + params.srcColumns = sourceParams.srcColumns + params.script = sourceParams.srcQuerySql + params.srcQueryType = sourceParams.srcQueryType + params.sourceMysqlType = sourceParams.srcType + params.sourceMysqlDatasource = sourceParams.srcDatasource + params.mapColumnHive = sourceParams.mapColumnHive || [] + params.mapColumnJava = sourceParams.mapColumnJava || [] + params.sourceHdfsExportDir = sourceParams.exportDir + params.sourceHiveDatabase = sourceParams.hiveDatabase + params.sourceHiveTable = sourceParams.hiveTable + params.sourceHivePartitionKey = sourceParams.hivePartitionKey + params.sourceHivePartitionValue = sourceParams.hivePartitionValue + } + + if (data.taskParams?.hiveSqlScript) { + params.script = data.taskParams?.hiveSqlScript + } + + if (data.taskParams?.switchResult) { + params.switchResult = data.taskParams.switchResult + params.dependTaskList = data.taskParams.switchResult?.dependTaskList + ? data.taskParams.switchResult?.dependTaskList + : [] + params.nextNode = data.taskParams.switchResult?.nextNode + } + + if (data.taskParams?.dependence) { + params.dependTaskList = data.taskParams?.dependence.dependTaskList || [] + params.relation = data.taskParams?.dependence.relation + } + if (data.taskParams?.ruleInputParameter) { + params.check_type = data.taskParams.ruleInputParameter.check_type + params.comparison_execute_sql = + data.taskParams.ruleInputParameter.comparison_execute_sql + params.comparison_type = data.taskParams.ruleInputParameter.comparison_type + params.comparison_name = data.taskParams.ruleInputParameter.comparison_name + params.failure_strategy = + data.taskParams.ruleInputParameter.failure_strategy + params.operator = data.taskParams.ruleInputParameter.operator + params.src_connector_type = + data.taskParams.ruleInputParameter.src_connector_type + params.src_datasource_id = + data.taskParams.ruleInputParameter.src_datasource_id + params.src_table = data.taskParams.ruleInputParameter.src_table + params.field_length = data.taskParams.ruleInputParameter.field_length + params.begin_time = data.taskParams.ruleInputParameter.begin_time + params.deadline = data.taskParams.ruleInputParameter.deadline + params.datetime_format = data.taskParams.ruleInputParameter.datetime_format + params.target_filter = data.taskParams.ruleInputParameter.target_filter + params.regexp_pattern = data.taskParams.ruleInputParameter.regexp_pattern + params.enum_list = data.taskParams.ruleInputParameter.enum_list + params.src_filter = data.taskParams.ruleInputParameter.src_filter + params.src_field = data.taskParams.ruleInputParameter.src_field + params.statistics_execute_sql = + data.taskParams.ruleInputParameter.statistics_execute_sql + params.statistics_name = data.taskParams.ruleInputParameter.statistics_name + params.target_connector_type = + data.taskParams.ruleInputParameter.target_connector_type + params.target_datasource_id = + data.taskParams.ruleInputParameter.target_datasource_id + params.target_table = data.taskParams.ruleInputParameter.target_table + params.threshold = data.taskParams.ruleInputParameter.threshold + params.logic_operator = data.taskParams.ruleInputParameter.logic_operator + if (data.taskParams.ruleInputParameter.mapping_columns) + params.mapping_columns = JSON.parse( + data.taskParams.ruleInputParameter.mapping_columns + ) + } + if (data.taskParams?.sparkParameters) { + params.deployMode = data.taskParams.sparkParameters.deployMode + params.driverCores = data.taskParams.sparkParameters.driverCores + params.driverMemory = data.taskParams.sparkParameters.driverMemory + params.executorCores = data.taskParams.sparkParameters.executorCores + params.executorMemory = data.taskParams.sparkParameters.executorMemory + params.numExecutors = data.taskParams.sparkParameters.numExecutors + params.others = data.taskParams.sparkParameters.others + } + + if (data.taskParams?.jobFlowDefineJson) { + params.script = data.taskParams.jobFlowDefineJson + } + + if (data.taskParams?.sagemakerRequestJson) { + params.script = data.taskParams.sagemakerRequestJson + } + + if (data.taskParams?.processDefinitionCode) { + params.processDefinitionCode = data.taskParams.processDefinitionCode + } + + if (data.taskParams?.conditionResult?.successNode?.length) { + params.successBranch = data.taskParams.conditionResult.successNode[0] + } + if (data.taskParams?.conditionResult?.failedNode?.length) { + params.failedBranch = data.taskParams.conditionResult.failedNode[0] + } + if (data.taskParams?.udfs) { + params.udfs = data.taskParams.udfs?.split(',') + } + if (data.taskParams?.customConfig !== void 0) { + params.customConfig = data.taskParams.customConfig === 1 ? true : false + } + if (data.taskParams?.jobType) { + params.isCustomTask = data.taskParams.jobType === 'CUSTOM' + } + if (data.taskParams?.otherParams?.dependStrategy) { + params.dependStrategy = data.taskParams.otherParams.dependStrategy + } + if (data.taskParams?.rawScript) { + params.script = data.taskParams.rawScript + } + if (data.taskParams?.method) { + params.script = data.taskParams.method + } + if (data.taskParams?.sql) { + params.script = data.taskParams.sql + } + if (data.taskType === 'CHUNJUN' && data.taskParams?.json) { + params.script = data.taskParams.json + } + + if (data.taskParams?.remoteConnection) { + params.open = data.taskParams.remoteConnection.open + params.datasourceType = data.taskParams.remoteConnection.datasourceType + params.datasourceId = data.taskParams.remoteConnection.datasourceId + } + + return params +} diff --git a/seatunnel-ui/src/views/task/components/node/index.module.scss b/seatunnel-ui/src/views/task/components/node/index.module.scss new file mode 100644 index 000000000..841c75848 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/index.module.scss @@ -0,0 +1,62 @@ +/* + * 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. + */ + +.field-title { + font-weight: bold; + width: 100%; + + &::before { + content: ''; + display: inline-block; + vertical-align: -2px; + width: 4px; + height: 1em; + background-color: var(--n-color); + border-radius: 4px; + margin-right: 8px; + } +} +.relaction-label { + height: 30px; + overflow: hidden; +} +.relaction-switch { + position: relative; + cursor: pointer; + display: flex; + align-items: center; + &::before { + content: ''; + display: block; + width: 4px; + height: 100%; + background-color: var(--n-color); + border-radius: 4px; + position: absolute; + top: 0px; + left: 20px; + } +} +.display-rows-tips { + margin-top: 32px; +} +.question-icon { + cursor: pointer; + vertical-align: -5px; + margin-left: 8px; + color: var(--n-color); +} diff --git a/seatunnel-ui/src/views/task/components/node/tasks/index.ts b/seatunnel-ui/src/views/task/components/node/tasks/index.ts new file mode 100644 index 000000000..c8e54e7d9 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/tasks/index.ts @@ -0,0 +1,86 @@ +/* + * 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 { useFlink } from './use-flink' +import { useShell } from './use-shell' +import { useSubProcess } from './use-sub-process' +import { usePigeon } from './use-pigeon' +import { usePython } from './use-python' +import { useSpark } from './use-spark' +import { useMr } from './use-mr' +import { useHttp } from './use-http' +import { useSql } from './use-sql' +import { useProcedure } from './use-procedure' +import { useSqoop } from './use-sqoop' +import { useSeaTunnel } from './use-sea-tunnel' +import { useWhaleSeaTunnel } from './use-whale-sea-tunnel' +import { useSwitch } from './use-switch' +import { useConditions } from './use-conditions' +import { useDataX } from './use-datax' +import { useDependent } from './use-dependent' +import { useDataQuality } from './use-data-quality' +import { useEmr } from './use-emr' +import { useSurveil } from './use-surveil' +import { useCard } from './use-card' +import { useHiveCli } from './use-hive-cli' +import { useDinky } from './use-dinky' +import { useZeppelin } from './use-zeppelin' +import { useJupyter } from './use-jupyter' +import { useMlflow } from './use-mlflow' +import { useOpenmldb } from './use-openmldb' +import { useDvc } from './use-dvc' +import { useSagemaker } from './use-sagemaker' +import { usePytorch } from './use-pytorch' +import { useChunjun } from './use-chunjun' +import { useJar } from './use-jar' +import { useInformatica } from './use-informatica' + +export default { + SHELL: useShell, + SUB_PROCESS: useSubProcess, + PYTHON: usePython, + SPARK: useSpark, + MR: useMr, + FLINK: useFlink, + HTTP: useHttp, + PIGEON: usePigeon, + SQL: useSql, + PROCEDURE: useProcedure, + SQOOP: useSqoop, + SEATUNNEL: useSeaTunnel, + WHALE_SEATUNNEL: useWhaleSeaTunnel, + SWITCH: useSwitch, + CONDITIONS: useConditions, + DATAX: useDataX, + DEPENDENT: useDependent, + DATA_QUALITY: useDataQuality, + EMR: useEmr, + SURVEIL: useSurveil, + NEXT_LOOP: useCard, + ZEPPELIN: useZeppelin, + HIVECLI: useHiveCli, + DINKY: useDinky, + CHUNJUN: useChunjun, + JUPYTER: useJupyter, + MLFLOW: useMlflow, + OPENMLDB: useOpenmldb, + DVC: useDvc, + SAGEMAKER: useSagemaker, + PYTORCH: usePytorch, + JAR: useJar, + INFORMATICA: useInformatica +} diff --git a/seatunnel-ui/src/views/task/components/node/tasks/use-card.ts b/seatunnel-ui/src/views/task/components/node/tasks/use-card.ts new file mode 100644 index 000000000..9ef2b0b6a --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/tasks/use-card.ts @@ -0,0 +1,89 @@ +/* + * 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 * as Fields from '../fields/index' +import type { IJsonItem, INodeData, ITaskData } from '../types' +import { useRouter } from 'vue-router' + +export function useCard({ + projectCode, + from = 0, + readonly, + data, + updateValue +}: { + projectCode: number + from?: number + readonly?: boolean + data?: ITaskData + updateValue?: (value: any, field: string) => void +}) { + const router = useRouter() + const workflowCode = + router.currentRoute.value.params.code || + router.currentRoute.value.query.code + const model = reactive({ + taskType: 'NEXT_LOOP', + name: '', + flag: 'YES', + description: '', + timeoutFlag: false, + timeoutNotifyStrategy: ['WARN'], + timeout: 30, + customConfig: false, + programType: 'SHELL', + localParams: [], + environmentCode: null, + workerGroup: 'default', + delayTime: 0, + rawScript: '', + cardCode: null + } as INodeData) + + let extra: IJsonItem[] = [] + if (from === 1) { + extra = [ + Fields.useTaskType(model, readonly), + Fields.useProcessName({ + model, + projectCode, + isCreate: !data?.id, + from, + processName: data?.processName + }) + ] + } + + return { + json: [ + Fields.useName(from), + ...extra, + Fields.useRunFlag(), + Fields.useDescription(), + Fields.useTaskPriority(), + Fields.useWorkerGroup(), + Fields.useEnvironmentName(model, !data?.id), + ...Fields.useTaskGroup(model, projectCode), + Fields.useDelayTime(model), + ...Fields.useTimeoutAlarm(model), + ...Fields.useNextLoop(model, projectCode, Number(workflowCode), updateValue), + Fields.usePreTasks(model) + ] as IJsonItem[], + model + } +} diff --git a/seatunnel-ui/src/views/task/components/node/tasks/use-chunjun.ts b/seatunnel-ui/src/views/task/components/node/tasks/use-chunjun.ts new file mode 100644 index 000000000..6a8462cde --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/tasks/use-chunjun.ts @@ -0,0 +1,86 @@ +/* + * 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 * as Fields from '../fields/index' +import type { IJsonItem, INodeData } from '../types' +import { ITaskData } from '../types' + +export function useChunjun({ + projectCode, + from = 0, + readonly, + data +}: { + projectCode: number + from?: number + readonly?: boolean + data?: ITaskData +}) { + const model = reactive({ + name: '', + taskType: 'CHUNJUN', + flag: 'YES', + description: '', + deployMode: 'local', + timeoutFlag: false, + localParams: [], + environmentCode: null, + failRetryInterval: 1, + failRetryTimes: 0, + workerGroup: 'default', + delayTime: 0, + timeout: 30, + customConfig: true, + preStatements: [], + postStatements: [], + timeoutNotifyStrategy: ['WARN'] + } as INodeData) + + let extra: IJsonItem[] = [] + if (from === 1) { + extra = [ + Fields.useTaskType(model, readonly), + Fields.useProcessName({ + model, + projectCode, + isCreate: !data?.id, + from, + processName: data?.processName + }) + ] + } + + return { + json: [ + Fields.useName(from), + ...extra, + Fields.useRunFlag(), + Fields.useDescription(), + Fields.useTaskPriority(), + Fields.useWorkerGroup(), + Fields.useEnvironmentName(model, !model.id), + ...Fields.useTaskGroup(model, projectCode), + ...Fields.useFailed(), + Fields.useDelayTime(model), + ...Fields.useTimeoutAlarm(model), + ...Fields.useChunjun(model), + Fields.usePreTasks(model) + ] as IJsonItem[], + model + } +} diff --git a/seatunnel-ui/src/views/task/components/node/tasks/use-conditions.ts b/seatunnel-ui/src/views/task/components/node/tasks/use-conditions.ts new file mode 100644 index 000000000..bb33f73d1 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/tasks/use-conditions.ts @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { reactive } from 'vue' +import * as Fields from '../fields/index' +import type { IJsonItem, INodeData, ITaskData } from '../types' + +export function useConditions({ + projectCode, + from = 0, + readonly, + data +}: { + projectCode: number + from?: number + readonly?: boolean + data?: ITaskData +}) { + const model = reactive({ + taskType: 'CONDITIONS', + name: '', + flag: 'YES', + description: '', + timeoutFlag: false, + localParams: [], + failRetryInterval: 1, + failRetryTimes: 0, + timeout: 30, + relation: 'AND', + dependTaskList: [], + preTasks: [], + successNode: 'success', + failedNode: 'failed' + } as INodeData) + + let extra: IJsonItem[] = [] + if (from === 1) { + extra = [ + Fields.useTaskType(model, readonly), + Fields.useProcessName({ + model, + projectCode, + isCreate: !data?.id, + from, + processName: data?.processName + }) + ] + } + + return { + json: [ + Fields.useName(from), + ...extra, + Fields.useRunFlag(), + Fields.useDescription(), + Fields.useTaskPriority(), + ...Fields.useFailed(), + ...Fields.useConditions(model), + Fields.usePreTasks(model) + ] as IJsonItem[], + model + } +} diff --git a/seatunnel-ui/src/views/task/components/node/tasks/use-data-quality.ts b/seatunnel-ui/src/views/task/components/node/tasks/use-data-quality.ts new file mode 100644 index 000000000..012992b6a --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/tasks/use-data-quality.ts @@ -0,0 +1,130 @@ +/* + * 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 { Ref, reactive } from 'vue' +import { useI18n } from 'vue-i18n' +import * as Fields from '../fields/index' +import type { IJsonItem, INodeData, ITaskData } from '../types' + +export function useDataQuality({ + projectCode, + from = 0, + readonly, + data, + jsonRef, + updateElements +}: { + projectCode: number + from?: number + readonly?: boolean + data?: ITaskData + jsonRef: Ref + updateElements: () => void +}) { + const { t } = useI18n() + const model = reactive({ + taskType: 'DATA_QUALITY', + name: '', + flag: 'YES', + description: '', + timeoutFlag: false, + timeoutNotifyStrategy: ['WARN'], + timeout: 30, + localParams: [], + environmentCode: null, + failRetryInterval: 1, + failRetryTimes: 0, + workerGroup: 'default', + delayTime: 0, + ruleId: 1, + deployMode: 'local', + driverCores: 1, + driverMemory: '512M', + numExecutors: 2, + executorMemory: '2G', + executorCores: 2, + others: + '--conf spark.yarn.maxAppAttempts=1 --class org.apache.dolphinscheduler.data.quality.DataQualityApplication' + } as INodeData) + + let extra: IJsonItem[] = [] + if (from === 1) { + extra = [ + Fields.useTaskType(model, readonly), + Fields.useProcessName({ + model, + projectCode, + isCreate: !data?.id, + from, + processName: data?.processName + }) + ] + } + + return { + json: [ + Fields.useName(from), + ...extra, + Fields.useRunFlag(), + Fields.useDescription(), + Fields.useTaskPriority(), + Fields.useWorkerGroup(), + Fields.useEnvironmentName(model, !data?.id), + ...Fields.useTaskGroup(model, projectCode), + ...Fields.useFailed(), + Fields.useDelayTime(model), + ...Fields.useTimeoutAlarm(model), + ...Fields.useRules(model, (items: IJsonItem[], len: number) => { + // delete rule key in model of before + jsonRef.value + .slice(15, 15 + len) + .forEach( + (item) => + delete model[ + (typeof item === 'function' + ? item().field + : item.field) as keyof typeof model + ] + ) + jsonRef.value.splice(15, len, ...items) + updateElements() + }), + Fields.useDeployMode(), + Fields.useDriverCores(), + Fields.useDriverMemory(), + Fields.useExecutorNumber(), + Fields.useExecutorMemory(), + Fields.useExecutorCores(), + { + type: 'input', + field: 'others', + name: t('project.node.option_parameters'), + props: { + type: 'textarea', + placeholder: t('project.node.option_parameters_tips') + } + }, + ...Fields.useCustomParams({ + model, + field: 'localParams', + isSimple: false + }), + Fields.usePreTasks(model) + ] as IJsonItem[], + model + } +} diff --git a/seatunnel-ui/src/views/task/components/node/tasks/use-datax.ts b/seatunnel-ui/src/views/task/components/node/tasks/use-datax.ts new file mode 100644 index 000000000..f62b16ee0 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/tasks/use-datax.ts @@ -0,0 +1,86 @@ +/* + * 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 * as Fields from '../fields/index' +import type { IJsonItem, INodeData } from '../types' +import { ITaskData } from '../types' + +export function useDataX({ + projectCode, + from = 0, + readonly, + data, + updateValue +}: { + projectCode: number + from?: number + readonly?: boolean + data?: ITaskData + updateValue?: (value: any, field: string) => void +}) { + const model = reactive({ + name: '', + taskType: 'DATAX', + flag: 'YES', + description: '', + timeoutFlag: false, + localParams: [], + environmentCode: null, + failRetryInterval: 1, + failRetryTimes: 0, + workerGroup: 'default', + delayTime: 0, + timeout: 30, + customConfig: false, + preStatements: [], + postStatements: [] + } as INodeData) + + let extra: IJsonItem[] = [] + if (from === 1) { + extra = [ + Fields.useTaskType(model, readonly), + Fields.useProcessName({ + model, + projectCode, + isCreate: !data?.id, + from, + processName: data?.processName + }) + ] + } + + return { + json: [ + Fields.useName(from), + ...extra, + Fields.useRunFlag(), + Fields.useDescription(), + Fields.useTaskPriority(), + Fields.useWorkerGroup(), + Fields.useEnvironmentName(model, !data?.id), + ...Fields.useTaskGroup(model, projectCode), + ...Fields.useFailed(), + Fields.useDelayTime(model), + ...Fields.useTimeoutAlarm(model), + ...Fields.useDataX(model, updateValue), + Fields.usePreTasks(model) + ] as IJsonItem[], + model + } +} diff --git a/seatunnel-ui/src/views/task/components/node/tasks/use-dependent.ts b/seatunnel-ui/src/views/task/components/node/tasks/use-dependent.ts new file mode 100644 index 000000000..9ade7cc0b --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/tasks/use-dependent.ts @@ -0,0 +1,82 @@ +/* + * 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 * as Fields from '../fields/index' +import type { IJsonItem, INodeData, ITaskData } from '../types' + +export function useDependent({ + projectCode, + from = 0, + readonly, + data, + setting +}: { + projectCode: number + from?: number + readonly?: boolean + data?: ITaskData + setting?: any +}) { + const model = reactive({ + taskType: 'DEPENDENT', + name: '', + flag: 'YES', + description: '', + timeoutShowFlag: false, + localParams: [], + failRetryInterval: 1, + failRetryTimes: 0, + delayTime: 0, + relation: 'AND', + dependTaskList: [], + preTasks: [], + timeoutNotifyStrategy: [], + timeout: 30, + timeoutFlag: false, + dependStrategy: 'failure-continue', + ...data + } as INodeData) + + let extra: IJsonItem[] = [] + if (from === 1) { + extra = [ + Fields.useTaskType(model, readonly), + Fields.useProcessName({ + model, + projectCode, + isCreate: !data?.id, + from, + processName: data?.processName + }) + ] + } + + return { + json: [ + Fields.useName(from), + ...extra, + Fields.useRunFlag(), + Fields.useDescription(), + Fields.useTaskPriority(), + ...Fields.useFailed(), + ...Fields.useDependent(model, setting), + Fields.usePreTasks(model) + ] as IJsonItem[], + model + } +} diff --git a/seatunnel-ui/src/views/task/components/node/tasks/use-dinky.ts b/seatunnel-ui/src/views/task/components/node/tasks/use-dinky.ts new file mode 100644 index 000000000..f0e7b1069 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/tasks/use-dinky.ts @@ -0,0 +1,81 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { reactive } from 'vue' +import * as Fields from '../fields/index' +import type { IJsonItem, INodeData, ITaskData } from '../types' + +export function useDinky({ + projectCode, + from = 0, + readonly, + data +}: { + projectCode: number + from?: number + readonly?: boolean + data?: ITaskData +}) { + const model = reactive({ + name: '', + taskType: 'DINKY', + flag: 'YES', + description: '', + timeoutFlag: false, + localParams: [], + environmentCode: null, + failRetryInterval: 1, + failRetryTimes: 0, + workerGroup: 'default', + delayTime: 0, + timeout: 30, + timeoutNotifyStrategy: ['WARN'] + } as INodeData) + + let extra: IJsonItem[] = [] + if (from === 1) { + extra = [ + Fields.useTaskType(model, readonly), + Fields.useProcessName({ + model, + projectCode, + isCreate: !data?.id, + from, + processName: data?.processName + }) + ] + } + + return { + json: [ + Fields.useName(from), + ...extra, + Fields.useRunFlag(), + Fields.useDescription(), + Fields.useTaskPriority(), + Fields.useWorkerGroup(), + Fields.useEnvironmentName(model, !data?.id), + ...Fields.useTaskGroup(model, projectCode), + ...Fields.useFailed(), + Fields.useDelayTime(model), + ...Fields.useTimeoutAlarm(model), + ...Fields.useDinky(model), + Fields.usePreTasks(model) + ] as IJsonItem[], + model + } +} diff --git a/seatunnel-ui/src/views/task/components/node/tasks/use-dvc.ts b/seatunnel-ui/src/views/task/components/node/tasks/use-dvc.ts new file mode 100644 index 000000000..61e909c54 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/tasks/use-dvc.ts @@ -0,0 +1,82 @@ +/* + * 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 * as Fields from '../fields/index' +import type { IJsonItem, INodeData, ITaskData } from '../types' + +export function useDvc({ + projectCode, + from = 0, + readonly, + data +}: { + projectCode: number + from?: number + readonly?: boolean + data?: ITaskData +}) { + const model = reactive({ + name: '', + taskType: 'MLFLOW', + flag: 'YES', + description: '', + timeoutFlag: false, + localParams: [], + environmentCode: null, + failRetryInterval: 1, + failRetryTimes: 0, + workerGroup: 'default', + delayTime: 0, + timeout: 30, + timeoutNotifyStrategy: ['WARN'], + dvcTaskType: 'Upload' + } as INodeData) + + let extra: IJsonItem[] = [] + if (from === 1) { + extra = [ + Fields.useTaskType(model, readonly), + Fields.useProcessName({ + model, + projectCode, + isCreate: !data?.id, + from, + processName: data?.processName + }) + ] + } + + return { + json: [ + Fields.useName(from), + ...extra, + Fields.useRunFlag(), + Fields.useDescription(), + Fields.useTaskPriority(), + Fields.useWorkerGroup(), + Fields.useEnvironmentName(model, !model.id), + ...Fields.useTaskGroup(model, projectCode), + ...Fields.useFailed(), + Fields.useDelayTime(model), + ...Fields.useTimeoutAlarm(model), + ...Fields.useDvc(model), + Fields.usePreTasks(model) + ] as IJsonItem[], + model + } +} diff --git a/seatunnel-ui/src/views/task/components/node/tasks/use-emr.ts b/seatunnel-ui/src/views/task/components/node/tasks/use-emr.ts new file mode 100644 index 000000000..314a60278 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/tasks/use-emr.ts @@ -0,0 +1,82 @@ +/* + * 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 * as Fields from '../fields/index' +import type { IJsonItem, INodeData } from '../types' +import { ITaskData } from '../types' + +export function useEmr({ + projectCode, + from = 0, + readonly, + data +}: { + projectCode: number + from?: number + readonly?: boolean + data?: ITaskData +}) { + const model = reactive({ + name: '', + taskType: 'EMR', + flag: 'YES', + description: '', + timeoutFlag: false, + localParams: [], + environmentCode: null, + failRetryInterval: 1, + failRetryTimes: 0, + workerGroup: 'default', + delayTime: 0, + timeout: 30, + programType: 'ADD_JOB_FLOW_STEPS' + } as INodeData) + + let extra: IJsonItem[] = [] + if (from === 1) { + extra = [ + Fields.useTaskType(model, readonly), + Fields.useProcessName({ + model, + projectCode, + isCreate: !data?.id, + from, + processName: data?.processName + }) + ] + } + + return { + json: [ + Fields.useName(from), + ...extra, + Fields.useRunFlag(), + Fields.useDescription(), + Fields.useTaskPriority(), + Fields.useWorkerGroup(), + Fields.useEnvironmentName(model, !data?.id), + ...Fields.useTaskGroup(model, projectCode), + ...Fields.useFailed(), + Fields.useDelayTime(model), + ...Fields.useTimeoutAlarm(model), + ...Fields.useEmr(model), + Fields.usePreTasks(model) + ] as IJsonItem[], + model + } +} diff --git a/seatunnel-ui/src/views/task/components/node/tasks/use-flink.ts b/seatunnel-ui/src/views/task/components/node/tasks/use-flink.ts new file mode 100644 index 000000000..ad1c36280 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/tasks/use-flink.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. + */ + +import { reactive } from 'vue' +import * as Fields from '../fields/index' +import type { IJsonItem, INodeData, ITaskData } from '../types' + +export function useFlink({ + projectCode, + from = 0, + readonly, + data +}: { + projectCode: number + from?: number + readonly?: boolean + data?: ITaskData +}) { + const model = reactive({ + taskType: 'FLINK', + name: '', + flag: 'YES', + description: '', + timeoutFlag: false, + localParams: [], + environmentCode: null, + failRetryInterval: 1, + failRetryTimes: 0, + workerGroup: 'default', + delayTime: 0, + timeout: 30, + programType: 'SCALA', + deployMode: 'cluster', + flinkVersion: '<1.10', + jobManagerMemory: '1G', + taskManagerMemory: '2G', + slot: 1, + taskManager: 2, + parallelism: 1 + }) + + let extra: IJsonItem[] = [] + if (from === 1) { + extra = [ + Fields.useTaskType(model, readonly), + Fields.useProcessName({ + model, + projectCode, + isCreate: !data?.id, + from, + processName: data?.processName + }) + ] + } + + return { + json: [ + Fields.useName(from), + ...extra, + Fields.useRunFlag(), + Fields.useDescription(), + Fields.useTaskPriority(), + Fields.useWorkerGroup(), + Fields.useEnvironmentName(model, !data?.id), + ...Fields.useTaskGroup(model, projectCode), + ...Fields.useFailed(), + Fields.useDelayTime(model), + ...Fields.useTimeoutAlarm(model), + ...Fields.useFlink(model), + Fields.usePreTasks(model) + ] as IJsonItem[], + model + } +} diff --git a/seatunnel-ui/src/views/task/components/node/tasks/use-hive-cli.ts b/seatunnel-ui/src/views/task/components/node/tasks/use-hive-cli.ts new file mode 100644 index 000000000..e562f891a --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/tasks/use-hive-cli.ts @@ -0,0 +1,80 @@ +/* + * 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 * as Fields from '../fields/index' +import type { IJsonItem, INodeData, ITaskData } from '../types' + +export function useHiveCli({ + projectCode, + from = 0, + readonly, + data +}: { + projectCode: number + from?: number + readonly?: boolean + data?: ITaskData +}) { + const model = reactive({ + name: '', + taskType: 'HIVECLI', + flag: 'YES', + description: '', + timeoutFlag: false, + localParams: [], + environmentCode: null, + failRetryInterval: 1, + failRetryTimes: 0, + workerGroup: 'default', + delayTime: 0, + timeout: 30 + } as INodeData) + + let extra: IJsonItem[] = [] + if (from === 1) { + extra = [ + Fields.useTaskType(model, readonly), + Fields.useProcessName({ + model, + projectCode, + isCreate: !data?.id, + from, + processName: data?.processName + }) + ] + } + + return { + json: [ + Fields.useName(from), + ...extra, + Fields.useRunFlag(), + Fields.useDescription(), + Fields.useTaskPriority(), + Fields.useWorkerGroup(), + Fields.useEnvironmentName(model, !model.id), + ...Fields.useTaskGroup(model, projectCode), + ...Fields.useFailed(), + Fields.useDelayTime(model), + ...Fields.useTimeoutAlarm(model), + ...Fields.useHiveCli(model), + Fields.usePreTasks(model) + ] as IJsonItem[], + model + } +} diff --git a/seatunnel-ui/src/views/task/components/node/tasks/use-http.ts b/seatunnel-ui/src/views/task/components/node/tasks/use-http.ts new file mode 100644 index 000000000..fa6b29efa --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/tasks/use-http.ts @@ -0,0 +1,87 @@ +/* + * 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 * as Fields from '../fields/index' +import type { IJsonItem, INodeData, ITaskData } from '../types' + +export function useHttp({ + projectCode, + from = 0, + readonly, + data +}: { + projectCode: number + from?: number + readonly?: boolean + data?: ITaskData +}) { + const model = reactive({ + taskType: 'HTTP', + name: '', + flag: 'YES', + description: '', + timeoutFlag: false, + localParams: [], + environmentCode: null, + failRetryInterval: 1, + failRetryTimes: 0, + workerGroup: 'default', + delayTime: 0, + timeout: 30, + httpMethod: 'GET', + httpCheckCondition: 'STATUS_CODE_DEFAULT', + httpParams: [], + url: '', + condition: '', + connectTimeout: 60000, + socketTimeout: 60000 + } as INodeData) + + let extra: IJsonItem[] = [] + if (from === 1) { + extra = [ + Fields.useTaskType(model, readonly), + Fields.useProcessName({ + model, + projectCode, + isCreate: !data?.id, + from, + processName: data?.processName + }) + ] + } + + return { + json: [ + Fields.useName(from), + ...extra, + Fields.useRunFlag(), + Fields.useDescription(), + Fields.useTaskPriority(), + Fields.useWorkerGroup(), + Fields.useEnvironmentName(model, !data?.id), + ...Fields.useTaskGroup(model, projectCode), + ...Fields.useFailed(), + Fields.useDelayTime(model), + ...Fields.useTimeoutAlarm(model), + ...Fields.useHttp(model), + Fields.usePreTasks(model) + ] as IJsonItem[], + model + } +} diff --git a/seatunnel-ui/src/views/task/components/node/tasks/use-informatica.ts b/seatunnel-ui/src/views/task/components/node/tasks/use-informatica.ts new file mode 100644 index 000000000..155a594b4 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/tasks/use-informatica.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 { reactive } from 'vue' +import type { IJsonItem, ITaskData, INodeData } from '../types' +import * as Fields from '../fields/index' + +//use-informatica + +export function useInformatica({ + projectCode, + from = 0, + readonly, + data +}: { + projectCode: number + from?: number + readonly?: boolean + data?: ITaskData +}) { + const model = reactive({ + taskType: 'INFORMATICA', + name: '', + flag: 'YES', + description: '', + timeoutFlag: false, + timeoutNotifyStrategy: ['WARN'], + timeout: 30, + localParams: [], + environmentCode: null, + failRetryInterval: 1, + failRetryTimes: 0, + workerGroup: 'default', + delayTime: 0, + informaticaFolderName: null, + informaticaWorkflowName: null + } as INodeData) + + let extra: IJsonItem[] = [] + if (from === 1) { + extra = [ + Fields.useTaskType(model, readonly), + Fields.useProcessName({ + model, + projectCode, + isCreate: !data?.id, + from, + processName: data?.processName + }) + ] + } + + return { + json: [ + Fields.useName(from), + ...extra, + Fields.useRunFlag(), + Fields.useDescription(), + Fields.useTaskPriority(), + Fields.useWorkerGroup(), + Fields.useEnvironmentName(model, !data?.id), + ...Fields.useTaskGroup(model, projectCode), + ...Fields.useFailed(), + Fields.useDelayTime(model), + ...Fields.useTimeoutAlarm(model), + ...Fields.useInformatica(), + Fields.usePreTasks(model) + ] as IJsonItem[], + model + } +} diff --git a/seatunnel-ui/src/views/task/components/node/tasks/use-jar.ts b/seatunnel-ui/src/views/task/components/node/tasks/use-jar.ts new file mode 100644 index 000000000..ca0006c1c --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/tasks/use-jar.ts @@ -0,0 +1,81 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { reactive } from 'vue' +import * as Fields from '../fields/index' +import type { IJsonItem, INodeData, ITaskData } from '../types' + +export function useJar({ + projectCode, + from = 0, + readonly, + data +}: { + projectCode: number + from?: number + readonly?: boolean + data?: ITaskData +}) { + const model = reactive({ + taskType: 'JAR', + name: '', + flag: 'YES', + description: '', + timeoutFlag: false, + localParams: [], + environmentCode: null, + failRetryInterval: 1, + failRetryTimes: 0, + workerGroup: 'default', + delayTime: 0, + timeout: 30, + programType: 'CLASS' + } as INodeData) + + let extra: IJsonItem[] = [] + if (from === 1) { + extra = [ + Fields.useTaskType(model, readonly), + Fields.useProcessName({ + model, + projectCode, + isCreate: !data?.id, + from, + processName: data?.processName + }) + ] + } + + return { + json: [ + Fields.useName(from), + ...extra, + Fields.useRunFlag(), + Fields.useDescription(), + Fields.useTaskPriority(), + Fields.useWorkerGroup(), + Fields.useEnvironmentName(model, !data?.id), + ...Fields.useTaskGroup(model, projectCode), + ...Fields.useFailed(), + Fields.useDelayTime(model), + ...Fields.useTimeoutAlarm(model), + ...Fields.useJar(model), + Fields.usePreTasks(model) + ] as IJsonItem[], + model + } +} diff --git a/seatunnel-ui/src/views/task/components/node/tasks/use-jupyter.ts b/seatunnel-ui/src/views/task/components/node/tasks/use-jupyter.ts new file mode 100644 index 000000000..5ccea2368 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/tasks/use-jupyter.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 { reactive } from 'vue' +import * as Fields from '../fields/index' +import type { IJsonItem, INodeData, ITaskData } from '../types' + +export function useJupyter({ + projectCode, + from = 0, + readonly, + data +}: { + projectCode: number + from?: number + readonly?: boolean + data?: ITaskData +}) { + const model = reactive({ + name: '', + taskType: 'JUPYTER', + flag: 'YES', + description: '', + timeoutFlag: false, + localParams: [], + environmentCode: null, + failRetryInterval: 1, + failRetryTimes: 0, + workerGroup: 'default', + cpuQuota: -1, + memoryMax: -1, + delayTime: 0, + timeout: 30, + timeoutNotifyStrategy: ['WARN'] + } as INodeData) + + let extra: IJsonItem[] = [] + if (from === 1) { + extra = [ + Fields.useTaskType(model, readonly), + Fields.useProcessName({ + model, + projectCode, + isCreate: !data?.id, + from, + processName: data?.processName + }) + ] + } + + return { + json: [ + Fields.useName(from), + ...extra, + Fields.useRunFlag(), + Fields.useDescription(), + Fields.useTaskPriority(), + Fields.useWorkerGroup(), + Fields.useEnvironmentName(model, !model.id), + ...Fields.useTaskGroup(model, projectCode), + ...Fields.useFailed(), + Fields.useDelayTime(model), + ...Fields.useTimeoutAlarm(model), + ...Fields.useJupyter(model), + Fields.usePreTasks(model) + ] as IJsonItem[], + model + } +} diff --git a/seatunnel-ui/src/views/task/components/node/tasks/use-mlflow.ts b/seatunnel-ui/src/views/task/components/node/tasks/use-mlflow.ts new file mode 100644 index 000000000..645af2645 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/tasks/use-mlflow.ts @@ -0,0 +1,89 @@ +/* + * 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 * as Fields from '../fields/index' +import type { IJsonItem, INodeData, ITaskData } from '../types' + +export function useMlflow({ + projectCode, + from = 0, + readonly, + data +}: { + projectCode: number + from?: number + readonly?: boolean + data?: ITaskData +}) { + const model = reactive({ + name: '', + taskType: 'MLFLOW', + flag: 'YES', + description: '', + timeoutFlag: false, + localParams: [], + environmentCode: null, + failRetryInterval: 1, + failRetryTimes: 0, + workerGroup: 'default', + algorithm: 'svm', + mlflowTrackingUri: 'http://127.0.0.1:5000', + mlflowTaskType: 'MLflow Projects', + deployType: 'MLFLOW', + deployPort: '7000', + mlflowJobType: 'CustomProject', + automlTool: 'flaml', + mlflowCustomProjectParameters: [], + delayTime: 0, + timeout: 30, + timeoutNotifyStrategy: ['WARN'] + } as INodeData) + + let extra: IJsonItem[] = [] + if (from === 1) { + extra = [ + Fields.useTaskType(model, readonly), + Fields.useProcessName({ + model, + projectCode, + isCreate: !data?.id, + from, + processName: data?.processName + }) + ] + } + + return { + json: [ + Fields.useName(from), + ...extra, + Fields.useRunFlag(), + Fields.useDescription(), + Fields.useTaskPriority(), + Fields.useWorkerGroup(), + Fields.useEnvironmentName(model, !model.id), + ...Fields.useTaskGroup(model, projectCode), + ...Fields.useFailed(), + Fields.useDelayTime(model), + ...Fields.useTimeoutAlarm(model), + ...Fields.useMlflow(model), + Fields.usePreTasks(model) + ] as IJsonItem[], + model + } +} diff --git a/seatunnel-ui/src/views/task/components/node/tasks/use-mr.ts b/seatunnel-ui/src/views/task/components/node/tasks/use-mr.ts new file mode 100644 index 000000000..685b3425c --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/tasks/use-mr.ts @@ -0,0 +1,81 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { reactive } from 'vue' +import * as Fields from '../fields/index' +import type { IJsonItem, INodeData, ITaskData } from '../types' + +export function useMr({ + projectCode, + from = 0, + readonly, + data +}: { + projectCode: number + from?: number + readonly?: boolean + data?: ITaskData +}) { + const model = reactive({ + taskType: 'MR', + name: '', + flag: 'YES', + description: '', + timeoutFlag: false, + localParams: [], + environmentCode: null, + failRetryInterval: 1, + failRetryTimes: 0, + workerGroup: 'default', + delayTime: 0, + timeout: 30, + programType: 'SCALA' + } as INodeData) + + let extra: IJsonItem[] = [] + if (from === 1) { + extra = [ + Fields.useTaskType(model, readonly), + Fields.useProcessName({ + model, + projectCode, + isCreate: !data?.id, + from, + processName: data?.processName + }) + ] + } + + return { + json: [ + Fields.useName(from), + ...extra, + Fields.useRunFlag(), + Fields.useDescription(), + Fields.useTaskPriority(), + Fields.useWorkerGroup(), + Fields.useEnvironmentName(model, !data?.id), + ...Fields.useTaskGroup(model, projectCode), + ...Fields.useFailed(), + Fields.useDelayTime(model), + ...Fields.useTimeoutAlarm(model), + ...Fields.useMr(model), + Fields.usePreTasks(model) + ] as IJsonItem[], + model + } +} diff --git a/seatunnel-ui/src/views/task/components/node/tasks/use-openmldb.ts b/seatunnel-ui/src/views/task/components/node/tasks/use-openmldb.ts new file mode 100644 index 000000000..29f76859e --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/tasks/use-openmldb.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 { reactive } from 'vue' +import * as Fields from '../fields/index' +import type { IJsonItem, INodeData } from '../types' +import { ITaskData } from '../types' + +export function useOpenmldb({ + projectCode, + from = 0, + readonly, + data +}: { + projectCode: number + from?: number + readonly?: boolean + data?: ITaskData +}) { + const model = reactive({ + name: '', + taskType: 'OPENMLDB', + flag: 'YES', + description: '', + timeoutFlag: false, + timeoutNotifyStrategy: ['WARN'], + localParams: [], + environmentCode: null, + failRetryInterval: 1, + failRetryTimes: 0, + workerGroup: 'default', + delayTime: 0, + timeout: 30, + zk: '', + zkPath: '', + executeMode: 'offline' + } as INodeData) + + let extra: IJsonItem[] = [] + if (from === 1) { + extra = [ + Fields.useTaskType(model, readonly), + Fields.useProcessName({ + model, + projectCode, + isCreate: !data?.id, + from, + processName: data?.processName + }) + ] + } + + return { + json: [ + Fields.useName(from), + ...extra, + Fields.useRunFlag(), + Fields.useDescription(), + Fields.useTaskPriority(), + Fields.useWorkerGroup(), + Fields.useEnvironmentName(model, !model.id), + ...Fields.useTaskGroup(model, projectCode), + ...Fields.useFailed(), + Fields.useDelayTime(model), + ...Fields.useTimeoutAlarm(model), + ...Fields.useOpenmldb(model), + Fields.usePreTasks(model) + ] as IJsonItem[], + model + } +} diff --git a/seatunnel-ui/src/views/task/components/node/tasks/use-pigeon.ts b/seatunnel-ui/src/views/task/components/node/tasks/use-pigeon.ts new file mode 100644 index 000000000..9c8c955ae --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/tasks/use-pigeon.ts @@ -0,0 +1,80 @@ +/* + * 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 * as Fields from '../fields/index' +import type { IJsonItem, INodeData, ITaskData } from '../types' + +export function usePigeon({ + projectCode, + from = 0, + readonly, + data +}: { + projectCode: number + from?: number + readonly?: boolean + data?: ITaskData +}) { + const model = reactive({ + taskType: 'PIGEON', + name: '', + flag: 'YES', + description: '', + timeoutFlag: false, + environmentCode: null, + failRetryInterval: 1, + failRetryTimes: 0, + workerGroup: 'default', + delayTime: 0, + timeout: 30, + targetJobName: '' + } as INodeData) + + let extra: IJsonItem[] = [] + if (from === 1) { + extra = [ + Fields.useTaskType(model, readonly), + Fields.useProcessName({ + model, + projectCode, + isCreate: !data?.id, + from, + processName: data?.processName + }) + ] + } + + return { + json: [ + Fields.useName(from), + ...extra, + Fields.useRunFlag(), + Fields.useDescription(), + Fields.useTaskPriority(), + Fields.useWorkerGroup(), + Fields.useEnvironmentName(model, !data?.id), + ...Fields.useTaskGroup(model, projectCode), + ...Fields.useFailed(), + Fields.useDelayTime(model), + ...Fields.useTimeoutAlarm(model), + Fields.useTargetTaskName(), + Fields.usePreTasks(model) + ] as IJsonItem[], + model + } +} diff --git a/seatunnel-ui/src/views/task/components/node/tasks/use-procedure.ts b/seatunnel-ui/src/views/task/components/node/tasks/use-procedure.ts new file mode 100644 index 000000000..84dbbaca8 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/tasks/use-procedure.ts @@ -0,0 +1,89 @@ +/* + * 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 * as Fields from '../fields/index' +import type { IJsonItem, INodeData } from '../types' +import { ITaskData } from '../types' + +export function useProcedure({ + projectCode, + from = 0, + readonly, + data +}: { + projectCode: number + from?: number + readonly?: boolean + data?: ITaskData +}) { + const model = reactive({ + name: '', + taskType: 'PROCEDURE', + flag: 'YES', + description: '', + timeoutFlag: false, + localParams: [], + environmentCode: null, + failRetryInterval: 1, + failRetryTimes: 0, + workerGroup: 'default', + delayTime: 0, + timeout: 30, + // type: data?.taskParams?.type ? data?.taskParams?.type : 'MYSQL', + datasource: data?.taskParams?.datasource, + method: data?.taskParams?.method + } as INodeData) + + let extra: IJsonItem[] = [] + if (from === 1) { + extra = [ + Fields.useTaskType(model, readonly), + Fields.useProcessName({ + model, + projectCode, + isCreate: !data?.id, + from, + processName: data?.processName + }) + ] + } + + return { + json: [ + Fields.useName(from), + ...extra, + Fields.useRunFlag(), + Fields.useDescription(), + Fields.useTaskPriority(), + Fields.useWorkerGroup(), + Fields.useEnvironmentName(model, !data?.id), + ...Fields.useTaskGroup(model, projectCode), + ...Fields.useFailed(), + Fields.useDelayTime(model), + ...Fields.useTimeoutAlarm(model), + ...Fields.useDatasource({ model }), + ...Fields.useCustomParams({ + model, + field: 'localParams', + isSimple: false + }), + Fields.usePreTasks(model) + ] as IJsonItem[], + model + } +} diff --git a/seatunnel-ui/src/views/task/components/node/tasks/use-python.ts b/seatunnel-ui/src/views/task/components/node/tasks/use-python.ts new file mode 100644 index 000000000..4706611db --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/tasks/use-python.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 { reactive } from 'vue' +import * as Fields from '../fields/index' +import type { IJsonItem, INodeData } from '../types' +import { ITaskData } from '../types' + +export function usePython({ + projectCode, + from = 0, + readonly, + data +}: { + projectCode: number + from?: number + readonly?: boolean + data?: ITaskData +}) { + const model = reactive({ + name: '', + taskType: 'PYTHON', + flag: 'YES', + description: '', + timeoutFlag: false, + localParams: [], + dataSourceList: [], + environmentCode: null, + failRetryInterval: 1, + failRetryTimes: 0, + workerGroup: 'default', + delayTime: 0, + timeout: 30, + rawScript: '' + } as INodeData) + + let extra: IJsonItem[] = [] + if (from === 1) { + extra = [ + Fields.useTaskType(model, readonly), + Fields.useProcessName({ + model, + projectCode, + isCreate: !data?.id, + from, + processName: data?.processName + }) + ] + } + + return { + json: [ + Fields.useName(from), + ...extra, + Fields.useRunFlag(), + Fields.useDescription(), + Fields.useTaskPriority(), + Fields.useWorkerGroup(), + Fields.useEnvironmentName(model, !data?.id), + ...Fields.useTaskGroup(model, projectCode), + ...Fields.useFailed(), + Fields.useDelayTime(model), + ...Fields.useTimeoutAlarm(model), + ...Fields.useShell(model, from), + Fields.usePreTasks(model) + ] as IJsonItem[], + model + } +} diff --git a/seatunnel-ui/src/views/task/components/node/tasks/use-pytorch.ts b/seatunnel-ui/src/views/task/components/node/tasks/use-pytorch.ts new file mode 100644 index 000000000..7ad772887 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/tasks/use-pytorch.ts @@ -0,0 +1,86 @@ +/* + * 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 * as Fields from '../fields/index' +import type { IJsonItem, INodeData, ITaskData } from '../types' + +export function usePytorch({ + projectCode, + from = 0, + readonly, + data +}: { + projectCode: number + from?: number + readonly?: boolean + data?: ITaskData +}) { + const model = reactive({ + name: '', + taskType: 'PYTORCH', + flag: 'YES', + description: '', + timeoutFlag: false, + localParams: [], + environmentCode: null, + failRetryInterval: 1, + failRetryTimes: 0, + workerGroup: 'default', + delayTime: 0, + timeout: 30, + timeoutNotifyStrategy: ['WARN'], + pythonEnvTool: 'conda', + pythonCommand: '${PYTHON_HOME}', + condaPythonVersion: '3.7', + requirements: 'requirements.txt', + pythonPath: '.' + } as INodeData) + + let extra: IJsonItem[] = [] + if (from === 1) { + extra = [ + Fields.useTaskType(model, readonly), + Fields.useProcessName({ + model, + projectCode, + isCreate: !data?.id, + from, + processName: data?.processName + }) + ] + } + + return { + json: [ + Fields.useName(from), + ...extra, + Fields.useRunFlag(), + Fields.useDescription(), + Fields.useTaskPriority(), + Fields.useWorkerGroup(), + Fields.useEnvironmentName(model, !model.id), + ...Fields.useTaskGroup(model, projectCode), + ...Fields.useFailed(), + Fields.useDelayTime(model), + ...Fields.useTimeoutAlarm(model), + ...Fields.usePytorch(model), + Fields.usePreTasks(model) + ] as IJsonItem[], + model + } +} diff --git a/seatunnel-ui/src/views/task/components/node/tasks/use-sagemaker.ts b/seatunnel-ui/src/views/task/components/node/tasks/use-sagemaker.ts new file mode 100644 index 000000000..48c65f157 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/tasks/use-sagemaker.ts @@ -0,0 +1,81 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { reactive } from 'vue' +import * as Fields from '../fields/index' +import type { IJsonItem, INodeData, ITaskData } from '../types' + +export function useSagemaker({ + projectCode, + from = 0, + readonly, + data +}: { + projectCode: number + from?: number + readonly?: boolean + data?: ITaskData +}) { + const model = reactive({ + name: '', + taskType: 'SAGEMAKER', + flag: 'YES', + description: '', + timeoutFlag: false, + localParams: [], + environmentCode: null, + failRetryInterval: 1, + failRetryTimes: 0, + workerGroup: 'default', + delayTime: 0, + timeout: 30, + timeoutNotifyStrategy: ['WARN'] + } as INodeData) + + let extra: IJsonItem[] = [] + if (from === 1) { + extra = [ + Fields.useTaskType(model, readonly), + Fields.useProcessName({ + model, + projectCode, + isCreate: !data?.id, + from, + processName: data?.processName + }) + ] + } + + return { + json: [ + Fields.useName(from), + ...extra, + Fields.useRunFlag(), + Fields.useDescription(), + Fields.useTaskPriority(), + Fields.useWorkerGroup(), + Fields.useEnvironmentName(model, !model.id), + ...Fields.useTaskGroup(model, projectCode), + ...Fields.useFailed(), + Fields.useDelayTime(model), + ...Fields.useTimeoutAlarm(model), + ...Fields.useSagemaker(model), + Fields.usePreTasks(model) + ] as IJsonItem[], + model + } +} diff --git a/seatunnel-ui/src/views/task/components/node/tasks/use-sea-tunnel.ts b/seatunnel-ui/src/views/task/components/node/tasks/use-sea-tunnel.ts new file mode 100644 index 000000000..b8e784f7f --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/tasks/use-sea-tunnel.ts @@ -0,0 +1,108 @@ +/* + * 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 * as Fields from '../fields/index' +import type { IJsonItem, INodeData } from '../types' +import { ITaskData } from '../types' + +export function useSeaTunnel({ + projectCode, + from = 0, + readonly, + data, + updateValue +}: { + projectCode: number + from?: number + readonly?: boolean + data?: ITaskData + updateValue?: (value: any, field: string) => void +}) { + const model = reactive({ + name: '', + taskType: 'SEATUNNEL', + flag: 'YES', + description: '', + timeoutFlag: false, + localParams: [], + environmentCode: null, + failRetryInterval: 1, + failRetryTimes: 0, + workerGroup: 'default', + delayTime: 0, + timeout: 30, + engine: 'SEATUNNEL', + useCustom: true, + resourceFiles: [], + timeoutNotifyStrategy: ['WARN'], + rawScript: + 'env {\n' + + ' execution.parallelism = 1\n' + + '}\n' + + '\n' + + 'source {\n' + + ' FakeSourceStream {\n' + + ' result_table_name = "fake"\n' + + ' field_name = "name,age"\n' + + ' }\n' + + '}\n' + + '\n' + + 'transform {\n' + + ' sql {\n' + + ' sql = "select name,age from fake"\n' + + ' }\n' + + '}\n' + + '\n' + + 'sink {\n' + + ' ConsoleSink {}\n' + + '}' + } as INodeData) + + let extra: IJsonItem[] = [] + if (from === 1) { + extra = [ + Fields.useTaskType(model, readonly), + Fields.useProcessName({ + model, + projectCode, + isCreate: !data?.id, + from, + processName: data?.processName + }) + ] + } + + return { + json: [ + Fields.useName(from), + ...extra, + Fields.useRunFlag(), + Fields.useDescription(), + Fields.useTaskPriority(), + Fields.useWorkerGroup(), + Fields.useEnvironmentName(model, !data?.id), + ...Fields.useTaskGroup(model, projectCode), + ...Fields.useFailed(), + Fields.useDelayTime(model), + ...Fields.useTimeoutAlarm(model), + ...Fields.useSeaTunnel(model, updateValue), + Fields.usePreTasks(model) + ] as IJsonItem[], + model + } +} diff --git a/seatunnel-ui/src/views/task/components/node/tasks/use-shell.ts b/seatunnel-ui/src/views/task/components/node/tasks/use-shell.ts new file mode 100644 index 000000000..9d4d709da --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/tasks/use-shell.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 { reactive } from 'vue' +import * as Fields from '../fields/index' +import type { IJsonItem, INodeData, ITaskData } from '../types' + +export function useShell({ + projectCode, + from = 0, + readonly, + data +}: { + projectCode: number + from?: number + readonly?: boolean + data?: ITaskData +}) { + const model = reactive({ + taskType: 'SHELL', + name: '', + flag: 'YES', + description: '', + timeoutFlag: false, + timeoutNotifyStrategy: ['WARN'], + timeout: 30, + localParams: [], + dataSourceList: [], + environmentCode: null, + failRetryInterval: 1, + failRetryTimes: 0, + workerGroup: 'default', + delayTime: 0, + rawScript: '', + code: -1 + } as INodeData) + + let extra: IJsonItem[] = [] + if (from === 1) { + extra = [ + Fields.useTaskType(model, readonly), + Fields.useProcessName({ + model, + projectCode, + isCreate: !data?.id, + from, + processName: data?.processName + }) + ] + } + + return { + json: [ + Fields.useName(from), + ...extra, + Fields.useRunFlag(), + Fields.useDescription(), + Fields.useTaskPriority(), + Fields.useWorkerGroup(), + Fields.useEnvironmentName(model, !data?.id), + ...Fields.useTaskGroup(model, projectCode), + ...Fields.useFailed(), + Fields.useDelayTime(model), + ...Fields.useTimeoutAlarm(model), + ...Fields.useRemoteConnection(model), + ...Fields.useShell(model, from), + Fields.usePreTasks(model) + ] as IJsonItem[], + model + } +} diff --git a/seatunnel-ui/src/views/task/components/node/tasks/use-spark.ts b/seatunnel-ui/src/views/task/components/node/tasks/use-spark.ts new file mode 100644 index 000000000..32e0a526f --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/tasks/use-spark.ts @@ -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 { reactive } from 'vue' +import * as Fields from '../fields/index' +import type { IJsonItem, INodeData, ITaskData } from '../types' + +export function useSpark({ + projectCode, + from = 0, + readonly, + data, + updateValue +}: { + projectCode: number + from?: number + readonly?: boolean + data?: ITaskData + updateValue?: (value: any, field: string) => void +}) { + const model = reactive({ + taskType: 'SPARK', + name: '', + flag: 'YES', + description: '', + timeoutFlag: false, + localParams: [], + environmentCode: null, + failRetryInterval: 1, + failRetryTimes: 0, + workerGroup: 'default', + delayTime: 0, + timeout: 30, + programType: 'SCALA', + rawScript: '', + deployMode: 'local', + driverCores: 1, + driverMemory: '512M', + numExecutors: 2, + executorMemory: '2G', + executorCores: 2 + } as INodeData) + + let extra: IJsonItem[] = [] + if (from === 1) { + extra = [ + Fields.useTaskType(model, readonly), + Fields.useProcessName({ + model, + projectCode, + isCreate: !data?.id, + from, + processName: data?.processName + }) + ] + } + + return { + json: [ + Fields.useName(from), + ...extra, + Fields.useRunFlag(), + Fields.useDescription(), + Fields.useTaskPriority(), + Fields.useWorkerGroup(), + Fields.useEnvironmentName(model, !data?.id), + ...Fields.useTaskGroup(model, projectCode), + ...Fields.useFailed(), + Fields.useDelayTime(model), + ...Fields.useTimeoutAlarm(model), + ...Fields.useSpark(model, updateValue), + Fields.usePreTasks(model) + ] as IJsonItem[], + model + } +} diff --git a/seatunnel-ui/src/views/task/components/node/tasks/use-sql.ts b/seatunnel-ui/src/views/task/components/node/tasks/use-sql.ts new file mode 100644 index 000000000..d18587c6c --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/tasks/use-sql.ts @@ -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 { reactive } from 'vue' +import * as Fields from '../fields/index' +import type { IJsonItem, INodeData } from '../types' +import { ITaskData } from '../types' + +export function useSql({ + projectCode, + from = 0, + readonly, + data +}: { + projectCode: number + from?: number + readonly?: boolean + data?: ITaskData +}) { + const model = reactive({ + name: '', + taskType: 'SQL', + flag: 'YES', + description: '', + timeoutFlag: false, + localParams: [], + environmentCode: null, + failRetryInterval: 1, + failRetryTimes: 0, + workerGroup: 'default', + delayTime: 0, + timeout: 30, + // type: null, + displayRows: 10, + sql: '', + sqlType: '0', + preStatements: [], + postStatements: [], + udfs: [] + } as INodeData) + + let extra: IJsonItem[] = [] + if (from === 1) { + extra = [ + Fields.useTaskType(model, readonly), + Fields.useProcessName({ + model, + projectCode, + isCreate: !data?.id, + from, + processName: data?.processName + }) + ] + } + + return { + json: [ + Fields.useName(from), + ...extra, + Fields.useRunFlag(), + Fields.useDescription(), + Fields.useTaskPriority(), + Fields.useWorkerGroup(), + Fields.useEnvironmentName(model, !data?.id), + ...Fields.useTaskGroup(model, projectCode), + ...Fields.useFailed(), + Fields.useDelayTime(model), + ...Fields.useTimeoutAlarm(model), + ...Fields.useDatasource({ model }), + ...Fields.useSqlUtils(model), + ...Fields.useSql(model, from), + Fields.usePreTasks(model) + ] as IJsonItem[], + model + } +} diff --git a/seatunnel-ui/src/views/task/components/node/tasks/use-sqoop.ts b/seatunnel-ui/src/views/task/components/node/tasks/use-sqoop.ts new file mode 100644 index 000000000..b87de6dc1 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/tasks/use-sqoop.ts @@ -0,0 +1,102 @@ +/* + * 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 * as Fields from '../fields/index' +import type { IJsonItem, INodeData, ITaskData } from '../types' + +export function useSqoop({ + projectCode, + from = 0, + readonly, + data, + updateValue +}: { + projectCode: number + from?: number + readonly?: boolean + data?: ITaskData + updateValue?: (value: any, field: string) => void +}) { + const model = reactive({ + taskType: 'SQOOP', + name: '', + flag: 'YES', + description: '', + timeoutFlag: false, + localParams: [], + environmentCode: null, + failRetryInterval: 1, + failRetryTimes: 0, + workerGroup: 'default', + delayTime: 0, + timeout: 30, + isCustomTask: false, + hadoopCustomParams: [], + sqoopAdvancedParams: [], + mapColumnHive: [], + mapColumnJava: [], + modelType: 'import', + sourceType: 'MYSQL', + srcQueryType: '1', + srcColumnType: '0', + targetType: 'HDFS', + // sourceMysqlType: 'MYSQL', + targetHdfsDeleteTargetDir: true, + targetHdfsCompressionCodec: 'snappy', + targetHdfsFileType: '--as-avrodatafile', + targetMysqlType: 'MYSQL', + targetMysqlUpdateMode: 'allowinsert', + targetHiveCreateTable: false, + targetHiveDropDelimiter: false, + targetHiveOverWrite: true, + concurrency: 1 + } as INodeData) + + let extra: IJsonItem[] = [] + if (from === 1) { + extra = [ + Fields.useTaskType(model, readonly), + Fields.useProcessName({ + model, + projectCode, + isCreate: !data?.id, + from, + processName: data?.processName + }) + ] + } + + return { + json: [ + Fields.useName(from), + ...extra, + Fields.useRunFlag(), + Fields.useDescription(), + Fields.useTaskPriority(), + Fields.useWorkerGroup(), + Fields.useEnvironmentName(model, !data?.id), + ...Fields.useTaskGroup(model, projectCode), + ...Fields.useFailed(), + Fields.useDelayTime(model), + ...Fields.useTimeoutAlarm(model), + ...Fields.useSqoop(model, updateValue), + Fields.usePreTasks(model) + ] as IJsonItem[], + model + } +} diff --git a/seatunnel-ui/src/views/task/components/node/tasks/use-sub-process.ts b/seatunnel-ui/src/views/task/components/node/tasks/use-sub-process.ts new file mode 100644 index 000000000..33f08c2f9 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/tasks/use-sub-process.ts @@ -0,0 +1,87 @@ +/* + * 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 * as Fields from '../fields/index' +import { useRouter } from 'vue-router' +import type { IJsonItem, INodeData, ITaskData } from '../types' + +export function useSubProcess({ + projectCode, + from = 0, + readonly, + data +}: { + projectCode: number + from?: number + readonly?: boolean + data?: ITaskData +}) { + const router = useRouter() + const workflowCode = router.currentRoute.value.params.code + const model = reactive({ + taskType: 'SUB_PROCESS', + name: '', + flag: 'YES', + description: '', + timeoutFlag: false, + localParams: [], + environmentCode: null, + failRetryInterval: 1, + failRetryTimes: 0, + workerGroup: 'default', + delayTime: 0, + timeout: 30 + } as INodeData) + + let extra: IJsonItem[] = [] + if (from === 1) { + extra = [ + Fields.useTaskType(model, readonly), + Fields.useProcessName({ + model, + projectCode, + isCreate: !data?.id, + from, + processName: data?.processName + }) + ] + } + + return { + json: [ + Fields.useName(from), + ...extra, + Fields.useRunFlag(), + Fields.useDescription(), + Fields.useTaskPriority(), + Fields.useWorkerGroup(), + Fields.useEnvironmentName(model, !data?.id), + ...Fields.useTimeoutAlarm(model), + Fields.useChildNode({ + model, + projectCode, + from, + processName: data?.processName, + code: from === 1 ? 0 : Number(workflowCode), + isCreate: !data?.id + }), + Fields.usePreTasks(model) + ] as IJsonItem[], + model + } +} diff --git a/seatunnel-ui/src/views/task/components/node/tasks/use-surveil.ts b/seatunnel-ui/src/views/task/components/node/tasks/use-surveil.ts new file mode 100644 index 000000000..b70797dee --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/tasks/use-surveil.ts @@ -0,0 +1,89 @@ +/* + * 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 * as Fields from '../fields/index' +import type { IJsonItem, INodeData } from '../types' +import { ITaskData } from '../types' + +export function useSurveil({ + projectCode, + from = 0, + readonly, + data, + updateValue +}: { + projectCode: number + from?: number + readonly?: boolean + data?: ITaskData + updateValue?: (value: any, field: string) => void +}) { + const model = reactive({ + name: '', + taskType: 'SURVEIL', + flag: 'YES', + description: '', + timeoutFlag: false, + localParams: [], + environmentCode: null, + failRetryInterval: 1, + failRetryTimes: 0, + workerGroup: 'default', + delayTime: 0, + timeout: 30, + scanType: 1, + // type: 'MYSQL', + interval: 60, + keyDeserializer: 'org.apache.kafka.common.serialization.StringDeserializer', + valueDeserializer: + 'org.apache.kafka.common.serialization.StringDeserializer' + } as INodeData) + + let extra: IJsonItem[] = [] + if (from === 1) { + extra = [ + Fields.useTaskType(model, readonly), + Fields.useProcessName({ + model, + projectCode, + isCreate: !data?.id, + from, + processName: data?.processName + }) + ] + } + + return { + json: [ + Fields.useName(from), + ...extra, + Fields.useRunFlag(), + Fields.useDescription(), + Fields.useTaskPriority(), + Fields.useWorkerGroup(), + Fields.useEnvironmentName(model, !data?.id), + ...Fields.useTaskGroup(model, projectCode), + ...Fields.useFailed(), + Fields.useDelayTime(model), + ...Fields.useTimeoutAlarm(model), + ...Fields.useSurveil(model, updateValue), + Fields.usePreTasks(model) + ] as IJsonItem[], + model + } +} diff --git a/seatunnel-ui/src/views/task/components/node/tasks/use-switch.ts b/seatunnel-ui/src/views/task/components/node/tasks/use-switch.ts new file mode 100644 index 000000000..6ce632a93 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/tasks/use-switch.ts @@ -0,0 +1,79 @@ +/* + * 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 * as Fields from '../fields/index' +import type { IJsonItem, INodeData, ITaskData } from '../types' + +export function useSwitch({ + projectCode, + from = 0, + readonly, + data +}: { + projectCode: number + from?: number + readonly?: boolean + data?: ITaskData +}) { + const model = reactive({ + taskType: 'SWITCH', + name: '', + flag: 'YES', + description: '', + timeoutFlag: false, + localParams: [], + failRetryInterval: 1, + failRetryTimes: 0, + delayTime: 0, + timeout: 30, + rawScript: '', + switchResult: {}, + dependTaskList: [], + nextNode: undefined + } as INodeData) + + let extra: IJsonItem[] = [] + if (from === 1) { + extra = [ + Fields.useTaskType(model, readonly), + Fields.useProcessName({ + model, + projectCode, + isCreate: !data?.id, + from, + processName: data?.processName + }) + ] + } + + return { + json: [ + Fields.useName(from), + ...extra, + Fields.useRunFlag(), + Fields.useDescription(), + Fields.useTaskPriority(), + ...Fields.useFailed(), + Fields.useDelayTime(model), + ...Fields.useTimeoutAlarm(model), + ...Fields.useSwitch(model, projectCode), + Fields.usePreTasks(model) + ] as IJsonItem[], + model + } +} diff --git a/seatunnel-ui/src/views/task/components/node/tasks/use-whale-sea-tunnel.ts b/seatunnel-ui/src/views/task/components/node/tasks/use-whale-sea-tunnel.ts new file mode 100644 index 000000000..089412d88 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/tasks/use-whale-sea-tunnel.ts @@ -0,0 +1,87 @@ +/* + * 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 * as Fields from '../fields/index' +import type { IJsonItem, INodeData } from '../types' +import { ITaskData } from '../types' + +export function useWhaleSeaTunnel({ + projectCode, + from = 0, + readonly, + data +}: { + projectCode: number + from?: number + readonly?: boolean + data?: ITaskData +}) { + const model = reactive({ + name: '', + taskType: 'WHALE_SEATUNNEL', + flag: 'YES', + description: '', + timeoutFlag: false, + localParams: [], + environmentCode: null, + failRetryInterval: 1, + failRetryTimes: 0, + workerGroup: 'default', + delayTime: 0, + timeout: 30, + deployMode: 'client', + queue: 'default', + master: 'yarn', + masterUrl: '', + resourceFiles: [], + breakContinue: false + } as INodeData) + + let extra: IJsonItem[] = [] + if (from === 1) { + extra = [ + Fields.useTaskType(model, readonly), + Fields.useProcessName({ + model, + projectCode, + isCreate: !data?.id, + from, + processName: data?.processName + }) + ] + } + + return { + json: [ + Fields.useName(from), + ...extra, + Fields.useRunFlag(), + Fields.useDescription(), + Fields.useTaskPriority(), + Fields.useWorkerGroup(), + Fields.useEnvironmentName(model, !data?.id), + ...Fields.useTaskGroup(model, projectCode), + ...Fields.useFailed(), + Fields.useDelayTime(model), + ...Fields.useTimeoutAlarm(model), + ...Fields.useWhaleTunnel(model, projectCode), + Fields.usePreTasks(model) + ] as IJsonItem[], + model + } +} diff --git a/seatunnel-ui/src/views/task/components/node/tasks/use-zeppelin.ts b/seatunnel-ui/src/views/task/components/node/tasks/use-zeppelin.ts new file mode 100644 index 000000000..2e231d778 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/tasks/use-zeppelin.ts @@ -0,0 +1,81 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { reactive } from 'vue' +import * as Fields from '../fields/index' +import type { IJsonItem, INodeData, ITaskData } from '../types' + +export function useZeppelin({ + projectCode, + from = 0, + readonly, + data +}: { + projectCode: number + from?: number + readonly?: boolean + data?: ITaskData +}) { + const model = reactive({ + name: '', + taskType: 'ZEPPELIN', + flag: 'YES', + description: '', + timeoutFlag: false, + localParams: [], + environmentCode: null, + failRetryInterval: 1, + failRetryTimes: 0, + workerGroup: 'default', + delayTime: 0, + timeout: 30, + timeoutNotifyStrategy: ['WARN'] + } as INodeData) + + let extra: IJsonItem[] = [] + if (from === 1) { + extra = [ + Fields.useTaskType(model, readonly), + Fields.useProcessName({ + model, + projectCode, + isCreate: !data?.id, + from, + processName: data?.processName + }) + ] + } + + return { + json: [ + Fields.useName(from), + ...extra, + Fields.useRunFlag(), + Fields.useDescription(), + Fields.useTaskPriority(), + Fields.useWorkerGroup(), + Fields.useEnvironmentName(model, !model.id), + ...Fields.useTaskGroup(model, projectCode), + ...Fields.useFailed(), + Fields.useDelayTime(model), + ...Fields.useTimeoutAlarm(model), + ...Fields.useZeppelin(model), + Fields.usePreTasks(model) + ] as IJsonItem[], + model + } +} diff --git a/seatunnel-ui/src/views/task/components/node/types.ts b/seatunnel-ui/src/views/task/components/node/types.ts new file mode 100644 index 000000000..1f71d3d55 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/types.ts @@ -0,0 +1,490 @@ +/* + * 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 { VNode } from 'vue' +import type { SelectOption } from 'naive-ui' +import type { + IFormItem, + IJsonItem, + FormRules, + IJsonItemParams +} from '@/components/form/types' +import type { ITaskTypeItem, TaskType } from '@/store/project/types' +export type { EditWorkflowDefinition } from '@/views/projects/workflow/components/dag/types' +export type { + IWorkflowTaskInstance, + WorkflowInstance +} from '@/views/projects/workflow/components/dag/types' +export type { IResource, ProgramType, IMainJar } from '@/store/project/types' +export type { ITaskState } from '@/common/types' + +type SourceType = 'MYSQL' | 'HDFS' | 'HIVE' +type ModelType = 'import' | 'export' +type RelationType = 'AND' | 'OR' +type ITaskType = TaskType +type ScanType = 1 | 2 | 3 | 4 +type IDateType = 'hour' | 'day' | 'week' | 'month' + +interface IOption { + label: string + value: string | number +} + +interface ITaskPriorityOption extends SelectOption { + icon: VNode + color: string +} +interface IEnvironmentNameOption { + label: string + value: string + workerGroups?: string[] +} + +interface ILocalParam { + prop: string + direct?: string + type?: string + value?: string +} + +interface IResponseJsonItem extends Omit { + type: 'input' | 'select' | 'radio' | 'group' + emit: 'change'[] + name?: string +} + +interface IDependpendItem { + depTaskCode?: number + status?: 'SUCCESS' | 'FAILURE' + definitionCodeOptions?: IOption[] + depTaskCodeOptions?: IOption[] + dateOptions?: IOption[] + projectCode?: number + definitionCode?: number + cycle?: 'month' | 'week' | 'day' | 'hour' + dateValue?: string + cardValue?: string +} + +interface IDependTask { + condition?: string + nextNode?: number + relation?: RelationType + dependItemList?: IDependpendItem[] +} + +interface ISwitchResult { + dependTaskList?: IDependTask[] + nextNode?: number +} + +interface ISourceItem { + id: number +} + +interface ISqoopTargetData { + targetHiveDatabase?: string + targetHiveTable?: string + targetHiveCreateTable?: boolean + targetHiveDropDelimiter?: boolean + targetHiveOverWrite?: boolean + targetHiveTargetDir?: string + targetHiveReplaceDelimiter?: string + targetHivePartitionKey?: string + targetHivePartitionValue?: string + targetHdfsTargetPath?: string + targetHdfsDeleteTargetDir?: boolean + targetHdfsCompressionCodec?: string + targetHdfsFileType?: string + targetHdfsFieldsTerminated?: string + targetHdfsLinesTerminated?: string + targetMysqlType?: string + targetMysqlDatasource?: string + targetMysqlTable?: string + targetMysqlColumns?: string + targetMysqlFieldsTerminated?: string + targetMysqlLinesTerminated?: string + targetMysqlIsUpdate?: string + targetMysqlTargetUpdateKey?: string + targetMysqlUpdateMode?: string +} + +interface ISqoopSourceData { + srcQueryType?: '1' | '0' + srcTable?: string + srcColumnType?: '1' | '0' + srcColumns?: string + sourceMysqlSrcQuerySql?: string + sourceMysqlType?: string + sourceMysqlDatasource?: string + mapColumnHive?: ILocalParam[] + mapColumnJava?: ILocalParam[] + sourceHdfsExportDir?: string + sourceHiveDatabase?: string + sourceHiveTable?: string + sourceHivePartitionKey?: string + sourceHivePartitionValue?: string +} + +interface ISqoopTargetParams { + hiveDatabase?: string + hiveTable?: string + createHiveTable?: boolean + dropDelimiter?: boolean + hiveOverWrite?: boolean + hiveTargetDir?: string + replaceDelimiter?: string + hivePartitionKey?: string + hivePartitionValue?: string + targetPath?: string + deleteTargetDir?: boolean + compressionCodec?: string + fileType?: string + fieldsTerminated?: string + linesTerminated?: string + targetType?: string + targetDatasource?: string + targetTable?: string + targetColumns?: string + isUpdate?: string + targetUpdateKey?: string + targetUpdateMode?: string +} +interface ISqoopSourceParams { + srcTable?: string + srcColumnType?: '1' | '0' + srcColumns?: string + srcQuerySql?: string + srcQueryType?: '1' | '0' + srcType?: string + srcDatasource?: string + mapColumnHive?: ILocalParam[] + mapColumnJava?: ILocalParam[] + exportDir?: string + hiveDatabase?: string + hiveTable?: string + hivePartitionKey?: string + hivePartitionValue?: string +} +interface ISparkParameters { + deployMode?: string + driverCores?: number + driverMemory?: string + executorCores?: number + executorMemory?: string + numExecutors?: number + others?: string +} + +interface IRuleParameters { + check_type?: string + comparison_execute_sql?: string + comparison_name?: string + comparison_type?: string + failure_strategy?: string + operator?: string + src_connector_type?: number + src_datasource_id?: number + src_table?: string + field_length?: number + begin_time?: string + deadline?: string + datetime_format?: string + target_filter?: string + regexp_pattern?: string + enum_list?: string + src_filter?: string + src_field?: string + statistics_execute_sql?: string + statistics_name?: string + target_connector_type?: number + target_datasource_id?: number + target_table?: string + threshold?: string + mapping_columns?: string + logic_operator?: string +} + +interface IOtherParams { + dependStrategy?: 'failure-continue' | 'failure-waiting' +} + +interface ITaskParams { + resourceList?: ISourceItem[] + mainJar?: ISourceItem + localParams?: ILocalParam[] + rawScript?: string + programType?: string + flinkVersion?: string + jobManagerMemory?: string + taskManagerMemory?: string + slot?: number + taskManager?: number + parallelism?: number + mainClass?: string + deployMode?: string + appName?: string + driverCores?: number + driverMemory?: string + numExecutors?: number + executorMemory?: string + executorCores?: number + mainArgs?: string + others?: string + httpMethod?: string + httpCheckCondition?: string + httpParams?: [] + url?: string + condition?: string + connectTimeout?: number + socketTimeout?: number + type?: string + datasource?: string + sql?: string + sqlType?: string + sendEmail?: boolean + displayRows?: number + title?: string + groupId?: string + preStatements?: string[] + postStatements?: string[] + method?: string + jobType?: 'CUSTOM' | 'TEMPLATE' + customShell?: string + jobName?: string + hadoopCustomParams?: ILocalParam[] + sqoopAdvancedParams?: ILocalParam[] + concurrency?: number + modelType?: ModelType + sourceType?: SourceType + targetType?: SourceType + targetParams?: string + sourceParams?: string + queue?: string + master?: string + switchResult?: ISwitchResult + dependTaskList?: IDependTask[] + nextNode?: number + dependence?: { + relation?: RelationType + dependTaskList?: IDependTask[] + } + customConfig?: number + json?: string + dsType?: string + dataSource?: number + dtType?: string + dataTarget?: number + targetTable?: string + jobSpeedByte?: number + jobSpeedRecord?: number + xms?: number + xmx?: number + sparkParameters?: ISparkParameters + ruleId?: number + ruleInputParameter?: IRuleParameters + jobFlowDefineJson?: string + stepsDefineJson?: string + processDefinitionCode?: number + conditionResult?: { + successNode?: number[] + failedNode?: number[] + } + udfs?: string + connParams?: string + targetJobName?: string + interval?: number + scanType?: ScanType + filePath?: string + topic?: string + bootstrapServers?: string + keyDeserializer?: string + valueDeserializer?: string + offsetTime?: string + cardCode?: number | null + otherParams?: IOtherParams + hiveCliOptions?: string + hiveSqlScript?: string + hiveCliTaskExecutionType?: string + zeppelinNoteId?: string + zeppelinParagraphId?: string + zeppelinRestEndpoint?: string + restEndpoint?: string + zeppelinProductionNoteDirectory?: string + productionNoteDirectory?: string + noteId?: string + paragraphId?: string + condaEnvName?: string + inputNotePath?: string + outputNotePath?: string + parameters?: string + kernel?: string + engine?: string + executionTimeout?: string + startTimeout?: string + useCustom?: boolean + algorithm?: string + params?: string + searchParams?: string + dataPath?: string + experimentName?: string + modelName?: string + mlflowTrackingUri?: string + mlflowJobType?: string + automlTool?: string + registerModel?: boolean + mlflowTaskType?: string + mlflowProjectRepository?: string + mlflowProjectVersion?: string + deployType?: string + deployPort?: string + deployModelKey?: string + zk?: string + zkPath?: string + dvcTaskType?: string + dvcRepository?: string + dvcVersion?: string + dvcDataLocation?: string + dvcMessage?: string + dvcLoadSaveDataPath?: string + dvcStoreUrl?: string + address?: string + taskId?: string + online?: boolean + executeMode?: string + sagemakerRequestJson?: string + script?: string + scriptParams?: string + pythonPath?: string + isCreateEnvironment?: string + pythonCommand?: string + pythonEnvTool?: string + requirements?: string + condaPythonVersion?: string + showOtherParams?: string + taskProcessType?: string + options?: string + classPath?: string + dataSourceList?: { + type: string + datasource: number + }[] + breakContinue?: boolean + cpResIds?: number[] + fieldStr?: string | null + ideContentField?: any + remoteConnection?: any + informaticaFolderName?: string | null + informaticaWorkflowName?: string | null +} + +interface INodeData + extends Omit< + ITaskParams, + | 'resourceList' + | 'mainJar' + | 'targetParams' + | 'sourceParams' + | 'dependence' + | 'sparkParameters' + | 'conditionResult' + | 'udfs' + | 'customConfig' + | 'otherParams' + >, + ISqoopTargetData, + ISqoopSourceData, + Omit, + IOtherParams { + id?: string + taskType?: ITaskType + processName?: number + delayTime?: number + description?: string + environmentCode?: number | null + failRetryInterval?: number + failRetryTimes?: number + flag?: 'YES' | 'NO' + taskGroupId?: number + taskGroupPriority?: number + taskPriority?: string + timeout?: number + timeoutFlag?: boolean + timeoutNotifyStrategy?: string[] + workerGroup?: string + code?: number + name?: string + preTasks?: number[] + preTaskOptions?: [] + postTaskOptions?: [] + resourceList?: number[] + mainJar?: number + timeoutSetting?: boolean + isCustomTask?: boolean + method?: string + masterUrl?: string + resourceFiles?: { id: number; fullName: string }[] | null + relation?: RelationType + definition?: object + successBranch?: number + failedBranch?: number + udfs?: string[] + customConfig?: boolean + script?: string + taskId?: string + mapping_columns?: object[] + resourceId?: string | number | null + open?: boolean + datasourceType?: string + datasourceId?: number +} + +interface ITaskData + extends Omit< + INodeData, + 'timeoutFlag' | 'taskPriority' | 'timeoutNotifyStrategy' + > { + name?: string + taskPriority?: string + timeoutFlag?: 'OPEN' | 'CLOSE' + timeoutNotifyStrategy?: string | [] + taskParams?: ITaskParams +} + +export { + ITaskPriorityOption, + IEnvironmentNameOption, + ILocalParam, + ITaskType, + ITaskTypeItem, + ITaskData, + INodeData, + ITaskParams, + IOption, + ModelType, + SourceType, + ISqoopSourceParams, + ISqoopTargetParams, + IDependTask, + IDependpendItem, + IFormItem, + IJsonItem, + FormRules, + IJsonItemParams, + IResponseJsonItem, + IDateType +} diff --git a/seatunnel-ui/src/views/task/components/node/use-task-config.ts b/seatunnel-ui/src/views/task/components/node/use-task-config.ts new file mode 100644 index 000000000..a5572178e --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/use-task-config.ts @@ -0,0 +1,177 @@ +/* + * 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 { ITaskType } from './types' +import type { ITabPanelType, IHandler } from '@/components/studio/types/tab' + +export interface IDefaultTaskType { + language: string + paneType?: ITabPanelType + handlers: IHandler[] +} + +export const useTaskConfig = () => { + const TASK_CONFIG = { + SHELL: { + language: 'shell', + paneType: 'setting', + handlers: [ + { + key: 'script', + name: 'project.node.script' + } + ] + }, + SQL: { + language: 'sql', + paneType: 'setting', + handlers: [ + { + key: 'script', + name: 'project.node.script' + } + ] + }, + PYTHON: { + language: 'python', + paneType: 'setting', + handlers: [ + { + key: 'script', + name: 'project.node.script' + } + ] + }, + PROCEDURE: { + language: 'sql', + paneType: 'setting', + handlers: [ + { + key: 'script', + name: 'project.node.procedure_method' + } + ] + }, + DATAX: { + language: 'sql', + paneType: 'setting', + handlers: [ + { + key: 'script', + name: 'project.node.sql_statement' + } + ] + }, + SQOOP: { + language: 'sql', + paneType: 'setting', + handlers: [ + { + key: 'script', + name: 'project.node.sql_statement' + } + ] + }, + EMR: { + language: 'json', + paneType: 'setting', + handlers: [ + { + key: 'script', + name: 'project.node.emr_flow_define_json' + } + ] + }, + HIVECLI: { + language: 'sql', + paneType: 'setting', + handlers: [ + { + key: 'script', + name: 'project.node.hive_sql_script' + } + ] + }, + SEATUNNEL: { + language: 'shell', + paneType: 'setting', + handlers: [ + { + key: 'script', + name: 'project.node.script' + } + ] + }, + OPENMLDB: { + language: 'sql', + paneType: 'setting', + handlers: [ + { + key: 'script', + name: 'project.node.sql_statement' + } + ] + }, + SAGEMAKER: { + language: 'json', + paneType: 'setting', + handlers: [ + { + key: 'script', + name: 'project.node.sagemaker_request_json' + } + ] + }, + CHUNJUN: { + language: 'json', + paneType: 'setting', + handlers: [ + { + key: 'script', + name: 'project.node.chunjun_json' + } + ] + } + } as { + [key in ITaskType]: { + language: string + paneType: ITabPanelType + handlers: IHandler[] + } + } + + const getTaskDefaultConfig = (taskType: ITaskType): IDefaultTaskType => { + return TASK_CONFIG[taskType] || { paneType: 'setting' } + } + return { getTaskDefaultConfig } +} + +export const fieldMap = { + rawScript: 'rawScript', + SQOOP: 'customShell', + SQL: 'sql', + PROCEDURE: 'method', + DATAX_IF: 'sql', + DATAX_ELSE: 'json', + RUN_JOB_FLOW_IF: 'jobFlowDefineJson', + RUN_JOB_FLOW_ELSE: 'stepsDefineJson', + OPENMLDB: 'sql', + SAGEMAKER: 'sagemakerRequestJson', + PYTORCH: 'script', + SURVEIL_TYPE2: 'sql', + HIVECLI: 'hiveSqlScript', + CHUNJUN: 'json' +} diff --git a/seatunnel-ui/src/views/task/components/node/use-task.ts b/seatunnel-ui/src/views/task/components/node/use-task.ts new file mode 100644 index 000000000..4bd976401 --- /dev/null +++ b/seatunnel-ui/src/views/task/components/node/use-task.ts @@ -0,0 +1,89 @@ +/* + * 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 { ref, Ref, unref } from 'vue' +import nodes from './tasks' +import getElementByJson from '@/components/form/get-elements-by-json' +import { useTaskNodeStore } from '@/store/project/task-node' +import type { + IFormItem, + IJsonItem, + INodeData, + ITaskData, + FormRules, + EditWorkflowDefinition +} from './types' + +export function useTask({ + data, + projectCode, + from, + readonly, + definitionRef, + updateValue, + setting +}: { + data: ITaskData + projectCode: number + from?: number + readonly?: boolean + definitionRef?: Ref + updateValue?: (value: any, field: string) => void + setting?: any +}): { + elementsRef: Ref + rulesRef: Ref + model: INodeData +} { + const taskStore = useTaskNodeStore() + taskStore.updateDefinition(unref(definitionRef), data?.code) + + const jsonRef = ref([]) as Ref + const elementsRef = ref([]) as Ref + const rulesRef = ref({}) + + const params = { + projectCode, + from, + readonly, + data, + jsonRef, + setting, + updateElements: () => { + getElements() + }, + updateValue + } + + const { model, json } = nodes[data.taskType || 'SHELL'](params) + jsonRef.value = json + model.preTasks = taskStore.getPreTasks + model.name = taskStore.getName + + const getElements = () => { + const { rules, elements } = getElementByJson( + jsonRef.value, + model, + updateValue + ) + elementsRef.value = elements + rulesRef.value = rules + } + + getElements() + + return { elementsRef, rulesRef, model } +} diff --git a/seatunnel-ui/src/views/task/coronation/components/import-modal.tsx b/seatunnel-ui/src/views/task/coronation/components/import-modal.tsx new file mode 100644 index 000000000..e062f9c6b --- /dev/null +++ b/seatunnel-ui/src/views/task/coronation/components/import-modal.tsx @@ -0,0 +1,268 @@ +/* + * 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, watch, onMounted, computed } from 'vue' +import Modal from '@/components/modal' +import { + NSpace, + NSteps, + NStep, + NUpload, + NButton, + NDataTable, + NIcon, + NForm, + NFormItem, + NUploadDragger, + NText, + NCheckbox, + NCheckboxGroup, + NCollapse, + NCollapseItem, + NGrid, + NGi +} from 'naive-ui' +import { useImport } from './use-import' +import { useI18n } from 'vue-i18n' +import { RightOutlined, UploadOutlined } from '@vicons/antd' +import { tasksState } from '@/common/common' +import { ITaskState } from '../../instance/types' +import styles from '../index.module.scss' + +const ImportModal = defineComponent({ + name: 'coronation-import-modal', + props: { + showModalRef: { + type: Boolean as PropType, + default: false + } + }, + emits: ['cancelModal', 'confirmModal'], + setup(props, ctx) { + const { + rules, + variables, + formRef, + createColumns, + nextStep, + prevStep, + onFileChange + } = useImport(ctx) + const { t } = useI18n() + + const tableData = computed(() => + !variables.coronationMore && variables.tableData.length > 5 + ? variables.tableData.slice(0, 5) + : variables.tableData + ) + + const treeData = computed(() => + !variables.upstreamMore && variables.treeData.length > 5 + ? variables.treeData.slice(0, 5) + : variables.treeData + ) + + const getAllTaskKeys = () => { + const taskKeys = new Set() + variables.treeData + .filter( + (item: any) => item.upstreamTasks && item.upstreamTasks.length > 0 + ) + .map((item: any) => taskKeys.add(item.key)) + return [...taskKeys] + } + + const cancelModal = () => { + ctx.emit('cancelModal', props.showModalRef) + } + + const getConfirmText = () => { + if (variables.currentStep === 3) return t('project.task.confirm') + else return t('project.task.next_step') + } + + onMounted(() => { + createColumns(variables) + }) + + watch(useI18n().locale, () => { + createColumns(variables) + }) + + watch( + () => props.showModalRef, + () => { + if (props.showModalRef) { + variables.currentStep = 1 + variables.file = undefined + variables.coronationMore = false + variables.upstreamMore = false + } + } + ) + + const expandedNames = computed(() => getAllTaskKeys() as Array) + + const renderCheckBoxLabel = (task: any) => { + const stateOption = tasksState(t)[task.taskStatus as ITaskState] || {} + + return task.taskStatus && stateOption.desc + ? `${task.taskNode} (${stateOption.desc})` + : `${task.taskNode}` + } + + const checkBoxRender = (key: string, upstreamTasks: any) => { + return ( + item.key)} + onUpdateValue={(value) => handleUpstreamValue(key, value)} + style={{ marginLeft: '20px' }} + > + + {upstreamTasks.map((item: any) => ( + + + + ))} + + + ) + } + + const handleUpstreamValue = (key: string, value: any) => { + variables.coronationTask[key] = value + } + + return () => ( + + {{ + default: () => ( + + + + + + + {variables.currentStep === 1 && ( + + + + + + + + + {t('project.isolation.upload_tips')} + + + + + + )} + {variables.currentStep === 2 && ( + + + {!variables.coronationMore && variables.tableData.length > 5 && ( + (variables.coronationMore = true)} + > + {t('project.task.more')} + + )} + + )} + {variables.currentStep === 3 && ( + + + {treeData.value.map((item: any) => ( + 0 + ? 0 + : '20px' + }} + > + {{ + default: () => + checkBoxRender(item.key, item.upstreamTasks), + arrow: () => + item.upstreamTasks && + item.upstreamTasks.length > 0 ? ( + + + + ) : ( + '' + ) + }} + + ))} + + {!variables.upstreamMore && variables.treeData.length > 5 && ( + (variables.upstreamMore = true)} + style={{ marginLeft: '20px' }} + > + {t('project.task.more')} + + )} + + )} + + ), + 'btn-middle': () => + variables.currentStep !== 1 && ( + + {t('project.task.prev_step')} + + ) + }} + + ) + } +}) + +export default ImportModal diff --git a/seatunnel-ui/src/views/task/coronation/components/use-import.ts b/seatunnel-ui/src/views/task/coronation/components/use-import.ts new file mode 100644 index 000000000..c1be4bc91 --- /dev/null +++ b/seatunnel-ui/src/views/task/coronation/components/use-import.ts @@ -0,0 +1,193 @@ +/* + * 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, SetupContext, ref } from 'vue' +import { useI18n } from 'vue-i18n' +import { UploadCustomRequestOptions, useMessage } from 'naive-ui' +import { useRouter } from 'vue-router' +import { + COLUMN_WIDTH_CONFIG, + calculateTableWidth, + DefaultTableWidth +} from '@/common/column-width-config' +import type { Router } from 'vue-router' +import { + importTaskCoronation, + parseTaskCoronation, + submitTaskCoronation +} from '@/service/modules/task-coronation' +import _ from 'lodash' +import { uuid } from '@/common/common' + +export function useImport( + ctx: SetupContext<('cancelModal' | 'confirmModal')[]> +) { + const { t } = useI18n() + const message = useMessage() + const router: Router = useRouter() + + const formRef = ref() + const rules = { + file: { + trigger: ['blur'], + required: true, + validator: () => { + if (variables.file) return + return Error(t('project.isolation.file_tips')) + } + } + } + + const variables = reactive({ + columns: [], + tableWidth: DefaultTableWidth, + tableData: [] as any, + treeData: [] as any, + currentStep: 1, + file: null as any, + saving: false, + projectCode: Number(router.currentRoute.value.params.projectCode), + coronationTask: {} as { [key: string]: Array }, + coronationMore: false, + upstreamMore: false + }) + + const createColumns = (variables: any) => { + variables.columns = [ + { + title: t('project.task.workflow_instance_name'), + key: 'workflowInstanceName', + ...COLUMN_WIDTH_CONFIG['name'] + }, + { + title: t('project.task.task_name'), + key: 'taskName', + ...COLUMN_WIDTH_CONFIG['note'] + } + ] + + if (variables.tableWidth) { + variables.tableWidth = calculateTableWidth(variables.columns) + } + } + + const nextStep = async () => { + if (variables.currentStep === 1) await handleImportCoronation() + if (variables.currentStep === 2) await handleCoronationList() + if (variables.currentStep === 3) await handleSubmitCoronation() + if (variables.currentStep < 3) variables.currentStep++ + } + + const prevStep = () => { + if (variables.currentStep > 1) { + variables.coronationMore = false + variables.upstreamMore = false + variables.currentStep-- + } + } + + const handleImportCoronation = async () => { + if (variables.saving) return + variables.saving = true + try { + await formRef.value.validate() + const formData = new FormData() + formData.append('file', variables.file.file) + variables.tableData = await importTaskCoronation( + formData, + variables.projectCode + ) + } finally { + variables.saving = false + } + } + + const formatTreeData = (data: any) => { + data.map((item: any) => { + item.key = uuid(item.taskCode + '_') + if (item.upstreamTasks) { + item.upstreamTasks.map( + (item: any) => (item.key = uuid(item.taskCode + '_')) + ) + } + }) + } + + const initDefaultCheckedKeys = () => { + for (let i = 0; i < variables.treeData.length; i++) { + if (variables.treeData[i].upstreamTasks) { + const keys = [] + for (let j = 0; j < variables.treeData[i].upstreamTasks.length; j++) { + keys.push(variables.treeData[i].upstreamTasks[j].key) + } + variables.coronationTask[variables.treeData[i].key] = keys + } + } + } + + const handleCoronationList = async () => { + variables.treeData = await parseTaskCoronation(variables.projectCode, { + coronationTasks: variables.tableData + }) + formatTreeData(variables.treeData) + initDefaultCheckedKeys() + } + + const handleSubmitCoronation = async () => { + const coronationReq = [] as any + for (let i = 0; i < variables.treeData.length; i++) { + const task = variables.treeData[i] + const coronationTask = _.omit(task, ['upstreamTasks', 'key']) + coronationTask.upstreamTasks = [] + for (let j = 0; j < task.upstreamTasks.length; j++) { + if ( + variables.coronationTask[task.key].includes(task.upstreamTasks[j].key) + ) { + coronationTask.upstreamTasks.push({ + ..._.omit(task.upstreamTasks[j], ['key']) + }) + } + } + coronationReq.push(coronationTask) + } + await submitTaskCoronation(variables.projectCode, { + coronationTasks: coronationReq + }) + message.success(t('project.task.success')) + ctx.emit('confirmModal') + } + + const onFileChange = (options: UploadCustomRequestOptions) => { + if (options.file.status !== 'pending') { + variables.file = undefined + formRef.value.validate() + return + } + variables.file = options.file + formRef.value.validate() + } + + return { + rules, + variables, + formRef, + createColumns, + nextStep, + prevStep, + handleImportCoronation, + onFileChange + } +} diff --git a/seatunnel-ui/src/views/task/coronation/index.module.scss b/seatunnel-ui/src/views/task/coronation/index.module.scss new file mode 100644 index 000000000..7c3c60bd8 --- /dev/null +++ b/seatunnel-ui/src/views/task/coronation/index.module.scss @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + .width_100 { + width: 100%; +} +.detail { + padding-top: 20px; +} + +.uploader { + margin-top: 20px; + :global { + .n-upload-trigger { + width: 100%; + } + } +} diff --git a/seatunnel-ui/src/views/task/coronation/index.tsx b/seatunnel-ui/src/views/task/coronation/index.tsx new file mode 100644 index 000000000..a1a36901b --- /dev/null +++ b/seatunnel-ui/src/views/task/coronation/index.tsx @@ -0,0 +1,262 @@ +/* + * 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, watch } from 'vue' +import { useI18n } from 'vue-i18n' +import { SearchOutlined, ReloadOutlined } from '@vicons/antd' +import { + NButton, + NDataTable, + NIcon, + NInput, + NPagination, + NSpace, + NPopconfirm +} from 'naive-ui' +import Card from '@/components/card' +import { useTable } from './use-table' +import ImportModal from './components/import-modal' +import ProjectSelector from '../../components/projectSelector' +import { useProjectStore } from '@/store/project' +import { useRoute, useRouter } from 'vue-router' +import _ from 'lodash' + +const TaskCoronation = defineComponent({ + name: 'task-coronation', + setup() { + const { t } = useI18n() + const route = useRoute() + const router = useRouter() + + const { + variables, + getTableData, + createColumns, + handleImport, + downloadTemplate, + onCancleCoronation + } = useTable() + + const requestData = () => { + getTableData({ + pageSize: variables.pageSize, + pageNo: variables.page, + taskName: variables.taskName, + workflowInstanceName: variables.workflowInstanceName + }) + } + + const handleChangePageSize = () => { + variables.page = 1 + requestData() + } + + const handleSearch = () => { + variables.page = 1 + + const query = {} as any + if (variables.taskName) { + query.taskName = variables.taskName + } + + if (variables.workflowInstanceName) { + query.workflowInstanceName = variables.workflowInstanceName + } + + router.replace({ + query: !_.isEmpty(query) + ? { + ...query, + project: route.query.project, + global: route.query.global, + searchProjectCode: variables.projectCodes + } + : { + ...route.query, + searchProjectCode: variables.projectCodes + } + }) + requestData() + } + + const onReset = () => { + variables.taskName = null + variables.workflowInstanceName = null + variables.projectCodes = useProjectStore().getCurrentProject + } + const handleCancleCoronation = () => { + onCancleCoronation() + requestData() + } + const handleKeyup = (event: KeyboardEvent) => { + if (event.key === 'Enter') { + handleSearch() + } + } + + const onCancelModal = () => { + variables.showModalRef = false + } + + const onConfirmModal = () => { + variables.showModalRef = false + requestData() + } + + const initSearch = () => { + const { taskName, workflowInstanceName } = route.query + if (taskName) { + variables.taskName = taskName as any + } + + if (workflowInstanceName) { + variables.workflowInstanceName = workflowInstanceName as any + } + } + + onMounted(() => { + initSearch() + createColumns(variables) + requestData() + }) + + watch(useI18n().locale, () => { + createColumns(variables) + }) + const getProjectCodeList = (codes: any) => { + if (!codes) { + variables.projectCodes = useProjectStore().getGolbalProject + } else { + variables.projectCodes = [codes] + } + } + + return () => ( + + + + + + {t('project.task.import_coronation_task')} + + + {t('project.workflow.download_template')} + + + + {variables.globalProject && ( + + )} + + + + + + + + + + + + + + + + + {{ + 'header-extra': () => ( + + + + {{ + default: () => + t('project.workflow.cancle_coronation_confirm'), + trigger: () => t('project.workflow.cancle_coronation') + }} + + + + ), + default: () => ( + + row.id} + v-model:checked-row-keys={variables.checkedRowKeys} + /> + + + + + ) + }} + + + + ) + } +}) + +export default TaskCoronation diff --git a/seatunnel-ui/src/views/task/coronation/use-table.ts b/seatunnel-ui/src/views/task/coronation/use-table.ts new file mode 100644 index 000000000..3e1d51b27 --- /dev/null +++ b/seatunnel-ui/src/views/task/coronation/use-table.ts @@ -0,0 +1,274 @@ +/* + * 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, h, withDirectives } from 'vue' +import { NButton, NIcon, NPopconfirm, NSpace, NSpin, NTooltip } from 'naive-ui' +import { useI18n } from 'vue-i18n' +import { RollbackOutlined } from '@vicons/antd' +import { useRouter, useRoute } from 'vue-router' +import { + COLUMN_WIDTH_CONFIG, + calculateTableWidth, + DefaultTableWidth +} from '@/common/column-width-config' +import { useLocalesStore } from '@/store/locales/locales' +import { + queryTaskCoronationListPaging, + cancleCoronation +} from '@/service/modules/task-coronation' +import { useTableLink } from '@/hooks' +import type { Router } from 'vue-router' +import { permission } from '@/directives/permission' +import { tasksState } from '@/common/common' +import type { ITaskState } from '@/common/types' +import type { RowKey } from 'naive-ui/lib/data-table/src/interface' +import { useProjectStore } from '@/store/project' +import { changeProject } from '../../utils/changeProject' + +export function useTable() { + const { t } = useI18n() + const router: Router = useRouter() + const route = useRoute() + const projectStore = useProjectStore() + + const handleImport = () => { + variables.showModalRef = true + } + + const handleCancel = (row: any) => { + cancleCoronation({ coronationTaskIds: [row.id] }).then(() => { + getTableData({ + pageSize: variables.pageSize, + pageNo: + variables.tableData.length === 1 && variables.page > 1 + ? variables.page - 1 + : variables.page, + taskName: variables.taskName, + workflowInstanceName: variables.workflowInstanceName + }) + }) + } + + const createColumns = (variables: any) => { + variables.columns = [ + { + type: 'selection', + className: 'btn-selected', + ...COLUMN_WIDTH_CONFIG['selection'] + }, + { + title: '#', + key: 'index', + render: (row: any, index: number) => index + 1, + ...COLUMN_WIDTH_CONFIG['index'] + }, + { + title: t('project.task.task_name'), + key: 'taskName', + ...COLUMN_WIDTH_CONFIG['note'] + }, + useTableLink({ + title: t('project.project_name'), + key: 'projectName', + ...COLUMN_WIDTH_CONFIG['name'], + button: { + onClick: (row: any) => { + changeProject(row.projectCode) + router.push({ + path: route.path, + query: { + project: String(row.projectCode), + global: 'false' + } + }) + } + } + }), + { + title: t('project.task.run_state'), + key: 'taskStatus', + ...COLUMN_WIDTH_CONFIG['state'], + render: (row: any) => renderStateCell(row.taskStatus, t) + }, + { + title: t('project.task.workflow_instance_name'), + key: 'workflowInstanceName', + ...COLUMN_WIDTH_CONFIG['time'] + }, + { + title: t('project.task.create_time'), + key: 'createTime', + ...COLUMN_WIDTH_CONFIG['time'] + }, + { + title: t('project.task.operation'), + key: 'actions', + ...COLUMN_WIDTH_CONFIG['operation'](1), + render(row: any) { + return h(NSpace, null, { + default: () => [ + h( + NPopconfirm, + { + onPositiveClick: () => { + handleCancel(row) + } + }, + { + trigger: () => + h( + NTooltip, + {}, + { + trigger: () => + withDirectives( + h( + NButton, + { + circle: true, + type: 'info', + size: 'small' + }, + { + icon: () => + h(NIcon, null, { + default: () => h(RollbackOutlined) + }) + } + ), + [[permission, 'project:coronation-task:cancel']] + ), + default: () => t('project.task.cancel_coronation') + } + ), + default: () => t('project.task.cancel_confirm') + } + ) + ] + }) + } + } + ] + if (variables.tableWidth) { + variables.tableWidth = calculateTableWidth(variables.columns) + } + } + + const onCancleCoronation = () => { + cancleCoronation({ coronationTaskIds: variables.checkedRowKeys }).then( + () => { + window.$message.success(t('project.workflow.success')) + if (variables.tableData.length === 1 && variables.page > 1) { + variables.page -= 1 + } + getTableData({ + pageSize: variables.pageSize, + pageNo: variables.page, + taskName: variables.taskName, + workflowInstanceName: variables.workflowInstanceName + }) + } + ) + } + + const variables = reactive({ + columns: [], + tableWidth: DefaultTableWidth, + tableData: [], + page: 1, + pageSize: 10, + searchVal: null, + totalPage: 1, + showModalRef: false, + statusRef: 0, + row: {}, + loadingRef: false, + checkedRowKeys: [] as Array, + projectCodes: + route.query.searchProjectCode || projectStore.getCurrentProject, + globalProject: projectStore.getGlobalFlag, + taskName: null as null | string, + workflowInstanceName: null as null | string + }) + + const getTableData = (params: any) => { + if ( + variables.loadingRef || + !variables.projectCodes || + variables.projectCodes.length === 0 || + typeof variables.projectCodes[0] === 'undefined' + ) + return + variables.loadingRef = true + params.projectCodes = variables.projectCodes + queryTaskCoronationListPaging({ ...params }) + .then((res: any) => { + variables.tableData = res.totalList.map((item: any, unused: number) => { + return { + ...item + } + }) + variables.totalPage = res.totalPage + }) + .finally(() => { + variables.loadingRef = false + }) + } + + const downloadTemplate = () => { + const localesStore = useLocalesStore() + window.location.href = `/dolphinscheduler/${ + localesStore.getLocales === 'zh_CN' + ? 'coronation-task-excel-template-cn' + : 'coronation-task-excel-template-en' + }.xlsx` + } + + function renderStateCell(state: ITaskState, t: Function) { + if (!state) return '' + const stateOption = tasksState(t)[state] + if (!stateOption) return '' + const Icon = h( + NIcon, + { + color: stateOption.color, + class: stateOption.classNames, + style: { + display: 'flex' + }, + size: 20 + }, + () => h(stateOption.icon) + ) + return h(NTooltip, null, { + trigger: () => { + if (!stateOption.isSpin) return Icon + return h(NSpin, { size: 20 }, { icon: () => Icon }) + }, + default: () => stateOption.desc + }) + } + + return { + variables, + getTableData, + createColumns, + downloadTemplate, + handleImport, + onCancleCoronation + } +} diff --git a/seatunnel-ui/src/views/task/definition/components/move-modal.tsx b/seatunnel-ui/src/views/task/definition/components/move-modal.tsx new file mode 100644 index 000000000..391e16635 --- /dev/null +++ b/seatunnel-ui/src/views/task/definition/components/move-modal.tsx @@ -0,0 +1,109 @@ +/* + * 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, toRefs, watch } from 'vue' +import Modal from '@/components/modal' +import { NForm, NFormItem, NSelect } from 'naive-ui' +import { useI18n } from 'vue-i18n' +import { useMove } from './use-move' + +const props = { + show: { + type: Boolean as PropType, + default: false + }, + row: { + type: Object as PropType, + default: {} + } +} + +const MoveModal = defineComponent({ + name: 'MoveModal', + props, + emits: ['refresh', 'cancel'], + setup(props, ctx) { + const { t } = useI18n() + const { variables, handleValidate, getListData } = useMove() + + const cancelModal = () => { + variables.model.targetProcessDefinitionCode = '' + ctx.emit('cancel') + } + + const confirmModal = () => { + handleValidate() + } + + watch( + () => props.show, + () => { + variables.taskCode = props.row.taskCode + variables.processDefinitionCode = props.row.processDefinitionCode + variables.model.targetProcessDefinitionCode = + props.row.processDefinitionCode + + props.show && getListData() + } + ) + + watch( + () => variables.refreshTaskDefinition, + () => { + if (variables.refreshTaskDefinition) { + ctx.emit('refresh') + variables.refreshTaskDefinition = false + } + } + ) + + return { t, ...toRefs(variables), cancelModal, confirmModal } + }, + render() { + const { t, show, cancelModal, confirmModal } = this + + return ( + + + + + + + + ) + } +}) + +export default MoveModal diff --git a/seatunnel-ui/src/views/task/definition/components/use-move.ts b/seatunnel-ui/src/views/task/definition/components/use-move.ts new file mode 100644 index 000000000..c0864e28f --- /dev/null +++ b/seatunnel-ui/src/views/task/definition/components/use-move.ts @@ -0,0 +1,105 @@ +/* + * 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, ref } from 'vue' +import { useI18n } from 'vue-i18n' +import { useAsyncState } from '@vueuse/core' +import { querySimpleList } from '@/service/modules/process-definition' +import { useRoute } from 'vue-router' +import { moveRelation } from '@/service/modules/process-task-relation' +import type { SimpleListRes } from '@/service/modules/process-definition/types' + +export function useMove() { + const { t } = useI18n() + const route = useRoute() + const projectCode = Number(route.params.projectCode) + + const variables = reactive({ + taskCode: ref(''), + processDefinitionCode: ref(''), + refreshTaskDefinition: ref(false), + taskDefinitionFormRef: ref(), + model: { + targetProcessDefinitionCode: ref(''), + generalOptions: [] + }, + saving: false, + rules: { + targetProcessDefinitionCode: { + required: true, + trigger: ['change', 'blur'], + validator() { + if (!variables.model.targetProcessDefinitionCode) { + return new Error(t('project.task.workflow_name_tips')) + } + } + } + } + }) + + const getListData = () => { + const { state } = useAsyncState( + querySimpleList().then((res: Array) => { + variables.model.generalOptions = res.map( + (item): { label: string; value: number } => { + return { + label: item.name, + value: item.code + } + } + ) as any + }), + {} + ) + + return state + } + + const handleValidate = () => { + variables.taskDefinitionFormRef.validate((errors: any) => { + if (errors) { + return + } + moveTask() + }) + } + + const moveTask = async () => { + if (variables.saving) return + variables.saving = true + try { + const data = { + targetProcessDefinitionCode: + variables.model.targetProcessDefinitionCode, + taskCode: variables.taskCode, + processDefinitionCode: variables.processDefinitionCode + } + await moveRelation(data, projectCode) + variables.saving = false + variables.model.targetProcessDefinitionCode = '' + variables.refreshTaskDefinition = true + } catch (err) { + variables.saving = false + } + } + + return { + variables, + handleValidate, + getListData + } +} diff --git a/seatunnel-ui/src/views/task/definition/components/use-version.ts b/seatunnel-ui/src/views/task/definition/components/use-version.ts new file mode 100644 index 000000000..e6903da30 --- /dev/null +++ b/seatunnel-ui/src/views/task/definition/components/use-version.ts @@ -0,0 +1,212 @@ +/* + * 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 { useI18n } from 'vue-i18n' +import { h, reactive, ref } from 'vue' +import { NButton, NPopconfirm, NSpace, NTag, NTooltip } from 'naive-ui' +import { DeleteOutlined, CheckOutlined } from '@vicons/antd' +import { useAsyncState } from '@vueuse/core' +import { + queryTaskVersions, + switchVersion, + deleteVersion +} from '@/service/modules/task-definition' +import { useRoute } from 'vue-router' +import type { + TaskDefinitionVersionRes, + TaskDefinitionVersionItem +} from '@/service/modules/task-definition/types' + +export function useVersion() { + const { t } = useI18n() + const route = useRoute() + const projectCode = Number(route.params.projectCode) + + const createColumns = (variables: any) => { + variables.columns = [ + { + title: t('project.task.version'), + key: 'version', + render: (row: TaskDefinitionVersionItem) => + h( + 'span', + null, + row.version !== variables.taskVersion + ? 'v' + row.version + : h( + NTag, + { type: 'success', size: 'small' }, + { + default: () => + `v${row.version} ${t('project.task.current_version')}` + } + ) + ) + }, + { + title: t('project.task.description'), + key: 'description', + render: (row: TaskDefinitionVersionItem) => + h('span', null, row.description ? row.description : '-') + }, + { + title: t('project.task.create_time'), + key: 'createTime' + }, + { + title: t('project.task.operation'), + key: 'operation', + render(row: TaskDefinitionVersionItem) { + return h(NSpace, null, { + default: () => [ + h( + NPopconfirm, + { + onPositiveClick: () => { + handleSwitchVersion(row) + } + }, + { + trigger: () => + h( + NTooltip, + {}, + { + trigger: () => + h( + NButton, + { + circle: true, + type: 'info', + size: 'small', + disabled: row.version === variables.taskVersion + }, + { + icon: () => h(CheckOutlined) + } + ), + default: () => t('project.task.switch_version') + } + ), + default: () => t('project.task.confirm_switch_version') + } + ), + h( + NPopconfirm, + { + onPositiveClick: () => { + handleDelete(row) + } + }, + { + trigger: () => + h( + NTooltip, + {}, + { + trigger: () => + h( + NButton, + { + circle: true, + type: 'error', + size: 'small', + disabled: row.version === variables.taskVersion + }, + { + icon: () => h(DeleteOutlined) + } + ), + default: () => t('project.task.delete') + } + ), + default: () => t('project.task.delete_confirm') + } + ) + ] + }) + } + } + ] + } + + const variables = reactive({ + columns: [], + tableData: [], + page: ref(1), + pageSize: ref(10), + totalPage: ref(1), + taskVersion: ref(null), + taskCode: ref(null), + refreshTaskDefinition: ref(false), + row: {}, + loadingRef: ref(false) + }) + + const handleSwitchVersion = (row: TaskDefinitionVersionItem) => { + switchVersion( + { version: row.version }, + { code: variables.taskCode }, + { projectCode } + ).then(() => { + variables.refreshTaskDefinition = true + }) + } + + const handleDelete = (row: TaskDefinitionVersionItem) => { + deleteVersion( + { version: row.version }, + { code: variables.taskCode }, + { projectCode } + ).then(() => { + variables.refreshTaskDefinition = true + }) + } + + const getTableData = (params: any) => { + if (variables.loadingRef) return + variables.loadingRef = true + const { state } = useAsyncState( + queryTaskVersions( + { ...params }, + { code: variables.taskCode }, + { projectCode } + ) + .then((res: TaskDefinitionVersionRes) => { + variables.tableData = res.totalList.map((item, unused) => { + return { + ...item + } + }) as any + variables.totalPage = res.totalPage + variables.loadingRef = false + }) + .catch(() => { + variables.loadingRef = false + }), + {} + ) + + return state + } + + return { + variables, + getTableData, + createColumns + } +} diff --git a/seatunnel-ui/src/views/task/definition/components/version-modal.tsx b/seatunnel-ui/src/views/task/definition/components/version-modal.tsx new file mode 100644 index 000000000..4040818ef --- /dev/null +++ b/seatunnel-ui/src/views/task/definition/components/version-modal.tsx @@ -0,0 +1,107 @@ +/* + * 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, toRefs, watch } from 'vue' +import Modal from '@/components/modal' +import { NDataTable, NPagination } from 'naive-ui' +import { useI18n } from 'vue-i18n' +import { useVersion } from './use-version' +import styles from './version.module.scss' + +const props = { + show: { + type: Boolean as PropType, + default: false + }, + row: { + type: Object as PropType, + default: {} + } +} + +const VersionModal = defineComponent({ + name: 'VersionModal', + props, + emits: ['confirm', 'refresh'], + setup(props, ctx) { + const { t } = useI18n() + const { variables, getTableData, createColumns } = useVersion() + + const requestData = () => { + getTableData({ + pageSize: variables.pageSize, + pageNo: variables.page + }) + } + + watch( + () => props.show, + () => { + if (props.show) { + variables.taskVersion = props.row?.taskVersion + variables.taskCode = props.row?.taskCode + createColumns(variables) + requestData() + } + } + ) + + watch( + () => variables.refreshTaskDefinition, + () => { + if (variables.refreshTaskDefinition) { + ctx.emit('refresh') + variables.refreshTaskDefinition = false + } + } + ) + + const onConfirm = () => { + ctx.emit('confirm') + } + + return { t, ...toRefs(variables), requestData, onConfirm } + }, + render() { + const { t, requestData, onConfirm, show, loadingRef } = this + + return ( + + +
+ +
+
+ ) + } +}) + +export default VersionModal diff --git a/seatunnel-ui/src/views/task/definition/components/version.module.scss b/seatunnel-ui/src/views/task/definition/components/version.module.scss new file mode 100644 index 000000000..2da65cac8 --- /dev/null +++ b/seatunnel-ui/src/views/task/definition/components/version.module.scss @@ -0,0 +1,22 @@ +/* + * 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. + */ + +.pagination { + margin-top: 20px; + display: flex; + justify-content: center; +} diff --git a/seatunnel-ui/src/views/task/definition/index.module.scss b/seatunnel-ui/src/views/task/definition/index.module.scss new file mode 100644 index 000000000..756290d16 --- /dev/null +++ b/seatunnel-ui/src/views/task/definition/index.module.scss @@ -0,0 +1,44 @@ +/* + * 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. + */ + +.search-card { + display: flex; + justify-content: space-between; + align-items: center; + + .box { + display: flex; + justify-content: flex-end; + align-items: center; + width: 300px; + + button, + input { + margin-left: 10px; + } + } +} + +.table-card { + margin-top: 8px; + + .pagination { + margin-top: 20px; + display: flex; + justify-content: center; + } +} diff --git a/seatunnel-ui/src/views/task/definition/index.tsx b/seatunnel-ui/src/views/task/definition/index.tsx new file mode 100644 index 000000000..d09682d43 --- /dev/null +++ b/seatunnel-ui/src/views/task/definition/index.tsx @@ -0,0 +1,236 @@ +/* + * 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, toRefs, watch } from 'vue' +import { useRoute } from 'vue-router' +import { + NButton, + NCard, + NDataTable, + NIcon, + NInput, + NPagination, + NSelect, + NSpace +} from 'naive-ui' +import { SearchOutlined, ReloadOutlined } from '@vicons/antd' +import { useI18n } from 'vue-i18n' +import { useTable } from './use-table' +import { useTask } from './use-task' +import { useTaskTypeStore } from '@/store/project' +import Card from '@/components/card' +import VersionModal from './components/version-modal' +import MoveModal from './components/move-modal' +import TaskModal from '@/views/projects/task/components/node/detail-modal' +import styles from './index.module.scss' +import type { INodeData } from './types' + +const TaskDefinition = defineComponent({ + name: 'task-definition', + setup() { + const route = useRoute() + const projectCode = Number(route.params.projectCode) + const { t } = useI18n() + const taskTypeStore = useTaskTypeStore() + + const { task, onToggleShow, onTaskSave, onEditTask, onInitTask } = + useTask(projectCode) + + const { variables, getTableData, createColumns } = useTable(onEditTask) + + const requestData = () => { + getTableData({ + pageSize: variables.pageSize, + pageNo: variables.page, + searchTaskName: variables.searchTaskName, + searchWorkflowName: variables.searchWorkflowName, + taskType: variables.taskType + }) + } + + const onUpdatePageSize = () => { + variables.page = 1 + requestData() + } + + const onSearch = () => { + variables.page = 1 + requestData() + } + + const onReset = () => { + variables.searchTaskName = null + variables.searchWorkflowName = null + variables.taskType = null + } + + const onRefresh = () => { + variables.showVersionModalRef = false + variables.showMoveModalRef = false + requestData() + } + const onCreate = () => { + onToggleShow(true) + } + const onTaskCancel = () => { + onToggleShow(false) + onInitTask() + } + const onTaskSubmit = async (params: { data: INodeData }) => { + const result = await onTaskSave(params.data) + if (result) { + onTaskCancel() + onRefresh() + } + } + onMounted(() => { + createColumns(variables) + requestData() + }) + + watch(useI18n().locale, () => { + createColumns(variables) + }) + + return { + t, + ...toRefs(variables), + ...toRefs(task), + onSearch, + onReset, + requestData, + onUpdatePageSize, + onRefresh, + onCreate, + onTaskSubmit, + onTaskCancel, + projectCode, + taskTypes: taskTypeStore.getTaskType + } + }, + render() { + const { + t, + onSearch, + onReset, + requestData, + onUpdatePageSize, + onRefresh, + onCreate, + loadingRef + } = this + + return ( + <> + +
+
+ + {t('project.task.create_task')} + +
+ + + + {this.taskTypes.length && ( + ({ + value: item.type, + label: item.alias + }))} + placeholder={t('project.task.task_type')} + style={{ width: '180px' }} + clearable + /> + )} + + + + + + + {{ + icon: () => ( + + + + ) + }} + + +
+
+ + +
+ +
+
+ (this.showVersionModalRef = false)} + onRefresh={onRefresh} + /> + (this.showMoveModalRef = false)} + onRefresh={onRefresh} + /> + + + ) + } +}) + +export default TaskDefinition diff --git a/seatunnel-ui/src/views/task/definition/types.ts b/seatunnel-ui/src/views/task/definition/types.ts new file mode 100644 index 000000000..c15c84035 --- /dev/null +++ b/seatunnel-ui/src/views/task/definition/types.ts @@ -0,0 +1,27 @@ +/* + * 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 type { ITaskData, INodeData } from '../components/node/types' +export type { ISingleSaveReq } from '@/service/modules/task-definition/types' + +interface IRecord { + processDefinitionCode: number + taskCode: number + taskName: string +} + +export { IRecord } diff --git a/seatunnel-ui/src/views/task/definition/use-table.ts b/seatunnel-ui/src/views/task/definition/use-table.ts new file mode 100644 index 000000000..7163b81b1 --- /dev/null +++ b/seatunnel-ui/src/views/task/definition/use-table.ts @@ -0,0 +1,317 @@ +/* + * 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 { useAsyncState } from '@vueuse/core' +import { reactive, h, ref } from 'vue' +import { NButton, NIcon, NPopconfirm, NSpace, NTag, NTooltip } from 'naive-ui' +import ButtonLink from '@/components/button-link' +import { useI18n } from 'vue-i18n' +import { + DeleteOutlined, + EditOutlined, + ExclamationCircleOutlined +} from '@vicons/antd' +import { + queryTaskDefinitionListPaging, + deleteTaskDefinition +} from '@/service/modules/task-definition' +import { useRoute } from 'vue-router' +import { + COLUMN_WIDTH_CONFIG, + calculateTableWidth, + DefaultTableWidth +} from '@/common/column-width-config' +import type { + TaskDefinitionItem, + TaskDefinitionRes +} from '@/service/modules/task-definition/types' +import type { IRecord } from './types' + +export function useTable(onEdit: Function) { + const { t } = useI18n() + const route = useRoute() + const projectCode = Number(route.params.projectCode) + + const createColumns = (variables: any) => { + variables.columns = [ + { + title: t('project.task.task_name'), + key: 'taskName', + render: (row: IRecord) => + h( + ButtonLink, + { + onClick: () => void onEdit(row, true) + }, + { default: () => row.taskName } + ), + ...COLUMN_WIDTH_CONFIG['name'] + }, + { + title: t('project.task.workflow_name'), + key: 'processDefinitionName', + ...COLUMN_WIDTH_CONFIG['name'] + }, + { + title: t('project.task.workflow_state'), + key: 'processReleaseState', + render: (row: any) => { + if (row.processReleaseState === 'OFFLINE') { + return h(NTag, { type: 'error', size: 'small' }, () => + t('project.task.offline') + ) + } else if (row.processReleaseState === 'ONLINE') { + return h(NTag, { type: 'info', size: 'small' }, () => + t('project.task.online') + ) + } + }, + width: 130 + }, + { + title: t('project.task.task_type'), + key: 'taskType', + ...COLUMN_WIDTH_CONFIG['type'] + }, + { + title: t('project.task.version'), + key: 'taskVersion', + render: (row: TaskDefinitionItem) => + h('span', null, 'v' + row.taskVersion), + ...COLUMN_WIDTH_CONFIG['version'] + }, + { + title: t('project.task.upstream_tasks'), + key: 'upstreamTaskMap', + render: (row: TaskDefinitionItem) => + row.upstreamTaskMap.map((item: string, index: number) => { + return h('p', null, { default: () => `[${index + 1}] ${item}` }) + }), + ...COLUMN_WIDTH_CONFIG['name'] + }, + { + title: t('project.task.create_time'), + key: 'taskCreateTime', + ...COLUMN_WIDTH_CONFIG['time'] + }, + { + title: t('project.task.update_time'), + key: 'taskUpdateTime', + ...COLUMN_WIDTH_CONFIG['time'] + }, + { + title: t('project.task.operation'), + key: 'operation', + ...COLUMN_WIDTH_CONFIG['operation'](4), + render(row: any) { + return h(NSpace, null, { + default: () => [ + h( + NTooltip, + {}, + { + trigger: () => + h( + NButton, + { + circle: true, + type: 'info', + size: 'small', + disabled: + ['CONDITIONS', 'SWITCH'].includes(row.taskType) || + (!!row.processDefinitionCode && + row.processReleaseState === 'ONLINE'), + onClick: () => { + onEdit(row, false) + } + }, + { + icon: () => + h(NIcon, null, { default: () => h(EditOutlined) }) + } + ), + default: () => t('project.task.edit') + } + ), + // h( + // NTooltip, + // {}, + // { + // trigger: () => + // h( + // NButton, + // { + // circle: true, + // type: 'info', + // size: 'small', + // disabled: + // !!row.processDefinitionCode && + // row.processReleaseState === 'ONLINE', + // onClick: () => { + // variables.showMoveModalRef = true + // variables.row = row + // } + // }, + // { + // icon: () => + // h(NIcon, null, { default: () => h(DragOutlined) }) + // } + // ), + // default: () => t('project.task.move') + // } + // ), + h( + NTooltip, + {}, + { + trigger: () => + h( + NButton, + { + circle: true, + type: 'info', + size: 'small', + onClick: () => { + variables.showVersionModalRef = true + variables.row = row + } + }, + { + icon: () => + h(NIcon, null, { + default: () => h(ExclamationCircleOutlined) + }) + } + ), + default: () => t('project.task.version') + } + ), + h( + NPopconfirm, + { + onPositiveClick: () => { + handleDelete(row) + } + }, + { + trigger: () => + h( + NTooltip, + {}, + { + trigger: () => + h( + NButton, + { + circle: true, + type: 'error', + size: 'small', + disabled: + !!row.processDefinitionCode && + row.processReleaseState === 'ONLINE' + }, + { + icon: () => + h(NIcon, null, { + default: () => h(DeleteOutlined) + }) + } + ), + default: () => t('project.task.delete') + } + ), + default: () => t('project.task.delete_confirm') + } + ) + ] + }) + } + } + ] + if (variables.tableWidth) { + variables.tableWidth = calculateTableWidth(variables.columns) + } + } + + const variables = reactive({ + columns: [], + tableWidth: DefaultTableWidth, + tableData: [], + page: ref(1), + pageSize: ref(10), + searchTaskName: ref(null), + searchWorkflowName: ref(null), + totalPage: ref(1), + taskType: ref(null), + showVersionModalRef: ref(false), + showMoveModalRef: ref(false), + row: {}, + loadingRef: ref(false) + }) + + const handleDelete = (row: any) => { + deleteTaskDefinition({ code: row.taskCode }, { projectCode }).then(() => { + getTableData({ + pageSize: variables.pageSize, + pageNo: + variables.tableData.length === 1 && variables.page > 1 + ? variables.page - 1 + : variables.page, + searchTaskName: variables.searchTaskName, + searchWorkflowName: variables.searchWorkflowName, + taskType: variables.taskType + }) + }) + } + + const getTableData = (params: any) => { + if (variables.loadingRef) return + variables.loadingRef = true + const { state } = useAsyncState( + queryTaskDefinitionListPaging({ ...params, projectCode }) + .then((res: TaskDefinitionRes) => { + variables.tableData = res.totalList.map((item, unused) => { + if (Object.keys(item.upstreamTaskMap).length > 0) { + item.upstreamTaskMap = Object.keys(item.upstreamTaskMap).map( + (code) => item.upstreamTaskMap[code] + ) + } else { + item.upstreamTaskMap = [] + } + + return { + ...item + } + }) as any + variables.totalPage = res.totalPage + variables.loadingRef = false + }) + .catch(() => { + variables.loadingRef = false + }), + {} + ) + + return state + } + + return { + variables, + getTableData, + createColumns + } +} diff --git a/seatunnel-ui/src/views/task/definition/use-task.ts b/seatunnel-ui/src/views/task/definition/use-task.ts new file mode 100644 index 000000000..a39903c9c --- /dev/null +++ b/seatunnel-ui/src/views/task/definition/use-task.ts @@ -0,0 +1,108 @@ +/* + * 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 { + genTaskCodeList, + saveSingle, + queryTaskDefinitionByCode, + updateWithUpstream +} from '@/service/modules/task-definition' +import { formatParams as formatData } from '../components/node/format-data' +import type { ITaskData, INodeData, ISingleSaveReq, IRecord } from './types' + +export function useTask(projectCode: number) { + const initalTask = { + taskType: 'SHELL' + } as ITaskData + const task = reactive({ + taskShow: false, + taskData: { ...initalTask }, + taskSaving: false, + taskReadonly: false + } as { taskShow: boolean; taskData: ITaskData; taskSaving: boolean; taskReadonly: boolean }) + + const formatParams = (data: INodeData, isCreate: boolean): ISingleSaveReq => { + const params = formatData(data) + if (isCreate) { + return { + processDefinitionCode: params.processDefinitionCode, + upstreamCodes: params.upstreamCodes, + taskDefinitionJsonObj: JSON.stringify(params.taskDefinitionJsonObj) + } + } + return { + upstreamCodes: params.upstreamCodes, + taskDefinitionJsonObj: JSON.stringify(params.taskDefinitionJsonObj) + } + } + + const getTaskCode = async () => { + const result = await genTaskCodeList(1, projectCode) + return result[0] + } + + const onToggleShow = (show: boolean) => { + task.taskShow = show + } + const onTaskSave = async (data: INodeData) => { + if (task.taskSaving) return + task.taskSaving = true + try { + if (data.id) { + data.code && + (await updateWithUpstream( + projectCode, + data.code, + formatParams({ ...data, code: data.code }, false) + )) + } else { + const taskCode = await getTaskCode() + await saveSingle({ + ...formatParams({ ...data, code: taskCode }, true), + projectCode + }) + } + + task.taskSaving = false + return true + } catch (err) { + task.taskSaving = false + return false + } + } + + const onEditTask = async (row: IRecord, readonly: boolean) => { + const result = await queryTaskDefinitionByCode(row.taskCode, projectCode) + task.taskData = { ...result, processName: row.processDefinitionCode } + task.taskShow = true + task.taskReadonly = readonly + } + + const onInitTask = () => { + task.taskData = { ...initalTask } + task.taskReadonly = false + } + + return { + task, + onToggleShow, + onTaskSave, + onEditTask, + onInitTask + } +} diff --git a/seatunnel-ui/src/service/virtual-tables/index.ts b/seatunnel-ui/src/views/task/instance/index.module.scss similarity index 81% rename from seatunnel-ui/src/service/virtual-tables/index.ts rename to seatunnel-ui/src/views/task/instance/index.module.scss index 1943eab2f..e6b0dfe01 100644 --- a/seatunnel-ui/src/service/virtual-tables/index.ts +++ b/seatunnel-ui/src/views/task/instance/index.module.scss @@ -15,12 +15,12 @@ * limitations under the License. */ -import { axios } from '@/service/service' +.table-card { + margin-top: 8px; -export function virtualTableList(params: any): any { - return axios({ - url: '/virtual_table/list', - method: 'get', - params - }) + .pagination { + margin-top: 20px; + display: flex; + justify-content: center; + } } diff --git a/seatunnel-ui/src/views/task/instance/index.tsx b/seatunnel-ui/src/views/task/instance/index.tsx new file mode 100644 index 000000000..9d774223a --- /dev/null +++ b/seatunnel-ui/src/views/task/instance/index.tsx @@ -0,0 +1,492 @@ +/* + * 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, + reactive, + onMounted, + onUnmounted, + toRefs, + watch +} from 'vue' +import { + NSpace, + NInput, + NSelect, + NDatePicker, + NButton, + NIcon, + NDataTable, + NPagination, + NCard, + NDropdown +} from 'naive-ui' +import { SearchOutlined, ReloadOutlined } from '@vicons/antd' +import { useTable } from './use-table' +import { useI18n } from 'vue-i18n' +import { stateType } from '@/common/common' +import Card from '@/components/card' +import LogModal from '@/components/log-modal' +import { useAsyncState } from '@vueuse/core' +import { queryLog } from '@/service/modules/log' +import styles from './index.module.scss' +import { LogRes } from '@/service/modules/log/types' +import ColumnSelector from '../../components/column-selector' +import ProjectSelector from '@/views/projects/components/projectSelector' +import { useProjectStore } from '@/store/project' +import { getRangeShortCuts } from '@/utils/timePickeroption' +import DependentChainModal from '@/components/dependent-chain-modal' +import DependentTaskModal from '@/components/dependent-task-modal' +import { DownOutlined } from '@vicons/antd' + +let initialState: any = null + +const TaskInstance = defineComponent({ + name: 'task-instance', + setup() { + const { + t, + variables, + getTableData, + createColumns, + onSearch, + onDownloadLogs, + onBatchCoronation, + onBatchIsolation, + batchBtnListClick, + creatInstanceButtons, + locale, + handleDependentChain, + handleBatchCleanState, + handleCheckTaskDependentChain + } = useTable() + let logTimer: number + const requestTableData = () => { + if (initialState === null) { + initialState = { + searchVal: variables.searchVal, + host: variables.host, + flag: variables.flag, + stateType: variables.stateType, + datePickerRange: variables.datePickerRange, + executorName: variables.executorName, + processInstanceName: variables.processInstanceName + } + } + getTableData({ + pageSize: variables.pageSize, + pageNo: variables.page, + searchVal: variables.searchVal, + processInstanceId: variables.processInstanceId, + host: variables.host, + flag: variables.flag, + stateType: variables.stateType, + datePickerRange: variables.datePickerRange, + executorName: variables.executorName, + processInstanceName: variables.processInstanceName + }) + } + + const tableColumn = ref([]) as any + + const rangeShortCuts = reactive({ + rangeOption: {} + }) + + rangeShortCuts.rangeOption = getRangeShortCuts(t) + const onReset = (): any => { + variables.searchVal = initialState.searchVal + variables.host = initialState.host + variables.flag = initialState.flag + variables.stateType = initialState.stateType + variables.datePickerRange = initialState.datePickerRange + variables.executorName = initialState.executorName + variables.processInstanceName = initialState.processInstanceName + variables.projectCode = useProjectStore().getCurrentProject + } + + const onUpdatePageSize = () => { + variables.page = 1 + requestTableData() + } + + const onConfirmModal = () => { + variables.showModalRef = false + } + + const getLogs = (row: any) => { + const { state } = useAsyncState( + queryLog({ + taskInstanceId: Number(row.id), + limit: variables.limit, + skipLineNum: variables.skipLineNum + }).then((res: LogRes) => { + if (res.log) { + variables.logRef += res.log + } + if (res.hasNext) { + variables.limit += 1000 + variables.skipLineNum += 1000 + clearTimeout(logTimer) + logTimer = setTimeout(() => { + getLogs(row) + }, 2000) + } else { + variables.logLoadingRef = false + } + }), + {} + ) + + return state + } + + const handleChangeColumn = (options: any) => { + tableColumn.value = options + } + + const refreshLogs = (row: any) => { + variables.logRef = '' + variables.limit = 1000 + variables.skipLineNum = 0 + getLogs(row) + } + + const handleBatchCoronation = () => { + onBatchCoronation() + } + + const handleBatchIsolation = () => { + onBatchIsolation() + } + + const handleConfirmDependentChainModal = () => { + handleDependentChain() + } + + const handleCancelDependentChainModal = () => { + variables.dependentChainShow = false + } + + const handleConfirmDependentTaskModal = (taskIds: number[]) => { + variables.dependentTasks = variables.dependentTasks.filter((task: any) => + taskIds.includes(task.id) + ) + handleDependentChain() + } + + const handleCancelDependentTaskModal = () => { + variables.dependentTaskShow = false + } + + const getFlag = () => [ + { label: t('project.task.newest'), value: 'YES' }, + { label: t('project.task.history'), value: 'NO' } + ] + + onMounted(() => { + createColumns(variables) + creatInstanceButtons(variables) + requestTableData() + }) + + onUnmounted(() => { + clearTimeout(logTimer) + }) + + watch(useI18n().locale, () => { + createColumns(variables) + creatInstanceButtons(variables) + }) + + watch( + () => variables.showModalRef, + () => { + if (variables.showModalRef) { + getLogs(variables.row) + } else { + variables.row = {} + variables.logRef = '' + variables.logLoadingRef = true + variables.skipLineNum = 0 + variables.limit = 1000 + clearTimeout(logTimer) + } + } + ) + + const getProjectCodeList = (codes: any) => { + if (!codes) { + variables.projectCode = useProjectStore().getGolbalProject + } else { + variables.projectCode = [codes] + } + } + watch( + () => locale.value, + () => { + rangeShortCuts.rangeOption = getRangeShortCuts(t) + } + ) + + return { + t, + ...toRefs(variables), + requestTableData, + onUpdatePageSize, + getProjectCodeList, + onSearch, + onReset, + onConfirmModal, + refreshLogs, + onDownloadLogs, + tableColumn, + handleChangeColumn, + handleBatchCoronation, + handleBatchIsolation, + batchBtnListClick, + rangeShortCuts, + handleBatchCleanState, + handleConfirmDependentChainModal, + handleCancelDependentChainModal, + handleConfirmDependentTaskModal, + handleCancelDependentTaskModal, + handleCheckTaskDependentChain, + getFlag + } + }, + render() { + const { + t, + requestTableData, + onUpdatePageSize, + onSearch, + onReset, + onConfirmModal, + loadingRef, + refreshLogs, + onDownloadLogs + } = this + + return ( + <> + + + {this.globalProject && ( + + )} + + + + + + + + + + + + + + + + + + + + + + {{ + 'header-extra': () => ( + + + + + {t('project.workflow.operation')} + + + + + + {/* + {{ + default: () => t('project.workflow.coronation'), + trigger: () => ( + + + {{ + default: () => + t('project.workflow.coronation_confirm'), + trigger: () => t('project.workflow.coronation') + }} + + + ) + }} + + + {{ + default: () => t('project.workflow.isolation'), + trigger: () => ( + + + {{ + default: () => + t('project.workflow.pause_recovery_confirm'), + trigger: () => t('project.workflow.isolation') + }} + + + ) + }} + */} + + ), + default: () => ( + + row.id} + loading={loadingRef} + columns={this.columns} + data={this.tableData} + scrollX={this.tableWidth} + v-model:checked-row-keys={this.checkedRowKeys} + /> + + + + + ) + }} + + + + + + ) + } +}) + +export default TaskInstance diff --git a/seatunnel-ui/src/views/task/instance/types.ts b/seatunnel-ui/src/views/task/instance/types.ts new file mode 100644 index 000000000..12c6a1809 --- /dev/null +++ b/seatunnel-ui/src/views/task/instance/types.ts @@ -0,0 +1,37 @@ +/* + * 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 { ITaskState } from '@/common/types' + +export type { Router } from 'vue-router' +export type { TaskInstancesRes } from '@/service/modules/task-instances/types' + +interface IRecord { + name: string + processInstanceName: string + executorName: string + taskType: string + state: ITaskState + submitTime: string + startTime: string + endTime: string + duration?: string + retryTimes: number + dryRun: number + host: string +} + +export { ITaskState, IRecord } diff --git a/seatunnel-ui/src/views/task/instance/use-table.ts b/seatunnel-ui/src/views/task/instance/use-table.ts new file mode 100644 index 000000000..6a85c0de4 --- /dev/null +++ b/seatunnel-ui/src/views/task/instance/use-table.ts @@ -0,0 +1,725 @@ +/* + * 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 { useI18n } from 'vue-i18n' +import { h, reactive, ref } from 'vue' +import { useAsyncState } from '@vueuse/core' +import { + queryTaskListPaging, + forceSuccess, + downloadLog, + cleanState, + checkDependentChain, + dependentChainRerun +} from '@/service/modules/task-instances' +import { NIcon, NTooltip, NSpin, NButton, NDropdown, NTag } from 'naive-ui' +import { + AlignLeftOutlined, + CheckCircleOutlined, + ClearOutlined, + PaperClipOutlined, + PartitionOutlined +} from '@vicons/antd' +import { useRoute, useRouter } from 'vue-router' +import { parseTime, renderTableTime, tasksState } from '@/common/common' +import { + COLUMN_WIDTH_CONFIG, + calculateTableWidth, + DefaultTableWidth +} from '@/common/column-width-config' +import { mergedPath } from '@/common/path' +import { useTableLink, useTableOperation } from '@/hooks' +import { getTime, format, subHours, addHours } from 'date-fns' +import { throttle } from 'lodash' +import { submitIsolationTasks } from '@/service/modules/isolation-task' +import type { Router, TaskInstancesRes, IRecord, ITaskState } from './types' +import { + submitTaskCoronation, + cleanStateByIds, + forcedSuccessByIds +} from '@/service/modules/task-coronation' +import { useProjectStore } from '@/store/project' +import { changeProject } from '../../utils/changeProject' + +export function useTable() { + const { t, locale } = useI18n() + const route = useRoute() + const router: Router = useRouter() + const projectCode = + route.params.projectCode || useProjectStore().getCurrentProject[0] + const date = route.query.date + ? (route.query.date as string).split(',') + : route.query.date === undefined + ? [getTime(subHours(Date.now(), 23)), getTime(addHours(Date.now(), 1))] + : [] + const projectStore = useProjectStore() + const variables = reactive({ + columns: [], + checkedRowKeys: [] as Array, + tableWidth: DefaultTableWidth, + tableData: [] as IRecord[], + page: ref(1), + pageSize: ref(10), + searchVal: (route.query.searchVal as string) || null, + processInstanceId: route.query.processInstanceId + ? Number(route.query.processInstanceId) + : null, + host: (route.query.host as string) || null, + flag: (route.query.flag as string) || null, + stateType: (route.query.stateType as string) || null, + datePickerRange: ref( + date.length === 2 ? [Number(date[0]), Number(date[1])] : null + ), + executorName: (route.query.executorName as string) || null, + processInstanceName: (route.query.processInstanceName as string) || null, + totalPage: ref(1), + showModalRef: ref(false), + row: {}, + loadingRef: ref(false), + logRef: '', + logLoadingRef: ref(true), + skipLineNum: ref(0), + limit: ref(1000), + buttonList: [], + projectCode: + route.query.searchProjectCode || projectStore.getCurrentProject, + dependentChainSaving: false, + dependentTaskShow: false, + dependentChainShow: false, + dependentChainName: '', + dependentTasks: [] as any, + runWorkflows: [] as any, + skipWorkflows: [] as any, + globalProject: projectStore.getGlobalFlag + }) + + const createColumns = (variables: any) => { + variables.columns = [ + { + type: 'selection', + className: 'btn-selected', + ...COLUMN_WIDTH_CONFIG['selection'] + }, + { + title: t('project.task.task_name'), + key: 'name', + ...COLUMN_WIDTH_CONFIG['name'] + }, + useTableLink( + { + title: t('project.task.workflow_instance'), + key: 'processInstanceName', + ...COLUMN_WIDTH_CONFIG['link_name'], + button: { + permission: 'project:process-instance:update', + getHref: (row: any) => { + return mergedPath( + `projects/${row.projectCode}/workflow/instances/${ + row.processInstanceId + }?code=${row.projectCode}&project=${ + (route.query.project as string) || 'all' + }&global=${String(projectStore.getGlobalFlag)}` + ) + } + } + }, + 'project' + ), + useTableLink({ + title: t('project.project_name'), + key: 'projectName', + ...COLUMN_WIDTH_CONFIG['link_name'], + button: { + onClick: (row: any) => { + changeProject(row.projectCode) + router.push({ + path: route.path, + query: { + project: row.projectCode, + global: 'false' + } + }) + } + } + }), + { + title: t('project.task.executor'), + key: 'executorName', + ...COLUMN_WIDTH_CONFIG['name'] + }, + { + title: t('project.task.node_type'), + key: 'taskType', + ...COLUMN_WIDTH_CONFIG['type'] + }, + { + title: t('project.task.flag'), + key: 'flag', + ...COLUMN_WIDTH_CONFIG['type'], + render: (rowData: any) => + h( + NTag, + { + size: 'small', + round: true, + type: rowData.flag === 'YES' ? 'info' : 'default' + }, + { + default: () => + rowData.flag === 'YES' + ? t('project.task.newest') + : t('project.task.history') + } + ) + }, + { + title: t('project.task.state'), + key: 'state', + ...COLUMN_WIDTH_CONFIG['state'], + render: (row: IRecord) => renderStateCell(row.state, t) + }, + { + title: t('project.task.submit_time'), + ...COLUMN_WIDTH_CONFIG['time'], + key: 'submitTime', + render: (row: IRecord) => renderTableTime(row.submitTime) + }, + { + title: t('project.task.start_time'), + ...COLUMN_WIDTH_CONFIG['time'], + key: 'startTime', + render: (row: IRecord) => renderTableTime(row.startTime) + }, + { + title: t('project.task.end_time'), + ...COLUMN_WIDTH_CONFIG['time'], + key: 'endTime', + render: (row: IRecord) => renderTableTime(row.endTime) + }, + { + title: t('project.task.duration'), + key: 'duration', + ...COLUMN_WIDTH_CONFIG['duration'], + render: (row: any) => h('span', null, row.duration ? row.duration : '-') + }, + { + title: t('project.task.retry_count'), + key: 'retryTimes', + ...COLUMN_WIDTH_CONFIG['times'] + }, + { + title: t('project.task.dry_run_flag'), + key: 'dryRun', + ...COLUMN_WIDTH_CONFIG['dryRun'], + render: (row: IRecord) => (row.dryRun === 1 ? 'YES' : 'NO') + }, + { + title: t('project.task.host'), + key: 'host', + ...COLUMN_WIDTH_CONFIG['name'], + render: (row: IRecord) => row.host || '-' + }, + useTableOperation( + { + title: t('project.task.operation'), + noPermission: projectStore.getGlobalFlag, + key: 'operation', + itemNum: 4, + buttons: [ + { + text: t('project.task.clean_state'), + permission: 'project:task-instance:clean-state', + isCustom: true, + customFunc: (rowData: any) => { + return h( + NDropdown, + { + trigger: 'click', + placement: 'bottom-end', + options: [ + { + label: t('project.task.only_current'), + key: 'CURRENT' + }, + { + label: t('project.task.current_downstream'), + key: 'DOWNSTREAM' + }, + { + label: t('project.task.cascaded_dependency_chain'), + key: 'CHAIN' + } + ], + onSelect: (key) => handleCleanState(key, rowData) + }, + { + default: () => + h(NTooltip, null, { + trigger: () => + h( + NButton, + { + tag: 'div', + circle: true, + size: 'small', + type: 'info', + disabled: + rowData.state === 'RUNNING_EXECUTION' || + rowData.isEdit === false + }, + { + default: () => + h(NIcon, null, { + default: () => h(ClearOutlined) + }) + } + ), + default: () => t('project.task.clean_state') + }) + } + ) + } + }, + { + text: t('project.task.forced_success'), + permission: 'project:task-instance:force-success', + icon: h(CheckCircleOutlined), + onClick: (rowData) => void handleForcedSuccess(rowData), + disabled: (rowData) => + !( + rowData.state === 'FAILURE' || + rowData.state === 'NEED_FAULT_TOLERANCE' || + rowData.state === 'KILL' + ) + }, + { + text: t('project.task.view_log'), + icon: h(AlignLeftOutlined), + onClick: (rowData) => void handleLog(rowData), + disabled: (rowData) => !rowData.host + }, + { + text: t('project.workflow.impact_anaysis'), + permission: 'project:task-instance:lineage', + icon: h(PaperClipOutlined), + onClick: (rowData) => void gotoWorkflowImpactAnalysis(rowData) + } + ] + }, + 'project' + ) + ] + if (variables.tableWidth) { + variables.tableWidth = calculateTableWidth(variables.columns) + } + } + + const creatInstanceButtons = (variables: any) => { + variables.buttonList = [ + { + label: t('project.task.clean_state'), + key: 'clean_state', + children: [ + { + label: t('project.task.only_current'), + key: 'CURRENT' + }, + { + label: t('project.task.current_downstream'), + key: 'DOWNSTREAM' + }, + { + label: t('project.task.cascaded_dependency_chain'), + key: 'CHAIN' + } + ] + }, + { + label: t('project.task.forced_success'), + key: 'forced_success' + }, + { + type: 'divider', + key: 'd1' + }, + { + label: t('project.workflow.coronation'), + key: 'coronation' + }, + { + label: t('project.workflow.isolation'), + key: 'isolation' + } + ] + } + + const gotoWorkflowImpactAnalysis = (row: any) => { + const url = router.resolve({ + name: 'impact-analysis', + params: { + projectCode: row.projectCode + }, + query: { + taskCode: row.id, + projectCode: row.projectCode, + workflowCode: row.processInstanceId, + workflowType: 'instance' + } + }) + window.open(url.href) + } + + const handleLog = (row: any) => { + variables.showModalRef = true + variables.row = row + } + + let cleaning = false + const handleCleanState = throttle((key: string, row: any) => { + if (cleaning) return + if (key === 'CURRENT' || key === 'DOWNSTREAM') { + cleaning = true + cleanState(row.projectCode, [row.id], key === 'DOWNSTREAM') + .then(() => { + window.$message.success(t('project.workflow.success')) + getList() + }) + .finally(() => { + cleaning = false + }) + } else if (key === 'CHAIN') { + variables.runWorkflows = [] + variables.skipWorkflows = [] + variables.loadingRef = true + checkDependentChain(projectCode, [row.id]).then((res: any) => { + variables.dependentChainName = '' + variables.dependentChainShow = true + variables.dependentTasks = [{ id: row.id, name: row.name }] + variables.loadingRef = false + variables.runWorkflows = res.runWorkflows.map((workflow: any) => ({ + label: workflow.workflowName, + key: workflow.workflowName, + prefix: () => h(NIcon, null, { default: () => h(PartitionOutlined) }), + children: workflow.tasks.map((task: string) => ({ + label: task, + key: task + })) + })) + variables.skipWorkflows = res.skipWorkflows.map((workflow: any) => ({ + label: workflow.workflowName, + key: workflow.workflowName, + prefix: () => h(NIcon, null, { default: () => h(PartitionOutlined) }), + children: workflow.tasks.map((task: string) => ({ + label: task, + key: task + })) + })) + }) + } + }, 3000) + + const handleDependentChain = () => { + const taskInstanceIds = variables.dependentTasks.map((task: any) => task.id) + variables.dependentChainSaving = true + dependentChainRerun(projectCode, taskInstanceIds) + .then(() => { + variables.checkedRowKeys = [] + window.$message.success(t('project.workflow.success')) + getList() + }) + .finally(() => { + variables.dependentTaskShow = false + variables.dependentChainShow = false + variables.dependentChainSaving = false + }) + } + + const handleForcedSuccess = (row: any) => { + forceSuccess({ id: row.id }, { projectCode: row.projectCode }).then(() => { + getList() + }) + } + + const getTableData = (params: any) => { + if ( + variables.loadingRef || + !variables.projectCode || + variables.projectCode.length === 0 || + typeof variables.projectCode[0] === 'undefined' + ) + return + variables.loadingRef = true + const data = { + pageSize: params.pageSize, + pageNo: params.pageNo, + searchVal: params.searchVal, + processInstanceId: params.processInstanceId, + host: params.host, + flag: params.flag, + stateType: params.stateType, + startDate: params.datePickerRange + ? format(parseTime(params.datePickerRange[0]), 'yyyy-MM-dd HH:mm:ss') + : '', + endDate: params.datePickerRange + ? format(parseTime(params.datePickerRange[1]), 'yyyy-MM-dd HH:mm:ss') + : '', + executorName: params.executorName, + processInstanceName: params.processInstanceName, + projectCodes: variables.projectCode + } + + const { state } = useAsyncState( + queryTaskListPaging(data) + .then((res: TaskInstancesRes) => { + variables.tableData = res.totalList as any + variables.totalPage = res.totalPage + variables.loadingRef = false + }) + .catch(() => { + variables.loadingRef = false + }), + {} + ) + + return state + } + const onSearch = () => { + const query = {} as { + searchVal?: string + processInstanceName?: string + host?: string + executorName?: string + flag?: string + stateType?: string + date?: string + project?: string + } + if (variables.searchVal) query.searchVal = variables.searchVal + if (variables.processInstanceName) + query.processInstanceName = variables.processInstanceName + if (variables.host) query.host = variables.host + if (variables.executorName) query.executorName = variables.executorName + if (variables.flag) query.flag = variables.flag + if (variables.stateType) query.stateType = variables.stateType + query.date = variables.datePickerRange + ? variables.datePickerRange.join(',') + : '' + router.replace({ + name: 'task-instance', + params: { + projectCode: route.params.projectCode + }, + query: { + ...query, + project: route.query.project, + global: route.query.global, + searchProjectCode: variables.projectCode + } + }) + } + + const getList = () => { + getTableData({ + pageSize: variables.pageSize, + pageNo: + variables.tableData.length === 1 && variables.page > 1 + ? variables.page - 1 + : variables.page, + searchVal: variables.searchVal, + processInstanceId: variables.processInstanceId, + host: variables.host, + flag: variables.flag, + stateType: variables.stateType, + datePickerRange: variables.datePickerRange, + executorName: variables.executorName, + processInstanceName: variables.processInstanceName + }) + } + + const onDownloadLogs = (row: { id: number }) => { + downloadLog(row.id) + } + + const getTaskList = () => { + return variables.tableData.filter((row: any) => + variables.checkedRowKeys.includes(row.id) + ) + } + + const onBatchCoronation = () => { + const tasks = getTaskList() + submitTaskCoronation(0, { + coronationTasks: tasks.map((task: any) => ({ + workflowInstanceId: task.processInstanceId, + workflowInstanceName: task.processInstanceName, + taskCode: task.taskCode, + taskNode: task.name + })) + }).then(() => { + window.$message.success(t('project.workflow.success')) + variables.checkedRowKeys = [] + getList() + }) + } + + const onBatchIsolation = () => { + const tasks = getTaskList() + submitIsolationTasks( + tasks.map((task: any) => ({ + workflowInstanceId: task.processInstanceId, + workflowInstanceName: task.processInstanceName, + taskCode: task.taskCode, + taskName: task.name + })), + 0 + ).then(() => { + window.$message.success(t('project.workflow.success')) + variables.checkedRowKeys = [] + getList() + }) + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars, no-unused-vars + const onBatchCleanState = () => { + cleanStateByIds(variables.checkedRowKeys).then(() => { + window.$message.success(t('project.workflow.success')) + variables.checkedRowKeys = [] + getList() + }) + } + const onBatchForcedSuccess = () => { + forcedSuccessByIds(variables.checkedRowKeys).then(() => { + window.$message.success(t('project.workflow.success')) + variables.checkedRowKeys = [] + getList() + }) + } + + const batchBtnListClick = (key: string) => { + if (variables.checkedRowKeys.length == 0) { + window.$message.warning(t('project.select_task_instance')) + return + } + switch (key) { + case 'coronation': + onBatchCoronation() + break + case 'isolation': + onBatchIsolation() + break + case 'CURRENT': + handleBatchCleanState() + break + case 'DOWNSTREAM': + handleBatchCleanState(true) + break + case 'CHAIN': + handleBatchCleanStateChain() + break + case 'forced_success': + onBatchForcedSuccess() + break + } + } + + const handleBatchCleanState = (cleanDownstream = false) => { + cleanState(projectCode, variables.checkedRowKeys, cleanDownstream).then( + () => { + variables.checkedRowKeys = [] + window.$message.success(t('project.workflow.success')) + getList() + } + ) + } + + const handleBatchCleanStateChain = () => { + variables.dependentTaskShow = true + variables.dependentTasks = variables.tableData.filter((rowData: any) => + variables.checkedRowKeys.includes(rowData.id) + ) + } + + const handleCheckTaskDependentChain = (taskId: number, taskName: string) => { + variables.runWorkflows = [] + variables.skipWorkflows = [] + checkDependentChain(projectCode, [taskId]).then((res: any) => { + variables.dependentChainShow = true + variables.dependentChainName = taskName + variables.runWorkflows = res.runWorkflows.map((workflow: any) => ({ + label: workflow.workflowName, + key: workflow.workflowName, + prefix: () => h(NIcon, null, { default: () => h(PartitionOutlined) }), + children: workflow.tasks.map((task: string) => ({ + label: task, + key: task + })) + })) + variables.skipWorkflows = res.skipWorkflows.map((workflow: any) => ({ + label: workflow.workflowName, + key: workflow.workflowName, + prefix: () => h(NIcon, null, { default: () => h(PartitionOutlined) }), + children: workflow.tasks.map((task: string) => ({ + label: task, + key: task + })) + })) + }) + } + + return { + t, + variables, + getTableData, + createColumns, + onSearch, + onDownloadLogs, + onBatchCoronation, + onBatchIsolation, + creatInstanceButtons, + batchBtnListClick, + locale, + handleBatchCleanState, + handleDependentChain, + handleCheckTaskDependentChain, + route + } +} + +export function renderStateCell(state: ITaskState, t: Function) { + if (!state) return '' + const stateOption = tasksState(t)[state] + if (!stateOption) return '' + const Icon = h( + NIcon, + { + color: stateOption.color, + class: stateOption.classNames, + style: { + display: 'flex' + }, + size: 20 + }, + () => h(stateOption.icon) + ) + return h(NTooltip, null, { + trigger: () => { + if (!stateOption.isSpin) return Icon + return h(NSpin, { size: 20 }, { icon: () => Icon }) + }, + default: () => stateOption.desc + }) +} diff --git a/seatunnel-ui/src/views/task/isolation/detail-modal.tsx b/seatunnel-ui/src/views/task/isolation/detail-modal.tsx new file mode 100644 index 000000000..5bd960e07 --- /dev/null +++ b/seatunnel-ui/src/views/task/isolation/detail-modal.tsx @@ -0,0 +1,171 @@ +/* + * 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, watch } from 'vue' +import { + NForm, + NFormItem, + NText, + NIcon, + NButton, + NUpload, + NUploadDragger, + NSpace, + NSteps, + NStep, + NDataTable, + NEllipsis +} from 'naive-ui' +import { useI18n } from 'vue-i18n' +import { useDetail } from './use-detail' +import { UploadOutlined } from '@vicons/antd' +import Modal from '@/components/modal' +import styles from './index.module.scss' + +const props = { + show: { + type: Boolean, + default: false + }, + projectCode: { + type: String, + default: '' + } +} +const DetailModal = defineComponent({ + name: 'DetailModal', + props, + emits: ['cancel', 'update'], + setup(props, ctx) { + const { t } = useI18n() + + const { state, formRef, rules, onReset, onConfirm, onFileChange } = + useDetail(Number(props.projectCode)) + + const onCancel = () => { + ctx.emit('cancel') + } + + const onSubmit = async () => { + const res = await onConfirm() + if (res) { + onCancel() + ctx.emit('update') + } + } + + watch( + () => props.show, + (value) => { + if (!value) onReset() + } + ) + + return () => ( + + {{ + default: () => ( + + + + + + {state.current === 1 && ( + + + + + + + + + {t('project.isolation.upload_tips')} + + + + + + )} + {state.current === 2 && ( + +
+ {t('project.isolation.total_items', { + total: state.tasks.length + })} +
+ ( + + {rowData.taskName} + + ) + }, + { + key: 'workflowInstanceName', + title: t('project.isolation.workflow_instance_name'), + render: (rowData) => ( + + {rowData.workflowInstanceName} + + ) + } + ]} + data={state.tasks} + max-height={300} + /> +
+ )} +
+ ), + 'btn-middle': () => + state.current === 2 && ( + void onConfirm(-1)} + > + {t('project.isolation.previous')} + + ) + }} +
+ ) + } +}) + +export default DetailModal diff --git a/seatunnel-ui/src/views/task/isolation/index.module.scss b/seatunnel-ui/src/views/task/isolation/index.module.scss new file mode 100644 index 000000000..3342046aa --- /dev/null +++ b/seatunnel-ui/src/views/task/isolation/index.module.scss @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +.width_100 { + width: 100%; +} +.detail { + padding-top: 20px; +} + +.uploader { + margin-top: 20px; + :global { + .n-upload-trigger { + width: 100%; + } + } +} diff --git a/seatunnel-ui/src/views/task/isolation/index.tsx b/seatunnel-ui/src/views/task/isolation/index.tsx new file mode 100644 index 000000000..e644a410a --- /dev/null +++ b/seatunnel-ui/src/views/task/isolation/index.tsx @@ -0,0 +1,208 @@ +/* + * 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 { + NButton, + NSpace, + NInput, + NIcon, + NDataTable, + NPagination, + NPopconfirm +} from 'naive-ui' +import { SearchOutlined, ReloadOutlined } from '@vicons/antd' +import Card from '@/components/card' +import DetailModal from './detail-modal' +import { useI18n } from 'vue-i18n' +import { useRoute } from 'vue-router' +import { useTable } from './use-table' +import { useColumns } from './use-columns' +import ProjectSelector from '../../components/projectSelector' +import { useProjectStore } from '@/store/project' + +const TaskIsolation = defineComponent({ + name: 'task-isolation', + setup() { + const { t, locale } = useI18n() + const route = useRoute() + const { + state, + onUpdateList, + onSearch, + onChangePage, + onChangePageSize, + onCallback, + onCancleIsolation + } = useTable() + const { columns } = useColumns(onCallback) + const showDetailModal = ref(false) + + const onReset = () => { + state.taskName = '' + state.workflowInstanceName = '' + state.projectCode = useProjectStore().getCurrentProject + } + + const onCreate = () => { + showDetailModal.value = true + } + const handleCancleIsolation = () => { + onCancleIsolation() + onSearch() + } + const onDownloadTemplate = () => { + window.location.href = `/dolphinscheduler/${ + locale.value === 'zh_CN' + ? 'isolation-task-excel-template-cn' + : 'isolation-task-excel-template-en' + }.xlsx` + } + + const handleKeyup = (event: KeyboardEvent) => { + if (event.key === 'Enter') { + onSearch() + } + } + const getProjectCodeList = (codes: any) => { + if (!codes) { + state.projectCode = useProjectStore().getGolbalProject + } else { + state.projectCode = [codes] + } + } + + return () => ( + + + {{ + default: () => ( + + + + {t('project.isolation.upload_isolation_tasks')} + + + {t('project.isolation.download_template')} + + + + {state.globalProject && ( + + )} + + + + + + + + + + + + + + + ) + }} + + + {{ + 'header-extra': () => ( + + + + {{ + default: () => + t('project.workflow.cancle_isolation__confirm'), + trigger: () => t('project.workflow.cancle_isolation') + }} + + + + ), + default: () => ( + + row.id} + v-model:checked-row-keys={state.checkedRowKeys} + striped + /> + + + + + ) + }} + + void (showDetailModal.value = false)} + onUpdate={onUpdateList} + /> + + ) + } +}) + +export default TaskIsolation diff --git a/seatunnel-ui/src/views/task/isolation/types.ts b/seatunnel-ui/src/views/task/isolation/types.ts new file mode 100644 index 000000000..0ab7acb0c --- /dev/null +++ b/seatunnel-ui/src/views/task/isolation/types.ts @@ -0,0 +1,33 @@ +/* + * 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 { ITaskState } from '@/common/types' +export type { TableColumns } from 'naive-ui/es/data-table/src/interface' +export type { DateItem } from 'naive-ui/es/calendar/src/interface' +export type { UploadCustomRequestOptions } from 'naive-ui' +export type { FileInfo } from 'naive-ui/es/upload/src/interface' + +export interface ExcelRamenReview { + date: string | number +} + +export interface IRecord { + workflowInstanceName: string + id: number + taskName: string + taskStatus: ITaskState + createTime?: string +} diff --git a/seatunnel-ui/src/views/task/isolation/use-columns.ts b/seatunnel-ui/src/views/task/isolation/use-columns.ts new file mode 100644 index 000000000..550c3cbac --- /dev/null +++ b/seatunnel-ui/src/views/task/isolation/use-columns.ts @@ -0,0 +1,117 @@ +/* + * 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, ref, watch, onMounted } from 'vue' +import { ClearOutlined } from '@vicons/antd' +import { useI18n } from 'vue-i18n' +import { useTableOperation } from '@/hooks' +import { + COLUMN_WIDTH_CONFIG, + calculateTableWidth, + DefaultTableWidth +} from '@/common/column-width-config' +import { renderStateCell } from '../instance/use-table' +import { renderTableTime } from '@/common/common' +import type { TableColumns, IRecord } from './types' +import { useTableLink } from '@/hooks' +import { changeProject } from '../../utils/changeProject' +import { useRoute, useRouter } from 'vue-router' + +export function useColumns(onCallback: Function) { + const { t } = useI18n() + const columns = ref() + const tableWidth = ref(DefaultTableWidth) + const route = useRoute() + const router = useRouter() + const getColumns = (): TableColumns => { + const columns = [ + { + type: 'selection', + className: 'btn-selected', + ...COLUMN_WIDTH_CONFIG['selection'] + }, + { + title: t('project.isolation.task_name'), + key: 'taskName', + ...COLUMN_WIDTH_CONFIG['name'] + }, + useTableLink({ + title: t('project.project_name'), + key: 'projectName', + ...COLUMN_WIDTH_CONFIG['link_name'], + button: { + onClick: (row: any) => { + changeProject(row.projectCode) + router.push({ + path: route.path, + query: { + project: String(row.projectCode), + global: 'false' + } + }) + } + } + }), + { + title: t('project.isolation.operation_status'), + key: 'taskStatus', + ...COLUMN_WIDTH_CONFIG['state'], + render: (rowData: IRecord) => renderStateCell(rowData.taskStatus, t) + }, + { + title: t('project.isolation.workflow_instance_name'), + key: 'workflowInstanceName', + ...COLUMN_WIDTH_CONFIG['name'] + }, + { + title: t('project.isolation.create_time'), + key: 'createTime', + ...COLUMN_WIDTH_CONFIG['time'], + render: (row: IRecord) => renderTableTime(row.createTime) + }, + useTableOperation( + { + title: t('project.isolation.operation'), + key: 'operation', + buttons: [ + { + text: t('project.isolation.cancel_isolation'), + permission: 'project:isolation-task:cancel', + icon: h(ClearOutlined), + onClick: (rowData) => void onCallback(rowData, 'cancel') + } + ] + }, + 'project' + ) + ] as TableColumns + tableWidth.value = calculateTableWidth(columns) + return columns + } + + watch(useI18n().locale, () => { + columns.value = getColumns() + }) + + onMounted(() => { + columns.value = getColumns() + }) + + return { + columns + } +} diff --git a/seatunnel-ui/src/views/task/isolation/use-detail.ts b/seatunnel-ui/src/views/task/isolation/use-detail.ts new file mode 100644 index 000000000..694e5f296 --- /dev/null +++ b/seatunnel-ui/src/views/task/isolation/use-detail.ts @@ -0,0 +1,112 @@ +/* + * 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, ref } from 'vue' +import { useI18n } from 'vue-i18n' +import { + parseIsolationFile, + submitIsolationTasks, + IIsolationTaskRecord +} from '@/service/modules/isolation-task' +import type { UploadCustomRequestOptions, FileInfo } from './types' + +interface DetailState { + status: 'process' | 'wait' | 'error' | 'finish' + current: 1 | 2 + saving: boolean + file?: FileInfo + tasks: IIsolationTaskRecord[] +} + +export const useDetail = (projectCode: number) => { + const { t } = useI18n() + + const formRef = ref() + const rules = { + file: { + trigger: ['blur'], + required: true, + validator: () => { + if (state.file) return + return Error(t('project.isolation.file_tips')) + } + } + } + const state = reactive({ + current: 1, + status: 'process', + saving: false, + tasks: [] + } as DetailState) + + const parseFile = async () => { + try { + await formRef.value.validate() + if (!state.file?.file) return + const formData = new FormData() + formData.append('file', state.file.file) + const result = await parseIsolationFile(formData, projectCode) + state.tasks = result + state.current = 2 + return true + } catch (err) {} + } + + const submitTasks = async () => { + await submitIsolationTasks(state.tasks, projectCode) + return true + } + + const onFileChange = (options: UploadCustomRequestOptions) => { + if (options.file.status !== 'pending') { + state.file = undefined + formRef.value.validate() + return + } + state.file = options.file + formRef.value.validate() + } + + const onReset = () => { + state.current = 1 + state.status = 'process' + state.saving = false + state.tasks = [] + } + + const onConfirm = async (step?: number) => { + if (step === -1) { + state.current = 1 + return false + } + if (state.current === 1) { + parseFile() + return false + } + if (state.current === 2) { + return await submitTasks() + } + } + + return { + state, + formRef, + rules, + onReset, + onConfirm, + onFileChange + } +} diff --git a/seatunnel-ui/src/views/task/isolation/use-table.ts b/seatunnel-ui/src/views/task/isolation/use-table.ts new file mode 100644 index 000000000..669548136 --- /dev/null +++ b/seatunnel-ui/src/views/task/isolation/use-table.ts @@ -0,0 +1,160 @@ +/* + * 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, onMounted } from 'vue' +import { + queryIsolationTasks, + cancleIsolation +} from '@/service/modules/isolation-task' +import type { IRecord } from './types' +import { useProjectStore } from '@/store/project' +import { useI18n } from 'vue-i18n' +import { useRoute, useRouter } from 'vue-router' +import _ from 'lodash' +export const useTable = () => { + const { t } = useI18n() + const projectStore = useProjectStore() + const route = useRoute() + const router = useRouter() + + const state = reactive({ + page: 1, + pageSize: 10, + totalPage: 1, + taskName: '', + workflowInstanceName: '', + list: [] as IRecord[], + loading: false, + checkedRowKeys: [], + projectCode: + route.query.searchProjectCode || projectStore.getCurrentProject, + globalProject: projectStore.getGlobalFlag + }) + const getList = async () => { + if ( + state.loading || + !state.projectCode || + state.projectCode.length === 0 || + typeof state.projectCode[0] === 'undefined' + ) + return + state.loading = true + try { + const result = await queryIsolationTasks({ + pageNo: state.page, + pageSize: state.pageSize, + taskName: state.taskName, + workflowInstanceName: state.workflowInstanceName, + projectCodes: state.projectCode + }) + state.list = result.totalList + state.totalPage = result.totalPage + } catch (err) {} + state.loading = false + } + const onSearch = () => { + state.page = 1 + + const query = {} as any + if (state.taskName) { + query.taskName = state.taskName + } + + if (state.workflowInstanceName) { + query.workflowInstanceName = state.workflowInstanceName + } + + router.replace({ + query: !_.isEmpty(query) + ? { + ...query, + project: route.query.project, + global: route.query.global, + searchProjectCode: state.projectCode + } + : { + ...route.query, + searchProjectCode: state.projectCode + } + }) + + getList() + } + const onChangePage = (page: number) => { + state.page = page + getList() + } + const onChangePageSize = (pageSize: number) => { + state.page = 1 + state.pageSize = pageSize + getList() + } + const onUpdateList = () => { + if (state.list.length === 1 && state.page > 1) { + --state.page + } + getList() + } + + const onCancleIsolation = () => { + cancleIsolation({ isolationTaskIds: state.checkedRowKeys }).then(() => { + window.$message.success(t('project.workflow.success')) + if (state.list.length === 1 && state.page > 1) { + state.page -= 1 + } + getList() + }) + } + + const onCancel = async (id: number) => { + await cancleIsolation({ isolationTaskIds: id }) + onUpdateList() + } + + const onCallback = (rowData: IRecord, type: string) => { + if (type === 'cancel') { + onCancel(rowData.id) + return + } + } + + const initSearch = () => { + const { taskName, workflowInstanceName } = route.query + if (taskName) { + state.taskName = taskName as string + } + + if (workflowInstanceName) { + state.workflowInstanceName = workflowInstanceName as string + } + } + + onMounted(() => { + initSearch() + onUpdateList() + }) + + return { + state, + onSearch, + onChangePage, + onChangePageSize, + onUpdateList, + onCallback, + onCancleIsolation + } +} diff --git a/seatunnel-ui/src/views/task/synchronization-definition/dag/canvas/dag-data.ts b/seatunnel-ui/src/views/task/synchronization-definition/dag/canvas/dag-data.ts new file mode 100644 index 000000000..52355b54b --- /dev/null +++ b/seatunnel-ui/src/views/task/synchronization-definition/dag/canvas/dag-data.ts @@ -0,0 +1,62 @@ +/* + * 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 { Graph, Edge } from '@antv/x6' + +export function getDagData(graph: Graph, t: Function) { + const rootNodes = graph.getRootNodes() + const checkRoot = rootNodes.every((rootNode) => { + return rootNode.getData().type === 'source' + }) + if (!checkRoot) { + window.$message.error( + t('project.synchronization_definition.start_node_tips') + ) + return + } + + const leafNodes = graph.getLeafNodes() + const checkLeaf = leafNodes.every((leafNode) => { + return leafNode.getData().type === 'sink' + }) + if (!checkLeaf) { + window.$message.error(t('project.synchronization_definition.end_node_tips')) + return + } + + const nodes = graph.getNodes() + const checkNode = nodes.every((node) => { + return !node.getData().unsaved + }) + if (!checkNode) { + window.$message.error( + t('project.synchronization_definition.save_node_tips') + ) + return + } + + const edgeCells = graph.getEdges() + const edges = edgeCells.map((edge: Edge) => { + return { + inputPluginId: edge.getSourceCell()?.getData().pluginId, + targetPluginId: edge.getTargetCell()?.getData().pluginId + } + }) + + return { + edges + } +} diff --git a/seatunnel-ui/src/views/task/synchronization-definition/dag/canvas/dag-setting.ts b/seatunnel-ui/src/views/task/synchronization-definition/dag/canvas/dag-setting.ts new file mode 100644 index 000000000..d6f6544cc --- /dev/null +++ b/seatunnel-ui/src/views/task/synchronization-definition/dag/canvas/dag-setting.ts @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export const DagNodeName = 'dag-node' +export const DagEdgeName = 'dag-edge' +export const NodeWidth = 200 +export const NodeHeight = 50 +export const NodeShape = 'rect' +export const PortAttrs = { + circle: { + r: 4, + magnet: true, + stroke: '#C2C8D5', + strokeWidth: 1, + fill: '#fff' + } +} +export const PortGroupsConfig = { + in: { + position: 'left', + attrs: PortAttrs + }, + out: { + position: 'right', + attrs: PortAttrs + } +} +export const EdgeDefaultConfig = { + attrs: { + line: { + stroke: '#C2C8D5', + strokeWidth: 1 + } + } +} diff --git a/seatunnel-ui/src/views/task/synchronization-definition/dag/canvas/dag-shape.ts b/seatunnel-ui/src/views/task/synchronization-definition/dag/canvas/dag-shape.ts new file mode 100644 index 000000000..6cb5ad958 --- /dev/null +++ b/seatunnel-ui/src/views/task/synchronization-definition/dag/canvas/dag-shape.ts @@ -0,0 +1,202 @@ +/* + * 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 { Graph, Cell } from '@antv/x6' +import { DagreLayout, GridLayout } from '@antv/layout' +import _ from 'lodash' +import { uuid } from '@/common/common' +import { DagNodeName, DagEdgeName, PortGroupsConfig } from './dag-setting' +import type { InputEdge, NodeType } from '../types' + +export function addNode(graph: Graph, cell: Cell.Metadata) { + const id = uuid(String(new Date().getTime())) + const nodeShape = { + id, + ...cell, + zIndex: 1, + shape: DagNodeName, + ports: { + groups: PortGroupsConfig, + items: getPorts(id, cell.node) + }, + data: { + type: cell.node, + unsaved: true, + isError: false, + name: cell.label, + pluginId: id, + connectorType: cell.node === 'transform' ? cell.label : '' + } + } as Cell.Metadata + ;(graph as Graph).addNode(nodeShape) +} + +export function initNodesAndEdges( + graph: Graph, + cells: Cell.Metadata[], + edges: InputEdge[] +) { + graph.addNodes( + cells.map((cell: Cell.Metadata) => ({ + shape: DagNodeName, + node: cell.type.toLowerCase(), + id: cell.pluginId, + label: cell.name, + ports: { + groups: PortGroupsConfig, + items: getPorts(cell.pluginId, cell.type.toLowerCase()) + }, + data: { + ...cell, + isError: false, + type: cell.type.toLowerCase() + } + })) + ) + graph.addEdges( + edges.map((edge) => ({ + shape: DagEdgeName, + source: { + cell: edge.inputPluginId, + port: edge.inputPluginId + '-out-port' + }, + target: { + cell: edge.targetPluginId, + port: edge.targetPluginId + '-in-port' + } + })) + ) + formatLayout(graph, 'dagre') +} + +export function updateNode(graph: Graph) { + graph.resetCells(graph.getCells()) +} + +export function formatLayout( + graph: any, + formatType: 'grid' | 'dagre' = 'dagre', + cols?: number, + rows?: number +) { + let layoutFunc = null + const layoutConfig: any = { + nodesep: 50, + padding: 50, + ranksep: 50, + type: formatType + } + + if (formatType === 'grid') { + layoutConfig['cols'] = cols + layoutConfig['rows'] = rows + } + + if (!graph) { + return + } + + graph.cleanSelection() + + if (layoutConfig.type === 'dagre') { + layoutFunc = new DagreLayout({ + type: 'dagre', + rankdir: 'LR', + align: 'UL', + // Calculate the node spacing based on the edge label length + ranksepFunc: (d) => { + const edges = graph.getOutgoingEdges(d.id) + let max = 0 + if (edges && edges.length > 0) { + edges.forEach((edge: any) => { + const edgeView = graph.findViewByCell(edge) + const labelView = edgeView?.findAttr( + 'width', + _.get(edgeView, ['labelSelectors', '0', 'body'], null) + ) + const labelWidth = labelView ? +labelView : 0 + max = Math.max(max, labelWidth) + }) + } + return layoutConfig.ranksep + max + }, + nodesep: layoutConfig.nodesep, + controlPoints: true + }) + } else if (layoutConfig.type === 'grid') { + layoutFunc = new GridLayout({ + type: 'grid', + preventOverlap: true, + preventOverlapPadding: layoutConfig.padding, + sortBy: '_index', + rows: layoutConfig.rows || undefined, + cols: layoutConfig.cols || undefined, + nodeSize: 220 + }) + } + + const json = graph.toJSON() + const nodes = json.cells + .filter((cell: any) => cell.shape === DagNodeName) + .map((item: any) => { + return { + ...item, + // sort by code aesc + _index: -(item.id as string) + } + }) + + const edges = json.cells.filter((cell: any) => cell.shape === DagEdgeName) + + const newModel: any = layoutFunc?.layout({ + nodes, + edges + } as any) + graph.fromJSON(newModel) +} + +function getPorts(id: string, type: NodeType) { + if (type === 'source') { + return [ + { + id: id + '-out-port', + group: 'out' + } + ] + } + if (type === 'sink') { + return [ + { + id: id + '-in-port', + group: 'in' + } + ] + } + if (type === 'transform') { + return [ + { + id: id + '-out-port', + group: 'out' + }, + { + id: id + '-in-port', + group: 'in' + } + ] + } + return [] +} diff --git a/seatunnel-ui/src/views/task/synchronization-definition/dag/canvas/index.module.scss b/seatunnel-ui/src/views/task/synchronization-definition/dag/canvas/index.module.scss new file mode 100644 index 000000000..e154ba0bf --- /dev/null +++ b/seatunnel-ui/src/views/task/synchronization-definition/dag/canvas/index.module.scss @@ -0,0 +1,59 @@ +/* + * 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. + */ + +.container { + height: 100%; + width: 100%; +} + +.dag-container { + height: 100%; + width: 100%; +} + +.minimap { + position: absolute; + right: 20px; + bottom: 60px; + border: dashed 1px #e4e4e4; +} + +.dag-node { + display: flex; + align-items: center; + height: 100%; + background-color: #fff; + border: 1px solid #c2c8d5; + border-radius: 4px; + box-shadow: 0 2px 5px 1px rgba(0, 0, 0, 0.06); + + .dag-node-icon { + width: 20px; + height: 20px; + margin: 0 10px; + } + + .dag-node-label { + width: 90px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + text-align: left; + color: #666; + font-size: 12px; + } +} diff --git a/seatunnel-ui/src/views/task/synchronization-definition/dag/canvas/index.tsx b/seatunnel-ui/src/views/task/synchronization-definition/dag/canvas/index.tsx new file mode 100644 index 000000000..62b149086 --- /dev/null +++ b/seatunnel-ui/src/views/task/synchronization-definition/dag/canvas/index.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 { defineComponent, ref, onMounted, reactive, provide } from 'vue' +import { Cell, Graph } from '@antv/x6' +import { useI18n } from 'vue-i18n' +import { DagEdgeName, DagNodeName, EdgeDefaultConfig } from './dag-setting' +import { useDagNode } from './use-dag-node' +import { useDagResize } from './use-dag-resize' +import { useDagGraph } from './use-dag-graph' +import { + addNode, + formatLayout, + initNodesAndEdges, + updateNode +} from './dag-shape' +import NodeSetting from '../node-setting' +import { getDagData } from './dag-data' +import styles from './index.module.scss' +import type { Ref } from 'vue' +import type { InputEdge, InputPlugin, NodeInfo } from '../types' + +const DagCanvas = defineComponent({ + name: 'DagCanvas', + emits: ['drop'], + setup(props, ctx) { + const graph = ref() + provide('graph', graph) + + const container = ref() + const dagContainer = ref() + const minimapContainer = ref() + const { t } = useI18n() + const state = reactive({ + nodeInfo: { + type: 'source', + label: '', + pluginId: '', + transform: '' + } as NodeInfo, + show: false + }) + + let currentNodeId = '' + + const handlePreventDefault = (e: DragEvent) => { + e.preventDefault() + } + + const handleDrop = (e: DragEvent) => { + ctx.emit('drop', e) + } + + const initGraph = () => { + graph.value = useDagGraph( + graph, + dagContainer.value, + minimapContainer.value + ) + } + + const registerNode = () => { + Graph.unregisterNode(DagNodeName) + Graph.registerNode(DagNodeName, useDagNode()) + } + + const registerEdge = () => { + Graph.unregisterEdge(DagEdgeName) + Graph.registerEdge(DagEdgeName, EdgeDefaultConfig) + } + + const onDoubleClick = () => { + graph.value && + (graph.value as Graph).on( + 'cell:dblclick', + ({ cell }: { cell: any }) => { + if (cell.isEdge()) return + let fields = [] as string[] + const incomingEdges = graph.value?.getIncomingEdges(cell) + if (incomingEdges?.length) { + const sourceNode = incomingEdges[0].getSourceNode() + const sourceData = sourceNode?.getData() + fields = sourceData.selectTableFields?.tableFields || [] + } + state.nodeInfo = { + ...cell.getData(), + type: cell.getData().type.toLowerCase(), + sourceFields: fields, + predecessorsNodeId: (graph.value?.getPredecessors(cell) as Cell[]).length > 0 ? graph.value?.getPredecessors(cell)[0].id : '' + } + currentNodeId = cell.id + state.show = true + graph.value!.lockScroller() + } + ) + } + + const onCancelModal = () => { + state.show = false + } + + const onConfirmModal = (values: InputPlugin) => { + state.show = false + const node = graph.value?.getCellById(currentNodeId) + node?.replaceData({ + ...values, + unsaved: false, + isError: false, + type: values.type.toLowerCase() + }) + // Used to determine whether the current node has data saved. + updateNode(graph.value as Graph) + } + + useDagResize(container, graph as Ref) + + ctx.expose({ + addNode: (cell: Cell.Metadata) => { + addNode(graph.value as Graph, cell) + }, + getGraph: () => graph.value, + addNodesAndEdges: (cells: Cell.Metadata[], edges: InputEdge[]) => { + initNodesAndEdges(graph.value as Graph, cells, edges) + }, + getSelectedCells: () => graph.value?.getSelectedCells(), + removeCell: (id: string) => graph.value?.removeCell(id), + getDagData: () => getDagData(graph.value as Graph, t), + layoutDag: (layoutType: 'grid' | 'dagre', cols: number, rows: number) => + formatLayout(graph.value, layoutType, cols, rows) + }) + + onMounted(() => { + initGraph() + registerNode() + registerEdge() + onDoubleClick() + }) + return () => ( +
+
+
+ {state.nodeInfo.type && ( + + )} +
+ ) + } +}) + +export { DagCanvas } diff --git a/seatunnel-ui/src/views/task/synchronization-definition/dag/canvas/node.tsx b/seatunnel-ui/src/views/task/synchronization-definition/dag/canvas/node.tsx new file mode 100644 index 000000000..e875761d1 --- /dev/null +++ b/seatunnel-ui/src/views/task/synchronization-definition/dag/canvas/node.tsx @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { defineComponent, inject, ref } from 'vue' +import { NTooltip } from 'naive-ui' +import styles from './index.module.scss' +import SourceImg from '../images/source.png' +import SinkImg from '../images/sink.png' +import FieldMapperImg from '../images/field-mapper.png' +import FilterEventTypeImg from '../images/filter-event-type.png' +import ReplaceImg from '../images/replace.png' +import SplitImg from '../images/spilt.png' +import CopyImg from '../images/copy.png' +import SqlImg from '../images/sql.png' + +const Node = defineComponent({ + name: 'Node', + setup() { + const getNode = inject('getNode') as any + const node = getNode() + const { name, unsaved, type, connectorType, isError } = node.getData() + + const icon = ref('') + + if (type === 'source') { + icon.value = SourceImg + } else if (type === 'sink') { + icon.value = SinkImg + } else if (type === 'transform' && connectorType === 'FieldMapper') { + icon.value = FieldMapperImg + } else if (type === 'transform' && connectorType === 'FilterRowKind') { + icon.value = FilterEventTypeImg + } else if (type === 'transform' && connectorType === 'Replace') { + icon.value = ReplaceImg + } else if (type === 'transform' && connectorType === 'MultiFieldSplit') { + icon.value = SplitImg + } else if (type === 'transform' && connectorType === 'Copy') { + icon.value = CopyImg + } else if (type === 'transform' && connectorType === 'Sql') { + icon.value = SqlImg + } + + return () => ( +
+ + + {{ + trigger: () =>
{name}
, + default: () => name + }} +
+
+ ) + } +}) + +export default Node diff --git a/seatunnel-ui/src/views/task/synchronization-definition/dag/canvas/use-dag-graph.ts b/seatunnel-ui/src/views/task/synchronization-definition/dag/canvas/use-dag-graph.ts new file mode 100644 index 000000000..5113a1944 --- /dev/null +++ b/seatunnel-ui/src/views/task/synchronization-definition/dag/canvas/use-dag-graph.ts @@ -0,0 +1,79 @@ +/* + * 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 { Graph, Edge } from '@antv/x6' +import { DagEdgeName } from './dag-setting' + +export function useDagGraph( + graph: any, + dagContainer: HTMLElement, + minimapContainer: HTMLElement +) { + return new Graph({ + container: dagContainer, + scroller: true, + grid: { + size: 10, + visible: true + }, + connecting: { + router: 'manhattan', + allowBlank: false, + allowLoop: false, + allowNode: false, + snap: true, + createEdge() { + return graph.value?.createEdge({ shape: DagEdgeName }) + }, + validateConnection(data) { + const { sourceCell, targetCell } = data + if (targetCell?.getData().type === 'source') return false + if (targetCell?.getData().type === 'sink') { + return graph.value?.getConnectedEdges(targetCell).length < 1 + } + + if (targetCell?.getData().type === 'transform') { + // The same 'Copy' transform node cannot be connected + const srcData = sourceCell?.getData(), tgtData = targetCell?.getData() + if (srcData.type === 'transform' && srcData.connectorType === 'Copy' && tgtData.connectorType === 'Copy') return false + + // don't connect self + const edges = graph.value?.getConnectedEdges(targetCell) + return !edges.some((edge: Edge) => { + return edge.getTargetCellId() === targetCell.id + }) + } + + return true + } + }, + snapline: true, + minimap: { + enabled: true, + width: 200, + height: 120, + container: minimapContainer + }, + selecting: { + enabled: true, + rubberband: false, + movable: true, + showNodeSelectionBox: true, + showEdgeSelectionBox: true + } + }) +} diff --git a/seatunnel-ui/src/views/task/synchronization-definition/dag/canvas/use-dag-node.ts b/seatunnel-ui/src/views/task/synchronization-definition/dag/canvas/use-dag-node.ts new file mode 100644 index 000000000..9aeaff364 --- /dev/null +++ b/seatunnel-ui/src/views/task/synchronization-definition/dag/canvas/use-dag-node.ts @@ -0,0 +1,33 @@ +/* + * 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 '@antv/x6-vue-shape' +import Node from './node' +import { createVNode } from 'vue' + +export function useDagNode() { + return { + inherit: 'vue-shape', + width: 150, + height: 36, + component: { + render: () => { + return createVNode(Node) + } + } + } +} diff --git a/seatunnel-ui/src/views/task/synchronization-definition/dag/canvas/use-dag-resize.ts b/seatunnel-ui/src/views/task/synchronization-definition/dag/canvas/use-dag-resize.ts new file mode 100644 index 000000000..e48101010 --- /dev/null +++ b/seatunnel-ui/src/views/task/synchronization-definition/dag/canvas/use-dag-resize.ts @@ -0,0 +1,33 @@ +/* + * 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 { debounce } from 'lodash' +import { useResizeObserver } from '@vueuse/core' +import type { Graph } from '@antv/x6' +import type { Ref } from 'vue' + +export function useDagResize(container: Ref, graph: Ref) { + const resize = debounce(() => { + if (container.value) { + const w = container.value.offsetWidth + const h = container.value.offsetHeight + graph.value?.resize(w, h) + } + }, 200) + + useResizeObserver(container, resize) +} \ No newline at end of file diff --git a/seatunnel-ui/src/views/task/synchronization-definition/dag/config.ts b/seatunnel-ui/src/views/task/synchronization-definition/dag/config.ts new file mode 100644 index 000000000..b32b31932 --- /dev/null +++ b/seatunnel-ui/src/views/task/synchronization-definition/dag/config.ts @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export const KINDS = [ + { + label: 'INSERT', + value: 'INSERT' + }, + { + label: 'UPDATE_BEFORE', + value: 'UPDATE_BEFORE' + }, + { + label: 'UPDATE_AFTER', + value: 'UPDATE_AFTER' + }, + { + label: 'DELETE', + value: 'DELETE' + } +] diff --git a/seatunnel-ui/src/views/task/synchronization-definition/dag/configuration-form.tsx b/seatunnel-ui/src/views/task/synchronization-definition/dag/configuration-form.tsx new file mode 100644 index 000000000..2554870cf --- /dev/null +++ b/seatunnel-ui/src/views/task/synchronization-definition/dag/configuration-form.tsx @@ -0,0 +1,306 @@ +/* + * 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, nextTick, PropType, ref, watchEffect } from 'vue' +import { + NForm, + NFormItem, + NInput, + NSelect, + NSpace, + NRadioGroup, + NRadio, + NCheckboxGroup, + NCheckbox, + NSpin, + NTransfer +} from 'naive-ui' +import { DynamicFormItem } from '@/components/dynamic-form/dynamic-form-item' +import { KINDS } from './config' +import { + useConfigurationForm, + getSceneModeOptions +} from './use-configuration-form' +import { useI18n } from 'vue-i18n' +import type { NodeType } from './types' + +import { debounce } from 'lodash' + +const ConfigurationForm = defineComponent({ + name: 'ConfigurationForm', + props: { + // eslint-disable-next-line vue/require-default-prop + nodeType: { + type: String as PropType + }, + // eslint-disable-next-line vue/require-default-prop + nodeId: { + type: String as PropType + }, + // eslint-disable-next-line vue/require-default-prop + transformType: { + type: String as PropType + } + }, + emits: ['tableNameChange'], + setup(props, { expose, emit }) { + const { + state, + dagStore, + getDatasourceOptions, + getDatabaseOptions, + getTableOptions, + updateFormValues + } = useConfigurationForm( + props.nodeType as NodeType, + props.transformType as string + ) + const { t } = useI18n() + const formRef = ref() + const transfer = ref() + + const onTableChange = (tableName: any) => { + state.model.tableName = tableName + emit('tableNameChange', state.model) + } + + + const prevQueryTableName = ref(''); + const onTableSearch = debounce((tableName: any) => { + // rely on database + if(state.model.database && prevQueryTableName.value !== tableName) { + getTableOptions(state.model.database, tableName) + prevQueryTableName.value = tableName + } + }, 1000) + + const onDatabaseChange = (v: any) => { + nextTick(() => { + if(state.model.database) { + let size = state.model.sceneMode === 'MULTIPLE_TABLE' ? 9999999 : 100 + getTableOptions(state.model.database as any, '', size) + } + }) + } + + // watchEffect(() => { + // // Track the src input of the transfer and refresh the table name list when the input value change + // let query = transfer?.value?.srcPattern + // onTableSearch(query) + // }) + + expose({ + validate: async () => { + try { + await formRef.value.validate() + return true + } catch (err) { + return false + } + }, + getValues: () => state.model, + setValues: updateFormValues + }) + + return () => ( + + + + + + + {props.nodeType === 'source' && ( + + { + if (v !== state.model.sceneMode) { + getDatasourceOptions(v) + state.model.datasourceInstanceId = null + state.model.database = null + state.model.tableName = null + state.formStructure = [] + state.databaseOptions = [] + state.tableOptions = [] + } + }} + /> + + )} + + {props.nodeType !== 'transform' && ( + + { + if (v !== state.model.datasourceInstanceId) { + getDatabaseOptions(v, option) + state.model.database = null + state.model.tableName = null + state.tableOptions = [] + } + }} + /> + + )} + + {props.nodeType !== 'transform' && ( + + { + if (v !== state.model.database) { + onDatabaseChange(v) + state.model.tableName = null + } + }} + /> + + )} + + {dagStore.getDagInfo.jobType === 'DATA_INTEGRATION' && + (props.nodeType === 'sink' || props.nodeType === 'source') && ( + + + + )} + + {state.model.sceneMode === 'MULTIPLE_TABLE' && ( + + + + )} + + {props.transformType === 'FilterRowKind' && ( + <> + + + + + {t('project.synchronization_definition.include_kind')} + + + {t('project.synchronization_definition.exclude_kind')} + + + + + + + + {KINDS.map((kind) => ( + + ))} + + + + + )} + + {props.transformType === 'Sql' && ( + + + + )} + + {state.formStructure.length > 0 && ( + + )} + + + ) + } +}) + +export default ConfigurationForm diff --git a/seatunnel-ui/src/views/task/synchronization-definition/dag/images/copy.png b/seatunnel-ui/src/views/task/synchronization-definition/dag/images/copy.png new file mode 100644 index 0000000000000000000000000000000000000000..26f7931a275d3679436202a1dbe9d04e736b1ce2 GIT binary patch literal 330 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5m8A!&LZC(qc45_&F_Ld>n zAp?p(%n0p@S5)jR$OOX;H2wYG=AQ2;bb~EE;yzI{InLl67|Gdw6b0%{pGeKXlHWlRG&kq8D z1kKHi?SS`j;6>%z2~3Z#WIYgw>(d2GhjYLdIO5~uVK5jl;cz%G0|Eko84?l#Y{SFD z5eNhli3DI|WMpt~u&=K#5c&D}g@uIy5DL)poCM!Y5N6owj6;o0m3nX?Y8o^ zm;7A@$WOSnhjYG_aPDwhd26)wl7Dyq)z8tk%0JtFfxnoXo^6}|OZ5NxTdO$dzs#RE zxs`K1Wh?o4zOBzmH#awVWS6pl2YoHv&e|TdgKH-@&n{lR-TVT-2?_~|h>D3zNJ{P5 zyKg`Efb_vbhh=2t{wNt1aoo~9{zN7uo-P7CGKQQ=yXn17w!`S%5Bz=nUaeC&{?A-jH z3yYt>EG@5mU0qvevc7G6Xa7)`j93A_fgN5JrpD`)qtWXi5MPbCv7tlM@Iqmvt0QbL zMR@A!?t~KK{kUDPdL=qtof`VcqDhHzWJ!cUY@8b^7=rsQfTUN>m43q$CjAo{NAVGQi>SL1>NuHgvg_3&~!zOsN-!GMae;o zy~8^DppWTwd@>CC4!g1*Q!#AaD^(+(qm(`=vt@NDkHmWw$<@Y1cexMxEup}DcfI|k z6_^h&yd@P0&!67%d#E{_w-d${DXzdwq>tX^uBLaR*SW{a61_KA$mG`HA~~d&tfGkO zK5Mzf2AFpHwMh&Hp4^$2?J3kNuRKNXzZp(X%Tc?N`$+@3WRlsy9~R|UrlLZ;<%rrf z{YADlC)T6;d}{Ln_nap#@%`*PM_q|99~1nu`yy-w@`9qH)&oaf?yyUIsAk@6)}|?R zTr^wCON}^#*jD%pmN)ea6cE3RU>5{CXVhLKPHCq#>2+T*hSsL$l~nF(p9<4!vDcpT z)h7D90l%r;n@R&?S{wViE}=x3FWK{u-`eo!MIvpPx6_&QBu`~LTab*8mtehrtBr~^ zFSS|MKlxiR8zwgK+Gbv?*na%z(yVD%sL1`5~BBOL4!& z0SB`?>Fh4M#TIYsaB9NylrnFy$*F!!?HIh`%Rtv{We~5v;;O#ZQAvvra*BBcYdapE>ng z`Z*IDmqp*l_`^U*RmE>+_h(b8r!!vOz?V?f+f!auM2z?-JuY|&fY4{CU2g8JiZ4SjQkA598; zhJ@Y$XY0=})-CyDu0(4|&403yhIwD?5RN9F<)*DA_KnyxYI14Iva4)sUQn z3ks*Fo?EmWAX~pwI4P;qFE;++UBX*YmWc;q%3@0L+n~_+TP+?9tB)?LeeF42#|mT^ z^H21P8U;PqoK!}t(oYdPzUL;HPgCuLo8~O?irET-e^#|G{bLU)TvuhJ!YGJ}3o?2BdkbRi>a36_Z`i)o9}_|yD-9+rdX$##QD&9U zk^L`Seb*!Huc+W6Vvp#!#O5P5UpS$4-B%5U*(~lMX}Y28t?t)jnHz&=87$(()IF9^ z52s_KGuUq;BK_1BmNvBJ ztcyK_^2D1oec#qY1EY&bF@cAt6iAPlEyTk*Pxd>RF5U^Z79~8&LMx};Em%(GwJD95 ziNlVkJ|z4oUB3M2DY(E;o?vPiF&~#b$UBv7N>NmR>>YDk5S+{6Ch*4I4I?cI%eW71 zP_IIa~i$9?gn74xuZtPIcaSCr|4!xD>Fa!u@GWm2YE=081OOSXMJ+2YkG7D4lj zAU%|{Y0J2OSXYfD_L0q=j(Xg0iSE2p_xr_yPh|bqI}0@uhol>9V-MkO9lqw1V7#DB zSVl(j2UG?dN{vJrT@DKgt#{m54f3py$RvxO<45Ftf9!;M=(HEEZEl{Rt|M@7Lf|rF z5o>wQUve4Oqx$~AP)dG)h;yf-S31K6F5|LPEMf%BIxeY0>1~^6EL=%c<%~~Kow#t!)4KF5BSM1h>Flhi(G$BVvHlWk z$}DK&2jnSwg>FcW-f@YwBX2gQ2^mRDm21S?zPKO!q$6wt%z_5HEoaial1m`G2>;by z{lsGBif+55*_iE(z}z@4T$rna6}#}-ytY}^JEUUD$z&ZI)?iJ3)$cf-n(-~UI5k}S zqVH)0d-A(iicEij)Cs$J3j2GjcTZO)eKIZ^y2&GxU!>(>=^FH}zbxkGtc`1oE+_mG D;WALL literal 0 HcmV?d00001 diff --git a/seatunnel-ui/src/views/task/synchronization-definition/dag/images/filter-event-type.png b/seatunnel-ui/src/views/task/synchronization-definition/dag/images/filter-event-type.png new file mode 100644 index 0000000000000000000000000000000000000000..dc6d40aac4991892f83b0f712bff930b03978625 GIT binary patch literal 1148 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k8A#4*i(3Pv4g~mwxB}^+3;zHA&)v$&1oVJi zNswPK0~0d~D;t-Pu!yL%jI5lgnT2I&ctlimc|}!Qd&lG{Q>V|EIeYcmb?Y~8*}84_ z#Y%Zt*t&$@lEuC7hz?c2zt?ekJLP48dm{d>cfxU|AK z#}&O-&yCJ6j;iim=)JmrtK9Qd^WI)qdUemdoVVM}XWH|}-rD)qX0lF)-rL1r?c87T z#oo$$^Z!NatNg^oTQfz^Snw|LzU?pKuKxZmU*fIKsb>!GUR>(Beb(alT6*mUZ!dH$ zd)mZtxoXwo-Fh7cZzsQcv65TKXLp#aZLwoZruWs08-ZN!5cl;l0*SL;uTFb=vbxKF zmqS3op@D&siG?5|Qrmaioaxe!GDQm_`8?J{C-8F5d>Pht*lF1=)%P1F^jT`KFUed~ zDXCyS(XiRb;g{RjO#u-nRwecrPJZOvc1$9E_Cb3ZLtg zzb08^l`h|$`f0!Oq^I9AmrF{vB}%|hmznhUy={AcD&$Y+t+w{Kr6>K3ZSIy>vp2fI znY(jt%)W7|?B<4k(J5~?CCY!%xSe{~t}1xj>9$%4t(@t++<`!5tb~MgaGxk&Dp2`S zsl?qm5gnU=`X5EE)>++f_mfWgw6xf5CfsM1Mrxk_t($V*Ou#>r$S?$k^NFa;r9a(2 zZ+d#>2CLw*lh;zyfwASg?SM@wFqSq={nQ7HrO8`Qo5ccSX;tjEV?VXl8u?z)yIo$- z!g1MTz1rHKUk|5bPAa>Zcx(A41K*2!yKi_mYg}e|yXoy0`zcd0@7+#ykF5X1dAVl$ z>A0}X3a3o=zuk2HcU*j*(b@FfZ&v)e^|i(1Z20aswbtjCPBc2}y*nrBe)f$mYj3A+ n{uOt4S?(RZ-GTR4U*DphyxxrY)IQI}pfbeM)z4*}Q$iB}D0QtT literal 0 HcmV?d00001 diff --git a/seatunnel-ui/src/views/task/synchronization-definition/dag/images/replace.png b/seatunnel-ui/src/views/task/synchronization-definition/dag/images/replace.png new file mode 100644 index 0000000000000000000000000000000000000000..093468b13ae7824d685c6fe32c275e24a3cd1b59 GIT binary patch literal 3087 zcmeH}`8U*y8^?#So6*>p(8r!F8q^g-GiDe}nueQ_Ey*^FOhopEG9<>nC0n-2xHGoN znk8`~CQF%!%2F9w5^3S<{`&p?2j6pF=RD7I-p_CE_c_lwPcq)Y1}dT;0s?`cwpcV_ ze-8XpLg4*gc+33}2*f*y$2np4<-h!!!2eSMdwY93iJr3imexWE4vrumUN9fOfS{1D zh$vL-08Cs$Qc7AzR!&~wprVrUAwWe{O#(+tuAcr81H+?mBV&_e$Nw-rVP=l7 zIEh4Ap)uArSX(=L+$o3C_%j5jKb_B=ce&u|=I-Hn(d&};Wgp)FVqg#{_)5st(6DRa z5!WN5qGMv?;%`tA5^vt3Ca2s=hQ_Am7S`j|w)T$Bt|#3+y?y=91_u9rJ~TYaemOQiG5PBC6z9$K%-h+yckkyH z7MDJL`n=3t`LepUzVUVQ+t&8?ogY7c{oc(QEZN%k&f{fogI-5<&Gvvmg3GpOq*DZE zrTFIsCyd-P-#j*_uHEY3=RgWlycBkni)t#t+hSH(UG5 zyT6(0uk^4PPNm~Z1kzrk7he9=4MfsUk48n5Rbt{S>Y-*65pg~tQ08OU$Z76&4YcSD z+oeho%rC>yx~6z(>~M>vmDsSUCeY)V;XBfH><(`I#n8!~sVhP@Pw7dB#-QA>9O(gr z)RQ;~6A^tzkh4h!)JAl}qB@xeI@qKFKs4U%zW>>TPg$VzF-RN z>yVE4Ab|=9?I=rqvr;=r0fup3I~Q_~V`=@#$zDd8p{2mUa-zJgIvzHk87On@I+!2n zVwA~O0}QM{Oub^EIzk)TYFM!w{7CRAK3lnSu!gDnm@CL>s@zK{kPIJGjWj630?^Wl zE~i-l5~|l4E(lW5BBy}_gjG&p4zYlb#$S9iJ!x10cDq%4W52JkZt)zYboPp63@8CM zG8y;;FnAYB03`s?s9)yZu;?1UP+kaAE___xaNyiD8~+68W>uu~BsBIa4f8_EpDF3{ zKqvu*HV=^$oS(Z(OuVU1#INTfP(7L~QBNcffU>g0{bsMfjqOB_>PVNb{@u=Y|Bb;NuWa7svE5r zdwkiWNlv3T!)oyAr!W&a*$P)$#h`ZRPB(FT!Dw9S^3VZm*M08%9?K4TEFH!yN`Ig$ z*r{9^W}b>H_@<+(?YxTFw7Dqii;U!0% z!uVZs$95$NMlT=a)VC5{XSkk#Mp6}xeRuY!jxOwb%~U6iTw@$Rp&nTHx8=qsR?HD4 z>J-Q4Me830DYK5J&M8c+L=}G@M}}$Xz`Sm6u7v00H`}UVoe=Z4;{3+cN(OesJAaHh zzq|4iCbq%oQ(R{H?4vvLaq_UU9n4tP6)$zz5AEaMH<yKxpxl6RI#|^=w(BFp^dER%jY|>FFc=@1mM4Fk~(q~_?i&<9SV77uNL2V z_Ng=dsZ6pPmFzp30Bl2U7m>$&ZvF`Hwpc(7t7{`)R>n-#j-fI zVByDfqd}IIs*&}D`4QljNJFtx&}Z!wv(t>Z(l>E>u}i+M&ClBZ8e{=#U>cOl;E)r#2?XB|}slm&+z+bKd25TQ{Y3_&>%CzA&3aKMm=YowEJ0wjyI8 zpdrX0G19Cdfkk O^U~R39ME)>C*?m&?@|8% literal 0 HcmV?d00001 diff --git a/seatunnel-ui/src/views/task/synchronization-definition/dag/images/sink.png b/seatunnel-ui/src/views/task/synchronization-definition/dag/images/sink.png new file mode 100644 index 0000000000000000000000000000000000000000..cdbbf826a4fb8acda708ce2717ab70eb59b9b346 GIT binary patch literal 3393 zcmY+Hdpy(sAH~PyQZ$?0F>G^-D3P!!_q*Im$}PF3QtsE>a;I4-_gc}F5Tz)WTr!t$ z@|8=HOK!Qu+>PkBP3rsG<7}Vp^L{643C5|gkuOHu63Q`;?=O<6S=AWJC5jyw6cldw!!Ww)=u?+Sm4?4)HCVI}-sVFzaf zW_Dz5^p|B6*>+$CX1(8T@%!ZV8Aemq1>ZUO+u^VOe^z%4|Fg@E^A5hVBlFu~CHen> z9fv<-f7AYP`1R}8a6Z+I@w2A+T9{e_*w}#_yEwVHd3NvN<=YG52SWs)FgOBQ zN=_lAl5eKnx}BbpnU$TBd*?3Y-u=A%2M-@Te)6>7Sy6Gx^U|^xFUwz7ym?zyT~qt6 zuD;=Y6ScXewe6qwj~)MZe(LJ(>HXaIrT^=|;Lz~5keF0Dwo>KwHBquoiIXX7rQ&5|+Zdy8#B$%G> zY4vMJuDkJ8Kyo?eh;?bK_0q+{Y#%MY)1YKe!{zT`Y>PvI!h)cF&HK7E`?jX~o^D`` zm`Ob{SMno}lseWi#Q~p;JeoQRz9ruclqcUGUfmC$iBxW;1rFc149eN}ZsZ61JZ7yh zkTf|4A<)8x+ZVKD>w#z9?pozvrOwyB&g{*+W@Up}^CM%FKt_wTTH0GhD?AJCS6Wx% zzmBZGy6DXBlj{@0>m)RMpSX`&5$NdIzw}BqjwZBG^lc)N>y0}|u%%2ONU2>?h)Bbm zg;^%oB0y5_hzD8ICBlxqK^s30+! z7zTohHvv;zQV+}tdb+VG`?_X*?yi4f7y4_eMyX_a>EgjWa^8i=q&ZJwIn@IMH7U5_ zpWk6qjTWe^Xsn+OP)#W$Th)trVBDc!Uy_HC2%W%9!;vf5-hR6G^{mmPs%4HK#jfyG z!~3*ThAJwK>sj0mic-N_eN~e-uGt%xItwbT!`KstV|Ybyc9=4mt*^9x86Y>w)p}l~ z3M=xCC?RTBQxJ6hO<~X0=G=8v9fewK*|p%I6YGT2QN-H`3*5nd&tK0yFmm4e>0C{8 zT!r=b+Q-s2^VVaLXM5tFHNE@=nk+EF3$GvXa+Fu~ojp5Ug;jPj1agwbm1V9Qd>ZNC zfBpXCK-Y%@;cM-EkFI)iJku-b0uFW@n%|Hs(Kz`fcICwx`jVYguHM*Hd$sxF^+zx6 z9u5^EmL|EOQN)~nkS(y#5&bA-kB`E;vZJ+`daH(>YWZC1)LAfxkZz{p6+7=-;X88; z=f2%cDpiyTo6DjL41P)$+1+a$96?;0&no#4Lz_iS&fn?GHsW$6k5SsajTTK_HoGim zeE%t-5^cj#90-@aAYxF^^0c<2q}dN(p8jFH%DknQ@j;%G_DPlDSzhrcgz(OJbr}YYMkbj#`#({!#F5RQ>3I z#AA2Lb+j8C(PX&>LV8Rkoo+vqkpW1ifW;~!wy^RmYN?}aw{TMyfoTw6>p0e{hQfE_ zM?$LXYif`;nit|7Wj>0;(~($bHCyLh;3GQDh%rI^g+^^^;A?Ihx6KDsQ`}>{N)q?H zOjPh_>YL?G(Sr{0wwBa__vNGda{Zrzc@r`ve6L=Y;?t7{PGDLz6H3%66t-lnFzWrl z_mGTuSIJ%rGc!S5axi5Ep_JrDaT(H!98DFrvbV)KOk0^PthbG-IVhK`d4U1Z(kJnvN>*p(K&vO2Ep>btG73LJ-WMX8Om(Z&}~ zoEDlP5Hob9>SrH-rrae{+Evwl+EP{?vKK5I?m6pFTFJZd6z)DUrTTfIx?V5DmoOtM z=~pOL6rFqj^@GD7Y4@VNRGP}2#M^k)S|?PsW7#$Y{a3h@KcAmiyjAGs?n4>ors0Wx z@oQ;-;|c@z!mWv1Sq7 zNa0^M<+@iIGaVBemWZ0DzJ?dzJ7F27BlhY3su^rhxY3AdA$kHzwN6CkPXysa*Q4PrbEC+X99QSN1@_!gb<2~+|Z z6SSGC*taS7VZ=}nPmZuM$4GDK?|T@dSt;}2ASXV1Z_ipt|MzSC5rYzY9QazN`lv+T zsye@=4uOi4(?*Fw9?;OKfg1m=3o_}0>EfR^!y!{CTInpi?4#$#Ra>P$Q#hW4!O zYw?CX5-oj4?xn6pd|cp_GuwGAfK=kR^<5khO-C zrA5k;b!sx9kRnS@HB^LmdOPPi&-b41JKy_#=X~dU_c{OTfBo)jxv%>^*ZJT76h{Xu zNwJ+`002PJ#v1Fye?R;6fQ0xmFUjK;03bN#Xiv1@6)#*G_%g+`+l z6&0nWrSXx$V9@FG!oouSBp>C^JY#?PJVtq--nNUx4$Fb?Kkp6 z_*OpV=H}+*<^4u})Nkbb@U8zN{CnVUoiF)G_{sk({B8Pg-Tu=g{7L@!EB5~s|JSSh z-uUm@_>#Zhf4|P(p8rnBzm)gaa{u2HE6rV3`OW?1tdj!)ARs6N+$Jmn5)~7dkOWIX zpwioAWOvBPE9_KMQdUugsi|x1+O4Ujy$7!IhpygU#6EokL!{CE0|yToqfAWA&={&V+`ApFJ0G{zBx% zsOXqWv6tiGuOuWUB~wzaT~AF*r)JPHv*_75xi|9i3kr)$7^USEmCUN@np?GX^$m?p zx9>E!-n-w{-qHEs;iJc0Po6&O>Fw)(J}@{mJTl6D@sjgujQe_gV)D(~sp*+_v+qC5 z&3{~6TK@Fs=PzG*E34nW|5#i9xv^>XzSD)jdjT&yE3Cklpzb69AWpTxqKWbfOAk|` z*eiu1f1dZz_m>XK)t<73?Tf=6J#!+|4SfE7u}d@8B~9Jj90u|ur0a^wt8d$h9zUaZ zbZ;4hRo7e~dhQ@rNKEVx*J3I}JaoIXzClmhR{Vr-(Wh_wMMR@J!gr$v+dL>K|8caI zm-~?3eeflfP4A$8Yp<+n{$x7*3SB-klClx9>9wlSde>V9#fsS4zvwc$$->Q+w)EX- zEsYag38DLxa@N+ppZdL>8;Kegmu+WFoTh-GLo;k_r&(X)S6DHN$e+puTjzx-G zywWCFD}i6Q=gzxxt2#hJh|?X&E91#>Ue(%(v~)K54RmRBZxm^14jkuoe*z<$+=A-M zODbRaBAe{$Fadp*6~*FvH2PjDN8Gl{@!<|>Y)+kyD48*PVyZQhMC){mY{)!uAWW69 zcOJRFK72_EHwn%#uIS(lEZmLFNYGwi-6Q;kZEu$x$D3!V2p?KWr)aJV$_cq2#{*AW z=;X^kU_D4gex&VA`G}Sr(X(1L(M9naY*7QZp zr*kLFOzR>ksCDvMs7s|9DF$9YJXmo7d#rbzce@7QxsSAe;9}V-hOAe~B$|((sq1h%5 z->k4CmD2R~7^uMU^|={8O?B8TA|~*v74|^$(9?$|Ameq_x!EdgSJwHF1tH@{L6Yr< zD%9ey8qYbWBBGK!>O?8b6upbjEshRq!hYuHZ39wZ3Zv7vkJJaVHCg zolj-!Nnx{Q4e6PVvlq8#`HZ{f1QT`BaEYr<~|0%x4bI;S4t zCm$)2Ykuyqzr|>lfB3zCJApXE**D1^R+qoqhnW=zPMMIA2m~upP?vD(Er$H~RPY{_ zKQ$j$0)O_rxEc7~qjcuel+pl z8KGOruhxkuAy2X)p$_)H3ZSDR#0E`>D)>WjN7HKidTBb|&>h;VgvoH%U4RqA-G9n!%1^y_AvX1+Xs(t^C7e`=Ch% ztoLens~+tj=fm`qUBULz>0E2;GM1B0@&j=;cSLPp5*LH`Y$%Hp+hto7vr{VW`wZI_ zmf;Yz!=8w51!n9d2P=r?DHX(J4n{iO*DlJf9g3M02fzl%$Elvbj9DAlgQNK`NeMZB4K?4T@8A9%@L7aFF(z66Lvk<-l<@#HIN ze`3h~)CH?0JV>H4!n8}JI6?~8O;`|bMYFrjPZ_ezDYAIQZE=n4FxY+y_ZaAUg$t-r zom>h-WV?djt>L3&1RqyTNHvNuCxqfYv)9b^u5hJbRI4Q@<*;oK0(i+26CG>fR`RQ+)!WsHcbqH33~9!CMEagI81T|sQ0hg}+M zOSF0|of{6hkm2ffXqQTv5m+;<>Uq*LAM$fdD8WLPZDdZ;V+H0?0vJs@)VBqb&!yZ= zDLc&`{{a~@o7L}MtPGRa?<|=O`qffVu9vpmz%duE++Y#idmugSf3;vX8e$VlI4)#> z!>{X+JQy6~lsT7?PxC7w)JO_fCby@uSzce%YTo%MC0Ite{Ai(&zG-E)2xf%{HfJ&u zJ#+`3l!~Xxxz4>n$(UO)-7i65m1ZLufI3$qwIj1pULOHv_yqb1t zpv?)Oih&Od4eC(y9BSn&gfBANL>gBl&iL5<_zq1>)ac$i+ovFWOr+7&>9fS;#?!m~lekuXCQPtNVm0ijZIEdHgk9|p;R-`SaiqS)PdQc;Bw;&yIVtW@ zXFq97-y<%SH6%1$sMb}QnuN3pdgvTAuYcBzth@7I_02mjeRi7IzaY6c!Sa)KULD)FwD^!yd7oCysz4I3RnKs*h*Ev_wuVqeYZxfYZ{ zT?U8xkWeVH$D+O!bo&MGqqZfG9x=4=1{&Oae*T}AZPeUKoU$k910*K!986uQvn*Yx zhjO|9G!H*21s&?lHc9h=ssFmj<*YOoyWPvuM>n8qV_Nf%e;6q8Z+hA_la6PWU8EPo zoJpmG;o=y6rvcQpzrtdW&#z;0yXeSaPMidmSoCeq_oUh*1~ci>lsL28N(Am+ zO8hF(&|=Ey-fX%i&=I*2EWdu=`3>Uo!fr*zd|YPRK#?!$ftk;Ia@&BM6s%V|r}cvJ zhxO6hFq-qvxJ*u{?h!0+B8qZi4%J2q6bsh&9v?LQNRk6fSfw}64IAv1H56~EWTxqj zuf8piRJ}h|zBX^f%VJj>MJ$APKa(&F)~99uAHB=yopE z^X>Qn$l;)&yc&t&7cUOZNsjdHMF3OjEu}5i7Zz*5!sX$dJG*%B^Li%e(@%B242es+ zIejR}{gAo)@lb%IcH~#VZF+$^%juJPAs_R&I*Tvqc}U#*>Fj-TCCB?(UZ{ke!G|K1 y>L;3`U*1(3XGk09FSi+JXh8S<%V=rZ+LnK$75@)&AjIeZ literal 0 HcmV?d00001 diff --git a/seatunnel-ui/src/views/task/synchronization-definition/dag/images/spilt.png b/seatunnel-ui/src/views/task/synchronization-definition/dag/images/spilt.png new file mode 100644 index 0000000000000000000000000000000000000000..0faffc77979ad0f823f72c4908f4cbf4b3c62edc GIT binary patch literal 337 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5m8A!&LZC(qc6a#!hT!C}|3Cqw8I!!-UAQtXdo2z1aH_hX& z*Y`J{=ly%$e%n7w$F;BWyd+bjgMt7D3y7MadLqhELBK;arK)Iq*xvMolTT>JCf;)-_G(`|;Qbdp-Rhohn2|`4gNC_%MDJG#vCn!ydsGvxZ z&;+CzLKOiEKOqp51O=prD&05U_5Ka_%l)wTtabLxGiT1(d-lu|cgfONfbS?D1OgE- zH8HT}$nJjwIvbB5#cMYUyS$*VA&Gf4j4ZD zRDDFYo!2cP<0NVHsxgRrR@m~caWTj;$YrW8e6cySf4KSm`tH*w=6m&@R&u8{b*Hu$ z7B<&_*Dx;hXCa~<9pp%HE`&kB1#neQ5BS1{LnUK732^oWJuCS6(ZVxka5%=p^o7tX z3C@Cq-`8vzFcwuU{e^GfAQz(jpvKKULBN1~^^sTf^g{wT{xGM;0|s>mtE|AHeHDPF z8e!Llevp1A16QPEj3kP4oy(-vLLxL_w*>1YLOyg|^Ca~Wr34*>srRV5x_K9k!Tln{ z!`2SswBkC0HQL~5II zF79W(5Jo@bB*9RfS4Ilx1c<)MVwZm1LSrODgz4|Q$q|R4KV$UxM3I_>nPWOI!PX1Z z@c@3OJw7fh0CgB51>b$Vc#*slxpR?`EPWj46d}$wgx3d4$3E%Fi|SItp6Qg!oK*yy z=!BnVEx3ZEO+XDEe6iWVM?%n_iF$nbX3VN}k+TT5MNplKA~r>sXoTYpaH0G8iSq+% z9&qU>RcQV9FZJ!wRJs(I#se#xGvx$f$;z!vvEu@{f&sq-R@qZdzyVCJV8&NC^hOF5 zsVru*3JmAcb}A3ra+Hh#Xh;JOfBaL_>02Kgq^bld{7`LIdTA``X2%ylQ&tOanLjlg zS@GO;TScQ0?jYjPs4cv&;t;){hkb+YJMP`7UgmtM!551bfw<~|cOKOR*>I5)9FT9YoqF@4NR$an`G*RZi9NWg|7=br8ho*2 zdyBKx9E#dO^SUQ}Sz>@$S(XO)zv9Ba+zUR**M8vAoQ zXuyJH*aA$g1oFj%ETTs&f{K2b)ThRdE&iBEePhcI#^8+J)s#qFZZXZ_Ha4dvQL$mZ zt6$An$EiZFvgwDrayjTx5o%S$dMcwuHYVlDa0;mM0)VluO{Tx@ar;vdcwxW&D(h#k zUs|gF2g110gZaT4=4idU_ZOU1HOTG3Pv#;UapG?#m*%%T@ z4*GBUgd!pop+pvb-*ANg?MlWjIT=FlvTjgUO(ZJ^;IJ z3UQIo`LAkAE(}dH3!66}Lj+uQ`PLR?8I$m)GZioy*Ii#X#&daBG$uDSo>~>>RmVQ8%0V z(8jHOPU3A{PHX>TiyVA~uf4TA@~e(JXSvy7A2(&WA~<;edja!?-~GfPbJ}CYvqtLw z(#JjKU$MJ~)mbBlBDyeLv3oZAyR>Fdr2Y9QN-UFSsQt+1Av>T#C8mhrIpP~tq1Mg& zXg<3qPWjxg88hGkxbw2$hvcApYL7{*p1=giN2^tVg$Kmx~!L6Db|T!pkQ|jv;n*@J%n3r-l^% zIgMkc5Um_!H^y#koWhm93_D(_vn)3;mbaxGEdvYt-sJ&2;N6K!@{5RRTVp&gy1BAs zX(U)G+y-UdlxNM2CZVh6AKNm%`w{~0MaMGlGjEGF%7y6?&3xSw++O%x%4yv-+5i5s z^w|6#aPQ-FzV$xkb4-T)L5T%fZ7CaiZ~3@zSnVaH!*EQ@4serJa;U$~)B&>S-)f;= zrLN-9g@=v-djm!-k-c20y90Bf$~z^V=XJhivqR++>MHYS7O?458FL~_n9OC7-=D^rY#cz70XlAj$#Q6fBrGbqb z17*jx4ah9yum4bu5k=_;j}jU_MFG*GIl{c^i6{vP+B-R4Oo0TND|tflw~Qoaac^aqDhQ#ZJNldAirYp50?_~`vO_k=@HE( zH1Ib?EtO_HRuuQoMnFe`l$ET%Q8rBe{q3ff3?b9J4B?1&kKA5zaXe}u z_I3of<&r}4WINW&H^LX*oNUqnzt>sEHHjp`_I0_Xo)+)vXkqGJA3-cU!a> z+P(gGo@=Kpv*mw29_6~ATSFVCs1VWh3M~w7@Z(OV!_NQap~i}5o$YcW`94+>&zecH z-rf+Od2%-=DkD9v3Ys^hHKHZcuc-ffgg5Y4(1E5P6-LY*FK50`DfX|`nadU%dfxZs zo0CW5QL1n+TBFT|DE1M6zZtP|-##7VH1auWsHmbP0r{!@o<;hJ#&ZlY7e()en&*(` zSN{M{Xj0HSitShw!I zQ9!$QJFJumw|{>+IiX6s&a#d@6ei!?sox*_d%QkCCiY#p_{Q6ewo>xMGrG)V2v@64 zz*2Z!c=LTVN{*}Fh2pa5nJ=yveywg6KXWZC~jFjB) zH*U!632VVoQ)a7!2w(Tln zM-=atdho#eQbID1LxB-FvYZ~v9ID8XiozOhbhr0*2hGxC3boHgwb)TUJCBL(ARP!@A6;npgPJ1am(jj?YSky3-1_)_r+f=o95)eAsZ#>CEW|o zfOpE63#31e)gq|gP~4w{6F3m~VVl{u|FNP_@EUMr8R_P@J{ip}e)gi-UY)y7L7f+G zasH#~l+_2(K1D~-d|ouNZO_4OO%P-B5Y^aeZXi7yYn2GyaIz zWQAQz-q=T@gEG~0uR-75yz4qSvj$P1iYxG>$Imh?K_MkoI4PlctgaAmjMUv#-H3Z* z9ew*>l^Rj`xac&k5{Me7hvOV+Gw#5;qM8C;fgP2YkX? z@7=oS95i1F8$B~OGGh}kf!*m_8l&;gjPS15;Q}G@*sHeX-yPGwrD&)|&dYC&!C0fP zjmlG6iMgQ4IB?T!^4R9Lf+7I#!0^|$rXO_^^3AuX{#z%@_GK0+M!Hg~{?+epwI6Or z@bQ!8{@#=G*^jf~%i#`@jXH(Iz4APMf0G_nFpwD1#qDut>V8i3sXU)aKzV!8=v4>a z`m|?fgiN>_IYa8L0jGU&cNDr}%V}F|(Q?}V*B;=+z$%b_#cWmRKNBgX!_Ik{!Z7cH zubbojJ}X|zAj&CN;J%qi$xV63=^=l3_Iyo;`N4s!snpPqnsV3B%t$>@V;{~Nd%vy) z34~N~buC6MhK9-+qM6Cm7vSELvfj7Bq5vlG&Yw=Uy8r2yCjc=7c-;_kSi7VTE^!EB z!m0rl6lW_|qxBF_bna1V`E9y8g9^HeS+MwFVdqZYbr}qv<|lrKXK_>VqD0U2+zd9n z{S5ffOwXX@2{?=qwq3nRE1Jh2X!3M&oh}`F|iiErc&XVp4XEB7eN9+?G;0J zT|CPo)vn*+Tj`QUy^{k@UO*9wuocgabJz?q&4wyxcM~x2Ak?ZIZ~g`87RWc1Xl1^9 z1-ueN^;ORO_Lx(XAgJ@{fpQo0aprIn774Ihmj)|>(|py#CbHUPG@|u{K%bQB=!)i8 zR21F@l4(C35*S{F-Yff*9DR^-45zRq}5r^&xABq$ZLs|a*Lh!1aQ zwsun^+#tl%(9)n>A4mFsz3x*D literal 0 HcmV?d00001 diff --git a/seatunnel-ui/src/views/task/synchronization-definition/dag/index.module.scss b/seatunnel-ui/src/views/task/synchronization-definition/dag/index.module.scss new file mode 100644 index 000000000..5e48cfc93 --- /dev/null +++ b/seatunnel-ui/src/views/task/synchronization-definition/dag/index.module.scss @@ -0,0 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.workflow-dag { + display: flex; + height: calc(100vh - 190px); +} \ No newline at end of file diff --git a/seatunnel-ui/src/views/task/synchronization-definition/dag/index.tsx b/seatunnel-ui/src/views/task/synchronization-definition/dag/index.tsx new file mode 100644 index 000000000..0ee3b7d20 --- /dev/null +++ b/seatunnel-ui/src/views/task/synchronization-definition/dag/index.tsx @@ -0,0 +1,108 @@ +/* + * 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, onMounted } from 'vue' +import { DagSidebar } from './sidebar' +import { DagCanvas } from './canvas' +import { DagToolbar } from './toolbar' +import { NSpace, NSpin } from 'naive-ui' +import { useI18n } from 'vue-i18n' +import { useDagDetail } from './use-dag-detail' +import styles from './index.module.scss' + +const SynchronizationDefinitionDag = defineComponent({ + name: 'SynchronizationDefinitionDag', + setup() { + const dagRef = ref() + + const tempNode = { + type: '', + name: '' + } + const { t } = useI18n() + const { state, detailInit, onDelete, onSave } = useDagDetail() + const handelDragstart = (type: any, name: any) => { + tempNode.type = type + tempNode.name = name + } + + const handelDrop = (e: DragEvent) => { + if (!tempNode.type) return + dagRef.value.addNode({ + x: e.offsetX, + y: e.offsetY, + label: tempNode.name || tempNode.type, + node: tempNode.type + }) + } + + const handleDelete = async () => { + const cells = dagRef.value.getSelectedCells() + if (!cells.length) { + window.$message.warning( + t('project.synchronization_definition.delete_empty_tips') + ) + return + } + let result = true + if (cells[0].isNode() && !cells[0].getData().unsaved) { + result = await onDelete(cells[0].getData().pluginId) + } + if (result) dagRef.value.removeCell(cells[0].id) + } + + const handleSave = () => { + const result = dagRef.value.getDagData() + result && onSave(dagRef.value.getDagData(), dagRef.value.getGraph()) + } + + const handleLayout = ( + layoutType: 'grid' | 'dagre', + cols: number, + rows: number + ) => { + dagRef.value.layoutDag(layoutType, cols, rows) + } + + onMounted(async () => { + const result = await detailInit() + if (result?.nodesAndEdges) { + dagRef.value.addNodesAndEdges( + result.nodesAndEdges.plugins, + result.nodesAndEdges.edges + ) + } + }) + + return () => ( + + + +
+ + +
+
+
+ ) + } +}) + +export default SynchronizationDefinitionDag diff --git a/seatunnel-ui/src/views/task/synchronization-definition/dag/node-mode-model.module.scss b/seatunnel-ui/src/views/task/synchronization-definition/dag/node-mode-model.module.scss new file mode 100644 index 000000000..5eabbcdd9 --- /dev/null +++ b/seatunnel-ui/src/views/task/synchronization-definition/dag/node-mode-model.module.scss @@ -0,0 +1,50 @@ +/* + * 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. + */ + +.list-container { + width: 250px; + + dl { + overflow: auto; + height: calc(100vh - 160px); + + dd { + cursor: pointer; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + padding: 15px 0; + border-bottom: 1px solid #ddd; + } + + .dd-active { + color: #1890ff; + } + } + +} +.adjust-th-height { + height: 48px; +} + +.table-container-margin { + margin: 0 12px 0 36px; +} + +:global .row-error { + color: #ff4d4f; +} diff --git a/seatunnel-ui/src/views/task/synchronization-definition/dag/node-model.tsx b/seatunnel-ui/src/views/task/synchronization-definition/dag/node-model.tsx new file mode 100644 index 000000000..b09034eae --- /dev/null +++ b/seatunnel-ui/src/views/task/synchronization-definition/dag/node-model.tsx @@ -0,0 +1,142 @@ +/* + * 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 } from 'vue' +import { + NSpace, + NDataTable, + NEmpty, + NGrid, + NGridItem, + NEllipsis +} from 'naive-ui' +import { useNodeModel } from './use-model' +import { useI18n } from 'vue-i18n' +import styles from './node-mode-model.module.scss' + +const NodeModeModal = defineComponent({ + name: 'NodeModeModal', + props: { + type: { + type: String, + default: 'source' + }, + transformType: { + type: String, + default: '' + }, + predecessorsNodeId: { + type: String, + default: '' + }, + currentNodeId: { + type: String, + default: '' + }, + schemaError: { + type: Object, + default: {} + }, + refForm: { + type: Object, + default: null + } + }, + setup(props, { expose }) { + const { t } = useI18n() + const { state, onInit, onSwitchTable, onUpdatedCheckedRowKeys } = + useNodeModel(props.type, props.transformType, props.predecessorsNodeId, props.schemaError, props.currentNodeId, props.refForm) + + expose({ + getOutputSchema: () => ({ + allTableData: state.allTableData, + outputTableData: state.outputTableData, + inputTableData: state.inputTableData + }), + getSelectFields: () => ({ + tableFields: state.selectedKeys, + all: state.selectedKeys.length === state.inputTableData.length + }), + setSelectFields: (selectedKeys: string[]) => + (state.selectedKeys = selectedKeys), + initData: (info: any) => { + onInit(info) + } + }) + + return () => ( + + + +

{t('project.synchronization_definition.table_name')}

+
+ {state.tables.map((table) => ( +
void onSwitchTable(table)} + > + {table} +
+ ))} +
+ {state.tables.length === 0 && } +
+
+ + +

+ {t('project.synchronization_definition.input_table_structure')} +

+ row.name} + checkedRowKeys={state.selectedKeys} + scrollX={state.inputTableWidth} + /> +
+
+ { + props.type !== 'sink' && + +

+ {t('project.synchronization_definition.output_table_structure')} +

+ +
+
+ } +
+ ) + } +}) + +export default NodeModeModal diff --git a/seatunnel-ui/src/views/task/synchronization-definition/dag/node-setting.tsx b/seatunnel-ui/src/views/task/synchronization-definition/dag/node-setting.tsx new file mode 100644 index 000000000..0461b70b8 --- /dev/null +++ b/seatunnel-ui/src/views/task/synchronization-definition/dag/node-setting.tsx @@ -0,0 +1,135 @@ +/* + * 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, nextTick, watch } from 'vue' +import { useI18n } from 'vue-i18n' +import { + NDrawer, + NDrawerContent, + NSpace, + NButton, + NTabs, + NTabPane +} from 'naive-ui' +import { useNodeSettingModal } from './use-node-setting' +import NodeModeModal from './node-model' +import ConfigurationForm from './configuration-form' +import type { PropType } from 'vue' +import type { NodeInfo } from './types' + +const props = { + show: { + type: Boolean as PropType, + default: false + }, + nodeInfo: { + type: Object as PropType, + default: {} + } +} + +const NodeSetting = defineComponent({ + name: 'SettingNodeModal', + props, + emits: ['cancelModal', 'confirmModal'], + setup(props, ctx) { + const { t } = useI18n() + const { + state, + configurationFormRef, + modelRef, + onSave, + handleTab, + handleChangeTable + } = useNodeSettingModal(props, ctx) + + const cancelModal = () => { + state.tab = 'configuration' + state.width = '60%' + ctx.emit('cancelModal', props.show) + } + + watch( + () => props.show, + async () => { + await nextTick() + + if (props.show && configurationFormRef.value) { + await configurationFormRef.value.setValues(props.nodeInfo) + } + if (props.show && modelRef.value) { + modelRef.value.setSelectFields( + props.nodeInfo.selectTableFields?.tableFields || [] + ) + } + } + ) + + return () => ( + + + {{ + default: () => ( + + + + + + + + + ), + footer: () => ( + + + {t('project.synchronization_definition.cancel')} + + + {t('project.synchronization_definition.confirm')} + + + ) + }} + + + ) + } +}) + +export default NodeSetting diff --git a/seatunnel-ui/src/views/task/synchronization-definition/dag/sidebar/index.module.scss b/seatunnel-ui/src/views/task/synchronization-definition/dag/sidebar/index.module.scss new file mode 100644 index 000000000..c2a429c6b --- /dev/null +++ b/seatunnel-ui/src/views/task/synchronization-definition/dag/sidebar/index.module.scss @@ -0,0 +1,40 @@ +/* + * 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. + */ + +.task-container { + width: 250px; + flex-shrink: 0; +} + +.task-item { + cursor: pointer; + padding: 1px 15px; + border: 1px solid #eee; + border-radius: 5px; + height: 36px; + display: flex; + align-items: center; + .task-image { + width: 20px; + height: 20px; + // margin-top: 6px; + } +} + +h3 { + margin: 0; +} \ No newline at end of file diff --git a/seatunnel-ui/src/views/task/synchronization-definition/dag/sidebar/index.tsx b/seatunnel-ui/src/views/task/synchronization-definition/dag/sidebar/index.tsx new file mode 100644 index 000000000..612b86de4 --- /dev/null +++ b/seatunnel-ui/src/views/task/synchronization-definition/dag/sidebar/index.tsx @@ -0,0 +1,128 @@ +/* + * 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, toRefs, ref } from 'vue' +import { NSpace, NCard } from 'naive-ui' +import { useSidebar } from './use-sidebar' +import { useI18n } from 'vue-i18n' +import styles from './index.module.scss' +import SourceImg from '../images/source.png' +import SinkImg from '../images/sink.png' +import FieldMapperImg from '../images/field-mapper.png' +import FilterEventTypeImg from '../images/filter-event-type.png' +import ReplaceImg from '../images/replace.png' +import SplitImg from '../images/spilt.png' +import CopyImg from '../images/copy.png' +import SqlImg from '../images/sql.png' + +const DagSidebar = defineComponent({ + name: 'DagSidebar', + emits: ['dragstart'], + setup(props, ctx) { + const businessModel = ref('data-integration') + const { variables, getConnectorTransformsTypeList } = useSidebar() + const { t } = useI18n() + const handleDragstart = (type: string, name?: string) => { + ctx.emit('dragstart', type, name) + } + + onMounted(() => { + getConnectorTransformsTypeList() + }) + + return { + ...toRefs(variables), + businessModel, + t, + handleDragstart + } + }, + render() { + return ( + + +

+ {this.t('project.synchronization_definition.source_and_sink')} +

+
this.handleDragstart('source', 'Source')} + > + + + Source + +
+
this.handleDragstart('sink', 'Sink')} + > + + + Sink + +
+ {this.transforms.length > 0 && ( +

{this.t('project.synchronization_definition.transforms')}

+ )} + {this.businessModel === 'data-integration' && + this.transforms.map((t: any) => { + const item: any = { + name: t.pluginIdentifier.pluginName + } + + if (item.name === 'FieldMapper') { + item.icon = FieldMapperImg + } else if (item.name === 'FilterRowKind') { + item.icon = FilterEventTypeImg + } else if (item.name === 'Replace') { + item.icon = ReplaceImg + } else if (item.name === 'MultiFieldSplit') { + item.icon = SplitImg + } else if (item.name === 'Copy') { + item.icon = CopyImg + } else if (item.name === 'Sql') { + item.icon = SqlImg + } + + return ( +
+ this.handleDragstart( + 'transform', + t.pluginIdentifier.pluginName + ) + } + > + + + {item.name} + +
+ ) + })} +
+
+ ) + } +}) + +export { DagSidebar } diff --git a/seatunnel-ui/src/views/task/synchronization-definition/dag/sidebar/use-sidebar.ts b/seatunnel-ui/src/views/task/synchronization-definition/dag/sidebar/use-sidebar.ts new file mode 100644 index 000000000..14c82b262 --- /dev/null +++ b/seatunnel-ui/src/views/task/synchronization-definition/dag/sidebar/use-sidebar.ts @@ -0,0 +1,38 @@ +/* + * 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, ref } from 'vue' +import { connectorTransformsTypeList } from '@/service/sync-task-definition' +import { useRoute } from 'vue-router' + +export function useSidebar() { + const route = useRoute() + const variables = reactive({ + transforms: ref([]) + }) + + const getConnectorTransformsTypeList = () => { + connectorTransformsTypeList(route.params.jobDefinitionCode as string).then((res: any) => { + variables.transforms = res + }) + } + + return { + variables, + getConnectorTransformsTypeList + } +} \ No newline at end of file diff --git a/seatunnel-ui/src/views/task/synchronization-definition/dag/split-modal.tsx b/seatunnel-ui/src/views/task/synchronization-definition/dag/split-modal.tsx new file mode 100644 index 000000000..e861d8c8e --- /dev/null +++ b/seatunnel-ui/src/views/task/synchronization-definition/dag/split-modal.tsx @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { defineComponent, PropType, ref } from 'vue' +import { NForm, NFormItem, NInput } from 'naive-ui' +import { useI18n } from 'vue-i18n' +import type { ModelRecord } from './types' + +const SplitModal = defineComponent({ + name: 'SplitModal', + props: { + rowData: { + type: Object as PropType, + default: {} + }, + outputData: { + type: Array as PropType>, + default: [] + } + }, + setup(props, { expose }) { + const { t } = useI18n() + const fieldsRef = ref() + const separatorRef = ref() + + expose({ + getFields: () => ({ + outputFields: fieldsRef.value.split(','), + separator: separatorRef.value, + original_field: props.rowData.name + }) + }) + + return () => ( + + + + + + {props.rowData.name} + + + + + + ) + } +}) + +export default SplitModal diff --git a/seatunnel-ui/src/views/task/synchronization-definition/dag/toolbar/index.tsx b/seatunnel-ui/src/views/task/synchronization-definition/dag/toolbar/index.tsx new file mode 100644 index 000000000..85e498a23 --- /dev/null +++ b/seatunnel-ui/src/views/task/synchronization-definition/dag/toolbar/index.tsx @@ -0,0 +1,240 @@ +/* + * 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, reactive } from 'vue' +import { NSpace, NCard, NButton, NIcon, NTooltip } from 'naive-ui' +import { + CopyOutlined, + CloseCircleOutlined, + SaveOutlined, + DeleteRowOutlined, + FullscreenOutlined, + FormatPainterOutlined, + FullscreenExitOutlined, + SettingOutlined +} from '@vicons/antd' +import { useI18n } from 'vue-i18n' +import { useTextCopy } from '@/hooks' +import { useRoute, useRouter } from 'vue-router' +import { useFullscreen } from '@vueuse/core' +import { LayoutModal } from './layout-modal' +import { TaskSettingModal } from './task-setting-modal' +import { useSynchronizationDefinitionStore } from '@/store/synchronization-definition' +import type { Router } from 'vue-router' + +const DagToolbar = defineComponent({ + name: 'DagToolbar', + emits: ['delete', 'save', 'layout'], + setup(props, { emit }) { + const state = reactive({ + showLayoutModal: false, + showSettingModal: false + }) + const { t } = useI18n() + const { copy } = useTextCopy() + const { isFullscreen, toggle } = useFullscreen() + const router: Router = useRouter() + const route = useRoute() + const dagStore = useSynchronizationDefinitionStore() + const onSave = () => { + emit('save') + } + + const onClose = () => { + router.push({ + name: 'synchronization-definition', + query: { + project: route.query.project, + global: route.query.global + } + }) + } + + const onDelete = () => { + emit('delete') + } + + const onLayout = ( + layoutType: 'grid' | 'dagre', + cols: number, + rows: number + ) => { + state.showLayoutModal = false + emit('layout', layoutType, cols, rows) + } + + return () => ( + <> + + + + {dagStore.getDagInfo.name} + + {{ + trigger: () => ( + { + copy(dagStore.getDagInfo.name) + }} + > + + + + + ), + default: () => t('project.synchronization_definition.copy') + }} + + + + + {{ + trigger: () => ( + void (state.showSettingModal = true)} + > + + + + + ), + default: () => t('project.synchronization_definition.setting') + }} + + + {{ + trigger: () => ( + + + + + + ), + default: () => t('project.synchronization_definition.delete') + }} + + + {{ + trigger: () => ( + + + {isFullscreen.value ? ( + + ) : ( + + )} + + + ), + default: () => + isFullscreen.value + ? t( + 'project.synchronization_definition.close_full_screen' + ) + : t('project.synchronization_definition.open_full_screen') + }} + + + {{ + trigger: () => ( + void (state.showLayoutModal = true)} + > + + + + + ), + default: () => t('project.synchronization_definition.format') + }} + + + {{ + trigger: () => ( + + + + + + ), + default: () => t('project.synchronization_definition.save') + }} + + + {{ + trigger: () => ( + + + + + + ), + default: () => t('project.synchronization_definition.close') + }} + + + + + void (state.showLayoutModal = false)} + onConfirmModal={onLayout} + /> + void (state.showSettingModal = false)} + /> + + ) + } +}) + +export { DagToolbar } diff --git a/seatunnel-ui/src/views/task/synchronization-definition/dag/toolbar/layout-modal.tsx b/seatunnel-ui/src/views/task/synchronization-definition/dag/toolbar/layout-modal.tsx new file mode 100644 index 000000000..9a2248b29 --- /dev/null +++ b/seatunnel-ui/src/views/task/synchronization-definition/dag/toolbar/layout-modal.tsx @@ -0,0 +1,120 @@ +/* + * 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 { NSelect, NForm, NFormItem, NInputNumber } from 'naive-ui' +import { useI18n } from 'vue-i18n' +import Modal from '@/components/modal' +import type { PropType } from 'vue' + +const props = { + showModalRef: { + type: Boolean as PropType, + default: false + } +} + +const LayoutModal = defineComponent({ + name: 'LayoutModal', + props, + emits: ['cancelModal', 'confirmModal'], + setup(props, ctx) { + const { t } = useI18n() + const model = ref({ + layoutType: 'dagre', + rows: 0, + cols: 0 + }) + + const resetModel = () => { + model.value.layoutType = 'dagre' + model.value.rows = 0 + model.value.cols = 0 + } + + const onCancelModal = () => { + ctx.emit('cancelModal') + resetModel() + } + + const onConfirmModal = () => { + ctx.emit( + 'confirmModal', + model.value.layoutType as 'grid' | 'dagre', + model.value.cols, + model.value.rows + ) + resetModel() + } + + return { + t, + model, + onCancelModal, + onConfirmModal + } + }, + render() { + return ( + + + + + + {this.model.layoutType === 'grid' && ( + <> + + + + + + + + )} + + + ) + } +}) + +export { LayoutModal } diff --git a/seatunnel-ui/src/views/task/synchronization-definition/dag/toolbar/task-setting-modal.tsx b/seatunnel-ui/src/views/task/synchronization-definition/dag/toolbar/task-setting-modal.tsx new file mode 100644 index 000000000..27f56c3af --- /dev/null +++ b/seatunnel-ui/src/views/task/synchronization-definition/dag/toolbar/task-setting-modal.tsx @@ -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 { defineComponent, PropType, watch, inject } from 'vue' +import { useI18n } from 'vue-i18n' +import { NForm, NFormItem, NInput, NSelect, NSpin } from 'naive-ui' +import { useTaskSettingModal } from './use-task-setting-modal' +import { DynamicFormItem } from '@/components/dynamic-form/dynamic-form-item' +import Modal from '@/components/modal' + +const props = { + show: { + type: Boolean as PropType, + default: false + } +} + +const TaskSettingModal = defineComponent({ + name: 'TaskSettingModal', + props, + emits: ['cancelModal'], + setup(props, ctx) { + const { t } = useI18n() + const { state, settingFormRef, handleValidate, settingInit } = + useTaskSettingModal(ctx) + + const onCancelModel = () => { + ctx.emit('cancelModal') + } + + const onConfirmModel = () => { + handleValidate() + } + + watch( + () => props.show, + () => { + props.show && settingInit() + } + ) + + return () => ( + + + + + + + + + + + + + {state.formStructure.length > 0 && ( + + )} + + + + ) + } +}) + +export { TaskSettingModal } diff --git a/seatunnel-ui/src/views/task/synchronization-definition/dag/toolbar/use-task-setting-modal.ts b/seatunnel-ui/src/views/task/synchronization-definition/dag/toolbar/use-task-setting-modal.ts new file mode 100644 index 000000000..1a2fdcdea --- /dev/null +++ b/seatunnel-ui/src/views/task/synchronization-definition/dag/toolbar/use-task-setting-modal.ts @@ -0,0 +1,123 @@ +/* + * 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, ref } from 'vue' +import { useI18n } from 'vue-i18n' +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 { useSynchronizationDefinitionStore } from '@/store/synchronization-definition' +import { taskDefinitionForm } from '@/service/sync-task-definition' +import { updateSyncTaskDefinition } from '@/service/sync-task-definition' +import { useRoute } from 'vue-router' +import { omit } from 'lodash' +import type { FormItemRule } from 'naive-ui' +import type { SetupContext } from 'vue' + +export function useTaskSettingModal(ctx: SetupContext<'cancelModal'[]>) { + const route = useRoute() + const { t } = useI18n() + const settingFormRef = ref() + const dagStore = useSynchronizationDefinitionStore() + const state = reactive({ + model: { + taskName: '', + description: '', + engine: ref('SeaTunnel') + }, + rules: { + taskName: { + required: true, + trigger: ['input', 'blur'], + validator: (rule: FormItemRule, value: any) => { + if (!value) { + return Error( + t('project.synchronization_definition.task_name_validate') + ) + } + } + } + }, + saving: false, + formStructure: ref([]), + formName: ref(''), + loading: false + }) + + const handleValidate = async () => { + await settingFormRef.value.validate() + + if (state.saving) return + state.saving = true + + try { + const params = { + name: state.model.taskName, + description: state.model.description, + engine: state.model.engine, + env: omit(state.model, ['taskName', 'description', 'engine']) + } + await updateSyncTaskDefinition( + route.params.jobDefinitionCode as string, + params + ) + dagStore.setDagInfo(params) + ctx.emit('cancelModal') + } finally { + state.saving = false + } + } + + const settingInit = async () => { + if (state.loading) return + state.loading = true + try { + const config = dagStore.getDagInfo + state.model.taskName = config.name + state.model.description = config.description + const resJson = await taskDefinitionForm() + const res = JSON.parse(resJson) + const forms = config.env + ? res.forms.map((f: any) => { + f.defaultValue = config.env[f.field] + return f + }) + : res.forms.map((item: any) => { + if(item.field === "job.mode") { + item.defaultValue = '' + } + return item + }) + state.formName = res.name + Object.assign(state.model, useFormField(forms)) + Object.assign(state.rules, useFormValidate(forms, state.model, t)) + state.formStructure = useFormStructure( + res.apis ? useFormRequest(res.apis, forms) : forms + ) as any + } finally { + state.loading = false + } + } + + return { + state, + settingFormRef, + handleValidate, + settingInit + } +} diff --git a/seatunnel-ui/src/views/task/synchronization-definition/dag/types.ts b/seatunnel-ui/src/views/task/synchronization-definition/dag/types.ts new file mode 100644 index 000000000..3ebc7c433 --- /dev/null +++ b/seatunnel-ui/src/views/task/synchronization-definition/dag/types.ts @@ -0,0 +1,61 @@ +/* + * 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 type NodeType = 'source' | 'sink' | 'transform' +export type OptionType = 'datasource' | 'database' | 'table' +export type { TableColumns } from 'naive-ui/es/data-table/src/interface' +export interface ModelRecord { + format: string + comment: string + defaultValue: string + nullable: boolean + primaryKey: boolean + type: string + name: string + isEdit?: boolean + isSplit?: boolean + copyTimes?: number + separator?: string + original_field?: string + splitDisabled?: boolean + unSupport?: boolean + outputDataType?: string +} +export type JobType = 'DATA_REPLICA' | 'DATA_INTEGRATION' +export type SceneMode = 'MULTIPLE_TABLE' | 'SPLIT_TABLE' | 'SINGLE_TABLE' +export type InputEdge = { + inputPluginId: string + targetPluginId: string +} +export type InputPlugin = { + config: string + connectorType: string + dataSourceId: 0 + name: string + pluginId: string + sceneMode: SceneMode + selectTableFields: { + all: boolean + tableFields: string[] + } + tableOption: { + databases: string[] + tables: string[] + } + type: NodeType +} +export type NodeInfo = { [key: string]: any } diff --git a/seatunnel-ui/src/views/task/synchronization-definition/dag/use-configuration-form.ts b/seatunnel-ui/src/views/task/synchronization-definition/dag/use-configuration-form.ts new file mode 100644 index 000000000..5252d7d86 --- /dev/null +++ b/seatunnel-ui/src/views/task/synchronization-definition/dag/use-configuration-form.ts @@ -0,0 +1,394 @@ +/* + * 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 _, { cloneDeep, find, omit } from 'lodash' +import { useI18n } from 'vue-i18n' +import { useRoute } from 'vue-router' +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 { + listSourceName, + getDatabaseByDatasource, + getTableByDatabase, + getFormStructureByDatasourceInstance, + getColumnProjection, + findSink +} from '@/service/sync-task-definition' +import { useSynchronizationDefinitionStore } from '@/store/synchronization-definition' +import type { NodeType } from './types' + +export const useConfigurationForm = ( + nodeType: NodeType, + transformType: string +) => { + const { t } = useI18n() + const dagStore = useSynchronizationDefinitionStore() + const route = useRoute() + const initialModel = { + name: '', + datasourceInstanceId: null, + datasourceInstanceName: null, + sceneMode: null, + database: null as null | string | string[], + tableName: null as null | string | string[], + kinds: [], + kind: 0, + columnSelectable: false, + pluginName: '', + datasourceName: '', + query: '' + } + + const state = reactive({ + model: cloneDeep(initialModel), + loading: false, + datasourceOptions: [], + datasourceLoading: false, + databaseOptions: [], + databaseLoading: false, + tableOptions: [], + tableLoading: false, + formStructure: [], + formLocales: {}, + formName: '', + formLoading: false, + inputTableData: [], + outputTableData: [], + tableColumnsLoading: false, + rules: { + name: { + required: true, + trigger: ['input', 'blur'], + validator: (ignore: any, value: string) => { + if (!value) { + return new Error( + t('project.synchronization_definition.node_name_validate') + ) + } + } + }, + datasourceInstanceId: { + required: true, + trigger: ['input', 'blur'], + validator: (ignore: any, value: string) => { + if (!value) { + return new Error( + t('project.synchronization_definition.source_name_validate') + ) + } + } + }, + sceneMode: { + required: true, + trigger: ['input', 'blur'], + validator: (ignore: any, value: string) => { + if (!value) { + return new Error( + t('project.synchronization_definition.scene_mode_validate') + ) + } + } + }, + database: { + required: true, + trigger: ['input', 'blur'], + validator: (ignore: any, value: string) => { + if (!value) { + return new Error( + t('project.synchronization_definition.database_validate') + ) + } + } + }, + tableName: { + required: true, + trigger: ['input', 'blur'], + validator: (ignore: any, value: string) => { + if (!value) { + return new Error( + t('project.synchronization_definition.table_name_validate') + ) + } + } + }, + kinds: { + required: true, + trigger: ['input', 'blur'], + validator: () => { + if (state.model.kinds.length === 0) { + return new Error( + state.model.kind + ? t('project.synchronization_definition.exclude_kind_validate') + : t('project.synchronization_definition.include_kind_validate') + ) + } + } + }, + query: { + required: true, + trigger: ['input', 'blur'], + validator: (ignore: any, value: string) => { + if (!value) { + return new Error( + t('project.synchronization_definition.query_validate') + ) + } + } + }, + } + }) + + const getDatasourceOptions = async (sceneMode: string) => { + if (state.datasourceLoading) return + state.datasourceLoading = true + try { + const result = await listSourceName( + route.params.jobDefinitionCode as string, + sceneMode + ) + state.datasourceOptions = result.map((item: any) => ({ + label: item.dataSourceInstanceName, + value: item.dataSourceInstanceId, + pluginName: + item.dataSourceInfo?.connectorInfo?.pluginIdentifier.pluginName, + datasourceName: item.dataSourceInfo?.datasourceName + })) + } finally { + state.datasourceLoading = false + } + } + + const getDatabaseOptions = async ( + datasourceInstanceId: string, + option?: any + ) => { + if (option?.label) state.model.datasourceInstanceName = option.label + if (option?.datasourceName) { + state.model.datasourceName = option.datasourceName + getColumnSelectable(option.datasourceName) + } + if (option?.pluginName) state.model.pluginName = option.pluginName + if (state.databaseLoading) return + state.databaseLoading = true + try { + if (nodeType !== 'transform') { + const result = await getDatabaseByDatasource(option.label) + state.databaseOptions = result.map((item: string) => ({ + label: item, + value: item + })) + } + await getFormStructure(datasourceInstanceId) + } finally { + state.databaseLoading = false + } + } + + const getTableOptions = async (databases: Array | string, filterName?: string, size?: number) => { + filterName = filterName || '' + if ( + nodeType === 'source' || + (dagStore.getDagInfo.jobType === 'DATA_INTEGRATION' && + nodeType === 'sink') + ) { + if (state.tableLoading) return + if (_.isArray(databases) && databases.length === 0) { + state.tableOptions = [] + return + } + state.tableLoading = true + try { + const result = await getTableByDatabase( + state.model.datasourceInstanceName || '', + typeof databases === 'string' ? databases : databases[0], + filterName, + size + ) + state.tableOptions = result.map((item: any) => ({ + label: item, + value: item + })) + } finally { + state.tableLoading = false + } + return + } + } + + const getSinks = async () => { + try { + if (state.datasourceLoading) return + state.datasourceLoading = true + const result = await findSink(route.params.jobDefinitionCode as string) + state.datasourceOptions = result.map((item: any) => ({ + label: item.dataSourceInstanceName, + value: item.dataSourceInstanceId, + pluginName: + item.dataSourceInfo?.connectorInfo?.pluginIdentifier.pluginName, + datasourceName: item.dataSourceInfo?.datasourceName + })) + } finally { + state.datasourceLoading = false + } + } + + const getFormStructure = async (datasourceInstanceId = '') => { + if (state.formLoading) return + state.formLoading = true + try { + const params = { + jobCode: Number(route.params.jobDefinitionCode) as number, + connectorType: nodeType + } as { + connectorType: string + connectorName?: string + dataSourceInstanceId?: string + jobCode: number + } + if (nodeType === 'transform') params.connectorName = transformType + if (nodeType !== 'transform') + params.dataSourceInstanceId = datasourceInstanceId + const resJson = await getFormStructureByDatasourceInstance(params) + if (resJson === 'null') return + const res = JSON.parse(resJson) + state.formName = res.name + res.forms = res.forms.filter( + (form: { field: string }) => + !['exclude_kinds', 'include_kinds'].includes(form.field) + ) + state.formLocales = res.locales + Object.assign(state.model, useFormField(res.forms)) + Object.assign(state.rules, useFormValidate(res.forms, state.model, t)) + state.formStructure = useFormStructure( + res.apis ? useFormRequest(res.apis, res.forms) : res.forms + ) as any + } finally { + state.formLoading = false + } + } + + const getColumnSelectable = async (pluginName: string) => { + if (state.model.sceneMode !== 'SINGLE_TABLE') { + state.model.columnSelectable = false + return + } + if (dagStore.getColumnSelectable(pluginName) !== undefined) { + state.model.columnSelectable = dagStore.getColumnSelectable(pluginName) + return + } + const res = await getColumnProjection(pluginName) + state.model.columnSelectable = res + dagStore.setColumnSelectable(pluginName, res) + } + + const updateFormValues = async (values: any) => { + if (state.loading) return + state.loading = true + try { + state.model.datasourceInstanceId = values.dataSourceId + state.model.name = values.name + state.model.sceneMode = values.sceneMode + if (values.sceneMode === 'SPLIT_TABLE') { + state.model.database = values.tableOption?.databases || [] + } else { + state.model.database = values.tableOption?.databases?.length + ? values.tableOption.databases[0] + : null + } + + if (values.sceneMode) { + await getDatasourceOptions(values.sceneMode) + } + if (nodeType === 'sink') { + await getSinks() + } + + if (values.dataSourceId) { + const option = find( + state.datasourceOptions, + (item: { value: string }) => item.value === values.dataSourceId + ) + await getDatabaseOptions(values.dataSourceId, option) + } + if (nodeType === 'transform') { + await getFormStructure() + } + + if (values.tableOption?.databases?.length) { + await getTableOptions(values.tableOption.databases[0], '', state.model.sceneMode === 'MULTIPLE_TABLE' ? 9999999 : 100) + } + + if (values.sceneMode === 'MULTIPLE_TABLE') { + state.model.tableName = values.tableOption?.tables || [] + } else { + state.model.tableName = values.tableOption?.tables.length + ? values.tableOption.tables[0] + : null + } + + if (values.config) { + const config = JSON.parse(values.config) + Object.assign( + state.model, + omit(config, ['exclude_kinds', 'include_kinds']) + ) + if (config.exclude_kinds || config.include_kinds) { + state.model.kind = config.exclude_kinds ? 1 : 0 + state.model.kinds = JSON.parse( + config.exclude_kinds || config.include_kinds + ) + } + } + } finally { + state.loading = false + } + } + + return { + state, + dagStore, + getDatasourceOptions, + getDatabaseOptions, + getTableOptions, + getFormStructure, + updateFormValues, + getSinks + } +} + +export const getSceneModeOptions = (jobType: string, t: Function) => { + return [ + { + label: t('project.synchronization_definition.multi_table_sync'), + value: 'MULTIPLE_TABLE', + disabled: jobType === 'DATA_INTEGRATION' + }, + { + label: t('project.synchronization_definition.sub_library_and_sub_table'), + value: 'SPLIT_TABLE', + disabled: jobType === 'DATA_REPLICA' + }, + { + label: t('project.synchronization_definition.single_table_sync'), + value: 'SINGLE_TABLE', + disabled: jobType === 'DATA_REPLICA' + } + ] +} diff --git a/seatunnel-ui/src/views/task/synchronization-definition/dag/use-dag-detail.ts b/seatunnel-ui/src/views/task/synchronization-definition/dag/use-dag-detail.ts new file mode 100644 index 000000000..f754bfd71 --- /dev/null +++ b/seatunnel-ui/src/views/task/synchronization-definition/dag/use-dag-detail.ts @@ -0,0 +1,166 @@ +/* + * 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 { + getDefinitionConfig, + getDefinitionDetail, + getDefinitionNodesAndEdges, + saveTaskDefinitionDag, + deleteTaskDefinitionDag, + checkDatabaseAndTable +} from '@/service/sync-task-definition' +import { useRoute, useRouter } from 'vue-router' +import { useSynchronizationDefinitionStore } from '@/store/synchronization-definition' +import { useMessage } from 'naive-ui' +import { useI18n } from 'vue-i18n' +import _ from 'lodash' +import type { InputPlugin, InputEdge } from './types' + +export const useDagDetail = () => { + const route = useRoute() + const router = useRouter() + const message = useMessage() + const { t } = useI18n() + const dagStore = useSynchronizationDefinitionStore() + const state = reactive({ + loading: false + }) + + const detailInit = async () => { + if (state.loading) return + state.loading = true + try { + const [nodesAndEdges, dagConfig, dagInfo] = await Promise.all([ + getDefinitionNodesAndEdges(route.params.jobDefinitionCode as string), + getDefinitionConfig(route.params.jobDefinitionCode as string), + getDefinitionDetail(route.params.jobDefinitionCode as string) + ]) + + const checkedNodesAndEdges = nodesAndEdges + ? await Promise.all( + nodesAndEdges.plugins.map(async (p: any) => { + if (p.type === 'SOURCE') { + return { + ...(await checkDatabaseAndTable( + String(p.dataSourceId), + p.tableOption + )), + pluginId: p.pluginId, + name: p.name + } + } + }) + ) + : null + + checkedNodesAndEdges + ? (nodesAndEdges.plugins = nodesAndEdges.plugins.map((pn: any) => { + if (pn.type === 'SOURCE') { + checkedNodesAndEdges.map((pc: any) => { + if (pc && pn.pluginId === pc.pluginId) { + if (pc.databases.length > 0) { + message.warning( + `(${pc.name}-${ + pc.pluginId + })[${pc.databases.toString()}] ${t( + 'project.synchronization_definition.database_exception_message' + )}`, + { closable: true, duration: 0 } + ) + } + if (pc.tables.length > 0) { + message.warning( + `(${pc.name}-${pc.pluginId})[${pc.tables.toString()}] ${t( + 'project.synchronization_definition.table_exception_message' + )}`, + { closable: true, duration: 0 } + ) + } + _.pullAll(pn.tableOption.databases, pc.databases) + _.pullAll(pn.tableOption.tables, pc.tables) + } + }) + } + return pn + })) + : null + + dagStore.setDagInfo({ ...dagConfig, ...dagInfo }) + return { nodesAndEdges: nodesAndEdges } + } finally { + state.loading = false + } + } + + const onDelete = async (id: string): Promise => { + try { + await deleteTaskDefinitionDag( + route.params.jobDefinitionCode as string, + id + ) + return true + } catch (err) { + return false + } + } + + const onSave = async ( + jobDag: { + plugins: InputPlugin[] + edges: InputEdge[] + }, + graph: any + ): Promise => { + if (state.loading) return false + state.loading = true + try { + const result = await saveTaskDefinitionDag( + route.params.jobDefinitionCode as string, + jobDag + ) + + if (result) { + const node = graph.getCellById(result.pluginId) + node.getData().isError = true + node.getData().schemaError = result.schemaError + graph.resetCells(graph.getCells()) + + window.$message.error(JSON.stringify(result.schemaError || ''), { + closable: true, + duration: 0 + }) + } else { + router.push({ + name: 'synchronization-definition', + query: { + project: route.query.project, + global: route.query.global + } + }) + } + + state.loading = false + return true + } catch (err) { + state.loading = false + return false + } + } + + return { state, detailInit, onDelete, onSave } +} diff --git a/seatunnel-ui/src/views/task/synchronization-definition/dag/use-model-columns.ts b/seatunnel-ui/src/views/task/synchronization-definition/dag/use-model-columns.ts new file mode 100644 index 000000000..fb8a4b0ea --- /dev/null +++ b/seatunnel-ui/src/views/task/synchronization-definition/dag/use-model-columns.ts @@ -0,0 +1,567 @@ +/* + * 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, ref } from 'vue' +import { + NFormItem, + NInput, + NSpace, + NButton, + NIcon, + NDropdown, + NPopconfirm, + NTooltip, + NEllipsis, + useDialog +} from 'naive-ui' +import { + EditOutlined, + SwapOutlined, + DeleteOutlined, + ColumnHeightOutlined, + CopyOutlined +} from '@vicons/antd' +import { useI18n } from 'vue-i18n' +import { + COLUMN_WIDTH_CONFIG, + calculateTableWidth +} from '@/common/column-width-config' +import { cloneDeep, remove, max } from 'lodash' +import SplitModal from './split-modal' +import type { TableColumns, ModelRecord } from './types' +import { TableColumn } from 'naive-ui/es/data-table/src/interface' + +export const useModelColumns = () => { + const { t } = useI18n() + const dialog = useDialog() + const splitModalRef = ref() + const handleSplit = (rowData: ModelRecord, outputData: ModelRecord[]) => { + dialog.create({ + title: t('project.synchronization_definition.split_field'), + content: () => h(SplitModal, { rowData, outputData, ref: splitModalRef }), + onPositiveClick: () => { + const { outputFields, separator, original_field } = splitModalRef.value.getFields() + + outputFields.forEach((field: string) => { + outputData.push({ + ...rowData, + name: field, + isSplit: true, + separator, + original_field + }) + }) + + // For fields that have been split, the button is grayed out. + const sourceGroup = outputData.filter((o: any) => !o.isSplit).map((o: any) => o.name) + const splitGroup = outputData.filter((o: any) => o.isSplit).map((o: any) => o.original_field) + + splitGroup.forEach((s: any) => { + if (sourceGroup.indexOf(s) >= 0) { + outputData[sourceGroup.indexOf(s)].splitDisabled = true + } + }) + }, + positiveText: t('project.synchronization_definition.confirm'), + negativeText: t('project.synchronization_definition.cancel') + }) + } + const createColumns = ({ + nodeType, + columnSelectable, + transformType, + outputTableData + }: { + nodeType: string + columnSelectable: boolean + transformType: string + outputTableData: ModelRecord[] + }) => { + const basicColumns = [ + { + title: '#', + key: 'index', + render: (row: any, index: number) => index + 1, + ...COLUMN_WIDTH_CONFIG['index'] + }, + { + title: t('project.synchronization_definition.field_name'), + key: 'name', + ...COLUMN_WIDTH_CONFIG['name'] + }, + { + title: t('project.synchronization_definition.field_type'), + key: 'type', + ...COLUMN_WIDTH_CONFIG['type'], + ellipsis: { + tooltip: true + } + }, + { + title: t('project.synchronization_definition.non_empty'), + key: 'nullable', + ...COLUMN_WIDTH_CONFIG['index'], + render: (row: any) => + row.nullable + ? t('project.synchronization_definition.yes') + : t('project.synchronization_definition.no') + }, + { + title: t('project.synchronization_definition.primary_key'), + key: 'primaryKey', + ...COLUMN_WIDTH_CONFIG['index'], + render: (row: any) => + row.primaryKey + ? t('project.synchronization_definition.yes') + : t('project.synchronization_definition.no') + }, + { + title: t('project.synchronization_definition.field_comment'), + key: 'comment', + ...COLUMN_WIDTH_CONFIG['name'], + ellipsis: { + tooltip: true + } + }, + { + title: t('project.synchronization_definition.default_value'), + key: 'defaultValue', + ...COLUMN_WIDTH_CONFIG['state'], + ellipsis: { + tooltip: true + } + } + ] as any[] + + const selection = { + type: 'selection', + disabled: (row: any) => { + return row.unSupport + }, + ...COLUMN_WIDTH_CONFIG['selection'] + } + + if (nodeType === 'transform' && transformType === 'FieldMapper') { + const outputColumns = cloneDeep(basicColumns) + const nameColumn = { + title: t('project.synchronization_definition.field_name'), + key: 'name', + ...COLUMN_WIDTH_CONFIG['name'], + render(row, index) { + return row.isEdit + ? h( + NFormItem, + { + showLabel: false, + validationStatus: !row.name ? 'error' : 'success', + feedback: !row.name + ? t('project.synchronization_definition.name_tips') + : '' + }, + () => + h(NInput, { + value: outputTableData[index].name, + onUpdateValue(v) { + outputTableData[index].name = v + }, + onBlur() { + outputTableData[index].isEdit = false + } + }) + ) + : // @ts-ignore + h('div', { align: 'center', style: { display: 'flex', 'flex-wrap': 'no-wrap', 'align-items': 'center' } }, [ + h(NEllipsis, {}, [row.name as any]), + h( + NButton, + { + quaternary: true, + circle: true, + size: 'small', + onClick() { + outputTableData[index].isEdit = true + } + }, + { icon: h(NIcon, null, () => h(EditOutlined)) } + ) + ]) + } + } as TableColumn + outputColumns.splice( + 1, + 1, + { + title: t('project.synchronization_definition.original_field'), + key: 'original_field', + ...COLUMN_WIDTH_CONFIG['name'], + render: (row: any) => { + return h('span', { class: row.isError && 'row-error' }, row.original_field) + } + }, + nameColumn + ) + outputColumns.push({ + title: t('project.synchronization_definition.operation'), + key: 'operation', + ...COLUMN_WIDTH_CONFIG['operation'](2), + render(row: any, index: number) { + return h(NSpace, {}, [ + h( + NDropdown, + { + options: [ + { + label: t('project.synchronization_definition.move_to_top'), + disabled: index === 0, + key: 'move_to_top' + }, + { + label: t( + 'project.synchronization_definition.move_to_bottom' + ), + disabled: index === outputTableData.length - 1, + key: 'move_to_bottom' + }, + { + label: t('project.synchronization_definition.move_up'), + disabled: index === 0, + key: 'move_up' + }, + { + label: t('project.synchronization_definition.move_down'), + disabled: index === outputTableData.length - 1, + key: 'move_down' + } + ], + onSelect(key) { + if (key === 'move_to_top') { + const records = outputTableData.splice(index, 1) + outputTableData.unshift(records[0]) + } else if (key === 'move_to_bottom') { + const records = outputTableData.splice(index, 1) + outputTableData.push(records[0]) + } else if (key === 'move_up') { + changeIndex(outputTableData, index, index - 1) + } else if (key === 'move_down') { + changeIndex(outputTableData, index, index + 1) + } + } + }, + h( + NButton, + { circle: true, size: 'small', type: 'info' }, + h(NIcon, { style: 'transform: rotate(90deg)' }, () => + h(SwapOutlined) + ) + ) + ), + h( + NPopconfirm, + { + onPositiveClick() { + outputTableData.splice(index, 1) + } + }, + { + trigger: () => + h( + NButton, + { circle: true, size: 'small', type: 'error' }, + h(NIcon, {}, () => h(DeleteOutlined)) + ), + default: () => + t('project.synchronization_definition.delete_confirm') + } + ) + ]) + } + }) + return { + inputColumns: !columnSelectable + ? basicColumns + : ([selection, ...basicColumns.slice(1)] as TableColumns), + outputColumns: outputColumns, + inputTableWidth: calculateTableWidth(basicColumns), + outputTableWidth: calculateTableWidth(outputColumns) + } + } + + if (nodeType === 'transform' && transformType === 'MultiFieldSplit') { + const outputColumns = cloneDeep(basicColumns) + const nameColumn = { + title: t('project.synchronization_definition.field_name'), + key: 'name', + ...COLUMN_WIDTH_CONFIG['name'], + render: (row: any) => { + // Mark the same field name value in red. + const onlyNameColumnTableData = outputTableData.map((o: any) => o.name) + return h('span', { class: onlyNameColumnTableData.indexOf(row.name) !== onlyNameColumnTableData.lastIndexOf(row.name) && 'row-error' }, row.name) + } + } as TableColumn + outputColumns.splice( + 1, + 1, + { + title: t('project.synchronization_definition.original_field'), + key: 'original_field', + ...COLUMN_WIDTH_CONFIG['name'], + render: (row: any) => { + return h('span', { class: row.isError && 'row-error' }, row.original_field) + } + }, + nameColumn + ) + + outputColumns.push({ + title: t('project.synchronization_definition.operation'), + key: 'operation', + ...COLUMN_WIDTH_CONFIG['operation'](1), + width: 60, + render(row: any, index: number) { + return row.isSplit + ? h( + NPopconfirm, + { + onPositiveClick() { + //outputTableData.splice(index, 1) + + // When a piece of information is deleted, the entries that + // are divided at the same time as the piece of information + // will be deleted together. + remove(outputTableData, (i => (i.original_field === row.original_field) && i.isSplit)) + + // After all the split fields are deleted, the Unsplit button is grayed out. + const sourceGroup = outputTableData.filter((o: any) => !o.isSplit).map((o: any) => o.name) + const splitGroup = outputTableData.filter((o: any) => o.isSplit).map((o: any) => o.original_field) + + sourceGroup.forEach((s: any) => { + if (splitGroup.indexOf(s) < 0) { + outputTableData[sourceGroup.indexOf(s)].splitDisabled = false + } + }) + } + }, + { + trigger: () => + h( + NButton, + { circle: true, size: 'small', type: 'error' }, + h(NIcon, {}, () => h(DeleteOutlined)) + ), + default: () => + t('project.synchronization_definition.delete_confirm') + } + ) + : h( + NTooltip, + {}, + { + trigger: () => + h( + NButton, + { + circle: true, + size: 'small', + type: 'info', + disabled: row.splitDisabled, + onClick() { + handleSplit(row, outputTableData) + } + }, + h(NIcon, {}, () => h(ColumnHeightOutlined)) + ), + default: () => t('project.synchronization_definition.split') + } + ) + } + }) + return { + inputColumns: !columnSelectable + ? basicColumns + : ([selection, ...basicColumns.slice(1)] as TableColumns), + outputColumns: outputColumns, + inputTableWidth: calculateTableWidth(basicColumns), + outputTableWidth: calculateTableWidth(outputColumns) + } + } + + if (nodeType === 'transform' && transformType === 'Copy') { + const outputColumns = cloneDeep(basicColumns) + + const nameColumn = { + title: t('project.synchronization_definition.field_name'), + key: 'name', + ...COLUMN_WIDTH_CONFIG['name'], + render: (row: any, index) => { + // Mark the same field name value in red. + const onlyNameColumnTableData = outputTableData.map((o: any) => o.name) + return row.copyTimes !== -1 ? + h('span', { class: onlyNameColumnTableData.indexOf(row.name) !== onlyNameColumnTableData.lastIndexOf(row.name) && 'row-error' }, row.name) + : row.isEdit ? + h(NFormItem, + { + showLabel: false, + validationStatus: !row.name ? 'error' : 'success', + feedback: !row.name + ? t('project.synchronization_definition.name_tips') + : '' + }, + () => + h(NInput, { + value: outputTableData[index].name, + onUpdateValue(v) { + outputTableData[index].name = v + }, + onBlur() { + outputTableData[index].isEdit = false + } + }) + ) + : // @ts-ignore + h('div', { align: 'center', style: { display: 'flex', 'flex-wrap': 'no-wrap', 'align-items': 'center' } }, [ + h(NEllipsis, {}, [row.name as any]), + h( + NButton, + { + quaternary: true, + circle: true, + size: 'small', + onClick() { + outputTableData[index].isEdit = true + } + }, + { icon: h(NIcon, null, () => h(EditOutlined)) } + ) + ]) + } + } as TableColumn + + outputColumns.splice( + 1, + 1, + { + title: t('project.synchronization_definition.original_field'), + key: 'original_field', + ...COLUMN_WIDTH_CONFIG['name'], + render: (row: any) => { + return row.original_field + } + }, + nameColumn + ) + + outputColumns.push({ + title: t('project.synchronization_definition.operation'), + key: 'operation', + ...COLUMN_WIDTH_CONFIG['operation'](1), + width: 60, + render(row: any, index: number) { + return row.copyTimes === -1 + ? h( + NPopconfirm, + { + onPositiveClick() { + outputTableData.splice(index, 1) + } + }, + { + trigger: () => + h( + NButton, + { circle: true, size: 'small', type: 'error' }, + h(NIcon, {}, () => h(DeleteOutlined)) + ), + default: () => + t('project.synchronization_definition.delete_confirm') + } + ) + : h( + NTooltip, + {}, + { + trigger: () => + h( + NButton, + { + circle: true, + size: 'small', + type: 'info', + onClick() { + const result = outputTableData.filter( + (o: any) => (o.original_field === row.original_field) && (o.original_field !== o.name) + ) + + const maxCopyTimes: any = max(result.map((r: any) => Number(r.name.split(r.original_field)[1]))) + + outputTableData[index].copyTimes = (maxCopyTimes ?? 0) + 1 + outputTableData.push({ + ...row, + name: row.name + row.copyTimes, + copyTimes: -1 + }) + } + }, + h(NIcon, {}, () => h(CopyOutlined)) + ), + default: () => + t('project.synchronization_definition.copy_field') + } + ) + } + }) + return { + inputColumns: !columnSelectable + ? basicColumns + : ([selection, ...basicColumns.slice(1)] as TableColumns), + outputColumns: outputColumns, + inputTableWidth: calculateTableWidth(basicColumns), + outputTableWidth: calculateTableWidth(outputColumns) + } + } + + const sourceOutputCheckType = () => { + const resultColumns = [...basicColumns] + const type = { + title: t('project.synchronization_definition.field_type'), + key: 'type', + ...COLUMN_WIDTH_CONFIG['type'], + ellipsis: { + tooltip: true + }, + render: (row: any) => row.outputDataType ? row.outputDataType : row.type + } + resultColumns.splice(2, 1, type) + return resultColumns + } + + return { + inputColumns: !columnSelectable + ? basicColumns + : ([selection, ...basicColumns.slice(1)] as TableColumns), + outputColumns: nodeType !== 'source' ? basicColumns : sourceOutputCheckType(), + inputTableWidth: calculateTableWidth(basicColumns), + outputTableWidth: calculateTableWidth(basicColumns) + } + } + return { createColumns } +} + +function changeIndex(list: any[], targetIndex: number, sourceIndex: number) { + const record = list[sourceIndex] + list[sourceIndex] = list[targetIndex] + list[targetIndex] = record +} diff --git a/seatunnel-ui/src/views/task/synchronization-definition/dag/use-model.ts b/seatunnel-ui/src/views/task/synchronization-definition/dag/use-model.ts new file mode 100644 index 000000000..3d89eaa69 --- /dev/null +++ b/seatunnel-ui/src/views/task/synchronization-definition/dag/use-model.ts @@ -0,0 +1,339 @@ +/* + * 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, watch } from 'vue' +import { useI18n } from 'vue-i18n' +import { + queryTaskDetail, + modelInfo, + sqlModelInfo +} from '@/service/sync-task-definition' +import { DefaultTableWidth } from '@/common/column-width-config' +import { useModelColumns } from './use-model-columns' +import { useRoute } from 'vue-router' +import type { TableColumns, ModelRecord } from './types' + +export function useNodeModel( + type: string, + transformType: string, + predecessorsNodeId: string, + schemaError: any, + currentNodeId: string, + refForm: any +) { + const { createColumns } = useModelColumns() + const route = useRoute() + const state = reactive({ + inputColumns: [] as TableColumns, + inputTableData: [] as ModelRecord[], + inputLoading: false, + outputColumns: [] as TableColumns, + outputTableData: [] as ModelRecord[], + allTableData: [], + schemaError: {} as any, + optionsOutputTableData: [], + transformOptions: {} as any, + secondTransformOptions: {} as any, + outputLoading: false, + currentTable: '', + selectedKeys: [] as string[], + tables: [] as string[], + columnSelectable: false, + database: '', + datasourceInstanceId: '', + datasourceName: '', + inputTableWidth: DefaultTableWidth, + outputTableWidth: DefaultTableWidth, + format: '' + }) + + let tempOutputTables = [] as ModelRecord[] + + const getModelData = () => { + // The sink or transform model does not have an output table structure, and + // the input table structure will be rendered according to the upstream node + // id to obtain the output table structure. + if (type === 'sink' || type === 'transform') { + + // if sql-transform, request when sql is not empty + if(transformType === 'Sql' && !refForm.value.getValues()?.query) return + // Request the data of the previous node. + predecessorsNodeId && queryTaskDetail( + route.params.jobDefinitionCode as string, + predecessorsNodeId + ).then((res: any) => { + state.tables = res.outputSchema.map((o: any) => o.tableName) + state.currentTable = res.outputSchema[0].tableName + state.allTableData = [{ database: res.outputSchema[0].database, tableInfos: res.outputSchema }] as any + if (res.transformOptions) { + state.transformOptions = res.transformOptions + } + onSwitchTable(state.currentTable) + }) + + } else { + modelInfo(state.datasourceInstanceId, [{ + database: state.database, + tables: state.tables + }]).then((res: any) => { + state.allTableData = res + onSwitchTable(state.currentTable) + }) + } + } + + const getColumns = () => { + const { inputColumns, outputColumns, inputTableWidth, outputTableWidth } = + createColumns({ + nodeType: type, + columnSelectable: state.columnSelectable, + transformType, + outputTableData: state.outputTableData + }) as { + inputColumns: TableColumns + outputColumns: TableColumns + inputTableWidth: number + outputTableWidth: number + outputTableData: ModelRecord[] + } + state.inputColumns = inputColumns + state.outputColumns = outputColumns + state.inputTableWidth = inputTableWidth + state.outputTableWidth = outputTableWidth + } + + const getSqlTransformOutputData = () => { + let sqlQuery = refForm.value.getValues()?.query as string + return sqlModelInfo(route.params.jobDefinitionCode as string, predecessorsNodeId, { + "sourceFieldName": null, + "query": sqlQuery + }).then((res: any) => res.fields) + } + + // Model indicates list click events. + const onSwitchTable = (table: string) => { + state.currentTable = table + if (state.format === 'COMPATIBLE_DEBEZIUM_JSON') return + + (state.allTableData[0] as any).tableInfos.forEach(async (t: any) => { + if (t.tableName === state.currentTable) { + tempOutputTables = t.fields + state.inputTableData = t.fields.filter((f: any) => !f.isSplit) + if (state.columnSelectable) { + if (state.optionsOutputTableData && (state.optionsOutputTableData[0] as any).tableName === state.currentTable) { + // The default assignment of the source node output table structure. + const result = state.optionsOutputTableData ? (state.optionsOutputTableData[0] as any).fields : tempOutputTables.filter((row: ModelRecord) => + state.selectedKeys.includes(row.name)) + state.outputTableData = result.filter((r: any) => !r.unSupport) + } else { + state.outputTableData = tempOutputTables.filter((t: any) => !t.unSupport) + } + // Assign default values to the optional input table structure of the source node. + state.selectedKeys = state.outputTableData.filter((o: any) => !o.unSupport).map((o: any) => o.name) + } else { + if (transformType === 'FieldMapper') { + // When the transform is empty, the data of the input model is directly copied to the output model. + if (!state.secondTransformOptions && !state.transformOptions) { + state.outputTableData = state.inputTableData.map((i: any) => { + i.original_field = i.name + return i + }) + return false + } + + const transformOptions = state.transformOptions.changeOrders ? state.transformOptions : state.secondTransformOptions + state.outputTableData = state.optionsOutputTableData ? + (state.optionsOutputTableData[0] as any).fields : + t.fields.map((f: any, i: number) => { + return { + ...f, + original_field: (transformOptions && Object.keys(transformOptions).length > 0) ? + transformOptions.changeOrders[i].sourceFieldName : + f.name + } + }) + state.outputTableData = state.outputTableData.map((o: any, i: number) => { + if (!o.original_field) { + o.original_field = transformOptions.changeOrders[i].sourceFieldName + } + return o + }) + state.outputTableData.forEach((o: any) => { + o.isError = o.original_field === state.schemaError.fieldName + }) + } else if (transformType === 'MultiFieldSplit') { + state.outputTableData = state.optionsOutputTableData ? (state.optionsOutputTableData[0] as any).fields.map((f: any) => { + if ( + state.transformOptions.splits || + ( + state.secondTransformOptions && + state.secondTransformOptions.splits + ) + ) { + // When the data is echoed, it is judged whether the original + // field has a split field, and if so, the split button of the + // original field is disabled. + const transformOptions = state.transformOptions.splits ? state.transformOptions : state.secondTransformOptions + const needSplitDisabled = transformOptions.splits.map((t: any) => t.sourceFieldName) + f.splitDisabled = needSplitDisabled.includes(f.name) + + // Add the split field to the original field value. + transformOptions.splits.forEach((t: any) => { + if (t.outputFields.includes(f.name)) { + f.original_field = t.sourceFieldName + f.separator = t.separator + f.isSplit = true + } + }) + + if (!f.original_field) { + f.original_field = f.name + } + } + + return f + }) : t.fields.map((f: any) => { + return { + ...f, + original_field: f.name + } + }) + } else if (transformType === 'Copy') { + state.outputTableData = state.optionsOutputTableData ? + (state.optionsOutputTableData[0] as any).fields.map((f: any) => { + if ( + state.transformOptions.copyList || + ( + state.secondTransformOptions && + state.secondTransformOptions.copyList + ) + ) { + const transformOptions = state.transformOptions.copyList ? state.transformOptions : state.secondTransformOptions + // Echo and judge the delete button of the copied data. and add the copied field to the original field value. + transformOptions.copyList.forEach((t: any) => { + const inputTableData = state.inputTableData.map((i: any) => i.name) + + if (!inputTableData.includes(f.name)) { + f.copyTimes = -1 + if (t.targetFieldName === f.name) { + f.original_field = t.sourceFieldName + } else { + // Request the data of the current node. + currentNodeId && queryTaskDetail( + route.params.jobDefinitionCode as string, + currentNodeId + ).then((res: any) => { + res.transformOptions && res.transformOptions.copyList.forEach((t: any) => { + if (t.targetFieldName === f.name) { + f.original_field = t.sourceFieldName + } + }) + }) + } + } + }) + + if (f.copyTimes !== -1) { + f.original_field = f.name + } + } + + return f + }) : + t.fields.map((f: any) => { + return { + ...f, + original_field: f.name + } + }) + } else if(transformType === 'Sql'){ + let table = await getSqlTransformOutputData() + state.outputTableData = table + + } else { + state.outputTableData = t.fields + } + } + } + }) + getColumns() + } + + const onUpdatedCheckedRowKeys = (keys: any[]) => { + state.selectedKeys = keys + state.outputTableData = tempOutputTables.filter((row: ModelRecord) => + keys.includes(row.name) + ) + } + + const onInit = (info: any) => { + state.currentTable = info.tables.length ? info.tables[0] : '' + state.tables = type === 'sink' ? [] : info.tables || [] + state.columnSelectable = info.columnSelectable || false + state.database = info.database || '' + state.datasourceInstanceId = info.datasourceInstanceId || '' + state.format = info.format + state.transformOptions = info.transformOptions + // It is used to judge the output model of multiple continuously operable transform nodes. + state.secondTransformOptions = info.transformOptions + state.schemaError = schemaError + + if ( + (info.sceneMode && info.sceneMode === 'SINGLE_TABLE') || + transformType === 'FieldMapper' || + transformType === 'MultiFieldSplit' || + transformType === 'Copy' + ) { + state.optionsOutputTableData = info.outputSchema + } + + // check debezium + if (state.format === 'COMPATIBLE_DEBEZIUM_JSON') { + state.inputTableData = [ + { name: 'topic', type: 'string' }, + { name: 'key', type: 'string' }, + { name: 'value', type: 'string' } + ] as any + state.outputTableData = [ + { name: 'topic', type: 'string' }, + { name: 'key', type: 'string' }, + { name: 'value', type: 'string' } + ] as any + } else { + (type === 'transform' || state.datasourceInstanceId) && + (type === 'transform' || state.database) && + ((type === 'sink' || type === 'transform') || state.tables.length) && + getModelData() + } + + getColumns() + } + + watch( + () => useI18n().locale, + () => { + getColumns() + } + ) + + return { + state, + onSwitchTable, + onUpdatedCheckedRowKeys, + onInit + } +} diff --git a/seatunnel-ui/src/views/task/synchronization-definition/dag/use-node-setting.ts b/seatunnel-ui/src/views/task/synchronization-definition/dag/use-node-setting.ts new file mode 100644 index 000000000..62f1b7ddf --- /dev/null +++ b/seatunnel-ui/src/views/task/synchronization-definition/dag/use-node-setting.ts @@ -0,0 +1,341 @@ +/* + * 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, ref, SetupContext } from 'vue' +import { + getInputTableSchema, + saveTaskDefinitionItem +} from '@/service/sync-task-definition' +import _, { omit } from 'lodash' +import { useRoute } from 'vue-router' +import { useI18n } from 'vue-i18n' +import type { ModelRecord } from './types' + +export function useNodeSettingModal( + props: any, + ctx: SetupContext<('cancelModal' | 'confirmModal')[]> +) { + const configurationFormRef = ref() + const { t } = useI18n() + const modelRef = ref() + const route = useRoute() + const state = reactive({ + nodeSettingModelFormRef: ref(), + saving: false, + loading: false, + width: '60%', + tab: 'configuration' + }) + + const formatParams = (values: any) => { + const params = { + pluginId: props.nodeInfo.pluginId, + name: values.name, + type: props.nodeInfo.type.toUpperCase(), + connectorType: + props.nodeInfo.type === 'transform' + ? props.nodeInfo.connectorType + : null + } as { [key: string]: any } + const config = omit(values, [ + 'name', + 'datasourceInstanceId', + 'sceneMode', + 'kind', + 'kinds', + 'database', + 'tableName', + 'datasourceInstanceName', + 'pluginName', + 'datasourceName', + 'columnSelectable', + 'excludeKind', + 'includeKind' + ]) as { [key: string]: string } + + if (values.kinds.length) { + config[values.kind ? 'exclude_kinds' : 'include_kinds'] = JSON.stringify( + values.kinds + ) + config[!values.kind ? 'exclude_kinds' : 'include_kinds'] = '' + } + + if (values.database || values.tableName) { + params.tableOption = { + databases: [] as string[], + tables: [] as string[] + } + params.tableOption.databases = + typeof values.database === 'string' + ? [values.database] + : Array.isArray(values.database) + ? values.database + : [] + params.tableOption.tables = + typeof values.tableName === 'string' + ? [values.tableName] + : Array.isArray(values.tableName) + ? values.tableName + : [] + } + + if (modelRef.value) { + params.selectTableFields = modelRef.value?.getSelectFields() + } + if (values.datasourceInstanceId) { + params.dataSourceId = values.datasourceInstanceId + } + if (values.sceneMode) { + params.sceneMode = values.sceneMode + } + params.config = JSON.stringify(config) + return params + } + + const formatOutputSchema = (data: any) => { + return data[0].tableInfos.map((t: any) => ({ + fields: t.fields, + tableName: t.tableName, + database: data[0].database + })) + } + + const onSave = async (): Promise => { + if (state.saving) return false + + // Determine whether the current node type is Transform or Sink and there is no previous node. + if ( + (props.nodeInfo.type === 'transform' || props.nodeInfo.type === 'sink') && + !props.nodeInfo.predecessorsNodeId + ) { + window.$message.warning(t('project.synchronization_definition.node_prev_check_tips')) + return false + } + + state.saving = true + try { + const validateResult = await configurationFormRef.value.validate() + if (!validateResult) { + handleTab('configuration') + state.saving = false + return false + } + + const values = configurationFormRef.value.getValues() + + let modelOutputTableData + if (props.nodeInfo.type === 'source') { + const resultSchema = modelRef.value.getOutputSchema() + // check debezium + if (values.format && values.format === 'COMPATIBLE_DEBEZIUM_JSON') { + modelOutputTableData = values.tableName.map((t: any) => { + return { + database: values.database, + tableName: t, + fields: [ + { name: 'topic', type: 'string' }, + { name: 'key', type: 'string' }, + { name: 'value', type: 'string' } + ] + } + }) + } else { + if (resultSchema.allTableData.length) { + if (values.sceneMode === 'SINGLE_TABLE') { + const result = resultSchema.allTableData + if (resultSchema.outputTableData.length) { + result[0].tableInfos[0].fields = resultSchema.outputTableData + } + modelOutputTableData = formatOutputSchema(result) + } else { + modelOutputTableData = formatOutputSchema(resultSchema.allTableData) + } + } else { + window.$message.warning(t('project.synchronization_definition.check_model'), { duration: 0, closable: true }) + state.saving = false + return false + } + } + } + + if (props.nodeInfo.type === 'transform' && props.nodeInfo.predecessorsNodeId) { + const resultSchema = modelRef.value.getOutputSchema() + if (resultSchema.allTableData.length) { + if ( + props.nodeInfo.connectorType === 'FieldMapper' || + props.nodeInfo.connectorType === 'MultiFieldSplit' || + props.nodeInfo.connectorType === 'Copy' + ) { + const result = resultSchema.allTableData + if (resultSchema.outputTableData.length) { + result[0].tableInfos[0].fields = resultSchema.outputTableData + } + modelOutputTableData = formatOutputSchema(result) + } else { + modelOutputTableData = formatOutputSchema(resultSchema.allTableData) + } + } else { + window.$message.warning(t('project.synchronization_definition.check_model'), { duration: 0, closable: true }) + state.saving = false + return false + } + } + + const transformOptions: any = {} + + if (props.nodeInfo.connectorType === 'FieldMapper') { + const resultSchema = modelRef.value.getOutputSchema() + transformOptions.changeOrders = resultSchema.outputTableData.map((o: any, i: number) => { + return { + sourceFieldName: o.original_field, + index: i + } + }) + transformOptions.renameFields = resultSchema.outputTableData.filter((o: any) => o.name !== o.original_field).map((o: any) => ({ + sourceFieldName: o.original_field, + targetName: o.name + })) + const outputTableDataNames = resultSchema.outputTableData.map((o: any) => ({sourceFieldName: o.original_field})) + const inputTableDataNames = resultSchema.inputTableData.map((o: any) => ({sourceFieldName: o.name})) + transformOptions.deleteFields = _.xorWith(outputTableDataNames, inputTableDataNames, _.isEqual) + } else if (props.nodeInfo.connectorType === 'MultiFieldSplit') { + const resultSchema = modelRef.value.getOutputSchema() + const hasSeparator = resultSchema.outputTableData.filter((o: any) => o.separator) + transformOptions.splits = _.uniqWith(hasSeparator.map((o: any) => { + return { + sourceFieldName: o.original_field, + separator: o.separator, + outputFields: _.groupBy(hasSeparator, 'separator')[o.separator].map((h: any) => h.name) + } + }), _.isEqual) + } else if (props.nodeInfo.connectorType === 'Copy') { + const resultSchema = modelRef.value.getOutputSchema() + const hasCopyColumn = resultSchema.outputTableData.filter((o: any) => o.copyTimes === -1) + transformOptions.copyList = hasCopyColumn.map((h: any) => ({ + sourceFieldName: h.original_field, + targetFieldName: h.name + })) + } else if(props.nodeInfo.connectorType === 'Sql') { + const resultSchema = modelRef.value.getOutputSchema() + const tableInfo = resultSchema.allTableData[0].tableInfos + transformOptions.sql = { + "sourceFieldName": resultSchema.outputTableData[0]?.name || null, + "query": values.query + } + modelOutputTableData = [{ + database: tableInfo[0].database, + tableName: tableInfo[0].tableName, + fields: resultSchema.outputTableData + }] + } + + await saveTaskDefinitionItem( + route.params.jobDefinitionCode as string, + { + ...formatParams(values), + outputSchema: modelOutputTableData, + transformOptions + } + ) + + ctx.emit( + 'confirmModal', + { + ...formatParams(values), + outputSchema: modelOutputTableData, + transformOptions + } + ) + + state.saving = false + state.tab = 'configuration' + state.width = '60%' + return true + } catch (err) { + state.saving = false + return false + } + } + + const initModelData = (node: any) => { + const obj: any = { + tables: node.tableName + ? typeof node.tableName === 'string' + ? [node.tableName] + : node.tableName + : [], + database: node.database + ? typeof node.database === 'string' + ? node.database + : node.database[0] + : [], + datasourceName: node.datasourceName, + datasourceInstanceId: node.datasourceInstanceId, + columnSelectable: node.columnSelectable, + outputSchema: props.nodeInfo.outputSchema, + sceneMode: node.sceneMode ? node.sceneMode : '', + transformOptions: props.nodeInfo.transformOptions + } + + if (node.format && node.format === 'COMPATIBLE_DEBEZIUM_JSON') { + obj.format = node.format + } else { + obj.format = 'DEFAULT' + } + + modelRef.value.initData(obj) + } + + const handleChangeTable = async (node: any) => { + if (!modelRef.value) return + const currentTable = _.isArray(node.tableName) + ? node.tableName[0] + : node.tableName + + const currentDatabase = _.isArray(node.database) + ? node.database[0] + : node.database + + if (!currentTable) return + + const result = await getInputTableSchema( + node.datasourceInstanceId, + currentDatabase, + currentTable + ) + + const selectedKeys = result.map((row: ModelRecord) => row.name) + modelRef.value.setSelectFields(selectedKeys) + } + + const handleTab = (tab: 'configuration' | 'model') => { + state.width = tab === 'configuration' ? '60%' : '80%' + state.tab = tab + if (tab === 'model' && modelRef.value) { + initModelData(configurationFormRef.value.getValues()) + } + } + + return { + state, + configurationFormRef, + modelRef, + onSave, + handleTab, + handleChangeTable + } +} diff --git a/seatunnel-ui/src/views/task/synchronization-definition/index.tsx b/seatunnel-ui/src/views/task/synchronization-definition/index.tsx new file mode 100644 index 000000000..8aa35ab68 --- /dev/null +++ b/seatunnel-ui/src/views/task/synchronization-definition/index.tsx @@ -0,0 +1,199 @@ +/* + * 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, toRefs, watch } from 'vue' +import { + NSpace, + NCard, + NButton, + NInput, + NIcon, + NDataTable, + NPagination +} from 'naive-ui' +import { useI18n } from 'vue-i18n' +import { SearchOutlined, ReloadOutlined } from '@vicons/antd' +import { useTable } from './use-table' +import { TaskModal } from './task-modal' +import ProjectSelector from '@/views/projects/components/projectSelector' +// import { useProjectStore } from '@/store/project' +import { useRoute, useRouter } from 'vue-router' +import _ from 'lodash' + +const SynchronizationDefinition = defineComponent({ + name: 'SynchronizationDefinition', + setup() { + const { t } = useI18n() + const route = useRoute() + const router = useRouter() + const { variables, createColumns, getTableData } = useTable() + + const requestData = () => { + getTableData({ + pageSize: variables.pageSize, + pageNo: variables.page, + searchName: variables.searchName + }) + } + + const onUpdatePageSize = () => { + variables.page = 1 + requestData() + } + + const onCancelModal = () => { + variables.showModalRef = false + } + + const onConfirmModal = () => { + variables.showModalRef = false + requestData() + } + + const handleModalChange = () => { + variables.showModalRef = true + } + + const onSearch = () => { + variables.page = 1 + + const query = {} as any + if (variables.searchName) { + query.searchName = variables.searchName + } + + router.replace({ + query: !_.isEmpty(query) + ? { + ...query, + ...route.query, + // searchProjectCode: variables.projectCodes + } + : { + ...route.query, + // searchProjectCode: variables.projectCodes + } + }) + requestData() + } + + const handleKeyup = (event: KeyboardEvent) => { + if (event.key === 'Enter') { + onSearch() + } + } + + const initSearch = () => { + const { searchName } = route.query + if (searchName) { + variables.searchName = searchName as string + } + } + + onMounted(() => { + initSearch() + createColumns(variables) + requestData() + }) + + watch(useI18n().locale, () => { + createColumns(variables) + }) + const getProjectCodeList = (codes: any) => { + if (!codes) { + // variables.projectCodes = useProjectStore().getGolbalProject + } else { + // variables.projectCodes = [codes] + } + } + + return { + t, + ...toRefs(variables), + onUpdatePageSize, + requestData, + onCancelModal, + onConfirmModal, + handleModalChange, + onSearch, + getProjectCodeList, + handleKeyup, + } + }, + render() { + return ( + + + + + {this.t( + 'project.synchronization_definition.create_synchronization_task' + )} + + + + + + + + + + + + + + + + + + + + + + + ) + } +}) + +export default SynchronizationDefinition diff --git a/seatunnel-ui/src/views/task/synchronization-definition/task-modal.tsx b/seatunnel-ui/src/views/task/synchronization-definition/task-modal.tsx new file mode 100644 index 000000000..a092344af --- /dev/null +++ b/seatunnel-ui/src/views/task/synchronization-definition/task-modal.tsx @@ -0,0 +1,220 @@ +/* + * 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, toRefs, ref } from 'vue' +import { useI18n } from 'vue-i18n' +import { NForm, NFormItem, NInput, NRadioGroup, NRadio, NSpace } from 'naive-ui' +import { useTaskModal } from './use-task-modal' +import Modal from '@/components/modal' +import { useProjectStore } from '@/store/project' +import ProjectSelector from '@/views/projects/components/projectSelector' + +const props = { + showModalRef: { + type: Boolean as PropType, + default: false + }, + row: { + type: Object as PropType, + default: {} + } +} + +const TaskModal = defineComponent({ + name: 'TaskModal', + props, + emits: ['cancelModal', 'confirmModal'], + setup(props, ctx) { + const { t } = useI18n() + const { variables, handleValidate } = useTaskModal(props, ctx) + // const projectStore = useProjectStore() + // const projectCode = ref([]) + // const globalFlag = projectStore.getGlobalFlag + // projectCode.value = globalFlag ? [] : projectStore.getCurrentProject[0] + // const showPre = ref(globalFlag ? true : false) + const synchronizationForm: any = ref(null) + + const cancelModal = () => { + variables.model.name = '' + variables.model.description = '' + ctx.emit('cancelModal', props.showModalRef) + } + + // const projectRule = { + // projectCode: { + // required: true, + // validator() { + // if (projectCode.value.length == 0) { + // return new Error(t('project.dag.project_empty')) + // } + // }, + // trigger: 'blur' + // } + // } + + const preCancle = () => { + ctx.emit('cancelModal') + // projectCode.value = [] + variables.model.name = '' + variables.model.description = '' + } + + const confirmModal = () => { + handleValidate() + // projectCode.value = [] + } + + const getNextStep = () => { + if (synchronizationForm.value) { + synchronizationForm.value.validate(async (valid: any) => { + if (!valid) { + // showPre.value = false + variables.model.name = '' + variables.model.description = '' + } + }) + } + } + + // const getProjectList = (projectListItem: any) => { + // if (projectListItem) { + // projectCode.value = [projectListItem] as any + // } else { + // projectCode.value = [] + // } + // } + return { + t, + ...toRefs(variables), + cancelModal, + confirmModal, + preCancle, + getNextStep, + synchronizationForm, + // projectCode, + // projectRule, + // showPre, + // getProjectList, + // globalFlag + } + }, + render() { + const { + t, + projectCode, + getNextStep, + showModalRef, + preCancle, + // projectRule, + // showPre, + // globalFlag, + // getProjectList, + } = this + return ( + + ) + } +}) + +export { TaskModal } diff --git a/seatunnel-ui/src/views/task/synchronization-definition/test/definitionNodesAndEdgesData.ts b/seatunnel-ui/src/views/task/synchronization-definition/test/definitionNodesAndEdgesData.ts new file mode 100644 index 000000000..5eba573c2 --- /dev/null +++ b/seatunnel-ui/src/views/task/synchronization-definition/test/definitionNodesAndEdgesData.ts @@ -0,0 +1,44 @@ +export default { + edges: [ + { + inputPluginId: '1', + targetPluginId: '2' + } + ], + plugins: [ + { + config: '', + connectorType: '', + dataSourceId: '8600164027328', + name: 'test-1', + pluginId: '1', + sceneMode: 'SINGLE_TABLE', + selectTableFields: { + all: false, + tableFields: [] + }, + tableOptions: { + databases: [], + tables: [] + }, + type: 'source' + }, + { + config: '', + connectorType: '', + dataSourceId: '8600164027328', + name: 'test-2', + pluginId: '2', + sceneMode: 'SINGLE_TABLE', + selectTableFields: { + all: false, + tableFields: [] + }, + tableOptions: { + databases: [], + tables: [] + }, + type: 'sink' + } + ] +} diff --git a/seatunnel-ui/src/views/task/synchronization-definition/test/index.ts b/seatunnel-ui/src/views/task/synchronization-definition/test/index.ts new file mode 100644 index 000000000..70b5ed72a --- /dev/null +++ b/seatunnel-ui/src/views/task/synchronization-definition/test/index.ts @@ -0,0 +1 @@ +export { default as testNodesAndEdges } from './definitionNodesAndEdgesData' diff --git a/seatunnel-ui/src/views/task/synchronization-definition/use-table.ts b/seatunnel-ui/src/views/task/synchronization-definition/use-table.ts new file mode 100644 index 000000000..8b90f5519 --- /dev/null +++ b/seatunnel-ui/src/views/task/synchronization-definition/use-table.ts @@ -0,0 +1,167 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { useI18n } from 'vue-i18n' +import { h, reactive, ref } from 'vue' +import { useTableOperation } from '@/hooks' +import { EditOutlined, PlayCircleOutlined } from '@vicons/antd' +import { + querySyncTaskDefinitionPaging, + deleteSyncTaskDefinition +} from '@/service/sync-task-definition' +import { useRoute, useRouter } from 'vue-router' +import type { Router } from 'vue-router' +import type { JobType } from './dag/types' +// import { changeProject } from '../../utils/changeProject' +// import { useProjectStore } from '@/store/project' +import { COLUMN_WIDTH_CONFIG } from '@/common/column-width-config' +import { useTableLink } from '@/hooks' + +export function useTable() { + const { t } = useI18n() + const router: Router = useRouter() + const route = useRoute() + // const projectStore = useProjectStore() + const variables = reactive({ + columns: [], + tableData: [], + page: ref(1), + pageSize: ref(10), + searchName: ref(''), + totalPage: ref(1), + showModalRef: ref(false), + statusRef: ref(0), + row: {}, + loadingRef: ref(false), + // projectCodes: + // route.query.searchProjectCode || projectStore.getCurrentProject, + // globalProject: projectStore.getGlobalFlag + }) + // const globalFlag = useProjectStore().getGlobalFlag + + const JOB_TYPE = { + DATA_REPLICA: 'whole_library_sync', + DATA_INTEGRATION: 'data_integration' + } as { [key in JobType]: string } + + const createColumns = (variables: any) => { + variables.columns = [ + { + title: t( + 'project.synchronization_definition.synchronization_task_name' + ), + key: 'name' + }, + { + title: t('project.synchronization_definition.business_model'), + key: 'jobKey', + render: (row: { jobType: JobType }) => + t(`project.synchronization_definition.${JOB_TYPE[row.jobType]}`) + }, + { + title: t('project.synchronization_definition.task_describe'), + key: 'description' + }, + { + title: t('project.synchronization_definition.create_user'), + key: 'createUserName' + }, + { + title: t('project.synchronization_definition.create_time'), + key: 'createTime' + }, + { + title: t('project.synchronization_definition.update_user'), + key: 'updateUserName' + }, + { + title: t('project.synchronization_definition.update_time'), + key: 'updateTime' + }, + useTableOperation( + { + title: t('project.synchronization_definition.operation'), + key: 'operation', + buttons: [ + { + text: t('project.synchronization_definition.edit'), + onClick: (row: any) => { + router.push({ + path: `/task/synchronization-definition/${row.id}`, + }) + }, + icon: h(EditOutlined) + }, + + { + text: t('project.synchronization_definition.start'), + icon: h(PlayCircleOutlined) + }, + { + isDelete: true, + text: t('project.synchronization_definition.delete'), + onPositiveClick: (row: any) => void handleDelete(row), + popTips: t('security.token.delete_confirm') + } + ] + }, + + ) + ] + } + + const getTableData = (params: any) => { + if (variables.loadingRef) return + variables.loadingRef = true + + // params['projectCodes'] = variables.projectCodes + + querySyncTaskDefinitionPaging(params) + .then((res: any) => { + variables.tableData = res.data + variables.totalPage = res.totalPage + variables.loadingRef = false + }) + .catch(() => { + variables.loadingRef = false + }) + } + + const handleDelete = (row: any) => { + if (variables.tableData.length === 1 && variables.page > 1) { + --variables.page + } + + deleteSyncTaskDefinition({ + projectCode: row.projectCode, + id: row.id + }).then(() => { + getTableData({ + pageSize: variables.pageSize, + pageNo: variables.page, + searchName: variables.searchName + }) + }) + } + + return { + variables, + createColumns, + getTableData, + // globalFlag + } +} diff --git a/seatunnel-ui/src/views/task/synchronization-definition/use-task-modal.ts b/seatunnel-ui/src/views/task/synchronization-definition/use-task-modal.ts new file mode 100644 index 000000000..bb6110423 --- /dev/null +++ b/seatunnel-ui/src/views/task/synchronization-definition/use-task-modal.ts @@ -0,0 +1,103 @@ +/* + * 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 { useI18n } from 'vue-i18n' +import { reactive, ref, SetupContext } from 'vue' +import { useRoute, useRouter } from 'vue-router' +// import { useSynchronizationDefinitionStore } from '@/store/synchronization-definition' +import { createSyncTaskDefinition } from '@/service/sync-task-definition' +import type { FormItemRule } from 'naive-ui' +import type { Router } from 'vue-router' +import { useProjectStore } from '@/store/project' + +export function useTaskModal( + props: any, + ctx: SetupContext<('cancelModal' | 'confirmModal')[]> +) { + const { t } = useI18n() + const router: Router = useRouter() + const route = useRoute() + const projectStore = useProjectStore() + + const variables = reactive({ + projectCode: projectStore.getCurrentProject[0], + taskModalFormRef: ref(), + saving: false, + model: { + name: ref(''), + description: ref(''), + jobType: ref('DATA_REPLICA') + }, + rules: { + name: { + required: true, + trigger: ['input', 'blur'], + validator: (rule: FormItemRule, value: string) => { + if (!value) { + return Error( + t('project.synchronization_definition.task_name_validate') + ) + } + } + }, + jobType: { + required: true, + trigger: ['change'] + } + } + }) + + const handleValidate = async () => { + await variables.taskModalFormRef.validate() + + if (variables.saving) return + variables.saving = true + + try { + await submitSyncTaskDefinition() + variables.saving = false + } catch (err) { + variables.saving = false + } + } + + const submitSyncTaskDefinition = () => { + createSyncTaskDefinition({ + description: variables.model.description, + name: variables.model.name, + jobType: variables.model.jobType + }).then((res: any) => { + variables.model.description = '' + variables.model.name = '' + + ctx.emit('confirmModal', props.showModalRef) + + router.push({ + path: `/task/synchronization-definition/${res}`, + query: { + global: String(projectStore.getGlobalFlag), + project: (route.query.project as string) || 'all' + } + }) + }) + } + + return { + variables, + handleValidate + } +} diff --git a/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/dag-setting.ts b/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/dag-setting.ts new file mode 100644 index 000000000..8878c9480 --- /dev/null +++ b/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/dag-setting.ts @@ -0,0 +1,20 @@ + +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export const DagNodeName = 'dag-node' +export const DagEdgeName = 'dag-edge' diff --git a/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/index.module.scss b/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/index.module.scss new file mode 100644 index 000000000..1ebd2fa0e --- /dev/null +++ b/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/index.module.scss @@ -0,0 +1,36 @@ +/* + * 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. + */ + +.dag-node { + display: flex; + align-items: center; + height: 100%; + background-color: #fff; + border: 1px solid #c2c8d5; + border-left: 4px solid #5F95FF; + border-radius: 4px; + box-shadow: 0 2px 5px 1px rgba(0, 0, 0, 0.06); +} + +.dag-node-label { + display: inline-block; + flex-shrink: 0; + width: 100%; + text-align: center; + color: #666; + font-size: 12px; +} \ No newline at end of file diff --git a/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/node.tsx b/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/node.tsx new file mode 100644 index 000000000..49e5ecae3 --- /dev/null +++ b/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/node.tsx @@ -0,0 +1,18 @@ +import { defineComponent, inject } from 'vue' +import styles from './index.module.scss' + +const Node = defineComponent({ + name: 'Node', + setup() { + const getNode = inject('getNode') as any + const node = getNode() + const { name } = node.getData() + return () => ( +
+ {name} +
+ ) + } +}) + +export default Node diff --git a/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/use-dag-add-shape.ts b/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/use-dag-add-shape.ts new file mode 100644 index 000000000..ada1c6d67 --- /dev/null +++ b/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/use-dag-add-shape.ts @@ -0,0 +1,157 @@ +/* + * 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 { DagEdgeName, DagNodeName } from './dag-setting' + +const stateColor = { + failed: { + fill: '#ffced7', + stroke: '#ffa8b7' + }, + running: { + fill: '#ceebff', + stroke: '#b0deff' + }, + finished: { + fill: '#ceffee', + stroke: '#a8ffe0' + }, + canceled: { + fill: '#d5d5d5', + stroke: '#b6b6b6' + } +} + +export function useDagAddShape( + graph: any, + nodes: any, + edges: Array, + t: any +) { + for (const i in nodes) { + const group = graph.addNode({ + x: 40, + y: 40, + width: 360, + height: 160, + zIndex: 1, + attrs: { + body: + String(nodes[i].status.toLowerCase()) === 'failed' && + 'finished' && + 'canceled' + ? stateColor.running + : stateColor[ + nodes[i].status.toLowerCase() as + | 'failed' + | 'finished' + | 'canceled' + ] + } + }) + + group.addTools({ + name: 'button', + args: { + markup: [ + { + tagName: 'text', + textContent: `pipeline#${nodes[i].pipelineId}`, + attrs: { + fill: '#333333', + 'font-size': 14, + 'text-anchor': 'center', + stroke: 'black' + } + }, + { + tagName: 'text', + textContent: `${t('project.synchronization_instance.state')}: ${ + nodes[i].status + }`, + attrs: { + fill: '#868686', + 'font-size': 12, + 'text-anchor': 'start', + x: '7em' + } + }, + { + tagName: 'text', + textContent: `${t('project.synchronization_instance.read')} ${ + nodes[i].readRowCount + }${t('project.synchronization_instance.line')}/${t( + 'project.synchronization_instance.write' + )} ${nodes[i].writeRowCount}${t( + 'project.synchronization_instance.line' + )}`, + attrs: { + fill: '#868686', + 'font-size': 12, + 'text-anchor': 'start', + x: '20em' + } + } + ], + x: 0, + y: 0, + offset: { x: 0, y: -18 } + } + }) + + nodes[i].child.forEach((n: any) => { + group.addChild( + graph.addNode({ + id: n.id, + // x: 50, + // y: 50, + // width: 120, + // height: 40, + shape: DagNodeName, + // label: n.label, + zIndex: 10, + // attrs: { + // body: { + // stroke: 'none', + // fill: '#858585' + // }, + // label: { + // fill: '#fff', + // fontSize: 12 + // } + // }, + data: { + name: n.label + } + }) + ) + }) + } + + edges.forEach((e: any) => { + graph.addEdge({ + shape: DagEdgeName, + source: { + cell: e.source + }, + target: { + cell: e.target + }, + id: e.id + }) + }) +} diff --git a/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/use-dag-edge.ts b/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/use-dag-edge.ts new file mode 100644 index 000000000..15a3f4f41 --- /dev/null +++ b/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/use-dag-edge.ts @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export function useDagEdge() { + return { + attrs: { + line: { + stroke: '#C2C8D5', + strokeWidth: 1 + } + } + } +} diff --git a/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/use-dag-graph.ts b/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/use-dag-graph.ts new file mode 100644 index 000000000..ebf95d8a3 --- /dev/null +++ b/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/use-dag-graph.ts @@ -0,0 +1,48 @@ +/* + * 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 { Graph } from '@antv/x6' +import { DagEdgeName } from './dag-setting' + +export function useDagGraph( + graph: any, + dagContainer: HTMLElement, + minimapContainer: HTMLElement +) { + return new Graph({ + container: dagContainer, + scroller: true, + grid: { + size: 10, + visible: true + }, + connecting: { + // router: 'orth', + allowBlank: false, + allowLoop: false, + createEdge() { + return graph.value?.createEdge({ shape: DagEdgeName }) + } + }, + minimap: { + enabled: true, + width: 200, + height: 120, + container: minimapContainer + } + }) +} diff --git a/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/use-dag-layout.ts b/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/use-dag-layout.ts new file mode 100644 index 000000000..5dc800bfb --- /dev/null +++ b/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/use-dag-layout.ts @@ -0,0 +1,115 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { DagreLayout } from '@antv/layout' +import _ from 'lodash' +import { DagEdgeName, DagNodeName } from './dag-setting' + +const updateParentNodePosition = (nodes: any, node: any) => { + if (node.children && node.children.length) { + const children = node.children + let minX = Number.MAX_VALUE + let maxX = 0 + let minY = Number.MAX_VALUE + let maxY = 0 + nodes + .filter((node: any) => children.includes(node.id)) + .map((node: any) => { + minX = Math.min(minX, node.x) + maxX = Math.max(maxX, node.x) + minY = Math.min(minY, node.y) + maxY = Math.max(maxY, node.y) + }) + + node.x = minX - 20 + node.y = minY - 20 + node.size = { + width: maxX - minX + 200, + height: maxY - minY + 80 + } + } +} + +const useDagLayout = (graph: any) => { + const layoutConfig = { + nodesep: 50, + padding: 50, + ranksep: 30 + } + + if (!graph) { + return + } + + graph.cleanSelection() + + const layoutFunc = new DagreLayout({ + type: 'dagre', + rankdir: 'LR', + align: 'UL', + // Calculate the node spacing based on the edge label length + ranksepFunc: (d) => { + const edges = graph.getOutgoingEdges(d.id) + let max = 0 + if (edges && edges.length > 0) { + edges.forEach((edge: any) => { + const edgeView = graph.findViewByCell(edge) + const labelView = edgeView?.findAttr( + 'width', + _.get(edgeView, ['labelSelectors', '0', 'body'], null) + ) + const labelWidth = labelView ? +labelView : 0 + max = Math.max(max, labelWidth) + }) + } + return layoutConfig.ranksep + max + }, + nodesep: layoutConfig.nodesep, + controlPoints: true + }) + + const json = graph.toJSON() + const groups = json.cells + .filter((cell: any) => cell.shape === 'rect') + .map((item: any) => { + return { + ...item, + // sort by code aesc + _index: -(item.id as string) + } + }) + const nodes = json.cells + .filter((cell: any) => cell.shape === DagNodeName) + .map((item: any) => { + return { + ...item, + // sort by code aesc + _index: -(item.id as string) + } + }) + const edges = json.cells.filter((cell: any) => cell.shape === DagEdgeName) + + const newModel: any = layoutFunc?.layout({ + nodes: [...nodes, ...groups], + edges + } as any) + + newModel.nodes.map((node: any) => updateParentNodePosition(nodes, node)) + graph.fromJSON(newModel) +} + +export { useDagLayout } diff --git a/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/use-dag-node-change-position.ts b/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/use-dag-node-change-position.ts new file mode 100644 index 000000000..ee6d7aa1b --- /dev/null +++ b/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/use-dag-node-change-position.ts @@ -0,0 +1,93 @@ +/* + * 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 { Graph } from '@antv/x6' + +const ctrlPressed = false +const embedPadding = 20 + +export function useDagNodeChangePosition(graph: Graph) { + graph.on('node:change:position', ({ node, options }) => { + if (options.skipParentHandler || ctrlPressed) { + return + } + + const children = node.getChildren() + if (children && children.length) { + node.prop('originPosition', node.getPosition()) + } + + const parent = node.getParent() + if (parent && parent.isNode()) { + let originSize = parent.prop('originSize') + if (originSize == null) { + originSize = parent.getSize() + parent.prop('originSize', originSize) + } + + let originPosition = parent.prop('originPosition') + if (originPosition == null) { + originPosition = parent.getPosition() + parent.prop('originPosition', originPosition) + } + + let x = originPosition.x + let y = originPosition.y + let cornerX = originPosition.x + originSize.width + let cornerY = originPosition.y + originSize.height + let hasChange = false + + const children = parent.getChildren() + if (children) { + children.forEach((child) => { + const bbox = child.getBBox().inflate(embedPadding) + const corner = bbox.getCorner() + + if (bbox.x < x) { + x = bbox.x + hasChange = true + } + + if (bbox.y < y) { + y = bbox.y + hasChange = true + } + + if (corner.x > cornerX) { + cornerX = corner.x + hasChange = true + } + + if (corner.y > cornerY) { + cornerY = corner.y + hasChange = true + } + }) + } + + if (hasChange) { + parent.prop( + { + position: { x, y }, + size: { width: cornerX - x, height: cornerY - y } + }, + { skipParentHandler: true } + ) + } + } + }) +} diff --git a/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/use-dag-node.ts b/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/use-dag-node.ts new file mode 100644 index 000000000..9aeaff364 --- /dev/null +++ b/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/use-dag-node.ts @@ -0,0 +1,33 @@ +/* + * 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 '@antv/x6-vue-shape' +import Node from './node' +import { createVNode } from 'vue' + +export function useDagNode() { + return { + inherit: 'vue-shape', + width: 150, + height: 36, + component: { + render: () => { + return createVNode(Node) + } + } + } +} diff --git a/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/use-dag-resize.ts b/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/use-dag-resize.ts new file mode 100644 index 000000000..e48101010 --- /dev/null +++ b/seatunnel-ui/src/views/task/synchronization-instance/detail/dag/use-dag-resize.ts @@ -0,0 +1,33 @@ +/* + * 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 { debounce } from 'lodash' +import { useResizeObserver } from '@vueuse/core' +import type { Graph } from '@antv/x6' +import type { Ref } from 'vue' + +export function useDagResize(container: Ref, graph: Ref) { + const resize = debounce(() => { + if (container.value) { + const w = container.value.offsetWidth + const h = container.value.offsetHeight + graph.value?.resize(w, h) + } + }, 200) + + useResizeObserver(container, resize) +} \ No newline at end of file diff --git a/seatunnel-ui/src/views/task/synchronization-instance/detail/index.tsx b/seatunnel-ui/src/views/task/synchronization-instance/detail/index.tsx new file mode 100644 index 000000000..7174070c4 --- /dev/null +++ b/seatunnel-ui/src/views/task/synchronization-instance/detail/index.tsx @@ -0,0 +1,93 @@ +/* + * 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 } from 'vue' +import { RunningInstance } from './running-instance' +import { TaskDefinition } from './task-definition' +import { + NBreadcrumb, + NBreadcrumbItem, + NCard, + NSpace, + NTabPane, + NTabs +} from 'naive-ui' +import { useI18n } from 'vue-i18n' +import { useRouter, useRoute } from 'vue-router' +import type { Router } from 'vue-router' + +const SynchronizationInstanceDetail = defineComponent({ + name: 'SynchronizationInstanceDetail', + setup() { + const { t } = useI18n() + const router: Router = useRouter() + const route = useRoute() + + return { + t, + router, + route + } + }, + render() { + return ( + + + + + + this.router.push({ + name: 'synchronization-instance', + params: { projectCode: this.route.params.projectCode }, + query: { + project: this.route.query.project, + global: this.route.query.global + } + }) + } + > + {this.t('menu.synchronization_instance')} + + + {this.route.query.taskName} + + + + + + + + + + + + ) + } +}) + +export default SynchronizationInstanceDetail diff --git a/seatunnel-ui/src/views/task/synchronization-instance/detail/running-instance.tsx b/seatunnel-ui/src/views/task/synchronization-instance/detail/running-instance.tsx new file mode 100644 index 000000000..7ce6c4906 --- /dev/null +++ b/seatunnel-ui/src/views/task/synchronization-instance/detail/running-instance.tsx @@ -0,0 +1,64 @@ +/* + * 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, toRefs, watch } from 'vue' +import { + NSpace, + NCard, + NDataTable +} from 'naive-ui' +import { useI18n } from 'vue-i18n' +import { useRunningInstance } from './use-running-instance' + +const RunningInstance = defineComponent({ + name: 'RunningInstance', + setup() { + const { t } = useI18n() + const { variables, getTableData, createColumns } = useRunningInstance() + + onMounted(() => { + createColumns(variables) + getTableData() + }) + + watch(useI18n().locale, () => { + createColumns(variables) + }) + + return { + t, + ...toRefs(variables) + } + }, + render() { + return ( + + + + + + + + ) + } +}) + +export { RunningInstance } \ No newline at end of file diff --git a/seatunnel-ui/src/views/task/synchronization-instance/detail/task-definition.module.scss b/seatunnel-ui/src/views/task/synchronization-instance/detail/task-definition.module.scss new file mode 100644 index 000000000..48de97d96 --- /dev/null +++ b/seatunnel-ui/src/views/task/synchronization-instance/detail/task-definition.module.scss @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.right-panel { + max-height: calc(100vh - 235px); + overflow: auto; + + h4 { + margin: 0; + } +} + +.left-panel { + min-height: calc(100vh - 235px); + + .workflow-dag { + display: flex; + height: calc(100vh - 275px); + + .container { + height: 100%; + width: 100%; + } + + .dag-container { + height: 100%; + width: 100%; + } + + .minimap { + position: absolute; + right: 50px; + bottom: 45px; + border: dashed 1px #e4e4e4; + z-index: 9000; + } + } +} diff --git a/seatunnel-ui/src/views/task/synchronization-instance/detail/task-definition.tsx b/seatunnel-ui/src/views/task/synchronization-instance/detail/task-definition.tsx new file mode 100644 index 000000000..34552b5b6 --- /dev/null +++ b/seatunnel-ui/src/views/task/synchronization-instance/detail/task-definition.tsx @@ -0,0 +1,141 @@ +/* + * 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, ref } from 'vue' +import { NGi, NGrid, NCard, NSpace } from 'naive-ui' +import { useI18n } from 'vue-i18n' +import { Graph } from '@antv/x6' +import { useDagGraph } from './dag/use-dag-graph' +import { useDagResize } from './dag/use-dag-resize' +import { useDagNodeChangePosition } from './dag/use-dag-node-change-position' +import { DagEdgeName, DagNodeName } from './dag/dag-setting' +import { useDagEdge } from './dag/use-dag-edge' +import { useTaskDefinition } from './use-task-definition' +import styles from './task-definition.module.scss' +import { useDagNode } from './dag/use-dag-node' + +interface IJobConfig { + name: string + engine: string + description: string + env: Array<{ [key: string]: string }> +} + +const TaskDefinition = defineComponent({ + name: 'TaskDefinition', + setup() { + const { t } = useI18n() + const container = ref() + const dagContainer = ref() + const minimapContainer = ref() + const jobConfig = ref({} as IJobConfig) + const graph = ref() + const { getJobConfig, getJobDag } = useTaskDefinition(t) + + const initGraph = () => { + graph.value = useDagGraph( + graph, + dagContainer.value, + minimapContainer.value + ) + } + + useDagResize(container, graph as Ref) + + const registerNode = () => { + Graph.unregisterNode(DagNodeName) + Graph.registerNode(DagNodeName, useDagNode()) + } + + const registerEdge = () => { + Graph.unregisterEdge(DagEdgeName) + Graph.registerEdge(DagEdgeName, useDagEdge()) + } + + const formatData = () => { + const obj = jobConfig.value.env + const arr = [] + for (const i in obj) { + arr.push({ label: String(i), value: obj[i] }) + } + return arr + } + + onMounted(async () => { + initGraph() + registerNode() + registerEdge() + useDagNodeChangePosition(graph.value as Graph) + jobConfig.value = (await getJobConfig()) || {} + + await getJobDag(graph.value as Graph) + }) + + return { + t, + container, + 'dag-container': dagContainer, + 'minimap-container': minimapContainer, + formatData, + jobConfig + } + }, + render() { + return ( + + + +
+
+
+
+
+
+ + + + + +
+

{this.t('project.synchronization_instance.task_name')}

+

{this.jobConfig.name}

+
+
+

+ {this.t('project.synchronization_instance.description')} +

+

{this.jobConfig.description}

+
+
+

{this.t('project.synchronization_instance.engine')}

+

{this.jobConfig.engine}

+
+ {this.formatData().map((i: any) => ( +
+

{i.label}

+

{i.value}

+
+ ))} +
+
+
+ + ) + } +}) + +export { TaskDefinition } diff --git a/seatunnel-ui/src/views/task/synchronization-instance/detail/use-running-instance.ts b/seatunnel-ui/src/views/task/synchronization-instance/detail/use-running-instance.ts new file mode 100644 index 000000000..05b3c994e --- /dev/null +++ b/seatunnel-ui/src/views/task/synchronization-instance/detail/use-running-instance.ts @@ -0,0 +1,95 @@ +/* + * 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, ref } from 'vue' +import { useI18n } from 'vue-i18n' +import { queryRunningInstancePaging } from '@/service/modules/sync-task-instance' +import { useRoute } from 'vue-router' + +export function useRunningInstance() { + const { t } = useI18n() + const route = useRoute() + + const variables = reactive({ + columns: [], + tableData: [], + loadingRef: ref(false) + }) + + const createColumns = (variables: any) => { + variables.columns = [ + { + title: t('project.synchronization_instance.pipeline_id'), + key: 'pipelineId', + }, + { + title: t('project.synchronization_instance.source'), + key: 'sourceTableNames' + }, + { + title: t('project.synchronization_instance.read_rate'), + key: 'readQps' + }, + { + title: t('project.synchronization_instance.amount_of_data_read'), + key: 'readRowCount' + }, + { + title: t('project.synchronization_instance.delay_of_data'), + key: 'recordDelay', + render: (row: any) => row.recordDelay / 1000 + }, + { + title: t('project.synchronization_instance.sink'), + key: 'sinkTableNames' + }, + { + title: t('project.synchronization_instance.processing_rate'), + key: 'writeQps' + }, + { + title: t('project.synchronization_instance.amount_of_data_written'), + key: 'writeRowCount' + }, + { + title: t('project.synchronization_instance.state'), + key: 'status' + } + ] + } + + const getTableData = () => { + if (variables.loadingRef) return + variables.loadingRef = true + + queryRunningInstancePaging({ + projectCode: route.params.projectCode, + key: route.query.key + }).then((res: any) => { + variables.tableData = res + variables.loadingRef = false + }).catch(() => { + variables.loadingRef = false + }) + } + + return { + variables, + createColumns, + getTableData + } +} \ No newline at end of file diff --git a/seatunnel-ui/src/views/task/synchronization-instance/detail/use-task-definition.ts b/seatunnel-ui/src/views/task/synchronization-instance/detail/use-task-definition.ts new file mode 100644 index 000000000..542334a6a --- /dev/null +++ b/seatunnel-ui/src/views/task/synchronization-instance/detail/use-task-definition.ts @@ -0,0 +1,99 @@ +/* + * 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 { + querySyncTaskInstanceDetail, + querySyncTaskInstanceDag +} from '@/service/modules/sync-task-instance' +import { useRoute } from 'vue-router' +import { uuid } from '@/common/common' +import { useDagAddShape } from '@/views/projects/task/synchronization-instance/detail/dag/use-dag-add-shape' +import { useDagLayout } from '@/views/projects/task/synchronization-instance/detail/dag/use-dag-layout' +import { Graph } from '@antv/x6' +import { getDefinitionConfig } from '@/service/sync-task-definition' + +export function useTaskDefinition(t: any) { + const route = useRoute() + + const getJobConfig = async () => { + return await getDefinitionConfig(route.query.seaTunnelJobId as string) + } + + const getJobDag = async (graph: Graph) => { + const dagData = await querySyncTaskInstanceDag({ + projectCode: route.params.projectCode, + key: route.query.key + }) + + const pipelineData = await querySyncTaskInstanceDetail({ + projectCode: route.params.projectCode, + key: route.query.key + }) + + if (Object.keys(dagData).length < 1 || pipelineData.length < 1) { + return false + } + + const obj: any = { + nodes: {}, + edges: [] + } + + // 给每一个节点分配一个唯一id + for (const i in dagData.vertexInfoMap) { + dagData.vertexInfoMap[i]['id'] = uuid(String(new Date().getTime())) + } + + // 通过pipeline中的inputVertexId和targetVertexId查询vertexInfoMap中的节点id + for (const i in dagData.pipelineEdges) { + dagData.pipelineEdges[i].forEach((l: any) => { + obj.edges.push({ + id: uuid(String(new Date().getTime())), + source: dagData.vertexInfoMap[l.inputVertexId].id, + target: dagData.vertexInfoMap[l.targetVertexId].id + }) + }) + } + + // 用pipelineData进行分组整合数据 + pipelineData.forEach((p: any) => { + const nodes = dagData.pipelineEdges[p.pipelineId] + .map((p: any) => [p.inputVertexId, p.targetVertexId]) + .flat(2) + + obj.nodes['group-' + p.pipelineId] = { + ...p, + child: nodes.map((n: any) => { + return { + id: dagData.vertexInfoMap[n].id, + label: dagData.vertexInfoMap[n].connectorType, + nodeType: dagData.vertexInfoMap[n].type, + vertexId: dagData.vertexInfoMap[n].vertexId + } + }) + } + }) + + useDagAddShape(graph, obj.nodes, obj.edges, t) + useDagLayout(graph) + } + + return { + getJobConfig, + getJobDag + } +} diff --git a/seatunnel-ui/src/views/task/synchronization-instance/index.tsx b/seatunnel-ui/src/views/task/synchronization-instance/index.tsx new file mode 100644 index 000000000..c88c8b14f --- /dev/null +++ b/seatunnel-ui/src/views/task/synchronization-instance/index.tsx @@ -0,0 +1,56 @@ +/* + * 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 { useRoute, useRouter } from 'vue-router' +import { NSpace, NTabs, NTabPane } from 'naive-ui' +import { useI18n } from 'vue-i18n' +import { SyncTask } from './sync-task' + +const SynchronizationInstance = defineComponent({ + name: 'SynchronizationInstance', + setup() { + const route = useRoute() + const router = useRouter() + const { t } = useI18n() + let syncTaskType = ref(route.query.syncTaskType || 'BATCH') + + return { t, syncTaskType } + }, + render() { + return ( + + + + + + + + + + + ) + } +}) + +export default SynchronizationInstance diff --git a/seatunnel-ui/src/views/task/synchronization-instance/sync-task.tsx b/seatunnel-ui/src/views/task/synchronization-instance/sync-task.tsx new file mode 100644 index 000000000..15fb8b53b --- /dev/null +++ b/seatunnel-ui/src/views/task/synchronization-instance/sync-task.tsx @@ -0,0 +1,419 @@ +/* + * 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, + onUnmounted, + PropType, + toRefs, + watch, + ref, + reactive +} from 'vue' +import { useSyncTask } from './use-sync-task' +import { + NSpace, + NCard, + NDataTable, + NPagination, + NInput, + NSelect, + NDatePicker, + NIcon, + NButton, + NGrid, + NGi, + NDropdown +} from 'naive-ui' +import { useI18n } from 'vue-i18n' +import { stateType } from '@/common/common' +import LogModal from '@/components/log-modal' +import { SearchOutlined, ReloadOutlined } from '@vicons/antd' +import { useAsyncState } from '@vueuse/core' +import { queryLog } from '@/service/log' +import { LogRes } from '@/service/log/types' +import ColumnSelector from '../../projects/components/column-selector' +import { useProjectStore } from '@/store/project' +import ProjectSelector from '../../projects/components/projectSelector' +import { getRangeShortCuts } from '@/utils/timePickeroption' +import { useRoute, useRouter } from 'vue-router' +import _ from 'lodash' +import { DownOutlined } from '@vicons/antd' + +const props = { + syncTaskType: { + type: String as PropType, + default: 'BATCH' + } +} + +const SyncTask = defineComponent({ + name: 'SyncTask', + props, + setup(props) { + let logTimer: number + const { t } = useI18n() + const { + variables, + getTableData, + batchBtnListClick, + creatInstanceButtons, + createColumns, + onReset + } = useSyncTask(props.syncTaskType) + const route = useRoute() + const router = useRouter() + + const tableColumn = ref([]) as any + const requestData = () => { + getTableData({ + pageNo: variables.page, + pageSize: variables.pageSize, + processInstanceName: variables.workflowInstance, + taskName: variables.taskName, + executorName: variables.executeUser, + host: variables.host, + stateType: variables.stateType, + startDate: variables.datePickerRange + ? variables.datePickerRange[0] + : '', + endDate: variables.datePickerRange ? variables.datePickerRange[1] : '', + syncTaskType: variables.syncTaskType + }) + } + const rangeShortCuts = reactive({ + rangeOption: {} + }) + rangeShortCuts.rangeOption = getRangeShortCuts(t) + + const onUpdatePageSize = () => { + variables.page = 1 + requestData() + } + + const getLogs = (row: any) => { + const { state } = useAsyncState( + queryLog({ + taskInstanceId: Number(row.id), + limit: variables.limit, + skipLineNum: variables.skipLineNum + }).then((res: LogRes) => { + if (res.log) { + variables.logRef += res.log + } + if (res.hasNext) { + variables.limit += 1000 + variables.skipLineNum += 1000 + clearTimeout(logTimer) + logTimer = setTimeout(() => { + getLogs(row) + }, 2000) + } else { + variables.logLoadingRef = false + } + }), + {} + ) + + return state + } + + const refreshLogs = (row: any) => { + variables.logRef = '' + variables.limit = 1000 + variables.skipLineNum = 0 + getLogs(row) + } + + const handleSearch = () => { + variables.page = 1 + + const query = {} as any + if (variables.taskName) { + query.taskName = variables.taskName + } + + if (variables.workflowInstance) { + query.workflowInstance = variables.workflowInstance + } + + if (variables.executeUser) { + query.executeUser = variables.executeUser + } + + if (variables.host) { + query.host = variables.host + } + + if (variables.stateType) { + query.stateType = variables.stateType + } + + if (variables.datePickerRange) { + query.startDate = variables.datePickerRange[0] + query.endDate = variables.datePickerRange[1] + } + + router.replace({ + query: !_.isEmpty(query) + ? { + ...route.query, + ...query, + syncTaskType: props.syncTaskType, + searchProjectCode: variables.projectCodes + } + : { + ...route.query, + syncTaskType: props.syncTaskType, + searchProjectCode: variables.projectCodes + } + }) + requestData() + } + + const handleKeyup = (event: KeyboardEvent) => { + if (event.key === 'Enter') { + handleSearch() + } + } + + const initSearch = () => { + const { startDate, endDate } = route.query + if (startDate && endDate) { + variables.datePickerRange = [startDate as string, endDate as string] + } + variables.taskName = (route.query.taskName as string) || '' + variables.workflowInstance = + (route.query.workflowInstance as string) || '' + variables.executeUser = (route.query.executeUser as string) || '' + variables.host = (route.query.host as string) || '' + variables.stateType = (route.query.stateType as string) || null + } + + onMounted(() => { + initSearch() + createColumns(variables) + creatInstanceButtons(variables) + requestData() + }) + + onUnmounted(() => { + clearTimeout(logTimer) + }) + + watch(useI18n().locale, () => { + createColumns(variables) + creatInstanceButtons(variables) + rangeShortCuts.rangeOption = getRangeShortCuts(t) + }) + + watch( + () => variables.showModalRef, + () => { + if (variables.showModalRef) { + getLogs(variables.row) + } else { + variables.row = {} + variables.logRef = '' + variables.logLoadingRef = true + variables.skipLineNum = 0 + variables.limit = 1000 + clearTimeout(logTimer) + } + } + ) + + const handleChangeColumn = (options: any) => { + tableColumn.value = options + } + const getProjectCodeList = (codes: any) => { + if (!codes) { + variables.projectCodes = useProjectStore().getGolbalProject + } else { + variables.projectCodes = [codes] + } + } + + return { + t, + ...toRefs(variables), + requestData, + onUpdatePageSize, + refreshLogs, + handleSearch, + onReset, + handleKeyup, + handleChangeColumn, + batchBtnListClick, + getProjectCodeList, + tableColumn, + rangeShortCuts + } + }, + render() { + const { t } = this + return ( + + + + {this.globalProject && ( + + + + )} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{ + 'header-extra': () => ( + + + + + {t('project.workflow.operation')} + + + + + + + ), + default: () => ( + + row.id} + scrollX={this.tableWidth} + v-model:checked-row-keys={this.checkedRowKeys} + /> + + + + + ) + }} + + (this.showModalRef = false)} + onRefreshLogs={this.refreshLogs} + /> + + ) + } +}) + +export { SyncTask } diff --git a/seatunnel-ui/src/views/task/synchronization-instance/use-sync-task.ts b/seatunnel-ui/src/views/task/synchronization-instance/use-sync-task.ts new file mode 100644 index 000000000..6c591bcd2 --- /dev/null +++ b/seatunnel-ui/src/views/task/synchronization-instance/use-sync-task.ts @@ -0,0 +1,403 @@ +/* + * 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, reactive, ref } from 'vue' +import { endOfToday, format, startOfToday } from 'date-fns' +import { useTableLink, useTableOperation } from '@/hooks' +import { + AlignLeftOutlined, + CheckCircleOutlined, + ClearOutlined, + DownloadOutlined +} from '@vicons/antd' +import { useI18n } from 'vue-i18n' +import { + cleanState, + downloadLog, + forceSuccess +} from '@/service/task-instances' +import { + COLUMN_WIDTH_CONFIG, + DefaultTableWidth, + calculateTableWidth +} from '@/common/column-width-config' +import { useRoute, useRouter } from 'vue-router' +import { ITaskState } from '@/common/types' +import { tasksState } from '@/common/common' +import { NIcon, NSpin, NTooltip } from 'naive-ui' +import { querySyncTaskInstancePaging } from '@/service/sync-task-instance' +import type { RowKey } from 'naive-ui/lib/data-table/src/interface' +import type { Router } from 'vue-router' +import { + cleanStateByIds, + forcedSuccessByIds +} from '@/service/sync-task-instance' +import { useProjectStore } from '@/store/project' +// import { changeProject } from '../../utils/changeProject' + +export function useSyncTask(syncTaskType = 'BATCH') { + const { t } = useI18n() + const router: Router = useRouter() + const projectStore = useProjectStore() + const route = useRoute() + + const variables = reactive({ + tableWidth: DefaultTableWidth, + columns: [], + tableData: [], + page: ref(1), + pageSize: ref(10), + totalPage: ref(1), + loadingRef: ref(false), + logRef: '', + logLoadingRef: ref(true), + showModalRef: ref(false), + row: {}, + skipLineNum: ref(0), + limit: ref(1000), + taskName: ref(''), + workflowInstance: ref(''), + executeUser: ref(''), + host: ref(''), + stateType: null as null | string, + syncTaskType, + checkedRowKeys: [] as Array, + buttonList: [], + projectCodes: + route.query.searchProjectCode || projectStore.getCurrentProject, + globalProject: projectStore.getGlobalFlag, + datePickerRange: [ + format(startOfToday(), 'yyyy-MM-dd HH:mm:ss'), + format(endOfToday(), 'yyyy-MM-dd HH:mm:ss') + ] + }) + + const creatInstanceButtons = (variables: any) => { + variables.buttonList = [ + { + label: t('project.task.clean_state'), + key: 'clean_state' + }, + { + label: t('project.task.forced_success'), + key: 'forced_success' + } + ] + } + + const createColumns = (variables: any) => { + variables.columns = [ + { + type: 'selection', + className: 'btn-selected', + ...COLUMN_WIDTH_CONFIG['selection'] + }, + useTableLink( + { + title: t('project.synchronization_definition.task_name'), + key: 'name', + ...COLUMN_WIDTH_CONFIG['link_name'], + button: { + permission: 'project:seatunnel-task-instance:details', + disabled: (row: any) => + !row.jobInstanceEngineId || + !row.jobInstanceEngineId.includes('::'), + onClick: (row: any) => { + router.push({ + path: `/projects/${row.projectCode}/task/synchronization-instance/${row.taskCode}`, + query: { + taskName: row.name, + key: row.jobInstanceEngineId, + seaTunnelJobId: row.seaTunnelJobId, + project: route.query.project, + global: route.query.global + } + }) + } + } + }, + // 'project' + ), + useTableLink({ + title: t('project.project_name'), + key: 'projectName', + ...COLUMN_WIDTH_CONFIG['link_name'], + button: { + onClick: (row: any) => { + // changeProject(row.projectCode) + router.push({ + path: route.path, + query: { + project: String(row.projectCode), + global: 'false' + } + }) + } + } + }), + { + title: t('project.synchronization_instance.workflow_instance'), + key: 'processInstanceName', + ...COLUMN_WIDTH_CONFIG['link_name'] + }, + { + title: t('project.synchronization_instance.amount_of_data_read'), + key: 'readRowCount', + ...COLUMN_WIDTH_CONFIG['tag'] + }, + { + title: t('project.synchronization_instance.amount_of_data_written'), + key: 'writeRowCount', + ...COLUMN_WIDTH_CONFIG['tag'] + }, + { + title: t('project.synchronization_instance.execute_user'), + key: 'executorName', + ...COLUMN_WIDTH_CONFIG['state'] + }, + { + title: t('project.synchronization_instance.node_type'), + key: 'taskType', + ...COLUMN_WIDTH_CONFIG['userName'] + }, + { + title: t('project.synchronization_instance.state'), + key: 'state', + render: (row: any) => renderStateCell(row.state, t), + ...COLUMN_WIDTH_CONFIG['state'] + }, + { + title: t('project.synchronization_instance.submit_time'), + key: 'submitTime', + ...COLUMN_WIDTH_CONFIG['time'] + }, + { + title: t('project.synchronization_instance.start_time'), + key: 'startTime', + ...COLUMN_WIDTH_CONFIG['time'] + }, + { + title: t('project.synchronization_instance.end_time'), + key: 'endTime', + ...COLUMN_WIDTH_CONFIG['time'] + }, + { + title: t('project.synchronization_instance.run_time'), + key: 'duration', + ...COLUMN_WIDTH_CONFIG['duration'] + }, + { + title: t('project.synchronization_instance.number_of_retries'), + key: 'retryTimes', + ...COLUMN_WIDTH_CONFIG['size'] + }, + { + title: t('project.synchronization_instance.rerun_mark'), + key: 'dryRun', + ...COLUMN_WIDTH_CONFIG['size'] + }, + { + title: t('project.synchronization_instance.host'), + key: 'host', + ...COLUMN_WIDTH_CONFIG['name'] + }, + useTableOperation( + { + title: t('project.synchronization_instance.operation'), + key: 'operation', + buttons: [ + { + text: t('project.synchronization_instance.clean_state'), + permission: 'project:seatunnel-task-instance:clean-state', + icon: h(ClearOutlined), + onClick: (row: any) => void handleCleanState(row), + disabled: (row: any) => row.state === 'RUNNING_EXECUTION' + }, + { + text: t('project.synchronization_instance.forced_success'), + permission: 'project:seatunnel-task-instance:force-success', + icon: h(CheckCircleOutlined), + onClick: (row: any) => void handleForcedSuccess(row), + disabled: (row: any) => + !( + row.state === 'FAILURE' || + row.state === 'NEED_FAULT_TOLERANCE' || + row.state === 'KILL' + ) + }, + { + text: t('project.synchronization_instance.view_log'), + permission: 'project:seatunnel-task-instance:log', + icon: h(AlignLeftOutlined), + onClick: (row) => void handleLog(row), + disabled: (row) => !row.host + }, + { + text: t('project.synchronization_instance.download_log'), + permission: 'project:seatunnel-task-instance:download-log', + icon: h(DownloadOutlined), + onClick: (row) => void downloadLog(row.id) + } + ] + }, + // 'project' + ) + ] + + if (variables.tableWidth) { + variables.tableWidth = calculateTableWidth(variables.columns) + } + } + + const getTableData = (params: any) => { + if ( + variables.loadingRef || + !variables.projectCodes || + variables.projectCodes.length === 0 || + variables.projectCodes[0] === undefined + ) + return + variables.loadingRef = true + + params['projectCodes'] = variables.projectCodes + + querySyncTaskInstancePaging(params) + .then((res: any) => { + variables.tableData = res.totalList as any + variables.totalPage = res.totalPage + variables.loadingRef = false + }) + .catch(() => { + variables.loadingRef = false + }) + } + + const handleLog = (row: any) => { + variables.showModalRef = true + variables.row = row + } + + const handleCleanState = (row: any) => { + cleanState(Number(row.projectCode), [row.id]).then(() => { + getList() + }) + } + + const handleForcedSuccess = (row: any) => { + forceSuccess({ id: row.id }, { projectCode: Number(row.projectCode) }).then( + () => { + getList() + } + ) + } + + const getList = () => { + getTableData({ + pageSize: variables.pageSize, + pageNo: + variables.tableData.length === 1 && variables.page > 1 + ? variables.page - 1 + : variables.page, + taskName: variables.taskName, + processInstanceName: variables.workflowInstance, + host: variables.host, + stateType: variables.stateType, + startDate: variables.datePickerRange ? variables.datePickerRange[0] : '', + endDate: variables.datePickerRange ? variables.datePickerRange[1] : '', + executorName: variables.executeUser, + syncTaskType: variables.syncTaskType + }) + } + + const onReset = () => { + variables.taskName = '' + variables.workflowInstance = '' + variables.executeUser = '' + variables.host = '' + variables.stateType = null + variables.datePickerRange = [ + format(startOfToday(), 'yyyy-MM-dd HH:mm:ss'), + format(endOfToday(), 'yyyy-MM-dd HH:mm:ss') + ] + variables.projectCodes = useProjectStore().getCurrentProject + } + const onBatchCleanState = (ids: any) => { + cleanStateByIds(ids).then(() => { + window.$message.success(t('project.workflow.success')) + variables.checkedRowKeys = [] + getList() + }) + } + + const onBatchForcedSuccess = (ids: any) => { + forcedSuccessByIds(ids).then(() => { + window.$message.success(t('project.workflow.success')) + variables.checkedRowKeys = [] + getList() + }) + } + + const batchBtnListClick = (key: string) => { + if (variables.checkedRowKeys.length == 0) { + window.$message.warning(t('project.select_task_instance')) + return + } + switch (key) { + case 'clean_state': + onBatchCleanState(variables.checkedRowKeys) + break + case 'forced_success': + onBatchForcedSuccess(variables.checkedRowKeys) + break + } + } + + return { + variables, + createColumns, + getTableData, + onReset, + batchBtnListClick, + creatInstanceButtons + } +} + +const renderStateCell = (state: ITaskState, t: Function) => { + if (!state) return '' + + const stateOption = tasksState(t)[state] + if (!stateOption) return '' + const Icon = h( + NIcon, + { + color: stateOption.color, + class: stateOption.classNames, + style: { + display: 'flex' + }, + size: 20 + }, + () => h(stateOption.icon) + ) + return h(NTooltip, null, { + trigger: () => { + if (!stateOption.isSpin) return Icon + return h(NSpin, { size: 20 }, { icon: () => Icon }) + }, + default: () => stateOption.desc + }) +} diff --git a/seatunnel-ui/src/views/tasks/list/use-table.ts b/seatunnel-ui/src/views/tasks/list/use-table.ts index 020312cf5..93cbbb829 100644 --- a/seatunnel-ui/src/views/tasks/list/use-table.ts +++ b/seatunnel-ui/src/views/tasks/list/use-table.ts @@ -140,7 +140,7 @@ export function useTable() { pageSize: params.pageSize, name: params.name }).then((res: ResponseTable | []>) => { - state.tableData = res.data.data as any + state.tableData = res.data as any state.totalPage = res.data.totalPage state.loading = false }) diff --git a/seatunnel-ui/src/views/user-manage/list/use-table.ts b/seatunnel-ui/src/views/user-manage/list/use-table.ts index bff9f974d..d781c8572 100644 --- a/seatunnel-ui/src/views/user-manage/list/use-table.ts +++ b/seatunnel-ui/src/views/user-manage/list/use-table.ts @@ -125,7 +125,7 @@ export function useTable() { state.loading = true userList({ ...params }).then( (res: ResponseTable | []>) => { - state.tableData = res.data.data as any + state.tableData = res.data as any state.totalPage = res.data.totalPage state.loading = false } diff --git a/seatunnel-ui/src/views/virtual-tables/detail.tsx b/seatunnel-ui/src/views/virtual-tables/detail.tsx index 294f0fd23..22464e71d 100644 --- a/seatunnel-ui/src/views/virtual-tables/detail.tsx +++ b/seatunnel-ui/src/views/virtual-tables/detail.tsx @@ -52,14 +52,14 @@ const VirtualTablesDetail = defineComponent({ onChangeStep, createOrUpdate } = useDetail(route.params.id as string) - + console.log('create') const onClose = () => { dialog.warning({ title: t('virtual_tables.warning'), content: t('virtual_tables.close_confirm_tips'), onPositiveClick: () => { router.push({ - name: 'datasource-list', + name: 'virtual-tables-list', query: { tab: 'virtual-tables' } }) }, @@ -80,16 +80,16 @@ const VirtualTablesDetail = defineComponent({ {t( route.params.id - ? 'virtualTables.edit_virtual_tables' - : 'virtualTables.create_virtual_tables' + ? t('virtualTables.edit_virtual_tables') + : t('virtual_tables.create_virtual_tables') )}
@@ -121,7 +121,7 @@ const VirtualTablesDetail = defineComponent({
@@ -155,7 +155,7 @@ const VirtualTablesDetail = defineComponent({
diff --git a/seatunnel-ui/src/views/virtual-tables/list/index.tsx b/seatunnel-ui/src/views/virtual-tables/list/index.tsx index 03bfdea16..d8ba60176 100644 --- a/seatunnel-ui/src/views/virtual-tables/list/index.tsx +++ b/seatunnel-ui/src/views/virtual-tables/list/index.tsx @@ -32,14 +32,14 @@ import { useI18n } from 'vue-i18n' import { useRouter } from 'vue-router' import { useTable } from './use-table' import { useColumns } from './use-columns' -//import { useSource } from '../datasource/list/use-source' +import { useSource } from '@/views/datasource/list/use-source' import styles from '../index.module.scss' const VirtualTablesList = defineComponent({ setup() { const { t } = useI18n() const router = useRouter() - //const { state: sourceState } = useSource(true) + const { state: sourceState } = useSource(true) const { columns } = useColumns( (id: string, type: 'edit' | 'delete') => { if (type === 'edit') { @@ -66,9 +66,9 @@ const VirtualTablesList = defineComponent({ v-model:value={state.params.pluginName} clearable placeholder={t('virtual_tables.source_type_tips')} - //options={ - // sourceState.types as Array - //} + options={ + sourceState.types as Array + } class={styles['type-width']} /> { - const result = await virtualTableList({ + const result = await getVirtualTableList({ pageNo: state.page, pageSize: state.pageSize, - ...state.params + pluginName: state.params.pluginName || '', + datasourceName: state.params.datasourceName || '', }) - console.log(result) state.list = result?.data state.itemCount = result?.total } @@ -57,16 +55,16 @@ export function useTable() { } const onDelete = async (id: string) => { - //await deleteVirtualTable(id) + await deleteVirtualTable(id) updateList() } const initSearch = () => { const { pluginName, datasourceName } = route.query if (pluginName) { - state.params.pluginName = pluginName as string + state.params.pluginName = pluginName as any if (datasourceName) { - state.params.datasourceName = datasourceName as string + state.params.datasourceName = datasourceName as any } } } diff --git a/seatunnel-ui/src/views/virtual-tables/step-one-form.tsx b/seatunnel-ui/src/views/virtual-tables/step-one-form.tsx index 632a32d5d..9f880abe7 100644 --- a/seatunnel-ui/src/views/virtual-tables/step-one-form.tsx +++ b/seatunnel-ui/src/views/virtual-tables/step-one-form.tsx @@ -24,7 +24,7 @@ import { SelectGroupOption } from 'naive-ui' import { useI18n } from 'vue-i18n' -//import { useSource } from '../datasource/list/use-source' +import { useSource } from '../datasource/list/use-source' import { useTable } from '../datasource/list/use-table' import styles from './index.module.scss' @@ -38,7 +38,7 @@ const StepOneForm = defineComponent({ }, setup(props, { expose }) { const { t } = useI18n() - //const { state: sourceState } = useSource(true) + const { state: sourceState } = useSource(true) const { data: datasourceState, getList } = useTable() const rules = { pluginName: { @@ -63,7 +63,7 @@ const StepOneForm = defineComponent({ const handleUpdateType = (type: string) => { if (type === paramsRef.value.pluginName) return datasourceState.pageSize = 99999 - //getList(type) + getList(type) paramsRef.value.datasourceName = null paramsRef.value.tableName = null } @@ -93,10 +93,10 @@ const StepOneForm = defineComponent({ v-model:value={props.params.pluginName} filterable placeholder={t('virtual_tables.source_type_tips')} - //options={ - // sourceState.types as Array - //} - //loading={sourceState.loading} + options={ + sourceState.types as Array + } + loading={sourceState.loading} class={styles['type-width']} onUpdateValue={(value) => void handleUpdateType(value)} /> diff --git a/seatunnel-ui/src/views/virtual-tables/step-two-form.tsx b/seatunnel-ui/src/views/virtual-tables/step-two-form.tsx index a14793ddb..f81253b83 100644 --- a/seatunnel-ui/src/views/virtual-tables/step-two-form.tsx +++ b/seatunnel-ui/src/views/virtual-tables/step-two-form.tsx @@ -18,8 +18,8 @@ import { defineComponent, reactive, ref } from 'vue' import { NForm } from 'naive-ui' import { useI18n } from 'vue-i18n' import { DynamicFormItem } from '@/components/dynamic-form/dynamic-form-item' -//import { StructureItem } from '@/store/datasource/form-structures' -//import { getDynamicConfig } from '@/service/modules/virtual-table' +import { StructureItem } from '@/store/datasource/form-structures' +import { getDynamicConfig } from '@/service/virtual-table' 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' @@ -33,7 +33,7 @@ const StepTwoForm = defineComponent({ const state = reactive({ rules: {}, - //formStructure: [] as StructureItem[], + formStructure: [] as StructureItem[], locales: {} as any, formName: 'step-two-form', detailForm: {} as { [key: string]: string }, @@ -42,27 +42,27 @@ const StepTwoForm = defineComponent({ const getFormItems = async (pluginName: string, datasourceName: string) => { if (!pluginName || !datasourceName) return false - //const result = await getDynamicConfig({ - // pluginName, - // datasourceName - //}) + const result = await getDynamicConfig({ + pluginName, + datasourceName + }) try { - //const res = JSON.parse(result) - // - //state.locales = res.locales - //Object.assign(state.detailForm, useFormField(res.forms)) - //Object.assign( - // state.rules, - // useFormValidate(res.forms, state.detailForm, t) - //) - //state.formStructure = useFormStructure( - // res.apis ? useFormRequest(res.apis, res.forms) : res.forms - //) as any - // - //state.formStructure = res.forms.map((item: any) => ({ - // ...item, - // span: 8 - //})) + const res = JSON.parse(result) + + state.locales = res.locales + Object.assign(state.detailForm, useFormField(res.forms)) + Object.assign( + state.rules, + useFormValidate(res.forms, state.detailForm, t) + ) + state.formStructure = useFormStructure( + res.apis ? useFormRequest(res.apis, res.forms) : res.forms + ) as any + + state.formStructure = res.forms.map((item: any) => ({ + ...item, + span: 8 + })) return true } catch (err) { return false @@ -71,13 +71,13 @@ const StepTwoForm = defineComponent({ const getValues = async () => { await stepTwoFormRef.value.validate() - //return state.formStructure.map((item) => { - // return { - // label: item.label, - // key: item.field, - // value: state.detailForm[item.field] - // } - //}) + return state.formStructure.map((item) => { + return { + label: item.label, + key: item.field, + value: state.detailForm[item.field] + } + }) } const setValues = (values: { [key: string]: string }) => { @@ -101,14 +101,14 @@ const StepTwoForm = defineComponent({ model={state.detailForm} labelWidth={100} > - {/*{state.formStructure.length > 0 && (*/} - {/* */} - {/*)}*/} + {state.formStructure.length > 0 && ( + + )} ) } diff --git a/seatunnel-ui/src/views/virtual-tables/types.ts b/seatunnel-ui/src/views/virtual-tables/types.ts index 09cee8c84..0d03e63db 100644 --- a/seatunnel-ui/src/views/virtual-tables/types.ts +++ b/seatunnel-ui/src/views/virtual-tables/types.ts @@ -16,11 +16,11 @@ */ export type { TableColumns } from 'naive-ui/es/data-table/src/interface' export type { DataTableColumns } from 'naive-ui' -//export type { -// VirtualTableDetail, -// VirtualTableRecord, -// IDetailTableRecord -//} from '@/service/modules/virtual-table/types' -//import type { VirtualTableListParameters } from '@/service/modules/virtual-table/types' +export type { + VirtualTableDetail, + VirtualTableRecord, + IDetailTableRecord +} from '@/service/virtual-table/types' +import type { VirtualTableListParameters } from '@/service/virtual-table/types' -//export type Params = Omit +export type Params = Omit diff --git a/seatunnel-ui/src/views/virtual-tables/use-detail.ts b/seatunnel-ui/src/views/virtual-tables/use-detail.ts index 0c2f709b8..bf81fd70e 100644 --- a/seatunnel-ui/src/views/virtual-tables/use-detail.ts +++ b/seatunnel-ui/src/views/virtual-tables/use-detail.ts @@ -16,15 +16,15 @@ */ import { reactive, ref, onMounted } from 'vue' import { useI18n } from 'vue-i18n' -//import { -// getVirtualTableDetail, -// createVirtualTable, -// updateVirtualTable, -// getFieldType -//} from '@/service/modules/virtual-table' +import { + getVirtualTableDetail, + createVirtualTable, + updateVirtualTable, + getFieldType +} from '@/service/virtual-table' import { omit } from 'lodash' import { useRouter } from 'vue-router' -//import type { IDetailTableRecord, VirtualTableDetail } from './types' +import type { IDetailTableRecord, VirtualTableDetail } from './types' export const useDetail = (id: string) => { const state = reactive({ @@ -38,7 +38,7 @@ export const useDetail = (id: string) => { datasourceId: '' }, stepTwo: { - //list: [] as IDetailTableRecord[], + list: [] as IDetailTableRecord[], loading: false, config: [] }, @@ -61,27 +61,28 @@ export const useDetail = (id: string) => { const queryById = async () => { if (state.loading) return {} state.loading = true - //const res = await getVirtualTableDetail(id) - //state.stepOne.pluginName = res.pluginName - //state.stepOne.datasourceName = res.datasourceName - //state.stepOne.datasourceId = res.datasourceId - //state.stepOne.tableName = res.tableName - //state.stepTwo.list = res.fields.map((item: { [key: string]: any }) => ({ - // ...item, - // nullable: Number(item.nullable), - // primaryKey: Number(item.primaryKey) - //})) - //tempDatabaseProperties = res.datasourceProperties + const res = await getVirtualTableDetail(id) + state.stepOne.pluginName = res.pluginName + state.stepOne.datasourceName = res.datasourceName + state.stepOne.datasourceId = res.datasourceId + state.stepOne.tableName = res.tableName + state.stepTwo.list = res.fields.map((item: { [key: string]: any }) => ({ + ...item, + nullable: Number(item.nullable), + primaryKey: Number(item.primaryKey) + })) + tempDatabaseProperties = res.datasourceProperties state.loading = false } const queryFieldsType = async () => { - //const res = await getFieldType() - //state.fieldTypes = res - //defaultRecord.fieldType = state.fieldTypes[0] + const res = await getFieldType() + console.log(res, 'type') + state.fieldTypes = res + defaultRecord.fieldType = state.fieldTypes[0] } - const formatParams = () => { + const formatParams = (): VirtualTableDetail => { const databaseProperties = {} as { [key: string]: string } state.stepTwo.config.forEach((item: { key: string; value: string }) => { databaseProperties[item.key] = item.value @@ -91,11 +92,11 @@ export const useDetail = (id: string) => { datasourceName: state.stepOne.datasourceName || '', pluginName: state.stepOne.pluginName || '', tableName: state.stepOne.tableName, - //tableFields: state.stepTwo.list.map((item) => ({ - // ...omit(item, ['key', 'isEdit']), - // nullable: Boolean(item.nullable), - // primaryKey: Boolean(item.nullable) - //})), + tableFields: state.stepTwo.list.map((item) => ({ + ...omit(item, ['key', 'isEdit']), + nullable: Boolean(item.nullable), + primaryKey: Boolean(item.nullable) + })), databaseProperties, databaseName: 'default' } @@ -107,13 +108,13 @@ export const useDetail = (id: string) => { state.saving = true try { - //id - // ? await updateVirtualTable(values, id) - // : await createVirtualTable(values) + id + ? await updateVirtualTable(values, id) + : await createVirtualTable(values) state.saving = false router.push({ - name: 'datasource-list', + name: 'virtual-tables-list', query: { tab: 'virtual-tables' } }) return true @@ -124,10 +125,11 @@ export const useDetail = (id: string) => { } const onAddRecord = () => { - //state.stepTwo.list.unshift({ - // ...defaultRecord, - // key: Date.now() + Math.random() * 1000 - //}) + state.stepTwo.list.unshift({ + ...defaultRecord, + key: Date.now() + Math.random() * 1000 + }) + console.log(state, 'ta') } const onChangeStep = async (step: -1 | 1) => { @@ -150,15 +152,15 @@ export const useDetail = (id: string) => { try { const values = await stepTwoFormRef.value.getValues() state.stepTwo.config = values - //const flag = state.stepTwo.list.some((item) => item.isEdit) - //if (flag) { - // window.$message.error(t('virtual_tables.save_data_tips')) - // return - //} - //if (state.stepTwo.list.length === 0) { - // window.$message.error(t('virtual_tables.table_data_required_tips')) - // return - //} + const flag = state.stepTwo.list.some((item) => item.isEdit) + if (flag) { + window.$message.error(t('virtualTables.save_data_tips')) + return + } + if (state.stepTwo.list.length === 0) { + window.$message.error(t('virtualTables.table_data_required_tips')) + return + } } finally { state.goNexting = false } @@ -167,6 +169,7 @@ export const useDetail = (id: string) => { } onMounted(() => { + console.log('vir') if (id) { queryById() } From f039833ec71df06e6336613f7d162d81df072628 Mon Sep 17 00:00:00 2001 From: zhangchengming601 <86779821+zhangchengming601@users.noreply.github.com> Date: Fri, 30 Jun 2023 17:58:30 +0800 Subject: [PATCH 2/3] [MODIFY][SYNC TASK DEFINITION] Add synchronization task definition module (#62) * [ADD][SYNC TASK DEFINITION] * [ADD][SYNC TASK DEFINITION] POM File --- pom.xml | 325 ++++++++++++++++- seatunnel-server/seatunnel-app/pom.xml | 330 +++++++++++++++++- .../app/controller/JobConfigController.java | 4 +- .../controller/JobDefinitionController.java | 3 +- .../SeatunnelDatasourceController.java | 5 +- .../app/controller/TableSchemaController.java | 67 ++++ .../app/dal/entity/ProcessTaskRelation.java | 6 +- .../dal/entity/ProcessTaskRelationLog.java | 2 +- .../datasource/DatasourceCheckReq.java | 4 +- .../app/domain/request/job/JobReq.java | 3 - .../AuthenticationInterceptor.java | 1 + .../service/impl/ConnectorServiceImpl.java | 3 +- .../seatunnel/app/dal/mapper/JobMapper.xml | 4 +- 13 files changed, 733 insertions(+), 24 deletions(-) create mode 100644 seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/controller/TableSchemaController.java diff --git a/pom.xml b/pom.xml index ae977907b..a8900c87d 100644 --- a/pom.xml +++ b/pom.xml @@ -71,7 +71,7 @@ 19.0 3.10.0 4.2.0 - 2.3.1 + 2.4.5-WS-SNAPSHOT 2.1.0.9 3.1.4 1.11.271 @@ -549,6 +549,329 @@ jsr305 ${jsr305.version} + + + + org.apache.seatunnel + connector-common + ${seatunnel-framework.version} + test + + + + org.apache.seatunnel + seatunnel-transforms-v2 + ${seatunnel-framework.version} + + + + org.apache.seatunnel + connector-console + ${seatunnel-framework.version} + test + + + + org.apache.seatunnel + connector-fake + ${seatunnel-framework.version} + test + + + + org.apache.seatunnel + connector-kafka + ${seatunnel-framework.version} + test + + + org.apache.seatunnel + connector-http-base + ${seatunnel-framework.version} + test + + + org.apache.seatunnel + connector-http-feishu + ${seatunnel-framework.version} + test + + + org.apache.seatunnel + connector-http-wechat + ${seatunnel-framework.version} + test + + + org.apache.seatunnel + connector-http-myhours + ${seatunnel-framework.version} + test + + + org.apache.seatunnel + connector-http-lemlist + ${seatunnel-framework.version} + test + + + org.apache.seatunnel + connector-http-klaviyo + ${seatunnel-framework.version} + test + + + org.apache.seatunnel + connector-http-onesignal + ${seatunnel-framework.version} + test + + + org.apache.seatunnel + connector-http-notion + ${seatunnel-framework.version} + test + + + org.apache.seatunnel + connector-jdbc + ${seatunnel-framework.version} + test + + + org.apache.seatunnel + connector-socket + ${seatunnel-framework.version} + test + + + org.apache.seatunnel + connector-clickhouse + ${seatunnel-framework.version} + test + + + org.apache.seatunnel + connector-pulsar + ${seatunnel-framework.version} + test + + + org.apache.seatunnel + connector-hive + ${seatunnel-framework.version} + test + + + org.apache.seatunnel + connector-file-hadoop + ${seatunnel-framework.version} + test + + + org.apache.seatunnel + connector-file-local + ${seatunnel-framework.version} + test + + + org.apache.seatunnel + connector-file-oss + ${seatunnel-framework.version} + test + + + org.apache.seatunnel + connector-file-oss-jindo + ${seatunnel-framework.version} + test + + + org.apache.seatunnel + connector-file-ftp + ${seatunnel-framework.version} + test + + + org.apache.seatunnel + connector-file-sftp + ${seatunnel-framework.version} + test + + + org.apache.seatunnel + connector-hudi + ${seatunnel-framework.version} + test + + + org.apache.seatunnel + connector-dingtalk + ${seatunnel-framework.version} + test + + + org.apache.seatunnel + connector-kudu + ${seatunnel-framework.version} + test + + + org.apache.seatunnel + connector-email + ${seatunnel-framework.version} + test + + + org.apache.seatunnel + connector-elasticsearch + ${seatunnel-framework.version} + test + + + org.apache.seatunnel + connector-iotdb + ${seatunnel-framework.version} + test + + + org.apache.seatunnel + connector-neo4j + ${seatunnel-framework.version} + test + + + org.apache.seatunnel + connector-redis + ${seatunnel-framework.version} + test + + + org.apache.seatunnel + connector-google-sheets + ${seatunnel-framework.version} + test + + + org.apache.seatunnel + connector-datahub + ${seatunnel-framework.version} + test + + + org.apache.seatunnel + connector-sentry + ${seatunnel-framework.version} + test + + + org.apache.seatunnel + connector-mongodb + ${seatunnel-framework.version} + test + + + org.apache.seatunnel + connector-iceberg + ${seatunnel-framework.version} + test + + + org.apache.seatunnel + connector-influxdb + ${seatunnel-framework.version} + test + + + org.apache.seatunnel + connector-cassandra + ${seatunnel-framework.version} + test + + + org.apache.seatunnel + connector-file-s3 + ${seatunnel-framework.version} + test + + + org.apache.seatunnel + connector-amazondynamodb + ${seatunnel-framework.version} + test + + + org.apache.seatunnel + connector-starrocks + ${seatunnel-framework.version} + test + + + org.apache.seatunnel + connector-tablestore + ${seatunnel-framework.version} + test + + + org.apache.seatunnel + connector-slack + ${seatunnel-framework.version} + test + + + org.apache.seatunnel + connector-http-gitlab + ${seatunnel-framework.version} + test + + + org.apache.seatunnel + connector-http-jira + ${seatunnel-framework.version} + test + + + org.apache.seatunnel + connector-rabbitmq + ${seatunnel-framework.version} + test + + + org.apache.seatunnel + connector-openmldb + ${seatunnel-framework.version} + test + + + org.apache.seatunnel + connector-doris + ${seatunnel-framework.version} + test + + + org.apache.seatunnel + connector-maxcompute + ${seatunnel-framework.version} + test + + + org.apache.seatunnel + connector-cdc-mysql + ${seatunnel-framework.version} + test + + + org.apache.seatunnel + connector-cdc-sqlserver + ${seatunnel-framework.version} + test + + + org.apache.seatunnel + connector-cdc-oracle + ${seatunnel-framework.version} + test + diff --git a/seatunnel-server/seatunnel-app/pom.xml b/seatunnel-server/seatunnel-app/pom.xml index 2e6e0ef2a..60264695d 100644 --- a/seatunnel-server/seatunnel-app/pom.xml +++ b/seatunnel-server/seatunnel-app/pom.xml @@ -61,6 +61,273 @@ ${project.version} + + + org.apache.seatunnel + seatunnel-transforms-v2 + + + + + + org.apache.seatunnel + connector-common + + + + + org.apache.seatunnel + seatunnel-transforms-v2 + + + + org.apache.seatunnel + connector-console + + + + + org.apache.seatunnel + connector-fake + test + + + + org.apache.seatunnel + connector-kafka + + + + org.apache.seatunnel + connector-http-base + test + + + org.apache.seatunnel + connector-http-feishu + test + + + org.apache.seatunnel + connector-http-wechat + test + + + org.apache.seatunnel + connector-http-myhours + test + + + org.apache.seatunnel + connector-http-lemlist + test + + + org.apache.seatunnel + connector-http-klaviyo + test + + + org.apache.seatunnel + connector-http-onesignal + test + + + org.apache.seatunnel + connector-http-notion + test + + + org.apache.seatunnel + connector-jdbc + + + + org.apache.seatunnel + connector-socket + test + + + org.apache.seatunnel + connector-clickhouse + test + + + org.apache.seatunnel + connector-pulsar + test + + + org.apache.seatunnel + connector-hive + test + + + org.apache.seatunnel + connector-file-hadoop + test + + + org.apache.seatunnel + connector-file-local + test + + + org.apache.seatunnel + connector-file-oss + test + + + org.apache.seatunnel + connector-file-ftp + test + + + org.apache.seatunnel + connector-file-sftp + test + + + org.apache.seatunnel + connector-hudi + test + + + org.apache.seatunnel + connector-dingtalk + test + + + org.apache.seatunnel + connector-kudu + test + + + org.apache.seatunnel + connector-email + test + + + org.apache.seatunnel + connector-elasticsearch + test + + + org.apache.seatunnel + connector-iotdb + test + + + org.apache.seatunnel + connector-neo4j + test + + + org.apache.seatunnel + connector-redis + test + + + org.apache.seatunnel + connector-google-sheets + test + + + org.apache.seatunnel + connector-datahub + test + + + org.apache.seatunnel + connector-sentry + test + + + org.apache.seatunnel + connector-mongodb + test + + + org.apache.seatunnel + connector-iceberg + test + + + org.apache.seatunnel + connector-influxdb + test + + + org.apache.seatunnel + connector-cassandra + test + + + org.apache.seatunnel + connector-file-s3 + test + + + org.apache.seatunnel + connector-amazondynamodb + test + + + org.apache.seatunnel + connector-starrocks + test + + + org.apache.seatunnel + connector-tablestore + test + + + org.apache.seatunnel + connector-slack + test + + + org.apache.seatunnel + connector-http-gitlab + test + + + org.apache.seatunnel + connector-http-jira + test + + + org.apache.seatunnel + connector-rabbitmq + test + + + org.apache.seatunnel + connector-openmldb + test + + + org.apache.seatunnel + connector-doris + test + + + org.apache.seatunnel + connector-maxcompute + test + + + org.apache.seatunnel + connector-cdc-mysql + test + + + org.apache.seatunnel + connector-cdc-sqlserver + test + + org.springframework.boot @@ -157,13 +424,6 @@ commons-lang3 - - mysql - mysql-connector-java - ${mysql.version} - provided - - com.h2database h2 @@ -225,10 +485,37 @@ com.google.auto.service auto-service-annotations + + com.google.auto.service + auto-service + org.apache.seatunnel datasource-s3 + provided + + + org.apache.seatunnel + datasource-kafka + 1.0.0-SNAPSHOT + provided + + + + org.apache.seatunnel + datasource-jdbc-mysql + 1.0.0-SNAPSHOT + provided + + + + org.apache.seatunnel + datasource-jdbc-sqlserver + 1.0.0-SNAPSHOT + provided + + com.google.code.findbugs jsr305 @@ -239,4 +526,33 @@ seatunnel-engine-client + + + + + org.apache.maven.plugins + maven-resources-plugin + 3.2.0 + + + copy-resources + + copy-resources + + process-classes + + ${project.build.outputDirectory}/META-INF/services + + + ${project.basedir}/src/main/resources/META-INF/services + true + + + + + + + + + diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/controller/JobConfigController.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/controller/JobConfigController.java index a3ac95fa7..054f49ce7 100644 --- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/controller/JobConfigController.java +++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/controller/JobConfigController.java @@ -24,9 +24,9 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestAttribute; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import com.fasterxml.jackson.core.JsonProcessingException; @@ -44,7 +44,7 @@ public class JobConfigController { @PutMapping("/{jobVersionId}") @ApiOperation(value = "update job config", httpMethod = "PUT") Result updateJobConfig( - @ApiParam(value = "userId", required = true) @RequestParam Integer userId, + @ApiParam(value = "userId", required = true) @RequestAttribute("userId") Integer userId, @ApiParam(value = "jobVersionId", required = true) @PathVariable long jobVersionId, @ApiParam(value = "jobConfig", required = true) @RequestBody JobConfig jobConfig) throws JsonProcessingException { diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/controller/JobDefinitionController.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/controller/JobDefinitionController.java index dbe72d0fa..e95baa8e2 100644 --- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/controller/JobDefinitionController.java +++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/controller/JobDefinitionController.java @@ -29,6 +29,7 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestAttribute; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; @@ -53,7 +54,7 @@ public class JobDefinitionController { @PostMapping @ApiOperation(value = "create job definition", httpMethod = "POST") Result createJobDefinition( - @ApiParam(value = "userId", required = true) @RequestParam Integer userId, + @ApiParam(value = "userId", required = true) @RequestAttribute("userId") Integer userId, @RequestBody JobReq jobReq) throws CodeGenerateUtils.CodeGenerateException { return Result.success(jobService.createJob(userId, jobReq)); diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/controller/SeatunnelDatasourceController.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/controller/SeatunnelDatasourceController.java index 85bdc0912..ac8929964 100644 --- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/controller/SeatunnelDatasourceController.java +++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/controller/SeatunnelDatasourceController.java @@ -150,13 +150,14 @@ Result createDatasource( Result testConnect( @ApiIgnore @RequestAttribute(value = SESSION_USER) User loginUser, @RequestBody DatasourceCheckReq req) { - Map stringStringMap = JSONUtils.toMap(req.getDatasourceConfig()); + + // Map stringStringMap = JSONUtils.toMap(req.getDatasourceConfig()); return Result.success( datasourceService.testDatasourceConnectionAble( loginUser.getId(), req.getPluginName(), DEFAULT_PLUGIN_VERSION, - stringStringMap)); + req.getDatasourceConfig())); } @ApiOperation(value = "update datasource", notes = "update datasource") diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/controller/TableSchemaController.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/controller/TableSchemaController.java new file mode 100644 index 000000000..e9f355150 --- /dev/null +++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/controller/TableSchemaController.java @@ -0,0 +1,67 @@ +/* + * 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. + */ + +package org.apache.seatunnel.app.controller; + +import org.apache.seatunnel.app.common.Result; +import org.apache.seatunnel.app.domain.request.job.DataSourceOption; +import org.apache.seatunnel.app.domain.request.job.TableSchemaReq; +import org.apache.seatunnel.app.domain.response.job.TableSchemaRes; +import org.apache.seatunnel.app.service.ITableSchemaService; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; + +import javax.annotation.Resource; + +@RestController +@RequestMapping("/seatunnel/api/v1/job/table") +public class TableSchemaController { + + @Resource private ITableSchemaService tableSchemaService; + + @PostMapping("/schema") + Result querySchemaMapping( + @ApiParam(value = "datasource plugin name", required = true) @RequestParam + String pluginName, + @ApiParam(value = "task info", required = true) @RequestBody + TableSchemaReq tableSchemaReq) { + return Result.success(tableSchemaService.getSeaTunnelSchema(pluginName, tableSchemaReq)); + } + + @PostMapping("/check") + @ApiOperation(value = "check database and table is exist", httpMethod = "POST") + public Result checkDatabaseAndTable( + @RequestParam String datasourceId, @RequestBody DataSourceOption dataSourceOption) { + return Result.success( + tableSchemaService.checkDatabaseAndTable(datasourceId, dataSourceOption)); + } + + @GetMapping("/column-projection") + Result queryColumnProjection( + @ApiParam(value = "datasource plugin name", required = true) @RequestParam + String pluginName) { + return Result.success(tableSchemaService.getColumnProjection(pluginName)); + } +} diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/entity/ProcessTaskRelation.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/entity/ProcessTaskRelation.java index 32817e7cb..be19c9b0d 100644 --- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/entity/ProcessTaskRelation.java +++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/entity/ProcessTaskRelation.java @@ -44,8 +44,8 @@ public class ProcessTaskRelation { /** process version */ private int processDefinitionVersion; - /** project code */ - private long projectCode; + // /** project code */ + // private long projectCode; /** process code */ private long processDefinitionCode; @@ -93,7 +93,7 @@ public ProcessTaskRelation( Date updateTime) { this.name = name; this.processDefinitionVersion = processDefinitionVersion; - this.projectCode = projectCode; + // this.projectCode = projectCode; this.processDefinitionCode = processDefinitionCode; this.preTaskCode = preTaskCode; this.preTaskVersion = preTaskVersion; diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/entity/ProcessTaskRelationLog.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/entity/ProcessTaskRelationLog.java index c594b708b..e7914e57b 100644 --- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/entity/ProcessTaskRelationLog.java +++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/dal/entity/ProcessTaskRelationLog.java @@ -41,7 +41,7 @@ public ProcessTaskRelationLog(ProcessTaskRelation processTaskRelation) { this.setName(processTaskRelation.getName()); this.setProcessDefinitionCode(processTaskRelation.getProcessDefinitionCode()); this.setProcessDefinitionVersion(processTaskRelation.getProcessDefinitionVersion()); - this.setProjectCode(processTaskRelation.getProjectCode()); + // this.setProjectCode(processTaskRelation.getProjectCode()); this.setPreTaskCode(processTaskRelation.getPreTaskCode()); this.setPreTaskVersion(processTaskRelation.getPreTaskVersion()); this.setPostTaskCode(processTaskRelation.getPostTaskCode()); diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/domain/request/datasource/DatasourceCheckReq.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/domain/request/datasource/DatasourceCheckReq.java index b8367c455..e99770199 100644 --- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/domain/request/datasource/DatasourceCheckReq.java +++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/domain/request/datasource/DatasourceCheckReq.java @@ -19,8 +19,10 @@ import lombok.Data; +import java.util.Map; + @Data public class DatasourceCheckReq { private String pluginName; - private String datasourceConfig; + private Map datasourceConfig; } diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/domain/request/job/JobReq.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/domain/request/job/JobReq.java index b6bbe5ced..407fa91e5 100644 --- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/domain/request/job/JobReq.java +++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/domain/request/job/JobReq.java @@ -34,7 +34,4 @@ public class JobReq { @ApiModelProperty(value = "job type", dataType = "String") private BusinessMode jobType; - - @ApiModelProperty(value = "project code", dataType = "Long") - private Long projectCode; } diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/interceptor/AuthenticationInterceptor.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/interceptor/AuthenticationInterceptor.java index 23948a955..43cb15cf6 100644 --- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/interceptor/AuthenticationInterceptor.java +++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/interceptor/AuthenticationInterceptor.java @@ -99,6 +99,7 @@ public boolean preHandle( // user.setStatus((Byte) map.get("status")); // user.setType((Byte) map.get("type")); request.setAttribute(Constants.SESSION_USER, user); + request.setAttribute("userId", userId); return true; } diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/ConnectorServiceImpl.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/ConnectorServiceImpl.java index a7c06683c..92c60e72a 100644 --- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/ConnectorServiceImpl.java +++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/service/impl/ConnectorServiceImpl.java @@ -132,7 +132,8 @@ public List listTransformsForJob(Long jobId) { || pluginName.equals("FilterRowKind") || pluginName.equals("Replace") || pluginName.equals("Copy") - || pluginName.equals("Split"); + || pluginName.equals("MultiFieldSplit") + || pluginName.equals("Sql"); }) .collect(Collectors.toList()); } diff --git a/seatunnel-server/seatunnel-app/src/main/resources/org/apache/seatunnel/app/dal/mapper/JobMapper.xml b/seatunnel-server/seatunnel-app/src/main/resources/org/apache/seatunnel/app/dal/mapper/JobMapper.xml index 6e7690f7a..73d91a2cc 100644 --- a/seatunnel-server/seatunnel-app/src/main/resources/org/apache/seatunnel/app/dal/mapper/JobMapper.xml +++ b/seatunnel-server/seatunnel-app/src/main/resources/org/apache/seatunnel/app/dal/mapper/JobMapper.xml @@ -27,12 +27,12 @@ id - , `name`, `description`,`job_type`, project_code, create_user_id, update_user_id + , `name`, `description`,`job_type`, create_user_id, update_user_id insert into `t_st_job_definition` () values (#{id,jdbcType=BIGINT}, #{name,jdbcType=VARCHAR}, - #{description,jdbcType=VARCHAR},#{jobType,jdbcType=VARCHAR},#{projectCode,jdbcType=BIGINT}, + #{description,jdbcType=VARCHAR},#{jobType,jdbcType=VARCHAR}, #{createUserId,jdbcType=BIGINT}, #{updateUserId,jdbcType=BIGINT})