fix: allow deleting system default sync channel with immediate heal
Remove hard ban that returned 400 with no UI feedback; delete then Ensure+rebinding, and show confirm/toast on SyncPage. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -121,21 +121,23 @@ func syncDeleteHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
|||||||
authx.WriteError(w, http.StatusNotFound, err.Error())
|
authx.WriteError(w, http.StatusNotFound, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Z12h-1:公司默认同步通道禁止删除(账号落点依赖它)
|
wasDefault := ch.IsSystemDefault
|
||||||
if ch.IsSystemDefault {
|
|
||||||
authx.WriteError(w, http.StatusBadRequest, "公司默认同步通道不可删除;账号绑定落点依赖此通道。若异常请刷新页面自动修复")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
svcCtx.DBSync.StopChannel(id)
|
svcCtx.DBSync.StopChannel(id)
|
||||||
if err := svcCtx.DBSync.Store().DeleteChannel(id); err != nil {
|
if err := svcCtx.DBSync.Store().DeleteChannel(id); err != nil {
|
||||||
authx.WriteError(w, http.StatusBadRequest, err.Error())
|
authx.WriteError(w, http.StatusBadRequest, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// 若误删后仍有 Binding 挂死,heal 会挂回默认通道
|
// Z12h-1:默认同步可删;删后 Ensure+heal,账号落点自动挂回新默认通道
|
||||||
if _, hErr := applogic.HealTenantSyncBind(r.Context(), svcCtx, tid); hErr != nil {
|
out := map[string]any{"ok": true, "deleted_id": id, "was_system_default": wasDefault}
|
||||||
_ = hErr
|
if healed, hErr := applogic.HealTenantSyncBind(r.Context(), svcCtx, tid); hErr != nil {
|
||||||
|
out["heal_error"] = hErr.Error()
|
||||||
|
} else if healed != nil {
|
||||||
|
out["channel_id"] = healed.ChannelID
|
||||||
|
out["recreated_default"] = wasDefault || healed.ChannelCreated
|
||||||
|
out["bindings_fixed"] = healed.BindingsFixed
|
||||||
|
out["agents_fixed"] = healed.AgentsFixed
|
||||||
}
|
}
|
||||||
httpx.OkJson(w, map[string]any{"ok": true})
|
httpx.OkJson(w, out)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -586,9 +586,35 @@ export function SyncPage(props: {
|
|||||||
<Button
|
<Button
|
||||||
size="small"
|
size="small"
|
||||||
danger
|
danger
|
||||||
onClick={async () => {
|
onClick={() => {
|
||||||
await deleteSyncChannel(session, r.id);
|
Modal.confirm({
|
||||||
await refresh();
|
title: `删除通道「${r.name || r.id}」?`,
|
||||||
|
content: r.is_system_default
|
||||||
|
? "这是公司默认同步通道。删除后将自动重建并重挂库绑定,账号落点不会丢失。"
|
||||||
|
: "删除后,仍挂在此通道上的绑定会自动改挂公司默认同步通道。",
|
||||||
|
okText: "确认删除",
|
||||||
|
okType: "danger",
|
||||||
|
cancelText: "取消",
|
||||||
|
onOk: async () => {
|
||||||
|
setBusy(true);
|
||||||
|
try {
|
||||||
|
const res = await deleteSyncChannel(session, r.id);
|
||||||
|
if (res.heal_error) {
|
||||||
|
message.warning(`通道已删,自愈未完成:${res.heal_error}`);
|
||||||
|
} else if (res.recreated_default) {
|
||||||
|
message.success("已删除;公司默认同步通道已自动重建并重挂绑定");
|
||||||
|
} else {
|
||||||
|
message.success("已删除");
|
||||||
|
}
|
||||||
|
await refresh();
|
||||||
|
} catch (e: any) {
|
||||||
|
message.error(e.message || String(e));
|
||||||
|
throw e;
|
||||||
|
} finally {
|
||||||
|
setBusy(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
删除
|
删除
|
||||||
|
|||||||
@@ -869,6 +869,8 @@ export type SyncChannel = {
|
|||||||
pk_columns: Record<string, string>;
|
pk_columns: Record<string, string>;
|
||||||
/** 绑定智能体:模块/库归属对照 */
|
/** 绑定智能体:模块/库归属对照 */
|
||||||
agent_id?: number;
|
agent_id?: number;
|
||||||
|
/** 公司默认同步通道(Z12) */
|
||||||
|
is_system_default?: boolean;
|
||||||
/** 绑定已发布模块 slug */
|
/** 绑定已发布模块 slug */
|
||||||
app_slug?: string;
|
app_slug?: string;
|
||||||
last_error?: string;
|
last_error?: string;
|
||||||
@@ -966,6 +968,16 @@ export async function deleteSyncChannel(session: Session, id: string) {
|
|||||||
});
|
});
|
||||||
const data = await readJson(res);
|
const data = await readJson(res);
|
||||||
throwIfBad(res, data, "delete sync channel failed");
|
throwIfBad(res, data, "delete sync channel failed");
|
||||||
|
return data as {
|
||||||
|
ok?: boolean;
|
||||||
|
deleted_id?: string;
|
||||||
|
was_system_default?: boolean;
|
||||||
|
recreated_default?: boolean;
|
||||||
|
channel_id?: string;
|
||||||
|
bindings_fixed?: number;
|
||||||
|
agents_fixed?: number;
|
||||||
|
heal_error?: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function testSyncEndpoints(
|
export async function testSyncEndpoints(
|
||||||
|
|||||||
@@ -62,7 +62,7 @@
|
|||||||
| 默认同步通道默认启动 | 创建即 enabled;重启自动 Start |
|
| 默认同步通道默认启动 | 创建即 enabled;重启自动 Start |
|
||||||
| **Z14c** ACL | 智能体 JWT 放行挂载 `online_db_id`(`agentOwnsOnlineDB`) |
|
| **Z14c** ACL | 智能体 JWT 放行挂载 `online_db_id`(`agentOwnsOnlineDB`) |
|
||||||
| Podman DNS / 脚本可执行位 | compose aliases;`restart.sh` 100755 |
|
| Podman DNS / 脚本可执行位 | compose aliases;`restart.sh` 100755 |
|
||||||
| **Z12h / Z12h-1 / Z12h-2** | `HealTenantSyncBind`:Ensure 默认通道 + 重挂 Agent/Binding;`is_system_default` 禁删;列表/换票/`agents/me`/ensure Binding 触发;见 §5.9 |
|
| **Z12h / Z12h-1 / Z12h-2** | `HealTenantSyncBind`:Ensure 默认通道 + 重挂 Agent/Binding;默认同步**可删**,删后立即 Ensure+heal;列表/换票/`agents/me`/ensure Binding 触发;见 §5.9 |
|
||||||
|
|
||||||
> 产品一句:**账号已绑定 ⇒ 落点信息固定;通道没了平台自动补,终端无需手填 channel_id。**
|
> 产品一句:**账号已绑定 ⇒ 落点信息固定;通道没了平台自动补,终端无需手填 channel_id。**
|
||||||
|
|
||||||
@@ -552,13 +552,13 @@ POST .../schema
|
|||||||
| **Z12f** | **P1** | **开通 UX** | 启用智能体即可;DSN 用公司级默认 | **智建已落实** |
|
| **Z12f** | **P1** | **开通 UX** | 启用智能体即可;DSN 用公司级默认 | **智建已落实** |
|
||||||
| **Z12g** | **P1** | **默认同步通道默认启动** | 创建 `Enabled=true`;重启对 `IsSystemDefault` 自动 Start;控制台保存后自动启动 | **智建已落实**(`cb76824`) |
|
| **Z12g** | **P1** | **默认同步通道默认启动** | 创建 `Enabled=true`;重启对 `IsSystemDefault` 自动 Start;控制台保存后自动启动 | **智建已落实**(`cb76824`) |
|
||||||
| **Z12h** | **P0** | **通道误删自愈** | `HealTenantSyncBind`:Ensure 默认通道 + 重挂挂死 Agent/Binding(保留 `online_db_id`);触发:`ensureAgentSyncBind` / `agents/me` / 换票 / 通道·Binding 列表 / 删非默认通道后 | **智建已落实** |
|
| **Z12h** | **P0** | **通道误删自愈** | `HealTenantSyncBind`:Ensure 默认通道 + 重挂挂死 Agent/Binding(保留 `online_db_id`);触发:`ensureAgentSyncBind` / `agents/me` / 换票 / 通道·Binding 列表 / 删非默认通道后 | **智建已落实** |
|
||||||
| **Z12h-1** | **P1** | **禁删或删后重建默认同步** | `is_system_default` **禁止删除**;删其它通道后 heal | **智建已落实** |
|
| **Z12h-1** | **P1** | **禁删或删后重建默认同步** | `is_system_default` **可删**;删后立即 Ensure+heal 重建并重挂 | **智建已落实** |
|
||||||
| **Z12h-2** | **P1** | **orphan Binding heal** | 列表 / Ensure Binding 时通道缺失 → 静默改挂默认通道 | **智建已落实** |
|
| **Z12h-2** | **P1** | **orphan Binding heal** | 列表 / Ensure Binding 时通道缺失 → 静默改挂默认通道 | **智建已落实** |
|
||||||
|
|
||||||
**缺口说明(2026-08-06 · 已关闭)**
|
**缺口说明(2026-08-06 · 已关闭)**
|
||||||
|
|
||||||
- 原:SyncPage「通道已删除」、`ensureAgentSyncBind` 见非空 `channel_id` 即跳过。
|
- 原:SyncPage「通道已删除」、`ensureAgentSyncBind` 见非空 `channel_id` 即跳过。
|
||||||
- 现:列表/换票等路径先 heal;默认通道不可删。生产须 **pull** 后验收第 4 条。
|
- 现:列表/换票等路径先 heal;默认通道可删并立即重建。生产须 **pull** 后验收第 4 条。
|
||||||
|
|
||||||
**宇恒已做(2026-08-05)**
|
**宇恒已做(2026-08-05)**
|
||||||
|
|
||||||
@@ -571,7 +571,7 @@ POST .../schema
|
|||||||
1. 新公司:只「启用智能体」,**不**点新建通道,换票已带 `channel_id`,宇恒可 drain。
|
1. 新公司:只「启用智能体」,**不**点新建通道,换票已带 `channel_id`,宇恒可 drain。
|
||||||
2. 账号 A 的本机库 push 不会出现在账号 B 的线上库;共享库仅在显式共享时可见。
|
2. 账号 A 的本机库 push 不会出现在账号 B 的线上库;共享库仅在显式共享时可见。
|
||||||
3. 用户全程无需 F12、无需手填 `YXD_SYNC_CHANNEL_ID`。
|
3. 用户全程无需 F12、无需手填 `YXD_SYNC_CHANNEL_ID`。
|
||||||
4. **(Z12h)** 默认同步通道 API **不可删**;若 Binding/Agent 仍挂历史死 `channel_id`(或误删非默认通道)→ `agents/me` / 换票 / 打开 SyncPage → Ensure 默认通道并重挂,「通道已删除」红字消失;宇恒无需手填新 ID。
|
4. **(Z12h)** 删除默认同步通道 → 立即重建并重挂;Binding/Agent 挂死 ID 时打开 SyncPage / 换票 / `agents/me` 亦会自愈,「通道已删除」红字消失;宇恒无需手填新 ID。
|
||||||
|
|
||||||
### 5.10 【双方代码已接 Z13】绑定流程简化:绑定码 / 手机号(2026-08-05)
|
### 5.10 【双方代码已接 Z13】绑定流程简化:绑定码 / 手机号(2026-08-05)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user