From 8770b9ccfec822fbe5999a7765e55e8d8f3c1e47 Mon Sep 17 00:00:00 2001 From: samanhappy Date: Sun, 30 Nov 2025 09:59:48 +0800 Subject: [PATCH] feat: Enhance Keep-Alive configuration handling (#455) --- frontend/src/components/ServerForm.tsx | 95 ++++++++++++++++++++++++++ frontend/src/types/index.ts | 6 ++ locales/en.json | 7 +- locales/fr.json | 7 +- locales/tr.json | 7 +- locales/zh.json | 7 +- src/dao/ServerDaoDbImpl.ts | 9 ++- src/db/entities/Server.ts | 3 + src/services/keepAliveService.ts | 73 ++++++++++++++++++++ src/services/mcpService.ts | 46 +++---------- src/services/sseService.ts | 54 --------------- src/types/index.ts | 1 + src/utils/migration.ts | 1 + tests/services/keepalive.test.ts | 93 ++++--------------------- 14 files changed, 234 insertions(+), 175 deletions(-) create mode 100644 src/services/keepAliveService.ts diff --git a/frontend/src/components/ServerForm.tsx b/frontend/src/components/ServerForm.tsx index 00251d4..28f04b5 100644 --- a/frontend/src/components/ServerForm.tsx +++ b/frontend/src/components/ServerForm.tsx @@ -95,6 +95,11 @@ const ServerForm = ({ undefined, }, oauth: getInitialOAuthConfig(initialData), + // KeepAlive configuration initialization + keepAlive: { + enabled: initialData?.config?.enableKeepAlive || false, + interval: initialData?.config?.keepAliveInterval || 60000, + }, // OpenAPI configuration initialization openapi: initialData && initialData.config && initialData.config.openapi @@ -151,6 +156,7 @@ const ServerForm = ({ const [isRequestOptionsExpanded, setIsRequestOptionsExpanded] = useState(false); const [isOAuthSectionExpanded, setIsOAuthSectionExpanded] = useState(false); + const [isKeepAliveSectionExpanded, setIsKeepAliveSectionExpanded] = useState(false); const [error, setError] = useState(null); const isEdit = !!initialData; @@ -377,6 +383,15 @@ const ServerForm = ({ env: Object.keys(env).length > 0 ? env : undefined, }), ...(Object.keys(options).length > 0 ? { options } : {}), + // KeepAlive configuration (only for SSE/streamable-http types) + ...(serverType === 'sse' || serverType === 'streamable-http' + ? { + enableKeepAlive: formData.keepAlive?.enabled || false, + ...(formData.keepAlive?.enabled + ? { keepAliveInterval: formData.keepAlive.interval || 60000 } + : {}), + } + : {}), }, }; @@ -1255,6 +1270,86 @@ const ServerForm = ({ )} + {/* KeepAlive Configuration - only for SSE/Streamable HTTP */} + {(serverType === 'sse' || serverType === 'streamable-http') && ( +
+
setIsKeepAliveSectionExpanded(!isKeepAliveSectionExpanded)} + > + + + {isKeepAliveSectionExpanded ? '▼' : '▶'} + +
+ + {isKeepAliveSectionExpanded && ( +
+
+ + setFormData((prev) => ({ + ...prev, + keepAlive: { + ...prev.keepAlive, + enabled: e.target.checked, + }, + })) + } + className="mr-2" + /> + +
+

+ {t( + 'server.keepAliveDescription', + 'Send periodic ping requests to maintain the connection. Useful for long-running connections that may timeout.', + )} +

+
+ + + setFormData((prev) => ({ + ...prev, + keepAlive: { + ...prev.keepAlive, + interval: parseInt(e.target.value) || 60000, + }, + })) + } + className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline form-input" + placeholder="60000" + min="5000" + max="300000" + /> +

+ {t( + 'server.keepAliveIntervalDescription', + 'Time between keep-alive pings in milliseconds (default: 60000ms = 1 minute)', + )} +

+
+
+ )} +
+ )} +