Files
getDomain/domain-web/src/views/dashboard/DashboardView.vue
Your Name bd6bcb240f debug
2026-04-17 16:17:19 +08:00

175 lines
5.2 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.
<template>
<div class="stack">
<div class="dashboard-toolbar">
<el-button plain :loading="loading" @click="loadOverview">刷新概览</el-button>
<el-switch v-model="autoRefresh" inline-prompt active-text="自动刷新" inactive-text="手动" />
<span class="updated-at">最近刷新{{ lastUpdatedAt || "暂无" }}</span>
</div>
<el-alert
v-if="loadError"
title="读取概览失败,已保留上一次成功结果。"
type="warning"
:closable="false"
show-icon
/>
<div class="stats-grid">
<PageCard v-for="item in stats" :key="item.label">
<div class="stat-item">
<span>{{ item.label }}</span>
<strong>{{ item.value }}</strong>
<small>{{ item.note }}</small>
</div>
</PageCard>
</div>
<PageCard title="第一期目标" description="先把运营最需要的控制面搬到 Web再逐步把检测执行和运维能力完全迁到 Linux。">
<el-timeline>
<el-timeline-item timestamp="当前阶段" placement="top">已创建 Web 后台API运行中心和导入/导出/筛选主链路</el-timeline-item>
<el-timeline-item timestamp="下一步" placement="top">继续补 Linux Worker 控制闭环和更完整的运行诊断</el-timeline-item>
<el-timeline-item timestamp="后续阶段" placement="top">把检测端逐步迁为 Linux Worker前后端彻底分离</el-timeline-item>
</el-timeline>
</PageCard>
</div>
</template>
<script setup lang="ts">
import { onBeforeUnmount, onMounted, ref, watch } from "vue";
import PageCard from "@/components/PageCard.vue";
import { dashboardApi } from "@/api/modules";
const loading = ref(false);
const loadError = ref(false);
const autoRefresh = ref(true);
const lastUpdatedAt = ref("");
let timer: number | null = null;
const stats = ref([
{ label: "总域名", value: "--", note: "数据库域名总量" },
{ label: "待检测", value: "--", note: "当前待处理域名" },
{ label: "黑名单", value: "--", note: "命中风险域名" },
{ label: "API", value: "离线", note: "当前 API 运行状态" },
{ label: "本机 Worker", value: "离线", note: "当前节点本机 Worker 运行状态" },
{ label: "有效执行节点", value: "离线", note: "当前集群可承担检测任务的在线节点" },
{ label: "模式", value: "--", note: "当前 Worker 运行模式" }
]);
const clearRefreshTimer = () => {
if (timer) {
window.clearInterval(timer);
timer = null;
}
};
const ensureRefreshTimer = () => {
clearRefreshTimer();
if (autoRefresh.value) {
timer = window.setInterval(() => {
loadOverview(false);
}, 10000);
}
};
const formatNow = () =>
new Date().toLocaleString("zh-CN", {
hour12: false
});
const loadOverview = async (showError = true) => {
loading.value = true;
try {
const { data } = await dashboardApi.overview();
const localWorkerValue =
data.local_worker_status === "not-applicable"
? "不承载"
: data.local_worker_status || data.worker_status;
const localWorkerNote =
data.local_worker_status === "not-applicable"
? `当前节点 ${data.node_region || "-"} / ${data.node_role || "-"},默认不承载本机 Worker`
: "当前节点本机 Worker 运行状态";
stats.value = [
{ label: "总域名", value: String(data.domains_total), note: "数据库域名总量" },
{ label: "待检测", value: String(data.pending_total), note: "当前待处理域名" },
{ label: "黑名单", value: String(data.blacklist_total), note: "命中风险域名" },
{ label: "API", value: data.api_status, note: "当前 API 运行状态" },
{ label: "本机 Worker", value: localWorkerValue, note: localWorkerNote },
{
label: "有效执行节点",
value: `${data.cluster_worker_status || "offline"} / ${data.cluster_online_worker_nodes || 0}`,
note: `可执行检测的在线节点,独立 Worker ${data.cluster_dedicated_online_worker_nodes || 0},控制面 ${data.cluster_online_control_nodes || 0}`
},
{ label: "模式", value: data.worker_mode, note: "当前 Worker 运行模式" }
];
loadError.value = false;
lastUpdatedAt.value = formatNow();
} catch {
loadError.value = true;
if (showError) {
lastUpdatedAt.value = lastUpdatedAt.value || "读取失败";
}
} finally {
loading.value = false;
}
};
watch(autoRefresh, ensureRefreshTimer);
onMounted(async () => {
await loadOverview();
ensureRefreshTimer();
});
onBeforeUnmount(clearRefreshTimer);
</script>
<style scoped lang="scss">
.stack {
display: grid;
gap: 20px;
}
.dashboard-toolbar {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
.updated-at {
color: #64748b;
font-size: 13px;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 18px;
}
.stat-item {
display: flex;
flex-direction: column;
gap: 8px;
}
.stat-item span {
color: #64748b;
}
.stat-item strong {
font-size: 32px;
color: #0f172a;
}
.stat-item small {
color: #94a3b8;
}
@media (max-width: 960px) {
.stats-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
</style>