├── .gitignore ├── CONTRIBUTION.md ├── CONTRIBUTION_CN.md ├── Dockerfile ├── Dockerfile.editor ├── Dockerfile.frontend ├── LICENSE ├── README.md ├── README_ZH.md ├── cmd └── server │ ├── server.go │ ├── with_ai.go │ ├── with_ci.go │ ├── with_etl.go │ ├── with_extend.go │ └── with_iot.go ├── config.conf ├── config ├── config.go └── logger │ └── logger.go ├── docker-compose.yml ├── docs └── imgs │ ├── architecture.png │ ├── architecture_zh.png │ ├── endpoint │ └── endpoint.png │ ├── logo.png │ ├── qq.png │ ├── rulechain │ ├── demo.png │ ├── img_1.png │ ├── img_2.png │ ├── img_3.png │ └── img_4.png │ └── wechat.png ├── go.mod ├── go.sum ├── internal ├── constants │ ├── constants.go │ └── errors.go ├── controller │ ├── base.go │ ├── locale.go │ ├── marketplace.go │ ├── mcp.go │ ├── node.go │ ├── rule.go │ ├── run_log.go │ └── share_node.go ├── dao │ ├── component.go │ ├── file_storage.go │ ├── rule.go │ ├── run_log.go │ ├── setting.go │ ├── share_node.go │ └── user.go ├── model │ ├── event.go │ ├── setting.go │ ├── user.go │ ├── variable.go │ └── workflow.go ├── router │ ├── http.go │ └── websocket.go ├── service │ ├── component.go │ ├── debug_data.go │ ├── engine.go │ ├── mcp.go │ ├── run_log.go │ ├── setup.go │ ├── share_node.go │ └── user.go └── utils │ ├── array.go │ └── file │ └── file.go ├── node_pool.json └── ui ├── .gitignore ├── .husky └── pre-commit ├── .prettierrc ├── LICENSE ├── README.md ├── index.html ├── jsconfig.json ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── config.js ├── favicon.ico └── vite.svg ├── src ├── api │ ├── index.js │ └── module │ │ ├── components.js │ │ ├── locales.js │ │ ├── login.js │ │ ├── logs.js │ │ └── rules.js ├── app.vue ├── assets │ ├── assistant.svg │ ├── nodes-graph.svg │ ├── split-screen.svg │ └── vue.svg ├── components │ ├── config-form │ │ ├── cache.js │ │ ├── config-form-item.vue │ │ ├── config-form.vue │ │ └── custom-form-item │ │ │ ├── array-form-item │ │ │ └── index.vue │ │ │ ├── default-endpoints-routers-form-item │ │ │ └── index.vue │ │ │ ├── map-form-item │ │ │ └── index.vue │ │ │ ├── script-form-item │ │ │ └── index.vue │ │ │ ├── select-form-item │ │ │ └── index.vue │ │ │ ├── simple-routers-form-item │ │ │ └── index.vue │ │ │ ├── slider-form-item │ │ │ └── index.vue │ │ │ ├── string-form-item │ │ │ └── index.vue │ │ │ ├── struct-form-item │ │ │ └── index.vue │ │ │ ├── switch-form-item │ │ │ └── index.vue │ │ │ ├── switch-node-form-item │ │ │ ├── and-item.vue │ │ │ ├── div-input.vue │ │ │ ├── index.vue │ │ │ ├── input-item.vue │ │ │ └── item.vue │ │ │ ├── table-form-item │ │ │ └── index.vue │ │ │ └── textarea-form-item │ │ │ └── index.vue │ └── json-editor │ │ └── json-editor.vue ├── constant │ ├── node-component-data.js │ └── workflow.js ├── layout │ ├── components │ │ └── default │ │ │ ├── main.vue │ │ │ └── menu.vue │ └── default-layout.vue ├── main.js ├── pages │ ├── login │ │ └── login.vue │ ├── share-node-list │ │ └── share-node-list.vue │ ├── workflow-list │ │ ├── app-card.vue │ │ ├── create-app-modal.vue │ │ ├── import-dialog.vue │ │ └── workflow-list.vue │ └── workflow │ │ ├── app-design-new │ │ ├── app-design-new.vue │ │ └── tool-buttons.vue │ │ ├── app-design │ │ ├── app-design.vue │ │ ├── constant.js │ │ ├── flow-edge │ │ │ └── custom-bezier.js │ │ ├── flow-node │ │ │ ├── comment │ │ │ │ ├── comment.js │ │ │ │ └── comment.vue │ │ │ ├── components │ │ │ │ ├── default-endpoint-node.vue │ │ │ │ ├── default-node.vue │ │ │ │ └── node-title.vue │ │ │ ├── default │ │ │ │ ├── default.js │ │ │ │ └── default.vue │ │ │ ├── dynamic-node │ │ │ │ ├── dynamic-node.js │ │ │ │ └── dynamic-node.vue │ │ │ ├── endpoint-node │ │ │ │ ├── endpoint-node.js │ │ │ │ └── endpoint-node.vue │ │ │ ├── flow │ │ │ │ ├── flow.js │ │ │ │ └── flow.vue │ │ │ ├── for │ │ │ │ ├── for.js │ │ │ │ └── for.vue │ │ │ ├── fork │ │ │ │ ├── fork.js │ │ │ │ └── fork.vue │ │ │ ├── group-action │ │ │ │ ├── group-action.js │ │ │ │ └── group-action.vue │ │ │ ├── group-filter │ │ │ │ ├── group-filter.js │ │ │ │ └── group-filter.vue │ │ │ ├── index.js │ │ │ ├── js-switch │ │ │ │ ├── js-switch.js │ │ │ │ └── js-switch.vue │ │ │ ├── msg-type-switch │ │ │ │ ├── msg-type-switch.js │ │ │ │ └── msg-type-switch.vue │ │ │ ├── ref │ │ │ │ ├── ref.js │ │ │ │ └── ref.vue │ │ │ ├── start │ │ │ │ ├── start.js │ │ │ │ └── start.vue │ │ │ └── switch │ │ │ │ ├── and-item.vue │ │ │ │ ├── case-item.vue │ │ │ │ ├── switch.js │ │ │ │ └── switch.vue │ │ ├── flow-view.vue │ │ ├── node-form │ │ │ ├── base-info.vue │ │ │ ├── log.vue │ │ │ ├── next-node │ │ │ │ ├── next-node.vue │ │ │ │ ├── node-item.vue │ │ │ │ └── node.vue │ │ │ └── node-form.vue │ │ ├── node-list │ │ │ └── node-list.vue │ │ ├── run-drawer │ │ │ ├── json-editor.vue │ │ │ └── run-drawer.vue │ │ └── utils.js │ │ ├── app-manage │ │ ├── app-integration.vue │ │ ├── app-manage.vue │ │ ├── base-info.vue │ │ └── variable.vue │ │ ├── app-source-code │ │ └── app-source-code.vue │ │ ├── app-split-screen │ │ └── app-split-screen.vue │ │ ├── chat-list-view │ │ ├── ai-chat-drawer.vue │ │ └── chat-list-view.vue │ │ └── workflow.vue ├── router │ └── index.js ├── store │ ├── index.js │ └── module │ │ └── user.js ├── style │ ├── element-plus.css │ ├── logic-flow.css │ └── tailwind.css └── utils │ ├── event-bus.js │ ├── flow-utils.js │ ├── request.js │ └── sessionstorage.js ├── tailwind.config.js └── vite.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | # JetBrains template 2 | .idea 3 | *.iml 4 | out/ 5 | 6 | # File-based project format 7 | *.iws 8 | 9 | # JIRA plugin 10 | atlassian-ide-plugin.xml 11 | 12 | ### Go template 13 | # Binaries for programs and plugins 14 | *.exe 15 | *.exe~ 16 | *.dll 17 | *.so 18 | *.dylib 19 | 20 | # Test binary, built with `go test -c` 21 | *.test 22 | 23 | # Output of the go coverage tool, specifically when used with LiteIDE 24 | *.out 25 | 26 | # Dependency directories (remove the comment below to include it) 27 | vendor/ 28 | 29 | ### macOS template 30 | .DS_Store 31 | .AppleDouble 32 | .LSOverride 33 | 34 | # Thumbnails 35 | ._* 36 | 37 | # VSCode 38 | .vscode 39 | ui/.vscode 40 | 41 | # log 42 | *.log 43 | 44 | # coverage file 45 | coverage.html 46 | 47 | data/ 48 | editor/ 49 | cmd/server/rulego-server 50 | 51 | volumes/ 52 | -------------------------------------------------------------------------------- /CONTRIBUTION_CN.md: -------------------------------------------------------------------------------- 1 | # 贡献指南 2 | 3 | **我们的使命是构建适用于各种场景需求的新一代规则引擎,并致力于培育充满活力的组件生态系统,引领软件开发进入全新的创新范式。** 4 | 5 | 欢迎加入rulego社区!我们是一个开放和包容的团队,致力于推动规则引擎技术的发展。我们相信,通过社区的力量,我们可以一起实现这一愿景。 6 | 7 | ## 2. 行为守则 8 | 9 | 我们致力于提供一个开放和包容的社区环境。请在参与本项目时遵守我们的[行为准则] 。 10 | 11 | ## 3. 提交Issue/处理Issue任务 12 | 13 | - **提交Issue**:在[Github Issues](https://github.com/rulego/rulego-server/issues) 中提交Bug报告、功能请求或建议。 14 | - **参与讨论**:加入现有Issue的讨论,分享你的想法和反馈。 15 | - **领取Issue**:如果你愿意处理某个Issue,请在评论中使用`/assign @yourself`将其分配给自己。 16 | 17 | ## 4. 贡献源码 18 | 19 | ### 4.1 提交拉取请求详细步骤 20 | 21 | 1. **检查现有PR**:在[Github Pull Requests](https://github.com/rulego/rulego-server/pulls) 中搜索相关PR,避免重复工作。 22 | 2. **讨论设计**:在提交PR前,讨论你的设计可以帮助确保你的工作符合项目需求。 23 | 3. **签署DCO**:使用`git commit -s`确保每次提交都签署了[DCO](https://developercertificate.org) 。 24 | 4. **Fork仓库**:在Github上Fork并Clone rulego/rulego仓库。 25 | 5. **创建分支**:`git checkout -b my-feature-branch main`。 26 | 6. **编写代码和测试**:添加你的代码和相应的测试用例。 27 | 7. **格式化代码**:使用`gofmt -s -w .`命令格式化代码。 28 | 8. **提交代码**:使用`git add .`和`git commit -s -m "fix: add new feature"`提交更改。 29 | - feat: feature的缩写, 新的功能或特性 30 | - fix: bug的修复 31 | - docs: 文档修改 32 | - style: 格式修改. 比如改变缩进, 空格, 删除多余的空行, 补上漏掉的分号. 总之, 就是不影响代码含义和功能的修改 33 | - refactor: 代码重构. 一些不算修复bug也没有加入新功能的代码修改 34 | - perf: performance的缩写, 提升代码性能 35 | - test: 测试文件的修改 36 | - chore: 其他的小改动. 一般为仅仅一两行的改动, 或者连续几次提交的小改动属于这种 37 | 38 | 更多详细信息,您可以参考[约定式提交](https://www.conventionalcommits.org/zh-hans/v1.0.0/) 。 39 | 9. **推送代码**:在提交代码之前,请先执行 rebase 操作,以确保您的分支与上游仓库的主分支保持同步。 40 | - `git fetch --all` 41 | - `git rebase upstream/main` 42 | - 将您的分支推送到Github `git push origin my-fix-branch` 43 | 11. **创建PR**:在Github上创建PR,并确保填写详细的PR描述。 44 | 45 | ### 4.2 编译源码 46 | 47 | #### 4.2.1 支持平台 48 | - 所有支持Go语言的操作系统。 49 | 50 | #### 4.2.2 编译环境信息 51 | - Go版本:v1.23+ 52 | - Git:[下载Git](https://git-scm.com/downloads) 53 | 54 | #### 4.2.3 GO环境变量设置(可选) 55 | ```bash 56 | # 设置GOPATH(可自定义目录) 57 | export GOPATH=$HOME/gocodez 58 | export GOPROXY=https://goproxy.cn,direct 59 | export GO111MODULE=on 60 | export GONOSUMDB=* 61 | export GOSUMDB=off 62 | ``` 63 | 64 | #### 4.2.4 下载源码编译 65 | ```bash 66 | git clone git@github.com:/rulego.git 67 | # 编译 68 | go build . 69 | # 或者 加入扩展组件编译 70 | go build -tags "with_extend,with_ai,with_ci,with_iot,with_etl" . 71 | ``` 72 | ### 4.3 启动服务 73 | ```bash 74 | ./server -c ./config.config 75 | ``` 76 | 77 | ## 5. 参与社区其他贡献 78 | 79 | ### 5.1 贡献扩展组件 80 | - [rulego](https://github.com/rulego/rulego) :rulego。 81 | - [rulego-components](https://github.com/rulego/rulego-components) :其他扩展组件。 82 | - [rulego-components-ai](https://github.com/rulego/rulego-components-ai) :AI场景组件。 83 | - [rulego-components-ci](https://github.com/rulego/rulego-components-ci) :CI/CD场景组件。 84 | - [rulego-components-iot](https://github.com/rulego/rulego-components-iot) :IoT场景组件。 85 | - [rulego-components-etl](https://github.com/rulego/rulego-components-etl) :ETL场景组件。 86 | - [streamsql](https://github.com/rulego/streamsql) : 增强边缘计算聚合计算能力的子项目。 87 | - [rulego-marketplace](https://github.com/rulego/rulego-marketplace) : 组件市场。 88 | 89 | ### 5.2 贡献文档 90 | - 官网文档:[rulego-doc](https://github.com/rulego/rulego-doc) 91 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # 第一阶段: 构建Go后端 2 | FROM golang:1.24.1 as backend-builder 3 | WORKDIR /root 4 | ADD . /root 5 | 6 | RUN mkdir -p bin/ && GOPROXY="https://goproxy.cn,direct" go build -ldflags '-w -s -extldflags "-static" -X main.Version=$(VERSION)' -tags "with_extend,with_ai,with_ci,with_iot,with_etl,musl" -o ./bin/ ./... 7 | 8 | # RUN go mod download 9 | 10 | # 第三阶段: 最终镜像 11 | FROM alpine:3.21.3 12 | WORKDIR /app 13 | 14 | RUN mkdir -p /app/logs 15 | # 从后端构建阶段复制二进制文件 16 | COPY --from=backend-builder /root/bin /app 17 | COPY --from=backend-builder /root/config.conf /app/config.conf 18 | # 暴露服务端口 19 | EXPOSE 9091 20 | 21 | # 启动服务 22 | CMD ["./server"] -------------------------------------------------------------------------------- /Dockerfile.editor: -------------------------------------------------------------------------------- 1 | 2 | # 使用 Nginx 托管静态文件 3 | FROM nginx:latest 4 | 5 | # 删除默认的 Nginx 配置文件 6 | RUN rm -rf /usr/share/nginx/html/* 7 | 8 | 9 | COPY editor /usr/share/nginx/html/ 10 | # 将前端构建结果复制到 Nginx 的默认目录 11 | RUN sed -i 's/127.0.0.1:9090/127.0.0.1:9099/g' "/usr/share/nginx/html/config/config.js" 12 | # 暴露端口 13 | EXPOSE 80 14 | 15 | # 启动 Nginx 16 | CMD ["nginx", "-g", "daemon off;"] -------------------------------------------------------------------------------- /Dockerfile.frontend: -------------------------------------------------------------------------------- 1 | # 第一阶段: 构建前端 2 | FROM node:20.11.1-alpine AS builder 3 | WORKDIR /app/frontend 4 | COPY ui/package*.json ./ 5 | RUN npm install 6 | COPY ui/ . 7 | # 定义要修改的文件路径 8 | # 使用 sed 命令替换 IP 地址和端口 9 | RUN sed -i 's/8.134.32.225:9090/127.0.0.1:9099/g' "public/config.js" 10 | 11 | 12 | RUN npm run build 13 | 14 | # 第二阶段:使用 Nginx 托管静态文件 15 | FROM nginx:latest 16 | 17 | # 删除默认的 Nginx 配置文件 18 | RUN rm -rf /usr/share/nginx/html/* 19 | 20 | # 将前端构建结果复制到 Nginx 的默认目录 21 | COPY --from=builder /app/frontend/dist /usr/share/nginx/html 22 | 23 | # 暴露端口 24 | EXPOSE 80 25 | 26 | # 启动 Nginx 27 | CMD ["nginx", "-g", "daemon off;"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | # 开源许可 2 | 3 | RuleGo-Server 采用修改版的 Apache License 2.0 许可证,附加以下条件: 4 | 5 | 1. 商业用途:RuleGo-Server 可以用于商业目的,包括作为其他应用程序的后端服务或作为企业应用开发平台。如果满足以下条件,则需从版权所有者处获得商业许可: 6 | a. 多租户服务:除非获得 RuleGo-Server 的明确书面授权,否则不得使用 RuleGo-Server 源代码运营多租户环境。 7 | - 租户定义:在 RuleGo-Server 的上下文中,一个租户对应一个工作区。工作区为每个租户的数据和配置提供独立的隔离区域。 8 | b. LOGO和版权信息:在使用 RuleGo-Server 前端的过程中,不得删除或修改 RuleGo-Server 控制台或应用程序中的 LOGO 或版权信息。此限制不适用于不涉及 RuleGo-Server 前端的使用。 9 | - 前端定义:在本许可证中,RuleGo-Server 的“前端”包括从原始源代码运行 RuleGo-Server 时位于 `ui/` 目录中的所有组件,或通过 Docker 运行 RuleGo-Server 时的"ui/"镜像。 10 | 11 | 2. **作为贡献者,您应同意以下内容**: 12 | a. 所有者可以根据需要调整开源协议,使其更加严格或宽松。 13 | b. 您贡献的代码可以用于商业目的,包括但不限于其云业务运营。 14 | 15 | 3. **其他条款**: 16 | a. 本协议条款的解释权归 RuleGo-Server 开发者所有。 17 | 18 | 如有任何问题或需申请商业授权,请联系 RuleGo-Server 开发团队。 19 | 20 | 除上述特定条件外,其他所有权利和限制均遵循 Apache License 2.0。关于 Apache License 2.0 的详细信息,请访问 http://www.apache.org/licenses/LICENSE-2.0。 21 | 22 | --- 23 | 24 | # Open Source License 25 | 26 | RuleGo-Server is licensed under a modified version of the Apache License 2.0, with the following additional conditions: 27 | 28 | 1. RuleGo-Server may be utilized commercially, including as a backend service for other applications or as an application development platform for enterprises. Should the conditions below be met, a commercial license must be obtained from the producer: 29 | 30 | a. Multi-tenant service: Unless explicitly authorized by RuleGo-Server in writing, you may not use the RuleGo-Server source code to operate a multi-tenant environment. 31 | - Tenant Definition: Within the context of RuleGo-Server, one tenant corresponds to one workspace. The workspace provides a separated area for each tenant's data and configurations. 32 | 33 | b. LOGO and copyright information: In the process of using RuleGo-Server's frontend, you may not remove or moRuleGo-Server the LOGO or copyright information in the RuleGo-Server console or applications. This restriction is inapplicable to uses of RuleGo-Server that do not involve its frontend. 34 | - Frontend Definition: For the purposes of this license, the "frontend" of RuleGo-Server includes all components located in the `web/` directory when running RuleGo-Server from the raw source code, or the "web" image when running RuleGo-Server with Docker. 35 | 36 | 2. As a contributor, you should agree that: 37 | 38 | a. The producer can adjust the open-source agreement to be more strict or relaxed as deemed necessary. 39 | b. Your contributed code may be used for commercial purposes, including but not limited to its cloud business operations. 40 | 41 | 3. Other Terms: 42 | a. The interpretation of the terms of this agreement shall be the prerogative of the RuleGo-Server development team. 43 | 44 | If you have any questions or need to apply for a commercial license, please contact the RuleGo-Server development team. 45 | Apart from the specific conditions mentioned above, all other rights and restrictions follow the Apache License 2.0. Detailed information about the Apache License 2.0 can be found at http://www.apache.org/licenses/LICENSE-2.0 . 46 | -------------------------------------------------------------------------------- /cmd/server/with_ai.go: -------------------------------------------------------------------------------- 1 | //go:build with_ai 2 | 3 | package main 4 | 5 | import ( 6 | //注册AI扩展组件库 7 | // 使用`go build -tags with_ai .`把扩展组件编译到运行文件 8 | _ "github.com/rulego/rulego-components-ai/ai/action" 9 | _ "github.com/rulego/rulego-components-ai/ai/endpoint" 10 | ) 11 | -------------------------------------------------------------------------------- /cmd/server/with_ci.go: -------------------------------------------------------------------------------- 1 | //go:build with_ci 2 | 3 | package main 4 | 5 | import ( 6 | // 注册CI/CD扩展组件库 7 | // 使用`go build -tags with_ci .`把CI/CD扩展组件编译到运行文件 8 | _ "github.com/rulego/rulego-components-ci/ci/action" 9 | ) 10 | -------------------------------------------------------------------------------- /cmd/server/with_etl.go: -------------------------------------------------------------------------------- 1 | //go:build with_etl 2 | 3 | package main 4 | 5 | import ( 6 | // 注册ETL扩展组件库 7 | // 使用`go build -tags with_etl .`把ETL扩展组件编译到运行文件 8 | _ "github.com/rulego/rulego-components-etl/endpoint/mysql_cdc" 9 | ) 10 | -------------------------------------------------------------------------------- /cmd/server/with_extend.go: -------------------------------------------------------------------------------- 1 | //go:build with_extend 2 | 3 | package main 4 | 5 | import ( 6 | // 注册扩展组件库 7 | // 使用`go build -tags with_extend .`把扩展组件编译到运行文件 8 | _ "github.com/rulego/rulego-components/endpoint/beanstalkd" 9 | _ "github.com/rulego/rulego-components/endpoint/grpc_stream" 10 | _ "github.com/rulego/rulego-components/endpoint/kafka" 11 | _ "github.com/rulego/rulego-components/endpoint/nats" 12 | _ "github.com/rulego/rulego-components/endpoint/rabbitmq" 13 | _ "github.com/rulego/rulego-components/endpoint/redis" 14 | _ "github.com/rulego/rulego-components/endpoint/redis_stream" 15 | _ "github.com/rulego/rulego-components/endpoint/wukongim" 16 | _ "github.com/rulego/rulego-components/external/beanstalkd" 17 | _ "github.com/rulego/rulego-components/external/grpc" //编译后文件大约增加7M 18 | _ "github.com/rulego/rulego-components/external/kafka" 19 | _ "github.com/rulego/rulego-components/external/mongodb" 20 | _ "github.com/rulego/rulego-components/external/nats" 21 | _ "github.com/rulego/rulego-components/external/opengemini" 22 | _ "github.com/rulego/rulego-components/external/otel" 23 | _ "github.com/rulego/rulego-components/external/rabbitmq" 24 | _ "github.com/rulego/rulego-components/external/redis" 25 | _ "github.com/rulego/rulego-components/external/wukongim" 26 | _ "github.com/rulego/rulego-components/filter" 27 | _ "github.com/rulego/rulego-components/transform" 28 | ) 29 | -------------------------------------------------------------------------------- /cmd/server/with_iot.go: -------------------------------------------------------------------------------- 1 | //go:build with_iot 2 | 3 | package main 4 | 5 | import ( 6 | // 注册扩展组件库 7 | // 使用`go build -tags with_iot .`把扩展组件编译到运行文件 8 | _ "github.com/rulego/rulego-components-iot/endpoint/opcua" 9 | _ "github.com/rulego/rulego-components-iot/external/modbus" 10 | _ "github.com/rulego/rulego-components-iot/external/opcua" 11 | ) 12 | -------------------------------------------------------------------------------- /config.conf: -------------------------------------------------------------------------------- 1 | # data dir 2 | data_dir = ./data 3 | # cmd node white list 4 | cmd_white_list = cp,scp,mvn,npm,yarn,git,make,cmake,docker,kubectl,helm,ansible,puppet,pytest,python,python3,pip,go,java,dotnet,gcc,g++,ctest 5 | # load lua libs 6 | load_lua_libs = true 7 | # http server 8 | server = :9090 9 | # default username 10 | default_username = admin 11 | # log node debug data to logger file 12 | debug = true 13 | # max node log size 14 | max_node_log_size=40 15 | # resource mapping for example:/ui/*filepath=/home/demo/dist,/images/*filepath=/home/demo/dist/images 16 | resource_mapping = /editor/*filepath=./editor,/images/*filepath=./editor/images 17 | # Node pool file 18 | #node_pool_file=./node_pool.json 19 | # save run log to file 20 | save_run_log = false 21 | # script max execution time 22 | script_max_execution_time = 5000 23 | # api是否开启jwt认证,如果关闭,则以默认用户(admin)身份操作 24 | require_auth = false 25 | # jwt secret key 26 | jwt_secret_key = r6G7qZ8xk9P0y1Q2w3E4r5T6y7U8i9O0pL7z8x9CvBnM3k2l1 27 | # jwt expire time 28 | jwt_expire_time = 43200000 29 | # jwt issuer 30 | jwt_issuer = rulego.cc 31 | # component marketplace base url 32 | marketplace_base_url = http://8.134.32.225:9090/api/v1 33 | # Set the default HTTP server as a shared node 34 | share_http_server = true 35 | 36 | # mcp server config 37 | [mcp] 38 | # Whether to enable the MCP service 39 | enable = true 40 | # Whether to use the component as an MCP tool 41 | load_components_as_tool = false 42 | # Whether to use the rule chain as an MCP tool 43 | load_chains_as_tool = false 44 | # Whether to add a rule chain api tool 45 | load_apis_as_tool = true 46 | # Exclude component list 47 | exclude_components = comment,iterator,delay,groupAction,ref,fork,join,for,*Filter 48 | # Exclude rule chain list 49 | exclude_chains = 50 | 51 | # pprof 52 | [pprof] 53 | enable = false 54 | addr = 0.0.0.0:6060 55 | 56 | # Global custom configuration, components can take values through the ${global.xxx} 57 | [global] 58 | sqlDriver = mysql 59 | sqlDsn = root:root@tcp(127.0.0.1:3306)/test 60 | 61 | # users list 62 | # format: username = password[,apiKey] 63 | # If apiKey is configured, the caller can access other interfaces directly using the apiKey without logging in. 64 | [users] 65 | admin = admin,2af255ea5618467d914c67a8beeca31d 66 | user01 = user01,2af255ea5618467d914c67a8beeca51c -------------------------------------------------------------------------------- /config/logger/logger.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import "log" 4 | 5 | var Logger *log.Logger 6 | 7 | func Set(logger *log.Logger) { 8 | Logger = logger 9 | } 10 | 11 | func Get() *log.Logger { 12 | return Logger 13 | } 14 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | # editor: 4 | # build: 5 | # context: . 6 | # dockerfile: Dockerfile.editor 7 | # container_name: editor 8 | # ports: 9 | # - "9997:80" 10 | # restart: always 11 | frontend: 12 | build: 13 | context: . 14 | dockerfile: Dockerfile.frontend 15 | container_name: frontend 16 | ports: 17 | - "9998:80" 18 | restart: always 19 | workflow: 20 | build: 21 | context: . 22 | dockerfile: Dockerfile 23 | container_name: workflow 24 | ports: 25 | - "9099:9091" 26 | volumes: 27 | - ./volumes/rulego_data:/app/data 28 | - ./volumes/rulego_logs:/app/logs 29 | environment: 30 | - RULEGO_SERVER=:9091 31 | - RULEGO_MCP_LOAD_COMPONENTS_AS_TOOL=false 32 | - RULEGO_DEBUG=true 33 | - RULEGO_LOG_FILE=./logs/rulego.log 34 | - RULEGO_SAVE_RUN_LOG=true 35 | restart: always 36 | networks: 37 | rulego: 38 | ipam: 39 | config: 40 | - subnet: 192.100.0.0/16 41 | -------------------------------------------------------------------------------- /docs/imgs/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rulego/rulego-server/07f42edaab103e25702012713e8ffb5f8dc89c79/docs/imgs/architecture.png -------------------------------------------------------------------------------- /docs/imgs/architecture_zh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rulego/rulego-server/07f42edaab103e25702012713e8ffb5f8dc89c79/docs/imgs/architecture_zh.png -------------------------------------------------------------------------------- /docs/imgs/endpoint/endpoint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rulego/rulego-server/07f42edaab103e25702012713e8ffb5f8dc89c79/docs/imgs/endpoint/endpoint.png -------------------------------------------------------------------------------- /docs/imgs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rulego/rulego-server/07f42edaab103e25702012713e8ffb5f8dc89c79/docs/imgs/logo.png -------------------------------------------------------------------------------- /docs/imgs/qq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rulego/rulego-server/07f42edaab103e25702012713e8ffb5f8dc89c79/docs/imgs/qq.png -------------------------------------------------------------------------------- /docs/imgs/rulechain/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rulego/rulego-server/07f42edaab103e25702012713e8ffb5f8dc89c79/docs/imgs/rulechain/demo.png -------------------------------------------------------------------------------- /docs/imgs/rulechain/img_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rulego/rulego-server/07f42edaab103e25702012713e8ffb5f8dc89c79/docs/imgs/rulechain/img_1.png -------------------------------------------------------------------------------- /docs/imgs/rulechain/img_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rulego/rulego-server/07f42edaab103e25702012713e8ffb5f8dc89c79/docs/imgs/rulechain/img_2.png -------------------------------------------------------------------------------- /docs/imgs/rulechain/img_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rulego/rulego-server/07f42edaab103e25702012713e8ffb5f8dc89c79/docs/imgs/rulechain/img_3.png -------------------------------------------------------------------------------- /docs/imgs/rulechain/img_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rulego/rulego-server/07f42edaab103e25702012713e8ffb5f8dc89c79/docs/imgs/rulechain/img_4.png -------------------------------------------------------------------------------- /docs/imgs/wechat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rulego/rulego-server/07f42edaab103e25702012713e8ffb5f8dc89c79/docs/imgs/wechat.png -------------------------------------------------------------------------------- /internal/constants/constants.go: -------------------------------------------------------------------------------- 1 | package constants 2 | 3 | const ( 4 | // DirWorkflows 工作流目录 5 | DirWorkflows = "workflows" 6 | DirLocales = "locales" 7 | DirWorkflowsRun = "runs" 8 | DirWorkflowsRule = "rules" 9 | DirWorkflowsComponent = "components" 10 | // DirWorkflowsShareNodes 共享节点目录 11 | DirWorkflowsShareNodes = "sharenodes" 12 | // DirShareNode 共享普通节点目录 13 | DirShareNode = "node" 14 | // DirShareEndpoint 共享端点目录 15 | DirShareEndpoint = "endpoint" 16 | // FileNameIndex 索引文件名 17 | FileNameIndex = "index" 18 | ) 19 | 20 | const ( 21 | // TypeShareNode 共享普通节点类型 22 | TypeShareNode = DirShareNode 23 | // TypeShareEndpoint 共享端点类型 24 | TypeShareEndpoint = DirShareEndpoint 25 | ) 26 | 27 | // GetShareNodesDir 获取共享节点目录 28 | func GetShareNodesDir() []string { 29 | return []string{ 30 | DirShareNode, 31 | DirShareEndpoint, 32 | } 33 | } 34 | 35 | const ( 36 | KeyMsgType = "msgType" 37 | KeyMsgId = "msgId" 38 | KeyChainId = "chainId" 39 | KeyNodeId = "nodeId" 40 | KeyUsername = "username" 41 | KeyClientId = "clientId" 42 | KeyVarType = "varType" 43 | KeySize = "size" 44 | KeyPage = "page" 45 | KeyId = "id" 46 | KeyKeywords = "keywords" 47 | KeyType = "type" 48 | KeyLang = "lang" 49 | KeyRoot = "root" 50 | KeyDisabled = "disabled" 51 | KeyWebhookSecret = "webhookSecret" 52 | KeyIntegrationType = "integrationType" 53 | // KeyWorkDir 工作目录 54 | KeyWorkDir = "workDir" 55 | // KeyDefaultIntegrationChainId 应用集成规则链ID 56 | KeyDefaultIntegrationChainId = "$event_bus" 57 | KeyUpdateTime = "updateTime" 58 | KeyHeadersToMetadata = "headersToMetadata" 59 | KeyInMessage = "inMessage" 60 | KeyBody = "body" 61 | ) 62 | 63 | const ( 64 | // OperateDeploy 部署 65 | OperateDeploy = "start" 66 | // OperateUndeploy 下架 67 | OperateUndeploy = "stop" 68 | // OperateSetToMain 设置成主规则链 69 | OperateSetToMain = "set-to-main" 70 | ) 71 | const ( 72 | // SettingKeyLatestChainId 最新打开的规则链 73 | SettingKeyLatestChainId = "latestChainId" 74 | // SettingKeyMainChainId 主规则链,server所有事件都会发送至此 75 | SettingKeyMainChainId = "mainChainId" 76 | ) 77 | 78 | const ( 79 | UserSuper = "super" 80 | UserAdmin = "admin" 81 | ) 82 | const ( 83 | RuleChainFileSuffix = ".json" 84 | ) 85 | const ( 86 | // AddiKeyMessage 记录规则链加载错误,扩展字段错误信息Key 87 | AddiKeyMessage = "message" 88 | ) 89 | const ( 90 | KeyAuthorization = "Authorization" 91 | KeyBearer = "Bearer " 92 | ) 93 | 94 | // LoadLuaLibs 加载lua库key 95 | const LoadLuaLibs = "load_lua_libs" 96 | 97 | // const ( 98 | // DefaultPoolDef = ` 99 | // { 100 | // "ruleChain": { 101 | // "id": "$default_node_pool", 102 | // "name": "全局共享节点池" 103 | // }, 104 | // "metadata": { 105 | // "endpoints": [ 106 | // { 107 | // "id": "core_endpoint_http", 108 | // "type": "endpoint/http", 109 | // "name": "http:9090", 110 | // "configuration": { 111 | // "allowCors": true, 112 | // "server": ":9090" 113 | // } 114 | // } 115 | // ] 116 | // } 117 | // } 118 | // ` 119 | // ) 120 | -------------------------------------------------------------------------------- /internal/constants/errors.go: -------------------------------------------------------------------------------- 1 | package constants 2 | 3 | import "errors" 4 | 5 | var ( 6 | ErrNotFound = errors.New("not found") 7 | ErrUsernameEmpty = errors.New("username cannot empty") 8 | ErrWorkflowIdEmpty = errors.New("workflowId cannot empty") 9 | ) 10 | -------------------------------------------------------------------------------- /internal/controller/locale.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "github.com/rulego/rulego-server/config" 5 | "github.com/rulego/rulego-server/internal/constants" 6 | endpointApi "github.com/rulego/rulego/api/types/endpoint" 7 | "github.com/rulego/rulego/endpoint" 8 | "github.com/rulego/rulego/utils/fs" 9 | "net/http" 10 | "os" 11 | "path/filepath" 12 | ) 13 | 14 | var Locale = &locale{} 15 | 16 | type locale struct { 17 | } 18 | 19 | func (c *locale) Locales(url string) endpointApi.Router { 20 | return endpoint.NewRouter().From(url).Process(AuthProcess).Process(func(router endpointApi.Router, exchange *endpointApi.Exchange) bool { 21 | lang := exchange.In.GetParam(constants.KeyLang) 22 | if lang == "" { 23 | lang = "zh_cn" 24 | } 25 | path := filepath.Join(config.C.DataDir, constants.DirLocales, lang+".json") 26 | buf, err := os.ReadFile(path) 27 | if err != nil { 28 | exchange.Out.SetBody([]byte("{}")) 29 | } else { 30 | exchange.Out.SetBody(buf) 31 | } 32 | return true 33 | }).End() 34 | } 35 | 36 | func (c *locale) Save(url string) endpointApi.Router { 37 | return endpoint.NewRouter().From(url).Process(AuthProcess).Process(func(router endpointApi.Router, exchange *endpointApi.Exchange) bool { 38 | lang := exchange.In.GetParam(constants.KeyLang) 39 | if lang == "" { 40 | lang = "zh_cn" 41 | } 42 | path := filepath.Join(config.C.DataDir, constants.DirLocales) 43 | _ = fs.CreateDirs(path) 44 | body := exchange.In.Body() 45 | 46 | if err := fs.SaveFile(filepath.Join(path, lang+".json"), body); err != nil { 47 | exchange.Out.SetStatusCode(http.StatusBadRequest) 48 | exchange.Out.SetBody([]byte(err.Error())) 49 | } 50 | return true 51 | }).End() 52 | } 53 | -------------------------------------------------------------------------------- /internal/controller/marketplace.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | endpointApi "github.com/rulego/rulego/api/types/endpoint" 5 | "github.com/rulego/rulego/endpoint" 6 | "strconv" 7 | ) 8 | 9 | // MarketplaceComponents 获取组件市场动态组件 10 | func (c *node) MarketplaceComponents(url string) endpointApi.Router { 11 | return endpoint.NewRouter().From(url).Process(AuthProcess).Process(func(router endpointApi.Router, exchange *endpointApi.Exchange) bool { 12 | checkMyStr := exchange.In.GetParam("checkMy") //是否检查自己的组件 13 | var checkMy bool 14 | if i, err := strconv.ParseBool(checkMyStr); err == nil { 15 | checkMy = i 16 | } 17 | c.getCustomNodeList(true, checkMy, exchange) 18 | return true 19 | }).End() 20 | } 21 | 22 | func (c *rule) MarketplaceChains(url string) endpointApi.Router { 23 | return endpoint.NewRouter().From(url).Process(AuthProcess).Process(func(router endpointApi.Router, exchange *endpointApi.Exchange) bool { 24 | c.list(true, exchange) 25 | return true 26 | }).End() 27 | } 28 | -------------------------------------------------------------------------------- /internal/controller/mcp.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "github.com/rulego/rulego-server/config" 5 | "github.com/rulego/rulego-server/internal/service" 6 | endpointApi "github.com/rulego/rulego/api/types/endpoint" 7 | "github.com/rulego/rulego/endpoint" 8 | "github.com/rulego/rulego/endpoint/rest" 9 | ) 10 | 11 | var MCP = &mcp{} 12 | 13 | type mcp struct { 14 | } 15 | 16 | func (c *mcp) Handler(url string) endpointApi.Router { 17 | return endpoint.NewRouter().From(url).Process(func(router endpointApi.Router, exchange *endpointApi.Exchange) bool { 18 | r := exchange.In.(*rest.RequestMessage) 19 | apiKey := r.Params.ByName("apiKey") 20 | var username string 21 | if apiKey != "" { 22 | username = service.UserServiceImpl.GetUsernameByApiKey(apiKey) 23 | } 24 | 25 | if config.Get().RequireAuth && username == "" { 26 | //不允许匿名访问 27 | return unauthorized(username, exchange) 28 | } else if username == "" { 29 | username = config.C.DefaultUsername 30 | } 31 | if s, ok := service.UserRuleEngineServiceImpl.Get(username); ok { 32 | inMsg := exchange.In.(*rest.RequestMessage) 33 | mcpService := s.MCPService() 34 | if mcpService != nil { 35 | mcpService.SSEServer().ServeHTTP(inMsg.Response(), inMsg.Request()) 36 | } 37 | } else { 38 | return userNotFound(username, exchange) 39 | } 40 | return true 41 | }).End() 42 | } 43 | -------------------------------------------------------------------------------- /internal/dao/file_storage.go: -------------------------------------------------------------------------------- 1 | package dao 2 | 3 | import ( 4 | "gopkg.in/ini.v1" 5 | "sync" 6 | ) 7 | 8 | type FileStorage struct { 9 | filename string 10 | file *ini.File 11 | lock sync.RWMutex 12 | } 13 | 14 | func NewFileStorage(filename string) (*FileStorage, error) { 15 | file, err := ini.LooseLoad(filename) 16 | if err != nil { 17 | return nil, err 18 | } 19 | return &FileStorage{filename: filename, file: file}, nil 20 | } 21 | 22 | // GetSection 获取分区 23 | func (d *FileStorage) GetSection(sectionName string) (*ini.Section, error) { 24 | return d.file.GetSection(sectionName) 25 | } 26 | func (d *FileStorage) Get(sectionName string, keyName string) string { 27 | if fs, err := d.file.GetSection(sectionName); err != nil { 28 | return "" 29 | } else if key := fs.Key(keyName); key != nil { 30 | return key.Value() 31 | } else { 32 | return "" 33 | } 34 | } 35 | func (d *FileStorage) GetAll(sectionName string) map[string]string { 36 | values := make(map[string]string) 37 | if s, _ := d.GetSection(sectionName); s != nil { 38 | for _, k := range s.Keys() { 39 | values[k.Name()] = k.Value() 40 | } 41 | } 42 | return values 43 | } 44 | 45 | // Save 保存单个值 46 | func (d *FileStorage) Save(sectionName, key, value string) error { 47 | section := d.file.Section(sectionName) // 如果分区不存在,将会创建一个新的分区 48 | section.Key(key).SetValue(value) 49 | return d.SaveToFile() 50 | } 51 | 52 | // SaveList 保存多个值 53 | func (d *FileStorage) SaveList(sectionName string, values map[string]string) error { 54 | section := d.file.Section(sectionName) // 如果分区不存在,将会创建一个新的分区 55 | for key, value := range values { 56 | section.Key(key).SetValue(value) 57 | } 58 | return d.SaveToFile() 59 | } 60 | 61 | // Delete 删除 62 | func (d *FileStorage) Delete(sectionName string, keys ...string) error { 63 | if !d.file.HasSection(sectionName) { 64 | return nil 65 | } 66 | section := d.file.Section(sectionName) 67 | for _, key := range keys { 68 | section.DeleteKey(key) 69 | } 70 | return d.SaveToFile() 71 | } 72 | 73 | // SaveToFile 保存 74 | func (d *FileStorage) SaveToFile() error { 75 | d.lock.Lock() 76 | defer d.lock.Unlock() 77 | return d.file.SaveTo(d.filename) 78 | } 79 | 80 | //var FileStorageManager =NewFileStorageManager() 81 | 82 | type FileStorageManager struct { 83 | // 文件存储 key=路径 84 | manager map[string]*FileStorage 85 | lock sync.RWMutex 86 | } 87 | 88 | func NewFileStorageManager() *FileStorageManager { 89 | return &FileStorageManager{ 90 | manager: make(map[string]*FileStorage), 91 | } 92 | } 93 | 94 | func (f *FileStorageManager) Init(filename string) (*FileStorage, error) { 95 | fs, err := NewFileStorage(filename) 96 | if err != nil { 97 | return nil, err 98 | } else { 99 | f.lock.Lock() 100 | defer f.lock.Unlock() 101 | f.manager[filename] = fs 102 | } 103 | return fs, nil 104 | } 105 | 106 | func (f *FileStorageManager) Get(filename string) (*FileStorage, error) { 107 | f.lock.RLock() 108 | fs, ok := f.manager[filename] 109 | f.lock.RUnlock() 110 | if ok { 111 | return fs, nil 112 | } 113 | return f.Init(filename) 114 | } 115 | 116 | func (f *FileStorageManager) Delete(filename string) { 117 | f.lock.Lock() 118 | defer f.lock.Unlock() 119 | delete(f.manager, filename) 120 | } 121 | -------------------------------------------------------------------------------- /internal/dao/setting.go: -------------------------------------------------------------------------------- 1 | package dao 2 | 3 | import ( 4 | "github.com/rulego/rulego-server/config" 5 | "github.com/rulego/rulego-server/internal/model" 6 | "github.com/rulego/rulego/utils/maps" 7 | "path" 8 | "strings" 9 | ) 10 | 11 | const ( 12 | SettingsSectionName = "" 13 | SettingsFileName = "settings.ini" 14 | ) 15 | 16 | type UserSettingDao struct { 17 | Config config.Config 18 | fs *FileStorage 19 | } 20 | 21 | func NewUserSettingDao(config config.Config, namespace string) (*UserSettingDao, error) { 22 | fs, err := NewFileStorage(path.Join(namespace, SettingsFileName)) 23 | if err != nil { 24 | return nil, err 25 | } 26 | return &UserSettingDao{ 27 | Config: config, 28 | fs: fs, 29 | }, nil 30 | } 31 | 32 | func (d *UserSettingDao) Save(key, value string) error { 33 | return d.fs.Save(SettingsSectionName, key, value) 34 | } 35 | 36 | func (d *UserSettingDao) Delete(key string) error { 37 | return d.fs.Delete(SettingsSectionName, key) 38 | } 39 | func (d *UserSettingDao) Get(key string) string { 40 | return strings.TrimSpace(d.fs.Get(SettingsSectionName, key)) 41 | } 42 | 43 | func (d *UserSettingDao) Setting() model.UserSetting { 44 | var setting model.UserSetting 45 | values := d.fs.GetAll(SettingsSectionName) 46 | _ = maps.Map2Struct(values, &setting) 47 | return setting 48 | } 49 | -------------------------------------------------------------------------------- /internal/dao/user.go: -------------------------------------------------------------------------------- 1 | package dao 2 | 3 | import ( 4 | "github.com/rulego/rulego-server/config" 5 | "github.com/rulego/rulego-server/internal/model" 6 | "path" 7 | ) 8 | 9 | const ( 10 | UsersSectionName = "" 11 | UsersFileName = "users.ini" 12 | ) 13 | 14 | type UserDao struct { 15 | Config config.Config 16 | fs *FileStorage 17 | } 18 | 19 | func NewUserDao(config config.Config) (*UserDao, error) { 20 | fs, err := NewFileStorage(path.Join(config.DataDir, UsersFileName)) 21 | if err != nil { 22 | return nil, err 23 | } 24 | return &UserDao{ 25 | Config: config, 26 | fs: fs, 27 | }, nil 28 | } 29 | 30 | func (d *UserDao) CreateUser(user model.User) error { 31 | return d.fs.Save(UsersSectionName, user.Username, user.Password) 32 | } 33 | 34 | // ValidatePassword 验证密码 35 | func (d *UserDao) ValidatePassword(username, password string) bool { 36 | if v := d.fs.Get(UsersSectionName, username); v == "" { 37 | return false 38 | } else { 39 | return v == password 40 | } 41 | } 42 | 43 | func (d *UserDao) Delete(username string) error { 44 | return d.fs.Delete(UsersSectionName, username) 45 | } 46 | 47 | func (d *UserDao) List() []model.User { 48 | var users []model.User 49 | values := d.fs.GetAll(UsersSectionName) 50 | for key, value := range values { 51 | users = append(users, model.User{ 52 | Username: key, 53 | Password: value, 54 | }) 55 | } 56 | return users 57 | } 58 | -------------------------------------------------------------------------------- /internal/model/event.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type Event struct { 4 | Type string `json:"Type"` 5 | } 6 | 7 | // DebugData 调试数据 8 | // OnDebug 回调函数提供的数据 9 | type DebugData struct { 10 | //debug数据发生时间 11 | Ts int64 `json:"ts"` 12 | //节点ID 13 | NodeId string `json:"nodeId"` 14 | //流向OUT/IN 15 | FlowType string `json:"flowType"` 16 | //消息类型 17 | MsgType string `json:"msgType"` 18 | //消息ID 19 | MsgId string `json:"msgId"` 20 | //消息内容 21 | Data string `json:"data"` 22 | //消息元数据 23 | Metadata string `json:"metadata"` 24 | //Err 错误 25 | Err string `json:"err"` 26 | //关系 27 | RelationType string `json:"relationType"` 28 | } 29 | 30 | // DebugDataPage 分页返回数据 31 | type DebugDataPage struct { 32 | //每页多少条,默认读取所有 33 | Size int `json:"PageSize"` 34 | //当前第几页,默认读取所有 35 | Current int `json:"current"` 36 | //总数 37 | Total int `json:"total"` 38 | //记录 39 | Items []DebugData `json:"items"` 40 | } 41 | -------------------------------------------------------------------------------- /internal/model/setting.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | // UserSetting 用户设置 4 | type UserSetting struct { 5 | // 最后修改规则链ID 6 | LatestChainId string `json:"latestChainId"` 7 | // 默认规则链ID,server所有事件都会发送至此 8 | CoreChainId string `json:"coreChainId"` 9 | } 10 | -------------------------------------------------------------------------------- /internal/model/user.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | // User 用户 4 | type User struct { 5 | // 用户名 6 | Username string `json:"username"` 7 | // 密码 8 | Password string `json:"password"` 9 | // 访问Key 10 | ApiKey string `json:"apiKey"` 11 | } 12 | -------------------------------------------------------------------------------- /internal/model/variable.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | // Variable 变量 4 | type Variable struct { 5 | // 标题 6 | Title string `json:"title"` 7 | // 名称,唯一的 8 | Name string `json:"name"` 9 | // 内容 10 | Value string `json:"value"` 11 | // 描述 12 | Description string `json:"description"` 13 | // 类型 0:变量;1:机密的;2:秘钥 14 | Type int `json:"type"` 15 | // 所属用户 16 | Owner string `json:"owner"` 17 | } 18 | -------------------------------------------------------------------------------- /internal/model/workflow.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | // Workflow 工作流 4 | type Workflow struct { 5 | // 名称 6 | Name string `json:"name"` 7 | // 所属用户 8 | Owner string `json:"owner"` 9 | // 描述 10 | Description string `json:"description"` 11 | // 创建时间 12 | CreateTime int64 `json:"createTime"` 13 | // 更新时间 14 | UpdateTime int64 `json:"updateTime"` 15 | // 扩展信息 16 | AdditionalInfo map[string]interface{} `json:"additionalInfo"` 17 | //规则链定义 18 | RuleChain string `json:"rulechain"` 19 | } 20 | -------------------------------------------------------------------------------- /internal/router/websocket.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "github.com/gorilla/websocket" 5 | "github.com/rulego/rulego-server/config" 6 | "github.com/rulego/rulego-server/internal/constants" 7 | "github.com/rulego/rulego-server/internal/controller" 8 | "github.com/rulego/rulego-server/internal/service" 9 | "github.com/rulego/rulego/api/types" 10 | endpointApi "github.com/rulego/rulego/api/types/endpoint" 11 | "github.com/rulego/rulego/endpoint/rest" 12 | websocketEndpoint "github.com/rulego/rulego/endpoint/websocket" 13 | "github.com/rulego/rulego/utils/json" 14 | "net/http" 15 | "time" 16 | ) 17 | 18 | // NewWebsocketServe Websocket服务 接收端点 19 | func NewWebsocketServe(c config.Config, restEndpoint *rest.Rest) *websocketEndpoint.Endpoint { 20 | //初始化日志 21 | wsEndpoint := &websocketEndpoint.Endpoint{ 22 | Rest: restEndpoint, 23 | Upgrader: websocket.Upgrader{ 24 | ReadBufferSize: 1024, 25 | WriteBufferSize: 1024, 26 | CheckOrigin: func(r *http.Request) bool { 27 | return true // 允许所有跨域请求 28 | }, 29 | }, 30 | } 31 | wsEndpoint.OnEvent = func(eventName string, params ...interface{}) { 32 | switch eventName { 33 | case endpointApi.EventConnect: 34 | exchange := params[0].(*endpointApi.Exchange) 35 | username := exchange.In.Headers().Get(constants.KeyUsername) 36 | if username == "" { 37 | username = config.C.DefaultUsername 38 | } 39 | if s, ok := service.UserRuleEngineServiceImpl.Get(username); ok { 40 | chainId := exchange.In.GetParam(constants.KeyChainId) 41 | clientId := exchange.In.GetParam(constants.KeyClientId) 42 | s.AddOnDebugObserver(chainId, clientId, func(chainId, flowType string, nodeId string, msg types.RuleMsg, relationType string, err error) { 43 | errStr := "" 44 | if err != nil { 45 | errStr = err.Error() 46 | } 47 | var log = map[string]interface{}{ 48 | "chainId": chainId, 49 | "flowType": flowType, 50 | "nodeId": nodeId, 51 | "relationType": relationType, 52 | "err": errStr, 53 | "msg": msg, 54 | "ts": time.Now().UnixMilli(), 55 | } 56 | jsonStr, _ := json.Marshal(log) 57 | exchange.Out.SetBody(jsonStr) 58 | //写入报错 59 | if exchange.Out.GetError() != nil { 60 | s.RemoveOnDebugObserver(clientId) 61 | } 62 | }) 63 | } 64 | case endpointApi.EventDisconnect: 65 | exchange := params[0].(*endpointApi.Exchange) 66 | username := exchange.In.Headers().Get(constants.KeyUsername) 67 | if username == "" { 68 | username = config.C.DefaultUsername 69 | } 70 | if s, ok := service.UserRuleEngineServiceImpl.Get(username); ok { 71 | s.RemoveOnDebugObserver(exchange.In.GetParam(constants.KeyClientId)) 72 | } 73 | } 74 | } 75 | _, _ = wsEndpoint.AddRouter(controller.Log.WsNodeLogRouter(apiBasePath + "/" + moduleLogs + "/ws/:chainId/:clientId")) 76 | 77 | return wsEndpoint 78 | } 79 | -------------------------------------------------------------------------------- /internal/service/run_log.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "github.com/rulego/rulego-server/config" 5 | "github.com/rulego/rulego-server/internal/dao" 6 | "github.com/rulego/rulego/api/types" 7 | ) 8 | 9 | var EventServiceImpl *EventService 10 | 11 | type EventService struct { 12 | EventDao *dao.EventDao 13 | config config.Config 14 | } 15 | 16 | func NewEventService(config config.Config) (*EventService, error) { 17 | if eventDao, err := dao.NewEventDao(config); err != nil { 18 | return nil, err 19 | } else { 20 | return &EventService{ 21 | EventDao: eventDao, 22 | config: config, 23 | }, nil 24 | } 25 | } 26 | 27 | // SaveRunLog 保存工作流运行日志快照 28 | func (s *EventService) SaveRunLog(username string, ctx types.RuleContext, snapshot types.RuleChainRunSnapshot) error { 29 | return s.EventDao.SaveRunLog(username, ctx, snapshot) 30 | } 31 | 32 | func (s *EventService) Delete(username, chainId, id string) error { 33 | return s.EventDao.Delete(username, chainId, id) 34 | } 35 | func (s *EventService) DeleteByChainId(username, chainId string) error { 36 | return s.EventDao.DeleteByChainId(username, chainId) 37 | } 38 | 39 | func (s *EventService) List(username, chainId string, current, size int) ([]types.RuleChainRunSnapshot, int, error) { 40 | return s.EventDao.List(username, chainId, current, size) 41 | } 42 | 43 | func (s *EventService) Get(username, chainId, snapshotId string) (types.RuleChainRunSnapshot, error) { 44 | return s.EventDao.Get(username, chainId, snapshotId) 45 | } 46 | -------------------------------------------------------------------------------- /internal/service/setup.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "github.com/rulego/rulego-server/config" 5 | ) 6 | 7 | func Setup(config config.Config) error { 8 | 9 | if s, err := NewUserService(config); err != nil { 10 | return err 11 | } else { 12 | UserServiceImpl = s 13 | } 14 | if s, err := NewUserRuleEngineServiceImpl(config); err != nil { 15 | return err 16 | } else { 17 | UserRuleEngineServiceImpl = s 18 | } 19 | 20 | if s, err := NewEventService(config); err != nil { 21 | return err 22 | } else { 23 | EventServiceImpl = s 24 | } 25 | return nil 26 | } 27 | -------------------------------------------------------------------------------- /internal/service/user.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "github.com/rulego/rulego-server/config" 5 | "github.com/rulego/rulego-server/internal/dao" 6 | ) 7 | 8 | var UserServiceImpl *UserService 9 | 10 | type UserService struct { 11 | UserDao *dao.UserDao 12 | Config config.Config 13 | } 14 | 15 | func NewUserService(config config.Config) (*UserService, error) { 16 | if userDao, err := dao.NewUserDao(config); err != nil { 17 | return nil, err 18 | } else { 19 | return &UserService{ 20 | UserDao: userDao, 21 | Config: config, 22 | }, nil 23 | } 24 | } 25 | 26 | func (s *UserService) CheckPassword(username, password string) bool { 27 | if username == "" { 28 | return false 29 | } 30 | return s.Config.CheckPassword(username, password) 31 | } 32 | 33 | func (s *UserService) GetUsernameByApiKey(apikey string) string { 34 | if apikey == "" { 35 | return "" 36 | } 37 | return s.Config.GetUsernameByApiKey(apikey) 38 | 39 | } 40 | 41 | func (s *UserService) GetApiKeyByUsername(username string) string { 42 | if username == "" { 43 | return "" 44 | } 45 | return s.Config.GetApiKeyByUsername(username) 46 | 47 | } 48 | -------------------------------------------------------------------------------- /internal/utils/array.go: -------------------------------------------------------------------------------- 1 | // Package utils provides various utility functions for working with arrays. 2 | package utils 3 | 4 | // InArray 判断字符串是否在切片中(区分大小写) 5 | // 线性遍历(适合小数据量) 6 | // 时间复杂度O(n),空间复杂度O(1) 7 | func InArray(target string, arr []string) bool { 8 | for idx := range arr { 9 | if arr[idx] == target { 10 | return true 11 | } 12 | } 13 | return false 14 | } 15 | -------------------------------------------------------------------------------- /internal/utils/file/file.go: -------------------------------------------------------------------------------- 1 | package file 2 | 3 | import ( 4 | "path/filepath" 5 | "sort" 6 | "strings" 7 | "time" 8 | ) 9 | 10 | // WithTimestamp 包含文件路径和解析出的时间戳 11 | type WithTimestamp struct { 12 | Path string 13 | Timestamp time.Time 14 | } 15 | 16 | // SortFilesByTimestamp 解析文件列表中的时间戳,返回按时间戳排序的文件列表 17 | func SortFilesByTimestamp(files []string) []WithTimestamp { 18 | var fileWithTimestamps []WithTimestamp 19 | for _, file := range files { 20 | timestamp, err := parseTimestampFromFilename(file) 21 | if err != nil { 22 | timestamp = time.Now() 23 | } 24 | fileWithTimestamps = append(fileWithTimestamps, WithTimestamp{Path: file, Timestamp: timestamp}) 25 | } 26 | // 使用 sort.Sort 进行排序 27 | sort.Sort(ByTimestamp(fileWithTimestamps)) 28 | return fileWithTimestamps 29 | } 30 | 31 | // 解析文件名中的时间戳 32 | func parseTimestampFromFilename(filename string) (time.Time, error) { 33 | // 使用filepath.Base获取文件名 34 | lastPart := filepath.Base(filename) 35 | timestampStr := strings.Split(lastPart, "_")[0] 36 | return time.Parse("20060102150405000", timestampStr) 37 | } 38 | 39 | // ByTimestamp 实现 sort.Interface 接口 40 | type ByTimestamp []WithTimestamp 41 | 42 | func (f ByTimestamp) Len() int { return len(f) } 43 | func (f ByTimestamp) Swap(i, j int) { f[i], f[j] = f[j], f[i] } 44 | func (f ByTimestamp) Less(i, j int) bool { return f[i].Timestamp.After(f[j].Timestamp) } 45 | -------------------------------------------------------------------------------- /node_pool.json: -------------------------------------------------------------------------------- 1 | { 2 | "ruleChain": { 3 | "id": "default_node_pool", 4 | "name": "全局共享节点池" 5 | }, 6 | "metadata": { 7 | "endpoints": [ 8 | { 9 | "id": "local_endpoint_nats", 10 | "type": "endpoint/nats", 11 | "name": "本地nats连接池", 12 | "configuration": { 13 | "server": "nats://127.0.0.1:4222" 14 | } 15 | } 16 | ], 17 | "nodes": [ 18 | { 19 | "id": "local_mqtt_client", 20 | "type": "mqttClient", 21 | "name": "本地MQTT连接池", 22 | "configuration": { 23 | "server": "127.0.0.1:1883" 24 | } 25 | }, 26 | { 27 | "id": "local_mysql_client", 28 | "type": "dbClient", 29 | "name": "本地MYSQL-test数据库连接池", 30 | "configuration": { 31 | "driverName": "mysql", 32 | "dsn": "root:root@tcp(127.0.0.1:3306)/test" 33 | } 34 | }, 35 | 36 | { 37 | "id": "local_nats", 38 | "type": "x/natsClient", 39 | "name": "本地nats连接池", 40 | "configuration": { 41 | "server": "nats://127.0.0.1:4222" 42 | } 43 | }, 44 | 45 | { 46 | "id": "local_rabbitmq", 47 | "type": "x/rabbitmqClient", 48 | "name": "本地rabbitmq连接池", 49 | "configuration": { 50 | "autoDelete": true, 51 | "durable": true, 52 | "exchange": "rulego", 53 | "exchangeType": "topic", 54 | "server": "amqp://guest:guest@127.0.0.1:5672/" 55 | } 56 | }, 57 | { 58 | "id": "local_redis", 59 | "type": "x/redisClient", 60 | "name": "本地redis连接池", 61 | "configuration": { 62 | "db": 0, 63 | "server": "127.0.0.1:6379" 64 | } 65 | }, 66 | { 67 | "id": "local_opengemini_write", 68 | "type": "x/opengeminiWrite", 69 | "name": "本地opengemini_write连接池", 70 | "configuration": { 71 | "database": "db0", 72 | "server": "127.0.0.1:8086" 73 | } 74 | }, 75 | { 76 | "id": "local_opengemini_query", 77 | "type": "x/opengeminiQuery", 78 | "name": "本地opengemini_query连接池", 79 | "configuration": { 80 | "database": "db0", 81 | "server": "127.0.0.1:8086" 82 | } 83 | } 84 | ] 85 | } 86 | } -------------------------------------------------------------------------------- /ui/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /ui/.husky/pre-commit: -------------------------------------------------------------------------------- 1 | npx lint-staged 2 | -------------------------------------------------------------------------------- /ui/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "plugins": ["prettier-plugin-tailwindcss"], 4 | "tailwindConfig": "./tailwind.config.js" 5 | } 6 | -------------------------------------------------------------------------------- /ui/LICENSE: -------------------------------------------------------------------------------- 1 | # 开源许可 2 | 3 | RuleGo-Server 采用修改版的 Apache License 2.0 许可证,附加以下条件: 4 | 5 | 1. 商业用途:RuleGo-Server 可以用于商业目的,包括作为其他应用程序的后端服务或作为企业应用开发平台。如果满足以下条件,则需从版权所有者处获得商业许可: 6 | a. 多租户服务:除非获得 RuleGo-Server 的明确书面授权,否则不得使用 RuleGo-Server 源代码运营多租户环境。 7 | - 租户定义:在 RuleGo-Server 的上下文中,一个租户对应一个工作区。工作区为每个租户的数据和配置提供独立的隔离区域。 8 | b. LOGO和版权信息:在使用 RuleGo-Server 前端的过程中,不得删除或修改 RuleGo-Server 控制台或应用程序中的 LOGO 或版权信息。此限制不适用于不涉及 RuleGo-Server 前端的使用。 9 | - 前端定义:在本许可证中,RuleGo-Server 的“前端”包括从原始源代码运行 RuleGo-Server 时位于 `ui/` 目录中的所有组件,或通过 Docker 运行 RuleGo-Server 时的"ui/"镜像。 10 | 11 | 2. 作为贡献者,您应同意以下内容: 12 | a. 所有者可以根据需要调整开源协议,使其更加严格或宽松。 13 | b. 您贡献的代码可以用于商业目的,包括但不限于其云业务运营。 14 | 15 | 3. 其他条款: 16 | a. 本协议条款的解释权归 RuleGo-Server 开发者所有。 17 | 18 | 如有任何问题或需申请商业授权,请联系 RuleGo-Server 开发团队。 19 | 20 | 除上述特定条件外,其他所有权利和限制均遵循 Apache License 2.0。关于 Apache License 2.0 的详细信息,请访问 [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)。 21 | 22 | --- 23 | 24 | # Open Source License 25 | 26 | RuleGo-Server is licensed under a modified version of the Apache License 2.0, with the following additional conditions: 27 | 28 | 1. RuleGo-Server may be utilized commercially, including as a backend service for other applications or as an application development platform for enterprises. Should the conditions below be met, a commercial license must be obtained from the producer: 29 | 30 | a. Multi-tenant service: Unless explicitly authorized by RuleGo-Server in writing, you may not use the RuleGo-Server source code to operate a multi-tenant environment. 31 | - Tenant Definition: Within the context of RuleGo-Server, one tenant corresponds to one workspace. The workspace provides a separated area for each tenant's data and configurations. 32 | 33 | b. LOGO and copyright information: In the process of using RuleGo-Server's frontend, you may not remove or moRuleGo-Server the LOGO or copyright information in the RuleGo-Server console or applications. This restriction is inapplicable to uses of RuleGo-Server that do not involve its frontend. 34 | - Frontend Definition: For the purposes of this license, the "frontend" of RuleGo-Server includes all components located in the `web/` directory when running RuleGo-Server from the raw source code, or the "web" image when running RuleGo-Server with Docker. 35 | 36 | 2. As a contributor, you should agree that: 37 | 38 | a. The producer can adjust the open-source agreement to be more strict or relaxed as deemed necessary. 39 | b. Your contributed code may be used for commercial purposes, including but not limited to its cloud business operations. 40 | 41 | 3. Other Terms: 42 | a. The interpretation of the terms of this agreement shall be the prerogative of the RuleGo-Server development team. 43 | 44 | If you have any questions or need to apply for a commercial license, please contact the RuleGo-Server development team. 45 | Apart from the specific conditions mentioned above, all other rights and restrictions follow the Apache License 2.0. Detailed information about the Apache License 2.0 can be found at http://www.apache.org/licenses/LICENSE-2.0. 46 | -------------------------------------------------------------------------------- /ui/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | RuleGo-IPAAS 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /ui/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": { 5 | "@src/*": ["./src/*"] 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "relego-ipaas-ui", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview", 10 | "prepare": "husky" 11 | }, 12 | "dependencies": { 13 | "@codemirror/lang-javascript": "^6.2.2", 14 | "@codemirror/lang-json": "^6.0.1", 15 | "@codemirror/state": "^6.5.1", 16 | "@codemirror/view": "^6.36.2", 17 | "@element-plus/icons-vue": "^2.3.1", 18 | "@logicflow/core": "^2.0.10", 19 | "@logicflow/extension": "^2.0.14", 20 | "@logicflow/vue-node-registry": "^1.0.12", 21 | "@tailwindcss/line-clamp": "^0.4.4", 22 | "@tinymce/tinymce-vue": "^6.1.0", 23 | "@tiptap/extension-bullet-list": "^2.11.2", 24 | "@tiptap/extension-link": "^2.11.2", 25 | "@tiptap/extension-list-item": "^2.11.2", 26 | "@tiptap/extension-text-style": "^2.11.2", 27 | "@tiptap/starter-kit": "^2.11.2", 28 | "@tiptap/vue-3": "^2.11.2", 29 | "@vueup/vue-quill": "^1.2.0", 30 | "@vueuse/core": "^11.2.0", 31 | "@wangeditor/editor-for-vue": "^5.1.12", 32 | "axios": "^1.7.9", 33 | "codemirror": "^6.0.1", 34 | "dayjs": "^1.11.13", 35 | "element-plus": "^2.8.6", 36 | "file-saver": "^2.0.5", 37 | "fuse.js": "^7.0.0", 38 | "humanize-duration": "^3.32.1", 39 | "js-beautify": "^1.15.1", 40 | "lodash-es": "^4.17.21", 41 | "nanoid": "^5.0.9", 42 | "openai": "^4.93.0", 43 | "pinia": "^2.2.4", 44 | "qs": "^6.13.1", 45 | "screenfull": "^6.0.2", 46 | "vite-svg-loader": "^5.1.0", 47 | "vue": "^3.5.12", 48 | "vue-codemirror": "^6.1.1", 49 | "vue-element-plus-x": "^1.1.1", 50 | "vue-router": "^4.4.5" 51 | }, 52 | "devDependencies": { 53 | "@vitejs/plugin-vue": "^5.1.4", 54 | "autoprefixer": "^10.4.20", 55 | "husky": "^9.1.6", 56 | "lint-staged": "^15.2.10", 57 | "postcss": "^8.4.47", 58 | "prettier": "3.3.3", 59 | "prettier-plugin-tailwindcss": "^0.6.8", 60 | "tailwindcss": "^3.4.14", 61 | "vite": "^5.4.9" 62 | }, 63 | "lint-staged": { 64 | "**/*": "prettier --write --ignore-unknown" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /ui/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /ui/public/config.js: -------------------------------------------------------------------------------- 1 | window.config = { 2 | baseURL: 'http://8.134.32.225:9090', 3 | wsURL: 'ws://8.134.32.225:9090', 4 | }; 5 | -------------------------------------------------------------------------------- /ui/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rulego/rulego-server/07f42edaab103e25702012713e8ffb5f8dc89c79/ui/public/favicon.ico -------------------------------------------------------------------------------- /ui/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ui/src/api/index.js: -------------------------------------------------------------------------------- 1 | export * from '@src/api/module/login'; 2 | export * from '@src/api/module/rules'; 3 | export * from '@src/api/module/components'; 4 | export * from '@src/api/module/locales'; 5 | export * from '@src/api/module/logs'; 6 | -------------------------------------------------------------------------------- /ui/src/api/module/components.js: -------------------------------------------------------------------------------- 1 | import { request } from '@src/utils/request'; 2 | 3 | export function getComponents() { 4 | return request.get('/components'); 5 | } 6 | -------------------------------------------------------------------------------- /ui/src/api/module/locales.js: -------------------------------------------------------------------------------- 1 | import { request } from '@src/utils/request'; 2 | 3 | export function getLocales() { 4 | return request.get('/locales'); 5 | } 6 | -------------------------------------------------------------------------------- /ui/src/api/module/login.js: -------------------------------------------------------------------------------- 1 | import { request } from '@src/utils/request'; 2 | 3 | export function login(data) { 4 | return request.post('/login', data); 5 | } 6 | -------------------------------------------------------------------------------- /ui/src/api/module/logs.js: -------------------------------------------------------------------------------- 1 | import { request } from '@src/utils/request'; 2 | 3 | export function getLogsDebug(params) { 4 | return request.get('/logs/debug', { 5 | params, 6 | }); 7 | } 8 | -------------------------------------------------------------------------------- /ui/src/api/module/rules.js: -------------------------------------------------------------------------------- 1 | import { request, rawRequest } from '@src/utils/request'; 2 | 3 | export function getRules(params) { 4 | return request.get('/rules', { 5 | params, 6 | }); 7 | } 8 | 9 | export function setRulesBase(id, data) { 10 | return request.post(`/rules/${id}/base`, data); 11 | } 12 | 13 | export function setRules(id, data) { 14 | return request.post(`/rules/${id}`, data); 15 | } 16 | 17 | export function deleteRules(id) { 18 | return request.delete(`/rules/${id}`); 19 | } 20 | 21 | export function getRulesDetail(id) { 22 | return request.get(`/rules/${id}`); 23 | } 24 | 25 | export function setRulesConfigVars(id, data) { 26 | return request.post(`/rules/${id}/config/vars`, data); 27 | } 28 | 29 | export function executeRules({ id, msgType, data, headers, params }) { 30 | return rawRequest.post(`/rules/${id}/execute/${msgType}`, data, { 31 | headers, 32 | params: { 33 | debugMode: true, 34 | ...params, 35 | }, 36 | }); 37 | } 38 | 39 | export function notifyRules({ id, msgType, data, headers, params }) { 40 | return rawRequest.post(`/rules/${id}/notify/${msgType}`, data, { 41 | headers, 42 | params: { 43 | debugMode: true, 44 | ...params, 45 | }, 46 | }); 47 | } 48 | 49 | export function deploymentRules(id, disabled) { 50 | const operate = disabled ? 'start' : 'stop'; 51 | return request.post(`/rules/${id}/operate/${operate}`); 52 | } 53 | -------------------------------------------------------------------------------- /ui/src/app.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 13 | -------------------------------------------------------------------------------- /ui/src/assets/assistant.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ui/src/assets/nodes-graph.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ui/src/assets/split-screen.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ui/src/assets/vue.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ui/src/components/config-form/cache.js: -------------------------------------------------------------------------------- 1 | import { shallowRef } from 'vue'; 2 | import DefaultEndpointsRoutersFormItem from '@src/components/config-form/custom-form-item/default-endpoints-routers-form-item/index.vue'; 3 | import SimpleRoutersFormItem from '@src/components/config-form/custom-form-item/simple-routers-form-item/index.vue'; 4 | import SwitchFormItem from '@src/components/config-form/custom-form-item/switch-form-item/index.vue'; 5 | import MapFormItem from '@src/components/config-form/custom-form-item/map-form-item/index.vue'; 6 | import ArrayFormItem from '@src/components/config-form/custom-form-item/array-form-item/index.vue'; 7 | import StructFormItem from '@src/components/config-form/custom-form-item/struct-form-item/index.vue'; 8 | import StringFormItem from '@src/components/config-form/custom-form-item/string-form-item/index.vue'; 9 | import ScriptFormItem from '@src/components/config-form/custom-form-item/script-form-item/index.vue'; 10 | import SelectFormItem from '@src/components/config-form/custom-form-item/select-form-item/index.vue'; 11 | import TextareaFormItem from '@src/components/config-form/custom-form-item/textarea-form-item/index.vue'; 12 | import TableFormItem from '@src/components/config-form/custom-form-item/table-form-item/index.vue'; 13 | import SwitchNodeFormItem from '@src/components/config-form/custom-form-item/switch-node-form-item/index.vue'; 14 | import SliderFormItem from '@src/components/config-form/custom-form-item/slider-form-item/index.vue'; 15 | 16 | const cache = { 17 | options: { 18 | componentMaps: { 19 | form: 'el-form', 20 | formItem: 'el-form-item', 21 | number: 'el-input-number', 22 | switch: shallowRef(SwitchFormItem), 23 | defaultEndpointsRoutersFormItem: shallowRef( 24 | DefaultEndpointsRoutersFormItem, 25 | ), 26 | simpleRoutersFormItem: shallowRef(SimpleRoutersFormItem), 27 | map: shallowRef(MapFormItem), 28 | array: shallowRef(ArrayFormItem), 29 | struct: shallowRef(StructFormItem), 30 | string: shallowRef(StringFormItem), 31 | script: shallowRef(ScriptFormItem), 32 | select: shallowRef(SelectFormItem), 33 | textarea: shallowRef(TextareaFormItem), 34 | table: shallowRef(TableFormItem), 35 | switchNode: shallowRef(SwitchNodeFormItem), 36 | slider: shallowRef(SliderFormItem), 37 | }, 38 | defaultProps: { 39 | form: { 40 | size: 'default', 41 | labelWidth: '80px', 42 | labelPosition: 'top', 43 | }, 44 | number: { 45 | style: 'width: 300px;', 46 | }, 47 | }, 48 | }, 49 | }; 50 | 51 | export function setCache(key, data) { 52 | cache[key] = data; 53 | } 54 | 55 | export function getCache(key) { 56 | return cache[key]; 57 | } 58 | -------------------------------------------------------------------------------- /ui/src/components/config-form/config-form-item.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 61 | -------------------------------------------------------------------------------- /ui/src/components/config-form/config-form.vue: -------------------------------------------------------------------------------- 1 | 71 | 72 | 126 | -------------------------------------------------------------------------------- /ui/src/components/config-form/custom-form-item/array-form-item/index.vue: -------------------------------------------------------------------------------- 1 | 57 | 58 | 104 | -------------------------------------------------------------------------------- /ui/src/components/config-form/custom-form-item/map-form-item/index.vue: -------------------------------------------------------------------------------- 1 | 62 | 63 | 107 | -------------------------------------------------------------------------------- /ui/src/components/config-form/custom-form-item/script-form-item/index.vue: -------------------------------------------------------------------------------- 1 | 89 | 90 | 128 | 129 | 135 | -------------------------------------------------------------------------------- /ui/src/components/config-form/custom-form-item/select-form-item/index.vue: -------------------------------------------------------------------------------- 1 | 52 | 53 | 72 | -------------------------------------------------------------------------------- /ui/src/components/config-form/custom-form-item/slider-form-item/index.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 48 | -------------------------------------------------------------------------------- /ui/src/components/config-form/custom-form-item/string-form-item/index.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 48 | -------------------------------------------------------------------------------- /ui/src/components/config-form/custom-form-item/struct-form-item/index.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 42 | -------------------------------------------------------------------------------- /ui/src/components/config-form/custom-form-item/switch-form-item/index.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 44 | -------------------------------------------------------------------------------- /ui/src/components/config-form/custom-form-item/switch-node-form-item/div-input.vue: -------------------------------------------------------------------------------- 1 | 74 | 75 | 80 | 81 | 86 | -------------------------------------------------------------------------------- /ui/src/components/config-form/custom-form-item/switch-node-form-item/index.vue: -------------------------------------------------------------------------------- 1 | 55 | 56 | 115 | -------------------------------------------------------------------------------- /ui/src/components/config-form/custom-form-item/switch-node-form-item/item.vue: -------------------------------------------------------------------------------- 1 | 58 | 59 | 118 | -------------------------------------------------------------------------------- /ui/src/components/config-form/custom-form-item/textarea-form-item/index.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 48 | -------------------------------------------------------------------------------- /ui/src/components/json-editor/json-editor.vue: -------------------------------------------------------------------------------- 1 | 106 | 107 | 112 | 113 | 119 | -------------------------------------------------------------------------------- /ui/src/constant/workflow.js: -------------------------------------------------------------------------------- 1 | export const WORKFLOW_MENU_KEY = { 2 | APP_MANAGE: 'app-manage', 3 | APP_DESIGN: 'app-design', 4 | }; 5 | -------------------------------------------------------------------------------- /ui/src/layout/components/default/main.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 15 | -------------------------------------------------------------------------------- /ui/src/layout/components/default/menu.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 79 | 80 | 85 | -------------------------------------------------------------------------------- /ui/src/layout/default-layout.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 15 | -------------------------------------------------------------------------------- /ui/src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue'; 2 | import ElementPlus from 'element-plus'; 3 | import * as ElementPlusIconsVue from '@element-plus/icons-vue'; 4 | import App from '@src/app.vue'; 5 | import router from '@src/router'; 6 | import store from '@src/store'; 7 | 8 | import '@src/style/tailwind.css'; 9 | import 'element-plus/dist/index.css'; 10 | import '@logicflow/core/lib/style/index.css'; 11 | import '@logicflow/extension/lib/style/index.css'; 12 | import '@src/style/logic-flow.css'; 13 | import '@src/style/element-plus.css'; 14 | 15 | const app = createApp(App); 16 | 17 | for (const [key, component] of Object.entries(ElementPlusIconsVue)) { 18 | app.component(`ElIcon${key}`, component); 19 | } 20 | 21 | app.use(ElementPlus).use(store).use(router).mount('#app'); 22 | -------------------------------------------------------------------------------- /ui/src/pages/share-node-list/share-node-list.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /ui/src/pages/workflow-list/app-card.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 103 | -------------------------------------------------------------------------------- /ui/src/pages/workflow-list/create-app-modal.vue: -------------------------------------------------------------------------------- 1 | 51 | 52 | 81 | -------------------------------------------------------------------------------- /ui/src/pages/workflow/app-design-new/app-design-new.vue: -------------------------------------------------------------------------------- 1 | 97 | 118 | -------------------------------------------------------------------------------- /ui/src/pages/workflow/app-design-new/tool-buttons.vue: -------------------------------------------------------------------------------- 1 | 25 | 37 | -------------------------------------------------------------------------------- /ui/src/pages/workflow/app-design/constant.js: -------------------------------------------------------------------------------- 1 | export const NODE_TITLE_HEIGHT = 36; 2 | export const NODE_DEFAULT_WIDTH = 240; 3 | export const NODE_DEFAULT_HEIGHT = 40; 4 | 5 | export const NODE_TYPE_MAP = { 6 | // 'endpoint/grpc/stream': 'endpoint-grpc-stream', 7 | // 'endpoint/kafka': 'endpoints-kafka', 8 | // 'endpoint/nats': 'endpoints-nats', 9 | // 'endpoint/opcua': 'endpoint-opcua', 10 | // 'endpoint/rabbitmq': 'endpoints-rabbitmq', 11 | // 'endpoint/redis': 'endpoints-redis', 12 | // 'endpoint/redis/stream': 'endpoints-redis-stream', 13 | // 'endpoint/mysql_cdc': 'endpoint-mysql-cdc', 14 | // 'endpoint/mqtt': 'endpoint-mqtt', 15 | // 'endpoint/net': 'endpoint-net', 16 | // 'endpoint/http': 'endpoint-http', 17 | // 'endpoint/schedule': 'endpoint-schedule', 18 | // 'endpoint/ws': 'endpoint-ws', 19 | fork: 'fork', 20 | groupFilter: 'group-filter', 21 | jsSwitch: 'js-switch', 22 | msgTypeSwitch: 'msg-type-switch', 23 | switch: 'switch', 24 | comment: 'comment-node', 25 | for: 'for', 26 | groupAction: 'group-action', 27 | }; 28 | 29 | export const NODE_TYPE_KEYS = Object.keys(NODE_TYPE_MAP); 30 | 31 | export const ENDPOINTS_NODE_TYPE_KEYS = [ 32 | // 'endpoint-grpc-stream', 33 | // 'endpoints-kafka', 34 | // 'endpoints-nats', 35 | // 'endpoint-opcua', 36 | // 'endpoints-rabbitmq', 37 | // 'endpoints-redis', 38 | // 'endpoints-redis-stream', 39 | // 'endpoint-mysql-cdc', 40 | // 'endpoint-mqtt', 41 | // 'endpoint-net', 42 | // 'endpoint-http', 43 | // 'endpoint-schedule', 44 | // 'endpoint-ws', 45 | ]; 46 | 47 | export const ONLY_ONE_NEXT_NODES_TYPE_KEYS = [ 48 | 'start', 49 | ...ENDPOINTS_NODE_TYPE_KEYS, 50 | ]; 51 | -------------------------------------------------------------------------------- /ui/src/pages/workflow/app-design/flow-edge/custom-bezier.js: -------------------------------------------------------------------------------- 1 | import { BezierEdge, BezierEdgeModel } from '@logicflow/core'; 2 | 3 | class CustomEdge extends BezierEdge {} 4 | 5 | class CustomEdgeModel extends BezierEdgeModel { 6 | // 设置边样式 7 | getEdgeStyle() { 8 | const style = super.getEdgeStyle(); 9 | if (this.isSelected) { 10 | style.stroke = '#466dfd'; 11 | } else { 12 | style.stroke = '#d1d5dd'; 13 | } 14 | return style; 15 | } 16 | 17 | // 设置 hover 轮廓样式 18 | getOutlineStyle() { 19 | const style = super.getOutlineStyle(); 20 | style.stroke = 'none'; 21 | style.hover.stroke = 'none'; 22 | return style; 23 | } 24 | 25 | /** 26 | * 给边自定义方案,使其支持基于锚点的位置更新边的路径 27 | */ 28 | updatePathByAnchor() { 29 | // TODO 30 | const sourceNodeModel = this.graphModel.getNodeModelById(this.sourceNodeId); 31 | const sourceAnchor = sourceNodeModel 32 | ?.getDefaultAnchor() 33 | .find((anchor) => anchor.id === this.sourceAnchorId); 34 | const targetNodeModel = this.graphModel.getNodeModelById(this.targetNodeId); 35 | const targetAnchor = targetNodeModel 36 | ?.getDefaultAnchor() 37 | .find((anchor) => anchor.id === this.targetAnchorId); 38 | 39 | if (sourceAnchor) { 40 | const startPoint = { 41 | x: sourceAnchor?.x, 42 | y: sourceAnchor?.y, 43 | }; 44 | this.updateStartPoint(startPoint); 45 | } 46 | if (targetAnchor) { 47 | const endPoint = { 48 | x: targetAnchor?.x, 49 | y: targetAnchor?.y, 50 | }; 51 | this.updateEndPoint(endPoint); 52 | } 53 | // 这里需要将原有的pointsList设置为空,才能触发bezier的自动计算control点。 54 | this.pointsList = []; 55 | this.initPoints(); 56 | } 57 | } 58 | 59 | export default { 60 | type: 'custom-bezier', 61 | view: CustomEdge, 62 | model: CustomEdgeModel, 63 | }; 64 | -------------------------------------------------------------------------------- /ui/src/pages/workflow/app-design/flow-node/comment/comment.js: -------------------------------------------------------------------------------- 1 | import { HtmlNode, HtmlNodeModel } from '@logicflow/core'; 2 | import { createApp, h } from 'vue'; 3 | import ElementPlus from 'element-plus'; 4 | import * as ElementPlusIconsVue from '@element-plus/icons-vue'; 5 | import CommentNode from './comment.vue'; 6 | import { 7 | NODE_DEFAULT_WIDTH, 8 | NODE_DEFAULT_HEIGHT, 9 | } from '@src/pages/workflow/app-design/constant'; 10 | import { customNodeGetAnchorShape } from '@src/pages/workflow/app-design/utils'; 11 | 12 | class CommentHtmlNode extends HtmlNode { 13 | constructor(props) { 14 | super(props); 15 | this.isMounted = false; 16 | this.r = h(CommentNode, { 17 | properties: props.model.properties, 18 | model: props.model, 19 | }); 20 | this.app = createApp({ 21 | render: () => this.r, 22 | }); 23 | this.app.use(ElementPlus); 24 | for (const [key, component] of Object.entries(ElementPlusIconsVue)) { 25 | this.app.component(`ElIcon${key}`, component); 26 | } 27 | } 28 | 29 | getAnchorShape(anchorData) { 30 | return customNodeGetAnchorShape.call(this, anchorData); 31 | } 32 | 33 | setHtml(rootEl) { 34 | if (!this.isMounted) { 35 | this.isMounted = true; 36 | const node = document.createElement('div'); 37 | rootEl.appendChild(node); 38 | this.app.mount(node); 39 | } else { 40 | this.r.component.props.properties = this.props.model.getProperties(); 41 | this.r.component.props.model = this.props.model; 42 | } 43 | } 44 | 45 | componentWillUnmount() { 46 | this.app.unmount(); 47 | } 48 | } 49 | 50 | class CommentHtmlNodeModel extends HtmlNodeModel { 51 | initNodeData(data) { 52 | super.initNodeData(data); 53 | this.text.draggable = false; 54 | this.text.editable = false; 55 | // 移除连线规则,因为注释节点不需要连线 56 | this.sourceRules = []; 57 | this.targetRules = []; 58 | 59 | this.resizable = true; 60 | } 61 | 62 | setAttributes() { 63 | const { width = NODE_DEFAULT_WIDTH, height = NODE_DEFAULT_HEIGHT } = 64 | this.properties; 65 | this.width = width; 66 | this.height = height; 67 | } 68 | 69 | getOutlineStyle() { 70 | const style = super.getOutlineStyle(); 71 | 72 | style.stroke = 'none'; 73 | style.strokeWidth = 0; 74 | return style; 75 | } 76 | 77 | getNodeStyle() { 78 | const style = super.getNodeStyle(); 79 | style.fill = 'none'; 80 | style.stroke = 'none'; 81 | return style; 82 | } 83 | 84 | getDefaultAnchor() { 85 | return []; 86 | } 87 | } 88 | 89 | export default { 90 | type: 'comment-node', 91 | view: CommentHtmlNode, 92 | model: CommentHtmlNodeModel, 93 | }; 94 | -------------------------------------------------------------------------------- /ui/src/pages/workflow/app-design/flow-node/components/default-endpoint-node.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 42 | -------------------------------------------------------------------------------- /ui/src/pages/workflow/app-design/flow-node/components/default-node.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 51 | -------------------------------------------------------------------------------- /ui/src/pages/workflow/app-design/flow-node/components/node-title.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 31 | -------------------------------------------------------------------------------- /ui/src/pages/workflow/app-design/flow-node/default/default.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 23 | -------------------------------------------------------------------------------- /ui/src/pages/workflow/app-design/flow-node/dynamic-node/dynamic-node.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 23 | -------------------------------------------------------------------------------- /ui/src/pages/workflow/app-design/flow-node/endpoint-node/endpoint-node.js: -------------------------------------------------------------------------------- 1 | import { HtmlNode, HtmlNodeModel } from '@logicflow/core'; 2 | import { createApp, h } from 'vue'; 3 | import ElementPlus from 'element-plus'; 4 | import * as ElementPlusIconsVue from '@element-plus/icons-vue'; 5 | import VueNode from '@src/pages/workflow/app-design/flow-node/endpoint-node/endpoint-node.vue'; 6 | import { 7 | NODE_TITLE_HEIGHT, 8 | NODE_DEFAULT_WIDTH, 9 | NODE_DEFAULT_HEIGHT, 10 | } from '@src/pages/workflow/app-design/constant'; 11 | import { customNodeGetAnchorShape } from '@src/pages/workflow/app-design/utils'; 12 | 13 | class VueHtmlNode extends HtmlNode { 14 | constructor(props) { 15 | super(props); 16 | this.isMounted = false; 17 | this.r = h(VueNode, { 18 | properties: props.model.properties, 19 | model: props.model, 20 | }); 21 | this.app = createApp({ 22 | render: () => this.r, 23 | }); 24 | this.app.use(ElementPlus); 25 | for (const [key, component] of Object.entries(ElementPlusIconsVue)) { 26 | this.app.component(`ElIcon${key}`, component); 27 | } 28 | } 29 | 30 | getAnchorShape(anchorData) { 31 | return customNodeGetAnchorShape.call(this, anchorData); 32 | } 33 | 34 | setHtml(rootEl) { 35 | if (!this.isMounted) { 36 | this.isMounted = true; 37 | const node = document.createElement('div'); 38 | rootEl.appendChild(node); 39 | this.app.mount(node); 40 | } else { 41 | this.r.component.props.properties = this.props.model.getProperties(); 42 | } 43 | } 44 | 45 | componentWillUnmount() { 46 | this.app.unmount(); 47 | } 48 | } 49 | 50 | class VueHtmlNodeModel extends HtmlNodeModel { 51 | initNodeData(data) { 52 | super.initNodeData(data); 53 | this.text.draggable = false; // 不允许文本被拖动 54 | this.text.editable = false; // 不允许文本被编辑 55 | this.resizable = false; //不允许缩放 56 | 57 | const inputOnlyAsTarget = { 58 | message: '只能连接输入锚点', 59 | validate: (sourceNode, targetNode, sourceAnchor, targetAnchor) => { 60 | const data = sourceNode.graphModel; 61 | return targetAnchor.type === 'input'; 62 | }, 63 | }; 64 | const oneOnlyAsSource = { 65 | message: '每个锚点只能连接一个节点', 66 | validate: (sourceNode, targetNode, sourceAnchor, targetAnchor) => { 67 | const outgoingEdgesSourceAnchorId = sourceNode.outgoing.edges.map( 68 | (item) => item.sourceAnchorId, 69 | ); 70 | return !outgoingEdgesSourceAnchorId.includes(sourceAnchor.id); 71 | }, 72 | }; 73 | this.sourceRules.push(inputOnlyAsTarget); 74 | this.sourceRules.push(oneOnlyAsSource); 75 | } 76 | 77 | setAttributes() { 78 | super.setAttributes(); 79 | const { width, height } = this.properties; 80 | this.width = width || NODE_DEFAULT_WIDTH; 81 | this.height = height || NODE_DEFAULT_HEIGHT; 82 | } 83 | 84 | getNodeStyle() { 85 | const style = super.getNodeStyle(); 86 | 87 | return style; 88 | } 89 | 90 | getOutlineStyle() { 91 | const style = super.getOutlineStyle(); 92 | style.stroke = 'none'; 93 | style.hover.stroke = 'none'; 94 | return style; 95 | } 96 | 97 | getDefaultAnchor() { 98 | const { id, x, y, width, height, properties } = this; 99 | const anchors = []; 100 | 101 | const pAnchors = properties.anchors || []; 102 | pAnchors.forEach((anchor) => { 103 | anchors.push({ 104 | x: x + width / 2, 105 | y: y - height / 2 + NODE_TITLE_HEIGHT + anchor.top + 10 + 2, 106 | id: anchor.id, 107 | type: anchor.type, 108 | name: anchor.name, 109 | isStatic: anchor.isStatic, 110 | }); 111 | }); 112 | 113 | return anchors; 114 | } 115 | } 116 | 117 | export default { 118 | type: 'endpoint-node', 119 | model: VueHtmlNodeModel, 120 | view: VueHtmlNode, 121 | }; 122 | -------------------------------------------------------------------------------- /ui/src/pages/workflow/app-design/flow-node/endpoint-node/endpoint-node.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 25 | -------------------------------------------------------------------------------- /ui/src/pages/workflow/app-design/flow-node/flow/flow.js: -------------------------------------------------------------------------------- 1 | import { HtmlNode, HtmlNodeModel } from '@logicflow/core'; 2 | import { createApp, h } from 'vue'; 3 | import ElementPlus from 'element-plus'; 4 | import * as ElementPlusIconsVue from '@element-plus/icons-vue'; 5 | import VueNode from '@src/pages/workflow/app-design/flow-node/flow/flow.vue'; 6 | import { 7 | NODE_TITLE_HEIGHT, 8 | NODE_DEFAULT_WIDTH, 9 | NODE_DEFAULT_HEIGHT, 10 | } from '@src/pages/workflow/app-design/constant'; 11 | import { customNodeGetAnchorShape } from '@src/pages/workflow/app-design/utils'; 12 | 13 | class VueHtmlNode extends HtmlNode { 14 | constructor(props) { 15 | super(props); 16 | this.isMounted = false; 17 | this.r = h(VueNode, { 18 | properties: props.model.properties, 19 | model: props.model, 20 | }); 21 | this.app = createApp({ 22 | render: () => this.r, 23 | }); 24 | this.app.use(ElementPlus); 25 | for (const [key, component] of Object.entries(ElementPlusIconsVue)) { 26 | this.app.component(`ElIcon${key}`, component); 27 | } 28 | } 29 | 30 | getAnchorShape(anchorData) { 31 | return customNodeGetAnchorShape.call(this, anchorData); 32 | } 33 | 34 | setHtml(rootEl) { 35 | if (!this.isMounted) { 36 | this.isMounted = true; 37 | const node = document.createElement('div'); 38 | rootEl.appendChild(node); 39 | this.app.mount(node); 40 | } else { 41 | this.r.component.props.properties = this.props.model.getProperties(); 42 | } 43 | } 44 | 45 | componentWillUnmount() { 46 | this.app.unmount(); 47 | } 48 | } 49 | 50 | class VueHtmlNodeModel extends HtmlNodeModel { 51 | initNodeData(data) { 52 | super.initNodeData(data); 53 | this.text.draggable = false; // 不允许文本被拖动 54 | this.text.editable = false; // 不允许文本被编辑 55 | this.resizable = false; //不允许缩放 56 | 57 | const inputOnlyAsTarget = { 58 | message: '只能连接输入锚点', 59 | validate: (sourceNode, targetNode, sourceAnchor, targetAnchor) => { 60 | const data = sourceNode.graphModel; 61 | return targetAnchor.type === 'input'; 62 | }, 63 | }; 64 | const oneOnlyAsSource = { 65 | message: '每个锚点只能连接一个节点', 66 | validate: (sourceNode, targetNode, sourceAnchor, targetAnchor) => { 67 | const outgoingEdgesSourceAnchorId = sourceNode.outgoing.edges.map( 68 | (item) => item.sourceAnchorId, 69 | ); 70 | return !outgoingEdgesSourceAnchorId.includes(sourceAnchor.id); 71 | }, 72 | }; 73 | this.sourceRules.push(inputOnlyAsTarget); 74 | this.sourceRules.push(oneOnlyAsSource); 75 | } 76 | 77 | setAttributes() { 78 | super.setAttributes(); 79 | const { width, height } = this.properties; 80 | this.width = width || NODE_DEFAULT_WIDTH; 81 | this.height = height || NODE_DEFAULT_HEIGHT; 82 | } 83 | 84 | getNodeStyle() { 85 | const style = super.getNodeStyle(); 86 | 87 | return style; 88 | } 89 | 90 | getOutlineStyle() { 91 | const style = super.getOutlineStyle(); 92 | style.stroke = 'none'; 93 | style.hover.stroke = 'none'; 94 | return style; 95 | } 96 | 97 | getDefaultAnchor() { 98 | const { id, x, y, width, height, properties } = this; 99 | const anchors = []; 100 | 101 | anchors.push({ 102 | x: x - width / 2, 103 | y: y - height / 2 + NODE_TITLE_HEIGHT / 2 + 2, 104 | id: `${id}_input`, 105 | type: 'input', 106 | }); 107 | 108 | const pAnchors = properties.anchors || []; 109 | pAnchors.forEach((anchor) => { 110 | anchors.push({ 111 | x: x + width / 2, 112 | y: y - height / 2 + NODE_TITLE_HEIGHT + anchor.top + 10 + 2, 113 | id: anchor.id, 114 | type: anchor.type, 115 | name: anchor.name, 116 | isStatic: anchor.isStatic, 117 | }); 118 | }); 119 | 120 | return anchors; 121 | } 122 | } 123 | 124 | export default { 125 | type: 'flow', 126 | model: VueHtmlNodeModel, 127 | view: VueHtmlNode, 128 | }; 129 | -------------------------------------------------------------------------------- /ui/src/pages/workflow/app-design/flow-node/flow/flow.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 25 | -------------------------------------------------------------------------------- /ui/src/pages/workflow/app-design/flow-node/for/for.js: -------------------------------------------------------------------------------- 1 | import { HtmlNode, HtmlNodeModel } from '@logicflow/core'; 2 | import { createApp, h } from 'vue'; 3 | import ElementPlus from 'element-plus'; 4 | import * as ElementPlusIconsVue from '@element-plus/icons-vue'; 5 | import VueNode from '@src/pages/workflow/app-design/flow-node/for/for.vue'; 6 | import { 7 | NODE_TITLE_HEIGHT, 8 | NODE_DEFAULT_WIDTH, 9 | NODE_DEFAULT_HEIGHT, 10 | } from '@src/pages/workflow/app-design/constant'; 11 | import { customNodeGetAnchorShape } from '@src/pages/workflow/app-design/utils'; 12 | 13 | class VueHtmlNode extends HtmlNode { 14 | constructor(props) { 15 | super(props); 16 | this.isMounted = false; 17 | this.r = h(VueNode, { 18 | properties: props.model.properties, 19 | model: props.model, 20 | }); 21 | this.app = createApp({ 22 | render: () => this.r, 23 | }); 24 | this.app.use(ElementPlus); 25 | for (const [key, component] of Object.entries(ElementPlusIconsVue)) { 26 | this.app.component(`ElIcon${key}`, component); 27 | } 28 | } 29 | 30 | getAnchorShape(anchorData) { 31 | return customNodeGetAnchorShape.call(this, anchorData); 32 | } 33 | 34 | setHtml(rootEl) { 35 | if (!this.isMounted) { 36 | this.isMounted = true; 37 | const node = document.createElement('div'); 38 | rootEl.appendChild(node); 39 | this.app.mount(node); 40 | } else { 41 | this.r.component.props.properties = this.props.model.getProperties(); 42 | } 43 | } 44 | 45 | componentWillUnmount() { 46 | this.app.unmount(); 47 | } 48 | } 49 | 50 | class VueHtmlNodeModel extends HtmlNodeModel { 51 | initNodeData(data) { 52 | super.initNodeData(data); 53 | this.text.draggable = false; // 不允许文本被拖动 54 | this.text.editable = false; // 不允许文本被编辑 55 | this.resizable = false; //不允许缩放 56 | 57 | const inputOnlyAsTarget = { 58 | message: '只能连接输入锚点', 59 | validate: (sourceNode, targetNode, sourceAnchor, targetAnchor) => { 60 | const data = sourceNode.graphModel; 61 | return targetAnchor.type === 'input'; 62 | }, 63 | }; 64 | const oneOnlyAsSource = { 65 | message: '每个锚点只能连接一个节点', 66 | validate: (sourceNode, targetNode, sourceAnchor, targetAnchor) => { 67 | const outgoingEdgesSourceAnchorId = sourceNode.outgoing.edges.map( 68 | (item) => item.sourceAnchorId, 69 | ); 70 | return !outgoingEdgesSourceAnchorId.includes(sourceAnchor.id); 71 | }, 72 | }; 73 | this.sourceRules.push(inputOnlyAsTarget); 74 | this.sourceRules.push(oneOnlyAsSource); 75 | } 76 | 77 | setAttributes() { 78 | super.setAttributes(); 79 | const { width, height } = this.properties; 80 | this.width = width || NODE_DEFAULT_WIDTH; 81 | this.height = height || NODE_DEFAULT_HEIGHT; 82 | } 83 | 84 | getNodeStyle() { 85 | const style = super.getNodeStyle(); 86 | 87 | return style; 88 | } 89 | 90 | getOutlineStyle() { 91 | const style = super.getOutlineStyle(); 92 | style.stroke = 'none'; 93 | style.hover.stroke = 'none'; 94 | return style; 95 | } 96 | 97 | getDefaultAnchor() { 98 | const { id, x, y, width, height, properties } = this; 99 | const anchors = []; 100 | 101 | anchors.push({ 102 | x: x - width / 2, 103 | y: y - height / 2 + NODE_TITLE_HEIGHT / 2 + 2, 104 | id: `${id}_input`, 105 | type: 'input', 106 | }); 107 | 108 | const pAnchors = properties.anchors || []; 109 | pAnchors.forEach((anchor) => { 110 | anchors.push({ 111 | x: x + width / 2, 112 | y: y - height / 2 + NODE_TITLE_HEIGHT + anchor.top + 10 + 2, 113 | id: anchor.id, 114 | type: anchor.type, 115 | name: anchor.name, 116 | isStatic: anchor.isStatic, 117 | }); 118 | }); 119 | 120 | return anchors; 121 | } 122 | } 123 | 124 | export default { 125 | type: 'for', 126 | model: VueHtmlNodeModel, 127 | view: VueHtmlNode, 128 | }; 129 | -------------------------------------------------------------------------------- /ui/src/pages/workflow/app-design/flow-node/for/for.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 25 | -------------------------------------------------------------------------------- /ui/src/pages/workflow/app-design/flow-node/fork/fork.js: -------------------------------------------------------------------------------- 1 | import { HtmlNode, HtmlNodeModel } from '@logicflow/core'; 2 | import { createApp, h } from 'vue'; 3 | import ElementPlus from 'element-plus'; 4 | import * as ElementPlusIconsVue from '@element-plus/icons-vue'; 5 | import VueNode from '@src/pages/workflow/app-design/flow-node/fork/fork.vue'; 6 | import { 7 | NODE_TITLE_HEIGHT, 8 | NODE_DEFAULT_WIDTH, 9 | NODE_DEFAULT_HEIGHT, 10 | } from '@src/pages/workflow/app-design/constant'; 11 | import { customNodeGetAnchorShape } from '@src/pages/workflow/app-design/utils'; 12 | 13 | class VueHtmlNode extends HtmlNode { 14 | constructor(props) { 15 | super(props); 16 | this.isMounted = false; 17 | this.r = h(VueNode, { 18 | properties: props.model.properties, 19 | model: props.model, 20 | }); 21 | this.app = createApp({ 22 | render: () => this.r, 23 | }); 24 | this.app.use(ElementPlus); 25 | for (const [key, component] of Object.entries(ElementPlusIconsVue)) { 26 | this.app.component(`ElIcon${key}`, component); 27 | } 28 | } 29 | 30 | getAnchorShape(anchorData) { 31 | return customNodeGetAnchorShape.call(this, anchorData); 32 | } 33 | 34 | setHtml(rootEl) { 35 | if (!this.isMounted) { 36 | this.isMounted = true; 37 | const node = document.createElement('div'); 38 | rootEl.appendChild(node); 39 | this.app.mount(node); 40 | } else { 41 | this.r.component.props.properties = this.props.model.getProperties(); 42 | } 43 | } 44 | 45 | componentWillUnmount() { 46 | this.app.unmount(); 47 | } 48 | } 49 | 50 | class VueHtmlNodeModel extends HtmlNodeModel { 51 | initNodeData(data) { 52 | super.initNodeData(data); 53 | this.text.draggable = false; // 不允许文本被拖动 54 | this.text.editable = false; // 不允许文本被编辑 55 | this.resizable = false; //不允许缩放 56 | 57 | const inputOnlyAsTarget = { 58 | message: '只能连接输入锚点', 59 | validate: (sourceNode, targetNode, sourceAnchor, targetAnchor) => { 60 | const data = sourceNode.graphModel; 61 | return targetAnchor.type === 'input'; 62 | }, 63 | }; 64 | const oneOnlyAsSource = { 65 | message: '每个锚点只能连接一个节点', 66 | validate: (sourceNode, targetNode, sourceAnchor, targetAnchor) => { 67 | const outgoingEdgesSourceAnchorId = sourceNode.outgoing.edges.map( 68 | (item) => item.sourceAnchorId, 69 | ); 70 | return !outgoingEdgesSourceAnchorId.includes(sourceAnchor.id); 71 | }, 72 | }; 73 | this.sourceRules.push(inputOnlyAsTarget); 74 | this.sourceRules.push(oneOnlyAsSource); 75 | } 76 | 77 | setAttributes() { 78 | super.setAttributes(); 79 | const { width, height } = this.properties; 80 | this.width = width || NODE_DEFAULT_WIDTH; 81 | this.height = height || NODE_DEFAULT_HEIGHT; 82 | } 83 | 84 | getNodeStyle() { 85 | const style = super.getNodeStyle(); 86 | 87 | return style; 88 | } 89 | 90 | getOutlineStyle() { 91 | const style = super.getOutlineStyle(); 92 | style.stroke = 'none'; 93 | style.hover.stroke = 'none'; 94 | return style; 95 | } 96 | 97 | getDefaultAnchor() { 98 | const { id, x, y, width, height, properties } = this; 99 | const anchors = []; 100 | 101 | anchors.push({ 102 | x: x - width / 2, 103 | y: y - height / 2 + NODE_TITLE_HEIGHT / 2 + 2, 104 | id: `${id}_input`, 105 | type: 'input', 106 | }); 107 | 108 | const pAnchors = properties.anchors || []; 109 | pAnchors.forEach((anchor) => { 110 | anchors.push({ 111 | x: x + width / 2, 112 | y: y - height / 2 + NODE_TITLE_HEIGHT + anchor.top + 10 + 2, 113 | id: anchor.id, 114 | type: anchor.type, 115 | name: anchor.name, 116 | isStatic: anchor.isStatic, 117 | }); 118 | }); 119 | 120 | return anchors; 121 | } 122 | } 123 | 124 | export default { 125 | type: 'fork', 126 | model: VueHtmlNodeModel, 127 | view: VueHtmlNode, 128 | }; 129 | -------------------------------------------------------------------------------- /ui/src/pages/workflow/app-design/flow-node/fork/fork.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 25 | -------------------------------------------------------------------------------- /ui/src/pages/workflow/app-design/flow-node/group-action/group-action.js: -------------------------------------------------------------------------------- 1 | import { HtmlNode, HtmlNodeModel } from '@logicflow/core'; 2 | import { createApp, h } from 'vue'; 3 | import ElementPlus from 'element-plus'; 4 | import * as ElementPlusIconsVue from '@element-plus/icons-vue'; 5 | import VueNode from '@src/pages/workflow/app-design/flow-node/group-action/group-action.vue'; 6 | import { 7 | NODE_TITLE_HEIGHT, 8 | NODE_DEFAULT_WIDTH, 9 | NODE_DEFAULT_HEIGHT, 10 | } from '@src/pages/workflow/app-design/constant'; 11 | import { customNodeGetAnchorShape } from '@src/pages/workflow/app-design/utils'; 12 | 13 | class VueHtmlNode extends HtmlNode { 14 | constructor(props) { 15 | super(props); 16 | this.isMounted = false; 17 | this.r = h(VueNode, { 18 | properties: props.model.properties, 19 | model: props.model, 20 | }); 21 | this.app = createApp({ 22 | render: () => this.r, 23 | }); 24 | this.app.use(ElementPlus); 25 | for (const [key, component] of Object.entries(ElementPlusIconsVue)) { 26 | this.app.component(`ElIcon${key}`, component); 27 | } 28 | } 29 | 30 | getAnchorShape(anchorData) { 31 | return customNodeGetAnchorShape.call(this, anchorData); 32 | } 33 | 34 | setHtml(rootEl) { 35 | if (!this.isMounted) { 36 | this.isMounted = true; 37 | const node = document.createElement('div'); 38 | rootEl.appendChild(node); 39 | this.app.mount(node); 40 | } else { 41 | this.r.component.props.properties = this.props.model.getProperties(); 42 | } 43 | } 44 | 45 | componentWillUnmount() { 46 | this.app.unmount(); 47 | } 48 | } 49 | 50 | class VueHtmlNodeModel extends HtmlNodeModel { 51 | initNodeData(data) { 52 | super.initNodeData(data); 53 | this.text.draggable = false; // 不允许文本被拖动 54 | this.text.editable = false; // 不允许文本被编辑 55 | this.resizable = false; //不允许缩放 56 | 57 | const inputOnlyAsTarget = { 58 | message: '只能连接输入锚点', 59 | validate: (sourceNode, targetNode, sourceAnchor, targetAnchor) => { 60 | const data = sourceNode.graphModel; 61 | return targetAnchor.type === 'input'; 62 | }, 63 | }; 64 | const oneOnlyAsSource = { 65 | message: '每个锚点只能连接一个节点', 66 | validate: (sourceNode, targetNode, sourceAnchor, targetAnchor) => { 67 | const outgoingEdgesSourceAnchorId = sourceNode.outgoing.edges.map( 68 | (item) => item.sourceAnchorId, 69 | ); 70 | return !outgoingEdgesSourceAnchorId.includes(sourceAnchor.id); 71 | }, 72 | }; 73 | this.sourceRules.push(inputOnlyAsTarget); 74 | this.sourceRules.push(oneOnlyAsSource); 75 | } 76 | 77 | setAttributes() { 78 | super.setAttributes(); 79 | const { width, height } = this.properties; 80 | this.width = width || NODE_DEFAULT_WIDTH; 81 | this.height = height || NODE_DEFAULT_HEIGHT; 82 | } 83 | 84 | getNodeStyle() { 85 | const style = super.getNodeStyle(); 86 | 87 | return style; 88 | } 89 | 90 | getOutlineStyle() { 91 | const style = super.getOutlineStyle(); 92 | style.stroke = 'none'; 93 | style.hover.stroke = 'none'; 94 | return style; 95 | } 96 | 97 | getDefaultAnchor() { 98 | const { id, x, y, width, height, properties } = this; 99 | const anchors = []; 100 | 101 | anchors.push({ 102 | x: x - width / 2, 103 | y: y - height / 2 + NODE_TITLE_HEIGHT / 2 + 2, 104 | id: `${id}_input`, 105 | type: 'input', 106 | }); 107 | 108 | const pAnchors = properties.anchors || []; 109 | pAnchors.forEach((anchor) => { 110 | anchors.push({ 111 | x: x + width / 2, 112 | y: y - height / 2 + NODE_TITLE_HEIGHT + anchor.top + 10 + 2, 113 | id: anchor.id, 114 | type: anchor.type, 115 | name: anchor.name, 116 | isStatic: anchor.isStatic, 117 | }); 118 | }); 119 | 120 | return anchors; 121 | } 122 | } 123 | 124 | export default { 125 | type: 'group-action', 126 | model: VueHtmlNodeModel, 127 | view: VueHtmlNode, 128 | }; 129 | -------------------------------------------------------------------------------- /ui/src/pages/workflow/app-design/flow-node/group-action/group-action.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 25 | -------------------------------------------------------------------------------- /ui/src/pages/workflow/app-design/flow-node/group-filter/group-filter.js: -------------------------------------------------------------------------------- 1 | import { HtmlNode, HtmlNodeModel } from '@logicflow/core'; 2 | import { createApp, h } from 'vue'; 3 | import ElementPlus from 'element-plus'; 4 | import * as ElementPlusIconsVue from '@element-plus/icons-vue'; 5 | import VueNode from '@src/pages/workflow/app-design/flow-node/group-filter/group-filter.vue'; 6 | import { 7 | NODE_TITLE_HEIGHT, 8 | NODE_DEFAULT_WIDTH, 9 | NODE_DEFAULT_HEIGHT, 10 | } from '@src/pages/workflow/app-design/constant'; 11 | import { customNodeGetAnchorShape } from '@src/pages/workflow/app-design/utils'; 12 | 13 | class VueHtmlNode extends HtmlNode { 14 | constructor(props) { 15 | super(props); 16 | this.isMounted = false; 17 | this.r = h(VueNode, { 18 | properties: props.model.properties, 19 | model: props.model, 20 | }); 21 | this.app = createApp({ 22 | render: () => this.r, 23 | }); 24 | this.app.use(ElementPlus); 25 | for (const [key, component] of Object.entries(ElementPlusIconsVue)) { 26 | this.app.component(`ElIcon${key}`, component); 27 | } 28 | } 29 | 30 | getAnchorShape(anchorData) { 31 | return customNodeGetAnchorShape.call(this, anchorData); 32 | } 33 | 34 | setHtml(rootEl) { 35 | if (!this.isMounted) { 36 | this.isMounted = true; 37 | const node = document.createElement('div'); 38 | rootEl.appendChild(node); 39 | this.app.mount(node); 40 | } else { 41 | this.r.component.props.properties = this.props.model.getProperties(); 42 | } 43 | } 44 | 45 | componentWillUnmount() { 46 | this.app.unmount(); 47 | } 48 | } 49 | 50 | class VueHtmlNodeModel extends HtmlNodeModel { 51 | initNodeData(data) { 52 | super.initNodeData(data); 53 | this.text.draggable = false; // 不允许文本被拖动 54 | this.text.editable = false; // 不允许文本被编辑 55 | this.resizable = false; //不允许缩放 56 | 57 | const inputOnlyAsTarget = { 58 | message: '只能连接输入锚点', 59 | validate: (sourceNode, targetNode, sourceAnchor, targetAnchor) => { 60 | const data = sourceNode.graphModel; 61 | return targetAnchor.type === 'input'; 62 | }, 63 | }; 64 | const oneOnlyAsSource = { 65 | message: '每个锚点只能连接一个节点', 66 | validate: (sourceNode, targetNode, sourceAnchor, targetAnchor) => { 67 | const outgoingEdgesSourceAnchorId = sourceNode.outgoing.edges.map( 68 | (item) => item.sourceAnchorId, 69 | ); 70 | return !outgoingEdgesSourceAnchorId.includes(sourceAnchor.id); 71 | }, 72 | }; 73 | this.sourceRules.push(inputOnlyAsTarget); 74 | this.sourceRules.push(oneOnlyAsSource); 75 | } 76 | 77 | setAttributes() { 78 | super.setAttributes(); 79 | const { width, height } = this.properties; 80 | this.width = width || NODE_DEFAULT_WIDTH; 81 | this.height = height || NODE_DEFAULT_HEIGHT; 82 | } 83 | 84 | getNodeStyle() { 85 | const style = super.getNodeStyle(); 86 | 87 | return style; 88 | } 89 | 90 | getOutlineStyle() { 91 | const style = super.getOutlineStyle(); 92 | style.stroke = 'none'; 93 | style.hover.stroke = 'none'; 94 | return style; 95 | } 96 | 97 | getDefaultAnchor() { 98 | const { id, x, y, width, height, properties } = this; 99 | const anchors = []; 100 | 101 | anchors.push({ 102 | x: x - width / 2, 103 | y: y - height / 2 + NODE_TITLE_HEIGHT / 2 + 2, 104 | id: `${id}_input`, 105 | type: 'input', 106 | }); 107 | 108 | const pAnchors = properties.anchors || []; 109 | pAnchors.forEach((anchor) => { 110 | anchors.push({ 111 | x: x + width / 2, 112 | y: y - height / 2 + NODE_TITLE_HEIGHT + anchor.top + 10 + 2, 113 | id: anchor.id, 114 | type: anchor.type, 115 | name: anchor.name, 116 | isStatic: anchor.isStatic, 117 | }); 118 | }); 119 | 120 | return anchors; 121 | } 122 | } 123 | 124 | export default { 125 | type: 'group-filter', 126 | model: VueHtmlNodeModel, 127 | view: VueHtmlNode, 128 | }; 129 | -------------------------------------------------------------------------------- /ui/src/pages/workflow/app-design/flow-node/group-filter/group-filter.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 25 | -------------------------------------------------------------------------------- /ui/src/pages/workflow/app-design/flow-node/index.js: -------------------------------------------------------------------------------- 1 | import StartNode from '@src/pages/workflow/app-design/flow-node/start/start'; 2 | import EndpointNode from '@src/pages/workflow/app-design/flow-node/endpoint-node/endpoint-node'; 3 | import ForkNode from '@src/pages/workflow/app-design/flow-node/fork/fork'; 4 | import GroupFilterNode from '@src/pages/workflow/app-design/flow-node/group-filter/group-filter'; 5 | import JsSwitchNode from '@src/pages/workflow/app-design/flow-node/js-switch/js-switch'; 6 | import MsgTypeSwitchNode from '@src/pages/workflow/app-design/flow-node/msg-type-switch/msg-type-switch'; 7 | import SwitchNode from '@src/pages/workflow/app-design/flow-node/switch/switch'; 8 | import CommentNode from '@src/pages/workflow/app-design/flow-node/comment/comment'; 9 | import ForNode from '@src/pages/workflow/app-design/flow-node/for/for'; 10 | import GroupActionNode from '@src/pages/workflow/app-design/flow-node/group-action/group-action'; 11 | import DefaultNode from '@src/pages/workflow/app-design/flow-node/default/default'; 12 | import DynamicNode from '@src/pages/workflow/app-design/flow-node/dynamic-node/dynamic-node'; 13 | 14 | export default [ 15 | StartNode, 16 | DefaultNode, 17 | EndpointNode, 18 | ForkNode, 19 | GroupFilterNode, 20 | JsSwitchNode, 21 | MsgTypeSwitchNode, 22 | SwitchNode, 23 | CommentNode, 24 | ForNode, 25 | GroupActionNode, 26 | DynamicNode, 27 | ]; 28 | -------------------------------------------------------------------------------- /ui/src/pages/workflow/app-design/flow-node/js-switch/js-switch.js: -------------------------------------------------------------------------------- 1 | import { HtmlNode, HtmlNodeModel } from '@logicflow/core'; 2 | import { createApp, h } from 'vue'; 3 | import ElementPlus from 'element-plus'; 4 | import * as ElementPlusIconsVue from '@element-plus/icons-vue'; 5 | import VueNode from '@src/pages/workflow/app-design/flow-node/js-switch/js-switch.vue'; 6 | import { 7 | NODE_TITLE_HEIGHT, 8 | NODE_DEFAULT_WIDTH, 9 | NODE_DEFAULT_HEIGHT, 10 | } from '@src/pages/workflow/app-design/constant'; 11 | import { customNodeGetAnchorShape } from '@src/pages/workflow/app-design/utils'; 12 | 13 | class VueHtmlNode extends HtmlNode { 14 | constructor(props) { 15 | super(props); 16 | this.isMounted = false; 17 | this.r = h(VueNode, { 18 | properties: props.model.properties, 19 | model: props.model, 20 | }); 21 | this.app = createApp({ 22 | render: () => this.r, 23 | }); 24 | this.app.use(ElementPlus); 25 | for (const [key, component] of Object.entries(ElementPlusIconsVue)) { 26 | this.app.component(`ElIcon${key}`, component); 27 | } 28 | } 29 | 30 | getAnchorShape(anchorData) { 31 | return customNodeGetAnchorShape.call(this, anchorData); 32 | } 33 | 34 | setHtml(rootEl) { 35 | if (!this.isMounted) { 36 | this.isMounted = true; 37 | const node = document.createElement('div'); 38 | rootEl.appendChild(node); 39 | this.app.mount(node); 40 | } else { 41 | this.r.component.props.properties = this.props.model.getProperties(); 42 | } 43 | } 44 | 45 | componentWillUnmount() { 46 | this.app.unmount(); 47 | } 48 | } 49 | 50 | class VueHtmlNodeModel extends HtmlNodeModel { 51 | initNodeData(data) { 52 | super.initNodeData(data); 53 | this.text.draggable = false; // 不允许文本被拖动 54 | this.text.editable = false; // 不允许文本被编辑 55 | this.resizable = false; //不允许缩放 56 | 57 | const inputOnlyAsTarget = { 58 | message: '只能连接输入锚点', 59 | validate: (sourceNode, targetNode, sourceAnchor, targetAnchor) => { 60 | const data = sourceNode.graphModel; 61 | return targetAnchor.type === 'input'; 62 | }, 63 | }; 64 | const oneOnlyAsSource = { 65 | message: '每个锚点只能连接一个节点', 66 | validate: (sourceNode, targetNode, sourceAnchor, targetAnchor) => { 67 | const outgoingEdgesSourceAnchorId = sourceNode.outgoing.edges.map( 68 | (item) => item.sourceAnchorId, 69 | ); 70 | return !outgoingEdgesSourceAnchorId.includes(sourceAnchor.id); 71 | }, 72 | }; 73 | this.sourceRules.push(inputOnlyAsTarget); 74 | this.sourceRules.push(oneOnlyAsSource); 75 | } 76 | 77 | setAttributes() { 78 | super.setAttributes(); 79 | const { width, height } = this.properties; 80 | this.width = width || NODE_DEFAULT_WIDTH; 81 | this.height = height || NODE_DEFAULT_HEIGHT; 82 | } 83 | 84 | getNodeStyle() { 85 | const style = super.getNodeStyle(); 86 | 87 | return style; 88 | } 89 | 90 | getOutlineStyle() { 91 | const style = super.getOutlineStyle(); 92 | style.stroke = 'none'; 93 | style.hover.stroke = 'none'; 94 | return style; 95 | } 96 | 97 | getDefaultAnchor() { 98 | const { id, x, y, width, height, properties } = this; 99 | const anchors = []; 100 | 101 | anchors.push({ 102 | x: x - width / 2, 103 | y: y - height / 2 + NODE_TITLE_HEIGHT / 2 + 2, 104 | id: `${id}_input`, 105 | type: 'input', 106 | }); 107 | 108 | const pAnchors = properties.anchors || []; 109 | pAnchors.forEach((anchor) => { 110 | anchors.push({ 111 | x: x + width / 2, 112 | y: y - height / 2 + NODE_TITLE_HEIGHT + anchor.top + 10 + 2, 113 | id: anchor.id, 114 | type: anchor.type, 115 | name: anchor.name, 116 | isStatic: anchor.isStatic, 117 | }); 118 | }); 119 | 120 | return anchors; 121 | } 122 | } 123 | 124 | export default { 125 | type: 'js-switch', 126 | model: VueHtmlNodeModel, 127 | view: VueHtmlNode, 128 | }; 129 | -------------------------------------------------------------------------------- /ui/src/pages/workflow/app-design/flow-node/js-switch/js-switch.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 25 | -------------------------------------------------------------------------------- /ui/src/pages/workflow/app-design/flow-node/msg-type-switch/msg-type-switch.js: -------------------------------------------------------------------------------- 1 | import { HtmlNode, HtmlNodeModel } from '@logicflow/core'; 2 | import { createApp, h } from 'vue'; 3 | import ElementPlus from 'element-plus'; 4 | import * as ElementPlusIconsVue from '@element-plus/icons-vue'; 5 | import VueNode from '@src/pages/workflow/app-design/flow-node/msg-type-switch/msg-type-switch.vue'; 6 | import { 7 | NODE_TITLE_HEIGHT, 8 | NODE_DEFAULT_WIDTH, 9 | NODE_DEFAULT_HEIGHT, 10 | } from '@src/pages/workflow/app-design/constant'; 11 | import { customNodeGetAnchorShape } from '@src/pages/workflow/app-design/utils'; 12 | 13 | class VueHtmlNode extends HtmlNode { 14 | constructor(props) { 15 | super(props); 16 | this.isMounted = false; 17 | this.r = h(VueNode, { 18 | properties: props.model.properties, 19 | model: props.model, 20 | }); 21 | this.app = createApp({ 22 | render: () => this.r, 23 | }); 24 | this.app.use(ElementPlus); 25 | for (const [key, component] of Object.entries(ElementPlusIconsVue)) { 26 | this.app.component(`ElIcon${key}`, component); 27 | } 28 | } 29 | 30 | getAnchorShape(anchorData) { 31 | return customNodeGetAnchorShape.call(this, anchorData); 32 | } 33 | 34 | setHtml(rootEl) { 35 | if (!this.isMounted) { 36 | this.isMounted = true; 37 | const node = document.createElement('div'); 38 | rootEl.appendChild(node); 39 | this.app.mount(node); 40 | } else { 41 | this.r.component.props.properties = this.props.model.getProperties(); 42 | } 43 | } 44 | 45 | componentWillUnmount() { 46 | this.app.unmount(); 47 | } 48 | } 49 | 50 | class VueHtmlNodeModel extends HtmlNodeModel { 51 | initNodeData(data) { 52 | super.initNodeData(data); 53 | this.text.draggable = false; // 不允许文本被拖动 54 | this.text.editable = false; // 不允许文本被编辑 55 | this.resizable = false; //不允许缩放 56 | 57 | const inputOnlyAsTarget = { 58 | message: '只能连接输入锚点', 59 | validate: (sourceNode, targetNode, sourceAnchor, targetAnchor) => { 60 | const data = sourceNode.graphModel; 61 | return targetAnchor.type === 'input'; 62 | }, 63 | }; 64 | const oneOnlyAsSource = { 65 | message: '每个锚点只能连接一个节点', 66 | validate: (sourceNode, targetNode, sourceAnchor, targetAnchor) => { 67 | const outgoingEdgesSourceAnchorId = sourceNode.outgoing.edges.map( 68 | (item) => item.sourceAnchorId, 69 | ); 70 | return !outgoingEdgesSourceAnchorId.includes(sourceAnchor.id); 71 | }, 72 | }; 73 | this.sourceRules.push(inputOnlyAsTarget); 74 | this.sourceRules.push(oneOnlyAsSource); 75 | } 76 | 77 | setAttributes() { 78 | super.setAttributes(); 79 | const { width, height } = this.properties; 80 | this.width = width || NODE_DEFAULT_WIDTH; 81 | this.height = height || NODE_DEFAULT_HEIGHT; 82 | } 83 | 84 | getNodeStyle() { 85 | const style = super.getNodeStyle(); 86 | 87 | return style; 88 | } 89 | 90 | getOutlineStyle() { 91 | const style = super.getOutlineStyle(); 92 | style.stroke = 'none'; 93 | style.hover.stroke = 'none'; 94 | return style; 95 | } 96 | 97 | getDefaultAnchor() { 98 | const { id, x, y, width, height, properties } = this; 99 | const anchors = []; 100 | 101 | anchors.push({ 102 | x: x - width / 2, 103 | y: y - height / 2 + NODE_TITLE_HEIGHT / 2 + 2, 104 | id: `${id}_input`, 105 | type: 'input', 106 | }); 107 | 108 | const pAnchors = properties.anchors || []; 109 | pAnchors.forEach((anchor) => { 110 | anchors.push({ 111 | x: x + width / 2, 112 | y: y - height / 2 + NODE_TITLE_HEIGHT + anchor.top + 10 + 2, 113 | id: anchor.id, 114 | type: anchor.type, 115 | name: anchor.name, 116 | isStatic: anchor.isStatic, 117 | }); 118 | }); 119 | 120 | return anchors; 121 | } 122 | } 123 | 124 | export default { 125 | type: 'msg-type-switch', 126 | model: VueHtmlNodeModel, 127 | view: VueHtmlNode, 128 | }; 129 | -------------------------------------------------------------------------------- /ui/src/pages/workflow/app-design/flow-node/msg-type-switch/msg-type-switch.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 25 | -------------------------------------------------------------------------------- /ui/src/pages/workflow/app-design/flow-node/ref/ref.js: -------------------------------------------------------------------------------- 1 | import { HtmlNode, HtmlNodeModel } from '@logicflow/core'; 2 | import { createApp, h } from 'vue'; 3 | import ElementPlus from 'element-plus'; 4 | import * as ElementPlusIconsVue from '@element-plus/icons-vue'; 5 | import VueNode from '@src/pages/workflow/app-design/flow-node/ref/ref.vue'; 6 | import { 7 | NODE_TITLE_HEIGHT, 8 | NODE_DEFAULT_WIDTH, 9 | NODE_DEFAULT_HEIGHT, 10 | } from '@src/pages/workflow/app-design/constant'; 11 | import { customNodeGetAnchorShape } from '@src/pages/workflow/app-design/utils'; 12 | 13 | class VueHtmlNode extends HtmlNode { 14 | constructor(props) { 15 | super(props); 16 | this.isMounted = false; 17 | this.r = h(VueNode, { 18 | properties: props.model.properties, 19 | model: props.model, 20 | }); 21 | this.app = createApp({ 22 | render: () => this.r, 23 | }); 24 | this.app.use(ElementPlus); 25 | for (const [key, component] of Object.entries(ElementPlusIconsVue)) { 26 | this.app.component(`ElIcon${key}`, component); 27 | } 28 | } 29 | 30 | getAnchorShape(anchorData) { 31 | return customNodeGetAnchorShape.call(this, anchorData); 32 | } 33 | 34 | setHtml(rootEl) { 35 | if (!this.isMounted) { 36 | this.isMounted = true; 37 | const node = document.createElement('div'); 38 | rootEl.appendChild(node); 39 | this.app.mount(node); 40 | } else { 41 | this.r.component.props.properties = this.props.model.getProperties(); 42 | } 43 | } 44 | 45 | componentWillUnmount() { 46 | this.app.unmount(); 47 | } 48 | } 49 | 50 | class VueHtmlNodeModel extends HtmlNodeModel { 51 | initNodeData(data) { 52 | super.initNodeData(data); 53 | this.text.draggable = false; // 不允许文本被拖动 54 | this.text.editable = false; // 不允许文本被编辑 55 | this.resizable = false; //不允许缩放 56 | 57 | const inputOnlyAsTarget = { 58 | message: '只能连接输入锚点', 59 | validate: (sourceNode, targetNode, sourceAnchor, targetAnchor) => { 60 | const data = sourceNode.graphModel; 61 | return targetAnchor.type === 'input'; 62 | }, 63 | }; 64 | const oneOnlyAsSource = { 65 | message: '每个锚点只能连接一个节点', 66 | validate: (sourceNode, targetNode, sourceAnchor, targetAnchor) => { 67 | const outgoingEdgesSourceAnchorId = sourceNode.outgoing.edges.map( 68 | (item) => item.sourceAnchorId, 69 | ); 70 | return !outgoingEdgesSourceAnchorId.includes(sourceAnchor.id); 71 | }, 72 | }; 73 | this.sourceRules.push(inputOnlyAsTarget); 74 | this.sourceRules.push(oneOnlyAsSource); 75 | } 76 | 77 | setAttributes() { 78 | super.setAttributes(); 79 | const { width, height } = this.properties; 80 | this.width = width || NODE_DEFAULT_WIDTH; 81 | this.height = height || NODE_DEFAULT_HEIGHT; 82 | } 83 | 84 | getNodeStyle() { 85 | const style = super.getNodeStyle(); 86 | 87 | return style; 88 | } 89 | 90 | getOutlineStyle() { 91 | const style = super.getOutlineStyle(); 92 | style.stroke = 'none'; 93 | style.hover.stroke = 'none'; 94 | return style; 95 | } 96 | 97 | getDefaultAnchor() { 98 | const { id, x, y, width, height, properties } = this; 99 | const anchors = []; 100 | 101 | anchors.push({ 102 | x: x - width / 2, 103 | y: y - height / 2 + NODE_TITLE_HEIGHT / 2 + 2, 104 | id: `${id}_input`, 105 | type: 'input', 106 | }); 107 | 108 | const pAnchors = properties.anchors || []; 109 | pAnchors.forEach((anchor) => { 110 | anchors.push({ 111 | x: x + width / 2, 112 | y: y - height / 2 + NODE_TITLE_HEIGHT + anchor.top + 10 + 2, 113 | id: anchor.id, 114 | type: anchor.type, 115 | name: anchor.name, 116 | isStatic: anchor.isStatic, 117 | }); 118 | }); 119 | 120 | return anchors; 121 | } 122 | } 123 | 124 | export default { 125 | type: 'ref', 126 | model: VueHtmlNodeModel, 127 | view: VueHtmlNode, 128 | }; 129 | -------------------------------------------------------------------------------- /ui/src/pages/workflow/app-design/flow-node/ref/ref.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 25 | -------------------------------------------------------------------------------- /ui/src/pages/workflow/app-design/flow-node/start/start.js: -------------------------------------------------------------------------------- 1 | import { HtmlNode, HtmlNodeModel, h as lfh } from '@logicflow/core'; 2 | import { createApp, h } from 'vue'; 3 | import ElementPlus from 'element-plus'; 4 | import * as ElementPlusIconsVue from '@element-plus/icons-vue'; 5 | import VueNode from '@src/pages/workflow/app-design/flow-node/start/start.vue'; 6 | import { NODE_TITLE_HEIGHT } from '@src/pages/workflow/app-design/constant'; 7 | import { customNodeGetAnchorShape } from '@src/pages/workflow/app-design/utils'; 8 | 9 | const defaultWidth = 240; 10 | const defaultHeight = 40; 11 | 12 | class VueHtmlNode extends HtmlNode { 13 | constructor(props) { 14 | super(props); 15 | this.isMounted = false; 16 | this.r = h(VueNode, { 17 | properties: props.model.properties, 18 | model: props.model, 19 | }); 20 | this.app = createApp({ 21 | render: () => this.r, 22 | }); 23 | this.app.use(ElementPlus); 24 | for (const [key, component] of Object.entries(ElementPlusIconsVue)) { 25 | this.app.component(`ElIcon${key}`, component); 26 | } 27 | } 28 | 29 | getAnchorShape(anchorData) { 30 | return customNodeGetAnchorShape.call(this, anchorData); 31 | } 32 | 33 | setHtml(rootEl) { 34 | if (!this.isMounted) { 35 | this.isMounted = true; 36 | const node = document.createElement('div'); 37 | rootEl.appendChild(node); 38 | this.app.mount(node); 39 | } else { 40 | this.r.component.props.properties = this.props.model.getProperties(); 41 | } 42 | } 43 | 44 | componentWillUnmount() { 45 | this.app.unmount(); 46 | } 47 | } 48 | 49 | class VueHtmlNodeModel extends HtmlNodeModel { 50 | initNodeData(data) { 51 | super.initNodeData(data); 52 | this.text.draggable = false; // 不允许文本被拖动 53 | this.text.editable = false; // 不允许文本被编辑 54 | this.resizable = false; //不允许缩放 55 | } 56 | 57 | setAttributes() { 58 | super.setAttributes(); 59 | const { width, height } = this.properties; 60 | this.width = width || defaultWidth; 61 | this.height = height || defaultHeight; 62 | } 63 | 64 | getNodeStyle() { 65 | const style = super.getNodeStyle(); 66 | 67 | return style; 68 | } 69 | 70 | getOutlineStyle() { 71 | const style = super.getOutlineStyle(); 72 | style.stroke = 'none'; 73 | style.hover.stroke = 'none'; 74 | return style; 75 | } 76 | 77 | getDefaultAnchor() { 78 | const { id, x, y, width, height } = this; 79 | const anchors = []; 80 | 81 | anchors.push({ 82 | x: x + width / 2, 83 | y: y - height / 2 + NODE_TITLE_HEIGHT / 2 + 2, 84 | id: `${id}_output`, 85 | type: 'output', 86 | }); 87 | 88 | return anchors; 89 | } 90 | } 91 | 92 | export default { 93 | type: 'start', 94 | model: VueHtmlNodeModel, 95 | view: VueHtmlNode, 96 | defaultWidth, 97 | defaultHeight, 98 | }; 99 | -------------------------------------------------------------------------------- /ui/src/pages/workflow/app-design/flow-node/start/start.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 23 | -------------------------------------------------------------------------------- /ui/src/pages/workflow/app-design/flow-node/switch/and-item.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 51 | -------------------------------------------------------------------------------- /ui/src/pages/workflow/app-design/flow-node/switch/case-item.vue: -------------------------------------------------------------------------------- 1 | 87 | 88 | 116 | -------------------------------------------------------------------------------- /ui/src/pages/workflow/app-design/flow-node/switch/switch.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 71 | -------------------------------------------------------------------------------- /ui/src/pages/workflow/app-design/node-form/base-info.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 55 | -------------------------------------------------------------------------------- /ui/src/pages/workflow/app-design/node-form/next-node/next-node.vue: -------------------------------------------------------------------------------- 1 | 54 | 55 | 121 | -------------------------------------------------------------------------------- /ui/src/pages/workflow/app-design/node-form/next-node/node-item.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 69 | -------------------------------------------------------------------------------- /ui/src/pages/workflow/app-design/node-form/next-node/node.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 58 | -------------------------------------------------------------------------------- /ui/src/pages/workflow/app-design/node-form/node-form.vue: -------------------------------------------------------------------------------- 1 | 64 | 65 | 113 | -------------------------------------------------------------------------------- /ui/src/pages/workflow/app-design/run-drawer/json-editor.vue: -------------------------------------------------------------------------------- 1 | 63 | 64 | 69 | 70 | 76 | -------------------------------------------------------------------------------- /ui/src/pages/workflow/app-manage/app-integration.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 77 | -------------------------------------------------------------------------------- /ui/src/pages/workflow/app-manage/app-manage.vue: -------------------------------------------------------------------------------- 1 | 62 | 63 | 101 | -------------------------------------------------------------------------------- /ui/src/pages/workflow/app-manage/base-info.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 71 | -------------------------------------------------------------------------------- /ui/src/pages/workflow/app-source-code/app-source-code.vue: -------------------------------------------------------------------------------- 1 | 57 | 60 | -------------------------------------------------------------------------------- /ui/src/pages/workflow/app-split-screen/app-split-screen.vue: -------------------------------------------------------------------------------- 1 | 84 | 97 | -------------------------------------------------------------------------------- /ui/src/pages/workflow/chat-list-view/ai-chat-drawer.vue: -------------------------------------------------------------------------------- 1 | 14 | -------------------------------------------------------------------------------- /ui/src/router/index.js: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHashHistory } from 'vue-router'; 2 | import { SESSIONSTORAGE_KEYS, getSession } from '@src/utils/sessionstorage'; 3 | 4 | const routes = [ 5 | { 6 | path: '/', 7 | redirect: '/workflow-list', 8 | component: () => import('@src/layout/default-layout.vue'), 9 | children: [ 10 | { 11 | path: '/workflow-list', 12 | name: 'workflow-list', 13 | component: () => import('@src/pages/workflow-list/workflow-list.vue'), 14 | }, 15 | { 16 | path: '/share-node-list', 17 | name: 'share-node-list', 18 | component: () => 19 | import('@src/pages/share-node-list/share-node-list.vue'), 20 | }, 21 | ], 22 | }, 23 | { 24 | path: '/workflow', 25 | name: 'workflow', 26 | component: () => import('@src/pages/workflow/workflow.vue'), 27 | }, 28 | { 29 | path: '/login', 30 | name: 'login', 31 | component: () => import('@src/pages/login/login.vue'), 32 | }, 33 | ]; 34 | 35 | const router = createRouter({ 36 | history: createWebHashHistory(), 37 | routes, 38 | }); 39 | 40 | router.beforeEach((to, from, next) => { 41 | const token = getSession(SESSIONSTORAGE_KEYS.TOKEN); 42 | if (token) { 43 | if (to.path === '/login') { 44 | next(from); 45 | } else { 46 | next(); 47 | } 48 | } else { 49 | if (to.path !== '/login') { 50 | next('/login'); 51 | } else { 52 | next(); 53 | } 54 | } 55 | }); 56 | 57 | export default router; 58 | -------------------------------------------------------------------------------- /ui/src/store/index.js: -------------------------------------------------------------------------------- 1 | import { createPinia } from 'pinia'; 2 | 3 | const pinia = createPinia(); 4 | 5 | export default pinia; 6 | -------------------------------------------------------------------------------- /ui/src/store/module/user.js: -------------------------------------------------------------------------------- 1 | import { ref, computed } from 'vue'; 2 | import { defineStore } from 'pinia'; 3 | 4 | const useUserStore = defineStore('user', () => { 5 | const count = ref(0); 6 | const doubleCount = computed(() => count.value * 2); 7 | 8 | function increment() { 9 | count.value++; 10 | } 11 | 12 | return { count, doubleCount, increment }; 13 | }); 14 | 15 | export default useUserStore; 16 | -------------------------------------------------------------------------------- /ui/src/style/element-plus.css: -------------------------------------------------------------------------------- 1 | .el-form-item .el-form-item { 2 | margin-bottom: 18px; 3 | } 4 | 5 | /* 自定义样式 */ 6 | .run-drawer .el-drawer__header { 7 | margin: 0; 8 | } 9 | -------------------------------------------------------------------------------- /ui/src/style/logic-flow.css: -------------------------------------------------------------------------------- 1 | .lf-custom-anchor-point { 2 | transition: all 0.3s ease-in-out; 3 | } 4 | .lf-custom-anchor-point:hover { 5 | transform: scale(1.25); 6 | } 7 | -------------------------------------------------------------------------------- /ui/src/style/tailwind.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /ui/src/utils/event-bus.js: -------------------------------------------------------------------------------- 1 | import { useEventBus } from '@vueuse/core'; 2 | 3 | export default { 4 | // 用于 flow 编辑器 5 | updateNodeProperties: () => useEventBus('updateNodeProperties'), 6 | closeNodeForm: () => useEventBus('closeNodeForm'), 7 | showNodeMenu: () => useEventBus('showNodeMenu'), 8 | changeFlowNode: () => useEventBus('changeFlowNode'), 9 | jumpToNode: () => useEventBus('jumpToNode'), 10 | deleteFlowNodeById: () => useEventBus('deleteFlowNodeById'), 11 | refreshNodeLog: () => useEventBus('refreshNodeLog'), 12 | clearNodeFormValidate: () => useEventBus('clearNodeFormValidate'), 13 | logicflowNodeMouseUp: () => useEventBus('logicflowNodeMouseUp'), 14 | }; 15 | -------------------------------------------------------------------------------- /ui/src/utils/request.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { SESSIONSTORAGE_KEYS, getSession } from '@src/utils/sessionstorage'; 3 | import { ElMessage } from 'element-plus'; 4 | 5 | export const baseURL = window.config.baseURL + '/api/v1'; 6 | 7 | export const request = axios.create({ 8 | baseURL, 9 | timeout: 1000 * 5, 10 | }); 11 | 12 | export const rawRequest = axios.create({ 13 | baseURL, 14 | timeout: 1000 * 5, 15 | }); 16 | 17 | request.interceptors.request.use( 18 | (config) => { 19 | const token = getSession(SESSIONSTORAGE_KEYS.TOKEN); 20 | if (token) { 21 | config.headers.Authorization = `Bearer ${token}`; 22 | } 23 | return config; 24 | }, 25 | (error) => { 26 | return Promise.reject(error); 27 | }, 28 | ); 29 | 30 | request.interceptors.response.use( 31 | (response) => { 32 | return response.data; 33 | }, 34 | (error) => { 35 | if (error.response) { 36 | const { status, data } = error.response; 37 | ElMessage.error(`Error ${status}: ${data.message || JSON.stringify(data)}`); 38 | } else { 39 | ElMessage.error(`Error: ${error.message}`); 40 | } 41 | return Promise.reject(error); 42 | }, 43 | ); 44 | -------------------------------------------------------------------------------- /ui/src/utils/sessionstorage.js: -------------------------------------------------------------------------------- 1 | export const SESSIONSTORAGE_KEYS = { 2 | TOKEN: 'token', 3 | }; 4 | 5 | export function getSession(key) { 6 | return JSON.parse(sessionStorage.getItem(key)); 7 | } 8 | 9 | export function setSession(key, value) { 10 | sessionStorage.setItem(key, JSON.stringify(value)); 11 | } 12 | -------------------------------------------------------------------------------- /ui/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | darkMode: 'selector', 4 | content: [ 5 | './index.html', 6 | './src/**/*.{vue,js,ts,jsx,tsx}', 7 | './base/**/*.{vue,js,ts,jsx,tsx}', 8 | './domain/**/*.{vue,js,ts,jsx,tsx}', 9 | ], 10 | theme: { 11 | extend: { 12 | colors: { 13 | primary: '#409EFF', 14 | success: '#67C23A', 15 | warning: '#E6A23C', 16 | danger: '#F56C6C', 17 | info: '#909399', 18 | border: { 19 | DEFAULT: '#DCDFE6', 20 | light: '#DCDFE6', 21 | dark: '#4C4D4F', 22 | }, 23 | background: { 24 | DEFAULT: '#FFFFFF', 25 | light: '#FFFFFF', 26 | dark: '#2C2D2F', 27 | }, 28 | }, 29 | }, 30 | }, 31 | plugins: ['@tailwindcss/line-clamp'], 32 | }; 33 | -------------------------------------------------------------------------------- /ui/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import { resolve } from 'path'; 3 | import vue from '@vitejs/plugin-vue'; 4 | import svgLoader from 'vite-svg-loader' 5 | 6 | // https://vite.dev/config/ 7 | export default defineConfig({ 8 | // base: '/rulego-ipaas-ui/', 9 | 10 | plugins: [vue(),svgLoader()], 11 | resolve: { 12 | alias: { 13 | '@src': resolve(__dirname, 'src'), 14 | }, 15 | }, 16 | }); 17 | --------------------------------------------------------------------------------