mosdns/web-ui/src/views/DashboardView.vue

259 lines
7.0 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 lang="ts">
import { onMounted, ref } from 'vue'
import { useServerStore } from '@/stores/server'
import { cacheApi } from '@/api/cache'
import { serverApi } from '@/api/server'
import { ElMessage, ElMessageBox, ElNotification } from 'element-plus'
import { Refresh, Delete, RefreshRight, Upload } from '@element-plus/icons-vue'
const serverStore = useServerStore()
const isReloading = ref(false)
const handleFlushCache = async () => {
try {
await ElMessageBox.confirm('确定要清空缓存吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
await cacheApi.flush()
ElMessage.success('缓存清空成功')
} catch (error) {
if (error !== 'cancel') {
console.error(error)
}
}
}
const handleReloadConfig = async () => {
try {
await ElMessageBox.confirm(
'热加载将重新加载配置文件旧插件会被关闭新插件会被加载。DNS 服务不会中断。确定要继续吗?',
'🔄 热加载配置',
{
confirmButtonText: '确定热加载',
cancelButtonText: '取消',
type: 'warning',
}
)
isReloading.value = true
const response = await serverApi.reloadConfig()
if (response.success && response.data) {
ElNotification({
title: '✅ 热加载成功',
message: `已加载 ${response.data.plugin_count} 个插件\n配置文件: ${response.data.config_path}\n重载时间: ${response.data.reload_time}`,
type: 'success',
duration: 5000,
})
// 刷新数据
await refreshData()
} else {
ElMessage.success(response.message || '热加载成功')
}
} catch (error: any) {
if (error !== 'cancel') {
console.error('热加载失败:', error)
ElMessage.error(error.response?.data?.error || error.message || '热加载失败')
}
} finally {
isReloading.value = false
}
}
const handleRestart = async () => {
try {
await ElMessageBox.confirm('确定要重启服务吗?服务将在 3 秒后重启。', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
await serverStore.restart()
ElMessage.success('重启请求已发送')
} catch (error) {
if (error !== 'cancel') {
console.error(error)
}
}
}
const refreshData = async () => {
await serverStore.fetchServerInfo()
await serverStore.fetchStats()
ElMessage.success('数据已刷新')
}
onMounted(async () => {
await serverStore.fetchServerInfo()
await serverStore.fetchStats()
})
</script>
<template>
<div class="dashboard">
<el-row :gutter="20">
<!-- 服务状态卡片 -->
<el-col :xs="24" :sm="12" :lg="8">
<el-card shadow="hover">
<template #header>
<div class="card-header">
<span>📊 服务状态</span>
</div>
</template>
<div class="stat-item">
<span class="stat-label">运行状态:</span>
<el-tag :type="serverStore.isOnline ? 'success' : 'danger'">
{{ serverStore.serverInfo?.status || '-' }}
</el-tag>
</div>
<div class="stat-item">
<span class="stat-label">运行时间:</span>
<span class="stat-value">{{ serverStore.uptime }}</span>
</div>
<div class="stat-item">
<span class="stat-label">DNS 端口:</span>
<span class="stat-value">
{{ serverStore.serverInfo?.dns_ports?.join(', ') || '未检测到' }}
</span>
</div>
<div class="stat-item">
<span class="stat-label">API 地址:</span>
<span class="stat-value">{{ serverStore.serverInfo?.api_address || '-' }}</span>
</div>
</el-card>
</el-col>
<!-- 查询统计卡片 -->
<el-col :xs="24" :sm="12" :lg="8">
<el-card shadow="hover">
<template #header>
<div class="card-header">
<span>📈 查询统计</span>
</div>
</template>
<div class="stat-item">
<span class="stat-label">总查询数:</span>
<span class="stat-value">{{ serverStore.stats?.totalQueries?.toLocaleString() || '-' }}</span>
</div>
<div class="stat-item">
<span class="stat-label">缓存命中:</span>
<span class="stat-value">{{ serverStore.stats?.cacheHits?.toLocaleString() || '-' }}</span>
</div>
<div class="stat-item">
<span class="stat-label">平均响应:</span>
<span class="stat-value">
{{ serverStore.stats?.avgResponseTime ? `${serverStore.stats.avgResponseTime}ms` : '-' }}
</span>
</div>
</el-card>
</el-col>
<!-- 快速操作卡片 -->
<el-col :xs="24" :sm="24" :lg="8">
<el-card shadow="hover">
<template #header>
<div class="card-header">
<span> 快速操作</span>
</div>
</template>
<div class="button-group">
<el-button type="primary" :icon="Refresh" @click="refreshData">刷新数据</el-button>
<el-button
type="success"
:icon="RefreshRight"
:loading="isReloading"
@click="handleReloadConfig"
>
{{ isReloading ? '热加载中...' : '🔄 热加载配置' }}
</el-button>
<el-button type="warning" :icon="Delete" @click="handleFlushCache">清空缓存</el-button>
<el-button type="danger" @click="handleRestart">重启服务</el-button>
</div>
<div class="tip-info">
<el-alert
title="💡 提示热加载可以在不重启服务的情况下重新加载配置DNS服务不会中断"
type="info"
:closable="false"
show-icon
style="margin-top: 15px;"
/>
</div>
</el-card>
</el-col>
</el-row>
</div>
</template>
<style scoped>
.dashboard {
width: 100%;
min-height: calc(100vh - 160px);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
font-weight: 600;
font-size: 16px;
}
:deep(.el-card) {
height: 100%;
min-height: 280px;
}
:deep(.el-card__body) {
padding: 20px;
}
.stat-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 0;
border-bottom: 1px solid #f0f0f0;
}
.stat-item:last-child {
border-bottom: none;
}
.stat-label {
color: #909399;
font-size: 15px;
}
.stat-value {
color: #303133;
font-weight: 600;
font-size: 15px;
}
.button-group {
display: flex;
flex-direction: column;
gap: 16px;
}
.button-group .el-button {
width: 100%;
height: 44px;
font-size: 15px;
}
/* 响应式调整 */
@media (max-width: 992px) {
.dashboard {
min-height: auto;
}
:deep(.el-card) {
margin-bottom: 16px;
min-height: auto;
}
}
</style>