Files
nl-tcm-agent-admin/src/views/system/index.vue
2026-08-15 12:52:39 +08:00

127 lines
5.7 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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)">满载时新请求快速失败503PHP 端自动降级直连</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>