Compare commits
173 Commits
Author | SHA1 | Date |
---|---|---|
somunslotus | 1d117b0ca5 | |
somunslotus | 911dd2bb72 | |
somunslotus | 53e725e821 | |
somunslotus | 64f949ec5e | |
somunslotus | aa37cfaede | |
somunslotus | e4858cc976 | |
chenzhihang | 994696da9a | |
chenzhihang | 432d7dd9ef | |
chenzhihang | 1091dfde55 | |
chenzhihang | 9e2d288fb8 | |
chenzhihang | 28da9b11b8 | |
chenzhihang | a08754f863 | |
chenzhihang | e374d39af8 | |
chenzhihang | 5311545d1b | |
chenzhihang | 50e66571a3 | |
chenzhihang | d5ad62af17 | |
fanshuai | b0ead1807b | |
chenzhihang | f9cf9ad312 | |
chenzhihang | b3d274f79c | |
chenzhihang | 7f112c3078 | |
chenzhihang | 50cfefac46 | |
fanshuai | c62d20f63d | |
fanshuai | 80d802391a | |
chenzhihang | 1ed2d061b3 | |
fanshuai | 73fdb64061 | |
fanshuai | db3ce4581d | |
chenzhihang | 469c575250 | |
chenzhihang | 658ad688d3 | |
chenzhihang | 39f7b05869 | |
chenzhihang | 0e8fb0ed0f | |
chenzhihang | 83c317b854 | |
chenzhihang | 332959cd21 | |
chenzhihang | 93d9ef8c93 | |
chenzhihang | 68d09fa06d | |
chenzhihang | 24a51f4ead | |
chenzhihang | 522ed32ff5 | |
fanshuai | 12942ddba2 | |
fanshuai | d0d25f9cc4 | |
fanshuai | 6c388f7384 | |
chenzhihang | 01299087d0 | |
chenzhihang | d74aa4efa2 | |
fanshuai | 07dae0f917 | |
fanshuai | d9d9406695 | |
somunslotus | 1b957fd33d | |
somunslotus | 2c3ed72301 | |
chenzhihang | 4519b6e346 | |
chenzhihang | 296c791671 | |
fanshuai | c37da222f7 | |
somunslotus | e81194e291 | |
fanshuai | 375f9bb4a2 | |
somunslotus | e3cc9a998c | |
chenzhihang | e3dee373d6 | |
fanshuai | f270ba7c0a | |
somunslotus | 7f32d7ce63 | |
somunslotus | 25a5c457eb | |
cp3hnu | 2cd7782bea | |
cp3hnu | 39e93b563f | |
chenzhihang | c1fc913ac1 | |
chenzhihang | 1673d9480f | |
somunslotus | b4de8aa7d8 | |
chenzhihang | 223f6ba92b | |
chenzhihang | a5ce1a56fe | |
somunslotus | d429a915eb | |
somunslotus | 752e2c851c | |
cp3hnu | eeb0d0c149 | |
cp3hnu | fa18e6a5fe | |
fanshuai | d38703abd7 | |
chenzhihang | dac7b2138a | |
chenzhihang | 8ed145326d | |
fanshuai | 2791f9c8db | |
fanshuai | d5a02212ff | |
fanshuai | d87b013ed6 | |
chenzhihang | 67d62678de | |
chenzhihang | a6e27df2f9 | |
chenzhihang | b610ae73cd | |
cp3hnu | f890b75f80 | |
chenzhihang | 696bcd0a20 | |
chenzhihang | d60b57af02 | |
chenzhihang | 7e90541d4c | |
chenzhihang | 1155def018 | |
chenzhihang | f6e7ddd1bf | |
chenzhihang | 07cf1250ac | |
chenzhihang | 6a737ce855 | |
chenzhihang | 6bbaa5a4b8 | |
cp3hnu | d9b70dabee | |
chenzhihang | 2a200fcabf | |
somunslotus | d5f5bae4ce | |
chenzhihang | f79c193393 | |
fanshuai | aba0396e06 | |
fanshuai | b8f4da80a3 | |
fanshuai | 38ced879f4 | |
chenzhihang | f29bb734a2 | |
chenzhihang | c7967bc8e9 | |
chenzhihang | 81908b4b86 | |
chenzhihang | 31cf861e87 | |
cp3hnu | 03c6415376 | |
cp3hnu | ccfe0d981a | |
chenzhihang | f7d71e3303 | |
chenzhihang | 1dadf81f94 | |
chenzhihang | c87603f993 | |
chenzhihang | 3955893da0 | |
cp3hnu | b7d81b000b | |
chenzhihang | 3d36c57fa6 | |
fanshuai | 7f21bbeef1 | |
fanshuai | f4581d5080 | |
chenzhihang | 1e5c82566c | |
chenzhihang | 78d3a33256 | |
chenzhihang | 64b59e8abc | |
fanshuai | 5752c50bf2 | |
chenzhihang | f8e2c293c7 | |
chenzhihang | 0d32f9828b | |
fanshuai | 2dd0102f34 | |
fanshuai | 2386d0d9d6 | |
fanshuai | 1e1da4cc28 | |
cp3hnu | da3f801fd0 | |
chenzhihang | 35aee7824e | |
chenzhihang | ef6bfa2d34 | |
chenzhihang | ad0d0c9a1b | |
fanshuai | 365ebef461 | |
chenzhihang | 173dcb1d66 | |
chenzhihang | 76a85ff996 | |
chenzhihang | 6074c20f12 | |
chenzhihang | 46f49582ee | |
chenzhihang | e58d73ae12 | |
chenzhihang | 006b625571 | |
fanshuai | de623ec3c5 | |
fanshuai | 9637209e72 | |
chenzhihang | 8f9c9d75d7 | |
chenzhihang | a888b88074 | |
fanshuai | 9b64f38835 | |
fanshuai | efd815edd7 | |
chenzhihang | 92e77bcfe9 | |
chenzhihang | 9fcb1a2db0 | |
fanshuai | c158ff2e25 | |
fanshuai | 2a54f602cf | |
chenzhihang | 31455d0420 | |
chenzhihang | e215da82c3 | |
chenzhihang | 7fa0d7cb64 | |
chenzhihang | 5f2ed6914c | |
chenzhihang | 38cd15b47a | |
chenzhihang | ed68d8f504 | |
chenzhihang | c4b569bb85 | |
chenzhihang | db7c0c77d9 | |
chenzhihang | 47c4bc86ac | |
chenzhihang | 6296d2781b | |
chenzhihang | a1c9ef7c3c | |
chenzhihang | 4e7b18bd2c | |
chenzhihang | 3c0e16ea65 | |
chenzhihang | 5661fa0bac | |
chenzhihang | 1eb68f16a4 | |
chenzhihang | 6c81e5611c | |
chenzhihang | 083342cc8a | |
fanshuai | dd1d1aae26 | |
chenzhihang | ecc911a9b0 | |
chenzhihang | 05f1a9880d | |
fanshuai | 23c2bb1770 | |
fanshuai | 81366d0edd | |
fanshuai | 53046a939a | |
fanshuai | 156888782e | |
chenzhihang | 77fadb29d9 | |
chenzhihang | ee25849a36 | |
chenzhihang | a5d4469ad7 | |
fanshuai | 6fdb5bcf78 | |
chenzhihang | e8cea9df83 | |
fanshuai | 7cbd258afe | |
chenzhihang | 4ec2ee52b3 | |
chenzhihang | 666a185a0f | |
chenzhihang | 16332d4d0b | |
fanshuai | 230029734c | |
fanshuai | c3723ee716 | |
cp3hnu | 877816cb14 | |
chenzhihang | a912b24ad2 | |
chenzhihang | 3d67f58e5b |
|
@ -1,6 +1,8 @@
|
|||
#!/bin/bash
|
||||
|
||||
baseDir="/home/somuns/ci4s"
|
||||
|
||||
|
||||
#判断$1是否为all,如果是,则编译所有模块,否则只编译management-platform模块
|
||||
if [ "$1" == "all" ]; then
|
||||
buildDir=$baseDir
|
||||
|
|
|
@ -3,12 +3,12 @@
|
|||
baseDir="/home/somuns/ci4s"
|
||||
cd ${baseDir}/react-ui
|
||||
|
||||
npm install
|
||||
# npm install
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Failed to install npm depend package"
|
||||
exit 1
|
||||
fi
|
||||
#if [ $? -ne 0 ]; then
|
||||
# echo "Failed to install npm depend package"
|
||||
# exit 1
|
||||
#fi
|
||||
|
||||
|
||||
npm run build
|
||||
|
|
|
@ -43,18 +43,23 @@ cd ${baseDir}
|
|||
|
||||
# 拉取指定分支的最新代码
|
||||
echo "Checking out and pulling branch $branch..."
|
||||
|
||||
git stash
|
||||
git checkout $branch
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "切换到分支 $branch 失败,请检查分支名称是否正确!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
git stash
|
||||
git pull origin $branch
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "拉取代码失败,请检查网络或联系管理员!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
chmod +777 ${baseDir}/k8s/*.sh
|
||||
|
||||
# 创建目录
|
||||
mkdir -p ${baseDir}/k8s/dockerfiles/jar
|
||||
mkdir -p ${baseDir}/k8s/dockerfiles/html
|
||||
|
|
|
@ -143,7 +143,7 @@ fi
|
|||
|
||||
if [ "$service" == "all" ]; then
|
||||
#部署前端
|
||||
build_and_deploy "nginx-dockerfile" "172.20.32.187/ci4s/ci4s-front:${tag}" "k8s-12front.yaml"
|
||||
# build_and_deploy "nginx-dockerfile" "172.20.32.187/ci4s/ci4s-front:${tag}" "k8s-12front.yaml"
|
||||
#部署管理平台
|
||||
build_and_deploy "managent-dockerfile" "172.20.32.187/ci4s/ci4s-managent:${tag}" "k8s-7management.yaml"
|
||||
#部署认证中心
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# 基础镜像
|
||||
#FROM openjdk:8-jre
|
||||
FROM 172.20.32.187/ci4s/openjdk:8-jre
|
||||
FROM 172.20.32.187/ci4s/openjdk-dvc:2024829
|
||||
# author
|
||||
MAINTAINER ruoyi
|
||||
|
||||
|
|
|
@ -1,60 +1,131 @@
|
|||
worker_processes 1;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
include mime.types;
|
||||
default_type application/octet-stream;
|
||||
sendfile on;
|
||||
keepalive_timeout 65;
|
||||
|
||||
server {
|
||||
listen 8000;
|
||||
server_name localhost;
|
||||
|
||||
location /api/{
|
||||
rewrite ^/prod-api/(.*)$ /$1 break;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header REMOTE-HOST $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_pass http://ci4s-gateway-service.argo.svc:8082/;
|
||||
}
|
||||
|
||||
location /label-studio {
|
||||
rewrite ^/prod-api/(.*)$ /$1 break;
|
||||
proxy_pass http://label-studio-ls-app.label-data.svc:80/;
|
||||
proxy_hide_header X-Frame-Options;
|
||||
add_header X-Frame-Options "ALLOW-FROM http://label-studio-ls-app.label-data.svc:80/";
|
||||
}
|
||||
|
||||
location /api/v1/model/ {
|
||||
proxy_pass http://pipeline-convert-service.argo.svc:80;
|
||||
proxy_set_header REMOTE-HOST $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
}
|
||||
|
||||
location / {
|
||||
rewrite ^/prod-api/(.*)$ /$1 break;
|
||||
root /home/ruoyi/projects/ruoyi-ui;
|
||||
try_files $uri $uri/ /index.html;
|
||||
index index.html index.htm;
|
||||
}
|
||||
|
||||
location @router {
|
||||
rewrite ^.*$ /index.html last;
|
||||
}
|
||||
|
||||
# 避免actuator暴露
|
||||
if ($request_uri ~ "/actuator") {
|
||||
return 403;
|
||||
}
|
||||
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root html;
|
||||
}
|
||||
}
|
||||
}
|
||||
worker_processes 1;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
include mime.types;
|
||||
default_type application/octet-stream;
|
||||
sendfile on;
|
||||
keepalive_timeout 65;
|
||||
client_max_body_size 20480m;
|
||||
error_log /var/log/nginx/error.log debug;
|
||||
server {
|
||||
listen 8000;
|
||||
server_name localhost;
|
||||
|
||||
location /api/{
|
||||
# rewrite ^/prod-api/(.*)$ /$1 break;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header REMOTE-HOST $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_pass http://ci4s-gateway-service.argo.svc:8082/;
|
||||
proxy_connect_timeout 500s; # 设置连接超时时间为 120 秒
|
||||
proxy_read_timeout 500s; # 设置读取超时时间为 120 秒
|
||||
proxy_send_timeout 500s; # 设置发送超时时间为 120 秒
|
||||
}
|
||||
|
||||
location /label-studio/ {
|
||||
# rewrite ^/label-studio/(.*)$ /$1 break;
|
||||
proxy_pass http://label-studio-service.argo.svc:8080/projects/;
|
||||
proxy_hide_header X-Frame-Options;
|
||||
add_header X-Frame-Options ALLOWALL;
|
||||
}
|
||||
|
||||
location / {
|
||||
rewrite ^/prod-api/(.*)$ /$1 break;
|
||||
root /home/ruoyi/projects/ruoyi-ui;
|
||||
try_files $uri $uri/ /index.html;
|
||||
index index.html index.htm;
|
||||
}
|
||||
|
||||
location /api/v1/model/ {
|
||||
proxy_pass http://pipeline-convert-service.argo.svc:80;
|
||||
proxy_set_header REMOTE-HOST $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
}
|
||||
# location /api/v1/realtimeStatus {
|
||||
# proxy_pass http://argo-server.argo.svc:2746/api/v1/workflow-events/argo;
|
||||
# proxy_set_header REMOTE-HOST $remote_addr;
|
||||
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
# }
|
||||
|
||||
|
||||
location /api/v1/tensorboard/show {
|
||||
# 提取查询参数中的 `svc` 值
|
||||
set $svc "";
|
||||
if ($arg_svc) {
|
||||
set $svc $arg_svc;
|
||||
}
|
||||
|
||||
# 将请求转发到动态生成的内部服务地址
|
||||
proxy_pass http://$svc.argo.svc:6006;
|
||||
|
||||
# 传递必要的头信息
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
# 对于 WebSocket 应用很重要
|
||||
proxy_buffering off;
|
||||
}
|
||||
|
||||
location /api/v1/realtimeStatus {
|
||||
rewrite ^/api/v1/realtimeStatus(.*)$ /api/v1/workflow-events/argo$1 break;
|
||||
|
||||
proxy_pass https://argo-server.argo.svc:2746;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header REMOTE-HOST $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
# 保留查询参数
|
||||
proxy_set_header X-Original-URI $request_uri;
|
||||
|
||||
# 禁用缓冲
|
||||
proxy_buffering off;
|
||||
|
||||
# 增加超时时间
|
||||
proxy_read_timeout 3600s;
|
||||
proxy_send_timeout 3600s;
|
||||
proxy_connect_timeout 60s;
|
||||
|
||||
# 设置传递的请求头
|
||||
# proxy_set_header Connection '';
|
||||
# chunked_transfer_encoding off;
|
||||
|
||||
# 如果需要保留自定义头部
|
||||
proxy_set_header Accept 'text/event-stream';
|
||||
}
|
||||
|
||||
|
||||
location /newlog/realtimeLog {
|
||||
proxy_pass http://loki.loki-log.svc:3100/loki/api/v1/tail;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "Upgrade";
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
location @router {
|
||||
rewrite ^.*$ /index.html last;
|
||||
}
|
||||
|
||||
# 避免actuator暴露
|
||||
if ($request_uri ~ "/actuator") {
|
||||
return 403;
|
||||
}
|
||||
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root html;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
worker_processes 1;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
include mime.types;
|
||||
default_type application/octet-stream;
|
||||
sendfile on;
|
||||
keepalive_timeout 65;
|
||||
|
||||
server {
|
||||
listen 8000;
|
||||
server_name localhost;
|
||||
|
||||
location /api/{
|
||||
rewrite ^/prod-api/(.*)$ /$1 break;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header REMOTE-HOST $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_pass http://ci4s-gateway-service.argo.svc:8082/;
|
||||
}
|
||||
|
||||
location /label-studio {
|
||||
rewrite ^/prod-api/(.*)$ /$1 break;
|
||||
proxy_pass http://label-studio-ls-app.label-data.svc:80/;
|
||||
proxy_hide_header X-Frame-Options;
|
||||
add_header X-Frame-Options "ALLOW-FROM http://label-studio-ls-app.label-data.svc:80/";
|
||||
}
|
||||
|
||||
location /api/v1/model/ {
|
||||
proxy_pass http://pipeline-convert-service.argo.svc:80;
|
||||
proxy_set_header REMOTE-HOST $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
}
|
||||
|
||||
location / {
|
||||
rewrite ^/prod-api/(.*)$ /$1 break;
|
||||
root /home/ruoyi/projects/ruoyi-ui;
|
||||
try_files $uri $uri/ /index.html;
|
||||
index index.html index.htm;
|
||||
}
|
||||
|
||||
location @router {
|
||||
rewrite ^.*$ /index.html last;
|
||||
}
|
||||
|
||||
# 避免actuator暴露
|
||||
if ($request_uri ~ "/actuator") {
|
||||
return 403;
|
||||
}
|
||||
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root html;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
# 基础镜像
|
||||
FROM 172.20.32.187/ci4s/openjdk:8-jre
|
||||
FROM 172.20.32.187/ci4s/openjdk-dvc:2024829
|
||||
#FROM openjdk:8-jre
|
||||
# author
|
||||
MAINTAINER ruoyi
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# 基础镜像
|
||||
#FROM openjdk:8-jre
|
||||
FROM 172.20.32.187/ci4s/openjdk:8-jre
|
||||
FROM 172.20.32.187/ci4s/openjdk-dvc:2024829
|
||||
# author
|
||||
MAINTAINER ruoyi
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# 基础镜像
|
||||
#FROM openjdk:8-jre
|
||||
FROM 172.20.32.187/ci4s/openjdk:8-jre
|
||||
FROM 172.20.32.187/ci4s/openjdk-dvc:2024829
|
||||
# author
|
||||
MAINTAINER ruoyi
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# 基础镜像
|
||||
#FROM openjdk:8-jre
|
||||
FROM 172.20.32.187/ci4s/openjdk:8-jre
|
||||
FROM 172.20.32.187/ci4s/openjdk-dvc:2024829
|
||||
# author
|
||||
MAINTAINER ruoyi
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# 基础镜像
|
||||
#FROM openjdk:8-jre
|
||||
FROM 172.20.32.187/ci4s/openjdk:8-jre
|
||||
#FROM 172.20.32.187/ci4s/openjdk:8u162
|
||||
FROM 172.20.32.187/ci4s/openjdk-dvc:2024829
|
||||
# author
|
||||
MAINTAINER ruoyi
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# 基础镜像
|
||||
#FROM openjdk:8-jre
|
||||
FROM 172.20.32.187/ci4s/openjdk:8-jre
|
||||
FROM 172.20.32.187/ci4s/openjdk-dvc:2024829
|
||||
# author
|
||||
MAINTAINER ruoyi
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# 基础镜像
|
||||
#FROM openjdk:8-jre
|
||||
FROM 172.20.32.187/ci4s/openjdk:8-jre
|
||||
FROM 172.20.32.187/ci4s/openjdk-dvc:2024829
|
||||
# author
|
||||
MAINTAINER ruoyi
|
||||
|
||||
|
|
|
@ -18,6 +18,14 @@ spec:
|
|||
image: 172.20.32.187/ci4s/managent:20240401
|
||||
ports:
|
||||
- containerPort: 9213
|
||||
volumeMounts:
|
||||
- name: resource
|
||||
mountPath: /home/resource/
|
||||
volumes:
|
||||
- name: resource
|
||||
hostPath:
|
||||
path: /home/resource/
|
||||
type: DirectoryOrCreate
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
|
|
|
@ -15,10 +15,21 @@ spec:
|
|||
spec:
|
||||
containers:
|
||||
- name: ci4s-management-platform
|
||||
image: 172.20.32.187/ci4s/managent:202406121003
|
||||
image: 172.20.32.187/ci4s/ci4s-managent:202409201355
|
||||
env:
|
||||
- name: TZ
|
||||
value: Asia/Shanghai
|
||||
- name: JAVA_TOOL_OPTIONS
|
||||
value: "-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=*:5005"
|
||||
ports:
|
||||
- containerPort: 9213
|
||||
|
||||
volumeMounts:
|
||||
- name: resource-volume
|
||||
mountPath: /home/resource/
|
||||
volumes:
|
||||
- name: resource-volume
|
||||
persistentVolumeClaim:
|
||||
claimName: platform-data-pvc-nfs
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
|
@ -28,9 +39,15 @@ metadata:
|
|||
spec:
|
||||
type: NodePort
|
||||
ports:
|
||||
- port: 9213
|
||||
- name: http
|
||||
port: 9213
|
||||
nodePort: 31208
|
||||
protocol: TCP
|
||||
- name: debug
|
||||
nodePort: 34567
|
||||
port: 5005
|
||||
protocol: TCP
|
||||
targetPort: 5005
|
||||
selector:
|
||||
app: ci4s-management-platform
|
||||
|
||||
|
|
|
@ -16,9 +16,20 @@ spec:
|
|||
containers:
|
||||
- name: ci4s-management-platform
|
||||
image: ${k8s-7management-image}
|
||||
env:
|
||||
- name: TZ
|
||||
value: Asia/Shanghai
|
||||
- name: JAVA_TOOL_OPTIONS
|
||||
value: "-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=*:5005"
|
||||
ports:
|
||||
- containerPort: 9213
|
||||
|
||||
volumeMounts:
|
||||
- name: resource-volume
|
||||
mountPath: /home/resource/
|
||||
volumes:
|
||||
- name: resource-volume
|
||||
persistentVolumeClaim:
|
||||
claimName: platform-data-pvc-nfs
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
|
@ -28,9 +39,15 @@ metadata:
|
|||
spec:
|
||||
type: NodePort
|
||||
ports:
|
||||
- port: 9213
|
||||
- name: http
|
||||
port: 9213
|
||||
nodePort: 31208
|
||||
protocol: TCP
|
||||
- name: debug
|
||||
nodePort: 31219
|
||||
port: 5005
|
||||
protocol: TCP
|
||||
targetPort: 5005
|
||||
selector:
|
||||
app: ci4s-management-platform
|
||||
|
||||
|
|
|
@ -40,4 +40,11 @@ screenshot
|
|||
|
||||
build
|
||||
|
||||
pnpm-lock.yaml
|
||||
pnpm-lock.yaml
|
||||
/src/services/codeConfig/index.js
|
||||
/src/pages/CodeConfig/components/AddCodeConfigModal/index.less
|
||||
/src/pages/CodeConfig/List/index.less
|
||||
/src/pages/Dataset/components/ResourceItem/index.less
|
||||
/src/pages/CodeConfig/components/AddCodeConfigModal/index.tsx
|
||||
/src/pages/CodeConfig/components/CodeConfigItem/index.tsx
|
||||
/src/pages/Dataset/components/ResourceItem/index.tsx
|
||||
|
|
|
@ -22,6 +22,7 @@ export { requestConfig as request } from './requestConfig';
|
|||
// const isDev = process.env.NODE_ENV === 'development';
|
||||
import { type GlobalInitialState } from '@/types';
|
||||
import { menuItemRender } from '@/utils/menuRender';
|
||||
import ErrorBoundary from './components/ErrorBoundary';
|
||||
import { gotoLoginPage } from './utils/ui';
|
||||
|
||||
/**
|
||||
|
@ -65,6 +66,7 @@ export async function getInitialState(): Promise<GlobalInitialState> {
|
|||
// ProLayout 支持的api https://procomponents.ant.design/components/layout
|
||||
export const layout: RuntimeConfig['layout'] = ({ initialState }) => {
|
||||
return {
|
||||
ErrorBoundary: ErrorBoundary,
|
||||
rightContentRender: false,
|
||||
waterMarkProps: {
|
||||
// content: initialState?.currentUser?.nickName,
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 286 KiB |
Binary file not shown.
Before Width: | Height: | Size: 151 KiB After Width: | Height: | Size: 111 KiB |
Binary file not shown.
After Width: | Height: | Size: 3.0 KiB |
Binary file not shown.
After Width: | Height: | Size: 335 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
|
@ -0,0 +1,44 @@
|
|||
.kf-basic-info {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px 40px;
|
||||
align-items: flex-start;
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.kf-basic-info-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
width: calc(50% - 20px);
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
|
||||
&__label {
|
||||
position: relative;
|
||||
color: @text-color-secondary;
|
||||
text-align: justify;
|
||||
text-align-last: justify;
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
content: ':';
|
||||
}
|
||||
}
|
||||
|
||||
&__value {
|
||||
flex: 1;
|
||||
margin-left: 16px;
|
||||
white-space: pre-line;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
&__text {
|
||||
color: @text-color;
|
||||
}
|
||||
|
||||
&__link:hover {
|
||||
text-decoration: underline @underline-color;
|
||||
text-underline-offset: 3px;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
import { isEmptyString } from '@/utils';
|
||||
import { Link } from '@umijs/max';
|
||||
import classNames from 'classnames';
|
||||
import './index.less';
|
||||
|
||||
export type BasicInfoData = {
|
||||
label: string;
|
||||
value?: any;
|
||||
link?: string;
|
||||
externalLink?: string;
|
||||
format?: (_value?: any) => string | undefined;
|
||||
};
|
||||
|
||||
type BasicInfoProps = {
|
||||
datas: BasicInfoData[];
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
labelWidth?: number;
|
||||
};
|
||||
|
||||
function BasicInfo({ datas, className, style, labelWidth = 100 }: BasicInfoProps) {
|
||||
return (
|
||||
<div className={classNames('kf-basic-info', className)} style={style}>
|
||||
{datas.map((item) => (
|
||||
<BasicInfoItem key={item.label} data={item} labelWidth={labelWidth} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
type BasicInfoItemProps = {
|
||||
data: BasicInfoData;
|
||||
labelWidth?: number;
|
||||
};
|
||||
function BasicInfoItem({ data, labelWidth = 100 }: BasicInfoItemProps) {
|
||||
const { label, value, externalLink, link, format } = data;
|
||||
const showValue = format ? format(value) : value;
|
||||
let valueComponent = undefined;
|
||||
if (externalLink && showValue) {
|
||||
valueComponent = (
|
||||
<a
|
||||
className="kf-basic-info-item__value kf-basic-info-item__link"
|
||||
href={externalLink}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{showValue}
|
||||
</a>
|
||||
);
|
||||
} else if (link && showValue) {
|
||||
valueComponent = (
|
||||
<Link to={link} className="kf-basic-info-item__value kf-basic-info-item__link">
|
||||
{showValue}
|
||||
</Link>
|
||||
);
|
||||
} else {
|
||||
valueComponent = (
|
||||
<div className="kf-basic-info-item__value kf-basic-info-item__text">
|
||||
{isEmptyString(showValue) ? '--' : showValue}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="kf-basic-info-item" key={label}>
|
||||
<div className="kf-basic-info-item__label" style={{ width: labelWidth }}>
|
||||
{label}
|
||||
</div>
|
||||
{valueComponent}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default BasicInfo;
|
|
@ -0,0 +1,78 @@
|
|||
import KFEmpty, { EmptyType } from '@/components/KFEmpty';
|
||||
import { Button } from 'antd';
|
||||
import { Component, ReactNode } from 'react';
|
||||
|
||||
interface ErrorBoundaryProps {
|
||||
children: ReactNode;
|
||||
fallback?: ReactNode; // Optional fallback UI to show in case of error
|
||||
}
|
||||
|
||||
interface ErrorBoundaryState {
|
||||
hasError: boolean;
|
||||
error: Error | null;
|
||||
}
|
||||
|
||||
class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
||||
constructor(props: ErrorBoundaryProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
hasError: false,
|
||||
error: null,
|
||||
};
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
|
||||
// Update state so the next render shows the fallback UI
|
||||
return { hasError: true, error };
|
||||
}
|
||||
|
||||
// componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
||||
// // You can log the error to an error reporting service here
|
||||
// console.error('Error caught by ErrorBoundary:', error.message, errorInfo.componentStack);
|
||||
// }
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
return this.props.fallback || <ErrorBoundaryFallback error={this.state.error} />;
|
||||
}
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
function ErrorBoundaryFallback({ error }: { error: Error | null }) {
|
||||
const message = error && error instanceof Error ? error.message : 'Unknown error';
|
||||
const errorMsg =
|
||||
process.env.NODE_ENV === 'development' ? message : '非常抱歉,程序运行错误,\n我们会尽快修复。';
|
||||
return (
|
||||
<KFEmpty
|
||||
style={{ height: '100vh' }}
|
||||
type={EmptyType.NotFound}
|
||||
title="出错了"
|
||||
content={errorMsg}
|
||||
footer={() => {
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
type="default"
|
||||
onClick={() => {
|
||||
window.history.pushState({}, '', '/');
|
||||
window.location.reload();
|
||||
}}
|
||||
>
|
||||
返回首页
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
style={{ marginLeft: 20 }}
|
||||
onClick={() => window.location.reload()}
|
||||
>
|
||||
刷新
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
></KFEmpty>
|
||||
);
|
||||
}
|
||||
|
||||
export default ErrorBoundary;
|
|
@ -9,6 +9,7 @@ import {
|
|||
} from '@/utils/sessionStorage';
|
||||
import classNames from 'classnames';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import './index.less';
|
||||
|
||||
export enum IframePageType {
|
||||
|
@ -61,7 +62,7 @@ function IframePage({ type, className, style }: IframePageProps) {
|
|||
|
||||
return (
|
||||
<div className={classNames('kf-iframe-page', className)} style={style}>
|
||||
{loading && <KFSpin />}
|
||||
{loading && createPortal(<KFSpin size="large" />, document.body)}
|
||||
<FullScreenFrame url={iframeUrl} onload={hideLoading} onerror={hideLoading} />
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
.kf-empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
|
||||
&__image {
|
||||
width: 475px;
|
||||
}
|
||||
|
||||
&__title {
|
||||
margin-top: 15px;
|
||||
color: @text-color;
|
||||
font-weight: 500;
|
||||
font-size: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&__content {
|
||||
max-width: 50%;
|
||||
margin-top: 15px;
|
||||
color: @text-color-secondary;
|
||||
font-size: 15px;
|
||||
white-space: pre-line;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&__footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 30px;
|
||||
|
||||
&__back-btn {
|
||||
height: 32px;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
import { Button } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import './index.less';
|
||||
|
||||
export enum EmptyType {
|
||||
NoData = 'NoData',
|
||||
NotFound = 'NotFound',
|
||||
Developing = 'Developing',
|
||||
}
|
||||
|
||||
type EmptyProps = {
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
type: EmptyType;
|
||||
title?: string;
|
||||
content?: string;
|
||||
hasFooter?: boolean;
|
||||
footer?: () => React.ReactNode;
|
||||
buttonTitle?: string;
|
||||
onRefresh?: () => void;
|
||||
};
|
||||
|
||||
function getEmptyImage(type: EmptyType) {
|
||||
switch (type) {
|
||||
case EmptyType.NoData:
|
||||
return require('@/assets/img/no-data.png');
|
||||
case EmptyType.NotFound:
|
||||
return require('@/assets/img/404.png');
|
||||
case EmptyType.Developing:
|
||||
return require('@/assets/img/missing-back.png');
|
||||
}
|
||||
}
|
||||
|
||||
function KFEmpty({
|
||||
className,
|
||||
style,
|
||||
type,
|
||||
title,
|
||||
content,
|
||||
hasFooter = true,
|
||||
footer,
|
||||
buttonTitle = '刷新',
|
||||
onRefresh,
|
||||
}: EmptyProps) {
|
||||
const image = getEmptyImage(type);
|
||||
|
||||
return (
|
||||
<div className={classNames('kf-empty', className)} style={style}>
|
||||
<img className="kf-empty__image" src={image} draggable={false} alt="" />
|
||||
<div className="kf-empty__title">{title}</div>
|
||||
<div className="kf-empty__content">{content}</div>
|
||||
{hasFooter && (
|
||||
<div className="kf-empty__footer">
|
||||
{footer ? (
|
||||
footer()
|
||||
) : (
|
||||
<Button className="kf-empty__footer__back-btn" type="primary" onClick={onRefresh}>
|
||||
{buttonTitle}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default KFEmpty;
|
|
@ -4,12 +4,12 @@
|
|||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 1000;
|
||||
z-index: 1001;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
background-color: rgba(255, 255, 255, 0.3);
|
||||
|
||||
&__label {
|
||||
margin-top: 20px;
|
||||
|
|
|
@ -18,7 +18,7 @@ type ModalTitleProps = {
|
|||
function ModalTitle({ title, image, style, className }: ModalTitleProps) {
|
||||
return (
|
||||
<div className={classNames('kf-modal-title', className)} style={style}>
|
||||
{image && <img className={'kf-modal-title__image'} src={image} alt="" />}
|
||||
{image && <img className={'kf-modal-title__image'} src={image} draggable={false} alt="" />}
|
||||
{title}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -14,7 +14,15 @@ const filterResourceStandard: SelectProps<string, ComputingResource>['filterOpti
|
|||
};
|
||||
|
||||
// id 从 number 转换为 string
|
||||
const convertId = (item: any) => ({ ...item, id: String(item.id) });
|
||||
const convertId = (item: any) => ({
|
||||
...item,
|
||||
id: JSON.stringify({
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
identifier: item.identifier,
|
||||
owner: item.owner,
|
||||
}),
|
||||
});
|
||||
|
||||
export type SelectPropsConfig = {
|
||||
getOptions: () => Promise<any>; // 获取下拉数据
|
||||
|
|
|
@ -11,6 +11,7 @@ import ParameterInput, { type ParameterInputProps } from '../ParameterInput';
|
|||
import './index.less';
|
||||
|
||||
export { requiredValidator, type ParameterInputObject } from '../ParameterInput';
|
||||
export { ResourceSelectorType, selectorTypeConfig, type ResourceSelectorResponse };
|
||||
|
||||
type ResourceSelectProps = {
|
||||
type: ResourceSelectorType;
|
||||
|
@ -36,7 +37,7 @@ function ResourceSelect({ type, value, onChange, ...rest }: ResourceSelectProps)
|
|||
onOk: (res) => {
|
||||
setSelectedResource(res);
|
||||
if (res) {
|
||||
const { activeTab, id, name, version, path } = res;
|
||||
const { activeTab, id, name, version, path, identifier, owner } = res;
|
||||
if (type === ResourceSelectorType.Mirror) {
|
||||
onChange?.({
|
||||
value: path,
|
||||
|
@ -49,8 +50,11 @@ function ResourceSelect({ type, value, onChange, ...rest }: ResourceSelectProps)
|
|||
} else {
|
||||
const jsonObj = {
|
||||
id,
|
||||
name,
|
||||
version,
|
||||
path,
|
||||
identifier,
|
||||
owner,
|
||||
};
|
||||
const jsonObjStr = JSON.stringify(jsonObj);
|
||||
const showValue = `${name}:${version}`;
|
||||
|
|
|
@ -17,7 +17,7 @@ type SubAreaTitleProps = {
|
|||
function SubAreaTitle({ title, image, style, className }: SubAreaTitleProps) {
|
||||
return (
|
||||
<div className={classNames('kf-subarea-title', className)} style={style}>
|
||||
<img src={image} width={14} />
|
||||
<img src={image} width={14} draggable={false} alt="" />
|
||||
<span style={{ marginLeft: '8px' }}>{title}</span>
|
||||
</div>
|
||||
);
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -116,6 +116,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
.ant-input.ant-input-disabled {
|
||||
height: 46px;
|
||||
}
|
||||
|
||||
// 选择框高度为46px
|
||||
.ant-select-single {
|
||||
height: 46px;
|
||||
|
|
|
@ -1,18 +1,20 @@
|
|||
import { history } from '@umijs/max';
|
||||
import { Button, Result } from 'antd';
|
||||
import React from 'react';
|
||||
import KFEmpty, { EmptyType } from '@/components/KFEmpty';
|
||||
import { useNavigate } from '@umijs/max';
|
||||
|
||||
const NoFoundPage: React.FC = () => (
|
||||
<Result
|
||||
status="404"
|
||||
title="404"
|
||||
subTitle="Sorry, the page you visited does not exist."
|
||||
extra={
|
||||
<Button type="primary" onClick={() => history.push('/')}>
|
||||
Back Home
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
);
|
||||
const NoFoundPage = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<KFEmpty
|
||||
style={{ height: '100vh' }}
|
||||
type={EmptyType.NotFound}
|
||||
title="404"
|
||||
content={'很抱歉,您访问的页面地址有误,\n或者该页面不存在。'}
|
||||
hasFooter={true}
|
||||
buttonTitle="返回首页"
|
||||
onRefresh={() => navigate('/')}
|
||||
></KFEmpty>
|
||||
);
|
||||
};
|
||||
|
||||
export default NoFoundPage;
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import KFEmpty, { EmptyType } from '@/components/KFEmpty';
|
||||
import KFIcon from '@/components/KFIcon';
|
||||
import { deleteCodeConfigReq, getCodeConfigListReq } from '@/services/codeConfig';
|
||||
import { openAntdModal } from '@/utils/modal';
|
||||
import { to } from '@/utils/promise';
|
||||
import { modalConfirm } from '@/utils/ui';
|
||||
import { App, Button, Empty, Input, Pagination, PaginationProps } from 'antd';
|
||||
import { App, Button, Input, Pagination, PaginationProps } from 'antd';
|
||||
import { useEffect, useState } from 'react';
|
||||
import AddCodeConfigModal, { OperationType } from '../components/AddCodeConfigModal';
|
||||
import CodeConfigItem from '../components/CodeConfigItem';
|
||||
|
@ -31,7 +32,7 @@ export type ResourceListRef = {
|
|||
};
|
||||
|
||||
function CodeConfigList() {
|
||||
const [dataList, setDataList] = useState<CodeConfigData[]>([]);
|
||||
const [dataList, setDataList] = useState<CodeConfigData[] | undefined>(undefined);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [pagination, setPagination] = useState<PaginationProps>({
|
||||
current: 1,
|
||||
|
@ -56,6 +57,9 @@ function CodeConfigList() {
|
|||
if (res && res.data && res.data.content) {
|
||||
setDataList(res.data.content);
|
||||
setTotal(res.data.totalElements);
|
||||
} else {
|
||||
setDataList([]);
|
||||
setTotal(0);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -117,7 +121,7 @@ function CodeConfigList() {
|
|||
return (
|
||||
<div className={styles['code-config-list']}>
|
||||
<div className={styles['code-config-list__header']}>
|
||||
<span>数据总数:{total}个</span>
|
||||
<span>数据总数:{total} 个</span>
|
||||
<div>
|
||||
<Input.Search
|
||||
placeholder="按代码仓库名称筛选"
|
||||
|
@ -139,10 +143,10 @@ function CodeConfigList() {
|
|||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{dataList?.length !== 0 ? (
|
||||
{dataList && dataList.length !== 0 && (
|
||||
<>
|
||||
<div className={styles['code-config-list__content']}>
|
||||
{dataList?.map((item) => (
|
||||
{dataList.map((item) => (
|
||||
<CodeConfigItem
|
||||
item={item}
|
||||
key={item.id}
|
||||
|
@ -161,10 +165,16 @@ function CodeConfigList() {
|
|||
{...pagination}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<div className={styles['code-config-list__empty']}>
|
||||
<Empty></Empty>
|
||||
</div>
|
||||
)}
|
||||
{dataList && dataList.length === 0 && (
|
||||
<KFEmpty
|
||||
className={styles['code-config-list__empty']}
|
||||
type={EmptyType.NoData}
|
||||
title="暂无数据"
|
||||
content={'很抱歉,没有搜索到您想要的内容\n建议刷新试试'}
|
||||
hasFooter={true}
|
||||
onRefresh={getDataList}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -63,11 +63,16 @@ function CodeConfigItem({ item, onClick, onEdit, onRemove }: CodeConfigItemProps
|
|||
</div>
|
||||
<Flex justify="space-between">
|
||||
<div className={styles['code-config-item__time']}>
|
||||
<img style={{ width: '17px', marginRight: '6px' }} src={creatByImg} alt="" />
|
||||
<img
|
||||
style={{ width: '17px', marginRight: '6px' }}
|
||||
src={creatByImg}
|
||||
alt=""
|
||||
draggable={false}
|
||||
/>
|
||||
<span>{item.create_by}</span>
|
||||
</div>
|
||||
<div className={styles['code-config-item__time']}>
|
||||
<img style={{ width: '12px', marginRight: '5px' }} src={clock} alt="" />
|
||||
<img style={{ width: '12px', marginRight: '5px' }} src={clock} alt="" draggable={false} />
|
||||
<span>最近更新: {formatDate(item.update_time, 'YYYY-MM-DD')}</span>
|
||||
</div>
|
||||
</Flex>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
.upload-tip {
|
||||
margin-top: 5px;
|
||||
color: @error-color;
|
||||
color: @text-color-secondary;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.upload-button {
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import { getAccessToken } from '@/access';
|
||||
import { DictValueEnumObj } from '@/components/DictTag';
|
||||
import KFIcon from '@/components/KFIcon';
|
||||
import KFModal from '@/components/KFModal';
|
||||
import { addDatesetAndVesion } from '@/services/dataset/index.js';
|
||||
import { getDictSelectOption } from '@/services/system/dict';
|
||||
import { CategoryData, ResourceType, resourceConfig } from '@/pages/Dataset/config';
|
||||
import { addDataset } from '@/services/dataset/index.js';
|
||||
import { to } from '@/utils/promise';
|
||||
import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui';
|
||||
import {
|
||||
|
@ -19,8 +18,7 @@ import {
|
|||
type UploadProps,
|
||||
} from 'antd';
|
||||
import { omit } from 'lodash';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { CategoryData } from '../../config';
|
||||
import { useState } from 'react';
|
||||
import styles from './index.less';
|
||||
|
||||
interface AddDatasetModalProps extends Omit<ModalProps, 'onOk'> {
|
||||
|
@ -31,15 +29,15 @@ interface AddDatasetModalProps extends Omit<ModalProps, 'onOk'> {
|
|||
|
||||
function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalProps) {
|
||||
const [uuid] = useState(Date.now());
|
||||
const [clusterOptions, setClusterOptions] = useState<DictValueEnumObj[]>([]);
|
||||
// const [clusterOptions, setClusterOptions] = useState<DictValueEnumObj[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
getClusterOptions();
|
||||
}, []);
|
||||
// useEffect(() => {
|
||||
// getClusterOptions();
|
||||
// }, []);
|
||||
|
||||
// 上传组件参数
|
||||
const uploadProps: UploadProps = {
|
||||
action: '/api/mmp/dataset/upload',
|
||||
action: resourceConfig[ResourceType.Dataset].uploadAction,
|
||||
headers: {
|
||||
Authorization: getAccessToken() || '',
|
||||
},
|
||||
|
@ -47,16 +45,16 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr
|
|||
};
|
||||
|
||||
// 获取集群版本数据
|
||||
const getClusterOptions = async () => {
|
||||
const [res] = await to(getDictSelectOption('available_cluster'));
|
||||
if (res) {
|
||||
setClusterOptions(res);
|
||||
}
|
||||
};
|
||||
// const getClusterOptions = async () => {
|
||||
// const [res] = await to(getDictSelectOption('available_cluster'));
|
||||
// if (res) {
|
||||
// setClusterOptions(res);
|
||||
// }
|
||||
// };
|
||||
|
||||
// 上传请求
|
||||
const createDataset = async (params: any) => {
|
||||
const [res] = await to(addDatesetAndVesion(params));
|
||||
const [res] = await to(addDataset(params));
|
||||
if (res) {
|
||||
message.success('创建成功');
|
||||
onOk?.();
|
||||
|
@ -94,7 +92,13 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr
|
|||
}}
|
||||
destroyOnClose
|
||||
>
|
||||
<Form name="form" layout="vertical" onFinish={onFinish} autoComplete="off">
|
||||
<Form
|
||||
name="form"
|
||||
layout="vertical"
|
||||
onFinish={onFinish}
|
||||
initialValues={{ is_public: false }}
|
||||
autoComplete="off"
|
||||
>
|
||||
<Form.Item
|
||||
label="数据集名称"
|
||||
name="name"
|
||||
|
@ -106,7 +110,7 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr
|
|||
},
|
||||
]}
|
||||
>
|
||||
<Input placeholder="请输入数据名称" showCount allowClear maxLength={64} />
|
||||
<Input placeholder="请输入数据名称" showCount allowClear maxLength={50} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="数据集版本"
|
||||
|
@ -116,6 +120,14 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr
|
|||
required: true,
|
||||
message: '请输入数据集版本',
|
||||
},
|
||||
{
|
||||
validator: (_rule, value) => {
|
||||
if (value === 'master') {
|
||||
return Promise.reject(`版本不能为 master`);
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input placeholder="请输入数据集版本" showCount allowClear maxLength={64} />
|
||||
|
@ -125,7 +137,7 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr
|
|||
allowClear
|
||||
placeholder="请选择数据集分类"
|
||||
options={typeList}
|
||||
fieldNames={{ label: 'name', value: 'id' }}
|
||||
fieldNames={{ label: 'name', value: 'name' }}
|
||||
optionFilterProp="name"
|
||||
showSearch
|
||||
/>
|
||||
|
@ -135,14 +147,14 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr
|
|||
allowClear
|
||||
placeholder="请选择研究方向/应用领域"
|
||||
options={tagList}
|
||||
fieldNames={{ label: 'name', value: 'id' }}
|
||||
fieldNames={{ label: 'name', value: 'name' }}
|
||||
optionFilterProp="name"
|
||||
showSearch
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label="集群版本" name="available_cluster">
|
||||
{/* <Form.Item label="集群版本" name="available_cluster">
|
||||
<Select allowClear placeholder="请选择集群版本" options={clusterOptions} />
|
||||
</Form.Item>
|
||||
</Form.Item> */}
|
||||
<Form.Item
|
||||
label="数据集简介"
|
||||
name="description"
|
||||
|
@ -156,15 +168,19 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr
|
|||
<Input.TextArea
|
||||
placeholder="请输入数据集简介"
|
||||
showCount
|
||||
maxLength={256}
|
||||
maxLength={200}
|
||||
autoSize={{ minRows: 2, maxRows: 6 }}
|
||||
allowClear
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label="选择流水线" name="range">
|
||||
<Form.Item
|
||||
label="可见性"
|
||||
name="is_public"
|
||||
rules={[{ required: true, message: '请选择可见性' }]}
|
||||
>
|
||||
<Radio.Group>
|
||||
<Radio value="0">仅自己可见</Radio>
|
||||
<Radio value="1">工作空间可见</Radio>
|
||||
<Radio value={false}>私有</Radio>
|
||||
<Radio value={true}>公开</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
|
@ -187,7 +203,7 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr
|
|||
>
|
||||
上传文件
|
||||
</Button>
|
||||
<div className={styles['upload-tip']}>只允许上传.zip,.tgz格式文件</div>
|
||||
<div className={styles['upload-tip']}>只允许上传 .zip 和 .tgz 格式文件</div>
|
||||
</Upload>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { getAccessToken } from '@/access';
|
||||
import KFIcon from '@/components/KFIcon';
|
||||
import KFModal from '@/components/KFModal';
|
||||
import { CategoryData } from '@/pages/Dataset/config';
|
||||
import { CategoryData, ResourceType, resourceConfig } from '@/pages/Dataset/config';
|
||||
import { addModel } from '@/services/dataset/index.js';
|
||||
import { to } from '@/utils/promise';
|
||||
import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui';
|
||||
|
@ -9,6 +9,7 @@ import {
|
|||
Button,
|
||||
Form,
|
||||
Input,
|
||||
Radio,
|
||||
Select,
|
||||
Upload,
|
||||
UploadFile,
|
||||
|
@ -31,7 +32,7 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps)
|
|||
|
||||
// 上传组件参数
|
||||
const uploadProps: UploadProps = {
|
||||
action: '/api/mmp/models/upload',
|
||||
action: resourceConfig[ResourceType.Model].uploadAction,
|
||||
headers: {
|
||||
Authorization: getAccessToken() || '',
|
||||
},
|
||||
|
@ -53,7 +54,7 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps)
|
|||
if (validateUploadFiles(fileList)) {
|
||||
const params = {
|
||||
...omit(formData, ['fileList']),
|
||||
models_version_vos: fileList.map((item) => {
|
||||
model_version_vos: fileList.map((item) => {
|
||||
const data = item.response?.data?.[0] ?? {};
|
||||
return {
|
||||
file_name: data.fileName,
|
||||
|
@ -77,7 +78,13 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps)
|
|||
form: 'form',
|
||||
}}
|
||||
>
|
||||
<Form name="form" layout="vertical" onFinish={onFinish} autoComplete="off">
|
||||
<Form
|
||||
name="form"
|
||||
layout="vertical"
|
||||
onFinish={onFinish}
|
||||
autoComplete="off"
|
||||
initialValues={{ is_public: false }}
|
||||
>
|
||||
<Form.Item
|
||||
label="模型名称"
|
||||
name="name"
|
||||
|
@ -88,9 +95,8 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps)
|
|||
},
|
||||
]}
|
||||
>
|
||||
<Input placeholder="请输入模型名称" showCount allowClear maxLength={64} />
|
||||
<Input placeholder="请输入模型名称" showCount allowClear maxLength={50} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="模型版本"
|
||||
name="version"
|
||||
|
@ -99,9 +105,37 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps)
|
|||
required: true,
|
||||
message: '请输入模型版本',
|
||||
},
|
||||
{
|
||||
validator: (_rule, value) => {
|
||||
if (value === 'master') {
|
||||
return Promise.reject(`版本不能为 master`);
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input placeholder="请输入模型版本" allowClear maxLength={64} />
|
||||
<Input placeholder="请输入模型版本" showCount allowClear maxLength={64} />
|
||||
</Form.Item>
|
||||
<Form.Item label="模型框架" name="model_type">
|
||||
<Select
|
||||
allowClear
|
||||
placeholder="请选择模型类型"
|
||||
options={typeList}
|
||||
fieldNames={{ label: 'name', value: 'name' }}
|
||||
optionFilterProp="name"
|
||||
showSearch
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label="模型能力" name="model_tag">
|
||||
<Select
|
||||
allowClear
|
||||
placeholder="请选择模型标签"
|
||||
options={tagList}
|
||||
fieldNames={{ label: 'name', value: 'name' }}
|
||||
optionFilterProp="name"
|
||||
showSearch
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="模型简介"
|
||||
|
@ -115,37 +149,21 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps)
|
|||
>
|
||||
<Input.TextArea
|
||||
placeholder="请输入模型简介"
|
||||
showCount
|
||||
maxLength={256}
|
||||
maxLength={200}
|
||||
autoSize={{ minRows: 2, maxRows: 6 }}
|
||||
showCount
|
||||
allowClear
|
||||
/>
|
||||
</Form.Item>
|
||||
{/* <Form.Item label="可见范围" name="available_range">
|
||||
<Form.Item
|
||||
label="可见性"
|
||||
name="is_public"
|
||||
rules={[{ required: true, message: '请选择可见性' }]}
|
||||
>
|
||||
<Radio.Group>
|
||||
<Radio value="0">仅自己可见</Radio>
|
||||
<Radio value="1">工作空间可见</Radio>
|
||||
<Radio value={false}>私有</Radio>
|
||||
<Radio value={true}>公开</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Item> */}
|
||||
<Form.Item label="模型框架" name="model_type">
|
||||
<Select
|
||||
allowClear
|
||||
placeholder="请选择模型类型"
|
||||
options={typeList}
|
||||
fieldNames={{ label: 'name', value: 'id' }}
|
||||
optionFilterProp="name"
|
||||
showSearch
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label="模型能力" name="model_tag">
|
||||
<Select
|
||||
allowClear
|
||||
placeholder="请选择模型标签"
|
||||
options={tagList}
|
||||
fieldNames={{ label: 'name', value: 'id' }}
|
||||
optionFilterProp="name"
|
||||
showSearch
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="模型文件"
|
||||
|
|
|
@ -21,14 +21,16 @@ import styles from '../AddDatasetModal/index.less';
|
|||
interface AddVersionModalProps extends Omit<ModalProps, 'onOk'> {
|
||||
resourceType: ResourceType;
|
||||
resourceId: number;
|
||||
initialName: string;
|
||||
identifier: string;
|
||||
resoureName: string;
|
||||
onOk: () => void;
|
||||
}
|
||||
|
||||
function AddVersionModal({
|
||||
resourceType,
|
||||
resourceId,
|
||||
initialName,
|
||||
resoureName,
|
||||
identifier,
|
||||
onOk,
|
||||
...rest
|
||||
}: AddVersionModalProps) {
|
||||
|
@ -58,17 +60,20 @@ function AddVersionModal({
|
|||
const onFinish = (formData: any) => {
|
||||
const fileList: UploadFile[] = formData['fileList'] ?? [];
|
||||
if (validateUploadFiles(fileList)) {
|
||||
const otherParams = omit(formData, ['fileList']);
|
||||
const params = fileList.map((item) => {
|
||||
const version_vos = fileList.map((item) => {
|
||||
const data = item.response?.data?.[0] ?? {};
|
||||
return {
|
||||
...otherParams,
|
||||
[config.idParamKey]: resourceId,
|
||||
file_name: data.fileName,
|
||||
file_size: data.fileSize,
|
||||
url: data.url,
|
||||
};
|
||||
});
|
||||
const params = {
|
||||
id: resourceId,
|
||||
identifier,
|
||||
[config.filePropKey]: version_vos,
|
||||
...omit(formData, 'fileList'),
|
||||
};
|
||||
createDatasetVersion(params);
|
||||
}
|
||||
};
|
||||
|
@ -90,7 +95,7 @@ function AddVersionModal({
|
|||
name="form"
|
||||
layout="vertical"
|
||||
initialValues={{
|
||||
name: initialName,
|
||||
name: resoureName,
|
||||
}}
|
||||
onFinish={onFinish}
|
||||
autoComplete="off"
|
||||
|
@ -115,13 +120,21 @@ function AddVersionModal({
|
|||
required: true,
|
||||
message: `请输入${name}版本`,
|
||||
},
|
||||
{
|
||||
validator: (_rule, value) => {
|
||||
if (value === 'master') {
|
||||
return Promise.reject(`版本不能为 master`);
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input placeholder={`请输入${name}版本`} maxLength={64} showCount allowClear />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="版本描述"
|
||||
name="description"
|
||||
name="version_desc"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
|
@ -158,7 +171,7 @@ function AddVersionModal({
|
|||
上传文件
|
||||
</Button>
|
||||
{resourceType === ResourceType.Dataset && (
|
||||
<div className={styles['upload-tip']}>只允许上传.zip格式文件</div>
|
||||
<div className={styles['upload-tip']}>只允许上传 .zip 格式文件</div>
|
||||
)}
|
||||
</Upload>
|
||||
</Form.Item>
|
||||
|
|
|
@ -23,12 +23,14 @@ function CategoryItem({ resourceType, item, isSelected, onClick }: CategoryItemP
|
|||
style={{ width: '22px' }}
|
||||
src={`/assets/images/${config.prefix}/${item.path}.png`}
|
||||
alt=""
|
||||
draggable={false}
|
||||
/>
|
||||
<img
|
||||
className={styles['category-item__active-icon']}
|
||||
style={{ width: '22px' }}
|
||||
src={`/assets/images/${config.prefix}/${item.path}-hover.png`}
|
||||
alt=""
|
||||
draggable={false}
|
||||
/>
|
||||
<span className={styles['category-item__name']}>{item.name}</span>
|
||||
</div>
|
||||
|
|
|
@ -3,17 +3,12 @@ import { CategoryData, ResourceType, resourceConfig } from '../../config';
|
|||
import CategoryItem from '../CategoryItem';
|
||||
import styles from './index.less';
|
||||
|
||||
export type CategoryValue = {
|
||||
dataType: number | undefined;
|
||||
dataTag: number | undefined;
|
||||
};
|
||||
|
||||
type CategoryProps = {
|
||||
resourceType: ResourceType; // 资源类型,数据集还是模型
|
||||
typeList: CategoryData[];
|
||||
tagList: CategoryData[];
|
||||
activeType?: number;
|
||||
activeTag?: number;
|
||||
activeType?: string;
|
||||
activeTag?: string;
|
||||
onTypeSelect: (value: CategoryData) => void;
|
||||
onTagSelect: (value: CategoryData) => void;
|
||||
onSearch: (value: string) => void;
|
||||
|
@ -44,7 +39,7 @@ function CategoryList({
|
|||
resourceType={resourceType}
|
||||
item={item}
|
||||
onClick={onTypeSelect}
|
||||
isSelected={item.id === activeType}
|
||||
isSelected={item.name === activeType}
|
||||
></CategoryItem>
|
||||
))}
|
||||
</Flex>
|
||||
|
@ -58,7 +53,7 @@ function CategoryList({
|
|||
resourceType={resourceType}
|
||||
item={item}
|
||||
onClick={onTagSelect}
|
||||
isSelected={item.id === activeTag}
|
||||
isSelected={item.name === activeTag}
|
||||
></CategoryItem>
|
||||
))}
|
||||
</Flex>
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
.resource-info {
|
||||
height: 100%;
|
||||
|
||||
&__top {
|
||||
width: 100%;
|
||||
height: 125px;
|
||||
margin-bottom: 10px;
|
||||
padding: 20px 30px;
|
||||
background-image: url(@/assets/img/dataset-intro-top.png);
|
||||
background-repeat: no-repeat;
|
||||
background-position: top center;
|
||||
background-size: 100% 100%;
|
||||
|
||||
&__name {
|
||||
margin-right: 10px;
|
||||
color: @text-color;
|
||||
font-weight: 500;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
&__tag {
|
||||
padding: 4px 10px;
|
||||
color: @primary-color;
|
||||
font-size: 14px;
|
||||
background: .addAlpha(@primary-color, 0.1) [];
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
:global {
|
||||
.ant-btn-dangerous {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__bottom {
|
||||
position: relative;
|
||||
height: calc(100% - 135px);
|
||||
padding: 8px 30px 20px;
|
||||
background: #ffffff;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09);
|
||||
|
||||
&__legend {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 30px;
|
||||
}
|
||||
|
||||
:global {
|
||||
.ant-tabs {
|
||||
height: 100%;
|
||||
.ant-tabs-content-holder {
|
||||
height: 100%;
|
||||
.ant-tabs-content {
|
||||
height: 100%;
|
||||
.ant-tabs-tabpane {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,247 @@
|
|||
/*
|
||||
* @Author: 赵伟
|
||||
* @Date: 2024-09-06 09:23:15
|
||||
* @Description: 数据集、模型详情
|
||||
*/
|
||||
|
||||
import KFIcon from '@/components/KFIcon';
|
||||
import {
|
||||
ResourceData,
|
||||
ResourceType,
|
||||
ResourceVersionData,
|
||||
resourceConfig,
|
||||
} from '@/pages/Dataset/config';
|
||||
import GraphLegend from '@/pages/Model/components/GraphLegend';
|
||||
import ModelEvolution from '@/pages/Model/components/ModelEvolution';
|
||||
import { openAntdModal } from '@/utils/modal';
|
||||
import { to } from '@/utils/promise';
|
||||
import { modalConfirm } from '@/utils/ui';
|
||||
import { useParams, useSearchParams } from '@umijs/max';
|
||||
import { App, Button, Flex, Select, Tabs } from 'antd';
|
||||
import { useEffect, useState } from 'react';
|
||||
import AddVersionModal from '../AddVersionModal';
|
||||
import ResourceIntro from '../ResourceIntro';
|
||||
import ResourceVersion from '../ResourceVersion';
|
||||
import styles from './index.less';
|
||||
|
||||
// 这里值小写是因为值会写在 url 中
|
||||
export enum ResourceInfoTabKeys {
|
||||
Introduction = 'introduction', // 简介
|
||||
Version = 'version', // 版本
|
||||
Evolution = 'evolution', // 演化
|
||||
}
|
||||
|
||||
type ResourceInfoProps = {
|
||||
resourceType: ResourceType;
|
||||
};
|
||||
|
||||
const ResourceInfo = ({ resourceType }: ResourceInfoProps) => {
|
||||
const [info, setInfo] = useState<ResourceData>({} as ResourceData);
|
||||
const locationParams = useParams();
|
||||
const [searchParams] = useSearchParams();
|
||||
const resourceId = Number(locationParams.id);
|
||||
// 模型演化传入的 tab
|
||||
const defaultTab = searchParams.get('tab') || ResourceInfoTabKeys.Introduction;
|
||||
// 模型演化传入的版本
|
||||
let versionParam = searchParams.get('version');
|
||||
const name = searchParams.get('name') || '';
|
||||
const owner = searchParams.get('owner') || '';
|
||||
const identifier = searchParams.get('identifier') || '';
|
||||
const [versionList, setVersionList] = useState<ResourceVersionData[]>([]);
|
||||
const [version, setVersion] = useState<string | undefined>(undefined);
|
||||
const [activeTab, setActiveTab] = useState<string>(defaultTab);
|
||||
const config = resourceConfig[resourceType];
|
||||
const typeName = config.name; // 数据集/模型
|
||||
const { message } = App.useApp();
|
||||
|
||||
useEffect(() => {
|
||||
getVersionList();
|
||||
}, [resourceId, owner, identifier]);
|
||||
|
||||
useEffect(() => {
|
||||
if (version) {
|
||||
getResourceDetail({
|
||||
id: resourceId,
|
||||
owner,
|
||||
name,
|
||||
identifier,
|
||||
version,
|
||||
});
|
||||
}
|
||||
}, [version]);
|
||||
|
||||
// 获取详情
|
||||
const getResourceDetail = async (params: {
|
||||
owner: string;
|
||||
name: string;
|
||||
id: number;
|
||||
identifier: string;
|
||||
version?: string;
|
||||
}) => {
|
||||
const request = config.getInfo;
|
||||
const [res] = await to(request(params));
|
||||
if (res) {
|
||||
setInfo(res.data);
|
||||
}
|
||||
};
|
||||
|
||||
// 获取版本列表
|
||||
const getVersionList = async () => {
|
||||
const request = config.getVersions;
|
||||
const [res] = await to(
|
||||
request({
|
||||
owner,
|
||||
identifier,
|
||||
}),
|
||||
);
|
||||
if (res && res.data && res.data.length > 0) {
|
||||
setVersionList(res.data);
|
||||
if (
|
||||
versionParam &&
|
||||
res.data.find((item: ResourceVersionData) => item.name === versionParam)
|
||||
) {
|
||||
setVersion(versionParam);
|
||||
versionParam = null;
|
||||
} else {
|
||||
setVersion(res.data[0].name);
|
||||
}
|
||||
} else {
|
||||
setVersion(undefined);
|
||||
}
|
||||
};
|
||||
|
||||
// 新建版本
|
||||
const showModal = () => {
|
||||
const { close } = openAntdModal(AddVersionModal, {
|
||||
resourceType: resourceType,
|
||||
resourceId: resourceId,
|
||||
resoureName: info.name,
|
||||
identifier: info.identifier,
|
||||
onOk: () => {
|
||||
getVersionList();
|
||||
close();
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 版本变化
|
||||
const handleVersionChange = (value: string) => {
|
||||
setVersion(value);
|
||||
};
|
||||
|
||||
// 删除版本
|
||||
const deleteVersion = async () => {
|
||||
const request = config.deleteVersion;
|
||||
const params = {
|
||||
id: resourceId,
|
||||
owner,
|
||||
identifier,
|
||||
relative_paths: info.relative_paths,
|
||||
version,
|
||||
};
|
||||
const [res] = await to(request(params));
|
||||
if (res) {
|
||||
message.success('删除成功');
|
||||
getVersionList();
|
||||
}
|
||||
};
|
||||
|
||||
// 处理删除
|
||||
const hanldeDelete = () => {
|
||||
modalConfirm({
|
||||
title: '删除后,该版本将不可恢复',
|
||||
content: '是否确认删除?',
|
||||
okText: '确认',
|
||||
cancelText: '取消',
|
||||
onOk: () => {
|
||||
deleteVersion();
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const items = [
|
||||
{
|
||||
key: ResourceInfoTabKeys.Introduction,
|
||||
label: `${typeName}简介`,
|
||||
icon: <KFIcon type="icon-moxingjianjie" />,
|
||||
children: <ResourceIntro resourceType={resourceType} info={info}></ResourceIntro>,
|
||||
},
|
||||
{
|
||||
key: ResourceInfoTabKeys.Version,
|
||||
label: `${typeName}文件`,
|
||||
icon: <KFIcon type="icon-moxingwenjian" />,
|
||||
children: <ResourceVersion resourceType={resourceType} info={info}></ResourceVersion>,
|
||||
},
|
||||
];
|
||||
|
||||
if (resourceType === ResourceType.Model) {
|
||||
items.push({
|
||||
key: ResourceInfoTabKeys.Evolution,
|
||||
label: `模型演化`,
|
||||
icon: <KFIcon type="icon-moxingyanhua1" />,
|
||||
children: (
|
||||
<ModelEvolution
|
||||
resourceId={resourceId}
|
||||
version={version}
|
||||
identifier={identifier}
|
||||
isActive={activeTab === ResourceInfoTabKeys.Evolution}
|
||||
onVersionChange={handleVersionChange}
|
||||
></ModelEvolution>
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
const typePropertyName = config.typeParamKey as keyof ResourceData;
|
||||
const tagPropertyName = config.tagParamKey as keyof ResourceData;
|
||||
|
||||
return (
|
||||
<div className={styles['resource-info']}>
|
||||
<div className={styles['resource-info__top']}>
|
||||
<Flex align="center" gap={10} style={{ marginBottom: '20px' }}>
|
||||
<div className={styles['resource-info__top__name']}>{info.name}</div>
|
||||
{info[typePropertyName] && (
|
||||
<div className={styles['resource-info__top__tag']}>
|
||||
{(info[typePropertyName] as string) || '--'}
|
||||
</div>
|
||||
)}
|
||||
{info[tagPropertyName] && (
|
||||
<div className={styles['resource-info__top__tag']}>
|
||||
{(info[tagPropertyName] as string) || '--'}
|
||||
</div>
|
||||
)}
|
||||
</Flex>
|
||||
<Flex align="center">
|
||||
<span style={{ marginRight: '10px' }}>版本号:</span>
|
||||
<Select
|
||||
placeholder="请选择版本号"
|
||||
style={{ width: '160px', marginRight: '20px' }}
|
||||
value={version}
|
||||
onChange={handleVersionChange}
|
||||
fieldNames={{ label: 'name', value: 'name' }}
|
||||
options={versionList}
|
||||
/>
|
||||
<Button type="default" onClick={showModal} icon={<KFIcon type="icon-xinjian2" />}>
|
||||
创建新版本
|
||||
</Button>
|
||||
<Button
|
||||
type="default"
|
||||
style={{ marginLeft: 'auto', marginRight: 0 }}
|
||||
onClick={hanldeDelete}
|
||||
icon={<KFIcon type="icon-shanchu" />}
|
||||
disabled={!version}
|
||||
danger
|
||||
>
|
||||
删除版本
|
||||
</Button>
|
||||
</Flex>
|
||||
</div>
|
||||
<div className={styles['resource-info__bottom']}>
|
||||
<Tabs activeKey={activeTab} items={items} onChange={(key) => setActiveTab(key)}></Tabs>
|
||||
<div className={styles['resource-info__bottom__legend']}>
|
||||
{activeTab === ResourceInfoTabKeys.Evolution && <GraphLegend />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default ResourceInfo;
|
|
@ -1,65 +1,10 @@
|
|||
.resource-intro {
|
||||
height: 100%;
|
||||
|
||||
&__top {
|
||||
width: 100%;
|
||||
margin-top: 24px;
|
||||
&__basic {
|
||||
width: 100%;
|
||||
height: 110px;
|
||||
margin-bottom: 10px;
|
||||
padding: 20px 30px 0;
|
||||
background-image: url(@/assets/img/dataset-intro-top.png);
|
||||
background-repeat: no-repeat;
|
||||
background-position: top center;
|
||||
background-size: 100% 100%;
|
||||
|
||||
&__name {
|
||||
margin-bottom: 12px;
|
||||
color: @text-color;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
&__tag {
|
||||
margin-right: 10px;
|
||||
padding: 4px 10px;
|
||||
color: @primary-color;
|
||||
font-size: 14px;
|
||||
background: rgba(22, 100, 255, 0.1);
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
&__bottom {
|
||||
height: calc(100% - 120px);
|
||||
padding: 8px 30px 20px;
|
||||
background: #ffffff;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09);
|
||||
|
||||
:global {
|
||||
.ant-tabs {
|
||||
height: 100%;
|
||||
.ant-tabs-content-holder {
|
||||
height: 100%;
|
||||
.ant-tabs-content {
|
||||
height: 100%;
|
||||
.ant-tabs-tabpane {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__title {
|
||||
margin: 30px 0 10px;
|
||||
color: @text-color;
|
||||
font-weight: 500;
|
||||
font-size: @font-size;
|
||||
}
|
||||
|
||||
&__intro {
|
||||
color: @text-color-secondary;
|
||||
font-size: 14px;
|
||||
&__usage {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,156 +1,181 @@
|
|||
import KFIcon from '@/components/KFIcon';
|
||||
import ModelEvolution from '@/pages/Model/components/ModelEvolution';
|
||||
import { to } from '@/utils/promise';
|
||||
import { useParams, useSearchParams } from '@umijs/max';
|
||||
import { Flex, Tabs } from 'antd';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { ResourceData, ResourceType, resourceConfig } from '../../config';
|
||||
import ResourceVersion from '../ResourceVersion';
|
||||
import BasicInfo, { BasicInfoData } from '@/components/BasicInfo';
|
||||
import SubAreaTitle from '@/components/SubAreaTitle';
|
||||
import { DatasetData, ModelData, ResourceType } from '@/pages/Dataset/config';
|
||||
import styles from './index.less';
|
||||
|
||||
// 这里值小写是因为值会写在 url 中
|
||||
export enum ResourceInfoTabKeys {
|
||||
Introduction = 'introduction', // 简介
|
||||
Version = 'version', // 版本
|
||||
Evolution = 'evolution', // 演化
|
||||
}
|
||||
|
||||
type ResourceIntroProps = {
|
||||
resourceType: ResourceType;
|
||||
info: DatasetData | ModelData;
|
||||
};
|
||||
|
||||
const ResourceIntro = ({ resourceType }: ResourceIntroProps) => {
|
||||
const [info, setInfo] = useState<ResourceData>({} as ResourceData);
|
||||
const locationParams = useParams();
|
||||
const [searchParams] = useSearchParams();
|
||||
const defaultTab = searchParams.get('tab') || ResourceInfoTabKeys.Introduction;
|
||||
let versionParam = searchParams.get('version');
|
||||
const [versionList, setVersionList] = useState([]);
|
||||
const [version, setVersion] = useState<string | undefined>(undefined);
|
||||
const [activeTab, setActiveTab] = useState<string>(defaultTab);
|
||||
const resourceId = Number(locationParams.id);
|
||||
const config = resourceConfig[resourceType];
|
||||
const typeName = config.name; // 数据集/模型
|
||||
// const formatArray = (arr?: ResourceData[]) => {
|
||||
// if (!arr || arr.length === 0) {
|
||||
// return '--';
|
||||
// }
|
||||
// return arr.map((item) => item.name).join('\n');
|
||||
// };
|
||||
|
||||
useEffect(() => {
|
||||
getModelByDetail();
|
||||
getVersionList();
|
||||
}, [resourceId]);
|
||||
|
||||
// 获取详情
|
||||
const getModelByDetail = async () => {
|
||||
const request = config.getInfo;
|
||||
const [res] = await to(request(resourceId));
|
||||
if (res) {
|
||||
setInfo(res.data);
|
||||
}
|
||||
};
|
||||
|
||||
// 获取版本列表
|
||||
const getVersionList = async () => {
|
||||
const request = config.getVersions;
|
||||
const [res] = await to(request(resourceId));
|
||||
if (res && res.data && res.data.length > 0) {
|
||||
setVersionList(
|
||||
res.data.map((item: string) => {
|
||||
return {
|
||||
label: item,
|
||||
value: item,
|
||||
};
|
||||
}),
|
||||
);
|
||||
if (versionParam && res.data.includes(versionParam)) {
|
||||
setVersion(versionParam);
|
||||
versionParam = null;
|
||||
} else {
|
||||
setVersion(res.data[0]);
|
||||
}
|
||||
} else {
|
||||
setVersion(undefined);
|
||||
}
|
||||
};
|
||||
|
||||
// 版本变化
|
||||
const handleVersionChange = (value: string) => {
|
||||
setVersion(value);
|
||||
};
|
||||
|
||||
const items = [
|
||||
{
|
||||
key: ResourceInfoTabKeys.Introduction,
|
||||
label: `${typeName}简介`,
|
||||
icon: <KFIcon type="icon-moxingjianjie" />,
|
||||
children: (
|
||||
<>
|
||||
<div className={styles['resource-intro__title']}>简介</div>
|
||||
<div className={styles['resource-intro__intro']}>{info.description}</div>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: ResourceInfoTabKeys.Version,
|
||||
label: `${typeName}文件/版本`,
|
||||
icon: <KFIcon type="icon-moxingwenjian" />,
|
||||
children: (
|
||||
<ResourceVersion
|
||||
resourceType={resourceType}
|
||||
resourceId={resourceId}
|
||||
resourceName={info.name}
|
||||
isPublic={info.available_range === 1}
|
||||
versionList={versionList}
|
||||
version={version}
|
||||
isActive={activeTab === ResourceInfoTabKeys.Version}
|
||||
getVersionList={getVersionList}
|
||||
onVersionChange={handleVersionChange}
|
||||
></ResourceVersion>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
if (resourceType === ResourceType.Model) {
|
||||
items.push({
|
||||
key: ResourceInfoTabKeys.Evolution,
|
||||
label: `模型演化`,
|
||||
icon: <KFIcon type="icon-moxingyanhua1" />,
|
||||
children: (
|
||||
<ModelEvolution
|
||||
resourceId={resourceId}
|
||||
versionList={versionList}
|
||||
version={version}
|
||||
isActive={activeTab === ResourceInfoTabKeys.Evolution}
|
||||
onVersionChange={handleVersionChange}
|
||||
></ModelEvolution>
|
||||
),
|
||||
});
|
||||
const formatDataset = (arr?: DatasetData[]) => {
|
||||
if (!arr || arr.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
return arr.map((item) => item.name).join('\n');
|
||||
};
|
||||
|
||||
const infoTypePropertyName = config.infoTypePropertyName as keyof ResourceData;
|
||||
const infoTagPropertyName = config.infoTagPropertyName as keyof ResourceData;
|
||||
const formatMap = (map?: Record<string, string>) => {
|
||||
if (!map || Object.keys(map).length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
return Object.entries(map)
|
||||
.map(([key, value]) => `${key} = ${value}`)
|
||||
.join('\n');
|
||||
};
|
||||
|
||||
const getDatasetDatas = (data: DatasetData): BasicInfoData[] => [
|
||||
{
|
||||
label: '数据集名称',
|
||||
value: data.name,
|
||||
},
|
||||
{
|
||||
label: '版本',
|
||||
value: data.version,
|
||||
},
|
||||
{
|
||||
label: '创建人',
|
||||
value: data.create_by,
|
||||
},
|
||||
{
|
||||
label: '更新时间',
|
||||
value: data.update_time,
|
||||
},
|
||||
{
|
||||
label: '数据来源',
|
||||
value: data.dataset_source,
|
||||
},
|
||||
{
|
||||
label: '处理代码',
|
||||
value: data.processing_code,
|
||||
},
|
||||
{
|
||||
label: '数据集分类',
|
||||
value: data.data_type,
|
||||
},
|
||||
{
|
||||
label: '研究方向',
|
||||
value: data.data_tag,
|
||||
},
|
||||
{
|
||||
label: '数据集描述',
|
||||
value: data.description,
|
||||
},
|
||||
{
|
||||
label: '版本描述',
|
||||
value: data.version_desc,
|
||||
},
|
||||
];
|
||||
|
||||
const getModelDatas = (data: ModelData): BasicInfoData[] => [
|
||||
{
|
||||
label: '模型名称',
|
||||
value: data.name,
|
||||
},
|
||||
{
|
||||
label: '版本',
|
||||
value: data.version,
|
||||
},
|
||||
{
|
||||
label: '创建人',
|
||||
value: data.create_by,
|
||||
},
|
||||
{
|
||||
label: '更新时间',
|
||||
value: data.update_time,
|
||||
},
|
||||
{
|
||||
label: '训练镜像',
|
||||
value: data.image,
|
||||
},
|
||||
{
|
||||
label: '训练代码',
|
||||
value: data.code,
|
||||
},
|
||||
{
|
||||
label: '训练数据集',
|
||||
value: data.train_datasets,
|
||||
format: formatDataset,
|
||||
},
|
||||
{
|
||||
label: '测试数据集',
|
||||
value: data.test_datasets,
|
||||
format: formatDataset,
|
||||
},
|
||||
{
|
||||
label: '参数',
|
||||
value: data.params,
|
||||
format: formatMap,
|
||||
},
|
||||
{
|
||||
label: '指标',
|
||||
value: data.metrics,
|
||||
format: formatMap,
|
||||
},
|
||||
{
|
||||
label: '训练任务',
|
||||
value: data.train_task,
|
||||
format: (value?: any) => value?.name,
|
||||
externalLink: data.train_task
|
||||
? `${location.origin}/pipeline/experiment/instance/${data.train_task.task_id}/${data.train_task.ins_id}`
|
||||
: '',
|
||||
},
|
||||
{
|
||||
label: '模型来源',
|
||||
value: data.model_source,
|
||||
},
|
||||
{
|
||||
label: '模型框架',
|
||||
value: data.model_type,
|
||||
},
|
||||
{
|
||||
label: '模型能力',
|
||||
value: data.model_tag,
|
||||
},
|
||||
{
|
||||
label: '模型描述',
|
||||
value: data.description,
|
||||
},
|
||||
{
|
||||
label: '版本描述',
|
||||
value: data.version_desc,
|
||||
},
|
||||
];
|
||||
|
||||
function ResourceIntro({ resourceType, info }: ResourceIntroProps) {
|
||||
const basicDatas: BasicInfoData[] =
|
||||
resourceType === ResourceType.Dataset
|
||||
? getDatasetDatas(info as DatasetData)
|
||||
: getModelDatas(info as ModelData);
|
||||
|
||||
return (
|
||||
<div className={styles['resource-intro']}>
|
||||
<div className={styles['resource-intro__top']}>
|
||||
<div className={styles['resource-intro__top__name']}>{info.name}</div>
|
||||
<Flex align="center">
|
||||
<div className={styles['resource-intro__top__tag']}>
|
||||
{typeName} id:{info.id}
|
||||
</div>
|
||||
{info[infoTypePropertyName] && (
|
||||
<div className={styles['resource-intro__top__tag']}>
|
||||
{info[infoTypePropertyName] || '--'}
|
||||
</div>
|
||||
)}
|
||||
{info[infoTagPropertyName] && (
|
||||
<div className={styles['resource-intro__top__tag']}>
|
||||
{info[infoTagPropertyName] || '--'}
|
||||
</div>
|
||||
)}
|
||||
</Flex>
|
||||
</div>
|
||||
<div className={styles['resource-intro__bottom']}>
|
||||
<Tabs activeKey={activeTab} items={items} onChange={(key) => setActiveTab(key)}></Tabs>
|
||||
<SubAreaTitle
|
||||
title="基本信息"
|
||||
image={require('@/assets/img/mirror-basic.png')}
|
||||
style={{ marginBottom: '26px' }}
|
||||
></SubAreaTitle>
|
||||
<div className={styles['resource-intro__basic']}>
|
||||
<BasicInfo datas={basicDatas} labelWidth={86}></BasicInfo>
|
||||
</div>
|
||||
<SubAreaTitle
|
||||
title="实例用法"
|
||||
image={require('@/assets/img/usage-icon.png')}
|
||||
style={{ margin: '40px 0 24px' }}
|
||||
></SubAreaTitle>
|
||||
<div
|
||||
className={styles['resource-intro__usage']}
|
||||
dangerouslySetInnerHTML={{ __html: info.usage ?? '暂无实例用法' }}
|
||||
></div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export default ResourceIntro;
|
||||
|
|
|
@ -39,12 +39,20 @@ function ResourceItem({ item, isPublic, onClick, onRemove }: ResourceItemProps)
|
|||
<div className={styles['resource-item__description']}>{item.description}</div>
|
||||
<Flex justify="space-between">
|
||||
<div className={styles['resource-item__time']}>
|
||||
<img style={{ width: '17px', marginRight: '6px' }} src={creatByImg} alt="" />
|
||||
<span>{item.create_by}</span>
|
||||
<img
|
||||
style={{ width: '17px', marginRight: '6px' }}
|
||||
src={creatByImg}
|
||||
draggable={false}
|
||||
alt=""
|
||||
/>
|
||||
<span>{item.create_by ?? ''}</span>
|
||||
</div>
|
||||
<div className={styles['resource-item__time']}>
|
||||
<img style={{ width: '12px', marginRight: '5px' }} src={clock} alt="" />
|
||||
<span>最近更新: {formatDate(item.update_time, 'YYYY-MM-DD')}</span>
|
||||
<img style={{ width: '12px', marginRight: '5px' }} src={clock} draggable={false} alt="" />
|
||||
<span>
|
||||
{'最近更新: '}
|
||||
{item.update_time ? formatDate(item.update_time, 'YYYY-MM-DD') : item.time_ago ?? ''}
|
||||
</span>
|
||||
</div>
|
||||
</Flex>
|
||||
</div>
|
||||
|
|
|
@ -36,4 +36,8 @@
|
|||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
&__empty {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import KFEmpty, { EmptyType } from '@/components/KFEmpty';
|
||||
import KFIcon from '@/components/KFIcon';
|
||||
import { CommonTabKeys } from '@/enums';
|
||||
import AddModelModal from '@/pages/Dataset/components/AddModelModal';
|
||||
|
@ -6,6 +7,7 @@ import { to } from '@/utils/promise';
|
|||
import { modalConfirm } from '@/utils/ui';
|
||||
import { useNavigate } from '@umijs/max';
|
||||
import { App, Button, Input, Pagination, PaginationProps } from 'antd';
|
||||
import { pick } from 'lodash';
|
||||
import { Ref, forwardRef, useEffect, useImperativeHandle, useState } from 'react';
|
||||
import { CategoryData, ResourceData, ResourceType, resourceConfig } from '../../config';
|
||||
import AddDatasetModal from '../AddDatasetModal';
|
||||
|
@ -18,8 +20,8 @@ export type ResourceListRef = {
|
|||
|
||||
type ResourceListProps = {
|
||||
resourceType: ResourceType;
|
||||
dataType?: number;
|
||||
dataTag?: number;
|
||||
dataType?: string;
|
||||
dataTag?: string;
|
||||
isPublic: boolean;
|
||||
typeList: CategoryData[];
|
||||
tagList: CategoryData[];
|
||||
|
@ -43,7 +45,7 @@ function ResourceList(
|
|||
ref: Ref<ResourceListRef>,
|
||||
) {
|
||||
const navigate = useNavigate();
|
||||
const [dataList, setDataList] = useState<ResourceData[]>([]);
|
||||
const [dataList, setDataList] = useState<ResourceData[] | undefined>(undefined);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [pagination, setPagination] = useState<PaginationProps>(
|
||||
initialPagination ?? {
|
||||
|
@ -71,7 +73,8 @@ function ResourceList(
|
|||
});
|
||||
setSearchText('');
|
||||
setInputText('');
|
||||
setDataList([]);
|
||||
setDataList(undefined);
|
||||
setTotal(0);
|
||||
},
|
||||
};
|
||||
},
|
||||
|
@ -80,12 +83,12 @@ function ResourceList(
|
|||
|
||||
// 获取数据请求
|
||||
const getDataList = async () => {
|
||||
const params = {
|
||||
const params: Record<string, any> = {
|
||||
page: pagination.current! - 1,
|
||||
size: pagination.pageSize,
|
||||
is_public: isPublic,
|
||||
[config.typeParamKey]: dataType,
|
||||
[config.tagParamKey]: dataTag,
|
||||
available_range: isPublic ? 1 : 0,
|
||||
name: searchText !== '' ? searchText : undefined,
|
||||
};
|
||||
const request = config.getList;
|
||||
|
@ -93,13 +96,16 @@ function ResourceList(
|
|||
if (res && res.data && res.data.content) {
|
||||
setDataList(res.data.content);
|
||||
setTotal(res.data.totalElements);
|
||||
} else {
|
||||
setDataList([]);
|
||||
setTotal(0);
|
||||
}
|
||||
};
|
||||
|
||||
// 删除请求
|
||||
const deleteRecord = async (id: number) => {
|
||||
const deleteRecord = async (params: { owner: string; identifier: string; repo_id?: number }) => {
|
||||
const request = config.deleteRecord;
|
||||
const [res] = await to(request(id));
|
||||
const [res] = await to(request(params));
|
||||
if (res) {
|
||||
getDataList();
|
||||
message.success('删除成功');
|
||||
|
@ -116,7 +122,7 @@ function ResourceList(
|
|||
modalConfirm({
|
||||
title: config.deleteModalTitle,
|
||||
onOk: () => {
|
||||
deleteRecord(record.id);
|
||||
deleteRecord(pick(record, ['owner', 'identifier', 'id']));
|
||||
},
|
||||
});
|
||||
};
|
||||
|
@ -131,7 +137,9 @@ function ResourceList(
|
|||
activeTag: dataTag,
|
||||
});
|
||||
const prefix = config.prefix;
|
||||
navigate(`/dataset/${prefix}/info/${record.id}`);
|
||||
navigate(
|
||||
`/dataset/${prefix}/info/${record.id}?name=${record.name}&owner=${record.owner}&identifier=${record.identifier}`,
|
||||
);
|
||||
};
|
||||
|
||||
// 分页切换
|
||||
|
@ -158,7 +166,7 @@ function ResourceList(
|
|||
return (
|
||||
<div className={styles['resource-list']}>
|
||||
<div className={styles['resource-list__header']}>
|
||||
<span>数据总数:{total}个</span>
|
||||
<span>数据总数:{total} 个</span>
|
||||
<div>
|
||||
<Input.Search
|
||||
placeholder={`按${config.name}名称筛选`}
|
||||
|
@ -182,26 +190,40 @@ function ResourceList(
|
|||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles['resource-list__content']}>
|
||||
{dataList?.map((item) => (
|
||||
<ResourceItem
|
||||
item={item}
|
||||
key={item.id}
|
||||
isPublic={isPublic}
|
||||
onRemove={handleRemove}
|
||||
onClick={handleClick}
|
||||
></ResourceItem>
|
||||
))}
|
||||
</div>
|
||||
<Pagination
|
||||
total={total}
|
||||
showSizeChanger
|
||||
defaultPageSize={20}
|
||||
pageSizeOptions={[20, 40, 60, 80, 100]}
|
||||
showQuickJumper
|
||||
onChange={handlePageChange}
|
||||
{...pagination}
|
||||
/>
|
||||
{dataList && dataList.length > 0 && (
|
||||
<>
|
||||
<div className={styles['resource-list__content']}>
|
||||
{dataList?.map((item) => (
|
||||
<ResourceItem
|
||||
item={item}
|
||||
key={item.id}
|
||||
isPublic={isPublic}
|
||||
onRemove={handleRemove}
|
||||
onClick={handleClick}
|
||||
></ResourceItem>
|
||||
))}
|
||||
</div>
|
||||
<Pagination
|
||||
total={total}
|
||||
showSizeChanger
|
||||
defaultPageSize={20}
|
||||
pageSizeOptions={[20, 40, 60, 80, 100]}
|
||||
showQuickJumper
|
||||
onChange={handlePageChange}
|
||||
{...pagination}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{dataList && dataList.length === 0 && (
|
||||
<KFEmpty
|
||||
className={styles['resource-list__empty']}
|
||||
type={EmptyType.NoData}
|
||||
title="暂无数据"
|
||||
content={'很抱歉,没有搜索到您想要的内容\n建议刷新试试'}
|
||||
hasFooter={true}
|
||||
onRefresh={getDataList}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -18,8 +18,8 @@ function ResourcePage({ resourceType }: ResourcePageProps) {
|
|||
const [activeTab, setActiveTab] = useState<string>(cacheState?.activeTab ?? CommonTabKeys.Public);
|
||||
const [typeList, setTypeList] = useState<CategoryData[]>([]);
|
||||
const [tagList, setTagList] = useState<CategoryData[]>([]);
|
||||
const [activeType, setActiveType] = useState<number | undefined>(cacheState?.activeType);
|
||||
const [activeTag, setActiveTag] = useState<number | undefined>(cacheState?.activeTag);
|
||||
const [activeType, setActiveType] = useState<string | undefined>(cacheState?.activeType);
|
||||
const [activeTag, setActiveTag] = useState<string | undefined>(cacheState?.activeTag);
|
||||
const dataListRef = useRef<ResourceListRef>(null);
|
||||
const config = resourceConfig[resourceType];
|
||||
|
||||
|
@ -34,12 +34,12 @@ function ResourcePage({ resourceType }: ResourcePageProps) {
|
|||
|
||||
// 选择类型
|
||||
const chooseType = (record: CategoryData) => {
|
||||
setActiveType((prev) => (prev === record.id ? undefined : record.id));
|
||||
setActiveType((prev) => (prev === record.name ? undefined : record.name));
|
||||
};
|
||||
|
||||
// 选择 Tag
|
||||
const chooseTag = (record: CategoryData) => {
|
||||
setActiveTag((prev) => (prev === record.id ? undefined : record.id));
|
||||
setActiveTag((prev) => (prev === record.name ? undefined : record.name));
|
||||
};
|
||||
|
||||
// 获取分类
|
||||
|
|
|
@ -1,125 +1,36 @@
|
|||
import CommonTableCell from '@/components/CommonTableCell';
|
||||
import DateTableCell from '@/components/DateTableCell';
|
||||
import KFIcon from '@/components/KFIcon';
|
||||
import { useEffectWhen } from '@/hooks';
|
||||
import AddVersionModal from '@/pages/Dataset/components/AddVersionModal';
|
||||
import {
|
||||
ResourceData,
|
||||
ResourceFileData,
|
||||
ResourceType,
|
||||
ResourceVersionData,
|
||||
resourceConfig,
|
||||
} from '@/pages/Dataset/config';
|
||||
import { downLoadZip } from '@/utils/downloadfile';
|
||||
import { openAntdModal } from '@/utils/modal';
|
||||
import { to } from '@/utils/promise';
|
||||
import { modalConfirm } from '@/utils/ui';
|
||||
import { App, Button, Flex, Select, Table } from 'antd';
|
||||
import { useState } from 'react';
|
||||
import { Button, Flex, Table } from 'antd';
|
||||
import styles from './index.less';
|
||||
|
||||
type ResourceVersionProps = {
|
||||
resourceType: ResourceType;
|
||||
resourceId: number;
|
||||
resourceName: string;
|
||||
isPublic: boolean;
|
||||
versionList: ResourceVersionData[];
|
||||
version?: string;
|
||||
isActive: boolean;
|
||||
getVersionList: () => void;
|
||||
onVersionChange: (version: string) => void;
|
||||
info: ResourceData;
|
||||
};
|
||||
function ResourceVersion({
|
||||
resourceType,
|
||||
resourceId,
|
||||
resourceName,
|
||||
isPublic,
|
||||
versionList,
|
||||
version,
|
||||
isActive,
|
||||
getVersionList,
|
||||
onVersionChange,
|
||||
}: ResourceVersionProps) {
|
||||
const [fileList, setFileList] = useState<ResourceFileData[]>([]);
|
||||
const { message } = App.useApp();
|
||||
function ResourceVersion({ resourceType, info }: ResourceVersionProps) {
|
||||
const config = resourceConfig[resourceType];
|
||||
|
||||
// 获取版本文件列表
|
||||
useEffectWhen(
|
||||
() => {
|
||||
if (version) {
|
||||
getFileList(version);
|
||||
} else {
|
||||
setFileList([]);
|
||||
}
|
||||
},
|
||||
[resourceId, version],
|
||||
isActive,
|
||||
);
|
||||
|
||||
// 获取版本下的文件列表
|
||||
const getFileList = async (version: string) => {
|
||||
const params = {
|
||||
version,
|
||||
[config.fileReqParamKey]: resourceId,
|
||||
};
|
||||
const request = config.getFiles;
|
||||
const [res] = await to(request(params));
|
||||
if (res) {
|
||||
setFileList(res?.data?.content ?? []);
|
||||
}
|
||||
};
|
||||
|
||||
// 删除版本
|
||||
const deleteVersion = async () => {
|
||||
const request = config.deleteVersion;
|
||||
const params = {
|
||||
[config.idParamKey]: resourceId,
|
||||
version,
|
||||
};
|
||||
const [res] = await to(request(params));
|
||||
if (res) {
|
||||
getVersionList();
|
||||
message.success('删除成功');
|
||||
}
|
||||
};
|
||||
|
||||
// 新建版本
|
||||
const showModal = () => {
|
||||
const { close } = openAntdModal(AddVersionModal, {
|
||||
resourceType: resourceType,
|
||||
resourceId: resourceId,
|
||||
initialName: resourceName,
|
||||
onOk: () => {
|
||||
getVersionList();
|
||||
close();
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 处理删除
|
||||
const hanldeDelete = () => {
|
||||
modalConfirm({
|
||||
title: '删除后,该版本将不可恢复',
|
||||
content: '是否确认删除?',
|
||||
okText: '确认',
|
||||
cancelText: '取消',
|
||||
|
||||
onOk: () => {
|
||||
deleteVersion();
|
||||
},
|
||||
});
|
||||
};
|
||||
const filePropKey = config.filePropKey as keyof ResourceData;
|
||||
const fileList = (info[filePropKey] ?? []) as ResourceFileData[];
|
||||
fileList.forEach((item) => (item.update_time = info.update_time));
|
||||
|
||||
// 全部导出
|
||||
const handleExport = async () => {
|
||||
const url = config.downloadAllAction;
|
||||
downLoadZip(url, { models_id: resourceId, version });
|
||||
downLoadZip(url, { name: info.name, id: info.id, version: info.version });
|
||||
};
|
||||
|
||||
// 单个导出
|
||||
const downloadAlone = (record: ResourceFileData) => {
|
||||
const downloadAlone = async (record: ResourceFileData) => {
|
||||
const url = config.downloadSingleAction;
|
||||
downLoadZip(`${url}/${record.id}`);
|
||||
downLoadZip(url, { url: record.url });
|
||||
};
|
||||
|
||||
const columns = [
|
||||
|
@ -142,12 +53,6 @@ function ResourceVersion({
|
|||
</a>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '版本号',
|
||||
dataIndex: 'version',
|
||||
key: 'version',
|
||||
render: CommonTableCell(),
|
||||
},
|
||||
{
|
||||
title: '文件大小',
|
||||
dataIndex: 'file_size',
|
||||
|
@ -163,7 +68,7 @@ function ResourceVersion({
|
|||
{
|
||||
title: '操作',
|
||||
dataIndex: 'option',
|
||||
width: '100px',
|
||||
width: 160,
|
||||
key: 'option',
|
||||
render: (_: any, record: ResourceFileData) => [
|
||||
<Button
|
||||
|
@ -183,32 +88,9 @@ function ResourceVersion({
|
|||
<div className={styles['resource-version']}>
|
||||
<Flex justify="space-between" align="center" style={{ margin: '30px 0' }}>
|
||||
<Flex align="center">
|
||||
<span style={{ marginRight: '10px' }}>版本号:</span>
|
||||
<Select
|
||||
placeholder="请选择版本号"
|
||||
style={{ width: '160px', marginRight: '20px' }}
|
||||
value={version}
|
||||
onChange={onVersionChange}
|
||||
options={versionList}
|
||||
/>
|
||||
<Button type="default" onClick={showModal} icon={<KFIcon type="icon-xinjian2" />}>
|
||||
创建新版本
|
||||
</Button>
|
||||
</Flex>
|
||||
<Flex align="center">
|
||||
{!isPublic && (
|
||||
<Button
|
||||
type="default"
|
||||
style={{ marginRight: '20px' }}
|
||||
onClick={hanldeDelete}
|
||||
icon={<KFIcon type="icon-shanchu" />}
|
||||
>
|
||||
删除
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
type="default"
|
||||
disabled={!version}
|
||||
disabled={fileList.length === 0}
|
||||
onClick={handleExport}
|
||||
icon={<KFIcon type="icon-xiazai" />}
|
||||
>
|
||||
|
@ -216,12 +98,7 @@ function ResourceVersion({
|
|||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<div style={{ marginBottom: '30px', fontSize: '15px' }}>
|
||||
{fileList.length > 0 && fileList[0].description
|
||||
? '版本描述:' + fileList[0].description
|
||||
: null}
|
||||
</div>
|
||||
<Table columns={columns} dataSource={fileList} pagination={false} rowKey="id" />
|
||||
<Table columns={columns} dataSource={fileList} pagination={false} rowKey="url" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,20 +1,18 @@
|
|||
import KFIcon from '@/components/KFIcon';
|
||||
import { CommonTabKeys } from '@/enums';
|
||||
import {
|
||||
addDatasetVersionDetail,
|
||||
addModelsVersionDetail,
|
||||
addDatasetVersion,
|
||||
addModelVersion,
|
||||
deleteDataset,
|
||||
deleteDatasetVersion,
|
||||
deleteModel,
|
||||
deleteModelVersion,
|
||||
getDatasetById,
|
||||
getDatasetInfo,
|
||||
getDatasetList,
|
||||
getDatasetVersionIdList,
|
||||
getDatasetVersionsById,
|
||||
getModelById,
|
||||
getDatasetVersionList,
|
||||
getModelInfo,
|
||||
getModelList,
|
||||
getModelVersionIdList,
|
||||
getModelVersionsById,
|
||||
getModelVersionList,
|
||||
} from '@/services/dataset/index.js';
|
||||
import type { TabsProps } from 'antd';
|
||||
|
||||
|
@ -26,15 +24,14 @@ export enum ResourceType {
|
|||
type ResourceTypeInfo = {
|
||||
getList: (params: any) => Promise<any>; // 获取资源列表
|
||||
getVersions: (params: any) => Promise<any>; // 获取版本列表
|
||||
getFiles: (params: any) => Promise<any>; // 获取版本下的文件列表
|
||||
deleteRecord: (params: any) => Promise<any>; // 删除
|
||||
addVersion: (params: any) => Promise<any>; // 新增版本
|
||||
deleteVersion: (params: any) => Promise<any>; // 删除版本
|
||||
getInfo: (params: any) => Promise<any>; // 获取详情
|
||||
name: string; // 名称
|
||||
typeParamKey: string; // 类型参数名称,获取资源列表接口使用
|
||||
tagParamKey: string; // 标签参数名称,获取资源列表接口使用
|
||||
fileReqParamKey: 'models_id' | 'dataset_id'; // 文件请求参数名称,获取文件列表接口使用
|
||||
typeParamKey: 'data_type' | 'model_type'; // 类型参数名称,获取资源列表接口使用
|
||||
tagParamKey: 'data_tag' | 'model_tag'; // 标签参数名称,获取资源列表接口使用
|
||||
filePropKey: 'dataset_version_vos' | 'model_version_vos'; // 文件列表属性
|
||||
tabItems: TabsProps['items']; // tab 列表
|
||||
typeTitle: string; // 类型标题
|
||||
tagTitle: string; // 标签标题
|
||||
|
@ -43,28 +40,24 @@ type ResourceTypeInfo = {
|
|||
prefix: string; // 图片资源、详情 url 的前缀
|
||||
deleteModalTitle: string; // 删除弹框的title
|
||||
addBtnTitle: string; // 新增按钮的title
|
||||
idParamKey: 'models_id' | 'dataset_id'; // 新建版本、删除版本接口,版本 id 的参数名称
|
||||
uploadAction: string; // 上传接口 url
|
||||
uploadAccept?: string; // 上传文件类型
|
||||
downloadAllAction: string; // 批量下载接口 url
|
||||
downloadSingleAction: string; // 单个下载接口 url
|
||||
infoTypePropertyName: string; // 详情数据中,类型属性名称
|
||||
infoTagPropertyName: string; // 详情数据中,标签属性名称
|
||||
};
|
||||
|
||||
export const resourceConfig: Record<ResourceType, ResourceTypeInfo> = {
|
||||
[ResourceType.Dataset]: {
|
||||
getList: getDatasetList,
|
||||
getVersions: getDatasetVersionsById,
|
||||
getFiles: getDatasetVersionIdList,
|
||||
getVersions: getDatasetVersionList,
|
||||
deleteRecord: deleteDataset,
|
||||
addVersion: addDatasetVersionDetail,
|
||||
addVersion: addDatasetVersion,
|
||||
deleteVersion: deleteDatasetVersion,
|
||||
getInfo: getDatasetById,
|
||||
getInfo: getDatasetInfo,
|
||||
name: '数据集',
|
||||
typeParamKey: 'data_type',
|
||||
tagParamKey: 'data_tag',
|
||||
fileReqParamKey: 'dataset_id',
|
||||
filePropKey: 'dataset_version_vos',
|
||||
tabItems: [
|
||||
{
|
||||
key: CommonTabKeys.Public,
|
||||
|
@ -84,26 +77,22 @@ export const resourceConfig: Record<ResourceType, ResourceTypeInfo> = {
|
|||
prefix: 'dataset',
|
||||
deleteModalTitle: '确定删除该条数据集实例吗?',
|
||||
addBtnTitle: '新建数据集',
|
||||
idParamKey: 'dataset_id',
|
||||
uploadAction: '/api/mmp/dataset/upload',
|
||||
uploadAction: '/api/mmp/newdataset/upload',
|
||||
uploadAccept: '.zip,.tgz',
|
||||
downloadAllAction: '/api/mmp/dataset/downloadAllFilesl',
|
||||
downloadSingleAction: '/api/mmp/dataset/download',
|
||||
infoTypePropertyName: 'dataset_type_name',
|
||||
infoTagPropertyName: 'dataset_tag_name',
|
||||
downloadAllAction: '/api/mmp/newdataset/downloadAllFiles',
|
||||
downloadSingleAction: '/api/mmp/newdataset/downloadSinggerFile',
|
||||
},
|
||||
[ResourceType.Model]: {
|
||||
getList: getModelList,
|
||||
getVersions: getModelVersionsById,
|
||||
getFiles: getModelVersionIdList,
|
||||
getVersions: getModelVersionList,
|
||||
deleteRecord: deleteModel,
|
||||
addVersion: addModelsVersionDetail,
|
||||
addVersion: addModelVersion,
|
||||
deleteVersion: deleteModelVersion,
|
||||
getInfo: getModelById,
|
||||
getInfo: getModelInfo,
|
||||
name: '模型',
|
||||
typeParamKey: 'model_type',
|
||||
tagParamKey: 'model_tag',
|
||||
fileReqParamKey: 'models_id',
|
||||
filePropKey: 'model_version_vos',
|
||||
tabItems: [
|
||||
{
|
||||
key: CommonTabKeys.Public,
|
||||
|
@ -123,13 +112,10 @@ export const resourceConfig: Record<ResourceType, ResourceTypeInfo> = {
|
|||
prefix: 'model',
|
||||
deleteModalTitle: '确定删除该条模型实例吗?',
|
||||
addBtnTitle: '新建模型',
|
||||
idParamKey: 'models_id',
|
||||
uploadAction: '/api/mmp/models/upload',
|
||||
uploadAction: '/api/mmp/newmodel/upload',
|
||||
uploadAccept: undefined,
|
||||
downloadAllAction: '/api/mmp/models/downloadAllFiles',
|
||||
downloadSingleAction: '/api/mmp/models/download_model',
|
||||
infoTypePropertyName: 'model_type_name',
|
||||
infoTagPropertyName: 'model_tag_name',
|
||||
downloadAllAction: '/api/mmp/newmodel/downloadAllFiles',
|
||||
downloadSingleAction: '/api/mmp/newmodel/downloadSingleFile',
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -141,36 +127,69 @@ export type CategoryData = {
|
|||
path: string;
|
||||
};
|
||||
|
||||
// 资源数据
|
||||
export type ResourceData = {
|
||||
// 数据集、模型列表数据
|
||||
export interface ResourceData {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
create_by: string;
|
||||
update_time: string;
|
||||
available_range: number;
|
||||
model_type_name?: string;
|
||||
model_tag_name?: string;
|
||||
dataset_type_name?: string;
|
||||
dataset_tag_name?: string;
|
||||
};
|
||||
identifier: string;
|
||||
owner: string;
|
||||
version: string;
|
||||
is_public: boolean;
|
||||
description?: string;
|
||||
create_by?: string;
|
||||
update_time?: string;
|
||||
time_ago?: string;
|
||||
version_desc?: string;
|
||||
usage?: string;
|
||||
relative_paths?: string;
|
||||
resourceType: ResourceType.Dataset | ResourceType.Model;
|
||||
}
|
||||
|
||||
// 数据集数据
|
||||
export interface DatasetData extends ResourceData {
|
||||
resourceType: ResourceType.Dataset; // 用于区别类型
|
||||
data_type?: string; // 数据集分类
|
||||
data_tag?: string; // 研究方向
|
||||
processing_code?: string; // 处理代码
|
||||
dataset_source?: string; // 数据来源
|
||||
dataset_version_vos?: ResourceFileData[];
|
||||
}
|
||||
|
||||
// 模型数据
|
||||
export interface ModelData extends ResourceData {
|
||||
resourceType: ResourceType.Model; // 用于区别类型
|
||||
model_type?: string; // 模型框架
|
||||
model_tag?: string; // 模型能力
|
||||
image?: string; // 训练镜像
|
||||
code?: string; // 训练镜像
|
||||
train_datasets?: DatasetData[]; // 训练数据集
|
||||
test_datasets?: DatasetData[]; // 测试数据集
|
||||
params?: Record<string, string>; // 参数
|
||||
metrics?: Record<string, string>; // 指标
|
||||
train_task?: TrainTask; // 训练任务
|
||||
model_source?: string; // 模型来源
|
||||
model_version_vos?: ResourceFileData[];
|
||||
}
|
||||
|
||||
// 版本数据
|
||||
export type ResourceVersionData = {
|
||||
label: string;
|
||||
value: string;
|
||||
name: string;
|
||||
http_url: string;
|
||||
tar_url: string;
|
||||
zip_url: string;
|
||||
};
|
||||
|
||||
// 版本文件数据
|
||||
export type ResourceFileData = {
|
||||
id: number;
|
||||
file_name: string;
|
||||
file_size: string;
|
||||
description: string;
|
||||
create_by: string;
|
||||
create_time: string;
|
||||
update_by: string;
|
||||
update_time: string;
|
||||
url: string;
|
||||
version: string;
|
||||
update_time?: string;
|
||||
};
|
||||
|
||||
// 训练任务
|
||||
export type TrainTask = {
|
||||
ins_id: number;
|
||||
name: string;
|
||||
task_id: string;
|
||||
};
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import ResourceIntro from '@/pages/Dataset/components/ResourceIntro';
|
||||
import ResourceInfo from '@/pages/Dataset/components/ResourceInfo';
|
||||
import { ResourceType } from '@/pages/Dataset/config';
|
||||
|
||||
function DatasetIntro() {
|
||||
return <ResourceIntro resourceType={ResourceType.Dataset} />;
|
||||
function DatasetInfo() {
|
||||
return <ResourceInfo resourceType={ResourceType.Dataset} />;
|
||||
}
|
||||
|
||||
export default DatasetIntro;
|
||||
export default DatasetInfo;
|
||||
|
|
|
@ -8,11 +8,11 @@ import KFRadio, { type KFRadioItem } from '@/components/KFRadio';
|
|||
import PageTitle from '@/components/PageTitle';
|
||||
import ResourceSelect, {
|
||||
requiredValidator,
|
||||
ResourceSelectorType,
|
||||
type ParameterInputObject,
|
||||
} from '@/components/ResourceSelect';
|
||||
import SubAreaTitle from '@/components/SubAreaTitle';
|
||||
import { useComputingResource } from '@/hooks/resource';
|
||||
import { ResourceSelectorType } from '@/pages/Pipeline/components/ResourceSelectorModal';
|
||||
import { createEditorReq } from '@/services/developmentEnvironment';
|
||||
import { to } from '@/utils/promise';
|
||||
import { useNavigate } from '@umijs/max';
|
||||
|
@ -90,7 +90,6 @@ function EditorCreate() {
|
|||
<Form
|
||||
name="editor-create"
|
||||
labelCol={{ flex: '100px' }}
|
||||
wrapperCol={{ flex: 1 }}
|
||||
labelAlign="left"
|
||||
form={form}
|
||||
initialValues={{ computing_resource: ComputingResourceType.GPU }}
|
||||
|
|
|
@ -34,7 +34,7 @@ function ExperimentComparison() {
|
|||
// const [cacheState, setCacheState] = useCacheState();
|
||||
// const [total, setTotal] = useState(0);
|
||||
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
// const [loading, setLoading] = useState(false);
|
||||
const { message } = App.useApp();
|
||||
const config = useMemo(() => comparisonConfig[comparisonType], [comparisonType]);
|
||||
// const [pagination, setPagination] = useState<TablePaginationConfig>(
|
||||
|
@ -50,11 +50,11 @@ function ExperimentComparison() {
|
|||
|
||||
// 获取对比数据列表
|
||||
const getComparisonData = async () => {
|
||||
setLoading(true);
|
||||
// setLoading(true);
|
||||
const request =
|
||||
comparisonType === ComparisonType.Train ? getExpTrainInfosReq : getExpEvaluateInfosReq;
|
||||
const [res] = await to(request(experimentId));
|
||||
setLoading(false);
|
||||
// setLoading(false);
|
||||
if (res && res.data) {
|
||||
// const { content = [], totalElements = 0 } = res.data;
|
||||
setTableData(res.data);
|
||||
|
@ -204,7 +204,7 @@ function ExperimentComparison() {
|
|||
scroll={{ y: 'calc(100% - 55px)', x: '100%' }}
|
||||
pagination={false}
|
||||
bordered={true}
|
||||
loading={loading}
|
||||
// loading={loading}
|
||||
// pagination={{
|
||||
// ...pagination,
|
||||
// total: total,
|
||||
|
|
|
@ -116,6 +116,8 @@ function ExperimentInstanceComponent({
|
|||
<img
|
||||
style={{ width: '17px', marginRight: '7px' }}
|
||||
src={experimentStatusInfo[item.status as ExperimentStatus]?.icon}
|
||||
draggable={false}
|
||||
alt=""
|
||||
/>
|
||||
<span
|
||||
style={{ color: experimentStatusInfo[item.status as ExperimentStatus]?.color }}
|
||||
|
|
|
@ -14,7 +14,12 @@ function ExperimentStatusCell(status?: ExperimentStatus | null) {
|
|||
}
|
||||
return (
|
||||
<div className={styles['experiment-status-cell']}>
|
||||
<img style={{ width: '17px', marginRight: '7px' }} src={statusInfo[status]?.icon} />
|
||||
<img
|
||||
style={{ width: '17px', marginRight: '7px' }}
|
||||
src={statusInfo[status]?.icon}
|
||||
draggable={false}
|
||||
alt=""
|
||||
/>
|
||||
<span
|
||||
style={{ color: statusInfo[status]?.color }}
|
||||
className={styles['experiment-status-cell__label']}
|
||||
|
|
|
@ -2,10 +2,10 @@ import editExperimentIcon from '@/assets/img/edit-experiment.png';
|
|||
import KFModal from '@/components/KFModal';
|
||||
import { type ResourceData } from '@/pages/Dataset/config';
|
||||
import {
|
||||
addModelsVersionDetail,
|
||||
addModelVersion,
|
||||
exportModelReq,
|
||||
getModelList,
|
||||
getModelVersionsById,
|
||||
getModelVersionList,
|
||||
} from '@/services/dataset';
|
||||
import { to } from '@/utils/promise';
|
||||
import { InfoCircleOutlined } from '@ant-design/icons';
|
||||
|
@ -85,7 +85,7 @@ function ExportModelModal({ path, onOk, ...rest }: ExportModelModalProps) {
|
|||
|
||||
// 获取模型版本列表
|
||||
const getModelVersions = async (id: number) => {
|
||||
const [res] = await to(getModelVersionsById(id));
|
||||
const [res] = await to(getModelVersionList(id));
|
||||
if (res && res.data) {
|
||||
setVersions(res.data);
|
||||
}
|
||||
|
@ -118,7 +118,7 @@ function ExportModelModal({ path, onOk, ...rest }: ExportModelModalProps) {
|
|||
|
||||
// 创建模型版本
|
||||
const createModelVersion = async (params: CreateModelVersionParams[]) => {
|
||||
const [res] = await to(addModelsVersionDetail(params));
|
||||
const [res] = await to(addModelVersion(params));
|
||||
if (res) {
|
||||
onOk();
|
||||
}
|
||||
|
|
|
@ -70,6 +70,8 @@ function TensorBoardStatusCell({
|
|||
className={styles['tensorBoard-status__icon']}
|
||||
src={statusConfig[status].icon}
|
||||
onClick={onClick}
|
||||
draggable={false}
|
||||
alt=""
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
|
|
@ -385,6 +385,8 @@ function Experiment() {
|
|||
style={{ width: '17px', marginRight: '6px' }}
|
||||
key={index}
|
||||
src={experimentStatusInfo[item].icon}
|
||||
draggable={false}
|
||||
alt=""
|
||||
/>
|
||||
);
|
||||
})
|
||||
|
|
|
@ -8,6 +8,7 @@ import DateTableCell from '@/components/DateTableCell';
|
|||
import KFIcon from '@/components/KFIcon';
|
||||
import PageTitle from '@/components/PageTitle';
|
||||
import SubAreaTitle from '@/components/SubAreaTitle';
|
||||
import { MirrorVersionStatus } from '@/enums';
|
||||
import { useDomSize } from '@/hooks';
|
||||
import { useCacheState } from '@/hooks/pageCacheState';
|
||||
import {
|
||||
|
@ -36,7 +37,7 @@ import { useEffect, useMemo, useState } from 'react';
|
|||
import MirrorStatusCell from '../components/MirrorStatusCell';
|
||||
import styles from './index.less';
|
||||
|
||||
type MirrorInfoData = {
|
||||
export type MirrorInfoData = {
|
||||
name?: string;
|
||||
description?: string;
|
||||
version_count?: string;
|
||||
|
@ -44,13 +45,14 @@ type MirrorInfoData = {
|
|||
image_type?: number;
|
||||
};
|
||||
|
||||
type MirrorVersionData = {
|
||||
export type MirrorVersionData = {
|
||||
id: number;
|
||||
version: string;
|
||||
url: string;
|
||||
status: string;
|
||||
status: MirrorVersionStatus;
|
||||
file_size: string;
|
||||
create_time: string;
|
||||
tag_name: string;
|
||||
};
|
||||
|
||||
function MirrorInfo() {
|
||||
|
|
|
@ -1,16 +1,11 @@
|
|||
.model-evolution {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-x: hidden;
|
||||
background-color: white;
|
||||
|
||||
&__top {
|
||||
padding: 30px 0;
|
||||
color: @text-color;
|
||||
font-size: @font-size-content;
|
||||
}
|
||||
|
||||
&__graph {
|
||||
height: calc(100% - 92px);
|
||||
height: calc(100%);
|
||||
background-color: @background-color;
|
||||
background-image: url(@/assets/img/pipeline-canvas-bg.png);
|
||||
background-size: 100% 100%;
|
||||
|
|
|
@ -5,15 +5,12 @@
|
|||
*/
|
||||
|
||||
import { useEffectWhen } from '@/hooks';
|
||||
import { ResourceVersionData } from '@/pages/Dataset/config';
|
||||
import { getModelAtlasReq } from '@/services/dataset/index.js';
|
||||
import themes from '@/styles/theme.less';
|
||||
import { to } from '@/utils/promise';
|
||||
import G6, { G6GraphEvent, Graph, INode } from '@antv/g6';
|
||||
// @ts-ignore
|
||||
import { Flex, Select } from 'antd';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import GraphLegend from '../GraphLegend';
|
||||
|
||||
import NodeTooltips from '../NodeTooltips';
|
||||
import styles from './index.less';
|
||||
import type { ModelDepsData, ProjectDependency, TrainDataset } from './utils';
|
||||
|
@ -29,7 +26,7 @@ import {
|
|||
|
||||
type modeModelEvolutionProps = {
|
||||
resourceId: number;
|
||||
versionList: ResourceVersionData[];
|
||||
identifier: string;
|
||||
version?: string;
|
||||
isActive: boolean;
|
||||
onVersionChange: (version: string) => void;
|
||||
|
@ -38,7 +35,7 @@ type modeModelEvolutionProps = {
|
|||
let graph: Graph;
|
||||
function ModelEvolution({
|
||||
resourceId,
|
||||
versionList,
|
||||
identifier,
|
||||
version,
|
||||
isActive,
|
||||
onVersionChange,
|
||||
|
@ -147,14 +144,15 @@ function ModelEvolution({
|
|||
// 更加缩放,调整 tooltip 位置
|
||||
const offsetX = (nodeWidth * zoom) / 4;
|
||||
const offsetY = (nodeHeight * zoom) / 4;
|
||||
point.x += offsetX;
|
||||
|
||||
const canvasWidth = graphRef.current!.clientWidth;
|
||||
if (point.x + 300 > canvasWidth) {
|
||||
point.x = canvasWidth - 300;
|
||||
if (point.x + 300 > canvasWidth + 30) {
|
||||
point.x = canvasWidth + 30 - 300;
|
||||
}
|
||||
|
||||
setHoverNodeData(model);
|
||||
setNodeToolTipX(point.x + offsetX);
|
||||
setNodeToolTipX(point.x);
|
||||
setNodeToolTipY(graphRef.current!.clientHeight - point.y + offsetY);
|
||||
setShowNodeTooltip(true);
|
||||
});
|
||||
|
@ -217,7 +215,8 @@ function ModelEvolution({
|
|||
// 获取模型依赖
|
||||
const getModelAtlas = async () => {
|
||||
const params = {
|
||||
current_model_id: resourceId,
|
||||
id: resourceId,
|
||||
identifier,
|
||||
version,
|
||||
};
|
||||
const [res] = await to(getModelAtlasReq(params));
|
||||
|
@ -249,18 +248,6 @@ function ModelEvolution({
|
|||
|
||||
return (
|
||||
<div className={styles['model-evolution']}>
|
||||
<Flex align="center" className={styles['model-evolution__top']}>
|
||||
<span style={{ marginRight: '10px' }}>版本号:</span>
|
||||
<Select
|
||||
placeholder="请选择版本号"
|
||||
style={{ width: '160px', marginRight: '20px' }}
|
||||
value={version}
|
||||
allowClear
|
||||
onChange={onVersionChange}
|
||||
options={versionList}
|
||||
/>
|
||||
<GraphLegend style={{ marginRight: 0, marginLeft: 'auto' }}></GraphLegend>
|
||||
</Flex>
|
||||
<div className={styles['model-evolution__graph']} id="canvas" ref={graphRef}></div>
|
||||
{(showNodeTooltip || enterTooltip) && (
|
||||
<NodeTooltips
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { TrainTask } from '@/pages/Dataset/config';
|
||||
import { changePropertyName, fittingString } from '@/utils';
|
||||
import { EdgeConfig, GraphData, LayoutConfig, NodeConfig, TreeGraphData, Util } from '@antv/g6';
|
||||
// @ts-ignore
|
||||
|
@ -31,16 +32,12 @@ export type Rect = {
|
|||
height: number;
|
||||
};
|
||||
|
||||
export type TrainTask = {
|
||||
ins_id: number;
|
||||
name: string;
|
||||
task_id: string;
|
||||
};
|
||||
|
||||
export interface TrainDataset extends NodeConfig {
|
||||
dataset_id: number;
|
||||
dataset_name: string;
|
||||
dataset_version: string;
|
||||
repo_id: number;
|
||||
name: string;
|
||||
version: string;
|
||||
identifier: string;
|
||||
owner: string;
|
||||
model_type: NodeType.TestDataset | NodeType.TrainDataset;
|
||||
}
|
||||
|
||||
|
@ -51,34 +48,33 @@ export interface ProjectDependency extends NodeConfig {
|
|||
model_type: NodeType.Project;
|
||||
}
|
||||
|
||||
export type ModalDetail = {
|
||||
export type ModelMeta = {
|
||||
train_datasets?: TrainDataset[];
|
||||
test_datasets?: TrainDataset[];
|
||||
project_depency?: ProjectDependency;
|
||||
train_task?: TrainTask;
|
||||
name: string;
|
||||
available_range: number;
|
||||
file_name: string;
|
||||
file_size: string;
|
||||
description: string;
|
||||
model_type_name: string;
|
||||
model_tag_name: string;
|
||||
version: string;
|
||||
model_source: string;
|
||||
model_type: string;
|
||||
create_time: string;
|
||||
file_size: string;
|
||||
is_public: boolean;
|
||||
};
|
||||
|
||||
export interface ModelDepsAPIData {
|
||||
current_model_id: number;
|
||||
repo_id: number;
|
||||
model_name: string;
|
||||
version: string;
|
||||
workflow_id: number;
|
||||
exp_ins_id: number;
|
||||
model_type: NodeType.Children | NodeType.Current | NodeType.Parent;
|
||||
current_model_name: string;
|
||||
project_dependency?: ProjectDependency;
|
||||
test_dataset: TrainDataset[];
|
||||
train_dataset: TrainDataset[];
|
||||
train_task: TrainTask;
|
||||
model_version_dependcy_vo: ModalDetail;
|
||||
children_models: ModelDepsAPIData[];
|
||||
parent_models: ModelDepsAPIData[];
|
||||
model_meta: ModelMeta;
|
||||
child_model_list: ModelDepsAPIData[];
|
||||
parent_model_vo?: ModelDepsAPIData;
|
||||
}
|
||||
|
||||
export interface ModelDepsData extends Omit<ModelDepsAPIData, 'children_models'>, TreeGraphData {
|
||||
export interface ModelDepsData extends Omit<ModelDepsAPIData, 'child_model_list'>, TreeGraphData {
|
||||
children: ModelDepsData[];
|
||||
expanded: boolean; // 是否展开
|
||||
level: number; // 层级,从 0 开始
|
||||
|
@ -92,8 +88,11 @@ export function normalizeChildren(data: ModelDepsData[]) {
|
|||
item.model_type = NodeType.Children;
|
||||
item.expanded = false;
|
||||
item.level = 0;
|
||||
item.datasetLen = item.train_dataset.length + item.test_dataset.length;
|
||||
item.id = `$M_${item.current_model_id}_${item.version}`;
|
||||
item.datasetLen = getDatasetLen(
|
||||
item.model_meta.train_datasets,
|
||||
item.model_meta.test_datasets,
|
||||
);
|
||||
item.id = `$M_${item.repo_id}_${item.version}`;
|
||||
item.label = getLabel(item);
|
||||
item.style = getStyle(NodeType.Children);
|
||||
normalizeChildren(item.children);
|
||||
|
@ -104,16 +103,17 @@ export function normalizeChildren(data: ModelDepsData[]) {
|
|||
// 获取 label
|
||||
export function getLabel(node: ModelDepsData | ModelDepsAPIData) {
|
||||
return (
|
||||
fittingString(
|
||||
`${node.model_version_dependcy_vo.name ?? ''}`,
|
||||
nodeWidth - labelPadding,
|
||||
nodeFontSize,
|
||||
) +
|
||||
fittingString(`${node.model_name ?? ''}`, nodeWidth - labelPadding, nodeFontSize) +
|
||||
'\n' +
|
||||
fittingString(`${node.version}`, nodeWidth - labelPadding, nodeFontSize)
|
||||
);
|
||||
}
|
||||
|
||||
// 获取数据集数量
|
||||
export function getDatasetLen(train?: TrainDataset[], test?: TrainDataset[]) {
|
||||
return (train?.length || 0) + (test?.length || 0);
|
||||
}
|
||||
|
||||
// 获取 style
|
||||
export function getStyle(model_type: NodeType) {
|
||||
let fill = '';
|
||||
|
@ -148,41 +148,43 @@ export function getStyle(model_type: NodeType) {
|
|||
export function normalizeTreeData(apiData: ModelDepsAPIData): ModelDepsData {
|
||||
// 将 children_models 转换成 children
|
||||
let normalizedData = changePropertyName(apiData, {
|
||||
children_models: 'children',
|
||||
child_model_list: 'children',
|
||||
}) as ModelDepsData;
|
||||
|
||||
// 设置当前模型的数据
|
||||
normalizedData.model_type = NodeType.Current;
|
||||
normalizedData.id = `$M_${normalizedData.current_model_id}_${normalizedData.version}`;
|
||||
normalizedData.id = `$M_${normalizedData.repo_id}_${normalizedData.version}`;
|
||||
normalizedData.label = getLabel(normalizedData);
|
||||
normalizedData.style = getStyle(NodeType.Current);
|
||||
normalizedData.expanded = true;
|
||||
normalizedData.datasetLen =
|
||||
normalizedData.train_dataset.length + normalizedData.test_dataset.length;
|
||||
normalizedData.datasetLen = getDatasetLen(
|
||||
normalizedData.model_meta.train_datasets,
|
||||
normalizedData.model_meta.test_datasets,
|
||||
);
|
||||
normalizeChildren(normalizedData.children as ModelDepsData[]);
|
||||
normalizedData.level = 0;
|
||||
|
||||
// 将 parent_models 转换成树形结构
|
||||
let parent_models = normalizedData.parent_models || [];
|
||||
while (parent_models.length > 0) {
|
||||
const parent = parent_models[0];
|
||||
let parent_model = normalizedData.parent_model_vo;
|
||||
while (parent_model) {
|
||||
const parent = parent_model;
|
||||
normalizedData = {
|
||||
...parent,
|
||||
expanded: false,
|
||||
level: 0,
|
||||
datasetLen: parent.train_dataset.length + parent.test_dataset.length,
|
||||
datasetLen: getDatasetLen(parent.model_meta.train_datasets, parent.model_meta.test_datasets),
|
||||
model_type: NodeType.Parent,
|
||||
id: `$M_${parent.current_model_id}_${parent.version}`,
|
||||
id: `$M_${parent.repo_id}_${parent.version}`,
|
||||
label: getLabel(parent),
|
||||
style: getStyle(NodeType.Parent),
|
||||
children: [
|
||||
{
|
||||
...normalizedData,
|
||||
parent_models: [],
|
||||
parent_model: null,
|
||||
},
|
||||
],
|
||||
};
|
||||
parent_models = normalizedData.parent_models || [];
|
||||
parent_model = normalizedData.parent_model_vo;
|
||||
}
|
||||
return normalizedData;
|
||||
}
|
||||
|
@ -195,11 +197,12 @@ export function getGraphData(data: ModelDepsData, hierarchyNodes: ModelDepsData[
|
|||
getWidth: () => nodeWidth,
|
||||
getVGap: (node: NodeConfig) => {
|
||||
const model = node as ModelDepsData;
|
||||
const { model_type, expanded, project_dependency } = model;
|
||||
const { model_type, expanded, model_meta } = model;
|
||||
const { project_depency } = model_meta;
|
||||
if (model_type === NodeType.Current || model_type === NodeType.Parent) {
|
||||
return vGap / 2;
|
||||
}
|
||||
const selfGap = expanded && project_dependency?.url ? nodeHeight + vGap : 0;
|
||||
const selfGap = expanded && project_depency?.url ? nodeHeight + vGap : 0;
|
||||
const nextNode = getSameHierarchyNextNode(model, hierarchyNodes);
|
||||
if (!nextNode) {
|
||||
return vGap / 2;
|
||||
|
@ -254,28 +257,35 @@ const addDatasetDependency = (
|
|||
nodes: NodeConfig[],
|
||||
edges: EdgeConfig[],
|
||||
) => {
|
||||
const { train_dataset, test_dataset, id } = data;
|
||||
train_dataset.forEach((item) => {
|
||||
item.id = `$DTrain_${id}_${item.dataset_id}_${item.dataset_version}`;
|
||||
const { repo_id, model_meta } = data;
|
||||
const { train_datasets, test_datasets } = model_meta;
|
||||
train_datasets?.forEach((item) => {
|
||||
if (!item.repo_id) {
|
||||
item.repo_id = item.id;
|
||||
}
|
||||
item.id = `$DTrain_${repo_id}_${item.repo_id}_${item.version}`;
|
||||
item.model_type = NodeType.TrainDataset;
|
||||
item.style = getStyle(NodeType.TrainDataset);
|
||||
});
|
||||
test_dataset.forEach((item) => {
|
||||
item.id = `$DTest_${id}_${item.dataset_id}_${item.dataset_version}`;
|
||||
test_datasets?.forEach((item) => {
|
||||
if (!item.repo_id) {
|
||||
item.repo_id = item.id;
|
||||
}
|
||||
item.id = `$DTest_${repo_id}_${item.repo_id}_${item.version}`;
|
||||
item.model_type = NodeType.TestDataset;
|
||||
item.style = getStyle(NodeType.TestDataset);
|
||||
});
|
||||
|
||||
datasetNodes.length = 0;
|
||||
const len = train_dataset.length + test_dataset.length;
|
||||
[...train_dataset, ...test_dataset].forEach((item, index) => {
|
||||
const len = getDatasetLen(train_datasets, test_datasets);
|
||||
[...(train_datasets ?? []), ...(test_datasets ?? [])].forEach((item, index) => {
|
||||
const node = { ...item };
|
||||
node.type = 'ellipse';
|
||||
node.size = [ellipseWidth, nodeHeight];
|
||||
node.label =
|
||||
fittingString(node.dataset_name, ellipseWidth - labelPadding, nodeFontSize) +
|
||||
fittingString(node.name, ellipseWidth - labelPadding, nodeFontSize) +
|
||||
'\n' +
|
||||
fittingString(node.dataset_version, ellipseWidth - labelPadding, nodeFontSize);
|
||||
fittingString(node.version, ellipseWidth - labelPadding, nodeFontSize);
|
||||
|
||||
const half = len / 2 - 0.5;
|
||||
node.x = currentNode.x! - (half - index) * (ellipseWidth + datasetHGap);
|
||||
|
@ -299,10 +309,11 @@ const addProjectDependency = (
|
|||
nodes: NodeConfig[],
|
||||
edges: EdgeConfig[],
|
||||
) => {
|
||||
const { project_dependency, id } = data;
|
||||
if (project_dependency?.url) {
|
||||
const node = { ...project_dependency };
|
||||
node.id = `$P_${id}_${node.url}_${node.branch}`;
|
||||
const { repo_id, model_meta } = data;
|
||||
const { project_depency } = model_meta;
|
||||
if (project_depency?.url) {
|
||||
const node = { ...project_depency };
|
||||
node.id = `$P_${repo_id}_${node.url}_${node.branch}`;
|
||||
node.model_type = NodeType.Project;
|
||||
node.type = 'rect';
|
||||
node.label = fittingString(node.name, nodeWidth - labelPadding, nodeFontSize);
|
||||
|
@ -322,6 +333,7 @@ const addProjectDependency = (
|
|||
}
|
||||
};
|
||||
|
||||
/*
|
||||
// 判断两个矩形是否相交
|
||||
function isRectanglesOverlap(rect1: Rect, rect2: Rect) {
|
||||
const a2x = rect1.x + rect1.width / 2;
|
||||
|
@ -366,6 +378,7 @@ function adjustDatasetPosition(node: NodeConfig) {
|
|||
});
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// 层级遍历树结构
|
||||
export function traverseHierarchically(data: ModelDepsData | undefined): ModelDepsData[] {
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
position: absolute;
|
||||
bottom: -100px;
|
||||
left: -300px;
|
||||
z-index: 10;
|
||||
width: 300px;
|
||||
padding: 10px;
|
||||
background: white;
|
||||
|
@ -50,6 +51,7 @@
|
|||
flex: 1;
|
||||
min-width: 0;
|
||||
font-weight: 500;
|
||||
word-break: break-all;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline @underline-color;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { ResourceInfoTabKeys } from '@/pages/Dataset/components/ResourceIntro';
|
||||
import { ResourceInfoTabKeys } from '@/pages/Dataset/components/ResourceInfo';
|
||||
import { formatDate } from '@/utils/date';
|
||||
import { useNavigate } from '@umijs/max';
|
||||
import { ModelDepsData, NodeType, ProjectDependency, TrainDataset } from '../ModelEvolution/utils';
|
||||
|
@ -14,9 +14,9 @@ function ModelInfo({ resourceId, data, onVersionChange }: ModelInfoProps) {
|
|||
const navigate = useNavigate();
|
||||
|
||||
const gotoExperimentPage = () => {
|
||||
if (data.train_task?.ins_id) {
|
||||
if (data.model_meta.train_task?.ins_id) {
|
||||
const { origin } = location;
|
||||
const url = `${origin}/pipeline/experiment/instance/${data.workflow_id}/${data.train_task.ins_id}`;
|
||||
const url = `${origin}/pipeline/experiment/instance/${data.model_meta.train_task.task_id}/${data.model_meta.train_task.ins_id}`;
|
||||
window.open(url, '_blank');
|
||||
}
|
||||
};
|
||||
|
@ -25,10 +25,10 @@ function ModelInfo({ resourceId, data, onVersionChange }: ModelInfoProps) {
|
|||
if (data.model_type === NodeType.Current) {
|
||||
return;
|
||||
}
|
||||
if (data.current_model_id === resourceId) {
|
||||
if (data.repo_id === resourceId) {
|
||||
onVersionChange?.(data.version);
|
||||
} else {
|
||||
const path = `/dataset/model/info/${data.current_model_id}?tab=${ResourceInfoTabKeys.Evolution}&version=${data.version}`;
|
||||
const path = `/dataset/model/info/${data.repo_id}?tab=${ResourceInfoTabKeys.Evolution}&version=${data.version}&name=${data.model_name}&owner=${data.owner}&identifier=${data.identifier}`;
|
||||
navigate(path);
|
||||
}
|
||||
};
|
||||
|
@ -40,12 +40,10 @@ function ModelInfo({ resourceId, data, onVersionChange }: ModelInfoProps) {
|
|||
<div className={styles['node-tooltips__row']}>
|
||||
<span className={styles['node-tooltips__row__title']}>模型名称:</span>
|
||||
{data.model_type === NodeType.Current ? (
|
||||
<span className={styles['node-tooltips__row__value']}>
|
||||
{data.model_version_dependcy_vo?.name || '--'}
|
||||
</span>
|
||||
<span className={styles['node-tooltips__row__value']}>{data.model_name || '--'}</span>
|
||||
) : (
|
||||
<ValueLink
|
||||
value={data.model_version_dependcy_vo?.name}
|
||||
value={data.model_name}
|
||||
className={styles['node-tooltips__row__link']}
|
||||
nullClassName={styles['node-tooltips__row__value']}
|
||||
onClick={gotoModelPage}
|
||||
|
@ -59,25 +57,25 @@ function ModelInfo({ resourceId, data, onVersionChange }: ModelInfoProps) {
|
|||
<div className={styles['node-tooltips__row']}>
|
||||
<span className={styles['node-tooltips__row__title']}>模型框架:</span>
|
||||
<span className={styles['node-tooltips__row__value']}>
|
||||
{data.model_version_dependcy_vo?.model_type_name || '--'}
|
||||
{data.model_meta.model_type || '--'}
|
||||
</span>
|
||||
</div>
|
||||
<div className={styles['node-tooltips__row']}>
|
||||
<span className={styles['node-tooltips__row__title']}>模型大小:</span>
|
||||
<span className={styles['node-tooltips__row__value']}>
|
||||
{data.model_version_dependcy_vo?.file_size || '--'}
|
||||
{data.model_meta.file_size || '--'}
|
||||
</span>
|
||||
</div>
|
||||
<div className={styles['node-tooltips__row']}>
|
||||
<span className={styles['node-tooltips__row__title']}>创建时间:</span>
|
||||
<span className={styles['node-tooltips__row__value']}>
|
||||
{formatDate(data.model_version_dependcy_vo?.create_time)}
|
||||
{formatDate(data.model_meta.create_time || '--')}
|
||||
</span>
|
||||
</div>
|
||||
<div className={styles['node-tooltips__row']}>
|
||||
<span className={styles['node-tooltips__row__title']}>模型权限:</span>
|
||||
<span className={styles['node-tooltips__row__value']}>
|
||||
{data.model_version_dependcy_vo?.available_range === 1 ? '公开' : '私有'}
|
||||
{data.model_meta.is_public ? '公开' : '私有'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -86,7 +84,7 @@ function ModelInfo({ resourceId, data, onVersionChange }: ModelInfoProps) {
|
|||
<div className={styles['node-tooltips__row']}>
|
||||
<span className={styles['node-tooltips__row__title']}>训练任务:</span>
|
||||
<ValueLink
|
||||
value={data.train_task?.name}
|
||||
value={data.model_meta.train_task?.name}
|
||||
className={styles['node-tooltips__row__link']}
|
||||
nullClassName={styles['node-tooltips__row__value']}
|
||||
onClick={gotoExperimentPage}
|
||||
|
@ -100,7 +98,7 @@ function ModelInfo({ resourceId, data, onVersionChange }: ModelInfoProps) {
|
|||
function DatasetInfo({ data }: { data: TrainDataset }) {
|
||||
const gotoDatasetPage = () => {
|
||||
const { origin } = location;
|
||||
const url = `${origin}/dataset/dataset/info/${data.dataset_id}?tab=${ResourceInfoTabKeys.Version}&version=${data.dataset_version}`;
|
||||
const url = `${origin}/dataset/dataset/info/${data.repo_id}?tab=${ResourceInfoTabKeys.Version}&version=${data.version}&name=${data.name}&owner=${data.owner}&identifier=${data.identifier}`;
|
||||
window.open(url, '_blank');
|
||||
};
|
||||
|
||||
|
@ -111,7 +109,7 @@ function DatasetInfo({ data }: { data: TrainDataset }) {
|
|||
<div className={styles['node-tooltips__row']}>
|
||||
<span className={styles['node-tooltips__row__title']}>数据集名称:</span>
|
||||
<ValueLink
|
||||
value={data.dataset_name}
|
||||
value={data.name}
|
||||
className={styles['node-tooltips__row__link']}
|
||||
nullClassName={styles['node-tooltips__row__value']}
|
||||
onClick={gotoDatasetPage}
|
||||
|
@ -119,9 +117,7 @@ function DatasetInfo({ data }: { data: TrainDataset }) {
|
|||
</div>
|
||||
<div className={styles['node-tooltips__row']}>
|
||||
<span className={styles['node-tooltips__row__title']}>数据集版本:</span>
|
||||
<span className={styles['node-tooltips__row__value']}>
|
||||
{data.dataset_version || '--'}
|
||||
</span>
|
||||
<span className={styles['node-tooltips__row__value']}>{data.version || '--'}</span>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import ResourceIntro from '@/pages/Dataset/components/ResourceIntro';
|
||||
import ResourceInfo from '@/pages/Dataset/components/ResourceInfo';
|
||||
import { ResourceType } from '@/pages/Dataset/config';
|
||||
|
||||
function ModelIntro() {
|
||||
return <ResourceIntro resourceType={ResourceType.Model} />;
|
||||
function ModelInfo() {
|
||||
return <ResourceInfo resourceType={ResourceType.Model} />;
|
||||
}
|
||||
|
||||
export default ModelIntro;
|
||||
export default ModelInfo;
|
||||
|
|
|
@ -7,12 +7,12 @@ import KFIcon from '@/components/KFIcon';
|
|||
import PageTitle from '@/components/PageTitle';
|
||||
import ResourceSelect, {
|
||||
requiredValidator,
|
||||
ResourceSelectorType,
|
||||
type ParameterInputObject,
|
||||
} from '@/components/ResourceSelect';
|
||||
import SubAreaTitle from '@/components/SubAreaTitle';
|
||||
import { CommonTabKeys } from '@/enums';
|
||||
import { useComputingResource } from '@/hooks/resource';
|
||||
import { ResourceSelectorType } from '@/pages/Pipeline/components/ResourceSelectorModal';
|
||||
import {
|
||||
createModelDeploymentReq,
|
||||
restartModelDeploymentReq,
|
||||
|
|
|
@ -67,7 +67,7 @@ function CodeSelectorModal({ onOk, ...rest }: CodeSelectorModalProps) {
|
|||
<KFModal
|
||||
{...rest}
|
||||
title="选择代码配置"
|
||||
image={require('@/assets/img/edit-experiment.png')}
|
||||
image={require('@/assets/img/modal-code-config.png')}
|
||||
width={920}
|
||||
footer={null}
|
||||
destroyOnClose
|
||||
|
|
|
@ -48,6 +48,7 @@ const ModelMenu = ({ onComponentDragEnd }: ModelMenuProps) => {
|
|||
<img
|
||||
style={{ height: '16px', marginRight: '15px' }}
|
||||
src={`/assets/images/${ele.icon_path}.png`}
|
||||
draggable={false}
|
||||
alt=""
|
||||
/>
|
||||
)}
|
||||
|
|
|
@ -204,11 +204,14 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
|
|||
});
|
||||
}
|
||||
} else {
|
||||
const { activeTab, id, name, version, path } = res;
|
||||
const { activeTab, id, name, version, path, identifier, owner } = res;
|
||||
const value = JSON.stringify({
|
||||
id,
|
||||
name,
|
||||
version,
|
||||
path,
|
||||
identifier,
|
||||
owner,
|
||||
});
|
||||
const showValue = `${name}:${version}`;
|
||||
form.setFieldValue(formItemName, {
|
||||
|
@ -245,6 +248,10 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
|
|||
// 获取选择数据集、模型后面按钮 icon
|
||||
const getSelectBtnIcon = (item: { item_type: string }) => {
|
||||
const type = item.item_type;
|
||||
if (type === 'code') {
|
||||
return <KFIcon type="icon-xuanzedaimapeizhi" />;
|
||||
}
|
||||
|
||||
let selectorType: ResourceSelectorType;
|
||||
if (type === 'dataset') {
|
||||
selectorType = ResourceSelectorType.Dataset;
|
||||
|
|
|
@ -1,17 +1,21 @@
|
|||
import datasetImg from '@/assets/img/modal-select-dataset.png';
|
||||
import mirrorImg from '@/assets/img/modal-select-mirror.png';
|
||||
import modelImg from '@/assets/img/modal-select-model.png';
|
||||
import { CommonTabKeys, MirrorVersionStatus } from '@/enums';
|
||||
import { AvailableRange, CommonTabKeys } from '@/enums';
|
||||
import { ResourceData, ResourceVersionData } from '@/pages/Dataset/config';
|
||||
import { MirrorVersionData } from '@/pages/Mirror/Info';
|
||||
import { MirrorData } from '@/pages/Mirror/List';
|
||||
import {
|
||||
getDatasetInfo,
|
||||
getDatasetList,
|
||||
getDatasetVersionIdList,
|
||||
getDatasetVersionsById,
|
||||
getDatasetVersionList,
|
||||
getModelInfo,
|
||||
getModelList,
|
||||
getModelVersionIdList,
|
||||
getModelVersionsById,
|
||||
getModelVersionList,
|
||||
} from '@/services/dataset/index.js';
|
||||
import { getMirrorListReq, getMirrorVersionListReq } from '@/services/mirror';
|
||||
import type { TabsProps } from 'antd';
|
||||
import type { TabsProps, TreeDataNode } from 'antd';
|
||||
import { pick } from 'lodash';
|
||||
|
||||
export enum ResourceSelectorType {
|
||||
Model = 'Model', // 模型
|
||||
|
@ -19,111 +23,229 @@ export enum ResourceSelectorType {
|
|||
Mirror = 'Mirror', //镜像
|
||||
}
|
||||
|
||||
export type MirrorVersion = {
|
||||
id: number; // 镜像版本 id
|
||||
status: MirrorVersionStatus; // 镜像版本状态
|
||||
tag_name: string; // 镜像版本 name
|
||||
url: string; // 镜像版本路径
|
||||
// 数据集、模型列表转为树形结构
|
||||
const convertDatasetToTreeData = (list: ResourceData[]): TreeDataNode[] => {
|
||||
return list.map((v) => ({
|
||||
...v,
|
||||
key: `${v.id}`,
|
||||
title: v.name,
|
||||
isLeaf: false,
|
||||
checkable: false,
|
||||
}));
|
||||
};
|
||||
|
||||
export type SelectorTypeInfo = {
|
||||
getList: (params: any) => Promise<any>; // 获取资源列表
|
||||
getVersions: (params: any) => Promise<any>; // 获取资源版本列表
|
||||
getFiles: (params: any) => Promise<any>; // 获取资源文件列表
|
||||
handleVersionResponse: (res: any) => any[]; // 处理版本列表接口数据
|
||||
modalIcon: string; // modal icon
|
||||
buttonIcon: string; // button icon
|
||||
name: string; // 名称
|
||||
litReqParamKey: 'available_range' | 'image_type'; // 表示是公开还是私有的参数名称,获取资源列表接口使用
|
||||
fileReqParamKey: 'models_id' | 'dataset_id'; // 文件请求参数名称,获取文件列表接口使用
|
||||
tabItems: TabsProps['items']; // tab 列表
|
||||
buttontTitle: string; // 按钮 title
|
||||
// 镜像列表转为树形结构
|
||||
const convertMirrorToTreeData = (list: MirrorData[]): TreeDataNode[] => {
|
||||
return list.map((v) => ({
|
||||
key: `${v.id}`,
|
||||
title: v.name,
|
||||
isLeaf: false,
|
||||
checkable: false,
|
||||
}));
|
||||
};
|
||||
|
||||
// 获取镜像文件列表,为了兼容数据集和模型
|
||||
const getMirrorFilesReq = ({ id, version }: { id: number; version: string }): Promise<any> => {
|
||||
const index = version.indexOf('-');
|
||||
const url = version.slice(index + 1);
|
||||
return Promise.resolve({
|
||||
data: {
|
||||
// 数据集版本列表转为树形结构
|
||||
const convertDatasetVersionToTreeData = (
|
||||
parentId: string,
|
||||
info: ResourceData,
|
||||
list: ResourceVersionData[],
|
||||
): TreeDataNode[] => {
|
||||
return list.map((item: ResourceVersionData) => ({
|
||||
...pick(info, ['id', 'name', 'owner', 'identifier']),
|
||||
version: item.name,
|
||||
title: item.name,
|
||||
key: `${parentId}-${item.name}`,
|
||||
isLeaf: true,
|
||||
checkable: true,
|
||||
}));
|
||||
};
|
||||
|
||||
// 镜像版本列表转为树形结构
|
||||
const convertMirrorVersionToTreeData = (
|
||||
parentId: string,
|
||||
list: MirrorVersionData[],
|
||||
): TreeDataNode[] => {
|
||||
return list.map((item: MirrorVersionData) => ({
|
||||
url: item.url,
|
||||
title: item.tag_name,
|
||||
key: `${parentId}-${item.id}`,
|
||||
isLeaf: true,
|
||||
checkable: true,
|
||||
}));
|
||||
};
|
||||
|
||||
interface SelectorTypeInfo {
|
||||
getList: (isPublic: boolean) => Promise<any>; // 获取资源列表
|
||||
getVersions: (parentKey: string, parentNode: any) => Promise<any>; // 获取资源版本列表
|
||||
getFiles: (parentKey: string, parentNode: any) => Promise<any>; // 获取资源文件列表
|
||||
readonly modalIcon: string; // modal icon
|
||||
readonly buttonIcon: string; // button icon
|
||||
readonly name: string; // 名称
|
||||
readonly tabItems: TabsProps['items']; // tab 列表
|
||||
readonly buttontTitle: string; // 按钮 title
|
||||
}
|
||||
|
||||
export class DatasetSelector implements SelectorTypeInfo {
|
||||
readonly name = '数据集';
|
||||
readonly modalIcon = datasetImg;
|
||||
readonly buttonIcon = 'icon-xuanzeshujuji';
|
||||
readonly tabItems = [
|
||||
{
|
||||
key: CommonTabKeys.Private,
|
||||
label: '我的数据集',
|
||||
},
|
||||
{
|
||||
key: CommonTabKeys.Public,
|
||||
label: '公开数据集',
|
||||
},
|
||||
];
|
||||
readonly buttontTitle = '选择数据集';
|
||||
|
||||
async getList(isPublic: boolean) {
|
||||
const res = await getDatasetList({ is_public: isPublic, page: 0, size: 2000 });
|
||||
if (res && res.data) {
|
||||
const list = res.data.content || [];
|
||||
return convertDatasetToTreeData(list);
|
||||
} else {
|
||||
return Promise.reject('获取数据集列表失败');
|
||||
}
|
||||
}
|
||||
async getVersions(parentKey: string, parentNode: ResourceData) {
|
||||
const res = await getDatasetVersionList(pick(parentNode, ['owner', 'identifier']));
|
||||
if (res && res.data) {
|
||||
const list = res.data;
|
||||
return convertDatasetVersionToTreeData(parentKey, parentNode, list);
|
||||
} else {
|
||||
return Promise.reject('获取数据集版本列表失败');
|
||||
}
|
||||
}
|
||||
|
||||
async getFiles(_parentKey: string, parentNode: ResourceData & ResourceVersionData) {
|
||||
const params = pick(parentNode, ['owner', 'identifier', 'id', 'name', 'version']);
|
||||
const res = await getDatasetInfo(params);
|
||||
if (res && res.data) {
|
||||
const path = res.data.relative_paths || '';
|
||||
const list = res.data.dataset_version_vos || [];
|
||||
return {
|
||||
path,
|
||||
content: list,
|
||||
};
|
||||
} else {
|
||||
return Promise.reject('获取数据集文件列表失败');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class ModelSelector implements SelectorTypeInfo {
|
||||
readonly name = '模型';
|
||||
readonly modalIcon = modelImg;
|
||||
readonly buttonIcon = 'icon-xuanzemoxing';
|
||||
readonly tabItems = [
|
||||
{
|
||||
key: CommonTabKeys.Private,
|
||||
label: '我的模型',
|
||||
},
|
||||
{
|
||||
key: CommonTabKeys.Public,
|
||||
label: '公开模型',
|
||||
},
|
||||
];
|
||||
readonly buttontTitle = '选择模型';
|
||||
|
||||
async getList(isPublic: boolean) {
|
||||
const res = await getModelList({ is_public: isPublic, page: 0, size: 2000 });
|
||||
if (res && res.data) {
|
||||
const list = res.data.content || [];
|
||||
return convertDatasetToTreeData(list);
|
||||
} else {
|
||||
return Promise.reject('获取模型列表失败');
|
||||
}
|
||||
}
|
||||
async getVersions(key: string, parentNode: ResourceData) {
|
||||
const res = await getModelVersionList(pick(parentNode, ['owner', 'identifier']));
|
||||
if (res && res.data) {
|
||||
const list = res.data;
|
||||
return convertDatasetVersionToTreeData(key, parentNode, list);
|
||||
} else {
|
||||
return Promise.reject('获取模型版本列表失败');
|
||||
}
|
||||
}
|
||||
|
||||
async getFiles(_parentKey: string, parentNode: ResourceData & ResourceVersionData) {
|
||||
const params = pick(parentNode, ['owner', 'identifier', 'id', 'name', 'version']);
|
||||
const res = await getModelInfo(params);
|
||||
if (res && res.data) {
|
||||
const path = res.data.relative_paths || '';
|
||||
const list = res.data.model_version_vos || [];
|
||||
return {
|
||||
path,
|
||||
content: list,
|
||||
};
|
||||
} else {
|
||||
return Promise.reject('获取模型文件列表失败');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class MirrorSelector implements SelectorTypeInfo {
|
||||
readonly name = '镜像';
|
||||
readonly modalIcon = mirrorImg;
|
||||
readonly buttonIcon = 'icon-xuanzejingxiang';
|
||||
readonly tabItems = [
|
||||
{
|
||||
key: CommonTabKeys.Private,
|
||||
label: '我的镜像',
|
||||
},
|
||||
{
|
||||
key: CommonTabKeys.Public,
|
||||
label: '公开镜像',
|
||||
},
|
||||
];
|
||||
readonly buttontTitle = '选择镜像';
|
||||
|
||||
async getList(isPublic: boolean) {
|
||||
const res = await getMirrorListReq({
|
||||
image_type: isPublic ? AvailableRange.Public : AvailableRange.Private,
|
||||
page: 0,
|
||||
size: 2000,
|
||||
});
|
||||
if (res && res.data) {
|
||||
const list = res.data.content || [];
|
||||
return convertMirrorToTreeData(list);
|
||||
} else {
|
||||
return Promise.reject('获取镜像列表失败');
|
||||
}
|
||||
}
|
||||
async getVersions(parentKey: string) {
|
||||
const res = await getMirrorVersionListReq({
|
||||
image_id: parentKey,
|
||||
page: 0,
|
||||
size: 2000,
|
||||
});
|
||||
if (res && res.data) {
|
||||
const list = res.data.content || [];
|
||||
return convertMirrorVersionToTreeData(parentKey, list);
|
||||
} else {
|
||||
return Promise.reject('获取镜像版本列表失败');
|
||||
}
|
||||
}
|
||||
|
||||
async getFiles(_parentKey: string, parentNode: MirrorVersionData) {
|
||||
const { url } = parentNode;
|
||||
return {
|
||||
path: url,
|
||||
content: [
|
||||
{
|
||||
id: `${id}-${version}`,
|
||||
url: url,
|
||||
file_name: `${url}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const selectorTypeConfig: Record<ResourceSelectorType, SelectorTypeInfo> = {
|
||||
[ResourceSelectorType.Model]: {
|
||||
getList: getModelList,
|
||||
getVersions: getModelVersionsById,
|
||||
getFiles: getModelVersionIdList,
|
||||
handleVersionResponse: (res) => res.data || [],
|
||||
name: '模型',
|
||||
modalIcon: modelImg,
|
||||
buttonIcon: 'icon-xuanzemoxing',
|
||||
litReqParamKey: 'available_range',
|
||||
fileReqParamKey: 'models_id',
|
||||
tabItems: [
|
||||
{
|
||||
key: CommonTabKeys.Private,
|
||||
label: '我的模型',
|
||||
},
|
||||
{
|
||||
key: CommonTabKeys.Public,
|
||||
label: '公开模型',
|
||||
},
|
||||
],
|
||||
buttontTitle: '选择模型',
|
||||
},
|
||||
[ResourceSelectorType.Dataset]: {
|
||||
getList: getDatasetList,
|
||||
getVersions: getDatasetVersionsById,
|
||||
getFiles: getDatasetVersionIdList,
|
||||
handleVersionResponse: (res) => res.data || [],
|
||||
name: '数据集',
|
||||
modalIcon: datasetImg,
|
||||
buttonIcon: 'icon-xuanzeshujuji',
|
||||
litReqParamKey: 'available_range',
|
||||
fileReqParamKey: 'dataset_id',
|
||||
tabItems: [
|
||||
{
|
||||
key: CommonTabKeys.Private,
|
||||
label: '我的数据集',
|
||||
},
|
||||
{
|
||||
key: CommonTabKeys.Public,
|
||||
label: '公开数据集',
|
||||
},
|
||||
],
|
||||
buttontTitle: '选择数据集',
|
||||
},
|
||||
[ResourceSelectorType.Mirror]: {
|
||||
getList: getMirrorListReq,
|
||||
getVersions: (id: number) => getMirrorVersionListReq({ image_id: id, page: 0, size: 200 }),
|
||||
getFiles: getMirrorFilesReq,
|
||||
handleVersionResponse: (res) =>
|
||||
res.data?.content?.filter((v: MirrorVersion) => v.status === MirrorVersionStatus.Available) ||
|
||||
[],
|
||||
name: '镜像',
|
||||
modalIcon: mirrorImg,
|
||||
buttonIcon: 'icon-xuanzejingxiang',
|
||||
litReqParamKey: 'image_type',
|
||||
fileReqParamKey: 'dataset_id',
|
||||
tabItems: [
|
||||
{
|
||||
key: CommonTabKeys.Private,
|
||||
label: '我的镜像',
|
||||
},
|
||||
{
|
||||
key: CommonTabKeys.Public,
|
||||
label: '公开镜像',
|
||||
},
|
||||
],
|
||||
buttontTitle: '选择镜像',
|
||||
},
|
||||
[ResourceSelectorType.Model]: new ModelSelector(),
|
||||
[ResourceSelectorType.Dataset]: new DatasetSelector(),
|
||||
[ResourceSelectorType.Mirror]: new MirrorSelector(),
|
||||
};
|
||||
|
|
|
@ -34,6 +34,11 @@
|
|||
border-bottom: 1px solid @border-color-secondary;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
&__tree-title {
|
||||
display: inline-block;
|
||||
.singleLine();
|
||||
}
|
||||
}
|
||||
|
||||
&__right {
|
||||
|
|
|
@ -6,34 +6,27 @@
|
|||
|
||||
import KFModal from '@/components/KFModal';
|
||||
import { CommonTabKeys } from '@/enums';
|
||||
import { ResourceFileData } from '@/pages/Dataset/config';
|
||||
import { to } from '@/utils/promise';
|
||||
import { Icon } from '@umijs/max';
|
||||
import type { GetRef, ModalProps, TreeDataNode, TreeProps } from 'antd';
|
||||
import { Input, Tabs, Tree } from 'antd';
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { MirrorVersion, ResourceSelectorType, selectorTypeConfig } from './config';
|
||||
import { ResourceSelectorType, selectorTypeConfig } from './config';
|
||||
import styles from './index.less';
|
||||
export { ResourceSelectorType, selectorTypeConfig };
|
||||
|
||||
// 选择数据集\模型\镜像的返回类型
|
||||
export type ResourceSelectorResponse = {
|
||||
id: number; // 数据集\模型\镜像 id
|
||||
id: string; // 数据集\模型\镜像 id
|
||||
name: string; // 数据集\模型\镜像 name
|
||||
version: string; // 数据集\模型\镜像版本
|
||||
path: string; // 数据集\模型\镜像版本路径
|
||||
identifier: string; // 数据集\模型 identifier
|
||||
owner: string; // 数据集\模型 owner
|
||||
activeTab: CommonTabKeys; // 是我的还是公开的
|
||||
};
|
||||
|
||||
type ResourceGroup = {
|
||||
id: number; // 数据集\模型\镜像 id
|
||||
name: string; // 数据集\模型\镜像 name
|
||||
};
|
||||
|
||||
type ResourceFile = {
|
||||
id: number; // 文件 id
|
||||
file_name: string; // 文件 name
|
||||
};
|
||||
|
||||
export interface ResourceSelectorModalProps extends Omit<ModalProps, 'onOk'> {
|
||||
type: ResourceSelectorType; // 数据集\模型\镜像
|
||||
defaultExpandedKeys?: React.Key[];
|
||||
|
@ -44,39 +37,8 @@ export interface ResourceSelectorModalProps extends Omit<ModalProps, 'onOk'> {
|
|||
|
||||
type TreeRef = GetRef<typeof Tree<TreeDataNode>>;
|
||||
|
||||
// list 数据转成 treeData
|
||||
const convertToTreeData = (list: ResourceGroup[]): TreeDataNode[] => {
|
||||
return list.map((v) => ({
|
||||
title: v.name,
|
||||
key: v.id,
|
||||
isLeaf: false,
|
||||
checkable: false,
|
||||
}));
|
||||
};
|
||||
|
||||
// 版本数据转成 treeData
|
||||
const convertVersionToTreeData = (parentId: number) => {
|
||||
return (item: string | MirrorVersion): TreeDataNode => {
|
||||
if (typeof item === 'string') {
|
||||
return {
|
||||
title: item,
|
||||
key: `${parentId}-${item}`,
|
||||
isLeaf: true,
|
||||
checkable: true,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
title: item.tag_name,
|
||||
key: `${parentId}-${item.id}-${item.url}`,
|
||||
isLeaf: true,
|
||||
checkable: true,
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// 更新树形结构的 children
|
||||
const updateChildren = (parentId: number, children: TreeDataNode[]) => {
|
||||
const updateChildren = (parentId: string, children: TreeDataNode[]) => {
|
||||
return (node: TreeDataNode) => {
|
||||
if (node.key === parentId) {
|
||||
return {
|
||||
|
@ -91,7 +53,7 @@ const updateChildren = (parentId: number, children: TreeDataNode[]) => {
|
|||
// 得到数据集\模型\镜像 id 和下属版本号
|
||||
const getIdAndVersion = (versionKey: string) => {
|
||||
const index = versionKey.indexOf('-');
|
||||
const id = Number(versionKey.slice(0, index));
|
||||
const id = versionKey.slice(0, index);
|
||||
const version = versionKey.slice(index + 1);
|
||||
return {
|
||||
id,
|
||||
|
@ -112,11 +74,11 @@ function ResourceSelectorModal({
|
|||
const [checkedKeys, setCheckedKeys] = useState<React.Key[]>([]);
|
||||
const [loadedKeys, setLoadedKeys] = useState<React.Key[]>([]);
|
||||
const [originTreeData, setOriginTreeData] = useState<TreeDataNode[]>([]);
|
||||
const [files, setFiles] = useState<ResourceFile[]>([]);
|
||||
const [files, setFiles] = useState<ResourceFileData[]>([]);
|
||||
const [versionPath, setVersionPath] = useState('');
|
||||
const [searchText, setSearchText] = useState('');
|
||||
const [fisrtLoadList, setFisrtLoadList] = useState(false);
|
||||
const [fisrtLoadVersions, setFisrtLoadVersions] = useState(false);
|
||||
const [firstLoadList, setFirstLoadList] = useState(false);
|
||||
const [firstLoadVersions, setFirstLoadVersions] = useState(false);
|
||||
const treeRef = useRef<TreeRef>(null);
|
||||
const config = selectorTypeConfig[type];
|
||||
|
||||
|
@ -140,18 +102,10 @@ function ResourceSelectorModal({
|
|||
|
||||
// 获取数据集\模型\镜像列表
|
||||
const getTreeData = async () => {
|
||||
const available_range = activeTab === CommonTabKeys.Private ? 0 : 1;
|
||||
const params = {
|
||||
page: 0,
|
||||
size: 1000,
|
||||
[config.litReqParamKey]: available_range,
|
||||
};
|
||||
const getListReq = config.getList;
|
||||
const [res] = await to(getListReq(params));
|
||||
const isPublic = activeTab === CommonTabKeys.Private ? false : true;
|
||||
const [res] = await to(config.getList(isPublic));
|
||||
if (res) {
|
||||
const list = res.data?.content || [];
|
||||
const treeData = convertToTreeData(list);
|
||||
setOriginTreeData(treeData);
|
||||
setOriginTreeData(res);
|
||||
|
||||
// 恢复上一次的 Expand 操作
|
||||
restoreLastExpand();
|
||||
|
@ -161,21 +115,22 @@ function ResourceSelectorModal({
|
|||
};
|
||||
|
||||
// 获取数据集\模型\镜像版本列表
|
||||
const getVersions = async (parentId: number) => {
|
||||
const getVersionsReq = config.getVersions;
|
||||
const [res, error] = await to(getVersionsReq(parentId));
|
||||
const getVersions = async (parentId: string, parentNode: any) => {
|
||||
const [res, error] = await to(config.getVersions(parentId, parentNode));
|
||||
if (res) {
|
||||
const list = config.handleVersionResponse(res);
|
||||
const children = list.map(convertVersionToTreeData(parentId));
|
||||
// 更新 treeData children
|
||||
setOriginTreeData((prev) => prev.map(updateChildren(parentId, children)));
|
||||
setOriginTreeData((prev) => prev.map(updateChildren(parentId, res)));
|
||||
|
||||
// 缓存 loadedKeys
|
||||
const index = loadedKeys.find((v) => v === parentId);
|
||||
if (!index) {
|
||||
setLoadedKeys((prev) => prev.concat(parentId));
|
||||
}
|
||||
|
||||
// 恢复上一次的 Check 操作
|
||||
restoreLastCheck(parentId);
|
||||
setTimeout(() => {
|
||||
restoreLastCheck(parentId, res);
|
||||
}, 300);
|
||||
} else {
|
||||
setExpandedKeys([]);
|
||||
return Promise.reject(error);
|
||||
|
@ -183,14 +138,11 @@ function ResourceSelectorModal({
|
|||
};
|
||||
|
||||
// 获取版本下的文件
|
||||
const getFiles = async (id: number, version: string) => {
|
||||
const getFilesReq = config.getFiles;
|
||||
const paramsKey = config.fileReqParamKey;
|
||||
const params = { version: version, [paramsKey]: id };
|
||||
const [res] = await to(getFilesReq(params));
|
||||
const getFiles = async (parentId: string, parentNode: any) => {
|
||||
const [res] = await to(config.getFiles(parentId, parentNode));
|
||||
if (res) {
|
||||
setVersionPath(res.data?.path || '');
|
||||
setFiles(res.data?.content || []);
|
||||
setVersionPath(res.path);
|
||||
setFiles(res.content);
|
||||
} else {
|
||||
setVersionPath('');
|
||||
setFiles([]);
|
||||
|
@ -198,11 +150,11 @@ function ResourceSelectorModal({
|
|||
};
|
||||
|
||||
// 动态加载 tree children
|
||||
const onLoadData = ({ key, children }: TreeDataNode) => {
|
||||
const onLoadData = ({ key, children, ...rest }: TreeDataNode) => {
|
||||
if (children) {
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
return getVersions(key as number);
|
||||
return getVersions(key as string, rest);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -213,14 +165,15 @@ function ResourceSelectorModal({
|
|||
};
|
||||
|
||||
// 选中
|
||||
const onCheck: TreeProps['onCheck'] = (checkedKeysValue) => {
|
||||
const onCheck: TreeProps['onCheck'] = (checkedKeysValue, { checkedNodes }) => {
|
||||
const lastKeys = (checkedKeysValue as React.Key[]).slice(-1);
|
||||
setCheckedKeys(lastKeys);
|
||||
if (lastKeys.length) {
|
||||
if (lastKeys.length && checkedNodes.length) {
|
||||
const last = lastKeys[0] as string;
|
||||
const { id, version } = getIdAndVersion(last);
|
||||
getFiles(id, version);
|
||||
const lastNode = checkedNodes[checkedNodes.length - 1];
|
||||
getFiles(last, lastNode);
|
||||
} else {
|
||||
setVersionPath('');
|
||||
setFiles([]);
|
||||
}
|
||||
};
|
||||
|
@ -229,10 +182,10 @@ function ResourceSelectorModal({
|
|||
// 判断是否有 defaultExpandedKeys,如果有,设置 expandedKeys
|
||||
// fisrtLoadList 标志位
|
||||
const restoreLastExpand = () => {
|
||||
if (!fisrtLoadList && defaultExpandedKeys.length > 0) {
|
||||
if (!firstLoadList && defaultExpandedKeys.length > 0) {
|
||||
setTimeout(() => {
|
||||
setExpandedKeys(defaultExpandedKeys);
|
||||
setFisrtLoadList(true);
|
||||
setFirstLoadList(true);
|
||||
setTimeout(() => {
|
||||
treeRef.current?.scrollTo({ key: defaultExpandedKeys[0], align: 'bottom' });
|
||||
}, 100);
|
||||
|
@ -243,16 +196,17 @@ function ResourceSelectorModal({
|
|||
// 恢复上一次的 Check 操作
|
||||
// 判断是否有 defaultCheckedKeys,如果有,设置 checkedKeys,并且调用获取文件列表接口
|
||||
// fisrtLoadVersions 标志位
|
||||
const restoreLastCheck = (parentId: number) => {
|
||||
if (!fisrtLoadVersions && defaultCheckedKeys.length > 0) {
|
||||
const restoreLastCheck = (parentId: string, versions: TreeDataNode[]) => {
|
||||
if (!firstLoadVersions && defaultCheckedKeys.length > 0) {
|
||||
const last = defaultCheckedKeys[0] as string;
|
||||
const { id, version } = getIdAndVersion(last);
|
||||
const { id } = getIdAndVersion(last);
|
||||
// 判断正在打开的 id 和 defaultCheckedKeys 的 id 是否一致
|
||||
if (id === parentId) {
|
||||
setTimeout(() => {
|
||||
setCheckedKeys(defaultCheckedKeys);
|
||||
getFiles(id, version);
|
||||
setFisrtLoadVersions(true);
|
||||
const parentNode = versions.find((v) => v.key === last);
|
||||
getFiles(last, parentNode);
|
||||
setFirstLoadVersions(true);
|
||||
setTimeout(() => {
|
||||
treeRef?.current?.scrollTo({
|
||||
key: defaultCheckedKeys[0],
|
||||
|
@ -269,12 +223,17 @@ function ResourceSelectorModal({
|
|||
if (checkedKeys.length > 0) {
|
||||
const last = checkedKeys[0] as string;
|
||||
const { id, version } = getIdAndVersion(last);
|
||||
const name = (treeData.find((v) => Number(v.key) === id)?.title ?? '') as string;
|
||||
const treeNode = treeData.find((v) => v.key === id) as any;
|
||||
const name = (treeNode?.title ?? '') as string;
|
||||
const identifier = (treeNode?.identifier ?? '') as string;
|
||||
const owner = (treeNode?.owner ?? '') as string;
|
||||
const res = {
|
||||
id,
|
||||
name,
|
||||
path: versionPath,
|
||||
version,
|
||||
identifier,
|
||||
owner,
|
||||
activeTab: activeTab as CommonTabKeys,
|
||||
};
|
||||
onOk?.(res);
|
||||
|
@ -324,13 +283,23 @@ function ResourceSelectorModal({
|
|||
expandedKeys={expandedKeys}
|
||||
onExpand={onExpand}
|
||||
checkable
|
||||
titleRender={(nodeData) => {
|
||||
return (
|
||||
<span
|
||||
className={styles['model-selector__left__tree-title']}
|
||||
style={{ width: nodeData.isLeaf ? '370px' : '420px' }}
|
||||
>
|
||||
{nodeData.title as string}
|
||||
</span>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles['model-selector__right']}>
|
||||
<div className={styles['model-selector__right__title']}>{fileTitle}</div>
|
||||
<div className={styles['model-selector__right__files']}>
|
||||
{files.map((v) => (
|
||||
<div key={v.id} className={styles['model-selector__right__files__file']}>
|
||||
<div key={v.url} className={styles['model-selector__right__files__file']}>
|
||||
{v.file_name}
|
||||
</div>
|
||||
))}
|
||||
|
|
|
@ -129,7 +129,7 @@ const AuthUserTableList: React.FC = () => {
|
|||
{
|
||||
title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
|
||||
dataIndex: 'option',
|
||||
width: '60px',
|
||||
width: '160px',
|
||||
valueType: 'option',
|
||||
render: (_, record) => [
|
||||
<Button
|
||||
|
|
|
@ -111,10 +111,7 @@ const DataScopeForm: React.FC<DataScopeFormProps> = (props) => {
|
|||
grid={true}
|
||||
layout="horizontal"
|
||||
onFinish={handleFinish}
|
||||
initialValues={{
|
||||
login_password: '',
|
||||
confirm_password: '',
|
||||
}}
|
||||
submitter={false}
|
||||
{...formLayout}
|
||||
size="large"
|
||||
labelAlign="right"
|
||||
|
@ -203,8 +200,9 @@ const DataScopeForm: React.FC<DataScopeFormProps> = (props) => {
|
|||
})}
|
||||
required={dataScopeType === '1'}
|
||||
hidden={dataScopeType !== '1'}
|
||||
style={{ width: '100%', padding: '0 4px' }}
|
||||
>
|
||||
<Row gutter={[16, 16]}>
|
||||
<Row gutter={[16, 16]} style={{ marginTop: '10px' }}>
|
||||
<Col md={24}>
|
||||
<Checkbox.Group
|
||||
options={[
|
||||
|
@ -235,7 +233,7 @@ const DataScopeForm: React.FC<DataScopeFormProps> = (props) => {
|
|||
}
|
||||
}}
|
||||
onExpand={(expandedKeys: Key[]) => {
|
||||
setDeptTreeExpandKey(deptTreeExpandKey.concat(expandedKeys));
|
||||
setDeptTreeExpandKey(expandedKeys);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
.user-selector-modal {
|
||||
:global {
|
||||
// 输入框高度为46px
|
||||
.ant-input-affix-wrapper {
|
||||
padding-top: 4px !important;
|
||||
padding-bottom: 4px !important;
|
||||
.ant-input {
|
||||
height: 22px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@ import {
|
|||
} from '@ant-design/pro-components';
|
||||
import { FormattedMessage, useIntl } from '@umijs/max';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import styles from './UserSelectorModal.less';
|
||||
/* *
|
||||
*
|
||||
* @author whiteshader@163.com
|
||||
|
@ -90,7 +91,7 @@ const UserSelectorModal: React.FC<DataScopeFormProps> = (props) => {
|
|||
|
||||
return (
|
||||
<KFModal
|
||||
width={800}
|
||||
width={920}
|
||||
title={intl.formatMessage({
|
||||
id: 'system.role.auth.user',
|
||||
defaultMessage: '选择用户',
|
||||
|
@ -99,6 +100,7 @@ const UserSelectorModal: React.FC<DataScopeFormProps> = (props) => {
|
|||
destroyOnClose
|
||||
onOk={handleOk}
|
||||
onCancel={handleCancel}
|
||||
className={styles['user-selector-modal']}
|
||||
>
|
||||
<ProTable<API.System.User>
|
||||
headerTitle={intl.formatMessage({
|
||||
|
|
|
@ -34,7 +34,7 @@ const AuthRoleForm: React.FC<AuthRoleFormProps> = (props) => {
|
|||
|
||||
return (
|
||||
<KFModal
|
||||
width={640}
|
||||
width={680}
|
||||
title={intl.formatMessage({
|
||||
id: 'system.user.auth.role',
|
||||
defaultMessage: '分配角色',
|
||||
|
@ -50,10 +50,10 @@ const AuthRoleForm: React.FC<AuthRoleFormProps> = (props) => {
|
|||
grid={true}
|
||||
layout="horizontal"
|
||||
onFinish={handleFinish}
|
||||
initialValues={{
|
||||
login_password: '',
|
||||
confirm_password: '',
|
||||
}}
|
||||
submitter={false}
|
||||
size="large"
|
||||
labelAlign="right"
|
||||
autoComplete="off"
|
||||
>
|
||||
<ProFormSelect
|
||||
name="roleIds"
|
||||
|
|
|
@ -39,7 +39,7 @@ const UpdateForm: React.FC<UpdateFormProps> = (props) => {
|
|||
|
||||
return (
|
||||
<KFModal
|
||||
width={640}
|
||||
width={680}
|
||||
title={intl.formatMessage({
|
||||
id: 'system.user.reset.password',
|
||||
defaultMessage: '密码重置',
|
||||
|
@ -58,8 +58,12 @@ const UpdateForm: React.FC<UpdateFormProps> = (props) => {
|
|||
password: '',
|
||||
confirm_password: '',
|
||||
}}
|
||||
submitter={false}
|
||||
size="large"
|
||||
labelAlign="right"
|
||||
autoComplete="off"
|
||||
>
|
||||
<p>请输入用户{props.values.userName}的新密码!</p>
|
||||
<p>请输入用户 {props.values.userName} 的新密码!</p>
|
||||
<ProFormText.Password
|
||||
name="password"
|
||||
label="登录密码"
|
||||
|
|
|
@ -62,6 +62,8 @@ const UserForm: React.FC<UserFormProps> = (props) => {
|
|||
loginIp: props.values.loginIp,
|
||||
loginDate: props.values.loginDate,
|
||||
remark: props.values.remark,
|
||||
gitLinkUsername: props.values.gitLinkUsername,
|
||||
gitLinkPassword: props.values.gitLinkPassword,
|
||||
});
|
||||
}, [form, props]);
|
||||
|
||||
|
@ -275,6 +277,28 @@ const UserForm: React.FC<UserFormProps> = (props) => {
|
|||
colProps={{ md: 12, xl: 12 }}
|
||||
rules={[{ required: true, message: '请选择角色!' }]}
|
||||
/>
|
||||
<ProFormText
|
||||
name="gitLinkUsername"
|
||||
label="Git 用户名"
|
||||
placeholder="请输入 Git 用户名"
|
||||
colProps={{ xs: 24, md: 12, xl: 12 }}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: '请输入 Git 用户名!',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<ProFormText.Password
|
||||
name="gitLinkPassword"
|
||||
label="Git 密码"
|
||||
placeholder="请输入 Git 密码"
|
||||
colProps={{ xs: 24, md: 12, xl: 12 }}
|
||||
fieldProps={{
|
||||
autoComplete: 'new-password',
|
||||
}}
|
||||
rules={props.values.userId ? [] : [{ required: true, message: '请输入 Git 密码!' }]}
|
||||
/>
|
||||
<ProFormTextArea
|
||||
name="remark"
|
||||
label={intl.formatMessage({
|
||||
|
|
|
@ -116,7 +116,12 @@ const AvatarCropperForm: React.FC<AvatarCropperProps> = (props) => {
|
|||
</Col>
|
||||
<Col span={12} order={2}>
|
||||
<div className={styles.avatarPreview}>
|
||||
<img src={previewData} style={{ height: '100%', width: '100%' }} />
|
||||
<img
|
||||
src={previewData}
|
||||
style={{ height: '100%', width: '100%' }}
|
||||
draggable={false}
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
|
|
|
@ -141,7 +141,7 @@ const Center: React.FC = () => {
|
|||
setCropperModalOpen(true);
|
||||
}}
|
||||
>
|
||||
<img alt="" src={currentUser.avatar} />
|
||||
<img src={currentUser.avatar} draggable={false} alt="" />
|
||||
</div>
|
||||
{renderUserInfo(currentUser)}
|
||||
<Divider dashed />
|
||||
|
|
|
@ -11,7 +11,7 @@ import styles from './login.less';
|
|||
const LoginInputPrefix = ({ icon }: { icon: string }) => {
|
||||
return (
|
||||
<div className={styles['login-input-prefix']}>
|
||||
<img className={styles['login-input-prefix__icon']} src={icon} alt="" />
|
||||
<img className={styles['login-input-prefix__icon']} src={icon} alt="" draggable={false} />
|
||||
<div className={styles['login-input-prefix__line']}></div>
|
||||
</div>
|
||||
);
|
||||
|
@ -98,6 +98,7 @@ const Login = () => {
|
|||
<img
|
||||
src={require('@/assets/img/logo.png')}
|
||||
style={{ width: '32px', marginRight: '12px' }}
|
||||
draggable={false}
|
||||
alt=""
|
||||
/>
|
||||
智能材料科研平台
|
||||
|
@ -107,6 +108,7 @@ const Login = () => {
|
|||
<img
|
||||
src={require('@/assets/img/login-ai-logo.png')}
|
||||
className={styles['user-login__left__title__img']}
|
||||
draggable={false}
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
|
@ -116,6 +118,7 @@ const Login = () => {
|
|||
<img
|
||||
className={styles['user-login__left__bottom-img']}
|
||||
src={require('@/assets/img/login-left-image.png')}
|
||||
draggable={false}
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -31,6 +31,8 @@ function ExperimentTable({ tableData = [], style }: ExperimentTableProps) {
|
|||
src={experimentStatusInfo[item.status as ExperimentStatus]?.icon}
|
||||
width={17}
|
||||
height={17}
|
||||
draggable={false}
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
<div className={styles['experiment-table__duration']}>
|
||||
|
|
|
@ -44,6 +44,7 @@ function WorkArrow({
|
|||
<img
|
||||
className={styles['work-arrow__img']}
|
||||
src={require('@/assets/img/blue-triangle.png')}
|
||||
draggable={false}
|
||||
alt=""
|
||||
width={10}
|
||||
height={9}
|
||||
|
|
|
@ -10,7 +10,7 @@ type TotalStatisticsProps = {
|
|||
function TotalStatistics({ icon = '', title = '', count = 0, style }: TotalStatisticsProps) {
|
||||
return (
|
||||
<div className={styles['total-statistics']} style={style}>
|
||||
<img className={styles['total-statistics__icon']} src={icon} />
|
||||
<img className={styles['total-statistics__icon']} src={icon} draggable={false} alt="" />
|
||||
<div>
|
||||
<div className={styles['total-statistics__title']}>
|
||||
<span>{title}</span>
|
||||
|
|
|
@ -20,7 +20,15 @@ function UserSpace({ users = [] }: UserSpaceProps) {
|
|||
className={styles['user-space__avatar']}
|
||||
src={currentUser?.avatar}
|
||||
alt=""
|
||||
icon={<img src={require('@/assets/img/avatar-default.png')} width={56} height={56} />}
|
||||
icon={
|
||||
<img
|
||||
src={require('@/assets/img/avatar-default.png')}
|
||||
width={56}
|
||||
height={56}
|
||||
draggable={false}
|
||||
alt=""
|
||||
/>
|
||||
}
|
||||
></Avatar>
|
||||
<div className={styles['user-space__name']}>{currentUser?.nickName}</div>
|
||||
<div className={styles['user-space__role']}>{currentUser?.roleNames?.[0]?.roleName}</div>
|
||||
|
@ -34,7 +42,7 @@ function UserSpace({ users = [] }: UserSpaceProps) {
|
|||
<div className={styles['user-space__participant__count']}>8</div>
|
||||
</Space>
|
||||
<Flex align="center" gap={12} wrap="wrap">
|
||||
{users?.map((item, index) => {
|
||||
{users?.map((_item, index) => {
|
||||
return (
|
||||
<Avatar
|
||||
key={index}
|
||||
|
@ -42,7 +50,13 @@ function UserSpace({ users = [] }: UserSpaceProps) {
|
|||
src={require(`@/assets/img/user-avatar/${index + 1}.png`)}
|
||||
alt=""
|
||||
icon={
|
||||
<img src={require('@/assets/img/avatar-default.png')} width={36} height={36} />
|
||||
<img
|
||||
src={require('@/assets/img/avatar-default.png')}
|
||||
width={36}
|
||||
height={36}
|
||||
draggable={false}
|
||||
alt=""
|
||||
/>
|
||||
}
|
||||
></Avatar>
|
||||
);
|
||||
|
|
|
@ -15,7 +15,13 @@ function WorkspaceIntro() {
|
|||
type="primary"
|
||||
style={{ marginRight: '20px' }}
|
||||
icon={
|
||||
<img src={require('@/assets/img/functional-material.png')} width={19} height={19} />
|
||||
<img
|
||||
src={require('@/assets/img/functional-material.png')}
|
||||
width={19}
|
||||
height={19}
|
||||
draggable={false}
|
||||
alt=""
|
||||
/>
|
||||
}
|
||||
>
|
||||
功能材料自主实验系统
|
||||
|
@ -23,7 +29,13 @@ function WorkspaceIntro() {
|
|||
<Button
|
||||
type="default"
|
||||
icon={
|
||||
<img src={require('@/assets/img/molecular-material.png')} width={19} height={19} />
|
||||
<img
|
||||
src={require('@/assets/img/molecular-material.png')}
|
||||
width={19}
|
||||
height={19}
|
||||
draggable={false}
|
||||
alt=""
|
||||
/>
|
||||
}
|
||||
>
|
||||
分子材料自主实验系统
|
||||
|
@ -34,6 +46,8 @@ function WorkspaceIntro() {
|
|||
<img
|
||||
className={styles['workspace-intro__icon']}
|
||||
src={require('@/assets/img/workspace-intro-icon.png')}
|
||||
draggable={false}
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -53,6 +53,7 @@
|
|||
width: 64px;
|
||||
height: 64px;
|
||||
background-color: white;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -76,7 +76,8 @@ function Workspace() {
|
|||
className={styles['workspace__robot-img']}
|
||||
src={require('@/assets/img/robot.png')}
|
||||
onClick={handleClick}
|
||||
onDragStart={(e) => e.preventDefault()}
|
||||
draggable={false}
|
||||
alt=""
|
||||
></img>
|
||||
</Draggable>
|
||||
|
||||
|
|
|
@ -1,10 +1,20 @@
|
|||
import missingPage from '@/assets/img/missing-back.png';
|
||||
import KFEmpty, { EmptyType } from '@/components/KFEmpty';
|
||||
import { useNavigate } from '@umijs/max';
|
||||
|
||||
const MissingPage = () => (
|
||||
<div style={{ width: '100%', display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
|
||||
<img src={missingPage} style={{ width: '575px', margin: '278px 0 44px 0' }} alt="" />
|
||||
<span style={{ color: '#575757', fontSize: '16px' }}>页面开发中,敬请期待......</span>
|
||||
</div>
|
||||
);
|
||||
const MissingPage = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<KFEmpty
|
||||
style={{ height: '100%' }}
|
||||
type={EmptyType.Developing}
|
||||
title="敬请期待~"
|
||||
content={'很抱歉,您访问的正在开发中,\n请耐心等待。'}
|
||||
hasFooter={true}
|
||||
buttonTitle="返回首页"
|
||||
onRefresh={() => navigate('/')}
|
||||
></KFEmpty>
|
||||
);
|
||||
};
|
||||
|
||||
export default MissingPage;
|
||||
|
|
|
@ -7,6 +7,7 @@ import type { AxiosRequestConfig, AxiosResponse, RequestConfig, RequestOptions }
|
|||
import { message } from 'antd';
|
||||
import { clearSessionToken, getAccessToken } from './access';
|
||||
import { setRemoteMenu } from './services/session';
|
||||
import Loading from './utils/loading';
|
||||
import { gotoLoginPage } from './utils/ui';
|
||||
|
||||
// [antd: Notification] You are calling notice in render which will break in React 18 concurrent mode. Please trigger in effect instead.
|
||||
|
@ -25,6 +26,7 @@ const popupError = (error: string, skipErrorHandler: boolean | undefined = false
|
|||
* @doc https://umijs.org/docs/max/request#配置
|
||||
*/
|
||||
export const requestConfig: RequestConfig = {
|
||||
timeout: 120 * 1000,
|
||||
requestInterceptors: [
|
||||
(url: string, options: AxiosRequestConfig) => {
|
||||
const headers = options.headers ?? {};
|
||||
|
@ -36,12 +38,14 @@ export const requestConfig: RequestConfig = {
|
|||
headers['Authorization'] = `Bearer ${accessToken}`;
|
||||
}
|
||||
}
|
||||
Loading.show();
|
||||
return { url, options };
|
||||
},
|
||||
],
|
||||
responseInterceptors: [
|
||||
[
|
||||
(response: AxiosResponse) => {
|
||||
Loading.hide();
|
||||
const { status, data, config } = response || {};
|
||||
const skipErrorHandler = (config as RequestOptions)?.skipErrorHandler;
|
||||
if (status >= 200 && status < 300) {
|
||||
|
@ -63,6 +67,7 @@ export const requestConfig: RequestConfig = {
|
|||
}
|
||||
},
|
||||
(error: Error) => {
|
||||
Loading.hide();
|
||||
popupError(error.message ?? '请求失败');
|
||||
return Promise.reject(error);
|
||||
},
|
||||
|
|
|
@ -1,140 +1,153 @@
|
|||
import { request } from '@umijs/max';
|
||||
// 分页查询数据集
|
||||
export function getDatasetList(params) {
|
||||
return request(`/api/mmp/dataset`, {
|
||||
method: 'GET',
|
||||
params,
|
||||
});
|
||||
}
|
||||
// 分页查询模型
|
||||
export function getModelList(params) {
|
||||
return request(`/api/mmp/models`, {
|
||||
method: 'GET',
|
||||
params,
|
||||
});
|
||||
}
|
||||
// 新增数据集
|
||||
export function addDatesetAndVesion(data) {
|
||||
return request(`/api/mmp/dataset/addDatasetAndVersion`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json;charset=UTF-8',
|
||||
},
|
||||
data,
|
||||
});
|
||||
}
|
||||
// 新增模型
|
||||
export function addModel(data) {
|
||||
return request(`/api/mmp/models/addModelAndVersion`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json;charset=UTF-8',
|
||||
},
|
||||
data,
|
||||
});
|
||||
}
|
||||
// 查询数据集简介
|
||||
export function getDatasetById(id) {
|
||||
return request(`/api/mmp/dataset/${id}`, {
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
||||
// 查询左侧列表
|
||||
|
||||
// 查询数据集、模型分类
|
||||
export function getAssetIcon(params) {
|
||||
return request(`/api/mmp/assetIcon`, {
|
||||
method: 'GET',
|
||||
params,
|
||||
});
|
||||
}
|
||||
// 查询模型简介
|
||||
export function getModelById(id) {
|
||||
return request(`/api/mmp/models/${id}`, {
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
||||
// 查询数据版本集
|
||||
export function getDatasetVersionsById(id) {
|
||||
return request(`/api/mmp/dataset/versions/${id}`, {
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
||||
// 查询模型版本集
|
||||
export function getModelVersionsById(id) {
|
||||
return request(`/api/mmp/models/versions/${id}`, {
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
||||
// 分页查询数据集
|
||||
export function getDatasetVersionIdList(params) {
|
||||
return request(`/api/mmp/datasetVersion/versions`, {
|
||||
|
||||
// ----------------------------数据集---------------------------------
|
||||
|
||||
// 分页查询数据集列表
|
||||
export function getDatasetList(params) {
|
||||
return request(`/api/mmp/newdataset/queryDatasets`, {
|
||||
method: 'GET',
|
||||
params,
|
||||
});
|
||||
}
|
||||
// 根据版本查询模型
|
||||
export function getModelVersionIdList(params) {
|
||||
return request(`/api/mmp/modelsVersion/versions`, {
|
||||
|
||||
// 查询数据集详情
|
||||
export function getDatasetInfo(params) {
|
||||
return request(`/api/mmp/newdataset/getDatasetDetail`, {
|
||||
method: 'GET',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 新增数据集
|
||||
export function addDataset(data) {
|
||||
return request(`/api/mmp/newdataset/addDatasetAndVersion`, {
|
||||
method: 'POST',
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
// 删除数据集
|
||||
export function deleteDatasetVersion(params) {
|
||||
return request(`/api/mmp/datasetVersion/deleteVersion`, {
|
||||
export function deleteDataset(params) {
|
||||
return request(`/api/mmp/newdataset/deleteDataset`, {
|
||||
method: 'DELETE',
|
||||
params,
|
||||
});
|
||||
}
|
||||
// 删除模型
|
||||
export function deleteModelVersion(params) {
|
||||
return request(`/api/mmp/modelsVersion/deleteVersion`, {
|
||||
method: 'DELETE',
|
||||
|
||||
|
||||
// 查询数据集版本列表
|
||||
export function getDatasetVersionList(params) {
|
||||
return request(`/api/mmp/newdataset/getVersionList`, {
|
||||
method: 'GET',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 新增数据集版本
|
||||
export function addDatasetVersionDetail(data) {
|
||||
return request(`/api/mmp/datasetVersion/addDatasetVersions`, {
|
||||
export function addDatasetVersion(data) {
|
||||
return request(`/api/mmp/newdataset/addVersion`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json;charset=UTF-8',
|
||||
},
|
||||
data,
|
||||
});
|
||||
}
|
||||
// 新增模型版本
|
||||
export function addModelsVersionDetail(data) {
|
||||
return request(`/api/mmp/modelsVersion/addModelVersions`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json;charset=UTF-8',
|
||||
},
|
||||
data,
|
||||
});
|
||||
}
|
||||
// 下载数据集
|
||||
export function exportDataset(id) {
|
||||
return request(`/api/mmp/dataset/download/${id}`, {
|
||||
|
||||
// 下载数据集所有文件
|
||||
export function downloadAllFiles(params) {
|
||||
return request(`/api/mmp/newdataset/downloadAllFiles`, {
|
||||
method: 'GET',
|
||||
params
|
||||
});
|
||||
}
|
||||
// 删除模型集
|
||||
export function deleteModel(id) {
|
||||
return request(`/api/mmp/models/${id}`, {
|
||||
|
||||
// 下载数据集单个文件
|
||||
export function downloadSingleFile(params) {
|
||||
return request(`/api/mmp/newdataset/downloadSinggerFile`, {
|
||||
method: 'GET',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 删除数据集版本
|
||||
export function deleteDatasetVersion(params) {
|
||||
return request(`/api/mmp/newdataset/deleteDatasetVersion`, {
|
||||
method: 'DELETE',
|
||||
params,
|
||||
});
|
||||
}
|
||||
// 删除数据集
|
||||
export function deleteDataset(id) {
|
||||
return request(`/api/mmp/dataset/${id}`, {
|
||||
method: 'DELETE',
|
||||
|
||||
// ----------------------------模型---------------------------------
|
||||
|
||||
// 分页查询模型列表
|
||||
export function getModelList(params) {
|
||||
return request(`/api/mmp/newmodel/queryModels`, {
|
||||
method: 'GET',
|
||||
params,
|
||||
});
|
||||
}
|
||||
// 获取模型依赖
|
||||
export function getModelAtlasReq(data) {
|
||||
return request(`/api/mmp/modelDependency/queryModelAtlas`, {
|
||||
|
||||
// 新增模型
|
||||
export function addModel(data) {
|
||||
return request(`/api/mmp/newmodel/addModel`, {
|
||||
method: 'POST',
|
||||
data
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
// 删除模型
|
||||
export function deleteModel(params) {
|
||||
return request(`/api/mmp/newmodel/delete`, {
|
||||
method: 'DELETE',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// 查询模型详情
|
||||
export function getModelInfo(params) {
|
||||
return request(`/api/mmp/newmodel/getModelDetail`, {
|
||||
method: 'GET',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 查询模型版本列表
|
||||
export function getModelVersionList(params) {
|
||||
return request(`/api/mmp/newmodel/getVersionList`, {
|
||||
method: 'GET',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 新增模型版本
|
||||
export function addModelVersion(data) {
|
||||
return request(`/api/mmp/newmodel/addVersion`, {
|
||||
method: 'POST',
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// 删除模型版本
|
||||
export function deleteModelVersion(params) {
|
||||
return request(`/api/mmp/newmodel/deleteVersion`, {
|
||||
method: 'DELETE',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 获取模型依赖
|
||||
export function getModelAtlasReq(params) {
|
||||
return request(`/api/mmp/newmodel/getModelDependencyTree`, {
|
||||
method: 'GET',
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue