127 lines
5.7 KiB
Vue
127 lines
5.7 KiB
Vue
<script setup>
|
||
// 系统状态:进程 / 内存(含前端采样的 60 点迷你趋势)/ 并发 / 运行时开关(只读)
|
||
// 5s 轮询;并发占用 >80% 时给告警条并联动运行记录
|
||
import { ref, computed } from 'vue'
|
||
import { useRouter } from 'vue-router'
|
||
import { useAppStore } from '@/stores/app'
|
||
import { getSystem, getAgentConfig } from '@/api/agent'
|
||
import { usePolling } from '@/composables/usePolling'
|
||
import { fmtUptime, fmtTimeFull } from '@/utils/format'
|
||
import PageHeader from '@/components/PageHeader.vue'
|
||
import EChart from '@/components/EChart.vue'
|
||
import GlassCard from '@/components/GlassCard.vue'
|
||
import KvGrid from '@/components/KvGrid.vue'
|
||
|
||
const router = useRouter()
|
||
const app = useAppStore()
|
||
const sys = ref(null)
|
||
const cfg = ref(null)
|
||
// 内存趋势采样(前端保留最近 60 个点,5s 一个 ≈ 5 分钟窗口)
|
||
const heapSamples = ref([])
|
||
|
||
async function load() {
|
||
const res = await getSystem({ silent: true })
|
||
sys.value = res.data
|
||
heapSamples.value.push(Number(res.data.heap_alloc_mb.toFixed(1)))
|
||
if (heapSamples.value.length > 60) heapSamples.value.shift()
|
||
}
|
||
usePolling(load, 5000)
|
||
|
||
// 运行时开关(低频,进页拉一次)
|
||
getAgentConfig({ silent: true }).then(res => { cfg.value = res.data }).catch(() => {})
|
||
|
||
const concUsed = computed(() => sys.value?.enhance_concurrency?.used ?? 0)
|
||
const concCap = computed(() => sys.value?.enhance_concurrency?.capacity ?? 16)
|
||
const concPercent = computed(() => Math.round((concUsed.value / Math.max(concCap.value, 1)) * 100))
|
||
|
||
// 进程卡键值对(KvGrid 数据化渲染)
|
||
const procItems = computed(() => {
|
||
const s = sys.value
|
||
if (!s) return []
|
||
return [
|
||
{ label: '启动时间', value: fmtTimeFull(s.started_at) },
|
||
{ label: '运行时长', value: fmtUptime(s.uptime_seconds), color: 'var(--ok)' },
|
||
{ label: 'Go 版本', value: s.go_version },
|
||
{ label: 'goroutines', value: s.goroutines },
|
||
{ label: 'RunLog 缓冲', value: `${s.runlog_usage?.used} / ${s.runlog_usage?.capacity}` }
|
||
]
|
||
})
|
||
// 运行时开关键值对(中文值不用等宽字体)
|
||
const switchItems = computed(() => {
|
||
const c = cfg.value
|
||
if (!c) return []
|
||
return [
|
||
{ label: 'ReAct 引擎', value: c.react?.enabled ? '开启' : '关闭', mono: false },
|
||
{ label: '医疗守卫', value: c.medical_guard?.enabled ? '开启' : '关闭', mono: false },
|
||
{ label: 'token 预算', value: c.token_budget?.enabled ? '开启' : '关闭', mono: false },
|
||
{ label: 'debug 日志', value: c.debug?.log_request_body ? '开(含请求体)' : '关', mono: false,
|
||
color: c.debug?.log_request_body ? 'var(--warn)' : undefined },
|
||
{ label: 'KB 来源', value: c.kb?.source }
|
||
]
|
||
})
|
||
|
||
const heapOption = computed(() => {
|
||
const dark = app.theme === 'dark'
|
||
const c = dark ? '#f59e0b' : '#4361ee'
|
||
return {
|
||
backgroundColor: 'transparent',
|
||
grid: { left: 36, right: 8, top: 8, bottom: 18 },
|
||
tooltip: { trigger: 'axis' },
|
||
xAxis: { type: 'category', data: heapSamples.value.map((_, i) => i), show: false },
|
||
yAxis: { type: 'value', axisLabel: { color: dark ? '#8d7c60' : '#98a1b3', fontSize: 10 }, splitLine: { lineStyle: { color: 'rgba(128,128,128,0.1)' } } },
|
||
series: [{ type: 'line', data: heapSamples.value, smooth: true, showSymbol: false, areaStyle: { opacity: .25 }, itemStyle: { color: c } }]
|
||
}
|
||
})
|
||
</script>
|
||
|
||
<template>
|
||
<div>
|
||
<PageHeader title="系统状态" desc="Go 进程运行时指标(5s 轮询,零外部依赖)" />
|
||
|
||
<!-- 并发告警(互通:直达运行记录) -->
|
||
<a-alert v-if="concPercent >= 80" type="warning" show-icon class="!mb-4"
|
||
:message="`enhance 并发已占用 ${concUsed}/${concCap}(${concPercent}%),接近限流阈值`">
|
||
<template #action>
|
||
<a-button size="small" @click="router.push('/runs')">查看运行中请求</a-button>
|
||
</template>
|
||
</a-alert>
|
||
|
||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-4 mb-4">
|
||
<!-- 进程卡 -->
|
||
<GlassCard title="进程">
|
||
<KvGrid v-if="sys" :items="procItems" />
|
||
<a-skeleton v-else active :paragraph="{ rows: 4 }" :title="false" />
|
||
</GlassCard>
|
||
|
||
<!-- 并发卡(居中环形图,非 KV 结构,仅换外壳) -->
|
||
<GlassCard title="enhance 并发信号量">
|
||
<div class="flex flex-col items-center justify-center">
|
||
<a-progress type="circle" :percent="concPercent" :size="120"
|
||
:stroke-color="concPercent >= 80 ? 'var(--err)' : concPercent >= 50 ? 'var(--warn)' : 'var(--ok)'">
|
||
<template #format><span class="mono text-sm">{{ concUsed }}/{{ concCap }}</span></template>
|
||
</a-progress>
|
||
<div class="text-xs mt-3" style="color: var(--text-3)">满载时新请求快速失败(503),PHP 端自动降级直连</div>
|
||
</div>
|
||
</GlassCard>
|
||
|
||
<!-- 运行时开关(只读) -->
|
||
<GlassCard title="运行时开关">
|
||
<template #extra><a-tag :bordered="false" color="default">只读</a-tag></template>
|
||
<KvGrid v-if="cfg" :items="switchItems" />
|
||
<a-skeleton v-else active :paragraph="{ rows: 4 }" :title="false" />
|
||
<a-button size="small" type="link" class="!px-0 mt-2" @click="router.push('/config')">完整配置 →</a-button>
|
||
</GlassCard>
|
||
</div>
|
||
|
||
<!-- 内存卡 + 趋势 -->
|
||
<GlassCard title="堆内存趋势(前端采样 5 分钟窗口)">
|
||
<template #extra>
|
||
<span v-if="sys" class="text-xs mono" style="color: var(--text-2)">
|
||
heap {{ sys.heap_alloc_mb?.toFixed(1) }} MB · OS {{ sys.sys_mb?.toFixed(0) }} MB · GC {{ sys.num_gc }} 次
|
||
</span>
|
||
</template>
|
||
<EChart :option="heapOption" height="200px" />
|
||
</GlassCard>
|
||
</div>
|
||
</template>
|