diff --git a/.github/workflows/agent.yml b/.github/workflows/agent.yml index a4c00272fbe..db418975aff 100644 --- a/.github/workflows/agent.yml +++ b/.github/workflows/agent.yml @@ -35,7 +35,7 @@ jobs: key: ${{ runner.os }}-go-${{ hashFiles('src/agent/agent/go.sum') }} - run: make BUILD_OUT_TAG=out clean all working-directory: src/agent/agent/ - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 if: ${{ inputs.upload == 'true' }} with: name: agent diff --git a/.github/workflows/backend.yml b/.github/workflows/backend.yml index 69af7fdec74..91014b65291 100644 --- a/.github/workflows/backend.yml +++ b/.github/workflows/backend.yml @@ -55,7 +55,7 @@ jobs: run: | ./gradlew clean test build :core:worker:worker-agent:shadowJar \ -DmysqlURL=127.0.0.1:${{ job.services.mysql.ports['3306'] }} -DmysqlUser=root -DmysqlPasswd=root --no-daemon - - uses: actions/upload-artifact@v1 + - uses: actions/upload-artifact@v4 if: ${{ inputs.upload == 'true' }} with: name: backend-jar @@ -95,7 +95,7 @@ jobs: run: | ./gradlew clean test build :core:worker:worker-agent:shadowJar -Ddevops.assemblyMode=KUBERNETES \ -DmysqlURL=127.0.0.1:${{ job.services.mysql.ports['3306'] }} -DmysqlUser=root -DmysqlPasswd=root --no-daemon - - uses: actions/upload-artifact@v1 + - uses: actions/upload-artifact@v4 if: ${{ inputs.upload == 'true' }} with: name: backend-docker diff --git a/.github/workflows/frontend.yml b/.github/workflows/frontend.yml index 75a8fc5c6fe..e5c7c8d0718 100644 --- a/.github/workflows/frontend.yml +++ b/.github/workflows/frontend.yml @@ -42,7 +42,7 @@ jobs: export NODE_OPTIONS=--openssl-legacy-provider yarn public working-directory: src/frontend - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 if: ${{ inputs.upload == 'true' }} with: name: frontend diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index db4b0eb2eeb..1bd2cc31aa6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -30,17 +30,17 @@ jobs: steps: - uses: actions/checkout@v3 - name: download frontend - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v4.1.7 with: name: frontend path: src/frontend/frontend - name: download agent - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v4.1.7 with: name: agent path: src/agent/agent/bin/ - name: download backend - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v4.1.7 with: name: backend-jar path: src/backend/ci/release @@ -49,7 +49,7 @@ jobs: version="$(basename $GITHUB_REF)" echo "version=$version" >> $GITHUB_OUTPUT ci_ms_wip="sign,monitoring" ci_pkg_dir=/dev/shm/ci ./scripts/packager-ci.sh "$version" bkci-slim.tar.gz - - uses: actions/upload-artifact@v1 + - uses: actions/upload-artifact@v4 with: name: bkci-slim path: bkci-slim.tar.gz @@ -61,17 +61,17 @@ jobs: steps: - uses: actions/checkout@v3 - name: download frontend - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v4.1.7 with: name: frontend path: src/frontend/frontend - name: download agent - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v4.1.7 with: name: agent path: src/agent/agent/bin/ - name: download bkci - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v4.1.7 with: name: backend-docker path: src/backend/ci/release @@ -120,7 +120,7 @@ jobs: version="$(basename $GITHUB_REF)" helm package . --version $version --app-version $version mv bk-ci-$version.tgz bk-ci-charts.tgz - - uses: actions/upload-artifact@v1 + - uses: actions/upload-artifact@v4 with: name: bkci-chart path: helm-charts/core/ci/bk-ci-charts.tgz @@ -131,12 +131,12 @@ jobs: needs: [package-zip, package-helm] steps: - name: download bkci-slim - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v4.1.7 with: name: bkci-slim path: ./ - name: download bkci-chart - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v4.1.7 with: name: bkci-chart path: ./ diff --git a/CHANGELOG/CHANGELOG-3.0.md b/CHANGELOG/CHANGELOG-3.0.md new file mode 100644 index 00000000000..36d6e65831f --- /dev/null +++ b/CHANGELOG/CHANGELOG-3.0.md @@ -0,0 +1,223 @@ + +- [v3.0.0](#v300) + - [Changelog since v2.1.0](#changelog-since-v210) + + + + + + +# v3.0.0 +## Changelog since v2.1.0 +#### 新增 +##### 流水线 +- pipeline as code + - [新增] feat:草稿版本UI展示 [链接](http://github.com/TencentBlueKing/bk-ci/issues/9861) + - [新增] 流水线版本管理机制 [链接](http://github.com/TencentBlueKing/bk-ci/issues/8161) + - [新增]【PAC】feat:开启PAC模式的代码库支持自动同步代码库YAML变更到蓝盾 [链接](http://github.com/TencentBlueKing/bk-ci/issues/8130) + - [新增] pac ui编辑流水线 [链接](http://github.com/TencentBlueKing/bk-ci/issues/8125) + - [新增] Code 方式创建的流水线,变量面板-输出变量未获取到问题优化 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10755) + - [新增] 新建/编辑流水线时支持调试流水线 [链接](http://github.com/TencentBlueKing/bk-ci/issues/8164) + - [新增] 上下文使用范围限定 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10655) + - [新增] 【PAC】feat:流水线常量 Code 语法和规范 [链接](http://github.com/TencentBlueKing/bk-ci/issues/9971) + - [新增] 发布流水线页面「静态」流水线组优化 [链接](http://github.com/TencentBlueKing/bk-ci/issues/9962) + - [新增] 动态流水线组支持根据代码库/.ci下的一级目录进行条件分组 [链接](http://github.com/TencentBlueKing/bk-ci/issues/9682) + - [新增] 【PAC】feat:支持code 方式禁用流水线 [链接](http://github.com/TencentBlueKing/bk-ci/issues/9788) + - [新增] 流水线维护过程中记录操作日志 [链接](http://github.com/TencentBlueKing/bk-ci/issues/8197) + - [新增] 【PAC】跨项目复用构建资源池,支持Code配置 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10225) + - [新增] 【PAC】feat:自定义构建号格式支持 Code 方式定义 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10210) + - [新增] 编辑变量交互优化 [链接](http://github.com/TencentBlueKing/bk-ci/issues/9652) + - [新增] 流水线构建详情页支持一键展开/收起 job [链接](http://github.com/TencentBlueKing/bk-ci/issues/9775) + - [新增] 支持蓝盾新表达式运行条件 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10467) + - [新增] 发布流水线页面,PAC模式增加说明 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10482) + - [新增] [PAC] code互转对api用户的影响 [链接](http://github.com/TencentBlueKing/bk-ci/issues/9813) + - [新增] 调试记录提示和入口优化 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10720) + - [新增] 流水线变量支持手动拖拽调整顺序 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10458) + - [新增] 流水线备注支持 上下文方式 设置和引用 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10459) + - [新增] 拉取构件支持流水线调试模式 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10291) + - [新增] 【PAC】feat:查看流水线 [链接](http://github.com/TencentBlueKing/bk-ci/issues/8195) +- [新增] 支持流水线指标监控 [链接](http://github.com/TencentBlueKing/bk-ci/issues/9860) +- [新增] 流水线权限代持功能重构 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10356) + - [新增] 增加权限代持人变量 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10890) +- [新增] 流水线模板设置优化 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10857) +- [新增] 流水线执行历史支持根据触发人筛选 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10752) +- [新增] 流水线通知方式未生效时的交互优化 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10615) +- [新增] 工蜂MR触发器支持设置监听的action [链接](http://github.com/TencentBlueKing/bk-ci/issues/8949) +- [新增] MR 事件触发器支持 WIP [链接](http://github.com/TencentBlueKing/bk-ci/issues/10683) +- [新增] P4触发器支持 Code 编写 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10551) +- [新增] Git事件触发器自定义触发条件支持通过 Code 方式定义 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10497) +- [新增] 流水线日志颜色优化 [链接](http://github.com/TencentBlueKing/bk-ci/issues/9934) +- [新增] openapi 触发流水线运行时,支持传入触发材料 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10302) +- [新增] 日志需要展示特殊字符 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10097) +- [新增] 流水线重命名优化 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10399) +- [新增] SVN事件触发的路径匹配规则增加兜底逻辑 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10510) +- [新增] 流水线执行历史列表增加「执行耗时」字段 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10251) +- [新增] 【蓝盾-产品-已评审】流水线支持展示运行进度 [链接](http://github.com/TencentBlueKing/bk-ci/issues/7932) +- [新增] 构建历史列表支持展示构建信息字段 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10724) +- [新增] 流水线支持POJO 属性按顺序导出 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10728) +- [新增] 流水线“文件”类型的变量优化 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10400) +- [新增] 定时触发器支持指定代码库和分支 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10300) +- [新增] 流水线模板管理编辑和实例管理优化 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10626) +- [新增] 保存流水线时校验引用到的子流水线权限 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10259) +- [新增] 流水线引擎动态配置管理 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10647) +- [新增] 支持在父流水线中查看异步执行的子流水线的状态 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10260) +- [新增] 新增下拉/复选类型变量时,预定义的选项支持批量输入跟输入key [链接](http://github.com/TencentBlueKing/bk-ci/issues/10290) +- [新增] 补全内置变量列表 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10436) +- [新增] 流水线构建详情页,每个 job/step 上的耗时直接显示 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10311) +- [新增] 回收站支持流水线名词搜索 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10408) +- [新增] 流水线列表最近执行展示内容优化 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10600) +- [新增] 制品下载无反应问题 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10555) +- [新增] 子流水线调用插件参数传递方式优化 [链接](http://github.com/TencentBlueKing/bk-ci/issues/9943) +- [新增] 流水线设置查看页面并发分组配置缺失问题fix [链接](http://github.com/TencentBlueKing/bk-ci/issues/10516) +- [新增] 日志复制出来的空格异常 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10540) +- [新增] 流水线版本描述,增加长度限制 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10520) +- [新增] 构建详情页面,版本号hover可以展示对应的版本描述 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10524) +##### 代码库 +- [新增] 关联工蜂代码库时,支持开启 Pipeline as Code 模式 [链接](http://github.com/TencentBlueKing/bk-ci/issues/8115) +- [新增] 代码库优化一期功能点 [链接](http://github.com/TencentBlueKing/bk-ci/issues/9347) +- [新增] github pr检查输出质量红线报告 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10607) +- [新增] 【openapi】关联代码库到蓝盾的api支持开启 PAC [链接](http://github.com/TencentBlueKing/bk-ci/issues/10770) +- [新增] 已开启 PAC 模式的代码库,支持关闭 PAC [链接](http://github.com/TencentBlueKing/bk-ci/issues/9993) +- [新增] 代码库触发事件结果展示优化 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10307) +- [新增] github check run应该支持GONGFENGSCAN渠道的流水线 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10704) +##### 质量红线 +- [新增] 流水线中有多个CodeCC插件时,质量红线跳转链接要能跳转到相应任务 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10605) +- [新增] quality新增matchRuleList的app接口 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10610) +##### 环境管理 +- [新增] 构建环境中的节点,支持停用/启用 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10258) +- [新增] 第三方构建机上下线记录清理 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10237) +- [新增] 装WINDOWS构建机,且点击install.bat完成安装,刷新节点没有显示 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10725) +- [新增] 支持批量安装 Agent [链接](http://github.com/TencentBlueKing/bk-ci/issues/10024) +##### 权限中心 +- [新增] 支持管理员查看项目成员 [链接](http://github.com/TencentBlueKing/bk-ci/issues/9620) +- [新增] 用户组相关接口优化 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10463) +- [新增] 根据组织ID拉取用户列表 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10513) +- [新增] 申请权限页面优化 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10145) +##### 项目管理 +- [新增] 项目查看页面运营产品未显示名称问题优化 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10668) +- [新增] 新增项目级事件回调 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10146) +##### 研发商店 +- [新增] 支持插件开发者设置默认的超时时间和默认的失败时的策略 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10019) +- [新增] 新增修改研发商店组件初始化项目的接口 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10126) +- [新增] 插件上传文件失败时重试 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10214) +- [新增] 研发商店-工作台-容器镜像,验证失败时的状态icon错位 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10696) +- [新增] 修复更新组件关联初始化项目信息时未删除关联的调试项目信息 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10621) +- [新增] 整合微拓展资源调度能力 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10122) +##### 日志服务 +- [新增] Log的Service接口补充subtag 查询条件 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10536) +##### 调度 +- [新增] 优化dockerhost dockerRun容器日志获取接口 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10811) +- [新增] kubernetes-manager 支持docker inspect image [链接](http://github.com/TencentBlueKing/bk-ci/issues/8862) +- [新增] 构建环境Agent并发上限为0不生效 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10740) +- [新增] 构建资源类型为第三方构建集群时支持指定Job并发数 [链接](http://github.com/TencentBlueKing/bk-ci/issues/9810) +- [新增] 调整dockerhost默认容器超时时间 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10645) +- [新增] 第三方构建机构建资源锁定策略优化 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10449) +- [新增] 获取job执行最大并发/项目活跃用户度量数据 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10232) +##### Agent +- [新增] Worker杀掉当前进程父进程导致Agent误报 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10362) +- [新增] Agent启动时对相同Id不同IP的重复安装做告警 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10264) +- [新增] Agent清理进程为worker兜底 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10234) +##### Stream +- [新增] [stream] 优化大仓触发耗时 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10861) +- [新增] [stream] 优化触发流程,减少触发时长 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10753) +- [新增] stream开启CI时,必填组织架构和运营产品 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10231) +- [新增] [stream]新增获取组成员 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10711) +##### 网关 +- [新增] 网关在auth_request时可以处理302的异常跳转 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10295) +- [新增] 网关默认tag不写死 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10334) +##### 其他 +- [新增] 压缩http返回json串 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10323) +- [新增] 蓝鲸7.2版本的改动 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10558) +- [新增] sql doc 文档更新 [链接](http://github.com/TencentBlueKing/bk-ci/issues/9974) +- [新增] bk-apigw接口认证方式调整 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10802) +- [新增] 修复swagger的扫包方式 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10806) +- [新增] 全局配置title/footer/logo/favicon/产品名称 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10678) +- [新增] 蓝盾网关信任安全域名的cors-header [链接](http://github.com/TencentBlueKing/bk-ci/issues/10767) +- [新增] 修复iam初始化脚本 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10658) +- [新增] openapi 访问无权限时新增文案 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10638) +- [新增] 依赖的服务未部署时的交互优化 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10612) +- [新增] 提高滚动发布的速度 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10236) +- [新增] 优化审计相关逻辑 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10671) +- [新增] 优化open接口切面校验 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10426) + +#### 优化 +##### 流水线 +- [优化] 流水线执行历史表格优化 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10769) +- [优化] 流水线实例复制功能没有复制相应实例的参数值 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10580) +- [优化] 表达式解析器增加对流水线变量处理的兼容 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10609) +- [优化] 禁用流水线功能优化 [链接](http://github.com/TencentBlueKing/bk-ci/issues/8190) +- [优化] UI 方式下新增/编辑变量页面改版 [链接](http://github.com/TencentBlueKing/bk-ci/issues/8185) +- [优化] 插件执行错误码优化 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10326) +##### 环境管理 +- [优化] 环境管理添加部分错误码 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10788) +- [优化] 环境管理部分代码优化 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10641) +- [优化] er:环境管理部分代码优化2 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10263) +##### 研发商店 +- [优化] 支持java插件target引用变量来设置jar包执行路径 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10643) +- [优化] 研发商店敏感接口权限校验优化 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10418) +- [优化] 研发商店插件运行支持通过task.json中的execution.target字段指定运行参数 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10072) +- [优化] 研发商店通用化接口封装 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10123) +- [优化] 研发商店logo上传暂不支持svg图片,防止xss攻击 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10374) +##### Agent +- [修复] windwos启动构建进程时偶现142问题 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10179) +##### 其他 +- [优化] 获取db集群名称方法支持db集群列表实现可配置化 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10372) + +#### 修复 +##### 流水线 +- [修复] 修正取消正在运行中构建可能产生的慢逻辑 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10874) +- [修复] 人工审核未勾选通知方式不应进行通知 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10183) +- [修复] 触发时前端手动跳过的矩阵依然运行 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10751) +- [修复] 新构建详情页插件渲染问题修复 [链接](http://github.com/TencentBlueKing/bk-ci/issues/9185) +- [修复] git事件触发插件支持第三方服务changeFiles值总是为null [链接](http://github.com/TencentBlueKing/bk-ci/issues/10255) +- [修复] 构建历史接口的调试记录查询问题 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10814) +- [修复] 流水线触发器配置查看时可编辑 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10827) +- [修复] 文件类型变量问题修复 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10822) +- [修复] 流水线Job异步开机后随即用户取消流水线,异步开机异常导致流水线状态刷新异常 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10816) +- [修复] 为job分配多个容器并发执行业务逻辑会导致构建取消 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10517) +- [修复] 归档构件的制品页,显示有误,路径不完整,缺少文件大小 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10667) +- [修复] 修复矩阵code校验时存在的并发问题 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10771) +- [修复] stream 流水线MR触发时分支变量值有误 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10707) +- [修复] 有时候取消final stage后,构建未彻底结束 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10619) +- [修复] 归档报告插件创建token没有实现 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10693) +- [修复] 合作版工蜂force push触发流水线失败 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10680) +- [修复] 保存流水线模板权限问题 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10681) +- [修复] 忽略工蜂webhook测试请求 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10666) +- [修复] 流水线删除后,执行中的任务没终止 [链接](http://github.com/TencentBlueKing/bk-ci/issues/8483) +- [修复] 新详情页的部分展示问题 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10557) +- [修复] 前端detail接口中返回草稿版本有误 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10545) +- [修复] 前序取消状态导致finally stage结束异常 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10533) +- [修复] 删除流水线接口异常 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10542) +- [修复] 新详情页显示问题修复 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10395) +- [修复] 解决stage审核参数值类型不一致问题 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10095) +- [修复] 回收站搜索不可用 [链接](http://github.com/TencentBlueKing/bk-ci/issues/8440) +- [修复] 子流水线插件执行超时,但是没有把子流水线停掉 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10331) +- [修复] 流水线版本保存记录未及时清理 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10244) +- [修复] 变量只读导致无法重写 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10245) +##### 代码库 +- [修复] 关联代码库已关联pac的项目名关闭弹框后未清空 [链接](http://github.com/TencentBlueKing/bk-ci/issues/8146) +##### 项目管理 +- [修复] 开源社区,项目管理界面 开源版权限需放开 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10382) +- [修复] 社区版simple权限中心前端应该隐藏最大授权范围 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10040) +- [修复] 项目最大可授权范围 序列化对比问题 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10649) +- [修复] 禁用项目不应该统计用户数 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10634) +- [修复] 修复CodeCC平台灰度标签设置不正确 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10434) +##### 研发商店 +- [修复] 研发商店应用首个版本处于测试中,查询接口按实例ID查询不到测试中的应用版本 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10691) +- [修复] 调低SampleFirstStoreHostDecorateImpl的优先级配置 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10401) +- [修复] [社区]上架失败&流水线执行页面白屏问题[v2.1.0+] [链接](http://github.com/TencentBlueKing/bk-ci/issues/10357) +- [修复] 研发商店通用接口国际化配置调整 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10640) +- [修复] 开源版插件升级版本未刷新LATEST_TEST_FLAG标识状态 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10701) +##### 调度 +- [修复] 无编译环境构建机执行带审核插件的矩阵job问题 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10599) +- [修复] 重试重新调度导致复用无法解锁 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10675) +##### Agent +- [修复] 修复arm64mac进程无法清理的问题 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10252) +- [修复] Agent复用在流水线重试的场景下存在问题 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10877) +- [修复] agent没有区域信息时默认没有bkrepo的网关 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10778) +- [修复] Agent复用同级节点时跳过了复用锁 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10795) +- [修复] Agent复用时取消后不能退出队列 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10589) +##### 其他 +- [修复] 2.1版本process服务启动失败 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10271) +- [修复] 同步差异代码 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10319) +- [修复] 修复npm依赖漏洞 [链接](http://github.com/TencentBlueKing/bk-ci/issues/10604) diff --git a/CHANGELOG/README.md b/CHANGELOG/README.md index abecde491c7..53f27a6a715 100644 --- a/CHANGELOG/README.md +++ b/CHANGELOG/README.md @@ -8,4 +8,4 @@ - [CHANGELOG-2.0.md](./CHANGELOG-2.0.md) - [CHANGELOG-2.0.md](./CHANGELOG-2.0.md) - [CHANGELOG-2.1.md](./CHANGELOG-2.1.md) -- [CHANGELOG-2.1.md](./CHANGELOG-2.1.md) +- [CHANGELOG-3.0.md](./CHANGELOG-3.0.md) diff --git a/docs/overview/db/devops_ci_auth.md b/docs/overview/db/devops_ci_auth.md index 5ef8c07c1fe..ae8fd164cb8 100644 --- a/docs/overview/db/devops_ci_auth.md +++ b/docs/overview/db/devops_ci_auth.md @@ -27,8 +27,12 @@ | T_AUTH_OAUTH2_SCOPE | 授权范围表 | | T_AUTH_OAUTH2_SCOPE_OPERATION | 授权操作信息表 | | T_AUTH_RESOURCE | 资源表 | +| T_AUTH_RESOURCE_AUTHORIZATION | 资源授权管理表 | | T_AUTH_RESOURCE_GROUP | 资源关联用户组表 | +| T_AUTH_RESOURCE_GROUP_APPLY | 用户组申请记录表 | | T_AUTH_RESOURCE_GROUP_CONFIG | 资源用户组配置表 | +| T_AUTH_RESOURCE_GROUP_MEMBER | 资源组成员 | +| T_AUTH_RESOURCE_SYNC | 同步 IAM 资源 | | T_AUTH_RESOURCE_TYPE | 权限资源类型表 | | T_AUTH_STRATEGY | 权限策略表 | | T_AUTH_TEMPORARY_VERIFY_RECORD | 迁移-鉴权记录表 | @@ -375,6 +379,25 @@ | 11 | CREATE_USER | varchar | 64 | 0 | N | N | | 创建者 | | 12 | UPDATE_USER | varchar | 64 | 0 | N | N | | 修改人 | +**表名:** T_AUTH_RESOURCE_AUTHORIZATION + +**说明:** 资源授权管理表 + +**数据列:** + +| 序号 | 名称 | 数据类型 | 长度 | 小数位 | 允许空值 | 主键 | 默认值 | 说明 | +| :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | +| 1 | ID | bigint | 20 | 0 | N | Y | | 主键 ID | +| 2 | PROJECT_CODE | varchar | 32 | 0 | N | N | | 项目 ID | +| 3 | RESOURCE_TYPE | varchar | 32 | 0 | N | N | | 资源类型 | +| 4 | RESOURCE_CODE | varchar | 255 | 0 | N | N | | 资源 ID | +| 5 | RESOURCE_NAME | varchar | 255 | 0 | N | N | | 资源名 | +| 6 | HANDOVER_FROM | varchar | 64 | 0 | N | N | | 授予人 | +| 7 | HANDOVER_FROM_CN_NAME | varchar | 64 | 0 | N | N | | 授予人中文名称 | +| 8 | HANDOVER_TIME | timestamp | 19 | 0 | N | N | CURRENT_TIMESTAMP | 授予时间 | +| 9 | CREATE_TIME | timestamp | 19 | 0 | Y | N | CURRENT_TIMESTAMP | 创建时间 | +| 10 | UPDATE_TIME | timestamp | 19 | 0 | Y | N | CURRENT_TIMESTAMP | 更新时间 | + **表名:** T_AUTH_RESOURCE_GROUP **说明:** 资源关联用户组表 @@ -395,6 +418,25 @@ | 10 | RELATION_ID | varchar | 32 | 0 | N | N | | 关联的 IAM 组 ID | | 11 | CREATE_TIME | datetime | 19 | 0 | N | N | CURRENT_TIMESTAMP | 创建时间 | | 12 | UPDATE_TIME | datetime | 19 | 0 | N | N | CURRENT_TIMESTAMP | 更新时间 | +| 13 | DESCRIPTION | varchar | 512 | 0 | Y | N | | 用户组描述 | +| 14 | IAM_TEMPLATE_ID | int | 10 | 0 | Y | N | | 人员模板 ID | + +**表名:** T_AUTH_RESOURCE_GROUP_APPLY + +**说明:** 用户组申请记录表 + +**数据列:** + +| 序号 | 名称 | 数据类型 | 长度 | 小数位 | 允许空值 | 主键 | 默认值 | 说明 | +| :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | +| 1 | ID | bigint | 20 | 0 | N | Y | | 主键 ID | +| 2 | PROJECT_CODE | varchar | 64 | 0 | N | N | | 项目 ID | +| 3 | MEMBER_ID | varchar | 64 | 0 | N | N | | 成员 ID | +| 4 | IAM_GROUP_ID | int | 10 | 0 | N | N | | IAM 组 ID | +| 5 | STATUS | int | 10 | 0 | Y | N | 0 | 状态,0-审批中,1-审批成功,2-审批超时 | +| 6 | NUMBER_OF_CHECKS | int | 10 | 0 | Y | N | 0 | 检查次数,用于同步组数据 | +| 7 | CREATE_TIME | datetime | 19 | 0 | N | N | CURRENT_TIMESTAMP | 创建时间 | +| 8 | UPDATE_TIME | datetime | 19 | 0 | N | N | CURRENT_TIMESTAMP | 更新时间 | **表名:** T_AUTH_RESOURCE_GROUP_CONFIG @@ -416,6 +458,42 @@ | 10 | CREATE_TIME | datetime | 19 | 0 | N | N | CURRENT_TIMESTAMP | 创建时间 | | 11 | UPDATE_TIME | datetime | 19 | 0 | N | N | CURRENT_TIMESTAMP | 更新时间 | +**表名:** T_AUTH_RESOURCE_GROUP_MEMBER + +**说明:** 资源组成员 + +**数据列:** + +| 序号 | 名称 | 数据类型 | 长度 | 小数位 | 允许空值 | 主键 | 默认值 | 说明 | +| :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | +| 1 | ID | bigint | 20 | 0 | N | Y | | 主键 ID | +| 2 | PROJECT_CODE | varchar | 64 | 0 | N | N | | 项目 ID | +| 3 | RESOURCE_TYPE | varchar | 32 | 0 | N | N | | 资源类型 | +| 4 | RESOURCE_CODE | varchar | 255 | 0 | N | N | | 资源 ID | +| 5 | GROUP_CODE | varchar | 32 | 0 | N | N | | 用户组标识 | +| 6 | IAM_GROUP_ID | int | 10 | 0 | N | N | | IAM 组 ID | +| 7 | MEMBER_ID | varchar | 64 | 0 | N | N | | 成员 ID | +| 8 | MEMBER_NAME | varchar | 512 | 0 | N | N | | 成员名 | +| 9 | MEMBER_TYPE | varchar | 32 | 0 | N | N | | 成员类型,用户/组织/模板 | +| 10 | EXPIRED_TIME | datetime | 19 | 0 | N | N | | 过期时间 | +| 11 | CREATE_TIME | datetime | 19 | 0 | N | N | CURRENT_TIMESTAMP | 创建时间 | +| 12 | UPDATE_TIME | datetime | 19 | 0 | N | N | CURRENT_TIMESTAMP | 更新时间 | + +**表名:** T_AUTH_RESOURCE_SYNC + +**说明:** 同步 IAM 资源 + +**数据列:** + +| 序号 | 名称 | 数据类型 | 长度 | 小数位 | 允许空值 | 主键 | 默认值 | 说明 | +| :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | +| 1 | PROJECT_CODE | varchar | 64 | 0 | N | Y | | 项目 ID | +| 2 | STATUS | int | 10 | 0 | Y | N | 0 | 迁移状态,0-同步中,1-同步成功,2-同步失败 | +| 3 | ERROR_MESSAGE | text | 65535 | 0 | Y | N | | 错误信息 | +| 4 | TOTAL_TIME | bigint | 20 | 0 | Y | N | | 总耗时 | +| 5 | CREATE_TIME | datetime | 19 | 0 | N | N | CURRENT_TIMESTAMP | 创建时间 | +| 6 | UPDATE_TIME | datetime | 19 | 0 | N | N | CURRENT_TIMESTAMP | 更新时间 | + **表名:** T_AUTH_RESOURCE_TYPE **说明:** 权限资源类型表 diff --git a/helm-charts/core/ci/templates/artifactory/statefulset.yaml b/helm-charts/core/ci/templates/artifactory/statefulset.yaml index 6f7038d5ab0..0947e9d7fb9 100644 --- a/helm-charts/core/ci/templates/artifactory/statefulset.yaml +++ b/helm-charts/core/ci/templates/artifactory/statefulset.yaml @@ -160,7 +160,7 @@ spec: command: - /bin/bash - -c - - sleep 10 && ps -ef|grep java|grep -v grep|awk '{print $2}'|xargs kill -15 + - sleep 20 && ps -ef|grep java|grep -v grep|awk '{print $2}'|xargs kill -15 volumes: - name: storage {{- if and .Values.persistence.enabled (eq .Values.config.bkCiArtifactoryRealm "local") }} diff --git a/helm-charts/core/ci/templates/auth/deployment.yaml b/helm-charts/core/ci/templates/auth/deployment.yaml index e9d8dd5ed19..e048980070d 100644 --- a/helm-charts/core/ci/templates/auth/deployment.yaml +++ b/helm-charts/core/ci/templates/auth/deployment.yaml @@ -142,7 +142,7 @@ spec: command: - /bin/bash - -c - - sleep 10 && ps -ef|grep java|grep -v grep|awk '{print $2}'|xargs kill -15 + - sleep 20 && ps -ef|grep java|grep -v grep|awk '{print $2}'|xargs kill -15 volumes: - hostPath: path: /data diff --git a/helm-charts/core/ci/templates/bklog.yaml b/helm-charts/core/ci/templates/bklog.yaml index 1754ea65616..e26e31b1e44 100644 --- a/helm-charts/core/ci/templates/bklog.yaml +++ b/helm-charts/core/ci/templates/bklog.yaml @@ -17,7 +17,7 @@ spec: app.kubernetes.io/instance: {{ .Release.Name }} app.kubernetes.io/managed-by: Helm path: - - /data/logs/*-.log + - /data/workspace/*/logs/service.log encoding: 'utf-8' multiline: pattern: '^[0-2][0-9][0-9][0-9].[0-1][0-9].[0-3][0-9]' diff --git a/helm-charts/core/ci/templates/dispatch/deployment.yaml b/helm-charts/core/ci/templates/dispatch/deployment.yaml index 63b8c4eb7b0..6dcbb649f47 100644 --- a/helm-charts/core/ci/templates/dispatch/deployment.yaml +++ b/helm-charts/core/ci/templates/dispatch/deployment.yaml @@ -144,7 +144,7 @@ spec: command: - /bin/bash - -c - - sleep 10 && ps -ef|grep java|grep -v grep|awk '{print $2}'|xargs kill -15 + - sleep 20 && ps -ef|grep java|grep -v grep|awk '{print $2}'|xargs kill -15 volumes: - hostPath: path: /data diff --git a/helm-charts/core/ci/templates/environment/deployment.yaml b/helm-charts/core/ci/templates/environment/deployment.yaml index 9b5d850242f..2ecf20e91d8 100644 --- a/helm-charts/core/ci/templates/environment/deployment.yaml +++ b/helm-charts/core/ci/templates/environment/deployment.yaml @@ -144,7 +144,7 @@ spec: command: - /bin/bash - -c - - sleep 10 && ps -ef|grep java|grep -v grep|awk '{print $2}'|xargs kill -15 + - sleep 20 && ps -ef|grep java|grep -v grep|awk '{print $2}'|xargs kill -15 volumes: - hostPath: path: /data diff --git a/helm-charts/core/ci/templates/log/deployment.yaml b/helm-charts/core/ci/templates/log/deployment.yaml index 279648a973e..f8fbd22e060 100644 --- a/helm-charts/core/ci/templates/log/deployment.yaml +++ b/helm-charts/core/ci/templates/log/deployment.yaml @@ -142,7 +142,7 @@ spec: command: - /bin/bash - -c - - sleep 10 && ps -ef|grep java|grep -v grep|awk '{print $2}'|xargs kill -15 + - sleep 20 && ps -ef|grep java|grep -v grep|awk '{print $2}'|xargs kill -15 volumes: - hostPath: path: /data diff --git a/helm-charts/core/ci/templates/metrics/deployment.yaml b/helm-charts/core/ci/templates/metrics/deployment.yaml index 6c042094fd9..78efb93f3ae 100644 --- a/helm-charts/core/ci/templates/metrics/deployment.yaml +++ b/helm-charts/core/ci/templates/metrics/deployment.yaml @@ -142,7 +142,7 @@ spec: command: - /bin/bash - -c - - sleep 10 && ps -ef|grep java|grep -v grep|awk '{print $2}'|xargs kill -15 + - sleep 20 && ps -ef|grep java|grep -v grep|awk '{print $2}'|xargs kill -15 volumes: - hostPath: path: /data diff --git a/helm-charts/core/ci/templates/misc/deployment.yaml b/helm-charts/core/ci/templates/misc/deployment.yaml index b6d96cfe6c8..ba9dfbbf212 100644 --- a/helm-charts/core/ci/templates/misc/deployment.yaml +++ b/helm-charts/core/ci/templates/misc/deployment.yaml @@ -142,7 +142,7 @@ spec: command: - /bin/bash - -c - - sleep 10 && ps -ef|grep java|grep -v grep|awk '{print $2}'|xargs kill -15 + - sleep 20 && ps -ef|grep java|grep -v grep|awk '{print $2}'|xargs kill -15 volumes: - hostPath: path: /data diff --git a/helm-charts/core/ci/templates/notify/deployment.yaml b/helm-charts/core/ci/templates/notify/deployment.yaml index e18c35fd2fa..953eac2a8c6 100644 --- a/helm-charts/core/ci/templates/notify/deployment.yaml +++ b/helm-charts/core/ci/templates/notify/deployment.yaml @@ -142,7 +142,7 @@ spec: command: - /bin/bash - -c - - sleep 10 && ps -ef|grep java|grep -v grep|awk '{print $2}'|xargs kill -15 + - sleep 20 && ps -ef|grep java|grep -v grep|awk '{print $2}'|xargs kill -15 volumes: - hostPath: path: /data diff --git a/helm-charts/core/ci/templates/openapi/deployment.yaml b/helm-charts/core/ci/templates/openapi/deployment.yaml index 6e1727e65e2..d15313d2368 100644 --- a/helm-charts/core/ci/templates/openapi/deployment.yaml +++ b/helm-charts/core/ci/templates/openapi/deployment.yaml @@ -147,7 +147,7 @@ spec: command: - /bin/bash - -c - - sleep 10 && ps -ef|grep java|grep -v grep|awk '{print $2}'|xargs kill -15 + - sleep 20 && ps -ef|grep java|grep -v grep|awk '{print $2}'|xargs kill -15 volumes: - hostPath: path: /data diff --git a/helm-charts/core/ci/templates/process/deployment.yaml b/helm-charts/core/ci/templates/process/deployment.yaml index 806ede7c8c3..d43db8e46a6 100644 --- a/helm-charts/core/ci/templates/process/deployment.yaml +++ b/helm-charts/core/ci/templates/process/deployment.yaml @@ -142,7 +142,7 @@ spec: command: - /bin/bash - -c - - sleep 10 && ps -ef|grep java|grep -v grep|awk '{print $2}'|xargs kill -15 + - sleep 20 && ps -ef|grep java|grep -v grep|awk '{print $2}'|xargs kill -15 volumes: - hostPath: path: /data diff --git a/helm-charts/core/ci/templates/project/deployment.yaml b/helm-charts/core/ci/templates/project/deployment.yaml index a163070b673..bdd5a8e74c2 100644 --- a/helm-charts/core/ci/templates/project/deployment.yaml +++ b/helm-charts/core/ci/templates/project/deployment.yaml @@ -142,7 +142,7 @@ spec: command: - /bin/bash - -c - - sleep 10 && ps -ef|grep java|grep -v grep|awk '{print $2}'|xargs kill -15 + - sleep 20 && ps -ef|grep java|grep -v grep|awk '{print $2}'|xargs kill -15 volumes: - hostPath: path: /data diff --git a/helm-charts/core/ci/templates/quality/deployment.yaml b/helm-charts/core/ci/templates/quality/deployment.yaml index 8da188dc19e..a35887528e6 100644 --- a/helm-charts/core/ci/templates/quality/deployment.yaml +++ b/helm-charts/core/ci/templates/quality/deployment.yaml @@ -142,7 +142,7 @@ spec: command: - /bin/bash - -c - - sleep 10 && ps -ef|grep java|grep -v grep|awk '{print $2}'|xargs kill -15 + - sleep 20 && ps -ef|grep java|grep -v grep|awk '{print $2}'|xargs kill -15 volumes: - hostPath: path: /data diff --git a/helm-charts/core/ci/templates/repository/deployment.yaml b/helm-charts/core/ci/templates/repository/deployment.yaml index c871cbce991..7fa7cfce728 100644 --- a/helm-charts/core/ci/templates/repository/deployment.yaml +++ b/helm-charts/core/ci/templates/repository/deployment.yaml @@ -142,7 +142,7 @@ spec: command: - /bin/bash - -c - - sleep 10 && ps -ef|grep java|grep -v grep|awk '{print $2}'|xargs kill -15 + - sleep 20 && ps -ef|grep java|grep -v grep|awk '{print $2}'|xargs kill -15 volumes: - hostPath: path: /data diff --git a/helm-charts/core/ci/templates/store/deployment.yaml b/helm-charts/core/ci/templates/store/deployment.yaml index e32a0d18725..cde89f895f7 100644 --- a/helm-charts/core/ci/templates/store/deployment.yaml +++ b/helm-charts/core/ci/templates/store/deployment.yaml @@ -142,7 +142,7 @@ spec: command: - /bin/bash - -c - - sleep 10 && ps -ef|grep java|grep -v grep|awk '{print $2}'|xargs kill -15 + - sleep 20 && ps -ef|grep java|grep -v grep|awk '{print $2}'|xargs kill -15 volumes: - hostPath: path: /data diff --git a/helm-charts/core/ci/templates/stream/deployment.yaml b/helm-charts/core/ci/templates/stream/deployment.yaml index b85919ff929..45e30b0358b 100644 --- a/helm-charts/core/ci/templates/stream/deployment.yaml +++ b/helm-charts/core/ci/templates/stream/deployment.yaml @@ -142,7 +142,7 @@ spec: command: - /bin/bash - -c - - sleep 10 && ps -ef|grep java|grep -v grep|awk '{print $2}'|xargs kill -15 + - sleep 20 && ps -ef|grep java|grep -v grep|awk '{print $2}'|xargs kill -15 volumes: - hostPath: path: /data diff --git a/helm-charts/core/ci/templates/ticket/deployment.yaml b/helm-charts/core/ci/templates/ticket/deployment.yaml index dd91cb732e1..a7a55b7b8eb 100644 --- a/helm-charts/core/ci/templates/ticket/deployment.yaml +++ b/helm-charts/core/ci/templates/ticket/deployment.yaml @@ -142,7 +142,7 @@ spec: command: - /bin/bash - -c - - sleep 10 && ps -ef|grep java|grep -v grep|awk '{print $2}'|xargs kill -15 + - sleep 20 && ps -ef|grep java|grep -v grep|awk '{print $2}'|xargs kill -15 volumes: - hostPath: path: /data diff --git a/helm-charts/core/ci/templates/websocket/statefulset.yaml b/helm-charts/core/ci/templates/websocket/statefulset.yaml index 4e4381c49a8..bbe4d66142b 100644 --- a/helm-charts/core/ci/templates/websocket/statefulset.yaml +++ b/helm-charts/core/ci/templates/websocket/statefulset.yaml @@ -138,7 +138,7 @@ spec: command: - /bin/bash - -c - - sleep 10 && ps -ef|grep java|grep -v grep|awk '{print $2}'|xargs kill -15 + - sleep 20 && ps -ef|grep java|grep -v grep|awk '{print $2}'|xargs kill -15 volumes: - hostPath: path: /data diff --git a/src/backend/ci/buildSrc/src/main/kotlin/plugins/task-docker-build.gradle.kts b/src/backend/ci/buildSrc/src/main/kotlin/plugins/task-docker-build.gradle.kts index f023d7da697..72df005d24b 100644 --- a/src/backend/ci/buildSrc/src/main/kotlin/plugins/task-docker-build.gradle.kts +++ b/src/backend/ci/buildSrc/src/main/kotlin/plugins/task-docker-build.gradle.kts @@ -42,8 +42,6 @@ if (toImage.isNullOrBlank() || (toImageRepo.isNullOrBlank() && toImageTag.isNull } + "bkci-" + service + ":" + toImageTag } - val configNamespace = System.getProperty("config.namespace") - val jvmFlagList = System.getProperty("jvmFlags.file")?.let { File(it).readLines() } ?: emptyList() val finalJvmFlags = mutableListOf( @@ -75,7 +73,6 @@ if (toImage.isNullOrBlank() || (toImageRepo.isNullOrBlank() && toImageTag.isNull "-Dspring.main.allow-circular-references=true", "-Dspring.cloud.kubernetes.config.sources[0].name=config-bk-ci-common", "-Dspring.cloud.kubernetes.config.sources[1].name=config-bk-ci-$service", - "-Dspring.cloud.kubernetes.config.namespace=$configNamespace", "-Dspring.cloud.kubernetes.discovery.all-namespaces=true", "-Dspring.cloud.kubernetes.config.includeProfileSpecificSources=false", "-Dio.undertow.legacy.cookie.ALLOW_HTTP_SEPARATORS_IN_V0=true", diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/migrate/OpAuthMigrateResource.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/migrate/OpAuthMigrateResource.kt index 1d380c079d9..868c8d533cc 100644 --- a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/migrate/OpAuthMigrateResource.kt +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/migrate/OpAuthMigrateResource.kt @@ -40,6 +40,7 @@ import javax.ws.rs.POST import javax.ws.rs.Path import javax.ws.rs.PathParam import javax.ws.rs.Produces +import javax.ws.rs.QueryParam import javax.ws.rs.core.MediaType @Tag(name = "AUTH_MIGRATE", description = "权限-迁移") @@ -137,6 +138,9 @@ interface OpAuthMigrateResource { @Path("/autoRenewal") @Operation(summary = "自动续期") fun autoRenewal( + @Parameter(description = "小于该值才会被续期,若传空,则默认用户在用户组中的过期时间小于180天会被自动续期", required = true) + @QueryParam("validExpiredDay") + validExpiredDay: Int?, @Parameter(description = "按条件迁移项目实体", required = true) projectConditionDTO: ProjectConditionDTO ): Result diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/user/UserAuthResourceGroupResource.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/user/UserAuthResourceGroupResource.kt index 50d7ce1996c..e403ce5b338 100644 --- a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/user/UserAuthResourceGroupResource.kt +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/user/UserAuthResourceGroupResource.kt @@ -92,6 +92,15 @@ interface UserAuthResourceGroupResource { @QueryParam("memberId") @Parameter(description = "组织ID/成员ID") memberId: String, + @QueryParam("groupName") + @Parameter(description = "用户组名称") + groupName: String?, + @QueryParam("minExpiredAt") + @Parameter(description = "最小过期时间") + minExpiredAt: Long?, + @QueryParam("maxExpiredAt") + @Parameter(description = "最大过期时间") + maxExpiredAt: Long?, @Parameter(description = "起始位置,从0开始") @QueryParam("start") start: Int, diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/user/UserAuthResourceMemberResource.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/user/UserAuthResourceMemberResource.kt index 8e0da2fd2f1..868f0d39d37 100644 --- a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/user/UserAuthResourceMemberResource.kt +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/api/user/UserAuthResourceMemberResource.kt @@ -6,6 +6,7 @@ import com.tencent.devops.auth.pojo.request.GroupMemberCommonConditionReq import com.tencent.devops.auth.pojo.request.GroupMemberHandoverConditionReq import com.tencent.devops.auth.pojo.request.GroupMemberRenewalConditionReq import com.tencent.devops.auth.pojo.request.GroupMemberSingleRenewalReq +import com.tencent.devops.auth.pojo.request.ProjectMembersQueryConditionReq import com.tencent.devops.auth.pojo.request.RemoveMemberFromProjectReq import com.tencent.devops.auth.pojo.vo.BatchOperateGroupMemberCheckVo import com.tencent.devops.auth.pojo.vo.GroupDetailsInfoVo @@ -32,6 +33,7 @@ import javax.ws.rs.core.MediaType @Path("/user/auth/resource/member/{projectId}/") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) +@Suppress("LongParameterList") interface UserAuthResourceMemberResource { @GET @Path("/listProjectMembers") @@ -64,6 +66,20 @@ interface UserAuthResourceMemberResource { pageSize: Int ): Result> + @POST + @Path("/listProjectMembersByCondition") + @Operation(summary = "根据条件获取项目下全体成员") + fun listProjectMembersByCondition( + @Parameter(description = "用户名", required = true) + @HeaderParam(AUTH_HEADER_USER_ID) + userId: String, + @Parameter(description = "项目ID", required = true) + @PathParam("projectId") + projectId: String, + @Parameter(description = "查询条件", required = true) + projectMembersQueryConditionReq: ProjectMembersQueryConditionReq + ): Result> + @PUT @Path("/renewal") @Operation(summary = "续期单个组成员权限--无需进行审批") @@ -177,6 +193,15 @@ interface UserAuthResourceMemberResource { projectId: String, @QueryParam("memberId") @Parameter(description = "组织ID/成员ID") - memberId: String + memberId: String, + @QueryParam("groupName") + @Parameter(description = "用户组名称") + groupName: String?, + @QueryParam("minExpiredAt") + @Parameter(description = "最小过期时间") + minExpiredAt: Long?, + @QueryParam("maxExpiredAt") + @Parameter(description = "最大过期时间") + maxExpiredAt: Long? ): Result> } diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/AuthResourceInfo.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/AuthResourceInfo.kt index c5bbb44c5d6..f5d0e670cfc 100644 --- a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/AuthResourceInfo.kt +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/AuthResourceInfo.kt @@ -44,5 +44,6 @@ data class AuthResourceInfo( val createUser: String, val updateUser: String, val createTime: LocalDateTime, - val updateTime: LocalDateTime + val updateTime: LocalDateTime, + val iamGradeManagerId: String? = null ) diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/dto/ProjectMembersQueryConditionDTO.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/dto/ProjectMembersQueryConditionDTO.kt new file mode 100644 index 00000000000..8034e532bab --- /dev/null +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/dto/ProjectMembersQueryConditionDTO.kt @@ -0,0 +1,61 @@ +package com.tencent.devops.auth.pojo.dto + +import com.tencent.devops.auth.pojo.request.ProjectMembersQueryConditionReq +import com.tencent.devops.common.api.util.DateTimeUtil +import com.tencent.devops.common.api.util.PageUtil +import io.swagger.v3.oas.annotations.media.Schema +import java.time.LocalDateTime + +@Schema(title = "项目成员查询业务处理实体") +data class ProjectMembersQueryConditionDTO( + @get:Schema(title = "项目ID") + val projectCode: String, + @get:Schema(title = "成员类型") + val memberType: String? = null, + @get:Schema(title = "用户名称") + val userName: String? = null, + @get:Schema(title = "部门名称") + val deptName: String? = null, + @get:Schema(title = "用户组名称") + val groupName: String? = null, + @get:Schema(title = "用户组Id") + val iamGroupIds: List? = null, + @get:Schema(title = "最小过期时间") + val minExpiredTime: LocalDateTime? = null, + @get:Schema(title = "最大过期时间") + val maxExpiredTime: LocalDateTime? = null, + @get:Schema(title = "离职标识") + val departedFlag: Boolean? = false, + @get:Schema(title = "是否查询模板") + val queryTemplate: Boolean? = false, + @get:Schema(title = "限制") + val limit: Int? = null, + @get:Schema(title = "起始值") + val offset: Int? = null +) { + companion object { + fun build( + projectMembersQueryConditionReq: ProjectMembersQueryConditionReq, + iamGroupIds: List? + ): ProjectMembersQueryConditionDTO { + return with(projectMembersQueryConditionReq) { + val minExpiredTime = minExpiredAt?.let { DateTimeUtil.convertTimestampToLocalDateTime(it / 1000) } + val maxExpiredTime = maxExpiredAt?.let { DateTimeUtil.convertTimestampToLocalDateTime(it / 1000) } + val limit = PageUtil.convertPageSizeToSQLLimit(page, pageSize) + ProjectMembersQueryConditionDTO( + projectCode = projectCode, + memberType = memberType, + userName = userName, + deptName = deptName, + groupName = groupName, + iamGroupIds = iamGroupIds, + minExpiredTime = minExpiredTime, + maxExpiredTime = maxExpiredTime, + departedFlag = departedFlag, + limit = limit.limit, + offset = limit.offset + ) + } + } + } +} diff --git a/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/ProjectMembersQueryConditionReq.kt b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/ProjectMembersQueryConditionReq.kt new file mode 100644 index 00000000000..14209b5fdf9 --- /dev/null +++ b/src/backend/ci/core/auth/api-auth/src/main/kotlin/com/tencent/devops/auth/pojo/request/ProjectMembersQueryConditionReq.kt @@ -0,0 +1,36 @@ +package com.tencent.devops.auth.pojo.request + +import io.swagger.v3.oas.annotations.media.Schema + +@Schema(title = "项目成员查询业务处理请求体") +data class ProjectMembersQueryConditionReq( + @get:Schema(title = "项目ID") + val projectCode: String, + @get:Schema(title = "成员类型") + val memberType: String?, + @get:Schema(title = "用户名称") + val userName: String?, + @get:Schema(title = "部门名称") + val deptName: String?, + @get:Schema(title = "用户组名称") + val groupName: String?, + @get:Schema(title = "最小过期时间") + val minExpiredAt: Long?, + @get:Schema(title = "最大过期时间") + val maxExpiredAt: Long?, + @get:Schema(title = "离职标识") + val departedFlag: Boolean? = false, + @get:Schema(title = "第几页") + val page: Int, + @get:Schema(title = "页数") + val pageSize: Int +) { + // 当查询到权限相关信息时,如组名称,过期时间,操作,资源类型时,走复杂查询逻辑 + fun isComplexQuery(): Boolean { + return groupName != null || minExpiredAt != null || maxExpiredAt != null + } + + fun isNeedToQueryIamGroupIds(): Boolean { + return groupName != null + } +} diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/cron/AuthCronSyncGroupAndMember.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/cron/AuthCronSyncGroupAndMember.kt index 774607c5e2b..aca24708f79 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/cron/AuthCronSyncGroupAndMember.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/cron/AuthCronSyncGroupAndMember.kt @@ -45,7 +45,10 @@ class AuthCronSyncGroupAndMember( } } - @Scheduled(cron = "0 0 8,16 * * ?") + /** + * 一小时,同步一次用户申请加入组的单据,若连续两个月未审批单据,将不再进行扫描 + * */ + @Scheduled(initialDelay = 10000, fixedRate = 3600000) fun syncIamGroupMembersOfApplyRegularly() { if (!enable) { return diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthAuthorizationDao.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthAuthorizationDao.kt index ef94562a6fc..4dc56b5b18d 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthAuthorizationDao.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthAuthorizationDao.kt @@ -96,7 +96,7 @@ class AuthAuthorizationDao { .set(HANDOVER_FROM_CN_NAME, resourceAuthorizationDto.handoverToCnName) .set(HANDOVER_TIME, LocalDateTime.now()) } else { - it + it.set(HANDOVER_TIME, HANDOVER_TIME) } } .set(RESOURCE_NAME, resourceAuthorizationDto.resourceName) diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceDao.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceDao.kt index bdfca175cf4..663de2de111 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceDao.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceDao.kt @@ -406,6 +406,7 @@ class AuthResourceDao { relationId = relationId, createUser = createUser, updateUser = updateUser, + iamGradeManagerId = relationId, createTime = createTime, updateTime = updateTime ) diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceGroupDao.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceGroupDao.kt index 49efc0066bd..fa567c941ab 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceGroupDao.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceGroupDao.kt @@ -29,6 +29,7 @@ package com.tencent.devops.auth.dao import com.tencent.devops.auth.pojo.AuthResourceGroup +import com.tencent.devops.common.auth.api.AuthResourceType import com.tencent.devops.model.auth.tables.TAuthResourceGroup import com.tencent.devops.model.auth.tables.records.TAuthResourceGroupRecord import org.jooq.DSLContext @@ -252,12 +253,31 @@ class AuthResourceGroupDao { fun listIamGroupIdsByConditions( dslContext: DSLContext, projectCode: String, - iamGroupIds: List + iamGroupIds: List? = null, + groupName: String? = null, + iamTemplateIds: List? = null ): List { return with(TAuthResourceGroup.T_AUTH_RESOURCE_GROUP) { dslContext.select(RELATION_ID).from(this) .where(PROJECT_CODE.eq(projectCode)) - .and(RELATION_ID.`in`(iamGroupIds)) + .let { + if (!iamGroupIds.isNullOrEmpty()) + it.and(RELATION_ID.`in`(iamGroupIds)) + else it + } + .let { + if (groupName != null) + it.and(GROUP_NAME.like("%$groupName%")) + else + it + } + .let { + if (!iamTemplateIds.isNullOrEmpty()) { + it.and(RESOURCE_TYPE.eq(AuthResourceType.PROJECT.value)) + it.and(IAM_TEMPLATE_ID.`in`(iamTemplateIds)) + } else + it + } .fetch().map { it.value1().toInt() } } } diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceGroupMemberDao.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceGroupMemberDao.kt index 1d95eb1b2f8..5f70950b7a4 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceGroupMemberDao.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/dao/AuthResourceGroupMemberDao.kt @@ -30,6 +30,7 @@ package com.tencent.devops.auth.dao import com.tencent.bk.sdk.iam.constants.ManagerScopesEnum import com.tencent.devops.auth.pojo.AuthResourceGroupMember import com.tencent.devops.auth.pojo.ResourceMemberInfo +import com.tencent.devops.auth.pojo.dto.ProjectMembersQueryConditionDTO import com.tencent.devops.common.auth.api.pojo.BkAuthGroup import com.tencent.devops.model.auth.tables.TAuthResourceAuthorization import com.tencent.devops.model.auth.tables.TAuthResourceGroupMember @@ -362,6 +363,74 @@ class AuthResourceGroupMemberDao { } } + fun listProjectMembersByComplexConditions( + dslContext: DSLContext, + conditionDTO: ProjectMembersQueryConditionDTO + ): List { + return with(TAuthResourceGroupMember.T_AUTH_RESOURCE_GROUP_MEMBER) { + dslContext.select(MEMBER_ID, MEMBER_NAME, MEMBER_TYPE).from(this) + .where(buildProjectMembersByComplexConditions(conditionDTO)) + .groupBy(MEMBER_ID) + .orderBy(MEMBER_ID) + .let { + if (conditionDTO.limit != null && conditionDTO.offset != null) { + it.offset(conditionDTO.offset).limit(conditionDTO.limit) + } else { + it + } + } + .fetch().map { + ResourceMemberInfo( + id = it.value1(), + name = it.value2(), + type = it.value3() + ) + } + } + } + + fun countProjectMembersByComplexConditions( + dslContext: DSLContext, + conditionDTO: ProjectMembersQueryConditionDTO + ): Long { + return with(TAuthResourceGroupMember.T_AUTH_RESOURCE_GROUP_MEMBER) { + dslContext.select(countDistinct(MEMBER_ID)).from(this) + .where(buildProjectMembersByComplexConditions(conditionDTO)) + .fetchOne(0, Long::class.java) ?: 0L + } + } + + fun buildProjectMembersByComplexConditions( + projectMembersQueryConditionDTO: ProjectMembersQueryConditionDTO + ): MutableList { + val conditions = mutableListOf() + with(TAuthResourceGroupMember.T_AUTH_RESOURCE_GROUP_MEMBER) { + with(projectMembersQueryConditionDTO) { + conditions.add(PROJECT_CODE.eq(projectCode)) + if (queryTemplate == false) { + conditions.add(MEMBER_TYPE.notEqual(ManagerScopesEnum.getType(ManagerScopesEnum.TEMPLATE))) + } else { + conditions.add(MEMBER_TYPE.eq(ManagerScopesEnum.getType(ManagerScopesEnum.TEMPLATE))) + } + memberType?.let { type -> conditions.add(MEMBER_TYPE.eq(type)) } + userName?.let { name -> + conditions.add(MEMBER_TYPE.eq(ManagerScopesEnum.getType(ManagerScopesEnum.USER))) + conditions.add(MEMBER_ID.like("%$name%").or(MEMBER_NAME.like("%$name%"))) + } + deptName?.let { name -> + conditions.add(MEMBER_TYPE.eq(ManagerScopesEnum.getType(ManagerScopesEnum.DEPARTMENT))) + conditions.add(MEMBER_NAME.like("%$name%")) + } + minExpiredTime?.let { minTime -> conditions.add(EXPIRED_TIME.ge(minTime)) } + maxExpiredTime?.let { maxTime -> conditions.add(EXPIRED_TIME.le(maxTime)) } + if (!iamGroupIds.isNullOrEmpty()) { + conditions.add(IAM_GROUP_ID.`in`(iamGroupIds)) + } + } + } + return conditions + } + fun countProjectMember( dslContext: DSLContext, projectCode: String @@ -416,6 +485,7 @@ class AuthResourceGroupMemberDao { .from(tResourceGroupMember) .where(tResourceGroupMember.PROJECT_CODE.eq(projectCode)) .and(tResourceGroupMember.MEMBER_TYPE.notEqual(ManagerScopesEnum.getType(ManagerScopesEnum.TEMPLATE))) + .groupBy(tResourceGroupMember.MEMBER_ID) .unionAll( dslContext.select( tResourceAuthorization.HANDOVER_FROM.`as`("MEMBER_ID"), @@ -424,6 +494,7 @@ class AuthResourceGroupMemberDao { ) .from(tResourceAuthorization) .where(tResourceAuthorization.PROJECT_CODE.eq(projectCode)) + .groupBy(tResourceAuthorization.HANDOVER_FROM) ) .asTable(TABLE_NAME) } @@ -452,24 +523,6 @@ class AuthResourceGroupMemberDao { return conditions } - /** - * 查询组下所有成员 - */ - fun listGroupMember( - dslContext: DSLContext, - projectCode: String, - iamGroupId: Int - ): List { - return with(TAuthResourceGroupMember.T_AUTH_RESOURCE_GROUP_MEMBER) { - dslContext.selectFrom(this) - .where(PROJECT_CODE.eq(projectCode)) - .and(IAM_GROUP_ID.eq(iamGroupId)) - .fetch().map { - convert(it) - } - } - } - /** * 获取成员按资源类型分组数量 */ @@ -479,14 +532,18 @@ class AuthResourceGroupMemberDao { memberId: String, iamTemplateIds: List, resourceType: String? = null, - iamGroupIds: List? = null + iamGroupIds: List? = null, + minExpiredAt: LocalDateTime? = null, + maxExpiredAt: LocalDateTime? = null ): Map { val conditions = buildMemberGroupCondition( projectCode = projectCode, memberId = memberId, iamTemplateIds = iamTemplateIds, resourceType = resourceType, - iamGroupIds = iamGroupIds + iamGroupIds = iamGroupIds, + minExpiredAt = minExpiredAt, + maxExpiredAt = maxExpiredAt ) return with(TAuthResourceGroupMember.T_AUTH_RESOURCE_GROUP_MEMBER) { val select = dslContext.select(RESOURCE_TYPE, count()) @@ -507,6 +564,8 @@ class AuthResourceGroupMemberDao { iamTemplateIds: List, resourceType: String? = null, iamGroupIds: List? = null, + minExpiredAt: LocalDateTime? = null, + maxExpiredAt: LocalDateTime? = null, offset: Int? = null, limit: Int? = null ): List { @@ -515,7 +574,9 @@ class AuthResourceGroupMemberDao { memberId = memberId, iamTemplateIds = iamTemplateIds, resourceType = resourceType, - iamGroupIds = iamGroupIds + iamGroupIds = iamGroupIds, + minExpiredAt = minExpiredAt, + maxExpiredAt = maxExpiredAt ) return with(TAuthResourceGroupMember.T_AUTH_RESOURCE_GROUP_MEMBER) { dslContext.selectFrom(this) @@ -532,7 +593,9 @@ class AuthResourceGroupMemberDao { memberId: String, iamTemplateIds: List, resourceType: String? = null, - iamGroupIds: List? = null + iamGroupIds: List? = null, + minExpiredAt: LocalDateTime? = null, + maxExpiredAt: LocalDateTime? = null ): MutableList { val conditions = mutableListOf() with(TAuthResourceGroupMember.T_AUTH_RESOURCE_GROUP_MEMBER) { @@ -552,7 +615,11 @@ class AuthResourceGroupMemberDao { ) ) resourceType?.let { conditions.add(RESOURCE_TYPE.eq(resourceType)) } - iamGroupIds?.let { conditions.add(IAM_GROUP_ID.`in`(iamGroupIds)) } + minExpiredAt?.let { conditions.add(EXPIRED_TIME.ge(minExpiredAt)) } + maxExpiredAt?.let { conditions.add(EXPIRED_TIME.le(maxExpiredAt)) } + if (!iamGroupIds.isNullOrEmpty()) { + conditions.add(IAM_GROUP_ID.`in`(iamGroupIds)) + } } return conditions } diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacCacheService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacCacheService.kt index 8b838fd0c47..1a9f467e7b7 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacCacheService.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacCacheService.kt @@ -178,11 +178,11 @@ class RbacCacheService constructor( val startEpoch = System.currentTimeMillis() try { val actionDTO = ActionDTO() - actionDTO.id = RbacAuthUtils.buildAction( + val action = RbacAuthUtils.buildAction( authPermission = permission, authResourceType = AuthResourceType.PROJECT ) - + actionDTO.id = action val resourceNode = V2ResourceNode.builder().system(iamConfiguration.systemId) .type(AuthResourceType.PROJECT.value) .id(projectCode) @@ -200,7 +200,11 @@ class RbacCacheService constructor( val result = policyService.verifyPermissions(queryPolicyDTO) if (result) { - authUserDailyService.save(projectId = projectCode, userId = userId) + authUserDailyService.save( + projectId = projectCode, + userId = userId, + operate = action + ) } return result } finally { diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionResourceGroupSyncService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionResourceGroupSyncService.kt index 6a2a651e8ee..2be996b9242 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionResourceGroupSyncService.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionResourceGroupSyncService.kt @@ -80,7 +80,7 @@ class RbacPermissionResourceGroupSyncService @Autowired constructor( private val syncExecutorService = Executors.newFixedThreadPool(5) private val syncProjectsExecutorService = Executors.newFixedThreadPool(10) private val syncResourceMemberExecutorService = Executors.newFixedThreadPool(50) - private const val MAX_NUMBER_OF_CHECKS = 120 + private const val MAX_NUMBER_OF_CHECKS = 1440 } override fun syncByCondition(projectConditionDTO: ProjectConditionDTO) { @@ -186,6 +186,7 @@ class RbacPermissionResourceGroupSyncService @Autowired constructor( limit = limit, offset = offset ) + // 检查60天内的申请的单据 val recordIdsOfTimeOut = records.filter { it.numberOfChecks >= MAX_NUMBER_OF_CHECKS }.map { it.id } val (recordsOfSuccess, recordsOfPending) = records.filterNot { recordIdsOfTimeOut.contains(it.id) diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionResourceMemberService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionResourceMemberService.kt index ee46893139b..ae42fa20b8e 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionResourceMemberService.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionResourceMemberService.kt @@ -17,6 +17,7 @@ import com.tencent.devops.auth.dao.AuthResourceGroupMemberDao import com.tencent.devops.auth.pojo.AuthResourceGroupMember import com.tencent.devops.auth.pojo.ResourceMemberInfo import com.tencent.devops.auth.pojo.dto.GroupMemberRenewalDTO +import com.tencent.devops.auth.pojo.dto.ProjectMembersQueryConditionDTO import com.tencent.devops.auth.pojo.enum.BatchOperateType import com.tencent.devops.auth.pojo.enum.JoinedType import com.tencent.devops.auth.pojo.enum.RemoveMemberButtonControl @@ -24,6 +25,7 @@ import com.tencent.devops.auth.pojo.request.GroupMemberCommonConditionReq import com.tencent.devops.auth.pojo.request.GroupMemberHandoverConditionReq import com.tencent.devops.auth.pojo.request.GroupMemberRenewalConditionReq import com.tencent.devops.auth.pojo.request.GroupMemberSingleRenewalReq +import com.tencent.devops.auth.pojo.request.ProjectMembersQueryConditionReq import com.tencent.devops.auth.pojo.request.RemoveMemberFromProjectReq import com.tencent.devops.auth.pojo.vo.BatchOperateGroupMemberCheckVo import com.tencent.devops.auth.pojo.vo.GroupDetailsInfoVo @@ -180,52 +182,166 @@ class RbacPermissionResourceMemberService constructor( return SQLPage(count = count, records = records) } + return SQLPage(count = count, records = addDepartedFlagToMembers(records)) + } + + private fun addDepartedFlagToMembers(records: List): List { val userMembers = records.filter { it.type == ManagerScopesEnum.getType(ManagerScopesEnum.USER) }.map { it.id } - val departedMembers = if (userMembers.isNotEmpty()) { deptService.listDepartedMembers( memberIds = userMembers ) } else { - return SQLPage(count = count, records = records) + return records } - - val recordsWithDepartedFlag = records.map { + return records.map { if (it.type != ManagerScopesEnum.getType(ManagerScopesEnum.USER)) { it.copy(departed = false) } else { it.copy(departed = departedMembers.contains(it.id)) } } - return SQLPage(count = count, records = recordsWithDepartedFlag) + } + + override fun listProjectMembersByComplexConditions( + conditionReq: ProjectMembersQueryConditionReq + ): SQLPage { + logger.info("list project members by complex conditions: $conditionReq") + // 不允许同时查询部门名称和用户名称 + if (conditionReq.userName != null && conditionReq.deptName != null) { + return SQLPage(count = 0, records = emptyList()) + } + + // 简单查询直接返回结果 + if (!conditionReq.isComplexQuery()) { + return listProjectMembers( + projectCode = conditionReq.projectCode, + memberType = conditionReq.memberType, + userName = conditionReq.userName, + deptName = conditionReq.deptName, + departedFlag = conditionReq.departedFlag, + page = conditionReq.page, + pageSize = conditionReq.pageSize + ) + } + + // 处理复杂查询条件 + val iamGroupIdsByCondition = if (conditionReq.isNeedToQueryIamGroupIds()) { + queryIamGroupIdsByConditions( + projectCode = conditionReq.projectCode, + groupName = conditionReq.groupName + ) + } else { + emptyList() + }.toMutableList() + + if (conditionReq.isNeedToQueryIamGroupIds() && iamGroupIdsByCondition.isEmpty()) { + return SQLPage(0, emptyList()) + } + + val conditionDTO = ProjectMembersQueryConditionDTO.build(conditionReq, iamGroupIdsByCondition) + + if (iamGroupIdsByCondition.isNotEmpty()) { + // 根据用户组Id查询出对应用户组中的人员模板成员 + val iamTemplateIds = authResourceGroupMemberDao.listProjectMembersByComplexConditions( + dslContext = dslContext, + conditionDTO = ProjectMembersQueryConditionDTO( + projectCode = conditionDTO.projectCode, + queryTemplate = true, + iamGroupIds = conditionDTO.iamGroupIds + ) + ) + if (iamTemplateIds.isNotEmpty()) { + // 根据查询出的人员模板ID,查询出对应的组ID + val iamGroupIdsFromTemplate = authResourceGroupDao.listIamGroupIdsByConditions( + dslContext = dslContext, + projectCode = conditionDTO.projectCode, + iamTemplateIds = iamTemplateIds.map { it.id.toInt() } + ) + iamGroupIdsByCondition.addAll(iamGroupIdsFromTemplate) + } + } + + val records = authResourceGroupMemberDao.listProjectMembersByComplexConditions( + dslContext = dslContext, + conditionDTO = conditionDTO + ) + + val count = authResourceGroupMemberDao.countProjectMembersByComplexConditions( + dslContext = dslContext, + conditionDTO = conditionDTO + ) + + // 添加离职标志 + return if (conditionDTO.departedFlag == false) { + SQLPage(count, records) + } else { + SQLPage(count, addDepartedFlagToMembers(records)) + } + } + + /** + * 该方法后期可进行扩展,根据用户组名称,操作,资源类型, + * 组策略查询出对应的组ID,传递用户组表进行查询。 + * */ + private fun queryIamGroupIdsByConditions( + projectCode: String, + groupName: String? = null, + iamGroupIds: List? = null + ): List { + val finalGroupIds = mutableListOf() + if (groupName != null) { + val iamGroupIdsByConditions = authResourceGroupDao.listIamGroupIdsByConditions( + dslContext = dslContext, + projectCode = projectCode, + groupName = groupName + ) + finalGroupIds.addAll(iamGroupIdsByConditions) + } + if (!iamGroupIds.isNullOrEmpty()) { + finalGroupIds.addAll(iamGroupIds) + } + return finalGroupIds } override fun getMemberGroupsCount( projectCode: String, - memberId: String + memberId: String, + groupName: String?, + minExpiredAt: Long?, + maxExpiredAt: Long? ): List { - // 1. 查询项目下包含该成员的组列表 + // 查询项目下包含该成员的组列表 val projectGroupIds = authResourceGroupMemberDao.listResourceGroupMember( dslContext = dslContext, projectCode = projectCode, resourceType = AuthResourceType.PROJECT.value, memberId = memberId ).map { it.iamGroupId.toString() } - // 2. 通过项目组ID获取人员模板ID + // 通过项目组ID获取人员模板ID val iamTemplateId = authResourceGroupDao.listByRelationId( dslContext = dslContext, projectCode = projectCode, iamGroupIds = projectGroupIds ).filter { it.iamTemplateId != null } .map { it.iamTemplateId.toString() } - // 3. 获取成员直接加入的组和通过模板加入的组 + + // 后续改造,根据操作/资源类型/资源实例进行筛选,只需要扩展该方法即可 + val iamGroupIdsByConditions = queryIamGroupIdsByConditions( + projectCode = projectCode, + groupName = groupName + ) + // 获取成员直接加入的组和通过模板加入的组 val memberGroupCountMap = authResourceGroupMemberDao.countMemberGroup( dslContext = dslContext, projectCode = projectCode, memberId = memberId, - iamTemplateIds = iamTemplateId + iamTemplateIds = iamTemplateId, + iamGroupIds = iamGroupIdsByConditions, + minExpiredAt = minExpiredAt?.let { DateTimeUtil.convertTimestampToLocalDateTime(it / 1000) }, + maxExpiredAt = maxExpiredAt?.let { DateTimeUtil.convertTimestampToLocalDateTime(it / 1000) } ) val memberGroupCountList = mutableListOf() // 项目排在第一位 @@ -579,7 +695,8 @@ class RbacPermissionResourceMemberService constructor( override fun autoRenewal( projectCode: String, resourceType: String, - resourceCode: String + resourceCode: String, + validExpiredDay: Int ) { // 1、获取分级管理员或者二级管理员ID val managerId = authResourceService.get( @@ -600,7 +717,7 @@ class RbacPermissionResourceMemberService constructor( ) val currentTime = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()) // 预期的自动过期天数 - val expectAutoExpiredAt = currentTime + AUTO_VALID_EXPIRED_AT + val expectAutoExpiredAt = currentTime + TimeUnit.DAYS.toSeconds(validExpiredDay.toLong()) val autoRenewalMembers = mutableSetOf() resourceGroupInfoList.forEach group@{ resourceGroup -> val iamGroupId = resourceGroup.relationId.toInt() @@ -610,11 +727,14 @@ class RbacPermissionResourceMemberService constructor( } val groupMemberInfoList = iamV2ManagerService.getRoleGroupMemberV2(iamGroupId, pageInfoDTO).results groupMemberInfoList.forEach member@{ member -> - // 已过期或者要半年后才过期的,不自动过期 + // 已过期或者小于自动续期范围内的不做续期 if (member.expiredAt < currentTime || member.expiredAt > expectAutoExpiredAt - ) return@member - + ) { + val dataTime = DateTimeUtil.convertTimestampToLocalDateTime(member.expiredAt) + logger.info("Group member does not need to be renewed|$iamGroupId|$member|$dataTime") + return@member + } // 自动续期时间由半年+随机天数,防止同一时间同时过期 val expiredTime = currentTime + AUTO_RENEWAL_EXPIRED_AT + TimeUnit.DAYS.toSeconds(RandomUtils.nextLong(0, 180)) @@ -1292,15 +1412,27 @@ class RbacPermissionResourceMemberService constructor( memberId: String, resourceType: String?, iamGroupIds: List?, + groupName: String?, + minExpiredAt: Long?, + maxExpiredAt: Long?, start: Int?, limit: Int? ): SQLPage { + // 后续改造,根据操作/资源类型/资源实例进行筛选,只需要扩展该方法即可 + // 根据查询条件查询得到iam组id + val iamGroupIdsByConditions = queryIamGroupIdsByConditions( + projectCode = projectId, + groupName = groupName, + iamGroupIds = iamGroupIds + ) // 查询成员所在资源用户组列表,直接加入+通过用户组(模板)加入 val (count, resourceGroupMembers) = listResourceGroupMembers( projectCode = projectId, memberId = memberId, resourceType = resourceType, - iamGroupIds = iamGroupIds, + iamGroupIds = iamGroupIdsByConditions, + minExpiredAt = minExpiredAt, + maxExpiredAt = maxExpiredAt, start = start, limit = limit ) @@ -1344,6 +1476,8 @@ class RbacPermissionResourceMemberService constructor( memberId: String, resourceType: String? = null, iamGroupIds: List? = null, + minExpiredAt: Long? = null, + maxExpiredAt: Long? = null, start: Int? = null, limit: Int? = null ): Pair> { @@ -1352,13 +1486,17 @@ class RbacPermissionResourceMemberService constructor( projectCode = projectCode, memberId = memberId ) + val minExpiredTime = minExpiredAt?.let { DateTimeUtil.convertTimestampToLocalDateTime(it / 1000) } + val maxExpiredTime = maxExpiredAt?.let { DateTimeUtil.convertTimestampToLocalDateTime(it / 1000) } val count = authResourceGroupMemberDao.countMemberGroup( dslContext = dslContext, projectCode = projectCode, memberId = memberId, iamTemplateIds = iamTemplateIds, resourceType = resourceType, - iamGroupIds = iamGroupIds + iamGroupIds = iamGroupIds, + minExpiredAt = minExpiredTime, + maxExpiredAt = maxExpiredTime )[resourceType] ?: 0L val resourceGroupMembers = authResourceGroupMemberDao.listMemberGroupDetail( dslContext = dslContext, @@ -1367,6 +1505,8 @@ class RbacPermissionResourceMemberService constructor( iamTemplateIds = iamTemplateIds, resourceType = resourceType, iamGroupIds = iamGroupIds, + minExpiredAt = minExpiredTime, + maxExpiredAt = maxExpiredTime, offset = start, limit = limit ) diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionService.kt index 683f5e39384..b0881d6f901 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionService.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/RbacPermissionService.kt @@ -214,7 +214,11 @@ class RbacPermissionService constructor( val result = policyService.verifyPermissions(queryPolicyDTO) if (result) { - authProjectUserMetricsService.save(projectId = projectCode, userId = userId) + authProjectUserMetricsService.save( + projectId = projectCode, + userId = userId, + operate = useAction + ) } return result } finally { @@ -300,8 +304,12 @@ class RbacPermissionService constructor( actionList, listOf(resourceDTO) ) - if (result.values.any { it }) { - authProjectUserMetricsService.save(projectId = projectCode, userId = userId) + result.filter { it.value }.keys.forEach { action -> + authProjectUserMetricsService.save( + projectId = projectCode, + userId = userId, + operate = action + ) } return result } finally { diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/migrate/RbacPermissionMigrateService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/migrate/RbacPermissionMigrateService.kt index 7523c4fff65..25241bdb92c 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/migrate/RbacPermissionMigrateService.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/rbac/service/migrate/RbacPermissionMigrateService.kt @@ -616,7 +616,10 @@ class RbacPermissionMigrateService constructor( ) } - override fun autoRenewal(projectConditionDTO: ProjectConditionDTO): Boolean { + override fun autoRenewal( + validExpiredDay: Int, + projectConditionDTO: ProjectConditionDTO + ): Boolean { val traceId = MDC.get(TraceTag.BIZID) toRbacExecutorService.submit { MDC.put(TraceTag.BIZID, traceId) @@ -634,7 +637,8 @@ class RbacPermissionMigrateService constructor( migrateProjectsExecutorService.submit { MDC.put(TraceTag.BIZID, traceId) autoRenewal( - projectCode = migrateProject.englishName + projectCode = migrateProject.englishName, + validExpiredDay = validExpiredDay ) } } @@ -644,7 +648,10 @@ class RbacPermissionMigrateService constructor( return true } - private fun autoRenewal(projectCode: String) { + private fun autoRenewal( + projectCode: String, + validExpiredDay: Int + ) { var offset = 0 val limit = 100 val startTime = System.currentTimeMillis() @@ -666,7 +673,8 @@ class RbacPermissionMigrateService constructor( permissionResourceMemberService.autoRenewal( projectCode = projectCode, resourceType = resourceType, - resourceCode = resourceCode + resourceCode = resourceCode, + validExpiredDay = validExpiredDay ) } catch (ignored: Throwable) { logger.error("Failed to auto renewal|$projectCode|$resourceType|$resourceCode") diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/service/SamplePermissionMigrateService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/service/SamplePermissionMigrateService.kt index 13b3f743a0e..aedbef85813 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/service/SamplePermissionMigrateService.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/service/SamplePermissionMigrateService.kt @@ -85,7 +85,10 @@ class SamplePermissionMigrateService( return true } - override fun autoRenewal(projectConditionDTO: ProjectConditionDTO): Boolean { + override fun autoRenewal( + validExpiredDay: Int, + projectConditionDTO: ProjectConditionDTO + ): Boolean { return true } diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/service/SamplePermissionResourceMemberService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/service/SamplePermissionResourceMemberService.kt index e6f5eddc799..0ac24d6ab83 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/service/SamplePermissionResourceMemberService.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/provider/sample/service/SamplePermissionResourceMemberService.kt @@ -10,6 +10,7 @@ import com.tencent.devops.auth.pojo.request.GroupMemberCommonConditionReq import com.tencent.devops.auth.pojo.request.GroupMemberHandoverConditionReq import com.tencent.devops.auth.pojo.request.GroupMemberRenewalConditionReq import com.tencent.devops.auth.pojo.request.GroupMemberSingleRenewalReq +import com.tencent.devops.auth.pojo.request.ProjectMembersQueryConditionReq import com.tencent.devops.auth.pojo.request.RemoveMemberFromProjectReq import com.tencent.devops.auth.pojo.vo.BatchOperateGroupMemberCheckVo import com.tencent.devops.auth.pojo.vo.GroupDetailsInfoVo @@ -61,7 +62,8 @@ class SamplePermissionResourceMemberService : PermissionResourceMemberService { override fun autoRenewal( projectCode: String, resourceType: String, - resourceCode: String + resourceCode: String, + validExpiredDay: Int ) = Unit override fun renewalGroupMember( @@ -173,9 +175,18 @@ class SamplePermissionResourceMemberService : PermissionResourceMemberService { return SQLPage(count = 0, records = emptyList()) } + override fun listProjectMembersByComplexConditions( + projectMembersQueryConditionReq: ProjectMembersQueryConditionReq + ): SQLPage { + return SQLPage(count = 0, records = emptyList()) + } + override fun getMemberGroupsCount( projectCode: String, - memberId: String + memberId: String, + groupName: String?, + minExpiredAt: Long?, + maxExpiredAt: Long? ): List { return emptyList() } @@ -185,6 +196,9 @@ class SamplePermissionResourceMemberService : PermissionResourceMemberService { memberId: String, resourceType: String?, iamGroupIds: List?, + groupName: String?, + minExpiredAt: Long?, + maxExpiredAt: Long?, start: Int?, limit: Int? ): SQLPage { diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/OpAuthMigrateResourceImpl.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/OpAuthMigrateResourceImpl.kt index 945d6c3c838..b2b53bb43bd 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/OpAuthMigrateResourceImpl.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/OpAuthMigrateResourceImpl.kt @@ -94,8 +94,11 @@ class OpAuthMigrateResourceImpl @Autowired constructor( return Result(permissionMigrateService.migrateMonitorResource(projectCodes = projectCodes)) } - override fun autoRenewal(projectConditionDTO: ProjectConditionDTO): Result { - permissionMigrateService.autoRenewal(projectConditionDTO) + override fun autoRenewal(validExpiredDay: Int?, projectConditionDTO: ProjectConditionDTO): Result { + permissionMigrateService.autoRenewal( + validExpiredDay = validExpiredDay ?: 180, + projectConditionDTO = projectConditionDTO + ) return Result(true) } diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/UserAuthResourceGroupResourceImpl.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/UserAuthResourceGroupResourceImpl.kt index 43ed6851abe..6861454c155 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/UserAuthResourceGroupResourceImpl.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/UserAuthResourceGroupResourceImpl.kt @@ -71,6 +71,9 @@ class UserAuthResourceGroupResourceImpl @Autowired constructor( projectId: String, resourceType: String, memberId: String, + groupName: String?, + minExpiredAt: Long?, + maxExpiredAt: Long?, start: Int, limit: Int ): Result> { @@ -79,6 +82,9 @@ class UserAuthResourceGroupResourceImpl @Autowired constructor( projectId = projectId, resourceType = resourceType, memberId = memberId, + groupName = groupName, + minExpiredAt = minExpiredAt, + maxExpiredAt = maxExpiredAt, start = start, limit = limit ) diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/UserAuthResourceMemberResourceImpl.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/UserAuthResourceMemberResourceImpl.kt index d28b12689f4..8a9ade6f75e 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/UserAuthResourceMemberResourceImpl.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/resources/UserAuthResourceMemberResourceImpl.kt @@ -7,6 +7,7 @@ import com.tencent.devops.auth.pojo.request.GroupMemberCommonConditionReq import com.tencent.devops.auth.pojo.request.GroupMemberHandoverConditionReq import com.tencent.devops.auth.pojo.request.GroupMemberRenewalConditionReq import com.tencent.devops.auth.pojo.request.GroupMemberSingleRenewalReq +import com.tencent.devops.auth.pojo.request.ProjectMembersQueryConditionReq import com.tencent.devops.auth.pojo.request.RemoveMemberFromProjectReq import com.tencent.devops.auth.pojo.vo.BatchOperateGroupMemberCheckVo import com.tencent.devops.auth.pojo.vo.GroupDetailsInfoVo @@ -59,6 +60,19 @@ class UserAuthResourceMemberResourceImpl( } } + @BkManagerCheck + override fun listProjectMembersByCondition( + userId: String, + projectId: String, + projectMembersQueryConditionReq: ProjectMembersQueryConditionReq + ): Result> { + return Result( + permissionResourceMemberService.listProjectMembersByComplexConditions( + conditionReq = projectMembersQueryConditionReq + ) + ) + } + @BkManagerCheck override fun renewalGroupMember( userId: String, @@ -170,12 +184,18 @@ class UserAuthResourceMemberResourceImpl( override fun getMemberGroupCount( userId: String, projectId: String, - memberId: String + memberId: String, + groupName: String?, + minExpiredAt: Long?, + maxExpiredAt: Long? ): Result> { return Result( permissionResourceMemberService.getMemberGroupsCount( projectCode = projectId, - memberId = memberId + memberId = memberId, + groupName = groupName, + minExpiredAt = minExpiredAt, + maxExpiredAt = maxExpiredAt ) ) } diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/AuthProjectUserMetricsService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/AuthProjectUserMetricsService.kt index b19cbd28d91..72a5fcd7bf4 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/AuthProjectUserMetricsService.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/AuthProjectUserMetricsService.kt @@ -33,11 +33,16 @@ import com.google.common.hash.BloomFilter import com.google.common.hash.Funnels import com.tencent.devops.common.event.dispatcher.pipeline.mq.MeasureEventDispatcher import com.tencent.devops.common.event.pojo.measure.ProjectUserDailyEvent +import com.tencent.devops.common.event.pojo.measure.ProjectUserOperateMetricsData +import com.tencent.devops.common.event.pojo.measure.ProjectUserOperateMetricsEvent +import com.tencent.devops.common.event.pojo.measure.UserOperateCounterData import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired +import org.springframework.scheduling.annotation.Scheduled import org.springframework.stereotype.Service import java.nio.charset.Charset import java.time.LocalDate +import java.util.concurrent.Executors import java.util.concurrent.TimeUnit @Service @@ -45,39 +50,53 @@ import java.util.concurrent.TimeUnit class AuthProjectUserMetricsService @Autowired constructor( private val measureEventDispatcher: MeasureEventDispatcher ) { + private val userOperateCounterData = UserOperateCounterData() companion object { private val logger = LoggerFactory.getLogger(AuthProjectUserMetricsService::class.java) + // 期待的用户数10w private const val EXPECTED_USER_COUNT = 100000 + // 错误率0.1% private const val EXPECTED_FPP = 0.001 private val bloomFilterMap = CacheBuilder.newBuilder() .maximumSize(2) .expireAfterWrite(1, TimeUnit.DAYS) .build>() + + private val executorService = Executors.newFixedThreadPool(5) } fun save( projectId: String, - userId: String + userId: String, + operate: String ) { - val theDate = LocalDate.now() - try { - val bloomKey = "${projectId}_$userId" - val bloomFilter = getBloomFilter(theDate) - if (!bloomFilter.mightContain(bloomKey)) { - measureEventDispatcher.dispatch( - ProjectUserDailyEvent( - projectId = projectId, - userId = userId, - theDate = theDate + executorService.execute { + val theDate = LocalDate.now() + try { + val bloomKey = "${projectId}_$userId" + val bloomFilter = getBloomFilter(theDate) + if (!bloomFilter.mightContain(bloomKey)) { + measureEventDispatcher.dispatch( + ProjectUserDailyEvent( + projectId = projectId, + userId = userId, + theDate = theDate + ) ) + bloomFilter.put(bloomKey) + } + saveProjectUserOperateMetrics( + projectId = projectId, + userId = userId, + operate = operate, + theDate = theDate ) - bloomFilter.put(bloomKey) + } catch (ignored: Throwable) { + logger.error("save auth user error", ignored) } - } catch (ignored: Throwable) { - logger.error("save auth user error", ignored) } } @@ -93,4 +112,32 @@ class AuthProjectUserMetricsService @Autowired constructor( } return bloomFilter!! } + + private fun saveProjectUserOperateMetrics( + projectId: String, + userId: String, + operate: String, + theDate: LocalDate + ) { + val projectUserOperateMetricsKey = ProjectUserOperateMetricsData( + projectId = projectId, + userId = userId, + theDate = theDate, + operate = operate + ).getProjectUserOperateMetricsKey() + userOperateCounterData.increment(projectUserOperateMetricsKey) + } + + @Scheduled(initialDelay = 20000, fixedDelay = 20000) + private fun uploadProjectUserOperateMetrics() { + if (logger.isDebugEnabled) { + logger.debug("upload project user operate metrics :$userOperateCounterData") + } + measureEventDispatcher.dispatch( + ProjectUserOperateMetricsEvent( + userOperateCounterData = userOperateCounterData + ) + ) + userOperateCounterData.reset() + } } diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/iam/PermissionMigrateService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/iam/PermissionMigrateService.kt index 2d839d78036..477cc8fe7c4 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/iam/PermissionMigrateService.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/iam/PermissionMigrateService.kt @@ -97,6 +97,7 @@ interface PermissionMigrateService { ): Boolean fun autoRenewal( + validExpiredDay: Int, projectConditionDTO: ProjectConditionDTO ): Boolean diff --git a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/iam/PermissionResourceMemberService.kt b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/iam/PermissionResourceMemberService.kt index e0f01355221..7c0e902d40c 100644 --- a/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/iam/PermissionResourceMemberService.kt +++ b/src/backend/ci/core/auth/biz-auth/src/main/kotlin/com/tencent/devops/auth/service/iam/PermissionResourceMemberService.kt @@ -8,6 +8,7 @@ import com.tencent.devops.auth.pojo.request.GroupMemberCommonConditionReq import com.tencent.devops.auth.pojo.request.GroupMemberHandoverConditionReq import com.tencent.devops.auth.pojo.request.GroupMemberRenewalConditionReq import com.tencent.devops.auth.pojo.request.GroupMemberSingleRenewalReq +import com.tencent.devops.auth.pojo.request.ProjectMembersQueryConditionReq import com.tencent.devops.auth.pojo.request.RemoveMemberFromProjectReq import com.tencent.devops.auth.pojo.vo.BatchOperateGroupMemberCheckVo import com.tencent.devops.auth.pojo.vo.GroupDetailsInfoVo @@ -34,6 +35,11 @@ interface PermissionResourceMemberService { fun getProjectMemberCount(projectCode: String): ResourceMemberCountVO + /** + * 之所以将简单查询接口抽成单独方法,是因为该方法只用于查询用户名称/部门名称等, + 一方面,该方法职责比较单一;另一方面,该接口需要连表查询到授权资源表中授权人。 + 复杂查询虽然需要查询各种权限,但是不需要关联授权资源表。 + * */ fun listProjectMembers( projectCode: String, memberType: String?, @@ -44,21 +50,35 @@ interface PermissionResourceMemberService { pageSize: Int ): SQLPage + /** + * 根据复杂条件进行搜索,用于用户管理界面 + * */ + fun listProjectMembersByComplexConditions( + conditionReq: ProjectMembersQueryConditionReq + ): SQLPage + /** * 获取用户有权限的用户组数量 * */ fun getMemberGroupsCount( projectCode: String, - memberId: String + memberId: String, + groupName: String?, + minExpiredAt: Long?, + maxExpiredAt: Long? ): List - // 查询成员所在资源用户组详情,直接加入+通过用户组(模板)加入 - @Suppress("LongParameterList") + /** + * 查询成员所在资源用户组详情,直接加入+通过用户组(模板)加入 + * */ fun getMemberGroupsDetails( projectId: String, memberId: String, resourceType: String?, iamGroupIds: List? = null, + groupName: String? = null, + minExpiredAt: Long? = null, + maxExpiredAt: Long? = null, start: Int?, limit: Int? ): SQLPage @@ -115,7 +135,8 @@ interface PermissionResourceMemberService { fun autoRenewal( projectCode: String, resourceType: String, - resourceCode: String + resourceCode: String, + validExpiredDay: Int ) // 需审批版本 @@ -161,7 +182,6 @@ interface PermissionResourceMemberService { expiredAt: Long ): Boolean - @Suppress("LongParameterList") fun batchAddResourceGroupMembers( projectCode: String, iamGroupId: Int, diff --git a/src/backend/ci/core/common/common-api/src/main/kotlin/com/tencent/devops/common/api/util/DateTimeUtil.kt b/src/backend/ci/core/common/common-api/src/main/kotlin/com/tencent/devops/common/api/util/DateTimeUtil.kt index 53876182365..0159909c2cc 100644 --- a/src/backend/ci/core/common/common-api/src/main/kotlin/com/tencent/devops/common/api/util/DateTimeUtil.kt +++ b/src/backend/ci/core/common/common-api/src/main/kotlin/com/tencent/devops/common/api/util/DateTimeUtil.kt @@ -137,6 +137,9 @@ object DateTimeUtil { return localDateTime?.toEpochSecond(ZoneOffset.ofHours(8)) ?: 0L } + /* + * 用于转化秒级时间戳,非毫秒级 + * */ fun convertTimestampToLocalDateTime(timestamp: Long): LocalDateTime { return LocalDateTime.ofInstant(Instant.ofEpochSecond(timestamp), ZoneId.systemDefault()) } diff --git a/src/backend/ci/core/common/common-api/src/main/kotlin/com/tencent/devops/common/api/util/EnumUtil.kt b/src/backend/ci/core/common/common-api/src/main/kotlin/com/tencent/devops/common/api/util/EnumUtil.kt index f30a88eb446..ad5b9dd154a 100644 --- a/src/backend/ci/core/common/common-api/src/main/kotlin/com/tencent/devops/common/api/util/EnumUtil.kt +++ b/src/backend/ci/core/common/common-api/src/main/kotlin/com/tencent/devops/common/api/util/EnumUtil.kt @@ -27,12 +27,12 @@ package com.tencent.devops.common.api.util -import sun.reflect.ConstructorAccessor -import sun.reflect.FieldAccessor +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import sun.misc.Unsafe import sun.reflect.ReflectionFactory import java.lang.reflect.AccessibleObject import java.lang.reflect.Field -import java.lang.reflect.Modifier import kotlin.reflect.full.isSubclassOf /** @@ -98,14 +98,17 @@ object EnumUtil { values.add(value) } - // 构建新枚举值 - val newValue: T = makeEnum( - enumClass = enumType, - value = enumName, - ordinal = ordinal, - additionalTypes = additionalTypes.toTypedArray(), - additionalValues = additionalValues - ) +// // 构建新枚举值 +// val newValue: T = makeEnum( +// enumClass = enumType, +// value = enumName, +// ordinal = ordinal, +// additionalTypes = additionalTypes.toTypedArray(), +// additionalValues = additionalValues +// ) + + // 构造新的枚举实例 + val newValue = makeEnum(enumType, enumName, ordinal, additionalValues) if (ordinal < previousValues.size) { values[ordinal] = newValue @@ -113,7 +116,7 @@ object EnumUtil { values.add(newValue) } - setFailSafeFieldValue(field = valuesField, target = null, value = values.toTypedArray()) + setStaticFieldValue(enumType, valuesField, values.toTypedArray()) cleanEnumCache(enumType) } catch (e: Exception) { @@ -124,22 +127,31 @@ object EnumUtil { } @Throws(NoSuchFieldException::class, IllegalAccessException::class) - fun setFailSafeFieldValue(field: Field, target: Any?, value: Any?) { - field.isAccessible = true - val modifiersField: Field = Field::class.java.getDeclaredField("modifiers") - modifiersField.isAccessible = true - var modifiers: Int = modifiersField.getInt(field) - modifiers = modifiers and Modifier.FINAL.inv() - modifiersField.setInt(field, modifiers) - val fieldAccessor: FieldAccessor = reflectionFactory.newFieldAccessor(field, false) - fieldAccessor.set(target, value) + inline fun setStaticFieldValue( + enumType: Class, + valuesField: Field, + newValues: Any? + ) { + val unsafe = getUnsafe() + val arrayBaseOffset = unsafe.staticFieldOffset(valuesField) + unsafe.putObjectVolatile(enumType, arrayBaseOffset, newValues) + } + + inline fun setObjectFieldValue( + enumType: Class, + valuesField: Field, + newValues: Any? + ) { + val unsafe = getUnsafe() + val arrayBaseOffset = unsafe.objectFieldOffset(valuesField) + unsafe.putObjectVolatile(enumType, arrayBaseOffset, newValues) } @Throws(NoSuchFieldException::class, IllegalAccessException::class) inline fun blankField(enumClass: Class, fieldName: String) { for (field in Class::class.java.declaredFields) { if (field.name.contains(fieldName)) { - setFailSafeFieldValue(field, enumClass, null) + setObjectFieldValue(enumClass, field, null) break } } @@ -151,28 +163,6 @@ object EnumUtil { blankField(enumClass, "enumConstants") // IBM JDK } - @Throws(NoSuchMethodException::class) - inline fun getConstructorAccessor( - enumClass: Class, - additionalParameterTypes: Array> - ): ConstructorAccessor? { - val parameterTypes = arrayOfNulls?>(additionalParameterTypes.size + 2) - parameterTypes[0] = String::class.java // enum class first field: field name - parameterTypes[1] = Int::class.javaPrimitiveType // enum class second field: ordinal - System.arraycopy(additionalParameterTypes, 0, parameterTypes, 2, additionalParameterTypes.size) - enumClass.declaredConstructors.forEach { constructor -> - if (compareParameterType(constructor.parameterTypes, parameterTypes)) { - try { - return reflectionFactory.newConstructorAccessor(constructor) - } catch (ignored: IllegalArgumentException) { - // skip illegal argument try next one - } - } - } - - return reflectionFactory.newConstructorAccessor(enumClass.getDeclaredConstructor(*parameterTypes)) - } - fun compareParameterType(constructorParameterType: Array>, parameterTypes: Array?>): Boolean { if (constructorParameterType.size != parameterTypes.size) { return false @@ -181,7 +171,8 @@ object EnumUtil { if (constructorParameterType[i] !== parameterTypes[i]) { if (constructorParameterType[i].isPrimitive && parameterTypes[i]!!.isPrimitive) { if (constructorParameterType[i].kotlin.javaPrimitiveType - !== parameterTypes[i]!!.kotlin.javaPrimitiveType) { + !== parameterTypes[i]!!.kotlin.javaPrimitiveType + ) { return false } } @@ -190,20 +181,83 @@ object EnumUtil { return true } - @Throws(Exception::class) +// @Throws(Exception::class) +// inline fun makeEnum( +// enumClass: Class, +// value: String, +// ordinal: Int, +// additionalTypes: Array>, +// additionalValues: Array +// ): T { +// val params = arrayOfNulls(additionalValues.size + 2) +// params[0] = value +// params[1] = Integer.valueOf(ordinal) +// System.arraycopy(additionalValues, 0, params, 2, additionalValues.size) +// return enumClass.cast(getConstructorAccessor(enumClass, additionalTypes)!!.newInstance(params)) +// } + + // 创建新的枚举实例 inline fun makeEnum( - enumClass: Class, - value: String, + enumType: Class, + name: String, ordinal: Int, - additionalTypes: Array>, additionalValues: Array ): T { - val params = arrayOfNulls(additionalValues.size + 2) - params[0] = value - params[1] = Integer.valueOf(ordinal) - System.arraycopy(additionalValues, 0, params, 2, additionalValues.size) - return enumClass.cast(getConstructorAccessor(enumClass, additionalTypes)!!.newInstance(params)) + val unsafe = getUnsafe() + val obj = unsafe.allocateInstance(enumType) + + // 使用Unsafe设置name、ordinal字段 + setEnumFieldValue(obj, "name", name) + setEnumFieldValue(obj, "ordinal", ordinal) + + // 初始化自定义字段 + val enumFields = + enumType.declaredFields.filterNot { it.isSynthetic || it.isEnumConstant || it.name == "Companion" } + if (additionalValues.size < enumFields.size) { + logger.warn("additionalValues size(${additionalValues.size}) less than enumField size(${enumFields.size})") + } else { + enumFields.forEachIndexed { index, field -> + field.isAccessible = true + field.set(obj, additionalValues[index]) + } + } + + return obj as T + } + + // 使用Unsafe设置枚举字段值 + fun setEnumFieldValue(enumInstance: Any, fieldName: String, value: Any) { + try { + val field = enumInstance.javaClass.superclass.getDeclaredField(fieldName) + val unsafe = getUnsafe() + val offset = unsafe.objectFieldOffset(field) + when (value) { + is Int -> unsafe.putInt(enumInstance, offset, value) + is Long -> unsafe.putLong(enumInstance, offset, value) + is Short -> unsafe.putShort(enumInstance, offset, value) + is Byte -> unsafe.putByte(enumInstance, offset, value) + is Float -> unsafe.putFloat(enumInstance, offset, value) + is Double -> unsafe.putDouble(enumInstance, offset, value) + is Boolean -> unsafe.putBoolean(enumInstance, offset, value) + is Char -> unsafe.putChar(enumInstance, offset, value) + else -> unsafe.putObject(enumInstance, offset, value) + } + } catch (e: Exception) { + e.printStackTrace() + } + } + + // 从 JVM 获取 Unsafe 实例 + fun getUnsafe(): Unsafe { + return try { + val field = Unsafe::class.java.getDeclaredField("theUnsafe") + field.isAccessible = true + field.get(null) as Unsafe + } catch (e: Exception) { + throw RuntimeException("Unsafe not found", e) + } } val reflectionFactory: ReflectionFactory = ReflectionFactory.getReflectionFactory() + val logger: Logger = LoggerFactory.getLogger(EnumUtil::class.java) } diff --git a/src/backend/ci/core/common/common-auth/common-auth-api/src/main/kotlin/com/tencent/devops/common/auth/api/pojo/EsbBaseReq.kt b/src/backend/ci/core/common/common-auth/common-auth-api/src/main/kotlin/com/tencent/devops/common/auth/api/pojo/EsbBaseReq.kt index 3431e484bf3..8b92069eaa8 100644 --- a/src/backend/ci/core/common/common-auth/common-auth-api/src/main/kotlin/com/tencent/devops/common/auth/api/pojo/EsbBaseReq.kt +++ b/src/backend/ci/core/common/common-auth/common-auth-api/src/main/kotlin/com/tencent/devops/common/auth/api/pojo/EsbBaseReq.kt @@ -27,8 +27,6 @@ package com.tencent.devops.common.auth.api.pojo -import com.tencent.devops.common.api.util.JsonUtil - @Suppress("ALL") abstract class EsbBaseReq( open var bk_app_code: String, @@ -36,5 +34,14 @@ abstract class EsbBaseReq( open var bk_username: String, open val bk_token: String = "" ) { - fun toMap() = mapOf("X-Bkapi-Authorization" to JsonUtil.toJson(this).replace("\\s".toRegex(), "")) + fun toMap(): Map { + return mapOf( + "X-Bkapi-Authorization" to """{ + "bk_app_code":"$bk_app_code", + "bk_app_secret":"$bk_app_secret", + "bk_username":"$bk_username", + "bk_token":"$bk_token" + }""".trimIndent().replace("\\s".toRegex(), "") + ) + } } diff --git a/src/backend/ci/core/common/common-event/src/main/kotlin/com/tencent/devops/common/event/dispatcher/pipeline/mq/MQ.kt b/src/backend/ci/core/common/common-event/src/main/kotlin/com/tencent/devops/common/event/dispatcher/pipeline/mq/MQ.kt index a7ac05e342e..e602baff8e6 100644 --- a/src/backend/ci/core/common/common-event/src/main/kotlin/com/tencent/devops/common/event/dispatcher/pipeline/mq/MQ.kt +++ b/src/backend/ci/core/common/common-event/src/main/kotlin/com/tencent/devops/common/event/dispatcher/pipeline/mq/MQ.kt @@ -326,6 +326,10 @@ object MQ { const val EXCHANGE_PROJECT_USER_DAILY_FANOUT = "e.metrics.project.user.daily.exchange.fanout" const val QUEUE_PROJECT_USER_DAILY_METRICS = "q.metrics.project.user.daily.queue" + const val ROUTE_PROJECT_USER_DAILY_METRICS = "r.metrics.project.user.daily" + + const val QUEUE_PROJECT_USER_DAILY_OPERATE_METRICS = "q.metrics.project.user.daily.operate.queue" + const val ROUTE_PROJECT_USER_DAILY_OPERATE_METRICS = "r.metrics.project.user.daily.operate" // 项目启用同步组和成员事件 const val QUEUE_PROJECT_ENABLED_SYNC_GROUP_AND_MEMBER = "q.project.enabled.sync.group.and.member" diff --git a/src/backend/ci/core/common/common-event/src/main/kotlin/com/tencent/devops/common/event/pojo/measure/ProjectUserDailyEvent.kt b/src/backend/ci/core/common/common-event/src/main/kotlin/com/tencent/devops/common/event/pojo/measure/ProjectUserDailyEvent.kt index 43fa018fd9a..25a72bc46c3 100644 --- a/src/backend/ci/core/common/common-event/src/main/kotlin/com/tencent/devops/common/event/pojo/measure/ProjectUserDailyEvent.kt +++ b/src/backend/ci/core/common/common-event/src/main/kotlin/com/tencent/devops/common/event/pojo/measure/ProjectUserDailyEvent.kt @@ -33,7 +33,7 @@ import com.tencent.devops.common.event.dispatcher.pipeline.mq.MQ import io.swagger.v3.oas.annotations.media.Schema import java.time.LocalDate -@Event(exchange = MQ.EXCHANGE_PROJECT_USER_DAILY_FANOUT) +@Event(exchange = MQ.EXCHANGE_PROJECT_USER_DAILY_FANOUT, routeKey = MQ.ROUTE_PROJECT_USER_DAILY_METRICS) data class ProjectUserDailyEvent( @get:Schema(title = "项目ID") override val projectId: String, diff --git a/src/backend/ci/core/common/common-event/src/main/kotlin/com/tencent/devops/common/event/pojo/measure/ProjectUserOperateMetricsData.kt b/src/backend/ci/core/common/common-event/src/main/kotlin/com/tencent/devops/common/event/pojo/measure/ProjectUserOperateMetricsData.kt new file mode 100644 index 00000000000..6dec36f25e0 --- /dev/null +++ b/src/backend/ci/core/common/common-event/src/main/kotlin/com/tencent/devops/common/event/pojo/measure/ProjectUserOperateMetricsData.kt @@ -0,0 +1,32 @@ +package com.tencent.devops.common.event.pojo.measure + +import com.tencent.devops.common.api.util.DateTimeUtil +import io.swagger.v3.oas.annotations.media.Schema +import java.time.LocalDate + +data class ProjectUserOperateMetricsData( + @get:Schema(title = "项目ID") + val projectId: String, + @get:Schema(title = "用户ID") + val userId: String, + @get:Schema(title = "统计日期") + val theDate: LocalDate, + @get:Schema(title = "操作") + val operate: String +) { + companion object { + fun build(projectUserOperateMetricsKey: String): ProjectUserOperateMetricsData { + val list = projectUserOperateMetricsKey.split(":") + return ProjectUserOperateMetricsData( + projectId = list[1], + userId = list[2], + operate = list[3], + theDate = DateTimeUtil.stringToLocalDate(list[4])!! + ) + } + } + + fun getProjectUserOperateMetricsKey(): String { + return "key:$projectId:$userId:$operate:$theDate" + } +} diff --git a/src/backend/ci/core/common/common-event/src/main/kotlin/com/tencent/devops/common/event/pojo/measure/ProjectUserOperateMetricsEvent.kt b/src/backend/ci/core/common/common-event/src/main/kotlin/com/tencent/devops/common/event/pojo/measure/ProjectUserOperateMetricsEvent.kt new file mode 100644 index 00000000000..894a37fb3eb --- /dev/null +++ b/src/backend/ci/core/common/common-event/src/main/kotlin/com/tencent/devops/common/event/pojo/measure/ProjectUserOperateMetricsEvent.kt @@ -0,0 +1,39 @@ +/* + * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license. + * + * A copy of the MIT License is included in this file. + * + * + * Terms of the MIT License: + * --------------------------------------------------- + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of + * the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT + * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +package com.tencent.devops.common.event.pojo.measure + +import com.tencent.devops.common.event.annotation.Event +import com.tencent.devops.common.event.dispatcher.pipeline.mq.MQ +import io.swagger.v3.oas.annotations.media.Schema + +@Event(exchange = MQ.EXCHANGE_PROJECT_USER_DAILY_FANOUT, routeKey = MQ.ROUTE_PROJECT_USER_DAILY_OPERATE_METRICS) +data class ProjectUserOperateMetricsEvent( + @get:Schema(title = "项目用户操作度量数据") + val userOperateCounterData: UserOperateCounterData +) : IMeasureEvent(projectId = "", pipelineId = "", buildId = "") diff --git a/src/backend/ci/core/common/common-event/src/main/kotlin/com/tencent/devops/common/event/pojo/measure/UserOperateCounterData.kt b/src/backend/ci/core/common/common-event/src/main/kotlin/com/tencent/devops/common/event/pojo/measure/UserOperateCounterData.kt new file mode 100644 index 00000000000..fdb1913124b --- /dev/null +++ b/src/backend/ci/core/common/common-event/src/main/kotlin/com/tencent/devops/common/event/pojo/measure/UserOperateCounterData.kt @@ -0,0 +1,23 @@ +package com.tencent.devops.common.event.pojo.measure + +import java.util.concurrent.ConcurrentHashMap + +class UserOperateCounterData { + private val userOperationCountMap: ConcurrentHashMap = ConcurrentHashMap() + + fun increment(projectUserOperateMetricsKey: String) { + userOperationCountMap.merge(projectUserOperateMetricsKey, 1, Integer::sum) + } + + fun getCount(projectUserOperateMetricsKey: String): Int { + return userOperationCountMap.getOrDefault(projectUserOperateMetricsKey, 0) + } + + fun reset() { + userOperationCountMap.clear() + } + + fun getUserOperationCountMap(): Map { + return userOperationCountMap + } +} diff --git a/src/backend/ci/core/common/common-pipeline-yaml/src/main/kotlin/com/tencent/devops/process/yaml/transfer/ContainerTransfer.kt b/src/backend/ci/core/common/common-pipeline-yaml/src/main/kotlin/com/tencent/devops/process/yaml/transfer/ContainerTransfer.kt index b99fb572a33..91afd4e7e5d 100644 --- a/src/backend/ci/core/common/common-pipeline-yaml/src/main/kotlin/com/tencent/devops/process/yaml/transfer/ContainerTransfer.kt +++ b/src/backend/ci/core/common/common-pipeline-yaml/src/main/kotlin/com/tencent/devops/process/yaml/transfer/ContainerTransfer.kt @@ -415,11 +415,11 @@ class ContainerTransfer @Autowired(required = false) constructor( } private fun getMutexYaml(resource: MutexGroup?): Mutex? { - if (resource?.mutexGroupName.isNullOrBlank()) { + if (resource?.mutexGroupName.isNullOrBlank() || resource?.enable != true) { return null } return Mutex( - label = resource?.mutexGroupName!!, + label = resource.mutexGroupName!!, queueLength = if (resource.queueEnable) { resource.queue } else { diff --git a/src/backend/ci/core/dispatch/biz-dispatch/src/main/kotlin/com/tencent/devops/dispatch/service/ThirdPartyDispatchService.kt b/src/backend/ci/core/dispatch/biz-dispatch/src/main/kotlin/com/tencent/devops/dispatch/service/ThirdPartyDispatchService.kt index 5fa689af8ee..5c44857c473 100644 --- a/src/backend/ci/core/dispatch/biz-dispatch/src/main/kotlin/com/tencent/devops/dispatch/service/ThirdPartyDispatchService.kt +++ b/src/backend/ci/core/dispatch/biz-dispatch/src/main/kotlin/com/tencent/devops/dispatch/service/ThirdPartyDispatchService.kt @@ -127,7 +127,7 @@ class ThirdPartyDispatchService @Autowired constructor( ) } - buildByAgentId(dispatchMessage, dispatchType.copy(displayName = agentId, agentType = AgentType.ID)) + buildByAgentId(dispatchMessage, dispatchType.copy(displayName = agentId)) } is ThirdPartyAgentEnvDispatchType -> { @@ -341,18 +341,21 @@ class ThirdPartyDispatchService @Autowired constructor( } catch (e: Exception) { logger.error("inQueue|doAgentInQueue|error", e) } - } else if (redisOperation.get(lockKey) != null) { - // 没有复用逻辑的需要检查下如果这个机器剩一个可调度空间且有复用锁那么不能进行调度 - val checkRes = if (dockerInfo != null) { - ((agent.dockerParallelTaskCount ?: 4) - - thirdPartyAgentBuildService.getDockerRunningBuilds(agent.agentId)) <= 1 - } else { - ((agent.parallelTaskCount ?: 4) - - thirdPartyAgentBuildService.getRunningBuilds(agent.agentId)) <= 1 - } - if (checkRes) { - logAgentReuse(lockKey, dispatchMessage, agent) - return false + } else { + val lockedBuildId = redisOperation.get(lockKey) + if (!lockedBuildId.isNullOrBlank() && lockedBuildId != event.buildId) { + // 没有复用逻辑的需要检查下如果这个机器剩一个可调度空间且有复用锁那么不能进行调度 + val checkRes = if (dockerInfo != null) { + ((agent.dockerParallelTaskCount ?: 4) - + thirdPartyAgentBuildService.getDockerRunningBuilds(agent.agentId)) <= 1 + } else { + ((agent.parallelTaskCount ?: 4) - + thirdPartyAgentBuildService.getRunningBuilds(agent.agentId)) <= 1 + } + if (checkRes) { + logAgentReuse(lockKey, dispatchMessage, agent) + return false + } } } diff --git a/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/config/MetricsListenerConfiguration.kt b/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/config/MetricsListenerConfiguration.kt index f204b227af8..e85fbb27727 100644 --- a/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/config/MetricsListenerConfiguration.kt +++ b/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/config/MetricsListenerConfiguration.kt @@ -30,6 +30,7 @@ package com.tencent.devops.metrics.config import com.tencent.devops.common.event.dispatcher.pipeline.mq.MQ import com.tencent.devops.common.event.dispatcher.pipeline.mq.MQ.QUEUE_DISPATCH_JOB_METRICS import com.tencent.devops.common.event.dispatcher.pipeline.mq.MQ.QUEUE_PROJECT_USER_DAILY_METRICS +import com.tencent.devops.common.event.dispatcher.pipeline.mq.MQ.QUEUE_PROJECT_USER_DAILY_OPERATE_METRICS import com.tencent.devops.common.event.dispatcher.pipeline.mq.Tools import com.tencent.devops.common.web.mq.EXTEND_CONNECTION_FACTORY_NAME import com.tencent.devops.common.web.mq.EXTEND_RABBIT_ADMIN_NAME @@ -37,8 +38,10 @@ import com.tencent.devops.metrics.listener.BuildEndMetricsDataReportListener import com.tencent.devops.metrics.listener.DispatchJobMetricsListener import com.tencent.devops.metrics.listener.LabelChangeMetricsDataSyncListener import com.tencent.devops.metrics.listener.ProjectUserDailyMetricsListener +import com.tencent.devops.metrics.listener.ProjectUserDailyOperateMetricsListener import org.springframework.amqp.core.Binding import org.springframework.amqp.core.BindingBuilder +import org.springframework.amqp.core.DirectExchange import org.springframework.amqp.core.FanoutExchange import org.springframework.amqp.core.Queue import org.springframework.amqp.rabbit.connection.ConnectionFactory @@ -96,26 +99,26 @@ class MetricsListenerConfiguration { ) } - @Bean - fun projectUserDailyMetricsQueue() = Queue(QUEUE_PROJECT_USER_DAILY_METRICS) - /** - * 插件监控数据上报广播交换机 + * 用户审计数据上报交换机 */ @Bean - fun projectUserDailyMetricsFanoutExchange(): FanoutExchange { - val fanoutExchange = FanoutExchange(MQ.EXCHANGE_PROJECT_USER_DAILY_FANOUT, true, false) - fanoutExchange.isDelayed = true - return fanoutExchange + fun projectUserDailyMetricsExchange(): DirectExchange { + val directExchange = DirectExchange(MQ.EXCHANGE_PROJECT_USER_DAILY_FANOUT, true, false) + directExchange.isDelayed = true + return directExchange } + @Bean + fun projectUserDailyMetricsQueue() = Queue(QUEUE_PROJECT_USER_DAILY_METRICS) + @Bean fun projectUserDailyMetricsQueueBind( @Autowired projectUserDailyMetricsQueue: Queue, - @Autowired projectUserDailyMetricsFanoutExchange: FanoutExchange + @Autowired projectUserDailyMetricsExchange: DirectExchange ): Binding { return BindingBuilder.bind(projectUserDailyMetricsQueue) - .to(projectUserDailyMetricsFanoutExchange) + .to(projectUserDailyMetricsExchange).with(MQ.ROUTE_PROJECT_USER_DAILY_METRICS) } @Bean @@ -139,6 +142,39 @@ class MetricsListenerConfiguration { ) } + @Bean + fun projectUserDailyOperateMetricsQueue() = Queue(QUEUE_PROJECT_USER_DAILY_OPERATE_METRICS) + + @Bean + fun projectUserDailyOperateMetricsQueueBind( + @Autowired projectUserDailyOperateMetricsQueue: Queue, + @Autowired projectUserDailyMetricsExchange: DirectExchange + ): Binding { + return BindingBuilder.bind(projectUserDailyOperateMetricsQueue) + .to(projectUserDailyMetricsExchange).with(MQ.ROUTE_PROJECT_USER_DAILY_OPERATE_METRICS) + } + + @Bean + fun projectUserDailyOperateMetricsListenerContainer( + @Qualifier(EXTEND_CONNECTION_FACTORY_NAME) @Autowired connectionFactory: ConnectionFactory, + @Autowired projectUserDailyOperateMetricsQueue: Queue, + @Qualifier(value = EXTEND_RABBIT_ADMIN_NAME) @Autowired rabbitAdmin: RabbitAdmin, + @Autowired listener: ProjectUserDailyOperateMetricsListener, + @Autowired messageConverter: Jackson2JsonMessageConverter + ): SimpleMessageListenerContainer { + return Tools.createSimpleMessageListenerContainer( + connectionFactory = connectionFactory, + queue = projectUserDailyOperateMetricsQueue, + rabbitAdmin = rabbitAdmin, + buildListener = listener, + messageConverter = messageConverter, + startConsumerMinInterval = 1000, + consecutiveActiveTrigger = 5, + concurrency = 5, + maxConcurrency = 50 + ) + } + @Bean fun pipelineLabelChangeMetricsDataSyncQueue() = Queue(QUEUE_PIPELINE_LABEL_CHANGE_METRICS_DATA_SYNC) diff --git a/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/dao/DispatchJobMetricsDao.kt b/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/dao/DispatchJobMetricsDao.kt index 762f25fcaff..95892c8aeb7 100644 --- a/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/dao/DispatchJobMetricsDao.kt +++ b/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/dao/DispatchJobMetricsDao.kt @@ -82,6 +82,7 @@ class DispatchJobMetricsDao { ).from(this) .where(PROJECT_ID.eq(dispatchJobReq.projectId)) .and(CREATE_TIME.between(startDateTime, endDateTime)) + .and(CHANNEL_CODE.eq("BS")) .groupBy(PROJECT_ID, JOB_TYPE) .asTable("subQuery") diff --git a/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/dao/ProjectBuildSummaryDao.kt b/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/dao/ProjectBuildSummaryDao.kt index b5131c10c0f..4867234d7f3 100644 --- a/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/dao/ProjectBuildSummaryDao.kt +++ b/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/dao/ProjectBuildSummaryDao.kt @@ -29,11 +29,13 @@ package com.tencent.devops.metrics.dao import com.tencent.devops.common.api.util.DateTimeUtil +import com.tencent.devops.common.event.pojo.measure.ProjectUserOperateMetricsData import com.tencent.devops.common.pipeline.enums.StartType import com.tencent.devops.metrics.pojo.vo.BaseQueryReqVO import com.tencent.devops.metrics.pojo.vo.ProjectUserCountV0 import com.tencent.devops.model.metrics.tables.TProjectBuildSummaryDaily import com.tencent.devops.model.metrics.tables.TProjectUserDaily +import com.tencent.devops.model.metrics.tables.TProjectUserOperateDaily import org.jooq.DSLContext import org.springframework.stereotype.Repository import java.time.LocalDate @@ -137,4 +139,37 @@ class ProjectBuildSummaryDao { .execute() } } + + fun saveUserOperateCount( + dslContext: DSLContext, + projectUserOperateMetricsData2OperateCount: Map + ) { + with(TProjectUserOperateDaily.T_PROJECT_USER_OPERATE_DAILY) { + dslContext.insertInto( + this, + PROJECT_ID, + USER_ID, + OPERATE, + THE_DATE, + OPERATE_COUNT, + CREATE_TIME + ).also { insert -> + projectUserOperateMetricsData2OperateCount.forEach { (projectUserOperateMetricsDataKey, operateCount) -> + val projectUserOperateMetricsData = ProjectUserOperateMetricsData.build( + projectUserOperateMetricsKey = projectUserOperateMetricsDataKey + ) + insert.values( + projectUserOperateMetricsData.projectId, + projectUserOperateMetricsData.userId, + projectUserOperateMetricsData.operate, + projectUserOperateMetricsData.theDate, + operateCount, + LocalDateTime.now() + ).onDuplicateKeyUpdate() + .set(OPERATE_COUNT, OPERATE_COUNT + operateCount) + .execute() + } + } + } + } } diff --git a/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/listener/ProjectUserDailyMetricsListener.kt b/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/listener/ProjectUserDailyMetricsListener.kt index eb9dab1c33e..e50c8a3f1b3 100644 --- a/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/listener/ProjectUserDailyMetricsListener.kt +++ b/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/listener/ProjectUserDailyMetricsListener.kt @@ -47,6 +47,9 @@ class ProjectUserDailyMetricsListener @Autowired constructor( override fun execute(event: ProjectUserDailyEvent) { try { with(event) { + if (logger.isDebugEnabled) { + logger.debug("consumer project user daily metrics event|$event") + } projectBuildSummaryService.saveProjectUser( projectId = projectId, userId = userId, diff --git a/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/listener/ProjectUserDailyOperateMetricsListener.kt b/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/listener/ProjectUserDailyOperateMetricsListener.kt new file mode 100644 index 00000000000..f4feb9265cb --- /dev/null +++ b/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/listener/ProjectUserDailyOperateMetricsListener.kt @@ -0,0 +1,58 @@ +/* + * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available. + * + * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license. + * + * A copy of the MIT License is included in this file. + * + * + * Terms of the MIT License: + * --------------------------------------------------- + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of + * the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT + * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +package com.tencent.devops.metrics.listener + +import com.tencent.devops.common.event.listener.Listener +import com.tencent.devops.common.event.pojo.measure.ProjectUserOperateMetricsEvent +import com.tencent.devops.metrics.service.ProjectBuildSummaryService +import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.stereotype.Component + +@Component +class ProjectUserDailyOperateMetricsListener @Autowired constructor( + private val projectBuildSummaryService: ProjectBuildSummaryService +) : Listener { + companion object { + private val logger = LoggerFactory.getLogger(ProjectUserDailyOperateMetricsListener::class.java) + } + + override fun execute(event: ProjectUserOperateMetricsEvent) { + try { + if (logger.isDebugEnabled) { + logger.debug("consumer project user daily operate metrics :${event.userOperateCounterData}") + } + projectBuildSummaryService.saveProjectUserOperateMetrics( + userOperateCounterData = event.userOperateCounterData + ) + } catch (ignored: Throwable) { + logger.warn("Fail to insert project user metrics data", ignored) + } + } +} diff --git a/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/service/ProjectBuildSummaryService.kt b/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/service/ProjectBuildSummaryService.kt index 4ce221cb7b7..a92630093e3 100644 --- a/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/service/ProjectBuildSummaryService.kt +++ b/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/service/ProjectBuildSummaryService.kt @@ -28,6 +28,7 @@ package com.tencent.devops.metrics.service +import com.tencent.devops.common.event.pojo.measure.UserOperateCounterData import com.tencent.devops.metrics.pojo.vo.BaseQueryReqVO import com.tencent.devops.metrics.pojo.vo.ProjectUserCountV0 import java.time.LocalDate @@ -51,6 +52,11 @@ interface ProjectBuildSummaryService { theDate: LocalDate ) + /** + * 保存用户操作度量数据 + */ + fun saveProjectUserOperateMetrics(userOperateCounterData: UserOperateCounterData) + /** * 获取项目活跃用户数 */ diff --git a/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/service/builds/MetricsCacheService.kt b/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/service/builds/MetricsCacheService.kt index 7f0f6075241..06ce9fc887a 100644 --- a/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/service/builds/MetricsCacheService.kt +++ b/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/service/builds/MetricsCacheService.kt @@ -72,7 +72,12 @@ class MetricsCacheService @Autowired constructor( cache.addPropertyChangeListener { change: PropertyChangeEvent -> when { change is ObservableMap.PropertyAddedEvent && this::addFunction.isInitialized -> { - addFunction(change.propertyName, change.newValue as MetricsUserPO) + kotlin.runCatching { + addFunction(change.propertyName, change.newValue as MetricsUserPO) + }.onFailure { + logger.error("cache error while adding " + change.propertyName, it) + removeCache(change.propertyName) + } } change is ObservableMap.PropertyUpdatedEvent && this::updateFunction.isInitialized -> { @@ -84,7 +89,9 @@ class MetricsCacheService @Autowired constructor( } change is ObservableMap.PropertyRemovedEvent && this::removeFunction.isInitialized -> { - removeFunction(change.propertyName, change.oldValue as MetricsUserPO) + if (change.oldValue != null) { + removeFunction(change.propertyName, change.oldValue as MetricsUserPO) + } } } } diff --git a/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/service/builds/MetricsUserService.kt b/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/service/builds/MetricsUserService.kt index 0ef3de632e9..3e2a5e4afcc 100644 --- a/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/service/builds/MetricsUserService.kt +++ b/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/service/builds/MetricsUserService.kt @@ -145,6 +145,7 @@ class MetricsUserService @Autowired constructor( override fun run() { while (true) { kotlin.runCatching { execute() } + .onFailure { logger.error("DeleteDelayProcess error ${it.message}", it) } Thread.sleep(SLEEP) } } @@ -195,7 +196,7 @@ class MetricsUserService @Autowired constructor( } CallBackEvent.BUILD_JOB_START -> { - if (event.jobId == null) { + if (event.jobId.isNullOrBlank()) { // job id 用户没填写将不会上报指标 return } @@ -210,7 +211,7 @@ class MetricsUserService @Autowired constructor( CallBackEvent.BUILD_TASK_START -> { date.startTime = checkNotNull(event.eventTime) - if (event.stepId == null) { + if (event.stepId.isNullOrBlank()) { // stepId id 用户没填写将不会上报指标 return } @@ -228,7 +229,7 @@ class MetricsUserService @Autowired constructor( } CallBackEvent.BUILD_JOB_END -> { - if (event.jobId == null) { + if (event.jobId.isNullOrBlank()) { // job id 用户没填写将不会上报指标 return } @@ -243,7 +244,7 @@ class MetricsUserService @Autowired constructor( CallBackEvent.BUILD_TASK_END -> { date.endTime = checkNotNull(event.eventTime) - if (event.stepId == null) { + if (event.stepId.isNullOrBlank()) { // stepId id 用户没填写将不会上报指标 return } diff --git a/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/service/impl/ProjectBuildSummaryServiceImpl.kt b/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/service/impl/ProjectBuildSummaryServiceImpl.kt index 333b4324328..4dce2e7dc5c 100644 --- a/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/service/impl/ProjectBuildSummaryServiceImpl.kt +++ b/src/backend/ci/core/metrics/biz-metrics/src/main/kotlin/com/tencent/devops/metrics/service/impl/ProjectBuildSummaryServiceImpl.kt @@ -28,6 +28,7 @@ package com.tencent.devops.metrics.service.impl +import com.tencent.devops.common.event.pojo.measure.UserOperateCounterData import com.tencent.devops.common.redis.RedisLock import com.tencent.devops.common.redis.RedisOperation import com.tencent.devops.metrics.dao.ProjectBuildSummaryDao @@ -87,6 +88,7 @@ class ProjectBuildSummaryServiceImpl @Autowired constructor( val lock = RedisLock(redisOperation, projectBuildKey(projectId), 120) lock.use { lock.lock() + logger.info("save Project User:$projectId|$userId|$theDate") val projectVO = cacheProjectInfoService.getProject(projectId) if (projectVO?.enabled == false) { logger.info("Project [${projectVO.englishName}] has disabled, skip user count") @@ -111,6 +113,15 @@ class ProjectBuildSummaryServiceImpl @Autowired constructor( } } + override fun saveProjectUserOperateMetrics( + userOperateCounterData: UserOperateCounterData + ) { + projectBuildSummaryDao.saveUserOperateCount( + dslContext = dslContext, + projectUserOperateMetricsData2OperateCount = userOperateCounterData.getUserOperationCountMap() + ) + } + override fun getProjectActiveUserCount( baseQueryReq: BaseQueryReqVO ): ProjectUserCountV0? { diff --git a/src/backend/ci/core/notify/biz-notify/src/main/kotlin/com/tencent/devops/notify/blueking/sdk/pojo/ApiReq.kt b/src/backend/ci/core/notify/biz-notify/src/main/kotlin/com/tencent/devops/notify/blueking/sdk/pojo/ApiReq.kt index 318bca17fb0..6d7ebd9398e 100644 --- a/src/backend/ci/core/notify/biz-notify/src/main/kotlin/com/tencent/devops/notify/blueking/sdk/pojo/ApiReq.kt +++ b/src/backend/ci/core/notify/biz-notify/src/main/kotlin/com/tencent/devops/notify/blueking/sdk/pojo/ApiReq.kt @@ -26,8 +26,6 @@ */ package com.tencent.devops.notify.blueking.sdk.pojo -import com.tencent.devops.common.api.util.JsonUtil - @Suppress("ALL") abstract class ApiReq( open var bk_app_code: String?, @@ -35,5 +33,14 @@ abstract class ApiReq( open var bk_token: String?, open val bk_username: String? ) { - fun toMap() = mapOf("X-Bkapi-Authorization" to JsonUtil.toJson(this).replace("\\s".toRegex(), "")) + fun toMap(): Map { + return mapOf( + "X-Bkapi-Authorization" to """{ + "bk_app_code":"$bk_app_code", + "bk_app_secret":"$bk_app_secret", + "bk_username":"$bk_username", + "bk_token":"$bk_token" + }""".trimIndent().replace("\\s".toRegex(), "") + ) + } } diff --git a/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/api/app/AppPipelineBuildResource.kt b/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/api/app/AppPipelineBuildResource.kt index 5a895eb73a7..2144be5ae4e 100644 --- a/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/api/app/AppPipelineBuildResource.kt +++ b/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/api/app/AppPipelineBuildResource.kt @@ -345,9 +345,9 @@ interface AppPipelineBuildResource { @Parameter(description = "构建信息", required = false) @QueryParam("buildMsg") buildMsg: String?, - @Parameter(description = "查看指定版本调试数据", required = false, example = "false") - @QueryParam("version") - customVersion: Int? = null, + @Parameter(description = "指定调试数据", required = false) + @QueryParam("debug") + debug: Boolean? = null, @Parameter(description = "触发代码库", required = false) @QueryParam("triggerAlias") triggerAlias: List?, diff --git a/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/api/service/ServiceBuildResource.kt b/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/api/service/ServiceBuildResource.kt index 6751c48ca3f..430822ffa07 100644 --- a/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/api/service/ServiceBuildResource.kt +++ b/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/api/service/ServiceBuildResource.kt @@ -502,9 +502,9 @@ interface ServiceBuildResource { @Parameter(description = "是否查询归档数据", required = false) @QueryParam("archiveFlag") archiveFlag: Boolean? = false, - @Parameter(description = "查看指定版本调试数据", required = false, example = "false") - @QueryParam("version") - customVersion: Int? = null, + @Parameter(description = "指定调试数据", required = false) + @QueryParam("debug") + debug: Boolean? = null, @Parameter(description = "触发代码库", required = false) @QueryParam("triggerAlias") triggerAlias: List? = null, diff --git a/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/api/user/UserBuildResource.kt b/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/api/user/UserBuildResource.kt index 85a4c979ad5..fd9a3526ed8 100644 --- a/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/api/user/UserBuildResource.kt +++ b/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/api/user/UserBuildResource.kt @@ -461,9 +461,9 @@ interface UserBuildResource { @Parameter(description = "是否查询归档数据", required = false) @QueryParam("archiveFlag") archiveFlag: Boolean? = false, - @Parameter(description = "查看指定版本调试数据", required = false, example = "false") - @QueryParam("version") - customVersion: Int? = null, + @Parameter(description = "指定调试数据", required = false) + @QueryParam("debug") + debug: Boolean? = null, @Parameter(description = "触发代码库", required = false) @QueryParam("triggerAlias") triggerAlias: List?, diff --git a/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/pojo/pipeline/PipelineResourceVersion.kt b/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/pojo/pipeline/PipelineResourceVersion.kt index 14d745f56c0..6913dcf8759 100644 --- a/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/pojo/pipeline/PipelineResourceVersion.kt +++ b/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/pojo/pipeline/PipelineResourceVersion.kt @@ -27,9 +27,12 @@ package com.tencent.devops.process.pojo.pipeline +import com.tencent.devops.common.api.util.timestampmilli import com.tencent.devops.common.pipeline.Model import com.tencent.devops.common.pipeline.enums.BranchVersionAction import com.tencent.devops.common.pipeline.enums.VersionStatus +import com.tencent.devops.process.pojo.setting.PipelineVersionSimple +import com.tencent.devops.process.utils.PipelineVersionUtils import io.swagger.v3.oas.annotations.media.Schema import java.time.LocalDateTime @@ -80,4 +83,30 @@ data class PipelineResourceVersion( val debugBuildId: String? = null, @get:Schema(title = "该版本的来源版本(空时一定为主路径)", required = false) val baseVersion: Int? = null -) +) { + fun toSimple() = PipelineVersionSimple( + pipelineId = pipelineId, + creator = creator, + createTime = createTime.timestampmilli(), + updater = updater, + updateTime = updateTime?.timestampmilli(), + version = version, + versionName = versionName ?: PipelineVersionUtils.getVersionName( + versionNum = version, + pipelineVersion = versionNum ?: version, + triggerVersion = 0, + settingVersion = 0 + ) ?: "", + referFlag = referFlag, + referCount = referCount, + versionNum = versionNum, + pipelineVersion = pipelineVersion, + triggerVersion = triggerVersion, + settingVersion = settingVersion, + status = status, + debugBuildId = debugBuildId, + baseVersion = baseVersion, + description = description, + yamlVersion = yamlVersion + ) +} diff --git a/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/pojo/setting/PipelineVersionSimple.kt b/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/pojo/setting/PipelineVersionSimple.kt index e2246515612..cf7f6f57b0f 100644 --- a/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/pojo/setting/PipelineVersionSimple.kt +++ b/src/backend/ci/core/process/api-process/src/main/kotlin/com/tencent/devops/process/pojo/setting/PipelineVersionSimple.kt @@ -68,6 +68,8 @@ data class PipelineVersionSimple( val debugBuildId: String? = null, @get:Schema(title = "该版本的来源版本(空时一定为主路径)", required = false) val baseVersion: Int? = null, + @get:Schema(title = "基准版本的版本名称") + var baseVersionName: String? = null, @get:Schema(title = "当前最新正式版本标识", required = false) var latestReleasedFlag: Boolean? = false ) diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/dao/PipelineBuildDao.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/dao/PipelineBuildDao.kt index 98ca32c996d..019320cf948 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/dao/PipelineBuildDao.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/dao/PipelineBuildDao.kt @@ -316,6 +316,12 @@ class PipelineBuildDao { return normal.plus(debug) } + /** + * 查询BuildInfo,兼容所有运行时调用,不排除已删除的调试记录 + * @param dslContext: 事务上下文 + * @param projectId: 项目Id + * @param buildId: 构建Id + */ fun getBuildInfo( dslContext: DSLContext, projectId: String, @@ -332,6 +338,29 @@ class PipelineBuildDao { } } + /** + * 查询BuildInfo,返回给用户侧的数据,需要排除已删除的调试记录 + * @param dslContext: 事务上下文 + * @param projectId: 项目Id + * @param buildId: 构建Id + */ + fun getUserBuildInfo( + dslContext: DSLContext, + projectId: String, + buildId: String + ): BuildInfo? { + return with(T_PIPELINE_BUILD_HISTORY) { + dslContext.selectFrom(this) + .where(PROJECT_ID.eq(projectId).and(BUILD_ID.eq(buildId))) + .fetchAny(mapper) + } ?: with(T_PIPELINE_BUILD_HISTORY_DEBUG) { + dslContext.selectFrom(this) + .where(PROJECT_ID.eq(projectId).and(BUILD_ID.eq(buildId))) + .and(DELETE_TIME.isNull) + .fetchAny(debugMapper) + } + } + fun getStartUser(dslContext: DSLContext, projectId: String, buildId: String): String? { return with(T_PIPELINE_BUILD_HISTORY) { dslContext.select(START_USER) @@ -376,7 +405,7 @@ class PipelineBuildDao { with(T_PIPELINE_BUILD_HISTORY_DEBUG) { val conditions = mutableListOf() conditions.add(BUILD_ID.`in`(buildIds)) - // 增加过滤,对前端屏蔽已删除的构建 + // 增加过滤,对前端屏蔽已删除的构建 conditions.add(DELETE_TIME.isNull) if (projectId != null) { conditions.add(PROJECT_ID.eq(projectId)) @@ -956,12 +985,12 @@ class PipelineBuildDao { buildNoEnd: Int?, buildMsg: String?, startUser: List?, - debugVersion: Int?, + debug: Boolean?, triggerAlias: List?, triggerBranch: List?, triggerUser: List? ): Int { - return if (debugVersion == null) { + return if (debug != true) { with(T_PIPELINE_BUILD_HISTORY) { val where = dslContext.selectCount() .from(this).where(PROJECT_ID.eq(projectId)).and(PIPELINE_ID.eq(pipelineId)) @@ -998,7 +1027,6 @@ class PipelineBuildDao { val where = dslContext.selectCount() .from(this) .where(PROJECT_ID.eq(projectId)).and(PIPELINE_ID.eq(pipelineId)) - .and(VERSION.eq(debugVersion)) makeDebugCondition( where = where, materialAlias = materialAlias, @@ -1057,12 +1085,12 @@ class PipelineBuildDao { buildMsg: String?, startUser: List?, updateTimeDesc: Boolean? = null, - debugVersion: Int?, + debug: Boolean?, triggerAlias: List?, triggerBranch: List?, triggerUser: List? ): Collection { - return if (debugVersion == null) { + return if (debug != true) { with(T_PIPELINE_BUILD_HISTORY) { val where = dslContext.selectFrom(this).where(PROJECT_ID.eq(projectId)).and(PIPELINE_ID.eq(pipelineId)) makeCondition( @@ -1104,7 +1132,6 @@ class PipelineBuildDao { with(T_PIPELINE_BUILD_HISTORY_DEBUG) { val where = dslContext.selectFrom(this) .where(PROJECT_ID.eq(projectId)).and(PIPELINE_ID.eq(pipelineId)) - .and(VERSION.eq(debugVersion)) makeDebugCondition( where = where, materialAlias = materialAlias, diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/dao/PipelineResourceVersionDao.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/dao/PipelineResourceVersionDao.kt index e5c54b9773a..a292b785860 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/dao/PipelineResourceVersionDao.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/dao/PipelineResourceVersionDao.kt @@ -77,8 +77,9 @@ class PipelineResourceVersionDao { val modelStr = JsonUtil.toJson(model, formatted = false) val createTime = LocalDateTime.now() val releaseTime = createTime.takeIf { - // 发布时间根据版本转为RELEASED状态为准,默认也是发布 - versionStatus == VersionStatus.RELEASED || versionStatus == null + // 发布时间根据版本转为RELEASED状态为准,默认和新增分支版本也记录为发布时间 + versionStatus == VersionStatus.RELEASED || + versionStatus == VersionStatus.BRANCH || versionStatus == null } return dslContext.insertInto(this) .set(PROJECT_ID, projectId) @@ -138,7 +139,10 @@ class PipelineResourceVersionDao { } else { // 非新的逻辑请求则保持旧逻辑 if (includeDraft != true) where.and( - STATUS.ne(VersionStatus.COMMITTING.name) + ( + STATUS.ne(VersionStatus.COMMITTING.name) + .and(STATUS.ne(VersionStatus.DELETE.name)) + ) .or(STATUS.isNull) ) where.orderBy(VERSION.desc()).limit(1) @@ -162,7 +166,10 @@ class PipelineResourceVersionDao { } else { // 非新的逻辑请求则保持旧逻辑 if (includeDraft != true) query.and( - STATUS.ne(VersionStatus.COMMITTING.name) + ( + STATUS.ne(VersionStatus.COMMITTING.name) + .and(STATUS.ne(VersionStatus.DELETE.name)) + ) .or(STATUS.isNull) ) query.orderBy(VERSION.desc()).limit(1) @@ -217,6 +224,7 @@ class PipelineResourceVersionDao { pipelineId: String ): PipelineResourceVersion? { with(T_PIPELINE_RESOURCE_VERSION) { + // 这里只需要返回当前VERSION数字最大的记录,不需要关心版本状态 return dslContext.selectFrom(this) .where(PIPELINE_ID.eq(pipelineId).and(PROJECT_ID.eq(projectId))) .orderBy(VERSION.desc()).limit(1) diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineBuildDetailService.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineBuildDetailService.kt index cbf8181b2e9..473594ef282 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineBuildDetailService.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineBuildDetailService.kt @@ -105,7 +105,7 @@ class PipelineBuildDetailService @Autowired constructor( val record = buildDetailDao.get(dslContext, projectId, buildId) ?: return null - val buildInfo = pipelineBuildDao.getBuildInfo( + val buildInfo = pipelineBuildDao.getUserBuildInfo( dslContext = dslContext, projectId = projectId, buildId = buildId @@ -367,12 +367,4 @@ class PipelineBuildDetailService @Autowired constructor( cancelUser = cancelUserId ) } - - fun getBuildDetailPipelineId(projectId: String, buildId: String): String? { - return pipelineBuildDao.getBuildInfo( - dslContext = dslContext, - projectId = projectId, - buildId = buildId - )?.pipelineId - } } diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineProgressRateService.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineProgressRateService.kt index 5d9c2a33528..b09cc842efa 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineProgressRateService.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineProgressRateService.kt @@ -18,7 +18,6 @@ import javax.ws.rs.core.Response @Suppress("LongParameterList") class PipelineProgressRateService constructor( private val taskBuildRecordService: TaskBuildRecordService, - private val pipelineBuildDetailService: PipelineBuildDetailService, private val pipelineTaskService: PipelineTaskService, private val pipelineRuntimeService: PipelineRuntimeService, private val buildRecordService: ContainerBuildRecordService, @@ -34,7 +33,7 @@ class PipelineProgressRateService constructor( logger.info("report progress rate:$projectId|$buildId|$executeCount|$jobHeartbeatRequest") val task2ProgressRate = jobHeartbeatRequest?.task2ProgressRate ?: return if (task2ProgressRate.isEmpty()) return - val pipelineId = pipelineBuildDetailService.getBuildDetailPipelineId(projectId, buildId) ?: return + val pipelineId = pipelineRuntimeService.getBuildInfo(projectId, buildId)?.pipelineId ?: return task2ProgressRate.forEach { (taskId, progressRate) -> taskBuildRecordService.updateTaskRecord( projectId = projectId, diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineRepositoryVersionService.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineRepositoryVersionService.kt index 5c0c570afab..3a5d4b276a5 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineRepositoryVersionService.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineRepositoryVersionService.kt @@ -54,7 +54,7 @@ import org.springframework.stereotype.Service import java.util.concurrent.Executors @Service -@Suppress("LongParameterList", "ReturnCount") +@Suppress("LongParameterList", "ReturnCount", "ComplexMethod") class PipelineRepositoryVersionService( private val dslContext: DSLContext, private val pipelineResourceDao: PipelineResourceDao, @@ -205,7 +205,7 @@ class PipelineRepositoryVersionService( if (pipelineInfo == null) { return Pair(0, mutableListOf()) } - + // 计算包括草稿在内的总数 var count = pipelineResourceVersionDao.count( dslContext = dslContext, projectId = projectId, @@ -215,24 +215,39 @@ class PipelineRepositoryVersionService( creator = creator, description = description ) + // 草稿单独提出来放在第一页,其他版本后插入结果 val result = mutableListOf() - result.addAll( - pipelineResourceVersionDao.listPipelineVersion( + if (includeDraft != false && offset == 0) { + pipelineResourceVersionDao.getDraftVersionResource( dslContext = dslContext, projectId = projectId, - pipelineId = pipelineId, - pipelineInfo = pipelineInfo, - creator = creator, - description = description, - versionName = versionName, - includeDraft = includeDraft, - excludeVersion = excludeVersion, - offset = offset, - limit = limit - ) + pipelineId = pipelineId + )?.toSimple()?.apply { + baseVersionName = baseVersion?.let { + pipelineResourceVersionDao.getPipelineVersionSimple( + dslContext, projectId, pipelineId, it + )?.versionName + } + }?.let { result.add(it) } + } + val others = pipelineResourceVersionDao.listPipelineVersion( + dslContext = dslContext, + projectId = projectId, + pipelineId = pipelineId, + pipelineInfo = pipelineInfo, + creator = creator, + description = description, + versionName = versionName, + includeDraft = false, + excludeVersion = excludeVersion, + offset = offset, + limit = limit ) + result.addAll(others) + // #8161 当过滤草稿时查到空结果是正常的,只在不过滤草稿时兼容老数据的版本表无记录 - if (result.isEmpty() && pipelineInfo.latestVersionStatus?.isNotReleased() != true) { + val noSearch = versionName.isNullOrBlank() && creator.isNullOrBlank() && description.isNullOrBlank() + if (result.isEmpty() && pipelineInfo.latestVersionStatus?.isNotReleased() != true && noSearch) { pipelineResourceDao.getReleaseVersionResource( dslContext, projectId, pipelineId )?.let { record -> diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineRuntimeService.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineRuntimeService.kt index e67becaab7d..50f62b8e885 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineRuntimeService.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/PipelineRuntimeService.kt @@ -372,7 +372,7 @@ class PipelineRuntimeService @Autowired constructor( startUser: List?, updateTimeDesc: Boolean? = null, queryDslContext: DSLContext? = null, - debugVersion: Int?, + debug: Boolean?, triggerAlias: List?, triggerBranch: List?, triggerUser: List? @@ -408,7 +408,7 @@ class PipelineRuntimeService @Autowired constructor( buildMsg = buildMsg, startUser = startUser, updateTimeDesc = updateTimeDesc, - debugVersion = debugVersion, + debug = debug, triggerAlias = triggerAlias, triggerBranch = triggerBranch, triggerUser = triggerUser @@ -1885,7 +1885,7 @@ class PipelineRuntimeService @Autowired constructor( buildMsg: String? = null, startUser: List? = null, queryDslContext: DSLContext? = null, - debugVersion: Int? = null, + debug: Boolean?, triggerAlias: List?, triggerBranch: List?, triggerUser: List? @@ -1914,7 +1914,7 @@ class PipelineRuntimeService @Autowired constructor( buildNoEnd = buildNoEnd, buildMsg = buildMsg, startUser = startUser, - debugVersion = debugVersion, + debug = debug, triggerAlias = triggerAlias, triggerBranch = triggerBranch, triggerUser = triggerUser diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/detail/BaseBuildDetailService.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/detail/BaseBuildDetailService.kt index df7fcf208e0..c9dc42c7ff7 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/detail/BaseBuildDetailService.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/detail/BaseBuildDetailService.kt @@ -245,7 +245,7 @@ open class BaseBuildDetailService constructor( } private fun pipelineDetailChangeEvent(projectId: String, buildId: String) { - val pipelineBuildInfo = pipelineBuildDao.getBuildInfo(dslContext, projectId, buildId) + val pipelineBuildInfo = pipelineBuildDao.getUserBuildInfo(dslContext, projectId, buildId) if (pipelineBuildInfo?.channelCode == ChannelCode.GIT) pipelineEventDispatcher.dispatch( // 异步转发,解耦核心 // TODO stream内部和开源前端未更新前,保持推送 diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/record/BaseBuildRecordService.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/record/BaseBuildRecordService.kt index 4ea5f9fd5a5..9b80b14bfd6 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/record/BaseBuildRecordService.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/engine/service/record/BaseBuildRecordService.kt @@ -242,7 +242,7 @@ open class BaseBuildRecordService( executeCount: Int ) { val userId = startUser - ?: pipelineBuildDao.getBuildInfo(dslContext, projectId, buildId)?.startUser + ?: pipelineBuildDao.getUserBuildInfo(dslContext, projectId, buildId)?.startUser ?: return pipelineEventDispatcher.dispatch( PipelineBuildWebSocketPushEvent( diff --git a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/service/pipeline/PipelineStatusService.kt b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/service/pipeline/PipelineStatusService.kt index 144d49120c5..08727c8f457 100644 --- a/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/service/pipeline/PipelineStatusService.kt +++ b/src/backend/ci/core/process/biz-base/src/main/kotlin/com/tencent/devops/process/service/pipeline/PipelineStatusService.kt @@ -75,7 +75,11 @@ class PipelineStatusService( buildTaskCountList.filter { it.value2() == BuildStatus.SUCCEED.ordinal }.sumOf { it.value3() } // 获取触发方式 - val buildInfo = pipelineBuildDao.getBuildInfo(dslContext, projectId, pipelineBuildSummary.latestBuildId) + val buildInfo = if (pipelineBuildSummary.latestBuildId.isNullOrBlank()) { + null + } else { + pipelineBuildDao.getBuildInfo(dslContext, projectId, pipelineBuildSummary.latestBuildId) + } // todo还没想好与Pipeline结合,减少这部分的代码,收归一处 return PipelineStatus( diff --git a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/api/ServiceBuildResourceImpl.kt b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/api/ServiceBuildResourceImpl.kt index 048277d3c73..83c96b73c46 100644 --- a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/api/ServiceBuildResourceImpl.kt +++ b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/api/ServiceBuildResourceImpl.kt @@ -42,7 +42,6 @@ import com.tencent.devops.common.pipeline.pojo.BuildFormValue import com.tencent.devops.common.pipeline.pojo.StageReviewRequest import com.tencent.devops.common.web.RestResource import com.tencent.devops.process.api.service.ServiceBuildResource -import com.tencent.devops.process.engine.service.PipelineBuildDetailService import com.tencent.devops.process.engine.service.PipelineRuntimeService import com.tencent.devops.process.engine.service.vmbuild.EngineVMBuildService import com.tencent.devops.process.pojo.BuildBasicInfo @@ -70,7 +69,6 @@ class ServiceBuildResourceImpl @Autowired constructor( private val pipelineBuildMaintainFacadeService: PipelineBuildMaintainFacadeService, private val pipelineBuildFacadeService: PipelineBuildFacadeService, private val engineVMBuildService: EngineVMBuildService, - private val pipelineBuildDetailService: PipelineBuildDetailService, private val pipelinePauseBuildFacadeService: PipelinePauseBuildFacadeService, private val pipelineRuntimeService: PipelineRuntimeService ) : ServiceBuildResource { @@ -79,7 +77,7 @@ class ServiceBuildResourceImpl @Autowired constructor( throw ParamBlankException("Invalid buildId, it must not empty.") } return Result( - pipelineBuildDetailService.getBuildDetailPipelineId(projectId, buildId) + pipelineRuntimeService.getBuildInfo(projectId, buildId)?.pipelineId ?: throw ParamBlankException("Invalid buildId, please check if projectId & buildId are related") ) } @@ -385,7 +383,7 @@ class ServiceBuildResourceImpl @Autowired constructor( buildMsg: String?, startUser: List?, archiveFlag: Boolean?, - customVersion: Int?, + debug: Boolean?, triggerAlias: List?, triggerBranch: List?, triggerUser: List? @@ -422,7 +420,7 @@ class ServiceBuildResourceImpl @Autowired constructor( startUser = startUser?.filter { it.isNotBlank() }, updateTimeDesc = updateTimeDesc, archiveFlag = archiveFlag, - customVersion = customVersion, + debug = debug, triggerAlias = triggerAlias, triggerBranch = triggerBranch, triggerUser = triggerUser diff --git a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/api/UserBuildResourceImpl.kt b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/api/UserBuildResourceImpl.kt index 9dc46ce5841..a269d560e49 100644 --- a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/api/UserBuildResourceImpl.kt +++ b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/api/UserBuildResourceImpl.kt @@ -451,7 +451,7 @@ class UserBuildResourceImpl @Autowired constructor( buildNoEnd: Int?, buildMsg: String?, archiveFlag: Boolean?, - customVersion: Int?, + debug: Boolean?, triggerAlias: List?, triggerBranch: List?, triggerUser: List? @@ -483,7 +483,7 @@ class UserBuildResourceImpl @Autowired constructor( buildNoEnd = buildNoEnd, buildMsg = buildMsg, archiveFlag = archiveFlag, - customVersion = customVersion, + debug = debug, triggerAlias = triggerAlias, triggerBranch = triggerBranch, triggerUser = triggerUser diff --git a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/api/app/AppPipelineBuildResourceImpl.kt b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/api/app/AppPipelineBuildResourceImpl.kt index eb870ce755c..304b945363c 100644 --- a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/api/app/AppPipelineBuildResourceImpl.kt +++ b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/api/app/AppPipelineBuildResourceImpl.kt @@ -198,7 +198,7 @@ class AppPipelineBuildResourceImpl @Autowired constructor( buildNoStart: Int?, buildNoEnd: Int?, buildMsg: String?, - customVersion: Int?, + debug: Boolean?, triggerAlias: List?, triggerBranch: List?, triggerUser: List? @@ -229,7 +229,7 @@ class AppPipelineBuildResourceImpl @Autowired constructor( buildNoStart = buildNoStart, buildNoEnd = buildNoEnd, buildMsg = buildMsg, - customVersion = customVersion, + debug = debug, triggerAlias = triggerAlias, triggerBranch = triggerBranch, triggerUser = triggerUser diff --git a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/builds/PipelineBuildFacadeService.kt b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/builds/PipelineBuildFacadeService.kt index c6ebca11aee..688205ba732 100644 --- a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/builds/PipelineBuildFacadeService.kt +++ b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/builds/PipelineBuildFacadeService.kt @@ -1978,7 +1978,7 @@ class PipelineBuildFacadeService( startUser: List? = null, updateTimeDesc: Boolean? = null, archiveFlag: Boolean? = false, - customVersion: Int?, + debug: Boolean?, triggerAlias: List?, triggerBranch: List?, triggerUser: List? @@ -2016,26 +2016,6 @@ class PipelineBuildFacadeService( permission = AuthPermission.VIEW ) } - // 如果请求的参数是草稿版本的版本号,则返回调试记录,如果是当前正式版本则返回正式记录 - // 否则按版本查询返回空数据 - val customResource = pipelineRepositoryService.getPipelineResourceVersion( - projectId = projectId, pipelineId = pipelineId, - version = customVersion, includeDraft = true - ) - val targetDebugVersion = if (customResource?.status == VersionStatus.COMMITTING) { - customVersion - } else if (customResource?.version == pipelineInfo.version) { - null - } else { - return BuildHistoryPage( - page = pageNotNull, - pageSize = limit, - count = 0, - records = emptyList(), - hasDownloadPermission = false, - pipelineVersion = pipelineInfo.version - ) - } val newTotalCount = pipelineRuntimeService.getPipelineBuildHistoryCount( projectId = projectId, @@ -2061,7 +2041,7 @@ class PipelineBuildFacadeService( buildMsg = buildMsg, startUser = startUser, queryDslContext = queryDslContext, - debugVersion = targetDebugVersion, + debug = debug, triggerAlias = triggerAlias, triggerBranch = triggerBranch, triggerUser = triggerUser @@ -2094,7 +2074,7 @@ class PipelineBuildFacadeService( startUser = startUser, updateTimeDesc = updateTimeDesc, queryDslContext = queryDslContext, - debugVersion = targetDebugVersion, + debug = debug, triggerAlias = triggerAlias, triggerBranch = triggerBranch, triggerUser = triggerUser diff --git a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/webhook/PipelineBuildWebhookService.kt b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/webhook/PipelineBuildWebhookService.kt index 1fcbcbccc02..318cdb56bc6 100644 --- a/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/webhook/PipelineBuildWebhookService.kt +++ b/src/backend/ci/core/process/biz-process/src/main/kotlin/com/tencent/devops/process/service/webhook/PipelineBuildWebhookService.kt @@ -33,6 +33,9 @@ import com.tencent.devops.common.api.util.JsonUtil import com.tencent.devops.common.client.Client import com.tencent.devops.common.event.dispatcher.pipeline.mq.MeasureEventDispatcher import com.tencent.devops.common.event.pojo.measure.ProjectUserDailyEvent +import com.tencent.devops.common.event.pojo.measure.ProjectUserOperateMetricsData +import com.tencent.devops.common.event.pojo.measure.ProjectUserOperateMetricsEvent +import com.tencent.devops.common.event.pojo.measure.UserOperateCounterData import com.tencent.devops.common.log.pojo.message.LogMessage import com.tencent.devops.common.log.utils.BuildLogPrinter import com.tencent.devops.common.pipeline.container.TriggerContainer @@ -99,6 +102,7 @@ class PipelineBuildWebhookService @Autowired constructor( ) { companion object { private val logger = LoggerFactory.getLogger(PipelineBuildWebhookService::class.java) + private const val WEBHOOK_COMMIT_TRIGGER = "webhook_commit_trigger" } fun dispatchTriggerPipelines( @@ -218,7 +222,8 @@ class PipelineBuildWebhookService @Autowired constructor( // 触发事件保存流水线名称 builder.pipelineName(pipelineInfo.pipelineName) // 获取授权人 - val userId = pipelineRepositoryService.getPipelineOauthUser(projectId, pipelineId) ?: pipelineInfo.lastModifyUser + val userId = pipelineRepositoryService.getPipelineOauthUser(projectId, pipelineId) + ?: pipelineInfo.lastModifyUser val variables = mutableMapOf() val container = model.stages[0].containers[0] as TriggerContainer // 解析变量 @@ -448,7 +453,7 @@ class PipelineBuildWebhookService @Autowired constructor( .webhookCommitNew(projectId, webhookCommit).data logger.info( "$pipelineId|${buildId?.id}|webhook trigger|(${triggerElement.name}|" + - "repo(${matcher.getRepoName()})" + "repo(${matcher.getRepoName()})" ) return WebhookBuildResult(result = true, pipelineInfo = pipelineInfo, buildId = buildId) } catch (ignore: Exception) { @@ -576,14 +581,13 @@ class PipelineBuildWebhookService @Autowired constructor( buildId = buildId.id, buildParameters = pipelineParamMap.values.toList() ) + // 上报项目用户度量 if (startParams[PIPELINE_START_WEBHOOK_USER_ID] != null) { - measureEventDispatcher.dispatch( - ProjectUserDailyEvent( - projectId = projectId, - userId = startParams[PIPELINE_START_WEBHOOK_USER_ID]!!.toString(), - theDate = LocalDate.now() - ) + uploadProjectUserMetrics( + userId = startParams[PIPELINE_START_WEBHOOK_USER_ID]!!.toString(), + projectId = projectId, + theDate = LocalDate.now() ) } } @@ -599,4 +603,33 @@ class PipelineBuildWebhookService @Autowired constructor( private fun checkPermission(userId: String, projectId: String, pipelineId: String) { pipelineBuildPermissionService.checkPermission(userId = userId, projectId = projectId, pipelineId = pipelineId) } + + private fun uploadProjectUserMetrics( + userId: String, + projectId: String, + theDate: LocalDate + ) { + try { + val projectUserOperateMetricsKey = ProjectUserOperateMetricsData( + projectId = projectId, + userId = userId, + operate = WEBHOOK_COMMIT_TRIGGER, + theDate = theDate + ).getProjectUserOperateMetricsKey() + measureEventDispatcher.dispatch( + ProjectUserDailyEvent( + projectId = projectId, + userId = userId, + theDate = theDate + ), + ProjectUserOperateMetricsEvent( + userOperateCounterData = UserOperateCounterData().apply { + this.increment(projectUserOperateMetricsKey) + } + ) + ) + } catch (ignored: Exception) { + logger.error("save auth user metrics", ignored) + } + } } diff --git a/src/frontend/bk-pipeline/dist/bk-pipeline.min.js b/src/frontend/bk-pipeline/dist/bk-pipeline.min.js index 4e4738e5273..434c4696b54 100644 --- a/src/frontend/bk-pipeline/dist/bk-pipeline.min.js +++ b/src/frontend/bk-pipeline/dist/bk-pipeline.min.js @@ -1,2 +1,2 @@ /*! For license information please see bk-pipeline.min.js.LICENSE.txt */ -!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("vue"),require("bk-magic-vue")):"function"==typeof define&&define.amd?define(["vue","bk-magic-vue"],t):"object"==typeof exports?exports.bkPipeline=t(require("vue"),require("bk-magic-vue")):e.bkPipeline=t(e.Vue,e.bkMagic)}(self,((e,t)=>(()=>{var i={125:()=>{!function(){const e='';document.body?document.body.insertAdjacentHTML("afterbegin",e):document.addEventListener("DOMContentLoaded",(function(){document.body.insertAdjacentHTML("afterbegin",e)}))}()},2:(e,t,i)=>{"use strict";i.d(t,{A:()=>s});var n=i(601),o=i.n(n),a=i(314),r=i.n(a)()(o());r.push([e.id,'/*!\n * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available.\n *\n * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.\n *\n * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license.\n *\n * A copy of the MIT License is included in this file.\n *\n *\n * Terms of the MIT License:\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n *//*!\n * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available.\n *\n * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.\n *\n * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license.\n *\n * A copy of the MIT License is included in this file.\n *\n *\n * Terms of the MIT License:\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */@keyframes rotating{from{transform:rotate(0)}to{transform:rotate(359deg)}}.connect-line{position:absolute;width:58px;top:19px;stroke:#3c96ff;stroke-width:1;fill:none}.insert-tip{position:absolute;display:block;padding:0 10px;max-width:100px;height:24px;display:flex;align-items:center;border:1px solid #addaff;border-radius:22px;color:#3c96ff;font-size:10px;cursor:pointer;background-color:#fff;box-shadow:0px 2px 4px 0px rgba(60,150,255,.2)}.insert-tip.direction:after{content:"";position:absolute;height:6px;width:6px;background-color:#fff;transform:rotate(45deg);bottom:-4px;left:20px;border-right:1px solid #addaff;border-bottom:1px solid #addaff}.insert-tip .tip-icon{margin:0 5px 0 0;cursor:pointer;position:relative;display:block;width:8px;height:8px;transition:all .3s ease}.insert-tip .tip-icon:before,.insert-tip .tip-icon:after{content:"";position:absolute;left:3px;top:0px;height:8px;width:2px;background-color:#3c96ff}.insert-tip .tip-icon:after{transform:rotate(90deg)}.insert-tip>span{white-space:nowrap}.insert-tip:hover{background-color:#3c96ff;color:#fff;border-color:#3c96ff}.insert-tip:hover.direction:after{background-color:#3c96ff;border-right-color:#3c96ff;border-bottom-color:#3c96ff}.insert-tip:hover .tip-icon{position:relative;display:block;width:8px;height:8px;transition:all .3s ease}.insert-tip:hover .tip-icon:before,.insert-tip:hover .tip-icon:after{content:"";position:absolute;left:3px;top:0px;height:8px;width:2px;background-color:#fff}.insert-tip:hover .tip-icon:after{transform:rotate(90deg)}.pointer{cursor:pointer}span.skip-name{text-decoration:line-through;color:#c4cdd6}span.skip-name:hover{color:#c4cdd6}.spin-icon{display:inline-block;animation:loading .8s linear infinite}.add-plus-icon,.minus-icon{position:relative;display:block;width:18px;height:18px;border:1px solid #addaff;background-color:#fff;border-radius:50%;transition:all .3s ease}.add-plus-icon:before,.minus-icon:before,.add-plus-icon:after,.minus-icon:after{content:"";position:absolute;left:8px;top:5px;left:7px;top:4px;height:8px;width:2px;background-color:#3c96ff}.add-plus-icon:after,.minus-icon:after{transform:rotate(90deg)}.add-plus-icon.active,.active.minus-icon{border-color:#3c96ff;background-color:#3c96ff}.add-plus-icon.active:before,.active.minus-icon:before,.add-plus-icon.active:after,.active.minus-icon:after{background-color:#fff}.add-plus-icon:hover,.minus-icon:hover{border-color:#3c96ff;background-color:#3c96ff}.add-plus-icon:hover:before,.minus-icon:hover:before,.add-plus-icon:hover:after,.minus-icon:hover:after{background-color:#fff}.minus-icon:before{display:none}.un-exec-this-time{opacity:.5}.un-exec-this-time .un-exec-this-time{opacity:1}.readonly .stage-connector{background:#c3cdd7;color:#c3cdd7}.readonly .stage-connector:before{background:#c3cdd7}.readonly .connect-line.left,.readonly .connect-line.right{stroke:#c3cdd7}.readonly .connect-line.left:before,.readonly .connect-line.right:before{stroke:#c3cdd7}.readonly .connect-line.left:after,.readonly .connect-line.right:after{stroke:#c3cdd7;background-color:#c3cdd7}.readonly:after{background:#c3cdd7}.readonly .container-connect-triangle{color:#c3cdd7}.readonly .container-title{cursor:pointer;background-color:#63656e}.readonly .container-title:before,.readonly .container-title:after{border-top-color:#c3cdd7}.readonly .container-title>.container-name span{color:#fff}.editing .stage-connector{background:#3c96ff;color:#3c96ff}.editing .stage-connector:before{background:#3c96ff}.editing .connect-line.left,.editing .connect-line.right{stroke:#3c96ff}.editing .connect-line.left:before,.editing .connect-line.right:before{stroke:#3c96ff}.editing .connect-line.left:after,.editing .connect-line.right:after{stroke:#3c96ff;background-color:#3c96ff}.editing:after{background:#3c96ff}.editing:before{color:#3c96ff}.container-type{font-size:12px;margin-right:12px;font-style:normal}.container-type .devops-icon{font-size:18px}.container-type .devops-icon.icon-exclamation-triangle-shape{font-size:14px}.container-type .devops-icon.icon-exclamation-triangle-shape.is-danger{color:#ff5656}.container-type i{font-style:normal}.bk-pipeline .stage-status:not(.readonly){background-color:#3c96ff}.bk-pipeline .stage-status:not(.readonly).WARNING{background-color:#ffb400;color:#fff}.bk-pipeline .stage-status:not(.readonly).FAILED{background-color:#ff5656;color:#fff}.bk-pipeline .stage-status:not(.readonly).SUCCEED{background-color:#5ac882;color:#fff}.bk-pipeline .stage-status.CANCELED,.bk-pipeline .stage-status.REVIEW_ABORT,.bk-pipeline .stage-status.REVIEWING,.bk-pipeline .stage-name-status-icon.CANCELED,.bk-pipeline .stage-name-status-icon.REVIEW_ABORT,.bk-pipeline .stage-name-status-icon.REVIEWING{color:#ffb400}.bk-pipeline .stage-status.FAILED,.bk-pipeline .stage-status.QUALITY_CHECK_FAIL,.bk-pipeline .stage-status.HEARTBEAT_TIMEOUT,.bk-pipeline .stage-status.QUEUE_TIMEOUT,.bk-pipeline .stage-status.EXEC_TIMEOUT,.bk-pipeline .stage-name-status-icon.FAILED,.bk-pipeline .stage-name-status-icon.QUALITY_CHECK_FAIL,.bk-pipeline .stage-name-status-icon.HEARTBEAT_TIMEOUT,.bk-pipeline .stage-name-status-icon.QUEUE_TIMEOUT,.bk-pipeline .stage-name-status-icon.EXEC_TIMEOUT{color:#ff5656}.bk-pipeline .stage-status.SUCCEED,.bk-pipeline .stage-status.REVIEW_PROCESSED,.bk-pipeline .stage-name-status-icon.SUCCEED,.bk-pipeline .stage-name-status-icon.REVIEW_PROCESSED{color:#5ac882}.bk-pipeline .stage-status.PAUSE,.bk-pipeline .stage-name-status-icon.PAUSE{color:#63656e}.bk-pipeline .container-title .stage-status{color:#fff}.bk-pipeline .container-title.UNEXEC,.bk-pipeline .container-title.SKIP,.bk-pipeline .container-title.DISABLED{color:#fff;background-color:#63656e}.bk-pipeline .container-title.UNEXEC .fold-atom-icon,.bk-pipeline .container-title.SKIP .fold-atom-icon,.bk-pipeline .container-title.DISABLED .fold-atom-icon{color:#63656e}.bk-pipeline .container-title.QUEUE,.bk-pipeline .container-title.RUNNING,.bk-pipeline .container-title.REVIEWING,.bk-pipeline .container-title.PREPARE_ENV,.bk-pipeline .container-title.LOOP_WAITING,.bk-pipeline .container-title.DEPENDENT_WAITING,.bk-pipeline .container-title.CALL_WAITING{background-color:#459fff;color:#fff}.bk-pipeline .container-title.QUEUE .fold-atom-icon,.bk-pipeline .container-title.RUNNING .fold-atom-icon,.bk-pipeline .container-title.REVIEWING .fold-atom-icon,.bk-pipeline .container-title.PREPARE_ENV .fold-atom-icon,.bk-pipeline .container-title.LOOP_WAITING .fold-atom-icon,.bk-pipeline .container-title.DEPENDENT_WAITING .fold-atom-icon,.bk-pipeline .container-title.CALL_WAITING .fold-atom-icon{color:#459fff}.bk-pipeline .container-title.CANCELED,.bk-pipeline .container-title.REVIEW_ABORT,.bk-pipeline .container-title.TRY_FINALLY,.bk-pipeline .container-title.QUEUE_CACHE{background-color:#f6b026}.bk-pipeline .container-title.CANCELED span.skip-name,.bk-pipeline .container-title.REVIEW_ABORT span.skip-name,.bk-pipeline .container-title.TRY_FINALLY span.skip-name,.bk-pipeline .container-title.QUEUE_CACHE span.skip-name{color:#fff}.bk-pipeline .container-title.CANCELED .fold-atom-icon,.bk-pipeline .container-title.REVIEW_ABORT .fold-atom-icon,.bk-pipeline .container-title.TRY_FINALLY .fold-atom-icon,.bk-pipeline .container-title.QUEUE_CACHE .fold-atom-icon{color:#f6b026}.bk-pipeline .container-title.FAILED,.bk-pipeline .container-title.TERMINATE,.bk-pipeline .container-title.HEARTBEAT_TIMEOUT,.bk-pipeline .container-title.QUALITY_CHECK_FAIL,.bk-pipeline .container-title.QUEUE_TIMEOUT,.bk-pipeline .container-title.EXEC_TIMEOUT{color:#fff;background-color:#ff5656}.bk-pipeline .container-title.FAILED .fold-atom-icon,.bk-pipeline .container-title.TERMINATE .fold-atom-icon,.bk-pipeline .container-title.HEARTBEAT_TIMEOUT .fold-atom-icon,.bk-pipeline .container-title.QUALITY_CHECK_FAIL .fold-atom-icon,.bk-pipeline .container-title.QUEUE_TIMEOUT .fold-atom-icon,.bk-pipeline .container-title.EXEC_TIMEOUT .fold-atom-icon{color:#ff5656}.bk-pipeline .container-title.SUCCEED,.bk-pipeline .container-title.REVIEW_PROCESSED,.bk-pipeline .container-title.STAGE_SUCCESS{color:#fff;background-color:#5ac882}.bk-pipeline .container-title.SUCCEED .fold-atom-icon,.bk-pipeline .container-title.REVIEW_PROCESSED .fold-atom-icon,.bk-pipeline .container-title.STAGE_SUCCESS .fold-atom-icon{color:#5ac882}.bk-pipeline .container-title.PAUSE{color:#fff;background-color:#ff9801}.bk-pipeline .container-title.PAUSE .fold-atom-icon{color:#ff9801}.bk-pipeline .bk-pipeline-atom.is-sub-pipeline-atom{box-shadow:4px 4px 0 0 rgba(99,101,110,.3)}.bk-pipeline .bk-pipeline-atom.UNEXEC,.bk-pipeline .bk-pipeline-atom.SKIP,.bk-pipeline .bk-pipeline-atom.DISABLED{color:#63656e}.bk-pipeline .bk-pipeline-atom.UNEXEC.is-sub-pipeline-atom,.bk-pipeline .bk-pipeline-atom.SKIP.is-sub-pipeline-atom,.bk-pipeline .bk-pipeline-atom.DISABLED.is-sub-pipeline-atom{box-shadow:4px 4px 0 0 rgba(99,101,110,.3)}.bk-pipeline .bk-pipeline-atom.CANCELED,.bk-pipeline .bk-pipeline-atom.REVIEW_ABORT,.bk-pipeline .bk-pipeline-atom.REVIEWING{border-color:#ffb400}.bk-pipeline .bk-pipeline-atom.CANCELED.is-sub-pipeline-atom,.bk-pipeline .bk-pipeline-atom.REVIEW_ABORT.is-sub-pipeline-atom,.bk-pipeline .bk-pipeline-atom.REVIEWING.is-sub-pipeline-atom{box-shadow:4px 4px 0 0 rgba(255,180,0,.3)}.bk-pipeline .bk-pipeline-atom.CANCELED:before,.bk-pipeline .bk-pipeline-atom.REVIEW_ABORT:before,.bk-pipeline .bk-pipeline-atom.REVIEWING:before{background-color:#ffb400}.bk-pipeline .bk-pipeline-atom.CANCELED:after,.bk-pipeline .bk-pipeline-atom.REVIEW_ABORT:after,.bk-pipeline .bk-pipeline-atom.REVIEWING:after{border-color:#ffb400}.bk-pipeline .bk-pipeline-atom.CANCELED .atom-icon,.bk-pipeline .bk-pipeline-atom.CANCELED .atom-execute-time,.bk-pipeline .bk-pipeline-atom.CANCELED .stage-check-icon,.bk-pipeline .bk-pipeline-atom.REVIEW_ABORT .atom-icon,.bk-pipeline .bk-pipeline-atom.REVIEW_ABORT .atom-execute-time,.bk-pipeline .bk-pipeline-atom.REVIEW_ABORT .stage-check-icon,.bk-pipeline .bk-pipeline-atom.REVIEWING .atom-icon,.bk-pipeline .bk-pipeline-atom.REVIEWING .atom-execute-time,.bk-pipeline .bk-pipeline-atom.REVIEWING .stage-check-icon{color:#ffb400}.bk-pipeline .bk-pipeline-atom.FAILED,.bk-pipeline .bk-pipeline-atom.TERMINATE,.bk-pipeline .bk-pipeline-atom.HEARTBEAT_TIMEOUT,.bk-pipeline .bk-pipeline-atom.QUALITY_CHECK_FAIL,.bk-pipeline .bk-pipeline-atom.QUEUE_TIMEOUT,.bk-pipeline .bk-pipeline-atom.EXEC_TIMEOUT{border-color:#ff5656}.bk-pipeline .bk-pipeline-atom.FAILED.is-sub-pipeline-atom,.bk-pipeline .bk-pipeline-atom.TERMINATE.is-sub-pipeline-atom,.bk-pipeline .bk-pipeline-atom.HEARTBEAT_TIMEOUT.is-sub-pipeline-atom,.bk-pipeline .bk-pipeline-atom.QUALITY_CHECK_FAIL.is-sub-pipeline-atom,.bk-pipeline .bk-pipeline-atom.QUEUE_TIMEOUT.is-sub-pipeline-atom,.bk-pipeline .bk-pipeline-atom.EXEC_TIMEOUT.is-sub-pipeline-atom{box-shadow:4px 4px 0 0 rgba(255,86,86,.3)}.bk-pipeline .bk-pipeline-atom.FAILED:before,.bk-pipeline .bk-pipeline-atom.TERMINATE:before,.bk-pipeline .bk-pipeline-atom.HEARTBEAT_TIMEOUT:before,.bk-pipeline .bk-pipeline-atom.QUALITY_CHECK_FAIL:before,.bk-pipeline .bk-pipeline-atom.QUEUE_TIMEOUT:before,.bk-pipeline .bk-pipeline-atom.EXEC_TIMEOUT:before{background-color:#ff5656}.bk-pipeline .bk-pipeline-atom.FAILED:after,.bk-pipeline .bk-pipeline-atom.TERMINATE:after,.bk-pipeline .bk-pipeline-atom.HEARTBEAT_TIMEOUT:after,.bk-pipeline .bk-pipeline-atom.QUALITY_CHECK_FAIL:after,.bk-pipeline .bk-pipeline-atom.QUEUE_TIMEOUT:after,.bk-pipeline .bk-pipeline-atom.EXEC_TIMEOUT:after{border-color:#ff5656}.bk-pipeline .bk-pipeline-atom.FAILED .atom-icon,.bk-pipeline .bk-pipeline-atom.FAILED .atom-execute-time,.bk-pipeline .bk-pipeline-atom.FAILED .stage-check-icon,.bk-pipeline .bk-pipeline-atom.TERMINATE .atom-icon,.bk-pipeline .bk-pipeline-atom.TERMINATE .atom-execute-time,.bk-pipeline .bk-pipeline-atom.TERMINATE .stage-check-icon,.bk-pipeline .bk-pipeline-atom.HEARTBEAT_TIMEOUT .atom-icon,.bk-pipeline .bk-pipeline-atom.HEARTBEAT_TIMEOUT .atom-execute-time,.bk-pipeline .bk-pipeline-atom.HEARTBEAT_TIMEOUT .stage-check-icon,.bk-pipeline .bk-pipeline-atom.QUALITY_CHECK_FAIL .atom-icon,.bk-pipeline .bk-pipeline-atom.QUALITY_CHECK_FAIL .atom-execute-time,.bk-pipeline .bk-pipeline-atom.QUALITY_CHECK_FAIL .stage-check-icon,.bk-pipeline .bk-pipeline-atom.QUEUE_TIMEOUT .atom-icon,.bk-pipeline .bk-pipeline-atom.QUEUE_TIMEOUT .atom-execute-time,.bk-pipeline .bk-pipeline-atom.QUEUE_TIMEOUT .stage-check-icon,.bk-pipeline .bk-pipeline-atom.EXEC_TIMEOUT .atom-icon,.bk-pipeline .bk-pipeline-atom.EXEC_TIMEOUT .atom-execute-time,.bk-pipeline .bk-pipeline-atom.EXEC_TIMEOUT .stage-check-icon{color:#ff5656}.bk-pipeline .bk-pipeline-atom.SUCCEED,.bk-pipeline .bk-pipeline-atom.REVIEW_PROCESSED{border-color:#5ac882}.bk-pipeline .bk-pipeline-atom.SUCCEED.is-sub-pipeline-atom,.bk-pipeline .bk-pipeline-atom.REVIEW_PROCESSED.is-sub-pipeline-atom{box-shadow:4px 4px 0 0 rgba(90,200,130,.3)}.bk-pipeline .bk-pipeline-atom.SUCCEED:before,.bk-pipeline .bk-pipeline-atom.REVIEW_PROCESSED:before{background-color:#5ac882}.bk-pipeline .bk-pipeline-atom.SUCCEED:after,.bk-pipeline .bk-pipeline-atom.REVIEW_PROCESSED:after{border-color:#5ac882}.bk-pipeline .bk-pipeline-atom.SUCCEED .atom-icon,.bk-pipeline .bk-pipeline-atom.SUCCEED .atom-execute-time,.bk-pipeline .bk-pipeline-atom.SUCCEED .stage-check-icon,.bk-pipeline .bk-pipeline-atom.REVIEW_PROCESSED .atom-icon,.bk-pipeline .bk-pipeline-atom.REVIEW_PROCESSED .atom-execute-time,.bk-pipeline .bk-pipeline-atom.REVIEW_PROCESSED .stage-check-icon{color:#5ac882}.bk-pipeline .bk-pipeline-atom.SUCCEED .stage-check-icon,.bk-pipeline .bk-pipeline-atom.REVIEW_PROCESSED .stage-check-icon{border-color:#5ac882}.bk-pipeline .bk-pipeline-atom.PAUSE{border-color:#ff9801;color:#ff9801}.bk-pipeline .bk-pipeline-atom.PAUSE.is-sub-pipeline-atom{box-shadow:4px 4px 0 0 rgba(255,152,1,.3)}.bk-pipeline .bk-pipeline-atom.PAUSE:before{background-color:#ff9801}.bk-pipeline .bk-pipeline-atom.PAUSE:after{border-color:#ff9801}.bk-pipeline .bk-pipeline-atom.PAUSE .atom-icon,.bk-pipeline .bk-pipeline-atom.PAUSE .atom-execute-time{color:#63656e}.bk-pipeline .bk-pipeline-atom.template-compare-atom{border-color:#ff6e00}.bk-pipeline .bk-pipeline-atom.template-compare-atom:before{background-color:#ff6e00}.bk-pipeline .bk-pipeline-atom.template-compare-atom:after{border-color:#ff6e00}.bk-pipeline .bk-pipeline-atom.template-compare-atom:hover{border-color:#ffb400}.bk-pipeline .bk-pipeline-atom.quality-atom{background-color:rgba(0,0,0,0)}.bk-pipeline .bk-pipeline-atom.quality-atom.SUCCEED{border-color:#5ac882}.bk-pipeline .bk-pipeline-atom.quality-atom.SUCCEED .atom-title{color:#5ac882}.bk-pipeline .bk-pipeline-atom.quality-atom.SUCCEED .atom-title>i{border-color:#5ac882}.bk-pipeline .bk-pipeline-atom.quality-atom.REVIEWING{border-color:#ffb400}.bk-pipeline .bk-pipeline-atom.quality-atom.REVIEWING .atom-title{color:#ffb400}.bk-pipeline .bk-pipeline-atom.quality-atom.REVIEWING .atom-title>i{border-color:#ffb400}.bk-pipeline .bk-pipeline-atom.quality-atom.REVIEWING .atom-title>i:last-child{border-color:rgba(0,0,0,0)}.bk-pipeline .bk-pipeline-atom.quality-atom.FAILED{border-color:#ff5656}.bk-pipeline .bk-pipeline-atom.quality-atom.FAILED .atom-title{color:#ff5656}.bk-pipeline .bk-pipeline-atom.quality-atom.FAILED .atom-title>i{border-top:2px solid #ff5656}',""]);const s=r},383:(e,t,i)=>{"use strict";i.d(t,{A:()=>s});var n=i(601),o=i.n(n),a=i(314),r=i.n(a)()(o());r.push([e.id,'/*!\n * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available.\n *\n * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.\n *\n * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license.\n *\n * A copy of the MIT License is included in this file.\n *\n *\n * Terms of the MIT License:\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */@keyframes rotating{from{transform:rotate(0)}to{transform:rotate(359deg)}}.bk-pipeline .bk-pipeline-atom{cursor:pointer;position:relative;display:flex;flex-direction:row;align-items:center;height:42px;margin:0 0 11px 0;background-color:#fff;border-radius:2px;font-size:14px;transition:all .4s ease-in-out;z-index:2;border:1px solid #c4cdd6}.bk-pipeline .bk-pipeline-atom .atom-progress{display:inline-flex;width:42px;height:42px;align-items:center;justify-content:center}.bk-pipeline .bk-pipeline-atom .active-atom-location-icon{position:absolute;color:#3c96ff;left:-30px}.bk-pipeline .bk-pipeline-atom.trigger-atom:before,.bk-pipeline .bk-pipeline-atom.trigger-atom:after{display:none}.bk-pipeline .bk-pipeline-atom:first-child:before{top:-16px}.bk-pipeline .bk-pipeline-atom:before{content:"";position:absolute;height:14px;width:2px;background:#c4cdd6;top:-12px;left:22px;z-index:1}.bk-pipeline .bk-pipeline-atom:after{content:"";position:absolute;height:4px;width:4px;border:2px solid #c4cdd6;border-radius:50%;background:#fff !important;top:-5px;left:19px;z-index:2}.bk-pipeline .bk-pipeline-atom.is-intercept{border-color:#ffb400}.bk-pipeline .bk-pipeline-atom.is-intercept:hover{border-color:#ffb400}.bk-pipeline .bk-pipeline-atom.is-error{border-color:#ff5656;color:#ff5656}.bk-pipeline .bk-pipeline-atom.is-error:hover .atom-invalid-icon{display:none}.bk-pipeline .bk-pipeline-atom.is-error .atom-invalid-icon{margin:0 12px}.bk-pipeline .bk-pipeline-atom:not(.readonly):hover{border-color:#3c96ff}.bk-pipeline .bk-pipeline-atom:not(.readonly):hover .atom-icon.skip-icon{color:#c4cdd6}.bk-pipeline .bk-pipeline-atom:not(.readonly):hover .atom-icon,.bk-pipeline .bk-pipeline-atom:not(.readonly):hover .atom-name{color:#3c96ff}.bk-pipeline .bk-pipeline-atom:not(.readonly):hover .add-plus-icon.close,.bk-pipeline .bk-pipeline-atom:not(.readonly):hover .copy{cursor:pointer;display:block}.bk-pipeline .bk-pipeline-atom .atom-icon{text-align:center;margin:0 14.5px;font-size:18px;width:18px;color:#63656e;fill:currentColor}.bk-pipeline .bk-pipeline-atom .atom-icon.skip-icon{color:#c4cdd6}.bk-pipeline .bk-pipeline-atom .atom-name span.skip-name{text-decoration:line-through;color:#c4cdd6}.bk-pipeline .bk-pipeline-atom .atom-name span.skip-name:hover{color:#c4cdd6}.bk-pipeline .bk-pipeline-atom .pause-button{margin-right:8px;color:#3c96ff}.bk-pipeline .bk-pipeline-atom .add-plus-icon.close{position:relative;display:block;width:16px;height:16px;border:1px solid #fff;background-color:#c4c6cd;border-radius:50%;transition:all .3s ease;display:none;margin-right:10px;border:none;transform:rotate(45deg)}.bk-pipeline .bk-pipeline-atom .add-plus-icon.close:before,.bk-pipeline .bk-pipeline-atom .add-plus-icon.close:after{content:"";position:absolute;left:7px;top:4px;left:6px;top:3px;height:8px;width:2px;background-color:#fff}.bk-pipeline .bk-pipeline-atom .add-plus-icon.close:after{transform:rotate(90deg)}.bk-pipeline .bk-pipeline-atom .add-plus-icon.close:hover{border-color:#ff5656;background-color:#ff5656}.bk-pipeline .bk-pipeline-atom .add-plus-icon.close:hover:before,.bk-pipeline .bk-pipeline-atom .add-plus-icon.close:hover:after{background-color:#fff}.bk-pipeline .bk-pipeline-atom .add-plus-icon.close:before,.bk-pipeline .bk-pipeline-atom .add-plus-icon.close:after{left:7px;top:4px}.bk-pipeline .bk-pipeline-atom .copy{display:none;margin-right:10px;color:#c4cdd6}.bk-pipeline .bk-pipeline-atom .copy:hover{color:#3c96ff}.bk-pipeline .bk-pipeline-atom>.atom-name{flex:1;color:#63656e;display:inline-block;max-width:auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:188px;margin-right:2px}.bk-pipeline .bk-pipeline-atom>.atom-name span:hover{color:#3c96ff}.bk-pipeline .bk-pipeline-atom .disabled{cursor:not-allowed;color:#c4cdd6}.bk-pipeline .bk-pipeline-atom .atom-execounter{color:#3c96ff;font-size:12px}.bk-pipeline .bk-pipeline-atom .atom-operate-area{margin:0 8px 0 0;color:#3c96ff;font-size:12px}.bk-pipeline .bk-pipeline-atom .atom-reviewing-tips[disabled]{cursor:not-allowed;color:#c3cdd7}.bk-pipeline .bk-pipeline-atom .atom-review-diasbled-tips{color:#c3cdd7;margin:0 8px 0 2px}.bk-pipeline .bk-pipeline-atom .atom-canskip-checkbox{margin-right:6px}.bk-pipeline .bk-pipeline-atom.quality-atom{display:flex;justify-content:center;border-color:rgba(0,0,0,0);height:24px;background:rgba(0,0,0,0);border-color:rgba(0,0,0,0) !important;font-size:12px}.bk-pipeline .bk-pipeline-atom.quality-atom:before{height:40px;z-index:8}.bk-pipeline .bk-pipeline-atom.quality-atom:after{display:none}.bk-pipeline .bk-pipeline-atom.quality-atom.last-quality-atom:before{height:22px}.bk-pipeline .bk-pipeline-atom.quality-atom .atom-title{display:flex;width:100%;align-items:center;justify-content:center;margin-left:22px}.bk-pipeline .bk-pipeline-atom.quality-atom .atom-title>span{border-radius:12px;font-weight:bold;border:1px solid #c4cdd6;padding:0 12px;margin:0 4px}.bk-pipeline .bk-pipeline-atom.quality-atom .atom-title>i{height:0;flex:1;border-top:2px dashed #c4cdd6}.bk-pipeline .bk-pipeline-atom.quality-atom .handler-list{position:absolute;right:0}.bk-pipeline .bk-pipeline-atom.quality-atom .handler-list span{color:#3c96ff;font-size:12px}.bk-pipeline .bk-pipeline-atom.quality-atom .handler-list span:first-child{margin-right:5px}.bk-pipeline .bk-pipeline-atom.quality-atom .executing-job{position:absolute;top:6px;right:42px}.bk-pipeline .bk-pipeline-atom.quality-atom .executing-job:before{display:inline-block;animation:rotating infinite .6s ease-in-out}.bk-pipeline .bk-pipeline-atom.quality-atom .disabled-review span{color:#c4cdd6;cursor:default}.bk-pipeline .bk-pipeline-atom.readonly{background-color:#fff}.bk-pipeline .bk-pipeline-atom.readonly .atom-name:hover span{color:#63656e}.bk-pipeline .bk-pipeline-atom.readonly .atom-name:hover .skip-name{text-decoration:line-through;color:#c4cdd6}.bk-pipeline .bk-pipeline-atom.readonly.quality-prev-atom:before{height:24px;top:-23px}',""]);const s=r},734:(e,t,i)=>{"use strict";i.d(t,{A:()=>s});var n=i(601),o=i.n(n),a=i(314),r=i.n(a)()(o());r.push([e.id,'/*!\n * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available.\n *\n * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.\n *\n * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license.\n *\n * A copy of the MIT License is included in this file.\n *\n *\n * Terms of the MIT License:\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */@keyframes rotating{from{transform:rotate(0)}to{transform:rotate(359deg)}}.container-atom-list{position:relative;z-index:3}.container-atom-list .sortable-ghost-atom{opacity:.5}.container-atom-list .sortable-chosen-atom{transform:scale(1)}.container-atom-list .add-atom-entry{position:absolute;bottom:-10px;left:calc(50% - 9px);cursor:pointer;z-index:3}.container-atom-list .add-atom-entry .add-plus-icon{position:relative;display:block;width:18px;height:18px;border:1px solid #c4cdd6;background-color:#fff;border-radius:50%;transition:all .3s ease}.container-atom-list .add-atom-entry .add-plus-icon:before,.container-atom-list .add-atom-entry .add-plus-icon:after{content:"";position:absolute;left:8px;top:5px;left:7px;top:4px;height:8px;width:2px;background-color:#c4cdd6}.container-atom-list .add-atom-entry .add-plus-icon:after{transform:rotate(90deg)}.container-atom-list .add-atom-entry .add-plus-icon:hover{border-color:#3c96ff;background-color:#3c96ff}.container-atom-list .add-atom-entry .add-plus-icon:hover:before,.container-atom-list .add-atom-entry .add-plus-icon:hover:after{background-color:#fff}.container-atom-list .add-atom-entry.block-add-entry{display:flex;flex-direction:row;align-items:center;height:42px;margin:0 0 11px 0;background-color:#fff;border-radius:2px;font-size:14px;transition:all .4s ease-in-out;z-index:2;position:static;padding-right:12px;border-style:dashed;color:#ff5656;border-color:#ff5656;border-width:1px}.container-atom-list .add-atom-entry.block-add-entry .add-atom-label{flex:1;color:#dde4eb}.container-atom-list .add-atom-entry.block-add-entry .add-plus-icon{margin:12px 13px}.container-atom-list .add-atom-entry.block-add-entry:before,.container-atom-list .add-atom-entry.block-add-entry:after{display:none}.container-atom-list .add-atom-entry:hover{border-color:#3c96ff;color:#3c96ff}.container-atom-list .post-action-arrow{position:relativecd;display:flex;align-items:center;justify-content:center;position:absolute;height:14px;width:14px;border:1px solid #c4cdd6;color:#c4cdd6;border-radius:50%;background:#fff !important;top:-7px;left:17px;z-index:3;font-weight:bold}.container-atom-list .post-action-arrow .toggle-post-action-icon{display:block;transition:all .5s ease}.container-atom-list .post-action-arrow.post-action-arrow-show .toggle-post-action-icon{transform:rotate(180deg)}.container-atom-list .post-action-arrow::after{content:"";position:absolute;width:2px;height:6px;background-color:#c4cdd6;left:5px;top:-6px}.container-atom-list .post-action-arrow.FAILED{border-color:#ff5656;color:#ff5656}.container-atom-list .post-action-arrow.FAILED::after{background-color:#ff5656}.container-atom-list .post-action-arrow.CANCELED{border-color:#ffb400;color:#ffb400}.container-atom-list .post-action-arrow.CANCELED::after{background-color:#ffb400}.container-atom-list .post-action-arrow.SUCCEED{border-color:#5ac882;color:#5ac882}.container-atom-list .post-action-arrow.SUCCEED::after{background-color:#5ac882}.container-atom-list .post-action-arrow.RUNNING{border-color:#3c96ff;color:#3c96ff}.container-atom-list .post-action-arrow.RUNNING::after{background-color:#3c96ff}',""]);const s=r},629:(e,t,i)=>{"use strict";i.d(t,{A:()=>s});var n=i(601),o=i.n(n),a=i(314),r=i.n(a)()(o());r.push([e.id,".is-danger[data-v-8b5d140e]{color:#ff5656}",""]);const s=r},577:(e,t,i)=>{"use strict";i.d(t,{A:()=>s});var n=i(601),o=i.n(n),a=i(314),r=i.n(a)()(o());r.push([e.id,'/*!\n * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available.\n *\n * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.\n *\n * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license.\n *\n * A copy of the MIT License is included in this file.\n *\n *\n * Terms of the MIT License:\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */@keyframes rotating{from{transform:rotate(0)}to{transform:rotate(359deg)}}.devops-stage-container .container-title{display:flex;height:42px;background:#33333f;cursor:pointer;color:#fff;font-size:14px;align-items:center;position:relative;margin:0 0 16px 0;z-index:3}.devops-stage-container .container-title>.container-name{display:inline-block;max-width:auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;padding:0 6px}.devops-stage-container .container-title .atom-canskip-checkbox{margin-right:6px}.devops-stage-container .container-title .atom-canskip-checkbox.is-disabled .bk-checkbox{background-color:rgba(0,0,0,0);border-color:#979ba4}.devops-stage-container .container-title input[type=checkbox]{border-radius:3px}.devops-stage-container .container-title .matrix-flag-icon{position:absolute;top:0px;font-size:16px}.devops-stage-container .container-title .fold-atom-icon{position:absolute;background:#fff;border-radius:50%;bottom:-10px;left:calc(50% - 9px);color:#c4cdd6;transition:all .3s ease}.devops-stage-container .container-title .fold-atom-icon.open{transform:rotate(-180deg)}.devops-stage-container .container-title .copyJob{display:none;margin-right:10px;color:#c4cdd6;cursor:pointer}.devops-stage-container .container-title .copyJob:hover{color:#3c96ff}.devops-stage-container .container-title .close{position:relative;display:block;width:16px;height:16px;border:1px solid #2e2e3a;background-color:#c4c6cd;border-radius:50%;transition:all .3s ease;border:none;display:none;margin-right:10px;transform:rotate(45deg);cursor:pointer}.devops-stage-container .container-title .close:before,.devops-stage-container .container-title .close:after{content:"";position:absolute;left:7px;top:4px;left:6px;top:3px;height:8px;width:2px;background-color:#2e2e3a}.devops-stage-container .container-title .close:after{transform:rotate(90deg)}.devops-stage-container .container-title .close:hover{border-color:#ff5656;background-color:#ff5656}.devops-stage-container .container-title .close:hover:before,.devops-stage-container .container-title .close:hover:after{background-color:#fff}.devops-stage-container .container-title .close:before,.devops-stage-container .container-title .close:after{left:7px;top:4px}.devops-stage-container .container-title .debug-btn{position:absolute;height:100%;right:0}.devops-stage-container .container-title .container-locate-icon{position:absolute;left:-30px;top:13px;color:#3c96ff}.devops-stage-container .container-title:hover .copyJob,.devops-stage-container .container-title:hover .close{display:block}.devops-stage-container .container-title:hover .hover-hide{display:none}',""]);const s=r},194:(e,t,i)=>{"use strict";i.d(t,{A:()=>s});var n=i(601),o=i.n(n),a=i(314),r=i.n(a)()(o());r.push([e.id,'/*!\n * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available.\n *\n * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.\n *\n * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license.\n *\n * A copy of the MIT License is included in this file.\n *\n *\n * Terms of the MIT License:\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */@keyframes rotating{from{transform:rotate(0)}to{transform:rotate(359deg)}}.bk-pipeline-matrix-group{border:1px solid #b5c0d5;padding:10px;background:#fff}.bk-pipeline-matrix-group .bk-pipeline-matrix-group-header{display:flex;align-items:center;cursor:pointer;justify-content:space-between;height:20px}.bk-pipeline-matrix-group .bk-pipeline-matrix-group-header .matrix-name{display:flex;align-items:center;font-size:14px;color:#222;min-width:0}.bk-pipeline-matrix-group .bk-pipeline-matrix-group-header .matrix-name .matrix-fold-icon{display:block;margin-right:10px;transition:all .3s ease;flex-shrink:0}.bk-pipeline-matrix-group .bk-pipeline-matrix-group-header .matrix-name .matrix-fold-icon.open{transform:rotate(-180deg)}.bk-pipeline-matrix-group .bk-pipeline-matrix-group-header .matrix-name>span{display:inline-block;max-width:auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.bk-pipeline-matrix-group .bk-pipeline-matrix-group-header .matrix-status{color:#3c96ff;display:flex;align-items:center}.bk-pipeline-matrix-group .bk-pipeline-matrix-group-header .matrix-status .status-desc{font-size:12px;display:inline-block;max-width:110px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.bk-pipeline-matrix-group .matrix-body{margin-top:12px}.bk-pipeline-matrix-group .matrix-body>div{margin-bottom:34px}',""]);const s=r},858:(e,t,i)=>{"use strict";i.d(t,{A:()=>s});var n=i(601),o=i.n(n),a=i(314),r=i.n(a)()(o());r.push([e.id,'/*!\n * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available.\n *\n * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.\n *\n * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license.\n *\n * A copy of the MIT License is included in this file.\n *\n *\n * Terms of the MIT License:\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */@keyframes rotating{from{transform:rotate(0)}to{transform:rotate(359deg)}}.pipeline-drag{cursor:grab,default}.pipeline-stage{position:relative;width:280px;border-radius:2px;padding:0;background:#f5f5f5;margin:0 80px 0 0}.pipeline-stage .pipeline-stage-entry{position:relative;cursor:pointer;display:flex;width:100%;height:50px;align-items:center;min-width:0;font-size:14px;background-color:#eff5ff;border:1px solid #d4e8ff;color:#3c96ff}.pipeline-stage .pipeline-stage-entry:hover{border-color:#1a6df3;background-color:#d1e2fd}.pipeline-stage .pipeline-stage-entry .check-in-icon,.pipeline-stage .pipeline-stage-entry .check-out-icon{position:absolute;left:-14px;top:11px}.pipeline-stage .pipeline-stage-entry .check-in-icon.check-out-icon,.pipeline-stage .pipeline-stage-entry .check-out-icon.check-out-icon{left:auto;right:-14px}.pipeline-stage .pipeline-stage-entry .stage-entry-name{flex:1;display:flex;align-items:center;justify-content:center;margin:0 80px;overflow:hidden}.pipeline-stage .pipeline-stage-entry .stage-entry-name .stage-title-name{display:inline-block;max-width:auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;margin-left:6px}.pipeline-stage .pipeline-stage-entry .stage-single-retry{cursor:pointer;position:absolute;right:6%;color:#3c96ff}.pipeline-stage .pipeline-stage-entry .stage-entry-error-icon,.pipeline-stage .pipeline-stage-entry .check-total-stage{position:absolute;right:27px}.pipeline-stage .pipeline-stage-entry .stage-entry-error-icon.stage-entry-error-icon,.pipeline-stage .pipeline-stage-entry .check-total-stage.stage-entry-error-icon{top:16px;right:8px;color:#ff5656}.pipeline-stage .pipeline-stage-entry .stage-entry-btns{position:absolute;right:0;top:16px;display:none;width:80px;align-items:center;justify-content:flex-end;color:#fff;fill:#fff;z-index:2}.pipeline-stage .pipeline-stage-entry .stage-entry-btns .copy-stage:hover{color:#3c96ff}.pipeline-stage .pipeline-stage-entry .stage-entry-btns .close{position:relative;display:block;width:16px;height:16px;border:1px solid #2e2e3a;background-color:#fff;border-radius:50%;transition:all .3s ease;border:none;margin:0 10px 0 8px;transform:rotate(45deg);cursor:pointer}.pipeline-stage .pipeline-stage-entry .stage-entry-btns .close:before,.pipeline-stage .pipeline-stage-entry .stage-entry-btns .close:after{content:"";position:absolute;left:7px;top:4px;left:6px;top:3px;height:8px;width:2px;background-color:#2e2e3a}.pipeline-stage .pipeline-stage-entry .stage-entry-btns .close:after{transform:rotate(90deg)}.pipeline-stage .pipeline-stage-entry .stage-entry-btns .close:hover{border-color:#ff5656;background-color:#ff5656}.pipeline-stage .pipeline-stage-entry .stage-entry-btns .close:hover:before,.pipeline-stage .pipeline-stage-entry .stage-entry-btns .close:hover:after{background-color:#fff}.pipeline-stage .pipeline-stage-entry .stage-entry-btns .close:before,.pipeline-stage .pipeline-stage-entry .stage-entry-btns .close:after{left:7px;top:4px}.pipeline-stage.editable:not(.readonly) .pipeline-stage-entry:hover{color:#000;border-color:#1a6df3;background-color:#d1e2fd}.pipeline-stage.editable .pipeline-stage-entry:hover .stage-entry-btns{display:flex}.pipeline-stage.editable .pipeline-stage-entry:hover .stage-entry-error-icon{display:none}.pipeline-stage.readonly.SKIP .pipeline-stage-entry{color:#c3cdd7;fill:#c3cdd7}.pipeline-stage.readonly.RUNNING .pipeline-stage-entry{background-color:#eff5ff;border-color:#d4e8ff;color:#3c96ff}.pipeline-stage.readonly.REVIEWING .pipeline-stage-entry{background-color:#f3f3f3;border-color:#d0d8ea;color:#000}.pipeline-stage.readonly.FAILED .pipeline-stage-entry{border-color:#ffd4d4;background-color:#fff9f9;color:#000}.pipeline-stage.readonly.SUCCEED .pipeline-stage-entry{background-color:#f3fff6;border-color:#bbefc9;color:#000}.pipeline-stage.readonly .pipeline-stage-entry{background-color:#f3f3f3;border-color:#d0d8ea;color:#000}.pipeline-stage.readonly .pipeline-stage-entry .skip-icon{vertical-align:middle}.pipeline-stage .add-connector{stroke-dasharray:4,4;top:7px;left:10px}.pipeline-stage .append-stage{position:absolute;top:16px;right:-44px;z-index:3}.pipeline-stage .append-stage .add-plus-icon{box-shadow:0px 2px 4px 0px rgba(60,150,255,.2)}.pipeline-stage .append-stage .line-add{top:-46px;left:-16px}.pipeline-stage .append-stage .add-plus-connector{position:absolute;width:40px;height:2px;left:-26px;top:8px;background-color:#3c96ff}.pipeline-stage .add-menu{position:absolute;top:16px;left:-53px;cursor:pointer;z-index:3}.pipeline-stage .add-menu .add-plus-icon{box-shadow:0px 2px 4px 0px rgba(60,150,255,.2)}.pipeline-stage .add-menu .minus-icon{z-index:4}.pipeline-stage .add-menu .line-add{top:-46px;left:-16px}.pipeline-stage .add-menu .parallel-add{left:50px}.pipeline-stage .add-menu .add-plus-connector{position:absolute;width:24px;height:2px;left:17px;top:8px;background-color:#3c96ff}.pipeline-stage:first-child .stage-connector{display:none}.pipeline-stage.is-final-stage .stage-connector{width:80px}.pipeline-stage .stage-connector{position:absolute;width:66px;height:2px;left:-80px;top:24px;color:#3c96ff;background-color:#3c96ff}.pipeline-stage .stage-connector:before{content:"";width:8px;height:8px;position:absolute;left:-4px;top:-3px;background-color:#3c96ff;border-radius:50%}.pipeline-stage .stage-connector .connector-angle{position:absolute;right:-3px;top:-6px}.pipeline-stage .insert-stage{position:absolute;display:block;width:160px;background-color:#fff;border:1px solid #dcdee5}.pipeline-stage .insert-stage .click-item{padding:0 15px;font-size:12px;line-height:32px}.pipeline-stage .insert-stage .click-item:hover,.pipeline-stage .insert-stage .click-item :hover{color:#3c96ff;background-color:#eaf3ff}.pipeline-stage .insert-stage .disabled-item{cursor:not-allowed;color:#c4cdd6}.pipeline-stage .insert-stage .disabled-item:hover,.pipeline-stage .insert-stage .disabled-item :hover{color:#c4cdd6;background-color:#fff}.stage-retry-dialog .bk-form-radio{display:block;margin-top:15px}.stage-retry-dialog .bk-form-radio .bk-radio-text{font-size:14px}',""]);const s=r},838:(e,t,i)=>{"use strict";i.d(t,{A:()=>s});var n=i(601),o=i.n(n),a=i(314),r=i.n(a)()(o());r.push([e.id,'/*!\n * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available.\n *\n * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.\n *\n * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license.\n *\n * A copy of the MIT License is included in this file.\n *\n *\n * Terms of the MIT License:\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */@keyframes rotating{from{transform:rotate(0)}to{transform:rotate(359deg)}}.stage-check-icon{border-radius:100px;border:1px solid #d0d8ea;display:flex;align-items:center;background:#fff;font-size:12px;z-index:3;color:#63656e}.stage-check-icon.is-readonly-check-icon{filter:grayscale(100%)}.stage-check-icon.reviewing{color:#3c96ff;border-color:#3c96ff}.stage-check-icon.quality-check{color:#5ac882;border-color:#5ac882}.stage-check-icon.quality-check-error,.stage-check-icon.review-error{color:#ff5656;border-color:#ff5656}.stage-check-icon .stage-check-txt{padding-right:10px}',""]);const s=r},99:(e,t,i)=>{"use strict";i.d(t,{A:()=>s});var n=i(601),o=i.n(n),a=i(314),r=i.n(a)()(o());r.push([e.id,'/*!\n * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available.\n *\n * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.\n *\n * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license.\n *\n * A copy of the MIT License is included in this file.\n *\n *\n * Terms of the MIT License:\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */@keyframes rotating{from{transform:rotate(0)}to{transform:rotate(359deg)}}.devops-stage-container{text-align:left;margin:16px 20px 24px 20px;position:relative}.devops-stage-container:not(.last-stage-container):after{content:"";width:6px;height:6px;position:absolute;right:-3px;top:19px;border-radius:50%}.devops-stage-container:not(.last-stage-container):after:not(.readonly){background:#3c96ff}.devops-stage-container .container-connect-triangle{position:absolute;color:#3c96ff;left:-9px;top:15.5px;z-index:2}.devops-stage-container .connect-line{position:absolute;top:1px;stroke:#3c96ff;stroke-width:1;fill:none;z-index:0}.devops-stage-container .connect-line.left{left:-54px}.devops-stage-container .connect-line.right{right:-46px}.devops-stage-container .connect-line.first-connect-line{height:76px;width:58px;top:-43px}.devops-stage-container .connect-line.first-connect-line.left{left:-63px}.devops-stage-container .connect-line.first-connect-line.right{left:auto;right:-55px}',""]);const s=r},423:(e,t,i)=>{"use strict";i.d(t,{A:()=>s});var n=i(601),o=i.n(n),a=i(314),r=i.n(a)()(o());r.push([e.id,'/*!\n * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available.\n *\n * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.\n *\n * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license.\n *\n * A copy of the MIT License is included in this file.\n *\n *\n * Terms of the MIT License:\n * ---------------------------------------------------\n * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n */@keyframes rotating{from{transform:rotate(0)}to{transform:rotate(359deg)}}.stage-status{position:relative;text-align:center;overflow:hidden;font-size:14px;width:42px;height:42px;box-sizing:border-box}.stage-status>span{position:absolute;width:100%;height:100%;display:flex;align-items:center;justify-content:center;left:0;top:0;transition:all .3s cubic-bezier(1, 0.5, 0.8, 1)}.stage-status.matrix{width:20px;height:20px}.stage-status .status-logo{position:absolute;left:15px;top:15px}.stage-status .slide-top-enter,.stage-status .slide-top-leave-to{transform:translateY(42px)}.stage-status .slide-down-enter,.stage-status .slide-down-leave-to{transform:translateY(-42px)}.stage-status .slide-left-enter,.stage-status .slide-left-leave-to{transform:translateX(42px)}.stage-status .slide-right-enter,.stage-status .slide-right-leave-to{transform:translateX(-42px)}.stage-status.readonly{font-size:12px;font-weight:normal;background-color:rgba(0,0,0,0)}.stage-status.readonly.container{color:#fff}',""]);const s=r},462:(e,t,i)=>{"use strict";i.d(t,{A:()=>s});var n=i(601),o=i.n(n),a=i(314),r=i.n(a)()(o());r.push([e.id,".bk-pipeline{display:flex;padding-right:120px;width:fit-content;position:relative;align-items:flex-start}.bk-pipeline ul,.bk-pipeline li{margin:0;padding:0}.list-item{transition:transform .2s ease-out}.list-enter,.list-leave-to{opacity:0;transform:translateY(36px) scale(0, 1)}.list-leave-active{position:absolute !important}",""]);const s=r},314:e=>{"use strict";e.exports=function(e){var t=[];return t.toString=function(){return this.map((function(t){var i="",n=void 0!==t[5];return t[4]&&(i+="@supports (".concat(t[4],") {")),t[2]&&(i+="@media ".concat(t[2]," {")),n&&(i+="@layer".concat(t[5].length>0?" ".concat(t[5]):""," {")),i+=e(t),n&&(i+="}"),t[2]&&(i+="}"),t[4]&&(i+="}"),i})).join("")},t.i=function(e,i,n,o,a){"string"==typeof e&&(e=[[null,e,void 0]]);var r={};if(n)for(var s=0;s0?" ".concat(p[5]):""," {").concat(p[1],"}")),p[5]=a),i&&(p[2]?(p[1]="@media ".concat(p[2]," {").concat(p[1],"}"),p[2]=i):p[2]=i),o&&(p[4]?(p[1]="@supports (".concat(p[4],") {").concat(p[1],"}"),p[4]=o):p[4]="".concat(o)),t.push(p))}},t}},601:e=>{"use strict";e.exports=function(e){return e[1]}},246:(e,t,i)=>{"use strict";function n(e){return n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},n(e)}function o(e,t,i){return t in e?Object.defineProperty(e,t,{value:i,enumerable:!0,configurable:!0,writable:!0}):e[t]=i,e}function a(){return a=Object.assign||function(e){for(var t=1;tgt,Sortable:()=>ze,Swap:()=>at,default:()=>yt});var l=s(/(?:Trident.*rv[ :]?11\.|msie|iemobile|Windows Phone)/i),c=s(/Edge/i),p=s(/firefox/i),d=s(/safari/i)&&!s(/chrome/i)&&!s(/android/i),u=s(/iP(ad|od|hone)/i),h=s(/chrome/i)&&s(/android/i),f={capture:!1,passive:!1};function m(e,t,i){e.addEventListener(t,i,!l&&f)}function g(e,t,i){e.removeEventListener(t,i,!l&&f)}function b(e,t){if(t){if(">"===t[0]&&(t=t.substring(1)),e)try{if(e.matches)return e.matches(t);if(e.msMatchesSelector)return e.msMatchesSelector(t);if(e.webkitMatchesSelector)return e.webkitMatchesSelector(t)}catch(e){return!1}return!1}}function v(e){return e.host&&e!==document&&e.host.nodeType?e.host:e.parentNode}function y(e,t,i,n){if(e){i=i||document;do{if(null!=t&&(">"===t[0]?e.parentNode===i&&b(e,t):b(e,t))||n&&e===i)return e;if(e===i)break}while(e=v(e))}return null}var E,x=/\s+/g;function k(e,t,i){if(e&&t)if(e.classList)e.classList[i?"add":"remove"](t);else{var n=(" "+e.className+" ").replace(x," ").replace(" "+t+" "," ");e.className=(n+(i?" "+t:"")).replace(x," ")}}function I(e,t,i){var n=e&&e.style;if(n){if(void 0===i)return document.defaultView&&document.defaultView.getComputedStyle?i=document.defaultView.getComputedStyle(e,""):e.currentStyle&&(i=e.currentStyle),void 0===t?i:i[t];t in n||-1!==t.indexOf("webkit")||(t="-webkit-"+t),n[t]=i+("string"==typeof i?"":"px")}}function T(e,t){var i="";if("string"==typeof e)i=e;else do{var n=I(e,"transform");n&&"none"!==n&&(i=n+" "+i)}while(!t&&(e=e.parentNode));var o=window.DOMMatrix||window.WebKitCSSMatrix||window.CSSMatrix||window.MSCSSMatrix;return o&&new o(i)}function C(e,t,i){if(e){var n=e.getElementsByTagName(t),o=0,a=n.length;if(i)for(;o=a:o<=a))return n;if(n===S())break;n=M(n,!1)}return!1}function O(e,t,i){for(var n=0,o=0,a=e.children;o2&&void 0!==arguments[2]?arguments[2]:{},n=i.evt,o=function(e,t){if(null==e)return{};var i,n,o=function(e,t){if(null==e)return{};var i,n,o={},a=Object.keys(e);for(n=0;n=0||(o[i]=e[i]);return o}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,i)&&(o[i]=e[i])}return o}(i,["evt"]);W.pluginEvent.bind(ze)(e,t,r({dragEl:Y,parentEl:q,ghostEl:K,rootEl:X,nextEl:Q,lastDownEl:J,cloneEl:Z,cloneHidden:ee,dragStarted:he,putSortable:re,activeSortable:ze.active,originalEvent:n,oldIndex:te,oldDraggableIndex:ne,newIndex:ie,newDraggableIndex:oe,hideGhostForTarget:Me,unhideGhostForTarget:Le,cloneNowHidden:function(){ee=!0},cloneNowShown:function(){ee=!1},dispatchSortableEvent:function(e){$({sortable:t,name:e,originalEvent:n})}},o))};function $(e){j(r({putSortable:re,cloneEl:Z,targetEl:Y,rootEl:X,oldIndex:te,oldDraggableIndex:ne,newIndex:ie,newDraggableIndex:oe},e))}var Y,q,K,X,Q,J,Z,ee,te,ie,ne,oe,ae,re,se,le,ce,pe,de,ue,he,fe,me,ge,be,ve=!1,ye=!1,Ee=[],xe=!1,ke=!1,Ie=[],Te=!1,Ce=[],Se="undefined"!=typeof document,Ae=u,we=c||l?"cssFloat":"float",Oe=Se&&!h&&!u&&"draggable"in document.createElement("div"),_e=function(){if(Se){if(l)return!1;var e=document.createElement("x");return e.style.cssText="pointer-events:auto","auto"===e.style.pointerEvents}}(),Ne=function(e,t){var i=I(e),n=parseInt(i.width)-parseInt(i.paddingLeft)-parseInt(i.paddingRight)-parseInt(i.borderLeftWidth)-parseInt(i.borderRightWidth),o=O(e,0,t),a=O(e,1,t),r=o&&I(o),s=a&&I(a),l=r&&parseInt(r.marginLeft)+parseInt(r.marginRight)+A(o).width,c=s&&parseInt(s.marginLeft)+parseInt(s.marginRight)+A(a).width;if("flex"===i.display)return"column"===i.flexDirection||"column-reverse"===i.flexDirection?"vertical":"horizontal";if("grid"===i.display)return i.gridTemplateColumns.split(" ").length<=1?"vertical":"horizontal";if(o&&r.float&&"none"!==r.float){var p="left"===r.float?"left":"right";return!a||"both"!==s.clear&&s.clear!==p?"horizontal":"vertical"}return o&&("block"===r.display||"flex"===r.display||"table"===r.display||"grid"===r.display||l>=n&&"none"===i[we]||a&&"none"===i[we]&&l+c>n)?"vertical":"horizontal"},Re=function(e){function t(e,i){return function(n,o,a,r){var s=n.options.group.name&&o.options.group.name&&n.options.group.name===o.options.group.name;if(null==e&&(i||s))return!0;if(null==e||!1===e)return!1;if(i&&"clone"===e)return e;if("function"==typeof e)return t(e(n,o,a,r),i)(n,o,a,r);var l=(i?n:o).options.group.name;return!0===e||"string"==typeof e&&e===l||e.join&&e.indexOf(l)>-1}}var i={},o=e.group;o&&"object"==n(o)||(o={name:o}),i.name=o.name,i.checkPull=t(o.pull,!0),i.checkPut=t(o.put),i.revertClone=o.revertClone,e.group=i},Me=function(){!_e&&K&&I(K,"display","none")},Le=function(){!_e&&K&&I(K,"display","")};Se&&document.addEventListener("click",(function(e){if(ye)return e.preventDefault(),e.stopPropagation&&e.stopPropagation(),e.stopImmediatePropagation&&e.stopImmediatePropagation(),ye=!1,!1}),!0);var De=function(e){if(Y){e=e.touches?e.touches[0]:e;var t=(o=e.clientX,a=e.clientY,Ee.some((function(e){if(!_(e)){var t=A(e),i=e[U].options.emptyInsertThreshold,n=o>=t.left-i&&o<=t.right+i,s=a>=t.top-i&&a<=t.bottom+i;return i&&n&&s?r=e:void 0}})),r);if(t){var i={};for(var n in e)e.hasOwnProperty(n)&&(i[n]=e[n]);i.target=i.rootEl=t,i.preventDefault=void 0,i.stopPropagation=void 0,t[U]._onDragOver(i)}}var o,a,r},He=function(e){Y&&Y.parentNode[U]._isOutsideThisEl(e.target)};function ze(e,t){if(!e||!e.nodeType||1!==e.nodeType)throw"Sortable: `el` must be an HTMLElement, not ".concat({}.toString.call(e));this.el=e,this.options=t=a({},t),e[U]=this;var i,n,o={group:null,sort:!0,disabled:!1,store:null,handle:null,draggable:/^[uo]l$/i.test(e.nodeName)?">li":">*",swapThreshold:1,invertSwap:!1,invertedSwapThreshold:null,removeCloneOnHide:!0,direction:function(){return Ne(e,this.options)},ghostClass:"sortable-ghost",chosenClass:"sortable-chosen",dragClass:"sortable-drag",ignore:"a, img",filter:null,preventOnFilter:!0,animation:0,easing:null,setData:function(e,t){e.setData("Text",t.textContent)},dropBubble:!1,dragoverBubble:!1,dataIdAttr:"data-id",delay:0,delayOnTouchOnly:!1,touchStartThreshold:(Number.parseInt?Number:window).parseInt(window.devicePixelRatio,10)||1,forceFallback:!1,fallbackClass:"sortable-fallback",fallbackOnBody:!1,fallbackTolerance:0,fallbackOffset:{x:0,y:0},supportPointer:!1!==ze.supportPointer&&"PointerEvent"in window,emptyInsertThreshold:5};for(var s in W.initializePlugins(this,e,o),o)!(s in t)&&(t[s]=o[s]);for(var l in Re(t),this)"_"===l.charAt(0)&&"function"==typeof this[l]&&(this[l]=this[l].bind(this));this.nativeDraggable=!t.forceFallback&&Oe,this.nativeDraggable&&(this.options.touchStartThreshold=1),t.supportPointer?m(e,"pointerdown",this._onTapStart):(m(e,"mousedown",this._onTapStart),m(e,"touchstart",this._onTapStart)),this.nativeDraggable&&(m(e,"dragover",this),m(e,"dragenter",this)),Ee.push(this.el),t.store&&t.store.get&&this.sort(t.store.get(this)||[]),a(this,(n=[],{captureAnimationState:function(){n=[],this.options.animation&&[].slice.call(this.el.children).forEach((function(e){if("none"!==I(e,"display")&&e!==ze.ghost){n.push({target:e,rect:A(e)});var t=r({},n[n.length-1].rect);if(e.thisAnimationDuration){var i=T(e,!0);i&&(t.top-=i.f,t.left-=i.e)}e.fromRect=t}}))},addAnimationState:function(e){n.push(e)},removeAnimationState:function(e){n.splice(function(e,t){for(var i in e)if(e.hasOwnProperty(i))for(var n in t)if(t.hasOwnProperty(n)&&t[n]===e[i][n])return Number(i);return-1}(n,{target:e}),1)},animateAll:function(e){var t=this;if(!this.options.animation)return clearTimeout(i),void("function"==typeof e&&e());var o=!1,a=0;n.forEach((function(e){var i=0,n=e.target,r=n.fromRect,s=A(n),l=n.prevFromRect,c=n.prevToRect,p=e.rect,d=T(n,!0);d&&(s.top-=d.f,s.left-=d.e),n.toRect=s,n.thisAnimationDuration&&L(l,s)&&!L(r,s)&&(p.top-s.top)/(p.left-s.left)==(r.top-s.top)/(r.left-s.left)&&(i=function(e,t,i,n){return Math.sqrt(Math.pow(t.top-e.top,2)+Math.pow(t.left-e.left,2))/Math.sqrt(Math.pow(t.top-i.top,2)+Math.pow(t.left-i.left,2))*n.animation}(p,l,c,t.options)),L(s,r)||(n.prevFromRect=r,n.prevToRect=s,i||(i=t.options.animation),t.animate(n,p,s,i)),i&&(o=!0,a=Math.max(a,i),clearTimeout(n.animationResetTimer),n.animationResetTimer=setTimeout((function(){n.animationTime=0,n.prevFromRect=null,n.fromRect=null,n.prevToRect=null,n.thisAnimationDuration=null}),i),n.thisAnimationDuration=i)})),clearTimeout(i),o?i=setTimeout((function(){"function"==typeof e&&e()}),a):"function"==typeof e&&e(),n=[]},animate:function(e,t,i,n){if(n){I(e,"transition",""),I(e,"transform","");var o=T(this.el),a=o&&o.a,r=o&&o.d,s=(t.left-i.left)/(a||1),l=(t.top-i.top)/(r||1);e.animatingX=!!s,e.animatingY=!!l,I(e,"transform","translate3d("+s+"px,"+l+"px,0)"),function(e){e.offsetWidth}(e),I(e,"transition","transform "+n+"ms"+(this.options.easing?" "+this.options.easing:"")),I(e,"transform","translate3d(0,0,0)"),"number"==typeof e.animated&&clearTimeout(e.animated),e.animated=setTimeout((function(){I(e,"transition",""),I(e,"transform",""),e.animated=!1,e.animatingX=!1,e.animatingY=!1}),n)}}}))}function Pe(e,t,i,n,o,a,r,s){var p,d,u=e[U],h=u.options.onMove;return!window.CustomEvent||l||c?(p=document.createEvent("Event")).initEvent("move",!0,!0):p=new CustomEvent("move",{bubbles:!0,cancelable:!0}),p.to=t,p.from=e,p.dragged=i,p.draggedRect=n,p.related=o||t,p.relatedRect=a||A(t),p.willInsertAfter=s,p.originalEvent=r,e.dispatchEvent(p),h&&(d=h.call(u,p,r)),d}function Fe(e){e.draggable=!1}function Ue(){Te=!1}function Be(e){for(var t=e.tagName+e.className+e.src+e.href+e.textContent,i=t.length,n=0;i--;)n+=t.charCodeAt(i);return n.toString(36)}function Ve(e){return setTimeout(e,0)}function We(e){return clearTimeout(e)}ze.prototype={constructor:ze,_isOutsideThisEl:function(e){this.el.contains(e)||e===this.el||(fe=null)},_getDirection:function(e,t){return"function"==typeof this.options.direction?this.options.direction.call(this,e,t,Y):this.options.direction},_onTapStart:function(e){if(e.cancelable){var t=this,i=this.el,n=this.options,o=n.preventOnFilter,a=e.type,r=e.touches&&e.touches[0]||e.pointerType&&"touch"===e.pointerType&&e,s=(r||e).target,l=e.target.shadowRoot&&(e.path&&e.path[0]||e.composedPath&&e.composedPath()[0])||s,c=n.filter;if(function(e){Ce.length=0;for(var t=e.getElementsByTagName("input"),i=t.length;i--;){var n=t[i];n.checked&&Ce.push(n)}}(i),!Y&&!(/mousedown|pointerdown/.test(a)&&0!==e.button||n.disabled||l.isContentEditable||(s=y(s,n.draggable,i,!1))&&s.animated||J===s)){if(te=N(s),ne=N(s,n.draggable),"function"==typeof c){if(c.call(this,e,s,this))return $({sortable:t,rootEl:l,name:"filter",targetEl:s,toEl:i,fromEl:i}),G("filter",t,{evt:e}),void(o&&e.cancelable&&e.preventDefault())}else if(c&&(c=c.split(",").some((function(n){if(n=y(l,n.trim(),i,!1))return $({sortable:t,rootEl:n,name:"filter",targetEl:s,fromEl:i,toEl:i}),G("filter",t,{evt:e}),!0}))))return void(o&&e.cancelable&&e.preventDefault());n.handle&&!y(l,n.handle,i,!1)||this._prepareDragStart(e,r,s)}}},_prepareDragStart:function(e,t,i){var n,o=this,a=o.el,r=o.options,s=a.ownerDocument;if(i&&!Y&&i.parentNode===a){var d=A(i);if(X=a,q=(Y=i).parentNode,Q=Y.nextSibling,J=i,ae=r.group,ze.dragged=Y,se={target:Y,clientX:(t||e).clientX,clientY:(t||e).clientY},de=se.clientX-d.left,ue=se.clientY-d.top,this._lastX=(t||e).clientX,this._lastY=(t||e).clientY,Y.style["will-change"]="all",n=function(){G("delayEnded",o,{evt:e}),ze.eventCanceled?o._onDrop():(o._disableDelayedDragEvents(),!p&&o.nativeDraggable&&(Y.draggable=!0),o._triggerDragStart(e,t),$({sortable:o,name:"choose",originalEvent:e}),k(Y,r.chosenClass,!0))},r.ignore.split(",").forEach((function(e){C(Y,e.trim(),Fe)})),m(s,"dragover",De),m(s,"mousemove",De),m(s,"touchmove",De),m(s,"mouseup",o._onDrop),m(s,"touchend",o._onDrop),m(s,"touchcancel",o._onDrop),p&&this.nativeDraggable&&(this.options.touchStartThreshold=4,Y.draggable=!0),G("delayStart",this,{evt:e}),!r.delay||r.delayOnTouchOnly&&!t||this.nativeDraggable&&(c||l))n();else{if(ze.eventCanceled)return void this._onDrop();m(s,"mouseup",o._disableDelayedDrag),m(s,"touchend",o._disableDelayedDrag),m(s,"touchcancel",o._disableDelayedDrag),m(s,"mousemove",o._delayedDragTouchMoveHandler),m(s,"touchmove",o._delayedDragTouchMoveHandler),r.supportPointer&&m(s,"pointermove",o._delayedDragTouchMoveHandler),o._dragStartTimer=setTimeout(n,r.delay)}}},_delayedDragTouchMoveHandler:function(e){var t=e.touches?e.touches[0]:e;Math.max(Math.abs(t.clientX-this._lastX),Math.abs(t.clientY-this._lastY))>=Math.floor(this.options.touchStartThreshold/(this.nativeDraggable&&window.devicePixelRatio||1))&&this._disableDelayedDrag()},_disableDelayedDrag:function(){Y&&Fe(Y),clearTimeout(this._dragStartTimer),this._disableDelayedDragEvents()},_disableDelayedDragEvents:function(){var e=this.el.ownerDocument;g(e,"mouseup",this._disableDelayedDrag),g(e,"touchend",this._disableDelayedDrag),g(e,"touchcancel",this._disableDelayedDrag),g(e,"mousemove",this._delayedDragTouchMoveHandler),g(e,"touchmove",this._delayedDragTouchMoveHandler),g(e,"pointermove",this._delayedDragTouchMoveHandler)},_triggerDragStart:function(e,t){t=t||"touch"==e.pointerType&&e,!this.nativeDraggable||t?this.options.supportPointer?m(document,"pointermove",this._onTouchMove):m(document,t?"touchmove":"mousemove",this._onTouchMove):(m(Y,"dragend",this),m(X,"dragstart",this._onDragStart));try{document.selection?Ve((function(){document.selection.empty()})):window.getSelection().removeAllRanges()}catch(e){}},_dragStarted:function(e,t){if(ve=!1,X&&Y){G("dragStarted",this,{evt:t}),this.nativeDraggable&&m(document,"dragover",He);var i=this.options;!e&&k(Y,i.dragClass,!1),k(Y,i.ghostClass,!0),ze.active=this,e&&this._appendGhost(),$({sortable:this,name:"start",originalEvent:t})}else this._nulling()},_emulateDragOver:function(){if(le){this._lastX=le.clientX,this._lastY=le.clientY,Me();for(var e=document.elementFromPoint(le.clientX,le.clientY),t=e;e&&e.shadowRoot&&(e=e.shadowRoot.elementFromPoint(le.clientX,le.clientY))!==t;)t=e;if(Y.parentNode[U]._isOutsideThisEl(e),t)do{if(t[U]&&t[U]._onDragOver({clientX:le.clientX,clientY:le.clientY,target:e,rootEl:t})&&!this.options.dragoverBubble)break;e=t}while(t=t.parentNode);Le()}},_onTouchMove:function(e){if(se){var t=this.options,i=t.fallbackTolerance,n=t.fallbackOffset,o=e.touches?e.touches[0]:e,a=K&&T(K,!0),r=K&&a&&a.a,s=K&&a&&a.d,l=Ae&&be&&R(be),c=(o.clientX-se.clientX+n.x)/(r||1)+(l?l[0]-Ie[0]:0)/(r||1),p=(o.clientY-se.clientY+n.y)/(s||1)+(l?l[1]-Ie[1]:0)/(s||1);if(!ze.active&&!ve){if(i&&Math.max(Math.abs(o.clientX-this._lastX),Math.abs(o.clientY-this._lastY))n.right+10||e.clientX<=n.right&&e.clientY>n.bottom&&e.clientX>=n.left:e.clientX>n.right&&e.clientY>n.top||e.clientX<=n.right&&e.clientY>n.bottom+10}(e,o,this)&&!g.animated){if(g===Y)return F(!1);if(g&&a===e.target&&(s=g),s&&(i=A(s)),!1!==Pe(X,a,Y,t,s,i,e,!!s))return P(),a.appendChild(Y),q=a,B(),F(!0)}else if(s.parentNode===a){i=A(s);var b,v,E,x=Y.parentNode!==a,T=!function(e,t,i){var n=i?e.left:e.top,o=i?e.right:e.bottom,a=i?e.width:e.height,r=i?t.left:t.top,s=i?t.right:t.bottom,l=i?t.width:t.height;return n===r||o===s||n+a/2===r+l/2}(Y.animated&&Y.toRect||t,s.animated&&s.toRect||i,o),C=o?"top":"left",S=w(s,"top","top")||w(Y,"top","top"),O=S?S.scrollTop:void 0;if(fe!==s&&(v=i[C],xe=!1,ke=!T&&l.invertSwap||x),b=function(e,t,i,n,o,a,r,s){var l=n?e.clientY:e.clientX,c=n?i.height:i.width,p=n?i.top:i.left,d=n?i.bottom:i.right,u=!1;if(!r)if(s&&gep+c*a/2:ld-ge)return-me}else if(l>p+c*(1-o)/2&&ld-c*a/2)?l>p+c/2?1:-1:0}(e,s,i,o,T?1:l.swapThreshold,null==l.invertedSwapThreshold?l.swapThreshold:l.invertedSwapThreshold,ke,fe===s),0!==b){var R=N(Y);do{R-=b,E=q.children[R]}while(E&&("none"===I(E,"display")||E===K))}if(0===b||E===s)return F(!1);fe=s,me=b;var M=s.nextElementSibling,L=!1,D=Pe(X,a,Y,t,s,i,e,L=1===b);if(!1!==D)return 1!==D&&-1!==D||(L=1===D),Te=!0,setTimeout(Ue,30),P(),L&&!M?a.appendChild(Y):s.parentNode.insertBefore(Y,L?M:s),S&&H(S,0,O-S.scrollTop),q=Y.parentNode,void 0===v||ke||(ge=Math.abs(v-A(s)[C])),B(),F(!0)}if(a.contains(Y))return F(!1)}return!1}function z(l,c){G(l,f,r({evt:e,isOwner:d,axis:o?"vertical":"horizontal",revert:n,dragRect:t,targetRect:i,canSort:u,fromSortable:h,target:s,completed:F,onMove:function(i,n){return Pe(X,a,Y,t,i,A(i),e,n)},changed:B},c))}function P(){z("dragOverAnimationCapture"),f.captureAnimationState(),f!==h&&h.captureAnimationState()}function F(t){return z("dragOverCompleted",{insertion:t}),t&&(d?p._hideClone():p._showClone(f),f!==h&&(k(Y,re?re.options.ghostClass:p.options.ghostClass,!1),k(Y,l.ghostClass,!0)),re!==f&&f!==ze.active?re=f:f===ze.active&&re&&(re=null),h===f&&(f._ignoreWhileAnimating=s),f.animateAll((function(){z("dragOverAnimationComplete"),f._ignoreWhileAnimating=null})),f!==h&&(h.animateAll(),h._ignoreWhileAnimating=null)),(s===Y&&!Y.animated||s===a&&!s.animated)&&(fe=null),l.dragoverBubble||e.rootEl||s===document||(Y.parentNode[U]._isOutsideThisEl(e.target),!t&&De(e)),!l.dragoverBubble&&e.stopPropagation&&e.stopPropagation(),m=!0}function B(){ie=N(Y),oe=N(Y,l.draggable),$({sortable:f,name:"change",toEl:a,newIndex:ie,newDraggableIndex:oe,originalEvent:e})}},_ignoreWhileAnimating:null,_offMoveEvents:function(){g(document,"mousemove",this._onTouchMove),g(document,"touchmove",this._onTouchMove),g(document,"pointermove",this._onTouchMove),g(document,"dragover",De),g(document,"mousemove",De),g(document,"touchmove",De)},_offUpEvents:function(){var e=this.el.ownerDocument;g(e,"mouseup",this._onDrop),g(e,"touchend",this._onDrop),g(e,"pointerup",this._onDrop),g(e,"touchcancel",this._onDrop),g(document,"selectstart",this)},_onDrop:function(e){var t=this.el,i=this.options;ie=N(Y),oe=N(Y,i.draggable),G("drop",this,{evt:e}),q=Y&&Y.parentNode,ie=N(Y),oe=N(Y,i.draggable),ze.eventCanceled||(ve=!1,ke=!1,xe=!1,clearInterval(this._loopId),clearTimeout(this._dragStartTimer),We(this.cloneId),We(this._dragStartId),this.nativeDraggable&&(g(document,"drop",this),g(t,"dragstart",this._onDragStart)),this._offMoveEvents(),this._offUpEvents(),d&&I(document.body,"user-select",""),I(Y,"transform",""),e&&(he&&(e.cancelable&&e.preventDefault(),!i.dropBubble&&e.stopPropagation()),K&&K.parentNode&&K.parentNode.removeChild(K),(X===q||re&&"clone"!==re.lastPutMode)&&Z&&Z.parentNode&&Z.parentNode.removeChild(Z),Y&&(this.nativeDraggable&&g(Y,"dragend",this),Fe(Y),Y.style["will-change"]="",he&&!ve&&k(Y,re?re.options.ghostClass:this.options.ghostClass,!1),k(Y,this.options.chosenClass,!1),$({sortable:this,name:"unchoose",toEl:q,newIndex:null,newDraggableIndex:null,originalEvent:e}),X!==q?(ie>=0&&($({rootEl:q,name:"add",toEl:q,fromEl:X,originalEvent:e}),$({sortable:this,name:"remove",toEl:q,originalEvent:e}),$({rootEl:q,name:"sort",toEl:q,fromEl:X,originalEvent:e}),$({sortable:this,name:"sort",toEl:q,originalEvent:e})),re&&re.save()):ie!==te&&ie>=0&&($({sortable:this,name:"update",toEl:q,originalEvent:e}),$({sortable:this,name:"sort",toEl:q,originalEvent:e})),ze.active&&(null!=ie&&-1!==ie||(ie=te,oe=ne),$({sortable:this,name:"end",toEl:q,originalEvent:e}),this.save())))),this._nulling()},_nulling:function(){G("nulling",this),X=Y=q=K=Q=Z=J=ee=se=le=he=ie=oe=te=ne=fe=me=re=ae=ze.dragged=ze.ghost=ze.clone=ze.active=null,Ce.forEach((function(e){e.checked=!0})),Ce.length=ce=pe=0},handleEvent:function(e){switch(e.type){case"drop":case"dragend":this._onDrop(e);break;case"dragenter":case"dragover":Y&&(this._onDragOver(e),function(e){e.dataTransfer&&(e.dataTransfer.dropEffect="move"),e.cancelable&&e.preventDefault()}(e));break;case"selectstart":e.preventDefault()}},toArray:function(){for(var e,t=[],i=this.el.children,n=0,o=i.length,a=this.options;n1&&(dt.forEach((function(e){n.addAnimationState({target:e,rect:ft?A(e):o}),F(e),e.fromRect=o,t.removeAnimationState(e)})),ft=!1,function(e,t){dt.forEach((function(i,n){var o=t.children[i.sortableIndex+(e?Number(n):0)];o?t.insertBefore(i,o):t.appendChild(i)}))}(!this.options.removeCloneOnHide,i))},dragOverCompleted:function(e){var t=e.sortable,i=e.isOwner,n=e.insertion,o=e.activeSortable,a=e.parentEl,r=e.putSortable,s=this.options;if(n){if(i&&o._hideClone(),ht=!1,s.animation&&dt.length>1&&(ft||!i&&!o.options.sort&&!r)){var l=A(lt,!1,!0,!0);dt.forEach((function(e){e!==lt&&(P(e,l),a.appendChild(e))})),ft=!0}if(!i)if(ft||vt(),dt.length>1){var c=pt;o._showClone(t),o.options.animation&&!pt&&c&&ut.forEach((function(e){o.addAnimationState({target:e,rect:ct}),e.fromRect=ct,e.thisAnimationDuration=null}))}else o._showClone(t)}},dragOverAnimationCapture:function(e){var t=e.dragRect,i=e.isOwner,n=e.activeSortable;if(dt.forEach((function(e){e.thisAnimationDuration=null})),n.options.animation&&!i&&n.multiDrag.isMultiDrag){ct=a({},t);var o=T(lt,!0);ct.top-=o.f,ct.left-=o.e}},dragOverAnimationComplete:function(){ft&&(ft=!1,vt())},drop:function(e){var t=e.originalEvent,i=e.rootEl,n=e.parentEl,o=e.sortable,a=e.dispatchSortableEvent,r=e.oldIndex,s=e.putSortable,l=s||this.sortable;if(t){var c=this.options,p=n.children;if(!mt)if(c.multiDragKey&&!this.multiDragKeyDown&&this._deselectMultiDrag(),k(lt,c.selectedClass,!~dt.indexOf(lt)),~dt.indexOf(lt))dt.splice(dt.indexOf(lt),1),rt=null,j({sortable:o,rootEl:i,name:"deselect",targetEl:lt,originalEvt:t});else{if(dt.push(lt),j({sortable:o,rootEl:i,name:"select",targetEl:lt,originalEvt:t}),t.shiftKey&&rt&&o.el.contains(rt)){var d,u,h=N(rt),f=N(lt);if(~h&&~f&&h!==f)for(f>h?(u=h,d=f):(u=f,d=h+1);u1){var m=A(lt),g=N(lt,":not(."+this.options.selectedClass+")");if(!ht&&c.animation&&(lt.thisAnimationDuration=null),l.captureAnimationState(),!ht&&(c.animation&&(lt.fromRect=m,dt.forEach((function(e){if(e.thisAnimationDuration=null,e!==lt){var t=ft?A(e):m;e.fromRect=t,l.addAnimationState({target:e,rect:t})}}))),vt(),dt.forEach((function(e){p[g]?n.insertBefore(e,p[g]):n.appendChild(e),g++})),r===N(lt))){var b=!1;dt.forEach((function(e){e.sortableIndex===N(e)||(b=!0)})),b&&a("update")}dt.forEach((function(e){F(e)})),l.animateAll()}st=l}(i===n||s&&"clone"!==s.lastPutMode)&&ut.forEach((function(e){e.parentNode&&e.parentNode.removeChild(e)}))}},nullingGlobal:function(){this.isMultiDrag=mt=!1,ut.length=0},destroyGlobal:function(){this._deselectMultiDrag(),g(document,"pointerup",this._deselectMultiDrag),g(document,"mouseup",this._deselectMultiDrag),g(document,"touchend",this._deselectMultiDrag),g(document,"keydown",this._checkKeyDown),g(document,"keyup",this._checkKeyUp)},_deselectMultiDrag:function(e){if(!(void 0!==mt&&mt||st!==this.sortable||e&&y(e.target,this.options.draggable,this.sortable.el,!1)||e&&0!==e.button))for(;dt.length;){var t=dt[0];k(t,this.options.selectedClass,!1),dt.shift(),j({sortable:this.sortable,rootEl:this.sortable.el,name:"deselect",targetEl:t,originalEvt:e})}},_checkKeyDown:function(e){e.key===this.options.multiDragKey&&(this.multiDragKeyDown=!0)},_checkKeyUp:function(e){e.key===this.options.multiDragKey&&(this.multiDragKeyDown=!1)}},a(e,{pluginName:"multiDrag",utils:{select:function(e){var t=e.parentNode[U];t&&t.options.multiDrag&&!~dt.indexOf(e)&&(st&&st!==t&&(st.multiDrag._deselectMultiDrag(),st=t),k(e,t.options.selectedClass,!0),dt.push(e))},deselect:function(e){var t=e.parentNode[U],i=dt.indexOf(e);t&&t.options.multiDrag&&~i&&(k(e,t.options.selectedClass,!1),dt.splice(i,1))}},eventProperties:function(){var e,t=this,i=[],n=[];return dt.forEach((function(e){var o;i.push({multiDragElement:e,index:e.sortableIndex}),o=ft&&e!==lt?-1:ft?N(e,":not(."+t.options.selectedClass+")"):N(e),n.push({multiDragElement:e,index:o})})),{items:(e=dt,function(e){if(Array.isArray(e)){for(var t=0,i=new Array(e.length);t1&&(e=e.charAt(0).toUpperCase()+e.substr(1)),e}}})}function bt(e,t){ut.forEach((function(i,n){var o=t.children[i.sortableIndex+(e?Number(n):0)];o?t.insertBefore(i,o):t.appendChild(i)}))}function vt(){dt.forEach((function(e){e!==lt&&e.parentNode&&e.parentNode.removeChild(e)}))}ze.mount(new function(){function e(){for(var e in this.defaults={scroll:!0,scrollSensitivity:30,scrollSpeed:10,bubbleScroll:!0},this)"_"===e.charAt(0)&&"function"==typeof this[e]&&(this[e]=this[e].bind(this))}return e.prototype={dragStarted:function(e){var t=e.originalEvent;this.sortable.nativeDraggable?m(document,"dragover",this._handleAutoScroll):this.options.supportPointer?m(document,"pointermove",this._handleFallbackAutoScroll):t.touches?m(document,"touchmove",this._handleFallbackAutoScroll):m(document,"mousemove",this._handleFallbackAutoScroll)},dragOverCompleted:function(e){var t=e.originalEvent;this.options.dragOverBubble||t.rootEl||this._handleAutoScroll(t)},drop:function(){this.sortable.nativeDraggable?g(document,"dragover",this._handleAutoScroll):(g(document,"pointermove",this._handleFallbackAutoScroll),g(document,"touchmove",this._handleFallbackAutoScroll),g(document,"mousemove",this._handleFallbackAutoScroll)),Ze(),Je(),clearTimeout(E),E=void 0},nulling:function(){qe=Ge=je=Qe=Ke=$e=Ye=null,Xe.length=0},_handleFallbackAutoScroll:function(e){this._handleAutoScroll(e,!0)},_handleAutoScroll:function(e,t){var i=this,n=(e.touches?e.touches[0]:e).clientX,o=(e.touches?e.touches[0]:e).clientY,a=document.elementFromPoint(n,o);if(qe=e,t||c||l||d){tt(e,this.options,a,t);var r=M(a,!0);!Qe||Ke&&n===$e&&o===Ye||(Ke&&Ze(),Ke=setInterval((function(){var a=M(document.elementFromPoint(n,o),!0);a!==r&&(r=a,Je()),tt(e,i.options,a,t)}),10),$e=n,Ye=o)}else{if(!this.options.bubbleScroll||M(a,!0)===S())return void Je();tt(e,this.options,M(a,!1),!1)}}},a(e,{pluginName:"scroll",initializeByDefault:!0})}),ze.mount(ot,nt);const yt=ze},72:e=>{"use strict";var t=[];function i(e){for(var i=-1,n=0;n{"use strict";var t={};e.exports=function(e,i){var n=function(e){if(void 0===t[e]){var i=document.querySelector(e);if(window.HTMLIFrameElement&&i instanceof window.HTMLIFrameElement)try{i=i.contentDocument.head}catch(e){i=null}t[e]=i}return t[e]}(e);if(!n)throw new Error("Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid.");n.appendChild(i)}},540:e=>{"use strict";e.exports=function(e){var t=document.createElement("style");return e.setAttributes(t,e.attributes),e.insert(t,e.options),t}},56:(e,t,i)=>{"use strict";e.exports=function(e){var t=i.nc;t&&e.setAttribute("nonce",t)}},825:e=>{"use strict";e.exports=function(e){if("undefined"==typeof document)return{update:function(){},remove:function(){}};var t=e.insertStyleElement(e);return{update:function(i){!function(e,t,i){var n="";i.supports&&(n+="@supports (".concat(i.supports,") {")),i.media&&(n+="@media ".concat(i.media," {"));var o=void 0!==i.layer;o&&(n+="@layer".concat(i.layer.length>0?" ".concat(i.layer):""," {")),n+=i.css,o&&(n+="}"),i.media&&(n+="}"),i.supports&&(n+="}");var a=i.sourceMap;a&&"undefined"!=typeof btoa&&(n+="\n/*# sourceMappingURL=data:application/json;base64,".concat(btoa(unescape(encodeURIComponent(JSON.stringify(a))))," */")),t.styleTagTransform(n,e,t.options)}(t,e,i)},remove:function(){!function(e){if(null===e.parentNode)return!1;e.parentNode.removeChild(e)}(t)}}}},113:e=>{"use strict";e.exports=function(e,t){if(t.styleSheet)t.styleSheet.cssText=e;else{for(;t.firstChild;)t.removeChild(t.firstChild);t.appendChild(document.createTextNode(e))}}},432:function(e,t,i){var n;"undefined"!=typeof self&&self,n=function(e){return function(e){var t={};function i(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,i),o.l=!0,o.exports}return i.m=e,i.c=t,i.d=function(e,t,n){i.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},i.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,t){if(1&t&&(e=i(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(i.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)i.d(n,o,function(t){return e[t]}.bind(null,o));return n},i.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(t,"a",t),t},i.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},i.p="",i(i.s="fb15")}({"01f9":function(e,t,i){"use strict";var n=i("2d00"),o=i("5ca1"),a=i("2aba"),r=i("32e9"),s=i("84f2"),l=i("41a0"),c=i("7f20"),p=i("38fd"),d=i("2b4c")("iterator"),u=!([].keys&&"next"in[].keys()),h="keys",f="values",m=function(){return this};e.exports=function(e,t,i,g,b,v,y){l(i,t,g);var E,x,k,I=function(e){if(!u&&e in A)return A[e];switch(e){case h:case f:return function(){return new i(this,e)}}return function(){return new i(this,e)}},T=t+" Iterator",C=b==f,S=!1,A=e.prototype,w=A[d]||A["@@iterator"]||b&&A[b],O=w||I(b),_=b?C?I("entries"):O:void 0,N="Array"==t&&A.entries||w;if(N&&(k=p(N.call(new e)))!==Object.prototype&&k.next&&(c(k,T,!0),n||"function"==typeof k[d]||r(k,d,m)),C&&w&&w.name!==f&&(S=!0,O=function(){return w.call(this)}),n&&!y||!u&&!S&&A[d]||r(A,d,O),s[t]=O,s[T]=m,b)if(E={values:C?O:I(f),keys:v?O:I(h),entries:_},y)for(x in E)x in A||a(A,x,E[x]);else o(o.P+o.F*(u||S),t,E);return E}},"02f4":function(e,t,i){var n=i("4588"),o=i("be13");e.exports=function(e){return function(t,i){var a,r,s=String(o(t)),l=n(i),c=s.length;return l<0||l>=c?e?"":void 0:(a=s.charCodeAt(l))<55296||a>56319||l+1===c||(r=s.charCodeAt(l+1))<56320||r>57343?e?s.charAt(l):a:e?s.slice(l,l+2):r-56320+(a-55296<<10)+65536}}},"0390":function(e,t,i){"use strict";var n=i("02f4")(!0);e.exports=function(e,t,i){return t+(i?n(e,t).length:1)}},"0bfb":function(e,t,i){"use strict";var n=i("cb7c");e.exports=function(){var e=n(this),t="";return e.global&&(t+="g"),e.ignoreCase&&(t+="i"),e.multiline&&(t+="m"),e.unicode&&(t+="u"),e.sticky&&(t+="y"),t}},"0d58":function(e,t,i){var n=i("ce10"),o=i("e11e");e.exports=Object.keys||function(e){return n(e,o)}},1495:function(e,t,i){var n=i("86cc"),o=i("cb7c"),a=i("0d58");e.exports=i("9e1e")?Object.defineProperties:function(e,t){o(e);for(var i,r=a(t),s=r.length,l=0;s>l;)n.f(e,i=r[l++],t[i]);return e}},"214f":function(e,t,i){"use strict";i("b0c5");var n=i("2aba"),o=i("32e9"),a=i("79e5"),r=i("be13"),s=i("2b4c"),l=i("520a"),c=s("species"),p=!a((function(){var e=/./;return e.exec=function(){var e=[];return e.groups={a:"7"},e},"7"!=="".replace(e,"$")})),d=function(){var e=/(?:)/,t=e.exec;e.exec=function(){return t.apply(this,arguments)};var i="ab".split(e);return 2===i.length&&"a"===i[0]&&"b"===i[1]}();e.exports=function(e,t,i){var u=s(e),h=!a((function(){var t={};return t[u]=function(){return 7},7!=""[e](t)})),f=h?!a((function(){var t=!1,i=/a/;return i.exec=function(){return t=!0,null},"split"===e&&(i.constructor={},i.constructor[c]=function(){return i}),i[u](""),!t})):void 0;if(!h||!f||"replace"===e&&!p||"split"===e&&!d){var m=/./[u],g=i(r,u,""[e],(function(e,t,i,n,o){return t.exec===l?h&&!o?{done:!0,value:m.call(t,i,n)}:{done:!0,value:e.call(i,t,n)}:{done:!1}})),b=g[0],v=g[1];n(String.prototype,e,b),o(RegExp.prototype,u,2==t?function(e,t){return v.call(e,this,t)}:function(e){return v.call(e,this)})}}},"230e":function(e,t,i){var n=i("d3f4"),o=i("7726").document,a=n(o)&&n(o.createElement);e.exports=function(e){return a?o.createElement(e):{}}},"23c6":function(e,t,i){var n=i("2d95"),o=i("2b4c")("toStringTag"),a="Arguments"==n(function(){return arguments}());e.exports=function(e){var t,i,r;return void 0===e?"Undefined":null===e?"Null":"string"==typeof(i=function(e,t){try{return e[t]}catch(e){}}(t=Object(e),o))?i:a?n(t):"Object"==(r=n(t))&&"function"==typeof t.callee?"Arguments":r}},2621:function(e,t){t.f=Object.getOwnPropertySymbols},"2aba":function(e,t,i){var n=i("7726"),o=i("32e9"),a=i("69a8"),r=i("ca5a")("src"),s=i("fa5b"),l="toString",c=(""+s).split(l);i("8378").inspectSource=function(e){return s.call(e)},(e.exports=function(e,t,i,s){var l="function"==typeof i;l&&(a(i,"name")||o(i,"name",t)),e[t]!==i&&(l&&(a(i,r)||o(i,r,e[t]?""+e[t]:c.join(String(t)))),e===n?e[t]=i:s?e[t]?e[t]=i:o(e,t,i):(delete e[t],o(e,t,i)))})(Function.prototype,l,(function(){return"function"==typeof this&&this[r]||s.call(this)}))},"2aeb":function(e,t,i){var n=i("cb7c"),o=i("1495"),a=i("e11e"),r=i("613b")("IE_PROTO"),s=function(){},l="prototype",c=function(){var e,t=i("230e")("iframe"),n=a.length;for(t.style.display="none",i("fab2").appendChild(t),t.src="javascript:",(e=t.contentWindow.document).open(),e.write(" diff --git a/src/frontend/locale/manage/en-US.json b/src/frontend/locale/manage/en-US.json index c85041c4f5e..a129dbd7ec8 100644 --- a/src/frontend/locale/manage/en-US.json +++ b/src/frontend/locale/manage/en-US.json @@ -256,5 +256,11 @@ "同步成功": "Sync successfully", "XXX拥有的权限均已过期,无需交接,确定移出用户并清理过期权限吗?": "All permissions of {0} have expired, no need for transfer. Are you sure you want to remove the user and clean up the expired permissions?", "确定": "Confirm", - "请选择项目": "Please select a project" + "请选择项目": "Please select a project", + "过期时间": "Expiration Time", + "用户组名称": "User Group Name", + "未来 X 小时": "Next {0} Hours", + "未来 X 天": "Next {0} Days", + "过去 X 小时": "Last {0} Hours", + "过去 X 天": "Last {0} Days" } diff --git a/src/frontend/locale/manage/zh-CN.json b/src/frontend/locale/manage/zh-CN.json index 51ae308f019..bc227da853f 100644 --- a/src/frontend/locale/manage/zh-CN.json +++ b/src/frontend/locale/manage/zh-CN.json @@ -257,5 +257,11 @@ "同步成功": "同步成功", "XXX拥有的权限均已过期,无需交接,确定移出用户并清理过期权限吗?": "{0} 拥有的权限均已过期,无需交接,确定移出用户并清理过期权限吗?", "确定": "确定", - "请选择项目": "请选择项目" + "请选择项目": "请选择项目", + "过期时间": "过期时间", + "用户组名称": "用户组名称", + "未来 X 小时": "未来 {0} 小时", + "未来 X 天": "未来 {0} 天", + "过去 X 小时": "过去 {0} 小时", + "过去 X 天": "过去 {0} 天" } diff --git a/support-files/sql/1001_ci_metrics_ddl_mysql.sql b/support-files/sql/1001_ci_metrics_ddl_mysql.sql index bf96aa69324..412a4021fa2 100644 --- a/support-files/sql/1001_ci_metrics_ddl_mysql.sql +++ b/support-files/sql/1001_ci_metrics_ddl_mysql.sql @@ -317,4 +317,14 @@ CREATE TABLE IF NOT EXISTS `T_PROJECT_BUILD_SUMMARY_DAILY` INDEX `IDX_PRODUCT_ID`(`PRODUCT_ID`,`THE_DATE`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT ='项目每日构建汇总表'; +CREATE TABLE IF NOT EXISTS `T_PROJECT_USER_OPERATE_DAILY` ( + `PROJECT_ID` varchar(64) NOT NULL COMMENT '项目ID', + `USER_ID` varchar(64) NOT NULL COMMENT '用户ID', + `THE_DATE` date NOT NULL COMMENT '日期', + `OPERATE` varchar(64) NOT NULL COMMENT '操作', + `OPERATE_COUNT` int(11) NOT NULL DEFAULT '0' COMMENT '操作次数', + `CREATE_TIME` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`PROJECT_ID`,`USER_ID`, `THE_DATE`, `OPERATE`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='项目用户日常操作记录表'; + SET FOREIGN_KEY_CHECKS = 1;