diff --git a/.gitignore b/.gitignore index 21a12484..5510490a 100644 --- a/.gitignore +++ b/.gitignore @@ -58,3 +58,5 @@ mvnw # web **/node_modules + +*storybook.log diff --git a/k8s/build.sh b/k8s/build.sh index 921a2ec5..5e77bdf9 100755 --- a/k8s/build.sh +++ b/k8s/build.sh @@ -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" } @@ -54,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 diff --git a/k8s/build_and_deploy.sh b/k8s/build_and_deploy.sh index 44ea23fa..6e561cdd 100755 --- a/k8s/build_and_deploy.sh +++ b/k8s/build_and_deploy.sh @@ -19,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" } diff --git a/k8s/deploy.sh b/k8s/deploy.sh index d06c05d9..2dffc04f 100755 --- a/k8s/deploy.sh +++ b/k8s/deploy.sh @@ -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" } @@ -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 diff --git a/k8s/k8s-10gen.yaml b/k8s/k8s-10gen.yaml deleted file mode 100644 index aaec3d8a..00000000 --- a/k8s/k8s-10gen.yaml +++ /dev/null @@ -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 - diff --git a/k8s/k8s-11visual.yaml b/k8s/k8s-11visual.yaml deleted file mode 100644 index 3c2b25fb..00000000 --- a/k8s/k8s-11visual.yaml +++ /dev/null @@ -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 - diff --git a/k8s/k8s-12front.yaml b/k8s/k8s-12front.yaml deleted file mode 100644 index fb24fc2d..00000000 --- a/k8s/k8s-12front.yaml +++ /dev/null @@ -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 - diff --git a/k8s/k8s-3nacos.yaml b/k8s/k8s-3nacos.yaml deleted file mode 100644 index 0c293016..00000000 --- a/k8s/k8s-3nacos.yaml +++ /dev/null @@ -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 diff --git a/k8s/k8s-4gateway.yaml b/k8s/k8s-4gateway.yaml deleted file mode 100644 index b0cf7991..00000000 --- a/k8s/k8s-4gateway.yaml +++ /dev/null @@ -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 - diff --git a/k8s/k8s-5auth.yaml b/k8s/k8s-5auth.yaml deleted file mode 100644 index 2066bd5d..00000000 --- a/k8s/k8s-5auth.yaml +++ /dev/null @@ -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 - diff --git a/k8s/k8s-6system.yaml b/k8s/k8s-6system.yaml deleted file mode 100644 index 8c6830bf..00000000 --- a/k8s/k8s-6system.yaml +++ /dev/null @@ -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 - diff --git a/k8s/k8s-7management.yaml b/k8s/k8s-7management.yaml deleted file mode 100644 index cb07a130..00000000 --- a/k8s/k8s-7management.yaml +++ /dev/null @@ -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 - diff --git a/k8s/k8s-8file.yaml b/k8s/k8s-8file.yaml deleted file mode 100644 index 3f54b8d0..00000000 --- a/k8s/k8s-8file.yaml +++ /dev/null @@ -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 - diff --git a/k8s/k8s-9job.yaml b/k8s/k8s-9job.yaml deleted file mode 100644 index b52cb355..00000000 --- a/k8s/k8s-9job.yaml +++ /dev/null @@ -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 - diff --git a/k8s/template-yaml/deploy/k8s-12front.yaml b/k8s/template-yaml/deploy/k8s-12front.yaml deleted file mode 100644 index 565b12ec..00000000 --- a/k8s/template-yaml/deploy/k8s-12front.yaml +++ /dev/null @@ -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 - diff --git a/k8s/template-yaml/deploy/k8s-7management.yaml b/k8s/template-yaml/deploy/k8s-7management.yaml deleted file mode 100644 index 75f1b522..00000000 --- a/k8s/template-yaml/deploy/k8s-7management.yaml +++ /dev/null @@ -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 - diff --git a/k8s/template-yaml/k8s-13oauth2.yaml b/k8s/template-yaml/k8s-13oauth2.yaml new file mode 100644 index 00000000..88be9902 --- /dev/null +++ b/k8s/template-yaml/k8s-13oauth2.yaml @@ -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 + diff --git a/k8s/template-yaml/k8s-3nacos.yaml b/k8s/template-yaml/k8s-3nacos.yaml index 225c6b23..6b242c2f 100644 --- a/k8s/template-yaml/k8s-3nacos.yaml +++ b/k8s/template-yaml/k8s-3nacos.yaml @@ -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 @@ -38,8 +38,8 @@ spec: - containerPort: 9848 - containerPort: 9849 initContainers: - - name: init-mydb - image: busybox:1.31 + - name: init-mydb-check + image: 172.20.32.187/ci4s/busybox:1.31 command: [ 'sh', '-c', 'nc -zv mysql.argo.svc 3306' ] restartPolicy: Always diff --git a/k8s/template-yaml/k8s-7management.yaml b/k8s/template-yaml/k8s-7management.yaml index edc1c621..57088d5d 100644 --- a/k8s/template-yaml/k8s-7management.yaml +++ b/k8s/template-yaml/k8s-7management.yaml @@ -16,6 +16,8 @@ spec: containers: - name: ci4s-management-platform image: ${k8s-7management-image} + securityContext: + privileged: true env: - name: TZ value: Asia/Shanghai @@ -27,10 +29,30 @@ spec: - name: resource-volume mountPath: /home/resource/ subPath: mini-model-platform-data + mountPropagation: Bidirectional volumes: - name: resource-volume 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 diff --git a/k8s/template-yaml/rolebindings.yaml b/k8s/template-yaml/rolebindings.yaml new file mode 100644 index 00000000..54c2e13d --- /dev/null +++ b/k8s/template-yaml/rolebindings.yaml @@ -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 diff --git a/react-ui/.gitignore b/react-ui/.gitignore index 889039c2..9d2581cd 100644 --- a/react-ui/.gitignore +++ b/react-ui/.gitignore @@ -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 diff --git a/react-ui/.nvmrc b/react-ui/.nvmrc new file mode 100644 index 00000000..8ddbc0c6 --- /dev/null +++ b/react-ui/.nvmrc @@ -0,0 +1 @@ +v18.16.0 diff --git a/react-ui/.storybook/babel-plugin-auto-css-modules.js b/react-ui/.storybook/babel-plugin-auto-css-modules.js new file mode 100644 index 00000000..9c7709ff --- /dev/null +++ b/react-ui/.storybook/babel-plugin-auto-css-modules.js @@ -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"; + } + } + }, + }, + }; +}; \ No newline at end of file diff --git a/react-ui/.storybook/main.ts b/react-ui/.storybook/main.ts new file mode 100644 index 00000000..54824837 --- /dev/null +++ b/react-ui/.storybook/main.ts @@ -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: ['../static'], + 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; diff --git a/react-ui/.storybook/mock/umijs.mock.tsx b/react-ui/.storybook/mock/umijs.mock.tsx new file mode 100644 index 00000000..ae8a7646 --- /dev/null +++ b/react-ui/.storybook/mock/umijs.mock.tsx @@ -0,0 +1,19 @@ +export const Link = ({ to, children, ...props }: any) => ( + + {children} + +); + +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; diff --git a/react-ui/.storybook/preview.tsx b/react-ui/.storybook/preview.tsx new file mode 100644 index 00000000..61e82aaa --- /dev/null +++ b/react-ui/.storybook/preview.tsx @@ -0,0 +1,107 @@ +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 './storybook.css'; + +/* + * Initializes MSW + * See https://github.com/mswjs/msw-storybook-addon#configuring-msw + * to learn how to customize it + */ +initialize(); + +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) => ( + + + + + + ), + ], + loaders: [mswLoader], // 👈 Add the MSW loader to all stories +}; + +export default preview; diff --git a/react-ui/.storybook/storybook.css b/react-ui/.storybook/storybook.css new file mode 100644 index 00000000..6c592a3c --- /dev/null +++ b/react-ui/.storybook/storybook.css @@ -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; +} diff --git a/react-ui/.storybook/tsconfig.json b/react-ui/.storybook/tsconfig.json new file mode 100644 index 00000000..e30a508b --- /dev/null +++ b/react-ui/.storybook/tsconfig.json @@ -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": "./" + } +} diff --git a/react-ui/.storybook/typings.d.ts b/react-ui/.storybook/typings.d.ts new file mode 100644 index 00000000..742f70c6 --- /dev/null +++ b/react-ui/.storybook/typings.d.ts @@ -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; diff --git a/react-ui/config/config.ts b/react-ui/config/config.ts index f64c8c77..04ab330d 100644 --- a/react-ui/config/config.ts +++ b/react-ui/config/config.ts @@ -75,7 +75,7 @@ export default defineConfig({ * @name layout 插件 * @doc https://umijs.org/docs/max/layout-menu */ - title: '复杂智能软件', + title: '智能材料科研平台', layout: { ...defaultSettings, }, diff --git a/react-ui/config/defaultSettings.ts b/react-ui/config/defaultSettings.ts index 306c89db..c4c59d2f 100644 --- a/react-ui/config/defaultSettings.ts +++ b/react-ui/config/defaultSettings.ts @@ -17,7 +17,7 @@ const Settings: ProLayoutProps & { fixSiderbar: false, splitMenus: false, colorWeak: false, - title: '复杂智能软件', + title: '智能材料科研平台', pwa: true, token: { // 参见ts声明,demo 见文档,通过token 修改样式 diff --git a/react-ui/config/routes.ts b/react-ui/config/routes.ts index eaddb001..e9363f91 100644 --- a/react-ui/config/routes.ts +++ b/react-ui/config/routes.ts @@ -44,7 +44,7 @@ export default [ { name: 'login', path: '/user/login', - component: './User/Login/login', + component: process.env.NO_SSO ? './User/Login/login' : './User/Login', }, ], }, @@ -181,6 +181,42 @@ export default [ }, ], }, + { + 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', + }, + ], + }, ], }, { diff --git a/react-ui/package.json b/react-ui/package.json index dc5be1c5..56a4b735 100644 --- a/react-ui/package.json +++ b/react-ui/package.json @@ -8,6 +8,7 @@ "build": "max build", "deploy": "npm run build && npm run gh-pages", "dev": "npm run start:dev", + "dev-no-sso": "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", @@ -83,6 +88,17 @@ }, "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/react": "~8.5.3", + "@storybook/react-webpack5": "~8.5.3", + "@storybook/test": "~8.5.3", "@testing-library/react": "^14.0.0", "@types/antd": "^1.0.0", "@types/express": "^4.17.14", @@ -96,15 +112,22 @@ "@umijs/max": "^4.0.66", "cross-env": "^7.0.3", "eslint": "^8.39.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" @@ -140,5 +163,10 @@ "CNAME", "create-umi" ] + }, + "msw": { + "workerDirectory": [ + "static" + ] } } diff --git a/react-ui/public/favicon-cc.ico b/react-ui/public/favicon-cc.ico new file mode 100644 index 00000000..4d544cb1 Binary files /dev/null and b/react-ui/public/favicon-cc.ico differ diff --git a/react-ui/public/favicon.ico b/react-ui/public/favicon.ico index 4d544cb1..408b8a23 100644 Binary files a/react-ui/public/favicon.ico and b/react-ui/public/favicon.ico differ diff --git a/react-ui/src/app.tsx b/react-ui/src/app.tsx index d08ca129..65b4440a 100644 --- a/react-ui/src/app.tsx +++ b/react-ui/src/app.tsx @@ -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, @@ -41,7 +40,7 @@ export async function getInitialState(): Promise { roleNames: response.user.roles, } as API.CurrentUser; } catch (error) { - console.error('1111', error); + console.error('getInitialState', error); gotoLoginPage(); } return undefined; @@ -215,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'], @@ -228,6 +227,7 @@ 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)', diff --git a/react-ui/src/assets/img/logo-cc.png b/react-ui/src/assets/img/logo-cc.png new file mode 100644 index 00000000..cae91fe5 Binary files /dev/null and b/react-ui/src/assets/img/logo-cc.png differ diff --git a/react-ui/src/assets/img/logo-cl.png b/react-ui/src/assets/img/logo-cl.png deleted file mode 100644 index e2fbcfe5..00000000 Binary files a/react-ui/src/assets/img/logo-cl.png and /dev/null differ diff --git a/react-ui/src/assets/img/logo.png b/react-ui/src/assets/img/logo.png index cae91fe5..e2fbcfe5 100644 Binary files a/react-ui/src/assets/img/logo.png and b/react-ui/src/assets/img/logo.png differ diff --git a/react-ui/src/assets/img/popover-bg.png b/react-ui/src/assets/img/popover-bg.png new file mode 100644 index 00000000..d783c637 Binary files /dev/null and b/react-ui/src/assets/img/popover-bg.png differ diff --git a/react-ui/src/components/BasicInfo/BasicInfoItem.tsx b/react-ui/src/components/BasicInfo/BasicInfoItem.tsx new file mode 100644 index 00000000..86e63891 --- /dev/null +++ b/react-ui/src/components/BasicInfo/BasicInfoItem.tsx @@ -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 =
{formatValue}
; + } else if (Array.isArray(formatValue)) { + valueComponent = ( +
+ {formatValue.map((item: BasicInfoLink) => ( + + ))} +
+ ); + } else if (typeof formatValue === 'object' && formatValue) { + valueComponent = ( + + ); + } else { + valueComponent = ( + + ); + } + return ( +
+
+ + {label} + +
+ {valueComponent} +
+ ); +} + +export default BasicInfoItem; diff --git a/react-ui/src/components/BasicInfo/BasicInfoItemValue.tsx b/react-ui/src/components/BasicInfo/BasicInfoItemValue.tsx new file mode 100644 index 00000000..c5a993e4 --- /dev/null +++ b/react-ui/src/components/BasicInfo/BasicInfoItemValue.tsx @@ -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 = ( + + {value} + + ); + } else if (link && value) { + component = ( + + {value} + + ); + } else { + component = {!isEmpty(value) ? value : '--'}; + } + + return ( +
+ + {component} + +
+ ); +} + +export default BasicInfoItemValue; diff --git a/react-ui/src/components/BasicInfo/components.tsx b/react-ui/src/components/BasicInfo/components.tsx deleted file mode 100644 index b8932a25..00000000 --- a/react-ui/src/components/BasicInfo/components.tsx +++ /dev/null @@ -1,113 +0,0 @@ -/* - * @Author: 赵伟 - * @Date: 2024-11-29 09:27:19 - * @Description: 用于 BasicInfo 和 BasicTableInfo 组件的子组件 - */ - -import { Link } from '@umijs/max'; -import { Typography } from 'antd'; -import React from 'react'; -import { type BasicInfoData, type BasicInfoLink } from './types'; - -type BasicInfoItemProps = { - data: BasicInfoData; - labelWidth: number; - classPrefix: string; -}; - -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 = ( -
- {formatValue.map((item: BasicInfoLink) => ( - - ))} -
- ); - } else if (React.isValidElement(formatValue)) { - // 这个判断必须在下面的判断之前 - valueComponent = ( - - ); - } else if (typeof formatValue === 'object' && formatValue) { - valueComponent = ( - - ); - } else { - valueComponent = ( - - ); - } - return ( -
-
- {label} -
- {valueComponent} -
- ); -} - -type BasicInfoItemValueProps = { - ellipsis?: boolean; - classPrefix: string; - value: string | React.ReactNode; - link?: string; - url?: string; -}; - -export function BasicInfoItemValue({ - value, - link, - url, - ellipsis, - classPrefix, -}: BasicInfoItemValueProps) { - const myClassName = `${classPrefix}__item__value`; - let component = undefined; - if (url && value) { - component = ( - - {value} - - ); - } else if (link && value) { - component = ( - - {value} - - ); - } else if (React.isValidElement(value)) { - return value; - } else { - component = {value ?? '--'}; - } - - return ( -
- - {component} - -
- ); -} diff --git a/react-ui/src/components/BasicInfo/format.ts b/react-ui/src/components/BasicInfo/format.ts deleted file mode 100644 index 0dae2422..00000000 --- a/react-ui/src/components/BasicInfo/format.ts +++ /dev/null @@ -1,48 +0,0 @@ -/* - * @Author: 赵伟 - * @Date: 2024-11-29 09:27:19 - * @Description: 用于 BasicInfo 和 BasicTableInfo 组件的常用转化格式 - */ - -// 格式化日期 -export { formatDate } from '@/utils/date'; - -/** - * 格式化字符串数组 - * @param value - 字符串数组 - * @returns 逗号分隔的字符串 - */ -export const formatList = (value: string[] | null | undefined): string => { - if ( - value === undefined || - value === null || - Array.isArray(value) === false || - value.length === 0 - ) { - return '--'; - } - return value.join(','); -}; - -/** - * 格式化布尔值 - * @param value - 布尔值 - * @returns "是" 或 "否" - */ -export const formatBoolean = (value: boolean): string => { - return value ? '是' : '否'; -}; - -type FormatEnum = (value: string | number) => string; - -/** - * 格式化枚举 - * @param options - 枚举选项 - * @returns 格式化枚举函数 - */ -export const formatEnum = (options: { value: string | number; label: string }[]): FormatEnum => { - return (value: string | number) => { - const option = options.find((item) => item.value === value); - return option ? option.label : '--'; - }; -}; diff --git a/react-ui/src/components/BasicInfo/index.less b/react-ui/src/components/BasicInfo/index.less index e4570868..ced2d2ba 100644 --- a/react-ui/src/components/BasicInfo/index.less +++ b/react-ui/src/components/BasicInfo/index.less @@ -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; + } } } diff --git a/react-ui/src/components/BasicInfo/index.tsx b/react-ui/src/components/BasicInfo/index.tsx index 1336d0b6..11eacfde 100644 --- a/react-ui/src/components/BasicInfo/index.tsx +++ b/react-ui/src/components/BasicInfo/index.tsx @@ -1,27 +1,56 @@ import classNames from 'classnames'; import React from 'react'; -import { BasicInfoItem } from './components'; +import BasicInfoItem from './BasicInfoItem'; import './index.less'; import type { BasicInfoData, BasicInfoLink } from './types'; -export * from './format'; export type { BasicInfoData, BasicInfoLink }; -type BasicInfoProps = { +export type BasicInfoProps = { + /** 基础信息 */ datas: BasicInfoData[]; + /** 标题宽度 */ + labelWidth: number; + /** 标题是否显示省略号 */ + labelEllipsis?: boolean; + /** 是否一行三列 */ + threeColumns?: boolean; + /** 标签对齐方式 */ + labelAlign?: 'start' | 'end' | 'justify'; + /** 自定义类名 */ className?: string; + /** 自定义样式 */ style?: React.CSSProperties; - labelWidth: number; }; -export default function BasicInfo({ datas, className, style, labelWidth }: BasicInfoProps) { +/** + * 基础信息展示组件,用于展示基础信息,支持一行两列或一行三列,支持数据格式化 + */ +export default function BasicInfo({ + datas, + className, + style, + labelWidth, + labelEllipsis = true, + threeColumns = false, + labelAlign = 'start', +}: BasicInfoProps) { return ( -
+
{datas.map((item) => ( ))}
diff --git a/react-ui/src/components/BasicInfo/types.ts b/react-ui/src/components/BasicInfo/types.ts index a7c10ba0..be2ac774 100644 --- a/react-ui/src/components/BasicInfo/types.ts +++ b/react-ui/src/components/BasicInfo/types.ts @@ -3,12 +3,12 @@ export type BasicInfoData = { label: string; value?: any; ellipsis?: boolean; - format?: (_value?: any) => string | BasicInfoLink | BasicInfoLink[] | undefined; + format?: (_value?: any) => string | React.ReactNode | BasicInfoLink | BasicInfoLink[] | undefined; }; // 值为链接的类型 export type BasicInfoLink = { - value: string; + value?: string; link?: string; url?: string; }; diff --git a/react-ui/src/components/BasicTableInfo/index.less b/react-ui/src/components/BasicTableInfo/index.less index 850af79c..1207d033 100644 --- a/react-ui/src/components/BasicTableInfo/index.less +++ b/react-ui/src/components/BasicTableInfo/index.less @@ -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; @@ -34,7 +34,6 @@ &__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; + } } } diff --git a/react-ui/src/components/BasicTableInfo/index.tsx b/react-ui/src/components/BasicTableInfo/index.tsx index 571c4b5b..68c350bd 100644 --- a/react-ui/src/components/BasicTableInfo/index.tsx +++ b/react-ui/src/components/BasicTableInfo/index.tsx @@ -1,30 +1,27 @@ import classNames from 'classnames'; -import { BasicInfoItem } from '../BasicInfo/components'; +import { BasicInfoProps } from '../BasicInfo'; +import BasicInfoItem from '../BasicInfo/BasicInfoItem'; import { type BasicInfoData, type BasicInfoLink } from '../BasicInfo/types'; import './index.less'; -export * from '../BasicInfo/format'; export type { BasicInfoData, BasicInfoLink }; -type BasicTableInfoProps = { - datas: BasicInfoData[]; - className?: string; - style?: React.CSSProperties; - labelWidth: number; -}; - +/** + * 表格基础信息展示组件,用于展示基础信息,一行四列,支持数据格式化 + */ export default function BasicTableInfo({ datas, className, style, labelWidth, -}: BasicTableInfoProps) { + labelEllipsis, +}: BasicInfoProps) { const remainder = datas.length % 4; const array = []; if (remainder > 0) { for (let i = 0; i < 4 - remainder; i++) { array.push({ label: '', - value: '', + value: false, // 用于区分是否是空数据,不能是空字符串、null、undefined }); } } @@ -37,6 +34,7 @@ export default function BasicTableInfo({ key={`${item.label}-${index}`} data={item} labelWidth={labelWidth} + labelEllipsis={labelEllipsis} classPrefix="kf-basic-table-info" /> ))} diff --git a/react-ui/src/pages/Pipeline/components/CodeConfigItem/index.less b/react-ui/src/components/CodeConfigItem/index.less similarity index 100% rename from react-ui/src/pages/Pipeline/components/CodeConfigItem/index.less rename to react-ui/src/components/CodeConfigItem/index.less diff --git a/react-ui/src/pages/Pipeline/components/CodeConfigItem/index.tsx b/react-ui/src/components/CodeConfigItem/index.tsx similarity index 100% rename from react-ui/src/pages/Pipeline/components/CodeConfigItem/index.tsx rename to react-ui/src/components/CodeConfigItem/index.tsx diff --git a/react-ui/src/components/CodeSelect/index.tsx b/react-ui/src/components/CodeSelect/index.tsx index 79401b25..d2afbb94 100644 --- a/react-ui/src/components/CodeSelect/index.tsx +++ b/react-ui/src/components/CodeSelect/index.tsx @@ -4,18 +4,32 @@ * @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 ( -
+
)} diff --git a/react-ui/src/components/KFIcon/index.tsx b/react-ui/src/components/KFIcon/index.tsx index d84257a7..38d3644c 100644 --- a/react-ui/src/components/KFIcon/index.tsx +++ b/react-ui/src/components/KFIcon/index.tsx @@ -14,14 +14,20 @@ const Icon = createFromIconfontCN({ type IconFontProps = Parameters[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, ...rest }: KFIconProps) { +/** 封装 iconfont 图标 */ +function KFIcon({ type, font = 15, color, className, style, ...rest }: KFIconProps) { const iconStyle = { ...style, fontSize: font, diff --git a/react-ui/src/components/ModalTitle/index.less b/react-ui/src/components/KFModal/KFModalTitle.less similarity index 100% rename from react-ui/src/components/ModalTitle/index.less rename to react-ui/src/components/KFModal/KFModalTitle.less diff --git a/react-ui/src/components/ModalTitle/index.tsx b/react-ui/src/components/KFModal/KFModalTitle.tsx similarity index 84% rename from react-ui/src/components/ModalTitle/index.tsx rename to react-ui/src/components/KFModal/KFModalTitle.tsx index 4c0179eb..d2ec3265 100644 --- a/react-ui/src/components/ModalTitle/index.tsx +++ b/react-ui/src/components/KFModal/KFModalTitle.tsx @@ -6,12 +6,16 @@ import classNames from 'classnames'; import React from 'react'; -import './index.less'; +import './KFModalTitle.less'; type ModalTitleProps = { + /** 标题 */ title: React.ReactNode; + /** 图片 */ image?: string; + /** 自定义样式 */ style?: React.CSSProperties; + /** 自定义类名 */ className?: string; }; diff --git a/react-ui/src/components/KFModal/index.tsx b/react-ui/src/components/KFModal/index.tsx index c073ab27..8156d6a9 100644 --- a/react-ui/src/components/KFModal/index.tsx +++ b/react-ui/src/components/KFModal/index.tsx @@ -4,19 +4,22 @@ * @Description: 自定义 Modal */ -import ModalTitle from '@/components/ModalTitle'; import { Modal, type ModalProps } from 'antd'; import classNames from 'classnames'; +import KFModalTitle from './KFModalTitle'; import './index.less'; export interface KFModalProps extends ModalProps { + /** 标题图片 */ image?: string; } + +/** 自定义 Modal,应用中的业务 Modal 应该使用它进行封装,推荐使用函数的方式打开 */ function KFModal({ title, image, children, - className = '', + className, centered, maskClosable, ...rest @@ -27,7 +30,7 @@ function KFModal({ {...rest} centered={centered ?? true} maskClosable={maskClosable ?? false} - title={} + title={} > {children} diff --git a/react-ui/src/components/KFRadio/index.tsx b/react-ui/src/components/KFRadio/index.tsx index 4bb4ccce..7191dae6 100644 --- a/react-ui/src/components/KFRadio/index.tsx +++ b/react-ui/src/components/KFRadio/index.tsx @@ -8,32 +8,40 @@ import classNames from 'classnames'; import './index.less'; export type KFRadioItem = { - key: string; title: string; + value: string; icon?: React.ReactNode; }; type KFRadioProps = { + /** 选项 */ items: KFRadioItem[]; + /** 当前选中项 */ value?: string; + /** 自定义样式 */ style?: React.CSSProperties; + /** 自定义类名 */ className?: string; + /** 选中回调 */ onChange?: (value: string) => void; }; +/** + * 自定义 Radio + */ function KFRadio({ items, value, style, className, onChange }: KFRadioProps) { return ( {items.map((item) => { return ( onChange?.(item.key)} + onClick={() => onChange?.(item.value)} > {item.icon} {item.title} diff --git a/react-ui/src/components/KFSpin/index.tsx b/react-ui/src/components/KFSpin/index.tsx index 519ab6ef..a3a6c3e5 100644 --- a/react-ui/src/components/KFSpin/index.tsx +++ b/react-ui/src/components/KFSpin/index.tsx @@ -5,13 +5,19 @@ */ import { Spin, SpinProps } from 'antd'; -import styles from './index.less'; +import './index.less'; -function KFSpin(props: SpinProps) { +interface KFSpinProps extends SpinProps { + /** 加载文本 */ + label: string; +} + +/** 自定义 Spin */ +function KFSpin({ label = '加载中', ...rest }: KFSpinProps) { return ( -
- -
加载中
+
+ +
{label}
); } diff --git a/react-ui/src/components/LabelValue/index.less b/react-ui/src/components/LabelValue/index.less deleted file mode 100644 index 5f1b9b0c..00000000 --- a/react-ui/src/components/LabelValue/index.less +++ /dev/null @@ -1,19 +0,0 @@ -.kf-label-value { - display: flex; - align-items: flex-start; - font-size: 16px; - line-height: 1.6; - - &__label { - flex: none; - width: 80px; - color: @text-color-secondary; - } - - &__value { - flex: 1; - color: @text-color; - white-space: pre-line; - word-break: break-all; - } -} diff --git a/react-ui/src/components/LabelValue/index.tsx b/react-ui/src/components/LabelValue/index.tsx deleted file mode 100644 index 22b9b3eb..00000000 --- a/react-ui/src/components/LabelValue/index.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import classNames from 'classnames'; -import './index.less'; - -type labelValueProps = { - label: string; - value?: any; - className?: string; - style?: React.CSSProperties; -}; - -function LabelValue({ label, value, className, style }: labelValueProps) { - return ( -
-
{label}
-
{value ?? '--'}
-
- ); -} - -export default LabelValue; diff --git a/react-ui/src/components/MenuIconSelector/index.less b/react-ui/src/components/MenuIconSelector/index.less index 77529762..5a64a8d3 100644 --- a/react-ui/src/components/MenuIconSelector/index.less +++ b/react-ui/src/components/MenuIconSelector/index.less @@ -1,5 +1,4 @@ .menu-icon-selector { - // grid 布局,每行显示 8 个图标 display: grid; grid-template-columns: repeat(4, 80px); gap: 20px; @@ -10,7 +9,7 @@ display: flex; align-items: center; justify-content: center; - width: 80x; + width: 80px; height: 80px; border: 1px solid transparent; border-radius: 4px; diff --git a/react-ui/src/components/MenuIconSelector/index.tsx b/react-ui/src/components/MenuIconSelector/index.tsx index dd57320e..fa38910b 100644 --- a/react-ui/src/components/MenuIconSelector/index.tsx +++ b/react-ui/src/components/MenuIconSelector/index.tsx @@ -12,7 +12,9 @@ import { useEffect, useState } from 'react'; import styles from './index.less'; interface MenuIconSelectorProps extends Omit { + /** 选中的图标 */ selectedIcon?: string; + /** 选择回调 */ onOk: (param: string) => void; } @@ -21,6 +23,7 @@ type IconObject = { font_class: string; }; +/** 菜单图标选择器 */ function MenuIconSelector({ open, selectedIcon, onOk, ...rest }: MenuIconSelectorProps) { const [icons, setIcons] = useState([]); useEffect(() => { diff --git a/react-ui/src/components/PageTitle/index.tsx b/react-ui/src/components/PageTitle/index.tsx index ca192454..2703e032 100644 --- a/react-ui/src/components/PageTitle/index.tsx +++ b/react-ui/src/components/PageTitle/index.tsx @@ -8,10 +8,17 @@ import React from 'react'; import './index.less'; type PageTitleProps = { - title: string; + /** 标题 */ + title: React.ReactNode; + /** 自定义类名 */ className?: string; + /** 自定义样式 */ style?: React.CSSProperties; }; + +/** + * 页面标题 + */ function PageTitle({ title, style, className = '' }: PageTitleProps) { return (
diff --git a/react-ui/src/components/ParameterInput/index.less b/react-ui/src/components/ParameterInput/index.less index 4b22d208..fff69eb1 100644 --- a/react-ui/src/components/ParameterInput/index.less +++ b/react-ui/src/components/ParameterInput/index.less @@ -39,7 +39,7 @@ &__placeholder { min-height: 22px; - color: rgba(0, 0, 0, 0.25); + color: @text-placeholder-color; font-size: @font-size-input; line-height: 1.5714285714285714; } @@ -49,18 +49,31 @@ padding: 10px 11px; font-size: @font-size-input-lg; - .parameter-input__placeholder { + .parameter-input__placeholder, + .parameter-input__content__value { + min-height: 24px; font-size: @font-size-input-lg; line-height: 1.5; } + .parameter-input__content__close-icon { + font-size: 12px; + } +} + +.parameter-input.parameter-input--small { + padding: 0 7px; + font-size: @font-size-input; + + .parameter-input__placeholder, .parameter-input__content__value { - font-size: @font-size-input-lg; - line-height: 1.5; + min-height: 22px; + font-size: @font-size-input; + line-height: 1.5714285714285714; } .parameter-input__content__close-icon { - font-size: 12px; + font-size: 10px; } } diff --git a/react-ui/src/components/ParameterInput/index.tsx b/react-ui/src/components/ParameterInput/index.tsx index 94fdddde..47ef5d0e 100644 --- a/react-ui/src/components/ParameterInput/index.tsx +++ b/react-ui/src/components/ParameterInput/index.tsx @@ -1,21 +1,22 @@ /* * @Author: 赵伟 * @Date: 2024-04-16 08:42:57 - * @Description: 参数输入组件 + * @Description: 参数输入表单组件,支持手动输入,也支持选择全局参数 */ +import { CommonTabKeys } from '@/enums'; import { CloseOutlined } from '@ant-design/icons'; import { Form, Input } from 'antd'; import { RuleObject } from 'antd/es/form'; import classNames from 'classnames'; import './index.less'; -// 对象 +// 如果值是对象时的类型 export type ParameterInputObject = { value?: any; // 值 showValue?: any; // 显示值 fromSelect?: boolean; // 是否来自选择 - activeTab?: string; // 选择镜像、数据集、模型时,保存当前激活的tab + activeTab?: CommonTabKeys; // 选择镜像、数据集、模型时,保存当前激活的tab expandedKeys?: string[]; // 选择镜像、数据集、模型时,保存展开的keys checkedKeys?: string[]; // 选择镜像、数据集、模型时,保存选中的keys [key: string]: any; @@ -25,18 +26,34 @@ export type ParameterInputObject = { export type ParameterInputValue = ParameterInputObject | string; export interface ParameterInputProps { + /** 值,可以是字符串,也可以是 ParameterInputObject 对象 */ value?: ParameterInputValue; + /** + * 值变化时的回调 + * @param value 值,可以是字符串,也可以是 ParameterInputObject 对象 + */ onChange?: (value?: ParameterInputValue) => void; + /** 点击时的回调 */ onClick?: () => void; + /** 删除时的回调 */ onRemove?: () => void; + /** 是否可以手动输入 */ canInput?: boolean; + /** 是否是文本框 */ textArea?: boolean; + /** 占位符 */ placeholder?: string; + /** 是否允许清除 */ allowClear?: boolean; + /** 自定义类名 */ className?: string; + /** 自定义样式 */ style?: React.CSSProperties; + /** 大小 */ size?: 'middle' | 'small' | 'large'; + /** 是否禁用 */ disabled?: boolean; + /** 元素 id */ id?: string; } @@ -88,6 +105,7 @@ function ParameterInput({ className={classNames( 'parameter-input', { 'parameter-input--large': size === 'large' }, + { 'parameter-input--small': size === 'small' }, { [`parameter-input--${status}`]: status }, className, )} diff --git a/react-ui/src/components/ParameterSelect/index.tsx b/react-ui/src/components/ParameterSelect/index.tsx index ffaf8415..2c9f862f 100644 --- a/react-ui/src/components/ParameterSelect/index.tsx +++ b/react-ui/src/components/ParameterSelect/index.tsx @@ -1,7 +1,7 @@ /* * @Author: 赵伟 * @Date: 2024-04-16 08:42:57 - * @Description: 参数选择组件 + * @Description: 参数下拉选择组件,支持资源规格、数据集、模型、服务 */ import { PipelineNodeModelParameter } from '@/types'; diff --git a/react-ui/src/components/ResourceSelect/index.tsx b/react-ui/src/components/ResourceSelect/index.tsx index 5f2142d8..c4bef4bf 100644 --- a/react-ui/src/components/ResourceSelect/index.tsx +++ b/react-ui/src/components/ResourceSelect/index.tsx @@ -9,30 +9,71 @@ import ResourceSelectorModal, { ResourceSelectorResponse, ResourceSelectorType, selectorTypeConfig, -} from '@/pages/Pipeline/components/ResourceSelectorModal'; +} from '@/components/ResourceSelectorModal'; import { openAntdModal } from '@/utils/modal'; import { Button } from 'antd'; -import { useState } from 'react'; +import classNames from 'classnames'; +import { pick } from 'lodash'; +import { useEffect, useState } from 'react'; import ParameterInput, { type ParameterInputProps } from '../ParameterInput'; import './index.less'; -export { requiredValidator, type ParameterInputObject } from '../ParameterInput'; +export { + requiredValidator, + type ParameterInputObject, + type ParameterInputValue, +} from '../ParameterInput'; export { ResourceSelectorType, selectorTypeConfig, type ResourceSelectorResponse }; -type ResourceSelectProps = { +interface ResourceSelectProps extends ParameterInputProps { + /** 类型,数据集、模型、镜像 */ type: ResourceSelectorType; -} & ParameterInputProps; +} -// 获取选择数据集、模型后面按钮 icon +// 获取选择数据集、模型、镜像后面按钮 icon const getSelectBtnIcon = (type: ResourceSelectorType) => { return ; }; -function ResourceSelect({ type, value, onChange, disabled, ...rest }: ResourceSelectProps) { +/** 数据集、模型、镜像选择表单组件 */ +function ResourceSelect({ + type, + value, + size, + disabled, + className, + style, + onChange, + ...rest +}: ResourceSelectProps) { const [selectedResource, setSelectedResource] = useState( undefined, ); + useEffect(() => { + if ( + value && + typeof value === 'object' && + value.activeTab && + value.id && + value.name && + value.version && + value.path && + (type === ResourceSelectorType.Mirror || (value.identifier && value.owner)) + ) { + const originResource = pick(value, [ + 'activeTab', + 'id', + 'identifier', + 'name', + 'owner', + 'version', + 'path', + ]) as ResourceSelectorResponse; + setSelectedResource(originResource); + } + }, [value]); + const selectResource = () => { const resource = selectedResource; const { close } = openAntdModal(ResourceSelectorModal, { @@ -50,8 +91,10 @@ function ResourceSelect({ type, value, onChange, disabled, ...rest }: ResourceSe showValue: path, fromSelect: true, activeTab, - expandedKeys: [`${id}`], - checkedKeys: [`${id}-${version}`], + id, + name, + version, + path, }); } else { const jsonObj = { @@ -69,8 +112,6 @@ function ResourceSelect({ type, value, onChange, disabled, ...rest }: ResourceSe showValue, fromSelect: true, activeTab, - expandedKeys: [`${id}`], - checkedKeys: [`${id}-${version}`], ...jsonObj, }); } @@ -80,8 +121,6 @@ function ResourceSelect({ type, value, onChange, disabled, ...rest }: ResourceSe showValue: undefined, fromSelect: false, activeTab: undefined, - expandedKeys: [], - checkedKeys: [], }); } close(); @@ -90,18 +129,19 @@ function ResourceSelect({ type, value, onChange, disabled, ...rest }: ResourceSe }; return ( -
+
setSelectedResource(undefined)} onClick={selectResource} > diff --git a/react-ui/src/pages/AutoML/Info/index.tsx b/react-ui/src/pages/AutoML/Info/index.tsx index cc5247e2..0d0ec460 100644 --- a/react-ui/src/pages/AutoML/Info/index.tsx +++ b/react-ui/src/pages/AutoML/Info/index.tsx @@ -3,9 +3,7 @@ * @Date: 2024-04-16 13:58:08 * @Description: 自主机器学习详情 */ -import KFIcon from '@/components/KFIcon'; import PageTitle from '@/components/PageTitle'; -import { CommonTabKeys } from '@/enums'; import { getAutoMLInfoReq } from '@/services/autoML'; import { safeInvoke } from '@/utils/functional'; import { to } from '@/utils/promise'; @@ -16,24 +14,10 @@ import { AutoMLData } from '../types'; import styles from './index.less'; function AutoMLInfo() { - const [activeTab, setActiveTab] = useState(CommonTabKeys.Public); const params = useParams(); const autoMLId = safeInvoke(Number)(params.id); const [autoMLInfo, setAutoMLInfo] = useState(undefined); - const tabItems = [ - { - key: CommonTabKeys.Public, - label: '基本信息', - icon: , - }, - { - key: CommonTabKeys.Private, - label: 'Trial列表', - icon: , - }, - ]; - useEffect(() => { if (autoMLId) { getAutoMLInfo(); diff --git a/react-ui/src/pages/AutoML/List/index.tsx b/react-ui/src/pages/AutoML/List/index.tsx index 13e3dcbe..a4488e4d 100644 --- a/react-ui/src/pages/AutoML/List/index.tsx +++ b/react-ui/src/pages/AutoML/List/index.tsx @@ -3,419 +3,11 @@ * @Date: 2024-04-16 13:58:08 * @Description: 自主机器学习列表 */ -import KFIcon from '@/components/KFIcon'; -import PageTitle from '@/components/PageTitle'; -import { ExperimentStatus } from '@/enums'; -import { useCacheState } from '@/hooks/pageCacheState'; -import { experimentStatusInfo } from '@/pages/Experiment/status'; -import { - deleteAutoMLReq, - getAutoMLListReq, - getExperimentInsListReq, - runAutoMLReq, -} from '@/services/autoML'; -import themes from '@/styles/theme.less'; -import { type ExperimentInstance as ExperimentInstanceData } from '@/types'; -import { to } from '@/utils/promise'; -import tableCellRender, { TableCellValueType } from '@/utils/table'; -import { modalConfirm } from '@/utils/ui'; -import { useNavigate } from '@umijs/max'; -import { - App, - Button, - ConfigProvider, - Input, - Table, - Tooltip, - type TablePaginationConfig, - type TableProps, -} from 'antd'; -import { type SearchProps } from 'antd/es/input'; -import classNames from 'classnames'; -import { useEffect, useState } from 'react'; -import ExperimentInstance from '../components/ExperimentInstance'; -import { AutoMLData } from '../types'; -import styles from './index.less'; -function AutoMLList() { - const navigate = useNavigate(); - const { message } = App.useApp(); - const [cacheState, setCacheState] = useCacheState(); - const [searchText, setSearchText] = useState(cacheState?.searchText); - const [inputText, setInputText] = useState(cacheState?.searchText); - const [tableData, setTableData] = useState([]); - const [total, setTotal] = useState(0); - const [experimentInsList, setExperimentInsList] = useState([]); - const [expandedRowKeys, setExpandedRowKeys] = useState([]); - const [experimentInsTotal, setExperimentInsTotal] = useState(0); - const [pagination, setPagination] = useState( - cacheState?.pagination ?? { - current: 1, - pageSize: 10, - }, - ); - - useEffect(() => { - getAutoMLList(); - }, [pagination, searchText]); - - // 获取自主机器学习列表 - const getAutoMLList = async () => { - const params: Record = { - page: pagination.current! - 1, - size: pagination.pageSize, - ml_name: searchText || undefined, - }; - const [res] = await to(getAutoMLListReq(params)); - if (res && res.data) { - const { content = [], totalElements = 0 } = res.data; - setTableData(content); - setTotal(totalElements); - } - }; - - // 搜索 - const onSearch: SearchProps['onSearch'] = (value) => { - setSearchText(value); - setPagination((prev) => ({ - ...prev, - current: 1, - })); - }; - - // 删除模型部署 - const deleteAutoML = async (record: AutoMLData) => { - const [res] = await to(deleteAutoMLReq(record.id)); - if (res) { - message.success('删除成功'); - // 如果是一页的唯一数据,删除时,请求第一页的数据 - // 否则直接刷新这一页的数据 - // 避免回到第一页 - if (tableData.length > 1) { - setPagination((prev) => ({ - ...prev, - current: 1, - })); - } else { - getAutoMLList(); - } - } - }; - - // 处理删除 - const handleAutoMLDelete = (record: AutoMLData) => { - modalConfirm({ - title: '删除后,该实验将不可恢复', - content: '是否确认删除?', - onOk: () => { - deleteAutoML(record); - }, - }); - }; - - // 创建、编辑、复制自动机器学习 - const createAutoML = (record?: AutoMLData, isCopy: boolean = false) => { - setCacheState({ - pagination, - searchText, - }); - - if (record) { - if (isCopy) { - navigate(`/pipeline/autoML/copy/${record.id}`); - } else { - navigate(`/pipeline/autoML/edit/${record.id}`); - } - } else { - navigate(`/pipeline/autoML/create`); - } - }; - - // 查看自动机器学习详情 - const gotoDetail = (record: AutoMLData) => { - setCacheState({ - pagination, - searchText, - }); - - navigate(`/pipeline/autoML/info/${record.id}`); - }; - - // 启动自动机器学习 - const startAutoML = async (record: AutoMLData) => { - const [res] = await to(runAutoMLReq(record.id)); - if (res) { - message.success('运行成功'); - setExpandedRowKeys([record.id]); - refreshExperimentList(); - refreshExperimentIns(record.id); - } - }; +import ExperimentList, { ExperimentListType } from '../components/ExperimentList'; - // --------------------------- 实验实例 --------------------------- - // 获取实验实例列表 - const getExperimentInsList = async (autoMLId: number, page: number) => { - const params = { - autoMlId: autoMLId, - page: page, - size: 5, - }; - const [res] = await to(getExperimentInsListReq(params)); - if (res && res.data) { - const { content = [], totalElements = 0 } = res.data; - try { - if (page === 0) { - setExperimentInsList(content); - } else { - setExperimentInsList((prev) => [...prev, ...content]); - } - setExperimentInsTotal(totalElements); - } catch (error) { - console.error('JSON parse error: ', error); - } - } - }; - // 展开实例 - const handleExpandChange = (expanded: boolean, record: AutoMLData) => { - setExperimentInsList([]); - if (expanded) { - setExpandedRowKeys([record.id]); - getExperimentInsList(record.id, 0); - } else { - setExpandedRowKeys([]); - } - }; - - // 跳转到实验实例详情 - const gotoInstanceInfo = (autoML: AutoMLData, record: ExperimentInstanceData) => { - navigate({ pathname: `/pipeline/automl/instance/${autoML.id}/${record.id}` }); - }; - - // 刷新实验实例列表 - const refreshExperimentIns = (experimentId: number) => { - getExperimentInsList(experimentId, 0); - }; - - // 加载更多实验实例 - const loadMoreExperimentIns = () => { - const page = Math.round(experimentInsList.length / 5); - const autoMLId = expandedRowKeys[0]; - getExperimentInsList(autoMLId, page); - }; - - // 实验实例终止 - const handleInstanceTerminate = async (experimentIns: ExperimentInstanceData) => { - // 刷新实验列表 - refreshExperimentList(); - setExperimentInsList((prevList) => { - return prevList.map((item) => { - if (item.id === experimentIns.id) { - return { - ...item, - status: ExperimentStatus.Terminated, - }; - } - return item; - }); - }); - }; - - // 刷新实验列表状态, - // 目前是直接刷新实验列表,后续需要优化,只刷新状态 - const refreshExperimentList = () => { - getAutoMLList(); - }; - - // --------------------------- Table --------------------------- - // 分页切换 - const handleTableChange: TableProps['onChange'] = ( - pagination, - _filters, - _sorter, - { action }, - ) => { - if (action === 'paginate') { - setPagination(pagination); - } - }; - - const columns: TableProps['columns'] = [ - { - title: '实验名称', - dataIndex: 'ml_name', - key: 'ml_name', - width: '16%', - render: tableCellRender(false, TableCellValueType.Link, { - onClick: gotoDetail, - }), - }, - { - title: '实验描述', - dataIndex: 'ml_description', - key: 'ml_description', - render: tableCellRender(true), - ellipsis: { showTitle: false }, - }, - - { - title: '创建时间', - dataIndex: 'update_time', - key: 'update_time', - width: '20%', - render: tableCellRender(true, TableCellValueType.Date), - ellipsis: { showTitle: false }, - }, - { - title: '最近五次运行状态', - dataIndex: 'status_list', - key: 'status_list', - width: 200, - render: (text) => { - const newText: string[] = text && text.replace(/\s+/g, '').split(','); - return ( - <> - {newText && newText.length > 0 - ? newText.map((item, index) => { - return ( - - - - ); - }) - : null} - - ); - }, - }, - { - title: '操作', - dataIndex: 'operation', - width: 360, - key: 'operation', - render: (_: any, record: AutoMLData) => ( -
- - - - - - - -
- ), - }, - ]; - - return ( -
- -
-
- setInputText(e.target.value)} - style={{ width: 300 }} - value={inputText} - allowClear - /> - -
-
- `共${total}条`, - }} - onChange={handleTableChange} - expandable={{ - expandedRowRender: (record) => ( - gotoInstanceInfo(record, item)} - onRemove={() => { - refreshExperimentIns(record.id); - refreshExperimentList(); - }} - onTerminate={handleInstanceTerminate} - onLoadMore={() => loadMoreExperimentIns()} - > - ), - onExpand: (e, a) => { - handleExpandChange(e, a); - }, - expandedRowKeys: expandedRowKeys, - rowExpandable: () => true, - }} - rowKey="id" - /> - - - - ); +function AutoMLList() { + return ; } export default AutoMLList; diff --git a/react-ui/src/pages/AutoML/components/AutoMLBasic/index.tsx b/react-ui/src/pages/AutoML/components/AutoMLBasic/index.tsx index 854c6035..5f207b7d 100644 --- a/react-ui/src/pages/AutoML/components/AutoMLBasic/index.tsx +++ b/react-ui/src/pages/AutoML/components/AutoMLBasic/index.tsx @@ -1,28 +1,16 @@ +import ConfigInfo, { type BasicInfoData } from '@/components/ConfigInfo'; import { AutoMLTaskType, autoMLEnsembleClassOptions, autoMLTaskTypeOptions } from '@/enums'; import { AutoMLData } from '@/pages/AutoML/types'; import { experimentStatusInfo } from '@/pages/Experiment/status'; import { type NodeStatus } from '@/types'; import { parseJsonText } from '@/utils'; import { elapsedTime } from '@/utils/date'; +import { formatBoolean, formatDataset, formatDate, formatEnum } from '@/utils/format'; import { Flex } from 'antd'; import classNames from 'classnames'; import { useMemo } from 'react'; -import ConfigInfo, { - formatBoolean, - formatDate, - formatEnum, - type BasicInfoData, -} from '../ConfigInfo'; import styles from './index.less'; -// 格式化数据集 -const formatDataset = (dataset: { name: string; version: string }) => { - if (!dataset || !dataset.name || !dataset.version) { - return '--'; - } - return `${dataset.name}:${dataset.version}`; -}; - // 格式化优化方向 const formatOptimizeMode = (value: boolean) => { return value ? '越大越好' : '越小越好'; @@ -58,28 +46,23 @@ function AutoMLBasic({ info, className, runStatus, isInstance = false }: AutoMLB { label: '实验名称', value: info.ml_name, - ellipsis: true, }, { label: '实验描述', value: info.ml_description, - ellipsis: true, }, { label: '创建人', value: info.create_by, - ellipsis: true, }, { label: '创建时间', value: info.create_time, - ellipsis: true, format: formatDate, }, { label: '更新时间', value: info.update_time, - ellipsis: true, format: formatDate, }, ]; @@ -93,18 +76,15 @@ function AutoMLBasic({ info, className, runStatus, isInstance = false }: AutoMLB { label: '任务类型', value: info.task_type, - ellipsis: true, format: formatEnum(autoMLTaskTypeOptions), }, { label: '特征预处理算法', value: info.include_feature_preprocessor, - ellipsis: true, }, { label: '排除的特征预处理算法', value: info.exclude_feature_preprocessor, - ellipsis: true, }, { label: info.task_type === AutoMLTaskType.Regression ? '回归算法' : '分类算法', @@ -112,7 +92,6 @@ function AutoMLBasic({ info, className, runStatus, isInstance = false }: AutoMLB info.task_type === AutoMLTaskType.Regression ? info.include_regressor : info.include_classifier, - ellipsis: true, }, { label: info.task_type === AutoMLTaskType.Regression ? '排除的回归算法' : '排除的分类算法', @@ -120,91 +99,73 @@ function AutoMLBasic({ info, className, runStatus, isInstance = false }: AutoMLB info.task_type === AutoMLTaskType.Regression ? info.exclude_regressor : info.exclude_classifier, - ellipsis: true, }, { label: '集成方式', value: info.ensemble_class, - ellipsis: true, format: formatEnum(autoMLEnsembleClassOptions), }, { label: '集成模型数量', value: info.ensemble_size, - ellipsis: true, }, { label: '集成最佳模型数量', value: info.ensemble_nbest, - ellipsis: true, }, { label: '最大数量', value: info.max_models_on_disc, - ellipsis: true, }, { label: '内存限制(MB)', value: info.memory_limit, - ellipsis: true, }, { label: '单次时间限制(秒)', value: info.per_run_time_limit, - ellipsis: true, }, { label: '搜索时间限制(秒)', value: info.time_left_for_this_task, - ellipsis: true, }, { label: '重采样策略', value: info.resampling_strategy, - ellipsis: true, }, { label: '交叉验证折数', value: info.folds, - ellipsis: true, }, { label: '是否打乱', value: info.shuffle, - ellipsis: true, format: formatBoolean, }, { label: '训练集比率', value: info.train_size, - ellipsis: true, }, { label: '测试集比率', value: info.test_size, - ellipsis: true, }, { label: '计算指标', value: info.scoring_functions, - ellipsis: true, }, { label: '随机种子', value: info.seed, - ellipsis: true, }, - { label: '数据集', value: info.dataset, - ellipsis: true, format: formatDataset, }, { label: '预测目标列', value: info.target_columns, - ellipsis: true, }, ]; }, [info]); @@ -217,18 +178,15 @@ function AutoMLBasic({ info, className, runStatus, isInstance = false }: AutoMLB { label: '指标名称', value: info.metric_name, - ellipsis: true, }, { label: '优化方向', value: info.greater_is_better, - ellipsis: true, format: formatOptimizeMode, }, { label: '指标权重', value: info.metrics, - ellipsis: true, format: formatMetricsWeight, }, ]; @@ -243,12 +201,10 @@ function AutoMLBasic({ info, className, runStatus, isInstance = false }: AutoMLB { label: '启动时间', value: formatDate(runStatus.startedAt), - ellipsis: true, }, { label: '执行时长', value: elapsedTime(runStatus.startedAt, runStatus.finishedAt), - ellipsis: true, }, { label: '状态', @@ -271,7 +227,6 @@ function AutoMLBasic({ info, className, runStatus, isInstance = false }: AutoMLB ), - ellipsis: true, }, ]; }, [runStatus]); @@ -281,7 +236,7 @@ function AutoMLBasic({ info, className, runStatus, isInstance = false }: AutoMLB {isInstance && runStatus && ( @@ -289,18 +244,18 @@ function AutoMLBasic({ info, className, runStatus, isInstance = false }: AutoMLB {!isInstance && ( )} - + ); } diff --git a/react-ui/src/pages/AutoML/components/ConfigInfo/index.less b/react-ui/src/pages/AutoML/components/ConfigInfo/index.less deleted file mode 100644 index 33fb3314..00000000 --- a/react-ui/src/pages/AutoML/components/ConfigInfo/index.less +++ /dev/null @@ -1,20 +0,0 @@ -.config-info { - :global { - .kf-basic-info { - width: 100%; - - &__item { - width: calc((100% - 80px) / 3); - &__label { - font-size: @font-size; - text-align: left; - text-align-last: left; - } - &__value { - min-width: 0; - font-size: @font-size; - } - } - } - } -} diff --git a/react-ui/src/pages/AutoML/components/ConfigInfo/index.tsx b/react-ui/src/pages/AutoML/components/ConfigInfo/index.tsx deleted file mode 100644 index 10e042e4..00000000 --- a/react-ui/src/pages/AutoML/components/ConfigInfo/index.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import BasicInfo, { type BasicInfoData } from '@/components/BasicInfo'; -import InfoGroup from '@/components/InfoGroup'; -import classNames from 'classnames'; -import styles from './index.less'; -export * from '@/components/BasicInfo/format'; -export type { BasicInfoData }; - -type ConfigInfoProps = { - title: string; - data: BasicInfoData[]; - labelWidth: number; - className?: string; - style?: React.CSSProperties; -}; - -function ConfigInfo({ title, data, labelWidth, className, style }: ConfigInfoProps) { - return ( - -
- -
-
- ); -} - -export default ConfigInfo; diff --git a/react-ui/src/pages/AutoML/components/CopyingText/index.tsx b/react-ui/src/pages/AutoML/components/CopyingText/index.tsx index b4c56f4e..586de40b 100644 --- a/react-ui/src/pages/AutoML/components/CopyingText/index.tsx +++ b/react-ui/src/pages/AutoML/components/CopyingText/index.tsx @@ -9,11 +9,7 @@ export type CopyingTextProps = { function CopyingText({ text }: CopyingTextProps) { return (
- + {text} - + + + + + + + +
+ ), + }, + ]; + + return ( +
+ +
+
+ setInputText(e.target.value)} + style={{ width: 300 }} + value={inputText} + allowClear + /> + +
+
+
`共${total}条`, + }} + onChange={handleTableChange} + expandable={{ + expandedRowRender: (record) => ( + gotoInstanceInfo(record, item)} + onRemove={() => { + refreshExperimentIns(record.id); + refreshExperimentList(); + }} + onTerminate={handleInstanceTerminate} + onLoadMore={() => loadMoreExperimentIns()} + > + ), + onExpand: (e, a) => { + handleExpandChange(e, a); + }, + expandedRowKeys: expandedRowKeys, + rowExpandable: () => true, + }} + rowKey="id" + /> + + + + ); +} + +export default ExperimentList; diff --git a/react-ui/src/pages/CodeConfig/List/index.tsx b/react-ui/src/pages/CodeConfig/List/index.tsx index 2efef04c..0c484e54 100644 --- a/react-ui/src/pages/CodeConfig/List/index.tsx +++ b/react-ui/src/pages/CodeConfig/List/index.tsx @@ -197,7 +197,7 @@ function CodeConfigList() { title="暂无数据" content={'很抱歉,没有搜索到您想要的内容\n建议刷新试试'} hasFooter={true} - onRefresh={getDataList} + onButtonClick={getDataList} /> )} diff --git a/react-ui/src/pages/Dataset/components/CategoryItem/index.tsx b/react-ui/src/pages/Dataset/components/CategoryItem/index.tsx index 08398e56..d308dd45 100644 --- a/react-ui/src/pages/Dataset/components/CategoryItem/index.tsx +++ b/react-ui/src/pages/Dataset/components/CategoryItem/index.tsx @@ -33,10 +33,7 @@ function CategoryItem({ resourceType, item, isSelected, onClick }: CategoryItemP alt="" draggable={false} /> - + {item.name} diff --git a/react-ui/src/pages/Dataset/components/ResourceIntro/index.tsx b/react-ui/src/pages/Dataset/components/ResourceIntro/index.tsx index b0bad9de..10d7d9d2 100644 --- a/react-ui/src/pages/Dataset/components/ResourceIntro/index.tsx +++ b/react-ui/src/pages/Dataset/components/ResourceIntro/index.tsx @@ -1,17 +1,8 @@ import BasicTableInfo, { BasicInfoData } from '@/components/BasicTableInfo'; import SubAreaTitle from '@/components/SubAreaTitle'; -import { ResourceInfoTabKeys } from '@/pages/Dataset/components/ResourceInfo'; -import { - DataSource, - DatasetData, - ModelData, - ProjectDependency, - ResourceType, - TrainTask, - resourceConfig, -} from '@/pages/Dataset/config'; +import { DatasetData, ModelData, ResourceType, resourceConfig } from '@/pages/Dataset/config'; import ModelMetrics from '@/pages/Model/components/ModelMetrics'; -import { getGitUrl } from '@/utils'; +import { formatCodeConfig, formatDatasets, formatSource, formatTrainTask } from '@/utils/format'; import classNames from 'classnames'; import styles from './index.less'; @@ -24,103 +15,45 @@ type ResourceIntroProps = { version?: string; }; -export const formatDataset = (datasets?: DatasetData[]) => { - if (!datasets || datasets.length === 0) { - return undefined; - } - return datasets.map((item) => ({ - value: item.name, - url: `${origin}/dataset/dataset/info/${item.id}?tab=${ResourceInfoTabKeys.Version}&version=${item.version}&name=${item.name}&owner=${item.owner}&identifier=${item.identifier}`, - })); -}; - -export const getProjectUrl = (project?: ProjectDependency) => { - if (!project || !project.url || !project.branch) { - return undefined; - } - const { url, branch } = project; - return getGitUrl(url, branch); -}; - -export const formatProject = (project?: ProjectDependency) => { - if (!project) { - return undefined; - } - return { - value: project.name, - url: getProjectUrl(project), - }; -}; - -export const formatTrainTask = (task?: TrainTask) => { - if (!task) { - return undefined; - } - return { - value: task.name, - url: `${origin}/pipeline/experiment/instance/${task.workflow_id}/${task.ins_id}`, - }; -}; - -export const formatSource = (source?: string) => { - if (source === DataSource.Create) { - return '用户上传'; - } else if (source === DataSource.HandExport) { - return '手动导入'; - } else if (source === DataSource.AtuoExport) { - return '实验自动导入'; - } - return source; -}; - const getDatasetDatas = (data: DatasetData): BasicInfoData[] => [ { label: '数据集名称', value: data.name, - ellipsis: true, }, { label: '版本', value: data.version, - ellipsis: true, }, { label: '创建人', value: data.create_by, - ellipsis: true, }, { label: '更新时间', value: data.update_time, - ellipsis: true, }, { label: '数据来源', value: data.dataset_source, format: formatSource, - ellipsis: true, }, { label: '训练任务', value: data.train_task, format: formatTrainTask, - ellipsis: true, }, { label: '处理代码', value: data.processing_code, - format: formatProject, - ellipsis: true, + format: formatCodeConfig, }, { label: '数据集分类', value: data.data_type, - ellipsis: true, }, { label: '研究方向', value: data.data_tag, - ellipsis: true, }, ]; @@ -153,19 +86,19 @@ const getModelDatas = (data: ModelData): BasicInfoData[] => [ { label: '训练代码', value: data.project_depency, - format: formatProject, + format: formatCodeConfig, ellipsis: true, }, { label: '训练数据集', value: data.train_datasets, - format: formatDataset, + format: formatDatasets, ellipsis: true, }, { label: '测试数据集', value: data.test_datasets, - format: formatDataset, + format: formatDatasets, ellipsis: true, }, { diff --git a/react-ui/src/pages/Dataset/components/ResourceList/index.tsx b/react-ui/src/pages/Dataset/components/ResourceList/index.tsx index 6f2c7523..9577ad41 100644 --- a/react-ui/src/pages/Dataset/components/ResourceList/index.tsx +++ b/react-ui/src/pages/Dataset/components/ResourceList/index.tsx @@ -226,7 +226,7 @@ function ResourceList( title="暂无数据" content={'很抱歉,没有搜索到您想要的内容\n建议刷新试试'} hasFooter={true} - onRefresh={getDataList} + onButtonClick={getDataList} /> )} diff --git a/react-ui/src/pages/Dataset/components/VersionCompareModal/index.less b/react-ui/src/pages/Dataset/components/VersionCompareModal/index.less index 70de3494..f1935eb2 100644 --- a/react-ui/src/pages/Dataset/components/VersionCompareModal/index.less +++ b/react-ui/src/pages/Dataset/components/VersionCompareModal/index.less @@ -3,6 +3,7 @@ .title(@color, @background) { width: 100%; margin-bottom: 20px; + padding: 0 15px; color: @color; font-weight: 500; font-size: @font-size; diff --git a/react-ui/src/pages/Dataset/components/VersionCompareModal/index.tsx b/react-ui/src/pages/Dataset/components/VersionCompareModal/index.tsx index 2ee76e78..b3b6b2f1 100644 --- a/react-ui/src/pages/Dataset/components/VersionCompareModal/index.tsx +++ b/react-ui/src/pages/Dataset/components/VersionCompareModal/index.tsx @@ -8,11 +8,11 @@ import { resourceConfig, } from '@/pages/Dataset/config'; import { isEmpty } from '@/utils'; +import { formatSource } from '@/utils/format'; import { to } from '@/utils/promise'; import { Typography, type ModalProps } from 'antd'; import classNames from 'classnames'; import { useEffect, useMemo, useState } from 'react'; -import { formatSource } from '../ResourceIntro'; import styles from './index.less'; type CompareData = { @@ -47,10 +47,10 @@ const formatProject = (project?: ProjectDependency) => { if (!project) { return undefined; } - return project.name; + return `${project.name}:${project.branch}`; }; -export const formatTrainTask = (task?: TrainTask) => { +const formatTrainTask = (task?: TrainTask) => { if (!task) { return undefined; } @@ -203,7 +203,7 @@ function VersionCompareModal({ [styles['version-compare__left__text--different']]: isDifferent(key), })} > - + {isEmpty(text) ? '--' : text} @@ -221,7 +221,7 @@ function VersionCompareModal({ [styles['version-compare__right__text--different']]: isDifferent(key), })} > - + {isEmpty(text) ? '--' : text} diff --git a/react-ui/src/pages/Dataset/components/VersionSelectorModal/index.tsx b/react-ui/src/pages/Dataset/components/VersionSelectorModal/index.tsx index c58bf87e..b63d02fc 100644 --- a/react-ui/src/pages/Dataset/components/VersionSelectorModal/index.tsx +++ b/react-ui/src/pages/Dataset/components/VersionSelectorModal/index.tsx @@ -49,9 +49,7 @@ function VersionSelectorModal({ versions, onOk, ...rest }: VersionSelectorModalP onClick={() => handleClick(item.name)} > - - {item.name} - + {item.name} ); })} diff --git a/react-ui/src/pages/DevelopmentEnvironment/Create/index.tsx b/react-ui/src/pages/DevelopmentEnvironment/Create/index.tsx index 90ad4d15..1a0b9a18 100644 --- a/react-ui/src/pages/DevelopmentEnvironment/Create/index.tsx +++ b/react-ui/src/pages/DevelopmentEnvironment/Create/index.tsx @@ -36,13 +36,13 @@ enum ComputingResourceType { const EditorRadioItems: KFRadioItem[] = [ { - key: ComputingResourceType.GPU, title: '英伟达GPU', + value: ComputingResourceType.GPU, icon: , }, { - key: ComputingResourceType.NPU, title: '昇腾NPU', + value: ComputingResourceType.NPU, icon: , }, ]; diff --git a/react-ui/src/pages/Experiment/Comparison/index.less b/react-ui/src/pages/Experiment/Comparison/index.less index 3be69ed9..4dce8268 100644 --- a/react-ui/src/pages/Experiment/Comparison/index.less +++ b/react-ui/src/pages/Experiment/Comparison/index.less @@ -29,7 +29,7 @@ div { flex: 1; height: 1px; - background-color: @border-color-base; + background-color: @border-color; } p { @@ -45,7 +45,7 @@ .ant-table-thead { .ant-table-cell { background-color: rgb(247, 247, 247); - border-color: @border-color-base !important; + border-color: @border-color !important; } } .ant-table-tbody { diff --git a/react-ui/src/pages/Experiment/components/ExperimentInstance/index.tsx b/react-ui/src/pages/Experiment/components/ExperimentInstance/index.tsx index 4d68d93c..9b37dba8 100644 --- a/react-ui/src/pages/Experiment/components/ExperimentInstance/index.tsx +++ b/react-ui/src/pages/Experiment/components/ExperimentInstance/index.tsx @@ -20,7 +20,7 @@ import TensorBoardStatusCell from '../TensorBoardStatus'; import styles from './index.less'; type ExperimentInstanceProps = { - experimentInList?: ExperimentInstance[]; + experimentInsList?: ExperimentInstance[]; experimentInsTotal: number; onClickInstance?: (instance: ExperimentInstance) => void; onClickTensorBoard?: (instance: ExperimentInstance) => void; @@ -30,7 +30,7 @@ type ExperimentInstanceProps = { }; function ExperimentInstanceComponent({ - experimentInList, + experimentInsList, experimentInsTotal, onClickInstance, onClickTensorBoard, @@ -40,8 +40,8 @@ function ExperimentInstanceComponent({ }: ExperimentInstanceProps) { const { message } = App.useApp(); const allIntanceIds = useMemo(() => { - return experimentInList?.map((item) => item.id) || []; - }, [experimentInList]); + return experimentInsList?.map((item) => item.id) || []; + }, [experimentInsList]); const [ selectedIns, setSelectedIns, @@ -57,7 +57,7 @@ function ExperimentInstanceComponent({ if (allIntanceIds.length === 0) { setSelectedIns([]); } - }, [experimentInList]); + }, [experimentInsList]); // 删除实验实例确认 const handleRemove = (instance: ExperimentInstance) => { @@ -118,8 +118,8 @@ function ExperimentInstanceComponent({ } }; - if (!experimentInList || experimentInList.length === 0) { - return null; + if (!experimentInsList || experimentInsList.length === 0) { + return
暂无数据
; } return ( @@ -152,7 +152,7 @@ function ExperimentInstanceComponent({ - {experimentInList.map((item, index) => ( + {experimentInsList.map((item, index) => (
))} - {experimentInsTotal > experimentInList.length ? ( + {experimentInsTotal > experimentInsList.length ? (