diff --git a/CHANGELOG.md b/CHANGELOG.md index f501ed2..6542708 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -311,3 +311,9 @@ - 管理员在切换“覆盖目标”后,会自动回到 `管理员配置台 -> 覆盖与审计`,直接进入当前目标的审计区。 - 系统主 Agent 历史回滚、系统平台策略历史回滚完成后,会自动回到 `管理员配置台 -> Agent 治理`,方便连续调整系统默认策略。 - 前端回归新增了这三条管理员治理落点断言,锁住“改完就能继续治理”的交互。 + +### 额度与管理员运维动作回跳补齐 + +- `租户额度` 保存后,现在会自动回到 `额度` 工作区的策略区域,不再只留一条成功提示。 +- `运维扫描 / 事件审计 / 修复计划生成 / 修复计划审计` 完成后,会统一回到 `管理员配置台 -> 运维审计`,方便连续处理下一条事件。 +- 前端回归新增了这批动作的 refocus 断言,并锁住了 `credits-quota-anchor` 与 `admin-ops-anchor` 两个工作区锚点。 diff --git a/web/storyforge-web-v4/assets/app.js b/web/storyforge-web-v4/assets/app.js index 381f948..2362de0 100644 --- a/web/storyforge-web-v4/assets/app.js +++ b/web/storyforge-web-v4/assets/app.js @@ -4615,7 +4615,7 @@ function renderAdminOpsPanel() { const overview = appState.adminOpsOverview; if (!overview) { return ` -
+

运维与审计 Agent

仅平台最高权限用户可见。

尚未拉到概览

刷新后会自动读取失败任务、集成健康和待审事件。

