Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion flocks/server/routes/workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand Down
1 change: 1 addition & 0 deletions webui/src/locales/en-US/workflow.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions webui/src/locales/zh-CN/workflow.json
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@
"syslogFormat": "解析格式",
"syslogInputKey": "Inputs 键名",
"syslogHint": "开启后 Flocks 在指定地址/端口接收 syslog,解析结果写入工作流 inputs(默认键名 syslog_message)。",
"syslogPortError": "端口范围无效,请输入 1 ~ 65535 之间的整数",
"syslogActive": "监听中",
"historySection": "执行历史",
"noHistory": "暂无执行记录",
Expand Down
42 changes: 40 additions & 2 deletions webui/src/pages/WorkflowDetail/tabs/IntegrationTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,7 @@ function SyslogSection({ workflowId }: { workflowId: string }) {
// falsely showing the listener as active.
const [listener, setListener] = useState<SyslogListenerStatus | null>(null);
const [saveError, setSaveError] = useState<string>('');
const [portError, setPortError] = useState<string>('');

const refreshStatus = useCallback(async () => {
try {
Expand Down Expand Up @@ -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('');
Expand Down Expand Up @@ -466,7 +492,19 @@ function SyslogSection({ workflowId }: { workflowId: string }) {
</select>
</div>
{inputField(t('detail.run.syslogHost'), host, setHost, '0.0.0.0')}
{inputField(t('detail.run.syslogPort'), port, setPort, '5140')}
<div>
<label className="block text-xs text-gray-500 mb-1">{t('detail.run.syslogPort')}</label>
<input
type="text"
value={port}
onChange={e => 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 && (
<p className="text-xs text-red-500 mt-1">{portError}</p>
)}
</div>
<div>
<label className="block text-xs text-gray-500 mb-1">{t('detail.run.syslogFormat')}</label>
<select
Expand All @@ -484,7 +522,7 @@ function SyslogSection({ workflowId }: { workflowId: string }) {
<button
type="button"
onClick={handleSave}
disabled={saving}
disabled={saving || !!portError}
className="w-full flex items-center justify-center gap-2 py-2 border border-gray-200 text-gray-600 text-xs font-medium rounded-lg hover:bg-gray-50 disabled:opacity-60 transition-colors"
>
{saving ? (
Expand Down
13 changes: 12 additions & 1 deletion webui/src/utils/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,24 @@
* Standard FastAPI/Starlette errors use:
* { "detail": "..." }
*
* Pydantic validation errors (HTTP 422) use a list shape:
* { "detail": [{ "loc": [...], "msg": "...", "type": "..." }, ...] }
*
* This helper checks all known fields before falling back to err.message.
*/
export function extractErrorMessage(err: unknown, fallback = '操作失败'): string {
if (!err) return fallback;
const e = err as any;
const detail = e?.response?.data?.detail;
if (Array.isArray(detail)) {
const msgs = detail
.map((d: any) => (typeof d === 'string' ? d : d?.msg || JSON.stringify(d)))
.filter(Boolean);
if (msgs.length > 0) return msgs.join('; ');
} else if (typeof detail === 'string' && detail) {
return detail;
}
return (
e?.response?.data?.detail ||
e?.response?.data?.message ||
e?.message ||
fallback
Expand Down