From 66d41420390185cc50197edcce21c8420964d032 Mon Sep 17 00:00:00 2001 From: samanhappy Date: Sun, 29 Jun 2025 22:23:42 +0800 Subject: [PATCH] feat: add variable detection and confirmation dialogs in server forms (#205) --- frontend/src/components/AddServerForm.tsx | 92 +++++++++++++++++- .../src/components/MarketServerDetail.tsx | 97 ++++++++++++++++++- frontend/src/components/ServerForm.tsx | 2 +- frontend/src/locales/en.json | 9 +- frontend/src/locales/zh.json | 9 +- frontend/src/utils/variableDetection.ts | 27 ++++++ 6 files changed, 226 insertions(+), 10 deletions(-) create mode 100644 frontend/src/utils/variableDetection.ts diff --git a/frontend/src/components/AddServerForm.tsx b/frontend/src/components/AddServerForm.tsx index 56643c7..05169e7 100644 --- a/frontend/src/components/AddServerForm.tsx +++ b/frontend/src/components/AddServerForm.tsx @@ -1,7 +1,8 @@ import { useState } from 'react' import { useTranslation } from 'react-i18next' import ServerForm from './ServerForm' -import { getApiUrl } from '../utils/runtime'; +import { getApiUrl } from '../utils/runtime' +import { detectVariables } from '../utils/variableDetection' interface AddServerFormProps { onAdd: () => void @@ -11,13 +12,26 @@ const AddServerForm = ({ onAdd }: AddServerFormProps) => { const { t } = useTranslation() const [modalVisible, setModalVisible] = useState(false) const [error, setError] = useState(null) + const [confirmationVisible, setConfirmationVisible] = useState(false) + const [pendingPayload, setPendingPayload] = useState(null) + const [detectedVariables, setDetectedVariables] = useState([]) const toggleModal = () => { setModalVisible(!modalVisible) setError(null) // Clear any previous errors when toggling modal + setConfirmationVisible(false) // Close confirmation dialog + setPendingPayload(null) // Clear pending payload } - const handleSubmit = async (payload: any) => { + const handleConfirmSubmit = async () => { + if (pendingPayload) { + await submitServer(pendingPayload) + setConfirmationVisible(false) + setPendingPayload(null) + } + } + + const submitServer = async (payload: any) => { try { setError(null) const token = localStorage.getItem('mcphub_token'); @@ -65,6 +79,26 @@ const AddServerForm = ({ onAdd }: AddServerFormProps) => { } } + const handleSubmit = async (payload: any) => { + try { + // Check for variables in the payload + const variables = detectVariables(payload) + + if (variables.length > 0) { + // Show confirmation dialog + setDetectedVariables(variables) + setPendingPayload(payload) + setConfirmationVisible(true) + } else { + // Submit directly if no variables found + await submitServer(payload) + } + } catch (err) { + console.error('Error processing server submission:', err) + setError(t('errors.serverAdd')) + } + } + return (
)} + + {confirmationVisible && ( +
+
+

+ {t('server.confirmVariables')} +

+

+ {t('server.variablesDetected')} +

+
+
+
+ + + +
+
+

+ {t('server.detectedVariables')}: +

+
    + {detectedVariables.map((variable, index) => ( +
  • + ${`{${variable}}`} +
  • + ))} +
+
+
+
+

+ {t('server.confirmVariablesMessage')} +

+
+ + +
+
+
+ )} ) } diff --git a/frontend/src/components/MarketServerDetail.tsx b/frontend/src/components/MarketServerDetail.tsx index 9b909c6..321228a 100644 --- a/frontend/src/components/MarketServerDetail.tsx +++ b/frontend/src/components/MarketServerDetail.tsx @@ -2,6 +2,7 @@ import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { MarketServer, MarketServerInstallation } from '@/types'; import ServerForm from './ServerForm'; +import { detectVariables } from '../utils/variableDetection'; import { ServerConfig } from '@/types'; @@ -23,6 +24,9 @@ const MarketServerDetail: React.FC = ({ const { t } = useTranslation(); const [modalVisible, setModalVisible] = useState(false); const [error, setError] = useState(null); + const [confirmationVisible, setConfirmationVisible] = useState(false); + const [pendingPayload, setPendingPayload] = useState(null); + const [detectedVariables, setDetectedVariables] = useState([]); // Helper function to determine button state const getButtonProps = () => { @@ -50,6 +54,27 @@ const MarketServerDetail: React.FC = ({ const toggleModal = () => { setModalVisible(!modalVisible); setError(null); // Clear any previous errors when toggling modal + setConfirmationVisible(false); + setPendingPayload(null); + }; + + const handleConfirmInstall = async () => { + if (pendingPayload) { + await proceedWithInstall(pendingPayload); + setConfirmationVisible(false); + setPendingPayload(null); + } + }; + + const proceedWithInstall = async (payload: any) => { + try { + setError(null); + onInstall(server, payload.config); + setModalVisible(false); + } catch (err) { + console.error('Error installing server:', err); + setError(t('errors.serverInstall')); + } }; const handleInstall = () => { @@ -84,12 +109,20 @@ const MarketServerDetail: React.FC = ({ const handleSubmit = async (payload: any) => { try { - setError(null); - // Pass the server object and the payload (includes env changes) for installation - onInstall(server, payload.config); - setModalVisible(false); + // Check for variables in the payload + const variables = detectVariables(payload); + + if (variables.length > 0) { + // Show confirmation dialog + setDetectedVariables(variables); + setPendingPayload(payload); + setConfirmationVisible(true); + } else { + // Install directly if no variables found + await proceedWithInstall(payload); + } } catch (err) { - console.error('Error installing server:', err); + console.error('Error processing server installation:', err); setError(t('errors.serverInstall')); } }; @@ -292,6 +325,60 @@ const MarketServerDetail: React.FC = ({ /> )} + + {confirmationVisible && ( +
+
+

+ {t('server.confirmVariables')} +

+

+ {t('server.variablesDetected')} +

+
+
+
+ + + +
+
+

+ {t('server.detectedVariables')}: +

+
    + {detectedVariables.map((variable, index) => ( +
  • + ${`{${variable}}`} +
  • + ))} +
+
+
+
+

+ {t('market.confirmVariablesMessage')} +

+
+ + +
+
+
+ )} ); }; diff --git a/frontend/src/components/ServerForm.tsx b/frontend/src/components/ServerForm.tsx index 75457e6..2cc5f2b 100644 --- a/frontend/src/components/ServerForm.tsx +++ b/frontend/src/components/ServerForm.tsx @@ -264,7 +264,7 @@ const ServerForm = ({ onSubmit, onCancel, initialData = null, modalTitle, formEr

{modalTitle}

-
diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json index 6d0b30a..f9383e5 100644 --- a/frontend/src/locales/en.json +++ b/frontend/src/locales/en.json @@ -117,6 +117,11 @@ "argumentsPlaceholder": "Enter arguments", "errorDetails": "Error Details", "viewErrorDetails": "View error details", + "confirmVariables": "Confirm Variable Configuration", + "variablesDetected": "Variables detected in configuration. Please confirm these variables are properly configured:", + "detectedVariables": "Detected Variables", + "confirmVariablesMessage": "Please ensure these variables are properly defined in your runtime environment. Continue adding server?", + "confirmAndAdd": "Confirm and Add", "openapi": { "inputMode": "Input Mode", "inputModeUrl": "Specification URL", @@ -297,7 +302,9 @@ "tagFilterError": "Error filtering servers by tag", "noInstallationMethod": "No installation method available for this server", "showing": "Showing {{from}}-{{to}} of {{total}} servers", - "perPage": "Per page" + "perPage": "Per page", + "confirmVariablesMessage": "Please ensure these variables are properly defined in your runtime environment. Continue installing server?", + "confirmAndInstall": "Confirm and Install" }, "tool": { "run": "Run", diff --git a/frontend/src/locales/zh.json b/frontend/src/locales/zh.json index b8c205d..d8565b2 100644 --- a/frontend/src/locales/zh.json +++ b/frontend/src/locales/zh.json @@ -117,6 +117,11 @@ "argumentsPlaceholder": "请输入参数", "errorDetails": "错误详情", "viewErrorDetails": "查看错误详情", + "confirmVariables": "确认变量配置", + "variablesDetected": "检测到配置中包含变量,请确认这些变量是否已正确配置:", + "detectedVariables": "检测到的变量", + "confirmVariablesMessage": "请确保这些变量在运行环境中已正确定义。是否继续添加服务器?", + "confirmAndAdd": "确认并添加", "openapi": { "inputMode": "输入模式", "inputModeUrl": "规范 URL", @@ -298,7 +303,9 @@ "tagFilterError": "按标签筛选服务器失败", "noInstallationMethod": "该服务器没有可用的安装方法", "showing": "显示 {{from}}-{{to}}/{{total}} 个服务器", - "perPage": "每页显示" + "perPage": "每页显示", + "confirmVariablesMessage": "请确保这些变量在运行环境中已正确定义。是否继续安装服务器?", + "confirmAndInstall": "确认并安装" }, "tool": { "run": "运行", diff --git a/frontend/src/utils/variableDetection.ts b/frontend/src/utils/variableDetection.ts new file mode 100644 index 0000000..4c4e834 --- /dev/null +++ b/frontend/src/utils/variableDetection.ts @@ -0,0 +1,27 @@ +// Utility function to detect ${} variables in server configurations +export const detectVariables = (payload: any): string[] => { + const variables = new Set(); + const variableRegex = /\$\{([^}]+)\}/g; + + const checkString = (str: string) => { + let match; + while ((match = variableRegex.exec(str)) !== null) { + variables.add(match[1]); + } + }; + + const checkObject = (obj: any, path: string = '') => { + if (typeof obj === 'string') { + checkString(obj); + } else if (Array.isArray(obj)) { + obj.forEach((item, index) => checkObject(item, `${path}[${index}]`)); + } else if (obj && typeof obj === 'object') { + Object.entries(obj).forEach(([key, value]) => { + checkObject(value, path ? `${path}.${key}` : key); + }); + } + }; + + checkObject(payload); + return Array.from(variables); +};