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:
Jason
2025-10-10 11:17:40 +08:00
parent eb8d9352c8
commit 7493f3f9dd
4 changed files with 32 additions and 5 deletions

View File

@@ -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} />

View File

@@ -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}
/> />

View File

@@ -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)",

View File

@@ -316,6 +316,7 @@
}, },
"error": { "error": {
"idRequired": "请填写标识", "idRequired": "请填写标识",
"idExists": "该标识已存在,请更换",
"jsonInvalid": "JSON 格式错误,请检查", "jsonInvalid": "JSON 格式错误,请检查",
"commandRequired": "请填写命令", "commandRequired": "请填写命令",
"singleServerObjectRequired": "此处只需单个服务器对象,请不要粘贴包含 mcpServers 的整份配置", "singleServerObjectRequired": "此处只需单个服务器对象,请不要粘贴包含 mcpServers 的整份配置",