feat(monitor): 30-day raw retention + timeline 4-tier style + CC template seed + JSON format button

- History retention 1d → 30d(60s × 30d ≈ 43200 行/model,PG 无压力);
  ComputeAvailability* 不再 UNION rollup 表,直接扫 histories 精度更高。
- Timeline bar 四级高度+颜色双重编码:operational 高+绿 / degraded 中+黄 /
  failed+error 短+红 / 未测试 很短+灰。
- migration 113 seed「Claude Code 伪装」模板(ON CONFLICT DO NOTHING)。
  user_id 用 legacy 格式(user_<64hex>_account_<uuid>_session_<uuid>),
  避免新版 JSON 字符串内嵌 JSON 在编辑器里一长串 \" 难读。
- MonitorAdvancedRequestConfig 加「格式化」按钮 + white-space:pre
  让 body textarea 对长字符串不压扁。
This commit is contained in:
erio
2026-04-21 15:24:48 +08:00
parent 6925ac25c4
commit a7415d4d2e
7 changed files with 102 additions and 64 deletions
@@ -38,12 +38,24 @@
<!-- Body JSON (仅当 mode != off) -->
<div v-if="bodyOverrideMode !== 'off'">
<label class="input-label">{{ t('admin.channelMonitor.advanced.bodyJson') }}</label>
<div class="mb-1 flex items-center justify-between">
<label class="input-label !mb-0">{{ t('admin.channelMonitor.advanced.bodyJson') }}</label>
<button
type="button"
class="text-xs text-primary-600 hover:underline disabled:cursor-not-allowed disabled:text-gray-400 disabled:no-underline dark:text-primary-400"
:disabled="!bodyText.trim()"
@click="formatBody"
>
{{ t('admin.channelMonitor.advanced.bodyJsonFormat') }}
</button>
</div>
<textarea
v-model="bodyText"
rows="8"
rows="10"
:placeholder="bodyPlaceholder"
class="input font-mono text-xs"
style="white-space: pre; overflow-wrap: normal; overflow-x: auto;"
spellcheck="false"
@blur="commitBody"
/>
<p v-if="bodyError" class="mt-1 text-xs text-red-500">{{ bodyError }}</p>
@@ -158,6 +170,25 @@ function commitBody() {
}
}
function formatBody() {
const trimmed = bodyText.value.trim()
if (trimmed === '') return
try {
const parsed = JSON.parse(trimmed)
bodyText.value = JSON.stringify(parsed, null, 2)
bodyError.value = ''
// 同步把校验过的对象提交,避免格式化后焦点未移走时父组件读到旧值
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
emit('update:bodyOverride', parsed as Record<string, unknown>)
}
} catch (e) {
bodyError.value =
t('admin.channelMonitor.advanced.bodyJsonError') +
': ' +
(e instanceof Error ? e.message : String(e))
}
}
function serializeBody(body: Record<string, unknown> | null): string {
if (!body || Object.keys(body).length === 0) return ''
return JSON.stringify(body, null, 2)
@@ -59,19 +59,21 @@ interface Bar {
title: string
}
// 4 级高度 + 颜色双重编码:高=好+绿,短=坏+红,灰=未测试。
// 长绿(正常) > 中黄(降级) > 短红(失败/系统错误) > 很短灰(未测试)。
const STATUS_HEIGHT: Record<string, number> = {
operational: 100,
degraded: 70,
failed: 55,
degraded: 65,
failed: 35,
error: 35,
empty: 20,
empty: 15,
}
const STATUS_COLOR: Record<string, string> = {
operational: 'bg-emerald-500',
degraded: 'bg-amber-500',
failed: 'bg-red-500',
error: 'bg-gray-400 dark:bg-dark-500',
error: 'bg-red-500',
empty: 'bg-gray-300 dark:bg-dark-600',
}