1
This commit is contained in:
parent
e28e3be451
commit
8a9e221b22
10532
frontend/package-lock.json
generated
10532
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -12,9 +12,6 @@
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^5.2.6",
|
||||
"@ant-design/pro-components": "^2.8.2",
|
||||
"@formily/antd-v5": "^1.2.3",
|
||||
"@formily/core": "^2.3.2",
|
||||
"@formily/react": "^2.3.2",
|
||||
"@hookform/resolvers": "^3.9.1",
|
||||
"@monaco-editor/react": "^4.6.0",
|
||||
"@radix-ui/react-accordion": "^1.2.12",
|
||||
@ -45,7 +42,6 @@
|
||||
"cmdk": "^1.0.4",
|
||||
"dagre": "^0.8.5",
|
||||
"dayjs": "^1.11.13",
|
||||
"form-render": "^2.5.1",
|
||||
"less": "^4.2.1",
|
||||
"monaco-editor": "^0.52.2",
|
||||
"react": "^18.2.0",
|
||||
|
||||
@ -1,420 +0,0 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {Modal, Button, message} from 'antd';
|
||||
import { FullscreenOutlined, FullscreenExitOutlined } from '@ant-design/icons';
|
||||
import type {DeploymentConfig} from '../types';
|
||||
import './styles.less';
|
||||
import {FormButtonGroup, FormItem, Select, Submit, FormGrid, Input, ArrayTable} from '@formily/antd-v5'
|
||||
import {createForm, Field, FieldDataSource, onFormInit} from '@formily/core'
|
||||
import {createSchemaField, FormProvider, ISchema} from '@formily/react'
|
||||
import {action} from '@formily/reactive'
|
||||
import request from '@/utils/request';
|
||||
import Editor from '@/components/Editor';
|
||||
|
||||
// 定义 ScriptEditor 组件
|
||||
const ScriptEditor: React.FC<any> = (props) => {
|
||||
const [isFullscreen, setIsFullscreen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const handleEscKey = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Escape' && isFullscreen) {
|
||||
setIsFullscreen(false);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('keydown', handleEscKey);
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handleEscKey);
|
||||
};
|
||||
}, [isFullscreen]);
|
||||
|
||||
const toggleFullscreen = () => {
|
||||
setIsFullscreen(!isFullscreen);
|
||||
};
|
||||
|
||||
const editorStyle = isFullscreen ? {
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
zIndex: 1000,
|
||||
backgroundColor: '#1e1e1e'
|
||||
} : {};
|
||||
|
||||
return (
|
||||
<div style={{ position: 'relative', height: isFullscreen ? '100vh' : '240px', ...editorStyle }}>
|
||||
<Editor {...props} height={isFullscreen ? '100vh' : '240px'} />
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
top: '10px',
|
||||
right: '10px',
|
||||
zIndex: 1001,
|
||||
display: 'flex',
|
||||
gap: '8px'
|
||||
}}>
|
||||
<Button
|
||||
type="text"
|
||||
icon={isFullscreen ? <FullscreenExitOutlined /> : <FullscreenOutlined />}
|
||||
onClick={toggleFullscreen}
|
||||
style={{ color: '#fff' }}
|
||||
>
|
||||
{isFullscreen ? '退出全屏' : '全屏'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
interface DeploymentConfigModalProps {
|
||||
open: boolean;
|
||||
onCancel: () => void;
|
||||
onSuccess: () => void;
|
||||
initialValues?: DeploymentConfig;
|
||||
envId: number;
|
||||
}
|
||||
|
||||
// 定义字段映射接口
|
||||
interface FieldMapping {
|
||||
label?: string;
|
||||
value?: string;
|
||||
}
|
||||
|
||||
const DeploymentConfigModal: React.FC<DeploymentConfigModalProps> = ({
|
||||
open,
|
||||
onCancel,
|
||||
onSuccess,
|
||||
initialValues,
|
||||
envId,
|
||||
}) => {
|
||||
|
||||
// 通用的异步数据源加载方法
|
||||
const useAsyncDataSource = (url: string | null, mapping: FieldMapping = {}) => (field: Field) => {
|
||||
if (!url) {
|
||||
field.dataSource = [];
|
||||
return;
|
||||
}
|
||||
|
||||
const { label = 'name', value = 'id' } = mapping;
|
||||
field.loading = true;
|
||||
request.get(url)
|
||||
.then(action.bound?.((response) => {
|
||||
field.dataSource = response.map((item: any) => ({
|
||||
label: item[label],
|
||||
value: item[value]
|
||||
}));
|
||||
field.loading = false;
|
||||
}))
|
||||
.catch(action.bound?.((error) => {
|
||||
console.error(`Failed to load data from ${url}:`, error);
|
||||
field.dataSource = [];
|
||||
field.loading = false;
|
||||
}));
|
||||
};
|
||||
|
||||
const SchemaField = createSchemaField({
|
||||
components: {
|
||||
Select,
|
||||
FormItem,
|
||||
FormGrid,
|
||||
Input,
|
||||
ArrayTable,
|
||||
ScriptEditor
|
||||
},
|
||||
scope: {
|
||||
useAsyncDataSource
|
||||
}
|
||||
})
|
||||
|
||||
// 创建表单实例
|
||||
const form = createForm()
|
||||
|
||||
const schema: ISchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
jenkinsConfig: {
|
||||
type: 'void',
|
||||
'x-component': 'FormGrid',
|
||||
'x-component-props': {
|
||||
maxColumns: 3,
|
||||
minColumns: 3,
|
||||
columnGap: 24
|
||||
},
|
||||
properties: {
|
||||
externalSystemId: {
|
||||
type: 'string',
|
||||
title: 'Jenkins系统',
|
||||
required: true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-decorator-props': {
|
||||
labelCol: 24,
|
||||
wrapperCol: 24,
|
||||
layout: 'vertical',
|
||||
colon: false,
|
||||
labelAlign: 'left'
|
||||
},
|
||||
'x-component': 'Select',
|
||||
'x-component-props': {
|
||||
style: {
|
||||
width: '100%'
|
||||
},
|
||||
placeholder: '请选择三方系统',
|
||||
allowClear: true
|
||||
},
|
||||
'x-reactions': ["{{useAsyncDataSource('/api/v1/external-system/list?type=JENKINS', { label: 'name' })}}"],
|
||||
},
|
||||
viewId: {
|
||||
type: 'string',
|
||||
title: 'Jenkins视图',
|
||||
required: true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-decorator-props': {
|
||||
labelCol: 24,
|
||||
wrapperCol: 24,
|
||||
layout: 'vertical',
|
||||
colon: false,
|
||||
labelAlign: 'left'
|
||||
},
|
||||
'x-component': 'Select',
|
||||
'x-component-props': {
|
||||
style: {
|
||||
width: '100%'
|
||||
},
|
||||
placeholder: '请选择Jenkins视图',
|
||||
disabled: '{{!$form.values.externalSystemId}}',
|
||||
allowClear: true
|
||||
},
|
||||
'x-reactions': {
|
||||
dependencies: ['externalSystemId'],
|
||||
fulfill: {
|
||||
state: {
|
||||
value: undefined
|
||||
},
|
||||
run: '{{useAsyncDataSource($deps[0] ? `/api/v1/jenkins-view/list?externalSystemId=${$deps[0]}` : null, { label: "viewName" })($self)}}'
|
||||
}
|
||||
}
|
||||
},
|
||||
jobId: {
|
||||
type: 'string',
|
||||
title: 'Jenkins作业',
|
||||
required: true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-decorator-props': {
|
||||
labelCol: 24,
|
||||
wrapperCol: 24,
|
||||
layout: 'vertical',
|
||||
colon: false,
|
||||
labelAlign: 'left'
|
||||
},
|
||||
'x-component': 'Select',
|
||||
'x-component-props': {
|
||||
style: {
|
||||
width: '100%'
|
||||
},
|
||||
placeholder: '请选择Jenkins作业',
|
||||
disabled: '{{!$form.values.viewId}}',
|
||||
allowClear: true
|
||||
},
|
||||
'x-reactions': {
|
||||
dependencies: ['externalSystemId', 'viewId'],
|
||||
fulfill: {
|
||||
state: {
|
||||
value: undefined
|
||||
},
|
||||
run: '{{useAsyncDataSource(($deps[0] && $deps[1]) ? `/api/v1/jenkins-job/list?externalSystemId=${$deps[0]}&viewId=${$deps[1]}` : null, { label: "jobName" })($self)}}'
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
envConfig: {
|
||||
type: 'void',
|
||||
'x-component': 'FormGrid',
|
||||
'x-component-props': {
|
||||
maxColumns: 1,
|
||||
minColumns: 1
|
||||
},
|
||||
properties: {
|
||||
envs: {
|
||||
type: 'array',
|
||||
title: '环境变量',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-decorator-props': {
|
||||
labelCol: 24,
|
||||
wrapperCol: 24,
|
||||
layout: 'vertical',
|
||||
colon: false,
|
||||
labelAlign: 'left'
|
||||
},
|
||||
'x-component': 'ArrayTable',
|
||||
'x-component-props': {
|
||||
pagination: { pageSize: 10 },
|
||||
scroll: { x: '100%' },
|
||||
style: {
|
||||
minHeight: '160px',
|
||||
maxHeight: '240px',
|
||||
overflow: 'auto'
|
||||
}
|
||||
},
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
key: {
|
||||
type: 'void',
|
||||
'x-component': 'ArrayTable.Column',
|
||||
'x-component-props': { title: '键', width: '40%' },
|
||||
properties: {
|
||||
key: {
|
||||
type: 'string',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
'x-validator': [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入环境变量名'
|
||||
},
|
||||
{
|
||||
pattern: '^[a-zA-Z][a-zA-Z0-9_]*$',
|
||||
message: '只能包含字母、数字和下划线,且必须以字母开头'
|
||||
}
|
||||
],
|
||||
'x-component-props': {
|
||||
placeholder: '请输入环境变量名',
|
||||
allowClear: true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
value: {
|
||||
type: 'void',
|
||||
'x-component': 'ArrayTable.Column',
|
||||
'x-component-props': { title: '值', width: '40%' },
|
||||
properties: {
|
||||
value: {
|
||||
type: 'string',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
'x-validator': {
|
||||
required: true,
|
||||
message: '请输入环境变量值'
|
||||
},
|
||||
'x-component-props': {
|
||||
placeholder: '请输入环境变量值',
|
||||
allowClear: true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
column3: {
|
||||
type: 'void',
|
||||
'x-component': 'ArrayTable.Column',
|
||||
'x-component-props': {
|
||||
title: '操作',
|
||||
dataIndex: 'operations',
|
||||
width: '20%',
|
||||
},
|
||||
properties: {
|
||||
remove: {
|
||||
type: 'void',
|
||||
'x-component': 'ArrayTable.Remove'
|
||||
},
|
||||
moveUp: {
|
||||
type: 'void',
|
||||
'x-component': 'ArrayTable.MoveUp'
|
||||
},
|
||||
moveDown: {
|
||||
type: 'void',
|
||||
'x-component': 'ArrayTable.MoveDown'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
properties: {
|
||||
add: {
|
||||
type: 'void',
|
||||
title: '添加环境变量',
|
||||
'x-component': 'ArrayTable.Addition'
|
||||
}
|
||||
}
|
||||
},
|
||||
script: {
|
||||
type: 'string',
|
||||
title: '脚本内容',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-decorator-props': {
|
||||
labelCol: 24,
|
||||
wrapperCol: 24,
|
||||
layout: 'vertical',
|
||||
colon: false,
|
||||
labelAlign: 'left',
|
||||
style: {
|
||||
marginBottom: '16px'
|
||||
}
|
||||
},
|
||||
'x-component': 'ScriptEditor',
|
||||
'x-component-props': {
|
||||
language: 'shell',
|
||||
theme: 'vs-dark',
|
||||
options: {
|
||||
minimap: {
|
||||
enabled: true,
|
||||
scale: 2,
|
||||
showSlider: "mouseover",
|
||||
renderCharacters: false
|
||||
},
|
||||
scrollBeyondLastLine: false,
|
||||
fontSize: 14,
|
||||
lineNumbers: 'on',
|
||||
automaticLayout: true,
|
||||
tabSize: 2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
const values = await form.submit()
|
||||
console.log('表单提交的值:', values)
|
||||
console.log('Schema:', schema)
|
||||
console.log('FORMILY:', JSON.stringify(schema, null, 2))
|
||||
onSuccess?.()
|
||||
form.reset()
|
||||
} catch (e) {
|
||||
console.error('表单提交出错:', e)
|
||||
message.error('提交失败,请检查表单数据是否正确')
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title="部署配置"
|
||||
open={open}
|
||||
onCancel={onCancel}
|
||||
width={800}
|
||||
centered
|
||||
footer={[
|
||||
<Button key="cancel" onClick={onCancel}>
|
||||
取消
|
||||
</Button>,
|
||||
<Button key="submit" type="primary" onClick={handleSubmit}>
|
||||
确定
|
||||
</Button>
|
||||
]}
|
||||
bodyStyle={{
|
||||
padding: '24px',
|
||||
maxHeight: '80vh',
|
||||
overflow: 'auto'
|
||||
}}
|
||||
>
|
||||
<FormProvider form={form}>
|
||||
<SchemaField schema={schema}/>
|
||||
</FormProvider>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeploymentConfigModal;
|
||||
@ -1,137 +0,0 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogDescription
|
||||
} from "@/components/ui/dialog";
|
||||
import type {DeploymentConfig} from '../types';
|
||||
import {FormButtonGroup, FormItem, Select, Submit} from '@formily/antd-v5'
|
||||
import {createForm, Field, FieldDataSource} from '@formily/core'
|
||||
import {createSchemaField, FormProvider, ISchema} from '@formily/react'
|
||||
import {action} from '@formily/reactive'
|
||||
|
||||
interface DeploymentConfigModalProps {
|
||||
open: boolean;
|
||||
onCancel: () => void;
|
||||
onSuccess: () => void;
|
||||
initialValues?: DeploymentConfig;
|
||||
envId: number;
|
||||
}
|
||||
|
||||
const DeploymentConfigModal: React.FC<DeploymentConfigModalProps> = ({
|
||||
open,
|
||||
onCancel,
|
||||
onSuccess,
|
||||
initialValues,
|
||||
envId,
|
||||
}) => {
|
||||
const SchemaField = createSchemaField({
|
||||
components: {
|
||||
Select,
|
||||
FormItem,
|
||||
},
|
||||
})
|
||||
|
||||
const loadData = async (field: Field) => {
|
||||
const linkage = field.query('linkage').get('value')
|
||||
if (!linkage) return []
|
||||
return new Promise<FieldDataSource>((resolve) => {
|
||||
setTimeout(() => {
|
||||
if (linkage === 1) {
|
||||
resolve([
|
||||
{
|
||||
label: 'AAA',
|
||||
value: 'aaa',
|
||||
},
|
||||
{
|
||||
label: 'BBB',
|
||||
value: 'ccc',
|
||||
},
|
||||
])
|
||||
} else if (linkage === 2) {
|
||||
resolve([
|
||||
{
|
||||
label: 'CCC',
|
||||
value: 'ccc',
|
||||
},
|
||||
{
|
||||
label: 'DDD',
|
||||
value: 'ddd',
|
||||
},
|
||||
])
|
||||
}
|
||||
}, 1500)
|
||||
})
|
||||
}
|
||||
|
||||
const useAsyncDataSource = (service: typeof loadData) => (field: Field) => {
|
||||
field.loading = true
|
||||
service(field).then(
|
||||
action.bound?.((data) => {
|
||||
field.dataSource = data
|
||||
field.loading = false
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
const form = createForm()
|
||||
|
||||
const schema: ISchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
linkage: {
|
||||
type: 'string',
|
||||
title: '联动选择框',
|
||||
enum: [
|
||||
{label: '发请求1', value: 1},
|
||||
{label: '发请求2', value: 2},
|
||||
],
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Select',
|
||||
'x-component-props': {
|
||||
style: {
|
||||
width: 120,
|
||||
},
|
||||
},
|
||||
},
|
||||
select: {
|
||||
type: 'string',
|
||||
title: '异步选择框',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Select',
|
||||
'x-component-props': {
|
||||
style: {
|
||||
width: 120,
|
||||
},
|
||||
},
|
||||
'x-reactions': ['{{useAsyncDataSource(loadData)}}'],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={(open) => !open && onCancel()}>
|
||||
<DialogContent className="max-w-3xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>部署配置</DialogTitle>
|
||||
<DialogDescription>
|
||||
配置三方Jenkins系统相关参数
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="h-[calc(100vh-250px)] overflow-y-auto p-6">
|
||||
<FormProvider form={form}>
|
||||
<SchemaField schema={schema} scope={{useAsyncDataSource, loadData}}/>
|
||||
<FormButtonGroup>
|
||||
<Submit onSubmit={console.log}>Submit</Submit>
|
||||
</FormButtonGroup>
|
||||
</FormProvider>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeploymentConfigModal;
|
||||
@ -43,11 +43,30 @@ export const getNodeCategory = (nodeType: NodeType | string): NodeCategory => {
|
||||
|
||||
// JSON Schema 定义
|
||||
/**
|
||||
* JSON Schema 接口 - 直接使用 Formily 官方提供的 ISchema 类型
|
||||
* 从 @formily/react 导入(它会从 @formily/json-schema 重新导出)
|
||||
* JSON Schema 接口 - 符合 JSON Schema Draft 7 标准
|
||||
* 用于定义节点配置和输入映射的表单结构
|
||||
*/
|
||||
import type { ISchema } from '@formily/react';
|
||||
export type JSONSchema = ISchema;
|
||||
export interface JSONSchema {
|
||||
type?: 'string' | 'number' | 'integer' | 'boolean' | 'object' | 'array';
|
||||
title?: string;
|
||||
description?: string;
|
||||
properties?: Record<string, JSONSchema>;
|
||||
required?: string[];
|
||||
default?: any;
|
||||
enum?: any[];
|
||||
format?: string;
|
||||
pattern?: string;
|
||||
minLength?: number;
|
||||
maxLength?: number;
|
||||
minimum?: number;
|
||||
maximum?: number;
|
||||
minItems?: number;
|
||||
maxItems?: number;
|
||||
'x-component'?: string;
|
||||
'x-component-props'?: Record<string, any>;
|
||||
'x-dataSource'?: string;
|
||||
[key: string]: any; // 允许扩展属性
|
||||
}
|
||||
|
||||
// ========== 输出字段定义 ==========
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user