debug
This commit is contained in:
261
src/views/site/domainImportFailedQueueDialog.vue
Normal file
261
src/views/site/domainImportFailedQueueDialog.vue
Normal file
@@ -0,0 +1,261 @@
|
||||
<template>
|
||||
<el-dialog v-model="visible" title="导入后探测失败队列" width="1100px" draggable>
|
||||
<div class="failed-queue-dialog">
|
||||
<div class="failed-queue-dialog__toolbar">
|
||||
<el-button type="primary" plain @click="loadQueue">刷新</el-button>
|
||||
<el-button type="success" plain :loading="selfHealingLoading" @click="runSelfHealing">后台执行自动修复</el-button>
|
||||
<el-button type="danger" plain :loading="rerunLoading" @click="runQueueRerun">后台执行整批重跑</el-button>
|
||||
<el-button type="warning" plain :loading="remediationLoading" @click="runQueueRemediation">后台执行补料候选</el-button>
|
||||
<el-button v-if="rerunAllCommand" type="warning" plain @click="showRerunAllCommand">整批重跑命令</el-button>
|
||||
<el-button type="info" plain @click="openSelfHealingDialog">最近自动修复记录</el-button>
|
||||
<el-button type="info" plain @click="openRerunDialog">最近重跑记录</el-button>
|
||||
<el-button type="info" plain @click="openRemediationDialog">最近补料记录</el-button>
|
||||
</div>
|
||||
<el-alert
|
||||
title="这里是导入成功后,自动页面探测仍未通过的 host"
|
||||
description="它不代表域名没有入库,而是代表首页、搜索、详情、播放这条页面链路还没有探通。"
|
||||
type="warning"
|
||||
show-icon
|
||||
:closable="false"
|
||||
/>
|
||||
<div class="failed-queue-dialog__summary">
|
||||
<span>当前探测失败:{{ queueSummary.active_count || healthTrend.active_count || healthTrend.queue_count || 0 }}</span>
|
||||
<span>已自动消退:{{ queueSummary.resolved_count || healthTrend.resolved_count || 0 }}</span>
|
||||
<span>重跑记录:{{ healthTrend.rerun_runs_count || 0 }}</span>
|
||||
<span>补料记录:{{ healthTrend.remediation_runs_count || 0 }}</span>
|
||||
<span>{{ trendLabel() }}</span>
|
||||
</div>
|
||||
<div class="failed-queue-dialog__summary">
|
||||
<span>重跑消退:{{ queueSummary.resolved_by_rerun_count || healthTrend.resolved_by_rerun_count || 0 }}</span>
|
||||
<span>补料消退:{{ queueSummary.resolved_by_remediation_count || healthTrend.resolved_by_remediation_count || 0 }}</span>
|
||||
</div>
|
||||
<div v-if="resolvedPreview().length" class="failed-queue-dialog__resolved">
|
||||
<span class="failed-queue-dialog__resolved-label">最近自动消退:</span>
|
||||
<span>{{ resolvedPreview().join(" / ") }}</span>
|
||||
</div>
|
||||
<el-table v-loading="loading" :data="rows" border>
|
||||
<el-table-column prop="host" label="Host" min-width="180" />
|
||||
<el-table-column prop="run_id" label="Run ID" min-width="160" />
|
||||
<el-table-column prop="failed_stage" label="失败阶段" width="100" />
|
||||
<el-table-column label="失败明细" min-width="260">
|
||||
<template #default="{ row }">
|
||||
<span>
|
||||
{{ formatFailedChecks(row) }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="search_keyword" label="搜索词样本" min-width="160" />
|
||||
<el-table-column label="补料候选" min-width="220">
|
||||
<template #default="{ row }">
|
||||
<span>
|
||||
{{ row.remediation_plan?.title || "-" }} / {{ row.remediation_plan?.summary || "-" }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="updated_at" label="更新时间" min-width="180" />
|
||||
<el-table-column label="操作" width="320" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button v-if="row.summary_html_path" link type="primary" @click="openPublicPath(row.summary_html_path)">HTML摘要</el-button>
|
||||
<el-button v-if="row.summary_json_path" link type="info" @click="openPublicPath(row.summary_json_path)">JSON摘要</el-button>
|
||||
<el-button v-if="row.rerun_command" link type="warning" @click="showCommand('单条重跑命令', row.rerun_command)">重跑命令</el-button>
|
||||
<el-button v-if="row.sample_command" link type="success" @click="showCommand('自动样本命令', row.sample_command)">样本命令</el-button>
|
||||
<el-button v-if="row.remediation_command" link type="danger" @click="showCommand('补料候选命令', row.remediation_command)">补料命令</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</el-dialog>
|
||||
<DomainImportSelfHealingDialog ref="selfHealingDialogRef" />
|
||||
<DomainImportRerunDialog ref="rerunDialogRef" />
|
||||
<DomainImportRemediationDialog ref="remediationDialogRef" />
|
||||
<DomainImportTaskConsoleDialog ref="taskConsoleDialogRef" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import { SiteList } from "@/api/interface/site/list";
|
||||
import { getDomainImportFailedQueue, getDomainImportHealthTrend, runDomainImportFailedQueue, runDomainImportFailureRemediation, runDomainImportSelfHealing } from "@/api/modules/site/list";
|
||||
import DomainImportSelfHealingDialog from "./domainImportSelfHealingDialog.vue";
|
||||
import DomainImportRerunDialog from "./domainImportRerunDialog.vue";
|
||||
import DomainImportRemediationDialog from "./domainImportRemediationDialog.vue";
|
||||
import DomainImportTaskConsoleDialog from "./domainImportTaskConsoleDialog.vue";
|
||||
|
||||
const BASE_URL = import.meta.env.VITE_API_URL as string;
|
||||
const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
const selfHealingLoading = ref(false);
|
||||
const rerunLoading = ref(false);
|
||||
const remediationLoading = ref(false);
|
||||
const rows = ref<SiteList.DomainImportFailedQueueItem[]>([]);
|
||||
const queueSummary = ref<SiteList.DomainImportFailedQueueData>({});
|
||||
const rerunAllCommand = ref("");
|
||||
const selfHealingDialogRef = ref();
|
||||
const rerunDialogRef = ref();
|
||||
const remediationDialogRef = ref();
|
||||
const taskConsoleDialogRef = ref();
|
||||
const healthTrend = ref<SiteList.DomainImportHealthTrendData>({});
|
||||
|
||||
const formatFailedChecks = (row: SiteList.DomainImportFailedQueueItem) => {
|
||||
return (row.failed_checks || [])
|
||||
.slice(0, 2)
|
||||
.map(item => `${item?.stage || "-"}:${item?.detail || "-"}`)
|
||||
.join(" | ") || "-";
|
||||
};
|
||||
|
||||
const openPublicPath = (path?: string) => {
|
||||
if (!path) {
|
||||
return;
|
||||
}
|
||||
window.open(`${BASE_URL}/${String(path).replace(/^\/+/, "")}`, "_blank", "noopener");
|
||||
};
|
||||
|
||||
const showCommand = async (title: string, command?: string) => {
|
||||
if (!command) {
|
||||
return;
|
||||
}
|
||||
await ElMessageBox.alert(`<code>${command}</code>`, title, {
|
||||
dangerouslyUseHTMLString: true,
|
||||
confirmButtonText: "知道了"
|
||||
});
|
||||
};
|
||||
|
||||
const showRerunAllCommand = async () => {
|
||||
await showCommand("整批重跑命令", rerunAllCommand.value);
|
||||
};
|
||||
|
||||
const openRerunDialog = () => {
|
||||
rerunDialogRef.value?.open();
|
||||
};
|
||||
|
||||
const openRemediationDialog = () => {
|
||||
remediationDialogRef.value?.open();
|
||||
};
|
||||
|
||||
const openSelfHealingDialog = () => {
|
||||
selfHealingDialogRef.value?.open();
|
||||
};
|
||||
|
||||
const trendLabel = () => {
|
||||
const label = healthTrend.value.failure_trend?.label || "flat";
|
||||
if (label === "improving") return "失败趋势改善中";
|
||||
if (label === "worsening") return "失败趋势上升";
|
||||
return "失败趋势持平";
|
||||
};
|
||||
|
||||
const resolvedPreview = () => {
|
||||
return queueSummary.value.resolved_hosts_preview || healthTrend.value.resolved_hosts_preview || [];
|
||||
};
|
||||
|
||||
const runQueueRerun = async () => {
|
||||
rerunLoading.value = true;
|
||||
try {
|
||||
const res = await runDomainImportFailedQueue({ limit: 50 });
|
||||
const job = res.data.job;
|
||||
if (!job?.job_id) {
|
||||
throw new Error("job_id missing");
|
||||
}
|
||||
ElMessage.success("重跑任务已创建");
|
||||
taskConsoleDialogRef.value?.open("失败队列重跑任务", job.job_id, async () => {
|
||||
await loadQueue();
|
||||
openRerunDialog();
|
||||
});
|
||||
} catch (_error) {
|
||||
ElMessage.error("执行失败队列重跑失败");
|
||||
} finally {
|
||||
rerunLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const runSelfHealing = async () => {
|
||||
selfHealingLoading.value = true;
|
||||
try {
|
||||
const res = await runDomainImportSelfHealing();
|
||||
const job = res.data.job;
|
||||
if (!job?.job_id) {
|
||||
throw new Error("job_id missing");
|
||||
}
|
||||
ElMessage.success("自动修复任务已创建");
|
||||
taskConsoleDialogRef.value?.open("自动修复任务", job.job_id, async () => {
|
||||
await loadQueue();
|
||||
openSelfHealingDialog();
|
||||
});
|
||||
} catch (_error) {
|
||||
ElMessage.error("执行自动修复失败");
|
||||
} finally {
|
||||
selfHealingLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const runQueueRemediation = async () => {
|
||||
remediationLoading.value = true;
|
||||
try {
|
||||
const res = await runDomainImportFailureRemediation({ limit: 50 });
|
||||
const job = res.data.job;
|
||||
if (!job?.job_id) {
|
||||
throw new Error("job_id missing");
|
||||
}
|
||||
ElMessage.success("补料任务已创建");
|
||||
taskConsoleDialogRef.value?.open("失败补料候选任务", job.job_id, async () => {
|
||||
await loadQueue();
|
||||
openRemediationDialog();
|
||||
});
|
||||
} catch (_error) {
|
||||
ElMessage.error("执行失败队列补料候选失败");
|
||||
} finally {
|
||||
remediationLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const loadQueue = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const [queueRes, trendRes] = await Promise.all([
|
||||
getDomainImportFailedQueue({ limit: 50 }),
|
||||
getDomainImportHealthTrend({ limit: 10 })
|
||||
]);
|
||||
queueSummary.value = queueRes.data ?? {};
|
||||
rows.value = queueSummary.value.items ?? [];
|
||||
rerunAllCommand.value = queueSummary.value.rerun_all_command ?? "";
|
||||
healthTrend.value = trendRes.data ?? {};
|
||||
} catch (_error) {
|
||||
ElMessage.error("读取导入后探测失败队列失败");
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const open = async () => {
|
||||
visible.value = true;
|
||||
await loadQueue();
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
open
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.failed-queue-dialog__toolbar {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.failed-queue-dialog__summary {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
margin-bottom: 12px;
|
||||
color: #606266;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.failed-queue-dialog__resolved {
|
||||
margin-bottom: 12px;
|
||||
font-size: 13px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.failed-queue-dialog__resolved-label {
|
||||
margin-right: 8px;
|
||||
color: #606266;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user