feat(web): nav regroup, site RSS tab and site types (IA-2)

- sidebar: 概览 / 获取 / 整理 / 系统. Tasks moves to 系统; sites,
  subscriptions and rules form the 获取 group (ACQUIRE_GROUP_TITLE in
  nav.ts, the single place to rename it); standalone RSS nav item removed
- site detail gets an RSS tab with everything the RSS page had (create,
  edit, enable, fetch now, fetch state) plus recent items; tabs and
  header actions follow the site's capabilities
- old /console/sources links redirect to the site's RSS tab (?feed= looks
  up the owning site) or the site list; task links to RSS point at sites
- site list shows and filters by type; add-site wizard starts with a type
  step (PT still picks from the definition library; public BT / custom
  RSS source only need name, URL and RSS URL)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-09-30 00:29:56 +08:00
1 parent ec65435328
commit 614262378e
20 files changed
+772 -304

No files matched your search

+30 -11
View File
@@ -1,13 +1,16 @@
import type {
SiteAccount,
SiteAccountOverall,
SiteAdapter,
SiteAuthKind,
SiteDefinitionView,
SiteLastHealth,
SiteListItem,
SiteLoginFailureReason,
SiteRateLimit,
import {
SITE_KINDS,
type SiteAccount,
type SiteAccountOverall,
type SiteAdapter,
type SiteAuthKind,
type SiteCapability,
type SiteDefinitionView,
type SiteKind,
type SiteLastHealth,
type SiteListItem,
type SiteLoginFailureReason,
type SiteRateLimit,
} from "@umt/contracts";
import type { StatusTone } from "~/components/console/status-badge.tsx";
import { relTime } from "~/components/workbench/release-fields.ts";
@@ -22,6 +25,21 @@ export const ADAPTER_LABEL: Record<SiteAdapter, string> = {
generic_rss: "通用 RSS",
};
/** 站点类型名(IA-2);旧数据缺 kind 时按 PT 站 */
export function kindLabel(kind: SiteKind | undefined): string {
return SITE_KINDS[kind ?? "pt"].label;
}
/** 站点支持某种获取方式(服务端按类型 ∩ 站点定义算好的 capabilities) */
export function siteCan(site: Pick<SiteListItem, "capabilities">, cap: SiteCapability): boolean {
return (site.capabilities ?? []).includes(cap);
}
/** 有登录与账号(凭证、账号数据、刷新登录、检测健康)的站点 */
export function hasLogin(site: Pick<SiteListItem, "capabilities">): boolean {
return siteCan(site, "login");
}
export const ACCOUNT_OVERALL: Record<SiteAccountOverall, { label: string; tone: StatusTone }> = {
active: { label: "正常", tone: "success" },
expired: { label: "已失效", tone: "destructive" },
@@ -80,8 +98,9 @@ export function overallStatus(site: SiteListItem): SiteAccountOverall {
return acc?.enabled ? acc.status : "unconfigured";
}
/** 需处理:账号非正常(已配置)或健康故障 */
/** 需处理:账号非正常(已配置)或健康故障;没有登录的站点只看健康 */
export function needsAttention(site: SiteListItem): boolean {
if (!hasLogin(site)) return site.enabled && site.healthStatus === "failing";
const st = overallStatus(site);
return (
site.enabled && (st === "expired" || st === "invalid" || st === "challenge" || site.healthStatus === "failing")