Browse Source

Merge branch 'dev' into dev-cp

dev-cp
chenpeng 1 year ago
parent
commit
89e9c83244
100 changed files with 1857 additions and 1960 deletions
  1. +5
    -0
      .gitignore
  2. +7
    -1
      k8s/build-java.sh
  3. +47
    -28
      k8s/build.sh
  4. +21
    -3
      k8s/build_and_deploy.sh
  5. +24
    -5
      k8s/deploy.sh
  6. +0
    -36
      k8s/k8s-10gen.yaml
  7. +0
    -36
      k8s/k8s-11visual.yaml
  8. +0
    -36
      k8s/k8s-12front.yaml
  9. +0
    -62
      k8s/k8s-3nacos.yaml
  10. +0
    -36
      k8s/k8s-4gateway.yaml
  11. +0
    -36
      k8s/k8s-5auth.yaml
  12. +0
    -36
      k8s/k8s-6system.yaml
  13. +0
    -44
      k8s/k8s-7management.yaml
  14. +0
    -36
      k8s/k8s-8file.yaml
  15. +0
    -36
      k8s/k8s-9job.yaml
  16. +0
    -36
      k8s/template-yaml/deploy/k8s-12front.yaml
  17. +0
    -53
      k8s/template-yaml/deploy/k8s-7management.yaml
  18. +43
    -0
      k8s/template-yaml/k8s-13oauth2.yaml
  19. +5
    -1
      k8s/template-yaml/k8s-3nacos.yaml
  20. +25
    -2
      k8s/template-yaml/k8s-7management.yaml
  21. +68
    -0
      k8s/template-yaml/rolebindings.yaml
  22. +2
    -1
      react-ui/.eslintignore
  23. +7
    -1
      react-ui/.eslintrc.js
  24. +2
    -7
      react-ui/.gitignore
  25. +1
    -0
      react-ui/.nvmrc
  26. +16
    -0
      react-ui/.storybook/babel-plugin-auto-css-modules.js
  27. +19
    -0
      react-ui/.storybook/blocks/StoryName.tsx
  28. +117
    -0
      react-ui/.storybook/main.ts
  29. +6
    -0
      react-ui/.storybook/manager.ts
  30. +19
    -0
      react-ui/.storybook/mock/umijs.mock.tsx
  31. +92
    -0
      react-ui/.storybook/mock/websocket.mock.js
  32. +112
    -0
      react-ui/.storybook/preview.tsx
  33. +19
    -0
      react-ui/.storybook/storybook.css
  34. +7
    -0
      react-ui/.storybook/theme.ts
  35. +27
    -0
      react-ui/.storybook/tsconfig.json
  36. +20
    -0
      react-ui/.storybook/typings.d.ts
  37. +1
    -0
      react-ui/config/config.ts
  38. +123
    -29
      react-ui/config/routes.ts
  39. +0
    -176
      react-ui/mock/listTableList.ts
  40. +31
    -1
      react-ui/package.json
  41. +0
    -1
      react-ui/public/CNAME
  42. BIN
      react-ui/public/assets/images/component-icon-9-Failed.png
  43. BIN
      react-ui/public/assets/images/component-icon-9-Omitted.png
  44. BIN
      react-ui/public/assets/images/component-icon-9-Pending.png
  45. BIN
      react-ui/public/assets/images/component-icon-9-Running.png
  46. BIN
      react-ui/public/assets/images/component-icon-9-Skipped.png
  47. BIN
      react-ui/public/assets/images/component-icon-9-Succeeded.png
  48. BIN
      react-ui/public/assets/images/component-icon-9.png
  49. BIN
      react-ui/public/favicon-cc.ico
  50. +0
    -1
      react-ui/public/logo.svg
  51. +307
    -0
      react-ui/public/mockServiceWorker.js
  52. +0
    -5
      react-ui/public/pro_icon.svg
  53. +14
    -10
      react-ui/src/app.tsx
  54. BIN
      react-ui/src/assets/img/dataset-config-icon.png
  55. BIN
      react-ui/src/assets/img/dataset-version.png
  56. BIN
      react-ui/src/assets/img/editor-parameter.png
  57. BIN
      react-ui/src/assets/img/logo-cc.png
  58. BIN
      react-ui/src/assets/img/mirror-basic.png
  59. BIN
      react-ui/src/assets/img/model-deployment.png
  60. BIN
      react-ui/src/assets/img/popover-bg.png
  61. BIN
      react-ui/src/assets/img/resample-icon.png
  62. BIN
      react-ui/src/assets/img/search-config-icon.png
  63. BIN
      react-ui/src/assets/img/trial-config-icon.png
  64. +0
    -37
      react-ui/src/components/ArrayTableCell/index.tsx
  65. +86
    -0
      react-ui/src/components/BasicInfo/BasicInfoItem.tsx
  66. +58
    -0
      react-ui/src/components/BasicInfo/BasicInfoItemValue.tsx
  67. +26
    -2
      react-ui/src/components/BasicInfo/index.less
  68. +38
    -109
      react-ui/src/components/BasicInfo/index.tsx
  69. +14
    -0
      react-ui/src/components/BasicInfo/types.ts
  70. +11
    -4
      react-ui/src/components/BasicTableInfo/index.less
  71. +13
    -11
      react-ui/src/components/BasicTableInfo/index.tsx
  72. +0
    -0
      react-ui/src/components/CodeConfigItem/index.less
  73. +0
    -0
      react-ui/src/components/CodeConfigItem/index.tsx
  74. +21
    -6
      react-ui/src/components/CodeSelect/index.tsx
  75. +49
    -0
      react-ui/src/components/CodeSelectorModal/index.less
  76. +29
    -21
      react-ui/src/components/CodeSelectorModal/index.tsx
  77. +41
    -0
      react-ui/src/components/ConfigInfo/index.tsx
  78. +0
    -45
      react-ui/src/components/Footer/index.tsx
  79. +19
    -0
      react-ui/src/components/FormInfo/index.less
  80. +74
    -0
      react-ui/src/components/FormInfo/index.tsx
  81. +13
    -5
      react-ui/src/components/FullScreenFrame/index.tsx
  82. +21
    -20
      react-ui/src/components/IFramePage/index.tsx
  83. +0
    -64
      react-ui/src/components/IconSelector/Category.tsx
  84. +0
    -44
      react-ui/src/components/IconSelector/CopyableIcon.tsx
  85. +0
    -237
      react-ui/src/components/IconSelector/IconPicSearcher.tsx
  86. +0
    -223
      react-ui/src/components/IconSelector/fields.ts
  87. +0
    -146
      react-ui/src/components/IconSelector/index.tsx
  88. +0
    -137
      react-ui/src/components/IconSelector/style.less
  89. +0
    -40
      react-ui/src/components/IconSelector/themeIcons.tsx
  90. +40
    -0
      react-ui/src/components/InfoGroup/InfoGroupTitle.less
  91. +31
    -0
      react-ui/src/components/InfoGroup/InfoGroupTitle.tsx
  92. +11
    -0
      react-ui/src/components/InfoGroup/index.less
  93. +43
    -0
      react-ui/src/components/InfoGroup/index.tsx
  94. +6
    -0
      react-ui/src/components/KFBreadcrumb/index.tsx
  95. +0
    -7
      react-ui/src/components/KFConfirmModal/index.less
  96. +0
    -37
      react-ui/src/components/KFConfirmModal/index.tsx
  97. +1
    -1
      react-ui/src/components/KFEmpty/index.less
  98. +16
    -6
      react-ui/src/components/KFEmpty/index.tsx
  99. +9
    -3
      react-ui/src/components/KFIcon/index.tsx
  100. +0
    -0
      react-ui/src/components/KFModal/KFModalTitle.less

+ 5
- 0
.gitignore View File

@@ -55,3 +55,8 @@ mvnw
/k8s/template-yaml/deploy/
/k8s/dockerfiles/html/
/k8s/dockerfiles/jar

# web
**/node_modules

*storybook.log

+ 7
- 1
k8s/build-java.sh View File

@@ -6,8 +6,14 @@ baseDir="/home/somuns/ci4s"
#判断$1是否为all,如果是,则编译所有模块,否则只编译management-platform模块
if [ "$1" == "all" ]; then
buildDir=$baseDir
else
elif [ "$1" == "manage" ]; then
buildDir="$baseDir/ruoyi-modules/management-platform"
elif [ "$1" == "auth" ]; then
buildDir="$baseDir/ruoyi-auth"
elif [ "$1" == "gateway" ]; then
buildDir="$baseDir/ruoyi-gateway"
elif [ "$1" == "system" ]; then
buildDir="$baseDir/ruoyi-modules/ruoyi-system"
fi

echo "Building $buildDir"


+ 47
- 28
k8s/build.sh View File

@@ -13,7 +13,7 @@ show_help() {
echo
echo "Options:"
echo " -b Branch to deploy, default is master"
echo " -s Service to deploy (manage-front, manage, front, all, default is manage-front)"
echo " -s Service to deploy (manage-front, manage, front, all, system, default is manage-front)"
echo " -h Show this help message"
}

@@ -30,7 +30,7 @@ done
echo "branch: $branch"
echo "service: $service"

