This is an automated email from the ASF dual-hosted git repository.
liuhongyu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shenyu-dashboard.git
The following commit(s) were added to refs/heads/master by this push:
new d72daa1a [feat] mcpserver support objectparam (#547)
d72daa1a is described below
commit d72daa1a0939fb54ec53109c2c0213f14c6d0455
Author: Wweiei <[email protected]>
AuthorDate: Tue Sep 16 17:36:44 2025 +0800
[feat] mcpserver support objectparam (#547)
* feat: 增加array和object类型
* feat: 增加items的禁用
* feat: 修改样式
* feat: 修改限制
* feat: 修改描述
* feat: 修改bug
* fix:mcp rule synchronization failure between form data and JSON data
* code normalize
* code normalize
---
src/locales/en-US.json | 4 +
src/locales/zh-CN.json | 4 +
src/routes/Plugin/McpServer/ToolsModal.js | 751 +++++++++++++++++++++++++-----
3 files changed, 636 insertions(+), 123 deletions(-)
diff --git a/src/locales/en-US.json b/src/locales/en-US.json
index 72a29f35..8ccf3578 100644
--- a/src/locales/en-US.json
+++ b/src/locales/en-US.json
@@ -53,6 +53,10 @@
"SHENYU.COMMON.PARAMETER.NAME": "ParamName",
"SHENYU.COMMON.PARAMETER.TYPE": "ParamType",
"SHENYU.COMMON.PARAMETER.DESCRIPTION": "ParamDescription",
+ "SHENYU.COMMON.PARAMETER.SUBTYPE.NAME": "SubParamName",
+ "SHENYU.COMMON.PARAMETER.SUBTYPE": "SubParamType",
+ "SHENYU.COMMON.PARAMETER.SUBTYPE.DESCRIPTION": "SubParamDescription",
+ "SHENYU.COMMON.PARAMETER.SUBTYPE.ADD": "add",
"SHENYU.COMMON.DEAL": "Handler",
"SHENYU.COMMON.DEAL.COMPONENT": "Component Handler",
"SHENYU.COMMON.DEAL.CUSTOM": "Custom Handler",
diff --git a/src/locales/zh-CN.json b/src/locales/zh-CN.json
index 7b7abfac..221ff74c 100644
--- a/src/locales/zh-CN.json
+++ b/src/locales/zh-CN.json
@@ -52,6 +52,10 @@
"SHENYU.COMMON.PARAMETER.NAME": "参数名称",
"SHENYU.COMMON.PARAMETER.TYPE": "参数类型",
"SHENYU.COMMON.PARAMETER.DESCRIPTION": "参数描述",
+ "SHENYU.COMMON.PARAMETER.SUBTYPE.NAME": "子参数名称",
+ "SHENYU.COMMON.PARAMETER.SUBTYPE": "子参数类型",
+ "SHENYU.COMMON.PARAMETER.SUBTYPE.DESCRIPTION": "子参数类型描述",
+ "SHENYU.COMMON.PARAMETER.SUBTYPE.ADD": "添加",
"SHENYU.COMMON.DEAL": "处理",
"SHENYU.COMMON.DEAL.COMPONENT": "组件处理",
"SHENYU.COMMON.DEAL.CUSTOM": "自定义处理",
diff --git a/src/routes/Plugin/McpServer/ToolsModal.js
b/src/routes/Plugin/McpServer/ToolsModal.js
index ee967530..f48db5d1 100644
--- a/src/routes/Plugin/McpServer/ToolsModal.js
+++ b/src/routes/Plugin/McpServer/ToolsModal.js
@@ -74,6 +74,10 @@ class AddModal extends Component {
try {
const handleObj = JSON.parse(handle);
parameters = handleObj.parameters || [];
+
+ // Handle array type parameters, force set the first sub-parameter's
name to "items"
+ parameters = this.fixArrayParameterNames(parameters);
+
questJson = handleObj.requestConfig
? JSON.parse(handleObj.requestConfig)
: {};
@@ -81,18 +85,20 @@ class AddModal extends Component {
// Failed to parse handle JSON
parameters = [
{
- type: "String",
+ type: "string",
name: "",
description: "",
+ required: true,
},
];
}
} else {
parameters = [
{
- type: "String",
+ type: "string",
name: "",
description: "",
+ required: true,
},
];
}
@@ -114,15 +120,21 @@ class AddModal extends Component {
if (handle) {
try {
const handleObj = JSON.parse(handle);
+ // Handle array type parameters, force set the first sub-parameter's
name to "items"
+ const fixedParameters = this.fixArrayParameterNames(
+ handleObj.parameters || [],
+ );
+
flattenedJson = {
name: name || "",
description: description || "",
enabled: enabled !== undefined ? enabled : true,
- parameters: handleObj.parameters || [],
+ parameters: fixedParameters,
requestConfig: handleObj.requestConfig || "{}",
};
} catch (e) {
// Failed to parse handle JSON
+ console.warn("Failed to parse handle JSON in initJsonMode:",
e.message);
}
}
@@ -175,49 +187,128 @@ class AddModal extends Component {
handleEditModeChange = (e) => {
const editMode = e.target.value;
- this.setState({ editMode });
- // 切换到JSON模式时,同步表单数据到JSON
+ // When switching to JSON mode, sync form data to JSON
if (editMode === "json") {
- const { form } = this.props;
- const { parameters, questJson } = this.state;
-
- // 获取表单数据
- form.validateFields((err, values) => {
- if (!err) {
- const toolJson = {
- name: values.name || "",
- description: values.description || "",
- enabled: values.enabled !== undefined ? values.enabled : true,
- handle: {
- parameters,
- requestConfig: questJson,
- description: values.description || "",
- },
- };
+ this.syncFormToJson();
+ } else {
+ // When switching to form mode, sync JSON data to form
+ this.syncJsonToForm();
+ }
+ this.setState({ editMode });
+ };
- this.setState({
- jsonText: JSON.stringify(toolJson, null, 2),
- jsonError: null,
- });
+ // Sync form data to JSON
+ syncFormToJson = () => {
+ const { form } = this.props;
+ const { parameters, questJson } = this.state;
+ const values = form.getFieldsValue();
+ // Get form data
+ const toolJson = {
+ name: values.name || "",
+ description: values.description || "",
+ enabled: values.enabled !== undefined ? values.enabled : true,
+ parameters,
+ requestConfig: JSON.stringify(questJson),
+ };
+ this.setState({
+ jsonText: JSON.stringify(toolJson, null, 2),
+ jsonError: null,
+ });
+ };
+
+ // Sync JSON data to form
+ syncJsonToForm = () => {
+ const { jsonText } = this.state;
+ const { form } = this.props;
+ setTimeout(() => {
+ try {
+ const parsedJson = JSON.parse(jsonText);
+
+ // Update form fields
+ form.setFieldsValue({
+ name: parsedJson.name || "",
+ description: parsedJson.description || "",
+ enabled: parsedJson.enabled !== undefined ? parsedJson.enabled :
true,
+ });
+
+ // 更新参数和请求配置
+ let questJson = {};
+ try {
+ questJson =
+ typeof parsedJson.requestConfig === "string"
+ ? JSON.parse(parsedJson.requestConfig)
+ : parsedJson.requestConfig || {};
+ } catch (e) {
+ questJson = {};
}
- });
- }
+
+ this.setState({
+ parameters: this.fixArrayParameterNames(parsedJson.parameters || []),
+ questJson,
+ });
+ } catch (error) {
+ // Do not sync when JSON format is invalid
+ console.warn("JSON格式错误,无法同步到表单:", error.message);
+ }
+ }, 0);
};
handleJsonTextChange = (e) => {
const jsonText = e.target.value;
this.setState({ jsonText });
- // 实时验证JSON格式
+ // Validate JSON format in real time
try {
JSON.parse(jsonText);
this.setState({ jsonError: null });
+
+ // If JSON format is valid and currently in JSON mode, sync to form in
real time
+ if (this.state.editMode === "json") {
+ this.syncJsonToFormRealtime(jsonText);
+ }
} catch (error) {
this.setState({ jsonError: error.message });
}
};
+ // Sync JSON data to form in real time (silent update, no validation errors)
+ syncJsonToFormRealtime = (jsonText) => {
+ const { form } = this.props;
+
+ try {
+ const parsedJson = JSON.parse(jsonText);
+
+ // Update form fields (silent update, no validation triggered)
+ const fieldsToUpdate = {};
+ if (parsedJson.name !== undefined) fieldsToUpdate.name = parsedJson.name;
+ if (parsedJson.description !== undefined)
+ fieldsToUpdate.description = parsedJson.description;
+ if (parsedJson.enabled !== undefined)
+ fieldsToUpdate.enabled = parsedJson.enabled;
+
+ form.setFieldsValue(fieldsToUpdate);
+
+ // Update parameters and request configuration
+ let questJson = {};
+ try {
+ questJson =
+ typeof parsedJson.requestConfig === "string"
+ ? JSON.parse(parsedJson.requestConfig)
+ : parsedJson.requestConfig || {};
+ } catch (e) {
+ questJson = {};
+ }
+
+ this.setState({
+ parameters: parsedJson.parameters || [],
+ questJson,
+ });
+ } catch (error) {
+ // Silently handle errors to avoid affecting user input
+ }
+ };
+
handleFormatJson = () => {
try {
const parsed = JSON.parse(this.state.jsonText);
@@ -257,32 +348,112 @@ class AddModal extends Component {
});
};
- updateJson = (obj) => {
- this.setState({
- jsonText: JSON.stringify(obj, null, 2),
- jsonError: null,
+ // Listen for form field changes
+ handleFormValuesChange = () => {
+ // If currently in form mode, sync form data to JSON in real time
+ if (this.state.editMode === "form") {
+ // Use setTimeout to ensure form values are updated before syncing
+ setTimeout(() => {
+ // Slightly delay to ensure form values are updated before syncing
+ this.syncFormToJson();
+ }, 100);
+ }
+ };
+
+ // Sync form data to JSON when parameters change
+ handleParametersChange = (newParameters) => {
+ this.setState({ parameters: newParameters }, () => {
+ // If currently in form mode, sync form data to JSON
+ if (this.state.editMode === "form") {
+ this.syncFormToJson();
+ }
});
};
+ updateJson = (obj) => {
+ const { form } = this.props;
+
+ // Get current form values
+ const values = form.getFieldsValue();
+
+ // Build complete tool configuration object
+ const toolJson = {
+ name: values.name || "",
+ description: values.description || "",
+ enabled: values.enabled !== undefined ? values.enabled : true,
+ parameters: this.state.parameters,
+ requestConfig: JSON.stringify(obj.updated_src),
+ };
+
+ this.setState(
+ {
+ questJson: obj.updated_src,
+ jsonText: JSON.stringify(toolJson, null, 2),
+ jsonError: null,
+ },
+ () => {
+ // If currently in form mode, sync form data to JSON
+ if (this.state.editMode === "form") {
+ this.syncFormToJson();
+ }
+ },
+ );
+ };
+
checkParams = () => {
let { parameters } = this.state;
let result = true;
- if (parameters) {
- parameters.forEach((item, index) => {
- const { type, name, description } = item;
- if (!type || !name || !description) {
+ // Maximum nesting depth limit
+ const MAX_NESTING_DEPTH = 6;
+
+ const checkParameterRecursive = (params, path = "", depth = 0) => {
+ if (!params || !Array.isArray(params)) return true;
+
+ // Check nesting depth
+ if (depth > MAX_NESTING_DEPTH) {
+ message.destroy();
+ message.error(
+ `参数嵌套深度超过限制(最大${MAX_NESTING_DEPTH}层): ${path}`,
+ );
+ return false;
+ }
+
+ for (let i = 0; i < params.length; i += 1) {
+ const item = params[i];
+ const { type, name } = item;
+ const currentPath = path ? `${path}[${i}]` : `第${i + 1}行`;
+
+ if (!type || !name) {
message.destroy();
- message.error(`Line ${index + 1} param is incomplete`);
- result = false;
+ message.error(`${currentPath} 参数不完整,请填写名称、类型`);
+ return false;
}
- // eslint-disable-next-line no-lonely-if
- if (!name) {
+
+ // Validate parameter name format (optional: add more strict
validation)
+ if (name && !/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {
message.destroy();
- message.error(`Line ${index + 1} param is incomplete`);
- result = false;
+ message.warning(
+ `${currentPath} Parameter name suggestion: Use letters, numbers,
and underscores, and start with a letter or underscore`,
+ );
}
- });
- }
+
+ // Recursively check child parameters
+ if ((type === "object" || type === "array") && item.parameters) {
+ if (
+ !checkParameterRecursive(
+ item.parameters,
+ `${currentPath}.parameters`,
+ depth + 1,
+ )
+ ) {
+ return false;
+ }
+ }
+ }
+ return true;
+ };
+
+ result = checkParameterRecursive(parameters);
return result;
};
@@ -291,10 +462,10 @@ class AddModal extends Component {
const { editMode } = this.state;
if (editMode === "form") {
- // 表单模式提交
+ // Form mode submission
this.handleFormSubmit();
} else {
- // JSON模式提交
+ // JSON mode submission
this.handleJsonSubmit();
}
};
@@ -359,13 +530,19 @@ class AddModal extends Component {
try {
const parsedJson = JSON.parse(jsonText);
- // 验证必要字段
+ // Validate required fields
+ if (!parsedJson.name || !parsedJson.name.trim()) {
+ message.error(getIntlContent("SHENYU.MCP.JSON.EDIT.TOOL.NAME.ERROR"));
+ return;
+ }
+
+ // Validate required fields
if (!parsedJson.name || !parsedJson.name.trim()) {
message.error(getIntlContent("SHENYU.MCP.JSON.EDIT.TOOL.NAME.ERROR"));
return;
}
- // 确保字段存在
+ // Ensure required fields exist
const finalData = {
name: parsedJson.name,
description: parsedJson.description || "",
@@ -374,7 +551,7 @@ class AddModal extends Component {
requestConfig: parsedJson.requestConfig || "{}",
};
- // 将扁平化数据转换回原始格式,并添加必需的规则级别字段
+ // Convert flattened data back to original format and add required
rule-level fields
const transformedData = {
name: finalData.name,
description: finalData.description,
@@ -384,7 +561,7 @@ class AddModal extends Component {
requestConfig: finalData.requestConfig,
description: finalData.description,
}),
- // 添加必需的规则级别字段默认值
+ // Add required rule-level fields default values
sort: 1,
loged: true,
matchMode: "0",
@@ -410,22 +587,287 @@ class AddModal extends Component {
handleAdd = () => {
let { parameters } = this.state;
parameters.push({
- type: "String",
+ type: "string",
name: "",
description: "",
+ required: true,
});
- this.setState({ parameters }, () => {
- let len = parameters.length || 0;
- let key = `typeValueEn${len - 1}`;
- this.setState({ [key]: true });
- });
+ this.handleParametersChange(parameters);
+ let len = parameters.length || 0;
+ let key = `typeValueEn${len - 1}`;
+ this.setState({ [key]: true });
};
handleDelete = (index) => {
let { parameters } = this.state;
parameters.splice(index, 1);
- this.setState({ parameters });
+ this.handleParametersChange(parameters);
+ };
+
+ // Add sub-parameter - supports arbitrary depth nesting but with depth limit
+ handleAddSubParameter = (path) => {
+ // Maximum nesting depth limit
+ const MAX_NESTING_DEPTH = 6;
+
+ // Check current path depth
+ if (path.length >= MAX_NESTING_DEPTH) {
+ message.destroy();
+ message.warning(
+ `已达到最大嵌套深度限制(${MAX_NESTING_DEPTH}层),无法继续添加子参数`,
+ );
+ return;
+ }
+
+ let { parameters } = this.state;
+ const newParameters = [...parameters];
+
+ // Find target parameter based on path
+ let targetParam = newParameters;
+ for (let i = 0; i < path.length; i += 1) {
+ if (i === path.length - 1) {
+ // Last level, add sub-parameter
+ if (!targetParam[path[i]].parameters) {
+ targetParam[path[i]].parameters = [];
+ }
+ targetParam[path[i]].parameters.push({
+ name: "",
+ type: "string",
+ description: "",
+ required: true,
+ });
+ } else {
+ // Intermediate level, continue navigating down
+ targetParam = targetParam[path[i]].parameters;
+ }
+ }
+
+ this.handleParametersChange(newParameters);
+ };
+
+ // Delete sub-parameter - supports arbitrary depth nesting
+ handleDeleteSubParameter = (path, subIndex) => {
+ let { parameters } = this.state;
+ const newParameters = [...parameters];
+
+ // Find parent of target parameter based on path
+ let targetParam = newParameters;
+ for (let i = 0; i < path.length; i += 1) {
+ if (i === path.length - 1) {
+ // Last level, delete sub-parameter
+ targetParam[path[i]].parameters.splice(subIndex, 1);
+ } else {
+ // Intermediate level, continue navigating down
+ targetParam = targetParam[path[i]].parameters;
+ }
+ }
+
+ this.handleParametersChange(newParameters);
+ };
+
+ // Update sub-parameter - supports arbitrary depth nesting
+ updateSubParameter = (path, subIndex, field, value) => {
+ let { parameters } = this.state;
+ const newParameters = [...parameters];
+
+ // Find target parameter based on path
+ let targetParam = newParameters;
+ for (let i = 0; i < path.length; i += 1) {
+ if (i === path.length - 1) {
+ // Last level, update sub-parameter
+ targetParam[path[i]].parameters[subIndex][field] = value;
+
+ // If sub-parameter type changes to object or array, also handle
nesting
+ if (field === "type") {
+ if (value === "object") {
+ targetParam[path[i]].parameters[subIndex].parameters = [];
+ } else if (value === "array") {
+ // Force set the first sub-parameter name to "items" for array type
+ targetParam[path[i]].parameters[subIndex].parameters = [
+ {
+ name: "items",
+ type: "string",
+ description: "",
+ required: true,
+ },
+ ];
+ } else {
+ delete targetParam[path[i]].parameters[subIndex].parameters;
+ }
+ }
+ } else {
+ // Intermediate level, continue navigating down
+ targetParam = targetParam[path[i]].parameters;
+ }
+ }
+
+ this.handleParametersChange(newParameters);
+ };
+
+ // Render sub-parameters - supports infinite level nesting
+ renderSubParameters = (subParams, parentType, path = [], level = 1) => {
+ if (!subParams || !Array.isArray(subParams)) return null;
+
+ const indentStyle = {
+ paddingLeft: `${level * 20}px`,
+ borderLeft: level > 1 ? "2px solid #e8e8e8" : "none",
+ marginLeft: level > 1 ? "10px" : "0",
+ };
+
+ return subParams.map((subParam, subIndex) => {
+ const currentPath = [...path, subIndex];
+
+ return (
+ <div key={`${path.join("-")}-${subIndex}`} style={indentStyle}>
+ <Row
+ gutter={8}
+ style={{ marginTop: "8px", display: "flex", alignItems: "center" }}
+ >
+ <Col span={4}>
+ <Input
+ allowClear
+ value={subParam.name}
+ placeholder={getIntlContent(
+ "SHENYU.COMMON.PARAMETER.SUBTYPE.NAME",
+ )}
+ onChange={(e) => {
+ this.updateSubParameter(
+ path,
+ subIndex,
+ "name",
+ e.target.value,
+ );
+ }}
+ disabled={parentType === "array"}
+ />
+ </Col>
+ <Col span={5}>
+ <Select
+ value={subParam.type}
+ placeholder={getIntlContent("SHENYU.COMMON.PARAMETER.SUBTYPE")}
+ onChange={(value) => {
+ this.updateSubParameter(path, subIndex, "type", value);
+ }}
+ >
+ <Option value="string">String</Option>
+ <Option value="integer">Integer</Option>
+ <Option value="long">Long</Option>
+ <Option value="double">Double</Option>
+ <Option value="float">Float</Option>
+ <Option value="boolean">Boolean</Option>
+ <Option value="object">Object</Option>
+ <Option value="array">Array</Option>
+ </Select>
+ </Col>
+ <Col span={11}>
+ <Input
+ allowClear
+ value={subParam.description}
+ placeholder={getIntlContent(
+ "SHENYU.COMMON.PARAMETER.SUBTYPE.DESCRIPTION",
+ )}
+ onChange={(e) => {
+ this.updateSubParameter(
+ path,
+ subIndex,
+ "description",
+ e.target.value,
+ );
+ }}
+ // Add hidden logic: hide description input when parent form
type is array
+ style={{ display: parentType === "array" ? "none" : "block" }}
+ />
+ </Col>
+ <Col span={4}>
+ <div style={{ display: "flex", gap: "4px", flexWrap: "wrap" }}>
+ {/* Show add button for Object type */}
+ {subParam.type === "object" && (
+ <Button
+ type="primary"
+ size="small"
+ onClick={() => {
+ this.handleAddSubParameter([...path, subIndex]);
+ }}
+ >
+ {getIntlContent("SHENYU.COMMON.PARAMETER.SUBTYPE.ADD")}
+ </Button>
+ )}
+ <Button
+ type="danger"
+ size="small"
+ onClick={() => {
+ this.handleDeleteSubParameter(path, subIndex);
+ }}
+ >
+ {getIntlContent("SHENYU.COMMON.DELETE.NAME")}
+ </Button>
+ </div>
+ </Col>
+ </Row>
+
+ {/* Recursively render deeper sub-parameters */}
+ {(subParam.type === "object" || subParam.type === "array") &&
+ subParam.parameters && (
+ <div
+ style={{
+ marginTop: "8px",
+ border: "1px solid #f0f0f0",
+ borderRadius: "4px",
+ padding: "8px",
+ backgroundColor: level % 2 === 1 ? "#fafafa" : "#f5f5f5",
+ position: "relative",
+ }}
+ >
+ {this.renderSubParameters(
+ subParam.parameters,
+ subParam.type,
+ currentPath,
+ level + 1,
+ )}
+ </div>
+ )}
+ </div>
+ );
+ });
+ };
+
+ // Fix array type parameter names, force set the first sub-parameter name to
"items"
+ fixArrayParameterNames = (parameters) => {
+ if (!parameters || !Array.isArray(parameters)) {
+ return parameters;
+ }
+
+ return parameters.map((param) => {
+ // If current parameter is array type and has sub-parameters, force set
first sub-parameter's name to "items"
+ if (
+ param.type === "array" &&
+ param.parameters &&
+ param.parameters.length > 0
+ ) {
+ return {
+ ...param,
+ parameters: param.parameters.map((subParam, index) => {
+ if (index === 0) {
+ return {
+ ...subParam,
+ name: "items",
+ };
+ }
+ return subParam;
+ }),
+ };
+ }
+
+ // Recursively handle sub-parameters
+ if (param.parameters && Array.isArray(param.parameters)) {
+ return {
+ ...param,
+ parameters: this.fixArrayParameterNames(param.parameters),
+ };
+ }
+
+ return param;
+ });
};
render() {
@@ -466,7 +908,7 @@ class AddModal extends Component {
},
};
- // 用于预览的JSON对象
+ // JSON object for preview
let previewJson = {};
try {
previewJson = JSON.parse(jsonText);
@@ -537,8 +979,12 @@ class AddModal extends Component {
</div>
{editMode === "form" ? (
- // 表单模式
- <Form onSubmit={this.handleSubmit} className="login-form">
+ // Form mode
+ <Form
+ onSubmit={this.handleSubmit}
+ className="login-form"
+ onValuesChange={this.handleFormValuesChange}
+ >
<FormItem
label={getIntlContent("SHENYU.PLUGIN.SELECTOR.LIST.COLUMN.NAME")}
{...formItemLayout}
@@ -667,68 +1113,127 @@ class AddModal extends Component {
>
{parameters.map((item, index) => {
return (
- <Row key={index} gutter={8}>
- <Col span={4}>
- <Input
- allowClear
- value={item.name}
- placeholder={getIntlContent(
- "SHENYU.COMMON.PARAMETER.NAME",
- )}
- onChange={(e) => {
- const newValue = e.target.value;
- const newParameters = [...parameters];
- newParameters[index].name = newValue;
- this.setState({ parameters: newParameters });
- }}
- />
- </Col>
- <Col span={5}>
- <Select
- value={item.type}
- placeholder={getIntlContent(
- "SHENYU.COMMON.PARAMETER.TYPE",
- )}
- onChange={(value) => {
- const newParameters = [...parameters];
- newParameters[index].type = value;
- this.setState({ parameters: newParameters });
- }}
- >
- <Option value="String">String</Option>
- <Option value="Integer">Integer</Option>
- <Option value="Long">Long</Option>
- <Option value="Double">Double</Option>
- <Option value="Float">Float</Option>
- <Option value="Boolean">Boolean</Option>
- </Select>
- </Col>
- <Col span={11}>
- <Input
- allowClear
- value={item.description}
- placeholder={getIntlContent(
- "SHENYU.COMMON.PARAMETER.DESCRIPTION",
- )}
- onChange={(e) => {
- const newValue = e.target.value;
- const newParameters = [...parameters];
- newParameters[index].description = newValue;
- this.setState({ parameters: newParameters });
- }}
- />
- </Col>
- <Col span={4}>
- <Button
- type="danger"
- onClick={() => {
- this.handleDelete(index);
- }}
- >
- {getIntlContent("SHENYU.COMMON.DELETE.NAME")}
- </Button>
- </Col>
- </Row>
+ <div key={index} style={{ marginBottom: "16px" }}>
+ <Row gutter={8}>
+ <Col span={4}>
+ <Input
+ allowClear
+ value={item.name}
+ placeholder={getIntlContent(
+ "SHENYU.COMMON.PARAMETER.NAME",
+ )}
+ onChange={(e) => {
+ const newValue = e.target.value;
+ const newParameters = [...parameters];
+ newParameters[index].name = newValue;
+ this.handleParametersChange(newParameters);
+ }}
+ />
+ </Col>
+ <Col span={5}>
+ <Select
+ value={item.type}
+ placeholder={getIntlContent(
+ "SHENYU.COMMON.PARAMETER.TYPE",
+ )}
+ onChange={(value) => {
+ const newParameters = [...parameters];
+ newParameters[index].type = value;
+
+ // Handle type change logic
+ if (value === "object") {
+ // Object type: clear sub-parameters, wait for
user to manually add
+ newParameters[index].parameters = [];
+ } else if (value === "array") {
+ // Array type: force set first sub-parameter
name to "items"
+ newParameters[index].parameters = [
+ {
+ name: "items",
+ type: "string",
+ description: "",
+ required: true,
+ },
+ ];
+ } else {
+ // Other basic types: delete sub-parameters
+ delete newParameters[index].parameters;
+ }
+
+ this.handleParametersChange(newParameters);
+ }}
+ >
+ <Option value="string">String</Option>
+ <Option value="integer">Integer</Option>
+ <Option value="long">Long</Option>
+ <Option value="double">Double</Option>
+ <Option value="float">Float</Option>
+ <Option value="boolean">Boolean</Option>
+ <Option value="object">Object</Option>
+ <Option value="array">Array</Option>
+ </Select>
+ </Col>
+ <Col span={11}>
+ <Input
+ allowClear
+ value={item.description}
+ placeholder={getIntlContent(
+ "SHENYU.COMMON.PARAMETER.DESCRIPTION",
+ )}
+ onChange={(e) => {
+ const newValue = e.target.value;
+ const newParameters = [...parameters];
+ newParameters[index].description = newValue;
+ this.handleParametersChange(newParameters);
+ }}
+ />
+ </Col>
+ <Col span={4}>
+ <div style={{ display: "flex", gap: "4px" }}>
+ {/* Object type: display add button */}
+ {item.type === "object" && (
+ <Button
+ type="primary"
+ onClick={() => {
+ this.handleAddSubParameter([index]);
+ }}
+ >
+ {getIntlContent(
+ "SHENYU.COMMON.PARAMETER.SUBTYPE.ADD",
+ )}
+ </Button>
+ )}
+ <Button
+ type="danger"
+ onClick={() => {
+ this.handleDelete(index);
+ }}
+ >
+ {getIntlContent("SHENYU.COMMON.DELETE.NAME")}
+ </Button>
+ </div>
+ </Col>
+ </Row>
+ {/* Render sub-parameters */}
+ {(item.type === "object" || item.type === "array") &&
+ item.parameters && (
+ <div
+ style={{
+ marginTop: "8px",
+ border: "1px solid #f0f0f0",
+ borderRadius: "4px",
+ padding: "8px",
+ backgroundColor: "#fafafa",
+ }}
+ >
+ {this.renderSubParameters(
+ item.parameters,
+ item.type,
+ [index],
+ 1,
+ )}
+ </div>
+ )}
+ </div>
);
})}
</FormItem>
@@ -771,7 +1276,7 @@ class AddModal extends Component {
</FormItem>
</Form>
) : (
- // JSON模式
+ // JSON mode
<Tabs
activeKey={activeTab}
onChange={(key) => this.setState({ activeTab: key })}