feat(filters): JSONPath field filters, operators, AST groups, fragments, and test-match

Add a field filter type reading any payload value by JSONPath with array expansion, 12 comparison operators (eq/ne/contains/startsWith/endsWith/regex/gt/gte/lt/lte/in/exists), a visual AST builder (all/any/not) in the route editor, chip-based multi-value input, a stateless POST /admin/api/test-match dry-run, and named filter fragments stored in D1 (d1_fragments, migration 0010) inlined into route ASTs on insert.
This commit is contained in:
RhenCloud 2026-08-18 12:19:08 +08:00
parent c955db03c3
commit c090281cb2
No known key found for this signature in database
GPG key ID: A574A617378C4E0B
30 changed files with 1793 additions and 422 deletions

View file

@ -153,6 +153,28 @@ const en: Dict = {
"filter.action": "Action",
"filter.branch": "Branch",
"filter.keyword": "Keyword",
"filter.field": "Field",
"filterOp.eq": "equals",
"filterOp.ne": "not equals",
"filterOp.contains": "contains",
"filterOp.startsWith": "starts with",
"filterOp.endsWith": "ends with",
"filterOp.regex": "matches regex",
"filterOp.gt": "greater than",
"filterOp.gte": "greater or equal",
"filterOp.lt": "less than",
"filterOp.lte": "less or equal",
"filterOp.in": "in",
"filterOp.exists": "exists",
"filterNode.all": "All of",
"filterNode.any": "Any of",
"filterNode.not": "not",
"filterNode.is": "is",
"filterNode.matches": "matches",
"filterNode.and": "and",
"filterNode.or": "or",
"filterNode.unwrap": "unwrap",
"filterNode.empty": "No conditions",
"routeEditor.editTitle": "Edit route",
"routeEditor.newTitle": "New route",
"routeEditor.eyebrow": "Route",
@ -219,6 +241,30 @@ const en: Dict = {
"routeEditor.errIdFormat": "ID must be a-z / 0-9 / dashes",
"routeEditor.errName": "Name is required",
"routeEditor.errChannel": "Target {n} channel ID is required",
"routeEditor.addLeaf": "Add condition",
"routeEditor.addGroup": "Add group",
"routeEditor.addNot": "Add not",
"routeEditor.remove": "Remove",
"routeEditor.op": "Operator",
"routeEditor.pathPlaceholder": "payload.path",
"routeEditor.valuesPlaceholder": "Type a value and press Enter",
"routeEditor.testMatch": "Test match",
"routeEditor.testEvent": "Event (optional)",
"routeEditor.testPayloadPlaceholder": "Paste a JSON webhook payload",
"routeEditor.testRun": "Run test",
"routeEditor.testMatched": "Matches",
"routeEditor.testNotMatched": "Does not match",
"routeEditor.testInvalidJson": "Invalid JSON",
"routeEditor.fragments": "Fragments",
"routeEditor.fragmentsEmpty": "No fragments yet",
"routeEditor.fragmentsInsert": "Insert",
"routeEditor.fragmentsDelete": "Delete",
"routeEditor.fragmentsSave": "Save as fragment",
"routeEditor.fragmentsNamePlaceholder": "Fragment name",
"routeEditor.fragmentsNameRequired": "Fragment name is required",
"routeEditor.errPath": "Field filters need a path",
"routeEditor.errValues": "Add at least one value",
"routeEditor.errGroupEmpty": "Group needs at least one condition",
"groupEditor.editTitle": "Edit group",
"groupEditor.newTitle": "New group",
"groupEditor.eyebrow": "Group",
@ -479,6 +525,28 @@ const zh: Dict = {
"filter.action": "动作",
"filter.branch": "分支",
"filter.keyword": "关键词",
"filter.field": "字段",
"filterOp.eq": "等于",
"filterOp.ne": "不等于",
"filterOp.contains": "包含",
"filterOp.startsWith": "开头是",
"filterOp.endsWith": "结尾是",
"filterOp.regex": "正则匹配",
"filterOp.gt": "大于",
"filterOp.gte": "大于等于",
"filterOp.lt": "小于",
"filterOp.lte": "小于等于",
"filterOp.in": "属于",
"filterOp.exists": "存在",
"filterNode.all": "全部满足",
"filterNode.any": "任一满足",
"filterNode.not": "非",
"filterNode.is": "是",
"filterNode.matches": "匹配",
"filterNode.and": "且",
"filterNode.or": "或",
"filterNode.unwrap": "取消非",
"filterNode.empty": "暂无条件",
"routeEditor.editTitle": "编辑路由",
"routeEditor.newTitle": "新建路由",
"routeEditor.eyebrow": "路由",
@ -544,6 +612,30 @@ const zh: Dict = {
"routeEditor.errIdFormat": "ID 只能是 a-z / 0-9 / 短横线",
"routeEditor.errName": "名称为必填项",
"routeEditor.errChannel": "第 {n} 个目标频道 ID 为必填项",
"routeEditor.addLeaf": "添加条件",
"routeEditor.addGroup": "添加分组",
"routeEditor.addNot": "添加非",
"routeEditor.remove": "删除",
"routeEditor.op": "操作符",
"routeEditor.pathPlaceholder": "payload.path",
"routeEditor.valuesPlaceholder": "输入值后回车",
"routeEditor.testMatch": "测试匹配",
"routeEditor.testEvent": "事件类型(可选)",
"routeEditor.testPayloadPlaceholder": "粘贴 JSON webhook 事件样本",
"routeEditor.testRun": "运行测试",
"routeEditor.testMatched": "匹配",
"routeEditor.testNotMatched": "不匹配",
"routeEditor.testInvalidJson": "无效的 JSON",
"routeEditor.fragments": "过滤器片段",
"routeEditor.fragmentsEmpty": "暂无片段",
"routeEditor.fragmentsInsert": "插入",
"routeEditor.fragmentsDelete": "删除",
"routeEditor.fragmentsSave": "存为片段",
"routeEditor.fragmentsNamePlaceholder": "片段名称",
"routeEditor.fragmentsNameRequired": "请填写片段名称",
"routeEditor.errPath": "字段过滤需要路径",
"routeEditor.errValues": "至少需要一个值",
"routeEditor.errGroupEmpty": "分组至少需要一个条件",
"groupEditor.editTitle": "编辑分组",
"groupEditor.newTitle": "新建分组",
"groupEditor.eyebrow": "分组",