This commit is contained in:
Your Name
2026-04-16 13:05:07 +08:00
commit 32efff1670
99 changed files with 9974 additions and 0 deletions

View File

@@ -0,0 +1,183 @@
<template>
<PageCard title="检测控制" description="当前已接入真实状态读取和本地 Worker 启停,后续 Linux 版会沿用同一页接 systemd。">
<div class="actions">
<el-button type="primary" :loading="actionLoading === 'start'" @click="invoke('start')">启动检测</el-button>
<el-button :loading="actionLoading === 'stop'" @click="invoke('stop')">停止检测</el-button>
<el-button plain :loading="loading" @click="loadStatus">刷新状态</el-button>
<el-switch v-model="autoRefresh" inline-prompt active-text="自动刷新" inactive-text="手动" />
</div>
<el-alert
v-if="lastAction.message"
:title="`${lastAction.label}${lastAction.message}`"
:type="lastAction.type"
:closable="false"
show-icon
class="action-alert"
/>
<el-descriptions :column="2" border>
<el-descriptions-item label="Worker 在线">{{ status.worker_online ? "是" : "否" }}</el-descriptions-item>
<el-descriptions-item label="运行模式">{{ status.worker_mode || "windows-local" }}</el-descriptions-item>
<el-descriptions-item label="Worker 服务">{{ status.worker_service_name || "-" }}</el-descriptions-item>
<el-descriptions-item label="API 服务">{{ status.api_service_name || "-" }}</el-descriptions-item>
<el-descriptions-item label="检测进程数">{{ status.worker_process_count }}</el-descriptions-item>
<el-descriptions-item label="线程数">{{ status.thread_count }}</el-descriptions-item>
<el-descriptions-item label="代理启用">{{ status.proxy_enable ? "是" : "否" }}</el-descriptions-item>
<el-descriptions-item label="允许直连">{{ status.allow_direct ? "是" : "否" }}</el-descriptions-item>
<el-descriptions-item label="代理池数量">{{ status.proxy_pool_count }}</el-descriptions-item>
<el-descriptions-item label="可用代理数">{{ status.available_proxy_count }}</el-descriptions-item>
<el-descriptions-item label="最近 Worker 日志时间">{{ status.last_worker_log_time || "暂无" }}</el-descriptions-item>
<el-descriptions-item label="最近启动时间">{{ status.worker_latest_start_time || "暂无" }}</el-descriptions-item>
</el-descriptions>
<el-alert
v-if="status.recent_warning"
type="warning"
:closable="false"
show-icon
:title="status.recent_warning"
style="margin-top: 16px"
/>
<el-alert
v-if="status.worker_runtime_message"
type="info"
:closable="false"
show-icon
:title="status.worker_runtime_message"
style="margin-top: 12px"
/>
<el-row :gutter="12" style="margin-top: 16px">
<el-col :xs="12" :sm="8" :md="4"><el-statistic title="待检测" :value="status.progress.pending || 0" /></el-col>
<el-col :xs="12" :sm="8" :md="4"><el-statistic title="检测中" :value="status.progress.running || 0" /></el-col>
<el-col :xs="12" :sm="8" :md="4"><el-statistic title="检测完成" :value="status.progress.completed || 0" /></el-col>
<el-col :xs="12" :sm="8" :md="4"><el-statistic title="黑名单" :value="status.progress.blacklisted || 0" /></el-col>
<el-col :xs="12" :sm="8" :md="4"><el-statistic title="检测失败" :value="status.progress.failed || 0" /></el-col>
</el-row>
</PageCard>
</template>
<script setup lang="ts">
import { onBeforeUnmount, onMounted, ref, watch } from "vue";
import { ElMessage } from "element-plus";
import PageCard from "@/components/PageCard.vue";
import { detectApi } from "@/api/modules";
const status = ref({
worker_online: false,
worker_mode: "windows-local",
worker_service_name: "",
api_service_name: "",
worker_process_count: 0,
worker_latest_start_time: "",
worker_runtime_message: "",
thread_count: 0,
proxy_enable: false,
allow_direct: false,
proxy_pool_count: 0,
available_proxy_count: 0,
last_worker_log_time: "",
recent_warning: "",
progress: {
pending: 0,
running: 0,
completed: 0,
failed: 0,
blacklisted: 0
}
});
const autoRefresh = ref(true);
const loading = ref(false);
const actionLoading = ref<"" | "start" | "stop">("");
const lastAction = ref({
label: "",
message: "",
type: "success" as "success" | "warning" | "info" | "error"
});
let timer: number | null = null;
const clearRefreshTimer = () => {
if (timer) {
window.clearInterval(timer);
timer = null;
}
};
const ensureRefreshTimer = () => {
clearRefreshTimer();
if (autoRefresh.value) {
timer = window.setInterval(() => {
loadStatus(false);
}, 10000);
}
};
const loadStatus = async (showError = true) => {
loading.value = true;
try {
const response = await detectApi.status();
status.value = response.data;
} catch {
if (showError) {
ElMessage.error("读取检测状态失败");
}
} finally {
loading.value = false;
}
};
const invoke = async (action: "start" | "stop") => {
actionLoading.value = action;
try {
const response = action === "start" ? await detectApi.start() : await detectApi.stop();
ElMessage.success(response.message);
lastAction.value = {
label: action === "start" ? "检测启动" : "检测停止",
message: response.message,
type: "success"
};
const pollDelaySeconds = Number(response.data?.poll_after_seconds || 2);
if (response.data?.refresh_status) {
window.setTimeout(() => {
loadStatus(false);
}, pollDelaySeconds * 1000);
return;
}
await loadStatus(false);
} catch (error: any) {
const message = error?.message || `${action === "start" ? "启动" : "停止"}操作失败`;
lastAction.value = {
label: action === "start" ? "检测启动" : "检测停止",
message,
type: "error"
};
ElMessage.error(message);
} finally {
actionLoading.value = "";
}
};
watch(autoRefresh, ensureRefreshTimer);
onMounted(async () => {
await loadStatus();
ensureRefreshTimer();
});
onBeforeUnmount(clearRefreshTimer);
</script>
<style scoped>
.actions {
display: flex;
gap: 12px;
margin-bottom: 16px;
flex-wrap: wrap;
}
.action-alert {
margin-bottom: 16px;
}
</style>