=== 设计边界(刻意不做"编辑完整 promtail.yaml")=== 开放的是**采集项本身**的 YAML 表示(字段与 models.ScrapeJob 一一对应): name / paths / labels / encoding / json / system multiline(firstline,maxLines) / timestamp / dropIfMatch / rateLimit clients、positions、server、scrape_configs 外层仍由平台生成。 理由(真实故障面): 用户删掉 positions → 重启后从头重采,大量重复 + Loki 以 entry too far behind 拒绝整批;删掉 clients → 完全不外发。 这两类问题在页面上看不出来,只能登机器看日志,排障成本极高。 只开放采集规则,写坏了最多某条日志没采到,不会让采集器整体失效。 === 后端 === 新增 services/collector_jobs_yaml.go: RenderJobsYAML(jobs) → 带边界说明注释的 YAML(明确写"clients/positions 不在此修改") ParseJobsYAML(text) → 复用 models.ValidateScrapeJobs,与表单完全同一套校验 错误信息必须具体: YAML 语法错 → 「YAML 语法错误:yaml: line 1: did not find expected ',' or ']'」 语义错 → 「采集项「app」至少需要填写一个日志路径」 新增路由(用 /collector-jobs/* 前缀,避开与 /collectors/:id 的 gin 通配冲突): POST /api/v1/collector-jobs/render POST /api/v1/collector-jobs/parse === 前端 === 编辑弹框(主机模式)加「表单 / YAML」切换: - 切到 YAML:调 render 把当前采集项渲染出来 - 切回表单:调 parse 回填;解析失败**留在 YAML 模式**并显示原因 (绝不能悄悄丢掉用户改动) - 停留在 YAML 模式点保存:先 parse,失败则中止保存 - 编辑区 min-h 固定、等宽字体、可拖拽高度 === 测试 === TestRenderParseJobsYAMLRoundTrip: 表单→YAML→表单 无损往返,逐字段核对 (multiline.maxLines / rateLimit / dropIfMatch / timestamp / labels / system) TestParseJobsYAMLErrors:空内容、语法错、空列表、缺名称、缺路径、名称非法 → 6 种输入都给出具体错误,不是笼统"格式错误" === 实测(c11d2badce64)=== render: code=0,17 行,含边界说明 parse : code=0,1 个采集项;往返校验 name=app paths=1 multiline=True rateLimit={rate:2000,burst:4000} drop=1 json=True 错误: 「采集项「app」至少需要填写一个日志路径」 「YAML 语法错误:yaml: line 1: did not find expected ',' or ']'」 Pod = Hub latest = c11d2badce64