From e0d1b83d1ce0f5a626b19071f39073cdc641542b Mon Sep 17 00:00:00 2001 From: duguwanglong Date: Fri, 15 May 2026 14:06:06 +0800 Subject: [PATCH] fix(syslog): validate listener port range on save Follow-up to PR #267: the syslog config form already validated host but accepted any integer for port, allowing values such as 0, 22, or 99999 to be saved and only blow up at bind time with a confusing OS error (e.g. "[Errno 49] can't assign requested address"). - Backend SyslogConfigRequest.port now enforces ge=1, le=65535 via Pydantic so out-of-range ports are rejected with HTTP 422 before the config is persisted or the listener is restarted. - IntegrationTab.tsx adds an inline validator that strictly matches a numeric string (rejecting "5140abc", "3.14", "-1", etc.) and the 1..65535 range; the input shows a red border with a localized hint and the save button is disabled while the value is invalid. - extractErrorMessage now flattens Pydantic 422 detail arrays ([{loc, msg, type}, ...]) into a readable "; "-joined string so scripted callers (or any caller that bypasses the UI validator) see the actual validation message instead of "[object Object]". - Adds zh-CN/en-US copy for detail.run.syslogPortError. Co-authored-by: Cursor --- flocks/server/routes/workflow.py | 2 +- webui/src/locales/en-US/workflow.json | 1 + webui/src/locales/zh-CN/workflow.json | 1 + .../WorkflowDetail/tabs/IntegrationTab.tsx | 42 ++++++++++++++++++- webui/src/utils/error.ts | 13 +++++- 5 files changed, 55 insertions(+), 4 deletions(-) diff --git a/flocks/server/routes/workflow.py b/flocks/server/routes/workflow.py index 77ea94825..97a78f8fe 100644 --- a/flocks/server/routes/workflow.py +++ b/flocks/server/routes/workflow.py @@ -1436,7 +1436,7 @@ class SyslogConfigRequest(BaseModel): enabled: bool = False protocol: str = "udp" host: str = "0.0.0.0" - port: int = 5140 + port: int = Field(5140, ge=1, le=65535, description="Listener port (1-65535)") msg_format: str = Field("auto", alias="format") input_key: str = Field("syslog_message", alias="inputKey") diff --git a/webui/src/locales/en-US/workflow.json b/webui/src/locales/en-US/workflow.json index 303565d56..5f11d0227 100644 --- a/webui/src/locales/en-US/workflow.json +++ b/webui/src/locales/en-US/workflow.json @@ -214,6 +214,7 @@ "syslogFormat": "Parse format", "syslogInputKey": "Inputs key", "syslogHint": "When enabled, Flocks listens for syslog on the given address/port and passes parsed payloads to workflow inputs (default key: syslog_message).", + "syslogPortError": "Invalid port: must be an integer between 1 and 65535", "syslogActive": "Listening", "historySection": "Execution History", "noHistory": "No execution records", diff --git a/webui/src/locales/zh-CN/workflow.json b/webui/src/locales/zh-CN/workflow.json index 2524d9925..a87304085 100644 --- a/webui/src/locales/zh-CN/workflow.json +++ b/webui/src/locales/zh-CN/workflow.json @@ -214,6 +214,7 @@ "syslogFormat": "解析格式", "syslogInputKey": "Inputs 键名", "syslogHint": "开启后 Flocks 在指定地址/端口接收 syslog,解析结果写入工作流 inputs(默认键名 syslog_message)。", + "syslogPortError": "端口范围无效,请输入 1 ~ 65535 之间的整数", "syslogActive": "监听中", "historySection": "执行历史", "noHistory": "暂无执行记录", diff --git a/webui/src/pages/WorkflowDetail/tabs/IntegrationTab.tsx b/webui/src/pages/WorkflowDetail/tabs/IntegrationTab.tsx index 71e8666a7..b98bd9e18 100644 --- a/webui/src/pages/WorkflowDetail/tabs/IntegrationTab.tsx +++ b/webui/src/pages/WorkflowDetail/tabs/IntegrationTab.tsx @@ -322,6 +322,7 @@ function SyslogSection({ workflowId }: { workflowId: string }) { // falsely showing the listener as active. const [listener, setListener] = useState(null); const [saveError, setSaveError] = useState(''); + const [portError, setPortError] = useState(''); const refreshStatus = useCallback(async () => { try { @@ -387,7 +388,32 @@ function SyslogSection({ workflowId }: { workflowId: string }) { return () => window.clearInterval(handle); }, [isBinding, refreshStatus]); + const validatePort = (value: string): boolean => { + const trimmed = value.trim(); + if (!/^\d+$/.test(trimmed)) { + setPortError(t('detail.run.syslogPortError')); + return false; + } + const num = Number.parseInt(trimmed, 10); + if (num < 1 || num > 65535) { + setPortError(t('detail.run.syslogPortError')); + return false; + } + setPortError(''); + return true; + }; + + const handlePortChange = (value: string) => { + setPort(value); + if (value !== '') { + validatePort(value); + } else { + setPortError(''); + } + }; + const handleSave = async () => { + if (!validatePort(port)) return; setSaving(true); setSaved(false); setSaveError(''); @@ -466,7 +492,19 @@ function SyslogSection({ workflowId }: { workflowId: string }) { {inputField(t('detail.run.syslogHost'), host, setHost, '0.0.0.0')} - {inputField(t('detail.run.syslogPort'), port, setPort, '5140')} +
+ + handlePortChange(e.target.value)} + placeholder="5140" + className={`w-full text-xs border rounded-lg px-3 py-1.5 focus:outline-none focus:ring-1 ${portError ? 'border-red-400 focus:ring-red-500' : 'border-gray-200 focus:ring-red-500'}`} + /> + {portError && ( +

{portError}

+ )} +