feat(channel-monitor): request templates with snapshot apply + headers/body override
Problem:
Upstream channels can reject monitor probes based on client fingerprint
(e.g. "only Claude Code clients allowed"). The monitor had no way to
customize the outgoing request to bypass such restrictions.
Solution:
Introduce reusable request templates that carry extra_headers plus an
optional body override; monitors reference a template and receive a
snapshot copy on apply. Template edits do NOT auto-propagate — users
must click "apply to associated monitors" to refresh snapshots, so a
bad template edit cannot instantly break all production monitors.
Data model (migration 112):
- channel_monitor_request_templates: id, name, provider, description,
extra_headers jsonb, body_override_mode ('off'|'merge'|'replace'),
body_override jsonb. Unique (provider, name).
- channel_monitors: +template_id (FK, ON DELETE SET NULL), +extra_headers,
+body_override_mode, +body_override (the three runtime snapshot fields).
Checker (channel_monitor_checker.go):
- callProvider + runCheckForModel accept a CheckOptions carrying the
snapshot fields. mergeHeaders applies user headers on top of adapter
defaults (forbidden list: Host / Content-Length / Transfer-Encoding /
Connection / Content-Encoding).
- buildRequestBody:
off -> adapter default body
merge -> shallow-merge over default; per-provider deny list
(model/messages/contents) protects the challenge contract
replace -> user body verbatim
- Replace mode skips challenge validation; instead HTTP 2xx + non-empty
extracted response text = operational, empty = failed.
- 4 new unit tests cover all three modes + replace/empty-response case.
Admin API:
- /admin/channel-monitor-templates CRUD + /:id/apply (overwrite snapshot
on all template_id=id monitors, returns affected count).
- channel_monitor request/response DTOs gain the 4 new fields.
Frontend:
- channelMonitorTemplate.ts API client.
- MonitorAdvancedRequestConfig.vue shared component for headers textarea
+ body mode radio + body JSON editor; used by both template and monitor
forms.
- MonitorTemplateManagerDialog.vue: provider tabs, list/create/edit/
delete/apply, live "associated monitors" count per row.
- MonitorFiltersBar: new 模板管理 button next to 新增监控.
- MonitorFormDialog: collapsible 高级 section with template dropdown
(filtered by form.provider, clears on provider change) + embedded
AdvancedRequestConfig. Picking a template copies its fields into the
form (snapshot semantics mirrored on the client).
- i18n zh/en entries for all new copy.
chore: bump version to 0.1.114.32
This commit is contained in:
@@ -2156,7 +2156,57 @@ export default {
|
||||
},
|
||||
runResultTitle: 'Check Result',
|
||||
noMonitorsYet: 'No monitors yet',
|
||||
createFirstMonitor: 'Create your first monitor to track channel availability'
|
||||
createFirstMonitor: 'Create your first monitor to track channel availability',
|
||||
advanced: {
|
||||
section: 'Advanced (optional)',
|
||||
sectionHint: 'Customize request headers and body to bypass upstream client-detection (e.g. "only Claude Code clients allowed").',
|
||||
headers: 'Custom request headers',
|
||||
headersPlaceholder: 'User-Agent: claude-cli/1.0.83 (external, cli)\nx-app: cli\nanthropic-beta: claude-code-20250219',
|
||||
headersHint: 'One Key: Value per line; merged on top of adapter defaults (user wins). Hop-by-hop headers (Host / Content-Length / ...) are ignored.',
|
||||
headersParseError: 'Cannot parse line: {line}',
|
||||
bodyMode: 'Body handling',
|
||||
bodyModeOff: 'Default',
|
||||
bodyModeMerge: 'Merge',
|
||||
bodyModeReplace: 'Replace',
|
||||
bodyModeHintOff: 'Use the adapter default body (includes challenge validation).',
|
||||
bodyModeHintMerge: 'Shallow-merge with the default body; user fields win but model / messages / contents are protected (use Replace to change those).',
|
||||
bodyModeHintReplace: 'Use the JSON below as the complete body. Challenge validation is skipped; HTTP 2xx + non-empty response text is treated as operational.',
|
||||
bodyJson: 'Body JSON',
|
||||
bodyJsonHint: 'Parsed on blur. Empty means no override.',
|
||||
bodyJsonError: 'JSON parse failed',
|
||||
bodyJsonObjectError: 'Body must be a JSON object (no arrays or primitives)'
|
||||
},
|
||||
templateField: {
|
||||
label: 'Request template',
|
||||
none: 'No template',
|
||||
placeholder: 'Pick a template (filtered by current provider)',
|
||||
applyHint: 'Picking a template copies its headers and body to this monitor (snapshot). Later template edits are not auto-synced.'
|
||||
},
|
||||
template: {
|
||||
manageButton: 'Templates',
|
||||
managerTitle: 'Request template manager',
|
||||
createButton: 'New template',
|
||||
emptyState: 'No templates for this provider yet',
|
||||
missingName: 'Template name is required',
|
||||
createSuccess: 'Template created',
|
||||
updateSuccess: 'Template updated',
|
||||
deleteSuccess: 'Template deleted',
|
||||
applyButton: 'Apply to monitors',
|
||||
applyTooltip: 'Overwrite snapshot fields on all associated monitors',
|
||||
applyTitle: 'Apply template',
|
||||
applyConfirm: 'Apply',
|
||||
applyConfirmMessage: 'Overwrite {n} associated monitor(s) with the current configuration of "{name}"? Any local customizations on those monitors will be discarded.',
|
||||
applySuccess: 'Applied to {n} monitor(s)',
|
||||
deleteConfirm: 'Delete template "{name}"? {n} associated monitor(s) will be disassociated but keep their current snapshot and continue running.',
|
||||
associatedCount: '{n} associated monitor(s)',
|
||||
headersSummary: '{n} custom header(s)',
|
||||
form: {
|
||||
name: 'Template name',
|
||||
namePlaceholder: 'e.g. Claude Code mimicry',
|
||||
description: 'Description',
|
||||
descriptionPlaceholder: 'Optional: what this template is for, capture date, etc.'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Subscriptions
|
||||
|
||||
Reference in New Issue
Block a user