112 lines
3.1 KiB
Vue
112 lines
3.1 KiB
Vue
<template>
|
||
<div class="aksrt-page-container">
|
||
<PageHeader
|
||
title="健康检查"
|
||
desc="集中查看运行环境、数据库表、邮箱验证迁移和系统诊断状态。"
|
||
>
|
||
<template #actions>
|
||
<n-space>
|
||
<n-button quaternary @click="go('/tools/import-export')">导入导出</n-button>
|
||
<n-button quaternary @click="go('/tools/email-rollback')">邮件回滚</n-button>
|
||
</n-space>
|
||
</template>
|
||
</PageHeader>
|
||
|
||
<n-spin :show="loading" style="width: 100%; min-height: 200px;">
|
||
<n-card class="aksrt-section-card">
|
||
<div class="aksrt-section-header">
|
||
<h3>邮箱验证状态</h3>
|
||
</div>
|
||
<n-space>
|
||
<n-tag round :type="emailHealth.feature_enabled ? 'success' : 'default'">
|
||
功能开关:{{ emailHealth.feature_enabled ? '已启用' : '未启用' }}
|
||
</n-tag>
|
||
<n-tag round :type="emailHealth.rest_blocking_active ? 'warning' : 'default'">
|
||
REST 阻断:{{ emailHealth.rest_blocking_active ? '已开启' : '仅记录/关闭' }}
|
||
</n-tag>
|
||
<n-tag round type="info">
|
||
待验证用户:{{ emailHealth.pending_users }}
|
||
</n-tag>
|
||
</n-space>
|
||
</n-card>
|
||
|
||
<div class="aksrt-health-grid">
|
||
<n-card v-for="check in checkItems" :key="check.key" class="aksrt-section-card">
|
||
<div class="aksrt-section-header">
|
||
<h3>{{ check.label }}</h3>
|
||
<n-tag round :type="statusToTag(check.status)">{{ check.status }}</n-tag>
|
||
</div>
|
||
<p style="margin: 0; color: #4b5565;">{{ check.value }}</p>
|
||
</n-card>
|
||
</div>
|
||
</n-spin>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { computed, onMounted, ref } from 'vue'
|
||
import { useRouter } from 'vue-router'
|
||
import { useMessage } from 'naive-ui'
|
||
import api from '@/api'
|
||
import PageHeader from '@/components/PageHeader.vue'
|
||
|
||
const router = useRouter()
|
||
const message = useMessage()
|
||
|
||
const loading = ref(true)
|
||
const healthData = ref({})
|
||
const emailHealth = ref({
|
||
feature_enabled: false,
|
||
pending_users: 0,
|
||
rest_blocking_active: false,
|
||
})
|
||
|
||
const checkItems = computed(() => {
|
||
return Object.entries(healthData.value || {}).map(([key, value]) => ({
|
||
key,
|
||
...(value || {}),
|
||
}))
|
||
})
|
||
|
||
function go(path) {
|
||
router.push(path)
|
||
}
|
||
|
||
function extractData(response) {
|
||
return response?.data ?? response ?? {}
|
||
}
|
||
|
||
function statusToTag(status) {
|
||
if (status === 'ok') return 'success'
|
||
if (status === 'warning') return 'warning'
|
||
if (status === 'error') return 'error'
|
||
return 'info'
|
||
}
|
||
|
||
async function load() {
|
||
loading.value = true
|
||
try {
|
||
const [healthRes, emailRes] = await Promise.all([
|
||
api.getHealthCheck(),
|
||
api.getHealthEmailVerification(),
|
||
])
|
||
healthData.value = extractData(healthRes).checks || {}
|
||
emailHealth.value = extractData(emailRes)
|
||
} catch (error) {
|
||
message.error(error.message || '加载健康检查失败')
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
onMounted(load)
|
||
</script>
|
||
|
||
<style scoped>
|
||
.aksrt-health-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
||
gap: 16px;
|
||
}
|
||
</style>
|