valid_services=("manage-front" "manage" "front" "all")
valid_services=("manage-front" "manage" "front" "all" "auth" "gateway" "system")
if [[ ! " ${valid_services[@]} " =~ " $service " ]]; then
echo "Invalid service name: $service" >&2
echo "Valid services are: ${valid_services[*]}"
@@ -41,25 +41,6 @@ fi
baseDir="/home/somuns/ci4s"
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
@@ -73,7 +54,7 @@ compile_front() {
# 编译前端
docker run -v ${baseDir}:${baseDir} \
-e http_proxy=http://172.20.32.253:3128 -e https_proxy=http://172.20.32.253:3128 \
172.20.32.187/ci4s/node:16.16.0 ${baseDir}/k8s/build-node.sh
172.20.32.187/tempimagefile/node:18.16.0 ${baseDir}/k8s/build-node.sh
if [ $? -ne 0 ]; then
echo "编译失败,请检查代码!"
exit 1
@@ -99,13 +80,45 @@ compile_java() {
fi

# 复制jar包
cp -rf ${baseDir}/ruoyi-modules/management-platform/target/management-platform.jar ${baseDir}/k8s/dockerfiles/jar/management-platform.jar
if [ $? -ne 0 ]; then
echo "复制jar包失败,请检查代码!"
exit 1
if [ "$param" == "manage-front" ] || [ "$param" == "manage" ]; then
cp -rf ${baseDir}/ruoyi-modules/management-platform/target/management-platform.jar ${baseDir}/k8s/dockerfiles/jar/management-platform.jar
if [ $? -ne 0 ]; then
echo "复制jar包失败,请检查代码!"
exit 1
fi
fi

if [ "$param" == "gateway" ]; then
cp -rf ${baseDir}/ruoyi-gateway/target/ruoyi-gateway.jar ${baseDir}/k8s/dockerfiles/jar/ruoyi-gateway.jar
if [ $? -ne 0 ]; then
echo "复制jar包失败,请检查代码!"
exit 1
fi
fi

if [ "$param" == "auth" ]; then
cp -rf ${baseDir}/ruoyi-auth/target/ruoyi-auth.jar ${baseDir}/k8s/dockerfiles/jar/ruoyi-auth.jar
if [ $? -ne 0 ]; then
echo "复制jar包失败,请检查代码!"
exit 1
fi
fi

if [ "$param" == "system" ]; then
cp -rf ${baseDir}/ruoyi-modules/ruoyi-system/target/ruoyi-modules-system.jar ${baseDir}/k8s/dockerfiles/jar/ruoyi-modules-system.jar
if [ $? -ne 0 ]; then
echo "复制jar包失败,请检查代码!"
exit 1
fi
fi

if [ "$param" == "all" ]; then
cp -rf ${baseDir}/ruoyi-modules/management-platform/target/management-platform.jar ${baseDir}/k8s/dockerfiles/jar/management-platform.jar
if [ $? -ne 0 ]; then
echo "复制jar包失败,请检查代码!"
exit 1
fi

cp -rf ${baseDir}/ruoyi-modules/ruoyi-system/target/ruoyi-modules-system.jar ${baseDir}/k8s/dockerfiles/jar/ruoyi-modules-system.jar
if [ $? -ne 0 ]; then
echo "复制jar包失败,请检查代码!"
@@ -126,13 +139,19 @@ compile_java() {
fi
}

if [ "$service" == "manage-front" ] || [ "$service" == "front" ]; then
if [ "$service" == "front" ]; then
# 编译前端
compile_front
fi

if [ "$service" == "manage-front" ]; then
# 编译前端
compile_front
# 编译java
compile_java "manage"
fi

if [ "$service" == "manage-front" ] || [ "$service" == "manage" ]; then
if [ "$service" != "manage-front" ] && [ "$service" != "all" ] && [ "$service" != "front" ]; then
# 编译java
compile_java $service
fi


+ 21
- 3
k8s/build_and_deploy.sh View File

@@ -7,7 +7,6 @@ startTime=$(date +%s)
baseDir="/home/somuns/ci4s"
cd ${baseDir}


#build
# 默认参数
branch="master"
@@ -20,7 +19,7 @@ show_help() {
echo
echo "Options:"
echo " -b Branch to deploy, default: master"
echo " -s Service to deploy (manage-front, manage, front, all, default: manage-front)"
echo " -s Service to deploy (manage-front, manage, front, all, system default: manage-front)"
echo " -e Environment (e.g., dev, test, default: dev)"
echo " -h Show this help message"
}
@@ -36,7 +35,26 @@ while getopts "b:s:e:h" opt; do
esac
done

valid_services=("manage-front" "manage" "front" "all")
# 拉取指定分支的最新代码
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

valid_services=("manage-front" "manage" "front" "all" "auth" "gateway" "system")
if [[ ! " ${valid_services[@]} " =~ " $service " ]]; then
echo "Invalid service name: $service" >&2
echo "Valid services are: ${valid_services[*]}"


+ 24
- 5
k8s/deploy.sh View File

@@ -10,7 +10,7 @@ show_help() {
echo "Usage: $0 [-s service] [-e environment]"
echo
echo "Options:"
echo " -s Service to deploy (manage-front, manage, front, all default: manage-front)"
echo " -s Service to deploy (manage-front, manage, front, all, system default: manage-front)"
echo " -e Environment (e.g., dev, test, default: dev)"
echo " -h Show this help message"
}
@@ -27,7 +27,7 @@ done

echo "Deploy service: $service, environment: $env"

valid_services=("manage-front" "manage" "front" "all")
valid_services=("manage-front" "manage" "front" "all" "auth" "gateway" "system")
if [[ ! " ${valid_services[@]} " =~ " $service " ]]; then
echo "Invalid service name: $service" >&2
echo "Valid services are: ${valid_services[*]}"
@@ -43,7 +43,7 @@ fi

# 根据环境设置 IP 地址
if [ "$env" == "dev" ]; then
remote_ip="172.20.32.181"
remote_ip="172.20.32.197"
elif [ "$env" == "test" ]; then
remote_ip="172.20.32.185"
else
@@ -129,15 +129,34 @@ build_and_deploy() {
deploy_service ${yaml_file}
}

if [ "$service" == "front" ]; then
build_and_deploy "nginx-dockerfile" "172.20.32.187/ci4s/ci4s-front:${tag}" "k8s-12front.yaml"
fi

# 构建和部署 manage 服务
if [ "$service" == "manage-front" ] || [ "$service" == "manage" ]; then
if [ "$service" == "manage" ]; then
build_and_deploy "managent-dockerfile" "172.20.32.187/ci4s/ci4s-managent:${tag}" "k8s-7management.yaml"
fi

if [ "$service" == "auth" ]; then
#部署认证中心
build_and_deploy "auth-dockerfile" "172.20.32.187/ci4s/ci4s-auth:${tag}" "k8s-5auth.yaml"
fi

if [ "$service" == "gateway" ]; then
#部署网关
build_and_deploy "gateway-dockerfile" "172.20.32.187/ci4s/ci4s-gateway:${tag}" "k8s-4gateway.yaml"
fi

if [ "$service" == "system" ]; then
#部署系统服务
build_and_deploy "system-dockerfile" "172.20.32.187/ci4s/ci4s-system:${tag}" "k8s-6system.yaml"
fi

# 构建和部署 front 服务
if [ "$service" == "manage-front" ] || [ "$service" == "front" ]; then
if [ "$service" == "manage-front" ]; then
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"
fi




+ 0
- 36
k8s/k8s-10gen.yaml View File

@@ -1,36 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: ci4s-gen-deployment
namespace: ci4s-test
spec:
replicas: 1
selector:
matchLabels:
app: ci4s-gen
template:
metadata:
labels:
app: ci4s-gen
spec:
containers:
- name: ci4s-gen
image: ci4s-gen:v1.0
ports:
- containerPort: 9202

---
apiVersion: v1
kind: Service
metadata:
name: ci4s-gen-service
namespace: ci4s-test
spec:
type: NodePort
ports:
- port: 9202
nodePort: 31211
protocol: TCP
selector:
app: ci4s-gen


+ 0
- 36
k8s/k8s-11visual.yaml View File

@@ -1,36 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: ci4s-visual-deployment
namespace: ci4s-test
spec:
replicas: 1
selector:
matchLabels:
app: ci4s-visual
template:
metadata:
labels:
app: ci4s-visual
spec:
containers:
- name: ci4s-visual
image: ci4s-visual:v1.0
ports:
- containerPort: 9100

---
apiVersion: v1
kind: Service
metadata:
name: ci4s-visual-service
namespace: ci4s-test
spec:
type: NodePort
ports:
- port: 9100
nodePort: 31212
protocol: TCP
selector:
app: ci4s-visual


+ 0
- 36
k8s/k8s-12front.yaml View File

@@ -1,36 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: ci4s-front-deployment
namespace: argo
spec:
replicas: 1
selector:
matchLabels:
app: ci4s-front
template:
metadata:
labels:
app: ci4s-front
spec:
containers:
- name: ci4s-front
image: 172.20.32.187/ci4s/ci4s-front:20240401
ports:
- containerPort: 8000

---
apiVersion: v1
kind: Service
metadata:
name: ci4s-front-service
namespace: argo
spec:
type: NodePort
ports:
- port: 8000
nodePort: 31213
protocol: TCP
selector:
app: ci4s-front


+ 0
- 62
k8s/k8s-3nacos.yaml View File

@@ -1,62 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
namespace: argo
name: nacos-ci4s
labels:
app: nacos-ci4s
spec:
replicas: 1
selector:
matchLabels:
app: nacos-ci4s
template:
metadata:
labels:
app: nacos-ci4s
spec:
containers:
- name: nacos-ci4s
image: nacos/nacos-server:v2.2.0
env:
- name: SPRING_DATASOURCE_PLATFORM
value: mysql
- name: MODE
value: standalone
- name: MYSQL_SERVICE_HOST
value: mysql.argo.svc
- name: MYSQL_SERVICE_PORT
value: "3306"
- name: MYSQL_SERVICE_DB_NAME
value: nacos-ci4s-config
- name: MYSQL_SERVICE_USER
value: root
- name: MYSQL_SERVICE_PASSWORD
value: qazxc123456.
ports:
- containerPort: 8848
- containerPort: 9848
restartPolicy: Always

---

apiVersion: v1
kind: Service
metadata:
namespace: argo
name: nacos-ci4s
labels:
app: nacos-ci4s
spec:
type: NodePort
selector:
app: nacos-ci4s
ports:
- port: 8848
targetPort: 8848
nodePort: 31203
name: web
- port: 9848
targetPort: 9848
nodePort: 31204
name: podsa

+ 0
- 36
k8s/k8s-4gateway.yaml View File

@@ -1,36 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: ci4s-gateway-deployment
namespace: argo
spec:
replicas: 1
selector:
matchLabels:
app: ci4s-gateway
template:
metadata:
labels:
app: ci4s-gateway
spec:
containers:
- name: ci4s-gateway
image: 172.20.32.187/ci4s/ci4s-gateway:20240401
ports:
- containerPort: 8082

---
apiVersion: v1
kind: Service
metadata:
name: ci4s-gateway-service
namespace: argo
spec:
type: NodePort
ports:
- port: 8082
nodePort: 31205
protocol: TCP
selector:
app: ci4s-gateway


+ 0
- 36
k8s/k8s-5auth.yaml View File

@@ -1,36 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: ci4s-auth-deployment
namespace: argo
spec:
replicas: 1
selector:
matchLabels:
app: ci4s-auth
template:
metadata:
labels:
app: ci4s-auth
spec:
containers:
- name: ci4s-auth
image: 172.20.32.187/ci4s/ci4s-auth:20240401
ports:
- containerPort: 9200

---
apiVersion: v1
kind: Service
metadata:
name: ci4s-auth-service
namespace: argo
spec:
type: NodePort
ports:
- port: 9200
nodePort: 31206
protocol: TCP
selector:
app: ci4s-auth


+ 0
- 36
k8s/k8s-6system.yaml View File

@@ -1,36 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: ci4s-system-deployment
namespace: argo
spec:
replicas: 1
selector:
matchLabels:
app: ci4s-system
template:
metadata:
labels:
app: ci4s-system
spec:
containers:
- name: ci4s-system
image: 172.20.32.187/ci4s/ci4s-system:20240401
ports:
- containerPort: 9201

---
apiVersion: v1
kind: Service
metadata:
name: ci4s-system-service
namespace: argo
spec:
type: NodePort
ports:
- port: 9201
nodePort: 31207
protocol: TCP
selector:
app: ci4s-system


+ 0
- 44
k8s/k8s-7management.yaml View File

@@ -1,44 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: ci4s-management-platform-deployment
namespace: argo
spec:
replicas: 1
selector:
matchLabels:
app: ci4s-management-platform
template:
metadata:
labels:
app: ci4s-management-platform
spec:
containers:
- name: ci4s-management-platform
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
kind: Service
metadata:
name: ci4s-management-platform-service
namespace: argo
spec:
type: NodePort
ports:
- port: 9213
nodePort: 31208
protocol: TCP
selector:
app: ci4s-management-platform


+ 0
- 36
k8s/k8s-8file.yaml View File

@@ -1,36 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: ci4s-file-deployment
namespace: ci4s-test
spec:
replicas: 1
selector:
matchLabels:
app: ci4s-file
template:
metadata:
labels:
app: ci4s-file
spec:
containers:
- name: ci4s-file
image: ci4s-file:v1.0
ports:
- containerPort: 9300

---
apiVersion: v1
kind: Service
metadata:
name: ci4s-file-service
namespace: ci4s-test
spec:
type: NodePort
ports:
- port: 9300
nodePort: 31209
protocol: TCP
selector:
app: ci4s-file


+ 0
- 36
k8s/k8s-9job.yaml View File

@@ -1,36 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: ci4s-job-deployment
namespace: ci4s-test
spec:
replicas: 1
selector:
matchLabels:
app: ci4s-job
template:
metadata:
labels:
app: ci4s-job
spec:
containers:
- name: ci4s-job
image: ci4s-job:v1.0
ports:
- containerPort: 9203

---
apiVersion: v1
kind: Service
metadata:
name: ci4s-job-service
namespace: ci4s-test
spec:
type: NodePort
ports:
- port: 9203
nodePort: 31210
protocol: TCP
selector:
app: ci4s-job


+ 0
- 36
k8s/template-yaml/deploy/k8s-12front.yaml View File

@@ -1,36 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: ci4s-front-deployment
namespace: argo
spec:
replicas: 1
selector:
matchLabels:
app: ci4s-front
template:
metadata:
labels:
app: ci4s-front
spec:
containers:
- name: ci4s-front
image: 172.20.32.187/ci4s/ci4s-front:202406120836
ports:
- containerPort: 8000

---
apiVersion: v1
kind: Service
metadata:
name: ci4s-front-service
namespace: argo
spec:
type: NodePort
ports:
- port: 8000
nodePort: 31213
protocol: TCP
selector:
app: ci4s-front


+ 0
- 53
k8s/template-yaml/deploy/k8s-7management.yaml View File

@@ -1,53 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: ci4s-management-platform-deployment
namespace: argo
spec:
replicas: 1
selector:
matchLabels:
app: ci4s-management-platform
template:
metadata:
labels:
app: ci4s-management-platform
spec:
containers:
- name: ci4s-management-platform
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
metadata:
name: ci4s-management-platform-service
namespace: argo
spec:
type: NodePort
ports:
- name: http
port: 9213
nodePort: 31208
protocol: TCP
- name: debug
nodePort: 34567
port: 5005
protocol: TCP
targetPort: 5005
selector:
app: ci4s-management-platform


+ 43
- 0
k8s/template-yaml/k8s-13oauth2.yaml View File

@@ -0,0 +1,43 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: ci4s-oauth2-authenticator-deployment
namespace: argo
spec:
replicas: 1
selector:
matchLabels:
app: ci4s-oauth2-authenticator
template:
metadata:
labels:
app: ci4s-oauth2-authenticator
spec:
containers:
- name: ci4s-oauth2-authenticator
image: 172.20.32.187/ci4s/spring-oauth2-authenticator:latest
env:
- name: DB_URL
value: mysql.argo.svc:3306
- name: DB_USERNAME
value: root
- name: DB_PASSWORD
value: qazxc123456.
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: ci4s-oauth2-authenticator-service
namespace: argo
spec:
type: NodePort
ports:
- name: http
port: 8080
nodePort: 31080
protocol: TCP
selector:
app: ci4s-oauth2-authenticator


+ 5
- 1
k8s/template-yaml/k8s-3nacos.yaml View File

@@ -17,7 +17,7 @@ spec:
spec:
containers:
- name: nacos-ci4s
image: nacos/nacos-server:v2.2.0
image: 172.20.32.187/ci4s/nacos-server:v2.2.0
env:
- name: SPRING_DATASOURCE_PLATFORM
value: mysql
@@ -37,6 +37,10 @@ spec:
- containerPort: 8848
- containerPort: 9848
- containerPort: 9849
initContainers:
- name: init-mydb-check
image: 172.20.32.187/ci4s/busybox:1.31
command: [ 'sh', '-c', 'nc -zv mysql.argo.svc 3306' ]
restartPolicy: Always

---


+ 25
- 2
k8s/template-yaml/k8s-7management.yaml View File

@@ -16,6 +16,8 @@ spec:
containers:
- name: ci4s-management-platform
image: ${k8s-7management-image}
securityContext:
privileged: true
env:
- name: TZ
value: Asia/Shanghai
@@ -26,10 +28,31 @@ spec:
volumeMounts:
- name: resource-volume
mountPath: /home/resource/
subPath: mini-model-platform-data
mountPropagation: Bidirectional
volumes:
- name: resource-volume
persistentVolumeClaim:
claimName: platform-data-pvc-nfs
hostPath:
path: /platform-data
initContainers:
- name: init-fs-check
image: 172.20.32.187/ci4s/ci4s-managent:202502141722
securityContext:
privileged: true
volumeMounts:
- name: resource-volume
mountPath: /home/resource/
subPath: mini-model-platform-data
mountPropagation: Bidirectional
command: [ "/bin/sh", "-c" ]
args:
- |
mounted=$(findmnt /home/resource/ | grep 'fuse.juicefs')
if [ -z "$mounted" ]; then
echo "/platform-data not mounted";
exit 1
fi
restartPolicy: Always
---
apiVersion: v1
kind: Service


+ 68
- 0
k8s/template-yaml/rolebindings.yaml View File

@@ -0,0 +1,68 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: custom-workflow
namespace: argo
rules:
- apiGroups:
- argoproj.io
resources:
- workflows
verbs:
- create
- get
- list
- watch
- update
- patch
- delete
- apiGroups:
- ""
resources:
- pods
- services
verbs:
- get
- list
- watch
- create
- update
- patch
- delete
- apiGroups:
- ""
resources:
- pods/exec
verbs:
- create
- get
- list
- watch
- update
- patch
- delete
- apiGroups:
- "apps"
resources:
- deployments
verbs:
- get
- list
- watch
- create
- update
- patch
- delete
---

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: custom-workflow-default
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: custom-workflow
subjects:
- kind: ServiceAccount
name: default

+ 2
- 1
react-ui/.eslintignore View File

@@ -5,4 +5,5 @@
public
dist
.umi
mock
mock
/src/iconfont/

+ 7
- 1
react-ui/.eslintrc.js View File

@@ -1,10 +1,16 @@
module.exports = {
extends: [require.resolve('@umijs/lint/dist/config/eslint')],
extends: [
require.resolve('@umijs/lint/dist/config/eslint'),
'plugin:react/recommended',
"plugin:react-hooks/recommended"
],
globals: {
page: true,
REACT_APP_ENV: true,
},
rules: {
'@typescript-eslint/no-use-before-define': 'off',
'react/react-in-jsx-scope': 'off',
'react/display-name': 'off'
},
};

+ 2
- 7
react-ui/.gitignore View File

@@ -41,10 +41,5 @@ screenshot
build

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

*storybook.log

+ 1
- 0
react-ui/.nvmrc View File

@@ -0,0 +1 @@
v18.16.0

+ 16
- 0
react-ui/.storybook/babel-plugin-auto-css-modules.js View File

@@ -0,0 +1,16 @@
export default function(babel) {
const { types: t } = babel;
return {
visitor: {
ImportDeclaration(path) {
const source = path.node.source.value;
// console.log("zzzz", source);
if (source.endsWith('.less')) {
if (path.node.specifiers.length > 0) {
path.node.source.value += "?modules";
}
}
},
},
};
};

+ 19
- 0
react-ui/.storybook/blocks/StoryName.tsx View File

@@ -0,0 +1,19 @@
import { Of, useOf } from '@storybook/blocks';

/**
* A block that displays the story name or title from the of prop
* - if a story reference is passed, it renders the story name
* - if a meta reference is passed, it renders the stories' title
* - if nothing is passed, it defaults to the primary story
*/
export const StoryName = ({ of }: { of?: Of }) => {
const resolvedOf = useOf(of || 'story', ['story', 'meta']);
switch (resolvedOf.type) {
case 'story': {
return <h3 className="css-wzniqs">{resolvedOf.story.name}</h3>;
}
case 'meta': {
return <h3 className="css-wzniqs">{resolvedOf.preparedMeta.title}</h3>;
}
}
};

+ 117
- 0
react-ui/.storybook/main.ts View File

@@ -0,0 +1,117 @@
import type { StorybookConfig } from '@storybook/react-webpack5';
import path from 'path';
import webpack from 'webpack';

const config: StorybookConfig = {
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
addons: [
// '@storybook/addon-webpack5-compiler-swc',
'@storybook/addon-webpack5-compiler-babel',
'@storybook/addon-onboarding',
'@storybook/addon-essentials',
'@chromatic-com/storybook',
'@storybook/addon-interactions',
],
framework: {
name: '@storybook/react-webpack5',
options: {},
},
staticDirs: ['../public'],
docs: {
defaultName: 'Documentation',
},
webpackFinal: async (config) => {
if (config.resolve) {
config.resolve.alias = {
...config.resolve.alias,
'@': path.resolve(__dirname, '../src'),
'@umijs/max$': path.resolve(__dirname, './mock/umijs.mock.tsx'),
};
}
if (config.module && config.module.rules) {
config.module.rules.push(
{
test: /\.less$/,
oneOf: [
{
resourceQuery: /modules/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 1,
import: true,
esModule: true,
modules: {
localIdentName: '[local]___[hash:base64:5]',
},
},
},
{
loader: 'less-loader',
options: {
lessOptions: {
javascriptEnabled: true, // 如果需要支持 Ant Design 的 Less 变量,开启此项
modifyVars: {
hack: 'true; @import "@/styles/theme.less";',
},
},
},
},
],
include: path.resolve(__dirname, '../src'), // 限制范围,避免处理 node_modules
},
{
use: [
'style-loader',
'css-loader',
{
loader: 'less-loader',
options: {
lessOptions: {
javascriptEnabled: true, // 如果需要支持 Ant Design 的 Less 变量,开启此项
modifyVars: {
hack: 'true; @import "@/styles/theme.less";',
},
},
},
},
],
include: path.resolve(__dirname, '../src'), // 限制范围,避免处理 node_modules
},
],
},
{
test: /\.(tsx?|jsx?)$/,
loader: 'ts-loader',
options: {
transpileOnly: true,
},
include: [
path.resolve(__dirname, '../src'), // 限制范围,避免处理 node_modules
path.resolve(__dirname, './'),
],
},
);
}
if (config.plugins) {
config.plugins.push(
new webpack.ProvidePlugin({
React: 'react', // 全局注入 React
}),
);
}

return config;
},
babel: async (config: any) => {
if (!config.plugins) {
config.plugins = [];
}

config.plugins.push(path.resolve(__dirname, './babel-plugin-auto-css-modules.js'));
return config;
},
};
export default config;

+ 6
- 0
react-ui/.storybook/manager.ts View File

