241 lines
11 KiB
Vue
241 lines
11 KiB
Vue
<template>
|
||
<div class="home card">
|
||
<el-card shadow="never" class="home-metadata-console">
|
||
<template #header>
|
||
<div class="home-metadata-console__header">
|
||
<div>
|
||
<div class="home-metadata-console__title">视频缺字段控制台</div>
|
||
<div class="home-metadata-console__subtitle">后台打开先看这里,工作台、任务池和计划任务状态都汇总到首屏。</div>
|
||
</div>
|
||
<div class="home-metadata-console__actions">
|
||
<el-button :loading="metadataLoading" @click="loadMetadataConsole">刷新状态</el-button>
|
||
<el-button type="primary" :loading="workbenchRunning" @click="handleRunWorkbench">刷新工作台</el-button>
|
||
<el-button type="warning" plain :loading="taskPoolRunning" @click="handleRunTaskPool">刷新任务池</el-button>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<el-alert
|
||
:type="metadataAlertType"
|
||
:closable="false"
|
||
show-icon
|
||
:title="metadataAlertTitle"
|
||
:description="metadataAlertDescription"
|
||
/>
|
||
|
||
<div class="home-metadata-console__stats">
|
||
<div class="home-metadata-stat">
|
||
<div class="home-metadata-stat__label">缺字段视频总量</div>
|
||
<div class="home-metadata-stat__value">{{ metadataSummary.workbench?.videos_with_any_missing_metadata || 0 }}</div>
|
||
<div class="home-metadata-stat__tip">越大越需要先看工作台,再按批次派给 Codex。</div>
|
||
</div>
|
||
<div class="home-metadata-stat">
|
||
<div class="home-metadata-stat__label">重采优先队列</div>
|
||
<div class="home-metadata-stat__value">{{ metadataSummary.workbench?.queue_size || 0 }}</div>
|
||
<div class="home-metadata-stat__tip">这是最适合优先处理的一批,适合先做首轮修复。</div>
|
||
</div>
|
||
<div class="home-metadata-stat">
|
||
<div class="home-metadata-stat__label">任务池批次数</div>
|
||
<div class="home-metadata-stat__value">{{ metadataSummary.task_pool?.batch_count || 0 }}</div>
|
||
<div class="home-metadata-stat__tip">批次越清晰,后续技术和 Codex 的协作越顺。</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="home-metadata-console__hint">
|
||
<strong>当前建议:</strong>{{ metadataSummary.recommended_action || "先刷新状态,再看工作台和任务池。" }}
|
||
<br />
|
||
<strong>首条提示:</strong>{{ metadataFirstHint }}
|
||
</div>
|
||
|
||
<div class="home-metadata-console__links">
|
||
<el-button text type="primary" @click="openMetadataRoute('/video/metadata/missing/task-pool/status')">打开状态总览</el-button>
|
||
<el-button text type="primary" @click="openMetadataRoute('/video/metadata/missing/task-pool/ops')">打开运维总览</el-button>
|
||
<el-button text type="primary" @click="openMetadataRoute('/video/metadata/missing/task-pool')">打开任务池</el-button>
|
||
<el-button text type="primary" @click="openMetadataRoute('/video/metadata/missing/workbench')">打开工作台</el-button>
|
||
</div>
|
||
</el-card>
|
||
|
||
<div class="home-bootstrap-actions">
|
||
<el-button type="primary" @click="openBootstrapCenter('overview')">打开 Bootstrap 总控台</el-button>
|
||
<el-button type="warning" plain @click="openBootstrapCenter({ tab: 'ops', opsFocus: { panel: 'oncall', stage: 'all', clearHostFocus: true, hostConsolePanel: 'oncall' } })">值班待办</el-button>
|
||
<el-button type="danger" plain @click="openBootstrapCenter({ tab: 'ops', opsFocus: { panel: 'attention', stage: 'attention', clearHostFocus: true, hostConsolePanel: 'runs' } })">异常聚焦</el-button>
|
||
<el-button type="info" plain @click="openBootstrapCenter('env')">查看环境模板</el-button>
|
||
</div>
|
||
<BootstrapOpsOverview />
|
||
<BootstrapEnvOverview action-text="前往站点页查看环境模板" @open-dialog="goSitePage" />
|
||
<BootstrapAdminCenter ref="bootstrapAdminCenterRef" />
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts" name="home">
|
||
import { computed, onMounted, ref } from "vue";
|
||
import { useRouter } from "vue-router";
|
||
import { ElMessage } from "element-plus";
|
||
import BootstrapOpsOverview from "@/views/site/bootstrapOpsOverview.vue";
|
||
import BootstrapEnvOverview from "@/views/site/bootstrapEnvOverview.vue";
|
||
import BootstrapAdminCenter from "@/views/site/bootstrapAdminCenter.vue";
|
||
import {
|
||
getMetadataMissingTaskPoolStatus,
|
||
runMetadataMissingTaskPool,
|
||
runMetadataMissingWorkbench
|
||
} from "@/api/modules/video/list";
|
||
import type { VideoList } from "@/api/interface/video/list";
|
||
|
||
const router = useRouter();
|
||
const bootstrapAdminCenterRef = ref<InstanceType<typeof BootstrapAdminCenter> | null>(null);
|
||
const metadataLoading = ref(false);
|
||
const workbenchRunning = ref(false);
|
||
const taskPoolRunning = ref(false);
|
||
const metadataSummary = ref<VideoList.MetadataMissingTaskPoolStatusData>({});
|
||
type BootstrapFocusMode = "all" | "task" | "oncall" | "pipeline" | "import" | "release";
|
||
type BootstrapFocusPayload = BootstrapFocusMode | BootstrapFocusMode[];
|
||
type BootstrapComparePreset =
|
||
| "latest_two"
|
||
| "latest_applied_vs_dry_run"
|
||
| "latest_template_two"
|
||
| "latest_template_apply_vs_preview"
|
||
| "latest_restore_two"
|
||
| "latest_restore_apply_vs_preview";
|
||
type BootstrapRestorePreset = "latest_restore_after_preview" | "latest_restore_before_preview";
|
||
type BootstrapHistoryActionPreset = "template_family" | "restore_family" | "restore_preview_only" | "restore_apply_only";
|
||
type BootstrapDialogPayload =
|
||
| BootstrapFocusPayload
|
||
| {
|
||
focusMode?: BootstrapFocusPayload;
|
||
comparePreset?: BootstrapComparePreset;
|
||
restorePreset?: BootstrapRestorePreset;
|
||
historyActionPreset?: BootstrapHistoryActionPreset;
|
||
};
|
||
type OpsPanelKey = "queue" | "oncall" | "tasks" | "runs" | "attention" | "completed";
|
||
type BootstrapStageKey =
|
||
| "all"
|
||
| "ready_to_register"
|
||
| "ready_to_apply"
|
||
| "ready_for_prepare"
|
||
| "ready_to_import"
|
||
| "ready_to_release_dry_run"
|
||
| "ready_to_publish"
|
||
| "attention"
|
||
| "published";
|
||
type HostConsolePanelKey = "bundle" | "oncall" | "runs" | "completed";
|
||
type OpsInspectorKind = "bundle" | "oncall" | "task" | "run" | "attention_bundle" | "failed_run" | "completed";
|
||
type BootstrapOpsFocusPayload = {
|
||
panel?: OpsPanelKey;
|
||
stage?: BootstrapStageKey;
|
||
host?: string;
|
||
keyword?: string;
|
||
clearHostFocus?: boolean;
|
||
hostConsolePanel?: HostConsolePanelKey;
|
||
inspectorKind?: OpsInspectorKind | "default";
|
||
};
|
||
type BootstrapAdminOpenPayload =
|
||
| "overview"
|
||
| "ops"
|
||
| "env"
|
||
| {
|
||
tab?: "overview" | "ops" | "env";
|
||
opsFocus?: BootstrapOpsFocusPayload;
|
||
envDialog?: BootstrapDialogPayload;
|
||
refresh?: boolean;
|
||
};
|
||
|
||
const openBootstrapCenter = (payload: BootstrapAdminOpenPayload = "overview") => {
|
||
bootstrapAdminCenterRef.value?.openDialog(payload);
|
||
};
|
||
|
||
const loadMetadataConsole = async () => {
|
||
metadataLoading.value = true;
|
||
try {
|
||
const response = await getMetadataMissingTaskPoolStatus();
|
||
metadataSummary.value = response.data || {};
|
||
} finally {
|
||
metadataLoading.value = false;
|
||
}
|
||
};
|
||
|
||
const handleRunWorkbench = async () => {
|
||
workbenchRunning.value = true;
|
||
try {
|
||
const response = await runMetadataMissingWorkbench();
|
||
ElMessage.success(response.msg || "工作台刷新已触发");
|
||
await loadMetadataConsole();
|
||
} finally {
|
||
workbenchRunning.value = false;
|
||
}
|
||
};
|
||
|
||
const handleRunTaskPool = async () => {
|
||
taskPoolRunning.value = true;
|
||
try {
|
||
const response = await runMetadataMissingTaskPool();
|
||
ElMessage.success(response.msg || "任务池刷新已触发");
|
||
await loadMetadataConsole();
|
||
} finally {
|
||
taskPoolRunning.value = false;
|
||
}
|
||
};
|
||
|
||
const openMetadataRoute = (path: string) => {
|
||
router.push(path);
|
||
};
|
||
|
||
const metadataFirstHint = computed(() => metadataSummary.value.operator_hint?.[0] || "当前还没有额外提示,建议先看状态总览。");
|
||
|
||
const metadataAlertType = computed(() => {
|
||
const status = metadataSummary.value.plan_task?.status_label || "missing";
|
||
if (status === "enabled") return "success";
|
||
if (status === "disabled") return "warning";
|
||
if (status === "db_blocked") return "error";
|
||
return "info";
|
||
});
|
||
|
||
const metadataAlertTitle = computed(() => {
|
||
const status = metadataSummary.value.plan_task?.status_label || "missing";
|
||
if (status === "enabled") return "视频缺字段计划任务已启用";
|
||
if (status === "disabled") return "视频缺字段计划任务已存在但未启用";
|
||
if (status === "db_blocked") return "当前环境暂时无法读取视频缺字段计划任务";
|
||
return "视频缺字段计划任务尚未补齐";
|
||
});
|
||
|
||
const metadataAlertDescription = computed(() => {
|
||
const status = metadataSummary.value.plan_task?.status_label || "missing";
|
||
const action = metadataSummary.value.recommended_action || "先刷新状态,再决定下一步。";
|
||
if (status === "enabled") return `当前工作台和任务池已经可持续刷新。${action}`;
|
||
if (status === "disabled") return `当前后台已经有这条计划任务,但还没有启用。${action}`;
|
||
if (status === "db_blocked") return `这通常说明当前后台环境没读到完整数据库上下文。${action}`;
|
||
return `当前还没补齐这条计划任务,建议先补齐再观察。${action}`;
|
||
});
|
||
|
||
const goSitePage = (payload: BootstrapDialogPayload = "all") => {
|
||
const normalizedFocus = typeof payload === "string" || Array.isArray(payload) ? payload : payload.focusMode ?? "all";
|
||
const comparePreset = typeof payload === "string" || Array.isArray(payload) ? undefined : payload.comparePreset;
|
||
const restorePreset = typeof payload === "string" || Array.isArray(payload) ? undefined : payload.restorePreset;
|
||
const historyActionPreset = typeof payload === "string" || Array.isArray(payload) ? undefined : payload.historyActionPreset;
|
||
const queryFocus = Array.isArray(normalizedFocus) ? normalizedFocus.join(",") : normalizedFocus;
|
||
router.push({
|
||
path: "/site/index",
|
||
query: {
|
||
...(queryFocus === "all" ? {} : { bootstrapFocus: queryFocus }),
|
||
...(comparePreset ? { bootstrapComparePreset: comparePreset } : {}),
|
||
...(restorePreset ? { bootstrapRestorePreset: restorePreset } : {}),
|
||
...(historyActionPreset ? { bootstrapHistoryActionPreset: historyActionPreset } : {})
|
||
}
|
||
});
|
||
};
|
||
|
||
onMounted(() => {
|
||
loadMetadataConsole();
|
||
});
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
@use "./index.scss" as *;
|
||
|
||
.home-bootstrap-actions {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 12px;
|
||
margin: 0 0 16px;
|
||
}
|
||
</style>
|