From db98018f06d9a3b80f1c4756459896f421877a07 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 18 Sep 2026 07:30:44 +0000 Subject: [PATCH 1/2] Fix Organization livez gate and Studio-style openXYOS restart overlay. Probe sidecar livez before loading the local test iframe so a down frontend/backend surfaces a restart CTA instead of Failed to fetch. Restart now shows an XYAI Studio-style progress card with advancing steps and still stops/starts the real FreeOS openXYOS node. Co-authored-by: XYAI Labs --- CHANGELOG.md | 1 + dashboard/src/api/modules/orgModule.ts | 4 + dashboard/src/layouts/sidebarNav.test.ts | 8 + dashboard/src/locales/en.json | 26 +++ dashboard/src/locales/zh.json | 26 +++ .../src/pages/Organization/OrgMiniBrowser.tsx | 26 ++- .../pages/Organization/OrgRestartOverlay.tsx | 129 +++++++++++++++ .../Organization/Organization.module.less | 154 ++++++++++++++++-- .../src/pages/Organization/index.test.tsx | 82 +++++++++- dashboard/src/pages/Organization/index.tsx | 134 +++++++++++---- .../pages/Organization/sidecarLivez.test.ts | 92 +++++++++++ .../src/pages/Organization/sidecarLivez.ts | 64 ++++++++ .../pages/Organization/sidecarRecover.test.ts | 18 +- .../src/pages/Organization/sidecarRecover.ts | 7 +- .../pages/Organization/sidecarRestart.test.ts | 64 ++++++++ .../src/pages/Organization/sidecarRestart.ts | 50 ++++++ src/octop/api/routers/org_module.py | 14 ++ tests/unit/test_org_module.py | 7 + 18 files changed, 845 insertions(+), 61 deletions(-) create mode 100644 dashboard/src/pages/Organization/OrgRestartOverlay.tsx create mode 100644 dashboard/src/pages/Organization/sidecarLivez.test.ts create mode 100644 dashboard/src/pages/Organization/sidecarLivez.ts create mode 100644 dashboard/src/pages/Organization/sidecarRestart.test.ts create mode 100644 dashboard/src/pages/Organization/sidecarRestart.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index e7991b74..ad8a655e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ - 组织页互生长通道两端都落到可选用状态:推送到 openXYOS 时员工/人才写成列表页能看到的 `active` / `internal` / `available`;从 openXYOS 回流时即使没有技能字段也会登记为同事,技能/插件/MCP 写入 `org-skills` / `org-plugins` / `org-mcps`,并把聊天专家挂到当前用户(修复 `user_id=NULL` 导致「专家」页看不见)。导入后跳到「专家」页,推送/循环后关闭管理抽屉并刷新预览。 - 组织页内嵌浏览器补上后退 / 前进 / 刷新;openXYOS 首页卡通助手改为打包进前端资源(并保留 `/assets/xyai-mascot.webp` 回退),不再显示破碎图片。 - 启动 FreeOS 时就会拉起本机 openXYOS 前后端(桌面 `startOrgSidecar` + 主机 `ensure_sidecar`),不必先进入组织页;登录不再因为 3780 未监听而 `Failed to fetch`。 +- 组织页打开本机测试页会先探测 `livez`:前后端未起来时不再把登录 iframe 暴露成 `Failed to fetch`,而是提示点击「重启前后端服务」,并高亮状态栏重启按钮。重启过程改为 XYAI Studio 风格进度卡(吉祥物对话气泡、转圈、逐步推进的停进程 / 本机完整目录 / 启动 API 与页面 / 等待端口 / 加载登录页),并真实停启 FreeOS openXYOS 节点。 - 组织页「最近同步」左侧增加「重启 openXYOS 前后端服务」:XYAI 吉祥物动画、真实停启进程,livez 通过后回到本机测试首页。 - 组织页「把 FreeOS 资产推到 openXYOS」不再把行写进 tenant 1 却让嵌入式登录(`demo@demo.com` → tenant 2)看不见。ingest 以当前登录 / 最近登录 / 本地演示租户为准,并把员工/人才写成列表页会显示的状态;打包回执带落地数量,预览跳到员工页。 diff --git a/dashboard/src/api/modules/orgModule.ts b/dashboard/src/api/modules/orgModule.ts index e5d775eb..9c11d45a 100644 --- a/dashboard/src/api/modules/orgModule.ts +++ b/dashboard/src/api/modules/orgModule.ts @@ -151,6 +151,10 @@ export const orgModuleApi = { request("/org-module/sidecar/start", { method: "POST" }), restartSidecar: () => request("/org-module/sidecar/restart", { method: "POST" }), + probeLivez: () => + request<{ reachable: boolean; url: string; detail: string }>( + "/org-module/sidecar/livez", + ), assemble: () => request("/org-module/assemble", { method: "POST" }), produce: (body: { diff --git a/dashboard/src/layouts/sidebarNav.test.ts b/dashboard/src/layouts/sidebarNav.test.ts index 9d1628de..488087ab 100644 --- a/dashboard/src/layouts/sidebarNav.test.ts +++ b/dashboard/src/layouts/sidebarNav.test.ts @@ -84,6 +84,8 @@ describe("sidebarNav", () => { expect(zh.organization.downloadSourceBar).toBe("下载最新源码"); expect(zh.organization.downloadSource).toBe("下载最新 openXYOS 源码"); expect(zh.organization.restartSidecar).toBe("重启 openXYOS 前后端服务"); + expect(zh.organization.restartSidecarCta).toBe("重启前后端服务"); + expect(zh.organization.restartOverlayTitle).toBe("正在重启前后端服务"); expect(zh.organization.browserReload).toBe("刷新"); expect(en.organization.enableModule).toBe("Enable organization module"); expect(en.organization.manageOs).toBe("Organization OS"); @@ -94,6 +96,12 @@ describe("sidebarNav", () => { expect(en.organization.restartSidecar).toBe( "Restart openXYOS frontend and backend", ); + expect(en.organization.restartSidecarCta).toBe( + "Restart frontend and backend", + ); + expect(en.organization.restartOverlayTitle).toBe( + "Restarting frontend and backend", + ); expect(en.organization.browserReload).toBe("Reload"); expect(zh.chat.modelPickerManage).toBe("模型管理"); expect(en.chat.modelPickerManage).toBe("Model management"); diff --git a/dashboard/src/locales/en.json b/dashboard/src/locales/en.json index cbad3628..e852ab8b 100644 --- a/dashboard/src/locales/en.json +++ b/dashboard/src/locales/en.json @@ -25,10 +25,34 @@ "sidecarOpening": "Opening local openXYOS", "lastSync": "Last sync", "restartSidecar": "Restart openXYOS frontend and backend", + "restartSidecarCta": "Restart frontend and backend", "restartSidecarHint": "Stopping and starting the local services, then returning to the test home page.", "restartingSidecar": "Restarting openXYOS frontend and backend…", "restartSidecarDone": "Local openXYOS restarted", "restartSidecarFailed": "Restart failed: {{detail}}", + "restartOverlayTitle": "Restarting frontend and backend", + "restartWaitHint": "Please wait — the little helper is working…", + "restartStep": { + "stop": "Stopping the old openXYOS process. Please wait.", + "preferLayout": "Prefer the complete local source tree so frontend and backend can start.", + "startApi": "Starting the API and page server. This usually takes a few seconds.", + "waitPort": "Waiting for the local port to be ready. Do not close this window.", + "loadLogin": "Frontend is ready. Loading the sign-in page." + }, + "restartSpeech": { + "stopTitle": "I’ll stop the old service first~", + "stopBody": "Stopping the old openXYOS process. Please wait.", + "preferLayoutTitle": "Using the complete local tree~", + "preferLayoutBody": "Prefer the complete local source tree so frontend and backend can start.", + "startApiTitle": "Bringing frontend and backend up~", + "startApiBody": "Starting the API and page server. This usually takes a few seconds.", + "waitPortTitle": "The local port is almost ready~", + "waitPortBody": "Waiting for the local port to be ready. Do not close this window.", + "loadLoginTitle": "You can sign in now~", + "loadLoginBody": "Frontend is ready. Loading the sign-in page.", + "needsRestartTitle": "Local frontend and backend are down~", + "needsRestartBody": "Opening the test page now would fail with Failed to fetch. Click Restart frontend and backend first." + }, "neverSynced": "Not synced yet", "dataPlane": "Data plane", "controlPlane": "Control plane", @@ -116,6 +140,8 @@ "disabled": "Organization module disabled", "previewTitle": "openXYOS preview", "previewOffline": "Opening local openXYOS (http://127.0.0.1:3780)…", + "previewNeedsRestartTitle": "Local openXYOS is not ready", + "previewNeedsRestart": "Local frontend and backend are down, so sign-in would fail with Failed to fetch. Click Restart frontend and backend first.", "previewBlank": "Local openXYOS is healthy, but the preview stayed blank: requests from its own origin (http://127.0.0.1:3780) were blocked by CORS, so scripts and styles never loaded. Fully quit and reopen FreeOS so the sidecar restarts with the current allow-list.", "previewLoading": "Opening local openXYOS…", "fullscreen": "Fullscreen", diff --git a/dashboard/src/locales/zh.json b/dashboard/src/locales/zh.json index c6f48cb6..d25da8a3 100644 --- a/dashboard/src/locales/zh.json +++ b/dashboard/src/locales/zh.json @@ -25,10 +25,34 @@ "sidecarOpening": "正在打开本机 openXYOS", "lastSync": "最近同步", "restartSidecar": "重启 openXYOS 前后端服务", + "restartSidecarCta": "重启前后端服务", "restartSidecarHint": "正在停止并重新拉起本机前后端,完成后回到测试首页。", "restartingSidecar": "正在重启 openXYOS 前后端服务…", "restartSidecarDone": "本机 openXYOS 已重启", "restartSidecarFailed": "重启失败:{{detail}}", + "restartOverlayTitle": "正在重启前后端服务", + "restartWaitHint": "请耐心等待,小精灵正在干活中...", + "restartStep": { + "stop": "正在停止旧的 openXYOS 进程,请稍候。", + "preferLayout": "会优先使用本机完整源码目录,好启动前后端。", + "startApi": "正在启动 API 与页面服务,通常需要几十秒。", + "waitPort": "正在等待本机端口就绪,不要关闭窗口。", + "loadLogin": "前端已就绪,正在加载可登录注册的页面。" + }, + "restartSpeech": { + "stopTitle": "我先帮你把旧服务停掉~", + "stopBody": "正在停止旧的 openXYOS 进程,请稍候。", + "preferLayoutTitle": "先用本机完整目录来启动~", + "preferLayoutBody": "会优先使用本机完整源码目录,好启动前后端。", + "startApiTitle": "正在把前后端拉起来~", + "startApiBody": "正在启动 API 与页面服务,通常需要几十秒。", + "waitPortTitle": "本机端口马上就好~", + "waitPortBody": "正在等待本机端口就绪,不要关闭窗口。", + "loadLoginTitle": "可以登录啦~", + "loadLoginBody": "前端已就绪,正在加载可登录注册的页面。", + "needsRestartTitle": "本机前后端还没起来~", + "needsRestartBody": "直接打开测试页会 Failed to fetch。请先点击「重启前后端服务」。" + }, "neverSynced": "尚未同步", "dataPlane": "数据面", "controlPlane": "控制面", @@ -116,6 +140,8 @@ "disabled": "组织模块已关闭", "previewTitle": "openXYOS 预览", "previewOffline": "正在打开本机 openXYOS(http://127.0.0.1:3780)…", + "previewNeedsRestartTitle": "本机 openXYOS 尚未就绪", + "previewNeedsRestart": "本机前后端还没起来,登录页会 Failed to fetch。请先点击「重启前后端服务」。", "previewBlank": "本机 openXYOS 健康检查已通过,但预览页是空白的:页面自己的地址(http://127.0.0.1:3780)被跨域拦截,脚本和样式没有加载。请完全退出并重新打开 FreeOS,用当前白名单重启本机服务。", "previewLoading": "正在打开本机 openXYOS…", "fullscreen": "全屏", diff --git a/dashboard/src/pages/Organization/OrgMiniBrowser.tsx b/dashboard/src/pages/Organization/OrgMiniBrowser.tsx index bd216949..fcecce33 100644 --- a/dashboard/src/pages/Organization/OrgMiniBrowser.tsx +++ b/dashboard/src/pages/Organization/OrgMiniBrowser.tsx @@ -10,6 +10,7 @@ import { iframeSandboxFor, type OrgBrowserTab, } from "./orgBrowser"; +import { shouldLoadSidecarFrame } from "./sidecarLivez"; import styles from "./Organization.module.less"; type OrgMiniBrowserProps = { @@ -20,7 +21,9 @@ type OrgMiniBrowserProps = { disabledKeys: string[]; previewNonce: string; sidecarUp: boolean; + livezOk: boolean; previewBlank: boolean; + recoverPhase: "hidden" | "opening" | "needsRestart"; iframeRefs: MutableRefObject>; onAddressChange: (value: string) => void; onAddressSubmit: () => void; @@ -41,7 +44,9 @@ export default function OrgMiniBrowser({ disabledKeys, previewNonce, sidecarUp, + livezOk, previewBlank, + recoverPhase, iframeRefs, onAddressChange, onAddressSubmit, @@ -124,7 +129,7 @@ export default function OrgMiniBrowser({
- {!sidecarUp && ( + {recoverPhase === "opening" && (
{t("organization.previewOffline")}
@@ -135,11 +140,18 @@ export default function OrgMiniBrowser({
)} {tabs.map((tab) => { - const src = buildEmbedSrc(tab.srcUrl, { + const allowFrame = shouldLoadSidecarFrame({ + url: tab.srcUrl, sidecarOrigin, - disabledKeys, - nonce: previewNonce, + livezOk: livezOk && sidecarUp, }); + const src = allowFrame + ? buildEmbedSrc(tab.srcUrl, { + sidecarOrigin, + disabledKeys, + nonce: previewNonce, + }) + : "about:blank"; const active = tab.id === activeId; return (