@@ -0,0 +1,6 @@
import { addons } from '@storybook/manager-api';
import theme from './theme';

addons.setConfig({
theme: theme,
});

+ 19
- 0
react-ui/.storybook/mock/umijs.mock.tsx View File

@@ -0,0 +1,19 @@
export const Link = ({ to, children, ...props }: any) => (
<a href={typeof to === 'string' ? to : '#'} {...props}>
{children}
</a>
);

export const request = (url: string, options: any) => {
return fetch(url, options)
.then((res) => {
if (!res.ok) {
throw new Error(res.statusText);
}
return res;
})
.then((res) => res.json());
};

export { useNavigate, useParams, useSearchParams } from 'react-router-dom';
export const history = window.history;

+ 92
- 0
react-ui/.storybook/mock/websocket.mock.js View File

@@ -0,0 +1,92 @@
export const createWebSocketMock = () => {
class WebSocketMock {
constructor(url) {
this.url = url;
this.readyState = WebSocket.OPEN;
this.listeners = {};
this.count = 0;

console.log("Mock WebSocket connected to:", url);

// 模拟服务器推送消息
this.intervalId = setInterval(() => {
this.count += 1;
if (this.count > 5) {
this.count = 0;
clearInterval(this.intervalId);
return;
}
this.sendMessage(JSON.stringify(logStreamData));
}, 3000);
}

sendMessage(data) {
if (this.listeners["message"]) {
this.listeners["message"].forEach((callback) => callback({ data }));
}
}

addEventListener(event, callback) {
if (!this.listeners[event]) {
this.listeners[event] = [];
}
this.listeners[event].push(callback);
}

removeEventListener(event, callback) {
if (this.listeners[event]) {
this.listeners[event] = this.listeners[event].filter((cb) => cb !== callback);
}
}

close() {
this.readyState = WebSocket.CLOSED;
console.log("Mock WebSocket closed");
}
}

return WebSocketMock;
};

export const logStreamData = {
streams: [
{
stream: {
workflows_argoproj_io_completed: 'false',
workflows_argoproj_io_workflow: 'workflow-p2ddj',
container: 'init',
filename:
'/var/log/pods/argo_workflow-p2ddj-git-clone-f33abcda-3988047653_e31cf6be-e013-4885-9eb6-ec84f83b9ba9/init/0.log',
job: 'argo/workflow-p2ddj-git-clone-f33abcda-3988047653',
namespace: 'argo',
pod: 'workflow-p2ddj-git-clone-f33abcda-3988047653',
stream: 'stderr',
},
values: [
[
'1742179591969785990',
'time="2025-03-17T02:46:31.969Z" level=info msg="Starting Workflow Executor" version=v3.5.10\n',
],
],
},
{
stream: {
filename:
'/var/log/pods/argo_workflow-p2ddj-git-clone-f33abcda-3988047653_e31cf6be-e013-4885-9eb6-ec84f83b9ba9/init/0.log',
job: 'argo/workflow-p2ddj-git-clone-f33abcda-3988047653',
namespace: 'argo',
pod: 'workflow-p2ddj-git-clone-f33abcda-3988047653',
stream: 'stderr',
workflows_argoproj_io_completed: 'false',
workflows_argoproj_io_workflow: 'workflow-p2ddj',
container: 'init',
},
values: [
[
'1742179591973414064',
'time="2025-03-17T02:46:31.973Z" level=info msg="Using executor retry strategy" Duration=1s Factor=1.6 Jitter=0.5 Steps=5\n',
],
],
},
],
};

+ 112
- 0
react-ui/.storybook/preview.tsx View File

@@ -0,0 +1,112 @@
import '@/global.less';
import '@/overrides.less';
import themes from '@/styles/theme.less';
import type { Preview } from '@storybook/react';
import { App, ConfigProvider } from 'antd';
import zhCN from 'antd/locale/zh_CN';
import { initialize, mswLoader } from 'msw-storybook-addon';
import { createWebSocketMock } from './mock/websocket.mock';
import './storybook.css';

/*
* Initializes MSW
* See https://github.com/mswjs/msw-storybook-addon#configuring-msw
* to learn how to customize it
*/
initialize();

// 替换全局 WebSocket 为 Mock 版本
// @ts-ignore
global.WebSocket = createWebSocketMock();

const preview: Preview = {
parameters: {
controls: {
expanded: true,
sort: 'requiredFirst',
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
backgrounds: {
values: [
{ name: 'Dark', value: '#000' },
{ name: 'Gray', value: '#f9fafb' },
{ name: 'Light', value: '#FFF' },
],
default: 'Light',
},
options: {
storySort: {
method: 'alphabetical',
order: ['Documentation', 'Components'],
},
},
},
decorators: [
(Story) => (
<ConfigProvider
locale={zhCN}
theme={{
cssVar: true,
token: {
colorPrimary: themes['primaryColor'],
colorSuccess: themes['successColor'],
colorError: themes['errorColor'],
colorWarning: themes['warningColor'],
colorLink: themes['primaryColor'],
colorText: themes['textColor'],
controlHeightLG: 46,
},
components: {
Button: {
defaultBg: 'rgba(22, 100, 255, 0.06)',
defaultBorderColor: 'rgba(22, 100, 255, 0.11)',
defaultColor: themes['textColor'],
defaultHoverBg: 'rgba(22, 100, 255, 0.06)',
defaultHoverBorderColor: 'rgba(22, 100, 255, 0.5)',
defaultHoverColor: '#3F7FFF ',
defaultActiveBg: 'rgba(22, 100, 255, 0.12)',
defaultActiveBorderColor: 'rgba(22, 100, 255, 0.75)',
defaultActiveColor: themes['primaryColor'],
contentFontSize: parseInt(themes['fontSize']),
},
Input: {
inputFontSize: parseInt(themes['fontSizeInput']),
inputFontSizeLG: parseInt(themes['fontSizeInputLg']),
paddingBlockLG: 10,
},
Select: {
singleItemHeightLG: 46,
optionSelectedColor: themes['primaryColor'],
},
Table: {
headerBg: 'rgba(242, 244, 247, 0.36)',
headerBorderRadius: 4,
rowSelectedBg: 'rgba(22, 100, 255, 0.05)',
},
Tabs: {
titleFontSize: 16,
},
Form: {
labelColor: 'rgba(29, 29, 32, 0.8);',
},
Breadcrumb: {
iconFontSize: parseInt(themes['fontSize']),
linkColor: 'rgba(29, 29, 32, 0.7)',
separatorColor: 'rgba(29, 29, 32, 0.7)',
},
},
}}
>
<App message={{ maxCount: 3 }}>
<Story />
</App>
</ConfigProvider>
),
],
loaders: [mswLoader], // 👈 Add the MSW loader to all stories
};

export default preview;

+ 19
- 0
react-ui/.storybook/storybook.css View File

@@ -0,0 +1,19 @@
html,
body,
#root {
min-width: unset;
height: 100%;
margin: 0;
padding: 0;
overflow-y: visible;
}

.ant-input-search-large .ant-input-affix-wrapper, .ant-input-search-large .ant-input-search-button {
height: 46px;
}

*,
*::before,
*::after {
box-sizing: border-box;
}

+ 7
- 0
react-ui/.storybook/theme.ts View File

@@ -0,0 +1,7 @@
import { create } from '@storybook/theming';
export default create({
base: 'light',
brandTitle: '组件库文档',
brandUrl: 'https://storybook.js.org/docs',
brandTarget: '_blank',
});

+ 27
- 0
react-ui/.storybook/tsconfig.json View File

@@ -0,0 +1,27 @@
{
"compilerOptions": {
"target": "esnext", // 指定ECMAScript目标版本
"lib": ["dom", "dom.iterable", "esnext"], // 要包含在编译中的库文件列表
"allowJs": true, // 允许编译JavaScript文件
"skipLibCheck": true, // 跳过所有声明文件的类型检查
"esModuleInterop": true, // 禁用命名空间导入(import * as fs from "fs"),并启用CJS/AMD/UMD样式的导入(import fs from "fs")
"allowSyntheticDefaultImports": true, // 允许从没有默认导出的模块进行默认导入
"strict": true, // 启用所有严格类型检查选项
"forceConsistentCasingInFileNames": false, // 允许对同一文件的引用使用不一致的大小写
"module": "esnext", // 指定模块代码生成
"moduleResolution": "bundler", // 使用bundlers样式解析模块
"isolatedModules": true, // 无条件地为未解析的文件发出导入
"resolveJsonModule": true, // 包含.json扩展名的模块
"noEmit": true, // 不发出输出(即不编译代码,只进行类型检查)
"jsx": "react-jsx", // 在.tsx文件中支持JSX
"sourceMap": true, // 生成相应的.map文件
"declaration": true, // 生成相应的.d.ts文件
"noUnusedLocals": true, // 报告未使用的局部变量错误
"noUnusedParameters": true, // 报告未使用的参数错误
"incremental": true, // 通过读写磁盘上的文件来启用增量编译
"noFallthroughCasesInSwitch": true, // 报告switch语句中的fallthrough案例错误
"strictNullChecks": true, // 启用严格的null检查
"importHelpers": true,
"baseUrl": "./"
}
}

+ 20
- 0
react-ui/.storybook/typings.d.ts View File

@@ -0,0 +1,20 @@
declare module 'slash2';
declare module '*.css';
declare module '*.less';
declare module '*.scss';
declare module '*.sass';
declare module '*.svg';
declare module '*.png';
declare module '*.jpg';
declare module '*.jpeg';
declare module '*.gif';
declare module '*.bmp';
declare module '*.tiff';
declare module 'omit.js';
declare module 'numeral';
declare module '@antv/data-set';
declare module 'mockjs';
declare module 'react-fittext';
declare module 'bizcharts-plugin-slider';

declare const REACT_APP_ENV: 'test' | 'dev' | 'pre' | false;

+ 1
- 0
react-ui/config/config.ts View File

@@ -156,4 +156,5 @@ export default defineConfig({
},
javascriptEnabled: true,
},
valtio: {},
});

+ 123
- 29
react-ui/config/routes.ts View File

@@ -44,7 +44,7 @@ export default [
{
name: 'login',
path: '/user/login',
component: './User/Login',
component: process.env.NO_SSO ? './User/Login/login' : './User/Login',
},
],
},
@@ -145,6 +145,78 @@ export default [
},
],
},
{
name: '自动机器学习',
path: 'automl',
routes: [
{
name: '自动机器学习',
path: '',
component: './AutoML/List/index',
},
{
name: '实验详情',
path: 'info/:id',
component: './AutoML/Info/index',
},
{
name: '创建实验',
path: 'create',
component: './AutoML/Create/index',
},
{
name: '编辑实验',
path: 'edit/:id',
component: './AutoML/Create/index',
},
{
name: '复制实验',
path: 'copy/:id',
component: './AutoML/Create/index',
},
{
name: '实验实例详情',
path: 'instance/:autoMLId/:id',
component: './AutoML/Instance/index',
},
],
},
{
name: '超参数自动寻优',
path: 'hyperparameter',
routes: [
{
name: '超参数寻优',
path: '',
component: './HyperParameter/List/index',
},
{
name: '实验详情',
path: 'info/:id',
component: './HyperParameter/Info/index',
},
{
name: '创建实验',
path: 'create',
component: './HyperParameter/Create/index',
},
{
name: '编辑实验',
path: 'edit/:id',
component: './HyperParameter/Create/index',
},
{
name: '复制实验',
path: 'copy/:id',
component: './HyperParameter/Create/index',
},
{
name: '实验实例详情',
path: 'instance/:autoMLId/:id',
component: './HyperParameter/Instance/index',
},
],
},
],
},
{
@@ -219,39 +291,61 @@ export default [
},
],
},
],
},
{
name: '模型部署',
path: '/modelDeployment',
routes: [
{
name: '模型部署',
path: '',
component: './ModelDeployment/List',
},
{
name: '服务详情',
path: 'serviceInfo/:id',
component: './ModelDeployment/ServiceInfo',
},
{
name: '服务版本详情',
path: 'versionInfo/:id',
component: './ModelDeployment/VersionInfo',
},
{
name: '创建推理服务',
path: 'createService',
component: './ModelDeployment/CreateService',
},
{
name: '新增服务版本',
path: 'addVersion/:id',
component: './ModelDeployment/CreateVersion',
path: 'modelDeployment',
routes: [
{
name: '模型部署',
path: '',
component: './ModelDeployment/List',
},
{
name: '创建推理服务',
path: 'createService',
component: './ModelDeployment/CreateService',
},
{
name: '编辑推理服务',
path: 'editService/:serviceId',
component: './ModelDeployment/CreateService',
},
{
name: '服务详情',
path: 'serviceInfo/:serviceId',
routes: [
{
name: '服务详情',
path: '',
component: './ModelDeployment/ServiceInfo',
},
{
name: '新增服务版本',
path: 'createVersion',
component: './ModelDeployment/CreateVersion',
},
{
name: '更新服务版本',
path: 'updateVersion',
component: './ModelDeployment/CreateVersion',
},
{
name: '重启服务版本',
path: 'restartVersion',
component: './ModelDeployment/CreateVersion',
},
{
name: '服务版本详情',
path: 'versionInfo/:id',
component: './ModelDeployment/VersionInfo',
},
],
},
],
},
],
},

