|
816 | 816 | appTitle: "策略切换", |
817 | 817 | appSubtitle: "选平台、目标账号和策略,一次执行完成切换。", |
818 | 818 | healthView: "策略健康", |
| 819 | + lifecycleView: "生命周期", |
819 | 820 | switchView: "实盘切换", |
| 821 | + lifecycleEyebrow: "生命周期矩阵 / 只读", |
| 822 | + lifecycleTitle: "每个策略目前走到哪一步。", |
| 823 | + lifecycleSubtitle: "这是脱敏研究状态摘要,不授予 promotion、live 或交易权限。", |
820 | 824 | healthEyebrow: "策略健康 / 只读", |
821 | 825 | healthTitle: "先看机器结论,再决定动作。", |
822 | 826 | healthSubtitle: "健康不等于已批准 live;正常实盘、资金和杠杆变更仍需人工确认。", |
|
991 | 995 | appTitle: "Strategy Switch", |
992 | 996 | appSubtitle: "Pick platform, target account, and strategy. One action switches everything.", |
993 | 997 | healthView: "Strategy Health", |
| 998 | + lifecycleView: "Lifecycle", |
994 | 999 | switchView: "Live Switch", |
| 1000 | + lifecycleEyebrow: "Lifecycle matrix / read only", |
| 1001 | + lifecycleTitle: "See where each strategy currently stands.", |
| 1002 | + lifecycleSubtitle: "A redacted research summary; it grants no promotion, live, or trading permission.", |
995 | 1003 | healthEyebrow: "Strategy health / read only", |
996 | 1004 | healthTitle: "Read the machine conclusion before choosing an action.", |
997 | 1005 | healthSubtitle: "Health does not approve live; normal live, funding, and leverage changes still need a human.", |
|
1194 | 1202 | selected: "longbridge", |
1195 | 1203 | lang: initialLang, |
1196 | 1204 | view: "health", |
| 1205 | + lifecycle: { payload: null }, |
1197 | 1206 | appReady: false, |
1198 | 1207 | bootMessageKey: "bootMessage", |
1199 | 1208 | auth: { available: false, allowed: false, admin: false, login: null }, |
|
3333 | 3342 |
|
3334 | 3343 | function renderConsoleView() { |
3335 | 3344 | const healthButton = el("health-view-button"); |
| 3345 | + const lifecycleButton = el("lifecycle-view-button"); |
3336 | 3346 | const switchButton = el("switch-view-button"); |
3337 | 3347 | const healthVisible = state.view === "health"; |
| 3348 | + const lifecycleVisible = state.view === "lifecycle"; |
3338 | 3349 | el("health-view").hidden = !healthVisible; |
| 3350 | + el("lifecycle-view").hidden = !lifecycleVisible; |
3339 | 3351 | el("switch-view").hidden = healthVisible; |
3340 | 3352 | healthButton.classList.toggle("active", healthVisible); |
| 3353 | + lifecycleButton.classList.toggle("active", lifecycleVisible); |
3341 | 3354 | switchButton.classList.toggle("active", !healthVisible); |
3342 | 3355 | } |
3343 | 3356 |
|
| 3357 | + function renderLifecycle() { |
| 3358 | + const payload = state.lifecycle.payload; |
| 3359 | + const status = el("lifecycle-status"); |
| 3360 | + const list = el("lifecycle-list"); |
| 3361 | + list.replaceChildren(); |
| 3362 | + if (!payload || !Array.isArray(payload.entries)) { |
| 3363 | + status.textContent = "不可用"; |
| 3364 | + el("lifecycle-notice").textContent = "没有可用矩阵快照;当前保持 fail-closed。"; |
| 3365 | + return; |
| 3366 | + } |
| 3367 | + status.textContent = "快照已加载"; |
| 3368 | + el("lifecycle-generated-at").textContent = `生成时间:${payload.generated_at || "—"}`; |
| 3369 | + el("lifecycle-notice").textContent = payload.source_policy || "只读研究状态;不授予 promotion、live 或交易权限。"; |
| 3370 | + for (const entry of payload.entries) { |
| 3371 | + const row = document.createElement("tr"); |
| 3372 | + const name = document.createElement("th"); name.scope = "row"; name.textContent = entry.display_name || entry.id || "unknown"; |
| 3373 | + row.appendChild(name); |
| 3374 | + for (const value of [entry.lineage, entry.p0, entry.p1, entry.p2, entry.p3, entry.p4, entry.p5, entry.p6]) { |
| 3375 | + const cell = document.createElement("td"); cell.textContent = value || "—"; if (["parked", "deferred", "not_started"].includes(value)) cell.className = `lifecycle-${value}`; row.appendChild(cell); |
| 3376 | + } |
| 3377 | + const next = document.createElement("td"); next.textContent = entry.next_action || "—"; row.appendChild(next); |
| 3378 | + list.appendChild(row); |
| 3379 | + } |
| 3380 | + } |
| 3381 | + |
3344 | 3382 | function render() { |
3345 | 3383 | applyLanguage(); |
3346 | 3384 | renderConsoleView(); |
3347 | 3385 | renderHealth(); |
| 3386 | + renderLifecycle(); |
3348 | 3387 | renderPlatforms(); |
3349 | 3388 | renderControls(); |
3350 | 3389 | renderSummary(); |
|
3395 | 3434 | renderHealth(); |
3396 | 3435 | } |
3397 | 3436 |
|
| 3437 | + async function refreshLifecycle() { |
| 3438 | + try { |
| 3439 | + const response = await fetch("/lifecycle-matrix.json", { cache: "no-store" }); |
| 3440 | + if (!response.ok) throw new Error("lifecycle_matrix_unavailable"); |
| 3441 | + const payload = await response.json(); |
| 3442 | + if (payload?.schema_version !== "strategy_lifecycle_matrix.v1" || !Array.isArray(payload.entries)) throw new Error("invalid_lifecycle_matrix"); |
| 3443 | + state.lifecycle.payload = payload; |
| 3444 | + } catch { |
| 3445 | + state.lifecycle.payload = null; |
| 3446 | + } |
| 3447 | + renderLifecycle(); |
| 3448 | + } |
| 3449 | + |
3398 | 3450 | async function refreshStrategyProfiles() { |
3399 | 3451 | state.bootMessageKey = "bootStrategy"; |
3400 | 3452 | render(); |
|
3582 | 3634 | } |
3583 | 3635 |
|
3584 | 3636 | document.querySelectorAll("[data-view]").forEach((button) => button.addEventListener("click", () => { |
3585 | | - state.view = button.dataset.view === "switch" ? "switch" : "health"; |
| 3637 | + state.view = ["switch", "lifecycle"].includes(button.dataset.view) ? button.dataset.view : "health"; |
3586 | 3638 | renderConsoleView(); |
3587 | 3639 | if (state.view === "health") refreshHealth(); |
| 3640 | + if (state.view === "lifecycle") refreshLifecycle(); |
3588 | 3641 | })); |
3589 | 3642 |
|
3590 | 3643 | document.querySelectorAll("[data-health-filter]").forEach((button) => button.addEventListener("click", () => { |
|
3746 | 3799 | applyStrategyProfiles(defaultStrategyProfiles); |
3747 | 3800 | for (const platform of Object.keys(platformMeta)) syncStrategyForAccount(platform); |
3748 | 3801 | render(); |
| 3802 | + refreshLifecycle(); |
3749 | 3803 | boot(); |
3750 | 3804 |
|
3751 | 3805 | async function boot() { |
|
0 commit comments