feat(admin): 渠道状态三态化 + 顶栏后端失联指示 — 不再把「读不到」画成「未连接」
起因:面板把「取数失败(后端不可达)」渲染成红色「未连接」,主人因此把「后端没在跑」误读成「微信适配器挂了」(AdapterDetailPage 的 record?.connected ?? false)。 - 新增 channels/channelStatus.ts(纯逻辑三态:ready/pending/unknown + 记录级 connected/disconnected/unconfigured/unknown/pending;局部类型,未动 lib/types.ts) - 新增 channelStatusView.tsx:ChannelStatusTag(三态 Tag + 「数据可能过期」)+ ChannelLoadError(失败/过期横幅 + 重试) - 覆盖 13 处渲染点(channels/ 全目录 + AdminApp):未知态一律灰色「状态未知」+ 原因,绝不落红色「未连接」;未知态禁用连接/断开与「新建/编辑配置」(避免 prefill 空列表误建/覆盖);读取失败不再冒充「尚未保存任何渠道配置」「暂无消息」「未声明能力」等事实性谎言 - 顺带修:WeixinLogin 首次 login-status 读取失败被 .catch 静默吞成「未登录」,现如实报「读取失败(与后端失联)」 - AdminApp 顶栏新增 useBackendHealth + BackendStatusTag:HTTP /api/v1/health 探活(15s 循环),**探测成功前不显示「在线」**;WS 状态作为独立 Tag 展示,两者不混算 - 验收:npm run build 绿(177 模块)+ 新增 45 断言 + 既有 54 断言回归 + renderToStaticMarkup 真渲染冒烟(unknown 渲染不含「未连接」也不含 err 色;未探测成功不显示在线) - ListPage 主函数 125→71 行(改前已超 80 行红线,行为等价)
This commit is contained in:
1 parent
a51aadeef4
commit
37fb3cd265
12 files changed
+782
-192
No files matched your search
@@ -16,10 +16,14 @@ export function GenericAdapterPanel({
|
||||
platform,
|
||||
accountId,
|
||||
record,
|
||||
recordUnknown = false,
|
||||
}: {
|
||||
platform: string;
|
||||
accountId?: string;
|
||||
record?: AdminChannelRecord;
|
||||
/** The channel list could not be read (backend unreachable): capability/account
|
||||
* emptiness is then unverified, so never present it as "declares nothing". */
|
||||
recordUnknown?: boolean;
|
||||
}): React.ReactNode {
|
||||
const label = PLATFORM_LABEL[platform] ?? platform;
|
||||
const account = record?.accounts?.find((a) =>
|
||||
@@ -39,20 +43,24 @@ export function GenericAdapterPanel({
|
||||
<span className="ml-auto text-xs text-[var(--mu-text-lo)]">通用壳 · 平台专属端点未接入</span>
|
||||
</div>
|
||||
|
||||
<CapabilityBadges caps={caps} />
|
||||
<FeaturePlaceholder caps={caps} />
|
||||
<ProactiveNote caps={caps} />
|
||||
<CapabilityBadges caps={caps} recordUnknown={recordUnknown} />
|
||||
<FeaturePlaceholder caps={caps} recordUnknown={recordUnknown} />
|
||||
<ProactiveNote caps={caps} recordUnknown={recordUnknown} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/** Capability badges: what this adapter declares it supports. Empty → honest note. */
|
||||
function CapabilityBadges({ caps }: { caps: readonly string[] }): React.ReactNode {
|
||||
function CapabilityBadges({ caps, recordUnknown }: { caps: readonly string[]; recordUnknown: boolean }): React.ReactNode {
|
||||
return (
|
||||
<Surface gap="sm" className="space-y-3">
|
||||
<h3 className="text-sm font-semibold">平台能力</h3>
|
||||
{caps.length === 0 ? (
|
||||
<Notice>该适配器尚未声明能力列表;平台专属端点未接入,当前仅提供通用操作(连接 / 断开 / 消息日志 / 身份测试)。</Notice>
|
||||
<Notice tone={recordUnknown ? "err" : "neutral"}>
|
||||
{recordUnknown
|
||||
? "读取失败(与后端失联),无法确认该适配器声明的能力;请先确认后端在线后重试。"
|
||||
: "该适配器尚未声明能力列表;平台专属端点未接入,当前仅提供通用操作(连接 / 断开 / 消息日志 / 身份测试)。"}
|
||||
</Notice>
|
||||
) : (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{caps.map((c) => {
|
||||
@@ -72,13 +80,17 @@ function CapabilityBadges({ caps }: { caps: readonly string[] }): React.ReactNod
|
||||
/** Placeholder for future platform-specific feature areas (friends/channels/members/
|
||||
* test-send). Only the admin-operations the adapter actually declares surface here,
|
||||
* each marked as not yet wired. */
|
||||
function FeaturePlaceholder({ caps }: { caps: readonly string[] }): React.ReactNode {
|
||||
function FeaturePlaceholder({ caps, recordUnknown }: { caps: readonly string[]; recordUnknown: boolean }): React.ReactNode {
|
||||
const adminOps = adminOpsOf(caps);
|
||||
return (
|
||||
<Surface gap="sm" className="space-y-3">
|
||||
<h3 className="text-sm font-semibold">平台专属功能区(占位)</h3>
|
||||
{adminOps.length === 0 ? (
|
||||
<Notice>该平台暂无已声明的专属功能模块。频道 / 成员 / 发送测试等平台能力将随后续接入后在此显示。</Notice>
|
||||
<Notice tone={recordUnknown ? "err" : "neutral"}>
|
||||
{recordUnknown
|
||||
? "读取失败(与后端失联),无法确认该平台的专属功能模块;请先确认后端在线后重试。"
|
||||
: "该平台暂无已声明的专属功能模块。频道 / 成员 / 发送测试等平台能力将随后续接入后在此显示。"}
|
||||
</Notice>
|
||||
) : (
|
||||
<>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
@@ -96,13 +108,15 @@ function FeaturePlaceholder({ caps }: { caps: readonly string[] }): React.ReactN
|
||||
}
|
||||
|
||||
/** Proactive-push note: declared → placeholder; otherwise an honest "not supported". */
|
||||
function ProactiveNote({ caps }: { caps: readonly string[] }): React.ReactNode {
|
||||
function ProactiveNote({ caps, recordUnknown }: { caps: readonly string[]; recordUnknown: boolean }): React.ReactNode {
|
||||
const supported = hasCapability(caps, "Proactive");
|
||||
return (
|
||||
<Surface gap="sm" className="space-y-3">
|
||||
<h3 className="text-sm font-semibold">主动推送</h3>
|
||||
{supported ? (
|
||||
<Notice tone="warn">主动推送(占位,后端待接):该适配器声明了主动推送能力,定时 / 主动消息功能待后端接入。</Notice>
|
||||
) : recordUnknown ? (
|
||||
<Notice tone="err">读取失败(与后端失联),无法确认该适配器是否支持主动推送;请先确认后端在线后重试。</Notice>
|
||||
) : (
|
||||
<Notice>当前适配器未声明主动推送能力(Proactive),不支持主动 / 定时消息;仅在被对话触发时响应。</Notice>
|
||||
)}
|
||||
|
||||
Reference in new issue
Block a user