{
name: '应用开发',
path: '/appsDeployment',


+ 0
- 176
react-ui/mock/listTableList.ts View File

@@ -1,176 +0,0 @@
import { Request, Response } from 'express';
import moment from 'moment';
import { parse } from 'url';

// mock tableListDataSource
const genList = (current: number, pageSize: number) => {
const tableListDataSource: API.RuleListItem[] = [];

for (let i = 0; i < pageSize; i += 1) {
const index = (current - 1) * 10 + i;
tableListDataSource.push({
key: index,
disabled: i % 6 === 0,
href: 'https://ant.design',
avatar: [
'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
][i % 2],
name: `TradeCode ${index}`,
owner: '曲丽丽',
desc: '这是一段描述',
callNo: Math.floor(Math.random() * 1000),
status: Math.floor(Math.random() * 10) % 4,
updatedAt: moment().format('YYYY-MM-DD'),
createdAt: moment().format('YYYY-MM-DD'),
progress: Math.ceil(Math.random() * 100),
});
}
tableListDataSource.reverse();
return tableListDataSource;
};

let tableListDataSource = genList(1, 100);

function getRule(req: Request, res: Response, u: string) {
let realUrl = u;
if (!realUrl || Object.prototype.toString.call(realUrl) !== '[object String]') {
realUrl = req.url;
}
const { current = 1, pageSize = 10 } = req.query;
const params = parse(realUrl, true).query as unknown as API.PageParams &
API.RuleListItem & {
sorter: any;
filter: any;
};

let dataSource = [...tableListDataSource].slice(
((current as number) - 1) * (pageSize as number),
(current as number) * (pageSize as number),
);
if (params.sorter) {
const sorter = JSON.parse(params.sorter);
dataSource = dataSource.sort((prev, next) => {
let sortNumber = 0;
(Object.keys(sorter) as Array<keyof API.RuleListItem>).forEach((key) => {
let nextSort = next?.[key] as number;
let preSort = prev?.[key] as number;
if (sorter[key] === 'descend') {
if (preSort - nextSort > 0) {
sortNumber += -1;
} else {
sortNumber += 1;
}
return;
}
if (preSort - nextSort > 0) {
sortNumber += 1;
} else {
sortNumber += -1;
}
});
return sortNumber;
});
}
if (params.filter) {
const filter = JSON.parse(params.filter as any) as {
[key: string]: string[];
};
if (Object.keys(filter).length > 0) {
dataSource = dataSource.filter((item) => {
return (Object.keys(filter) as Array<keyof API.RuleListItem>).some((key) => {
if (!filter[key]) {
return true;
}
if (filter[key].includes(`${item[key]}`)) {
return true;
}
return false;
});
});
}
}

if (params.name) {
dataSource = dataSource.filter((data) => data?.name?.includes(params.name || ''));
}
const result = {
data: dataSource,
total: tableListDataSource.length,
success: true,
pageSize,
current: parseInt(`${params.current}`, 10) || 1,
};

return res.json(result);
}

function postRule(req: Request, res: Response, u: string, b: Request) {
let realUrl = u;
if (!realUrl || Object.prototype.toString.call(realUrl) !== '[object String]') {
realUrl = req.url;
}

const body = (b && b.body) || req.body;
const { method, name, desc, key } = body;

switch (method) {
/* eslint no-case-declarations:0 */
case 'delete':
tableListDataSource = tableListDataSource.filter((item) => key.indexOf(item.key) === -1);
break;
case 'post':
(() => {
const i = Math.ceil(Math.random() * 10000);
const newRule: API.RuleListItem = {
key: tableListDataSource.length,
href: 'https://ant.design',
avatar: [
'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
][i % 2],
name,
owner: '曲丽丽',
desc,
callNo: Math.floor(Math.random() * 1000),
status: Math.floor(Math.random() * 10) % 2,
updatedAt: moment().format('YYYY-MM-DD'),
createdAt: moment().format('YYYY-MM-DD'),
progress: Math.ceil(Math.random() * 100),
};
tableListDataSource.unshift(newRule);
return res.json(newRule);
})();
return;

case 'update':
(() => {
let newRule = {};
tableListDataSource = tableListDataSource.map((item) => {
if (item.key === key) {
newRule = { ...item, desc, name };
return { ...item, desc, name };
}
return item;
});
return res.json(newRule);
})();
return;
default:
break;
}

const result = {
list: tableListDataSource,
pagination: {
total: tableListDataSource.length,
},
};

res.json(result);
}

export default {
'GET /api/rule': getRule,
'POST /api/rule': postRule,
};

+ 31
- 1
react-ui/package.json View File

@@ -8,6 +8,7 @@
"build": "max build",
"deploy": "npm run build && npm run gh-pages",
"dev": "npm run start:dev",
"dev-no-sso": "cross-env NO_SSO=true npm run start:dev",
"docker-hub:build": "docker build -f Dockerfile.hub -t ant-design-pro ./",
"docker-prod:build": "docker-compose -f ./docker/docker-compose.yml build",
"docker-prod:dev": "docker-compose -f ./docker/docker-compose.yml up",
@@ -36,6 +37,10 @@
"start:mock": "cross-env REACT_APP_ENV=dev UMI_ENV=dev max dev",
"start:pre": "cross-env REACT_APP_ENV=pre UMI_ENV=dev max dev",
"start:test": "cross-env REACT_APP_ENV=test MOCK=none UMI_ENV=dev max dev",
"storybook": "storybook dev -p 6006",
"storybook-build": "storybook build",
"storybook-docs": "storybook dev --docs",
"storybook-docs-build": "storybook build --docs",
"test": "jest",
"test:coverage": "npm run jest -- --coverage",
"test:update": "npm run jest -- -u",
@@ -67,7 +72,6 @@
"fabric": "^5.3.0",
"highlight.js": "^11.7.0",
"lodash": "^4.17.21",
"moment": "^2.30.1",
"omit.js": "^2.0.2",
"pnpm": "^8.9.0",
"query-string": "^8.1.0",
@@ -84,6 +88,19 @@
},
"devDependencies": {
"@ant-design/pro-cli": "^3.1.0",
"@chromatic-com/storybook": "~3.2.4",
"@storybook/addon-essentials": "~8.5.3",
"@storybook/addon-interactions": "~8.5.3",
"@storybook/addon-onboarding": "~8.5.3",
"@storybook/addon-styling-webpack": "~1.0.1",
"@storybook/addon-webpack5-compiler-babel": "~3.0.5",
"@storybook/addon-webpack5-compiler-swc": "~2.0.0",
"@storybook/blocks": "~8.5.3",
"@storybook/manager-api": "~8.6.0",
"@storybook/react": "~8.5.3",
"@storybook/react-webpack5": "~8.5.3",
"@storybook/test": "~8.5.3",
"@storybook/theming": "~8.6.0",
"@testing-library/react": "^14.0.0",
"@types/antd": "^1.0.0",
"@types/express": "^4.17.14",
@@ -97,15 +114,23 @@
"@umijs/max": "^4.0.66",
"cross-env": "^7.0.3",
"eslint": "^8.39.0",
"eslint-plugin-react-hooks": "~5.2.0",
"eslint-plugin-storybook": "~0.11.2",
"express": "^4.18.2",
"gh-pages": "^5.0.0",
"husky": "^8.0.3",
"jest": "^29.5.0",
"jest-environment-jsdom": "^29.5.0",
"less": "~4.2.2",
"less-loader": "~12.2.0",
"lint-staged": "^13.2.0",
"mockjs": "^1.1.0",
"msw": "~2.7.0",
"msw-storybook-addon": "~2.0.4",
"prettier": "^2.8.1",
"storybook": "~8.5.3",
"swagger-ui-dist": "^4.18.2",
"ts-loader": "~9.5.2",
"ts-node": "^10.9.1",
"typescript": "^5.0.4",
"umi-presets-pro": "^2.0.0"
@@ -141,5 +166,10 @@
"CNAME",
"create-umi"
]
},
"msw": {
"workerDirectory": [
"public"
]
}
}

+ 0
- 1
react-ui/public/CNAME View File

@@ -1 +0,0 @@
preview.pro.ant.design

BIN
react-ui/public/assets/images/component-icon-9-Failed.png View File

Before After
Width: 108  |  Height: 108  |  Size: 4.0 kB

BIN
react-ui/public/assets/images/component-icon-9-Omitted.png View File

Before After
Width: 108  |  Height: 108  |  Size: 3.8 kB

BIN
react-ui/public/assets/images/component-icon-9-Pending.png View File

Before After
Width: 108  |  Height: 108  |  Size: 4.0 kB

BIN
react-ui/public/assets/images/component-icon-9-Running.png View File

Before After
Width: 108  |  Height: 108  |  Size: 3.8 kB

BIN
react-ui/public/assets/images/component-icon-9-Skipped.png View File

Before After
Width: 108  |  Height: 108  |  Size: 3.8 kB

BIN
react-ui/public/assets/images/component-icon-9-Succeeded.png View File

Before After
Width: 108  |  Height: 108  |  Size: 4.1 kB

BIN
react-ui/public/assets/images/component-icon-9.png View File

Before After
Width: 72  |  Height: 72  |  Size: 2.3 kB

BIN
react-ui/public/favicon-cc.ico View File

Before After

+ 0
- 1
react-ui/public/logo.svg View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" version="1.1" viewBox="0 0 200 200"><title>Group 28 Copy 5</title><desc>Created with Sketch.</desc><defs><linearGradient id="linearGradient-1" x1="62.102%" x2="108.197%" y1="0%" y2="37.864%"><stop offset="0%" stop-color="#4285EB"/><stop offset="100%" stop-color="#2EC7FF"/></linearGradient><linearGradient id="linearGradient-2" x1="69.644%" x2="54.043%" y1="0%" y2="108.457%"><stop offset="0%" stop-color="#29CDFF"/><stop offset="37.86%" stop-color="#148EFF"/><stop offset="100%" stop-color="#0A60FF"/></linearGradient><linearGradient id="linearGradient-3" x1="69.691%" x2="16.723%" y1="-12.974%" y2="117.391%"><stop offset="0%" stop-color="#FA816E"/><stop offset="41.473%" stop-color="#F74A5C"/><stop offset="100%" stop-color="#F51D2C"/></linearGradient><linearGradient id="linearGradient-4" x1="68.128%" x2="30.44%" y1="-35.691%" y2="114.943%"><stop offset="0%" stop-color="#FA8E7D"/><stop offset="51.264%" stop-color="#F74A5C"/><stop offset="100%" stop-color="#F51D2C"/></linearGradient></defs><g id="Page-1" fill="none" fill-rule="evenodd" stroke="none" stroke-width="1"><g id="logo" transform="translate(-20.000000, -20.000000)"><g id="Group-28-Copy-5" transform="translate(20.000000, 20.000000)"><g id="Group-27-Copy-3"><g id="Group-25" fill-rule="nonzero"><g id="2"><path id="Shape" fill="url(#linearGradient-1)" d="M91.5880863,4.17652823 L4.17996544,91.5127728 C-0.519240605,96.2081146 -0.519240605,103.791885 4.17996544,108.487227 L91.5880863,195.823472 C96.2872923,200.518814 103.877304,200.518814 108.57651,195.823472 L145.225487,159.204632 C149.433969,154.999611 149.433969,148.181924 145.225487,143.976903 C141.017005,139.771881 134.193707,139.771881 129.985225,143.976903 L102.20193,171.737352 C101.032305,172.906015 99.2571609,172.906015 98.0875359,171.737352 L28.285908,101.993122 C27.1162831,100.824459 27.1162831,99.050775 28.285908,97.8821118 L98.0875359,28.1378823 C99.2571609,26.9692191 101.032305,26.9692191 102.20193,28.1378823 L129.985225,55.8983314 C134.193707,60.1033528 141.017005,60.1033528 145.225487,55.8983314 C149.433969,51.69331 149.433969,44.8756232 145.225487,40.6706018 L108.58055,4.05574592 C103.862049,-0.537986846 96.2692618,-0.500797906 91.5880863,4.17652823 Z"/><path id="Shape" fill="url(#linearGradient-2)" d="M91.5880863,4.17652823 L4.17996544,91.5127728 C-0.519240605,96.2081146 -0.519240605,103.791885 4.17996544,108.487227 L91.5880863,195.823472 C96.2872923,200.518814 103.877304,200.518814 108.57651,195.823472 L145.225487,159.204632 C149.433969,154.999611 149.433969,148.181924 145.225487,143.976903 C141.017005,139.771881 134.193707,139.771881 129.985225,143.976903 L102.20193,171.737352 C101.032305,172.906015 99.2571609,172.906015 98.0875359,171.737352 L28.285908,101.993122 C27.1162831,100.824459 27.1162831,99.050775 28.285908,97.8821118 L98.0875359,28.1378823 C100.999864,25.6271836 105.751642,20.541824 112.729652,19.3524487 C117.915585,18.4685261 123.585219,20.4140239 129.738554,25.1889424 C125.624663,21.0784292 118.571995,14.0340304 108.58055,4.05574592 C103.862049,-0.537986846 96.2692618,-0.500797906 91.5880863,4.17652823 Z"/></g><path id="Shape" fill="url(#linearGradient-3)" d="M153.685633,135.854579 C157.894115,140.0596 164.717412,140.0596 168.925894,135.854579 L195.959977,108.842726 C200.659183,104.147384 200.659183,96.5636133 195.960527,91.8688194 L168.690777,64.7181159 C164.472332,60.5180858 157.646868,60.5241425 153.435895,64.7316526 C149.227413,68.936674 149.227413,75.7543607 153.435895,79.9593821 L171.854035,98.3623765 C173.02366,99.5310396 173.02366,101.304724 171.854035,102.473387 L153.685633,120.626849 C149.47715,124.83187 149.47715,131.649557 153.685633,135.854579 Z"/></g><ellipse id="Combined-Shape" cx="100.519" cy="100.437" fill="url(#linearGradient-4)" rx="23.6" ry="23.581"/></g></g></g></g></svg>

+ 307
- 0
react-ui/public/mockServiceWorker.js View File

@@ -0,0 +1,307 @@
/* eslint-disable */
/* tslint:disable */

/**
* Mock Service Worker.
* @see https://github.com/mswjs/msw
* - Please do NOT modify this file.
* - Please do NOT serve this file on production.
*/

const PACKAGE_VERSION = '2.7.0'
const INTEGRITY_CHECKSUM = '00729d72e3b82faf54ca8b9621dbb96f'
const IS_MOCKED_RESPONSE = Symbol('isMockedResponse')
const activeClientIds = new Set()

self.addEventListener('install', function () {
self.skipWaiting()
})

self.addEventListener('activate', function (event) {
event.waitUntil(self.clients.claim())
})

self.addEventListener('message', async function (event) {
const clientId = event.source.id

if (!clientId || !self.clients) {
return
}

const client = await self.clients.get(clientId)

if (!client) {
return
}

const allClients = await self.clients.matchAll({
type: 'window',
})

switch (event.data) {
case 'KEEPALIVE_REQUEST': {
sendToClient(client, {
type: 'KEEPALIVE_RESPONSE',
})
break
}

case 'INTEGRITY_CHECK_REQUEST': {
sendToClient(client, {
type: 'INTEGRITY_CHECK_RESPONSE',
payload: {
packageVersion: PACKAGE_VERSION,
checksum: INTEGRITY_CHECKSUM,
},
})
break
}

case 'MOCK_ACTIVATE': {
activeClientIds.add(clientId)

sendToClient(client, {
type: 'MOCKING_ENABLED',
payload: {
client: {
id: client.id,
frameType: client.frameType,
},
},
})
break
}

case 'MOCK_DEACTIVATE': {
activeClientIds.delete(clientId)
break
}

case 'CLIENT_CLOSED': {
activeClientIds.delete(clientId)

const remainingClients = allClients.filter((client) => {
return client.id !== clientId
})

// Unregister itself when there are no more clients
if (remainingClients.length === 0) {
self.registration.unregister()
}

break
}
}
})

self.addEventListener('fetch', function (event) {
const { request } = event

// Bypass navigation requests.
if (request.mode === 'navigate') {
return
}

// Opening the DevTools triggers the "only-if-cached" request
// that cannot be handled by the worker. Bypass such requests.
if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') {
return
}

// Bypass all requests when there are no active clients.
// Prevents the self-unregistered worked from handling requests
// after it's been deleted (still remains active until the next reload).
if (activeClientIds.size === 0) {
return
}

// Generate unique request ID.
const requestId = crypto.randomUUID()
event.respondWith(handleRequest(event, requestId))
})

async function handleRequest(event, requestId) {
const client = await resolveMainClient(event)
const response = await getResponse(event, client, requestId)

// Send back the response clone for the "response:*" life-cycle events.
// Ensure MSW is active and ready to handle the message, otherwise
// this message will pend indefinitely.
if (client && activeClientIds.has(client.id)) {
;(async function () {
const responseClone = response.clone()

sendToClient(
client,
{
type: 'RESPONSE',
payload: {
requestId,
isMockedResponse: IS_MOCKED_RESPONSE in response,
type: responseClone.type,
status: responseClone.status,
statusText: responseClone.statusText,
body: responseClone.body,
headers: Object.fromEntries(responseClone.headers.entries()),
},
},
[responseClone.body],
)
})()
}

return response
}

// Resolve the main client for the given event.
// Client that issues a request doesn't necessarily equal the client
// that registered the worker. It's with the latter the worker should
// communicate with during the response resolving phase.
async function resolveMainClient(event) {
const client = await self.clients.get(event.clientId)

if (activeClientIds.has(event.clientId)) {
return client
}

if (client?.frameType === 'top-level') {
return client
}

const allClients = await self.clients.matchAll({
type: 'window',
})

return allClients
.filter((client) => {
// Get only those clients that are currently visible.
return client.visibilityState === 'visible'
})
.find((client) => {
// Find the client ID that's recorded in the
// set of clients that have registered the worker.
return activeClientIds.has(client.id)
})
}

async function getResponse(event, client, requestId) {
const { request } = event

// Clone the request because it might've been already used
// (i.e. its body has been read and sent to the client).
const requestClone = request.clone()

function passthrough() {
// Cast the request headers to a new Headers instance
// so the headers can be manipulated with.
const headers = new Headers(requestClone.headers)

// Remove the "accept" header value that marked this request as passthrough.
// This prevents request alteration and also keeps it compliant with the
// user-defined CORS policies.
const acceptHeader = headers.get('accept')
if (acceptHeader) {
const values = acceptHeader.split(',').map((value) => value.trim())
const filteredValues = values.filter(
(value) => value !== 'msw/passthrough',
)

if (filteredValues.length > 0) {
headers.set('accept', filteredValues.join(', '))
} else {
headers.delete('accept')
}
}

return fetch(requestClone, { headers })
}

// Bypass mocking when the client is not active.
if (!client) {
return passthrough()
}

// Bypass initial page load requests (i.e. static assets).
// The absence of the immediate/parent client in the map of the active clients
// means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet
// and is not ready to handle requests.
if (!activeClientIds.has(client.id)) {
return passthrough()
}

// Notify the client that a request has been intercepted.
const requestBuffer = await request.arrayBuffer()
const clientMessage = await sendToClient(
client,
{
type: 'REQUEST',
payload: {
id: requestId,
url: request.url,
mode: request.mode,
method: request.method,
headers: Object.fromEntries(request.headers.entries()),
cache: request.cache,
credentials: request.credentials,
destination: request.destination,
integrity: request.integrity,
redirect: request.redirect,
referrer: request.referrer,
referrerPolicy: request.referrerPolicy,
body: requestBuffer,
keepalive: request.keepalive,
},
},
[requestBuffer],
)

switch (clientMessage.type) {
case 'MOCK_RESPONSE': {
return respondWithMock(clientMessage.data)
}

case 'PASSTHROUGH': {
return passthrough()
}
}

return passthrough()
}

