Compare commits

...

291 Commits

Author SHA1 Message Date
  chenzhihang 9eabe7d9d4 首页接口 1 year ago
  chenzhihang c8a4255987 流水线模板优化 1 year ago
  chenzhihang 6ccf797811 首页接口 1 year ago
  chenzhihang f390e2b797 首页接口 1 year ago
  chenzhihang f22dc6c2d6 服务版本优化 1 year ago
  chenzhihang 530cfdf324 流水线模板优化 1 year ago
  chenzhihang 1614f73d15 首页统计接口 1 year ago
  chenzhihang 254aa5f952 优化 1 year ago
  chenzhihang b35276f83a 优化 1 year ago
  chenzhihang a117d92e7e Merge branch 'dev-opt' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev-opt 1 year ago
  chenzhihang 748adf10c4 优化 1 year ago
  zchzch 65c2f4ea80 Merge pull request '数据标注数据集导出为zip且包含源文件' (#272) from dev-zch into dev-opt 1 year ago
  chenzhihang 611d7b7082 优化 1 year ago
  chenzhihang a407c54ec5 优化 1 year ago
  chenzhihang fc7a4f7c17 优化查询代码配置 1 year ago
  chenzhihang 11ce36c0ad Merge branch 'dev-opt' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev-opt 1 year ago
  chenzhihang d0897f96cb 优化查询代码配置 1 year ago
  ddmte32 efe222d122 开发环境可选代码配置 1 year ago
  ddmte32 3bd07c7428 开发环境可选代码配置 1 year ago
  ddmte32 fbeb6eafb1 开发环境可选代码配置 1 year ago
  ddmte32 7d92b7050c 开发环境可选代码配置 1 year ago
  ddmte32 af79d15a4a 开发环境可选代码配置 1 year ago
  chenzhihang 8311b6eea0 增加修改资源版本功能 1 year ago
  chenzhihang 5561776030 增加修改资源版本功能 1 year ago
  chenzhihang 49eaf6f783 增加修改资源版本功能 1 year ago
  chenzhihang 550b3126af 增加修改资源版本功能 1 year ago
  chenzhihang 3047f793e6 增加修改资源版本功能 1 year ago
  chenzhihang 9f1685eb2c 增加修改资源版本功能 1 year ago
  chenzhihang 060870943a 增加修改资源版本功能 1 year ago
  chenzhihang daffbe1733 增加修改资源版本功能 1 year ago
  chenzhihang 9edac46179 增加修改资源版本功能 1 year ago
  zhanchunhu c286cd5870 修改工具类 1 year ago
  zhanchunhu 3a8ae0e131 修复导出到数据集的zip包打不开的bug 1 year ago
  chenzhihang a83e10c664 优化导出数据集bug 1 year ago
  chenzhihang 6d2a84aa93 优化dvc测试 1 year ago
  chenzhihang 284115b499 优化dvc测试 1 year ago
  chenzhihang fb863bf1de 优化dvc测试 1 year ago
  chenzhihang 94555dea40 优化dvc测试 1 year ago
  chenzhihang 089b13aafb 优化dvc测试 1 year ago
  chenzhihang 163e9bab8d 优化dvc测试 1 year ago
  chenzhihang 2f618703e4 优化dvc测试 1 year ago
  chenzhihang 24798fa4dd 优化dvc测试 1 year ago
  chenzhihang 879f1b7966 Merge remote-tracking branch 'origin/dev-check' into dev-opt 1 year ago
  chenzhihang dfefc792c4 优化dvc测试 1 year ago
  chenzhihang 7fad3190eb 优化dvc测试 1 year ago
  chenzhihang cc567ce45a 优化dvc测试 1 year ago
  chenzhihang 7985f9f1a1 优化dvc测试 1 year ago
  chenzhihang e8cfaaea05 优化dvc测试 1 year ago
  chenzhihang ec6dadd55c 优化dvc测试 1 year ago
  chenzhihang 561d414514 优化dvc测试 1 year ago
  chenzhihang bf77aff1dd 优化dvc测试 1 year ago
  chenzhihang 4670b2bb5e 优化dvc测试 1 year ago
  chenzhihang 3ea8756d61 优化dvc测试 1 year ago
  chenzhihang fcb6664e09 Merge branch 'dev-check' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev-check 1 year ago
  chenzhihang b75dbf88df 优化mysql连接超时重试 1 year ago
  cp3hnu 05860a9325 Merge pull request '合并' (#265) from dev-zw into dev-check 1 year ago
  zhaowei e8af394523 fix: 超参数寻优添加可视化对比iframe 1 year ago
  cp3hnu 1abf90da28 Merge pull request '合并' (#264) from dev-zw into dev-check 1 year ago
  zhaowei eafca94b60 fix: 导出到数据集添加is_public参数 1 year ago
  cp3hnu bb26e7291f Merge pull request '合并' (#263) from dev-zw into dev-check 1 year ago
  zhaowei cd4071149b fix: 导出到数据集添加owner参数 1 year ago
  zhaowei f11582bc64 fix: 数据集和模型回退时分页没有设置 1 year ago
  cp3hnu a1f9d10617 Merge pull request '合并' (#262) from dev-zw into dev-check 1 year ago
  zhaowei fddb63d293 fix: 流水线模板配置参数修改,历史实验实例配置参数变换 1 year ago
  cp3hnu 6b8eb0fb33 Merge pull request '合并' (#261) from dev-zw into dev-check 1 year ago
  chenzhihang 5a402acd44 优化实验 1 year ago
  chenzhihang 8803f78bde 优化实验 1 year ago
  chenzhihang 9baf28b312 Merge branch 'dev-check' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev-check 1 year ago
  chenzhihang 0f316b2b80 新增代码,服务统计 1 year ago
  chenzhihang 0b0ccd26d5 文件分片上传,断点续传 1 year ago
  zhaowei fd7f0008c8 Merge branch 'dev-check' into dev-zw 1 year ago
  cp3hnu 7257c82f0e Merge pull request '合并' (#260) from dev-zw into dev-check 1 year ago
  zhaowei 531beedac6 chore: merge 1 year ago
  zhaowei 0e8efbb692 fix: 工作空间添加代码配置和服务数量 1 year ago
  chenzhihang 742be0bb03 Merge branch 'dev-check' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev-check 1 year ago
  zhaowei 512aa61b05 fix: 调整工作空间快速开始按钮的偏移 1 year ago
  chenzhihang b6273098c6 新增代码,服务统计 1 year ago
  zhaowei 921bc1d49d fix: 调整工作空间样式 1 year ago
  zhaowei 78eb6ee12f fix: 修复控制参数转成object的问题 1 year ago
  cp3hnu 57a0ec1040 Merge pull request '合并' (#258) from dev-zw into dev-check 1 year ago
  cp3hnu 154c834223 Merge pull request '合并' (#257) from dev-zw into dev-check 1 year ago
  zhaowei 560ff3411c fix: 实验隐藏流水线控制参数 1 year ago
  zhaowei bd269cffbf fix: 流水线隐藏参数 1 year ago
  cp3hnu 6646aff405 Merge pull request '合并' (#256) from dev-zw into dev-check 1 year ago
  chenzhihang 944c3cc919 文件分片上传,断点续传 1 year ago
  zhaowei 1b497e1743 fix: 自动机器学习添加算法描述 1 year ago
  cp3hnu ff69531b22 Merge pull request '合并' (#255) from dev-zw into dev-check 1 year ago
  chenzhihang 399d611607 优化用户 1 year ago
  chenzhihang 8e024c9813 优化用户 1 year ago
  chenzhihang 5c9f59887c 优化实验 1 year ago
  chenzhihang b3e5eb08e9 优化实验 1 year ago
  chenzhihang 6efe25a3f0 Merge branch 'dev-check' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev-check 1 year ago
  chenzhihang 548016091e 优化实验 1 year ago
  cp3hnu 5d61ade863 Merge pull request '合并' (#254) from dev-zw into dev-check 1 year ago
  zhaowei 458621fe91 fix: 自主机器学习改为自动机器学习 1 year ago
  chenzhihang 55b49047c3 Merge branch 'dev-check' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev-check 1 year ago
  chenzhihang 597e16f81f 优化实验 1 year ago
  zhaowei aef75f4a37 fix: 修改角色 1 year ago
  cp3hnu 153b7de3ec Merge pull request '合并' (#253) from dev-zw into dev-check 1 year ago
  chenzhihang 4f415719f8 优化主动学习 1 year ago
  chenzhihang aa410acdfe 优化 1 year ago
  chenzhihang 4252f1710d 优化项目分页查询 1 year ago
  chenzhihang 23af257179 优化项目分页查询 1 year ago
  chenzhihang f0b70feca7 优化项目分页查询 1 year ago
  chenzhihang b014c9ce92 优化项目分页查询 1 year ago
  chenzhihang 976fb1dce5 优化项目分页查询 1 year ago
  chenzhihang 3a5845b623 优化项目分页查询 1 year ago
  chenzhihang b811bb51cd 优化项目分页查询 1 year ago
  chenzhihang 90c958b974 优化项目分页查询 1 year ago
  chenzhihang 19a4d6aed3 优化项目分页查询 1 year ago
  chenzhihang 6e27e5da0d 优化项目分页查询 1 year ago
  chenzhihang 3d0ea6603f 优化项目分页查询 1 year ago
  chenzhihang dc7e8dc801 优化项目分页查询 1 year ago
  chenzhihang 8e1f5fc587 优化 1 year ago
  chenzhihang 746314e5d2 优化 1 year ago
  chenzhihang d169d3d9db 优化 1 year ago
  chenzhihang 7a939f3f29 优化 1 year ago
  chenzhihang dc6cf41651 Merge branch 'dev-check' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev-check 1 year ago
  chenzhihang 17c1c86043 测试 1 year ago
  cp3hnu a46a948760 Merge pull request '合并' (#252) from dev-zw into dev-check 1 year ago
  zhaowei 40ca029363 fix:自动机器学习添加mlp算法 1 year ago
  zhaowei 767a208732 fix:工作空间实验运行时长动态变化 1 year ago
  cp3hnu b314476ac3 Merge pull request '合并' (#251) from dev-zw into dev-check 1 year ago
  chenzhihang 39c0c2c01a 优化 1 year ago
  chenzhihang 873dd0ed5e Merge branch 'dev-check' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev-check 1 year ago
  chenzhihang 2a101d7f47 优化 1 year ago
  cp3hnu bcff86269b Merge pull request '合并' (#250) from dev-zw into dev-check 1 year ago
  zhaowei b2b74686ca fix: 自动机器学习创建时间改为更新时间 1 year ago
  chenzhihang 316cca31a4 优化排序 1 year ago
  zhaowei d1c41934b0 fix: 预测有两个loading 1 year ago
  zhaowei 274b8612e9 fix: 流水线模型部署服务版本验证 1 year ago
  chenzhihang df7460a01b Merge branch 'dev-check' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev-check 1 year ago
  chenzhihang 48d46a0723 优化用户 1 year ago
  zhaowei 8f72953683 fix: 服务日志样式错误 1 year ago
  cp3hnu 62bd8049ba Merge pull request '合并' (#249) from dev-zw into dev-check 1 year ago
  zhaowei 1ec43a60cf fix: 添加预测加载状态 1 year ago
  cp3hnu c42cf77939 Merge pull request '合并' (#248) from dev-zw into dev-check 1 year ago
  chenzhihang 85c8a3e6dc Merge branch 'dev-check' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev-check 1 year ago
  chenzhihang 96bf3351db 优化积分扣除结束 1 year ago
  cp3hnu b2ac5877d0 Merge pull request '合并' (#247) from dev-zw into dev-check 1 year ago
  zhaowei 0bace90a23 fix: 全局参数删除脱敏的配置 1 year ago
  cp3hnu 4c73d4339d Merge branch 'dev-zw' of code.gitlink.org.cn:ci4s/ci4sManagement-cloud into dev-zw 1 year ago
  cp3hnu ef9a78b167 fix: 最近更新时间 1 year ago
  chenzhihang 68ad21fadf 优化 1 year ago
  chenzhihang 696f939295 优化 1 year ago
  chenzhihang cc98a699d4 优化 1 year ago
  chenzhihang c11c728c71 Merge branch 'dev-check' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev-check 1 year ago
  chenzhihang 31334c3a11 优化 1 year ago
  cp3hnu 3066853aeb Merge pull request '合并' (#246) from dev-zw into dev-check 1 year ago
  zhaowei f3f9846dff fix: 模型指标对比图错误 1 year ago
  cp3hnu 7715ee272d Merge pull request '合并' (#245) from dev-zw into dev-check 1 year ago
  zhaowei 65c588ac8a fix: 集成模型数量>=1 1 year ago
  zhaowei 34e2b8bb05 fix: mlp 显示成tablenet 1 year ago
  chenzhihang ff07d4c4a8 优化用户 1 year ago
  chenzhihang 6ad1910cb0 Merge branch 'dev-check' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev-check 1 year ago
  chenzhihang 879c2e5802 优化更新ray 1 year ago
  chenzhihang 50dce4003f 优化用户 1 year ago
  cp3hnu e4385544b1 Merge pull request '合并dev-zw' (#244) from dev-zw into dev-check 1 year ago
  cp3hnu 4a6f4d2120 fix: 退出登录获取label-studio地址 1 year ago
  cp3hnu 8ef236f5b4 feat: 修改服务调用指南 1 year ago
  chenzhihang a5914e67c8 优化 1 year ago
  chenzhihang cccb2ae8c2 测试登录 1 year ago
  chenzhihang e5978fde0c 测试登录 1 year ago
  chenzhihang e76a0d7544 测试登录 1 year ago
  chenzhihang d19f0cfa82 优化 1 year ago
  chenzhihang d3508e6eba Merge branch 'dev-check' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev-check 1 year ago
  chenzhihang b42a63f209 优化 1 year ago
  cp3hnu 603b943699 Merge pull request '合并dev' (#243) from dev into dev-check 1 year ago
  cp3hnu c8fc258089 Merge pull request '合并dev-zw' (#242) from dev-zw into dev 1 year ago
  cp3hnu 53fe983462 fix: 用户管理界面无法退出登录 1 year ago
  chenzhihang 1b63a74ce0 优化 1 year ago
  chenzhihang c4a3275358 优化 1 year ago
  chenzhihang fc026e9d15 优化 1 year ago
  chenzhihang 9d3e0ad5f3 Merge branch 'dev-check' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev-check 1 year ago
  chenzhihang a2555f6ac3 优化 1 year ago
  cp3hnu fefc545dad Merge pull request '合并dev' (#241) from dev into dev-check 1 year ago
  cp3hnu b419ab9485 Merge pull request '合并dev-zw' (#240) from dev-zw into dev 1 year ago
  cp3hnu 16d4b476f2 fix: 分配用户创建时间为null 1 year ago
  cp3hnu b8049721df fix: 退出登录两次 1 year ago
  chenzhihang 272ec6ef97 优化 1 year ago
  chenzhihang 46eff25e01 优化 1 year ago
  chenzhihang dcc591cacd Merge branch 'dev-check' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev-check 1 year ago
  chenzhihang 4da7042f61 优化 1 year ago
  chenzhihang 236ac4da0f 优化 1 year ago
  cp3hnu 2b0c11525b test: 添加 giturl 的测试 1 year ago
  cp3hnu 67a2b41240 Merge pull request '合并dev' (#239) from dev into dev-check 1 year ago
  cp3hnu 7de2295191 Merge pull request '合并dev-zw' (#238) from dev-zw into dev 1 year ago
  cp3hnu 0ac624ed2a fix: 实验无法查看更多 1 year ago
  chenzhihang 277ed0a710 优化积分扣除结束 1 year ago
  chenzhihang 2aaa8a9fe7 Merge branch 'dev-check' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev-check 1 year ago
  chenzhihang 19b797189d 优化积分扣除 1 year ago
  cp3hnu 2d5538251e Merge pull request '合并dev' (#237) from dev into dev-check 1 year ago
  cp3hnu b142d5985d Merge pull request '合并dev-zw' (#236) from dev-zw into dev 1 year ago
  cp3hnu 14a9629167 fix: 添加iframe 加载失败日志 1 year ago
  chenzhihang 48fdf61dff 优化积分扣除 1 year ago
  chenzhihang 48851a2d4b 优化积分查询 1 year ago
  cp3hnu 5fe010f52d fix: 服务只有运行中才显示预测 1 year ago
  cp3hnu e6f74dc513 Merge pull request '合并dev' (#235) from dev into dev-check 1 year ago
  cp3hnu e4ffcea914 Merge pull request '合并dev' (#234) from dev-zw into dev 1 year ago
  cp3hnu 44673f39ed fix: 实验状态不同步 1 year ago
  chenzhihang 88227a85cb 优化运行开发环境 1 year ago
  chenzhihang a0d17e6dd3 优化查询pod状态 1 year ago
  chenzhihang adf3b8d02a 优化查询pod状态 1 year ago
  chenzhihang 596aa80315 优化查询pod状态 1 year ago
  chenzhihang 7b146651c7 优化创建pod 1 year ago
  chenzhihang eb50e76a54 优化实验状态查询 1 year ago
  chenzhihang a2f1c0532b 优化dvc 1 year ago
  chenzhihang 694f142b3f 优化积分扣除,优化dvc 1 year ago
  chenzhihang cdceefcb24 优化实验状态查询 1 year ago
  chenzhihang 1cfbd5185c 优化积分扣除 1 year ago
  chenzhihang bee9f43762 优化 1 year ago
  chenzhihang aa3909a047 优化 1 year ago
  chenzhihang 111ade2f49 优化项目排序 1 year ago
  chenzhihang 6670aa0658 优化项目排序 1 year ago
  chenzhihang 6748c46d6f 优化项目排序 1 year ago
  chenzhihang d6ed84ac59 Merge branch 'dev-check' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev-check 1 year ago
  chenzhihang 5a7a188478 优化积分更新 1 year ago
  cp3hnu 5d641cb61b Merge pull request '合并dev' (#233) from dev into dev-check 1 year ago
  chenzhihang 6809f62b9c 优化 1 year ago
  cp3hnu 8110739002 Merge pull request '合并dev-zw' (#232) from dev-zw into dev 1 year ago
  cp3hnu 1fd40f927c fix: 用户账号支持4-15位 1 year ago
  chenzhihang b02d15662f Merge branch 'dev-check' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev-check 1 year ago
  chenzhihang 20b1e78df7 优化 1 year ago
  cp3hnu d7ddcefb96 fix: 自动机器学习创建时间有误 1 year ago
  cp3hnu 1f08241ca9 Merge pull request '合并dev' (#231) from dev into dev-check 1 year ago
  cp3hnu b4873208c2 Merge pull request '合并dev-zw' (#230) from dev-zw into dev 1 year ago
  chenzhihang 25381c26ae 优化 1 year ago
  chenzhihang 567263b11a Merge remote-tracking branch 'origin/dev' into dev-check 1 year ago
  chenzhihang cdb3fafd14 Merge branch 'dev' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev 1 year ago
  chenzhihang 25dec31eaa 优化 1 year ago
  cp3hnu 5b8a10006d feat: 删除机器人 1 year ago
  zhaowei b0d8a19975 feat: 快速开始删除开发环境 1 year ago
  cp3hnu f5d4158532 Merge pull request '合并dev' (#229) from dev into dev-check 1 year ago
  cp3hnu dcb73eacf9 Merge pull request '合并dev-zw' (#228) from dev-zw into dev 1 year ago
  fanshuai 84dbb1e8fb 修改BUG 【开发环境】新创建开发环境处于列表底部,未处于列表前列 1 year ago
  cp3hnu 95cfbefda4 feat: 自动机器学习运行&获取tensorboard状态 1 year ago
  fanshuai ae86b2a835 Merge remote-tracking branch 'origin/dev-check' into dev-check 1 year ago
  fanshuai 68ee173591 修改BUG 【工作空间】AI资产卡片数据统计错误 1 year ago
  chenzhihang 71edeb6922 优化状态更新 1 year ago
  chenzhihang a4b82fb9f5 优化状态更新 1 year ago
  cp3hnu 9a89988e95 fix: 删除“构建中”状态镜像版本,构建成功/失败状态返回后重新显示在列表 1 year ago
  cp3hnu b9f4c48ea6 fix: 位于大于筛选结果的页码,点击左侧边栏筛选,页面提示暂无数据 1 year ago
  chenzhihang 8270406d3c 优化http请求 1 year ago
  chenzhihang 870fbce684 优化查询代码配置bug 1 year ago
  cp3hnu 7a4852908b fix: 添加镜像版本描述 1 year ago
  chenzhihang ce0d898af8 优化积分扣除 1 year ago
  chenzhihang b5fd8eb031 Merge branch 'dev-check' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev-check 1 year ago
  chenzhihang b9b0db8442 优化异常提示 1 year ago
  chenzhihang a2cf8fe75e 优化 1 year ago
  cp3hnu 6bb5a9e8e6 Merge pull request '合并dev' (#227) from dev into dev-check 1 year ago
  cp3hnu aa980985d5 Merge pull request '合并dev-zw' (#226) from dev-zw into dev 1 year ago
  cp3hnu b4d99f8e5c fix: 代码配置30202改为30203 1 year ago
  chenzhihang 5d49e2d1b5 优化镜像版本描述 1 year ago
  chenzhihang 20ce4e4758 优化镜像版本描述 1 year ago
  chenzhihang b994fb3f31 优化 1 year ago
  chenzhihang c0320a2a68 优化 1 year ago
  chenzhihang 41c2faf9cb 优化 1 year ago
  cp3hnu e92ac40694 fix: 列表运行时长和详情运行时长不一致 1 year ago
  chenzhihang 45990fa2b7 优化 1 year ago
  cp3hnu 9c62812424 fix: 列表运行时长和详情运行时长不一致 1 year ago
  chenzhihang d1928d702b 优化 1 year ago
  chenzhihang eea826de2c 优化构建镜像 1 year ago
  chenzhihang 55fd9d7271 优化代码配置 1 year ago
  chenzhihang 3905841db7 优化状态更新 1 year ago
  chenzhihang 0a7be4e261 优化结束时间 1 year ago
  chenzhihang 47f2c80a00 Merge branch 'dev-check' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev-check 1 year ago
  chenzhihang 9def5a4d7a 优化状态更新 1 year ago
  cp3hnu 7fc016c3a0 Merge pull request '合并dev' (#225) from dev into dev-check 1 year ago
  cp3hnu 260812125a Merge pull request '合并dev-zw' (#224) from dev-zw into dev 1 year ago
  cp3hnu 22f85fb3ae fix: 实验取流水线节点 1 year ago
  chenzhihang 765f37ab7c 优化主动学习 1 year ago
  chenzhihang 57296d364e 优化主动学习 1 year ago
  chenzhihang 04a9f8f125 优化主动学习 1 year ago
  chenzhihang 640c49f507 优化 1 year ago
  chenzhihang 85c92e8fae 优化主动学习 1 year ago
  chenzhihang 7b51ed5bc6 优化主动学习 1 year ago
  chenzhihang b78b0075cd 优化主动学习 1 year ago
  chenzhihang e4dd26c87d 优化 1 year ago
  chenzhihang f5f3aca3e5 Merge branch 'dev-check' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev-check 1 year ago
  chenzhihang 5aa9cdc62f 优化 1 year ago
  cp3hnu 5d60751a66 feat: 内嵌tesorboard 1 year ago
  cp3hnu 0e54f6e95d Merge pull request '合并dev' (#223) from dev into dev-check 1 year ago
  cp3hnu bc18fb14c2 Merge pull request '合并dev-zw' (#222) from dev-zw into dev 1 year ago
  cp3hnu c103375c8c fix: 自动机器学习日志一片空白 1 year ago
  cp3hnu ebd1d8680a fix: 全选时可以选中运行的实验实例 1 year ago
  chenzhihang ac95c975a5 Merge branch 'dev-check' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev-check 1 year ago
  chenzhihang c16c9d75a3 Merge remote-tracking branch 'origin/dev' into dev-check 1 year ago
  cp3hnu b6f0ffbfe1 style: 调整样式 1 year ago
  cp3hnu 039ad81fc7 Merge pull request '合并dev' (#221) from dev into dev-check 1 year ago
  cp3hnu e553a21c2f Merge pull request '合并dev-zw' (#220) from dev-zw into dev 1 year ago
  zhaowei c719141676 feat: 验收 1 year ago
100 changed files with 1918 additions and 1062 deletions
Unified View
  1. +1
    -1
      k8s/dockerfiles/managent-dockerfile
  2. +12
    -1
      k8s/template-yaml/k8s-5auth.yaml
  3. +27
    -5
      react-ui/config/routes.ts
  4. +2
    -0
      react-ui/src/app.tsx
  5. +45
    -14
      react-ui/src/components/IFramePage/index.tsx
  6. +6
    -7
      react-ui/src/components/ResourceSelectorModal/config.tsx
  7. +11
    -4
      react-ui/src/components/RightContent/AvatarDropdown.tsx
  8. +10
    -1
      react-ui/src/components/RunDuration/index.tsx
  9. +3
    -3
      react-ui/src/enums/index.ts
  10. +22
    -10
      react-ui/src/hooks/useSSE.ts
  11. +8
    -10
      react-ui/src/pages/ActiveLearn/Instance/index.tsx
  12. +5
    -9
      react-ui/src/pages/ActiveLearn/components/ActiveLearnBasic/index.tsx
  13. +14
    -4
      react-ui/src/pages/ActiveLearn/components/CreateForm/ExecuteConfig.tsx
  14. +4
    -0
      react-ui/src/pages/ActiveLearn/components/CreateForm/utils.ts
  15. +7
    -2
      react-ui/src/pages/ActiveLearn/components/ExperimentLog/index.tsx
  16. +11
    -9
      react-ui/src/pages/AutoML/Instance/index.tsx
  17. +1
    -1
      react-ui/src/pages/AutoML/List/index.tsx
  18. +44
    -9
      react-ui/src/pages/AutoML/components/AutoMLBasic/index.tsx
  19. +6
    -67
      react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfig.tsx
  20. +78
    -0
      react-ui/src/pages/AutoML/components/CreateForm/utils.ts
  21. +0
    -4
      react-ui/src/pages/AutoML/components/ExperimentInstanceList/index.less
  22. +16
    -8
      react-ui/src/pages/AutoML/components/ExperimentInstanceList/index.tsx
  23. +35
    -32
      react-ui/src/pages/AutoML/components/ExperimentInstanceList/instance.tsx
  24. +14
    -3
      react-ui/src/pages/AutoML/components/ExperimentList/config.ts
  25. +126
    -61
      react-ui/src/pages/AutoML/components/ExperimentList/index.tsx
  26. +7
    -0
      react-ui/src/pages/AutoML/components/ExperimentLog/empty.tsx
  27. +11
    -0
      react-ui/src/pages/AutoML/components/ExperimentLog/index.less
  28. +4
    -1
      react-ui/src/pages/AutoML/components/ExperimentLog/index.tsx
  29. +18
    -16
      react-ui/src/pages/AutoML/components/ExperimentRunBasic/index.tsx
  30. +8
    -0
      react-ui/src/pages/AutoML/components/ExperimentVisualResult/index.less
  31. +69
    -17
      react-ui/src/pages/AutoML/components/ExperimentVisualResult/index.tsx
  32. +2
    -2
      react-ui/src/pages/Dataset/components/AddModelModal/index.tsx
  33. +4
    -0
      react-ui/src/pages/Dataset/components/ResourceInfo/index.less
  34. +1
    -3
      react-ui/src/pages/Dataset/components/ResourceItem/index.tsx
  35. +7
    -0
      react-ui/src/pages/Dataset/components/ResourceList/index.tsx
  36. +3
    -1
      react-ui/src/pages/Dataset/components/ResourcePage/index.tsx
  37. +1
    -0
      react-ui/src/pages/Dataset/config.tsx
  38. +1
    -1
      react-ui/src/pages/Experiment/Comparison/index.tsx
  39. +102
    -54
      react-ui/src/pages/Experiment/Info/index.jsx
  40. +12
    -0
      react-ui/src/pages/Experiment/Tensorboard/index.tsx
  41. +11
    -18
      react-ui/src/pages/Experiment/components/AddExperimentModal/index.tsx
  42. +0
    -4
      react-ui/src/pages/Experiment/components/ExperimentInstanceList/index.less
  43. +15
    -10
      react-ui/src/pages/Experiment/components/ExperimentInstanceList/index.tsx
  44. +35
    -32
      react-ui/src/pages/Experiment/components/ExperimentInstanceList/instance.tsx
  45. +60
    -50
      react-ui/src/pages/Experiment/components/ExperimentParameter/index.tsx
  46. +4
    -0
      react-ui/src/pages/Experiment/components/ExportModelModal/index.tsx
  47. +4
    -1
      react-ui/src/pages/Experiment/components/LogGroup/index.tsx
  48. +1
    -4
      react-ui/src/pages/Experiment/components/ViewParamsModal/index.tsx
  49. +189
    -136
      react-ui/src/pages/Experiment/index.jsx
  50. +12
    -0
      react-ui/src/pages/HyperParameter/Aim/index.tsx
  51. +1
    -1
      react-ui/src/pages/HyperParameter/Info/index.tsx
  52. +6
    -8
      react-ui/src/pages/HyperParameter/Instance/index.tsx
  53. +5
    -1
      react-ui/src/pages/HyperParameter/components/ExperimentHistory/index.tsx
  54. +7
    -2
      react-ui/src/pages/HyperParameter/components/ExperimentLog/index.tsx
  55. +5
    -54
      react-ui/src/pages/HyperParameter/components/HyperParameterBasic/index.tsx
  56. +8
    -0
      react-ui/src/pages/Mirror/Create/index.less
  57. +29
    -6
      react-ui/src/pages/Mirror/Create/index.tsx
  58. +5
    -3
      react-ui/src/pages/Mirror/Info/index.tsx
  59. +13
    -9
      react-ui/src/pages/Model/components/MetricsChart/index.tsx
  60. +1
    -1
      react-ui/src/pages/ModelDeployment/CreateVersion/index.tsx
  61. +29
    -26
      react-ui/src/pages/ModelDeployment/VersionInfo/index.tsx
  62. +9
    -1
      react-ui/src/pages/ModelDeployment/components/ServerLog/index.less
  63. +9
    -2
      react-ui/src/pages/ModelDeployment/components/ServerLog/index.tsx
  64. +1
    -1
      react-ui/src/pages/Pipeline/Info/index.jsx
  65. +2
    -2
      react-ui/src/pages/Pipeline/components/GlobalParamsDrawer/index.tsx
  66. +130
    -92
      react-ui/src/pages/Pipeline/components/PipelineNodeDrawer/index.tsx
  67. +2
    -2
      react-ui/src/pages/Pipeline/index.jsx
  68. +1
    -1
      react-ui/src/pages/System/Role/authUser.tsx
  69. +1
    -1
      react-ui/src/pages/System/Role/components/UserSelectorModal.tsx
  70. +2
    -2
      react-ui/src/pages/System/Role/index.tsx
  71. +5
    -8
      react-ui/src/pages/System/User/components/DeptTree.tsx
  72. +2
    -3
      react-ui/src/pages/System/User/edit.tsx
  73. +12
    -1
      react-ui/src/pages/Workspace/components/AssetsManagement/index.less
  74. +16
    -8
      react-ui/src/pages/Workspace/components/AssetsManagement/index.tsx
  75. +4
    -4
      react-ui/src/pages/Workspace/components/ExperimentTable/index.less
  76. +8
    -29
      react-ui/src/pages/Workspace/components/ExperimentTable/index.tsx
  77. +76
    -0
      react-ui/src/pages/Workspace/components/ExperimentTable/instance.tsx
  78. +21
    -21
      react-ui/src/pages/Workspace/components/QuickStart/index.tsx
  79. +7
    -7
      react-ui/src/pages/Workspace/components/TotalStatistics/index.less
  80. +1
    -0
      react-ui/src/pages/Workspace/components/UserPoints/index.less
  81. +1
    -1
      react-ui/src/pages/Workspace/components/WorkspaceIntro/index.less
  82. +4
    -5
      react-ui/src/pages/Workspace/components/WorkspaceIntro/index.tsx
  83. +5
    -5
      react-ui/src/pages/Workspace/index.less
  84. +11
    -13
      react-ui/src/pages/Workspace/index.tsx
  85. +13
    -2
      react-ui/src/services/activeLearn/index.js
  86. +13
    -2
      react-ui/src/services/autoML/index.js
  87. +14
    -2
      react-ui/src/services/experiment/index.js
  88. +14
    -2
      react-ui/src/services/hyperParameter/index.js
  89. +2
    -1
      react-ui/src/services/system/role.ts
  90. +3
    -2
      react-ui/src/types.ts
  91. +5
    -2
      react-ui/src/utils/date.ts
  92. +60
    -0
      react-ui/src/utils/experiment.ts
  93. +11
    -7
      react-ui/src/utils/index.ts
  94. +1
    -1
      react-ui/tests/date.test.ts
  95. +35
    -0
      react-ui/tests/getGitUrl.test.ts
  96. +8
    -2
      ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/constant/Constant.java
  97. +91
    -94
      ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/domain/SysRole.java
  98. +7
    -7
      ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/domain/SysUser.java
  99. +3
    -3
      ruoyi-common/ruoyi-common-datascope/src/main/java/com/ruoyi/common/datascope/aspect/DataScopeAspect.java
  100. +110
    -0
      ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/config/MultiDSConfig.java

+ 1
- 1
k8s/dockerfiles/managent-dockerfile View File

@@ -13,4 +13,4 @@ WORKDIR /home/ruoyi
# 复制jar文件到路径 # 复制jar文件到路径
COPY ./jar/management-platform.jar /home/ruoyi/management-platform.jar COPY ./jar/management-platform.jar /home/ruoyi/management-platform.jar
# 启动系统服务 # 启动系统服务
ENTRYPOINT ["java","-jar","-Djdk.tls.client.protocols=TLSv1.2","management-platform.jar"]
ENTRYPOINT ["java","-jar","-Djdk.tls.client.protocols=TLSv1.2","-Dfile.encoding=UTF-8","management-platform.jar"]

+ 12
- 1
k8s/template-yaml/k8s-5auth.yaml View File

@@ -18,6 +18,11 @@ spec:
image: ${k8s-5auth-image} image: ${k8s-5auth-image}
ports: ports:
- containerPort: 9200 - containerPort: 9200
env:
- name: TZ
value: Asia/Shanghai
- name: JAVA_TOOL_OPTIONS
value: "-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=*:5005"


--- ---
apiVersion: v1 apiVersion: v1
@@ -28,9 +33,15 @@ metadata:
spec: spec:
type: NodePort type: NodePort
ports: ports:
- port: 9200
- name: http
port: 9200
nodePort: 31206 nodePort: 31206
protocol: TCP protocol: TCP
- name: debug
nodePort: 31221
port: 5005
protocol: TCP
targetPort: 5005
selector: selector:
app: ci4s-auth app: ci4s-auth



+ 27
- 5
react-ui/config/routes.ts View File

@@ -141,12 +141,23 @@ export default [
{ {
name: '实验对比', name: '实验对比',
path: 'compare', path: 'compare',
component: './Experiment/Comparison/index',
routes: [
{
name: '实验对比',
path: '',
component: './Experiment/Comparison/index',
},
{
name: '可视化对比',
path: 'compare-visual',
component: './Experiment/Aim/index',
},
],
}, },
{ {
name: '实验可视化对比',
path: 'compare-visual',
component: './Experiment/Aim/index',
name: '可视化',
path: 'visual',
component: './Experiment/Tensorboard/index',
}, },
], ],
}, },
@@ -218,7 +229,18 @@ export default [
{ {
name: '实验实例详情', name: '实验实例详情',
path: 'instance/:experimentId/:id', path: 'instance/:experimentId/:id',
component: './HyperParameter/Instance/index',
routes: [
{
name: '实验实例详情',
path: '',
component: './HyperParameter/Instance/index',
},
{
name: '可视化对比',
path: 'compare-visual',
component: './HyperParameter/Aim/index',
},
],
}, },
], ],
}, },


+ 2
- 0
react-ui/src/app.tsx View File

@@ -21,6 +21,7 @@ import {
} from './services/session'; } from './services/session';
import './styles/menu.less'; import './styles/menu.less';
import { needAuth } from './utils'; import { needAuth } from './utils';
// import { closeAllModals } from './utils/modal';
import { gotoLoginPage } from './utils/ui'; import { gotoLoginPage } from './utils/ui';
export { requestConfig as request } from './requestConfig'; export { requestConfig as request } from './requestConfig';


@@ -96,6 +97,7 @@ export const layout: RuntimeConfig['layout'] = ({ initialState }) => {
}, },
onPageChange: () => { onPageChange: () => {
const { location } = history; const { location } = history;
// closeAllModals();
// 如果没有登录,重定向到 login // 如果没有登录,重定向到 login
if (!initialState?.currentUser && needAuth(location.pathname)) { if (!initialState?.currentUser && needAuth(location.pathname)) {
gotoLoginPage(); gotoLoginPage();


+ 45
- 14
react-ui/src/components/IFramePage/index.tsx View File

@@ -1,12 +1,11 @@
import FullScreenFrame from '@/components/FullScreenFrame'; import FullScreenFrame from '@/components/FullScreenFrame';
import KFSpin from '@/components/KFSpin';
import { getKnowledgeGraphUrl, getLabelStudioUrl } from '@/services/developmentEnvironment'; import { getKnowledgeGraphUrl, getLabelStudioUrl } from '@/services/developmentEnvironment';
import Loading from '@/utils/loading';
import { to } from '@/utils/promise'; import { to } from '@/utils/promise';
import SessionStorage from '@/utils/sessionStorage'; import SessionStorage from '@/utils/sessionStorage';
import { FloatButton } from 'antd'; import { FloatButton } from 'antd';
import classNames from 'classnames'; import classNames from 'classnames';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { createPortal } from 'react-dom';
import './index.less'; import './index.less';


export enum IframePageType { export enum IframePageType {
@@ -54,9 +53,13 @@ const getRequestAPI = (type: IframePageType): (() => Promise<any>) => {


type IframePageProps = { type IframePageProps = {
/** 子系统 */ /** 子系统 */
type: IframePageType;
type?: IframePageType;
/** url */
url?: string;
/** 是否可以在页签上打开 */ /** 是否可以在页签上打开 */
openInTab?: boolean; openInTab?: boolean;
/** 是否显示加载 */
showLoading?: boolean;
/** 自定义样式类名 */ /** 自定义样式类名 */
className?: string; className?: string;
/** 自定义样式 */ /** 自定义样式 */
@@ -64,32 +67,60 @@ type IframePageProps = {
}; };


/** 系统内嵌 iframe,目前系统有数据标注、应用开发、开发环境、GitLink 四个子系统,使用时可以添加其他子系统 */ /** 系统内嵌 iframe,目前系统有数据标注、应用开发、开发环境、GitLink 四个子系统,使用时可以添加其他子系统 */
function IframePage({ type, openInTab = false, className, style }: IframePageProps) {
function IframePage({
type,
url,
showLoading = true,
openInTab = false,
className,
style,
}: IframePageProps) {
const [iframeUrl, setIframeUrl] = useState(''); const [iframeUrl, setIframeUrl] = useState('');
const [loading, setLoading] = useState(false);
// const [loading, setLoading] = useState(false);


useEffect(() => { useEffect(() => {
const requestIframeUrl = async () => {
setLoading(true);
const requestIframeUrl = async (type: IframePageType) => {
if (showLoading) {
Loading.show();
}
const [res] = await to(getRequestAPI(type)()); const [res] = await to(getRequestAPI(type)());
if (res && res.data) { if (res && res.data) {
setIframeUrl(res.data); setIframeUrl(res.data);
} else { } else {
setLoading(false);
if (showLoading) {
Loading.hide();
}
} }
}; };


requestIframeUrl();
}, [type]);
if (type) {
requestIframeUrl(type);
} else if (url) {
if (showLoading) {
Loading.show();
}

setIframeUrl(url);
}
}, [type, url, showLoading]);

const handleLoad = () => {
if (showLoading) {
Loading.hide();
}
};


const hideLoading = () => {
setLoading(false);
const handleError = (error?: React.SyntheticEvent<HTMLIFrameElement, Event>) => {
console.log('error', error);
if (showLoading) {
Loading.hide();
}
}; };


return ( return (
<div className={classNames('kf-iframe-page', className)} style={style}> <div className={classNames('kf-iframe-page', className)} style={style}>
{loading && createPortal(<KFSpin size="large" />, document.body)}
<FullScreenFrame url={iframeUrl} onLoad={hideLoading} onError={hideLoading} />
{/* {loading && createPortal(<KFSpin size="large" />, document.body)} */}
{iframeUrl && <FullScreenFrame url={iframeUrl} onLoad={handleLoad} onError={handleError} />}
{openInTab && <FloatButton onClick={() => window.open(iframeUrl, '_blank')} />} {openInTab && <FloatButton onClick={() => window.open(iframeUrl, '_blank')} />}
</div> </div>
); );


+ 6
- 7
react-ui/src/components/ResourceSelectorModal/config.tsx View File

@@ -1,7 +1,7 @@
import datasetImg from '@/assets/img/modal-select-dataset.png'; import datasetImg from '@/assets/img/modal-select-dataset.png';
import mirrorImg from '@/assets/img/modal-select-mirror.png'; import mirrorImg from '@/assets/img/modal-select-mirror.png';
import modelImg from '@/assets/img/modal-select-model.png'; import modelImg from '@/assets/img/modal-select-model.png';
import { AvailableRange, CommonTabKeys } from '@/enums';
import { AvailableRange, CommonTabKeys, MirrorVersionStatus } from '@/enums';
import { ResourceData, ResourceVersionData } from '@/pages/Dataset/config'; import { ResourceData, ResourceVersionData } from '@/pages/Dataset/config';
import { MirrorVersionData } from '@/pages/Mirror/Info'; import { MirrorVersionData } from '@/pages/Mirror/Info';
import { MirrorData } from '@/pages/Mirror/List'; import { MirrorData } from '@/pages/Mirror/List';
@@ -24,11 +24,11 @@ export enum ResourceSelectorType {
} }


// 数据集、模型列表转为树形结构 // 数据集、模型列表转为树形结构
const convertDatasetToTreeData = (list: ResourceData[]): TreeDataNode[] => {
const convertDatasetToTreeData = (list: ResourceData[], isPublic: boolean): TreeDataNode[] => {
return list.map((v) => ({ return list.map((v) => ({
...v, ...v,
key: `${v.id}`, key: `${v.id}`,
title: v.name,
title: isPublic ? `${v.name} (${v.owner})` : v.name,
isLeaf: false, isLeaf: false,
checkable: false, checkable: false,
})); }));
@@ -106,7 +106,7 @@ export class DatasetSelector implements SelectorTypeInfo {
const res = await getDatasetList({ is_public: isPublic, page: 0, size: 2000 }); const res = await getDatasetList({ is_public: isPublic, page: 0, size: 2000 });
if (res && res.data) { if (res && res.data) {
const list = res.data.content || []; const list = res.data.content || [];
return convertDatasetToTreeData(list);
return convertDatasetToTreeData(list, isPublic);
} else { } else {
return Promise.reject('获取数据集列表失败'); return Promise.reject('获取数据集列表失败');
} }
@@ -158,7 +158,7 @@ export class ModelSelector implements SelectorTypeInfo {
const res = await getModelList({ is_public: isPublic, page: 0, size: 2000 }); const res = await getModelList({ is_public: isPublic, page: 0, size: 2000 });
if (res && res.data) { if (res && res.data) {
const list = res.data.content || []; const list = res.data.content || [];
return convertDatasetToTreeData(list);
return convertDatasetToTreeData(list, isPublic);
} else { } else {
return Promise.reject('获取模型列表失败'); return Promise.reject('获取模型列表失败');
} }
@@ -224,8 +224,7 @@ export class MirrorSelector implements SelectorTypeInfo {
image_id: parentKey, image_id: parentKey,
page: 0, page: 0,
size: 2000, size: 2000,
status: 'available',
state: 1,
status: MirrorVersionStatus.Available,
}); });
if (res && res.data) { if (res && res.data) {
const list = res.data.content || []; const list = res.data.content || [];


+ 11
- 4
react-ui/src/components/RightContent/AvatarDropdown.tsx View File

@@ -1,10 +1,11 @@
import { clearSessionToken } from '@/access'; import { clearSessionToken } from '@/access';
import { getLabelStudioUrl } from '@/services/developmentEnvironment';
import { setRemoteMenu } from '@/services/session'; import { setRemoteMenu } from '@/services/session';
import { logout } from '@/services/system/auth'; import { logout } from '@/services/system/auth';
import { ClientInfo } from '@/types'; import { ClientInfo } from '@/types';
import { sleep } from '@/utils/promise';
import { sleep, to } from '@/utils/promise';
import SessionStorage from '@/utils/sessionStorage'; import SessionStorage from '@/utils/sessionStorage';
import { gotoLoginPage, oauthLogout } from '@/utils/ui';
import { oauthLogout } from '@/utils/ui';
import { LogoutOutlined, UserOutlined } from '@ant-design/icons'; import { LogoutOutlined, UserOutlined } from '@ant-design/icons';
import { setAlpha } from '@ant-design/pro-components'; import { setAlpha } from '@ant-design/pro-components';
import { useEmotionCss } from '@ant-design/use-emotion-css'; import { useEmotionCss } from '@ant-design/use-emotion-css';
@@ -63,17 +64,23 @@ const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({ menu }) => {
* 退出登录,并且将当前的 url 保存 * 退出登录,并且将当前的 url 保存
*/ */
const loginOut = async () => { const loginOut = async () => {
oauthLogout('http://172.20.32.197:31209/oauth/logout');
const [res] = await to(getLabelStudioUrl());
if (res && res.data) {
oauthLogout(`${res.data}/oauth/logout`);
}
// 至少 1 秒后跳转,希望子系统能完成注销 // 至少 1 秒后跳转,希望子系统能完成注销
await Promise.all([logout(), sleep(1000)]); await Promise.all([logout(), sleep(1000)]);
clearSessionToken(); clearSessionToken();
setRemoteMenu(null); setRemoteMenu(null);
gotoLoginPage();
// 退出 oauth2
const clientInfo: ClientInfo = SessionStorage.getItem(SessionStorage.clientInfoKey, true); const clientInfo: ClientInfo = SessionStorage.getItem(SessionStorage.clientInfoKey, true);
if (clientInfo) { if (clientInfo) {
const { logoutUri } = clientInfo; const { logoutUri } = clientInfo;
location.replace(logoutUri); location.replace(logoutUri);
} }
// setTimeout(() => {
// gotoLoginPage();
// }, 100);
}; };
const actionClassName = useEmotionCss(({ token }) => { const actionClassName = useEmotionCss(({ token }) => {
return { return {


+ 10
- 1
react-ui/src/components/RunDuration/index.tsx View File

@@ -10,13 +10,21 @@ type RunDurationProps = {
}; };
function RunDuration({ createTime, finishTime, className, style }: RunDurationProps) { function RunDuration({ createTime, finishTime, className, style }: RunDurationProps) {
const [now] = useServerTime(); const [now] = useServerTime();
const [currentTime, setCurrentTime] = useState<Date>(now());
const [currentTime, setCurrentTime] = useState<Date>(finishTime ? new Date(finishTime) : now());

// console.log(
// 'currentTime',
// new Date(createTime ?? 0),
// currentTime,
// (currentTime.getTime() - new Date(createTime ?? 0).getTime()) / 1000,
// );


// 定时刷新耗时 // 定时刷新耗时
useEffect(() => { useEffect(() => {
if (finishTime) { if (finishTime) {
setCurrentTime(new Date(finishTime)); setCurrentTime(new Date(finishTime));
} else { } else {
setCurrentTime(now());
const timer = setInterval(() => { const timer = setInterval(() => {
setCurrentTime(now()); setCurrentTime(now());
}, 1000); }, 1000);
@@ -25,6 +33,7 @@ function RunDuration({ createTime, finishTime, className, style }: RunDurationPr
}; };
} }
}, [finishTime, now]); }, [finishTime, now]);

return ( return (
<span className={className} style={style}> <span className={className} style={style}>
{elapsedTime(createTime, currentTime)} {elapsedTime(createTime, currentTime)}


+ 3
- 3
react-ui/src/enums/index.ts View File

@@ -33,7 +33,7 @@ export enum TensorBoardStatus {
Unknown = 'Unknown', // 未知 Unknown = 'Unknown', // 未知
Pending = 'Pending', // 启动中 Pending = 'Pending', // 启动中
Running = 'Running', // 运行中 Running = 'Running', // 运行中
Terminated = 'Terminated', // 未启动或者已终止
Terminated = 'Terminated', // 未启动
Failed = 'Failed', // 失败 Failed = 'Failed', // 失败
} }


@@ -95,8 +95,8 @@ export enum AutoMLType {


export const autoMLTypeOptions = [ export const autoMLTypeOptions = [
{ label: '表格', value: AutoMLType.Table }, { label: '表格', value: AutoMLType.Table },
{ label: '文本分类', value: AutoMLType.Text },
{ label: '视频分类', value: AutoMLType.Video },
{ label: '文本', value: AutoMLType.Text },
{ label: '视频', value: AutoMLType.Video },
]; ];


// 自动化任务类型 // 自动化任务类型


+ 22
- 10
react-ui/src/hooks/useSSE.ts View File

@@ -1,11 +1,24 @@
import { parseJsonText } from '@/utils';
import { useEffect } from 'react';
import { ExperimentStatus } from '@/enums'; import { ExperimentStatus } from '@/enums';
import { NodeStatus } from '@/types'; import { NodeStatus } from '@/types';
import { parseJsonText } from '@/utils';
import { useEffect } from 'react';


export type MessageHandler = (experimentInsId: number, status: string, finishedAt: string, nodes: Record<string, NodeStatus>) => void
export const useSSE = (experimentInsId: number, status: ExperimentStatus, name: string, namespace: string, onMessage: MessageHandler) => {
const isRunning = status === ExperimentStatus.Pending || status === ExperimentStatus.Running
export type MessageHandler = (
experimentId: number,
experimentInsId: number,
status: string,
finishTime: string,
nodes: Record<string, NodeStatus>,
) => void;
export const useSSE = (
experimentId: number,
experimentInsId: number,
status: ExperimentStatus,
name: string,
namespace: string,
onMessage: MessageHandler,
) => {
const isRunning = status === ExperimentStatus.Pending || status === ExperimentStatus.Running;
useEffect(() => { useEffect(() => {
if (isRunning) { if (isRunning) {
const { origin } = location; const { origin } = location;
@@ -22,8 +35,8 @@ export const useSSE = (experimentInsId: number, status: ExperimentStatus, name:
const dataJson = parseJsonText(data); const dataJson = parseJsonText(data);
const statusData = dataJson?.result?.object?.status; const statusData = dataJson?.result?.object?.status;
if (statusData) { if (statusData) {
const { finishedAt, phase, nodes } = statusData;
onMessage(experimentInsId, phase, finishedAt, nodes);
const { finishedAt, phase, nodes } = statusData;
onMessage(experimentId, experimentInsId, phase, finishedAt, nodes);
} }
}; };


@@ -33,8 +46,7 @@ export const useSSE = (experimentInsId: number, status: ExperimentStatus, name:


return () => { return () => {
evtSource.close(); evtSource.close();
}
};
} }
}, [experimentInsId, isRunning, name, namespace, onMessage]);
}, [experimentId, experimentInsId, isRunning, name, namespace, onMessage]);
}; };

+ 8
- 10
react-ui/src/pages/ActiveLearn/Instance/index.tsx View File

@@ -51,13 +51,12 @@ function ActiveLearnInstance() {
const [res] = await to(getActiveLearnInsReq(instanceId)); const [res] = await to(getActiveLearnInsReq(instanceId));
if (res && res.data) { if (res && res.data) {
const info = res.data as ActiveLearnInstanceData; const info = res.data as ActiveLearnInstanceData;
const { param, node_status, argo_ins_name, argo_ins_ns, status, create_time } = info;
const { param, node_status, argo_ins_name, argo_ins_ns, status } = info;
// 解析配置参数 // 解析配置参数
const paramJson = parseJsonText(param); const paramJson = parseJsonText(param);
if (paramJson) { if (paramJson) {
setExperimentInfo({ setExperimentInfo({
...paramJson.data, ...paramJson.data,
create_time,
}); });
} }


@@ -69,7 +68,7 @@ function ActiveLearnInstance() {
return; return;
} }


// 进行节点状态
// 设置总 workflow 状态
const nodeStatusJson = parseJsonText(node_status); const nodeStatusJson = parseJsonText(node_status);
if (nodeStatusJson) { if (nodeStatusJson) {
setNodes(nodeStatusJson); setNodes(nodeStatusJson);
@@ -106,18 +105,17 @@ function ActiveLearnInstance() {
if (dataJson) { if (dataJson) {
const nodes = dataJson?.result?.object?.status?.nodes; const nodes = dataJson?.result?.object?.status?.nodes;
if (nodes) { if (nodes) {
// 节点
// 设置节点
setNodes(nodes); setNodes(nodes);


// 设置总 workflow 状态
const workflowStatus = Object.values(nodes).find((node: any) => const workflowStatus = Object.values(nodes).find((node: any) =>
node.displayName.startsWith(NodePrefix), node.displayName.startsWith(NodePrefix),
) as NodeStatus; ) as NodeStatus;

// 设置工作流状态
if (workflowStatus) { if (workflowStatus) {
setWorkflowStatus(workflowStatus); setWorkflowStatus(workflowStatus);


// 实验结束,关闭 SSE
// 实验结束,关闭 SSE,获取实验实例结果
if ( if (
workflowStatus.phase !== ExperimentStatus.Pending && workflowStatus.phase !== ExperimentStatus.Pending &&
workflowStatus.phase !== ExperimentStatus.Running workflowStatus.phase !== ExperimentStatus.Running
@@ -152,8 +150,8 @@ function ActiveLearnInstance() {
<ActiveLearnBasic <ActiveLearnBasic
className={styles['active-learn-instance__basic']} className={styles['active-learn-instance__basic']}
info={experimentInfo} info={experimentInfo}
runStatus={workflowStatus}
instanceStatus={instanceInfo?.status}
workflowStatus={workflowStatus}
instanceStatus={instanceInfo?.status as ExperimentStatus}
isInstance isInstance
/> />
), ),
@@ -181,7 +179,7 @@ function ActiveLearnInstance() {
}, },
{ {
key: TabKeys.History, key: TabKeys.History,
label: '训练列表',
label: '运行列表',
icon: <KFIcon type="icon-Trialliebiao" />, icon: <KFIcon type="icon-Trialliebiao" />,
children: ( children: (
<ExperimentHistory <ExperimentHistory


+ 5
- 9
react-ui/src/pages/ActiveLearn/components/ActiveLearnBasic/index.tsx View File

@@ -28,14 +28,14 @@ type BasicInfoProps = {
info?: ActiveLearnData; info?: ActiveLearnData;
className?: string; className?: string;
isInstance?: boolean; isInstance?: boolean;
runStatus?: NodeStatus;
workflowStatus?: NodeStatus;
instanceStatus?: ExperimentStatus; instanceStatus?: ExperimentStatus;
}; };


function BasicInfo({ function BasicInfo({
info, info,
className, className,
runStatus,
workflowStatus,
instanceStatus, instanceStatus,
isInstance = false, isInstance = false,
}: BasicInfoProps) { }: BasicInfoProps) {
@@ -154,7 +154,7 @@ function BasicInfo({
value: info.dataset_py, value: info.dataset_py,
}, },
{ {
label: '数据集类名',
label: '数据集处理类名',
value: info.dataset_class_name, value: info.dataset_class_name,
}, },
{ {
@@ -212,12 +212,8 @@ function BasicInfo({


return ( return (
<div className={classNames(styles['active-learn-basic'], className)}> <div className={classNames(styles['active-learn-basic'], className)}>
{isInstance && runStatus && (
<ExperimentRunBasic
create_time={info?.create_time}
runStatus={runStatus}
instanceStatus={instanceStatus}
/>
{isInstance && workflowStatus && (
<ExperimentRunBasic workflowStatus={workflowStatus} instanceStatus={instanceStatus} />
)} )}
{!isInstance && ( {!isInstance && (
<ConfigInfo <ConfigInfo


+ 14
- 4
react-ui/src/pages/ActiveLearn/components/CreateForm/ExecuteConfig.tsx View File

@@ -17,6 +17,11 @@ import {


function ExecuteConfig() { function ExecuteConfig() {
const form = Form.useFormInstance(); const form = Form.useFormInstance();
const task_type = Form.useWatch('task_type', form);
const queryStrategiesOptions =
task_type === AutoMLTaskType.Classification
? queryStrategies.slice(0, 2)
: queryStrategies.slice(2);
return ( return (
<> <>
<SubAreaTitle <SubAreaTitle
@@ -101,16 +106,16 @@ function ExecuteConfig() {
<Row gutter={8}> <Row gutter={8}>
<Col span={10}> <Col span={10}>
<Form.Item <Form.Item
label="数据集类名"
label="数据集处理类名"
name="dataset_class_name" name="dataset_class_name"
rules={[ rules={[
{ {
required: true, required: true,
message: '请输入数据集类名',
message: '请输入数据集处理类名',
}, },
]} ]}
> >
<Input placeholder="请输入数据集类名" maxLength={64} showCount allowClear />
<Input placeholder="请输入数据集处理类名" maxLength={64} showCount allowClear />
</Form.Item> </Form.Item>
</Col> </Col>
</Row> </Row>
@@ -488,7 +493,12 @@ function ExecuteConfig() {
}, },
]} ]}
> >
<Select placeholder="请选择查询策略" options={queryStrategies} showSearch allowClear />
<Select
placeholder="请选择查询策略"
options={queryStrategiesOptions}
showSearch
allowClear
/>
</Form.Item> </Form.Item>
</Col> </Col>
</Row> </Row>


+ 4
- 0
react-ui/src/pages/ActiveLearn/components/CreateForm/utils.ts View File

@@ -87,4 +87,8 @@ export const queryStrategies = [
label: 'upper_confidence_bound', label: 'upper_confidence_bound',
value: 'upper_confidence_bound', value: 'upper_confidence_bound',
}, },
{
label: 'probability_of_improvement',
value: 'probability_of_improvement',
},
]; ];

+ 7
- 2
react-ui/src/pages/ActiveLearn/components/ExperimentLog/index.tsx View File

@@ -1,5 +1,6 @@
import { ExperimentStatus } from '@/enums'; import { ExperimentStatus } from '@/enums';
import { ActiveLearnInstanceData } from '@/pages/ActiveLearn/types'; import { ActiveLearnInstanceData } from '@/pages/ActiveLearn/types';
import EmptyLog from '@/pages/AutoML/components/ExperimentLog/empty';
import LogList from '@/pages/Experiment/components/LogList'; import LogList from '@/pages/Experiment/components/LogList';
import { NodeStatus } from '@/types'; import { NodeStatus } from '@/types';
import { Tabs } from 'antd'; import { Tabs } from 'antd';
@@ -64,7 +65,7 @@ function ExperimentLog({ instanceInfo, nodes }: ExperimentLogProps) {
// icon: <KFIcon type="icon-rizhi1" />, // icon: <KFIcon type="icon-rizhi1" />,
children: ( children: (
<div className={styles['experiment-log__tabs__log']}> <div className={styles['experiment-log__tabs__log']}>
{trainCloneNodeStatus && (
{trainCloneNodeStatus ? (
<LogList <LogList
instanceName={instanceInfo.argo_ins_name} instanceName={instanceInfo.argo_ins_name}
instanceNamespace={instanceInfo.argo_ins_ns} instanceNamespace={instanceInfo.argo_ins_ns}
@@ -73,6 +74,8 @@ function ExperimentLog({ instanceInfo, nodes }: ExperimentLogProps) {
instanceNodeStartTime={trainCloneNodeStatus.startedAt} instanceNodeStartTime={trainCloneNodeStatus.startedAt}
instanceNodeStatus={trainCloneNodeStatus.phase as ExperimentStatus} instanceNodeStatus={trainCloneNodeStatus.phase as ExperimentStatus}
></LogList> ></LogList>
) : (
<EmptyLog />
)} )}
</div> </div>
), ),
@@ -83,7 +86,7 @@ function ExperimentLog({ instanceInfo, nodes }: ExperimentLogProps) {
// icon: <KFIcon type="icon-rizhi1" />, // icon: <KFIcon type="icon-rizhi1" />,
children: ( children: (
<div className={styles['experiment-log__tabs__log']}> <div className={styles['experiment-log__tabs__log']}>
{hpoNodeStatus && (
{hpoNodeStatus ? (
<LogList <LogList
instanceName={instanceInfo.argo_ins_name} instanceName={instanceInfo.argo_ins_name}
instanceNamespace={instanceInfo.argo_ins_ns} instanceNamespace={instanceInfo.argo_ins_ns}
@@ -92,6 +95,8 @@ function ExperimentLog({ instanceInfo, nodes }: ExperimentLogProps) {
instanceNodeStartTime={hpoNodeStatus.startedAt} instanceNodeStartTime={hpoNodeStatus.startedAt}
instanceNodeStatus={hpoNodeStatus.phase as ExperimentStatus} instanceNodeStatus={hpoNodeStatus.phase as ExperimentStatus}
></LogList> ></LogList>
) : (
<EmptyLog />
)} )}
</div> </div>
), ),


+ 11
- 9
react-ui/src/pages/AutoML/Instance/index.tsx View File

@@ -51,7 +51,7 @@ function AutoMLInstance() {
const [res] = await to(getExperimentInsReq(instanceId)); const [res] = await to(getExperimentInsReq(instanceId));
if (res && res.data) { if (res && res.data) {
const info = res.data as AutoMLInstanceData; const info = res.data as AutoMLInstanceData;
const { param, node_status, argo_ins_name, argo_ins_ns, status, create_time, type } = info;
const { param, node_status, argo_ins_name, argo_ins_ns, status, type } = info;


setType(type); setType(type);
// 解析配置参数 // 解析配置参数
@@ -59,7 +59,6 @@ function AutoMLInstance() {
if (paramJson) { if (paramJson) {
setAutoMLInfo({ setAutoMLInfo({
...paramJson.data, ...paramJson.data,
create_time,
type, type,
}); });
} }
@@ -95,7 +94,10 @@ function AutoMLInstance() {
}; };


const setupSSE = (name: string, namespace: string) => { const setupSSE = (name: string, namespace: string) => {
const { origin } = location;
let { origin } = location;
if (process.env.NODE_ENV === 'development') {
origin = 'http://172.20.32.235:31213';
}
const params = encodeURIComponent(`metadata.namespace=${namespace},metadata.name=${name}`); const params = encodeURIComponent(`metadata.namespace=${namespace},metadata.name=${name}`);
const evtSource = new EventSource( const evtSource = new EventSource(
`${origin}/api/v1/realtimeStatus?listOptions.fieldSelector=${params}`, `${origin}/api/v1/realtimeStatus?listOptions.fieldSelector=${params}`,
@@ -110,17 +112,17 @@ function AutoMLInstance() {
if (dataJson) { if (dataJson) {
const nodes = dataJson?.result?.object?.status?.nodes; const nodes = dataJson?.result?.object?.status?.nodes;
if (nodes) { if (nodes) {
// 节点
// 设置节点
setNodes(nodes); setNodes(nodes);


// 设置总 workflow 状态
const workflowStatus = Object.values(nodes).find((node: any) => const workflowStatus = Object.values(nodes).find((node: any) =>
node.displayName.startsWith(NodePrefix), node.displayName.startsWith(NodePrefix),
) as NodeStatus; ) as NodeStatus;

if (workflowStatus) { if (workflowStatus) {
setWorkflowStatus(workflowStatus); setWorkflowStatus(workflowStatus);


// 实验结束,关闭 SSE
// 实验结束,关闭 SSE,获取实验实例结果
if ( if (
workflowStatus.phase !== ExperimentStatus.Pending && workflowStatus.phase !== ExperimentStatus.Pending &&
workflowStatus.phase !== ExperimentStatus.Running workflowStatus.phase !== ExperimentStatus.Running
@@ -155,8 +157,8 @@ function AutoMLInstance() {
<AutoMLBasic <AutoMLBasic
className={styles['auto-ml-instance__basic']} className={styles['auto-ml-instance__basic']}
info={autoMLInfo} info={autoMLInfo}
runStatus={workflowStatus}
instanceStatus={instanceInfo?.status}
workflowStatus={workflowStatus}
instanceStatus={instanceInfo?.status as ExperimentStatus}
isInstance isInstance
/> />
), ),
@@ -202,7 +204,7 @@ function AutoMLInstance() {
} }
: { : {
key: TabKeys.History, key: TabKeys.History,
label: '试验列表',
label: '运行列表',
icon: <KFIcon type="icon-Trialliebiao" />, icon: <KFIcon type="icon-Trialliebiao" />,
children: ( children: (
<ExperimentHistory <ExperimentHistory


+ 1
- 1
react-ui/src/pages/AutoML/List/index.tsx View File

@@ -1,7 +1,7 @@
/* /*
* @Author: 赵伟 * @Author: 赵伟
* @Date: 2024-04-16 13:58:08 * @Date: 2024-04-16 13:58:08
* @Description: 自主机器学习列表
* @Description: 自动机器学习列表
*/ */


import ExperimentList, { ExperimentListType } from '../components/ExperimentList'; import ExperimentList, { ExperimentListType } from '../components/ExperimentList';


+ 44
- 9
react-ui/src/pages/AutoML/components/AutoMLBasic/index.tsx View File

@@ -7,10 +7,21 @@ import {
autoMLTaskTypeOptions, autoMLTaskTypeOptions,
} from '@/enums'; } from '@/enums';
import { useComputingResource } from '@/hooks/useComputingResource'; import { useComputingResource } from '@/hooks/useComputingResource';
import {
classificationAlgorithms,
featureAlgorithms,
regressorAlgorithms,
} from '@/pages/AutoML/components/CreateForm/utils';
import { AutoMLData } from '@/pages/AutoML/types'; import { AutoMLData } from '@/pages/AutoML/types';
import { type NodeStatus } from '@/types'; import { type NodeStatus } from '@/types';
import { parseJsonText } from '@/utils'; import { parseJsonText } from '@/utils';
import { formatBoolean, formatDataset, formatDate, formatEnum } from '@/utils/format';
import {
formatBoolean,
formatDataset,
formatDate,
formatEnum,
type EnumOptions,
} from '@/utils/format';
import classNames from 'classnames'; import classNames from 'classnames';
import { useMemo } from 'react'; import { useMemo } from 'react';
import ExperimentRunBasic from '../ExperimentRunBasic'; import ExperimentRunBasic from '../ExperimentRunBasic';
@@ -21,6 +32,7 @@ const formatOptimizeMode = (value: boolean) => {
return value ? '越大越好' : '越小越好'; return value ? '越大越好' : '越小越好';
}; };


// 格式化权重
const formatMetricsWeight = (value: string) => { const formatMetricsWeight = (value: string) => {
if (!value) { if (!value) {
return '--'; return '--';
@@ -34,18 +46,33 @@ const formatMetricsWeight = (value: string) => {
.join('\n'); .join('\n');
}; };


// 格式化算法
const formatAlgorithm = (algorithms: EnumOptions[]) => {
return (value: string) => {
if (!value) {
return '--';
}
const list = value
.split(',')
.filter((v) => v !== '')
.map((v) => v.trim());
return list.map((v) => formatEnum(algorithms)(v)).join(',');
};
};

type AutoMLBasicProps = { type AutoMLBasicProps = {
info?: AutoMLData; info?: AutoMLData;
className?: string; className?: string;
isInstance?: boolean; isInstance?: boolean;
runStatus?: NodeStatus;
workflowStatus?: NodeStatus;
instanceStatus?: ExperimentStatus; instanceStatus?: ExperimentStatus;
instanceCreateTime?: string;
}; };


function AutoMLBasic({ function AutoMLBasic({
info, info,
className, className,
runStatus,
workflowStatus,
instanceStatus, instanceStatus,
isInstance = false, isInstance = false,
}: AutoMLBasicProps) { }: AutoMLBasicProps) {
@@ -95,10 +122,12 @@ function AutoMLBasic({
{ {
label: '特征预处理算法', label: '特征预处理算法',
value: info.include_feature_preprocessor, value: info.include_feature_preprocessor,
format: formatAlgorithm(featureAlgorithms),
}, },
{ {
label: '排除的特征预处理算法', label: '排除的特征预处理算法',
value: info.exclude_feature_preprocessor, value: info.exclude_feature_preprocessor,
format: formatAlgorithm(featureAlgorithms),
}, },
{ {
label: info.task_type === AutoMLTaskType.Regression ? '回归算法' : '分类算法', label: info.task_type === AutoMLTaskType.Regression ? '回归算法' : '分类算法',
@@ -106,6 +135,11 @@ function AutoMLBasic({
info.task_type === AutoMLTaskType.Regression info.task_type === AutoMLTaskType.Regression
? info.include_regressor ? info.include_regressor
: info.include_classifier, : info.include_classifier,
format: formatAlgorithm(
info.task_type === AutoMLTaskType.Regression
? regressorAlgorithms
: classificationAlgorithms,
),
}, },
{ {
label: info.task_type === AutoMLTaskType.Regression ? '排除的回归算法' : '排除的分类算法', label: info.task_type === AutoMLTaskType.Regression ? '排除的回归算法' : '排除的分类算法',
@@ -113,6 +147,11 @@ function AutoMLBasic({
info.task_type === AutoMLTaskType.Regression info.task_type === AutoMLTaskType.Regression
? info.exclude_regressor ? info.exclude_regressor
: info.exclude_classifier, : info.exclude_classifier,
format: formatAlgorithm(
info.task_type === AutoMLTaskType.Regression
? regressorAlgorithms
: classificationAlgorithms,
),
}, },
{ {
label: '集成方式', label: '集成方式',
@@ -292,12 +331,8 @@ function AutoMLBasic({


return ( return (
<div className={classNames(styles['auto-ml-basic'], className)}> <div className={classNames(styles['auto-ml-basic'], className)}>
{isInstance && runStatus && (
<ExperimentRunBasic
create_time={info?.create_time}
runStatus={runStatus}
instanceStatus={instanceStatus}
/>
{isInstance && workflowStatus && (
<ExperimentRunBasic workflowStatus={workflowStatus} instanceStatus={instanceStatus} />
)} )}
{!isInstance && ( {!isInstance && (
<ConfigInfo <ConfigInfo


+ 6
- 67
react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfig.tsx View File

@@ -8,69 +8,7 @@ import {
autoMLTaskTypeOptions, autoMLTaskTypeOptions,
} from '@/enums'; } from '@/enums';
import { Col, Form, InputNumber, Radio, Row, Select, Switch } from 'antd'; import { Col, Form, InputNumber, Radio, Row, Select, Switch } from 'antd';

// 分类算法
const classificationAlgorithms = [
'adaboost',
'bernoulli_nb',
'decision_tree',
'extra_trees',
'gaussian_nb',
'gradient_boosting',
'k_nearest_neighbors',
'lda',
'liblinear_svc',
'libsvm_svc',
'mlp',
'multinomial_nb',
'passive_aggressive',
'qda',
'random_forest',
'sgd',
'LightGBMClassification',
'XGBoostClassification',
'StackingClassification',
].map((name) => ({ label: name, value: name }));

// 回归算法
const regressorAlgorithms = [
'adaboost',
'ard_regression',
'decision_tree',
'extra_trees',
'gaussian_process',
'gradient_boosting',
'k_nearest_neighbors',
'liblinear_svr',
'libsvm_svr',
'mlp',
'random_forest',
'sgd',
'LightGBMRegression',
'XGBoostRegression',
].map((name) => ({ label: name, value: name }));

// 特征预处理算法
const featureAlgorithms = [
'densifier',
'extra_trees_preproc_for_classification',
'extra_trees_preproc_for_regression',
'fast_ica',
'feature_agglomeration',
'kernel_pca',
'kitchen_sinks',
'liblinear_svc_preprocessor',
'no_preprocessing',
'nystroem_sampler',
'pca',
'polynomial',
'random_trees_embedding',
'select_percentile_classification',
'select_percentile_regression',
'select_rates_classification',
'select_rates_regression',
'truncatedSVD',
].map((name) => ({ label: name, value: name }));
import { classificationAlgorithms, featureAlgorithms, regressorAlgorithms } from './utils';


// 分类指标 // 分类指标
export const classificationMetrics = [ export const classificationMetrics = [
@@ -280,9 +218,9 @@ function ExecuteConfig() {
<Form.Item <Form.Item
label="集成模型数量" label="集成模型数量"
name="ensemble_size" name="ensemble_size"
tooltip="集成模型数量,如果设置为0,则没有集成。默认50"
tooltip="集成模型数量,必须是大于等于1的整数,默认50"
> >
<InputNumber placeholder="请输入集成模型数量" min={0} precision={0} />
<InputNumber placeholder="请输入集成模型数量" min={1} precision={0} />
</Form.Item> </Form.Item>
</Col> </Col>
</Row> </Row>
@@ -292,7 +230,7 @@ function ExecuteConfig() {
<Form.Item <Form.Item
label="集成最佳模型数量" label="集成最佳模型数量"
name="ensemble_nbest" name="ensemble_nbest"
tooltip="仅集成最佳的N个模型"
tooltip="仅集成最佳的N个模型,必须是大于等于1的整数"
> >
<InputNumber placeholder="请输入集成最佳模型数量" min={1} precision={0} /> <InputNumber placeholder="请输入集成最佳模型数量" min={1} precision={0} />
</Form.Item> </Form.Item>
@@ -419,6 +357,7 @@ function ExecuteConfig() {
<Form.Item <Form.Item
label="交叉验证折数" label="交叉验证折数"
name="folds" name="folds"
tooltip="交叉验证折数必须是大于等于2的整数"
rules={[ rules={[
{ {
required: true, required: true,
@@ -426,7 +365,7 @@ function ExecuteConfig() {
}, },
]} ]}
> >
<InputNumber placeholder="请输入交叉验证折数" min={1} precision={0} />
<InputNumber placeholder="请输入交叉验证折数" min={2} precision={0} />
</Form.Item> </Form.Item>
</Col> </Col>
</Row> </Row>


+ 78
- 0
react-ui/src/pages/AutoML/components/CreateForm/utils.ts View File

@@ -0,0 +1,78 @@
// 分类算法
export const classificationAlgorithms = [
{ label: 'adaboost (自适应提升算法)', value: 'adaboost' },
{ label: 'bernoulli_nb (伯努利朴素贝叶斯)', value: 'bernoulli_nb' },
{ label: 'decision_tree (决策树)', value: 'decision_tree' },
{ label: 'extra_trees (极端随机树)', value: 'extra_trees' },
{ label: 'gaussian_nb (高斯朴素贝叶斯)', value: 'gaussian_nb' },
{ label: 'gradient_boosting (梯度提升)', value: 'gradient_boosting' },
{ label: 'k_nearest_neighbors (k近邻)', value: 'k_nearest_neighbors' },
{ label: 'lda (线性判别分析)', value: 'lda' },
{ label: 'liblinear_svc (liblinear支持向量分类)', value: 'liblinear_svc' },
{ label: 'libsvm_svc (libsvm支持向量分类)', value: 'libsvm_svc' },
{ label: 'mlp (多层感知器)', value: 'mlp' },
{ label: 'multinomial_nb (多项式朴素贝叶斯)', value: 'multinomial_nb' },
{ label: 'passive_aggressive (被动攻击算法)', value: 'passive_aggressive' },
{ label: 'qda (二次判别式分析)', value: 'qda' },
{ label: 'random_forest (随机森林)', value: 'random_forest' },
{ label: 'sgd (随机梯度下降)', value: 'sgd' },
{ label: 'tablenet (表格网络)', value: 'tablenet' },
{ label: 'LightGBMClassification (轻量梯度提升机分类)', value: 'LightGBMClassification' },
{ label: 'XGBoostClassification (极端梯度提升机分类)', value: 'XGBoostClassification' },
{ label: 'StackingClassification (堆叠泛化)', value: 'StackingClassification' },
];

// 回归算法
export const regressorAlgorithms = [
{ label: 'adaboost (自适应提升算法)', value: 'adaboost' },
{ label: 'ard_regression (自动相关性确定回归)', value: 'ard_regression' },
{ label: 'decision_tree (决策树)', value: 'decision_tree' },
{ label: 'extra_trees (极端随机树)', value: 'extra_trees' },
{ label: 'gaussian_process (高斯过程回归)', value: 'gaussian_process' },
{ label: 'gradient_boosting (梯度提升)', value: 'gradient_boosting' },
{ label: 'k_nearest_neighbors (梯度提升)', value: 'k_nearest_neighbors' },
{ label: 'liblinear_svr (liblinear支持向量回归)', value: 'liblinear_svr' },
{ label: 'libsvm_svr (libsvm支持向量回归)', value: 'libsvm_svr' },
{ label: 'mlp (多层感知器)', value: 'mlp' },
{ label: 'random_forest (随机森林)', value: 'random_forest' },
{ label: 'sgd (随机梯度下降)', value: 'sgd' },
{ label: 'LightGBMRegression (轻量梯度提升机回归)', value: 'LightGBMRegression' },
{ label: 'XGBoostRegression (极端梯度提升机回归)', value: 'XGBoostRegression' },
];

// 特征预处理算法
export const featureAlgorithms = [
{ label: 'densifier (数据增稠)', value: 'densifier' },
{
label: 'extra_trees_preproc_for_classification (分类任务极端随机树)',
value: 'extra_trees_preproc_for_classification',
},
{
label: 'extra_trees_preproc_for_regression (回归任务极端随机树)',
value: 'extra_trees_preproc_for_regression',
},
{ label: 'fast_ica (快速独立成分分析)', value: 'fast_ica' },
{ label: 'feature_agglomeration (特征聚合)', value: 'feature_agglomeration' },
{ label: 'kernel_pca (核主成分分析)', value: 'kernel_pca' },
{ label: 'kitchen_sinks (随机特征映射)', value: 'kitchen_sinks' },
{ label: 'liblinear_svc_preprocessor (线性svc预处理器)', value: 'liblinear_svc_preprocessor' },
{ label: 'no_preprocessing (无预处理)', value: 'no_preprocessing' },
{ label: 'nystroem_sampler (尼斯特罗姆采样器)', value: 'nystroem_sampler' },
{ label: 'pca (主成分分析)', value: 'pca' },
{ label: 'polynomial (多项式特征扩展)', value: 'polynomial' },
{ label: 'random_trees_embedding (随机森林特征嵌入)', value: 'random_trees_embedding' },
{
label: 'select_percentile_classification (基于百分位的分类特征选择)',
value: 'select_percentile_classification',
},
{
label: 'select_percentile_regression (基于百分位的回归特征选择)',
value: 'select_percentile_regression',
},
{
label: 'select_rates_classification (基于比率的分类特征选择)',
value: 'select_rates_classification',
},
{ label: 'select_rates_regression (基于比率的回归特征选择)', value: 'select_rates_regression' },
{ label: 'truncatedSVD (截断奇异值分解)', value: 'truncatedSVD' },
];

+ 0
- 4
react-ui/src/pages/AutoML/components/ExperimentInstanceList/index.less View File

@@ -54,10 +54,6 @@
display: flex; display: flex;
align-items: center; align-items: center;
width: 200px; width: 200px;

.statusIcon {
visibility: visible;
}
} }
} }




+ 16
- 8
react-ui/src/pages/AutoML/components/ExperimentInstanceList/index.tsx View File

@@ -34,7 +34,14 @@ function ExperimentInstanceList({
}: ExperimentInstanceListProps) { }: ExperimentInstanceListProps) {
const { message } = App.useApp(); const { message } = App.useApp();
const allIntanceIds = useMemo(() => { const allIntanceIds = useMemo(() => {
return experimentInsList?.map((item) => item.id) || [];
return (
experimentInsList
?.filter(
(item) =>
item.status !== ExperimentStatus.Running && item.status !== ExperimentStatus.Pending,
)
.map((item) => item.id) || []
);
}, [experimentInsList]); }, [experimentInsList]);
const [ const [
selectedIns, selectedIns,
@@ -126,7 +133,12 @@ function ExperimentInstanceList({
<div> <div>
<div className={styles.tableExpandBox} style={{ paddingBottom: '16px' }}> <div className={styles.tableExpandBox} style={{ paddingBottom: '16px' }}>
<div className={styles.check}> <div className={styles.check}>
<Checkbox checked={checked} indeterminate={indeterminate} onChange={checkAll}></Checkbox>
<Checkbox
checked={checked}
indeterminate={indeterminate}
disabled={allIntanceIds.length === 0}
onChange={checkAll}
></Checkbox>
</div> </div>
<div className={styles.index}>序号</div> <div className={styles.index}>序号</div>
<div className={styles.description}>运行时长</div> <div className={styles.description}>运行时长</div>
@@ -171,12 +183,8 @@ function ExperimentInstanceList({
{index + 1} {index + 1}
</a> </a>
<ExperimentInstanceComponent <ExperimentInstanceComponent
create_time={item.create_time}
finish_time={item.finish_time}
status={item.status as ExperimentStatus}
argo_ins_name={item.argo_ins_name}
argo_ins_ns={item.argo_ins_ns}
experimentInsId={item.id}
experimentId={item[config['idInsProperty'] as keyof ExperimentInstance] as number}
instance={item}
></ExperimentInstanceComponent> ></ExperimentInstanceComponent>
<div className={styles.operation}> <div className={styles.operation}>
<Button <Button


+ 35
- 32
react-ui/src/pages/AutoML/components/ExperimentInstanceList/instance.tsx View File

@@ -2,67 +2,70 @@ import RunDuration from '@/components/RunDuration';
import { ExperimentStatus } from '@/enums'; import { ExperimentStatus } from '@/enums';
import { useSSE, type MessageHandler } from '@/hooks/useSSE'; import { useSSE, type MessageHandler } from '@/hooks/useSSE';
import { experimentStatusInfo } from '@/pages/Experiment/status'; import { experimentStatusInfo } from '@/pages/Experiment/status';
import { ExperimentInstance, NodeStatus } from '@/types';
import { ExperimentCompleted } from '@/utils/constant'; import { ExperimentCompleted } from '@/utils/constant';
import { formatDate } from '@/utils/date'; import { formatDate } from '@/utils/date';
import { getWorkflowStatus } from '@/utils/experiment';
import { Typography } from 'antd'; import { Typography } from 'antd';
import React, { useCallback } from 'react'; import React, { useCallback } from 'react';
import styles from './index.less'; import styles from './index.less';


type ExperimentInstanceProps = {
create_time?: string;
finish_time?: string;
status: ExperimentStatus;
argo_ins_name: string;
argo_ins_ns: string;
experimentInsId: number;
type ExperimentInstanceComponentProps = {
experimentId: number;
instance: ExperimentInstance;
}; };


function ExperimentInstance({
create_time,
finish_time,
status,
argo_ins_name,
argo_ins_ns,
experimentInsId,
}: ExperimentInstanceProps) {
function ExperimentInstanceComponent({ experimentId, instance }: ExperimentInstanceComponentProps) {
const { id, argo_ins_name, argo_ins_ns, node_status } = instance;
const workflowStatus = getWorkflowStatus(node_status) as NodeStatus | undefined;
const status = instance.status as ExperimentStatus;
const createTime = workflowStatus?.startedAt;
const finishTime = workflowStatus?.finishedAt;
const statusInfo = experimentStatusInfo[status];
const handleSSEMessage: MessageHandler = useCallback( const handleSSEMessage: MessageHandler = useCallback(
(experimentInsId: number, status: string, finish_time: string) => {
(experimentId: number, experimentInsId: number, status: string, finishTime: string) => {
window.postMessage({ window.postMessage({
type: ExperimentCompleted, type: ExperimentCompleted,
payload: { payload: {
id: experimentInsId,
experimentId,
experimentInsId,
status, status,
finish_time,
finishTime,
}, },
}); });
}, },
[], [],
); );
useSSE(experimentInsId, status, argo_ins_name, argo_ins_ns, handleSSEMessage);
useSSE(experimentId, id, status, argo_ins_name, argo_ins_ns, handleSSEMessage);


return ( return (
<React.Fragment> <React.Fragment>
<div className={styles.description}> <div className={styles.description}>
<RunDuration createTime={create_time} finishTime={finish_time} />
<RunDuration createTime={createTime} finishTime={finishTime} />
</div> </div>
<div className={styles.startTime}> <div className={styles.startTime}>
<Typography.Text ellipsis={{ tooltip: formatDate(create_time) }}>
{formatDate(create_time)}
<Typography.Text ellipsis={{ tooltip: formatDate(createTime) }}>
{formatDate(createTime)}
</Typography.Text> </Typography.Text>
</div> </div>
<div className={styles.statusBox}> <div className={styles.statusBox}>
<img
style={{ width: '17px', marginRight: '7px' }}
src={experimentStatusInfo[status]?.icon}
draggable={false}
alt=""
/>
<span style={{ color: experimentStatusInfo[status]?.color }} className={styles.statusIcon}>
{experimentStatusInfo[status]?.label}
</span>
{statusInfo ? (
<>
<img
style={{ width: '17px', marginRight: '7px' }}
src={statusInfo.icon}
draggable={false}
alt=""
/>
<span style={{ color: statusInfo.color }}>{statusInfo.label}</span>
</>
) : (
'--'
)}
</div> </div>
</React.Fragment> </React.Fragment>
); );
} }


export default ExperimentInstance;
export default ExperimentInstanceComponent;

+ 14
- 3
react-ui/src/pages/AutoML/components/ExperimentList/config.ts View File

@@ -8,6 +8,7 @@ import {
batchDeleteActiveLearnInsReq, batchDeleteActiveLearnInsReq,
deleteActiveLearnInsReq, deleteActiveLearnInsReq,
deleteActiveLearnReq, deleteActiveLearnReq,
editActiveLearnInsReq,
getActiveLearnInsListReq, getActiveLearnInsListReq,
getActiveLearnListReq, getActiveLearnListReq,
runActiveLearnReq, runActiveLearnReq,
@@ -17,6 +18,7 @@ import {
batchDeleteExperimentInsReq, batchDeleteExperimentInsReq,
deleteAutoMLReq, deleteAutoMLReq,
deleteExperimentInsReq, deleteExperimentInsReq,
editExperimentInsReq,
getAutoMLListReq, getAutoMLListReq,
getExperimentInsListReq, getExperimentInsListReq,
runAutoMLReq, runAutoMLReq,
@@ -26,6 +28,7 @@ import {
batchDeleteRayInsReq, batchDeleteRayInsReq,
deleteRayInsReq, deleteRayInsReq,
deleteRayReq, deleteRayReq,
editRayInsReq,
getRayInsListReq, getRayInsListReq,
getRayListReq, getRayListReq,
runRayReq, runRayReq,
@@ -39,18 +42,20 @@ export enum ExperimentListType {
} }


type ExperimentListInfo = { type ExperimentListInfo = {
getListReq: (params: any) => Promise<any>; // 获取列表
getInsListReq: (params: any) => Promise<any>; // 获取实例列表
getListReq: (params: any, skipLoading?: boolean) => Promise<any>; // 获取列表
getInsListReq: (params: any, skipLoading?: boolean) => Promise<any>; // 获取实例列表
deleteRecordReq: (params: any) => Promise<any>; // 删除 deleteRecordReq: (params: any) => Promise<any>; // 删除
runRecordReq: (params: any) => Promise<any>; // 运行 runRecordReq: (params: any) => Promise<any>; // 运行
deleteInsReq: (params: any) => Promise<any>; // 删除实例 deleteInsReq: (params: any) => Promise<any>; // 删除实例
batchDeleteInsReq: (params: any) => Promise<any>; // 批量删除实例 batchDeleteInsReq: (params: any) => Promise<any>; // 批量删除实例
stopInsReq: (params: any) => Promise<any>; // 终止实例 stopInsReq: (params: any) => Promise<any>; // 终止实例
editInsReq: (params: any) => Promise<any>; // 编辑实例
title: string; // 标题 title: string; // 标题
pathPrefix: string; // 路由路径前缀 pathPrefix: string; // 路由路径前缀
idProperty: string; // ID属性 idProperty: string; // ID属性
nameProperty: string; // 名称属性 nameProperty: string; // 名称属性
descProperty: string; // 描述属性 descProperty: string; // 描述属性
idInsProperty: string; // 实例返回的ID属性
}; };


export const experimentListConfig: Record<ExperimentListType, ExperimentListInfo> = { export const experimentListConfig: Record<ExperimentListType, ExperimentListInfo> = {
@@ -62,11 +67,13 @@ export const experimentListConfig: Record<ExperimentListType, ExperimentListInfo
deleteInsReq: deleteExperimentInsReq, deleteInsReq: deleteExperimentInsReq,
batchDeleteInsReq: batchDeleteExperimentInsReq, batchDeleteInsReq: batchDeleteExperimentInsReq,
stopInsReq: stopExperimentInsReq, stopInsReq: stopExperimentInsReq,
title: '自主机器学习',
editInsReq: editExperimentInsReq,
title: '自动机器学习',
pathPrefix: 'automl', pathPrefix: 'automl',
nameProperty: 'name', nameProperty: 'name',
descProperty: 'description', descProperty: 'description',
idProperty: 'machineLearnId', idProperty: 'machineLearnId',
idInsProperty: 'machine_learn_id',
}, },
[ExperimentListType.HyperParameter]: { [ExperimentListType.HyperParameter]: {
getListReq: getRayListReq, getListReq: getRayListReq,
@@ -76,11 +83,13 @@ export const experimentListConfig: Record<ExperimentListType, ExperimentListInfo
deleteInsReq: deleteRayInsReq, deleteInsReq: deleteRayInsReq,
batchDeleteInsReq: batchDeleteRayInsReq, batchDeleteInsReq: batchDeleteRayInsReq,
stopInsReq: stopRayInsReq, stopInsReq: stopRayInsReq,
editInsReq: editRayInsReq,
title: '超参数自动寻优', title: '超参数自动寻优',
pathPrefix: 'hyperparameter', pathPrefix: 'hyperparameter',
nameProperty: 'name', nameProperty: 'name',
descProperty: 'description', descProperty: 'description',
idProperty: 'rayId', idProperty: 'rayId',
idInsProperty: 'ray_id',
}, },
[ExperimentListType.ActiveLearn]: { [ExperimentListType.ActiveLearn]: {
getListReq: getActiveLearnListReq, getListReq: getActiveLearnListReq,
@@ -90,10 +99,12 @@ export const experimentListConfig: Record<ExperimentListType, ExperimentListInfo
deleteInsReq: deleteActiveLearnInsReq, deleteInsReq: deleteActiveLearnInsReq,
batchDeleteInsReq: batchDeleteActiveLearnInsReq, batchDeleteInsReq: batchDeleteActiveLearnInsReq,
stopInsReq: stopActiveLearnInsReq, stopInsReq: stopActiveLearnInsReq,
editInsReq: editActiveLearnInsReq,
title: '自动学习', title: '自动学习',
pathPrefix: 'active-learn', pathPrefix: 'active-learn',
nameProperty: 'name', nameProperty: 'name',
descProperty: 'description', descProperty: 'description',
idProperty: 'activeLearnId', idProperty: 'activeLearnId',
idInsProperty: 'active_learn_id',
}, },
}; };

+ 126
- 61
react-ui/src/pages/AutoML/components/ExperimentList/index.tsx View File

@@ -30,7 +30,7 @@ import {
} from 'antd'; } from 'antd';
import { type SearchProps } from 'antd/es/input'; import { type SearchProps } from 'antd/es/input';
import classNames from 'classnames'; import classNames from 'classnames';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useCallback, useEffect, useState } from 'react';
import ExperimentInstanceList from '../ExperimentInstanceList'; import ExperimentInstanceList from '../ExperimentInstanceList';
import { ExperimentListType, experimentListConfig } from './config'; import { ExperimentListType, experimentListConfig } from './config';
import styles from './index.less'; import styles from './index.less';
@@ -52,7 +52,6 @@ function ExperimentList({ type }: ExperimentListProps) {
const [experimentInsList, setExperimentInsList] = useState<ExperimentInstanceData[]>([]); const [experimentInsList, setExperimentInsList] = useState<ExperimentInstanceData[]>([]);
const [expandedRowKeys, setExpandedRowKeys] = useState<number[]>([]); const [expandedRowKeys, setExpandedRowKeys] = useState<number[]>([]);
const [experimentInsTotal, setExperimentInsTotal] = useState(0); const [experimentInsTotal, setExperimentInsTotal] = useState(0);
const [now] = useServerTime();
const [pagination, setPagination] = useState<TablePaginationConfig>( const [pagination, setPagination] = useState<TablePaginationConfig>(
cacheState?.pagination ?? { cacheState?.pagination ?? {
current: 1, current: 1,
@@ -60,38 +59,37 @@ function ExperimentList({ type }: ExperimentListProps) {
}, },
); );
const config = experimentListConfig[type]; const config = experimentListConfig[type];
const timerRef = useRef<ReturnType<typeof window.setTimeout> | undefined>();

// 获取自主机器学习或超参数自动优化列表
const getAutoMLList = useCallback(async () => {
const params: Record<string, any> = {
page: pagination.current! - 1,
size: pagination.pageSize,
[config.nameProperty]: searchText || undefined,
};
const request = config.getListReq;
const [res] = await to(request(params));
if (res && res.data) {
const { content = [], totalElements = 0 } = res.data;
setTableData(content);
setTotal(totalElements);
}
}, [pagination, searchText, config]);
const [now] = useServerTime();


useEffect(() => {
getAutoMLList();
}, [getAutoMLList]);
// 获取实验列表
const getExperimentList = useCallback(
async (skipLoading: boolean = false) => {
const params: Record<string, any> = {
page: pagination.current! - 1,
size: pagination.pageSize,
[config.nameProperty]: searchText || undefined,
};
const request = config.getListReq;
const [res] = await to(request(params, skipLoading));
if (res && res.data) {
const { content = [], totalElements = 0 } = res.data;
setTableData(content);
setTotal(totalElements);
}
},
[pagination, searchText, config],
);


// 获取实验实例列表 // 获取实验实例列表
const getExperimentInsList = useCallback( const getExperimentInsList = useCallback(
async (recordId: number, page: number, size: number) => {
async (recordId: number, page: number, size: number, skipLoading: boolean = false) => {
const params = { const params = {
[config.idProperty]: recordId, [config.idProperty]: recordId,
page: page, page: page,
size: size, size: size,
}; };
const request = config.getInsListReq; const request = config.getInsListReq;
const [res] = await to(request(params));
const [res] = await to(request(params, skipLoading));
if (res && res.data) { if (res && res.data) {
const { content = [], totalElements = 0 } = res.data; const { content = [], totalElements = 0 } = res.data;
try { try {
@@ -111,59 +109,115 @@ function ExperimentList({ type }: ExperimentListProps) {


// 刷新实验列表状态, // 刷新实验列表状态,
// TODO: 目前是直接刷新实验列表,后续需要优化,只刷新状态 // TODO: 目前是直接刷新实验列表,后续需要优化,只刷新状态
const refreshExperimentList = useCallback(() => {
getAutoMLList();
}, [getAutoMLList]);
const refreshExperimentList = useCallback(
(skipLoading: boolean = false) => {
getExperimentList(skipLoading);
},
[getExperimentList],
);


// 刷新实验实例列表 // 刷新实验实例列表
const refreshExperimentIns = useCallback( const refreshExperimentIns = useCallback(
(experimentId: number) => {
(experimentId: number, skipLoading: boolean = false) => {
const length = experimentInsList.length; const length = experimentInsList.length;
getExperimentInsList(experimentId, 0, length);
getExperimentInsList(experimentId, 0, length, skipLoading);
}, },
[getExperimentInsList, experimentInsList], [getExperimentInsList, experimentInsList],
); );


// 新增,删除版本时,重置分页,然后刷新版本列表
// 更新实验实例状态
const editExperimentIns = useCallback(
async (
experimentId: number,
experimentInsId: number,
status: ExperimentStatus,
argo_ins_name: string,
argo_ins_ns: string,
) => {
const params = {
[config.idInsProperty]: experimentId,
id: experimentInsId,
status: status,
argo_ins_name,
argo_ins_ns,
};
const request = config.editInsReq;
const [res] = await to(request(params));
if (res && res.data) {
refreshExperimentIns(experimentId, true);
refreshExperimentList(true);
}
},
[config, refreshExperimentIns, refreshExperimentList],
);

// 获取实验列表
useEffect(() => {
getExperimentList();
}, [getExperimentList]);

// expandedRowKeys 变化
useEffect(() => {
if (expandedRowKeys.length > 0) {
getExperimentInsList(expandedRowKeys[0], 0, 5);
refreshExperimentList();
}
}, [expandedRowKeys, getExperimentInsList, refreshExperimentList]);

// 实验实例状态变化
useEffect(() => { useEffect(() => {
const handleMessage = (e: MessageEvent) => { const handleMessage = (e: MessageEvent) => {
const { type, payload } = e.data; const { type, payload } = e.data;
if (type === ExperimentCompleted) { if (type === ExperimentCompleted) {
const { id, status, finish_time } = payload;

// 修改实例的状态和结束时间
setExperimentInsList((prev) =>
prev.map((v) =>
v.id === id
? {
...v,
status: status,
finish_time: finish_time,
}
: v,
),
const { experimentId, experimentInsId, status, finishTime } = payload;
const currentIns = experimentInsList.find((v) => v.id === experimentInsId);
console.log(
'实验实例状态变化',
currentIns?.status,
status,
experimentId,
experimentInsId,
finishTime,
); );


if (timerRef.current) {
clearTimeout(timerRef.current);
timerRef.current = undefined;
if (
!currentIns ||
currentIns.status === ExperimentStatus.Terminated ||
currentIns.status === status
) {
return;
} }


timerRef.current = setTimeout(() => {
refreshExperimentList();
}, 10000);
// refreshExperimentList(true);
// refreshExperimentIns(experimentId);
editExperimentIns(
experimentId,
experimentInsId,
status,
currentIns.argo_ins_name,
currentIns.argo_ins_ns,
);

// 修改实例的状态和结束时间
// setExperimentInsList((prev) =>
// prev.map((v) =>
// v.id === experimentInsId
// ? {
// ...v,
// status: status,
// finish_time: finishTime,
// }
// : v,
// ),
// );
} }
}; };


window.addEventListener('message', handleMessage); window.addEventListener('message', handleMessage);
return () => { return () => {
window.removeEventListener('message', handleMessage); window.removeEventListener('message', handleMessage);
if (timerRef.current) {
clearTimeout(timerRef.current);
timerRef.current = undefined;
}
}; };
}, [refreshExperimentList]);
}, [experimentInsList, editExperimentIns]);


// 搜索 // 搜索
const onSearch: SearchProps['onSearch'] = (value) => { const onSearch: SearchProps['onSearch'] = (value) => {
@@ -207,6 +261,7 @@ function ExperimentList({ type }: ExperimentListProps) {
setCacheState({ setCacheState({
pagination, pagination,
searchText, searchText,
expandedRowKeys,
}); });


if (record) { if (record) {
@@ -225,6 +280,7 @@ function ExperimentList({ type }: ExperimentListProps) {
setCacheState({ setCacheState({
pagination, pagination,
searchText, searchText,
expandedRowKeys,
}); });


navigate(`info/${record.id}`); navigate(`info/${record.id}`);
@@ -237,8 +293,8 @@ function ExperimentList({ type }: ExperimentListProps) {
if (res) { if (res) {
message.success('运行成功'); message.success('运行成功');
setExpandedRowKeys([record.id]); setExpandedRowKeys([record.id]);
refreshExperimentList();
getExperimentInsList(record.id, 0, 5);
// getExperimentInsList(record.id, 0, 5);
// refreshExperimentList();
} }
}; };


@@ -248,8 +304,8 @@ function ExperimentList({ type }: ExperimentListProps) {
setExperimentInsList([]); setExperimentInsList([]);
if (expanded) { if (expanded) {
setExpandedRowKeys([record.id]); setExpandedRowKeys([record.id]);
getExperimentInsList(record.id, 0, 5);
refreshExperimentList();
// getExperimentInsList(record.id, 0, 5);
// refreshExperimentList();
} else { } else {
setExpandedRowKeys([]); setExpandedRowKeys([]);
} }
@@ -257,6 +313,11 @@ function ExperimentList({ type }: ExperimentListProps) {


// 跳转到实验实例详情 // 跳转到实验实例详情
const gotoInstanceInfo = (autoML: AutoMLData, record: ExperimentInstanceData) => { const gotoInstanceInfo = (autoML: AutoMLData, record: ExperimentInstanceData) => {
setCacheState({
pagination,
searchText,
expandedRowKeys,
});
navigate(`instance/${autoML.id}/${record.id}`); navigate(`instance/${autoML.id}/${record.id}`);
}; };


@@ -269,8 +330,7 @@ function ExperimentList({ type }: ExperimentListProps) {


// 实验实例终止 // 实验实例终止
const handleInstanceTerminate = async (experimentIns: ExperimentInstanceData) => { const handleInstanceTerminate = async (experimentIns: ExperimentInstanceData) => {
// 刷新实验列表
refreshExperimentList();
// 修改实例的状态和结束时间
setExperimentInsList((prevList) => { setExperimentInsList((prevList) => {
return prevList.map((item) => { return prevList.map((item) => {
if (item.id === experimentIns.id) { if (item.id === experimentIns.id) {
@@ -283,6 +343,11 @@ function ExperimentList({ type }: ExperimentListProps) {
return item; return item;
}); });
}); });
// 刷新实验列表和实例列表
refreshExperimentList(true);
if (expandedRowKeys.length > 0) {
refreshExperimentIns(expandedRowKeys[0]);
}
}; };
// --------------------------- Table --------------------------- // --------------------------- Table ---------------------------
// 分页切换 // 分页切换
@@ -330,7 +395,7 @@ function ExperimentList({ type }: ExperimentListProps) {
}, },
...diffColumns, ...diffColumns,
{ {
title: '创建时间',
title: '更新时间',
dataIndex: 'update_time', dataIndex: 'update_time',
key: 'update_time', key: 'update_time',
width: '20%', width: '20%',


+ 7
- 0
react-ui/src/pages/AutoML/components/ExperimentLog/empty.tsx View File

@@ -0,0 +1,7 @@
import styles from './index.less';

function EmptyLog() {
return <div className={styles['empty-log']}>暂无日志</div>;
}

export default EmptyLog;

+ 11
- 0
react-ui/src/pages/AutoML/components/ExperimentLog/index.less View File

@@ -5,3 +5,14 @@
height: 100%; height: 100%;
} }
} }

.empty-log {
height: 100%;
padding: 15px;
color: white;
font-size: 14px;
white-space: pre-line;
text-align: center;
word-break: break-all;
background: #19253b;
}

+ 4
- 1
react-ui/src/pages/AutoML/components/ExperimentLog/index.tsx View File

@@ -2,6 +2,7 @@ import { ExperimentStatus } from '@/enums';
import { AutoMLInstanceData } from '@/pages/AutoML/types'; import { AutoMLInstanceData } from '@/pages/AutoML/types';
import LogList from '@/pages/Experiment/components/LogList'; import LogList from '@/pages/Experiment/components/LogList';
import { NodeStatus } from '@/types'; import { NodeStatus } from '@/types';
import EmptyLog from './empty';
import styles from './index.less'; import styles from './index.less';


const NodePrefix = 'auto-ml'; const NodePrefix = 'auto-ml';
@@ -19,7 +20,7 @@ function ExperimentLog({ instanceInfo, nodes }: ExperimentLogProps) {
return ( return (
<div className={styles['experiment-log']}> <div className={styles['experiment-log']}>
<div className={styles['experiment-log__log']}> <div className={styles['experiment-log__log']}>
{nodeStatus && (
{nodeStatus ? (
<LogList <LogList
instanceName={instanceInfo.argo_ins_name} instanceName={instanceInfo.argo_ins_name}
instanceNamespace={instanceInfo.argo_ins_ns} instanceNamespace={instanceInfo.argo_ins_ns}
@@ -28,6 +29,8 @@ function ExperimentLog({ instanceInfo, nodes }: ExperimentLogProps) {
instanceNodeStartTime={nodeStatus.startedAt} instanceNodeStartTime={nodeStatus.startedAt}
instanceNodeStatus={nodeStatus.phase as ExperimentStatus} instanceNodeStatus={nodeStatus.phase as ExperimentStatus}
></LogList> ></LogList>
) : (
<EmptyLog />
)} )}
</div> </div>
</div> </div>


+ 18
- 16
react-ui/src/pages/AutoML/components/ExperimentRunBasic/index.tsx View File

@@ -3,59 +3,61 @@ import RunDuration from '@/components/RunDuration';
import { ExperimentStatus } from '@/enums'; import { ExperimentStatus } from '@/enums';
import { experimentStatusInfo } from '@/pages/Experiment/status'; import { experimentStatusInfo } from '@/pages/Experiment/status';
import { type NodeStatus } from '@/types'; import { type NodeStatus } from '@/types';
import { getExperimentInstanceStatus } from '@/utils/experiment';
import { formatDate } from '@/utils/format'; import { formatDate } from '@/utils/format';
import { Flex } from 'antd'; import { Flex } from 'antd';
import { useMemo } from 'react'; import { useMemo } from 'react';


type ExperimentRunBasicProps = { type ExperimentRunBasicProps = {
create_time?: string;
runStatus?: NodeStatus;
workflowStatus?: NodeStatus;
instanceStatus?: ExperimentStatus; instanceStatus?: ExperimentStatus;
}; };


function ExperimentRunBasic({ create_time, runStatus, instanceStatus }: ExperimentRunBasicProps) {
function ExperimentRunBasic({ workflowStatus, instanceStatus }: ExperimentRunBasicProps) {
const instanceDatas = useMemo(() => { const instanceDatas = useMemo(() => {
if (!runStatus) {
return [];
}

const status =
instanceStatus === ExperimentStatus.Terminated ? instanceStatus : runStatus.phase;
const status = getExperimentInstanceStatus(instanceStatus as ExperimentStatus, workflowStatus);
const statusInfo = experimentStatusInfo[status]; const statusInfo = experimentStatusInfo[status];


return [ return [
{ {
label: '启动时间', label: '启动时间',
value: formatDate(create_time),
value: formatDate(workflowStatus?.startedAt),
}, },
{ {
label: '执行时长', label: '执行时长',
value: <RunDuration createTime={create_time} finishTime={runStatus.finishedAt} />,
value: (
<RunDuration
createTime={workflowStatus?.startedAt}
finishTime={workflowStatus?.finishedAt}
/>
),
}, },
{ {
label: '状态', label: '状态',
value: (
value: statusInfo ? (
<Flex align="center"> <Flex align="center">
<img <img
style={{ width: '17px', marginRight: '7px' }} style={{ width: '17px', marginRight: '7px' }}
src={statusInfo?.icon}
src={statusInfo.icon}
draggable={false} draggable={false}
alt="" alt=""
/> />
<div <div
style={{ style={{
color: statusInfo?.color,
color: statusInfo.color,
fontSize: '15px', fontSize: '15px',
lineHeight: 1.6, lineHeight: 1.6,
}} }}
> >
{statusInfo?.label}
{statusInfo.label}
</div> </div>
</Flex> </Flex>
) : (
'--'
), ),
}, },
]; ];
}, [runStatus, create_time, instanceStatus]);
}, [workflowStatus, instanceStatus]);


return ( return (
<ConfigInfo <ConfigInfo


+ 8
- 0
react-ui/src/pages/AutoML/components/ExperimentVisualResult/index.less View File

@@ -0,0 +1,8 @@
.experiment-visual {
width: 100%;
height: 100%;

&__empty {
height: 100%;
}
}

+ 69
- 17
react-ui/src/pages/AutoML/components/ExperimentVisualResult/index.tsx View File

@@ -5,10 +5,15 @@
*/ */


import IframePage, { IframePageType } from '@/components/IFramePage'; import IframePage, { IframePageType } from '@/components/IFramePage';
import { runTensorBoardReq } from '@/services/experiment/index.js';
import KFEmpty, { EmptyType } from '@/components/KFEmpty';
import KFSpin from '@/components/KFSpin';
import { TensorBoardStatus } from '@/enums';
import { getTensorBoardStatusReq, runTensorBoardReq } from '@/services/experiment/index.js';
import { to } from '@/utils/promise'; import { to } from '@/utils/promise';
import SessionStorage from '@/utils/sessionStorage'; import SessionStorage from '@/utils/sessionStorage';
import { useEffect, useState } from 'react';
import { LoadingOutlined } from '@ant-design/icons';
import { useCallback, useEffect, useState } from 'react';
import styles from './index.less';


type TensorBoardProps = { type TensorBoardProps = {
namespace?: string; namespace?: string;
@@ -16,28 +21,75 @@ type TensorBoardProps = {
}; };


function ExperimentVisualResult({ namespace, path }: TensorBoardProps) { function ExperimentVisualResult({ namespace, path }: TensorBoardProps) {
const [tensorboardUrl, setTensorboardUrl] = useState('');
useEffect(() => {
// 运行 TensorBoard
const runTensorBoard = async () => {
const params = {
namespace: namespace,
path: path,
};
const [res] = await to(runTensorBoardReq(params));
if (res && res.data) {
const url = res.data;
SessionStorage.setItem(SessionStorage.tensorBoardUrlKey, url);
setTensorboardUrl(url);
const [tensorboardUrl, setTensorboardUrl] = useState<string | undefined | null>(undefined);
const [status, setStatus] = useState<TensorBoardStatus | undefined>(undefined);

// 获取 TensorBoard 状态
const getTensorBoardStatus = useCallback(async () => {
const params = {
namespace: namespace,
path: path,
};
const [res] = await to(getTensorBoardStatusReq(params));
if (res && res.data) {
const status = res.data.status as TensorBoardStatus | undefined;
setStatus(res.data.status);
if (!status || status === TensorBoardStatus.Pending) {
setTimeout(() => {
getTensorBoardStatus();
}, 5000);
} }
}
}, [namespace, path]);

// 运行 TensorBoard
const runTensorBoard = useCallback(async () => {
const params = {
namespace: namespace,
path: path,
}; };
const [res] = await to(runTensorBoardReq(params));
if (res && res.data) {
const url = res.data;
SessionStorage.setItem(SessionStorage.tensorBoardUrlKey, url);
setTensorboardUrl(url);
getTensorBoardStatus();
} else {
setTensorboardUrl(null);
}
}, [namespace, path, getTensorBoardStatus]);


useEffect(() => {
if (namespace && path) { if (namespace && path) {
runTensorBoard(); runTensorBoard();
} }
}, [namespace, path]);
}, [namespace, path, runTensorBoard]);


return <>{tensorboardUrl && <IframePage type={IframePageType.TensorBoard}></IframePage>}</>;
if (tensorboardUrl === null || status === TensorBoardStatus.Failed) {
return (
<div className={styles['experiment-visual']}>
<KFEmpty
className={styles['experiment-visual__empty']}
type={EmptyType.NoData}
title="运行可视化失败"
buttonTitle="重新运行"
onButtonClick={runTensorBoard}
/>
</div>
);
} else if (status === TensorBoardStatus.Pending) {
return (
<div className={styles['experiment-visual']}>
<KFSpin indicator={<LoadingOutlined spin />} size="large" />
</div>
);
} else if (status === TensorBoardStatus.Running) {
return (
<div className={styles['experiment-visual']}>
<IframePage type={IframePageType.TensorBoard}></IframePage>
</div>
);
}
} }


export default ExperimentVisualResult; export default ExperimentVisualResult;

+ 2
- 2
react-ui/src/pages/Dataset/components/AddModelModal/index.tsx View File

@@ -126,7 +126,7 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps)
<Form.Item label="模型框架" name="model_type"> <Form.Item label="模型框架" name="model_type">
<Select <Select
allowClear allowClear
placeholder="请选择模型类型"
placeholder="请选择模型框架"
options={typeList} options={typeList}
fieldNames={{ label: 'name', value: 'name' }} fieldNames={{ label: 'name', value: 'name' }}
optionFilterProp="name" optionFilterProp="name"
@@ -136,7 +136,7 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps)
<Form.Item label="模型能力" name="model_tag"> <Form.Item label="模型能力" name="model_tag">
<Select <Select
allowClear allowClear
placeholder="请选择模型标签"
placeholder="请选择模型能力"
options={tagList} options={tagList}
fieldNames={{ label: 'name', value: 'name' }} fieldNames={{ label: 'name', value: 'name' }}
optionFilterProp="name" optionFilterProp="name"


+ 4
- 0
react-ui/src/pages/Dataset/components/ResourceInfo/index.less View File

@@ -42,6 +42,10 @@
border-radius: 4px; border-radius: 4px;
cursor: pointer; cursor: pointer;


&:hover {
border-color: .addAlpha(@primary-color, 0.5) [];
}

&--praised { &--praised {
color: @primary-color; color: @primary-color;
} }


+ 1
- 3
react-ui/src/pages/Dataset/components/ResourceItem/index.tsx View File

@@ -14,9 +14,7 @@ type ResourceItemProps = {
}; };


function ResourceItem({ item, isPublic, onClick, onRemove }: ResourceItemProps) { function ResourceItem({ item, isPublic, onClick, onRemove }: ResourceItemProps) {
const timeAgo = `更新于${
item.update_time ? formatDate(item.update_time, 'YYYY-MM-DD') : item.time_ago ?? ''
}`;
const timeAgo = `最近更新:${formatDate(item.full_last_update_time, 'YYYY-MM-DD HH:mm')}`;
const create_by = item.create_by ?? ''; const create_by = item.create_by ?? '';
return ( return (
<div className={styles['resource-item']} onClick={() => onClick(item)}> <div className={styles['resource-item']} onClick={() => onClick(item)}>


+ 7
- 0
react-ui/src/pages/Dataset/components/ResourceList/index.tsx View File

@@ -16,6 +16,7 @@ import styles from './index.less';


export type ResourceListRef = { export type ResourceListRef = {
reset: () => void; reset: () => void;
resetPage: () => void;
}; };


type ResourceListProps = { type ResourceListProps = {
@@ -97,6 +98,12 @@ function ResourceList(
setDataList(undefined); setDataList(undefined);
setTotal(0); setTotal(0);
}, },
resetPage: () => {
setPagination((prev) => ({
...prev,
current: 1,
}));
},
}; };
}, },
[], [],


+ 3
- 1
react-ui/src/pages/Dataset/components/ResourcePage/index.tsx View File

@@ -56,11 +56,13 @@ function ResourcePage({ resourceType }: ResourcePageProps) {


// 选择类型 // 选择类型
const chooseType = (record: CategoryData) => { const chooseType = (record: CategoryData) => {
dataListRef.current?.resetPage();
setActiveType((prev) => (prev === record.name ? undefined : record.name)); setActiveType((prev) => (prev === record.name ? undefined : record.name));
}; };


// 选择 Tag // 选择 Tag
const chooseTag = (record: CategoryData) => { const chooseTag = (record: CategoryData) => {
dataListRef.current?.resetPage();
setActiveTag((prev) => (prev === record.name ? undefined : record.name)); setActiveTag((prev) => (prev === record.name ? undefined : record.name));
}; };


@@ -96,7 +98,7 @@ function ResourcePage({ resourceType }: ResourcePageProps) {
dataType={activeType} dataType={activeType}
dataTag={activeTag} dataTag={activeTag}
initialSearchText={cacheState?.searchText} initialSearchText={cacheState?.searchText}
initialPagination={cacheState?.initialPagination}
initialPagination={cacheState?.pagination}
setCacheState={setCacheState} setCacheState={setCacheState}
></ResourceList> ></ResourceList>
</Flex> </Flex>


+ 1
- 0
react-ui/src/pages/Dataset/config.tsx View File

@@ -164,6 +164,7 @@ export interface ResourceData {
train_task?: TrainTask; // 训练任务 train_task?: TrainTask; // 训练任务
praises_count: number; // 点赞数 praises_count: number; // 点赞数
praised: boolean; // 是否点赞 praised: boolean; // 是否点赞
full_last_update_time: string; // 完整的更新时间
} }


// 数据集数据 // 数据集数据


+ 1
- 1
react-ui/src/pages/Experiment/Comparison/index.tsx View File

@@ -77,7 +77,7 @@ function ExperimentComparison() {
const url = res.data; const url = res.data;
// window.open(url, '_blank'); // window.open(url, '_blank');
SessionStorage.setItem(SessionStorage.aimUrlKey, url); SessionStorage.setItem(SessionStorage.aimUrlKey, url);
navigate('../compare-visual');
navigate('compare-visual');
} }
}; };




+ 102
- 54
react-ui/src/pages/Experiment/Info/index.jsx View File

@@ -3,10 +3,10 @@ import { ExperimentStatus } from '@/enums';
import { useStateRef } from '@/hooks/useStateRef'; import { useStateRef } from '@/hooks/useStateRef';
import { useVisible } from '@/hooks/useVisible'; import { useVisible } from '@/hooks/useVisible';
import { getExperimentIns } from '@/services/experiment/index.js'; import { getExperimentIns } from '@/services/experiment/index.js';
import { getWorkflowById } from '@/services/pipeline/index.js';
import themes from '@/styles/theme.less'; import themes from '@/styles/theme.less';
import { fittingString, parseJsonText } from '@/utils'; import { fittingString, parseJsonText } from '@/utils';
import { formatDate } from '@/utils/date'; import { formatDate } from '@/utils/date';
import { getExperimentInstanceStatus } from '@/utils/experiment';
import { to } from '@/utils/promise'; import { to } from '@/utils/promise';
import G6, { Util } from '@antv/g6'; import G6, { Util } from '@antv/g6';
import { Button } from 'antd'; import { Button } from 'antd';
@@ -18,10 +18,12 @@ import { experimentStatusInfo } from '../status';
import styles from './index.less'; import styles from './index.less';


let graph = null; let graph = null;
const NodePrefix = 'workflow';


function ExperimentText() { function ExperimentText() {
const [experimentIns, setExperimentIns] = useState(undefined); const [experimentIns, setExperimentIns] = useState(undefined);
const [experimentNodeData, setExperimentNodeData, experimentNodeDataRef] = useStateRef(undefined); const [experimentNodeData, setExperimentNodeData, experimentNodeDataRef] = useStateRef(undefined);
const [workflowStatus, setWorkflowStatus] = useState(undefined);
const graphRef = useRef(); const graphRef = useRef();
const workflowRef = useRef(); const workflowRef = useRef();
const locationParams = useParams(); // 新版本获取路由参数接口 const locationParams = useParams(); // 新版本获取路由参数接口
@@ -32,10 +34,12 @@ function ExperimentText() {
const evtSourceRef = useRef(); const evtSourceRef = useRef();
const width = 110; const width = 110;
const height = 36; const height = 36;
const status = getExperimentInstanceStatus(experimentIns?.status, workflowStatus);
const statusInfo = experimentStatusInfo[status];


useEffect(() => { useEffect(() => {
initGraph(); initGraph();
getWorkflow();
getExperimentInstance();


return () => { return () => {
if (evtSourceRef.current) { if (evtSourceRef.current) {
@@ -61,54 +65,83 @@ function ExperimentText() {
}, []); }, []);


// 获取流水线模版 // 获取流水线模版
const getWorkflow = async () => {
const [res] = await to(getWorkflowById(locationParams.workflowId));
if (res && res.data && res.data.dag) {
try {
const dag = JSON.parse(res.data.dag);
dag.nodes.forEach((item) => {
item.in_parameters = JSON.parse(item.in_parameters);
item.out_parameters = JSON.parse(item.out_parameters);
item.control_strategy = JSON.parse(item.control_strategy);
item.imgName = item.img.slice(0, item.img.length - 4);
});
workflowRef.current = dag;
getExperimentInstance();
} catch (error) {
// JSON.parse 错误
console.error('JSON.parse error: ', error);
}
}
};
// const getWorkflow = async () => {
// const [res] = await to(getWorkflowById(locationParams.workflowId));
// if (res && res.data && res.data.dag) {
// try {
// const dag = JSON.parse(res.data.dag);
// dag.nodes.forEach((item) => {
// item.in_parameters = JSON.parse(item.in_parameters);
// item.out_parameters = JSON.parse(item.out_parameters);
// item.control_strategy = JSON.parse(item.control_strategy);
// item.imgName = item.img.slice(0, item.img.length - 4);
// });
// workflowRef.current = dag;
// getExperimentInstance();
// } catch (error) {
// // JSON.parse 错误
// console.error('JSON.parse error: ', error);
// }
// }
// };


// 获取实验实例 // 获取实验实例
const getExperimentInstance = async () => { const getExperimentInstance = async () => {
const [res] = await to(getExperimentIns(locationParams.id)); const [res] = await to(getExperimentIns(locationParams.id));
if (res && res.data && workflowRef.current) {
if (res && res.data) {
setExperimentIns(res.data); setExperimentIns(res.data);
const { status, nodes_status, argo_ins_ns, argo_ins_name, finish_time } = res.data;
const workflowData = workflowRef.current;
const { status, nodes_status, argo_ins_ns, argo_ins_name, finish_time, dag } = res.data;
if (!dag) {
return;
}

const workflow = parseJsonText(dag);
const experimentStatusObjs = parseJsonText(nodes_status); const experimentStatusObjs = parseJsonText(nodes_status);
workflowData.nodes.forEach((item) => {
const experimentNode = experimentStatusObjs?.[item.id];
updateWorkflowNode(item, experimentNode);
if (!workflow || !workflow.nodes) {
return;
}

workflow.nodes.forEach((item) => {
item.in_parameters = parseJsonText(item.in_parameters);
item.out_parameters = parseJsonText(item.out_parameters);
item.control_strategy = parseJsonText(item.control_strategy);
item.imgName = item.img.slice(0, item.img.length - 4);
}); });
workflowRef.current = workflow;

if (experimentStatusObjs) {
// 更新各个节点
workflow.nodes.forEach((item) => {
const experimentNode = experimentStatusObjs[item.id];
updateWorkflowNode(item, experimentNode);
});

// 设置 workflow 总状态
Object.keys(experimentStatusObjs).some((key) => {
if (key.startsWith(NodePrefix)) {
const tempWorkflowStatus = experimentStatusObjs[key];
setWorkflowStatus(tempWorkflowStatus);
return true;
}
return false;
});
}


// 绘制图 // 绘制图
getGraphData(workflowData, true);
getGraphData(workflow, true);


if (status === ExperimentStatus.Pending) { if (status === ExperimentStatus.Pending) {
// 如果状态是 Pending, 打开第一个节点 // 如果状态是 Pending, 打开第一个节点
const node = workflowData.nodes[0];
const node = workflow.nodes[0];
if (node) { if (node) {
setExperimentNodeData(node); setExperimentNodeData(node);
openPropsDrawer(); openPropsDrawer();
} }
} else if (status === ExperimentStatus.Running) { } else if (status === ExperimentStatus.Running) {
// 如果状态是 Running,打开第一个运行中的节点,如果没有运行中的节点,则打开第一个节点
// 如果状态是 Running,打开第一个 Running 或者 pending 的节点,如果没有,则打开第一个节点
const node = const node =
workflowData.nodes.find((item) => item.experimentStatus === ExperimentStatus.Running) ??
workflowData.nodes[0];
workflow.nodes.find((item) => item.experimentStatus === ExperimentStatus.Running || item.experimentStatus === ExperimentStatus.Pending) ??
workflow.nodes[0];
if (node) { if (node) {
setExperimentNodeData(node); setExperimentNodeData(node);
openPropsDrawer(); openPropsDrawer();
@@ -135,23 +168,36 @@ function ExperimentText() {
return; return;
} }
try { try {
const dataJson = JSON.parse(data);
const dataJson = parseJsonText(data);
const statusData = dataJson?.result?.object?.status; const statusData = dataJson?.result?.object?.status;
if (!statusData) { if (!statusData) {
return; return;
} }
const { startedAt, finishedAt, phase, nodes = {} } = statusData;
setExperimentIns((prev) => ({
...prev,
finish_time: finishedAt,
status: phase,
}));
const { finishedAt, phase, nodes = {} } = statusData;

// 更新实验实例状态和结束时间
// setExperimentIns((prev) => ({
// ...prev,
// finish_time: finishedAt,
// status: phase,
// }));

// 设置总 workflow 状态
const tempWorkflowStatus = Object.values(nodes).find((node) =>
node.displayName.startsWith(NodePrefix),
);
if (tempWorkflowStatus) {
setWorkflowStatus(tempWorkflowStatus);
}


// 更新各个节点
const workflowData = workflowRef.current; const workflowData = workflowRef.current;
workflowData.nodes.forEach((item) => { workflowData.nodes.forEach((item) => {
const experimentNode = Object.values(nodes).find((node) => node.displayName === item.id); const experimentNode = Object.values(nodes).find((node) => node.displayName === item.id);
updateWorkflowNode(item, experimentNode); updateWorkflowNode(item, experimentNode);
}); });

// 绘制图
getGraphData(workflowData, false); getGraphData(workflowData, false);


// 更新打开的抽屉数据 // 更新打开的抽屉数据
@@ -177,6 +223,7 @@ function ExperimentText() {
evtSourceRef.current = evtSource; evtSourceRef.current = evtSource;
}; };


// 更新各个节点
function updateWorkflowNode(workflowNode, statusNode) { function updateWorkflowNode(workflowNode, statusNode) {
if (!statusNode) { if (!statusNode) {
return; return;
@@ -471,29 +518,30 @@ function ExperimentText() {
<div className={styles['pipeline-container']}> <div className={styles['pipeline-container']}>
<div className={styles['pipeline-container__top']}> <div className={styles['pipeline-container__top']}>
<div className={styles['pipeline-container__top__info']}> <div className={styles['pipeline-container__top__info']}>
启动时间:{formatDate(experimentIns?.create_time)}
启动时间:{formatDate(workflowStatus?.startedAt)}
</div> </div>
<div className={styles['pipeline-container__top__info']}> <div className={styles['pipeline-container__top__info']}>
执行时长: 执行时长:
<RunDuration <RunDuration
createTime={experimentIns?.create_time}
finishTime={experimentIns?.finish_time}
createTime={workflowStatus?.startedAt}
finishTime={workflowStatus?.finishedAt}
/> />
</div> </div>
<div className={styles['pipeline-container__top__info']}> <div className={styles['pipeline-container__top__info']}>
状态: 状态:
<div
style={{
width: '8px',
height: '8px',
borderRadius: '50%',
marginRight: '6px',
backgroundColor: experimentStatusInfo[experimentIns?.status]?.color,
}}
></div>
<span style={{ color: experimentStatusInfo[experimentIns?.status]?.color }}>
{experimentStatusInfo[experimentIns?.status]?.label}
</span>
{statusInfo ? (
<>
<img
style={{ width: '17px', marginRight: '7px' }}
src={statusInfo.icon}
draggable={false}
alt=""
/>
<span style={{ color: statusInfo.color }}>{statusInfo.label}</span>
</>
) : (
'--'
)}
</div> </div>
<Button <Button
className={styles['pipeline-container__top__param-button']} className={styles['pipeline-container__top__param-button']}


+ 12
- 0
react-ui/src/pages/Experiment/Tensorboard/index.tsx View File

@@ -0,0 +1,12 @@
/*
* @Author: 赵伟
* @Date: 2025-03-31 16:38:59
* @Description: 实验可视化 Tensorboard
*/

import IframePage, { IframePageType } from '@/components/IFramePage';

function TensorboardPage() {
return <IframePage type={IframePageType.TensorBoard}></IframePage>;
}
export default TensorboardPage;

+ 11
- 18
react-ui/src/pages/Experiment/components/AddExperimentModal/index.tsx View File

@@ -30,7 +30,7 @@ interface Workflow {
} }


// 根据参数设置输入组件 // 根据参数设置输入组件
export const getParamComponent = (paramType: number, isSensitive?: number): JSX.Element => {
export const getParamComponent = (paramType: number): JSX.Element => {
// 防止后台返回不是 number 类型 // 防止后台返回不是 number 类型
if (Number(paramType) === 3) { if (Number(paramType) === 3) {
return ( return (
@@ -40,9 +40,9 @@ export const getParamComponent = (paramType: number, isSensitive?: number): JSX.
</Radio.Group> </Radio.Group>
); );
} }
if (isSensitive && Number(isSensitive) === 1) {
return <Input.Password placeholder="请输入值" visibilityToggle={false} allowClear />;
}
// if (isSensitive && Number(isSensitive) === 1) {
// return <Input.Password placeholder="请输入值" visibilityToggle={false} allowClear />;
// }
return <Input placeholder="请输入值" allowClear />; return <Input placeholder="请输入值" allowClear />;
}; };


@@ -95,8 +95,8 @@ function AddExperimentModal({
}; };


const layout = { const layout = {
labelCol: { span: 4 },
wrapperCol: { span: 20 },
labelCol: { span: 5 },
wrapperCol: { span: 19 },
}; };


const paramLayout = { const paramLayout = {
@@ -181,13 +181,13 @@ function AddExperimentModal({
/> />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label="选择流水线"
label="选择流水线模板"
name="workflow_id" name="workflow_id"
rules={[{ required: true, message: '请选择流水线' }]}
rules={[{ required: true, message: '请选择流水线模板' }]}
> >
<Select <Select
disabled={workflowDisabled} disabled={workflowDisabled}
placeholder="请选择流水线"
placeholder="请选择流水线模板"
onChange={handleWorkflowChange} onChange={handleWorkflowChange}
> >
{Array.isArray(workflowList) {Array.isArray(workflowList)
@@ -202,11 +202,7 @@ function AddExperimentModal({
</Select> </Select>
</Form.Item> </Form.Item>
{globalParam.length > 0 && ( {globalParam.length > 0 && (
<Form.Item
label="运行参数"
tooltip="展示关联的流水线的参数,脱敏的参数以xxxx展示"
{...tailLayout}
>
<Form.Item label="运行参数" tooltip="展示关联的流水线的参数" {...tailLayout}>
<div className={styles.global_param_item}> <div className={styles.global_param_item}>
<Form.List name="global_param"> <Form.List name="global_param">
{(fields) => {(fields) =>
@@ -219,10 +215,7 @@ function AddExperimentModal({
name={[name, 'param_value']} name={[name, 'param_value']}
rules={getParamRules(globalParam[name]['param_type'], true)} rules={getParamRules(globalParam[name]['param_type'], true)}
> >
{getParamComponent(
globalParam[name]['param_type'],
globalParam[name]['is_sensitive'],
)}
{getParamComponent(globalParam[name]['param_type'])}
</Form.Item> </Form.Item>
)) ))
} }


+ 0
- 4
react-ui/src/pages/Experiment/components/ExperimentInstanceList/index.less View File

@@ -55,10 +55,6 @@
display: flex; display: flex;
align-items: center; align-items: center;
width: 160px; width: 160px;

.statusIcon {
visibility: visible;
}
} }
} }




+ 15
- 10
react-ui/src/pages/Experiment/components/ExperimentInstanceList/index.tsx View File

@@ -39,7 +39,14 @@ function ExperimentInstanceList({
}: ExperimentInstanceListProps) { }: ExperimentInstanceListProps) {
const { message } = App.useApp(); const { message } = App.useApp();
const allIntanceIds = useMemo(() => { const allIntanceIds = useMemo(() => {
return experimentInsList?.map((item) => item.id) || [];
return (
experimentInsList
?.filter(
(item) =>
item.status !== ExperimentStatus.Running && item.status !== ExperimentStatus.Pending,
)
.map((item) => item.id) || []
);
}, [experimentInsList]); }, [experimentInsList]);
const [ const [
selectedIns, selectedIns,
@@ -127,7 +134,12 @@ function ExperimentInstanceList({
<div> <div>
<div className={styles.tableExpandBox} style={{ paddingBottom: '16px' }}> <div className={styles.tableExpandBox} style={{ paddingBottom: '16px' }}>
<div className={styles.check}> <div className={styles.check}>
<Checkbox checked={checked} indeterminate={indeterminate} onChange={checkAll}></Checkbox>
<Checkbox
checked={checked}
indeterminate={indeterminate}
disabled={allIntanceIds.length === 0}
onChange={checkAll}
></Checkbox>
</div> </div>
<div className={styles.index}>序号</div> <div className={styles.index}>序号</div>
<div className={styles.tensorBoard}>可视化</div> <div className={styles.tensorBoard}>可视化</div>
@@ -185,14 +197,7 @@ function ExperimentInstanceList({
)} )}
</div> </div>


<ExperimentInstanceComponent
create_time={item.create_time}
finish_time={item.finish_time}
status={item.status as ExperimentStatus}
argo_ins_name={item.argo_ins_name}
argo_ins_ns={item.argo_ins_ns}
experimentInsId={item.id}
></ExperimentInstanceComponent>
<ExperimentInstanceComponent instance={item}></ExperimentInstanceComponent>


<div className={styles.operation}> <div className={styles.operation}>
<Button <Button


+ 35
- 32
react-ui/src/pages/Experiment/components/ExperimentInstanceList/instance.tsx View File

@@ -2,69 +2,72 @@ import RunDuration from '@/components/RunDuration';
import { ExperimentStatus } from '@/enums'; import { ExperimentStatus } from '@/enums';
import { useSSE, type MessageHandler } from '@/hooks/useSSE'; import { useSSE, type MessageHandler } from '@/hooks/useSSE';
import { experimentStatusInfo } from '@/pages/Experiment/status'; import { experimentStatusInfo } from '@/pages/Experiment/status';
import { ExperimentInstance, NodeStatus } from '@/types';
import { ExperimentCompleted } from '@/utils/constant'; import { ExperimentCompleted } from '@/utils/constant';
import { formatDate } from '@/utils/date'; import { formatDate } from '@/utils/date';
import { getWorkflowStatus } from '@/utils/experiment';
import { Typography } from 'antd'; import { Typography } from 'antd';
import React, { useCallback } from 'react'; import React, { useCallback } from 'react';
import styles from './index.less'; import styles from './index.less';


type ExperimentInstanceProps = {
create_time?: string;
finish_time?: string;
status: ExperimentStatus;
argo_ins_name: string;
argo_ins_ns: string;
experimentInsId: number;
type ExperimentInstanceComponentProps = {
instance: ExperimentInstance;
}; };


function ExperimentInstance({
create_time,
finish_time,
status,
argo_ins_name,
argo_ins_ns,
experimentInsId,
}: ExperimentInstanceProps) {
function ExperimentInstanceComponent({ instance }: ExperimentInstanceComponentProps) {
const { id, experiment_id, argo_ins_name, argo_ins_ns, nodes_status, create_time, finish_time } =
instance;
const workflowStatus = getWorkflowStatus(nodes_status) as NodeStatus | undefined;
const status = instance.status as ExperimentStatus;
const createTime = workflowStatus?.startedAt ?? create_time;
const finishTime = workflowStatus?.finishedAt ?? finish_time;
const statusInfo = experimentStatusInfo[status];

const handleSSEMessage: MessageHandler = useCallback( const handleSSEMessage: MessageHandler = useCallback(
(experimentInsId: number, status: string, finish_time: string) => {
(experimentId: number, experimentInsId: number, status: string, finishTime: string) => {
window.postMessage({ window.postMessage({
type: ExperimentCompleted, type: ExperimentCompleted,
payload: { payload: {
id: experimentInsId,
experimentId,
experimentInsId,
status, status,
finish_time,
finishTime,
}, },
}); });
}, },
[], [],
); );
useSSE(experimentInsId, status, argo_ins_name, argo_ins_ns, handleSSEMessage);
useSSE(experiment_id, id, status, argo_ins_name, argo_ins_ns, handleSSEMessage);


return ( return (
<React.Fragment> <React.Fragment>
<div className={styles.description}> <div className={styles.description}>
<div style={{ width: '50%' }}> <div style={{ width: '50%' }}>
<RunDuration createTime={create_time} finishTime={finish_time} />
<RunDuration createTime={createTime} finishTime={finishTime} />
</div> </div>
<div style={{ width: '50%' }} className={styles.startTime}> <div style={{ width: '50%' }} className={styles.startTime}>
<Typography.Text ellipsis={{ tooltip: formatDate(create_time) }}>
{formatDate(create_time)}
<Typography.Text ellipsis={{ tooltip: formatDate(createTime) }}>
{formatDate(createTime)}
</Typography.Text> </Typography.Text>
</div> </div>
</div> </div>
<div className={styles.statusBox}> <div className={styles.statusBox}>
<img
style={{ width: '17px', marginRight: '7px' }}
src={experimentStatusInfo[status]?.icon}
draggable={false}
alt=""
/>
<span style={{ color: experimentStatusInfo[status]?.color }} className={styles.statusIcon}>
{experimentStatusInfo[status]?.label}
</span>
{statusInfo ? (
<>
<img
style={{ width: '17px', marginRight: '7px' }}
src={statusInfo.icon}
draggable={false}
alt=""
/>
<span style={{ color: statusInfo.color }}>{statusInfo.label}</span>
</>
) : (
'--'
)}
</div> </div>
</React.Fragment> </React.Fragment>
); );
} }


export default ExperimentInstance;
export default ExperimentInstanceComponent;

+ 60
- 50
react-ui/src/pages/Experiment/components/ExperimentParameter/index.tsx View File

@@ -11,9 +11,15 @@ type ExperimentParameterProps = {


function ExperimentParameter({ nodeData }: ExperimentParameterProps) { function ExperimentParameter({ nodeData }: ExperimentParameterProps) {
// 控制策略 // 控制策略
const controlStrategyList = Object.entries(nodeData.control_strategy ?? {}).map(
([key, value]) => ({ key, value }),
);
// const controlStrategyList = Object.entries(nodeData.control_strategy ?? {}).map(
// ([key, value]) => ({ key, value }),
// );
const nodeId = nodeData.id;
const hasTaskInfo =
nodeId &&
!nodeId.startsWith('git-clone') &&
!nodeId.startsWith('dataset-export') &&
!nodeId.startsWith('model-export');


// 输入参数 // 输入参数
const inParametersList = Object.entries(nodeData.in_parameters ?? {}).map(([key, value]) => ({ const inParametersList = Object.entries(nodeData.in_parameters ?? {}).map(([key, value]) => ({
@@ -74,54 +80,58 @@ function ExperimentParameter({ nodeData }: ExperimentParameterProps) {
> >
<FormInfo /> <FormInfo />
</Form.Item> </Form.Item>
<div className={styles['experiment-parameter__title']}>
<SubAreaTitle
image={require('@/assets/img/duty-message.png')}
title="任务信息"
></SubAreaTitle>
</div>
<Form.Item
label="镜像"
name="image"
rules={[
{
required: true,
message: '请输入镜像',
},
]}
>
<FormInfo />
</Form.Item>
<Form.Item label="工作目录" name="working_directory">
<FormInfo />
</Form.Item>
{hasTaskInfo && (
<>
<div className={styles['experiment-parameter__title']}>
<SubAreaTitle
image={require('@/assets/img/duty-message.png')}
title="任务信息"
></SubAreaTitle>
</div>
<Form.Item
label="镜像"
name="image"
rules={[
{
required: true,
message: '请输入镜像',
},
]}
>
<FormInfo />
</Form.Item>
<Form.Item label="工作目录" name="working_directory">
<FormInfo />
</Form.Item>


<Form.Item label="启动命令" name="command">
<FormInfo textArea />
</Form.Item>
<Form.Item
label="资源规格"
name="resources_standard"
rules={[
{
required: true,
message: '请输入资源规格',
},
]}
>
<ParameterSelect dataType="resource" placeholder="请选择资源规格" display />
</Form.Item>
<Form.Item label="挂载路径" name="mount_path">
<FormInfo />
</Form.Item>
<Form.Item label="环境变量" name="env_variables">
<FormInfo textArea />
</Form.Item>
{controlStrategyList.map((item) => (
<Form.Item key={item.key} name={['control_strategy', item.key]} label={item.value.label}>
<FormInfo valuePropName="showValue" />
</Form.Item>
))}
<Form.Item label="启动命令" name="command">
<FormInfo textArea />
</Form.Item>
<Form.Item
label="资源规格"
name="resources_standard"
rules={[
{
required: true,
message: '请输入资源规格',
},
]}
>
<ParameterSelect dataType="resource" placeholder="请选择资源规格" display />
</Form.Item>
{/* <Form.Item label="挂载路径" name="mount_path">
<FormInfo />
</Form.Item> */}
<Form.Item label="环境变量" name="env_variables">
<FormInfo textArea />
</Form.Item>
{/* {controlStrategyList.map((item) => (
<Form.Item key={item.key} name={['control_strategy', item.key]} label={item.value.label}>
<FormInfo valuePropName="showValue" />
</Form.Item>
))} */}
</>
)}
<div className={styles['experiment-parameter__title']}> <div className={styles['experiment-parameter__title']}>
<SubAreaTitle <SubAreaTitle
image={require('@/assets/img/duty-message.png')} image={require('@/assets/img/duty-message.png')}


+ 4
- 0
react-ui/src/pages/Experiment/components/ExportModelModal/index.tsx View File

@@ -121,6 +121,8 @@ function ExportModelModal({
const params = { const params = {
...formData, ...formData,
identifier: resource?.identifier, identifier: resource?.identifier,
owner: resource?.owner,
is_public: resource?.is_public,
name: resource?.name, name: resource?.name,
[config.sourceParamKey]: DataSource.HandExport, [config.sourceParamKey]: DataSource.HandExport,
train_task: { train_task: {
@@ -174,6 +176,8 @@ function ExportModelModal({
onChange={handleResourceChange} onChange={handleResourceChange}
options={resources} options={resources}
fieldNames={{ label: 'name', value: 'id' }} fieldNames={{ label: 'name', value: 'id' }}
optionFilterProp="name"
showSearch
allowClear allowClear
></Select> ></Select>
</Form.Item> </Form.Item>


+ 4
- 1
react-ui/src/pages/Experiment/components/LogGroup/index.tsx View File

@@ -54,7 +54,10 @@ function LogGroup({
useEffect(() => { useEffect(() => {
// 建立 socket 连接 // 建立 socket 连接
const setupSockect = () => { const setupSockect = () => {
const { host } = location;
let { host } = location;
if (process.env.NODE_ENV === 'development') {
host = '172.20.32.235:31213';
}
const socket = new WebSocket( const socket = new WebSocket(
`ws://${host}/newlog/realtimeLog?start=${start_time}&query={pod="${pod_name}"}`, `ws://${host}/newlog/realtimeLog?start=${start_time}&query={pod="${pod_name}"}`,
); );


+ 1
- 4
react-ui/src/pages/Experiment/components/ViewParamsModal/index.tsx View File

@@ -47,10 +47,7 @@ function ParamsModal({ open, onCancel, globalParam = [] }: ParamsModalProps) {
name={[name, 'param_value']} name={[name, 'param_value']}
label={getParamLabel(globalParam[name])} label={getParamLabel(globalParam[name])}
> >
{getParamComponent(
globalParam[name]['param_type'],
globalParam[name]['is_sensitive'],
)}
{getParamComponent(globalParam[name]['param_type'])}
</Form.Item> </Form.Item>
)) ))
} }


+ 189
- 136
react-ui/src/pages/Experiment/index.jsx View File

@@ -2,8 +2,10 @@ import KFIcon from '@/components/KFIcon';
import PageTitle from '@/components/PageTitle'; import PageTitle from '@/components/PageTitle';
import { ExperimentStatus, TensorBoardStatus } from '@/enums'; import { ExperimentStatus, TensorBoardStatus } from '@/enums';
import { useCacheState } from '@/hooks/useCacheState'; import { useCacheState } from '@/hooks/useCacheState';
import { useServerTime } from '@/hooks/useServerTime';
import { import {
deleteExperimentById, deleteExperimentById,
editExperimentInsReq,
getExperiment, getExperiment,
getExperimentById, getExperimentById,
getQueryByExperimentId, getQueryByExperimentId,
@@ -17,6 +19,7 @@ import { getWorkflow } from '@/services/pipeline/index.js';
import themes from '@/styles/theme.less'; import themes from '@/styles/theme.less';
import { ExperimentCompleted } from '@/utils/constant'; import { ExperimentCompleted } from '@/utils/constant';
import { to } from '@/utils/promise'; import { to } from '@/utils/promise';
import SessionStorage from '@/utils/sessionStorage';
import tableCellRender, { TableCellValueType } from '@/utils/table'; import tableCellRender, { TableCellValueType } from '@/utils/table';
import { modalConfirm } from '@/utils/ui'; import { modalConfirm } from '@/utils/ui';
import { App, Button, ConfigProvider, Dropdown, Input, Space, Table, Tooltip } from 'antd'; import { App, Button, ConfigProvider, Dropdown, Input, Space, Table, Tooltip } from 'antd';
@@ -28,7 +31,6 @@ import AddExperimentModal from './components/AddExperimentModal';
import ExperimentInstanceList from './components/ExperimentInstanceList'; import ExperimentInstanceList from './components/ExperimentInstanceList';
import styles from './index.less'; import styles from './index.less';
import { experimentStatusInfo } from './status'; import { experimentStatusInfo } from './status';
import { useServerTime } from '@/hooks/useServerTime';


// 定时器 // 定时器
const timerIds = new Map(); const timerIds = new Map();
@@ -39,7 +41,7 @@ function Experiment() {
const [workflowList, setWorkflowList] = useState([]); const [workflowList, setWorkflowList] = useState([]);
const [experimentId, setExperimentId] = useState(null); const [experimentId, setExperimentId] = useState(null);
const [experimentInsList, setExperimentInsList] = useState([]); const [experimentInsList, setExperimentInsList] = useState([]);
const [expandedRowKeys, setExpandedRowKeys] = useState(null);
const [expandedRowKeys, setExpandedRowKeys] = useState([]);
const [total, setTotal] = useState(0); const [total, setTotal] = useState(0);
const [isAdd, setIsAdd] = useState(true); const [isAdd, setIsAdd] = useState(true);
const [isModalOpen, setIsModalOpen] = useState(false); const [isModalOpen, setIsModalOpen] = useState(false);
@@ -59,29 +61,137 @@ function Experiment() {
const timerRef = useRef(); const timerRef = useRef();


// 获取实验列表 // 获取实验列表
const getExperimentList = useCallback(async () => {
const getExperimentList = useCallback(
async (skipLoading = false) => {
const params = {
page: pagination.current - 1,
size: pagination.pageSize,
name: searchText || undefined,
};
const [res] = await to(getExperiment(params, skipLoading));
if (res && res.data && Array.isArray(res.data.content)) {
setExperimentList(
res.data.content.map((item) => {
return { ...item, key: item.id };
}),
);

setTotal(res.data.totalElements);
}
},
[pagination, searchText],
);

// 刷新实验列表状态,
// 目前是直接刷新实验列表,后续需要优化,只刷新状态
const refreshExperimentList = useCallback(
(skipLoading = false) => {
getExperimentList(skipLoading);
},
[getExperimentList],
);

// 获取 TensorBoard 状态
const getTensorBoardStatus = useCallback(async (experimentIn) => {
const params = { const params = {
page: pagination.current - 1,
size: pagination.pageSize,
name: searchText || undefined,
namespace: experimentIn.nodes_result.tensorboard_log.namespace,
path: experimentIn.nodes_result.tensorboard_log.path,
pvc_name: experimentIn.nodes_result.tensorboard_log.pvc_name,
}; };
const [res] = await to(getExperiment(params));
if (res && res.data && Array.isArray(res.data.content)) {
setExperimentList(
res.data.content.map((item) => {
return { ...item, key: item.id };
}),
);

setTotal(res.data.totalElements);
const [res] = await to(getTensorBoardStatusReq(params));
if (res && res.data) {
setExperimentInsList((prevList) => {
return prevList.map((item) => {
if (item.id === experimentIn.id) {
return {
...item,
tensorBoardStatus: res.data.status,
tensorboardUrl: res.data.url,
};
}
return item;
});
});

let timerId = timerIds.get(experimentIn.id);
if (timerId) {
clearTimeout(timerId);
timerIds.delete(experimentIn.id);
}
timerId = setTimeout(() => {
getTensorBoardStatus(experimentIn);
}, 10 * 1000);
timerIds.set(experimentIn.id, timerId);
} }
}, [pagination, searchText]);
}, []);


// 刷新实验列表状态,
// 目前是直接刷新实验列表,后续需要优化,只刷新状态
const refreshExperimentList = useCallback(() => {
getExperimentList();
}, [getExperimentList]);
// 获取实验实例列表
const getExperimentInsList = useCallback(
async (experimentId, page, size = 5, skipLoading = false) => {
const params = {
experimentId: experimentId,
page: page,
size: size,
};
const [res, error] = await to(getQueryByExperimentId(params, skipLoading));
if (res && res.data) {
const { content = [], totalElements = 0 } = res.data;
try {
const list = content.map((v) => {
const nodes_result = v.nodes_result ? JSON.parse(v.nodes_result) : {};
return {
...v,
nodes_result,
};
});
if (page === 0) {
setExperimentInsList(list);
clearExperimentInTimers();
} else {
setExperimentInsList((prev) => [...prev, ...list]);
}
setExperimentInsTotal(totalElements);
// 获取 TensorBoard 状态
list.forEach((item) => {
if (item.nodes_result?.tensorboard_log) {
getTensorBoardStatus(item);
}
});
} catch (error) {
console.error('JSON parse error: ', error);
}
}
},
[getTensorBoardStatus],
);

// 刷新实验实例列表
const refreshExperimentIns = useCallback(
(experimentId, skipLoading = false) => {
const length = experimentInsList.length;
getExperimentInsList(experimentId, 0, length, skipLoading);
},
[experimentInsList, getExperimentInsList],
);

// 更新实验状态
const editExperimentIns = useCallback(
async (experimentId, experimentInsId, status, argo_ins_name, argo_ins_ns) => {
const params = {
experiment_id: experimentId,
id: experimentInsId,
status: status,
argo_ins_name,
argo_ins_ns,
};
const [res, error] = await to(editExperimentInsReq(params));
if (res && res.data) {
refreshExperimentIns(experimentId, true);
refreshExperimentList(true);
}
},
[refreshExperimentIns, refreshExperimentList],
);


// 获取流水线列表 // 获取流水线列表
useEffect(() => { useEffect(() => {
@@ -104,52 +214,66 @@ function Experiment() {
clearExperimentInTimers(); clearExperimentInTimers();
}; };
}, []); }, []);

// 获取实验列表 // 获取实验列表
useEffect(() => { useEffect(() => {
getExperimentList(); getExperimentList();
}, [getExperimentList]); }, [getExperimentList]);


// 新增,删除版本时,重置分页,然后刷新版本列表
// 更新实验实例状态
useEffect(() => { useEffect(() => {
const handleMessage = (e) => { const handleMessage = (e) => {
const { type, payload } = e.data; const { type, payload } = e.data;
if (type === ExperimentCompleted) { if (type === ExperimentCompleted) {
const { id, status, finish_time } = payload;

// 修改实例的状态和结束时间
setExperimentInsList((prev) =>
prev.map((v) =>
v.id === id
? {
...v,
status: status,
finish_time: finish_time,
}
: v,
),
const { experimentId, experimentInsId, status, finishTime } = payload;
const currentIns = experimentInsList.find((v) => v.id === experimentInsId);
console.log(
'实验实例状态变化',
currentIns?.status,
status,
experimentId,
experimentInsId,
finishTime,
); );


if (timerRef.current) {
clearTimeout(timerRef.current);
timerRef.current = undefined;
if (
!currentIns ||
currentIns.status === ExperimentStatus.Terminated ||
currentIns.status === status
) {
return;
} }


timerRef.current = setTimeout(() => {
refreshExperimentList();
}, 10000);
editExperimentIns(
experimentId,
experimentInsId,
status,
currentIns.argo_ins_name,
currentIns.argo_ins_ns,
);

// refreshExperimentList(true);
// refreshExperimentIns(experimentId);

// 修改实例的状态和结束时间
// setExperimentInsList((prev) =>
// prev.map((v) =>
// v.id === experimentInsId
// ? {
// ...v,
// status: status,
// finish_time: finishTime,
// }
// : v,
// ),
// );
} }
}; };


window.addEventListener('message', handleMessage); window.addEventListener('message', handleMessage);
return () => { return () => {
window.removeEventListener('message', handleMessage); window.removeEventListener('message', handleMessage);
if (timerRef.current) {
clearTimeout(timerRef.current);
timerRef.current = undefined;
}
}; };
}, [refreshExperimentList]);
}, [experimentInsList, editExperimentIns]);


// 搜索 // 搜索
const onSearch = (value) => { const onSearch = (value) => {
@@ -160,44 +284,6 @@ function Experiment() {
})); }));
}; };


// 获取实验实例列表
const getQueryByExperiment = async (experimentId, page, size = 5) => {
const params = {
experimentId: experimentId,
page: page,
size: size,
};
const [res, error] = await to(getQueryByExperimentId(params));
if (res && res.data) {
const { content = [], totalElements = 0 } = res.data;
setExpandedRowKeys(experimentId);
try {
const list = content.map((v) => {
const nodes_result = v.nodes_result ? JSON.parse(v.nodes_result) : {};
return {
...v,
nodes_result,
};
});
if (page === 0) {
setExperimentInsList(list);
clearExperimentInTimers();
} else {
setExperimentInsList((prev) => [...prev, ...list]);
}
setExperimentInsTotal(totalElements);
// 获取 TensorBoard 状态
list.forEach((item) => {
if (item.nodes_result?.tensorboard_log) {
getTensorBoardStatus(item);
}
});
} catch (error) {
console.error('JSON parse error: ', error);
}
}
};

// 运行 TensorBoard // 运行 TensorBoard
const runTensorBoard = async (experimentIn) => { const runTensorBoard = async (experimentIn) => {
const params = { const params = {
@@ -217,49 +303,16 @@ function Experiment() {
} }
}; };


// 获取 TensorBoard 状态
const getTensorBoardStatus = async (experimentIn) => {
const params = {
namespace: experimentIn.nodes_result.tensorboard_log.namespace,
path: experimentIn.nodes_result.tensorboard_log.path,
pvc_name: experimentIn.nodes_result.tensorboard_log.pvc_name,
};
const [res] = await to(getTensorBoardStatusReq(params));
if (res && res.data) {
setExperimentInsList((prevList) => {
return prevList.map((item) => {
if (item.id === experimentIn.id) {
return {
...item,
tensorBoardStatus: res.data.status,
tensorboardUrl: res.data.url,
};
}
return item;
});
});

let timerId = timerIds.get(experimentIn.id);
if (timerId) {
clearTimeout(timerId);
timerIds.delete(experimentIn.id);
}
timerId = setTimeout(() => {
getTensorBoardStatus(experimentIn);
}, 10 * 1000);
timerIds.set(experimentIn.id, timerId);
}
};

// 展开实例 // 展开实例
const expandChange = (e, record) => {
const expandChange = (expanded, record) => {
clearExperimentInTimers(); clearExperimentInTimers();
setExperimentInsList([]); setExperimentInsList([]);
if (record.id === expandedRowKeys) {
setExpandedRowKeys(null);
} else {
getQueryByExperiment(record.id, 0, 5);
if (expanded) {
setExpandedRowKeys([record.id]);
getExperimentInsList(record.id, 0, 5);
refreshExperimentList(); refreshExperimentList();
} else {
setExpandedRowKeys([]);
} }
}; };


@@ -337,8 +390,9 @@ function Experiment() {
const [res] = await to(runExperiments(id)); const [res] = await to(runExperiments(id));
if (res) { if (res) {
message.success('运行成功'); message.success('运行成功');
setExpandedRowKeys([id]);
refreshExperimentList(); refreshExperimentList();
getQueryByExperiment(id, 0, 5);
getExperimentInsList(id, 0, 5);
} }
}; };


@@ -372,14 +426,16 @@ function Experiment() {
experimentIn.tensorBoardStatus === TensorBoardStatus.Running && experimentIn.tensorBoardStatus === TensorBoardStatus.Running &&
experimentIn.tensorboardUrl experimentIn.tensorboardUrl
) { ) {
window.open(experimentIn.tensorboardUrl, '_blank');
const url = experimentIn.tensorboardUrl;
SessionStorage.setItem(SessionStorage.tensorBoardUrlKey, url);
navigateToUrl(`/pipeline/experiment/visual`);
// window.open(experimentIn.tensorboardUrl, '_blank');
} }
}; };


// 实验实例终止 // 实验实例终止
const handleInstanceTerminate = async (experimentIn) => { const handleInstanceTerminate = async (experimentIn) => {
// 刷新实验列表
refreshExperimentList();
// 修改实例的状态和结束时间
setExperimentInsList((prevList) => { setExperimentInsList((prevList) => {
return prevList.map((item) => { return prevList.map((item) => {
if (item.id === experimentIn.id) { if (item.id === experimentIn.id) {
@@ -392,6 +448,9 @@ function Experiment() {
return item; return item;
}); });
}); });
// 刷新实验列表和实例列表
refreshExperimentList(true);
refreshExperimentIns(experimentIn.experiment_id);
}; };


// 实验对比菜单 // 实验对比菜单
@@ -413,16 +472,10 @@ function Experiment() {
}; };
}; };


// 刷新实验实例列表
const refreshExperimentIns = (experimentId) => {
const length = experimentInsList.length;
getQueryByExperiment(experimentId, 0, length);
};

// 加载更多实验实例 // 加载更多实验实例
const loadMoreExperimentIns = () => { const loadMoreExperimentIns = () => {
const page = Math.round(experimentInsList.length / 5); const page = Math.round(experimentInsList.length / 5);
getQueryByExperiment(expandedRowKeys, page, 5);
getExperimentInsList(expandedRowKeys[0], page, 5);
}; };


// 处理删除 // 处理删除
@@ -607,7 +660,7 @@ function Experiment() {
></ExperimentInstanceList> ></ExperimentInstanceList>
), ),
onExpand: expandChange, onExpand: expandChange,
expandedRowKeys: [expandedRowKeys],
expandedRowKeys: expandedRowKeys,
}} }}
/> />
</div> </div>


+ 12
- 0
react-ui/src/pages/HyperParameter/Aim/index.tsx View File

@@ -0,0 +1,12 @@
/*
* @Author: 赵伟
* @Date: 2025-03-31 16:38:59
* @Description: 实验对比 Aim
*/

import IframePage, { IframePageType } from '@/components/IFramePage';

function AimPage() {
return <IframePage type={IframePageType.Aim}></IframePage>;
}
export default AimPage;

+ 1
- 1
react-ui/src/pages/HyperParameter/Info/index.tsx View File

@@ -1,7 +1,7 @@
/* /*
* @Author: 赵伟 * @Author: 赵伟
* @Date: 2024-04-16 13:58:08 * @Date: 2024-04-16 13:58:08
* @Description: 自主机器学习详情
* @Description: 自动机器学习详情
*/ */
import PageTitle from '@/components/PageTitle'; import PageTitle from '@/components/PageTitle';
import { getRayInfoReq } from '@/services/hyperParameter'; import { getRayInfoReq } from '@/services/hyperParameter';


+ 6
- 8
react-ui/src/pages/HyperParameter/Instance/index.tsx View File

@@ -51,7 +51,7 @@ function HyperParameterInstance() {
const [res] = await to(getRayInsReq(instanceId)); const [res] = await to(getRayInsReq(instanceId));
if (res && res.data) { if (res && res.data) {
const info = res.data as HyperParameterInstanceData; const info = res.data as HyperParameterInstanceData;
const { param, node_status, argo_ins_name, argo_ins_ns, status, create_time } = info;
const { param, node_status, argo_ins_name, argo_ins_ns, status } = info;
// 解析配置参数 // 解析配置参数
const paramJson = parseJsonText(param).data; const paramJson = parseJsonText(param).data;
if (paramJson) { if (paramJson) {
@@ -72,7 +72,6 @@ function HyperParameterInstance() {
} }
setExperimentInfo({ setExperimentInfo({
...paramJson, ...paramJson,
create_time,
}); });
} }


@@ -121,18 +120,17 @@ function HyperParameterInstance() {
if (dataJson) { if (dataJson) {
const nodes = dataJson?.result?.object?.status?.nodes; const nodes = dataJson?.result?.object?.status?.nodes;
if (nodes) { if (nodes) {
// 节点
// 设置节点
setNodes(nodes); setNodes(nodes);


// 设置总 workflow 状态
const workflowStatus = Object.values(nodes).find((node: any) => const workflowStatus = Object.values(nodes).find((node: any) =>
node.displayName.startsWith(NodePrefix), node.displayName.startsWith(NodePrefix),
) as NodeStatus; ) as NodeStatus;

// 设置工作流状态
if (workflowStatus) { if (workflowStatus) {
setWorkflowStatus(workflowStatus); setWorkflowStatus(workflowStatus);


// 实验结束,关闭 SSE
// 实验结束,关闭 SSE,获取实验实例结果
if ( if (
workflowStatus.phase !== ExperimentStatus.Pending && workflowStatus.phase !== ExperimentStatus.Pending &&
workflowStatus.phase !== ExperimentStatus.Running workflowStatus.phase !== ExperimentStatus.Running
@@ -167,8 +165,8 @@ function HyperParameterInstance() {
<HyperParameterBasic <HyperParameterBasic
className={styles['hyper-parameter-instance__basic']} className={styles['hyper-parameter-instance__basic']}
info={experimentInfo} info={experimentInfo}
runStatus={workflowStatus}
instanceStatus={instanceInfo?.status}
workflowStatus={workflowStatus}
instanceStatus={instanceInfo?.status as ExperimentStatus}
isInstance isInstance
/> />
), ),


+ 5
- 1
react-ui/src/pages/HyperParameter/components/ExperimentHistory/index.tsx View File

@@ -3,7 +3,9 @@ import TrialStatusCell from '@/pages/HyperParameter/components/TrialStatusCell';
import { HyperParameterTrial } from '@/pages/HyperParameter/types'; import { HyperParameterTrial } from '@/pages/HyperParameter/types';
import { getExpMetricsReq } from '@/services/hyperParameter'; import { getExpMetricsReq } from '@/services/hyperParameter';
import { to } from '@/utils/promise'; import { to } from '@/utils/promise';
import SessionStorage from '@/utils/sessionStorage';
import tableCellRender, { TableCellValueType } from '@/utils/table'; import tableCellRender, { TableCellValueType } from '@/utils/table';
import { useNavigate } from '@umijs/max';
import { App, Button, Table, type TableProps } from 'antd'; import { App, Button, Table, type TableProps } from 'antd';
import classNames from 'classnames'; import classNames from 'classnames';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
@@ -36,6 +38,7 @@ function ExperimentHistory({ trialList = [] }: ExperimentHistoryProps) {
const metricAnalysis: Record<string, any> = first?.metric_analysis ?? {}; const metricAnalysis: Record<string, any> = first?.metric_analysis ?? {};
const paramsNames = Object.keys(config); const paramsNames = Object.keys(config);
const metricNames = Object.keys(metricAnalysis); const metricNames = Object.keys(metricAnalysis);
const navigate = useNavigate();


const trialColumns: TableProps<HyperParameterTrial>['columns'] = [ const trialColumns: TableProps<HyperParameterTrial>['columns'] = [
{ {
@@ -160,7 +163,8 @@ function ExperimentHistory({ trialList = [] }: ExperimentHistoryProps) {
const [res] = await to(getExpMetricsReq(selectedRowKeys)); const [res] = await to(getExpMetricsReq(selectedRowKeys));
if (res && res.data) { if (res && res.data) {
const url = res.data; const url = res.data;
window.open(url, '_blank');
SessionStorage.setItem(SessionStorage.aimUrlKey, url);
navigate('compare-visual');
} }
}; };




+ 7
- 2
react-ui/src/pages/HyperParameter/components/ExperimentLog/index.tsx View File

@@ -1,4 +1,5 @@
import { ExperimentStatus } from '@/enums'; import { ExperimentStatus } from '@/enums';
import EmptyLog from '@/pages/AutoML/components/ExperimentLog/empty';
import LogList from '@/pages/Experiment/components/LogList'; import LogList from '@/pages/Experiment/components/LogList';
import { HyperParameterInstanceData } from '@/pages/HyperParameter/types'; import { HyperParameterInstanceData } from '@/pages/HyperParameter/types';
import { NodeStatus } from '@/types'; import { NodeStatus } from '@/types';
@@ -64,7 +65,7 @@ function ExperimentLog({ instanceInfo, nodes }: ExperimentLogProps) {
// icon: <KFIcon type="icon-rizhi1" />, // icon: <KFIcon type="icon-rizhi1" />,
children: ( children: (
<div className={styles['experiment-log__tabs__log']}> <div className={styles['experiment-log__tabs__log']}>
{trainCloneNodeStatus && (
{trainCloneNodeStatus ? (
<LogList <LogList
instanceName={instanceInfo.argo_ins_name} instanceName={instanceInfo.argo_ins_name}
instanceNamespace={instanceInfo.argo_ins_ns} instanceNamespace={instanceInfo.argo_ins_ns}
@@ -73,6 +74,8 @@ function ExperimentLog({ instanceInfo, nodes }: ExperimentLogProps) {
instanceNodeStartTime={trainCloneNodeStatus.startedAt} instanceNodeStartTime={trainCloneNodeStatus.startedAt}
instanceNodeStatus={trainCloneNodeStatus.phase as ExperimentStatus} instanceNodeStatus={trainCloneNodeStatus.phase as ExperimentStatus}
></LogList> ></LogList>
) : (
<EmptyLog />
)} )}
</div> </div>
), ),
@@ -83,7 +86,7 @@ function ExperimentLog({ instanceInfo, nodes }: ExperimentLogProps) {
// icon: <KFIcon type="icon-rizhi1" />, // icon: <KFIcon type="icon-rizhi1" />,
children: ( children: (
<div className={styles['experiment-log__tabs__log']}> <div className={styles['experiment-log__tabs__log']}>
{hpoNodeStatus && (
{hpoNodeStatus ? (
<LogList <LogList
instanceName={instanceInfo.argo_ins_name} instanceName={instanceInfo.argo_ins_name}
instanceNamespace={instanceInfo.argo_ins_ns} instanceNamespace={instanceInfo.argo_ins_ns}
@@ -92,6 +95,8 @@ function ExperimentLog({ instanceInfo, nodes }: ExperimentLogProps) {
instanceNodeStartTime={hpoNodeStatus.startedAt} instanceNodeStartTime={hpoNodeStatus.startedAt}
instanceNodeStatus={hpoNodeStatus.phase as ExperimentStatus} instanceNodeStatus={hpoNodeStatus.phase as ExperimentStatus}
></LogList> ></LogList>
) : (
<EmptyLog />
)} )}
</div> </div>
), ),


+ 5
- 54
react-ui/src/pages/HyperParameter/components/HyperParameterBasic/index.tsx View File

@@ -1,9 +1,7 @@
import ConfigInfo, { type BasicInfoData } from '@/components/ConfigInfo'; import ConfigInfo, { type BasicInfoData } from '@/components/ConfigInfo';
import RunDuration from '@/components/RunDuration';
import { ExperimentStatus, hyperParameterOptimizedMode } from '@/enums'; import { ExperimentStatus, hyperParameterOptimizedMode } from '@/enums';
import { useComputingResource } from '@/hooks/useComputingResource'; import { useComputingResource } from '@/hooks/useComputingResource';
import ExperimentRunBasic from '@/pages/AutoML/components/ExperimentRunBasic'; import ExperimentRunBasic from '@/pages/AutoML/components/ExperimentRunBasic';
import { experimentStatusInfo } from '@/pages/Experiment/status';
import { import {
schedulerAlgorithms, schedulerAlgorithms,
searchAlgorithms, searchAlgorithms,
@@ -18,7 +16,6 @@ import {
formatMirror, formatMirror,
formatModel, formatModel,
} from '@/utils/format'; } from '@/utils/format';
import { Flex } from 'antd';
import classNames from 'classnames'; import classNames from 'classnames';
import { useMemo } from 'react'; import { useMemo } from 'react';
import ParameterInfo from '../ParameterInfo'; import ParameterInfo from '../ParameterInfo';
@@ -33,14 +30,14 @@ type HyperParameterBasicProps = {
info?: HyperParameterData; info?: HyperParameterData;
className?: string; className?: string;
isInstance?: boolean; isInstance?: boolean;
runStatus?: NodeStatus;
workflowStatus?: NodeStatus;
instanceStatus?: ExperimentStatus; instanceStatus?: ExperimentStatus;
}; };


function HyperParameterBasic({ function HyperParameterBasic({
info, info,
className, className,
runStatus,
workflowStatus,
instanceStatus, instanceStatus,
isInstance = false, isInstance = false,
}: HyperParameterBasicProps) { }: HyperParameterBasicProps) {
@@ -83,7 +80,7 @@ function HyperParameterBasic({
} }
return [ return [
{ {
label: '代码',
label: '代码配置',
value: info.code_config, value: info.code_config,
format: formatCodeConfig, format: formatCodeConfig,
}, },
@@ -145,56 +142,10 @@ function HyperParameterBasic({
]; ];
}, [info, getResourceDescription]); }, [info, getResourceDescription]);


const instanceDatas = useMemo(() => {
if (!info || !runStatus) {
return [];
}

return [
{
label: '启动时间',
value: formatDate(info.create_time),
ellipsis: true,
},
{
label: '执行时长',
value: <RunDuration createTime={info.create_time} finishTime={runStatus.finishedAt} />,
ellipsis: true,
},
{
label: '状态',
value: (
<Flex align="center">
<img
style={{ width: '17px', marginRight: '7px' }}
src={experimentStatusInfo[runStatus.phase]?.icon}
draggable={false}
alt=""
/>
<div
style={{
color: experimentStatusInfo[runStatus?.phase]?.color,
fontSize: '15px',
lineHeight: 1.6,
}}
>
{experimentStatusInfo[runStatus?.phase]?.label}
</div>
</Flex>
),
ellipsis: true,
},
];
}, [runStatus, info]);

return ( return (
<div className={classNames(styles['hyper-parameter-basic'], className)}> <div className={classNames(styles['hyper-parameter-basic'], className)}>
{isInstance && runStatus && (
<ExperimentRunBasic
create_time={info?.create_time}
runStatus={runStatus}
instanceStatus={instanceStatus}
/>
{isInstance && workflowStatus && (
<ExperimentRunBasic workflowStatus={workflowStatus} instanceStatus={instanceStatus} />
)} )}
{!isInstance && ( {!isInstance && (
<ConfigInfo <ConfigInfo


+ 8
- 0
react-ui/src/pages/Mirror/Create/index.less View File

@@ -9,6 +9,14 @@
background-color: white; background-color: white;
border-radius: 10px; border-radius: 10px;


&__name-row {
:global {
.ant-form-item-row {
flex-wrap: nowrap;
}
}
}

&__type { &__type {
color: @text-color; color: @text-color;
font-size: @font-size-input-lg; font-size: @font-size-input-lg;


+ 29
- 6
react-ui/src/pages/Mirror/Create/index.tsx View File

@@ -123,8 +123,6 @@ function MirrorCreate() {
return true; return true;
}; };


const descTitle = isAddVersion ? '版本描述' : '镜像描述';

return ( return (
<div className={styles['mirror-create']}> <div className={styles['mirror-create']}>
<PageTitle title={!isAddVersion ? '创建镜像' : '新增镜像版本'}></PageTitle> <PageTitle title={!isAddVersion ? '创建镜像' : '新增镜像版本'}></PageTitle>
@@ -161,6 +159,7 @@ function MirrorCreate() {
message: '只支持小写字母、数字、点(.)、下划线(_)、中横线(-)、斜杠(/)', message: '只支持小写字母、数字、点(.)、下划线(_)、中横线(-)、斜杠(/)',
}, },
]} ]}
className={styles['mirror-create__content__name-row']}
> >
<Input <Input
placeholder="请输入镜像名称" placeholder="请输入镜像名称"
@@ -193,21 +192,45 @@ function MirrorCreate() {
</Form.Item> </Form.Item>
</Col> </Col>
</Row> </Row>
{!isAddVersion && (
<Row gutter={10}>
<Col span={20}>
<Form.Item
label="镜像描述"
name="description"
rules={[
{
required: true,
message: '请输入镜像描述',
},
]}
>
<Input.TextArea
autoSize={{ minRows: 2, maxRows: 6 }}
placeholder="请输入镜像描述"
maxLength={128}
showCount
allowClear
/>
</Form.Item>
</Col>
</Row>
)}
<Row gutter={10}> <Row gutter={10}>
<Col span={20}> <Col span={20}>
<Form.Item <Form.Item
label={descTitle}
name="description"
label="版本描述"
name="version_description"
rules={[ rules={[
{ {
required: true, required: true,
message: `请输入${descTitle}`,
message: '请输入版本描述',
}, },
]} ]}
> >
<Input.TextArea <Input.TextArea
autoSize={{ minRows: 2, maxRows: 6 }} autoSize={{ minRows: 2, maxRows: 6 }}
placeholder={`请输入${descTitle}`}
placeholder="请输入版本描述"
maxLength={128} maxLength={128}
showCount showCount
allowClear allowClear


+ 5
- 3
react-ui/src/pages/Mirror/Info/index.tsx View File

@@ -125,6 +125,7 @@ function MirrorInfo() {
current: tableData.length === 1 ? Math.max(1, prev.current! - 1) : prev.current, current: tableData.length === 1 ? Math.max(1, prev.current! - 1) : prev.current,
}; };
}); });
getMirrorInfo();
} }
}; };


@@ -164,20 +165,21 @@ function MirrorInfo() {
title: '镜像版本', title: '镜像版本',
dataIndex: 'tag_name', dataIndex: 'tag_name',
key: 'tag_name', key: 'tag_name',
width: '25%',
width: '30%',
render: tableCellRender(), render: tableCellRender(),
}, },
{ {
title: '镜像地址', title: '镜像地址',
dataIndex: 'url', dataIndex: 'url',
key: 'url', key: 'url',
width: '25%',
width: '40%',
render: tableCellRender('auto', TableCellValueType.Text, { copyable: true }), render: tableCellRender('auto', TableCellValueType.Text, { copyable: true }),
}, },
{ {
title: '版本描述', title: '版本描述',
dataIndex: 'description', dataIndex: 'description',
key: 'description', key: 'description',
width: '30%',
render: tableCellRender(true), render: tableCellRender(true),
}, },
{ {
@@ -209,7 +211,7 @@ function MirrorInfo() {
hidden: isPublic, hidden: isPublic,
render: (_: any, record: MirrorVersionData) => ( render: (_: any, record: MirrorVersionData) => (
<div> <div>
{!isPublic && (
{!isPublic && record.status && record.status !== MirrorVersionStatus.Building && (
<ConfigProvider <ConfigProvider
theme={{ theme={{
token: { token: {


+ 13
- 9
react-ui/src/pages/Model/components/MetricsChart/index.tsx View File

@@ -65,14 +65,18 @@ export type MetricsChartProps = {
function MetricsChart({ name, chartData }: MetricsChartProps) { function MetricsChart({ name, chartData }: MetricsChartProps) {
const chartRef = useRef<HTMLDivElement>(null); const chartRef = useRef<HTMLDivElement>(null);
const xAxisData = chartData[0]?.iters; const xAxisData = chartData[0]?.iters;
const seriesData = chartData.map((item) => {
return {
name: item.version,
type: 'line' as const,
smooth: true,
data: item.values,
};
});
const seriesData = useMemo(
() =>
chartData.map((item) => {
return {
name: item.version,
type: 'line' as const,
smooth: true,
data: item.values,
};
}),
[chartData],
);


const options: echarts.EChartsOption = useMemo( const options: echarts.EChartsOption = useMemo(
() => ({ () => ({
@@ -158,7 +162,7 @@ function MetricsChart({ name, chartData }: MetricsChartProps) {


// 组件卸载 // 组件卸载
return () => { return () => {
// myChart.dispose() 销毁实例
// 销毁实例
chart.dispose(); chart.dispose();
}; };
}, [options]); }, [options]);


+ 1
- 1
react-ui/src/pages/ModelDeployment/CreateVersion/index.tsx View File

@@ -238,7 +238,7 @@ function CreateServiceVersion() {
}, },
{ {
pattern: /^[a-zA-Z0-9._-]+$/, pattern: /^[a-zA-Z0-9._-]+$/,
message: '版本只支持字母、数字、点(.)、下划线(_)、中横线(-)',
message: '服务支持字母、数字、点(.)、下划线(_)、中横线(-)',
}, },
]} ]}
> >


+ 29
- 26
react-ui/src/pages/ModelDeployment/VersionInfo/index.tsx View File

@@ -3,16 +3,16 @@
* @Date: 2024-04-16 13:58:08 * @Date: 2024-04-16 13:58:08
* @Description: 服务版本详情 * @Description: 服务版本详情
*/ */
import FullScreenFrame from '@/components/FullScreenFrame';
import IframePage from '@/components/IFramePage';
import KFIcon from '@/components/KFIcon'; import KFIcon from '@/components/KFIcon';
import PageTitle from '@/components/PageTitle'; import PageTitle from '@/components/PageTitle';
import { ServiceRunStatus } from '@/enums';
import { getServiceVersionInfoReq } from '@/services/modelDeployment'; import { getServiceVersionInfoReq } from '@/services/modelDeployment';
import { to } from '@/utils/promise'; import { to } from '@/utils/promise';
import { useParams } from '@umijs/max'; import { useParams } from '@umijs/max';
import { Tabs } from 'antd'; import { Tabs } from 'antd';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import ServerLog from '../components/ServerLog'; import ServerLog from '../components/ServerLog';
import UserGuide from '../components/UserGuide';
import VersionBasicInfo from '../components/VersionBasicInfo'; import VersionBasicInfo from '../components/VersionBasicInfo';
import { ServiceVersionData } from '../types'; import { ServiceVersionData } from '../types';
import styles from './index.less'; import styles from './index.less';
@@ -50,32 +50,35 @@ function ServiceVersionInfo() {
icon: <KFIcon type="icon-jibenxinxi" />, icon: <KFIcon type="icon-jibenxinxi" />,
children: <VersionBasicInfo info={versionInfo} />, children: <VersionBasicInfo info={versionInfo} />,
}, },
{
key: ModelDeploymentTabKey.Predict,
label: '预测',
icon: <KFIcon type="icon-yuce" />,
children: (
<div style={{ height: '100%', width: '100%' }}>
{versionInfo?.page_path && (
<FullScreenFrame url={versionInfo?.page_path}></FullScreenFrame>
)}
</div>
),
},
{
key: ModelDeploymentTabKey.Guide,
label: '调用指南',
icon: <KFIcon type="icon-tiaoyongzhinan" />,
children: <UserGuide info={versionInfo}></UserGuide>,
},
{
key: ModelDeploymentTabKey.Log,
label: '服务日志',
icon: <KFIcon type="icon-fuwurizhi" />,
children: <ServerLog info={versionInfo}></ServerLog>,
},
]; ];


if (versionInfo?.run_state === ServiceRunStatus.Running) {
if (versionInfo?.page_path) {
tabItems.push({
key: ModelDeploymentTabKey.Predict,
label: '预测',
icon: <KFIcon type="icon-yuce" />,
children: <IframePage url={versionInfo?.page_path} showLoading={false}></IframePage>,
});
}

if (versionInfo?.doc_path) {
tabItems.push({
key: ModelDeploymentTabKey.Guide,
label: '调用指南',
icon: <KFIcon type="icon-tiaoyongzhinan" />,
children: <IframePage url={versionInfo?.doc_path}></IframePage>,
});
}
}

tabItems.push({
key: ModelDeploymentTabKey.Log,
label: '服务日志',
icon: <KFIcon type="icon-fuwurizhi" />,
children: <ServerLog info={versionInfo}></ServerLog>,
});

return ( return (
<div className={styles['service-version-info']}> <div className={styles['service-version-info']}>
<PageTitle title="服务版本详情"></PageTitle> <PageTitle title="服务版本详情"></PageTitle>


+ 9
- 1
react-ui/src/pages/ModelDeployment/components/ServerLog/index.less View File

@@ -11,10 +11,18 @@
font-family: 'Roboto Mono', 'Menlo', 'Consolas', 'Monaco', monospace; font-family: 'Roboto Mono', 'Menlo', 'Consolas', 'Monaco', monospace;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center;
align-items: left;

&--empty {
align-items: center;
}


&__more { &__more {
padding: 20px 0; padding: 20px 0;
} }

&::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.5);
}
} }
} }

+ 9
- 2
react-ui/src/pages/ModelDeployment/components/ServerLog/index.tsx View File

@@ -3,6 +3,7 @@ import { getServiceVersionLogReq } from '@/services/modelDeployment';
import { to } from '@/utils/promise'; import { to } from '@/utils/promise';
import { DoubleRightOutlined } from '@ant-design/icons'; import { DoubleRightOutlined } from '@ant-design/icons';
import { Button, DatePicker, type TimeRangePickerProps } from 'antd'; import { Button, DatePicker, type TimeRangePickerProps } from 'antd';
import classNames from 'classnames';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import styles from './index.less'; import styles from './index.less';
@@ -106,6 +107,8 @@ function ServerLog({ info }: ServerLogProps) {
} }
}; };


const logContent = logData.map((v) => v.log_content).join('');

return ( return (
<div className={styles['server-log']}> <div className={styles['server-log']}>
<div> <div>
@@ -122,9 +125,9 @@ function ServerLog({ info }: ServerLogProps) {
查询 查询
</Button> </Button>
</div> </div>
{logData.length > 0 && (
{logContent ? (
<div className={styles['server-log__data']} id="server-log"> <div className={styles['server-log__data']} id="server-log">
<div>{logData.map((v) => v.log_content).join('') || '暂无日志'}</div>
<div>{logContent}</div>
{hasMore && ( {hasMore && (
<Button <Button
type="text" type="text"
@@ -137,6 +140,10 @@ function ServerLog({ info }: ServerLogProps) {
</Button> </Button>
)} )}
</div> </div>
) : (
<div className={classNames(styles['server-log__data'], styles['server-log__data--empty'])}>
暂无日志
</div>
)} )}
</div> </div>
); );


+ 1
- 1
react-ui/src/pages/Pipeline/Info/index.jsx View File

@@ -110,7 +110,7 @@ const EditPipeline = () => {
// console.log(data); // console.log(data);
const errorNode = data.nodes.find((item) => item.formError === true); const errorNode = data.nodes.find((item) => item.formError === true);
if (errorNode) { if (errorNode) {
message.error(`【${errorNode.label}】节点必填项必须配置`);
message.error(`【${errorNode.label}】节点配置验证失败`);
const graphNode = graph.findById(errorNode.id); const graphNode = graph.findById(errorNode.id);
if (graphNode) { if (graphNode) {
openNodeDrawer(graphNode, true); openNodeDrawer(graphNode, true);


+ 2
- 2
react-ui/src/pages/Pipeline/components/GlobalParamsDrawer/index.tsx View File

@@ -148,7 +148,7 @@ const GlobalParamsDrawer = forwardRef(
> >
{getParamComponent(type)} {getParamComponent(type)}
</Form.Item> </Form.Item>
{type !== 3 && (
{/* {type !== 3 && (
<Form.Item <Form.Item
{...restField} {...restField}
name={[name, 'is_sensitive']} name={[name, 'is_sensitive']}
@@ -161,7 +161,7 @@ const GlobalParamsDrawer = forwardRef(
<Radio value={0}>否</Radio> <Radio value={0}>否</Radio>
</Radio.Group> </Radio.Group>
</Form.Item> </Form.Item>
)}
)} */}
</> </>
); );
}} }}


+ 130
- 92
react-ui/src/pages/Pipeline/components/PipelineNodeDrawer/index.tsx View File

@@ -19,6 +19,7 @@ import { openAntdModal } from '@/utils/modal';
import { to } from '@/utils/promise'; import { to } from '@/utils/promise';
import { INode } from '@antv/g6'; import { INode } from '@antv/g6';
import { Button, Drawer, Form, Input, MenuProps } from 'antd'; import { Button, Drawer, Form, Input, MenuProps } from 'antd';
import { RuleObject } from 'antd/es/form';
import { NamePath } from 'antd/es/form/interface'; import { NamePath } from 'antd/es/form/interface';
import { forwardRef, useImperativeHandle, useState } from 'react'; import { forwardRef, useImperativeHandle, useState } from 'react';
import PropsLabel from '../PropsLabel'; import PropsLabel from '../PropsLabel';
@@ -34,6 +35,12 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
const [stagingItem, setStagingItem] = useState<PipelineNodeModelSerialize>( const [stagingItem, setStagingItem] = useState<PipelineNodeModelSerialize>(
{} as PipelineNodeModelSerialize, {} as PipelineNodeModelSerialize,
); );
const nodeId = Form.useWatch('id', form) as string;
const hasTaskInfo =
nodeId &&
!nodeId.startsWith('git-clone') &&
!nodeId.startsWith('dataset-export') &&
!nodeId.startsWith('model-export');
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [menuItems, setMenuItems] = useState<MenuProps['items']>([]); const [menuItems, setMenuItems] = useState<MenuProps['items']>([]);


@@ -45,10 +52,11 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
const fields = form.getFieldsValue(); const fields = form.getFieldsValue();


// 保存字段顺序 // 保存字段顺序
const control_strategy = {
...stagingItem.control_strategy,
...fields.control_strategy,
};
// const control_strategy = {
// ...stagingItem.control_strategy,
// ...fields.control_strategy,
// };

const in_parameters = { const in_parameters = {
...stagingItem.in_parameters, ...stagingItem.in_parameters,
...fields.in_parameters, ...fields.in_parameters,
@@ -63,7 +71,7 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
const res = { const res = {
...stagingItem, ...stagingItem,
...fields, ...fields,
control_strategy: JSON.stringify(control_strategy),
// control_strategy: JSON.stringify(control_strategy),
in_parameters: JSON.stringify(in_parameters), in_parameters: JSON.stringify(in_parameters),
out_parameters: JSON.stringify(out_parameters), out_parameters: JSON.stringify(out_parameters),
formError: !!error, formError: !!error,
@@ -90,7 +98,7 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
...model, ...model,
in_parameters: JSON.parse(model.in_parameters), in_parameters: JSON.parse(model.in_parameters),
out_parameters: JSON.parse(model.out_parameters), out_parameters: JSON.parse(model.out_parameters),
control_strategy: JSON.parse(model.control_strategy),
// control_strategy: JSON.parse(model.control_strategy),
}; };
// console.log('model', nodeData); // console.log('model', nodeData);
setStagingItem({ setStagingItem({
@@ -309,22 +317,47 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
); );
}; };


// 模型部署-服务版本验证
const serviceVersionValidator = (_rule: RuleObject, value: any) => {
// 输入参数值都是对象,如果是手动输入,要求 /^[a-zA-Z0-9._-]+$/
if (
typeof value === 'object' &&
value &&
!value.fromSelect &&
value.value &&
!/^[a-zA-Z0-9._-]+$/.test(value.value)
) {
return Promise.reject('服务版本只支持字母、数字、点(.)、下划线(_)、中横线(-)');
}

return Promise.resolve();
};

// 必填项校验规则 // 必填项校验规则
const getFormRules = (item: { key: string; value: PipelineNodeModelParameter }) => { const getFormRules = (item: { key: string; value: PipelineNodeModelParameter }) => {
return item.value.require
const id = form.getFieldValue('id') as string;
const rules = item.value.require
? [ ? [
{ {
validator: requiredValidator, validator: requiredValidator,
message: '必填项',
}, },
] ]
: []; : [];

// 模型部署-服务版本验证
if (id.startsWith('model-deploy') && item.key === '--version') {
rules.push({
validator: serviceVersionValidator,
});
}

return rules;
}; };


// 控制策略 // 控制策略
const controlStrategyList = Object.entries(stagingItem.control_strategy ?? {}).map(
([key, value]) => ({ key, value }),
);
// const controlStrategyList = Object.entries(stagingItem.control_strategy ?? {}).map(
// ([key, value]) => ({ key, value }),
// );


// 输入参数 // 输入参数
const inParametersList = Object.entries(stagingItem.in_parameters ?? {}).map(([key, value]) => ({ const inParametersList = Object.entries(stagingItem.in_parameters ?? {}).map(([key, value]) => ({
@@ -396,71 +429,73 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
> >
<Input disabled /> <Input disabled />
</Form.Item> </Form.Item>
<div className={styles['pipeline-drawer__title']}>
<SubAreaTitle
image={require('@/assets/img/duty-message.png')}
title="任务信息"
></SubAreaTitle>
</div>
<Form.Item label="镜像" required>
<div className={styles['pipeline-drawer__ref-row']}>
<Form.Item name="image" noStyle rules={[{ required: true, message: '请输入镜像' }]}>
<Input placeholder="请输入或选择镜像" allowClear />
{hasTaskInfo && (
<>
<div className={styles['pipeline-drawer__title']}>
<SubAreaTitle
image={require('@/assets/img/duty-message.png')}
title="任务信息"
></SubAreaTitle>
</div>
<Form.Item label="镜像" required>
<div className={styles['pipeline-drawer__ref-row']}>
<Form.Item name="image" noStyle rules={[{ required: true, message: '请输入镜像' }]}>
<Input placeholder="请输入或选择镜像" allowClear />
</Form.Item>
<Form.Item noStyle>
<Button
type="link"
size="small"
icon={getSelectBtnIcon({ item_type: 'image' })}
onClick={() => selectResource('image', { item_type: 'image' })}
className={styles['pipeline-drawer__ref-row__select-button']}
>
选择镜像
</Button>
</Form.Item>
</div>
</Form.Item> </Form.Item>
<Form.Item noStyle>
<Button
type="link"
size="small"
icon={getSelectBtnIcon({ item_type: 'image' })}
onClick={() => selectResource('image', { item_type: 'image' })}
className={styles['pipeline-drawer__ref-row__select-button']}
>
选择镜像
</Button>
<Form.Item
name="working_directory"
label={
<PropsLabel
menuItems={menuItems}
title="工作目录"
onClick={(value) => {
handleParameterClick('working_directory', value);
}}
/>
}
>
<Input placeholder="请输入工作目录" allowClear />
</Form.Item> </Form.Item>
</div>
</Form.Item>
<Form.Item
name="working_directory"
label={
<PropsLabel
menuItems={menuItems}
title="工作目录"
onClick={(value) => {
handleParameterClick('working_directory', value);
}}
/>
}
>
<Input placeholder="请输入工作目录" allowClear />
</Form.Item>
<Form.Item
name="command"
label={
<PropsLabel
menuItems={menuItems}
title="启动命令"
onClick={(value) => {
handleParameterClick('command', value);
}}
/>
}
>
<TextArea placeholder="请输入启动命令" allowClear />
</Form.Item>
<Form.Item
label="资源规格"
name="resources_standard"
rules={[
{
required: true,
message: '请选择资源规格',
},
]}
>
<ParameterSelect dataType="resource" placeholder="请选择资源规格" />
</Form.Item>
<Form.Item
<Form.Item
name="command"
label={
<PropsLabel
menuItems={menuItems}
title="启动命令"
onClick={(value) => {
handleParameterClick('command', value);
}}
/>
}
>
<TextArea placeholder="请输入启动命令" allowClear />
</Form.Item>
<Form.Item
label="资源规格"
name="resources_standard"
rules={[
{
required: true,
message: '请选择资源规格',
},
]}
>
<ParameterSelect dataType="resource" placeholder="请选择资源规格" />
</Form.Item>
{/* <Form.Item
name="mount_path" name="mount_path"
label={ label={
<PropsLabel <PropsLabel
@@ -473,23 +508,23 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
} }
> >
<Input placeholder="请输入挂载路径" allowClear /> <Input placeholder="请输入挂载路径" allowClear />
</Form.Item>
<Form.Item
name="env_variables"
label={
<PropsLabel
menuItems={menuItems}
title="环境变量"
onClick={(value) => {
handleParameterClick('env_variables', value);
}}
/>
}
>
<TextArea placeholder="请输入环境变量" allowClear />
</Form.Item>
{/* 控制参数 */}
{controlStrategyList.map((item) => (
</Form.Item> */}
<Form.Item
name="env_variables"
label={
<PropsLabel
menuItems={menuItems}
title="环境变量"
onClick={(value) => {
handleParameterClick('env_variables', value);
}}
/>
}
>
<TextArea placeholder="请输入环境变量" allowClear />
</Form.Item>
{/* 控制参数 */}
{/* {controlStrategyList.map((item) => (
<Form.Item <Form.Item
key={item.key} key={item.key}
name={['control_strategy', item.key]} name={['control_strategy', item.key]}
@@ -499,7 +534,10 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
> >
<ParameterInput placeholder={item.value.placeholder} allowClear></ParameterInput> <ParameterInput placeholder={item.value.placeholder} allowClear></ParameterInput>
</Form.Item> </Form.Item>
))}
))} */}
</>
)}

{/* 输入参数 */} {/* 输入参数 */}
{inParametersList.length > 0 && ( {inParametersList.length > 0 && (
<> <>


+ 2
- 2
react-ui/src/pages/Pipeline/index.jsx View File

@@ -190,7 +190,7 @@ const Pipeline = () => {
}), }),
}, },
{ {
title: '流水线名称',
title: '流水线模板名称',
dataIndex: 'name', dataIndex: 'name',
key: 'name', key: 'name',
width: '50%', width: '50%',
@@ -199,7 +199,7 @@ const Pipeline = () => {
}), }),
}, },
{ {
title: '流水线描述',
title: '流水线模板描述',
dataIndex: 'description', dataIndex: 'description',
key: 'description', key: 'description',
width: '50%', width: '50%',


+ 1
- 1
react-ui/src/pages/System/Role/authUser.tsx View File

@@ -113,7 +113,7 @@ const AuthUserTableList: React.FC = () => {
dataIndex: 'createTime', dataIndex: 'createTime',
valueType: 'dateRange', valueType: 'dateRange',
render: (_, record) => { render: (_, record) => {
return <span>{record.createTime.toString()} </span>;
return <span>{record.createTime?.toString()} </span>;
}, },
hideInSearch: true, hideInSearch: true,
}, },


+ 1
- 1
react-ui/src/pages/System/Role/components/UserSelectorModal.tsx View File

@@ -84,7 +84,7 @@ const UserSelectorModal: React.FC<DataScopeFormProps> = (props) => {
valueType: 'dateRange', valueType: 'dateRange',
hideInSearch: true, hideInSearch: true,
render: (_, record) => { render: (_, record) => {
return <span>{record.createTime.toString()} </span>;
return <span>{record.createTime?.toString()} </span>;
}, },
}, },
]; ];


+ 2
- 2
react-ui/src/pages/System/Role/index.tsx View File

@@ -177,7 +177,7 @@ const RoleTableList: React.FC = () => {
confirm({ confirm({
title: `确认要${text}${record.roleName}角色吗?`, title: `确认要${text}${record.roleName}角色吗?`,
onOk() { onOk() {
changeRoleStatus(record.roleId, newStatus).then((resp) => {
changeRoleStatus(record.roleId, record.roleKey, newStatus).then((resp) => {
if (resp.code === 200) { if (resp.code === 200) {
messageApi.open({ messageApi.open({
type: 'success', type: 'success',
@@ -240,7 +240,7 @@ const RoleTableList: React.FC = () => {
dataIndex: 'createTime', dataIndex: 'createTime',
valueType: 'dateRange', valueType: 'dateRange',
render: (_, record) => { render: (_, record) => {
return <span>{record.createTime.toString()} </span>;
return <span>{record.createTime?.toString()} </span>;
}, },
search: { search: {
transform: (value) => { transform: (value) => {


+ 5
- 8
react-ui/src/pages/System/User/components/DeptTree.tsx View File

@@ -28,8 +28,11 @@ const DeptTree: React.FC<TreeProps> = (props) => {
const res = await getDeptTree({}); const res = await getDeptTree({});
const treeData = res.map((item: any) => ({ ...item, key: item.id })); const treeData = res.map((item: any) => ({ ...item, key: item.id }));
setTreeData(treeData); setTreeData(treeData);
setExpandedKeys([treeData[0].key]);
setSelectedKeys([treeData[0].key]);
if (treeData.length > 0) {
onSelect(treeData[0]);
setExpandedKeys([treeData[0].key]);
setSelectedKeys([treeData[0].key]);
}
hide(); hide();
return true; return true;
} catch (error) { } catch (error) {
@@ -41,12 +44,6 @@ const DeptTree: React.FC<TreeProps> = (props) => {
fetchDeptList(); fetchDeptList();
}, []); }, []);


useEffect(() => {
if (treeData.length > 0) {
onSelect(treeData[0]);
}
}, [treeData, onSelect]);

const handleSelect = (keys: React.Key[], info: any) => { const handleSelect = (keys: React.Key[], info: any) => {
setSelectedKeys(keys); setSelectedKeys(keys);
onSelect(info.node); onSelect(info.node);


+ 2
- 3
react-ui/src/pages/System/User/edit.tsx View File

@@ -203,9 +203,8 @@ const UserForm: React.FC<UserFormProps> = (props) => {
required: true, required: true,
}, },
{ {
pattern: /^[a-zA-Z](?:[a-zA-Z0-9_.-]*[a-zA-Z0-9])?$/,
message:
'只能包含数字,字母,下划线(_),中横线(-),英文句号(.),且必须以字母开头,数字或字母结尾',
pattern: /^[a-zA-Z][a-zA-Z0-9]{3,14}$/,
message: '用户账号长度为4 ~ 15位,且必须以字母开头,只支持字母和数字',
}, },
]} ]}
/> />


+ 12
- 1
react-ui/src/pages/Workspace/components/AssetsManagement/index.less View File

@@ -37,7 +37,18 @@
&__summary { &__summary {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
width: 33.33%;
width: 40%;
text-align: left;

&:nth-child(3n+2) {
text-align: center;
width: 30%;
}

&:nth-child(3n) {
text-align: right;
width: 30%;
}


&__title { &__title {
margin-bottom: 12px; margin-bottom: 12px;


+ 16
- 8
react-ui/src/pages/Workspace/components/AssetsManagement/index.tsx View File

@@ -16,7 +16,7 @@ function AssetsManagement() {
}; };
const [res] = await to(getWorkspaceAssetCountReq(params)); const [res] = await to(getWorkspaceAssetCountReq(params));
if (res && res.data) { if (res && res.data) {
const { component, dataset, image, model, workflow } = res.data;
const { dataset, image, model, workflow, codeConfig, service } = res.data;
const items = [ const items = [
{ {
title: '数据集', title: '数据集',
@@ -30,10 +30,10 @@ function AssetsManagement() {
title: '镜像', title: '镜像',
value: image, value: image,
}, },
{
title: '组件',
value: component,
},
// {
// title: '组件',
// value: component,
// },
// { // {
// title: '代码配置', // title: '代码配置',
// value: 0, // value: 0,
@@ -42,6 +42,14 @@ function AssetsManagement() {
title: '流水线模版', title: '流水线模版',
value: workflow, value: workflow,
}, },
{
title: '代码配置',
value: codeConfig,
},
{
title: '服务',
value: service,
},
]; ];
setAssetCounts(items); setAssetCounts(items);
} }
@@ -53,7 +61,7 @@ function AssetsManagement() {
return ( return (
<div className={styles['assets-management']}> <div className={styles['assets-management']}>
<Flex justify="space-between"> <Flex justify="space-between">
<div className={styles['assets-management__title']}>AI资产</div>
<div className={styles['assets-management__title']}>多形态资源库</div>
<Select <Select
size="small" size="small"
value={type} value={type}
@@ -67,11 +75,11 @@ function AssetsManagement() {
/> />
</Flex> </Flex>
{/* <div className={styles['assets-management__increase']}>今日新增数量:5</div> */} {/* <div className={styles['assets-management__increase']}>今日新增数量:5</div> */}
<Flex gap="22px 0" wrap="wrap" style={{ marginTop: '40px' }}>
<Flex gap="22px 0" wrap="wrap" style={{ marginTop: '40px', padding: '0 8px' }}>
{assetCounts.map((item, index) => ( {assetCounts.map((item, index) => (
<div className={styles['assets-management__summary']} key={index}> <div className={styles['assets-management__summary']} key={index}>
<div className={styles['assets-management__summary__title']}>{item.title}</div> <div className={styles['assets-management__summary__title']}>{item.title}</div>
<div className={styles['assets-management__summary__value']}>{item.value}</div>
<div className={styles['assets-management__summary__value']}>{item.value ?? '-'}</div>
</div> </div>
))} ))}
</Flex> </Flex>


+ 4
- 4
react-ui/src/pages/Workspace/components/ExperimentTable/index.less View File

@@ -1,6 +1,6 @@
.experiment-table { .experiment-table {
flex: 1; flex: 1;
min-width: 378px;
min-width: 460px;
height: 140px; height: 140px;
padding: 12px; padding: 12px;
background: @workspace-background; background: @workspace-background;
@@ -32,7 +32,7 @@
} }


&__status { &__status {
width: 15%;
width: 25%;
} }


&__duration { &__duration {
@@ -40,11 +40,11 @@
} }


&__date { &__date {
width: calc(60% - 60px);
width: 40%;
} }


&__operation { &__operation {
width: 60px;
width: 10%;


:global { :global {
.ant-btn-link { .ant-btn-link {


+ 8
- 29
react-ui/src/pages/Workspace/components/ExperimentTable/index.tsx View File

@@ -1,10 +1,9 @@
import KFIcon from '@/components/KFIcon';
import { ExperimentStatus, experimentStatusInfo } from '@/pages/Experiment/status';
import { ExperimentInstance } from '@/types'; import { ExperimentInstance } from '@/types';
import { elapsedTime, formatDate } from '@/utils/date';
import { useNavigate } from '@umijs/max'; import { useNavigate } from '@umijs/max';
import { Button, Empty } from 'antd';
import { Empty } from 'antd';
import styles from './index.less'; import styles from './index.less';
import ExperimentInstanceComponent from './instance';

type ExperimentTableProps = { type ExperimentTableProps = {
tableData: ExperimentInstance[]; tableData: ExperimentInstance[];
style?: React.CSSProperties; style?: React.CSSProperties;
@@ -26,31 +25,11 @@ function ExperimentTable({ tableData = [], style }: ExperimentTableProps) {
</div> </div>
{Array.isArray(tableData) && tableData.length > 0 ? ( {Array.isArray(tableData) && tableData.length > 0 ? (
tableData.map((item) => ( tableData.map((item) => (
<div className={styles['experiment-table__content']} key={item.id}>
<div className={styles['experiment-table__status']} style={{ paddingLeft: '6.5px' }}>
<img
src={experimentStatusInfo[item.status as ExperimentStatus]?.icon}
width={17}
height={17}
draggable={false}
alt=""
/>
</div>
<div className={styles['experiment-table__duration']}>
{elapsedTime(item.create_time, item.finish_time)}
</div>
<div className={styles['experiment-table__date']}>{formatDate(item.create_time)}</div>
<div className={styles['experiment-table__operation']}>
<Button
size="small"
type="link"
icon={<KFIcon type="icon-xiangqing2" font={14} />}
onClick={() => gotoExperiment(item)}
>
详情
</Button>
</div>
</div>
<ExperimentInstanceComponent
instance={item}
key={item.id}
onClick={() => gotoExperiment(item)}
/>
)) ))
) : ( ) : (
<Empty <Empty


+ 76
- 0
react-ui/src/pages/Workspace/components/ExperimentTable/instance.tsx View File

@@ -0,0 +1,76 @@
import KFIcon from '@/components/KFIcon';
import RunDuration from '@/components/RunDuration';
import { ExperimentStatus } from '@/enums';
import { useSSE, type MessageHandler } from '@/hooks/useSSE';
import { experimentStatusInfo } from '@/pages/Experiment/status';
import { ExperimentInstance, NodeStatus } from '@/types';
import { formatDate } from '@/utils/date';
import { getExperimentInstanceStatus, getWorkflowStatus } from '@/utils/experiment';
import { Button } from 'antd';
import { useCallback, useState } from 'react';
import styles from './index.less';

const NodePrefix = 'workflow';

type ExperimentInstanceComponentProps = {
instance: ExperimentInstance;
onClick: () => void;
};

function ExperimentInstanceComponent({ instance, onClick }: ExperimentInstanceComponentProps) {
const { experiment_id, id, argo_ins_name, argo_ins_ns, nodes_status } = instance;
const initialWorkflowStatus = getWorkflowStatus(nodes_status) as NodeStatus | undefined;
const [workflowStatus, setWorkflowStatus] = useState<NodeStatus | undefined>(
initialWorkflowStatus,
);
const status = getExperimentInstanceStatus(instance.status as ExperimentStatus, workflowStatus);
const createTime = workflowStatus?.startedAt;
const finishTime = workflowStatus?.finishedAt;
const statusInfo = experimentStatusInfo[status];

const handleSSEMessage: MessageHandler = useCallback(
(
_experimentId: number,
_experimentInsId: number,
_status: string,
_finishTime: string,
nodes,
) => {
if (nodes) {
// 设置总 workflow 状态
const workflowStatus = Object.values(nodes).find((node: any) =>
node.displayName.startsWith(NodePrefix),
) as NodeStatus;
if (workflowStatus) {
setWorkflowStatus(workflowStatus);
}
}
},
[],
);
useSSE(experiment_id, id, status, argo_ins_name, argo_ins_ns, handleSSEMessage);

return (
<div className={styles['experiment-table__content']} key={id}>
<div className={styles['experiment-table__status']} style={{ paddingLeft: '6.5px' }}>
<img src={statusInfo?.icon} width={17} height={17} draggable={false} alt="" />
</div>
<div className={styles['experiment-table__duration']}>
<RunDuration createTime={createTime} finishTime={finishTime} />
</div>
<div className={styles['experiment-table__date']}>{formatDate(createTime)}</div>
<div className={styles['experiment-table__operation']}>
<Button
size="small"
type="link"
icon={<KFIcon type="icon-xiangqing2" font={14} />}
onClick={onClick}
>
详情
</Button>
</div>
</div>
);
}

export default ExperimentInstanceComponent;

+ 21
- 21
react-ui/src/pages/Workspace/components/QuickStart/index.tsx View File

@@ -16,13 +16,13 @@ function QuickStart() {
const changeScale = () => { const changeScale = () => {
// body 的宽度 - 菜单的宽度 - 两个 padding - 右边用户管理的宽度 - 右边用户管理的 marginLeft - 滚动条的宽度, // body 的宽度 - 菜单的宽度 - 两个 padding - 右边用户管理的宽度 - 右边用户管理的 marginLeft - 滚动条的宽度,
const width = document.body.offsetWidth - 256 - 80 - 60 - 326 - 15 - 8; const width = document.body.offsetWidth - 256 - 80 - 60 - 326 - 15 - 8;
if (width >= 1223) {
const spaceX = (width - 192 * 5 - 60) / 7;
if (width >= 1002) {
const spaceX = (width - 192 * 4 - 60) / 6;
setSpace(spaceX); setSpace(spaceX);
setCanvasWidth('100%'); setCanvasWidth('100%');
setScale(1.0); setScale(1.0);
} else { } else {
const ratio = width / 1223;
const ratio = width / 1002;
setCanvasWidth('1223px'); setCanvasWidth('1223px');
setSpace(29); setSpace(29);
setScale(ratio); setScale(ratio);
@@ -54,56 +54,56 @@ function QuickStart() {
> >
<WorkFlow <WorkFlow
content="为开发者提供数据智能标注与数据回流服务" content="为开发者提供数据智能标注与数据回流服务"
buttonText="数据准备"
buttonTop={40}
buttonText="数据标注"
buttonTop={20}
x={left} x={left}
y={309} y={309}
onClick={() => navigate('/datasetPreparation/datasetAnnotation')} onClick={() => navigate('/datasetPreparation/datasetAnnotation')}
/> />
<WorkFlow
{/* <WorkFlow
content="为开发者提供定制化编辑器,开发者可根据自己需求选择配置,保存编译器中的调试环境为镜像供训练使用" content="为开发者提供定制化编辑器,开发者可根据自己需求选择配置,保存编译器中的调试环境为镜像供训练使用"
buttonText="开发环境" buttonText="开发环境"
buttonTop={20} buttonTop={20}
x={left + 192 + space} x={left + 192 + space}
y={301} y={301}
onClick={() => navigate('/developmentEnvironment')} onClick={() => navigate('/developmentEnvironment')}
/>
/> */}
<WorkFlow <WorkFlow
content="为开发者提供定制化编辑器,开发者可根据自己需求选择配置,保存编译器中的调试环境为镜像供训练使用" content="为开发者提供定制化编辑器,开发者可根据自己需求选择配置,保存编译器中的调试环境为镜像供训练使用"
tips="可视化建模Designer" tips="可视化建模Designer"
buttonText="流水线"
buttonText="流水线模板"
buttonTop={20} buttonTop={20}
x={left + 2 * (192 + space)}
x={left + 1 * (192 + space)}
y={276} y={276}
onClick={() => navigate('/pipeline/template')} onClick={() => navigate('/pipeline/template')}
/> />
<WorkFlow <WorkFlow
content="开发者可以在这里运行流水线模板,产生实验实例,对比实验训练过程与产生的实验训练数据" content="开发者可以在这里运行流水线模板,产生实验实例,对比实验训练过程与产生的实验训练数据"
buttonText="实验" buttonText="实验"
buttonTop={40}
x={left + 3 * (192 + space)}
buttonTop={20}
x={left + 2 * (192 + space)}
y={295} y={295}
onClick={() => navigate('/pipeline/experiment')} onClick={() => navigate('/pipeline/experiment')}
/> />
<WorkFlow <WorkFlow
content="支持异构硬件(CPU/GPU)的模型加载,高吞吐,低延迟;支持大规模复杂模型的一键部署,实时弹性扩缩容;提供完整的运维监控体系。" content="支持异构硬件(CPU/GPU)的模型加载,高吞吐,低延迟;支持大规模复杂模型的一键部署,实时弹性扩缩容;提供完整的运维监控体系。"
tips="模型在线服务" tips="模型在线服务"
buttonText="模型在线部署"
buttonText="服务"
buttonTop={20} buttonTop={20}
x={left + 4 * (192 + space) + 60 + space}
x={left + 3 * (192 + space) + 60 + space}
y={263} y={263}
onClick={() => navigate('/dataset/modelDeployment')} onClick={() => navigate('/dataset/modelDeployment')}
/> />
<div <div
className={styles['quick-start__content__canvas__model']} className={styles['quick-start__content__canvas__model']}
style={{ top: '358px', left: left + 4 * (192 + space) + 'px' }}
style={{ top: '358px', left: left + 3 * (192 + space) + 'px' }}
> >
<KFIcon type="icon-moxingguanli" font={38} /> <KFIcon type="icon-moxingguanli" font={38} />
<span>模型管理</span> <span>模型管理</span>
</div> </div>
<div <div
className={styles['quick-start__content__canvas__task']} className={styles['quick-start__content__canvas__task']}
style={{ top: '110px', left: left + 2 * (192 + space) + 56 + taskLeftArrowWidth + 16 }}
style={{ top: '110px', left: left + 1 * (192 + space) + 56 + taskLeftArrowWidth + 16 }}
> >
<KFIcon type="icon-tiaoduguanli" font={13} style={{ marginRight: '5px' }} /> <KFIcon type="icon-tiaoduguanli" font={13} style={{ marginRight: '5px' }} />
<span>任务自动调度</span> <span>任务自动调度</span>
@@ -117,7 +117,7 @@ function QuickStart() {
arrorwTop={-4} arrorwTop={-4}
borderBottom={1} borderBottom={1}
/> />
<WorkArrow
{/* <WorkArrow
x={left + 2 * 192 + space + 1} x={left + 2 * 192 + space + 1}
y={378} y={378}
width={arrowWidth} width={arrowWidth}
@@ -125,9 +125,9 @@ function QuickStart() {
arrowLeft={arrowWidth} arrowLeft={arrowWidth}
arrorwTop={-4} arrorwTop={-4}
borderBottom={1} borderBottom={1}
/>
/> */}
<WorkArrow <WorkArrow
x={left + 4 * 192 + 3 * space + 1}
x={left + 3 * 192 + 2 * space + 1}
y={378} y={378}
width={arrowWidth + 10} width={arrowWidth + 10}
height={1} height={1}
@@ -136,7 +136,7 @@ function QuickStart() {
borderBottom={1} borderBottom={1}
/> />
<WorkArrow <WorkArrow
x={left + 4 * 192 + 60 + 4 * space + 1 - 10}
x={left + 3 * 192 + 60 + 3 * space + 1 - 10}
y={378} y={378}
width={arrowWidth + 10} width={arrowWidth + 10}
height={1} height={1}
@@ -145,7 +145,7 @@ function QuickStart() {
borderBottom={1} borderBottom={1}
/> />
<WorkArrow <WorkArrow
x={left + 2 * (192 + space) + 56}
x={left + 1 * (192 + space) + 56}
y={139} y={139}
width={taskLeftArrowWidth} width={taskLeftArrowWidth}
height={120} height={120}
@@ -155,7 +155,7 @@ function QuickStart() {
borderTop={1} borderTop={1}
/> />
<WorkArrow <WorkArrow
x={left + 2 * (192 + space) + 56 + taskLeftArrowWidth + 16 + 131 + 4}
x={left + 1 * (192 + space) + 56 + taskLeftArrowWidth + 16 + 131 + 4}
y={127} y={127}
width={taskRightArrowWidth} width={taskRightArrowWidth}
height={156} height={156}


+ 7
- 7
react-ui/src/pages/Workspace/components/TotalStatistics/index.less View File

@@ -3,16 +3,16 @@
align-items: center; align-items: center;
justify-content: center; justify-content: center;
height: 140px; height: 140px;
padding: 0 16px;
padding: 0 35px;


// 媒体查询
@media screen and (max-width: 1600px) {
flex: auto;
}
// // 媒体查询
// @media screen and (max-width: 1600px) {
// flex: auto;
// }


&__icon { &__icon {
width: 63px;
margin-right: 16px;
width: 80px;
margin-right: 20px;
} }


&__title { &__title {


+ 1
- 0
react-ui/src/pages/Workspace/components/UserPoints/index.less View File

@@ -7,6 +7,7 @@
height: 228px; height: 228px;
padding: 0 20px; padding: 0 20px;
.backgroundFullImage(url(@/assets/img/user-points-bg.png)); .backgroundFullImage(url(@/assets/img/user-points-bg.png));
margin-bottom: 16px;


&__label { &__label {
margin-top: 60px; margin-top: 60px;


+ 1
- 1
react-ui/src/pages/Workspace/components/WorkspaceIntro/index.less View File

@@ -2,7 +2,7 @@
display: flex; display: flex;
align-items: center; align-items: center;
margin-bottom: 16px; margin-bottom: 16px;
padding: 0 30px;
padding: 20px 30px;
background-image: url(@/assets/img/workspace-intro.png); background-image: url(@/assets/img/workspace-intro.png);
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: top right; background-position: top right;


+ 4
- 5
react-ui/src/pages/Workspace/components/WorkspaceIntro/index.tsx View File

@@ -1,16 +1,15 @@
import { Button } from 'antd';
import styles from './index.less'; import styles from './index.less';


function WorkspaceIntro() { function WorkspaceIntro() {
return ( return (
<div className={styles['workspace-intro']}> <div className={styles['workspace-intro']}>
<div className={styles['workspace-intro__left']}> <div className={styles['workspace-intro__left']}>
<div className={styles['workspace-intro__title']}>自主实验平台</div>
<div className={styles['workspace-intro__title']}>智能材料科研平台</div>
<div className={styles['workspace-intro__content']}> <div className={styles['workspace-intro__content']}>
材料领域的自主实验系统是一种用于材料研究和开发的技术平台,它旨在提供实验数据收集、分析和可视化等功能,
智能材料科研平台是用于材料研究和开发的技术平台,它旨在提供实验数据收集、分析和可视化等功能,
以支持材料工程师、科学家和研究人员在材料设计、性能评估和工艺优化方面的工作 以支持材料工程师、科学家和研究人员在材料设计、性能评估和工艺优化方面的工作
</div> </div>
<div className={styles['workspace-intro__buttons']}>
{/* <div className={styles['workspace-intro__buttons']}>
<Button <Button
type="primary" type="primary"
style={{ marginRight: '20px' }} style={{ marginRight: '20px' }}
@@ -40,7 +39,7 @@ function WorkspaceIntro() {
> >
分子材料自主实验系统 分子材料自主实验系统
</Button> </Button>
</div>
</div> */}
</div> </div>
<div className={styles['workspace-intro__right']}> <div className={styles['workspace-intro__right']}>
<img <img


+ 5
- 5
react-ui/src/pages/Workspace/index.less View File

@@ -27,7 +27,7 @@


&__statistics { &__statistics {
flex: none; flex: none;
min-width: 431px;
// min-width: 500px;
background: linear-gradient( background: linear-gradient(
123.08deg, 123.08deg,
rgba(138, 138, 138, 0.06) 1.32%, rgba(138, 138, 138, 0.06) 1.32%,
@@ -35,10 +35,10 @@
); );
border-radius: 4px; border-radius: 4px;


// 媒体查询
@media screen and (max-width: 1600px) {
flex: 1;
}
// // 媒体查询
// @media screen and (max-width: 1600px) {
// flex: 1;
// }
} }
} }
} }


+ 11
- 13
react-ui/src/pages/Workspace/index.tsx View File

@@ -1,18 +1,17 @@
import { useDraggable } from '@/hooks/useDraggable';
// import { useDraggable } from '@/hooks/useDraggable';
import { getWorkspaceOverviewReq } from '@/services/workspace'; import { getWorkspaceOverviewReq } from '@/services/workspace';
import { ExperimentInstance } from '@/types'; import { ExperimentInstance } from '@/types';
import { to } from '@/utils/promise'; import { to } from '@/utils/promise';
import { Divider, Flex } from 'antd'; import { Divider, Flex } from 'antd';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import Draggable from 'react-draggable';
// import Draggable from 'react-draggable';
import AssetsManagement from './components/AssetsManagement'; import AssetsManagement from './components/AssetsManagement';
import ExperimentChart, { type ExperimentStatistics } from './components/ExperimentChart'; import ExperimentChart, { type ExperimentStatistics } from './components/ExperimentChart';
import ExperitableTable from './components/ExperimentTable'; import ExperitableTable from './components/ExperimentTable';
import QuickStart from './components/QuickStart'; import QuickStart from './components/QuickStart';
import RobotFrame from './components/RobotFrame';
// import RobotFrame from './components/RobotFrame';
import TotalStatistics from './components/TotalStatistics'; import TotalStatistics from './components/TotalStatistics';
import UserPoints from './components/UserPoints'; import UserPoints from './components/UserPoints';
import UserSpace from './components/UserSpace';
import WorkspaceIntro from './components/WorkspaceIntro'; import WorkspaceIntro from './components/WorkspaceIntro';
import styles from './index.less'; import styles from './index.less';


@@ -25,10 +24,10 @@ type OverviewData = {


function Workspace() { function Workspace() {
const [overviewData, setOverviewData] = useState<OverviewData>(); const [overviewData, setOverviewData] = useState<OverviewData>();
const [robotFrameVisible, setRobotFrameVisible] = useState(false);
const { handleStart, handleStop, handleDrag, handleClick } = useDraggable(() =>
setRobotFrameVisible((prev) => !prev),
);
// const [robotFrameVisible, setRobotFrameVisible] = useState(false);
// const { handleStart, handleStop, handleDrag, handleClick } = useDraggable(() =>
// setRobotFrameVisible((prev) => !prev),
// );
const users: number[] = new Array(8).fill(0); const users: number[] = new Array(8).fill(0);


useEffect(() => { useEffect(() => {
@@ -67,7 +66,6 @@ function Workspace() {
count={overviewData?.runningExperimentInsCount} count={overviewData?.runningExperimentInsCount}
/> />
</Flex> </Flex>

<ExperitableTable <ExperitableTable
tableData={overviewData?.latestExperimentInsList || []} tableData={overviewData?.latestExperimentInsList || []}
></ExperitableTable> ></ExperitableTable>
@@ -76,16 +74,16 @@ function Workspace() {
)} )}
</div> </div>
</div> </div>
<UserPoints />
</Flex> </Flex>
<div className={styles['workspace__quick-start']}> <div className={styles['workspace__quick-start']}>
<QuickStart></QuickStart> <QuickStart></QuickStart>
<div className={styles['workspace__user']}> <div className={styles['workspace__user']}>
<UserSpace users={users}></UserSpace>
<UserPoints />
{/* <UserSpace users={users}></UserSpace> */}
<AssetsManagement></AssetsManagement> <AssetsManagement></AssetsManagement>
</div> </div>
</div> </div>
<Draggable onStart={handleStart} onStop={handleStop} onDrag={handleDrag} bounds="body">
{/* <Draggable onStart={handleStart} onStop={handleStop} onDrag={handleDrag} bounds="body">
<img <img
className={styles['workspace__robot-img']} className={styles['workspace__robot-img']}
src={require('@/assets/img/robot.png')} src={require('@/assets/img/robot.png')}
@@ -98,7 +96,7 @@ function Workspace() {
<RobotFrame <RobotFrame
visible={robotFrameVisible} visible={robotFrameVisible}
onClose={() => setRobotFrameVisible(false)} onClose={() => setRobotFrameVisible(false)}
></RobotFrame>
></RobotFrame> */}
</div> </div>
); );
} }


+ 13
- 2
react-ui/src/services/activeLearn/index.js View File

@@ -7,10 +7,11 @@
import { request } from '@umijs/max'; import { request } from '@umijs/max';


// 分页查询超参数自动寻优 // 分页查询超参数自动寻优
export function getActiveLearnListReq(params) {
export function getActiveLearnListReq(params, skipLoading) {
return request(`/api/mmp/activeLearn`, { return request(`/api/mmp/activeLearn`, {
method: 'GET', method: 'GET',
params, params,
skipLoading
}); });
} }


@@ -54,10 +55,11 @@ export function runActiveLearnReq(id) {


// ----------------------- 实验实例 ----------------------- // ----------------------- 实验实例 -----------------------
// 获取实验实例列表 // 获取实验实例列表
export function getActiveLearnInsListReq(params) {
export function getActiveLearnInsListReq(params, skipLoading) {
return request(`/api/mmp/activeLearnIns`, { return request(`/api/mmp/activeLearnIns`, {
method: 'GET', method: 'GET',
params, params,
skipLoading
}); });
} }


@@ -99,3 +101,12 @@ export function getExpMetricsReq(data) {
}); });
} }


// 编辑实验实例
export function editActiveLearnInsReq(data) {
return request(`/api/mmp/activeLearnIns`, {
method: 'PUT',
data,
skipLoading: true,
});
}


+ 13
- 2
react-ui/src/services/autoML/index.js View File

@@ -7,10 +7,11 @@
import { request } from '@umijs/max'; import { request } from '@umijs/max';


// 分页查询自动学习 // 分页查询自动学习
export function getAutoMLListReq(params) {
export function getAutoMLListReq(params, skipLoading) {
return request(`/api/mmp/machineLearn`, { return request(`/api/mmp/machineLearn`, {
method: 'GET', method: 'GET',
params, params,
skipLoading,
}); });
} }


@@ -54,10 +55,11 @@ export function runAutoMLReq(id) {


// ----------------------- 实验实例 ----------------------- // ----------------------- 实验实例 -----------------------
// 获取实验实例列表 // 获取实验实例列表
export function getExperimentInsListReq(params) {
export function getExperimentInsListReq(params, skipLoading) {
return request(`/api/mmp/machineLearnIns`, { return request(`/api/mmp/machineLearnIns`, {
method: 'GET', method: 'GET',
params, params,
skipLoading,
}); });
} }


@@ -89,3 +91,12 @@ export function batchDeleteExperimentInsReq(data) {
data, data,
}); });
} }

// 编辑实验实例
export function editExperimentInsReq(data) {
return request(`/api/mmp/machineLearnIns`, {
method: 'PUT',
data,
skipLoading: true,
});
}

+ 14
- 2
react-ui/src/services/experiment/index.js View File

@@ -1,9 +1,10 @@
import { request } from '@umijs/max'; import { request } from '@umijs/max';
// 查询实验列表 // 查询实验列表
export function getExperiment(params) {
export function getExperiment(params, skipLoading) {
return request(`/api/mmp/experiment`, { return request(`/api/mmp/experiment`, {
method: 'GET', method: 'GET',
params, params,
skipLoading,
}); });
} }
// 运行实验 // 运行实验
@@ -28,10 +29,11 @@ export function deleteExperimentById(id) {
}); });
} }
// 根据id查询实验实例 // 根据id查询实验实例
export function getQueryByExperimentId(params) {
export function getQueryByExperimentId(params, skipLoading) {
return request(`/api/mmp/experimentIns`, { return request(`/api/mmp/experimentIns`, {
method: 'GET', method: 'GET',
params, params,
skipLoading
}); });
} }
// 根据id删除实验实例 // 根据id删除实验实例
@@ -53,6 +55,16 @@ export function putQueryByExperimentInsId(id) {
method: 'PUT', method: 'PUT',
}); });
} }

// 编辑实验实例
export function editExperimentInsReq(data) {
return request(`/api/mmp/experimentIns`, {
method: 'PUT',
data,
skipLoading: true,
});
}

// 查询实验实例实时日志 // 查询实验实例实时日志
export function getQueryByExperimentLog(data) { export function getQueryByExperimentLog(data) {
return request('/api/mmp/experimentIns/realTimeLog/', { return request('/api/mmp/experimentIns/realTimeLog/', {


+ 14
- 2
react-ui/src/services/hyperParameter/index.js View File

@@ -7,10 +7,11 @@
import { request } from '@umijs/max'; import { request } from '@umijs/max';


// 分页查询超参数自动寻优 // 分页查询超参数自动寻优
export function getRayListReq(params) {
export function getRayListReq(params, skipLoading) {
return request(`/api/mmp/ray`, { return request(`/api/mmp/ray`, {
method: 'GET', method: 'GET',
params, params,
skipLoading
}); });
} }


@@ -54,10 +55,11 @@ export function runRayReq(id) {


// ----------------------- 实验实例 ----------------------- // ----------------------- 实验实例 -----------------------
// 获取实验实例列表 // 获取实验实例列表
export function getRayInsListReq(params) {
export function getRayInsListReq(params, skipLoading) {
return request(`/api/mmp/rayIns`, { return request(`/api/mmp/rayIns`, {
method: 'GET', method: 'GET',
params, params,
skipLoading,
}); });
} }


@@ -97,3 +99,13 @@ export function getExpMetricsReq(data) {
data, data,
}); });
} }


// 编辑实验实例
export function editRayInsReq(data) {
return request(`/api/mmp/rayIns`, {
method: 'PUT',
data,
skipLoading: true,
});
}

+ 2
- 1
react-ui/src/services/system/role.ts View File

@@ -70,9 +70,10 @@ export function updateRoleDataScope(data: Record<string, any>) {
} }


// 角色状态修改 // 角色状态修改
export function changeRoleStatus(roleId: number, status: string) {
export function changeRoleStatus(roleId: number, roleKey: string, status: string) {
const data = { const data = {
roleId, roleId,
roleKey,
status, status,
}; };
return request<API.Result>('/api/system/role/changeStatus', { return request<API.Result>('/api/system/role/changeStatus', {


+ 3
- 2
react-ui/src/types.ts View File

@@ -52,6 +52,7 @@ export type ExperimentInstance = {
nodes_result: { nodes_result: {
[key: string]: any; [key: string]: any;
}; };
node_status: string;
nodes_status: string; nodes_status: string;
global_param: PipelineGlobalParam[]; global_param: PipelineGlobalParam[];
tensorBoardStatus?: TensorBoardStatus; tensorBoardStatus?: TensorBoardStatus;
@@ -111,9 +112,9 @@ export type KeysToCamelCase<T> = {
// 序列化后的流水线节点 // 序列化后的流水线节点
export type PipelineNodeModelSerialize = Omit< export type PipelineNodeModelSerialize = Omit<
PipelineNodeModel, PipelineNodeModel,
'control_strategy' | 'in_parameters' | 'out_parameters'
'in_parameters' | 'out_parameters'
> & { > & {
control_strategy: Record<string, PipelineNodeModelParameter>;
// control_strategy: Record<string, PipelineNodeModelParameter>;
in_parameters: Record<string, PipelineNodeModelParameter>; in_parameters: Record<string, PipelineNodeModelParameter>;
out_parameters: Record<string, PipelineNodeModelParameter>; out_parameters: Record<string, PipelineNodeModelParameter>;
}; };


+ 5
- 2
react-ui/src/utils/date.ts View File

@@ -1,4 +1,3 @@
import { now } from '@/hooks/useServerTime';
import dayjs from 'dayjs'; import dayjs from 'dayjs';


/** /**
@@ -13,8 +12,12 @@ export const elapsedTime = (begin?: string | Date | null, end?: string | Date |
return '--'; return '--';
} }


if (end === undefined || end === null) {
return '--';
}

const beginDate = dayjs(begin); const beginDate = dayjs(begin);
const endDate = end === undefined || end === null ? dayjs(now()) : dayjs(end);
const endDate = dayjs(end); // end === undefined || end === null ? dayjs(now()) : dayjs(end);
if (!beginDate.isValid() || !endDate.isValid()) { if (!beginDate.isValid() || !endDate.isValid()) {
return '--'; return '--';
} }


+ 60
- 0
react-ui/src/utils/experiment.ts View File

@@ -0,0 +1,60 @@
import { ExperimentStatus } from '@/enums';
import { NodeStatus } from '@/types';
import { parseJsonText } from './index';

/**
* 获取工作流节点
*
* @param node_status - 流水线workflow节点,json字符串
* @return workflow 节点
*/
export const getWorkflowStatus = (node_status?: string | null) => {
if (!node_status) {
return undefined;
}

const nodeStatusJson = parseJsonText(node_status);
if (!nodeStatusJson) {
return undefined;
}

for (const key in nodeStatusJson) {
if (key.startsWith('workflow')) {
return nodeStatusJson[key];
}
}
return undefined;
};

/**
* 获取实例状态
* 终止或者 workflowStatus 不存在时,取实例状态,否则取流水线状态
*
* @param instanceStatus - 实例状态
* @param workflowStatus - 流水线workflow节点
* @return 实例状态
*/
export const getExperimentInstanceStatus = (
instanceStatus: ExperimentStatus,
workflowStatus?: NodeStatus,
): ExperimentStatus => {
return instanceStatus === ExperimentStatus.Terminated || !workflowStatus
? instanceStatus
: workflowStatus?.phase;
};

/**
* 获取实例状态
* 终止或者 workflowStatus 不存在时,取实例状态,否则取流水线状态
*
* @param instanceStatus - 实例状态
* @param workflowStatus - 流水线workflow节点
* @return 实例状态
*/
export const getInstanceStatusInList = (
instanceStatus: ExperimentStatus,
node_status?: string | null,
): ExperimentStatus => {
const workflowStatus = getWorkflowStatus(node_status);
return getExperimentInstanceStatus(instanceStatus, workflowStatus);
};

+ 11
- 7
react-ui/src/utils/index.ts View File

@@ -6,7 +6,6 @@


import { PageEnum } from '@/enums/pagesEnums'; import { PageEnum } from '@/enums/pagesEnums';
import G6 from '@antv/g6'; import G6 from '@antv/g6';
import { number } from 'echarts';


/** /**
* 生成 8 位随机数 * 生成 8 位随机数
@@ -268,19 +267,25 @@ export const hasNoValue = (value?: any | null): boolean => {
/** /**
* 获取 git 仓库的 url * 获取 git 仓库的 url
* *
* @param {string} url - the url of the git repository
* @param {string} branch - the branch of the repository
* @param {string} [url] - the url of the git repository
* @param {string} [branch] - the branch of the repository
* @return {string} the url of the repository * @return {string} the url of the repository
* *
* If `branch` is given, the url will be in the format of 'http://gitlab.com/user/repo/tree/branch'. * If `branch` is given, the url will be in the format of 'http://gitlab.com/user/repo/tree/branch'.
* Otherwise, the url will be in the format of 'http://gitlab.com/user/repo'. * Otherwise, the url will be in the format of 'http://gitlab.com/user/repo'.
*/ */
export const getGitUrl = (url: string, branch: string): string => {
export const getGitUrl = (url?: string, branch?: string): string => {
if (!url) { if (!url) {
return ''; return '';
} }
const gitUrl = url.replace(/\.git$/, '');
return branch ? `${gitUrl}/tree/${branch}` : gitUrl;
let gitUrlStr = url.replace(/\.git$/, '');
const gitUrl = new URL(gitUrlStr);
if (gitUrl.port === '30202') {
gitUrl.port = '30203'; // 30202 该为 30203
}
gitUrlStr = gitUrl.href;

return branch ? `${gitUrlStr.toString()}/tree/${branch}` : gitUrlStr;
}; };


/** /**
@@ -347,4 +352,3 @@ export const trimCharacter = (str: string, ch: string): string => {
export const convertEmptyStringToUndefined = (value?: string): string | undefined => { export const convertEmptyStringToUndefined = (value?: string): string | undefined => {
return value === '' ? undefined : value; return value === '' ? undefined : value;
}; };


+ 1
- 1
react-ui/tests/date.test.ts View File

@@ -1,6 +1,6 @@
import { canBeConvertToDate } from '../src/utils/date'; import { canBeConvertToDate } from '../src/utils/date';


describe('canBeConvertToDate()', () => {
describe('test canBeConvertToDate()', () => {
test('null', () => { test('null', () => {
expect(canBeConvertToDate(null)).toBe(false); expect(canBeConvertToDate(null)).toBe(false);
}); });


+ 35
- 0
react-ui/tests/getGitUrl.test.ts View File

@@ -0,0 +1,35 @@
import { getGitUrl } from '../src/utils';

describe('test getGitUrl', () => {
test('undefined', () => {
expect(getGitUrl(undefined)).toBe('');
});

test('empty url', () => {
expect(getGitUrl('')).toBe('');
});

test('url domain with branch', () => {
expect(
getGitUrl('https://gitlink.org.cn/somunslotus/material-atom-predict.git', 'master'),
).toBe('https://gitlink.org.cn/somunslotus/material-atom-predict/tree/master');
});

test('url domain without branch', () => {
expect(getGitUrl('https://gitlink.org.cn/somunslotus/material-atom-predict.git')).toBe(
'https://gitlink.org.cn/somunslotus/material-atom-predict',
);
});

test('30202 port with branch', () => {
expect(getGitUrl('http://172.20.32.235:30202/fanshuai/active_learn_demo.git', 'master')).toBe(
'http://172.20.32.235:30203/fanshuai/active_learn_demo/tree/master',
);
});

test('30202 port without branch', () => {
expect(getGitUrl('http://172.20.32.235:30202/fanshuai/active_learn_demo.git')).toBe(
'http://172.20.32.235:30203/fanshuai/active_learn_demo',
);
});
});

+ 8
- 2
ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/constant/Constant.java View File

@@ -19,8 +19,6 @@ public class Constant {
public final static String Computing_Resource_CPU = "CPU"; // 计算资源_CPU public final static String Computing_Resource_CPU = "CPU"; // 计算资源_CPU
public final static String Computing_Resource_GPU = "GPU"; // 计算资源_GPU public final static String Computing_Resource_GPU = "GPU"; // 计算资源_GPU


public final static int Git_Category_Id = 39;

public final static String Topic_Dataset = "dataset"; public final static String Topic_Dataset = "dataset";
public final static String Topic_model = "model"; public final static String Topic_model = "model";


@@ -44,6 +42,9 @@ public class Constant {
public final static String Unknown = "Unknown"; public final static String Unknown = "Unknown";
public final static String Available = "Available"; public final static String Available = "Available";


public final static String Ready = "Ready";
public final static String True = "True";

public final static String Type_Train = "train"; public final static String Type_Train = "train";
public final static String Type_Evaluate = "evaluate"; public final static String Type_Evaluate = "evaluate";


@@ -67,10 +68,15 @@ public class Constant {
public final static String ML_TextClassification = "text_classification"; public final static String ML_TextClassification = "text_classification";
public final static String ML_VideoClassification = "video_classification"; public final static String ML_VideoClassification = "video_classification";


public final static String AL_PYTORCH = "pytorch";
public final static String AL_SKLEARN = "sklearn";
public final static String AL_KERAS = "keras";

public final static String DelFlag = "2"; public final static String DelFlag = "2";


public final static String Code = "123123"; public final static String Code = "123123";


public final static String Sex_Men = "0"; public final static String Sex_Men = "0";


public final static String Admin = "admin";
} }

+ 91
- 94
ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/domain/SysRole.java View File

@@ -4,6 +4,8 @@ import java.util.Set;
import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size; import javax.validation.constraints.Size;

import com.ruoyi.system.api.constant.Constant;
import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.commons.lang3.builder.ToStringStyle;
import com.ruoyi.common.core.annotation.Excel; import com.ruoyi.common.core.annotation.Excel;
@@ -12,230 +14,225 @@ import com.ruoyi.common.core.web.domain.BaseEntity;


/** /**
* 角色表 sys_role * 角色表 sys_role
*
*
* @author ruoyi * @author ruoyi
*/ */
public class SysRole extends BaseEntity
{
public class SysRole extends BaseEntity {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;


/** 角色ID */
/**
* 角色ID
*/
@Excel(name = "角色序号", cellType = ColumnType.NUMERIC) @Excel(name = "角色序号", cellType = ColumnType.NUMERIC)
private Long roleId; private Long roleId;


/** 角色名称 */
/**
* 角色名称
*/
@Excel(name = "角色名称") @Excel(name = "角色名称")
private String roleName; private String roleName;


/** 角色权限 */
/**
* 角色权限
*/
@Excel(name = "角色权限") @Excel(name = "角色权限")
private String roleKey; private String roleKey;


/** 角色排序 */
/**
* 角色排序
*/
@Excel(name = "角色排序") @Excel(name = "角色排序")
private Integer roleSort; private Integer roleSort;


/** 数据范围(1:所有数据权限;2:自定义数据权限;3:本部门数据权限;4:本部门及以下数据权限;5:仅本人数据权限) */
/**
* 数据范围(1:所有数据权限;2:自定义数据权限;3:本部门数据权限;4:本部门及以下数据权限;5:仅本人数据权限)
*/
@Excel(name = "数据范围", readConverterExp = "1=所有数据权限,2=自定义数据权限,3=本部门数据权限,4=本部门及以下数据权限,5=仅本人数据权限") @Excel(name = "数据范围", readConverterExp = "1=所有数据权限,2=自定义数据权限,3=本部门数据权限,4=本部门及以下数据权限,5=仅本人数据权限")
private String dataScope; private String dataScope;


/** 菜单树选择项是否关联显示( 0:父子不互相关联显示 1:父子互相关联显示) */
/**
* 菜单树选择项是否关联显示( 0:父子不互相关联显示 1:父子互相关联显示)
*/
private boolean menuCheckStrictly; private boolean menuCheckStrictly;


/** 部门树选择项是否关联显示(0:父子不互相关联显示 1:父子互相关联显示 ) */
/**
* 部门树选择项是否关联显示(0:父子不互相关联显示 1:父子互相关联显示 )
*/
private boolean deptCheckStrictly; private boolean deptCheckStrictly;


/** 角色状态(0正常 1停用) */
/**
* 角色状态(0正常 1停用)
*/
@Excel(name = "角色状态", readConverterExp = "0=正常,1=停用") @Excel(name = "角色状态", readConverterExp = "0=正常,1=停用")
private String status; private String status;


/** 删除标志(0代表存在 2代表删除) */
/**
* 删除标志(0代表存在 2代表删除)
*/
private String delFlag; private String delFlag;


/** 用户是否存在此角色标识 默认不存在 */
/**
* 用户是否存在此角色标识 默认不存在
*/
private boolean flag = false; private boolean flag = false;


/** 菜单组 */
/**
* 菜单组
*/
private Long[] menuIds; private Long[] menuIds;


/** 部门组(数据权限) */
/**
* 部门组(数据权限)
*/
private Long[] deptIds; private Long[] deptIds;


/** 角色菜单权限 */
/**
* 角色菜单权限
*/
private Set<String> permissions; private Set<String> permissions;


public SysRole()
{
public SysRole() {


} }


public SysRole(Long roleId)
{
public SysRole(Long roleId) {
this.roleId = roleId; this.roleId = roleId;
} }


public Long getRoleId()
{
public Long getRoleId() {
return roleId; return roleId;
} }


public void setRoleId(Long roleId)
{
public void setRoleId(Long roleId) {
this.roleId = roleId; this.roleId = roleId;
} }


public boolean isAdmin()
{
return isAdmin(this.roleId);
public boolean isAdmin() {
return isAdmin(this.roleKey);
} }


public static boolean isAdmin(Long roleId)
{
return roleId != null && 1L == roleId;
public static boolean isAdmin(String roleKey) {
return Constant.Admin.equals(roleKey);
} }


@NotBlank(message = "角色名称不能为空") @NotBlank(message = "角色名称不能为空")
@Size(min = 0, max = 30, message = "角色名称长度不能超过30个字符") @Size(min = 0, max = 30, message = "角色名称长度不能超过30个字符")
public String getRoleName()
{
public String getRoleName() {
return roleName; return roleName;
} }


public void setRoleName(String roleName)
{
public void setRoleName(String roleName) {
this.roleName = roleName; this.roleName = roleName;
} }


@NotBlank(message = "权限字符不能为空") @NotBlank(message = "权限字符不能为空")
@Size(min = 0, max = 100, message = "权限字符长度不能超过100个字符") @Size(min = 0, max = 100, message = "权限字符长度不能超过100个字符")
public String getRoleKey()
{
public String getRoleKey() {
return roleKey; return roleKey;
} }


public void setRoleKey(String roleKey)
{
public void setRoleKey(String roleKey) {
this.roleKey = roleKey; this.roleKey = roleKey;
} }


@NotNull(message = "显示顺序不能为空") @NotNull(message = "显示顺序不能为空")
public Integer getRoleSort()
{
public Integer getRoleSort() {
return roleSort; return roleSort;
} }


public void setRoleSort(Integer roleSort)
{
public void setRoleSort(Integer roleSort) {
this.roleSort = roleSort; this.roleSort = roleSort;
} }


public String getDataScope()
{
public String getDataScope() {
return dataScope; return dataScope;
} }


public void setDataScope(String dataScope)
{
public void setDataScope(String dataScope) {
this.dataScope = dataScope; this.dataScope = dataScope;
} }


public boolean isMenuCheckStrictly()
{
public boolean isMenuCheckStrictly() {
return menuCheckStrictly; return menuCheckStrictly;
} }


public void setMenuCheckStrictly(boolean menuCheckStrictly)
{
public void setMenuCheckStrictly(boolean menuCheckStrictly) {
this.menuCheckStrictly = menuCheckStrictly; this.menuCheckStrictly = menuCheckStrictly;
} }


public boolean isDeptCheckStrictly()
{
public boolean isDeptCheckStrictly() {
return deptCheckStrictly; return deptCheckStrictly;
} }


public void setDeptCheckStrictly(boolean deptCheckStrictly)
{
public void setDeptCheckStrictly(boolean deptCheckStrictly) {
this.deptCheckStrictly = deptCheckStrictly; this.deptCheckStrictly = deptCheckStrictly;
} }


public String getStatus()
{
public String getStatus() {
return status; return status;
} }


public void setStatus(String status)
{
public void setStatus(String status) {
this.status = status; this.status = status;
} }


public String getDelFlag()
{
public String getDelFlag() {
return delFlag; return delFlag;
} }


public void setDelFlag(String delFlag)
{
public void setDelFlag(String delFlag) {
this.delFlag = delFlag; this.delFlag = delFlag;
} }


public boolean isFlag()
{
public boolean isFlag() {
return flag; return flag;
} }


public void setFlag(boolean flag)
{
public void setFlag(boolean flag) {
this.flag = flag; this.flag = flag;
} }


public Long[] getMenuIds()
{
public Long[] getMenuIds() {
return menuIds; return menuIds;
} }


public void setMenuIds(Long[] menuIds)
{
public void setMenuIds(Long[] menuIds) {
this.menuIds = menuIds; this.menuIds = menuIds;
} }


public Long[] getDeptIds()
{
public Long[] getDeptIds() {
return deptIds; return deptIds;
} }


public void setDeptIds(Long[] deptIds)
{
public void setDeptIds(Long[] deptIds) {
this.deptIds = deptIds; this.deptIds = deptIds;
} }


public Set<String> getPermissions()
{
public Set<String> getPermissions() {
return permissions; return permissions;
} }


public void setPermissions(Set<String> permissions)
{
public void setPermissions(Set<String> permissions) {
this.permissions = permissions; this.permissions = permissions;
} }


@Override @Override
public String toString() { public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
.append("roleId", getRoleId())
.append("roleName", getRoleName())
.append("roleKey", getRoleKey())
.append("roleSort", getRoleSort())
.append("dataScope", getDataScope())
.append("menuCheckStrictly", isMenuCheckStrictly())
.append("deptCheckStrictly", isDeptCheckStrictly())
.append("status", getStatus())
.append("delFlag", getDelFlag())
.append("createBy", getCreateBy())
.append("createTime", getCreateTime())
.append("updateBy", getUpdateBy())
.append("updateTime", getUpdateTime())
.append("remark", getRemark())
.toString();
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
.append("roleId", getRoleId())
.append("roleName", getRoleName())
.append("roleKey", getRoleKey())
.append("roleSort", getRoleSort())
.append("dataScope", getDataScope())
.append("menuCheckStrictly", isMenuCheckStrictly())
.append("deptCheckStrictly", isDeptCheckStrictly())
.append("status", getStatus())
.append("delFlag", getDelFlag())
.append("createBy", getCreateBy())
.append("createTime", getCreateTime())
.append("updateBy", getUpdateBy())
.append("updateTime", getUpdateTime())
.append("remark", getRemark())
.toString();
} }
} }

+ 7
- 7
ruoyi-api/ruoyi-api-system/src/main/java/com/ruoyi/system/api/domain/SysUser.java View File

@@ -155,13 +155,13 @@ public class SysUser extends BaseEntity {
this.userId = userId; this.userId = userId;
} }


public boolean isAdmin() {
return isAdmin(this.userId);
}
public static boolean isAdmin(Long userId) {
return userId != null && 1L == userId;
}
// public boolean isAdmin() {
// return isAdmin(this.userId);
// }
//
// public static boolean isAdmin(Long userId) {
// return userId != null && 1L == userId;
// }


public Long getDeptId() { public Long getDeptId() {
return deptId; return deptId;


+ 3
- 3
ruoyi-common/ruoyi-common-datascope/src/main/java/com/ruoyi/common/datascope/aspect/DataScopeAspect.java View File

@@ -70,12 +70,12 @@ public class DataScopeAspect
{ {
SysUser currentUser = loginUser.getSysUser(); SysUser currentUser = loginUser.getSysUser();
// 如果是超级管理员,则不过滤数据 // 如果是超级管理员,则不过滤数据
if (StringUtils.isNotNull(currentUser) && !currentUser.isAdmin())
{
// if (StringUtils.isNotNull(currentUser) && !currentUser.isAdmin())
// {
String permission = StringUtils.defaultIfEmpty(controllerDataScope.permission(), SecurityContextHolder.getPermission()); String permission = StringUtils.defaultIfEmpty(controllerDataScope.permission(), SecurityContextHolder.getPermission());
dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(), dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(),
controllerDataScope.userAlias(), permission); controllerDataScope.userAlias(), permission);
}
// }
} }
} }




+ 110
- 0
ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/config/MultiDSConfig.java View File

@@ -0,0 +1,110 @@
package com.ruoyi.platform.config;

import java.util.HashMap;
import java.util.Map;

import javax.sql.DataSource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import com.alibaba.druid.pool.DruidDataSource;

@Configuration
public class MultiDSConfig {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Value("${mysql.datasource.master.url}")
private String masterUrl;
@Value("${mysql.datasource.master.username}")
private String username;
@Value("${mysql.datasource.master.password}")
private String password;
@Value("${mysql.datasource.master.driverClassName}")
private String driverClassName;
@Value("${mysql.datasource.master.initialSize}")
private int initialSize;
@Value("${mysql.datasource.master.minIdle}")
private int minIdle;
@Value("${mysql.datasource.master.maxActive}")
private int maxActive;
@Value("${mysql.datasource.master.maxWait}")
private int maxWait;
// 故意注释
@Value("${mysql.datasource.master.timeBetweenEvictionRunsMillis}")
private int timeBetweenEvictionRunsMillis;
@Value("${mysql.datasource.master.validationQuery}")
private String validationQuery;
@Value("${mysql.datasource.master.testWhileIdle}")
private boolean testWhileIdle;
@Value("${mysql.datasource.master.testOnBorrow}")
private boolean testOnBorrow;
@Value("${mysql.datasource.master.testOnReturn}")
private boolean testOnReturn;
@Value("${mysql.datasource.master.ConnectionErrorRetryAttempts}")
private int connectionErrorRetryAttempts;
@Value("${mysql.datasource.master.NotFullTimeoutRetryCount}")
private int notFullTimeoutRetryCount;
// @Value("${mysql.datasource.master.removeAbandonedTimeoutMillis}")
// private long removeAbandonedTimeoutMillis;
// @Value("${mysql.datasource.master.removeAbandoned}")
// private boolean removeAbandoned;
@Value("${mysql.datasource.master.keepalive}")
private boolean keepAlive = true;
@Value("${mysql.datasource.master.minEvictableIdleTimeMillis}")
private int minEvictableIdleTimeMillis;
@Value("${mysql.datasource.master.maxEvictableIdleTimeMillis}")
private int maxEvictableIdleTimeMillis;
@Value("${mysql.datasource.master.keepAliveBetweenTimeMillis}")
private int keepAliveBetweenTimeMillis;

@Bean
@ConfigurationProperties(prefix = "mysql.datasource.master")
public DataSource masterDataSource() {
DruidDataSource datasource = new DruidDataSource();
datasource.setUrl(masterUrl);
datasource.setUsername(username);
datasource.setPassword(password);
datasource.setDriverClassName(driverClassName);
// configuration
datasource.setInitialSize(initialSize);
datasource.setMinIdle(minIdle);
datasource.setMaxActive(maxActive);
datasource.setMaxWait(maxWait);
// 故意注释下面这一句
datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
logger.info(">>>>>>timeBetweenEvictionRunsMillis->" + timeBetweenEvictionRunsMillis);
// datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
datasource.setValidationQuery(validationQuery);
datasource.setTestWhileIdle(testWhileIdle);
datasource.setTestOnBorrow(testOnBorrow);
datasource.setTestOnReturn(testOnReturn);
// datasource.setPoolPreparedStatements(poolPreparedStatements);
// datasource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolPreparedStatementPerConnectionSize);
datasource.setConnectionErrorRetryAttempts(connectionErrorRetryAttempts);
datasource.setNotFullTimeoutRetryCount(notFullTimeoutRetryCount);
//datasource.setRemoveAbandonedTimeoutMillis(removeAbandonedTimeoutMillis);
//datasource.setRemoveAbandoned(removeAbandoned);
datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
datasource.setMaxEvictableIdleTimeMillis(maxEvictableIdleTimeMillis);
datasource.setKeepAliveBetweenTimeMillis(keepAliveBetweenTimeMillis);
datasource.setKeepAlive(keepAlive);
return datasource;
}

@Bean
public JdbcTemplate dataSource(DataSource myRoutingDataSource) {
return new JdbcTemplate(myRoutingDataSource);
}

@Bean
public DataSourceTransactionManager txManager(DataSource myRoutingDataSource) {
return new DataSourceTransactionManager(myRoutingDataSource);
}
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save