From fa4b1466932ff41f69fb27ea5a1d0d5c6ab47cf7 Mon Sep 17 00:00:00 2001 From: dengqichen Date: Thu, 26 Dec 2024 18:06:31 +0800 Subject: [PATCH] 1 --- frontend/package-lock.json | 285 +++++++++++++++++- frontend/package.json | 4 + .../List/components/DeploymentConfigModal.tsx | 205 +++++++++++++ .../pages/Deploy/Deployment/List/index.tsx | 274 +++++++++++++++++ .../pages/Deploy/Deployment/List/service.ts | 34 +++ .../src/pages/Deploy/Deployment/List/types.ts | 51 ++++ frontend/src/router/index.tsx | 5 + frontend/src/types/common.ts | 19 ++ 8 files changed, 876 insertions(+), 1 deletion(-) create mode 100644 frontend/src/pages/Deploy/Deployment/List/components/DeploymentConfigModal.tsx create mode 100644 frontend/src/pages/Deploy/Deployment/List/index.tsx create mode 100644 frontend/src/pages/Deploy/Deployment/List/service.ts create mode 100644 frontend/src/pages/Deploy/Deployment/List/types.ts create mode 100644 frontend/src/types/common.ts diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 6a4a91e4..3cddb143 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -24,6 +24,10 @@ "@logicflow/core": "^2.0.9", "@logicflow/extension": "^2.0.13", "@reduxjs/toolkit": "^2.0.1", + "@rjsf/antd": "^5.23.2", + "@rjsf/core": "^5.23.2", + "@rjsf/utils": "^5.23.2", + "@rjsf/validator-ajv8": "^5.23.2", "@types/recharts": "^1.8.29", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", @@ -1948,6 +1952,163 @@ "node": ">=14.0.0" } }, + "node_modules/@rjsf/antd": { + "version": "5.23.2", + "resolved": "https://registry.npmmirror.com/@rjsf/antd/-/antd-5.23.2.tgz", + "integrity": "sha512-XZG0sIiJTjxdNXAIFyM88nP7MmCLUxugm3wyJ34jQlwGThZqmsgbHqyhrTum9ZcOvMAknDeNK3MSwI0CugswtQ==", + "license": "Apache-2.0", + "dependencies": { + "classnames": "^2.5.1", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "rc-picker": "2.7.6" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@ant-design/icons": "^4.0.0 || ^5.0.0", + "@rjsf/core": "^5.23.x", + "@rjsf/utils": "^5.23.x", + "antd": "^4.24.0 || ^5.8.5", + "dayjs": "^1.8.0", + "react": "^16.14.0 || >=17" + } + }, + "node_modules/@rjsf/antd/node_modules/rc-picker": { + "version": "2.7.6", + "resolved": "https://registry.npmmirror.com/rc-picker/-/rc-picker-2.7.6.tgz", + "integrity": "sha512-H9if/BUJUZBOhPfWcPeT15JUI3/ntrG9muzERrXDkSoWmDj4yzmBvumozpxYrHwjcKnjyDGAke68d+whWwvhHA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.1", + "date-fns": "2.x", + "dayjs": "1.x", + "moment": "^2.24.0", + "rc-trigger": "^5.0.4", + "rc-util": "^5.37.0", + "shallowequal": "^1.1.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rjsf/antd/node_modules/rc-picker/node_modules/rc-trigger": { + "version": "5.3.4", + "resolved": "https://registry.npmmirror.com/rc-trigger/-/rc-trigger-5.3.4.tgz", + "integrity": "sha512-mQv+vas0TwKcjAO2izNPkqR4j86OemLRmvL2nOzdP9OWNWA1ivoTt5hzFqYNW9zACwmTezRiN8bttrC7cZzYSw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "classnames": "^2.2.6", + "rc-align": "^4.0.0", + "rc-motion": "^2.0.0", + "rc-util": "^5.19.2" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rjsf/antd/node_modules/rc-picker/node_modules/rc-trigger/node_modules/rc-align": { + "version": "4.0.15", + "resolved": "https://registry.npmmirror.com/rc-align/-/rc-align-4.0.15.tgz", + "integrity": "sha512-wqJtVH60pka/nOX7/IspElA8gjPNQKIx/ZqJ6heATCkXpe1Zg4cPVrMD2vC96wjsFFL8WsmhPbx9tdMo1qqlIA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "dom-align": "^1.7.0", + "rc-util": "^5.26.0", + "resize-observer-polyfill": "^1.5.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rjsf/core": { + "version": "5.23.2", + "resolved": "https://registry.npmmirror.com/@rjsf/core/-/core-5.23.2.tgz", + "integrity": "sha512-jSz3X8SOZ1xL3wMPhBt01j/cA4FszhdGTLBhHfncM9nme4SpbQ6GRAiePOBakEVhT89GAM11ML1DA888YTZfmg==", + "license": "Apache-2.0", + "dependencies": { + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "markdown-to-jsx": "^7.4.1", + "nanoid": "^3.3.7", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@rjsf/utils": "^5.23.x", + "react": "^16.14.0 || >=17" + } + }, + "node_modules/@rjsf/utils": { + "version": "5.23.2", + "resolved": "https://registry.npmmirror.com/@rjsf/utils/-/utils-5.23.2.tgz", + "integrity": "sha512-CDpZTroRE1O2lkZBNYsWCRj7moF3fqnwIo/Vf3flhoS7rGwb8kWlIHakOOBZxlqorKZB1UPzhQZn+FcwhzNldw==", + "license": "Apache-2.0", + "dependencies": { + "json-schema-merge-allof": "^0.8.1", + "jsonpointer": "^5.0.1", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "react-is": "^18.2.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": "^16.14.0 || >=17" + } + }, + "node_modules/@rjsf/validator-ajv8": { + "version": "5.23.2", + "resolved": "https://registry.npmmirror.com/@rjsf/validator-ajv8/-/validator-ajv8-5.23.2.tgz", + "integrity": "sha512-EHOTZ/YxTcFHpCj2p42PD3daxJyozZ5gkIVCfbtIXgTtubt/pOMFvM2OxNcQ6OBQ+ulo96OmnDxYGsNPpZZVaw==", + "license": "Apache-2.0", + "dependencies": { + "ajv": "^8.12.0", + "ajv-formats": "^2.1.1", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@rjsf/utils": "^5.23.x" + } + }, + "node_modules/@rjsf/validator-ajv8/node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.27.4", "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.27.4.tgz", @@ -3224,6 +3385,27 @@ "resolved": "https://registry.npmmirror.com/component-indexof/-/component-indexof-0.0.3.tgz", "integrity": "sha512-puDQKvx/64HZXb4hBwIcvQLaLgux8o1CbWl39s41hrIIZDl1lJiD5jc22gj3RBeGK0ovxALDYpIbyjqDUUl0rw==" }, + "node_modules/compute-gcd": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/compute-gcd/-/compute-gcd-1.2.1.tgz", + "integrity": "sha512-TwMbxBNz0l71+8Sc4czv13h4kEqnchV9igQZBi6QUaz09dnz13juGnnaWWJTRsP3brxOoxeB4SA2WELLw1hCtg==", + "dependencies": { + "validate.io-array": "^1.0.3", + "validate.io-function": "^1.0.2", + "validate.io-integer-array": "^1.0.0" + } + }, + "node_modules/compute-lcm": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/compute-lcm/-/compute-lcm-1.1.2.tgz", + "integrity": "sha512-OFNPdQAXnQhDSKioX8/XYT6sdUlXwpeMjfd6ApxMJfyZ4GxmLR1xvMERctlYhlHwIiz6CSpBc2+qYKjHGZw4TQ==", + "dependencies": { + "compute-gcd": "^1.2.1", + "validate.io-array": "^1.0.3", + "validate.io-function": "^1.0.2", + "validate.io-integer-array": "^1.0.0" + } + }, "node_modules/compute-scroll-into-view": { "version": "3.1.0", "resolved": "https://registry.npmmirror.com/compute-scroll-into-view/-/compute-scroll-into-view-3.1.0.tgz", @@ -3495,6 +3677,22 @@ "lodash": "^4.17.15" } }, + "node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmmirror.com/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, "node_modules/dayjs": { "version": "1.11.13", "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.13.tgz", @@ -4608,6 +4806,29 @@ "resolved": "https://registry.npmmirror.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" }, + "node_modules/json-schema-compare": { + "version": "0.2.2", + "resolved": "https://registry.npmmirror.com/json-schema-compare/-/json-schema-compare-0.2.2.tgz", + "integrity": "sha512-c4WYmDKyJXhs7WWvAWm3uIYnfyWFoIp+JEoX34rctVvEkMYCPGhXtvmFFXiffBbxfZsvQ0RNnV5H7GvDF5HCqQ==", + "license": "MIT", + "dependencies": { + "lodash": "^4.17.4" + } + }, + "node_modules/json-schema-merge-allof": { + "version": "0.8.1", + "resolved": "https://registry.npmmirror.com/json-schema-merge-allof/-/json-schema-merge-allof-0.8.1.tgz", + "integrity": "sha512-CTUKmIlPJbsWfzRRnOXz+0MjIqvnleIXwFTzz+t9T86HnYX/Rozria6ZVGLktAU9e+NygNljveP+yxqtQp/Q4w==", + "license": "MIT", + "dependencies": { + "compute-lcm": "^1.1.2", + "json-schema-compare": "^0.2.2", + "lodash": "^4.17.20" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -4638,6 +4859,15 @@ "node": ">=6" } }, + "node_modules/jsonpointer": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/jsonpointer/-/jsonpointer-5.0.1.tgz", + "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmmirror.com/keyv/-/keyv-4.5.4.tgz", @@ -4790,6 +5020,18 @@ "semver": "bin/semver" } }, + "node_modules/markdown-to-jsx": { + "version": "7.7.2", + "resolved": "https://registry.npmmirror.com/markdown-to-jsx/-/markdown-to-jsx-7.7.2.tgz", + "integrity": "sha512-N3AKfYRvxNscvcIH6HDnDKILp4S8UWbebp+s92Y8SwIq0CuSbLW4Jgmrbjku3CWKjTQO0OyIMS6AhzqrwjEa3g==", + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "peerDependencies": { + "react": ">= 0.14.0" + } + }, "node_modules/medium-editor": { "version": "5.23.3", "resolved": "https://registry.npmmirror.com/medium-editor/-/medium-editor-5.23.3.tgz", @@ -4940,6 +5182,15 @@ "mobx": "^4.13.1 || ^5.13.1" } }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmmirror.com/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/mousetrap": { "version": "1.6.5", "resolved": "https://registry.npmmirror.com/mousetrap/-/mousetrap-1.6.5.tgz", @@ -4954,7 +5205,6 @@ "version": "3.3.7", "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.7.tgz", "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", - "dev": true, "funding": [ { "type": "github", @@ -6817,6 +7067,39 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/validate.io-array": { + "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/validate.io-array/-/validate.io-array-1.0.6.tgz", + "integrity": "sha512-DeOy7CnPEziggrOO5CZhVKJw6S3Yi7e9e65R1Nl/RTN1vTQKnzjfvks0/8kQ40FP/dsjRAOd4hxmJ7uLa6vxkg==", + "license": "MIT" + }, + "node_modules/validate.io-function": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/validate.io-function/-/validate.io-function-1.0.2.tgz", + "integrity": "sha512-LlFybRJEriSuBnUhQyG5bwglhh50EpTL2ul23MPIuR1odjO7XaMLFV8vHGwp7AZciFxtYOeiSCT5st+XSPONiQ==" + }, + "node_modules/validate.io-integer": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/validate.io-integer/-/validate.io-integer-1.0.5.tgz", + "integrity": "sha512-22izsYSLojN/P6bppBqhgUDjCkr5RY2jd+N2a3DCAUey8ydvrZ/OkGvFPR7qfOpwR2LC5p4Ngzxz36g5Vgr/hQ==", + "dependencies": { + "validate.io-number": "^1.0.3" + } + }, + "node_modules/validate.io-integer-array": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/validate.io-integer-array/-/validate.io-integer-array-1.0.0.tgz", + "integrity": "sha512-mTrMk/1ytQHtCY0oNO3dztafHYyGU88KL+jRxWuzfOmQb+4qqnWmI+gykvGp8usKZOM0H7keJHEbRaFiYA0VrA==", + "dependencies": { + "validate.io-array": "^1.0.3", + "validate.io-integer": "^1.0.4" + } + }, + "node_modules/validate.io-number": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/validate.io-number/-/validate.io-number-1.0.3.tgz", + "integrity": "sha512-kRAyotcbNaSYoDnXvb4MHg/0a1egJdLwS6oJ38TJY7aw9n93Fl/3blIXdyYvPOp55CNxywooG/3BcrwNrBpcSg==" + }, "node_modules/vanilla-picker": { "version": "2.12.3", "resolved": "https://registry.npmmirror.com/vanilla-picker/-/vanilla-picker-2.12.3.tgz", diff --git a/frontend/package.json b/frontend/package.json index f7f1952a..0265b9c3 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -26,6 +26,10 @@ "@logicflow/core": "^2.0.9", "@logicflow/extension": "^2.0.13", "@reduxjs/toolkit": "^2.0.1", + "@rjsf/antd": "^5.23.2", + "@rjsf/core": "^5.23.2", + "@rjsf/utils": "^5.23.2", + "@rjsf/validator-ajv8": "^5.23.2", "@types/recharts": "^1.8.29", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", diff --git a/frontend/src/pages/Deploy/Deployment/List/components/DeploymentConfigModal.tsx b/frontend/src/pages/Deploy/Deployment/List/components/DeploymentConfigModal.tsx new file mode 100644 index 00000000..249d25da --- /dev/null +++ b/frontend/src/pages/Deploy/Deployment/List/components/DeploymentConfigModal.tsx @@ -0,0 +1,205 @@ +import React, {useEffect, useState} from 'react'; +import {Modal, Form, Select, Switch, InputNumber, message} from 'antd'; +import type {DeploymentConfig, DeployConfigTemplate} from '../types'; +import {createDeploymentConfig, updateDeploymentConfig, getDeployConfigTemplates} from '../service'; +import {getApplicationList} from '../../../Application/List/service'; +import type {Application} from '../../../Application/List/types'; +import {withTheme} from '@rjsf/core'; +import {Theme as AntdTheme} from '@rjsf/antd'; +import validator from '@rjsf/validator-ajv8'; +import type {IChangeEvent} from '@rjsf/utils'; + +const JsonForm = withTheme(AntdTheme); + +interface DeploymentConfigModalProps { + visible: boolean; + onCancel: () => void; + onSuccess: () => void; + initialValues?: DeploymentConfig; + envId: number; +} + +const {Option} = Select; + +const DeploymentConfigModal: React.FC = ({ + visible, + onCancel, + onSuccess, + initialValues, + envId, +}) => { + const [form] = Form.useForm(); + const [applications, setApplications] = useState([]); + const [templates, setTemplates] = useState([]); + const [selectedTemplate, setSelectedTemplate] = useState(); + const [buildVariables, setBuildVariables] = useState>({}); + const isEdit = !!initialValues?.id; + + // 获取应用列表 + const fetchApplications = async () => { + try { + const data = await getApplicationList(); + setApplications(data); + } catch (error) { + message.error('获取应用列表失败'); + } + }; + + // 获取配置模板 + const fetchTemplates = async () => { + try { + const data = await getDeployConfigTemplates(); + setTemplates(data); + } catch (error) { + message.error('获取配置模板失败'); + } + }; + + // 仅在模态框显示时获取数据 + useEffect(() => { + if (visible) { + fetchApplications(); + fetchTemplates(); + } + }, [visible]); + + // 初始化表单数据 + useEffect(() => { + if (!visible) return; + + if (initialValues) { + form.setFieldsValue(initialValues); + const template = templates.find(t => t.code === initialValues.templateCode); + if (template) { + setSelectedTemplate(template); + setBuildVariables(initialValues.buildVariables || {}); + } + } else { + form.resetFields(); + form.setFieldsValue({ + envId, + enabled: true, + sort: 0, + }); + setSelectedTemplate(undefined); + setBuildVariables({}); + } + }, [visible, initialValues, envId, form]); + + const handleAppChange = (appId: number) => { + const app = applications.find(a => a.id === appId); + if (app) { + const template = templates.find(t => t.languageType === app.language); + setSelectedTemplate(template); + setBuildVariables({}); + } + }; + + const handleSubmit = async () => { + try { + const values = await form.validateFields(); + const submitData = { + ...values, + templateCode: selectedTemplate?.code, + buildVariables, + }; + + if (isEdit) { + await updateDeploymentConfig({ + ...submitData, + id: initialValues.id, + }); + } else { + await createDeploymentConfig(submitData); + } + message.success(`${isEdit ? '更新' : '创建'}成功`); + form.resetFields(); + onSuccess(); + } catch (error) { + message.error(`${isEdit ? '更新' : '创建'}失败`); + } + }; + + const handleJsonFormChange = (e: IChangeEvent) => { + if (e.formData) { + setBuildVariables(e.formData); + } + }; + + return ( + { + form.resetFields(); + onCancel(); + }} + onOk={handleSubmit} + width={800} + > +
+ + + + + {selectedTemplate && ( + + + + )} + + + + + + + + +
+
+ ); +}; + +export default DeploymentConfigModal; \ No newline at end of file diff --git a/frontend/src/pages/Deploy/Deployment/List/index.tsx b/frontend/src/pages/Deploy/Deployment/List/index.tsx new file mode 100644 index 00000000..b1f51e59 --- /dev/null +++ b/frontend/src/pages/Deploy/Deployment/List/index.tsx @@ -0,0 +1,274 @@ +import React, {useState, useEffect} from 'react'; +import {PageContainer} from '@ant-design/pro-layout'; +import {Button, message, Popconfirm, Select} from 'antd'; +import {PlusOutlined, EditOutlined, DeleteOutlined} from '@ant-design/icons'; +import {getDeploymentConfigPage, deleteDeploymentConfig} from './service'; +import {getEnvironmentList} from '../../Environment/List/service'; +import type {DeploymentConfig, DeploymentConfigQueryParams} from './types'; +import type {Environment} from '../../Environment/List/types'; +import DeploymentConfigModal from './components/DeploymentConfigModal'; +import {ProTable} from '@ant-design/pro-components'; +import type {ProColumns, ActionType} from '@ant-design/pro-components'; + +const {Option} = Select; + +const DeploymentConfigList: React.FC = () => { + const [environments, setEnvironments] = useState([]); + const [selectedEnvId, setSelectedEnvId] = useState(); + const [modalVisible, setModalVisible] = useState(false); + const [currentConfig, setCurrentConfig] = useState(); + const actionRef = React.useRef(); + + // 获取环境列表 + const fetchEnvironments = async () => { + try { + const data = await getEnvironmentList(); + setEnvironments(data); + if (data.length > 0 && !selectedEnvId) { + setSelectedEnvId(data[0].id); + } + } catch (error) { + message.error('获取环境列表失败'); + } + }; + + useEffect(() => { + fetchEnvironments(); + }, []); + + const handleDelete = async (id: number) => { + try { + await deleteDeploymentConfig(id); + message.success('删除成功'); + actionRef.current?.reload(); + } catch (error) { + message.error('删除失败'); + } + }; + + const handleAdd = () => { + if (!selectedEnvId) { + message.warning('请先选择环境'); + return; + } + setCurrentConfig(undefined); + setModalVisible(true); + }; + + const handleEdit = (config: DeploymentConfig) => { + setCurrentConfig(config); + setModalVisible(true); + }; + + const handleEnvChange = (value: number) => { + setSelectedEnvId(value); + actionRef.current?.reload(); + }; + + const renderResourceInfo = (resources?: { cpu?: string; memory?: string }) => { + if (!resources?.cpu && !resources?.memory) return '-'; + const items = []; + if (resources.cpu) items.push(`CPU: ${resources.cpu}`); + if (resources.memory) items.push(`内存: ${resources.memory}`); + return items.join(' / '); + }; + + const columns: ProColumns[] = [ + { + title: '应用名称', + dataIndex: ['application', 'appName'], + width: 150, + ellipsis: true, + fixed: 'left', + }, + { + title: '应用编码', + dataIndex: ['application', 'appCode'], + width: 150, + copyable: true, + ellipsis: true, + }, + { + title: '构建配置', + children: [ + { + title: '构建脚本', + dataIndex: ['buildConfig', 'buildScript'], + width: 200, + ellipsis: true, + }, + { + title: '构建产物路径', + dataIndex: ['buildConfig', 'buildPath'], + width: 150, + ellipsis: true, + }, + ], + }, + { + title: '部署配置', + children: [ + { + title: '副本数', + dataIndex: ['deployConfig', 'replicas'], + width: 100, + align: 'center', + }, + { + title: '容器端口', + dataIndex: ['deployConfig', 'containerPort'], + width: 100, + align: 'center', + }, + { + title: '资源配置', + dataIndex: ['deployConfig', 'resources'], + width: 200, + ellipsis: true, + render: (_, record) => renderResourceInfo(record.deployConfig?.resources), + }, + ], + }, + { + title: '状态', + dataIndex: 'enabled', + width: 100, + align: 'center', + valueEnum: { + true: {text: '启用', status: 'Success'}, + false: {text: '禁用', status: 'Default'}, + }, + }, + { + title: '排序', + dataIndex: 'sort', + width: 80, + align: 'center', + sorter: true, + }, + { + title: '操作', + width: 180, + key: 'action', + valueType: 'option', + fixed: 'right', + align: 'center', + render: (_, record) => { + const buttons = [ + , + handleDelete(record.id)} + > + + + ]; + return
{buttons}
; + }, + }, + ]; + + return ( + + {environments.map((env) => ( + + ))} + , + ], + }} + > + + columns={columns} + actionRef={actionRef} + scroll={{x: 'max-content'}} + cardBordered + request={async (params) => { + if (!selectedEnvId) { + return { + data: [], + success: true, + total: 0, + }; + } + const queryParams: DeploymentConfigQueryParams = { + pageSize: params.pageSize, + pageNum: params.current, + envId: selectedEnvId, + enabled: params.enabled as boolean, + }; + const data = await getDeploymentConfigPage(queryParams); + return { + data: data.content || [], + success: true, + total: data.totalElements || 0, + }; + }} + rowKey="id" + search={false} + options={{ + setting: { + listsHeight: 400, + }, + }} + pagination={{ + pageSize: 10, + showQuickJumper: true, + }} + dateFormatter="string" + toolBarRender={() => [ + , + ]} + /> + + {selectedEnvId && ( + setModalVisible(false)} + onSuccess={() => { + setModalVisible(false); + actionRef.current?.reload(); + }} + initialValues={currentConfig} + envId={selectedEnvId} + /> + )} + + ); +}; + +export default DeploymentConfigList; \ No newline at end of file diff --git a/frontend/src/pages/Deploy/Deployment/List/service.ts b/frontend/src/pages/Deploy/Deployment/List/service.ts new file mode 100644 index 00000000..64e130ae --- /dev/null +++ b/frontend/src/pages/Deploy/Deployment/List/service.ts @@ -0,0 +1,34 @@ +import request from '@/utils/request'; +import type {DeploymentConfig, CreateDeploymentConfigRequest, UpdateDeploymentConfigRequest, DeploymentConfigQueryParams, DeployConfigTemplate} from './types'; +import type {Page} from '@/types/base'; + +const BASE_URL = '/api/v1/deployment-configs'; +const TEMPLATE_URL = '/api/v1/deploy-app-config'; + +// 获取部署配置分页列表 +export const getDeploymentConfigPage = (params: DeploymentConfigQueryParams) => + request.get>(`${BASE_URL}/page`, {params}); + +// 创建部署配置 +export const createDeploymentConfig = (data: CreateDeploymentConfigRequest) => + request.post(BASE_URL, data); + +// 更新部署配置 +export const updateDeploymentConfig = (data: UpdateDeploymentConfigRequest) => + request.put(`${BASE_URL}/${data.id}`, data); + +// 删除部署配置 +export const deleteDeploymentConfig = (id: number) => + request.delete(`${BASE_URL}/${id}`); + +// 获取部署配置详情 +export const getDeploymentConfig = (id: number) => + request.get(`${BASE_URL}/${id}`); + +// 获取环境下的所有部署配置 +export const getDeploymentConfigsByEnv = (envId: number) => + request.get(`${BASE_URL}/env/${envId}`); + +// 获取部署配置模板列表 +export const getDeployConfigTemplates = () => + request.get(`${TEMPLATE_URL}/defined`); \ No newline at end of file diff --git a/frontend/src/pages/Deploy/Deployment/List/types.ts b/frontend/src/pages/Deploy/Deployment/List/types.ts new file mode 100644 index 00000000..a1602d8a --- /dev/null +++ b/frontend/src/pages/Deploy/Deployment/List/types.ts @@ -0,0 +1,51 @@ +import {BaseResponse, BaseRequest, BaseQuery} from '@/types/base'; +import {Environment} from '../../Environment/List/types'; +import {Application} from '../../Application/List/types'; +import {DevelopmentLanguageTypeEnum} from '../../Application/List/types'; +import {BuildTypeEnum} from '../../Environment/List/types'; +import type {JsonNode} from '@/types/common'; + +// 部署配置模板 +export interface DeployConfigTemplate { + code: string; + name: string; + buildType: BuildTypeEnum; + languageType: DevelopmentLanguageTypeEnum; + buildVariablesSchema: JsonNode; +} + +// 部署配置基础信息 +export interface DeploymentConfig extends BaseResponse { + tenantCode: string; + envId: number; + environment: Environment; + appId: number; + application: Application; + templateCode: string; + buildVariables: Record; + enabled: boolean; + sort: number; +} + +// 创建部署配置请求参数 +export interface CreateDeploymentConfigRequest extends BaseRequest { + tenantCode: string; + envId: number; + appId: number; + templateCode: string; + buildVariables: Record; + enabled: boolean; + sort: number; +} + +// 更新部署配置请求参数 +export interface UpdateDeploymentConfigRequest extends CreateDeploymentConfigRequest { + id: number; +} + +// 分页查询参数 +export interface DeploymentConfigQueryParams extends BaseQuery { + envId?: number; + appId?: number; + enabled?: boolean; +} \ No newline at end of file diff --git a/frontend/src/router/index.tsx b/frontend/src/router/index.tsx index be75abb6..969039cb 100644 --- a/frontend/src/router/index.tsx +++ b/frontend/src/router/index.tsx @@ -41,6 +41,7 @@ const NodeDesignForm = lazy(() => import('../pages/Workflow/NodeDesign/Design')) const ProjectGroupList = lazy(() => import('../pages/Deploy/ProjectGroup/List')); const ApplicationList = lazy(() => import('../pages/Deploy/Application/List')); const EnvironmentList = lazy(() => import('../pages/Deploy/Environment/List')); +const DeploymentConfigList = lazy(() => import('../pages/Deploy/Deployment/List')); const External = lazy(() => import('../pages/Deploy/External')); // 创建路由 @@ -84,6 +85,10 @@ const router = createBrowserRouter([ path: 'environments', element: }> }, + { + path: 'deployment', + element: }> + }, { path: 'external', element: ( diff --git a/frontend/src/types/common.ts b/frontend/src/types/common.ts new file mode 100644 index 00000000..aef2b172 --- /dev/null +++ b/frontend/src/types/common.ts @@ -0,0 +1,19 @@ +// JSON Schema 相关类型 +export type JsonNode = { + type?: string; + properties?: Record; + items?: JsonNode; + required?: string[]; + title?: string; + description?: string; + enum?: any[]; + enumNames?: string[]; + default?: any; + minimum?: number; + maximum?: number; + minLength?: number; + maxLength?: number; + pattern?: string; + format?: string; + [key: string]: any; +}; \ No newline at end of file