function sendToClient(client, message, transferrables = []) {
return new Promise((resolve, reject) => {
const channel = new MessageChannel()

channel.port1.onmessage = (event) => {
if (event.data && event.data.error) {
return reject(event.data.error)
}

resolve(event.data)
}

client.postMessage(
message,
[channel.port2].concat(transferrables.filter(Boolean)),
)
})
}

async function respondWithMock(response) {
// Setting response status code to 0 is a no-op.
// However, when responding with a "Response.error()", the produced Response
// instance will have status code set to 0. Since it's not possible to create
// a Response instance with status code 0, handle that use-case separately.
if (response.status === 0) {
return Response.error()
}

const mockedResponse = new Response(response.body, response)

Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, {
value: true,
enumerable: true,
})

return mockedResponse
}

+ 0
- 5
react-ui/public/pro_icon.svg View File

@@ -1,5 +0,0 @@
<svg width="42" height="42" xmlns="http://www.w3.org/2000/svg">
<g>
<path fill="#070707" d="m6.717392,13.773912l5.6,0c2.8,0 4.7,1.9 4.7,4.7c0,2.8 -2,4.7 -4.9,4.7l-2.5,0l0,4.3l-2.9,0l0,-13.7zm2.9,2.2l0,4.9l1.9,0c1.6,0 2.6,-0.9 2.6,-2.4c0,-1.6 -0.9,-2.4 -2.6,-2.4l-1.9,0l0,-0.1zm8.9,11.5l2.7,0l0,-5.7c0,-1.4 0.8,-2.3 2.2,-2.3c0.4,0 0.8,0.1 1,0.2l0,-2.4c-0.2,-0.1 -0.5,-0.1 -0.8,-0.1c-1.2,0 -2.1,0.7 -2.4,2l-0.1,0l0,-1.9l-2.7,0l0,10.2l0.1,0zm11.7,0.1c-3.1,0 -5,-2 -5,-5.3c0,-3.3 2,-5.3 5,-5.3s5,2 5,5.3c0,3.4 -1.9,5.3 -5,5.3zm0,-2.1c1.4,0 2.2,-1.1 2.2,-3.2c0,-2 -0.8,-3.2 -2.2,-3.2c-1.4,0 -2.2,1.2 -2.2,3.2c0,2.1 0.8,3.2 2.2,3.2z" class="st0" id="Ant-Design-Pro"/>
</g>
</svg>

+ 14
- 10
react-ui/src/app.tsx View File

