feat: 运行期渠道冷却可见并可手动恢复
运行期冷却只存在网关进程内存里,DB 的 health_status 看不见它:渠道被踢到 30s 冷却后,管理后台仍显示 healthy,模型也照常出现在 /v1/models,但请求 返回 503 no_channel,管理员只能去翻日志。 - channel.Service 增 RuntimeState(连续失败次数 + 冷却剩余)与 ClearRuntime; Gateway 包装出 ChannelRuntime / RecoverChannel 供管理端调用。 - GET /api/v1/admin/channels 每项返回 runtime_fails 与 cooldown_seconds。 - 新增 POST /api/v1/admin/channels/:id/recover:清运行期冷却与失败计数,并把 探测写的 health_status 置回 healthy;不改 enabled,上游若真挂着,下一次探测 或再连续 3 次请求失败会重新把它踢掉。 - 渠道页健康列下方显示「冷却中 Ns」/「连续失败 n/3」,仅在冷却或探测不健康时 出现「立即恢复」按钮(卡片与桌面表格两套布局都覆盖)。 仍未处理:冷却到期是裸恢复、没有 half-open 试探;计数是进程内的,多实例各自 为战,要共享得等 Redis 那一步。
This commit is contained in:
1 parent
e865f69a41
commit
d4c5263f1e
8 files changed
+166
-2
No files matched your search
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { PhCaretDown, PhPulse, PhNotePencil, PhTrash, PhStack } from '@phosphor-icons/vue'
|
||||
import { PhCaretDown, PhPulse, PhNotePencil, PhTrash, PhStack, PhArrowsClockwise } from '@phosphor-icons/vue'
|
||||
import { http, errMsg } from '@/api/client'
|
||||
import { useToastStore } from '@/stores/toast'
|
||||
import { PROTOCOL_OPTIONS, protocolShort } from '@/lib/protocol'
|
||||
@@ -127,6 +127,30 @@ async function testChannel(ch: Channel) {
|
||||
}
|
||||
}
|
||||
|
||||
// 运行期冷却是网关进程内的状态,探测写的 health_status 看不到它,所以单独提示
|
||||
function runtimeHint(ch: Channel): string {
|
||||
if (ch.cooldown_seconds > 0) return `冷却中 ${ch.cooldown_seconds}s`
|
||||
if (ch.runtime_fails > 0) return `连续失败 ${ch.runtime_fails}/3`
|
||||
return ''
|
||||
}
|
||||
|
||||
function needRecover(ch: Channel): boolean {
|
||||
return ch.cooldown_seconds > 0 || ch.health_status !== 'healthy'
|
||||
}
|
||||
|
||||
async function recoverChannel(ch: Channel) {
|
||||
busyId.value = ch.id
|
||||
try {
|
||||
await http.post(`/admin/channels/${ch.id}/recover`)
|
||||
toast.ok(`渠道 ${ch.name} 已恢复`)
|
||||
} catch (e) {
|
||||
toast.err(errMsg(e))
|
||||
} finally {
|
||||
busyId.value = null
|
||||
await load()
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
</script>
|
||||
|
||||
@@ -159,6 +183,9 @@ onMounted(load)
|
||||
{{ ch.health_status }}
|
||||
</Badge>
|
||||
<Badge :variant="ch.enabled ? 'ok' : 'neutral'">{{ ch.enabled ? '启用' : '停用' }}</Badge>
|
||||
<Badge v-if="runtimeHint(ch)" :variant="ch.cooldown_seconds > 0 ? 'err' : 'warn'">
|
||||
{{ runtimeHint(ch) }}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
<p class="mt-2 truncate font-mono text-[11px] text-muted">{{ ch.base_url }}</p>
|
||||
@@ -167,6 +194,15 @@ onMounted(load)
|
||||
<PhPulse :size="13" />
|
||||
{{ busyId === ch.id ? '测试中…' : '测试' }}
|
||||
</button>
|
||||
<button
|
||||
v-if="needRecover(ch)"
|
||||
class="inline-flex items-center gap-1 text-xs text-accent hover:text-accent-strong"
|
||||
:disabled="busyId === ch.id"
|
||||
@click="recoverChannel(ch)"
|
||||
>
|
||||
<PhArrowsClockwise :size="13" />
|
||||
立即恢复
|
||||
</button>
|
||||
<button class="inline-flex items-center gap-1 text-xs text-accent hover:text-accent-strong" @click="toggleDrawer(ch)">
|
||||
<PhStack :size="13" />
|
||||
支持的模型 {{ expandedId === ch.id ? '▴' : '▾' }}
|
||||
@@ -228,6 +264,11 @@ onMounted(load)
|
||||
<Badge :variant="ch.health_status === 'healthy' ? 'ok' : ch.health_status === 'cooldown' ? 'err' : 'warn'">
|
||||
{{ ch.health_status }}
|
||||
</Badge>
|
||||
<div
|
||||
v-if="runtimeHint(ch)"
|
||||
class="mt-1 font-mono text-[11px]"
|
||||
:class="ch.cooldown_seconds > 0 ? 'text-err' : 'text-warn'"
|
||||
>{{ runtimeHint(ch) }}</div>
|
||||
</td>
|
||||
<td class="px-4 py-2.5 text-xs text-muted">{{ ch.enabled ? '是' : '否' }}</td>
|
||||
<td class="px-4 py-2.5 text-right">
|
||||
@@ -236,6 +277,15 @@ onMounted(load)
|
||||
<PhPulse :size="13" />
|
||||
{{ busyId === ch.id ? '测试中…' : '测试' }}
|
||||
</button>
|
||||
<button
|
||||
v-if="needRecover(ch)"
|
||||
class="inline-flex items-center gap-1 text-xs text-accent hover:text-accent-strong"
|
||||
:disabled="busyId === ch.id"
|
||||
@click="recoverChannel(ch)"
|
||||
>
|
||||
<PhArrowsClockwise :size="13" />
|
||||
立即恢复
|
||||
</button>
|
||||
<button class="inline-flex items-center gap-1 text-xs text-muted hover:text-ink" @click="openEdit(ch)">
|
||||
<PhNotePencil :size="13" />
|
||||
编辑
|
||||
|
||||
Reference in new issue
Block a user