${renderAdminFixRunsPanel()} @@ -4626,7 +4626,7 @@ function renderAdminOpsPanel() { const incidents = safeArray(overview.incidents).slice(0, 6); const audits = safeArray(overview.recent_audits).slice(0, 5); return ` -
+

运维与审计 Agent

@@ -5749,6 +5749,15 @@ function focusTrackingWorkspace() { }); } +function focusCreditsWorkspace(anchorId = "credits-quota-anchor") { + setScreen("credits"); + renderAll(); + window.requestAnimationFrame(() => { + (document.getElementById(anchorId) || document.querySelector('[data-screen="credits"] .mobile-flow-focus-card, [data-screen="credits"] .panel')) + ?.scrollIntoView({ behavior: "smooth", block: "start" }); + }); +} + function focusProductionDetailTab(tabValue) { appState.productionDetailTab = tabValue; setScreen("production"); @@ -5803,6 +5812,16 @@ function focusReviewWorkspace(reviewId = "") { }); } +function focusAdminOpsWorkspace(anchorId = "admin-ops-anchor") { + appState.adminWorkbenchTab = "ops"; + setScreen("admin-workbench"); + renderAll(); + window.requestAnimationFrame(() => { + (document.getElementById(anchorId) || document.querySelector('[data-screen="admin-workbench"] .mobile-flow-focus-card, [data-screen="admin-workbench"] .panel')) + ?.scrollIntoView({ behavior: "smooth", block: "start" }); + }); +} + function renderDiscoveryOverviewSection({ selected, selectedProject, importedSources, tracked, topVideos, reports, latestVideos, currentPlatformLabel, topVideoBatchResult }) { return `
@@ -7485,7 +7504,7 @@ function renderCreditsScreen() {
-
+

当前额度策略

先让用户能看懂“还剩多少、风险在哪、下一步怎么做”。
@@ -10034,7 +10053,7 @@ function openTenantQuotaAction() { }); rememberAction("租户额度已更新", "当前项目的预算与配额已经保存。", "green", saved); await loadAgentControlSurfaces(project.id); - renderAll(); + focusCreditsWorkspace("credits-quota-anchor"); } }); } @@ -10284,6 +10303,7 @@ function openBenchmarkLinkAction(defaults = {}) { async function scanAdminOpsAction() { if (!isSuperAdmin()) throw new Error("只有平台管理者才能调用运维 Agent。"); setBusy(true, "运维 Agent 正在扫描故障事件..."); + let shouldRefocus = false; try { const payload = await storyforgeFetch("/v2/admin/ops/incidents/scan", { method: "POST", @@ -10291,9 +10311,14 @@ async function scanAdminOpsAction() { }); rememberAction("运维扫描已完成", `本轮共归集 ${formatNumber(payload.count)} 条故障事件。`, payload.count ? "orange" : "green", payload); await loadAgentControlSurfaces(getOneLinerProjectId()); + shouldRefocus = true; } finally { setBusy(false, ""); - renderAll(); + if (shouldRefocus) { + focusAdminOpsWorkspace("admin-ops-anchor"); + } else { + renderAll(); + } } } @@ -10352,7 +10377,7 @@ function openAdminIncidentReviewAction(incidentId) { }); rememberAction("审计结果已保存", `事件「${saved.title}」已更新为 ${saved.status}。`, "green", saved); await loadAgentControlSurfaces(getOneLinerProjectId()); - renderAll(); + focusAdminOpsWorkspace("admin-ops-anchor"); } }); } @@ -10388,7 +10413,7 @@ function openAdminRepairPlanAction(incidentId) { }); rememberAction("修复计划已生成", `已为事件「${incident.title}」生成 repair plan。`, "green", saved); await loadAgentControlSurfaces(getOneLinerProjectId()); - renderAll(); + focusAdminOpsWorkspace("admin-ops-anchor"); } }); } @@ -10490,7 +10515,7 @@ function openAdminFixRunAuditAction(runId) { }); rememberAction("修复计划已审计", `修复计划 ${runId} 已更新为 ${saved.audit_status || values.reviewStatus}。`, "green", saved); await loadAgentControlSurfaces(getOneLinerProjectId()); - renderAll(); + focusAdminOpsWorkspace("admin-ops-anchor"); } }); } diff --git a/web/storyforge-web-v4/tests/workbench-pages.test.mjs b/web/storyforge-web-v4/tests/workbench-pages.test.mjs index cd77901..5ad5d60 100644 --- a/web/storyforge-web-v4/tests/workbench-pages.test.mjs +++ b/web/storyforge-web-v4/tests/workbench-pages.test.mjs @@ -1017,6 +1017,24 @@ test("review actions return to the review workspace with the saved review in foc assert.match(APP, /data-review-id="\$\{escapeHtml\(review\.id\)\}"/); }); +test("quota and admin ops mutations refocus the user into the most relevant workbench area", () => { + const quota = extractBetween(APP, "function openTenantQuotaAction()", "function openCreateAssistantAction()"); + const scanOps = extractBetween(APP, "async function scanAdminOpsAction()", "function openAdminIncidentReviewAction(incidentId)"); + const reviewIncident = extractBetween(APP, "function openAdminIncidentReviewAction(incidentId)", "function openAdminRepairPlanAction(incidentId)"); + const repairPlan = extractBetween(APP, "function openAdminRepairPlanAction(incidentId)", "function openAdminFixRunDetailAction(runId)"); + const auditFixRun = extractBetween(APP, "function openAdminFixRunAuditAction(runId)", "function openJobDetailAction(jobId)"); + + assert.match(APP, /function focusCreditsWorkspace\(anchorId = "credits-quota-anchor"\)/); + assert.match(APP, /function focusAdminOpsWorkspace\(anchorId = "admin-ops-anchor"\)/); + assert.match(APP, /id="credits-quota-anchor"/); + assert.match(APP, /id="admin-ops-anchor"/); + assert.match(quota, /focusCreditsWorkspace\("credits-quota-anchor"\)/); + assert.match(scanOps, /focusAdminOpsWorkspace\("admin-ops-anchor"\)/); + assert.match(reviewIncident, /focusAdminOpsWorkspace\("admin-ops-anchor"\)/); + assert.match(repairPlan, /focusAdminOpsWorkspace\("admin-ops-anchor"\)/); + assert.match(auditFixRun, /focusAdminOpsWorkspace\("admin-ops-anchor"\)/); +}); + test("assistant actions return to the playbook workspace with the saved assistant in focus", () => { const createAssistant = extractBetween(APP, "function openCreateAssistantAction()", "function openEditAssistantAction(assistantId = \"\")"); const editAssistant = extractBetween(APP, "function openEditAssistantAction(assistantId = \"\")", "function openAnalyzeSelectedAccountAction()");