@@ -7,7 +7,6 @@ import defaultSettings from '../config/defaultSettings';
import '../public/fonts/font.css';
import { getAccessToken } from './access';
import './dayjsConfig';
import './global.less';
import { removeAllPageCacheState } from './hooks/pageCacheState';
import {
getRemoteMenu,
@@ -20,6 +19,7 @@ import './styles/menu.less';
export { requestConfig as request } from './requestConfig';
// const isDev = process.env.NODE_ENV === 'development';
import { type GlobalInitialState } from '@/types';
// import '@/utils/clipboard';
import { menuItemRender } from '@/utils/menuRender';
import ErrorBoundary from './components/ErrorBoundary';
import { needAuth } from './utils';
@@ -40,7 +40,7 @@ export async function getInitialState(): Promise<GlobalInitialState> {
roleNames: response.user.roles,
} as API.CurrentUser;
} catch (error) {
console.error('1111', error);
console.error('getInitialState', error);
gotoLoginPage();
}
return undefined;
@@ -49,7 +49,7 @@ export async function getInitialState(): Promise<GlobalInitialState> {
// 如果不是登录页面,执行
const { location } = history;

console.log('getInitialState', needAuth(location.pathname));
// console.log('getInitialState', needAuth(location.pathname));
if (needAuth(location.pathname)) {
const currentUser = await fetchUserInfo();
return {
@@ -162,23 +162,23 @@ export const layout: RuntimeConfig['layout'] = ({ initialState }) => {
export const onRouteChange: RuntimeConfig['onRouteChange'] = async (e) => {
const { location } = e;
const menus = getRemoteMenu();
console.log('onRouteChange', menus);
// console.log('onRouteChange', menus);
if (menus === null && needAuth(location.pathname)) {
history.go(0);
}
};

export const patchRoutes: RuntimeConfig['patchRoutes'] = (e) => {
export const patchRoutes: RuntimeConfig['patchRoutes'] = () => {
//console.log('patchRoutes', e);
};

export const patchClientRoutes: RuntimeConfig['patchClientRoutes'] = (e) => {
console.log('patchClientRoutes', e);
// console.log('patchClientRoutes', e);
patchRouteWithRemoteMenus(e.routes);
};

export function render(oldRender: () => void) {
console.log('render');
// console.log('render');
const token = getAccessToken();
if (!token || token?.length === 0) {
oldRender();
@@ -214,7 +214,7 @@ export const antd: RuntimeAntdConfig = (memo) => {
defaultColor: themes['textColor'],
defaultHoverBg: 'rgba(22, 100, 255, 0.06)',
defaultHoverBorderColor: 'rgba(22, 100, 255, 0.5)',
defaultHoverColor: '#3F7FFF ',
defaultHoverColor: '#3F7FFF',
defaultActiveBg: 'rgba(22, 100, 255, 0.12)',
defaultActiveBorderColor: 'rgba(22, 100, 255, 0.75)',
defaultActiveColor: themes['primaryColor'],
@@ -227,11 +227,12 @@ export const antd: RuntimeAntdConfig = (memo) => {
};
memo.theme.components.Select = {
singleItemHeightLG: 46,
optionSelectedColor: themes['primaryColor'],
};
memo.theme.components.Table = {
headerBg: 'rgba(242, 244, 247, 0.36)',
headerBorderRadius: 4,
rowSelectedBg: 'rgba(22, 100, 255, 0.05)',
// rowSelectedBg: 'rgba(22, 100, 255, 0.05)', 固定列时,横向滑动导致重叠
};
memo.theme.components.Tabs = {
titleFontSize: 16,
@@ -244,9 +245,12 @@ export const antd: RuntimeAntdConfig = (memo) => {
linkColor: 'rgba(29, 29, 32, 0.7)',
separatorColor: 'rgba(29, 29, 32, 0.7)',
};
memo.theme.components.Tree = {
directoryNodeSelectedBg: 'rgba(22, 100, 255, 0.7)',
};

memo.theme.cssVar = true;
// memo.theme.hashed = false;
memo.theme.hashed = false;

memo.appConfig = {
message: {


BIN
react-ui/src/assets/img/dataset-config-icon.png View File

Before After
Width: 51  |  Height: 51  |  Size: 3.4 kB

BIN
react-ui/src/assets/img/dataset-version.png View File

Before After
Width: 78  |  Height: 76  |  Size: 5.1 kB

BIN
react-ui/src/assets/img/editor-parameter.png View File

Before After
Width: 34  |  Height: 31  |  Size: 1.3 kB Width: 51  |  Height: 47  |  Size: 1.8 kB

BIN
react-ui/src/assets/img/logo-cc.png View File

Before After
Width: 104  |  Height: 132  |  Size: 9.2 kB

BIN
react-ui/src/assets/img/mirror-basic.png View File

Before After
Width: 14  |  Height: 15  |  Size: 468 B Width: 51  |  Height: 51  |  Size: 1.5 kB

BIN
react-ui/src/assets/img/model-deployment.png View File

Before After
Width: 48  |  Height: 47  |  Size: 1.6 kB Width: 51  |  Height: 51  |  Size: 1.7 kB

BIN
react-ui/src/assets/img/popover-bg.png View File

Before After
Width: 1200  |  Height: 452  |  Size: 49 kB

BIN
react-ui/src/assets/img/resample-icon.png View File

Before After
Width: 51  |  Height: 51  |  Size: 1.3 kB

BIN
react-ui/src/assets/img/search-config-icon.png View File

Before After
Width: 51  |  Height: 51  |  Size: 1.6 kB

BIN
react-ui/src/assets/img/trial-config-icon.png View File

Before After
Width: 51  |  Height: 51  |  Size: 1.7 kB

+ 0
- 37
react-ui/src/components/ArrayTableCell/index.tsx View File

@@ -1,37 +0,0 @@
/*
* @Author: 赵伟
* @Date: 2024-04-28 14:18:11
* @Description: 自定义 Table 数组类单元格
*/

import { Tooltip } from 'antd';

function ArrayTableCell(ellipsis: boolean = false, property?: string) {
return (value?: any | null) => {
if (
value === undefined ||
value === null ||
Array.isArray(value) === false ||
value.length === 0
) {
return <span>--</span>;
}

let list = value;
if (property && typeof value[0] === 'object') {
list = value.map((item) => item[property]);
}
const text = list.join(',');
if (ellipsis) {
return (
<Tooltip title={text} placement="topLeft" overlayStyle={{ maxWidth: '400px' }}>
<span>{text}</span>;
</Tooltip>
);
} else {
return <span>{text}</span>;
}
};
}

export default ArrayTableCell;

+ 86
- 0
react-ui/src/components/BasicInfo/BasicInfoItem.tsx View File

@@ -0,0 +1,86 @@
/*
* @Author: 赵伟
* @Date: 2024-11-29 09:27:19
* @Description: 用于 BasicInfo 和 BasicTableInfo 组件的子组件
*/

import { Typography } from 'antd';
import React from 'react';
import BasicInfoItemValue from './BasicInfoItemValue';
import { type BasicInfoData, type BasicInfoLink } from './types';

type BasicInfoItemProps = {
/** 基础信息 */
data: BasicInfoData;
/** 标题宽度 */
labelWidth: number;
/** 自定义类名前缀 */
classPrefix: string;
/** 标题是否显示省略号 */
labelEllipsis?: boolean;
/** 标签对齐方式 */
labelAlign?: 'start' | 'end' | 'justify';
};

function BasicInfoItem({
data,
labelWidth,
classPrefix,
labelEllipsis = true,
labelAlign = 'start',
}: BasicInfoItemProps) {
const { label, value, format, ellipsis } = data;
const formatValue = format ? format(value) : value;
const myClassName = `${classPrefix}__item`;
let valueComponent = undefined;
if (React.isValidElement(formatValue)) {
valueComponent = <div className={`${myClassName}__node`}>{formatValue}</div>;
} else if (Array.isArray(formatValue)) {
valueComponent = (
<div className={`${myClassName}__value-container`}>
{formatValue.map((item: BasicInfoLink) => (
<BasicInfoItemValue
key={item.value}
value={item.value}
link={item.link}
url={item.url}
ellipsis={ellipsis}
classPrefix={classPrefix}
/>
))}
</div>
);
} else if (typeof formatValue === 'object' && formatValue) {
valueComponent = (
<BasicInfoItemValue
value={formatValue.value}
link={formatValue.link}
url={formatValue.url}
ellipsis={ellipsis}
classPrefix={classPrefix}
/>
);
} else {
valueComponent = (
<BasicInfoItemValue value={formatValue} ellipsis={ellipsis} classPrefix={classPrefix} />
);
}
return (
<div className={myClassName} key={label}>
<div
className={`${myClassName}__label`}
style={{ width: labelWidth, textAlign: labelAlign, textAlignLast: labelAlign }}
>
<Typography.Text
ellipsis={labelEllipsis !== false ? { tooltip: label } : false}
style={{ width: labelAlign === 'justify' ? '100%' : 'auto' }}
>
{label}
</Typography.Text>
</div>
{valueComponent}
</div>
);
}

export default BasicInfoItem;

+ 58
- 0
react-ui/src/components/BasicInfo/BasicInfoItemValue.tsx View File

@@ -0,0 +1,58 @@
/*
* @Author: 赵伟
* @Date: 2024-11-29 09:27:19
* @Description: 用于 BasicInfoItem 的组件
*/

import { isEmpty } from '@/utils';
import { Link } from '@umijs/max';
import { Typography } from 'antd';

type BasicInfoItemValueProps = {
/** 值是否显示省略号 */
ellipsis?: boolean;
/** 自定义类名前缀 */
classPrefix: string;
/** 值 */
value?: string;
/** 内部链接 */
link?: string;
/** 外部链接 */
url?: string;
};

function BasicInfoItemValue({
value,
link,
url,
classPrefix,
ellipsis = true,
}: BasicInfoItemValueProps) {
const myClassName = `${classPrefix}__item__value`;
let component = undefined;
if (url && value) {
component = (
<a className={`${myClassName}__link`} href={url} target="_blank" rel="noopener noreferrer">
{value}
</a>
);
} else if (link && value) {
component = (
<Link to={link} className={`${myClassName}__link`}>
{value}
</Link>
);
} else {
component = <span className={`${myClassName}__text`}>{!isEmpty(value) ? value : '--'}</span>;
}

return (
<div className={myClassName}>
<Typography.Text ellipsis={ellipsis !== false ? { tooltip: value } : false}>
{component}
</Typography.Text>
</div>
);
}

export default BasicInfoItemValue;

+ 26
- 2
react-ui/src/components/BasicInfo/index.less View File

@@ -17,8 +17,6 @@
color: @text-color-secondary;
font-size: @font-size-content;
line-height: 1.6;
text-align: justify;
text-align-last: justify;

&::after {
position: absolute;
@@ -31,10 +29,12 @@
flex: 1;
flex-direction: column;
gap: 5px 0;
min-width: 0;
}

&__value {
flex: 1;
min-width: 0;
margin-left: 16px;
font-size: @font-size-content;
line-height: 1.6;
@@ -49,5 +49,29 @@
text-underline-offset: 3px;
}
}

&__node {
flex: 1;
min-width: 0;
margin-left: 16px;
font-size: @font-size-content;
line-height: 1.6;
word-break: break-all;
}
}
}

.kf-basic-info--three-columns {
width: 100%;

.kf-basic-info__item {
width: calc((100% - 80px) / 3);

&__label {
font-size: @font-size;
}
&__value {
font-size: @font-size;
}
}
}

+ 38
- 109
react-ui/src/components/BasicInfo/index.tsx View File

@@ -1,129 +1,58 @@
import { Link } from '@umijs/max';
import { Typography } from 'antd';
import classNames from 'classnames';
import React from 'react';
import BasicInfoItem from './BasicInfoItem';
import './index.less';
import type { BasicInfoData, BasicInfoLink } from './types';
export type { BasicInfoData, BasicInfoLink };

export type BasicInfoLink = {
value: string;
link?: string;
url?: string;
};

export type BasicInfoData = {
label: string;
value?: any;
ellipsis?: boolean;
format?: (_value?: any) => string | BasicInfoLink | BasicInfoLink[] | undefined;
};

type BasicInfoProps = {
export type BasicInfoProps = {
/** 基础信息 */
datas: BasicInfoData[];
/** 标题宽度 */
labelWidth: number;
/** 标题是否显示省略号 */
labelEllipsis?: boolean;
/** 是否一行三列 */
threeColumns?: boolean;
/** 标签对齐方式 */
labelAlign?: 'start' | 'end' | 'justify';
/** 自定义类名 */
className?: string;
/** 自定义样式 */
style?: React.CSSProperties;
labelWidth: number;
};

type BasicInfoItemProps = {
data: BasicInfoData;
labelWidth: number;
classPrefix: string;
};

type BasicInfoItemValueProps = BasicInfoLink & {
ellipsis?: boolean;
classPrefix: string;
};

export default function BasicInfo({ datas, className, style, labelWidth }: BasicInfoProps) {
/**
* 基础信息展示组件,用于展示基础信息,支持一行两列或一行三列,支持数据格式化
*/
export default function BasicInfo({
datas,
labelWidth,
labelEllipsis = true,
labelAlign = 'start',
threeColumns = false,
className,
style,
}: BasicInfoProps) {
return (
<div className={classNames('kf-basic-info', className)} style={style}>
<div
className={classNames(
'kf-basic-info',
{ 'kf-basic-info--three-columns': threeColumns },
className,
)}
style={style}
>
{datas.map((item) => (
<BasicInfoItem
key={item.label}
data={item}
labelWidth={labelWidth}
classPrefix="kf-basic-info"
labelEllipsis={labelEllipsis}
labelAlign={labelAlign}
/>
))}
</div>
);
}

export function BasicInfoItem({ data, labelWidth, classPrefix }: BasicInfoItemProps) {
const { label, value, format, ellipsis } = data;
const formatValue = format ? format(value) : value;
const myClassName = `${classPrefix}__item`;
let valueComponent = undefined;
if (Array.isArray(formatValue)) {
valueComponent = (
<div className={`${myClassName}__value-container`}>
{formatValue.map((item: BasicInfoLink) => (
<BasicInfoItemValue
key={item.value}
value={item.value}
link={item.link}
url={item.url}
ellipsis={ellipsis}
classPrefix={classPrefix}
/>
))}
</div>
);
} else if (typeof formatValue === 'object' && formatValue) {
valueComponent = (
<BasicInfoItemValue
value={formatValue.value}
link={formatValue.link}
url={formatValue.url}
ellipsis={ellipsis}
classPrefix={classPrefix}
/>
);
} else {
valueComponent = (
<BasicInfoItemValue value={formatValue} ellipsis={ellipsis} classPrefix={classPrefix} />
);
}
return (
<div className={myClassName} key={label}>
<div className={`${myClassName}__label`} style={{ width: labelWidth }}>
{label}
</div>
{valueComponent}
</div>
);
}

export function BasicInfoItemValue({
value,
link,
url,
ellipsis,
classPrefix,
}: BasicInfoItemValueProps) {
const myClassName = `${classPrefix}__item__value`;
let component = undefined;
if (url && value) {
component = (
<a className={`${myClassName}__link`} href={url} target="_blank" rel="noopener noreferrer">
{value}
</a>
);
} else if (link && value) {
component = (
<Link to={link} className={`${myClassName}__link`}>
{value}
</Link>
);
} else {
component = <span className={`${myClassName}__text`}>{value ?? '--'}</span>;
}

return (
<div className={myClassName}>
<Typography.Text ellipsis={ellipsis ? { tooltip: value } : false}>
{component}
</Typography.Text>
</div>
);
}

+ 14
- 0
react-ui/src/components/BasicInfo/types.ts View File

@@ -0,0 +1,14 @@
// 基础信息
export type BasicInfoData = {
label: string;
value?: any;
ellipsis?: boolean;
format?: (_value?: any) => string | React.ReactNode | BasicInfoLink | BasicInfoLink[] | undefined;
};

// 值为链接的类型
export type BasicInfoLink = {
value?: string;
link?: string;
url?: string;
};

+ 11
- 4
react-ui/src/components/BasicTableInfo/index.less View File

@@ -4,7 +4,7 @@
flex-wrap: wrap;
align-items: stretch;
width: 100%;
border: 1px solid @border-color-base;
border: 1px solid @border-color;
border-bottom: none;
border-radius: 4px;

@@ -12,7 +12,7 @@
display: flex;
align-items: stretch;
width: 25%;
border-bottom: 1px solid @border-color-base;
border-bottom: 1px solid @border-color;

&__label {
flex: none;
@@ -27,14 +27,13 @@
display: flex;
flex: 1;
flex-direction: column;
align-items: flex-start;
align-items: stretch;
min-width: 0;
}

&__value {
flex: 1;
min-width: 0;
margin: 0 !important;
padding: 12px 20px 4px;
font-size: @font-size;
word-break: break-all;
@@ -56,5 +55,13 @@
text-underline-offset: 3px;
}
}

&__node {
flex: 1;
min-width: 0;
padding: 12px 20px;
font-size: @font-size;
word-break: break-all;
}
}
}

+ 13
- 11
react-ui/src/components/BasicTableInfo/index.tsx View File

@@ -1,20 +1,21 @@
import classNames from 'classnames';
import { BasicInfoItem, type BasicInfoData, type BasicInfoLink } from '../BasicInfo';
import { BasicInfoProps } from '../BasicInfo';
import BasicInfoItem from '../BasicInfo/BasicInfoItem';
import { type BasicInfoData, type BasicInfoLink } from '../BasicInfo/types';
import './index.less';
export type { BasicInfoData, BasicInfoLink };

type BasicTableInfoProps = {
datas: BasicInfoData[];
className?: string;
style?: React.CSSProperties;
labelWidth: number;
};
export type BasicTableInfoProps = Omit<BasicInfoProps, 'labelAlign' | 'threeColumns'>;

/**
* 表格基础信息展示组件,用于展示基础信息,一行四列,支持数据格式化
*/
export default function BasicTableInfo({
datas,
labelWidth,
labelEllipsis,
className,
style,
labelWidth,
}: BasicTableInfoProps) {
const remainder = datas.length % 4;
const array = [];
@@ -22,7 +23,7 @@ export default function BasicTableInfo({
for (let i = 0; i < 4 - remainder; i++) {
array.push({
label: '',
value: '',
value: false, // 用于区分是否是空数据,不能是空字符串、null、undefined
});
}
}
@@ -30,11 +31,12 @@ export default function BasicTableInfo({

return (
<div className={classNames('kf-basic-table-info', className)} style={style}>
{showDatas.map((item) => (
{showDatas.map((item, index) => (
<BasicInfoItem
key={item.label}
key={`${item.label}-${index}`}
data={item}
labelWidth={labelWidth}
labelEllipsis={labelEllipsis}
classPrefix="kf-basic-table-info"
/>
))}


react-ui/src/pages/Pipeline/components/CodeConfigItem/index.less → react-ui/src/components/CodeConfigItem/index.less View File


react-ui/src/pages/Pipeline/components/CodeConfigItem/index.tsx → react-ui/src/components/CodeConfigItem/index.tsx View File


+ 21
- 6
react-ui/src/components/CodeSelect/index.tsx View File

@@ -1,21 +1,35 @@
/*
* @Author: 赵伟
* @Date: 2024-10-08 15:36:08
* @Description: 代码配置选择表单组件
* @Description: 流水线选择代码配置表单
*/

import CodeSelectorModal from '@/components/CodeSelectorModal';
import KFIcon from '@/components/KFIcon';
import CodeSelectorModal from '@/pages/Pipeline/components/CodeSelectorModal';
import { openAntdModal } from '@/utils/modal';
import { Button } from 'antd';
import classNames from 'classnames';
import ParameterInput, { type ParameterInputProps } from '../ParameterInput';
import './index.less';

export { requiredValidator, type ParameterInputObject } from '../ParameterInput';
export {
requiredValidator,
type ParameterInputObject,
type ParameterInputValue,
} from '../ParameterInput';

type CodeSelectProps = ParameterInputProps;

function CodeSelect({ value, onChange, disabled, ...rest }: CodeSelectProps) {
/** 代码配置选择表单组件 */
function CodeSelect({
value,
size,
disabled,
className,
style,
onChange,
...rest
}: CodeSelectProps) {
const selectResource = () => {
const { close } = openAntdModal(CodeSelectorModal, {
onOk: (res) => {
@@ -46,9 +60,10 @@ function CodeSelect({ value, onChange, disabled, ...rest }: CodeSelectProps) {
};

return (
<div className="kf-code-select">
<div className={classNames('kf-code-select', className)} style={style}>
<ParameterInput
{...rest}
size={size}
disabled={disabled}
value={value}
onChange={onChange}
@@ -56,7 +71,7 @@ function CodeSelect({ value, onChange, disabled, ...rest }: CodeSelectProps) {
></ParameterInput>
<Button
className="kf-code-select__button"
size="large"
size={size}
type="link"
icon={<KFIcon type="icon-xuanzedaimapeizhi" font={16} />}
disabled={disabled}


+ 49
- 0
react-ui/src/components/CodeSelectorModal/index.less View File

@@ -0,0 +1,49 @@
.kf-code-selector-modal {
width: 100%;
height: 100%;

&__search {
width: 100%;
}

&__content {
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 10px;
width: 100%;
max-height: 50vh;
margin-top: 24px;
margin-bottom: 30px;
overflow-x: hidden;
overflow-y: auto;
}

&__empty {
padding-top: 40px;
}

// 覆盖 antd 样式
.ant-input-affix-wrapper {
border-radius: 23px !important;
.ant-input-prefix {
margin-inline-end: 12px;
}
.ant-input-suffix {
margin-inline-end: 12px;
}
.ant-input-clear-icon {
font-size: 16px;
}
}

.ant-input-group-addon {
display: none;
}

.ant-pagination {
.ant-select-single {
height: 32px !important;
}
}
}

react-ui/src/pages/Pipeline/components/CodeSelectorModal/index.tsx → react-ui/src/components/CodeSelectorModal/index.tsx View File

@@ -4,16 +4,16 @@
* @Description: 选择代码
*/

import KFIcon from '@/components/KFIcon';
import KFModal from '@/components/KFModal';
import { type CodeConfigData } from '@/pages/CodeConfig/List';
import { getCodeConfigListReq } from '@/services/codeConfig';
import { to } from '@/utils/promise';
import { Icon } from '@umijs/max';
import type { ModalProps, PaginationProps } from 'antd';
import { Empty, Input, Pagination } from 'antd';
import { useEffect, useState } from 'react';
import CodeConfigItem from '../CodeConfigItem';
import styles from './index.less';
import './index.less';

export { type CodeConfigData };

@@ -21,6 +21,7 @@ export interface CodeSelectorModalProps extends Omit<ModalProps, 'onOk'> {
onOk?: (params: CodeConfigData | undefined) => void;
}

/** 选择代码配置的弹窗,推荐使用函数的方式打开 */
function CodeSelectorModal({ onOk, ...rest }: CodeSelectorModalProps) {
const [dataList, setDataList] = useState<CodeConfigData[]>([]);
const [total, setTotal] = useState(0);
@@ -32,26 +33,30 @@ function CodeSelectorModal({ onOk, ...rest }: CodeSelectorModalProps) {
const [inputText, setInputText] = useState<string | undefined>(undefined);

useEffect(() => {
// 获取数据请求
const getDataList = async () => {
const params = {
page: pagination.current! - 1,
size: pagination.pageSize,
code_repo_name: searchText || undefined,
};
const [res] = await to(getCodeConfigListReq(params));
if (res && res.data && res.data.content) {
setDataList(res.data.content);
setTotal(res.data.totalElements);
}
};

getDataList();
}, [pagination, searchText]);

// 获取数据请求
const getDataList = async () => {
const params = {
page: pagination.current! - 1,
size: pagination.pageSize,
code_repo_name: searchText !== '' ? searchText : undefined,
};
const [res] = await to(getCodeConfigListReq(params));
if (res && res.data && res.data.content) {
setDataList(res.data.content);
setTotal(res.data.totalElements);
}
};

// 搜索
const handleSearch = (value: string) => {
setSearchText(value);
setPagination((prev) => ({
...prev,
current: 1,
}));
};

const handleClick = (item: CodeConfigData) => {
@@ -75,9 +80,9 @@ function CodeSelectorModal({ onOk, ...rest }: CodeSelectorModalProps) {
footer={null}
destroyOnClose
>
<div className={styles['code-selector']}>
<div className="kf-code-selector-modal">
<Input.Search
className={styles['code-selector__search']}
className="kf-code-selector-modal__search"
placeholder="按代码仓库名称筛选"
allowClear
onSearch={handleSearch}
@@ -86,12 +91,15 @@ function CodeSelectorModal({ onOk, ...rest }: CodeSelectorModalProps) {
suffix={null}
value={inputText}
prefix={
<Icon icon="local:magnifying-glass" style={{ marginLeft: '10px', marginTop: '2px' }} />
<KFIcon type="icon-sousuo" color="rgba(22,100,255,0.4" style={{ marginLeft: '10px' }} />
}
// prefix={
// <Icon icon="local:magnifying-glass" style={{ marginLeft: '10px', marginTop: '2px' }} />
// }
/>
{dataList?.length !== 0 ? (
<>
<div className={styles['code-selector__content']}>
<div className="kf-code-selector-modal__content">
{dataList?.map((item) => (
<CodeConfigItem item={item} key={item.id} onClick={handleClick} />
))}
@@ -108,7 +116,7 @@ function CodeSelectorModal({ onOk, ...rest }: CodeSelectorModalProps) {
/>
</>
) : (
<div className={styles['code-selector__empty']}>
<div className="kf-code-selector-modal__empty">
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE}></Empty>
</div>
)}

+ 41
- 0
react-ui/src/components/ConfigInfo/index.tsx View File

@@ -0,0 +1,41 @@
import BasicInfo, { type BasicInfoData, type BasicInfoProps } from '@/components/BasicInfo';
import InfoGroup from '@/components/InfoGroup';
import classNames from 'classnames';
export type { BasicInfoData };

interface ConfigInfoProps extends BasicInfoProps {
/** 标题 */
title: string;
/** 子元素 */
children?: React.ReactNode;
}

/** 详情基本信息块,目前主要用于主动机器学习、超参数寻优、自主学习详情中 */
function ConfigInfo({
title,
datas,
labelWidth,
labelAlign = 'start',
labelEllipsis = true,
threeColumns = true,
className,
style,
children,
}: ConfigInfoProps) {
return (
<InfoGroup title={title} className={classNames('kf-config-info', className)} style={style}>
<div className={'kf-config-info__content'}>
<BasicInfo
datas={datas}
labelWidth={labelWidth}
labelAlign={labelAlign}
labelEllipsis={labelEllipsis}
threeColumns={threeColumns}
/>
{children}
</div>
</InfoGroup>
);
}

export default ConfigInfo;

+ 0
- 45
react-ui/src/components/Footer/index.tsx View File

@@ -1,45 +0,0 @@
import { GithubOutlined } from '@ant-design/icons';
import { DefaultFooter } from '@ant-design/pro-components';
import { useIntl } from '@umijs/max';
import React from 'react';

const Footer: React.FC = () => {
const intl = useIntl();
const defaultMessage = intl.formatMessage({
id: 'app.copyright.produced',
defaultMessage: '蚂蚁集团体验技术部出品',
});

const currentYear = new Date().getFullYear();

return (
<DefaultFooter
style={{
background: 'none',
}}
copyright={`${currentYear} ${defaultMessage}`}
links={[
{
key: 'Ant Design Pro',
title: 'Ant Design Pro',
href: 'https://pro.ant.design',
blankTarget: true,
},
{
key: 'github',
title: <GithubOutlined />,
href: 'https://github.com/ant-design/ant-design-pro',
blankTarget: true,
},
{
key: 'Ant Design',
title: 'Ant Design',
href: 'https://ant.design',
blankTarget: true,
},
]}
/>
);
};

export default Footer;

+ 19
- 0
react-ui/src/components/FormInfo/index.less View File

@@ -0,0 +1,19 @@
.form-info {
min-height: 32px;
padding: 4px 11px;
color: @text-disabled-color;
font-size: @font-size-input;
background-color: rgba(0, 0, 0, 0.04);
border: 1px solid #d9d9d9;
border-radius: 6px;

.ant-typography {
margin: 0 !important;
}
}

.form-info--multiline {
.ant-typography {
white-space: pre-wrap;
}
}

+ 74
- 0
react-ui/src/components/FormInfo/index.tsx View File

@@ -0,0 +1,74 @@
import { formatEnum } from '@/utils/format';
import { Typography, type SelectProps } from 'antd';
import classNames from 'classnames';
import './index.less';

type FormInfoProps = {
/** 值 */
value?: any;
/** 如果 `value` 是对象,取对象的哪个属性作为值 */
valuePropName?: string;
/** 是否是多行文本 */
textArea?: boolean;
/** 是否是下拉框 */
select?: boolean;
/** 下拉框数据 */
options?: SelectProps['options'];
/** 自定义节点 label、value 的字段 */
fieldNames?: SelectProps['fieldNames'];
/** 自定义类名 */
className?: string;
/** 自定义样式 */
style?: React.CSSProperties;
};

/**
* 模拟禁用的输入框,但是内容超长时,hover 时显示所有内容
*/
function FormInfo({
value,
valuePropName,
textArea = false,
select = false,
options,
fieldNames,
className,
style,
}: FormInfoProps) {
let showValue = value;
if (value && typeof value === 'object' && valuePropName) {
showValue = value[valuePropName];
} else if (select === true && options) {
let _options: SelectProps['options'] = options;
if (fieldNames) {
_options = options.map((v) => {
return {
...v,
label: fieldNames.label && v[fieldNames.label],
value: fieldNames.value && v[fieldNames.value],
options: fieldNames.options && v[fieldNames.options],
};
});
}
showValue = formatEnum(_options)(value);
}

return (
<div
className={classNames(
'form-info',
{
'form-info--multiline': textArea,
},
className,
)}
style={style}
>
<Typography.Paragraph ellipsis={textArea ? false : { tooltip: showValue }}>
{showValue}
</Typography.Paragraph>
</div>
);
}

export default FormInfo;

+ 13
- 5
react-ui/src/components/FullScreenFrame/index.tsx View File

@@ -2,22 +2,30 @@ import classNames from 'classnames';
import './index.less';

type FullScreenFrameProps = {
/** URL */
url: string;
/** 自定义类名 */
className?: string;
/** 自定义样式 */
style?: React.CSSProperties;
onload?: (e?: React.SyntheticEvent<HTMLIFrameElement, Event>) => void;
onerror?: (e?: React.SyntheticEvent<HTMLIFrameElement, Event>) => void;
/** 加载完成回调 */
onLoad?: (e?: React.SyntheticEvent<HTMLIFrameElement, Event>) => void;
/** 加载失败回调 */
onError?: (e?: React.SyntheticEvent<HTMLIFrameElement, Event>) => void;
};

function FullScreenFrame({ url, className, style, onload, onerror }: FullScreenFrameProps) {
/**
* 全屏 iframe,IFramePage 组件的子组件,开发中应该使用 IFramePage
*/
function FullScreenFrame({ url, className, style, onLoad, onError }: FullScreenFrameProps) {
return (
<div className={classNames('kf-full-screen-frame', className ?? '')} style={style}>
{url && (
<iframe
src={url}
className="kf-full-screen-frame__iframe"
onLoad={onload}
onError={onerror}
onLoad={onLoad}
onError={onError}
></iframe>
)}
</div>


+ 21
- 20
react-ui/src/components/IFramePage/index.tsx View File

@@ -12,52 +12,53 @@ export enum IframePageType {
DatasetAnnotation = 'DatasetAnnotation', // 数据标注
AppDevelopment = 'AppDevelopment', // 应用开发
DevEnv = 'DevEnv', // 开发环境
GitLink = 'GitLink',
GitLink = 'GitLink', // git link
}

const getRequestAPI = (type: IframePageType): (() => Promise<any>) => {
switch (type) {
case IframePageType.DatasetAnnotation:
case IframePageType.DatasetAnnotation: // 数据标注
return getLabelStudioUrl;
case IframePageType.AppDevelopment:
case IframePageType.AppDevelopment: // 应用开发
return () => Promise.resolve({ code: 200, data: 'http://172.20.32.185:30080/' });
case IframePageType.DevEnv:
case IframePageType.DevEnv: // 开发环境
return () =>
Promise.resolve({
code: 200,
data: SessionStorage.getItem(SessionStorage.editorUrlKey) || '',
});
case IframePageType.GitLink:
case IframePageType.GitLink: // git link
return () => Promise.resolve({ code: 200, data: 'http://172.20.32.201:4000' });
}
};

type IframePageProps = {
/** 子系统 */
type: IframePageType;
/** 自定义样式类名 */
className?: string;
/** 自定义样式 */
style?: React.CSSProperties;
};

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

useEffect(() => {
requestIframeUrl();
return () => {
if (type === IframePageType.DevEnv) {
SessionStorage.removeItem(SessionStorage.editorUrlKey);
const requestIframeUrl = async () => {
setLoading(true);
const [res] = await to(getRequestAPI(type)());
if (res && res.data) {
setIframeUrl(res.data);
} else {
setLoading(false);
}
};
}, []);
const requestIframeUrl = async () => {
setLoading(true);
const [res] = await to(getRequestAPI(type)());
if (res && res.data) {
setIframeUrl(res.data);
} else {
setLoading(false);
}
};

requestIframeUrl();
}, [type]);

const hideLoading = () => {
setLoading(false);
@@ -66,7 +67,7 @@ function IframePage({ type, className, style }: IframePageProps) {
return (
<div className={classNames('kf-iframe-page', className)} style={style}>
{loading && createPortal(<KFSpin size="large" />, document.body)}
<FullScreenFrame url={iframeUrl} onload={hideLoading} onerror={hideLoading} />
<FullScreenFrame url={iframeUrl} onLoad={hideLoading} onError={hideLoading} />
</div>
);
}


+ 0
- 64
react-ui/src/components/IconSelector/Category.tsx View File

@@ -1,64 +0,0 @@
import { useIntl } from '@umijs/max';
import * as React from 'react';
import CopyableIcon from './CopyableIcon';
import type { CategoriesKeys } from './fields';
import type { ThemeType } from './index';
import styles from './style.less';

interface CategoryProps {
title: CategoriesKeys;
icons: string[];
theme: ThemeType;
newIcons: string[];
onSelect: (type: string, name: string) => any;
}

const Category: React.FC<CategoryProps> = (props) => {
const { icons, title, newIcons, theme } = props;
const intl = useIntl();
const [justCopied, setJustCopied] = React.useState<string | null>(null);
const copyId = React.useRef<NodeJS.Timeout | null>(null);
const onSelect = React.useCallback((type: string, text: string) => {
const { onSelect } = props;
if (onSelect) {
onSelect(type, text);
}
setJustCopied(type);
copyId.current = setTimeout(() => {
setJustCopied(null);
}, 2000);
}, []);
React.useEffect(
() => () => {
if (copyId.current) {
clearTimeout(copyId.current);
}
},
[],
);

return (
<div>
<h4>
{intl.formatMessage({
id: `app.docs.components.icon.category.${title}`,
defaultMessage: '信息',
})}
</h4>
<ul className={styles.anticonsList}>
{icons.map((name) => (
<CopyableIcon
key={name}
name={name}
theme={theme}
isNew={newIcons.includes(name)}
justCopied={justCopied}
onSelect={onSelect}
/>
))}
</ul>
</div>
);
};

export default Category;

+ 0
- 44
react-ui/src/components/IconSelector/CopyableIcon.tsx View File

@@ -1,44 +0,0 @@
import * as AntdIcons from '@ant-design/icons';
import { Tooltip } from 'antd';
import classNames from 'classnames';
import * as React from 'react';
import type { ThemeType } from './index';
import styles from './style.less';

const allIcons: {
[key: string]: any;
} = AntdIcons;

export interface CopyableIconProps {
name: string;
isNew: boolean;
theme: ThemeType;
justCopied: string | null;
onSelect: (type: string, text: string) => any;
}

const CopyableIcon: React.FC<CopyableIconProps> = ({ name, justCopied, onSelect, theme }) => {
const className = classNames({
copied: justCopied === name,
[theme]: !!theme,
});
return (
<li
className={className}
onClick={() => {
if (onSelect) {
onSelect(theme, name);
}
}}
>
<Tooltip title={name}>
{React.createElement(allIcons[name], { className: styles.anticon })}
</Tooltip>
{/* <span className={styles.anticonClass}>
<Badge dot={isNew}>{name}</Badge>
</span> */}
</li>
);
};

export default CopyableIcon;

+ 0
- 237
react-ui/src/components/IconSelector/IconPicSearcher.tsx View File

@@ -1,237 +0,0 @@
import KFModal from '@/components/KFModal';
import * as AntdIcons from '@ant-design/icons';
import { useIntl } from '@umijs/max';
import { Popover, Progress, Result, Spin, Tooltip, Upload } from 'antd';
import React, { useCallback, useEffect, useState } from 'react';
import './style.less';

const allIcons: { [key: string]: any } = AntdIcons;

const { Dragger } = Upload;
interface AntdIconClassifier {
load: () => void;
predict: (imgEl: HTMLImageElement) => void;
}
declare global {
interface Window {
antdIconClassifier: AntdIconClassifier;
}
}

interface PicSearcherState {
loading: boolean;
modalOpen: boolean;
popoverVisible: boolean;
icons: iconObject[];
fileList: any[];
error: boolean;
modelLoaded: boolean;
}

interface iconObject {
type: string;
score: number;
}

const PicSearcher: React.FC = () => {
const intl = useIntl();
const { formatMessage } = intl;
const [state, setState] = useState<PicSearcherState>({
loading: false,
modalOpen: false,
popoverVisible: false,
icons: [],
fileList: [],
error: false,
modelLoaded: false,
});
const predict = (imgEl: HTMLImageElement) => {
try {
let icons: any[] = window.antdIconClassifier.predict(imgEl);
if (gtag && icons.length) {
gtag('event', 'icon', {
event_category: 'search-by-image',
event_label: icons[0].className,
});
}
icons = icons.map((i) => ({ score: i.score, type: i.className.replace(/\s/g, '-') }));
setState((prev) => ({ ...prev, loading: false, error: false, icons }));
} catch {
setState((prev) => ({ ...prev, loading: false, error: true }));
}
};
// eslint-disable-next-line class-methods-use-this
const toImage = (url: string) =>
new Promise((resolve) => {
const img = new Image();
img.setAttribute('crossOrigin', 'anonymous');
img.src = url;
img.onload = () => {
resolve(img);
};
});

const uploadFile = useCallback((file: File) => {
setState((prev) => ({ ...prev, loading: true }));
const reader = new FileReader();
reader.onload = () => {
toImage(reader.result as string).then(predict);
setState((prev) => ({
...prev,
fileList: [{ uid: 1, name: file.name, status: 'done', url: reader.result }],
}));
};
reader.readAsDataURL(file);
}, []);

const onPaste = useCallback((event: ClipboardEvent) => {
const items = event.clipboardData && event.clipboardData.items;
let file = null;
if (items && items.length) {
for (let i = 0; i < items.length; i++) {
if (items[i].type.includes('image')) {
file = items[i].getAsFile();
break;
}
}
}
if (file) {
uploadFile(file);
}
}, []);
const toggleModal = useCallback(() => {
setState((prev) => ({
...prev,
modalOpen: !prev.modalOpen,
popoverVisible: false,
fileList: [],
icons: [],
}));
if (!localStorage.getItem('disableIconTip')) {
localStorage.setItem('disableIconTip', 'true');
}
}, []);

useEffect(() => {
const script = document.createElement('script');
script.onload = async () => {
await window.antdIconClassifier.load();
setState((prev) => ({ ...prev, modelLoaded: true }));
document.addEventListener('paste', onPaste);
};
script.src = 'https://cdn.jsdelivr.net/gh/lewis617/antd-icon-classifier@0.0/dist/main.js';
document.head.appendChild(script);
setState((prev) => ({ ...prev, popoverVisible: !localStorage.getItem('disableIconTip') }));
return () => {
document.removeEventListener('paste', onPaste);
};
}, []);

return (
<div className="iconPicSearcher">
<Popover
content={formatMessage({ id: 'app.docs.components.icon.pic-searcher.intro' })}
open={state.popoverVisible}
>
<AntdIcons.CameraOutlined className="icon-pic-btn" onClick={toggleModal} />
</Popover>
<KFModal
title={intl.formatMessage({
id: 'app.docs.components.icon.pic-searcher.title',
defaultMessage: '信息',
})}
open={state.modalOpen}
onCancel={toggleModal}
footer={null}
>
{state.modelLoaded || (
<Spin
spinning={!state.modelLoaded}
tip={formatMessage({
id: 'app.docs.components.icon.pic-searcher.modelloading',
})}
>
<div style={{ height: 100 }} />
</Spin>
)}
{state.modelLoaded && (
<Dragger
accept="image/jpeg, image/png"
listType="picture"
customRequest={(o) => uploadFile(o.file as File)}
fileList={state.fileList}
showUploadList={{ showPreviewIcon: false, showRemoveIcon: false }}
>
<p className="ant-upload-drag-icon">
<AntdIcons.InboxOutlined />
</p>
<p className="ant-upload-text">
{formatMessage({ id: 'app.docs.components.icon.pic-searcher.upload-text' })}
</p>
<p className="ant-upload-hint">
{formatMessage({ id: 'app.docs.components.icon.pic-searcher.upload-hint' })}
</p>
</Dragger>
)}
<Spin
spinning={state.loading}
tip={formatMessage({ id: 'app.docs.components.icon.pic-searcher.matching' })}
>
<div className="icon-pic-search-result">
{state.icons.length > 0 && (
<div className="result-tip">
{formatMessage({ id: 'app.docs.components.icon.pic-searcher.result-tip' })}
</div>
)}
<table>
{state.icons.length > 0 && (
<thead>
<tr>
<th className="col-icon">
{formatMessage({ id: 'app.docs.components.icon.pic-searcher.th-icon' })}
</th>
<th>
{formatMessage({ id: 'app.docs.components.icon.pic-searcher.th-score' })}
</th>
</tr>
</thead>
)}
<tbody>
{state.icons.map((icon) => {
const { type } = icon;
const iconName = `${type
.split('-')
.map((str) => `${str[0].toUpperCase()}${str.slice(1)}`)
.join('')}Outlined`;
return (
<tr key={iconName}>
<td className="col-icon">
<Tooltip title={icon.type} placement="right">
{React.createElement(allIcons[iconName])}
</Tooltip>
</td>
<td>
<Progress percent={Math.ceil(icon.score * 100)} />
</td>
</tr>
);
})}
</tbody>
</table>
{state.error && (
<Result
status="500"
title="503"
subTitle={formatMessage({
id: 'app.docs.components.icon.pic-searcher.server-error',
})}
/>
)}
</div>
</Spin>
</KFModal>
</div>
);
};

export default PicSearcher;

+ 0
- 223
react-ui/src/components/IconSelector/fields.ts View File

@@ -1,223 +0,0 @@
import * as AntdIcons from '@ant-design/icons/lib/icons';

const all = Object.keys(AntdIcons)
.map((n) => n.replace(/(Outlined|Filled|TwoTone)$/, ''))
.filter((n, i, arr) => arr.indexOf(n) === i);

const direction = [
'StepBackward',
'StepForward',
'FastBackward',
'FastForward',
'Shrink',
'ArrowsAlt',
'Down',
'Up',
'Left',
'Right',
'CaretUp',
'CaretDown',
'CaretLeft',
'CaretRight',
'UpCircle',
'DownCircle',
'LeftCircle',
'RightCircle',
'DoubleRight',
'DoubleLeft',
'VerticalLeft',
'VerticalRight',
'VerticalAlignTop',
'VerticalAlignMiddle',
'VerticalAlignBottom',
'Forward',
'Backward',
'Rollback',
'Enter',
'Retweet',
'Swap',
'SwapLeft',
'SwapRight',
'ArrowUp',
'ArrowDown',
'ArrowLeft',
'ArrowRight',
'PlayCircle',
'UpSquare',
'DownSquare',
'LeftSquare',
'RightSquare',
'Login',
'Logout',
'MenuFold',
'MenuUnfold',
'BorderBottom',
'BorderHorizontal',
'BorderInner',
'BorderOuter',
'BorderLeft',
'BorderRight',
'BorderTop',
'BorderVerticle',
'PicCenter',
'PicLeft',
'PicRight',
'RadiusBottomleft',
'RadiusBottomright',
'RadiusUpleft',
'RadiusUpright',
'Fullscreen',
'FullscreenExit',
];

const suggestion = [
'Question',
'QuestionCircle',
'Plus',
'PlusCircle',
'Pause',
'PauseCircle',
'Minus',
'MinusCircle',
'PlusSquare',
'MinusSquare',
'Info',
'InfoCircle',
'Exclamation',
'ExclamationCircle',
'Close',
'CloseCircle',
'CloseSquare',
'Check',
'CheckCircle',
'CheckSquare',
'ClockCircle',
'Warning',
'IssuesClose',
'Stop',
];

const editor = [
'Edit',
'Form',
'Copy',
'Scissor',
'Delete',
'Snippets',
'Diff',
'Highlight',
'AlignCenter',
'AlignLeft',
'AlignRight',
'BgColors',
'Bold',
'Italic',
'Underline',
'Strikethrough',
'Redo',
'Undo',
'ZoomIn',
'ZoomOut',
'FontColors',
'FontSize',
'LineHeight',
'Dash',
'SmallDash',
'SortAscending',
'SortDescending',
'Drag',
'OrderedList',
'UnorderedList',
'RadiusSetting',
'ColumnWidth',
'ColumnHeight',
];

const data = [
'AreaChart',
'PieChart',
'BarChart',
'DotChart',
'LineChart',
'RadarChart',
'HeatMap',
'Fall',
'Rise',
'Stock',
'BoxPlot',
'Fund',
'Sliders',
];

const logo = [
'Android',
'Apple',
'Windows',
'Ie',
'Chrome',
'Github',
'Aliwangwang',
'Dingding',
'WeiboSquare',
'WeiboCircle',
'TaobaoCircle',
'Html5',
'Weibo',
'Twitter',
'Wechat',
'Youtube',
'AlipayCircle',
'Taobao',
'Skype',
'Qq',
'MediumWorkmark',
'Gitlab',
'Medium',
'Linkedin',
'GooglePlus',
'Dropbox',
'Facebook',
'Codepen',
'CodeSandbox',
'CodeSandboxCircle',
'Amazon',
'Google',
'CodepenCircle',
'Alipay',
'AntDesign',
'AntCloud',
'Aliyun',
'Zhihu',
'Slack',
'SlackSquare',
'Behance',
'BehanceSquare',
'Dribbble',
'DribbbleSquare',
'Instagram',
'Yuque',
'Alibaba',
'Yahoo',
'Reddit',
'Sketch',
'WhatsApp',
'Dingtalk',
];

const datum = [...direction, ...suggestion, ...editor, ...data, ...logo];

const other = all.filter((n) => !datum.includes(n));

export const categories = {
direction,
suggestion,
editor,
data,
logo,
other,
};

export default categories;

export type Categories = typeof categories;
export type CategoriesKeys = keyof Categories;

+ 0
- 146
react-ui/src/components/IconSelector/index.tsx View File

@@ -1,146 +0,0 @@
import Icon, * as AntdIcons from '@ant-design/icons';
import { Empty, Input, Radio } from 'antd';
import type { RadioChangeEvent } from 'antd/es/radio/interface';
import debounce from 'lodash/debounce';
import * as React from 'react';
import Category from './Category';
import IconPicSearcher from './IconPicSearcher';
import type { CategoriesKeys } from './fields';
import { categories } from './fields';
import { FilledIcon, OutlinedIcon, TwoToneIcon } from './themeIcons';
// import { useIntl } from '@umijs/max';

export enum ThemeType {
Filled = 'Filled',
Outlined = 'Outlined',
TwoTone = 'TwoTone',
}

const allIcons: { [key: string]: any } = AntdIcons;

interface IconSelectorProps {
//intl: any;
onSelect: any;
}

interface IconSelectorState {
theme: ThemeType;
searchKey: string;
}

const IconSelector: React.FC<IconSelectorProps> = (props) => {
// const intl = useIntl();
// const { messages } = intl;
const { onSelect } = props;
const [displayState, setDisplayState] = React.useState<IconSelectorState>({
theme: ThemeType.Outlined,
searchKey: '',
});

const newIconNames: string[] = [];

const handleSearchIcon = React.useCallback(
debounce((searchKey: string) => {
setDisplayState((prevState) => ({ ...prevState, searchKey }));
}),
[],
);

const handleChangeTheme = React.useCallback((e: RadioChangeEvent) => {
setDisplayState((prevState) => ({ ...prevState, theme: e.target.value as ThemeType }));
}, []);

const renderCategories = React.useMemo<React.ReactNode | React.ReactNode[]>(() => {
const { searchKey = '', theme } = displayState;

const categoriesResult = Object.keys(categories)
.map((key: CategoriesKeys) => {
let iconList = categories[key];
if (searchKey) {
const matchKey = searchKey
// eslint-disable-next-line prefer-regex-literals
.replace(new RegExp(`^<([a-zA-Z]*)\\s/>$`, 'gi'), (_, name) => name)
.replace(/(Filled|Outlined|TwoTone)$/, '')
.toLowerCase();
iconList = iconList.filter((iconName: string) =>
iconName.toLowerCase().includes(matchKey),
);
}

// CopyrightCircle is same as Copyright, don't show it
iconList = iconList.filter((icon: string) => icon !== 'CopyrightCircle');

return {
category: key,
icons: iconList
.map((iconName: string) => iconName + theme)
.filter((iconName: string) => allIcons[iconName]),
};
})
.filter(({ icons }) => !!icons.length)
.map(({ category, icons }) => (
<Category
key={category}
title={category as CategoriesKeys}
theme={theme}
icons={icons}
newIcons={newIconNames}
onSelect={(type, name) => {
if (onSelect) {
onSelect(name, allIcons[name]);
}
}}
/>
));
return categoriesResult.length === 0 ? <Empty style={{ margin: '2em 0' }} /> : categoriesResult;
}, [displayState.searchKey, displayState.theme]);
return (
<>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<Radio.Group
value={displayState.theme}
onChange={handleChangeTheme}
size="large"
optionType="button"
buttonStyle="solid"
options={[
{
label: <Icon component={OutlinedIcon} />,
value: ThemeType.Outlined,
},
{
label: <Icon component={FilledIcon} />,
value: ThemeType.Filled,
},
{
label: <Icon component={TwoToneIcon} />,
value: ThemeType.TwoTone,
},
]}
>
{/* <Radio.Button value={ThemeType.Outlined}>
<Icon component={OutlinedIcon} /> {messages['app.docs.components.icon.outlined']}
</Radio.Button>
<Radio.Button value={ThemeType.Filled}>
<Icon component={FilledIcon} /> {messages['app.docs.components.icon.filled']}
</Radio.Button>
<Radio.Button value={ThemeType.TwoTone}>
<Icon component={TwoToneIcon} /> {messages['app.docs.components.icon.two-tone']}
</Radio.Button> */}
</Radio.Group>
<Input.Search
// placeholder={messages['app.docs.components.icon.search.placeholder']}
style={{ margin: '0 10px', flex: 1 }}
allowClear
onChange={(e) => handleSearchIcon(e.currentTarget.value)}
size="large"
autoFocus
suffix={<IconPicSearcher />}
/>
</div>
{renderCategories}
</>
);
};

export default IconSelector;

+ 0
- 137
react-ui/src/components/IconSelector/style.less View File

@@ -1,137 +0,0 @@
.iconPicSearcher {
display: inline-block;
margin: 0 8px;

.icon-pic-btn {
color: @text-color-secondary;
cursor: pointer;
transition: all 0.3s;

&:hover {
color: @input-icon-hover-color;
}
}
}

.icon-pic-preview {
width: 30px;
height: 30px;
margin-top: 10px;
padding: 8px;
text-align: center;
border: 1px solid @border-color-base;
border-radius: 4px;

> img {
max-width: 50px;
max-height: 50px;
}
}

.icon-pic-search-result {
min-height: 50px;
padding: 0 10px;

> .result-tip {
padding: 10px 0;
color: @text-color-secondary;
}

> table {
width: 100%;

.col-icon {
width: 80px;
padding: 10px 0;

> .anticon {
font-size: 30px;

:hover {
color: @link-hover-color;
}
}
}
}
}

ul.anticonsList {
margin: 2px 0;
overflow: hidden;
direction: ltr;
list-style: none;

li {
position: relative;
float: left;
width: 48px;
height: 48px;
margin: 3px 0;
padding: 2px 0 0;
overflow: hidden;
color: #555;
text-align: center;
list-style: none;
background-color: inherit;
border-radius: 4px;
cursor: pointer;
transition: color 0.3s ease-in-out, background-color 0.3s ease-in-out;

.rtl & {
margin: 3px 0;
padding: 2px 0 0;
}

.anticon {
margin: 4px 0 2px;
font-size: 24px;
transition: transform 0.3s ease-in-out;
will-change: transform;
}

.anticonClass {
display: block;
font-family: 'Lucida Console', Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
white-space: nowrap;
text-align: center;
transform: scale(0.83);

.ant-badge {
transition: color 0.3s ease-in-out;
}
}

&:hover {
color: #fff;
background-color: @primary-color;

.anticon {
transform: scale(1.4);
}

.ant-badge {
color: #fff;
}
}

&.TwoTone:hover {
background-color: #8ecafe;
}

&.copied:hover {
color: rgba(255, 255, 255, 0.2);
}

&.copied::after {
top: -2px;
opacity: 1;
}
}
}

.copied-code {
padding: 2px 4px;
font-size: 12px;
background: #f5f5f5;
border-radius: 2px;
}

+ 0
- 40
react-ui/src/components/IconSelector/themeIcons.tsx View File

@@ -1,40 +0,0 @@
import * as React from 'react';

export const FilledIcon: React.FC = (props) => {
const path =
'M864 64H160C107 64 64 107 64 160v' +
'704c0 53 43 96 96 96h704c53 0 96-43 96-96V16' +
'0c0-53-43-96-96-96z';
return (
<svg {...props} viewBox="0 0 1024 1024">
<path d={path} />
</svg>
);
};

export const OutlinedIcon: React.FC = (props) => {
const path =
'M864 64H160C107 64 64 107 64 160v7' +
'04c0 53 43 96 96 96h704c53 0 96-43 96-96V160c' +
'0-53-43-96-96-96z m-12 800H172c-6.6 0-12-5.4-' +
'12-12V172c0-6.6 5.4-12 12-12h680c6.6 0 12 5.4' +
' 12 12v680c0 6.6-5.4 12-12 12z';
return (
<svg {...props} viewBox="0 0 1024 1024">
<path d={path} />
</svg>
);
};

export const TwoToneIcon: React.FC = (props) => {
const path =
'M16 512c0 273.932 222.066 496 496 49' +
'6s496-222.068 496-496S785.932 16 512 16 16 238.' +
'066 16 512z m496 368V144c203.41 0 368 164.622 3' +
'68 368 0 203.41-164.622 368-368 368z';
return (
<svg {...props} viewBox="0 0 1024 1024">
<path d={path} />
</svg>
);
};

+ 40
- 0
react-ui/src/components/InfoGroup/InfoGroupTitle.less View File

@@ -0,0 +1,40 @@
.kf-info-group-title {
width: 100%;
height: 56px;
padding: 0 @content-padding;
background: linear-gradient(
179.03deg,
rgba(199, 223, 255, 0.12) 0%,
rgba(22, 100, 255, 0.04) 100%
);
border: 1px solid #e8effb;
border-radius: 4px 4px 0 0;

&__image {
width: 16px;
height: 16px;
margin-right: 10px;
}

&__text {
position: relative;
color: @text-color;
font-weight: 500;
font-size: @font-size-title;
.singleLine();

&::after {
position: absolute;
bottom: 6px;
left: 0;
width: 100%;
height: 6px;
background: linear-gradient(
to right,
.addAlpha(@primary-color, 0.4) [] 0,
.addAlpha(@primary-color, 0) [] 100%
);
content: '';
}
}
}

+ 31
- 0
react-ui/src/components/InfoGroup/InfoGroupTitle.tsx View File

@@ -0,0 +1,31 @@
import { Flex } from 'antd';
import classNames from 'classnames';
import './InfoGroupTitle.less';

type InfoGroupTitleProps = {
/** 标题 */
title: string;
/** 自定义类名 */
className?: string;
/** 自定义样式 */
style?: React.CSSProperties;
};

/**
* 信息组标题
*/
function InfoGroupTitle({ title, style, className }: InfoGroupTitleProps) {
return (
<Flex align="center" className={classNames('kf-info-group-title', className)} style={style}>
<img
src={require('@/assets/img/code-name-icon.png')}
className="kf-info-group-title__image"
alt=""
draggable={false}
/>
<span className="kf-info-group-title__text">{title}</span>
</Flex>
);
}

export default InfoGroupTitle;

+ 11
- 0
react-ui/src/components/InfoGroup/index.less View File

@@ -0,0 +1,11 @@
.kf-info-group {
width: 100%;

&__content {
padding: 20px @content-padding;
background-color: white;
border: 1px solid @border-color;
border-top: none;
border-radius: 0 0 4px 4px;
}
}

+ 43
- 0
react-ui/src/components/InfoGroup/index.tsx View File

@@ -0,0 +1,43 @@
import classNames from 'classnames';
import InfoGroupTitle from './InfoGroupTitle';
import './index.less';

type InfoGroupProps = {
/** 标题 */
title: string;
/** 高度, 如果要纵向滚动,需要设置高度 */
height?: string | number;
/** 宽度, 如果要横向滚动,需要设置宽度 */
width?: string | number;
/** 自定义类名 */
className?: string;
/** 自定义样式 */
style?: React.CSSProperties;
/** 子元素 */
children?: React.ReactNode;
};

/**
* 信息组,用于展示基本信息,支持横向、纵向滚动。自动机器学习、超参数寻优都是使用这个组件
*/
function InfoGroup({ title, height, width, className, style, children }: InfoGroupProps) {
const contentStyle: React.CSSProperties = {};
if (height) {
contentStyle.height = height;
contentStyle.overflowY = 'auto';
}
if (width) {
contentStyle.width = width;
contentStyle.overflowX = 'auto';
}
return (
<div className={classNames('kf-info-group', className)} style={style}>
<InfoGroupTitle title={title} />
<div style={contentStyle} className={'kf-info-group__content'}>
{children}
</div>
</div>
);
}

export default InfoGroup;

+ 6
- 0
react-ui/src/components/KFBreadcrumb/index.tsx View File

@@ -1,3 +1,9 @@
/*
* @Author: 赵伟
* @Date: 2024-09-02 08:42:57
* @Description: 自定义面包屑,暂时不用,使用了 ProBreadcrumb
*/

import { Breadcrumb, type BreadcrumbProps } from 'antd';
import { Link, matchPath, useLocation } from 'umi';
// import routes from '../../../config/config'; // 导入你的路由配置


+ 0
- 7
react-ui/src/components/KFConfirmModal/index.less View File

@@ -1,7 +0,0 @@
.kf-confirm-modal {
&__content {
width: 100%;
font-size: 18px;
text-align: center;
}
}

+ 0
- 37
react-ui/src/components/KFConfirmModal/index.tsx View File

@@ -1,37 +0,0 @@
/*
* @Author: 赵伟
* @Date: 2024-10-10 10:54:25
* @Description: 自定义 Confirm Modal
*/

import classNames from 'classnames';
import KFModal, { KFModalProps } from '../KFModal';
import './index.less';

export interface KFConfirmModalProps extends KFModalProps {
content: string;
}
function KFConfirmModal({
title,
image,
className = '',
centered,
maskClosable,
content,
...rest
}: KFConfirmModalProps) {
return (
<KFModal
className={classNames(['kf-confirm-modal', className])}
{...rest}
centered={centered ?? true}
maskClosable={maskClosable ?? false}
title={title}
image={image ?? require('@/assets/img/edit-experiment.png')}
>
<div className="kf-confirm-modal__content">{content}</div>
</KFModal>
);
}

export default KFConfirmModal;

+ 1
- 1
react-ui/src/components/KFEmpty/index.less View File

@@ -33,7 +33,7 @@
margin-top: 20px;
margin-bottom: 30px;

&__back-btn {
&__button {
height: 32px;
}
}


+ 16
- 6
react-ui/src/components/KFEmpty/index.tsx View File

@@ -9,15 +9,24 @@ export enum EmptyType {
}

type EmptyProps = {
className?: string;
style?: React.CSSProperties;
/** 类型 */
type: EmptyType;
/** 标题 */
title?: string;
/** 内容 */
content?: string;
/** 是否有页脚,如果有默认是一个按钮 */
hasFooter?: boolean;
footer?: () => React.ReactNode;
/** 按钮标题,默认是"刷新" */
buttonTitle?: string;
onRefresh?: () => void;
/** 按钮点击回调 */
onButtonClick?: () => void;
/** 自定义页脚内容 */
footer?: () => React.ReactNode;
/** 自定义类名 */
className?: string;
/** 自定义样式 */
style?: React.CSSProperties;
};

function getEmptyImage(type: EmptyType) {
@@ -31,6 +40,7 @@ function getEmptyImage(type: EmptyType) {
}
}

/** 空状态 */
function KFEmpty({
className,
style,
@@ -40,7 +50,7 @@ function KFEmpty({
hasFooter = true,
footer,
buttonTitle = '刷新',
onRefresh,
onButtonClick,
}: EmptyProps) {
const image = getEmptyImage(type);

@@ -54,7 +64,7 @@ function KFEmpty({
{footer ? (
footer()
) : (
<Button className="kf-empty__footer__back-btn" type="primary" onClick={onRefresh}>
<Button className="kf-empty__footer__button" type="primary" onClick={onButtonClick}>
{buttonTitle}
</Button>
)}


+ 9
- 3
react-ui/src/components/KFIcon/index.tsx View File

@@ -14,20 +14,26 @@ const Icon = createFromIconfontCN({
type IconFontProps = Parameters<typeof Icon>[0];

interface KFIconProps extends IconFontProps {
/** 图标 */
type: string;
/** 字体大小 */
font?: number;
/** 字体颜色 */
color?: string;
style?: React.CSSProperties;
/** 自定义类名 */
className?: string;
/** 自定义样式 */
style?: React.CSSProperties;
}

function KFIcon({ type, font = 15, color = '', style = {}, className }: KFIconProps) {
/** 封装 iconfont 图标 */
function KFIcon({ type, font = 15, color, className, style, ...rest }: KFIconProps) {
const iconStyle = {
...style,
fontSize: font,
color,
};
return <Icon type={type} className={className} style={iconStyle} />;
return <Icon {...rest} type={type} className={className} style={iconStyle} />;
}

export default KFIcon;

react-ui/src/components/ModalTitle/index.less → react-ui/src/components/KFModal/KFModalTitle.less View File


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

Loading…
Cancel
Save