feat(mcp): show inline duplicate ID error and block submit in add mode
- Display red hint next to title when ID exists - Disable Add/Save button and prevent submit on duplicate - Accept existing IDs via prop for real-time validation - Remove overwrite confirmation dialog on add - i18n: add duplicate-ID error strings and remove unused overwrite prompt - files: - src/components/mcp/McpFormModal.tsx - src/components/mcp/McpPanel.tsx - src/i18n/locales/en.json - src/i18n/locales/zh.json
This commit is contained in:
@@ -11,6 +11,7 @@ interface McpFormModalProps {
|
|||||||
initialData?: McpServer;
|
initialData?: McpServer;
|
||||||
onSave: (id: string, server: McpServer) => Promise<void>;
|
onSave: (id: string, server: McpServer) => Promise<void>;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
|
existingIds?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -38,6 +39,7 @@ const McpFormModal: React.FC<McpFormModalProps> = ({
|
|||||||
initialData,
|
initialData,
|
||||||
onSave,
|
onSave,
|
||||||
onClose,
|
onClose,
|
||||||
|
existingIds = [],
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [formId, setFormId] = useState(editingId || "");
|
const [formId, setFormId] = useState(editingId || "");
|
||||||
@@ -50,10 +52,19 @@ const McpFormModal: React.FC<McpFormModalProps> = ({
|
|||||||
const [jsonError, setJsonError] = useState("");
|
const [jsonError, setJsonError] = useState("");
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
const [isWizardOpen, setIsWizardOpen] = useState(false);
|
const [isWizardOpen, setIsWizardOpen] = useState(false);
|
||||||
|
const [idError, setIdError] = useState("");
|
||||||
|
|
||||||
// 编辑模式下禁止修改 ID
|
// 编辑模式下禁止修改 ID
|
||||||
const isEditing = !!editingId;
|
const isEditing = !!editingId;
|
||||||
|
|
||||||
|
const handleIdChange = (value: string) => {
|
||||||
|
setFormId(value);
|
||||||
|
if (!isEditing) {
|
||||||
|
const exists = existingIds.includes(value.trim());
|
||||||
|
setIdError(exists ? t("mcp.error.idExists") : "");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleJsonChange = (value: string) => {
|
const handleJsonChange = (value: string) => {
|
||||||
setFormJson(value);
|
setFormJson(value);
|
||||||
|
|
||||||
@@ -104,6 +115,12 @@ const McpFormModal: React.FC<McpFormModalProps> = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 新增模式:阻止提交重名 ID
|
||||||
|
if (!isEditing && existingIds.includes(formId.trim())) {
|
||||||
|
setIdError(t("mcp.error.idExists"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 验证 JSON
|
// 验证 JSON
|
||||||
const currentJsonError = validateJson(formJson);
|
const currentJsonError = validateJson(formJson);
|
||||||
setJsonError(currentJsonError);
|
setJsonError(currentJsonError);
|
||||||
@@ -186,14 +203,21 @@ const McpFormModal: React.FC<McpFormModalProps> = ({
|
|||||||
<div className="p-6 space-y-4">
|
<div className="p-6 space-y-4">
|
||||||
{/* ID (标题) */}
|
{/* ID (标题) */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
<div className="flex items-center justify-between mb-2">
|
||||||
{t("mcp.form.title")} <span className="text-red-500">*</span>
|
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
</label>
|
{t("mcp.form.title")} <span className="text-red-500">*</span>
|
||||||
|
</label>
|
||||||
|
{!isEditing && idError && (
|
||||||
|
<span className="text-xs text-red-500 dark:text-red-400">
|
||||||
|
{idError}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<input
|
<input
|
||||||
className={inputStyles.text}
|
className={inputStyles.text}
|
||||||
placeholder={t("mcp.form.titlePlaceholder")}
|
placeholder={t("mcp.form.titlePlaceholder")}
|
||||||
value={formId}
|
value={formId}
|
||||||
onChange={(e) => setFormId(e.target.value)}
|
onChange={(e) => handleIdChange(e.target.value)}
|
||||||
disabled={isEditing}
|
disabled={isEditing}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -247,7 +271,7 @@ const McpFormModal: React.FC<McpFormModalProps> = ({
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
disabled={saving}
|
disabled={saving || (!isEditing && !!idError)}
|
||||||
className={buttonStyles.primary}
|
className={buttonStyles.primary}
|
||||||
>
|
>
|
||||||
<Save size={16} />
|
<Save size={16} />
|
||||||
|
|||||||
@@ -292,6 +292,7 @@ const McpPanel: React.FC<McpPanelProps> = ({ onClose, onNotify }) => {
|
|||||||
<McpFormModal
|
<McpFormModal
|
||||||
editingId={editingId || undefined}
|
editingId={editingId || undefined}
|
||||||
initialData={editingId ? servers[editingId] : undefined}
|
initialData={editingId ? servers[editingId] : undefined}
|
||||||
|
existingIds={Object.keys(servers)}
|
||||||
onSave={handleSave}
|
onSave={handleSave}
|
||||||
onClose={handleCloseForm}
|
onClose={handleCloseForm}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -316,6 +316,7 @@
|
|||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"idRequired": "Please enter identifier",
|
"idRequired": "Please enter identifier",
|
||||||
|
"idExists": "Identifier already exists. Please choose another.",
|
||||||
"jsonInvalid": "Invalid JSON format",
|
"jsonInvalid": "Invalid JSON format",
|
||||||
"commandRequired": "Please enter command",
|
"commandRequired": "Please enter command",
|
||||||
"singleServerObjectRequired": "Please paste a single MCP server object (do not include top-level mcpServers)",
|
"singleServerObjectRequired": "Please paste a single MCP server object (do not include top-level mcpServers)",
|
||||||
|
|||||||
@@ -316,6 +316,7 @@
|
|||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"idRequired": "请填写标识",
|
"idRequired": "请填写标识",
|
||||||
|
"idExists": "该标识已存在,请更换",
|
||||||
"jsonInvalid": "JSON 格式错误,请检查",
|
"jsonInvalid": "JSON 格式错误,请检查",
|
||||||
"commandRequired": "请填写命令",
|
"commandRequired": "请填写命令",
|
||||||
"singleServerObjectRequired": "此处只需单个服务器对象,请不要粘贴包含 mcpServers 的整份配置",
|
"singleServerObjectRequired": "此处只需单个服务器对象,请不要粘贴包含 mcpServers 的整份配置",
|
||||||
|
|||||||
Reference in New Issue
Block a user